* fix(#2297): scope resolve_model_ids:"omit" to the resolving runtime The installer writes resolve_model_ids:"omit" for non-alias runtimes into the machine-wide ~/.gsd/defaults.json (#1156); any runtime read it back, so install order silently flipped Claude's adaptive tier aliases (executor->sonnet, planner->opus) to '' in no-project sessions. Resolution is now scoped to the runtime actually resolving, identified by a new per-install <install>/gsd-core/.gsd-runtime marker (installer writes it beside VERSION). The "omit" branch returns '' only when the PROJECT explicitly set omit (honored for all runtimes, #2517 finding #4) OR the active runtime lacks native aliases. Claude ignores a global-defaults-only omit and keeps its aliases; the active runtime is canonicalized (GSD_RUNTIME -> config.runtime -> marker -> claude) so alias/case spellings can't defeat the check; explicit project omit is workstream/project-scope aware; explicit true still materializes IDs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(#2297): backfill PR number 2332 into changeset Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
5
.changeset/nimble-yaks-climb.md
Normal file
5
.changeset/nimble-yaks-climb.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2332
|
||||
---
|
||||
**Installing a non-Claude runtime no longer breaks Claude's model resolution in no-project sessions** — the installer writes `resolve_model_ids:"omit"` for non-alias runtimes into the machine-wide `~/.gsd/defaults.json`, which any runtime read back, so install order silently flipped Claude's adaptive tier aliases (executor→sonnet, planner→opus) to an empty model string. Resolution is now scoped to the runtime actually resolving, via a per-install `.gsd-runtime` marker: Claude ignores a global-defaults omit and keeps its tier aliases, non-alias runtimes still omit, and an explicit project-level `omit`/`true` is always honored. (#2297)
|
||||
@@ -10880,6 +10880,22 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
failures.push('VERSION');
|
||||
}
|
||||
|
||||
// #2297: write a per-install runtime marker co-located with VERSION at
|
||||
// <install>/gsd-core/.gsd-runtime. It gives resolveModelInternal a reliable
|
||||
// "which runtime owns THIS install" signal in a no-project session (config.runtime
|
||||
// is null and GSD_RUNTIME is not exported), so the shared ~/.gsd/defaults.json
|
||||
// resolve_model_ids:"omit" policy (written below for non-alias runtimes only)
|
||||
// applies ONLY when a non-alias runtime is actually resolving — a Claude session
|
||||
// reads its own marker and keeps its tier aliases instead of inheriting another
|
||||
// runtime's install-order-dependent "omit". See src/model-resolver.cts.
|
||||
const runtimeMarkerDest = path.join(targetDir, 'gsd-core', '.gsd-runtime');
|
||||
fs.writeFileSync(runtimeMarkerDest, `${runtime}\n`);
|
||||
if (verifyFileInstalled(runtimeMarkerDest, '.gsd-runtime')) {
|
||||
console.log(` ${green}✓${reset} Wrote runtime marker (.gsd-runtime: ${runtime})`);
|
||||
} else {
|
||||
failures.push('.gsd-runtime');
|
||||
}
|
||||
|
||||
// Reusable: copy hooks/dist/ + hooks/lib/ into destRootDir, writing the
|
||||
// CommonJS package.json marker alongside them. Used below for the generic
|
||||
// configDir install path (guarded by hostBehaviors.skipSharedHooksInstall),
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
* epic #1267; callers import resolvers from model-resolver.cjs directly.
|
||||
*
|
||||
* Dependencies (leaf modules only):
|
||||
* - node:fs / node:path (stdlib, not currently needed — included for future use)
|
||||
* - node:fs / node:path (read the per-install .gsd-runtime marker + project config for the #2297 omit gate)
|
||||
* - ./runtime-name-policy.cjs (resolveRuntimeNameFromCandidates — canonicalize the active runtime)
|
||||
* - ./planning-workspace.cjs (planningDir — workstream/project-aware project-config path)
|
||||
* - ./config-loader.cjs (loadConfig)
|
||||
* - ./configuration.cjs (CONFIG_DEFAULTS as CANONICAL_CONFIG_DEFAULTS)
|
||||
* - ./model-profiles.cjs (MODEL_PROFILES, AGENT_TO_PHASE_TYPE, AGENT_DEFAULT_TIERS, VALID_AGENT_TIERS, nextTier)
|
||||
@@ -30,6 +32,98 @@ const { MODEL_PROFILES, AGENT_TO_PHASE_TYPE, AGENT_DEFAULT_TIERS, VALID_AGENT_TI
|
||||
|
||||
import { MODEL_ALIAS_MAP, RUNTIME_PROFILE_MAP, PROVIDER_PRESETS } from './model-catalog.cjs';
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { resolveRuntimeNameFromCandidates } from './runtime-name-policy.cjs';
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import planningWorkspaceMod = require('./planning-workspace.cjs');
|
||||
const { planningDir } = planningWorkspaceMod;
|
||||
|
||||
// ─── #2297: per-install runtime identity for the resolve_model_ids:"omit" gate ─
|
||||
//
|
||||
// The installer writes `resolve_model_ids:"omit"` into the SHARED
|
||||
// ~/.gsd/defaults.json for every runtime that lacks native model aliases (#1156).
|
||||
// Because that file is machine-wide, a non-Claude install would otherwise poison
|
||||
// a Claude no-project resolution into returning '' — silently defeating Claude's
|
||||
// adaptive tier aliases. The "omit" must therefore apply only when a runtime that
|
||||
// genuinely lacks native aliases is the one resolving.
|
||||
//
|
||||
// In a no-project session there is no `.planning/config.json` (so config.runtime
|
||||
// is null) and GSD_RUNTIME is not exported by gsd-core, so the only reliable
|
||||
// current-runtime signal is the per-install marker the installer co-locates next
|
||||
// to VERSION at <install>/gsd-core/.gsd-runtime (this file's dir is
|
||||
// <install>/gsd-core/bin/lib). Precedence for the gate: project config.runtime →
|
||||
// GSD_RUNTIME env (manual/CI override + test seam) → install marker → 'claude'.
|
||||
//
|
||||
// `claude` is currently the ONLY runtime with nativeModelAliases:true; a
|
||||
// registry-parity test guards this set so a future alias-capable runtime fails
|
||||
// loudly here instead of silently omitting.
|
||||
const RUNTIMES_WITH_NATIVE_ALIASES: ReadonlySet<string> = new Set(['claude']);
|
||||
|
||||
let _installMarkerCache: string | null | undefined;
|
||||
function readInstallRuntimeMarker(): string | null {
|
||||
if (_installMarkerCache !== undefined) return _installMarkerCache;
|
||||
try {
|
||||
const markerPath = path.join(__dirname, '..', '..', '.gsd-runtime');
|
||||
const raw = fs.readFileSync(markerPath, 'utf8').trim();
|
||||
_installMarkerCache = raw || null;
|
||||
} catch {
|
||||
// No marker: dev/source tree, or an install predating #2297. Fall through to
|
||||
// the 'claude' default (keeps tier aliases — never worse than the bug).
|
||||
_installMarkerCache = null;
|
||||
}
|
||||
return _installMarkerCache;
|
||||
}
|
||||
|
||||
// Test seams for the install-marker rung (the dev/source tree has no marker, so
|
||||
// the file read always bottoms out at 'claude' — these let tests exercise the
|
||||
// third precedence rung and reset the module-level cache between cases).
|
||||
function _setInstallRuntimeMarkerForTests(value: string | null): void {
|
||||
_installMarkerCache = value;
|
||||
}
|
||||
function _resetInstallRuntimeMarkerCacheForTests(): void {
|
||||
_installMarkerCache = undefined;
|
||||
}
|
||||
|
||||
// The runtime whose install is actually resolving, canonicalized so an alias or
|
||||
// case variant (e.g. "claude-code"/"Claude") cannot defeat the native-alias
|
||||
// check below (#2297 review). Precedence mirrors resolveRuntime()
|
||||
// (runtime-slash.cts): GSD_RUNTIME env → project config.runtime → per-install
|
||||
// .gsd-runtime marker → 'claude'.
|
||||
function resolveActiveRuntime(config: Record<string, unknown>): string {
|
||||
return resolveRuntimeNameFromCandidates(
|
||||
process.env['GSD_RUNTIME'],
|
||||
config['runtime'],
|
||||
readInstallRuntimeMarker(),
|
||||
) || 'claude';
|
||||
}
|
||||
|
||||
// Did the PROJECT's own config (root `.planning/config.json` or the active
|
||||
// workstream/project override) explicitly set resolve_model_ids to "omit"?
|
||||
// Project config takes precedence over the shared ~/.gsd/defaults.json (#2297
|
||||
// out-of-scope guard + #2517 finding #4): an explicit project "omit" is honored
|
||||
// regardless of runtime, whereas an "omit" that came only from the global
|
||||
// defaults is ignored by native-alias runtimes. Workstream/project-scope aware
|
||||
// via planningDir (mirrors loadConfig's precedence: workstream value wins over
|
||||
// root); a plain read avoids loadConfig's normalization side effects.
|
||||
function projectExplicitlySetsOmit(cwd: string): boolean {
|
||||
const wsDir = planningDir(cwd);
|
||||
const rootDir = path.join(cwd, '.planning');
|
||||
const layers = wsDir === rootDir ? [rootDir] : [wsDir, rootDir]; // workstream > root
|
||||
for (const dir of layers) {
|
||||
try {
|
||||
const parsed = JSON.parse(fs.readFileSync(path.join(dir, 'config.json'), 'utf8')) as Record<string, unknown>;
|
||||
const value = parsed?.['resolve_model_ids'];
|
||||
// First layer that sets the key wins (matches loadConfig's deep-merge
|
||||
// precedence). A layer that omits the key falls through to the next.
|
||||
if (value !== undefined) return value === 'omit';
|
||||
} catch {
|
||||
// Absent/unreadable layer — try the next.
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ─── Model alias resolution ───────────────────────────────────────────────────
|
||||
|
||||
interface TierEntryResolved {
|
||||
@@ -277,8 +371,18 @@ function resolveModelInternal(cwd: string, agentType: string): string {
|
||||
if (entry?.model) return entry.model;
|
||||
}
|
||||
|
||||
// 4. resolve_model_ids: "omit"
|
||||
if (config['resolve_model_ids'] === 'omit') {
|
||||
// 4. resolve_model_ids: "omit" — runtime-aware (#2297). Honor "omit" when the
|
||||
// PROJECT explicitly set it (user intent — project config wins, #2517 finding
|
||||
// #4) OR when the active runtime genuinely lacks native model aliases. Only a
|
||||
// native-alias runtime (Claude) ignores an "omit" that came solely from the
|
||||
// SHARED ~/.gsd/defaults.json — the #2297 poisoning fix — and falls through to
|
||||
// its tier aliases below. Active runtime: GSD_RUNTIME → config.runtime → the
|
||||
// per-install .gsd-runtime marker → 'claude' (canonicalized).
|
||||
// NOTE: a non-Claude runtime that HAS a populated runtime-tier map already
|
||||
// returned its own model id at step 3 above, before this gate — for those the
|
||||
// explicit-project-omit honoring here is moot (step 3 wins, by #2517 design).
|
||||
if (config['resolve_model_ids'] === 'omit'
|
||||
&& (projectExplicitlySetsOmit(cwd) || !RUNTIMES_WITH_NATIVE_ALIASES.has(resolveActiveRuntime(config)))) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -292,7 +396,11 @@ function resolveModelInternal(cwd: string, agentType: string): string {
|
||||
if (tier === 'inherit') return 'inherit';
|
||||
const alias = tier;
|
||||
|
||||
if (config['resolve_model_ids']) {
|
||||
// Only the explicit `true` opt-in materializes full model IDs (#1569). Guard
|
||||
// against the loose-truthy check catching a "omit" that a native-alias runtime
|
||||
// ignored above (#2297): "omit" must fall through to the tier ALIAS here, not
|
||||
// be materialized into a full ID Claude's Agent tool cannot spawn.
|
||||
if (config['resolve_model_ids'] === true) {
|
||||
return (MODEL_ALIAS_MAP as Record<string, string>)[alias!] || alias!;
|
||||
}
|
||||
|
||||
@@ -578,6 +686,8 @@ export = {
|
||||
resolveModelInternal,
|
||||
_resetModelPolicyWarningCacheForTests,
|
||||
_resetModelOverrideWarningCacheForTests,
|
||||
_setInstallRuntimeMarkerForTests,
|
||||
_resetInstallRuntimeMarkerCacheForTests,
|
||||
VALID_GRANULARITIES,
|
||||
resolveGranularityInternal,
|
||||
assertValidGranularityOverride,
|
||||
|
||||
411
tests/fix-2297-resolve-model-ids-runtime-scoping.test.cjs
Normal file
411
tests/fix-2297-resolve-model-ids-runtime-scoping.test.cjs
Normal file
@@ -0,0 +1,411 @@
|
||||
/**
|
||||
* Bug #2297 — `resolve_model_ids:"omit"` must be scoped to the ACTIVE runtime,
|
||||
* not applied blindly whenever it appears anywhere in the merged config.
|
||||
*
|
||||
* Root cause (pre-fix): the installer writes `resolve_model_ids:"omit"` into the
|
||||
* SHARED `~/.gsd/defaults.json` for every runtime that lacks native model
|
||||
* aliases (#1156). Because that file is machine-wide, installing a non-Claude
|
||||
* runtime (e.g. codex) on a box that also runs Claude poisoned Claude's
|
||||
* no-project resolution: Claude would see `resolve_model_ids:"omit"` in the
|
||||
* merged global defaults and return `''` instead of its tier aliases
|
||||
* (opus/sonnet/haiku), silently defeating Claude's adaptive tier distinction.
|
||||
*
|
||||
* Fix (`src/model-resolver.cts` `resolveModelInternal`): the `"omit"` branch
|
||||
* now returns `''` ONLY when either
|
||||
* (a) the PROJECT's own `.planning/config.json` explicitly sets
|
||||
* `resolve_model_ids:"omit"` (user intent — #2517 finding #4, unchanged), OR
|
||||
* (b) the ACTIVE runtime genuinely lacks native model aliases.
|
||||
* A native-alias runtime (currently only `claude`) IGNORES an `"omit"` that
|
||||
* came solely from the shared global defaults and falls through to its tier
|
||||
* aliases. Active-runtime precedence: `process.env.GSD_RUNTIME` -> `config.runtime`
|
||||
* -> per-install `.gsd-runtime` marker (absent in this dev/test tree, so the
|
||||
* chain always bottoms out at `'claude'`) -> `'claude'` (all canonicalized).
|
||||
*
|
||||
* IMPORTANT (empirically verified — see dispatch report): the global-defaults
|
||||
* merge path in `config-loader.cjs` (branch D: "no .planning/ at all") is ONLY
|
||||
* exercised when the project directory has NO `.planning/` directory whatsoever.
|
||||
* The moment a `.planning/` directory exists — even with an empty or absent
|
||||
* `config.json` inside it — the loader takes a different branch that does NOT
|
||||
* merge `~/.gsd/defaults.json` for these fields at all. So Group A below
|
||||
* (which specifically exercises the global-defaults poisoning fix) uses BARE
|
||||
* `fs.mkdtempSync` project dirs with no `.planning/` subdirectory. Group A #4,
|
||||
* Group B, and Group C all need a real per-project config, so those DO create
|
||||
* `.planning/config.json`.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { describe, test, beforeEach, afterEach } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
const {
|
||||
resolveModelInternal,
|
||||
_setInstallRuntimeMarkerForTests,
|
||||
_resetInstallRuntimeMarkerCacheForTests,
|
||||
} = require('../gsd-core/bin/lib/model-resolver.cjs');
|
||||
|
||||
// ─── HOME / GSD_HOME / GSD_RUNTIME isolation ────────────────────────────────
|
||||
// config-loader.cjs reads global defaults from
|
||||
// path.join(process.env.GSD_HOME || os.homedir(), '.gsd', 'defaults.json').
|
||||
// Isolate both HOME and GSD_HOME to a fresh tmpdir per test (so a developer's
|
||||
// real ~/.gsd/defaults.json never bleeds into assertions), and save/restore
|
||||
// GSD_RUNTIME since several tests set it directly to drive the active-runtime
|
||||
// chain (#2297's second precedence rung). Also save/restore GSD_WORKSTREAM and
|
||||
// GSD_PROJECT (#2297 correctness-review hermeticity gap): planningDir() reads
|
||||
// both directly from process.env when its ws/project params are omitted, so an
|
||||
// ambient GSD_WORKSTREAM/GSD_PROJECT in a developer's shell could silently
|
||||
// redirect projectExplicitlySetsOmit()'s config-file reads to the wrong layer.
|
||||
let _origHome;
|
||||
let _origUserProfile;
|
||||
let _origGsdHome;
|
||||
let _origGsdRuntime;
|
||||
let _origGsdWorkstream;
|
||||
let _origGsdProject;
|
||||
let _isolatedHome;
|
||||
|
||||
function isolateHome() {
|
||||
_origHome = process.env.HOME;
|
||||
_origUserProfile = process.env.USERPROFILE;
|
||||
_origGsdHome = process.env.GSD_HOME;
|
||||
_origGsdRuntime = process.env.GSD_RUNTIME;
|
||||
_origGsdWorkstream = process.env.GSD_WORKSTREAM;
|
||||
_origGsdProject = process.env.GSD_PROJECT;
|
||||
_isolatedHome = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2297-home-'));
|
||||
process.env.HOME = _isolatedHome;
|
||||
// Windows resolves the home dir from USERPROFILE, not HOME — set both so the
|
||||
// isolation holds cross-platform (local/require-userprofile-with-home).
|
||||
process.env.USERPROFILE = _isolatedHome;
|
||||
process.env.GSD_HOME = _isolatedHome;
|
||||
delete process.env.GSD_RUNTIME;
|
||||
delete process.env.GSD_WORKSTREAM;
|
||||
delete process.env.GSD_PROJECT;
|
||||
}
|
||||
|
||||
function restoreHome() {
|
||||
if (_origHome === undefined) delete process.env.HOME; else process.env.HOME = _origHome;
|
||||
if (_origUserProfile === undefined) delete process.env.USERPROFILE; else process.env.USERPROFILE = _origUserProfile;
|
||||
if (_origGsdHome === undefined) delete process.env.GSD_HOME; else process.env.GSD_HOME = _origGsdHome;
|
||||
if (_origGsdRuntime === undefined) delete process.env.GSD_RUNTIME; else process.env.GSD_RUNTIME = _origGsdRuntime;
|
||||
if (_origGsdWorkstream === undefined) delete process.env.GSD_WORKSTREAM; else process.env.GSD_WORKSTREAM = _origGsdWorkstream;
|
||||
if (_origGsdProject === undefined) delete process.env.GSD_PROJECT; else process.env.GSD_PROJECT = _origGsdProject;
|
||||
rmDir(_isolatedHome);
|
||||
_isolatedHome = null;
|
||||
}
|
||||
|
||||
function rmDir(dir) {
|
||||
if (typeof dir !== 'string' || dir.length === 0) return;
|
||||
// eslint-disable-next-line local/no-raw-rmsync-in-tests -- carries the same maxRetries/retryDelay budget as helpers.cleanup; used for both the isolated-home and bare project temp dirs
|
||||
fs.rmSync(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 50 });
|
||||
}
|
||||
|
||||
function writeGlobalDefaults(obj) {
|
||||
fs.mkdirSync(path.join(_isolatedHome, '.gsd'), { recursive: true });
|
||||
fs.writeFileSync(path.join(_isolatedHome, '.gsd', 'defaults.json'), JSON.stringify(obj, null, 2));
|
||||
}
|
||||
|
||||
// Bare project dir with NO .planning/ subdirectory — needed to exercise the
|
||||
// config-loader's global-defaults merge branch (see file header).
|
||||
function mkProjNoPlanning() {
|
||||
return fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2297-proj-noplan-'));
|
||||
}
|
||||
|
||||
// Project dir WITH a .planning/config.json — the normal "inside a project" path.
|
||||
function mkProjWithConfig(obj) {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2297-proj-'));
|
||||
fs.mkdirSync(path.join(dir, '.planning'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, '.planning', 'config.json'), JSON.stringify(obj, null, 2));
|
||||
return dir;
|
||||
}
|
||||
|
||||
// ─── Group A: GLOBAL-defaults "omit" is runtime-scoped (the #2297 fix) ─────
|
||||
describe('#2297: global-defaults resolve_model_ids:"omit" is scoped to the active runtime', () => {
|
||||
let projDir;
|
||||
beforeEach(() => { isolateHome(); projDir = null; });
|
||||
afterEach(() => { rmDir(projDir); restoreHome(); });
|
||||
|
||||
test('no runtime signal defaults to claude: executor and planner get distinct non-empty tier aliases (acceptance #3)', () => {
|
||||
// Global defaults poison the shared file with "omit" (simulating a
|
||||
// non-Claude runtime having been installed on this machine). With no
|
||||
// .planning/config.json (no project) and no GSD_RUNTIME, the active
|
||||
// runtime falls back to 'claude', which has native aliases and must
|
||||
// ignore the poisoned global "omit" — the adaptive tier distinction
|
||||
// between executor (sonnet) and planner (opus) must survive.
|
||||
writeGlobalDefaults({ resolve_model_ids: 'omit' });
|
||||
projDir = mkProjNoPlanning();
|
||||
|
||||
const executor = resolveModelInternal(projDir, 'gsd-executor');
|
||||
const planner = resolveModelInternal(projDir, 'gsd-planner');
|
||||
|
||||
assert.strictEqual(executor, 'sonnet');
|
||||
assert.strictEqual(planner, 'opus');
|
||||
assert.notStrictEqual(executor, '');
|
||||
assert.notStrictEqual(planner, '');
|
||||
assert.notStrictEqual(executor, planner);
|
||||
});
|
||||
|
||||
test('GSD_RUNTIME="claude" explicitly: executor still resolves to "sonnet" (claude ignores global omit)', () => {
|
||||
writeGlobalDefaults({ resolve_model_ids: 'omit' });
|
||||
projDir = mkProjNoPlanning();
|
||||
process.env.GSD_RUNTIME = 'claude';
|
||||
|
||||
assert.strictEqual(resolveModelInternal(projDir, 'gsd-executor'), 'sonnet');
|
||||
});
|
||||
|
||||
test('GSD_RUNTIME="codex": a non-alias runtime still honors the global omit (acceptance #4)', () => {
|
||||
writeGlobalDefaults({ resolve_model_ids: 'omit' });
|
||||
projDir = mkProjNoPlanning();
|
||||
process.env.GSD_RUNTIME = 'codex';
|
||||
|
||||
assert.strictEqual(resolveModelInternal(projDir, 'gsd-executor'), '');
|
||||
});
|
||||
|
||||
// #2297 correctness-review BLOCKER: resolveActiveRuntime() must canonicalize
|
||||
// its candidates via resolveRuntimeNameFromCandidates before checking
|
||||
// RUNTIMES_WITH_NATIVE_ALIASES, or an alias/case variant of "claude" would
|
||||
// fail the Set('claude').has() check and wrongly fall through to honoring the
|
||||
// poisoned global omit. These would FAIL against a non-canonicalizing resolver.
|
||||
test('GSD_RUNTIME="claude-code" (alias, not canonical "claude"): executor and planner still ignore the global omit', () => {
|
||||
writeGlobalDefaults({ resolve_model_ids: 'omit' });
|
||||
projDir = mkProjNoPlanning();
|
||||
process.env.GSD_RUNTIME = 'claude-code';
|
||||
|
||||
assert.strictEqual(resolveModelInternal(projDir, 'gsd-executor'), 'sonnet');
|
||||
assert.strictEqual(resolveModelInternal(projDir, 'gsd-planner'), 'opus');
|
||||
});
|
||||
|
||||
test('GSD_RUNTIME="Claude" (case variant): executor still resolves to "sonnet" (canonicalization is case-insensitive)', () => {
|
||||
writeGlobalDefaults({ resolve_model_ids: 'omit' });
|
||||
projDir = mkProjNoPlanning();
|
||||
process.env.GSD_RUNTIME = 'Claude';
|
||||
|
||||
assert.strictEqual(resolveModelInternal(projDir, 'gsd-executor'), 'sonnet');
|
||||
});
|
||||
|
||||
test('project config.runtime="codex" (no resolve_model_ids in project) takes precedence over GSD_RUNTIME/marker in the active-runtime chain', () => {
|
||||
// config.runtime is checked before GSD_RUNTIME / the install marker. This
|
||||
// scenario uses a REAL project (.planning/config.json present), so the
|
||||
// config-loader does NOT merge ~/.gsd/defaults.json for resolve_model_ids
|
||||
// at all here (see file header) — resolution instead reaches the #2517
|
||||
// runtime-tier path (step 3 in resolveModelInternal, which fires before
|
||||
// the omit gate) and returns codex's native sonnet-tier model id directly,
|
||||
// rather than the omit gate's ''. Verified empirically: the built resolver
|
||||
// returns 'gpt-5.6-terra', not ''. Assert it is non-empty and NOT a claude
|
||||
// alias, which is the property this test actually needs to guarantee
|
||||
// (config.runtime, not GSD_RUNTIME/env, drove the resolution).
|
||||
projDir = mkProjWithConfig({ runtime: 'codex' });
|
||||
writeGlobalDefaults({ resolve_model_ids: 'omit' }); // irrelevant: not merged when .planning/ exists
|
||||
|
||||
const result = resolveModelInternal(projDir, 'gsd-executor');
|
||||
assert.notStrictEqual(result, '');
|
||||
assert.ok(
|
||||
!['sonnet', 'opus', 'haiku'].includes(result),
|
||||
`expected a non-claude-alias result for config.runtime="codex", got ${JSON.stringify(result)}`
|
||||
);
|
||||
});
|
||||
|
||||
test('install-order independence (acceptance #1/#2): a global omit poisoned by a prior non-Claude install does not affect Claude resolution, and Claude retains its adaptive tier distinction', () => {
|
||||
// Resolution depends on the RESOLVING runtime (active runtime at call
|
||||
// time), not on install order — installing codex (or any non-alias
|
||||
// runtime) before/after Claude must never change what Claude itself
|
||||
// resolves to. Global omit present, no project, no runtime signal ->
|
||||
// default 'claude' -> tier aliases survive. Distinct from the first Group A
|
||||
// test above: this asserts install-order independence AND, specifically,
|
||||
// that executor/planner remain DIFFERENT tiers under the poisoned global
|
||||
// omit — i.e. install order never collapses Claude's adaptive tier
|
||||
// distinction into a single omitted value.
|
||||
writeGlobalDefaults({ resolve_model_ids: 'omit' });
|
||||
projDir = mkProjNoPlanning();
|
||||
|
||||
const executor = resolveModelInternal(projDir, 'gsd-executor');
|
||||
const planner = resolveModelInternal(projDir, 'gsd-planner');
|
||||
|
||||
assert.strictEqual(executor, 'sonnet');
|
||||
assert.strictEqual(planner, 'opus');
|
||||
assert.notStrictEqual(executor, planner, 'install-order poisoning must not collapse the adaptive tier distinction');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Group B: explicit PROJECT "omit" is still honored for EVERY runtime ───
|
||||
// (#2517 finding #4 — preserved, NOT changed by #2297.)
|
||||
describe('#2297: explicit project-level resolve_model_ids:"omit" is honored regardless of runtime', () => {
|
||||
let projDir;
|
||||
beforeEach(() => { isolateHome(); projDir = null; });
|
||||
afterEach(() => { rmDir(projDir); restoreHome(); });
|
||||
|
||||
test('no runtime set, explicit project omit -> "" even though the default runtime is claude', () => {
|
||||
projDir = mkProjWithConfig({ resolve_model_ids: 'omit' });
|
||||
|
||||
assert.strictEqual(resolveModelInternal(projDir, 'gsd-planner'), '');
|
||||
});
|
||||
|
||||
test('runtime:"claude" + explicit project omit -> "" (mirrors #2517 finding #4)', () => {
|
||||
projDir = mkProjWithConfig({ runtime: 'claude', resolve_model_ids: 'omit' });
|
||||
|
||||
assert.strictEqual(resolveModelInternal(projDir, 'gsd-planner'), '');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Group B2: projectExplicitlySetsOmit is workstream-scope aware (#2297) ──
|
||||
// The root .planning/config.json does NOT set resolve_model_ids, but the
|
||||
// ACTIVE workstream's own config.json does — projectExplicitlySetsOmit()
|
||||
// resolves via planningDir(cwd) (workstream layer wins over root, mirroring
|
||||
// loadConfig's precedence), so the workstream's explicit "omit" must still be
|
||||
// honored even though no global default and the default runtime (claude) would
|
||||
// otherwise have returned a tier alias.
|
||||
describe('#2297: explicit project-level "omit" is honored at the active-workstream config layer', () => {
|
||||
let projDir;
|
||||
let _origGsdWorkstreamForBlock;
|
||||
beforeEach(() => {
|
||||
isolateHome(); // clears GSD_WORKSTREAM/GSD_PROJECT as part of hermeticity
|
||||
projDir = null;
|
||||
_origGsdWorkstreamForBlock = process.env.GSD_WORKSTREAM;
|
||||
});
|
||||
afterEach(() => {
|
||||
if (_origGsdWorkstreamForBlock === undefined) delete process.env.GSD_WORKSTREAM;
|
||||
else process.env.GSD_WORKSTREAM = _origGsdWorkstreamForBlock;
|
||||
rmDir(projDir);
|
||||
restoreHome();
|
||||
});
|
||||
|
||||
test('root config has no resolve_model_ids, but the active workstream config sets "omit" -> "" despite default runtime claude', () => {
|
||||
const ws = 'ws-alpha';
|
||||
projDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2297-proj-ws-'));
|
||||
fs.mkdirSync(path.join(projDir, '.planning'), { recursive: true });
|
||||
// Root config exists but does NOT set resolve_model_ids at all.
|
||||
fs.writeFileSync(
|
||||
path.join(projDir, '.planning', 'config.json'),
|
||||
JSON.stringify({ model_profile: 'balanced' }, null, 2)
|
||||
);
|
||||
// The active workstream's own config explicitly sets "omit".
|
||||
const wsConfigDir = path.join(projDir, '.planning', 'workstreams', ws);
|
||||
fs.mkdirSync(wsConfigDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(wsConfigDir, 'config.json'),
|
||||
JSON.stringify({ resolve_model_ids: 'omit' }, null, 2)
|
||||
);
|
||||
process.env.GSD_WORKSTREAM = ws;
|
||||
|
||||
assert.strictEqual(resolveModelInternal(projDir, 'gsd-planner'), '');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Group C: explicit `true` still materializes full model ids (acceptance #5) ──
|
||||
describe('#2297: resolve_model_ids:true still materializes full Claude model ids', () => {
|
||||
let projDir;
|
||||
beforeEach(() => { isolateHome(); projDir = null; });
|
||||
afterEach(() => { rmDir(projDir); restoreHome(); });
|
||||
|
||||
test('resolve_model_ids:true + balanced profile -> full materialized claude-opus-4-8 id', () => {
|
||||
projDir = mkProjWithConfig({ resolve_model_ids: true, model_profile: 'balanced' });
|
||||
|
||||
assert.strictEqual(resolveModelInternal(projDir, 'gsd-planner'), 'claude-opus-4-8');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Group D: registry parity guard ─────────────────────────────────────────
|
||||
describe('#2297: capability-registry nativeModelAliases parity guard', () => {
|
||||
test('exactly the runtimes with hostBehaviors.nativeModelAliases:true match RUNTIMES_WITH_NATIVE_ALIASES ([\'claude\'])', () => {
|
||||
// The model-resolver hardcodes RUNTIMES_WITH_NATIVE_ALIASES = new Set(['claude'])
|
||||
// rather than reading the registry at runtime. This test keeps that
|
||||
// hardcoded set honest against the generated registry's actual contract:
|
||||
// registry.runtimes[id].runtime.hostBehaviors.nativeModelAliases.
|
||||
// If a future runtime gains nativeModelAliases:true, this fails loudly so
|
||||
// RUNTIMES_WITH_NATIVE_ALIASES in model-resolver.cts is updated in lockstep.
|
||||
const registry = require('../gsd-core/bin/lib/capability-registry.cjs');
|
||||
|
||||
const nativeAliasRuntimes = Object.keys(registry.runtimes)
|
||||
.filter((id) => registry.runtimes[id]?.runtime?.hostBehaviors?.nativeModelAliases === true)
|
||||
.sort();
|
||||
|
||||
assert.deepStrictEqual(nativeAliasRuntimes, ['claude']);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Group E: installer writes the per-install .gsd-runtime marker ─────────
|
||||
describe('#2297: installer emits the gsd-core/.gsd-runtime marker (fixture parity)', () => {
|
||||
test('claude and codex install-tree fixtures both list gsd-core/.gsd-runtime', () => {
|
||||
// These fixtures are flat JSON arrays of install-relative paths, generated
|
||||
// by running the real installer (tests/fixtures/install-tree/*.json). Their
|
||||
// presence here proves the installer actually emits the per-install marker
|
||||
// that resolveActiveRuntime()'s precedence chain falls back to.
|
||||
const claudeFixturePath = path.join(__dirname, 'fixtures', 'install-tree', 'claude.json');
|
||||
const codexFixturePath = path.join(__dirname, 'fixtures', 'install-tree', 'codex.json');
|
||||
|
||||
const claudeFixture = JSON.parse(fs.readFileSync(claudeFixturePath, 'utf8'));
|
||||
const codexFixture = JSON.parse(fs.readFileSync(codexFixturePath, 'utf8'));
|
||||
|
||||
assert.ok(Array.isArray(claudeFixture), 'expected claude.json fixture to be a flat array of paths');
|
||||
assert.ok(Array.isArray(codexFixture), 'expected codex.json fixture to be a flat array of paths');
|
||||
|
||||
assert.ok(
|
||||
claudeFixture.includes('gsd-core/.gsd-runtime'),
|
||||
'expected claude.json install-tree fixture to include gsd-core/.gsd-runtime'
|
||||
);
|
||||
assert.ok(
|
||||
codexFixture.includes('gsd-core/.gsd-runtime'),
|
||||
'expected codex.json install-tree fixture to include gsd-core/.gsd-runtime'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Group F: the install-marker precedence rung, driven directly (#2297) ──
|
||||
// Previously untested: with no GSD_RUNTIME and no project config.runtime, the
|
||||
// active runtime falls all the way through to the per-install .gsd-runtime
|
||||
// marker (third precedence rung). The dev/source tree has no real marker file,
|
||||
// so these tests drive that rung directly via the _setInstallRuntimeMarkerForTests
|
||||
// / _resetInstallRuntimeMarkerCacheForTests seams exported specifically for this
|
||||
// purpose (#2297 correctness-review gap).
|
||||
describe('#2297: install-marker precedence rung (GSD_RUNTIME and config.runtime both absent)', () => {
|
||||
let projDir;
|
||||
beforeEach(() => {
|
||||
isolateHome(); // also deletes GSD_RUNTIME
|
||||
projDir = null;
|
||||
// Belt-and-suspenders: the marker rung is only reached when GSD_RUNTIME and
|
||||
// config.runtime are both absent; isolateHome() already deletes GSD_RUNTIME.
|
||||
delete process.env.GSD_RUNTIME;
|
||||
});
|
||||
afterEach(() => {
|
||||
rmDir(projDir);
|
||||
restoreHome();
|
||||
// CRITICAL: reset the module-level marker cache after every case in this
|
||||
// block so a set value never leaks into a later case here, or into any
|
||||
// OTHER describe block in this file (readInstallRuntimeMarker() otherwise
|
||||
// memoizes the first value it sees for the lifetime of the process).
|
||||
_resetInstallRuntimeMarkerCacheForTests();
|
||||
});
|
||||
|
||||
test('marker="codex" (non-alias runtime): honors the poisoned global omit -> ""', () => {
|
||||
writeGlobalDefaults({ resolve_model_ids: 'omit' });
|
||||
projDir = mkProjNoPlanning();
|
||||
_setInstallRuntimeMarkerForTests('codex');
|
||||
|
||||
assert.strictEqual(resolveModelInternal(projDir, 'gsd-executor'), '');
|
||||
});
|
||||
|
||||
test('marker="claude": ignores the poisoned global omit -> "sonnet"', () => {
|
||||
writeGlobalDefaults({ resolve_model_ids: 'omit' });
|
||||
projDir = mkProjNoPlanning();
|
||||
_setInstallRuntimeMarkerForTests('claude');
|
||||
|
||||
assert.strictEqual(resolveModelInternal(projDir, 'gsd-executor'), 'sonnet');
|
||||
});
|
||||
|
||||
test('marker="claude-code" (alias): canonicalized to "claude" and still ignores the poisoned global omit -> "sonnet"', () => {
|
||||
writeGlobalDefaults({ resolve_model_ids: 'omit' });
|
||||
projDir = mkProjNoPlanning();
|
||||
_setInstallRuntimeMarkerForTests('claude-code');
|
||||
|
||||
assert.strictEqual(resolveModelInternal(projDir, 'gsd-executor'), 'sonnet');
|
||||
});
|
||||
|
||||
test('marker unset (null): falls through to the "claude" default and ignores the poisoned global omit -> "sonnet"', () => {
|
||||
writeGlobalDefaults({ resolve_model_ids: 'omit' });
|
||||
projDir = mkProjNoPlanning();
|
||||
_setInstallRuntimeMarkerForTests(null);
|
||||
|
||||
assert.strictEqual(resolveModelInternal(projDir, 'gsd-executor'), 'sonnet');
|
||||
});
|
||||
});
|
||||
@@ -35,6 +35,7 @@
|
||||
"agents/gsd-ui-researcher.md": "5f86de1decbd16d2",
|
||||
"agents/gsd-user-profiler.md": "25d65f6458454764",
|
||||
"agents/gsd-verifier.md": "224e8df2d8fd1d95",
|
||||
"gsd-core/.gsd-runtime": "54e851f5dd76ba94",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "ea841e2865248e74",
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
"commands/gsd-verify-work.md": "1cb62ea69b117acb",
|
||||
"commands/gsd-workspace.md": "dd1bc09d2b768e0b",
|
||||
"commands/gsd-workstreams.md": "52ab9c585d3a00f3",
|
||||
"gsd-core/.gsd-runtime": "ba737a9959aad238",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
"commands/gsd-verify-work.md": "246ac304c4dfde48",
|
||||
"commands/gsd-workspace.md": "5928e93b8ab475ac",
|
||||
"commands/gsd-workstreams.md": "52ab9c585d3a00f3",
|
||||
"gsd-core/.gsd-runtime": "98038b25280788a4",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"agents/gsd-ui-researcher.md": "9e3ac030767167e0",
|
||||
"agents/gsd-user-profiler.md": "003276f85792cfda",
|
||||
"agents/gsd-verifier.md": "2271174b5aa20e31",
|
||||
"gsd-core/.gsd-runtime": "98038b25280788a4",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"agents/gsd-ui-researcher.md": "1bb303f3a3c4dfc9",
|
||||
"agents/gsd-user-profiler.md": "622220df0654b6bf",
|
||||
"agents/gsd-verifier.md": "75912e9cadd83eb3",
|
||||
"gsd-core/.gsd-runtime": "98d4891544955482",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "476aa24e8c4f03cf",
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
"commands/gsd-verify-work.md": "6edbbb82a1f2aea7",
|
||||
"commands/gsd-workspace.md": "d765ff60cde657a6",
|
||||
"commands/gsd-workstreams.md": "884b6c8d648422c7",
|
||||
"gsd-core/.gsd-runtime": "04e9f0f6433d6d58",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
"agents/gsd-verifier.md": "4ac4b860e2504374",
|
||||
"agents/gsd-verifier.toml": "8ed9fb961409e894",
|
||||
"config.toml": "b5f627b42f060910",
|
||||
"gsd-core/.gsd-runtime": "243b0dc9b847e66c",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"agents/gsd-user-profiler.agent.md": "ae16a248e18dd42b",
|
||||
"agents/gsd-verifier.agent.md": "87a8e3238e838a39",
|
||||
"copilot-instructions.md": "1fb04111759f1645",
|
||||
"gsd-core/.gsd-runtime": "d0bcfb0a7f01f8c9",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "ea841e2865248e74",
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
"commands/gsd-verify-work.md": "86a42f859bc26dd6",
|
||||
"commands/gsd-workspace.md": "5c40114e6af87e3d",
|
||||
"commands/gsd-workstreams.md": "112660ceb663c750",
|
||||
"gsd-core/.gsd-runtime": "0e49decc490cf91d",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "2525f1ae8b086828",
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"agents/gsd-ui-researcher.md": "8799b6013e06ae49",
|
||||
"agents/gsd-user-profiler.md": "ca3bf75581f211a0",
|
||||
"agents/gsd-verifier.md": "82d3e9015ed04017",
|
||||
"gsd-core/.gsd-runtime": "d53ba9ce30ffd743",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "3a3409215044af9f",
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
"command/gsd-verify-work.md": "d07409c940a6ff00",
|
||||
"command/gsd-workspace.md": "9048133312f47fdc",
|
||||
"command/gsd-workstreams.md": "5e57eed1881c3891",
|
||||
"gsd-core/.gsd-runtime": "687db6a1b110b149",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
"agents/subagents/gsd-user-profiler.yaml": "645826a29d079159",
|
||||
"agents/subagents/gsd-verifier.md": "699f62763424ac68",
|
||||
"agents/subagents/gsd-verifier.yaml": "2d2bd6b37626f382",
|
||||
"gsd-core/.gsd-runtime": "cb556f5d5cee2779",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
"command/gsd-verify-work.md": "f23fff8e6d71f704",
|
||||
"command/gsd-workspace.md": "1e581bdb33bc8f55",
|
||||
"command/gsd-workstreams.md": "5e57eed1881c3891",
|
||||
"gsd-core/.gsd-runtime": "0d74cc0a7af2a416",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
|
||||
|
||||
1
tests/fixtures/golden-install-parity/pi.json
vendored
1
tests/fixtures/golden-install-parity/pi.json
vendored
@@ -2,6 +2,7 @@
|
||||
".gsd-profile": "0e716a5fef4e6dc1",
|
||||
".gsd/defaults.json": "560664b045e645cb",
|
||||
"extensions/gsd.cjs": "619cec0af9cfdadf",
|
||||
"gsd-core/.gsd-runtime": "94e95f0bb38f8e0f",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"agents/gsd-ui-researcher.md": "e3768304c1c77753",
|
||||
"agents/gsd-user-profiler.md": "13388a80dc302a66",
|
||||
"agents/gsd-verifier.md": "113d8dcff860b595",
|
||||
"gsd-core/.gsd-runtime": "2b626670f713516a",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "6e98d76e955e35a2",
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"agents/gsd-ui-researcher.md": "9e1a84a55a4cac99",
|
||||
"agents/gsd-user-profiler.md": "622220df0654b6bf",
|
||||
"agents/gsd-verifier.md": "99e95e235aacffbf",
|
||||
"gsd-core/.gsd-runtime": "c96852d55ce78a68",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "de4627dff103d527",
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"agents/gsd-ui-researcher.md": "c348aa3ff8412ecb",
|
||||
"agents/gsd-user-profiler.md": "622220df0654b6bf",
|
||||
"agents/gsd-verifier.md": "a07b00b9c5b7b338",
|
||||
"gsd-core/.gsd-runtime": "fe100af8906532d6",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "5636ca0b726871b2",
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
"commands/gsd-verify-work.md": "71aad09ecfd3f844",
|
||||
"commands/gsd-workspace.md": "611002e4941d5654",
|
||||
"commands/gsd-workstreams.md": "57a8ef0af8c085d7",
|
||||
"gsd-core/.gsd-runtime": "d0f601b915afd6bf",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
|
||||
|
||||
1
tests/fixtures/install-tree/antigravity.json
vendored
1
tests/fixtures/install-tree/antigravity.json
vendored
@@ -35,6 +35,7 @@
|
||||
"agents/gsd-ui-researcher.md",
|
||||
"agents/gsd-user-profiler.md",
|
||||
"agents/gsd-verifier.md",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs",
|
||||
|
||||
1
tests/fixtures/install-tree/augment.json
vendored
1
tests/fixtures/install-tree/augment.json
vendored
@@ -106,6 +106,7 @@
|
||||
"commands/gsd-verify-work.md",
|
||||
"commands/gsd-workspace.md",
|
||||
"commands/gsd-workstreams.md",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs",
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
"commands/gsd-verify-work.md",
|
||||
"commands/gsd-workspace.md",
|
||||
"commands/gsd-workstreams.md",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs",
|
||||
|
||||
1
tests/fixtures/install-tree/claude.json
vendored
1
tests/fixtures/install-tree/claude.json
vendored
@@ -34,6 +34,7 @@
|
||||
"agents/gsd-ui-researcher.md",
|
||||
"agents/gsd-user-profiler.md",
|
||||
"agents/gsd-verifier.md",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs",
|
||||
|
||||
1
tests/fixtures/install-tree/cline.json
vendored
1
tests/fixtures/install-tree/cline.json
vendored
@@ -38,6 +38,7 @@
|
||||
"agents/gsd-ui-researcher.md",
|
||||
"agents/gsd-user-profiler.md",
|
||||
"agents/gsd-verifier.md",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs",
|
||||
|
||||
1
tests/fixtures/install-tree/codebuddy.json
vendored
1
tests/fixtures/install-tree/codebuddy.json
vendored
@@ -106,6 +106,7 @@
|
||||
"commands/gsd-verify-work.md",
|
||||
"commands/gsd-workspace.md",
|
||||
"commands/gsd-workstreams.md",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs",
|
||||
|
||||
1
tests/fixtures/install-tree/codex.json
vendored
1
tests/fixtures/install-tree/codex.json
vendored
@@ -141,6 +141,7 @@
|
||||
"agents/gsd-verifier.md",
|
||||
"agents/gsd-verifier.toml",
|
||||
"config.toml",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs",
|
||||
|
||||
1
tests/fixtures/install-tree/copilot.json
vendored
1
tests/fixtures/install-tree/copilot.json
vendored
@@ -36,6 +36,7 @@
|
||||
"agents/gsd-user-profiler.agent.md",
|
||||
"agents/gsd-verifier.agent.md",
|
||||
"copilot-instructions.md",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs",
|
||||
|
||||
1
tests/fixtures/install-tree/cursor.json
vendored
1
tests/fixtures/install-tree/cursor.json
vendored
@@ -106,6 +106,7 @@
|
||||
"commands/gsd-verify-work.md",
|
||||
"commands/gsd-workspace.md",
|
||||
"commands/gsd-workstreams.md",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs",
|
||||
|
||||
1
tests/fixtures/install-tree/hermes.json
vendored
1
tests/fixtures/install-tree/hermes.json
vendored
@@ -35,6 +35,7 @@
|
||||
"agents/gsd-ui-researcher.md",
|
||||
"agents/gsd-user-profiler.md",
|
||||
"agents/gsd-verifier.md",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs",
|
||||
|
||||
1
tests/fixtures/install-tree/kilo.json
vendored
1
tests/fixtures/install-tree/kilo.json
vendored
@@ -106,6 +106,7 @@
|
||||
"command/gsd-verify-work.md",
|
||||
"command/gsd-workspace.md",
|
||||
"command/gsd-workstreams.md",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs",
|
||||
|
||||
1
tests/fixtures/install-tree/kimi.json
vendored
1
tests/fixtures/install-tree/kimi.json
vendored
@@ -99,6 +99,7 @@
|
||||
"agents/subagents/gsd-user-profiler.yaml",
|
||||
"agents/subagents/gsd-verifier.md",
|
||||
"agents/subagents/gsd-verifier.yaml",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs",
|
||||
|
||||
1
tests/fixtures/install-tree/opencode.json
vendored
1
tests/fixtures/install-tree/opencode.json
vendored
@@ -106,6 +106,7 @@
|
||||
"command/gsd-verify-work.md",
|
||||
"command/gsd-workspace.md",
|
||||
"command/gsd-workstreams.md",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs",
|
||||
|
||||
1
tests/fixtures/install-tree/pi.json
vendored
1
tests/fixtures/install-tree/pi.json
vendored
@@ -2,6 +2,7 @@
|
||||
".gsd-profile",
|
||||
".gsd/defaults.json",
|
||||
"extensions/gsd.cjs",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs",
|
||||
|
||||
1
tests/fixtures/install-tree/qwen.json
vendored
1
tests/fixtures/install-tree/qwen.json
vendored
@@ -35,6 +35,7 @@
|
||||
"agents/gsd-ui-researcher.md",
|
||||
"agents/gsd-user-profiler.md",
|
||||
"agents/gsd-verifier.md",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs",
|
||||
|
||||
1
tests/fixtures/install-tree/trae.json
vendored
1
tests/fixtures/install-tree/trae.json
vendored
@@ -35,6 +35,7 @@
|
||||
"agents/gsd-ui-researcher.md",
|
||||
"agents/gsd-user-profiler.md",
|
||||
"agents/gsd-verifier.md",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs",
|
||||
|
||||
1
tests/fixtures/install-tree/windsurf.json
vendored
1
tests/fixtures/install-tree/windsurf.json
vendored
@@ -35,6 +35,7 @@
|
||||
"agents/gsd-ui-researcher.md",
|
||||
"agents/gsd-user-profiler.md",
|
||||
"agents/gsd-verifier.md",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs",
|
||||
|
||||
1
tests/fixtures/install-tree/zcode.json
vendored
1
tests/fixtures/install-tree/zcode.json
vendored
@@ -106,6 +106,7 @@
|
||||
"commands/gsd-verify-work.md",
|
||||
"commands/gsd-workspace.md",
|
||||
"commands/gsd-workstreams.md",
|
||||
"gsd-core/.gsd-runtime",
|
||||
"gsd-core/VERSION",
|
||||
"gsd-core/bin/check-latest-version.cjs",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs",
|
||||
|
||||
Reference in New Issue
Block a user