fix(#4782): claude's agents kind skips compact variants — consumed only by the non-claude persona gate (#4878)
* test(#4782): failing-first — claude install must not stage compact agent variants (and must prune stale ones) * fix(#4782): claude's agents kind skips compact variants — they are consumed only by the non-claude persona-fallback gate Emitted-Drift-Ack-Hash: agents/gsd-advisor-researcher.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-ai-researcher.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-assumptions-analyzer.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-code-fixer.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-code-reviewer.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-codebase-mapper.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-debug-session-manager.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-doc-classifier.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-doc-synthesizer.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-doc-verifier.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-doc-writer.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-dom-verifier.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-domain-researcher.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-eval-auditor.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-eval-planner.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-framework-selector.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-integration-checker.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-intel-updater.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-mempalace-curator.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-nyquist-auditor.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-pattern-mapper.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-project-researcher.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-research-synthesizer.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-roadmapper.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-security-auditor.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-ui-auditor.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-ui-checker.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-ui-researcher.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies Emitted-Drift-Ack-Hash: agents/gsd-user-profiler.compact.md — #4782: claude never selects compact (the init agent-skills persona gate is non-claude-only), so the claude agents kind no longer stages name-shadowing copies * chore(#4782): changeset fragment * chore(#4782): backfill changeset PR number (4878) --------- Co-authored-by: sim <sim@local>
This commit is contained in:
5
.changeset/calm-ravens-wander.md
Normal file
5
.changeset/calm-ravens-wander.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 4878
|
||||
---
|
||||
**Claude installs no longer stage the 29 compact agent variants** — agents/*.compact.md shipped beside their canonical siblings with identical name frontmatter, leaving the harness's pick unstated; Claude never selects compact (it is a non-Claude-runtime payload), so the Claude agents directory now holds only the canonical agents, and upgrading removes the stale copies. (#4782)
|
||||
@@ -792,6 +792,15 @@ function _copyStaged(stagedDir: string, destDir: string, kind: any, configDir: s
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile()) continue;
|
||||
if (!entry.name.endsWith('.md')) continue;
|
||||
// #4782: compact agent variants are consumed ONLY through the non-claude
|
||||
// gate in init.cts's agent-skills persona fallback (`runtime !== 'claude'`
|
||||
// — claude's contract is a skills-injection path, never a persona
|
||||
// fallback). Staging them into Claude's agents directory shipped 29 dead
|
||||
// files whose `name:` frontmatter is identical to their canonical
|
||||
// sibling's, leaving the harness resolution unstated. Claude never
|
||||
// selects compact, so claude is the one runtime whose agents kind skips
|
||||
// them; every other runtime's emission is byte-identical.
|
||||
if (kind.kind === 'agents' && runtime === 'claude' && entry.name.endsWith('.compact.md')) continue;
|
||||
const stem = entry.name.slice(0, -3); // strip .md
|
||||
|
||||
let destName: string;
|
||||
|
||||
102
tests/compact-agent-staging.test.cjs
Normal file
102
tests/compact-agent-staging.test.cjs
Normal file
@@ -0,0 +1,102 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* #4782 — compact agent variants must not stage into Claude's agents directory.
|
||||
*
|
||||
* The compact/canonical choice is made in code at init.cts's agent-skills
|
||||
* persona fallback, gated `runtime !== 'claude'` (claude's contract is a
|
||||
* skills-injection path, not a persona fallback). Staging the 29
|
||||
* `agents/*.compact.md` files into `.claude/agents/` therefore shipped dead
|
||||
* files whose `name:` frontmatter is byte-identical to their canonical
|
||||
* sibling's — the harness has to pick one and nothing states which (#4553
|
||||
* family). Claude is the one runtime whose agents kind skips them; every
|
||||
* other runtime's emission is byte-identical, and the existing
|
||||
* `_removeGsdEntries` prune (every `gsd-*` entry) cleans previously staged
|
||||
* compact copies on upgrade.
|
||||
*/
|
||||
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { createTempDir, cleanup } = require('./helpers.cjs');
|
||||
const { runMinimalInstall } = require('./helpers/install-shared.cjs');
|
||||
|
||||
const SOURCE_AGENTS_DIR = path.join(__dirname, '..', 'agents');
|
||||
const listAgents = (agentsDir) => (fs.existsSync(agentsDir) ? fs.readdirSync(agentsDir) : []);
|
||||
const isCompact = (f) => f.endsWith('.compact.md');
|
||||
|
||||
describe('#4782 compact agent staging', () => {
|
||||
test('claude install stages no *.compact.md and leaves canonical agents intact', (t) => {
|
||||
const root = createTempDir('gsd-4782-clean-');
|
||||
t.after(() => cleanup(root));
|
||||
|
||||
const { configDir } = runMinimalInstall({ runtime: 'claude', scope: 'global', root });
|
||||
assert.strictEqual(configDir, path.join(root, '.claude'));
|
||||
|
||||
const staged = listAgents(path.join(configDir, 'agents'));
|
||||
const compact = staged.filter(isCompact);
|
||||
const canonical = staged.filter((f) => f.startsWith('gsd-') && f.endsWith('.md') && !isCompact(f));
|
||||
|
||||
assert.deepStrictEqual(
|
||||
compact,
|
||||
[],
|
||||
`claude agents dir must not contain compact variants; got: ${compact.join(', ')}`,
|
||||
);
|
||||
const sourceCanonical = fs.readdirSync(SOURCE_AGENTS_DIR)
|
||||
.filter((f) => f.startsWith('gsd-') && f.endsWith('.md') && !isCompact(f));
|
||||
assert.strictEqual(
|
||||
canonical.length,
|
||||
sourceCanonical.length,
|
||||
`canonical agent count must be unchanged (source: ${sourceCanonical.length})`,
|
||||
);
|
||||
});
|
||||
|
||||
test('claude install removes previously staged compact variants (upgrade over 1.14.0)', (t) => {
|
||||
const root = createTempDir('gsd-4782-upgrade-');
|
||||
t.after(() => cleanup(root));
|
||||
// Plant what a 1.14.0 install left behind: GSD-owned compact copies (one
|
||||
// real, one synthetic) next to the canonical agent.
|
||||
const agentsDir = path.join(root, '.claude', 'agents');
|
||||
fs.mkdirSync(agentsDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(agentsDir, 'gsd-doc-writer.compact.md'), '---\nname: gsd-doc-writer\n---\ncompact');
|
||||
fs.writeFileSync(path.join(agentsDir, 'gsd-zombie.compact.md'), '---\nname: gsd-zombie\n---\ncompact');
|
||||
|
||||
const { configDir } = runMinimalInstall({
|
||||
runtime: 'claude',
|
||||
scope: 'global',
|
||||
root,
|
||||
// The planted copies are not manifest-managed, so the first-time
|
||||
// baseline-scan migration demands an explicit operator choice for them
|
||||
// (fail-closed by design). Resolving "remove" IS the upgrade semantics
|
||||
// under test: the operator retires the stale compact copies.
|
||||
extraEnv: { GSD_INSTALLER_MIGRATION_RESOLVE: 'remove' },
|
||||
});
|
||||
|
||||
const staged = listAgents(path.join(configDir, 'agents'));
|
||||
assert.deepStrictEqual(
|
||||
staged.filter(isCompact),
|
||||
[],
|
||||
'the stale-agent prune must remove previously staged compact variants',
|
||||
);
|
||||
assert.ok(
|
||||
staged.includes('gsd-doc-writer.md'),
|
||||
'the canonical agent must still be staged',
|
||||
);
|
||||
});
|
||||
|
||||
test('non-claude agents kinds still stage compact variants (opencode parity)', (t) => {
|
||||
const root = createTempDir('gsd-4782-opencode-');
|
||||
t.after(() => cleanup(root));
|
||||
|
||||
const { configDir } = runMinimalInstall({ runtime: 'opencode', scope: 'global', root });
|
||||
|
||||
const compact = listAgents(path.join(configDir, 'agents')).filter(isCompact);
|
||||
const sourceCompact = fs.readdirSync(SOURCE_AGENTS_DIR).filter(isCompact);
|
||||
assert.strictEqual(
|
||||
compact.length,
|
||||
sourceCompact.length,
|
||||
`opencode must still stage every compact variant (source: ${sourceCompact.length})`,
|
||||
);
|
||||
});
|
||||
});
|
||||
29
tests/fixtures/install-tree/claude-local.json
vendored
29
tests/fixtures/install-tree/claude-local.json
vendored
@@ -1,67 +1,38 @@
|
||||
[
|
||||
".gsd-profile",
|
||||
"agents/gsd-advisor-researcher.compact.md",
|
||||
"agents/gsd-advisor-researcher.md",
|
||||
"agents/gsd-ai-researcher.compact.md",
|
||||
"agents/gsd-ai-researcher.md",
|
||||
"agents/gsd-assumptions-analyzer.compact.md",
|
||||
"agents/gsd-assumptions-analyzer.md",
|
||||
"agents/gsd-code-fixer.compact.md",
|
||||
"agents/gsd-code-fixer.md",
|
||||
"agents/gsd-code-reviewer.compact.md",
|
||||
"agents/gsd-code-reviewer.md",
|
||||
"agents/gsd-codebase-mapper.compact.md",
|
||||
"agents/gsd-codebase-mapper.md",
|
||||
"agents/gsd-debug-session-manager.compact.md",
|
||||
"agents/gsd-debug-session-manager.md",
|
||||
"agents/gsd-debugger.md",
|
||||
"agents/gsd-doc-classifier.compact.md",
|
||||
"agents/gsd-doc-classifier.md",
|
||||
"agents/gsd-doc-synthesizer.compact.md",
|
||||
"agents/gsd-doc-synthesizer.md",
|
||||
"agents/gsd-doc-verifier.compact.md",
|
||||
"agents/gsd-doc-verifier.md",
|
||||
"agents/gsd-doc-writer.compact.md",
|
||||
"agents/gsd-doc-writer.md",
|
||||
"agents/gsd-dom-verifier.compact.md",
|
||||
"agents/gsd-dom-verifier.md",
|
||||
"agents/gsd-domain-researcher.compact.md",
|
||||
"agents/gsd-domain-researcher.md",
|
||||
"agents/gsd-eval-auditor.compact.md",
|
||||
"agents/gsd-eval-auditor.md",
|
||||
"agents/gsd-eval-planner.compact.md",
|
||||
"agents/gsd-eval-planner.md",
|
||||
"agents/gsd-executor.md",
|
||||
"agents/gsd-framework-selector.compact.md",
|
||||
"agents/gsd-framework-selector.md",
|
||||
"agents/gsd-integration-checker.compact.md",
|
||||
"agents/gsd-integration-checker.md",
|
||||
"agents/gsd-intel-updater.compact.md",
|
||||
"agents/gsd-intel-updater.md",
|
||||
"agents/gsd-mempalace-curator.compact.md",
|
||||
"agents/gsd-mempalace-curator.md",
|
||||
"agents/gsd-nyquist-auditor.compact.md",
|
||||
"agents/gsd-nyquist-auditor.md",
|
||||
"agents/gsd-pattern-mapper.compact.md",
|
||||
"agents/gsd-pattern-mapper.md",
|
||||
"agents/gsd-phase-researcher.md",
|
||||
"agents/gsd-plan-checker.md",
|
||||
"agents/gsd-planner.md",
|
||||
"agents/gsd-project-researcher.compact.md",
|
||||
"agents/gsd-project-researcher.md",
|
||||
"agents/gsd-research-synthesizer.compact.md",
|
||||
"agents/gsd-research-synthesizer.md",
|
||||
"agents/gsd-roadmapper.compact.md",
|
||||
"agents/gsd-roadmapper.md",
|
||||
"agents/gsd-security-auditor.compact.md",
|
||||
"agents/gsd-security-auditor.md",
|
||||
"agents/gsd-ui-auditor.compact.md",
|
||||
"agents/gsd-ui-auditor.md",
|
||||
"agents/gsd-ui-checker.compact.md",
|
||||
"agents/gsd-ui-checker.md",
|
||||
"agents/gsd-ui-researcher.compact.md",
|
||||
"agents/gsd-ui-researcher.md",
|
||||
"agents/gsd-user-profiler.compact.md",
|
||||
"agents/gsd-user-profiler.md",
|
||||
"agents/gsd-verifier.md",
|
||||
"commands/gsd-add-tests.md",
|
||||
|
||||
29
tests/fixtures/install-tree/claude.json
vendored
29
tests/fixtures/install-tree/claude.json
vendored
@@ -1,67 +1,38 @@
|
||||
[
|
||||
".gsd-profile",
|
||||
"agents/gsd-advisor-researcher.compact.md",
|
||||
"agents/gsd-advisor-researcher.md",
|
||||
"agents/gsd-ai-researcher.compact.md",
|
||||
"agents/gsd-ai-researcher.md",
|
||||
"agents/gsd-assumptions-analyzer.compact.md",
|
||||
"agents/gsd-assumptions-analyzer.md",
|
||||
"agents/gsd-code-fixer.compact.md",
|
||||
"agents/gsd-code-fixer.md",
|
||||
"agents/gsd-code-reviewer.compact.md",
|
||||
"agents/gsd-code-reviewer.md",
|
||||
"agents/gsd-codebase-mapper.compact.md",
|
||||
"agents/gsd-codebase-mapper.md",
|
||||
"agents/gsd-debug-session-manager.compact.md",
|
||||
"agents/gsd-debug-session-manager.md",
|
||||
"agents/gsd-debugger.md",
|
||||
"agents/gsd-doc-classifier.compact.md",
|
||||
"agents/gsd-doc-classifier.md",
|
||||
"agents/gsd-doc-synthesizer.compact.md",
|
||||
"agents/gsd-doc-synthesizer.md",
|
||||
"agents/gsd-doc-verifier.compact.md",
|
||||
"agents/gsd-doc-verifier.md",
|
||||
"agents/gsd-doc-writer.compact.md",
|
||||
"agents/gsd-doc-writer.md",
|
||||
"agents/gsd-dom-verifier.compact.md",
|
||||
"agents/gsd-dom-verifier.md",
|
||||
"agents/gsd-domain-researcher.compact.md",
|
||||
"agents/gsd-domain-researcher.md",
|
||||
"agents/gsd-eval-auditor.compact.md",
|
||||
"agents/gsd-eval-auditor.md",
|
||||
"agents/gsd-eval-planner.compact.md",
|
||||
"agents/gsd-eval-planner.md",
|
||||
"agents/gsd-executor.md",
|
||||
"agents/gsd-framework-selector.compact.md",
|
||||
"agents/gsd-framework-selector.md",
|
||||
"agents/gsd-integration-checker.compact.md",
|
||||
"agents/gsd-integration-checker.md",
|
||||
"agents/gsd-intel-updater.compact.md",
|
||||
"agents/gsd-intel-updater.md",
|
||||
"agents/gsd-mempalace-curator.compact.md",
|
||||
"agents/gsd-mempalace-curator.md",
|
||||
"agents/gsd-nyquist-auditor.compact.md",
|
||||
"agents/gsd-nyquist-auditor.md",
|
||||
"agents/gsd-pattern-mapper.compact.md",
|
||||
"agents/gsd-pattern-mapper.md",
|
||||
"agents/gsd-phase-researcher.md",
|
||||
"agents/gsd-plan-checker.md",
|
||||
"agents/gsd-planner.md",
|
||||
"agents/gsd-project-researcher.compact.md",
|
||||
"agents/gsd-project-researcher.md",
|
||||
"agents/gsd-research-synthesizer.compact.md",
|
||||
"agents/gsd-research-synthesizer.md",
|
||||
"agents/gsd-roadmapper.compact.md",
|
||||
"agents/gsd-roadmapper.md",
|
||||
"agents/gsd-security-auditor.compact.md",
|
||||
"agents/gsd-security-auditor.md",
|
||||
"agents/gsd-ui-auditor.compact.md",
|
||||
"agents/gsd-ui-auditor.md",
|
||||
"agents/gsd-ui-checker.compact.md",
|
||||
"agents/gsd-ui-checker.md",
|
||||
"agents/gsd-ui-researcher.compact.md",
|
||||
"agents/gsd-ui-researcher.md",
|
||||
"agents/gsd-user-profiler.compact.md",
|
||||
"agents/gsd-user-profiler.md",
|
||||
"agents/gsd-verifier.md",
|
||||
"gsd-core/.gsd-runtime",
|
||||
|
||||
Reference in New Issue
Block a user