From 27508842b16d19a2d2d2c6ed778aac52cc5fee0c Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Mon, 11 May 2026 02:33:29 -0400 Subject: [PATCH] fix: sync skill frontmatter and catalog counts Adds missing skill frontmatter, normalizes strict YAML metadata, syncs README catalog counts, and extends catalog validation for README/plugin/marketplace count drift. --- README.md | 2 +- docs/zh-CN/skills/skill-stocktake/SKILL.md | 1 + scripts/ci/catalog.js | 106 +++++++++++++++++++++ skills/openclaw-persona-forge/SKILL.md | 10 +- skills/skill-stocktake/SKILL.md | 1 + tests/ci/catalog.test.js | 26 +++++ tests/ci/validators.test.js | 49 +++++++++- 7 files changed, 183 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index a991f60b..05352cf5 100644 --- a/README.md +++ b/README.md @@ -448,7 +448,7 @@ everything-claude-code/ | |-- plugin.json # Plugin metadata and component paths | |-- marketplace.json # Marketplace catalog for /plugin marketplace add | -|-- agents/ # 36 specialized subagents for delegation +|-- agents/ # 48 specialized subagents for delegation | |-- planner.md # Feature implementation planning | |-- architect.md # System design decisions | |-- tdd-guide.md # Test-driven development diff --git a/docs/zh-CN/skills/skill-stocktake/SKILL.md b/docs/zh-CN/skills/skill-stocktake/SKILL.md index 1a06597f..2a0c13cc 100644 --- a/docs/zh-CN/skills/skill-stocktake/SKILL.md +++ b/docs/zh-CN/skills/skill-stocktake/SKILL.md @@ -1,4 +1,5 @@ --- +name: skill-stocktake description: "用于审计Claude技能和命令的质量。支持快速扫描(仅变更技能)和全面盘点模式,采用顺序子代理批量评估。" origin: ECC --- diff --git a/scripts/ci/catalog.js b/scripts/ci/catalog.js index 4c482d2c..3c86eb4e 100644 --- a/scripts/ci/catalog.js +++ b/scripts/ci/catalog.js @@ -21,6 +21,8 @@ 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 PLUGIN_JSON_PATH = path.join(ROOT, '.claude-plugin', 'plugin.json'); +const MARKETPLACE_JSON_PATH = path.join(ROOT, '.claude-plugin', 'marketplace.json'); const WRITE_MODE = process.argv.includes('--write'); const OUTPUT_MODE = process.argv.includes('--md') @@ -99,6 +101,18 @@ function parseReadmeExpectations(readmeContent) { { category: 'commands', mode: 'exact', expected: Number(quickStartMatch[3]), source: 'README.md quick-start 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'); + } + + expectations.push({ + category: 'agents', + mode: 'exact', + expected: Number(projectTreeAgentsMatch[1]), + source: 'README.md project tree (agents)' + }); + 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' }, @@ -346,6 +360,31 @@ function parseZhAgentsDocExpectations(agentsContent) { return expectations; } +function parseCatalogDescriptionExpectations(content, source, getDescription) { + let parsed; + try { + parsed = JSON.parse(content); + } catch (error) { + throw new Error(`${source} is not valid JSON: ${error.message}`); + } + + const description = getDescription(parsed); + if (typeof description !== 'string') { + throw new Error(`${source} is missing the catalog count description`); + } + + const match = description.match(/(\d+)\s+agents,\s+(\d+)\s+skills,\s+(\d+)\s+legacy command shims?/i); + if (!match) { + throw new Error(`${source} is missing the catalog count description`); + } + + return [ + { category: 'agents', mode: 'exact', expected: Number(match[1]), source }, + { category: 'skills', mode: 'exact', expected: Number(match[2]), source }, + { category: 'commands', mode: 'exact', expected: Number(match[3]), source }, + ]; +} + function evaluateExpectations(catalog, expectations) { return expectations.map(expectation => { const actual = catalog[expectation.category].count; @@ -376,6 +415,12 @@ 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, + /^(\|\s*--\s*agents\/\s*#\s*)(\d+)(\s+specialized subagents for delegation\s*)$/im, + (_, prefix, __, suffix) => `${prefix}${catalog.agents.count}${suffix}`, + 'README.md project tree (agents)' + ); nextContent = replaceOrThrow( nextContent, /(\|\s*(?:\*\*)?Agents(?:\*\*)?\s*\|\s*(?:(?:PASS:|\u2705)\s*)?)(\d+)(\s+agents\s*\|)/i, @@ -540,6 +585,31 @@ function syncZhAgents(content, catalog) { return nextContent; } +function syncCatalogDescription(content, catalog, source, getDescription, setDescription) { + let parsed; + try { + parsed = JSON.parse(content); + } catch (error) { + throw new Error(`${source} is not valid JSON: ${error.message}`); + } + + const description = getDescription(parsed); + if (typeof description !== 'string') { + throw new Error(`${source} is missing the catalog count description`); + } + + const nextDescription = replaceOrThrow( + description, + /(\d+)(\s+agents,\s+)(\d+)(\s+skills,\s+)(\d+)(\s+legacy command shims?)/i, + (_, __, agentsSuffix, ___, skillsSuffix, ____, commandsSuffix) => + `${catalog.agents.count}${agentsSuffix}${catalog.skills.count}${skillsSuffix}${catalog.commands.count}${commandsSuffix}`, + source + ); + + setDescription(parsed, nextDescription); + return `${JSON.stringify(parsed, null, 2)}\n`; +} + function createDocumentSpecs(paths = {}) { const { readmePath = README_PATH, @@ -547,6 +617,8 @@ function createDocumentSpecs(paths = {}) { zhRootReadmePath = README_ZH_CN_PATH, zhDocsReadmePath = DOCS_ZH_CN_README_PATH, zhDocsAgentsPath = DOCS_ZH_CN_AGENTS_PATH, + pluginJsonPath = PLUGIN_JSON_PATH, + marketplaceJsonPath = MARKETPLACE_JSON_PATH, } = paths; return [ @@ -575,6 +647,36 @@ function createDocumentSpecs(paths = {}) { parseExpectations: parseZhAgentsDocExpectations, syncContent: syncZhAgents, }, + { + filePath: pluginJsonPath, + parseExpectations: content => parseCatalogDescriptionExpectations( + content, + '.claude-plugin/plugin.json description', + parsed => parsed.description + ), + syncContent: (content, catalog) => syncCatalogDescription( + content, + catalog, + '.claude-plugin/plugin.json description', + parsed => parsed.description, + (parsed, description) => { parsed.description = description; } + ), + }, + { + filePath: marketplaceJsonPath, + parseExpectations: content => parseCatalogDescriptionExpectations( + content, + '.claude-plugin/marketplace.json plugin description', + parsed => parsed.plugins?.[0]?.description + ), + syncContent: (content, catalog) => syncCatalogDescription( + content, + catalog, + '.claude-plugin/marketplace.json plugin description', + parsed => parsed.plugins?.[0]?.description, + (parsed, description) => { parsed.plugins[0].description = description; } + ), + }, ]; } @@ -585,6 +687,8 @@ function createDocumentSpecsForRoot(root) { zhRootReadmePath: path.join(root, 'README.zh-CN.md'), zhDocsReadmePath: path.join(root, 'docs', 'zh-CN', 'README.md'), zhDocsAgentsPath: path.join(root, 'docs', 'zh-CN', 'AGENTS.md'), + pluginJsonPath: path.join(root, '.claude-plugin', 'plugin.json'), + marketplaceJsonPath: path.join(root, '.claude-plugin', 'marketplace.json'), }); } @@ -689,11 +793,13 @@ module.exports = { formatExpectation, main, parseAgentsDocExpectations, + parseCatalogDescriptionExpectations, parseReadmeExpectations, parseZhAgentsDocExpectations, parseZhDocsReadmeExpectations, parseZhRootReadmeExpectations, runCatalogCheck, + syncCatalogDescription, syncEnglishAgents, syncEnglishReadme, syncZhAgents, diff --git a/skills/openclaw-persona-forge/SKILL.md b/skills/openclaw-persona-forge/SKILL.md index f7c47f4e..37c7a3a0 100644 --- a/skills/openclaw-persona-forge/SKILL.md +++ b/skills/openclaw-persona-forge/SKILL.md @@ -1,14 +1,6 @@ --- name: openclaw-persona-forge -description: |- - 为 OpenClaw AI Agent 锻造完整的龙虾灵魂方案。根据用户偏好或随机抽卡, - 输出身份定位、灵魂描述(SOUL.md)、角色化底线规则、名字和头像生图提示词。 - 如当前环境提供已审核的生图 skill,可自动生成统一风格头像图片。 - 当用户需要创建、设计或定制 OpenClaw 龙虾灵魂时使用。 - 不适用于:微调已有 SOUL.md、非 OpenClaw 平台的角色设计、纯工具型无性格 Agent。 - 触发词:龙虾灵魂、虾魂、OpenClaw 灵魂、养虾灵魂、龙虾角色、龙虾定位、 - 龙虾剧本杀角色、龙虾游戏角色、龙虾 NPC、龙虾性格、龙虾背景故事、 - lobster soul、lobster character、抽卡、随机龙虾、龙虾 SOUL、gacha。 +description: "为 OpenClaw AI Agent 锻造完整的龙虾灵魂方案。根据用户偏好或随机抽卡, 输出身份定位、灵魂描述(SOUL.md)、角色化底线规则、名字和头像生图提示词。 如当前环境提供已审核的生图 skill,可自动生成统一风格头像图片。 当用户需要创建、设计或定制 OpenClaw 龙虾灵魂时使用。 不适用于:微调已有 SOUL.md、非 OpenClaw 平台的角色设计、纯工具型无性格 Agent。 触发词:龙虾灵魂、虾魂、OpenClaw 灵魂、养虾灵魂、龙虾角色、龙虾定位、 龙虾剧本杀角色、龙虾游戏角色、龙虾 NPC、龙虾性格、龙虾背景故事、 lobster soul、lobster character、抽卡、随机龙虾、龙虾 SOUL、gacha。" origin: community --- diff --git a/skills/skill-stocktake/SKILL.md b/skills/skill-stocktake/SKILL.md index 7ae77c27..42a0242b 100644 --- a/skills/skill-stocktake/SKILL.md +++ b/skills/skill-stocktake/SKILL.md @@ -1,4 +1,5 @@ --- +name: skill-stocktake description: "Use when auditing Claude skills and commands for quality. Supports Quick Scan (changed skills only) and Full Stocktake modes with sequential subagent batch evaluation." origin: ECC --- diff --git a/tests/ci/catalog.test.js b/tests/ci/catalog.test.js index 98e22a84..ee7a6540 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. +|-- agents/ # ${counts.agents} specialized subagents for delegation | Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode | | --- | --- | --- | --- | --- | | Agents | PASS: ${tableCounts.agents} agents | @@ -64,6 +65,22 @@ function writeEnglishReadme(root, counts, options = {}) { `); } +function writePluginMetadata(root, counts) { + const pluginDir = path.join(root, '.claude-plugin'); + fs.mkdirSync(pluginDir, { recursive: true }); + + fs.writeFileSync(path.join(pluginDir, 'plugin.json'), JSON.stringify({ + name: 'ecc', + description: `Fixture plugin — ${counts.agents} agents, ${counts.skills} skills, ${counts.commands} legacy command shims`, + }, null, 2)); + fs.writeFileSync(path.join(pluginDir, 'marketplace.json'), JSON.stringify({ + plugins: [{ + name: 'ecc', + description: `Fixture marketplace plugin — ${counts.agents} agents, ${counts.skills} skills, ${counts.commands} legacy command shims`, + }], + }, null, 2)); +} + function writeEnglishAgents(root, counts, options = {}) { const plus = options.skillsMinimum ? '+' : ''; @@ -143,6 +160,7 @@ function writeCatalogFixture(root, options = {}) { writeZhRootReadme(root, documentedCounts); writeZhDocsReadme(root, documentedCounts, { unrelatedSkillsCount }); writeZhAgents(root, documentedCounts, { skillsMinimum }); + writePluginMetadata(root, documentedCounts); } function test(name, fn) { @@ -203,7 +221,10 @@ function runTests() { .join('\n'); assert.ok(formatted.includes('README.md quick-start 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')); + assert.ok(formatted.includes('.claude-plugin/marketplace.json plugin description')); 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')); @@ -230,14 +251,19 @@ function runTests() { 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'); + const pluginJson = fs.readFileSync(path.join(testDir, '.claude-plugin', 'plugin.json'), 'utf8'); + 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('|-- 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')); 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+ 个工作流技能和领域知识')); + assert.ok(pluginJson.includes('1 agents, 1 skills, 1 legacy command shims')); + assert.ok(marketplaceJson.includes('1 agents, 1 skills, 1 legacy command shims')); } finally { cleanupTestDir(testDir); } diff --git a/tests/ci/validators.test.js b/tests/ci/validators.test.js index a35c760e..c10c8cf1 100644 --- a/tests/ci/validators.test.js +++ b/tests/ci/validators.test.js @@ -169,6 +169,8 @@ function runCatalogValidator(overrides = {}) { README_ZH_CN_PATH: path.join(repoRoot, 'README.zh-CN.md'), DOCS_ZH_CN_README_PATH: path.join(repoRoot, 'docs', 'zh-CN', 'README.md'), DOCS_ZH_CN_AGENTS_PATH: path.join(repoRoot, 'docs', 'zh-CN', 'AGENTS.md'), + PLUGIN_JSON_PATH: path.join(repoRoot, '.claude-plugin', 'plugin.json'), + MARKETPLACE_JSON_PATH: path.join(repoRoot, '.claude-plugin', 'marketplace.json'), ...overrides, }; @@ -225,6 +227,7 @@ function runSkillsValidator(testDir, argv = [], envOverrides = {}) { function writeCatalogFixture(testDir, options = {}) { const { readmeCounts = { agents: 1, skills: 1, commands: 1 }, + readmeProjectTreeAgents = readmeCounts.agents, readmeTableCounts = readmeCounts, readmeParityCounts = readmeCounts, readmeUnrelatedSkillsCount = 16, @@ -245,6 +248,8 @@ function writeCatalogFixture(testDir, options = {}) { 'skills/ — 1 个工作流技能和领域知识', 'commands/ — 1 个斜杠命令', ], + pluginCounts = { agents: 1, skills: 1, commands: 1 }, + marketplaceCounts = { agents: 1, skills: 1, commands: 1 }, } = options; const readmePath = path.join(testDir, 'README.md'); @@ -252,23 +257,36 @@ function writeCatalogFixture(testDir, options = {}) { const zhRootReadmePath = path.join(testDir, 'README.zh-CN.md'); const zhDocsReadmePath = path.join(testDir, 'docs', 'zh-CN', 'README.md'); const zhAgentsPath = path.join(testDir, 'docs', 'zh-CN', 'AGENTS.md'); + const pluginJsonPath = path.join(testDir, '.claude-plugin', 'plugin.json'); + const marketplaceJsonPath = path.join(testDir, '.claude-plugin', 'marketplace.json'); fs.mkdirSync(path.join(testDir, 'agents'), { recursive: true }); fs.mkdirSync(path.join(testDir, 'commands'), { recursive: true }); fs.mkdirSync(path.join(testDir, 'skills', 'demo-skill'), { recursive: true }); fs.mkdirSync(path.join(testDir, 'docs', 'zh-CN'), { recursive: true }); + fs.mkdirSync(path.join(testDir, '.claude-plugin'), { recursive: true }); fs.writeFileSync(path.join(testDir, 'agents', 'planner.md'), '---\nmodel: sonnet\ntools: Read\n---\n# Planner'); 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| 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|-- 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`); fs.writeFileSync(zhAgentsPath, `这是一个**生产就绪的 AI 编码插件**,提供 ${zhAgentsSummaryCounts.agents} 个专业代理、${zhAgentsSummaryCounts.skills} 项技能、${zhAgentsSummaryCounts.commands} 条命令以及自动化钩子工作流,用于软件开发。\n\n\`\`\`\n${zhAgentsStructureLines.join('\n')}\n\`\`\`\n`); + fs.writeFileSync(pluginJsonPath, JSON.stringify({ + name: 'ecc', + description: `Battle-tested plugin — ${pluginCounts.agents} agents, ${pluginCounts.skills} skills, ${pluginCounts.commands} legacy command shims`, + }, null, 2)); + fs.writeFileSync(marketplaceJsonPath, JSON.stringify({ + plugins: [{ + name: 'ecc', + description: `Marketplace plugin — ${marketplaceCounts.agents} agents, ${marketplaceCounts.skills} skills, ${marketplaceCounts.commands} legacy command shims`, + }], + }, null, 2)); - return { readmePath, agentsPath, zhRootReadmePath, zhDocsReadmePath, zhAgentsPath }; + return { readmePath, agentsPath, zhRootReadmePath, zhDocsReadmePath, zhAgentsPath, pluginJsonPath, marketplaceJsonPath }; } function runTests() { @@ -417,6 +435,8 @@ function runTests() { zhRootReadmePath, zhDocsReadmePath, zhAgentsPath, + pluginJsonPath, + marketplaceJsonPath, } = writeCatalogFixture(testDir, { readmeCounts: { agents: 99, skills: 99, commands: 99 }, readmeTableCounts: { agents: 99, skills: 99, commands: 99 }, @@ -446,6 +466,8 @@ function runTests() { README_ZH_CN_PATH: zhRootReadmePath, DOCS_ZH_CN_README_PATH: zhDocsReadmePath, DOCS_ZH_CN_AGENTS_PATH: zhAgentsPath, + PLUGIN_JSON_PATH: pluginJsonPath, + MARKETPLACE_JSON_PATH: marketplaceJsonPath, }); assert.strictEqual(result.code, 1, 'Should fail when catalog counts drift'); @@ -461,6 +483,8 @@ function runTests() { zhRootReadmePath, zhDocsReadmePath, zhAgentsPath, + pluginJsonPath, + marketplaceJsonPath, } = writeCatalogFixture(testDir, { readmeCounts: { agents: 1, skills: 1, commands: 1 }, readmeTableCounts: { agents: 1, skills: 1, commands: 1 }, @@ -475,6 +499,8 @@ function runTests() { README_ZH_CN_PATH: zhRootReadmePath, DOCS_ZH_CN_README_PATH: zhDocsReadmePath, DOCS_ZH_CN_AGENTS_PATH: zhAgentsPath, + PLUGIN_JSON_PATH: pluginJsonPath, + MARKETPLACE_JSON_PATH: marketplaceJsonPath, }); assert.strictEqual(result.code, 1, 'Should fail when README parity table drifts'); @@ -492,6 +518,8 @@ function runTests() { agentsPath, zhRootReadmePath, zhDocsReadmePath, + pluginJsonPath, + marketplaceJsonPath, } = writeCatalogFixture(testDir); const missingZhAgentsPath = path.join(testDir, 'docs', 'zh-CN', 'AGENTS.md'); fs.rmSync(missingZhAgentsPath); @@ -503,6 +531,8 @@ function runTests() { README_ZH_CN_PATH: zhRootReadmePath, DOCS_ZH_CN_README_PATH: zhDocsReadmePath, DOCS_ZH_CN_AGENTS_PATH: missingZhAgentsPath, + PLUGIN_JSON_PATH: pluginJsonPath, + MARKETPLACE_JSON_PATH: marketplaceJsonPath, }); assert.strictEqual(result.code, 1, 'Should fail when a tracked doc is missing'); @@ -521,6 +551,8 @@ function runTests() { zhRootReadmePath, zhDocsReadmePath, zhAgentsPath, + pluginJsonPath, + marketplaceJsonPath, } = writeCatalogFixture(testDir, { readmeCounts: { agents: 9, skills: 9, commands: 9 }, readmeTableCounts: { agents: 8, skills: 8, commands: 8 }, @@ -531,6 +563,8 @@ function runTests() { zhDocsTableCounts: { agents: 12, skills: 12, commands: 12 }, zhDocsParityCounts: { agents: 13, skills: 13, commands: 13 }, zhAgentsSummaryCounts: { agents: 14, skills: 14, commands: 14 }, + pluginCounts: { agents: 18, skills: 18, commands: 18 }, + marketplaceCounts: { agents: 19, skills: 19, commands: 19 }, zhAgentsStructureLines: [ 'agents/ — 15 个专业子代理', 'skills/ — 16 个工作流技能和领域知识', @@ -546,6 +580,8 @@ function runTests() { README_ZH_CN_PATH: zhRootReadmePath, DOCS_ZH_CN_README_PATH: zhDocsReadmePath, DOCS_ZH_CN_AGENTS_PATH: zhAgentsPath, + PLUGIN_JSON_PATH: pluginJsonPath, + MARKETPLACE_JSON_PATH: marketplaceJsonPath, }); assert.strictEqual(result.code, 0, `Should sync and pass, got stderr: ${result.stderr}`); @@ -555,8 +591,11 @@ function runTests() { const zhRootReadme = fs.readFileSync(zhRootReadmePath, 'utf8'); const zhDocsReadme = fs.readFileSync(zhDocsReadmePath, 'utf8'); const zhAgentsDoc = fs.readFileSync(zhAgentsPath, 'utf8'); + const pluginJson = fs.readFileSync(pluginJsonPath, 'utf8'); + 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('|-- 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'); assert.ok(readme.includes('| **Agents** | 1 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 |'), 'Should sync README parity table'); @@ -569,6 +608,8 @@ function runTests() { assert.ok(zhDocsReadme.includes('| **智能体** | 1 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |'), 'Should sync docs/zh-CN/README parity table'); assert.ok(zhAgentsDoc.includes('提供 1 个专业代理、1 项技能、1 条命令'), 'Should sync docs/zh-CN/AGENTS summary'); assert.ok(zhAgentsDoc.includes('commands/ — 1 个斜杠命令'), 'Should sync docs/zh-CN/AGENTS structure'); + assert.ok(pluginJson.includes('1 agents, 1 skills, 1 legacy command shims'), 'Should sync plugin manifest catalog description'); + assert.ok(marketplaceJson.includes('1 agents, 1 skills, 1 legacy command shims'), 'Should sync marketplace plugin catalog description'); cleanupTestDir(testDir); })) passed++; else failed++; @@ -581,6 +622,8 @@ function runTests() { zhRootReadmePath, zhDocsReadmePath, zhAgentsPath, + pluginJsonPath, + marketplaceJsonPath, } = writeCatalogFixture(testDir, { structureLines: [ ' agents/ - 1 specialized subagents ', @@ -601,6 +644,8 @@ function runTests() { README_ZH_CN_PATH: zhRootReadmePath, DOCS_ZH_CN_README_PATH: zhDocsReadmePath, DOCS_ZH_CN_AGENTS_PATH: zhAgentsPath, + PLUGIN_JSON_PATH: pluginJsonPath, + MARKETPLACE_JSON_PATH: marketplaceJsonPath, }); assert.strictEqual(result.code, 0, `Should accept formatting variations, got stderr: ${result.stderr}`);