feat: add command registry and coverage checks (#1906)

Salvages the useful parts of #1897 without generated .caliber state or stale counts.

- adds a deterministic command registry generator and drift check
- commits the current command registry for 75 commands
- validates the rc.1 README catalog summary against live counts
- adds a single Ubuntu Node 20 coverage job instead of running coverage in every matrix cell

Co-authored-by: jodunk <jodunk@users.noreply.github.com>
This commit is contained in:
Affaan Mustafa
2026-05-14 22:02:36 -04:00
committed by GitHub
parent 375d750b4c
commit f7315016c0
9 changed files with 1453 additions and 3 deletions

View File

@@ -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'));

View File

@@ -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();

View File

@@ -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');