From d435723c951b15548174ec20300e23ee4ad8d307 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sat, 19 Sep 2026 17:25:21 -0400 Subject: [PATCH] =?UTF-8?q?fix(#4782):=20claude's=20agents=20kind=20skips?= =?UTF-8?q?=20compact=20variants=20=E2=80=94=20consumed=20only=20by=20the?= =?UTF-8?q?=20non-claude=20persona=20gate=20(#4878)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- .changeset/calm-ravens-wander.md | 5 + src/install-engine.cts | 9 ++ tests/compact-agent-staging.test.cjs | 102 ++++++++++++++++++ tests/fixtures/install-tree/claude-local.json | 29 ----- tests/fixtures/install-tree/claude.json | 29 ----- 5 files changed, 116 insertions(+), 58 deletions(-) create mode 100644 .changeset/calm-ravens-wander.md create mode 100644 tests/compact-agent-staging.test.cjs diff --git a/.changeset/calm-ravens-wander.md b/.changeset/calm-ravens-wander.md new file mode 100644 index 000000000..43c73c8e8 --- /dev/null +++ b/.changeset/calm-ravens-wander.md @@ -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) diff --git a/src/install-engine.cts b/src/install-engine.cts index 058da129e..9a9324142 100644 --- a/src/install-engine.cts +++ b/src/install-engine.cts @@ -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; diff --git a/tests/compact-agent-staging.test.cjs b/tests/compact-agent-staging.test.cjs new file mode 100644 index 000000000..3aae8fdc6 --- /dev/null +++ b/tests/compact-agent-staging.test.cjs @@ -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})`, + ); + }); +}); diff --git a/tests/fixtures/install-tree/claude-local.json b/tests/fixtures/install-tree/claude-local.json index ed43854d1..34471e910 100644 --- a/tests/fixtures/install-tree/claude-local.json +++ b/tests/fixtures/install-tree/claude-local.json @@ -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", diff --git a/tests/fixtures/install-tree/claude.json b/tests/fixtures/install-tree/claude.json index a14d356db..39c6d8781 100644 --- a/tests/fixtures/install-tree/claude.json +++ b/tests/fixtures/install-tree/claude.json @@ -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",