* test(#3547): failing-first regression for collapsed global install shape * fix(#3547): exercise the real global config-home shape in the install harness * test(#3547): align ripple suites with the real global install shape * test(#3547): update stale collapsed-shape pins in provenance and migration suites * fix(#3547): bump emitted-baseline schema version for the real install shape --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -323,9 +323,10 @@ test('spot-check: cline rules are code-derived (attributable), not exempt', () =
|
||||
assert.equal(got.kind, 'code-derived', `${rel} must stay attributable`);
|
||||
assert.deepEqual(got.sources, [CLINE_BODY_SRC]);
|
||||
}
|
||||
const agentsMd = attributeEmittedPath('.agents/AGENTS.md', 'cline');
|
||||
assert.equal(agentsMd.kind, 'code-derived');
|
||||
assert.deepEqual(agentsMd.sources, [CLINE_BODY_SRC]);
|
||||
// #3547 dropped the `.agents/AGENTS.md` spot-check: that path only entered a
|
||||
// manifest while the harness's collapsed --config-dir walked cline's
|
||||
// ~/.agents sibling; with the real `.cline` config home it can never occur,
|
||||
// and the rule was removed by the dead-rule arm.
|
||||
});
|
||||
|
||||
test('spot-check: install-time state is exempt with an empty source list', () => {
|
||||
@@ -334,7 +335,6 @@ test('spot-check: install-time state is exempt with an empty source list', () =>
|
||||
['gsd-core/VERSION', 'claude'],
|
||||
['gsd-core/.gsd-runtime', 'claude'],
|
||||
['package.json', 'opencode'],
|
||||
['.gsd/defaults.json', 'opencode'],
|
||||
['opencode.json', 'opencode'],
|
||||
]) {
|
||||
const got = attributeEmittedPath(rel, rt);
|
||||
@@ -620,7 +620,9 @@ test('emitted paths that could traverse out of the repo are rejected', () => {
|
||||
assert.throws(() => attributeEmittedPath('', 'claude'), /non-empty string/);
|
||||
|
||||
// A dot-prefixed segment is NOT traversal — this must still resolve normally.
|
||||
assert.doesNotThrow(() => attributeEmittedPath('.gsd/defaults.json', 'opencode'));
|
||||
// #3547: '.gsd/defaults.json' left the manifests with the collapsed shape; a
|
||||
// dot-prefixed STILL-EMITTED path serves the same not-traversal example.
|
||||
assert.doesNotThrow(() => attributeEmittedPath('.gsd-profile', 'opencode'));
|
||||
});
|
||||
|
||||
test('sampleLimit truncation is exact at limit-1 / limit / limit+1', () => {
|
||||
|
||||
1
tests/fixtures/install-tree/antigravity.json
vendored
1
tests/fixtures/install-tree/antigravity.json
vendored
@@ -1,6 +1,5 @@
|
||||
[
|
||||
".gsd-profile",
|
||||
".gsd/defaults.json",
|
||||
"agents/gsd-advisor-researcher.md",
|
||||
"agents/gsd-ai-researcher.md",
|
||||
"agents/gsd-assumptions-analyzer.md",
|
||||
|
||||
1
tests/fixtures/install-tree/augment.json
vendored
1
tests/fixtures/install-tree/augment.json
vendored
@@ -1,6 +1,5 @@
|
||||
[
|
||||
".gsd-profile",
|
||||
".gsd/defaults.json",
|
||||
"agents/gsd-advisor-researcher.md",
|
||||
"agents/gsd-ai-researcher.md",
|
||||
"agents/gsd-assumptions-analyzer.md",
|
||||
|
||||
2
tests/fixtures/install-tree/cline.json
vendored
2
tests/fixtures/install-tree/cline.json
vendored
@@ -1,9 +1,7 @@
|
||||
[
|
||||
".agents/AGENTS.md",
|
||||
".clinerules/gsd.md",
|
||||
".clinerules/hooks/PreToolUse",
|
||||
".gsd-profile",
|
||||
".gsd/defaults.json",
|
||||
"agents/gsd-advisor-researcher.md",
|
||||
"agents/gsd-ai-researcher.md",
|
||||
"agents/gsd-assumptions-analyzer.md",
|
||||
|
||||
1
tests/fixtures/install-tree/codebuddy.json
vendored
1
tests/fixtures/install-tree/codebuddy.json
vendored
@@ -1,6 +1,5 @@
|
||||
[
|
||||
".gsd-profile",
|
||||
".gsd/defaults.json",
|
||||
"agents/gsd-advisor-researcher.md",
|
||||
"agents/gsd-ai-researcher.md",
|
||||
"agents/gsd-assumptions-analyzer.md",
|
||||
|
||||
72
tests/fixtures/install-tree/codex.json
vendored
72
tests/fixtures/install-tree/codex.json
vendored
@@ -1,77 +1,5 @@
|
||||
[
|
||||
".agents/skills/gsd-add-tests/SKILL.md",
|
||||
".agents/skills/gsd-ai-integration-phase/SKILL.md",
|
||||
".agents/skills/gsd-audit-fix/SKILL.md",
|
||||
".agents/skills/gsd-audit-milestone/SKILL.md",
|
||||
".agents/skills/gsd-audit-uat/SKILL.md",
|
||||
".agents/skills/gsd-autonomous/SKILL.md",
|
||||
".agents/skills/gsd-capture/SKILL.md",
|
||||
".agents/skills/gsd-cleanup/SKILL.md",
|
||||
".agents/skills/gsd-code-review/SKILL.md",
|
||||
".agents/skills/gsd-complete-milestone/SKILL.md",
|
||||
".agents/skills/gsd-config/SKILL.md",
|
||||
".agents/skills/gsd-debug/SKILL.md",
|
||||
".agents/skills/gsd-discuss-phase/SKILL.md",
|
||||
".agents/skills/gsd-docs-update/SKILL.md",
|
||||
".agents/skills/gsd-eval-review/SKILL.md",
|
||||
".agents/skills/gsd-execute-phase/SKILL.md",
|
||||
".agents/skills/gsd-explore/SKILL.md",
|
||||
".agents/skills/gsd-extract-learnings/SKILL.md",
|
||||
".agents/skills/gsd-fast/SKILL.md",
|
||||
".agents/skills/gsd-forensics/SKILL.md",
|
||||
".agents/skills/gsd-graphify/SKILL.md",
|
||||
".agents/skills/gsd-health/SKILL.md",
|
||||
".agents/skills/gsd-help/SKILL.md",
|
||||
".agents/skills/gsd-import/SKILL.md",
|
||||
".agents/skills/gsd-inbox/SKILL.md",
|
||||
".agents/skills/gsd-ingest-docs/SKILL.md",
|
||||
".agents/skills/gsd-manager/SKILL.md",
|
||||
".agents/skills/gsd-map-codebase/SKILL.md",
|
||||
".agents/skills/gsd-mempalace-capture/SKILL.md",
|
||||
".agents/skills/gsd-mempalace-recall/SKILL.md",
|
||||
".agents/skills/gsd-milestone-summary/SKILL.md",
|
||||
".agents/skills/gsd-mvp-phase/SKILL.md",
|
||||
".agents/skills/gsd-new-milestone/SKILL.md",
|
||||
".agents/skills/gsd-new-project/SKILL.md",
|
||||
".agents/skills/gsd-next/SKILL.md",
|
||||
".agents/skills/gsd-ns-context/SKILL.md",
|
||||
".agents/skills/gsd-ns-ideate/SKILL.md",
|
||||
".agents/skills/gsd-ns-manage/SKILL.md",
|
||||
".agents/skills/gsd-ns-project/SKILL.md",
|
||||
".agents/skills/gsd-ns-review/SKILL.md",
|
||||
".agents/skills/gsd-ns-workflow/SKILL.md",
|
||||
".agents/skills/gsd-onboard/SKILL.md",
|
||||
".agents/skills/gsd-pause-work/SKILL.md",
|
||||
".agents/skills/gsd-phase/SKILL.md",
|
||||
".agents/skills/gsd-plan-phase/SKILL.md",
|
||||
".agents/skills/gsd-plan-review-convergence/SKILL.md",
|
||||
".agents/skills/gsd-pr-branch/SKILL.md",
|
||||
".agents/skills/gsd-profile-user/SKILL.md",
|
||||
".agents/skills/gsd-progress/SKILL.md",
|
||||
".agents/skills/gsd-quick/SKILL.md",
|
||||
".agents/skills/gsd-resume-work/SKILL.md",
|
||||
".agents/skills/gsd-review-backlog/SKILL.md",
|
||||
".agents/skills/gsd-review/SKILL.md",
|
||||
".agents/skills/gsd-secure-phase/SKILL.md",
|
||||
".agents/skills/gsd-settings/SKILL.md",
|
||||
".agents/skills/gsd-ship/SKILL.md",
|
||||
".agents/skills/gsd-sketch/SKILL.md",
|
||||
".agents/skills/gsd-spec-phase/SKILL.md",
|
||||
".agents/skills/gsd-spike/SKILL.md",
|
||||
".agents/skills/gsd-stats/SKILL.md",
|
||||
".agents/skills/gsd-surface/SKILL.md",
|
||||
".agents/skills/gsd-thread/SKILL.md",
|
||||
".agents/skills/gsd-ui-phase/SKILL.md",
|
||||
".agents/skills/gsd-ui-review/SKILL.md",
|
||||
".agents/skills/gsd-ultraplan-phase/SKILL.md",
|
||||
".agents/skills/gsd-undo/SKILL.md",
|
||||
".agents/skills/gsd-update/SKILL.md",
|
||||
".agents/skills/gsd-validate-phase/SKILL.md",
|
||||
".agents/skills/gsd-verify-work/SKILL.md",
|
||||
".agents/skills/gsd-workspace/SKILL.md",
|
||||
".agents/skills/gsd-workstreams/SKILL.md",
|
||||
".gsd-profile",
|
||||
".gsd/defaults.json",
|
||||
"agents/gsd-advisor-researcher.md",
|
||||
"agents/gsd-advisor-researcher.toml",
|
||||
"agents/gsd-ai-researcher.md",
|
||||
|
||||
1
tests/fixtures/install-tree/copilot.json
vendored
1
tests/fixtures/install-tree/copilot.json
vendored
@@ -1,6 +1,5 @@
|
||||
[
|
||||
".gsd-profile",
|
||||
".gsd/defaults.json",
|
||||
"agents/gsd-advisor-researcher.agent.md",
|
||||
"agents/gsd-ai-researcher.agent.md",
|
||||
"agents/gsd-assumptions-analyzer.agent.md",
|
||||
|
||||
1
tests/fixtures/install-tree/cursor.json
vendored
1
tests/fixtures/install-tree/cursor.json
vendored
@@ -1,6 +1,5 @@
|
||||
[
|
||||
".gsd-profile",
|
||||
".gsd/defaults.json",
|
||||
"agents/gsd-advisor-researcher.md",
|
||||
"agents/gsd-ai-researcher.md",
|
||||
"agents/gsd-assumptions-analyzer.md",
|
||||
|
||||
1
tests/fixtures/install-tree/hermes.json
vendored
1
tests/fixtures/install-tree/hermes.json
vendored
@@ -1,6 +1,5 @@
|
||||
[
|
||||
".gsd-profile",
|
||||
".gsd/defaults.json",
|
||||
"agents/gsd-advisor-researcher.md",
|
||||
"agents/gsd-ai-researcher.md",
|
||||
"agents/gsd-assumptions-analyzer.md",
|
||||
|
||||
1
tests/fixtures/install-tree/kilo.json
vendored
1
tests/fixtures/install-tree/kilo.json
vendored
@@ -1,6 +1,5 @@
|
||||
[
|
||||
".gsd-profile",
|
||||
".gsd/defaults.json",
|
||||
"agents/gsd-advisor-researcher.md",
|
||||
"agents/gsd-ai-researcher.md",
|
||||
"agents/gsd-assumptions-analyzer.md",
|
||||
|
||||
68
tests/fixtures/install-tree/kimi-code.json
vendored
68
tests/fixtures/install-tree/kimi-code.json
vendored
@@ -1,39 +1,5 @@
|
||||
[
|
||||
".gsd-profile",
|
||||
".gsd/defaults.json",
|
||||
".kimi-code/hooks/gsd-agent-isolation-guard.js",
|
||||
".kimi-code/hooks/gsd-check-update-worker.js",
|
||||
".kimi-code/hooks/gsd-check-update.js",
|
||||
".kimi-code/hooks/gsd-config-reload.js",
|
||||
".kimi-code/hooks/gsd-context-monitor.js",
|
||||
".kimi-code/hooks/gsd-cursor-post-tool.js",
|
||||
".kimi-code/hooks/gsd-cursor-pre-tool.js",
|
||||
".kimi-code/hooks/gsd-cursor-session-start.js",
|
||||
".kimi-code/hooks/gsd-cursor-stop.js",
|
||||
".kimi-code/hooks/gsd-cursor-subagent-start.js",
|
||||
".kimi-code/hooks/gsd-cursor-subagent-stop.js",
|
||||
".kimi-code/hooks/gsd-ensure-canonical-path.js",
|
||||
".kimi-code/hooks/gsd-graphify-update.sh",
|
||||
".kimi-code/hooks/gsd-phase-boundary.sh",
|
||||
".kimi-code/hooks/gsd-prompt-guard.js",
|
||||
".kimi-code/hooks/gsd-read-guard.js",
|
||||
".kimi-code/hooks/gsd-read-injection-scanner.js",
|
||||
".kimi-code/hooks/gsd-session-state.sh",
|
||||
".kimi-code/hooks/gsd-statusline.js",
|
||||
".kimi-code/hooks/gsd-update-banner.js",
|
||||
".kimi-code/hooks/gsd-validate-commit.sh",
|
||||
".kimi-code/hooks/gsd-windsurf-pre-command.js",
|
||||
".kimi-code/hooks/gsd-windsurf-pre-write.js",
|
||||
".kimi-code/hooks/gsd-workflow-guard.js",
|
||||
".kimi-code/hooks/gsd-worktree-path-guard.js",
|
||||
".kimi-code/hooks/gsd-write-guard.js",
|
||||
".kimi-code/hooks/lib/cursor-workspace.js",
|
||||
".kimi-code/hooks/lib/git-cmd.js",
|
||||
".kimi-code/hooks/lib/gsd-graphify-rebuild.sh",
|
||||
".kimi-code/hooks/lib/injection-patterns.js",
|
||||
".kimi-code/hooks/lib/isolation-sentinel.js",
|
||||
".kimi-code/hooks/managed-hooks-registry.cjs",
|
||||
".kimi-code/hooks/package.json",
|
||||
"agents/gsd-advisor-researcher.md",
|
||||
"agents/gsd-ai-researcher.md",
|
||||
"agents/gsd-assumptions-analyzer.md",
|
||||
@@ -68,6 +34,7 @@
|
||||
"agents/gsd-ui-researcher.md",
|
||||
"agents/gsd-user-profiler.md",
|
||||
"agents/gsd-verifier.md",
|
||||
"config.toml",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
@@ -402,6 +369,39 @@
|
||||
"gsd-core/workflows/verify-work.md",
|
||||
"gsd-core/workflows/verify-work/steps/automated-ui-verification.md",
|
||||
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md",
|
||||
"hooks/gsd-agent-isolation-guard.js",
|
||||
"hooks/gsd-check-update-worker.js",
|
||||
"hooks/gsd-check-update.js",
|
||||
"hooks/gsd-config-reload.js",
|
||||
"hooks/gsd-context-monitor.js",
|
||||
"hooks/gsd-cursor-post-tool.js",
|
||||
"hooks/gsd-cursor-pre-tool.js",
|
||||
"hooks/gsd-cursor-session-start.js",
|
||||
"hooks/gsd-cursor-stop.js",
|
||||
"hooks/gsd-cursor-subagent-start.js",
|
||||
"hooks/gsd-cursor-subagent-stop.js",
|
||||
"hooks/gsd-ensure-canonical-path.js",
|
||||
"hooks/gsd-graphify-update.sh",
|
||||
"hooks/gsd-phase-boundary.sh",
|
||||
"hooks/gsd-prompt-guard.js",
|
||||
"hooks/gsd-read-guard.js",
|
||||
"hooks/gsd-read-injection-scanner.js",
|
||||
"hooks/gsd-session-state.sh",
|
||||
"hooks/gsd-statusline.js",
|
||||
"hooks/gsd-update-banner.js",
|
||||
"hooks/gsd-validate-commit.sh",
|
||||
"hooks/gsd-windsurf-pre-command.js",
|
||||
"hooks/gsd-windsurf-pre-write.js",
|
||||
"hooks/gsd-workflow-guard.js",
|
||||
"hooks/gsd-worktree-path-guard.js",
|
||||
"hooks/gsd-write-guard.js",
|
||||
"hooks/lib/cursor-workspace.js",
|
||||
"hooks/lib/git-cmd.js",
|
||||
"hooks/lib/gsd-graphify-rebuild.sh",
|
||||
"hooks/lib/injection-patterns.js",
|
||||
"hooks/lib/isolation-sentinel.js",
|
||||
"hooks/managed-hooks-registry.cjs",
|
||||
"hooks/package.json",
|
||||
"scripts/changeset/README.md",
|
||||
"scripts/changeset/cli.cjs",
|
||||
"scripts/changeset/github-release-notes.cjs",
|
||||
|
||||
34
tests/fixtures/install-tree/kimi.json
vendored
34
tests/fixtures/install-tree/kimi.json
vendored
@@ -1,39 +1,5 @@
|
||||
[
|
||||
".gsd-profile",
|
||||
".gsd/defaults.json",
|
||||
".kimi/hooks/gsd-agent-isolation-guard.js",
|
||||
".kimi/hooks/gsd-check-update-worker.js",
|
||||
".kimi/hooks/gsd-check-update.js",
|
||||
".kimi/hooks/gsd-config-reload.js",
|
||||
".kimi/hooks/gsd-context-monitor.js",
|
||||
".kimi/hooks/gsd-cursor-post-tool.js",
|
||||
".kimi/hooks/gsd-cursor-pre-tool.js",
|
||||
".kimi/hooks/gsd-cursor-session-start.js",
|
||||
".kimi/hooks/gsd-cursor-stop.js",
|
||||
".kimi/hooks/gsd-cursor-subagent-start.js",
|
||||
".kimi/hooks/gsd-cursor-subagent-stop.js",
|
||||
".kimi/hooks/gsd-ensure-canonical-path.js",
|
||||
".kimi/hooks/gsd-graphify-update.sh",
|
||||
".kimi/hooks/gsd-phase-boundary.sh",
|
||||
".kimi/hooks/gsd-prompt-guard.js",
|
||||
".kimi/hooks/gsd-read-guard.js",
|
||||
".kimi/hooks/gsd-read-injection-scanner.js",
|
||||
".kimi/hooks/gsd-session-state.sh",
|
||||
".kimi/hooks/gsd-statusline.js",
|
||||
".kimi/hooks/gsd-update-banner.js",
|
||||
".kimi/hooks/gsd-validate-commit.sh",
|
||||
".kimi/hooks/gsd-windsurf-pre-command.js",
|
||||
".kimi/hooks/gsd-windsurf-pre-write.js",
|
||||
".kimi/hooks/gsd-workflow-guard.js",
|
||||
".kimi/hooks/gsd-worktree-path-guard.js",
|
||||
".kimi/hooks/gsd-write-guard.js",
|
||||
".kimi/hooks/lib/cursor-workspace.js",
|
||||
".kimi/hooks/lib/git-cmd.js",
|
||||
".kimi/hooks/lib/gsd-graphify-rebuild.sh",
|
||||
".kimi/hooks/lib/injection-patterns.js",
|
||||
".kimi/hooks/lib/isolation-sentinel.js",
|
||||
".kimi/hooks/managed-hooks-registry.cjs",
|
||||
".kimi/hooks/package.json",
|
||||
"agents/gsd.md",
|
||||
"agents/gsd.yaml",
|
||||
"agents/subagents/gsd-advisor-researcher.md",
|
||||
|
||||
1
tests/fixtures/install-tree/opencode.json
vendored
1
tests/fixtures/install-tree/opencode.json
vendored
@@ -1,6 +1,5 @@
|
||||
[
|
||||
".gsd-profile",
|
||||
".gsd/defaults.json",
|
||||
"agents/gsd-advisor-researcher.md",
|
||||
"agents/gsd-ai-researcher.md",
|
||||
"agents/gsd-assumptions-analyzer.md",
|
||||
|
||||
1
tests/fixtures/install-tree/pi.json
vendored
1
tests/fixtures/install-tree/pi.json
vendored
@@ -1,6 +1,5 @@
|
||||
[
|
||||
".gsd-profile",
|
||||
".gsd/defaults.json",
|
||||
"extensions/gsd.js",
|
||||
"extensions/package.json",
|
||||
"gsd-core/.gsd-runtime",
|
||||
|
||||
1
tests/fixtures/install-tree/qwen.json
vendored
1
tests/fixtures/install-tree/qwen.json
vendored
@@ -1,6 +1,5 @@
|
||||
[
|
||||
".gsd-profile",
|
||||
".gsd/defaults.json",
|
||||
"agents/gsd-advisor-researcher.md",
|
||||
"agents/gsd-ai-researcher.md",
|
||||
"agents/gsd-assumptions-analyzer.md",
|
||||
|
||||
1
tests/fixtures/install-tree/trae.json
vendored
1
tests/fixtures/install-tree/trae.json
vendored
@@ -1,6 +1,5 @@
|
||||
[
|
||||
".gsd-profile",
|
||||
".gsd/defaults.json",
|
||||
"agents/gsd-advisor-researcher.md",
|
||||
"agents/gsd-ai-researcher.md",
|
||||
"agents/gsd-assumptions-analyzer.md",
|
||||
|
||||
1
tests/fixtures/install-tree/windsurf.json
vendored
1
tests/fixtures/install-tree/windsurf.json
vendored
@@ -1,6 +1,5 @@
|
||||
[
|
||||
".gsd-profile",
|
||||
".gsd/defaults.json",
|
||||
"agents/gsd-advisor-researcher.md",
|
||||
"agents/gsd-ai-researcher.md",
|
||||
"agents/gsd-assumptions-analyzer.md",
|
||||
|
||||
1
tests/fixtures/install-tree/zcode.json
vendored
1
tests/fixtures/install-tree/zcode.json
vendored
@@ -1,6 +1,5 @@
|
||||
[
|
||||
".gsd-profile",
|
||||
".gsd/defaults.json",
|
||||
"agents/gsd-advisor-researcher.md",
|
||||
"agents/gsd-ai-researcher.md",
|
||||
"agents/gsd-assumptions-analyzer.md",
|
||||
|
||||
@@ -151,3 +151,65 @@ test('install tree — claude (local legacy layout)', async (t) => {
|
||||
assert.deepEqual(actual, fixture, lines.join('\n'));
|
||||
}
|
||||
});
|
||||
|
||||
// ─── #3547: the harness's global install must exercise the REAL config-home shape ───
|
||||
//
|
||||
// runMinimalInstall's global scope used to pass `--config-dir <root>` where the
|
||||
// same <root> was also the sandbox HOME — collapsing configDir onto HOME. A real
|
||||
// global install resolves its config home to a STRICT SUBDIRECTORY of $HOME
|
||||
// (<HOME>/.claude, <HOME>/.codex, <HOME>/.config/opencode, …), and that is what
|
||||
// computePathPrefix's `isGlobal && posixTarget.startsWith(posixHome)` branch
|
||||
// needs to emit `$HOME/<suffix>/`-shaped prefixes. Under the collapsed shape the
|
||||
// prefix was bare `$HOME/`, so every emitted global artifact referenced
|
||||
// `$HOME/gsd-core/…` — a path that exists on no real install — and the entire
|
||||
// emitted-artifact apparatus (ADR-2719 differential, install-tree fixtures,
|
||||
// 19-family baseline) was structurally blind to drift confined to the real
|
||||
// global shape (#3544's fix rewrote 54 includes on live installs with ZERO
|
||||
// manifest/fixture diffs).
|
||||
|
||||
test('#3547 global harness install uses the real config-home shape (claude)', async (t) => {
|
||||
const { configDir, root } = runMinimalInstall({ runtime: 'claude', scope: 'global' });
|
||||
t.after(() => cleanup(root));
|
||||
|
||||
// Strict-subdirectory shape: <root>/.claude, never the collapsed <root>.
|
||||
assert.equal(configDir, path.join(root, RUNTIME_META.claude.globalSuffix),
|
||||
'global configDir must be the runtime\'s real global dir under the sandbox HOME');
|
||||
assert.ok(path.dirname(configDir) === root, 'configDir must be a direct subdirectory of the sandbox HOME');
|
||||
|
||||
// The deployed contract: emitted agent text references $HOME/.claude/gsd-core/,
|
||||
// never the unsuffixed $HOME/gsd-core/ form (posixNormalized on every platform).
|
||||
const planner = fs.readFileSync(path.join(configDir, 'agents', 'gsd-planner.md'), 'utf-8');
|
||||
assert.match(planner, /\$HOME\/\.claude\/gsd-core\//,
|
||||
'emitted agents must carry the real global prefix shape');
|
||||
assert.doesNotMatch(planner, /\$HOME\/gsd-core\//,
|
||||
'the collapsed bare-$HOME prefix must not appear in real-shape installs');
|
||||
});
|
||||
|
||||
test('#3547 codex global harness install resolves the .codex subdirectory', async (t) => {
|
||||
const { configDir, root } = runMinimalInstall({ runtime: 'codex', scope: 'global' });
|
||||
t.after(() => cleanup(root));
|
||||
assert.equal(configDir, path.join(root, RUNTIME_META.codex.globalSuffix));
|
||||
});
|
||||
|
||||
test('#3547 opencode global harness install resolves the XDG subdirectory', async (t) => {
|
||||
const { configDir, root } = runMinimalInstall({ runtime: 'opencode', scope: 'global' });
|
||||
t.after(() => cleanup(root));
|
||||
assert.equal(configDir, path.join(root, '.config', 'opencode'));
|
||||
});
|
||||
|
||||
test('#3547 local scope configDir unchanged', async (t) => {
|
||||
const { configDir, root } = runMinimalInstall({ runtime: 'claude', scope: 'local' });
|
||||
t.after(() => cleanup(root));
|
||||
assert.equal(configDir, path.join(root, '.claude'),
|
||||
'local installs keep the root/<localDir> shape — untouched by #3547');
|
||||
});
|
||||
|
||||
test('#3547 unknown runtime global scope fails loudly', () => {
|
||||
// grok is in the capability registry but not RUNTIME_META: a silent
|
||||
// `path.join(root, undefined)` is the #3023 failure mode this guards.
|
||||
assert.throws(
|
||||
() => runMinimalInstall({ runtime: 'grok', scope: 'global' }),
|
||||
/grok|RUNTIME_META/,
|
||||
'a runtime without a known global dir must fail loudly before any install spawns',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -47,8 +47,14 @@ const BASELINE_ENV = 'GSD_EMITTED_BASELINE';
|
||||
/** Default on-disk cache location, relative to the repo root. */
|
||||
const DEFAULT_CACHE_PATH = '.gsd-cache/emitted-baseline.json';
|
||||
|
||||
/** Baseline artifact schema version — pinned so a format change fails loudly. */
|
||||
const BASELINE_VERSION = 1;
|
||||
/** Baseline artifact schema version — pinned so a format change fails loudly.
|
||||
* v2 (#3547): the harness now measures every global family in the REAL
|
||||
* config-home shape (<root>/<globalSuffix> instead of the collapsed
|
||||
* <root>), so manifests from a v1 baseline — built with the collapsed
|
||||
* harness — are not comparable (HOME-level paths present, prefix bytes
|
||||
* absent). Bumping refuses v1 caches and forces the same-schema fallback
|
||||
* build; the push-to-next job republishes at v2 when this merges. */
|
||||
const BASELINE_VERSION = 2;
|
||||
|
||||
/**
|
||||
* Validate a baseline artifact's shape and freshness.
|
||||
|
||||
@@ -530,13 +530,17 @@ const PROVENANCE_RULES = [
|
||||
pattern: /^(gsd\.md|hooks\/PreToolUse)$/,
|
||||
sources: () => [CLINE_BODY_SRC],
|
||||
},
|
||||
{
|
||||
id: 'agents-md-code-derived',
|
||||
kind: 'code-derived',
|
||||
roots: ['.agents'],
|
||||
pattern: /^AGENTS\.md$/,
|
||||
sources: () => [CLINE_BODY_SRC],
|
||||
},
|
||||
// #3547 removed `agents-md-code-derived` (roots: ['.agents'],
|
||||
// ^AGENTS\.md$) and `synthesized-gsd-defaults` (^\.gsd/defaults\.json$):
|
||||
// both paths only appeared in a manifest while the harness collapsed
|
||||
// configDir onto the sandbox HOME, walking HOME-level siblings
|
||||
// (cline's ~/.agents/AGENTS.md, every non-Claude runtime's
|
||||
// ~/.gsd/defaults.json). With the harness installing into each runtime's
|
||||
// real global subdirectory those files sit outside the walked configDir,
|
||||
// the rules matched nothing, and the totality guard's dead-rule arm fired
|
||||
// — exactly as designed. The files are still written, still covered by the
|
||||
// existence/config suites (kimi-upgrades, codex-config, install suites);
|
||||
// they are simply no longer manifest members to attribute.
|
||||
{
|
||||
id: 'hermes-category-description',
|
||||
kind: 'code-derived',
|
||||
@@ -568,13 +572,6 @@ const PROVENANCE_RULES = [
|
||||
pattern: /^(\.gsd-profile|package\.json|\.kimi(-code)?\/package\.json|gsd-core\/VERSION|gsd-core\/\.gsd-runtime)$/,
|
||||
sources: () => [],
|
||||
},
|
||||
{
|
||||
id: 'synthesized-gsd-defaults',
|
||||
kind: 'synthesized',
|
||||
roots: null,
|
||||
pattern: /^\.gsd\/defaults\.json$/,
|
||||
sources: () => [],
|
||||
},
|
||||
{
|
||||
id: 'synthesized-host-config',
|
||||
kind: 'synthesized',
|
||||
|
||||
@@ -191,18 +191,22 @@ const HOOK_CONFIG_FILES = new Set(['settings.json', 'settings.local.json', 'hook
|
||||
// platform-stable `[features] hooks = true` flag — the real hook commands
|
||||
// live in Codex's separate hooks.json, already excluded above). Blanket-
|
||||
// excluding the 'config.toml' basename would silently blind Codex's fixture
|
||||
// to any future regression there. Kimi's config.toml instead lives OUTSIDE
|
||||
// its GSD configDir at runtime (resolveKimiHooksTomlDir resolves ~/.kimi, a
|
||||
// sibling of the configDir ~/.config/agents) — it only appears inside this
|
||||
// harness's walked tree at all because runMinimalInstall sets HOME to the
|
||||
// same temp root used as --config-dir, collapsing the two into one directory
|
||||
// for the isolated test run. So it is excluded by its exact relative path
|
||||
// under that collapsed root, not by basename.
|
||||
// Both Kimi products' native config.toml embeds a platform-varying node-runner
|
||||
// command, so neither belongs in the golden-tracked emitted manifest. kimi-code
|
||||
// resolves its own root since #2755 — listing only `.kimi/config.toml` here made
|
||||
// kimi-code's config.toml newly manifest-visible and unattributable.
|
||||
const HOOK_CONFIG_RELATIVE_PATHS = new Set(['.kimi/config.toml', '.kimi-code/config.toml']);
|
||||
// to any future regression there — and it would blind kimi-code's too: since
|
||||
// #3547 the harness installs into each runtime's REAL global subdirectory, so
|
||||
// kimi-code's hooks config.toml sits at its configDir root (rel `config.toml`)
|
||||
// and is legitimately manifest-visible. Tracking it is safe now: the
|
||||
// install-tree fixture carries paths only, and the ADR-2719 differential
|
||||
// compares base-vs-current on the same machine, so the platform-varying
|
||||
// node-runner command embedded in the TOML never crosses platforms inside a
|
||||
// gate (that was a golden-content-era hazard, and the goldens are gone).
|
||||
// Kimi CLI's config.toml (KIMI_SHARE_DIR root ~/.kimi) lives OUTSIDE its GSD
|
||||
// configDir (~/.config/agents) and never enters the walk. The pre-#3547
|
||||
// relative-path exclusions ('.kimi/config.toml', '.kimi-code/config.toml')
|
||||
// existed only for the collapsed shape — where the walked root was the HOME
|
||||
// itself and those HOME-level siblings were inside it; with no walker rooting
|
||||
// at HOME anymore they matched nothing and were removed (#3547). kimi-code
|
||||
// resolves its own root since #2755.
|
||||
const HOOK_CONFIG_RELATIVE_PATHS = new Set();
|
||||
|
||||
// Path prefixes excluded from the parity manifest. `gsd-core/bin/lib/` holds the
|
||||
// tsc-built runtime artifacts (compiled from src/*.cts) that the install COPIES
|
||||
@@ -570,8 +574,31 @@ function runMinimalInstall({ runtime, scope, extraArgs = [], installScript = INS
|
||||
let cwd = process.cwd();
|
||||
const args = [installScript, `--${runtime}`];
|
||||
if (scope === 'global') {
|
||||
args.push('--global', '--config-dir', root);
|
||||
configDir = root;
|
||||
// #3547 — install into the runtime's REAL global config home: the strict
|
||||
// subdirectory of the sandbox HOME a genuine global install resolves
|
||||
// (RUNTIME_META.globalSuffix mirrors the registry's getGlobalConfigDir
|
||||
// for every runtime). The previous `--config-dir <root>` collapsed
|
||||
// configDir onto HOME, so computePathPrefix emitted bare `$HOME/`
|
||||
// prefixes and the emitted bytes referenced `$HOME/gsd-core/…` — a path
|
||||
// no real install produces — leaving every emitted-artifact gate
|
||||
// (ADR-2719 differential, install-tree fixtures, the 19-family baseline)
|
||||
// blind to drift confined to the real global shape (#3544 evidence: 54
|
||||
// includes rewritten on live installs, zero manifest/fixture diffs). The
|
||||
// explicit flag stays: hermeticity-by-override is immune to ambient
|
||||
// redirect envs (CI runners export XDG_CONFIG_HOME, which redefines the
|
||||
// opencode/kilo XDG descriptors' resolution when no explicit dir wins).
|
||||
const globalMeta = RUNTIME_META[runtime];
|
||||
if (!globalMeta || !globalMeta.globalSuffix) {
|
||||
// #3023 lesson: a silent `path.join(root, undefined)` here throws a
|
||||
// bare TypeError naming neither the runtime nor the map at fault; a
|
||||
// runtime without a known global home must fail loudly before any
|
||||
// install spawns.
|
||||
throw new Error(
|
||||
`runMinimalInstall: no RUNTIME_META.globalSuffix for runtime "${runtime}" — refusing to guess a global config dir (#3547)`,
|
||||
);
|
||||
}
|
||||
configDir = path.join(root, globalMeta.globalSuffix);
|
||||
args.push('--global', '--config-dir', configDir);
|
||||
} else {
|
||||
args.push('--local');
|
||||
cwd = root;
|
||||
|
||||
@@ -285,11 +285,13 @@ test('UPGRADE 2: a corrupted/undeclared dispatch still fails closed to inline (s
|
||||
// docs/reference/host-integration-capability-matrix.md's kimi EoS-status
|
||||
// paragraph) — the installer's deliverable stops at the Agent-tool grant plus
|
||||
// the negotiated backgroundDispatch axis asserted above.
|
||||
test('UPGRADE 2 (installer-testable proxy): kimi --global install with subagents present grants kimi_cli.tools.agent:Agent on the root agent', (t) => {
|
||||
const { root } = runMinimalInstall({ runtime: 'kimi', scope: 'global' });
|
||||
t.after(() => cleanup(root));
|
||||
test('UPGRADE 2 (installer-testable proxy): kimi --global install with subagents present grants kimi_cli.tools.agent:Agent on the root agent', (t) => {
|
||||
const { configDir, root } = runMinimalInstall({ runtime: 'kimi', scope: 'global' });
|
||||
t.after(() => cleanup(root));
|
||||
|
||||
const rootYamlPath = path.join(root, 'agents', 'gsd.yaml');
|
||||
// #3547 — the root agent lives under the runtime's real global config
|
||||
// home (<root>/.config/agents), not the sandbox HOME itself.
|
||||
const rootYamlPath = path.join(configDir, 'agents', 'gsd.yaml');
|
||||
assert.ok(fs.existsSync(rootYamlPath), 'kimi: agents/gsd.yaml must exist');
|
||||
const rootYaml = fs.readFileSync(rootYamlPath, 'utf8');
|
||||
|
||||
@@ -420,8 +422,11 @@ describe('kimi vs kimi-code hooks-TOML root (#2755)', () => {
|
||||
|
||||
assert.ok(hasGsdHooksBlock(path.join(altHome, 'config.toml')),
|
||||
'KIMI_CODE_HOME must redirect the hooks block');
|
||||
assert.ok(!fs.existsSync(path.join(root, '.kimi-code')),
|
||||
'the default kimi-code root must not be used when the env var is set');
|
||||
// #3547 — <root>/.kimi-code legitimately exists as kimi-code's GSD config
|
||||
// home in the harness's real global shape; what must NOT happen is the
|
||||
// DEFAULT hooks root receiving the GSD block while the override is set.
|
||||
assert.ok(!hasGsdHooksBlock(path.join(root, '.kimi-code', 'config.toml')),
|
||||
'the default kimi-code hooks root must not receive the GSD hooks block when the env var is set');
|
||||
assert.ok(!fs.existsSync(path.join(root, '.kimi')),
|
||||
"Kimi CLI's root must not be touched either");
|
||||
});
|
||||
@@ -488,8 +493,10 @@ describe('kimi vs kimi-code hooks-TOML root (#2755)', () => {
|
||||
assert.ok(hasGsdHooksBlock(path.join(codeAlt, 'config.toml')),
|
||||
'kimi-code must honor KIMI_CODE_HOME while KIMI_SHARE_DIR is also set');
|
||||
assert.ok(!fs.existsSync(path.join(root, '.kimi')),
|
||||
'neither default root may be used when both overrides are set');
|
||||
assert.ok(!fs.existsSync(path.join(root, '.kimi-code')),
|
||||
'neither default root may be used when both overrides are set');
|
||||
'neither default hooks root may be used when both overrides are set');
|
||||
// #3547 — <root>/.kimi-code is kimi-code's GSD config home now; assert the
|
||||
// hooks BLOCK stayed off the default root instead of directory absence.
|
||||
assert.ok(!hasGsdHooksBlock(path.join(root, '.kimi-code', 'config.toml')),
|
||||
'neither default hooks root may receive the GSD block when both overrides are set');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,12 +53,16 @@ const EXPECTED_COMMAND_DIR = 'commands';
|
||||
/** Re-run the installer against an EXISTING configDir/root to simulate an
|
||||
* upgrade/reapply pass (the installer runs its migration planner on every
|
||||
* invocation — see bin/install.js installAllRuntimes -> install() ->
|
||||
* runInstallerMigrations, unconditional, not gated on first-install). */
|
||||
function reinstallOpencode(root, scope = 'global') {
|
||||
* runInstallerMigrations, unconditional, not gated on first-install).
|
||||
* #3547 — `configDir` is the runtime's REAL global home (<root>/<globalSuffix>),
|
||||
* the same shape runMinimalInstall now installs into; reinstalling into the
|
||||
* bare <root> would target a different config home and never see the
|
||||
* fabricated legacy state. */
|
||||
function reinstallOpencode(root, configDir = null, scope = 'global') {
|
||||
const args = [INSTALL_SCRIPT, '--opencode'];
|
||||
let cwd = process.cwd();
|
||||
if (scope === 'global') {
|
||||
args.push('--global', '--config-dir', root);
|
||||
args.push('--global', '--config-dir', configDir || root);
|
||||
} else {
|
||||
args.push('--local');
|
||||
cwd = root;
|
||||
@@ -305,7 +309,7 @@ describe('#2329: upgrading an install with an orphaned command/ dir migrates it
|
||||
assert.ok(gsdMdFiles(legacyDir).length >= 60, 'sanity: legacyDir must be populated before reinstall');
|
||||
assert.ok(!fs.existsSync(pluralDir), 'sanity: pluralDir must not exist before reinstall (fabricated pre-fix state)');
|
||||
|
||||
const result = reinstallOpencode(root, 'global');
|
||||
const result = reinstallOpencode(root, configDir, 'global');
|
||||
assert.strictEqual(
|
||||
result.status, 0,
|
||||
`reinstall (upgrade) must exit 0\nstdout: ${result.stdout}\nstderr: ${result.stderr}`
|
||||
@@ -341,7 +345,7 @@ describe('#2329: upgrading an install with an orphaned command/ dir migrates it
|
||||
const userFileLegacy = path.join(legacyDir, 'my-notes.md');
|
||||
fs.writeFileSync(userFileLegacy, userContent, 'utf8');
|
||||
|
||||
const result = reinstallOpencode(root, 'global');
|
||||
const result = reinstallOpencode(root, configDir, 'global');
|
||||
assert.strictEqual(
|
||||
result.status, 0,
|
||||
`reinstall (upgrade) must exit 0\nstdout: ${result.stdout}\nstderr: ${result.stderr}`
|
||||
@@ -406,7 +410,7 @@ describe('#2329: upgrading an install with an orphaned command/ dir migrates it
|
||||
`sanity: manifest must record >=60 "command/" keys before reinstall, got ${legacyManifestKeys.length}`
|
||||
);
|
||||
|
||||
const result = reinstallOpencode(root, 'global');
|
||||
const result = reinstallOpencode(root, configDir, 'global');
|
||||
assert.strictEqual(
|
||||
result.status, 0,
|
||||
`reinstall (upgrade) must exit 0\nstdout: ${result.stdout}\nstderr: ${result.stderr}`
|
||||
|
||||
@@ -80,6 +80,12 @@ const RUNTIMES = Object.keys(RUNTIME_META);
|
||||
* assert success — callers decide (row 36 expects failure). */
|
||||
function spawnGlobalInstall(installScript, runtime, extraArgs = []) {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), `gsd-2930-dest-${runtime}-`));
|
||||
// #3547 — same real config-home shape as runMinimalInstall: the strict
|
||||
// <root>/<globalSuffix> subdirectory, not the collapsed <root>. The size
|
||||
// comparisons below normalize each side's own root out of the text; a
|
||||
// collapsed stub against a real-shape install bakes in a `.claude`-style
|
||||
// suffix delta that has nothing to do with the compose wiring under test.
|
||||
const configDir = path.join(root, RUNTIME_META[runtime].globalSuffix);
|
||||
const args = [
|
||||
'--preserve-symlinks',
|
||||
'--preserve-symlinks-main',
|
||||
@@ -87,7 +93,7 @@ function spawnGlobalInstall(installScript, runtime, extraArgs = []) {
|
||||
`--${runtime}`,
|
||||
'--global',
|
||||
'--config-dir',
|
||||
root,
|
||||
configDir,
|
||||
...extraArgs,
|
||||
];
|
||||
const seamResult = runNode(args, {
|
||||
@@ -96,7 +102,7 @@ function spawnGlobalInstall(installScript, runtime, extraArgs = []) {
|
||||
timeoutMs: INSTALL_TIMEOUT_MS,
|
||||
});
|
||||
const result = { status: seamResult.exitCode, stdout: seamResult.stdout, stderr: seamResult.stderr };
|
||||
return { result, configDir: root, root };
|
||||
return { result, configDir, root };
|
||||
}
|
||||
|
||||
/** Convert native path separators to POSIX forward slashes, unconditionally
|
||||
|
||||
Reference in New Issue
Block a user