mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-01 00:50:00 +08:00
266 lines
9.6 KiB
JavaScript
266 lines
9.6 KiB
JavaScript
/**
|
|
* Direct coverage for scripts/ci/catalog.js.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
|
|
const {
|
|
buildCatalog,
|
|
formatExpectation,
|
|
runCatalogCheck,
|
|
} = require('../../scripts/ci/catalog');
|
|
|
|
function createTestDir() {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-ci-catalog-'));
|
|
}
|
|
|
|
function cleanupTestDir(testDir) {
|
|
fs.rmSync(testDir, { recursive: true, force: true });
|
|
}
|
|
|
|
function writeCountedFiles(root, category, count) {
|
|
const dir = path.join(root, category);
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
|
|
for (let index = 1; index <= count; index += 1) {
|
|
if (category === 'skills') {
|
|
const skillDir = path.join(dir, `skill-${index}`);
|
|
fs.mkdirSync(skillDir, { recursive: true });
|
|
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), `# Skill ${index}\n`);
|
|
} else {
|
|
fs.writeFileSync(path.join(dir, `${category}-${index}.md`), `# ${category} ${index}\n`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function writeEnglishReadme(root, counts, options = {}) {
|
|
const tableCounts = options.tableCounts || counts;
|
|
const parityCounts = options.parityCounts || counts;
|
|
const unrelatedSkillsCount = options.unrelatedSkillsCount || 16;
|
|
|
|
fs.writeFileSync(path.join(root, 'README.md'), `Access to ${counts.agents} agents, ${counts.skills} skills, and ${counts.commands} commands.
|
|
| Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode |
|
|
| --- | --- | --- | --- | --- |
|
|
| Agents | PASS: ${tableCounts.agents} agents |
|
|
| Commands | PASS: ${tableCounts.commands} commands |
|
|
| Skills | PASS: ${tableCounts.skills} skills |
|
|
|
|
| Feature | Count | Format |
|
|
| --- | ---: | --- |
|
|
| Skills | ${unrelatedSkillsCount} | .agents/skills/ |
|
|
|
|
## Cross-Tool Feature Parity
|
|
|
|
| Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode |
|
|
| --- | --- | --- | --- | --- |
|
|
| **Agents** | ${parityCounts.agents} | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 |
|
|
| **Commands** | ${parityCounts.commands} | Shared | Instruction-based | 31 |
|
|
| **Skills** | ${parityCounts.skills} | Shared | 10 (native format) | 37 |
|
|
`);
|
|
}
|
|
|
|
function writeEnglishAgents(root, counts, options = {}) {
|
|
const plus = options.skillsMinimum ? '+' : '';
|
|
|
|
fs.writeFileSync(path.join(root, 'AGENTS.md'), `This is a production plugin providing ${counts.agents} specialized agents, ${counts.skills}${plus} skills, ${counts.commands} commands.
|
|
|
|
\`\`\`
|
|
agents/ - ${counts.agents} specialized subagents
|
|
skills/ - ${counts.skills}${plus} workflow skills and domain knowledge
|
|
commands/ - ${counts.commands} slash commands
|
|
\`\`\`
|
|
`);
|
|
}
|
|
|
|
function writeZhRootReadme(root, counts) {
|
|
fs.writeFileSync(path.join(root, 'README.zh-CN.md'), `你现在可以使用 ${counts.agents} 个代理、${counts.skills} 个技能和 ${counts.commands} 个命令。\n`);
|
|
}
|
|
|
|
function writeZhDocsReadme(root, counts, options = {}) {
|
|
const tableCounts = options.tableCounts || counts;
|
|
const parityCounts = options.parityCounts || counts;
|
|
const unrelatedSkillsCount = options.unrelatedSkillsCount || 16;
|
|
const dir = path.join(root, 'docs', 'zh-CN');
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
|
|
fs.writeFileSync(path.join(dir, 'README.md'), `你现在可以使用 ${counts.agents} 个智能体、${counts.skills} 项技能和 ${counts.commands} 个命令了。
|
|
| 功能特性 | Claude Code | OpenCode | 状态 |
|
|
| --- | --- | --- | --- |
|
|
| 智能体 | PASS: ${tableCounts.agents} 个 |
|
|
| 命令 | PASS: ${tableCounts.commands} 个 |
|
|
| 技能 | PASS: ${tableCounts.skills} 项 |
|
|
|
|
| 功能特性 | 数量 | 格式 |
|
|
| --- | ---: | --- |
|
|
| 技能 | ${unrelatedSkillsCount} | .agents/skills/ |
|
|
|
|
## 跨工具功能对等
|
|
|
|
| 功能特性 | Claude Code | Cursor IDE | Codex CLI | OpenCode |
|
|
| --- | --- | --- | --- | --- |
|
|
| **智能体** | ${parityCounts.agents} | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |
|
|
| **命令** | ${parityCounts.commands} | 共享 | 基于指令 | 31 |
|
|
| **技能** | ${parityCounts.skills} | 共享 | 10 (原生格式) | 37 |
|
|
`);
|
|
}
|
|
|
|
function writeZhAgents(root, counts, options = {}) {
|
|
const plus = options.skillsMinimum ? '+' : '';
|
|
const dir = path.join(root, 'docs', 'zh-CN');
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
|
|
fs.writeFileSync(path.join(dir, 'AGENTS.md'), `这是一个生产就绪的 AI 编码插件,提供 ${counts.agents} 个专业代理、${counts.skills}${plus} 项技能、${counts.commands} 条命令。
|
|
|
|
\`\`\`
|
|
agents/ - ${counts.agents} 个专业子代理
|
|
skills/ - ${counts.skills}${plus} 个工作流技能和领域知识
|
|
commands/ - ${counts.commands} 个斜杠命令
|
|
\`\`\`
|
|
`);
|
|
}
|
|
|
|
function writeCatalogFixture(root, options = {}) {
|
|
const actualCounts = options.actualCounts || { agents: 1, skills: 1, commands: 1 };
|
|
const documentedCounts = options.documentedCounts || actualCounts;
|
|
const skillsMinimum = Boolean(options.skillsMinimum);
|
|
const unrelatedSkillsCount = options.unrelatedSkillsCount || 16;
|
|
|
|
writeCountedFiles(root, 'agents', actualCounts.agents);
|
|
writeCountedFiles(root, 'commands', actualCounts.commands);
|
|
writeCountedFiles(root, 'skills', actualCounts.skills);
|
|
|
|
fs.writeFileSync(path.join(root, 'agents', 'notes.txt'), 'not counted\n');
|
|
fs.writeFileSync(path.join(root, 'commands', 'notes.txt'), 'not counted\n');
|
|
fs.mkdirSync(path.join(root, 'skills', 'missing-skill-file'), { recursive: true });
|
|
|
|
writeEnglishReadme(root, documentedCounts, { unrelatedSkillsCount });
|
|
writeEnglishAgents(root, documentedCounts, { skillsMinimum });
|
|
writeZhRootReadme(root, documentedCounts);
|
|
writeZhDocsReadme(root, documentedCounts, { unrelatedSkillsCount });
|
|
writeZhAgents(root, documentedCounts, { skillsMinimum });
|
|
}
|
|
|
|
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 CI catalog.js ===\n');
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
if (test('builds catalog counts from a supplied root', () => {
|
|
const testDir = createTestDir();
|
|
try {
|
|
writeCatalogFixture(testDir, {
|
|
actualCounts: { agents: 2, skills: 1, commands: 3 },
|
|
documentedCounts: { agents: 2, skills: 1, commands: 3 },
|
|
});
|
|
|
|
const catalog = buildCatalog(testDir);
|
|
|
|
assert.deepStrictEqual(
|
|
{
|
|
agents: catalog.agents.count,
|
|
skills: catalog.skills.count,
|
|
commands: catalog.commands.count,
|
|
},
|
|
{ agents: 2, skills: 1, commands: 3 }
|
|
);
|
|
assert.ok(catalog.agents.files.every(file => file.endsWith('.md')));
|
|
assert.ok(catalog.skills.files.every(file => file.endsWith('/SKILL.md')));
|
|
} finally {
|
|
cleanupTestDir(testDir);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('reports mismatches from every tracked catalog document', () => {
|
|
const testDir = createTestDir();
|
|
try {
|
|
writeCatalogFixture(testDir, {
|
|
actualCounts: { agents: 1, skills: 1, commands: 1 },
|
|
documentedCounts: { agents: 9, skills: 9, commands: 9 },
|
|
});
|
|
|
|
const result = runCatalogCheck({ root: testDir });
|
|
const formatted = result.checks
|
|
.filter(check => !check.ok)
|
|
.map(formatExpectation)
|
|
.join('\n');
|
|
|
|
assert.ok(formatted.includes('README.md quick-start summary'));
|
|
assert.ok(formatted.includes('AGENTS.md summary'));
|
|
assert.ok(formatted.includes('README.zh-CN.md quick-start summary'));
|
|
assert.ok(formatted.includes('docs/zh-CN/README.md parity table'));
|
|
assert.ok(formatted.includes('docs/zh-CN/AGENTS.md project structure'));
|
|
} finally {
|
|
cleanupTestDir(testDir);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('write mode syncs counts while preserving plus suffixes and unrelated tables', () => {
|
|
const testDir = createTestDir();
|
|
try {
|
|
writeCatalogFixture(testDir, {
|
|
actualCounts: { agents: 1, skills: 1, commands: 1 },
|
|
documentedCounts: { agents: 7, skills: 7, commands: 7 },
|
|
skillsMinimum: true,
|
|
unrelatedSkillsCount: 42,
|
|
});
|
|
|
|
const result = runCatalogCheck({ root: testDir, writeMode: true });
|
|
|
|
assert.strictEqual(result.checks.filter(check => !check.ok).length, 0);
|
|
|
|
const readme = fs.readFileSync(path.join(testDir, 'README.md'), 'utf8');
|
|
const agentsDoc = fs.readFileSync(path.join(testDir, 'AGENTS.md'), 'utf8');
|
|
const zhReadme = fs.readFileSync(path.join(testDir, 'docs', 'zh-CN', 'README.md'), 'utf8');
|
|
const zhAgentsDoc = fs.readFileSync(path.join(testDir, 'docs', 'zh-CN', 'AGENTS.md'), 'utf8');
|
|
|
|
assert.ok(readme.includes('Access to 1 agents, 1 skills, and 1 legacy command shims'));
|
|
assert.ok(readme.includes('| Skills | 42 | .agents/skills/ |'));
|
|
assert.ok(agentsDoc.includes('providing 1 specialized agents, 1+ skills, 1 commands'));
|
|
assert.ok(agentsDoc.includes('skills/ - 1+ workflow skills and domain knowledge'));
|
|
assert.ok(zhReadme.includes('| 技能 | 42 | .agents/skills/ |'));
|
|
assert.ok(zhAgentsDoc.includes('提供 1 个专业代理、1+ 项技能、1 条命令'));
|
|
assert.ok(zhAgentsDoc.includes('skills/ - 1+ 个工作流技能和领域知识'));
|
|
} finally {
|
|
cleanupTestDir(testDir);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('throws a clear error for missing tracked documents', () => {
|
|
const testDir = createTestDir();
|
|
try {
|
|
writeCatalogFixture(testDir);
|
|
fs.rmSync(path.join(testDir, 'docs', 'zh-CN', 'AGENTS.md'));
|
|
|
|
assert.throws(
|
|
() => runCatalogCheck({ root: testDir }),
|
|
/Failed to read AGENTS\.md/
|
|
);
|
|
} finally {
|
|
cleanupTestDir(testDir);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
|
process.exit(failed > 0 ? 1 : 0);
|
|
}
|
|
|
|
runTests();
|