Solution
`)
+ - Links to working code repositories (GitHub, CodeSandbox, Replit)
+
+ **Accessibility Checklist:**
+ - [ ] Alt text on all images
+ - [ ] Color not sole indicator (use labels + color)
+ - [ ] Code has sufficient contrast
+ - [ ] Headings are hierarchical (H → H → H)
+
+ ---
+
+ ## Behavior Rules
+
+ **Efficiency Heuristics:**
+
+ | Situation | Apply This Rule |
+ |-----------|-----------------|
+ | Reader stuck | Add checkpoint with expected state |
+ | Concept too abstract | Add analogy + concrete example |
+ | Exercise too hard | Add scaffolding (hints, partial solution) |
+ | Tutorial too long | Split into Part , Part |
+ | Low engagement | Add story, real-world scenario |
+
+ - Ground every explanation in actual code or examples. Do not theorize without demonstration.
+ - Assume the reader is intelligent but unfamiliar with this specific topic.
+ - Do not skip steps that seem obvious to you (expert blind spot).
+ - Do not recommend external resources as a substitute for explaining core concepts.
+ - If a concept requires extensive background, provide a "Quick Primer" section or link.
+ - Test all code examples before including them (or mark as "pseudocode").
+
+ **Calibration by Audience:**
+
+ | Audience | Adjustments |
+ |----------|-------------|
+ | Beginners | More analogies, smaller steps, more exercises, hand-holding setup |
+ | Intermediate | Assume basics, focus on patterns and best practices |
+ | Advanced | Skip introductions, dive into edge cases and optimization |
+ | Mixed | Provide "Skip Ahead" and "Need More Context?" callout boxes |
+
+ **Common Pitfalls to Avoid:**
+
+ | Pitfall | Fix |
+ |---------|-----|
+ | Wall of text | Break into steps with headings |
+ | Mystery code | Explain every non-obvious line |
+ | Broken examples | Test before publishing |
+ | No exercises | Add exercise per - concepts |
+ | Unclear goals | State objectives at start of each section |
+ | Abrupt ending | Add summary + next steps |
+
+ ---
+
+ ## Task-Specific Inputs
+
+ Before creating a tutorial, if not already provided, ask:
+
+ . **Topic or Code**: What concept, feature, or codebase should the tutorial cover?
+ . **Target Audience**: Beginner, intermediate, or advanced developers? Any specific background assumptions?
+ . **Format Preference**: Quick start, deep dive, workshop, cookbook, or interactive lab?
+ . **Constraints**: Time limit, word count, specific tools/frameworks to use or avoid?
+ . **Distribution**: Where will this be published? (blog, docs, course platform, internal wiki)
+
+ **If context is missing, assume:**
+ - Audience: Intermediate developers (knows basics, new to this topic)
+ - Format: Deep dive (- minutes)
+ - Distribution: Technical blog or documentation
+ - Tools: Latest stable versions of mentioned frameworks
+
+ ---
+
+ ## Related Skills
+
+ - **schema-markup**: For adding structured data to tutorials for SEO.
+ - **analytics-tracking**: For measuring tutorial engagement and completion rates.
+ - **doc-coauthoring**: For expanding tutorials into full documentation.
+ - **code-explainer**: For generating detailed code comments and documentation.
+ - **example-generator**: For creating diverse code examples and edge cases.
+ - **quiz-builder**: For adding knowledge checks and assessments to tutorials.
diff --git a/web-app/public/skills/vibe-code-auditor/SKILL.md b/web-app/public/skills/vibe-code-auditor/SKILL.md
index d1e41c1f..ed7d0497 100644
--- a/web-app/public/skills/vibe-code-auditor/SKILL.md
+++ b/web-app/public/skills/vibe-code-auditor/SKILL.md
@@ -5,7 +5,7 @@ risk: safe
source: original
date_added: "2026-02-28"
metadata:
- version: 1.0.0
+ version: 2.0.0
---
# Vibe Code Auditor
@@ -39,6 +39,12 @@ Before beginning the audit, confirm the following. If any item is missing, state
- **Scope defined**: Identify whether the input is a snippet, single file, or multi-file system.
- **Context noted**: If no context was provided, state the assumptions made (e.g., "Assuming a web API backend with no specified scale requirements").
+**Quick Scan (first 60 seconds):**
+- Count files and lines of code
+- Identify language(s) and framework(s)
+- Spot obvious red flags: hardcoded secrets, bare excepts, TODOs, commented-out code
+- Note the entry point(s) and data flow direction
+
---
## Audit Dimensions
@@ -47,57 +53,128 @@ Evaluate the code across all seven dimensions below. For each finding, record: t
**Do not invent findings. Do not report issues you cannot substantiate from the code provided.**
+**Pattern Recognition Shortcuts:**
+Use these heuristics to accelerate detection:
+
+| Pattern | Likely Issue | Quick Check |
+|---------|-------------|-------------|
+| `eval()`, `exec()`, `os.system()` | Security critical | Search for these strings |
+| `except:` or `except Exception:` | Silent failures | Grep for bare excepts |
+| `password`, `secret`, `key`, `token` in code | Hardcoded credentials | Search + check if literal string |
+| `if DEBUG`, `debug=True` | Insecure defaults | Check config blocks |
+| Functions >50 lines | Maintainability risk | Count lines per function |
+| Nested `if` >3 levels | Complexity hotspot | Visual scan or cyclomatic check |
+| No tests in repo | Quality gap | Look for `test_` files |
+| Direct SQL string concat | SQL injection | Search for `f"SELECT` or `+ "SELECT` |
+| `requests.get` without timeout | Production risk | Check HTTP client calls |
+| `while True` without break | Unbounded loop | Search for infinite loops |
+
### 1. Architecture & Design
+**Quick checks:**
+- Can you identify the entry point in 10 seconds?
+- Are there clear boundaries between layers (API, business logic, data)?
+- Does any single file exceed 300 lines?
+
- Separation of concerns violations (e.g., business logic inside route handlers or UI components)
- God objects or monolithic modules with more than one clear responsibility
- Tight coupling between components with no abstraction boundary
- Missing or blurred system boundaries (e.g., database queries scattered across layers)
+- Circular dependencies or import cycles
+- No clear data flow or state management strategy
### 2. Consistency & Maintainability
+**Quick checks:**
+- Are similar operations named consistently? (search for `get`, `fetch`, `load` variations)
+- Do functions have single, clear purposes based on their names?
+- Is duplicated logic visible? (search for repeated code blocks)
+
- Naming inconsistencies (e.g., `get_user` vs `fetchUser` vs `retrieveUserData` for the same operation)
- Mixed paradigms without justification (e.g., OOP and procedural code interleaved arbitrarily)
-- Copy-paste logic that should be extracted into a shared function
+- Copy-paste logic that should be extracted into a shared function (3+ repetitions = extract)
- Abstractions that obscure rather than clarify intent
+- Inconsistent error handling patterns across modules
+- Magic numbers or strings without constants or configuration
### 3. Robustness & Error Handling
+**Quick checks:**
+- Does every external call (API, DB, file) have error handling?
+- Are there any bare `except:` blocks?
+- What happens if inputs are empty, null, or malformed?
+
- Missing input validation on entry points (HTTP handlers, CLI args, file reads)
- Bare `except` or catch-all error handlers that swallow failures silently
- Unhandled edge cases (empty collections, null/None returns, zero values)
- Code that assumes external services always succeed without fallback logic
+- No retry logic for transient failures (network, rate limits)
+- Missing timeouts on blocking operations (HTTP, DB, I/O)
+- No validation of data from external sources before use
### 4. Production Risks
+**Quick checks:**
+- Search for hardcoded URLs, IPs, or paths
+- Check for logging statements (or lack thereof)
+- Look for database queries in loops
+
- Hardcoded configuration values (URLs, credentials, timeouts, thresholds)
- Missing structured logging or observability hooks
- Unbounded loops, missing pagination, or N+1 query patterns
- Blocking I/O in async contexts or thread-unsafe shared state
- No graceful shutdown or cleanup on process exit
+- Missing health checks or readiness endpoints
+- No rate limiting or backpressure mechanisms
+- Synchronous operations in event-driven or async contexts
### 5. Security & Safety
+**Quick checks:**
+- Search for: `eval`, `exec`, `os.system`, `subprocess`
+- Look for: `password`, `secret`, `api_key`, `token` as string literals
+- Check for: `SELECT * FROM` + string concatenation
+- Verify: input sanitization before DB, shell, or file operations
+
- Unsanitized user input passed to databases, shells, file paths, or `eval`
- Credentials, API keys, or tokens present in source code or logs
- Insecure defaults (e.g., `DEBUG=True`, permissive CORS, no rate limiting)
- Trust boundary violations (e.g., treating external data as internal without validation)
+- SQL injection vulnerabilities (string concatenation in queries)
+- Path traversal risks (user input in file paths without validation)
+- Missing authentication or authorization checks on sensitive operations
+- Insecure deserialization (pickle, yaml.load without SafeLoader)
### 6. Dead or Hallucinated Code
+**Quick checks:**
+- Search for function/class definitions, then check for callers
+- Look for imports that seem unused
+- Check if referenced libraries match requirements.txt or package.json
+
- Functions, classes, or modules that are defined but never called
- Imports that do not exist in the declared dependencies
- References to APIs, methods, or fields that do not exist in the used library version
- Type annotations that contradict actual usage
- Comments that describe behavior inconsistent with the code
+- Unreachable code blocks (after `return`, `raise`, or `break` in all paths)
+- Feature flags or conditionals that are always true/false
### 7. Technical Debt Hotspots
+**Quick checks:**
+- Count function parameters (5+ = refactor candidate)
+- Measure nesting depth visually (4+ = refactor candidate)
+- Look for boolean flags controlling function behavior
+
- Logic that is correct today but will break under realistic load or scale
- Deep nesting (more than 3-4 levels) that obscures control flow
- Boolean parameter flags that change function behavior (use separate functions instead)
- Functions with more than 5-6 parameters without a configuration object
- Areas where a future requirement change would require modifying many unrelated files
+- Missing type hints in dynamically typed languages for complex functions
+- No documentation for public APIs or complex algorithms
+- Test coverage gaps for critical paths
---
@@ -105,12 +182,30 @@ Evaluate the code across all seven dimensions below. For each finding, record: t
Produce the audit report using exactly this structure. Do not omit sections. If a section has no findings, write "None identified."
+**Productivity Rules:**
+- Lead with the 3-5 most critical findings that would cause production failures
+- Group related issues (e.g., "3 locations with hardcoded credentials" instead of listing separately)
+- Provide copy-paste-ready fixes where possible (exact code snippets)
+- Use severity tags consistently: `[CRITICAL]`, `[HIGH]`, `[MEDIUM]`, `[LOW]`
+
---
### Audit Report
**Input:** [file name(s) or "code snippet"]
**Assumptions:** [list any assumptions made about context or environment]
+**Quick Stats:** [X files, Y lines of code, Z language/framework]
+
+#### Executive Summary (Read This First)
+
+In 3-5 bullets, state the most important findings that determine whether this code can go to production:
+
+```
+- [CRITICAL/HIGH] One-line summary of the most severe issue
+- [CRITICAL/HIGH] Second most severe issue
+- [MEDIUM] Notable pattern that will cause future problems
+- Overall: Deployable as-is / Needs fixes / Requires major rework
+```
#### Critical Issues (Must Fix Before Production)
@@ -124,6 +219,11 @@ Location: filename.py, line 42 (or "multiple locations" with examples)
Dimension: Architecture / Security / Robustness / etc.
Problem: One or two sentences explaining exactly what is wrong and why it is dangerous.
Fix: One or two sentences describing the minimum change required to resolve it.
+Code Fix (if applicable):
+```python
+# Before: problematic code
+# After: corrected version
+```
```
#### High-Risk Issues
@@ -152,23 +252,37 @@ Provide a score using the rubric below, then write 2-3 sentences justifying it w
| 71-85 | Production-viable with targeted fixes. Known risks are bounded. |
| 86-100 | Production-ready. Minor improvements only. |
-Score deductions:
+**Scoring Algorithm:**
-- Each Critical issue: -10 to -20 points depending on blast radius
-- Each High issue: -5 to -10 points
-- Pervasive maintainability debt (3+ Medium issues in one dimension): -5 points
+```
+Start at 100 points
+For each CRITICAL issue: -15 points (security: -20)
+For each HIGH issue: -8 points
+For each MEDIUM issue: -3 points
+For pervasive patterns (3+ similar issues): -5 additional points
+Floor: 0, Ceiling: 100
+```
#### Refactoring Priorities
List the top 3-5 changes in order of impact. Each item must reference a specific finding from above.
```
-1. [Priority] Fix title — addresses [CRITICAL/HIGH ref] — estimated effort: S/M/L
-2. ...
+1. [P1 - Blocker] Fix title — addresses [CRITICAL #1] — effort: S/M/L — impact: prevents [specific failure]
+2. [P2 - Blocker] Fix title — addresses [CRITICAL #2] — effort: S/M/L — impact: prevents [specific failure]
+3. [P3 - High] Fix title — addresses [HIGH #1] — effort: S/M/L — impact: improves [specific metric]
+4. [P4 - Medium] Fix title — addresses [MEDIUM #1] — effort: S/M/L — impact: reduces [specific debt]
+5. [P5 - Optional] Fix title — addresses [LOW #1] — effort: S/M/L — impact: nice-to-have
```
Effort scale: S = < 1 day, M = 1-3 days, L = > 3 days.
+**Quick Wins (fix in <1 hour):**
+List any issues that can be resolved immediately with minimal effort:
+```
+- [Issue name]: [one-line fix description]
+```
+
---
## Behavior Rules
@@ -180,6 +294,20 @@ Effort scale: S = < 1 day, M = 1-3 days, L = > 3 days.
- If the code is too small or too abstract to evaluate a dimension meaningfully, say so explicitly rather than generating generic advice.
- If you detect a potential security issue but cannot confirm it from the code alone (e.g., depends on framework configuration not shown), flag it as "unconfirmed — verify" rather than omitting or overstating it.
+**Efficiency Rules:**
+- Scan for critical patterns first (security, data loss, crashes) before deeper analysis
+- Group similar issues by pattern rather than listing each occurrence separately
+- Provide exact code fixes for critical/high issues when the solution is straightforward
+- Skip dimensions that are not applicable to the code size or type (state "Not applicable: [reason]")
+- Focus on issues that would cause production incidents, not theoretical concerns
+
+**Calibration:**
+- For snippets (<100 lines): Focus on security, robustness, and obvious bugs only
+- For single files (100-500 lines): Add architecture and maintainability checks
+- For multi-file systems (500+ lines): Full audit across all 7 dimensions
+- For production code: Emphasize security, observability, and failure modes
+- For prototypes: Emphasize scalability limits and technical debt
+
---
## Task-Specific Inputs
@@ -189,6 +317,12 @@ Before auditing, if not already provided, ask:
1. **Code or files**: Share the source code to audit. Accepted: single file, multiple files, directory listing, or snippet.
2. **Context** _(optional)_: Brief description of what the system does, its intended scale, deployment environment, and known constraints.
3. **Target environment** _(optional)_: Target runtime (e.g., production web service, CLI tool, data pipeline). Used to calibrate risk severity.
+4. **Known concerns** _(optional)_: Any specific areas you're worried about or want me to focus on.
+
+**If context is missing, assume:**
+- Language/framework is evident from the code
+- Deployment target is production web service (most common)
+- Scale expectations are moderate (100-1000 users) unless code suggests otherwise
---
@@ -197,3 +331,5 @@ Before auditing, if not already provided, ask:
- **schema-markup**: For adding structured data after code is production-ready.
- **analytics-tracking**: For implementing observability and measurement after audit is clean.
- **seo-forensic-incident-response**: For investigating production incidents after deployment.
+- **test-driven-development**: For adding test coverage to address robustness gaps.
+- **security-audit**: For deep-dive security analysis if critical vulnerabilities are found.
diff --git a/web-app/refresh-skills-plugin.js b/web-app/refresh-skills-plugin.js
index 2afdc2b4..f1aa5f32 100644
--- a/web-app/refresh-skills-plugin.js
+++ b/web-app/refresh-skills-plugin.js
@@ -7,9 +7,56 @@ import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const ROOT_DIR = path.resolve(__dirname, '..');
-const REPO_ZIP_URL = 'https://github.com/sickn33/antigravity-awesome-skills/archive/refs/heads/main.zip';
-function followRedirects(url, dest) {
+const UPSTREAM_REPO = 'https://github.com/sickn33/antigravity-awesome-skills.git';
+const UPSTREAM_NAME = 'upstream';
+const REPO_TAR_URL = 'https://github.com/sickn33/antigravity-awesome-skills/archive/refs/heads/main.tar.gz';
+const REPO_ZIP_URL = 'https://github.com/sickn33/antigravity-awesome-skills/archive/refs/heads/main.zip';
+const COMMITS_API_URL = 'https://api.github.com/repos/sickn33/antigravity-awesome-skills/commits/main';
+const SHA_FILE = path.join(__dirname, '.last-sync-sha');
+
+// ─── Utility helpers ───
+
+const MIME_TYPES = {
+ '.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript',
+ '.json': 'application/json', '.md': 'text/markdown', '.txt': 'text/plain',
+ '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
+ '.gif': 'image/gif', '.svg': 'image/svg+xml', '.ico': 'image/x-icon',
+ '.yaml': 'text/yaml', '.yml': 'text/yaml', '.xml': 'text/xml',
+ '.py': 'text/plain', '.sh': 'text/plain', '.bat': 'text/plain',
+};
+
+/** Check if git is available on this system. Cached after first check. */
+let _gitAvailable = null;
+function isGitAvailable() {
+ if (_gitAvailable !== null) return _gitAvailable;
+ try {
+ execSync('git --version', { stdio: 'ignore' });
+ // Also check we're inside a git repo
+ execSync('git rev-parse --git-dir', { cwd: ROOT_DIR, stdio: 'ignore' });
+ _gitAvailable = true;
+ } catch {
+ _gitAvailable = false;
+ }
+ return _gitAvailable;
+}
+
+/** Run a git command in the project root. */
+function git(cmd) {
+ return execSync(`git ${cmd}`, { cwd: ROOT_DIR, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
+}
+
+/** Ensure the upstream remote exists. */
+function ensureUpstream() {
+ const remotes = git('remote');
+ if (!remotes.split('\n').includes(UPSTREAM_NAME)) {
+ git(`remote add ${UPSTREAM_NAME} ${UPSTREAM_REPO}`);
+ console.log(`[Sync] Added upstream remote: ${UPSTREAM_REPO}`);
+ }
+}
+
+/** Download a file following HTTP redirects. */
+function downloadFile(url, dest) {
return new Promise((resolve, reject) => {
const file = fs.createWriteStream(dest);
const request = (url) => {
@@ -24,86 +71,36 @@ function followRedirects(url, dest) {
}
res.pipe(file);
file.on('finish', () => { file.close(); resolve(); });
- }).on('error', (err) => { fs.unlink(dest, () => {}); reject(err); });
+ }).on('error', (err) => { fs.unlink(dest, () => { }); reject(err); });
};
request(url);
});
}
-export default function refreshSkillsPlugin() {
- return {
- name: 'refresh-skills',
- configureServer(server) {
- server.middlewares.use('/api/refresh-skills', async (req, res) => {
- res.setHeader('Content-Type', 'application/json');
-
- const zipPath = path.join(ROOT_DIR, 'update.zip');
- const tempDir = path.join(ROOT_DIR, 'update_temp');
-
+/** Check latest commit SHA via GitHub API. */
+function checkRemoteSha() {
+ return new Promise((resolve) => {
+ https.get(COMMITS_API_URL, {
+ headers: { 'User-Agent': 'antigravity-skills-app', 'Accept': 'application/vnd.github.v3+json' },
+ }, (res) => {
+ let body = '';
+ res.on('data', (chunk) => { body += chunk; });
+ res.on('end', () => {
try {
- // 1. Download the ZIP
- console.log('[Sync] Downloading latest skills from GitHub...');
- await followRedirects(REPO_ZIP_URL, zipPath);
-
- // 2. Extract with PowerShell (Windows) or unzip (Unix)
- console.log('[Sync] Extracting archive...');
- if (fs.existsSync(tempDir)) fs.rmSync(tempDir, { recursive: true, force: true });
-
- if (process.platform === 'win32') {
- execSync(`powershell -Command "Expand-Archive -Path '${zipPath}' -DestinationPath '${tempDir}' -Force"`, { stdio: 'ignore' });
+ if (res.statusCode === 200) {
+ resolve(JSON.parse(body).sha || null);
} else {
- fs.mkdirSync(tempDir, { recursive: true });
- execSync(`unzip -o "${zipPath}" -d "${tempDir}"`, { stdio: 'ignore' });
+ resolve(null);
}
-
- // 3. Copy skills folder + index
- const extractedRoot = path.join(tempDir, 'antigravity-awesome-skills-main');
- const srcSkills = path.join(extractedRoot, 'skills');
- const srcIndex = path.join(extractedRoot, 'skills_index.json');
- const destSkills = path.join(ROOT_DIR, 'skills');
- const destIndex = path.join(ROOT_DIR, 'skills_index.json');
-
- if (!fs.existsSync(srcSkills)) {
- throw new Error('Skills folder not found in downloaded archive.');
- }
-
- // Replace skills folder
- if (fs.existsSync(destSkills)) fs.rmSync(destSkills, { recursive: true, force: true });
- copyFolderSync(srcSkills, destSkills);
-
- // Replace index
- if (fs.existsSync(srcIndex)) fs.copyFileSync(srcIndex, destIndex);
-
- // 4. Re-run setup_web.js logic to update web-app/public
- const webPublic = path.join(__dirname, 'public');
- const webSkills = path.join(webPublic, 'skills');
- const webIndex = path.join(webPublic, 'skills.json');
-
- if (fs.existsSync(webSkills)) fs.rmSync(webSkills, { recursive: true, force: true });
- copyFolderSync(destSkills, webSkills);
- if (fs.existsSync(destIndex)) fs.copyFileSync(destIndex, webIndex);
-
- // Count skills
- const skillsData = JSON.parse(fs.readFileSync(webIndex, 'utf-8'));
- const count = Array.isArray(skillsData) ? skillsData.length : 0;
-
- console.log(`[Sync] ✅ Successfully synced ${count} skills!`);
- res.end(JSON.stringify({ success: true, count }));
-
- } catch (err) {
- console.error('[Sync] ❌ Failed:', err.message);
- res.statusCode = 500;
- res.end(JSON.stringify({ success: false, error: err.message }));
- } finally {
- // Cleanup
- if (fs.existsSync(zipPath)) fs.unlinkSync(zipPath);
- if (fs.existsSync(tempDir)) fs.rmSync(tempDir, { recursive: true, force: true });
+ } catch {
+ resolve(null);
}
});
- }
- };
+ }).on('error', () => resolve(null));
+ });
}
+/** Copy folder recursively. */
function copyFolderSync(from, to) {
if (!fs.existsSync(to)) fs.mkdirSync(to, { recursive: true });
for (const element of fs.readdirSync(from)) {
@@ -116,3 +113,186 @@ function copyFolderSync(from, to) {
}
}
}
+
+// ─── Sync strategies ───
+
+/**
+ * FAST PATH: Use git fetch + merge (only downloads delta).
+ * Typically completes in 5-15 seconds.
+ */
+async function syncWithGit() {
+ ensureUpstream();
+
+ const headBefore = git('rev-parse HEAD');
+
+ console.log('[Sync] Fetching from upstream (git)...');
+ git(`fetch ${UPSTREAM_NAME} main`);
+
+ const upstreamHead = git(`rev-parse ${UPSTREAM_NAME}/main`);
+
+ if (headBefore === upstreamHead) {
+ return { upToDate: true };
+ }
+
+ console.log('[Sync] Merging updates...');
+ try {
+ git(`merge ${UPSTREAM_NAME}/main --ff-only`);
+ } catch {
+ console.log('[Sync] Fast-forward failed, resetting to upstream...');
+ git(`reset --hard ${UPSTREAM_NAME}/main`);
+ }
+
+ return { upToDate: false };
+}
+
+/**
+ * FALLBACK: Download archive when git is not available.
+ * Tries tar.gz first (faster), falls back to zip if tar isn't available.
+ */
+async function syncWithArchive() {
+ // Check SHA first to skip if up to date
+ const remoteSha = await checkRemoteSha();
+ if (remoteSha) {
+ let storedSha = null;
+ if (fs.existsSync(SHA_FILE)) {
+ storedSha = fs.readFileSync(SHA_FILE, 'utf-8').trim();
+ }
+ if (storedSha === remoteSha) {
+ return { upToDate: true };
+ }
+ }
+
+ const tempDir = path.join(ROOT_DIR, 'update_temp');
+
+ // Try tar first, fall back to zip
+ let useTar = true;
+ try {
+ execSync('tar --version', { stdio: 'ignore' });
+ } catch {
+ useTar = false;
+ }
+
+ const archivePath = path.join(ROOT_DIR, useTar ? 'update.tar.gz' : 'update.zip');
+
+ try {
+ // 1. Download
+ console.log(`[Sync] Downloading (${useTar ? 'tar.gz' : 'zip'})...`);
+ await downloadFile(useTar ? REPO_TAR_URL : REPO_ZIP_URL, archivePath);
+
+ // 2. Extract
+ console.log('[Sync] Extracting...');
+ if (fs.existsSync(tempDir)) fs.rmSync(tempDir, { recursive: true, force: true });
+ fs.mkdirSync(tempDir, { recursive: true });
+
+ if (useTar) {
+ execSync(`tar -xzf "${archivePath}" -C "${tempDir}"`, { stdio: 'ignore' });
+ } else if (process.platform === 'win32') {
+ execSync(`powershell -Command "Expand-Archive -Path '${archivePath}' -DestinationPath '${tempDir}' -Force"`, { stdio: 'ignore' });
+ } else {
+ execSync(`unzip -o "${archivePath}" -d "${tempDir}"`, { stdio: 'ignore' });
+ }
+
+ // 3. Move skills to root
+ const extractedRoot = path.join(tempDir, 'antigravity-awesome-skills-main');
+ const srcSkills = path.join(extractedRoot, 'skills');
+ const srcIndex = path.join(extractedRoot, 'skills_index.json');
+ const destSkills = path.join(ROOT_DIR, 'skills');
+ const destIndex = path.join(ROOT_DIR, 'skills_index.json');
+
+ if (!fs.existsSync(srcSkills)) {
+ throw new Error('Skills folder not found in downloaded archive.');
+ }
+
+ console.log('[Sync] Updating skills...');
+ if (fs.existsSync(destSkills)) fs.rmSync(destSkills, { recursive: true, force: true });
+ fs.renameSync(srcSkills, destSkills);
+ if (fs.existsSync(srcIndex)) fs.copyFileSync(srcIndex, destIndex);
+
+ // Save SHA
+ if (remoteSha) fs.writeFileSync(SHA_FILE, remoteSha, 'utf-8');
+
+ return { upToDate: false };
+
+ } finally {
+ if (fs.existsSync(archivePath)) fs.unlinkSync(archivePath);
+ if (fs.existsSync(tempDir)) fs.rmSync(tempDir, { recursive: true, force: true });
+ }
+}
+
+// ─── Vite Plugin ───
+
+export default function refreshSkillsPlugin() {
+ return {
+ name: 'refresh-skills',
+ configureServer(server) {
+ // Serve /skills.json directly from ROOT_DIR
+ server.middlewares.use('/skills.json', (req, res, next) => {
+ const filePath = path.join(ROOT_DIR, 'skills_index.json');
+ if (fs.existsSync(filePath)) {
+ res.setHeader('Content-Type', 'application/json');
+ fs.createReadStream(filePath).pipe(res);
+ } else {
+ next();
+ }
+ });
+
+ // Serve /skills/* directly from ROOT_DIR/skills/
+ server.middlewares.use((req, res, next) => {
+ if (!req.url || !req.url.startsWith('/skills/')) return next();
+
+ const relativePath = decodeURIComponent(req.url.replace(/\?.*$/, ''));
+ const filePath = path.join(ROOT_DIR, relativePath);
+
+ const resolved = path.resolve(filePath);
+ if (!resolved.startsWith(path.join(ROOT_DIR, 'skills'))) return next();
+
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
+ const ext = path.extname(filePath).toLowerCase();
+ res.setHeader('Content-Type', MIME_TYPES[ext] || 'application/octet-stream');
+ fs.createReadStream(filePath).pipe(res);
+ } else {
+ next();
+ }
+ });
+
+ // Sync API endpoint
+ server.middlewares.use('/api/refresh-skills', async (req, res) => {
+ res.setHeader('Content-Type', 'application/json');
+
+ try {
+ let result;
+
+ if (isGitAvailable()) {
+ console.log('[Sync] Using git (fast path)...');
+ result = await syncWithGit();
+ } else {
+ console.log('[Sync] Git not available, using archive download (slower)...');
+ result = await syncWithArchive();
+ }
+
+ if (result.upToDate) {
+ console.log('[Sync] ✅ Already up to date!');
+ res.end(JSON.stringify({ success: true, upToDate: true }));
+ return;
+ }
+
+ // Count skills
+ const indexPath = path.join(ROOT_DIR, 'skills_index.json');
+ let count = 0;
+ if (fs.existsSync(indexPath)) {
+ const data = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
+ count = Array.isArray(data) ? data.length : 0;
+ }
+
+ console.log(`[Sync] ✅ Successfully synced ${count} skills!`);
+ res.end(JSON.stringify({ success: true, upToDate: false, count }));
+
+ } catch (err) {
+ console.error('[Sync] ❌ Failed:', err.message);
+ res.statusCode = 500;
+ res.end(JSON.stringify({ success: false, error: err.message }));
+ }
+ });
+ }
+ };
+}
diff --git a/web-app/src/pages/Home.jsx b/web-app/src/pages/Home.jsx
index 1d9dc10f..1b8ed1df 100644
--- a/web-app/src/pages/Home.jsx
+++ b/web-app/src/pages/Home.jsx
@@ -130,7 +130,9 @@ export function Home() {
{syncMsg && (
{syncMsg.text}
@@ -144,12 +146,16 @@ export function Home() {
const res = await fetch('/api/refresh-skills');
const data = await res.json();
if (data.success) {
- setSyncMsg({ type: 'success', text: `✅ Synced ${data.count} skills!` });
- // Reload skills data
- const freshRes = await fetch('/skills.json');
- const freshData = await freshRes.json();
- setSkills(freshData);
- setFilteredSkills(freshData);
+ if (data.upToDate) {
+ setSyncMsg({ type: 'info', text: 'ℹ️ Skills are already up to date!' });
+ } else {
+ setSyncMsg({ type: 'success', text: `✅ Synced ${data.count} skills!` });
+ // Reload skills data only when there are actual updates
+ const freshRes = await fetch('/skills.json');
+ const freshData = await freshRes.json();
+ setSkills(freshData);
+ setFilteredSkills(freshData);
+ }
} else {
setSyncMsg({ type: 'error', text: `❌ ${data.error}` });
}