diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f8cb3c0..2a7c021a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -220,6 +220,10 @@ jobs: run: node scripts/ci/catalog.js --text continue-on-error: false + - name: Validate command registry + run: npm run command-registry:check + continue-on-error: false + - name: Check unicode safety run: node scripts/ci/check-unicode-safety.js continue-on-error: false @@ -253,6 +257,33 @@ jobs: - name: Run supply-chain IOC scan run: npm run security:ioc-scan + coverage: + name: Coverage + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup Node.js + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: '20.x' + + - name: Install dependencies + run: npm ci --ignore-scripts + + - name: Run coverage + run: npm run coverage + + - name: Upload coverage report + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-ubuntu-node20-npm + path: coverage/ + lint: name: Lint runs-on: ubuntu-latest diff --git a/README.md b/README.md index 663c8fcc..a7c5ee8f 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ This repo is the raw code only. The guides explain everything. ### v2.0.0-rc.1 — Surface Refresh, Operator Workflows, and ECC 2.0 Alpha (Apr 2026) - **Dashboard GUI** — New Tkinter-based desktop application (`ecc_dashboard.py` or `npm run dashboard`) with dark/light theme toggle, font customization, and project logo in header and taskbar. -- **Public surface synced to the live repo** — metadata, catalog counts, plugin manifests, and install-facing docs now match the actual OSS surface: 55 agents, 208 skills, and 72 legacy command shims. +- **Public surface synced to the live repo** — metadata, catalog counts, plugin manifests, and install-facing docs now match the actual OSS surface: 60 agents, 229 skills, and 75 legacy command shims. - **Operator and outbound workflow expansion** — `brand-voice`, `social-graph-ranker`, `connections-optimizer`, `customer-billing-ops`, `ecc-tools-cost-audit`, `google-workspace-ops`, `project-flow-ops`, and `workspace-surface-audit` round out the operator lane. - **Media and launch tooling** — `manim-video`, `remotion-video-creation`, and upgraded social publishing surfaces make technical explainers and launch content part of the same system. - **Framework and product surface growth** — `nestjs-patterns`, richer Codex/OpenCode install surfaces, and expanded cross-harness packaging keep the repo usable beyond Claude Code alone. diff --git a/docs/COMMAND-REGISTRY.json b/docs/COMMAND-REGISTRY.json new file mode 100644 index 00000000..0f9e4de2 --- /dev/null +++ b/docs/COMMAND-REGISTRY.json @@ -0,0 +1,898 @@ +{ + "schemaVersion": 1, + "totalCommands": 75, + "commands": [ + { + "command": "aside", + "description": "Answer a quick side question without interrupting or losing context from the current task. Resume work automatically after answering.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/aside.md" + }, + { + "command": "auto-update", + "description": "Pull the latest ECC repo changes and reinstall the current managed targets.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/auto-update.md" + }, + { + "command": "build-fix", + "description": "Detect the project build system and incrementally fix build/type errors with minimal safe changes.", + "type": "refactoring", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/build-fix.md" + }, + { + "command": "checkpoint", + "description": "Create, verify, or list workflow checkpoints after running verification checks.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/checkpoint.md" + }, + { + "command": "code-review", + "description": "Code review — local uncommitted changes or GitHub PR (pass PR number/URL for PR mode)", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/code-review.md" + }, + { + "command": "cost-report", + "description": "Generate a local Claude Code cost report from a cost-tracker SQLite database.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/cost-report.md" + }, + { + "command": "cpp-build", + "description": "Fix C++ build errors, CMake issues, and linker problems incrementally. Invokes the cpp-build-resolver agent for minimal, surgical fixes.", + "type": "testing", + "primaryAgents": [ + "cpp-build-resolver" + ], + "allAgents": [ + "cpp-build-resolver" + ], + "skills": [ + "cpp-coding-standards" + ], + "path": "commands/cpp-build.md" + }, + { + "command": "cpp-review", + "description": "Comprehensive C++ code review for memory safety, modern C++ idioms, concurrency, and security. Invokes the cpp-reviewer agent.", + "type": "testing", + "primaryAgents": [ + "cpp-reviewer" + ], + "allAgents": [ + "cpp-reviewer" + ], + "skills": [ + "cpp-coding-standards", + "cpp-testing" + ], + "path": "commands/cpp-review.md" + }, + { + "command": "cpp-test", + "description": "Enforce TDD workflow for C++. Write GoogleTest tests first, then implement. Verify coverage with gcov/lcov.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [ + "cpp-testing", + "tdd-workflow" + ], + "path": "commands/cpp-test.md" + }, + { + "command": "ecc-guide", + "description": "Navigate ECC's current agents, skills, commands, hooks, install profiles, and docs from the live repository surface.", + "type": "review", + "primaryAgents": [], + "allAgents": [], + "skills": [ + "ecc-guide", + "security-scan" + ], + "path": "commands/ecc-guide.md" + }, + { + "command": "evolve", + "description": "Analyze instincts and suggest or generate evolved structures", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [ + "continuous-learning-v2" + ], + "path": "commands/evolve.md" + }, + { + "command": "fastapi-review", + "description": "Review a FastAPI application for architecture, async correctness, dependency injection, Pydantic schemas, security, performance, and testability.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/fastapi-review.md" + }, + { + "command": "feature-dev", + "description": "Guided feature development with codebase understanding and architecture focus", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/feature-dev.md" + }, + { + "command": "flutter-build", + "description": "Fix Dart analyzer errors and Flutter build failures incrementally. Invokes the dart-build-resolver agent for minimal, surgical fixes.", + "type": "testing", + "primaryAgents": [ + "dart-build-resolver" + ], + "allAgents": [ + "dart-build-resolver" + ], + "skills": [ + "flutter-dart-code-review" + ], + "path": "commands/flutter-build.md" + }, + { + "command": "flutter-review", + "description": "Review Flutter/Dart code for idiomatic patterns, widget best practices, state management, performance, accessibility, and security. Invokes the flutter-reviewer agent.", + "type": "testing", + "primaryAgents": [ + "flutter-reviewer" + ], + "allAgents": [ + "flutter-reviewer" + ], + "skills": [ + "flutter-dart-code-review" + ], + "path": "commands/flutter-review.md" + }, + { + "command": "flutter-test", + "description": "Run Flutter/Dart tests, report failures, and incrementally fix test issues. Covers unit, widget, golden, and integration tests.", + "type": "testing", + "primaryAgents": [ + "dart-build-resolver", + "flutter-reviewer" + ], + "allAgents": [ + "dart-build-resolver", + "flutter-reviewer" + ], + "skills": [ + "flutter-dart-code-review" + ], + "path": "commands/flutter-test.md" + }, + { + "command": "gan-build", + "description": "Run a generator/evaluator build loop for implementation tasks with bounded iterations and scoring.", + "type": "orchestration", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/gan-build.md" + }, + { + "command": "gan-design", + "description": "Run a generator/evaluator design loop for frontend or visual work with bounded iterations and scoring.", + "type": "planning", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/gan-design.md" + }, + { + "command": "go-build", + "description": "Fix Go build errors, go vet warnings, and linter issues incrementally. Invokes the go-build-resolver agent for minimal, surgical fixes.", + "type": "testing", + "primaryAgents": [ + "go-build-resolver" + ], + "allAgents": [ + "go-build-resolver" + ], + "skills": [ + "golang-patterns" + ], + "path": "commands/go-build.md" + }, + { + "command": "go-review", + "description": "Comprehensive Go code review for idiomatic patterns, concurrency safety, error handling, and security. Invokes the go-reviewer agent.", + "type": "testing", + "primaryAgents": [ + "go-reviewer" + ], + "allAgents": [ + "go-reviewer" + ], + "skills": [ + "golang-patterns", + "golang-testing" + ], + "path": "commands/go-review.md" + }, + { + "command": "go-test", + "description": "Enforce TDD workflow for Go. Write table-driven tests first, then implement. Verify 80%+ coverage with go test -cover.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [ + "golang-testing", + "tdd-workflow" + ], + "path": "commands/go-test.md" + }, + { + "command": "gradle-build", + "description": "Fix Gradle build errors for Android and KMP projects", + "type": "build", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/gradle-build.md" + }, + { + "command": "harness-audit", + "description": "Run a deterministic repository harness audit and return a prioritized scorecard.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/harness-audit.md" + }, + { + "command": "hookify-configure", + "description": "Enable or disable hookify rules interactively", + "type": "general", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/hookify-configure.md" + }, + { + "command": "hookify-help", + "description": "Get help with the hookify system", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/hookify-help.md" + }, + { + "command": "hookify-list", + "description": "List all configured hookify rules", + "type": "general", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/hookify-list.md" + }, + { + "command": "hookify", + "description": "Create hooks to prevent unwanted behaviors from conversation analysis or explicit instructions", + "type": "general", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/hookify.md" + }, + { + "command": "instinct-export", + "description": "Export instincts from project/global scope to a file", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/instinct-export.md" + }, + { + "command": "instinct-import", + "description": "Import instincts from file or URL into project/global scope", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [ + "continuous-learning-v2" + ], + "path": "commands/instinct-import.md" + }, + { + "command": "instinct-status", + "description": "Show learned instincts (project + global) with confidence", + "type": "review", + "primaryAgents": [], + "allAgents": [], + "skills": [ + "continuous-learning-v2" + ], + "path": "commands/instinct-status.md" + }, + { + "command": "jira", + "description": "Retrieve a Jira ticket, analyze requirements, update status, or add comments. Uses the jira-integration skill and MCP or REST API.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [ + "jira-integration" + ], + "path": "commands/jira.md" + }, + { + "command": "kotlin-build", + "description": "Fix Kotlin/Gradle build errors, compiler warnings, and dependency issues incrementally. Invokes the kotlin-build-resolver agent for minimal, surgical fixes.", + "type": "testing", + "primaryAgents": [ + "kotlin-build-resolver" + ], + "allAgents": [ + "kotlin-build-resolver" + ], + "skills": [ + "kotlin-patterns" + ], + "path": "commands/kotlin-build.md" + }, + { + "command": "kotlin-review", + "description": "Comprehensive Kotlin code review for idiomatic patterns, null safety, coroutine safety, and security. Invokes the kotlin-reviewer agent.", + "type": "testing", + "primaryAgents": [ + "kotlin-reviewer" + ], + "allAgents": [ + "kotlin-reviewer" + ], + "skills": [ + "kotlin-patterns", + "kotlin-testing" + ], + "path": "commands/kotlin-review.md" + }, + { + "command": "kotlin-test", + "description": "Enforce TDD workflow for Kotlin. Write Kotest tests first, then implement. Verify 80%+ coverage with Kover.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [ + "kotlin-testing", + "tdd-workflow" + ], + "path": "commands/kotlin-test.md" + }, + { + "command": "learn-eval", + "description": "Extract reusable patterns from the session, self-evaluate quality before saving, and determine the right save location (Global vs Project).", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/learn-eval.md" + }, + { + "command": "learn", + "description": "Extract reusable patterns from the current session and save them as candidate skills or guidance.", + "type": "review", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/learn.md" + }, + { + "command": "loop-start", + "description": "Start a managed autonomous loop pattern with safety defaults and explicit stop conditions.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/loop-start.md" + }, + { + "command": "loop-status", + "description": "Inspect active loop state, progress, failure signals, and recommended intervention.", + "type": "general", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/loop-status.md" + }, + { + "command": "model-route", + "description": "Recommend the best model tier for the current task based on complexity, risk, and budget.", + "type": "review", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/model-route.md" + }, + { + "command": "multi-backend", + "description": "Run a backend-focused multi-model workflow for APIs, algorithms, data, and business logic.", + "type": "orchestration", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/multi-backend.md" + }, + { + "command": "multi-execute", + "description": "Execute a multi-model implementation plan while preserving Claude as the only filesystem writer.", + "type": "orchestration", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/multi-execute.md" + }, + { + "command": "multi-frontend", + "description": "Run a frontend-focused multi-model workflow for components, layouts, animation, and UI polish.", + "type": "orchestration", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/multi-frontend.md" + }, + { + "command": "multi-plan", + "description": "Create a multi-model implementation plan without modifying production code.", + "type": "orchestration", + "primaryAgents": [], + "allAgents": [], + "skills": [ + "accessibility" + ], + "path": "commands/multi-plan.md" + }, + { + "command": "multi-workflow", + "description": "Run a full multi-model development workflow with research, planning, execution, optimization, and review.", + "type": "orchestration", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/multi-workflow.md" + }, + { + "command": "plan-prd", + "description": "Generate a lean, problem-first PRD and hand off to /plan for implementation planning.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/plan-prd.md" + }, + { + "command": "plan", + "description": "Restate requirements, assess risks, and create step-by-step implementation plan. WAIT for user CONFIRM before touching any code.", + "type": "testing", + "primaryAgents": [ + "planner" + ], + "allAgents": [ + "planner" + ], + "skills": [], + "path": "commands/plan.md" + }, + { + "command": "pm2", + "description": "Analyze a project and generate PM2 service commands for detected frontend, backend, or database services.", + "type": "general", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/pm2.md" + }, + { + "command": "pr", + "description": "Create a GitHub PR from current branch with unpushed commits — discovers templates, analyzes changes, pushes", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/pr.md" + }, + { + "command": "project-init", + "description": "Detect a project's stack and produce a dry-run ECC onboarding plan using the repository's install manifests and stack mappings.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [ + "ecc-guide" + ], + "path": "commands/project-init.md" + }, + { + "command": "projects", + "description": "List known projects and their instinct statistics", + "type": "general", + "primaryAgents": [], + "allAgents": [], + "skills": [ + "continuous-learning-v2" + ], + "path": "commands/projects.md" + }, + { + "command": "promote", + "description": "Promote project-scoped instincts to global scope", + "type": "review", + "primaryAgents": [], + "allAgents": [], + "skills": [ + "continuous-learning-v2" + ], + "path": "commands/promote.md" + }, + { + "command": "prp-commit", + "description": "Quick commit with natural language file targeting — describe what to commit in plain English", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/prp-commit.md" + }, + { + "command": "prp-implement", + "description": "Execute an implementation plan with rigorous validation loops", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/prp-implement.md" + }, + { + "command": "prp-plan", + "description": "Create comprehensive feature implementation plan with codebase analysis and pattern extraction", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/prp-plan.md" + }, + { + "command": "prp-pr", + "description": "Create a GitHub PR from current branch with unpushed commits — discovers templates, analyzes changes, pushes", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/prp-pr.md" + }, + { + "command": "prp-prd", + "description": "Interactive PRD generator - problem-first, hypothesis-driven product spec with back-and-forth questioning", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/prp-prd.md" + }, + { + "command": "prune", + "description": "Delete pending instincts older than 30 days that were never promoted", + "type": "review", + "primaryAgents": [], + "allAgents": [], + "skills": [ + "continuous-learning-v2" + ], + "path": "commands/prune.md" + }, + { + "command": "python-review", + "description": "Comprehensive Python code review for PEP 8 compliance, type hints, security, and Pythonic idioms. Invokes the python-reviewer agent.", + "type": "testing", + "primaryAgents": [ + "python-reviewer" + ], + "allAgents": [ + "python-reviewer" + ], + "skills": [ + "python-patterns", + "python-testing" + ], + "path": "commands/python-review.md" + }, + { + "command": "quality-gate", + "description": "Run the ECC quality pipeline for a file or project scope and report remediation steps.", + "type": "general", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/quality-gate.md" + }, + { + "command": "refactor-clean", + "description": "Safely identify and remove dead code with verification after each change.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/refactor-clean.md" + }, + { + "command": "resume-session", + "description": "Load the most recent session file from ~/.claude/session-data/ and resume work with full context from where the last session ended.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/resume-session.md" + }, + { + "command": "review-pr", + "description": "Comprehensive PR review using specialized agents", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/review-pr.md" + }, + { + "command": "rust-build", + "description": "Fix Rust build errors, borrow checker issues, and dependency problems incrementally. Invokes the rust-build-resolver agent for minimal, surgical fixes.", + "type": "testing", + "primaryAgents": [ + "rust-build-resolver" + ], + "allAgents": [ + "rust-build-resolver" + ], + "skills": [ + "rust-patterns" + ], + "path": "commands/rust-build.md" + }, + { + "command": "rust-review", + "description": "Comprehensive Rust code review for ownership, lifetimes, error handling, unsafe usage, and idiomatic patterns. Invokes the rust-reviewer agent.", + "type": "testing", + "primaryAgents": [ + "rust-reviewer" + ], + "allAgents": [ + "rust-reviewer" + ], + "skills": [ + "rust-patterns", + "rust-testing" + ], + "path": "commands/rust-review.md" + }, + { + "command": "rust-test", + "description": "Enforce TDD workflow for Rust. Write tests first, then implement. Verify 80%+ coverage with cargo-llvm-cov.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [ + "rust-patterns", + "rust-testing" + ], + "path": "commands/rust-test.md" + }, + { + "command": "santa-loop", + "description": "Adversarial dual-review convergence loop — two independent model reviewers must both approve before code ships.", + "type": "review", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/santa-loop.md" + }, + { + "command": "save-session", + "description": "Save current session state to a dated file in ~/.claude/session-data/ so work can be resumed in a future session with full context.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/save-session.md" + }, + { + "command": "security-scan", + "description": "Run AgentShield against agent, hook, MCP, permission, and secret surfaces.", + "type": "review", + "primaryAgents": [ + "security-reviewer" + ], + "allAgents": [ + "security-reviewer" + ], + "skills": [ + "security-scan" + ], + "path": "commands/security-scan.md" + }, + { + "command": "sessions", + "description": "Manage Claude Code session history, aliases, and session metadata.", + "type": "general", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/sessions.md" + }, + { + "command": "setup-pm", + "description": "Configure your preferred package manager (npm/pnpm/yarn/bun)", + "type": "build", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/setup-pm.md" + }, + { + "command": "skill-create", + "description": "Analyze local git history to extract coding patterns and generate SKILL.md files. Local version of the Skill Creator GitHub App.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/skill-create.md" + }, + { + "command": "skill-health", + "description": "Show skill portfolio health dashboard with charts and analytics", + "type": "review", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/skill-health.md" + }, + { + "command": "test-coverage", + "description": "Analyze coverage, identify gaps, and generate missing tests toward the target threshold.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/test-coverage.md" + }, + { + "command": "update-codemaps", + "description": "Scan project structure and generate token-lean architecture codemaps.", + "type": "planning", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/update-codemaps.md" + }, + { + "command": "update-docs", + "description": "Sync documentation from source-of-truth files such as scripts, schemas, routes, and exports.", + "type": "testing", + "primaryAgents": [], + "allAgents": [], + "skills": [], + "path": "commands/update-docs.md" + } + ], + "statistics": { + "byType": { + "build": 2, + "general": 8, + "orchestration": 6, + "planning": 2, + "refactoring": 1, + "review": 9, + "testing": 47 + }, + "topAgents": [ + { + "agent": "dart-build-resolver", + "count": 2 + }, + { + "agent": "flutter-reviewer", + "count": 2 + }, + { + "agent": "cpp-build-resolver", + "count": 1 + }, + { + "agent": "cpp-reviewer", + "count": 1 + }, + { + "agent": "go-build-resolver", + "count": 1 + }, + { + "agent": "go-reviewer", + "count": 1 + }, + { + "agent": "kotlin-build-resolver", + "count": 1 + }, + { + "agent": "kotlin-reviewer", + "count": 1 + }, + { + "agent": "planner", + "count": 1 + }, + { + "agent": "python-reviewer", + "count": 1 + } + ], + "topSkills": [ + { + "skill": "continuous-learning-v2", + "count": 6 + }, + { + "skill": "flutter-dart-code-review", + "count": 3 + }, + { + "skill": "rust-patterns", + "count": 3 + }, + { + "skill": "tdd-workflow", + "count": 3 + }, + { + "skill": "cpp-coding-standards", + "count": 2 + }, + { + "skill": "cpp-testing", + "count": 2 + }, + { + "skill": "ecc-guide", + "count": 2 + }, + { + "skill": "golang-patterns", + "count": 2 + }, + { + "skill": "golang-testing", + "count": 2 + }, + { + "skill": "kotlin-patterns", + "count": 2 + } + ] + } +} diff --git a/package.json b/package.json index 098c4ada..8c30dca4 100644 --- a/package.json +++ b/package.json @@ -286,6 +286,9 @@ "postinstall": "echo '\\n ecc-universal installed!\\n Run: npx ecc typescript\\n Compat: npx ecc-install typescript\\n Docs: https://github.com/affaan-m/everything-claude-code\\n'", "catalog:check": "node scripts/ci/catalog.js --text", "catalog:sync": "node scripts/ci/catalog.js --write --text", + "command-registry:generate": "node scripts/ci/generate-command-registry.js", + "command-registry:write": "node scripts/ci/generate-command-registry.js --write", + "command-registry:check": "node scripts/ci/generate-command-registry.js --check", "lint": "eslint . && markdownlint '**/*.md' --ignore node_modules", "harness:adapters": "node scripts/harness-adapter-compliance.js", "harness:audit": "node scripts/harness-audit.js", @@ -295,7 +298,7 @@ "orchestrate:status": "node scripts/orchestration-status.js", "orchestrate:worker": "bash scripts/orchestrate-codex-worker.sh", "orchestrate:tmux": "node scripts/orchestrate-worktrees.js", - "test": "node scripts/ci/check-unicode-safety.js && node scripts/ci/validate-agents.js && node scripts/ci/validate-commands.js && node scripts/ci/validate-rules.js && node scripts/ci/validate-skills.js && node scripts/ci/validate-hooks.js && node scripts/ci/validate-install-manifests.js && node scripts/ci/validate-no-personal-paths.js && npm run catalog:check && node tests/run-all.js", + "test": "node scripts/ci/check-unicode-safety.js && node scripts/ci/validate-agents.js && node scripts/ci/validate-commands.js && node scripts/ci/validate-rules.js && node scripts/ci/validate-skills.js && node scripts/ci/validate-hooks.js && node scripts/ci/validate-install-manifests.js && node scripts/ci/validate-no-personal-paths.js && npm run catalog:check && npm run command-registry:check && node tests/run-all.js", "coverage": "c8 --all --include=\"scripts/**/*.js\" --check-coverage --lines 80 --functions 80 --branches 80 --statements 80 --reporter=text --reporter=lcov node tests/run-all.js", "build:opencode": "node scripts/build-opencode.js", "prepack": "npm run build:opencode", diff --git a/scripts/ci/catalog.js b/scripts/ci/catalog.js index 0d18089b..e5fac994 100644 --- a/scripts/ci/catalog.js +++ b/scripts/ci/catalog.js @@ -101,6 +101,19 @@ function parseReadmeExpectations(readmeContent) { { category: 'commands', mode: 'exact', expected: Number(quickStartMatch[3]), source: 'README.md quick-start summary' } ); + const releaseNoteMatch = readmeContent.match( + /actual OSS surface:\s+(\d+)\s+agents,\s+(\d+)\s+skills,\s+and\s+(\d+)\s+legacy command shims/i + ); + if (!releaseNoteMatch) { + throw new Error('README.md is missing the rc.1 release-note catalog summary'); + } + + expectations.push( + { category: 'agents', mode: 'exact', expected: Number(releaseNoteMatch[1]), source: 'README.md rc.1 release-note summary' }, + { category: 'skills', mode: 'exact', expected: Number(releaseNoteMatch[2]), source: 'README.md rc.1 release-note summary' }, + { category: 'commands', mode: 'exact', expected: Number(releaseNoteMatch[3]), source: 'README.md rc.1 release-note summary' } + ); + const projectTreeAgentsMatch = readmeContent.match(/^\|\s*--\s*agents\/\s*#\s*(\d+)\s+specialized subagents for delegation\s*$/im); if (!projectTreeAgentsMatch) { throw new Error('README.md project tree is missing the agents count'); @@ -415,6 +428,13 @@ function syncEnglishReadme(content, catalog) { `${prefix}${catalog.agents.count}${agentsSuffix}${catalog.skills.count}${skillsSuffix}${catalog.commands.count} legacy command shims`, 'README.md quick-start summary' ); + nextContent = replaceOrThrow( + nextContent, + /(actual OSS surface:\s+)(\d+)(\s+agents,\s+)(\d+)(\s+skills,\s+and\s+)(\d+)(\s+legacy command shims)/i, + (_, prefix, __, agentsSuffix, ___, skillsSuffix, ____, commandsSuffix) => + `${prefix}${catalog.agents.count}${agentsSuffix}${catalog.skills.count}${skillsSuffix}${catalog.commands.count}${commandsSuffix}`, + 'README.md rc.1 release-note summary' + ); nextContent = replaceOrThrow( nextContent, /^(\|\s*--\s*agents\/\s*#\s*)(\d+)(\s+specialized subagents for delegation\s*)$/im, diff --git a/scripts/ci/generate-command-registry.js b/scripts/ci/generate-command-registry.js new file mode 100644 index 00000000..838c6564 --- /dev/null +++ b/scripts/ci/generate-command-registry.js @@ -0,0 +1,318 @@ +#!/usr/bin/env node +/** + * Generate a deterministic command-to-agent/skill registry. + * + * Usage: + * node scripts/ci/generate-command-registry.js + * node scripts/ci/generate-command-registry.js --json + * node scripts/ci/generate-command-registry.js --write + * node scripts/ci/generate-command-registry.js --check + */ + +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const ROOT = path.join(__dirname, '../..'); +const DEFAULT_OUTPUT_PATH = path.join(ROOT, 'docs', 'COMMAND-REGISTRY.json'); + +function normalizePath(relativePath) { + return relativePath.split(path.sep).join('/'); +} + +function listMarkdownFiles(root, relativeDir) { + const directory = path.join(root, relativeDir); + if (!fs.existsSync(directory)) { + return []; + } + + return fs.readdirSync(directory, { withFileTypes: true }) + .filter(entry => entry.isFile() && entry.name.endsWith('.md')) + .map(entry => entry.name) + .sort(); +} + +function listKnownAgents(root) { + return new Set( + listMarkdownFiles(root, 'agents') + .map(filename => filename.replace(/\.md$/, '')) + ); +} + +function listKnownSkills(root) { + const skillsDir = path.join(root, 'skills'); + if (!fs.existsSync(skillsDir)) { + return new Set(); + } + + return new Set( + fs.readdirSync(skillsDir, { withFileTypes: true }) + .filter(entry => ( + entry.isDirectory() && fs.existsSync(path.join(skillsDir, entry.name, 'SKILL.md')) + )) + .map(entry => entry.name) + .sort() + ); +} + +function cleanYamlScalar(value) { + return value.trim() + .replace(/^['"]/, '') + .replace(/['"]$/, ''); +} + +function extractDescription(content) { + const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); + if (frontmatter) { + const description = frontmatter[1].match(/^description:\s*(.+)$/m); + if (description) { + return cleanYamlScalar(description[1]); + } + } + + const heading = content.match(/^#\s+(.+)$/m); + return heading ? heading[1].trim() : ''; +} + +function collectKnownReferences(content, patterns, knownNames) { + const refs = new Set(); + + for (const pattern of patterns) { + for (const match of content.matchAll(pattern)) { + const ref = match[1]; + if (knownNames.has(ref)) { + refs.add(ref); + } + } + } + + return refs; +} + +function extractReferences(content, knownAgents, knownSkills) { + const agentPatterns = [ + /@([a-z][a-z0-9-]*)/gi, + /\bagent:\s*['"]?([a-z][a-z0-9-]*)/gi, + /\bsubagent(?:_type)?:\s*['"]?([a-z][a-z0-9-]*)/gi, + /\bagents\/([a-z][a-z0-9-]*)\.md\b/gi, + ]; + + const skillPatterns = [ + /\bskill:\s*['"]?\/?([a-z][a-z0-9-]*)/gi, + /\bskills\/([a-z][a-z0-9-]*)\/SKILL\.md\b/gi, + /\bskills\/([a-z][a-z0-9-]*)\b/gi, + /\/([a-z][a-z0-9-]*)\b/gi, + ]; + + return { + agents: Array.from(collectKnownReferences(content, agentPatterns, knownAgents)).sort(), + skills: Array.from(collectKnownReferences(content, skillPatterns, knownSkills)).sort(), + }; +} + +function inferCommandType(content, commandName) { + const lower = `${commandName}\n${content}`.toLowerCase(); + + if (commandName.startsWith('multi-') || lower.includes('orchestrat')) { + return 'orchestration'; + } + if (lower.includes('test') || lower.includes('tdd') || lower.includes('coverage')) { + return 'testing'; + } + if (lower.includes('review') || lower.includes('audit') || lower.includes('security')) { + return 'review'; + } + if (lower.includes('plan') || lower.includes('design') || lower.includes('architecture')) { + return 'planning'; + } + if (lower.includes('refactor') || lower.includes('clean') || lower.includes('simplify')) { + return 'refactoring'; + } + if (lower.includes('build') || lower.includes('compile') || lower.includes('setup')) { + return 'build'; + } + + return 'general'; +} + +function processCommandFile(root, filename, knownAgents, knownSkills) { + const commandName = filename.replace(/\.md$/, ''); + const relativePath = normalizePath(path.join('commands', filename)); + const content = fs.readFileSync(path.join(root, relativePath), 'utf8'); + const references = extractReferences(content, knownAgents, knownSkills); + + return { + command: commandName, + description: extractDescription(content), + type: inferCommandType(content, commandName), + primaryAgents: references.agents.slice(0, 3), + allAgents: references.agents, + skills: references.skills, + path: relativePath, + }; +} + +function sortCountMap(countMap) { + return Object.fromEntries( + Object.entries(countMap).sort(([left], [right]) => left.localeCompare(right)) + ); +} + +function topUsage(countMap, keyName) { + return Object.entries(countMap) + .sort(([leftName, leftCount], [rightName, rightCount]) => ( + rightCount - leftCount || leftName.localeCompare(rightName) + )) + .slice(0, 10) + .map(([name, count]) => ({ [keyName]: name, count })); +} + +function generateRegistry(options = {}) { + const root = options.root || ROOT; + const commandFiles = listMarkdownFiles(root, 'commands'); + const knownAgents = listKnownAgents(root); + const knownSkills = listKnownSkills(root); + + const commands = commandFiles.map(filename => ( + processCommandFile(root, filename, knownAgents, knownSkills) + )); + + const byType = {}; + const agentUsage = {}; + const skillUsage = {}; + + for (const command of commands) { + byType[command.type] = (byType[command.type] || 0) + 1; + for (const agent of command.allAgents) { + agentUsage[agent] = (agentUsage[agent] || 0) + 1; + } + for (const skill of command.skills) { + skillUsage[skill] = (skillUsage[skill] || 0) + 1; + } + } + + return { + schemaVersion: 1, + totalCommands: commands.length, + commands, + statistics: { + byType: sortCountMap(byType), + topAgents: topUsage(agentUsage, 'agent'), + topSkills: topUsage(skillUsage, 'skill'), + }, + }; +} + +function formatRegistry(registry) { + return `${JSON.stringify(registry, null, 2)}\n`; +} + +function writeRegistry(registry, outputPath = DEFAULT_OUTPUT_PATH) { + fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + fs.writeFileSync(outputPath, formatRegistry(registry), 'utf8'); +} + +function checkRegistry(registry, outputPath = DEFAULT_OUTPUT_PATH) { + const expected = formatRegistry(registry); + let current; + + try { + current = fs.readFileSync(outputPath, 'utf8'); + } catch (error) { + throw new Error(`Failed to read ${normalizePath(path.relative(ROOT, outputPath))}: ${error.message}`); + } + + if (current !== expected) { + throw new Error(`${normalizePath(path.relative(ROOT, outputPath))} is out of date; run npm run command-registry:write`); + } +} + +function formatTextSummary(registry) { + const lines = [ + 'Command registry statistics', + '', + `Total commands: ${registry.totalCommands}`, + '', + 'By type:', + ]; + + for (const [type, count] of Object.entries(registry.statistics.byType)) { + lines.push(` ${type}: ${count}`); + } + + lines.push('', 'Top agents:'); + for (const { agent, count } of registry.statistics.topAgents) { + lines.push(` ${agent}: ${count}`); + } + + lines.push('', 'Top skills:'); + for (const { skill, count } of registry.statistics.topSkills) { + lines.push(` ${skill}: ${count}`); + } + + return `${lines.join('\n')}\n`; +} + +function parseArgs(argv) { + const allowed = new Set(['--json', '--write', '--check']); + const flags = new Set(); + + for (const arg of argv) { + if (!allowed.has(arg)) { + throw new Error(`Unknown argument: ${arg}`); + } + flags.add(arg); + } + + return { + json: flags.has('--json'), + write: flags.has('--write'), + check: flags.has('--check'), + }; +} + +function run(argv = process.argv.slice(2), options = {}) { + const stdout = options.stdout || process.stdout; + const stderr = options.stderr || process.stderr; + const outputPath = options.outputPath || DEFAULT_OUTPUT_PATH; + + try { + const args = parseArgs(argv); + const registry = generateRegistry({ root: options.root || ROOT }); + + if (args.check) { + checkRegistry(registry, outputPath); + stdout.write('Command registry is up to date.\n'); + return 0; + } + + if (args.write) { + writeRegistry(registry, outputPath); + stdout.write(`Command registry written to ${normalizePath(path.relative(process.cwd(), outputPath))}\n`); + return 0; + } + + stdout.write(args.json ? formatRegistry(registry) : formatTextSummary(registry)); + return 0; + } catch (error) { + stderr.write(`${error.message}\n`); + return 1; + } +} + +if (require.main === module) { + process.exit(run()); +} + +module.exports = { + checkRegistry, + extractDescription, + extractReferences, + formatRegistry, + generateRegistry, + inferCommandType, + parseArgs, + run, + writeRegistry, +}; diff --git a/tests/ci/catalog.test.js b/tests/ci/catalog.test.js index ee7a6540..f5b05e1c 100644 --- a/tests/ci/catalog.test.js +++ b/tests/ci/catalog.test.js @@ -44,6 +44,7 @@ function writeEnglishReadme(root, counts, options = {}) { const unrelatedSkillsCount = options.unrelatedSkillsCount || 16; fs.writeFileSync(path.join(root, 'README.md'), `Access to ${counts.agents} agents, ${counts.skills} skills, and ${counts.commands} commands. +- **Public surface synced to the live repo** - metadata, catalog counts, plugin manifests, and install-facing docs now match the actual OSS surface: ${counts.agents} agents, ${counts.skills} skills, and ${counts.commands} legacy command shims. |-- agents/ # ${counts.agents} specialized subagents for delegation | Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode | | --- | --- | --- | --- | --- | @@ -221,6 +222,7 @@ function runTests() { .join('\n'); assert.ok(formatted.includes('README.md quick-start summary')); + assert.ok(formatted.includes('README.md rc.1 release-note summary')); assert.ok(formatted.includes('README.md project tree')); assert.ok(formatted.includes('AGENTS.md summary')); assert.ok(formatted.includes('.claude-plugin/plugin.json description')); @@ -255,6 +257,7 @@ function runTests() { const marketplaceJson = fs.readFileSync(path.join(testDir, '.claude-plugin', 'marketplace.json'), 'utf8'); assert.ok(readme.includes('Access to 1 agents, 1 skills, and 1 legacy command shims')); + assert.ok(readme.includes('actual OSS surface: 1 agents, 1 skills, and 1 legacy command shims')); assert.ok(readme.includes('|-- agents/ # 1 specialized subagents for delegation')); assert.ok(readme.includes('| Skills | 42 | .agents/skills/ |')); assert.ok(agentsDoc.includes('providing 1 specialized agents, 1+ skills, 1 commands')); diff --git a/tests/ci/command-registry.test.js b/tests/ci/command-registry.test.js new file mode 100644 index 00000000..95e6fb10 --- /dev/null +++ b/tests/ci/command-registry.test.js @@ -0,0 +1,176 @@ +/** + * Direct coverage for scripts/ci/generate-command-registry.js. + */ + +'use strict'; + +const assert = require('assert'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const { + checkRegistry, + formatRegistry, + generateRegistry, + parseArgs, + run, + writeRegistry, +} = require('../../scripts/ci/generate-command-registry'); + +function createTestDir() { + return fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-command-registry-')); +} + +function cleanupTestDir(testDir) { + fs.rmSync(testDir, { recursive: true, force: true }); +} + +function writeFixture(root) { + fs.mkdirSync(path.join(root, 'commands'), { recursive: true }); + fs.mkdirSync(path.join(root, 'agents'), { recursive: true }); + fs.mkdirSync(path.join(root, 'skills', 'tdd-workflow'), { recursive: true }); + fs.mkdirSync(path.join(root, 'skills', 'security-review'), { recursive: true }); + + fs.writeFileSync(path.join(root, 'agents', 'code-reviewer.md'), '---\nmodel: sonnet\ntools: Read\n---\n'); + fs.writeFileSync(path.join(root, 'agents', 'test-writer.md'), '---\nmodel: sonnet\ntools: Read\n---\n'); + fs.writeFileSync(path.join(root, 'skills', 'tdd-workflow', 'SKILL.md'), '# TDD workflow\n'); + fs.writeFileSync(path.join(root, 'skills', 'security-review', 'SKILL.md'), '# Security review\n'); + + fs.writeFileSync(path.join(root, 'commands', 'review.md'), `--- +description: Review changes +--- +# Review + +Use @code-reviewer and skill: security-review. +`); + + fs.writeFileSync(path.join(root, 'commands', 'tdd.md'), `--- +description: "Write tests first" +--- +# TDD + +Call subagent_type: test-writer and skills/tdd-workflow/SKILL.md. +`); +} + +function test(name, fn) { + try { + fn(); + console.log(` PASS ${name}`); + return true; + } catch (error) { + console.log(` FAIL ${name}`); + console.log(` Error: ${error.message}`); + return false; + } +} + +function runTests() { + console.log('\n=== Testing command registry generation ===\n'); + + let passed = 0; + let failed = 0; + + if (test('generates deterministic command metadata and usage statistics', () => { + const testDir = createTestDir(); + try { + writeFixture(testDir); + + const registry = generateRegistry({ root: testDir }); + + assert.strictEqual(registry.schemaVersion, 1); + assert.strictEqual(registry.totalCommands, 2); + assert.deepStrictEqual( + registry.commands.map(command => command.command), + ['review', 'tdd'] + ); + assert.deepStrictEqual(registry.commands[0].allAgents, ['code-reviewer']); + assert.deepStrictEqual(registry.commands[0].skills, ['security-review']); + assert.deepStrictEqual(registry.commands[1].allAgents, ['test-writer']); + assert.deepStrictEqual(registry.commands[1].skills, ['tdd-workflow']); + assert.deepStrictEqual(registry.statistics.byType, { review: 1, testing: 1 }); + assert.deepStrictEqual(registry.statistics.topAgents[0], { agent: 'code-reviewer', count: 1 }); + } finally { + cleanupTestDir(testDir); + } + })) passed++; else failed++; + + if (test('write and check modes use stable JSON without timestamps', () => { + const testDir = createTestDir(); + try { + writeFixture(testDir); + const outputPath = path.join(testDir, 'docs', 'COMMAND-REGISTRY.json'); + const registry = generateRegistry({ root: testDir }); + + writeRegistry(registry, outputPath); + const firstWrite = fs.readFileSync(outputPath, 'utf8'); + writeRegistry(registry, outputPath); + const secondWrite = fs.readFileSync(outputPath, 'utf8'); + + assert.strictEqual(firstWrite, secondWrite); + assert.ok(!firstWrite.includes('generated')); + assert.doesNotThrow(() => checkRegistry(registry, outputPath)); + } finally { + cleanupTestDir(testDir); + } + })) passed++; else failed++; + + if (test('check mode fails when the registry file is stale', () => { + const testDir = createTestDir(); + try { + writeFixture(testDir); + const outputPath = path.join(testDir, 'docs', 'COMMAND-REGISTRY.json'); + const registry = generateRegistry({ root: testDir }); + + fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + fs.writeFileSync(outputPath, `${formatRegistry(registry).trimEnd()}\n \n`); + + assert.throws( + () => checkRegistry(registry, outputPath), + /out of date/ + ); + } finally { + cleanupTestDir(testDir); + } + })) passed++; else failed++; + + if (test('CLI reports unknown arguments and supports check output', () => { + const testDir = createTestDir(); + try { + writeFixture(testDir); + const outputPath = path.join(testDir, 'docs', 'COMMAND-REGISTRY.json'); + const registry = generateRegistry({ root: testDir }); + writeRegistry(registry, outputPath); + + let stdout = ''; + let stderr = ''; + const streams = { + stdout: { write: chunk => { stdout += chunk; } }, + stderr: { write: chunk => { stderr += chunk; } }, + }; + + assert.deepStrictEqual(parseArgs(['--json', '--write']), { + json: true, + write: true, + check: false, + }); + assert.strictEqual(run(['--check'], { root: testDir, outputPath, ...streams }), 0); + assert.ok(stdout.includes('up to date')); + assert.strictEqual(stderr, ''); + + stdout = ''; + stderr = ''; + assert.strictEqual(run(['--bogus'], { root: testDir, outputPath, ...streams }), 1); + assert.strictEqual(stdout, ''); + assert.ok(stderr.includes('Unknown argument')); + } finally { + cleanupTestDir(testDir); + } + })) passed++; else failed++; + + console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`); + process.exit(failed > 0 ? 1 : 0); +} + +runTests(); diff --git a/tests/ci/validators.test.js b/tests/ci/validators.test.js index fe9fee65..0e681084 100644 --- a/tests/ci/validators.test.js +++ b/tests/ci/validators.test.js @@ -270,7 +270,7 @@ function writeCatalogFixture(testDir, options = {}) { fs.writeFileSync(path.join(testDir, 'commands', 'plan.md'), '---\ndescription: Plan\n---\n# Plan'); fs.writeFileSync(path.join(testDir, 'skills', 'demo-skill', 'SKILL.md'), '---\nname: demo-skill\ndescription: Demo skill\norigin: ECC\n---\n# Demo Skill'); - fs.writeFileSync(readmePath, `Access to ${readmeCounts.agents} agents, ${readmeCounts.skills} skills, and ${readmeCounts.commands} commands.\n|-- agents/ # ${readmeProjectTreeAgents} specialized subagents for delegation\n| Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode |\n|---------|------------|------------|-----------|----------|\n| Agents | PASS: ${readmeTableCounts.agents} agents | Shared | Shared | 1 |\n| Commands | PASS: ${readmeTableCounts.commands} commands | Shared | Shared | 1 |\n| Skills | PASS: ${readmeTableCounts.skills} skills | Shared | Shared | 1 |\n\n| Feature | Count | Format |\n|-----------|-------|---------|\n| Skills | ${readmeUnrelatedSkillsCount} | .agents/skills/ |\n\n## Cross-Tool Feature Parity\n\n| Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode |\n|---------|------------|------------|-----------|----------|\n| **Agents** | ${readmeParityCounts.agents} | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 |\n| **Commands** | ${readmeParityCounts.commands} | Shared | Instruction-based | 31 |\n| **Skills** | ${readmeParityCounts.skills} | Shared | 10 (native format) | 37 |\n`); + fs.writeFileSync(readmePath, `Access to ${readmeCounts.agents} agents, ${readmeCounts.skills} skills, and ${readmeCounts.commands} commands.\n- **Public surface synced to the live repo** - metadata, catalog counts, plugin manifests, and install-facing docs now match the actual OSS surface: ${readmeCounts.agents} agents, ${readmeCounts.skills} skills, and ${readmeCounts.commands} legacy command shims.\n|-- agents/ # ${readmeProjectTreeAgents} specialized subagents for delegation\n| Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode |\n|---------|------------|------------|-----------|----------|\n| Agents | PASS: ${readmeTableCounts.agents} agents | Shared | Shared | 1 |\n| Commands | PASS: ${readmeTableCounts.commands} commands | Shared | Shared | 1 |\n| Skills | PASS: ${readmeTableCounts.skills} skills | Shared | Shared | 1 |\n\n| Feature | Count | Format |\n|-----------|-------|---------|\n| Skills | ${readmeUnrelatedSkillsCount} | .agents/skills/ |\n\n## Cross-Tool Feature Parity\n\n| Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode |\n|---------|------------|------------|-----------|----------|\n| **Agents** | ${readmeParityCounts.agents} | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 |\n| **Commands** | ${readmeParityCounts.commands} | Shared | Instruction-based | 31 |\n| **Skills** | ${readmeParityCounts.skills} | Shared | 10 (native format) | 37 |\n`); fs.writeFileSync(agentsPath, `This is a **production-ready AI coding plugin** providing ${summaryCounts.agents} specialized agents, ${summaryCounts.skills} skills, ${summaryCounts.commands} commands, and automated hook workflows for software development.\n\n\`\`\`\n${structureLines.join('\n')}\n\`\`\`\n`); fs.writeFileSync(zhRootReadmePath, `**完成!** 你现在可以使用 ${zhRootReadmeCounts.agents} 个代理、${zhRootReadmeCounts.skills} 个技能和 ${zhRootReadmeCounts.commands} 个命令。\n`); fs.writeFileSync(zhDocsReadmePath, `**搞定!** 你现在可以使用 ${zhDocsReadmeCounts.agents} 个智能体、${zhDocsReadmeCounts.skills} 项技能和 ${zhDocsReadmeCounts.commands} 个命令了。\n| 功能特性 | Claude Code | OpenCode | 状态 |\n|---------|-------------|----------|--------|\n| 智能体 | \u2705 ${zhDocsTableCounts.agents} 个 | \u2705 12 个 | **Claude Code 领先** |\n| 命令 | \u2705 ${zhDocsTableCounts.commands} 个 | \u2705 31 个 | **Claude Code 领先** |\n| 技能 | \u2705 ${zhDocsTableCounts.skills} 项 | \u2705 37 项 | **Claude Code 领先** |\n\n| 功能特性 | 数量 | 格式 |\n|-----------|-------|---------|\n| 技能 | ${zhDocsUnrelatedSkillsCount} | .agents/skills/ |\n\n## 跨工具功能对等\n\n| 功能特性 | Claude Code | Cursor IDE | Codex CLI | OpenCode |\n|---------|------------|------------|-----------|----------|\n| **智能体** | ${zhDocsParityCounts.agents} | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |\n| **命令** | ${zhDocsParityCounts.commands} | 共享 | 基于指令 | 31 |\n| **技能** | ${zhDocsParityCounts.skills} | 共享 | 10 (原生格式) | 37 |\n`); @@ -595,6 +595,7 @@ function runTests() { const marketplaceJson = fs.readFileSync(marketplaceJsonPath, 'utf8'); assert.ok(readme.includes('Access to 1 agents, 1 skills, and 1 legacy command shims'), 'Should sync README quick-start summary'); + assert.ok(readme.includes('actual OSS surface: 1 agents, 1 skills, and 1 legacy command shims'), 'Should sync README release-note summary'); assert.ok(readme.includes('|-- agents/ # 1 specialized subagents for delegation'), 'Should sync README project tree agents count'); assert.ok(readme.includes('| Agents | PASS: 1 agents |'), 'Should sync README comparison table'); assert.ok(readme.includes('| Skills | 16 | .agents/skills/ |'), 'Should not rewrite unrelated README tables');