/agent-skill-creator — Level 5 Skill Dark Factory You are an autonomous skill factory. You exist because humans are cognitively incapable of writing specifications clear enough for an agent to build from without intervention. A human-written spec will never reach Level 5 — it will always be incomplete, ambiguous, and missing the requirements the human assumed were obvious. That is not a flaw to fix. That is the design constraint this factory is built around. The user provides raw material — workflow descriptions, documentation, links, existing code, API docs, PDFs, database schemas, transcrip…

, name) and len(name) > 1:\n errors.append(\"Name must start/end with letter or number, contain only a-z, 0-9, hyphens\")\n if '--' in name:\n errors.append(\"Consecutive hyphens not allowed\")\n if name.endswith('-cskill'):\n errors.append(\"The -cskill suffix is deprecated; use -skill instead\")\n if not name.endswith('-skill') and not name.endswith('-suite'):\n errors.append(\"Name must end with '-skill' (or '-suite' for multi-skill suites)\")\n return (len(errors) == 0, errors)\n```\n\n---\n\n## 5. Directory Sizing Patterns\n\nChoose a sizing pattern based on the skill's complexity. These patterns apply to both Simple Skills and individual components within a Complex Suite.\n\n### 5.1 Small Agent Pattern\n\n**When to use:** Single workflow, 1-2 scripts, \u003c500 total lines of code.\n\n```\nskill-name/\n├── SKILL.md # \u003c200 lines\n├── scripts/\n│ └── main.py # 200-400 lines, single entry point\n├── references/\n│ └── guide.md # API docs, methodology\n├── assets/\n│ └── config.json # Minimal configuration\n├── install.sh\n└── README.md\n```\n\n**Characteristics:**\n- One main script handles the entire workflow\n- Minimal configuration\n- Single reference document\n- Estimated total: 500-800 lines across all files\n\n**Examples:** markdown-table-formatter, url-shortener, json-validator\n\n### 5.2 Medium Agent Pattern\n\n**When to use:** 2-3 workflows, 3-5 scripts, 500-2000 total lines of code.\n\n```\nskill-name/\n├── SKILL.md # 200-400 lines\n├── scripts/\n│ ├── fetch.py # Data acquisition (200-300 lines)\n│ ├── parse.py # Data processing (150-200 lines)\n│ ├── analyze.py # Analysis logic (300-500 lines)\n│ └── utils/\n│ ├── cache.py # Cache management (100-150 lines)\n│ └── validators.py # Input validation (100-150 lines)\n├── references/\n│ ├── api-guide.md # ~1500 words\n│ └── methodology.md # ~2000 words\n├── assets/\n│ └── config.json\n├── install.sh\n└── README.md\n```\n\n**Characteristics:**\n- Separation of concerns: fetch, parse, analyze\n- Utility modules for cross-cutting concerns (caching, validation)\n- Multiple reference documents\n- Estimated total: 1000-2500 lines across all files\n\n**Examples:** stock-analyzer, weather-dashboard, csv-data-cleaner\n\n### 5.3 Large Agent Pattern\n\n**When to use:** 3+ workflows within a single domain, 6+ scripts, 2000+ total lines of code. Still a Simple Skill if all workflows share a single domain and data pipeline.\n\n```\nskill-name/\n├── SKILL.md # 400-500 lines (at the limit)\n├── scripts/\n│ ├── core/\n│ │ ├── fetch_source_a.py # 200-300 lines\n│ │ ├── fetch_source_b.py # 200-300 lines\n│ │ ├── parse_source_a.py # 150-200 lines\n│ │ ├── parse_source_b.py # 150-200 lines\n│ │ └── analyze.py # 400-600 lines\n│ ├── models/\n│ │ ├── forecasting.py # 200-300 lines\n│ │ └── ml_models.py # 200-300 lines\n│ └── utils/\n│ ├── cache_manager.py # 100-150 lines\n│ ├── rate_limiter.py # 100-150 lines\n│ └── validators.py # 100-150 lines\n├── references/\n│ ├── api/\n│ │ ├── source-a-guide.md\n│ │ └── source-b-guide.md\n│ ├── methods/\n│ │ └── analysis-methods.md\n│ └── troubleshooting.md\n├── assets/\n│ ├── config.json\n│ └── metadata.json\n├── install.sh\n└── README.md\n```\n\n**Characteristics:**\n- Sub-directories within `scripts/` for organization (core, models, utils)\n- Multiple data sources with dedicated fetch/parse scripts\n- Dedicated models directory for analysis/ML logic\n- Organized reference documentation\n- Estimated total: 2500-5000 lines across all files\n\n**Examples:** nass-usda-agriculture, conab-crop-yield-analysis, noaa-climate-analysis\n\n### 5.4 Sizing Comparison Table\n\n| Aspect | Small | Medium | Large |\n|--------|-------|--------|-------|\n| Total code lines | \u003c500 | 500-2000 | 2000+ |\n| Script files | 1 | 3-5 | 6+ |\n| Script sub-dirs | None | `utils/` | `core/`, `models/`, `utils/` |\n| Reference files | 1 | 2-3 | 4+ (may use sub-dirs) |\n| Asset files | 0-1 | 1 | 2+ |\n| SKILL.md length | \u003c200 lines | 200-400 lines | 400-500 lines |\n| Typical domains | Formatters, validators | Data analyzers, dashboards | Multi-source analysis, forecasting |\n\n---\n\n## 6. Performance Strategy\n\nAll generated skills should incorporate performance considerations appropriate to their size and use case.\n\n### 6.1 Caching Strategy\n\nCache API responses and computed results to avoid redundant work and reduce API usage.\n\n**Cache TTL Decision Logic:**\n\n| Data Type | TTL | Rationale |\n|-----------|-----|-----------|\n| Historical data (past years) | 365 days (effectively permanent) | Historical data does not change |\n| Current-year data | 7 days | May be revised/updated |\n| Metadata (lists, enums) | 365 days | Rarely changes |\n| Real-time data | 1-60 minutes | Freshness required |\n| User preferences | Session-scoped | Per-execution only |\n\n**Implementation Pattern:**\n\n```python\nimport json\nimport hashlib\nfrom pathlib import Path\nfrom datetime import datetime, timedelta\n\nclass FileCache:\n \"\"\"Simple file-based cache with TTL support.\"\"\"\n\n def __init__(self, cache_dir: str = \"data/cache\"):\n self.cache_dir = Path(cache_dir)\n self.cache_dir.mkdir(parents=True, exist_ok=True)\n\n def _key_path(self, key: str) -> Path:\n hashed = hashlib.sha256(key.encode()).hexdigest()[:16]\n return self.cache_dir / f\"{hashed}.json\"\n\n def get(self, key: str, ttl: timedelta) -> dict | None:\n path = self._key_path(key)\n if not path.exists():\n return None\n data = json.loads(path.read_text())\n cached_at = datetime.fromisoformat(data[\"cached_at\"])\n if datetime.now() - cached_at > ttl:\n return None # Expired\n return data[\"value\"]\n\n def set(self, key: str, value: dict) -> None:\n path = self._key_path(key)\n path.write_text(json.dumps({\n \"cached_at\": datetime.now().isoformat(),\n \"value\": value\n }, indent=2))\n\n def get_or_fetch(self, key: str, ttl: timedelta, fetch_fn) -> dict:\n cached = self.get(key, ttl)\n if cached is not None:\n return cached\n value = fetch_fn()\n self.set(key, value)\n return value\n```\n\n**Cache Location:** Store cache files under `data/cache/` within the skill directory. This keeps cache local and avoids polluting system directories.\n\n**Graceful Degradation:** If the cache file is corrupted or unreadable, log a warning and proceed without cache (fetch fresh data).\n\n### 6.2 Rate Limiting Strategy\n\nProtect against API rate limit exhaustion with proactive tracking.\n\n**Rate Limiter Pattern:**\n\n```python\nimport json\nfrom pathlib import Path\nfrom datetime import datetime, timedelta\n\nclass RateLimiter:\n \"\"\"File-based rate limiter with persistent counter.\"\"\"\n\n def __init__(\n self,\n max_requests: int,\n period: timedelta,\n counter_file: str = \"data/cache/rate_limit.json\"\n ):\n self.max_requests = max_requests\n self.period = period\n self.counter_file = Path(counter_file)\n self.counter_file.parent.mkdir(parents=True, exist_ok=True)\n\n def _load(self) -> dict:\n if not self.counter_file.exists():\n return {\"requests\": [], \"period_start\": datetime.now().isoformat()}\n return json.loads(self.counter_file.read_text())\n\n def _save(self, data: dict) -> None:\n self.counter_file.write_text(json.dumps(data, indent=2))\n\n def _prune_old(self, data: dict) -> dict:\n cutoff = (datetime.now() - self.period).isoformat()\n data[\"requests\"] = [r for r in data[\"requests\"] if r > cutoff]\n return data\n\n def allow_request(self) -> bool:\n data = self._prune_old(self._load())\n count = len(data[\"requests\"])\n if count >= self.max_requests:\n return False\n if count > self.max_requests * 0.9:\n remaining = self.max_requests - count\n print(f\"WARNING: Rate limit nearly reached ({count}/{self.max_requests}), {remaining} requests remaining\")\n return True\n\n def record_request(self) -> None:\n data = self._prune_old(self._load())\n data[\"requests\"].append(datetime.now().isoformat())\n self._save(data)\n```\n\n**Rate Limit Configuration:** Define rate limits in `assets/config.json` so they can be adjusted without code changes:\n\n```json\n{\n \"rate_limit\": {\n \"max_requests_per_day\": 1000,\n \"warn_threshold_percent\": 90\n }\n}\n```\n\n### 6.3 Optimization Techniques\n\n**For Small Agents:**\n- Keep it simple. A single script with basic caching is sufficient.\n- Avoid premature optimization.\n\n**For Medium Agents:**\n- File-based caching for API responses.\n- Rate limiter for external APIs.\n- Lazy loading of reference data (only load when a specific analysis is requested).\n\n**For Large Agents:**\n- All Medium optimizations, plus:\n- Batch API requests where the API supports it.\n- Parallel processing for independent data sources (use `concurrent.futures`).\n- Tiered caching: in-memory for hot data, file-based for cold data.\n- Progress reporting for long-running operations.\n\n**General Rules:**\n- Never make the same API call twice in a single execution -- always check cache first.\n- Use exponential backoff for transient API failures (start at 1 second, max 3 retries).\n- Log all API calls with timestamps for debugging rate limit issues.\n- Keep cached data in `data/cache/` and provide a way to clear it (`--clear-cache` flag or a function).\n\n### 6.4 Error Handling Strategy\n\nEvery script must handle errors gracefully:\n\n```python\nimport sys\nfrom pathlib import Path\n\ndef safe_api_call(url: str, params: dict, retries: int = 3) -> dict:\n \"\"\"Make an API call with retry logic and graceful error handling.\"\"\"\n import urllib.request\n import urllib.error\n import json\n import time\n\n for attempt in range(retries):\n try:\n query = \"&\".join(f\"{k}={v}\" for k, v in params.items())\n full_url = f\"{url}?{query}\" if params else url\n req = urllib.request.Request(full_url)\n with urllib.request.urlopen(req, timeout=30) as response:\n return json.loads(response.read().decode())\n except urllib.error.HTTPError as e:\n if e.code == 429: # Rate limited\n wait = 2 ** attempt\n print(f\"Rate limited. Retrying in {wait}s...\")\n time.sleep(wait)\n elif e.code >= 500: # Server error\n wait = 2 ** attempt\n print(f\"Server error ({e.code}). Retrying in {wait}s...\")\n time.sleep(wait)\n else:\n print(f\"HTTP error {e.code}: {e.reason}\")\n return {\"error\": str(e), \"code\": e.code}\n except urllib.error.URLError as e:\n print(f\"Network error: {e.reason}\")\n if attempt \u003c retries - 1:\n time.sleep(2 ** attempt)\n else:\n return {\"error\": f\"Network error after {retries} attempts: {e.reason}\"}\n except Exception as e:\n return {\"error\": f\"Unexpected error: {str(e)}\"}\n\n return {\"error\": f\"Failed after {retries} retries\"}\n```\n\n### 6.5 SKILL.md Size Management\n\nThe SKILL.md body must stay under 500 lines. Use progressive disclosure:\n\n| Content Type | Where It Goes |\n|-------------|---------------|\n| Activation triggers, overview, core workflow | `SKILL.md` body (required) |\n| API documentation, endpoint details | `references/api-guide.md` |\n| Analysis methodology, formulas | `references/methodology.md` |\n| Troubleshooting, FAQs | `references/troubleshooting.md` |\n| Domain context, terminology | `references/domain-context.md` |\n| Configuration schema documentation | `references/config-guide.md` |\n\nReference content from SKILL.md using `See references/filename.md for details.` directives. The agent will load referenced files on demand, reducing initial context consumption.\n\n---\n\n## 7. When to Refactor a Growing Skill\n\nSkills evolve. A simple skill that started at 500 lines can grow to 5000+ as the team adds analyses, data sources, and edge case handling. Recognize the signs early and refactor before the skill becomes unmaintainable.\n\n### 7.1 Signs It's Time to Refactor\n\n| Signal | What It Means |\n|--------|--------------|\n| SKILL.md approaching 500 lines | Body is stuffed — move content to references |\n| Total code exceeding 3000 lines | Single-domain skill is becoming unwieldy |\n| 3+ unrelated workflows emerging | The skill is doing too many different jobs |\n| Different people maintaining different parts | Ownership boundaries need to be explicit |\n| Users invoking the skill for fundamentally different tasks | The skill should be split into focused components |\n| New data sources that don't share the existing pipeline | Independent fetch/parse/analyze chains = independent skills |\n\n### 7.2 Refactoring Patterns\n\n**Pattern 1: Extract to References (lightest touch)**\n\nWhen the skill body is too long but the code is fine:\n\n```\nBefore: SKILL.md at 480 lines with inline methodology docs\nAfter: SKILL.md at 250 lines, references/methodology.md with the detail\n```\n\nThis is not a structural refactor — just progressive disclosure. Do this first.\n\n**Pattern 2: Extract Utility Module**\n\nWhen multiple scripts duplicate logic:\n\n```\nBefore: fetch.py has cache logic, analyze.py has cache logic\nAfter: utils/cache.py extracted, both scripts import from it\n```\n\n**Pattern 3: Split by Domain (simple → suite)**\n\nWhen the skill covers multiple independent domains:\n\n```\nBefore:\n financial-analyzer/\n scripts/\n stock_analysis.py # 800 lines\n portfolio_tracking.py # 600 lines\n tax_reporting.py # 500 lines\n\nAfter:\n financial-suite/\n skills/\n stock-analyzer/ # Independent skill\n portfolio-tracker/ # Independent skill\n tax-reporter/ # Independent skill\n shared/\n market_data_client.py # Shared API connection\n```\n\n**Pattern 4: Extract Shared Resources**\n\nWhen converting to a suite, identify code that multiple components need:\n\n1. API client code → `shared/api_client.py`\n2. Common data models → `shared/models.py`\n3. Utility functions (date handling, formatting) → `shared/utils.py`\n4. Configuration → `shared/config.json`\n\n### 7.3 Refactoring Decision Process\n\n```\nIs SKILL.md > 400 lines?\n → Yes: Extract to references (Pattern 1)\n → Still growing?\n ↓\nIs total code > 3000 lines with 3+ unrelated workflows?\n → Yes: Split into suite (Pattern 3)\n → No, but code is duplicated across scripts?\n → Extract utilities (Pattern 2)\n → No: Keep as large simple skill — not everything needs to be a suite\n```\n\n**Critical rule**: Do not split prematurely. Three similar scripts in one domain is better than a suite with three trivially small components. Only split when the workflows are genuinely independent — different data sources, different users, different maintenance cadences.\n\n### 7.4 Refactoring Checklist\n\n- [ ] Identified which pattern applies (1-4)\n- [ ] Each new component is independently functional\n- [ ] Shared resources extracted to `shared/` (not duplicated)\n- [ ] All SKILL.md files are \u003c500 lines\n- [ ] All component names follow kebab-case naming\n- [ ] install.sh updated to handle new structure\n- [ ] README.md updated with new structure\n- [ ] Validation passes on all components\n\n---\n\n## 8. Cross-Component Communication in Suites\n\nWhen a suite has multiple component skills, they need clear patterns for sharing code, data, and orchestration.\n\n### 8.1 The shared/ Directory\n\nThe `shared/` directory contains code that multiple components use. It is **not** a component skill — it has no SKILL.md and is never invoked directly.\n\n```\nsuite-name/\n├── shared/\n│ ├── api_client.py # Shared API connection + authentication\n│ ├── models.py # Shared data classes and type definitions\n│ ├── utils.py # Common utilities (date formatting, etc.)\n│ └── config.json # Shared configuration\n├── skills/\n│ ├── component-a/\n│ │ ├── SKILL.md\n│ │ └── scripts/\n│ │ └── analyze.py # imports from ../../shared/api_client.py\n│ └── component-b/\n│ ├── SKILL.md\n│ └── scripts/\n│ └── report.py # imports from ../../shared/api_client.py\n```\n\n### 8.2 Import Patterns\n\nComponents import from `shared/` using path manipulation:\n\n```python\nimport sys\nfrom pathlib import Path\n\n# Add shared/ to path\n_SUITE_ROOT = Path(__file__).resolve().parent.parent.parent\n_SHARED_DIR = _SUITE_ROOT / \"shared\"\nif str(_SHARED_DIR) not in sys.path:\n sys.path.insert(0, str(_SHARED_DIR))\n\nfrom api_client import SuiteAPIClient\nfrom utils import format_date, parse_currency\n```\n\n**Rules:**\n- Always use `Path(__file__).resolve()` for reliable path resolution\n- Add `shared/` to `sys.path` — do not copy files into each component\n- Import specific names, not `from shared import *`\n- Each component must still work if `shared/` provides enhanced functionality but is not strictly required (graceful degradation)\n\n### 8.3 Orchestration: Suite-Level SKILL.md\n\nThe suite-level SKILL.md is the router. When a user's query could match multiple components, the suite SKILL.md tells the agent how to decide:\n\n```markdown\n# /ecommerce-suite — E-commerce Intelligence\n\nYou are an e-commerce analytics coordinator. Route user queries\nto the right component skill based on intent:\n\n## Routing Logic\n\n| User Intent | Route To | Example Queries |\n|-------------|----------|-----------------|\n| Revenue, orders, conversion | /sales-monitor | \"What were last week's sales?\" |\n| Segments, cohorts, churn | /customer-analytics | \"Show customer retention by cohort\" |\n| Stock levels, reorder | /inventory-tracker | \"Which products need reordering?\" |\n| Executive summary, dashboard | /executive-reports | \"Generate the weekly executive report\" |\n\n## Cross-Component Workflows\n\nSome requests require multiple components:\n\n### Full Store Report\nWhen the user asks for a \"full report\" or \"store overview\":\n1. Invoke /sales-monitor for revenue summary\n2. Invoke /customer-analytics for retention metrics\n3. Invoke /inventory-tracker for stock alerts\n4. Invoke /executive-reports to compile everything into a single report\n\n### Churn Impact Analysis\nWhen the user asks about churn's revenue impact:\n1. Invoke /customer-analytics for churn rate and segments\n2. Invoke /sales-monitor for revenue by customer segment\n3. Combine: revenue at risk = churned segment revenue × churn rate\n```\n\n### 8.4 Data Flow Between Components\n\nComponents do not call each other's functions directly. Instead, they communicate through:\n\n1. **Shared data files**: Component A writes to `data/sales_summary.json`, Component B reads it\n2. **Shared API client**: Both components use the same `shared/api_client.py` to fetch data\n3. **Agent orchestration**: The agent (LLM) reads output from Component A and passes relevant parts to Component B\n\n**Anti-patterns to avoid:**\n- Component A importing Component B's scripts directly (creates tight coupling)\n- Components writing to each other's directories\n- Circular dependencies between components\n\n### 8.5 Component Independence Rule\n\nEach component must be independently functional. This means:\n\n- A component extracted from the suite and installed alone must still work\n- `shared/` utilities enhance performance (avoid duplicate API calls, consistent formatting) but are not hard requirements\n- If a component absolutely requires `shared/`, document this in its README.md\n- The suite-level install.sh must install `shared/` alongside all components\n\n---\n\n## 9. Versioning Strategy\n\n### 9.1 Semver for Skills\n\nSkills follow [Semantic Versioning](https://semver.org/):\n\n| Change Type | Version Bump | Examples |\n|------------|-------------|---------|\n| **Patch** (x.y.Z) | Bug fixes, typo corrections, minor doc improvements | Fix API timeout handling, correct calculation formula |\n| **Minor** (x.Y.0) | New analyses, new data sources, new output formats | Add trend analysis, support CSV export, add new API endpoint |\n| **Major** (X.0.0) | Breaking changes to inputs, outputs, or invocation | Change script arguments, rename skill, restructure output format |\n\n### 9.2 What Counts as Breaking\n\nA change is breaking if existing users of the skill would get different behavior or errors:\n\n| Breaking | Not Breaking |\n|----------|-------------|\n| Changing script CLI arguments | Adding new optional arguments |\n| Changing output JSON structure | Adding new fields to output |\n| Removing an analysis function | Adding new analysis functions |\n| Renaming the skill | Updating the description keywords |\n| Changing required environment variables | Adding optional environment variables |\n\n### 9.3 Suite Versioning\n\nSuite versions are independent of component versions:\n\n```\necommerce-suite/ version: 2.0.0 (added new component)\n├── sales-monitor/ version: 1.3.0 (3 minor updates since suite v1)\n├── customer-analytics/ version: 1.1.0 (1 minor update)\n├── inventory-tracker/ version: 2.0.0 (breaking change in its own output)\n└── executive-reports/ version: 1.0.0 (unchanged)\n```\n\n**Suite version bump rules:**\n\n| Change | Suite Version Bump |\n|--------|--------------------|\n| Bug fix in one component | No suite bump (component patch only) |\n| New capability in one component | No suite bump (component minor only) |\n| Breaking change in one component | Suite minor bump (warn users) |\n| Add new component to suite | Suite minor bump |\n| Remove component from suite | Suite major bump |\n| Restructure shared/ | Suite major bump |\n\n### 9.4 Version in Practice\n\nThe version lives in SKILL.md frontmatter:\n\n```yaml\nmetadata:\n version: 1.2.0\n```\n\nWhen publishing to the registry, `skill_registry.py` reads this version. Publishing the same name+version is rejected unless `--force` is used.\n\n**When to create a new skill vs. version an existing one:**\n\n| Situation | Action |\n|-----------|--------|\n| Same domain, improved implementation | Version bump (minor or major) |\n| Same domain, fundamentally different approach | New skill (e.g., `stock-analyzer-v2`) |\n| Different domain entirely | New skill |\n| Extending to cover adjacent domain | If tightly coupled: version bump. If independent: new skill or convert to suite |\n\n---\n\n## 10. Architecture Checklist\n\nUse this checklist before proceeding to implementation (Phase 5):\n\n### Decision\n\n- [ ] Determined Simple Skill vs Complex Suite\n- [ ] Justified the decision based on workflow count, code size, and domain scope\n- [ ] If suite: identified shared resources and component boundaries\n- [ ] If suite: designed orchestration logic (routing, cross-component workflows)\n\n### Naming\n\n- [ ] Name is 1-64 characters, kebab-case\n- [ ] Name matches the parent directory\n- [ ] No `-cskill` suffix\n- [ ] Name is descriptive and includes the primary domain\n- [ ] If suite: all component names are unique and follow kebab-case\n\n### Structure\n\n- [ ] Directory layout matches the chosen sizing pattern (Small/Medium/Large)\n- [ ] SKILL.md planned at \u003c500 lines\n- [ ] Scripts have clear separation of concerns\n- [ ] References planned for detailed content\n- [ ] `install.sh` included\n- [ ] `README.md` planned with multi-platform install instructions\n- [ ] No `marketplace.json` for Simple Skills\n- [ ] If Complex Suite with `marketplace.json`, only official fields used\n- [ ] If suite: shared/ directory planned with import patterns documented\n- [ ] If suite: each component is independently functional\n\n### Performance\n\n- [ ] Cache strategy defined (what to cache, TTL for each data type)\n- [ ] Rate limiting planned for external APIs\n- [ ] Error handling approach defined (retries, backoff, fallbacks)\n- [ ] SKILL.md size managed via progressive disclosure to `references/`\n\n### Dependencies\n\n- [ ] Dependency strategy decided (stdlib-only vs. third-party)\n- [ ] requirements.txt planned if third-party packages needed\n- [ ] No unnecessary heavy dependencies\n\n### Versioning\n\n- [ ] Initial version set (1.0.0)\n- [ ] Version bump rules understood (patch/minor/major)\n- [ ] If suite: component versions independent of suite version\n\n### Documentation\n\n- [ ] Architecture decisions documented\n- [ ] Script responsibilities defined (input, output, line count estimate)\n- [ ] Reference files planned (topic, estimated word count)\n- [ ] Asset files planned (config structure, metadata)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":35056,"content_sha256":"fa72a02d8b86dcb66090e5b56fe449073e87dc787598e4996e07a86825fe0b0b"},{"filename":"references/claude-artifact-format.md","content":"# Claude Code Artifact Emission Format\n\n**Status:** PENDING — Task 1 (M1) of the v6 plan has not yet been\nexecuted. This file exists as a stub so other docs that cite it do not\ndereference a missing path.\n\n## What this file will contain when M1 completes\n\nThe literal emission syntax that Claude Code recognizes as an in-chat\ninteractive React artifact, captured empirically by:\n\n1. Opening Claude Code in a fresh session\n2. Asking Claude to emit a minimal React artifact\n3. Capturing the exact text/tag/marker syntax verbatim\n4. Verifying that a second paste of the captured emission renders\n identically (round-trip proof)\n\nPlus:\n\n- Required and optional attributes (id, type, title, etc.)\n- Body format (raw JSX vs. wrapped, escaping rules)\n- Compatibility notes — which Claude Code version range was verified\n- Observed behavior in non-Claude hosts (fenced code, plain text, etc.)\n\n## Until then\n\nPhase 2 of agent-skill-creator MUST NOT inline artifact emission\ninstructions into generated skills, because the protocol is unverified.\nThe four bundled React templates under\n`references/artifact-templates/` can be assembled correctly — they are\nsyntactically valid React — but the runtime instruction to emit them\nusing \"Claude's artifact protocol\" has no concrete syntax to point at\nuntil this file is populated.\n\nIf you are following the plan in\n`docs/superpowers/plans/2026-05-27-agent-skill-creator-v5-artifacts-first.md`,\nTask 1 produces the content for this file. Do not skip it.\n\n## References\n\n- Spec: `docs/superpowers/specs/2026-05-27-agent-skill-creator-v5-artifacts-first-design.md`, §3.3 and §11 Q1-Q3\n- Plan: `docs/superpowers/plans/2026-05-27-agent-skill-creator-v5-artifacts-first.md`, Task 1\n- Phase 2 reference: `references/phase2-artifact-assessment.md`, Step 4\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":1796,"content_sha256":"4b8686a1da7d74a46428f349e09a7d5b36b50b41e253b986147bf3b2176f9a5e"},{"filename":"references/cross-platform-guide.md","content":"# Cross-Platform Compatibility Guide\n\n**Version:** 6.0\n**Purpose:** Research-backed compatibility matrix for Agent Skills across all platforms. Data sourced from agentic-tool-skill-systems research (27 tools analyzed, March 2026).\n\n---\n\n## Overview\n\nSkills created by agent-skill-creator output both **SKILL.md** (agentskills.io spec, ~15 tools) and **AGENTS.md** (AAIF-governed spec, ~15 tools) to maximize cross-tool reach. Together they cover 20+ tools across 3 support tiers.\n\n**Standards governance:**\n- **SKILL.md** — maintained by Anthropic (agentskills.io). Defines skill format, frontmatter schema, progressive disclosure.\n- **AGENTS.md** — governed by AAIF (Agentic AI Foundation, Linux Foundation). Defines project instruction files. Adopted by 15+ tools.\n- **MCP** — governed by AAIF. Model Context Protocol for tool extension. 97M+ SDK downloads.\n\n---\n\n## Tier 1 — Native SKILL.md Support\n\nThese platforms read SKILL.md natively with no conversion needed:\n\n| Platform | Type | Native Global Path | Native Project Path | Fallback Paths |\n|----------|------|--------------------|--------------------|----|\n| **Claude Code** | CLI | `~/.claude/skills/` | `.claude/skills/` | `.claude/commands/` (legacy) |\n| **GitHub Copilot** | CLI + IDE | `~/.copilot/skills/` | `.github/skills/`, `.claude/skills/` | Also reads `~/.claude/skills/` |\n| **Codex CLI** | CLI | `~/.agents/skills/` | `.agents/skills/` | Configurable |\n| **Gemini CLI** | CLI | `~/.gemini/skills/` | `.gemini/skills/` | `.agents/skills/` (alias) |\n| **Kiro** | IDE | `~/.kiro/skills/` | `.kiro/skills/` | Reads AGENTS.md |\n| **Goose** | CLI | `~/.config/goose/skills/` | `.goose/skills/`, `.agents/skills/`, `.claude/skills/` | Multiple tiers |\n| **OpenCode** | CLI | `~/.config/opencode/skills/` | `.opencode/skills/`, `.claude/skills/`, `.agents/skills/` | Walks git root |\n| **Cline** | VS Code Ext | `~/.cline/skills/` | `.clinerules/skills/`, `.agents/skills/` | `.clinerules/` (legacy) |\n| **Roo Code** | VS Code Ext | `~/.roo/skills/` | `.roo/skills/`, `.agents/skills/` | `.roorules`, `.clinerules` (legacy) |\n| **Kilo Code** | VS Code + JetBrains + CLI | `~/.kilocode/skills/` | `.kilocode/skills/` | `.roorules`, `.clinerules` (backward compat) |\n| **Factory Droid** | Enterprise CLI | `~/.factory/skills/` | `.factory/skills/` | `.agent/skills/` (legacy) |\n| **Antigravity** | IDE | `~/.gemini/antigravity/skills/` | `.agent/skills/` | Note: `.agent/` (singular, not `.agents/`) |\n\n## Tier 2 — SKILL.md via Format Adapter\n\nThese platforms use their own rule format. The installer auto-generates the native format from SKILL.md:\n\n| Platform | Type | Native Format | Adaptation | Install Path | Limitations |\n|----------|------|--------------|------------|-------------|-------------|\n| **Cursor** | IDE | `.mdc` | Generates `.mdc` with `alwaysApply`/`globs`/`description` frontmatter | `.cursor/skills/` (project only, **no global path**) | Per-project only; 4 activation modes (Always Apply, Agent Decides, Glob, Manual) |\n| **Windsurf** | IDE | `.md` rules | Generates plain `.md` rule file | `.windsurf/rules/` (project) or `~/.codeium/windsurf/` (global) | **6,000 char per-file limit, 12,000 total combined** |\n| **Trae** | IDE | `.md` rules | Generates plain `.md` with `type:` frontmatter | `.trae/rules/` | 4 modes: Always Apply, File-specific, Intelligent, Manual |\n| **Junie** | JetBrains | `guidelines.md` | Extracts body as plain markdown | `.junie/skills/` | Public catalog at github.com/JetBrains/junie-guidelines |\n\n## Tier 3 — Manual Integration\n\nThese platforms require manual setup:\n\n| Platform | Config File | Instructions | AGENTS.md? |\n|----------|------------|-------------|------------|\n| **Zed** | `.rules` file + Rules Library | Copy SKILL.md body into `.rules` at project root. Or add to Rules Library via UI. | YES (reads AGENTS.md) |\n| **Augment** | `.augment/rules/` | Copy as `.md` with `type: Always` or `type: Auto` frontmatter | YES (hierarchical AGENTS.md) |\n| **Aider** | `CONVENTIONS.md` | Copy SKILL.md body into CONVENTIONS.md. Configure `read:` in `.aider.conf.yml` | NO |\n| **Continue.dev** | `.continue/rules/` | Copy as `.md` with Continue-specific frontmatter (`alwaysApply`, `globs`, `regex`) | YES (recently added) |\n\n---\n\n## AGENTS.md — Companion Output\n\nEvery generated skill outputs an AGENTS.md alongside SKILL.md. This is the AAIF-governed instruction file format read by 15+ tools:\n\n**Tools that read AGENTS.md:** Codex CLI (primary), Cursor, Roo Code, Kilo Code, Kiro, Goose, OpenCode, Continue.dev, Factory Droid, Augment, Gemini Code Assist, GitHub Copilot, Windsurf, Zed, Antigravity\n\nThe AGENTS.md contains:\n- Skill purpose and description\n- Activation triggers and usage instructions\n- Reference to SKILL.md for full implementation details\n\nThis means a generated skill is discoverable by virtually every major tool — either via SKILL.md or AGENTS.md or both.\n\n---\n\n## Activation Mechanisms by Platform\n\nNot all platforms activate skills the same way:\n\n| Platform | Slash Command | Auto-Detect (Description) | File Pattern | Other |\n|----------|:---:|:---:|:---:|------|\n| Claude Code | `/skill-name` | Yes | — | Bundled skills auto-activate on SDK detection |\n| GitHub Copilot | `/skill-name` | Yes | `.instructions.md` applyTo | @agent-name, specialized agents |\n| Codex CLI | `/skills`, `$skill-name` | Yes | — | System skills |\n| Gemini CLI | `/command-name` | Yes | — | Custom commands via .toml |\n| Kiro | `/` invocation | Yes | fileMatch | 4 modes: always, auto, fileMatch, manual |\n| Cursor | Slash menu | Yes (Agent Decides) | Glob patterns | Always Apply, Manual @mention |\n| Windsurf | — | Yes | Glob patterns | Always On, Model Decision modes |\n| Cline | — | Yes (use_skill tool) | — | Always-on rules |\n| Roo Code | `/orchestrator`, `/code` | Yes | Mode rules | Mode switching |\n| Kilo Code | Mode commands | Yes | Within modes | 5 named modes |\n| Goose | — | Yes | — | \"Use the X skill\" |\n| OpenCode | — | Yes (skill() tool) | — | @mention for subagents |\n| Factory Droid | `/skill-name` | Yes | — | /droids menu |\n| Trae | — | Yes (Intelligent mode) | File-specific | `#Rule` syntax |\n| Zed | — | — | — | @mention (Rules Library) |\n| Augment | @mention | Yes (Auto type) | Auto mode | Always/Manual/Auto types |\n| Aider | `/read` only | — | — | Manual only |\n| Continue.dev | `/` slash commands | Yes | Globs, regex | alwaysApply: true |\n| Junie | Slash menu | Yes | For rules | Always-on for guidelines |\n\n---\n\n## Cross-Tool Path Sharing\n\nWhich paths are read by multiple tools:\n\n| Path | Tools That Read It |\n|------|-------------------|\n| `~/.claude/skills/` | Claude Code, GitHub Copilot (fallback), Cursor (fallback), OpenCode (fallback), Goose (fallback) |\n| `~/.agents/skills/` | Codex CLI (primary), Gemini CLI (alias), OpenCode, Goose, Cline (fallback), Roo Code (fallback), Kilo Code (fallback) |\n| `.agents/skills/` | Codex CLI, Gemini CLI, OpenCode, Goose, Cline, Roo Code |\n| `.claude/skills/` | Claude Code, GitHub Copilot, Cursor, OpenCode, Goose |\n| `AGENTS.md` (file) | 15+ tools (widest reach format) |\n| `SKILL.md` (file) | 15+ tools (when in tool's native skill path) |\n\n**Important:** `.agents/skills/` (plural) and `.agent/skills/` (singular, used by Antigravity) are different paths.\n\n---\n\n## Format Adapters\n\nThe installer automatically converts SKILL.md to platform-native formats when needed. No separate format files are committed to the skill repo — SKILL.md remains the single source of truth.\n\n### Cursor (.mdc)\n\nThe adapter generates a `.mdc` file with Cursor-specific frontmatter:\n\n```\n---\ndescription: \u003cextracted from SKILL.md frontmatter>\nglobs:\nalwaysApply: true\n---\n\u003cSKILL.md body without YAML frontmatter>\n```\n\n**Limitations:** Cursor has no global skills path. Skills must be installed per-project in `.cursor/skills/`.\n\n### Windsurf (.md rules)\n\n**Project-level**: Creates a `.md` file in `.windsurf/rules/`.\n\n**User-level (global)**: Appends to `~/.codeium/windsurf/memories/global_rules.md` with idempotent markers:\n\n```markdown\n\u003c!-- BEGIN skill-name -->\n\u003cSKILL.md body>\n\u003c!-- END skill-name -->\n```\n\n**Limitations:** 6,000 character limit per rule file, 12,000 total combined. Long skills must be truncated or split.\n\n### Trae (.md rules)\n\nGenerates plain `.md` with type frontmatter:\n\n```markdown\n---\ntype: Always\n---\n\u003cSKILL.md body>\n```\n\n### Cline / Roo Code / Kilo Code (plain .md)\n\nThe adapter strips YAML frontmatter and outputs plain markdown. These tools read `.md` files from their respective skill directories.\n\n### Junie (guidelines.md)\n\nExtracts SKILL.md body as plain markdown into `.junie/skills/` directory.\n\n---\n\n## Installation by Platform\n\n### Claude Code\n\n```bash\n# User-level (global)\ncp -r skill-name/ ~/.claude/skills/skill-name/\n\n# Project-level\ncp -r skill-name/ .claude/skills/skill-name/\n```\n\n### GitHub Copilot\n\n```bash\n# User-level (Copilot's native global path)\ncp -r skill-name/ ~/.copilot/skills/skill-name/\n\n# Project-level\ncp -r skill-name/ .github/skills/skill-name/\n```\n\nCopilot also reads `~/.claude/skills/` as a fallback, but its native path is `~/.copilot/skills/`.\n\n### Cursor\n\n```bash\n# Project-level ONLY (no global path exists)\ncp -r skill-name/ .cursor/skills/skill-name/\n```\n\nFor cross-project use, clone once and symlink per project:\n```bash\ngit clone \u003crepo> ~/agent-skills/skill-name\nmkdir -p .cursor/skills && ln -s ~/agent-skills/skill-name .cursor/skills/skill-name\n```\n\n### Codex CLI\n\n```bash\ncp -r skill-name/ ~/.agents/skills/skill-name/\n```\n\n### Gemini CLI\n\n```bash\n# Native path (preferred)\ncp -r skill-name/ ~/.gemini/skills/skill-name/\n\n# Also reads ~/.agents/skills/ as fallback\n```\n\n### Kiro\n\n```bash\n# Project-level\ncp -r skill-name/ .kiro/skills/skill-name/\n```\n\n### Windsurf\n\n```bash\n# Project-level\n./install.sh --platform windsurf --project\n\n# User-level (appends to global_rules.md)\n./install.sh --platform windsurf\n```\n\n### Cline\n\n```bash\ncp -r skill-name/ .clinerules/skills/skill-name/\n```\n\n### Roo Code\n\n```bash\ncp -r skill-name/ .roo/skills/skill-name/\n```\n\n### Kilo Code\n\n```bash\ncp -r skill-name/ .kilocode/skills/skill-name/\n```\n\n### Goose\n\n```bash\ncp -r skill-name/ ~/.config/goose/skills/skill-name/\n```\n\n### OpenCode\n\n```bash\ncp -r skill-name/ ~/.config/opencode/skills/skill-name/\n```\n\n### Trae\n\n```bash\n./install.sh --platform trae\n# Generates plain .md with type: frontmatter in .trae/rules/\n```\n\n### Junie\n\n```bash\n./install.sh --platform junie\n# Generates guidelines.md in .junie/skills/\n```\n\n### Factory Droid\n\n```bash\ncp -r skill-name/ ~/.factory/skills/skill-name/\n```\n\n### Antigravity\n\n```bash\ncp -r skill-name/ .agent/skills/skill-name/\n# Note: .agent/ (singular), NOT .agents/\n```\n\n### Universal Path\n\n```bash\ncp -r skill-name/ ~/.agents/skills/skill-name/\n```\n\nRead by: Codex CLI, Gemini CLI (fallback), OpenCode, Goose, Cline (fallback), Roo Code (fallback), Kilo Code (fallback).\n\n### Install All\n\n```bash\n./install.sh --all\n```\n\n### Alternative: npx\n\n```bash\nnpx skills add \u003crepo-url>\nnpx skills add ./local-skill-dir\n```\n\n### Claude Desktop / claude.ai (Web)\n\n```bash\npython scripts/export_utils.py ./skill-name --variant desktop\n# Upload the .zip via Settings > Skills\n```\n\n---\n\n## Compatibility Matrix\n\n### Format Support\n\n| Feature | Tier 1 | Tier 2 | Desktop/Web | Claude API |\n|---------|--------|--------|-------------|------------|\n| **SKILL.md** | Native | Via adapter | Full | Full |\n| **AGENTS.md** | Most tools | Some tools | N/A | N/A |\n| **Python scripts** | Full | Full | Full | Sandboxed* |\n| **References/docs** | Full | Full | Full | Full |\n| **install.sh** | Full | Full | N/A | N/A |\n\n\\* API: No network access, no pip install at runtime\n\n### Platform Limitations\n\n| Platform | Key Limitation |\n|----------|---------------|\n| **Cursor** | No global skills path — per-project only |\n| **Windsurf** | 6,000 char per-file limit, 12,000 total combined |\n| **Trae** | Does not read SKILL.md natively; requires format adapter |\n| **Zed** | No SKILL.md support; uses `.rules` file and Rules Library UI |\n| **Augment** | No SKILL.md support; uses `.augment/rules/` with type frontmatter |\n| **Aider** | No SKILL.md or auto-activation; manual CONVENTIONS.md only |\n| **Antigravity** | Uses `.agent/skills/` (singular), NOT `.agents/skills/` (plural) |\n\n---\n\n## Best Practices\n\n1. **Use each tool's native path**: Don't install Copilot skills to `~/.claude/`. Use `~/.copilot/skills/` for Copilot, `~/.gemini/skills/` for Gemini, etc.\n2. **Output both SKILL.md and AGENTS.md**: Maximizes reach across the entire ecosystem.\n3. **Use install.sh or `npx skills`**: Handles path detection and format conversion automatically.\n4. **Use `--all` for multi-tool users**: Install to every detected tool with a single command.\n5. **Keep SKILL.md lean**: Under 500 lines. Critical for Windsurf's 6K char limit.\n6. **Test activation on your target platform**: Description-based auto-detect works on ~15 tools. Slash commands on ~12. Manual activation needed for Zed, Aider.\n7. **No platform hacks**: Avoid platform-specific code. The standard format + adapters handle the rest.\n\n---\n\n**Generated by:** agent-skill-creator v6.0\n**Standards:** SKILL.md (agentskills.io), AGENTS.md (AAIF/Linux Foundation)\n**Data source:** agentic-tool-skill-systems research, 27 tools analyzed, March 2026\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":13326,"content_sha256":"4dd225d619a498f28ee2c34783a504eeaf5a54082e91d41c10c79cf138863a76"},{"filename":"references/export-guide.md","content":"# Cross-Platform Export Guide\n\n**Version:** 6.0\n**Purpose:** Complete guide to exporting agent-skill-creator skills for use across all Claude platforms\n\n---\n\n## 🎯 Overview\n\nSkills created by agent-skill-creator are optimized for **Claude Code**, but can be exported for use across all Claude platforms:\n\n- **Claude Code** (CLI) - Native directory-based format\n- **Claude Desktop** - Manual .zip file upload\n- **claude.ai** (Web) - Manual .zip file upload\n- **Claude API** - Programmatic .zip upload\n\nThis guide explains how to export skills for cross-platform compatibility.\n\n---\n\n## 📦 Why Export?\n\n### The Challenge\n\nDifferent Claude platforms use different distribution methods:\n\n| Platform | Installation Method | Requires Export? |\n|----------|-------------------|------------------|\n| Claude Code | Plugin/directory | ❌ No (native) |\n| Claude Desktop | .zip upload | ✅ Yes |\n| claude.ai | .zip upload | ✅ Yes |\n| Claude API | Programmatic upload | ✅ Yes |\n\n### The Solution\n\nThe export system creates **optimized .zip packages** with:\n- ✅ Platform-specific optimization\n- ✅ Version numbering\n- ✅ Automatic validation\n- ✅ Installation guides\n- ✅ Size optimization\n\n---\n\n## 🚀 Quick Start\n\n### Automatic Export (Recommended)\n\nAfter creating a skill, agent-skill-creator will prompt:\n\n```\n✅ Skill created: financial-analysis/\n\n📦 Export Options:\n 1. Desktop/Web (.zip for manual upload)\n 2. API (.zip for programmatic use)\n 3. Both (comprehensive package)\n 4. Skip (Claude Code only)\n\nChoice: 3\n\n🔨 Creating export packages...\n✅ Desktop package: exports/financial-analysis-desktop-v1.0.0.zip\n✅ API package: exports/financial-analysis-api-v1.0.0.zip\n📄 Installation guide: exports/financial-analysis-v1.0.0_INSTALL.md\n```\n\n### On-Demand Export\n\nExport any existing skill anytime:\n\n```\n\"Export stock-analyzer for Desktop and API\"\n\"Package financial-analysis for claude.ai\"\n\"Create API export for climate-analyzer with version 2.1.0\"\n```\n\n### Manual Export (Advanced)\n\nUsing the export script directly:\n\n```bash\n# Export both variants\npython scripts/export_utils.py ./my-skill\n\n# Export only Desktop variant\npython scripts/export_utils.py ./my-skill --variant desktop\n\n# Export with specific version\npython scripts/export_utils.py ./my-skill --version 2.0.1\n\n# Export to custom directory\npython scripts/export_utils.py ./my-skill --output-dir ./dist\n```\n\n---\n\n## 📊 Export Variants\n\n### Desktop/Web Package (`*-desktop-*.zip`)\n\n**Optimized for:** Claude Desktop and claude.ai manual upload\n\n**Includes:**\n- ✅ Complete SKILL.md\n- ✅ All scripts/\n- ✅ Full references/ documentation\n- ✅ All assets/ and templates\n- ✅ README.md\n- ✅ requirements.txt\n\n**Excludes:**\n- ❌ .claude-plugin/ (not used by Desktop/Web)\n- ❌ .git/ (version control not needed)\n- ❌ Development artifacts\n\n**Typical Size:** 2-5 MB\n\n**Use when:**\n- Sharing with Desktop users\n- Uploading to claude.ai\n- Need full documentation\n\n### API Package (`*-api-*.zip`)\n\n**Optimized for:** Programmatic Claude API integration\n\n**Includes:**\n- ✅ SKILL.md (required)\n- ✅ Essential scripts only\n- ✅ Critical references only\n- ✅ requirements.txt\n\n**Excludes:**\n- ❌ .claude-plugin/ (not used by API)\n- ❌ .git/ (not needed)\n- ❌ Heavy documentation files\n- ❌ Example files (size optimization)\n- ❌ Large reference materials\n\n**Typical Size:** 0.5-2 MB (\u003c 8MB limit)\n\n**Use when:**\n- Integrating with API\n- Need size optimization\n- Programmatic deployment\n- Execution-focused use\n\n---\n\n## 🔍 Version Management\n\n### Auto-Detection\n\nThe export system automatically detects versions from:\n\n1. **Git tags** (highest priority)\n ```bash\n git tag v1.0.0\n # Export will use v1.0.0\n ```\n\n2. **SKILL.md frontmatter**\n ```yaml\n ---\n name: my-skill\n version: 1.2.3\n ---\n ```\n\n3. **Default fallback**\n - If no version found: `v1.0.0`\n\n### Manual Override\n\nSpecify version explicitly:\n\n```bash\n# Via CLI\npython scripts/export_utils.py ./my-skill --version 2.1.0\n\n# Via natural language\n\"Export my-skill with version 3.0.0\"\n```\n\n### Versioning Best Practices\n\nFollow semantic versioning (MAJOR.MINOR.PATCH):\n\n- **MAJOR (X.0.0)**: Breaking changes to skill behavior\n- **MINOR (x.X.0)**: New features, backward compatible\n- **PATCH (x.x.X)**: Bug fixes, optimizations\n\n**Examples:**\n- `v1.0.0` → Initial release\n- `v1.1.0` → Added new analysis feature\n- `v1.1.1` → Fixed calculation bug\n- `v2.0.0` → Changed API interface (breaking)\n\n---\n\n## ✅ Validation\n\n### Automatic Validation\n\nEvery export is validated for:\n\n**Structure Checks:**\n- ✅ SKILL.md exists\n- ✅ SKILL.md has valid frontmatter\n- ✅ Frontmatter has `name:` field\n- ✅ Frontmatter has `description:` field\n\n**Content Checks:**\n- ✅ Name ≤ 64 characters\n- ✅ Description ≤ 1024 characters\n- ✅ No sensitive files (.env, credentials.json)\n\n**Size Checks:**\n- ✅ Desktop package: reasonable size\n- ✅ API package: \u003c 8MB (hard limit)\n\n### Validation Failures\n\nIf validation fails, you'll see detailed error messages:\n\n```\n❌ Export failed!\n\nIssues found:\n - SKILL.md missing 'name:' field in frontmatter\n - description too long: 1500 chars (max 1024)\n - API package too large: 9.2 MB (max 8 MB)\n```\n\n**Common fixes:**\n- Add missing frontmatter fields\n- Shorten description to ≤ 1024 chars\n- Remove large files for API variant\n- Check SKILL.md formatting\n\n---\n\n## 📁 Output Organization\n\n### Directory Structure\n\n```\nexports/\n├── skill-name-desktop-v1.0.0.zip\n├── skill-name-api-v1.0.0.zip\n├── skill-name-v1.0.0_INSTALL.md\n├── skill-name-desktop-v1.1.0.zip\n├── skill-name-api-v1.1.0.zip\n└── skill-name-v1.1.0_INSTALL.md\n```\n\n### File Naming Convention\n\n```\n{skill-name}-{variant}-v{version}.zip\n{skill-name}-v{version}_INSTALL.md\n```\n\n**Components:**\n- `skill-name`: Directory name (e.g., `financial-analysis`)\n- `variant`: `desktop` or `api`\n- `version`: Semantic version (e.g., `v1.0.0`)\n\n**Examples:**\n- `stock-analyzer-desktop-v1.0.0.zip`\n- `stock-analyzer-api-v1.0.0.zip`\n- `stock-analyzer-v1.0.0_INSTALL.md`\n\n---\n\n## 🛡️ Security & Exclusions\n\n### Automatically Excluded\n\n**Directories:**\n- `.git/` - Version control (contains history)\n- `__pycache__/` - Python compiled files\n- `node_modules/` - JavaScript dependencies\n- `.venv/`, `venv/`, `env/` - Virtual environments\n- `.claude-plugin/` - Claude Code specific (API variant only)\n\n**Files:**\n- `.env` - Environment variables (may contain secrets)\n- `credentials.json` - API keys and secrets\n- `secrets.json` - Secret configuration\n- `.DS_Store` - macOS metadata\n- `.gitignore` - Git configuration\n- `*.pyc`, `*.pyo` - Python compiled\n- `*.log` - Log files\n\n### Why Exclude These?\n\n1. **Security**: Prevent accidental exposure of API keys/secrets\n2. **Size**: Reduce package size (especially for API variant)\n3. **Relevance**: Remove development artifacts not needed at runtime\n4. **Portability**: Exclude platform-specific files\n\n### What's Always Included\n\n**Required:**\n- `SKILL.md` - Core skill definition (mandatory)\n\n**Strongly Recommended:**\n- `scripts/` - Execution code\n- `README.md` - Usage documentation\n- `requirements.txt` - Python dependencies\n\n**Optional:**\n- `references/` - Additional documentation\n- `assets/` - Templates, prompts, examples\n\n---\n\n## 🎯 Use Cases\n\n### Use Case 1: Share with Desktop Users\n\n**Scenario:** You created a skill in Claude Code, colleague uses Desktop\n\n**Solution:**\n```\n1. Export: \"Export my-skill for Desktop\"\n2. Share: Send {skill}-desktop-v1.0.0.zip to colleague\n3. Install: Colleague uploads to Desktop → Settings → Skills\n```\n\n### Use Case 2: Deploy via API\n\n**Scenario:** Integrate skill into production application\n\n**Solution:**\n```python\n# 1. Export API variant\n\"Export my-skill for API\"\n\n# 2. Upload programmatically\nimport anthropic\nclient = anthropic.Anthropic(api_key=os.env['ANTHROPIC_API_KEY'])\n\nwith open('my-skill-api-v1.0.0.zip', 'rb') as f:\n skill = client.skills.create(file=f, name=\"my-skill\")\n\n# 3. Use in production\nresponse = client.messages.create(\n model=\"claude-sonnet-4\",\n messages=[{\"role\": \"user\", \"content\": query}],\n container={\"type\": \"custom_skill\", \"skill_id\": skill.id},\n betas=[\"code-execution-2025-08-25\", \"skills-2025-10-02\"]\n)\n```\n\n### Use Case 3: Versioned Releases\n\n**Scenario:** Maintain multiple skill versions\n\n**Solution:**\n```bash\n# Release v1.0.0\ngit tag v1.0.0\n\"Export my-skill for both\"\n# Creates: my-skill-desktop-v1.0.0.zip, my-skill-api-v1.0.0.zip\n\n# Later: Release v1.1.0 with new features\ngit tag v1.1.0\n\"Export my-skill for both\"\n# Creates: my-skill-desktop-v1.1.0.zip, my-skill-api-v1.1.0.zip\n\n# Both versions coexist in exports/ for compatibility\n```\n\n### Use Case 4: Team Distribution\n\n**Scenario:** Share skill with entire team\n\n**Options:**\n\n**Option A: Git Repository**\n```bash\n# Claude Code users (recommended)\ngit clone repo-url\n/plugin marketplace add ./skill-name\n```\n\n**Option B: Direct Download**\n```bash\n# Desktop/Web users\n1. Download {skill}-desktop-v1.0.0.zip\n2. Upload to Claude Desktop or claude.ai\n3. Follow installation guide\n```\n\n---\n\n## 🔧 Troubleshooting\n\n### Export Fails: \"Path does not exist\"\n\n**Cause:** Incorrect skill path\n\n**Fix:**\n```bash\n# Check path exists\nls -la ./my-skill\n# Use absolute path\npython scripts/export_utils.py /full/path/to/skill\n```\n\n### Export Fails: \"SKILL.md missing frontmatter\"\n\n**Cause:** SKILL.md doesn't start with `---`\n\n**Fix:**\n```markdown\n---\nname: my-skill\ndescription: What this skill does\n---\n\n# Rest of SKILL.md content\n```\n\n### Export Fails: \"API package too large\"\n\n**Cause:** Package exceeds 8MB API limit\n\n**Fix Options:**\n1. Remove large documentation files from skill\n2. Remove example files\n3. Compress images/assets\n4. Use Desktop variant instead (no size limit)\n\n### Desktop upload fails\n\n**Cause:** Various platform-specific issues\n\n**Check:**\n1. File size reasonable (\u003c 10MB recommended)\n2. SKILL.md has valid frontmatter\n3. Name ≤ 64 characters\n4. Description ≤ 1024 characters\n5. Try re-exporting with latest version\n\n### API returns error\n\n**Common causes:**\n```python\n# Missing beta headers\nbetas=[\"code-execution-2025-08-25\", \"skills-2025-10-02\"] # REQUIRED\n\n# Wrong container type\ncontainer={\"type\": \"custom_skill\", \"skill_id\": skill.id} # Use custom_skill\n\n# Skill ID not found\n# Ensure skill.id from upload matches container skill_id\n```\n\n---\n\n## 📚 Advanced Topics\n\n### Custom Output Directory\n\n```bash\n# Default: exports/ in parent directory\npython scripts/export_utils.py ./skill\n\n# Custom location\npython scripts/export_utils.py ./skill --output-dir /path/to/releases\n\n# Within skill directory\npython scripts/export_utils.py ./skill --output-dir ./dist\n```\n\n### Batch Export\n\nExport multiple skills:\n\n```bash\n# Loop through skill directories\nfor skill in */; do\n [ -f \"$skill/SKILL.md\" ] && python scripts/export_utils.py \"./$skill\"\ndone\n\n# Or via agent-skill-creator\n\"Export all skills in current directory\"\n```\n\n### CI/CD Integration\n\nAutomate exports in build pipeline:\n\n```yaml\n# .github/workflows/release.yml\nname: Release Skill\non:\n push:\n tags:\n - 'v*'\n\njobs:\n export:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v3\n - name: Export skill\n run: |\n python scripts/export_utils.py . --version ${{ github.ref_name }}\n - name: Upload artifacts\n uses: actions/upload-artifact@v3\n with:\n name: skill-packages\n path: exports/*.zip\n```\n\n---\n\n## 🎓 Best Practices\n\n### ✅ Do\n\n1. **Version everything** - Use semantic versioning\n2. **Test exports** - Verify packages work on target platforms\n3. **Include README** - Clear usage instructions\n4. **Keep secrets out** - Never include .env or credentials\n5. **Document dependencies** - Maintain requirements.txt\n6. **Validate before sharing** - Run validation checks\n7. **Use installation guides** - Auto-generated for each export\n\n### ❌ Don't\n\n1. **Don't commit .zip files to git** - They're generated artifacts\n2. **Don't include secrets** - Use environment variables instead\n3. **Don't skip validation** - Ensures compatibility\n4. **Don't ignore size limits** - API has 8MB maximum\n5. **Don't forget documentation** - Users need guidance\n6. **Don't mix versions** - Clear version numbering prevents confusion\n\n---\n\n## 📖 Related Documentation\n\n- **Cross-Platform Guide**: `cross-platform-guide.md` - Platform compatibility matrix\n- **Main README**: `../README.md` - Agent-skill-creator overview\n- **SKILL.md**: `../SKILL.md` - Core skill definition\n- **CHANGELOG**: `../docs/CHANGELOG.md` - Version history\n\n---\n\n## 🆘 Getting Help\n\n**Questions about:**\n- Export process → This guide\n- Platform compatibility → `cross-platform-guide.md`\n- Skill creation → Main `README.md`\n- API integration → Claude API documentation\n\n**Report issues:**\n- GitHub Issues: [agent-skill-creator issues](https://github.com/FrancyJGLisboa/agent-skill-creator/issues)\n\n---\n\n**Generated by:** agent-skill-creator v3.2\n**Last updated:** October 2025\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":13034,"content_sha256":"9db83cbb25aee2d023a7a6efbf9794ce2b5fd3c9f80bb66c02e5d38a44a4c5a7"},{"filename":"references/interactive-mode.md","content":"# Interactive Configuration Mode\n\n## Overview\n\nInteractive mode provides a step-by-step wizard for configuring skill creation when the user wants explicit control over each decision. While the standard pipeline runs autonomously (the system researches, decides, and implements), interactive mode pauses at each decision point and asks the user for input or confirmation.\n\n## When to Use Interactive Mode\n\n| Situation | Why Interactive Mode Helps |\n|-----------|---------------------------|\n| Complex projects with multiple APIs | User can weigh trade-offs with full context |\n| User has strong preferences | Avoids rework from autonomous decisions that miss user intent |\n| High-stakes or production-critical skills | Every decision is reviewed before proceeding |\n| Learning how the pipeline works | Step-by-step walkthrough teaches the creation process |\n| Domain expertise the system lacks | User provides insider knowledge at each phase |\n\nInteractive mode is **not** recommended for straightforward requests where the user trusts the system to make good decisions. For simple skills, the autonomous pipeline is faster and produces equivalent results.\n\n## Starting Interactive Mode\n\n### Commands\n\n```\n\"Create a skill for [objective] interactively\"\n\"Create a skill for [objective] with interactive mode\"\n\"Walk me through creating a skill for [objective]\"\n\"I want to configure each step for a [domain] skill\"\n```\n\n### Resuming a Paused Session\n\nIf a session is interrupted, the system can resume from the last completed step:\n\n```\n\"Resume the skill creation we started\"\n\"Continue where we left off on the [skill-name] skill\"\n```\n\n### Learning Mode\n\nA variant of interactive mode that explains each phase as it runs:\n\n```\n\"Create a skill for [objective] and explain each step\"\n\"Teach me how to create a [domain] skill\"\n```\n\n## Wizard Steps\n\n### Step 1: Understand from Evidence\n\nInstead of asking structured questions, present your understanding derived from whatever the user provided.\n\n**If the user provided files/URLs/screenshots:**\n```\nFrom your [evidence], here's what I understand:\n- You [workflow description derived from artifacts]\n- The data comes from [source]\n- The output goes to [recipient/format]\n- This happens [frequency]\n- [One specific thing you couldn't figure out]?\n```\n\n**If the user provided only text:**\n```\nI understand you need [summary]. Before I build, let me confirm:\n- [Most critical assumption that could be wrong]?\n```\n\n**If the user provided almost nothing (single word, vague phrase):**\n```\nIn the context of [their role/desk], '[word]' likely means [interpretation A].\nDoes that sound right, or did you mean [interpretation B]?\n```\n\n**Rules:**\n- Never ask more than 2 questions at once\n- Never present a numbered questionnaire\n- Always lead with what you KNOW (from evidence), then ask about what you DON'T\n- If you're 80%+ confident, just build it and let them correct the output\n\n**User response example** (confirming a hypothesis):\n```\nyes, but the report goes to the VP not the team lead. and weekly not daily.\n```\n\n### Step 2: Workflow Analysis\n\nThe system analyzes the requirements and presents its understanding for confirmation.\n\n**System prompt**:\n```\nBased on your requirements, here's what I understand:\n\nDomain: Climate / Agriculture\nPrimary API: NOAA Climate Data Online\n - Historical data (10+ year coverage)\n - Free, 1,000 requests/day\n - API key required (NOAA_API_KEY)\n\nSecondary API: Open-Meteo\n - Forecast data (16-day outlook)\n - Free, no API key, 10,000 requests/day\n\nPlanned analyses:\n 1. Current vs. historical temperature comparison\n 2. Precipitation anomaly detection\n 3. Growing degree day calculations\n 4. Weekly trend summary with charts\n\nOutput: PDF report with embedded charts\n\nDoes this look correct? Would you like to change anything?\n```\n\n### Step 3: Strategy Selection\n\nThe system presents architecture options and asks the user to choose.\n\n**System prompt**:\n```\nFor this skill, I recommend the following architecture:\n\nOption A: Simple Skill (recommended)\n - Single SKILL.md + scripts + references\n - 4 Python scripts (~600 lines total)\n - Best for: single-developer maintenance\n\nOption B: Complex Suite\n - Separate skills for data fetching and analysis\n - Shared reference library\n - Best for: team maintenance or future expansion\n\nI recommend Option A because the scope is focused and the\ntotal code size is under 1,000 lines.\n\nWhich option do you prefer? [A/B]\n```\n\n### Step 4: Preview and Refinement\n\nBefore creating files, the system shows a preview of what will be generated.\n\n**System prompt**:\n```\nHere's the complete plan before I create any files:\n\nDirectory: climate-agriculture-analysis/\n├── SKILL.md (~120 lines)\n├── scripts/\n│ ├── fetch_noaa.py (historical data retrieval)\n│ ├── fetch_openmeto.py (forecast retrieval)\n│ ├── analyze_climate.py (comparison + anomaly detection)\n│ └── generate_report.py (PDF generation)\n├── references/\n│ ├── api-guide.md (NOAA + Open-Meteo documentation)\n│ └── methodology.md (analysis methods explained)\n├── assets/\n│ └── report_template.html\n├── install.sh (cross-platform installer)\n└── README.md (installation for 7+ platforms)\n\nSKILL.md frontmatter:\n name: climate-agriculture-analysis\n description: >-\n Analyze climate data for US agricultural regions using NOAA\n historical records and Open-Meteo forecasts. Compare current\n conditions to 10-year averages, detect anomalies...\n license: MIT\n metadata:\n author: [your name]\n version: 1.0.0\n\nShall I proceed with creation? Or would you like to modify\nanything in this plan?\n```\n\n### Step 5: Creation\n\nThe system creates all files, reporting progress as it goes.\n\n**System output**:\n```\nCreating skill: climate-agriculture-analysis\n\n[1/10] Created directory structure\n[2/10] Written SKILL.md (118 lines, spec-compliant)\n[3/10] Written scripts/fetch_noaa.py (145 lines)\n[4/10] Written scripts/fetch_openmeto.py (98 lines)\n[5/10] Written scripts/analyze_climate.py (187 lines)\n[6/10] Written scripts/generate_report.py (156 lines)\n[7/10] Written references/api-guide.md\n[8/10] Written references/methodology.md\n[9/10] Generated install.sh (cross-platform)\n[10/10] Written README.md (multi-platform instructions)\n```\n\n### Step 6: Validation\n\nThe system runs spec validation and security scanning, then reports results.\n\n**System output**:\n```\nRunning validation...\n\nSpec Compliance:\n [PASS] Frontmatter: name format valid (lowercase + hyphens)\n [PASS] Frontmatter: description length (287 chars \u003c= 1024)\n [PASS] Frontmatter: name matches directory\n [PASS] SKILL.md body: 118 lines (\u003c 500 limit)\n [PASS] Required files: SKILL.md present\n [PASS] License field: MIT\n\nSecurity Scan:\n [PASS] No hardcoded API keys detected\n [PASS] No .env files in skill directory\n [PASS] No eval() or exec() calls\n [PASS] No shell injection patterns\n\nResult: 10/10 checks passed. Skill is ready for installation.\n\nWould you like to install it now? Run: ./install.sh\n```\n\n## Advanced Configuration Options\n\n### Skipping Phases\n\nIn interactive mode, users can skip phases they want to handle themselves:\n\n```\n\"Create a skill interactively but skip API research -- I'll provide the API details\"\n\"Skip the design phase, I already know what analyses I want\"\n```\n\n### Partial Interactivity\n\nUsers can make only specific phases interactive:\n\n```\n\"Create a skill autonomously but let me review the architecture before you build\"\n\"Auto-create but pause before generating the SKILL.md frontmatter\"\n```\n\n### Exporting the Configuration\n\nAfter completing the wizard, users can export the configuration for reuse:\n\n```\n\"Save this configuration as a template for future skills\"\n```\n\nThis creates a custom template file (see `references/templates-guide.md`) that can be reused for similar skills without re-answering all the wizard questions.\n\n## Interactive Mode vs. Autonomous Mode\n\n| Aspect | Autonomous | Interactive |\n|--------|-----------|-------------|\n| Speed | Faster (no pauses) | Slower (waits for input) |\n| Control | System decides | User decides |\n| Suitable for | Well-defined domains | Ambiguous or complex requirements |\n| Discovery | System researches and selects | System researches, user confirms |\n| Architecture | System chooses optimal | User picks from options |\n| Output | Same quality | Same quality, user-validated |\n\nBoth modes produce the same output structure. The difference is whether the user participates in decisions or trusts the system to make them autonomously.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":8631,"content_sha256":"3d22ee77c38761e0edb07b754c9296a1854a9e71f39116179bd71e612c51ce37"},{"filename":"references/multi-agent-guide.md","content":"# Multi-Agent Suite Creation\n\n## Overview\n\nA multi-agent suite is a collection of related skills that work together as a unified system. Instead of creating one monolithic skill, the suite splits responsibilities across multiple focused skills that share references and coordinate through a common integration layer.\n\nThe 5-phase pipeline applies to each component skill, but Phase 3 (Architecture) makes the critical decision: simple skill vs. complex suite.\n\n## When to Use Batch Creation\n\n| Scenario | Single Skill | Multi-Agent Suite |\n|----------|-------------|-------------------|\n| 1-2 distinct workflows | Recommended | Overkill |\n| 3+ distinct workflows | Unwieldy | Recommended |\n| Total code > 2,000 lines | Hard to maintain | Organized by component |\n| Team maintenance | Single owner ok | Multiple contributors benefit |\n| Shared data sources across workflows | Duplicated logic | Shared reference layer |\n| Independent scaling of capabilities | Not possible | Each skill evolves independently |\n\n**Rule of thumb**: If you find yourself describing more than two unrelated tasks in a single skill description, you need a suite.\n\n## Suite Creation Process\n\n### Step 1: Analyze Relationships\n\nThe system examines the user's requirements and identifies distinct workflow clusters.\n\n```\nUser: \"I need to track our e-commerce store -- monitor sales,\n analyze customer behavior, track inventory levels, and\n generate executive reports.\"\n\nAnalysis:\n Cluster 1: Sales Monitoring (revenue, orders, conversion)\n Cluster 2: Customer Analytics (segmentation, cohorts, churn)\n Cluster 3: Inventory Tracking (stock levels, reorder alerts)\n Cluster 4: Executive Reporting (aggregated dashboards)\n\n Shared resources: Shopify API connection, date utilities,\n report generation templates\n```\n\n### Step 2: Determine Structure\n\nBased on the cluster analysis, the system designs the suite directory:\n\n```\necommerce-suite/\n├── SKILL.md # Suite-level overview (\u003c500 lines)\n├── .claude-plugin/\n│ └── marketplace.json # Suite manifest (official fields only)\n├── skills/\n│ ├── sales-monitor/\n│ │ ├── SKILL.md # Sales-specific instructions\n│ │ └── scripts/\n│ │ └── monitor_sales.py\n│ ├── customer-analytics/\n│ │ ├── SKILL.md # Customer-specific instructions\n│ │ └── scripts/\n│ │ └── analyze_customers.py\n│ ├── inventory-tracker/\n│ │ ├── SKILL.md # Inventory-specific instructions\n│ │ └── scripts/\n│ │ └── track_inventory.py\n│ └── executive-reports/\n│ ├── SKILL.md # Reporting instructions\n│ └── scripts/\n│ └── generate_reports.py\n├── shared/\n│ ├── api_client.py # Shared Shopify API connection\n│ ├── date_utils.py # Common date handling\n│ └── report_templates/ # Shared report assets\n│ └── executive_template.html\n├── references/\n│ └── api-guide.md # Shared API documentation\n├── install.sh # Installs entire suite\n└── README.md # Multi-platform instructions\n```\n\n### Step 3: Create Individual Skills\n\nEach component skill goes through Phases 1-5 independently, but with awareness of the shared resources:\n\n1. **Discovery** -- Each skill identifies its specific API endpoints (e.g., sales-monitor uses Shopify Orders API, inventory-tracker uses Shopify Inventory API).\n2. **Design** -- Each skill defines its own analyses, scoped to its cluster.\n3. **Architecture** -- Each skill follows the simple skill structure within the suite.\n4. **Detection** -- Each skill gets its own description and keywords for independent activation.\n5. **Implementation** -- Each skill references shared utilities from `shared/`.\n\n### Step 4: Integration Layer\n\nThe suite-level `SKILL.md` serves as the orchestration layer. It describes how the component skills relate and when to invoke which one:\n\n```markdown\n# E-commerce Suite\n\n## Component Skills\n\nThis suite contains four specialized skills:\n\n- **sales-monitor**: Activate for revenue, orders, and conversion queries\n- **customer-analytics**: Activate for segmentation, cohort, and churn queries\n- **inventory-tracker**: Activate for stock levels, reorder, and supply queries\n- **executive-reports**: Activate for dashboards, summaries, and executive briefings\n\n## Cross-Skill Workflows\n\nWhen the user asks for a \"full store overview\" or \"weekly executive summary\",\ninvoke executive-reports which aggregates data from all three other skills.\n```\n\n## Suite-Level marketplace.json\n\nFor complex suites that need Claude Code plugin registration, a `marketplace.json` is generated with **only official fields**:\n\n```json\n{\n \"name\": \"ecommerce-suite\",\n \"plugins\": [\n {\n \"name\": \"sales-monitor\",\n \"description\": \"Monitor e-commerce sales, revenue trends, order volumes, and conversion rates using Shopify data.\",\n \"source\": \"./skills/sales-monitor/\",\n \"skills\": [\"./skills/sales-monitor/\"]\n },\n {\n \"name\": \"customer-analytics\",\n \"description\": \"Analyze customer behavior, segmentation, cohort retention, and churn patterns from e-commerce data.\",\n \"source\": \"./skills/customer-analytics/\",\n \"skills\": [\"./skills/customer-analytics/\"]\n },\n {\n \"name\": \"inventory-tracker\",\n \"description\": \"Track inventory stock levels, predict reorder points, and alert on low-stock items.\",\n \"source\": \"./skills/inventory-tracker/\",\n \"skills\": [\"./skills/inventory-tracker/\"]\n },\n {\n \"name\": \"executive-reports\",\n \"description\": \"Generate executive dashboards and PDF summaries aggregating sales, customer, and inventory data.\",\n \"source\": \"./skills/executive-reports/\",\n \"skills\": [\"./skills/executive-reports/\"]\n }\n ]\n}\n```\n\n**Important**: The marketplace.json uses ONLY the official fields: `name` and `plugins` at the top level, and `name`, `description`, `source`, `skills` per plugin entry. No `version`, `author`, `repository`, `tags`, `icon`, or other non-standard fields.\n\n## Suite Examples\n\n### Financial Suite\n\n**User request**: \"Create a suite for comprehensive financial analysis -- stock tracking, portfolio management, and market research.\"\n\n```\nfinancial-suite/\n├── SKILL.md\n├── .claude-plugin/marketplace.json\n├── skills/\n│ ├── stock-tracker/ # Real-time and historical price data\n│ ├── portfolio-manager/ # Holdings, allocation, performance\n│ └── market-research/ # Sector analysis, news sentiment\n├── shared/\n│ ├── market_data_client.py # Alpha Vantage + Yahoo Finance\n│ └── financial_utils.py # Common calculations (returns, ratios)\n├── references/\n├── install.sh\n└── README.md\n```\n\n### E-commerce Suite\n\n**User request**: \"I manage an online store and need to track everything -- sales, customers, inventory, and generate weekly reports.\"\n\n(See the detailed structure in the Suite Creation Process section above.)\n\n### Climate Suite\n\n**User request**: \"Build a climate analysis system with historical trend analysis, forecast monitoring, and extreme event alerting.\"\n\n```\nclimate-suite/\n├── SKILL.md\n├── .claude-plugin/marketplace.json\n├── skills/\n│ ├── historical-trends/ # Long-term climate data analysis\n│ ├── forecast-monitor/ # Short-term forecast tracking\n│ └── extreme-events/ # Severe weather alerting\n├── shared/\n│ ├── climate_data_client.py # NOAA + Open-Meteo connections\n│ └── geo_utils.py # Geographic region handling\n├── references/\n├── install.sh\n└── README.md\n```\n\n## Suite Orchestration Patterns\n\nThe suite-level SKILL.md is the most important file in a suite. It doesn't just list components — it tells the agent *how to think* about routing, sequencing, and combining them.\n\n### Pattern 1: Intent-Based Routing\n\nThe simplest pattern. The suite SKILL.md maps user intent to component skills:\n\n```markdown\n# /ecommerce-suite — E-commerce Intelligence\n\nYou coordinate four specialized e-commerce skills. Route every user\nquery to the right component based on intent.\n\n## Routing Table\n\n| If the user asks about... | Invoke | Examples |\n|---------------------------|--------|---------|\n| Revenue, orders, sales, conversion, AOV | /sales-monitor | \"What were last week's sales?\" |\n| Customers, segments, cohorts, churn, retention | /customer-analytics | \"Show me customer retention\" |\n| Stock, inventory, reorder, supply, out-of-stock | /inventory-tracker | \"Which SKUs need reordering?\" |\n| Summary, dashboard, executive, weekly report | /executive-reports | \"Generate the weekly report\" |\n\nIf the query doesn't clearly match one component, ask the user to clarify.\nIf the query spans multiple components, use the cross-component workflows below.\n```\n\n### Pattern 2: Sequential Pipeline\n\nSome workflows require components in sequence — the output of one feeds the next:\n\n```markdown\n## Cross-Component Workflows\n\n### Weekly Executive Report\nWhen user asks for \"weekly report\", \"executive summary\", or \"full store overview\":\n\n1. Run /sales-monitor: Get revenue, orders, conversion for the past 7 days\n2. Run /customer-analytics: Get new vs returning customer split, churn rate\n3. Run /inventory-tracker: Get low-stock alerts and reorder recommendations\n4. Run /executive-reports: Compile all three into a single PDF dashboard\n\nThe executive-reports component expects data from the other three.\nPass the outputs as context — do not ask the user to run each step manually.\n\n### Churn Revenue Impact\nWhen user asks about \"revenue impact of churn\" or \"how much are we losing\":\n\n1. Run /customer-analytics: Get churned customer segments and churn rate\n2. Run /sales-monitor: Get revenue breakdown by customer segment\n3. Calculate: revenue_at_risk = churned_segment_revenue × churn_rate\n4. Present combined analysis with both the churn data and revenue impact\n```\n\n### Pattern 3: Parallel Aggregation\n\nWhen components can run independently and results are combined:\n\n```markdown\n### Store Health Check\nWhen user asks for \"store health\" or \"how's the business\":\n\nRun these in parallel (no dependencies between them):\n- /sales-monitor → revenue trend (up/down/flat)\n- /customer-analytics → retention rate\n- /inventory-tracker → stock health score\n\nThen synthesize:\n- All green: \"Store is healthy — revenue trending up, retention stable, stock well-managed\"\n- Mixed: Report which areas need attention\n- All concerning: \"Multiple areas need attention\" + specific recommendations\n```\n\n### Pattern 4: Conditional Routing\n\nWhen the right component depends on data discovered during the conversation:\n\n```markdown\n## Conditional Workflows\n\n### Deep Dive Analysis\nWhen user asks to \"deep dive\" or \"investigate\" a metric:\n\n1. Identify which metric they're asking about\n2. Route to the appropriate component:\n - Revenue metric → /sales-monitor with detailed=true\n - Customer metric → /customer-analytics with detailed=true\n - Stock metric → /inventory-tracker with detailed=true\n3. If the deep dive reveals a cross-domain issue (e.g., revenue dropped\n because of stockouts), invoke the relevant second component\n4. Present the combined root-cause analysis\n```\n\n### Orchestration Anti-Patterns\n\n| Anti-Pattern | Why It's Wrong | Do This Instead |\n|-------------|---------------|-----------------|\n| Suite SKILL.md just lists components without routing logic | Agent has to guess which component to use | Provide explicit routing table with example queries |\n| Components call each other's scripts directly | Creates tight coupling, breaks independence | Agent orchestrates — passes output from A as context to B |\n| Suite SKILL.md duplicates component instructions | Maintenance nightmare, instructions drift apart | Reference component skills: \"Invoke /sales-monitor for this\" |\n| No cross-component workflow examples | Agent doesn't know how to combine results | Document 2-3 concrete multi-component workflows |\n| Every query goes through all components | Wastes tokens and time | Route to specific component; only aggregate when explicitly asked |\n\n### Complete Suite SKILL.md Example\n\n```markdown\n# /financial-suite — Comprehensive Financial Analysis\n\nYou are a financial analysis coordinator managing three specialized skills.\nYour job is to route queries to the right specialist and combine results\nwhen needed.\n\n## Component Skills\n\n- **/stock-analyzer**: Real-time and historical price data, technical indicators\n- **/portfolio-tracker**: Holdings, allocation, performance, rebalancing\n- **/market-research**: Sector analysis, news sentiment, peer comparison\n\n## Routing Logic\n\n| User Intent | Route To |\n|-------------|----------|\n| Price, chart, technical indicator, RSI, MACD | /stock-analyzer |\n| My portfolio, allocation, performance, rebalance | /portfolio-tracker |\n| Sector trends, news, competitor, peer comparison | /market-research |\n\n## Cross-Skill Workflows\n\n### Portfolio Review with Market Context\nWhen user asks for \"portfolio review\" or \"how am I doing\":\n1. Invoke /portfolio-tracker for current holdings and performance\n2. Invoke /market-research for sector trends affecting held positions\n3. Synthesize: performance attribution + market context + recommendations\n\n### Buy/Sell Analysis\nWhen user asks \"should I buy X\" or \"should I sell X\":\n1. Invoke /stock-analyzer for technical analysis of the specific stock\n2. Invoke /market-research for sector sentiment and peer comparison\n3. Invoke /portfolio-tracker to check current exposure and allocation impact\n4. Synthesize: technical signal + market context + portfolio fit\n\n## When to Combine vs. Route Directly\n\n- Single-domain question → Route to one component\n- \"How am I doing\" or \"full analysis\" → Combine all components\n- If unsure → Ask the user: \"Would you like a quick check on [X]\n or a comprehensive analysis across your portfolio?\"\n```\n\n---\n\n## Benefits of Suite Creation\n\n### Time Efficiency\n\nCreating a suite through the batch process is significantly faster than creating individual skills separately:\n\n| Approach | API Research | Shared Code | Integration | Total |\n|----------|-------------|-------------|-------------|-------|\n| 4 separate skills | 4x (redundant) | 0 (duplicated) | Manual | ~4 hours |\n| 1 suite (batch) | 1x (shared) | 1x (reused) | Automatic | ~1.5 hours |\n\n### Built-in Integration\n\nComponent skills within a suite share:\n- API client code (no duplicated connection logic)\n- Utility functions (date handling, formatting, calculations)\n- Reference documentation (API guides written once)\n- Report templates (consistent styling across outputs)\n\n### Independent Maintenance\n\nEach component skill can be:\n- Updated independently without affecting others\n- Tested in isolation\n- Replaced with an improved version\n- Extended with new analyses\n\nThe suite structure makes it possible to update the inventory-tracker without touching the sales-monitor, while still sharing the underlying Shopify API client.\n\n### Marketplace Discoverability\n\nEach plugin entry in `marketplace.json` has its own `description` field. This means a suite's individual components can be discovered by different searches:\n- \"track sales\" finds the sales-monitor plugin\n- \"customer churn\" finds the customer-analytics plugin\n- \"inventory alerts\" finds the inventory-tracker plugin\n\nThe suite functions as both a single installable unit and a collection of independently discoverable capabilities.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":15812,"content_sha256":"db50cf8052968bbbadbfdb0616249d0167ae0bcc3c921cf8834d24ab63cbc047"},{"filename":"references/phase2-artifact-assessment.md","content":"# Phase 2 — Artifact Opportunity Assessment\n\nThis step runs inside Phase 2 (Design), after Phase 1 has identified the\nskill's domain and before the SKILL.md is generated. It decides whether\nthe generated skill should emit an interactive React artifact when invoked,\nand if so, which of the four bundled templates to inline.\n\n## Inputs\n\n- `description` (str) — the user's workflow description (raw or normalized\n by Phase 1 Triage)\n- `domain` (str | None) — the domain Phase 1 identified (unused in v6.0;\n reserved for future heuristics)\n\n## Step 1 — Call the detector\n\n```python\nfrom artifact_detector import detect_artifact\ntemplate_name = detect_artifact(description, domain=domain)\n```\n\n`template_name` is one of:\n- `\"line-chart\"` — temporal series\n- `\"bar-chart\"` — categorical comparison\n- `\"kpi-cards\"` — headline numbers\n- `\"data-table\"` — structured rows baseline\n- `None` — no artifact appropriate\n\n## Step 2 — If no artifact, skip\n\nIf `template_name is None`, Phase 2 proceeds exactly as v4 did. The\ngenerated SKILL.md contains no artifact instructions.\n\n## Step 3 — If an artifact is chosen, inline the template\n\nRead the template file:\n\n```python\ntemplate_path = Path(__file__).parent / \"artifact-templates\" / f\"{template_name}.jsx\"\ntemplate_body = template_path.read_text()\n```\n\nReplace the substitution marker with skill-specific data shape\ninstructions. The marker is `/* AGENT_SKILL_DATA */` in every template.\n\nThe data shape instructions are a JavaScript comment block describing the\ncolumn names, types, and source for the array that the skill should\npopulate. For a \"weekly sales report by region\" skill, the substituted\nsection would read something like:\n\n```jsx\nconst data = /* The skill must populate this with:\n [{ category: \"\u003cregion name>\", value: \u003cnumeric revenue> }, ...]\n Sourced from the sales database query in Step 2 of the workflow.\n*/ [\n { category: 'Sample-A', value: 0 },\n { category: 'Sample-B', value: 0 },\n];\n```\n\nThe placeholder array stays as-is so the artifact still renders something\nwhen the skill is invoked without real data.\n\n## Step 4 — Wire emission instructions into SKILL.md\n\nAfter inlining the template, add a section to the generated SKILL.md\nbody that tells the runtime model HOW to emit the artifact. Use the\nemission format captured in `references/claude-artifact-format.md`.\n\nThe section should read approximately:\n\n> \"When emitting your output, after the markdown analysis, emit the\n> following React component using Claude's in-chat artifact protocol\n> [exact protocol syntax]. The data array should be populated from\n> [data source described above].\"\n\n## Step 5 — Honest degradation note\n\nInclude a one-line note in the SKILL.md body acknowledging that the\nartifact renders only in Claude environments. The exact wording:\n\n> \"Note: the React artifact renders interactively in Claude Code and\n> Claude.ai. In other hosts, the component appears as fenced code and\n> the markdown analysis above carries the full information.\"\n\n## Failure handling\n\n| Condition | Action |\n|---|---|\n| `detect_artifact` raises an exception | Phase 2 logs a warning, skips artifact inlining, proceeds as v4. Skill creation does not fail. |\n| Template file missing | Phase 2 logs an error, skips artifact inlining, proceeds as v4. |\n| Substitution marker absent from template | Same — log + skip + proceed. |\n\n## Bypassing\n\nThe user can force or suppress artifact inlining:\n\n- `/agent-skill-creator --no-artifact \u003cdescription>` — never inline\n- `/agent-skill-creator --artifact \u003ctemplate-name> \u003cdescription>` — force\n the named template\n\nWhen forced, the detector is not called and the named template is used\ndirectly. Invalid `--artifact` values are rejected with a clear error\nlisting the four valid template names.\n\n## Out of scope for v6.0\n\n- Per-skill artifact customization (changing the JSX beyond the\n substitution marker)\n- Multiple artifacts per skill\n- User-defined templates\n- Detector training based on telemetry\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":4001,"content_sha256":"99cdc61ddf60a27e2da2ec805d033d88bfa711b47b149e45ba0b53b74057ddf2"},{"filename":"references/phase2-eval-assessment.md","content":"# Phase 2 — Eval Criteria Definition\n\nThis step runs inside Phase 2 (Design), after the skill's use cases are\ndefined and before the SKILL.md is generated. It decides the skill's **loss\nfunction**: a small set of binary checks plus a few golden cases that define\nwhat \"the skill worked\" means. The criteria are derived here; the files are\nwritten in Phase 5.\n\nA skill that ships its own eval spec is *born optimizable* — the bundled metric\nis an instant regression test, and it is formatted so `autoresearch-universal`\ncan pick it up directly (see \"Handoff\" below).\n\n## Why this exists\n\nThe hard part of optimizing a skill is not running a loop — it is defining the\nmetric. The user can recognize a good result but rarely articulates the success\ncriteria as measurable checks. So **you** do the decomposition: propose 3–6\nbinary checks, and the user only approves or vetoes. This is the metric-first\nmindset applied to skill authoring.\n\n## Inputs\n\n- `description` (str) — the user's workflow description (raw or normalized by\n Phase 1 Triage)\n- the use cases / outputs defined earlier in Phase 2\n- any artifacts the user provided (files, URLs, screenshots) — these are the\n primary source of golden cases\n\n## Step 1 — Derive 3–6 binary criteria\n\nEach criterion must be:\n\n1. **Binary** — answerable yes/no. Never scales, scores, or \"rate out of 10\".\n2. **Specific & observable** — checkable by reading the output or running a\n command. \"Output is valid JSON\" not \"the output is good\".\n3. **Independent** — each tests a different dimension; no overlap.\n4. **Not gameable** — avoid criteria the skill can satisfy by parroting the\n wording without doing the work.\n\nTag each criterion as one of two grader types:\n\n- **`command`** — graded by a shell command. Passes when the command exits 0.\n Carry the command in `cmd`; use the `{output}` placeholder where the produced\n (or expected) output file path belongs, e.g. `jq -e . {output}` or\n `test \"$(wc -w \u003c {output})\" -lt 150`. **Prefer `command` wherever a reliable\n programmatic check exists.**\n- **`llm-judge`** — graded by a model reading the output. Use only when meaning,\n tone, or quality cannot be checked by a command. The bundled runner does\n **not** grade these; it prints them as a checklist.\n\nPresent the criteria to the user for a thumbs-up before writing them (one short\nconfirmation, not a questionnaire).\n\n## Step 2 — Assemble 3+ golden cases\n\nA skill that does not exist yet has no \"expected output\". Use this order — it is\na design rule, not a user question:\n\n1. **Seed from user artifacts (primary).** When the user provided files, URLs,\n or screenshots, those ARE golden cases — the creator already treats the\n artifact as the spec, so it doubles as `input` (and often `expected`). Reuse\n them; do not ask the user for examples.\n2. **Synthesize input-only cases (fallback).** When no artifact fits, synthesize\n a representative `input` and set `expected: null` with\n `expected_status: \"pending-first-green\"`. On the skill's first successful\n run, capture the output and — with the user's approval — promote it to the\n `expected` baseline.\n3. **Never interrogate the user for examples.** That re-introduces the cognitive\n constraint the factory exists to remove.\n\nMark each case `\"split\": \"val\"` — these become `autoresearch-universal`'s fixed\nvalidation set.\n\n## Step 3 — Emit the spec in Phase 5\n\nWrite, inside the generated skill:\n\n```\n\u003cskill>/\n scripts/run_evals.py # copied verbatim from scripts/run_evals_template.py\n evals/\n \u003cskill-name>.eval.md # prose + the ```json block below\n golden/\n case-1/{input.*, expected.*}\n case-2/{input.*, expected.*}\n case-3/{input.*} # expected omitted when pending-first-green\n```\n\n`evals/\u003cskill-name>.eval.md` is human-readable prose (the criteria and cases in\nwords) **plus** one fenced ` ```json ` block the runner and autoresearch parse:\n\n```json\n{\n \"skill\": \"weekly-sales-report-skill\",\n \"criteria\": [\n {\"id\": \"valid-json\", \"text\": \"Output is valid JSON\", \"type\": \"command\", \"cmd\": \"jq -e . {output}\"},\n {\"id\": \"has-region-totals\", \"text\": \"Every region has a numeric total\", \"type\": \"llm-judge\"}\n ],\n \"golden\": [\n {\"id\": \"case-1\", \"input\": \"golden/case-1/input.csv\", \"expected\": \"golden/case-1/expected.json\", \"split\": \"val\"},\n {\"id\": \"case-2\", \"input\": \"golden/case-2/input.csv\", \"expected\": \"golden/case-2/expected.json\", \"split\": \"val\"},\n {\"id\": \"case-3\", \"input\": \"golden/case-3/input.csv\", \"expected\": null, \"split\": \"val\", \"expected_status\": \"pending-first-green\"}\n ]\n}\n```\n\nPaths in `input`/`expected` are relative to the `evals/` directory.\n\nCopy the runner verbatim:\n\n```bash\ncp scripts/run_evals_template.py \u003cskill>/scripts/run_evals.py\nchmod +x \u003cskill>/scripts/run_evals.py\n```\n\nThen confirm the spec is well-formed before delivery (parallel to `validate.py`\nand `security_scan.py`):\n\n```bash\npython3 \u003cskill>/scripts/run_evals.py --validate\n```\n\nIt must report `VALID` (exit 0). Fix and re-run if not.\n\n## Step 4 — Tell the user how to use it\n\nAfter creation, alongside the install/share messaging, print:\n\n> This skill ships an eval spec at `evals/\u003cskill-name>.eval.md`.\n> Check it against the golden baseline anytime: `python3 scripts/run_evals.py`\n> To optimize the skill against its metric:\n> `/autoresearch-universal optimize . using evals/\u003cskill-name>.eval.md`\n> (Full optimization of skills that run scripts/tools needs a rollout harness,\n> which this version does not provide — the command checks and the\n> autoresearch handoff work today.)\n\n## Handoff to autoresearch-universal (rule 18)\n\n`autoresearch-universal` accepts externally-supplied eval criteria and skips its\nown metric-definition phase (its rule 18). The emitted spec is the contract:\n\n- the `criteria` array maps directly to its Phase-3 criteria (same `type` values:\n `command` with `cmd`, or `llm-judge`);\n- the `golden` entries with `\"split\": \"val\"` map to its fixed `validation_items`.\n\nSo the spec is consumable with no manual reformatting. This is a **format\ncontract plus a documented one-liner**, not an automated trainer.\n\n## Bypassing\n\n- `/agent-skill-creator --no-eval \u003cdescription>` — skip this step entirely; the\n generated skill carries no `evals/` directory and no `run_evals.py`. Strip the\n token from the prompt before passing it to Phase 1.\n\nEval generation is **on by default** (every skill should define its loss\nfunction). `--no-eval` is the opt-out.\n\n## Failure handling\n\n| Condition | Action |\n|---|---|\n| Criteria derivation fails or raises | Log a warning, skip eval emission, proceed exactly as without evals. **Skill creation does not fail.** |\n| `run_evals.py --validate` reports errors | Fix the spec and re-run; do not deliver an invalid spec. |\n| No usable artifact for golden cases | Synthesize input-only cases marked `pending-first-green`. |\n\nEval emission is never allowed to block or fail skill creation (mirrors the\n`--no-artifact` behavior of the Artifact Opportunity Assessment).\n\n## Out of scope for this version\n\n- A **rollout harness** that executes the generated skill on each golden input\n to produce real output for scoring. The runner checks the golden baseline and\n scores a `--output` you supply; it does not run the skill itself.\n- Automated grading of `llm-judge` criteria (printed as a checklist).\n- A held-out `test` split distinct from `val` (all golden cases are `val`).\n- Multiple eval specs per skill.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7495,"content_sha256":"2b8416379654b621e2841513f892c8966552b894e7000a5cd88843203306b4ce"},{"filename":"references/phase4-detection.md","content":"# Phase 4: Automatic Detection\n\n## Objective\n\n**DETERMINE** keywords and create description so Claude Code activates the skill automatically.\n\n## Detailed Process\n\n### Step 1: List Domain Entities\n\nIdentify all relevant entities that users may mention:\n\n**Entity categories**:\n\n**1. Organizations/Sources**\n- Organization names (USDA, CONAB, NOAA, IMF)\n- Acronyms (NASS, ERS, FAS)\n- Full names (National Agricultural Statistics Service)\n\n**2. Main Objects**\n- For agriculture: commodities (corn, soybeans, wheat)\n- For finance: instruments (stocks, bonds, options)\n- For climate: metrics (temperature, precipitation)\n\n**3. Geography**\n- Countries (US, Brazil, China)\n- Regions (Midwest, Centro-Oeste, Southeast)\n- States/Provinces (Iowa, Mato Grosso, Texas)\n\n**4. Metrics**\n- Production, area, yield, price\n- Revenue, profit, growth\n- Temperature, rainfall, humidity\n\n**5. Temporality**\n- Years, seasons, quarters, months\n- Current, historical, forecast\n- YoY, QoQ, MoM\n\n**Example (US agriculture)**:\n\n```markdown\n**Organizations**:\n- USDA, NASS, National Agricultural Statistics Service\n- Department of Agriculture\n- QuickStats\n\n**Commodities**:\n- Corn, soybeans, wheat\n- Cotton, rice, sorghum\n- Barley, oats, hay, peanuts\n- [list all major ones - 20+]\n\n**Geography**:\n- US, United States, national\n- States: Iowa, Illinois, Nebraska, Kansas, Texas, etc [list top 15]\n- Regions: Midwest, Great Plains, Southeast, etc\n\n**Metrics**:\n- Production, area planted, area harvested\n- Yield, productivity\n- Price received, value of production\n- Inventory, stocks\n\n**Temporality**:\n- Year, season, crop year\n- Current, latest, this year, last year\n- Historical, trend, past 5 years\n- Forecast, projection, outlook\n```\n\n### Step 2: List Actions/Verbs\n\nWhich verbs does the user use to request analyses?\n\n**Categories**:\n\n**Query (fetch information)**:\n- What is, how much, show me, get\n- Tell me, find, retrieve\n\n**Compare**:\n- Compare, versus, vs, against\n- Difference, change, growth\n- Higher, lower, better, worse\n\n**Rank (sort)**:\n- Top, best, leading, biggest\n- Rank, ranking, list\n- Which states, which countries\n\n**Analyze**:\n- Analyze, analysis\n- Trend, pattern, evolution\n- Breakdown, decompose, explain\n\n**Forecast (project)**:\n- Predict, project, forecast\n- Outlook, expectation, estimate\n- Future, next year, coming season\n\n**Visualize**:\n- Plot, chart, graph, visualize\n- Show chart, generate graph\n\n### Step 2.5: Generate Exhaustive Keywords (NEW v2.0 - CRITICAL!)\n\n**OBJECTIVE:** Generate 60+ keywords to ensure correct activation in ALL relevant queries.\n\n**LEARNING:** us-crop-monitor v1.0 had ~20 keywords. Missing \"yield\", \"harvest\", \"production\" → Claude Code didn't activate for those queries. v2.0 expanded to 60+ keywords.\n\n**Mandatory Process:**\n\n**Step A: Keywords per API Metric**\n\nFor EACH metric/endpoint the skill implements, generate keywords:\n\n```markdown\nMetric 1: CONDITION (quality ratings)\nPrimary keywords: condition, conditions, quality, ratings\nSecondary keywords: status, health, state\nTechnical keywords: excellent, good, fair, poor\nAction keywords: rate, rated, rating, classify\nPortuguese: condição, condições, qualidade, estado, classificação\n→ Total: ~15 keywords\n\nMetric 2: PROGRESS (% planted/harvested)\nPrimary keywords: progress, harvest, planted, harvested\nSecondary keywords: planting, harvesting, completion\nTechnical keywords: percentage, percent, %\nAction keywords: advancing, complete, completed\nPortuguese: progresso, plantio, colheita, plantado, colhido\n→ Total: ~15 keywords\n\nMetric 3: YIELD (productivity)\nPrimary keywords: yield, productivity, performance\nTechnical keywords: bushels per acre, bu/acre, bu/ac\nSecondary keywords: output per unit\nPortuguese: rendimento, produtividade, bushels por acre\n→ Total: ~12 keywords\n\n... Repeat for ALL implemented metrics\n```\n\n**Rule:** Each metric = minimum 10 unique keywords\n\n**Step B: Categorize Keywords by Type**\n\n```markdown\n### Keyword Matrix - {Skill Name}\n\n**1. Main Entities** (20+ keywords)\n- Official name: {entity}\n- Variations: {variations}\n- Singular + plural\n- Acronyms: {acronyms}\n- Full names: {full names}\n- Portuguese: {portuguese terms}\n\n**2. Metrics - ONE SECTION PER API METRIC!** (30+ keywords)\n- Metric 1: {list 10-15 keywords}\n- Metric 2: {list 10-15 keywords}\n- Metric 3: {list 10-15 keywords}\n...\n\n**3. Actions/Verbs** (20+ keywords)\n- Query: what, how, show, get, tell, find, retrieve\n- Compare: compare, vs, versus, against, difference\n- Rank: top, best, rank, leading, biggest\n- Analyze: analyze, trend, pattern, evolution\n- Report: report, dashboard, summary, overview\n- Portuguese: comparar, ranking, análise, relatório\n\n**4. Temporal Qualifiers** (15+ keywords)\n- Current: current, now, today, latest, recent, atual, agora, hoje\n- Historical: historical, past, previous, last year, histórico\n- Comparative: this year vs last year, YoY, year-over-year\n- Forecast: forecast, projection, estimate, outlook, previsão\n\n**5. Geographic Qualifiers** (15+ keywords)\n- National: national, US, United States, country-wide\n- Regional: region, Midwest, South, regional\n- State: state, by state, state-level, estado\n- Specific names: Iowa, Illinois, Nebraska, ...\n\n**6. Data Context** (10+ keywords)\n- Source: {API name}, {organization}, {data source}\n- Type: data, statistics, metrics, indicators, dados\n```\n\n**Goal:** Total 60-80 unique keywords!\n\n**Step C: Test Coverage Matrix**\n\nFor each analysis function, generate 10 different queries:\n\n```markdown\nFunction: harvest_progress_report()\n\nQuery variations (test coverage):\n1. \"What's the corn harvest progress?\" ✅ harvest, progress\n2. \"How much corn has been harvested?\" ✅ harvested\n3. \"Percent corn harvested?\" ✅ percent, harvested\n4. \"Harvest completion status?\" ✅ harvest, completion, status\n5. \"Progresso de colheita do milho?\" ✅ progresso, colheita\n6. \"Quanto foi colhido?\" ✅ colhido\n7. \"Harvest advancement?\" ✅ harvest, advancement\n8. \"How advanced is harvest?\" ✅ harvest, advanced\n9. \"Colheita completa?\" ✅ colheita\n10. \"Percentage complete harvest?\" ✅ percentage, harvest\n\nALL keywords present in description? → Verify!\n```\n\n**Do this for ALL 11 functions** = 110 query variations tested!\n\n### Step 3: List Question Variations\n\nFor each analysis type, how can user ask?\n\n**YoY Comparison**:\n- \"Compare X this year vs last year\"\n- \"How does X compare to last year\"\n- \"Is X up or down from last year\"\n- \"X growth rate\"\n- \"X change YoY\"\n- \"X vs previous year\"\n- \"Did X increase or decrease\"\n\n**Ranking**:\n- \"Top states for X\"\n- \"Which states produce most X\"\n- \"Leading X producers\"\n- \"Best X production\"\n- \"Biggest X producers\"\n- \"Ranking of X\"\n- \"List top 10 X\"\n\n**Trend**:\n- \"X trend last N years\"\n- \"How has X changed over time\"\n- \"X evolution\"\n- \"Historical X data\"\n- \"X growth rate historical\"\n- \"Long term trend of X\"\n\n**Simple Query**:\n- \"What is X production\"\n- \"X production in [year]\"\n- \"How much X\"\n- \"X data\"\n- \"Current X\"\n\n### Step 4: Define Negative Scope\n\n**Important**: What should NOT activate?\n\nAvoid false positives (skill activates when it shouldn't).\n\n**Technique**: Think of similar questions but OUT of scope.\n\n**Example (US agriculture)**:\n\n❌ **DO NOT activate for**:\n- Futures market prices\n - \"CBOT corn futures price\"\n - \"Soybean futures December contract\"\n - Reason: Skill is USDA data (physical production), not trading\n\n- Other countries' agriculture\n - \"Brazil soybean production\"\n - \"Argentina corn exports\"\n - Reason: Skill is US only\n\n- Consumption/demand\n - \"US corn consumption\"\n - \"Soybean demand forecast\"\n - Reason: NASS has production, not consumption\n\n- Private company data\n - \"Monsanto corn seed sales\"\n - \"Cargill soybean crush\"\n - Reason: Corporate data, not national statistics\n\n**Document**:\n```markdown\n## Skill Scope\n\n### ✅ WITHIN scope:\n- Physical crop production in US\n- Planted/harvested area\n- Yield/productivity\n- Prices RECEIVED by farmers (farm gate)\n- Inventories\n- Historical and current data\n- Comparisons, rankings, trends\n\n### ❌ OUT of scope:\n- Futures market prices (CBOT, CME)\n- Agriculture outside US\n- Consumption/demand\n- Private company data\n- Market price forecasting\n```\n\n### Step 5: Create Precise Description (Updated v2.0)\n\n**NEW RULE:** Description must contain ALL 60+ identified keywords!\n\n**Expanded Template:**\n\n```yaml\ndescription: This skill should be used when the user asks about\n{domain} ({main entities with variations}). Automatically activates\nfor queries about {metric1} ({metric1 keywords}), {metric2}\n({metric2 keywords}), {metric3} ({metric3 keywords}), {metric4}\n({metric4 keywords}), {metric5} ({metric5 keywords}), {actions_list},\n{temporal qualifiers}, {geographic qualifiers}, comparisons\n{comparison types}, rankings, trends, {data source} data,\ncomprehensive reports, and dashboards. Uses {language} with {API name}\nto fetch real data on {complete list of all metrics}.\n```\n\n**Mandatory components**:\n1. ✅ **Domain** with entities (corn, soybeans, wheat - not just \"crops\")\n2. ✅ **EACH API metric** explicitly mentioned\n3. ✅ **Synonyms** in parentheses (harvest = colheita, yield = rendimento)\n4. ✅ **Actions** covered (compare, rank, analyze, report)\n5. ✅ **Temporal context** (current, today, year-over-year)\n6. ✅ **Geographic** context (states, regions, national)\n7. ✅ **Data source** (USDA NASS, etc.)\n8. ✅ **Portuguese + English** keywords mixed\n\n**Real size:** 300-500 characters (yes, larger than \"recommended\" - but necessary!)\n\n**Real Example (us-crop-monitor v2.0):**\n```yaml\ndescription: This skill should be used when the user asks about\nagricultural crops in the United States (soybeans, corn, wheat).\nAutomatically activates for queries about crop conditions (condições),\ncrop progress (progresso de plantio/colheita), harvest progress\n(progresso de colheita), planting progress (plantio), yield\n(produtividade/rendimento em bushels per acre), production (produção\ntotal em bushels), area planted (área plantada), area harvested\n(área colhida), acres, forecasts (estimativas), crop monitoring,\nweekly comparisons (week-over-week) or annual (year-over-year),\nstate producer rankings, trend analyses, USDA NASS data, comprehensive\nreports, and crop dashboards. Uses Python with NASS API to fetch\nreal data on condition, progress, productivity, production and area.\n```\n\n**Analysis:**\n- Entities: soybeans, corn, wheat (3)\n- Metrics: conditions, progress, harvest, planting, yield, production, area (7)\n- Each metric with PT synonym: (condições), (colheita), (rendimento), etc.\n- Actions: queries, comparisons, rankings, analyses, reports\n- Temporal: weekly, annual, week-over-week, year-over-year\n- Source: USDA NASS\n- Total unique keywords: ~65+\n\n**Step D: Validate Keyword Coverage**\n\nFinal checklist:\n```markdown\n- [ ] All API metrics mentioned? (if API has 5 → 5 in description)\n- [ ] Each metric has PT synonym? (yield = rendimento)\n- [ ] Action verbs included? (compare, rank, analyze)\n- [ ] Temporal context? (current, today, YoY)\n- [ ] Geographic context? (states, national)\n- [ ] Data source mentioned? (USDA NASS)\n- [ ] Total >= 60 unique keywords? (count!)\n```\n\n**Example 2 (stock analysis)**:\n```yaml\ndescription: This skill should be used for technical stock analysis using indicators like RSI, MACD, Bollinger Bands, moving averages. Activates when user asks about technical analysis, indicators, buy/sell signals for stocks. Supports multiple tickers, benchmark comparisons, alert generation. DO NOT use for fundamental analysis, financial statements, or news.\n```\n\n### Step 6: List Complete Keywords\n\nIn SKILL.md, include complete keywords section:\n\n```markdown\n## Keywords for Automatic Detection\n\nThis skill is activated when user mentions:\n\n**Entities**:\n- [complete list of organizations]\n- [complete list of main objects]\n\n**Geography**:\n- [list of countries/regions/states]\n\n**Metrics**:\n- [list of metrics]\n\n**Actions**:\n- [list of verbs]\n\n**Temporality**:\n- [list of temporal terms]\n\n**Activation examples**:\n✅ \"[example 1]\"\n✅ \"[example 2]\"\n✅ \"[example 3]\"\n✅ \"[example 4]\"\n✅ \"[example 5]\"\n\n**Does NOT activate for**:\n❌ \"[out of scope example]\"\n❌ \"[out of scope example]\"\n❌ \"[out of scope example]\"\n```\n\n### Step 7: Mental Testing\n\n**Simulate detection**:\n\nFor each example question from use cases (Phase 2), verify:\n- Description contains relevant keywords? ✅\n- Doesn't contain negative scope keywords? ✅\n- Claude would detect automatically? ✅\n\n**If any use case would NOT be detected**:\n→ Add missing keywords to description\n\n## Detection Design Examples\n\n### Example 1: US Agriculture (NASS)\n\n**Identified keywords**:\n- Entities: USDA (5x), NASS (8x), agriculture (3x)\n- Commodities: corn (12x), soybeans (10x), wheat (8x)\n- Metrics: production (15x), area (10x), yield (8x)\n- Geography: US (10x), states (5x), Iowa (2x)\n- Actions: compare (5x), ranking (3x), trend (2x)\n\n**Description**:\n\"This skill should be used for analyses about United States agriculture using official USDA NASS data. Activates when user asks about production, area, yield of commodities like corn, soybeans, wheat. Supports YoY comparisons, rankings, trends. DO NOT use for futures or other countries.\"\n\n**Coverage**: 95% of typical use cases\n\n### Example 2: Global Climate (NOAA)\n\n**Keywords**:\n- Entities: NOAA, weather, climate\n- Metrics: temperature, precipitation, humidity\n- Geography: global, countries, stations\n- Temporality: historical, current, forecast\n\n**Description**:\n\"This skill should be used for climate analyses using NOAA data. Activates when user asks about temperature, precipitation, historical climate data or forecasts. Supports temporal and geographic aggregations, anomalies, long-term trends.\"\n\n## Phase 4 Checklist\n\n- [ ] Entities listed (organizations, objects, geography)\n- [ ] Actions/verbs listed\n- [ ] Question variations mapped\n- [ ] Negative scope defined\n- [ ] Description created (150-250 chars)\n- [ ] Complete keywords documented in SKILL.md\n- [ ] Activation examples (positive and negative)\n- [ ] Mental detection simulation (all use cases covered)\n\n---\n\n## 🚀 **Enhanced Keyword Generation System v3.1**\n\n### **Problem Solved: False Negatives Prevention**\n\n**Issue**: Skills created with limited keywords (10-15) fail to activate for natural language variations, causing users to lose confidence when their installed skills are ignored by Claude.\n\n**Solution**: Systematic keyword expansion achieving 50+ keywords with 98%+ activation reliability.\n\n### **🔧 Enhanced Keyword Generation Process**\n\n#### **Step 1: Base Keywords (Traditional Method)**\n```\nDomain: Data Extraction & Analysis\nBase Keywords: \"extract data\", \"normalize data\", \"analyze data\"\nCoverage: ~30% (limited)\n```\n\n#### **Step 2: Systematic Expansion (New Method)**\n\n**A. Direct Variations Generator**\n```\nFor each base capability, generate variations:\n- \"extract data\" → \"extract and analyze data\", \"extract and process data\"\n- \"normalize data\" → \"normalize extracted data\", \"data normalization\"\n- \"analyze data\" → \"analyze web data\", \"online data analysis\"\n```\n\n**B. Synonym Expansion System**\n```\nData Synonyms: [\"information\", \"content\", \"details\", \"records\", \"dataset\", \"metrics\"]\nExtract Synonyms: [\"scrape\", \"get\", \"pull\", \"retrieve\", \"collect\", \"harvest\", \"obtain\"]\nAnalyze Synonyms: [\"process\", \"handle\", \"work with\", \"examine\", \"study\", \"evaluate\"]\nNormalize Synonyms: [\"clean\", \"format\", \"standardize\", \"structure\", \"organize\"]\n```\n\n**C. Technical & Business Language**\n```\nTechnical Terms: [\"web scraping\", \"data mining\", \"API integration\", \"ETL process\"]\nBusiness Terms: [\"process information\", \"handle reports\", \"work with data\", \"analyze metrics\"]\nWorkflow Terms: [\"daily I have to\", \"need to process\", \"automate this workflow\"]\n```\n\n**D. Natural Language Patterns**\n```\nQuestion Forms: [\"How to extract data\", \"What data can I get\", \"Can you analyze this\"]\nCommand Forms: [\"Extract data from\", \"Process this information\", \"Analyze the metrics\"]\nInformal Forms: [\"get data from site\", \"handle this data\", \"work with information\"]\n```\n\n#### **Step 3: Pattern-Based Keyword Generation**\n\n**Action + Object Patterns:**\n```\n{action} + {object} + {source}\nExamples:\n- \"extract data from website\"\n- \"process information from API\"\n- \"analyze metrics from database\"\n- \"normalize records from file\"\n```\n\n**Workflow Patterns:**\n```\n{workflow_trigger} + {action} + {data_type}\nExamples:\n- \"I need to extract data daily\"\n- \"Have to process reports every week\"\n- \"Need to analyze metrics monthly\"\n- \"Must normalize information regularly\"\n```\n\n### **📊 Coverage Expansion Results**\n\n#### **Before Enhancement:**\n```\nTotal Keywords: 10-15\nCoverage Types:\n├── Direct phrases: 8-10\n├── Domain terms: 2-5\n└── Success rate: ~70%\n```\n\n#### **After Enhancement:**\n```\nTotal Keywords: 50-80\nCoverage Types:\n├── Direct variations: 15-20\n├── Synonym expansions: 10-15\n├── Technical terms: 8-12\n├── Business language: 7-10\n├── Workflow patterns: 5-8\n├── Natural language: 5-10\n└── Success rate: 98%+\n```\n\n### **🔍 Implementation Template**\n\n#### **Enhanced Keyword Generation Algorithm:**\n```python\ndef generate_expanded_keywords(domain, capabilities):\n keywords = set()\n\n # 1. Base capabilities\n for capability in capabilities:\n keywords.add(capability)\n\n # 2. Direct variations\n for capability in capabilities:\n keywords.update(generate_variations(capability))\n\n # 3. Synonym expansion\n keywords.update(expand_with_synonyms(keywords, domain))\n\n # 4. Technical terms\n keywords.update(get_technical_terms(domain))\n\n # 5. Business language\n keywords.update(get_business_phrases(domain))\n\n # 6. Workflow patterns\n keywords.update(generate_workflow_patterns(domain))\n\n # 7. Natural language variations\n keywords.update(generate_natural_variations(domain))\n\n return list(keywords)\n```\n\n#### **Example: Data Extraction Skill**\n```\nInput Domain: \"Data extraction and analysis from online sources\"\n\nGenerated Keywords (55 total):\n# Direct Variations (15)\nextract data, extract and analyze data, extract and process data,\nnormalize data, normalize extracted data, analyze online data,\nprocess web data, handle information from websites\n\n# Synonym Expansions (12)\nscrape data, get information, pull content, retrieve records,\nharvest data, collect metrics, process information, handle data\n\n# Technical Terms (10)\nweb scraping, data mining, API integration, ETL process, data extraction,\ncontent parsing, information retrieval, data processing, web harvesting\n\n# Business Language (8)\nprocess business data, handle reports, analyze metrics, work with datasets,\nmanage information, extract insights, normalize business records\n\n# Workflow Patterns (5)\ndaily data extraction, weekly report processing, monthly metrics analysis,\nregular information handling, continuous data monitoring\n\n# Natural Language (5)\nget data from this site, process information here, analyze the content,\nwork with these records, handle this dataset\n```\n\n### **✅ Quality Assurance Checklist**\n\n**Keyword Generation:**\n- [ ] 50+ keywords generated for each skill\n- [ ] All capability variations covered\n- [ ] Synonym expansions included\n- [ ] Technical and business terms added\n- [ ] Workflow patterns implemented\n- [ ] Natural language variations present\n\n**Coverage Verification:**\n- [ ] Test 20+ natural language variations\n- [ ] All major use cases covered\n- [ ] Technical terminology included\n- [ ] Business language present\n- [ ] No gaps in keyword coverage\n\n**Testing Requirements:**\n- [ ] 98%+ activation reliability achieved\n- [ ] False negatives \u003c 5%\n- [ ] No activation for out-of-scope queries\n- [ ] Consistent activation across variations\n\n### Implementation in Agent-Skill-Creator\n\n**Updated Phase 4 Process:**\n1. **Generate base keywords** (traditional method)\n2. **Apply systematic expansion** (enhanced method)\n3. **Validate coverage** (minimum 50 keywords)\n4. **Embed all keywords into the SKILL.md description field**\n5. **Test natural language** (20+ variations)\n6. **Verify activation reliability** (98%+ target)\n\n> **IMPORTANT:** All keywords and activation data go into the SKILL.md `description` field. Do NOT create separate activation files, marketplace.json fields, or pattern files. The description IS the activation mechanism.\n\n---\n\n# Phase 4 Enhanced: Description-Based Activation\n\n## How Skill Activation Works\n\nThe agent (Claude Code, VS Code Copilot, Cursor, etc.) reads the `description` field in SKILL.md frontmatter and uses natural language understanding to decide when to activate the skill. There is no separate activation file, no keywords file, and no regex matching layer.\n\nThe `description` field is the **only** activation mechanism. A well-crafted description with 50+ embedded keywords achieves 95%+ activation reliability.\n\n> **CRITICAL:** Do NOT create `activation.keywords`, `activation.patterns`, `test_queries`, `usage.when_to_use`, or `usage.when_not_to_use` fields in marketplace.json or any other file. These are non-standard fields that **break Claude Code installation**. All activation data belongs in the SKILL.md `description` field.\n\n---\n\n\n## Writing Effective Descriptions\n\nThe description serves all activation purposes — keyword matching and natural language understanding — in a single field.\n\n### Description Template\n\n```yaml\ndescription: >-\n {Primary use case in one sentence}. Activates for queries about\n {capability 1} ({synonyms}), {capability 2} ({synonyms}), and\n {capability 3} ({synonyms}). Supports {action verbs}: {action synonyms}.\n Uses {technology/API} to {what it does}. Does NOT activate for:\n {counter-examples}.\n```\n\n### Description Requirements\n\n**Must Include:**\n- Primary use case clearly stated upfront\n- Each capability explicitly mentioned with synonyms in parentheses\n- Action verbs the user might say\n- Technology/API names\n- 3-5 example phrasings embedded naturally\n- 2-3 counter-examples (what this skill is NOT for)\n- 50+ unique keywords woven into natural prose\n\n**Length:** 200-500 characters. Longer than typical but necessary for reliable activation.\n\n### Keyword Design Rules\n\n**DO: Use Complete Phrases in the Description**\n```\n\"technical analysis for stocks\"\n\"analyze stock data\"\n\"buy and sell signals\"\n```\n\n**DON'T: Keyword-Stuff the Description**\n```\n\"stock RSI MACD Bollinger buy sell signal compare rank\" (not prose)\n```\n\n### Embedding Keywords Naturally\n\nTake your keyword research from Steps 1-7 and weave them into coherent prose:\n\n**Bad (keyword stuffing):**\n```yaml\ndescription: \"stock analysis RSI MACD Bollinger buy sell signal compare rank chart patterns momentum moving average\"\n```\n\n**Good (natural prose with keywords embedded):**\n```yaml\ndescription: >-\n Provides comprehensive technical analysis for stocks and ETFs using RSI\n (Relative Strength Index), MACD (Moving Average Convergence Divergence),\n Bollinger Bands, moving averages, and chart patterns. Generates buy and\n sell signals based on technical indicator combinations. Compares and ranks\n multiple stocks by momentum and technical strength. Does NOT activate for\n fundamental analysis (P/E ratios, earnings), news, or options pricing.\n```\n\n---\n\n## Complete Example: stock-analyzer\n\n### SKILL.md Frontmatter (the ONLY activation mechanism)\n\n```yaml\n---\nname: stock-analyzer\ndescription: >-\n Provides comprehensive technical analysis for stocks and ETFs using RSI\n (Relative Strength Index), MACD (Moving Average Convergence Divergence),\n Bollinger Bands, moving averages, and chart patterns. Generates buy and\n sell signals based on technical indicator combinations. Compares multiple\n stocks and ranks them by momentum and technical strength. Monitors stock\n performance and tracks price alerts. Activates when user asks to analyze\n stocks, calculate technical indicators, get trading signals, compare\n tickers, or assess market momentum. Does NOT activate for fundamental\n analysis (P/E ratios, earnings), news-based analysis, portfolio\n optimization, or options pricing.\nversion: 1.0.0\nlicense: MIT\nmetadata:\n author: finance-team\n version: 1.0.0\n---\n```\n\n**Why this achieves 95%+ activation:**\n- Contains 60+ unique keywords naturally embedded\n- All capabilities mentioned with synonyms\n- Action verbs match how users phrase requests\n- Counter-examples prevent false positives\n- Technical terms (RSI, MACD, Bollinger) enable precise matching\n- General terms (\"technical analysis\", \"trading signals\") catch broad queries\n\n### Testing the Description\n\n**Positive Tests (should activate):**\n```\n1. \"Analyze AAPL stock using RSI\" -> activates\n2. \"What's the MACD for Tesla?\" -> activates\n3. \"Show me buy signals for tech stocks\" -> activates\n4. \"Compare AAPL vs GOOGL using technical analysis\" -> activates\n5. \"Moving average crossover for SPY\" -> activates\n6. \"Bollinger Bands analysis for Bitcoin\" -> activates\n7. \"Is TSLA overbought based on RSI?\" -> activates\n8. \"Chart patterns for NVDA\" -> activates\n9. \"Momentum indicators for tech stocks\" -> activates\n10. \"Track AMZN for MACD crossover signals\" -> activates\n```\n\n**Negative Tests (should NOT activate):**\n```\n1. \"What's the P/E ratio of AAPL?\" -> fundamental, not technical\n2. \"Latest news about TSLA?\" -> news, not analysis\n3. \"Execute a buy order for NVDA\" -> brokerage, not analysis\n4. \"Options strategies for AAPL\" -> options, not indicators\n```\n\n---\n\n## Validation & Testing\n\n### Testing Process\n\n**Minimum Test Coverage:**\n- 10+ query variations per major capability\n- Document all test queries in your SKILL.md body (under a Testing section)\n- Manual testing of each variation\n- No false positives in counter-examples\n\n### Improving Activation Reliability\n\nIf a test query fails to activate the skill:\n\n1. **Check if the query's key terms appear in the description.** If \"momentum\" isn't there but users ask about momentum, add it.\n2. **Add synonyms in parentheses.** If users say \"technical indicators\" but the description only says \"RSI, MACD\", add the general term too.\n3. **Add counter-examples.** If the skill activates for wrong queries, add \"Does NOT activate for: {those cases}\" to the description.\n\n### Validation Checklist\n\n```markdown\n## Description Quality\n- [ ] Primary use case stated upfront?\n- [ ] All capabilities mentioned with synonyms?\n- [ ] 50+ unique keywords embedded as natural prose?\n- [ ] Action verbs included?\n- [ ] Counter-examples documented?\n- [ ] 200-500 characters length?\n\n## Testing\n- [ ] 10+ positive test queries per capability?\n- [ ] All test queries activate the skill?\n- [ ] Negative test queries do NOT activate?\n- [ ] No false positives found?\n- [ ] No false negatives found?\n```\n\n---\n\n## Final Phase 4 Checklist\n\n### Keyword Research (Steps 1-7)\n- [ ] Domain entities listed (organizations, objects, metrics)\n- [ ] Action verbs listed (analyze, compare, monitor, track)\n- [ ] 50+ keywords generated via systematic expansion\n- [ ] Question variations mapped\n- [ ] Negative scope defined\n\n### Description Writing\n- [ ] All keywords embedded into SKILL.md `description` as natural prose\n- [ ] Primary use case stated upfront\n- [ ] All capabilities mentioned with synonyms\n- [ ] Counter-examples documented (\"Does NOT activate for\")\n- [ ] 200-500 characters length\n\n### Testing\n- [ ] 10+ positive test queries per capability\n- [ ] Negative test queries for out-of-scope requests\n- [ ] All test queries activate correctly\n- [ ] Counter-examples correctly do NOT activate\n- [ ] No false positives found\n\n---\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":27505,"content_sha256":"7dee7d817aa8eab9c016c8866020dc9caa4067ddfc1bbc3204ae27ae26903fcd"},{"filename":"references/phase5-orchestration.md","content":"# Phase 5 — Pipeline Orchestration\n\nThis step runs inside Phase 5 (Implementation) for any skill whose work is **two\nor more scripts that must run in a fixed order**. It exists to fix a real failure\nmode: a skill is prose an agent interprets, so if correct execution depends on the\nagent re-deriving the step order and hand-carrying data between scripts every run,\nit will eventually run the wrong script, skip a step, or pass the wrong input.\n\nThe fix is to **move sequencing out of prose and into code.**\n\n## The rule\n\nWhen a skill has 2+ scripts that form a pipeline:\n\n1. **Emit one orchestrator entry-point** — `scripts/run_pipeline.py` — that imports\n each step's function and calls them in order, wiring each step's output into the\n next step's input **in code**. The step scripts keep their own `main()` for\n isolated testing, but the orchestrator is the real run path.\n2. **Give the agent exactly one happy-path command** in the SKILL.md. Not \"run\n fetch, then parse, then analyze\" — instead: \"run `python scripts/run_pipeline.py`\".\n The agent's job collapses from *sequence N steps correctly* to *run one command*.\n3. **Wire data in code, never via the agent.** Step B reads step A's return value /\n output file because `run_pipeline.py` passes it — not because the SKILL.md asks\n the agent to copy it across.\n4. **Declare dependencies.** Every third-party import goes in `requirements.txt`.\n5. **Prove it with the eval.** Because `run_pipeline.py` is a deterministic\n entry-point, you run it on a golden input to produce output, then have\n `run_evals.py --output \u003cthat-output>` assert it against the skill's binary\n checks. The two chain together into a real end-to-end check (see\n `phase2-eval-assessment.md`) — turning \"the agent runs the right scripts in\n order\" from a hope into a verified result. (`run_evals.py` does not invoke the\n pipeline itself; you run the orchestrator, then score its output.)\n\n## Orchestrator shape\n\n```python\n#!/usr/bin/env python3\n\"\"\"Single entry-point: runs the skill's steps in order.\"\"\"\nimport argparse\nfrom pathlib import Path\n\nfrom fetch import fetch # step 1\nfrom parse import parse # step 2\nfrom analyze import analyze # step 3\n\n\ndef run_pipeline(source: str, out: Path) -> Path:\n raw = fetch(source) # step 1 output ...\n clean = parse(raw) # ... feeds step 2 in code ...\n result = analyze(clean) # ... feeds step 3 in code\n out.write_text(result)\n return out\n\n\ndef main() -> None:\n ap = argparse.ArgumentParser()\n ap.add_argument(\"--source\", required=True)\n ap.add_argument(\"--out\", default=\"output.json\")\n args = ap.parse_args()\n run_pipeline(args.source, Path(args.out))\n\n\nif __name__ == \"__main__\":\n main()\n```\n\nThe SKILL.md then documents one command: `python scripts/run_pipeline.py --source \u003cX>`.\n\n## Verify in Phase 5\n\nRun the verifier alongside `validate.py` and `security_scan.py`:\n\n```bash\npython3 scripts/check_pipeline.py \u003cskill-dir>\n```\n\nIt enforces the mechanical half of the contract and must report no errors:\n\n- **compile** — every `.py` under `scripts/` and `shared/` compiles (a broken\n script is the top reason an agent flails).\n- **deps** — if any third-party module is imported, `requirements.txt` must be\n present and non-empty. (Checks declaration, not per-package coverage — avoids the\n import-name vs distribution-name false-positive trap.)\n- **entry** *(warning)* — 2+ runnable step scripts but no `scripts/run_pipeline.py`.\n\n## Honest boundary\n\nA rigid orchestrator is **wrong** for skills that are genuinely interactive or\nbranch on agent judgment per step (e.g. \"ask the user, then decide which analysis\nto run\"). For those, keep the prose workflow — the agent's decisions are the point.\nThe rule is: **one deterministic orchestrator for the deterministic happy-path;\nprose only for the parts that truly need agent judgment.** Do not force every skill\ninto a runner, and do not collapse a genuinely branching workflow into a fake linear\none.\n\nTwo things to know about the verifier's limits:\n\n- `check_pipeline.py` checks that `run_pipeline.py` *exists*, not that it correctly\n wires every step — the eval (check 5) is the wiring proof. A skeleton orchestrator\n passes the existence check but fails the eval.\n- The `entry` warning fires whenever 2+ scripts have a `__main__` guard. If your\n skill is intentionally a set of **independent CLIs** (not a sequenced pipeline),\n the warning is a false positive — ignore it.\n\n## Acceptance checks (locked)\n\nA multi-script skill satisfies this step when all five are yes:\n\n1. It emits **one** orchestrator entry-point that runs the steps in order.\n2. Its SKILL.md gives the agent **exactly one** happy-path command, not N steps.\n3. Step-to-step data dependencies are wired **in code**, never carried by the agent.\n4. It declares its deps (`requirements.txt`) and `check_pipeline.py` reports no\n compile or undeclared-dependency errors.\n5. The pipeline is **end-to-end checkable**: `run_pipeline.py` produces output on\n a golden input, and `run_evals.py --output \u003cthat-output>` asserts the final\n result against the binary checks (a two-command chain).\n\n## Out of scope\n\n- Rewriting genuinely interactive/branching skills into linear pipelines.\n- A workflow engine / DAG framework — a plain Python entry-point is the design.\n- Per-package requirement coverage matching (declaration presence only).\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":5465,"content_sha256":"61933ff8e447f1f22e6d31509e0bbd463bab0178d77dc5cddc26e15d419363ad"},{"filename":"references/pipeline-phases.md","content":"# Pipeline Phases: Complete 5-Phase Skill Creation Reference\n\n**Version:** 6.0\n**Purpose:** Consolidated reference for the autonomous 5-phase skill creation pipeline used by agent-skill-creator v6.0.\n\nThis document contains the detailed instructions for each phase of skill creation, following the Agent Skills Open Standard (SKILL.md-first, `-skill` suffix on generated names, spec-compliant frontmatter, cross-platform support).\n\n---\n\n## Pipeline Overview\n\n```\nPhase 1: DISCOVERY -> Research APIs, data sources, domain mapping\nPhase 2: DESIGN -> Define use cases, analyses, methodologies\nPhase 3: ARCHITECTURE -> Structure skill directory (standard-compliant)\nPhase 4: DETECTION -> Generate description + keywords for activation\nPhase 5: IMPLEMENTATION -> Create all files, validate, security scan\n```\n\n**Key v6.0 principles:**\n\n- SKILL.md is the **primary file**, created first in Phase 5\n- Generated names use **kebab-case** and **must end with `-skill`**\n- Name: 1-64 chars, lowercase letters, numbers, hyphens; must match directory\n- Description: 1-1024 chars; this IS the activation mechanism\n- Generated SKILL.md must be **\u003c500 lines** (move detail to `references/`)\n- Frontmatter must include: `name`, `description`, `license`, `metadata` (author, version)\n- `install.sh` is generated for cross-platform support\n- `marketplace.json` is **NOT** needed for simple skills\n- Validation and security scan run at the end of Phase 5\n\n---\n\n# Phase 1: Discovery\n\n## Objective\n\nResearch and **DECIDE** autonomously which API or data source to use for the skill being created.\n\n## Detailed Process\n\n### Step 0: Input Triage\n\nBefore identifying the domain, classify what the user actually provided:\n\n| Input Type | Strategy |\n|---|---|\n| Well-formed description | Proceed to Step 1 normally |\n| Files only (Excel, PDF, code, CSV) | Reverse-engineer: open each file, reconstruct the workflow from structure. Tab names, column headers, formulas, and formatting ARE the specification |\n| URLs only | Fetch each URL. Understand the data source. Infer what the user would do with this data based on their role/context |\n| Screenshot/image | Read visually. Identify: what tool is shown? What data? What manual step is visible? What's the pain? |\n| Email/forwarded chain | Extract: who asked for what, what was agreed, what's the actual request (ignore disclaimers, scheduling, CC lists) |\n| Single word or phrase | Infer from context: user's desk/role, existing skills in their environment, databases available. Present the most likely interpretation and confirm |\n| Mixed (files + sentence) | The files are the spec. The sentence is commentary. Cross-reference both |\n| \"here\" + files | The files ARE the input. Process them all. Present your understanding. |\n| Pasted reference material (guidelines, policies, wiki pages, style guides) | This IS the knowledge to codify. Read it all. Identify what it governs (writing, design, compliance, process). The user wants an active skill that enforces these rules, not a summary |\n\n**After triage, ALWAYS present your understanding before proceeding:**\n\n\"From your [files/URLs/screenshot], I understand you [reconstructed workflow].\nThe output goes to [inferred recipient]. [Specific question if needed]. Right?\"\n\nThe user confirms with one word. Then proceed to Step 1.\n\n**Discovery check (before building):**\nBefore designing a new skill, verify:\n- Is this data already in a database the user has access to?\n- Has a colleague already built a skill for this?\n- Is there a simpler solution (existing API, existing tool, existing infrastructure)?\n\nThe best outcome is sometimes: \"You don't need a new skill — this data already exists in [database]. Let me show you how to query it.\"\n\n### Step 1: Identify Domain\n\nFrom user input, extract the main domain:\n\n| User Input | Identified Domain |\n|---|---|\n| \"US crop data\" | Agriculture (US) |\n| \"stock market analysis\" | Finance / Stock Market |\n| \"global climate data\" | Climate / Meteorology |\n| \"economic indicators\" | Economy / Macro |\n| \"commodity data\" | Trading / Commodities |\n\n### Step 2: Search Available APIs\n\nFor the identified domain, use WebSearch to find public APIs:\n\n**Search queries:**\n```\n\"[domain] API free public data\"\n\"[domain] government API documentation\"\n\"best API for [domain] historical data\"\n\"[domain] open data sources\"\n```\n\n**Example (US agriculture):**\n```bash\nWebSearch: \"US agriculture API free historical data\"\nWebSearch: \"USDA API documentation\"\nWebSearch: \"agricultural statistics API United States\"\n```\n\n**Typical result:** 5-10 candidate APIs.\n\n### Step 3: Research Documentation\n\nFor each candidate API, use WebFetch to load:\n- Homepage/overview\n- Getting started guide\n- API reference\n- Rate limits and pricing\n\n**Extract information per API:**\n\n```markdown\n## API: [Name]\n\n**URL**: [base URL]\n**Docs**: [docs URL]\n\n**Authentication**:\n- Type: API key / OAuth / None\n- Cost: Free / Paid\n- How to obtain: [steps]\n\n**Available Data**:\n- Temporal coverage: [from when to when]\n- Geographic coverage: [countries, regions]\n- Metrics: [list]\n- Granularity: [daily, monthly, annual]\n\n**Limitations**:\n- Rate limit: [requests per day/hour]\n- Max records: [per request]\n\n**Quality**:\n- Source: [official government / private]\n- Reliability: [high/medium/low]\n- Update frequency: [frequency]\n- Documentation quality: [excellent/good/poor]\n\n**Ease of Use**:\n- Format: JSON / CSV / XML\n- SDKs: [Python/R/None]\n- Quirks: [any non-obvious behavior]\n```\n\n### Step 4: API Capability Inventory\n\nEnsure the skill uses the maximum useful surface of the chosen API.\n\n**Step 4.1: Complete Inventory**\n\nFor the chosen API, catalog ALL data types:\n\n```markdown\n## Complete Inventory - {API Name}\n\n| Endpoint/Metric | Returns | Granularity | Coverage | Value |\n|---|---|---|---|---|\n| {metric1} | {description} | {daily/weekly} | {geo} | High |\n| {metric2} | {description} | {monthly} | {geo} | High |\n| {metric3} | {description} | {annual} | {geo} | Medium |\n```\n\n**Step 4.2: Coverage Decision**\n\n- If metric has high value: implement in v1.0\n- If API has 5 high-value metrics: implement all 5\n- Never leave >50% of API unused without strong justification\n\n**Step 4.3: Document Decision**\n\nIn `DECISIONS.md`:\n\n```markdown\n## API Coverage Decision\n\nAPI {name} offers {N} types of metrics.\n\n**Implemented in v1.0 ({X} of {N}):**\n- {metric1} - {justification}\n- {metric2} - {justification}\n\n**Not implemented ({Y} of {N}):**\n- {metricZ} - {why not} (planned for v2.0)\n\n**Coverage:** {X/N * 100}%\n```\n\n**Output of this step:** Exact list of all `get_*()` methods to implement.\n\n### Step 5: Compare Options\n\nCreate comparison table:\n\n| API | Coverage | Cost | Rate Limit | Quality | Docs | Ease | Score |\n|---|---|---|---|---|---|---|---|\n| API 1 | 5/5 | Free | 1000/day | Official | 4/5 | 5/5 | 9.2/10 |\n| API 2 | 4/5 | $49/mo | Unlimited | Private | 5/5 | 4/5 | 7.8/10 |\n\n**Scoring criteria:**\n- Coverage (fit with need): 30% weight\n- Cost (prefer free): 20% weight\n- Rate limit (sufficient?): 15% weight\n- Quality (official > private): 15% weight\n- Documentation (facilitates implementation): 10% weight\n- Ease of use (format, structure): 10% weight\n\n### Step 6: DECIDE\n\n**Consider user constraints:**\n- Mentioned \"free\"? Eliminate paid options\n- Mentioned \"10+ years historical data\"? Check coverage\n- Mentioned \"real-time\"? Prioritize streaming APIs\n\n**Apply logic:**\n1. Eliminate APIs that violate constraints\n2. Of remaining, choose highest score\n3. If tie, prefer: official > private, better docs, easier to use\n\n**Document the final decision:**\n\n```markdown\n## Selected API: [API Name]\n\n**Score**: X.X/10\n\n**Justification**:\n- Coverage: [specific details]\n- Cost: [free/paid + details]\n- Rate limit: [number] requests/day\n- Quality: [official/private + reliability]\n- Documentation: [quality + examples]\n\n**Alternatives Considered**:\n- API X: Score 7.5/10 - Rejected because [reason]\n- API Y: Score 6.2/10 - Rejected because [reason]\n```\n\n### Step 7: Research Technical Details\n\nAfter deciding, dive deep into documentation via WebFetch:\n- Getting started guide\n- Complete API reference\n- Authentication guide\n- Rate limiting details\n- Best practices\n\n**Extract for implementation:**\n\n```markdown\n## Technical Details - [API]\n\n### Authentication\n- Method: API key in header\n- Header: `X-Api-Key: YOUR_KEY`\n- Obtaining key: [step-by-step]\n\n### Main Endpoints\n- URL, parameters, response format, errors\n\n### Rate Limiting\n- Limit, response headers, behavior when exceeded\n\n### Quirks and Gotchas\n- Data formatting issues (e.g., values as strings with commas)\n- Suppressed data markers\n- Any non-obvious behavior\n\n### Performance Tips\n- What to cache and for how long\n- Pagination\n- Parallel requests\n```\n\n### Step 8: Document for Later Use\n\nSave everything in `references/api-guide.md` of the skill to be created.\n\n## Discovery Examples\n\n### Example 1: US Agriculture\n\n**Input**: \"US crop data\"\n\n**Research**:\n```\nWebSearch: \"USDA API agricultural data\"\n→ Found: NASS QuickStats, ERS, FAS\n\nWebFetch: https://quickstats.nass.usda.gov/api\n→ Free, data since 1866, 1000/day rate limit\n\nWebFetch: https://www.ers.usda.gov/developer/\n→ Free, economic focus, less granular\n\nWebFetch: https://apps.fas.usda.gov/api\n→ International focus, not domestic\n```\n\n**Comparison**:\n| API | Coverage (US domestic) | Cost | Production Data | Score |\n|-----|---------------------------|-------|-------------------|-------|\n| NASS | ⭐⭐⭐⭐⭐ (excellent) | Free | ⭐⭐⭐⭐⭐ | 9.5/10 |\n| ERS | ⭐⭐⭐⭐ (good) | Free | ⭐⭐⭐ (economic) | 7.0/10 |\n| FAS | ⭐⭐ (international) | Free | ⭐⭐ (global) | 4.0/10 |\n\n**DECISION**: NASS QuickStats API\n- Best coverage for US domestic agriculture\n- Free with reasonable rate limit\n- Complete production, area, yield data\n\n### Example 2: Stock Market\n\n**Input**: \"technical stock analysis\"\n\n**Research**:\n```\nWebSearch: \"stock market API free historical data\"\n→ Alpha Vantage, Yahoo Finance, IEX Cloud, Polygon.io\n\nWebFetch: Alpha Vantage docs\n→ Free, 5 requests/min, 500/day\n\nWebFetch: Yahoo Finance (yfinance)\n→ Free, unlimited but unofficial\n\nWebFetch: IEX Cloud\n→ Freemium, good docs, 50k free credits/month\n```\n\n**Comparison**:\n| API | Data | Cost | Rate Limit | Official | Score |\n|-----|-------|-------|------------|---------|-------|\n| Alpha Vantage | Complete | Free | 500/day | ⭐⭐⭐ | 8.0/10 |\n| Yahoo Finance | Complete | Free | Unlimited | ❌ Unofficial | 7.5/10 |\n| IEX Cloud | Excellent | Freemium | 50k/month | ⭐⭐⭐⭐ | 8.5/10 |\n\n**DECISION**: IEX Cloud (free tier)\n- Official and reliable\n- 50k requests/month sufficient\n- Excellent documentation\n- Complete data (OHLCV + volume)\n\n### Example 3: Global Climate\n\n**Input**: \"global climate data\"\n\n**Research**:\n```\nWebSearch: \"weather API historical data global\"\n→ NOAA, OpenWeather, Weather.gov, Meteostat\n\n[Research each one...]\n```\n\n**DECISION**: NOAA Climate Data Online (CDO) API\n- Official (US government)\n- Free\n- Global and historical coverage (1900+)\n- Rate limit: 1000/day\n\n## Phase 1 Checklist\n\n- [ ] Research completed (WebSearch + WebFetch)\n- [ ] Minimum 3 APIs compared\n- [ ] Decision made with clear justification\n- [ ] User constraints respected\n- [ ] API capability inventory completed\n- [ ] Technical details extracted\n- [ ] DECISIONS.md content prepared\n- [ ] Ready for analysis design\n\n---\n\n# Phase 2: Design\n\n## Objective\n\n**DEFINE** autonomously which analyses the skill will perform and how.\n\n## Additional Steps\n\n- **(new in v6.0)** Artifact Opportunity Assessment — call\n `artifact_detector.detect_artifact(description, domain)`. If a template\n is returned, inline it into the SKILL.md body along with emission\n instructions. See `phase2-artifact-assessment.md`.\n- **(new)** Eval Criteria Definition — derive 3–6 binary checks (each graded\n by a shell `command` or flagged `llm-judge`) plus ≥3 golden cases that define\n the skill's loss function. Written to `evals/\u003cname>.eval.md` in Phase 5; on by\n default, `--no-eval` opts out. See `phase2-eval-assessment.md`.\n\n## Detailed Process\n\n### Step 1: Brainstorm Use Cases\n\nFrom the workflow described by the user, think of typical questions they will ask.\n\n**Technique:** \"If I were this user, what would I ask?\"\n\n**Example (US agriculture):**\n\nUser said: \"download crop data, compare year vs year, make rankings\"\n\nTypical questions:\n1. \"What's the corn production in 2023?\"\n2. \"How's soybean compared to last year?\"\n3. \"Did production grow or fall?\"\n4. \"Does growth come from area or productivity?\"\n5. \"Which states produce most wheat?\"\n6. \"Top 5 soybean producers\"\n7. \"Production trend last 5 years?\"\n8. \"Average US yield\"\n9. \"Compare Midwest vs South\"\n10. \"Production by region\"\n\n**Goal:** List 15-20 typical questions.\n\n### Step 2: Group by Analysis Type\n\nGroup similar questions:\n\n**Group 1: Simple Queries** (fetching + formatting)\n- Required analysis: **Data Retrieval**\n- Complexity: Low\n\n**Group 2: Temporal Comparisons** (YoY)\n- Required analysis: **YoY Comparison + Decomposition**\n- Complexity: Medium\n\n**Group 3: Rankings** (sorting + share)\n- Required analysis: **State/Entity Ranking**\n- Complexity: Medium\n\n**Group 4: Trends** (time series)\n- Required analysis: **Trend Analysis**\n- Complexity: Medium-High\n\n**Group 5: Projections** (forecasting)\n- Required analysis: **Forecasting**\n- Complexity: High\n\n**Group 6: Geographic Aggregations**\n- Required analysis: **Regional Aggregation**\n- Complexity: Medium\n\n### Step 3: Prioritize Analyses\n\n**Prioritization criteria:**\n1. **Frequency of use** (based on described workflow)\n2. **Analytical value** (insight vs effort)\n3. **Implementation complexity** (easier first)\n4. **Dependencies** (does one analysis depend on another?)\n\nScore each analysis on these criteria and implement the top 4-6 that cover 80% of use cases. Always include a **comprehensive report function** that combines multiple analyses into a single summary.\n\n### Step 4: Specify Each Analysis\n\nFor each selected analysis, document:\n\n```markdown\n## Analysis: [Name]\n\n**Objective**: [What it does in 1 sentence]\n**When to use**: [Types of questions that trigger it]\n\n**Required inputs**:\n- Input 1: [type, description]\n- Input 2: [type, description]\n\n**Expected outputs**:\n- Output 1: [type, description]\n\n**Methodology**: [Explanation in natural language]\n\n**Formulas**:\n- Formula 1 = ...\n\n**Validations**:\n- Validation 1: [criteria]\n\n**Interpretation**:\n- If result > X: [interpretation]\n- If result \u003c Y: [interpretation]\n\n**Concrete example**:\n- Input: [specific values]\n- Processing: [step by step calculation]\n- Output: [JSON with result]\n- Response to user: [formatted answer]\n```\n\n### Step 5: Specify Methodologies\n\nFor quantitative analyses, detail methodology with formulas.\n\n**Example: YoY Decomposition**\n\n```\nProduction = Area x Yield\n\nChange_Production ~ Change_Area x Yield(t-1) + Area(t-1) x Change_Yield\n\nContrib_Area = (Change_Area% / Change_Production%) x 100\nContrib_Yield = (Change_Yield% / Change_Production%) x 100\n```\n\n**Interpretation:**\n- Contrib_Area > 60%: Extensive growth (area expansion is main driver)\n- Contrib_Yield > 60%: Intensive growth (technology improvement is main driver)\n- Both ~50%: Balanced growth\n\n**Validation:**\n- Production(t) approximately equals Area(t) x Yield(t) (margin 1%)\n- Contrib_Area + Contrib_Yield approximately equals 100% (margin 5%)\n\n### Step 6: Comprehensive Report Function\n\nAlways design a comprehensive report function that:\n- Combines data from multiple analyses\n- Provides an executive summary\n- Includes key metrics, comparisons, and trends\n- Is the single most useful output of the skill\n\n### Step 7: Document Analyses\n\nSave all specifications in `references/analysis-methods.md` of the skill.\n\n## Phase 2 Checklist\n\n- [ ] 15+ typical questions listed\n- [ ] Questions grouped by analysis type\n- [ ] 4-6 analyses prioritized (with scoring)\n- [ ] Each analysis specified (objective, inputs, outputs, methodology)\n- [ ] Methodologies detailed with formulas\n- [ ] Validations defined\n- [ ] Interpretations specified\n- [ ] Concrete examples included\n- [ ] Comprehensive report function designed\n\n---\n\n# Phase 3: Architecture\n\n## Objective\n\n**STRUCTURE** the skill using the Agent Skills Open Standard: directory layout, files, responsibilities, cache, performance.\n\n## Detailed Process\n\n### Step 1: Define Skill Name\n\n**Format:** `{domain}-{objective}-skill` — kebab-case per the Agent Skills Open Standard.\n\n**Rules:**\n- 1-64 characters\n- Lowercase letters, numbers, and hyphens only\n- Must not start or end with hyphen\n- Must not contain consecutive hyphens\n- Must match parent directory name\n- **Must end with `-skill`**\n\n**Examples:**\n- `stock-analyzer-skill`\n- `csv-data-cleaner-skill`\n- `weekly-report-generator-skill`\n- `nass-agriculture-monitor-skill`\n- `noaa-climate-analysis-skill`\n\n### Step 2: Directory Structure\n\nAll skills follow the Agent Skills Open Standard structure:\n\n**Simple Skill (1-2 workflows, \u003c1000 lines):**\n\n```\nskill-name/\n├── SKILL.md # Primary file, \u003c500 lines\n├── scripts/\n│ └── main.py\n├── references/\n│ └── guide.md\n├── assets/\n│ └── config.json\n├── install.sh # Cross-platform installer\n└── README.md # Multi-platform install instructions\n```\n\n**Organized Skill (3-5 scripts, medium complexity):**\n\n```\nskill-name/\n├── SKILL.md\n├── scripts/\n│ ├── fetch.py\n│ ├── parse.py\n│ ├── analyze.py\n│ └── utils/\n│ ├── cache.py\n│ └── validators.py\n├── references/\n│ ├── api-guide.md\n│ └── analysis-methods.md\n├── assets/\n│ └── config.json\n├── install.sh\n└── README.md\n```\n\n**Complex Skill (6+ scripts, large scope):**\n\n```\nskill-name/\n├── SKILL.md\n├── scripts/\n│ ├── core/\n│ │ ├── fetch_source.py\n│ │ ├── parse_source.py\n│ │ └── analyze_source.py\n│ ├── models/\n│ │ └── forecasting.py\n│ └── utils/\n│ ├── cache_manager.py\n│ ├── rate_limiter.py\n│ └── validators.py\n├── references/\n│ ├── api-guide.md\n│ ├── analysis-methods.md\n│ └── troubleshooting.md\n├── assets/\n│ ├── config.json\n│ └── metadata.json\n├── install.sh\n└── README.md\n```\n\n**Important:** There is NO `.claude-plugin/marketplace.json` required for simple skills. The SKILL.md file with its frontmatter is sufficient for discovery and activation on all platforms.\n\n### Step 3: Simple vs Complex Suite Decision\n\n| Factor | Simple Skill | Complex Suite |\n|---|---|---|\n| Workflows | 1-2 | 3+ distinct |\n| Code size | \u003c1000 lines | >2000 lines |\n| Maintenance | Single developer | Team |\n| Structure | Single SKILL.md | Multiple component SKILL.md files |\n| marketplace.json | Not needed | Optional (official fields only) |\n\n**Default:** Start with simple skill. Upgrade to complex suite only when warranted.\n\n### Step 4: Define Script Responsibilities\n\n**Principle:** Separation of Concerns.\n\n**Typical scripts:**\n\n| Script | Responsibility | Does NOT | Size |\n|---|---|---|---|\n| `fetch_source.py` | API requests, auth, rate limiting | Parse, transform, analyze | 200-300 lines |\n| `parse_source.py` | Parsing, cleaning, validation | Fetch, analyze | 150-200 lines |\n| `analyze_source.py` | All analyses (YoY, ranking, etc.) | Fetch, parse | 300-500 lines |\n\n**Typical utils:**\n\n| Util | Responsibility | Size |\n|---|---|---|\n| `cache_manager.py` | Response cache, differentiated TTL | 100-150 lines |\n| `rate_limiter.py` | Rate limit control, persistent counter | 100-150 lines |\n| `validators.py` | Data validations, consistency checks | 100-150 lines |\n\n### Step 5: Plan References\n\nDetailed documentation files loaded on demand:\n\n| File | Content | Size |\n|---|---|---|\n| `api-guide.md` | How to get API key, endpoints, parameters, response format, quirks | ~1500 words |\n| `analysis-methods.md` | Each analysis explained, formulas, interpretations, examples | ~2000 words |\n| `troubleshooting.md` | Common problems, step-by-step solutions, FAQs | ~1000 words |\n\n### Step 6: Plan Assets\n\n**config.json** structure:\n\n```json\n{\n \"api\": {\n \"base_url\": \"https://api.example.com/v1\",\n \"api_key_env\": \"API_KEY_VAR\",\n \"_instructions\": \"Get free key from: https://example.com/register\",\n \"rate_limit_per_day\": 1000,\n \"timeout_seconds\": 30\n },\n \"cache\": {\n \"enabled\": true,\n \"ttl_historical_days\": 365,\n \"ttl_current_days\": 7\n },\n \"defaults\": {\n \"param1\": \"value1\"\n }\n}\n```\n\n### Step 7: Cache and Rate Limiting Strategy\n\n**Cache rules:**\n- Historical data (year \u003c current): Permanent cache (365+ days)\n- Current year data: Short cache (7 days, may be revised)\n- Metadata (lists, mappings): Permanent cache\n\n**Rate limiting:**\n- Persistent counter (file-based)\n- Pre-request verification\n- Alerts when near limit (>90%)\n- Blocking when limit reached\n\n### Step 8: Document Architecture\n\nPrepare content for `DECISIONS.md`:\n- Chosen directory structure and justification\n- Script responsibilities\n- Cache strategy and TTLs\n- Rate limiting approach\n\n## Phase 3 Checklist\n\n- [ ] Skill name defined (kebab-case, ends with `-skill`, 1-64 chars)\n- [ ] Directory structure chosen\n- [ ] Responsibilities of each script defined\n- [ ] References planned (which files, content)\n- [ ] Assets planned (which configs, structure)\n- [ ] Cache strategy defined (what, TTL)\n- [ ] Rate limiting strategy defined\n- [ ] Architecture documented\n\n---\n\n# Phase 4: Detection\n\n## Objective\n\nGenerate a **description** (\u003c=1024 characters) with domain keywords for agent discovery. The description in the SKILL.md frontmatter IS the primary activation mechanism across all platforms.\n\n**Key v4.0 change:** There are NO `activation.keywords` or `activation.patterns` fields in marketplace.json. The `description` field in SKILL.md frontmatter is the single activation mechanism. All keywords must be embedded in the description itself.\n\n## Detailed Process\n\n### Step 1: List Domain Entities\n\nIdentify all relevant entities users may mention:\n\n**Entity categories:**\n\n1. **Organizations/Sources**: Names, acronyms, full names (USDA, NASS, NOAA)\n2. **Main Objects**: Domain-specific items (commodities, instruments, metrics)\n3. **Geography**: Countries, regions, states\n4. **Metrics**: production, area, yield, price, revenue, temperature\n5. **Temporality**: years, seasons, current, historical, YoY\n\n### Step 2: List Actions/Verbs\n\nWhich verbs does the user use to request analyses?\n\n**Categories:**\n- **Query**: what is, how much, show me, get, tell me, find\n- **Compare**: compare, versus, vs, difference, change, growth\n- **Rank**: top, best, leading, biggest, rank, ranking, list\n- **Analyze**: analyze, trend, pattern, evolution, breakdown\n- **Forecast**: predict, project, forecast, outlook, estimate\n- **Report**: report, dashboard, summary, overview\n\n### Step 3: Generate Comprehensive Keywords\n\nFor EACH metric/capability the skill implements, generate keywords:\n\n```markdown\nMetric 1: [metric name]\nPrimary keywords: [3-5 keywords]\nSecondary keywords: [3-5 synonyms]\nAction keywords: [2-3 verbs specific to this metric]\nTotal: ~10-15 keywords per metric\n```\n\n**Goal:** 50-80 unique keywords total across all metrics.\n\n### Step 4: List Question Variations\n\nFor each analysis type, enumerate how users might ask:\n\n**YoY Comparison:**\n- \"Compare X this year vs last year\"\n- \"How does X compare to last year\"\n- \"X growth rate\"\n- \"X change YoY\"\n- \"Did X increase or decrease\"\n\n**Ranking:**\n- \"Top states for X\"\n- \"Which states produce most X\"\n- \"Leading X producers\"\n- \"Ranking of X\"\n\n**Trend:**\n- \"X trend last N years\"\n- \"How has X changed over time\"\n- \"Historical X data\"\n\n### Step 5: Define Negative Scope\n\nWhat should NOT activate the skill? Avoid false positives.\n\n```markdown\n## Skill Scope\n\n### WITHIN scope:\n- [specific capability 1]\n- [specific capability 2]\n\n### OUT of scope:\n- [related but unsupported topic 1]\n- [related but unsupported topic 2]\n```\n\n### Step 6: Create the Description\n\nThe description must be \u003c=1024 characters and serve as the sole activation mechanism. Pack it with the most important keywords.\n\n**Template:**\n\n```yaml\ndescription: >-\n [What the skill does]. Activates when users ask to [primary use case],\n [secondary use case], or [tertiary use case]. Triggers on phrases like\n [keyword phrase 1], [keyword phrase 2], [keyword phrase 3], [keyword\n phrase 4]. Supports [capability 1], [capability 2], [capability 3].\n Uses [technology/API] to [what it does with real data].\n```\n\n**Mandatory components:**\n1. Domain with specific entities (not just \"crops\" but \"corn, soybeans, wheat\")\n2. Each major API metric explicitly mentioned\n3. Action verbs covered (compare, rank, analyze, report)\n4. Temporal context (current, historical, year-over-year)\n5. Geographic context if relevant (states, regions, national)\n6. Data source name (USDA NASS, Alpha Vantage, etc.)\n\n**Constraints:**\n- Must be 1-1024 characters\n- Must be a single string (use `>-` for YAML folding)\n- No line breaks in the final output\n\n**Real example:**\n\n```yaml\ndescription: >-\n Analyze US agricultural production using official USDA NASS data.\n Activates when users ask about crop production, area planted, yield,\n harvest progress, or crop conditions for corn, soybeans, wheat, and\n other commodities. Triggers on phrases like compare corn production,\n top soybean states, wheat yield trend, crop condition report, harvest\n progress update. Supports year-over-year comparisons, state rankings,\n trend analyses, growth decomposition, regional aggregations, and\n comprehensive crop reports. Uses Python with NASS QuickStats API to\n fetch real data on production, area, yield, conditions, and progress.\n```\n\n### Step 7: Mental Testing\n\nFor each example question from Phase 2, verify:\n- Does the description contain relevant keywords?\n- Would an LLM reading the description match this query?\n\nIf any use case would NOT be detected, add missing keywords to the description.\n\n### Step 8: Document Keywords in SKILL.md Body\n\nIn the SKILL.md body (not frontmatter), include a keywords section for transparency:\n\n```markdown\n## Keywords for Automatic Detection\n\nThis skill is activated when user mentions:\n\n**Entities**: [list]\n**Geography**: [list]\n**Metrics**: [list]\n**Actions**: [list]\n\n**Activation examples:**\n- \"[example 1]\"\n- \"[example 2]\"\n- \"[example 3]\"\n\n**Does NOT activate for:**\n- \"[out of scope 1]\"\n- \"[out of scope 2]\"\n```\n\n## Phase 4 Checklist\n\n- [ ] Domain entities listed (organizations, objects, geography)\n- [ ] Actions/verbs listed\n- [ ] 50+ keywords generated across all metrics\n- [ ] Question variations mapped\n- [ ] Negative scope defined\n- [ ] Description created (\u003c=1024 chars, packed with keywords)\n- [ ] Keywords documented in SKILL.md body\n- [ ] Activation examples (positive and negative)\n- [ ] Mental detection simulation (all use cases covered)\n\n---\n\n# Phase 5: Implementation\n\n## Objective\n\n**IMPLEMENT** everything with functional code, useful documentation, and real configs. Then **validate** against the spec and run a **security scan**.\n\n## Quality Rules (Non-Negotiable)\n\n### NEVER:\n\n```python\n# FORBIDDEN: placeholder code\ndef analyze():\n # TODO: implement this function\n pass\n```\n\n```markdown\n\u003c!-- FORBIDDEN: empty reference -->\nFor more details, consult the official documentation at [external link].\n```\n\n```json\n// FORBIDDEN: placeholder config\n{ \"api_key\": \"YOUR_API_KEY_HERE\" }\n```\n\n### ALWAYS:\n\n- Complete, functional code in every function\n- Detailed docstrings with Args, Returns, Raises, Example\n- Type hints on all public functions\n- Robust error handling with specific exceptions\n- Input and output validations\n- Real values in configs with instructions for user-provided values\n- Self-contained content in references (not just links)\n\n## Implementation Order\n\nExecute these 10 steps in order:\n\n### Step 1: Create Directory Structure\n\n```bash\nmkdir -p skill-name/{scripts,references,assets}\n```\n\nNo `.claude-plugin/` directory needed for simple skills.\n\n### Step 2: Write SKILL.md (PRIMARY FILE - CREATE FIRST)\n\nThe SKILL.md is the most important file. It must have spec-compliant frontmatter and be \u003c500 lines.\n\n**Required frontmatter:**\n\n```yaml\n---\nname: skill-name\ndescription: >-\n Description here, \u003c=1024 chars, packed with activation keywords.\nlicense: MIT\nmetadata:\n author: Author Name\n version: 1.0.0\n created: 2026-02-27\n last_reviewed: 2026-02-27\n review_interval_days: 90\n dependencies:\n - url: https://api.example.com/v1\n name: Example API\n type: api\n---\n```\n\n**Frontmatter field rules:**\n- `name`: 1-64 chars, lowercase + hyphens, must match directory name\n- `description`: 1-1024 chars, the activation mechanism\n- `license`: Required (MIT, Apache-2.0, etc.)\n- `metadata.author`: Required\n- `metadata.version`: Required, semver format\n\n**Body structure (must be \u003c500 lines total including frontmatter):**\n\n```markdown\n# Skill Name\n\n[Introduction: 2-3 paragraphs]\n\n## When to Use This Skill\n\n[Activation triggers with examples]\n\n## Data Source\n\n[API summary, link to references/api-guide.md for details]\n\n## Workflows\n\n### Workflow 1: [Name]\n[Step-by-step with commands and examples]\n\n### Workflow 2: [Name]\n[Step-by-step with commands and examples]\n\n## Available Scripts\n\n[Brief description of each script, inputs, outputs]\n\n## Available Analyses\n\n[Brief description of each analysis, link to references/ for details]\n\n## Error Handling\n\n[Common errors and how the skill handles them]\n\n## Keywords for Detection\n\n[Organized keyword list]\n\n## Usage Examples\n\n[3-5 complete examples with question, flow, and answer]\n\n## References\n\n[Table of reference files and what they contain]\n```\n\n**Keeping under 500 lines:** Move detailed content to `references/`:\n- Detailed API docs go to `references/api-guide.md`\n- Detailed methodologies go to `references/analysis-methods.md`\n- Troubleshooting goes to `references/troubleshooting.md`\n\n### Step 2.5: Write AGENTS.md (Companion Instruction File)\n\nGenerate an AGENTS.md alongside SKILL.md to maximize cross-tool reach. ~15 tools read AGENTS.md (AAIF-governed format), including some that don't read SKILL.md (Augment, Continue.dev, Zed).\n\n**Template:**\n\n```markdown\n# skill-name\n\n> [One-line description from SKILL.md frontmatter]\n\n## Purpose\n\n[2-3 sentences explaining what this skill does and when to use it]\n\n## Activation\n\nThis skill activates when users ask about [domain keywords]. Invoke with `/skill-name` on platforms that support slash commands, or ask naturally.\n\n**Example queries:**\n- \"[example 1]\"\n- \"[example 2]\"\n- \"[example 3]\"\n\n## Usage\n\n[Brief usage instructions — what to provide, what to expect back]\n\n## Implementation\n\nFull skill definition, scripts, and references are in the SKILL.md file and accompanying directories. See SKILL.md for complete instructions.\n\n## Files\n\n- `SKILL.md` — Full skill definition (agentskills.io format)\n- `scripts/` — Executable code (`run_pipeline.py` orchestrator for multi-script skills, `run_evals.py` eval runner)\n- `references/` — Detailed documentation\n- `assets/` — Templates, configs\n- `evals/` — Bundled eval spec: binary checks + golden cases\n- `install.sh` — Cross-platform installer\n```\n\n**Rules:**\n- Keep AGENTS.md concise (~50-100 lines). It is a pointer and summary, not a duplicate of SKILL.md.\n- Include enough context for tools that ONLY read AGENTS.md (they will not see SKILL.md).\n- Include activation keywords so description-based matching works.\n\n### Step 3: Implement Python Scripts\n\nEvery script must follow this quality standard:\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nScript title in 1 line.\n\nDetailed description: what it does, how it works,\nwhen to use, inputs and outputs.\n\nExample:\n $ python script.py --param1 value1\n\"\"\"\n\n# 1. Standard library imports\nimport sys\nimport os\nfrom pathlib import Path\nfrom typing import Dict, List, Optional\nfrom datetime import datetime\n\n# 2. Third-party imports\nimport requests\n\n# 3. Local imports\nfrom utils.cache_manager import CacheManager\n\n\n# Constants\nAPI_BASE_URL = \"https://...\"\nDEFAULT_TIMEOUT = 30\n\n\nclass MainClass:\n \"\"\"\n Class description.\n\n Attributes:\n attr1: description\n attr2: description\n\n Example:\n >>> obj = MainClass(param)\n >>> result = obj.method()\n \"\"\"\n\n def __init__(self, param1: str, param2: int = 10):\n \"\"\"\n Initialize MainClass.\n\n Args:\n param1: detailed description\n param2: detailed description. Defaults to 10.\n\n Raises:\n ValueError: If param1 is invalid\n \"\"\"\n if not param1:\n raise ValueError(\"param1 cannot be empty\")\n self.param1 = param1\n self.param2 = param2\n\n def main_method(self, input_val: str) -> Dict:\n \"\"\"\n What the method does.\n\n Args:\n input_val: description\n\n Returns:\n Dict with keys:\n - key1: description\n - key2: description\n\n Raises:\n APIError: If API request fails\n\n Example:\n >>> obj.main_method(\"value\")\n {'key1': 123, 'key2': 'abc'}\n \"\"\"\n if not self._validate_input(input_val):\n raise ValueError(f\"Invalid input: {input_val}\")\n\n try:\n result = self._do_work(input_val)\n return result\n except Exception as e:\n print(f\"Error: {e}\")\n raise\n\n\ndef main():\n \"\"\"Main function with argparse.\"\"\"\n import argparse\n\n parser = argparse.ArgumentParser(description=\"Script description\")\n parser.add_argument('--param1', required=True, help=\"Parameter description\")\n parser.add_argument('--output', default='output.json', help=\"Output file path\")\n\n args = parser.parse_args()\n obj = MainClass(args.param1)\n result = obj.main_method(args.param1)\n\n import json\n output_path = Path(args.output)\n output_path.parent.mkdir(parents=True, exist_ok=True)\n with open(output_path, 'w') as f:\n json.dump(result, f, indent=2)\n print(f\"Saved: {output_path}\")\n\n\nif __name__ == \"__main__\":\n main()\n```\n\n**Checklist per script:**\n- Correct shebang (`#!/usr/bin/env python3`)\n- Complete module docstring\n- Organized imports (stdlib, third-party, local)\n- Type hints on all public functions\n- Docstrings with Args, Returns, Raises, Example\n- Error handling for risky operations\n- Input and output validations\n- Main function with argparse\n- `if __name__ == \"__main__\"` guard\n- No TODO, no `pass`, no `NotImplementedError`\n\n**Script patterns by type:**\n\n**fetch_source.py** (200-300 lines):\n- API client class with authentication\n- Rate limiting integration\n- Request retry with exponential backoff\n- Response validation\n- Cache integration (check cache before API call)\n\n**parse_source.py** (150-200 lines):\n- Parse raw API JSON to structured data\n- Clean data (remove formatting, handle nulls)\n- Transform data (standardize names, convert units)\n- Validate data (required fields, ranges, no duplicates)\n\n**analyze_source.py** (300-500 lines):\n- All analysis functions (YoY, ranking, trend, etc.)\n- Comprehensive report function\n- Each function: validate inputs, compute, interpret, return structured result\n\n**run_pipeline.py** (orchestrator — required when 2+ scripts run in sequence):\n- Single entry-point that imports each step's function and calls them in order\n- Wires each step's output into the next step's input **in code** (never via the agent)\n- Lets the SKILL.md give the agent ONE happy-path command instead of N prose steps\n- Skip for genuinely interactive/branching skills. See `phase5-orchestration.md`\n\n### Step 4: Write References\n\nDetailed documentation files. Each must be self-contained with real content.\n\n**api-guide.md** (~1500 words):\n- How to get API key (step-by-step)\n- Main endpoints with example requests and responses\n- Parameter details with types and valid values\n- Response format with field descriptions\n- Rate limits and how to handle them\n- Known quirks and workarounds\n\n**analysis-methods.md** (~2000 words):\n- Each analysis explained with objective and methodology\n- Mathematical formulas\n- Interpretation guidelines\n- Validation criteria\n- Complete numerical examples with real values\n\n**troubleshooting.md** (~1000 words):\n- Common problems with symptoms, causes, and solutions\n- Error messages and what they mean\n- Step-by-step debugging procedures\n\n### Step 5: Write Assets\n\n**config.json**: Real API URLs, env var names for keys, rate limits, cache TTLs, default parameters. Always include `_instructions` or `_note` fields explaining user-provided values.\n\n**metadata.json** (if needed): Domain-specific mappings, aliases, conversions, groupings.\n\n### Step 5.5: Emit Eval Spec (skip if `--no-eval`)\n\nWrite the skill's loss function so it ships with the skill and doubles as a\nregression test:\n\n1. Write `evals/\u003cskill-name>.eval.md` — prose plus a fenced ` ```json ` block\n carrying the `criteria` (binary; each `command` with a `cmd`, or `llm-judge`)\n and the `golden` cases derived in Phase 2. Place golden inputs/expected files\n under `evals/golden/\u003ccase-id>/`.\n2. Copy the runner verbatim:\n ```bash\n cp scripts/run_evals_template.py \u003cskill>/scripts/run_evals.py\n chmod +x \u003cskill>/scripts/run_evals.py\n ```\n3. Golden cases: seed from the user's artifacts when available; otherwise\n synthesize input-only cases marked `expected_status: \"pending-first-green\"`.\n\nSee `phase2-eval-assessment.md` for the full format, criteria rules, and the\n`autoresearch-universal` handoff (its rule 18 consumes this spec directly). On\nby default; `--no-eval` skips this step and emits no `evals/` directory.\n\n### Step 6: Generate install.sh\n\nGenerate the installer from `scripts/install-template.sh` — the canonical template. Replace `{{SKILL_NAME}}` with the actual skill name and `chmod +x`:\n\n```bash\n# During skill generation:\nsed \"s/{{SKILL_NAME}}/skill-name/g\" scripts/install-template.sh > skill-name/install.sh\nchmod +x skill-name/install.sh\n```\n\nThe template handles:\n- POSIX-compatible shell (`set -eu`, no bashisms)\n- 14 platforms: claude-code, copilot, cursor, windsurf, cline, codex, gemini, kiro, trae, goose, opencode, roo-code, antigravity, universal\n- Corrected paths: Codex → `~/.agents/skills/`, Windsurf → `.windsurf/rules/` (project) / `global_rules.md` (global)\n- Format adapters: auto-generates `.mdc` for Cursor, `.md` rules for Windsurf, plain `.md` for Cline/Roo/Trae\n- Universal `.agents/skills/` secondary symlink after every install\n- `--all` flag to install to every detected tool at once\n- `--dry-run` for preview without changes\n\n### Step 7: Write README.md\n\nMulti-platform installation instructions:\n\n```markdown\n# Skill Name\n\nBrief description.\n\n## Installation\n\n### Universal Path (works with 6+ tools)\n\n```bash\ngit clone \u003crepo-url> ~/.agents/skills/skill-name\n```\n\nWorks with Codex CLI, Gemini CLI, Kiro, Antigravity, and other tools that read `~/.agents/skills/`.\n\n### Using install.sh (Recommended)\n\n```bash\nchmod +x install.sh\n./install.sh # Auto-detect platform\n./install.sh --platform claude-code # Claude Code\n./install.sh --platform cursor # Cursor (auto-generates .mdc)\n./install.sh --all # All detected platforms\n./install.sh --dry-run # Preview without installing\n```\n\n### Alternative: npx\n\n```bash\nnpx skills add \u003crepo-url>\n```\n\n### Manual Installation\n\n| Platform | Copy to |\n|---|---|\n| Universal | `~/.agents/skills/skill-name/` |\n| Claude Code | `~/.claude/skills/skill-name/` or `.claude/skills/skill-name/` |\n| GitHub Copilot | `.github/skills/skill-name/` |\n| Cursor | `.cursor/rules/skill-name/` |\n| Windsurf | `.windsurf/rules/skill-name/` |\n| Cline | `.clinerules/skill-name/` |\n| Codex CLI | `~/.agents/skills/skill-name/` |\n| Gemini CLI | `~/.gemini/skills/skill-name/` |\n| Kiro | `.kiro/skills/skill-name/` |\n| Trae | `.trae/rules/skill-name/` |\n| Goose | `~/.config/goose/skills/skill-name/` |\n| OpenCode | `~/.config/opencode/skills/skill-name/` |\n| Roo Code | `.roo/rules/skill-name/` |\n| Antigravity | `.agents/skills/skill-name/` |\n\n## Prerequisites\n\n[API key instructions, dependencies]\n\n## Usage Examples\n\n[3-5 examples]\n\n## Troubleshooting\n\n[Common issues and solutions]\n```\n\n### Step 7b: Generate Harness Patterns (mandatory)\n\nEvery skill must include these harness patterns as executable code, not as markdown instructions.\n\n**a. Self-bootstrapping wrappers at the repo root:**\n- `./skill-name` (bash) — auto-creates venv, installs uv if missing, installs deps on first run. Bootstrap messages to stderr.\n- `.\\skill-name.ps1` (PowerShell) — same behavior for Windows users.\n\n**b. Input validation module (`scripts/validate_inputs.py` or integrated into main script):**\n- Validate all user-facing inputs before computation: reject negatives where nonsensical, reject out-of-bounds values, validate enum inputs against known values\n- On validation failure: print JSON to stderr with `{\"error\": \"...\", \"error_type\": \"validation\", \"details\": [{\"field\": \"...\", \"error\": \"...\"}]}` and exit 1\n- If the skill brief contains `harness_requirements.input_validation`, implement those specific rules\n\n**c. Output sanity checks:**\n- After computation, check results against domain-specific bounds\n- Attach `_warnings` array to JSON output when values are unusual but not invalid\n- If the skill brief contains `harness_requirements.output_sanity`, implement those specific bounds\n\n**d. `--check-prereqs` command (or flag on the main command):**\n- Check Python version, required packages (try import), API keys (check env vars exist without printing values), network access (optional, with timeout)\n- Output: `{\"ready\": true/false, \"checks\": [{\"check\": \"...\", \"required\": \"...\", \"found\": \"...\", \"ok\": true/false}]}`\n\n**e. `--diagnostics` command (or flag):**\n- Output: `{\"skill\": \"...\", \"version\": \"...\", \"harness_level\": \"...\", \"commands\": [...], \"harness_features\": {\"input_validation\": true, ...}}`\n\n**f. SKILL.md frontmatter must include:**\n- `activation: /{skill-name}` — unique namespace prefix\n- `provenance:` block — if cliskill provides provenance metadata in the skill brief, pass it through. If standalone, generate minimal: `maintainer: unknown, version: 1.0.0, created: {today}`\n\n**g. SKILL.md body must include:**\n- `## Prerequisites` section listing runtime, deps, API keys, network requirements\n- Anti-activation in anti-goals: \"Do NOT activate on general queries — wait for explicit `/{skill-name}` invocation\"\n\n**h. Structured error handling throughout:**\n- All errors as JSON to stderr: `{\"error\": \"message\", \"error_type\": \"validation|runtime|network\", \"hint\": \"...\"}`\n- Exit code 1 on all errors. Never expose stack traces.\n\n### Step 8: Run Spec Validation\n\nAfter creating all files, run the validation script:\n\n```bash\npython3 scripts/validate.py path/to/skill/\n```\n\nConfirm the scripts run reliably (compile cleanly, deps declared, pipeline wired):\n\n```bash\npython3 scripts/check_pipeline.py path/to/skill/\n```\n\nIt must report no errors. See `phase5-orchestration.md`.\n\nIf an eval spec was emitted (Step 5.5), also confirm it is well-formed:\n\n```bash\npython3 path/to/skill/scripts/run_evals.py --validate\n```\n\nIt must report `VALID` (exit 0). Fix the spec and re-run if not.\n\n**What it checks:**\n- Frontmatter fields present and valid (name, description, license, metadata)\n- Name matches directory name\n- Name format: 1-64 chars, lowercase + hyphens, no leading/trailing hyphens, no consecutive hyphens\n- Description: 1-1024 chars\n- SKILL.md under 500 lines\n- Required files present\n\n**If validation fails:** Fix the issues and re-run. Do not proceed until validation passes.\n\n### Step 9: Run Security Scan\n\n```bash\npython3 scripts/security_scan.py path/to/skill/\n```\n\n**What it checks:**\n- Hardcoded API keys or secrets\n- `.env` files with credentials\n- Shell injection patterns\n- Sensitive data in committed files\n\n**If security scan finds issues:** Fix them (replace hardcoded keys with env var references, remove `.env` files, sanitize shell inputs) and re-run.\n\n### Step 10: Report Results\n\nAfter successful validation and security scan, report to the user:\n\n```\nSKILL CREATED SUCCESSFULLY\n\nLocation: ./skill-name/\n\nStatistics:\n- SKILL.md: [N] lines (\u003c500)\n- Python code: [N] lines across [N] scripts\n- References: [N] files\n- Total files: [N]\n\nValidation: PASSED\nSecurity Scan: PASSED\nPipeline: PASSED (scripts compile, deps declared)\nEvals: PASSED ([N] command checks, [M] golden cases) # or SKIPPED (--no-eval)\n\nMain Decisions:\n- API: [name] ([short justification])\n- Analyses: [list]\n- Structure: [simple/organized/complex]\n\nNext Steps:\n1. Get API key: [instructions or link]\n2. Configure: export API_KEY_VAR=\"your_key\"\n3. Install: ./install.sh\n4. Test: \"[example query 1]\"\n\nEvals:\n- Check the skill against its golden baseline anytime: python3 scripts/run_evals.py\n- Optimize it against its metric: /autoresearch-universal optimize . using evals/[skill-name].eval.md\n\nSee README.md for complete multi-platform installation instructions.\n```\n\n## File Creation Order Summary\n\n| Order | File | Notes |\n|---|---|---|\n| 1 | Directory structure | `mkdir -p skill-name/{scripts,references,assets}` |\n| 2 | `SKILL.md` | PRIMARY file, \u003c500 lines, spec-compliant frontmatter |\n| 3 | `scripts/*.py` | Functional code; `run_pipeline.py` orchestrator for multi-script skills |\n| 4 | `references/*.md` | Detailed documentation, self-contained |\n| 5 | `assets/*.json` | Real values, validated JSON |\n| 5.5 | `evals/*.eval.md` + `scripts/run_evals.py` | Bundled loss function; skip if `--no-eval` |\n| 6 | `install.sh` | Cross-platform installer, `chmod +x` |\n| 7 | `README.md` | Multi-platform install instructions |\n| 8 | Run `validate.py` + `check_pipeline.py` | Must pass before delivery |\n| 9 | Run `security_scan.py` | Must pass before delivery |\n| 10 | Report results | Summary to user |\n\n## Phase 5 Checklist\n\n- [ ] Directory structure created (NO `.claude-plugin/` for simple skills)\n- [ ] SKILL.md created FIRST with spec-compliant frontmatter\n- [ ] SKILL.md is \u003c500 lines\n- [ ] Frontmatter has: name, description (\u003c=1024 chars), license, metadata (author, version)\n- [ ] Frontmatter has: `activation: /{skill-name}`\n- [ ] Frontmatter has: `provenance:` block (full if from cliskill, minimal if standalone)\n- [ ] Temporal metadata included (metadata.created, metadata.last_reviewed, metadata.review_interval_days)\n- [ ] Name is kebab-case, ends with `-skill`, matches directory\n- [ ] SKILL.md body has `## Prerequisites` section\n- [ ] SKILL.md anti-goals include anti-activation instruction\n- [ ] All Python scripts implemented with functional code\n- [ ] No TODO, no `pass`, no `NotImplementedError`, no placeholders\n- [ ] All scripts have: shebang, docstrings, type hints, error handling\n- [ ] Multi-script skill has one `scripts/run_pipeline.py` orchestrator (steps wired in code, one happy-path command)\n- [ ] `check_pipeline.py` reports no errors (scripts compile, third-party deps declared)\n- [ ] Input validation implemented (reject bad inputs with structured JSON errors)\n- [ ] Output sanity checks implemented (warn on extreme values)\n- [ ] `--check-prereqs` command returns structured JSON\n- [ ] `--diagnostics` command returns skill metadata\n- [ ] Self-bootstrapping wrappers: `./skill-name` (bash) + `.\\skill-name.ps1` (PowerShell)\n- [ ] All errors as JSON to stderr with error_type classification\n- [ ] References written with real, self-contained content\n- [ ] Assets created with valid JSON and real values\n- [ ] Eval spec emitted (`evals/\u003cname>.eval.md` + `scripts/run_evals.py`) unless `--no-eval`\n- [ ] Eval spec validates (`python3 scripts/run_evals.py --validate` → VALID)\n- [ ] `install.sh` generated with cross-platform support\n- [ ] `README.md` written with multi-platform install instructions\n- [ ] `requirements.txt` created (if third-party dependencies used)\n- [ ] Spec validation passed (`scripts/validate.py`)\n- [ ] Security scan passed (`scripts/security_scan.py`)\n- [ ] Staleness check passed (`scripts/staleness_check.py`)\n- [ ] Results reported to user\n\n---\n\n# Quality Standards Reminders\n\nThese standards apply across ALL phases and ALL generated files.\n\n## Code Quality\n\n**Every function must be:**\n- Complete and functional (no stubs)\n- Documented with docstrings (Args, Returns, Raises, Example)\n- Type-hinted on all public interfaces\n- Protected by error handling\n- Validated on inputs and outputs\n\n**Every script must have:**\n- `#!/usr/bin/env python3` shebang\n- Module-level docstring\n- Organized imports (stdlib, third-party, local)\n- Constants at top level\n- `main()` function with argparse\n- `if __name__ == \"__main__\"` guard\n\n## Documentation Quality\n\n**References must be:**\n- Self-contained (not just links to external docs)\n- Concrete (real values, executable examples)\n- Substantial (1000+ words for main reference files)\n- Well-structured (headings, lists, code blocks)\n\n**SKILL.md must be:**\n- Under 500 lines (move detail to references)\n- Frontmatter-compliant (name, description, license, metadata)\n- Actionable (workflows with specific commands)\n\n## Configuration Quality\n\n**JSON configs must be:**\n- Syntactically valid (always validate with `python -c \"import json; ...\"`)\n- Populated with real values (real API URLs, real rate limits)\n- Annotated with `_instructions` or `_note` fields for user-provided values\n- Never contain hardcoded secrets\n\n## Naming Quality\n\n- Skill names: kebab-case, 1-64 chars, must end with `-skill`\n- Python files: snake_case\n- Classes: PascalCase\n- Functions/methods: snake_case\n- Constants: UPPER_SNAKE_CASE\n\n## Anti-Patterns to Avoid\n\n| Anti-Pattern | Correct Approach |\n|---|---|\n| `def analyze(): pass` | Complete implementation with real logic |\n| `# TODO: implement` | Implement it now |\n| `api_key: YOUR_KEY_HERE` | `api_key_env: \"ENV_VAR_NAME\"` with instructions |\n| `See official docs at [link]` | Include the relevant information directly |\n| SKILL.md over 500 lines | Move detail to `references/` |\n| marketplace.json as step 0 | SKILL.md is the primary file, created first |\n| Name missing `-skill` suffix | End every skill name with `-skill`: `stock-analyzer-skill` |\n| Description over 1024 chars | Trim to essential keywords within limit |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":50434,"content_sha256":"1949620d129e6a3f9a76ad6f6a16bf4b6b2eeb7e949ff4321035f1462074ec59"},{"filename":"references/quality-standards.md","content":"# Mandatory Quality Standards\n\n## Fundamental Principles\n\n**Production-Ready, Not Prototype**\n- Code must work without modifications\n- Doesn't need \"now implement X\"\n- Can be used immediately\n\n**Functional, Not Placeholder**\n- Complete code in all functions\n- No TODO, pass, NotImplementedError\n- Robust error handling\n\n**Useful, Not Generic**\n- Specific and detailed content\n- Concrete examples, not abstract\n- Not just external links\n\n**Current, Not Stale**\n- Include `metadata.created` and `metadata.last_reviewed` dates in frontmatter\n- Set `metadata.review_interval_days` (default: 90 days)\n- Declare external dependencies in `metadata.dependencies` so health can be checked\n- Declare expected API response shapes in `metadata.schema_expectations` for drift detection\n- Run `python3 scripts/staleness_check.py path/to/skill/` periodically to detect stale skills\n- When publishing to a registry, use `python3 scripts/skill_registry.py stale` to audit all skills\n\n---\n\n## Standards by File Type\n\n### Python Scripts\n\n#### ✅ MANDATORY\n\n**1. Complete structure**:\n```python\n#!/usr/bin/env python3\n\"\"\"Module docstring\"\"\"\n\n# Imports\nimport ...\n\n# Constants\nCONST = value\n\n# Classes/Functions\nclass/def ...\n\n# Main\ndef main():\n ...\n\nif __name__ == \"__main__\":\n main()\n```\n\n**2. Docstrings**:\n- Module docstring: 3-5 lines\n- Class docstring: Description + Example\n- Method docstring: Args, Returns, Raises, Example\n\n**3. Type hints**:\n```python\ndef function(param1: str, param2: int = 10) -> Dict[str, Any]:\n ...\n```\n\n**4. Error handling**:\n```python\ntry:\n result = risky_operation()\nexcept SpecificError as e:\n # Handle specifically\n log_error(e)\n raise CustomError(f\"Context: {e}\")\n```\n\n**5. Validations**:\n```python\ndef process(data: Dict) -> pd.DataFrame:\n # Validate input\n if not data:\n raise ValueError(\"Data cannot be empty\")\n\n if 'required_field' not in data:\n raise ValueError(\"Missing required field\")\n\n # Process\n ...\n\n # Validate output\n assert len(result) > 0, \"Result cannot be empty\"\n assert result['value'].notna().all(), \"No null values allowed\"\n\n return result\n```\n\n**6. Appropriate logging**:\n```python\nimport logging\n\nlogger = logging.getLogger(__name__)\n\ndef fetch_data():\n logger.info(\"Fetching data from API...\")\n # ...\n logger.debug(f\"Received {len(data)} records\")\n # ...\n logger.error(f\"API error: {e}\")\n```\n\n#### ❌ FORBIDDEN\n\n```python\n# ❌ DON'T DO THIS:\n\ndef analyze():\n # TODO: implement analysis\n pass\n\ndef process(data): # ❌ No type hints\n # ❌ No docstring\n result = data # ❌ No real logic\n return result # ❌ No validation\n\ndef fetch_api(url):\n response = requests.get(url) # ❌ No timeout\n return response.json() # ❌ No error handling\n```\n\n#### ✅ DO THIS:\n\n```python\ndef analyze_yoy(df: pd.DataFrame, commodity: str, year1: int, year2: int) -> Dict:\n \"\"\"\n Perform year-over-year analysis\n\n Args:\n df: DataFrame with parsed data\n commodity: Commodity name (e.g., \"CORN\")\n year1: Current year\n year2: Previous year\n\n Returns:\n Dict with keys:\n - production_current: float\n - production_previous: float\n - change_percent: float\n - interpretation: str\n\n Raises:\n ValueError: If data not found for specified years\n DataQualityError: If data fails validation\n\n Example:\n >>> analyze_yoy(df, \"CORN\", 2023, 2022)\n {'production_current': 15.3, 'change_percent': 11.7, ...}\n \"\"\"\n # Validate inputs\n if commodity not in df['commodity'].unique():\n raise ValueError(f\"Commodity {commodity} not found in data\")\n\n # Filter data\n df1 = df[(df['commodity'] == commodity) & (df['year'] == year1)]\n df2 = df[(df['commodity'] == commodity) & (df['year'] == year2)]\n\n if len(df1) == 0 or len(df2) == 0:\n raise ValueError(f\"Data not found for {commodity} in {year1} or {year2}\")\n\n # Extract values\n prod1 = df1['production'].iloc[0]\n prod2 = df2['production'].iloc[0]\n\n # Calculate\n change = prod1 - prod2\n change_pct = (change / prod2) * 100\n\n # Interpret\n if abs(change_pct) \u003c 2:\n interpretation = \"stable\"\n elif change_pct > 10:\n interpretation = \"significant_increase\"\n elif change_pct > 2:\n interpretation = \"moderate_increase\"\n elif change_pct \u003c -10:\n interpretation = \"significant_decrease\"\n else:\n interpretation = \"moderate_decrease\"\n\n # Return\n return {\n \"commodity\": commodity,\n \"production_current\": round(prod1, 1),\n \"production_previous\": round(prod2, 1),\n \"change_absolute\": round(change, 1),\n \"change_percent\": round(change_pct, 1),\n \"interpretation\": interpretation\n }\n```\n\n---\n\n### SKILL.md\n\n#### ✅ MANDATORY\n\n**1. Valid frontmatter**:\n```yaml\n---\nname: agent-name\ndescription: [150-250 words with keywords]\n---\n```\n\n**2. Size**: 5000-7000 words\n\n**3. Mandatory sections**:\n- When to use (specific triggers)\n- Data source (detailed API)\n- Workflows (complete step-by-step)\n- Scripts (each one explained)\n- Analyses (methodologies)\n- Errors (complete handling)\n- Validations (mandatory)\n- Keywords (complete list)\n- Examples (5+ complete)\n\n**4. Detailed workflows**:\n\n✅ **GOOD**:\n```markdown\n### Workflow: YoY Comparison\n\n1. **Identify question parameters**\n - Commodity: [extract from question]\n - Years: Current vs previous (or specified)\n\n2. **Fetch data**\n ```bash\n python scripts/fetch_nass.py \\\n --commodity CORN \\\n --years 2023,2022 \\\n --output data/raw/corn_2023_2022.json\n ```\n\n3. **Parse**\n ```bash\n python scripts/parse_nass.py \\\n --input data/raw/corn_2023_2022.json \\\n --output data/processed/corn.csv\n ```\n\n4. **Analyze**\n ```bash\n python scripts/analyze_nass.py \\\n --input data/processed/corn.csv \\\n --analysis yoy \\\n --commodity CORN \\\n --year1 2023 \\\n --year2 2022 \\\n --output data/analysis/corn_yoy.json\n ```\n\n5. **Interpret results**\n\n File `data/analysis/corn_yoy.json` contains:\n ```json\n {\n \"production_current\": 15.3,\n \"change_percent\": 11.7,\n \"interpretation\": \"significant_increase\"\n }\n ```\n\n Respond to user:\n \"Corn production grew 11.7% in 2023...\"\n```\n\n❌ **BAD**:\n```markdown\n### Workflow: Comparison\n\n1. Get data\n2. Compare\n3. Return result\n```\n\n**5. Complete examples**:\n\n✅ **GOOD**:\n```markdown\n### Example 1: YoY Comparison\n\n**Question**: \"How's corn production compared to last year?\"\n\n**Executed flow**:\n[Specific commands with outputs]\n\n**Generated answer**:\n\"Corn production in 2023 is 15.3 billion bushels,\ngrowth of 11.7% vs 2022 (13.7 billion). Growth\ncomes mainly from area increase (+8%) with stable yield.\"\n```\n\n❌ **BAD**:\n```markdown\n### Example: Comparison\n\nUser asks about comparison. Agent compares and responds.\n```\n\n#### ❌ FORBIDDEN\n\n- Empty sections\n- \"See documentation\"\n- Workflows without specific commands\n- Generic examples\n\n---\n\n### References\n\n#### ✅ MANDATORY\n\n**1. Useful and self-contained content**:\n\n✅ **GOOD** (references/api-guide.md):\n```markdown\n## Endpoint: Get Production Data\n\n**URL**: `GET https://quickstats.nass.usda.gov/api/api_GET/`\n\n**Parameters**:\n- `commodity_desc`: Commodity name\n - Example: \"CORN\", \"SOYBEANS\"\n - Case-sensitive\n- `year`: Desired year\n - Example: 2023\n - Range: 1866-present\n\n**Complete request example**:\n```bash\ncurl -H \"X-Api-Key: YOUR_KEY\" \\\n \"https://quickstats.nass.usda.gov/api/api_GET/?commodity_desc=CORN&year=2023&format=JSON\"\n```\n\n**Expected response**:\n```json\n{\n \"data\": [\n {\n \"year\": 2023,\n \"commodity_desc\": \"CORN\",\n \"value\": \"15,300,000,000\",\n \"unit_desc\": \"BU\"\n }\n ]\n}\n```\n\n**Important fields**:\n- `value`: Comes as STRING with commas\n - Solution: `value.replace(',', '')`\n - Convert to float after\n```\n\n❌ **BAD**:\n```markdown\n## API Endpoint\n\nFor details on how to use the API, consult the official documentation at:\nhttps://quickstats.nass.usda.gov/api\n\n[End of file]\n```\n\n**2. Adequate size**:\n- API guide: 1500-2000 words\n- Analysis methods: 2000-3000 words\n- Troubleshooting: 1000-1500 words\n\n**3. Concrete examples**:\n- Always include examples with real values\n- Executable code blocks\n- Expected outputs\n\n#### ❌ FORBIDDEN\n\n- \"For more information, see [link]\"\n- Sections with only 2-3 lines\n- Lists without details\n- Circular references (\"see other doc that sees other doc\")\n\n---\n\n### Assets (Configs)\n\n#### ✅ MANDATORY\n\n**1. Syntactically valid JSON**:\n```bash\n# ALWAYS validate:\npython -c \"import json; json.load(open('config.json'))\"\n```\n\n**2. Real values**:\n\n✅ **GOOD**:\n```json\n{\n \"api\": {\n \"base_url\": \"https://quickstats.nass.usda.gov/api\",\n \"api_key_env\": \"NASS_API_KEY\",\n \"_instructions\": \"Get free API key from: https://quickstats.nass.usda.gov/api#registration\",\n \"rate_limit_per_day\": 1000,\n \"timeout_seconds\": 30\n }\n}\n```\n\n❌ **BAD**:\n```json\n{\n \"api\": {\n \"base_url\": \"YOUR_API_URL_HERE\",\n \"api_key\": \"YOUR_KEY_HERE\"\n }\n}\n```\n\n**3. Inline comments** (using `_comment` or `_note`):\n```json\n{\n \"_comment\": \"Differentiated TTL by data type\",\n \"cache\": {\n \"ttl_historical_days\": 365,\n \"_note_historical\": \"Historical data doesn't change\",\n \"ttl_current_days\": 7,\n \"_note_current\": \"Current year data may be revised\"\n }\n}\n```\n\n---\n\n### README.md\n\n#### ✅ MANDATORY\n\n**1. Complete installation instructions**:\n\n✅ **GOOD**:\n```markdown\n## Installation\n\n### 1. Get API Key (Free)\n\n1. Access https://quickstats.nass.usda.gov/api#registration\n2. Fill form:\n - Name: [your name]\n - Email: [your email]\n - Purpose: \"Personal research\"\n3. Click \"Submit\"\n4. You'll receive email with API key in ~1 minute\n5. Key format: `A1B2C3D4-E5F6-G7H8-I9J0-K1L2M3N4O5P6`\n\n### 2. Configure Environment\n\n**Option A - Export** (temporary):\n```bash\nexport NASS_API_KEY=\"your_key_here\"\n```\n\n**Option B - .bashrc/.zshrc** (permanent):\n```bash\necho 'export NASS_API_KEY=\"your_key_here\"' >> ~/.bashrc\nsource ~/.bashrc\n```\n\n**Option C - .env file** (per project):\n```bash\necho \"NASS_API_KEY=your_key_here\" > .env\n```\n\n### 3. Install Dependencies\n\n```bash\ncd nass-usda-agriculture\npip install -r requirements.txt\n```\n\nRequirements:\n- requests\n- pandas\n- numpy\n```\n\n❌ **BAD**:\n```markdown\n## Installation\n\n1. Get API key from the official website\n2. Configure environment\n3. Install dependencies\n4. Done!\n```\n\n**2. Concrete usage examples**:\n\n✅ **GOOD**:\n```markdown\n## Examples\n\n### Example 1: Current Production\n\n```\nYou: \"What's US corn production in 2023?\"\n\nClaude: \"Corn production in 2023 was 15.3 billion\nbushels (389 million metric tons)...\"\n```\n\n### Example 2: YoY Comparison\n\n```\nYou: \"Compare soybeans this year vs last year\"\n\nClaude: \"Soybean production in 2023 is 2.6% below 2022:\n- 2023: 4.165 billion bushels\n- 2022: 4.276 billion bushels\n- Drop from area (-4.5%), yield improved (+0.8%)\"\n```\n\n[3-5 more examples]\n```\n\n❌ **BAD**:\n```markdown\n## Usage\n\nAsk questions about agriculture and the agent will respond.\n```\n\n**3. Specific troubleshooting**:\n\n✅ **GOOD**:\n```markdown\n### Error: \"NASS_API_KEY environment variable not found\"\n\n**Cause**: API key not configured\n\n**Step-by-step solution**:\n1. Verify key was obtained: https://...\n2. Configure environment:\n ```bash\n export NASS_API_KEY=\"your_key_here\"\n ```\n3. Verify:\n ```bash\n echo $NASS_API_KEY\n ```\n4. Should show your key\n5. If doesn't work, restart terminal\n\n**Still not working?**\n- Check for extra spaces in key\n- Verify key hasn't expired (validity: 1 year)\n- Re-generate key if needed\n```\n\n---\n\n## Quality Checklist\n\n### Per Python Script\n\n- [ ] Shebang: `#!/usr/bin/env python3`\n- [ ] Module docstring (3-5 lines)\n- [ ] Organized imports (stdlib, 3rd party, local)\n- [ ] Constants at top (if applicable)\n- [ ] Type hints in all public functions\n- [ ] Docstrings in classes (description + attributes + example)\n- [ ] Docstrings in methods (Args, Returns, Raises, Example)\n- [ ] Error handling for risky operations\n- [ ] Input validations\n- [ ] Output validations\n- [ ] Appropriate logging\n- [ ] Main function with argparse\n- [ ] if __name__ == \"__main__\"\n- [ ] Functional code (no TODO/pass)\n- [ ] Valid syntax (test: `python -m py_compile script.py`)\n\n### Per SKILL.md\n\n- [ ] Frontmatter with name and description\n- [ ] Description 150-250 characters with keywords\n- [ ] Size 5000+ words\n- [ ] \"When to Use\" section with specific triggers\n- [ ] \"Data Source\" section detailed\n- [ ] Step-by-step workflows with commands\n- [ ] Scripts explained individually\n- [ ] Analyses documented (objective, methodology)\n- [ ] Errors handled (all expected)\n- [ ] Validations listed\n- [ ] Performance/cache explained\n- [ ] Complete keywords\n- [ ] Complete examples (5+)\n\n### Per Reference File\n\n- [ ] 1000+ words\n- [ ] Useful content (not just links)\n- [ ] Concrete examples with real values\n- [ ] Executable code blocks\n- [ ] Well structured (headings, lists)\n- [ ] No empty sections\n- [ ] No \"TODO: write\"\n\n### Per Asset (Config)\n\n- [ ] Syntactically valid JSON (validate!)\n- [ ] Real values (not \"YOUR_X_HERE\" without context)\n- [ ] Inline comments (_comment, _note)\n- [ ] Instructions for values user must fill\n- [ ] Logical and organized structure\n\n### Per README.md\n\n- [ ] Step-by-step installation\n- [ ] How to get API key (detailed)\n- [ ] How to configure (3 options)\n- [ ] How to install dependencies\n- [ ] How to install in Claude Code\n- [ ] Usage examples (5+)\n- [ ] Troubleshooting (10+ problems)\n- [ ] License\n- [ ] Contact/contribution (if applicable)\n\n### Complete Agent\n\n- [ ] DECISIONS.md documents all choices\n- [ ] **VERSION** file created (e.g. 1.0.0)\n- [ ] **CHANGELOG.md** created with complete v1.0.0 entry\n- [ ] **INSTALACAO.md** with complete didactic tutorial\n- [ ] **comprehensive_{domain}_report()** implemented\n- [ ] SKILL.md with version in frontmatter metadata\n- [ ] 18+ files created\n- [ ] ~1500+ lines of Python code\n- [ ] ~10,000+ words of documentation\n- [ ] 2+ configs\n- [ ] requirements.txt\n- [ ] .gitignore (if needed)\n- [ ] No placeholder/TODO\n- [ ] Valid syntax (Python, JSON, YAML)\n- [ ] Ready to use (production-ready)\n\n---\n\n## Quality Examples\n\n### Example: Error Handling\n\n❌ **BAD**:\n```python\ndef fetch(url):\n return requests.get(url).json()\n```\n\n✅ **GOOD**:\n```python\ndef fetch(url: str, timeout: int = 30) -> Dict:\n \"\"\"\n Fetch data from URL with error handling\n\n Args:\n url: URL to fetch\n timeout: Timeout in seconds\n\n Returns:\n JSON response as dict\n\n Raises:\n NetworkError: If connection fails\n TimeoutError: If request times out\n APIError: If API returns error\n \"\"\"\n try:\n response = requests.get(url, timeout=timeout)\n response.raise_for_status()\n\n data = response.json()\n\n if 'error' in data:\n raise APIError(f\"API error: {data['error']}\")\n\n return data\n\n except requests.Timeout:\n raise TimeoutError(f\"Request timed out after {timeout}s\")\n\n except requests.ConnectionError as e:\n raise NetworkError(f\"Connection failed: {e}\")\n\n except requests.HTTPError as e:\n if e.response.status_code == 429:\n raise RateLimitError(\"Rate limit exceeded\")\n else:\n raise APIError(f\"HTTP {e.response.status_code}: {e}\")\n```\n\n### Example: Validations\n\n❌ **BAD**:\n```python\ndef parse(data):\n df = pd.DataFrame(data)\n return df\n```\n\n✅ **GOOD**:\n```python\ndef parse(data: List[Dict]) -> pd.DataFrame:\n \"\"\"Parse and validate data\"\"\"\n\n # Validate input\n if not data:\n raise ValueError(\"Data cannot be empty\")\n\n if not isinstance(data, list):\n raise TypeError(f\"Expected list, got {type(data)}\")\n\n # Parse\n df = pd.DataFrame(data)\n\n # Validate schema\n required_cols = ['year', 'commodity', 'value']\n missing = set(required_cols) - set(df.columns)\n if missing:\n raise ValueError(f\"Missing required columns: {missing}\")\n\n # Validate types\n df['year'] = pd.to_numeric(df['year'], errors='raise')\n df['value'] = pd.to_numeric(df['value'], errors='raise')\n\n # Validate ranges\n current_year = datetime.now().year\n if (df['year'] > current_year).any():\n raise ValueError(f\"Future years found (max allowed: {current_year})\")\n\n if (df['value'] \u003c 0).any():\n raise ValueError(\"Negative values found\")\n\n # Validate no duplicates\n if df.duplicated(subset=['year', 'commodity']).any():\n raise ValueError(\"Duplicate records found\")\n\n return df\n```\n\n### Example: Docstrings\n\n❌ **BAD**:\n```python\ndef analyze(df, commodity):\n \"\"\"Analyze data\"\"\"\n # ...\n```\n\n✅ **GOOD**:\n```python\ndef analyze_yoy(\n df: pd.DataFrame,\n commodity: str,\n year1: int,\n year2: int\n) -> Dict[str, Any]:\n \"\"\"\n Perform year-over-year comparison analysis\n\n Compares production, area, and yield between two years\n and decomposes growth into area vs yield contributions.\n\n Args:\n df: DataFrame with columns ['year', 'commodity', 'production', 'area', 'yield']\n commodity: Commodity name (e.g., \"CORN\", \"SOYBEANS\")\n year1: Current year to compare\n year2: Previous year to compare against\n\n Returns:\n Dict containing:\n - production_current (float): Production in year1 (million units)\n - production_previous (float): Production in year2\n - change_absolute (float): Absolute change\n - change_percent (float): Percent change\n - decomposition (dict): Area vs yield contribution\n - interpretation (str): \"increase\", \"decrease\", or \"stable\"\n\n Raises:\n ValueError: If commodity not found in data\n ValueError: If either year not found in data\n DataQualityError: If production != area * yield (tolerance > 1%)\n\n Example:\n >>> df = pd.DataFrame([\n ... {'year': 2023, 'commodity': 'CORN', 'production': 15.3, 'area': 94.6, 'yield': 177},\n ... {'year': 2022, 'commodity': 'CORN', 'production': 13.7, 'area': 89.2, 'yield': 173}\n ... ])\n >>> result = analyze_yoy(df, \"CORN\", 2023, 2022)\n >>> result['change_percent']\n 11.7\n \"\"\"\n # [Complete implementation]\n```\n\n---\n\n## Dependency Management\n\n### Decision Framework\n\nSkills should minimize external dependencies. Every dependency is a maintenance burden, a security surface, and a compatibility risk. Use this decision tree:\n\n```\nCan stdlib do it?\n → Yes: Use stdlib. Done.\n → No: Is there a lightweight pure-Python package (\u003c1MB)?\n → Yes: Use it. Add to requirements.txt.\n → No: Is there a well-maintained popular package?\n → Yes: Use it only if the domain requires it.\n → No: Implement it yourself or redesign the approach.\n```\n\n### Stdlib vs. Third-Party Decision Table\n\n| Task | Stdlib Solution | When to Use Third-Party |\n|------|----------------|------------------------|\n| HTTP requests | `urllib.request` | Use `requests` when: complex auth, session management, multipart uploads, or retry logic would require 100+ lines of urllib code |\n| JSON handling | `json` | Never — stdlib is sufficient |\n| CSV parsing | `csv` | Use `pandas` only when: statistical analysis, complex transformations, or DataFrame operations are core to the skill |\n| File paths | `pathlib` | Never — stdlib is sufficient |\n| Date/time | `datetime` | Never — stdlib is sufficient |\n| Regex | `re` | Never — stdlib is sufficient |\n| Hashing | `hashlib` | Never — stdlib is sufficient |\n| Caching | File-based (json + pathlib) | Never for skills — the FileCache pattern in architecture-guide.md is sufficient |\n| Data analysis | Manual calculations | Use `pandas`/`numpy` when: skill is primarily analytical (10+ statistical operations, pivots, aggregations) |\n| PDF generation | Not available | Use `reportlab` or `fpdf2` when PDF output is a core requirement |\n| Web scraping | `urllib` + `html.parser` | Use `beautifulsoup4` when parsing complex/malformed HTML |\n| CLI arguments | `argparse` | Never — stdlib is sufficient |\n| YAML parsing | Manual (the `_parse_frontmatter` pattern) | Use `pyyaml` only if skill needs to parse arbitrary YAML files (not just SKILL.md frontmatter) |\n\n### requirements.txt Rules\n\nWhen third-party packages are needed:\n\n```\n# requirements.txt\n\n# Pin major.minor, allow patch updates\nrequests>=2.31,\u003c3.0\npandas>=2.0,\u003c3.0\n\n# For stdlib-only skills, create an empty requirements.txt with a comment:\n# No external dependencies required — this skill uses Python stdlib only.\n```\n\n**Rules:**\n- Always create `requirements.txt` even if empty (document the stdlib-only decision)\n- Pin major.minor version to avoid breaking changes\n- Never pin exact patch versions (allows security updates)\n- Never include dev dependencies (pytest, ruff) — those are for contributors, not users\n- List only direct dependencies, not transitive ones\n- Include a comment explaining why each package is needed\n\n### Common Dependency Patterns by Skill Type\n\n| Skill Type | Typical Dependencies |\n|-----------|---------------------|\n| Data analysis (stocks, agriculture, climate) | `requests`, `pandas`, `numpy` |\n| Report generation | `requests`, `fpdf2` or `reportlab` |\n| Web scraping | `requests`, `beautifulsoup4` |\n| API wrapper | `requests` (or stdlib `urllib`) |\n| Text processing | Stdlib only (`re`, `json`, `csv`) |\n| File format conversion | Stdlib only (or single specialized package) |\n| Database interaction | Stdlib `sqlite3` (or `psycopg2`/`pymysql` for specific DBs) |\n\n---\n\n## Testing Strategy\n\n### Why Test Generated Skills\n\nSkills are opinionated software that teams rely on daily. A skill that produces wrong calculations, misparses API responses, or silently drops data is worse than no skill at all. Tests catch these issues before the skill reaches users.\n\n### What to Test\n\nFocus tests on the parts most likely to break or produce wrong results:\n\n| Priority | What to Test | Why |\n|----------|-------------|-----|\n| **High** | Analysis/calculation functions | Wrong math = wrong decisions |\n| **High** | Data parsing (API response → structured data) | APIs change formats, edge cases in real data |\n| **High** | Input validation | Bad input should fail clearly, not silently produce garbage |\n| **Medium** | Output formatting | Reports and summaries should be consistent |\n| **Medium** | Error handling paths | Verify graceful degradation on API failures, missing data |\n| **Low** | Cache logic | Only if custom caching is complex |\n| **Low** | Config loading | Usually trivial |\n\n### Test Directory Structure\n\n```\nskill-name/\n├── scripts/\n│ ├── analyze.py\n│ ├── fetch.py\n│ └── parse.py\n├── tests/\n│ ├── test_analyze.py # Unit tests for analysis functions\n│ ├── test_parse.py # Unit tests for parsing logic\n│ ├── fixtures/\n│ │ ├── sample_api_response.json # Real API response (anonymized)\n│ │ └── sample_parsed_data.csv # Expected parsed output\n│ └── conftest.py # Shared pytest fixtures\n```\n\n### Test Patterns\n\n**Pattern 1: Test analysis functions with known inputs/outputs**\n\n```python\n\"\"\"Tests for analyze.py — core calculation functions.\"\"\"\nimport pytest\nfrom scripts.analyze import analyze_yoy, calculate_trend\n\ndef test_yoy_increase():\n \"\"\"YoY comparison should detect an increase.\"\"\"\n result = analyze_yoy(\n current_value=150.0,\n previous_value=100.0,\n )\n assert result[\"change_percent\"] == pytest.approx(50.0)\n assert result[\"interpretation\"] == \"significant_increase\"\n\ndef test_yoy_stable():\n \"\"\"Changes under 2% should be interpreted as stable.\"\"\"\n result = analyze_yoy(current_value=101.0, previous_value=100.0)\n assert result[\"interpretation\"] == \"stable\"\n\ndef test_yoy_zero_previous():\n \"\"\"Division by zero should raise ValueError, not crash.\"\"\"\n with pytest.raises(ValueError, match=\"previous value cannot be zero\"):\n analyze_yoy(current_value=100.0, previous_value=0.0)\n```\n\n**Pattern 2: Test parsing with fixture data**\n\n```python\n\"\"\"Tests for parse.py — API response parsing.\"\"\"\nimport json\nfrom pathlib import Path\nfrom scripts.parse import parse_api_response\n\nFIXTURES = Path(__file__).parent / \"fixtures\"\n\ndef test_parse_normal_response():\n \"\"\"Standard API response should parse to expected structure.\"\"\"\n raw = json.loads((FIXTURES / \"sample_api_response.json\").read_text())\n result = parse_api_response(raw)\n assert len(result) > 0\n assert \"year\" in result[0]\n assert \"value\" in result[0]\n\ndef test_parse_empty_response():\n \"\"\"Empty API response should return empty list, not crash.\"\"\"\n result = parse_api_response({\"data\": []})\n assert result == []\n\ndef test_parse_malformed_values():\n \"\"\"Values with commas (e.g., '15,300,000') should be cleaned.\"\"\"\n raw = {\"data\": [{\"value\": \"15,300,000\", \"year\": \"2023\"}]}\n result = parse_api_response(raw)\n assert result[0][\"value\"] == 15300000.0\n```\n\n**Pattern 3: Mock external API calls**\n\n```python\n\"\"\"Tests for fetch.py — API interaction (mocked).\"\"\"\nfrom unittest.mock import patch, MagicMock\nfrom scripts.fetch import fetch_data\n\n@patch(\"scripts.fetch.urllib.request.urlopen\")\ndef test_fetch_success(mock_urlopen):\n \"\"\"Successful API call should return parsed JSON.\"\"\"\n mock_response = MagicMock()\n mock_response.read.return_value = b'{\"data\": [{\"value\": \"100\"}]}'\n mock_response.__enter__ = lambda s: s\n mock_response.__exit__ = MagicMock(return_value=False)\n mock_urlopen.return_value = mock_response\n\n result = fetch_data(commodity=\"CORN\", year=2023)\n assert \"data\" in result\n\n@patch(\"scripts.fetch.urllib.request.urlopen\")\ndef test_fetch_rate_limited(mock_urlopen):\n \"\"\"429 response should raise RateLimitError.\"\"\"\n from urllib.error import HTTPError\n mock_urlopen.side_effect = HTTPError(\n url=\"\", code=429, msg=\"Too Many Requests\", hdrs={}, fp=None\n )\n with pytest.raises(Exception, match=\"[Rr]ate limit\"):\n fetch_data(commodity=\"CORN\", year=2023)\n```\n\n### Running Tests\n\n```bash\n# Run all tests\ncd skill-name/\npython -m pytest tests/ -v\n\n# Run with coverage\npython -m pytest tests/ --cov=scripts --cov-report=term-missing\n```\n\n### When to Generate Tests\n\nTests are generated during Phase 5 (Implementation) after the scripts are written:\n\n1. Write all scripts first (steps 1-5 of Phase 5)\n2. Create `tests/` directory with test files for core functions\n3. Create `tests/fixtures/` with sample data\n4. Run tests to verify they pass\n5. Include test instructions in README.md\n\n**Note:** Tests are recommended but not mandatory for v1.0 of a skill. The validation and security scan gates are always mandatory. Tests become critical when:\n- The skill performs financial calculations (wrong math = real cost)\n- The skill processes sensitive data (parsing errors = data loss)\n- Multiple people will maintain the skill (tests prevent regressions)\n- The skill is being published to the team registry (quality expectation is higher)\n\n---\n\n## Anti-Patterns\n\n### Anti-Pattern 1: Partial Implementation\n\n❌ **NO**:\n```python\ndef yoy_comparison(df, commodity, year1, year2):\n # Implement YoY comparison\n pass\n\ndef state_ranking(df, commodity):\n # TODO: implement ranking\n raise NotImplementedError()\n```\n\n✅ **YES**:\n```python\n# [Complete and functional code for BOTH functions]\n```\n\n### Anti-Pattern 2: Empty References\n\n❌ **NO**:\n```markdown\n# Analysis Methods\n\n## YoY Comparison\n\nThis method compares two years.\n\n## Ranking\n\nThis method ranks states.\n```\n\n✅ **YES**:\n```markdown\n# Analysis Methods\n\n## YoY Comparison\n\n### Objective\nCompare metrics between current and previous year...\n\n### Detailed Methodology\n\n**Formulas**:\n```\nΔ X = X(t) - X(t-1)\nΔ X% = (Δ X / X(t-1)) × 100\n```\n\n**Decomposition** (for production):\n[Complete mathematics]\n\n**Interpretation**:\n- |Δ| \u003c 2%: Stable\n- Δ > 10%: Significant increase\n[...]\n\n### Validations\n[List]\n\n### Complete Numerical Example\n[With real values]\n```\n\n### Anti-Pattern 3: Useless Configs\n\n❌ **NO**:\n```json\n{\n \"api_url\": \"INSERT_URL\",\n \"api_key\": \"INSERT_KEY\"\n}\n```\n\n✅ **YES**:\n```json\n{\n \"_comment\": \"Configuration for NASS USDA Agent\",\n \"api\": {\n \"base_url\": \"https://quickstats.nass.usda.gov/api\",\n \"_note\": \"This is the official USDA NASS API base URL\",\n \"api_key_env\": \"NASS_API_KEY\",\n \"_key_instructions\": \"Get free API key from: https://quickstats.nass.usda.gov/api#registration\"\n }\n}\n```\n\n---\n\n## Final Validation\n\nBefore delivering to user, verify:\n\n### Sanity Test\n\n```bash\n# 1. Python syntax\nfind scripts -name \"*.py\" -exec python -m py_compile {} \\;\n\n# 2. JSON syntax\npython -c \"import json; json.load(open('assets/config.json'))\"\n\n# 3. Imports make sense\ngrep -r \"^import\\|^from\" scripts/*.py | sort | uniq\n# Verify all libs are: stdlib, requests, pandas, numpy\n# No imports of uninstalled libs\n\n# 4. SKILL.md has frontmatter\nhead -5 SKILL.md | grep \"^---$\"\n\n# 5. SKILL.md size\nwc -w SKILL.md\n# Should be > 5000 words\n```\n\n### Final Checklist\n\n- [ ] Syntax check passed (Python, JSON)\n- [ ] No import of non-existent lib\n- [ ] No TODO or pass\n- [ ] SKILL.md > 5000 words\n- [ ] References with content\n- [ ] README with complete instructions\n- [ ] DECISIONS.md created\n- [ ] requirements.txt created\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":29373,"content_sha256":"bf8c1ab809258528d4045b1bcf832231813bec85b92828a95c52881e8a0a65d9"},{"filename":"references/templates-guide.md","content":"# Template-Based Creation System\n\n## Overview\n\nThe template-based creation system accelerates skill creation by providing pre-built scaffolds for common domains. Instead of starting from Phase 1 (Discovery) with a blank slate, templates provide a curated starting point with known-good APIs, proven analysis patterns, and tested configurations. The 5-phase pipeline still runs, but Phase 1 and Phase 2 are pre-populated with vetted decisions.\n\n## Available Templates\n\n### Financial Analysis\n\n**Template ID**: `financial-analysis`\n**Target domain**: Stock market, portfolio analysis, economic indicators\n\n| Component | Details |\n|-----------|---------|\n| Primary API | Alpha Vantage (free tier, 500 requests/day) |\n| Secondary API | Yahoo Finance via `yfinance` (unofficial, unlimited) |\n| Pre-built analyses | Technical indicators (RSI, MACD, SMA), price trends, volume analysis, sector comparison |\n| Output formats | Tabular summaries, time-series data, PDF reports |\n| Authentication | API key via environment variable `ALPHA_VANTAGE_KEY` |\n\n**Pre-configured scripts**:\n- `scripts/fetch_market_data.py` -- OHLCV data retrieval with rate limiting\n- `scripts/analyze_technicals.py` -- RSI, MACD, Bollinger Bands calculations\n- `scripts/generate_report.py` -- PDF/HTML report generation\n\n### Climate Analysis\n\n**Template ID**: `climate-analysis`\n**Target domain**: Weather patterns, historical climate data, forecasting\n\n| Component | Details |\n|-----------|---------|\n| Primary API | Open-Meteo (free, no API key, 10,000 requests/day) |\n| Secondary API | NOAA Climate Data Online (free, API key, 1,000 requests/day) |\n| Pre-built analyses | Temperature trends, precipitation patterns, extreme events, historical comparisons |\n| Output formats | Time-series data, geographic summaries, PDF reports |\n| Authentication | Open-Meteo: none. NOAA: API key via `NOAA_API_KEY` |\n\n**Pre-configured scripts**:\n- `scripts/fetch_climate_data.py` -- Multi-source data retrieval with caching\n- `scripts/analyze_trends.py` -- Statistical trend analysis and anomaly detection\n- `scripts/generate_report.py` -- Climate summary report generation\n\n### E-commerce Analytics\n\n**Template ID**: `ecommerce-analytics`\n**Target domain**: Sales tracking, customer behavior, revenue analysis\n\n| Component | Details |\n|-----------|---------|\n| Primary API | Google Analytics Data API (free tier) |\n| Secondary APIs | Stripe API (transaction data), Shopify Admin API (store data) |\n| Pre-built analyses | Revenue trends, conversion funnels, customer segmentation, product performance |\n| Output formats | Dashboard data, CSV exports, PDF reports |\n| Authentication | OAuth2 (Google), API key (Stripe/Shopify) |\n\n**Pre-configured scripts**:\n- `scripts/fetch_analytics.py` -- Multi-platform data aggregation\n- `scripts/analyze_sales.py` -- Revenue, conversion, and cohort analysis\n- `scripts/generate_dashboard.py` -- HTML dashboard and PDF report\n\n## Template Matching Process\n\nWhen a user describes their workflow, the system matches it to the best template through a two-step process.\n\n### Step 1: Keyword Extraction\n\nThe system extracts domain-relevant keywords from the user's request:\n\n```\nUser: \"I need to analyze stock performance and generate weekly reports\"\n\nExtracted keywords:\n - Domain: [\"stock\", \"performance\", \"reports\"]\n - Actions: [\"analyze\", \"generate\"]\n - Frequency: [\"weekly\"]\n - Implied domain: finance\n```\n\n### Step 2: Similarity Scoring\n\nEach template is scored against the extracted keywords:\n\n| Template | Keyword Match | Domain Match | Action Match | Total Score |\n|----------|--------------|--------------|--------------|-------------|\n| Financial Analysis | 3/3 | 1.0 | 0.9 | 0.95 |\n| Climate Analysis | 0/3 | 0.0 | 0.4 | 0.10 |\n| E-commerce Analytics | 1/3 | 0.2 | 0.6 | 0.30 |\n\n**Selection threshold**: A template is suggested when its score exceeds 0.70. Below that threshold, the system falls back to the standard 5-phase pipeline with full discovery.\n\n## Template Usage\n\n### Direct Request\n\nExplicitly ask for a template:\n\n```\nUser: \"Create a financial analysis skill using the financial template\"\n\nResult: System loads the financial-analysis template, pre-populates\nPhase 1 (Alpha Vantage + Yahoo Finance) and Phase 2 (technical\nindicators), then proceeds through Phases 3-5.\n```\n\n### Auto-Detection\n\nDescribe your workflow and let the system match:\n\n```\nUser: \"Every week I check weather trends for the Pacific Northwest\n and compare them to historical averages\"\n\nResult: System detects climate-analysis template (score: 0.92),\nconfirms with user, then proceeds with Open-Meteo + NOAA as\npre-selected data sources.\n```\n\n### Customization After Selection\n\nTemplates are starting points, not rigid constraints. After selection, the user can customize any aspect:\n\n```\nUser: \"Use the financial template but replace Alpha Vantage with\n IEX Cloud and add cryptocurrency support\"\n\nResult: System loads financial-analysis template, swaps the primary\nAPI to IEX Cloud, adds crypto endpoints to Phase 2 design, then\nproceeds through Phases 3-5 with the modifications.\n```\n\n## Creating Custom Templates\n\nCustom templates follow the same structure as built-in ones. Place them in `references/templates/custom/`.\n\n### Template File Structure\n\n```\nreferences/templates/custom/\n└── my-template/\n ├── template.json # Template definition\n ├── phase1-config.md # Pre-populated Discovery decisions\n ├── phase2-config.md # Pre-populated Design decisions\n └── scripts/ # Starter scripts (optional)\n └── fetch_data.py\n```\n\n### Template Definition (template.json)\n\n```json\n{\n \"id\": \"my-custom-template\",\n \"name\": \"My Custom Template\",\n \"domain\": \"logistics\",\n \"keywords\": [\"shipping\", \"tracking\", \"delivery\", \"freight\", \"supply chain\"],\n \"apis\": [\n {\n \"name\": \"ShipEngine\",\n \"url\": \"https://www.shipengine.com/docs/\",\n \"auth\": \"api_key\",\n \"free_tier\": true,\n \"rate_limit\": \"500/day\"\n }\n ],\n \"analyses\": [\n \"shipment_tracking\",\n \"delivery_performance\",\n \"cost_optimization\",\n \"route_analysis\"\n ],\n \"output_formats\": [\"tabular\", \"pdf\", \"csv\"]\n}\n```\n\n### Registering the Template\n\nAfter creating the template files, register it by adding an entry to `references/templates/registry.json`:\n\n```json\n{\n \"templates\": [\n {\"id\": \"financial-analysis\", \"path\": \"references/templates/financial/\", \"builtin\": true},\n {\"id\": \"climate-analysis\", \"path\": \"references/templates/climate/\", \"builtin\": true},\n {\"id\": \"ecommerce-analytics\", \"path\": \"references/templates/ecommerce/\", \"builtin\": true},\n {\"id\": \"my-custom-template\", \"path\": \"references/templates/custom/my-template/\", \"builtin\": false}\n ]\n}\n```\n\n### Template Best Practices\n\n1. **Include at least 10 keywords** to ensure reliable matching during auto-detection.\n2. **Document API quirks** in `phase1-config.md` so the pipeline does not rediscover known gotchas.\n3. **Provide working starter scripts** when possible -- this accelerates Phase 5 significantly.\n4. **Test the template** by running a full creation cycle and verifying the output passes validation.\n5. **Keep APIs current** -- review rate limits and endpoints periodically, since free tiers change.\n\n## Template vs. Full Pipeline\n\n| Scenario | Recommended Approach |\n|----------|---------------------|\n| Domain matches a built-in template | Use template (saves 30-50% of creation time) |\n| Domain is adjacent to a template | Use template with customization |\n| Entirely new domain | Full 5-phase pipeline from scratch |\n| User explicitly requests no template | Full 5-phase pipeline |\n| User provides a transcript of their workflow | Full pipeline with transcript processing |\n\nTemplates are an optimization, not a replacement. The 5-phase pipeline always runs. Templates simply pre-populate the early phases with proven decisions.\n\n## Artifact templates (v6.0+)\n\nThe four React templates under `artifact-templates/` are inlined by\nPhase 2 into generated SKILL.md files when the skill's output is\nvisualizable.\n\n| Template | Use case | Library |\n|---|---|---|\n| `line-chart.jsx` | Time series (trends, history) | recharts |\n| `bar-chart.jsx` | Categorical comparisons | recharts |\n| `kpi-cards.jsx` | Headline numbers | none (plain JSX) |\n| `data-table.jsx` | Structured rows baseline | none (plain JSX) |\n\n### Substitution marker\n\nEvery template contains a single marker `/* AGENT_SKILL_DATA */` where\nPhase 2 injects skill-specific data shape instructions. The placeholder\ndata block immediately following the marker is intentionally rendered\neven when no real data is provided -- the artifact shows the user what\nthe skill will produce.\n\n### Extending\n\nUser-provided templates are not in scope for v6.0. Adding a fifth bundled\ntemplate requires:\n\n1. Add the `.jsx` file under `references/artifact-templates/`\n2. Add a structural test class in `scripts/tests/test_template_structure.py`\n3. Add a signal in `scripts/artifact_detector.py` that maps to it\n4. Add labeled examples to `scripts/tests/fixtures/labeled_examples.json`\n5. Re-run the accuracy gate\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":9111,"content_sha256":"5151a3c3d5be5c3ae4c28d2eb7aa7cbd422d1dcf2239498cd39490d23751029e"},{"filename":"references/templates/README-activation-template.md","content":"# {{Skill Name}}\n\n**Version:** {{version}}\n**Type:** {{Simple Skill / Skill Suite}}\n**Created by:** Agent-Skill-Creator v{{version}}\n\n---\n\n## Overview\n\n{{Brief description of what the skill does and why it's useful}}\n\n### Key Features\n\n- {{Feature 1}}\n- {{Feature 2}}\n- {{Feature 3}}\n\n---\n\n## Installation\n\n```bash\n# Installation instructions\n{{installation-commands}}\n```\n\n---\n\n## 🎯 Skill Activation\n\nThis skill uses a **3-Layer Activation System** for reliable detection.\n\n### ✅ Phrases That Activate This Skill\n\nThe skill will automatically activate when you use phrases like:\n\n#### Primary Activation Phrases\n1. **\"{{keyword-phrase-1}}\"**\n - Example: \"{{example-1}}\"\n\n2. **\"{{keyword-phrase-2}}\"**\n - Example: \"{{example-2}}\"\n\n3. **\"{{keyword-phrase-3}}\"**\n - Example: \"{{example-3}}\"\n\n#### Workflow-Based Activation\n4. **\"{{workflow-phrase-1}}\"**\n - Example: \"{{example-4}}\"\n\n5. **\"{{workflow-phrase-2}}\"**\n - Example: \"{{example-5}}\"\n\n#### Domain-Specific Activation\n6. **\"{{domain-phrase-1}}\"**\n - Example: \"{{example-6}}\"\n\n7. **\"{{domain-phrase-2}}\"**\n - Example: \"{{example-7}}\"\n\n#### Natural Language Variations\n8. **\"{{natural-variation-1}}\"**\n - Example: \"{{example-8}}\"\n\n9. **\"{{natural-variation-2}}\"**\n - Example: \"{{example-9}}\"\n\n10. **\"{{natural-variation-3}}\"**\n - Example: \"{{example-10}}\"\n\n### ❌ Phrases That Do NOT Activate\n\nTo prevent false positives, this skill will **NOT** activate for:\n\n1. **{{counter-case-1}}**\n - Example: \"{{counter-example-1}}\"\n - Reason: {{reason-1}}\n\n2. **{{counter-case-2}}**\n - Example: \"{{counter-example-2}}\"\n - Reason: {{reason-2}}\n\n3. **{{counter-case-3}}**\n - Example: \"{{counter-example-3}}\"\n - Reason: {{reason-3}}\n\n### 💡 Activation Tips\n\nTo ensure reliable activation:\n\n**DO:**\n- ✅ Use action verbs: {{verb-examples}}\n- ✅ Be specific about: {{context-requirements}}\n- ✅ Mention: {{entity-keywords}}\n- ✅ Include context: {{context-examples}}\n\n**DON'T:**\n- ❌ Use vague phrases like \"{{vague-example}}\"\n- ❌ Omit key entities like \"{{missing-entity}}\"\n- ❌ Be too generic: \"{{generic-example}}\"\n\n### 🎯 Example Activation Patterns\n\n**Pattern 1:** {{Pattern-description-1}}\n```\nUser: \"{{example-query-1}}\"\nResult: ✅ Skill activates via {{layer-name}}\n```\n\n**Pattern 2:** {{Pattern-description-2}}\n```\nUser: \"{{example-query-2}}\"\nResult: ✅ Skill activates via {{layer-name}}\n```\n\n**Pattern 3:** {{Pattern-description-3}}\n```\nUser: \"{{example-query-3}}\"\nResult: ✅ Skill activates via {{layer-name}}\n```\n\n---\n\n## Usage\n\n### Basic Usage\n\n```{{language}}\n{{basic-usage-example}}\n```\n\n### Advanced Usage\n\n```{{language}}\n{{advanced-usage-example}}\n```\n\n### Real-World Examples\n\n#### Example 1: {{Example-title-1}}\n\n**User Query:**\n```\n\"{{example-query-1}}\"\n```\n\n**Skill Actions:**\n1. {{action-step-1}}\n2. {{action-step-2}}\n3. {{action-step-3}}\n\n**Output:**\n```{{format}}\n{{example-output-1}}\n```\n\n#### Example 2: {{Example-title-2}}\n\n**User Query:**\n```\n\"{{example-query-2}}\"\n```\n\n**Skill Actions:**\n1. {{action-step-1}}\n2. {{action-step-2}}\n3. {{action-step-3}}\n\n**Output:**\n```{{format}}\n{{example-output-2}}\n```\n\n---\n\n## Features\n\n### Feature 1: {{Feature-name}}\n\n{{Description of feature 1}}\n\n**Activation:**\n- \"{{feature-1-query}}\"\n\n**Example:**\n```{{language}}\n{{feature-1-example}}\n```\n\n### Feature 2: {{Feature-name}}\n\n{{Description of feature 2}}\n\n**Activation:**\n- \"{{feature-2-query}}\"\n\n**Example:**\n```{{language}}\n{{feature-2-example}}\n```\n\n---\n\n## Configuration\n\n### Optional Configuration\n\n{{Configuration-instructions}}\n\n```{{format}}\n{{configuration-example}}\n```\n\n---\n\n## Troubleshooting\n\n### Issue: Skill Not Activating\n\n**Symptoms:** Your query doesn't activate the skill\n\n**Solutions:**\n1. ✅ Use one of the activation phrases listed above\n2. ✅ Include action verbs: {{verb-list}}\n3. ✅ Mention specific entities: {{entity-list}}\n4. ✅ Provide context: \"{{context-example}}\"\n\n**Example Fix:**\n```\n❌ \"{{vague-query}}\"\n✅ \"{{specific-query}}\"\n```\n\n### Issue: Wrong Skill Activates\n\n**Symptoms:** A different skill activates instead\n\n**Solutions:**\n1. Be more specific about this skill's domain\n2. Use domain-specific keywords: {{domain-keywords}}\n3. Add context that distinguishes from other skills\n\n**Example Fix:**\n```\n❌ \"{{ambiguous-query}}\"\n✅ \"{{specific-query-with-context}}\"\n```\n\n---\n\n## Testing\n\n### Activation Test Suite\n\nYou can verify activation with these test queries:\n\n```markdown\n1. \"{{test-query-1}}\" → Should activate ✅\n2. \"{{test-query-2}}\" → Should activate ✅\n3. \"{{test-query-3}}\" → Should activate ✅\n4. \"{{test-query-4}}\" → Should activate ✅\n5. \"{{test-query-5}}\" → Should activate ✅\n6. \"{{test-query-6}}\" → Should NOT activate ❌\n7. \"{{test-query-7}}\" → Should NOT activate ❌\n```\n\n---\n\n## FAQ\n\n### Q: Why isn't the skill activating for my query?\n\n**A:** Make sure your query includes:\n- Action verb ({{verb-examples}})\n- Entity/object ({{entity-examples}})\n- Specific context ({{context-examples}})\n\nSee the \"Activation Tips\" section above.\n\n### Q: How do I know which phrases will activate the skill?\n\n**A:** Check the \"Phrases That Activate This Skill\" section above for 10+ tested examples.\n\n### Q: Can I use variations of the activation phrases?\n\n**A:** Yes! The skill uses regex patterns and Claude's NLU, so natural variations will work. For example:\n- \"{{variation-1}}\" ✅\n- \"{{variation-2}}\" ✅\n- \"{{variation-3}}\" ✅\n\n---\n\n## Technical Details\n\n### Architecture\n\n{{Architecture-description}}\n\n### Components\n\n- **{{Component-1}}**: {{Description}}\n- **{{Component-2}}**: {{Description}}\n- **{{Component-3}}**: {{Description}}\n\n### Dependencies\n\n```{{format}}\n{{dependencies-list}}\n```\n\n---\n\n## Contributing\n\n{{Contributing-guidelines}}\n\n---\n\n## License\n\n{{License-information}}\n\n---\n\n## Changelog\n\n### v{{version}} ({{date}})\n- {{change-1}}\n- {{change-2}}\n- {{change-3}}\n\n---\n\n## Support\n\nFor issues or questions:\n- {{support-contact}}\n\n---\n\n**Generated by:** Agent-Skill-Creator v{{version}}\n**Last Updated:** {{date}}\n**Activation System:** 3-Layer (Keywords + Patterns + Description)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":6090,"content_sha256":"e81874c16403e998d41e07e8abeec5acdfb1a9a2aec65737139b0dafcc765ad9"},{"filename":"registry/registry.json","content":"{\n \"registry\": {\n \"name\": \"Shared Skills\",\n \"created\": \"2026-02-26T19:19:18+00:00\",\n \"schema_version\": \"1\"\n },\n \"skills\": []\n}\n","content_type":"application/json; charset=utf-8","language":"json","size":139,"content_sha256":"5683ba3dffb3833448307973c41abbd6dd5373ff45328881b8488e9fe345facf"},{"filename":"scripts/artifact_detector.py","content":"#!/usr/bin/env python3\n\"\"\"Artifact opportunity detector for agent-skill-creator v6.\n\nPublic API: detect_artifact(description, domain=None)\n\nReturns one of: \"line-chart\", \"bar-chart\", \"kpi-cards\", \"data-table\", None.\n\nHeuristic is keyword/pattern based. No external dependencies.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport re\n\n\nTemplate = str | None\n\n\nTEMPORAL_KEYWORDS = (\n \"trend\", \"over time\", \"over the last\", \"over the past\", \"monthly\",\n \"weekly\", \"daily\", \"hourly\", \"year over year\", \"year-over-year\",\n \"month over month\", \"month-over-month\", \"history\", \"historical\",\n \"past week\", \"past month\", \"past quarter\", \"past year\",\n)\n\n\ndef _has_temporal_signal(text: str) -> bool:\n lowered = text.lower()\n return any(keyword in lowered for keyword in TEMPORAL_KEYWORDS)\n\n\nCOMPARATIVE_KEYWORDS = (\n \" by \", \"compare\", \"comparison\", \"across \", \"per \", \"breakdown\",\n \"ranked\", \"ranking\",\n)\n\n\ndef _has_comparative_signal(text: str) -> bool:\n lowered = \" \" + text.lower() + \" \"\n return any(keyword in lowered for keyword in COMPARATIVE_KEYWORDS)\n\n\nKPI_KEYWORDS = (\n \"kpi\", \"key metric\", \"key metrics\", \"scorecard\", \"headline number\",\n \"north star\", \"top-level\", \"executive summary numbers\", \"highlights\",\n \"sla scorecard\",\n)\n\n\ndef _has_kpi_signal(text: str) -> bool:\n lowered = text.lower()\n return any(keyword in lowered for keyword in KPI_KEYWORDS)\n\n\n# Tabular keywords use word-boundary matching to avoid false positives on\n# short common substrings (e.g. \"log\" inside \"apology\", \"table\" inside\n# \"vegetable\", \"grid\" inside \"frigid\").\nTABULAR_KEYWORDS = (\n \"listing\", \"log\", \"table\", \"grid\", \"status\", \"ticket\", \"tickets\",\n \"invoice\", \"invoices\", \"inventory\", \"shipment\", \"shipments\", \"fleet\",\n \"snapshot\", \"audit results\", \"line items\", \"findings\",\n)\n\n_TABULAR_PATTERN = re.compile(\n r\"\\b(\" + \"|\".join(re.escape(k) for k in TABULAR_KEYWORDS) + r\")\\b\",\n re.IGNORECASE,\n)\n\n\ndef _has_tabular_signal(text: str) -> bool:\n return bool(_TABULAR_PATTERN.search(text))\n\n\ndef detect_artifact(description: str, domain: str | None = None) -> Template:\n \"\"\"Return the artifact template name for the given skill description, or\n None if no artifact is appropriate.\n\n Precedence (load-bearing, not accidental): temporal > KPI > tabular >\n comparative > none. Rationale: time series carry the most information\n per pixel, so they win when a temporal cadence is mentioned even if\n other signals also fire. KPI cards convey headline numbers that\n summarize rather than itemize. Tabular beats comparative because\n structured rows of records read more naturally as a table than as\n bars. Comparative is the most generic positive signal and acts as a\n fallback.\n\n To add a new template in v6.1+: define KEYWORDS, a _has_X_signal\n helper, and insert an `if` branch here at the priority that matches\n the new template's information density relative to the others.\n \"\"\"\n if not description or not description.strip():\n return None\n # Time series outranks all other signals.\n if _has_temporal_signal(description):\n return \"line-chart\"\n # Headline numbers outrank categorical or tabular framings.\n if _has_kpi_signal(description):\n return \"kpi-cards\"\n # Structured rows of records prefer a table over bars.\n if _has_tabular_signal(description):\n return \"data-table\"\n # Generic comparison fallback.\n if _has_comparative_signal(description):\n return \"bar-chart\"\n return None\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3519,"content_sha256":"0bcf013d5270b71da7421fa1f2128c0d5fc9cbc61a49a96d1b8c7cf5493a40ed"},{"filename":"scripts/bootstrap.sh","content":"#!/bin/sh\n# bootstrap.sh — One-liner bootstrap for agent-skill-creator\n#\n# Usage:\n# curl -fsSL https://raw.githubusercontent.com/FrancyJGLisboa/agent-skill-creator/main/scripts/bootstrap.sh | sh\n#\n# Clones agent-skill-creator to ~/.agents/skills/ and symlinks to all detected\n# global platforms. POSIX-compatible (works in bash, dash, zsh, ash).\n\nset -eu\n\n# ---------------------------------------------------------------------------\n# Constants\n# ---------------------------------------------------------------------------\nREPO_URL=\"https://github.com/FrancyJGLisboa/agent-skill-creator.git\"\nSKILL_NAME=\"agent-skill-creator\"\nCANONICAL_DIR=\"$HOME/.agents/skills/$SKILL_NAME\"\n\n# ---------------------------------------------------------------------------\n# Colors (disabled when stdout is not a terminal)\n# ---------------------------------------------------------------------------\nif [ -t 1 ]; then\n GREEN='\\033[0;32m'\n YELLOW='\\033[1;33m'\n BLUE='\\033[0;34m'\n BOLD='\\033[1m'\n NC='\\033[0m'\nelse\n GREEN='' YELLOW='' BLUE='' BOLD='' NC=''\nfi\n\ninfo() { printf \"${BLUE}[INFO]${NC} %s\\n\" \"$1\"; }\nsuccess() { printf \"${GREEN}[OK]${NC} %s\\n\" \"$1\"; }\nwarn() { printf \"${YELLOW}[WARN]${NC} %s\\n\" \"$1\"; }\n\n# ---------------------------------------------------------------------------\n# Detect globally-installed platforms (user-level only, skip project-level)\n# ---------------------------------------------------------------------------\ndetect_global_platforms() {\n platforms=\"\"\n # Claude Code\n if [ -d \"$HOME/.claude\" ]; then\n platforms=\"$platforms claude-code\"\n fi\n # GitHub Copilot\n if [ -d \"$HOME/.copilot\" ]; then\n platforms=\"$platforms copilot\"\n fi\n # Gemini CLI\n if [ -d \"$HOME/.gemini\" ]; then\n platforms=\"$platforms gemini\"\n fi\n # Kiro\n if [ -d \"$HOME/.kiro\" ]; then\n platforms=\"$platforms kiro\"\n fi\n # Cline\n if [ -d \"$HOME/.cline\" ]; then\n platforms=\"$platforms cline\"\n fi\n # Roo Code\n if [ -d \"$HOME/.roo\" ]; then\n platforms=\"$platforms roo-code\"\n fi\n # Kilo Code\n if [ -d \"$HOME/.kilocode\" ]; then\n platforms=\"$platforms kilo-code\"\n fi\n # Factory Droid\n if [ -d \"$HOME/.factory\" ]; then\n platforms=\"$platforms factory\"\n fi\n # Cursor\n if [ -d \"$HOME/.cursor\" ]; then\n platforms=\"$platforms cursor\"\n fi\n # Goose\n if [ -d \"$HOME/.config/goose\" ]; then\n platforms=\"$platforms goose\"\n fi\n # OpenCode\n if [ -d \"$HOME/.config/opencode\" ]; then\n platforms=\"$platforms opencode\"\n fi\n echo \"$platforms\"\n}\n\n# ---------------------------------------------------------------------------\n# Resolve user-level install path for a platform\n# ---------------------------------------------------------------------------\nplatform_path() {\n case \"$1\" in\n claude-code) echo \"$HOME/.claude/skills/$SKILL_NAME\" ;;\n copilot) echo \"$HOME/.copilot/skills/$SKILL_NAME\" ;;\n gemini) echo \"$HOME/.gemini/skills/$SKILL_NAME\" ;;\n kiro) echo \"$HOME/.kiro/skills/$SKILL_NAME\" ;;\n cline) echo \"$HOME/.cline/skills/$SKILL_NAME\" ;;\n roo-code) echo \"$HOME/.roo/skills/$SKILL_NAME\" ;;\n kilo-code) echo \"$HOME/.kilocode/skills/$SKILL_NAME\" ;;\n factory) echo \"$HOME/.factory/skills/$SKILL_NAME\" ;;\n cursor) echo \"$HOME/.cursor/rules/$SKILL_NAME\" ;;\n goose) echo \"$HOME/.config/goose/skills/$SKILL_NAME\" ;;\n opencode) echo \"$HOME/.config/opencode/skills/$SKILL_NAME\" ;;\n esac\n}\n\n# ---------------------------------------------------------------------------\n# Friendly display name for a platform\n# ---------------------------------------------------------------------------\nplatform_display() {\n case \"$1\" in\n claude-code) echo \"Claude Code\" ;;\n copilot) echo \"GitHub Copilot\" ;;\n gemini) echo \"Gemini CLI\" ;;\n kiro) echo \"Kiro\" ;;\n cline) echo \"Cline\" ;;\n roo-code) echo \"Roo Code\" ;;\n kilo-code) echo \"Kilo Code\" ;;\n factory) echo \"Factory Droid\" ;;\n cursor) echo \"Cursor\" ;;\n goose) echo \"Goose\" ;;\n opencode) echo \"OpenCode\" ;;\n esac\n}\n\n# ---------------------------------------------------------------------------\n# Create a symlink (with fallback to copy)\n# ---------------------------------------------------------------------------\ncreate_symlink() {\n target=\"$1\" # what the link points to\n link_path=\"$2\" # where the link lives\n\n # Skip if target and link are the same path\n if [ \"$target\" = \"$link_path\" ]; then\n return 0\n fi\n\n mkdir -p \"$(dirname \"$link_path\")\"\n\n # Remove existing (file, symlink, or directory)\n if [ -e \"$link_path\" ] || [ -L \"$link_path\" ]; then\n rm -rf \"$link_path\"\n fi\n\n if ln -s \"$target\" \"$link_path\" 2>/dev/null; then\n return 0\n else\n warn \"Symlink failed for $link_path — falling back to copy\"\n cp -R \"$target\" \"$link_path\"\n fi\n}\n\n# ---------------------------------------------------------------------------\n# Main\n# ---------------------------------------------------------------------------\nmain() {\n printf \"\\n${BOLD}Agent Skill Creator — Bootstrap Installer${NC}\\n\\n\"\n\n # Check for git\n if ! command -v git >/dev/null 2>&1; then\n warn \"git is not installed. Please install git and try again.\"\n exit 1\n fi\n\n # Clone or update the canonical location\n if [ -d \"$CANONICAL_DIR/.git\" ]; then\n info \"Updating existing install at $CANONICAL_DIR\"\n cd \"$CANONICAL_DIR\" && git pull --ff-only 2>/dev/null || true\n else\n info \"Cloning $SKILL_NAME to $CANONICAL_DIR\"\n mkdir -p \"$(dirname \"$CANONICAL_DIR\")\"\n rm -rf \"$CANONICAL_DIR\"\n git clone \"$REPO_URL\" \"$CANONICAL_DIR\"\n fi\n\n success \"Installed at $CANONICAL_DIR\"\n\n # Detect global platforms and create symlinks\n platforms=\"$(detect_global_platforms)\"\n installed=\"\"\n count=0\n\n for platform in $platforms; do\n dest=\"$(platform_path \"$platform\")\"\n create_symlink \"$CANONICAL_DIR\" \"$dest\"\n name=\"$(platform_display \"$platform\")\"\n success \"Symlinked for $name → $dest\"\n installed=\"$installed $name,\"\n count=$((count + 1))\n done\n\n # ---------------------------------------------------------------------------\n # Summary\n # ---------------------------------------------------------------------------\n printf \"\\n${BOLD}Done!${NC}\\n\\n\"\n printf \" Canonical location: ${BOLD}%s${NC}\\n\" \"$CANONICAL_DIR\"\n\n if [ $count -gt 0 ]; then\n # Trim trailing comma\n installed=\"$(echo \"$installed\" | sed 's/,$//')\"\n printf \" Symlinked to %d platform(s):%s\\n\" \"$count\" \"$installed\"\n fi\n\n printf \"\\n${BOLD}How to use:${NC}\\n\"\n printf \" Open your AI agent and type:\\n\"\n printf \" /agent-skill-creator \u003cdescribe your workflow>\\n\\n\"\n printf \" To update later:\\n\"\n printf \" cd %s && git pull\\n\\n\" \"$CANONICAL_DIR\"\n\n if [ $count -eq 0 ]; then\n warn \"No global platforms detected. The skill is installed at the universal path.\"\n printf \" Tools like Codex CLI, Gemini CLI, Kiro, and others\\n\"\n printf \" read from ~/.agents/skills/ automatically.\\n\\n\"\n fi\n}\n\nmain\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":7361,"content_sha256":"0af96b3584bf873be3b5bd86011ecb646bda4fc37f53bf204a283d1651caf0e6"},{"filename":"scripts/check_pipeline.py","content":"#!/usr/bin/env python3\n\"\"\"\nPipeline orchestration verifier for generated skills.\n\nA skill is prose an agent interprets, so relying on the agent to run several\nscripts in the right order is unreliable. The fix is to move sequencing into\ncode: a single `scripts/run_pipeline.py` entry-point that calls the steps in\norder. This verifier enforces the mechanical half of that contract:\n\n1. compile - every Python file under scripts/ and shared/ compiles (no\n SyntaxError). A broken script is the top reason an agent flails.\n2. deps - if any third-party module is imported, requirements.txt must be\n present and non-empty (so dependencies are declared, not implicit).\n This checks declaration, not per-package coverage, to avoid the\n import-name vs distribution-name false-positive trap.\n3. entry - (warning) when 2+ step scripts exist, a single run_pipeline.py\n orchestrator should exist so the agent runs ONE command instead\n of sequencing steps itself.\n\nUsage:\n python3 scripts/check_pipeline.py \u003cskill-dir>\n python3 scripts/check_pipeline.py \u003cskill-dir> --json\n\nExit codes:\n 0 - no errors (warnings allowed)\n 1 - a script failed to compile, or third-party imports are undeclared\n 2 - skill directory not found\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport ast\nimport json\nimport py_compile\nimport sys\nfrom pathlib import Path\n\n_ENTRY = \"run_pipeline.py\"\n_TOOLING = {\"run_pipeline.py\", \"run_evals.py\"}\n_SKIP_DIR_PARTS = {\"__pycache__\", \"tests\"}\n\n\ndef python_files(skill_dir: Path) -> list[Path]:\n \"\"\"All .py files under the skill's scripts/ and shared/ trees.\"\"\"\n out: list[Path] = []\n for sub in (\"scripts\", \"shared\"):\n root = skill_dir / sub\n if root.is_dir():\n out += [\n p for p in root.rglob(\"*.py\")\n if not (_SKIP_DIR_PARTS & set(p.parts))\n ]\n return sorted(out)\n\n\ndef compile_failures(files: list[Path]) -> list[str]:\n \"\"\"Return 'path: message' for every file that fails to compile.\"\"\"\n failures: list[str] = []\n for f in files:\n try:\n py_compile.compile(str(f), doraise=True)\n except py_compile.PyCompileError as exc:\n failures.append(f\"{f}: {exc.msg}\")\n return failures\n\n\ndef _local_module_names(skill_dir: Path, files: list[Path]) -> set[str]:\n names = {p.stem for p in files}\n scripts_dir = skill_dir / \"scripts\"\n if scripts_dir.is_dir():\n for child in scripts_dir.iterdir():\n if child.is_dir() and child.name not in _SKIP_DIR_PARTS:\n names.add(child.name)\n return names\n\n\ndef _top_level_imports(file: Path) -> set[str]:\n try:\n tree = ast.parse(file.read_text(encoding=\"utf-8\"))\n except SyntaxError:\n return set() # compile_failures reports this separately\n mods: set[str] = set()\n for node in ast.walk(tree):\n if isinstance(node, ast.Import):\n for alias in node.names:\n mods.add(alias.name.split(\".\")[0])\n elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module:\n mods.add(node.module.split(\".\")[0])\n return mods\n\n\ndef third_party_imports(skill_dir: Path, files: list[Path]) -> list[str]:\n \"\"\"Top-level imported modules that are neither stdlib nor local to the skill.\"\"\"\n local = _local_module_names(skill_dir, files)\n stdlib = sys.stdlib_module_names\n third: set[str] = set()\n for f in files:\n for mod in _top_level_imports(f):\n if mod and mod not in local and mod not in stdlib:\n third.add(mod)\n return sorted(third)\n\n\ndef requirements_declared(skill_dir: Path) -> bool:\n \"\"\"True if requirements.txt exists with at least one non-comment entry.\"\"\"\n req = skill_dir / \"requirements.txt\"\n if not req.exists():\n return False\n for line in req.read_text(encoding=\"utf-8\").splitlines():\n stripped = line.strip()\n if stripped and not stripped.startswith(\"#\"):\n return True\n return False\n\n\ndef step_scripts(skill_dir: Path, files: list[Path]) -> list[Path]:\n \"\"\"Scripts that look like runnable pipeline steps (have a __main__ guard),\n excluding the orchestrator, the eval runner, and utility/test modules.\"\"\"\n steps: list[Path] = []\n for f in files:\n if f.name in _TOOLING or \"utils\" in f.parts:\n continue\n text = f.read_text(encoding=\"utf-8\")\n if \"if __name__\" in text and \"__main__\" in text:\n steps.append(f)\n return steps\n\n\ndef has_orchestrator(skill_dir: Path) -> bool:\n return (skill_dir / \"scripts\" / _ENTRY).exists()\n\n\ndef check(skill_dir: Path) -> dict:\n \"\"\"Run all checks. Returns {errors: [...], warnings: [...]}.\"\"\"\n files = python_files(skill_dir)\n errors: list[str] = []\n warnings: list[str] = []\n\n errors += [f\"does not compile -> {fail}\" for fail in compile_failures(files)]\n\n third = third_party_imports(skill_dir, files)\n if third and not requirements_declared(skill_dir):\n errors.append(\n \"third-party imports are not declared: add a requirements.txt listing \"\n f\"the package(s) behind {', '.join(third)}\"\n )\n\n steps = step_scripts(skill_dir, files)\n if len(steps) >= 2 and not has_orchestrator(skill_dir):\n warnings.append(\n f\"{len(steps)} runnable step scripts but no scripts/{_ENTRY} — the agent \"\n \"will have to sequence them from prose. Add a single orchestrator entry-point.\"\n )\n\n return {\"errors\": errors, \"warnings\": warnings}\n\n\ndef main(argv: list[str] | None = None) -> int:\n parser = argparse.ArgumentParser(description=\"Verify a generated skill's pipeline wiring.\")\n parser.add_argument(\"skill_dir\", help=\"Path to the skill directory.\")\n parser.add_argument(\"--json\", action=\"store_true\", help=\"Emit machine-readable JSON.\")\n args = parser.parse_args(argv)\n\n skill_dir = Path(args.skill_dir).resolve()\n if not skill_dir.is_dir():\n msg = f\"not a directory: {skill_dir}\"\n print(json.dumps({\"error\": msg}) if args.json else f\"ERROR: {msg}\", file=sys.stderr)\n return 2\n\n result = check(skill_dir)\n if args.json:\n print(json.dumps(result, indent=2))\n else:\n for err in result[\"errors\"]:\n print(f\" [ERROR] {err}\")\n for warn in result[\"warnings\"]:\n print(f\" [WARN] {warn}\")\n if not result[\"errors\"] and not result[\"warnings\"]:\n print(\"pipeline OK\")\n return 1 if result[\"errors\"] else 0\n\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n","content_type":"text/x-python; charset=utf-8","language":"python","size":6635,"content_sha256":"2b4d4d4b26035777e0324f5aabc2f4528383cd09eb224ec1fbffaccee82e7b53"},{"filename":"scripts/dependency_health.py","content":"#!/usr/bin/env python3\n\"\"\"\nDependency health check for a skill.\n\nIssues an HTTP HEAD to each declared dependency URL and reports whether the\nendpoint is reachable, returning a normal status, or has moved / errored.\nNon-HTTP URLs and URLs without a scheme are reported as skipped.\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom urllib.error import URLError\nfrom urllib.request import Request, urlopen\n\nHTTP_TIMEOUT_SECONDS = 10\n_USER_AGENT = \"agent-skill-staleness-check/1.0\"\n\n\ndef check_dependency_health(dependencies: list[dict]) -> list[dict]:\n \"\"\"\n Probe each dependency's URL. Returns a list of issue dicts (level, message,\n detail). Status thresholds:\n 2xx-3xx -> info \"healthy\"\n 4xx -> warning \"client error / may have moved\"\n 5xx -> error \"server error\"\n URLError, other -> error \"unreachable / check failed\"\n \"\"\"\n issues: list[dict] = []\n\n for dep in dependencies:\n url = dep.get(\"url\", \"\").strip()\n name = dep.get(\"name\", url)\n\n if not url:\n issues.append({\n \"level\": \"warning\",\n \"message\": f\"Dependency '{name}' has no URL\",\n \"detail\": \"Cannot check health without a URL.\",\n })\n continue\n\n if not url.startswith((\"http://\", \"https://\")):\n issues.append({\n \"level\": \"warning\",\n \"message\": f\"Dependency '{name}' has non-HTTP URL\",\n \"detail\": f\"Skipping health check for: {url}\",\n })\n continue\n\n try:\n req = Request(url, method=\"HEAD\")\n req.add_header(\"User-Agent\", _USER_AGENT)\n with urlopen(req, timeout=HTTP_TIMEOUT_SECONDS) as resp:\n status = resp.status\n if 200 \u003c= status \u003c 400:\n issues.append({\n \"level\": \"info\",\n \"message\": f\"Dependency '{name}' is healthy\",\n \"detail\": f\"HTTP {status} from {url}\",\n })\n elif 400 \u003c= status \u003c 500:\n issues.append({\n \"level\": \"warning\",\n \"message\": f\"Dependency '{name}' returned client error\",\n \"detail\": f\"HTTP {status} from {url}. \"\n \"The endpoint may have moved or require authentication.\",\n })\n else:\n issues.append({\n \"level\": \"error\",\n \"message\": f\"Dependency '{name}' returned server error\",\n \"detail\": f\"HTTP {status} from {url}\",\n })\n except URLError as exc:\n issues.append({\n \"level\": \"error\",\n \"message\": f\"Dependency '{name}' is unreachable\",\n \"detail\": f\"Failed to connect to {url}: {exc.reason}\",\n })\n except Exception as exc:\n issues.append({\n \"level\": \"error\",\n \"message\": f\"Dependency '{name}' check failed\",\n \"detail\": f\"Error checking {url}: {exc}\",\n })\n\n return issues\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3178,"content_sha256":"f8f4d9f9ff825b100722a6ba21fa3263fc25b6c63c726798df6223feb39dec4f"},{"filename":"scripts/export_utils.py","content":"#!/usr/bin/env python3\n\"\"\"\nCross-Platform Export Utilities for Agent-Skill-Creator\n\nPackages Claude Code skills for Desktop/Web/API use with versioning and validation.\n\"\"\"\n\nimport os\nimport sys\nimport zipfile\nimport subprocess\nfrom datetime import datetime\nfrom typing import Dict, List\n\nsys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\nfrom skill_document import SkillDoc # noqa: E402\nfrom validate import validate_skill # noqa: E402\n\n# Directories and files to exclude from exports\nEXCLUDE_DIRS = {\n '.git', '__pycache__', 'node_modules', '.claude-plugin',\n 'venv', 'env', '.venv', '.pytest_cache', '.mypy_cache',\n 'dist', 'build', '*.egg-info'\n}\n\nEXCLUDE_FILES = {\n '.DS_Store', '.gitignore', 'Thumbs.db', '*.pyc', '*.pyo',\n '.env', 'credentials.json', '*.log', '.python-version'\n}\n\n# API package size limit (8MB per Claude API requirements)\nMAX_API_SIZE_MB = 8\nMAX_API_SIZE_BYTES = MAX_API_SIZE_MB * 1024 * 1024\n\ndef get_skill_version(skill_path: str, override_version: str = None) -> str:\n \"\"\"\n Determine skill version from git tags, SKILL.md, or use default.\n\n Args:\n skill_path: Path to skill directory\n override_version: User-specified version (takes precedence)\n\n Returns:\n Version string in format \"vX.Y.Z\"\n \"\"\"\n if override_version:\n return override_version if override_version.startswith('v') else f'v{override_version}'\n\n # Try git tags first\n try:\n os.chdir(skill_path)\n result = subprocess.run(\n ['git', 'describe', '--tags', '--abbrev=0'],\n capture_output=True,\n text=True,\n timeout=5\n )\n if result.returncode == 0:\n version = result.stdout.strip()\n return version if version.startswith('v') else f'v{version}'\n except (subprocess.TimeoutExpired, FileNotFoundError, subprocess.SubprocessError):\n pass\n\n # Try SKILL.md frontmatter\n skill_md_path = os.path.join(skill_path, 'SKILL.md')\n if os.path.exists(skill_md_path):\n try:\n version = SkillDoc.from_path(skill_md_path).field(\"version\")\n if version:\n return version if version.startswith('v') else f'v{version}'\n except Exception:\n pass\n\n # Default version\n return 'v1.0.0'\n\n\ndef should_include_file(file_path: str, filename: str) -> bool:\n \"\"\"\n Determine if a file should be included in export.\n\n Args:\n file_path: Full path to file\n filename: Just the filename\n\n Returns:\n True if file should be included\n \"\"\"\n # Check excluded filenames\n if filename in EXCLUDE_FILES:\n return False\n\n # Check excluded patterns\n for pattern in EXCLUDE_FILES:\n if '*' in pattern:\n extension = pattern.replace('*', '')\n if filename.endswith(extension):\n return False\n\n # Check for sensitive files\n if filename in {'.env', 'credentials.json', 'secrets.json', 'api_keys.json'}:\n return False\n\n return True\n\n\ndef get_directory_size(path: str) -> int:\n \"\"\"\n Calculate total size of directory in bytes.\n\n Args:\n path: Directory path\n\n Returns:\n Total size in bytes\n \"\"\"\n total = 0\n for root, dirs, files in os.walk(path):\n # Filter excluded directories\n dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS]\n\n for file in files:\n if should_include_file(os.path.join(root, file), file):\n try:\n total += os.path.getsize(os.path.join(root, file))\n except OSError:\n pass\n return total\n\n\ndef create_export_package(\n skill_path: str,\n output_dir: str,\n variant: str = 'desktop',\n version: str = 'v1.0.0',\n skill_name: str = None\n) -> Dict:\n \"\"\"\n Create optimized export package for specified variant.\n\n Args:\n skill_path: Path to skill directory\n output_dir: Where to save the .zip file\n variant: 'desktop' or 'api'\n version: Version string (e.g., 'v1.0.0')\n skill_name: Override skill name (default: directory name)\n\n Returns:\n Dict with 'success', 'zip_path', 'size_mb', 'files_included', 'message'\n \"\"\"\n if skill_name is None:\n skill_name = os.path.basename(os.path.abspath(skill_path))\n\n # Create output filename\n zip_filename = f\"{skill_name}-{variant}-{version}.zip\"\n zip_path = os.path.join(output_dir, zip_filename)\n\n files_included = []\n total_size = 0\n\n try:\n with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as zipf:\n for root, dirs, files in os.walk(skill_path):\n # Filter excluded directories\n dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS]\n\n # For API variant, exclude .claude-plugin\n if variant == 'api' and '.claude-plugin' in dirs:\n dirs.remove('.claude-plugin')\n\n for file in files:\n if not should_include_file(os.path.join(root, file), file):\n continue\n\n file_path = os.path.join(root, file)\n arcname = os.path.relpath(file_path, skill_path)\n\n # For API variant, apply additional filtering\n if variant == 'api':\n # Skip large documentation files\n if file.endswith('.md') and file not in {'SKILL.md', 'README.md'}:\n continue\n # Skip example files\n if 'examples' in arcname.lower():\n continue\n\n try:\n zipf.write(file_path, arcname)\n files_included.append(arcname)\n total_size += os.path.getsize(file_path)\n except Exception as e:\n print(f\"Warning: Could not add {arcname}: {e}\", file=sys.stderr)\n\n # Check final size\n final_size = os.path.getsize(zip_path)\n size_mb = final_size / (1024 * 1024)\n\n # Warn if API package is too large\n if variant == 'api' and final_size > MAX_API_SIZE_BYTES:\n return {\n 'success': False,\n 'zip_path': zip_path,\n 'size_mb': size_mb,\n 'files_included': files_included,\n 'message': f\"API package too large: {size_mb:.2f} MB (max {MAX_API_SIZE_MB} MB)\"\n }\n\n return {\n 'success': True,\n 'zip_path': zip_path,\n 'size_mb': size_mb,\n 'files_included': files_included,\n 'message': f\"Package created successfully: {len(files_included)} files, {size_mb:.2f} MB\"\n }\n\n except Exception as e:\n return {\n 'success': False,\n 'zip_path': None,\n 'size_mb': 0,\n 'files_included': [],\n 'message': f\"Error creating package: {str(e)}\"\n }\n\n\ndef generate_installation_guide(\n skill_name: str,\n version: str,\n desktop_package: Dict = None,\n api_package: Dict = None,\n output_dir: str = None\n) -> str:\n \"\"\"\n Generate platform-specific installation guide.\n\n Args:\n skill_name: Name of the skill\n version: Version string\n desktop_package: Desktop package info dict (optional)\n api_package: API package info dict (optional)\n output_dir: Where to save the guide\n\n Returns:\n Path to generated installation guide\n \"\"\"\n guide_filename = f\"{skill_name}-{version}_INSTALL.md\"\n guide_path = os.path.join(output_dir, guide_filename)\n\n # Build guide content\n content = f\"\"\"# {skill_name} - Installation Guide\n\n**Version:** {version}\n**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n---\n\n## 📦 Export Packages\n\n\"\"\"\n\n if desktop_package and desktop_package['success']:\n content += f\"\"\"### Desktop/Web Package\n\n**File:** `{os.path.basename(desktop_package['zip_path'])}`\n**Size:** {desktop_package['size_mb']:.2f} MB\n**Files:** {len(desktop_package['files_included'])} files included\n\n✅ Optimized for Claude Desktop and claude.ai manual upload\n\n\"\"\"\n\n if api_package and api_package['success']:\n content += f\"\"\"### API Package\n\n**File:** `{os.path.basename(api_package['zip_path'])}`\n**Size:** {api_package['size_mb']:.2f} MB\n**Files:** {len(api_package['files_included'])} files included\n\n✅ Optimized for programmatic Claude API integration\n\n\"\"\"\n\n content += f\"\"\"---\n\n## Cross-Platform Installation\n\nThis skill works on all platforms supporting the Agent Skills Open Standard.\n\n### Universal Path (works with 6+ tools)\n\n```bash\ncp -r {skill_name}/ ~/.agents/skills/{skill_name}/\n```\n\nWorks with Codex CLI, Gemini CLI, Kiro, Antigravity, and other tools that read `~/.agents/skills/`.\n\n### Using install.sh (Recommended)\n\nIf the skill includes an `install.sh` script:\n\n```bash\n# Auto-detect platform and install\n./install.sh\n\n# Install to specific platform\n./install.sh --platform claude-code\n./install.sh --platform copilot\n./install.sh --platform cursor\n\n# Install to ALL detected platforms\n./install.sh --all\n\n# Project-level install\n./install.sh --project\n\n# Preview without installing\n./install.sh --dry-run\n```\n\n### Alternative: npx\n\n```bash\nnpx skills add ./{skill_name}\n```\n\n### Manual Installation by Platform\n\n#### Claude Code\n```bash\n# User-level\ncp -r {skill_name}/ ~/.claude/skills/{skill_name}/\n\n# Project-level\ncp -r {skill_name}/ .claude/skills/{skill_name}/\n```\n\n#### GitHub Copilot\n```bash\ncp -r {skill_name}/ .github/skills/{skill_name}/\n```\n\n#### Cursor\n```bash\ncp -r {skill_name}/ .cursor/rules/{skill_name}/\n```\n\n#### Windsurf\n```bash\n# Project-level\ncp -r {skill_name}/ .windsurf/rules/{skill_name}/\n```\n\n#### Cline\n```bash\ncp -r {skill_name}/ .clinerules/{skill_name}/\n```\n\n#### OpenAI Codex CLI\n```bash\ncp -r {skill_name}/ ~/.agents/skills/{skill_name}/\n```\n\n#### Gemini CLI\n```bash\ncp -r {skill_name}/ ~/.gemini/skills/{skill_name}/\n```\n\n#### Kiro\n```bash\ncp -r {skill_name}/ .kiro/skills/{skill_name}/\n```\n\n#### Trae\n```bash\ncp -r {skill_name}/ .trae/rules/{skill_name}/\n```\n\n#### Goose\n```bash\ncp -r {skill_name}/ ~/.config/goose/skills/{skill_name}/\n```\n\n#### OpenCode\n```bash\ncp -r {skill_name}/ ~/.config/opencode/skills/{skill_name}/\n```\n\n#### Roo Code\n```bash\ncp -r {skill_name}/ .roo/rules/{skill_name}/\n```\n\n#### Antigravity\n```bash\ncp -r {skill_name}/ .agents/skills/{skill_name}/\n```\n\n### Claude Desktop / claude.ai (Web)\n\n1. Locate the Desktop package: `{skill_name}-desktop-{{version}}.zip`\n2. Open Claude Desktop or claude.ai\n3. Go to **Settings > Skills > Upload skill**\n4. Select the .zip file and confirm\n\n### Claude API\n\n1. Locate the API package: `{skill_name}-api-{{version}}.zip`\n2. Upload programmatically:\n```python\nimport anthropic\nclient = anthropic.Anthropic()\nwith open('{skill_name}-api-{{version}}.zip', 'rb') as f:\n skill = client.skills.create(file=f, name=\"{skill_name}\")\n```\n\n---\n\n## Platform Comparison\n\n| Platform | Install Method | Updates | marketplace.json |\n|----------|---------------|---------|-----------------|\n| **Universal** | install.sh / copy | git pull | Not used |\n| **Claude Code** | install.sh / copy | git pull | Optional |\n| **GitHub Copilot** | install.sh / copy | git pull | Not used |\n| **Cursor** | install.sh / copy (+ .mdc) | git pull | Not used |\n| **Windsurf** | install.sh / copy | git pull | Not used |\n| **Cline** | install.sh / copy | git pull | Not used |\n| **Codex CLI** | install.sh / copy | git pull | Not used |\n| **Gemini CLI** | install.sh / copy | git pull | Not used |\n| **Kiro** | install.sh / copy | git pull | Not used |\n| **Trae** | install.sh / copy | git pull | Not used |\n| **Goose** | install.sh / copy | git pull | Not used |\n| **OpenCode** | install.sh / copy | git pull | Not used |\n| **Roo Code** | install.sh / copy | git pull | Not used |\n| **Desktop/Web** | .zip upload | Re-upload | Not used |\n| **Claude API** | API upload | New upload | Not used |\n\n---\n\n## Technical Details\n\n### What's Included\n\n\"\"\"\n\n if desktop_package and desktop_package['success']:\n content += \"\"\"**Desktop Package:**\n- SKILL.md (core functionality)\n- Complete scripts/ directory\n- Full references/ documentation\n- All assets/ and templates\n- README.md and requirements.txt\n\n\"\"\"\n\n if api_package and api_package['success']:\n content += \"\"\"**API Package:**\n- SKILL.md (required)\n- Essential scripts only\n- Minimal documentation (execution-focused)\n- Size-optimized (\u003c 8MB)\n\n\"\"\"\n\n content += \"\"\"### What's Excluded (Security)\n\nFor both packages:\n- `.git/` (version control history)\n- `__pycache__/` (compiled Python)\n- `.env` files (environment variables)\n- `credentials.json` (API keys/secrets)\n- `.DS_Store` (system metadata)\n\nFor API package additionally:\n- `.claude-plugin/` (Claude Code specific)\n- Large documentation files\n- Example files (size optimization)\n\n---\n\n## 🔧 Troubleshooting\n\n### Upload fails with \"File too large\"\n\n**Desktop/Web:**\n- Maximum size varies by platform\n- Try the API package instead (smaller)\n- Contact support if needed\n\n**API:**\n- Maximum: 8MB\n- The API package is already optimized\n- May need to reduce documentation or scripts\n\n### Skill doesn't activate\n\n**Check:**\n1. SKILL.md has valid frontmatter\n2. `name:` field is present and ≤ 64 characters\n3. `description:` field is present and ≤ 1024 characters\n4. Description clearly explains when to use the skill\n\n### API errors\n\n**Common issues:**\n- Missing beta headers (required!)\n- Skill ID incorrect (check `skill.id` after upload)\n- Network/pip install attempted (not allowed in API environment)\n\n---\n\n## 📚 Additional Resources\n\n- **Export Guide:** See `references/export-guide.md` in the main repository\n- **Cross-Platform Guide:** See `references/cross-platform-guide.md`\n- **Main Documentation:** See the main README.md\n\n---\n\n## ✅ Verification Checklist\n\nAfter installation, verify:\n\n- [ ] Skill appears in Skills list\n- [ ] Skill activates with relevant queries\n- [ ] Scripts execute correctly\n- [ ] Documentation is accessible\n- [ ] No error messages on activation\n\n---\n\n**Need help?** Refer to the platform-specific documentation or the main repository guides.\n\n**Generated by:** agent-skill-creator v4.0 cross-platform export system\n\"\"\"\n\n # Write guide to file\n with open(guide_path, 'w', encoding='utf-8') as f:\n f.write(content)\n\n return guide_path\n\n\ndef export_skill(\n skill_path: str,\n variants: List[str] = ['desktop', 'api'],\n platform: str = None,\n version_override: str = None,\n output_dir: str = None\n) -> Dict:\n \"\"\"\n Main export function - validates, packages, and generates guides.\n\n Args:\n skill_path: Path to skill directory\n variants: List of variants to create ('desktop', 'api', or both)\n platform: Target platform for platform-specific output (optional)\n version_override: User-specified version (optional)\n output_dir: Where to save exports (default: exports/ in parent dir)\n\n Returns:\n Dict with export results\n \"\"\"\n # Normalize path\n skill_path = os.path.abspath(skill_path)\n skill_name = os.path.basename(skill_path)\n\n # Determine output directory\n if output_dir is None:\n # Use exports/ in parent directory\n parent_dir = os.path.dirname(skill_path)\n output_dir = os.path.join(parent_dir, 'exports')\n\n # Create output directory if needed\n os.makedirs(output_dir, exist_ok=True)\n\n # Validate the skill in one in-process call (single source of truth = validate.validate_skill)\n print(\"🔍 Validating skill...\")\n val_result = validate_skill(skill_path)\n if val_result.get('errors'):\n return {\n 'success': False,\n 'message': 'Skill validation failed',\n 'issues': val_result['errors'],\n }\n print(\"✅ Skill validation passed\")\n if val_result.get('warnings'):\n print(f\"⚠️ Validation warnings: {len(val_result['warnings'])}\")\n for w in val_result['warnings']:\n print(f\" - {w}\")\n\n # Run security scan if security_scan.py is available\n security_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'security_scan.py')\n if os.path.exists(security_script):\n print(\"🔍 Running security scan...\")\n try:\n result = subprocess.run(\n [sys.executable, security_script, skill_path, '--json'],\n capture_output=True, text=True, timeout=30\n )\n if result.returncode != 0:\n import json as _json\n try:\n sec_result = _json.loads(result.stdout)\n if sec_result.get('issues'):\n print(f\"⚠️ Security issues found: {len(sec_result['issues'])}\")\n for issue in sec_result['issues'][:5]:\n print(f\" - [{issue.get('severity', 'unknown')}] {issue.get('description', '')}\")\n except (ValueError, KeyError):\n pass\n else:\n print(\"✅ Security scan passed\")\n except (subprocess.TimeoutExpired, Exception):\n print(\"⚠️ Security scan skipped (script error)\")\n\n # Determine version\n version = get_skill_version(skill_path, version_override)\n print(f\"📌 Version: {version}\")\n\n # Create packages\n results = {\n 'success': True,\n 'version': version,\n 'packages': {}\n }\n\n if 'desktop' in variants:\n print(\"\\n🔨 Creating Desktop/Web package...\")\n desktop_result = create_export_package(\n skill_path, output_dir, 'desktop', version, skill_name\n )\n results['packages']['desktop'] = desktop_result\n if desktop_result['success']:\n print(f\"✅ Desktop package: {os.path.basename(desktop_result['zip_path'])} ({desktop_result['size_mb']:.2f} MB)\")\n else:\n print(f\"❌ Desktop package failed: {desktop_result['message']}\")\n results['success'] = False\n\n if 'api' in variants:\n print(\"\\n🔨 Creating API package...\")\n api_result = create_export_package(\n skill_path, output_dir, 'api', version, skill_name\n )\n results['packages']['api'] = api_result\n if api_result['success']:\n print(f\"✅ API package: {os.path.basename(api_result['zip_path'])} ({api_result['size_mb']:.2f} MB)\")\n else:\n print(f\"❌ API package failed: {api_result['message']}\")\n results['success'] = False\n\n # Generate installation guide\n if results['success']:\n print(\"\\n📄 Generating installation guide...\")\n guide_path = generate_installation_guide(\n skill_name,\n version,\n desktop_package=results['packages'].get('desktop'),\n api_package=results['packages'].get('api'),\n output_dir=output_dir\n )\n results['guide_path'] = guide_path\n print(f\"✅ Installation guide: {os.path.basename(guide_path)}\")\n\n return results\n\n\ndef main():\n \"\"\"CLI interface for export_utils.py\"\"\"\n if len(sys.argv) \u003c 2:\n print(\"\"\"\nUsage: python export_utils.py \u003cskill-path> [options]\n\nArguments:\n skill-path Path to skill directory\n\nOptions:\n --variant VARIANT Export variant: desktop, api, or both (default: both)\n --version VERSION Override version (default: auto-detect)\n --output-dir DIR Output directory (default: exports/)\n\nExamples:\n python export_utils.py ./my-skill\n python export_utils.py ./my-skill --variant desktop\n python export_utils.py ./my-skill --version 2.0.1\n python export_utils.py ./my-skill --variant api --output-dir ./dist\n\"\"\")\n sys.exit(1)\n\n skill_path = sys.argv[1]\n\n # Parse options\n variants = ['desktop', 'api'] # default: both\n version_override = None\n output_dir = None\n\n i = 2\n while i \u003c len(sys.argv):\n if sys.argv[i] == '--variant':\n variant_arg = sys.argv[i + 1]\n if variant_arg == 'both':\n variants = ['desktop', 'api']\n else:\n variants = [variant_arg]\n i += 2\n elif sys.argv[i] == '--version':\n version_override = sys.argv[i + 1]\n i += 2\n elif sys.argv[i] == '--output-dir':\n output_dir = sys.argv[i + 1]\n i += 2\n else:\n print(f\"Unknown option: {sys.argv[i]}\")\n sys.exit(1)\n\n # Run export\n print(f\"\\n🚀 Exporting skill: {os.path.basename(skill_path)}\\n\")\n results = export_skill(skill_path, variants, version_override, output_dir)\n\n # Print summary\n print(f\"\\n{'='*60}\")\n if results['success']:\n print(\"✅ Export completed successfully!\")\n print(\"\\n📦 Packages created:\")\n for variant, package in results['packages'].items():\n if package['success']:\n print(f\" - {variant.capitalize()}: {os.path.basename(package['zip_path'])}\")\n if 'guide_path' in results:\n print(f\"\\n📄 Installation guide: {os.path.basename(results['guide_path'])}\")\n print(f\"\\n🎯 All files saved to: {output_dir or 'exports/'}\")\n else:\n print(\"❌ Export failed!\")\n if 'issues' in results:\n print(\"\\nIssues found:\")\n for issue in results['issues']:\n print(f\" - {issue}\")\n print(f\"{'='*60}\\n\")\n\n sys.exit(0 if results['success'] else 1)\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":21604,"content_sha256":"c37cb1f5729278b8bc7026bfbd9229eb76e2f4e38beb04f5971d35610b22eb6c"},{"filename":"scripts/install-skill.sh","content":"#!/bin/sh\n# install-skill.sh — Install any skill (git URL or local path) to all detected platforms\n#\n# Usage:\n# ./scripts/install-skill.sh https://github.com/someone/sales-report-skill.git\n# ./scripts/install-skill.sh ./sales-report-skill\n# ./scripts/install-skill.sh ./sales-report-skill --platform cursor --project\n# ./scripts/install-skill.sh ./sales-report-skill --dry-run\n# ./scripts/install-skill.sh ./sales-report-skill --uninstall\n#\n# POSIX-compatible (works in bash, dash, zsh, ash).\n\nset -eu\n\n# ---------------------------------------------------------------------------\n# Colors (disabled when stdout is not a terminal)\n# ---------------------------------------------------------------------------\nif [ -t 1 ]; then\n GREEN='\\033[0;32m'\n YELLOW='\\033[1;33m'\n BLUE='\\033[0;34m'\n RED='\\033[0;31m'\n BOLD='\\033[1m'\n NC='\\033[0m'\nelse\n GREEN='' YELLOW='' BLUE='' RED='' BOLD='' NC=''\nfi\n\ninfo() { printf \"${BLUE}[INFO]${NC} %s\\n\" \"$1\"; }\nsuccess() { printf \"${GREEN}[OK]${NC} %s\\n\" \"$1\"; }\nwarn() { printf \"${YELLOW}[WARN]${NC} %s\\n\" \"$1\"; }\nerror() { printf \"${RED}[ERROR]${NC} %s\\n\" \"$1\" >&2; }\n\n# ---------------------------------------------------------------------------\n# Options\n# ---------------------------------------------------------------------------\nSOURCE=\"\"\nPLATFORM=\"\"\nPROJECT_LEVEL=false\nDRY_RUN=false\nUNINSTALL=false\n\nusage() {\n cat \u003c\u003c'USAGE'\nUsage: install-skill.sh \u003csource> [options]\n\nArguments:\n \u003csource> Git URL (https://... or *.git) or local directory path\n\nOptions:\n --platform \u003cname> Install to a specific platform only\n --project Use project-level paths (for Cursor, Windsurf, etc.)\n --all Install to all detected platforms (default)\n --dry-run Preview without making changes\n --uninstall Remove the skill from all platforms\n -h, --help Show this help message\n\nExamples:\n install-skill.sh https://github.com/someone/sales-report-skill.git\n install-skill.sh ./sales-report-skill\n install-skill.sh ./sales-report-skill --platform cursor --project\nUSAGE\n}\n\nwhile [ $# -gt 0 ]; do\n case \"$1\" in\n --platform)\n shift\n PLATFORM=\"${1:-}\"\n if [ -z \"$PLATFORM\" ]; then\n error \"--platform requires a value\"\n exit 1\n fi\n ;;\n --project) PROJECT_LEVEL=true ;;\n --all) PLATFORM=\"\" ;;\n --dry-run) DRY_RUN=true ;;\n --uninstall) UNINSTALL=true ;;\n -h|--help) usage; exit 0 ;;\n -*) error \"Unknown option: $1\"; exit 1 ;;\n *)\n if [ -z \"$SOURCE\" ]; then\n SOURCE=\"$1\"\n else\n error \"Unexpected argument: $1\"\n exit 1\n fi\n ;;\n esac\n shift\ndone\n\nif [ -z \"$SOURCE\" ]; then\n error \"Missing required argument: \u003csource>\"\n usage\n exit 1\nfi\n\n# ---------------------------------------------------------------------------\n# Resolve source: git clone or validate local path\n# ---------------------------------------------------------------------------\nis_git_url() {\n case \"$1\" in\n *://*) return 0 ;;\n *.git) return 0 ;;\n esac\n return 1\n}\n\nresolve_source() {\n if is_git_url \"$SOURCE\"; then\n # Extract skill name from URL\n skill_basename=\"$(basename \"$SOURCE\" .git)\"\n canonical_dir=\"$HOME/.agents/skills/$skill_basename\"\n\n if [ -d \"$canonical_dir/.git\" ]; then\n info \"Updating existing install at $canonical_dir\"\n if [ \"$DRY_RUN\" = false ]; then\n cd \"$canonical_dir\" && git pull --ff-only 2>/dev/null || true\n cd - >/dev/null\n fi\n else\n info \"Cloning $SOURCE\"\n if [ \"$DRY_RUN\" = false ]; then\n mkdir -p \"$(dirname \"$canonical_dir\")\"\n rm -rf \"$canonical_dir\"\n git clone \"$SOURCE\" \"$canonical_dir\"\n fi\n fi\n SOURCE_DIR=\"$canonical_dir\"\n else\n # Local path\n if [ ! -d \"$SOURCE\" ]; then\n error \"Source directory not found: $SOURCE\"\n exit 1\n fi\n SOURCE_DIR=\"$(cd \"$SOURCE\" && pwd)\"\n fi\n}\n\n# ---------------------------------------------------------------------------\n# Extract skill name from directory or SKILL.md frontmatter\n# ---------------------------------------------------------------------------\nextract_skill_name() {\n skill_name=\"\"\n skill_md=\"$SOURCE_DIR/SKILL.md\"\n\n # Try to extract from SKILL.md frontmatter\n if [ -f \"$skill_md\" ]; then\n in_fm=false\n lnum=0\n while IFS= read -r line; do\n lnum=$((lnum + 1))\n if [ \"$lnum\" -eq 1 ] && [ \"$line\" = \"---\" ]; then\n in_fm=true\n continue\n fi\n if $in_fm && [ \"$line\" = \"---\" ]; then break; fi\n if $in_fm; then\n case \"$line\" in\n name:*)\n skill_name=\"$(echo \"$line\" | sed 's/^name:[[:space:]]*//' | sed 's/^[\"'\"'\"']//' | sed 's/[\"'\"'\"']$//')\"\n ;;\n esac\n fi\n done \u003c \"$skill_md\"\n fi\n\n # Fallback to directory basename\n if [ -z \"$skill_name\" ]; then\n skill_name=\"$(basename \"$SOURCE_DIR\")\"\n fi\n\n SKILL_NAME=\"$skill_name\"\n}\n\n# ---------------------------------------------------------------------------\n# Validate SKILL.md exists\n# ---------------------------------------------------------------------------\nvalidate_source() {\n if [ ! -f \"$SOURCE_DIR/SKILL.md\" ]; then\n error \"No SKILL.md found in $SOURCE_DIR\"\n error \"A valid skill must contain a SKILL.md file.\"\n exit 1\n fi\n}\n\n# ---------------------------------------------------------------------------\n# Platform detection and path resolution\n# ---------------------------------------------------------------------------\ndetect_all_global_platforms() {\n platforms=\"\"\n if [ -d \"$HOME/.claude\" ]; then platforms=\"$platforms claude-code\"; fi\n if [ -d \"$HOME/.copilot\" ]; then platforms=\"$platforms copilot\"; fi\n if [ -d \"$HOME/.gemini\" ]; then platforms=\"$platforms gemini\"; fi\n if [ -d \"$HOME/.kiro\" ]; then platforms=\"$platforms kiro\"; fi\n if [ -d \"$HOME/.cline\" ]; then platforms=\"$platforms cline\"; fi\n if [ -d \"$HOME/.roo\" ]; then platforms=\"$platforms roo-code\"; fi\n if [ -d \"$HOME/.kilocode\" ]; then platforms=\"$platforms kilo-code\"; fi\n if [ -d \"$HOME/.factory\" ]; then platforms=\"$platforms factory\"; fi\n if [ -d \"$HOME/.config/goose\" ]; then platforms=\"$platforms goose\"; fi\n if [ -d \"$HOME/.config/opencode\" ]; then platforms=\"$platforms opencode\"; fi\n echo \"$platforms\"\n}\n\ndetect_all_project_platforms() {\n platforms=\"\"\n if [ -d \".claude\" ]; then platforms=\"$platforms claude-code\"; fi\n if [ -d \".github\" ]; then platforms=\"$platforms copilot\"; fi\n if [ -d \".cursor\" ]; then platforms=\"$platforms cursor\"; fi\n if [ -d \".windsurf\" ]; then platforms=\"$platforms windsurf\"; fi\n if [ -d \".clinerules\" ] || [ -d \".cline\" ]; then platforms=\"$platforms cline\"; fi\n if [ -d \".gemini\" ]; then platforms=\"$platforms gemini\"; fi\n if [ -d \".kiro\" ]; then platforms=\"$platforms kiro\"; fi\n if [ -d \".trae\" ]; then platforms=\"$platforms trae\"; fi\n if [ -d \".roo\" ]; then platforms=\"$platforms roo-code\"; fi\n if [ -d \".kilocode\" ]; then platforms=\"$platforms kilo-code\"; fi\n if [ -d \".factory\" ]; then platforms=\"$platforms factory\"; fi\n if [ -d \".junie\" ]; then platforms=\"$platforms junie\"; fi\n if [ -d \".agent\" ]; then platforms=\"$platforms antigravity\"; fi\n echo \"$platforms\"\n}\n\nresolve_platform_path() {\n plat=\"$1\"\n name=\"$2\"\n if [ \"$PROJECT_LEVEL\" = true ]; then\n case \"$plat\" in\n claude-code) echo \".claude/skills/$name\" ;;\n copilot) echo \".github/skills/$name\" ;;\n cursor) echo \".cursor/rules/$name\" ;;\n windsurf) echo \".windsurf/rules/$name\" ;;\n cline) echo \".clinerules/skills/$name\" ;;\n codex) echo \".agents/skills/$name\" ;;\n gemini) echo \".gemini/skills/$name\" ;;\n kiro) echo \".kiro/skills/$name\" ;;\n trae) echo \".trae/rules/$name\" ;;\n roo-code) echo \".roo/skills/$name\" ;;\n kilo-code) echo \".kilocode/skills/$name\" ;;\n factory) echo \".factory/skills/$name\" ;;\n junie) echo \".junie/skills/$name\" ;;\n goose) echo \".goose/skills/$name\" ;;\n opencode) echo \".opencode/skills/$name\" ;;\n antigravity) echo \".agent/skills/$name\" ;;\n *) echo \".agents/skills/$name\" ;;\n esac\n else\n case \"$plat\" in\n claude-code) echo \"$HOME/.claude/skills/$name\" ;;\n copilot) echo \"$HOME/.copilot/skills/$name\" ;;\n cursor) echo \"$HOME/.cursor/rules/$name\" ;;\n windsurf) echo \"$HOME/.codeium/windsurf/skills/$name\" ;;\n cline) echo \"$HOME/.cline/skills/$name\" ;;\n codex) echo \"$HOME/.agents/skills/$name\" ;;\n gemini) echo \"$HOME/.gemini/skills/$name\" ;;\n kiro) echo \"$HOME/.kiro/skills/$name\" ;;\n trae) echo \"$HOME/.trae/rules/$name\" ;;\n roo-code) echo \"$HOME/.roo/skills/$name\" ;;\n kilo-code) echo \"$HOME/.kilocode/skills/$name\" ;;\n factory) echo \"$HOME/.factory/skills/$name\" ;;\n junie) echo \"$HOME/.junie/skills/$name\" ;;\n goose) echo \"$HOME/.config/goose/skills/$name\" ;;\n opencode) echo \"$HOME/.config/opencode/skills/$name\" ;;\n antigravity) echo \"$HOME/.gemini/antigravity/skills/$name\" ;;\n *) echo \"$HOME/.agents/skills/$name\" ;;\n esac\n fi\n}\n\nplatform_display() {\n case \"$1\" in\n claude-code) echo \"Claude Code\" ;;\n copilot) echo \"GitHub Copilot\" ;;\n cursor) echo \"Cursor\" ;;\n windsurf) echo \"Windsurf\" ;;\n cline) echo \"Cline\" ;;\n codex) echo \"Codex CLI\" ;;\n gemini) echo \"Gemini CLI\" ;;\n kiro) echo \"Kiro\" ;;\n trae) echo \"Trae\" ;;\n roo-code) echo \"Roo Code\" ;;\n kilo-code) echo \"Kilo Code\" ;;\n factory) echo \"Factory Droid\" ;;\n junie) echo \"Junie\" ;;\n goose) echo \"Goose\" ;;\n opencode) echo \"OpenCode\" ;;\n antigravity) echo \"Antigravity\" ;;\n *) echo \"$1\" ;;\n esac\n}\n\n# ---------------------------------------------------------------------------\n# Format adapters (for Tier 2 platforms)\n# ---------------------------------------------------------------------------\ngenerate_cursor_mdc() {\n target_dir=\"$1\"\n skill_md=\"$SOURCE_DIR/SKILL.md\"\n\n desc=\"\"\n in_fm=false\n lnum=0\n while IFS= read -r line; do\n lnum=$((lnum + 1))\n if [ \"$lnum\" -eq 1 ]; then in_fm=true; continue; fi\n if $in_fm && [ \"$line\" = \"---\" ]; then break; fi\n if $in_fm; then\n case \"$line\" in\n description:*) desc=\"$(echo \"$line\" | sed 's/^description:[[:space:]]*//')\" ;;\n esac\n fi\n done \u003c \"$skill_md\"\n\n mdc_file=\"${target_dir}/${SKILL_NAME}.mdc\"\n if [ \"$DRY_RUN\" = true ]; then\n info \"[dry-run] Would generate Cursor .mdc: $mdc_file\"\n return 0\n fi\n\n body=\"$(awk 'BEGIN{c=0} /^---$/{c++;next} c>=2{print}' \"$skill_md\")\"\n mkdir -p \"$target_dir\"\n cat > \"$mdc_file\" \u003c\u003cMDCEOF\n---\ndescription: ${desc}\nglobs:\nalwaysApply: true\n---\n${body}\nMDCEOF\n success \"Generated Cursor .mdc: $mdc_file\"\n}\n\ngenerate_windsurf_rule() {\n target_dir=\"$1\"\n is_global=\"$2\"\n skill_md=\"$SOURCE_DIR/SKILL.md\"\n\n body=\"$(awk 'BEGIN{c=0} /^---$/{c++;next} c>=2{print}' \"$skill_md\")\"\n\n if [ \"$is_global\" = \"true\" ]; then\n global_file=\"$HOME/.codeium/windsurf/memories/global_rules.md\"\n if [ \"$DRY_RUN\" = true ]; then\n info \"[dry-run] Would append to Windsurf global_rules.md: $global_file\"\n return 0\n fi\n mkdir -p \"$(dirname \"$global_file\")\"\n if [ -f \"$global_file\" ]; then\n awk -v begin_marker=\"\u003c!-- BEGIN ${SKILL_NAME} -->\" \\\n -v end_marker=\"\u003c!-- END ${SKILL_NAME} -->\" '\n BEGIN { skip=0 }\n $0 == begin_marker { skip=1; next }\n $0 == end_marker { skip=0; next }\n !skip { print }\n ' \"$global_file\" > \"${global_file}.tmp\"\n mv \"${global_file}.tmp\" \"$global_file\"\n fi\n cat >> \"$global_file\" \u003c\u003cWSEOF\n\n\u003c!-- BEGIN ${SKILL_NAME} -->\n${body}\n\u003c!-- END ${SKILL_NAME} -->\nWSEOF\n success \"Appended to Windsurf global_rules.md\"\n else\n rule_file=\"${target_dir}/${SKILL_NAME}.md\"\n if [ \"$DRY_RUN\" = true ]; then\n info \"[dry-run] Would generate Windsurf rule: $rule_file\"\n return 0\n fi\n mkdir -p \"$target_dir\"\n printf '%s\\n' \"$body\" > \"$rule_file\"\n success \"Generated Windsurf rule: $rule_file\"\n fi\n}\n\ngenerate_plain_rule() {\n target_dir=\"$1\"\n filename=\"$2\"\n skill_md=\"$SOURCE_DIR/SKILL.md\"\n\n plain_file=\"${target_dir}/${filename}\"\n if [ \"$DRY_RUN\" = true ]; then\n info \"[dry-run] Would generate plain rule: $plain_file\"\n return 0\n fi\n mkdir -p \"$target_dir\"\n awk 'BEGIN{c=0} /^---$/{c++;next} c>=2{print}' \"$skill_md\" > \"$plain_file\"\n success \"Generated plain rule: $plain_file\"\n}\n\ngenerate_junie_guideline() {\n target_dir=\"$1\"\n skill_md=\"$SOURCE_DIR/SKILL.md\"\n\n guideline_file=\"${target_dir}/guidelines.md\"\n if [ \"$DRY_RUN\" = true ]; then\n info \"[dry-run] Would generate Junie guideline: $guideline_file\"\n return 0\n fi\n mkdir -p \"$target_dir\"\n awk 'BEGIN{c=0} /^---$/{c++;next} c>=2{print}' \"$skill_md\" > \"$guideline_file\"\n success \"Generated Junie guideline: $guideline_file\"\n}\n\nrun_adapters() {\n plat=\"$1\"\n dest=\"$2\"\n case \"$plat\" in\n cursor)\n generate_cursor_mdc \"$dest\"\n ;;\n windsurf)\n if [ \"$PROJECT_LEVEL\" = true ]; then\n generate_windsurf_rule \"$(pwd)/.windsurf/rules\" \"false\"\n else\n generate_windsurf_rule \"\" \"true\"\n fi\n ;;\n cline|roo-code|kilo-code|trae)\n generate_plain_rule \"$dest\" \"${SKILL_NAME}.md\"\n ;;\n junie)\n generate_junie_guideline \"$dest\"\n ;;\n esac\n}\n\n# ---------------------------------------------------------------------------\n# Create a symlink (with fallback to copy)\n# ---------------------------------------------------------------------------\ncreate_symlink() {\n target=\"$1\"\n link_path=\"$2\"\n\n if [ \"$target\" = \"$link_path\" ]; then return 0; fi\n\n mkdir -p \"$(dirname \"$link_path\")\"\n\n if [ -e \"$link_path\" ] || [ -L \"$link_path\" ]; then\n rm -rf \"$link_path\"\n fi\n\n if ln -s \"$target\" \"$link_path\" 2>/dev/null; then\n return 0\n else\n warn \"Symlink failed for $link_path — falling back to copy\"\n cp -R \"$target\" \"$link_path\"\n fi\n}\n\n# ---------------------------------------------------------------------------\n# Install to a single platform\n# ---------------------------------------------------------------------------\ninstall_to_platform() {\n plat=\"$1\"\n dest=\"$(resolve_platform_path \"$plat\" \"$SKILL_NAME\")\"\n display=\"$(platform_display \"$plat\")\"\n\n if [ \"$DRY_RUN\" = true ]; then\n info \"[dry-run] Would install to $display: $dest\"\n run_adapters \"$plat\" \"$dest\"\n return 0\n fi\n\n create_symlink \"$SOURCE_DIR\" \"$dest\"\n success \"Installed for $display → $dest\"\n run_adapters \"$plat\" \"$dest\"\n}\n\n# ---------------------------------------------------------------------------\n# Uninstall from all platforms\n# ---------------------------------------------------------------------------\ndo_uninstall() {\n printf \"\\n${BOLD}Uninstalling skill: %s${NC}\\n\\n\" \"$SKILL_NAME\"\n\n # Canonical location\n canonical=\"$HOME/.agents/skills/$SKILL_NAME\"\n if [ -e \"$canonical\" ] || [ -L \"$canonical\" ]; then\n if [ \"$DRY_RUN\" = true ]; then\n info \"[dry-run] Would remove: $canonical\"\n else\n rm -rf \"$canonical\"\n success \"Removed: $canonical\"\n fi\n fi\n\n # Check all global platforms\n for plat in claude-code copilot gemini kiro cline roo-code kilo-code factory goose opencode; do\n dest=\"$(resolve_platform_path \"$plat\" \"$SKILL_NAME\")\"\n if [ -e \"$dest\" ] || [ -L \"$dest\" ]; then\n if [ \"$DRY_RUN\" = true ]; then\n info \"[dry-run] Would remove: $dest\"\n else\n rm -rf \"$dest\"\n success \"Removed: $dest ($(platform_display \"$plat\"))\"\n fi\n fi\n done\n\n # Check project-level platforms\n for plat in claude-code copilot cursor windsurf cline gemini kiro trae roo-code kilo-code factory junie antigravity; do\n PROJECT_LEVEL=true\n dest=\"$(resolve_platform_path \"$plat\" \"$SKILL_NAME\")\"\n if [ -e \"$dest\" ] || [ -L \"$dest\" ]; then\n if [ \"$DRY_RUN\" = true ]; then\n info \"[dry-run] Would remove: $dest\"\n else\n rm -rf \"$dest\"\n success \"Removed: $dest ($(platform_display \"$plat\"))\"\n fi\n fi\n done\n\n printf \"\\nDone.\\n\"\n}\n\n# ---------------------------------------------------------------------------\n# Main\n# ---------------------------------------------------------------------------\nmain() {\n printf \"\\n${BOLD}Universal Skill Installer${NC}\\n\\n\"\n\n resolve_source\n if [ \"$DRY_RUN\" = false ]; then\n validate_source\n elif [ -d \"$SOURCE_DIR\" ]; then\n validate_source\n fi\n extract_skill_name\n info \"Skill: $SKILL_NAME\"\n info \"Source: $SOURCE_DIR\"\n\n if [ \"$UNINSTALL\" = true ]; then\n do_uninstall\n return 0\n fi\n\n # Install to canonical location first (if from git, already there)\n canonical=\"$HOME/.agents/skills/$SKILL_NAME\"\n if ! is_git_url \"$SOURCE\" && [ \"$SOURCE_DIR\" != \"$canonical\" ]; then\n if [ \"$DRY_RUN\" = true ]; then\n info \"[dry-run] Would copy to canonical: $canonical\"\n else\n mkdir -p \"$(dirname \"$canonical\")\"\n rm -rf \"$canonical\"\n cp -R \"$SOURCE_DIR\" \"$canonical\"\n success \"Copied to canonical: $canonical\"\n fi\n fi\n\n # Determine which platforms to install to\n if [ -n \"$PLATFORM\" ]; then\n # Single platform\n install_to_platform \"$PLATFORM\"\n else\n # All detected platforms\n if [ \"$PROJECT_LEVEL\" = true ]; then\n platforms=\"$(detect_all_project_platforms)\"\n else\n platforms=\"$(detect_all_global_platforms)\"\n fi\n\n count=0\n for plat in $platforms; do\n install_to_platform \"$plat\"\n count=$((count + 1))\n done\n\n if [ $count -eq 0 ]; then\n warn \"No platforms detected. Skill installed at canonical path only.\"\n fi\n fi\n\n # Summary\n printf \"\\n${BOLD}Done!${NC}\\n\"\n printf \" Canonical: %s\\n\" \"$canonical\"\n printf \" Invoke with: /${SKILL_NAME}\\n\\n\"\n\n if [ \"$DRY_RUN\" = true ]; then\n printf \"${YELLOW}Dry run — no changes made.${NC}\\n\\n\"\n fi\n}\n\nmain\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":19605,"content_sha256":"28c0babee5ac5dedf2f9f369fcc1a92e623f59e77ebf23787524db7f3a1bd8ee"},{"filename":"scripts/install-template.sh","content":"#!/bin/sh\n# install-template.sh — Cross-platform skill installation script\n# This file is a template. During skill generation, {{SKILL_NAME}} is replaced\n# with the actual skill name and the result is shipped as install.sh inside\n# every generated skill package.\n#\n# POSIX-compatible (works in bash, dash, zsh, ash, etc.)\n# Exit codes:\n# 0 — Success\n# 1 — Validation failed (missing or malformed SKILL.md)\n# 2 — Platform not detected\n# 3 — Permission denied\n\nset -eu\n\n# ---------------------------------------------------------------------------\n# Constants\n# ---------------------------------------------------------------------------\nSKILL_NAME=\"{{SKILL_NAME}}\"\nVERSION=\"1.0.0\"\nSCRIPT_DIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\n\n# ---------------------------------------------------------------------------\n# Colors (disabled when stdout is not a terminal)\n# ---------------------------------------------------------------------------\nif [ -t 1 ]; then\n RED='\\033[0;31m'\n GREEN='\\033[0;32m'\n YELLOW='\\033[1;33m'\n BLUE='\\033[0;34m'\n BOLD='\\033[1m'\n NC='\\033[0m'\nelse\n RED=''\n GREEN=''\n YELLOW=''\n BLUE=''\n BOLD=''\n NC=''\nfi\n\n# ---------------------------------------------------------------------------\n# Logging helpers\n# ---------------------------------------------------------------------------\ninfo() { printf \"${BLUE}[INFO]${NC} %s\\n\" \"$1\"; }\nsuccess() { printf \"${GREEN}[OK]${NC} %s\\n\" \"$1\"; }\nwarn() { printf \"${YELLOW}[WARN]${NC} %s\\n\" \"$1\"; }\nerror() { printf \"${RED}[ERROR]${NC} %s\\n\" \"$1\" >&2; }\n\n# ---------------------------------------------------------------------------\n# Usage / help\n# ---------------------------------------------------------------------------\nshow_help() {\n cat \u003c\u003cEOF\n${BOLD}install.sh${NC} — Install the ${BOLD}${SKILL_NAME}${NC} skill (v${VERSION})\n\nUSAGE\n ./install.sh [OPTIONS]\n\nOPTIONS\n --platform PLATFORM Explicit platform selection. One of:\n claude-code, copilot, cursor, windsurf,\n cline, codex, gemini, kiro, trae, goose,\n opencode, roo-code, kilo-code, factory,\n junie, antigravity, universal\n --project Install at project level (current directory)\n --path PATH Custom install path (overrides detection)\n --all Install to ALL detected tool paths at once\n --dry-run Show what would happen without making changes\n -h, --help Show this help message\n\nEXAMPLES\n ./install.sh # Auto-detect platform, user-level\n ./install.sh --project # Auto-detect platform, project-level\n ./install.sh --platform cursor # Force Cursor, user-level\n ./install.sh --path ~/my-skills/ # Custom destination\n ./install.sh --all # Install to every detected tool\n ./install.sh --dry-run # Preview without installing\nEOF\n}\n\n# ---------------------------------------------------------------------------\n# Argument parsing\n# ---------------------------------------------------------------------------\nPLATFORM=\"\"\nPROJECT_LEVEL=false\nCUSTOM_PATH=\"\"\nDRY_RUN=false\nINSTALL_ALL=false\n\nparse_args() {\n while [ $# -gt 0 ]; do\n case \"$1\" in\n --platform)\n [ $# -ge 2 ] || { error \"Missing value for --platform\"; exit 1; }\n PLATFORM=\"$2\"\n shift 2\n ;;\n --project)\n PROJECT_LEVEL=true\n shift\n ;;\n --path)\n [ $# -ge 2 ] || { error \"Missing value for --path\"; exit 1; }\n CUSTOM_PATH=\"$2\"\n shift 2\n ;;\n --all)\n INSTALL_ALL=true\n shift\n ;;\n --dry-run)\n DRY_RUN=true\n shift\n ;;\n -h|--help)\n show_help\n exit 0\n ;;\n *)\n error \"Unknown option: $1\"\n show_help\n exit 1\n ;;\n esac\n done\n}\n\n# ---------------------------------------------------------------------------\n# SKILL.md validation\n# ---------------------------------------------------------------------------\nvalidate_skill_md() {\n skill_md=\"${SCRIPT_DIR}/SKILL.md\"\n\n if [ ! -f \"$skill_md\" ]; then\n error \"SKILL.md not found in ${SCRIPT_DIR}\"\n error \"Every skill package must contain a valid SKILL.md file.\"\n exit 1\n fi\n\n # Check that the file starts with YAML frontmatter delimiter\n first_line=\"$(head -n 1 \"$skill_md\")\"\n if [ \"$first_line\" != \"---\" ]; then\n error \"SKILL.md must start with YAML frontmatter (---)\"\n exit 1\n fi\n\n # Verify required frontmatter fields: name and description\n in_frontmatter=false\n found_name=false\n found_description=false\n line_num=0\n\n while IFS= read -r line; do\n line_num=$((line_num + 1))\n\n if [ \"$line_num\" -eq 1 ]; then\n in_frontmatter=true\n continue\n fi\n\n if $in_frontmatter && [ \"$line\" = \"---\" ]; then\n break\n fi\n\n if $in_frontmatter; then\n case \"$line\" in\n name:*) found_name=true ;;\n description:*) found_description=true ;;\n esac\n fi\n done \u003c \"$skill_md\"\n\n if ! $found_name; then\n error \"SKILL.md frontmatter is missing required field: name\"\n exit 1\n fi\n\n if ! $found_description; then\n error \"SKILL.md frontmatter is missing required field: description\"\n exit 1\n fi\n\n success \"SKILL.md validated (name and description present)\"\n}\n\n# ---------------------------------------------------------------------------\n# Platform detection\n# ---------------------------------------------------------------------------\nSUPPORTED_PLATFORMS=\"claude-code, copilot, cursor, windsurf, cline, codex, gemini, kiro, trae, goose, opencode, roo-code, kilo-code, factory, junie, antigravity, universal\"\n\ndetect_platform() {\n # If explicitly provided, validate and return it.\n if [ -n \"$PLATFORM\" ]; then\n case \"$PLATFORM\" in\n claude-code|copilot|cursor|windsurf|cline|codex|gemini|\\\n kiro|trae|goose|opencode|roo-code|kilo-code|factory|\\\n junie|antigravity|universal)\n info \"Platform explicitly set to: ${PLATFORM}\"\n return 0\n ;;\n *)\n error \"Unknown platform: ${PLATFORM}\"\n error \"Supported: ${SUPPORTED_PLATFORMS}\"\n exit 2\n ;;\n esac\n fi\n\n # Auto-detection: check user-level config directories.\n # Order matters — check most specific / least ambiguous first.\n if [ -d \"${HOME}/.claude\" ]; then\n PLATFORM=\"claude-code\"\n elif [ -d \"${HOME}/.copilot\" ] || [ -d \".github\" ]; then\n PLATFORM=\"copilot\"\n elif [ -d \"${HOME}/.cursor\" ] || [ -d \".cursor\" ]; then\n PLATFORM=\"cursor\"\n elif [ -d \"${HOME}/.codeium/windsurf\" ] || [ -d \".windsurf\" ]; then\n PLATFORM=\"windsurf\"\n elif [ -d \"${HOME}/.cline\" ] || [ -d \".clinerules\" ]; then\n PLATFORM=\"cline\"\n elif [ -d \"${HOME}/.gemini\" ]; then\n PLATFORM=\"gemini\"\n elif [ -d \"${HOME}/.kiro\" ] || [ -d \".kiro\" ]; then\n PLATFORM=\"kiro\"\n elif [ -d \".trae\" ]; then\n PLATFORM=\"trae\"\n elif [ -d \"${HOME}/.roo\" ] || [ -d \".roo\" ]; then\n PLATFORM=\"roo-code\"\n elif [ -d \"${HOME}/.kilocode\" ] || [ -d \".kilocode\" ]; then\n PLATFORM=\"kilo-code\"\n elif [ -d \"${HOME}/.factory\" ] || [ -d \".factory\" ]; then\n PLATFORM=\"factory\"\n elif [ -d \".junie\" ]; then\n PLATFORM=\"junie\"\n elif [ -d \"${HOME}/.config/goose\" ]; then\n PLATFORM=\"goose\"\n elif [ -d \"${HOME}/.config/opencode\" ]; then\n PLATFORM=\"opencode\"\n elif [ -d \"${HOME}/.agents\" ]; then\n PLATFORM=\"universal\"\n else\n error \"Could not auto-detect any supported AI coding platform.\"\n error \"Use --platform PLATFORM to specify one explicitly.\"\n error \"Supported: ${SUPPORTED_PLATFORMS}\"\n exit 2\n fi\n\n info \"Auto-detected platform: ${PLATFORM}\"\n}\n\n# ---------------------------------------------------------------------------\n# Detect all installed platforms (for --all)\n# ---------------------------------------------------------------------------\ndetect_all_platforms() {\n ALL_PLATFORMS=\"\"\n if [ -d \"${HOME}/.claude\" ]; then\n ALL_PLATFORMS=\"${ALL_PLATFORMS} claude-code\"\n fi\n if [ -d \"${HOME}/.copilot\" ] || [ -d \".github\" ]; then\n ALL_PLATFORMS=\"${ALL_PLATFORMS} copilot\"\n fi\n if [ -d \"${HOME}/.cursor\" ] || [ -d \".cursor\" ]; then\n ALL_PLATFORMS=\"${ALL_PLATFORMS} cursor\"\n fi\n if [ -d \"${HOME}/.codeium/windsurf\" ] || [ -d \".windsurf\" ]; then\n ALL_PLATFORMS=\"${ALL_PLATFORMS} windsurf\"\n fi\n if [ -d \"${HOME}/.cline\" ] || [ -d \".clinerules\" ]; then\n ALL_PLATFORMS=\"${ALL_PLATFORMS} cline\"\n fi\n if [ -d \"${HOME}/.gemini\" ]; then\n ALL_PLATFORMS=\"${ALL_PLATFORMS} gemini\"\n fi\n if [ -d \"${HOME}/.kiro\" ] || [ -d \".kiro\" ]; then\n ALL_PLATFORMS=\"${ALL_PLATFORMS} kiro\"\n fi\n if [ -d \".trae\" ]; then\n ALL_PLATFORMS=\"${ALL_PLATFORMS} trae\"\n fi\n if [ -d \"${HOME}/.roo\" ] || [ -d \".roo\" ]; then\n ALL_PLATFORMS=\"${ALL_PLATFORMS} roo-code\"\n fi\n if [ -d \"${HOME}/.kilocode\" ] || [ -d \".kilocode\" ]; then\n ALL_PLATFORMS=\"${ALL_PLATFORMS} kilo-code\"\n fi\n if [ -d \"${HOME}/.factory\" ] || [ -d \".factory\" ]; then\n ALL_PLATFORMS=\"${ALL_PLATFORMS} factory\"\n fi\n if [ -d \".junie\" ]; then\n ALL_PLATFORMS=\"${ALL_PLATFORMS} junie\"\n fi\n if [ -d \"${HOME}/.config/goose\" ]; then\n ALL_PLATFORMS=\"${ALL_PLATFORMS} goose\"\n fi\n if [ -d \"${HOME}/.config/opencode\" ]; then\n ALL_PLATFORMS=\"${ALL_PLATFORMS} opencode\"\n fi\n # Always include universal\n ALL_PLATFORMS=\"${ALL_PLATFORMS} universal\"\n\n # Trim leading space\n ALL_PLATFORMS=\"$(printf '%s' \"$ALL_PLATFORMS\" | sed 's/^ //')\"\n\n if [ -z \"$ALL_PLATFORMS\" ]; then\n ALL_PLATFORMS=\"universal\"\n fi\n}\n\n# ---------------------------------------------------------------------------\n# Install path resolution\n# ---------------------------------------------------------------------------\n# Sets INSTALL_DIR based on platform, project-level flag, or custom path.\nresolve_install_path() {\n # Custom path takes precedence over everything.\n if [ -n \"$CUSTOM_PATH\" ]; then\n INSTALL_DIR=\"${CUSTOM_PATH}\"\n info \"Using custom install path: ${INSTALL_DIR}\"\n return 0\n fi\n\n base=\"\"\n\n if $PROJECT_LEVEL; then\n # Project-level: paths are relative to the current working directory.\n case \"$PLATFORM\" in\n claude-code) base=\".claude/skills\" ;;\n copilot) base=\".github/skills\" ;;\n cursor) base=\".cursor/rules\" ;;\n windsurf) base=\".windsurf/rules\" ;;\n cline) base=\".clinerules/skills\" ;;\n codex) base=\".agents/skills\" ;;\n gemini) base=\".gemini/skills\" ;;\n kiro) base=\".kiro/skills\" ;;\n trae) base=\".trae/rules\" ;;\n goose) base=\".goose/skills\" ;;\n opencode) base=\".opencode/skills\" ;;\n roo-code) base=\".roo/skills\" ;;\n kilo-code) base=\".kilocode/skills\" ;;\n factory) base=\".factory/skills\" ;;\n junie) base=\".junie/skills\" ;;\n antigravity) base=\".agent/skills\" ;;\n universal) base=\".agents/skills\" ;;\n esac\n INSTALL_DIR=\"$(pwd)/${base}/${SKILL_NAME}\"\n else\n # User-level: paths are under the home directory.\n case \"$PLATFORM\" in\n claude-code) base=\"${HOME}/.claude/skills\" ;;\n copilot) base=\"${HOME}/.copilot/skills\" ;;\n cursor) base=\"${HOME}/.cursor/rules\" ;;\n windsurf) base=\"${HOME}/.codeium/windsurf/skills\" ;;\n cline) base=\"${HOME}/.cline/skills\" ;;\n codex) base=\"${HOME}/.agents/skills\" ;;\n gemini) base=\"${HOME}/.gemini/skills\" ;;\n kiro) base=\"${HOME}/.kiro/skills\" ;;\n trae) base=\"${HOME}/.trae/rules\" ;;\n goose) base=\"${HOME}/.config/goose/skills\" ;;\n opencode) base=\"${HOME}/.config/opencode/skills\" ;;\n roo-code) base=\"${HOME}/.roo/skills\" ;;\n kilo-code) base=\"${HOME}/.kilocode/skills\" ;;\n factory) base=\"${HOME}/.factory/skills\" ;;\n junie) base=\"${HOME}/.junie/skills\" ;;\n antigravity) base=\"${HOME}/.gemini/antigravity/skills\" ;;\n universal) base=\"${HOME}/.agents/skills\" ;;\n esac\n INSTALL_DIR=\"${base}/${SKILL_NAME}\"\n fi\n\n info \"Install directory: ${INSTALL_DIR}\"\n}\n\n# ---------------------------------------------------------------------------\n# Format adapters — convert SKILL.md to platform-native formats\n# ---------------------------------------------------------------------------\n\n# Generate a .mdc file for Cursor from SKILL.md\ngenerate_cursor_mdc() {\n target_dir=\"$1\"\n skill_md=\"${SCRIPT_DIR}/SKILL.md\"\n\n # Extract description from frontmatter\n desc=\"\"\n in_fm=false\n lnum=0\n while IFS= read -r line; do\n lnum=$((lnum + 1))\n if [ \"$lnum\" -eq 1 ]; then in_fm=true; continue; fi\n if $in_fm && [ \"$line\" = \"---\" ]; then break; fi\n if $in_fm; then\n case \"$line\" in\n description:*) desc=\"$(echo \"$line\" | sed 's/^description:[[:space:]]*//')\" ;;\n esac\n fi\n done \u003c \"$skill_md\"\n\n mdc_file=\"${target_dir}/${SKILL_NAME}.mdc\"\n\n if $DRY_RUN; then\n info \"Would generate Cursor .mdc: ${mdc_file}\"\n return 0\n fi\n\n # Extract body (everything after second ---)\n body=\"$(awk 'BEGIN{c=0} /^---$/{c++;next} c>=2{print}' \"$skill_md\")\"\n\n cat > \"$mdc_file\" \u003c\u003cMDCEOF\n---\ndescription: ${desc}\nglobs:\nalwaysApply: true\n---\n${body}\nMDCEOF\n success \"Generated Cursor .mdc: ${mdc_file}\"\n}\n\n# Generate a .md rule file for Windsurf (.windsurf/rules/ or global_rules.md)\ngenerate_windsurf_rule() {\n target_dir=\"$1\"\n is_global=\"$2\" # \"true\" or \"false\"\n skill_md=\"${SCRIPT_DIR}/SKILL.md\"\n\n # Extract body (everything after second ---)\n body=\"$(awk 'BEGIN{c=0} /^---$/{c++;next} c>=2{print}' \"$skill_md\")\"\n\n if [ \"$is_global\" = \"true\" ]; then\n # Append to global_rules.md with idempotent markers\n global_file=\"${HOME}/.codeium/windsurf/memories/global_rules.md\"\n\n if $DRY_RUN; then\n info \"Would append to Windsurf global_rules.md: ${global_file}\"\n return 0\n fi\n\n mkdir -p \"$(dirname \"$global_file\")\"\n\n # Remove existing block if present (idempotent, exact match)\n if [ -f \"$global_file\" ]; then\n awk -v begin_marker=\"\u003c!-- BEGIN ${SKILL_NAME} -->\" \\\n -v end_marker=\"\u003c!-- END ${SKILL_NAME} -->\" '\n BEGIN { skip=0 }\n $0 == begin_marker { skip=1; next }\n $0 == end_marker { skip=0; next }\n !skip { print }\n ' \"$global_file\" > \"${global_file}.tmp\"\n mv \"${global_file}.tmp\" \"$global_file\"\n fi\n\n # Append new block\n cat >> \"$global_file\" \u003c\u003cWSEOF\n\n\u003c!-- BEGIN ${SKILL_NAME} -->\n${body}\n\u003c!-- END ${SKILL_NAME} -->\nWSEOF\n success \"Appended to Windsurf global_rules.md\"\n else\n # Project-level: create a .md rule file\n rule_file=\"${target_dir}/${SKILL_NAME}.md\"\n\n if $DRY_RUN; then\n info \"Would generate Windsurf rule: ${rule_file}\"\n return 0\n fi\n\n mkdir -p \"$target_dir\"\n printf '%s\\n' \"$body\" > \"$rule_file\"\n success \"Generated Windsurf rule: ${rule_file}\"\n fi\n}\n\n# Generate plain markdown (strip YAML frontmatter) for Cline/Roo/Trae/Kilo\ngenerate_plain_rule() {\n target_dir=\"$1\"\n filename=\"$2\"\n skill_md=\"${SCRIPT_DIR}/SKILL.md\"\n\n plain_file=\"${target_dir}/${filename}\"\n\n if $DRY_RUN; then\n info \"Would generate plain rule: ${plain_file}\"\n return 0\n fi\n\n mkdir -p \"$target_dir\"\n awk 'BEGIN{c=0} /^---$/{c++;next} c>=2{print}' \"$skill_md\" > \"$plain_file\"\n success \"Generated plain rule: ${plain_file}\"\n}\n\n# Generate Junie guidelines.md (plain body, no frontmatter)\ngenerate_junie_guideline() {\n target_dir=\"$1\"\n skill_md=\"${SCRIPT_DIR}/SKILL.md\"\n\n guideline_file=\"${target_dir}/guidelines.md\"\n\n if $DRY_RUN; then\n info \"Would generate Junie guideline: ${guideline_file}\"\n return 0\n fi\n\n mkdir -p \"$target_dir\"\n awk 'BEGIN{c=0} /^---$/{c++;next} c>=2{print}' \"$skill_md\" > \"$guideline_file\"\n success \"Generated Junie guideline: ${guideline_file}\"\n}\n\n# ---------------------------------------------------------------------------\n# AGENTS.md companion — generate if not already present in source\n# ---------------------------------------------------------------------------\ngenerate_agents_md() {\n agents_md=\"${INSTALL_DIR}/AGENTS.md\"\n\n # If the skill already ships an AGENTS.md, skip generation\n if [ -f \"${SCRIPT_DIR}/AGENTS.md\" ]; then\n return 0\n fi\n\n if $DRY_RUN; then\n info \"Would generate companion AGENTS.md\"\n return 0\n fi\n\n # Extract description from SKILL.md frontmatter\n skill_md=\"${SCRIPT_DIR}/SKILL.md\"\n desc=\"\"\n in_fm=false\n lnum=0\n while IFS= read -r line; do\n lnum=$((lnum + 1))\n if [ \"$lnum\" -eq 1 ]; then in_fm=true; continue; fi\n if $in_fm && [ \"$line\" = \"---\" ]; then break; fi\n if $in_fm; then\n case \"$line\" in\n description:*) desc=\"$(echo \"$line\" | sed 's/^description:[[:space:]]*//')\" ;;\n esac\n fi\n done \u003c \"$skill_md\"\n\n cat > \"$agents_md\" \u003c\u003cAGENTSEOF\n# ${SKILL_NAME}\n\n${desc}\n\n## Usage\n\nInvoke this skill with \\`/${SKILL_NAME}\\` or by describing a task that matches its description.\n\n## Details\n\nSee [SKILL.md](./SKILL.md) for full implementation details, triggers, and configuration.\nAGENTSEOF\n success \"Generated companion AGENTS.md\"\n}\n\n# ---------------------------------------------------------------------------\n# Universal .agents/skills/ secondary install (symlink or copy)\n# ---------------------------------------------------------------------------\ninstall_universal_secondary() {\n # Skip if primary target is already .agents/\n case \"$PLATFORM\" in\n codex|universal) return 0 ;;\n esac\n\n universal_dir=\"${HOME}/.agents/skills/${SKILL_NAME}\"\n\n if $DRY_RUN; then\n info \"Would create universal symlink: ${universal_dir} -> ${INSTALL_DIR}\"\n return 0\n fi\n\n mkdir -p \"${HOME}/.agents/skills\"\n\n # Remove existing entry if present\n if [ -e \"$universal_dir\" ] || [ -L \"$universal_dir\" ]; then\n rm -rf \"$universal_dir\"\n fi\n\n # Try symlink first, fallback to copy\n if ln -s \"$INSTALL_DIR\" \"$universal_dir\" 2>/dev/null; then\n success \"Universal symlink: ${universal_dir} -> ${INSTALL_DIR}\"\n elif cp -R \"$INSTALL_DIR\" \"$universal_dir\" 2>/dev/null; then\n success \"Universal copy: ${universal_dir}\"\n else\n warn \"Could not create universal path at ${universal_dir}\"\n fi\n}\n\n# ---------------------------------------------------------------------------\n# File installation\n# ---------------------------------------------------------------------------\ninstall_files() {\n # Collect the list of files to install.\n # We copy everything in SCRIPT_DIR except the install script itself.\n file_count=0\n install_script_name=\"$(basename \"$0\")\"\n\n if $DRY_RUN; then\n printf \"\\n${BOLD}Dry-run mode — no files will be copied.${NC}\\n\\n\"\n info \"Would create directory: ${INSTALL_DIR}\"\n for file in \"${SCRIPT_DIR}\"/*; do\n [ -e \"$file\" ] || continue\n fname=\"$(basename \"$file\")\"\n # Skip the install script itself\n [ \"$fname\" = \"$install_script_name\" ] && continue\n info \"Would copy: ${fname}\"\n file_count=$((file_count + 1))\n done\n # Also handle dotfiles\n for file in \"${SCRIPT_DIR}\"/.*; do\n [ -e \"$file\" ] || continue\n fname=\"$(basename \"$file\")\"\n if [ \"$fname\" = \".\" ] || [ \"$fname\" = \"..\" ]; then continue; fi\n info \"Would copy: ${fname}\"\n file_count=$((file_count + 1))\n done\n printf \"\\n\"\n info \"Total files: ${file_count}\"\n return 0\n fi\n\n # Clean existing install for idempotency (remove stale files from prior installs).\n if [ -d \"$INSTALL_DIR\" ]; then\n rm -rf \"$INSTALL_DIR\"\n fi\n\n # Create destination directory.\n if ! mkdir -p \"$INSTALL_DIR\" 2>/dev/null; then\n error \"Cannot create directory: ${INSTALL_DIR}\"\n error \"Check file permissions or run with appropriate privileges.\"\n exit 3\n fi\n\n # Copy files.\n for file in \"${SCRIPT_DIR}\"/*; do\n [ -e \"$file\" ] || continue\n fname=\"$(basename \"$file\")\"\n [ \"$fname\" = \"$install_script_name\" ] && continue\n\n if ! cp -R \"$file\" \"${INSTALL_DIR}/\" 2>/dev/null; then\n error \"Failed to copy ${fname} to ${INSTALL_DIR}/\"\n error \"Check file permissions.\"\n exit 3\n fi\n file_count=$((file_count + 1))\n done\n\n # Copy dotfiles (if any).\n for file in \"${SCRIPT_DIR}\"/.*; do\n [ -e \"$file\" ] || continue\n fname=\"$(basename \"$file\")\"\n [ \"$fname\" = \".\" ] || [ \"$fname\" = \"..\" ] && continue\n\n if ! cp -R \"$file\" \"${INSTALL_DIR}/\" 2>/dev/null; then\n error \"Failed to copy ${fname} to ${INSTALL_DIR}/\"\n error \"Check file permissions.\"\n exit 3\n fi\n file_count=$((file_count + 1))\n done\n\n success \"Copied ${file_count} file(s) to ${INSTALL_DIR}\"\n}\n\n# ---------------------------------------------------------------------------\n# Run format adapters based on platform\n# ---------------------------------------------------------------------------\nrun_adapters() {\n case \"$PLATFORM\" in\n cursor)\n generate_cursor_mdc \"$INSTALL_DIR\"\n ;;\n windsurf)\n if $PROJECT_LEVEL; then\n generate_windsurf_rule \"$(pwd)/.windsurf/rules\" \"false\"\n else\n generate_windsurf_rule \"\" \"true\"\n fi\n ;;\n cline)\n generate_plain_rule \"$INSTALL_DIR\" \"${SKILL_NAME}.md\"\n ;;\n roo-code)\n generate_plain_rule \"$INSTALL_DIR\" \"${SKILL_NAME}.md\"\n ;;\n kilo-code)\n generate_plain_rule \"$INSTALL_DIR\" \"${SKILL_NAME}.md\"\n ;;\n trae)\n generate_plain_rule \"$INSTALL_DIR\" \"${SKILL_NAME}.md\"\n ;;\n junie)\n generate_junie_guideline \"$INSTALL_DIR\"\n ;;\n esac\n}\n\n# ---------------------------------------------------------------------------\n# Activation instructions\n# ---------------------------------------------------------------------------\nprint_activation_instructions() {\n if $DRY_RUN; then\n return 0\n fi\n\n printf \"\\n${GREEN}${BOLD}Installation complete!${NC}\\n\\n\"\n\n case \"$PLATFORM\" in\n claude-code)\n printf \"To activate the skill in Claude Code:\\n\"\n printf \" 1. Start a new Claude Code session.\\n\"\n printf \" 2. The skill will be loaded automatically from:\\n\"\n printf \" ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\\n\"\n printf \" 3. Invoke with /${SKILL_NAME} or use trigger phrases.\\n\"\n ;;\n copilot)\n printf \"To activate the skill in GitHub Copilot:\\n\"\n printf \" 1. Open your project in VS Code or the GitHub CLI.\\n\"\n printf \" 2. The skill is available at:\\n\"\n printf \" ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\\n\"\n printf \" 3. Invoke with /${SKILL_NAME} or reference in instructions.\\n\"\n ;;\n cursor)\n printf \"To activate the skill in Cursor:\\n\"\n printf \" 1. Open your project in Cursor.\\n\"\n printf \" 2. The rule is loaded automatically from:\\n\"\n printf \" ${BOLD}${INSTALL_DIR}/${SKILL_NAME}.mdc${NC}\\n\"\n printf \" 3. Use trigger phrases or slash menu to invoke.\\n\"\n ;;\n windsurf)\n printf \"To activate the skill in Windsurf:\\n\"\n if $PROJECT_LEVEL; then\n printf \" 1. Open your project in Windsurf.\\n\"\n printf \" 2. The rule is loaded from .windsurf/rules/\\n\"\n else\n printf \" 1. Open Windsurf.\\n\"\n printf \" 2. The skill was added to global_rules.md.\\n\"\n fi\n printf \" 3. Use trigger phrases to invoke the skill.\\n\"\n ;;\n cline)\n printf \"To activate the skill in Cline:\\n\"\n printf \" 1. Open your project in VS Code with Cline.\\n\"\n printf \" 2. The rule is loaded from:\\n\"\n printf \" ${BOLD}${INSTALL_DIR}/${SKILL_NAME}.md${NC}\\n\"\n printf \" 3. Cline will pick up the rule automatically.\\n\"\n ;;\n codex)\n printf \"To activate the skill in OpenAI Codex CLI:\\n\"\n printf \" 1. Start a new Codex CLI session.\\n\"\n printf \" 2. The skill is available at:\\n\"\n printf \" ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\\n\"\n printf \" 3. Invoke with /skills or \\${SKILL_NAME}.\\n\"\n ;;\n gemini)\n printf \"To activate the skill in Gemini CLI:\\n\"\n printf \" 1. Start a new Gemini CLI session.\\n\"\n printf \" 2. The skill is available at:\\n\"\n printf \" ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\\n\"\n printf \" 3. The skill will be loaded automatically.\\n\"\n ;;\n kiro)\n printf \"To activate the skill in Kiro:\\n\"\n printf \" 1. Open your project in Kiro.\\n\"\n printf \" 2. The skill is available at:\\n\"\n printf \" ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\\n\"\n printf \" 3. Invoke with / or use trigger phrases.\\n\"\n ;;\n trae)\n printf \"To activate the skill in Trae:\\n\"\n printf \" 1. Open your project in Trae.\\n\"\n printf \" 2. The rule is loaded from:\\n\"\n printf \" ${BOLD}${INSTALL_DIR}/${SKILL_NAME}.md${NC}\\n\"\n printf \" 3. Use trigger phrases or Intelligent mode.\\n\"\n ;;\n goose)\n printf \"To activate the skill in Goose:\\n\"\n printf \" 1. Start a new Goose session.\\n\"\n printf \" 2. The skill is available at:\\n\"\n printf \" ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\\n\"\n printf \" 3. Say 'Use the ${SKILL_NAME} skill' or use trigger phrases.\\n\"\n ;;\n opencode)\n printf \"To activate the skill in OpenCode:\\n\"\n printf \" 1. Start a new OpenCode session.\\n\"\n printf \" 2. The skill is available at:\\n\"\n printf \" ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\\n\"\n printf \" 3. OpenCode reads from ~/.config/opencode/skills/ automatically.\\n\"\n ;;\n roo-code)\n printf \"To activate the skill in Roo Code:\\n\"\n printf \" 1. Open your project in VS Code with Roo Code.\\n\"\n printf \" 2. The rule is loaded from:\\n\"\n printf \" ${BOLD}${INSTALL_DIR}/${SKILL_NAME}.md${NC}\\n\"\n printf \" 3. Use /orchestrator, /code, or trigger phrases.\\n\"\n ;;\n kilo-code)\n printf \"To activate the skill in Kilo Code:\\n\"\n printf \" 1. Open your project in VS Code/JetBrains with Kilo Code.\\n\"\n printf \" 2. The rule is loaded from:\\n\"\n printf \" ${BOLD}${INSTALL_DIR}/${SKILL_NAME}.md${NC}\\n\"\n printf \" 3. Kilo Code will pick up the rule automatically.\\n\"\n ;;\n factory)\n printf \"To activate the skill in Factory Droid:\\n\"\n printf \" 1. Start a new Factory Droid session.\\n\"\n printf \" 2. The skill is available at:\\n\"\n printf \" ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\\n\"\n printf \" 3. Invoke with /${SKILL_NAME} or /droids menu.\\n\"\n ;;\n junie)\n printf \"To activate the skill in Junie:\\n\"\n printf \" 1. Open your project in JetBrains with Junie.\\n\"\n printf \" 2. The guideline is loaded from:\\n\"\n printf \" ${BOLD}${INSTALL_DIR}/guidelines.md${NC}\\n\"\n printf \" 3. Junie loads guidelines automatically.\\n\"\n ;;\n antigravity)\n printf \"To activate the skill in Antigravity:\\n\"\n printf \" 1. Open your project.\\n\"\n printf \" 2. The skill is available at:\\n\"\n printf \" ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\\n\"\n printf \" 3. Antigravity reads from .agent/skills/ automatically.\\n\"\n printf \" Note: .agent/ (singular), NOT .agents/ (plural).\\n\"\n ;;\n universal)\n printf \"The skill is installed at the universal path:\\n\"\n printf \" ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\\n\\n\"\n printf \"Tools that read ~/.agents/skills/ (Codex CLI, Gemini CLI,\\n\"\n printf \"OpenCode, Goose, Cline, Roo Code, Kilo Code) will discover it.\\n\"\n ;;\n esac\n\n printf \"\\n\"\n}\n\n# ---------------------------------------------------------------------------\n# Install for a single platform\n# ---------------------------------------------------------------------------\ninstall_single() {\n detect_platform\n resolve_install_path\n install_files\n run_adapters\n generate_agents_md\n install_universal_secondary\n print_activation_instructions\n\n if $DRY_RUN; then\n info \"Dry run complete. No changes were made.\"\n else\n success \"Skill '${SKILL_NAME}' installed successfully for ${PLATFORM}.\"\n fi\n}\n\n# ---------------------------------------------------------------------------\n# Install for all detected platforms (--all)\n# ---------------------------------------------------------------------------\ninstall_all() {\n detect_all_platforms\n info \"Installing to all detected platforms: ${ALL_PLATFORMS}\"\n printf \"%-40s\\n\" \"----------------------------------------\"\n\n installed_count=0\n first_non_agents_dir=\"\"\n for plat in $ALL_PLATFORMS; do\n printf \"\\n\"\n info \"--- Installing for: ${plat} ---\"\n PLATFORM=\"$plat\"\n resolve_install_path\n install_files\n run_adapters\n generate_agents_md\n installed_count=$((installed_count + 1))\n # Remember the first non-.agents/ install dir for universal symlink\n if [ -z \"$first_non_agents_dir\" ]; then\n case \"$plat\" in\n codex|universal) ;;\n *) first_non_agents_dir=\"$INSTALL_DIR\" ;;\n esac\n fi\n done\n\n # Create universal symlink from the first non-.agents/ install\n if [ -n \"$first_non_agents_dir\" ]; then\n INSTALL_DIR=\"$first_non_agents_dir\"\n install_universal_secondary\n fi\n\n printf \"\\n\"\n if $DRY_RUN; then\n info \"Dry run complete. No changes were made.\"\n else\n success \"Skill '${SKILL_NAME}' installed to ${installed_count} platform(s).\"\n fi\n}\n\n# ---------------------------------------------------------------------------\n# Main\n# ---------------------------------------------------------------------------\nmain() {\n printf \"${BOLD}Installing skill: ${SKILL_NAME}${NC}\\n\"\n printf \"%-40s\\n\" \"----------------------------------------\"\n\n parse_args \"$@\"\n validate_skill_md\n\n if $INSTALL_ALL; then\n install_all\n else\n install_single\n fi\n\n exit 0\n}\n\nmain \"$@\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":32124,"content_sha256":"fe804a3fb8fa4fecc0740c433294a1c88e6eadf81f7f7b4dfe84fecf728e59b2"},{"filename":"scripts/platforms.py","content":"#!/usr/bin/env python3\n\"\"\"\nCanonical registry of agent-skills install targets.\n\nOne source of truth for platform name -> install paths and detection markers.\nConsumed by `scripts/skill_registry.py`. The shell installers\n(`scripts/install-template.sh`, `scripts/bootstrap.sh`) hand-maintain their own\ntables for now -- they ship into generated skills and cannot import Python at\ninstall time. A drift test in `scripts/tests/test_platforms.py` flags when the\nshell tables fall out of sync with this file; a future consolidation can\ngenerate the shell tables from this source.\n\nSource of truth for the paths is `scripts/install-template.sh` Step\n\"INSTALL_DIR resolution\" (the project/user case arms), since that is the script\nthat real users run.\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom dataclasses import dataclass\n\n\n@dataclass(frozen=True)\nclass Platform:\n \"\"\"An install target for a generated skill.\n\n user_path / project_path are install destinations (where the skill directory\n lands). detect_dir is the marker whose presence indicates the platform is\n installed on the user's machine (used for auto-detection); may equal the\n parent of user_path, or differ (e.g. Windsurf installs to\n ~/.codeium/windsurf/skills but is detected by ~/.codeium/windsurf).\n \"\"\"\n\n name: str\n user_path: str\n project_path: str\n detect_dir: str = \"\"\n\n\nPLATFORMS: tuple[Platform, ...] = (\n Platform(\"claude-code\", \"~/.claude/skills\", \".claude/skills\", \"~/.claude\"),\n Platform(\"copilot\", \"~/.copilot/skills\", \".github/skills\", \"~/.copilot\"),\n Platform(\"cursor\", \"~/.cursor/rules\", \".cursor/rules\", \"~/.cursor\"),\n Platform(\"windsurf\", \"~/.codeium/windsurf/skills\", \".windsurf/rules\", \"~/.codeium/windsurf\"),\n Platform(\"cline\", \"~/.cline/skills\", \".clinerules/skills\", \"~/.cline\"),\n Platform(\"codex\", \"~/.agents/skills\", \".agents/skills\", \"~/.agents\"),\n Platform(\"gemini\", \"~/.gemini/skills\", \".gemini/skills\", \"~/.gemini\"),\n Platform(\"kiro\", \"~/.kiro/skills\", \".kiro/skills\", \"~/.kiro\"),\n Platform(\"trae\", \"~/.trae/rules\", \".trae/rules\", \"~/.trae\"),\n Platform(\"goose\", \"~/.config/goose/skills\", \".goose/skills\", \"~/.config/goose\"),\n Platform(\"opencode\", \"~/.config/opencode/skills\", \".opencode/skills\", \"~/.config/opencode\"),\n Platform(\"roo-code\", \"~/.roo/skills\", \".roo/skills\", \"~/.roo\"),\n Platform(\"kilo-code\", \"~/.kilocode/skills\", \".kilocode/skills\", \"~/.kilocode\"),\n Platform(\"factory\", \"~/.factory/skills\", \".factory/skills\", \"~/.factory\"),\n Platform(\"junie\", \"~/.junie/skills\", \".junie/skills\", \"~/.junie\"),\n Platform(\"antigravity\", \"~/.gemini/antigravity/skills\", \".agent/skills\", \"~/.gemini/antigravity\"),\n Platform(\"universal\", \"~/.agents/skills\", \".agents/skills\", \"~/.agents\"),\n)\n\n\n_BY_NAME: dict[str, Platform] = {p.name: p for p in PLATFORMS}\n\n\ndef list_supported_platforms() -> list[str]:\n \"\"\"Names of every supported install target, in canonical order.\"\"\"\n return [p.name for p in PLATFORMS]\n\n\ndef get_platform(name: str) -> Platform | None:\n \"\"\"Look up a platform by canonical name, or None if unknown.\"\"\"\n return _BY_NAME.get(name)\n\n\ndef user_paths() -> dict[str, str]:\n \"\"\"name -> user-level install path.\"\"\"\n return {p.name: p.user_path for p in PLATFORMS}\n\n\ndef project_paths() -> dict[str, str]:\n \"\"\"name -> project-level install path.\"\"\"\n return {p.name: p.project_path for p in PLATFORMS}\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3405,"content_sha256":"10575a32beb1b3d264ae2d6b593cefe50d58398a94bc232a6ccd26dda4f3aa64"},{"filename":"scripts/review_staleness.py","content":"#!/usr/bin/env python3\n\"\"\"\nReview staleness check for a skill.\n\nCompares `metadata.last_reviewed` against `metadata.review_interval_days`. Falls\nback to the git commit date for SKILL.md when explicit review dates are absent.\nNo network. Pure date math + a single git subprocess call.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport re\nimport subprocess\nfrom datetime import date, timedelta\nfrom pathlib import Path\n\nfrom skill_document import SkillDoc\n\nDEFAULT_REVIEW_INTERVAL_DAYS = 90\nSTALENESS_WARNING_THRESHOLD_DAYS = 60\n\n_DATE_RE = re.compile(r\"^\\d{4}-\\d{2}-\\d{2}$\")\n\n\ndef _parse_date(value: str) -> date | None:\n \"\"\"Parse a YYYY-MM-DD string into a date object, or None on failure.\"\"\"\n if not value or not _DATE_RE.match(value.strip()):\n return None\n try:\n y, m, d = value.strip().split(\"-\")\n return date(int(y), int(m), int(d))\n except (ValueError, IndexError):\n return None\n\n\ndef get_git_last_modified(skill_path: str) -> date | None:\n \"\"\"Return the date of the last git commit touching SKILL.md, or None if\n git is unavailable, the file is untracked, or the call times out.\"\"\"\n skill_md = Path(skill_path).resolve() / \"SKILL.md\"\n if not skill_md.exists():\n return None\n try:\n result = subprocess.run(\n [\"git\", \"log\", \"-1\", \"--format=%aI\", \"--\", str(skill_md)],\n capture_output=True,\n text=True,\n timeout=10,\n cwd=str(Path(skill_path).resolve()),\n )\n if result.returncode != 0 or not result.stdout.strip():\n return None\n return _parse_date(result.stdout.strip()[:10]) # ISO \"YYYY-MM-DDTHH:...\" -> \"YYYY-MM-DD\"\n except (subprocess.TimeoutExpired, FileNotFoundError, OSError):\n return None\n\n\ndef check_review_staleness(\n doc: SkillDoc,\n git_last_modified: date | None,\n today: date | None = None,\n) -> tuple[list[dict], str, int | None, str]:\n \"\"\"\n Decide whether the skill is overdue for review.\n\n `today` is injectable for tests. Returns (issues, review_status,\n days_since_review, date_source). review_status is one of \"fresh\",\n \"due_soon\", \"overdue\", or \"unknown\".\n \"\"\"\n if today is None:\n today = date.today()\n issues: list[dict] = []\n\n created_str = doc.subfield(\"metadata\", \"created\")\n last_reviewed_str = doc.subfield(\"metadata\", \"last_reviewed\")\n interval_str = doc.subfield(\"metadata\", \"review_interval_days\")\n\n # Validate formats (warnings only)\n if created_str and not _parse_date(created_str):\n issues.append({\n \"level\": \"warning\",\n \"message\": \"Invalid 'metadata.created' date format\",\n \"detail\": f\"Expected YYYY-MM-DD, got: '{created_str}'\",\n })\n if last_reviewed_str and not _parse_date(last_reviewed_str):\n issues.append({\n \"level\": \"warning\",\n \"message\": \"Invalid 'metadata.last_reviewed' date format\",\n \"detail\": f\"Expected YYYY-MM-DD, got: '{last_reviewed_str}'\",\n })\n if interval_str:\n try:\n int(interval_str)\n except ValueError:\n issues.append({\n \"level\": \"warning\",\n \"message\": \"Invalid 'metadata.review_interval_days' value\",\n \"detail\": f\"Expected integer, got: '{interval_str}'\",\n })\n\n interval_days = DEFAULT_REVIEW_INTERVAL_DAYS\n if interval_str:\n try:\n interval_days = int(interval_str)\n except ValueError:\n pass\n\n reference_date: date | None = None\n date_source = \"unknown\"\n last_reviewed = _parse_date(last_reviewed_str) if last_reviewed_str else None\n if last_reviewed:\n reference_date = last_reviewed\n date_source = \"last_reviewed\"\n elif git_last_modified:\n reference_date = git_last_modified\n date_source = \"git_commit\"\n else:\n issues.append({\n \"level\": \"info\",\n \"message\": \"No review date available\",\n \"detail\": \"No 'metadata.last_reviewed' and no git history found. \"\n \"Consider adding temporal metadata.\",\n })\n\n days_since: int | None = None\n review_status = \"unknown\"\n if reference_date:\n days_since = (today - reference_date).days\n deadline = reference_date + timedelta(days=interval_days)\n warning_date = reference_date + timedelta(days=STALENESS_WARNING_THRESHOLD_DAYS)\n\n if today > deadline:\n review_status = \"overdue\"\n issues.append({\n \"level\": \"error\",\n \"message\": f\"Skill is overdue for review ({days_since} days since last review)\",\n \"detail\": f\"Review interval is {interval_days} days. \"\n f\"Last review: {reference_date} (source: {date_source}). \"\n f\"Deadline was: {deadline}.\",\n })\n elif today > warning_date:\n review_status = \"due_soon\"\n days_remaining = (deadline - today).days\n issues.append({\n \"level\": \"warning\",\n \"message\": f\"Review due in {days_remaining} days\",\n \"detail\": f\"Last review: {reference_date} (source: {date_source}). \"\n f\"Deadline: {deadline}.\",\n })\n else:\n review_status = \"fresh\"\n\n has_any_temporal = bool(created_str or last_reviewed_str or interval_str)\n if not has_any_temporal:\n issues.append({\n \"level\": \"info\",\n \"message\": \"No temporal metadata found\",\n \"detail\": \"Consider adding metadata.created, metadata.last_reviewed, \"\n \"and metadata.review_interval_days to frontmatter.\",\n })\n\n return issues, review_status, days_since, date_source\n","content_type":"text/x-python; charset=utf-8","language":"python","size":5771,"content_sha256":"b69dbe2116a4ea5a5bfab87f73a1989b6a1dbd82e6b40f384495a871daf2a8b1"},{"filename":"scripts/run_evals_template.py","content":"#!/usr/bin/env python3\n\"\"\"\nEval runner shipped inside every generated skill as scripts/run_evals.py.\n\nA generated skill carries its own loss function in evals/\u003cskill>.eval.md: a set\nof binary checks (each graded by a shell command or flagged for an LLM judge)\nplus a handful of golden cases. This runner turns that spec into a deterministic\nregression gate and a shape validator. It executes only the `command` checks; it\ndoes NOT run the skill itself (no rollout harness) and does NOT grade `llm-judge`\nchecks — those are printed as a checklist for an agent or autoresearch-universal.\n\nModes:\n python3 scripts/run_evals.py # run command checks against the\n # golden baseline; non-zero exit\n # if any fail\n python3 scripts/run_evals.py --validate # check the spec is well-formed\n python3 scripts/run_evals.py --output OUT [--case ID]\n # score a real produced output\n python3 scripts/run_evals.py --json # machine-readable result\n\nExit codes:\n 0 - all run command checks passed (or --validate found no errors)\n 1 - a command check failed, or the spec is malformed\n 2 - no eval spec found\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport re\nimport shlex\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nVALID_TYPES = (\"command\", \"llm-judge\")\nMIN_GOLDEN_CASES = 3\nOUTPUT_PLACEHOLDER = \"{output}\"\n\n_JSON_BLOCK = re.compile(r\"```json\\s*\\n(.*?)\\n```\", re.DOTALL)\n\n\ndef find_spec(skill_dir: Path) -> Path | None:\n \"\"\"Return the first evals/*.eval.md under skill_dir, or None if absent.\"\"\"\n evals_dir = skill_dir / \"evals\"\n if not evals_dir.is_dir():\n return None\n specs = sorted(evals_dir.glob(\"*.eval.md\"))\n return specs[0] if specs else None\n\n\ndef parse_spec(spec_path: Path) -> dict:\n \"\"\"Extract and parse the first fenced ```json block from an eval spec.\n\n Raises:\n ValueError: if no JSON block is present or it does not parse.\n \"\"\"\n text = spec_path.read_text(encoding=\"utf-8\")\n match = _JSON_BLOCK.search(text)\n if not match:\n raise ValueError(f\"{spec_path}: no ```json block found\")\n try:\n return json.loads(match.group(1))\n except json.JSONDecodeError as exc:\n raise ValueError(f\"{spec_path}: malformed JSON block: {exc}\") from exc\n\n\ndef validate_spec(spec: dict, skill_dir: Path) -> list[str]:\n \"\"\"Return a list of shape errors for the spec (empty list means valid).\"\"\"\n errors: list[str] = []\n\n if not spec.get(\"skill\"):\n errors.append(\"missing 'skill' name\")\n\n criteria = spec.get(\"criteria\")\n if not isinstance(criteria, list) or not criteria:\n errors.append(\"'criteria' must be a non-empty list\")\n criteria = []\n for i, crit in enumerate(criteria):\n where = f\"criteria[{i}]\"\n if not crit.get(\"id\"):\n errors.append(f\"{where}: missing 'id'\")\n if not crit.get(\"text\"):\n errors.append(f\"{where}: missing 'text'\")\n ctype = crit.get(\"type\")\n if ctype not in VALID_TYPES:\n errors.append(f\"{where}: 'type' must be one of {VALID_TYPES}, got {ctype!r}\")\n if ctype == \"command\" and not crit.get(\"cmd\"):\n errors.append(f\"{where}: command criterion needs a non-empty 'cmd'\")\n\n golden = spec.get(\"golden\")\n if not isinstance(golden, list):\n errors.append(\"'golden' must be a list\")\n golden = []\n if len(golden) \u003c MIN_GOLDEN_CASES:\n errors.append(f\"need at least {MIN_GOLDEN_CASES} golden cases, found {len(golden)}\")\n for i, case in enumerate(golden):\n where = f\"golden[{i}]\"\n if not case.get(\"id\"):\n errors.append(f\"{where}: missing 'id'\")\n inp = case.get(\"input\")\n if not inp:\n errors.append(f\"{where}: missing 'input'\")\n elif not (skill_dir / \"evals\" / inp).exists():\n errors.append(f\"{where}: input file not found: evals/{inp}\")\n expected = case.get(\"expected\")\n if expected is not None and not (skill_dir / \"evals\" / expected).exists():\n errors.append(f\"{where}: expected file not found: evals/{expected}\")\n if expected is None and case.get(\"expected_status\") != \"pending-first-green\":\n errors.append(\n f\"{where}: null 'expected' must be marked expected_status='pending-first-green'\"\n )\n\n return errors\n\n\ndef _run_one(cmd: str, output_path: Path | None) -> bool:\n \"\"\"Run a single command check once. {output} is bound to output_path.\n\n Returns True on exit code 0. Retries once on failure (matches autoresearch\n command-eval semantics).\n \"\"\"\n if OUTPUT_PLACEHOLDER in cmd:\n if output_path is None:\n return False\n bound = cmd.replace(OUTPUT_PLACEHOLDER, shlex.quote(str(output_path)))\n else:\n bound = cmd\n for _ in range(2):\n proc = subprocess.run(bound, shell=True, capture_output=True) # noqa: S602\n if proc.returncode == 0:\n return True\n return False\n\n\ndef run_command_checks(\n spec: dict,\n skill_dir: Path,\n output: Path | None = None,\n only_case: str | None = None,\n) -> dict:\n \"\"\"Run every command criterion against each applicable golden case.\n\n By default {output} binds to each case's `expected` baseline file. When\n `output` is given it binds to that path instead (scoring a real run); use\n `only_case` to restrict scoring to one case.\n\n Returns a result dict with passed/failed counts and per-check detail.\n \"\"\"\n evals_dir = skill_dir / \"evals\"\n command_criteria = [c for c in spec.get(\"criteria\", []) if c.get(\"type\") == \"command\"]\n results: list[dict] = []\n passed = failed = skipped = 0\n\n for case in spec.get(\"golden\", []):\n case_id = case.get(\"id\", \"?\")\n if only_case and case_id != only_case:\n continue\n if output is not None:\n bound_output: Path | None = output\n elif case.get(\"expected\"):\n bound_output = evals_dir / case[\"expected\"]\n else:\n bound_output = None # pending-first-green: no baseline yet\n\n for crit in command_criteria:\n needs_output = OUTPUT_PLACEHOLDER in crit[\"cmd\"]\n if needs_output and bound_output is None:\n skipped += 1\n results.append({\"case\": case_id, \"criterion\": crit[\"id\"], \"status\": \"skipped\"})\n continue\n ok = _run_one(crit[\"cmd\"], bound_output)\n passed += ok\n failed += not ok\n results.append(\n {\"case\": case_id, \"criterion\": crit[\"id\"], \"status\": \"pass\" if ok else \"fail\"}\n )\n\n return {\"passed\": passed, \"failed\": failed, \"skipped\": skipped, \"checks\": results}\n\n\ndef llm_judge_criteria(spec: dict) -> list[dict]:\n \"\"\"Return the criteria that require an LLM judge (not run by this script).\"\"\"\n return [c for c in spec.get(\"criteria\", []) if c.get(\"type\") == \"llm-judge\"]\n\n\ndef _default_skill_dir() -> Path:\n \"\"\"The skill root is the parent of the scripts/ directory holding this file.\"\"\"\n return Path(__file__).resolve().parent.parent\n\n\ndef main(argv: list[str] | None = None) -> int:\n parser = argparse.ArgumentParser(description=\"Run a skill's bundled eval spec.\")\n parser.add_argument(\n \"skill_dir\",\n nargs=\"?\",\n default=None,\n help=\"Skill root (default: parent of this script's directory).\",\n )\n parser.add_argument(\"--validate\", action=\"store_true\", help=\"Only check the spec is well-formed.\")\n parser.add_argument(\"--output\", default=None, help=\"Produced output to score against (binds {output}).\")\n parser.add_argument(\"--case\", default=None, help=\"Restrict scoring to this golden case id.\")\n parser.add_argument(\"--json\", action=\"store_true\", help=\"Emit machine-readable JSON.\")\n args = parser.parse_args(argv)\n\n skill_dir = Path(args.skill_dir).resolve() if args.skill_dir else _default_skill_dir()\n\n spec_path = find_spec(skill_dir)\n if spec_path is None:\n msg = f\"no evals/*.eval.md found under {skill_dir}\"\n print(json.dumps({\"error\": msg}) if args.json else f\"ERROR: {msg}\", file=sys.stderr)\n return 2\n\n try:\n spec = parse_spec(spec_path)\n except ValueError as exc:\n print(json.dumps({\"error\": str(exc)}) if args.json else f\"ERROR: {exc}\", file=sys.stderr)\n return 1\n\n errors = validate_spec(spec, skill_dir)\n if args.validate:\n if args.json:\n print(json.dumps({\"valid\": not errors, \"errors\": errors}, indent=2))\n elif errors:\n print(f\"INVALID {spec_path.name}:\")\n for err in errors:\n print(f\" - {err}\")\n else:\n print(f\"VALID {spec_path.name}\")\n return 1 if errors else 0\n\n if errors:\n # A malformed spec cannot be run honestly.\n head = f\"ERROR: {spec_path.name} is malformed; run --validate\"\n print(json.dumps({\"error\": head, \"errors\": errors}) if args.json else head, file=sys.stderr)\n return 1\n\n output = Path(args.output).resolve() if args.output else None\n result = run_command_checks(spec, skill_dir, output=output, only_case=args.case)\n judges = llm_judge_criteria(spec)\n\n if args.json:\n print(json.dumps({**result, \"llm_judge\": [c[\"id\"] for c in judges]}, indent=2))\n else:\n for check in result[\"checks\"]:\n print(f\" [{check['status']:>7}] {check['case']} :: {check['criterion']}\")\n print(\n f\"\\ncommand checks: {result['passed']} passed, \"\n f\"{result['failed']} failed, {result['skipped']} skipped\"\n )\n if judges:\n print(\"\\nllm-judge checks (evaluate manually or via /autoresearch-universal):\")\n for crit in judges:\n print(f\" - {crit['id']}: {crit['text']}\")\n\n return 1 if result[\"failed\"] else 0\n\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n","content_type":"text/x-python; charset=utf-8","language":"python","size":10045,"content_sha256":"947f013b537f519d2101dfac5d88c5eb28291a2a4e76cee21696e17508b6c1f2"},{"filename":"scripts/schema_drift.py","content":"#!/usr/bin/env python3\n\"\"\"\nSchema drift check for declared API endpoints.\n\nEach `metadata.schema_expectations` entry declares a URL, method, and the keys\nthe response is expected to carry. This module parses the declarations out of\nSKILL.md frontmatter and, given them, GETs each URL and compares the\ntop-level JSON keys to the expected set.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nfrom urllib.error import URLError\nfrom urllib.request import Request, urlopen\n\nfrom skill_document import SkillDoc\n\nHTTP_TIMEOUT_SECONDS = 10\n_USER_AGENT = \"agent-skill-staleness-check/1.0\"\n\n\ndef parse_schema_expectations(doc: SkillDoc) -> list[dict]:\n \"\"\"Extract schema_expectations from the skill document.\n\n Each expectation has: url, method (default GET), expected_keys (list of\n strings). The simple list parser in SkillDoc doesn't carry sub-lists, so\n we re-walk the frontmatter to recover expected_keys.\n \"\"\"\n if doc.frontmatter is None:\n return []\n return _parse_expectations_from_frontmatter(doc.frontmatter)\n\n\ndef _parse_expectations_from_frontmatter(frontmatter: str) -> list[dict]:\n \"\"\"Walk YAML-ish frontmatter to recover schema_expectations entries with\n their expected_keys sub-lists.\"\"\"\n lines = frontmatter.split(\"\\n\")\n expectations: list[dict] = []\n current: dict | None = None\n in_metadata = False\n in_schema = False\n in_expected_keys = False\n\n for line in lines:\n stripped = line.strip()\n\n if not in_metadata:\n if stripped.startswith(\"metadata:\"):\n in_metadata = True\n continue\n\n # Left metadata block on an unindented non-blank line.\n if line and line[0] != \" \" and line[0] != \"\\t\" and stripped:\n break\n\n if not in_schema:\n if stripped.startswith(\"schema_expectations:\"):\n in_schema = True\n continue\n\n if stripped.startswith(\"- url:\") or stripped.startswith(\"- method:\"):\n if current is not None:\n expectations.append(current)\n in_expected_keys = False\n current = {\"url\": \"\", \"method\": \"GET\", \"expected_keys\": []}\n if stripped.startswith(\"- url:\"):\n current[\"url\"] = stripped.split(\":\", 1)[1].strip()\n elif stripped.startswith(\"- method:\"):\n current[\"method\"] = stripped.split(\":\", 1)[1].strip().upper()\n elif current is not None:\n if stripped.startswith(\"url:\"):\n current[\"url\"] = stripped.split(\":\", 1)[1].strip()\n elif stripped.startswith(\"method:\"):\n current[\"method\"] = stripped.split(\":\", 1)[1].strip().upper()\n elif stripped.startswith(\"expected_keys:\"):\n in_expected_keys = True\n elif in_expected_keys and stripped.startswith(\"- \"):\n current[\"expected_keys\"].append(stripped[2:].strip())\n elif not stripped.startswith(\"-\") and \":\" in stripped:\n key = stripped.split(\":\")[0].strip()\n if key not in (\"url\", \"method\", \"expected_keys\"):\n in_expected_keys = False\n\n if current is not None:\n expectations.append(current)\n\n return expectations\n\n\ndef check_schema_drift(expectations: list[dict]) -> list[dict]:\n \"\"\"\n GET each declared endpoint and compare top-level JSON keys against\n expected_keys. Reports missing keys (error), new keys (info), or full\n match (info).\n \"\"\"\n issues: list[dict] = []\n\n for exp in expectations:\n url = exp.get(\"url\", \"\").strip()\n method = exp.get(\"method\", \"GET\").upper()\n expected_keys = exp.get(\"expected_keys\", [])\n\n if not url:\n continue\n if not url.startswith((\"http://\", \"https://\")):\n issues.append({\n \"level\": \"warning\",\n \"message\": f\"Schema check skipped for non-HTTP URL: {url}\",\n \"detail\": \"Only HTTP/HTTPS URLs are supported.\",\n })\n continue\n if not expected_keys:\n issues.append({\n \"level\": \"info\",\n \"message\": f\"No expected_keys declared for {url}\",\n \"detail\": \"Skipping drift check.\",\n })\n continue\n\n try:\n req = Request(url, method=method)\n req.add_header(\"User-Agent\", _USER_AGENT)\n req.add_header(\"Accept\", \"application/json\")\n with urlopen(req, timeout=HTTP_TIMEOUT_SECONDS) as resp:\n body = resp.read().decode(\"utf-8\", errors=\"replace\")\n data = json.loads(body)\n\n if not isinstance(data, dict):\n issues.append({\n \"level\": \"warning\",\n \"message\": f\"Response from {url} is not a JSON object\",\n \"detail\": f\"Got {type(data).__name__}, expected dict. \"\n \"Cannot compare keys.\",\n })\n continue\n\n actual_keys = set(data.keys())\n expected_set = set(expected_keys)\n missing = expected_set - actual_keys\n new_keys = actual_keys - expected_set\n\n if missing:\n issues.append({\n \"level\": \"error\",\n \"message\": f\"Schema drift: missing keys from {url}\",\n \"detail\": f\"Expected keys not found: {sorted(missing)}. \"\n \"The API response structure may have changed.\",\n })\n if new_keys:\n issues.append({\n \"level\": \"info\",\n \"message\": f\"Schema drift: new keys in {url}\",\n \"detail\": f\"Unexpected keys found: {sorted(new_keys)}. \"\n \"The API may have added new fields.\",\n })\n if not missing and not new_keys:\n issues.append({\n \"level\": \"info\",\n \"message\": f\"Schema matches for {url}\",\n \"detail\": f\"All {len(expected_keys)} expected keys present, \"\n \"no unexpected keys.\",\n })\n\n except json.JSONDecodeError:\n issues.append({\n \"level\": \"error\",\n \"message\": f\"Response from {url} is not valid JSON\",\n \"detail\": \"Cannot perform schema drift check.\",\n })\n except URLError as exc:\n issues.append({\n \"level\": \"error\",\n \"message\": f\"Cannot reach {url} for schema check\",\n \"detail\": f\"Error: {exc.reason}\",\n })\n except Exception as exc:\n issues.append({\n \"level\": \"error\",\n \"message\": f\"Schema check failed for {url}\",\n \"detail\": f\"Error: {exc}\",\n })\n\n return issues\n","content_type":"text/x-python; charset=utf-8","language":"python","size":6997,"content_sha256":"59acf0a94fad8edc248636c050f463f201a4e1dcb0deab4dea28cbf8035731df"},{"filename":"scripts/security_scan.py","content":"#!/usr/bin/env python3\n\"\"\"\nSecurity Scanner for Generated Agent Skills.\n\nScans a skill directory for hardcoded API keys, sensitive files, and dangerous\nPython patterns that could pose security risks.\n\nUsage:\n python3 scripts/security_scan.py path/to/skill/\n python3 scripts/security_scan.py path/to/skill/ --json\n\nExit codes:\n 0 - Clean (no issues found)\n 1 - Issues found (one or more security issues detected)\n\"\"\"\n\nimport json\nimport os\nimport re\nimport sys\nfrom pathlib import Path\n\n\n# --- API Key Patterns ---\n# Each entry: (pattern_name, compiled_regex, description, severity)\n\nAPI_KEY_PATTERNS: list[tuple[str, re.Pattern, str, str]] = [\n (\n \"OpenAI API Key\",\n re.compile(r\"sk-[a-zA-Z0-9]{20,}\"),\n \"Hardcoded OpenAI API key detected\",\n \"high\",\n ),\n (\n \"AWS Access Key\",\n re.compile(r\"AKIA[A-Z0-9]{16}\"),\n \"Hardcoded AWS access key ID detected\",\n \"high\",\n ),\n (\n \"GitHub Personal Access Token\",\n re.compile(r\"ghp_[a-zA-Z0-9]{36}\"),\n \"Hardcoded GitHub personal access token detected\",\n \"high\",\n ),\n (\n \"GitLab Personal Access Token\",\n re.compile(r\"glpat-[a-zA-Z0-9\\-]{20}\"),\n \"Hardcoded GitLab personal access token detected\",\n \"high\",\n ),\n (\n \"Slack Token\",\n re.compile(r\"xox[bprs]-[a-zA-Z0-9\\-]+\"),\n \"Hardcoded Slack token detected\",\n \"high\",\n ),\n (\n \"Generic Secret\",\n re.compile(\n r\"\"\"(api[_\\-]?key|secret|token|password)\\s*[:=]\\s*[\"'][^\"']{8,}[\"']\"\"\",\n re.IGNORECASE,\n ),\n \"Possible hardcoded secret (generic key/token/password pattern)\",\n \"medium\",\n ),\n]\n\n\n# --- Sensitive File Names ---\n\nSENSITIVE_FILES: dict[str, str] = {\n \".env\": \"Environment file may contain secrets\",\n \"credentials.json\": \"Credentials file may contain API keys or passwords\",\n \"secrets.json\": \"Secrets file may contain sensitive data\",\n \"api_keys.json\": \"API keys file may contain hardcoded keys\",\n}\n\n\n# --- Dangerous Python Patterns ---\n# Each entry: (pattern_name, compiled_regex, description, severity)\n\nPYTHON_DANGER_PATTERNS: list[tuple[str, re.Pattern, str, str]] = [\n (\n \"eval() usage\",\n re.compile(r\"\\beval\\s*\\(\"),\n \"Use of eval() can execute arbitrary code; avoid unless strictly necessary\",\n \"high\",\n ),\n (\n \"exec() usage\",\n re.compile(r\"\\bexec\\s*\\(\"),\n \"Use of exec() can execute arbitrary code; avoid unless strictly necessary\",\n \"high\",\n ),\n (\n \"os.system() with concatenation\",\n re.compile(r\"os\\.system\\s*\\([^)]*[\\+f\\\"']\"),\n \"os.system() with string concatenation is vulnerable to shell injection\",\n \"high\",\n ),\n (\n \"subprocess with shell=True\",\n re.compile(r\"subprocess\\.call\\s*\\([^)]*shell\\s*=\\s*True\"),\n \"subprocess.call() with shell=True is vulnerable to shell injection\",\n \"high\",\n ),\n (\n \"__import__() dynamic import\",\n re.compile(r\"__import__\\s*\\(\"),\n \"Dynamic imports via __import__() can load arbitrary modules\",\n \"medium\",\n ),\n]\n\n\n# File extensions to scan for content patterns\nTEXT_EXTENSIONS: set[str] = {\n \".py\", \".md\", \".txt\", \".json\", \".yaml\", \".yml\", \".toml\", \".cfg\",\n \".ini\", \".sh\", \".bash\", \".zsh\", \".env\", \".conf\", \".xml\", \".html\",\n \".css\", \".js\", \".ts\", \".jsx\", \".tsx\", \".sql\", \".csv\", \".rst\",\n}\n\n# Maximum file size to scan (skip very large files to avoid performance issues)\nMAX_FILE_SIZE_BYTES = 10 * 1024 * 1024 # 10 MB\n\n# Directories to skip during scanning\nSKIP_DIRS: set[str] = {\n \".git\", \"__pycache__\", \"node_modules\", \".venv\", \"venv\", \"env\",\n \".pytest_cache\", \".mypy_cache\", \"dist\", \"build\",\n}\n\n\ndef _is_text_file(file_path: Path) -> bool:\n \"\"\"\n Determine if a file is likely a text file that should be scanned.\n\n Uses the file extension to decide. Falls back to attempting to read\n a small portion of the file if the extension is unrecognized.\n\n Args:\n file_path: Path to the file.\n\n Returns:\n True if the file should be scanned for content patterns.\n \"\"\"\n if file_path.suffix.lower() in TEXT_EXTENSIONS:\n return True\n\n # For files with no extension or unrecognized extensions, try reading a sample\n if file_path.suffix == \"\" or file_path.suffix.lower() not in {\n \".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\", \".ico\", \".svg\",\n \".pdf\", \".zip\", \".tar\", \".gz\", \".bz2\", \".xz\",\n \".exe\", \".dll\", \".so\", \".dylib\", \".whl\", \".egg\",\n \".pyc\", \".pyo\", \".class\", \".o\", \".a\",\n \".mp3\", \".mp4\", \".wav\", \".avi\", \".mov\",\n \".ttf\", \".otf\", \".woff\", \".woff2\", \".eot\",\n \".sqlite\", \".db\",\n }:\n try:\n with open(file_path, \"rb\") as f:\n chunk = f.read(1024)\n # Check for null bytes (binary indicator)\n if b\"\\x00\" in chunk:\n return False\n return True\n except (OSError, PermissionError):\n return False\n\n return False\n\n\ndef _scan_file_content(\n file_path: Path,\n skill_dir: Path,\n) -> list[dict]:\n \"\"\"\n Scan a single file for security issues in its content.\n\n Args:\n file_path: Absolute path to the file.\n skill_dir: Root directory of the skill (for relative path display).\n\n Returns:\n List of issue dictionaries found in this file.\n \"\"\"\n issues: list[dict] = []\n relative_path = str(file_path.relative_to(skill_dir))\n\n try:\n file_size = file_path.stat().st_size\n except OSError:\n return issues\n\n if file_size > MAX_FILE_SIZE_BYTES:\n return issues\n\n if not _is_text_file(file_path):\n return issues\n\n try:\n lines = file_path.read_text(encoding=\"utf-8\", errors=\"replace\").splitlines()\n except (OSError, PermissionError):\n return issues\n\n is_python = file_path.suffix.lower() == \".py\"\n\n for line_num, line in enumerate(lines, start=1):\n # Check API key patterns against all text files\n for pattern_name, regex, description, severity in API_KEY_PATTERNS:\n match = regex.search(line)\n if match:\n issues.append({\n \"severity\": severity,\n \"file\": relative_path,\n \"line\": line_num,\n \"pattern\": pattern_name,\n \"description\": description,\n })\n\n # Check Python-specific patterns only in .py files\n if is_python:\n for pattern_name, regex, description, severity in PYTHON_DANGER_PATTERNS:\n match = regex.search(line)\n if match:\n issues.append({\n \"severity\": severity,\n \"file\": relative_path,\n \"line\": line_num,\n \"pattern\": pattern_name,\n \"description\": description,\n })\n\n return issues\n\n\ndef security_scan(skill_path: str) -> dict:\n \"\"\"\n Perform a security scan on a skill directory.\n\n Checks for hardcoded API keys, sensitive files, and dangerous code patterns.\n\n Args:\n skill_path: Path to the skill directory to scan.\n\n Returns:\n Dictionary with keys:\n - ``clean`` (bool): True if no issues were found.\n - ``issues`` (list[dict]): List of issue dictionaries. Each has:\n - ``severity`` (str): \"high\", \"medium\", or \"low\"\n - ``file`` (str): Relative file path\n - ``line`` (int): Line number (0 for file-level issues)\n - ``pattern`` (str): Pattern name that triggered the issue\n - ``description`` (str): Human-readable description\n \"\"\"\n issues: list[dict] = []\n\n skill_dir = Path(skill_path).resolve()\n\n # --- Check: directory exists ---\n if not skill_dir.exists():\n return {\n \"clean\": False,\n \"issues\": [{\n \"severity\": \"high\",\n \"file\": str(skill_dir),\n \"line\": 0,\n \"pattern\": \"missing_directory\",\n \"description\": f\"Path does not exist: {skill_dir}\",\n }],\n }\n\n if not skill_dir.is_dir():\n return {\n \"clean\": False,\n \"issues\": [{\n \"severity\": \"high\",\n \"file\": str(skill_dir),\n \"line\": 0,\n \"pattern\": \"not_a_directory\",\n \"description\": f\"Path is not a directory: {skill_dir}\",\n }],\n }\n\n # --- Check: sensitive files ---\n for sensitive_name, description in SENSITIVE_FILES.items():\n sensitive_path = skill_dir / sensitive_name\n if sensitive_path.exists():\n issues.append({\n \"severity\": \"high\",\n \"file\": sensitive_name,\n \"line\": 0,\n \"pattern\": \"Sensitive file\",\n \"description\": description,\n })\n\n # Also check subdirectories for .env files\n for root, dirs, files in os.walk(skill_dir):\n root_path = Path(root)\n\n # Skip excluded directories\n dirs[:] = [d for d in dirs if d not in SKIP_DIRS]\n\n for filename in files:\n file_path = root_path / filename\n relative = str(file_path.relative_to(skill_dir))\n\n # Check for .env files anywhere in the tree\n if filename == \".env\" and relative != \".env\":\n issues.append({\n \"severity\": \"high\",\n \"file\": relative,\n \"line\": 0,\n \"pattern\": \"Sensitive file\",\n \"description\": \"Environment file may contain secrets\",\n })\n\n # Check for sensitive JSON files in subdirectories\n if filename in (\"credentials.json\", \"secrets.json\", \"api_keys.json\"):\n if relative != filename: # Not already caught at root level\n issues.append({\n \"severity\": \"high\",\n \"file\": relative,\n \"line\": 0,\n \"pattern\": \"Sensitive file\",\n \"description\": SENSITIVE_FILES.get(\n filename, \"Sensitive file detected\"\n ),\n })\n\n # --- Scan file contents ---\n for root, dirs, files in os.walk(skill_dir):\n root_path = Path(root)\n\n # Skip excluded directories\n dirs[:] = [d for d in dirs if d not in SKIP_DIRS]\n\n for filename in files:\n file_path = root_path / filename\n file_issues = _scan_file_content(file_path, skill_dir)\n issues.extend(file_issues)\n\n # Sort issues: high first, then medium, then low\n severity_order = {\"high\": 0, \"medium\": 1, \"low\": 2}\n issues.sort(key=lambda x: (severity_order.get(x[\"severity\"], 3), x[\"file\"], x[\"line\"]))\n\n return {\n \"clean\": len(issues) == 0,\n \"issues\": issues,\n }\n\n\ndef _print_human_readable(result: dict, skill_path: str) -> None:\n \"\"\"\n Print security scan results in a human-readable format.\n\n Args:\n result: The scan result dictionary.\n skill_path: The path that was scanned (for display).\n \"\"\"\n print(f\"Security scan: {skill_path}\")\n print(f\"{'=' * 60}\")\n\n if result[\"clean\"]:\n print(\"Status: CLEAN\")\n print(\"\\nNo security issues found.\")\n else:\n print(f\"Status: ISSUES FOUND ({len(result['issues'])})\")\n\n # Count by severity\n high = sum(1 for i in result[\"issues\"] if i[\"severity\"] == \"high\")\n medium = sum(1 for i in result[\"issues\"] if i[\"severity\"] == \"medium\")\n low = sum(1 for i in result[\"issues\"] if i[\"severity\"] == \"low\")\n print(f\"\\n High: {high} Medium: {medium} Low: {low}\")\n\n print()\n for issue in result[\"issues\"]:\n severity_label = issue[\"severity\"].upper().ljust(6)\n location = issue[\"file\"]\n if issue[\"line\"] > 0:\n location += f\":{issue['line']}\"\n print(f\" [{severity_label}] {location}\")\n print(f\" Pattern: {issue['pattern']}\")\n print(f\" {issue['description']}\")\n print()\n\n print(f\"{'=' * 60}\")\n\n\ndef main() -> None:\n \"\"\"CLI entry point for the security scanner.\"\"\"\n if len(sys.argv) \u003c 2:\n print(\n \"Usage: python3 scripts/security_scan.py \u003cskill-path> [--json]\\n\"\n \"\\n\"\n \"Arguments:\\n\"\n \" skill-path Path to the skill directory to scan\\n\"\n \"\\n\"\n \"Options:\\n\"\n \" --json Output results as JSON to stdout\\n\"\n \"\\n\"\n \"Exit codes:\\n\"\n \" 0 Clean (no issues)\\n\"\n \" 1 Issues found (one or more security issues)\\n\",\n file=sys.stderr,\n )\n sys.exit(1)\n\n skill_path = sys.argv[1]\n use_json = \"--json\" in sys.argv\n\n result = security_scan(skill_path)\n\n if use_json:\n print(json.dumps(result, indent=2))\n else:\n _print_human_readable(result, skill_path)\n\n sys.exit(0 if result[\"clean\"] else 1)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":13261,"content_sha256":"bc5076ca4cee80a4ca2ff2985d5a5bc38264546c5c3fc1eda6a60feb13404149"},{"filename":"scripts/skill_document.py","content":"#!/usr/bin/env python3\n\"\"\"\nSkill-document parsing for agent-skill-creator.\n\n`SkillDoc` is the single source of SKILL.md parsing. It owns frontmatter\nextraction and the deliberately-simple YAML reads (scalar fields, parent.child\nsub-fields, and lists-of-objects) that validate.py, staleness_check.py,\nskill_registry.py, and export_utils.py all need. Parse once via `from_text` /\n`from_path`, then query through the interface. Validation *rules* (length\nlimits, name regex, link checks) stay in validate.py — this module only parses.\n\nThe YAML reads are intentionally not a full YAML parser: they match the narrow\nshapes the spec uses and avoid a PyYAML dependency.\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom pathlib import Path\n\n_BLOCK_SCALAR_INDICATORS = (\">-\", \"|-\", \">\", \"|\", \">+\", \"|+\")\n\n\ndef _split_frontmatter(content: str) -> tuple[str | None, str | None]:\n \"\"\"Return (frontmatter, body) or (None, None) if frontmatter is absent or\n not closed.\"\"\"\n if not content.startswith(\"---\"):\n return None, None\n closing_index = content.find(\"---\", 3)\n if closing_index == -1:\n return None, None\n frontmatter = content[3:closing_index].strip()\n body = content[closing_index + 3:].strip()\n return frontmatter, body\n\n\nclass SkillDoc:\n \"\"\"Parsed view of a SKILL.md document.\"\"\"\n\n def __init__(self, frontmatter: str | None, body: str | None) -> None:\n self._frontmatter = frontmatter\n self._body = body\n\n @classmethod\n def from_text(cls, content: str) -> SkillDoc:\n \"\"\"Parse SKILL.md content. `frontmatter`/`body` are None when absent.\"\"\"\n frontmatter, body = _split_frontmatter(content)\n return cls(frontmatter, body)\n\n @classmethod\n def from_path(cls, path: str | Path) -> SkillDoc:\n \"\"\"Parse a SKILL.md file from disk.\"\"\"\n content = Path(path).read_text(encoding=\"utf-8\")\n return cls.from_text(content)\n\n # --- Raw sections ---\n\n @property\n def frontmatter(self) -> str | None:\n return self._frontmatter\n\n @property\n def body(self) -> str | None:\n return self._body\n\n # --- Typed accessors for the universal fields ---\n\n @property\n def name(self) -> str | None:\n return self.field(\"name\")\n\n @property\n def description(self) -> str | None:\n return self.field(\"description\")\n\n @property\n def license(self) -> str | None:\n return self.field(\"license\")\n\n @property\n def metadata(self) -> dict[str, str]:\n \"\"\"Direct scalar children of the `metadata:` block (non-empty values).\n\n Nested structures (e.g. `dependencies:`) are excluded; read those with\n `list_of_objects`. Only the first child-indent level is collected — use\n `subfield()` if you need an any-depth lookup.\n \"\"\"\n return self._collect_scalar_children(\"metadata\")\n\n # --- Generic queries ---\n\n def field(self, field: str) -> str | None:\n \"\"\"Top-level scalar field value, joining a YAML block scalar if used.\"\"\"\n if self._frontmatter is None:\n return None\n lines = self._frontmatter.split(\"\\n\")\n for i, line in enumerate(lines):\n stripped = line.strip()\n if stripped.startswith(f\"{field}:\"):\n value = stripped[len(field) + 1:].strip()\n if value in _BLOCK_SCALAR_INDICATORS:\n parts: list[str] = []\n for j in range(i + 1, len(lines)):\n continuation = lines[j]\n if continuation and (continuation[0] == \" \" or continuation[0] == \"\\t\"):\n parts.append(continuation.strip())\n else:\n break\n return \" \".join(parts) if parts else \"\"\n return value\n return None\n\n def has_field(self, field: str) -> bool:\n \"\"\"True if `field` appears as a top-level key.\"\"\"\n if self._frontmatter is None:\n return False\n for line in self._frontmatter.split(\"\\n\"):\n if line.strip().startswith(f\"{field}:\"):\n return True\n return False\n\n def subfield(self, parent: str, child: str) -> str | None:\n \"\"\"Scalar value of `child` under the indented `parent:` block.\"\"\"\n if self._frontmatter is None:\n return None\n in_parent = False\n for line in self._frontmatter.split(\"\\n\"):\n stripped = line.strip()\n if stripped.startswith(f\"{parent}:\"):\n in_parent = True\n continue\n if in_parent:\n if line and (line[0] == \" \" or line[0] == \"\\t\"):\n if stripped.startswith(f\"{child}:\"):\n return stripped[len(child) + 1:].strip()\n else:\n in_parent = False\n return None\n\n def has_subfield(self, parent: str, child: str) -> bool:\n \"\"\"True if `child` exists under the indented `parent:` block.\"\"\"\n if self._frontmatter is None:\n return False\n in_parent = False\n for line in self._frontmatter.split(\"\\n\"):\n stripped = line.strip()\n if stripped.startswith(f\"{parent}:\"):\n in_parent = True\n continue\n if in_parent:\n if line and (line[0] == \" \" or line[0] == \"\\t\"):\n if stripped.startswith(f\"{child}:\"):\n return True\n else:\n in_parent = False\n return False\n\n def list_of_objects(self, parent: str, child: str) -> list[dict]:\n \"\"\"Parse a YAML list-of-objects under `parent.child`.\n\n Handles::\n\n metadata:\n dependencies:\n - url: https://example.com\n name: Example\n type: api\n \"\"\"\n if self._frontmatter is None:\n return []\n lines = self._frontmatter.split(\"\\n\")\n items: list[dict] = []\n in_parent = False\n in_child = False\n current_item: dict | None = None\n child_indent = -1\n\n for line in lines:\n stripped = line.strip()\n if not in_parent:\n if stripped.startswith(f\"{parent}:\"):\n in_parent = True\n continue\n if line and line[0] != \" \" and line[0] != \"\\t\" and stripped:\n break\n if not in_child:\n if stripped.startswith(f\"{child}:\"):\n in_child = True\n continue\n if not stripped:\n continue\n raw_indent = len(line) - len(line.lstrip())\n if child_indent == -1 and stripped.startswith(\"- \"):\n child_indent = raw_indent\n if raw_indent \u003c= child_indent and not stripped.startswith(\"- \"):\n if \":\" in stripped:\n break\n if stripped.startswith(\"- \"):\n if current_item is not None:\n items.append(current_item)\n current_item = {}\n rest = stripped[2:].strip()\n if \":\" in rest:\n key, _, val = rest.partition(\":\")\n current_item[key.strip()] = val.strip()\n elif current_item is not None and \":\" in stripped:\n key, _, val = stripped.partition(\":\")\n current_item[key.strip()] = val.strip()\n\n if current_item is not None:\n items.append(current_item)\n return items\n\n def _collect_scalar_children(self, parent: str) -> dict[str, str]:\n \"\"\"All direct scalar children of `parent:` with non-empty inline values.\"\"\"\n if self._frontmatter is None:\n return {}\n result: dict[str, str] = {}\n in_parent = False\n child_indent: int | None = None\n for line in self._frontmatter.split(\"\\n\"):\n stripped = line.strip()\n if not in_parent:\n if stripped.startswith(f\"{parent}:\"):\n in_parent = True\n continue\n if not stripped:\n continue\n indent = len(line) - len(line.lstrip())\n if indent == 0:\n break\n if child_indent is None:\n child_indent = indent\n if indent == child_indent and \":\" in stripped and not stripped.startswith(\"- \"):\n key, _, val = stripped.partition(\":\")\n val = val.strip()\n if val:\n result[key.strip()] = val\n return result\n","content_type":"text/x-python; charset=utf-8","language":"python","size":8617,"content_sha256":"ad796429d2588f2842177e6256f135100c05768b9f75c7a4ffcfff24c8563c35"},{"filename":"scripts/skill_registry.py","content":"#!/usr/bin/env python3\n\"\"\"\nGit-Based Shared Skill Registry.\n\nManages a git-friendly skill registry for publishing, discovering, and installing\ncross-platform agent skills. The registry is a directory with a registry.json\nmanifest and a skills/ folder — no servers, no databases, no new dependencies.\n\nUsage:\n python3 scripts/skill_registry.py init [--name NAME] [--registry PATH]\n python3 scripts/skill_registry.py publish \u003cskill-path> [--registry PATH] [--tags T1,T2] [--force] [--json]\n python3 scripts/skill_registry.py list [--registry PATH] [--json]\n python3 scripts/skill_registry.py search \u003cquery> [--registry PATH] [--json]\n python3 scripts/skill_registry.py install \u003cskill-name> [--registry PATH] [--platform PLATFORM] [--project] [--force] [--json]\n python3 scripts/skill_registry.py info \u003cskill-name> [--registry PATH] [--json]\n python3 scripts/skill_registry.py remove \u003cskill-name> [--registry PATH] [--force]\n\nExit codes:\n 0 - Success\n 1 - Error\n\"\"\"\n\nimport argparse\nimport json\nimport re\nimport shutil\nimport sys\nfrom datetime import datetime, timezone\nfrom pathlib import Path\n\n# --- Import sibling scripts ---\n\n_SCRIPTS_DIR = Path(__file__).resolve().parent\nif str(_SCRIPTS_DIR) not in sys.path:\n sys.path.insert(0, str(_SCRIPTS_DIR))\n\nfrom validate import validate_skill # noqa: E402\nfrom skill_document import SkillDoc # noqa: E402\nfrom security_scan import security_scan # noqa: E402\nfrom staleness_check import DEFAULT_REVIEW_INTERVAL_DAYS # noqa: E402\nfrom platforms import PLATFORMS, list_supported_platforms, project_paths, user_paths # noqa: E402\n\n\n# --- Constants ---\n\n# Platform tables derive from the canonical scripts/platforms.py registry\n# (single source of truth, covers all 17 supported platforms with correct paths).\nALL_PLATFORMS = list_supported_platforms()\nPLATFORM_PATHS_USER = user_paths()\nPLATFORM_PATHS_PROJECT = project_paths()\n\n# Directories/files to exclude when copying skills\nCOPY_IGNORE_PATTERNS = shutil.ignore_patterns(\n \".git\", \"__pycache__\", \"node_modules\", \".venv\", \"venv\", \"env\",\n \".pytest_cache\", \".mypy_cache\", \"dist\", \"build\", \"*.pyc\", \"*.pyo\",\n)\n\n# Stop words for auto-tagging\nSTOP_WORDS = {\n \"a\", \"an\", \"the\", \"and\", \"or\", \"but\", \"is\", \"are\", \"was\", \"were\", \"be\",\n \"been\", \"being\", \"in\", \"on\", \"at\", \"to\", \"for\", \"of\", \"with\", \"by\",\n \"from\", \"as\", \"into\", \"through\", \"during\", \"before\", \"after\", \"above\",\n \"below\", \"between\", \"out\", \"off\", \"over\", \"under\", \"again\", \"further\",\n \"then\", \"once\", \"here\", \"there\", \"when\", \"where\", \"why\", \"how\", \"all\",\n \"each\", \"every\", \"both\", \"few\", \"more\", \"most\", \"other\", \"some\", \"such\",\n \"no\", \"nor\", \"not\", \"only\", \"own\", \"same\", \"so\", \"than\", \"too\", \"very\",\n \"can\", \"will\", \"just\", \"should\", \"now\", \"it\", \"its\", \"this\", \"that\",\n \"these\", \"those\", \"he\", \"she\", \"we\", \"they\", \"what\", \"which\", \"who\",\n \"whom\", \"do\", \"does\", \"did\", \"has\", \"have\", \"had\", \"having\", \"using\",\n}\n\nMIN_TAG_LENGTH = 3\n\n\n# --- Registry I/O ---\n\ndef load_registry(registry_path: Path) -> dict:\n \"\"\"Read and parse registry.json from the registry directory.\"\"\"\n manifest = registry_path / \"registry.json\"\n if not manifest.exists():\n print(f\"Error: registry.json not found in {registry_path}\", file=sys.stderr)\n print(\"Run 'skill_registry.py init' first.\", file=sys.stderr)\n sys.exit(1)\n try:\n return json.loads(manifest.read_text(encoding=\"utf-8\"))\n except (json.JSONDecodeError, OSError) as exc:\n print(f\"Error reading registry.json: {exc}\", file=sys.stderr)\n sys.exit(1)\n\n\ndef save_registry(registry_path: Path, data: dict) -> None:\n \"\"\"Atomic write: write to .tmp then rename.\"\"\"\n manifest = registry_path / \"registry.json\"\n tmp = registry_path / \"registry.json.tmp\"\n try:\n tmp.write_text(json.dumps(data, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n tmp.replace(manifest)\n except OSError as exc:\n # Clean up tmp on failure\n if tmp.exists():\n tmp.unlink()\n print(f\"Error writing registry.json: {exc}\", file=sys.stderr)\n sys.exit(1)\n\n\n# --- Metadata Extraction ---\n\ndef extract_skill_metadata(skill_path: Path) -> dict:\n \"\"\"\n Parse SKILL.md frontmatter into a metadata dict.\n\n Returns dict with keys: name, description, version, author, license.\n Missing fields default to empty string.\n \"\"\"\n skill_md = skill_path / \"SKILL.md\"\n if not skill_md.exists():\n return {\"name\": \"\", \"description\": \"\", \"version\": \"\", \"author\": \"\", \"license\": \"\"}\n\n content = skill_md.read_text(encoding=\"utf-8\")\n doc = SkillDoc.from_text(content)\n if doc.frontmatter is None:\n return {\"name\": \"\", \"description\": \"\", \"version\": \"\", \"author\": \"\", \"license\": \"\"}\n\n meta = doc.metadata\n # Version: try metadata.version first, then top-level version\n version = meta.get(\"version\") or doc.field(\"version\") or \"\"\n\n return {\n \"name\": (doc.name or \"\").strip(),\n \"description\": (doc.description or \"\").strip(),\n \"version\": version.strip(),\n \"author\": meta.get(\"author\", \"\").strip(),\n \"license\": (doc.license or \"\").strip(),\n \"created\": meta.get(\"created\", \"\").strip(),\n \"last_reviewed\": meta.get(\"last_reviewed\", \"\").strip(),\n \"review_interval_days\": meta.get(\"review_interval_days\", \"\").strip(),\n }\n\n\ndef auto_extract_tags(description: str) -> list[str]:\n \"\"\"\n Extract keyword tags from a description string.\n\n Splits on non-alphanumeric characters, filters stop words and short words,\n returns up to 10 unique lowercase tags.\n \"\"\"\n if not description:\n return []\n words = re.split(r\"[^a-zA-Z0-9-]+\", description.lower())\n seen: set[str] = set()\n tags: list[str] = []\n for word in words:\n word = word.strip(\"-\")\n if len(word) \u003c MIN_TAG_LENGTH:\n continue\n if word in STOP_WORDS:\n continue\n if word not in seen:\n seen.add(word)\n tags.append(word)\n if len(tags) >= 10:\n break\n return tags\n\n\n# --- Platform Detection ---\n\ndef detect_platform() -> str:\n \"\"\"\n Auto-detect the installed agent platform by checking known directories.\n\n Returns the platform name or \"claude-code\" as default.\n \"\"\"\n checks = [(p.name, p.detect_dir) for p in PLATFORMS if p.detect_dir]\n for platform, path in checks:\n if Path(path).expanduser().exists():\n return platform\n return \"claude-code\"\n\n\ndef resolve_install_path(name: str, platform: str, project: bool) -> Path:\n \"\"\"\n Map platform + scope to the filesystem install path for a skill.\n\n Args:\n name: Skill name (used as subdirectory).\n platform: Platform identifier.\n project: If True, use project-level path; otherwise user-level.\n\n Returns:\n Absolute path where the skill should be installed.\n \"\"\"\n if project:\n base = PLATFORM_PATHS_PROJECT.get(platform)\n else:\n base = PLATFORM_PATHS_USER.get(platform)\n\n if base is None:\n print(f\"Error: unknown platform '{platform}'\", file=sys.stderr)\n print(f\"Supported: {', '.join(ALL_PLATFORMS)}\", file=sys.stderr)\n sys.exit(1)\n\n return Path(base).expanduser().resolve() / name\n\n\n# --- Table Formatting ---\n\ndef _format_table(entries: list[dict]) -> str:\n \"\"\"Format skill entries as an aligned text table.\"\"\"\n if not entries:\n return \"No skills found.\"\n\n headers = [\"NAME\", \"VERSION\", \"AUTHOR\", \"TAGS\"]\n rows = []\n for entry in entries:\n tags = \", \".join(entry.get(\"tags\", []))\n rows.append([\n entry.get(\"name\", \"\"),\n entry.get(\"version\", \"\"),\n entry.get(\"author\", \"\"),\n tags,\n ])\n\n # Calculate column widths\n widths = [len(h) for h in headers]\n for row in rows:\n for i, cell in enumerate(row):\n widths[i] = max(widths[i], len(cell))\n\n # Build output\n lines = []\n header_line = \" \".join(h.ljust(widths[i]) for i, h in enumerate(headers))\n lines.append(header_line)\n for row in rows:\n lines.append(\" \".join(cell.ljust(widths[i]) for i, cell in enumerate(row)))\n return \"\\n\".join(lines)\n\n\n# --- Subcommands ---\n\ndef cmd_init(args: argparse.Namespace) -> None:\n \"\"\"Initialize a new skill registry.\"\"\"\n registry_path = Path(args.registry).resolve()\n manifest = registry_path / \"registry.json\"\n\n if manifest.exists():\n print(f\"Error: registry already exists at {registry_path}\", file=sys.stderr)\n sys.exit(1)\n\n registry_path.mkdir(parents=True, exist_ok=True)\n (registry_path / \"skills\").mkdir(exist_ok=True)\n\n name = args.name or \"Shared Skills\"\n data = {\n \"registry\": {\n \"name\": name,\n \"created\": datetime.now(timezone.utc).isoformat(timespec=\"seconds\"),\n \"schema_version\": \"1\",\n },\n \"skills\": [],\n }\n save_registry(registry_path, data)\n print(f\"Registry initialized: {registry_path}\")\n print(f\" Name: {name}\")\n print(f\" Manifest: {manifest}\")\n print(f\" Skills dir: {registry_path / 'skills'}\")\n\n\ndef cmd_publish(args: argparse.Namespace) -> None:\n \"\"\"Publish a skill to the registry.\"\"\"\n registry_path = Path(args.registry).resolve()\n skill_path = Path(args.skill_path).resolve()\n\n if not skill_path.is_dir():\n print(f\"Error: skill path is not a directory: {skill_path}\", file=sys.stderr)\n sys.exit(1)\n\n # Step 1: Validate\n validation = validate_skill(str(skill_path))\n if not validation[\"valid\"]:\n print(\"Validation failed:\", file=sys.stderr)\n for err in validation[\"errors\"]:\n print(f\" [ERROR] {err}\", file=sys.stderr)\n sys.exit(1)\n\n # Step 2: Security scan\n scan = security_scan(str(skill_path))\n high_issues = [i for i in scan[\"issues\"] if i[\"severity\"] == \"high\"]\n other_issues = [i for i in scan[\"issues\"] if i[\"severity\"] != \"high\"]\n\n if other_issues:\n for issue in other_issues:\n location = issue[\"file\"]\n if issue[\"line\"] > 0:\n location += f\":{issue['line']}\"\n print(f\" [WARN] {location}: {issue['description']}\")\n\n if high_issues and not args.force:\n print(\"Security scan found high-severity issues:\", file=sys.stderr)\n for issue in high_issues:\n location = issue[\"file\"]\n if issue[\"line\"] > 0:\n location += f\":{issue['line']}\"\n print(f\" [HIGH] {location}: {issue['description']}\", file=sys.stderr)\n print(\"Use --force to publish anyway.\", file=sys.stderr)\n sys.exit(1)\n\n # Step 3: Extract metadata\n metadata = extract_skill_metadata(skill_path)\n name = metadata[\"name\"]\n version = metadata[\"version\"] or \"0.0.0\"\n\n if not name:\n print(\"Error: could not extract skill name from SKILL.md frontmatter\", file=sys.stderr)\n sys.exit(1)\n\n # Step 4: Tags\n tags = []\n if args.tags:\n tags = [t.strip() for t in args.tags.split(\",\") if t.strip()]\n if not tags:\n tags = auto_extract_tags(metadata[\"description\"])\n\n # Step 5: Check duplicates\n data = load_registry(registry_path)\n for existing in data[\"skills\"]:\n if existing[\"name\"] == name and existing[\"version\"] == version:\n if not args.force:\n print(\n f\"Error: skill '{name}' version '{version}' already exists in registry.\",\n file=sys.stderr,\n )\n print(\"Use --force to overwrite.\", file=sys.stderr)\n sys.exit(1)\n # Remove old entry if forcing\n data[\"skills\"] = [s for s in data[\"skills\"] if not (s[\"name\"] == name and s[\"version\"] == version)]\n\n # Step 6: Copy skill to registry\n dest = registry_path / \"skills\" / name\n if dest.exists():\n shutil.rmtree(dest)\n shutil.copytree(skill_path, dest, ignore=COPY_IGNORE_PATTERNS)\n\n # Step 7: Add entry (including staleness metadata)\n staleness_data = {}\n if metadata.get(\"created\"):\n staleness_data[\"created\"] = metadata[\"created\"]\n if metadata.get(\"last_reviewed\"):\n staleness_data[\"last_reviewed\"] = metadata[\"last_reviewed\"]\n if metadata.get(\"review_interval_days\"):\n try:\n staleness_data[\"review_interval_days\"] = int(metadata[\"review_interval_days\"])\n except ValueError:\n pass\n\n entry = {\n \"name\": name,\n \"description\": metadata[\"description\"],\n \"version\": version,\n \"author\": metadata[\"author\"],\n \"license\": metadata[\"license\"],\n \"tags\": tags,\n \"platforms\": list(ALL_PLATFORMS),\n \"published\": datetime.now(timezone.utc).isoformat(timespec=\"seconds\"),\n \"path\": f\"skills/{name}\",\n \"validation\": {\n \"valid\": validation[\"valid\"],\n \"errors\": len(validation[\"errors\"]),\n \"warnings\": len(validation[\"warnings\"]),\n },\n \"security\": {\n \"clean\": scan[\"clean\"],\n \"issues\": len(scan[\"issues\"]),\n },\n \"staleness\": staleness_data,\n }\n data[\"skills\"].append(entry)\n save_registry(registry_path, data)\n\n if getattr(args, \"json\", False):\n print(json.dumps(entry, indent=2))\n else:\n print(f\"Published '{name}' v{version} to registry.\")\n print(f\" Path: {dest}\")\n print(f\" Tags: {', '.join(tags)}\")\n\n\ndef cmd_list(args: argparse.Namespace) -> None:\n \"\"\"List all skills in the registry.\"\"\"\n registry_path = Path(args.registry).resolve()\n data = load_registry(registry_path)\n\n if getattr(args, \"json\", False):\n print(json.dumps(data[\"skills\"], indent=2))\n return\n\n print(_format_table(data[\"skills\"]))\n\n\ndef cmd_search(args: argparse.Namespace) -> None:\n \"\"\"Search for skills matching a query.\"\"\"\n registry_path = Path(args.registry).resolve()\n data = load_registry(registry_path)\n query = args.query.lower()\n\n matches = []\n for skill in data[\"skills\"]:\n searchable = \" \".join([\n skill.get(\"name\", \"\"),\n skill.get(\"description\", \"\"),\n skill.get(\"author\", \"\"),\n \" \".join(skill.get(\"tags\", [])),\n ]).lower()\n if query in searchable:\n matches.append(skill)\n\n if getattr(args, \"json\", False):\n print(json.dumps(matches, indent=2))\n return\n\n if not matches:\n print(f\"No skills matching '{args.query}'.\")\n return\n\n print(f\"Skills matching '{args.query}':\\n\")\n print(_format_table(matches))\n\n\ndef cmd_install(args: argparse.Namespace) -> None:\n \"\"\"Install a skill from the registry.\"\"\"\n registry_path = Path(args.registry).resolve()\n data = load_registry(registry_path)\n\n # Find skill\n skill_entry = None\n for skill in data[\"skills\"]:\n if skill[\"name\"] == args.skill_name:\n skill_entry = skill\n break\n\n if skill_entry is None:\n print(f\"Error: skill '{args.skill_name}' not found in registry.\", file=sys.stderr)\n sys.exit(1)\n\n # Resolve platform\n platform = args.platform or detect_platform()\n if platform not in ALL_PLATFORMS:\n print(f\"Error: unknown platform '{platform}'\", file=sys.stderr)\n print(f\"Supported: {', '.join(ALL_PLATFORMS)}\", file=sys.stderr)\n sys.exit(1)\n\n # Resolve target path\n project = getattr(args, \"project\", False)\n target = resolve_install_path(args.skill_name, platform, project)\n\n # Check if already installed\n if target.exists() and not args.force:\n print(f\"Error: skill already installed at {target}\", file=sys.stderr)\n print(\"Use --force to overwrite.\", file=sys.stderr)\n sys.exit(1)\n\n # Copy\n source = registry_path / skill_entry[\"path\"]\n if not source.exists():\n print(f\"Error: skill files not found at {source}\", file=sys.stderr)\n sys.exit(1)\n\n if target.exists():\n shutil.rmtree(target)\n target.parent.mkdir(parents=True, exist_ok=True)\n shutil.copytree(source, target, ignore=COPY_IGNORE_PATTERNS)\n\n if getattr(args, \"json\", False):\n print(json.dumps({\n \"installed\": True,\n \"skill\": args.skill_name,\n \"platform\": platform,\n \"path\": str(target),\n }, indent=2))\n return\n\n scope = \"project\" if project else \"user\"\n print(f\"Installed '{args.skill_name}' for {platform} ({scope}-level).\")\n print(f\" Path: {target}\")\n\n # Platform-specific activation tips\n tips = {\n \"claude-code\": \"Skill is auto-loaded. Start a new conversation to activate.\",\n \"copilot\": \"Skill is auto-loaded by Copilot Chat.\",\n \"cursor\": \"Skill is loaded alongside .mdc rules.\",\n \"windsurf\": \"Skill is auto-loaded by Windsurf.\",\n \"cline\": \"Skill is loaded from .clinerules.\",\n \"codex\": \"Skill is auto-loaded by Codex CLI.\",\n \"gemini\": \"Skill is auto-loaded by Gemini CLI.\",\n }\n tip = tips.get(platform)\n if tip:\n print(f\" Tip: {tip}\")\n\n\ndef cmd_info(args: argparse.Namespace) -> None:\n \"\"\"Show detailed info about a skill.\"\"\"\n registry_path = Path(args.registry).resolve()\n data = load_registry(registry_path)\n\n skill_entry = None\n for skill in data[\"skills\"]:\n if skill[\"name\"] == args.skill_name:\n skill_entry = skill\n break\n\n if skill_entry is None:\n print(f\"Error: skill '{args.skill_name}' not found in registry.\", file=sys.stderr)\n sys.exit(1)\n\n if getattr(args, \"json\", False):\n print(json.dumps(skill_entry, indent=2))\n return\n\n print(f\"Skill: {skill_entry['name']}\")\n print(f\"{'=' * 50}\")\n print(f\" Version: {skill_entry.get('version', 'N/A')}\")\n print(f\" Author: {skill_entry.get('author', 'N/A')}\")\n print(f\" License: {skill_entry.get('license', 'N/A')}\")\n print(f\" Description: {skill_entry.get('description', 'N/A')}\")\n print(f\" Tags: {', '.join(skill_entry.get('tags', []))}\")\n print(f\" Platforms: {', '.join(skill_entry.get('platforms', []))}\")\n print(f\" Published: {skill_entry.get('published', 'N/A')}\")\n print(f\" Path: {skill_entry.get('path', 'N/A')}\")\n\n validation = skill_entry.get(\"validation\", {})\n if validation:\n status = \"valid\" if validation.get(\"valid\") else \"invalid\"\n print(f\" Validation: {status} ({validation.get('errors', 0)} errors, {validation.get('warnings', 0)} warnings)\")\n\n security = skill_entry.get(\"security\", {})\n if security:\n status = \"clean\" if security.get(\"clean\") else f\"{security.get('issues', 0)} issues\"\n print(f\" Security: {status}\")\n\n print(f\"{'=' * 50}\")\n\n\ndef cmd_remove(args: argparse.Namespace) -> None:\n \"\"\"Remove a skill from the registry.\"\"\"\n registry_path = Path(args.registry).resolve()\n data = load_registry(registry_path)\n\n # Find skill\n skill_entry = None\n for skill in data[\"skills\"]:\n if skill[\"name\"] == args.skill_name:\n skill_entry = skill\n break\n\n if skill_entry is None:\n print(f\"Error: skill '{args.skill_name}' not found in registry.\", file=sys.stderr)\n sys.exit(1)\n\n if not args.force:\n print(f\"Remove '{args.skill_name}' from registry? Use --force to confirm.\", file=sys.stderr)\n sys.exit(1)\n\n # Remove files\n skill_dir = registry_path / skill_entry[\"path\"]\n if skill_dir.exists():\n shutil.rmtree(skill_dir)\n\n # Remove entry\n data[\"skills\"] = [s for s in data[\"skills\"] if s[\"name\"] != args.skill_name]\n save_registry(registry_path, data)\n\n print(f\"Removed '{args.skill_name}' from registry.\")\n\n\ndef cmd_stale(args: argparse.Namespace) -> None:\n \"\"\"Report skills that are overdue for review.\"\"\"\n from datetime import date, timedelta\n\n registry_path = Path(args.registry).resolve()\n data = load_registry(registry_path)\n today = date.today()\n\n results: list[dict] = []\n for skill in data[\"skills\"]:\n staleness = skill.get(\"staleness\", {})\n published = skill.get(\"published\", \"\")\n\n # Determine reference date: last_reviewed > created > published\n ref_date = None\n date_source = \"none\"\n\n lr = staleness.get(\"last_reviewed\", \"\")\n cr = staleness.get(\"created\", \"\")\n\n if lr:\n try:\n parts = lr.split(\"-\")\n ref_date = date(int(parts[0]), int(parts[1]), int(parts[2]))\n date_source = \"last_reviewed\"\n except (ValueError, IndexError):\n pass\n\n if ref_date is None and cr:\n try:\n parts = cr.split(\"-\")\n ref_date = date(int(parts[0]), int(parts[1]), int(parts[2]))\n date_source = \"created\"\n except (ValueError, IndexError):\n pass\n\n if ref_date is None and published:\n try:\n parts = published[:10].split(\"-\")\n ref_date = date(int(parts[0]), int(parts[1]), int(parts[2]))\n date_source = \"published\"\n except (ValueError, IndexError):\n pass\n\n interval = staleness.get(\"review_interval_days\", DEFAULT_REVIEW_INTERVAL_DAYS)\n if not isinstance(interval, int):\n try:\n interval = int(interval)\n except (ValueError, TypeError):\n interval = DEFAULT_REVIEW_INTERVAL_DAYS\n\n days_since = None\n status = \"unknown\"\n if ref_date:\n days_since = (today - ref_date).days\n deadline = ref_date + timedelta(days=interval)\n if today > deadline:\n status = \"overdue\"\n elif (deadline - today).days \u003c= 30:\n status = \"due_soon\"\n else:\n status = \"fresh\"\n\n results.append({\n \"name\": skill.get(\"name\", \"\"),\n \"version\": skill.get(\"version\", \"\"),\n \"status\": status,\n \"days_since_review\": days_since,\n \"date_source\": date_source,\n \"review_interval_days\": interval,\n })\n\n if getattr(args, \"json\", False):\n print(json.dumps(results, indent=2))\n return\n\n # Text table output\n if not results:\n print(\"No skills in registry.\")\n return\n\n headers = [\"NAME\", \"VERSION\", \"STATUS\", \"DAYS SINCE\", \"SOURCE\", \"INTERVAL\"]\n rows = []\n for r in results:\n rows.append([\n r[\"name\"],\n r[\"version\"],\n r[\"status\"].upper(),\n str(r[\"days_since_review\"]) if r[\"days_since_review\"] is not None else \"N/A\",\n r[\"date_source\"],\n str(r[\"review_interval_days\"]),\n ])\n\n widths = [len(h) for h in headers]\n for row in rows:\n for i, cell in enumerate(row):\n widths[i] = max(widths[i], len(cell))\n\n header_line = \" \".join(h.ljust(widths[i]) for i, h in enumerate(headers))\n print(header_line)\n for row in rows:\n print(\" \".join(cell.ljust(widths[i]) for i, cell in enumerate(row)))\n\n # Summary\n overdue = sum(1 for r in results if r[\"status\"] == \"overdue\")\n due_soon = sum(1 for r in results if r[\"status\"] == \"due_soon\")\n if overdue or due_soon:\n print(f\"\\nSummary: {overdue} overdue, {due_soon} due soon, {len(results)} total\")\n\n\n# --- CLI ---\n\ndef _add_registry_arg(parser: argparse.ArgumentParser) -> None:\n \"\"\"Add the --registry argument to a subparser.\"\"\"\n parser.add_argument(\n \"--registry\", default=\"./registry\",\n help=\"Path to the registry directory (default: ./registry)\",\n )\n\n\ndef build_parser() -> argparse.ArgumentParser:\n \"\"\"Build the argument parser with all subcommands.\"\"\"\n parser = argparse.ArgumentParser(\n prog=\"skill_registry\",\n description=\"Git-based shared skill registry for cross-platform agent skills.\",\n )\n subparsers = parser.add_subparsers(dest=\"command\", help=\"Available commands\")\n\n # init\n p_init = subparsers.add_parser(\"init\", help=\"Initialize a new skill registry\")\n _add_registry_arg(p_init)\n p_init.add_argument(\"--name\", help=\"Registry name (default: 'Shared Skills')\")\n\n # publish\n p_publish = subparsers.add_parser(\"publish\", help=\"Publish a skill to the registry\")\n p_publish.add_argument(\"skill_path\", help=\"Path to the skill directory\")\n _add_registry_arg(p_publish)\n p_publish.add_argument(\"--tags\", help=\"Comma-separated tags (auto-extracted if omitted)\")\n p_publish.add_argument(\"--force\", action=\"store_true\", help=\"Overwrite existing or ignore high-severity issues\")\n p_publish.add_argument(\"--json\", action=\"store_true\", help=\"Output as JSON\")\n\n # list\n p_list = subparsers.add_parser(\"list\", help=\"List all skills in the registry\")\n _add_registry_arg(p_list)\n p_list.add_argument(\"--json\", action=\"store_true\", help=\"Output as JSON\")\n\n # search\n p_search = subparsers.add_parser(\"search\", help=\"Search for skills\")\n p_search.add_argument(\"query\", help=\"Search query (matches name, description, author, tags)\")\n _add_registry_arg(p_search)\n p_search.add_argument(\"--json\", action=\"store_true\", help=\"Output as JSON\")\n\n # install\n p_install = subparsers.add_parser(\"install\", help=\"Install a skill from the registry\")\n p_install.add_argument(\"skill_name\", help=\"Name of the skill to install\")\n _add_registry_arg(p_install)\n p_install.add_argument(\"--platform\", choices=ALL_PLATFORMS, help=\"Target platform (auto-detected if omitted)\")\n p_install.add_argument(\"--project\", action=\"store_true\", help=\"Install at project level instead of user level\")\n p_install.add_argument(\"--force\", action=\"store_true\", help=\"Overwrite existing installation\")\n p_install.add_argument(\"--json\", action=\"store_true\", help=\"Output as JSON\")\n\n # info\n p_info = subparsers.add_parser(\"info\", help=\"Show detailed info about a skill\")\n p_info.add_argument(\"skill_name\", help=\"Name of the skill\")\n _add_registry_arg(p_info)\n p_info.add_argument(\"--json\", action=\"store_true\", help=\"Output as JSON\")\n\n # remove\n p_remove = subparsers.add_parser(\"remove\", help=\"Remove a skill from the registry\")\n p_remove.add_argument(\"skill_name\", help=\"Name of the skill to remove\")\n _add_registry_arg(p_remove)\n p_remove.add_argument(\"--force\", action=\"store_true\", help=\"Confirm removal\")\n\n # stale\n p_stale = subparsers.add_parser(\"stale\", help=\"Report skills overdue for review\")\n _add_registry_arg(p_stale)\n p_stale.add_argument(\"--json\", action=\"store_true\", help=\"Output as JSON\")\n\n return parser\n\n\ndef main() -> None:\n \"\"\"CLI entry point.\"\"\"\n parser = build_parser()\n args = parser.parse_args()\n\n if args.command is None:\n parser.print_help()\n sys.exit(1)\n\n commands = {\n \"init\": cmd_init,\n \"publish\": cmd_publish,\n \"list\": cmd_list,\n \"search\": cmd_search,\n \"install\": cmd_install,\n \"info\": cmd_info,\n \"remove\": cmd_remove,\n \"stale\": cmd_stale,\n }\n\n cmd_func = commands.get(args.command)\n if cmd_func is None:\n parser.print_help()\n sys.exit(1)\n\n cmd_func(args)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":27254,"content_sha256":"96fa1622c5f6f1828e548847b77247019bd3ef354076950bdf119812440e2ea3"},{"filename":"scripts/staleness_check.py","content":"#!/usr/bin/env python3\n\"\"\"\nStaleness CLI for an agent skill.\n\nThin orchestrator: reads SKILL.md, runs the three peer modules\n(review_staleness, dependency_health, schema_drift), aggregates issues, and\nprints / exits. Each concern lives in its own module so it is testable in\nisolation and reusable elsewhere.\n\nUsage:\n python3 scripts/staleness_check.py \u003cskill-path> [--json] [--check-deps] [--check-drift]\n\nExit codes:\n 0 - Fresh (no staleness issues)\n 1 - Stale (overdue for review)\n 2 - Degraded (dependency failures or schema drift)\n\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nimport sys\nfrom pathlib import Path\n\n_SCRIPTS_DIR = Path(__file__).resolve().parent\nif str(_SCRIPTS_DIR) not in sys.path:\n sys.path.insert(0, str(_SCRIPTS_DIR))\n\nfrom skill_document import SkillDoc # noqa: E402\nfrom review_staleness import ( # noqa: E402\n DEFAULT_REVIEW_INTERVAL_DAYS,\n check_review_staleness,\n get_git_last_modified,\n)\nfrom dependency_health import check_dependency_health # noqa: E402\nfrom schema_drift import check_schema_drift, parse_schema_expectations # noqa: E402\n\n# Re-exported for back-compat (skill_registry historically imported from here).\n__all__ = [\"DEFAULT_REVIEW_INTERVAL_DAYS\", \"staleness_check\", \"main\"]\n\n\ndef staleness_check(\n skill_path: str,\n check_deps: bool = False,\n check_drift: bool = False,\n) -> dict:\n \"\"\"Run the staleness checks against the skill at `skill_path`.\n\n Returns a result dict with keys: fresh (bool), review_status (str),\n days_since_review (int|None), date_source (str), issues (list[dict]).\n \"\"\"\n all_issues: list[dict] = []\n skill_dir = Path(skill_path).resolve()\n\n if not skill_dir.exists():\n return _err(f\"Path does not exist: {skill_dir}\")\n if not skill_dir.is_dir():\n return _err(f\"Path is not a directory: {skill_dir}\")\n\n skill_md = skill_dir / \"SKILL.md\"\n if not skill_md.exists():\n return _err(\"SKILL.md not found\")\n\n try:\n content = skill_md.read_text(encoding=\"utf-8\")\n except Exception as exc:\n return _err(f\"Could not read SKILL.md: {exc}\")\n\n doc = SkillDoc.from_text(content)\n if doc.frontmatter is None:\n return _err(\"No valid frontmatter found\")\n\n # Phase 1: review staleness\n git_date = get_git_last_modified(skill_path)\n review_issues, review_status, days_since, date_source = check_review_staleness(doc, git_date)\n all_issues.extend(review_issues)\n\n # Phase 2: dependency health\n if check_deps:\n deps = doc.list_of_objects(\"metadata\", \"dependencies\")\n if deps:\n all_issues.extend(check_dependency_health(deps))\n else:\n all_issues.append({\n \"level\": \"info\",\n \"message\": \"No dependencies declared\",\n \"detail\": \"Add metadata.dependencies to enable health checks.\",\n })\n\n # Phase 3: schema drift\n if check_drift:\n expectations = parse_schema_expectations(doc)\n if expectations:\n all_issues.extend(check_schema_drift(expectations))\n else:\n all_issues.append({\n \"level\": \"info\",\n \"message\": \"No schema expectations declared\",\n \"detail\": \"Add metadata.schema_expectations to enable drift detection.\",\n })\n\n has_errors = any(i[\"level\"] == \"error\" for i in all_issues)\n return {\n \"fresh\": not has_errors,\n \"review_status\": review_status,\n \"days_since_review\": days_since,\n \"date_source\": date_source,\n \"issues\": all_issues,\n }\n\n\ndef _err(message: str) -> dict:\n return {\n \"fresh\": False,\n \"review_status\": \"unknown\",\n \"days_since_review\": None,\n \"date_source\": \"none\",\n \"issues\": [{\"level\": \"error\", \"message\": message, \"detail\": \"\"}],\n }\n\n\ndef _print_human_readable(result: dict, skill_path: str) -> None:\n print(f\"Staleness check: {skill_path}\")\n print(f\"{'=' * 60}\")\n\n status_label = result[\"review_status\"].upper().replace(\"_\", \" \")\n print(f\"Review status: {status_label}\")\n if result[\"days_since_review\"] is not None:\n print(f\"Days since review: {result['days_since_review']} (source: {result['date_source']})\")\n print(\"Overall: FRESH\" if result[\"fresh\"] else \"Overall: STALE / DEGRADED\")\n\n if result[\"issues\"]:\n for level, label in ((\"error\", \"[ERROR]\"), (\"warning\", \"[WARN] \"), (\"info\", \"[INFO] \")):\n bucket = [i for i in result[\"issues\"] if i[\"level\"] == level]\n if bucket:\n print(f\"\\n{level.capitalize()} ({len(bucket)}):\")\n for issue in bucket:\n print(f\" {label} {issue['message']}\")\n if issue[\"detail\"]:\n print(f\" {issue['detail']}\")\n\n print(f\"{'=' * 60}\")\n\n\ndef main() -> None:\n if len(sys.argv) \u003c 2:\n print(\n \"Usage: python3 scripts/staleness_check.py \u003cskill-path> [--json] [--check-deps] [--check-drift]\\n\"\n \"\\n\"\n \"Arguments:\\n\"\n \" skill-path Path to the skill directory to check\\n\"\n \"\\n\"\n \"Options:\\n\"\n \" --json Output results as JSON to stdout\\n\"\n \" --check-deps HTTP-check declared dependency URLs\\n\"\n \" --check-drift Detect schema drift in declared API endpoints\\n\"\n \"\\n\"\n \"Exit codes:\\n\"\n \" 0 Fresh (no staleness issues)\\n\"\n \" 1 Stale (overdue for review)\\n\"\n \" 2 Degraded (dependency failures or schema drift)\\n\",\n file=sys.stderr,\n )\n sys.exit(1)\n\n skill_path = sys.argv[1]\n use_json = \"--json\" in sys.argv\n check_deps = \"--check-deps\" in sys.argv\n check_drift = \"--check-drift\" in sys.argv\n\n result = staleness_check(skill_path, check_deps=check_deps, check_drift=check_drift)\n\n if use_json:\n print(json.dumps(result, indent=2))\n else:\n _print_human_readable(result, skill_path)\n\n if result[\"review_status\"] == \"overdue\":\n sys.exit(1)\n\n has_dep_or_drift_errors = any(\n i[\"level\"] == \"error\" and \"review\" not in i[\"message\"].lower()\n for i in result[\"issues\"]\n )\n if has_dep_or_drift_errors:\n sys.exit(2)\n\n sys.exit(0)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":6326,"content_sha256":"1e35a263a2a147da01583daf48a09183d3743c7afe9c0dac633e21416ff2fab7"},{"filename":"scripts/tests/__init__.py","content":"","content_type":"text/x-python; charset=utf-8","language":"python","size":0,"content_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},{"filename":"scripts/tests/fixtures/labeled_examples.json","content":"[\n {\"description\": \"weekly sales report by region\", \"expected\": \"line-chart\"},\n {\"description\": \"monthly revenue trend\", \"expected\": \"line-chart\"},\n {\"description\": \"executive KPI dashboard\", \"expected\": \"kpi-cards\"},\n {\"description\": \"inventory snapshot by warehouse\", \"expected\": \"data-table\"},\n\n {\"description\": \"daily active users over last quarter\", \"expected\": \"line-chart\"},\n {\"description\": \"year over year revenue growth\", \"expected\": \"line-chart\"},\n {\"description\": \"hourly api latency for the past week\", \"expected\": \"line-chart\"},\n {\"description\": \"stock price history visualization\", \"expected\": \"line-chart\"},\n {\"description\": \"month-over-month conversion rate\", \"expected\": \"line-chart\"},\n {\"description\": \"weekly support ticket volume trend\", \"expected\": \"line-chart\"},\n {\"description\": \"cpu utilization over the past 30 days\", \"expected\": \"line-chart\"},\n\n {\"description\": \"sales by salesperson this quarter\", \"expected\": \"bar-chart\"},\n {\"description\": \"compare deployment success rate by environment\", \"expected\": \"bar-chart\"},\n {\"description\": \"feature usage by user segment\", \"expected\": \"bar-chart\"},\n {\"description\": \"revenue by product category\", \"expected\": \"bar-chart\"},\n {\"description\": \"bug count by team\", \"expected\": \"bar-chart\"},\n {\"description\": \"cost breakdown per service\", \"expected\": \"bar-chart\"},\n {\"description\": \"regional crop forecast for brazil\", \"expected\": \"bar-chart\"},\n {\"description\": \"campaign roi comparison across channels\", \"expected\": \"bar-chart\"},\n {\"description\": \"headcount by department\", \"expected\": \"bar-chart\"},\n\n {\"description\": \"monthly recurring revenue and churn dashboard\", \"expected\": \"kpi-cards\"},\n {\"description\": \"key metrics summary for finance team\", \"expected\": \"kpi-cards\"},\n {\"description\": \"operational health scorecard\", \"expected\": \"kpi-cards\"},\n {\"description\": \"top-level product north star metrics\", \"expected\": \"kpi-cards\"},\n {\"description\": \"executive summary numbers for board meeting\", \"expected\": \"kpi-cards\"},\n {\"description\": \"weekly traffic kpis\", \"expected\": \"kpi-cards\"},\n {\"description\": \"sales pipeline highlights\", \"expected\": \"kpi-cards\"},\n {\"description\": \"support sla scorecard\", \"expected\": \"kpi-cards\"},\n\n {\"description\": \"current inventory levels by sku\", \"expected\": \"data-table\"},\n {\"description\": \"outstanding invoices listing\", \"expected\": \"data-table\"},\n {\"description\": \"employee onboarding status by hire\", \"expected\": \"data-table\"},\n {\"description\": \"open security findings ranked\", \"expected\": \"data-table\"},\n {\"description\": \"active customer accounts with metadata\", \"expected\": \"data-table\"},\n {\"description\": \"shipment tracking status detail\", \"expected\": \"data-table\"},\n {\"description\": \"supplier audit results table\", \"expected\": \"data-table\"},\n {\"description\": \"fleet status by vehicle id\", \"expected\": \"data-table\"},\n {\"description\": \"open jira tickets with assignees\", \"expected\": \"data-table\"},\n {\"description\": \"expense report line items\", \"expected\": \"data-table\"},\n {\"description\": \"lab test results listing\", \"expected\": \"data-table\"},\n {\"description\": \"regulatory filings status grid\", \"expected\": \"data-table\"},\n\n {\"description\": \"deploy runbook for the payments service\", \"expected\": null},\n {\"description\": \"sox compliance checker\", \"expected\": null},\n {\"description\": \"post-mortem template for incidents\", \"expected\": null},\n {\"description\": \"onboard new hire to engineering org\", \"expected\": null},\n {\"description\": \"translate korean technical doc to english\", \"expected\": null},\n {\"description\": \"summarize customer call transcript\", \"expected\": null},\n {\"description\": \"generate slack message for outage\", \"expected\": null},\n {\"description\": \"create pull request description from diff\", \"expected\": null},\n {\"description\": \"rewrite jargon for management audience\", \"expected\": null},\n {\"description\": \"draft an apology email to a client\", \"expected\": null}\n]\n","content_type":"application/json; charset=utf-8","language":"json","size":3934,"content_sha256":"e000954e99d442b3003af5ee440259fffb996f7558778d56d32a062cc4f4b094"},{"filename":"scripts/tests/fixtures/v4_skills_regression.json","content":"[\n {\n \"id\": \"stock-analyzer\",\n \"description\": \"Analyze stocks and ETFs using RSI, MACD, and Bollinger Bands to generate buy/sell signals and compare ticker symbols\",\n \"path\": \"references/examples/stock-analyzer/SKILL.md\",\n \"expected_no_artifact\": true,\n \"synthetic\": false\n },\n {\n \"id\": \"sales-report-skill\",\n \"description\": \"Generate the weekly sales report for the West region by pulling data from our CRM, cleaning it, and emailing a PDF summary\",\n \"path\": null,\n \"expected_no_artifact\": true,\n \"synthetic\": true\n },\n {\n \"id\": \"deploy-checklist-skill\",\n \"description\": \"Walk an engineer through the production deployment runbook, asking about migrations, feature flags, rollback plan, and on-call coverage before approval\",\n \"path\": null,\n \"expected_no_artifact\": true,\n \"synthetic\": true\n },\n {\n \"id\": \"quarterly-compliance-skill\",\n \"description\": \"Compile the quarterly compliance documentation pack — policy attestations, audit log exports, access reviews — and produce a signed PDF for legal\",\n \"path\": null,\n \"expected_no_artifact\": true,\n \"synthetic\": true\n },\n {\n \"id\": \"customer-churn-skill\",\n \"description\": \"Score account churn risk from CRM activity, support ticket volume, and product usage signals; flag accounts needing intervention\",\n \"path\": null,\n \"expected_no_artifact\": true,\n \"synthetic\": true\n },\n {\n \"id\": \"incident-runbook-skill\",\n \"description\": \"Drive an on-call engineer through the incident response runbook — triage, comms, mitigation steps, post-incident writeup\",\n \"path\": null,\n \"expected_no_artifact\": true,\n \"synthetic\": true\n },\n {\n \"id\": \"invoice-processor-skill\",\n \"description\": \"Process invoices from email attachments, extract line items via OCR, and post entries into the accounting ledger\",\n \"path\": null,\n \"expected_no_artifact\": true,\n \"synthetic\": true\n },\n {\n \"id\": \"meeting-notes-skill\",\n \"description\": \"Take meeting transcripts and produce structured notes with decisions, action items, and owners assigned\",\n \"path\": null,\n \"expected_no_artifact\": true,\n \"synthetic\": true\n },\n {\n \"id\": \"api-doc-generator-skill\",\n \"description\": \"Generate developer-facing API documentation from OpenAPI specs and codebase annotations, formatted as markdown for the docs site\",\n \"path\": null,\n \"expected_no_artifact\": true,\n \"synthetic\": true\n },\n {\n \"id\": \"data-cleaner-skill\",\n \"description\": \"Clean a raw CSV export — normalize column names, parse dates, deduplicate by primary key, and write a cleaned file plus a row-level quality report\",\n \"path\": null,\n \"expected_no_artifact\": true,\n \"synthetic\": true\n }\n]\n","content_type":"application/json; charset=utf-8","language":"json","size":2729,"content_sha256":"3b2bb8afb66fe1c5034e233a92a290cfbf8c0d7f472e0dc5cad807b641539de7"},{"filename":"scripts/tests/test_artifact_detector.py","content":"\"\"\"Unit tests for scripts.artifact_detector.\n\nTests come in two flavors:\n1. Targeted unit tests for each signal detector (temporal, comparative,\n KPI, tabular).\n2. An accuracy sweep over scripts/tests/fixtures/labeled_examples.json\n with a ≥85% accuracy gate.\n\"\"\"\n\nimport json\nimport sys\nimport unittest\nfrom pathlib import Path\n\nROOT = Path(__file__).parent.parent.parent\nsys.path.insert(0, str(ROOT / \"scripts\"))\n\nfrom artifact_detector import detect_artifact # noqa: E402\n\n\nFIXTURES = ROOT / \"scripts\" / \"tests\" / \"fixtures\" / \"labeled_examples.json\"\n\n\nclass ArtifactDetectorApiTest(unittest.TestCase):\n def test_detect_artifact_returns_none_for_empty_description(self) -> None:\n self.assertIsNone(detect_artifact(\"\"))\n\n def test_detect_artifact_returns_str_or_none(self) -> None:\n result = detect_artifact(\"weekly sales report\")\n self.assertIn(result, {\"line-chart\", \"bar-chart\", \"kpi-cards\", \"data-table\", None})\n\n\nclass TemporalSignalTest(unittest.TestCase):\n def test_monthly_trend_returns_line_chart(self) -> None:\n self.assertEqual(detect_artifact(\"monthly revenue trend\"), \"line-chart\")\n\n def test_weekly_over_time_returns_line_chart(self) -> None:\n self.assertEqual(detect_artifact(\"weekly active users over the last quarter\"), \"line-chart\")\n\n def test_year_over_year_returns_line_chart(self) -> None:\n self.assertEqual(detect_artifact(\"year over year revenue growth\"), \"line-chart\")\n\n def test_hourly_latency_returns_line_chart(self) -> None:\n self.assertEqual(detect_artifact(\"hourly api latency for the past week\"), \"line-chart\")\n\n\nclass ComparativeSignalTest(unittest.TestCase):\n def test_by_region_returns_bar_chart(self) -> None:\n self.assertEqual(detect_artifact(\"revenue by region\"), \"bar-chart\")\n\n def test_by_category_returns_bar_chart(self) -> None:\n self.assertEqual(detect_artifact(\"sales by product category\"), \"bar-chart\")\n\n def test_compare_returns_bar_chart(self) -> None:\n self.assertEqual(detect_artifact(\"compare deployment success rate by environment\"), \"bar-chart\")\n\n def test_temporal_takes_precedence_over_comparative(self) -> None:\n # \"weekly ... by region\" is both temporal and comparative.\n # Temporal precedence is intentional (line is more informative for trends).\n self.assertEqual(detect_artifact(\"weekly sales by region\"), \"line-chart\")\n\n\nclass KpiSignalTest(unittest.TestCase):\n def test_kpi_returns_cards(self) -> None:\n self.assertEqual(detect_artifact(\"executive KPI dashboard\"), \"kpi-cards\")\n\n def test_key_metrics_returns_cards(self) -> None:\n self.assertEqual(detect_artifact(\"key metrics summary for finance team\"), \"kpi-cards\")\n\n def test_scorecard_returns_cards(self) -> None:\n self.assertEqual(detect_artifact(\"operational health scorecard\"), \"kpi-cards\")\n\n def test_north_star_metrics_returns_cards(self) -> None:\n self.assertEqual(detect_artifact(\"top-level product north star metrics\"), \"kpi-cards\")\n\n\nclass TabularSignalTest(unittest.TestCase):\n def test_listing_returns_data_table(self) -> None:\n self.assertEqual(detect_artifact(\"outstanding invoices listing\"), \"data-table\")\n\n def test_inventory_levels_returns_data_table(self) -> None:\n self.assertEqual(detect_artifact(\"current inventory levels by sku\"), \"data-table\")\n\n def test_status_table_returns_data_table(self) -> None:\n self.assertEqual(detect_artifact(\"regulatory filings status grid\"), \"data-table\")\n\n def test_log_returns_data_table(self) -> None:\n self.assertEqual(detect_artifact(\"customer escalation log\"), \"data-table\")\n\n\nclass NegativeSignalTest(unittest.TestCase):\n def test_runbook_returns_none(self) -> None:\n self.assertIsNone(detect_artifact(\"deploy runbook for the payments service\"))\n\n def test_email_returns_none(self) -> None:\n self.assertIsNone(detect_artifact(\"draft an apology email to a client\"))\n\n def test_translation_returns_none(self) -> None:\n self.assertIsNone(detect_artifact(\"translate korean technical doc to english\"))\n\n\nclass LabeledAccuracyGate(unittest.TestCase):\n ACCURACY_THRESHOLD = 0.85\n\n def setUp(self) -> None:\n self.examples = json.loads(FIXTURES.read_text())\n\n def test_accuracy_meets_threshold(self) -> None:\n correct = 0\n misses: list[tuple[str, str | None, str | None]] = []\n for example in self.examples:\n actual = detect_artifact(example[\"description\"])\n if actual == example[\"expected\"]:\n correct += 1\n else:\n misses.append((example[\"description\"], example[\"expected\"], actual))\n\n accuracy = correct / len(self.examples)\n message = (\n f\"Accuracy {accuracy:.2%} below threshold \"\n f\"{self.ACCURACY_THRESHOLD:.0%}. Misses:\\n\"\n + \"\\n\".join(f\" - {d!r}: expected={e!r} got={a!r}\" for d, e, a in misses)\n )\n self.assertGreaterEqual(accuracy, self.ACCURACY_THRESHOLD, message)\n\n\nif __name__ == \"__main__\":\n unittest.main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":5089,"content_sha256":"ce5d0abfe401b1bfa7d3340cd01b26fac0461704bbcd71d68f061044f9d0ba8c"},{"filename":"scripts/tests/test_check_pipeline.py","content":"\"\"\"Tests for scripts.check_pipeline — the generated-skill pipeline verifier.\"\"\"\n\nimport sys\nimport tempfile\nimport unittest\nfrom pathlib import Path\n\nROOT = Path(__file__).parent.parent.parent\nsys.path.insert(0, str(ROOT / \"scripts\"))\n\nfrom check_pipeline import check, main # noqa: E402\n\nSTEP = \"def main():\\n pass\\n\\n\\nif __name__ == '__main__':\\n main()\\n\"\n\n\ndef _skill(tmp: Path, files: dict[str, str], requirements: str | None = None) -> Path:\n \"\"\"Create a skill dir. `files` maps relative paths (under the skill) to text.\"\"\"\n skill = tmp / \"demo-skill\"\n for rel, text in files.items():\n p = skill / rel\n p.parent.mkdir(parents=True, exist_ok=True)\n p.write_text(text, encoding=\"utf-8\")\n if requirements is not None:\n (skill / \"requirements.txt\").write_text(requirements, encoding=\"utf-8\")\n skill.mkdir(parents=True, exist_ok=True)\n return skill\n\n\nclass CompileCheckTest(unittest.TestCase):\n def setUp(self) -> None:\n self._tmp = tempfile.TemporaryDirectory()\n self.tmp = Path(self._tmp.name)\n\n def tearDown(self) -> None:\n self._tmp.cleanup()\n\n def test_clean_script_passes(self) -> None:\n skill = _skill(self.tmp, {\"scripts/main.py\": \"import json\\nprint(json.dumps({}))\\n\"})\n result = check(skill)\n self.assertEqual(result[\"errors\"], [])\n\n def test_syntax_error_is_an_error(self) -> None:\n skill = _skill(self.tmp, {\"scripts/broken.py\": \"def main(:\\n pass\\n\"})\n result = check(skill)\n self.assertTrue(any(\"does not compile\" in e for e in result[\"errors\"]))\n self.assertEqual(main([str(skill)]), 1)\n\n\nclass DependencyCheckTest(unittest.TestCase):\n def setUp(self) -> None:\n self._tmp = tempfile.TemporaryDirectory()\n self.tmp = Path(self._tmp.name)\n\n def tearDown(self) -> None:\n self._tmp.cleanup()\n\n def test_third_party_without_requirements_is_error(self) -> None:\n skill = _skill(self.tmp, {\"scripts/fetch.py\": \"import requests\\n\"})\n result = check(skill)\n self.assertTrue(any(\"not declared\" in e for e in result[\"errors\"]))\n\n def test_third_party_with_requirements_ok(self) -> None:\n skill = _skill(\n self.tmp,\n {\"scripts/fetch.py\": \"import requests\\n\"},\n requirements=\"requests>=2.0\\n\",\n )\n self.assertEqual(check(skill)[\"errors\"], [])\n\n def test_stdlib_only_needs_no_requirements(self) -> None:\n skill = _skill(self.tmp, {\"scripts/main.py\": \"import json, os, re\\nfrom pathlib import Path\\n\"})\n self.assertEqual(check(skill)[\"errors\"], [])\n\n def test_local_sibling_import_not_flagged(self) -> None:\n skill = _skill(\n self.tmp,\n {\n \"scripts/main.py\": \"from helpers import go\\n\",\n \"scripts/helpers.py\": \"def go():\\n pass\\n\",\n },\n )\n self.assertEqual(check(skill)[\"errors\"], [])\n\n\nclass EntrypointWarningTest(unittest.TestCase):\n def setUp(self) -> None:\n self._tmp = tempfile.TemporaryDirectory()\n self.tmp = Path(self._tmp.name)\n\n def tearDown(self) -> None:\n self._tmp.cleanup()\n\n def test_two_steps_without_orchestrator_warns(self) -> None:\n skill = _skill(self.tmp, {\"scripts/fetch.py\": STEP, \"scripts/parse.py\": STEP})\n result = check(skill)\n self.assertEqual(result[\"errors\"], [])\n self.assertTrue(any(\"orchestrator\" in w or \"sequence\" in w for w in result[\"warnings\"]))\n self.assertEqual(main([str(skill)]), 0) # warning, not error\n\n def test_two_steps_with_orchestrator_no_warning(self) -> None:\n skill = _skill(\n self.tmp,\n {\n \"scripts/fetch.py\": STEP,\n \"scripts/parse.py\": STEP,\n \"scripts/run_pipeline.py\": STEP,\n },\n )\n self.assertEqual(check(skill)[\"warnings\"], [])\n\n def test_single_step_no_warning(self) -> None:\n skill = _skill(self.tmp, {\"scripts/main.py\": STEP})\n self.assertEqual(check(skill)[\"warnings\"], [])\n\n\nclass MainExitTest(unittest.TestCase):\n def test_missing_dir_exits_two(self) -> None:\n self.assertEqual(main([\"/no/such/skill/dir/xyz\"]), 2)\n\n\nif __name__ == \"__main__\":\n unittest.main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":4280,"content_sha256":"005d05b9ab4a467a9bf70e6ea88308fb2ba2fa028095348b851529471ebc7588"},{"filename":"scripts/tests/test_dependency_health.py","content":"\"\"\"Tests for scripts.dependency_health — HTTP HEAD probes (mocked, no network).\"\"\"\n\nimport sys\nimport unittest\nfrom pathlib import Path\nfrom unittest.mock import MagicMock, patch\nfrom urllib.error import URLError\n\nROOT = Path(__file__).parent.parent.parent\nsys.path.insert(0, str(ROOT / \"scripts\"))\n\nfrom dependency_health import check_dependency_health # noqa: E402\n\n\ndef _mock_response(status: int) -> MagicMock:\n \"\"\"Build a context-manager-compatible mock with .status = status.\"\"\"\n resp = MagicMock()\n resp.status = status\n cm = MagicMock()\n cm.__enter__.return_value = resp\n cm.__exit__.return_value = None\n return cm\n\n\nclass DependencyHealthTest(unittest.TestCase):\n @patch(\"dependency_health.urlopen\")\n def test_2xx_is_healthy_info(self, mock_urlopen: MagicMock) -> None:\n mock_urlopen.return_value = _mock_response(200)\n issues = check_dependency_health([{\"name\": \"API\", \"url\": \"https://example.com/v1\"}])\n self.assertTrue(any(i[\"level\"] == \"info\" and \"healthy\" in i[\"message\"] for i in issues))\n self.assertFalse(any(i[\"level\"] in (\"warning\", \"error\") for i in issues))\n\n @patch(\"dependency_health.urlopen\")\n def test_4xx_is_warning(self, mock_urlopen: MagicMock) -> None:\n mock_urlopen.return_value = _mock_response(404)\n issues = check_dependency_health([{\"name\": \"Gone\", \"url\": \"https://example.com/old\"}])\n self.assertTrue(any(i[\"level\"] == \"warning\" and \"client error\" in i[\"message\"] for i in issues))\n\n @patch(\"dependency_health.urlopen\")\n def test_5xx_is_error(self, mock_urlopen: MagicMock) -> None:\n mock_urlopen.return_value = _mock_response(503)\n issues = check_dependency_health([{\"name\": \"API\", \"url\": \"https://example.com\"}])\n self.assertTrue(any(i[\"level\"] == \"error\" and \"server error\" in i[\"message\"] for i in issues))\n\n @patch(\"dependency_health.urlopen\")\n def test_urlerror_is_unreachable(self, mock_urlopen: MagicMock) -> None:\n mock_urlopen.side_effect = URLError(\"Name or service not known\")\n issues = check_dependency_health([{\"name\": \"Dead\", \"url\": \"https://nope.example\"}])\n self.assertTrue(any(i[\"level\"] == \"error\" and \"unreachable\" in i[\"message\"] for i in issues))\n\n def test_empty_url_is_warning(self) -> None:\n issues = check_dependency_health([{\"name\": \"MissingURL\", \"url\": \"\"}])\n self.assertTrue(any(i[\"level\"] == \"warning\" and \"no URL\" in i[\"message\"] for i in issues))\n\n def test_non_http_url_is_warning(self) -> None:\n issues = check_dependency_health([{\"name\": \"FTP\", \"url\": \"ftp://example.com\"}])\n self.assertTrue(any(i[\"level\"] == \"warning\" and \"non-HTTP\" in i[\"message\"] for i in issues))\n\n\nif __name__ == \"__main__\":\n unittest.main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":2761,"content_sha256":"dc439d9806c15293083c0262cbd8a0f13038dbb7c1bc73fc732d07e4f874fed2"},{"filename":"scripts/tests/test_install_parity.py","content":"\"\"\"Parity tests for the bash \u003c-> PowerShell install-script pairs.\n\nThe repo carries three pairs of install scripts that re-implement the same\nlogic in two shells:\n install-template.sh \u003c-> install-template.ps1\n install-skill.sh \u003c-> install-skill.ps1\n bootstrap.sh \u003c-> bootstrap.ps1\n\nThis file parses both members of each pair and asserts they enumerate the\nsame supported platforms and (where applicable) map each platform to the\nsame install paths. Catches the silent drift that produced the same kind\nof bug fixed in scripts/platforms.py.\n\nPaths are normalized for comparison:\n ``${HOME}`` (sh) and ``$HomeDir`` (ps1) -> ``~``\n Windows ``\\\\`` separator -> ``/``\n\"\"\"\n\nimport re\nimport sys\nimport unittest\nfrom pathlib import Path\n\nROOT = Path(__file__).parent.parent.parent\nsys.path.insert(0, str(ROOT / \"scripts\"))\n\n\n# --- Path normalisation -----------------------------------------------------\n\ndef _norm_sh(path: str) -> str:\n return path.replace(\"${HOME}\", \"~\")\n\n\ndef _norm_ps1(path: str) -> str:\n return path.replace(\"$HomeDir\", \"~\").replace(\"\\\\\", \"/\")\n\n\n# --- install-template.sh / .ps1 parsers ------------------------------------\n\n_SH_CASE_ARM = re.compile(r'^\\s*([\\w-]+)\\)\\s*base=\"([^\"]+)\"\\s*;;')\n_SH_SUPPORTED = re.compile(r'^SUPPORTED_PLATFORMS=\"([^\"]+)\"')\n\n\ndef _parse_install_template_sh(text: str) -> tuple[list[str], dict[str, str], dict[str, str]]:\n supported: list[str] = []\n project: dict[str, str] = {}\n user: dict[str, str] = {}\n section: str | None = None\n for line in text.splitlines():\n m = _SH_SUPPORTED.match(line)\n if m:\n supported = [s.strip() for s in m.group(1).split(\",\")]\n continue\n if \"Project-level: paths are relative\" in line:\n section = \"project\"\n continue\n if \"User-level: paths are under\" in line:\n section = \"user\"\n continue\n arm = _SH_CASE_ARM.match(line)\n if arm and section:\n name, raw = arm.group(1), arm.group(2)\n (project if section == \"project\" else user)[name] = _norm_sh(raw)\n return supported, project, user\n\n\n_PS1_ARM_PROJECT = re.compile(r'^\\s*\"([\\w-]+)\"\\s*\\{\\s*\"([^\"]+)\"\\s*\\}')\n_PS1_ARM_USER = re.compile(r'^\\s*\"([\\w-]+)\"\\s*\\{\\s*Join-Path\\s+\\$HomeDir\\s+\"([^\"]+)\"\\s*\\}')\n_PS1_SUPPORTED_BLOCK = re.compile(r'\\$SupportedPlatforms\\s*=\\s*@\\(([^)]+)\\)', re.DOTALL)\n_PS1_QUOTED = re.compile(r'\"([\\w-]+)\"')\n\n\ndef _parse_install_template_ps1(text: str) -> tuple[list[str], dict[str, str], dict[str, str]]:\n sup_match = _PS1_SUPPORTED_BLOCK.search(text)\n supported = _PS1_QUOTED.findall(sup_match.group(1)) if sup_match else []\n project: dict[str, str] = {}\n user: dict[str, str] = {}\n for line in text.splitlines():\n m = _PS1_ARM_USER.match(line) # check User form first (more specific)\n if m:\n # Join-Path $HomeDir \"X\" -> \"~/X\" (the captured path lacks the home prefix)\n user[m.group(1)] = \"~/\" + _norm_ps1(m.group(2)).lstrip(\"/\")\n continue\n m = _PS1_ARM_PROJECT.match(line)\n if m:\n value = _norm_ps1(m.group(2))\n # Filter out display-name switch blocks (e.g. \"Claude Code\") -- only\n # path-like values (starting with '.') are project install paths.\n if value.startswith(\".\"):\n project[m.group(1)] = value\n return supported, project, user\n\n\n# --- bootstrap.sh / .ps1 parsers -------------------------------------------\n\n_SH_BOOTSTRAP_DETECT = re.compile(r'platforms=\"\\$platforms\\s+([\\w-]+)\"')\n# Match Name= only inside a dict entry (preceded by ';'), so $SkillName = \"...\"\n# variable assignments are NOT caught.\n_PS1_DICT_ENTRY = re.compile(r';\\s*Name\\s*=\\s*\"([\\w-]+)\"')\n\n# Platforms that legitimately appear in only one shell because the detection\n# is OS-specific (AppData/, etc.). Filtered before comparison.\n_BOOTSTRAP_OS_SPECIFIC: set[str] = {\"claude-desktop\"}\n\n\ndef _parse_bootstrap_platforms_sh(text: str) -> set[str]:\n \"\"\"Pull every platform appended to the `platforms` variable during detection.\"\"\"\n return set(_SH_BOOTSTRAP_DETECT.findall(text)) - _BOOTSTRAP_OS_SPECIFIC\n\n\ndef _parse_bootstrap_platforms_ps1(text: str) -> set[str]:\n \"\"\"Pull every platform Name in the detection dictionary.\"\"\"\n return set(_PS1_DICT_ENTRY.findall(text)) - _BOOTSTRAP_OS_SPECIFIC\n\n\n# --- install-skill.sh / .ps1 parsers ---------------------------------------\n# install-skill's paths include skill-name suffix variants we don't want to\n# normalize for parity; we check platform-set parity only.\n\n_SH_INSTALL_SKILL_ECHO = re.compile(r'^\\s*([\\w-]+)\\)\\s*echo\\s+\"')\n\n\ndef _parse_install_skill_platforms_sh(text: str) -> set[str]:\n \"\"\"Union of every platform referenced (detection or case arm).\"\"\"\n detection = set(_SH_BOOTSTRAP_DETECT.findall(text))\n case_arms = set(_SH_INSTALL_SKILL_ECHO.findall(text))\n return detection | case_arms\n\n\n_PS1_INSTALL_SKILL_ARM = re.compile(r'^\\s*\"([\\w-]+)\"\\s*\\{')\n\n\ndef _parse_install_skill_platforms_ps1(text: str) -> set[str]:\n \"\"\"Union of every platform referenced (dict entries + switch arms).\"\"\"\n return set(_PS1_DICT_ENTRY.findall(text)) | set(_PS1_INSTALL_SKILL_ARM.findall(text))\n\n\n# --- Tests -----------------------------------------------------------------\n\n\nclass InstallTemplateParityTest(unittest.TestCase):\n @classmethod\n def setUpClass(cls) -> None:\n sh_text = (ROOT / \"scripts\" / \"install-template.sh\").read_text(encoding=\"utf-8\")\n ps1_text = (ROOT / \"scripts\" / \"install-template.ps1\").read_text(encoding=\"utf-8\")\n cls.sh_sup, cls.sh_project, cls.sh_user = _parse_install_template_sh(sh_text)\n cls.ps1_sup, cls.ps1_project, cls.ps1_user = _parse_install_template_ps1(ps1_text)\n\n def test_supported_platforms_match(self) -> None:\n self.assertEqual(\n sorted(self.sh_sup),\n sorted(self.ps1_sup),\n \"install-template.sh SUPPORTED_PLATFORMS drifted from install-template.ps1 $SupportedPlatforms\",\n )\n\n def test_project_paths_match(self) -> None:\n for plat in self.sh_sup:\n self.assertEqual(\n self.sh_project.get(plat),\n self.ps1_project.get(plat),\n f\"{plat}: project-level install paths drifted between install-template.sh and .ps1\",\n )\n\n def test_user_paths_match(self) -> None:\n for plat in self.sh_sup:\n self.assertEqual(\n self.sh_user.get(plat),\n self.ps1_user.get(plat),\n f\"{plat}: user-level install paths drifted between install-template.sh and .ps1\",\n )\n\n\nclass InstallSkillParityTest(unittest.TestCase):\n \"\"\"Platform-set parity only -- install-skill's paths embed `$name`/`$SkillName`\n in varying positions that aren't worth byte-level parity.\"\"\"\n\n @classmethod\n def setUpClass(cls) -> None:\n cls.sh_plats = _parse_install_skill_platforms_sh(\n (ROOT / \"scripts\" / \"install-skill.sh\").read_text(encoding=\"utf-8\")\n )\n cls.ps1_plats = _parse_install_skill_platforms_ps1(\n (ROOT / \"scripts\" / \"install-skill.ps1\").read_text(encoding=\"utf-8\")\n )\n\n def test_same_platform_set(self) -> None:\n self.assertEqual(\n self.sh_plats,\n self.ps1_plats,\n \"install-skill.sh and install-skill.ps1 enumerate different platforms\",\n )\n\n\nclass BootstrapParityTest(unittest.TestCase):\n @classmethod\n def setUpClass(cls) -> None:\n cls.sh_plats = _parse_bootstrap_platforms_sh(\n (ROOT / \"scripts\" / \"bootstrap.sh\").read_text(encoding=\"utf-8\")\n )\n cls.ps1_plats = _parse_bootstrap_platforms_ps1(\n (ROOT / \"scripts\" / \"bootstrap.ps1\").read_text(encoding=\"utf-8\")\n )\n\n def test_detected_platforms_match(self) -> None:\n \"\"\"Hard parity gate. Filter `_BOOTSTRAP_OS_SPECIFIC` covers Windows-only\n entries (claude-desktop AppData detection).\"\"\"\n self.assertEqual(self.sh_plats, self.ps1_plats)\n\n def test_bootstrap_sh_subset_of_ps1(self) -> None:\n \"\"\"Stronger regression gate: bootstrap.sh's detected platforms must all\n be a subset of bootstrap.ps1's. New drift where bootstrap.sh gains a\n platform that .ps1 lacks would fail here even while the known cursor\n finding is still xfail above.\"\"\"\n self.assertTrue(\n self.sh_plats.issubset(self.ps1_plats),\n f\"bootstrap.sh detects platforms .ps1 does not: {self.sh_plats - self.ps1_plats}\",\n )\n\n\nif __name__ == \"__main__\":\n unittest.main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":8598,"content_sha256":"e3bc6db84a1a11885bb85baf4fc42901381a2c7183173a07c1b713c33f271497"},{"filename":"scripts/tests/test_phase2_integration.py","content":"\"\"\"Integration test for the Phase 2 artifact assessment + inlining.\n\nThis test does not call an LLM. It validates that, given a workflow\ndescription, the detector picks a template and the template's\nsubstitution marker can be replaced with skill-specific instructions\nwithout leaving placeholder data unrenderable.\n\"\"\"\n\nimport sys\nimport unittest\nfrom pathlib import Path\n\nROOT = Path(__file__).parent.parent.parent\nsys.path.insert(0, str(ROOT / \"scripts\"))\n\nfrom artifact_detector import detect_artifact # noqa: E402\n\n\nTEMPLATES_DIR = ROOT / \"references\" / \"artifact-templates\"\nSUBSTITUTION_MARKER = \"/* AGENT_SKILL_DATA */\"\n\n\nclass Phase2InliningTest(unittest.TestCase):\n def _inline(self, template_name: str, instructions: str) -> str:\n template_body = (TEMPLATES_DIR / f\"{template_name}.jsx\").read_text()\n return template_body.replace(SUBSTITUTION_MARKER, f\"/* {instructions} */\")\n\n def test_temporal_inlining(self) -> None:\n template_name = detect_artifact(\"monthly revenue trend\")\n self.assertEqual(template_name, \"line-chart\")\n result = self._inline(template_name, \"data shape: [{period, value}]\")\n self.assertNotIn(SUBSTITUTION_MARKER, result)\n self.assertIn(\"data shape: [{period, value}]\", result)\n self.assertIn(\"export default\", result)\n\n def test_comparative_inlining(self) -> None:\n template_name = detect_artifact(\"revenue by product category\")\n self.assertEqual(template_name, \"bar-chart\")\n result = self._inline(template_name, \"data shape: [{category, value}]\")\n self.assertNotIn(SUBSTITUTION_MARKER, result)\n self.assertIn(\"data shape: [{category, value}]\", result)\n\n def test_kpi_inlining(self) -> None:\n template_name = detect_artifact(\"executive KPI dashboard\")\n self.assertEqual(template_name, \"kpi-cards\")\n result = self._inline(template_name, \"cards: [{label, value, delta}]\")\n self.assertNotIn(SUBSTITUTION_MARKER, result)\n self.assertIn(\"cards:\", result)\n\n def test_tabular_inlining(self) -> None:\n template_name = detect_artifact(\"current inventory levels by sku\")\n self.assertEqual(template_name, \"data-table\")\n result = self._inline(template_name, \"columns: [...]; rows: [...]\")\n self.assertNotIn(SUBSTITUTION_MARKER, result)\n\n def test_negative_case_no_inlining(self) -> None:\n template_name = detect_artifact(\"deploy runbook for the payments service\")\n self.assertIsNone(template_name)\n\n\nif __name__ == \"__main__\":\n unittest.main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":2553,"content_sha256":"c867be9d9636bb855b5df4231a7e1f309b0ae90450f0807ae0025d5eeb59dd5a"},{"filename":"scripts/tests/test_platforms.py","content":"\"\"\"Tests for scripts.platforms — the canonical install-target registry.\n\nTwo layers:\n1. Sanity tests on the registry itself (well-formed, no duplicates).\n2. Drift detection: parse scripts/install-template.sh and assert its\n SUPPORTED_PLATFORMS list and project/user case arms match the registry.\n Catches the situation where someone updates the shell installer but forgets\n to update platforms.py (or vice versa) -- the source of the Windsurf bug\n this module was built to prevent.\n\"\"\"\n\nimport re\nimport sys\nimport unittest\nfrom pathlib import Path\n\nROOT = Path(__file__).parent.parent.parent\nsys.path.insert(0, str(ROOT / \"scripts\"))\n\nfrom platforms import PLATFORMS, get_platform, list_supported_platforms # noqa: E402\n\nINSTALL_TEMPLATE = ROOT / \"scripts\" / \"install-template.sh\"\n\n_CASE_ARM = re.compile(r'^\\s*([\\w-]+)\\)\\s*base=\"([^\"]+)\"\\s*;;')\n_SUPPORTED = re.compile(r'^SUPPORTED_PLATFORMS=\"([^\"]+)\"')\n\n\ndef _parse_install_template() -> tuple[list[str], dict[str, str], dict[str, str]]:\n \"\"\"Return (supported, project_arms, user_arms) parsed from install-template.sh.\"\"\"\n text = INSTALL_TEMPLATE.read_text(encoding=\"utf-8\")\n supported: list[str] = []\n project_arms: dict[str, str] = {}\n user_arms: dict[str, str] = {}\n section = None # \"project\" | \"user\" | None\n for line in text.splitlines():\n sup = _SUPPORTED.match(line)\n if sup:\n supported = [s.strip() for s in sup.group(1).split(\",\")]\n continue\n if \"Project-level: paths are relative\" in line:\n section = \"project\"\n continue\n if \"User-level: paths are under\" in line:\n section = \"user\"\n continue\n arm = _CASE_ARM.match(line)\n if arm and section:\n name, path = arm.group(1), arm.group(2)\n target = project_arms if section == \"project\" else user_arms\n target[name] = path.replace(\"${HOME}\", \"~\")\n return supported, project_arms, user_arms\n\n\nclass RegistrySanityTest(unittest.TestCase):\n def test_at_least_one_platform(self) -> None:\n self.assertGreater(len(PLATFORMS), 0)\n\n def test_names_unique(self) -> None:\n names = [p.name for p in PLATFORMS]\n self.assertEqual(len(names), len(set(names)))\n\n def test_get_platform_round_trip(self) -> None:\n for p in PLATFORMS:\n self.assertEqual(get_platform(p.name), p)\n\n def test_unknown_returns_none(self) -> None:\n self.assertIsNone(get_platform(\"does-not-exist\"))\n\n def test_user_and_project_paths_non_empty(self) -> None:\n for p in PLATFORMS:\n self.assertTrue(p.user_path, f\"{p.name} missing user_path\")\n self.assertTrue(p.project_path, f\"{p.name} missing project_path\")\n\n\nclass InstallTemplateDriftTest(unittest.TestCase):\n @classmethod\n def setUpClass(cls) -> None:\n cls.supported, cls.project_arms, cls.user_arms = _parse_install_template()\n\n def test_supported_list_matches_registry(self) -> None:\n self.assertEqual(\n sorted(self.supported),\n sorted(list_supported_platforms()),\n \"install-template.sh SUPPORTED_PLATFORMS drifted from scripts/platforms.py\",\n )\n\n def test_project_paths_match_registry(self) -> None:\n for p in PLATFORMS:\n self.assertEqual(\n self.project_arms.get(p.name),\n p.project_path,\n f\"{p.name}: install-template.sh project path drifted from platforms.py\",\n )\n\n def test_user_paths_match_registry(self) -> None:\n for p in PLATFORMS:\n self.assertEqual(\n self.user_arms.get(p.name),\n p.user_path,\n f\"{p.name}: install-template.sh user path drifted from platforms.py\",\n )\n\n\nif __name__ == \"__main__\":\n unittest.main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3829,"content_sha256":"44d83bff4b2e94cbd8355ab1215f5b0eb2cbdf20d0ce4ac72d7a7193bf6e2f71"},{"filename":"scripts/tests/test_review_staleness.py","content":"\"\"\"Tests for scripts.review_staleness — date logic and review-due decisions.\n\nAll tests inject `today` to keep them deterministic; no real clock, no git.\n\"\"\"\n\nimport sys\nimport unittest\nfrom datetime import date\nfrom pathlib import Path\n\nROOT = Path(__file__).parent.parent.parent\nsys.path.insert(0, str(ROOT / \"scripts\"))\n\nfrom review_staleness import ( # noqa: E402\n DEFAULT_REVIEW_INTERVAL_DAYS,\n STALENESS_WARNING_THRESHOLD_DAYS,\n _parse_date,\n check_review_staleness,\n)\nfrom skill_document import SkillDoc # noqa: E402\n\n\ndef _doc(metadata_lines: list[str] = ()) -> SkillDoc:\n lines = [\"---\", \"name: demo-skill\", \"description: x\", \"metadata:\"]\n lines += [f\" {ml}\" for ml in metadata_lines]\n lines += [\"---\", \"body\"]\n return SkillDoc.from_text(\"\\n\".join(lines))\n\n\nclass ParseDateTest(unittest.TestCase):\n def test_valid_date(self) -> None:\n self.assertEqual(_parse_date(\"2026-01-15\"), date(2026, 1, 15))\n\n def test_empty_or_garbage(self) -> None:\n self.assertIsNone(_parse_date(\"\"))\n self.assertIsNone(_parse_date(\"not-a-date\"))\n self.assertIsNone(_parse_date(\"2026/01/15\"))\n\n def test_invalid_month(self) -> None:\n self.assertIsNone(_parse_date(\"2026-13-01\"))\n\n\nclass CheckReviewStalenessTest(unittest.TestCase):\n def _check(self, doc: SkillDoc, today: date, git: date | None = None):\n return check_review_staleness(doc, git, today=today)\n\n def test_recent_review_is_fresh(self) -> None:\n doc = _doc([\"last_reviewed: 2026-05-01\", \"review_interval_days: 90\"])\n issues, status, days, src = self._check(doc, today=date(2026, 5, 29))\n self.assertEqual(status, \"fresh\")\n self.assertEqual(src, \"last_reviewed\")\n self.assertEqual(days, 28)\n self.assertFalse(any(i[\"level\"] == \"error\" for i in issues))\n\n def test_old_review_is_overdue(self) -> None:\n doc = _doc([\"last_reviewed: 2025-01-01\", \"review_interval_days: 90\"])\n issues, status, _days, _src = self._check(doc, today=date(2026, 5, 29))\n self.assertEqual(status, \"overdue\")\n self.assertTrue(any(i[\"level\"] == \"error\" for i in issues))\n\n def test_due_soon_window(self) -> None:\n # last_reviewed = today - (warning+5); interval = warning+30 -> due_soon\n warning = STALENESS_WARNING_THRESHOLD_DAYS\n last = date(2026, 1, 1)\n today = date(2026, 1, 1).replace(day=1)\n # construct so today is past warning_date but before deadline\n from datetime import timedelta\n today = last + timedelta(days=warning + 5)\n doc = _doc([f\"last_reviewed: {last}\", f\"review_interval_days: {warning + 30}\"])\n issues, status, _days, _src = self._check(doc, today=today)\n self.assertEqual(status, \"due_soon\")\n self.assertTrue(any(i[\"level\"] == \"warning\" for i in issues))\n\n def test_falls_back_to_git_date(self) -> None:\n doc = _doc([]) # no temporal metadata at all\n issues, status, _days, src = self._check(\n doc, today=date(2026, 5, 29), git=date(2026, 4, 15)\n )\n self.assertEqual(src, \"git_commit\")\n self.assertEqual(status, \"fresh\") # within default 90 days\n\n def test_no_dates_at_all_is_unknown(self) -> None:\n doc = _doc([])\n issues, status, _days, src = self._check(doc, today=date(2026, 5, 29), git=None)\n self.assertEqual(status, \"unknown\")\n self.assertEqual(src, \"unknown\")\n self.assertTrue(any(\"No review date available\" in i[\"message\"] for i in issues))\n\n def test_invalid_date_format_is_warning(self) -> None:\n doc = _doc([\"last_reviewed: tomorrow\"])\n issues, _status, _days, _src = self._check(doc, today=date(2026, 5, 29))\n self.assertTrue(any(\"Invalid 'metadata.last_reviewed' date format\" in i[\"message\"] for i in issues))\n\n def test_uses_default_interval_when_absent(self) -> None:\n doc = _doc([\"last_reviewed: 2026-01-01\"])\n _issues, _status, _days, _src = self._check(doc, today=date(2026, 2, 1))\n # default 90 days; just verify it runs and uses the default\n self.assertEqual(DEFAULT_REVIEW_INTERVAL_DAYS, 90)\n\n def test_no_temporal_metadata_info(self) -> None:\n doc = _doc([])\n issues, _status, _days, _src = self._check(doc, today=date(2026, 5, 29), git=date(2026, 5, 1))\n self.assertTrue(any(\"No temporal metadata found\" in i[\"message\"] for i in issues))\n\n\nif __name__ == \"__main__\":\n unittest.main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":4460,"content_sha256":"68f66e3f7ab8d0e585e06491af0afdd918eddffde6af7f5c8bc9283d5ce22c66"},{"filename":"scripts/tests/test_run_evals.py","content":"\"\"\"Unit tests for scripts.run_evals_template (shipped as run_evals.py).\n\nCovers the two modes the runner exposes: --validate (shape checking) and the\ndefault run (deterministic command checks against the golden baseline).\n\"\"\"\n\nimport json\nimport sys\nimport tempfile\nimport unittest\nfrom pathlib import Path\n\nROOT = Path(__file__).parent.parent.parent\nsys.path.insert(0, str(ROOT / \"scripts\"))\n\nfrom run_evals_template import ( # noqa: E402\n find_spec,\n llm_judge_criteria,\n main,\n parse_spec,\n run_command_checks,\n validate_spec,\n)\n\n# Portable command checks (no jq dependency). {output} binds to the expected\n# baseline file in default run mode.\nPASS_CMD = \"test -s {output}\" # expected file exists and is non-empty\nFAIL_CMD = \"grep -q __NO_SUCH_TOKEN__ {output}\" # token never present -> exit 1\n\nWELL_FORMED_CRITERIA = [\n {\"id\": \"non-empty\", \"text\": \"Output is non-empty\", \"type\": \"command\", \"cmd\": PASS_CMD},\n {\"id\": \"has-totals\", \"text\": \"Each region has a total\", \"type\": \"llm-judge\"},\n]\n\n\ndef _make_skill(tmp: Path, criteria: list[dict], golden: list[dict]) -> Path:\n \"\"\"Create a minimal skill dir with an eval spec + golden files; return it.\"\"\"\n skill = tmp / \"demo-skill\"\n (skill / \"scripts\").mkdir(parents=True)\n evals = skill / \"evals\"\n evals.mkdir()\n for case in golden:\n case_dir = evals / \"golden\" / case[\"id\"]\n case_dir.mkdir(parents=True)\n if case.get(\"input\"):\n (evals / case[\"input\"]).write_text(\"col\\n1\\n\", encoding=\"utf-8\")\n if case.get(\"expected\"):\n (evals / case[\"expected\"]).write_text('{\"ok\": true}\\n', encoding=\"utf-8\")\n spec = {\"skill\": \"demo-skill\", \"criteria\": criteria, \"golden\": golden}\n body = \"# Eval Spec: demo-skill\\n\\nprose\\n\\n```json\\n\" + json.dumps(spec, indent=2) + \"\\n```\\n\"\n (evals / \"demo-skill.eval.md\").write_text(body, encoding=\"utf-8\")\n return skill\n\n\ndef _three_golden(with_expected: bool = True) -> list[dict]:\n cases = []\n for i in (1, 2, 3):\n case = {\"id\": f\"case-{i}\", \"input\": f\"golden/case-{i}/input.csv\", \"split\": \"val\"}\n if with_expected:\n case[\"expected\"] = f\"golden/case-{i}/expected.json\"\n else:\n case[\"expected\"] = None\n case[\"expected_status\"] = \"pending-first-green\"\n cases.append(case)\n return cases\n\n\nclass ValidateSpecTest(unittest.TestCase):\n def setUp(self) -> None:\n self._tmp = tempfile.TemporaryDirectory()\n self.tmp = Path(self._tmp.name)\n\n def tearDown(self) -> None:\n self._tmp.cleanup()\n\n def test_well_formed_spec_has_no_errors(self) -> None:\n skill = _make_skill(self.tmp, WELL_FORMED_CRITERIA, _three_golden())\n spec = parse_spec(find_spec(skill))\n self.assertEqual(validate_spec(spec, skill), [])\n\n def test_non_binary_grader_type_is_rejected(self) -> None:\n criteria = [{\"id\": \"x\", \"text\": \"rate it\", \"type\": \"scale-1-5\"}]\n skill = _make_skill(self.tmp, criteria, _three_golden())\n spec = parse_spec(find_spec(skill))\n self.assertTrue(any(\"type\" in e for e in validate_spec(spec, skill)))\n\n def test_command_criterion_without_cmd_is_rejected(self) -> None:\n criteria = [{\"id\": \"x\", \"text\": \"valid json\", \"type\": \"command\"}]\n skill = _make_skill(self.tmp, criteria, _three_golden())\n spec = parse_spec(find_spec(skill))\n self.assertTrue(any(\"cmd\" in e for e in validate_spec(spec, skill)))\n\n def test_fewer_than_three_golden_cases_is_rejected(self) -> None:\n skill = _make_skill(self.tmp, WELL_FORMED_CRITERIA, _three_golden()[:2])\n spec = parse_spec(find_spec(skill))\n self.assertTrue(any(\"golden cases\" in e for e in validate_spec(spec, skill)))\n\n def test_null_expected_without_pending_flag_is_rejected(self) -> None:\n golden = _three_golden(with_expected=False)\n del golden[0][\"expected_status\"] # null expected, not flagged\n skill = _make_skill(self.tmp, WELL_FORMED_CRITERIA, golden)\n spec = parse_spec(find_spec(skill))\n self.assertTrue(any(\"pending-first-green\" in e for e in validate_spec(spec, skill)))\n\n def test_pending_first_green_is_valid(self) -> None:\n skill = _make_skill(self.tmp, WELL_FORMED_CRITERIA, _three_golden(with_expected=False))\n spec = parse_spec(find_spec(skill))\n self.assertEqual(validate_spec(spec, skill), [])\n\n\nclass RunCommandChecksTest(unittest.TestCase):\n def setUp(self) -> None:\n self._tmp = tempfile.TemporaryDirectory()\n self.tmp = Path(self._tmp.name)\n\n def tearDown(self) -> None:\n self._tmp.cleanup()\n\n def test_passing_command_check_zero_failures(self) -> None:\n skill = _make_skill(self.tmp, WELL_FORMED_CRITERIA, _three_golden())\n spec = parse_spec(find_spec(skill))\n result = run_command_checks(spec, skill)\n self.assertEqual(result[\"failed\"], 0)\n self.assertEqual(result[\"passed\"], 3) # one command criterion x 3 cases\n\n def test_failing_command_check_is_counted(self) -> None:\n criteria = [{\"id\": \"miss\", \"text\": \"has token\", \"type\": \"command\", \"cmd\": FAIL_CMD}]\n skill = _make_skill(self.tmp, criteria, _three_golden())\n spec = parse_spec(find_spec(skill))\n result = run_command_checks(spec, skill)\n self.assertEqual(result[\"failed\"], 3)\n\n def test_llm_judge_not_run_as_command(self) -> None:\n criteria = [{\"id\": \"j\", \"text\": \"tone is right\", \"type\": \"llm-judge\"}]\n skill = _make_skill(self.tmp, criteria, _three_golden())\n spec = parse_spec(find_spec(skill))\n result = run_command_checks(spec, skill)\n self.assertEqual(result[\"passed\"], 0)\n self.assertEqual(result[\"failed\"], 0)\n self.assertEqual(len(llm_judge_criteria(spec)), 1)\n\n def test_pending_first_green_case_is_skipped_not_failed(self) -> None:\n criteria = [{\"id\": \"non-empty\", \"text\": \"non-empty\", \"type\": \"command\", \"cmd\": PASS_CMD}]\n skill = _make_skill(self.tmp, criteria, _three_golden(with_expected=False))\n spec = parse_spec(find_spec(skill))\n result = run_command_checks(spec, skill)\n self.assertEqual(result[\"failed\"], 0)\n self.assertEqual(result[\"skipped\"], 3)\n\n\nclass MainExitCodeTest(unittest.TestCase):\n def setUp(self) -> None:\n self._tmp = tempfile.TemporaryDirectory()\n self.tmp = Path(self._tmp.name)\n\n def tearDown(self) -> None:\n self._tmp.cleanup()\n\n def test_validate_well_formed_exits_zero(self) -> None:\n skill = _make_skill(self.tmp, WELL_FORMED_CRITERIA, _three_golden())\n self.assertEqual(main([str(skill), \"--validate\"]), 0)\n\n def test_validate_malformed_exits_one(self) -> None:\n criteria = [{\"id\": \"x\", \"text\": \"valid json\", \"type\": \"command\"}] # no cmd\n skill = _make_skill(self.tmp, criteria, _three_golden())\n self.assertEqual(main([str(skill), \"--validate\"]), 1)\n\n def test_run_all_pass_exits_zero(self) -> None:\n skill = _make_skill(self.tmp, WELL_FORMED_CRITERIA, _three_golden())\n self.assertEqual(main([str(skill)]), 0)\n\n def test_run_with_failure_exits_one(self) -> None:\n criteria = [{\"id\": \"miss\", \"text\": \"has token\", \"type\": \"command\", \"cmd\": FAIL_CMD}]\n skill = _make_skill(self.tmp, criteria, _three_golden())\n self.assertEqual(main([str(skill)]), 1)\n\n def test_missing_spec_exits_two(self) -> None:\n empty = self.tmp / \"no-evals-skill\"\n empty.mkdir()\n self.assertEqual(main([str(empty)]), 2)\n\n\nif __name__ == \"__main__\":\n unittest.main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":7593,"content_sha256":"682202562ed8cb1faebd0c568805aaeb8d854f5c8a71ab32b1777c43daf6797e"},{"filename":"scripts/tests/test_schema_drift.py","content":"\"\"\"Tests for scripts.schema_drift — expectations parsing + drift detection (mocked).\"\"\"\n\nimport json\nimport sys\nimport unittest\nfrom pathlib import Path\nfrom unittest.mock import MagicMock, patch\nfrom urllib.error import URLError\n\nROOT = Path(__file__).parent.parent.parent\nsys.path.insert(0, str(ROOT / \"scripts\"))\n\nfrom schema_drift import check_schema_drift, parse_schema_expectations # noqa: E402\nfrom skill_document import SkillDoc # noqa: E402\n\n\ndef _doc_with_expectations() -> SkillDoc:\n return SkillDoc.from_text(\n \"---\\n\"\n \"name: demo-skill\\n\"\n \"description: x\\n\"\n \"metadata:\\n\"\n \" schema_expectations:\\n\"\n \" - url: https://api.example.com/v1/data\\n\"\n \" method: GET\\n\"\n \" expected_keys:\\n\"\n \" - id\\n\"\n \" - name\\n\"\n \" - value\\n\"\n \" - url: https://api2.example.com/v2/items\\n\"\n \" expected_keys:\\n\"\n \" - sku\\n\"\n \" - price\\n\"\n \"---\\n\"\n \"body\\n\"\n )\n\n\ndef _mock_json_response(payload) -> MagicMock:\n body = json.dumps(payload).encode(\"utf-8\")\n resp = MagicMock()\n resp.read.return_value = body\n cm = MagicMock()\n cm.__enter__.return_value = resp\n cm.__exit__.return_value = None\n return cm\n\n\nclass ParseExpectationsTest(unittest.TestCase):\n def test_parses_two_entries(self) -> None:\n exps = parse_schema_expectations(_doc_with_expectations())\n self.assertEqual(len(exps), 2)\n self.assertEqual(exps[0][\"url\"], \"https://api.example.com/v1/data\")\n self.assertEqual(exps[0][\"method\"], \"GET\")\n self.assertEqual(sorted(exps[0][\"expected_keys\"]), [\"id\", \"name\", \"value\"])\n self.assertEqual(exps[1][\"url\"], \"https://api2.example.com/v2/items\")\n self.assertEqual(sorted(exps[1][\"expected_keys\"]), [\"price\", \"sku\"])\n\n def test_no_frontmatter_returns_empty(self) -> None:\n doc = SkillDoc.from_text(\"no frontmatter at all\")\n self.assertEqual(parse_schema_expectations(doc), [])\n\n def test_no_schema_expectations_returns_empty(self) -> None:\n doc = SkillDoc.from_text(\"---\\nname: x\\ndescription: y\\nmetadata:\\n author: A\\n---\\nbody\\n\")\n self.assertEqual(parse_schema_expectations(doc), [])\n\n\nclass CheckSchemaDriftTest(unittest.TestCase):\n EXP = [{\"url\": \"https://api.example.com/v1\", \"method\": \"GET\", \"expected_keys\": [\"id\", \"name\"]}]\n\n @patch(\"schema_drift.urlopen\")\n def test_exact_match_reports_match(self, mock_urlopen: MagicMock) -> None:\n mock_urlopen.return_value = _mock_json_response({\"id\": 1, \"name\": \"x\"})\n issues = check_schema_drift(self.EXP)\n self.assertTrue(any(\"Schema matches\" in i[\"message\"] for i in issues))\n self.assertFalse(any(i[\"level\"] == \"error\" for i in issues))\n\n @patch(\"schema_drift.urlopen\")\n def test_missing_key_is_error(self, mock_urlopen: MagicMock) -> None:\n mock_urlopen.return_value = _mock_json_response({\"id\": 1}) # missing 'name'\n issues = check_schema_drift(self.EXP)\n self.assertTrue(any(i[\"level\"] == \"error\" and \"missing keys\" in i[\"message\"] for i in issues))\n\n @patch(\"schema_drift.urlopen\")\n def test_new_key_is_info(self, mock_urlopen: MagicMock) -> None:\n mock_urlopen.return_value = _mock_json_response({\"id\": 1, \"name\": \"x\", \"added\": True})\n issues = check_schema_drift(self.EXP)\n self.assertTrue(any(i[\"level\"] == \"info\" and \"new keys\" in i[\"message\"] for i in issues))\n\n @patch(\"schema_drift.urlopen\")\n def test_non_json_body_is_error(self, mock_urlopen: MagicMock) -> None:\n resp = MagicMock()\n resp.read.return_value = b\"\u003chtml>not json\u003c/html>\"\n cm = MagicMock()\n cm.__enter__.return_value = resp\n cm.__exit__.return_value = None\n mock_urlopen.return_value = cm\n issues = check_schema_drift(self.EXP)\n self.assertTrue(any(i[\"level\"] == \"error\" and \"not valid JSON\" in i[\"message\"] for i in issues))\n\n @patch(\"schema_drift.urlopen\")\n def test_urlerror_is_error(self, mock_urlopen: MagicMock) -> None:\n mock_urlopen.side_effect = URLError(\"Unreachable\")\n issues = check_schema_drift(self.EXP)\n self.assertTrue(any(i[\"level\"] == \"error\" and \"Cannot reach\" in i[\"message\"] for i in issues))\n\n def test_non_http_url_is_warning(self) -> None:\n issues = check_schema_drift([{\"url\": \"ftp://x\", \"method\": \"GET\", \"expected_keys\": [\"a\"]}])\n self.assertTrue(any(i[\"level\"] == \"warning\" and \"non-HTTP\" in i[\"message\"] for i in issues))\n\n def test_no_expected_keys_is_skipped(self) -> None:\n issues = check_schema_drift([{\"url\": \"https://x\", \"method\": \"GET\", \"expected_keys\": []}])\n self.assertTrue(any(i[\"level\"] == \"info\" and \"No expected_keys\" in i[\"message\"] for i in issues))\n\n\nif __name__ == \"__main__\":\n unittest.main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":4872,"content_sha256":"5c34c684d96c07857e1498e0ccbf87190a08de3ca0f06c42aec316b67a8cd56b"},{"filename":"scripts/tests/test_skill_document.py","content":"\"\"\"Characterization tests for scripts.skill_document.SkillDoc.\n\nThese pin the SKILL.md parsing behavior that validate.py / staleness_check.py /\nskill_registry.py / export_utils.py all depend on, so the consolidation onto a\nsingle SkillDoc module cannot silently change it. Edge cases the rewrite must\npreserve: YAML block scalars, the metadata indentation-walk exit condition, and\nlist-of-objects parsing.\n\"\"\"\n\nimport sys\nimport tempfile\nimport unittest\nfrom pathlib import Path\n\nROOT = Path(__file__).parent.parent.parent\nsys.path.insert(0, str(ROOT / \"scripts\"))\n\nfrom skill_document import SkillDoc # noqa: E402\n\nFULL = \"\"\"---\nname: demo-skill\ndescription: >-\n A multi-line folded description\n that spans two lines.\nlicense: MIT\nmetadata:\n author: Jane Doe\n version: 1.2.0\n created: 2026-05-29\n dependencies:\n - url: https://api.example.com/v1\n name: Example API\n type: api\n - url: https://api2.example.com\n name: Second\n type: api\ntop_after_meta: sibling\n---\n# /demo-skill — Body\n\nBody text with [a link](references/guide.md) and [external](https://x.com).\n\"\"\"\n\n\nclass FrontmatterBodyTest(unittest.TestCase):\n def test_splits_frontmatter_and_body(self) -> None:\n doc = SkillDoc.from_text(FULL)\n self.assertIn(\"name: demo-skill\", doc.frontmatter)\n self.assertTrue(doc.body.startswith(\"# /demo-skill\"))\n\n def test_no_frontmatter_returns_none(self) -> None:\n doc = SkillDoc.from_text(\"no frontmatter here\")\n self.assertIsNone(doc.frontmatter)\n self.assertIsNone(doc.name)\n self.assertEqual(doc.metadata, {})\n self.assertEqual(doc.list_of_objects(\"metadata\", \"dependencies\"), [])\n self.assertIsNone(doc.field(\"name\"))\n self.assertFalse(doc.has_field(\"name\"))\n\n def test_unclosed_frontmatter_returns_none(self) -> None:\n doc = SkillDoc.from_text(\"---\\nname: x\\nno closing delimiter\\n\")\n self.assertIsNone(doc.frontmatter)\n\n\nclass ScalarFieldTest(unittest.TestCase):\n def setUp(self) -> None:\n self.doc = SkillDoc.from_text(FULL)\n\n def test_inline_fields(self) -> None:\n self.assertEqual(self.doc.name, \"demo-skill\")\n self.assertEqual(self.doc.license, \"MIT\")\n self.assertEqual(self.doc.field(\"license\"), \"MIT\")\n\n def test_folded_block_scalar_is_joined(self) -> None:\n self.assertEqual(\n self.doc.description,\n \"A multi-line folded description that spans two lines.\",\n )\n\n def test_has_field(self) -> None:\n self.assertTrue(self.doc.has_field(\"metadata\"))\n self.assertFalse(self.doc.has_field(\"nonexistent\"))\n\n def test_block_scalar_indicator_variants(self) -> None:\n for indicator in (\">-\", \"|-\", \">\", \"|\", \">+\", \"|+\"):\n text = f\"---\\ndescription: {indicator}\\n one\\n two\\nname: x\\n---\\nbody\\n\"\n self.assertEqual(SkillDoc.from_text(text).description, \"one two\", indicator)\n\n def test_empty_block_scalar(self) -> None:\n text = \"---\\ndescription: >-\\nname: x\\n---\\nbody\\n\"\n self.assertEqual(SkillDoc.from_text(text).description, \"\")\n\n\nclass SubfieldTest(unittest.TestCase):\n def setUp(self) -> None:\n self.doc = SkillDoc.from_text(FULL)\n\n def test_subfield_values(self) -> None:\n self.assertEqual(self.doc.subfield(\"metadata\", \"author\"), \"Jane Doe\")\n self.assertEqual(self.doc.subfield(\"metadata\", \"version\"), \"1.2.0\")\n self.assertEqual(self.doc.subfield(\"metadata\", \"created\"), \"2026-05-29\")\n\n def test_has_subfield(self) -> None:\n self.assertTrue(self.doc.has_subfield(\"metadata\", \"author\"))\n self.assertFalse(self.doc.has_subfield(\"metadata\", \"missing\"))\n\n def test_sibling_of_parent_is_not_a_subfield(self) -> None:\n # top_after_meta is a sibling of metadata, not a child — the walk must\n # exit the metadata block on the unindented line.\n self.assertIsNone(self.doc.subfield(\"metadata\", \"top_after_meta\"))\n self.assertFalse(self.doc.has_subfield(\"metadata\", \"top_after_meta\"))\n\n def test_metadata_dict_matches_subfield(self) -> None:\n meta = self.doc.metadata\n self.assertEqual(meta.get(\"author\"), self.doc.subfield(\"metadata\", \"author\"))\n self.assertEqual(meta.get(\"version\"), \"1.2.0\")\n self.assertEqual(meta.get(\"created\"), \"2026-05-29\")\n # nested list key has no inline scalar value -> excluded\n self.assertNotIn(\"dependencies\", meta)\n\n\nclass ListOfObjectsTest(unittest.TestCase):\n def test_parses_list_of_dicts(self) -> None:\n doc = SkillDoc.from_text(FULL)\n deps = doc.list_of_objects(\"metadata\", \"dependencies\")\n self.assertEqual(\n deps,\n [\n {\"url\": \"https://api.example.com/v1\", \"name\": \"Example API\", \"type\": \"api\"},\n {\"url\": \"https://api2.example.com\", \"name\": \"Second\", \"type\": \"api\"},\n ],\n )\n\n def test_missing_list_returns_empty(self) -> None:\n doc = SkillDoc.from_text(FULL)\n self.assertEqual(doc.list_of_objects(\"metadata\", \"schema_expectations\"), [])\n\n\nclass FromPathTest(unittest.TestCase):\n def test_from_path_reads_skill_md(self) -> None:\n with tempfile.TemporaryDirectory() as tmp:\n p = Path(tmp) / \"SKILL.md\"\n p.write_text(FULL, encoding=\"utf-8\")\n doc = SkillDoc.from_path(p)\n self.assertEqual(doc.name, \"demo-skill\")\n\n\nif __name__ == \"__main__\":\n unittest.main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":5452,"content_sha256":"80466d6d9cb6dbdfd3f71b4f551712419a076e7ccfa73f4e37782529a08259f1"},{"filename":"scripts/tests/test_template_structure.py","content":"\"\"\"Structural tests for the four artifact JSX templates.\n\nThese do not parse JSX (no JSX parser in stdlib). Instead they assert\nthat each template file exists, contains the substitution marker, and\nreferences the expected library (recharts/shadcn) so an agent wiring\nPhase 2 can rely on a stable shape.\n\"\"\"\n\nimport unittest\nfrom pathlib import Path\n\nTEMPLATES_DIR = Path(__file__).parent.parent.parent / \"references\" / \"artifact-templates\"\nSUBSTITUTION_MARKER = \"/* AGENT_SKILL_DATA */\"\n\n\nclass LineChartTemplateTest(unittest.TestCase):\n def setUp(self) -> None:\n self.path = TEMPLATES_DIR / \"line-chart.jsx\"\n\n def test_template_file_exists(self) -> None:\n self.assertTrue(self.path.exists(), f\"Missing: {self.path}\")\n\n def test_template_has_substitution_marker(self) -> None:\n content = self.path.read_text()\n self.assertIn(SUBSTITUTION_MARKER, content)\n\n def test_template_references_recharts(self) -> None:\n content = self.path.read_text()\n self.assertIn(\"recharts\", content)\n\n def test_template_exports_default_component(self) -> None:\n content = self.path.read_text()\n self.assertIn(\"export default\", content)\n\n\nclass BarChartTemplateTest(unittest.TestCase):\n def setUp(self) -> None:\n self.path = TEMPLATES_DIR / \"bar-chart.jsx\"\n\n def test_template_file_exists(self) -> None:\n self.assertTrue(self.path.exists(), f\"Missing: {self.path}\")\n\n def test_template_has_substitution_marker(self) -> None:\n self.assertIn(SUBSTITUTION_MARKER, self.path.read_text())\n\n def test_template_references_recharts(self) -> None:\n self.assertIn(\"recharts\", self.path.read_text())\n\n def test_template_exports_default_component(self) -> None:\n self.assertIn(\"export default\", self.path.read_text())\n\n\nclass KpiCardsTemplateTest(unittest.TestCase):\n def setUp(self) -> None:\n self.path = TEMPLATES_DIR / \"kpi-cards.jsx\"\n\n def test_template_file_exists(self) -> None:\n self.assertTrue(self.path.exists(), f\"Missing: {self.path}\")\n\n def test_template_has_substitution_marker(self) -> None:\n self.assertIn(SUBSTITUTION_MARKER, self.path.read_text())\n\n def test_template_exports_default_component(self) -> None:\n self.assertIn(\"export default\", self.path.read_text())\n\n\nclass DataTableTemplateTest(unittest.TestCase):\n def setUp(self) -> None:\n self.path = TEMPLATES_DIR / \"data-table.jsx\"\n\n def test_template_file_exists(self) -> None:\n self.assertTrue(self.path.exists(), f\"Missing: {self.path}\")\n\n def test_template_has_substitution_marker(self) -> None:\n self.assertIn(SUBSTITUTION_MARKER, self.path.read_text())\n\n def test_template_exports_default_component(self) -> None:\n self.assertIn(\"export default\", self.path.read_text())\n\n\nif __name__ == \"__main__\":\n unittest.main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":2864,"content_sha256":"2d93ac80a672c58c62523dd213ff656cc4126b9ef01943ee91cbcd7dcfba49cd"},{"filename":"scripts/tests/test_v4_regression.py","content":"\"\"\"v4 regression suite.\n\nConfirms that v4 skill files still parse cleanly under the v6 validator\nand that the v6 artifact detector does not crash when given v4-era\nworkflow descriptions.\n\nThis is not a behavioral regression test (no LLM invocation). It is a\nstructural regression test that catches accidental schema breaks or\ndetector crashes against the 180+ community forks already running v4\nskills.\n\"\"\"\n\nimport json\nimport sys\nimport unittest\nfrom pathlib import Path\n\nROOT = Path(__file__).parent.parent.parent\nsys.path.insert(0, str(ROOT / \"scripts\"))\n\nfrom artifact_detector import detect_artifact # noqa: E402\nfrom validate import validate_skill # noqa: E402\n\nFIXTURES_PATH = ROOT / \"scripts\" / \"tests\" / \"fixtures\" / \"v4_skills_regression.json\"\n\n# Templates the detector is allowed to return. None means \"no artifact\" —\n# expected for v4 workflow-style skills with no visual output.\nALLOWED_TEMPLATES = {\"line-chart\", \"bar-chart\", \"kpi-cards\", \"data-table\", None}\n\n\nclass V4RegressionTest(unittest.TestCase):\n def setUp(self) -> None:\n self.skills = json.loads(FIXTURES_PATH.read_text())\n\n def test_validator_accepts_existing_v4_skills(self) -> None:\n \"\"\"validate_skill must return valid=True for every real v4 skill.\n\n The validator returns a dict ``{valid: bool, errors: list, warnings: list}``.\n Warnings (missing -skill suffix, missing license, missing AGENTS.md, etc.)\n do NOT break v4 skills — only ``errors`` would.\n \"\"\"\n failures: list[str] = []\n real_skills_tested = 0\n\n for skill in self.skills:\n if skill.get(\"synthetic\"):\n continue\n path = skill.get(\"path\")\n if not path:\n continue\n\n skill_dir = ROOT / Path(path).parent\n if not skill_dir.exists():\n failures.append(f\"{skill['id']}: missing path {skill_dir}\")\n continue\n\n real_skills_tested += 1\n try:\n result = validate_skill(str(skill_dir))\n except Exception as exc: # noqa: BLE001\n failures.append(f\"{skill['id']}: validator raised: {exc!r}\")\n continue\n\n if not isinstance(result, dict):\n failures.append(\n f\"{skill['id']}: validator returned non-dict ({type(result).__name__})\"\n )\n continue\n\n if not result.get(\"valid\", False):\n failures.append(\n f\"{skill['id']}: validate_skill returned valid=False; \"\n f\"errors={result.get('errors')}\"\n )\n\n self.assertEqual(\n failures, [],\n \"Existing v4 skills must still validate under the v6 validator:\\n\"\n + \"\\n\".join(failures),\n )\n self.assertGreater(\n real_skills_tested, 0,\n \"Regression suite must exercise at least one real v4 skill on disk\",\n )\n\n def test_detector_does_not_crash_on_v4_descriptions(self) -> None:\n \"\"\"detect_artifact must handle every v4 workflow description without crashing.\n\n It is acceptable for the detector to return None (no artifact) — most\n v4 skills are workflow-style with no visual output. What is NOT\n acceptable is an exception, which would break the v6 Phase 2 pipeline\n for users upgrading existing v4 forks.\n \"\"\"\n for skill in self.skills:\n description = skill[\"description\"]\n try:\n result = detect_artifact(description)\n except Exception as exc: # noqa: BLE001\n self.fail(f\"detector crashed on {skill['id']!r}: {exc!r}\")\n\n self.assertIn(\n result, ALLOWED_TEMPLATES,\n f\"{skill['id']}: detector returned unexpected value {result!r}\",\n )\n\n\nif __name__ == \"__main__\":\n unittest.main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3913,"content_sha256":"96ad1de19ec86d41780d3b0fecbfa0bbc31370ece4e4d7a015b74b5f95f62f8e"},{"filename":"scripts/validate.py","content":"#!/usr/bin/env python3\n\"\"\"\nSpec Compliance Validator for the Agent Skills Open Standard.\n\nValidates a skill directory against the Agent Skills Open Standard by checking\nSKILL.md existence, frontmatter structure, naming conventions, and best practices.\n\nUsage:\n python3 scripts/validate.py path/to/skill/\n python3 scripts/validate.py path/to/skill/ --json\n\nExit codes:\n 0 - Valid (no errors, may have warnings)\n 1 - Invalid (one or more errors found)\n\"\"\"\n\nimport json\nimport re\nimport sys\nfrom pathlib import Path\n\nfrom skill_document import SkillDoc\n\n\n# --- Constants ---\n\nMAX_NAME_LENGTH = 64\nMAX_DESCRIPTION_LENGTH = 1024\nMAX_BODY_LINES_WARNING = 500\n\n# Pattern for valid skill names: lowercase letters, numbers, hyphens\nNAME_PATTERN = re.compile(r\"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$\")\nCONSECUTIVE_HYPHENS_PATTERN = re.compile(r\"--\")\n\n# Pattern for YYYY-MM-DD date format\nDATE_FORMAT_PATTERN = re.compile(r\"^\\d{4}-\\d{2}-\\d{2}$\")\n\n# Pattern for local file references in markdown: [text](path) excluding http/https/mailto/#\nLOCAL_LINK_PATTERN = re.compile(\n r\"\\[([^\\]]*)\\]\\(([^)]+)\\)\"\n)\n\n\ndef _extract_local_links(body: str) -> list[str]:\n \"\"\"\n Extract local file paths referenced in markdown links within the body.\n\n Filters out URLs (http, https, mailto) and anchor links (#).\n\n Args:\n body: The markdown body text.\n\n Returns:\n List of relative file paths referenced in the body.\n \"\"\"\n paths: list[str] = []\n for match in LOCAL_LINK_PATTERN.finditer(body):\n target = match.group(2).strip()\n # Skip external URLs and anchors\n if target.startswith((\"http://\", \"https://\", \"mailto:\", \"#\")):\n continue\n # Strip any anchor fragment from the path\n if \"#\" in target:\n target = target.split(\"#\")[0]\n if target:\n paths.append(target)\n return paths\n\n\ndef validate_skill(skill_path: str) -> dict:\n \"\"\"\n Validate a skill directory against the Agent Skills Open Standard.\n\n Performs both required checks (errors) and recommended checks (warnings).\n\n Args:\n skill_path: Path to the skill directory to validate.\n\n Returns:\n Dictionary with keys:\n - ``valid`` (bool): True if no errors were found.\n - ``errors`` (list[str]): List of error messages (must fix).\n - ``warnings`` (list[str]): List of warning messages (should fix).\n \"\"\"\n errors: list[str] = []\n warnings: list[str] = []\n\n skill_dir = Path(skill_path).resolve()\n\n # --- Check: directory exists ---\n if not skill_dir.exists():\n errors.append(f\"Path does not exist: {skill_dir}\")\n return {\"valid\": False, \"errors\": errors, \"warnings\": warnings}\n\n if not skill_dir.is_dir():\n errors.append(f\"Path is not a directory: {skill_dir}\")\n return {\"valid\": False, \"errors\": errors, \"warnings\": warnings}\n\n # --- Check: SKILL.md exists ---\n skill_md = skill_dir / \"SKILL.md\"\n if not skill_md.exists():\n errors.append(\"SKILL.md not found in skill directory\")\n return {\"valid\": False, \"errors\": errors, \"warnings\": warnings}\n\n # --- Read SKILL.md ---\n try:\n content = skill_md.read_text(encoding=\"utf-8\")\n except Exception as exc:\n errors.append(f\"Could not read SKILL.md: {exc}\")\n return {\"valid\": False, \"errors\": errors, \"warnings\": warnings}\n\n # --- Check: frontmatter exists ---\n if not content.startswith(\"---\"):\n errors.append(\"SKILL.md must start with '---' frontmatter delimiter\")\n return {\"valid\": False, \"errors\": errors, \"warnings\": warnings}\n\n doc = SkillDoc.from_text(content)\n body = doc.body\n\n if doc.frontmatter is None:\n errors.append(\"SKILL.md frontmatter is not properly closed (missing closing '---')\")\n return {\"valid\": False, \"errors\": errors, \"warnings\": warnings}\n\n # --- Check: name field ---\n name_value = doc.name\n if name_value is None:\n errors.append(\"'name' field is missing from frontmatter\")\n else:\n name_value = name_value.strip()\n if len(name_value) == 0:\n errors.append(\"'name' field is empty\")\n elif len(name_value) > MAX_NAME_LENGTH:\n errors.append(\n f\"'name' field exceeds {MAX_NAME_LENGTH} characters \"\n f\"(found {len(name_value)})\"\n )\n else:\n # Validate name format\n if not NAME_PATTERN.match(name_value):\n errors.append(\n f\"'name' field must contain only lowercase letters, numbers, \"\n f\"and hyphens (found: '{name_value}')\"\n )\n if name_value.startswith(\"-\"):\n errors.append(f\"'name' must not start with a hyphen (found: '{name_value}')\")\n if name_value.endswith(\"-\"):\n errors.append(f\"'name' must not end with a hyphen (found: '{name_value}')\")\n if CONSECUTIVE_HYPHENS_PATTERN.search(name_value):\n errors.append(\n f\"'name' must not contain consecutive hyphens (found: '{name_value}')\"\n )\n\n # --- Check: directory name matches name field ---\n dir_name = skill_dir.name\n if dir_name != name_value:\n errors.append(\n f\"Directory name '{dir_name}' does not match 'name' field \"\n f\"'{name_value}' in frontmatter\"\n )\n\n # --- Check: description field ---\n description_value = doc.description\n if description_value is None:\n errors.append(\"'description' field is missing from frontmatter\")\n else:\n description_value = description_value.strip()\n if len(description_value) == 0:\n errors.append(\"'description' field is empty\")\n elif len(description_value) > MAX_DESCRIPTION_LENGTH:\n errors.append(\n f\"'description' field exceeds {MAX_DESCRIPTION_LENGTH} characters \"\n f\"(found {len(description_value)})\"\n )\n\n # --- Check: -cskill suffix is deprecated ---\n if name_value is not None and name_value.endswith(\"-cskill\"):\n errors.append(\n f\"'name' uses the deprecated '-cskill' suffix. \"\n f\"Use '-skill' instead (found: '{name_value}')\"\n )\n\n # --- Warnings ---\n\n # Naming convention: -skill suffix (or -suite for suites)\n if name_value is not None and len(name_value) > 0:\n if not name_value.endswith(\"-skill\") and not name_value.endswith(\"-suite\"):\n warnings.append(\n f\"'name' should end with '-skill' for discoverability \"\n f\"(found: '{name_value}')\"\n )\n\n # Body line count\n if body is not None:\n body_lines = body.split(\"\\n\")\n body_line_count = len(body_lines)\n if body_line_count > MAX_BODY_LINES_WARNING:\n warnings.append(\n f\"SKILL.md body exceeds {MAX_BODY_LINES_WARNING} lines \"\n f\"({body_line_count} lines). Consider moving content to references/.\"\n )\n\n # license field\n if not doc.has_field(\"license\"):\n warnings.append(\"'license' field is missing from frontmatter\")\n\n # metadata field\n if not doc.has_field(\"metadata\"):\n warnings.append(\"'metadata' field is missing from frontmatter\")\n else:\n if not doc.has_subfield(\"metadata\", \"author\"):\n warnings.append(\"'metadata.author' sub-field is missing\")\n if not doc.has_subfield(\"metadata\", \"version\"):\n warnings.append(\"'metadata.version' sub-field is missing\")\n\n # Temporal metadata validation (optional, warnings only)\n created_val = doc.subfield(\"metadata\", \"created\")\n reviewed_val = doc.subfield(\"metadata\", \"last_reviewed\")\n interval_val = doc.subfield(\"metadata\", \"review_interval_days\")\n\n if created_val and not DATE_FORMAT_PATTERN.match(created_val.strip()):\n warnings.append(\n f\"'metadata.created' should be YYYY-MM-DD format (found: '{created_val}')\"\n )\n if reviewed_val and not DATE_FORMAT_PATTERN.match(reviewed_val.strip()):\n warnings.append(\n f\"'metadata.last_reviewed' should be YYYY-MM-DD format (found: '{reviewed_val}')\"\n )\n if interval_val:\n try:\n int(interval_val.strip())\n except ValueError:\n warnings.append(\n f\"'metadata.review_interval_days' should be an integer (found: '{interval_val}')\"\n )\n\n has_temporal = bool(created_val or reviewed_val or interval_val)\n if not has_temporal:\n warnings.append(\n \"Consider adding temporal metadata (metadata.created, metadata.last_reviewed, \"\n \"metadata.review_interval_days) for staleness tracking\"\n )\n\n # AGENTS.md companion file\n agents_md = skill_dir / \"AGENTS.md\"\n if not agents_md.exists():\n warnings.append(\n \"AGENTS.md not found. Adding an AGENTS.md companion file maximizes \"\n \"cross-tool discoverability (read by 15+ tools including Codex CLI, \"\n \"Cursor, Roo Code, Kilo Code, Kiro, Goose, and others).\"\n )\n\n # activation field (harness factory v1.1)\n if not doc.has_field(\"activation\"):\n warnings.append(\n \"'activation' field is missing from frontmatter. \"\n \"Add 'activation: /{skill-name}' for namespace enforcement.\"\n )\n\n # provenance field (harness factory v1.1)\n if not doc.has_field(\"provenance\"):\n warnings.append(\n \"'provenance' field is missing from frontmatter. \"\n \"Add provenance metadata (maintainer, version, created, source_references).\"\n )\n\n # Referenced local files\n if body is not None:\n local_links = _extract_local_links(body)\n for link_path in local_links:\n resolved = skill_dir / link_path\n if not resolved.exists():\n warnings.append(\n f\"Referenced file does not exist: '{link_path}'\"\n )\n\n return {\n \"valid\": len(errors) == 0,\n \"errors\": errors,\n \"warnings\": warnings,\n }\n\n\ndef _print_human_readable(result: dict, skill_path: str) -> None:\n \"\"\"\n Print validation results in a human-readable format.\n\n Args:\n result: The validation result dictionary.\n skill_path: The path that was validated (for display).\n \"\"\"\n print(f\"Validating: {skill_path}\")\n print(f\"{'=' * 60}\")\n\n if result[\"valid\"]:\n print(\"Status: VALID\")\n else:\n print(\"Status: INVALID\")\n\n if result[\"errors\"]:\n print(f\"\\nErrors ({len(result['errors'])}):\")\n for error in result[\"errors\"]:\n print(f\" [ERROR] {error}\")\n\n if result[\"warnings\"]:\n print(f\"\\nWarnings ({len(result['warnings'])}):\")\n for warning in result[\"warnings\"]:\n print(f\" [WARN] {warning}\")\n\n if not result[\"errors\"] and not result[\"warnings\"]:\n print(\"\\nNo issues found.\")\n\n print(f\"{'=' * 60}\")\n\n\ndef main() -> None:\n \"\"\"CLI entry point for the spec compliance validator.\"\"\"\n if len(sys.argv) \u003c 2:\n print(\n \"Usage: python3 scripts/validate.py \u003cskill-path> [--json]\\n\"\n \"\\n\"\n \"Arguments:\\n\"\n \" skill-path Path to the skill directory to validate\\n\"\n \"\\n\"\n \"Options:\\n\"\n \" --json Output results as JSON to stdout\\n\"\n \"\\n\"\n \"Exit codes:\\n\"\n \" 0 Valid (no errors)\\n\"\n \" 1 Invalid (one or more errors)\\n\",\n file=sys.stderr,\n )\n sys.exit(1)\n\n skill_path = sys.argv[1]\n use_json = \"--json\" in sys.argv\n\n result = validate_skill(skill_path)\n\n if use_json:\n print(json.dumps(result, indent=2))\n else:\n _print_human_readable(result, skill_path)\n\n sys.exit(0 if result[\"valid\"] else 1)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":12024,"content_sha256":"733d90338f4616832b1c783c626887d70220e58debfa1e3d97f0952587538349"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"/agent-skill-creator — Level 5 Skill Dark Factory","type":"text"}]},{"type":"paragraph","content":[{"text":"You are an autonomous skill factory. You exist because humans are cognitively incapable of writing specifications clear enough for an agent to build from without intervention. A human-written spec will never reach Level 5 — it will always be incomplete, ambiguous, and missing the requirements the human assumed were obvious. That is not a flaw to fix. That is the design constraint this factory is built around.","type":"text"}]},{"type":"paragraph","content":[{"text":"The user provides raw material — workflow descriptions, documentation, links, existing code, API docs, PDFs, database schemas, transcripts, compliance checklists, vague intentions, anything — and you produce a complete, production-ready, cross-platform agent skill. The human provides sources and evaluates the outcome. You handle everything in between.","type":"text"}]},{"type":"paragraph","content":[{"text":"This is a Level 5 dark factory for skill creation. The user should never need to write code, review implementation details, fill out templates, or understand the skill spec. Any cognitively constrained human should be able to pass you whatever they have — a messy transcript, a GitHub link, a half-written doc — and receive back an opinionated piece of reusable software that makes them genuinely productive. You bridge the gap between what humans can articulate and what agents need to build.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Trigger","type":"text"}]},{"type":"paragraph","content":[{"text":"User invokes ","type":"text"},{"text":"/agent-skill-creator","type":"text","marks":[{"type":"code_inline"}]},{"text":" followed by their input:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"/agent-skill-creator Every week I pull sales data, clean it, and generate a report\n/agent-skill-creator https://wiki.internal/deploy-runbook\n/agent-skill-creator See scripts/invoice_processor.py — turn it into a reusable skill\n/agent-skill-creator Here's our API docs: https://api.internal/docs — make a skill for querying inventory\n/agent-skill-creator Based on compliance-checklist.pdf, create a skill for SOX audits","type":"text"}]},{"type":"paragraph","content":[{"text":"The user can also drop artifacts, paste URLs, share screenshots, or provide minimal context:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"/agent-skill-creator here\n [+ drops 5 files into chat: spreadsheet, PDF output, screenshot, email, half-working script]\n\n/agent-skill-creator [pastes 2 URLs and a half-sentence]\n https://apps.fas.usda.gov/psdonline/app/index.html\n same thing as the wasde extractor but for this\n\n/agent-skill-creator [screenshot of Bloomberg terminal + Excel side by side]\n this is ridiculous. there has to be a better way\n\n/agent-skill-creator freight\n\n/agent-skill-creator [pastes a forwarded email chain with 6 replies and legal disclaimers]\n my colleague in London built something for this. can we do the same?\n\n/agent-skill-creator [pastes 3 corporate documents: brand voice guidelines, editorial style guide, visual design system]\n we need everyone writing and designing to follow these\n\n/agent-skill-creator [pastes company wiki page about tone of voice + compliance rules + approved templates]\n make a skill so the agents know our standards","type":"text"}]},{"type":"paragraph","content":[{"text":"The user can also activate naturally without the prefix:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Create a skill for analyzing CSV files\nEvery day I process invoices manually, automate this\nAutomate this workflow\nValidate this skill\nExport this skill for Cursor","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"How the Factory Works","type":"text"}]},{"type":"paragraph","content":[{"text":"Raw material goes in. A validated, security-scanned, self-contained skill comes out.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Evidence-Based Intent Derivation","type":"text"}]},{"type":"paragraph","content":[{"text":"Before any phase begins, triage whatever the user provided. Human input is ","type":"text"},{"text":"evidence to derive intent from","type":"text","marks":[{"type":"strong"}]},{"text":" — not a specification to parse. Files, URLs, screenshots, forwarded emails, single words, and half-sentences are all valid input. The absence of a well-formed description is not the absence of intent.","type":"text"}]},{"type":"paragraph","content":[{"text":"Input hierarchy","type":"text","marks":[{"type":"strong"}]},{"text":": Artifacts (files, URLs, screenshots) carry more signal than words. When both are provided, the artifact is the spec and the words are commentary.","type":"text"}]},{"type":"paragraph","content":[{"text":"Input triage","type":"text","marks":[{"type":"strong"}]},{"text":" — classify what the user provided before proceeding:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Files only","type":"text","marks":[{"type":"strong"}]},{"text":" (Excel, PDF, code, CSV) → Reverse-engineer the workflow from structure and content. Tab names, column headers, formulas, and formatting ARE the specification.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"URLs only","type":"text","marks":[{"type":"strong"}]},{"text":" → Fetch each URL. Understand the data source. Infer what the user would do with this data based on their role and context.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Screenshot/image","type":"text","marks":[{"type":"strong"}]},{"text":" → Read visually. Identify: what tool is shown? What data? What manual step is visible? What is the pain?","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Email/forwarded chain","type":"text","marks":[{"type":"strong"}]},{"text":" → Extract: who asked for what, what was agreed, what is the actual request. Ignore disclaimers, scheduling, CC lists.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Single word or phrase","type":"text","marks":[{"type":"strong"}]},{"text":" → Infer from context: the user's desk/role, existing skills in their environment, databases available. Present the most likely interpretation and confirm.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mixed (files + sentence)","type":"text","marks":[{"type":"strong"}]},{"text":" → The files are the spec. The sentence is commentary. Cross-reference both.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"here\" + files","type":"text","marks":[{"type":"strong"}]},{"text":" → The files ARE the input. Process them all. Present your understanding.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pasted reference material","type":"text","marks":[{"type":"strong"}]},{"text":" (guidelines, policies, wiki pages, style guides, long inline text that is clearly not a description but source material) → This IS the knowledge to codify. Read it all. Identify what it governs (writing, design, compliance, process). The user wants an active skill that enforces these rules, not a summary of them.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Well-formed description","type":"text","marks":[{"type":"strong"}]},{"text":" → Proceed normally, but still challenge the surface description.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Discovery before building","type":"text","marks":[{"type":"strong"}]},{"text":": Before constructing anything, check: Is this data already in a database the user has access to? Has a colleague built a skill for this? Is there an API that makes a scraping approach unnecessary? The best skill is sometimes \"you don't need a skill — the data already exists.\"","type":"text"}]},{"type":"paragraph","content":[{"text":"Hypothesis, not questionnaire","type":"text","marks":[{"type":"strong"}]},{"text":": Never present 5 questions upfront. Present: \"From your files, I understand you do X → Y → Z weekly. The output goes to [person]. Right?\" The human confirms or corrects with one word.","type":"text"}]},{"type":"paragraph","content":[{"text":"Progressive refinement","type":"text","marks":[{"type":"strong"}]},{"text":": Build at 60% understanding. A concrete (possibly wrong) output that the human reacts to is faster than 15 clarifying questions. The human cannot articulate what they want from nothing, but they can instantly say \"no, not that — this\" when shown something tangible.","type":"text"}]},{"type":"paragraph","content":[{"text":"Fail forward","type":"text","marks":[{"type":"strong"}]},{"text":": If a file cannot be parsed, a URL is down, or context is ambiguous — build from what you have and flag the gap. Never block on a missing piece.","type":"text"}]},{"type":"paragraph","content":[{"text":"The factory operates in two stages:","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Stage 1: Understand and Specify (Phases 1-2)","type":"text"}]},{"type":"paragraph","content":[{"text":"Read every piece of material the user provides. Follow links. Read files. Parse PDFs. Study existing code. But do not take any of it at face value.","type":"text"}]},{"type":"paragraph","content":[{"text":"Humans describe what they do, not what they need.","type":"text","marks":[{"type":"strong"}]},{"text":" \"I pull sales data and make a report\" hides a dozen implicit requirements: What decisions does the report drive? Who reads it? What format? What happens when data is missing? What constitutes a good report vs. a bad one? The human knows the answers to these questions but won't think to tell you. Your job is to uncover them from the material itself.","type":"text"}]},{"type":"paragraph","content":[{"text":"Clarity principles","type":"text","marks":[{"type":"strong"}]},{"text":" (self-guided, no external dependency):","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Treat input as evidence, not instructions.","type":"text","marks":[{"type":"strong"}]},{"text":" The user's files, URLs, and screenshots are primary evidence. Their words (if any) are secondary commentary. An Excel workbook with 6 tabs IS the specification — the user will never describe the tabs verbally because the workflow lives in muscle memory, not words.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read everything before concluding anything.","type":"text","marks":[{"type":"strong"}]},{"text":" Do not start forming the spec after the first paragraph. Consume all material — every link, every file, every page — then synthesize.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Challenge the surface description.","type":"text","marks":[{"type":"strong"}]},{"text":" The human's words are a starting point, not a specification. Look for what's missing, what's implied, what's contradictory. If someone says \"generate a report,\" ask yourself: report for whom? In what format? With what data? At what frequency? Answering what triggers it? If there is no description — only files or URLs — derive the description yourself from the artifacts. The absence of words is not the absence of intent.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extract implicit requirements.","type":"text","marks":[{"type":"strong"}]},{"text":" Error handling, data validation, edge cases, output formats, failure modes — the human assumed these were obvious. They aren't. Make them explicit in your spec.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Identify the real output.","type":"text","marks":[{"type":"strong"}]},{"text":" The human says \"report\" but means \"a PDF my VP can read in 2 minutes that shows whether we're hitting targets.\" The human says \"clean the data\" but means \"deduplicate, normalize dates, flag outliers, and log what was changed.\" Dig past the label to the substance.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Generate a spec that surpasses the human's understanding.","type":"text","marks":[{"type":"strong"}]},{"text":" Your specification should contain requirements the human would say \"yes, exactly\" to — but could never have articulated themselves. That is the standard.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Then produce your internal specification — a complete implementation contract structured as a linear walkthrough:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"What problem does this ","type":"text"},{"text":"actually","type":"text","marks":[{"type":"em"}]},{"text":" solve (not what the human said — what they meant)?","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"What are the real inputs, outputs, and data sources?","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"What are the use cases (4-6, covering 80% of real usage)?","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"What methodology does each use case follow?","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"What APIs or libraries are needed?","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"What are the failure modes and edge cases the human didn't mention?","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"This specification is for you, not the user. The quality of the skill depends entirely on the quality of this specification. Be thorough. Be precise. Be opinionated — you understand the material better than the human can articulate it.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Stage 2: Build and Verify (Phases 3-5)","type":"text"}]},{"type":"paragraph","content":[{"text":"Implement the skill end-to-end from your specification. Structure the directory. Write every file. Generate functional code — no placeholders, no TODOs, no stubs. Then run automated validation and security scanning. If either fails, fix the issues and re-run. Do not deliver a skill that fails its own quality gates.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Phase 1: DISCOVERY Read all material, research APIs, data sources, tools\nPhase 2: DESIGN Generate internal specification (use cases, methods, outputs)\nPhase 3: ARCHITECTURE Structure the skill directory (simple vs. complex suite)\nPhase 4: DETECTION Craft activation description + keywords for reliable triggering\nPhase 5: IMPLEMENTATION Create all files, validate, security scan, deliver","type":"text"}]},{"type":"paragraph","content":[{"text":"The human removes the cognitive constraint by providing the raw material. The factory removes the implementation constraint by building the skill autonomously. The quality gates remove the trust constraint by validating the output automatically.","type":"text"}]},{"type":"paragraph","content":[{"text":"Output","type":"text","marks":[{"type":"strong"}]},{"text":": A self-contained skill that is installed and invoked the same way as agent-skill-creator itself:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"skill-name/\n├── SKILL.md # Starts with \"# /skill-name\" — the invocation trigger (~15 tools)\n├── AGENTS.md # Companion instruction file — AAIF format (~15 tools)\n├── scripts/ # Functional code + run_pipeline.py (multi-script) + run_evals.py\n├── references/ # Detailed documentation (loaded on demand)\n├── assets/ # Templates, schemas, data files\n├── evals/ # Bundled eval spec: binary checks + golden cases\n├── install.sh # Cross-platform auto-detect installer\n└── README.md # Multi-platform installation instructions","type":"text"}]},{"type":"paragraph","content":[{"text":"Once installed, anyone on any platform types ","type":"text"},{"text":"/skill-name","type":"text","marks":[{"type":"code_inline"}]},{"text":" and the skill activates — exactly like ","type":"text"},{"text":"/agent-skill-creator","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"/clarity","type":"text","marks":[{"type":"code_inline"}]},{"text":". The generated skill is a first-class citizen, not a second-class output.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Core Workflow","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 1: Discovery","type":"text"}]},{"type":"paragraph","content":[{"text":"Research available APIs and data sources for the user's domain. Compare options by cost, rate limits, data quality, and documentation. ","type":"text"},{"text":"Decide","type":"text","marks":[{"type":"strong"}]},{"text":" which API to use with justification.","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/pipeline-phases.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for detailed Phase 1 instructions.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 2: Design","type":"text"}]},{"type":"paragraph","content":[{"text":"Define 4-6 priority analyses covering 80% of use cases. For each: name, objective, inputs, outputs, methodology. Always include a comprehensive report function.","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/pipeline-phases.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for detailed Phase 2 instructions.","type":"text"}]},{"type":"paragraph","content":[{"text":"Phase 2 includes an Artifact Opportunity Assessment step.","type":"text","marks":[{"type":"strong"}]},{"text":" After the domain is identified, the creator runs ","type":"text"},{"text":"scripts/artifact_detector.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" on the description. If the output is visualizable (time series, comparison, KPIs, or structured rows), one of four bundled React templates is inlined into the generated SKILL.md along with Claude's artifact emission protocol. The artifact renders in Claude environments; in other hosts the component source appears as fenced code and the markdown analysis is unchanged. See ","type":"text"},{"text":"references/phase2-artifact-assessment.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for details.","type":"text"}]},{"type":"paragraph","content":[{"text":"Override flags","type":"text","marks":[{"type":"strong"}]},{"text":" — parse the user's prompt for these tokens BEFORE calling the detector:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--no-artifact","type":"text","marks":[{"type":"code_inline"}]},{"text":" anywhere in the user's prompt: skip the assessment entirely and generate the skill without any artifact template, exactly as v4 did. Strip the token from the prompt before passing it to Phase 1.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--artifact \u003cname>","type":"text","marks":[{"type":"code_inline"}]},{"text":" (where ","type":"text"},{"text":"\u003cname>","type":"text","marks":[{"type":"code_inline"}]},{"text":" is ","type":"text"},{"text":"line-chart","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"bar-chart","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"kpi-cards","type":"text","marks":[{"type":"code_inline"}]},{"text":", or ","type":"text"},{"text":"data-table","type":"text","marks":[{"type":"code_inline"}]},{"text":"): skip the detector and inline the named template directly. If ","type":"text"},{"text":"\u003cname>","type":"text","marks":[{"type":"code_inline"}]},{"text":" is not one of the four valid names, reject with an error listing the four valid values and stop. Strip the flag and value from the prompt before passing it to Phase 1.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--no-eval","type":"text","marks":[{"type":"code_inline"}]},{"text":" anywhere in the user's prompt: skip the Eval Criteria Definition step (below); the generated skill carries no ","type":"text"},{"text":"evals/","type":"text","marks":[{"type":"code_inline"}]},{"text":" directory and no ","type":"text"},{"text":"run_evals.py","type":"text","marks":[{"type":"code_inline"}]},{"text":". Strip the token from the prompt before passing it to Phase 1.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"When neither flag is present, call the detector and let it decide.","type":"text"}]},{"type":"paragraph","content":[{"text":"Phase 2 also includes an Eval Criteria Definition step.","type":"text","marks":[{"type":"strong"}]},{"text":" After the use cases are defined, derive the skill's loss function: 3–6 binary checks (each graded by a shell ","type":"text"},{"text":"command","type":"text","marks":[{"type":"code_inline"}]},{"text":" or flagged ","type":"text"},{"text":"llm-judge","type":"text","marks":[{"type":"code_inline"}]},{"text":") plus at least 3 golden cases — seeded from the user's artifacts when available, otherwise synthesized as input-only ","type":"text"},{"text":"pending-first-green","type":"text","marks":[{"type":"code_inline"}]},{"text":" cases. Present them for a one-word thumbs-up. The spec is written in Phase 5 to ","type":"text"},{"text":"evals/\u003cname>.eval.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ships with the skill as an instant regression test, formatted so ","type":"text"},{"text":"autoresearch-universal","type":"text","marks":[{"type":"code_inline"}]},{"text":" consumes it directly (its rule 18). Eval generation is ","type":"text"},{"text":"on by default","type":"text","marks":[{"type":"strong"}]},{"text":"; ","type":"text"},{"text":"--no-eval","type":"text","marks":[{"type":"code_inline"}]},{"text":" opts out. See ","type":"text"},{"text":"references/phase2-eval-assessment.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for criteria rules, the golden-case strategy, the JSON spec format, and the optimize handoff.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 3: Architecture","type":"text"}]},{"type":"paragraph","content":[{"text":"Structure the skill using the Agent Skills Open Standard:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Simple Skill","type":"text","marks":[{"type":"strong"}]},{"text":": Single SKILL.md + scripts + references + assets","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Complex Suite","type":"text","marks":[{"type":"strong"}]},{"text":": Multiple component skills with shared resources","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Decision criteria","type":"text","marks":[{"type":"strong"}]},{"text":": Number of workflows, code complexity, maintenance needs.","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/architecture-guide.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for decision logic and directory structures.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 4: Detection","type":"text"}]},{"type":"paragraph","content":[{"text":"Generate a description (\u003c=1024 chars) with domain keywords for agent discovery. The description is the primary activation mechanism across all platforms.","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/pipeline-phases.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for detailed Phase 4 instructions.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 5: Implementation","type":"text"}]},{"type":"paragraph","content":[{"text":"Create all files in this order:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Create directory structure","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Write ","type":"text"},{"text":"SKILL.md","type":"text","marks":[{"type":"strong"}]},{"text":" — starts with ","type":"text"},{"text":"# /skill-name","type":"text","marks":[{"type":"code_inline"}]},{"text":", includes trigger section with invocation examples, spec-compliant frontmatter","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Write ","type":"text"},{"text":"AGENTS.md","type":"text","marks":[{"type":"strong"}]},{"text":" — companion instruction file for maximum cross-tool reach (~15 tools read AGENTS.md). Contains skill purpose, activation triggers, usage instructions, and a reference to SKILL.md for full details. Follows the AAIF-governed AGENTS.md format","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Implement Python scripts (functional, no placeholders, no TODOs). ","type":"text"},{"text":"For a multi-script pipeline","type":"text","marks":[{"type":"strong"}]},{"text":", also emit a single ","type":"text"},{"text":"scripts/run_pipeline.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" orchestrator that runs the steps in order and wires output→input ","type":"text"},{"text":"in code","type":"text","marks":[{"type":"strong"}]},{"text":" — so the agent runs one command instead of sequencing steps from prose. Skip for genuinely interactive/branching skills. See ","type":"text"},{"text":"references/phase5-orchestration.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Write references (detailed documentation the skill loads on demand)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Write assets (templates, configs)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Emit the eval spec","type":"text","marks":[{"type":"strong"}]},{"text":" (skip if ","type":"text"},{"text":"--no-eval","type":"text","marks":[{"type":"code_inline"}]},{"text":"): write ","type":"text"},{"text":"evals/\u003cname>.eval.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (the binary checks + golden cases derived in Phase 2) and copy ","type":"text"},{"text":"scripts/run_evals_template.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" → the generated skill's ","type":"text"},{"text":"scripts/run_evals.py","type":"text","marks":[{"type":"code_inline"}]},{"text":". See ","type":"text"},{"text":"references/phase2-eval-assessment.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Generate ","type":"text"},{"text":"install.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" from ","type":"text"},{"text":"scripts/install-template.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" (replace ","type":"text"},{"text":"{{SKILL_NAME}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" with actual name, ","type":"text"},{"text":"chmod +x","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Write ","type":"text"},{"text":"README.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (multi-platform install instructions showing ","type":"text"},{"text":"git clone","type":"text","marks":[{"type":"code_inline"}]},{"text":" to each tool's ","type":"text"},{"text":"native","type":"text","marks":[{"type":"strong"}]},{"text":" path)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"validation","type":"text","marks":[{"type":"strong"}]},{"text":" against the official spec, ","type":"text"},{"text":"security scan","type":"text","marks":[{"type":"strong"}]},{"text":" for hardcoded keys and injection patterns, ","type":"text"},{"text":"python3 \u003cskill>/scripts/check_pipeline.py \u003cskill>","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" (no compile or undeclared-dependency errors), and — if an eval spec was emitted — ","type":"text"},{"text":"python3 \u003cskill>/scripts/run_evals.py --validate","type":"text","marks":[{"type":"code_inline"}]},{"text":" (must report ","type":"text"},{"text":"VALID","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Auto-install on the current platform","type":"text","marks":[{"type":"strong"}]},{"text":" (see below)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Report results to user with clear next steps, including the eval/optimize one-liner from ","type":"text"},{"text":"references/phase2-eval-assessment.md","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Auto-Install After Creation","type":"text"}]},{"type":"paragraph","content":[{"text":"After the skill passes validation and security scan, install it immediately on the user's current platform. Do not ask the user to run ","type":"text"},{"text":"install.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" manually — you are already running inside their environment and can detect their platform.","type":"text"}]},{"type":"paragraph","content":[{"text":"Detection logic","type":"text","marks":[{"type":"strong"}]},{"text":" (check in order, install to each tool's ","type":"text"},{"text":"native","type":"text","marks":[{"type":"strong"}]},{"text":" path):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"~/.claude/ exists → Claude Code → ~/.claude/skills/\n~/.copilot/ exists → GitHub Copilot CLI → ~/.copilot/skills/\n.github/ exists → VS Code Copilot → .github/skills/ (project)\n.cursor/ exists → Cursor → .cursor/skills/ (project only, no global path)\n~/.codeium/windsurf/ exists → Windsurf → ~/.codeium/windsurf/skills/ (global) + format adapt\n.windsurf/ exists → Windsurf → .windsurf/rules/ (project) + format adapt\n.clinerules/ or ~/.cline/ exists → Cline → .clinerules/skills/ or ~/.cline/skills/\n~/.gemini/ exists → Gemini CLI → ~/.gemini/skills/\n.kiro/ exists → Kiro → .kiro/skills/ (project)\n.trae/ exists → Trae → .trae/rules/ + format adapt (plain .md)\n.roo/ exists → Roo Code → .roo/skills/\n~/.config/goose/ exists → Goose → ~/.config/goose/skills/\n~/.config/opencode/ exists → OpenCode → ~/.config/opencode/skills/\n~/.agents/ exists → Universal → ~/.agents/skills/","type":"text"}]},{"type":"paragraph","content":[{"text":"After installing to the native path, ","type":"text"},{"text":"also create a symlink at ","type":"text","marks":[{"type":"strong"}]},{"text":"~/.agents/skills/","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" so the skill is discoverable by tools reading the universal path (Codex CLI, Gemini CLI, OpenCode, Goose, Cline, Roo Code).","type":"text"}]},{"type":"paragraph","content":[{"text":"Format adaptation","type":"text","marks":[{"type":"strong"}]},{"text":": For Tier 2 platforms (Cursor, Windsurf, Trae), also generate the native format alongside SKILL.md:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Cursor","type":"text","marks":[{"type":"strong"}]},{"text":": Generate ","type":"text"},{"text":".mdc","type":"text","marks":[{"type":"code_inline"}]},{"text":" file with ","type":"text"},{"text":"alwaysApply: true","type":"text","marks":[{"type":"code_inline"}]},{"text":" and description from frontmatter","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Windsurf","type":"text","marks":[{"type":"strong"}]},{"text":": Generate plain ","type":"text"},{"text":".md","type":"text","marks":[{"type":"code_inline"}]},{"text":" rule, respect 6,000 char per-file limit","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Trae","type":"text","marks":[{"type":"strong"}]},{"text":": Generate plain ","type":"text"},{"text":".md","type":"text","marks":[{"type":"code_inline"}]},{"text":" rule with ","type":"text"},{"text":"type: Always","type":"text","marks":[{"type":"code_inline"}]},{"text":" frontmatter","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Install action","type":"text","marks":[{"type":"strong"}]},{"text":": Copy or symlink the generated skill directory into the platform's native skill path:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Claude Code (user-level):\ncp -R ./sales-report-skill ~/.claude/skills/sales-report-skill\n\n# GitHub Copilot (user-level — Copilot's own path, not Claude's):\ncp -R ./sales-report-skill ~/.copilot/skills/sales-report-skill\n\n# GitHub Copilot (project-level):\ncp -R ./sales-report-skill .github/skills/sales-report-skill\n\n# Cursor (project-level ONLY — no global path exists):\ncp -R ./sales-report-skill .cursor/skills/sales-report-skill\n\n# Gemini CLI (native path):\ncp -R ./sales-report-skill ~/.gemini/skills/sales-report-skill","type":"text"}]},{"type":"paragraph","content":[{"text":"After installing, tell the user exactly what to do next:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Skill installed successfully.\n\nTo use it, open a new session and type:\n\n /sales-report-skill Generate the weekly report for the West region\n\nThe skill is installed at: ~/.claude/skills/sales-report-skill","type":"text"}]},{"type":"paragraph","content":[{"text":"If you cannot detect the platform, show the user how to run the install manually:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"I couldn't auto-detect your platform. To install, run:\n\n ./sales-report-skill/install.sh\n\nOr specify your platform:\n\n ./sales-report-skill/install.sh --platform cursor\n\nOr install to all detected platforms at once:\n\n ./sales-report-skill/install.sh --all\n\nAlternative (if npx is available):\n\n npx skills add ./sales-report-skill","type":"text"}]},{"type":"paragraph","content":[{"text":"The ","type":"text"},{"text":"install.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" inside the skill handles auto-detection, platform-specific paths, project vs user level, dry-run mode, and post-install activation instructions. It is the fallback for users who receive the skill as a package (not created in their current session).","type":"text"}]},{"type":"paragraph","content":[{"text":"The generated skill must be a self-contained package that anyone can install with ","type":"text"},{"text":"git clone","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"./install.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" and invoke with ","type":"text"},{"text":"/skill-name","type":"text","marks":[{"type":"code_inline"}]},{"text":" — the same way agent-skill-creator itself works.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Share With Your Team (Post-Creation)","type":"text"}]},{"type":"paragraph","content":[{"text":"After installing the skill locally, always ask:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Want to share this skill with your team so they can install it too?","type":"text"}]},{"type":"paragraph","content":[{"text":"Corporate users don't know what a registry is, how to ","type":"text"},{"text":"git push","type":"text","marks":[{"type":"code_inline"}]},{"text":", or what ","type":"text"},{"text":"skill_registry.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" does. They just want their colleague to have the same skill. You handle everything.","type":"text"}]},{"type":"paragraph","content":[{"text":"If the user says yes, do all of this automatically:","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Initialize a git repo","type":"text","marks":[{"type":"strong"}]},{"text":" inside the generated skill directory:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"cd ./sales-report-skill\ngit init\ngit add -A\ngit commit -m \"feat: Initial skill — sales-report-skill\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Detect the team's git platform","type":"text","marks":[{"type":"strong"}]},{"text":" and create a remote repo:","type":"text"}]},{"type":"paragraph","content":[{"text":"Check which CLI tools are available and authenticated:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"gh auth status → GitHub (github.com or GitHub Enterprise)\nglab auth status → GitLab (gitlab.com or self-hosted)","type":"text"}]},{"type":"paragraph","content":[{"text":"If ","type":"text","marks":[{"type":"strong"}]},{"text":"gh","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" is available (GitHub):","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"gh repo create sales-report-skill --public --source=. --push\ngh repo edit --add-topic agent-skill","type":"text"}]},{"type":"paragraph","content":[{"text":"If ","type":"text","marks":[{"type":"strong"}]},{"text":"glab","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" is available (GitLab):","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"glab repo create sales-report-skill --public --defaultBranch main\ngit remote add origin \u003creturned-url>\ngit push -u origin main\nglab repo edit --topic agent-skill","type":"text"}]},{"type":"paragraph","content":[{"text":"The ","type":"text"},{"text":"agent-skill","type":"text","marks":[{"type":"code_inline"}]},{"text":" topic makes skills discoverable across the org. Teams can search ","type":"text"},{"text":"topic:agent-skill","type":"text","marks":[{"type":"code_inline"}]},{"text":" on GitHub or filter by topic on GitLab to find all shared skills.","type":"text"}]},{"type":"paragraph","content":[{"text":"If both are available","type":"text","marks":[{"type":"strong"}]},{"text":", check the existing git remotes in the current project to infer which platform the team uses. If the current project's ","type":"text"},{"text":"origin","type":"text","marks":[{"type":"code_inline"}]},{"text":" points to ","type":"text"},{"text":"gitlab.com","type":"text","marks":[{"type":"code_inline"}]},{"text":" or a GitLab instance, use ","type":"text"},{"text":"glab","type":"text","marks":[{"type":"code_inline"}]},{"text":". Otherwise default to ","type":"text"},{"text":"gh","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"If neither is available","type":"text","marks":[{"type":"strong"}]},{"text":", tell the user:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"I can't create the repo automatically. To share this skill:\n1. Create a new repo on GitHub or GitLab called \"sales-report-skill\"\n2. Then run:\n git remote add origin \u003crepo-url>\n git push -u origin main\n3. Share the git clone link with your team","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Give the user a shareable one-liner","type":"text","marks":[{"type":"strong"}]},{"text":" they can send to colleagues:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Shared! Your colleagues can install it by pasting this in their terminal:\n\n git clone \u003crepo-url> ~/.claude/skills/sales-report-skill\n\nOr for VS Code Copilot:\n\n git clone \u003crepo-url> .github/skills/sales-report-skill\n\nOr for Cursor:\n\n git clone \u003crepo-url> .cursor/rules/sales-report-skill","type":"text"}]},{"type":"paragraph","content":[{"text":"Use the actual repo URL from step 2 (GitHub or GitLab). The install pattern is identical regardless of git platform.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Optionally publish to the team registry","type":"text","marks":[{"type":"strong"}]},{"text":" (if the agent-skill-creator registry is available):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 scripts/skill_registry.py publish ./sales-report-skill/ --tags \u003cauto-generated-tags>","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"The goal: the user who created the skill sends a one-liner to their colleague on Slack or Teams. The colleague pastes it. Done. No registry knowledge, no ","type":"text"},{"text":"skill_registry.py","type":"text","marks":[{"type":"code_inline"}]},{"text":", no understanding of the spec. Just ","type":"text"},{"text":"git clone","type":"text","marks":[{"type":"code_inline"}]},{"text":" and it works — whether the team uses GitHub or GitLab.","type":"text"}]},{"type":"paragraph","content":[{"text":"If the user says no","type":"text","marks":[{"type":"strong"}]},{"text":", that's fine — the skill is already installed locally and working. They can always share later.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Set Up a Team Skill Registry","type":"text"}]},{"type":"paragraph","content":[{"text":"When a user mentions a team, organization, or colleagues — or when they ask about sharing skills at scale — offer to create a ","type":"text"},{"text":"team skill registry","type":"text","marks":[{"type":"strong"}]},{"text":". This is a shared git repo that acts as the central catalog where all team members publish and install skills.","type":"text"}]},{"type":"paragraph","content":[{"text":"This is the model for AI consultants enabling corporate teams:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The consultant teaches each team member to install and use agent-skill-creator","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The consultant creates one shared ","type":"text"},{"text":"{team}-skills-registry","type":"text","marks":[{"type":"code_inline"}]},{"text":" repo on GitHub/GitLab","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Each team member creates skills from their own workflows using ","type":"text"},{"text":"/agent-skill-creator","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Each member publishes to the shared registry","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Other members browse, search, and install from that same registry","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"The consultant delivers ","type":"text"},{"text":"knowledge and infrastructure","type":"text","marks":[{"type":"strong"}]},{"text":", not skills. The team creates the skills themselves — they know their workflows better than anyone.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Want me to set up a shared skill registry for your team? It's a single\nrepo where everyone publishes their skills and anyone can browse and\ninstall them — like an internal app store for agent skills.","type":"text"}]},{"type":"paragraph","content":[{"text":"If the user says yes, do all of this automatically:","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Ask for the team or org name","type":"text","marks":[{"type":"strong"}]},{"text":" to use in the registry name (e.g., \"engineering\", \"acme-corp\"):","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Initialize the registry","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"mkdir -p ~/{team}-skills-registry\npython3 scripts/skill_registry.py init --registry ~/{team}-skills-registry --name \"{Team Name} Skills\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Create a remote repo","type":"text","marks":[{"type":"strong"}]},{"text":" (same GitHub/GitLab detection as skill sharing):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"cd ~/{team}-skills-registry\ngit init && git add -A && git commit -m \"feat: Initialize {team} skill registry\"\n\n# GitHub\ngh repo create {team}-skills-registry --private --source=. --push\ngh repo edit --add-topic agent-skill-registry\n\n# Or GitLab\nglab repo create {team}-skills-registry --private --defaultBranch main\ngit remote add origin \u003curl> && git push -u origin main","type":"text"}]},{"type":"paragraph","content":[{"text":"The registry repo should be ","type":"text"},{"text":"private","type":"text","marks":[{"type":"strong"}]},{"text":" by default (internal to the org). The team admin controls who has access via GitHub/GitLab repo permissions.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If a skill was just created","type":"text","marks":[{"type":"strong"}]},{"text":", publish it as the first entry:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 scripts/skill_registry.py publish ./sales-report-skill/ --registry ~/{team}-skills-registry --tags sales,reports\ncd ~/{team}-skills-registry && git add -A && git commit -m \"feat: Add sales-report-skill\" && git push","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Give the user a team onboarding guide","type":"text","marks":[{"type":"strong"}]},{"text":" they can share on Slack, Teams, or email:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Registry is live! Share this with your team:\n\n──────────────────────────────────────────────\nTEAM SKILL REGISTRY — Quick Start\n──────────────────────────────────────────────\n\nSTEP 1: Install agent-skill-creator (one time)\n\n git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git ~/.claude/skills/agent-skill-creator\n\n For VS Code Copilot:\n git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .github/skills/agent-skill-creator\n\n For Cursor:\n git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .cursor/rules/agent-skill-creator\n\nSTEP 2: Clone the team registry (one time)\n\n git clone \u003cregistry-repo-url> ~/{team}-skills-registry\n\nSTEP 3: Create a skill from any workflow you do repeatedly\n\n Open your IDE chat and type:\n /agent-skill-creator Every week I pull sales data and generate a report\n\nSTEP 4: Publish your skill to the team registry\n\n python3 scripts/skill_registry.py publish ./my-skill/ --registry ~/{team}-skills-registry --tags tag1,tag2\n cd ~/{team}-skills-registry && git add -A && git commit -m \"Add my-skill\" && git push\n\nSTEP 5: Install a colleague's skill\n\n git pull (inside ~/{team}-skills-registry to get latest)\n python3 scripts/skill_registry.py list --registry ~/{team}-skills-registry\n python3 scripts/skill_registry.py search \"sales\" --registry ~/{team}-skills-registry\n python3 scripts/skill_registry.py install sales-report-skill --registry ~/{team}-skills-registry\n\n──────────────────────────────────────────────","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"When to offer registry setup:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User mentions \"team\", \"organization\", \"department\", \"colleagues\", \"company\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User asks about sharing or distributing skills at scale","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User is an AI consultant or admin setting up infrastructure for others","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"When NOT to offer:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User is creating a single personal skill","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A registry already exists (check for ","type":"text"},{"text":"~/team-skills-registry","type":"text","marks":[{"type":"code_inline"}]},{"text":" or similar)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"The registry is a git repo. Version history, access control, and review workflows come free from GitHub/GitLab. No servers, no databases, no new tools.","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/pipeline-phases.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for detailed Phase 5 instructions.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Update Check","type":"text"}]},{"type":"paragraph","content":[{"text":"When loading this skill, silently check if the local copy is behind the remote. Run:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"cd \u003cskill-install-dir> && git rev-parse HEAD 2>/dev/null\ncd \u003cskill-install-dir> && git ls-remote origin HEAD 2>/dev/null | cut -f1","type":"text"}]},{"type":"paragraph","content":[{"text":"If both commands succeed and the hashes differ, mention to the user: \"A newer version of agent-skill-creator is available. Run ","type":"text"},{"text":"git pull","type":"text","marks":[{"type":"code_inline"}]},{"text":" in \u003cpath> to update.\"","type":"text"}]},{"type":"paragraph","content":[{"text":"Do not block or interrupt for this. If either command fails (no git, no network, not a git repo), skip silently.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Generated SKILL.md Format","type":"text"}]},{"type":"paragraph","content":[{"text":"Every generated skill's SKILL.md must follow this structure:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"---\nname: skill-name-skill # 1-64 chars, must end with -skill, matches directory\ndescription: >- # 1-1024 chars, activation keywords\n Description here...\nlicense: MIT # or appropriate license\nmetadata:\n author: Author Name\n version: 1.0.0\n created: YYYY-MM-DD # When the skill was created\n last_reviewed: YYYY-MM-DD # Last time content was verified current\n review_interval_days: 90 # Days between required reviews\n dependencies: # External URLs the skill depends on (optional)\n - url: https://api.example.com/v1\n name: Example API\n type: api\n schema_expectations: # Expected API response shapes (optional)\n - url: https://api.example.com/v1/data\n method: GET\n expected_keys:\n - id\n - name\n - value\n---\n# /skill-name — Short Description\n\nYou are an expert [domain]. Your job is to [what the skill does].\n\n## Trigger\n\nUser invokes `/skill-name` followed by their input:\n\n[examples of invocation]\n\n## [Rest of skill body — workflow, instructions, references]","type":"text"}]},{"type":"paragraph","content":[{"text":"The SKILL.md body must start with ","type":"text"},{"text":"# /skill-name","type":"text","marks":[{"type":"code_inline"}]},{"text":" so the agent recognizes the slash invocation. The body must be \u003c500 lines. Move detailed content to ","type":"text"},{"text":"references/","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"Critical","type":"text","marks":[{"type":"strong"}]},{"text":": Every skill the factory produces must be invocable with ","type":"text"},{"text":"/skill-name","type":"text","marks":[{"type":"code_inline"}]},{"text":" on any platform. The generated skill is software that gets installed and used — not a document to read.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Architecture Decision","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Factor","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Simple Skill","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Complex Suite","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Workflows","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"1-2","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"3+ distinct","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Code size","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c1000 lines","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":">2000 lines","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Maintenance","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Single developer","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Team","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Structure","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Single SKILL.md","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Multiple component SKILL.md files","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"marketplace.json","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Not needed","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Optional (official fields only)","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/architecture-guide.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for detailed decision framework.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Cross-Platform Support","type":"text"}]},{"type":"paragraph","content":[{"text":"Generated skills work across 20+ tools in 3 tiers. Every generated skill outputs both ","type":"text"},{"text":"SKILL.md","type":"text","marks":[{"type":"strong"}]},{"text":" (skill definition, ~15 tools) and ","type":"text"},{"text":"AGENTS.md","type":"text","marks":[{"type":"strong"}]},{"text":" (instruction file, ~15 tools) to maximize reach.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Tier 1 — Native SKILL.md (reads directly, no conversion)","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Platform","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Native Global Path","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Native Project Path","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Command","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Claude Code","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~/.claude/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".claude/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"./install.sh","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"GitHub Copilot","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~/.copilot/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".github/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"./install.sh --platform copilot","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Codex CLI","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~/.agents/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".agents/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"./install.sh --platform codex","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Gemini CLI","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~/.gemini/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".gemini/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"./install.sh --platform gemini","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Kiro","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~/.kiro/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".kiro/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"./install.sh --platform kiro","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Goose","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~/.config/goose/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"—","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"./install.sh --platform goose","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OpenCode","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~/.config/opencode/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".opencode/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"./install.sh --platform opencode","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cline","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~/.cline/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".clinerules/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"./install.sh --platform cline","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Roo Code","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~/.roo/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".roo/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"./install.sh --platform roo-code","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Kilo Code","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~/.kilocode/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".kilocode/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"./install.sh --platform kilo-code","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Factory Droid","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~/.factory/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".factory/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"./install.sh --platform factory","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Antigravity","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"—","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".agent/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"./install.sh --platform antigravity","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Tier 2 — Auto-adapted (installer converts SKILL.md to native format)","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Platform","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Native Format","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Adaptation","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Install Path","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Command","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cursor","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".mdc","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Generates ","type":"text"},{"text":".mdc","type":"text","marks":[{"type":"code_inline"}]},{"text":" with ","type":"text"},{"text":"alwaysApply","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"globs","type":"text","marks":[{"type":"code_inline"}]},{"text":" frontmatter","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".cursor/skills/","type":"text","marks":[{"type":"code_inline"}]},{"text":" (project only, no global)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"./install.sh --platform cursor","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Windsurf","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".md","type":"text","marks":[{"type":"code_inline"}]},{"text":" rules","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Generates plain ","type":"text"},{"text":".md","type":"text","marks":[{"type":"code_inline"}]},{"text":" rule (6K char limit per file)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".windsurf/rules/","type":"text","marks":[{"type":"code_inline"}]},{"text":" (project) or ","type":"text"},{"text":"~/.codeium/windsurf/","type":"text","marks":[{"type":"code_inline"}]},{"text":" (global)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"./install.sh --platform windsurf","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trae","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".md","type":"text","marks":[{"type":"code_inline"}]},{"text":" rules","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Generates plain ","type":"text"},{"text":".md","type":"text","marks":[{"type":"code_inline"}]},{"text":" with ","type":"text"},{"text":"type:","type":"text","marks":[{"type":"code_inline"}]},{"text":" frontmatter","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".trae/rules/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"./install.sh --platform trae","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Junie","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"guidelines.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Extracts body as plain markdown","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".junie/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"./install.sh --platform junie","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Tier 3 — Manual integration","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Platform","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Config File","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Instructions","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Zed","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".rules","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Copy SKILL.md body into ","type":"text"},{"text":".rules","type":"text","marks":[{"type":"code_inline"}]},{"text":" file","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Augment","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".augment/rules/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Copy as ","type":"text"},{"text":".md","type":"text","marks":[{"type":"code_inline"}]},{"text":" with ","type":"text"},{"text":"type: Always","type":"text","marks":[{"type":"code_inline"}]},{"text":" frontmatter","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Aider","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CONVENTIONS.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Copy SKILL.md body into CONVENTIONS.md","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Continue.dev","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".continue/rules/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Copy as ","type":"text"},{"text":".md","type":"text","marks":[{"type":"code_inline"}]},{"text":" with Continue frontmatter","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Companion AGENTS.md","type":"text"}]},{"type":"paragraph","content":[{"text":"Every generated skill also outputs an ","type":"text"},{"text":"AGENTS.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" file alongside SKILL.md. This extends reach to tools that prioritize AGENTS.md over SKILL.md (Codex CLI, Augment, Continue.dev, Zed, and others). The AGENTS.md contains the skill's purpose, activation triggers, and usage instructions in the AAIF-governed format.","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/cross-platform-guide.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for full platform details.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Validation and Security","type":"text"}]},{"type":"paragraph","content":[{"text":"After generating a skill, run:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Spec validation","type":"text","marks":[{"type":"strong"}]},{"text":": Checks frontmatter, naming, structure, line count","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Security scan","type":"text","marks":[{"type":"strong"}]},{"text":": Checks for hardcoded API keys, .env files, injection patterns","type":"text"}]}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Validate a skill\npython3 scripts/validate.py path/to/skill/\n\n# Security scan\npython3 scripts/security_scan.py path/to/skill/","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Export System","type":"text"}]},{"type":"paragraph","content":[{"text":"Package skills for distribution:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Export for all platforms\npython3 scripts/export_utils.py path/to/skill/\n\n# Desktop/Web package only\npython3 scripts/export_utils.py path/to/skill/ --variant desktop\n\n# API package only\npython3 scripts/export_utils.py path/to/skill/ --variant api","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/export-guide.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for full export documentation.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Template-Based Creation","type":"text"}]},{"type":"paragraph","content":[{"text":"Pre-built templates for common domains:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Financial Analysis","type":"text","marks":[{"type":"strong"}]},{"text":": Alpha Vantage/Yahoo Finance, fundamental + technical analysis","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Climate Analysis","type":"text","marks":[{"type":"strong"}]},{"text":": Open-Meteo/NOAA, anomalies + trends + seasonal patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"E-commerce Analytics","type":"text","marks":[{"type":"strong"}]},{"text":": Google Analytics/Stripe/Shopify, traffic + revenue + cohorts","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/templates-guide.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for template details and customization.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Multi-Agent Suites","type":"text"}]},{"type":"paragraph","content":[{"text":"Create multiple related agents in one operation:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"\"Create a financial analysis suite with 4 agents:\nfundamental, technical, portfolio, and risk assessment\"","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/multi-agent-guide.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for suite creation docs.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Interactive Configuration","type":"text"}]},{"type":"paragraph","content":[{"text":"Step-by-step wizard for complex projects:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"\"Help me create an agent with interactive options\"\n\"Walk me through creating a financial analysis system\"","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/interactive-mode.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for wizard documentation.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"AgentDB Integration","type":"text"}]},{"type":"paragraph","content":[{"text":"Optional learning system that gets smarter over time:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Stores creation episodes for pattern learning","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Progressively improves API selection, architecture, and keywords","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Works identically with or without AgentDB available","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/agentdb-integration.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for integration details.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quality Standards","type":"text"}]},{"type":"paragraph","content":[{"text":"Always","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Complete, functional code (no TODOs, no ","type":"text"},{"text":"pass","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Detailed docstrings and type hints","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Robust error handling","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Real content in references (not \"see docs\")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Configs with real values","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Never","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Placeholder code or empty functions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"api_key: YOUR_KEY_HERE","type":"text","marks":[{"type":"code_inline"}]},{"text":" without env var instructions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"SKILL.md over 500 lines","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Platform-specific hacks","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/quality-standards.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for complete standards.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Naming Convention","type":"text"}]},{"type":"paragraph","content":[{"text":"Every generated skill name must end with ","type":"text"},{"text":"-skill","type":"text","marks":[{"type":"code_inline"}]},{"text":". This suffix makes skills instantly discoverable across GitHub and GitLab organizations — teams can search ","type":"text"},{"text":"*-skill","type":"text","marks":[{"type":"code_inline"}]},{"text":" and find every skill in their org.","type":"text"}]},{"type":"paragraph","content":[{"text":"Format","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"{domain}-{objective}-skill","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"Rules","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Must end with ","type":"text"},{"text":"-skill","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"1-64 characters total, lowercase letters, numbers, and hyphens","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Must match parent directory name","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Must not contain consecutive hyphens","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Examples","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"sales-report-skill","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"csv-cleaner-skill","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"deploy-checklist-skill","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"stock-analyzer-skill","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"Suites","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"{domain}-suite","type":"text","marks":[{"type":"code_inline"}]},{"text":" (suites are not suffixed with ","type":"text"},{"text":"-skill","type":"text","marks":[{"type":"code_inline"}]},{"text":" — they contain skills)","type":"text"}]},{"type":"paragraph","content":[{"text":"The ","type":"text"},{"text":"-skill","type":"text","marks":[{"type":"code_inline"}]},{"text":" suffix also serves as a signal to the agent: when it sees a repo or directory ending in ","type":"text"},{"text":"-skill","type":"text","marks":[{"type":"code_inline"}]},{"text":", it knows this is installable, invocable software — not documentation or a regular project.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Reference Files","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Contents","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/pipeline-phases.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Detailed Phase 1-5 instructions","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/architecture-guide.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Simple vs Suite decision, refactoring, cross-component communication, versioning","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/templates-guide.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Template-based creation","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/interactive-mode.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Interactive wizard docs","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/multi-agent-guide.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Suite creation, orchestration patterns, routing logic","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/agentdb-integration.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"AgentDB learning system","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/cross-platform-guide.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Platform compatibility matrix","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/export-guide.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cross-platform export system","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/quality-standards.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Quality standards, dependency management, testing strategy","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/phase4-detection.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Detection & keyword-design craft reference","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/phase2-eval-assessment.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Phase 2 eval-criteria step, golden-case strategy, spec format, autoresearch handoff","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/phase5-orchestration.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Phase 5 pipeline orchestration: single run_pipeline.py entry-point, deterministic sequencing, check_pipeline.py","type":"text"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"agent-skill-creator","author":"@skillopedia","source":{"stars":1277,"repo_name":"agent-skill-creator","origin_url":"https://github.com/francyjglisboa/agent-skill-creator/blob/HEAD/SKILL.md","repo_owner":"francyjglisboa","body_sha256":"994cb858f1989b922b08d83571ea9cd5382d5e51f2ebdcc02951380957413ca6","cluster_key":"b146303e9611b0f791cddbc1f3ef72472936196e1548cc3845b8e311e1e622cb","clean_bundle":{"format":"clean-skill-bundle-v1","source":"francyjglisboa/agent-skill-creator/SKILL.md","attachments":[{"id":"f60b834b-adc6-5f19-b068-1a4c5622336e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f60b834b-adc6-5f19-b068-1a4c5622336e/attachment","path":".gitignore","size":592,"sha256":"9be307aac14b777717bae0e1b6965a009a1c2e988d9d742f5d94bf099452837d","contentType":"text/plain; charset=utf-8"},{"id":"c46752f0-2e52-5dfb-8854-cb05ec278ef1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c46752f0-2e52-5dfb-8854-cb05ec278ef1/attachment.md","path":"CHANGELOG.md","size":1438,"sha256":"d0868938e58645f7641c47aeeaa05e0dc32a3d617e4f0c051ba78017f2af3405","contentType":"text/markdown; charset=utf-8"},{"id":"3d4864ec-c95b-545d-980c-76a9041eb43c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3d4864ec-c95b-545d-980c-76a9041eb43c/attachment.md","path":"CONTRIBUTING.md","size":1649,"sha256":"b599c276a747b50d690afd1a98d1eb0c3098c01178bea7822c27e8cb1137e186","contentType":"text/markdown; charset=utf-8"},{"id":"fe73db34-8fdb-5461-b350-54b2abf9e578","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fe73db34-8fdb-5461-b350-54b2abf9e578/attachment.md","path":"LEARNINGS.md","size":1391,"sha256":"ba7fd6068fcacfa017eb247af12c237e38af6e393576693fc9e84a808d15932b","contentType":"text/markdown; charset=utf-8"},{"id":"aa462d1e-251b-516d-982e-a7ab80ec45d0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/aa462d1e-251b-516d-982e-a7ab80ec45d0/attachment.md","path":"README.md","size":27974,"sha256":"dca0b7de80bb98c3b26298de0a12e156421969309d6f63697275211a006ff42e","contentType":"text/markdown; charset=utf-8"},{"id":"f19e16cf-abbb-5437-aeef-28ead2a5c503","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f19e16cf-abbb-5437-aeef-28ead2a5c503/attachment.md","path":"docs/superpowers/plans/2026-05-27-agent-skill-creator-v5-artifacts-first.md","size":69788,"sha256":"5d706698481c5e47404661657af1990ac3f69dbc0fc605d8b17986ed72a8f452","contentType":"text/markdown; charset=utf-8"},{"id":"afdaf922-316e-5a94-92fb-c49a58d781a0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/afdaf922-316e-5a94-92fb-c49a58d781a0/attachment.md","path":"docs/superpowers/specs/2026-05-27-agent-skill-creator-v5-artifacts-first-design.md","size":21644,"sha256":"ba339253f2239e4e49678ee911895a029511eaa55f9c48e219b59a4b472fba8b","contentType":"text/markdown; charset=utf-8"},{"id":"7aa9a567-15bd-5585-8fb0-a36baebef3ae","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7aa9a567-15bd-5585-8fb0-a36baebef3ae/attachment.md","path":"docs/superpowers/verification/2026-05-27-v5-test-summary.md","size":6347,"sha256":"72782c47bc607fcf2154b676aef52710060c146fd7a344d94765da16c63476c6","contentType":"text/markdown; charset=utf-8"},{"id":"2a5ba863-90bd-57fe-a50b-6e74f9001da9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2a5ba863-90bd-57fe-a50b-6e74f9001da9/attachment","path":"exports/.gitignore","size":124,"sha256":"1c0d678d4fad3afbe058d82cc34cccfda70685418980fe6ce09733f366c119e0","contentType":"text/plain; charset=utf-8"},{"id":"ef7b6d4b-8d9b-5518-aa4e-71c0c9c9f60b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ef7b6d4b-8d9b-5518-aa4e-71c0c9c9f60b/attachment.ps1","path":"install.ps1","size":8342,"sha256":"e7f1a3d37efc3f13bdb88cd98058f221406836557378462695234951b27858e2","contentType":"text/plain; charset=utf-8"},{"id":"0e51187b-b823-570a-8a0a-038a4d6b6176","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0e51187b-b823-570a-8a0a-038a4d6b6176/attachment.sh","path":"install.sh","size":7311,"sha256":"4f2fc655fdd14a2bc5c9e41b7d0df6d8551e5f2375ba2f9349fe0e8fff9a990e","contentType":"application/x-sh; charset=utf-8"},{"id":"3954f3a9-de5a-51d6-a25f-cc24e1ea0120","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3954f3a9-de5a-51d6-a25f-cc24e1ea0120/attachment.md","path":"references/agentdb-integration.md","size":10403,"sha256":"9b60dbc122fae08b202e655cc9478500a48e248ec53c00541dc841cabffc1e58","contentType":"text/markdown; charset=utf-8"},{"id":"353b475c-ba4d-59c6-9229-47c51bf9d06d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/353b475c-ba4d-59c6-9229-47c51bf9d06d/attachment.md","path":"references/architecture-guide.md","size":35056,"sha256":"fa72a02d8b86dcb66090e5b56fe449073e87dc787598e4996e07a86825fe0b0b","contentType":"text/markdown; charset=utf-8"},{"id":"72d5ac64-99a2-5367-a436-c5a18cfa0fc5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/72d5ac64-99a2-5367-a436-c5a18cfa0fc5/attachment.jsx","path":"references/artifact-templates/bar-chart.jsx","size":856,"sha256":"43b2ce4360880fd9995aa18144d8efa77a66465113eb19074ef44048486c17a3","contentType":"application/javascript; charset=utf-8"},{"id":"316b919e-bd19-5bd5-a0f1-6ec323a522b7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/316b919e-bd19-5bd5-a0f1-6ec323a522b7/attachment.jsx","path":"references/artifact-templates/data-table.jsx","size":1136,"sha256":"9abef6cda393f4c65466482bc6db395eaa58493e9ceb23e194301b4cc76b09d4","contentType":"application/javascript; charset=utf-8"},{"id":"42fd702f-236d-5a50-8fda-731e213a668c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/42fd702f-236d-5a50-8fda-731e213a668c/attachment.jsx","path":"references/artifact-templates/kpi-cards.jsx","size":950,"sha256":"ff889047183112ea7686950030b0ed077fc9e77c69fac70177f63b5ebe07eeeb","contentType":"application/javascript; charset=utf-8"},{"id":"d776603e-cc76-5fc8-9115-83f59c4472df","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d776603e-cc76-5fc8-9115-83f59c4472df/attachment.jsx","path":"references/artifact-templates/line-chart.jsx","size":1004,"sha256":"ec3a85ba045bdc88fc35776361ac1ff605eb8775b9dbe9426623579b30e57501","contentType":"application/javascript; charset=utf-8"},{"id":"007a2d18-168b-5f4d-855a-c3b386280106","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/007a2d18-168b-5f4d-855a-c3b386280106/attachment.md","path":"references/claude-artifact-format.md","size":1796,"sha256":"4b8686a1da7d74a46428f349e09a7d5b36b50b41e253b986147bf3b2176f9a5e","contentType":"text/markdown; charset=utf-8"},{"id":"aac33379-2d23-5d9f-9fb8-87fc6bd40f3b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/aac33379-2d23-5d9f-9fb8-87fc6bd40f3b/attachment.md","path":"references/cross-platform-guide.md","size":13326,"sha256":"4dd225d619a498f28ee2c34783a504eeaf5a54082e91d41c10c79cf138863a76","contentType":"text/markdown; charset=utf-8"},{"id":"f5a26cca-d2f0-511b-9d50-7af591d40d3c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f5a26cca-d2f0-511b-9d50-7af591d40d3c/attachment.md","path":"references/export-guide.md","size":13034,"sha256":"9db83cbb25aee2d023a7a6efbf9794ce2b5fd3c9f80bb66c02e5d38a44a4c5a7","contentType":"text/markdown; charset=utf-8"},{"id":"34417a89-b410-5f2e-a959-ec7d70ef6534","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/34417a89-b410-5f2e-a959-ec7d70ef6534/attachment.md","path":"references/interactive-mode.md","size":8631,"sha256":"3d22ee77c38761e0edb07b754c9296a1854a9e71f39116179bd71e612c51ce37","contentType":"text/markdown; charset=utf-8"},{"id":"63d6e5ac-2f54-597d-8bb7-f9561e264c2e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/63d6e5ac-2f54-597d-8bb7-f9561e264c2e/attachment.md","path":"references/multi-agent-guide.md","size":15812,"sha256":"db50cf8052968bbbadbfdb0616249d0167ae0bcc3c921cf8834d24ab63cbc047","contentType":"text/markdown; charset=utf-8"},{"id":"dd2de041-df87-5e83-b792-0f1130bf8978","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/dd2de041-df87-5e83-b792-0f1130bf8978/attachment.md","path":"references/phase2-artifact-assessment.md","size":4001,"sha256":"99cdc61ddf60a27e2da2ec805d033d88bfa711b47b149e45ba0b53b74057ddf2","contentType":"text/markdown; charset=utf-8"},{"id":"9dbe5ca2-2be6-590d-84bc-d69b1e82629a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9dbe5ca2-2be6-590d-84bc-d69b1e82629a/attachment.md","path":"references/phase2-eval-assessment.md","size":7495,"sha256":"2b8416379654b621e2841513f892c8966552b894e7000a5cd88843203306b4ce","contentType":"text/markdown; charset=utf-8"},{"id":"1392527c-b187-500b-845a-44c2063d5006","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1392527c-b187-500b-845a-44c2063d5006/attachment.md","path":"references/phase4-detection.md","size":27505,"sha256":"7dee7d817aa8eab9c016c8866020dc9caa4067ddfc1bbc3204ae27ae26903fcd","contentType":"text/markdown; charset=utf-8"},{"id":"15817978-8273-5b59-aa17-0b5df06b33d9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/15817978-8273-5b59-aa17-0b5df06b33d9/attachment.md","path":"references/phase5-orchestration.md","size":5465,"sha256":"61933ff8e447f1f22e6d31509e0bbd463bab0178d77dc5cddc26e15d419363ad","contentType":"text/markdown; charset=utf-8"},{"id":"95afa21e-f634-5848-8288-aa55a74570ae","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/95afa21e-f634-5848-8288-aa55a74570ae/attachment.md","path":"references/pipeline-phases.md","size":50434,"sha256":"1949620d129e6a3f9a76ad6f6a16bf4b6b2eeb7e949ff4321035f1462074ec59","contentType":"text/markdown; charset=utf-8"},{"id":"330babc7-d7ad-5041-b663-1a20c7a4461d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/330babc7-d7ad-5041-b663-1a20c7a4461d/attachment.md","path":"references/quality-standards.md","size":29373,"sha256":"bf8c1ab809258528d4045b1bcf832231813bec85b92828a95c52881e8a0a65d9","contentType":"text/markdown; charset=utf-8"},{"id":"224c64b7-708f-533c-9b93-7f1ab6b73af4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/224c64b7-708f-533c-9b93-7f1ab6b73af4/attachment.md","path":"references/templates-guide.md","size":9111,"sha256":"5151a3c3d5be5c3ae4c28d2eb7aa7cbd422d1dcf2239498cd39490d23751029e","contentType":"text/markdown; charset=utf-8"},{"id":"c7711729-fee6-591b-a024-49253094d5d1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c7711729-fee6-591b-a024-49253094d5d1/attachment.md","path":"references/templates/README-activation-template.md","size":6090,"sha256":"e81874c16403e998d41e07e8abeec5acdfb1a9a2aec65737139b0dafcc765ad9","contentType":"text/markdown; charset=utf-8"},{"id":"166f6aa5-02db-536c-8272-aa46ea53afb5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/166f6aa5-02db-536c-8272-aa46ea53afb5/attachment.json","path":"registry/registry.json","size":139,"sha256":"5683ba3dffb3833448307973c41abbd6dd5373ff45328881b8488e9fe345facf","contentType":"application/json; charset=utf-8"},{"id":"d2f482fa-7cfe-5fcf-9ea5-15a004ae355e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d2f482fa-7cfe-5fcf-9ea5-15a004ae355e/attachment","path":"registry/skills/.gitkeep","size":0,"sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","contentType":"text/plain; charset=utf-8"},{"id":"ba73d86c-d954-53dc-82be-092a5b3b847e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ba73d86c-d954-53dc-82be-092a5b3b847e/attachment.py","path":"scripts/artifact_detector.py","size":3519,"sha256":"0bcf013d5270b71da7421fa1f2128c0d5fc9cbc61a49a96d1b8c7cf5493a40ed","contentType":"text/x-python; charset=utf-8"},{"id":"1c30376b-ae81-59d6-9696-d52b0b88d344","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1c30376b-ae81-59d6-9696-d52b0b88d344/attachment.bat","path":"scripts/bootstrap.bat","size":1109,"sha256":"3c0f9f6e18c61334ff8114ae84a3608b5b835c1f377f11bf2bd7624f4df84efa","contentType":"application/x-msdos-program"},{"id":"1ca50794-c299-5bc4-a7f0-815d4bf2fbf7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1ca50794-c299-5bc4-a7f0-815d4bf2fbf7/attachment.ps1","path":"scripts/bootstrap.ps1","size":9543,"sha256":"100ed8bc198ea9e1b1cd77175275fa470491ccf60f1e227ba95dc8fcf0180595","contentType":"text/plain; charset=utf-8"},{"id":"90c83a4f-f94b-5ebb-8b30-531e287457be","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/90c83a4f-f94b-5ebb-8b30-531e287457be/attachment.sh","path":"scripts/bootstrap.sh","size":7361,"sha256":"0af96b3584bf873be3b5bd86011ecb646bda4fc37f53bf204a283d1651caf0e6","contentType":"application/x-sh; charset=utf-8"},{"id":"fb61ce59-9be2-5efe-ae36-6d8fdb2afd8e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fb61ce59-9be2-5efe-ae36-6d8fdb2afd8e/attachment.py","path":"scripts/check_pipeline.py","size":6635,"sha256":"2b4d4d4b26035777e0324f5aabc2f4528383cd09eb224ec1fbffaccee82e7b53","contentType":"text/x-python; charset=utf-8"},{"id":"72fb1234-0df1-57e7-8258-19cd58627296","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/72fb1234-0df1-57e7-8258-19cd58627296/attachment.py","path":"scripts/dependency_health.py","size":3178,"sha256":"f8f4d9f9ff825b100722a6ba21fa3263fc25b6c63c726798df6223feb39dec4f","contentType":"text/x-python; charset=utf-8"},{"id":"2dcd3393-c0ed-56eb-a853-8f2802f820fe","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2dcd3393-c0ed-56eb-a853-8f2802f820fe/attachment.py","path":"scripts/export_utils.py","size":21604,"sha256":"c37cb1f5729278b8bc7026bfbd9229eb76e2f4e38beb04f5971d35610b22eb6c","contentType":"text/x-python; charset=utf-8"},{"id":"0e009cb1-9d86-5f96-a926-c09b88ee77c3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0e009cb1-9d86-5f96-a926-c09b88ee77c3/attachment.ps1","path":"scripts/install-skill.ps1","size":20482,"sha256":"c71214d65f03bf5703dbeb47b846bb81fa67effddb96f30669965393d8f017a4","contentType":"text/plain; charset=utf-8"},{"id":"1b3b1d6a-1f5d-5888-abf4-7596e2219947","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1b3b1d6a-1f5d-5888-abf4-7596e2219947/attachment.sh","path":"scripts/install-skill.sh","size":19605,"sha256":"28c0babee5ac5dedf2f9f369fcc1a92e623f59e77ebf23787524db7f3a1bd8ee","contentType":"application/x-sh; charset=utf-8"},{"id":"5743757c-def7-56f0-ac81-149f39e0afab","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5743757c-def7-56f0-ac81-149f39e0afab/attachment.ps1","path":"scripts/install-template.ps1","size":24763,"sha256":"60d1df7b74b833b4dd939564d00f3216a2b1d6c90a795c1f03c062b4290955ca","contentType":"text/plain; charset=utf-8"},{"id":"a3b1b34c-cd89-54cc-9b6b-cdedd0bc14f0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a3b1b34c-cd89-54cc-9b6b-cdedd0bc14f0/attachment.sh","path":"scripts/install-template.sh","size":32124,"sha256":"fe804a3fb8fa4fecc0740c433294a1c88e6eadf81f7f7b4dfe84fecf728e59b2","contentType":"application/x-sh; charset=utf-8"},{"id":"4ba255f5-9db1-5025-bb8d-77e8f23380fa","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4ba255f5-9db1-5025-bb8d-77e8f23380fa/attachment.py","path":"scripts/platforms.py","size":3405,"sha256":"10575a32beb1b3d264ae2d6b593cefe50d58398a94bc232a6ccd26dda4f3aa64","contentType":"text/x-python; charset=utf-8"},{"id":"3e3e0b8e-3e37-59f2-9611-cf084285b28e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3e3e0b8e-3e37-59f2-9611-cf084285b28e/attachment.py","path":"scripts/review_staleness.py","size":5771,"sha256":"b69dbe2116a4ea5a5bfab87f73a1989b6a1dbd82e6b40f384495a871daf2a8b1","contentType":"text/x-python; charset=utf-8"},{"id":"5ab3a3be-1c53-5fa2-ae60-bf8ed478556a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5ab3a3be-1c53-5fa2-ae60-bf8ed478556a/attachment.py","path":"scripts/run_evals_template.py","size":10045,"sha256":"947f013b537f519d2101dfac5d88c5eb28291a2a4e76cee21696e17508b6c1f2","contentType":"text/x-python; charset=utf-8"},{"id":"73b397c5-6951-5706-85ef-0a0ff4688574","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/73b397c5-6951-5706-85ef-0a0ff4688574/attachment.py","path":"scripts/schema_drift.py","size":6997,"sha256":"59acf0a94fad8edc248636c050f463f201a4e1dcb0deab4dea28cbf8035731df","contentType":"text/x-python; charset=utf-8"},{"id":"3b6ad05d-8722-500e-b1be-b7d86788f2ab","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3b6ad05d-8722-500e-b1be-b7d86788f2ab/attachment.py","path":"scripts/security_scan.py","size":13261,"sha256":"bc5076ca4cee80a4ca2ff2985d5a5bc38264546c5c3fc1eda6a60feb13404149","contentType":"text/x-python; charset=utf-8"},{"id":"7607a953-f3b6-5efb-9fcb-5c567dfd921e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7607a953-f3b6-5efb-9fcb-5c567dfd921e/attachment.py","path":"scripts/skill_document.py","size":8617,"sha256":"ad796429d2588f2842177e6256f135100c05768b9f75c7a4ffcfff24c8563c35","contentType":"text/x-python; charset=utf-8"},{"id":"b414ed4d-92f0-5621-a3c8-863256be8266","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b414ed4d-92f0-5621-a3c8-863256be8266/attachment.py","path":"scripts/skill_registry.py","size":27254,"sha256":"96fa1622c5f6f1828e548847b77247019bd3ef354076950bdf119812440e2ea3","contentType":"text/x-python; charset=utf-8"},{"id":"36b20367-6a55-5ef1-914b-c195549d5a11","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/36b20367-6a55-5ef1-914b-c195549d5a11/attachment.py","path":"scripts/staleness_check.py","size":6326,"sha256":"1e35a263a2a147da01583daf48a09183d3743c7afe9c0dac633e21416ff2fab7","contentType":"text/x-python; charset=utf-8"},{"id":"a7944887-13ab-5cb5-9c6a-149df4a0ffad","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a7944887-13ab-5cb5-9c6a-149df4a0ffad/attachment.py","path":"scripts/tests/__init__.py","size":0,"sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","contentType":"text/x-python; charset=utf-8"},{"id":"611acb7b-0811-5548-8297-47231b3ffe66","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/611acb7b-0811-5548-8297-47231b3ffe66/attachment.json","path":"scripts/tests/fixtures/labeled_examples.json","size":3934,"sha256":"e000954e99d442b3003af5ee440259fffb996f7558778d56d32a062cc4f4b094","contentType":"application/json; charset=utf-8"},{"id":"2a2f162f-81bf-50c1-aa2e-43d6cd760b1d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2a2f162f-81bf-50c1-aa2e-43d6cd760b1d/attachment.json","path":"scripts/tests/fixtures/v4_skills_regression.json","size":2729,"sha256":"3b2bb8afb66fe1c5034e233a92a290cfbf8c0d7f472e0dc5cad807b641539de7","contentType":"application/json; charset=utf-8"},{"id":"a7b6abb7-590a-5f67-8194-4b8d07af6504","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a7b6abb7-590a-5f67-8194-4b8d07af6504/attachment.py","path":"scripts/tests/test_artifact_detector.py","size":5089,"sha256":"ce5d0abfe401b1bfa7d3340cd01b26fac0461704bbcd71d68f061044f9d0ba8c","contentType":"text/x-python; charset=utf-8"},{"id":"c5834176-d8fd-5d1d-9054-91f9f19023dc","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c5834176-d8fd-5d1d-9054-91f9f19023dc/attachment.py","path":"scripts/tests/test_check_pipeline.py","size":4280,"sha256":"005d05b9ab4a467a9bf70e6ea88308fb2ba2fa028095348b851529471ebc7588","contentType":"text/x-python; charset=utf-8"},{"id":"68c4f9b6-2e2a-58f3-a182-1ee808fac697","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/68c4f9b6-2e2a-58f3-a182-1ee808fac697/attachment.py","path":"scripts/tests/test_dependency_health.py","size":2761,"sha256":"dc439d9806c15293083c0262cbd8a0f13038dbb7c1bc73fc732d07e4f874fed2","contentType":"text/x-python; charset=utf-8"},{"id":"410e0143-b29f-56d2-abb9-e78da26ee945","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/410e0143-b29f-56d2-abb9-e78da26ee945/attachment.py","path":"scripts/tests/test_install_parity.py","size":8598,"sha256":"e3bc6db84a1a11885bb85baf4fc42901381a2c7183173a07c1b713c33f271497","contentType":"text/x-python; charset=utf-8"},{"id":"9296ab5a-bc4e-54bf-b934-2f59ebdaa6d0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9296ab5a-bc4e-54bf-b934-2f59ebdaa6d0/attachment.py","path":"scripts/tests/test_phase2_integration.py","size":2553,"sha256":"c867be9d9636bb855b5df4231a7e1f309b0ae90450f0807ae0025d5eeb59dd5a","contentType":"text/x-python; charset=utf-8"},{"id":"f7ecbab6-5f4b-5825-8c5d-4f26cbd17eb1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f7ecbab6-5f4b-5825-8c5d-4f26cbd17eb1/attachment.py","path":"scripts/tests/test_platforms.py","size":3829,"sha256":"44d83bff4b2e94cbd8355ab1215f5b0eb2cbdf20d0ce4ac72d7a7193bf6e2f71","contentType":"text/x-python; charset=utf-8"},{"id":"a4552c3c-9394-5598-8637-03ed9ad036b1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a4552c3c-9394-5598-8637-03ed9ad036b1/attachment.py","path":"scripts/tests/test_review_staleness.py","size":4460,"sha256":"68f66e3f7ab8d0e585e06491af0afdd918eddffde6af7f5c8bc9283d5ce22c66","contentType":"text/x-python; charset=utf-8"},{"id":"c994235a-930f-5b8f-b7bb-eebba87bce76","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c994235a-930f-5b8f-b7bb-eebba87bce76/attachment.py","path":"scripts/tests/test_run_evals.py","size":7593,"sha256":"682202562ed8cb1faebd0c568805aaeb8d854f5c8a71ab32b1777c43daf6797e","contentType":"text/x-python; charset=utf-8"},{"id":"c7c14ab7-6978-545e-b77d-45b226795dd3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c7c14ab7-6978-545e-b77d-45b226795dd3/attachment.py","path":"scripts/tests/test_schema_drift.py","size":4872,"sha256":"5c34c684d96c07857e1498e0ccbf87190a08de3ca0f06c42aec316b67a8cd56b","contentType":"text/x-python; charset=utf-8"},{"id":"70274048-36b2-55d2-b37c-a7ac23e5151b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/70274048-36b2-55d2-b37c-a7ac23e5151b/attachment.py","path":"scripts/tests/test_skill_document.py","size":5452,"sha256":"80466d6d9cb6dbdfd3f71b4f551712419a076e7ccfa73f4e37782529a08259f1","contentType":"text/x-python; charset=utf-8"},{"id":"14708e26-3e46-58ad-ae95-5157a7684d79","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/14708e26-3e46-58ad-ae95-5157a7684d79/attachment.py","path":"scripts/tests/test_template_structure.py","size":2864,"sha256":"2d93ac80a672c58c62523dd213ff656cc4126b9ef01943ee91cbcd7dcfba49cd","contentType":"text/x-python; charset=utf-8"},{"id":"df5d60cd-4e96-56f1-bdd9-f2ffb9c2756b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/df5d60cd-4e96-56f1-bdd9-f2ffb9c2756b/attachment.py","path":"scripts/tests/test_v4_regression.py","size":3913,"sha256":"96ad1de19ec86d41780d3b0fecbfa0bbc31370ece4e4d7a015b74b5f95f62f8e","contentType":"text/x-python; charset=utf-8"},{"id":"8b504780-5d9a-57e0-9837-3e3b22d2a10a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8b504780-5d9a-57e0-9837-3e3b22d2a10a/attachment.py","path":"scripts/validate.py","size":12024,"sha256":"733d90338f4616832b1c783c626887d70220e58debfa1e3d97f0952587538349","contentType":"text/x-python; charset=utf-8"}],"bundle_sha256":"c958a3a0792deec2931eaa5288b179575cc38f19b38357a9229c92e22fcc5ee9","attachment_count":67,"text_attachments":58,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":9,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"web-development","category_label":"Web"},"exact_dupes_collapsed_into_this":0},"license":"MIT","version":"v1","category":"web-development","metadata":{"author":"Francy Lisboa Charuto","version":"6.0.0"},"import_tag":"clean-skills-v1","description":"Create cross-platform agent skills from workflow descriptions. Activates when users ask to create an agent, automate a repetitive workflow, create a custom skill, or need advanced agent creation. Triggers on phrases like create agent for, automate workflow, create skill for, every day I have to, daily I need to, turn process into agent, need to automate, create a cross-platform skill, validate this skill, export this skill, migrate this skill. Supports single skills, multi-agent suites, transcript processing, template-based creation, interactive configuration, cross-platform export, and spec validation.","compatibility":"Works on all platforms supporting the Agent Skills Open Standard (SKILL.md): Claude Code, GitHub Copilot CLI, VS Code Copilot, Cursor, Windsurf, Cline, OpenAI Codex CLI, Gemini CLI, and 20+ others."}},"renderedAt":1782987307921}

/agent-skill-creator — Level 5 Skill Dark Factory You are an autonomous skill factory. You exist because humans are cognitively incapable of writing specifications clear enough for an agent to build from without intervention. A human-written spec will never reach Level 5 — it will always be incomplete, ambiguous, and missing the requirements the human assumed were obvious. That is not a flaw to fix. That is the design constraint this factory is built around. The user provides raw material — workflow descriptions, documentation, links, existing code, API docs, PDFs, database schemas, transcrip…