@opengsd/gsd-core 1.7.0-rc.1 → 1.7.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +20 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +711 -0
- package/GEMINI.md +25 -23
- package/README.md +3 -3
- package/agents/gsd-advisor-researcher.md +3 -1
- package/agents/gsd-ai-researcher.md +2 -2
- package/agents/gsd-assumptions-analyzer.md +2 -0
- package/agents/gsd-code-fixer.md +3 -1
- package/agents/gsd-code-reviewer.md +2 -0
- package/agents/gsd-codebase-mapper.md +2 -0
- package/agents/gsd-debug-session-manager.md +1 -1
- package/agents/gsd-debugger.md +3 -1
- package/agents/gsd-doc-writer.md +2 -0
- package/agents/gsd-domain-researcher.md +1 -1
- package/agents/gsd-eval-auditor.md +3 -1
- package/agents/gsd-executor.md +12 -9
- package/agents/gsd-integration-checker.md +2 -0
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +1 -1
- package/agents/gsd-nyquist-auditor.md +2 -0
- package/agents/gsd-phase-researcher.md +4 -2
- package/agents/gsd-plan-checker.md +3 -1
- package/agents/gsd-planner.md +4 -2
- package/agents/gsd-project-researcher.md +4 -2
- package/agents/gsd-research-synthesizer.md +3 -1
- package/agents/gsd-roadmapper.md +2 -0
- package/agents/gsd-security-auditor.md +2 -0
- package/agents/gsd-ui-auditor.md +2 -0
- package/agents/gsd-ui-checker.md +2 -0
- package/agents/gsd-ui-researcher.md +4 -2
- package/agents/gsd-verifier.md +6 -4
- package/bin/install.js +226 -459
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/mempalace-recall.md +9 -5
- package/commands/gsd/next.md +29 -0
- package/commands/gsd/ns-workflow.md +5 -2
- package/gsd-core/bin/ensure-runtime-build.cjs +246 -0
- package/gsd-core/bin/gsd-tools.cjs +43 -8
- package/gsd-core/bin/lib/capability-loader.cjs +27 -9
- package/gsd-core/bin/lib/capability-registry.cjs +209 -205
- package/gsd-core/bin/lib/capability-source.cjs +22 -7
- package/gsd-core/bin/lib/capability-validator.cjs +24 -2
- package/gsd-core/bin/lib/check-command-router.cjs +111 -1
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/commands.cjs +2 -1
- package/gsd-core/bin/lib/config.cjs +19 -1
- package/gsd-core/bin/lib/core-utils.cjs +41 -0
- package/gsd-core/bin/lib/external-job.cjs +287 -0
- package/gsd-core/bin/lib/frontmatter.cjs +53 -6
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +151 -0
- package/gsd-core/bin/lib/graphify.cjs +41 -9
- package/gsd-core/bin/lib/handshake-serialized.cjs +70 -0
- package/gsd-core/bin/lib/host-integration-sdk.cjs +53 -0
- package/gsd-core/bin/lib/host-integration.cjs +61 -0
- package/gsd-core/bin/lib/init.cjs +34 -6
- package/gsd-core/bin/lib/milestone.cjs +56 -11
- package/gsd-core/bin/lib/phase-id.cjs +18 -0
- package/gsd-core/bin/lib/phase.cjs +51 -29
- package/gsd-core/bin/lib/phases-command-router.cjs +4 -3
- package/gsd-core/bin/lib/plan-scan.cjs +9 -1
- package/gsd-core/bin/lib/probe-core.cjs +44 -4
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +21 -19
- package/gsd-core/bin/lib/roadmap-command-router.cjs +3 -2
- package/gsd-core/bin/lib/roadmap-parser.cjs +21 -11
- package/gsd-core/bin/lib/roadmap.cjs +45 -22
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +18 -13
- package/gsd-core/bin/lib/runtime-name-policy.cjs +8 -10
- package/gsd-core/bin/lib/shell-command-projection.cjs +12 -8
- package/gsd-core/bin/lib/smart-entry.cjs +521 -0
- package/gsd-core/bin/lib/state-transition.cjs +15 -0
- package/gsd-core/bin/lib/state.cjs +27 -8
- package/gsd-core/bin/lib/surface.cjs +18 -9
- package/gsd-core/bin/lib/update-context.cjs +0 -5
- package/gsd-core/bin/lib/validate.cjs +2 -1
- package/gsd-core/bin/lib/verify.cjs +6 -4
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +12 -2
- package/gsd-core/bin/lib/workstream-inventory.cjs +28 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -13
- package/gsd-core/bin/shared/runtime-aliases.manifest.json +0 -5
- package/gsd-core/references/agent-skills-bootstrap.md +60 -0
- package/gsd-core/references/gates.md +1 -1
- package/gsd-core/references/model-profiles.md +27 -0
- package/gsd-core/references/planning-config.md +1 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +1 -1
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +1 -1
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +1 -1
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous.md +23 -25
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +1 -1
- package/gsd-core/workflows/code-review-fix.md +1 -1
- package/gsd-core/workflows/code-review.md +1 -1
- package/gsd-core/workflows/complete-milestone.md +7 -11
- package/gsd-core/workflows/debug.md +1 -1
- package/gsd-core/workflows/diagnose-issues.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/auto.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +2 -2
- package/gsd-core/workflows/do.md +1 -1
- package/gsd-core/workflows/docs-update.md +1 -1
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
- package/gsd-core/workflows/execute-phase.md +5 -6
- package/gsd-core/workflows/execute-plan.md +1 -1
- package/gsd-core/workflows/explore.md +1 -1
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/forensics.md +4 -4
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +1 -1
- package/gsd-core/workflows/help/modes/full.md +15 -1
- package/gsd-core/workflows/help/modes/topic.md +1 -0
- package/gsd-core/workflows/import.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +1 -1
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +1 -1
- package/gsd-core/workflows/map-codebase.md +1 -1
- package/gsd-core/workflows/milestone-summary.md +4 -4
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +7 -1
- package/gsd-core/workflows/new-project.md +1 -1
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-milestone-gaps.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +42 -0
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +102 -0
- package/gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md +23 -0
- package/gsd-core/workflows/plan-phase.md +12 -160
- package/gsd-core/workflows/plan-review-convergence.md +1 -1
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +1 -1
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick.md +1 -1
- package/gsd-core/workflows/reapply-patches.md +1 -1
- package/gsd-core/workflows/remove-phase.md +1 -1
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +1 -1
- package/gsd-core/workflows/review.md +50 -9
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/settings-advanced.md +13 -11
- package/gsd-core/workflows/settings-integrations.md +1 -1
- package/gsd-core/workflows/settings.md +1 -1
- package/gsd-core/workflows/ship.md +1 -1
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +120 -0
- package/gsd-core/workflows/spec-phase.md +8 -6
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/thread.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +1 -1
- package/gsd-core/workflows/ui-review.md +1 -1
- package/gsd-core/workflows/ultraplan-phase.md +1 -1
- package/gsd-core/workflows/update.md +3 -6
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +63 -6
- package/package.json +5 -5
- package/scripts/ci-test-scope.cjs +19 -1
- package/scripts/generate-package-identity.cjs +27 -2
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -148
- package/scripts/lint-regression-test-names.allowlist.json +1 -273
- package/scripts/lint-test-file-count.allowlist.json +7 -91
- package/scripts/research-profiles.cjs +6 -6
- package/scripts/run-tests.cjs +67 -4
- package/scripts/slurm-adapter.cjs +269 -0
- package/scripts/sync-manifest-versions.cjs +67 -14
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +9 -5
- package/skills/gsd-next/SKILL.md +30 -0
- package/skills/gsd-ns-workflow/SKILL.md +4 -1
- package/skills/gsd-review/SKILL.md +6 -0
- package/gemini-extension.json +0 -6
package/bin/install.js
CHANGED
|
@@ -374,6 +374,7 @@ const {
|
|
|
374
374
|
} = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'legacy-cleanup.cjs'));
|
|
375
375
|
const {
|
|
376
376
|
updateCacheFileName,
|
|
377
|
+
PACKAGE_NAME,
|
|
377
378
|
} = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'package-identity.cjs'));
|
|
378
379
|
|
|
379
380
|
// ADR-1239 Phase B: runtime-artifact install cluster extracted to install-engine.cjs.
|
|
@@ -433,7 +434,7 @@ if (hasMinimal && _profileArgRaw) {
|
|
|
433
434
|
|
|
434
435
|
function selectRuntimesFromArgs(runtimeArgs) {
|
|
435
436
|
if (runtimeArgs.includes('--all')) {
|
|
436
|
-
return ['claude', 'kimi', 'kilo', 'opencode', '
|
|
437
|
+
return ['claude', 'kimi', 'kilo', 'opencode', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline'];
|
|
437
438
|
}
|
|
438
439
|
if (runtimeArgs.includes('--both')) {
|
|
439
440
|
return ['claude', 'opencode'];
|
|
@@ -442,7 +443,6 @@ function selectRuntimesFromArgs(runtimeArgs) {
|
|
|
442
443
|
const selected = [];
|
|
443
444
|
if (runtimeArgs.includes('--claude')) selected.push('claude');
|
|
444
445
|
if (runtimeArgs.includes('--opencode')) selected.push('opencode');
|
|
445
|
-
if (runtimeArgs.includes('--gemini')) selected.push('gemini');
|
|
446
446
|
if (runtimeArgs.includes('--kilo')) selected.push('kilo');
|
|
447
447
|
if (runtimeArgs.includes('--codex')) selected.push('codex');
|
|
448
448
|
if (runtimeArgs.includes('--copilot')) selected.push('copilot');
|
|
@@ -462,6 +462,37 @@ function selectRuntimesFromArgs(runtimeArgs) {
|
|
|
462
462
|
// Runtime selection - can be set by flags or interactive prompt
|
|
463
463
|
let selectedRuntimes = selectRuntimesFromArgs(args);
|
|
464
464
|
|
|
465
|
+
// #1928: Google sunset Gemini CLI on 2026-06-18; Antigravity CLI is its
|
|
466
|
+
// official successor. `--gemini` is no longer a valid runtime selector —
|
|
467
|
+
// selectRuntimesFromArgs above no longer recognizes it, so it never lands in
|
|
468
|
+
// selectedRuntimes. Print a one-time redirect notice, and — when `--gemini`
|
|
469
|
+
// was the ONLY runtime flag supplied (selectedRuntimes is empty) — exit
|
|
470
|
+
// deterministically rather than silently falling through to the "no runtime
|
|
471
|
+
// specified" defaults below (which would install Claude Code, surprising a
|
|
472
|
+
// user who explicitly asked for Gemini). Other flags (e.g. `--codex`) still
|
|
473
|
+
// parse and install normally alongside the notice.
|
|
474
|
+
if (args.includes('--gemini')) {
|
|
475
|
+
const wantsHelp = args.includes('--help') || args.includes('-h');
|
|
476
|
+
console.error('Gemini CLI was sunset by Google on 2026-06-18 and is no longer served for free/Pro/Ultra tiers.');
|
|
477
|
+
console.error('GSD now supports Antigravity CLI (the official successor). Re-run with: --antigravity');
|
|
478
|
+
if (hasUninstall) {
|
|
479
|
+
// The gemini runtime was removed (#1928), so there is no automated
|
|
480
|
+
// `--gemini --uninstall`. Guide manual cleanup and exit — do NOT fall
|
|
481
|
+
// through to the uninstall dispatch below, which defaults an empty runtime
|
|
482
|
+
// selection to 'claude' and would wrongly uninstall the user's Claude install.
|
|
483
|
+
console.error('The gemini runtime was removed, so `--gemini --uninstall` is no longer available.');
|
|
484
|
+
console.error('To remove a prior Gemini install, delete GSD files under your Gemini config dir');
|
|
485
|
+
console.error('(e.g. ~/.gemini/commands/gsd) and GSD hook entries in ~/.gemini/settings.json.');
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
// For `--gemini --help`, fall through so the usage block still prints. For a
|
|
489
|
+
// bare install attempt (no other runtime selected), exit rather than silently
|
|
490
|
+
// installing Claude.
|
|
491
|
+
if (!wantsHelp && selectedRuntimes.length === 0) {
|
|
492
|
+
process.exit(1);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
465
496
|
// WSL + Windows Node.js detection
|
|
466
497
|
// When Windows-native Node runs on WSL, os.homedir() and path.join() produce
|
|
467
498
|
// backslash paths that don't resolve correctly on the Linux filesystem.
|
|
@@ -502,7 +533,7 @@ Then re-run: npx ${pkg.name}@latest
|
|
|
502
533
|
/**
|
|
503
534
|
* Get the config directory path relative to home directory for a runtime
|
|
504
535
|
* Used for templating hooks that use path.join(homeDir, '<configDir>', ...)
|
|
505
|
-
* @param {string} runtime - 'claude', 'opencode', '
|
|
536
|
+
* @param {string} runtime - 'claude', 'opencode', 'codex', or 'copilot'
|
|
506
537
|
* @param {boolean} isGlobal - Whether this is a global install
|
|
507
538
|
*/
|
|
508
539
|
function getConfigDirFromHome(runtime, isGlobal) {
|
|
@@ -548,7 +579,7 @@ const banner = '\n' +
|
|
|
548
579
|
' GSD Core ' + dim + 'v' + pkg.version + reset + '\n' +
|
|
549
580
|
' Git. Ship. Done.\n' +
|
|
550
581
|
' A meta-prompting, context engineering and spec-driven\n' +
|
|
551
|
-
' development workflows for Claude Code, OpenCode,
|
|
582
|
+
' development workflows for Claude Code, OpenCode, Kimi CLI, Kilo, Codex, Copilot, Antigravity, Cursor, Windsurf, Augment, Trae, Qwen Code, Hermes Agent, Cline and CodeBuddy.\n';
|
|
552
583
|
|
|
553
584
|
// Pure seam: parse --config-dir / -c from an arbitrary args array.
|
|
554
585
|
// Returns the path string, '' for an empty equals-form value, or null when the
|
|
@@ -605,7 +636,7 @@ if (hasUninstall) {
|
|
|
605
636
|
|
|
606
637
|
// Show help if requested
|
|
607
638
|
if (hasHelp) {
|
|
608
|
-
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}--
|
|
639
|
+
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 <path>${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=<name>${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`);
|
|
609
640
|
process.exit(0);
|
|
610
641
|
}
|
|
611
642
|
|
|
@@ -1378,7 +1409,7 @@ const attributionCache = new Map();
|
|
|
1378
1409
|
|
|
1379
1410
|
/**
|
|
1380
1411
|
* Get commit attribution setting for a runtime
|
|
1381
|
-
* @param {string} runtime - 'claude', 'opencode', '
|
|
1412
|
+
* @param {string} runtime - 'claude', 'opencode', 'codex', or 'copilot'
|
|
1382
1413
|
* @returns {null|undefined|string} null = remove, undefined = keep default, string = custom
|
|
1383
1414
|
*/
|
|
1384
1415
|
function getCommitAttribution(runtime) {
|
|
@@ -1395,16 +1426,6 @@ function getCommitAttribution(runtime) {
|
|
|
1395
1426
|
: resolveKiloConfigPath;
|
|
1396
1427
|
const config = readSettings(resolveConfigPath(getGlobalConfigDir(runtime, null)));
|
|
1397
1428
|
result = (config && config.disable_ai_attribution === true) ? null : undefined;
|
|
1398
|
-
} else if (runtime === 'gemini') {
|
|
1399
|
-
// Gemini: check gemini settings.json for attribution config
|
|
1400
|
-
const settings = readSettings(path.join(getGlobalConfigDir('gemini', explicitConfigDir), 'settings.json'));
|
|
1401
|
-
if (!settings || !settings.attribution || settings.attribution.commit === undefined) {
|
|
1402
|
-
result = undefined;
|
|
1403
|
-
} else if (settings.attribution.commit === '') {
|
|
1404
|
-
result = null;
|
|
1405
|
-
} else {
|
|
1406
|
-
result = settings.attribution.commit;
|
|
1407
|
-
}
|
|
1408
1429
|
} else if (runtime === 'claude') {
|
|
1409
1430
|
// Claude Code
|
|
1410
1431
|
const settings = readSettings(path.join(getGlobalConfigDir('claude', explicitConfigDir), 'settings.json'));
|
|
@@ -5717,11 +5738,6 @@ function installCodexConfig(targetDir, agentsSrc, sandboxTier = 'codex-agent-san
|
|
|
5717
5738
|
return agents.length;
|
|
5718
5739
|
}
|
|
5719
5740
|
|
|
5720
|
-
/**
|
|
5721
|
-
* Strip HTML <sub> tags for Gemini CLI output
|
|
5722
|
-
* Terminals don't support subscript — Gemini renders these as raw HTML.
|
|
5723
|
-
* Converts <sub>text</sub> to italic *(text)* for readable terminal output.
|
|
5724
|
-
*/
|
|
5725
5741
|
/**
|
|
5726
5742
|
* Runtime-neutral agent name and instruction file replacement.
|
|
5727
5743
|
* Used by ALL non-Claude runtime converters to avoid Claude-specific
|
|
@@ -5752,200 +5768,6 @@ function neutralizeAgentReferences(content, instructionFile) {
|
|
|
5752
5768
|
return c;
|
|
5753
5769
|
}
|
|
5754
5770
|
|
|
5755
|
-
function stripSubTags(content) {
|
|
5756
|
-
return content.replace(/<sub>(.*?)<\/sub>/g, '*($1)*');
|
|
5757
|
-
}
|
|
5758
|
-
|
|
5759
|
-
/**
|
|
5760
|
-
* Convert Claude Code agent frontmatter to Gemini CLI format
|
|
5761
|
-
* Gemini agents use .md files with YAML frontmatter, same as Claude,
|
|
5762
|
-
* but with different field names and formats:
|
|
5763
|
-
* - tools: must be a YAML array (not comma-separated string)
|
|
5764
|
-
* - tool names: must use Gemini built-in names (read_file, not Read)
|
|
5765
|
-
* - color: must be removed (causes validation error)
|
|
5766
|
-
* - skills: must be removed (causes validation error)
|
|
5767
|
-
* - mcp__* tools: must be excluded (auto-discovered at runtime)
|
|
5768
|
-
*/
|
|
5769
|
-
let _gsdCommandRoster = null;
|
|
5770
|
-
let _gsdCommandRosterWarned = false;
|
|
5771
|
-
|
|
5772
|
-
/**
|
|
5773
|
-
* Get the list of known GSD commands from the source directory.
|
|
5774
|
-
* Caches the result after the first scan. Emits a one-shot warning if the
|
|
5775
|
-
* source directory cannot be located — an empty roster silently neutralises
|
|
5776
|
-
* every Gemini slash-command conversion, which is the bug this code exists
|
|
5777
|
-
* to prevent. The warning is gated on GSD_TEST_MODE to keep test output clean.
|
|
5778
|
-
* @returns {Set<string>} Set of command names (without .md extension)
|
|
5779
|
-
*/
|
|
5780
|
-
function getGsdCommandRoster() {
|
|
5781
|
-
if (_gsdCommandRoster) return _gsdCommandRoster;
|
|
5782
|
-
const baseDir = (typeof __dirname !== 'undefined') ? __dirname : process.cwd();
|
|
5783
|
-
const gsdSrc = path.join(baseDir, '..', 'commands', 'gsd');
|
|
5784
|
-
if (fs.existsSync(gsdSrc)) {
|
|
5785
|
-
_gsdCommandRoster = new Set(
|
|
5786
|
-
fs.readdirSync(gsdSrc)
|
|
5787
|
-
.filter(f => f.endsWith('.md'))
|
|
5788
|
-
.map(f => f.replace('.md', ''))
|
|
5789
|
-
);
|
|
5790
|
-
} else {
|
|
5791
|
-
_gsdCommandRoster = new Set();
|
|
5792
|
-
if (!_gsdCommandRosterWarned && !process.env.GSD_TEST_MODE) {
|
|
5793
|
-
_gsdCommandRosterWarned = true;
|
|
5794
|
-
console.warn(
|
|
5795
|
-
`WARNING: GSD command roster not found at ${gsdSrc}. ` +
|
|
5796
|
-
`Gemini /gsd- → /gsd: conversion will be a no-op. ` +
|
|
5797
|
-
`This usually means the package was installed without commands/gsd/.`
|
|
5798
|
-
);
|
|
5799
|
-
}
|
|
5800
|
-
}
|
|
5801
|
-
return _gsdCommandRoster;
|
|
5802
|
-
}
|
|
5803
|
-
|
|
5804
|
-
// Test-only: reset the cached roster. Exported via GSD_TEST_MODE bundle below.
|
|
5805
|
-
function _resetGsdCommandRoster() {
|
|
5806
|
-
_gsdCommandRoster = null;
|
|
5807
|
-
_gsdCommandRosterWarned = false;
|
|
5808
|
-
}
|
|
5809
|
-
|
|
5810
|
-
function convertSlashCommandsToGeminiMentions(content) {
|
|
5811
|
-
const commands = getGsdCommandRoster();
|
|
5812
|
-
// Defense in depth: regex boundary AND roster lookup must both agree.
|
|
5813
|
-
//
|
|
5814
|
-
// - Lookbehind `(?<![A-Za-z0-9./])` rejects URLs (`example.com/gsd-…`),
|
|
5815
|
-
// sub-paths (`bin/gsd-…`), and root-relative file paths preceded by a
|
|
5816
|
-
// path char. Without it the roster alone is insufficient: a URL like
|
|
5817
|
-
// `https://example.com/gsd-plan-phase` ends in a known command name and
|
|
5818
|
-
// would convert incorrectly.
|
|
5819
|
-
// - `(?!\/)` rejects sub-path continuation (`/gsd-foo/bar`).
|
|
5820
|
-
// - `(?!\.[a-z])` rejects file extensions (`.cjs`, `.md`) but PERMITS
|
|
5821
|
-
// sentence-ending punctuation like `/gsd-help.` because `.` at end of
|
|
5822
|
-
// string or before whitespace is not followed by a lowercase letter.
|
|
5823
|
-
// - Roster lookup ensures only real commands convert — agent names like
|
|
5824
|
-
// `gsd-planner` (no leading slash anyway) and unknown tokens pass through.
|
|
5825
|
-
//
|
|
5826
|
-
// GSD commands are always lowercase, so no case-insensitive flag.
|
|
5827
|
-
return content.replace(/(?<![A-Za-z0-9./])\/gsd-([a-z0-9-]+)(?!\/)(?!\.[a-z])/g, (match, commandName) => {
|
|
5828
|
-
return commands.has(commandName) ? `/gsd:${commandName}` : match;
|
|
5829
|
-
});
|
|
5830
|
-
}
|
|
5831
|
-
|
|
5832
|
-
function convertClaudeToGeminiMarkdown(content, { isCommand = false, commandName = null } = {}) {
|
|
5833
|
-
// Apply Gemini-specific slash command namespacing
|
|
5834
|
-
let converted = convertSlashCommandsToGeminiMentions(content);
|
|
5835
|
-
// Gemini CLI does not expose Claude's AskUserQuestion tool. Convert body
|
|
5836
|
-
// references to runtime-neutral wording so converted agents do not instruct
|
|
5837
|
-
// Gemini to call a nonexistent tool (#3362).
|
|
5838
|
-
converted = converted.replace(/\b(?:AskUserQuestion|ask_user)\b/g, 'conversational prompting');
|
|
5839
|
-
// Strip HTML subscript tags — terminals can't render them. Done before
|
|
5840
|
-
// TOML conversion so the prompt body of a command file is also clean.
|
|
5841
|
-
converted = stripSubTags(converted);
|
|
5842
|
-
|
|
5843
|
-
if (isCommand) {
|
|
5844
|
-
// Convert to Gemini TOML format (threads the command name so per-command
|
|
5845
|
-
// enrichment — e.g. the #778 live-state injection — can target a command).
|
|
5846
|
-
converted = convertClaudeToGeminiToml(converted, { commandName });
|
|
5847
|
-
}
|
|
5848
|
-
|
|
5849
|
-
return converted;
|
|
5850
|
-
}
|
|
5851
|
-
|
|
5852
|
-
function convertClaudeToGeminiAgent(content) {
|
|
5853
|
-
if (!content.startsWith('---')) return content;
|
|
5854
|
-
|
|
5855
|
-
const endIndex = content.indexOf('---', 3);
|
|
5856
|
-
if (endIndex === -1) return content;
|
|
5857
|
-
|
|
5858
|
-
const frontmatter = content.substring(3, endIndex).trim();
|
|
5859
|
-
const body = content.substring(endIndex + 3);
|
|
5860
|
-
|
|
5861
|
-
const lines = frontmatter.split('\n');
|
|
5862
|
-
const newLines = [];
|
|
5863
|
-
let inAllowedTools = false;
|
|
5864
|
-
let inSkippedArrayField = false;
|
|
5865
|
-
const tools = [];
|
|
5866
|
-
|
|
5867
|
-
for (const line of lines) {
|
|
5868
|
-
const trimmed = line.trim();
|
|
5869
|
-
|
|
5870
|
-
if (inSkippedArrayField) {
|
|
5871
|
-
if (!trimmed || trimmed.startsWith('- ')) {
|
|
5872
|
-
continue;
|
|
5873
|
-
}
|
|
5874
|
-
inSkippedArrayField = false;
|
|
5875
|
-
}
|
|
5876
|
-
|
|
5877
|
-
// Convert allowed-tools YAML array to tools list
|
|
5878
|
-
if (trimmed.startsWith('allowed-tools:')) {
|
|
5879
|
-
inAllowedTools = true;
|
|
5880
|
-
continue;
|
|
5881
|
-
}
|
|
5882
|
-
|
|
5883
|
-
// Handle inline tools: field (comma-separated string)
|
|
5884
|
-
if (trimmed.startsWith('tools:')) {
|
|
5885
|
-
const toolsValue = trimmed.substring(6).trim();
|
|
5886
|
-
if (toolsValue) {
|
|
5887
|
-
const parsed = toolsValue.split(',').map(t => t.trim()).filter(t => t);
|
|
5888
|
-
for (const t of parsed) {
|
|
5889
|
-
const mapped = convertGeminiToolName(t);
|
|
5890
|
-
if (mapped) tools.push(mapped);
|
|
5891
|
-
}
|
|
5892
|
-
} else {
|
|
5893
|
-
// tools: with no value means YAML array follows
|
|
5894
|
-
inAllowedTools = true;
|
|
5895
|
-
}
|
|
5896
|
-
continue;
|
|
5897
|
-
}
|
|
5898
|
-
|
|
5899
|
-
// Strip color field (not supported by Gemini CLI, causes validation error)
|
|
5900
|
-
if (trimmed.startsWith('color:')) continue;
|
|
5901
|
-
|
|
5902
|
-
// Strip skills field (not supported by Gemini CLI, causes validation error)
|
|
5903
|
-
if (trimmed.startsWith('skills:')) {
|
|
5904
|
-
inSkippedArrayField = true;
|
|
5905
|
-
continue;
|
|
5906
|
-
}
|
|
5907
|
-
|
|
5908
|
-
// Collect allowed-tools/tools array items
|
|
5909
|
-
if (inAllowedTools) {
|
|
5910
|
-
if (trimmed.startsWith('- ')) {
|
|
5911
|
-
const mapped = convertGeminiToolName(trimmed.substring(2).trim());
|
|
5912
|
-
if (mapped) tools.push(mapped);
|
|
5913
|
-
continue;
|
|
5914
|
-
} else if (trimmed && !trimmed.startsWith('-')) {
|
|
5915
|
-
inAllowedTools = false;
|
|
5916
|
-
}
|
|
5917
|
-
}
|
|
5918
|
-
|
|
5919
|
-
if (!inAllowedTools) {
|
|
5920
|
-
newLines.push(line);
|
|
5921
|
-
}
|
|
5922
|
-
}
|
|
5923
|
-
|
|
5924
|
-
// Add tools as YAML array (Gemini requires array format)
|
|
5925
|
-
if (tools.length > 0) {
|
|
5926
|
-
newLines.push('tools:');
|
|
5927
|
-
for (const tool of tools) {
|
|
5928
|
-
newLines.push(` - ${tool}`);
|
|
5929
|
-
}
|
|
5930
|
-
}
|
|
5931
|
-
|
|
5932
|
-
const newFrontmatter = newLines.join('\n').trim();
|
|
5933
|
-
|
|
5934
|
-
// Escape ${VAR} patterns in agent body for Gemini CLI compatibility.
|
|
5935
|
-
// Gemini's templateString() treats all ${word} patterns as template variables
|
|
5936
|
-
// and throws "Template validation failed: Missing required input parameters"
|
|
5937
|
-
// when they can't be resolved. GSD agents use ${PHASE}, ${PLAN}, etc. as
|
|
5938
|
-
// shell variables in bash code blocks — convert to $VAR (no braces) which
|
|
5939
|
-
// is equivalent bash and invisible to Gemini's /\$\{(\w+)\}/g regex.
|
|
5940
|
-
const escapedBody = body.replace(/\$\{(\w+)\}/g, '$$$1');
|
|
5941
|
-
|
|
5942
|
-
// Runtime-neutral agent name replacement (#766)
|
|
5943
|
-
const neutralBody = neutralizeAgentReferences(escapedBody, 'GEMINI.md');
|
|
5944
|
-
// Apply Gemini-specific transformations (slash commands + sub-tag stripping)
|
|
5945
|
-
const geminiBody = convertClaudeToGeminiMarkdown(neutralBody);
|
|
5946
|
-
return `---\n${newFrontmatter}\n---${geminiBody}`;
|
|
5947
|
-
}
|
|
5948
|
-
|
|
5949
5771
|
function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOverride = null } = {}) {
|
|
5950
5772
|
// Replace tool name references in content (applies to all files)
|
|
5951
5773
|
let convertedContent = content;
|
|
@@ -6282,76 +6104,6 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false } = {}) {
|
|
|
6282
6104
|
// convertClaudeCommandToKiloSkill: moved to src/install-engine.cts (ADR-1239 Phase B).
|
|
6283
6105
|
// Imported from installEngine above.
|
|
6284
6106
|
|
|
6285
|
-
/**
|
|
6286
|
-
* Convert Claude Code markdown command to Gemini TOML format
|
|
6287
|
-
* @param {string} content - Markdown file content with YAML frontmatter
|
|
6288
|
-
* @returns {string} - TOML content
|
|
6289
|
-
*/
|
|
6290
|
-
function convertClaudeToGeminiToml(content, { commandName = null } = {}) {
|
|
6291
|
-
// #778 (c) — Gemini {{args}} interpolation. Claude's $ARGUMENTS placeholder
|
|
6292
|
-
// maps to Gemini's {{args}} so inline argument references interpolate into the
|
|
6293
|
-
// command body instead of being emitted as a dead literal. Applied before
|
|
6294
|
-
// frontmatter parsing so every return path benefits (a command's frontmatter
|
|
6295
|
-
// never contains $ARGUMENTS, so this is body-only in practice). Gemini injects
|
|
6296
|
-
// {{args}} as typed outside shell blocks; we never place it inside a !{...}
|
|
6297
|
-
// block, so there is no shell-escaping/injection interaction.
|
|
6298
|
-
content = content.replace(/\$ARGUMENTS\b/g, '{{args}}');
|
|
6299
|
-
|
|
6300
|
-
// Check if content has frontmatter
|
|
6301
|
-
if (!content.startsWith('---')) {
|
|
6302
|
-
return `prompt = ${JSON.stringify(content)}\n`;
|
|
6303
|
-
}
|
|
6304
|
-
|
|
6305
|
-
const endIndex = content.indexOf('---', 3);
|
|
6306
|
-
if (endIndex === -1) {
|
|
6307
|
-
return `prompt = ${JSON.stringify(content)}\n`;
|
|
6308
|
-
}
|
|
6309
|
-
|
|
6310
|
-
const frontmatter = content.substring(3, endIndex).trim();
|
|
6311
|
-
let body = content.substring(endIndex + 3).trim();
|
|
6312
|
-
|
|
6313
|
-
// #778 (c) — Gemini !{...} dynamic-output injection for the situational
|
|
6314
|
-
// `progress` command (GSD's status/dashboard surface). Inject the live
|
|
6315
|
-
// .planning/STATE.md so the model sees current project state without relying
|
|
6316
|
-
// on session memory.
|
|
6317
|
-
//
|
|
6318
|
-
// SECURITY: the shell command is a FIXED `cat` with NO interpolated user
|
|
6319
|
-
// input — no {{args}} appears inside the block — so there is no
|
|
6320
|
-
// shell-injection vector. Gemini still shows its standard per-invocation
|
|
6321
|
-
// confirmation dialog (verified behavior). `2>/dev/null` keeps an
|
|
6322
|
-
// uninitialized project (missing STATE.md) from injecting stderr noise.
|
|
6323
|
-
// Braces inside the block are balanced (none present), per Gemini's parser
|
|
6324
|
-
// requirement. The append happens AFTER the {{args}} mapping above so the
|
|
6325
|
-
// injected block can never accidentally carry interpolated arguments.
|
|
6326
|
-
if (commandName === 'progress') {
|
|
6327
|
-
body += '\n\n## Live project state\n'
|
|
6328
|
-
+ 'Current contents of `.planning/STATE.md` '
|
|
6329
|
-
+ '(empty if the project is not yet initialized):\n\n'
|
|
6330
|
-
+ '!{cat .planning/STATE.md 2>/dev/null}\n';
|
|
6331
|
-
}
|
|
6332
|
-
|
|
6333
|
-
// Extract description from frontmatter
|
|
6334
|
-
let description = '';
|
|
6335
|
-
const lines = frontmatter.split('\n');
|
|
6336
|
-
for (const line of lines) {
|
|
6337
|
-
const trimmed = line.trim();
|
|
6338
|
-
if (trimmed.startsWith('description:')) {
|
|
6339
|
-
description = trimmed.substring(12).trim();
|
|
6340
|
-
break;
|
|
6341
|
-
}
|
|
6342
|
-
}
|
|
6343
|
-
|
|
6344
|
-
// Construct TOML
|
|
6345
|
-
let toml = '';
|
|
6346
|
-
if (description) {
|
|
6347
|
-
toml += `description = ${JSON.stringify(description)}\n`;
|
|
6348
|
-
}
|
|
6349
|
-
|
|
6350
|
-
toml += `prompt = ${JSON.stringify(body)}\n`;
|
|
6351
|
-
|
|
6352
|
-
return toml;
|
|
6353
|
-
}
|
|
6354
|
-
|
|
6355
6107
|
/**
|
|
6356
6108
|
* Copy commands to a flat structure for OpenCode
|
|
6357
6109
|
* OpenCode expects: command/gsd-help.md (invoked as /gsd-help)
|
|
@@ -6529,7 +6281,9 @@ const _stampNonClaudeRuntimeDefaults = runtimeArtifactConversion._stampNonClaude
|
|
|
6529
6281
|
* mdSkipGenericRewrite?: boolean — skip the ~/.claude/ rewrite block (copilot, antigravity)
|
|
6530
6282
|
* md?: (content, ctx) => string — per-runtime .md transform
|
|
6531
6283
|
* mdReattributeAfter?: boolean — re-run processAttribution after md() (copilot, antigravity)
|
|
6532
|
-
* mdTomlRenameOnCommand?: boolean — when isCommand, rename dest .md → .toml
|
|
6284
|
+
* mdTomlRenameOnCommand?: boolean — when isCommand, rename dest .md → .toml
|
|
6285
|
+
* (unused since the gemini runtime was removed, #1928;
|
|
6286
|
+
* kept as generic dispatch infra for a future TOML-command runtime)
|
|
6533
6287
|
* js?: (content, ctx) => string — per-runtime .cjs/.js transform (absent = plain copyFileSync)
|
|
6534
6288
|
*
|
|
6535
6289
|
* ctx = { isCommand, isGlobal, dirName, pathPrefix, entryName, runtime }
|
|
@@ -6541,14 +6295,6 @@ const RUNTIME_CONTENT_DISPATCH = {
|
|
|
6541
6295
|
kilo: {
|
|
6542
6296
|
md: (content) => convertClaudeToKiloFrontmatter(content),
|
|
6543
6297
|
},
|
|
6544
|
-
gemini: {
|
|
6545
|
-
md: (content, ctx) =>
|
|
6546
|
-
convertClaudeToGeminiMarkdown(content, {
|
|
6547
|
-
isCommand: ctx.isCommand,
|
|
6548
|
-
commandName: ctx.isCommand ? ctx.entryName.replace(/\.md$/, '') : null,
|
|
6549
|
-
}),
|
|
6550
|
-
mdTomlRenameOnCommand: true,
|
|
6551
|
-
},
|
|
6552
6298
|
codex: {
|
|
6553
6299
|
md: (content) => convertClaudeToCodexMarkdown(content),
|
|
6554
6300
|
},
|
|
@@ -6644,7 +6390,7 @@ const RUNTIME_CONTENT_DISPATCH = {
|
|
|
6644
6390
|
* @param {string} srcDir - Source directory
|
|
6645
6391
|
* @param {string} destDir - Destination directory
|
|
6646
6392
|
* @param {string} pathPrefix - Path prefix for file references
|
|
6647
|
-
* @param {string} runtime - Target runtime ('claude', 'opencode', '
|
|
6393
|
+
* @param {string} runtime - Target runtime ('claude', 'opencode', 'codex')
|
|
6648
6394
|
* @param {boolean} isCommand - Whether the source is a command directory
|
|
6649
6395
|
* @param {boolean} isGlobal - Whether the install is global
|
|
6650
6396
|
*/
|
|
@@ -6723,7 +6469,7 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
|
|
|
6723
6469
|
// copyWithPathReplacement for runtimes that register commands under the
|
|
6724
6470
|
// hyphen form; normalizeAgentBodyForRuntime self-gates on
|
|
6725
6471
|
// shouldNormalizeHyphenNamespaceInAgentBody(runtime) and is a no-op for
|
|
6726
|
-
// colon-canonical runtimes
|
|
6472
|
+
// colon-canonical / self-converting runtimes.
|
|
6727
6473
|
content = normalizeAgentBodyForRuntime(content, runtime, readGsdCommandNames());
|
|
6728
6474
|
|
|
6729
6475
|
// Apply per-runtime .md converter (if any)
|
|
@@ -6732,7 +6478,7 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
|
|
|
6732
6478
|
// Re-run attribution after converter for runtimes that need it (copilot, antigravity)
|
|
6733
6479
|
if (dispatch.mdReattributeAfter) content = processAttribution(content, getCommitAttribution(runtime));
|
|
6734
6480
|
|
|
6735
|
-
//
|
|
6481
|
+
// Rename .md → .toml for command files (unused since gemini removal, #1928)
|
|
6736
6482
|
const finalPath = (dispatch.mdTomlRenameOnCommand && isCommand) ? destPath.replace(/\.md$/, '.toml') : destPath;
|
|
6737
6483
|
fs.writeFileSync(finalPath, content);
|
|
6738
6484
|
} else if (entry.name.endsWith('.cjs') || entry.name.endsWith('.js')) {
|
|
@@ -6915,10 +6661,10 @@ const GSD_UNINSTALL_HOOKS = [
|
|
|
6915
6661
|
* Uninstall GSD from the specified directory for a specific runtime
|
|
6916
6662
|
* Removes only GSD-specific files/directories, preserves user content
|
|
6917
6663
|
* @param {boolean} isGlobal - Whether to uninstall from global or local
|
|
6918
|
-
* @param {string} runtime - Target runtime ('claude', 'opencode', '
|
|
6664
|
+
* @param {string} runtime - Target runtime ('claude', 'opencode', 'codex', 'copilot')
|
|
6919
6665
|
*/
|
|
6920
6666
|
function uninstall(isGlobal, runtime = 'claude') {
|
|
6921
|
-
const { isOpencode, isKilo,
|
|
6667
|
+
const { isOpencode, isKilo, isCodex, isCopilot, isAntigravity, isCursor, isWindsurf, isAugment, isTrae, isQwen, isHermes, isCodebuddy, isCline, isKimi } = runtimeFlags(runtime);
|
|
6922
6668
|
const dirName = getDirName(runtime);
|
|
6923
6669
|
|
|
6924
6670
|
// Get the target directory based on runtime and install type. Cline local
|
|
@@ -7178,29 +6924,6 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
7178
6924
|
}
|
|
7179
6925
|
}
|
|
7180
6926
|
|
|
7181
|
-
// 1d. Gemini: remove commands/gsd/ with dev-preferences.md preservation.
|
|
7182
|
-
// The layout removes gsd-*.toml files but not the directory itself.
|
|
7183
|
-
// Preserve user files before removing the directory.
|
|
7184
|
-
if (isGemini) {
|
|
7185
|
-
const gsdCommandsDir = path.join(targetDir, 'commands', 'gsd');
|
|
7186
|
-
if (fs.existsSync(gsdCommandsDir)) {
|
|
7187
|
-
const devPrefsPath = path.join(gsdCommandsDir, 'dev-preferences.md');
|
|
7188
|
-
const preservedDevPrefs = fs.existsSync(devPrefsPath) ? fs.readFileSync(devPrefsPath, 'utf-8') : null;
|
|
7189
|
-
fs.rmSync(gsdCommandsDir, { recursive: true });
|
|
7190
|
-
removedCount++;
|
|
7191
|
-
console.log(` ${green}✓${reset} Removed commands/gsd/`);
|
|
7192
|
-
if (preservedDevPrefs) {
|
|
7193
|
-
try {
|
|
7194
|
-
fs.mkdirSync(gsdCommandsDir, { recursive: true });
|
|
7195
|
-
fs.writeFileSync(devPrefsPath, preservedDevPrefs);
|
|
7196
|
-
console.log(` ${green}✓${reset} Preserved commands/gsd/dev-preferences.md`);
|
|
7197
|
-
} catch (err) {
|
|
7198
|
-
console.error(` ${red}✗${reset} Failed to restore dev-preferences.md: ${err.message}`);
|
|
7199
|
-
}
|
|
7200
|
-
}
|
|
7201
|
-
}
|
|
7202
|
-
}
|
|
7203
|
-
|
|
7204
6927
|
// 1d. Qwen/Hermes: migrate dev-preferences.md from legacy commands/gsd/ location
|
|
7205
6928
|
// during uninstall. _runLegacyUninstallCleanup (called by uninstallRuntimeArtifacts)
|
|
7206
6929
|
// removes the directory; we must preserve/restore user artifacts before that path.
|
|
@@ -7316,6 +7039,22 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
7316
7039
|
}
|
|
7317
7040
|
}
|
|
7318
7041
|
|
|
7042
|
+
// 4z. Remove the OpenCode native plugin adapter (#1914). Only GSD's own
|
|
7043
|
+
// plugin file is removed; the plugins/ dir is pruned only if it becomes
|
|
7044
|
+
// empty, preserving any user-authored OpenCode plugins.
|
|
7045
|
+
if (isOpencode) {
|
|
7046
|
+
const pluginsDir = path.join(targetDir, 'plugins');
|
|
7047
|
+
const pluginPath = path.join(pluginsDir, 'gsd-core.js');
|
|
7048
|
+
if (fs.existsSync(pluginPath)) {
|
|
7049
|
+
try {
|
|
7050
|
+
fs.unlinkSync(pluginPath);
|
|
7051
|
+
removedCount++;
|
|
7052
|
+
console.log(` ${green}✓${reset} Removed OpenCode plugin`);
|
|
7053
|
+
} catch (_) { /* best-effort */ }
|
|
7054
|
+
try { fs.rmdirSync(pluginsDir); } catch (_) { /* not empty — user plugins present */ }
|
|
7055
|
+
}
|
|
7056
|
+
}
|
|
7057
|
+
|
|
7319
7058
|
// 4a. Remove scripts/changeset/ and scripts/lib/ (#935)
|
|
7320
7059
|
// GSD-managed files only: enumerate the exact set the installer writes.
|
|
7321
7060
|
// Any file NOT in this set is user-owned and must survive uninstall.
|
|
@@ -7360,6 +7099,11 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
7360
7099
|
const fixSlashUninstallPath = path.join(targetDir, 'scripts', 'fix-slash-commands.cjs');
|
|
7361
7100
|
try { fs.unlinkSync(fixSlashUninstallPath); } catch (_) { /* best-effort */ }
|
|
7362
7101
|
|
|
7102
|
+
// Remove the capability registry generator scripts (#1920) — before the scripts/ rmdir
|
|
7103
|
+
for (const gen of ['gen-capability-registry.cjs', 'gen-loop-host-contract.cjs']) {
|
|
7104
|
+
try { fs.unlinkSync(path.join(targetDir, 'scripts', gen)); } catch (_) { /* best-effort */ }
|
|
7105
|
+
}
|
|
7106
|
+
|
|
7363
7107
|
// If scripts/ dir is now empty, remove it too
|
|
7364
7108
|
const scriptsUninstallDir = path.join(targetDir, 'scripts');
|
|
7365
7109
|
if (fs.existsSync(scriptsUninstallDir)) {
|
|
@@ -7403,7 +7147,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
7403
7147
|
// Remove GSD hooks from settings — per-hook granularity to preserve
|
|
7404
7148
|
// user hooks that share an entry with a GSD hook (#1755 followup).
|
|
7405
7149
|
// Includes the 3 Qwen-only events added in #788 (SubagentStop, Stop,
|
|
7406
|
-
// PreCompact, also registered for Claude in #770), the 3
|
|
7150
|
+
// PreCompact, also registered for Claude in #770), the 3 Antigravity-only
|
|
7407
7151
|
// events added in #776 (BeforeAgent, AfterAgent, BeforeModel), and the
|
|
7408
7152
|
// Claude-only FileChanged event added in #770 — safe to iterate for all
|
|
7409
7153
|
// runtimes; installs that don't register these events simply find no
|
|
@@ -7702,7 +7446,7 @@ function configureOpencodePermissions(isGlobal = true, configDir = null) {
|
|
|
7702
7446
|
modified = true;
|
|
7703
7447
|
}
|
|
7704
7448
|
|
|
7705
|
-
// Configure external_directory permission (the safety guard for paths outside
|
|
7449
|
+
// Configure external_directory permission (the safety guard for paths outside)
|
|
7706
7450
|
if (!config.permission.external_directory || typeof config.permission.external_directory !== 'object') {
|
|
7707
7451
|
config.permission.external_directory = {};
|
|
7708
7452
|
}
|
|
@@ -7711,6 +7455,25 @@ function configureOpencodePermissions(isGlobal = true, configDir = null) {
|
|
|
7711
7455
|
modified = true;
|
|
7712
7456
|
}
|
|
7713
7457
|
|
|
7458
|
+
// ADR-1239 Phase D / #1682 — register the companion MCP server (Phase 4) so
|
|
7459
|
+
// OpenCode connects to GSD's command (point 1) + state-IO (point 5) surface
|
|
7460
|
+
// with NO bespoke plugin. Idempotent + non-clobbering: only added when
|
|
7461
|
+
// `mcp.gsd` is absent (a user-defined `mcp.gsd` is respected — Hyrum's Law).
|
|
7462
|
+
// Local-stdio schema per OpenCode config (packages/core/src/config/mcp.ts).
|
|
7463
|
+
// `-p @opengsd/gsd-core` resolves the `gsd-mcp-server` bin from this package
|
|
7464
|
+
// (bin name != package name) regardless of global-install state.
|
|
7465
|
+
if (!config.mcp || typeof config.mcp !== 'object') {
|
|
7466
|
+
config.mcp = {};
|
|
7467
|
+
}
|
|
7468
|
+
if (config.mcp.gsd === undefined) {
|
|
7469
|
+
config.mcp.gsd = {
|
|
7470
|
+
type: 'local',
|
|
7471
|
+
command: ['npx', '-y', '-p', PACKAGE_NAME, 'gsd-mcp-server'],
|
|
7472
|
+
enabled: true,
|
|
7473
|
+
};
|
|
7474
|
+
modified = true;
|
|
7475
|
+
}
|
|
7476
|
+
|
|
7714
7477
|
if (!modified) {
|
|
7715
7478
|
return; // Already configured
|
|
7716
7479
|
}
|
|
@@ -7829,7 +7592,7 @@ function verifyFileInstalled(filePath, description) {
|
|
|
7829
7592
|
/**
|
|
7830
7593
|
* Install to the specified directory for a specific runtime
|
|
7831
7594
|
* @param {boolean} isGlobal - Whether to install globally or locally
|
|
7832
|
-
* @param {string} runtime - Target runtime ('claude', 'opencode', '
|
|
7595
|
+
* @param {string} runtime - Target runtime ('claude', 'opencode', 'codex')
|
|
7833
7596
|
*/
|
|
7834
7597
|
|
|
7835
7598
|
// ──────────────────────────────────────────────────────
|
|
@@ -7903,12 +7666,10 @@ function resolveInstallRelativePath(baseDir, relPath) {
|
|
|
7903
7666
|
* Write file manifest after installation for future modification detection
|
|
7904
7667
|
*/
|
|
7905
7668
|
function writeManifest(configDir, runtime = 'claude', options = {}) {
|
|
7906
|
-
const { isOpencode, isKilo,
|
|
7669
|
+
const { isOpencode, isKilo, isCodex, isCopilot, isAntigravity, isCursor, isWindsurf, isAugment, isTrae, isQwen, isHermes, isCodebuddy, isCline, isKimi } = runtimeFlags(runtime);
|
|
7907
7670
|
const gsdDir = path.join(configDir, 'gsd-core');
|
|
7908
7671
|
// #1367: Claude local now writes flat gsd-*.md files at commands/ (not commands/gsd/).
|
|
7909
|
-
// commandsDir points to the old location for Gemini (which still uses commands/gsd/).
|
|
7910
7672
|
// Claude local uses flatCommandsDir instead for manifest recording.
|
|
7911
|
-
const commandsDir = path.join(configDir, 'commands', 'gsd');
|
|
7912
7673
|
const flatCommandsDir = path.join(configDir, 'commands');
|
|
7913
7674
|
const opencodeCommandDir = path.join(configDir, 'command');
|
|
7914
7675
|
// Hermes nests GSD skills under skills/gsd/ as a single category (#2841).
|
|
@@ -7936,16 +7697,9 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
|
|
|
7936
7697
|
manifest.files['gsd-core/' + rel] = hash;
|
|
7937
7698
|
}
|
|
7938
7699
|
// Record commands surface for runtimes that emit it:
|
|
7939
|
-
// Gemini: commands/gsd/<cmd>.toml (nested, colon-namespaced)
|
|
7940
7700
|
// Claude local (#1367 fix): flat gsd-<cmd>.md at commands/ level
|
|
7941
7701
|
// Manifest must reflect everything on disk so saveLocalPatches() can detect
|
|
7942
7702
|
// user edits and per-runtime minimal-mode assertions can read manifest.files.
|
|
7943
|
-
if (isGemini && fs.existsSync(commandsDir)) {
|
|
7944
|
-
const cmdHashes = generateManifest(commandsDir);
|
|
7945
|
-
for (const [rel, hash] of Object.entries(cmdHashes)) {
|
|
7946
|
-
manifest.files['commands/gsd/' + rel] = hash;
|
|
7947
|
-
}
|
|
7948
|
-
}
|
|
7949
7703
|
// Claude local (#1367): flat gsd-*.md files at commands/ level.
|
|
7950
7704
|
// Only claude local writes gsd-*.md here; global installs don't emit commands,
|
|
7951
7705
|
// so this branch is a no-op for global (no matching files to find).
|
|
@@ -7963,7 +7717,7 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
|
|
|
7963
7717
|
}
|
|
7964
7718
|
}
|
|
7965
7719
|
}
|
|
7966
|
-
if ((isCodex || isCopilot || isAntigravity || isCursor || isWindsurf || isTrae ||
|
|
7720
|
+
if ((isCodex || isCopilot || isAntigravity || isCursor || isWindsurf || isTrae || !isOpencode) && fs.existsSync(codexSkillsDir)) {
|
|
7967
7721
|
// All runtimes (including Hermes post-#947) use the canonical 'gsd-' prefix.
|
|
7968
7722
|
const skillListPrefix = 'gsd-';
|
|
7969
7723
|
for (const skillName of listCodexSkillNames(codexSkillsDir, skillListPrefix)) {
|
|
@@ -8064,6 +7818,24 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
|
|
|
8064
7818
|
manifest.files['scripts/fix-slash-commands.cjs'] = fileHash(fixSlashInstallPath);
|
|
8065
7819
|
}
|
|
8066
7820
|
|
|
7821
|
+
// Track the capability registry generator scripts (#1920) — top-level scripts/ files
|
|
7822
|
+
// not covered by the changeset/lib loops.
|
|
7823
|
+
for (const gen of ['gen-capability-registry.cjs', 'gen-loop-host-contract.cjs']) {
|
|
7824
|
+
const genInstallPath = path.join(configDir, 'scripts', gen);
|
|
7825
|
+
if (fs.existsSync(genInstallPath)) {
|
|
7826
|
+
manifest.files['scripts/' + gen] = fileHash(genInstallPath);
|
|
7827
|
+
}
|
|
7828
|
+
}
|
|
7829
|
+
|
|
7830
|
+
// Track the OpenCode native plugin adapter (#1914) so update/drift detection
|
|
7831
|
+
// and uninstall can account for it.
|
|
7832
|
+
if (isOpencode) {
|
|
7833
|
+
const pluginInstallPath = path.join(configDir, 'plugins', 'gsd-core.js');
|
|
7834
|
+
if (fs.existsSync(pluginInstallPath)) {
|
|
7835
|
+
manifest.files['plugins/gsd-core.js'] = fileHash(pluginInstallPath);
|
|
7836
|
+
}
|
|
7837
|
+
}
|
|
7838
|
+
|
|
8067
7839
|
fs.writeFileSync(path.join(configDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2));
|
|
8068
7840
|
return manifest;
|
|
8069
7841
|
}
|
|
@@ -8355,10 +8127,8 @@ function reportLocalPatches(configDir, runtime = 'claude') {
|
|
|
8355
8127
|
if (meta.files && meta.files.length > 0) {
|
|
8356
8128
|
const reapplyCommand = (runtime === 'opencode' || runtime === 'kilo' || runtime === 'copilot')
|
|
8357
8129
|
? '/gsd-update --reapply'
|
|
8358
|
-
: runtime === '
|
|
8359
|
-
? '
|
|
8360
|
-
: runtime === 'codex'
|
|
8361
|
-
? '$gsd-update --reapply'
|
|
8130
|
+
: runtime === 'codex'
|
|
8131
|
+
? '$gsd-update --reapply'
|
|
8362
8132
|
: runtime === 'cursor'
|
|
8363
8133
|
? 'gsd-update --reapply (mention the skill name)'
|
|
8364
8134
|
: runtime === 'kimi'
|
|
@@ -8390,7 +8160,7 @@ function reportInstallerMigrationResult(result) {
|
|
|
8390
8160
|
}
|
|
8391
8161
|
|
|
8392
8162
|
function install(isGlobal, runtime = 'claude', options = {}) {
|
|
8393
|
-
const { isOpencode, isKilo,
|
|
8163
|
+
const { isOpencode, isKilo, isCodex, isCopilot, isAntigravity, isCursor, isWindsurf, isAugment, isTrae, isQwen, isHermes, isCodebuddy, isCline, isKimi } = runtimeFlags(runtime);
|
|
8394
8164
|
const plan = resolveInstallPlan(runtime);
|
|
8395
8165
|
const dirName = getDirName(runtime);
|
|
8396
8166
|
const src = path.join(__dirname, '..');
|
|
@@ -8805,7 +8575,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
8805
8575
|
|
|
8806
8576
|
// Artifact install dispatcher — routes to layout-driven path for all
|
|
8807
8577
|
// skills-based runtimes (both full and minimal/core profiles); keeps
|
|
8808
|
-
// back-compat paths for commands-based runtimes (OpenCode/Kilo/
|
|
8578
|
+
// back-compat paths for commands-based runtimes (OpenCode/Kilo/
|
|
8809
8579
|
// Claude-local).
|
|
8810
8580
|
//
|
|
8811
8581
|
// installRuntimeArtifacts handles legacy migration + skill/agent staging
|
|
@@ -8816,7 +8586,6 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
8816
8586
|
// Hermes: writeHermesCategoryDescription (not a layout kind)
|
|
8817
8587
|
// Cline global: skills emitted via layout; .clinerules still written below (#782)
|
|
8818
8588
|
// Cline local: no skills (only .clinerules) — falls through to cline-rules surface
|
|
8819
|
-
// Gemini: conflict-detection logic (not expressible in layout)
|
|
8820
8589
|
// OpenCode/Kilo: copyFlattenedCommands (frontmatter conversion not in commandsKind)
|
|
8821
8590
|
// Claude local: copyWithPathReplacement + stale-skills cleanup
|
|
8822
8591
|
|
|
@@ -8999,62 +8768,44 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
8999
8768
|
} else {
|
|
9000
8769
|
failures.push('skills/gsd-*');
|
|
9001
8770
|
}
|
|
8771
|
+
|
|
8772
|
+
// OpenCode-only: install the native plugin adapter (#1914). OpenCode
|
|
8773
|
+
// declares hooksSurface: 'none', so GSD's lifecycle hooks are never
|
|
8774
|
+
// registered as settings.json hooks the way Claude Code does — the hook
|
|
8775
|
+
// *scripts* ship to <configDir>/hooks/ but nothing invokes them. This
|
|
8776
|
+
// plugin bridges OpenCode's event bus onto those existing hook scripts
|
|
8777
|
+
// (prompt guard, read guard, injection scanner, context monitor, ...),
|
|
8778
|
+
// spawning them as subprocesses. OpenCode auto-discovers plugin files under
|
|
8779
|
+
// <configDir>/plugins/ at startup — no opencode.json registration needed
|
|
8780
|
+
// (its `plugin` array is for npm packages, not local file paths).
|
|
8781
|
+
//
|
|
8782
|
+
// The file MUST land as `.js`: OpenCode's loader globs
|
|
8783
|
+
// `{plugin,plugins}/*.{ts,js}` (verified against its source) — a `.cjs`
|
|
8784
|
+
// extension would never be discovered. The config dir carries a
|
|
8785
|
+
// `{"type":"commonjs"}` package.json (written above), so the `.js` file is
|
|
8786
|
+
// interpreted as CommonJS, matching the adapter's module.exports/require.
|
|
8787
|
+
// Kilo has no plugin surface, so this is gated to OpenCode only.
|
|
8788
|
+
if (isOpencode) {
|
|
8789
|
+
const pluginSrc = path.join(src, '.opencode', 'plugins', 'gsd-core.js');
|
|
8790
|
+
const pluginDestDir = path.join(targetDir, 'plugins');
|
|
8791
|
+
const pluginDest = path.join(pluginDestDir, 'gsd-core.js');
|
|
8792
|
+
if (fs.existsSync(pluginSrc)) {
|
|
8793
|
+
fs.mkdirSync(pluginDestDir, { recursive: true });
|
|
8794
|
+
fs.copyFileSync(pluginSrc, pluginDest);
|
|
8795
|
+
if (fs.existsSync(pluginDest)) {
|
|
8796
|
+
console.log(` ${green}✓${reset} Installed OpenCode plugin (bridges GSD hooks)`);
|
|
8797
|
+
} else {
|
|
8798
|
+
failures.push('plugins/gsd-core.js');
|
|
8799
|
+
}
|
|
8800
|
+
} else {
|
|
8801
|
+
failures.push('plugins/gsd-core.js');
|
|
8802
|
+
}
|
|
8803
|
+
}
|
|
9002
8804
|
} else if (isCline) {
|
|
9003
8805
|
// Cline local install: rules-based only — commands are embedded in .clinerules (generated below).
|
|
9004
8806
|
// No skills/commands directory needed for local installs.
|
|
9005
8807
|
// Global installs are handled above by _isSkillsRuntime (#782).
|
|
9006
8808
|
console.log(` ${green}✓${reset} Cline: commands will be available via .clinerules`);
|
|
9007
|
-
} else if (isGemini) {
|
|
9008
|
-
// #3037: when running --local --gemini and a GSD-managed user-scope
|
|
9009
|
-
// command directory already exists at ~/.gemini/commands/gsd/, skip
|
|
9010
|
-
// the local copy. Gemini conflict-detects by command name across
|
|
9011
|
-
// scopes and renames every overlapping /gsd:* command to
|
|
9012
|
-
// /workspace.gsd:* and /user.gsd:*, breaking the documented namespace.
|
|
9013
|
-
// The user-scope install already provides the same commands, so the
|
|
9014
|
-
// local copy adds zero value at the cost of namespace conflicts.
|
|
9015
|
-
//
|
|
9016
|
-
// CR #3041 (Major): the detection must be specific to PACKAGE-MANAGED
|
|
9017
|
-
// GSD content, not just "directory is non-empty". A user who hand-
|
|
9018
|
-
// dropped a single override (e.g. ~/.gemini/commands/gsd/my-override
|
|
9019
|
-
// .toml) would otherwise be unable to run a local install at all.
|
|
9020
|
-
// Detection rule: at least 3 of the canonical GSD command files
|
|
9021
|
-
// ('help.toml', 'progress.toml', 'new-project.toml') must be present.
|
|
9022
|
-
// These three ship in every GSD Gemini install (minimal mode included
|
|
9023
|
-
// — they're in the core skill set per #2790's consolidation), and 3-of-
|
|
9024
|
-
// 3 with that specific basename set is structurally impossible to
|
|
9025
|
-
// produce by accident.
|
|
9026
|
-
const homeGeminiGsd = path.join(os.homedir(), '.gemini', 'commands', 'gsd');
|
|
9027
|
-
const GSD_MANAGED_CANARIES = ['help.toml', 'progress.toml', 'new-project.toml'];
|
|
9028
|
-
const userScopeHasGsd =
|
|
9029
|
-
!isGlobal &&
|
|
9030
|
-
path.resolve(targetDir) !== path.resolve(path.join(os.homedir(), '.gemini')) &&
|
|
9031
|
-
fs.existsSync(homeGeminiGsd) &&
|
|
9032
|
-
GSD_MANAGED_CANARIES.every((f) =>
|
|
9033
|
-
fs.existsSync(path.join(homeGeminiGsd, f))
|
|
9034
|
-
);
|
|
9035
|
-
|
|
9036
|
-
if (userScopeHasGsd) {
|
|
9037
|
-
console.log(
|
|
9038
|
-
` ${yellow}⚠${reset} Skipping commands/gsd/ for local install — GSD is already installed at user scope (${homeGeminiGsd}).`
|
|
9039
|
-
);
|
|
9040
|
-
console.log(
|
|
9041
|
-
` Gemini conflict-detects across scopes and would rename every /gsd:* command to /workspace.gsd:* and /user.gsd:*.`
|
|
9042
|
-
);
|
|
9043
|
-
console.log(
|
|
9044
|
-
` The user-scope install already provides /gsd:* commands in this project; no local copy is needed.`
|
|
9045
|
-
);
|
|
9046
|
-
} else {
|
|
9047
|
-
const commandsDir = path.join(targetDir, 'commands');
|
|
9048
|
-
fs.mkdirSync(commandsDir, { recursive: true });
|
|
9049
|
-
const gsdSrc = _stageSkills(_commandsDir);
|
|
9050
|
-
const gsdDest = path.join(commandsDir, 'gsd');
|
|
9051
|
-
copyWithPathReplacement(gsdSrc, gsdDest, pathPrefix, runtime, true, isGlobal, targetDir);
|
|
9052
|
-
if (verifyInstalled(gsdDest, 'commands/gsd')) {
|
|
9053
|
-
console.log(` ${green}✓${reset} Installed commands/gsd`);
|
|
9054
|
-
} else {
|
|
9055
|
-
failures.push('commands/gsd');
|
|
9056
|
-
}
|
|
9057
|
-
}
|
|
9058
8809
|
} else {
|
|
9059
8810
|
// Claude Code local: flat gsd-<cmd>.md layout — Claude Code registers
|
|
9060
8811
|
// commands from .claude/commands/ using the filename stem as the command
|
|
@@ -9138,6 +8889,33 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9138
8889
|
failures.push('gsd-core');
|
|
9139
8890
|
}
|
|
9140
8891
|
|
|
8892
|
+
// Write the .gsd-source marker so runtime source resolution succeeds at
|
|
8893
|
+
// runtime (#1477). The Claude-global skills layout ships gsd-core/{bin,
|
|
8894
|
+
// contexts,references,templates,workflows} but NOT the commands/gsd source
|
|
8895
|
+
// tree, and _runLegacyUninstallCleanup actively removes any commands/gsd/
|
|
8896
|
+
// for that scope — so findInstallSourceRoot's walk-up has nothing to find
|
|
8897
|
+
// and /gsd-surface (list/status) throws. This is the writer half of the
|
|
8898
|
+
// marker that runtime-artifact-layout.cjs's finders already read (the reader
|
|
8899
|
+
// landed in #1476). It points at the package's own commands/gsd source.
|
|
8900
|
+
// Scoped to the Claude-global layout (issue #1477) — the only install path
|
|
8901
|
+
// that ships the skills layout without a commands/gsd source tree; every
|
|
8902
|
+
// other runtime/scope deploys commands/gsd, so its walk-up already resolves
|
|
8903
|
+
// and needs no marker. Guarded on source presence so a half-published
|
|
8904
|
+
// package never writes a dangling marker.
|
|
8905
|
+
if (runtime === 'claude' && isGlobal) {
|
|
8906
|
+
const gsdSourceCommands = path.join(src, 'commands', 'gsd');
|
|
8907
|
+
if (fs.existsSync(gsdSourceCommands)) {
|
|
8908
|
+
try {
|
|
8909
|
+
fs.writeFileSync(path.join(targetDir, '.gsd-source'), gsdSourceCommands + '\n', 'utf8');
|
|
8910
|
+
} catch (err) {
|
|
8911
|
+
// Non-fatal: install proceeds. But on the Claude-global layout walk-up
|
|
8912
|
+
// also fails (no commands/gsd source tree), so a silent write failure
|
|
8913
|
+
// still leaves /gsd-surface broken at runtime — warn so it's diagnosable.
|
|
8914
|
+
console.warn(` ${yellow}!${reset} Could not write .gsd-source marker (${err.message}); /gsd-surface list/status may fail`);
|
|
8915
|
+
}
|
|
8916
|
+
}
|
|
8917
|
+
}
|
|
8918
|
+
|
|
9141
8919
|
// #1629 critical fix: Windsurf workflow wrappers (convertClaudeCommandToWindsurfWorkflow)
|
|
9142
8920
|
// delegate to command bodies at <targetDir>/gsd-core/commands/gsd/${stem}.md via a
|
|
9143
8921
|
// hardcoded @~/.claude/gsd-core/commands/gsd/ path that _applyRuntimeRewrites rewrites
|
|
@@ -9287,8 +9065,6 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9287
9065
|
content = convertClaudeToOpencodeFrontmatter(content, { isAgent: true, modelOverride: _ocModelOverride });
|
|
9288
9066
|
} else if (isKilo) {
|
|
9289
9067
|
content = convertClaudeToKiloFrontmatter(content, { isAgent: true });
|
|
9290
|
-
} else if (isGemini) {
|
|
9291
|
-
content = convertClaudeToGeminiAgent(content);
|
|
9292
9068
|
} else if (isCodex) {
|
|
9293
9069
|
content = convertClaudeAgentToCodexAgent(content);
|
|
9294
9070
|
} else if (isCopilot) {
|
|
@@ -9317,12 +9093,12 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9317
9093
|
content = content.replace(/\.claude\//g, '.hermes/');
|
|
9318
9094
|
}
|
|
9319
9095
|
// #443 — Inject `effort:` into the Claude .md frontmatter ONLY.
|
|
9320
|
-
//
|
|
9096
|
+
// OpenCode/Qwen/Hermes also produce .md files but break on
|
|
9321
9097
|
// unknown frontmatter keys (the repo bans skills:/permissionMode: for
|
|
9322
9098
|
// the same reason — see tests/agent-frontmatter.test.cjs).
|
|
9323
9099
|
// Claude Code reads per-subagent `effort:` frontmatter (anthropics/claude-code #31536).
|
|
9324
9100
|
// Injection is per-runtime at install time because the canonical source
|
|
9325
|
-
// agents/*.md must stay
|
|
9101
|
+
// agents/*.md must stay runtime-safe (no effort: key in source).
|
|
9326
9102
|
if (runtime === 'claude') {
|
|
9327
9103
|
const _effortCfg = readGsdEffectiveEffortConfig(targetDir);
|
|
9328
9104
|
const _agentName = entry.name.replace(/\.md$/, '');
|
|
@@ -9334,8 +9110,8 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9334
9110
|
}
|
|
9335
9111
|
// #3677 — normalize retired `/gsd:<cmd>` colon refs in the agent body
|
|
9336
9112
|
// to the canonical hyphen form `/gsd-<cmd>` for hyphen-`name:`
|
|
9337
|
-
// runtimes (claude / qwen / hermes). Self-converting
|
|
9338
|
-
//
|
|
9113
|
+
// runtimes (claude / qwen / hermes). Self-converting and
|
|
9114
|
+
// colon-canonical runtimes are skipped by the predicate — see
|
|
9339
9115
|
// shouldNormalizeHyphenNamespaceInAgentBody above. Mirrors the
|
|
9340
9116
|
// SKILL.md-body fix shipped via #3629.
|
|
9341
9117
|
content = normalizeAgentBodyForRuntime(content, runtime, readGsdCommandNames());
|
|
@@ -9551,6 +9327,31 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9551
9327
|
}
|
|
9552
9328
|
}
|
|
9553
9329
|
|
|
9330
|
+
// Copy scripts/gen-capability-registry.cjs + scripts/gen-loop-host-contract.cjs —
|
|
9331
|
+
// required by gsd-core/bin/lib/capability-loader.cjs at overlay-composition time via
|
|
9332
|
+
// require('../../../scripts/gen-capability-registry.cjs') (which itself requires
|
|
9333
|
+
// gen-loop-host-contract.cjs). Without these, the loader's never-crash invariant
|
|
9334
|
+
// discards EVERY third-party capability overlay and silently falls back to the frozen
|
|
9335
|
+
// first-party registry, so installed capabilities are inert (#1920). Same class of
|
|
9336
|
+
// gap as #1223 (fix-slash-commands.cjs) and copied unconditionally for the same reason:
|
|
9337
|
+
// any runtime that installs gsd-core/ needs the capability system to compose.
|
|
9338
|
+
{
|
|
9339
|
+
const capGenDestDir = path.join(targetDir, 'scripts');
|
|
9340
|
+
fs.mkdirSync(capGenDestDir, { recursive: true });
|
|
9341
|
+
for (const gen of ['gen-capability-registry.cjs', 'gen-loop-host-contract.cjs']) {
|
|
9342
|
+
const genSrc = path.join(src, 'scripts', gen);
|
|
9343
|
+
const genDest = path.join(capGenDestDir, gen);
|
|
9344
|
+
if (!fs.existsSync(genSrc)) {
|
|
9345
|
+
failures.push(`scripts/${gen} (source missing from package — reinstall from npm)`);
|
|
9346
|
+
} else {
|
|
9347
|
+
fs.copyFileSync(genSrc, genDest);
|
|
9348
|
+
if (!verifyFileInstalled(genDest, `scripts/${gen}`)) {
|
|
9349
|
+
failures.push(`scripts/${gen}`);
|
|
9350
|
+
}
|
|
9351
|
+
}
|
|
9352
|
+
}
|
|
9353
|
+
}
|
|
9354
|
+
|
|
9554
9355
|
// Remove legacy get-shit-done-cc artifacts and stale update caches (#607).
|
|
9555
9356
|
// cleanupLegacyGsdCc handles both the legacy shared cache and the per-package
|
|
9556
9357
|
// cache (formerly an inline unlinkSync here). A cleanup failure must never
|
|
@@ -10092,7 +9893,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
10092
9893
|
// Configure statusline and hooks in settings.json (or settings.local.json for local Claude installs).
|
|
10093
9894
|
// ADR-857 phase 5f-2: drive the hook event dialect from the registry descriptor.
|
|
10094
9895
|
// runtimes with hookEvents='gemini' use AfterTool/BeforeTool; all others use PostToolUse/PreToolUse.
|
|
10095
|
-
// Equivalence: hookEvents='gemini' iff runtime
|
|
9896
|
+
// Equivalence: hookEvents='gemini' iff runtime===antigravity — identical to the old check.
|
|
10096
9897
|
// A missing registry or missing descriptor defaults to 'not gemini' → PostToolUse (safe).
|
|
10097
9898
|
const _hookEventsDialect = plan.hookEvents;
|
|
10098
9899
|
const postToolEvent = _hookEventsDialect === 'gemini' ? 'AfterTool' : 'PostToolUse';
|
|
@@ -10192,9 +9993,9 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
10192
9993
|
console.log(` ${green}✓${reset} Rewrote legacy bare-node managed-hook commands to absolute path (#2979)`);
|
|
10193
9994
|
}
|
|
10194
9995
|
// Local installs anchor hook paths so they resolve regardless of cwd (#1906).
|
|
10195
|
-
// Claude Code sets $CLAUDE_PROJECT_DIR;
|
|
10196
|
-
// Windows
|
|
10197
|
-
//
|
|
9996
|
+
// Claude Code sets $CLAUDE_PROJECT_DIR; Antigravity does not — and on
|
|
9997
|
+
// Windows its own substitution logic doubles the path (#2557). It runs
|
|
9998
|
+
// project hooks with the project dir as cwd, so bare relative paths work.
|
|
10198
9999
|
const localPrefix = projectLocalHookPrefix({ runtime, dirName });
|
|
10199
10000
|
const hookOpts = { portableHooks: hasPortableHooks, runtime };
|
|
10200
10001
|
// #2979: local-install hook commands also use the absolute node path so
|
|
@@ -10258,17 +10059,6 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
10258
10059
|
console.warn(` ${yellow}⚠${reset} Skipping managed JS hook registration — Node executable path unavailable (process.execPath is empty). See #2979 / #3002.`);
|
|
10259
10060
|
}
|
|
10260
10061
|
|
|
10261
|
-
// Enable experimental agents for Gemini CLI (required for custom sub-agents)
|
|
10262
|
-
if (isGemini) {
|
|
10263
|
-
if (!settings.experimental) {
|
|
10264
|
-
settings.experimental = {};
|
|
10265
|
-
}
|
|
10266
|
-
if (!settings.experimental.enableAgents) {
|
|
10267
|
-
settings.experimental.enableAgents = true;
|
|
10268
|
-
console.log(` ${green}✓${reset} Enabled experimental agents`);
|
|
10269
|
-
}
|
|
10270
|
-
}
|
|
10271
|
-
|
|
10272
10062
|
// Register all GSD-managed hook entries into settings.hooks.* for runtimes
|
|
10273
10063
|
// that use the settings.json hook surface (ADR-857 phase 5f-1b).
|
|
10274
10064
|
// settings is mutated in place by applySettingsJsonHooks.
|
|
@@ -10291,23 +10081,6 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
10291
10081
|
localShellCmd,
|
|
10292
10082
|
});
|
|
10293
10083
|
|
|
10294
|
-
// ── Gemini hooksConfig.enabled check (#776) ───────────────────────────────
|
|
10295
|
-
// Detect `hooksConfig.enabled: false` in the already-loaded settings object
|
|
10296
|
-
// and emit a clear warning. When this field is false the Gemini CLI silently
|
|
10297
|
-
// disables ALL hook execution — gsd hooks are registered but will never run.
|
|
10298
|
-
// The check is read-only (warning only; we do not mutate hooksConfig).
|
|
10299
|
-
// Note: we use the in-memory `settings` object (already read from disk and
|
|
10300
|
-
// cleaned up by validateHookFields/cleanupOrphanedHooks above) rather than
|
|
10301
|
-
// re-reading settings.json, avoiding a TOCTOU window between the two reads.
|
|
10302
|
-
if (isGemini && settings && settings.hooksConfig && settings.hooksConfig.enabled === false) {
|
|
10303
|
-
console.warn(
|
|
10304
|
-
` ${yellow}⚠${reset} Warning: hooksConfig.enabled is false in your Gemini settings.json.\n` +
|
|
10305
|
-
` gsd-core hooks are registered but will NOT run until you set\n` +
|
|
10306
|
-
` hooksConfig.enabled: true in ${path.join(targetDir, 'settings.json')}.`
|
|
10307
|
-
);
|
|
10308
|
-
}
|
|
10309
|
-
// ── end hooksConfig.enabled check ────────────────────────────────────────
|
|
10310
|
-
|
|
10311
10084
|
// Compute the update-banner hook command alongside the others so
|
|
10312
10085
|
// installAllRuntimes can register it at finalize time when the user opts
|
|
10313
10086
|
// in (#2795). Computed here (not in finishInstall) so the same buildHookCommand
|
|
@@ -10396,7 +10169,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
10396
10169
|
* Apply statusline config, then print completion message
|
|
10397
10170
|
*/
|
|
10398
10171
|
function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline, runtime = 'claude', isGlobal = true, configDir = null, bannerOpts = {}) {
|
|
10399
|
-
const { isOpencode, isKilo,
|
|
10172
|
+
const { isOpencode, isKilo, isCodex, isCopilot, isAntigravity, isCursor, isWindsurf, isAugment, isTrae, isQwen, isHermes, isCodebuddy, isCline, isKimi } = runtimeFlags(runtime);
|
|
10400
10173
|
const plan = resolveInstallPlan(runtime);
|
|
10401
10174
|
|
|
10402
10175
|
if (shouldInstallStatusline && plan.writesSharedSettings && !isOpencode) {
|
|
@@ -10452,7 +10225,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
10452
10225
|
|
|
10453
10226
|
// #768 — Pre-populate permissions.allow/deny for Claude Code installs.
|
|
10454
10227
|
// Merges GSD-owned entries non-destructively (preserves existing user permissions).
|
|
10455
|
-
// Scoped to Claude only:
|
|
10228
|
+
// Scoped to Claude only: antigravity/qwen/hermes/codebuddy also write
|
|
10456
10229
|
// settings.json but use different runtimes and do not use these permission strings.
|
|
10457
10230
|
if (runtime === 'claude') {
|
|
10458
10231
|
mergeClaudePermissions(settings);
|
|
@@ -10622,17 +10395,16 @@ const runtimeMap = {
|
|
|
10622
10395
|
'6': 'codex',
|
|
10623
10396
|
'7': 'copilot',
|
|
10624
10397
|
'8': 'cursor',
|
|
10625
|
-
'9': '
|
|
10626
|
-
'10': '
|
|
10627
|
-
'11': '
|
|
10628
|
-
'12': '
|
|
10629
|
-
'13': '
|
|
10630
|
-
'14': '
|
|
10631
|
-
'15': '
|
|
10632
|
-
'16': 'windsurf'
|
|
10398
|
+
'9': 'hermes',
|
|
10399
|
+
'10': 'kimi',
|
|
10400
|
+
'11': 'kilo',
|
|
10401
|
+
'12': 'opencode',
|
|
10402
|
+
'13': 'qwen',
|
|
10403
|
+
'14': 'trae',
|
|
10404
|
+
'15': 'windsurf'
|
|
10633
10405
|
};
|
|
10634
|
-
const allRuntimes = ['claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex', 'copilot', 'cursor', '
|
|
10635
|
-
const ALL_RUNTIMES_OPTION = '
|
|
10406
|
+
const allRuntimes = ['claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex', 'copilot', 'cursor', 'hermes', 'kimi', 'kilo', 'opencode', 'qwen', 'trae', 'windsurf'];
|
|
10407
|
+
const ALL_RUNTIMES_OPTION = '16';
|
|
10636
10408
|
|
|
10637
10409
|
/**
|
|
10638
10410
|
* Build the runtime-selection prompt text shown by the interactive installer.
|
|
@@ -10648,15 +10420,14 @@ function buildRuntimePromptText() {
|
|
|
10648
10420
|
${cyan}6${reset}) Codex ${dim}(~/.codex)${reset}
|
|
10649
10421
|
${cyan}7${reset}) Copilot ${dim}(~/.copilot)${reset}
|
|
10650
10422
|
${cyan}8${reset}) Cursor ${dim}(~/.cursor)${reset}
|
|
10651
|
-
${cyan}9${reset})
|
|
10652
|
-
${cyan}10${reset})
|
|
10653
|
-
${cyan}11${reset})
|
|
10654
|
-
${cyan}12${reset})
|
|
10655
|
-
${cyan}13${reset})
|
|
10656
|
-
${cyan}14${reset})
|
|
10657
|
-
${cyan}15${reset})
|
|
10658
|
-
${cyan}16${reset})
|
|
10659
|
-
${cyan}17${reset}) All
|
|
10423
|
+
${cyan}9${reset}) Hermes Agent ${dim}(~/.hermes)${reset}
|
|
10424
|
+
${cyan}10${reset}) Kimi ${dim}(~/.config/agents, then ~/.agents if existing)${reset}
|
|
10425
|
+
${cyan}11${reset}) Kilo ${dim}(~/.config/kilo)${reset}
|
|
10426
|
+
${cyan}12${reset}) OpenCode ${dim}(~/.config/opencode)${reset}
|
|
10427
|
+
${cyan}13${reset}) Qwen Code ${dim}(~/.qwen)${reset}
|
|
10428
|
+
${cyan}14${reset}) Trae ${dim}(~/.trae)${reset}
|
|
10429
|
+
${cyan}15${reset}) Windsurf ${dim}(~/.codeium/windsurf)${reset}
|
|
10430
|
+
${cyan}16${reset}) All
|
|
10660
10431
|
|
|
10661
10432
|
${dim}Select multiple: 1,2,6 or 1 2 6${reset}
|
|
10662
10433
|
`;
|
|
@@ -10667,7 +10438,7 @@ function buildRuntimePromptText() {
|
|
|
10667
10438
|
* Pure function — exported so tests can verify split/dedupe/fallback behavior.
|
|
10668
10439
|
* - Accepts comma- and/or whitespace-separated choices
|
|
10669
10440
|
* - Deduplicates while preserving order
|
|
10670
|
-
* - Maps option
|
|
10441
|
+
* - Maps option 16 ("All") to every runtime
|
|
10671
10442
|
* - Falls back to ['claude'] when nothing valid is selected
|
|
10672
10443
|
*/
|
|
10673
10444
|
function parseRuntimeInput(answer) {
|
|
@@ -11307,7 +11078,7 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
|
|
11307
11078
|
throw error;
|
|
11308
11079
|
}
|
|
11309
11080
|
|
|
11310
|
-
const statuslineRuntimes = ['claude'
|
|
11081
|
+
const statuslineRuntimes = ['claude'];
|
|
11311
11082
|
const primaryStatuslineResult = results.find(r => statuslineRuntimes.includes(r.runtime));
|
|
11312
11083
|
|
|
11313
11084
|
const finalize = (shouldInstallStatusline, shouldInstallBanner) => {
|
|
@@ -11400,10 +11171,6 @@ module.exports = {
|
|
|
11400
11171
|
convertClaudeCommandToCursorSkill,
|
|
11401
11172
|
convertClaudeCommandToCursorCommand,
|
|
11402
11173
|
convertClaudeAgentToCursorAgent,
|
|
11403
|
-
convertClaudeToGeminiMarkdown,
|
|
11404
|
-
convertSlashCommandsToGeminiMentions,
|
|
11405
|
-
_resetGsdCommandRoster,
|
|
11406
|
-
convertClaudeToGeminiAgent,
|
|
11407
11174
|
convertClaudeAgentToCodexAgent,
|
|
11408
11175
|
generateCodexAgentToml,
|
|
11409
11176
|
cleanupCodexSkillMetadataSidecars,
|