diff --git a/.changeset/daring-otters-dart.md b/.changeset/daring-otters-dart.md new file mode 100644 index 000000000..54f340c3a --- /dev/null +++ b/.changeset/daring-otters-dart.md @@ -0,0 +1,5 @@ +--- +type: Changed +pr: 1757 +--- +**Internal: getDirName is now derived from a documented `runtime.localConfigDir` descriptor field** — each runtime's local content-rewrite directory (e.g. `cursor`→`.cursor`, `copilot`→`.github`) moved from a hand-maintained if-chain into its capability descriptor (ADR-1239 Phase B), so it can no longer drift from the registry. Install output is byte-identical for all 16 runtimes (golden-parity asserted); no user-facing change. diff --git a/capabilities/antigravity/capability.json b/capabilities/antigravity/capability.json index 471da735f..ef7680f2f 100644 --- a/capabilities/antigravity/capability.json +++ b/capabilities/antigravity/capability.json @@ -24,6 +24,7 @@ ], "probeExists": "gsd-core/VERSION" }, + "localConfigDir": ".agents", "configFormat": "settings-json", "artifactLayout": { "global": [ diff --git a/capabilities/augment/capability.json b/capabilities/augment/capability.json index 18320b74d..7cd74f23a 100644 --- a/capabilities/augment/capability.json +++ b/capabilities/augment/capability.json @@ -17,6 +17,7 @@ "AUGMENT_CONFIG_DIR" ] }, + "localConfigDir": ".augment", "configFormat": "settings-json", "artifactLayout": { "global": [ diff --git a/capabilities/claude/capability.json b/capabilities/claude/capability.json index b3806cfd7..712b332f1 100644 --- a/capabilities/claude/capability.json +++ b/capabilities/claude/capability.json @@ -17,6 +17,7 @@ "CLAUDE_CONFIG_DIR" ] }, + "localConfigDir": ".claude", "configFormat": "settings-json", "artifactLayout": { "global": [ diff --git a/capabilities/cline/capability.json b/capabilities/cline/capability.json index 113675b95..119269871 100644 --- a/capabilities/cline/capability.json +++ b/capabilities/cline/capability.json @@ -17,6 +17,7 @@ "CLINE_CONFIG_DIR" ] }, + "localConfigDir": ".cline", "configFormat": "markdown-dir", "artifactLayout": { "global": [ diff --git a/capabilities/codebuddy/capability.json b/capabilities/codebuddy/capability.json index 8f1558ceb..500565c51 100644 --- a/capabilities/codebuddy/capability.json +++ b/capabilities/codebuddy/capability.json @@ -17,6 +17,7 @@ "CODEBUDDY_CONFIG_DIR" ] }, + "localConfigDir": ".codebuddy", "configFormat": "settings-json", "artifactLayout": { "global": [ diff --git a/capabilities/codex/capability.json b/capabilities/codex/capability.json index c2b332408..a1ddc49b7 100644 --- a/capabilities/codex/capability.json +++ b/capabilities/codex/capability.json @@ -17,6 +17,7 @@ "CODEX_HOME" ] }, + "localConfigDir": ".codex", "configFormat": "toml", "artifactLayout": { "global": [ diff --git a/capabilities/copilot/capability.json b/capabilities/copilot/capability.json index 0164c4dee..be769009b 100644 --- a/capabilities/copilot/capability.json +++ b/capabilities/copilot/capability.json @@ -18,6 +18,7 @@ "COPILOT_HOME" ] }, + "localConfigDir": ".github", "configFormat": "markdown", "artifactLayout": { "global": [ diff --git a/capabilities/cursor/capability.json b/capabilities/cursor/capability.json index f83065df3..17d94b257 100644 --- a/capabilities/cursor/capability.json +++ b/capabilities/cursor/capability.json @@ -17,6 +17,7 @@ "CURSOR_CONFIG_DIR" ] }, + "localConfigDir": ".cursor", "configFormat": "none", "artifactLayout": { "global": [ diff --git a/capabilities/gemini/capability.json b/capabilities/gemini/capability.json index 97d539fed..ccc67a8d1 100644 --- a/capabilities/gemini/capability.json +++ b/capabilities/gemini/capability.json @@ -17,6 +17,7 @@ "GEMINI_CONFIG_DIR" ] }, + "localConfigDir": ".gemini", "configFormat": "settings-json", "artifactLayout": { "global": [ diff --git a/capabilities/hermes/capability.json b/capabilities/hermes/capability.json index 1c9034c8d..5dd4e4b05 100644 --- a/capabilities/hermes/capability.json +++ b/capabilities/hermes/capability.json @@ -17,6 +17,7 @@ "HERMES_HOME" ] }, + "localConfigDir": ".hermes", "configFormat": "settings-json", "artifactLayout": { "global": [ diff --git a/capabilities/kilo/capability.json b/capabilities/kilo/capability.json index 6bae4c38e..032f9be2e 100644 --- a/capabilities/kilo/capability.json +++ b/capabilities/kilo/capability.json @@ -24,6 +24,7 @@ "env": [] } }, + "localConfigDir": ".kilo", "configFormat": "settings-json", "artifactLayout": { "global": [ diff --git a/capabilities/kimi/capability.json b/capabilities/kimi/capability.json index 5f81e5675..d9bf8da7d 100644 --- a/capabilities/kimi/capability.json +++ b/capabilities/kimi/capability.json @@ -22,6 +22,7 @@ ], "probeExists": "skills" }, + "localConfigDir": ".kimi-code", "configFormat": "none", "artifactLayout": { "global": [ diff --git a/capabilities/opencode/capability.json b/capabilities/opencode/capability.json index 0030c7f04..21987141b 100644 --- a/capabilities/opencode/capability.json +++ b/capabilities/opencode/capability.json @@ -19,6 +19,7 @@ "XDG_CONFIG_HOME" ] }, + "localConfigDir": ".opencode", "configFormat": "settings-json", "artifactLayout": { "global": [ diff --git a/capabilities/qwen/capability.json b/capabilities/qwen/capability.json index bd833b655..2199c8a5e 100644 --- a/capabilities/qwen/capability.json +++ b/capabilities/qwen/capability.json @@ -17,6 +17,7 @@ "QWEN_CONFIG_DIR" ] }, + "localConfigDir": ".qwen", "configFormat": "settings-json", "artifactLayout": { "global": [ diff --git a/capabilities/trae/capability.json b/capabilities/trae/capability.json index 1ce5d1e61..c900a5660 100644 --- a/capabilities/trae/capability.json +++ b/capabilities/trae/capability.json @@ -17,6 +17,7 @@ "TRAE_CONFIG_DIR" ] }, + "localConfigDir": ".trae", "configFormat": "none", "artifactLayout": { "global": [ diff --git a/capabilities/windsurf/capability.json b/capabilities/windsurf/capability.json index 06a2bc705..274b5e421 100644 --- a/capabilities/windsurf/capability.json +++ b/capabilities/windsurf/capability.json @@ -18,6 +18,7 @@ "WINDSURF_CONFIG_DIR" ] }, + "localConfigDir": ".windsurf", "configFormat": "none", "artifactLayout": { "global": [], diff --git a/docs/reference/capability-manifest.md b/docs/reference/capability-manifest.md index db971bda3..6ddae7076 100644 --- a/docs/reference/capability-manifest.md +++ b/docs/reference/capability-manifest.md @@ -143,6 +143,7 @@ Runtime capabilities describe how GSD projects its artefacts onto one host CLI. | Axis | Field | Type summary | |---|---|---| | Config home | `runtime.configHome` | Structured object with `kind` (`dot-home` \| `dot-home-nested` \| `xdg` \| `generic-agents-root`), `name`, optional `parent`, `env[]`, `probe[]`, `probeExists`, `skillsHome`. `probeExists` is an optional sub-path applied to probe candidates: for `generic-agents-root` it is a hard filter (a candidate qualifies only if `/` exists); for `dot-home-nested` it is a preference that makes probing pick the candidate GSD owns (e.g. `gsd-core/VERSION`) over a bare-existing sibling before falling back — see ADR-1016 and #213/#217. | +| Local config dir | `runtime.localConfigDir` | Required dot-prefixed string. The runtime's **local** content-rewrite directory — the `./` target GSD stamps into rewritten artefact bodies (e.g. `./.claude/` → `.//`) and the local install dir basename. Backs `getDirName()` (registry-derived, #1679). Usually `.` (the runtime's home dot-dir), but **three runtimes diverge** because they read GSD's content from a non-home directory: `copilot` → `.github` (GitHub Copilot reads custom instructions from `.github/copilot-instructions.md` / `.github/instructions/`; see `convertClaudeToCopilotContent` rewrites in `src/runtime-artifact-conversion.cts`), `antigravity` → `.agents` (local agent/workflow dir; see the antigravity rewrites in `src/runtime-artifact-conversion.cts`), `kimi` → `.kimi-code`. Distinct from `configHome.name` (the **global** install home, which for these three is `.copilot` / `antigravity` / `agents`). Byte-parity-proven against the prior hand-maintained mapping by the golden-install-parity harness. | | Config format | `runtime.configFormat` | Closed enum: `settings-json` \| `toml` \| `markdown` \| `markdown-dir` \| `none`. | | Artefact layout | `runtime.artifactLayout` | Object with `global` and `local` arrays of `ArtifactKind` (`kind`, `destSubpath`, `prefix`, `nesting`, `recursive`, `stage`). | | Command style | `runtime.commandStyle` | Closed enum: `slash-hyphen` \| `shell-var`. | diff --git a/gsd-core/bin/lib/capability-registry.cjs b/gsd-core/bin/lib/capability-registry.cjs index f4f964253..c2fd94c50 100644 --- a/gsd-core/bin/lib/capability-registry.cjs +++ b/gsd-core/bin/lib/capability-registry.cjs @@ -86,6 +86,7 @@ const capabilities = { ], "probeExists": "gsd-core/VERSION" }, + "localConfigDir": ".agents", "configFormat": "settings-json", "artifactLayout": { "global": [ @@ -193,6 +194,7 @@ const capabilities = { "AUGMENT_CONFIG_DIR" ] }, + "localConfigDir": ".augment", "configFormat": "settings-json", "artifactLayout": { "global": [ @@ -279,6 +281,7 @@ const capabilities = { "CLAUDE_CONFIG_DIR" ] }, + "localConfigDir": ".claude", "configFormat": "settings-json", "artifactLayout": { "global": [ @@ -362,6 +365,7 @@ const capabilities = { "CLINE_CONFIG_DIR" ] }, + "localConfigDir": ".cline", "configFormat": "markdown-dir", "artifactLayout": { "global": [ @@ -483,6 +487,7 @@ const capabilities = { "CODEBUDDY_CONFIG_DIR" ] }, + "localConfigDir": ".codebuddy", "configFormat": "settings-json", "artifactLayout": { "global": [ @@ -569,6 +574,7 @@ const capabilities = { "CODEX_HOME" ] }, + "localConfigDir": ".codex", "configFormat": "toml", "artifactLayout": { "global": [ @@ -640,6 +646,7 @@ const capabilities = { "COPILOT_HOME" ] }, + "localConfigDir": ".github", "configFormat": "markdown", "artifactLayout": { "global": [ @@ -709,6 +716,7 @@ const capabilities = { "CURSOR_CONFIG_DIR" ] }, + "localConfigDir": ".cursor", "configFormat": "none", "artifactLayout": { "global": [ @@ -914,6 +922,7 @@ const capabilities = { "GEMINI_CONFIG_DIR" ] }, + "localConfigDir": ".gemini", "configFormat": "settings-json", "artifactLayout": { "global": [ @@ -1029,6 +1038,7 @@ const capabilities = { "HERMES_HOME" ] }, + "localConfigDir": ".hermes", "configFormat": "settings-json", "artifactLayout": { "global": [ @@ -1158,6 +1168,7 @@ const capabilities = { "env": [] } }, + "localConfigDir": ".kilo", "configFormat": "settings-json", "artifactLayout": { "global": [ @@ -1248,6 +1259,7 @@ const capabilities = { ], "probeExists": "skills" }, + "localConfigDir": ".kimi-code", "configFormat": "none", "artifactLayout": { "global": [ @@ -1542,6 +1554,7 @@ const capabilities = { "XDG_CONFIG_HOME" ] }, + "localConfigDir": ".opencode", "configFormat": "settings-json", "artifactLayout": { "global": [ @@ -1758,6 +1771,7 @@ const capabilities = { "QWEN_CONFIG_DIR" ] }, + "localConfigDir": ".qwen", "configFormat": "settings-json", "artifactLayout": { "global": [ @@ -2082,6 +2096,7 @@ const capabilities = { "TRAE_CONFIG_DIR" ] }, + "localConfigDir": ".trae", "configFormat": "none", "artifactLayout": { "global": [ @@ -2247,6 +2262,7 @@ const capabilities = { "WINDSURF_CONFIG_DIR" ] }, + "localConfigDir": ".windsurf", "configFormat": "none", "artifactLayout": { "global": [], @@ -3038,6 +3054,7 @@ const runtimes = { ], "probeExists": "gsd-core/VERSION" }, + "localConfigDir": ".agents", "configFormat": "settings-json", "artifactLayout": { "global": [ @@ -3108,6 +3125,7 @@ const runtimes = { "AUGMENT_CONFIG_DIR" ] }, + "localConfigDir": ".augment", "configFormat": "settings-json", "artifactLayout": { "global": [ @@ -3194,6 +3212,7 @@ const runtimes = { "CLAUDE_CONFIG_DIR" ] }, + "localConfigDir": ".claude", "configFormat": "settings-json", "artifactLayout": { "global": [ @@ -3277,6 +3296,7 @@ const runtimes = { "CLINE_CONFIG_DIR" ] }, + "localConfigDir": ".cline", "configFormat": "markdown-dir", "artifactLayout": { "global": [ @@ -3337,6 +3357,7 @@ const runtimes = { "CODEBUDDY_CONFIG_DIR" ] }, + "localConfigDir": ".codebuddy", "configFormat": "settings-json", "artifactLayout": { "global": [ @@ -3423,6 +3444,7 @@ const runtimes = { "CODEX_HOME" ] }, + "localConfigDir": ".codex", "configFormat": "toml", "artifactLayout": { "global": [ @@ -3494,6 +3516,7 @@ const runtimes = { "COPILOT_HOME" ] }, + "localConfigDir": ".github", "configFormat": "markdown", "artifactLayout": { "global": [ @@ -3563,6 +3586,7 @@ const runtimes = { "CURSOR_CONFIG_DIR" ] }, + "localConfigDir": ".cursor", "configFormat": "none", "artifactLayout": { "global": [ @@ -3649,6 +3673,7 @@ const runtimes = { "GEMINI_CONFIG_DIR" ] }, + "localConfigDir": ".gemini", "configFormat": "settings-json", "artifactLayout": { "global": [ @@ -3723,6 +3748,7 @@ const runtimes = { "HERMES_HOME" ] }, + "localConfigDir": ".hermes", "configFormat": "settings-json", "artifactLayout": { "global": [ @@ -3800,6 +3826,7 @@ const runtimes = { "env": [] } }, + "localConfigDir": ".kilo", "configFormat": "settings-json", "artifactLayout": { "global": [ @@ -3890,6 +3917,7 @@ const runtimes = { ], "probeExists": "skills" }, + "localConfigDir": ".kimi-code", "configFormat": "none", "artifactLayout": { "global": [ @@ -3960,6 +3988,7 @@ const runtimes = { "XDG_CONFIG_HOME" ] }, + "localConfigDir": ".opencode", "configFormat": "settings-json", "artifactLayout": { "global": [ @@ -4045,6 +4074,7 @@ const runtimes = { "QWEN_CONFIG_DIR" ] }, + "localConfigDir": ".qwen", "configFormat": "settings-json", "artifactLayout": { "global": [ @@ -4119,6 +4149,7 @@ const runtimes = { "TRAE_CONFIG_DIR" ] }, + "localConfigDir": ".trae", "configFormat": "none", "artifactLayout": { "global": [ @@ -4189,6 +4220,7 @@ const runtimes = { "WINDSURF_CONFIG_DIR" ] }, + "localConfigDir": ".windsurf", "configFormat": "none", "artifactLayout": { "global": [], diff --git a/gsd-core/bin/lib/capability-validator.cjs b/gsd-core/bin/lib/capability-validator.cjs index b1494091c..6d936b90d 100644 --- a/gsd-core/bin/lib/capability-validator.cjs +++ b/gsd-core/bin/lib/capability-validator.cjs @@ -1030,6 +1030,20 @@ function validateRuntimeBody(cap) { ); } + // localConfigDir — REQUIRED non-empty dot-dir string (ADR-1239 Phase B #1679) + // Must start with '.' (e.g. ".claude", ".cursor"). Validated here so the registry + // generator catches any descriptor missing the field before regenerating. + if (typeof r.localConfigDir !== 'string' || r.localConfigDir.length === 0) { + errors.push( + 'runtime.localConfigDir is required and must be a non-empty string (e.g. ".claude"); ' + + 'got: ' + JSON.stringify(r.localConfigDir), + ); + } else if (!r.localConfigDir.startsWith('.')) { + errors.push( + 'runtime.localConfigDir must start with "." (a dot-dir); got: ' + JSON.stringify(r.localConfigDir), + ); + } + // extendedHookEvents — required array; every element must be in closed enum if (!Array.isArray(r.extendedHookEvents)) { errors.push( diff --git a/src/runtime-name-policy.cts b/src/runtime-name-policy.cts index 0204106d0..6b20fe053 100644 --- a/src/runtime-name-policy.cts +++ b/src/runtime-name-policy.cts @@ -142,20 +142,12 @@ export function getProjectInstructionFile(runtime: unknown): string { * `bin/install.js` re-exports this same function for back-compat. */ export function getDirName(runtime: string): string { - if (runtime === 'copilot') return '.github'; - if (runtime === 'opencode') return '.opencode'; - if (runtime === 'gemini') return '.gemini'; - if (runtime === 'kilo') return '.kilo'; - if (runtime === 'codex') return '.codex'; - if (runtime === 'antigravity') return '.agents'; - if (runtime === 'cursor') return '.cursor'; - if (runtime === 'windsurf') return '.windsurf'; - if (runtime === 'augment') return '.augment'; - if (runtime === 'trae') return '.trae'; - if (runtime === 'qwen') return '.qwen'; - if (runtime === 'hermes') return '.hermes'; - if (runtime === 'kimi') return '.kimi-code'; - if (runtime === 'codebuddy') return '.codebuddy'; - if (runtime === 'cline') return '.cline'; + if (!runtime) return '.claude'; + // eslint-disable-next-line @typescript-eslint/no-require-imports + const { runtimes } = require('./capability-registry.cjs') as { + runtimes: Record; + }; + const dir = runtimes[runtime]?.runtime?.localConfigDir; + if (typeof dir === 'string' && dir.length > 0) return dir; return '.claude'; } diff --git a/tests/capability-manifest-version.test.cjs b/tests/capability-manifest-version.test.cjs index 918e67ab7..aa757a298 100644 --- a/tests/capability-manifest-version.test.cjs +++ b/tests/capability-manifest-version.test.cjs @@ -76,6 +76,7 @@ function runtimeCap(overrides) { engines: { gsd: '>=1.6.0' }, runtime: { configHome: { kind: 'dot-home', name: '.demo', env: [] }, + localConfigDir: '.demo', configFormat: 'settings-json', artifactLayout: { global: [], local: [] }, commandStyle: 'slash-hyphen', diff --git a/tests/capability-registry.test.cjs b/tests/capability-registry.test.cjs index 481906a8a..2ae04f743 100644 --- a/tests/capability-registry.test.cjs +++ b/tests/capability-registry.test.cjs @@ -1764,6 +1764,7 @@ describe('C3: role:runtime body validation', () => { tier: 'standard', requires: [], runtime: { configHome: { kind: 'dot-home', name: '.cursor', env: ['CURSOR_CONFIG_DIR'] }, + localConfigDir: '.cursor', configFormat: 'settings-json', artifactLayout: { global: [], local: [] }, commandStyle: 'slash-hyphen', @@ -3217,6 +3218,7 @@ function makeRuntimeCap(overrides) { requires: [], runtime: { configHome: { kind: 'dot-home', name: '.test-rt', env: ['TEST_RT_DIR'] }, + localConfigDir: '.test-rt', configFormat: 'settings-json', artifactLayout: { global: [], local: [] }, commandStyle: 'slash-hyphen', @@ -4292,6 +4294,7 @@ describe('ADR-857 phase 5f: cross-field consistency gate rejection tests (DEFECT requires: [], runtime: { configHome: { kind: 'dot-home', name: '.test-runtime', env: [] }, + localConfigDir: '.test-runtime', configFormat: 'settings-json', artifactLayout: { global: [], local: [] }, commandStyle: 'slash-hyphen', diff --git a/tests/getdirname-registry-derivation.test.cjs b/tests/getdirname-registry-derivation.test.cjs new file mode 100644 index 000000000..99bd36875 --- /dev/null +++ b/tests/getdirname-registry-derivation.test.cjs @@ -0,0 +1,93 @@ +'use strict'; +/** + * Drift-guard: getDirName must be derived from the capability registry. + * Verifies: + * 1. For every known runtime id, getDirName(id) equals the hardcoded golden + * expected map — a pinned oracle that catches BOTH formula bugs AND + * unintended registry drift (adding/removing a runtime or changing its + * localConfigDir forces a deliberate golden-map update here). + * 2. getDirName('unknown') and getDirName('') fall back to '.claude'. + * 3. Every registry runtime entry has a non-empty dot-dir localConfigDir string — + * cross-check from a different angle than the production derivation formula. + * + * ADR-1239 Phase B (#1679). + * Behavioral tests only: assert on returned values, no source-grep. + */ + +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const runtimeNamePolicy = require('../gsd-core/bin/lib/runtime-name-policy.cjs'); +const registry = require('../gsd-core/bin/lib/capability-registry.cjs'); + +const { getDirName } = runtimeNamePolicy; + +// Golden oracle: hardcoded expected map of all 16 runtime ids to their local config dir. +// A pinned expected value in a TEST is correct — the test IS the oracle (non-circular). +// Only PRODUCTION code should derive dynamically from the registry. +// If this map diverges from getDirName output, either the formula is wrong +// OR the registry changed — both require a deliberate golden-map update here. +const GOLDEN_DIR_MAP = { + claude: '.claude', + copilot: '.github', + opencode: '.opencode', + gemini: '.gemini', + kilo: '.kilo', + codex: '.codex', + antigravity: '.agents', + cursor: '.cursor', + windsurf: '.windsurf', + augment: '.augment', + trae: '.trae', + qwen: '.qwen', + hermes: '.hermes', + kimi: '.kimi-code', + codebuddy: '.codebuddy', + cline: '.cline', +}; + +test('getDirName: golden map matches for all 16 known runtime ids', () => { + for (const [id, expected] of Object.entries(GOLDEN_DIR_MAP)) { + const actual = getDirName(id); + assert.strictEqual( + actual, + expected, + `getDirName('${id}') diverged from golden.\n` + + ` actual: ${JSON.stringify(actual)}\n` + + ` expected: ${JSON.stringify(expected)}`, + ); + } +}); + +test('drift guard: registry runtime id set EXACTLY equals the golden map (adding/removing a runtime forces a golden update)', () => { + // Without this, a newly-added runtime would pass (its value never checked) and + // removing `claude` could pass via the .claude fallback. Pin the set both ways. + const registryIds = Object.keys(registry.runtimes).sort(); + const goldenIds = Object.keys(GOLDEN_DIR_MAP).sort(); + assert.deepEqual(registryIds, goldenIds, + 'registry.runtimes id set must exactly match GOLDEN_DIR_MAP — update the golden map when adding/removing a runtime'); +}); + +test('getDirName fallback: unknown runtime returns ".claude"', () => { + assert.strictEqual(getDirName('unknown'), '.claude', + 'getDirName("unknown") must return ".claude" (default fallback)'); +}); + +test('getDirName fallback: empty string returns ".claude"', () => { + assert.strictEqual(getDirName(''), '.claude', + 'getDirName("") must return ".claude" (empty-input fallback)'); +}); + +test('registry cross-check: every runtimes[id].runtime.localConfigDir is a non-empty dot-dir string', () => { + for (const [id, entry] of Object.entries(registry.runtimes)) { + if (!entry || typeof entry !== 'object') continue; + const runtimeBlock = entry.runtime; + if (!runtimeBlock || typeof runtimeBlock !== 'object') continue; + const dir = runtimeBlock.localConfigDir; + assert.strictEqual(typeof dir, 'string', + `registry.runtimes['${id}'].runtime.localConfigDir must be a string (got: ${typeof dir})`); + assert.ok(dir.length > 0, + `registry.runtimes['${id}'].runtime.localConfigDir must be non-empty`); + assert.ok(dir.startsWith('.'), + `registry.runtimes['${id}'].runtime.localConfigDir must start with '.' (got: ${JSON.stringify(dir)})`); + } +}); diff --git a/tests/host-integration-validator-parity.test.cjs b/tests/host-integration-validator-parity.test.cjs index 7837cd7a3..eab47c458 100644 --- a/tests/host-integration-validator-parity.test.cjs +++ b/tests/host-integration-validator-parity.test.cjs @@ -51,6 +51,7 @@ function makeMinimalRuntimeCap(overrides = {}) { sandboxTier: 'none', supportTier: 1, installSurface: 'profile-marker-only', + localConfigDir: '.test-runtime', writesSharedSettings: false, permissionWriter: null, extendedHookEvents: [], @@ -331,6 +332,7 @@ describe('Fix 3: reserved-key guard on hostIntegration and hostIntegration.dispa sandboxTier: 'none', supportTier: 1, installSurface: 'settings-json', + localConfigDir: '.test-runtime', writesSharedSettings: true, permissionWriter: null, extendedHookEvents: ['SubagentStop', 'Stop', 'PreCompact', 'FileChanged'],