diff --git a/.changeset/vivid-foxes-click.md b/.changeset/vivid-foxes-click.md new file mode 100644 index 000000000..e814c6865 --- /dev/null +++ b/.changeset/vivid-foxes-click.md @@ -0,0 +1,5 @@ +--- +type: Removed +pr: 1996 +--- +**Removed the sunset Gemini CLI runtime — use Antigravity CLI instead** — Google discontinued Gemini CLI on 2026-06-18, so `npx gsd-core --gemini` now prints a deprecation notice and points you to Antigravity CLI (the official successor), which GSD already ships as a first-class runtime. (#1928) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f1bfa3727..b408974a8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -37,7 +37,6 @@ body: description: Which AI coding tool are you using GSD with? options: - Claude Code - - Gemini CLI - OpenCode - Codex - Copilot @@ -174,7 +173,6 @@ body: **How to retrieve:** - Claude Code: `cat ~/.claude/settings.json` - - Gemini CLI: `cat ~/.gemini/settings.json` - OpenCode: `cat ~/.config/opencode/opencode.json` or `opencode.jsonc` **⚠️ PII Warning:** This file may contain API keys, tokens, or custom paths. **Remove all API keys and tokens before pasting.** We recommend running through [presidio-anonymizer](https://microsoft.github.io/presidio/) or manually redacting any line containing "key", "token", or "secret". diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 3df867eae..4107215a5 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -170,7 +170,6 @@ body: description: Which runtimes must this work with? Check all that apply. options: - label: Claude Code - - label: Gemini CLI - label: OpenCode - label: Codex - label: Copilot diff --git a/.github/workflows/auto-backmerge.yml b/.github/workflows/auto-backmerge.yml index d9cb9be9e..37fc83931 100644 --- a/.github/workflows/auto-backmerge.yml +++ b/.github/workflows/auto-backmerge.yml @@ -115,7 +115,7 @@ jobs: # filter those out. A substantive change still parks: it leaves # non-"version" lines (deps in package.json; resolved/integrity in the # lockfile when a dependency actually changes). - VERSION_STAMP_MANIFESTS='package.json package-lock.json .claude-plugin/plugin.json .claude-plugin/marketplace.json gemini-extension.json' + VERSION_STAMP_MANIFESTS='package.json package-lock.json .claude-plugin/plugin.json .claude-plugin/marketplace.json' DROPPED=$(printf '%s\n' "$DROPPED" | while IFS= read -r f; do [ -n "$f" ] || continue case " $VERSION_STAMP_MANIFESTS " in diff --git a/CONTEXT.md b/CONTEXT.md index b4a2ffab3..04797d8da 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -131,7 +131,7 @@ Module owning the layout-driven runtime-artifact install pipeline — `installRu Module owning validation for Installer Migration Module records and planned actions. It enforces migration metadata, explicit install scopes, ownership evidence for destructive/config actions, and runtime contract citations for runtime config rewrites before a migration can enter planning or apply. ### Installer Module -Primary installer for all runtimes. Single production file: `bin/install.js` (generated). Exports: `install(isGlobal, runtime[, configDir])` → typed result `{ runtime, configDir, settingsPath, settings, statuslineCommand, updateBannerCommand }`; `uninstall(isGlobal, runtime[, configDir])`; `installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile)`; `uninstallRuntimeArtifacts(runtime, configDir, scope)`; `writeManifest(configDir, runtime)`. Runtime enum: `allRuntimes` (15 values: claude, antigravity, augment, cline, codebuddy, codex, copilot, cursor, gemini, hermes, kilo, opencode, qwen, trae, windsurf). Directory helpers: `getDirName(runtime)` → local dir name; `getConfigDirFromHome(runtime, isGlobal)` → shell-quoted path fragment. Per-runtime global config-dir resolution is delegated to `gsd-core/bin/lib/runtime-homes.cjs:getGlobalConfigDir(runtime[, explicitDir])` — the canonical, env-var–aware projection (`explicitDir` override + opencode/kilo `*_CONFIG` file-path precedence); the legacy in-installer `getGlobalDir`/`getOpencodeGlobalDir`/`getKiloGlobalDir` were retired into it (#56). The same module exposes `detectAntigravityDirAmbiguity(opts)` — a side-effect-free probe reporting whether multiple `~/.gemini/antigravity{,-ide,-cli}` dirs coexist and which one GSD's `gsd-core/VERSION` marker (the `dot-home-nested` `probeExists`) resolves to, for installer / `/gsd-update` operator guidance when a pre-#217 install landed in the wrong sibling dir (#1441). Runtime-specific helpers: `resolveKiloConfigPath(configDir)`, `configureKiloPermissions(isGlobal[, explicitDir])`. Claude-specific permission helpers: `mergeClaudePermissions(settings)` — non-destructively appends GSD-owned allow/deny entries (see `GSD_CLAUDE_ALLOW_PERMISSIONS`, `GSD_CLAUDE_DENY_PERMISSIONS` constants) to a Claude Code settings object; called from `finishInstall` for `runtime === 'claude'` only; uninstall removes exactly these entries (#768). Layout-driven artifact copy/removal delegates to `gsd-core/bin/lib/runtime-artifact-layout.cjs:resolveRuntimeArtifactLayout` (throws `TypeError` for unknown runtimes). Seven runtimes with non-recursive skill loaders (claude global, cline, qwen, hermes, augment, trae, antigravity) use a nested router layout: 6 `gsd-ns-*` router bundles emitted as top-level skills, with concrete skills nested at `/skills//SKILL.md` (hermes prefix='': `skills/gsd/ns-*/…`). The remaining skills-runtimes (cursor, codex, copilot, windsurf, codebuddy, opencode, kilo) use the flat `skills/gsd-/` layout unchanged. See Skill Surface Budget Module and Runtime Artifact Layout Module. +Primary installer for all runtimes. Single production file: `bin/install.js` (generated). Exports: `install(isGlobal, runtime[, configDir])` → typed result `{ runtime, configDir, settingsPath, settings, statuslineCommand, updateBannerCommand }`; `uninstall(isGlobal, runtime[, configDir])`; `installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile)`; `uninstallRuntimeArtifacts(runtime, configDir, scope)`; `writeManifest(configDir, runtime)`. Runtime enum: `allRuntimes` (15 values: claude, antigravity, augment, cline, codebuddy, codex, copilot, cursor, hermes, kilo, kimi, opencode, qwen, trae, windsurf). Directory helpers: `getDirName(runtime)` → local dir name; `getConfigDirFromHome(runtime, isGlobal)` → shell-quoted path fragment. Per-runtime global config-dir resolution is delegated to `gsd-core/bin/lib/runtime-homes.cjs:getGlobalConfigDir(runtime[, explicitDir])` — the canonical, env-var–aware projection (`explicitDir` override + opencode/kilo `*_CONFIG` file-path precedence); the legacy in-installer `getGlobalDir`/`getOpencodeGlobalDir`/`getKiloGlobalDir` were retired into it (#56). The same module exposes `detectAntigravityDirAmbiguity(opts)` — a side-effect-free probe reporting whether multiple `~/.gemini/antigravity{,-ide,-cli}` dirs coexist and which one GSD's `gsd-core/VERSION` marker (the `dot-home-nested` `probeExists`) resolves to, for installer / `/gsd-update` operator guidance when a pre-#217 install landed in the wrong sibling dir (#1441). Runtime-specific helpers: `resolveKiloConfigPath(configDir)`, `configureKiloPermissions(isGlobal[, explicitDir])`. Claude-specific permission helpers: `mergeClaudePermissions(settings)` — non-destructively appends GSD-owned allow/deny entries (see `GSD_CLAUDE_ALLOW_PERMISSIONS`, `GSD_CLAUDE_DENY_PERMISSIONS` constants) to a Claude Code settings object; called from `finishInstall` for `runtime === 'claude'` only; uninstall removes exactly these entries (#768). Layout-driven artifact copy/removal delegates to `gsd-core/bin/lib/runtime-artifact-layout.cjs:resolveRuntimeArtifactLayout` (throws `TypeError` for unknown runtimes). Seven runtimes with non-recursive skill loaders (claude global, cline, qwen, hermes, augment, trae, antigravity) use a nested router layout: 6 `gsd-ns-*` router bundles emitted as top-level skills, with concrete skills nested at `/skills//SKILL.md` (hermes prefix='': `skills/gsd/ns-*/…`). The remaining skills-runtimes (cursor, codex, copilot, windsurf, codebuddy, opencode, kilo) use the flat `skills/gsd-/` layout unchanged. See Skill Surface Budget Module and Runtime Artifact Layout Module. ### I/O Module Module owning the tool's CLI I/O primitives: `output()` result emission (with large-payload temp-file spillover via `GSD_TEMP_DIR`/`ensureGsdTempDir`/`reapStaleTempFiles`), `error()` stderr emission with exit-code mapping, and the JSON-error-mode toggle (`setJsonErrorMode`/`getJsonErrorMode`, `ERROR_REASON`). Extracted from the Core module per ADR-857 rollout phase 1 (#859) so feature modules (`graphify`, `intel`, `audit`, `profile-pipeline`) depend on a small I/O seam instead of the core god-module; the `core.cjs` re-export spine was retired in epic #1267, so callers import this leaf directly. Source of truth: `gsd-core/bin/lib/io.cjs` (generated from `src/io.cts`). @@ -273,9 +273,6 @@ Module owning the explicit per-runtime config-mutation dispatch table for the in ### Claude Code Plugin Manifest Module Module owning the projection of gsd-core's artifact surfaces (`commands`, `agents`, hooks) onto the Claude Code plugin contract (`.claude-plugin/plugin.json` + `hooks/hooks.json`) — the plugin-contract sibling of the Runtime Artifact Layout Module (which projects the same surfaces onto filesystem placements). Defined mapping: `name`=`binName` (drives the `/gsd-core:` command namespace), `repository`/`homepage`=`repoUrl` (Package Identity Module), `version`/`description`/`license` from `package.json` (`version` is required for `claude plugin validate --strict`), `commands`=`./commands/gsd/`, agents via Claude Code's default `agents/` discovery (the explicit string form is schema-rejected), `hooks`=`./hooks/hooks.json`. The hook projection carries ONLY the always-on subset of the Installer Module's Claude `settings.json` wiring (check-update, context-monitor, prompt-guard, read-guard, worktree-path-guard, read-injection-scanner) via `${CLAUDE_PLUGIN_ROOT}`; config-gated opt-in hooks are excluded because a static manifest cannot honor per-project config gates, and plugin-shipped agents cannot carry hook frontmatter (so all plugin-path hook wiring lives in hooks.json). `hooks.json` covers all seven Claude Code lifecycle events: SessionStart, PreToolUse, PostToolUse, SubagentStop, Stop, PreCompact (all wired to context-monitor for context-headroom awareness), and FileChanged (matcher: `config.json` → config-reload, injects `additionalContext` when `.planning/config.json` changes mid-session). Additive — the file-copy path (Runtime Artifact Layout / Install Policy / Installer Modules) is unchanged. Conformance is validated by `claude plugin validate --strict` plus the in-repo drift-guard `tests/issue-766-plugin-manifest.test.cjs`. _Avoid_: "the plugin API", "the plugin file" (when you mean the seam). See ADR-766 and Runtime Artifact Layout Module. -### Gemini Extension Package -The repo-root `gemini-extension.json` + `GEMINI.md` pair that projects gsd-core onto the Gemini CLI extension contract, enabling one-step lifecycle management via `gemini extensions install ` / `update` / `remove` (and `gemini extensions link ` for dev). The Gemini-CLI sibling of the Claude Code Plugin Manifest Module — same additive idea, different runtime package format. Defined mapping: `name`=`binName` (`gsd-core`; lowercase-dashes per Gemini's extension naming rule), `version` tracks `package.json` (Gemini's `gemini extensions update` keys off the manifest `version` field), `description` (required by the manifest schema), `contextFileName`=`GEMINI.md` (the extension's context payload, loaded into every Gemini session). Intentionally minimal: no `mcpServers` (gsd-core ships no MCP server). Slash-command / agent / hook projection into the extension (which would require committing the Gemini-format TOML/agent conversions the Installer Module produces at `--gemini` install time) is deferred — the manual `npx gsd-core --gemini` path remains the way to install the `/gsd:*` commands, and is unchanged (additive, no breaking change). Conformance is guarded by the in-repo drift test `tests/issue-775-gemini-extension.test.cjs` (manifest validity, `version`↔`package.json` parity, `contextFileName` existence, `files[]` publication). _Avoid_: "the Gemini plugin" (Gemini calls them extensions, not plugins). See #775, ADR-766, Claude Code Plugin Manifest Module, and Runtime Artifact Layout Module. - ### Knowledge Graph Module Module owning the graphify integration: tri-state capability gate (`isCapabilityActive('graphify', cwd)` from capability-state.cjs — requires installed AND surfaced AND config-enabled; replaces the former config-only `isGraphifyEnabled` gate, cutover in #1306), disabled response (`disabledResponse`), subprocess helper (`execGraphify`, typed `GRAPHIFY_REASON` enum), presence detection (`checkGraphifyInstalled`), version checking (`checkGraphifyVersion`), query surface (`graphifyQuery` — BFS seed-expand + budget trim), status surface (`graphifyStatus` — node/edge counts, mtime staleness, commit-staleness tri-state via `built_at_commit`/`commits_behind`/`commit_stale`), diff surface (`graphifyDiff` — added/removed/changed nodes+edges), build pre-flight (`graphifyBuild`), snapshot management (`writeSnapshot`). Config leg reads `.planning/config.json:graphify.enabled`; all three legs (install, surface, config) must be active; writes to `.planning/graphs/`. Auto-update hook (`hooks/gsd-graphify-update.sh`) triggers a detached background rebuild after HEAD-advancing git operations on the default branch when `graphify.auto_update=true`. Status file `.planning/graphs/.last-build-status.json` carries `{ ts, status, exit_code, duration_ms, head_at_build, graphify_version }`. Graph IR uses `nodes[]`, `edges[]` (or `links[]` for graphify ≥0.7 compat), `hyperedges[]`, `built_at_commit`. `commit_stale` is tri-state: `false` (known fresh), `true` (stale), `null` (unknown — no git or pre-v0.7 graph). Source: `gsd-core/bin/lib/graphify.cjs`. Skill: `commands/gsd/graphify.md`. @@ -433,9 +430,6 @@ The prompt-level data/instruction isolation seam for untrusted web/document ingr `RULESET.ARGUMENTS-SANITIZE=any workflow step constructing .planning/.../{SLUG}.md path from user input ($ARGUMENTS, parsed remainder) must sanitize inline ([a-z0-9-] only, reject ..//\\, max-length) — "(already sanitized)" must trace back to explicit guard; RESUME/fallback modes need own guards` `RULESET.SHARED-HELPERS-LINT-VS-TEST=when a lint script and test suite both implement same constant (CANONICAL_TOOLS) or parser (parseFrontmatter, executionContextRefs), extract to scripts/*-helpers.cjs required by both — silent divergence otherwise` -`RULESET.GEMINI.TOOLS.ask_user=Gemini CLI has no ask_user tool; filter both AskUserQuestion and lowercase ask_user from tools frontmatter and neutralize both names in body text` -`RULESET.GEMINI.TEST_SENTINEL=convertClaudeToGeminiAgent regression should assert tools excludes ask_user, body excludes AskUserQuestion/ask_user, and Read still maps to read_file` - `RULESET.ADR-HEADER=every docs/adr/NNNN-*.md must open with - **Status:** Accepted|Proposed|Deprecated + - **Date:** YYYY-MM-DD immediately after title` `RULESET.MANIFEST-CANONICAL-KEY=docs/INVENTORY-MANIFEST.json has a single top-level key: families; ALL SIX families.* arrays (agents/commands/workflows/references/cli_modules/hooks) are canonical, consumed by test suites — tests/inventory-manifest-sync.test.cjs reads all six, edit-phase/enh-2380/enh-2430 tests read commands+workflows; the old generated date field and the stale top-level workflows key are both gone; regen via node scripts/gen-inventory-manifest.cjs --write` @@ -465,9 +459,6 @@ The prompt-level data/instruction isolation seam for untrusted web/document ingr `RULESET.CODERABBIT.GUARD.SCOPE=if a new @me open PR appears during final list, include it in the same guard pass before declaring all-open-PRs complete` `RULESET.TESTS.CODERABBIT_FIX=prefer exported-function behavioral tests over source-grep; lint-no-source-grep rejects readFileSync source assertions without allow-test-rule` `RULESET.WORKFLOW_MARKDOWN.FENCES=when editing shell snippets inside workflow markdown, preserve the opening language fence; malformed fence can create fresh CodeRabbit threads` -`RULESET.GEMINI.TOOLS.ask_user=Gemini CLI has no ask_user tool; filter both AskUserQuestion and lowercase ask_user from tools frontmatter and neutralize both names in Gemini body text` -`RULESET.GEMINI.TEST_SENTINEL=convertClaudeToGeminiAgent regression should assert tools excludes ask_user, body excludes AskUserQuestion/ask_user, and Read still maps to read_file` - `CI.GATE.issue-link-required=hard-fail if PR body lacks closes/fixes/resolves #` `CI.GATE.changeset-lint=hard-fail for user-facing code diffs unless .changeset/* or PR has no-changelog label` `CI.GATE.repair-sequence(PR)=create issue -> apply approval label -> edit PR body w/ closing keyword -> apply no-changelog if appropriate -> re-run checks` @@ -830,7 +821,7 @@ Migration plan: Phase 1 (#3465) seam additions complete; Phase 2 (#3466) targets `SESSION.2026-05-09=[8-PR triage wave, 7 merged + 1 subsumed; META.RULE.* introduced; WAVE.LESSON.* captured; k320/k322/k323/k326/k331 evidence; AI Ops Memory predicate format established]` `SESSION.2026-05-10=[ai-ops memory consolidation; release-notes standard taxonomy + templates; RELEASE-NOTES.* predicates introduced]` `SESSION.2026-05-13=[Shell Command Projection Module expansion (#3465-#3468); ADR-0009 superseded; new exports for subprocess dispatch and platform file I/O; phase-gated migration plan; PR #3464 three-gate invariant CI+CR+unresolved=0; PR #3470 stash-include-untracked rebase pattern]` -`SESSION.2026-05-14=[#3095/PR #3490 EXEC.CLASSIFY.* introduced (Anthropic/Copilot/Codex/Gemini cross-runtime rate-limit sentinel coverage); #3489/PR #3499 DEFECT.STATE-TRAMPLE.idempotency-oracle (STATE.md current_phase field is oracle for state.complete-phase); #3488/PR #3501 DAG resolver same-phase short-form depends_on (shortFormToId index added to sdk/src/query/phase.ts); #3491/PR #3502 DEFECT.NESTED-GIT-INIT (gitWorktreeInfoInternal helper); #3493/PR #3500 extractCurrentMilestone generic Phase Details continuation past planned-milestone siblings; #3503/PR #3504 DEFECT.PATH-SUBSTRING-CHECK (trailing-slash anchor for homedir checks); #3346/PR #3505 codex AoT TOML leaf-key via extractFlatHookEventName; #3506/PR #3507 label-scoped stale-bot sub-job pattern; multi-PR triage operational lessons folded into PROC.TRIAGE.*; #3508 DEFECT.AGENT-ISOLATION-SILENT-FAIL; gsd-test image-missing auto-build (locally-built image via embedded heredoc Dockerfile); refined PRED.k322 threshold to 3 PRs/<10min]` +`SESSION.2026-05-14=[#3095/PR #3490 EXEC.CLASSIFY.* introduced (Anthropic/Copilot/Codex/Gemini [runtime removed #1928] cross-runtime rate-limit sentinel coverage); #3489/PR #3499 DEFECT.STATE-TRAMPLE.idempotency-oracle (STATE.md current_phase field is oracle for state.complete-phase); #3488/PR #3501 DAG resolver same-phase short-form depends_on (shortFormToId index added to sdk/src/query/phase.ts); #3491/PR #3502 DEFECT.NESTED-GIT-INIT (gitWorktreeInfoInternal helper); #3493/PR #3500 extractCurrentMilestone generic Phase Details continuation past planned-milestone siblings; #3503/PR #3504 DEFECT.PATH-SUBSTRING-CHECK (trailing-slash anchor for homedir checks); #3346/PR #3505 codex AoT TOML leaf-key via extractFlatHookEventName; #3506/PR #3507 label-scoped stale-bot sub-job pattern; multi-PR triage operational lessons folded into PROC.TRIAGE.*; #3508 DEFECT.AGENT-ISOLATION-SILENT-FAIL; gsd-test image-missing auto-build (locally-built image via embedded heredoc Dockerfile); refined PRED.k322 threshold to 3 PRs/<10min]` `SESSION.2026-05-15=[#3537/PR #3538 DEFECT.PHASE-REGEX-FANOUT — phaseMarkdownRegexSource promoted to core.cjs and wired to 7 sites; parity-style regression test established as DEFECT.GENERATIVE-FIX exemplar; trek-e/gsd-test-runner#1 filed for DEFECT.GSD-TEST-MIRROR-POISONED — chown-back-before-exec legacy gap (poisoned holodeck mirror unstuck via authorized docker chown to remote 1000:1000); RULESET.PR-FLOW.* codified from project CLAUDE.md load-bearing rule; first dispatch under run-tests-before-create held cleanly (PR #3520 worker stopped on Docker exit 12 infra failure, orchestrator opened PR after unblock); CONTEXT.md refactored from 882 lines of mixed prose+predicates into ~500 lines of pure-predicate format with chronological session log]` `SESSION.2026-05-15.parallel-fix-dispatch=[#3542/PR #3546 prohibit git stash family in executor agents (shared refs/stash across worktrees); #3541/PR #3547 non-TTY resolution for installer prompt-user actions (default remove for SDK build artifacts, keep for skills/gsd-*/SKILL.md); #3545 filed for gsd-test-summary concurrent /tmp output collision; new predicates DEFECT.HOOK-OVER-ENFORCEMENT.read-tool-tracking, DEFECT.GSD-TEST-CONCURRENT-OUTPUT-COLLISION, DEFECT.SUBAGENT-LONG-RUNNING-BG-STALL, DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT, PROC.PARALLEL-FIX-DISPATCH; agent-trust-but-verify caught /gsd-update retired-syntax comment slip in #3541 implementation before PR open]` `SESSION.2026-05-16=[multi-PR triage wave (#3577/3581/3640/3641/3642/3648/3649/3637/3639). Established global PreToolUse hook ~/.claude/hooks/test-memory-guard.sh denying new node/test spawns when sum(RSS of node|vitest|jest|...) >= 4 GiB on the 24 GB Mac OR when a same-runner process is already in argv[0] — hard deny via hookSpecificOutput.permissionDecision=deny. PR #3577 fix: revert config-ensure-section dispatch to CJS cmdConfigEnsureSection (SDK author wrote single-section semantics under a name whose legacy callers expect full-default config init); plus 3 SDK parity carve-outs (configNewProject defaults align with sdk/shared/config-defaults.manifest.json, return relative .planning/config.json path, drop quotes from Unknown config key, lead malformed-JSON error with "Failed to read config.json:"). PR #3649 fix: chunk node --test spawn at 28K argv ceiling (Windows CreateProcess lpCommandLine cap 32,767 was instantly aborting unchunked spawn of 546 paths). Chunking fix surfaced 14 pre-existing Windows-only test bugs (4010 pass / 14 fail; vs 0/0 before — entire suite was un-runnable on Windows). PRs #3639 + #3637 confirmed unable to stand alone (legitimately depend on Phase 6 scaffolding only present on feat/3575-enforcement-hardening) — user decision: cherry-pick into #3577 and close. Five other PRs each had ≤1 unresolved CR thread of the changeset-pr-number / null-vs-throw / implicit-Claude-runtime / docs-stale-guidance / hardcoded-tests-path family — all quick wins. New predicates: DEFECT.SDK-PORT-NAME-COLLISION, DEFECT.WINDOWS-ARGV-OVERFLOW, DEFECT.STACKED-PR-CANNOT-STAND-ALONE, DEFECT.CANARY-VERSION-LEAK, DEFECT.GSD-TEST-HOST-MID-RUN-DEATH, RULESET.HARNESS.test-memory-guard, RULESET.PR-FLOW.docker-before-push, RULESET.PR-FLOW.templates-mandatory]` @@ -884,7 +875,7 @@ Migration plan: Phase 1 (#3465) seam additions complete; Phase 2 (#3466) targets `EXEC.CLASSIFY.workflow=gsd-core/workflows/execute-phase.md step 7; class-distinct prompts (quota-to-wait-for-reset; classify-handoff-bug-to-spot-check; unknown-to-continue/stop)` `EXEC.CLASSIFY.classes={class:'quota-exceeded'|'classify-handoff-bug'|'unknown-failure', sentinel?, retryAfterSeconds?}` `EXEC.CLASSIFY.sentinel-order=most specific first: 429 beats too-many-requests; quota beats resource_exhausted; case-insensitive; canonical sentinel value is lower-cased form` -`EXEC.CLASSIFY.cross-runtime=Anthropic/CC: usage limit|rate limit|quota|429|retry-after; Copilot CLI: rate_limit (stem); Codex CLI: 429|usage_limit_reached|too many requests; Gemini CLI: RESOURCE_EXHAUSTED|exceeded your` +`EXEC.CLASSIFY.cross-runtime=Anthropic/CC: usage limit|rate limit|quota|429|retry-after; Copilot CLI: rate_limit (stem); Codex CLI: 429|usage_limit_reached|too many requests` `EXEC.CLASSIFY.precedence=quota sentinel wins over classifyHandoffIfNeeded bug when both appear` `EXEC.CLASSIFY.retry-after-parser=\bretry[-_ ]after[:\s]+(\d+)\b avoids embedded-word false matches like noretry-after` `EXEC.CLASSIFY.proactive-signal-not-usable=Anthropic exposes anthropic-ratelimit-* headers + Agent SDK RateLimitEvent; Claude Code subprocess does NOT forward to hooks/statusline today (upstream #33820, #22407, #32796)` diff --git a/GEMINI.md b/GEMINI.md index 0d298557d..98fc45d8c 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -1,9 +1,13 @@ -# GSD Core — Gemini CLI context +# GSD Core — Antigravity CLI context -This context is loaded by the **gsd-core Gemini CLI extension**. It gives Gemini -the operating context for [GSD Core](https://github.com/open-gsd/gsd-core), a -meta-prompting, context-engineering, and spec-driven development system for AI -coding agents. +> **Gemini CLI was sunset by Google on 2026-06-18** and is no longer served for +> free/Pro/Ultra tiers. Antigravity CLI is its official successor, and this file +> is the context Antigravity reads automatically (its `contextFileName` is +> `GEMINI.md`, inherited from the shared Gemini 3 backend). + +This context gives Antigravity the operating context for +[GSD Core](https://github.com/open-gsd/gsd-core), a meta-prompting, +context-engineering, and spec-driven development system for AI coding agents. ## What GSD is @@ -16,30 +20,28 @@ files rather than in the conversation. ## The slash commands (installed separately) -> **This extension ships only the context above — not the slash commands.** It -> loads gsd's operating context into your Gemini sessions and is managed through -> `gemini extensions list / update / uninstall`. To install the `/gsd:*` command -> set, agents, and hooks into `~/.gemini/`, run the dedicated installer: +> **This file ships only the context above — not the slash commands.** To +> install the `/gsd-*` command set, agents, and hooks into `~/.gemini/antigravity/`, +> run the dedicated installer: > > ```bash -> npx gsd-core --gemini --global +> npx gsd-core --antigravity --global > ``` > -> The two paths are complementary and the manual installer remains fully -> supported. The commands below are available only once that installer has run. +> The commands below are available only once that installer has run. -If you have installed the gsd commands, the workflow is driven by these `/gsd:*` -slash commands (Gemini registers gsd's commands under the `gsd` namespace, so the -colon form is canonical): +If you have installed the gsd commands, the workflow is driven by these `/gsd-*` +slash commands (Antigravity registers gsd's commands under a hyphenated +namespace): -- `/gsd:new-project` — initialise a project and gather deep context. -- `/gsd:progress` — the unified situational command: check progress, advance the +- `/gsd-new-project` — initialise a project and gather deep context. +- `/gsd-progress` — the unified situational command: check progress, advance the workflow, or dispatch a freeform intent. -- `/gsd:plan-phase ` — produce a detailed phase plan with a verification loop. -- `/gsd:execute-phase ` — execute a phase's plans with wave-based parallelism. -- `/gsd:verify-work` — validate built features through conversational UAT. -- `/gsd:ship` — open a PR, run review, and prepare for merge. -- `/gsd:help` — list every available command. +- `/gsd-plan-phase ` — produce a detailed phase plan with a verification loop. +- `/gsd-execute-phase ` — execute a phase's plans with wave-based parallelism. +- `/gsd-verify-work` — validate built features through conversational UAT. +- `/gsd-ship` — open a PR, run review, and prepare for merge. +- `/gsd-help` — list every available command. ## Working with GSD @@ -47,7 +49,7 @@ colon form is canonical): acting, and keep it current as work progresses. - Prefer the smallest change that satisfies the phase's verification criteria. - Run the project's tests and linters before declaring a phase done. -- When unsure what to do next, and the gsd commands are installed, `/gsd:progress` +- When unsure what to do next, and the gsd commands are installed, `/gsd-progress` is the situational entry point. Learn more: diff --git a/README.md b/README.md index db7187704..d18deabc7 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ **English** · [Português](README.pt-BR.md) · [简体中文](README.zh-CN.md) · [日本語](README.ja-JP.md) · [한국어](README.ko-KR.md) -**A light-weight meta-prompting, context engineering, and spec-driven development system for Claude Code, OpenCode, Gemini CLI, Kimi CLI, Kilo, Codex, Copilot, Cursor, Windsurf, and more.** +**A light-weight meta-prompting, context engineering, and spec-driven development system for Claude Code, OpenCode, Antigravity CLI, Kimi CLI, Kilo, Codex, Copilot, Cursor, Windsurf, and more.** [![npm version](https://img.shields.io/npm/v/%40opengsd%2Fgsd-core?style=for-the-badge&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/@opengsd/gsd-core) [![npm downloads](https://img.shields.io/npm/dm/%40opengsd%2Fgsd-core?style=for-the-badge&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/@opengsd/gsd-core) @@ -21,7 +21,7 @@ ## What is GSD Core -GSD Core is a context-engineering and spec-driven development framework that drives AI coding agents (Claude Code, Codex, Gemini CLI, Kimi CLI, Copilot, Cursor, and more) through a disciplined phase loop. It solves [context rot](docs/explanation/context-engineering.md) — the quality degradation that accumulates as an AI fills its context window — by running all heavy research, planning, and execution work in fresh-context subagents while keeping your main session lean. +GSD Core is a context-engineering and spec-driven development framework that drives AI coding agents (Claude Code, Codex, Antigravity CLI, Kimi CLI, Copilot, Cursor, and more) through a disciplined phase loop. It solves [context rot](docs/explanation/context-engineering.md) — the quality degradation that accumulates as an AI fills its context window — by running all heavy research, planning, and execution work in fresh-context subagents while keeping your main session lean. --- @@ -43,7 +43,7 @@ Each milestone repeats the same five-step loop, one phase at a time: npx @opengsd/gsd-core@latest ``` -The installer prompts for your runtime (Claude Code, OpenCode, Gemini CLI, Kimi CLI, Kilo, Codex, Copilot, Cursor, Windsurf, and more) and whether to install globally or locally. The installer is required for cross-runtime compatibility — do not copy files from `agents/` or `commands/` directly. +The installer prompts for your runtime (Claude Code, OpenCode, Antigravity CLI, Kimi CLI, Kilo, Codex, Copilot, Cursor, Windsurf, and more) and whether to install globally or locally. The installer is required for cross-runtime compatibility — do not copy files from `agents/` or `commands/` directly. On another runtime or without Node.js? See [Install on your runtime](docs/how-to/install-on-your-runtime.md). diff --git a/bin/install.js b/bin/install.js index 81c9f3ce4..bc6b891bc 100755 --- a/bin/install.js +++ b/bin/install.js @@ -434,7 +434,7 @@ if (hasMinimal && _profileArgRaw) { function selectRuntimesFromArgs(runtimeArgs) { if (runtimeArgs.includes('--all')) { - return ['claude', 'kimi', 'kilo', 'opencode', 'gemini', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline']; + return ['claude', 'kimi', 'kilo', 'opencode', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline']; } if (runtimeArgs.includes('--both')) { return ['claude', 'opencode']; @@ -443,7 +443,6 @@ function selectRuntimesFromArgs(runtimeArgs) { const selected = []; if (runtimeArgs.includes('--claude')) selected.push('claude'); if (runtimeArgs.includes('--opencode')) selected.push('opencode'); - if (runtimeArgs.includes('--gemini')) selected.push('gemini'); if (runtimeArgs.includes('--kilo')) selected.push('kilo'); if (runtimeArgs.includes('--codex')) selected.push('codex'); if (runtimeArgs.includes('--copilot')) selected.push('copilot'); @@ -463,6 +462,37 @@ function selectRuntimesFromArgs(runtimeArgs) { // Runtime selection - can be set by flags or interactive prompt let selectedRuntimes = selectRuntimesFromArgs(args); +// #1928: Google sunset Gemini CLI on 2026-06-18; Antigravity CLI is its +// official successor. `--gemini` is no longer a valid runtime selector — +// selectRuntimesFromArgs above no longer recognizes it, so it never lands in +// selectedRuntimes. Print a one-time redirect notice, and — when `--gemini` +// was the ONLY runtime flag supplied (selectedRuntimes is empty) — exit +// deterministically rather than silently falling through to the "no runtime +// specified" defaults below (which would install Claude Code, surprising a +// user who explicitly asked for Gemini). Other flags (e.g. `--codex`) still +// parse and install normally alongside the notice. +if (args.includes('--gemini')) { + const wantsHelp = args.includes('--help') || args.includes('-h'); + console.error('Gemini CLI was sunset by Google on 2026-06-18 and is no longer served for free/Pro/Ultra tiers.'); + console.error('GSD now supports Antigravity CLI (the official successor). Re-run with: --antigravity'); + if (hasUninstall) { + // The gemini runtime was removed (#1928), so there is no automated + // `--gemini --uninstall`. Guide manual cleanup and exit — do NOT fall + // through to the uninstall dispatch below, which defaults an empty runtime + // selection to 'claude' and would wrongly uninstall the user's Claude install. + console.error('The gemini runtime was removed, so `--gemini --uninstall` is no longer available.'); + console.error('To remove a prior Gemini install, delete GSD files under your Gemini config dir'); + console.error('(e.g. ~/.gemini/commands/gsd) and GSD hook entries in ~/.gemini/settings.json.'); + process.exit(1); + } + // For `--gemini --help`, fall through so the usage block still prints. For a + // bare install attempt (no other runtime selected), exit rather than silently + // installing Claude. + if (!wantsHelp && selectedRuntimes.length === 0) { + process.exit(1); + } +} + // WSL + Windows Node.js detection // When Windows-native Node runs on WSL, os.homedir() and path.join() produce // backslash paths that don't resolve correctly on the Linux filesystem. @@ -503,7 +533,7 @@ Then re-run: npx ${pkg.name}@latest /** * Get the config directory path relative to home directory for a runtime * Used for templating hooks that use path.join(homeDir, '', ...) - * @param {string} runtime - 'claude', 'opencode', 'gemini', 'codex', or 'copilot' + * @param {string} runtime - 'claude', 'opencode', 'codex', or 'copilot' * @param {boolean} isGlobal - Whether this is a global install */ function getConfigDirFromHome(runtime, isGlobal) { @@ -549,7 +579,7 @@ const banner = '\n' + ' GSD Core ' + dim + 'v' + pkg.version + reset + '\n' + ' Git. Ship. Done.\n' + ' A meta-prompting, context engineering and spec-driven\n' + - ' development workflows for Claude Code, OpenCode, Gemini, Kimi CLI, Kilo, Codex, Copilot, Antigravity, Cursor, Windsurf, Augment, Trae, Qwen Code, Hermes Agent, Cline and CodeBuddy.\n'; + ' development workflows for Claude Code, OpenCode, Kimi CLI, Kilo, Codex, Copilot, Antigravity, Cursor, Windsurf, Augment, Trae, Qwen Code, Hermes Agent, Cline and CodeBuddy.\n'; // Pure seam: parse --config-dir / -c from an arbitrary args array. // Returns the path string, '' for an empty equals-form value, or null when the @@ -606,7 +636,7 @@ if (hasUninstall) { // Show help if requested if (hasHelp) { - console.log(` ${yellow}Usage:${reset} npx ${pkg.name} [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--kilo${reset} Install for Kilo only\n ${cyan}--codex${reset} Install for Codex only\n ${cyan}--kimi${reset} Install for Kimi CLI only\n ${cyan}--copilot${reset} Install for Copilot only\n ${cyan}--antigravity${reset} Install for Antigravity only\n ${cyan}--cursor${reset} Install for Cursor only\n ${cyan}--windsurf${reset} Install for Windsurf only\n ${cyan}--augment${reset} Install for Augment only\n ${cyan}--trae${reset} Install for Trae only\n ${cyan}--qwen${reset} Install for Qwen Code only\n ${cyan}--hermes${reset} Install for Hermes Agent only\n ${cyan}--cline${reset} Install for Cline only\n ${cyan}--codebuddy${reset} Install for CodeBuddy only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}-u, --uninstall${reset} Uninstall GSD (remove all GSD files)\n ${cyan}-c, --config-dir ${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n ${cyan}--portable-hooks${reset} Emit \$HOME-relative hook paths in settings.json\n (for WSL/Docker bind-mount setups; also GSD_PORTABLE_HOOKS=1)\n ${cyan}--profile=${reset} Install a named skill profile. Profiles:\n core — ${PROFILES.core.length} main-loop skills incl. phase (~130 desc tokens)\n standard — ${PROFILES.standard.length} skills incl. phase, review, config (~700)\n full — all skills (default)\n Composable: --profile=core,audit installs union of closures.\n Profile is persisted and respected by \`gsd update\`.\n ${cyan}--minimal${reset} Alias for --profile=core (back-compat).\n Cuts cold-start overhead from ~12k tokens to ~700.\n Alias: --core-only.\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime and location)${reset}\n npx ${pkg.name}\n\n ${dim}# Install for Claude Code globally${reset}\n npx ${pkg.name} --claude --global\n\n ${dim}# Install for Gemini globally${reset}\n npx ${pkg.name} --gemini --global\n\n ${dim}# Install for Kilo globally${reset}\n npx ${pkg.name} --kilo --global\n\n ${dim}# Install for Codex globally${reset}\n npx ${pkg.name} --codex --global\n\n ${dim}# Install for Kimi CLI globally${reset}\n npx ${pkg.name} --kimi --global\n\n ${dim}# Install for Kimi CLI under ~/.kimi-code${reset}\n npx ${pkg.name} --kimi --global --config-dir ~/.kimi-code\n\n ${dim}# Install for Copilot globally${reset}\n npx ${pkg.name} --copilot --global\n\n ${dim}# Install for Copilot locally${reset}\n npx ${pkg.name} --copilot --local\n\n ${dim}# Install for Antigravity globally${reset}\n npx ${pkg.name} --antigravity --global\n\n ${dim}# Install for Antigravity locally${reset}\n npx ${pkg.name} --antigravity --local\n\n ${dim}# Install for Cursor globally${reset}\n npx ${pkg.name} --cursor --global\n\n ${dim}# Install for Cursor locally${reset}\n npx ${pkg.name} --cursor --local\n\n ${dim}# Install for Windsurf globally${reset}\n npx ${pkg.name} --windsurf --global\n\n ${dim}# Install for Windsurf locally${reset}\n npx ${pkg.name} --windsurf --local\n\n ${dim}# Install for Augment globally${reset}\n npx ${pkg.name} --augment --global\n\n ${dim}# Install for Augment locally${reset}\n npx ${pkg.name} --augment --local\n\n ${dim}# Install for Trae globally${reset}\n npx ${pkg.name} --trae --global\n\n ${dim}# Install for Trae locally${reset}\n npx ${pkg.name} --trae --local\n\n ${dim}# Install for Hermes Agent globally${reset}\n npx ${pkg.name} --hermes --global\n\n ${dim}# Install for Hermes Agent locally${reset}\n npx ${pkg.name} --hermes --local\n\n ${dim}# Install for Cline globally${reset}\n npx ${pkg.name} --cline --global\n\n ${dim}# Install for Cline locally${reset}\n npx ${pkg.name} --cline --local\n\n ${dim}# Install for CodeBuddy globally${reset}\n npx ${pkg.name} --codebuddy --global\n\n ${dim}# Install for CodeBuddy locally${reset}\n npx ${pkg.name} --codebuddy --local\n\n ${dim}# Install for all runtimes globally${reset}\n npx ${pkg.name} --all --global\n\n ${dim}# Install to custom config directory${reset}\n npx ${pkg.name} --kilo --global --config-dir ~/.kilo-work\n\n ${dim}# Install to current project only${reset}\n npx ${pkg.name} --claude --local\n\n ${dim}# Uninstall GSD from Cursor globally${reset}\n npx ${pkg.name} --cursor --global --uninstall\n\n ${yellow}Notes:${reset}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over CLAUDE_CONFIG_DIR / OPENCODE_CONFIG_DIR / GEMINI_CONFIG_DIR / KILO_CONFIG_DIR / CODEX_HOME / KIMI_CONFIG_DIR / COPILOT_CONFIG_DIR / COPILOT_HOME / ANTIGRAVITY_CONFIG_DIR / CURSOR_CONFIG_DIR / WINDSURF_CONFIG_DIR / AUGMENT_CONFIG_DIR / TRAE_CONFIG_DIR / QWEN_CONFIG_DIR / HERMES_HOME / CLINE_CONFIG_DIR / CODEBUDDY_CONFIG_DIR environment variables.\n Kimi CLI defaults to the first existing generic skills root: ${cyan}~/.config/agents/skills${reset}, then ${cyan}~/.agents/skills${reset}; if neither exists, GSD creates ${cyan}~/.config/agents${reset}.\n Use ${cyan}--config-dir ~/.kimi-code${reset} or ${cyan}KIMI_CONFIG_DIR=~/.kimi-code${reset} for brand-specific Kimi installs.\n`); + console.log(` ${yellow}Usage:${reset} npx ${pkg.name} [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--kilo${reset} Install for Kilo only\n ${cyan}--codex${reset} Install for Codex only\n ${cyan}--kimi${reset} Install for Kimi CLI only\n ${cyan}--copilot${reset} Install for Copilot only\n ${cyan}--antigravity${reset} Install for Antigravity only\n ${cyan}--cursor${reset} Install for Cursor only\n ${cyan}--windsurf${reset} Install for Windsurf only\n ${cyan}--augment${reset} Install for Augment only\n ${cyan}--trae${reset} Install for Trae only\n ${cyan}--qwen${reset} Install for Qwen Code only\n ${cyan}--hermes${reset} Install for Hermes Agent only\n ${cyan}--cline${reset} Install for Cline only\n ${cyan}--codebuddy${reset} Install for CodeBuddy only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}-u, --uninstall${reset} Uninstall GSD (remove all GSD files)\n ${cyan}-c, --config-dir ${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n ${cyan}--portable-hooks${reset} Emit \$HOME-relative hook paths in settings.json\n (for WSL/Docker bind-mount setups; also GSD_PORTABLE_HOOKS=1)\n ${cyan}--profile=${reset} Install a named skill profile. Profiles:\n core — ${PROFILES.core.length} main-loop skills incl. phase (~130 desc tokens)\n standard — ${PROFILES.standard.length} skills incl. phase, review, config (~700)\n full — all skills (default)\n Composable: --profile=core,audit installs union of closures.\n Profile is persisted and respected by \`gsd update\`.\n ${cyan}--minimal${reset} Alias for --profile=core (back-compat).\n Cuts cold-start overhead from ~12k tokens to ~700.\n Alias: --core-only.\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime and location)${reset}\n npx ${pkg.name}\n\n ${dim}# Install for Claude Code globally${reset}\n npx ${pkg.name} --claude --global\n\n ${dim}# Install for Kilo globally${reset}\n npx ${pkg.name} --kilo --global\n\n ${dim}# Install for Codex globally${reset}\n npx ${pkg.name} --codex --global\n\n ${dim}# Install for Kimi CLI globally${reset}\n npx ${pkg.name} --kimi --global\n\n ${dim}# Install for Kimi CLI under ~/.kimi-code${reset}\n npx ${pkg.name} --kimi --global --config-dir ~/.kimi-code\n\n ${dim}# Install for Copilot globally${reset}\n npx ${pkg.name} --copilot --global\n\n ${dim}# Install for Copilot locally${reset}\n npx ${pkg.name} --copilot --local\n\n ${dim}# Install for Antigravity globally${reset}\n npx ${pkg.name} --antigravity --global\n\n ${dim}# Install for Antigravity locally${reset}\n npx ${pkg.name} --antigravity --local\n\n ${dim}# Install for Cursor globally${reset}\n npx ${pkg.name} --cursor --global\n\n ${dim}# Install for Cursor locally${reset}\n npx ${pkg.name} --cursor --local\n\n ${dim}# Install for Windsurf globally${reset}\n npx ${pkg.name} --windsurf --global\n\n ${dim}# Install for Windsurf locally${reset}\n npx ${pkg.name} --windsurf --local\n\n ${dim}# Install for Augment globally${reset}\n npx ${pkg.name} --augment --global\n\n ${dim}# Install for Augment locally${reset}\n npx ${pkg.name} --augment --local\n\n ${dim}# Install for Trae globally${reset}\n npx ${pkg.name} --trae --global\n\n ${dim}# Install for Trae locally${reset}\n npx ${pkg.name} --trae --local\n\n ${dim}# Install for Hermes Agent globally${reset}\n npx ${pkg.name} --hermes --global\n\n ${dim}# Install for Hermes Agent locally${reset}\n npx ${pkg.name} --hermes --local\n\n ${dim}# Install for Cline globally${reset}\n npx ${pkg.name} --cline --global\n\n ${dim}# Install for Cline locally${reset}\n npx ${pkg.name} --cline --local\n\n ${dim}# Install for CodeBuddy globally${reset}\n npx ${pkg.name} --codebuddy --global\n\n ${dim}# Install for CodeBuddy locally${reset}\n npx ${pkg.name} --codebuddy --local\n\n ${dim}# Install for all runtimes globally${reset}\n npx ${pkg.name} --all --global\n\n ${dim}# Install to custom config directory${reset}\n npx ${pkg.name} --kilo --global --config-dir ~/.kilo-work\n\n ${dim}# Install to current project only${reset}\n npx ${pkg.name} --claude --local\n\n ${dim}# Uninstall GSD from Cursor globally${reset}\n npx ${pkg.name} --cursor --global --uninstall\n\n ${yellow}Notes:${reset}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over CLAUDE_CONFIG_DIR / OPENCODE_CONFIG_DIR / KILO_CONFIG_DIR / CODEX_HOME / KIMI_CONFIG_DIR / COPILOT_CONFIG_DIR / COPILOT_HOME / ANTIGRAVITY_CONFIG_DIR / CURSOR_CONFIG_DIR / WINDSURF_CONFIG_DIR / AUGMENT_CONFIG_DIR / TRAE_CONFIG_DIR / QWEN_CONFIG_DIR / HERMES_HOME / CLINE_CONFIG_DIR / CODEBUDDY_CONFIG_DIR environment variables.\n Kimi CLI defaults to the first existing generic skills root: ${cyan}~/.config/agents/skills${reset}, then ${cyan}~/.agents/skills${reset}; if neither exists, GSD creates ${cyan}~/.config/agents${reset}.\n Use ${cyan}--config-dir ~/.kimi-code${reset} or ${cyan}KIMI_CONFIG_DIR=~/.kimi-code${reset} for brand-specific Kimi installs.\n`); process.exit(0); } @@ -1379,7 +1409,7 @@ const attributionCache = new Map(); /** * Get commit attribution setting for a runtime - * @param {string} runtime - 'claude', 'opencode', 'gemini', 'codex', or 'copilot' + * @param {string} runtime - 'claude', 'opencode', 'codex', or 'copilot' * @returns {null|undefined|string} null = remove, undefined = keep default, string = custom */ function getCommitAttribution(runtime) { @@ -1396,16 +1426,6 @@ function getCommitAttribution(runtime) { : resolveKiloConfigPath; const config = readSettings(resolveConfigPath(getGlobalConfigDir(runtime, null))); result = (config && config.disable_ai_attribution === true) ? null : undefined; - } else if (runtime === 'gemini') { - // Gemini: check gemini settings.json for attribution config - const settings = readSettings(path.join(getGlobalConfigDir('gemini', explicitConfigDir), 'settings.json')); - if (!settings || !settings.attribution || settings.attribution.commit === undefined) { - result = undefined; - } else if (settings.attribution.commit === '') { - result = null; - } else { - result = settings.attribution.commit; - } } else if (runtime === 'claude') { // Claude Code const settings = readSettings(path.join(getGlobalConfigDir('claude', explicitConfigDir), 'settings.json')); @@ -5718,11 +5738,6 @@ function installCodexConfig(targetDir, agentsSrc, sandboxTier = 'codex-agent-san return agents.length; } -/** - * Strip HTML tags for Gemini CLI output - * Terminals don't support subscript — Gemini renders these as raw HTML. - * Converts text to italic *(text)* for readable terminal output. - */ /** * Runtime-neutral agent name and instruction file replacement. * Used by ALL non-Claude runtime converters to avoid Claude-specific @@ -5753,200 +5768,6 @@ function neutralizeAgentReferences(content, instructionFile) { return c; } -function stripSubTags(content) { - return content.replace(/(.*?)<\/sub>/g, '*($1)*'); -} - -/** - * Convert Claude Code agent frontmatter to Gemini CLI format - * Gemini agents use .md files with YAML frontmatter, same as Claude, - * but with different field names and formats: - * - tools: must be a YAML array (not comma-separated string) - * - tool names: must use Gemini built-in names (read_file, not Read) - * - color: must be removed (causes validation error) - * - skills: must be removed (causes validation error) - * - mcp__* tools: must be excluded (auto-discovered at runtime) - */ -let _gsdCommandRoster = null; -let _gsdCommandRosterWarned = false; - -/** - * Get the list of known GSD commands from the source directory. - * Caches the result after the first scan. Emits a one-shot warning if the - * source directory cannot be located — an empty roster silently neutralises - * every Gemini slash-command conversion, which is the bug this code exists - * to prevent. The warning is gated on GSD_TEST_MODE to keep test output clean. - * @returns {Set} Set of command names (without .md extension) - */ -function getGsdCommandRoster() { - if (_gsdCommandRoster) return _gsdCommandRoster; - const baseDir = (typeof __dirname !== 'undefined') ? __dirname : process.cwd(); - const gsdSrc = path.join(baseDir, '..', 'commands', 'gsd'); - if (fs.existsSync(gsdSrc)) { - _gsdCommandRoster = new Set( - fs.readdirSync(gsdSrc) - .filter(f => f.endsWith('.md')) - .map(f => f.replace('.md', '')) - ); - } else { - _gsdCommandRoster = new Set(); - if (!_gsdCommandRosterWarned && !process.env.GSD_TEST_MODE) { - _gsdCommandRosterWarned = true; - console.warn( - `WARNING: GSD command roster not found at ${gsdSrc}. ` + - `Gemini /gsd- → /gsd: conversion will be a no-op. ` + - `This usually means the package was installed without commands/gsd/.` - ); - } - } - return _gsdCommandRoster; -} - -// Test-only: reset the cached roster. Exported via GSD_TEST_MODE bundle below. -function _resetGsdCommandRoster() { - _gsdCommandRoster = null; - _gsdCommandRosterWarned = false; -} - -function convertSlashCommandsToGeminiMentions(content) { - const commands = getGsdCommandRoster(); - // Defense in depth: regex boundary AND roster lookup must both agree. - // - // - Lookbehind `(? { - return commands.has(commandName) ? `/gsd:${commandName}` : match; - }); -} - -function convertClaudeToGeminiMarkdown(content, { isCommand = false, commandName = null } = {}) { - // Apply Gemini-specific slash command namespacing - let converted = convertSlashCommandsToGeminiMentions(content); - // Gemini CLI does not expose Claude's AskUserQuestion tool. Convert body - // references to runtime-neutral wording so converted agents do not instruct - // Gemini to call a nonexistent tool (#3362). - converted = converted.replace(/\b(?:AskUserQuestion|ask_user)\b/g, 'conversational prompting'); - // Strip HTML subscript tags — terminals can't render them. Done before - // TOML conversion so the prompt body of a command file is also clean. - converted = stripSubTags(converted); - - if (isCommand) { - // Convert to Gemini TOML format (threads the command name so per-command - // enrichment — e.g. the #778 live-state injection — can target a command). - converted = convertClaudeToGeminiToml(converted, { commandName }); - } - - return converted; -} - -function convertClaudeToGeminiAgent(content) { - if (!content.startsWith('---')) return content; - - const endIndex = content.indexOf('---', 3); - if (endIndex === -1) return content; - - const frontmatter = content.substring(3, endIndex).trim(); - const body = content.substring(endIndex + 3); - - const lines = frontmatter.split('\n'); - const newLines = []; - let inAllowedTools = false; - let inSkippedArrayField = false; - const tools = []; - - for (const line of lines) { - const trimmed = line.trim(); - - if (inSkippedArrayField) { - if (!trimmed || trimmed.startsWith('- ')) { - continue; - } - inSkippedArrayField = false; - } - - // Convert allowed-tools YAML array to tools list - if (trimmed.startsWith('allowed-tools:')) { - inAllowedTools = true; - continue; - } - - // Handle inline tools: field (comma-separated string) - if (trimmed.startsWith('tools:')) { - const toolsValue = trimmed.substring(6).trim(); - if (toolsValue) { - const parsed = toolsValue.split(',').map(t => t.trim()).filter(t => t); - for (const t of parsed) { - const mapped = convertGeminiToolName(t); - if (mapped) tools.push(mapped); - } - } else { - // tools: with no value means YAML array follows - inAllowedTools = true; - } - continue; - } - - // Strip color field (not supported by Gemini CLI, causes validation error) - if (trimmed.startsWith('color:')) continue; - - // Strip skills field (not supported by Gemini CLI, causes validation error) - if (trimmed.startsWith('skills:')) { - inSkippedArrayField = true; - continue; - } - - // Collect allowed-tools/tools array items - if (inAllowedTools) { - if (trimmed.startsWith('- ')) { - const mapped = convertGeminiToolName(trimmed.substring(2).trim()); - if (mapped) tools.push(mapped); - continue; - } else if (trimmed && !trimmed.startsWith('-')) { - inAllowedTools = false; - } - } - - if (!inAllowedTools) { - newLines.push(line); - } - } - - // Add tools as YAML array (Gemini requires array format) - if (tools.length > 0) { - newLines.push('tools:'); - for (const tool of tools) { - newLines.push(` - ${tool}`); - } - } - - const newFrontmatter = newLines.join('\n').trim(); - - // Escape ${VAR} patterns in agent body for Gemini CLI compatibility. - // Gemini's templateString() treats all ${word} patterns as template variables - // and throws "Template validation failed: Missing required input parameters" - // when they can't be resolved. GSD agents use ${PHASE}, ${PLAN}, etc. as - // shell variables in bash code blocks — convert to $VAR (no braces) which - // is equivalent bash and invisible to Gemini's /\$\{(\w+)\}/g regex. - const escapedBody = body.replace(/\$\{(\w+)\}/g, '$$$1'); - - // Runtime-neutral agent name replacement (#766) - const neutralBody = neutralizeAgentReferences(escapedBody, 'GEMINI.md'); - // Apply Gemini-specific transformations (slash commands + sub-tag stripping) - const geminiBody = convertClaudeToGeminiMarkdown(neutralBody); - return `---\n${newFrontmatter}\n---${geminiBody}`; -} - function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOverride = null } = {}) { // Replace tool name references in content (applies to all files) let convertedContent = content; @@ -6283,76 +6104,6 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false } = {}) { // convertClaudeCommandToKiloSkill: moved to src/install-engine.cts (ADR-1239 Phase B). // Imported from installEngine above. -/** - * Convert Claude Code markdown command to Gemini TOML format - * @param {string} content - Markdown file content with YAML frontmatter - * @returns {string} - TOML content - */ -function convertClaudeToGeminiToml(content, { commandName = null } = {}) { - // #778 (c) — Gemini {{args}} interpolation. Claude's $ARGUMENTS placeholder - // maps to Gemini's {{args}} so inline argument references interpolate into the - // command body instead of being emitted as a dead literal. Applied before - // frontmatter parsing so every return path benefits (a command's frontmatter - // never contains $ARGUMENTS, so this is body-only in practice). Gemini injects - // {{args}} as typed outside shell blocks; we never place it inside a !{...} - // block, so there is no shell-escaping/injection interaction. - content = content.replace(/\$ARGUMENTS\b/g, '{{args}}'); - - // Check if content has frontmatter - if (!content.startsWith('---')) { - return `prompt = ${JSON.stringify(content)}\n`; - } - - const endIndex = content.indexOf('---', 3); - if (endIndex === -1) { - return `prompt = ${JSON.stringify(content)}\n`; - } - - const frontmatter = content.substring(3, endIndex).trim(); - let body = content.substring(endIndex + 3).trim(); - - // #778 (c) — Gemini !{...} dynamic-output injection for the situational - // `progress` command (GSD's status/dashboard surface). Inject the live - // .planning/STATE.md so the model sees current project state without relying - // on session memory. - // - // SECURITY: the shell command is a FIXED `cat` with NO interpolated user - // input — no {{args}} appears inside the block — so there is no - // shell-injection vector. Gemini still shows its standard per-invocation - // confirmation dialog (verified behavior). `2>/dev/null` keeps an - // uninitialized project (missing STATE.md) from injecting stderr noise. - // Braces inside the block are balanced (none present), per Gemini's parser - // requirement. The append happens AFTER the {{args}} mapping above so the - // injected block can never accidentally carry interpolated arguments. - if (commandName === 'progress') { - body += '\n\n## Live project state\n' - + 'Current contents of `.planning/STATE.md` ' - + '(empty if the project is not yet initialized):\n\n' - + '!{cat .planning/STATE.md 2>/dev/null}\n'; - } - - // Extract description from frontmatter - let description = ''; - const lines = frontmatter.split('\n'); - for (const line of lines) { - const trimmed = line.trim(); - if (trimmed.startsWith('description:')) { - description = trimmed.substring(12).trim(); - break; - } - } - - // Construct TOML - let toml = ''; - if (description) { - toml += `description = ${JSON.stringify(description)}\n`; - } - - toml += `prompt = ${JSON.stringify(body)}\n`; - - return toml; -} - /** * Copy commands to a flat structure for OpenCode * OpenCode expects: command/gsd-help.md (invoked as /gsd-help) @@ -6530,7 +6281,9 @@ const _stampNonClaudeRuntimeDefaults = runtimeArtifactConversion._stampNonClaude * mdSkipGenericRewrite?: boolean — skip the ~/.claude/ rewrite block (copilot, antigravity) * md?: (content, ctx) => string — per-runtime .md transform * mdReattributeAfter?: boolean — re-run processAttribution after md() (copilot, antigravity) - * mdTomlRenameOnCommand?: boolean — when isCommand, rename dest .md → .toml (gemini) + * mdTomlRenameOnCommand?: boolean — when isCommand, rename dest .md → .toml + * (unused since the gemini runtime was removed, #1928; + * kept as generic dispatch infra for a future TOML-command runtime) * js?: (content, ctx) => string — per-runtime .cjs/.js transform (absent = plain copyFileSync) * * ctx = { isCommand, isGlobal, dirName, pathPrefix, entryName, runtime } @@ -6542,14 +6295,6 @@ const RUNTIME_CONTENT_DISPATCH = { kilo: { md: (content) => convertClaudeToKiloFrontmatter(content), }, - gemini: { - md: (content, ctx) => - convertClaudeToGeminiMarkdown(content, { - isCommand: ctx.isCommand, - commandName: ctx.isCommand ? ctx.entryName.replace(/\.md$/, '') : null, - }), - mdTomlRenameOnCommand: true, - }, codex: { md: (content) => convertClaudeToCodexMarkdown(content), }, @@ -6645,7 +6390,7 @@ const RUNTIME_CONTENT_DISPATCH = { * @param {string} srcDir - Source directory * @param {string} destDir - Destination directory * @param {string} pathPrefix - Path prefix for file references - * @param {string} runtime - Target runtime ('claude', 'opencode', 'gemini', 'codex') + * @param {string} runtime - Target runtime ('claude', 'opencode', 'codex') * @param {boolean} isCommand - Whether the source is a command directory * @param {boolean} isGlobal - Whether the install is global */ @@ -6724,7 +6469,7 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand // copyWithPathReplacement for runtimes that register commands under the // hyphen form; normalizeAgentBodyForRuntime self-gates on // shouldNormalizeHyphenNamespaceInAgentBody(runtime) and is a no-op for - // colon-canonical runtimes (Gemini). + // colon-canonical / self-converting runtimes. content = normalizeAgentBodyForRuntime(content, runtime, readGsdCommandNames()); // Apply per-runtime .md converter (if any) @@ -6733,7 +6478,7 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand // Re-run attribution after converter for runtimes that need it (copilot, antigravity) if (dispatch.mdReattributeAfter) content = processAttribution(content, getCommitAttribution(runtime)); - // Gemini: rename .md → .toml for command files + // Rename .md → .toml for command files (unused since gemini removal, #1928) const finalPath = (dispatch.mdTomlRenameOnCommand && isCommand) ? destPath.replace(/\.md$/, '.toml') : destPath; fs.writeFileSync(finalPath, content); } else if (entry.name.endsWith('.cjs') || entry.name.endsWith('.js')) { @@ -6916,10 +6661,10 @@ const GSD_UNINSTALL_HOOKS = [ * Uninstall GSD from the specified directory for a specific runtime * Removes only GSD-specific files/directories, preserves user content * @param {boolean} isGlobal - Whether to uninstall from global or local - * @param {string} runtime - Target runtime ('claude', 'opencode', 'gemini', 'codex', 'copilot') + * @param {string} runtime - Target runtime ('claude', 'opencode', 'codex', 'copilot') */ function uninstall(isGlobal, runtime = 'claude') { - const { isOpencode, isKilo, isGemini, isCodex, isCopilot, isAntigravity, isCursor, isWindsurf, isAugment, isTrae, isQwen, isHermes, isCodebuddy, isCline, isKimi } = runtimeFlags(runtime); + const { isOpencode, isKilo, isCodex, isCopilot, isAntigravity, isCursor, isWindsurf, isAugment, isTrae, isQwen, isHermes, isCodebuddy, isCline, isKimi } = runtimeFlags(runtime); const dirName = getDirName(runtime); // Get the target directory based on runtime and install type. Cline local @@ -7179,29 +6924,6 @@ function uninstall(isGlobal, runtime = 'claude') { } } - // 1d. Gemini: remove commands/gsd/ with dev-preferences.md preservation. - // The layout removes gsd-*.toml files but not the directory itself. - // Preserve user files before removing the directory. - if (isGemini) { - const gsdCommandsDir = path.join(targetDir, 'commands', 'gsd'); - if (fs.existsSync(gsdCommandsDir)) { - const devPrefsPath = path.join(gsdCommandsDir, 'dev-preferences.md'); - const preservedDevPrefs = fs.existsSync(devPrefsPath) ? fs.readFileSync(devPrefsPath, 'utf-8') : null; - fs.rmSync(gsdCommandsDir, { recursive: true }); - removedCount++; - console.log(` ${green}✓${reset} Removed commands/gsd/`); - if (preservedDevPrefs) { - try { - fs.mkdirSync(gsdCommandsDir, { recursive: true }); - fs.writeFileSync(devPrefsPath, preservedDevPrefs); - console.log(` ${green}✓${reset} Preserved commands/gsd/dev-preferences.md`); - } catch (err) { - console.error(` ${red}✗${reset} Failed to restore dev-preferences.md: ${err.message}`); - } - } - } - } - // 1d. Qwen/Hermes: migrate dev-preferences.md from legacy commands/gsd/ location // during uninstall. _runLegacyUninstallCleanup (called by uninstallRuntimeArtifacts) // removes the directory; we must preserve/restore user artifacts before that path. @@ -7425,7 +7147,7 @@ function uninstall(isGlobal, runtime = 'claude') { // Remove GSD hooks from settings — per-hook granularity to preserve // user hooks that share an entry with a GSD hook (#1755 followup). // Includes the 3 Qwen-only events added in #788 (SubagentStop, Stop, - // PreCompact, also registered for Claude in #770), the 3 Gemini-only + // PreCompact, also registered for Claude in #770), the 3 Antigravity-only // events added in #776 (BeforeAgent, AfterAgent, BeforeModel), and the // Claude-only FileChanged event added in #770 — safe to iterate for all // runtimes; installs that don't register these events simply find no @@ -7870,7 +7592,7 @@ function verifyFileInstalled(filePath, description) { /** * Install to the specified directory for a specific runtime * @param {boolean} isGlobal - Whether to install globally or locally - * @param {string} runtime - Target runtime ('claude', 'opencode', 'gemini', 'codex') + * @param {string} runtime - Target runtime ('claude', 'opencode', 'codex') */ // ────────────────────────────────────────────────────── @@ -7944,12 +7666,10 @@ function resolveInstallRelativePath(baseDir, relPath) { * Write file manifest after installation for future modification detection */ function writeManifest(configDir, runtime = 'claude', options = {}) { - const { isOpencode, isKilo, isGemini, isCodex, isCopilot, isAntigravity, isCursor, isWindsurf, isAugment, isTrae, isQwen, isHermes, isCodebuddy, isCline, isKimi } = runtimeFlags(runtime); + const { isOpencode, isKilo, isCodex, isCopilot, isAntigravity, isCursor, isWindsurf, isAugment, isTrae, isQwen, isHermes, isCodebuddy, isCline, isKimi } = runtimeFlags(runtime); const gsdDir = path.join(configDir, 'gsd-core'); // #1367: Claude local now writes flat gsd-*.md files at commands/ (not commands/gsd/). - // commandsDir points to the old location for Gemini (which still uses commands/gsd/). // Claude local uses flatCommandsDir instead for manifest recording. - const commandsDir = path.join(configDir, 'commands', 'gsd'); const flatCommandsDir = path.join(configDir, 'commands'); const opencodeCommandDir = path.join(configDir, 'command'); // Hermes nests GSD skills under skills/gsd/ as a single category (#2841). @@ -7977,16 +7697,9 @@ function writeManifest(configDir, runtime = 'claude', options = {}) { manifest.files['gsd-core/' + rel] = hash; } // Record commands surface for runtimes that emit it: - // Gemini: commands/gsd/.toml (nested, colon-namespaced) // Claude local (#1367 fix): flat gsd-.md at commands/ level // Manifest must reflect everything on disk so saveLocalPatches() can detect // user edits and per-runtime minimal-mode assertions can read manifest.files. - if (isGemini && fs.existsSync(commandsDir)) { - const cmdHashes = generateManifest(commandsDir); - for (const [rel, hash] of Object.entries(cmdHashes)) { - manifest.files['commands/gsd/' + rel] = hash; - } - } // Claude local (#1367): flat gsd-*.md files at commands/ level. // Only claude local writes gsd-*.md here; global installs don't emit commands, // so this branch is a no-op for global (no matching files to find). @@ -8004,7 +7717,7 @@ function writeManifest(configDir, runtime = 'claude', options = {}) { } } } - if ((isCodex || isCopilot || isAntigravity || isCursor || isWindsurf || isTrae || (!isOpencode && !isGemini)) && fs.existsSync(codexSkillsDir)) { + if ((isCodex || isCopilot || isAntigravity || isCursor || isWindsurf || isTrae || !isOpencode) && fs.existsSync(codexSkillsDir)) { // All runtimes (including Hermes post-#947) use the canonical 'gsd-' prefix. const skillListPrefix = 'gsd-'; for (const skillName of listCodexSkillNames(codexSkillsDir, skillListPrefix)) { @@ -8414,10 +8127,8 @@ function reportLocalPatches(configDir, runtime = 'claude') { if (meta.files && meta.files.length > 0) { const reapplyCommand = (runtime === 'opencode' || runtime === 'kilo' || runtime === 'copilot') ? '/gsd-update --reapply' - : runtime === 'gemini' - ? '/gsd:update --reapply' - : runtime === 'codex' - ? '$gsd-update --reapply' + : runtime === 'codex' + ? '$gsd-update --reapply' : runtime === 'cursor' ? 'gsd-update --reapply (mention the skill name)' : runtime === 'kimi' @@ -8449,7 +8160,7 @@ function reportInstallerMigrationResult(result) { } function install(isGlobal, runtime = 'claude', options = {}) { - const { isOpencode, isKilo, isGemini, isCodex, isCopilot, isAntigravity, isCursor, isWindsurf, isAugment, isTrae, isQwen, isHermes, isCodebuddy, isCline, isKimi } = runtimeFlags(runtime); + const { isOpencode, isKilo, isCodex, isCopilot, isAntigravity, isCursor, isWindsurf, isAugment, isTrae, isQwen, isHermes, isCodebuddy, isCline, isKimi } = runtimeFlags(runtime); const plan = resolveInstallPlan(runtime); const dirName = getDirName(runtime); const src = path.join(__dirname, '..'); @@ -8864,7 +8575,7 @@ function install(isGlobal, runtime = 'claude', options = {}) { // Artifact install dispatcher — routes to layout-driven path for all // skills-based runtimes (both full and minimal/core profiles); keeps - // back-compat paths for commands-based runtimes (OpenCode/Kilo/Gemini/ + // back-compat paths for commands-based runtimes (OpenCode/Kilo/ // Claude-local). // // installRuntimeArtifacts handles legacy migration + skill/agent staging @@ -8875,7 +8586,6 @@ function install(isGlobal, runtime = 'claude', options = {}) { // Hermes: writeHermesCategoryDescription (not a layout kind) // Cline global: skills emitted via layout; .clinerules still written below (#782) // Cline local: no skills (only .clinerules) — falls through to cline-rules surface - // Gemini: conflict-detection logic (not expressible in layout) // OpenCode/Kilo: copyFlattenedCommands (frontmatter conversion not in commandsKind) // Claude local: copyWithPathReplacement + stale-skills cleanup @@ -9096,57 +8806,6 @@ function install(isGlobal, runtime = 'claude', options = {}) { // No skills/commands directory needed for local installs. // Global installs are handled above by _isSkillsRuntime (#782). console.log(` ${green}✓${reset} Cline: commands will be available via .clinerules`); - } else if (isGemini) { - // #3037: when running --local --gemini and a GSD-managed user-scope - // command directory already exists at ~/.gemini/commands/gsd/, skip - // the local copy. Gemini conflict-detects by command name across - // scopes and renames every overlapping /gsd:* command to - // /workspace.gsd:* and /user.gsd:*, breaking the documented namespace. - // The user-scope install already provides the same commands, so the - // local copy adds zero value at the cost of namespace conflicts. - // - // CR #3041 (Major): the detection must be specific to PACKAGE-MANAGED - // GSD content, not just "directory is non-empty". A user who hand- - // dropped a single override (e.g. ~/.gemini/commands/gsd/my-override - // .toml) would otherwise be unable to run a local install at all. - // Detection rule: at least 3 of the canonical GSD command files - // ('help.toml', 'progress.toml', 'new-project.toml') must be present. - // These three ship in every GSD Gemini install (minimal mode included - // — they're in the core skill set per #2790's consolidation), and 3-of- - // 3 with that specific basename set is structurally impossible to - // produce by accident. - const homeGeminiGsd = path.join(os.homedir(), '.gemini', 'commands', 'gsd'); - const GSD_MANAGED_CANARIES = ['help.toml', 'progress.toml', 'new-project.toml']; - const userScopeHasGsd = - !isGlobal && - path.resolve(targetDir) !== path.resolve(path.join(os.homedir(), '.gemini')) && - fs.existsSync(homeGeminiGsd) && - GSD_MANAGED_CANARIES.every((f) => - fs.existsSync(path.join(homeGeminiGsd, f)) - ); - - if (userScopeHasGsd) { - console.log( - ` ${yellow}⚠${reset} Skipping commands/gsd/ for local install — GSD is already installed at user scope (${homeGeminiGsd}).` - ); - console.log( - ` Gemini conflict-detects across scopes and would rename every /gsd:* command to /workspace.gsd:* and /user.gsd:*.` - ); - console.log( - ` The user-scope install already provides /gsd:* commands in this project; no local copy is needed.` - ); - } else { - const commandsDir = path.join(targetDir, 'commands'); - fs.mkdirSync(commandsDir, { recursive: true }); - const gsdSrc = _stageSkills(_commandsDir); - const gsdDest = path.join(commandsDir, 'gsd'); - copyWithPathReplacement(gsdSrc, gsdDest, pathPrefix, runtime, true, isGlobal, targetDir); - if (verifyInstalled(gsdDest, 'commands/gsd')) { - console.log(` ${green}✓${reset} Installed commands/gsd`); - } else { - failures.push('commands/gsd'); - } - } } else { // Claude Code local: flat gsd-.md layout — Claude Code registers // commands from .claude/commands/ using the filename stem as the command @@ -9379,8 +9038,6 @@ function install(isGlobal, runtime = 'claude', options = {}) { content = convertClaudeToOpencodeFrontmatter(content, { isAgent: true, modelOverride: _ocModelOverride }); } else if (isKilo) { content = convertClaudeToKiloFrontmatter(content, { isAgent: true }); - } else if (isGemini) { - content = convertClaudeToGeminiAgent(content); } else if (isCodex) { content = convertClaudeAgentToCodexAgent(content); } else if (isCopilot) { @@ -9409,12 +9066,12 @@ function install(isGlobal, runtime = 'claude', options = {}) { content = content.replace(/\.claude\//g, '.hermes/'); } // #443 — Inject `effort:` into the Claude .md frontmatter ONLY. - // Gemini/OpenCode/Qwen/Hermes also produce .md files but break on + // OpenCode/Qwen/Hermes also produce .md files but break on // unknown frontmatter keys (the repo bans skills:/permissionMode: for // the same reason — see tests/agent-frontmatter.test.cjs). // Claude Code reads per-subagent `effort:` frontmatter (anthropics/claude-code #31536). // Injection is per-runtime at install time because the canonical source - // agents/*.md must stay Gemini-safe (no effort: key in source). + // agents/*.md must stay runtime-safe (no effort: key in source). if (runtime === 'claude') { const _effortCfg = readGsdEffectiveEffortConfig(targetDir); const _agentName = entry.name.replace(/\.md$/, ''); @@ -9426,8 +9083,8 @@ function install(isGlobal, runtime = 'claude', options = {}) { } // #3677 — normalize retired `/gsd:` colon refs in the agent body // to the canonical hyphen form `/gsd-` for hyphen-`name:` - // runtimes (claude / qwen / hermes). Self-converting runtimes and - // Gemini are skipped by the predicate — see + // runtimes (claude / qwen / hermes). Self-converting and + // colon-canonical runtimes are skipped by the predicate — see // shouldNormalizeHyphenNamespaceInAgentBody above. Mirrors the // SKILL.md-body fix shipped via #3629. content = normalizeAgentBodyForRuntime(content, runtime, readGsdCommandNames()); @@ -10209,7 +9866,7 @@ function install(isGlobal, runtime = 'claude', options = {}) { // Configure statusline and hooks in settings.json (or settings.local.json for local Claude installs). // ADR-857 phase 5f-2: drive the hook event dialect from the registry descriptor. // runtimes with hookEvents='gemini' use AfterTool/BeforeTool; all others use PostToolUse/PreToolUse. - // Equivalence: hookEvents='gemini' iff runtime∈{gemini,antigravity} — identical to the old check. + // Equivalence: hookEvents='gemini' iff runtime===antigravity — identical to the old check. // A missing registry or missing descriptor defaults to 'not gemini' → PostToolUse (safe). const _hookEventsDialect = plan.hookEvents; const postToolEvent = _hookEventsDialect === 'gemini' ? 'AfterTool' : 'PostToolUse'; @@ -10309,9 +9966,9 @@ function install(isGlobal, runtime = 'claude', options = {}) { console.log(` ${green}✓${reset} Rewrote legacy bare-node managed-hook commands to absolute path (#2979)`); } // Local installs anchor hook paths so they resolve regardless of cwd (#1906). - // Claude Code sets $CLAUDE_PROJECT_DIR; Gemini/Antigravity do not — and on - // Windows their own substitution logic doubles the path (#2557). Those runtimes - // run project hooks with the project dir as cwd, so bare relative paths work. + // Claude Code sets $CLAUDE_PROJECT_DIR; Antigravity does not — and on + // Windows its own substitution logic doubles the path (#2557). It runs + // project hooks with the project dir as cwd, so bare relative paths work. const localPrefix = projectLocalHookPrefix({ runtime, dirName }); const hookOpts = { portableHooks: hasPortableHooks, runtime }; // #2979: local-install hook commands also use the absolute node path so @@ -10375,17 +10032,6 @@ function install(isGlobal, runtime = 'claude', options = {}) { console.warn(` ${yellow}⚠${reset} Skipping managed JS hook registration — Node executable path unavailable (process.execPath is empty). See #2979 / #3002.`); } - // Enable experimental agents for Gemini CLI (required for custom sub-agents) - if (isGemini) { - if (!settings.experimental) { - settings.experimental = {}; - } - if (!settings.experimental.enableAgents) { - settings.experimental.enableAgents = true; - console.log(` ${green}✓${reset} Enabled experimental agents`); - } - } - // Register all GSD-managed hook entries into settings.hooks.* for runtimes // that use the settings.json hook surface (ADR-857 phase 5f-1b). // settings is mutated in place by applySettingsJsonHooks. @@ -10408,23 +10054,6 @@ function install(isGlobal, runtime = 'claude', options = {}) { localShellCmd, }); - // ── Gemini hooksConfig.enabled check (#776) ─────────────────────────────── - // Detect `hooksConfig.enabled: false` in the already-loaded settings object - // and emit a clear warning. When this field is false the Gemini CLI silently - // disables ALL hook execution — gsd hooks are registered but will never run. - // The check is read-only (warning only; we do not mutate hooksConfig). - // Note: we use the in-memory `settings` object (already read from disk and - // cleaned up by validateHookFields/cleanupOrphanedHooks above) rather than - // re-reading settings.json, avoiding a TOCTOU window between the two reads. - if (isGemini && settings && settings.hooksConfig && settings.hooksConfig.enabled === false) { - console.warn( - ` ${yellow}⚠${reset} Warning: hooksConfig.enabled is false in your Gemini settings.json.\n` + - ` gsd-core hooks are registered but will NOT run until you set\n` + - ` hooksConfig.enabled: true in ${path.join(targetDir, 'settings.json')}.` - ); - } - // ── end hooksConfig.enabled check ──────────────────────────────────────── - // Compute the update-banner hook command alongside the others so // installAllRuntimes can register it at finalize time when the user opts // in (#2795). Computed here (not in finishInstall) so the same buildHookCommand @@ -10513,7 +10142,7 @@ function install(isGlobal, runtime = 'claude', options = {}) { * Apply statusline config, then print completion message */ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline, runtime = 'claude', isGlobal = true, configDir = null, bannerOpts = {}) { - const { isOpencode, isKilo, isGemini, isCodex, isCopilot, isAntigravity, isCursor, isWindsurf, isAugment, isTrae, isQwen, isHermes, isCodebuddy, isCline, isKimi } = runtimeFlags(runtime); + const { isOpencode, isKilo, isCodex, isCopilot, isAntigravity, isCursor, isWindsurf, isAugment, isTrae, isQwen, isHermes, isCodebuddy, isCline, isKimi } = runtimeFlags(runtime); const plan = resolveInstallPlan(runtime); if (shouldInstallStatusline && plan.writesSharedSettings && !isOpencode) { @@ -10569,7 +10198,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS // #768 — Pre-populate permissions.allow/deny for Claude Code installs. // Merges GSD-owned entries non-destructively (preserves existing user permissions). - // Scoped to Claude only: gemini/antigravity/qwen/hermes/codebuddy also write + // Scoped to Claude only: antigravity/qwen/hermes/codebuddy also write // settings.json but use different runtimes and do not use these permission strings. if (runtime === 'claude') { mergeClaudePermissions(settings); @@ -10739,17 +10368,16 @@ const runtimeMap = { '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 allRuntimes = ['claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex', 'copilot', 'cursor', 'gemini', 'hermes', 'kimi', 'kilo', 'opencode', 'qwen', 'trae', 'windsurf']; -const ALL_RUNTIMES_OPTION = '17'; +const allRuntimes = ['claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex', 'copilot', 'cursor', 'hermes', 'kimi', 'kilo', 'opencode', 'qwen', 'trae', 'windsurf']; +const ALL_RUNTIMES_OPTION = '16'; /** * Build the runtime-selection prompt text shown by the interactive installer. @@ -10765,15 +10393,14 @@ function buildRuntimePromptText() { ${cyan}6${reset}) Codex ${dim}(~/.codex)${reset} ${cyan}7${reset}) Copilot ${dim}(~/.copilot)${reset} ${cyan}8${reset}) Cursor ${dim}(~/.cursor)${reset} - ${cyan}9${reset}) Gemini ${dim}(~/.gemini)${reset} - ${cyan}10${reset}) Hermes Agent ${dim}(~/.hermes)${reset} - ${cyan}11${reset}) Kimi ${dim}(~/.config/agents, then ~/.agents if existing)${reset} - ${cyan}12${reset}) Kilo ${dim}(~/.config/kilo)${reset} - ${cyan}13${reset}) OpenCode ${dim}(~/.config/opencode)${reset} - ${cyan}14${reset}) Qwen Code ${dim}(~/.qwen)${reset} - ${cyan}15${reset}) Trae ${dim}(~/.trae)${reset} - ${cyan}16${reset}) Windsurf ${dim}(~/.codeium/windsurf)${reset} - ${cyan}17${reset}) All + ${cyan}9${reset}) Hermes Agent ${dim}(~/.hermes)${reset} + ${cyan}10${reset}) Kimi ${dim}(~/.config/agents, then ~/.agents if existing)${reset} + ${cyan}11${reset}) Kilo ${dim}(~/.config/kilo)${reset} + ${cyan}12${reset}) OpenCode ${dim}(~/.config/opencode)${reset} + ${cyan}13${reset}) Qwen Code ${dim}(~/.qwen)${reset} + ${cyan}14${reset}) Trae ${dim}(~/.trae)${reset} + ${cyan}15${reset}) Windsurf ${dim}(~/.codeium/windsurf)${reset} + ${cyan}16${reset}) All ${dim}Select multiple: 1,2,6 or 1 2 6${reset} `; @@ -10784,7 +10411,7 @@ function buildRuntimePromptText() { * Pure function — exported so tests can verify split/dedupe/fallback behavior. * - Accepts comma- and/or whitespace-separated choices * - Deduplicates while preserving order - * - Maps option 17 ("All") to every runtime + * - Maps option 16 ("All") to every runtime * - Falls back to ['claude'] when nothing valid is selected */ function parseRuntimeInput(answer) { @@ -11424,7 +11051,7 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) { throw error; } - const statuslineRuntimes = ['claude', 'gemini']; + const statuslineRuntimes = ['claude']; const primaryStatuslineResult = results.find(r => statuslineRuntimes.includes(r.runtime)); const finalize = (shouldInstallStatusline, shouldInstallBanner) => { @@ -11517,10 +11144,6 @@ module.exports = { convertClaudeCommandToCursorSkill, convertClaudeCommandToCursorCommand, convertClaudeAgentToCursorAgent, - convertClaudeToGeminiMarkdown, - convertSlashCommandsToGeminiMentions, - _resetGsdCommandRoster, - convertClaudeToGeminiAgent, convertClaudeAgentToCodexAgent, generateCodexAgentToml, cleanupCodexSkillMetadataSidecars, diff --git a/capabilities/gemini/capability.json b/capabilities/gemini/capability.json deleted file mode 100644 index 393cee428..000000000 --- a/capabilities/gemini/capability.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "id": "gemini", - "role": "runtime", - "version": "1.7.0-rc.2", - "title": "Gemini CLI", - "description": "Google Gemini CLI — commands-only artifact layout (TOML); Gemini hook event dialect; settings-json hook surface; tier-2 support.", - "tier": "core", - "requires": [], - "engines": { - "gsd": ">=1.6.0" - }, - "runtime": { - "configHome": { - "kind": "dot-home", - "name": ".gemini", - "env": [ - "GEMINI_CONFIG_DIR" - ] - }, - "localConfigDir": ".gemini", - "configFormat": "settings-json", - "artifactLayout": { - "global": [ - { - "kind": "commands", - "destSubpath": "commands/gsd", - "prefix": "gsd-", - "nesting": "flat", - "recursive": false, - "converter": null - } - ], - "local": [ - { - "kind": "commands", - "destSubpath": "commands/gsd", - "prefix": "gsd-", - "nesting": "flat", - "recursive": false, - "converter": null - } - ] - }, - "commandStyle": "slash-hyphen", - "hooksSurface": "settings-json", - "hookEvents": "gemini", - "sandboxTier": "none", - "supportTier": 2, - "installSurface": "settings-json", - "writesSharedSettings": true, - "permissionWriter": null, - "extendedHookEvents": [ - "BeforeAgent", - "AfterAgent", - "BeforeModel" - ], - "hostIntegration": { - "embeddingMode": "declarative", - "commandSurface": "slash-toml", - "dispatch": { - "namedDispatch": true, - "nested": false, - "maxDepth": 1, - "background": "undocumented", - "subagentToolkit": "undocumented", - "backgroundDispatch": false - }, - "modelMode": "passive", - "hookBus": "host", - "stateIO": "filesystem", - "transport": "mcp", - "runtime": "node" - } - } -} diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index af2c2968c..07e25df3a 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -21,7 +21,7 @@ ## System Overview -GSD Core is a **meta-prompting framework** that sits between the user and AI coding agents (Claude Code, Gemini CLI, Kimi CLI, OpenCode, Kilo, Codex, Copilot, Antigravity, Trae, Cline, Augment Code). It provides: +GSD Core is a **meta-prompting framework** that sits between the user and AI coding agents (Claude Code, Kimi CLI, OpenCode, Kilo, Codex, Copilot, Antigravity, Trae, Cline, Augment Code). It provides: 1. **Context engineering** — Structured artifacts that give the AI everything it needs per task (see [Context engineering](explanation/context-engineering.md)) 2. **Multi-agent orchestration** — Thin orchestrators that spawn specialized agents with fresh context windows (see [Multi-agent orchestration](explanation/multi-agent-orchestration.md)) @@ -115,7 +115,6 @@ User-facing entry points. Each file contains YAML frontmatter (name, description - **OpenCode / Kilo:** Slash commands (hyphen form, `/gsd-command-name`) - **Codex:** Skills (`$gsd-command-name`) - **Copilot:** Slash commands (hyphen form, `/gsd-command-name`) -- **Gemini CLI:** Slash commands under the `gsd:` namespace (colon form, `/gsd:command-name`) — Gemini namespaces all custom commands under their plugin id, so the install path rewrites every body-text reference to colon form - **Kimi CLI:** Agent Skills (`/skill:gsd-command-name`) plus an explicit custom agent launch with `kimi --agent-file` - **Antigravity:** Skills @@ -592,7 +591,6 @@ Equivalent paths for other runtimes: - **OpenCode:** `~/.config/opencode/` global or `./.opencode/` local - **Kilo:** `~/.config/kilo/` global or `./.kilo/` local -- **Gemini CLI:** `~/.gemini/` global or `./.gemini/` local - **Kimi CLI:** first-existing generic global root (`~/.config/agents/` recommended, then `~/.agents/` if its `skills/` directory already exists); local install is deferred and guarded - **Codex:** `~/.codex/` global or `./.codex/` local - **Copilot:** `~/.copilot/` global or `./.github/` local @@ -688,7 +686,7 @@ verification. The installer (`bin/install.js`, ~10,700 lines) handles: -1. **Runtime detection** — Interactive prompt or CLI flags (`--claude`, `--opencode`, `--gemini`, `--kimi`, `--kilo`, `--codex`, `--copilot`, `--antigravity`, `--cursor`, `--windsurf`, `--augment`, `--trae`, `--qwen`, `--hermes`, `--codebuddy`, `--cline`, `--all`) +1. **Runtime detection** — Interactive prompt or CLI flags (`--claude`, `--opencode`, `--kimi`, `--kilo`, `--codex`, `--copilot`, `--antigravity`, `--cursor`, `--windsurf`, `--augment`, `--trae`, `--qwen`, `--hermes`, `--codebuddy`, `--cline`, `--all`) 2. **Location selection** — Global (`--global`) or local (`--local`) 3. **File deployment** — Copies commands, skills, workflows, references, templates, agents, and hooks 4. **Runtime adaptation** — Transforms file content per runtime: @@ -698,8 +696,7 @@ The installer (`bin/install.js`, ~10,700 lines) handles: - Codex: Generates TOML config + skills from commands - Kimi CLI: Generates Agent Skills under `skills/gsd-*/SKILL.md`, custom agent YAML/prompt files, and explicit `kimi_cli.tools.*` module paths - Copilot: Maps tool names (Read→read, Bash→execute, etc.) - - Gemini: Adjusts hook event names (`AfterTool` instead of `PostToolUse`) - - Antigravity: Skills-first with Google model equivalents + - Antigravity: Skills-first with Google model equivalents; adjusts hook event names (`AfterTool` instead of `PostToolUse`) - Cursor: Skills-first with Cursor rule references - Windsurf: Skills-first with Windsurf rule references - Trae: Skills-first install to `~/.trae` / `./.trae` with no `settings.json` or hook integration @@ -737,7 +734,7 @@ The plan drift guard (`plan_review.source_grounding`) — which verifies symbol ### Architecture ``` -Runtime Engine (Claude Code / Gemini CLI) +Runtime Engine (Claude Code / Antigravity CLI) │ ├── statusLine event ──► gsd-statusline.js │ Reads: stdin (session JSON) @@ -836,7 +833,6 @@ The migration-specific ownership and source snapshots live in | Claude Code | `~/.claude` | `./.claude` | Global `skills/gsd-*/SKILL.md` (flat, #924); local `commands/gsd/*.md` | `agents/gsd-*.md` | `settings.json` hook and statusLine entries | | OpenCode | `~/.config/opencode` | `./.opencode` | `command/gsd-*.md` | `agents/gsd-*.md` | `opencode.json` or `opencode.jsonc`; no GSD hooks | | Kilo | `~/.config/kilo` | `./.kilo` | `command/gsd-*.md` | `agents/gsd-*.md` | `kilo.json` or `kilo.jsonc`; no GSD hooks | -| Gemini CLI | `~/.gemini` | `./.gemini` | `commands/gsd/*.toml` | `agents/gsd-*.md` | `settings.json` feature flag, hooks, and statusline | | Kimi CLI | First-existing generic root: `~/.config/agents` recommended, then `~/.agents` when `~/.agents/skills` exists and `~/.config/agents/skills` does not | Deferred and guarded | `skills/gsd-*/SKILL.md` (flat) invoked as `/skill:gsd-*` | `agents/gsd.yaml`, `agents/gsd.md`, and `agents/subagents/gsd-*` YAML/prompt pairs | Explicit `kimi --agent-file /agents/gsd.yaml`; no GSD hooks or statusline | | Codex | `~/.codex` | `./.codex` | `skills/gsd-*/SKILL.md` (flat) | `agents/` source markdown plus per-agent TOML | `config.toml` `[agents.gsd-*]`, `[features].hooks` (canonical; legacy alias `codex_hooks` is recognized and migrated forward on reinstall, #3566), and hook tables | | GitHub Copilot | `~/.copilot` | `./.github` | `skills/gsd-*/SKILL.md` (flat), `copilot-instructions.md`, and `AGENTS.md` (repo root, local) | `.agent.md` files | Self-contained `sessionStart` hook (`hooks/gsd-session.json`, inline `command` type); no statusline | @@ -858,7 +854,7 @@ on 2026-06-07: - Claude Code: Anthropic slash commands, settings, hooks, and subagents docs. - OpenCode and Kilo: OpenCode config docs and Kilo custom subagent docs. -- Gemini CLI and Qwen Code: command/config docs; Qwen command docs were last +- Qwen Code: command/config docs; Qwen command docs were last updated 2026-05-06. - Kimi CLI: Agent Skills docs for user-level brand roots and first-existing generic roots (`~/.config/agents/skills/` recommended, then @@ -875,7 +871,7 @@ on 2026-06-07: ### Abstraction Points 1. **Tool name mapping** — Each runtime has its own tool names (e.g., Claude's `Bash` → Copilot's `execute`) -2. **Hook event names** — Claude uses `PostToolUse`, Gemini uses `AfterTool` +2. **Hook event names** — Claude uses `PostToolUse`, Antigravity uses `AfterTool` 3. **Agent frontmatter** — Each runtime has its own agent definition format 4. **Path conventions** — Each runtime stores config in different directories 5. **Model references** — `inherit` profile lets GSD defer to runtime's model selection diff --git a/docs/BETA.md b/docs/BETA.md index 0e078eb31..2f7275785 100644 --- a/docs/BETA.md +++ b/docs/BETA.md @@ -29,7 +29,7 @@ When you're satisfied with the draft, you save it and import it back into GSD ### Requirements -- **Runtime:** Claude Code only. The command exits with an error on Gemini CLI, Copilot CLI, and other runtimes. +- **Runtime:** Claude Code only. The command exits with an error on Antigravity CLI, Copilot CLI, and other runtimes. - **Version:** Claude Code v2.1.91 or later (the `$CLAUDE_CODE_VERSION` env var must be set). - **Cost:** No extra charge for Pro and Max subscribers. Ultraplan is included at no additional cost. diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index 5e3672a92..e8f968d1b 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -7,7 +7,6 @@ ## Command Syntax - **Claude Code / Copilot / OpenCode / Kilo:** `/gsd-command-name [args]` (hyphen form) -- **Gemini CLI:** `/gsd:command-name [args]` (colon form — Gemini namespaces commands under `gsd:`) - **Codex:** `$gsd-command-name [args]` The hyphen and colon forms are *runtime-specific spellings of the same command*. Whichever runtime you're on, the installer writes the correct form into your runtime's command directory. @@ -18,7 +17,7 @@ Heavy workflow skills (`/gsd-plan-phase`, `/gsd-execute-phase`, `/gsd-autonomous Quick-status skills (`/gsd-progress`, `/gsd-stats`) declare `effort: low`, directing the runtime to use a minimal token budget for fast reads. -These fields are Claude Code–specific frontmatter. On runtimes that do not recognise them (Gemini, Codex, Cursor, etc.) the fields are silently ignored — existing behaviour is unchanged. +These fields are Claude Code–specific frontmatter. On runtimes that do not recognise them (Antigravity, Codex, Cursor, etc.) the fields are silently ignored — existing behaviour is unchanged. --- diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 4a1669ede..3d2c467b6 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -304,7 +304,7 @@ All workflow toggles follow the **absent = enabled** pattern. If a key is missin | `workflow.max_discuss_passes` | number | `3` | Maximum number of question rounds in discuss-phase before the workflow stops asking. Useful in headless/auto mode to prevent infinite discussion loops. | | `workflow.skip_discuss` | boolean | `false` | When `true`, `/gsd-autonomous` bypasses the discuss-phase entirely, writing minimal CONTEXT.md from the ROADMAP phase goal. Useful for projects where developer preferences are fully captured in PROJECT.md/REQUIREMENTS.md. Added in v1.28 | | `workflow.text_mode` | boolean | `false` | Replaces AskUserQuestion TUI menus with plain-text numbered lists. Required for Claude Code remote sessions (`/rc` mode) where TUI menus don't render. Can also be set per-session with `--text` flag on discuss-phase. Added in v1.28 | -| `workflow.use_worktrees` | boolean | `true` | When `false`, disables git worktree isolation for parallel execution. Users who prefer sequential execution or whose environment does not support worktrees can disable this. Added in v1.31. **Branch-divergence note:** when your branch has diverged from `origin/HEAD`, GSD auto-degrades to sequential and prints a warning. See [`worktree.baseRef`](#worktree-settings) to restore parallel execution on a diverged branch. **Non-Claude note:** git worktree isolation uses Claude Code's `isolation="worktree"` agent primitive, which no other runtime honors. On any non-Claude install (Codex, Cursor, Gemini, Qwen, etc.) a runtime-neutral `.planning/config.json` resolves the runtime to that install's own id and defaults this key to `false`; forcing `use_worktrees: true` on a non-Claude install fails closed before any executor dispatch (#1515, #1521). | +| `workflow.use_worktrees` | boolean | `true` | When `false`, disables git worktree isolation for parallel execution. Users who prefer sequential execution or whose environment does not support worktrees can disable this. Added in v1.31. **Branch-divergence note:** when your branch has diverged from `origin/HEAD`, GSD auto-degrades to sequential and prints a warning. See [`worktree.baseRef`](#worktree-settings) to restore parallel execution on a diverged branch. **Non-Claude note:** git worktree isolation uses Claude Code's `isolation="worktree"` agent primitive, which no other runtime honors. On any non-Claude install (Codex, Cursor, Antigravity, Qwen, etc.) a runtime-neutral `.planning/config.json` resolves the runtime to that install's own id and defaults this key to `false`; forcing `use_worktrees: true` on a non-Claude install fails closed before any executor dispatch (#1515, #1521). | | `workflow.worktree_skip_hooks` | boolean | `false` | When `true`, executor agents in worktree mode pass `--no-verify` (skipping pre-commit hooks) and post-wave hook validation runs against the merged result instead. Opt-in escape hatch for projects whose hooks cannot run in agent worktrees. Default `false` runs hooks on every commit (#2924). | | `workflow.code_review` | boolean | `true` | Enable `/gsd-code-review` and `/gsd-code-review --fix` commands. When `false`, the commands exit with a configuration gate message. Added in v1.34 | | `workflow.code_review_depth` | string | `standard` | Default review depth for `/gsd-code-review`: `quick` (pattern-matching only), `standard` (per-file analysis), or `deep` (cross-file with import graphs). Can be overridden per-run with `--depth=`. Added in v1.34 | @@ -1140,7 +1140,7 @@ The five layers compose top-down: `model_profile` is the base tier, `models[ [--effort ] [--fas --- -### Non-Claude Runtimes (Codex, OpenCode, Gemini CLI, Kilo) +### Non-Claude Runtimes (Codex, OpenCode, Antigravity CLI, Kilo) > **Codex CLI minimum supported version: `0.130.0`** (issue [#3562](https://github.com/open-gsd/gsd-core/issues/3562)). > @@ -1402,7 +1402,7 @@ The intent is the same as the Claude profile tiers -- use a stronger model for p |-------|----------|----------| | `false` (default) | Returns Claude aliases (`opus`, `sonnet`, `haiku`) | Claude Code with native Anthropic API | | `true` | Maps aliases to full Claude model IDs (`claude-opus-4-8`) | Claude Code with API that requires full IDs | -| `"omit"` | Returns empty string (runtime picks its default) | Non-Claude runtimes (Codex, OpenCode, Gemini CLI, Kilo) | +| `"omit"` | Returns empty string (runtime picks its default) | Non-Claude runtimes (Codex, OpenCode, Antigravity CLI, Kilo) | ### Runtime-Aware Profiles (#2517) @@ -1416,14 +1416,13 @@ When `runtime` is set, profile tiers (`opus`/`sonnet`/`haiku`) resolve to runtim |---------|--------|----------|---------|------------------| | `claude` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` | (not used) | | `codex` | `gpt-5.5` | `gpt-5.4` | `gpt-5.4-mini` | `xhigh` / `medium` / `medium` | -| `gemini` | `gemini-3.1-pro-preview` | `gemini-3-flash` | `gemini-2.5-flash-lite` | (not used) | | `qwen` | `qwen3-max-2026-01-23` | `qwen3-coder-plus` | `qwen3-coder-next` | (not used) | | `opencode` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` | (not used) | | `copilot` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` | (not used) | | `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` | (not used) | | Group B (`kilo`, `cline`, `cursor`, `windsurf` (alias: `devin-desktop`), `augment`, `trae`, `codebuddy`, `antigravity`) | (no built-in default — your runtime handles model selection) | | | | -> **How these model IDs are sourced.** The catalog (`bin/shared/model-catalog.json`) pins each runtime's tier defaults to that provider's current frontier IDs, and may intentionally carry forward-dated IDs ahead of a provider's public docs. To verify an ID is live before changing it, check the provider's own source/API — e.g. Gemini: gemini-cli `packages/core/src/config/models.ts` or `gemini --model --prompt ping`; Codex: `codex debug models` or the OpenAI Codex models page; Qwen: Alibaba Model Studio model list. Only change an ID that the provider actually rejects — absence from documentation alone is not proof of invalidity. +> **How these model IDs are sourced.** The catalog (`bin/shared/model-catalog.json`) pins each runtime's tier defaults to that provider's current frontier IDs, and may intentionally carry forward-dated IDs ahead of a provider's public docs. To verify an ID is live before changing it, check the provider's own source/API — e.g. Codex: `codex debug models` or the OpenAI Codex models page; Qwen: Alibaba Model Studio model list. Only change an ID that the provider actually rejects — absence from documentation alone is not proof of invalidity. **Codex example** — one config, tiered models, no large `model_overrides` block: diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 1bd42dd0d..17c251d79 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -1011,7 +1011,7 @@ fix(03-01): correct auth token expiry **Purpose:** Run GSD across multiple AI coding agent runtimes. **Requirements:** -- REQ-RUNTIME-01: System MUST support Claude Code, OpenCode, Gemini CLI, Kilo, Codex, Copilot, Antigravity, Trae, Cline, Augment Code, CodeBuddy, Qwen Code +- REQ-RUNTIME-01: System MUST support Claude Code, OpenCode, Kilo, Codex, Copilot, Antigravity, Trae, Cline, Augment Code, CodeBuddy, Qwen Code - REQ-RUNTIME-02: Installer MUST transform content per runtime (tool names, paths, frontmatter) - REQ-RUNTIME-03: Installer MUST support interactive and non-interactive (`--claude --global`) modes - REQ-RUNTIME-04: Installer MUST support both global and local installation @@ -1022,13 +1022,13 @@ fix(03-01): correct auth token expiry **Runtime Transformations:** -| Aspect | Claude Code | OpenCode | Gemini | Kilo | Codex | Copilot | Antigravity | Cursor | Trae | Cline | Augment | CodeBuddy | Qwen Code | -|--------|------------|----------|--------|-------|-------|---------|-------------|--------|------|-------|---------|-----------|-----------| -| Commands | Slash commands | Slash commands | Slash commands (`{{args}}`) | Slash commands | Skills (TOML) | Slash commands | Skills | Skills + Slash commands | Skills | Rules | Skills + Slash commands | Slash commands | Skills | -| Agent format | Claude native | `mode: subagent` | Claude native | `mode: subagent` | Skills | Tool mapping | Skills | Skills | Skills | Rules | Skills | Skills | Skills | -| Skills emission | N/A | On-demand SKILL.md (1.4.0) | N/A | On-demand SKILL.md (1.4.0) | `/skills` picker (1.4.0) | N/A | N/A | SKILL.md | N/A | On-demand SKILL.md (1.4.0) | N/A | N/A | N/A | -| Hook events | `SessionStart`, `PreToolUse`, `PostToolUse`, `SubagentStop`, `Stop`, `PreCompact`, `FileChanged` | N/A | `SessionStart`, `BeforeTool`, `AfterTool`, `BeforeAgent`, `AfterAgent`, `BeforeModel` | N/A | `SessionStart`, `SubagentStart`, `Stop`, `PostToolUse` | `sessionStart` | N/A | `sessionStart`, `postToolUse` | N/A | `PreToolUse` | N/A | N/A | `SessionStart`, `PreToolUse`, `PostToolUse`, `SubagentStop`, `Stop`, `PreCompact` | -| Config | `settings.json` | `opencode.json(c)` | `settings.json` | `kilo.json(c)` | TOML | Instructions | Config | Config | Config | `.clinerules` | Config | Config | Config | +| Aspect | Claude Code | OpenCode | Kilo | Codex | Copilot | Antigravity | Cursor | Trae | Cline | Augment | CodeBuddy | Qwen Code | +|--------|------------|----------|-------|-------|---------|-------------|--------|------|-------|---------|-----------|-----------| +| Commands | Slash commands | Slash commands | Slash commands | Skills (TOML) | Slash commands | Skills | Skills + Slash commands | Skills | Rules | Skills + Slash commands | Slash commands | Skills | +| Agent format | Claude native | `mode: subagent` | `mode: subagent` | Skills | Tool mapping | Skills | Skills | Skills | Rules | Skills | Skills | Skills | +| Skills emission | N/A | On-demand SKILL.md (1.4.0) | On-demand SKILL.md (1.4.0) | `/skills` picker (1.4.0) | N/A | N/A | SKILL.md | N/A | On-demand SKILL.md (1.4.0) | N/A | N/A | N/A | +| Hook events | `SessionStart`, `PreToolUse`, `PostToolUse`, `SubagentStop`, `Stop`, `PreCompact`, `FileChanged` | N/A | N/A | `SessionStart`, `SubagentStart`, `Stop`, `PostToolUse` | `sessionStart` | N/A | `sessionStart`, `postToolUse` | N/A | `PreToolUse` | N/A | N/A | `SessionStart`, `PreToolUse`, `PostToolUse`, `SubagentStop`, `Stop`, `PreCompact` | +| Config | `settings.json` | `opencode.json(c)` | `kilo.json(c)` | TOML | Instructions | Config | Config | Config | `.clinerules` | Config | Config | Config | **Cursor artifact surfaces:** `gsd install --cursor` writes two artifact kinds: - `~/.cursor/skills/gsd-/SKILL.md` — rich skills with YAML frontmatter, Cursor tool-name mapping, and adapter context header (existing surface) @@ -1046,7 +1046,6 @@ fix(03-01): correct auth token expiry **Cross-runtime lifecycle hooks (1.4.0):** Each supported runtime registers lifecycle hook events for per-turn context-headroom tracking and workflow state management. Notable registrations: - **Claude Code:** `SubagentStop`, `Stop`, `PreCompact` (context-headroom warnings), `FileChanged` (hot-reloads `.planning/config.json` mid-session) -- **Gemini:** `BeforeAgent`, `AfterAgent`, `BeforeModel` - **Qwen Code:** `SubagentStop`, `Stop`, `PreCompact` - **Codex:** `SubagentStart`, `Stop`, `PostToolUse` (new in 1.4.0); on Windows the `SessionStart` hook entry gains a `commandWindows` field so the `.cmd` shim is used for native execution - **Cline:** `PreToolUse` @@ -1055,11 +1054,9 @@ fix(03-01): correct auth token expiry **Runtime-specific enrichments (1.4.0):** - Codex emits `service_tier: flex` for light-tier agents; GSD skills appear in the Codex `/skills` picker via `SKILL.md` (no `agents/openai.yaml` sidecar is emitted — doing so caused duplicate autocomplete entries, #1326) -- Gemini commands use native `{{args}}` interpolation **Native packaging:** - **Claude Code:** GSD Core ships a `.claude-plugin/plugin.json` manifest, enabling installation and lifecycle management via `claude plugin install|enable|disable|update gsd-core`. Commands load under the `/gsd-core:` namespace (e.g. `/gsd-core:plan-phase`), avoiding slash-command collisions with the classic npm installer which uses `/gsd:`. Always-on guard and update hooks are wired automatically via `hooks/hooks.json`. The plugin path is additive — the npm installer (`npx @opengsd/gsd-core`) remains fully supported. -- **Gemini CLI:** Ships `gemini-extension.json`, enabling installation and lifecycle management via `gemini extensions install|update|uninstall|link`. --- @@ -1481,7 +1478,7 @@ Test suite that scans all agent, workflow, and command files for embedded inject **Purpose:** Select multiple runtimes in a single interactive install session. **Requirements:** -- REQ-MULTI-RT-01: Interactive prompt MUST support multi-select (e.g., Claude Code + Gemini) +- REQ-MULTI-RT-01: Interactive prompt MUST support multi-select (e.g., Claude Code + Antigravity) - REQ-MULTI-RT-02: CLI flags MUST continue to work for non-interactive installs **Process:** @@ -1722,13 +1719,13 @@ Test suite that scans all agent, workflow, and command files for embedded inject **Requirements:** - REQ-SKILLS-01: Installer MUST write `skills/gsd-*/SKILL.md` for Claude Code 2.1.88+ - REQ-SKILLS-02: Installer MUST auto-clean legacy `commands/gsd/` directory -- REQ-SKILLS-03: Installer MUST maintain backward compatibility with older Claude Code versions via Gemini path +- REQ-SKILLS-03: Installer MUST maintain backward compatibility with older Claude Code versions via the legacy `commands/gsd/` path **Process:** 1. **Detect** — Check Claude Code version to determine skills support 2. **Migrate** — Write `skills/gsd-*/SKILL.md` files for each GSD command 3. **Clean** — Remove legacy `commands/gsd/` directory if skills are installed -4. **Fallback** — Maintain Gemini path compatibility for older Claude Code versions +4. **Fallback** — Maintain legacy `commands/gsd/` path compatibility for older Claude Code versions --- @@ -2997,7 +2994,7 @@ explicit reviewer flags -> --all -> review.default_reviewers -> all detected rev **Requirements:** - REQ-QUOTA-01: Quota failures MUST NOT offer immediate retry as the primary recovery. -- REQ-QUOTA-02: Classification MUST cover Claude, Copilot, Codex, Gemini, and generic provider sentinels. +- REQ-QUOTA-02: Classification MUST cover Claude, Copilot, Codex, and generic provider sentinels. - REQ-QUOTA-03: Non-quota failures MUST continue through the normal execution failure path. **Reference:** [Provider Rate Limit Signals](research/provider-rate-limit-signals.md) diff --git a/docs/USER-GUIDE.md b/docs/USER-GUIDE.md index 3245a38be..e06c10d4d 100644 --- a/docs/USER-GUIDE.md +++ b/docs/USER-GUIDE.md @@ -31,14 +31,13 @@ execute → verify → review → ship loop using existing GSD primitives. --- -## Slash-command forms (hyphen vs colon) +## Slash-command form -GSD ships **the same set of skills** to every supported runtime, but two slash-form spellings are in play: +GSD ships **the same set of skills** to every supported runtime, using the hyphen slash-form spelling: - **Hyphen form** — `/gsd-command-name` — used by Claude Code, Copilot, OpenCode, Kilo, Cursor, Windsurf, Augment, Antigravity, and Trae. -- **Colon form** — `/gsd:command-name` — used by **Gemini CLI only**. Gemini namespaces every plugin's commands under the plugin id, so the install path rewrites every body-text reference and command file to the colon form during `--gemini` install. -You don't need to choose — the installer writes the correct form into the command directory of each runtime you target. When following a walkthrough on a Gemini terminal, replace the hyphen after `gsd` with a colon as you read each slash command. +The installer writes this form into the command directory of each runtime you target. ## Namespace routing primer (`gsd-ns-*`, v1.40+) @@ -732,7 +731,7 @@ Each disabled server removes its schema from every subsequent turn. Trimming MCP For the full audit, harness reference, and the composition note with `model_profile`, see [MCP Tool Schema Cost](../gsd-core/references/context-budget.md#mcp-tool-schema-cost-harness-concern) in the bundled `context-budget.md` reference. -### Using Non-Claude Runtimes (Codex, OpenCode, Gemini CLI, Kilo) +### Using Non-Claude Runtimes (Codex, OpenCode, Antigravity CLI, Kilo) > **Codex CLI minimum supported version: `0.130.0`** (issue [#3562](https://github.com/open-gsd/gsd-core/issues/3562)). @@ -776,7 +775,6 @@ See [Runtime-Aware Profiles](CONFIGURATION.md#runtime-aware-profiles-2517). When generating artifacts, the installer adapts GSD commands to each runtime's native command schema: -- **Gemini CLI** — generated TOML commands use Gemini's `{{args}}` placeholder (translated from Claude's `$ARGUMENTS`) so typed arguments interpolate into the prompt, and `/gsd:progress` injects live project state via a fixed `!{cat .planning/STATE.md 2>/dev/null}` shell block (no interpolated input, so no injection risk; Gemini shows its standard confirmation dialog). - **Qwen Code** — main-loop skills carry Qwen's numeric `priority` field so the most-used workflows (e.g. `new-project`, `plan-phase`, `execute-phase`) sort first in the `/skills` list; utility skills are left unset. Higher values sort earlier; the field affects only the `/skills` list order. See [How to install GSD Core on your runtime](how-to/install-on-your-runtime.md) for the full per-runtime details. @@ -817,43 +815,6 @@ GSD installs four surfaces for CodeBuddy: `/gsd-*` slash commands in `~/.codebud npx @opengsd/gsd-core --qwen --global ``` -### Installing as a Gemini CLI extension (#775) - -GSD ships a `gemini-extension.json` extension manifest at the repository root, so -Gemini CLI users can install, update, and remove GSD through Gemini's own -extension lifecycle — and have it show up in `gemini extensions list`: - -```bash -# Install (Gemini clones the repo and copies the extension) -gemini extensions install https://github.com/open-gsd/gsd-core - -# Update to the latest released manifest version -gemini extensions update gsd-core - -# Remove -gemini extensions uninstall gsd-core -``` - -For local development against a checkout, symlink it instead of copying: - -```bash -gemini extensions link /path/to/gsd-core -``` - -**What the extension delivers today:** it loads GSD's operating context -(`GEMINI.md`) into every Gemini session in the project, and gives you the -discoverable install/update/remove lifecycle above. The `/gsd:*` slash commands, -agents, and hooks are still installed via the dedicated installer: - -```bash -npx @opengsd/gsd-core --gemini --global -``` - -The two paths are complementary and additive — installing the extension does not -change or replace the `npx gsd-core --gemini` install, and either can be used on -its own. (Slash-command/agent/hook projection into the extension package itself -is a planned follow-up.) - ### Installing for Prerelease Editions Set the runtime's `*_CONFIG_DIR` env var to the prerelease directory before running the installer: @@ -867,7 +828,6 @@ WINDSURF_CONFIG_DIR=~/.codeium/windsurf-next npx @opengsd/gsd-core@latest --wind | Runtime | Stable default | Override env var | |---|---|---| | Claude Code | `~/.claude` | `CLAUDE_CONFIG_DIR` | -| Gemini CLI | `~/.gemini` | `GEMINI_CONFIG_DIR` | | OpenCode | `XDG_CONFIG_HOME/opencode` | `OPENCODE_CONFIG_DIR` | | Codex | (per Codex CLI) | `--config-dir` flag | | Copilot | `~/.copilot` | `COPILOT_CONFIG_DIR` (or `COPILOT_HOME`) | diff --git a/docs/context-monitor.md b/docs/context-monitor.md index da530e1e3..8f7fdbf9d 100644 --- a/docs/context-monitor.md +++ b/docs/context-monitor.md @@ -1,6 +1,6 @@ # Context Window Monitor -A post-tool hook (`PostToolUse` for Claude Code, `AfterTool` for Gemini CLI) that warns the agent when context window usage is high. +A post-tool hook (`PostToolUse` for Claude Code, `AfterTool` for Antigravity CLI) that warns the agent when context window usage is high. ## Problem @@ -62,7 +62,7 @@ GSD's `/gsd-pause-work` command saves execution state. The WARNING message sugge Both hooks are registered automatically during `npx @opengsd/gsd-core` installation — no manual steps are needed under normal circumstances. For hook configuration details, threshold overrides, and manual registration examples, see [Configuration](CONFIGURATION.md). -As a brief reference: the statusline hook registers as `statusLine` in `settings.json`; the context monitor (`gsd-context-monitor.js`) registers as a `PostToolUse` hook (or `AfterTool` for Gemini CLI). Both entries use the absolute Node executable path that ran the installer. On Windows PowerShell, prefix quoted executable paths with `&`. +As a brief reference: the statusline hook registers as `statusLine` in `settings.json`; the context monitor (`gsd-context-monitor.js`) registers as a `PostToolUse` hook (or `AfterTool` for Antigravity CLI). Both entries use the absolute Node executable path that ran the installer. On Windows PowerShell, prefix quoted executable paths with `&`. ## Safety diff --git a/docs/explanation/context-engineering.md b/docs/explanation/context-engineering.md index a9a84a239..d58526529 100644 --- a/docs/explanation/context-engineering.md +++ b/docs/explanation/context-engineering.md @@ -77,7 +77,6 @@ Since GSD 1.4.0, this is addressed by registering runtime lifecycle hooks. Rathe Each AI runtime exposes lifecycle events in its own vocabulary, but the purpose is the same across all of them: fire at boundaries that correspond to context pressure or turn transitions, so GSD can observe and react. - **Claude Code** fires `PreCompact` when a compaction is about to occur, `Stop` when a session turn ends, and `SubagentStop` when a spawned subagent completes. Together these bracket the moments when context has grown or a context-consuming task has just finished. -- **Gemini** fires `BeforeAgent`/`AfterAgent` around each agent invocation, and `BeforeModel` before each model call — giving a per-inference opportunity to check headroom. - **Qwen** exposes `SubagentStop`, `Stop`, and `PreCompact`, mirroring Claude Code's shape in its own event system. Think of these as the same concept — "notify GSD at context boundaries" — expressed in each runtime's native event vocabulary. This is the multi-runtime philosophy applied at the observability layer: GSD registers the semantically equivalent hook wherever each runtime exposes it, rather than demanding every runtime adopt a single event schema. diff --git a/docs/how-to/attach-a-plugin-skill-to-a-gsd-agent.md b/docs/how-to/attach-a-plugin-skill-to-a-gsd-agent.md index e4c3d305b..4be8dacd4 100644 --- a/docs/how-to/attach-a-plugin-skill-to-a-gsd-agent.md +++ b/docs/how-to/attach-a-plugin-skill-to-a-gsd-agent.md @@ -2,7 +2,7 @@ Use this guide when you have a Claude Code plugin that ships a skill and you want GSD agents to load it automatically at spawn time. The `global::` entry form makes GSD emit a Skill-tool load directive so the agent picks up the plugin skill alongside any project-local or personal global skills. -**This feature works on the Claude runtime only.** On other runtimes (Codex, Gemini, Cursor, etc.) the entry is skipped with a warning. Plan accordingly if your team uses multiple runtimes. +**This feature works on the Claude runtime only.** On other runtimes (Codex, Antigravity, Cursor, etc.) the entry is skipped with a warning. Plan accordingly if your team uses multiple runtimes. --- diff --git a/docs/how-to/configure-model-profiles.md b/docs/how-to/configure-model-profiles.md index 5f71d0130..153611526 100644 --- a/docs/how-to/configure-model-profiles.md +++ b/docs/how-to/configure-model-profiles.md @@ -155,7 +155,7 @@ Every attempt uses `tier_models[default_tier]` regardless of outcome — useful ## Using GSD on non-Anthropic runtimes -If you installed GSD for Codex, OpenCode, Gemini CLI, or Kilo, the installer already set `resolve_model_ids: "omit"` in your config. This tells GSD to skip Anthropic model ID resolution and let the runtime choose its own default model. No manual setup is needed for the basic case. +If you installed GSD for Codex, OpenCode, Antigravity CLI, or Kilo, the installer already set `resolve_model_ids: "omit"` in your config. This tells GSD to skip Anthropic model ID resolution and let the runtime choose its own default model. No manual setup is needed for the basic case. **If you want tiered models on Codex:** diff --git a/docs/how-to/connect-gsd-mcp-server.md b/docs/how-to/connect-gsd-mcp-server.md index a036419cd..7d803e597 100644 --- a/docs/how-to/connect-gsd-mcp-server.md +++ b/docs/how-to/connect-gsd-mcp-server.md @@ -1,7 +1,7 @@ # How to connect a host to the GSD companion MCP server This guide shows you how to make a MCP-capable host (Claude Code, Codex, -OpenCode, VS Code, Gemini CLI, Cursor, Cline, Hermes) drive GSD — run GSD +OpenCode, VS Code, Antigravity CLI, Cursor, Cline, Hermes) drive GSD — run GSD commands and read/write `.planning/` state — through the companion MCP server, with no bespoke plugin. @@ -29,7 +29,7 @@ host. - **Claude Code / Codex / OpenCode / Cursor / Cline / Hermes** — under the host's `mcpServers` object (project or user config). - **VS Code** — in the workspace MCP servers list. -- **Gemini CLI** — under its `mcpServers` block. +- **Antigravity CLI** — under its `mcpServers` block. Set `cwd` to the project whose `.planning/` you want GSD to manage — the server resolves state paths against it. diff --git a/docs/how-to/develop-a-capability.md b/docs/how-to/develop-a-capability.md index 21f66f628..75de679c3 100644 --- a/docs/how-to/develop-a-capability.md +++ b/docs/how-to/develop-a-capability.md @@ -1,6 +1,6 @@ # Develop a Capability for GSD 1.5+ -This guide shows you how to add or change a first-party GSD Capability after the ADR-857 cutover. In GSD terms, the extension unit is a **Capability**. A plugin is a packaging or host-runtime term, for example a Claude Code plugin or Gemini extension, not the unit that owns a GSD feature. +This guide shows you how to add or change a first-party GSD Capability after the ADR-857 cutover. In GSD terms, the extension unit is a **Capability**. A plugin is a packaging or host-runtime term, for example a Claude Code plugin, not the unit that owns a GSD feature. A Capability is right when the feature can be toggled as one unit and owns its own skills, agents, hooks, config keys, or command family. Keep verifier predicate contracts, the five-step loop spine, and shared infrastructure in core unless the ADRs explicitly move that boundary. diff --git a/docs/how-to/install-on-your-runtime.md b/docs/how-to/install-on-your-runtime.md index d97430123..d24c1203f 100644 --- a/docs/how-to/install-on-your-runtime.md +++ b/docs/how-to/install-on-your-runtime.md @@ -8,7 +8,7 @@ Install GSD Core (`@opengsd/gsd-core`) into the AI coding runtime you use every ## Why the installer is required -GSD Core ships agent and command files in Claude Code's native frontmatter format. Each supported runtime expects a different schema, directory layout, and command-invocation syntax. The installer performs the necessary transformations — for example, converting tool lists and colour values for OpenCode, writing TOML agent entries for Codex, and rewriting every command body from hyphen form (`/gsd-update`) to colon form (`/gsd:update`) for Gemini CLI. +GSD Core ships agent and command files in Claude Code's native frontmatter format. Each supported runtime expects a different schema, directory layout, and command-invocation syntax. The installer performs the necessary transformations — for example, converting tool lists and colour values for OpenCode and writing TOML agent entries for Codex. **Do not copy files from `agents/` or `commands/` directly.** Doing so bypasses the transformations and produces schema-validation errors or missing commands. @@ -113,57 +113,6 @@ This path is **additive** and changes nothing about the Claude Code plugin insta --- -### Gemini CLI - -```bash -npx @opengsd/gsd-core@latest --gemini --global -``` - -Skills land in `~/.gemini/`. The installer rewrites all command bodies to Gemini's colon namespace (`/gsd:update`, `/gsd:config`, etc.). Restart Gemini CLI after install. - -The installer also enriches the generated TOML commands with two native Gemini custom-command features: - -- **`{{args}}` interpolation** — every command that references arguments inline is emitted with Gemini's `{{args}}` placeholder (translated from Claude's `$ARGUMENTS`), so flags and free-text you type after the command name are interpolated into the prompt body rather than ignored. -- **`!{...}` live-state injection** — `/gsd:progress` injects the current contents of `.planning/STATE.md` via a fixed `!{cat .planning/STATE.md 2>/dev/null}` shell block, giving Gemini live project state without relying on session memory. The shell block contains no interpolated input, so there is no injection risk; Gemini still shows its standard confirmation dialog the first time the command runs in a session. - -**Override the install directory:** - -```bash -GEMINI_CONFIG_DIR=~/.gemini-alt npx @opengsd/gsd-core@latest --gemini --global -``` - -**Hook coverage** - -GSD registers the following hook events automatically on install: - -| Event | Hook | Purpose | -|---|---|---| -| `SessionStart` | `gsd-check-update.js`, `gsd-session-state.sh` | Update check, session orientation | -| `BeforeTool` | `gsd-prompt-guard.js`, `gsd-read-guard.js`, `gsd-workflow-guard.js`, `gsd-worktree-path-guard.js`, `gsd-validate-commit.sh` | Prompt guard, read-before-edit, workflow + worktree safety, commit validation | -| `AfterTool` | `gsd-context-monitor.js`, `gsd-read-injection-scanner.js`, `gsd-phase-boundary.sh`, `gsd-graphify-update.sh` | Context monitoring, read-time scan, phase boundary detection | -| `BeforeAgent` | `gsd-context-monitor.js` | Context headroom awareness before the agent begins planning each prompt | -| `AfterAgent` | `gsd-context-monitor.js` | Context headroom tracking after each agent turn's final response | -| `BeforeModel` | `gsd-context-monitor.js` | Per-turn context injection before each LLM call | - -> **`hooksConfig.enabled: false` warning.** If your Gemini `settings.json` contains `hooksConfig.enabled: false`, the Gemini CLI silently disables all hook execution — GSD hooks are registered but will never run. The installer detects this and emits a warning. To enable hooks, set `hooksConfig.enabled: true` in `~/.gemini/settings.json` (or the directory matching your `GEMINI_CONFIG_DIR`). - ---- - -### Gemini CLI — native extension install (#775) - -GSD also ships a `gemini-extension.json` extension manifest, so you can manage GSD through Gemini's own extension lifecycle and see it in `gemini extensions list`: - -```bash -gemini extensions install https://github.com/open-gsd/gsd-core # install -gemini extensions update gsd-core # update -gemini extensions uninstall gsd-core # remove -gemini extensions link /path/to/gsd-core # dev: symlink a checkout -``` - -The extension loads GSD's operating context (`GEMINI.md`) into every session and gives you the discoverable install/update/remove lifecycle. The `/gsd:*` slash commands, agents, and hooks are installed separately by `npx @opengsd/gsd-core --gemini --global` (above). The two paths are complementary and additive — neither replaces the other, and slash-command projection into the extension is a planned follow-up. - ---- - ### OpenCode ```bash diff --git a/docs/how-to/spike-and-sketch.md b/docs/how-to/spike-and-sketch.md index ea93c9d0f..baa403961 100644 --- a/docs/how-to/spike-and-sketch.md +++ b/docs/how-to/spike-and-sketch.md @@ -90,7 +90,7 @@ GSD opens a short conversation to explore feel, visual references, and the core `--quick` skips the intake conversation entirely and uses the argument as the design direction. -### Non-Claude runtimes (Codex, Gemini CLI, etc.) +### Non-Claude runtimes (Codex, Antigravity CLI, etc.) ```bash /gsd-sketch --text "onboarding flow" diff --git a/docs/installer-migrations.md b/docs/installer-migrations.md index cc4ceb8fd..0edc4c6b9 100644 --- a/docs/installer-migrations.md +++ b/docs/installer-migrations.md @@ -145,7 +145,7 @@ module.exports = { title: 'Move legacy commands into runtime skills', description: 'Move legacy runtime command files into the generated skill layout.', introducedIn: '1.50.0', - runtimes: ['claude', 'codex', 'gemini'], + runtimes: ['claude', 'codex', 'antigravity'], scopes: ['global', 'local'], destructive: true, plan(ctx) { @@ -280,7 +280,7 @@ The installer runs migrations before materializing the new package payload. The Phase 4 install integration wires this flow into the normal install/update entry point for every supported runtime: Claude Code, Antigravity, Augment, -Cline, CodeBuddy, Codex, Copilot, Cursor, Gemini, Hermes Agent, Kilo, OpenCode, +Cline, CodeBuddy, Codex, Copilot, Cursor, Hermes Agent, Kilo, OpenCode, Qwen Code, Trae, and Windsurf. The installer invokes the same migration runner with `baselineScan: true`, reports the projected action rows, applies safe non-interactive actions before materialization, persists install state only after @@ -367,7 +367,6 @@ for the new shape before changing migration behavior. | Claude Code | Global skills in `skills/gsd-*/SKILL.md`; local slash commands in `commands/gsd/*.md`; agents in `agents/gsd-*.md`; hooks in `hooks/`; `settings.json` registrations | Global `CLAUDE_CONFIG_DIR` or `~/.claude`; local `./.claude` | GSD owns only generated skills, local commands, `gsd-*` agents, hook files, and GSD hook/statusLine entries in `settings.json` | [Slash commands](https://docs.anthropic.com/en/docs/claude-code/slash-commands), [settings](https://docs.anthropic.com/en/docs/claude-code/settings), [hooks](https://docs.anthropic.com/en/docs/claude-code/hooks), [subagents](https://docs.anthropic.com/en/docs/claude-code/sub-agents); docs not versioned, checked 2026-05-11 | | OpenCode | Flat markdown commands in `command/gsd-*.md`; agents in `agents/gsd-*.md`; config updates in `opencode.json` or `opencode.jsonc` | Global `OPENCODE_CONFIG_DIR`, `dirname(OPENCODE_CONFIG)`, `XDG_CONFIG_HOME/opencode`, or `~/.config/opencode`; local `./.opencode` | GSD owns generated command/agent files and GSD entries in structured config only | [Config](https://opencode.ai/docs/config/); docs published 2026-05, checked 2026-05-11 | | Kilo | OpenCode-style flat markdown commands in `command/gsd-*.md`; agents in `agents/gsd-*.md`; config updates in `kilo.json` or `kilo.jsonc` | Global `KILO_CONFIG_DIR`, `dirname(KILO_CONFIG)`, `XDG_CONFIG_HOME/kilo`, or `~/.config/kilo`; local `./.kilo` | GSD owns generated command/agent files and GSD entries in structured config only | [Custom subagents](https://docs.kilo.ai/docs/customize/custom-subagents); docs not versioned, checked 2026-05-11 | -| Gemini CLI | TOML slash commands in `commands/gsd/*.toml`; agents in `agents/gsd-*.md`; `settings.json` feature flag, hooks, and statusline | Global `GEMINI_CONFIG_DIR` or `~/.gemini`; local `./.gemini` | GSD owns generated commands/agents/hooks and only GSD settings entries; local command copy may be skipped when global GSD commands already exist | [Custom commands](https://google-gemini.github.io/gemini-cli/docs/cli/custom-commands.html), [configuration](https://google-gemini.github.io/gemini-cli/docs/cli/configuration.html); docs checked 2026-05-11 | | Kimi CLI | Agent Skills in `skills/gsd-*/SKILL.md`; explicit custom agent YAML/prompt artifacts in `agents/gsd.yaml`, `agents/gsd.md`, and `agents/subagents/gsd-*`; `gsd-core/` payload files referenced by generated skills; manifest, pristine, local-patch, and migration journal files from the normal installer safety pipeline | Global `KIMI_CONFIG_DIR`, explicit `--config-dir`, or first-existing generic skills root: `~/.config/agents` when `~/.config/agents/skills` exists or no generic skills root exists yet, otherwise `~/.agents` when `~/.agents/skills` exists and `~/.config/agents/skills` does not; `KIMI_CONFIG_DIR` and `--config-dir` are GSD write-location overrides and arbitrary roots require Kimi-side `--skills-dir` or `extra_skill_dirs` configuration for skill discovery; local `--kimi --local` is guarded and writes no project-level artifacts | GSD owns only generated `skills/gsd-*`, `agents/gsd.*`, `agents/subagents/gsd-*`, installed `gsd-core/` payload files, and manifest/preservation/migration records. GSD does not own Kimi config files, hooks, settings, rules, statusline, update-banner registration, or non-GSD Kimi skills/agents. Reinstall/update must preserve locally modified generated Kimi artifacts through manifest-backed `gsd-local-patches/`; uninstall removes only GSD-owned Kimi artifacts and preserves non-GSD user content. | [Agent Skills](https://moonshotai.github.io/kimi-cli/en/customization/skills.html), [Agents and Subagents](https://moonshotai.github.io/kimi-cli/en/customization/agents.html), [Tools](https://moonshotai.github.io/kimi-code/en/reference/tools.html); docs checked 2026-06-07 | | Codex | Skills in `skills/gsd-*/SKILL.md`; agents as source markdown plus per-agent TOML in `agents/`; `[agents.gsd-*]` and hooks in `config.toml` | Global `CODEX_HOME` or `~/.codex`; local `./.codex` | GSD owns generated skills, generated agent TOML, `agents.gsd-*` config sections, `[features].hooks` when added by GSD (canonical; legacy alias `codex_hooks` is recognized and migrated forward, #3566), and GSD hook entries | [Codex config schema](https://developers.openai.com/codex/config-schema.json), [Codex developer docs](https://developers.openai.com/codex/); docs not versioned, checked 2026-05-15; installer compatibility sentinel: Codex 0.130.0 features.hooks key (legacy `codex_hooks` recognized) | | GitHub Copilot | Skills in `skills/gsd-*/SKILL.md`; agents as `.agent.md`; repository instructions in `copilot-instructions.md` | Global `COPILOT_CONFIG_DIR`, `COPILOT_HOME`, or `~/.copilot`; local `./.github` | GSD owns generated skill/agent files and GSD-authored instruction files; no hook/statusline ownership | [Repository custom instructions](https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions), [Copilot CLI custom instructions](https://docs.github.com/en/copilot/how-tos/copilot-cli/add-custom-instructions); GitHub Docs product docs, checked 2026-05-11 | diff --git a/docs/manual-update.md b/docs/manual-update.md index b6e570266..9ae8746b0 100644 --- a/docs/manual-update.md +++ b/docs/manual-update.md @@ -32,7 +32,6 @@ Replace `--claude` with the flag for your runtime: | Runtime | Flag | |---|---| | Claude Code | `--claude` | -| Gemini CLI | `--gemini` | | OpenCode | `--opencode` | | Kilo | `--kilo` | | Codex | `--codex` | diff --git a/docs/reference/capability-matrix.md b/docs/reference/capability-matrix.md index b70d9af59..a1286fd77 100644 --- a/docs/reference/capability-matrix.md +++ b/docs/reference/capability-matrix.md @@ -71,7 +71,7 @@ points. | `tdd` | feature | full | `>=1.6.0` | `plan:pre`, `execute:post` | contribution, gate | first-party | | `ui` | feature | full | `>=1.6.0` | `plan:pre`, `execute:wave:post`, `verify:post` | step, gate | first-party | -### Runtime capabilities (role: runtime) — 16 +### Runtime capabilities (role: runtime) — 15 Runtime capabilities adapt GSD to a specific AI runtime or IDE — emitting skills, agents, hooks configuration, and surface files for that host. They @@ -88,7 +88,6 @@ emission), so their extension-point and hook-kind cells are `—`. | `codex` | runtime | core | `>=1.6.0` | — | — | first-party | | `copilot` | runtime | core | `>=1.6.0` | — | — | first-party | | `cursor` | runtime | core | `>=1.6.0` | — | — | first-party | -| `gemini` | runtime | core | `>=1.6.0` | — | — | first-party | | `hermes` | runtime | core | `>=1.6.0` | — | — | first-party | | `kilo` | runtime | core | `>=1.6.0` | — | — | first-party | | `kimi` | runtime | core | `>=1.6.0` | — | — | first-party | diff --git a/docs/reference/host-integration-capability-matrix.md b/docs/reference/host-integration-capability-matrix.md index 3b6f06d08..b382414d3 100644 --- a/docs/reference/host-integration-capability-matrix.md +++ b/docs/reference/host-integration-capability-matrix.md @@ -119,38 +119,6 @@ Documentation gaps: --- -## gemini - -| Axis | Value | Source | Evidence | -|---|---|---|---| -| embeddingMode | declarative | https://github.com/google-gemini/gemini-cli/blob/main/packages/sdk/SDK_DESIGN.md | "This feature is currently not implemented. (repeated for both extension and subagent SDK APIs; all actual integration is via files: TOML com" | -| commandSurface | slash-toml | https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/custom-commands.md | "Custom commands in Gemini CLI are defined in TOML format with the .toml file extension … Commands are invoked as slash commands in the CLI." | -| modelMode | passive | https://github.com/google-gemini/gemini-cli/blob/main/docs/hooks/reference.md | "BeforeModel Hook: Fires before sending a request to the LLM … hookSpecificOutput.llm_request (object) — An object that overrides parts of th" | -| hookBus | host | https://github.com/google-gemini/gemini-cli/blob/main/docs/hooks/reference.md | "Hooks function as host-fired events … The CLI fires events at predetermined lifecycle points [BeforeAgent, AfterAgent, BeforeTool, AfterTool" | -| stateIO | filesystem | https://github.com/google-gemini/gemini-cli/blob/main/docs/reference/configuration.md | "Local access by default with gitignore/geminiignore respect … Set to a boolean to enable or disable the sandbox" | -| transport | mcp | https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md | "Configure a Node.js MCP server using stdio … { \"mcpServers\": { \"nodeServer\": { \"command\": \"node\", \"args\": [\"dist/server.js\"] } } }" | -| runtime | node | https://github.com/google-gemini/gemini-cli/blob/main/docs/reference/configuration.md | "References to node-pty and child_process indicate JavaScript/Node.js execution environment" | -| dispatch.namedDispatch | true | https://github.com/google-gemini/gemini-cli/blob/main/docs/core/subagents.md | "Example of a custom subagent definition file (.gemini/agents/security-auditor.md) … name: security-auditor" | -| dispatch.nested | false | https://github.com/google-gemini/gemini-cli/blob/main/docs/core/subagents.md | "Each subagent operates in an isolated context loop … This isolation also includes recursion protection, preventing subag" | -| dispatch.maxDepth | 1 | https://github.com/google-gemini/gemini-cli/blob/main/docs/core/subagents.md | "To prevent infinite loops and excessive token usage, subagents cannot call other subagents. … The architecture enforces" | -| dispatch.background | undocumented | no authoritative doc — searched: https://github.com/google-gemini/gemini-cli/blob/main/docs/core/subagents.md, /google-gemini/gemini-cli (Context7 library) | — | -| dispatch.subagentToolkit | undocumented | no authoritative doc — searched: https://github.com/google-gemini/gemini-cli/blob/main/docs/core/subagents.md, /google-gemini/gemini-cli (Context7 library) | — | -| dispatch.backgroundDispatch | false | https://raw.githubusercontent.com/google-gemini/gemini-cli/main/docs/core/subagents.md | "To prevent infinite loops and excessive token usage, subagents cannot call other subagents." Additionally: "If a subagent is granted the `*` tool wildcard, it will still be unable to see or invoke ot | - -Sources consulted: -- https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/custom-commands.md -- https://github.com/google-gemini/gemini-cli/blob/main/docs/core/subagents.md -- https://github.com/google-gemini/gemini-cli/blob/main/docs/hooks/reference.md -- https://github.com/google-gemini/gemini-cli/blob/main/docs/reference/configuration.md -- https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md -- https://github.com/google-gemini/gemini-cli/blob/main/packages/sdk/SDK_DESIGN.md -- /google-gemini/gemini-cli (Context7 library) - -Documentation gaps: -- dispatch.background — docs describe subagents as operating in isolated context loops but do not state whether they execute synchronously or asynchronously. -- dispatch.subagentToolkit — subagents have a configurable tool grant model but no single fixed value of 'full' or 'read-only' is stated as the architecture-level constraint. - ---- ## opencode diff --git a/docs/reference/skill-mapping-matrix.md b/docs/reference/skill-mapping-matrix.md index 6001cfb65..959c63d01 100644 --- a/docs/reference/skill-mapping-matrix.md +++ b/docs/reference/skill-mapping-matrix.md @@ -20,13 +20,12 @@ GSD ships skills (and commands/agents) as Markdown files under `commands/gsd/*.m For the transform each converter applies, see [ADR-1593 §3 — converter transform-contract categories](../adr/1593-skill-mapping-converter-methodology.md#3-the-converter-transform-contract-categories). -## The 16-runtime matrix +## The 15-runtime matrix | Runtime | Skill dest (global) | Prefix | Nesting | Loader | Converter | Notes | |---------|---------------------|--------|---------|--------|-----------|-------| | **claude** | `skills/` | `gsd-` | flat | one-level (reverted from nested, #924) | `convertClaudeCommandToClaudeSkill` | Local scope ships commands+agents only (no `skills` kind). Plugin manifest (ADR-766) ships skills via build-generated `skills/` dir (Phase B-provide, PR #1597, merged). | | **codex** | `skills/` | `gsd-` | flat | unconfirmed → conservative | `convertClaudeCommandToCodexSkill` | TOML config (`configFormat: toml`). Description truncated to 180 chars (`metadata.short-description`). `sandboxTier: codex-agent-sandbox`. | -| **gemini** | — *(no skills kind)* | — | — | — | — | Commands-only (TOML `.toml` in `commands/gsd`). No skill surface; extension model has no `skills` field (C1: N/A). | | **opencode** | `skills/` | `gsd-` | flat | recursive (`**` glob) | `convertClaudeCommandToOpencodeSkill` | XDG config home. Shares the opencode-family converter entry point (`convertClaudeCommandToOpencodeFamilySkill`). Also ships `command` (singular) commands. | | **kilo** | `skills/` | `gsd-` | flat | recursive (`**` glob) | `convertClaudeCommandToKiloSkill` | OpenCode fork; same `**` glob loader. `permissionWriter: kilo`. Also ships `command` commands. | | **cursor** | `skills/` | `gsd-` | flat | recursive | `convertClaudeCommandToCursorSkill` | Also ships flat `commands/` via `convertClaudeCommandToCursorCommand`. `configFormat: none`. | @@ -43,7 +42,7 @@ For the transform each converter applies, see [ADR-1593 §3 — converter transf ### Structural facts -- **All 14 skill-bearing runtimes use `prefix: "gsd-"`.** Two runtimes have no skills kind: Gemini (commands-only TOML) and Windsurf (emits `.windsurf/workflows/gsd-*.md` slash-command workflows instead — #1615). +- **All 14 skill-bearing runtimes use `prefix: "gsd-"`.** One runtime has no skills kind: Windsurf (emits `.windsurf/workflows/gsd-*.md` slash-command workflows instead — #1615). - **Five runtimes nest** (cline, qwen, hermes, augment, trae) because their skill loaders scan one level deep — nesting drops nested concrete skills out of the eager top-level listing while keeping them readable by file path (the namespace-router contract, #69). - **Eight runtimes stay flat**: three because their loaders recurse (cursor, opencode, kilo — nesting saves nothing), two because nesting was reverted (claude — the Skill tool errors on unknown names rather than re-routing, #924; antigravity — `agy` scans only `skills//SKILL.md`, so nested sub-skills were unreachable, #1614), and three conservatively where the loader depth is unconfirmed (codex, copilot, codebuddy). - **Three runtimes share `convertClaudeCommandToClaudeSkill`** (claude, qwen, hermes). The converter branches on the `runtime` arg for per-runtime branding (Hermes `version:`, Qwen `priority:`). @@ -66,14 +65,13 @@ Per [ADR-1593 §5](../adr/1593-skill-mapping-converter-methodology.md#5-plugin-- | Runtime | Provision model | Consumption model | Outcome | |---------|-----------------|-------------------|---------| | **claude** | `.claude-plugin/plugin.json` `"skills": "./skills/"` — build-generated dir (PR #1597, merged) | Sub-agent `skills:` preload + runtime `Skill` tool (PR #1261, merged) | **Implemented (Phase B)** | -| **gemini** | **N/A** — `gemini-extension.json` supports `mcpServers` + `contextFileName` only; no `skills` field. Gemini CLI SDK lists skills as a future extension primitive (*"currently not implemented"*). | **N/A** — same rationale. | **C1: N/A** | | **codex** | **N/A** — no plugin/extension manifest model. Uses `AGENTS.md` + TOML via file-copy install. | **N/A** — same rationale. | **C2: N/A** | | **opencode / kilo** | **N/A** — no first-party plugin manifest. Recursive `skills/**/SKILL.md` glob loader scans the local config dir that `bin/install.js` writes to. | **N/A** — same rationale. | **C3: N/A** | | **cursor, copilot, windsurf, codebuddy** | **N/A** — IDE-based tools with no plugin skill-provision model. File-copy install only. | **N/A** — same rationale. | **C4: N/A** | | **cline, qwen, hermes, augment, trae, antigravity** | **N/A** — CLI tools with no plugin marketplace model. File-copy install only. | **N/A** — same rationale. | **C5: N/A** | | **kimi** | **N/A** — special `kimi-agents` kind but no plugin/extension manifest. File-copy install only. | **N/A** — same rationale. | **C6: N/A** | -**Phase D (first-party packaging parity):** Complete. Claude Code's `.claude-plugin/plugin.json` is the only first-party manifest with a `skills` field (Phase B-provide, PR #1597). Gemini's `gemini-extension.json` is context-only (no skills field — C1 N/A). No other first-party packaging exists. +**Phase D (first-party packaging parity):** Complete. Claude Code's `.claude-plugin/plugin.json` is the only first-party manifest with a `skills` field (Phase B-provide, PR #1597). No other first-party packaging exists. > **Rejected for all platforms:** reading another plugin's ephemeral/undocumented cache (e.g. Claude Code's `${CLAUDE_PLUGIN_ROOT}` / `~/.claude/plugins/cache`). The platform's native mechanism is the contract; cache-reading is a workaround, not a fix. diff --git a/gemini-extension.json b/gemini-extension.json deleted file mode 100644 index 032a4b555..000000000 --- a/gemini-extension.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "gsd-core", - "version": "1.7.0-rc.2", - "description": "GSD Core — a meta-prompting, context engineering, and spec-driven development system for AI coding agents. Loads gsd's operating context into every Gemini CLI session.", - "contextFileName": "GEMINI.md" -} diff --git a/gsd-core/bin/lib/capability-registry.cjs b/gsd-core/bin/lib/capability-registry.cjs index 82ded790a..6dfb625ac 100644 --- a/gsd-core/bin/lib/capability-registry.cjs +++ b/gsd-core/bin/lib/capability-registry.cjs @@ -1080,81 +1080,6 @@ const capabilities = { } ] }, - "gemini": { - "id": "gemini", - "role": "runtime", - "version": "1.7.0-rc.2", - "title": "Gemini CLI", - "description": "Google Gemini CLI — commands-only artifact layout (TOML); Gemini hook event dialect; settings-json hook surface; tier-2 support.", - "tier": "core", - "requires": [], - "engines": { - "gsd": ">=1.6.0" - }, - "runtime": { - "configHome": { - "kind": "dot-home", - "name": ".gemini", - "env": [ - "GEMINI_CONFIG_DIR" - ] - }, - "localConfigDir": ".gemini", - "configFormat": "settings-json", - "artifactLayout": { - "global": [ - { - "kind": "commands", - "destSubpath": "commands/gsd", - "prefix": "gsd-", - "nesting": "flat", - "recursive": false, - "converter": null - } - ], - "local": [ - { - "kind": "commands", - "destSubpath": "commands/gsd", - "prefix": "gsd-", - "nesting": "flat", - "recursive": false, - "converter": null - } - ] - }, - "commandStyle": "slash-hyphen", - "hooksSurface": "settings-json", - "hookEvents": "gemini", - "sandboxTier": "none", - "supportTier": 2, - "installSurface": "settings-json", - "writesSharedSettings": true, - "permissionWriter": null, - "extendedHookEvents": [ - "BeforeAgent", - "AfterAgent", - "BeforeModel" - ], - "hostIntegration": { - "embeddingMode": "declarative", - "commandSurface": "slash-toml", - "dispatch": { - "namedDispatch": true, - "nested": false, - "maxDepth": 1, - "background": "undocumented", - "subagentToolkit": "undocumented", - "backgroundDispatch": false - }, - "modelMode": "passive", - "hookBus": "host", - "stateIO": "filesystem", - "transport": "mcp", - "runtime": "node" - } - } - }, "graphify": { "id": "graphify", "role": "feature", @@ -4004,81 +3929,6 @@ const runtimes = { } } }, - "gemini": { - "id": "gemini", - "role": "runtime", - "version": "1.7.0-rc.2", - "title": "Gemini CLI", - "description": "Google Gemini CLI — commands-only artifact layout (TOML); Gemini hook event dialect; settings-json hook surface; tier-2 support.", - "tier": "core", - "requires": [], - "engines": { - "gsd": ">=1.6.0" - }, - "runtime": { - "configHome": { - "kind": "dot-home", - "name": ".gemini", - "env": [ - "GEMINI_CONFIG_DIR" - ] - }, - "localConfigDir": ".gemini", - "configFormat": "settings-json", - "artifactLayout": { - "global": [ - { - "kind": "commands", - "destSubpath": "commands/gsd", - "prefix": "gsd-", - "nesting": "flat", - "recursive": false, - "converter": null - } - ], - "local": [ - { - "kind": "commands", - "destSubpath": "commands/gsd", - "prefix": "gsd-", - "nesting": "flat", - "recursive": false, - "converter": null - } - ] - }, - "commandStyle": "slash-hyphen", - "hooksSurface": "settings-json", - "hookEvents": "gemini", - "sandboxTier": "none", - "supportTier": 2, - "installSurface": "settings-json", - "writesSharedSettings": true, - "permissionWriter": null, - "extendedHookEvents": [ - "BeforeAgent", - "AfterAgent", - "BeforeModel" - ], - "hostIntegration": { - "embeddingMode": "declarative", - "commandSurface": "slash-toml", - "dispatch": { - "namedDispatch": true, - "nested": false, - "maxDepth": 1, - "background": "undocumented", - "subagentToolkit": "undocumented", - "backgroundDispatch": false - }, - "modelMode": "passive", - "hookBus": "host", - "stateIO": "filesystem", - "transport": "mcp", - "runtime": "node" - } - } - }, "hermes": { "id": "hermes", "role": "runtime", @@ -4807,7 +4657,6 @@ const _requiresGraph = { "drift": [], "external-job": [], "gap-analysis": [], - "gemini": [], "graphify": [], "hermes": [], "intel": [], diff --git a/gsd-core/bin/shared/model-catalog.json b/gsd-core/bin/shared/model-catalog.json index 6f2a28011..7a811f507 100644 --- a/gsd-core/bin/shared/model-catalog.json +++ b/gsd-core/bin/shared/model-catalog.json @@ -17,11 +17,6 @@ "sonnet": { "model": "gpt-5.4", "reasoning_effort": "medium" }, "haiku": { "model": "gpt-5.4-mini", "reasoning_effort": "medium" } }, - "gemini": { - "opus": { "model": "gemini-3.1-pro-preview" }, - "sonnet": { "model": "gemini-3-flash" }, - "haiku": { "model": "gemini-2.5-flash-lite" } - }, "qwen": { "opus": { "model": "qwen3-max-2026-01-23" }, "sonnet": { "model": "qwen3-coder-plus" }, diff --git a/gsd-core/bin/shared/runtime-aliases.manifest.json b/gsd-core/bin/shared/runtime-aliases.manifest.json index e7264048d..d5140e63b 100644 --- a/gsd-core/bin/shared/runtime-aliases.manifest.json +++ b/gsd-core/bin/shared/runtime-aliases.manifest.json @@ -13,11 +13,6 @@ "kilo", "kilo-cli" ], - "gemini": [ - "gemini", - "gemini-cli", - "gemini-code" - ], "codex": [ "codex", "codex-app", diff --git a/gsd-core/workflows/update.md b/gsd-core/workflows/update.md index 9c88fa10d..d3ac790fa 100644 --- a/gsd-core/workflows/update.md +++ b/gsd-core/workflows/update.md @@ -13,7 +13,7 @@ Detect the installed GSD version, scope, runtime, and config dir. First, derive `PREFERRED_CONFIG_DIR` and `PREFERRED_RUNTIME` from the invoking prompt's `execution_context` path — this is the one input only the workflow knows: - If the path contains `/gsd-core/workflows/update.md`, strip that suffix and store the remainder as `PREFERRED_CONFIG_DIR`. -- Infer `PREFERRED_RUNTIME` from the path: `/.codex/` -> `codex`; `/.gemini/antigravity-ide/`, `/.gemini/antigravity-cli/`, `/.gemini/antigravity/`, `/.agents/` or `/.agent/` -> `antigravity` (`.agents` is the canonical local Antigravity install dir (#791); `.agent` is the legacy form (#503); see bin/install.js `getDirName('antigravity')`); `/.gemini/` -> `gemini`; `/.config/kilo/` or `/.kilo/` -> `kilo`; `/.config/opencode/` or `/.opencode/` -> `opencode`; otherwise `claude`. +- Infer `PREFERRED_RUNTIME` from the path: `/.codex/` -> `codex`; `/.gemini/antigravity-ide/`, `/.gemini/antigravity-cli/`, `/.gemini/antigravity/`, `/.agents/` or `/.agent/` -> `antigravity` (`.agents` is the canonical local Antigravity install dir (#791); `.agent` is the legacy form (#503); see bin/install.js `getDirName('antigravity')`); `/.config/kilo/` or `/.kilo/` -> `kilo`; `/.config/opencode/` or `/.opencode/` -> `opencode`; otherwise `claude`. Then resolve the install context via the deterministic projection (#498). **Do NOT re-derive scope, runtime, or version by hand** — `update-context` owns that cascade in tested code (`gsd-core/bin/lib/update-context.cjs`), the same way `check-latest-version` owns the package name (#2992): @@ -64,7 +64,7 @@ echo "$GSD_DIR" Parse output: - Line 1 = installed version (`0.0.0` means unknown version) - Line 2 = install scope (`LOCAL`, `GLOBAL`, or `UNKNOWN`) -- Line 3 = target runtime (`claude`, `opencode`, `gemini`, `kilo`, `codex`, `antigravity`) +- Line 3 = target runtime (`claude`, `opencode`, `kilo`, `codex`, `antigravity`) - Line 4 = resolved GSD config dir (e.g. `/Users/me/.claude`, `/Users/me/.gemini`); empty if scope is `UNKNOWN`. Capture this as `GSD_DIR` and pass it to subsequent steps so they don't re-derive the runtime path. - If scope is `UNKNOWN`, proceed to install using the `--claude --global` fallback. @@ -392,9 +392,6 @@ fi if [ -n "$CLAUDE_CONFIG_DIR" ]; then CACHE_DIRS+=( "$(expand_home "$CLAUDE_CONFIG_DIR")" ) fi -if [ -n "$GEMINI_CONFIG_DIR" ]; then - CACHE_DIRS+=( "$(expand_home "$GEMINI_CONFIG_DIR")" ) -fi if [ -n "$KILO_CONFIG_DIR" ]; then CACHE_DIRS+=( "$(expand_home "$KILO_CONFIG_DIR")" ) elif [ -n "$KILO_CONFIG" ]; then @@ -443,7 +440,7 @@ for dir in "${CACHE_DIRS[@]}"; do fi done -for dir in .claude .config/opencode .opencode .gemini/antigravity-ide .gemini/antigravity-cli .gemini/antigravity .agents .agent .gemini .config/kilo .kilo .codex .cursor .codeium/windsurf .augment .trae .qwen .hermes .codebuddy .cline; do +for dir in .claude .config/opencode .opencode .gemini/antigravity-ide .gemini/antigravity-cli .gemini/antigravity .agents .agent .config/kilo .kilo .codex .cursor .codeium/windsurf .augment .trae .qwen .hermes .codebuddy .cline; do rm -f "./$dir/cache/gsd-update-check"*.json rm -f "$HOME/$dir/cache/gsd-update-check"*.json done diff --git a/package.json b/package.json index 0878f0b2e..928dd9b33 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "agents", ".claude-plugin", ".opencode", - "gemini-extension.json", "GEMINI.md", "hooks", "scripts" @@ -30,8 +29,6 @@ "meta-prompting", "context-engineering", "spec-driven-development", - "gemini", - "gemini-cli", "codex", "codex-cli" ], diff --git a/scripts/sync-manifest-versions.cjs b/scripts/sync-manifest-versions.cjs index a4fba6df3..dcb37d6a4 100644 --- a/scripts/sync-manifest-versions.cjs +++ b/scripts/sync-manifest-versions.cjs @@ -24,15 +24,16 @@ const ROOT = path.resolve(__dirname, '..'); // Single source of truth: runtime-integration manifests whose `version` field // MUST track package.json. Each entry names the manifest path plus the dotted // path to the version field inside that JSON document. Top-level `version` is -// the default (plugin.json, gemini-extension.json); the Claude plugin +// the default (plugin.json); the Claude plugin // marketplace manifest carries its canonical version at plugins[0].version // (the schema-canonical location runtimes read — issue #1855). // // Add a new manifest here so `npm version` keeps it in sync — the regression // guard test (issue 844) fails if you forget. +// #1928: gemini-extension.json was removed with the gemini runtime (Google +// sunset Gemini CLI 2026-06-18); it is no longer a registered manifest. const VERSIONED_MANIFESTS = [ { path: '.claude-plugin/plugin.json', versionKey: 'version' }, - { path: 'gemini-extension.json', versionKey: 'version' }, { path: '.claude-plugin/marketplace.json', versionKey: 'plugins.0.version' }, { path: 'vscode/package.json', versionKey: 'version' }, ]; diff --git a/src/profile-output.cts b/src/profile-output.cts index f0520d577..daa2d4634 100644 --- a/src/profile-output.cts +++ b/src/profile-output.cts @@ -1132,7 +1132,7 @@ function cmdGenerateClaudeMd(cwd: string, options: CmdGenerateClaudeMdOptions, r // project-instruction-file`). Previously this was a codex-only override // (#3163) that left AGENTS-native runtimes (opencode/kilo/kimi) emitting // CLAUDE.md; copilot now resolves to .github/copilot-instructions.md, and - // antigravity/gemini to GEMINI.md. GSD_RUNTIME env var takes precedence + // antigravity to GEMINI.md. GSD_RUNTIME env var takes precedence // over config.runtime, mirroring detectRuntime(). // // Non-claude runtimes always win over a stale `claude_md_path` (the #3163 diff --git a/src/runtime-hooks-surface.cts b/src/runtime-hooks-surface.cts index b98a5eacf..cb0151da0 100644 --- a/src/runtime-hooks-surface.cts +++ b/src/runtime-hooks-surface.cts @@ -1127,7 +1127,7 @@ function writeCopilotHookConfig(targetDir: string): string { // // MUTATES `settings` by reference — registers all GSD-managed hook entries // into settings.hooks.* for runtimes that use a settings.json hook surface -// (Claude Code, Gemini, Antigravity, Qwen Code, and others). +// (Claude Code, Antigravity, Qwen Code, and others). // Skipped entirely for runtimes whose hooksSurface descriptor field is 'none' // (opencode and kilo, which have their own hook surface). // @@ -1138,7 +1138,7 @@ function writeCopilotHookConfig(targetDir: string): string { // // @param settings - The settings object already read from disk. Mutated in place. // @param opts - Closure values the block read from install()'s scope. -// runtime - runtime ID string (e.g. 'claude', 'gemini', 'qwen') +// runtime - runtime ID string (e.g. 'claude', 'antigravity', 'qwen') // hooksSurface - descriptor hooksSurface field ('settings-json'|'none'|…); if !== 'none', hooks are written // isGlobal - true for global installs // targetDir - absolute path to the runtime config dir @@ -1290,7 +1290,7 @@ function applySettingsJsonHooks(settings: any, opts: ApplySettingsJsonHooksOpts) // Configure PreToolUse hook for prompt injection detection // ADR-857 phase 5f-2: drive dialect from opts.hookEvents (registry descriptor). // hookEvents='gemini' → BeforeTool; all others → PreToolUse. - // Equivalence: hookEvents='gemini' iff runtime∈{gemini,antigravity} (same as old check). + // Equivalence: hookEvents='gemini' iff runtime===antigravity (same as old check). const preToolEvent = hookEvents === 'gemini' ? 'BeforeTool' : 'PreToolUse'; if (!settings.hooks[preToolEvent]) { settings.hooks[preToolEvent] = []; @@ -1583,9 +1583,14 @@ function applySettingsJsonHooks(settings: any, opts: ApplySettingsJsonHooksOpts) } // ── end SubagentStop / Stop / PreCompact events ──────────────────────────── - // ── Gemini-only extended hook events (#776) ─────────────────────────────── - // Gemini CLI exposes several hook events beyond BeforeTool/AfterTool that - // gsd previously did not register. Three high-value events are added here: + // ── Extended hook events (#776; Gemini runtime removed #1928) ────────────── + // The Gemini-3-backend dialect exposes several hook events beyond + // BeforeTool/AfterTool. These were added for the now-removed Gemini CLI + // runtime (#776). No currently supported runtime declares them — + // Antigravity's descriptor carries `extendedHookEvents: []` — so this loop + // is an inert, descriptor-driven seam: it no-ops for every present runtime + // and re-activates automatically if a future runtime declares any of them. + // Three high-value events would be wired here: // // BeforeAgent — fires after user submits a prompt, before the agent // plans. Wire gsd-context-monitor for context headroom @@ -1606,16 +1611,16 @@ function applySettingsJsonHooks(settings: any, opts: ApplySettingsJsonHooksOpts) // gsd hook use case at this time; deferred to a follow-on issue. // // Guard is now descriptor-driven: only events present in extendedEvents are wired. - for (const geminiEvent of ['BeforeAgent', 'AfterAgent', 'BeforeModel']) { - if (!extendedEvents.includes(geminiEvent)) continue; - if (!Array.isArray(settings.hooks[geminiEvent])) { - settings.hooks[geminiEvent] = []; + for (const extendedEvent of ['BeforeAgent', 'AfterAgent', 'BeforeModel']) { + if (!extendedEvents.includes(extendedEvent)) continue; + if (!Array.isArray(settings.hooks[extendedEvent])) { + settings.hooks[extendedEvent] = []; } - const alreadyHasContextMonitor = settings.hooks[geminiEvent].some((entry: HookGroup) => + const alreadyHasContextMonitor = settings.hooks[extendedEvent].some((entry: HookGroup) => entry.hooks && entry.hooks.some((h: HookEntry) => referencesHook(h as Record, 'gsd-context-monitor')) ); if (!alreadyHasContextMonitor && fs.existsSync(contextMonitorFile) && contextMonitorCommand) { - settings.hooks[geminiEvent].push({ + settings.hooks[extendedEvent].push({ hooks: [ { type: 'command', @@ -1624,12 +1629,12 @@ function applySettingsJsonHooks(settings: any, opts: ApplySettingsJsonHooksOpts) } ] }); - console.log(` ${green}✓${reset} Configured ${geminiEvent} context monitor hook (Gemini)`); + console.log(` ${green}✓${reset} Configured ${extendedEvent} context monitor hook`); } else if (!alreadyHasContextMonitor && !fs.existsSync(contextMonitorFile)) { - console.warn(` ${yellow}⚠${reset} Skipped ${geminiEvent} hook — gsd-context-monitor.js not found at target`); + console.warn(` ${yellow}⚠${reset} Skipped ${extendedEvent} hook — gsd-context-monitor.js not found at target`); } } - // ── end Gemini-only extended hook events ────────────────────────────────── + // ── end Antigravity-only extended hook events ────────────────────────────── // ── FileChanged hook: hot-reload gsd config on .planning/config.json edits ─ // Claude Code fires FileChanged when a watched file changes on disk. Wire diff --git a/src/runtime-name-policy.cts b/src/runtime-name-policy.cts index 6df48adba..a1680fb46 100644 --- a/src/runtime-name-policy.cts +++ b/src/runtime-name-policy.cts @@ -18,7 +18,6 @@ const FALLBACK_ALIASES: Readonly> = { claude: ['claude', 'claude-code', 'claude-cli'], opencode: ['opencode', 'open-code', 'opencode-cli'], kilo: ['kilo', 'kilo-cli'], - gemini: ['gemini', 'gemini-cli', 'gemini-code'], codex: ['codex', 'codex-app', 'codex-cli', 'codex_desktop', 'codex-desktop'], copilot: ['copilot', 'copilot-cli', 'github-copilot'], antigravity: ['antigravity', 'antigravity-cli', 'antigravity-agent'], @@ -102,7 +101,7 @@ export function resolveRuntimeNameFromCandidates(...candidates: unknown[]): stri * claude → .claude/CLAUDE.md * codex, opencode, kilo, kimi → AGENTS.md * copilot → .github/copilot-instructions.md - * antigravity, gemini → GEMINI.md + * antigravity → GEMINI.md * unknown / future runtimes → AGENTS.md (safe cross-agent default) * * Source-of-truth references for each runtime's read path: @@ -114,7 +113,9 @@ export function resolveRuntimeNameFromCandidates(...candidates: unknown[]): stri * 'copilot-instructions' writes the same `.github/copilot-instructions.md`.) * - codex/opencode/kilo/kimi: AGENTS.md is the documented cross-agent * instruction file (agentsmd/agents.md convention). - * - antigravity/gemini: GEMINI.md is Gemini CLI's contextFileName. + * - antigravity: GEMINI.md is Antigravity CLI's contextFileName (the Gemini + * CLI runtime that historically shared this file was removed — #1928; + * Google sunset Gemini CLI 2026-06-18 and Antigravity CLI is its successor). * * Aliases are normalized via `canonicalizeRuntimeName` first, so inputs like * `codex-cli` resolve to `codex` → `AGENTS.md`. Replaces the prior codex-only @@ -125,7 +126,7 @@ export function getProjectInstructionFile(runtime: unknown): string { const canonical = canonicalizeRuntimeName(runtime); if (canonical === 'claude') return '.claude/CLAUDE.md'; if (canonical === 'copilot') return '.github/copilot-instructions.md'; - if (canonical === 'antigravity' || canonical === 'gemini') return 'GEMINI.md'; + if (canonical === 'antigravity') return 'GEMINI.md'; // codex, opencode, kilo, kimi, AND unknown/future runtimes all default to // root AGENTS.md (the safe cross-agent instruction file). return 'AGENTS.md'; @@ -167,7 +168,7 @@ export function getDirName(runtime: string): string { * * Voice: these are the SHORT UI labels, intentionally distinct from the * descriptor `title` (the long product name — e.g. "OpenAI Codex CLI", - * "GitHub Copilot", "Gemini CLI") which serves documentation/registry display, + * "GitHub Copilot") which serves documentation/registry display, * not the install console. A future slice may relocate this to a * `runtime.label` descriptor field; until then this table is the source. * @@ -182,7 +183,6 @@ export function getDirName(runtime: string): string { const RUNTIME_LABELS: Readonly> = { claude: 'Claude Code', opencode: 'OpenCode', - gemini: 'Gemini', kilo: 'Kilo', codex: 'Codex', copilot: 'Copilot', @@ -219,7 +219,7 @@ export function getRuntimeLabel(runtime: string): string { * bin/install.js (ADR-1239 Phase B / #1679, AC2 slice 2) — the add-a-host tax: * a new runtime meant remembering to add a branch here. Values are preserved * BYTE-FOR-BYTE from the prior chain; golden install parity asserts generated - * hook output is unchanged across all 16 runtimes. + * hook output is unchanged across all 15 runtimes. * * Two runtimes are intentionally absent (handled by the caller, NOT this table): * - `claude` → the default; falls through to `DEFAULT_FRAGMENT`. @@ -232,7 +232,6 @@ const DEFAULT_CONFIG_HOME_FRAGMENT = "'.claude'"; const GLOBAL_CONFIG_HOME_FRAGMENTS: Readonly> = { copilot: "'.copilot'", opencode: "'.config', 'opencode'", - gemini: "'.gemini'", kilo: "'.config', 'kilo'", codex: "'.codex'", cursor: "'.cursor'", @@ -266,7 +265,7 @@ export function getGlobalConfigHomeFragment(runtime: string): string { * removes). */ const RUNTIME_FLAG_IDS = Object.freeze([ - 'opencode', 'kilo', 'gemini', 'codex', 'copilot', 'antigravity', 'cursor', + 'opencode', 'kilo', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline', 'kimi', ] as const); @@ -294,7 +293,6 @@ export function runtimeFlags(runtime: string): Readonly> */ const DEFAULT_NEW_PROJECT_COMMAND = '/gsd-new-project'; const RUNTIME_NEW_PROJECT_COMMANDS: Readonly> = { - gemini: '/gsd:new-project', codex: '$gsd-new-project', cursor: 'gsd-new-project (mention the skill name)', kimi: '/skill:gsd-new-project', diff --git a/src/shell-command-projection.cts b/src/shell-command-projection.cts index 761bd6167..97aaab59f 100644 --- a/src/shell-command-projection.cts +++ b/src/shell-command-projection.cts @@ -24,16 +24,20 @@ import childProcess from 'node:child_process'; * runtime/shell combination. * * Current evidence-backed policy: - * - Gemini on Windows requires `& ` for quoted node/bash runners. - * - Claude Code on Windows does NOT: its hook commands execute under bash/Git - * Bash and `& ` breaks there (#3413). + * - Claude Code on Windows does NOT need it: its hook commands execute under + * bash/Git Bash and `& ` breaks there (#3413). + * - #1928: Gemini CLI — the ONLY runtime with a verified need for the `& ` + * prefix on Windows — was removed (Google sunset it 2026-06-18). No currently + * supported runtime has a verified need, so this seam is now inert. It is + * retained (not deleted) so a future runtime with a verified need is a + * one-line re-enable, per the conservative policy below. Note: Antigravity — + * the Gemini-backend successor — never matched the old `runtime === 'gemini'` + * check, so its behavior (no prefix) is unchanged. * * Keep the policy conservative until another runtime has a verified need. */ -export function hookCommandNeedsPowerShellCallOperator(opts: { platform?: string; runtime?: string } = {}): boolean { - const platform = opts.platform || process.platform; - const runtime = opts.runtime || 'generic'; - return platform === 'win32' && runtime === 'gemini'; +export function hookCommandNeedsPowerShellCallOperator(_opts: { platform?: string; runtime?: string } = {}): boolean { + return false; } /** @@ -96,7 +100,7 @@ export function formatManagedHookScriptToken(scriptPath: string, opts: { platfor export function projectLocalHookPrefix({ runtime = 'claude', dirName }: { runtime?: string; dirName?: string | null }): string | undefined | null { if (!dirName) return dirName; - return (runtime === 'gemini' || runtime === 'antigravity') + return (runtime === 'antigravity') ? dirName : `"$CLAUDE_PROJECT_DIR"/${dirName}`; } diff --git a/src/update-context.cts b/src/update-context.cts index 9492f22bb..29712d85f 100644 --- a/src/update-context.cts +++ b/src/update-context.cts @@ -28,7 +28,6 @@ export const RUNTIME_DIRS: RuntimeDirEntry[] = [ ['antigravity', '.agent'], // local Antigravity install dir legacy (#503; backward-compat with pre-#791 installs) ['windsurf', '.windsurf'], // local Windsurf workflow dir canonical (#1615; bin/install.js getDirName('windsurf')) ['windsurf', '.devin'], // local Devin Desktop install dir legacy (#1085; backward-compat) - ['gemini', '.gemini'], ['kilo', '.config/kilo'], ['kilo', '.kilo'], ['codex', '.codex'], @@ -86,7 +85,6 @@ export function inferPreferredRuntime({ fs, env, preferredConfigDir }: InferPref } if (env['CODEX_HOME']) return 'codex'; if (env['ANTIGRAVITY_CONFIG_DIR']) return 'antigravity'; - if (env['GEMINI_CONFIG_DIR']) return 'gemini'; if (env['KILO_CONFIG_DIR'] || env['KILO_CONFIG']) return 'kilo'; if (env['OPENCODE_CONFIG_DIR'] || env['OPENCODE_CONFIG']) return 'opencode'; if (env['CLAUDE_CONFIG_DIR']) return 'claude'; @@ -104,7 +102,6 @@ export function envRuntimeDirs({ env, home }: EnvRuntimeDirsOpts): RuntimeDirEnt const ex = (v: string | undefined) => expandHome(v, home); if (env['CLAUDE_CONFIG_DIR']) out.push(['claude', ex(env['CLAUDE_CONFIG_DIR'])]); if (env['ANTIGRAVITY_CONFIG_DIR']) out.push(['antigravity', ex(env['ANTIGRAVITY_CONFIG_DIR'])]); - if (env['GEMINI_CONFIG_DIR']) out.push(['gemini', ex(env['GEMINI_CONFIG_DIR'])]); if (env['KILO_CONFIG_DIR']) out.push(['kilo', ex(env['KILO_CONFIG_DIR'])]); else if (env['KILO_CONFIG']) out.push(['kilo', path.dirname(ex(env['KILO_CONFIG']))]); else if (env['XDG_CONFIG_HOME']) out.push(['kilo', path.join(ex(env['XDG_CONFIG_HOME']), 'kilo')]); diff --git a/tests/adapter-declarative-equivalence.test.cjs b/tests/adapter-declarative-equivalence.test.cjs index b7adbb580..4ce47ea07 100644 --- a/tests/adapter-declarative-equivalence.test.cjs +++ b/tests/adapter-declarative-equivalence.test.cjs @@ -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'`); diff --git a/tests/adapter-imperative.test.cjs b/tests/adapter-imperative.test.cjs index 48cc450b3..7ddf30138 100644 --- a/tests/adapter-imperative.test.cjs +++ b/tests/adapter-imperative.test.cjs @@ -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'`); diff --git a/tests/capability-registry.test.cjs b/tests/capability-registry.test.cjs index cf9c3aa45..082a78751 100644 --- a/tests/capability-registry.test.cjs +++ b/tests/capability-registry.test.cjs @@ -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); }); }); diff --git a/tests/codex-config.test.cjs b/tests/codex-config.test.cjs index 686f1db1e..b8f3f9c58 100644 --- a/tests/codex-config.test.cjs +++ b/tests/codex-config.test.cjs @@ -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 ──────────────────────────────────────────────── diff --git a/tests/copilot-install.test.cjs b/tests/copilot-install.test.cjs index 40945f10d..1c833298c 100644 --- a/tests/copilot-install.test.cjs +++ b/tests/copilot-install.test.cjs @@ -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'"); }); diff --git a/tests/declarative-reference-antigravity.test.cjs b/tests/declarative-reference-antigravity.test.cjs index e7e7ee706..5d5615afd 100644 --- a/tests/declarative-reference-antigravity.test.cjs +++ b/tests/declarative-reference-antigravity.test.cjs @@ -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'); }); }); }); diff --git a/tests/fixtures/golden-install-parity/antigravity.json b/tests/fixtures/golden-install-parity/antigravity.json index 0f2ab6d27..8ad9da951 100644 --- a/tests/fixtures/golden-install-parity/antigravity.json +++ b/tests/fixtures/golden-install-parity/antigravity.json @@ -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", diff --git a/tests/fixtures/golden-install-parity/augment.json b/tests/fixtures/golden-install-parity/augment.json index 7b852a4e9..0ecfccd2f 100644 --- a/tests/fixtures/golden-install-parity/augment.json +++ b/tests/fixtures/golden-install-parity/augment.json @@ -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", diff --git a/tests/fixtures/golden-install-parity/claude.json b/tests/fixtures/golden-install-parity/claude.json index 395db4e22..f173ebd0d 100644 --- a/tests/fixtures/golden-install-parity/claude.json +++ b/tests/fixtures/golden-install-parity/claude.json @@ -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", diff --git a/tests/fixtures/golden-install-parity/cline.json b/tests/fixtures/golden-install-parity/cline.json index 573d58e74..67dc509ab 100644 --- a/tests/fixtures/golden-install-parity/cline.json +++ b/tests/fixtures/golden-install-parity/cline.json @@ -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", diff --git a/tests/fixtures/golden-install-parity/codebuddy.json b/tests/fixtures/golden-install-parity/codebuddy.json index a45e7ca20..99b10d015 100644 --- a/tests/fixtures/golden-install-parity/codebuddy.json +++ b/tests/fixtures/golden-install-parity/codebuddy.json @@ -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", diff --git a/tests/fixtures/golden-install-parity/codex.json b/tests/fixtures/golden-install-parity/codex.json index e65a3a8e6..ea74f7ab3 100644 --- a/tests/fixtures/golden-install-parity/codex.json +++ b/tests/fixtures/golden-install-parity/codex.json @@ -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", diff --git a/tests/fixtures/golden-install-parity/copilot.json b/tests/fixtures/golden-install-parity/copilot.json index c94681814..929527ce6 100644 --- a/tests/fixtures/golden-install-parity/copilot.json +++ b/tests/fixtures/golden-install-parity/copilot.json @@ -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", diff --git a/tests/fixtures/golden-install-parity/cursor.json b/tests/fixtures/golden-install-parity/cursor.json index cfe00e676..e887e96a3 100644 --- a/tests/fixtures/golden-install-parity/cursor.json +++ b/tests/fixtures/golden-install-parity/cursor.json @@ -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", diff --git a/tests/fixtures/golden-install-parity/gemini.json b/tests/fixtures/golden-install-parity/gemini.json deleted file mode 100644 index 110449421..000000000 --- a/tests/fixtures/golden-install-parity/gemini.json +++ /dev/null @@ -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" -} diff --git a/tests/fixtures/golden-install-parity/hermes.json b/tests/fixtures/golden-install-parity/hermes.json index c8aaf63b5..e01911fb4 100644 --- a/tests/fixtures/golden-install-parity/hermes.json +++ b/tests/fixtures/golden-install-parity/hermes.json @@ -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", diff --git a/tests/fixtures/golden-install-parity/kilo.json b/tests/fixtures/golden-install-parity/kilo.json index ce2fd9e2c..bb833dd29 100644 --- a/tests/fixtures/golden-install-parity/kilo.json +++ b/tests/fixtures/golden-install-parity/kilo.json @@ -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", diff --git a/tests/fixtures/golden-install-parity/kimi.json b/tests/fixtures/golden-install-parity/kimi.json index 886f96bcc..9ec4454ab 100644 --- a/tests/fixtures/golden-install-parity/kimi.json +++ b/tests/fixtures/golden-install-parity/kimi.json @@ -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", diff --git a/tests/fixtures/golden-install-parity/opencode.json b/tests/fixtures/golden-install-parity/opencode.json index 84f5c594c..0c28a1ef0 100644 --- a/tests/fixtures/golden-install-parity/opencode.json +++ b/tests/fixtures/golden-install-parity/opencode.json @@ -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", diff --git a/tests/fixtures/golden-install-parity/qwen.json b/tests/fixtures/golden-install-parity/qwen.json index d4c10878c..6a70ce791 100644 --- a/tests/fixtures/golden-install-parity/qwen.json +++ b/tests/fixtures/golden-install-parity/qwen.json @@ -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", diff --git a/tests/fixtures/golden-install-parity/trae.json b/tests/fixtures/golden-install-parity/trae.json index 5d62f5250..89594dce5 100644 --- a/tests/fixtures/golden-install-parity/trae.json +++ b/tests/fixtures/golden-install-parity/trae.json @@ -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", diff --git a/tests/fixtures/golden-install-parity/windsurf.json b/tests/fixtures/golden-install-parity/windsurf.json index 98c6c1a6e..1e40b993c 100644 --- a/tests/fixtures/golden-install-parity/windsurf.json +++ b/tests/fixtures/golden-install-parity/windsurf.json @@ -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", diff --git a/tests/gemini-namespacing.test.cjs b/tests/gemini-namespacing.test.cjs deleted file mode 100644 index a4685d0f8..000000000 --- a/tests/gemini-namespacing.test.cjs +++ /dev/null @@ -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 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 tiny /gsd-help now.'; - const result = convertClaudeToGeminiMarkdown(input, { isCommand: false }); - assert.doesNotMatch(result, /|<\/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, /(? { + 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)'); + }); +}); diff --git a/tests/getdirname-registry-derivation.test.cjs b/tests/getdirname-registry-derivation.test.cjs index 99bd36875..78a9cf5f0 100644 --- a/tests/getdirname-registry-derivation.test.cjs +++ b/tests/getdirname-registry-derivation.test.cjs @@ -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( diff --git a/tests/global-config-home-fragment.test.cjs b/tests/global-config-home-fragment.test.cjs index 19ee42c95..e6eee1ae0 100644 --- a/tests/global-config-home-fragment.test.cjs +++ b/tests/global-config-home-fragment.test.cjs @@ -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( diff --git a/tests/helpers/install-shared.cjs b/tests/helpers/install-shared.cjs index 3ede59d96..12282f471 100644 --- a/tests/helpers/install-shared.cjs +++ b/tests/helpers/install-shared.cjs @@ -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: '.', diff --git a/tests/host-integration-descriptors.test.cjs b/tests/host-integration-descriptors.test.cjs index 146c5c1c4..1da2cea5f 100644 --- a/tests/host-integration-descriptors.test.cjs +++ b/tests/host-integration-descriptors.test.cjs @@ -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]; diff --git a/tests/host-integration-validator-parity.test.cjs b/tests/host-integration-validator-parity.test.cjs index eab47c458..e23ce44d4 100644 --- a/tests/host-integration-validator-parity.test.cjs +++ b/tests/host-integration-validator-parity.test.cjs @@ -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: { diff --git a/tests/install-minimal-hooks.test.cjs b/tests/install-minimal-hooks.test.cjs index eb54239ca..a5be6f0c3 100644 --- a/tests/install-minimal-hooks.test.cjs +++ b/tests/install-minimal-hooks.test.cjs @@ -1251,7 +1251,7 @@ describe('enh-1076 phase 5f-3: qwen extendedHookEvents → SubagentStop/Stop/Pre // ─── Suite 3: gemini shape (BeforeAgent+AfterAgent+BeforeModel) ─────────────── -describe('enh-1076 phase 5f-3: gemini extendedHookEvents → BeforeAgent/AfterAgent/BeforeModel', () => { +describe('enh-1076 phase 5f-3: extendedHookEvents → BeforeAgent/AfterAgent/BeforeModel (Gemini-3 backend dialect)', () => { let targetDir; let settings; @@ -1259,7 +1259,7 @@ describe('enh-1076 phase 5f-3: gemini extendedHookEvents → BeforeAgent/AfterAg targetDir = createStubTargetDir(); settings = { hooks: {} }; const opts = buildOpts(targetDir, { - runtime: 'gemini', + runtime: 'antigravity', extendedHookEvents: ['BeforeAgent', 'AfterAgent', 'BeforeModel'], }); applySettingsJsonHooks(settings, opts); @@ -1559,71 +1559,9 @@ function hasHooksFor(settings, eventName) { // Registry runtimes with hookEvents='gemini': gemini, antigravity describe('enh-1077 phase 5f-2: gemini hookEvents dialect → AfterTool/BeforeTool', () => { - // ── gemini ── - - describe('gemini install uses AfterTool for post-tool hooks', () => { - let tmpDir; - let previousCwd; - let settings; - - beforeEach(() => { - tmpDir = createTempDir('gsd-1077-gemini-'); - previousCwd = process.cwd(); - process.chdir(tmpDir); - - const geminiDir = path.join(tmpDir, '.gemini'); - fs.mkdirSync(geminiDir, { recursive: true }); - const result = install(false, 'gemini'); - settings = result && result.settings; - }); - - afterEach(() => { - process.chdir(previousCwd); - cleanup(tmpDir); - }); - - test('registry confirms gemini hookEvents is "gemini"', () => { - // Parity assertion: if the registry changes, this test fails first. - const he = registryHookEvents('gemini'); - if (he !== undefined) { - assert.strictEqual(he, 'gemini', - 'Registry descriptor for gemini must declare hookEvents="gemini"'); - } - }); - - test('gemini install returns a settings object', () => { - assert.ok(settings !== null && typeof settings === 'object', - 'gemini install must return a non-null settings object'); - }); - - test('gemini install registers at least one hook under AfterTool (post-tool)', () => { - assert.ok(hasHooksFor(settings, 'AfterTool'), - `Expected AfterTool hooks on gemini; got hooks keys: ${JSON.stringify(Object.keys((settings && settings.hooks) || {}))}`); - }); - - test('gemini install does NOT register context-monitor under PostToolUse (wrong dialect)', () => { - const cmds = hooksForEvent(settings, 'PostToolUse'); - const hasMonitor = cmds.some(c => c && c.includes('gsd-context-monitor')); - assert.strictEqual(hasMonitor, false, - `gemini must NOT use PostToolUse for context-monitor; got PostToolUse commands: ${JSON.stringify(cmds)}`); - }); - - test('gemini install registers at least one pre-tool hook (prompt-guard) under BeforeTool', () => { - const cmds = hooksForEvent(settings, 'BeforeTool'); - const hasPromptGuard = cmds.some(c => c && c.includes('gsd-prompt-guard')); - assert.ok(hasPromptGuard, - `Expected prompt-guard hook under BeforeTool on gemini; BeforeTool commands: ${JSON.stringify(cmds)}; hooks keys: ${JSON.stringify(Object.keys((settings && settings.hooks) || {}))}`); - }); - - test('gemini install does NOT register prompt-guard under PreToolUse (wrong pre-tool dialect)', () => { - const cmds = hooksForEvent(settings, 'PreToolUse'); - const hasPromptGuard = cmds.some(c => c && c.includes('gsd-prompt-guard')); - assert.strictEqual(hasPromptGuard, false, - `gemini must NOT use PreToolUse for prompt-guard; got PreToolUse commands: ${JSON.stringify(cmds)}`); - }); - }); - - // ── antigravity ── + // #1928: the gemini runtime was removed (Google sunset Gemini CLI + // 2026-06-18). antigravity — the Gemini-backend successor — is the only + // remaining runtime whose descriptor carries hookEvents='gemini'. describe('antigravity install uses AfterTool/BeforeTool (gemini dialect)', () => { let tmpDir; @@ -1835,7 +1773,7 @@ describe('enh-1077 phase 5f-2: registry-parity — hookEvents descriptor drives } // Runtimes that have settings.json surfaces and a hookEvents descriptor - const SETTINGS_JSON_RUNTIMES = ['claude', 'gemini', 'antigravity', 'augment', 'qwen', 'hermes', 'codebuddy']; + const SETTINGS_JSON_RUNTIMES = ['claude', 'antigravity', 'augment', 'qwen', 'hermes', 'codebuddy']; const failures = []; @@ -1895,422 +1833,6 @@ describe('enh-1077 phase 5f-2: registry-parity — hookEvents descriptor drives } -// ──────────────────────────────────────────────────────────────────────── -// Folded from tests/enh-776-install-gemini-hook-events.test.cjs — consolidation epic #1969 (B1 #1970) -// ──────────────────────────────────────────────────────────────────────── -{ - const { describe: __foldDescribe } = require('node:test'); - __foldDescribe("folded:enh-776-install-gemini-hook-events (consolidation epic #1969 B1 #1970)", () => { -'use strict'; - -process.env.GSD_TEST_MODE = '1'; - -/** - * Enhancement #776: Adopt new Gemini hook events + detect hooksConfig.enabled:false. - * - * Gemini CLI exposes several hook events beyond BeforeTool/AfterTool that gsd - * previously did not register. This suite asserts that a Gemini install - * registers the 3 new high-value events: - * - BeforeAgent — fires before the agent plans (context headroom tracking) - * - AfterAgent — fires after final response generation (context tracking) - * - BeforeModel — fires before each LLM call (per-turn context awareness) - * - * All three are wired to gsd-context-monitor.js — the same hook used for - * AfterTool — so context headroom warnings surface at these lifecycle moments. - * - * Also asserts: - * - Claude Code installs do NOT gain these Gemini-only events (strict scope guard). - * - Reinstalls are idempotent (no hook duplication). - * - Uninstall removes the new event registrations. - * - hooksConfig.enabled:false warning is emitted during a Gemini install. - * - * Source: https://github.com/google-gemini/gemini-cli/blob/main/docs/hooks/reference.md - */ - -const { test, describe, beforeEach, afterEach } = require('node:test'); -const assert = require('node:assert/strict'); -const fs = require('node:fs'); -const path = require('node:path'); - -const { install, uninstall, validateHookFields } = require('../bin/install.js'); -const { createTempDir, cleanup } = require('./helpers.cjs'); - -// ─── Helpers ───────────────────────────────────────────────────────────────── - -/** Extract all hook commands registered under `eventName` from settings. */ -function hooksForEvent(settings, eventName) { - if (!settings || !settings.hooks || !Array.isArray(settings.hooks[eventName])) return []; - return settings.hooks[eventName].flatMap(entry => - (entry && Array.isArray(entry.hooks) ? entry.hooks : []) - .map(h => h && h.command) - .filter(Boolean) - ); -} - -// Stub JS hook files that the installer checks with fs.existsSync() so hook -// registration guards pass even when hooks/dist/ isn't built. -// -// For Gemini, the installer migration baseline includes 'hooks/' in its surface -// list (unlike Qwen which excludes it). Pre-install stubs placed in .gemini/hooks/ -// are classified as 'bundled-gsd-hook' and auto-removed by the migration before -// registration can succeed. The workaround: run a first install (which writes the -// gsd-file-manifest.json), then add the stubs, then run install again. On the -// second install the manifest marks the hook files as managed, so migration keeps -// them and registration guards (fs.existsSync) pass. -const HOOKS_SRC = path.join(__dirname, '..', 'hooks'); -const STUB_HOOKS = [ - 'gsd-context-monitor.js', - 'gsd-prompt-guard.js', - 'gsd-check-update.js', -]; - -function stubHooksIntoTarget(targetDir) { - const hooksDest = path.join(targetDir, 'hooks'); - fs.mkdirSync(hooksDest, { recursive: true }); - for (const hookFile of STUB_HOOKS) { - const src = path.join(HOOKS_SRC, hookFile); - const dest = path.join(hooksDest, hookFile); - if (fs.existsSync(src)) { - fs.copyFileSync(src, dest); - } else { - // Minimal stub so existsSync passes - fs.writeFileSync(dest, '#!/usr/bin/env node\n// stub\n'); - } - try { fs.chmodSync(dest, 0o755); } catch { /* Windows */ } - } -} - -/** - * Two-pass Gemini install. - * - * Pass 1: install() with no hook stubs — writes gsd-file-manifest.json. - * Migration runs on an empty hooks/ so nothing gets auto-removed. - * Pass 2: stub hooks into the target dir (now manifest-tracked on next scan), - * then run install() again. Migration now classifies the hooks as - * managed-unchanged and preserves them; registration guards pass. - * - * Returns the settings from pass 2. - */ -function twoPassGeminiInstall(tmpDir) { - const targetDir = path.join(tmpDir, '.gemini'); - fs.mkdirSync(targetDir, { recursive: true }); - - // Pass 1 — no hook stubs yet; writes the manifest - const result1 = install(false, 'gemini'); - persistSettings(result1.settingsPath, result1.settings); - - // Inject stubs so the registration guards (fs.existsSync) pass on pass 2 - stubHooksIntoTarget(targetDir); - - // Pass 2 — manifest exists, migration keeps stubs, registration succeeds - process.chdir(tmpDir); - const result2 = install(false, 'gemini'); - return result2; -} - -/** - * Persist in-memory settings to disk, simulating what finishInstall() does - * (finishInstall is not exported). Required for tests that call install() - * twice and need the second call to read the first call's hook registrations. - */ -function persistSettings(settingsPath, settings) { - fs.mkdirSync(path.dirname(settingsPath), { recursive: true }); - fs.writeFileSync(settingsPath, JSON.stringify(validateHookFields(settings), null, 2) + '\n', 'utf8'); -} - -// ─── Suite 1: Gemini — new events are registered ───────────────────────────── - -describe('enh-776: Gemini install registers 3 new hook events', () => { - let tmpDir; - let previousCwd; - let settings; - - beforeEach(() => { - tmpDir = createTempDir('gsd-776-gemini-'); - previousCwd = process.cwd(); - process.chdir(tmpDir); - - // Two-pass install: first pass writes manifest, second pass with stubs - // so registration guards (fs.existsSync) pass. - const result = twoPassGeminiInstall(tmpDir); - settings = result.settings; - }); - - afterEach(() => { - process.chdir(previousCwd); - cleanup(tmpDir); - }); - - test('install returns a settings object (not null)', () => { - assert.ok(settings !== null && typeof settings === 'object', - 'Gemini install must return a non-null settings object'); - }); - - test('BeforeAgent event is registered with at least one hook', () => { - const cmds = hooksForEvent(settings, 'BeforeAgent'); - assert.ok(cmds.length > 0, - `Expected BeforeAgent hooks; got hooks: ${JSON.stringify(settings && settings.hooks)}`); - }); - - test('AfterAgent event is registered with at least one hook', () => { - const cmds = hooksForEvent(settings, 'AfterAgent'); - assert.ok(cmds.length > 0, - `Expected AfterAgent hooks; got hooks: ${JSON.stringify(settings && settings.hooks)}`); - }); - - test('BeforeModel event is registered with at least one hook', () => { - const cmds = hooksForEvent(settings, 'BeforeModel'); - assert.ok(cmds.length > 0, - `Expected BeforeModel hooks; got hooks: ${JSON.stringify(settings && settings.hooks)}`); - }); - - test('BeforeAgent / AfterAgent / BeforeModel all use gsd-context-monitor', () => { - for (const event of ['BeforeAgent', 'AfterAgent', 'BeforeModel']) { - const cmds = hooksForEvent(settings, event); - assert.ok( - cmds.some(c => c.includes('gsd-context-monitor')), - `Event ${event} should use gsd-context-monitor; got commands: ${JSON.stringify(cmds)}` - ); - } - }); -}); - -// ─── Suite 2: Non-Gemini installs do NOT get the new events ────────────────── -// -// Two runtimes are particularly important to guard: -// Claude — the canonical non-Gemini runtime -// Antigravity — shares Gemini-style BeforeTool/AfterTool naming and uses -// isGemini-adjacent logic; a future accidental -// `isGemini || isAntigravity` change must be caught here. - -describe('enh-776: Claude install does NOT register Gemini-only hook events', () => { - let tmpDir; - let previousCwd; - let settings; - - beforeEach(() => { - tmpDir = createTempDir('gsd-776-claude-'); - previousCwd = process.cwd(); - process.chdir(tmpDir); - - const result = install(false, 'claude'); - settings = result && result.settings; - }); - - afterEach(() => { - process.chdir(previousCwd); - cleanup(tmpDir); - }); - - test('Claude install does not register BeforeAgent', () => { - const cmds = hooksForEvent(settings, 'BeforeAgent'); - assert.strictEqual(cmds.length, 0, - `Claude should NOT have BeforeAgent; got: ${JSON.stringify(cmds)}`); - }); - - test('Claude install does not register AfterAgent', () => { - const cmds = hooksForEvent(settings, 'AfterAgent'); - assert.strictEqual(cmds.length, 0, - `Claude should NOT have AfterAgent; got: ${JSON.stringify(cmds)}`); - }); - - test('Claude install does not register BeforeModel', () => { - const cmds = hooksForEvent(settings, 'BeforeModel'); - assert.strictEqual(cmds.length, 0, - `Claude should NOT have BeforeModel; got: ${JSON.stringify(cmds)}`); - }); -}); - -describe('enh-776: Antigravity install does NOT register Gemini-only hook events', () => { - let tmpDir; - let previousCwd; - let settings; - - beforeEach(() => { - tmpDir = createTempDir('gsd-776-antigravity-'); - previousCwd = process.cwd(); - process.chdir(tmpDir); - - const result = install(false, 'antigravity'); - settings = result && result.settings; - }); - - afterEach(() => { - process.chdir(previousCwd); - cleanup(tmpDir); - }); - - test('Antigravity install does not register BeforeAgent', () => { - const cmds = hooksForEvent(settings, 'BeforeAgent'); - assert.strictEqual(cmds.length, 0, - `Antigravity should NOT have BeforeAgent; got: ${JSON.stringify(cmds)}`); - }); - - test('Antigravity install does not register AfterAgent', () => { - const cmds = hooksForEvent(settings, 'AfterAgent'); - assert.strictEqual(cmds.length, 0, - `Antigravity should NOT have AfterAgent; got: ${JSON.stringify(cmds)}`); - }); - - test('Antigravity install does not register BeforeModel', () => { - const cmds = hooksForEvent(settings, 'BeforeModel'); - assert.strictEqual(cmds.length, 0, - `Antigravity should NOT have BeforeModel; got: ${JSON.stringify(cmds)}`); - }); -}); - -// ─── Suite 3: Idempotency — persisted reinstall does not duplicate hooks ────── - -describe('enh-776: Gemini install is idempotent across persisted reinstalls', () => { - let tmpDir; - let previousCwd; - - beforeEach(() => { - tmpDir = createTempDir('gsd-776-idem-'); - previousCwd = process.cwd(); - process.chdir(tmpDir); - }); - - afterEach(() => { - process.chdir(previousCwd); - cleanup(tmpDir); - }); - - test('re-running after persisted first install does not duplicate hook entries', () => { - // Two-pass to get hooks installed. - const result2 = twoPassGeminiInstall(tmpDir); - const s2 = result2.settings; - - // Assert hooks ARE registered after pass 2 (guards against false-pass where - // hooks never registered and idempotency passes trivially at count=0). - for (const event of ['BeforeAgent', 'AfterAgent', 'BeforeModel']) { - const cmds = hooksForEvent(s2, event); - assert.strictEqual(cmds.length, 1, - `Event ${event} should have exactly 1 hook after two-pass install; got ${cmds.length}: ${JSON.stringify(cmds)}`); - } - - persistSettings(result2.settingsPath, s2); - - // Third install: reads the persisted settings.json — dedup guards apply - process.chdir(tmpDir); - const result3 = install(false, 'gemini'); - const s3 = result3.settings; - - for (const event of ['BeforeAgent', 'AfterAgent', 'BeforeModel']) { - const cmds = hooksForEvent(s3, event); - assert.strictEqual(cmds.length, 1, - `Event ${event} should have exactly 1 hook command after idempotent reinstall; got ${cmds.length}: ${JSON.stringify(cmds)}`); - } - }); -}); - -// ─── Suite 4: Uninstall removes the new event registrations ────────────────── - -describe('enh-776: Gemini uninstall removes new hook event entries', () => { - let tmpDir; - let previousCwd; - - beforeEach(() => { - tmpDir = createTempDir('gsd-776-uninstall-'); - previousCwd = process.cwd(); - process.chdir(tmpDir); - - // Two-pass install and persist so uninstall has a settings.json to clean - const result = twoPassGeminiInstall(tmpDir); - persistSettings(result.settingsPath, result.settings); - }); - - afterEach(() => { - process.chdir(previousCwd); - cleanup(tmpDir); - }); - - test('settings.json hook entries are removed on uninstall', () => { - uninstall(false, 'gemini'); - const settingsPath = path.join(tmpDir, '.gemini', 'settings.json'); - if (!fs.existsSync(settingsPath)) return; // file removed entirely is fine - const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); - for (const event of ['BeforeAgent', 'AfterAgent', 'BeforeModel']) { - const cmds = hooksForEvent(settings, event); - assert.strictEqual(cmds.length, 0, - `After uninstall, ${event} should have 0 hooks; got: ${JSON.stringify(cmds)}`); - } - }); -}); - -// ─── Suite 5: hooksConfig.enabled:false warning ─────────────────────────────── - -describe('enh-776: hooksConfig.enabled:false warning during Gemini install', () => { - let tmpDir; - let previousCwd; - let stderrLines; - let originalWarn; - - beforeEach(() => { - tmpDir = createTempDir('gsd-776-hookscfg-'); - previousCwd = process.cwd(); - process.chdir(tmpDir); - - const targetDir = path.join(tmpDir, '.gemini'); - fs.mkdirSync(targetDir, { recursive: true }); - - // Capture console.warn output - stderrLines = []; - originalWarn = console.warn; - console.warn = (...args) => { stderrLines.push(args.join(' ')); }; - }); - - afterEach(() => { - console.warn = originalWarn; - process.chdir(previousCwd); - cleanup(tmpDir); - }); - - test('emits a warning when hooksConfig.enabled is false', () => { - // Write a settings.json with hooksConfig.enabled: false BEFORE install - // (the check in install() reads the existing settings.json on disk). - const targetDir = path.join(tmpDir, '.gemini'); - const settingsPath = path.join(targetDir, 'settings.json'); - fs.writeFileSync(settingsPath, JSON.stringify({ hooksConfig: { enabled: false } }, null, 2) + '\n', 'utf8'); - - install(false, 'gemini'); - const warnText = stderrLines.join('\n'); - assert.ok( - warnText.includes('hooksConfig.enabled is false'), - `Expected hooksConfig.enabled warning; got console.warn output:\n${warnText}` - ); - }); - - test('does NOT emit the hooksConfig warning when hooksConfig.enabled is true', () => { - const targetDir = path.join(tmpDir, '.gemini'); - const settingsPath = path.join(targetDir, 'settings.json'); - fs.writeFileSync(settingsPath, JSON.stringify({ hooksConfig: { enabled: true } }, null, 2) + '\n', 'utf8'); - - install(false, 'gemini'); - const warnText = stderrLines.join('\n'); - assert.ok( - !warnText.includes('hooksConfig.enabled is false'), - `Should NOT warn when hooksConfig.enabled is true; got:\n${warnText}` - ); - }); - - test('does NOT emit the hooksConfig warning when hooksConfig is absent', () => { - const targetDir = path.join(tmpDir, '.gemini'); - const settingsPath = path.join(targetDir, 'settings.json'); - fs.writeFileSync(settingsPath, JSON.stringify({}, null, 2) + '\n', 'utf8'); - - install(false, 'gemini'); - const warnText = stderrLines.join('\n'); - assert.ok( - !warnText.includes('hooksConfig.enabled is false'), - `Should NOT warn when hooksConfig is absent; got:\n${warnText}` - ); - }); -}); - }); -} - - // ──────────────────────────────────────────────────────────────────────── // Folded from tests/enh-788-qwen-hook-events.test.cjs — consolidation epic #1969 (B1 #1970) // ──────────────────────────────────────────────────────────────────────── @@ -3479,7 +3001,7 @@ describe('enh-1076 phase 5f-3: qwen extendedHookEvents → SubagentStop/Stop/Pre // ─── Suite 3: gemini shape (BeforeAgent+AfterAgent+BeforeModel) ─────────────── -describe('enh-1076 phase 5f-3: gemini extendedHookEvents → BeforeAgent/AfterAgent/BeforeModel', () => { +describe('enh-1076 phase 5f-3: extendedHookEvents → BeforeAgent/AfterAgent/BeforeModel (Gemini-3 backend dialect)', () => { let targetDir; let settings; @@ -3487,7 +3009,7 @@ describe('enh-1076 phase 5f-3: gemini extendedHookEvents → BeforeAgent/AfterAg targetDir = createStubTargetDir(); settings = { hooks: {} }; const opts = buildOpts(targetDir, { - runtime: 'gemini', + runtime: 'antigravity', extendedHookEvents: ['BeforeAgent', 'AfterAgent', 'BeforeModel'], }); applySettingsJsonHooks(settings, opts); @@ -3787,71 +3309,9 @@ function hasHooksFor(settings, eventName) { // Registry runtimes with hookEvents='gemini': gemini, antigravity describe('enh-1077 phase 5f-2: gemini hookEvents dialect → AfterTool/BeforeTool', () => { - // ── gemini ── - - describe('gemini install uses AfterTool for post-tool hooks', () => { - let tmpDir; - let previousCwd; - let settings; - - beforeEach(() => { - tmpDir = createTempDir('gsd-1077-gemini-'); - previousCwd = process.cwd(); - process.chdir(tmpDir); - - const geminiDir = path.join(tmpDir, '.gemini'); - fs.mkdirSync(geminiDir, { recursive: true }); - const result = install(false, 'gemini'); - settings = result && result.settings; - }); - - afterEach(() => { - process.chdir(previousCwd); - cleanup(tmpDir); - }); - - test('registry confirms gemini hookEvents is "gemini"', () => { - // Parity assertion: if the registry changes, this test fails first. - const he = registryHookEvents('gemini'); - if (he !== undefined) { - assert.strictEqual(he, 'gemini', - 'Registry descriptor for gemini must declare hookEvents="gemini"'); - } - }); - - test('gemini install returns a settings object', () => { - assert.ok(settings !== null && typeof settings === 'object', - 'gemini install must return a non-null settings object'); - }); - - test('gemini install registers at least one hook under AfterTool (post-tool)', () => { - assert.ok(hasHooksFor(settings, 'AfterTool'), - `Expected AfterTool hooks on gemini; got hooks keys: ${JSON.stringify(Object.keys((settings && settings.hooks) || {}))}`); - }); - - test('gemini install does NOT register context-monitor under PostToolUse (wrong dialect)', () => { - const cmds = hooksForEvent(settings, 'PostToolUse'); - const hasMonitor = cmds.some(c => c && c.includes('gsd-context-monitor')); - assert.strictEqual(hasMonitor, false, - `gemini must NOT use PostToolUse for context-monitor; got PostToolUse commands: ${JSON.stringify(cmds)}`); - }); - - test('gemini install registers at least one pre-tool hook (prompt-guard) under BeforeTool', () => { - const cmds = hooksForEvent(settings, 'BeforeTool'); - const hasPromptGuard = cmds.some(c => c && c.includes('gsd-prompt-guard')); - assert.ok(hasPromptGuard, - `Expected prompt-guard hook under BeforeTool on gemini; BeforeTool commands: ${JSON.stringify(cmds)}; hooks keys: ${JSON.stringify(Object.keys((settings && settings.hooks) || {}))}`); - }); - - test('gemini install does NOT register prompt-guard under PreToolUse (wrong pre-tool dialect)', () => { - const cmds = hooksForEvent(settings, 'PreToolUse'); - const hasPromptGuard = cmds.some(c => c && c.includes('gsd-prompt-guard')); - assert.strictEqual(hasPromptGuard, false, - `gemini must NOT use PreToolUse for prompt-guard; got PreToolUse commands: ${JSON.stringify(cmds)}`); - }); - }); - - // ── antigravity ── + // #1928: the gemini runtime was removed (Google sunset Gemini CLI + // 2026-06-18). antigravity — the Gemini-backend successor — is the only + // remaining runtime whose descriptor carries hookEvents='gemini'. describe('antigravity install uses AfterTool/BeforeTool (gemini dialect)', () => { let tmpDir; @@ -4063,7 +3523,7 @@ describe('enh-1077 phase 5f-2: registry-parity — hookEvents descriptor drives } // Runtimes that have settings.json surfaces and a hookEvents descriptor - const SETTINGS_JSON_RUNTIMES = ['claude', 'gemini', 'antigravity', 'augment', 'qwen', 'hermes', 'codebuddy']; + const SETTINGS_JSON_RUNTIMES = ['claude', 'antigravity', 'augment', 'qwen', 'hermes', 'codebuddy']; const failures = []; @@ -4123,422 +3583,6 @@ describe('enh-1077 phase 5f-2: registry-parity — hookEvents descriptor drives } -// ──────────────────────────────────────────────────────────────────────── -// Folded from tests/enh-776-install-gemini-hook-events.test.cjs — consolidation epic #1969 (B1 #1970) -// ──────────────────────────────────────────────────────────────────────── -{ - const { describe: __foldDescribe } = require('node:test'); - __foldDescribe("folded:enh-776-install-gemini-hook-events (consolidation epic #1969 B1 #1970)", () => { -'use strict'; - -process.env.GSD_TEST_MODE = '1'; - -/** - * Enhancement #776: Adopt new Gemini hook events + detect hooksConfig.enabled:false. - * - * Gemini CLI exposes several hook events beyond BeforeTool/AfterTool that gsd - * previously did not register. This suite asserts that a Gemini install - * registers the 3 new high-value events: - * - BeforeAgent — fires before the agent plans (context headroom tracking) - * - AfterAgent — fires after final response generation (context tracking) - * - BeforeModel — fires before each LLM call (per-turn context awareness) - * - * All three are wired to gsd-context-monitor.js — the same hook used for - * AfterTool — so context headroom warnings surface at these lifecycle moments. - * - * Also asserts: - * - Claude Code installs do NOT gain these Gemini-only events (strict scope guard). - * - Reinstalls are idempotent (no hook duplication). - * - Uninstall removes the new event registrations. - * - hooksConfig.enabled:false warning is emitted during a Gemini install. - * - * Source: https://github.com/google-gemini/gemini-cli/blob/main/docs/hooks/reference.md - */ - -const { test, describe, beforeEach, afterEach } = require('node:test'); -const assert = require('node:assert/strict'); -const fs = require('node:fs'); -const path = require('node:path'); - -const { install, uninstall, validateHookFields } = require('../bin/install.js'); -const { createTempDir, cleanup } = require('./helpers.cjs'); - -// ─── Helpers ───────────────────────────────────────────────────────────────── - -/** Extract all hook commands registered under `eventName` from settings. */ -function hooksForEvent(settings, eventName) { - if (!settings || !settings.hooks || !Array.isArray(settings.hooks[eventName])) return []; - return settings.hooks[eventName].flatMap(entry => - (entry && Array.isArray(entry.hooks) ? entry.hooks : []) - .map(h => h && h.command) - .filter(Boolean) - ); -} - -// Stub JS hook files that the installer checks with fs.existsSync() so hook -// registration guards pass even when hooks/dist/ isn't built. -// -// For Gemini, the installer migration baseline includes 'hooks/' in its surface -// list (unlike Qwen which excludes it). Pre-install stubs placed in .gemini/hooks/ -// are classified as 'bundled-gsd-hook' and auto-removed by the migration before -// registration can succeed. The workaround: run a first install (which writes the -// gsd-file-manifest.json), then add the stubs, then run install again. On the -// second install the manifest marks the hook files as managed, so migration keeps -// them and registration guards (fs.existsSync) pass. -const HOOKS_SRC = path.join(__dirname, '..', 'hooks'); -const STUB_HOOKS = [ - 'gsd-context-monitor.js', - 'gsd-prompt-guard.js', - 'gsd-check-update.js', -]; - -function stubHooksIntoTarget(targetDir) { - const hooksDest = path.join(targetDir, 'hooks'); - fs.mkdirSync(hooksDest, { recursive: true }); - for (const hookFile of STUB_HOOKS) { - const src = path.join(HOOKS_SRC, hookFile); - const dest = path.join(hooksDest, hookFile); - if (fs.existsSync(src)) { - fs.copyFileSync(src, dest); - } else { - // Minimal stub so existsSync passes - fs.writeFileSync(dest, '#!/usr/bin/env node\n// stub\n'); - } - try { fs.chmodSync(dest, 0o755); } catch { /* Windows */ } - } -} - -/** - * Two-pass Gemini install. - * - * Pass 1: install() with no hook stubs — writes gsd-file-manifest.json. - * Migration runs on an empty hooks/ so nothing gets auto-removed. - * Pass 2: stub hooks into the target dir (now manifest-tracked on next scan), - * then run install() again. Migration now classifies the hooks as - * managed-unchanged and preserves them; registration guards pass. - * - * Returns the settings from pass 2. - */ -function twoPassGeminiInstall(tmpDir) { - const targetDir = path.join(tmpDir, '.gemini'); - fs.mkdirSync(targetDir, { recursive: true }); - - // Pass 1 — no hook stubs yet; writes the manifest - const result1 = install(false, 'gemini'); - persistSettings(result1.settingsPath, result1.settings); - - // Inject stubs so the registration guards (fs.existsSync) pass on pass 2 - stubHooksIntoTarget(targetDir); - - // Pass 2 — manifest exists, migration keeps stubs, registration succeeds - process.chdir(tmpDir); - const result2 = install(false, 'gemini'); - return result2; -} - -/** - * Persist in-memory settings to disk, simulating what finishInstall() does - * (finishInstall is not exported). Required for tests that call install() - * twice and need the second call to read the first call's hook registrations. - */ -function persistSettings(settingsPath, settings) { - fs.mkdirSync(path.dirname(settingsPath), { recursive: true }); - fs.writeFileSync(settingsPath, JSON.stringify(validateHookFields(settings), null, 2) + '\n', 'utf8'); -} - -// ─── Suite 1: Gemini — new events are registered ───────────────────────────── - -describe('enh-776: Gemini install registers 3 new hook events', () => { - let tmpDir; - let previousCwd; - let settings; - - beforeEach(() => { - tmpDir = createTempDir('gsd-776-gemini-'); - previousCwd = process.cwd(); - process.chdir(tmpDir); - - // Two-pass install: first pass writes manifest, second pass with stubs - // so registration guards (fs.existsSync) pass. - const result = twoPassGeminiInstall(tmpDir); - settings = result.settings; - }); - - afterEach(() => { - process.chdir(previousCwd); - cleanup(tmpDir); - }); - - test('install returns a settings object (not null)', () => { - assert.ok(settings !== null && typeof settings === 'object', - 'Gemini install must return a non-null settings object'); - }); - - test('BeforeAgent event is registered with at least one hook', () => { - const cmds = hooksForEvent(settings, 'BeforeAgent'); - assert.ok(cmds.length > 0, - `Expected BeforeAgent hooks; got hooks: ${JSON.stringify(settings && settings.hooks)}`); - }); - - test('AfterAgent event is registered with at least one hook', () => { - const cmds = hooksForEvent(settings, 'AfterAgent'); - assert.ok(cmds.length > 0, - `Expected AfterAgent hooks; got hooks: ${JSON.stringify(settings && settings.hooks)}`); - }); - - test('BeforeModel event is registered with at least one hook', () => { - const cmds = hooksForEvent(settings, 'BeforeModel'); - assert.ok(cmds.length > 0, - `Expected BeforeModel hooks; got hooks: ${JSON.stringify(settings && settings.hooks)}`); - }); - - test('BeforeAgent / AfterAgent / BeforeModel all use gsd-context-monitor', () => { - for (const event of ['BeforeAgent', 'AfterAgent', 'BeforeModel']) { - const cmds = hooksForEvent(settings, event); - assert.ok( - cmds.some(c => c.includes('gsd-context-monitor')), - `Event ${event} should use gsd-context-monitor; got commands: ${JSON.stringify(cmds)}` - ); - } - }); -}); - -// ─── Suite 2: Non-Gemini installs do NOT get the new events ────────────────── -// -// Two runtimes are particularly important to guard: -// Claude — the canonical non-Gemini runtime -// Antigravity — shares Gemini-style BeforeTool/AfterTool naming and uses -// isGemini-adjacent logic; a future accidental -// `isGemini || isAntigravity` change must be caught here. - -describe('enh-776: Claude install does NOT register Gemini-only hook events', () => { - let tmpDir; - let previousCwd; - let settings; - - beforeEach(() => { - tmpDir = createTempDir('gsd-776-claude-'); - previousCwd = process.cwd(); - process.chdir(tmpDir); - - const result = install(false, 'claude'); - settings = result && result.settings; - }); - - afterEach(() => { - process.chdir(previousCwd); - cleanup(tmpDir); - }); - - test('Claude install does not register BeforeAgent', () => { - const cmds = hooksForEvent(settings, 'BeforeAgent'); - assert.strictEqual(cmds.length, 0, - `Claude should NOT have BeforeAgent; got: ${JSON.stringify(cmds)}`); - }); - - test('Claude install does not register AfterAgent', () => { - const cmds = hooksForEvent(settings, 'AfterAgent'); - assert.strictEqual(cmds.length, 0, - `Claude should NOT have AfterAgent; got: ${JSON.stringify(cmds)}`); - }); - - test('Claude install does not register BeforeModel', () => { - const cmds = hooksForEvent(settings, 'BeforeModel'); - assert.strictEqual(cmds.length, 0, - `Claude should NOT have BeforeModel; got: ${JSON.stringify(cmds)}`); - }); -}); - -describe('enh-776: Antigravity install does NOT register Gemini-only hook events', () => { - let tmpDir; - let previousCwd; - let settings; - - beforeEach(() => { - tmpDir = createTempDir('gsd-776-antigravity-'); - previousCwd = process.cwd(); - process.chdir(tmpDir); - - const result = install(false, 'antigravity'); - settings = result && result.settings; - }); - - afterEach(() => { - process.chdir(previousCwd); - cleanup(tmpDir); - }); - - test('Antigravity install does not register BeforeAgent', () => { - const cmds = hooksForEvent(settings, 'BeforeAgent'); - assert.strictEqual(cmds.length, 0, - `Antigravity should NOT have BeforeAgent; got: ${JSON.stringify(cmds)}`); - }); - - test('Antigravity install does not register AfterAgent', () => { - const cmds = hooksForEvent(settings, 'AfterAgent'); - assert.strictEqual(cmds.length, 0, - `Antigravity should NOT have AfterAgent; got: ${JSON.stringify(cmds)}`); - }); - - test('Antigravity install does not register BeforeModel', () => { - const cmds = hooksForEvent(settings, 'BeforeModel'); - assert.strictEqual(cmds.length, 0, - `Antigravity should NOT have BeforeModel; got: ${JSON.stringify(cmds)}`); - }); -}); - -// ─── Suite 3: Idempotency — persisted reinstall does not duplicate hooks ────── - -describe('enh-776: Gemini install is idempotent across persisted reinstalls', () => { - let tmpDir; - let previousCwd; - - beforeEach(() => { - tmpDir = createTempDir('gsd-776-idem-'); - previousCwd = process.cwd(); - process.chdir(tmpDir); - }); - - afterEach(() => { - process.chdir(previousCwd); - cleanup(tmpDir); - }); - - test('re-running after persisted first install does not duplicate hook entries', () => { - // Two-pass to get hooks installed. - const result2 = twoPassGeminiInstall(tmpDir); - const s2 = result2.settings; - - // Assert hooks ARE registered after pass 2 (guards against false-pass where - // hooks never registered and idempotency passes trivially at count=0). - for (const event of ['BeforeAgent', 'AfterAgent', 'BeforeModel']) { - const cmds = hooksForEvent(s2, event); - assert.strictEqual(cmds.length, 1, - `Event ${event} should have exactly 1 hook after two-pass install; got ${cmds.length}: ${JSON.stringify(cmds)}`); - } - - persistSettings(result2.settingsPath, s2); - - // Third install: reads the persisted settings.json — dedup guards apply - process.chdir(tmpDir); - const result3 = install(false, 'gemini'); - const s3 = result3.settings; - - for (const event of ['BeforeAgent', 'AfterAgent', 'BeforeModel']) { - const cmds = hooksForEvent(s3, event); - assert.strictEqual(cmds.length, 1, - `Event ${event} should have exactly 1 hook command after idempotent reinstall; got ${cmds.length}: ${JSON.stringify(cmds)}`); - } - }); -}); - -// ─── Suite 4: Uninstall removes the new event registrations ────────────────── - -describe('enh-776: Gemini uninstall removes new hook event entries', () => { - let tmpDir; - let previousCwd; - - beforeEach(() => { - tmpDir = createTempDir('gsd-776-uninstall-'); - previousCwd = process.cwd(); - process.chdir(tmpDir); - - // Two-pass install and persist so uninstall has a settings.json to clean - const result = twoPassGeminiInstall(tmpDir); - persistSettings(result.settingsPath, result.settings); - }); - - afterEach(() => { - process.chdir(previousCwd); - cleanup(tmpDir); - }); - - test('settings.json hook entries are removed on uninstall', () => { - uninstall(false, 'gemini'); - const settingsPath = path.join(tmpDir, '.gemini', 'settings.json'); - if (!fs.existsSync(settingsPath)) return; // file removed entirely is fine - const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); - for (const event of ['BeforeAgent', 'AfterAgent', 'BeforeModel']) { - const cmds = hooksForEvent(settings, event); - assert.strictEqual(cmds.length, 0, - `After uninstall, ${event} should have 0 hooks; got: ${JSON.stringify(cmds)}`); - } - }); -}); - -// ─── Suite 5: hooksConfig.enabled:false warning ─────────────────────────────── - -describe('enh-776: hooksConfig.enabled:false warning during Gemini install', () => { - let tmpDir; - let previousCwd; - let stderrLines; - let originalWarn; - - beforeEach(() => { - tmpDir = createTempDir('gsd-776-hookscfg-'); - previousCwd = process.cwd(); - process.chdir(tmpDir); - - const targetDir = path.join(tmpDir, '.gemini'); - fs.mkdirSync(targetDir, { recursive: true }); - - // Capture console.warn output - stderrLines = []; - originalWarn = console.warn; - console.warn = (...args) => { stderrLines.push(args.join(' ')); }; - }); - - afterEach(() => { - console.warn = originalWarn; - process.chdir(previousCwd); - cleanup(tmpDir); - }); - - test('emits a warning when hooksConfig.enabled is false', () => { - // Write a settings.json with hooksConfig.enabled: false BEFORE install - // (the check in install() reads the existing settings.json on disk). - const targetDir = path.join(tmpDir, '.gemini'); - const settingsPath = path.join(targetDir, 'settings.json'); - fs.writeFileSync(settingsPath, JSON.stringify({ hooksConfig: { enabled: false } }, null, 2) + '\n', 'utf8'); - - install(false, 'gemini'); - const warnText = stderrLines.join('\n'); - assert.ok( - warnText.includes('hooksConfig.enabled is false'), - `Expected hooksConfig.enabled warning; got console.warn output:\n${warnText}` - ); - }); - - test('does NOT emit the hooksConfig warning when hooksConfig.enabled is true', () => { - const targetDir = path.join(tmpDir, '.gemini'); - const settingsPath = path.join(targetDir, 'settings.json'); - fs.writeFileSync(settingsPath, JSON.stringify({ hooksConfig: { enabled: true } }, null, 2) + '\n', 'utf8'); - - install(false, 'gemini'); - const warnText = stderrLines.join('\n'); - assert.ok( - !warnText.includes('hooksConfig.enabled is false'), - `Should NOT warn when hooksConfig.enabled is true; got:\n${warnText}` - ); - }); - - test('does NOT emit the hooksConfig warning when hooksConfig is absent', () => { - const targetDir = path.join(tmpDir, '.gemini'); - const settingsPath = path.join(targetDir, 'settings.json'); - fs.writeFileSync(settingsPath, JSON.stringify({}, null, 2) + '\n', 'utf8'); - - install(false, 'gemini'); - const warnText = stderrLines.join('\n'); - assert.ok( - !warnText.includes('hooksConfig.enabled is false'), - `Should NOT warn when hooksConfig is absent; got:\n${warnText}` - ); - }); -}); - }); -} - - // ──────────────────────────────────────────────────────────────────────── // Folded from tests/enh-788-qwen-hook-events.test.cjs — consolidation epic #1969 (B1 #1970) // ──────────────────────────────────────────────────────────────────────── diff --git a/tests/install-regressions.test.cjs b/tests/install-regressions.test.cjs index 70efb7c43..99d628cc7 100644 --- a/tests/install-regressions.test.cjs +++ b/tests/install-regressions.test.cjs @@ -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'); } }); diff --git a/tests/install-runtime-artifacts.test.cjs b/tests/install-runtime-artifacts.test.cjs index 06555f065..6e9e86076 100644 --- a/tests/install-runtime-artifacts.test.cjs +++ b/tests/install-runtime-artifacts.test.cjs @@ -104,14 +104,18 @@ describe('installRuntimeArtifacts — consumes Runtime Artifact Install Plan Mod }); t.after(restore); - installer.installRuntimeArtifacts('gemini', configDir, 'global', RESOLVED_CORE); + // #1928: gemini (the only runtime whose 'commands' kind used a + // namespaced-by-dir 'commands/gsd' layout) was removed; cursor is the + // stand-in — it has a 'commands' kind but prefixes files 'gsd-' + // (destSubpath basename !== prefix stem), so proof.md → gsd-proof.md. + installer.installRuntimeArtifacts('cursor', configDir, 'global', RESOLVED_CORE); - assert.strictEqual(planArgs.layout.runtime, 'gemini'); + assert.strictEqual(planArgs.layout.runtime, 'cursor'); assert.strictEqual(planArgs.layout.configDir, configDir); assert.strictEqual(planArgs.layout.scope, 'global'); assert.strictEqual(planArgs.resolvedProfile, RESOLVED_CORE); - assert.strictEqual(planArgs.resolveAttribution('gemini'), undefined); - assert.ok(fs.existsSync(path.join(configDir, 'commands', 'gsd', 'proof.md'))); + assert.strictEqual(planArgs.resolveAttribution('claude'), undefined); + assert.ok(fs.existsSync(path.join(configDir, 'commands', 'gsd', 'gsd-proof.md'))); assert.ok(!fs.existsSync(cleanupDir), 'returned cleanup dir must be removed after copy'); }); @@ -134,7 +138,7 @@ describe('installRuntimeArtifacts — consumes Runtime Artifact Install Plan Mod t.after(restore); assert.throws( - () => installer.installRuntimeArtifacts('gemini', configDir, 'global', RESOLVED_CORE), + () => installer.installRuntimeArtifacts('claude', configDir, 'global', RESOLVED_CORE), /planned failure/, ); assert.ok(!fs.existsSync(cleanupDir), 'failure cleanup dir must be removed'); @@ -147,7 +151,7 @@ const SKILLS_RUNTIMES_LAYOUT = [ ]; const ALL_RUNTIMES_LAYOUT = [ - 'claude', 'cursor', 'gemini', 'codex', 'copilot', 'antigravity', + 'claude', 'cursor', 'codex', 'copilot', 'antigravity', 'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline', 'kimi', 'opencode', 'kilo', ]; @@ -263,19 +267,6 @@ describe('installRuntimeArtifacts — hermes nested layout', () => { }); }); -describe('installRuntimeArtifacts — gemini commands layout', () => { - test('gemini: commands/gsd/ created, no skills/', (t) => { - const configDir = createTempDir('gsd-ial-gemini-'); - t.after(() => cleanup(configDir)); - - installRuntimeArtifacts('gemini', configDir, 'global', RESOLVED_CORE); - - assert.ok(fs.existsSync(path.join(configDir, 'commands', 'gsd'))); - assert.ok(fs.existsSync(path.join(configDir, 'commands', 'gsd', 'help.md'))); - assert.ok(!fs.existsSync(path.join(configDir, 'skills'))); - }); -}); - describe('installRuntimeArtifacts — cursor commands layout (#785)', () => { test('cursor: skills/ AND commands/ both created; commands/gsd-help.md is plain markdown', (t) => { const configDir = createTempDir('gsd-ial-cursor-cmds-'); @@ -475,9 +466,9 @@ describe('uninstallRuntimeArtifacts — consumes Runtime Artifact Uninstall Plan }); t.after(restore); - installer.uninstallRuntimeArtifacts('gemini', configDir, 'global'); + installer.uninstallRuntimeArtifacts('cursor', configDir, 'global'); - assert.strictEqual(planLayout.runtime, 'gemini'); + assert.strictEqual(planLayout.runtime, 'cursor'); assert.strictEqual(planLayout.configDir, configDir); assert.strictEqual(planLayout.scope, 'global'); assert.ok(!fs.existsSync(path.join(commandsDir, 'gsd-help.md'))); @@ -1981,207 +1972,6 @@ describe('resolveRuntimeArtifactLayout — cline scope-aware (Fix 2)', () => { } -// ──────────────────────────────────────────────────────────────────────── -// Folded from tests/bug-3037-gemini-duplicate-commands.test.cjs — consolidation epic #1969 (B1 #1970) -// ──────────────────────────────────────────────────────────────────────── -{ - const { describe: __foldDescribe } = require('node:test'); - __foldDescribe("folded:bug-3037-gemini-duplicate-commands (consolidation epic #1969 B1 #1970)", () => { -/** - * Bug #3037: Gemini global+local install creates duplicate /gsd:* commands - * across user (HOME/.gemini/) and workspace (PROJECT/.gemini/) scopes. - * - * Reproduction (from issue body): - * 1. install --gemini --global with HOME=tmpHome - * 2. cd tmpProject; install --gemini --local - * → both ~/.gemini/commands/gsd/ and PROJECT/.gemini/commands/gsd/ contain - * 65 overlapping command filenames. - * → Gemini conflict detection renames every overlapping command to - * /workspace.gsd:* and /user.gsd:*, breaking the documented /gsd:* - * namespace. - * - * Fix: when the local Gemini install detects the user-scope GSD command - * directory already exists with managed-shape content, skip the local copy - * and emit a clear warning explaining the conflict avoidance. - * - * Tests assert on the post-install filesystem shape and capture the skip - * warning so the full test log remains warning-clean. - */ - -'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('node:fs'); -const path = require('node:path'); -const { createTempDir, cleanup, captureConsole } = require('./helpers.cjs'); - -const { install } = require('../bin/install.js'); - -describe('bug #3037: Gemini global+local install must not create duplicate command scopes', () => { - let tmpHome; - let tmpProject; - let originalHome; - let originalUserprofile; - let originalCwd; - - beforeEach(() => { - tmpHome = createTempDir('gsd-3037-home-'); - tmpProject = createTempDir('gsd-3037-work-'); - originalHome = process.env.HOME; - originalUserprofile = process.env.USERPROFILE; - originalCwd = process.cwd(); - // Point HOME at the temp dir so install(true, 'gemini') writes to - // tmpHome/.gemini, not the developer's real home. - process.env.HOME = tmpHome; - process.env.USERPROFILE = tmpHome; - }); - - afterEach(() => { - if (originalHome === undefined) delete process.env.HOME; - else process.env.HOME = originalHome; - // CR #3041: also restore USERPROFILE so the temp HOME doesn't leak - // into later tests and create order-dependent failures on Windows - // or any code path that reads USERPROFILE. - if (originalUserprofile === undefined) delete process.env.USERPROFILE; - else process.env.USERPROFILE = originalUserprofile; - process.chdir(originalCwd); - cleanup(tmpHome); - cleanup(tmpProject); - }); - - function listCommandFiles(geminiCommandsRoot) { - if (!fs.existsSync(geminiCommandsRoot)) return []; - const out = []; - function walk(dir) { - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const full = path.join(dir, entry.name); - if (entry.isDirectory()) walk(full); - else if (entry.isFile()) out.push(path.relative(geminiCommandsRoot, full)); - } - } - walk(geminiCommandsRoot); - return out.sort(); - } - - function runInstall(...args) { - return captureConsole(() => install(...args)); - } - - test('global install populates HOME/.gemini/commands/gsd', () => { - runInstall(true, 'gemini'); - const globalCmds = path.join(tmpHome, '.gemini', 'commands', 'gsd'); - const files = listCommandFiles(globalCmds); - assert.ok( - files.length > 0, - 'global install must populate HOME/.gemini/commands/gsd' - ); - }); - - test('local install after global does NOT populate PROJECT/.gemini/commands/gsd (avoids /gsd:* namespace conflict)', () => { - // Step 1: global install - runInstall(true, 'gemini'); - const globalCmds = path.join(tmpHome, '.gemini', 'commands', 'gsd'); - const globalFiles = listCommandFiles(globalCmds); - assert.ok(globalFiles.length > 0, 'precondition: global install must succeed'); - - // Step 2: local install in a temp project - process.chdir(tmpProject); - const { stdout } = runInstall(false, 'gemini'); - assert.match( - stdout, - /Skipping commands\/gsd\/ for local install/, - 'local install must explain why it skips duplicate Gemini commands' - ); - - // Assertion: the local commands/gsd/ directory must NOT exist (or must - // be empty) so Gemini's conflict detection has nothing to rename. The - // fix may either skip the directory entirely (preferred — no leftover - // file system noise) or create an empty directory (acceptable but odd). - const localCmds = path.join(tmpProject, '.gemini', 'commands', 'gsd'); - const localFiles = listCommandFiles(localCmds); - assert.equal( - localFiles.length, - 0, - `local install must skip commands/gsd/ when global already exists; ` + - `found ${localFiles.length} duplicate command file(s) at ${localCmds}` - ); - }); - - test('local install with NO existing global GSD does still populate PROJECT/.gemini/commands/gsd', () => { - // No global install first — local should proceed normally so users who - // only ever run --local still get GSD commands in their project. - process.chdir(tmpProject); - runInstall(false, 'gemini'); - - const localCmds = path.join(tmpProject, '.gemini', 'commands', 'gsd'); - const localFiles = listCommandFiles(localCmds); - assert.ok( - localFiles.length > 0, - `local-only install must populate PROJECT/.gemini/commands/gsd; ` + - `found ${localFiles.length} files at ${localCmds}` - ); - }); - - test('local install when HOME has hand-dropped overrides UNDER commands/gsd/ (but no full GSD) still populates locally', () => { - // CR #3041 regression: the previous detection was - // `fs.readdirSync(homeGeminiGsd).length > 0` which would skip the - // local install for a user who manually dropped a single override - // command at ~/.gemini/commands/gsd/.toml without ever - // running --gemini --global. The fix narrows detection to require - // at least 3 canonical GSD command files (help.toml, progress.toml, - // new-project.toml) — a marker that's structurally impossible to - // produce by accident. - const homeGsdDir = path.join(tmpHome, '.gemini', 'commands', 'gsd'); - fs.mkdirSync(homeGsdDir, { recursive: true }); - fs.writeFileSync( - path.join(homeGsdDir, 'my-override.toml'), - 'description = "user override"\nprompt = "..."\n' - ); - - process.chdir(tmpProject); - runInstall(false, 'gemini'); - - const localCmds = path.join(tmpProject, '.gemini', 'commands', 'gsd'); - const localFiles = listCommandFiles(localCmds); - assert.ok( - localFiles.length > 0, - `local install must proceed when HOME/.gemini/commands/gsd contains ` + - `only user overrides (not the full GSD canary set); ` + - `found ${localFiles.length} files at ${localCmds}` - ); - }); - - test('local install when HOME/.gemini exists but commands/gsd is absent (non-GSD Gemini user) still populates locally', () => { - // Simulate a user who has Gemini configured but never installed GSD - // globally. ~/.gemini/ exists with unrelated content; ~/.gemini/commands/ - // may or may not exist with non-gsd subdirectories. Local install must - // still proceed because no GSD-managed user-scope directory is present. - fs.mkdirSync(path.join(tmpHome, '.gemini', 'commands', 'someone-else'), { - recursive: true, - }); - fs.writeFileSync( - path.join(tmpHome, '.gemini', 'commands', 'someone-else', 'foo.toml'), - 'description = "user command"\nprompt = "..."\n' - ); - - process.chdir(tmpProject); - runInstall(false, 'gemini'); - - const localCmds = path.join(tmpProject, '.gemini', 'commands', 'gsd'); - const localFiles = listCommandFiles(localCmds); - assert.ok( - localFiles.length > 0, - `local install must proceed when no GSD-managed user-scope directory ` + - `exists, even if other Gemini commands are present at the user scope` - ); - }); -}); - }); -} - // ──────────────────────────────────────────────────────────────────────── // Folded from tests/enh-789-codebuddy-commands.test.cjs — consolidation epic #1969 (B1 #1970) @@ -2939,12 +2729,8 @@ describe('skill frontmatter name parity (#2643 / #2808)', () => { /** * GSD Tools Tests — #778 cross-runtime command enrichment. * - * Two independently-verified, additive sub-features: - * (b) Qwen Code skills: numeric `priority` field (higher sorts earlier in the - * /skills TUI listing per the Qwen skills spec). Scoped to runtime='qwen'. - * (c) Gemini custom-command TOML: $ARGUMENTS → {{args}} interpolation, and a - * fixed `!{cat .planning/STATE.md}` live-state injection on the - * situational `progress` command (injection-safe — no interpolated input). + * Qwen Code skills: numeric `priority` field (higher sorts earlier in the + * /skills TUI listing per the Qwen skills spec). Scoped to runtime='qwen'. * * The OpenCode sub-feature (per-command model/agent/subtask/variant) is * intentionally NOT implemented — see PR description: `model` reintroduces the @@ -2952,21 +2738,21 @@ describe('skill frontmatter name parity (#2643 / #2808)', () => { * `subtask`/`agent` change execution semantics for GSD's interactive commands, * and `variant` is not in the OpenCode command schema. * + * #1928: the Gemini custom-command TOML sub-feature ($ARGUMENTS → {{args}} + * interpolation, !{cat .planning/STATE.md} live-state injection) was removed + * along with the gemini runtime (Google sunset Gemini CLI 2026-06-18). + * convertClaudeToGeminiMarkdown no longer exists in bin/install.js. + * * Uses node:test and node:assert (NOT Jest). */ process.env.GSD_TEST_MODE = '1'; -const { test, describe, beforeEach, afterEach } = require('node:test'); +const { test, describe } = require('node:test'); const assert = require('node:assert/strict'); -const fs = require('fs'); -const path = require('path'); -const { createTempDir, cleanup } = require('./helpers.cjs'); const { convertClaudeCommandToClaudeSkill, - convertClaudeToGeminiMarkdown, - install, } = require('../bin/install.js'); // ─── (b) Qwen Code: priority ordering ─────────────────────────────────────── @@ -3018,147 +2804,6 @@ describe('#778 (b) Qwen skills priority', () => { }); }); -// ─── (c) Gemini: {{args}} interpolation ───────────────────────────────────── - -describe('#778 (c) Gemini {{args}} interpolation', () => { - const cmd = (body) => - ['---', 'name: gsd:demo', 'description: Demo', '---', '', body].join('\n'); - - test('maps $ARGUMENTS to {{args}} in the TOML prompt', () => { - const out = convertClaudeToGeminiMarkdown( - cmd('Operate on $ARGUMENTS now.'), - { isCommand: true, commandName: 'demo' } - ); - assert.ok(out.includes('{{args}}'), '{{args}} present'); - assert.ok(!out.includes('$ARGUMENTS'), 'literal $ARGUMENTS removed'); - assert.ok(out.startsWith('description =') || out.includes('prompt ='), 'TOML shape'); - }); - - test('command without $ARGUMENTS gets no injected {{args}}', () => { - const out = convertClaudeToGeminiMarkdown( - cmd('No arguments referenced here.'), - { isCommand: true, commandName: 'demo' } - ); - assert.ok(!out.includes('{{args}}'), 'no spurious {{args}}'); - }); - - test('non-command Gemini content is not TOML-converted and keeps $ARGUMENTS', () => { - const out = convertClaudeToGeminiMarkdown( - cmd('Reference $ARGUMENTS.'), - { isCommand: false } - ); - // isCommand:false keeps markdown — no TOML wrap and no {{args}} mapping - // (the $ARGUMENTS→{{args}} translation is scoped to the TOML command path). - assert.ok(!out.startsWith('prompt ='), 'not wrapped as TOML prompt'); - assert.ok(out.includes('$ARGUMENTS'), '$ARGUMENTS left intact for non-command content'); - assert.ok(!out.includes('{{args}}'), 'no {{args}} injected outside the command path'); - }); -}); - -// ─── (c) Gemini: end-to-end install wiring ────────────────────────────────── -// Proves the install path derives the per-command name from the file stem so a -// regression in the call-site wiring (not just the converter) is caught. - -describe('#778 (c) Gemini install wiring (end-to-end)', () => { - let tmpDir; - let tmpHome; - let prevCwd; - let prevHome; - let prevUserprofile; - - beforeEach(() => { - tmpDir = createTempDir('gsd-enh778-gem-'); - tmpHome = createTempDir('gsd-enh778-home-'); - prevCwd = process.cwd(); - prevHome = process.env.HOME; - prevUserprofile = process.env.USERPROFILE; - process.chdir(tmpDir); - // Isolate HOME so a real ~/.gemini/commands/gsd/ doesn't trigger the #3037 - // local-install conflict-skip path. - process.env.HOME = tmpHome; - process.env.USERPROFILE = tmpHome; - }); - - afterEach(() => { - process.chdir(prevCwd); - if (prevHome === undefined) delete process.env.HOME; else process.env.HOME = prevHome; - if (prevUserprofile === undefined) delete process.env.USERPROFILE; - else process.env.USERPROFILE = prevUserprofile; - cleanup(tmpDir); - cleanup(tmpHome); - }); - - test('installed progress.toml carries the !{} block; arg-bearing commands get {{args}}', () => { - const oldLog = console.log; - console.log = () => {}; - try { - install(false, 'gemini'); - } finally { - console.log = oldLog; - } - - const commandsDir = path.join(tmpDir, '.gemini', 'commands', 'gsd'); - const progressToml = path.join(commandsDir, 'progress.toml'); - assert.ok(fs.existsSync(progressToml), 'progress.toml installed'); - const progress = fs.readFileSync(progressToml, 'utf8'); - // Proves commandName was derived as 'progress' from the file stem. - assert.ok( - progress.includes('!{cat .planning/STATE.md 2>/dev/null}'), - 'progress.toml has the live-state shell block' - ); - - // A non-situational command must NOT receive the shell block. - const helpToml = path.join(commandsDir, 'help.toml'); - if (fs.existsSync(helpToml)) { - assert.ok(!fs.readFileSync(helpToml, 'utf8').includes('!{'), 'help.toml has no shell block'); - } - - // At least one installed command must use {{args}} and none may retain a - // literal $ARGUMENTS (every command body's $ARGUMENTS is translated). - const tomls = fs.readdirSync(commandsDir).filter((f) => f.endsWith('.toml')); - const withArgs = tomls.filter((f) => - fs.readFileSync(path.join(commandsDir, f), 'utf8').includes('{{args}}')); - const withLiteral = tomls.filter((f) => - fs.readFileSync(path.join(commandsDir, f), 'utf8').includes('$ARGUMENTS')); - assert.ok(withArgs.length > 0, 'at least one installed command interpolates {{args}}'); - assert.equal(withLiteral.length, 0, 'no installed command retains literal $ARGUMENTS'); - }); -}); - -// ─── (c) Gemini: !{...} live-state injection (progress) ───────────────────── - -describe('#778 (c) Gemini !{} live-state injection', () => { - const cmd = (name) => - ['---', `name: gsd:${name}`, `description: ${name}`, '---', '', 'Workflow body.'].join('\n'); - - test('progress command injects a fixed !{cat .planning/STATE.md} block', () => { - const out = convertClaudeToGeminiMarkdown( - cmd('progress'), - { isCommand: true, commandName: 'progress' } - ); - assert.ok(out.includes('!{cat .planning/STATE.md'), 'STATE.md injection present'); - }); - - test('non-progress commands get no !{} shell block', () => { - const out = convertClaudeToGeminiMarkdown( - cmd('help'), - { isCommand: true, commandName: 'help' } - ); - assert.ok(!out.includes('!{'), 'no shell block for non-situational command'); - }); - - test('SECURITY: the !{} block interpolates NO user input ({{args}})', () => { - const out = convertClaudeToGeminiMarkdown( - ['---', 'name: gsd:progress', 'description: progress', '---', '', - 'Body uses $ARGUMENTS too.'].join('\n'), - { isCommand: true, commandName: 'progress' } - ); - const blocks = out.match(/!\{([^}]*)\}/g) || []; - assert.equal(blocks.length, 1, 'exactly one shell block'); - assert.ok(!/\{\{args\}\}/.test(blocks[0]), 'no {{args}} inside the shell block'); - assert.ok(/^!\{cat \.planning\/STATE\.md/.test(blocks[0]), 'fixed cat command only'); - }); -}); }); } @@ -3540,13 +3185,15 @@ describe('#769/#921/#1319 Claude global install: spawning-orchestrator SKILL.md * * Verifies: * 1. Claude global install injects `effort:` into agent .md frontmatter. - * 2. Gemini global install does NOT inject `effort:` (Gemini-safe .md). - * 3. Codex inherited-model installs omit `model_reasoning_effort` so model + * 2. Codex inherited-model installs omit `model_reasoning_effort` so model * and effort are not partially pinned (#838). - * 4. Config-driven proof: effort.agent_overrides wins over tier defaults + * 3. Config-driven proof: effort.agent_overrides wins over tier defaults * for Claude .md and for Codex .toml when runtime:"codex" pins a model. - * 5. Source agents/gsd-planner.md has NO effort: key (injection is - * install-only, source stays Gemini-safe). + * 4. Source agents/gsd-planner.md has NO effort: key (injection is + * install-only, source markdown carries no effort: key). + * + * #1928: the gemini runtime (and its "Gemini install does NOT inject effort:" + * coverage) was removed — Google sunset Gemini CLI 2026-06-18. */ 'use strict'; @@ -3585,7 +3232,6 @@ function readFrontmatter(mdPath) { * * Env-var redirection: * claude → CLAUDE_CONFIG_DIR - * gemini → GEMINI_CONFIG_DIR * codex → CODEX_HOME * * HOME is also redirected to an isolated temp dir for the duration of the @@ -3608,7 +3254,6 @@ function readFrontmatter(mdPath) { function runGlobalInstall(runtime, tmpHome) { const envVarMap = { claude: 'CLAUDE_CONFIG_DIR', - gemini: 'GEMINI_CONFIG_DIR', codex: 'CODEX_HOME', }; const envVar = envVarMap[runtime]; @@ -3694,37 +3339,6 @@ describe('#443 Claude install: effort: injected into frontmatter', () => { }); }); -// ─── describe 2: Gemini install does NOT inject effort: ────────────────────── - -describe('#443 Gemini install: effort: absent (Gemini-safe)', () => { - let tmpDir; - let geminiHome; - - beforeEach(() => { - tmpDir = makeTmpDir('gsd-443-gemini-'); - geminiHome = path.join(tmpDir, 'gemini-home'); - fs.mkdirSync(geminiHome, { recursive: true }); - }); - - afterEach(() => { - cleanup(tmpDir); - }); - - test('gsd-planner.md does NOT contain effort: (Gemini install)', () => { - runGlobalInstall('gemini', geminiHome); - const fm = readFrontmatter(path.join(geminiHome, 'agents', 'gsd-planner.md')); - assert.doesNotMatch(fm, /^effort:/m, - `gsd-planner (Gemini) frontmatter must NOT have effort:\nActual:\n${fm}`); - }); - - test('gsd-executor.md does NOT contain effort: (Gemini install)', () => { - runGlobalInstall('gemini', geminiHome); - const fm = readFrontmatter(path.join(geminiHome, 'agents', 'gsd-executor.md')); - assert.doesNotMatch(fm, /^effort:/m, - `gsd-executor (Gemini) frontmatter must NOT have effort:\nActual:\n${fm}`); - }); -}); - // ─── describe 3: Codex inherited-model install omits model_reasoning_effort ── describe('#838 Codex install: inherited model omits model_reasoning_effort', () => { @@ -3991,7 +3605,6 @@ describe('getDirName (relocated to runtime-name-policy)', () => { claude: '.claude', copilot: '.github', opencode: '.opencode', - gemini: '.gemini', kilo: '.kilo', codex: '.codex', antigravity: '.agents', @@ -5789,207 +5402,6 @@ describe('resolveRuntimeArtifactLayout — cline scope-aware (Fix 2)', () => { } -// ──────────────────────────────────────────────────────────────────────── -// Folded from tests/bug-3037-gemini-duplicate-commands.test.cjs — consolidation epic #1969 (B1 #1970) -// ──────────────────────────────────────────────────────────────────────── -{ - const { describe: __foldDescribe } = require('node:test'); - __foldDescribe("folded:bug-3037-gemini-duplicate-commands (consolidation epic #1969 B1 #1970)", () => { -/** - * Bug #3037: Gemini global+local install creates duplicate /gsd:* commands - * across user (HOME/.gemini/) and workspace (PROJECT/.gemini/) scopes. - * - * Reproduction (from issue body): - * 1. install --gemini --global with HOME=tmpHome - * 2. cd tmpProject; install --gemini --local - * → both ~/.gemini/commands/gsd/ and PROJECT/.gemini/commands/gsd/ contain - * 65 overlapping command filenames. - * → Gemini conflict detection renames every overlapping command to - * /workspace.gsd:* and /user.gsd:*, breaking the documented /gsd:* - * namespace. - * - * Fix: when the local Gemini install detects the user-scope GSD command - * directory already exists with managed-shape content, skip the local copy - * and emit a clear warning explaining the conflict avoidance. - * - * Tests assert on the post-install filesystem shape and capture the skip - * warning so the full test log remains warning-clean. - */ - -'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('node:fs'); -const path = require('node:path'); -const { createTempDir, cleanup, captureConsole } = require('./helpers.cjs'); - -const { install } = require('../bin/install.js'); - -describe('bug #3037: Gemini global+local install must not create duplicate command scopes', () => { - let tmpHome; - let tmpProject; - let originalHome; - let originalUserprofile; - let originalCwd; - - beforeEach(() => { - tmpHome = createTempDir('gsd-3037-home-'); - tmpProject = createTempDir('gsd-3037-work-'); - originalHome = process.env.HOME; - originalUserprofile = process.env.USERPROFILE; - originalCwd = process.cwd(); - // Point HOME at the temp dir so install(true, 'gemini') writes to - // tmpHome/.gemini, not the developer's real home. - process.env.HOME = tmpHome; - process.env.USERPROFILE = tmpHome; - }); - - afterEach(() => { - if (originalHome === undefined) delete process.env.HOME; - else process.env.HOME = originalHome; - // CR #3041: also restore USERPROFILE so the temp HOME doesn't leak - // into later tests and create order-dependent failures on Windows - // or any code path that reads USERPROFILE. - if (originalUserprofile === undefined) delete process.env.USERPROFILE; - else process.env.USERPROFILE = originalUserprofile; - process.chdir(originalCwd); - cleanup(tmpHome); - cleanup(tmpProject); - }); - - function listCommandFiles(geminiCommandsRoot) { - if (!fs.existsSync(geminiCommandsRoot)) return []; - const out = []; - function walk(dir) { - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const full = path.join(dir, entry.name); - if (entry.isDirectory()) walk(full); - else if (entry.isFile()) out.push(path.relative(geminiCommandsRoot, full)); - } - } - walk(geminiCommandsRoot); - return out.sort(); - } - - function runInstall(...args) { - return captureConsole(() => install(...args)); - } - - test('global install populates HOME/.gemini/commands/gsd', () => { - runInstall(true, 'gemini'); - const globalCmds = path.join(tmpHome, '.gemini', 'commands', 'gsd'); - const files = listCommandFiles(globalCmds); - assert.ok( - files.length > 0, - 'global install must populate HOME/.gemini/commands/gsd' - ); - }); - - test('local install after global does NOT populate PROJECT/.gemini/commands/gsd (avoids /gsd:* namespace conflict)', () => { - // Step 1: global install - runInstall(true, 'gemini'); - const globalCmds = path.join(tmpHome, '.gemini', 'commands', 'gsd'); - const globalFiles = listCommandFiles(globalCmds); - assert.ok(globalFiles.length > 0, 'precondition: global install must succeed'); - - // Step 2: local install in a temp project - process.chdir(tmpProject); - const { stdout } = runInstall(false, 'gemini'); - assert.match( - stdout, - /Skipping commands\/gsd\/ for local install/, - 'local install must explain why it skips duplicate Gemini commands' - ); - - // Assertion: the local commands/gsd/ directory must NOT exist (or must - // be empty) so Gemini's conflict detection has nothing to rename. The - // fix may either skip the directory entirely (preferred — no leftover - // file system noise) or create an empty directory (acceptable but odd). - const localCmds = path.join(tmpProject, '.gemini', 'commands', 'gsd'); - const localFiles = listCommandFiles(localCmds); - assert.equal( - localFiles.length, - 0, - `local install must skip commands/gsd/ when global already exists; ` + - `found ${localFiles.length} duplicate command file(s) at ${localCmds}` - ); - }); - - test('local install with NO existing global GSD does still populate PROJECT/.gemini/commands/gsd', () => { - // No global install first — local should proceed normally so users who - // only ever run --local still get GSD commands in their project. - process.chdir(tmpProject); - runInstall(false, 'gemini'); - - const localCmds = path.join(tmpProject, '.gemini', 'commands', 'gsd'); - const localFiles = listCommandFiles(localCmds); - assert.ok( - localFiles.length > 0, - `local-only install must populate PROJECT/.gemini/commands/gsd; ` + - `found ${localFiles.length} files at ${localCmds}` - ); - }); - - test('local install when HOME has hand-dropped overrides UNDER commands/gsd/ (but no full GSD) still populates locally', () => { - // CR #3041 regression: the previous detection was - // `fs.readdirSync(homeGeminiGsd).length > 0` which would skip the - // local install for a user who manually dropped a single override - // command at ~/.gemini/commands/gsd/.toml without ever - // running --gemini --global. The fix narrows detection to require - // at least 3 canonical GSD command files (help.toml, progress.toml, - // new-project.toml) — a marker that's structurally impossible to - // produce by accident. - const homeGsdDir = path.join(tmpHome, '.gemini', 'commands', 'gsd'); - fs.mkdirSync(homeGsdDir, { recursive: true }); - fs.writeFileSync( - path.join(homeGsdDir, 'my-override.toml'), - 'description = "user override"\nprompt = "..."\n' - ); - - process.chdir(tmpProject); - runInstall(false, 'gemini'); - - const localCmds = path.join(tmpProject, '.gemini', 'commands', 'gsd'); - const localFiles = listCommandFiles(localCmds); - assert.ok( - localFiles.length > 0, - `local install must proceed when HOME/.gemini/commands/gsd contains ` + - `only user overrides (not the full GSD canary set); ` + - `found ${localFiles.length} files at ${localCmds}` - ); - }); - - test('local install when HOME/.gemini exists but commands/gsd is absent (non-GSD Gemini user) still populates locally', () => { - // Simulate a user who has Gemini configured but never installed GSD - // globally. ~/.gemini/ exists with unrelated content; ~/.gemini/commands/ - // may or may not exist with non-gsd subdirectories. Local install must - // still proceed because no GSD-managed user-scope directory is present. - fs.mkdirSync(path.join(tmpHome, '.gemini', 'commands', 'someone-else'), { - recursive: true, - }); - fs.writeFileSync( - path.join(tmpHome, '.gemini', 'commands', 'someone-else', 'foo.toml'), - 'description = "user command"\nprompt = "..."\n' - ); - - process.chdir(tmpProject); - runInstall(false, 'gemini'); - - const localCmds = path.join(tmpProject, '.gemini', 'commands', 'gsd'); - const localFiles = listCommandFiles(localCmds); - assert.ok( - localFiles.length > 0, - `local install must proceed when no GSD-managed user-scope directory ` + - `exists, even if other Gemini commands are present at the user scope` - ); - }); -}); - }); -} - // ──────────────────────────────────────────────────────────────────────── // Folded from tests/enh-789-codebuddy-commands.test.cjs — consolidation epic #1969 (B1 #1970) @@ -6747,12 +6159,8 @@ describe('skill frontmatter name parity (#2643 / #2808)', () => { /** * GSD Tools Tests — #778 cross-runtime command enrichment. * - * Two independently-verified, additive sub-features: - * (b) Qwen Code skills: numeric `priority` field (higher sorts earlier in the - * /skills TUI listing per the Qwen skills spec). Scoped to runtime='qwen'. - * (c) Gemini custom-command TOML: $ARGUMENTS → {{args}} interpolation, and a - * fixed `!{cat .planning/STATE.md}` live-state injection on the - * situational `progress` command (injection-safe — no interpolated input). + * Qwen Code skills: numeric `priority` field (higher sorts earlier in the + * /skills TUI listing per the Qwen skills spec). Scoped to runtime='qwen'. * * The OpenCode sub-feature (per-command model/agent/subtask/variant) is * intentionally NOT implemented — see PR description: `model` reintroduces the @@ -6760,21 +6168,21 @@ describe('skill frontmatter name parity (#2643 / #2808)', () => { * `subtask`/`agent` change execution semantics for GSD's interactive commands, * and `variant` is not in the OpenCode command schema. * + * #1928: the Gemini custom-command TOML sub-feature ($ARGUMENTS → {{args}} + * interpolation, !{cat .planning/STATE.md} live-state injection) was removed + * along with the gemini runtime (Google sunset Gemini CLI 2026-06-18). + * convertClaudeToGeminiMarkdown no longer exists in bin/install.js. + * * Uses node:test and node:assert (NOT Jest). */ process.env.GSD_TEST_MODE = '1'; -const { test, describe, beforeEach, afterEach } = require('node:test'); +const { test, describe } = require('node:test'); const assert = require('node:assert/strict'); -const fs = require('fs'); -const path = require('path'); -const { createTempDir, cleanup } = require('./helpers.cjs'); const { convertClaudeCommandToClaudeSkill, - convertClaudeToGeminiMarkdown, - install, } = require('../bin/install.js'); // ─── (b) Qwen Code: priority ordering ─────────────────────────────────────── @@ -6826,147 +6234,6 @@ describe('#778 (b) Qwen skills priority', () => { }); }); -// ─── (c) Gemini: {{args}} interpolation ───────────────────────────────────── - -describe('#778 (c) Gemini {{args}} interpolation', () => { - const cmd = (body) => - ['---', 'name: gsd:demo', 'description: Demo', '---', '', body].join('\n'); - - test('maps $ARGUMENTS to {{args}} in the TOML prompt', () => { - const out = convertClaudeToGeminiMarkdown( - cmd('Operate on $ARGUMENTS now.'), - { isCommand: true, commandName: 'demo' } - ); - assert.ok(out.includes('{{args}}'), '{{args}} present'); - assert.ok(!out.includes('$ARGUMENTS'), 'literal $ARGUMENTS removed'); - assert.ok(out.startsWith('description =') || out.includes('prompt ='), 'TOML shape'); - }); - - test('command without $ARGUMENTS gets no injected {{args}}', () => { - const out = convertClaudeToGeminiMarkdown( - cmd('No arguments referenced here.'), - { isCommand: true, commandName: 'demo' } - ); - assert.ok(!out.includes('{{args}}'), 'no spurious {{args}}'); - }); - - test('non-command Gemini content is not TOML-converted and keeps $ARGUMENTS', () => { - const out = convertClaudeToGeminiMarkdown( - cmd('Reference $ARGUMENTS.'), - { isCommand: false } - ); - // isCommand:false keeps markdown — no TOML wrap and no {{args}} mapping - // (the $ARGUMENTS→{{args}} translation is scoped to the TOML command path). - assert.ok(!out.startsWith('prompt ='), 'not wrapped as TOML prompt'); - assert.ok(out.includes('$ARGUMENTS'), '$ARGUMENTS left intact for non-command content'); - assert.ok(!out.includes('{{args}}'), 'no {{args}} injected outside the command path'); - }); -}); - -// ─── (c) Gemini: end-to-end install wiring ────────────────────────────────── -// Proves the install path derives the per-command name from the file stem so a -// regression in the call-site wiring (not just the converter) is caught. - -describe('#778 (c) Gemini install wiring (end-to-end)', () => { - let tmpDir; - let tmpHome; - let prevCwd; - let prevHome; - let prevUserprofile; - - beforeEach(() => { - tmpDir = createTempDir('gsd-enh778-gem-'); - tmpHome = createTempDir('gsd-enh778-home-'); - prevCwd = process.cwd(); - prevHome = process.env.HOME; - prevUserprofile = process.env.USERPROFILE; - process.chdir(tmpDir); - // Isolate HOME so a real ~/.gemini/commands/gsd/ doesn't trigger the #3037 - // local-install conflict-skip path. - process.env.HOME = tmpHome; - process.env.USERPROFILE = tmpHome; - }); - - afterEach(() => { - process.chdir(prevCwd); - if (prevHome === undefined) delete process.env.HOME; else process.env.HOME = prevHome; - if (prevUserprofile === undefined) delete process.env.USERPROFILE; - else process.env.USERPROFILE = prevUserprofile; - cleanup(tmpDir); - cleanup(tmpHome); - }); - - test('installed progress.toml carries the !{} block; arg-bearing commands get {{args}}', () => { - const oldLog = console.log; - console.log = () => {}; - try { - install(false, 'gemini'); - } finally { - console.log = oldLog; - } - - const commandsDir = path.join(tmpDir, '.gemini', 'commands', 'gsd'); - const progressToml = path.join(commandsDir, 'progress.toml'); - assert.ok(fs.existsSync(progressToml), 'progress.toml installed'); - const progress = fs.readFileSync(progressToml, 'utf8'); - // Proves commandName was derived as 'progress' from the file stem. - assert.ok( - progress.includes('!{cat .planning/STATE.md 2>/dev/null}'), - 'progress.toml has the live-state shell block' - ); - - // A non-situational command must NOT receive the shell block. - const helpToml = path.join(commandsDir, 'help.toml'); - if (fs.existsSync(helpToml)) { - assert.ok(!fs.readFileSync(helpToml, 'utf8').includes('!{'), 'help.toml has no shell block'); - } - - // At least one installed command must use {{args}} and none may retain a - // literal $ARGUMENTS (every command body's $ARGUMENTS is translated). - const tomls = fs.readdirSync(commandsDir).filter((f) => f.endsWith('.toml')); - const withArgs = tomls.filter((f) => - fs.readFileSync(path.join(commandsDir, f), 'utf8').includes('{{args}}')); - const withLiteral = tomls.filter((f) => - fs.readFileSync(path.join(commandsDir, f), 'utf8').includes('$ARGUMENTS')); - assert.ok(withArgs.length > 0, 'at least one installed command interpolates {{args}}'); - assert.equal(withLiteral.length, 0, 'no installed command retains literal $ARGUMENTS'); - }); -}); - -// ─── (c) Gemini: !{...} live-state injection (progress) ───────────────────── - -describe('#778 (c) Gemini !{} live-state injection', () => { - const cmd = (name) => - ['---', `name: gsd:${name}`, `description: ${name}`, '---', '', 'Workflow body.'].join('\n'); - - test('progress command injects a fixed !{cat .planning/STATE.md} block', () => { - const out = convertClaudeToGeminiMarkdown( - cmd('progress'), - { isCommand: true, commandName: 'progress' } - ); - assert.ok(out.includes('!{cat .planning/STATE.md'), 'STATE.md injection present'); - }); - - test('non-progress commands get no !{} shell block', () => { - const out = convertClaudeToGeminiMarkdown( - cmd('help'), - { isCommand: true, commandName: 'help' } - ); - assert.ok(!out.includes('!{'), 'no shell block for non-situational command'); - }); - - test('SECURITY: the !{} block interpolates NO user input ({{args}})', () => { - const out = convertClaudeToGeminiMarkdown( - ['---', 'name: gsd:progress', 'description: progress', '---', '', - 'Body uses $ARGUMENTS too.'].join('\n'), - { isCommand: true, commandName: 'progress' } - ); - const blocks = out.match(/!\{([^}]*)\}/g) || []; - assert.equal(blocks.length, 1, 'exactly one shell block'); - assert.ok(!/\{\{args\}\}/.test(blocks[0]), 'no {{args}} inside the shell block'); - assert.ok(/^!\{cat \.planning\/STATE\.md/.test(blocks[0]), 'fixed cat command only'); - }); -}); }); } @@ -7348,13 +6615,15 @@ describe('#769/#921/#1319 Claude global install: spawning-orchestrator SKILL.md * * Verifies: * 1. Claude global install injects `effort:` into agent .md frontmatter. - * 2. Gemini global install does NOT inject `effort:` (Gemini-safe .md). - * 3. Codex inherited-model installs omit `model_reasoning_effort` so model + * 2. Codex inherited-model installs omit `model_reasoning_effort` so model * and effort are not partially pinned (#838). - * 4. Config-driven proof: effort.agent_overrides wins over tier defaults + * 3. Config-driven proof: effort.agent_overrides wins over tier defaults * for Claude .md and for Codex .toml when runtime:"codex" pins a model. - * 5. Source agents/gsd-planner.md has NO effort: key (injection is - * install-only, source stays Gemini-safe). + * 4. Source agents/gsd-planner.md has NO effort: key (injection is + * install-only, source markdown carries no effort: key). + * + * #1928: the gemini runtime (and its "Gemini install does NOT inject effort:" + * coverage) was removed — Google sunset Gemini CLI 2026-06-18. */ 'use strict'; @@ -7393,7 +6662,6 @@ function readFrontmatter(mdPath) { * * Env-var redirection: * claude → CLAUDE_CONFIG_DIR - * gemini → GEMINI_CONFIG_DIR * codex → CODEX_HOME * * HOME is also redirected to an isolated temp dir for the duration of the @@ -7416,7 +6684,6 @@ function readFrontmatter(mdPath) { function runGlobalInstall(runtime, tmpHome) { const envVarMap = { claude: 'CLAUDE_CONFIG_DIR', - gemini: 'GEMINI_CONFIG_DIR', codex: 'CODEX_HOME', }; const envVar = envVarMap[runtime]; @@ -7502,37 +6769,6 @@ describe('#443 Claude install: effort: injected into frontmatter', () => { }); }); -// ─── describe 2: Gemini install does NOT inject effort: ────────────────────── - -describe('#443 Gemini install: effort: absent (Gemini-safe)', () => { - let tmpDir; - let geminiHome; - - beforeEach(() => { - tmpDir = makeTmpDir('gsd-443-gemini-'); - geminiHome = path.join(tmpDir, 'gemini-home'); - fs.mkdirSync(geminiHome, { recursive: true }); - }); - - afterEach(() => { - cleanup(tmpDir); - }); - - test('gsd-planner.md does NOT contain effort: (Gemini install)', () => { - runGlobalInstall('gemini', geminiHome); - const fm = readFrontmatter(path.join(geminiHome, 'agents', 'gsd-planner.md')); - assert.doesNotMatch(fm, /^effort:/m, - `gsd-planner (Gemini) frontmatter must NOT have effort:\nActual:\n${fm}`); - }); - - test('gsd-executor.md does NOT contain effort: (Gemini install)', () => { - runGlobalInstall('gemini', geminiHome); - const fm = readFrontmatter(path.join(geminiHome, 'agents', 'gsd-executor.md')); - assert.doesNotMatch(fm, /^effort:/m, - `gsd-executor (Gemini) frontmatter must NOT have effort:\nActual:\n${fm}`); - }); -}); - // ─── describe 3: Codex inherited-model install omits model_reasoning_effort ── describe('#838 Codex install: inherited model omits model_reasoning_effort', () => { @@ -7799,7 +7035,6 @@ describe('getDirName (relocated to runtime-name-policy)', () => { claude: '.claude', copilot: '.github', opencode: '.opencode', - gemini: '.gemini', kilo: '.kilo', codex: '.codex', antigravity: '.agents', @@ -10486,207 +9721,6 @@ describe('resolveRuntimeArtifactLayout — cline scope-aware (Fix 2)', () => { } -// ──────────────────────────────────────────────────────────────────────── -// Folded from tests/bug-3037-gemini-duplicate-commands.test.cjs — consolidation epic #1969 (B1 #1970) -// ──────────────────────────────────────────────────────────────────────── -{ - const { describe: __foldDescribe } = require('node:test'); - __foldDescribe("folded:bug-3037-gemini-duplicate-commands (consolidation epic #1969 B1 #1970)", () => { -/** - * Bug #3037: Gemini global+local install creates duplicate /gsd:* commands - * across user (HOME/.gemini/) and workspace (PROJECT/.gemini/) scopes. - * - * Reproduction (from issue body): - * 1. install --gemini --global with HOME=tmpHome - * 2. cd tmpProject; install --gemini --local - * → both ~/.gemini/commands/gsd/ and PROJECT/.gemini/commands/gsd/ contain - * 65 overlapping command filenames. - * → Gemini conflict detection renames every overlapping command to - * /workspace.gsd:* and /user.gsd:*, breaking the documented /gsd:* - * namespace. - * - * Fix: when the local Gemini install detects the user-scope GSD command - * directory already exists with managed-shape content, skip the local copy - * and emit a clear warning explaining the conflict avoidance. - * - * Tests assert on the post-install filesystem shape and capture the skip - * warning so the full test log remains warning-clean. - */ - -'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('node:fs'); -const path = require('node:path'); -const { createTempDir, cleanup, captureConsole } = require('./helpers.cjs'); - -const { install } = require('../bin/install.js'); - -describe('bug #3037: Gemini global+local install must not create duplicate command scopes', () => { - let tmpHome; - let tmpProject; - let originalHome; - let originalUserprofile; - let originalCwd; - - beforeEach(() => { - tmpHome = createTempDir('gsd-3037-home-'); - tmpProject = createTempDir('gsd-3037-work-'); - originalHome = process.env.HOME; - originalUserprofile = process.env.USERPROFILE; - originalCwd = process.cwd(); - // Point HOME at the temp dir so install(true, 'gemini') writes to - // tmpHome/.gemini, not the developer's real home. - process.env.HOME = tmpHome; - process.env.USERPROFILE = tmpHome; - }); - - afterEach(() => { - if (originalHome === undefined) delete process.env.HOME; - else process.env.HOME = originalHome; - // CR #3041: also restore USERPROFILE so the temp HOME doesn't leak - // into later tests and create order-dependent failures on Windows - // or any code path that reads USERPROFILE. - if (originalUserprofile === undefined) delete process.env.USERPROFILE; - else process.env.USERPROFILE = originalUserprofile; - process.chdir(originalCwd); - cleanup(tmpHome); - cleanup(tmpProject); - }); - - function listCommandFiles(geminiCommandsRoot) { - if (!fs.existsSync(geminiCommandsRoot)) return []; - const out = []; - function walk(dir) { - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const full = path.join(dir, entry.name); - if (entry.isDirectory()) walk(full); - else if (entry.isFile()) out.push(path.relative(geminiCommandsRoot, full)); - } - } - walk(geminiCommandsRoot); - return out.sort(); - } - - function runInstall(...args) { - return captureConsole(() => install(...args)); - } - - test('global install populates HOME/.gemini/commands/gsd', () => { - runInstall(true, 'gemini'); - const globalCmds = path.join(tmpHome, '.gemini', 'commands', 'gsd'); - const files = listCommandFiles(globalCmds); - assert.ok( - files.length > 0, - 'global install must populate HOME/.gemini/commands/gsd' - ); - }); - - test('local install after global does NOT populate PROJECT/.gemini/commands/gsd (avoids /gsd:* namespace conflict)', () => { - // Step 1: global install - runInstall(true, 'gemini'); - const globalCmds = path.join(tmpHome, '.gemini', 'commands', 'gsd'); - const globalFiles = listCommandFiles(globalCmds); - assert.ok(globalFiles.length > 0, 'precondition: global install must succeed'); - - // Step 2: local install in a temp project - process.chdir(tmpProject); - const { stdout } = runInstall(false, 'gemini'); - assert.match( - stdout, - /Skipping commands\/gsd\/ for local install/, - 'local install must explain why it skips duplicate Gemini commands' - ); - - // Assertion: the local commands/gsd/ directory must NOT exist (or must - // be empty) so Gemini's conflict detection has nothing to rename. The - // fix may either skip the directory entirely (preferred — no leftover - // file system noise) or create an empty directory (acceptable but odd). - const localCmds = path.join(tmpProject, '.gemini', 'commands', 'gsd'); - const localFiles = listCommandFiles(localCmds); - assert.equal( - localFiles.length, - 0, - `local install must skip commands/gsd/ when global already exists; ` + - `found ${localFiles.length} duplicate command file(s) at ${localCmds}` - ); - }); - - test('local install with NO existing global GSD does still populate PROJECT/.gemini/commands/gsd', () => { - // No global install first — local should proceed normally so users who - // only ever run --local still get GSD commands in their project. - process.chdir(tmpProject); - runInstall(false, 'gemini'); - - const localCmds = path.join(tmpProject, '.gemini', 'commands', 'gsd'); - const localFiles = listCommandFiles(localCmds); - assert.ok( - localFiles.length > 0, - `local-only install must populate PROJECT/.gemini/commands/gsd; ` + - `found ${localFiles.length} files at ${localCmds}` - ); - }); - - test('local install when HOME has hand-dropped overrides UNDER commands/gsd/ (but no full GSD) still populates locally', () => { - // CR #3041 regression: the previous detection was - // `fs.readdirSync(homeGeminiGsd).length > 0` which would skip the - // local install for a user who manually dropped a single override - // command at ~/.gemini/commands/gsd/.toml without ever - // running --gemini --global. The fix narrows detection to require - // at least 3 canonical GSD command files (help.toml, progress.toml, - // new-project.toml) — a marker that's structurally impossible to - // produce by accident. - const homeGsdDir = path.join(tmpHome, '.gemini', 'commands', 'gsd'); - fs.mkdirSync(homeGsdDir, { recursive: true }); - fs.writeFileSync( - path.join(homeGsdDir, 'my-override.toml'), - 'description = "user override"\nprompt = "..."\n' - ); - - process.chdir(tmpProject); - runInstall(false, 'gemini'); - - const localCmds = path.join(tmpProject, '.gemini', 'commands', 'gsd'); - const localFiles = listCommandFiles(localCmds); - assert.ok( - localFiles.length > 0, - `local install must proceed when HOME/.gemini/commands/gsd contains ` + - `only user overrides (not the full GSD canary set); ` + - `found ${localFiles.length} files at ${localCmds}` - ); - }); - - test('local install when HOME/.gemini exists but commands/gsd is absent (non-GSD Gemini user) still populates locally', () => { - // Simulate a user who has Gemini configured but never installed GSD - // globally. ~/.gemini/ exists with unrelated content; ~/.gemini/commands/ - // may or may not exist with non-gsd subdirectories. Local install must - // still proceed because no GSD-managed user-scope directory is present. - fs.mkdirSync(path.join(tmpHome, '.gemini', 'commands', 'someone-else'), { - recursive: true, - }); - fs.writeFileSync( - path.join(tmpHome, '.gemini', 'commands', 'someone-else', 'foo.toml'), - 'description = "user command"\nprompt = "..."\n' - ); - - process.chdir(tmpProject); - runInstall(false, 'gemini'); - - const localCmds = path.join(tmpProject, '.gemini', 'commands', 'gsd'); - const localFiles = listCommandFiles(localCmds); - assert.ok( - localFiles.length > 0, - `local install must proceed when no GSD-managed user-scope directory ` + - `exists, even if other Gemini commands are present at the user scope` - ); - }); -}); - }); -} - // ──────────────────────────────────────────────────────────────────────── // Folded from tests/enh-789-codebuddy-commands.test.cjs — consolidation epic #1969 (B1 #1970) @@ -11444,12 +10478,8 @@ describe('skill frontmatter name parity (#2643 / #2808)', () => { /** * GSD Tools Tests — #778 cross-runtime command enrichment. * - * Two independently-verified, additive sub-features: - * (b) Qwen Code skills: numeric `priority` field (higher sorts earlier in the - * /skills TUI listing per the Qwen skills spec). Scoped to runtime='qwen'. - * (c) Gemini custom-command TOML: $ARGUMENTS → {{args}} interpolation, and a - * fixed `!{cat .planning/STATE.md}` live-state injection on the - * situational `progress` command (injection-safe — no interpolated input). + * Qwen Code skills: numeric `priority` field (higher sorts earlier in the + * /skills TUI listing per the Qwen skills spec). Scoped to runtime='qwen'. * * The OpenCode sub-feature (per-command model/agent/subtask/variant) is * intentionally NOT implemented — see PR description: `model` reintroduces the @@ -11457,21 +10487,21 @@ describe('skill frontmatter name parity (#2643 / #2808)', () => { * `subtask`/`agent` change execution semantics for GSD's interactive commands, * and `variant` is not in the OpenCode command schema. * + * #1928: the Gemini custom-command TOML sub-feature ($ARGUMENTS → {{args}} + * interpolation, !{cat .planning/STATE.md} live-state injection) was removed + * along with the gemini runtime (Google sunset Gemini CLI 2026-06-18). + * convertClaudeToGeminiMarkdown no longer exists in bin/install.js. + * * Uses node:test and node:assert (NOT Jest). */ process.env.GSD_TEST_MODE = '1'; -const { test, describe, beforeEach, afterEach } = require('node:test'); +const { test, describe } = require('node:test'); const assert = require('node:assert/strict'); -const fs = require('fs'); -const path = require('path'); -const { createTempDir, cleanup } = require('./helpers.cjs'); const { convertClaudeCommandToClaudeSkill, - convertClaudeToGeminiMarkdown, - install, } = require('../bin/install.js'); // ─── (b) Qwen Code: priority ordering ─────────────────────────────────────── @@ -11523,147 +10553,6 @@ describe('#778 (b) Qwen skills priority', () => { }); }); -// ─── (c) Gemini: {{args}} interpolation ───────────────────────────────────── - -describe('#778 (c) Gemini {{args}} interpolation', () => { - const cmd = (body) => - ['---', 'name: gsd:demo', 'description: Demo', '---', '', body].join('\n'); - - test('maps $ARGUMENTS to {{args}} in the TOML prompt', () => { - const out = convertClaudeToGeminiMarkdown( - cmd('Operate on $ARGUMENTS now.'), - { isCommand: true, commandName: 'demo' } - ); - assert.ok(out.includes('{{args}}'), '{{args}} present'); - assert.ok(!out.includes('$ARGUMENTS'), 'literal $ARGUMENTS removed'); - assert.ok(out.startsWith('description =') || out.includes('prompt ='), 'TOML shape'); - }); - - test('command without $ARGUMENTS gets no injected {{args}}', () => { - const out = convertClaudeToGeminiMarkdown( - cmd('No arguments referenced here.'), - { isCommand: true, commandName: 'demo' } - ); - assert.ok(!out.includes('{{args}}'), 'no spurious {{args}}'); - }); - - test('non-command Gemini content is not TOML-converted and keeps $ARGUMENTS', () => { - const out = convertClaudeToGeminiMarkdown( - cmd('Reference $ARGUMENTS.'), - { isCommand: false } - ); - // isCommand:false keeps markdown — no TOML wrap and no {{args}} mapping - // (the $ARGUMENTS→{{args}} translation is scoped to the TOML command path). - assert.ok(!out.startsWith('prompt ='), 'not wrapped as TOML prompt'); - assert.ok(out.includes('$ARGUMENTS'), '$ARGUMENTS left intact for non-command content'); - assert.ok(!out.includes('{{args}}'), 'no {{args}} injected outside the command path'); - }); -}); - -// ─── (c) Gemini: end-to-end install wiring ────────────────────────────────── -// Proves the install path derives the per-command name from the file stem so a -// regression in the call-site wiring (not just the converter) is caught. - -describe('#778 (c) Gemini install wiring (end-to-end)', () => { - let tmpDir; - let tmpHome; - let prevCwd; - let prevHome; - let prevUserprofile; - - beforeEach(() => { - tmpDir = createTempDir('gsd-enh778-gem-'); - tmpHome = createTempDir('gsd-enh778-home-'); - prevCwd = process.cwd(); - prevHome = process.env.HOME; - prevUserprofile = process.env.USERPROFILE; - process.chdir(tmpDir); - // Isolate HOME so a real ~/.gemini/commands/gsd/ doesn't trigger the #3037 - // local-install conflict-skip path. - process.env.HOME = tmpHome; - process.env.USERPROFILE = tmpHome; - }); - - afterEach(() => { - process.chdir(prevCwd); - if (prevHome === undefined) delete process.env.HOME; else process.env.HOME = prevHome; - if (prevUserprofile === undefined) delete process.env.USERPROFILE; - else process.env.USERPROFILE = prevUserprofile; - cleanup(tmpDir); - cleanup(tmpHome); - }); - - test('installed progress.toml carries the !{} block; arg-bearing commands get {{args}}', () => { - const oldLog = console.log; - console.log = () => {}; - try { - install(false, 'gemini'); - } finally { - console.log = oldLog; - } - - const commandsDir = path.join(tmpDir, '.gemini', 'commands', 'gsd'); - const progressToml = path.join(commandsDir, 'progress.toml'); - assert.ok(fs.existsSync(progressToml), 'progress.toml installed'); - const progress = fs.readFileSync(progressToml, 'utf8'); - // Proves commandName was derived as 'progress' from the file stem. - assert.ok( - progress.includes('!{cat .planning/STATE.md 2>/dev/null}'), - 'progress.toml has the live-state shell block' - ); - - // A non-situational command must NOT receive the shell block. - const helpToml = path.join(commandsDir, 'help.toml'); - if (fs.existsSync(helpToml)) { - assert.ok(!fs.readFileSync(helpToml, 'utf8').includes('!{'), 'help.toml has no shell block'); - } - - // At least one installed command must use {{args}} and none may retain a - // literal $ARGUMENTS (every command body's $ARGUMENTS is translated). - const tomls = fs.readdirSync(commandsDir).filter((f) => f.endsWith('.toml')); - const withArgs = tomls.filter((f) => - fs.readFileSync(path.join(commandsDir, f), 'utf8').includes('{{args}}')); - const withLiteral = tomls.filter((f) => - fs.readFileSync(path.join(commandsDir, f), 'utf8').includes('$ARGUMENTS')); - assert.ok(withArgs.length > 0, 'at least one installed command interpolates {{args}}'); - assert.equal(withLiteral.length, 0, 'no installed command retains literal $ARGUMENTS'); - }); -}); - -// ─── (c) Gemini: !{...} live-state injection (progress) ───────────────────── - -describe('#778 (c) Gemini !{} live-state injection', () => { - const cmd = (name) => - ['---', `name: gsd:${name}`, `description: ${name}`, '---', '', 'Workflow body.'].join('\n'); - - test('progress command injects a fixed !{cat .planning/STATE.md} block', () => { - const out = convertClaudeToGeminiMarkdown( - cmd('progress'), - { isCommand: true, commandName: 'progress' } - ); - assert.ok(out.includes('!{cat .planning/STATE.md'), 'STATE.md injection present'); - }); - - test('non-progress commands get no !{} shell block', () => { - const out = convertClaudeToGeminiMarkdown( - cmd('help'), - { isCommand: true, commandName: 'help' } - ); - assert.ok(!out.includes('!{'), 'no shell block for non-situational command'); - }); - - test('SECURITY: the !{} block interpolates NO user input ({{args}})', () => { - const out = convertClaudeToGeminiMarkdown( - ['---', 'name: gsd:progress', 'description: progress', '---', '', - 'Body uses $ARGUMENTS too.'].join('\n'), - { isCommand: true, commandName: 'progress' } - ); - const blocks = out.match(/!\{([^}]*)\}/g) || []; - assert.equal(blocks.length, 1, 'exactly one shell block'); - assert.ok(!/\{\{args\}\}/.test(blocks[0]), 'no {{args}} inside the shell block'); - assert.ok(/^!\{cat \.planning\/STATE\.md/.test(blocks[0]), 'fixed cat command only'); - }); -}); }); } @@ -12045,13 +10934,15 @@ describe('#769/#921/#1319 Claude global install: spawning-orchestrator SKILL.md * * Verifies: * 1. Claude global install injects `effort:` into agent .md frontmatter. - * 2. Gemini global install does NOT inject `effort:` (Gemini-safe .md). - * 3. Codex inherited-model installs omit `model_reasoning_effort` so model + * 2. Codex inherited-model installs omit `model_reasoning_effort` so model * and effort are not partially pinned (#838). - * 4. Config-driven proof: effort.agent_overrides wins over tier defaults + * 3. Config-driven proof: effort.agent_overrides wins over tier defaults * for Claude .md and for Codex .toml when runtime:"codex" pins a model. - * 5. Source agents/gsd-planner.md has NO effort: key (injection is - * install-only, source stays Gemini-safe). + * 4. Source agents/gsd-planner.md has NO effort: key (injection is + * install-only, source markdown carries no effort: key). + * + * #1928: the gemini runtime (and its "Gemini install does NOT inject effort:" + * coverage) was removed — Google sunset Gemini CLI 2026-06-18. */ 'use strict'; @@ -12090,7 +10981,6 @@ function readFrontmatter(mdPath) { * * Env-var redirection: * claude → CLAUDE_CONFIG_DIR - * gemini → GEMINI_CONFIG_DIR * codex → CODEX_HOME * * HOME is also redirected to an isolated temp dir for the duration of the @@ -12113,7 +11003,6 @@ function readFrontmatter(mdPath) { function runGlobalInstall(runtime, tmpHome) { const envVarMap = { claude: 'CLAUDE_CONFIG_DIR', - gemini: 'GEMINI_CONFIG_DIR', codex: 'CODEX_HOME', }; const envVar = envVarMap[runtime]; @@ -12199,37 +11088,6 @@ describe('#443 Claude install: effort: injected into frontmatter', () => { }); }); -// ─── describe 2: Gemini install does NOT inject effort: ────────────────────── - -describe('#443 Gemini install: effort: absent (Gemini-safe)', () => { - let tmpDir; - let geminiHome; - - beforeEach(() => { - tmpDir = makeTmpDir('gsd-443-gemini-'); - geminiHome = path.join(tmpDir, 'gemini-home'); - fs.mkdirSync(geminiHome, { recursive: true }); - }); - - afterEach(() => { - cleanup(tmpDir); - }); - - test('gsd-planner.md does NOT contain effort: (Gemini install)', () => { - runGlobalInstall('gemini', geminiHome); - const fm = readFrontmatter(path.join(geminiHome, 'agents', 'gsd-planner.md')); - assert.doesNotMatch(fm, /^effort:/m, - `gsd-planner (Gemini) frontmatter must NOT have effort:\nActual:\n${fm}`); - }); - - test('gsd-executor.md does NOT contain effort: (Gemini install)', () => { - runGlobalInstall('gemini', geminiHome); - const fm = readFrontmatter(path.join(geminiHome, 'agents', 'gsd-executor.md')); - assert.doesNotMatch(fm, /^effort:/m, - `gsd-executor (Gemini) frontmatter must NOT have effort:\nActual:\n${fm}`); - }); -}); - // ─── describe 3: Codex inherited-model install omits model_reasoning_effort ── describe('#838 Codex install: inherited model omits model_reasoning_effort', () => { @@ -12496,7 +11354,6 @@ describe('getDirName (relocated to runtime-name-policy)', () => { claude: '.claude', copilot: '.github', opencode: '.opencode', - gemini: '.gemini', kilo: '.kilo', codex: '.codex', antigravity: '.agents', diff --git a/tests/install.test.cjs b/tests/install.test.cjs index 7db338019..c9560061b 100644 --- a/tests/install.test.cjs +++ b/tests/install.test.cjs @@ -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) { diff --git a/tests/issue-2517-runtime-aware-profiles.test.cjs b/tests/issue-2517-runtime-aware-profiles.test.cjs index 783324fec..0932b1e9f 100644 --- a/tests/issue-2517-runtime-aware-profiles.test.cjs +++ b/tests/issue-2517-runtime-aware-profiles.test.cjs @@ -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', diff --git a/tests/issue-498-update-context.test.cjs b/tests/issue-498-update-context.test.cjs index 462c3b70d..1ff5f5c37 100644 --- a/tests/issue-498-update-context.test.cjs +++ b/tests/issue-498-update-context.test.cjs @@ -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', () => { diff --git a/tests/issue-775-gemini-extension.test.cjs b/tests/issue-775-gemini-extension.test.cjs deleted file mode 100644 index 33d5caf38..000000000 --- a/tests/issue-775-gemini-extension.test.cjs +++ /dev/null @@ -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 `) - * 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` - ); - } - }); -}); diff --git a/tests/multi-runtime-select.test.cjs b/tests/multi-runtime-select.test.cjs index 66eaa1050..51d02bbac 100644 --- a/tests/multi-runtime-select.test.cjs +++ b/tests/multi-runtime-select.test.cjs @@ -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', () => { diff --git a/tests/non-claude-runtimes-registry-derivation.test.cjs b/tests/non-claude-runtimes-registry-derivation.test.cjs index 50293358b..5fbd45bc1 100644 --- a/tests/non-claude-runtimes-registry-derivation.test.cjs +++ b/tests/non-claude-runtimes-registry-derivation.test.cjs @@ -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', ]; diff --git a/tests/runtime-artifact-layout-descriptor-drive.test.cjs b/tests/runtime-artifact-layout-descriptor-drive.test.cjs index 14bf94450..e31e064fe 100644 --- a/tests/runtime-artifact-layout-descriptor-drive.test.cjs +++ b/tests/runtime-artifact-layout-descriptor-drive.test.cjs @@ -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', ]; diff --git a/tests/runtime-artifact-layout-surface.test.cjs b/tests/runtime-artifact-layout-surface.test.cjs index 5c124618f..7c0a89fab 100644 --- a/tests/runtime-artifact-layout-surface.test.cjs +++ b/tests/runtime-artifact-layout-surface.test.cjs @@ -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. diff --git a/tests/runtime-artifact-layout.test.cjs b/tests/runtime-artifact-layout.test.cjs index 17ce90abe..c8c16bef5 100644 --- a/tests/runtime-artifact-layout.test.cjs +++ b/tests/runtime-artifact-layout.test.cjs @@ -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'); diff --git a/tests/runtime-config-adapter-registry.test.cjs b/tests/runtime-config-adapter-registry.test.cjs index c6327b164..b0fbbc31f 100644 --- a/tests/runtime-config-adapter-registry.test.cjs +++ b/tests/runtime-config-adapter-registry.test.cjs @@ -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) { diff --git a/tests/runtime-converters.test.cjs b/tests/runtime-converters.test.cjs index 52dea3e1d..20cdcb393 100644 --- a/tests/runtime-converters.test.cjs +++ b/tests/runtime-converters.test.cjs @@ -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 ---- - - -Use \${PHASE} in shell examples. -`; - - 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 ---- - - -Coordinate debugger agents. -Offer choices via AskUserQuestion when user input is needed. -`; - - 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 ---- - - -Plan the phase. -`; - - 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 Plan the phase.`; - 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'); }); }); diff --git a/tests/runtime-flags.test.cjs b/tests/runtime-flags.test.cjs index a03a6e6f5..520ff07c7 100644 --- a/tests/runtime-flags.test.cjs +++ b/tests/runtime-flags.test.cjs @@ -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'); }); diff --git a/tests/runtime-homes-descriptor-drive.test.cjs b/tests/runtime-homes-descriptor-drive.test.cjs index 41555f415..96f551119 100644 --- a/tests/runtime-homes-descriptor-drive.test.cjs +++ b/tests/runtime-homes-descriptor-drive.test.cjs @@ -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')], diff --git a/tests/runtime-label-policy.test.cjs b/tests/runtime-label-policy.test.cjs index 953885eb3..0a7cfe908 100644 --- a/tests/runtime-label-policy.test.cjs +++ b/tests/runtime-label-policy.test.cjs @@ -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}`); } diff --git a/tests/runtime-name-policy.test.cjs b/tests/runtime-name-policy.test.cjs index 0a21e8e82..2fb0e5f43 100644 --- a/tests/runtime-name-policy.test.cjs +++ b/tests/runtime-name-policy.test.cjs @@ -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'); }); diff --git a/tests/shell-command-projection-dispatch.test.cjs b/tests/shell-command-projection-dispatch.test.cjs index dfdd7665f..0edcb38ab 100644 --- a/tests/shell-command-projection-dispatch.test.cjs +++ b/tests/shell-command-projection-dispatch.test.cjs @@ -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', () => { diff --git a/tests/slash-command-namespace.test.cjs b/tests/slash-command-namespace.test.cjs index 265dd0bcb..acedc6b30 100644 --- a/tests/slash-command-namespace.test.cjs +++ b/tests/slash-command-namespace.test.cjs @@ -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:` 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: 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: 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(', ')}`, - ); - }); - }); }); }); } diff --git a/tests/workflow-maintainer-skip.test.cjs b/tests/workflow-maintainer-skip.test.cjs index acf094230..fef6b6e00 100644 --- a/tests/workflow-maintainer-skip.test.cjs +++ b/tests/workflow-maintainer-skip.test.cjs @@ -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( diff --git a/tests/workflow-size-baseline.json b/tests/workflow-size-baseline.json index 73e71df7e..6a0fab970 100644 --- a/tests/workflow-size-baseline.json +++ b/tests/workflow-size-baseline.json @@ -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