feat(#1928): remove sunset Gemini CLI runtime, redirect to Antigravity (#1996)

* feat(#1928): remove sunset gemini cli runtime, redirect to antigravity

Google sunset Gemini CLI on 2026-06-18; Antigravity CLI is its official successor (already a first-class GSD runtime). Remove the gemini runtime from the enum (16->15), aliases, labels, config-home fragment, install path, converters (convertClaudeToGemini{Markdown,Toml,Agent}, convertSlashCommandsToGeminiMentions), capability descriptor, gemini-extension.json, RULESET.GEMINI.*, and the interactive menu (renumbered, no gap).

--gemini now prints an explicit deprecation notice citing the 2026-06-18 sunset and redirects to --antigravity (no silent alias, per the issue's Hyrum's-Law rejection). Antigravity is preserved throughout: its GEMINI.md contextFileName, .gemini/antigravity config home, the shared convertGeminiToolName/claudeToGeminiTools tool vocabulary, and the 'gemini' hookEvents dialect it declares. GEMINI.md retargeted as Antigravity's context file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(#1928): backfill changeset PR number (#1996)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(#1928): drop Gemini CLI from issue templates (review nit)

Removes the sunset Gemini CLI runtime from the two GitHub issue-template
runtime lists that the removal PR missed, per @davesienkowski's review nit:
- feature_request.yml: 'Applicable runtimes' checkbox (a user could otherwise
  request a feature for a runtime GSD no longer supports)
- bug_report.yml: 'Runtime' dropdown + the stale ~/.gemini/settings.json
  retrieval-help line

Leaves the post-removal templates fully consistent with the Antigravity redirect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-07-04 13:32:51 -04:00
committed by GitHub
parent 55604e9124
commit 8f2ebbe9bf
91 changed files with 872 additions and 4528 deletions

View File

@@ -12,7 +12,7 @@
* IN-PROCESS to install-engine's `installRuntimeArtifacts` /
* `uninstallRuntimeArtifacts` with the EXACT args passed through. Because
* the adapter calls the SAME engine functions `bin/install.js` uses, its
* output is byte-identical to today's install — the 16-runtime byte
* output is byte-identical to today's install — the 15-runtime byte
* identity is gated by `tests/golden-install-parity.test.cjs` (which
* exercises these engine functions end-to-end). Asserting the delegation
* link here proves the adapter never diverges from the reference path.
@@ -32,8 +32,8 @@ const registry = require('../gsd-core/bin/lib/capability-registry.cjs');
const RUNTIMES = Object.keys(registry.runtimes);
test('declarative adapter: kind === "declarative" + runtime echoed, for all 16 runtimes', () => {
assert.ok(RUNTIMES.length >= 16, `expected ≥16 runtimes in registry, got ${RUNTIMES.length}`);
test('declarative adapter: kind === "declarative" + runtime echoed, for all 15 runtimes', () => {
assert.ok(RUNTIMES.length >= 15, `expected ≥15 runtimes in registry, got ${RUNTIMES.length}`);
for (const r of RUNTIMES) {
const adapter = createDeclarativeAdapter({ runtime: r });
assert.strictEqual(adapter.kind, 'declarative', `${r}: kind must be 'declarative'`);

View File

@@ -25,7 +25,7 @@ const registry = require('../gsd-core/bin/lib/capability-registry.cjs');
const RUNTIMES = Object.keys(registry.runtimes);
test('imperative adapter: kind === "imperative" + runtime echoed + registry present, for all 16 runtimes', () => {
test('imperative adapter: kind === "imperative" + runtime echoed + registry present, for all 15 runtimes', () => {
for (const r of RUNTIMES) {
const adapter = createImperativeAdapter({ runtime: r });
assert.strictEqual(adapter.kind, 'imperative', `${r}: kind must be 'imperative'`);

View File

@@ -3189,7 +3189,7 @@ const {
const VALID_EXTENSION_EVENTS = capValidatorModule.VALID_EXTENSION_EVENTS;
const RUNTIME_IDS = [
'claude', 'codex', 'antigravity', 'gemini', 'cursor', 'opencode',
'claude', 'codex', 'antigravity', 'cursor', 'opencode',
'kilo', 'copilot', 'augment', 'trae', 'qwen', 'hermes',
'codebuddy', 'cline', 'kimi', 'windsurf',
];
@@ -3234,18 +3234,18 @@ function makeRuntimeCap(overrides) {
};
}
// ── 24a. All 16 runtime ids appear in the runtimes index ─────────────────────
// ── 24a. All 15 runtime ids appear in the runtimes index ─────────────────────
describe('ADR-1016 phase 5a: all 16 runtimes in registry index', () => {
describe('ADR-1016 phase 5a: all 15 runtimes in registry index', () => {
let registry;
test('loadAndValidate + buildRegistry produces runtimes index with 16 entries', () => {
test('loadAndValidate + buildRegistry produces runtimes index with 15 entries', () => {
const { capMap, errors } = loadAndValidate(new Set());
const hardErrors = errors.filter((e) => !e.includes('pending-migration'));
assert.deepEqual(hardErrors, [], 'Expected no hard errors: ' + JSON.stringify(hardErrors));
registry = buildRegistry(capMap);
const runtimeKeys = Object.keys(registry.runtimes).sort();
assert.strictEqual(runtimeKeys.length, 16, 'Expected 16 runtime entries, got: ' + runtimeKeys.join(', '));
assert.strictEqual(runtimeKeys.length, 15, 'Expected 15 runtime entries, got: ' + runtimeKeys.join(', '));
for (const id of RUNTIME_IDS) {
assert.ok(
Object.prototype.hasOwnProperty.call(registry.runtimes, id),
@@ -3451,10 +3451,10 @@ describe('ADR-1016 phase 5a: sample axis value assertions', () => {
}
});
test('tier-2 runtimes: gemini through windsurf have supportTier === 2', () => {
test('tier-2 runtimes: cursor through windsurf have supportTier === 2', () => {
const { capMap } = loadAndValidate(new Set());
const registry = buildRegistry(capMap);
const tier2 = ['gemini', 'cursor', 'opencode', 'kilo', 'copilot', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline', 'kimi', 'windsurf'];
const tier2 = ['cursor', 'opencode', 'kilo', 'copilot', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline', 'kimi', 'windsurf'];
for (const id of tier2) {
assert.strictEqual(
registry.runtimes[id].runtime.supportTier, 2,
@@ -4074,14 +4074,14 @@ describe('ADR-857 phase 5e: validateArtifactKindEntry — ConverterName enum (FA
assert.deepEqual(converterErrors, [], 'converter: null must always be accepted, got: ' + JSON.stringify(converterErrors));
});
// Parity: all 16 runtime descriptors must have converters in the valid set (or null)
test('all 16 real runtime descriptors have converters in VALID_CONVERTER_NAMES or null', () => {
// Parity: all 15 runtime descriptors must have converters in the valid set (or null)
test('all 15 real runtime descriptors have converters in VALID_CONVERTER_NAMES or null', () => {
const { capMap, errors } = loadAndValidate(new Set());
const hardErrors = errors.filter((e) => !e.includes('pending-migration'));
assert.deepEqual(hardErrors, [], 'Expected no hard errors from real capabilities, got: ' + JSON.stringify(hardErrors));
const runtimeIds = [
'claude', 'codex', 'antigravity', 'gemini', 'cursor', 'opencode',
'claude', 'codex', 'antigravity', 'cursor', 'opencode',
'kilo', 'copilot', 'augment', 'trae', 'qwen', 'hermes',
'codebuddy', 'cline', 'kimi', 'windsurf',
];
@@ -6283,7 +6283,7 @@ describe('fix-1628 capability validation: mempalace.wing (string)', () => {
* hand-kept REGISTRY const.
*
* This golden-master test pins the observable contract: the return shape and
* values must be identical to the pre-change behavior for all 16 runtimes.
* values must be identical to the pre-change behavior for all 15 runtimes.
* Purely behavioral — no source-grep.
*/
@@ -6303,7 +6303,6 @@ const {
const EXPECTED = [
{ runtime: 'claude', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'gemini', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'antigravity', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'augment', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'qwen', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
@@ -6321,7 +6320,7 @@ const EXPECTED = [
];
// ---------------------------------------------------------------------------
// Test 1: Golden master — all 16 runtimes resolve to expected values
// Test 1: Golden master — all 15 runtimes resolve to expected values
// ---------------------------------------------------------------------------
describe('enh-1055 descriptor-drive: resolveRuntimeConfigIntent golden master', () => {
@@ -6361,21 +6360,21 @@ describe('enh-1055 descriptor-drive: unknown runtime throws TypeError', () => {
});
// ---------------------------------------------------------------------------
// Test 4: ALLOWED_CONFIG_RUNTIMES contains all 16 expected runtimes
// Test 4: ALLOWED_CONFIG_RUNTIMES contains all 15 expected runtimes
// ---------------------------------------------------------------------------
describe('enh-1055 descriptor-drive: ALLOWED_CONFIG_RUNTIMES completeness', () => {
const EXPECTED_16 = new Set([
'claude', 'gemini', 'antigravity', 'augment', 'qwen', 'hermes', 'codebuddy',
const EXPECTED_15 = new Set([
'claude', 'antigravity', 'augment', 'qwen', 'hermes', 'codebuddy',
'opencode', 'kilo', 'codex', 'copilot', 'cline', 'cursor', 'windsurf', 'trae', 'kimi',
]);
test('contains exactly the 16 expected runtimes', () => {
assert.deepStrictEqual(new Set(ALLOWED_CONFIG_RUNTIMES), EXPECTED_16);
test('contains exactly the 15 expected runtimes', () => {
assert.deepStrictEqual(new Set(ALLOWED_CONFIG_RUNTIMES), EXPECTED_15);
});
test('has exactly 16 entries', () => {
assert.strictEqual([...ALLOWED_CONFIG_RUNTIMES].length, 16);
test('has exactly 15 entries', () => {
assert.strictEqual([...ALLOWED_CONFIG_RUNTIMES].length, 15);
});
});

View File

@@ -680,7 +680,7 @@ describe('installCodexConfig sandboxTier threading seam', () => {
// same process. The throw path is verified at the type level (tsc) and by the
// build passing, and the happy-path coverage (claude.sandboxTier === 'none' and
// codex.sandboxTier === 'codex-agent-sandbox') confirms the real registry has
// valid values for all 16 runtimes.
// valid values for all 15 runtimes.
// ─── CODEX_AGENT_SANDBOX mapping ────────────────────────────────────────────────

View File

@@ -70,7 +70,7 @@ describe('getDirName (Copilot)', () => {
test('does not break existing runtimes', () => {
assert.strictEqual(getDirName('claude'), '.claude');
assert.strictEqual(getDirName('opencode'), '.opencode');
assert.strictEqual(getDirName('gemini'), '.gemini');
assert.strictEqual(getDirName('antigravity'), '.agents');
assert.strictEqual(getDirName('kilo'), '.kilo');
assert.strictEqual(getDirName('codex'), '.codex');
});
@@ -172,7 +172,7 @@ describe('getConfigDirFromHome (Copilot)', () => {
test('does not break existing runtimes', () => {
assert.strictEqual(getConfigDirFromHome('opencode', true), "'.config', 'opencode'");
assert.strictEqual(getConfigDirFromHome('claude', true), "'.claude'");
assert.strictEqual(getConfigDirFromHome('gemini', true), "'.gemini'");
assert.strictEqual(getConfigDirFromHome('cursor', true), "'.cursor'");
assert.strictEqual(getConfigDirFromHome('kilo', true), "'.config', 'kilo'");
assert.strictEqual(getConfigDirFromHome('codex', true), "'.codex'");
});

View File

@@ -74,9 +74,7 @@ test('a real Antigravity install emits a gsd command/skill surface (invocable)',
const { describe: __foldDescribe } = require('node:test');
__foldDescribe("folded:bug-3608-antigravity-update-runtime-classification (consolidation epic #1969 B3 #1972)", () => {
/**
* Bug #3608: /gsd:update must model Antigravity as a first-class runtime, so an
* Antigravity install (~/.gemini/antigravity*) is not misclassified as base
* Gemini.
* Bug #3608: /gsd:update must model Antigravity as a first-class runtime.
*
* The installer (bin/install.js) and SDK already treat Antigravity as a distinct
* runtime with its own config dirs, env var (ANTIGRAVITY_CONFIG_DIR), and CLI
@@ -87,12 +85,14 @@ test('a real Antigravity install emits a gsd command/skill surface (invocable)',
* `gsd-core/bin/lib/update-context.cjs` (resolveUpdateContext). The
* antigravity-first-class contract now lives there as data + behavior, so this
* test asserts it on the projection. The only piece still authored in update.md
* is the execution_context path classification (prose the agent applies), which
* this test still checks for antigravity-before-gemini ordering.
* is the execution_context path classification (prose the agent applies).
*
* Order matters: every probe list / env ladder that contains a Gemini entry
* MUST place the more-specific Antigravity entry first, else an install with
* both signals present falls through to gemini.
* #1928: Gemini CLI was sunset by Google on 2026-06-18 and the `gemini` runtime
* was removed from GSD entirely (Antigravity CLI is the successor). Antigravity
* still resolves under the shared `.gemini/antigravity*` dirs (it runs on the
* Gemini 3 backend), so those probe entries and the ANTIGRAVITY_CONFIG_DIR env
* var remain — but there is no longer a competing bare-`gemini` runtime to
* disambiguate against.
*/
'use strict';
@@ -120,13 +120,12 @@ function firstIndex(arr, token) {
}
describe('bug #3608 / #498: update-context models Antigravity as a first-class runtime', () => {
test('RUNTIME_DIRS lists antigravity before gemini', () => {
test('RUNTIME_DIRS lists antigravity (no gemini runtime remains — #1928)', () => {
const order = runtimeOrder();
const antIdx = firstIndex(order, 'antigravity');
const gemIdx = firstIndex(order, 'gemini');
assert.notStrictEqual(antIdx, -1, 'RUNTIME_DIRS missing antigravity');
assert.notStrictEqual(gemIdx, -1, 'RUNTIME_DIRS missing gemini');
assert.ok(antIdx < gemIdx, `antigravity (@${antIdx}) must precede gemini (@${gemIdx}) — first match wins`);
assert.strictEqual(firstIndex(order, 'gemini'), -1,
'gemini runtime was removed (#1928) — RUNTIME_DIRS must not list it');
});
test('RUNTIME_DIRS includes antigravity 2.x (ide/cli) + legacy dirs', () => {
@@ -134,28 +133,27 @@ describe('bug #3608 / #498: update-context models Antigravity as a first-class r
assert.ok(dirs.includes('.gemini/antigravity-ide'), 'missing .gemini/antigravity-ide');
assert.ok(dirs.includes('.gemini/antigravity-cli'), 'missing .gemini/antigravity-cli');
assert.ok(dirs.includes('.gemini/antigravity'), 'missing legacy .gemini/antigravity fallback');
// All antigravity dirs precede the .gemini probe.
// The bare .gemini probe (former gemini-runtime dir) no longer exists (#1928).
const order = RUNTIME_DIRS.map(([, d]) => d);
const gemIdx = order.indexOf('.gemini');
for (const d of dirs) {
assert.ok(order.indexOf(d) < gemIdx, `${d} must precede .gemini in the probe order`);
}
assert.strictEqual(order.indexOf('.gemini'), -1,
'bare .gemini probe entry was removed with the gemini runtime (#1928)');
});
test('env inference recognizes ANTIGRAVITY_CONFIG_DIR before GEMINI_CONFIG_DIR', () => {
test('env inference recognizes ANTIGRAVITY_CONFIG_DIR', () => {
const rt = inferPreferredRuntime({
fs: { exists: () => false },
env: { ANTIGRAVITY_CONFIG_DIR: '/x', GEMINI_CONFIG_DIR: '/y' },
env: { ANTIGRAVITY_CONFIG_DIR: '/x' },
preferredConfigDir: '',
});
assert.equal(rt, 'antigravity', 'both env vars set must resolve to antigravity, not gemini');
assert.equal(rt, 'antigravity');
});
test('envRuntimeDirs emits an antigravity entry (before gemini) when ANTIGRAVITY_CONFIG_DIR is set', () => {
const entries = envRuntimeDirs({ env: { ANTIGRAVITY_CONFIG_DIR: '/x/ag', GEMINI_CONFIG_DIR: '/x/gem' }, home: '/home/u' });
test('envRuntimeDirs emits an antigravity entry when ANTIGRAVITY_CONFIG_DIR is set', () => {
const entries = envRuntimeDirs({ env: { ANTIGRAVITY_CONFIG_DIR: '/x/ag' }, home: '/home/u' });
const order = entries.map(([rt]) => rt);
assert.ok(order.includes('antigravity'), 'expected an antigravity env candidate');
assert.ok(order.indexOf('antigravity') < order.indexOf('gemini'), 'antigravity env candidate must precede gemini');
assert.strictEqual(order.indexOf('gemini'), -1,
'GEMINI_CONFIG_DIR is no longer recognized (#1928) — no gemini env candidate should ever appear');
});
test('behavioral: an Antigravity install resolves to runtime "antigravity", not "gemini"', () => {
@@ -175,13 +173,12 @@ describe('bug #3608 / #498: update-context models Antigravity as a first-class r
assert.equal(normKey(r.gsdDir), normKey(agDir));
});
test('update.md execution_context classification still lists antigravity paths before /.gemini/', () => {
test('update.md execution_context classification lists antigravity paths and no longer classifies bare /.gemini/ as gemini (#1928)', () => {
const content = fs.readFileSync(UPDATE_MD, 'utf-8');
const antIde = content.indexOf('/.gemini/antigravity-ide/');
const gemBare = content.indexOf('`/.gemini/` -> `gemini`');
assert.notStrictEqual(antIde, -1, 'update.md must document the antigravity-ide execution_context path');
assert.notStrictEqual(gemBare, -1, 'update.md must document the bare /.gemini/ -> gemini classification');
assert.ok(antIde < gemBare, 'antigravity path classification must precede the bare /.gemini/ rule');
assert.strictEqual(content.indexOf('`/.gemini/` -> `gemini`'), -1,
'gemini runtime was removed (#1928) — update.md must not classify bare /.gemini/ as gemini');
});
});
});

View File

@@ -41,8 +41,8 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "9e74bf9cc8aa3774",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "8bc541aabc2e143c",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
@@ -300,7 +300,7 @@
"gsd-core/workflows/ui-review.md": "816b2bde136157f9",
"gsd-core/workflows/ultraplan-phase.md": "2404a01cb2e0c450",
"gsd-core/workflows/undo.md": "6ab639d1fc7e0721",
"gsd-core/workflows/update.md": "dc93f366e2156e37",
"gsd-core/workflows/update.md": "2c58df5e21c41c31",
"gsd-core/workflows/validate-phase.md": "5bac28c71d21c740",
"gsd-core/workflows/verify-phase.md": "e7059c04c816e62d",
"gsd-core/workflows/verify-work.md": "d1f5cfc8e22b7ce7",

View File

@@ -111,8 +111,8 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "9e74bf9cc8aa3774",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
@@ -370,7 +370,7 @@
"gsd-core/workflows/ui-review.md": "1ad3654435000881",
"gsd-core/workflows/ultraplan-phase.md": "66c17ad2e555e262",
"gsd-core/workflows/undo.md": "96d2775f008b3a85",
"gsd-core/workflows/update.md": "231e7c305b40c417",
"gsd-core/workflows/update.md": "fd160e13f8b7e83c",
"gsd-core/workflows/validate-phase.md": "50f37b705b6e44fb",
"gsd-core/workflows/verify-phase.md": "7579af6cd757f644",
"gsd-core/workflows/verify-work.md": "d77ace0fc4f33ff7",

View File

@@ -40,8 +40,8 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "9e74bf9cc8aa3774",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
@@ -299,7 +299,7 @@
"gsd-core/workflows/ui-review.md": "51945fda8e931f99",
"gsd-core/workflows/ultraplan-phase.md": "92dbc48e9a7162aa",
"gsd-core/workflows/undo.md": "791e0bf96d9a057f",
"gsd-core/workflows/update.md": "0b389258dcc09332",
"gsd-core/workflows/update.md": "f9e7d8a760d0d3c8",
"gsd-core/workflows/validate-phase.md": "2aa540f2c2479501",
"gsd-core/workflows/verify-phase.md": "15a999f82868ad29",
"gsd-core/workflows/verify-work.md": "ea61f9e88a44b68b",

View File

@@ -44,8 +44,8 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "9e74bf9cc8aa3774",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
@@ -303,7 +303,7 @@
"gsd-core/workflows/ui-review.md": "0af83311f5e41f48",
"gsd-core/workflows/ultraplan-phase.md": "b4e64685b40bce09",
"gsd-core/workflows/undo.md": "96d2775f008b3a85",
"gsd-core/workflows/update.md": "9cad8a8f4baff922",
"gsd-core/workflows/update.md": "165beec33490bd28",
"gsd-core/workflows/validate-phase.md": "54687a0f2a562fc4",
"gsd-core/workflows/verify-phase.md": "5caca0023bc88a9a",
"gsd-core/workflows/verify-work.md": "a327274299317cb1",

View File

@@ -111,8 +111,8 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "9e74bf9cc8aa3774",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
@@ -370,7 +370,7 @@
"gsd-core/workflows/ui-review.md": "1ad3654435000881",
"gsd-core/workflows/ultraplan-phase.md": "66c17ad2e555e262",
"gsd-core/workflows/undo.md": "96d2775f008b3a85",
"gsd-core/workflows/update.md": "6a593863d1b0a287",
"gsd-core/workflows/update.md": "5ff1f77222977648",
"gsd-core/workflows/validate-phase.md": "50f37b705b6e44fb",
"gsd-core/workflows/verify-phase.md": "7579af6cd757f644",
"gsd-core/workflows/verify-work.md": "d77ace0fc4f33ff7",

View File

@@ -76,8 +76,8 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "9e74bf9cc8aa3774",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
@@ -335,7 +335,7 @@
"gsd-core/workflows/ui-review.md": "a9b2cbba80482cd8",
"gsd-core/workflows/ultraplan-phase.md": "18bbb3b5fcd30f0c",
"gsd-core/workflows/undo.md": "5ff7d63b0a2f46d5",
"gsd-core/workflows/update.md": "4166fd3e3ca72a7b",
"gsd-core/workflows/update.md": "5c35c0ec0f462ea6",
"gsd-core/workflows/validate-phase.md": "4e94708c9ca15d70",
"gsd-core/workflows/verify-phase.md": "59bb0b12ebb47f5e",
"gsd-core/workflows/verify-work.md": "c5cf0c2fc422fa4e",

View File

@@ -42,8 +42,8 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "9e74bf9cc8aa3774",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "10226e9512dd44bf",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
@@ -301,7 +301,7 @@
"gsd-core/workflows/ui-review.md": "731bca05f9770a86",
"gsd-core/workflows/ultraplan-phase.md": "2dda203295895bc1",
"gsd-core/workflows/undo.md": "ba1ef7aa80bef6bd",
"gsd-core/workflows/update.md": "712ab18a9b7c14f5",
"gsd-core/workflows/update.md": "f444a7cfcd246cfb",
"gsd-core/workflows/validate-phase.md": "f923eac9442b6053",
"gsd-core/workflows/verify-phase.md": "eea9b642393b6b90",
"gsd-core/workflows/verify-work.md": "a5bcec82bfc2da53",

View File

@@ -111,8 +111,8 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "9e74bf9cc8aa3774",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
@@ -370,7 +370,7 @@
"gsd-core/workflows/ui-review.md": "6b16a7f7783be471",
"gsd-core/workflows/ultraplan-phase.md": "752df97bb6c14ce2",
"gsd-core/workflows/undo.md": "18dec684fb1076f9",
"gsd-core/workflows/update.md": "a27dcfd2814bf2b1",
"gsd-core/workflows/update.md": "23e294ba707c3580",
"gsd-core/workflows/validate-phase.md": "f513c28c01a44cb7",
"gsd-core/workflows/verify-phase.md": "15a999f82868ad29",
"gsd-core/workflows/verify-work.md": "531a00b7f46d08db",

View File

@@ -1,412 +0,0 @@
{
".gsd-profile": "0e716a5fef4e6dc1",
".gsd/defaults.json": "560664b045e645cb",
"agents/gsd-advisor-researcher.md": "44245524f341f64d",
"agents/gsd-ai-researcher.md": "abb231823ae8d226",
"agents/gsd-assumptions-analyzer.md": "395f29ee7b10fbb9",
"agents/gsd-code-fixer.md": "afc266fabeba96b7",
"agents/gsd-code-reviewer.md": "64de9ad808aa1880",
"agents/gsd-codebase-mapper.md": "5f1cd143513ce959",
"agents/gsd-debug-session-manager.md": "e79b81434bd8b6ee",
"agents/gsd-debugger.md": "04ce5f7d8202698f",
"agents/gsd-doc-classifier.md": "e2774e90fbf7d637",
"agents/gsd-doc-synthesizer.md": "f9350f45ec29bd78",
"agents/gsd-doc-verifier.md": "2ec24cbe0582bbe6",
"agents/gsd-doc-writer.md": "1ad9b8ac7f2e96de",
"agents/gsd-domain-researcher.md": "ba1b6b24e2f8a952",
"agents/gsd-eval-auditor.md": "115214236cd73d96",
"agents/gsd-eval-planner.md": "ad757b253472269e",
"agents/gsd-executor.md": "5d9338bcfe37fc6a",
"agents/gsd-framework-selector.md": "1254f9adc2e95e4b",
"agents/gsd-integration-checker.md": "e337990c0a4117b1",
"agents/gsd-intel-updater.md": "3111fd1028d155b5",
"agents/gsd-mempalace-curator.md": "ab78cc2c2e75c992",
"agents/gsd-nyquist-auditor.md": "b7bbcab5929559b3",
"agents/gsd-pattern-mapper.md": "43a2ba37b24acdb2",
"agents/gsd-phase-researcher.md": "2f6b14864918df18",
"agents/gsd-plan-checker.md": "1fba4fc736554af7",
"agents/gsd-planner.md": "07472a59265e30e1",
"agents/gsd-project-researcher.md": "fc319b62dc1d843d",
"agents/gsd-research-synthesizer.md": "ac7cc32c30dcb6f6",
"agents/gsd-roadmapper.md": "fb1cd15a1ae9496e",
"agents/gsd-security-auditor.md": "eed800c19f595c2c",
"agents/gsd-ui-auditor.md": "efdad9ad8bdc9216",
"agents/gsd-ui-checker.md": "7fbf366a9a423588",
"agents/gsd-ui-researcher.md": "95a245ab29ecd158",
"agents/gsd-user-profiler.md": "d6cb5430d841cea6",
"agents/gsd-verifier.md": "6754b501e3f5f638",
"commands/gsd/add-tests.toml": "297ba4d4c285fd99",
"commands/gsd/ai-integration-phase.toml": "411ba33b9a7d9e78",
"commands/gsd/audit-fix.toml": "f4a198a455f668a9",
"commands/gsd/audit-milestone.toml": "18842eaf6ed1807a",
"commands/gsd/audit-uat.toml": "c0239d3bd04986bf",
"commands/gsd/autonomous.toml": "4f4b576a7115d8b1",
"commands/gsd/capture.toml": "c1c5793b86c8f48b",
"commands/gsd/cleanup.toml": "8e6b1c74639e50f4",
"commands/gsd/code-review.toml": "ca94ab57acdc19e6",
"commands/gsd/complete-milestone.toml": "bb7089791fcae6f4",
"commands/gsd/config.toml": "eeae6354c86627db",
"commands/gsd/debug.toml": "55b7123102654b05",
"commands/gsd/discuss-phase.toml": "894826ab56cf5607",
"commands/gsd/docs-update.toml": "47e230a6ccc690e0",
"commands/gsd/eval-review.toml": "924a24261ca3fbe2",
"commands/gsd/execute-phase.toml": "04e76e06df6d726c",
"commands/gsd/explore.toml": "7f39ab33875ab2a4",
"commands/gsd/extract-learnings.toml": "f49b286ed791997f",
"commands/gsd/fast.toml": "41874bcaee134a50",
"commands/gsd/forensics.toml": "fc2286dc36a2be56",
"commands/gsd/graphify.toml": "feac28a2841a0425",
"commands/gsd/health.toml": "b1cbaa78f2a6d289",
"commands/gsd/help.toml": "c320e77702d95d87",
"commands/gsd/import.toml": "be6c5a8f9388e8cb",
"commands/gsd/inbox.toml": "63d2fddec20d38f1",
"commands/gsd/ingest-docs.toml": "cdc0a5130f64aaa4",
"commands/gsd/manager.toml": "e4164b936405c06b",
"commands/gsd/map-codebase.toml": "3f149a36061e4eea",
"commands/gsd/mempalace-capture.toml": "b6a089205e96820c",
"commands/gsd/mempalace-recall.toml": "18359171387de00b",
"commands/gsd/milestone-summary.toml": "03de0ae6e4d23ebc",
"commands/gsd/mvp-phase.toml": "fbaa7f45ec213bb5",
"commands/gsd/new-milestone.toml": "ca13145a1b414535",
"commands/gsd/new-project.toml": "ed9c9d44e2bad612",
"commands/gsd/next.toml": "f434ec4e11d96141",
"commands/gsd/ns-context.toml": "fc29ba4b8e8cd6ca",
"commands/gsd/ns-ideate.toml": "dd813a2d4dd91ade",
"commands/gsd/ns-manage.toml": "2fdb297bc681288e",
"commands/gsd/ns-project.toml": "81146d2a189556bc",
"commands/gsd/ns-review.toml": "2e48bcc682bb0c6a",
"commands/gsd/ns-workflow.toml": "8968e5b15969cb1f",
"commands/gsd/pause-work.toml": "11899f0727c0ffe9",
"commands/gsd/phase.toml": "e062b8c130d3e954",
"commands/gsd/plan-phase.toml": "7d1c635bbdbd1f8b",
"commands/gsd/plan-review-convergence.toml": "aa71f85915b8b0de",
"commands/gsd/pr-branch.toml": "3ac7f2bf26c5acda",
"commands/gsd/profile-user.toml": "7192730d9bf899f5",
"commands/gsd/progress.toml": "bdc9ffaf4eee0b2f",
"commands/gsd/quick.toml": "b1f6d47488560def",
"commands/gsd/resume-work.toml": "5d1e36d643573d4c",
"commands/gsd/review-backlog.toml": "19461ca74224422c",
"commands/gsd/review.toml": "8ce97adf16aec351",
"commands/gsd/secure-phase.toml": "bbf0dc4d648f11fa",
"commands/gsd/settings.toml": "5369a6fbdce65ea9",
"commands/gsd/ship.toml": "fa360f1f63c8adec",
"commands/gsd/sketch.toml": "c0892fd97a9ed127",
"commands/gsd/spec-phase.toml": "fd8eed82220ae8d3",
"commands/gsd/spike.toml": "c9e37c9efa49cfbf",
"commands/gsd/stats.toml": "33ac1de9cfdb5e06",
"commands/gsd/surface.toml": "64e1d035854eb75d",
"commands/gsd/thread.toml": "ae86f2f38d8e1697",
"commands/gsd/ui-phase.toml": "300f0618f5756083",
"commands/gsd/ui-review.toml": "9a255ddc3ff1bd6e",
"commands/gsd/ultraplan-phase.toml": "c8c09e0313da7bfd",
"commands/gsd/undo.toml": "eea7699033036219",
"commands/gsd/update.toml": "20717a113502c7ac",
"commands/gsd/validate-phase.toml": "175c1e4ce78b7274",
"commands/gsd/verify-work.toml": "58ec25cb1f5d5434",
"commands/gsd/workspace.toml": "7f1658bb61c9743d",
"commands/gsd/workstreams.toml": "95f0c349b808a84c",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/gsd-tools.cjs": "e504d3b82da834ae",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "9e74bf9cc8aa3774",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
"gsd-core/contexts/review.md": "dc578fdd74bbea11",
"gsd-core/references/agent-contracts.md": "ff65e633c656c0d2",
"gsd-core/references/agent-skills-bootstrap.md": "5ab875054b1adda9",
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
"gsd-core/references/ai-frameworks.md": "f827de93dde124eb",
"gsd-core/references/artifact-types.md": "a6d2e1f9453ffbf5",
"gsd-core/references/autonomous-smart-discuss.md": "273b371c5751f35a",
"gsd-core/references/checkpoints.md": "6aa620c6ca38bdf0",
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
"gsd-core/references/context-budget.md": "f1ce57bf418824af",
"gsd-core/references/continuation-format.md": "a57656e8b4206ed4",
"gsd-core/references/debugger-philosophy.md": "0466f95a3d6bfcd3",
"gsd-core/references/decimal-phase-calculation.md": "46b5ba045852c474",
"gsd-core/references/doc-conflict-engine.md": "67d019d23e17f934",
"gsd-core/references/domain-probes.md": "62d23ed1992c48a9",
"gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json": "72d1e29cedc854ec",
"gsd-core/references/edge-probe-fixtures/01-round-half-even/requirements.json": "fbc1b355d8625eeb",
"gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json": "fad67dcc8294f6da",
"gsd-core/references/edge-probe-fixtures/02-merge-intervals/requirements.json": "30a78ee9ce3473ea",
"gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json": "66dd60957fee45f0",
"gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/requirements.json": "47fca61f076835fa",
"gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json": "72d1e29cedc854ec",
"gsd-core/references/edge-probe-fixtures/04-money-rounding/requirements.json": "80f04f5c04fb24cf",
"gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json": "fad67dcc8294f6da",
"gsd-core/references/edge-probe-fixtures/05-list-dedupe/requirements.json": "d38147adb0e5b342",
"gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json": "bc552c01939bf4f8",
"gsd-core/references/edge-probe-fixtures/06-resolved-mixed/requirements.json": "30a78ee9ce3473ea",
"gsd-core/references/edge-probe-fixtures/06-resolved-mixed/resolutions.json": "688ec62c13e08afe",
"gsd-core/references/edge-probe.md": "5687eba25a078561",
"gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc",
"gsd-core/references/execute-phase-between-wave-reset.md": "3ad96ca0f7fee37e",
"gsd-core/references/execute-phase-context-guard.md": "982006c1f3364242",
"gsd-core/references/execute-phase-wave-guard.md": "de9ac22cead4cfd8",
"gsd-core/references/executor-examples.md": "ba59243ed45c8ab1",
"gsd-core/references/few-shot-examples/plan-checker.md": "2574808188ac9de4",
"gsd-core/references/few-shot-examples/verifier.md": "5badee4560b14ae8",
"gsd-core/references/gate-prompts.md": "e69f5993ab944d80",
"gsd-core/references/gates.md": "bd79c7f90c8cb8d7",
"gsd-core/references/git-integration.md": "77bf9dff38b2c9d4",
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
"gsd-core/references/model-profile-resolution.md": "f32bb05102839767",
"gsd-core/references/model-profiles.md": "8484ad9799b7f680",
"gsd-core/references/mvp-concepts.md": "72f7e8b1f8ae7118",
"gsd-core/references/phase-argument-parsing.md": "e5bbb985f3bc3e34",
"gsd-core/references/planner-antipatterns.md": "7ed54ec1e2cc54ac",
"gsd-core/references/planner-chunked.md": "79fe674221e738e6",
"gsd-core/references/planner-gap-closure.md": "76bee257911413e7",
"gsd-core/references/planner-graphify-auto-update.md": "6aeffd9097e25e22",
"gsd-core/references/planner-guidance.md": "96486cac2f7885e6",
"gsd-core/references/planner-human-verify-mode.md": "56d05e841630b3f4",
"gsd-core/references/planner-interface-context.md": "b28fa3da6ae739a8",
"gsd-core/references/planner-load-graph-context.md": "ca7a7af3f35ae61b",
"gsd-core/references/planner-mvp-mode.md": "ec33050db81101a8",
"gsd-core/references/planner-reviews.md": "dda0193a0fbd4947",
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
"gsd-core/references/planning-config.md": "dc492b7de6203355",
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
"gsd-core/references/prohibition-probe.md": "605dc3f5a118ff3b",
"gsd-core/references/project-skills-discovery.md": "c155e03dce8dc3c2",
"gsd-core/references/questioning.md": "faac32813d1735bd",
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
"gsd-core/references/research-philosophy.md": "62930e66cc979c1a",
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
"gsd-core/references/reviewer-instances.md": "385501e4f9bbb31d",
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
"gsd-core/references/skeleton-template.md": "528691d1f0efa878",
"gsd-core/references/sketch-interactivity.md": "7d982fe877e1e1cc",
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
"gsd-core/references/tdd.md": "e4708ede157478b6",
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
"gsd-core/references/thinking-models-execution.md": "dcc650a8b5f3e049",
"gsd-core/references/thinking-models-planning.md": "7e19462313fa028f",
"gsd-core/references/thinking-models-research.md": "5f6bf3f3b889c6e4",
"gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d",
"gsd-core/references/thinking-partner.md": "41069529ef776e39",
"gsd-core/references/ui-brand.md": "48717bcfcd63bd27",
"gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01",
"gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a",
"gsd-core/references/user-profiling.md": "b50416fe57c1b321",
"gsd-core/references/user-story-template.md": "0cc50e06a144ff8a",
"gsd-core/references/verification-overrides.md": "8213de9bd62283b6",
"gsd-core/references/verification-patterns.md": "72f4d4814fdae9a8",
"gsd-core/references/verify-mvp-mode.md": "534bdc7f2432903a",
"gsd-core/references/workstream-flag.md": "ca99ca79e716f0f5",
"gsd-core/references/worktree-branch-check.md": "21d9c31bf6542b93",
"gsd-core/references/worktree-path-safety.md": "3c8d74756f9b16a8",
"gsd-core/templates/AI-SPEC.md": "efa1f8354bd3a24b",
"gsd-core/templates/DEBUG.md": "a13470b82b1935e7",
"gsd-core/templates/README.md": "93d3426fc64e2c12",
"gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f",
"gsd-core/templates/UAT.md": "9e296471b97ebcec",
"gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01",
"gsd-core/templates/VALIDATION.md": "f53e0ca061d3528e",
"gsd-core/templates/claude-md.md": "d1d333e4b963c0d2",
"gsd-core/templates/codebase/architecture.md": "6be88214162fdd89",
"gsd-core/templates/codebase/concerns.md": "efa26d1fb5132f25",
"gsd-core/templates/codebase/conventions.md": "c2e07698dad6b364",
"gsd-core/templates/codebase/integrations.md": "39bd23c71eedd564",
"gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec",
"gsd-core/templates/codebase/structure.md": "222997133232a6f5",
"gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea",
"gsd-core/templates/config.json": "1473dff11272d3c4",
"gsd-core/templates/context.md": "69b01e7909ea3f66",
"gsd-core/templates/continue-here.md": "f522a51b6895fba8",
"gsd-core/templates/copilot-instructions.md": "d2872ac579934f0a",
"gsd-core/templates/debug-subagent-prompt.md": "920656683dedb869",
"gsd-core/templates/dev-preferences.md": "88d0a65ec0993a3a",
"gsd-core/templates/discovery.md": "9a0e0935cc825dbc",
"gsd-core/templates/discussion-log.md": "cac1b48ec0f4dcb8",
"gsd-core/templates/milestone-archive.md": "591b6decdc0c0e51",
"gsd-core/templates/milestone.md": "74d2f750ae9f4a9c",
"gsd-core/templates/phase-prompt.md": "b811bf951092df2f",
"gsd-core/templates/planner-subagent-prompt.md": "ebf29dbb27042370",
"gsd-core/templates/project.md": "ab4af6becca2bb23",
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
"gsd-core/templates/research-project/PITFALLS.md": "3ef75fa768422eec",
"gsd-core/templates/research-project/STACK.md": "82c85799ac4dd344",
"gsd-core/templates/research-project/SUMMARY.md": "dceb2f346388839d",
"gsd-core/templates/research.md": "88ce0920417091d0",
"gsd-core/templates/retrospective.md": "03981e30dd760103",
"gsd-core/templates/roadmap.md": "e4e35a9eb5dd4d4f",
"gsd-core/templates/spec.md": "26d55bce940f0288",
"gsd-core/templates/state.md": "4d123aa6cea167fe",
"gsd-core/templates/summary-complex.md": "a5e40574fd8894dc",
"gsd-core/templates/summary-minimal.md": "7d09b5e709e2e67c",
"gsd-core/templates/summary-standard.md": "e8d9cf4a8377cdff",
"gsd-core/templates/summary.md": "23c40f6503b3ea98",
"gsd-core/templates/user-profile.md": "20749f23e4c413fc",
"gsd-core/templates/user-setup.md": "78b7d718b6e8d67c",
"gsd-core/templates/verification-report.md": "dd5faa6254183731",
"gsd-core/workflows/_runtime-launcher.snippet.sh": "c0994e00f976dbfb",
"gsd-core/workflows/add-backlog.md": "1153d35058e3e24f",
"gsd-core/workflows/add-phase.md": "b98c2fc51cd123ba",
"gsd-core/workflows/add-tests.md": "7ec84770cbd9b850",
"gsd-core/workflows/add-todo.md": "a71015ef950e905a",
"gsd-core/workflows/ai-integration-phase.md": "56a079a218d9e0ee",
"gsd-core/workflows/analyze-dependencies.md": "52942af10f140717",
"gsd-core/workflows/audit-fix.md": "eaead7ea85761e5b",
"gsd-core/workflows/audit-milestone.md": "fe7185bb70eafdb9",
"gsd-core/workflows/audit-uat.md": "ca6f1f0ef174f793",
"gsd-core/workflows/autonomous.md": "cec1819b64329a25",
"gsd-core/workflows/check-todos.md": "34b3034a31e7dbe5",
"gsd-core/workflows/cleanup.md": "e73b3969f5c10dcf",
"gsd-core/workflows/code-review-fix.md": "b442c670f8c1fe93",
"gsd-core/workflows/code-review.md": "1bfa3bf542e30c5e",
"gsd-core/workflows/complete-milestone.md": "d768b744210ef4bc",
"gsd-core/workflows/debug.md": "1c137aaa9ffac0dc",
"gsd-core/workflows/diagnose-issues.md": "dfd26bce63e52b00",
"gsd-core/workflows/discovery-phase.md": "100b931f0f40bf1c",
"gsd-core/workflows/discuss-phase-assumptions.md": "5e26c16d4be817a1",
"gsd-core/workflows/discuss-phase-power.md": "0841f7dc6e9a054a",
"gsd-core/workflows/discuss-phase.md": "3eeeda447a9dd3b5",
"gsd-core/workflows/discuss-phase/modes/advisor.md": "d0bcd7bafe7c9a91",
"gsd-core/workflows/discuss-phase/modes/all.md": "6c456679da748bdb",
"gsd-core/workflows/discuss-phase/modes/analyze.md": "da0788f3be7f8105",
"gsd-core/workflows/discuss-phase/modes/auto.md": "057b13a030f0d14d",
"gsd-core/workflows/discuss-phase/modes/batch.md": "390f2c1fff33c963",
"gsd-core/workflows/discuss-phase/modes/chain.md": "ca3cc1ddfd19cd9d",
"gsd-core/workflows/discuss-phase/modes/default.md": "4ada4fe332c5c985",
"gsd-core/workflows/discuss-phase/modes/power.md": "fdc7a728eaaa2261",
"gsd-core/workflows/discuss-phase/modes/text.md": "5590f1f1a4554ad3",
"gsd-core/workflows/discuss-phase/templates/checkpoint.json": "e3bc3dca49db59eb",
"gsd-core/workflows/discuss-phase/templates/context.md": "6cd929e989fe2b0f",
"gsd-core/workflows/discuss-phase/templates/discussion-log.md": "cd5d01c9ef84ab73",
"gsd-core/workflows/do.md": "cd4f183e89f95c44",
"gsd-core/workflows/docs-update.md": "4f0207fdaab85b1d",
"gsd-core/workflows/edit-phase.md": "e5624ac6e3f8bef5",
"gsd-core/workflows/eval-review.md": "f28d703285d611d2",
"gsd-core/workflows/execute-phase.md": "7ac6af28dca27d5f",
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "8898e0ea533cc643",
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "abd2aca069c04a80",
"gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e",
"gsd-core/workflows/execute-plan.md": "73a7c87e975fa393",
"gsd-core/workflows/explore.md": "11523b829cc67c29",
"gsd-core/workflows/extract-learnings.md": "a2e4836abdeb1fdb",
"gsd-core/workflows/fast.md": "94136fb570d20a9d",
"gsd-core/workflows/forensics.md": "b4bde768fd27a982",
"gsd-core/workflows/graduation.md": "d00ac4dfe7e1cc43",
"gsd-core/workflows/health.md": "ae213b0d090641a2",
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
"gsd-core/workflows/help/modes/brief.md": "5fe077fbbdb5d9f7",
"gsd-core/workflows/help/modes/default.md": "cc8ef97de634c995",
"gsd-core/workflows/help/modes/full.md": "bc96c127412c4171",
"gsd-core/workflows/help/modes/topic.md": "6762e41e3291ae24",
"gsd-core/workflows/import.md": "c740cfabacf7b70a",
"gsd-core/workflows/inbox.md": "41371f790ef06101",
"gsd-core/workflows/ingest-docs.md": "1ee3890d42d95576",
"gsd-core/workflows/insert-phase.md": "621f9e75725542b1",
"gsd-core/workflows/list-phase-assumptions.md": "53dd4b69536c1bc3",
"gsd-core/workflows/list-seeds.md": "0a466d04df300854",
"gsd-core/workflows/list-workspaces.md": "0409cb5e3859964f",
"gsd-core/workflows/manager.md": "fd5816b3fc7a68de",
"gsd-core/workflows/map-codebase.md": "582bddef590de9c6",
"gsd-core/workflows/milestone-summary.md": "8b6ceec4aa454b8a",
"gsd-core/workflows/mvp-phase.md": "6cb2d9d97bd239b3",
"gsd-core/workflows/new-milestone.md": "86959fbb6f5e8893",
"gsd-core/workflows/new-project.md": "72b2c17e9f9cb79d",
"gsd-core/workflows/new-workspace.md": "226482083477bc43",
"gsd-core/workflows/next.md": "48bd4b5d54f0d1f8",
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
"gsd-core/workflows/note.md": "95199087905ba3e6",
"gsd-core/workflows/pause-work.md": "7bcbdf27ba957c8b",
"gsd-core/workflows/plan-milestone-gaps.md": "02fee851c82e3b25",
"gsd-core/workflows/plan-phase.md": "3fe02a5fc860bb46",
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072",
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "d64097345ffac40d",
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "49f58c3f75be3eb5",
"gsd-core/workflows/plan-review-convergence.md": "57c284df29121220",
"gsd-core/workflows/plant-seed.md": "339890021123e017",
"gsd-core/workflows/pr-branch.md": "f2a35833fe784a53",
"gsd-core/workflows/profile-user.md": "abf4520bd9975305",
"gsd-core/workflows/progress.md": "1caa7b2be914c717",
"gsd-core/workflows/quick.md": "7d1e7a62213fa190",
"gsd-core/workflows/reapply-patches.md": "7915e40411490bbe",
"gsd-core/workflows/remove-phase.md": "d030f80ca0df4fa9",
"gsd-core/workflows/remove-workspace.md": "9f5589b4b3a0639a",
"gsd-core/workflows/resume-project.md": "b18b51fd15cbce95",
"gsd-core/workflows/review.md": "fbfa6eebcad41aa0",
"gsd-core/workflows/scan.md": "54ff1ff60041d065",
"gsd-core/workflows/secure-phase.md": "a2f6a03034444f14",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "b43519adb4d8a5eb",
"gsd-core/workflows/settings-integrations.md": "9753c48c7826cfdd",
"gsd-core/workflows/settings.md": "82536480fe362e82",
"gsd-core/workflows/ship.md": "dfbf2069ce4b276d",
"gsd-core/workflows/sketch-wrap-up.md": "067e2654f3fdaba2",
"gsd-core/workflows/sketch.md": "e1a544c83bb6e5be",
"gsd-core/workflows/smart-entry.md": "aead1f97e32cc345",
"gsd-core/workflows/spec-phase.md": "72ba501da5edbfee",
"gsd-core/workflows/spike-wrap-up.md": "99a9e23d1c6cf66e",
"gsd-core/workflows/spike.md": "c9482514e665bcac",
"gsd-core/workflows/stats.md": "01c24349370a0e6d",
"gsd-core/workflows/sync-skills.md": "b505e6f8331c0918",
"gsd-core/workflows/thread.md": "6d075b5d26500e9d",
"gsd-core/workflows/transition.md": "69143573741c52e4",
"gsd-core/workflows/ui-phase.md": "bb5167948032872e",
"gsd-core/workflows/ui-review.md": "d256bec482e67af8",
"gsd-core/workflows/ultraplan-phase.md": "66c17ad2e555e262",
"gsd-core/workflows/undo.md": "993bb0a31edca7f8",
"gsd-core/workflows/update.md": "51c3af33474bdc24",
"gsd-core/workflows/validate-phase.md": "2d998cb78c918d08",
"gsd-core/workflows/verify-phase.md": "7579af6cd757f644",
"gsd-core/workflows/verify-work.md": "ba76036f0eff5c5e",
"hooks/gsd-check-update-worker.js": "5f5f2b73e6c14a7f",
"hooks/gsd-check-update.js": "0e955593b7884d99",
"hooks/gsd-config-reload.js": "96546e0e8bb47904",
"hooks/gsd-context-monitor.js": "57cf670fa45153a7",
"hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a",
"hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375",
"hooks/gsd-ensure-canonical-path.js": "6046bb90482883ad",
"hooks/gsd-graphify-update.sh": "e4c6e14fe6ad64ff",
"hooks/gsd-phase-boundary.sh": "32739d5fbe0d0a1c",
"hooks/gsd-prompt-guard.js": "a749b8cb2c5248de",
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
"hooks/gsd-read-injection-scanner.js": "f1adfec005911860",
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
"hooks/gsd-statusline.js": "d20735894543598a",
"hooks/gsd-update-banner.js": "55143a25f978f301",
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
"hooks/gsd-workflow-guard.js": "91ae24a15d2bca6f",
"hooks/gsd-worktree-path-guard.js": "e07a5c35190a182e",
"hooks/lib/git-cmd.js": "268ba15992ca0b23",
"hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9",
"hooks/managed-hooks-registry.cjs": "9372d0d21a2c4298",
"package.json": "dbf8353f77358bc1",
"scripts/changeset/README.md": "86ff89331dfd94b2",
"scripts/changeset/cli.cjs": "68f92a344b199271",
"scripts/changeset/github-release-notes.cjs": "795677f0c009b132",
"scripts/changeset/lint.cjs": "0066faed159154f0",
"scripts/changeset/new.cjs": "4991e21fd17f5541",
"scripts/changeset/parse.cjs": "f9a949cbcab56445",
"scripts/changeset/render.cjs": "e47bc3e1587c3cae",
"scripts/changeset/serialize.cjs": "ac0b8fe6f87cdb0e",
"scripts/fix-slash-commands.cjs": "0519742531ff3529",
"scripts/gen-capability-registry.cjs": "c52201ff4d1c2cd7",
"scripts/gen-loop-host-contract.cjs": "c7f15237234811a0",
"scripts/lib/allowlist-ratchet.cjs": "ffaceaac3efc2660",
"scripts/lib/cli-exit.cjs": "612d0c372c75b7e7"
}

View File

@@ -41,8 +41,8 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "9e74bf9cc8aa3774",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
@@ -300,7 +300,7 @@
"gsd-core/workflows/ui-review.md": "83d6d3b1f26597ff",
"gsd-core/workflows/ultraplan-phase.md": "600377d719253f14",
"gsd-core/workflows/undo.md": "791e0bf96d9a057f",
"gsd-core/workflows/update.md": "472d4905fc8c5ce5",
"gsd-core/workflows/update.md": "7499bb4cb2a3ce6f",
"gsd-core/workflows/validate-phase.md": "067acd7aeed52c5b",
"gsd-core/workflows/verify-phase.md": "4e1d99795e8e9413",
"gsd-core/workflows/verify-work.md": "e93eb5a56b769694",

View File

@@ -111,8 +111,8 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "9e74bf9cc8aa3774",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
@@ -370,7 +370,7 @@
"gsd-core/workflows/ui-review.md": "013272816a291305",
"gsd-core/workflows/ultraplan-phase.md": "db7df53187dae993",
"gsd-core/workflows/undo.md": "7cd2153f8b15e30d",
"gsd-core/workflows/update.md": "5d0a2356dadf2017",
"gsd-core/workflows/update.md": "07dc2fba78ad1865",
"gsd-core/workflows/validate-phase.md": "7915e3a261f7c9c9",
"gsd-core/workflows/verify-phase.md": "15a999f82868ad29",
"gsd-core/workflows/verify-work.md": "6194c487f858d85e",

View File

@@ -77,8 +77,8 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "9e74bf9cc8aa3774",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
@@ -336,7 +336,7 @@
"gsd-core/workflows/ui-review.md": "1ad3654435000881",
"gsd-core/workflows/ultraplan-phase.md": "66c17ad2e555e262",
"gsd-core/workflows/undo.md": "96d2775f008b3a85",
"gsd-core/workflows/update.md": "6369691790864147",
"gsd-core/workflows/update.md": "6718e0632bba26ca",
"gsd-core/workflows/validate-phase.md": "50f37b705b6e44fb",
"gsd-core/workflows/verify-phase.md": "7579af6cd757f644",
"gsd-core/workflows/verify-work.md": "d77ace0fc4f33ff7",

View File

@@ -111,8 +111,8 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "9e74bf9cc8aa3774",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
@@ -370,7 +370,7 @@
"gsd-core/workflows/ui-review.md": "3d972d3bd30527b3",
"gsd-core/workflows/ultraplan-phase.md": "11db7f58a45aca2e",
"gsd-core/workflows/undo.md": "0bba5e7f6196c894",
"gsd-core/workflows/update.md": "af51041a3172c523",
"gsd-core/workflows/update.md": "71b6cd852f38b4bc",
"gsd-core/workflows/validate-phase.md": "0fc5991f6d7c6c39",
"gsd-core/workflows/verify-phase.md": "13a1a43395b5e47b",
"gsd-core/workflows/verify-work.md": "c218bc9c5972dcdb",

View File

@@ -41,8 +41,8 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "9e74bf9cc8aa3774",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
@@ -300,7 +300,7 @@
"gsd-core/workflows/ui-review.md": "f1f82d8257e910cc",
"gsd-core/workflows/ultraplan-phase.md": "edcaa0b29f942df0",
"gsd-core/workflows/undo.md": "791e0bf96d9a057f",
"gsd-core/workflows/update.md": "baae1a977fbeba49",
"gsd-core/workflows/update.md": "b34cb866152d4de3",
"gsd-core/workflows/validate-phase.md": "0e153ccb3bdb9dda",
"gsd-core/workflows/verify-phase.md": "0ef74d5b7f7646f6",
"gsd-core/workflows/verify-work.md": "69d9d98563b9ec81",

View File

@@ -41,8 +41,8 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "9e74bf9cc8aa3774",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
@@ -300,7 +300,7 @@
"gsd-core/workflows/ui-review.md": "b3221cac976daffa",
"gsd-core/workflows/ultraplan-phase.md": "06ac6fbb061b2464",
"gsd-core/workflows/undo.md": "59b8baa54efc4110",
"gsd-core/workflows/update.md": "e259898f86ae7133",
"gsd-core/workflows/update.md": "1f935251fca1f276",
"gsd-core/workflows/validate-phase.md": "70d102b4d5113dd4",
"gsd-core/workflows/verify-phase.md": "67eefbd1f6417281",
"gsd-core/workflows/verify-work.md": "b48ff071a4c733c9",

View File

@@ -41,8 +41,8 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "7d398e94c44e5268",
"gsd-core/bin/shared/model-catalog.json": "9e74bf9cc8aa3774",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
"gsd-core/bin/shared/model-catalog.json": "ca909d55f7966269",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
@@ -300,7 +300,7 @@
"gsd-core/workflows/ui-review.md": "fea93c281767f8d7",
"gsd-core/workflows/ultraplan-phase.md": "a164cc6aa4fb5bbb",
"gsd-core/workflows/undo.md": "18dec684fb1076f9",
"gsd-core/workflows/update.md": "cbf978622ad0f577",
"gsd-core/workflows/update.md": "79aaf4b8f1f83045",
"gsd-core/workflows/validate-phase.md": "a36cf2c688c261ac",
"gsd-core/workflows/verify-phase.md": "8a89a915dad5960b",
"gsd-core/workflows/verify-work.md": "f4429f002440a5ae",

View File

@@ -1,217 +0,0 @@
/**
* Regression tests for Gemini namespacing (PR #2768)
*
* Verifies that slash commands are correctly converted to colon format (/gsd:)
* while preserving URLs, file paths, and agent names.
*/
'use strict';
process.env.GSD_TEST_MODE = '1';
const { test, describe, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { createTempDir, cleanup } = require('./helpers.cjs');
const {
convertSlashCommandsToGeminiMentions,
convertClaudeToGeminiMarkdown,
convertClaudeToGeminiAgent,
_resetGsdCommandRoster,
install
} = require('../bin/install.js');
/**
* Minimal parser for the simple TOML emitted by convertClaudeToGeminiToml —
* exactly two top-level keys (`description` and `prompt`), each a JSON-quoted
* string. Throws on unparseable lines so a regression in the emitter shape
* fails loudly rather than silently mis-parsing.
*/
function parseGeminiCommandToml(toml) {
const result = {};
for (const rawLine of toml.split('\n')) {
const line = rawLine.trim();
if (!line) continue;
const match = line.match(/^([a-z_]+)\s*=\s*(.*)$/);
if (!match) throw new Error(`Unparseable TOML line: ${rawLine}`);
const [, key, value] = match;
// Values are JSON-encoded strings — JSON.parse handles all escapes.
result[key] = JSON.parse(value);
}
return result;
}
describe('Gemini Slash Command Namespacing (Regex)', () => {
test('converts simple slash commands', () => {
const input = 'Run /gsd-plan-phase to start.';
const expected = 'Run /gsd:plan-phase to start.';
assert.strictEqual(convertSlashCommandsToGeminiMentions(input), expected);
});
test('preserves URLs with /gsd- in them', () => {
const input = 'Documentation: https://example.com/gsd-tools/info';
assert.strictEqual(convertSlashCommandsToGeminiMentions(input), input);
});
// The roster check is the safety property: a token like /gsd-plan-phase IS
// a known command name, but when it appears inside a URL path it must NOT
// be converted. This pins that the roster check actually fires — a regex-only
// approach without a roster would convert this incorrectly.
test('preserves URLs even when path contains a KNOWN command name', () => {
const input = 'See https://example.com/gsd-plan-phase for context.';
assert.strictEqual(convertSlashCommandsToGeminiMentions(input), input);
});
test('preserves sub-paths: bin/gsd-tools.cjs', () => {
const input = 'See bin/gsd-tools.cjs for details.';
assert.strictEqual(convertSlashCommandsToGeminiMentions(input), input);
});
test('preserves sub-paths even when leaf is a KNOWN command name', () => {
// bin/gsd-plan-phase looks like a known command but is a file path.
// The leading / on a sub-path follows a non-slash char so the regex
// boundary is the safety net here, not the roster.
const input = 'Reference bin/gsd-plan-phase for details.';
assert.strictEqual(convertSlashCommandsToGeminiMentions(input), input);
});
test('preserves root-relative paths with extensions: /gsd-tools.cjs', () => {
const input = 'Load /gsd-tools.cjs now.';
assert.strictEqual(convertSlashCommandsToGeminiMentions(input), input);
});
test('preserves agent names: gsd-planner', () => {
const input = 'The gsd-planner agent will help you.';
assert.strictEqual(convertSlashCommandsToGeminiMentions(input), input);
});
test('converts commands in backticks', () => {
const input = 'Run `/gsd-new-project` in a terminal.';
const expected = 'Run `/gsd:new-project` in a terminal.';
assert.strictEqual(convertSlashCommandsToGeminiMentions(input), expected);
});
test('converts commands ending with punctuation', () => {
// Use two stable, foundational commands so this test doesn't drift when
// the roster gets consolidated (cf. #2790, which removed `scan`). `help`
// and `health` are both bedrock; if either ever gets removed, swap to
// any other entry currently in commands/gsd/.
const input = 'Run /gsd-help. Or /gsd-health!';
const expected = 'Run /gsd:help. Or /gsd:health!';
assert.strictEqual(convertSlashCommandsToGeminiMentions(input), expected);
});
test('roster has loaded — non-empty (would otherwise silently no-op all conversions)', () => {
_resetGsdCommandRoster();
// First conversion call lazily populates the roster. If it returned an
// empty Set (because commands/gsd/ was not found), every conversion
// becomes a no-op — exactly the bug this code exists to prevent.
const result = convertSlashCommandsToGeminiMentions('Run /gsd-plan-phase.');
assert.strictEqual(result, 'Run /gsd:plan-phase.',
'Roster failed to load — all /gsd- conversions would silently no-op');
});
});
describe('Gemini Markdown Processor', () => {
test('handles command to TOML conversion', () => {
const input = '---\ndescription: Test\n---\nRun /gsd-help.';
const result = convertClaudeToGeminiMarkdown(input, { isCommand: true });
const parsed = parseGeminiCommandToml(result);
assert.equal(parsed.description, 'Test', 'description must round-trip through TOML');
assert.match(parsed.prompt, /\/gsd:help/, 'prompt must contain namespaced command');
assert.doesNotMatch(parsed.prompt, /\/gsd-help/, 'prompt must not retain hyphen form');
});
test('strips <sub> tags from Gemini markdown output (#2768 regression)', () => {
// The pre-refactor command path called stripSubTags before TOML conversion.
// After centralizing through convertClaudeToGeminiMarkdown, sub tags must
// still be stripped — terminals can't render HTML subscript.
const input = 'Run <sub>tiny</sub> /gsd-help now.';
const result = convertClaudeToGeminiMarkdown(input, { isCommand: false });
assert.doesNotMatch(result, /<sub>|<\/sub>/, '<sub> tags must be stripped');
assert.match(result, /\/gsd:help/, 'slash command must still be converted');
});
test('removes AskUserQuestion and ask_user from Gemini agent tools and body (#3362)', () => {
const input = [
'---',
'name: tester',
'tools: Read, AskUserQuestion, ask_user',
'---',
'Use AskUserQuestion or ask_user to ask the user.'
].join('\n');
const result = convertClaudeToGeminiAgent(input);
assert.match(result, /^ {2}- read_file$/m, 'Read should still map to Gemini read_file');
assert.doesNotMatch(result, /^ {2}- ask_user$/m, 'ask_user must not be emitted as a Gemini tool');
assert.doesNotMatch(result, /\bAskUserQuestion\b/, 'Claude tool references must be neutralized');
assert.doesNotMatch(result, /\bask_user\b/, 'lowercase ask_user references must be neutralized');
assert.match(result, /conversational prompting/, 'body should use runtime-neutral wording');
});
});
describe('Gemini Install (Behavioral)', () => {
let tmpDir;
let tmpHome;
let previousCwd;
let previousHome;
let previousUserprofile;
beforeEach(() => {
tmpDir = createTempDir('gsd-gemini-test-');
tmpHome = createTempDir('gsd-gemini-home-');
previousCwd = process.cwd();
previousHome = process.env.HOME;
previousUserprofile = process.env.USERPROFILE;
process.chdir(tmpDir);
// #3037: isolate HOME so the developer's real ~/.gemini/commands/gsd/
// doesn't trigger the local-install conflict-avoidance skip path. This
// test wants to assert that the local install populates commands/gsd/
// when no global GSD is present at the user scope.
process.env.HOME = tmpHome;
process.env.USERPROFILE = tmpHome;
});
afterEach(() => {
process.chdir(previousCwd);
if (previousHome === undefined) delete process.env.HOME;
else process.env.HOME = previousHome;
if (previousUserprofile === undefined) delete process.env.USERPROFILE;
else process.env.USERPROFILE = previousUserprofile;
cleanup(tmpDir);
cleanup(tmpHome);
});
test('install creates correct directory structure for Gemini', () => {
// Run install in silent mode
const oldLog = console.log;
console.log = () => {};
try {
install(false, 'gemini');
} finally {
console.log = oldLog;
}
const commandsDir = path.join(tmpDir, '.gemini', 'commands', 'gsd');
assert.ok(fs.existsSync(commandsDir), `Commands should be in ${commandsDir}`);
const agentsDir = path.join(tmpDir, '.gemini', 'agents');
assert.ok(fs.existsSync(agentsDir), 'Agents should be installed');
// Structurally verify a real installed command artifact: parse the TOML
// and assert the prompt body has been namespaced. A directory-only check
// would pass even if every conversion silently no-op'd.
const planPhaseToml = path.join(commandsDir, 'plan-phase.toml');
assert.ok(fs.existsSync(planPhaseToml), 'plan-phase.toml must be installed');
const parsed = parseGeminiCommandToml(fs.readFileSync(planPhaseToml, 'utf8'));
assert.equal(typeof parsed.prompt, 'string', 'plan-phase.toml must have a prompt');
// The plan-phase prompt cross-references other GSD commands; pin that at
// least one of those references survived as a colon-namespaced mention.
assert.match(parsed.prompt, /\/gsd:[a-z][a-z0-9-]*/,
'installed plan-phase.toml prompt must contain at least one /gsd: reference');
assert.doesNotMatch(parsed.prompt, /(?<![A-Za-z0-9./])\/gsd-plan-phase\b/,
'installed plan-phase.toml prompt must not retain unconverted /gsd-plan-phase');
});
});

View File

@@ -0,0 +1,193 @@
/**
* #1928 — Gemini CLI runtime removal + Antigravity redirect.
*
* Google sunset Gemini CLI on 2026-06-18; Antigravity CLI is the official
* successor. GSD removes the `gemini` runtime and turns `--gemini` into an
* explicit deprecation redirect (NOT a silent alias — Hyrum's Law, per the
* issue's rejected alternative #2).
*
* Coverage:
* A. CLI redirect contract (spawned installer): the sunset notice, the
* no-silent-install failure path, clean UX (no stack trace), and that a
* co-selected valid runtime still installs.
* B. The `gemini` runtime is gone from every runtime-name-policy surface.
* C. Antigravity is PRESERVED everywhere it shared surface with gemini
* (GEMINI.md instruction file + the shared convertGeminiToolName tool
* vocabulary) — the shared-infra regression this change had to avoid.
*/
'use strict';
const { describe, test, before } = require('node:test');
const assert = require('node:assert/strict');
const path = require('node:path');
const fs = require('node:fs');
const { spawnSync, execFileSync } = require('node:child_process');
const { createTempDir, cleanup } = require('./helpers.cjs');
const { runMinimalInstall, BUILD_SCRIPT } = require('./helpers/install-shared.cjs');
const ROOT = path.join(__dirname, '..');
const INSTALL_JS = path.join(ROOT, 'bin', 'install.js');
// hooks/dist is gitignored + built; build it idempotently so a real install
// emits hooks (mirrors golden-install-parity / install-minimal-hooks).
before(() => {
execFileSync(process.execPath, [BUILD_SCRIPT], { stdio: 'pipe' });
});
const {
canonicalizeRuntimeName,
getRuntimeLabel,
getGlobalConfigHomeFragment,
getRuntimeNewProjectCommand,
runtimeFlags,
getProjectInstructionFile,
} = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'runtime-name-policy.cjs'));
const { convertClaudeAgentToAntigravityAgent } = require('../bin/install.js');
// Run the installer as a subprocess with an isolated HOME so no install can
// touch the real machine. Runtime-config env overrides are stripped so the
// child resolves config dirs strictly under the temp HOME.
function runInstaller(args, homeDir) {
const env = { ...process.env, HOME: homeDir, USERPROFILE: homeDir, GSD_TEST_MODE: '1' };
for (const k of [
'CLAUDE_CONFIG_DIR', 'GEMINI_CONFIG_DIR', 'ANTIGRAVITY_CONFIG_DIR',
'XDG_CONFIG_HOME', 'CODEX_CONFIG_DIR', 'OPENCODE_CONFIG_DIR', 'KILO_CONFIG_DIR',
]) delete env[k];
return spawnSync(process.execPath, [INSTALL_JS, ...args], {
cwd: homeDir, env, encoding: 'utf8', timeout: 120000,
});
}
describe('#1928 --gemini CLI deprecation redirect', () => {
test('--gemini alone prints the sunset notice and exits non-zero without installing', (t) => {
const home = createTempDir('gsd-1928-gemini-only-');
t.after(() => cleanup(home));
const r = runInstaller(['--gemini'], home);
const out = `${r.stdout || ''}${r.stderr || ''}`;
assert.strictEqual(r.status, 1, 'a bare --gemini must exit 1, not silently fall through to a Claude install');
assert.match(out, /sunset by Google on 2026-06-18/, 'must cite the 2026-06-18 sunset date');
assert.match(out, /--antigravity/, 'must redirect the user to --antigravity');
assert.match(out, /Antigravity CLI \(the official successor\)/);
// No silent install: nothing was written under the isolated HOME.
assert.ok(!fs.existsSync(path.join(home, '.gemini')), 'must not create a .gemini runtime dir');
assert.ok(!fs.existsSync(path.join(home, '.claude')), 'bare --gemini must not silently install Claude');
});
test('--gemini --global still exits 1 (removed flag regardless of scope)', (t) => {
const home = createTempDir('gsd-1928-gemini-global-');
t.after(() => cleanup(home));
const r = runInstaller(['--gemini', '--global'], home);
assert.strictEqual(r.status, 1);
assert.match(`${r.stdout || ''}${r.stderr || ''}`, /sunset by Google on 2026-06-18/);
});
test('the redirect is a clean message — no stack trace leaks to the user', (t) => {
const home = createTempDir('gsd-1928-gemini-clean-');
t.after(() => cleanup(home));
const r = runInstaller(['--gemini'], home);
const err = r.stderr || '';
assert.doesNotMatch(err, /^\s+at .+:\d+:\d+/m, 'no V8 stack frame in redirect output');
assert.doesNotMatch(err, /\bError:|\bTypeError:|\bthrow\b/, 'no thrown-error prose in redirect output');
});
test('--gemini --help still prints usage (the redirect must not suppress help)', (t) => {
const home = createTempDir('gsd-1928-gemini-help-');
t.after(() => cleanup(home));
const r = runInstaller(['--gemini', '--help'], home);
assert.strictEqual(r.status, 0, '--help must exit 0, not the redirect error code');
assert.match(`${r.stdout || ''}`, /Usage:/, 'the usage/help block must still print to stdout');
assert.match(`${r.stderr || ''}`, /sunset by Google on 2026-06-18/, 'the notice also prints');
});
test('--gemini --uninstall guides manual cleanup and does NOT uninstall Claude', (t) => {
const home = createTempDir('gsd-1928-gemini-uninstall-');
t.after(() => cleanup(home));
// Sentinel: a pre-existing Claude install that must survive. Run WITHOUT
// GSD_TEST_MODE so the real uninstall dispatch is active — the redirect must
// exit before it (the dispatch defaults an empty selection to 'claude').
fs.mkdirSync(path.join(home, '.claude'), { recursive: true });
fs.writeFileSync(path.join(home, '.claude', 'sentinel.txt'), 'keep me');
const env = { ...process.env, HOME: home, USERPROFILE: home };
delete env.GSD_TEST_MODE;
delete env.CLAUDE_CONFIG_DIR;
const r = spawnSync(process.execPath, [INSTALL_JS, '--gemini', '--uninstall', '--global'], {
cwd: home, env, encoding: 'utf8', timeout: 120000,
});
assert.strictEqual(r.status, 1, 'must exit 1, not fall through to the uninstall dispatch');
assert.match(`${r.stderr || ''}`, /`--gemini --uninstall` is no longer available/, 'must guide manual cleanup');
assert.ok(fs.existsSync(path.join(home, '.claude', 'sentinel.txt')),
'the Claude install must NOT be uninstalled (the dispatch defaults empty selection to claude)');
});
test('--gemini co-selected with a valid runtime prints the notice AND still installs the other runtime', (t) => {
// Hermetic install via the repo harness (explicit --config-dir + isolated
// HOME). `--gemini` is added alongside a valid runtime (codex): the installer
// prints the notice but does NOT exit 1 (runMinimalInstall asserts status 0
// internally) and installs codex.
const { manifest, root, stderr } = runMinimalInstall({ runtime: 'codex', scope: 'global', extraArgs: ['--gemini'] });
t.after(() => cleanup(root));
assert.match(stderr, /sunset by Google on 2026-06-18/, 'the redirect notice still prints alongside the valid install');
assert.match(stderr, /--antigravity/);
assert.ok(manifest, 'the co-selected codex runtime must be installed (manifest written)');
});
test('control: an install WITHOUT --gemini does not print the sunset notice', (t) => {
const { root, stderr } = runMinimalInstall({ runtime: 'codex', scope: 'global' });
t.after(() => cleanup(root));
assert.doesNotMatch(stderr, /sunset by Google/, 'the notice must be conditional on --gemini');
});
});
describe('#1928 gemini removed from every runtime-name-policy surface', () => {
test('gemini aliases no longer canonicalize', () => {
for (const alias of ['gemini', 'gemini-cli', 'gemini-code']) {
assert.strictEqual(canonicalizeRuntimeName(alias), null, `${alias} must not resolve to a known runtime`);
}
});
test('gemini falls back on label / config-fragment / new-project surfaces', () => {
assert.strictEqual(getRuntimeLabel('gemini'), 'Claude Code', 'label table entry removed → fail-closed default');
assert.strictEqual(getGlobalConfigHomeFragment('gemini'), "'.claude'", 'config-home fragment removed → default');
assert.strictEqual(getRuntimeNewProjectCommand('gemini'), '/gsd-new-project', 'new-project override removed → default');
});
test('runtimeFlags has exactly 14 non-claude runtimes and no isGemini', () => {
const flags = runtimeFlags('claude');
assert.ok(!('isGemini' in flags), 'isGemini flag must be gone');
assert.strictEqual(Object.keys(flags).length, 14, 'flag count drops 15 → 14 with gemini removed');
});
test('gemini no longer maps to GEMINI.md (defaults to AGENTS.md)', () => {
assert.strictEqual(getProjectInstructionFile('gemini'), 'AGENTS.md');
});
});
describe('#1928 Antigravity preserved (shared surface with the removed gemini runtime)', () => {
test('antigravity still resolves and keeps its GEMINI.md instruction file', () => {
assert.strictEqual(canonicalizeRuntimeName('antigravity'), 'antigravity');
assert.strictEqual(canonicalizeRuntimeName('antigravity-cli'), 'antigravity');
assert.strictEqual(getProjectInstructionFile('antigravity'), 'GEMINI.md',
'Antigravity CLI reads GEMINI.md as its contextFileName — this mapping must survive gemini removal');
assert.strictEqual(getRuntimeLabel('antigravity'), 'Antigravity');
});
test('the shared Gemini-backend tool vocabulary still powers Antigravity agent conversion', () => {
const input = ['---', 'name: gsd-x', 'description: d', 'tools: Read, Write, WebFetch, Skill', '---', '', 'body'].join('\n');
const toolsLine = convertClaudeAgentToAntigravityAgent(input).split('\n').find((l) => l.startsWith('tools:')) || '';
assert.ok(toolsLine.includes('read_file'), 'Read → read_file via the retained convertGeminiToolName');
assert.ok(toolsLine.includes('write_file'), 'Write → write_file');
assert.ok(toolsLine.includes('web_fetch'), 'WebFetch → web_fetch');
assert.ok(!/\bskill\b/.test(toolsLine), 'Skill is still excluded (would be an invalid backend tool name)');
});
});

View File

@@ -21,7 +21,7 @@ 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.
// Golden oracle: hardcoded expected map of all 15 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
@@ -30,7 +30,6 @@ const GOLDEN_DIR_MAP = {
claude: '.claude',
copilot: '.github',
opencode: '.opencode',
gemini: '.gemini',
kilo: '.kilo',
codex: '.codex',
antigravity: '.agents',
@@ -45,7 +44,7 @@ const GOLDEN_DIR_MAP = {
cline: '.cline',
};
test('getDirName: golden map matches for all 16 known runtime ids', () => {
test('getDirName: golden map matches for all 15 known runtime ids', () => {
for (const [id, expected] of Object.entries(GOLDEN_DIR_MAP)) {
const actual = getDirName(id);
assert.strictEqual(

View File

@@ -4,11 +4,11 @@
* of truth for the runtime → global config-home path-fragment mapping used by
* `getConfigDirFromHome` in bin/install.js for hook path.join() codegen.
*
* Collapses the prior 14-branch `if (runtime === 'x') return "'...'"` chain
* Collapses the prior branch `if (runtime === 'x') return "'...'"` chain
* (install.js ~514-543) into one table — ADR-1239 Phase B / #1679, AC2 slice 2.
*
* Invariants pinned here:
* 1. Each of the 14 table runtimes returns its exact verbatim source fragment
* 1. Each of the 13 table runtimes returns its exact verbatim source fragment
* (byte-identical to the prior chain — golden install parity asserts the
* generated hook output is unchanged).
* 2. claude + unknown + empty fall back to the default "'.claude'" fragment.
@@ -35,7 +35,6 @@ const { getGlobalConfigHomeFragment } = runtimeNamePolicy;
const GOLDEN_FRAGMENT_MAP = {
copilot: "'.copilot'",
opencode: "'.config', 'opencode'",
gemini: "'.gemini'",
kilo: "'.config', 'kilo'",
codex: "'.codex'",
cursor: "'.cursor'",
@@ -53,7 +52,7 @@ const GOLDEN_FRAGMENT_MAP = {
// resolved dynamically by the caller (resolveAntigravityGlobalDir + path.relative).
const SPECIAL_CASED = new Set(['claude', 'antigravity']);
test('getGlobalConfigHomeFragment: golden map matches for all 14 table runtimes', () => {
test('getGlobalConfigHomeFragment: golden map matches for all 13 table runtimes', () => {
for (const [id, expected] of Object.entries(GOLDEN_FRAGMENT_MAP)) {
const actual = getGlobalConfigHomeFragment(id);
assert.strictEqual(

View File

@@ -49,7 +49,6 @@ const RUNTIME_META = {
codex: { localDir: '.codex', globalSuffix: '.codex' },
copilot: { localDir: '.github', globalSuffix: '.copilot' },
cursor: { localDir: '.cursor', globalSuffix: '.cursor' },
gemini: { localDir: '.gemini', globalSuffix: '.gemini' },
hermes: { localDir: '.hermes', globalSuffix: '.hermes' },
kimi: { localDir: '.kimi-code', globalSuffix: path.join('.config', 'agents') },
kilo: { localDir: '.kilo', globalSuffix: path.join('.config', 'kilo') },
@@ -61,7 +60,7 @@ const RUNTIME_META = {
// Runtimes that emit per-skill files under skills/ (not rules-based or commands-based)
const SKILL_RUNTIMES = [
'claude', 'opencode', 'gemini', 'kilo', 'codex', 'copilot', 'antigravity',
'claude', 'opencode', 'kilo', 'codex', 'copilot', 'antigravity',
'cursor', 'augment', 'trae', 'qwen', 'codebuddy',
];
@@ -112,7 +111,7 @@ function runMinimalInstall({ runtime, scope, extraArgs = [] }) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), `gsd-${runtime}-${scope}-`));
try {
const LOCAL_DIR_NAME = {
claude: '.claude', opencode: '.opencode', gemini: '.gemini', kilo: '.kilo',
claude: '.claude', opencode: '.opencode', kilo: '.kilo',
codex: '.codex', copilot: '.github', antigravity: '.agents', cursor: '.cursor',
windsurf: '.windsurf', augment: '.augment', trae: '.trae', qwen: '.qwen',
codebuddy: '.codebuddy', cline: '.',

View File

@@ -1,7 +1,7 @@
'use strict';
/**
* ADR-1239 Phase A: Descriptor tests — validate that all 16 role:runtime
* ADR-1239 Phase A: Descriptor tests — validate that all 15 role:runtime
* capability descriptors have correct hostIntegration axes, pass the validator,
* and negotiate correctly via the host-integration module.
*
@@ -31,16 +31,16 @@ const SCALAR_AXES = ['embeddingMode', 'commandSurface', 'modelMode', 'hookBus',
// All 6 dispatch sub-keys (includes backgroundDispatch added in feat/1679-dispatch-flatten)
const DISPATCH_KEYS = ['namedDispatch', 'nested', 'maxDepth', 'background', 'subagentToolkit', 'backgroundDispatch'];
// All 16 runtime IDs (ordered alphabetically)
// All 15 runtime IDs (ordered alphabetically)
const RUNTIME_IDS = [
'antigravity', 'augment', 'claude', 'cline', 'codebuddy',
'codex', 'copilot', 'cursor', 'gemini', 'hermes',
'codex', 'copilot', 'cursor', 'hermes',
'kilo', 'kimi', 'opencode', 'qwen', 'trae', 'windsurf',
];
// Contract-pinned profile split (derived from .host-cli-final.json):
// programmatic-cli: claude, cline, cursor, hermes, kilo, kimi, opencode, qwen, trae (9)
// declarative-cli: antigravity, augment, codebuddy, codex, copilot, gemini, windsurf (7)
// declarative-cli: antigravity, augment, codebuddy, codex, copilot, windsurf (6)
// ide: 0
const EXPECTED_PROFILES = {
claude: 'programmatic-cli',
@@ -57,14 +57,13 @@ const EXPECTED_PROFILES = {
codebuddy: 'declarative-cli',
codex: 'declarative-cli',
copilot: 'declarative-cli',
gemini: 'declarative-cli',
windsurf: 'declarative-cli',
};
describe('ADR-1239 Phase A: hostIntegration descriptors', () => {
// ─── Registry shape ──────────────────────────────────────────────────────────
test('registry.runtimes contains all 16 expected runtime ids', () => {
test('registry.runtimes contains all 15 expected runtime ids', () => {
for (const id of RUNTIME_IDS) {
assert.ok(
Object.prototype.hasOwnProperty.call(registry.runtimes, id),
@@ -73,8 +72,8 @@ describe('ADR-1239 Phase A: hostIntegration descriptors', () => {
}
assert.strictEqual(
Object.keys(registry.runtimes).length,
16,
'registry.runtimes must have exactly 16 entries',
15,
'registry.runtimes must have exactly 15 entries',
);
});
@@ -209,7 +208,7 @@ describe('ADR-1239 Phase A: hostIntegration descriptors', () => {
// ─── Contract-pin profile split ───────────────────────────────────────────────
test('contract-pin: exactly 9 programmatic-cli, 7 declarative-cli, 0 ide', () => {
test('contract-pin: exactly 9 programmatic-cli, 6 declarative-cli, 0 ide', () => {
const counts = { 'programmatic-cli': 0, 'declarative-cli': 0, 'ide': 0 };
for (const id of RUNTIME_IDS) {
const cap = registry.runtimes[id];
@@ -222,7 +221,7 @@ describe('ADR-1239 Phase A: hostIntegration descriptors', () => {
}
}
assert.strictEqual(counts['programmatic-cli'], 9, 'Must have exactly 9 programmatic-cli runtimes');
assert.strictEqual(counts['declarative-cli'], 7, 'Must have exactly 7 declarative-cli runtimes');
assert.strictEqual(counts['declarative-cli'], 6, 'Must have exactly 6 declarative-cli runtimes');
assert.strictEqual(counts['ide'], 0, 'Must have exactly 0 ide runtimes');
});
@@ -260,7 +259,6 @@ describe('ADR-1239 Phase A: hostIntegration descriptors', () => {
codex: false,
copilot: true,
cursor: false,
gemini: true,
hermes: true,
kilo: true,
kimi: true,
@@ -297,12 +295,12 @@ describe('ADR-1239 Phase A: hostIntegration descriptors', () => {
);
});
test('contract-pin: spot-check claude→programmatic-cli, codex→declarative-cli, opencode→programmatic-cli, gemini→declarative-cli', () => {
test('contract-pin: spot-check claude→programmatic-cli, codex→declarative-cli, opencode→programmatic-cli, windsurf→declarative-cli', () => {
const checks = [
['claude', 'programmatic-cli'],
['codex', 'declarative-cli'],
['opencode', 'programmatic-cli'],
['gemini', 'declarative-cli'],
['windsurf', 'declarative-cli'],
];
for (const [id, expectedProfile] of checks) {
const cap = registry.runtimes[id];

View File

@@ -286,7 +286,7 @@ describe('ADR-1239 validator behavioral: undocumented sentinel passes, bogus fai
`bogus value "zzz" for backgroundDispatch must produce a validator error`);
});
test('dispatch without backgroundDispatch key → validator error (required field — all 16 descriptors carry it)', () => {
test('dispatch without backgroundDispatch key → validator error (required field — all 15 descriptors carry it)', () => {
// backgroundDispatch is now REQUIRED (matches sibling fields namedDispatch/nested/background/subagentToolkit/maxDepth).
const cap = makeMinimalRuntimeCap({
hostIntegration: {

File diff suppressed because it is too large Load Diff

View File

@@ -527,13 +527,13 @@ describe('mergeClaudePermissions (#768): end-to-end install writes permissions t
'settings.json permissions.deny must include Read(.env)');
});
test('non-claude runtime (gemini) does NOT write GSD allow/deny permissions to settings.json', (t) => {
const root = createTempDir('gsd-gemini-perm-install-');
test('non-claude runtime (antigravity) does NOT write GSD allow/deny permissions to settings.json', (t) => {
const root = createTempDir('gsd-antigravity-perm-install-');
t.after(() => cleanup(root));
const result = spawnSync(
process.execPath,
[INSTALL_SCRIPT, '--gemini', '--global', '--config-dir', root],
[INSTALL_SCRIPT, '--antigravity', '--global', '--config-dir', root],
{ encoding: 'utf8', env: { ...process.env, HOME: root, USERPROFILE: root } },
);
@@ -546,7 +546,7 @@ describe('mergeClaudePermissions (#768): end-to-end install writes permissions t
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
const allow = settings.permissions?.allow ?? [];
assert.ok(!allow.includes('Bash(npx gsd-core *)'),
'Gemini settings.json must NOT include Bash(npx gsd-core *) in permissions.allow');
'Antigravity settings.json must NOT include Bash(npx gsd-core *) in permissions.allow');
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -752,14 +752,14 @@ describe('Kilo integration — install/uninstall behaviour', () => {
assert.deepStrictEqual(selectRuntimesFromArgs(['--kilo']), ['kilo']);
});
test('runtimeMap has Kilo as option 12 after Kimi', () => {
assert.strictEqual(runtimeMap['12'], 'kilo');
test('runtimeMap has Kilo as option 11 after Kimi', () => {
assert.strictEqual(runtimeMap['11'], 'kilo');
});
test('prompt text shows Kilo above OpenCode without marketing copy', () => {
const plain = stripAnsi(buildRuntimePromptText());
assert.ok(/\b12\)\s*Kilo\b/.test(plain));
assert.ok(plain.indexOf('12) Kilo') < plain.indexOf('OpenCode'));
assert.ok(/\b11\)\s*Kilo\b/.test(plain));
assert.ok(plain.indexOf('11) Kilo') < plain.indexOf('OpenCode'));
assert.ok(!plain.includes('the #1 AI coding platform on OpenRouter'));
});
@@ -1549,7 +1549,7 @@ describe('cleanupWindsurfLegacyDevinSkills — removes pre-#1615 skill artifacts
// 1. Claude install: Group A agents have disallowedTools == {Write, Edit, MultiEdit} exactly.
// 2. Claude install: Group B agents have disallowedTools == {Edit, MultiEdit} exactly.
// 3. Negative: gsd-nyquist-auditor has NO disallowedTools key (legitimately writes+edits).
// 4. Cross-runtime no-leak: Gemini-installed read-only agents do NOT contain disallowedTools.
// 4. Cross-runtime no-leak: Qwen-installed read-only agents do NOT contain disallowedTools.
// 5. Source purity: source agents/*.md must not contain disallowedTools (inject-only).
// 6. Parity: docs/AGENTS.md "Disallowed Tools" rows match READONLY_AGENT_DISALLOWED_TOOLS.
// (DEFECT.GENERATIVE-FIX guard)
@@ -1595,7 +1595,6 @@ function parseDisallowedToolsSet(fm) {
function runGlobalInstall767(runtime, tmpHome) {
const envVarMap = {
claude: 'CLAUDE_CONFIG_DIR',
gemini: 'GEMINI_CONFIG_DIR',
qwen: 'QWEN_CONFIG_DIR',
};
const envVar = envVarMap[runtime];
@@ -1714,15 +1713,15 @@ describe('#767 Claude install: gsd-nyquist-auditor has no disallowedTools (legit
});
});
describe('#767 Gemini install: read-only agents do NOT contain disallowedTools (cross-runtime leak guard)', () => {
describe('#767 Qwen install: read-only agents do NOT contain disallowedTools (cross-runtime leak guard)', () => {
let tmpDir;
let geminiHome;
let qwenHome;
beforeEach(() => {
tmpDir = createTempDir('gsd-767-gemini-');
geminiHome = path.join(tmpDir, 'gemini-home');
fs.mkdirSync(geminiHome, { recursive: true });
runGlobalInstall767('gemini', geminiHome);
tmpDir = createTempDir('gsd-767-qwen-');
qwenHome = path.join(tmpDir, 'qwen-home');
fs.mkdirSync(qwenHome, { recursive: true });
runGlobalInstall767('qwen', qwenHome);
});
afterEach(() => {
@@ -1730,11 +1729,11 @@ describe('#767 Gemini install: read-only agents do NOT contain disallowedTools (
});
for (const agent of [...GROUP_A_767, ...GROUP_B_767]) {
test(`Gemini ${agent}.md has no disallowedTools`, () => {
const agentPath = path.join(geminiHome, 'agents', `${agent}.md`);
test(`Qwen ${agent}.md has no disallowedTools`, () => {
const agentPath = path.join(qwenHome, 'agents', `${agent}.md`);
const content = fs.readFileSync(agentPath, 'utf8');
assert.ok(!content.includes('disallowedTools'),
`${agent} (Gemini) must NOT contain disallowedTools\nContent excerpt:\n${content.slice(0, 400)}`);
`${agent} (Qwen) must NOT contain disallowedTools\nContent excerpt:\n${content.slice(0, 400)}`);
});
}
});
@@ -2589,24 +2588,27 @@ describe('Bug #2979: buildHookCommand for .js hooks emits absolute node runner',
});
describe('Bug #3362 / #3413: Windows hook commands are runtime-aware', () => {
test('Gemini global install: .js hook command starts with & so quoted runners execute in PowerShell', () => {
const cmd = buildHookCommand('C:/Program Files/Gemini/.gemini', 'gsd-check-update.js', {
// #1928: gemini runtime removed — the PowerShell call-operator seam is now
// inert for every runtime. Antigravity (the Gemini-backend successor) never
// needed the call operator either; lock the inert contract explicitly.
test('Antigravity global install: .js hook command stays shell-neutral on Windows (seam inert after gemini removal)', () => {
const cmd = buildHookCommand('C:/Users/me/.gemini/antigravity', 'gsd-check-update.js', {
platform: 'win32',
runtime: 'gemini',
runtime: 'antigravity',
});
assert.ok(cmd.startsWith('& '), `Gemini PowerShell commands need call operator, got: ${cmd}`);
assert.ok(cmd.includes('"C:/Program Files/Gemini/.gemini/hooks/gsd-check-update.js"'));
assert.ok(!cmd.startsWith('& '), `Antigravity hook command must not use PowerShell call operator: ${cmd}`);
assert.ok(cmd.includes('"C:/Users/me/.gemini/antigravity/hooks/gsd-check-update.js"'));
});
test('Gemini portable install: .js hook command also uses & on Windows PowerShell', () => {
test('Antigravity portable install: .js hook command also stays shell-neutral on Windows (seam inert after gemini removal)', () => {
const home = require('node:os').homedir().replace(/\\/g, '/');
const cmd = buildHookCommand(`${home}/.gemini`, 'gsd-check-update.js', {
const cmd = buildHookCommand(`${home}/.gemini/antigravity`, 'gsd-check-update.js', {
portableHooks: true,
platform: 'win32',
runtime: 'gemini',
runtime: 'antigravity',
});
assert.ok(cmd.startsWith('& '), `Gemini PowerShell commands need call operator, got: ${cmd}`);
assert.equal(parseHookCommand(cmd.slice(2)).hookPath, '$HOME/.gemini/hooks/gsd-check-update.js');
assert.ok(!cmd.startsWith('& '), `Antigravity hook command must not use PowerShell call operator: ${cmd}`);
assert.equal(parseHookCommand(cmd).hookPath, '$HOME/.gemini/antigravity/hooks/gsd-check-update.js');
});
test('Claude global install: .js hook command stays shell-neutral on Windows Git Bash', () => {
@@ -2626,12 +2628,12 @@ describe('Bug #3362 / #3413: Windows hook commands are runtime-aware', () => {
assert.equal(parseHookCommand(cmd).hookPath, 'C:/Users/me/.claude/hooks/gsd-check-update.js');
});
test('Gemini runtime on non-Windows platform does not get PowerShell syntax', () => {
test('Antigravity runtime on non-Windows platform does not get PowerShell syntax', () => {
const cmd = buildHookCommand('/home/me/.claude', 'gsd-check-update.js', {
platform: 'linux',
runtime: 'gemini',
runtime: 'antigravity',
});
assert.ok(!cmd.startsWith('& '), `Non-Windows Gemini hook must stay shell-neutral: ${cmd}`);
assert.ok(!cmd.startsWith('& '), `Non-Windows Antigravity hook must stay shell-neutral: ${cmd}`);
assert.equal(parseHookCommand(cmd).hookPath, '/home/me/.claude/hooks/gsd-check-update.js');
});
});
@@ -2722,52 +2724,55 @@ describe('Bug #2979 (#3002 CR): rewriteLegacyManagedNodeHookCommands rewrites ba
assert.equal(settings.hooks.SessionStart[0].hooks[0].command, before);
});
test('Gemini on Windows adds PowerShell call operator to existing quoted managed hooks', () => {
// #1928: gemini runtime removed — the PowerShell call-operator seam is now
// inert for every runtime (including antigravity, the Gemini-backend
// successor). An already-correct absolute-runner command needs no rewrite.
test('Antigravity on Windows leaves an already-correct quoted managed hook untouched (seam inert after gemini removal)', () => {
const settings = {
hooks: {
SessionStart: [{
hooks: [{ type: 'command', command: '"/usr/local/bin/node" "C:/Program Files/Gemini/.gemini/hooks/gsd-check-update.js"' }],
}],
},
};
const runner = '"/usr/local/bin/node"';
const changed = rewriteLegacyManagedNodeHookCommands(settings, runner, { platform: 'win32', runtime: 'gemini' });
assert.equal(changed, true);
assert.equal(
settings.hooks.SessionStart[0].hooks[0].command,
'& "/usr/local/bin/node" "C:/Program Files/Gemini/.gemini/hooks/gsd-check-update.js"',
);
});
test('Gemini on Windows does NOT double-prefix managed hooks that already use the PowerShell call operator', () => {
const settings = {
hooks: {
SessionStart: [{
hooks: [{ type: 'command', command: '& "/usr/local/bin/node" "C:/Program Files/Gemini/.gemini/hooks/gsd-check-update.js"' }],
hooks: [{ type: 'command', command: '"/usr/local/bin/node" "C:/Program Files/Antigravity/.gemini/antigravity/hooks/gsd-check-update.js"' }],
}],
},
};
const runner = '"/usr/local/bin/node"';
const before = settings.hooks.SessionStart[0].hooks[0].command;
const changed = rewriteLegacyManagedNodeHookCommands(settings, runner, { platform: 'win32', runtime: 'gemini' });
const changed = rewriteLegacyManagedNodeHookCommands(settings, runner, { platform: 'win32', runtime: 'antigravity' });
assert.equal(changed, false);
assert.equal(settings.hooks.SessionStart[0].hooks[0].command, before);
});
test('Gemini on Windows rewrites PowerShell bare-node managed hooks to absolute runner without dropping &', () => {
test('Antigravity on Windows strips a stale PowerShell call operator from managed hooks on reinstall (seam inert after gemini removal)', () => {
const settings = {
hooks: {
SessionStart: [{
hooks: [{ type: 'command', command: '& node "C:/Users/me/.gemini/hooks/gsd-check-update.js"' }],
hooks: [{ type: 'command', command: '& "/usr/local/bin/node" "C:/Program Files/Antigravity/.gemini/antigravity/hooks/gsd-check-update.js"' }],
}],
},
};
const runner = '"/usr/local/bin/node"';
const changed = rewriteLegacyManagedNodeHookCommands(settings, runner, { platform: 'win32', runtime: 'gemini' });
const changed = rewriteLegacyManagedNodeHookCommands(settings, runner, { platform: 'win32', runtime: 'antigravity' });
assert.equal(changed, true);
assert.equal(
settings.hooks.SessionStart[0].hooks[0].command,
'& "/usr/local/bin/node" "C:/Users/me/.gemini/hooks/gsd-check-update.js"',
'"/usr/local/bin/node" "C:/Program Files/Antigravity/.gemini/antigravity/hooks/gsd-check-update.js"',
);
});
test('Antigravity on Windows rewrites PowerShell bare-node managed hooks to absolute runner and drops the stale & (seam inert after gemini removal)', () => {
const settings = {
hooks: {
SessionStart: [{
hooks: [{ type: 'command', command: '& node "C:/Users/me/.gemini/antigravity/hooks/gsd-check-update.js"' }],
}],
},
};
const runner = '"/usr/local/bin/node"';
const changed = rewriteLegacyManagedNodeHookCommands(settings, runner, { platform: 'win32', runtime: 'antigravity' });
assert.equal(changed, true);
assert.equal(
settings.hooks.SessionStart[0].hooks[0].command,
'"/usr/local/bin/node" "C:/Users/me/.gemini/antigravity/hooks/gsd-check-update.js"',
);
});
@@ -2901,7 +2906,7 @@ describe('Bug #2979 (#3002 CR): rewriteLegacyManagedNodeHookCommands rewrites ba
assert.equal(changed, true);
});
test('Gemini on Windows normalizes single-quoted managed hook paths to double-quoted forward-slash paths (#3392)', () => {
test('Antigravity on Windows normalizes single-quoted managed hook paths to double-quoted forward-slash paths without adding & (#3392; seam inert after gemini removal)', () => {
const settings = {
hooks: {
PreToolUse: [{
@@ -2913,11 +2918,11 @@ describe('Bug #2979 (#3002 CR): rewriteLegacyManagedNodeHookCommands rewrites ba
},
};
const runner = '"C:/nvm4w/nodejs/node.exe"';
const changed = rewriteLegacyManagedNodeHookCommands(settings, runner, { platform: 'win32', runtime: 'gemini' });
const changed = rewriteLegacyManagedNodeHookCommands(settings, runner, { platform: 'win32', runtime: 'antigravity' });
assert.equal(changed, true);
assert.equal(
settings.hooks.PreToolUse[0].hooks[0].command,
'& "C:/nvm4w/nodejs/node.exe" "C:/Users/me/.gemini/hooks/gsd-prompt-guard.js"',
'"C:/nvm4w/nodejs/node.exe" "C:/Users/me/.gemini/hooks/gsd-prompt-guard.js"',
);
});
});
@@ -4260,20 +4265,20 @@ describe('Bug #410: finishInstall non-Claude runtime + GSD_TEST_MODE side-effect
);
});
test('defaults.json is NOT written for gemini runtime under GSD_TEST_MODE', () => {
test('defaults.json is NOT written for antigravity runtime under GSD_TEST_MODE', () => {
// Reset in case previous test left artifacts (it shouldn't).
assert.equal(
fs.existsSync(DEFAULTS_PATH),
false,
'defaults.json should not exist before gemini test',
'defaults.json should not exist before antigravity test',
);
callFinishInstallForRuntime('gemini');
callFinishInstallForRuntime('antigravity');
assert.equal(
fs.existsSync(DEFAULTS_PATH),
false,
`defaults.json must NOT be created under GSD_TEST_MODE for gemini; found at ${DEFAULTS_PATH}`,
`defaults.json must NOT be created under GSD_TEST_MODE for antigravity; found at ${DEFAULTS_PATH}`,
);
});
@@ -4337,7 +4342,7 @@ describe('Bug #1569: non-Claude finishInstall preserves explicit resolve_model_i
// The clobber guard is runtime-agnostic (`runtime !== 'claude'`); parameterize
// across a representative slice of non-Claude runtimes.
for (const runtime of ['codex', 'opencode', 'gemini']) {
for (const runtime of ['codex', 'opencode', 'antigravity']) {
test(`explicit resolve_model_ids:true survives a ${runtime} global install`, () => {
withUserPath(() => {
seedDefaults({ runtime, resolve_model_ids: true });
@@ -6574,24 +6579,27 @@ describe('Bug #2979: buildHookCommand for .js hooks emits absolute node runner',
});
describe('Bug #3362 / #3413: Windows hook commands are runtime-aware', () => {
test('Gemini global install: .js hook command starts with & so quoted runners execute in PowerShell', () => {
const cmd = buildHookCommand('C:/Program Files/Gemini/.gemini', 'gsd-check-update.js', {
// #1928: gemini runtime removed — the PowerShell call-operator seam is now
// inert for every runtime. Antigravity (the Gemini-backend successor) never
// needed the call operator either; lock the inert contract explicitly.
test('Antigravity global install: .js hook command stays shell-neutral on Windows (seam inert after gemini removal)', () => {
const cmd = buildHookCommand('C:/Users/me/.gemini/antigravity', 'gsd-check-update.js', {
platform: 'win32',
runtime: 'gemini',
runtime: 'antigravity',
});
assert.ok(cmd.startsWith('& '), `Gemini PowerShell commands need call operator, got: ${cmd}`);
assert.ok(cmd.includes('"C:/Program Files/Gemini/.gemini/hooks/gsd-check-update.js"'));
assert.ok(!cmd.startsWith('& '), `Antigravity hook command must not use PowerShell call operator: ${cmd}`);
assert.ok(cmd.includes('"C:/Users/me/.gemini/antigravity/hooks/gsd-check-update.js"'));
});
test('Gemini portable install: .js hook command also uses & on Windows PowerShell', () => {
test('Antigravity portable install: .js hook command also stays shell-neutral on Windows (seam inert after gemini removal)', () => {
const home = require('node:os').homedir().replace(/\\/g, '/');
const cmd = buildHookCommand(`${home}/.gemini`, 'gsd-check-update.js', {
const cmd = buildHookCommand(`${home}/.gemini/antigravity`, 'gsd-check-update.js', {
portableHooks: true,
platform: 'win32',
runtime: 'gemini',
runtime: 'antigravity',
});
assert.ok(cmd.startsWith('& '), `Gemini PowerShell commands need call operator, got: ${cmd}`);
assert.equal(parseHookCommand(cmd.slice(2)).hookPath, '$HOME/.gemini/hooks/gsd-check-update.js');
assert.ok(!cmd.startsWith('& '), `Antigravity hook command must not use PowerShell call operator: ${cmd}`);
assert.equal(parseHookCommand(cmd).hookPath, '$HOME/.gemini/antigravity/hooks/gsd-check-update.js');
});
test('Claude global install: .js hook command stays shell-neutral on Windows Git Bash', () => {
@@ -6611,12 +6619,12 @@ describe('Bug #3362 / #3413: Windows hook commands are runtime-aware', () => {
assert.equal(parseHookCommand(cmd).hookPath, 'C:/Users/me/.claude/hooks/gsd-check-update.js');
});
test('Gemini runtime on non-Windows platform does not get PowerShell syntax', () => {
test('Antigravity runtime on non-Windows platform does not get PowerShell syntax', () => {
const cmd = buildHookCommand('/home/me/.claude', 'gsd-check-update.js', {
platform: 'linux',
runtime: 'gemini',
runtime: 'antigravity',
});
assert.ok(!cmd.startsWith('& '), `Non-Windows Gemini hook must stay shell-neutral: ${cmd}`);
assert.ok(!cmd.startsWith('& '), `Non-Windows Antigravity hook must stay shell-neutral: ${cmd}`);
assert.equal(parseHookCommand(cmd).hookPath, '/home/me/.claude/hooks/gsd-check-update.js');
});
});
@@ -6707,52 +6715,55 @@ describe('Bug #2979 (#3002 CR): rewriteLegacyManagedNodeHookCommands rewrites ba
assert.equal(settings.hooks.SessionStart[0].hooks[0].command, before);
});
test('Gemini on Windows adds PowerShell call operator to existing quoted managed hooks', () => {
// #1928: gemini runtime removed — the PowerShell call-operator seam is now
// inert for every runtime (including antigravity, the Gemini-backend
// successor). An already-correct absolute-runner command needs no rewrite.
test('Antigravity on Windows leaves an already-correct quoted managed hook untouched (seam inert after gemini removal)', () => {
const settings = {
hooks: {
SessionStart: [{
hooks: [{ type: 'command', command: '"/usr/local/bin/node" "C:/Program Files/Gemini/.gemini/hooks/gsd-check-update.js"' }],
}],
},
};
const runner = '"/usr/local/bin/node"';
const changed = rewriteLegacyManagedNodeHookCommands(settings, runner, { platform: 'win32', runtime: 'gemini' });
assert.equal(changed, true);
assert.equal(
settings.hooks.SessionStart[0].hooks[0].command,
'& "/usr/local/bin/node" "C:/Program Files/Gemini/.gemini/hooks/gsd-check-update.js"',
);
});
test('Gemini on Windows does NOT double-prefix managed hooks that already use the PowerShell call operator', () => {
const settings = {
hooks: {
SessionStart: [{
hooks: [{ type: 'command', command: '& "/usr/local/bin/node" "C:/Program Files/Gemini/.gemini/hooks/gsd-check-update.js"' }],
hooks: [{ type: 'command', command: '"/usr/local/bin/node" "C:/Program Files/Antigravity/.gemini/antigravity/hooks/gsd-check-update.js"' }],
}],
},
};
const runner = '"/usr/local/bin/node"';
const before = settings.hooks.SessionStart[0].hooks[0].command;
const changed = rewriteLegacyManagedNodeHookCommands(settings, runner, { platform: 'win32', runtime: 'gemini' });
const changed = rewriteLegacyManagedNodeHookCommands(settings, runner, { platform: 'win32', runtime: 'antigravity' });
assert.equal(changed, false);
assert.equal(settings.hooks.SessionStart[0].hooks[0].command, before);
});
test('Gemini on Windows rewrites PowerShell bare-node managed hooks to absolute runner without dropping &', () => {
test('Antigravity on Windows strips a stale PowerShell call operator from managed hooks on reinstall (seam inert after gemini removal)', () => {
const settings = {
hooks: {
SessionStart: [{
hooks: [{ type: 'command', command: '& node "C:/Users/me/.gemini/hooks/gsd-check-update.js"' }],
hooks: [{ type: 'command', command: '& "/usr/local/bin/node" "C:/Program Files/Antigravity/.gemini/antigravity/hooks/gsd-check-update.js"' }],
}],
},
};
const runner = '"/usr/local/bin/node"';
const changed = rewriteLegacyManagedNodeHookCommands(settings, runner, { platform: 'win32', runtime: 'gemini' });
const changed = rewriteLegacyManagedNodeHookCommands(settings, runner, { platform: 'win32', runtime: 'antigravity' });
assert.equal(changed, true);
assert.equal(
settings.hooks.SessionStart[0].hooks[0].command,
'& "/usr/local/bin/node" "C:/Users/me/.gemini/hooks/gsd-check-update.js"',
'"/usr/local/bin/node" "C:/Program Files/Antigravity/.gemini/antigravity/hooks/gsd-check-update.js"',
);
});
test('Antigravity on Windows rewrites PowerShell bare-node managed hooks to absolute runner and drops the stale & (seam inert after gemini removal)', () => {
const settings = {
hooks: {
SessionStart: [{
hooks: [{ type: 'command', command: '& node "C:/Users/me/.gemini/antigravity/hooks/gsd-check-update.js"' }],
}],
},
};
const runner = '"/usr/local/bin/node"';
const changed = rewriteLegacyManagedNodeHookCommands(settings, runner, { platform: 'win32', runtime: 'antigravity' });
assert.equal(changed, true);
assert.equal(
settings.hooks.SessionStart[0].hooks[0].command,
'"/usr/local/bin/node" "C:/Users/me/.gemini/antigravity/hooks/gsd-check-update.js"',
);
});
@@ -6886,7 +6897,7 @@ describe('Bug #2979 (#3002 CR): rewriteLegacyManagedNodeHookCommands rewrites ba
assert.equal(changed, true);
});
test('Gemini on Windows normalizes single-quoted managed hook paths to double-quoted forward-slash paths (#3392)', () => {
test('Antigravity on Windows normalizes single-quoted managed hook paths to double-quoted forward-slash paths without adding & (#3392; seam inert after gemini removal)', () => {
const settings = {
hooks: {
PreToolUse: [{
@@ -6898,11 +6909,11 @@ describe('Bug #2979 (#3002 CR): rewriteLegacyManagedNodeHookCommands rewrites ba
},
};
const runner = '"C:/nvm4w/nodejs/node.exe"';
const changed = rewriteLegacyManagedNodeHookCommands(settings, runner, { platform: 'win32', runtime: 'gemini' });
const changed = rewriteLegacyManagedNodeHookCommands(settings, runner, { platform: 'win32', runtime: 'antigravity' });
assert.equal(changed, true);
assert.equal(
settings.hooks.PreToolUse[0].hooks[0].command,
'& "C:/nvm4w/nodejs/node.exe" "C:/Users/me/.gemini/hooks/gsd-prompt-guard.js"',
'"C:/nvm4w/nodejs/node.exe" "C:/Users/me/.gemini/hooks/gsd-prompt-guard.js"',
);
});
});
@@ -8245,20 +8256,20 @@ describe('Bug #410: finishInstall non-Claude runtime + GSD_TEST_MODE side-effect
);
});
test('defaults.json is NOT written for gemini runtime under GSD_TEST_MODE', () => {
test('defaults.json is NOT written for antigravity runtime under GSD_TEST_MODE', () => {
// Reset in case previous test left artifacts (it shouldn't).
assert.equal(
fs.existsSync(DEFAULTS_PATH),
false,
'defaults.json should not exist before gemini test',
'defaults.json should not exist before antigravity test',
);
callFinishInstallForRuntime('gemini');
callFinishInstallForRuntime('antigravity');
assert.equal(
fs.existsSync(DEFAULTS_PATH),
false,
`defaults.json must NOT be created under GSD_TEST_MODE for gemini; found at ${DEFAULTS_PATH}`,
`defaults.json must NOT be created under GSD_TEST_MODE for antigravity; found at ${DEFAULTS_PATH}`,
);
});
@@ -8322,7 +8333,7 @@ describe('Bug #1569: non-Claude finishInstall preserves explicit resolve_model_i
// The clobber guard is runtime-agnostic (`runtime !== 'claude'`); parameterize
// across a representative slice of non-Claude runtimes.
for (const runtime of ['codex', 'opencode', 'gemini']) {
for (const runtime of ['codex', 'opencode', 'antigravity']) {
test(`explicit resolve_model_ids:true survives a ${runtime} global install`, () => {
withUserPath(() => {
seedDefaults({ runtime, resolve_model_ids: true });
@@ -9949,7 +9960,7 @@ describe('install.js --skills-root', () => {
{ runtime: 'codex', expected: path.join(os.homedir(), '.codex', 'skills') },
{ runtime: 'copilot', expected: path.join(os.homedir(), '.copilot', 'skills') },
{ runtime: 'cursor', expected: path.join(os.homedir(), '.cursor', 'skills') },
{ runtime: 'gemini', expected: path.join(os.homedir(), '.gemini', 'skills') },
{ runtime: 'trae', expected: path.join(os.homedir(), '.trae', 'skills') },
];
for (const { runtime, expected } of CASES) {

View File

@@ -642,33 +642,10 @@ describe('issue #2517: RUNTIME_PROFILE_MAP single source of truth (finding #16)'
});
});
// ─── Issue #2612: gemini runtime tier resolution ─────────────────────────────
describe('issue #2612: runtime "gemini" — Gemini tier resolution', () => {
let tmpDir;
beforeEach(() => { isolateHome(); tmpDir = createTempProject(); resetRuntimeWarningCaches(); });
afterEach(() => { cleanup(tmpDir); restoreHome(); });
test('opus tier -> gemini-3.1-pro-preview', () => {
writeConfig(tmpDir, { runtime: 'gemini', model_profile: 'quality' });
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-planner'), 'gemini-3.1-pro-preview');
});
test('sonnet tier -> gemini-3-flash', () => {
writeConfig(tmpDir, { runtime: 'gemini', model_profile: 'balanced' });
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-roadmapper'), 'gemini-3-flash');
});
test('haiku tier -> gemini-2.5-flash-lite', () => {
writeConfig(tmpDir, { runtime: 'gemini', model_profile: 'budget' });
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-codebase-mapper'), 'gemini-2.5-flash-lite');
});
test('gemini: effort resolves universally but render param is null (no wire param)', () => {
writeConfig(tmpDir, { runtime: 'gemini', model_profile: 'quality' });
const eff = resolveEffortInternal(tmpDir, 'gsd-planner');
assert.strictEqual(renderEffortForRuntime('gemini', eff).param, null);
});
});
// #1928: the "gemini" runtime tier-resolution suite was removed with the
// sunset Gemini CLI runtime. The gemini-3.x models remain in the catalog for
// Antigravity (which runs on the Gemini backend and carries its own
// runtimeTierDefaults); Antigravity's tier resolution is covered elsewhere.
// ─── Issue #2612: qwen runtime tier resolution ───────────────────────────────
describe('issue #2612: runtime "qwen" — Qwen tier resolution', () => {
@@ -811,20 +788,6 @@ describe('issue #2612: partial override merge for new Group A runtimes', () => {
beforeEach(() => { isolateHome(); tmpDir = createTempProject(); resetRuntimeWarningCaches(); });
afterEach(() => { cleanup(tmpDir); restoreHome(); });
test('gemini.opus override wins; sonnet and haiku use built-in defaults', () => {
writeConfig(tmpDir, {
runtime: 'gemini',
model_profile: 'quality',
model_profile_overrides: {
gemini: { opus: 'gemini-3-ultra' },
},
});
// opus is overridden
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-planner'), 'gemini-3-ultra');
// sonnet not overridden — built-in default (quality -> sonnet for gsd-codebase-mapper)
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-codebase-mapper'), 'gemini-3-flash');
});
test('qwen.opus override wins; sonnet and haiku use built-in defaults', () => {
writeConfig(tmpDir, {
runtime: 'qwen',

View File

@@ -75,11 +75,11 @@ describe('resolveUpdateContext: scope cascade', () => {
});
test('runtime detected but VERSION missing -> 0.0.0, keep scope/runtime', () => {
const fs = fakeFs({ [marker(`${HOME}/.gemini`)]: 'x' });
const fs = fakeFs({ [marker(`${HOME}/.codex`)]: 'x' });
const r = resolveUpdateContext({ home: HOME, cwd: CWD, env: {}, fs });
assert.equal(r.installedVersion, '0.0.0');
assert.equal(r.scope, 'GLOBAL');
assert.equal(r.runtime, 'gemini');
assert.equal(r.runtime, 'codex');
});
test('no install anywhere -> UNKNOWN / claude / empty gsdDir', () => {

View File

@@ -1,119 +0,0 @@
'use strict';
/**
* Regression tests for issue #775: additive Gemini CLI extension manifest.
*
* Asserts structural and semantic correctness of the Gemini Extension Package:
* gemini-extension.json — extension manifest (consumed by
* `gemini extensions install <git-url>`)
* GEMINI.md — the extension's context-file payload, referenced
* by the manifest's `contextFileName` field.
*
* This mirrors tests/issue-766-plugin-manifest.test.cjs (the parallel Claude
* Code plugin manifest) — the Gemini extension is the same artifact-surface
* projection onto Gemini CLI's package contract.
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..');
const identity = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'package-identity.cjs'));
const pkg = require(path.join(ROOT, 'package.json'));
const MANIFEST_PATH = path.join(ROOT, 'gemini-extension.json');
// ─── Section A: gemini-extension.json ────────────────────────────────────────
describe('A: gemini-extension.json', () => {
let manifest;
test('exists and is valid JSON', () => {
assert.ok(fs.existsSync(MANIFEST_PATH), 'gemini-extension.json must exist at repo root');
const raw = fs.readFileSync(MANIFEST_PATH, 'utf-8');
manifest = JSON.parse(raw); // throws on invalid JSON
assert.ok(typeof manifest === 'object' && manifest !== null, 'manifest must be a JSON object');
});
test('name equals identity.binName ("gsd-core")', (t) => {
if (!manifest) { t.skip('manifest could not be parsed'); return; }
assert.equal(manifest.name, identity.binName, `name should be "${identity.binName}"`);
});
test('name is lowercase/dashes only (Gemini extension naming rule)', (t) => {
if (!manifest) { t.skip('manifest could not be parsed'); return; }
// Gemini reference: "lowercase or numbers and use dashes instead of
// underscores or spaces".
assert.match(
manifest.name,
/^[a-z0-9]+(?:-[a-z0-9]+)*$/,
'name must be lowercase letters/numbers with dashes (no underscores, spaces, or uppercase)'
);
});
test('version is a non-empty string matching package.json version', (t) => {
if (!manifest) { t.skip('manifest could not be parsed'); return; }
assert.equal(
manifest.version,
pkg.version,
`gemini-extension.json version (${manifest.version}) must match package.json version (${pkg.version}). ` +
`When bumping the package version, update gemini-extension.json \`version\` to match — ` +
`Gemini CLI's \`gemini extensions update\` keys off the manifest version field. (#775)`
);
});
test('description is a non-empty string (required by Gemini manifest schema)', (t) => {
if (!manifest) { t.skip('manifest could not be parsed'); return; }
assert.ok(
typeof manifest.description === 'string' && manifest.description.trim().length > 0,
'description must be a non-empty string'
);
});
test('contextFileName points to an existing repo-root file', (t) => {
if (!manifest) { t.skip('manifest could not be parsed'); return; }
assert.equal(manifest.contextFileName, 'GEMINI.md', 'contextFileName must be "GEMINI.md"');
const ctx = path.join(ROOT, manifest.contextFileName);
assert.ok(fs.existsSync(ctx), `context file must exist on disk: ${ctx}`);
const body = fs.readFileSync(ctx, 'utf-8');
assert.ok(body.trim().length > 0, 'context file must be non-empty');
});
test('only declares schema-known top-level keys', (t) => {
if (!manifest) { t.skip('manifest could not be parsed'); return; }
// Per Gemini extension reference. We intentionally ship the minimal
// context-loading subset; this guards against typos / unknown keys that
// would fail `gemini extensions install` manifest validation.
const ALLOWED = new Set([
'name', 'version', 'description', 'contextFileName',
'mcpServers', 'excludeTools', 'migratedTo', 'plan', 'settings', 'themes',
]);
for (const key of Object.keys(manifest)) {
assert.ok(ALLOWED.has(key), `Unknown top-level manifest key "${key}" is not in the Gemini extension schema`);
}
});
test('does not declare mcpServers (gsd-core ships no MCP server)', (t) => {
if (!manifest) { t.skip('manifest could not be parsed'); return; }
assert.ok(
!Object.prototype.hasOwnProperty.call(manifest, 'mcpServers'),
'manifest must NOT declare mcpServers — gsd-core has no MCP server'
);
});
});
// ─── Section B: package publication ──────────────────────────────────────────
describe('B: package.json publication', () => {
test('files[] includes gemini-extension.json and GEMINI.md', () => {
const files = pkg.files || [];
for (const required of ['gemini-extension.json', 'GEMINI.md']) {
assert.ok(
files.includes(required),
`package.json "files" must include "${required}" so it ships to npm consumers`
);
}
});
});

View File

@@ -8,6 +8,11 @@
* behavior is asserted via the install module's exported pure functions
* (`runtimeMap`, `allRuntimes`, `parseRuntimeInput`, `buildRuntimePromptText`)
* instead of regexing bin/install.js source text.
*
* #1928: Google sunset Gemini CLI (2026-06-18) and the `gemini` runtime was
* removed from GSD (Antigravity CLI is the successor). The runtime option
* numbering below is renumbered accordingly — option 9 (formerly gemini) is
* now hermes, and the "All" shortcut moved from 17 to 16.
*/
process.env.GSD_TEST_MODE = '1';
@@ -42,62 +47,62 @@ describe('multi-runtime selection parsing', () => {
});
test('comma-separated choices return multiple runtimes', () => {
assert.deepStrictEqual(parseRuntimeInput('1,7,9'), ['claude', 'copilot', 'gemini']);
assert.deepStrictEqual(parseRuntimeInput('1,7,9'), ['claude', 'copilot', 'hermes']);
assert.deepStrictEqual(parseRuntimeInput('2,3'), ['antigravity', 'augment']);
assert.deepStrictEqual(parseRuntimeInput('3,6'), ['augment', 'codex']);
});
test('space-separated choices return multiple runtimes', () => {
assert.deepStrictEqual(parseRuntimeInput('1 7 9'), ['claude', 'copilot', 'gemini']);
assert.deepStrictEqual(parseRuntimeInput('8 12'), ['cursor', 'kilo']);
assert.deepStrictEqual(parseRuntimeInput('1 7 9'), ['claude', 'copilot', 'hermes']);
assert.deepStrictEqual(parseRuntimeInput('8 11'), ['cursor', 'kilo']);
});
test('mixed comma and space separators work', () => {
assert.deepStrictEqual(parseRuntimeInput('1, 7, 9'), ['claude', 'copilot', 'gemini']);
assert.deepStrictEqual(parseRuntimeInput('1, 7, 9'), ['claude', 'copilot', 'hermes']);
assert.deepStrictEqual(parseRuntimeInput('2 , 8'), ['antigravity', 'cursor']);
});
test('single choice for hermes', () => {
assert.deepStrictEqual(parseRuntimeInput('10'), ['hermes']);
assert.deepStrictEqual(parseRuntimeInput('9'), ['hermes']);
});
test('single choice for kilo', () => {
assert.deepStrictEqual(parseRuntimeInput('12'), ['kilo']);
assert.deepStrictEqual(parseRuntimeInput('11'), ['kilo']);
});
test('single choice for opencode', () => {
assert.deepStrictEqual(parseRuntimeInput('13'), ['opencode']);
assert.deepStrictEqual(parseRuntimeInput('12'), ['opencode']);
});
test('single choice for qwen', () => {
assert.deepStrictEqual(parseRuntimeInput('14'), ['qwen']);
assert.deepStrictEqual(parseRuntimeInput('13'), ['qwen']);
});
test('single choice for trae', () => {
assert.deepStrictEqual(parseRuntimeInput('15'), ['trae']);
assert.deepStrictEqual(parseRuntimeInput('14'), ['trae']);
});
test('single choice for windsurf', () => {
assert.deepStrictEqual(parseRuntimeInput('16'), ['windsurf']);
assert.deepStrictEqual(parseRuntimeInput('15'), ['windsurf']);
});
test('single choice for kimi', () => {
assert.deepStrictEqual(parseRuntimeInput('11'), ['kimi']);
assert.deepStrictEqual(parseRuntimeInput('10'), ['kimi']);
});
test('choice 17 returns all runtimes', () => {
assert.deepStrictEqual(parseRuntimeInput('17'), allRuntimes);
test('choice 16 returns all runtimes', () => {
assert.deepStrictEqual(parseRuntimeInput('16'), allRuntimes);
});
test('choice 17 returns all runtimes when mixed with separators or other tokens', () => {
// CR feedback: tokenized inputs that include 17 (e.g. trailing comma, or
test('choice 16 returns all runtimes when mixed with separators or other tokens', () => {
// CR feedback: tokenized inputs that include 16 (e.g. trailing comma, or
// alongside other choices) must still expand to all-runtimes — previously
// only the bare all-runtimes option matched, so "17," or "17 1" silently installed a
// only the bare all-runtimes option matched, so "16," or "16 1" silently installed a
// subset.
assert.deepStrictEqual(parseRuntimeInput('17,'), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput('17 1'), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput('1,17'), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput(' 17 '), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput('16,'), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput('16 1'), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput('1,16'), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput(' 16 '), allRuntimes);
});
test('empty input defaults to claude', () => {
@@ -106,24 +111,24 @@ describe('multi-runtime selection parsing', () => {
});
test('invalid choices are ignored, falls back to claude if all invalid', () => {
assert.deepStrictEqual(parseRuntimeInput('18'), ['claude']);
assert.deepStrictEqual(parseRuntimeInput('17'), ['claude']);
assert.deepStrictEqual(parseRuntimeInput('0'), ['claude']);
assert.deepStrictEqual(parseRuntimeInput('abc'), ['claude']);
});
test('invalid choices mixed with valid are filtered out', () => {
assert.deepStrictEqual(parseRuntimeInput('1,18,7'), ['claude', 'copilot']);
assert.deepStrictEqual(parseRuntimeInput('1,17,7'), ['claude', 'copilot']);
assert.deepStrictEqual(parseRuntimeInput('abc 3 xyz'), ['augment']);
});
test('duplicate choices are deduplicated', () => {
assert.deepStrictEqual(parseRuntimeInput('1,1,1'), ['claude']);
assert.deepStrictEqual(parseRuntimeInput('7,7,9,9'), ['copilot', 'gemini']);
assert.deepStrictEqual(parseRuntimeInput('7,7,9,9'), ['copilot', 'hermes']);
});
test('preserves selection order', () => {
assert.deepStrictEqual(parseRuntimeInput('9,1,7'), ['gemini', 'claude', 'copilot']);
assert.deepStrictEqual(parseRuntimeInput('12,2,8'), ['kilo', 'antigravity', 'cursor']);
assert.deepStrictEqual(parseRuntimeInput('9,1,7'), ['hermes', 'claude', 'copilot']);
assert.deepStrictEqual(parseRuntimeInput('11,2,8'), ['kilo', 'antigravity', 'cursor']);
});
});
@@ -137,18 +142,17 @@ describe('install.js exports multi-select runtime metadata', () => {
'6': 'codex',
'7': 'copilot',
'8': 'cursor',
'9': 'gemini',
'10': 'hermes',
'11': 'kimi',
'12': 'kilo',
'13': 'opencode',
'14': 'qwen',
'15': 'trae',
'16': 'windsurf',
'9': 'hermes',
'10': 'kimi',
'11': 'kilo',
'12': 'opencode',
'13': 'qwen',
'14': 'trae',
'15': 'windsurf',
};
const expectedRuntimes = [
'claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex',
'copilot', 'cursor', 'gemini', 'hermes', 'kimi', 'kilo', 'opencode',
'copilot', 'cursor', 'hermes', 'kimi', 'kilo', 'opencode',
'qwen', 'trae', 'windsurf',
];
@@ -166,8 +170,8 @@ describe('install.js exports multi-select runtime metadata', () => {
'allRuntimes has no duplicates');
});
test('"All" shortcut (option 17) selects every runtime', () => {
assert.deepStrictEqual(parseRuntimeInput('17'), allRuntimes);
test('"All" shortcut (option 16) selects every runtime', () => {
assert.deepStrictEqual(parseRuntimeInput('16'), allRuntimes);
});
test('--kimi flag selects Kimi without interactive prompt', () => {
@@ -181,20 +185,25 @@ describe('install.js exports multi-select runtime metadata', () => {
'--all includes kimi exactly once');
});
test('prompt lists Hermes Agent (10), Kimi (11), Qwen Code (14), Trae (15), and All (17)', () => {
test('prompt lists Hermes Agent (9), Kimi (10), Qwen Code (13), Trae (14), and All (16)', () => {
const prompt = stripAnsi(buildRuntimePromptText());
assert.ok(/\b10\)\s*Hermes Agent\b/.test(prompt),
'prompt lists Hermes Agent as option 10');
assert.ok(/\b11\)\s*Kimi\b/.test(prompt),
'prompt lists Kimi as option 11');
assert.ok(/\b9\)\s*Hermes Agent\b/.test(prompt),
'prompt lists Hermes Agent as option 9');
assert.ok(/\b10\)\s*Kimi\b/.test(prompt),
'prompt lists Kimi as option 10');
assert.ok(/Kimi\s+\(~\/\.config\/agents, then ~\/\.agents if existing\)/.test(prompt),
'prompt shows the Kimi first-existing generic root policy');
assert.ok(/\b14\)\s*Qwen Code\b/.test(prompt),
'prompt lists Qwen Code as option 14');
assert.ok(/\b15\)\s*Trae\b/.test(prompt),
'prompt lists Trae as option 15');
assert.ok(/\b17\)\s*All\b/.test(prompt),
'prompt lists All as option 17');
assert.ok(/\b13\)\s*Qwen Code\b/.test(prompt),
'prompt lists Qwen Code as option 13');
assert.ok(/\b14\)\s*Trae\b/.test(prompt),
'prompt lists Trae as option 14');
assert.ok(/\b16\)\s*All\b/.test(prompt),
'prompt lists All as option 16');
});
test('prompt does not list Gemini (removed #1928)', () => {
const prompt = stripAnsi(buildRuntimePromptText());
assert.ok(!/Gemini/.test(prompt), 'prompt must not mention Gemini');
});
test('prompt text shows multi-select hint', () => {

View File

@@ -32,7 +32,7 @@ const { getDirName } = runtimeNamePolicy;
// OR the registry changed — both require a deliberate golden-list update here.
const EXPECTED = [
'antigravity', 'augment', 'cline', 'codebuddy', 'codex', 'copilot',
'cursor', 'gemini', 'hermes', 'kilo', 'kimi', 'opencode', 'qwen',
'cursor', 'hermes', 'kilo', 'kimi', 'opencode', 'qwen',
'trae', 'windsurf',
];

View File

@@ -3,7 +3,7 @@
/**
* Equivalence proof for ADR-857 phase 5d: descriptor-driven resolveRuntimeArtifactLayout.
*
* For every runtime in the 16-entry capability registry × {global, local} scopes,
* For every runtime in the 15-entry capability registry × {global, local} scopes,
* this test asserts that:
* 1. kind.kind, kind.destSubpath, kind.prefix are byte-identical to the STEP-0
* golden captured from the old switch() before any edits.
@@ -11,11 +11,11 @@
* 3. layout.runtime === runtime, layout.configDir === configDir, layout.scope === scope.
*
* SCOPE-FALL-THROUGH NOTE:
* The old switch() had no scope branches for 13 runtimes (cursor, gemini, codex,
* The old switch() had no scope branches for 12 runtimes (cursor, codex,
* copilot, antigravity, windsurf, augment, trae, qwen, hermes, codebuddy, opencode,
* kilo), meaning scope='local' returned the same kinds as scope='global'. The 5a
* descriptors incorrectly set local:[] for those runtimes, causing 31 local-install
* test regressions. The 5b backfill sets local == global for these 13, restoring
* test regressions. The 5b backfill sets local == global for these 12, restoring
* the old switch's scope-agnostic behaviour.
*
* For runtimes that had explicit scope branches in the old switch
@@ -72,15 +72,6 @@ const GOLDEN = {
{ kind: 'agents', destSubpath: 'agents', prefix: 'gsd-' },
],
// ── gemini ───────────────────────────────────────────────────────────────────
// Old switch: no scope branch → local == global. 5b backfill restores this.
'gemini/global': [
{ kind: 'commands', destSubpath: 'commands/gsd', prefix: 'gsd-' },
],
'gemini/local': [
{ kind: 'commands', destSubpath: 'commands/gsd', prefix: 'gsd-' },
],
// ── codex ────────────────────────────────────────────────────────────────────
// Old switch: no scope branch → local == global. 5b backfill restores this.
'codex/global': [
@@ -217,7 +208,7 @@ const GOLDEN = {
// ── Parametrized tests ────────────────────────────────────────────────────────
const RUNTIMES = [
'claude', 'cursor', 'gemini', 'codex', 'copilot',
'claude', 'cursor', 'codex', 'copilot',
'antigravity', 'windsurf', 'augment', 'trae', 'qwen',
'hermes', 'codebuddy', 'cline', 'kimi', 'opencode', 'kilo',
];

View File

@@ -385,8 +385,7 @@ describe('applySurface', () => {
// command-naming logic.
//
// Matrix: opencode/kilo = flat command/ + prefix gsd-;
// cursor/augment = flat commands/ + prefix gsd-;
// gemini = namespaced commands/gsd/ (no file prefix — dir is namespace).
// cursor/augment = flat commands/ + prefix gsd-.
// For each runtime we: run install into installDir, run applySurface into
// surfaceDir (same 'standard' profile both sides), then compare sorted .md
// filename sets in the commands dest dir. On a fresh dir (no superseded files)
@@ -400,7 +399,7 @@ describe('applySurface', () => {
// the same skill set so any filename difference is purely a naming bug.
const resolvedProfile = resolveProfile({ modes: ['standard'], manifest });
const PARITY_RUNTIMES = ['opencode', 'cursor', 'augment', 'kilo', 'gemini'];
const PARITY_RUNTIMES = ['opencode', 'cursor', 'augment', 'kilo'];
for (const runtime of PARITY_RUNTIMES) {
// Create two independent temp dirs — one for install, one for surface.

View File

@@ -87,19 +87,6 @@ describe('resolveRuntimeArtifactLayout — cursor', () => {
});
});
describe('resolveRuntimeArtifactLayout — gemini', () => {
test('returns correct layout for gemini', () => {
const layout = resolveRuntimeArtifactLayout('gemini', FAKE_DIR);
assert.strictEqual(layout.runtime, 'gemini');
assert.strictEqual(layout.configDir, FAKE_DIR);
assert.strictEqual(layout.kinds.length, 1);
assert.strictEqual(layout.kinds[0].kind, 'commands');
assert.strictEqual(layout.kinds[0].destSubpath, 'commands/gsd');
assert.strictEqual(layout.kinds[0].prefix, 'gsd-');
assert.strictEqual(typeof layout.kinds[0].stage, 'function');
});
});
describe('resolveRuntimeArtifactLayout — codex', () => {
test('returns correct layout for codex', () => {
const layout = resolveRuntimeArtifactLayout('codex', FAKE_DIR);
@@ -367,12 +354,6 @@ describe('resolveRuntimeArtifactLayout edge-cases', () => {
assert.strictEqual(layout.kinds[0].prefix, 'gsd-'); // #947: bare-stem prefix='' reversed
});
test('gemini has one commands kind', () => {
const layout = resolveRuntimeArtifactLayout('gemini', '/tmp/x');
assert.strictEqual(layout.kinds.length, 1);
assert.strictEqual(layout.kinds[0].kind, 'commands');
});
test('claude local has both commands and agents kinds', () => {
const layout = resolveRuntimeArtifactLayout('claude', '/tmp/x', 'local');
const kindNames = layout.kinds.map(k => k.kind);
@@ -441,22 +422,6 @@ const PROFILE_CORE = { skills: CORE_SKILLS, agents: CORE_AGENTS };
const PROFILE_FULL = { skills: '*', agents: new Set() };
const FAKE_STAGE_DIR = '/tmp/fake-config-dir-stage';
describe('stage — commands kind (gemini)', () => {
test('stage returns a directory containing only the selected skill .md files', () => {
const layout = resolveRuntimeArtifactLayout('gemini', FAKE_STAGE_DIR);
const commandsKind = layout.kinds.find(k => k.kind === 'commands');
assert.ok(commandsKind, 'should have a commands kind');
const stagedDir = commandsKind.stage(PROFILE_CORE);
const entries = fs.readdirSync(stagedDir).filter(f => f.endsWith('.md'));
for (const entry of entries) {
const stem = entry.slice(0, -3);
assert.ok(CORE_SKILLS.has(stem), `unexpected skill staged: ${stem}`);
}
assert.ok(entries.length >= 1, 'at least one skill file should be staged');
});
});
describe('stage — agents kind (claude local)', () => {
test('stage returns a valid directory for the agents kind', () => {
const layout = resolveRuntimeArtifactLayout('claude', FAKE_STAGE_DIR, 'local');

View File

@@ -22,7 +22,6 @@ const {
const EXPECTED_TABLE = [
{ runtime: 'claude', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'gemini', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'antigravity', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'augment', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
{ runtime: 'qwen', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
@@ -179,8 +178,8 @@ describe('installSurface correctness', () => {
assert.strictEqual(resolveRuntimeConfigIntent('kimi').installSurface, 'profile-marker-only');
});
test('the 7 passthroughs + opencode + kilo -> "settings-json"', () => {
const settingsJsonRuntimes = ['claude', 'gemini', 'antigravity', 'augment', 'qwen', 'hermes', 'codebuddy', 'opencode', 'kilo'];
test('the 6 passthroughs + opencode + kilo -> "settings-json"', () => {
const settingsJsonRuntimes = ['claude', 'antigravity', 'augment', 'qwen', 'hermes', 'codebuddy', 'opencode', 'kilo'];
for (const runtime of settingsJsonRuntimes) {
assert.strictEqual(
resolveRuntimeConfigIntent(runtime).installSurface,
@@ -212,15 +211,15 @@ describe('resolveRuntimeConfigIntent — fresh object each call', () => {
// ---------------------------------------------------------------------------
describe('ALLOWED_CONFIG_RUNTIMES completeness', () => {
const EXPECTED_16 = new Set([
'claude', 'gemini', 'antigravity', 'augment', 'qwen', 'hermes', 'codebuddy',
const EXPECTED_15 = new Set([
'claude', 'antigravity', 'augment', 'qwen', 'hermes', 'codebuddy',
'opencode', 'kilo', 'codex', 'copilot', 'cline', 'cursor', 'windsurf', 'trae',
'kimi',
]);
test('ALLOWED_CONFIG_RUNTIMES contains exactly the 16 expected runtimes', () => {
test('ALLOWED_CONFIG_RUNTIMES contains exactly the 15 expected runtimes', () => {
const runtimeSet = new Set(ALLOWED_CONFIG_RUNTIMES);
assert.deepStrictEqual(runtimeSet, EXPECTED_16);
assert.deepStrictEqual(runtimeSet, EXPECTED_15);
});
test('every member of ALLOWED_CONFIG_RUNTIMES resolves without throwing', () => {
@@ -229,8 +228,8 @@ describe('ALLOWED_CONFIG_RUNTIMES completeness', () => {
}
});
test('ALLOWED_CONFIG_RUNTIMES has exactly 16 entries', () => {
assert.strictEqual([...ALLOWED_CONFIG_RUNTIMES].length, 16);
test('ALLOWED_CONFIG_RUNTIMES has exactly 15 entries', () => {
assert.strictEqual([...ALLOWED_CONFIG_RUNTIMES].length, 15);
});
});
@@ -317,7 +316,7 @@ describe('resolveInstallPlan — hooksSurface is descriptor-owned', () => {
/**
* Golden-master test for resolveInstallPlan — ADR-857 phase 5g capstone.
*
* Pins the exact InstallPlan shape for all 16 runtimes to guard against
* Pins the exact InstallPlan shape for all 15 runtimes to guard against
* descriptor drift. Derived from actual resolveInstallPlan output at the time
* the seam was introduced (2026-06-11). Behavioral: calls the exported
* function and asserts on typed fields — no source-grep.
@@ -362,16 +361,6 @@ const EXPECTED = {
hooksSurface: 'settings-json',
sandboxTier: 'none',
},
gemini: {
runtime: 'gemini',
installSurface: 'settings-json',
writesSharedSettings: true,
finishPermissionWriter: null,
hookEvents: 'gemini',
extendedHookEvents: ['BeforeAgent', 'AfterAgent', 'BeforeModel'],
hooksSurface: 'settings-json',
sandboxTier: 'none',
},
cursor: {
runtime: 'cursor',
installSurface: 'cursor-hooks-json',
@@ -497,8 +486,8 @@ const EXPECTED = {
const ALL_RUNTIMES = Object.keys(EXPECTED);
describe('resolveInstallPlan — ADR-857 phase 5g golden master', () => {
it('covers exactly 16 runtimes', () => {
assert.strictEqual(ALL_RUNTIMES.length, 16);
it('covers exactly 15 runtimes', () => {
assert.strictEqual(ALL_RUNTIMES.length, 15);
});
for (const runtime of ALL_RUNTIMES) {

View File

@@ -1,5 +1,5 @@
/**
* Runtime Converter Tests — OpenCode + Kilo + Gemini
* Runtime Converter Tests — OpenCode + Kilo
*
* Tests for small runtime-specific conversion functions from install.js.
* Larger runtime test suites (Copilot, Codex, Antigravity) have their own files.
@@ -7,7 +7,6 @@
* OpenCode/Kilo: flat-runtime frontmatter converters (agent + command modes)
* model: inherit is NOT added (runtime uses its configured default model)
* but mode: subagent IS added (required by both runtimes' agents).
* Gemini: convertClaudeToGeminiAgent (frontmatter + tool mapping + body escaping)
*/
const { test, describe } = require('node:test');
@@ -17,7 +16,6 @@ process.env.GSD_TEST_MODE = '1';
const {
convertClaudeToOpencodeFrontmatter,
convertClaudeToKiloFrontmatter,
convertClaudeToGeminiAgent,
convertClaudeAgentToAntigravityAgent,
convertClaudeCommandToOpencodeSkill,
convertClaudeCommandToKiloSkill,
@@ -232,113 +230,29 @@ Fallback skills live in .agents/skills/.`;
}
// ─────────────────────────────────────────────────────────────────────────────
// Gemini CLI agent conversion (merged from gemini-config.test.cjs)
// Antigravity agent conversion — shared Gemini-backend tool mapping (#1394 / #1928)
// ─────────────────────────────────────────────────────────────────────────────
//
// The gemini-RUNTIME's own top-level converter (convertClaudeToGeminiAgent) and
// its dedicated test coverage were removed with the gemini runtime (#1928,
// Google sunset Gemini CLI 2026-06-18). convertGeminiToolName and
// claudeToGeminiTools STAY — they are shared infra reused by Antigravity (which
// runs on the same backend tool-name vocabulary), so the Antigravity-facing
// regression coverage below is retained unchanged.
describe('convertClaudeToGeminiAgent', () => {
test('drops unsupported skills frontmatter while keeping converted tools', () => {
describe('#1394 regression: excludes Skill/SlashCommand from Antigravity frontmatter', () => {
// Skill/SlashCommand are Claude-only tools with no Gemini-backend built-in
// equivalent. Without explicit exclusion they hit the lowercase fallback and
// emit an invalid 'skill'/'slashcommand' tool name, which fails frontmatter
// validation (tools.N: Invalid tool name) and aborts the entire agent load.
// Antigravity reuses convertGeminiToolName (it runs on the Gemini backend),
// so the exclusion intentionally applies there too. Antigravity surfaces GSD
// skills through the skill surface (SKILL.md), not the agent tools: allowlist,
// so dropping the invalid 'skill' tool name does not remove skill access —
// this locks that cross-runtime behavior (criterion 4).
test('Antigravity conversion also excludes Skill/SlashCommand (shared Gemini backend)', () => {
const input = `---
name: gsd-codebase-mapper
description: Explores codebase and writes structured analysis documents.
tools: Read, Bash, Grep, Glob, Write
color: cyan
skills:
- gsd-mapper-workflow
---
<role>
Use \${PHASE} in shell examples.
</role>`;
const result = convertClaudeToGeminiAgent(input);
const frontmatter = result.split('---')[1] || '';
assert.ok(frontmatter.includes('name: gsd-codebase-mapper'), 'keeps name');
assert.ok(frontmatter.includes('description: Explores codebase and writes structured analysis documents.'), 'keeps description');
assert.ok(frontmatter.includes('tools:'), 'adds Gemini tools array');
assert.ok(frontmatter.includes(' - read_file'), 'maps Read -> read_file');
assert.ok(frontmatter.includes(' - run_shell_command'), 'maps Bash -> run_shell_command');
assert.ok(frontmatter.includes(' - search_file_content'), 'maps Grep -> search_file_content');
assert.ok(frontmatter.includes(' - glob'), 'maps Glob -> glob');
assert.ok(frontmatter.includes(' - write_file'), 'maps Write -> write_file');
assert.ok(!frontmatter.includes('color:'), 'drops unsupported color field');
assert.ok(!frontmatter.includes('skills:'), 'drops unsupported skills field');
assert.ok(!frontmatter.includes('gsd-mapper-workflow'), 'drops skills list items');
assert.ok(result.includes('$PHASE'), 'escapes ${PHASE} shell variable for Gemini');
assert.ok(!result.includes('${PHASE}'), 'removes Gemini template-string pattern');
});
test('excludes Claude-only agent interaction tools from Gemini frontmatter', () => {
const input = `---
name: gsd-debug-session-manager
description: Manages debug sessions.
tools: Read, Task, Agent, AskUserQuestion
---
<role>
Coordinate debugger agents.
Offer choices via AskUserQuestion when user input is needed.
</role>`;
const result = convertClaudeToGeminiAgent(input);
const frontmatter = result.split('---')[1] || '';
assert.ok(frontmatter.includes(' - read_file'), 'maps Read -> read_file');
assert.ok(!frontmatter.includes(' - ask_user'), 'does not emit invalid Gemini ask_user tool');
assert.ok(!frontmatter.includes(' - task'), 'does not emit invalid Gemini task tool');
assert.ok(!frontmatter.includes(' - agent'), 'does not emit invalid Gemini agent tool');
assert.ok(!frontmatter.includes('Task'), 'does not preserve Claude-only Task tool');
assert.ok(!frontmatter.includes('Agent'), 'does not preserve Claude-only Agent tool');
assert.ok(!frontmatter.includes('AskUserQuestion'), 'does not preserve Claude-only AskUserQuestion tool');
assert.ok(!result.includes('AskUserQuestion'), 'does not leave Claude-only tool references in the body');
assert.ok(result.includes('conversational prompting'), 'uses runtime-neutral body wording for user prompts');
});
describe('#1394 regression: excludes Skill/SlashCommand from Gemini frontmatter', () => {
// Skill/SlashCommand are Claude-only tools with no Gemini built-in equivalent.
// Without explicit exclusion they hit the lowercase fallback and emit an
// invalid 'skill'/'slashcommand' tool name, which fails Gemini frontmatter
// validation (tools.N: Invalid tool name) and aborts the entire agent load —
// previously killing 22 of 34 GSD agents on Gemini.
// Agent-level assertion against the live install path (criterion 2/3).
// Asserts the emitted frontmatter rather than the internal converter so the
// test exercises the public, install-path-exported API (convertGeminiToolName
// is an internal helper, deliberately not exported per #1559). The Skill,
// SlashCommand, and AskUserQuestion inputs all exercise the exclusion if-block;
// Read/WebFetch exercise the mapped-tool path that must survive.
test('Skill/SlashCommand/AskUserQuestion are dropped from emitted Gemini frontmatter', () => {
const input = `---
name: gsd-planner
description: Creates executable phase plans.
tools: Read, Write, Bash, Glob, Grep, Skill, WebFetch, SlashCommand, AskUserQuestion
---
<role>
Plan the phase.
</role>`;
const result = convertClaudeToGeminiAgent(input);
const frontmatter = result.split('---')[1] || '';
// Mapped tools still convert (the exclusion must not break the happy path).
assert.ok(frontmatter.includes(' - read_file'), 'maps Read -> read_file');
assert.ok(frontmatter.includes(' - web_fetch'), 'maps WebFetch -> web_fetch');
// Claude-only tools with no Gemini equivalent are excluded, not lowercased
// into invalid names that would fail frontmatter validation (#1394 / #3362).
assert.ok(!frontmatter.includes(' - skill'), 'does not emit invalid Gemini skill tool');
assert.ok(!frontmatter.includes(' - slashcommand'), 'does not emit invalid Gemini slashcommand tool');
assert.ok(!frontmatter.includes(' - ask_user'), 'AskUserQuestion remains excluded');
assert.ok(!frontmatter.includes(' - askuserquestion'), 'AskUserQuestion is not lowercased into an invalid tool');
});
// Antigravity reuses convertGeminiToolName (it runs on the Gemini backend),
// so the exclusion intentionally applies there too. Antigravity surfaces GSD
// skills through the skill surface (SKILL.md), not the agent tools: allowlist,
// so dropping the invalid 'skill' tool name does not remove skill access —
// this locks that cross-runtime behavior (criterion 4).
test('Antigravity conversion also excludes Skill/SlashCommand (shared Gemini backend)', () => {
const input = `---
name: gsd-planner
description: Creates executable phase plans.
tools: Read, Write, Bash, Skill, WebFetch, SlashCommand
@@ -346,14 +260,13 @@ tools: Read, Write, Bash, Skill, WebFetch, SlashCommand
<role>Plan the phase.</role>`;
const result = convertClaudeAgentToAntigravityAgent(input);
const toolsLine = result.split('\n').find(l => l.startsWith('tools:')) || '';
const result = convertClaudeAgentToAntigravityAgent(input);
const toolsLine = result.split('\n').find(l => l.startsWith('tools:')) || '';
assert.ok(toolsLine.includes('read_file'), 'maps Read -> read_file');
assert.ok(toolsLine.includes('web_fetch'), 'maps WebFetch -> web_fetch');
assert.ok(!/\bskill\b/.test(toolsLine), 'no invalid skill tool in Antigravity frontmatter');
assert.ok(!/\bslashcommand\b/.test(toolsLine), 'no invalid slashcommand tool in Antigravity frontmatter');
});
assert.ok(toolsLine.includes('read_file'), 'maps Read -> read_file');
assert.ok(toolsLine.includes('web_fetch'), 'maps WebFetch -> web_fetch');
assert.ok(!/\bskill\b/.test(toolsLine), 'no invalid skill tool in Antigravity frontmatter');
assert.ok(!/\bslashcommand\b/.test(toolsLine), 'no invalid slashcommand tool in Antigravity frontmatter');
});
});

View File

@@ -12,7 +12,7 @@ const { runtimeFlags } = require('../gsd-core/bin/lib/runtime-name-policy.cjs');
const registry = require('../gsd-core/bin/lib/capability-registry.cjs');
const EXPECTED_FLAGS = [
'isOpencode', 'isKilo', 'isGemini', 'isCodex', 'isCopilot', 'isAntigravity',
'isOpencode', 'isKilo', 'isCodex', 'isCopilot', 'isAntigravity',
'isCursor', 'isWindsurf', 'isAugment', 'isTrae', 'isQwen', 'isHermes',
'isCodebuddy', 'isCline', 'isKimi',
];
@@ -35,12 +35,12 @@ test('runtimeFlags: claude / unknown / empty → all flags false (fail-closed)',
}
});
test('runtimeFlags: all 15 flags present + boolean + the object is frozen', () => {
test('runtimeFlags: all 14 flags present + boolean + the object is frozen', () => {
const flags = runtimeFlags('opencode');
for (const f of EXPECTED_FLAGS) {
assert.strictEqual(typeof flags[f], 'boolean', `${f} must be boolean`);
}
assert.deepStrictEqual(Object.keys(flags).sort(), [...EXPECTED_FLAGS].sort(), 'exactly the 15 flags');
assert.deepStrictEqual(Object.keys(flags).sort(), [...EXPECTED_FLAGS].sort(), 'exactly the 14 flags');
assert.ok(Object.isFrozen(flags), 'flags object must be frozen');
});

View File

@@ -3,7 +3,7 @@
/**
* Equivalence proof for ADR-857 phase 5b: descriptor-driven getGlobalConfigDir.
*
* For every runtime in the 16-entry capability registry, plus grok and unknown
* For every runtime in the 15-entry capability registry, plus grok and unknown
* runtime, this test asserts that getGlobalConfigDir() produces exactly the
* same path that the old hardcoded switch produced (golden expected values
* captured from the switch BEFORE any edits). All assertions are byte-identical.
@@ -55,7 +55,7 @@ function withEnv(overrides, fn) {
// All env vars for all runtimes — cleared in each test that calls getGlobalConfigDir directly
const ALL_ENV_KEYS = [
'CLAUDE_CONFIG_DIR', 'CURSOR_CONFIG_DIR', 'GEMINI_CONFIG_DIR', 'CODEX_HOME',
'CLAUDE_CONFIG_DIR', 'CURSOR_CONFIG_DIR', 'CODEX_HOME',
'GROK_AGENTS_HOME', 'COPILOT_CONFIG_DIR', 'COPILOT_HOME', 'ANTIGRAVITY_CONFIG_DIR',
'WINDSURF_CONFIG_DIR', 'AUGMENT_CONFIG_DIR', 'TRAE_CONFIG_DIR', 'QWEN_CONFIG_DIR',
'HERMES_HOME', 'CODEBUDDY_CONFIG_DIR', 'CLINE_CONFIG_DIR', 'KIMI_CONFIG_DIR',
@@ -88,7 +88,6 @@ function restoreEnvKeys(saved) {
const GOLDEN_DEFAULTS = {
claude: path.join(HOME, '.claude'),
cursor: path.join(HOME, '.cursor'),
gemini: path.join(HOME, '.gemini'),
codex: path.join(HOME, '.codex'),
grok: path.join(HOME, '.agents'),
copilot: path.join(HOME, '.copilot'),
@@ -144,7 +143,6 @@ describe('descriptor-driven equivalence: env-var overrides', () => {
const cases = [
{ runtime: 'claude', envKey: 'CLAUDE_CONFIG_DIR', value: '/custom/claude' },
{ runtime: 'cursor', envKey: 'CURSOR_CONFIG_DIR', value: '/custom/cursor' },
{ runtime: 'gemini', envKey: 'GEMINI_CONFIG_DIR', value: '/custom/gemini' },
{ runtime: 'codex', envKey: 'CODEX_HOME', value: '/custom/codex' },
{ runtime: 'grok', envKey: 'GROK_AGENTS_HOME', value: '/custom/grok' },
{ runtime: 'augment', envKey: 'AUGMENT_CONFIG_DIR', value: '/custom/augment' },
@@ -841,9 +839,9 @@ describe('descriptor-driven global skills base', () => {
});
});
// ── GOLDEN PARITY: getGlobalConfigDir via process.env for all 16 registry runtimes ──
// ── GOLDEN PARITY: getGlobalConfigDir via process.env for all 15 registry runtimes ──
describe('descriptor-driven parity: 14 non-probe registry runtimes × no-env-vars = golden defaults', () => {
describe('descriptor-driven parity: 13 non-probe registry runtimes × no-env-vars = golden defaults', () => {
// This is the hardest assertion: it drives getGlobalConfigDir() (which calls
// the registry internally) and compares against GOLDEN_DEFAULTS captured from
// the old switch. Any discrepancy means a regression.
@@ -927,7 +925,6 @@ describe('bug #3126: runtime-homes getGlobalConfigDir — defaults', () => {
const defaults = [
['claude', path.join(os.homedir(), '.claude')],
['cursor', path.join(os.homedir(), '.cursor')],
['gemini', path.join(os.homedir(), '.gemini')],
['codex', path.join(os.homedir(), '.codex')],
['copilot', path.join(os.homedir(), '.copilot')],
['antigravity', path.join(os.homedir(), '.gemini', 'antigravity')],

View File

@@ -35,7 +35,7 @@ const registry = require('../gsd-core/bin/lib/capability-registry.cjs');
const { getRuntimeLabel, getRuntimeNewProjectCommand } = runtimeNamePolicy;
// Golden oracle: hardcoded expected map of all 16 runtime ids to their short
// Golden oracle: hardcoded expected map of all 15 runtime ids to their short
// install/uninstall display label. A pinned expected value in a TEST is correct
// — the test IS the oracle (non-circular). Only PRODUCTION code should derive
// dynamically. If this map diverges from getRuntimeLabel output, either the
@@ -44,7 +44,6 @@ const { getRuntimeLabel, getRuntimeNewProjectCommand } = runtimeNamePolicy;
const GOLDEN_LABEL_MAP = {
claude: 'Claude Code',
opencode: 'OpenCode',
gemini: 'Gemini',
kilo: 'Kilo',
codex: 'Codex',
copilot: 'Copilot',
@@ -60,7 +59,7 @@ const GOLDEN_LABEL_MAP = {
cline: 'Cline',
};
test('getRuntimeLabel: golden map matches for all 16 known runtime ids', () => {
test('getRuntimeLabel: golden map matches for all 15 known runtime ids', () => {
for (const [id, expected] of Object.entries(GOLDEN_LABEL_MAP)) {
const actual = getRuntimeLabel(id);
assert.strictEqual(
@@ -108,15 +107,14 @@ test('getRuntimeLabel fallback: alias is NOT auto-expanded (raw id match only)',
// ---------------------------------------------------------------------------
const GOLDEN_COMMAND_MAP = {
// 4 real overrides (the rest use the default):
gemini: '/gsd:new-project',
// 3 real overrides (the rest use the default):
codex: '$gsd-new-project',
cursor: 'gsd-new-project (mention the skill name)',
kimi: '/skill:gsd-new-project',
};
const DEFAULT_CMD = '/gsd-new-project';
test('getRuntimeNewProjectCommand: the 4 overrides + the default for the other 12 runtimes', () => {
test('getRuntimeNewProjectCommand: the 3 overrides + the default for the other 12 runtimes', () => {
for (const [id, expected] of Object.entries(GOLDEN_COMMAND_MAP)) {
assert.strictEqual(getRuntimeLabel ? getRuntimeNewProjectCommand(id) : null, expected, `override ${id}`);
}

View File

@@ -98,8 +98,8 @@ describe('runtime-name-policy getProjectInstructionFile (#1529)', () => {
assert.strictEqual(getProjectInstructionFile('copilot'), '.github/copilot-instructions.md');
});
test('gemini maps to GEMINI.md', () => {
assert.strictEqual(getProjectInstructionFile('gemini'), 'GEMINI.md');
test('gemini is no longer a known runtime — falls back to AGENTS.md (#1928: Gemini CLI runtime removed)', () => {
assert.strictEqual(getProjectInstructionFile('gemini'), 'AGENTS.md');
});
test('antigravity maps to GEMINI.md', () => {
@@ -118,8 +118,9 @@ describe('runtime-name-policy getProjectInstructionFile (#1529)', () => {
assert.strictEqual(getProjectInstructionFile('codex-cli'), 'AGENTS.md');
// opencode-cli is an alias for opencode.
assert.strictEqual(getProjectInstructionFile('opencode-cli'), 'AGENTS.md');
// gemini-cli is an alias for gemini.
assert.strictEqual(getProjectInstructionFile('gemini-cli'), 'GEMINI.md');
// gemini-cli was an alias for gemini; the gemini runtime was removed
// (#1928) so it is now an unrecognized runtime -> safe AGENTS.md default.
assert.strictEqual(getProjectInstructionFile('gemini-cli'), 'AGENTS.md');
// github-copilot is an alias for copilot.
assert.strictEqual(getProjectInstructionFile('github-copilot'), '.github/copilot-instructions.md');
});

View File

@@ -333,14 +333,19 @@ describe('bug #1906: local hook commands use $CLAUDE_PROJECT_DIR', () => {
/**
* Bug #2557: Gemini CLI local hook commands must NOT use $CLAUDE_PROJECT_DIR.
*
* $CLAUDE_PROJECT_DIR is a Claude Code-specific env variable. Gemini CLI does
* $CLAUDE_PROJECT_DIR is a Claude Code-specific env variable. Gemini CLI did
* not set it. On Windows, Gemini's own variable-substitution + path-join logic
* produced a doubled path like `D:\Projects\GSD\'D:\Projects\GSD'`, causing
* every local project hook to fail at SessionStart.
*
* Fix: localPrefix is now runtime-conditional. Gemini/Antigravity use bare
* Fix: localPrefix was made runtime-conditional. Gemini/Antigravity used bare
* dirName (relative path) since they always run project hooks with the project
* dir as cwd. Claude Code and others still use "$CLAUDE_PROJECT_DIR"/ (#1906).
*
* #1928: Google sunset Gemini CLI (2026-06-18) and the `gemini` runtime was
* removed from GSD entirely. `projectLocalHookPrefix` now special-cases only
* `antigravity` — an unrecognized runtime string like the former `'gemini'`
* falls through to the default $CLAUDE_PROJECT_DIR-anchored prefix.
*/
const { describe, test } = require('node:test');
@@ -350,24 +355,21 @@ const path = require('node:path');
const projection = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'shell-command-projection.cjs'));
const { projectLocalHookPrefix, projectShellCommandText } = projection;
describe('bug #2557: Gemini/Antigravity local hooks use relative paths (not $CLAUDE_PROJECT_DIR)', () => {
test('Gemini local prefix is bare dirName', () => {
assert.equal(projectLocalHookPrefix({ runtime: 'gemini', dirName: '.gemini' }), '.gemini');
});
describe('bug #2557: Antigravity local hooks use relative paths (not $CLAUDE_PROJECT_DIR); gemini runtime removed (#1928)', () => {
test('Antigravity local prefix is bare dirName', () => {
assert.equal(projectLocalHookPrefix({ runtime: 'antigravity', dirName: '.agents' }), '.agents');
});
test('non-Gemini local prefix remains $CLAUDE_PROJECT_DIR anchored', () => {
test('non-Antigravity local prefix remains $CLAUDE_PROJECT_DIR anchored', () => {
assert.equal(
projectLocalHookPrefix({ runtime: 'claude', dirName: '.claude' }),
'"$CLAUDE_PROJECT_DIR"/.claude',
);
});
test('Gemini local command projection does not contain "$CLAUDE_PROJECT_DIR"', () => {
test('an unrecognized runtime string (e.g. the former "gemini") falls through to $CLAUDE_PROJECT_DIR anchoring (#1928)', () => {
const prefix = projectLocalHookPrefix({ runtime: 'gemini', dirName: '.gemini' });
assert.equal(prefix, '"$CLAUDE_PROJECT_DIR"/.gemini');
const command = projectShellCommandText({
runnerToken: '"/usr/local/bin/node"',
argTokens: [`${prefix}/hooks/gsd-check-update.js`],
@@ -375,8 +377,8 @@ describe('bug #2557: Gemini/Antigravity local hooks use relative paths (not $CLA
platform: 'linux',
});
assert.ok(
!command.includes('$CLAUDE_PROJECT_DIR'),
'Gemini local command must not include $CLAUDE_PROJECT_DIR',
command.includes('$CLAUDE_PROJECT_DIR'),
'an unrecognized runtime must use the default anchored prefix, not the retired Gemini-only bare-path carve-out',
);
});
});
@@ -413,14 +415,18 @@ const {
const { buildHookCommand, rewriteLegacyManagedNodeHookCommands } = install;
describe('bug #3413: Shell Command Projection Module uses runtime-aware hook policy', () => {
test('Gemini on Windows requires PowerShell call operator', () => {
test('#1928: no current runtime needs the PowerShell call operator (seam inert after gemini removal)', () => {
// Gemini CLI was the only runtime that needed `& ` on Windows; it was
// removed (#1928, Google sunset 2026-06-18). Antigravity — the Gemini-backend
// successor — never matched the old check, so it stays prefix-free. Lock the
// now-inert contract so a future re-enable is a deliberate change.
assert.equal(
hookCommandNeedsPowerShellCallOperator({ platform: 'win32', runtime: 'gemini' }),
true,
hookCommandNeedsPowerShellCallOperator({ platform: 'win32', runtime: 'antigravity' }),
false,
);
assert.equal(
formatHookCommandForRuntime('"C:/node.exe" "C:/hook.js"', { platform: 'win32', runtime: 'gemini' }),
'& "C:/node.exe" "C:/hook.js"',
formatHookCommandForRuntime('"C:/node.exe" "C:/hook.js"', { platform: 'win32', runtime: 'antigravity' }),
'"C:/node.exe" "C:/hook.js"',
);
});
@@ -492,23 +498,31 @@ describe('bug #3439: shell projection module owns managed-hook policy and legacy
});
test('projectLegacySettingsHookCommand normalizes Windows managed paths and runtime wrapper policy', () => {
// #1928: gemini runtime removed — the PowerShell call-operator seam is now
// inert for every runtime (including the former 'gemini' string and its
// Gemini-backend successor 'antigravity'). No `& ` prefix is ever added.
const command = projectLegacySettingsHookCommand({
absoluteRunner: '"C:/nvm4w/nodejs/node.exe"',
scriptPath: 'C:\\Users\\me\\.gemini\\hooks\\gsd-prompt-guard.js',
scriptToken: "'C:\\Users\\me\\.gemini\\hooks\\gsd-prompt-guard.js'",
platform: 'win32',
runtime: 'gemini',
runtime: 'antigravity',
});
assert.equal(command, '& "C:/nvm4w/nodejs/node.exe" "C:/Users/me/.gemini/hooks/gsd-prompt-guard.js"');
assert.equal(command, '"C:/nvm4w/nodejs/node.exe" "C:/Users/me/.gemini/hooks/gsd-prompt-guard.js"');
});
test('projectLocalHookPrefix centralizes runtime-specific project-dir interpolation policy', () => {
assert.equal(projectLocalHookPrefix({ runtime: 'gemini', dirName: '.gemini' }), '.gemini');
assert.equal(projectLocalHookPrefix({ runtime: 'antigravity', dirName: '.agents' }), '.agents');
assert.equal(
projectLocalHookPrefix({ runtime: 'claude', dirName: '.claude' }),
'"$CLAUDE_PROJECT_DIR"/.claude',
);
// #1928: gemini runtime removed — an unrecognized runtime string falls
// through to the default $CLAUDE_PROJECT_DIR-anchored prefix.
assert.equal(
projectLocalHookPrefix({ runtime: 'gemini', dirName: '.gemini' }),
'"$CLAUDE_PROJECT_DIR"/.gemini',
);
});
test('projectPortableHookBaseDir centralizes $HOME interpolation policy', () => {

View File

@@ -274,7 +274,6 @@ const { transformContentToHyphen } = require(path.join(REPO_ROOT, 'scripts', 'fi
// runtime-artifact-layout.cjs MUST appear in exactly one bucket.
const HYPHEN_NAME_AGENT_RUNTIMES = ['claude', 'qwen', 'hermes'];
const SELF_CONVERTING_OR_COLON_RUNTIMES = [
'gemini', // intentionally colon-namespaced
'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment',
'trae', 'codebuddy', 'cline',
'opencode', 'kilo',
@@ -367,12 +366,6 @@ describe('bug #3677 — agent body colon-namespace leak (Claude / Qwen / Hermes)
assert.ok(!out.includes('/gsd:execute-phase'));
});
test('C4: gemini — colon refs preserved (intentional namespace)', () => {
const out = normalizeAgentBodyForRuntime(inputBody, 'gemini', cmdNames);
assert.ok(out.includes('/gsd:execute-phase'), 'Gemini intentionally uses colon namespace; do not rewrite');
assert.ok(!out.includes('/gsd-execute-phase'), 'Gemini agents must NOT have hyphen form');
});
test('C5: self-converting runtime (copilot) — body returned unchanged at this layer', () => {
const out = normalizeAgentBodyForRuntime(inputBody, 'copilot', cmdNames);
// Copilot has its own convertClaudeAgentToCopilotAgent that handles
@@ -785,21 +778,6 @@ function runClaudeLocalInstall(cwd) {
});
}
/**
* Run `node install.js --gemini --local --no-sdk` in tmpDir.
* GSD_TEST_MODE must be cleared so the install() main block executes.
*/
function runGeminiLocalInstall(cwd) {
const env = { ...process.env };
delete env.GSD_TEST_MODE;
execFileSync(process.execPath, [INSTALL_PATH, '--gemini', '--local', '--no-sdk'], {
cwd,
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
env,
});
}
/**
* Build the roster regex that matches `gsd:<known-cmd>` references.
* Mirrors the pattern used by the Cycle 1 command test.
@@ -835,7 +813,7 @@ function collectOffenders(dir, regex) {
// ---------------------------------------------------------------------------
// Suite — integration: staged gsd-core/workflows/ and references/ must
// have no colon-namespace refs for claude, and must preserve them for gemini.
// have no colon-namespace refs for claude.
// ---------------------------------------------------------------------------
describe('bug #3683 — workflow/reference colon-namespace leak (Claude local install)', () => {
@@ -849,8 +827,7 @@ describe('bug #3683 — workflow/reference colon-namespace leak (Claude local in
const rosterRegex = buildRosterRegex(cmdNames);
// Shared claude local install — used by W (workflow/reference clean-slate) and
// R (routing-block positive assertion) sub-suites. G suite runs its own separate
// gemini install and does not depend on claudeTmpDir.
// R (routing-block positive assertion) sub-suites.
before(() => {
claudeTmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3683-claude-'));
runClaudeLocalInstall(claudeTmpDir);
@@ -1115,64 +1092,6 @@ describe('bug #3683 — workflow/reference colon-namespace leak (Claude local in
});
});
// -------------------------------------------------------------------------
// G — negative: gemini install must PRESERVE colon form (no-op normalizer)
// -------------------------------------------------------------------------
describe('G — negative: staged gemini workflows preserve colon-namespace refs', () => {
let tmpDir;
const cmdNames = readCmdNames();
before(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3683-gem-'));
runGeminiLocalInstall(tmpDir);
});
after(() => {
cleanup(tmpDir);
});
test('G0: staged gemini gsd-core/workflows/ directory exists after install', () => {
const workflowsDir = path.join(tmpDir, '.gemini', 'gsd-core', 'workflows');
assert.ok(
fs.existsSync(workflowsDir),
`gemini gsd-core/workflows/ must be created at ${workflowsDir}`,
);
});
test('G1: gemini discuss-phase.md preserves colon form (normalizer is a no-op for gemini)', () => {
// Gemini registers /gsd:<cmd> as its canonical form — normalization
// must NOT fire for this runtime. Verify colon refs survive unchanged.
const stagedFile = path.join(
tmpDir, '.gemini', 'gsd-core', 'workflows', 'discuss-phase.md',
);
assert.ok(
fs.existsSync(stagedFile),
`gemini discuss-phase.md must exist in staged gsd-core/workflows/`,
);
const content = fs.readFileSync(stagedFile, 'utf-8');
// The source has 7 colon refs; at least one must be present in gemini output.
const colonMatches = content.match(/gsd:[a-z][a-z0-9-]*/g) || [];
const knownColonRefs = colonMatches.filter(m => cmdNames.includes(m.slice(4)));
assert.ok(
knownColonRefs.length > 0,
`gemini staged discuss-phase.md must preserve /gsd:<cmd> colon refs — ` +
`they are Gemini's canonical command namespace and must not be rewritten to hyphen form`,
);
});
test('G2: gemini workflows are not over-normalized (no /gsd-- double-hyphen artifacts)', () => {
const workflowsDir = path.join(tmpDir, '.gemini', 'gsd-core', 'workflows');
if (!fs.existsSync(workflowsDir)) return; // guard — G0 already asserts existence
const doubleHyphenRegex = /\/gsd--[a-z]/;
const garbled = collectOffenders(workflowsDir, doubleHyphenRegex);
const relGarbled = garbled.map(f => path.relative(tmpDir, f));
assert.deepEqual(
relGarbled,
[],
`Gemini staged workflows contain /gsd-- double-hyphen artifacts — normalizer ran when it should not have. Garbled: ${relGarbled.join(', ')}`,
);
});
});
});
});
}

View File

@@ -108,13 +108,14 @@ describe('Auto-backmerge needs_review version-manifest carve-out (#1404)', () =>
const workflow = readWorkflow('.github/workflows/auto-backmerge.yml');
test('all version-bearing manifests are filtered via version-only detection', () => {
// package.json / package-lock.json / plugin.json / marketplace.json /
// gemini-extension.json diverge every release; a drop that is ONLY
// "version" lines must not park (parking is what lets the back-merge go
// stale). A substantive change still parks. The grep matches the indented
// "version": line for marketplace.json's plugins[0].version too. (#1404 / #1855)
// package.json / package-lock.json / plugin.json / marketplace.json
// diverge every release; a drop that is ONLY "version" lines must not park
// (parking is what lets the back-merge go stale). A substantive change
// still parks. The grep matches the indented "version": line for
// marketplace.json's plugins[0].version too. (#1404 / #1855)
// #1928: gemini-extension.json was removed with the sunset gemini runtime.
assert.ok(
workflow.includes("VERSION_STAMP_MANIFESTS='package.json package-lock.json .claude-plugin/plugin.json .claude-plugin/marketplace.json gemini-extension.json'"),
workflow.includes("VERSION_STAMP_MANIFESTS='package.json package-lock.json .claude-plugin/plugin.json .claude-plugin/marketplace.json'"),
'auto-backmerge.yml must version-only-filter all version-bearing manifests (incl. marketplace.json #1855)'
);
assert.ok(

View File

@@ -85,7 +85,7 @@
"ui-review.md": 11172,
"ultraplan-phase.md": 10468,
"undo.md": 10431,
"update.md": 21053,
"update.md": 20914,
"validate-phase.md": 10745,
"verify-phase.md": 40728,
"verify-work.md": 35171