mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-01 22:53:27 +08:00
647 lines
23 KiB
JavaScript
647 lines
23 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* Verify repo catalog counts against tracked documentation files.
|
||
*
|
||
* Usage:
|
||
* node scripts/ci/catalog.js
|
||
* node scripts/ci/catalog.js --json
|
||
* node scripts/ci/catalog.js --md
|
||
* node scripts/ci/catalog.js --text
|
||
* node scripts/ci/catalog.js --write --text
|
||
*/
|
||
|
||
'use strict';
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const ROOT = path.join(__dirname, '../..');
|
||
const README_PATH = path.join(ROOT, 'README.md');
|
||
const AGENTS_PATH = path.join(ROOT, 'AGENTS.md');
|
||
const README_ZH_CN_PATH = path.join(ROOT, 'README.zh-CN.md');
|
||
const DOCS_ZH_CN_README_PATH = path.join(ROOT, 'docs', 'zh-CN', 'README.md');
|
||
const DOCS_ZH_CN_AGENTS_PATH = path.join(ROOT, 'docs', 'zh-CN', 'AGENTS.md');
|
||
const WRITE_MODE = process.argv.includes('--write');
|
||
|
||
const OUTPUT_MODE = process.argv.includes('--md')
|
||
? 'md'
|
||
: process.argv.includes('--text')
|
||
? 'text'
|
||
: 'json';
|
||
|
||
function normalizePathSegments(relativePath) {
|
||
return relativePath.split(path.sep).join('/');
|
||
}
|
||
|
||
function listMatchingFiles(relativeDir, matcher) {
|
||
const directory = path.join(ROOT, relativeDir);
|
||
if (!fs.existsSync(directory)) {
|
||
return [];
|
||
}
|
||
|
||
return fs.readdirSync(directory, { withFileTypes: true })
|
||
.filter(entry => matcher(entry))
|
||
.map(entry => normalizePathSegments(path.join(relativeDir, entry.name)))
|
||
.sort();
|
||
}
|
||
|
||
function buildCatalog() {
|
||
const agents = listMatchingFiles('agents', entry => entry.isFile() && entry.name.endsWith('.md'));
|
||
const commands = listMatchingFiles('commands', entry => entry.isFile() && entry.name.endsWith('.md'));
|
||
const skills = listMatchingFiles('skills', entry => (
|
||
entry.isDirectory() && fs.existsSync(path.join(ROOT, 'skills', entry.name, 'SKILL.md'))
|
||
)).map(skillDir => `${skillDir}/SKILL.md`);
|
||
|
||
return {
|
||
agents: { count: agents.length, files: agents, glob: 'agents/*.md' },
|
||
commands: { count: commands.length, files: commands, glob: 'commands/*.md' },
|
||
skills: { count: skills.length, files: skills, glob: 'skills/*/SKILL.md' }
|
||
};
|
||
}
|
||
|
||
function readFileOrThrow(filePath) {
|
||
try {
|
||
return fs.readFileSync(filePath, 'utf8');
|
||
} catch (error) {
|
||
throw new Error(`Failed to read ${path.basename(filePath)}: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
function writeFileOrThrow(filePath, content) {
|
||
try {
|
||
fs.writeFileSync(filePath, content, 'utf8');
|
||
} catch (error) {
|
||
throw new Error(`Failed to write ${path.basename(filePath)}: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
function replaceOrThrow(content, regex, replacer, source) {
|
||
if (!regex.test(content)) {
|
||
throw new Error(`${source} is missing the expected catalog marker`);
|
||
}
|
||
|
||
return content.replace(regex, replacer);
|
||
}
|
||
|
||
function parseReadmeExpectations(readmeContent) {
|
||
const expectations = [];
|
||
|
||
const quickStartMatch = readmeContent.match(/access to\s+(\d+)\s+agents,\s+(\d+)\s+skills,\s+and\s+(\d+)\s+commands/i);
|
||
if (!quickStartMatch) {
|
||
throw new Error('README.md is missing the quick-start catalog summary');
|
||
}
|
||
|
||
expectations.push(
|
||
{ category: 'agents', mode: 'exact', expected: Number(quickStartMatch[1]), source: 'README.md quick-start summary' },
|
||
{ category: 'skills', mode: 'exact', expected: Number(quickStartMatch[2]), source: 'README.md quick-start summary' },
|
||
{ category: 'commands', mode: 'exact', expected: Number(quickStartMatch[3]), source: 'README.md quick-start summary' }
|
||
);
|
||
|
||
const tablePatterns = [
|
||
{ category: 'agents', regex: /\|\s*(?:\*\*)?Agents(?:\*\*)?\s*\|\s*(?:(?:PASS:|\u2705)\s*)?(\d+)\s+agents\s*\|/i, source: 'README.md comparison table' },
|
||
{ category: 'commands', regex: /\|\s*(?:\*\*)?Commands(?:\*\*)?\s*\|\s*(?:(?:PASS:|\u2705)\s*)?(\d+)\s+commands\s*\|/i, source: 'README.md comparison table' },
|
||
{ category: 'skills', regex: /\|\s*(?:\*\*)?Skills(?:\*\*)?\s*\|\s*(?:(?:PASS:|\u2705)\s*)?(\d+)\s+skills\s*\|/i, source: 'README.md comparison table' }
|
||
];
|
||
|
||
for (const pattern of tablePatterns) {
|
||
const match = readmeContent.match(pattern.regex);
|
||
if (!match) {
|
||
throw new Error(`${pattern.source} is missing the ${pattern.category} row`);
|
||
}
|
||
|
||
expectations.push({
|
||
category: pattern.category,
|
||
mode: 'exact',
|
||
expected: Number(match[1]),
|
||
source: `${pattern.source} (${pattern.category})`
|
||
});
|
||
}
|
||
|
||
const parityPatterns = [
|
||
{
|
||
category: 'agents',
|
||
regex: /^\|\s*(?:\*\*)?Agents(?:\*\*)?\s*\|\s*(\d+)\s*\|\s*Shared\s*\(AGENTS\.md\)\s*\|\s*Shared\s*\(AGENTS\.md\)\s*\|\s*12\s*\|$/im,
|
||
source: 'README.md parity table'
|
||
},
|
||
{
|
||
category: 'commands',
|
||
regex: /^\|\s*(?:\*\*)?Commands(?:\*\*)?\s*\|\s*(\d+)\s*\|\s*Shared\s*\|\s*Instruction-based\s*\|\s*31\s*\|$/im,
|
||
source: 'README.md parity table'
|
||
},
|
||
{
|
||
category: 'skills',
|
||
regex: /^\|\s*(?:\*\*)?Skills(?:\*\*)?\s*\|\s*(\d+)\s*\|\s*Shared\s*\|\s*10\s*\(native format\)\s*\|\s*37\s*\|$/im,
|
||
source: 'README.md parity table'
|
||
}
|
||
];
|
||
|
||
for (const pattern of parityPatterns) {
|
||
const match = readmeContent.match(pattern.regex);
|
||
if (!match) {
|
||
throw new Error(`${pattern.source} is missing the ${pattern.category} row`);
|
||
}
|
||
|
||
expectations.push({
|
||
category: pattern.category,
|
||
mode: 'exact',
|
||
expected: Number(match[1]),
|
||
source: `${pattern.source} (${pattern.category})`
|
||
});
|
||
}
|
||
|
||
return expectations;
|
||
}
|
||
|
||
function parseZhRootReadmeExpectations(readmeContent) {
|
||
const match = readmeContent.match(/你现在可以使用\s+(\d+)\s+个代理、\s*(\d+)\s*个技能和\s*(\d+)\s*个命令/i);
|
||
if (!match) {
|
||
throw new Error('README.zh-CN.md is missing the quick-start catalog summary');
|
||
}
|
||
|
||
return [
|
||
{ category: 'agents', mode: 'exact', expected: Number(match[1]), source: 'README.zh-CN.md quick-start summary' },
|
||
{ category: 'skills', mode: 'exact', expected: Number(match[2]), source: 'README.zh-CN.md quick-start summary' },
|
||
{ category: 'commands', mode: 'exact', expected: Number(match[3]), source: 'README.zh-CN.md quick-start summary' }
|
||
];
|
||
}
|
||
|
||
function parseZhDocsReadmeExpectations(readmeContent) {
|
||
const expectations = [];
|
||
|
||
const quickStartMatch = readmeContent.match(/你现在可以使用\s+(\d+)\s+个智能体、\s*(\d+)\s*项技能和\s*(\d+)\s*个命令了/i);
|
||
if (!quickStartMatch) {
|
||
throw new Error('docs/zh-CN/README.md is missing the quick-start catalog summary');
|
||
}
|
||
|
||
expectations.push(
|
||
{ category: 'agents', mode: 'exact', expected: Number(quickStartMatch[1]), source: 'docs/zh-CN/README.md quick-start summary' },
|
||
{ category: 'skills', mode: 'exact', expected: Number(quickStartMatch[2]), source: 'docs/zh-CN/README.md quick-start summary' },
|
||
{ category: 'commands', mode: 'exact', expected: Number(quickStartMatch[3]), source: 'docs/zh-CN/README.md quick-start summary' }
|
||
);
|
||
|
||
const tablePatterns = [
|
||
{ category: 'agents', regex: /\|\s*智能体\s*\|\s*(?:(?:PASS:|\u2705)\s*)?(\d+)\s*个\s*\|/i, source: 'docs/zh-CN/README.md comparison table' },
|
||
{ category: 'commands', regex: /\|\s*命令\s*\|\s*(?:(?:PASS:|\u2705)\s*)?(\d+)\s*个\s*\|/i, source: 'docs/zh-CN/README.md comparison table' },
|
||
{ category: 'skills', regex: /\|\s*技能\s*\|\s*(?:(?:PASS:|\u2705)\s*)?(\d+)\s*项\s*\|/i, source: 'docs/zh-CN/README.md comparison table' }
|
||
];
|
||
|
||
for (const pattern of tablePatterns) {
|
||
const match = readmeContent.match(pattern.regex);
|
||
if (!match) {
|
||
throw new Error(`${pattern.source} is missing the ${pattern.category} row`);
|
||
}
|
||
|
||
expectations.push({
|
||
category: pattern.category,
|
||
mode: 'exact',
|
||
expected: Number(match[1]),
|
||
source: `${pattern.source} (${pattern.category})`
|
||
});
|
||
}
|
||
|
||
const parityPatterns = [
|
||
{
|
||
category: 'agents',
|
||
regex: /^\|\s*(?:\*\*)?智能体(?:\*\*)?\s*\|\s*(\d+)\s*\|\s*共享\s*\(AGENTS\.md\)\s*\|\s*共享\s*\(AGENTS\.md\)\s*\|\s*12\s*\|$/im,
|
||
source: 'docs/zh-CN/README.md parity table'
|
||
},
|
||
{
|
||
category: 'commands',
|
||
regex: /^\|\s*(?:\*\*)?命令(?:\*\*)?\s*\|\s*(\d+)\s*\|\s*共享\s*\|\s*基于指令\s*\|\s*31\s*\|$/im,
|
||
source: 'docs/zh-CN/README.md parity table'
|
||
},
|
||
{
|
||
category: 'skills',
|
||
regex: /^\|\s*(?:\*\*)?技能(?:\*\*)?\s*\|\s*(\d+)\s*\|\s*共享\s*\|\s*10\s*\(原生格式\)\s*\|\s*37\s*\|$/im,
|
||
source: 'docs/zh-CN/README.md parity table'
|
||
}
|
||
];
|
||
|
||
for (const pattern of parityPatterns) {
|
||
const match = readmeContent.match(pattern.regex);
|
||
if (!match) {
|
||
throw new Error(`${pattern.source} is missing the ${pattern.category} row`);
|
||
}
|
||
|
||
expectations.push({
|
||
category: pattern.category,
|
||
mode: 'exact',
|
||
expected: Number(match[1]),
|
||
source: `${pattern.source} (${pattern.category})`
|
||
});
|
||
}
|
||
|
||
return expectations;
|
||
}
|
||
|
||
function parseAgentsDocExpectations(agentsContent) {
|
||
const summaryMatch = agentsContent.match(/providing\s+(\d+)\s+specialized agents,\s+(\d+)(\+)?\s+skills,\s+(\d+)\s+commands/i);
|
||
if (!summaryMatch) {
|
||
throw new Error('AGENTS.md is missing the catalog summary line');
|
||
}
|
||
|
||
const expectations = [
|
||
{ category: 'agents', mode: 'exact', expected: Number(summaryMatch[1]), source: 'AGENTS.md summary' },
|
||
{
|
||
category: 'skills',
|
||
mode: summaryMatch[3] ? 'minimum' : 'exact',
|
||
expected: Number(summaryMatch[2]),
|
||
source: 'AGENTS.md summary'
|
||
},
|
||
{ category: 'commands', mode: 'exact', expected: Number(summaryMatch[4]), source: 'AGENTS.md summary' }
|
||
];
|
||
|
||
const structurePatterns = [
|
||
{
|
||
category: 'agents',
|
||
mode: 'exact',
|
||
regex: /^\s*agents\/\s*[—–-]\s*(\d+)\s+specialized subagents\s*$/im,
|
||
source: 'AGENTS.md project structure'
|
||
},
|
||
{
|
||
category: 'skills',
|
||
mode: 'minimum',
|
||
regex: /^\s*skills\/\s*[—–-]\s*(\d+)(\+)?\s+workflow skills and domain knowledge\s*$/im,
|
||
source: 'AGENTS.md project structure'
|
||
},
|
||
{
|
||
category: 'commands',
|
||
mode: 'exact',
|
||
regex: /^\s*commands\/\s*[—–-]\s*(\d+)\s+slash commands\s*$/im,
|
||
source: 'AGENTS.md project structure'
|
||
}
|
||
];
|
||
|
||
for (const pattern of structurePatterns) {
|
||
const match = agentsContent.match(pattern.regex);
|
||
if (!match) {
|
||
throw new Error(`${pattern.source} is missing the ${pattern.category} entry`);
|
||
}
|
||
|
||
expectations.push({
|
||
category: pattern.category,
|
||
mode: pattern.mode === 'minimum' && match[2] ? 'minimum' : pattern.mode,
|
||
expected: Number(match[1]),
|
||
source: `${pattern.source} (${pattern.category})`
|
||
});
|
||
}
|
||
|
||
return expectations;
|
||
}
|
||
|
||
function parseZhAgentsDocExpectations(agentsContent) {
|
||
const summaryMatch = agentsContent.match(/提供\s+(\d+)\s+个专业代理、\s*(\d+)(\+)?\s*项技能、\s*(\d+)\s+条命令/i);
|
||
if (!summaryMatch) {
|
||
throw new Error('docs/zh-CN/AGENTS.md is missing the catalog summary line');
|
||
}
|
||
|
||
const expectations = [
|
||
{ category: 'agents', mode: 'exact', expected: Number(summaryMatch[1]), source: 'docs/zh-CN/AGENTS.md summary' },
|
||
{
|
||
category: 'skills',
|
||
mode: summaryMatch[3] ? 'minimum' : 'exact',
|
||
expected: Number(summaryMatch[2]),
|
||
source: 'docs/zh-CN/AGENTS.md summary'
|
||
},
|
||
{ category: 'commands', mode: 'exact', expected: Number(summaryMatch[4]), source: 'docs/zh-CN/AGENTS.md summary' }
|
||
];
|
||
|
||
const structurePatterns = [
|
||
{
|
||
category: 'agents',
|
||
mode: 'exact',
|
||
regex: /^\s*agents\/\s*[—–-]\s*(\d+)\s+个专业子代理\s*$/im,
|
||
source: 'docs/zh-CN/AGENTS.md project structure'
|
||
},
|
||
{
|
||
category: 'skills',
|
||
mode: 'minimum',
|
||
regex: /^\s*skills\/\s*[—–-]\s*(\d+)(\+)?\s+个工作流技能和领域知识\s*$/im,
|
||
source: 'docs/zh-CN/AGENTS.md project structure'
|
||
},
|
||
{
|
||
category: 'commands',
|
||
mode: 'exact',
|
||
regex: /^\s*commands\/\s*[—–-]\s*(\d+)\s+个斜杠命令\s*$/im,
|
||
source: 'docs/zh-CN/AGENTS.md project structure'
|
||
}
|
||
];
|
||
|
||
for (const pattern of structurePatterns) {
|
||
const match = agentsContent.match(pattern.regex);
|
||
if (!match) {
|
||
throw new Error(`${pattern.source} is missing the ${pattern.category} entry`);
|
||
}
|
||
|
||
expectations.push({
|
||
category: pattern.category,
|
||
mode: pattern.mode === 'minimum' && match[2] ? 'minimum' : pattern.mode,
|
||
expected: Number(match[1]),
|
||
source: `${pattern.source} (${pattern.category})`
|
||
});
|
||
}
|
||
|
||
return expectations;
|
||
}
|
||
|
||
function evaluateExpectations(catalog, expectations) {
|
||
return expectations.map(expectation => {
|
||
const actual = catalog[expectation.category].count;
|
||
const ok = expectation.mode === 'minimum'
|
||
? actual >= expectation.expected
|
||
: actual === expectation.expected;
|
||
|
||
return {
|
||
...expectation,
|
||
actual,
|
||
ok
|
||
};
|
||
});
|
||
}
|
||
|
||
function formatExpectation(expectation) {
|
||
const comparator = expectation.mode === 'minimum' ? '>=' : '=';
|
||
return `${expectation.source}: ${expectation.category} documented ${comparator} ${expectation.expected}, actual ${expectation.actual}`;
|
||
}
|
||
|
||
function syncEnglishReadme(content, catalog) {
|
||
let nextContent = content;
|
||
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/(access to\s+)(\d+)(\s+agents,\s+)(\d+)(\s+skills,\s+and\s+)(\d+)(\s+commands)/i,
|
||
(_, prefix, __, agentsSuffix, ___, skillsSuffix, ____, commandsSuffix) =>
|
||
`${prefix}${catalog.agents.count}${agentsSuffix}${catalog.skills.count}${skillsSuffix}${catalog.commands.count}${commandsSuffix}`,
|
||
'README.md quick-start summary'
|
||
);
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/(\|\s*(?:\*\*)?Agents(?:\*\*)?\s*\|\s*(?:(?:PASS:|\u2705)\s*)?)(\d+)(\s+agents\s*\|)/i,
|
||
(_, prefix, __, suffix) => `${prefix}${catalog.agents.count}${suffix}`,
|
||
'README.md comparison table (agents)'
|
||
);
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/(\|\s*(?:\*\*)?Commands(?:\*\*)?\s*\|\s*(?:(?:PASS:|\u2705)\s*)?)(\d+)(\s+commands\s*\|)/i,
|
||
(_, prefix, __, suffix) => `${prefix}${catalog.commands.count}${suffix}`,
|
||
'README.md comparison table (commands)'
|
||
);
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/(\|\s*(?:\*\*)?Skills(?:\*\*)?\s*\|\s*(?:(?:PASS:|\u2705)\s*)?)(\d+)(\s+skills\s*\|)/i,
|
||
(_, prefix, __, suffix) => `${prefix}${catalog.skills.count}${suffix}`,
|
||
'README.md comparison table (skills)'
|
||
);
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/^(\|\s*(?:\*\*)?Agents(?:\*\*)?\s*\|\s*)(\d+)(\s*\|\s*Shared\s*\(AGENTS\.md\)\s*\|\s*Shared\s*\(AGENTS\.md\)\s*\|\s*12\s*\|)$/im,
|
||
(_, prefix, __, suffix) => `${prefix}${catalog.agents.count}${suffix}`,
|
||
'README.md parity table (agents)'
|
||
);
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/^(\|\s*(?:\*\*)?Commands(?:\*\*)?\s*\|\s*)(\d+)(\s*\|\s*Shared\s*\|\s*Instruction-based\s*\|\s*31\s*\|)$/im,
|
||
(_, prefix, __, suffix) => `${prefix}${catalog.commands.count}${suffix}`,
|
||
'README.md parity table (commands)'
|
||
);
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/^(\|\s*(?:\*\*)?Skills(?:\*\*)?\s*\|\s*)(\d+)(\s*\|\s*Shared\s*\|\s*10\s*\(native format\)\s*\|\s*37\s*\|)$/im,
|
||
(_, prefix, __, suffix) => `${prefix}${catalog.skills.count}${suffix}`,
|
||
'README.md parity table (skills)'
|
||
);
|
||
|
||
return nextContent;
|
||
}
|
||
|
||
function syncEnglishAgents(content, catalog) {
|
||
let nextContent = content;
|
||
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/(providing\s+)(\d+)(\s+specialized agents,\s+)(\d+)(\+?)(\s+skills,\s+)(\d+)(\s+commands)/i,
|
||
(_, prefix, __, agentsSuffix, ___, skillsPlus, skillsSuffix, ____, commandsSuffix) =>
|
||
`${prefix}${catalog.agents.count}${agentsSuffix}${catalog.skills.count}${skillsPlus}${skillsSuffix}${catalog.commands.count}${commandsSuffix}`,
|
||
'AGENTS.md summary'
|
||
);
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/^(\s*agents\/\s*[—–-]\s*)(\d+)(\s+specialized subagents\s*)$/im,
|
||
(_, prefix, __, suffix) => `${prefix}${catalog.agents.count}${suffix}`,
|
||
'AGENTS.md project structure (agents)'
|
||
);
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/^(\s*skills\/\s*[—–-]\s*)(\d+)(\+?)(\s+workflow skills and domain knowledge\s*)$/im,
|
||
(_, prefix, __, plus, suffix) => `${prefix}${catalog.skills.count}${plus}${suffix}`,
|
||
'AGENTS.md project structure (skills)'
|
||
);
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/^(\s*commands\/\s*[—–-]\s*)(\d+)(\s+slash commands\s*)$/im,
|
||
(_, prefix, __, suffix) => `${prefix}${catalog.commands.count}${suffix}`,
|
||
'AGENTS.md project structure (commands)'
|
||
);
|
||
|
||
return nextContent;
|
||
}
|
||
|
||
function syncZhRootReadme(content, catalog) {
|
||
return replaceOrThrow(
|
||
content,
|
||
/(你现在可以使用\s+)(\d+)(\s+个代理、\s*)(\d+)(\s*个技能和\s*)(\d+)(\s*个命令[。.!!]?)/i,
|
||
(_, prefix, __, agentsSuffix, ___, skillsSuffix, ____, commandsSuffix) =>
|
||
`${prefix}${catalog.agents.count}${agentsSuffix}${catalog.skills.count}${skillsSuffix}${catalog.commands.count}${commandsSuffix}`,
|
||
'README.zh-CN.md quick-start summary'
|
||
);
|
||
}
|
||
|
||
function syncZhDocsReadme(content, catalog) {
|
||
let nextContent = content;
|
||
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/(你现在可以使用\s+)(\d+)(\s+个智能体、\s*)(\d+)(\s*项技能和\s*)(\d+)(\s*个命令了[。.!!]?)/i,
|
||
(_, prefix, __, agentsSuffix, ___, skillsSuffix, ____, commandsSuffix) =>
|
||
`${prefix}${catalog.agents.count}${agentsSuffix}${catalog.skills.count}${skillsSuffix}${catalog.commands.count}${commandsSuffix}`,
|
||
'docs/zh-CN/README.md quick-start summary'
|
||
);
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/(\|\s*智能体\s*\|\s*(?:(?:PASS:|\u2705)\s*)?)(\d+)(\s*个\s*\|)/i,
|
||
(_, prefix, __, suffix) => `${prefix}${catalog.agents.count}${suffix}`,
|
||
'docs/zh-CN/README.md comparison table (agents)'
|
||
);
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/(\|\s*命令\s*\|\s*(?:(?:PASS:|\u2705)\s*)?)(\d+)(\s*个\s*\|)/i,
|
||
(_, prefix, __, suffix) => `${prefix}${catalog.commands.count}${suffix}`,
|
||
'docs/zh-CN/README.md comparison table (commands)'
|
||
);
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/(\|\s*技能\s*\|\s*(?:(?:PASS:|\u2705)\s*)?)(\d+)(\s*项\s*\|)/i,
|
||
(_, prefix, __, suffix) => `${prefix}${catalog.skills.count}${suffix}`,
|
||
'docs/zh-CN/README.md comparison table (skills)'
|
||
);
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/^(\|\s*(?:\*\*)?智能体(?:\*\*)?\s*\|\s*)(\d+)(\s*\|\s*共享\s*\(AGENTS\.md\)\s*\|\s*共享\s*\(AGENTS\.md\)\s*\|\s*12\s*\|)$/im,
|
||
(_, prefix, __, suffix) => `${prefix}${catalog.agents.count}${suffix}`,
|
||
'docs/zh-CN/README.md parity table (agents)'
|
||
);
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/^(\|\s*(?:\*\*)?命令(?:\*\*)?\s*\|\s*)(\d+)(\s*\|\s*共享\s*\|\s*基于指令\s*\|\s*31\s*\|)$/im,
|
||
(_, prefix, __, suffix) => `${prefix}${catalog.commands.count}${suffix}`,
|
||
'docs/zh-CN/README.md parity table (commands)'
|
||
);
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/^(\|\s*(?:\*\*)?技能(?:\*\*)?\s*\|\s*)(\d+)(\s*\|\s*共享\s*\|\s*10\s*\(原生格式\)\s*\|\s*37\s*\|)$/im,
|
||
(_, prefix, __, suffix) => `${prefix}${catalog.skills.count}${suffix}`,
|
||
'docs/zh-CN/README.md parity table (skills)'
|
||
);
|
||
|
||
return nextContent;
|
||
}
|
||
|
||
function syncZhAgents(content, catalog) {
|
||
let nextContent = content;
|
||
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/(提供\s+)(\d+)(\s+个专业代理、\s*)(\d+)(\+?)(\s*项技能、\s*)(\d+)(\s+条命令)/i,
|
||
(_, prefix, __, agentsSuffix, ___, skillsPlus, skillsSuffix, ____, commandsSuffix) =>
|
||
`${prefix}${catalog.agents.count}${agentsSuffix}${catalog.skills.count}${skillsPlus}${skillsSuffix}${catalog.commands.count}${commandsSuffix}`,
|
||
'docs/zh-CN/AGENTS.md summary'
|
||
);
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/^(\s*agents\/\s*[—–-]\s*)(\d+)(\s+个专业子代理\s*)$/im,
|
||
(_, prefix, __, suffix) => `${prefix}${catalog.agents.count}${suffix}`,
|
||
'docs/zh-CN/AGENTS.md project structure (agents)'
|
||
);
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/^(\s*skills\/\s*[—–-]\s*)(\d+)(\+?)(\s+个工作流技能和领域知识\s*)$/im,
|
||
(_, prefix, __, plus, suffix) => `${prefix}${catalog.skills.count}${plus}${suffix}`,
|
||
'docs/zh-CN/AGENTS.md project structure (skills)'
|
||
);
|
||
nextContent = replaceOrThrow(
|
||
nextContent,
|
||
/^(\s*commands\/\s*[—–-]\s*)(\d+)(\s+个斜杠命令\s*)$/im,
|
||
(_, prefix, __, suffix) => `${prefix}${catalog.commands.count}${suffix}`,
|
||
'docs/zh-CN/AGENTS.md project structure (commands)'
|
||
);
|
||
|
||
return nextContent;
|
||
}
|
||
|
||
const DOCUMENT_SPECS = [
|
||
{
|
||
filePath: README_PATH,
|
||
parseExpectations: parseReadmeExpectations,
|
||
syncContent: syncEnglishReadme,
|
||
},
|
||
{
|
||
filePath: AGENTS_PATH,
|
||
parseExpectations: parseAgentsDocExpectations,
|
||
syncContent: syncEnglishAgents,
|
||
},
|
||
{
|
||
filePath: README_ZH_CN_PATH,
|
||
parseExpectations: parseZhRootReadmeExpectations,
|
||
syncContent: syncZhRootReadme,
|
||
},
|
||
{
|
||
filePath: DOCS_ZH_CN_README_PATH,
|
||
parseExpectations: parseZhDocsReadmeExpectations,
|
||
syncContent: syncZhDocsReadme,
|
||
},
|
||
{
|
||
filePath: DOCS_ZH_CN_AGENTS_PATH,
|
||
parseExpectations: parseZhAgentsDocExpectations,
|
||
syncContent: syncZhAgents,
|
||
},
|
||
];
|
||
|
||
function renderText(result) {
|
||
console.log('Catalog counts:');
|
||
console.log(`- agents: ${result.catalog.agents.count}`);
|
||
console.log(`- commands: ${result.catalog.commands.count}`);
|
||
console.log(`- skills: ${result.catalog.skills.count}`);
|
||
console.log('');
|
||
|
||
const mismatches = result.checks.filter(check => !check.ok);
|
||
if (mismatches.length === 0) {
|
||
console.log('Documentation counts match the repository catalog.');
|
||
return;
|
||
}
|
||
|
||
console.error('Documentation count mismatches found:');
|
||
for (const mismatch of mismatches) {
|
||
console.error(`- ${formatExpectation(mismatch)}`);
|
||
}
|
||
}
|
||
|
||
function renderMarkdown(result) {
|
||
const mismatches = result.checks.filter(check => !check.ok);
|
||
console.log('# ECC Catalog Verification\n');
|
||
console.log('| Category | Count | Pattern |');
|
||
console.log('| --- | ---: | --- |');
|
||
console.log(`| Agents | ${result.catalog.agents.count} | \`${result.catalog.agents.glob}\` |`);
|
||
console.log(`| Commands | ${result.catalog.commands.count} | \`${result.catalog.commands.glob}\` |`);
|
||
console.log(`| Skills | ${result.catalog.skills.count} | \`${result.catalog.skills.glob}\` |`);
|
||
console.log('');
|
||
|
||
if (mismatches.length === 0) {
|
||
console.log('Documentation counts match the repository catalog.');
|
||
return;
|
||
}
|
||
|
||
console.log('## Mismatches\n');
|
||
for (const mismatch of mismatches) {
|
||
console.log(`- ${formatExpectation(mismatch)}`);
|
||
}
|
||
}
|
||
|
||
function main() {
|
||
const catalog = buildCatalog();
|
||
|
||
if (WRITE_MODE) {
|
||
for (const spec of DOCUMENT_SPECS) {
|
||
const currentContent = readFileOrThrow(spec.filePath);
|
||
const nextContent = spec.syncContent(currentContent, catalog);
|
||
if (nextContent !== currentContent) {
|
||
writeFileOrThrow(spec.filePath, nextContent);
|
||
}
|
||
}
|
||
}
|
||
|
||
const expectations = DOCUMENT_SPECS.flatMap(spec => (
|
||
spec.parseExpectations(readFileOrThrow(spec.filePath))
|
||
));
|
||
const checks = evaluateExpectations(catalog, expectations);
|
||
const result = { catalog, checks };
|
||
|
||
if (OUTPUT_MODE === 'json') {
|
||
console.log(JSON.stringify(result, null, 2));
|
||
} else if (OUTPUT_MODE === 'md') {
|
||
renderMarkdown(result);
|
||
} else {
|
||
renderText(result);
|
||
}
|
||
|
||
if (checks.some(check => !check.ok)) {
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
try {
|
||
main();
|
||
} catch (error) {
|
||
console.error(`ERROR: ${error.message}`);
|
||
process.exit(1);
|
||
}
|