fix(acr): enumerateAgents counts only CC-registered agents — recurse + frontmatter filter + HOME dedup (M-BUG-3/4/5)
enumerateAgents previously counted every .md in an agents dir as an always-loaded agent. Per the official CC subagents docs, CC registers a subagent only when its frontmatter declares both name and description, scans agents dirs recursively, and (at a HOME self-scan) must not count ~/.claude/agents twice. - M-BUG-5: require valid name+description frontmatter; frontmatter-less files are registration no-ops costing 0 always-loaded tokens. Fixes the user-agent over-count (this machine: 13 -> 0). - M-BUG-3: listMarkdownFiles gains opt-in recursion; enumerateAgents recurses so agents in subfolders (agents/review/x.md) are counted, matching CC. - M-BUG-4: configDirs dedupes project==user paths, so a `manifest --global` self-scan (repoPath===$HOME) counts ~/.claude once (user scope), killing the spurious "project 13" double-count. Benefits rules/agents/output-styles. TDD: 4 failing tests -> green. Full suite 1305/0; --json/--raw byte-stable, frozen v5.0.0 + SC-5 + default-output snapshots untouched (no snapshot records agent enumeration rows). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EnUvKEqyEa1m9gy6Aqhdqq
This commit is contained in:
parent
a1e786ba4f
commit
7f097d524f
2 changed files with 97 additions and 5 deletions
|
|
@ -952,6 +952,70 @@ describe('enumerateAgents (v5.6)', () => {
|
|||
await mkdir(root, { recursive: true });
|
||||
assert.deepEqual(await enumerateAgents(root, []), []);
|
||||
});
|
||||
|
||||
// M-BUG-5: CC registers a subagent only when its frontmatter declares both
|
||||
// `name` and `description` (docs: identity comes only from `name`; both are
|
||||
// required). Frontmatter-less / incomplete files are registration no-ops that
|
||||
// cost zero always-loaded tokens — they must NOT be counted as agents.
|
||||
it('M-BUG-5: skips files with no frontmatter at all', async () => {
|
||||
const dir = join(root, '.claude', 'agents');
|
||||
await mkdir(dir, { recursive: true });
|
||||
await writeFile(join(dir, 'reviewer.md'), '---\nname: reviewer\ndescription: reviews code\n---\nbody\n');
|
||||
await writeFile(join(dir, 'not-an-agent.md'), '# Not An Agent\n\nJust a markdown doc, no frontmatter.\n');
|
||||
const agents = await enumerateAgents(root, []);
|
||||
assert.deepEqual(agents.map(a => a.name).sort(), ['reviewer']);
|
||||
});
|
||||
|
||||
it('M-BUG-5: skips files missing name or description', async () => {
|
||||
const dir = join(root, '.claude', 'agents');
|
||||
await mkdir(dir, { recursive: true });
|
||||
await writeFile(join(dir, 'reviewer.md'), '---\nname: reviewer\ndescription: reviews code\n---\nbody\n');
|
||||
await writeFile(join(dir, 'name-only.md'), '---\nname: name-only\n---\nbody\n');
|
||||
await writeFile(join(dir, 'desc-only.md'), '---\ndescription: has no name\n---\nbody\n');
|
||||
await writeFile(join(dir, 'empty-name.md'), '---\nname:\ndescription: blank name\n---\nbody\n');
|
||||
const agents = await enumerateAgents(root, []);
|
||||
assert.deepEqual(agents.map(a => a.name).sort(), ['reviewer']);
|
||||
});
|
||||
|
||||
// M-BUG-3: CC scans agents dirs recursively, so a valid agent in a subfolder
|
||||
// (e.g. agents/review/security.md) is registered and must be counted.
|
||||
it('M-BUG-3: recurses into agent subdirectories', async () => {
|
||||
const sub = join(root, '.claude', 'agents', 'review');
|
||||
await mkdir(sub, { recursive: true });
|
||||
await writeFile(join(sub, 'security.md'), '---\nname: security\ndescription: security review\n---\nbody\n');
|
||||
const agents = await enumerateAgents(root, []);
|
||||
const a = agents.find(x => x.name === 'security');
|
||||
assert.ok(a, 'agent in subdir should be counted');
|
||||
assert.equal(a.source, 'project');
|
||||
});
|
||||
});
|
||||
|
||||
// M-BUG-4: when repoPath === $HOME (the `manifest --global` self-scan), the
|
||||
// project agents dir resolves to the SAME path as the user agents dir. The
|
||||
// shared configDirs helper must count it once (as user scope), not twice.
|
||||
describe('enumerateAgents — HOME self-scan dedup (M-BUG-4)', () => {
|
||||
let home, originalHome;
|
||||
beforeEach(async () => {
|
||||
home = uniqueDir('agents-home-selfscan');
|
||||
await mkdir(join(home, '.claude', 'agents'), { recursive: true });
|
||||
originalHome = process.env.HOME;
|
||||
process.env.HOME = home;
|
||||
});
|
||||
afterEach(async () => {
|
||||
process.env.HOME = originalHome;
|
||||
await rm(home, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('does not double-count user agents when repoPath === HOME', async () => {
|
||||
await writeFile(
|
||||
join(home, '.claude', 'agents', 'solo.md'),
|
||||
'---\nname: solo\ndescription: only one of me\n---\nbody\n',
|
||||
);
|
||||
const agents = await enumerateAgents(home, []);
|
||||
const solos = agents.filter(a => a.name === 'solo');
|
||||
assert.equal(solos.length, 1, 'agent at HOME must be counted once, not twice');
|
||||
assert.equal(solos[0].source, 'user', 'HOME self-scan agents are user scope');
|
||||
});
|
||||
});
|
||||
|
||||
describe('enumerateOutputStyles (v5.6)', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue