@opengsd/gsd-core 1.11.0 → 1.12.0
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 +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +17 -9
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +0 -1
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +3 -1
- package/agents/gsd-plan-checker.md +15 -55
- package/agents/gsd-planner.md +6 -4
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +2 -2
- package/agents/gsd-roadmapper.md +15 -11
- package/agents/gsd-ui-checker.md +63 -4
- package/agents/gsd-ui-researcher.md +41 -3
- package/agents/gsd-verifier.md +1 -1
- package/bin/install.js +609 -134
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/quick.md +8 -4
- package/gsd-core/bin/gsd-tools.cjs +567 -51
- package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
- package/gsd-core/bin/lib/api-coverage.cjs +30 -9
- package/gsd-core/bin/lib/artifacts.cjs +2 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
- package/gsd-core/bin/lib/audit.cjs +163 -41
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +336 -95
- package/gsd-core/bin/lib/capability-state.cjs +18 -3
- package/gsd-core/bin/lib/capability-validator.cjs +205 -18
- package/gsd-core/bin/lib/check-command-router.cjs +145 -5
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +543 -44
- package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
- package/gsd-core/bin/lib/config-loader.cjs +118 -29
- package/gsd-core/bin/lib/config.cjs +92 -2
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +84 -7
- package/gsd-core/bin/lib/edge-probe.cjs +9 -1
- package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
- package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
- package/gsd-core/bin/lib/frontmatter.cjs +840 -305
- package/gsd-core/bin/lib/gap-checker.cjs +27 -3
- package/gsd-core/bin/lib/git-base-branch.cjs +174 -39
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +6 -3
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
- package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
- package/gsd-core/bin/lib/host-integration.cjs +39 -6
- package/gsd-core/bin/lib/init-command-router.cjs +118 -21
- package/gsd-core/bin/lib/init.cjs +120 -41
- package/gsd-core/bin/lib/install-engine.cjs +68 -3
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -1
- package/gsd-core/bin/lib/install-profiles.cjs +78 -4
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +160 -15
- package/gsd-core/bin/lib/learnings.cjs +85 -14
- package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
- package/gsd-core/bin/lib/markdown-table.cjs +52 -4
- package/gsd-core/bin/lib/milestone.cjs +90 -5
- package/gsd-core/bin/lib/model-catalog.cjs +177 -19
- package/gsd-core/bin/lib/model-resolver.cjs +10 -28
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
- package/gsd-core/bin/lib/phase-id.cjs +70 -4
- package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
- package/gsd-core/bin/lib/phase-locator.cjs +138 -17
- package/gsd-core/bin/lib/phase.cjs +405 -84
- package/gsd-core/bin/lib/plan-document.cjs +263 -0
- package/gsd-core/bin/lib/plan-scan.cjs +13 -2
- package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +18 -14
- package/gsd-core/bin/lib/planning-workspace.cjs +56 -0
- package/gsd-core/bin/lib/probe-core.cjs +4 -1
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
- package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
- package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +9 -9
- package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
- package/gsd-core/bin/lib/roadmap-parser.cjs +79 -16
- package/gsd-core/bin/lib/roadmap.cjs +74 -19
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +96 -8
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +34 -1
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +287 -55
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +71 -8
- package/gsd-core/bin/lib/smart-entry.cjs +12 -22
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +47 -18
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +186 -0
- package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
- package/gsd-core/bin/lib/state-transition.cjs +517 -101
- package/gsd-core/bin/lib/state.cjs +946 -163
- package/gsd-core/bin/lib/surface.cjs +10 -2
- package/gsd-core/bin/lib/task-command-router.cjs +111 -1
- package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
- package/gsd-core/bin/lib/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
- package/gsd-core/bin/lib/uat.cjs +1376 -125
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
- package/gsd-core/bin/lib/unusable-input.cjs +13 -0
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/vendor/README.md +43 -5
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/verification.cjs +14 -1
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify.cjs +95 -40
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +177 -21
- package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
- package/gsd-core/bin/shared/exit-codes.json +8 -0
- package/gsd-core/bin/shared/exit-codes.sh +20 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -1
- package/gsd-core/references/agent-contracts.md +3 -2
- package/gsd-core/references/api-coverage.md +24 -2
- package/gsd-core/references/autonomous-smart-discuss.md +3 -3
- package/gsd-core/references/checkpoints.md +37 -19
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/edge-probe.md +8 -0
- package/gsd-core/references/execute-mvp-tdd.md +1 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
- package/gsd-core/references/execute-phase-wave-guard.md +11 -9
- package/gsd-core/references/failing-direction.md +78 -0
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-integration.md +5 -5
- package/gsd-core/references/git-planning-commit.md +3 -3
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +22 -0
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/nyquist-compliance.md +74 -0
- package/gsd-core/references/offer-next.md +3 -5
- package/gsd-core/references/phase-argument-parsing.md +3 -3
- package/gsd-core/references/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-revision.md +1 -1
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +37 -8
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/tdd.md +1 -3
- package/gsd-core/references/ui-brand.md +65 -21
- package/gsd-core/references/ui-consideration-probe.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +2 -2
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +11 -11
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/SECURITY.md +3 -3
- package/gsd-core/templates/UI-SPEC.md +25 -3
- package/gsd-core/templates/VALIDATION.md +3 -3
- package/gsd-core/templates/phase-prompt.md +3 -0
- package/gsd-core/templates/state.md +7 -0
- 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 +3 -3
- package/gsd-core/workflows/add-tests.md +3 -8
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +4 -9
- package/gsd-core/workflows/audit-fix.md +12 -3
- package/gsd-core/workflows/audit-milestone.md +9 -9
- package/gsd-core/workflows/audit-uat.md +17 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +10 -26
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +2 -2
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +1 -1
- package/gsd-core/workflows/code-review-fix.md +1 -1
- package/gsd-core/workflows/code-review.md +121 -40
- package/gsd-core/workflows/complete-milestone.md +15 -10
- package/gsd-core/workflows/debug.md +5 -3
- package/gsd-core/workflows/diagnose-issues.md +12 -6
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +3 -7
- package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +2 -2
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +3 -6
- package/gsd-core/workflows/docs-update.md +5 -4
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +4 -9
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +113 -11
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +22 -4
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase.md +38 -54
- package/gsd-core/workflows/execute-plan.md +17 -12
- package/gsd-core/workflows/explore.md +1 -1
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/fast.md +2 -2
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +5 -5
- package/gsd-core/workflows/health.md +3 -6
- package/gsd-core/workflows/import.md +14 -11
- package/gsd-core/workflows/inbox.md +4 -5
- package/gsd-core/workflows/ingest-docs.md +44 -11
- package/gsd-core/workflows/insert-phase.md +5 -5
- package/gsd-core/workflows/list-seeds.md +5 -3
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +12 -23
- package/gsd-core/workflows/map-codebase.md +1 -1
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +2 -2
- package/gsd-core/workflows/new-milestone.md +9 -21
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +12 -26
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +2 -2
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
- package/gsd-core/workflows/plan-phase.md +121 -42
- package/gsd-core/workflows/plan-review-convergence.md +46 -9
- package/gsd-core/workflows/plant-seed.md +2 -2
- package/gsd-core/workflows/pr-branch.md +187 -51
- package/gsd-core/workflows/profile-user.md +16 -14
- package/gsd-core/workflows/progress.md +27 -12
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -3
- package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
- package/gsd-core/workflows/quick/steps/research-phase.md +2 -4
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick.md +20 -29
- package/gsd-core/workflows/remove-phase.md +4 -4
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +8 -12
- package/gsd-core/workflows/review.md +193 -15
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +2 -2
- package/gsd-core/workflows/settings-advanced.md +7 -9
- package/gsd-core/workflows/settings-integrations.md +64 -31
- package/gsd-core/workflows/settings.md +3 -5
- package/gsd-core/workflows/ship.md +12 -6
- package/gsd-core/workflows/sketch-wrap-up.md +11 -17
- package/gsd-core/workflows/sketch.md +12 -18
- package/gsd-core/workflows/smart-entry.md +3 -5
- package/gsd-core/workflows/spec-phase.md +23 -1
- package/gsd-core/workflows/spike-wrap-up.md +7 -11
- package/gsd-core/workflows/spike.md +20 -31
- package/gsd-core/workflows/stats.md +2 -2
- package/gsd-core/workflows/sync-skills.md +1 -1
- package/gsd-core/workflows/thread.md +11 -7
- package/gsd-core/workflows/transition.md +5 -5
- package/gsd-core/workflows/ui-phase.md +10 -16
- package/gsd-core/workflows/ui-review.md +6 -10
- package/gsd-core/workflows/ultraplan-phase.md +5 -13
- package/gsd-core/workflows/undo.md +8 -16
- package/gsd-core/workflows/update.md +6 -10
- package/gsd-core/workflows/validate-phase.md +2 -2
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +57 -18
- package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +19 -10
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
- package/hooks/dist/gsd-cursor-session-start.js +2 -1
- package/hooks/dist/gsd-cursor-stop.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
- package/hooks/dist/gsd-graphify-update.sh +22 -18
- package/hooks/dist/gsd-node-runner.sh +76 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +16 -7
- package/hooks/dist/gsd-read-guard.js +16 -7
- package/hooks/dist/gsd-read-injection-scanner.js +17 -8
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +215 -26
- package/hooks/dist/gsd-validate-commit.sh +80 -6
- package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
- package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
- package/hooks/dist/gsd-workflow-guard.js +34 -16
- package/hooks/dist/gsd-worktree-path-guard.js +36 -21
- package/hooks/dist/gsd-write-guard.js +35 -25
- package/hooks/dist/lib/cli-exit.js +560 -0
- package/hooks/dist/lib/exit-code-registry.js +98 -0
- package/hooks/dist/lib/git-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-agent-isolation-guard.js +77 -38
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +19 -10
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +3 -1
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +28 -23
- package/hooks/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +76 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +16 -7
- package/hooks/gsd-read-guard.js +16 -7
- package/hooks/gsd-read-injection-scanner.js +17 -8
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +215 -26
- package/hooks/gsd-validate-commit.sh +80 -6
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +34 -16
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +12 -7
- package/scripts/base64-scan.sh +74 -12
- package/scripts/build-hooks.js +5 -0
- package/scripts/check-glossary-refs.cjs +77 -15
- package/scripts/check-mutation-score-ratchet.cjs +156 -0
- package/scripts/ci-check-job-near-cap.cjs +49 -0
- package/scripts/ci-pr-mergeability.cjs +262 -0
- package/scripts/ci-test-scope.cjs +45 -12
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/docs-guard-registry.cjs +396 -0
- package/scripts/gen-capability-registry.cjs +8 -6
- package/scripts/gen-exit-code-docs.cjs +318 -0
- package/scripts/gen-exit-code-registry.cjs +891 -0
- package/scripts/gen-features.cjs +836 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-loop-host-contract.cjs +134 -1
- package/scripts/gen-scripts-cli-exit.cjs +185 -0
- package/scripts/gen-state-md-docs.cjs +727 -0
- package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +32 -2
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +21 -8
- package/scripts/lint-planning-prompt-drift.cjs +38 -1
- package/scripts/lint-removed-but-needed.cjs +184 -16
- package/scripts/lint-seam-enforcement.cjs +182 -0
- package/scripts/lint-slug-derivation-drift.cjs +921 -0
- package/scripts/lint-source-test-name-collision.cjs +241 -0
- package/scripts/lint-state-write-path-drift.cjs +337 -432
- package/scripts/lint-test-file-count.allowlist.json +122 -4
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +51 -64
- package/scripts/lint-vendored-deps.cjs +208 -35
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/prompt-injection-scan.sh +75 -14
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +22 -3
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-quick/SKILL.md +8 -4
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -109
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/state-write-path-drift-baseline.json +0 -19
package/bin/install.js
CHANGED
|
@@ -41,6 +41,7 @@ const {
|
|
|
41
41
|
// consentRequired, hostPrecedenceRank) instead of the id being re-derived
|
|
42
42
|
// and re-interpreted at each call site. See src/install-scope.cts.
|
|
43
43
|
const { resolveScope } = require('../gsd-core/bin/lib/install-scope.cjs');
|
|
44
|
+
const { isTestHomeGuardRefusal } = require('../gsd-core/bin/lib/real-home-guard.cjs');
|
|
44
45
|
// getDirName (runtime -> local config dir name) is relocated out of this
|
|
45
46
|
// installer to the runtime-name-policy leaf (ADR-1508 / #1510 Phase 1) so the
|
|
46
47
|
// conversion module's rewrite engine can consume it without importing
|
|
@@ -410,7 +411,7 @@ const GSD_CHANGESET_FILES = [
|
|
|
410
411
|
'github-release-notes.cjs', 'lint.cjs', 'new.cjs',
|
|
411
412
|
'README.md', // documentation only — not user-authored
|
|
412
413
|
];
|
|
413
|
-
const GSD_SCRIPTS_LIB_FILES = ['cli-exit.cjs', 'allowlist-ratchet.cjs', 'drift-scan.cjs', 'alias-drift-families.cjs'];
|
|
414
|
+
const GSD_SCRIPTS_LIB_FILES = ['cli-exit.cjs', 'allowlist-ratchet.cjs', 'drift-scan.cjs', 'alias-drift-families.cjs', 'exit-code-registry.cjs', 'ndjson-reporter.cjs', 'ci-job-timing.cjs'];
|
|
414
415
|
|
|
415
416
|
/**
|
|
416
417
|
* Resolve a runtime's shared-hooks directory name from its descriptor.
|
|
@@ -459,19 +460,32 @@ function resolveSharedHooksDirName(runtime) {
|
|
|
459
460
|
return name;
|
|
460
461
|
}
|
|
461
462
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
463
|
+
// #3897 rung 3 — sandbox_mode derivation, the hold list, and the hold-roster
|
|
464
|
+
// validator now live in `src/codex-agent-toml.cts` (compiled to
|
|
465
|
+
// `gsd-core/bin/lib/codex-agent-toml.cjs`), NOT here. This module used to be
|
|
466
|
+
// the sole owner, and `agent-install-check.cts`'s `checkCodexSandboxPosture`
|
|
467
|
+
// lazily `require()`d THIS FILE to reach `deriveCodexSandboxMode` — but
|
|
468
|
+
// requiring `bin/install.js` runs its whole top-level script, including the
|
|
469
|
+
// CLI's ASCII banner print to stdout, which corrupted every stdout-JSON
|
|
470
|
+
// caller downstream of that posture check (`gsd-tools validate agents`).
|
|
471
|
+
// `codex-agent-toml.cjs` is a genuine leaf (no top-level side effects), so
|
|
472
|
+
// both this file and `agent-install-check.cts` import the derivation from
|
|
473
|
+
// there — ONE owner, no second predicate. See that module's header for the
|
|
474
|
+
// full rationale, and CAUSE B (below, `installCodexConfig`) for the removal
|
|
475
|
+
// of `validateCodexSandboxHolds`'s call from the install runtime path.
|
|
476
|
+
const {
|
|
477
|
+
CODEX_SANDBOX_HOLDS,
|
|
478
|
+
deriveCodexSandboxMode,
|
|
479
|
+
validateCodexSandboxHolds,
|
|
480
|
+
// #3897 list-form parse fix, Fix 3 (generative-fix-divergence): this
|
|
481
|
+
// file's own `generateCodexAgentToml` used to pull `tools:` via its
|
|
482
|
+
// private `extractFrontmatterField` (single-line only) instead of this
|
|
483
|
+
// shared reader — the two sandbox-feeding paths (this emitter and
|
|
484
|
+
// `agent-install-check.cts`'s `checkCodexSandboxPosture`) silently
|
|
485
|
+
// disagreed on YAML block-list `tools:` form. Both now route through this
|
|
486
|
+
// ONE extractor.
|
|
487
|
+
extractToolsValue,
|
|
488
|
+
} = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'codex-agent-toml.cjs'));
|
|
475
489
|
|
|
476
490
|
// Copilot tool name mapping — Claude Code tools to GitHub Copilot tools
|
|
477
491
|
// Tool mapping applies ONLY to agents, NOT to skills (per CONTEXT.md decision)
|
|
@@ -668,6 +682,68 @@ function _resolveScopeSafe(id, runtime) {
|
|
|
668
682
|
}
|
|
669
683
|
}
|
|
670
684
|
|
|
685
|
+
/**
|
|
686
|
+
* Resolve a layout kind's on-disk destination directory. Shared by the
|
|
687
|
+
* skills-root resolution above and the #3664 warning below so the
|
|
688
|
+
* home-override + destSubpath join exists once (#3659-class re-encoding guard).
|
|
689
|
+
*/
|
|
690
|
+
function _kindDestDir(layout, kindName, targetDir) {
|
|
691
|
+
const kind = layout.kinds.find((k) => k.kind === kindName);
|
|
692
|
+
if (!kind) return null;
|
|
693
|
+
return path.join(kind.home || targetDir, kind.destSubpath);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* #3738: scope-aware, layout-resolving wrapper over _kindDestDir for callers
|
|
698
|
+
* that have (runtime, configDir, scope) rather than a resolved Layout — the
|
|
699
|
+
* writeManifest agents surface being the first. Never throws: a runtime whose
|
|
700
|
+
* layout cannot be resolved (unknown id, descriptor error) keeps the caller's
|
|
701
|
+
* own fallback rather than losing the manifest.
|
|
702
|
+
*/
|
|
703
|
+
function _kindDestDirSafe(runtime, configDir, scope, kindName) {
|
|
704
|
+
try {
|
|
705
|
+
return _kindDestDir(resolveRuntimeArtifactLayout(runtime, configDir, scope), kindName, configDir);
|
|
706
|
+
} catch {
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* #3664 — warn (never refuse) when `--config-dir` points the install at a
|
|
713
|
+
* directory whose agent destination already holds FOREIGN (non-GSD) agent
|
|
714
|
+
* files — the fingerprint of another harness's config home (e.g. ~/.junie,
|
|
715
|
+
* ~/.factory) or a hand-curated agents dir. GSD emits the selected runtime's
|
|
716
|
+
* artifacts verbatim: tool IDs (`Skill`) and MCP grants (`mcp__server__tool`)
|
|
717
|
+
* that are inert or invalid in a foreign harness surface only at dispatch
|
|
718
|
+
* time, months later. Warn-and-proceed is the issue-sanctioned option (b):
|
|
719
|
+
* a fresh custom dir (the documented brand-specific-dir use), a gsd-only dir
|
|
720
|
+
* (updates, the --all shared dir — including kimi's root `gsd.md`, which is
|
|
721
|
+
* GSD-owned despite the bare `gsd` stem), and the no-flag default-home path
|
|
722
|
+
* (users keep personal agents in ~/.claude/agents) all stay silent. Degrades
|
|
723
|
+
* silently on any resolution failure — the warn path never blocks install.
|
|
724
|
+
*/
|
|
725
|
+
function warnIfForeignAgentDest(runtime, targetDir, scope, explicitConfigDir) {
|
|
726
|
+
if (explicitConfigDir !== true) return;
|
|
727
|
+
try {
|
|
728
|
+
const layout = resolveRuntimeArtifactLayout(runtime, targetDir, scope);
|
|
729
|
+
// kimi's global agents kind is `kimi-agents` (#2095 EoS), every other
|
|
730
|
+
// runtime's is `agents`.
|
|
731
|
+
const agentsDir = _kindDestDir(layout, 'agents', targetDir)
|
|
732
|
+
|| _kindDestDir(layout, 'kimi-agents', targetDir);
|
|
733
|
+
if (!agentsDir) return;
|
|
734
|
+
if (!fs.existsSync(agentsDir) || !fs.statSync(agentsDir).isDirectory()) return;
|
|
735
|
+
const foreign = fs
|
|
736
|
+
.readdirSync(agentsDir)
|
|
737
|
+
.filter((f) => f.endsWith('.md') && !f.startsWith('gsd-') && f !== 'gsd.md');
|
|
738
|
+
if (foreign.length === 0) return;
|
|
739
|
+
console.log(
|
|
740
|
+
` ${yellow}⚠${reset} ${bold}${targetDir}${reset} already contains ${foreign.length} non-GSD agent file(s) — this may be another harness's config home. GSD emits artifacts shaped for ${runtime}: tool IDs and MCP grants may be inert or invalid for whatever harness reads this directory (#3664).`
|
|
741
|
+
);
|
|
742
|
+
} catch (_) {
|
|
743
|
+
/* never block install on the warning path */
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
671
747
|
/**
|
|
672
748
|
* Resolve the ACTUAL on-disk skills-install directory for a runtime, honoring a
|
|
673
749
|
* skills-kind `home` override (ADR-1239 upgrade 3 / #2088: e.g. Codex skills ->
|
|
@@ -678,8 +754,8 @@ function _resolveScopeSafe(id, runtime) {
|
|
|
678
754
|
function _resolveSkillsRootDir(runtime, targetDir, scope) {
|
|
679
755
|
try {
|
|
680
756
|
const layout = resolveRuntimeArtifactLayout(runtime, targetDir, scope);
|
|
681
|
-
const
|
|
682
|
-
if (
|
|
757
|
+
const skillsDir = _kindDestDir(layout, 'skills', targetDir);
|
|
758
|
+
if (skillsDir) return skillsDir;
|
|
683
759
|
} catch (_e) { /* fall through to the configDir default */ }
|
|
684
760
|
return path.join(targetDir, 'skills');
|
|
685
761
|
}
|
|
@@ -700,6 +776,7 @@ function _runtimeAdapter(runtime) {
|
|
|
700
776
|
}
|
|
701
777
|
}
|
|
702
778
|
const {
|
|
779
|
+
acquireInstallMigrationLock,
|
|
703
780
|
applyInstallerMigrationPlan,
|
|
704
781
|
discoverInstallerMigrations,
|
|
705
782
|
MANIFEST_SCHEMA_VERSION,
|
|
@@ -812,6 +889,17 @@ const hasSkillsRoot = args.includes('--skills-root');
|
|
|
812
889
|
const hasPortableHooks = args.includes('--portable-hooks') || process.env.GSD_PORTABLE_HOOKS === '1';
|
|
813
890
|
const hasMinimal = args.includes('--minimal') || args.includes('--core-only');
|
|
814
891
|
const hasDryRun = args.includes('--dry-run');
|
|
892
|
+
// #3031: opt-in reclaim of the GSD artifacts a PRE-#2755 `--kimi-code` install
|
|
893
|
+
// orphaned in Kimi CLI's `~/.kimi`. Opt-in and not automatic because the stale
|
|
894
|
+
// block is BYTE-IDENTICAL to a legitimate Kimi CLI one — both runtimes render
|
|
895
|
+
// the same bytes for the same root, since the command paths derive from the
|
|
896
|
+
// hooks root and not from the runtime — so no inspection can tell "litter GSD
|
|
897
|
+
// wrote for kimi-code" from "Kimi CLI's working hooks". Cleaning unasked would
|
|
898
|
+
// break #2755's own acceptance criterion ("Uninstalling GSD hooks for one
|
|
899
|
+
// runtime does not touch or remove the other runtime's hooks") for anyone with
|
|
900
|
+
// both products installed. The user, who knows which products they run, is the
|
|
901
|
+
// only party that can decide — so they ask for it explicitly.
|
|
902
|
+
const hasReclaimKimiLegacy = args.includes('--reclaim-kimi-legacy');
|
|
815
903
|
// --profile=<name> or --profile=<n1>,<n2> (composable); mutually exclusive with --minimal
|
|
816
904
|
const _profileArgRaw = (() => {
|
|
817
905
|
for (const arg of args) {
|
|
@@ -911,6 +999,21 @@ function disambiguateKimiVariant(runtimes) {
|
|
|
911
999
|
return notices;
|
|
912
1000
|
}
|
|
913
1001
|
|
|
1002
|
+
// #3031: `--reclaim-kimi-legacy` only ever acts inside the kimi-code GLOBAL
|
|
1003
|
+
// install branch. Say so when it cannot act, rather than exiting 0 having
|
|
1004
|
+
// silently done nothing: the user asked for a cleanup, and silence is
|
|
1005
|
+
// indistinguishable from "it ran and found nothing". Not a hard error — it
|
|
1006
|
+
// stays composable with `--all`, where it is legitimately inert for the other
|
|
1007
|
+
// seventeen runtimes.
|
|
1008
|
+
if (hasReclaimKimiLegacy && !selectedRuntimes.includes('kimi-code')) {
|
|
1009
|
+
console.error(`${yellow}⚠ --reclaim-kimi-legacy ignored${reset} — it applies only to a --kimi-code install; nothing in ~/.kimi was touched.`);
|
|
1010
|
+
} else if (hasReclaimKimiLegacy && hasLocal) {
|
|
1011
|
+
// Scope, checked HERE rather than inside install(): kimi-code declares
|
|
1012
|
+
// hostBehaviors.localInstallDeferred, so install() returns early long before
|
|
1013
|
+
// the kimi-hooks-toml branch — a warning placed there would be unreachable.
|
|
1014
|
+
console.error(`${yellow}⚠ --reclaim-kimi-legacy ignored${reset} — the legacy root is a global location; re-run with --global to reclaim it.`);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
914
1017
|
if (selectedRuntimes.includes('kimi') || selectedRuntimes.includes('kimi-code')) {
|
|
915
1018
|
const kimiNotices = disambiguateKimiVariant(selectedRuntimes);
|
|
916
1019
|
for (const notice of kimiNotices) {
|
|
@@ -1075,6 +1178,14 @@ function parseConfigDirFromArgs(argsArray) {
|
|
|
1075
1178
|
return null;
|
|
1076
1179
|
}
|
|
1077
1180
|
|
|
1181
|
+
// Parse --no-legacy-cleanup (#3799) — skip the legacy get-shit-done-cc scan
|
|
1182
|
+
// entirely. Some users run gsd-core alongside a live legacy install on
|
|
1183
|
+
// purpose; the scan is best-effbelt cleanup, never load-bearing for the
|
|
1184
|
+
// install itself.
|
|
1185
|
+
function parseNoLegacyCleanupArg(args = process.argv) {
|
|
1186
|
+
return args.includes('--no-legacy-cleanup');
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1078
1189
|
// Parse --config-dir argument
|
|
1079
1190
|
function parseConfigDirArg() {
|
|
1080
1191
|
const result = parseConfigDirFromArgs(args);
|
|
@@ -1105,7 +1216,7 @@ if (hasUninstall) {
|
|
|
1105
1216
|
|
|
1106
1217
|
// Show help if requested
|
|
1107
1218
|
if (hasHelp) {
|
|
1108
|
-
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}--zcode${reset} Install for ZCode only\n ${cyan}--pi${reset} Install for Pi only\n ${cyan}--gemini${reset} Install for Gemini CLI 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 (
|
|
1219
|
+
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}--kimi-code${reset} Install for Kimi Code 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}--zcode${reset} Install for ZCode only\n ${cyan}--pi${reset} Install for Pi only\n ${cyan}--gemini${reset} Install for Gemini CLI 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}--no-legacy-cleanup${reset} Skip the legacy get-shit-done-cc artifact scan\n (an explicit --config-dir already scopes the scan to it)\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 and resolve the node runner at hook-fire time via\n hooks/gsd-node-runner.sh (WSL/Docker bind-mount\n setups; also GSD_PORTABLE_HOOKS=1)\n ${cyan}--reclaim-kimi-legacy${reset} With --kimi-code: also remove the GSD hooks a\n pre-1.10.0 --kimi-code install orphaned in ~/.kimi.\n Opt-in — those artifacts are indistinguishable from\n Kimi CLI's own, so skip it if you use Kimi CLI too.\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 Code globally (its own ~/.kimi-code root)${reset}\n npx ${pkg.name} --kimi-code --global\n\n ${dim}# Kimi Code, also reclaiming hooks a pre-1.10.0 install left in ~/.kimi${reset}\n npx ${pkg.name} --kimi-code --global --reclaim-kimi-legacy\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 Kimi CLI and Kimi Code are separate products with separate hook roots: use ${cyan}--kimi${reset} (${cyan}~/.kimi${reset}, ${cyan}KIMI_SHARE_DIR${reset}) or ${cyan}--kimi-code${reset} (${cyan}~/.kimi-code${reset}, ${cyan}KIMI_CODE_HOME${reset}).\n`);
|
|
1109
1220
|
process.exit(0);
|
|
1110
1221
|
}
|
|
1111
1222
|
|
|
@@ -1125,6 +1236,10 @@ if (hasHelp) {
|
|
|
1125
1236
|
// had no internal caller and no export consumer for it — hooksSurface owns
|
|
1126
1237
|
// the single implementation now, used internally by resolveNodeRunner there.)
|
|
1127
1238
|
const resolveNodeRunner = hooksSurface.resolveNodeRunner;
|
|
1239
|
+
// #3662: the runtime-resolving runner token for managed JS hooks — the baked
|
|
1240
|
+
// absolute node path tried FIRST, then `command -v node`, then well-known
|
|
1241
|
+
// layouts, resolved by the shell at hook-fire time instead of bake time.
|
|
1242
|
+
const buildNodeRunnerChainToken = hooksSurface.buildNodeRunnerChainToken;
|
|
1128
1243
|
const resolveBashRunner = hooksSurface.resolveBashRunner;
|
|
1129
1244
|
// referencesHook: pure predicate over hook entry objects, shared between
|
|
1130
1245
|
// install() and finishInstall() (ADR-857 phase 5f-1b).
|
|
@@ -1377,10 +1492,18 @@ function readSettings(settingsPath) {
|
|
|
1377
1492
|
}
|
|
1378
1493
|
|
|
1379
1494
|
/**
|
|
1380
|
-
* Write settings.json with proper formatting
|
|
1495
|
+
* Write settings.json with proper formatting.
|
|
1496
|
+
*
|
|
1497
|
+
* Atomic (temp+rename) because hosts discard the ENTIRE settings file on any
|
|
1498
|
+
* parse failure, so a truncated write costs the user every hook, permission,
|
|
1499
|
+
* and statusline they have — not just GSD's entries. This is the sole writer
|
|
1500
|
+
* of that surface for six runtimes.
|
|
1501
|
+
*
|
|
1502
|
+
* `atomicWriteFileSync` is declared further down this file; it is dereferenced
|
|
1503
|
+
* at call time, after module evaluation, so the ordering is safe.
|
|
1381
1504
|
*/
|
|
1382
1505
|
function writeSettings(settingsPath, settings) {
|
|
1383
|
-
|
|
1506
|
+
atomicWriteFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
1384
1507
|
}
|
|
1385
1508
|
|
|
1386
1509
|
// #2875 Part 2 (J8): model-override resolution (readGsdGlobalModelOverrides /
|
|
@@ -2253,7 +2376,8 @@ function convertClaudeAgentToCopilotAgent(content, isGlobal = false) {
|
|
|
2253
2376
|
/**
|
|
2254
2377
|
* Apply Antigravity-specific content conversion — path replacement + command name conversion.
|
|
2255
2378
|
* Path mappings depend on install mode:
|
|
2256
|
-
* Global: ~/.claude/ → ~/.gemini/
|
|
2379
|
+
* Global: ~/.claude/skills/ → ~/.gemini/config/skills/ (#3738),
|
|
2380
|
+
* ~/.claude/ → ~/.gemini/antigravity/, ./.claude/ → ./.agents/
|
|
2257
2381
|
* Local: ~/.claude/ → .agents/, ./.claude/ → ./.agents/
|
|
2258
2382
|
* Applied to ALL Antigravity content (skills, agents, engine files).
|
|
2259
2383
|
* @param {string} content - Source content to convert
|
|
@@ -2262,6 +2386,19 @@ function convertClaudeAgentToCopilotAgent(content, isGlobal = false) {
|
|
|
2262
2386
|
function convertClaudeToAntigravityContent(content, isGlobal = false) {
|
|
2263
2387
|
let c = content;
|
|
2264
2388
|
if (isGlobal) {
|
|
2389
|
+
// #3738: global skills install under ~/.gemini/config/skills (the dir AGY
|
|
2390
|
+
// scans for global discovery), so skills-path references must divert there
|
|
2391
|
+
// — BEFORE the configHome rewrite below, which is correct for gsd-core
|
|
2392
|
+
// runtime-file references (settings, workflows, VERSION) but wrong for the
|
|
2393
|
+
// skills dir itself. Mirrors src/runtime-artifact-conversion.cts (ADR-1508
|
|
2394
|
+
// keeps bin/install.js hand-authored; the two copies must stay in sync).
|
|
2395
|
+
c = c.replace(/\$HOME\/\.claude\/skills\//g, '$HOME/.gemini/config/skills/');
|
|
2396
|
+
c = c.replace(/~\/\.claude\/skills\//g, '~/.gemini/config/skills/');
|
|
2397
|
+
// Bare skills form (no trailing slash) — must also precede the generic
|
|
2398
|
+
// slash rule, which would otherwise divert it to the retired configHome
|
|
2399
|
+
// path ($HOME/.gemini/antigravity/skills).
|
|
2400
|
+
c = c.replace(/\$HOME\/\.claude\/skills\b/g, '$HOME/.gemini/config/skills');
|
|
2401
|
+
c = c.replace(/~\/\.claude\/skills\b/g, '~/.gemini/config/skills');
|
|
2265
2402
|
c = c.replace(/\$HOME\/\.claude\//g, '$HOME/.gemini/antigravity/');
|
|
2266
2403
|
c = c.replace(/~\/\.claude\//g, '~/.gemini/antigravity/');
|
|
2267
2404
|
// Bare form (no trailing slash) — must come after slash form to avoid double-replace
|
|
@@ -3903,10 +4040,39 @@ function _resetCodexWarningDedupeForTests() {
|
|
|
3903
4040
|
* @param {object|null} effortCfg — #443: merged effort config from readGsdEffectiveEffortConfig
|
|
3904
4041
|
*/
|
|
3905
4042
|
function generateCodexAgentToml(agentName, agentContent, modelOverrides = null, runtimeResolver = null, effortCfg = null, sandboxTier = 'codex-agent-sandbox') {
|
|
3906
|
-
const sandboxMode = CODEX_AGENT_SANDBOX[agentName] || 'read-only';
|
|
3907
4043
|
const { frontmatter, body } = extractFrontmatterAndBody(agentContent);
|
|
3908
4044
|
const frontmatterText = frontmatter || '';
|
|
4045
|
+
// #3897 list-form parse fix, Fix 3: `toolsRaw` MUST come from the same
|
|
4046
|
+
// shared `extractToolsValue` reader `checkCodexSandboxPosture` uses, not
|
|
4047
|
+
// this file's own `extractFrontmatterField` — the two used to disagree on
|
|
4048
|
+
// YAML block-list `tools:` form (`extractFrontmatterField`'s single-line
|
|
4049
|
+
// regex read only the first list item), which is exactly the generative-
|
|
4050
|
+
// fix-divergence shape CLAUDE.md warns about for two paths feeding one
|
|
4051
|
+
// derivation. `extractToolsValue` does its own `---`-delimited frontmatter
|
|
4052
|
+
// scan of the full `agentContent`, so it is not re-derived from
|
|
4053
|
+
// `frontmatterText` here.
|
|
4054
|
+
const toolsRaw = extractToolsValue(agentContent) ?? '';
|
|
3909
4055
|
const resolvedName = extractFrontmatterField(frontmatterText, 'name') || agentName;
|
|
4056
|
+
// #3897 rung 3 — derived from the role's own tool contract (HALT.md option
|
|
4057
|
+
// 2). The former hand-maintained CODEX_AGENT_SANDBOX map is deleted (ADR-3473
|
|
4058
|
+
// §8.3): it was fully redundant with this derivation, zero disagreements
|
|
4059
|
+
// across all 11 entries. Never a silent `|| 'read-only'` fallback either.
|
|
4060
|
+
// Derivation itself lives in codex-agent-toml.cjs, which does no frontmatter
|
|
4061
|
+
// parsing of its own (no third copy of that extraction) — it takes the
|
|
4062
|
+
// already-resolved `tools:` value, extracted via the shared reader above.
|
|
4063
|
+
//
|
|
4064
|
+
// #3897 security review F1 (blocker): pass BOTH candidate identities —
|
|
4065
|
+
// `agentName` (the caller's filename-stem identity) AND `resolvedName`
|
|
4066
|
+
// (the frontmatter `name:` this function's OWN emitted `name = ...` line
|
|
4067
|
+
// uses, and what `installCodexConfig`'s caller keys the output PATH on) —
|
|
4068
|
+
// never just one. Deciding the sandbox for `agentName` alone and applying
|
|
4069
|
+
// it to an artifact that a DIFFERENT identity (`resolvedName`) names is
|
|
4070
|
+
// exactly how a held role's own `.toml` could end up `workspace-write`
|
|
4071
|
+
// (rename the source file, or plant a sibling whose `name:` collides with
|
|
4072
|
+
// a held role). `deriveCodexSandboxMode` takes the most restrictive result
|
|
4073
|
+
// across every candidate — see its doc and `isSandboxHeld` in
|
|
4074
|
+
// codex-agent-toml.cjs.
|
|
4075
|
+
const sandboxMode = deriveCodexSandboxMode([agentName, resolvedName], toolsRaw);
|
|
3910
4076
|
const resolvedDescription = toSingleLine(
|
|
3911
4077
|
extractFrontmatterField(frontmatterText, 'description') || `GSD agent ${resolvedName}`
|
|
3912
4078
|
);
|
|
@@ -3986,8 +4152,10 @@ function generateCodexAgentToml(agentName, agentContent, modelOverrides = null,
|
|
|
3986
4152
|
// #443 — Unified effort for Codex .toml. Uses the same config-driven precedence chain
|
|
3987
4153
|
// as the Claude .md effort injection (resolveInstallTimeEffort), so both runtimes read
|
|
3988
4154
|
// from the same effort.agent_overrides / effort.routing_tier_defaults / effort.default
|
|
3989
|
-
// config source.
|
|
3990
|
-
//
|
|
4155
|
+
// config source. #3007 — Codex advertises supported_reasoning_levels per model, so the
|
|
4156
|
+
// pinned model id is passed through and the value is resolved against that model's own
|
|
4157
|
+
// set: 'max' now passes, 'minimal' clamps up to 'low', and 'ultra' is refused (no key
|
|
4158
|
+
// emitted) rather than clamped to a fabricated level.
|
|
3991
4159
|
// #838 — Do not pin effort when Codex is intentionally inheriting the parent
|
|
3992
4160
|
// chat model. A TOML with no `model` but a static `model_reasoning_effort`
|
|
3993
4161
|
// creates confusing partial routing: model follows the Codex UI while effort
|
|
@@ -3997,8 +4165,12 @@ function generateCodexAgentToml(agentName, agentContent, modelOverrides = null,
|
|
|
3997
4165
|
// #3533 (10d): 'inherit' means OMIT the pin — the agent follows the host's
|
|
3998
4166
|
// own effort default. Never write the literal.
|
|
3999
4167
|
if (_universalEffortCodex !== 'inherit') {
|
|
4000
|
-
const _renderedEffortCodex = _getGsdEffortCatalog().renderEffortForRuntime('codex', _universalEffortCodex).value;
|
|
4001
|
-
|
|
4168
|
+
const _renderedEffortCodex = _getGsdEffortCatalog().renderEffortForRuntime('codex', _universalEffortCodex, pinnedModel).value;
|
|
4169
|
+
// #3007 — 'ultra' is rejected by the model's supported_reasoning_levels and
|
|
4170
|
+
// renders as null. Omit the key entirely rather than write a literal `null`.
|
|
4171
|
+
if (_renderedEffortCodex !== null) {
|
|
4172
|
+
lines.push(`model_reasoning_effort = ${JSON.stringify(_renderedEffortCodex)}`);
|
|
4173
|
+
}
|
|
4002
4174
|
}
|
|
4003
4175
|
}
|
|
4004
4176
|
|
|
@@ -6206,6 +6378,31 @@ const __atomicWrittenTmps = hooksSurface.__atomicWrittenTmps;
|
|
|
6206
6378
|
* All writes go through atomicWriteFileSync so a mid-write failure leaves
|
|
6207
6379
|
* the original config.toml untouched (#2760 fix 4).
|
|
6208
6380
|
*/
|
|
6381
|
+
/**
|
|
6382
|
+
* Split TOML content into its leading TOP-LEVEL key lines and everything from
|
|
6383
|
+
* the first table header onward (#3610).
|
|
6384
|
+
*
|
|
6385
|
+
* Top-level keys were file-scoped before a merge. The regenerated GSD block
|
|
6386
|
+
* opens with a table header (`[agents]`, #2088/ADR-1239 upgrade 2), so placing
|
|
6387
|
+
* that block ABOVE surviving top-level keys re-scopes them into `[agents]` —
|
|
6388
|
+
* `validateCodexConfigSchema` then correctly rejects the merged file and the
|
|
6389
|
+
* install aborts. Hoisting the keys above the block preserves their scope.
|
|
6390
|
+
*
|
|
6391
|
+
* Table headers inside multiline strings do not start the "rest" region (the
|
|
6392
|
+
* record parser already excludes them via startsInMultilineString).
|
|
6393
|
+
*/
|
|
6394
|
+
function splitTopLevelKeys(content) {
|
|
6395
|
+
for (const record of getTomlLineRecords(content)) {
|
|
6396
|
+
if (record.tableHeader && !record.startsInMultilineString) {
|
|
6397
|
+
return {
|
|
6398
|
+
topLevel: content.slice(0, record.start).trim(),
|
|
6399
|
+
rest: content.slice(record.start).trim(),
|
|
6400
|
+
};
|
|
6401
|
+
}
|
|
6402
|
+
}
|
|
6403
|
+
return { topLevel: content.trim(), rest: '' };
|
|
6404
|
+
}
|
|
6405
|
+
|
|
6209
6406
|
function mergeCodexConfig(configPath, gsdBlock) {
|
|
6210
6407
|
// Case 1: No config.toml — create fresh
|
|
6211
6408
|
if (!fs.existsSync(configPath)) {
|
|
@@ -6253,10 +6450,25 @@ function mergeCodexConfig(configPath, gsdBlock) {
|
|
|
6253
6450
|
.replace(/^\r?\n# GSD codex_hooks ownership: (?:section|root_dotted)\r?\n/, '');
|
|
6254
6451
|
const afterUser = stripLeakedGsdCodexSections(markerStripped).trim();
|
|
6255
6452
|
|
|
6453
|
+
// #3610: top-level keys that survived BELOW the marker were file-scoped
|
|
6454
|
+
// before this merge; the regenerated block opens with the `[agents]` table
|
|
6455
|
+
// header, so they must be hoisted to FILE scope or TOML re-scopes them
|
|
6456
|
+
// into a table. File scope means BEFORE the first table header of the
|
|
6457
|
+
// pre-marker region too — appending them after a pre-marker table (the
|
|
6458
|
+
// default real-world layout: user tables precede the marker) would merely
|
|
6459
|
+
// capture them into THAT table instead of [agents], and the schema
|
|
6460
|
+
// validator is blind to non-agents tables.
|
|
6461
|
+
const beforeSplit = before ? splitTopLevelKeys(before) : { topLevel: '', rest: '' };
|
|
6462
|
+
const { topLevel: afterTopLevel, rest: afterTables } = splitTopLevelKeys(afterUser);
|
|
6463
|
+
|
|
6256
6464
|
const parts = [];
|
|
6257
|
-
|
|
6465
|
+
const topParts = [];
|
|
6466
|
+
if (beforeSplit.topLevel) topParts.push(beforeSplit.topLevel);
|
|
6467
|
+
if (afterTopLevel) topParts.push(afterTopLevel);
|
|
6468
|
+
if (topParts.length > 0) parts.push(topParts.join(eol + eol));
|
|
6469
|
+
if (beforeSplit.rest) parts.push(beforeSplit.rest);
|
|
6258
6470
|
parts.push(normalizedGsdBlock);
|
|
6259
|
-
if (
|
|
6471
|
+
if (afterTables) parts.push(afterTables);
|
|
6260
6472
|
atomicWriteFileSync(configPath, parts.join(eol + eol) + eol);
|
|
6261
6473
|
return;
|
|
6262
6474
|
}
|
|
@@ -6726,29 +6938,50 @@ function writeNonClaudeDefaults(runtime) {
|
|
|
6726
6938
|
if (_hostBehaviors(runtime).nativeModelAliases || process.env.GSD_TEST_MODE) return;
|
|
6727
6939
|
const gsdDir = path.join(os.homedir(), '.gsd');
|
|
6728
6940
|
const defaultsPath = path.join(gsdDir, 'defaults.json');
|
|
6941
|
+
let releaseLock = null;
|
|
6729
6942
|
try {
|
|
6730
6943
|
fs.mkdirSync(gsdDir, { recursive: true });
|
|
6944
|
+
// defaults.json is machine-global — every runtime and project on the box
|
|
6945
|
+
// reads it. Serialize the read-modify-write so two concurrent installs
|
|
6946
|
+
// cannot lose each other's key, and apply both mutations in ONE atomic
|
|
6947
|
+
// write so a crash cannot leave the file truncated (the read path swallows
|
|
6948
|
+
// parse errors and treats a corrupt file as absent, which would silently
|
|
6949
|
+
// degrade model resolution everywhere until repaired by hand).
|
|
6950
|
+
releaseLock = acquireInstallMigrationLock(gsdDir);
|
|
6731
6951
|
let defaults = {};
|
|
6732
6952
|
try { defaults = JSON.parse(fs.readFileSync(defaultsPath, 'utf8')); } catch { /* new file */ }
|
|
6733
6953
|
if (defaults === null || typeof defaults !== 'object' || Array.isArray(defaults)) {
|
|
6734
6954
|
defaults = {};
|
|
6735
6955
|
}
|
|
6956
|
+
const applied = [];
|
|
6736
6957
|
// Three-valued domain: false/absent → aliases; true → full IDs; "omit" → ''.
|
|
6737
6958
|
const existing = defaults.resolve_model_ids;
|
|
6738
6959
|
const shouldDefaultToOmit = existing !== true && existing !== 'omit';
|
|
6739
6960
|
if (shouldDefaultToOmit) {
|
|
6740
6961
|
defaults.resolve_model_ids = 'omit';
|
|
6741
|
-
|
|
6742
|
-
console.log(` ${green}✓${reset} Set resolve_model_ids: "omit" in ~/.gsd/defaults.json`);
|
|
6962
|
+
applied.push(`Set resolve_model_ids: "omit" in ~/.gsd/defaults.json`);
|
|
6743
6963
|
}
|
|
6744
6964
|
// #2395: persist runtime for non-Claude runtimes.
|
|
6745
6965
|
if (defaults.runtime === undefined || defaults.runtime === null || defaults.runtime === '') {
|
|
6746
6966
|
defaults.runtime = runtime;
|
|
6747
|
-
|
|
6748
|
-
|
|
6967
|
+
applied.push(`Set runtime: "${runtime}" in ~/.gsd/defaults.json`);
|
|
6968
|
+
}
|
|
6969
|
+
if (applied.length > 0) {
|
|
6970
|
+
atomicWriteFileSync(defaultsPath, JSON.stringify(defaults, null, 2) + '\n', 'utf8');
|
|
6971
|
+
for (const message of applied) console.log(` ${green}✓${reset} ${message}`);
|
|
6749
6972
|
}
|
|
6750
6973
|
} catch (e) {
|
|
6751
6974
|
console.log(` ${yellow}⚠${reset} Could not write ~/.gsd/defaults.json: ${e.message}`);
|
|
6975
|
+
} finally {
|
|
6976
|
+
if (releaseLock) {
|
|
6977
|
+
try {
|
|
6978
|
+
releaseLock();
|
|
6979
|
+
} catch (releaseError) {
|
|
6980
|
+
// A leaked lock blocks the next install, so surface it rather than
|
|
6981
|
+
// swallowing; the stale-lock reaper clears it once this pid exits.
|
|
6982
|
+
console.log(` ${yellow}⚠${reset} Could not release the ~/.gsd install lock: ${releaseError.message}`);
|
|
6983
|
+
}
|
|
6984
|
+
}
|
|
6752
6985
|
}
|
|
6753
6986
|
}
|
|
6754
6987
|
|
|
@@ -6772,6 +7005,36 @@ function installCodexConfig(targetDir, agentsSrc, sandboxTier = 'codex-agent-san
|
|
|
6772
7005
|
}
|
|
6773
7006
|
fs.mkdirSync(agentsTomlDir, { recursive: true });
|
|
6774
7007
|
|
|
7008
|
+
// #3897 rung 3 (CAUSE B fix) — validateCodexSandboxHolds is deliberately
|
|
7009
|
+
// NOT called here. The "no stale holds" invariant is a REPO invariant about
|
|
7010
|
+
// the canonical roster in `agents/`, not a property of whatever directory
|
|
7011
|
+
// an install happens to read from: a partial or synthetic `agentsSrc` (a
|
|
7012
|
+
// test fixture, a `--config-dir` subset) legitimately contains only a few
|
|
7013
|
+
// agents, and a held role simply absent from THIS source dir must be
|
|
7014
|
+
// inert, not fatal. Throwing here also masked unrelated failures further
|
|
7015
|
+
// down this same loop (e.g. the name-injection/path-escape guard on
|
|
7016
|
+
// `agentTomlPath` below), since this check ran first and unconditionally.
|
|
7017
|
+
// The invariant is still enforced — as a test over the real `agents/`
|
|
7018
|
+
// roster (tests/codex-config.test.cjs T24/T25) — just never on this
|
|
7019
|
+
// runtime path. See src/codex-agent-toml.cts's `validateCodexSandboxHolds`
|
|
7020
|
+
// docblock for the full rationale.
|
|
7021
|
+
//
|
|
7022
|
+
// #3897 security review F2 (assessed post-F1-fix, verified by execution —
|
|
7023
|
+
// not asserted): before F1's fix, the "runtime detector removed" gap here
|
|
7024
|
+
// was real — a rename (case a) or a sibling `name:` clobber (case b) could
|
|
7025
|
+
// reach this loop and land a held role's `.toml` at `workspace-write` with
|
|
7026
|
+
// nothing here to catch it. After F1 (`deriveCodexSandboxMode` now decides
|
|
7027
|
+
// over BOTH the filename stem and the resolved frontmatter `name:`, most
|
|
7028
|
+
// restrictive wins), both cases were re-run end-to-end through this exact
|
|
7029
|
+
// function and the EMITTED ARTIFACT for both is `read-only` — the
|
|
7030
|
+
// dangerous condition no longer produces a wrong artifact, it produces the
|
|
7031
|
+
// SAFE one. A detector guarding a now-fail-safe condition is not
|
|
7032
|
+
// load-bearing, and restoring a throw here would re-break the legitimate
|
|
7033
|
+
// partial-source-dir case CAUSE B removed it for (see above). Regression
|
|
7034
|
+
// coverage for both cases lives in `tests/codex-config.test.cjs` (F1(a)
|
|
7035
|
+
// rename / F1(b) sibling-clobber rows), asserted on the emitted `.toml`'s
|
|
7036
|
+
// `sandbox_mode`, not on the derivation's return value.
|
|
7037
|
+
|
|
6775
7038
|
const agentEntries = fs.readdirSync(agentsSrc).filter(f => f.startsWith('gsd-') && f.endsWith('.md'));
|
|
6776
7039
|
const agents = [];
|
|
6777
7040
|
|
|
@@ -6800,7 +7063,20 @@ function installCodexConfig(targetDir, agentsSrc, sandboxTier = 'codex-agent-san
|
|
|
6800
7063
|
// CLAUDE.md neutralization via neutralizeAgentReferences(..., 'AGENTS.md').
|
|
6801
7064
|
content = convertClaudeToCodexMarkdown(content);
|
|
6802
7065
|
const { frontmatter } = extractFrontmatterAndBody(content);
|
|
6803
|
-
|
|
7066
|
+
// #3897 security review F1 (blocker, post-merge): this loop used to key
|
|
7067
|
+
// the sandbox/hold decision off ONLY the filename stem while the emitted
|
|
7068
|
+
// `.toml`'s OUTPUT PATH below is keyed off `name` (frontmatter-derived,
|
|
7069
|
+
// attacker-editable) — so a renamed source file, or a sibling file whose
|
|
7070
|
+
// `name:` collides with a held role, could make the decided identity and
|
|
7071
|
+
// the landed artifact disagree, widening a held role's own file to
|
|
7072
|
+
// `workspace-write`. `generateCodexAgentToml` (below) now derives
|
|
7073
|
+
// `sandbox_mode` over BOTH the filename stem it is given AND the
|
|
7074
|
+
// frontmatter `name:` it resolves internally, taking the most
|
|
7075
|
+
// restrictive result — this loop no longer needs to choose one identity
|
|
7076
|
+
// for that call; see `deriveCodexSandboxMode`'s doc in
|
|
7077
|
+
// `codex-agent-toml.cts` for the resolution.
|
|
7078
|
+
const fileStem = file.replace(/\.md$/, '');
|
|
7079
|
+
const name = extractFrontmatterField(frontmatter, 'name') || fileStem;
|
|
6804
7080
|
const description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
6805
7081
|
|
|
6806
7082
|
agents.push({ name, description: toSingleLine(description) });
|
|
@@ -6822,7 +7098,10 @@ function installCodexConfig(targetDir, agentsSrc, sandboxTier = 'codex-agent-san
|
|
|
6822
7098
|
// #443 — pass unified effort config so model_reasoning_effort in the .toml
|
|
6823
7099
|
// follows the same config-driven precedence as the Claude .md effort key.
|
|
6824
7100
|
const effortCfg = readGsdEffectiveEffortConfig(targetDir);
|
|
6825
|
-
|
|
7101
|
+
// Pass `fileStem`; `generateCodexAgentToml` itself additionally resolves
|
|
7102
|
+
// and folds in the frontmatter `name:` for the sandbox decision (F1
|
|
7103
|
+
// above) — this call site does not need to pass `name` explicitly.
|
|
7104
|
+
const tomlContent = generateCodexAgentToml(fileStem, content, modelOverrides, runtimeResolver, effortCfg, sandboxTier);
|
|
6826
7105
|
// Confine the per-agent write to the agents/ dir itself: a crafted agent
|
|
6827
7106
|
// `name` containing path separators must not escape agents/ (which would let
|
|
6828
7107
|
// it clobber config.toml or write elsewhere under the configHome).
|
|
@@ -7822,6 +8101,133 @@ function validateHookFields(settings) {
|
|
|
7822
8101
|
*/
|
|
7823
8102
|
const GSD_UNINSTALL_HOOKS = [..._HOOKS_TO_COPY, 'gsd-check-update.cmd'];
|
|
7824
8103
|
|
|
8104
|
+
/**
|
|
8105
|
+
* Whether two paths denote the SAME directory — used to stop a reclaim from
|
|
8106
|
+
* deleting the very root the current install just wrote (#3031).
|
|
8107
|
+
*
|
|
8108
|
+
* A plain `path.resolve` comparison is not enough here, because both roots come
|
|
8109
|
+
* from user-controlled env vars (`KIMI_SHARE_DIR`, `KIMI_CODE_HOME`) and two
|
|
8110
|
+
* different strings routinely name one directory:
|
|
8111
|
+
* - case-insensitive filesystems (macOS, Windows): `~/Kimi` vs `~/kimi`
|
|
8112
|
+
* - symlinks / bind mounts: `~/link-to-kimi` vs the real target
|
|
8113
|
+
* Getting this wrong is not cosmetic — it is the difference between skipping a
|
|
8114
|
+
* reclaim and deleting a live install's own hooks.
|
|
8115
|
+
*
|
|
8116
|
+
* Strategy, cheapest-first: string equality after `resolve`, then identity by
|
|
8117
|
+
* `dev`+`ino` (definitive when both exist and the platform reports them), then
|
|
8118
|
+
* `realpath` string equality (resolves symlinks AND canonicalizes case). Any
|
|
8119
|
+
* rung answering "same" wins; a path that does not exist cannot be the root we
|
|
8120
|
+
* just wrote, so a failed stat simply falls through.
|
|
8121
|
+
*
|
|
8122
|
+
* @returns {boolean} true only when both paths are proven to be one directory.
|
|
8123
|
+
*/
|
|
8124
|
+
function isSameDirectory(a, b) {
|
|
8125
|
+
if (path.resolve(a) === path.resolve(b)) return true;
|
|
8126
|
+
try {
|
|
8127
|
+
const sa = fs.statSync(a);
|
|
8128
|
+
const sb = fs.statSync(b);
|
|
8129
|
+
// `ino` is 0 on some Windows filesystems; only trust a positive match.
|
|
8130
|
+
if (sa.ino && sb.ino && sa.dev === sb.dev && sa.ino === sb.ino) return true;
|
|
8131
|
+
} catch (_) { /* one side missing — fall through to realpath */ }
|
|
8132
|
+
try {
|
|
8133
|
+
return fs.realpathSync.native(a) === fs.realpathSync.native(b);
|
|
8134
|
+
} catch (_) {
|
|
8135
|
+
return false;
|
|
8136
|
+
}
|
|
8137
|
+
}
|
|
8138
|
+
|
|
8139
|
+
/**
|
|
8140
|
+
* Remove every GSD-owned artifact from a Kimi hooks root (`~/.kimi` for kimi,
|
|
8141
|
+
* `~/.kimi-code` for kimi-code — resolveKimiHooksTomlDir, #2755): the managed
|
|
8142
|
+
* `[[hooks]]` block in the native config.toml, the hook scripts, hooks/lib/,
|
|
8143
|
+
* and the CommonJS marker at both its current (hooks/) and pre-#2544 (root)
|
|
8144
|
+
* locations.
|
|
8145
|
+
*
|
|
8146
|
+
* This root is Kimi's own native config home — SHARED space that may hold the
|
|
8147
|
+
* user's real config.toml, providers and their own scripts — so only exact
|
|
8148
|
+
* GSD-owned filenames are removed and directories are pruned only when that
|
|
8149
|
+
* removal leaves them empty.
|
|
8150
|
+
*
|
|
8151
|
+
* TWO callers, deliberately one implementation (#3031). `uninstall()` calls it
|
|
8152
|
+
* for the runtime being uninstalled; the opt-in `--reclaim-kimi-legacy` path in
|
|
8153
|
+
* `install()` calls it for the LEGACY `~/.kimi` root a pre-#2755 `--kimi-code`
|
|
8154
|
+
* install orphaned. Duplicating this sequence for the second caller would be
|
|
8155
|
+
* exactly the generative-divergence hazard the repo bans — the reclaim must
|
|
8156
|
+
* remove precisely what a real uninstall removes, forever, by construction.
|
|
8157
|
+
*
|
|
8158
|
+
* @param {string} kimiHooksRoot - Absolute path to the Kimi hooks root.
|
|
8159
|
+
* @returns {number} count of removal steps performed (0 when nothing matched).
|
|
8160
|
+
*/
|
|
8161
|
+
function reclaimKimiHooksRoot(kimiHooksRoot) {
|
|
8162
|
+
let steps = 0;
|
|
8163
|
+
const kimiHooksTomlPath = path.join(kimiHooksRoot, 'config.toml');
|
|
8164
|
+
const kimiHooksCleanup = removeKimiHooksToml(kimiHooksTomlPath);
|
|
8165
|
+
if (kimiHooksCleanup.changed) {
|
|
8166
|
+
steps++;
|
|
8167
|
+
console.log(` ${green}✓${reset} Removed GSD hooks from ${kimiHooksTomlPath}`);
|
|
8168
|
+
}
|
|
8169
|
+
|
|
8170
|
+
// Kimi's shared hook scripts + CommonJS package.json marker are installed
|
|
8171
|
+
// into this SAME ~/.kimi root (installSharedHooksBundle, install()'s
|
|
8172
|
+
// kimi-hooks-toml branch) rather than under targetDir — mirror steps "4.
|
|
8173
|
+
// Remove GSD hooks" / "5. Remove GSD package.json" below, but scoped to
|
|
8174
|
+
// kimiHooksRoot. ~/.kimi is Kimi's own native config home (shared space —
|
|
8175
|
+
// may hold the user's real config.toml/providers), so only the exact
|
|
8176
|
+
// GSD-owned filenames are removed, and directories are pruned only if left
|
|
8177
|
+
// empty by that removal.
|
|
8178
|
+
const kimiHooksDir = path.join(kimiHooksRoot, 'hooks');
|
|
8179
|
+
if (fs.existsSync(kimiHooksDir)) {
|
|
8180
|
+
let kimiHookCount = 0;
|
|
8181
|
+
for (const hook of GSD_UNINSTALL_HOOKS) {
|
|
8182
|
+
const hookPath = path.join(kimiHooksDir, hook);
|
|
8183
|
+
if (fs.existsSync(hookPath)) {
|
|
8184
|
+
fs.unlinkSync(hookPath);
|
|
8185
|
+
kimiHookCount++;
|
|
8186
|
+
}
|
|
8187
|
+
}
|
|
8188
|
+
if (kimiHookCount > 0) {
|
|
8189
|
+
steps++;
|
|
8190
|
+
console.log(` ${green}✓${reset} Removed ${kimiHookCount} GSD hooks from ${kimiHooksDir}`);
|
|
8191
|
+
}
|
|
8192
|
+
|
|
8193
|
+
const kimiHooksLibDir = path.join(kimiHooksDir, 'lib');
|
|
8194
|
+
if (fs.existsSync(kimiHooksLibDir)) {
|
|
8195
|
+
let removedKimiLibFiles = 0;
|
|
8196
|
+
for (const file of GSD_HOOK_LIB_FILES) {
|
|
8197
|
+
try {
|
|
8198
|
+
fs.unlinkSync(path.join(kimiHooksLibDir, file));
|
|
8199
|
+
removedKimiLibFiles++;
|
|
8200
|
+
} catch (_) { /* best-effort */ }
|
|
8201
|
+
}
|
|
8202
|
+
try { fs.rmdirSync(kimiHooksLibDir); } catch (_) { /* not empty or other error — leave it */ }
|
|
8203
|
+
if (removedKimiLibFiles > 0) {
|
|
8204
|
+
steps++;
|
|
8205
|
+
console.log(` ${green}✓${reset} Removed ${removedKimiLibFiles} hooks/lib/ helper(s) from ${kimiHooksLibDir}`);
|
|
8206
|
+
}
|
|
8207
|
+
}
|
|
8208
|
+
|
|
8209
|
+
// #2544: the marker now lives inside kimi's hooks/ dir — remove it
|
|
8210
|
+
// before the emptiness check below, or the dir would never prune.
|
|
8211
|
+
if (removeCommonJsMarker(kimiHooksDir)) {
|
|
8212
|
+
steps++;
|
|
8213
|
+
console.log(` ${green}✓${reset} Removed GSD package.json from ${kimiHooksDir}`);
|
|
8214
|
+
}
|
|
8215
|
+
|
|
8216
|
+
try {
|
|
8217
|
+
if (fs.readdirSync(kimiHooksDir).length === 0) fs.rmdirSync(kimiHooksDir);
|
|
8218
|
+
} catch (_) { /* not empty — leave it */ }
|
|
8219
|
+
}
|
|
8220
|
+
|
|
8221
|
+
// Retire the pre-#2544 marker at kimi's root (~/.kimi), where the bundle
|
|
8222
|
+
// used to write it. Exact content match — a user's own package.json in
|
|
8223
|
+
// kimi's native config home is never touched.
|
|
8224
|
+
if (removeCommonJsMarker(kimiHooksRoot)) {
|
|
8225
|
+
steps++;
|
|
8226
|
+
console.log(` ${green}✓${reset} Removed GSD package.json from ${kimiHooksRoot} (pre-#2544 marker)`);
|
|
8227
|
+
}
|
|
8228
|
+
return steps;
|
|
8229
|
+
}
|
|
8230
|
+
|
|
7825
8231
|
/**
|
|
7826
8232
|
* Uninstall GSD from the specified directory for a specific runtime
|
|
7827
8233
|
* Removes only GSD-specific files/directories, preserves user content
|
|
@@ -8019,72 +8425,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8019
8425
|
// cleanup can't be driven by anything under targetDir the way every other
|
|
8020
8426
|
// hook surface above is.
|
|
8021
8427
|
if (resolveInstallPlan(runtime).hooksSurface === 'kimi-hooks-toml') {
|
|
8022
|
-
|
|
8023
|
-
const kimiHooksTomlPath = path.join(kimiHooksRoot, 'config.toml');
|
|
8024
|
-
const kimiHooksCleanup = removeKimiHooksToml(kimiHooksTomlPath);
|
|
8025
|
-
if (kimiHooksCleanup.changed) {
|
|
8026
|
-
removedCount++;
|
|
8027
|
-
console.log(` ${green}✓${reset} Removed GSD hooks from ${kimiHooksTomlPath}`);
|
|
8028
|
-
}
|
|
8029
|
-
|
|
8030
|
-
// Kimi's shared hook scripts + CommonJS package.json marker are installed
|
|
8031
|
-
// into this SAME ~/.kimi root (installSharedHooksBundle, install()'s
|
|
8032
|
-
// kimi-hooks-toml branch) rather than under targetDir — mirror steps "4.
|
|
8033
|
-
// Remove GSD hooks" / "5. Remove GSD package.json" below, but scoped to
|
|
8034
|
-
// kimiHooksRoot. ~/.kimi is Kimi's own native config home (shared space —
|
|
8035
|
-
// may hold the user's real config.toml/providers), so only the exact
|
|
8036
|
-
// GSD-owned filenames are removed, and directories are pruned only if left
|
|
8037
|
-
// empty by that removal.
|
|
8038
|
-
const kimiHooksDir = path.join(kimiHooksRoot, 'hooks');
|
|
8039
|
-
if (fs.existsSync(kimiHooksDir)) {
|
|
8040
|
-
let kimiHookCount = 0;
|
|
8041
|
-
for (const hook of GSD_UNINSTALL_HOOKS) {
|
|
8042
|
-
const hookPath = path.join(kimiHooksDir, hook);
|
|
8043
|
-
if (fs.existsSync(hookPath)) {
|
|
8044
|
-
fs.unlinkSync(hookPath);
|
|
8045
|
-
kimiHookCount++;
|
|
8046
|
-
}
|
|
8047
|
-
}
|
|
8048
|
-
if (kimiHookCount > 0) {
|
|
8049
|
-
removedCount++;
|
|
8050
|
-
console.log(` ${green}✓${reset} Removed ${kimiHookCount} GSD hooks from ${kimiHooksDir}`);
|
|
8051
|
-
}
|
|
8052
|
-
|
|
8053
|
-
const kimiHooksLibDir = path.join(kimiHooksDir, 'lib');
|
|
8054
|
-
if (fs.existsSync(kimiHooksLibDir)) {
|
|
8055
|
-
let removedKimiLibFiles = 0;
|
|
8056
|
-
for (const file of GSD_HOOK_LIB_FILES) {
|
|
8057
|
-
try {
|
|
8058
|
-
fs.unlinkSync(path.join(kimiHooksLibDir, file));
|
|
8059
|
-
removedKimiLibFiles++;
|
|
8060
|
-
} catch (_) { /* best-effort */ }
|
|
8061
|
-
}
|
|
8062
|
-
try { fs.rmdirSync(kimiHooksLibDir); } catch (_) { /* not empty or other error — leave it */ }
|
|
8063
|
-
if (removedKimiLibFiles > 0) {
|
|
8064
|
-
removedCount++;
|
|
8065
|
-
console.log(` ${green}✓${reset} Removed ${removedKimiLibFiles} hooks/lib/ helper(s) from ${kimiHooksLibDir}`);
|
|
8066
|
-
}
|
|
8067
|
-
}
|
|
8068
|
-
|
|
8069
|
-
// #2544: the marker now lives inside kimi's hooks/ dir — remove it
|
|
8070
|
-
// before the emptiness check below, or the dir would never prune.
|
|
8071
|
-
if (removeCommonJsMarker(kimiHooksDir)) {
|
|
8072
|
-
removedCount++;
|
|
8073
|
-
console.log(` ${green}✓${reset} Removed GSD package.json from ${kimiHooksDir}`);
|
|
8074
|
-
}
|
|
8075
|
-
|
|
8076
|
-
try {
|
|
8077
|
-
if (fs.readdirSync(kimiHooksDir).length === 0) fs.rmdirSync(kimiHooksDir);
|
|
8078
|
-
} catch (_) { /* not empty — leave it */ }
|
|
8079
|
-
}
|
|
8080
|
-
|
|
8081
|
-
// Retire the pre-#2544 marker at kimi's root (~/.kimi), where the bundle
|
|
8082
|
-
// used to write it. Exact content match — a user's own package.json in
|
|
8083
|
-
// kimi's native config home is never touched.
|
|
8084
|
-
if (removeCommonJsMarker(kimiHooksRoot)) {
|
|
8085
|
-
removedCount++;
|
|
8086
|
-
console.log(` ${green}✓${reset} Removed GSD package.json from ${kimiHooksRoot} (pre-#2544 marker)`);
|
|
8087
|
-
}
|
|
8428
|
+
removedCount += reclaimKimiHooksRoot(resolveKimiHooksTomlDir({ runtime }));
|
|
8088
8429
|
}
|
|
8089
8430
|
|
|
8090
8431
|
// 1b. Non-layout Copilot side-effect: copilot-instructions.md cleanup
|
|
@@ -9450,7 +9791,14 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9450
9791
|
const resolvedScope = options.scope === 'local' ? 'local' : 'global';
|
|
9451
9792
|
const codexSkillsDir = _resolveSkillsRootDir(runtime, configDir, resolvedScope);
|
|
9452
9793
|
const codexSkillsManifestPrefix = _hostBehaviors(runtime).skillsManifestPrefix || 'skills/';
|
|
9453
|
-
|
|
9794
|
+
// #3738: resolve the ACTUAL agents-install dir honoring an agents-kind `home`
|
|
9795
|
+
// override (antigravity global → $HOME/.gemini/config/agents), mirroring
|
|
9796
|
+
// _resolveSkillsRootDir for skills. Hardcoding configDir/agents left the
|
|
9797
|
+
// manifest blind to the whole agents surface the moment the override landed —
|
|
9798
|
+
// no drift detection, no patch backup. Falls back to <configDir>/agents.
|
|
9799
|
+
const agentsDir = _kindDestDirSafe(runtime, configDir, resolvedScope, 'agents')
|
|
9800
|
+
|| _kindDestDirSafe(runtime, configDir, resolvedScope, 'kimi-agents')
|
|
9801
|
+
|| path.join(configDir, 'agents');
|
|
9454
9802
|
const manifest = {
|
|
9455
9803
|
// Schema version of this DOCUMENT (#2872) — distinct from `version`
|
|
9456
9804
|
// below, which is the GSD package version. Absent ⇒ a pre-#2872 (v1)
|
|
@@ -10080,6 +10428,12 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10080
10428
|
? process.cwd()
|
|
10081
10429
|
: path.join(process.cwd(), dirName);
|
|
10082
10430
|
|
|
10431
|
+
// #3664: a --config-dir destination holding foreign agent files gets an
|
|
10432
|
+
// explicit install-time warning — never a silent Claude-shaped emit.
|
|
10433
|
+
if (isGlobal) {
|
|
10434
|
+
warnIfForeignAgentDest(runtime, targetDir, _installScopeId, Boolean(explicitConfigDir));
|
|
10435
|
+
}
|
|
10436
|
+
|
|
10083
10437
|
// #2875 (#1874-F19 anti-inertness, test-matrix C7): recover any user
|
|
10084
10438
|
// artifact orphaned by a PRIOR install run that died between staging and
|
|
10085
10439
|
// its own restore/discard, BEFORE this run's own preserve step stages
|
|
@@ -11374,10 +11728,21 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11374
11728
|
// abort a successful install — log a warning and continue.
|
|
11375
11729
|
// install() is never reached in --dry-run mode (the early-exit at the CLI
|
|
11376
11730
|
// dispatch handles preview), so cleanup here always applies for real.
|
|
11377
|
-
|
|
11378
|
-
|
|
11379
|
-
|
|
11380
|
-
|
|
11731
|
+
//
|
|
11732
|
+
// #3799: when --config-dir redirected the install, the scan is SCOPED to
|
|
11733
|
+
// that destination ([targetDir]) — the default home's live install must
|
|
11734
|
+
// never be planned for removal from a sandboxed install. --no-legacy-cleanup
|
|
11735
|
+
// skips the scan entirely.
|
|
11736
|
+
const skipNoLegacyCleanup = parseNoLegacyCleanupArg();
|
|
11737
|
+
const legacyCleanupScope = (explicitConfigDir !== null && isGlobal)
|
|
11738
|
+
? [targetDir]
|
|
11739
|
+
: undefined;
|
|
11740
|
+
if (!skipNoLegacyCleanup) {
|
|
11741
|
+
try {
|
|
11742
|
+
cleanupLegacyGsdCc({ dryRun: false, ...(legacyCleanupScope ? { configDirs: legacyCleanupScope } : {}) });
|
|
11743
|
+
} catch (cleanupErr) {
|
|
11744
|
+
console.warn(` ${yellow}Warning: legacy cleanup failed: ${cleanupErr.message}${reset}`);
|
|
11745
|
+
}
|
|
11381
11746
|
}
|
|
11382
11747
|
|
|
11383
11748
|
if (failures.length > 0) {
|
|
@@ -11480,7 +11845,20 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11480
11845
|
// #3245 CR finding 2 — any throw in the pre-config install operations (skills copy,
|
|
11481
11846
|
// agents copy, VERSION write, manifest write, etc.) triggers the Codex pre-config
|
|
11482
11847
|
// rollback so the caller is never left in a partially-installed state.
|
|
11483
|
-
rollbackInstallerMigrations()
|
|
11848
|
+
// (The second, identical rollbackInstallerMigrations() that used to sit here was
|
|
11849
|
+
// a duplicate of the line above, not a second phase — removed in #3725 review.)
|
|
11850
|
+
// #3712 — the test-home guard refuses before any LAYOUT-DRIVEN write, so no
|
|
11851
|
+
// gsd-* directory in the skills root has been touched and there is nothing
|
|
11852
|
+
// there to undo. (Legacy install migrations DO run first; that is why the
|
|
11853
|
+
// rollbackInstallerMigrations() calls above still execute, and why the one
|
|
11854
|
+
// migration that can reach a `home` override carries its own assertion.)
|
|
11855
|
+
// Running the codex rollback anyway would delete and recreate every
|
|
11856
|
+
// snapshotted gsd-* directory in the resolved skills root, which for an
|
|
11857
|
+
// un-sandboxed codex install IS the real ~/.agents/skills: the guard's own
|
|
11858
|
+
// refusal would provoke the mutation it exists to prevent. This is the only
|
|
11859
|
+
// _codexPreConfigRollback() call site, and applySurface/uninstall cannot
|
|
11860
|
+
// reach it. Every other error still rolls back. Found by review, not by CI.
|
|
11861
|
+
if (isTestHomeGuardRefusal(_earlyInstallErr)) throw _earlyInstallErr;
|
|
11484
11862
|
if (_codexPreConfigRollback) {
|
|
11485
11863
|
_codexPreConfigRollback();
|
|
11486
11864
|
}
|
|
@@ -12024,6 +12402,44 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12024
12402
|
if (kimiHooksResult.changed) {
|
|
12025
12403
|
console.log(` ${green}✓${reset} Configured ${kimiHooksResult.entryCount} GSD hook(s) in ${kimiHooksTomlPath}`);
|
|
12026
12404
|
}
|
|
12405
|
+
|
|
12406
|
+
// #3031: opt-in reclaim of the pre-#2755 legacy root. Runs LAST in this
|
|
12407
|
+
// branch so the kimi-code install above is already complete and durable —
|
|
12408
|
+
// a reclaim can only ever remove, never leave the install half-written.
|
|
12409
|
+
//
|
|
12410
|
+
// Gated on `runtime === 'kimi-code'`: a `--kimi` install resolves this
|
|
12411
|
+
// very same `~/.kimi` as its own hooks root, so reclaiming there would
|
|
12412
|
+
// delete the hooks it just wrote. The flag is silently inert for kimi
|
|
12413
|
+
// rather than an error — `--all` passes every runtime through this branch,
|
|
12414
|
+
// and one opt-in flag must not fail an otherwise valid multi-runtime run.
|
|
12415
|
+
//
|
|
12416
|
+
// ALSO gated on kimi NOT being installed by this same invocation. The
|
|
12417
|
+
// flag asserts "I only use Kimi Code"; `--all`, or an explicit `--kimi
|
|
12418
|
+
// --kimi-code`, falsifies that outright. Both orderings put `kimi` BEFORE
|
|
12419
|
+
// `kimi-code` (selectRuntimesFromArgs), so without this guard the run
|
|
12420
|
+
// installs Kimi CLI's hooks and then deletes them moments later — the run
|
|
12421
|
+
// reports success and the user is left with the very breakage the opt-in
|
|
12422
|
+
// exists to prevent. Verified reproducible before this guard existed.
|
|
12423
|
+
const kimiInstalledThisRun = selectedRuntimes.includes('kimi');
|
|
12424
|
+
if (hasReclaimKimiLegacy && runtime === 'kimi-code' && kimiInstalledThisRun) {
|
|
12425
|
+
console.log(` ${dim}•${reset} Skipped --reclaim-kimi-legacy: this run also installs --kimi, so ${resolveKimiHooksTomlDir({ runtime: 'kimi' })} is a live Kimi CLI install`);
|
|
12426
|
+
} else if (hasReclaimKimiLegacy && runtime === 'kimi-code') {
|
|
12427
|
+
const legacyKimiRoot = resolveKimiHooksTomlDir({ runtime: 'kimi' });
|
|
12428
|
+
// Both roots honor their own env override (KIMI_SHARE_DIR /
|
|
12429
|
+
// KIMI_CODE_HOME). A user who points both at ONE directory collapses
|
|
12430
|
+
// "the legacy root" onto "the root this install just wrote", and an
|
|
12431
|
+
// unguarded reclaim would delete its own output. isSameDirectory compares
|
|
12432
|
+
// the DIRECTORIES, not the strings — case-insensitive filesystems and
|
|
12433
|
+
// symlinked aliases both name one dir with two spellings.
|
|
12434
|
+
if (isSameDirectory(legacyKimiRoot, kimiHooksRoot)) {
|
|
12435
|
+
console.log(` ${dim}•${reset} Skipped --reclaim-kimi-legacy: ${legacyKimiRoot} is this install's own hooks root`);
|
|
12436
|
+
} else {
|
|
12437
|
+
const reclaimed = reclaimKimiHooksRoot(legacyKimiRoot);
|
|
12438
|
+
console.log(reclaimed > 0
|
|
12439
|
+
? ` ${green}✓${reset} Reclaimed ${reclaimed} orphaned GSD artifact group(s) from ${legacyKimiRoot} (pre-#2755)`
|
|
12440
|
+
: ` ${dim}•${reset} No orphaned GSD artifacts found in ${legacyKimiRoot}`);
|
|
12441
|
+
}
|
|
12442
|
+
}
|
|
12027
12443
|
}
|
|
12028
12444
|
|
|
12029
12445
|
// ADR-1239 / #2100 Stage 2: Windsurf's own independent hooksSurface —
|
|
@@ -12117,9 +12533,19 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12117
12533
|
);
|
|
12118
12534
|
const hasGsdStatusline = sharedRaw.statusLine && sharedRaw.statusLine.command &&
|
|
12119
12535
|
isManagedHookCommand(sharedRaw.statusLine.command, { surface: 'settings-json' });
|
|
12120
|
-
|
|
12536
|
+
const needsMigration = hasGsdHooks || hasGsdStatusline;
|
|
12537
|
+
// readSettings returns null ONLY for an unparseable file — its documented
|
|
12538
|
+
// "preserve existing, don't touch" signal. Stand the WHOLE migration down
|
|
12539
|
+
// in that case: skipping just the local merge while still stripping the
|
|
12540
|
+
// shared file below would destroy the GSD entries outright instead of
|
|
12541
|
+
// relocating them. Leaving both files untouched lets the migration retry
|
|
12542
|
+
// once the user repairs the local file.
|
|
12543
|
+
const localRaw = needsMigration ? readSettings(settingsPath) : null;
|
|
12544
|
+
if (needsMigration && localRaw === null) {
|
|
12545
|
+
console.log(' ' + yellow + 'i' + reset + ' Skipping #338 migration — ' + settingsFileName +
|
|
12546
|
+
' could not be parsed. Your existing settings are preserved.');
|
|
12547
|
+
} else if (needsMigration) {
|
|
12121
12548
|
// Merge GSD entries into settings.local.json
|
|
12122
|
-
const localRaw = readSettings(settingsPath) || {};
|
|
12123
12549
|
if (hasGsdStatusline && !localRaw.statusLine) {
|
|
12124
12550
|
localRaw.statusLine = sharedRaw.statusLine;
|
|
12125
12551
|
}
|
|
@@ -12179,16 +12605,22 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12179
12605
|
if (rawSettings === null) {
|
|
12180
12606
|
console.log(' ' + yellow + 'i' + reset + ' Skipping settings.local.json configuration — file could not be parsed (comments or malformed JSON). Your existing settings are preserved.');
|
|
12181
12607
|
persistActiveProfileMarker();
|
|
12182
|
-
|
|
12608
|
+
// Callers index this result by `runtime` (installAllRuntimes' statusline
|
|
12609
|
+
// lookup), so every early exit must return the full shape — a bare return
|
|
12610
|
+
// crashes the install rather than skipping one file.
|
|
12611
|
+
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir };
|
|
12183
12612
|
}
|
|
12184
12613
|
const settings = validateHookFields(cleanupOrphanedHooks(rawSettings));
|
|
12185
|
-
// #3002 CR: rewrite legacy `node .../gsd-*.js` command strings
|
|
12186
|
-
//
|
|
12187
|
-
//
|
|
12188
|
-
//
|
|
12189
|
-
|
|
12614
|
+
// #3002 CR / #3662: rewrite legacy `node .../gsd-*.js` command strings (pre-
|
|
12615
|
+
// #2979 installs) AND entries baked with another environment's absolute node
|
|
12616
|
+
// path onto the runtime-resolving runner. Without this, existing managed
|
|
12617
|
+
// hook entries stay bare-`node`-prefixed or foreign-absolute across
|
|
12618
|
+
// reinstalls and remain broken under GUI/minimal-PATH runtimes and shared
|
|
12619
|
+
// config roots — the #3662 mixed state where no environment can run all
|
|
12620
|
+
// hooks.
|
|
12621
|
+
const settingsRunner = buildNodeRunnerChainToken();
|
|
12190
12622
|
if (settingsRunner && rewriteLegacyManagedNodeHookCommands(settings, settingsRunner, { platform: process.platform, runtime })) {
|
|
12191
|
-
console.log(` ${green}✓${reset} Rewrote legacy
|
|
12623
|
+
console.log(` ${green}✓${reset} Rewrote legacy managed-hook commands to the runtime-resolving node runner (#2979/#3662)`);
|
|
12192
12624
|
}
|
|
12193
12625
|
// Local installs anchor hook paths so they resolve regardless of cwd (#1906).
|
|
12194
12626
|
// Claude Code sets $CLAUDE_PROJECT_DIR; Antigravity does not — and on
|
|
@@ -12199,12 +12631,14 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12199
12631
|
// check inside projectLocalHookPrefix.
|
|
12200
12632
|
const localPrefix = projectLocalHookPrefix({ runtime, dirName, hookPathStyle: _hostBehaviors(runtime).hookPathStyle });
|
|
12201
12633
|
const hookOpts = { portableHooks: hasPortableHooks, runtime };
|
|
12202
|
-
// #2979: local-install hook commands also use
|
|
12203
|
-
//
|
|
12204
|
-
//
|
|
12205
|
-
|
|
12634
|
+
// #2979: local-install hook commands also use a runner GUI/minimal-PATH
|
|
12635
|
+
// runtimes can resolve. Bare `node` fails when the host launches the
|
|
12636
|
+
// runtime with a stripped PATH (Finder/Antigravity/etc) — #3662 replaces
|
|
12637
|
+
// the baked absolute path with the runtime-resolving chain (baked path
|
|
12638
|
+
// first, so the minimal-PATH guarantee is unchanged).
|
|
12639
|
+
const localNodeRunner = buildNodeRunnerChainToken();
|
|
12206
12640
|
const localBashRunner = resolveBashRunner({ platform: process.platform });
|
|
12207
|
-
// If we cannot resolve
|
|
12641
|
+
// If we cannot resolve a node runner AND this is a local install,
|
|
12208
12642
|
// skip managed-hook registration. Returning null from buildHookCommand on
|
|
12209
12643
|
// global installs has the same effect. Better to skip than to emit a bare
|
|
12210
12644
|
// `node` command that recreates the #2979 failure.
|
|
@@ -13209,31 +13643,49 @@ const _LEGACY_SCAN_SUBDIR_NAMES = [
|
|
|
13209
13643
|
* @param {object} [opts.logger=console] - injectable logger
|
|
13210
13644
|
* @returns {{ plan: {path:string,reason:string}[], result: object }}
|
|
13211
13645
|
*/
|
|
13212
|
-
function cleanupLegacyGsdCc({ homeDir = os.homedir(), dryRun = false, logger = console } = {}) {
|
|
13646
|
+
function cleanupLegacyGsdCc({ homeDir = os.homedir(), configDirs = null, dryRun = false, logger = console } = {}) {
|
|
13213
13647
|
// Build de-duplicated list of candidate config dirs to scan.
|
|
13214
13648
|
// Only scan under homeDir — never cwd — to prevent accidental deletion of
|
|
13215
13649
|
// the user's active-project hooks when the installer is invoked from a
|
|
13216
13650
|
// project directory that has .claude/hooks or similar subdirs.
|
|
13651
|
+
// #3799: an explicit configDirs override (install() passes [targetDir]
|
|
13652
|
+
// whenever --config-dir redirected the destination) scopes the WHOLE scan
|
|
13653
|
+
// to that dir — the default-home scan must never plan removals of a live
|
|
13654
|
+
// install that lives outside the destination the user chose.
|
|
13217
13655
|
const seen = new Set();
|
|
13218
|
-
const
|
|
13219
|
-
|
|
13220
|
-
const candidate
|
|
13221
|
-
|
|
13222
|
-
|
|
13223
|
-
|
|
13656
|
+
const scanDirs = [];
|
|
13657
|
+
if (Array.isArray(configDirs) && configDirs.length > 0) {
|
|
13658
|
+
for (const candidate of configDirs) {
|
|
13659
|
+
if (!seen.has(candidate) && fs.existsSync(candidate)) {
|
|
13660
|
+
seen.add(candidate);
|
|
13661
|
+
scanDirs.push(candidate);
|
|
13662
|
+
}
|
|
13663
|
+
}
|
|
13664
|
+
} else {
|
|
13665
|
+
for (const name of _LEGACY_SCAN_SUBDIR_NAMES) {
|
|
13666
|
+
const candidate = path.join(homeDir, name);
|
|
13667
|
+
if (!seen.has(candidate) && fs.existsSync(candidate)) {
|
|
13668
|
+
seen.add(candidate);
|
|
13669
|
+
scanDirs.push(candidate);
|
|
13670
|
+
}
|
|
13224
13671
|
}
|
|
13225
13672
|
}
|
|
13226
13673
|
|
|
13227
13674
|
// planLegacyCleanup scans each configDir and already includes the legacy
|
|
13228
13675
|
// shared cache (gsd-update-check.json) as a plan entry.
|
|
13229
|
-
const plan = planLegacyCleanup(
|
|
13676
|
+
const plan = planLegacyCleanup(scanDirs, { homeDir, ...(Array.isArray(configDirs) && configDirs.length > 0 ? { configDirs } : {}) });
|
|
13230
13677
|
|
|
13231
13678
|
// Apply the plan (dryRun honors the flag).
|
|
13232
13679
|
const result = applyLegacyCleanup(plan, { dryRun, logger });
|
|
13233
13680
|
|
|
13234
13681
|
// Also clear / preview the per-package cache so next session re-evaluates
|
|
13235
13682
|
// hook versions (replaces the former inline unlinkSync on line ~9104).
|
|
13236
|
-
|
|
13683
|
+
// #3799: under a configDirs override the cache is read/cleared under the
|
|
13684
|
+
// SCOPE root, never the default home — same invariant as the scan itself.
|
|
13685
|
+
const perPkgCacheRoot = (Array.isArray(configDirs) && configDirs.length > 0)
|
|
13686
|
+
? configDirs[0]
|
|
13687
|
+
: homeDir;
|
|
13688
|
+
const perPkgCacheFile = path.join(perPkgCacheRoot, '.cache', 'gsd', updateCacheFileName);
|
|
13237
13689
|
if (dryRun) {
|
|
13238
13690
|
logger.log('[dry-run] would remove: ' + perPkgCacheFile + ' (per-package-update-cache)');
|
|
13239
13691
|
} else {
|
|
@@ -13352,7 +13804,10 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
|
|
13352
13804
|
});
|
|
13353
13805
|
};
|
|
13354
13806
|
|
|
13355
|
-
|
|
13807
|
+
// `settings` is null on every early exit (unparseable file, skipped runtime),
|
|
13808
|
+
// and handleStatusline dereferences it — an install that declined to touch a
|
|
13809
|
+
// settings file has no statusline to prompt about, so fall through.
|
|
13810
|
+
if (primaryStatuslineResult && primaryStatuslineResult.settings) {
|
|
13356
13811
|
handleStatusline(primaryStatuslineResult.settings, isInteractive, continueAfterStatusline);
|
|
13357
13812
|
} else if (canInstallBanner) {
|
|
13358
13813
|
// No statusline-capable runtime, but at least one runtime can host the
|
|
@@ -13372,6 +13827,8 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
|
|
13372
13827
|
module.exports = {
|
|
13373
13828
|
// #3677 — hyphen-namespace normalization seam for agent bodies
|
|
13374
13829
|
shouldNormalizeHyphenNamespaceInAgentBody,
|
|
13830
|
+
// #3664: --config-dir foreign-agent-destination warning (warn-and-proceed)
|
|
13831
|
+
warnIfForeignAgentDest,
|
|
13375
13832
|
normalizeAgentBodyForRuntime,
|
|
13376
13833
|
yamlIdentifier,
|
|
13377
13834
|
getCodexSkillAdapterHeader,
|
|
@@ -13426,7 +13883,10 @@ module.exports = {
|
|
|
13426
13883
|
GSD_CLAUDE_LEGACY_ALLOW_PERMISSIONS,
|
|
13427
13884
|
GSD_CLAUDE_DENY_PERMISSIONS,
|
|
13428
13885
|
GSD_CODEX_MARKER,
|
|
13429
|
-
|
|
13886
|
+
// #3897 rung 3 (ADR-3473 §8.3, HALT.md option 2)
|
|
13887
|
+
CODEX_SANDBOX_HOLDS,
|
|
13888
|
+
deriveCodexSandboxMode,
|
|
13889
|
+
validateCodexSandboxHolds,
|
|
13430
13890
|
getGlobalDir,
|
|
13431
13891
|
getConfigDirFromHome,
|
|
13432
13892
|
resolveKiloConfigPath,
|
|
@@ -13505,6 +13965,8 @@ module.exports = {
|
|
|
13505
13965
|
cleanupLegacyGsdCc,
|
|
13506
13966
|
// #1191 — exported so tests exercise the REAL readSettings, not a replica
|
|
13507
13967
|
readSettings,
|
|
13968
|
+
writeSettings,
|
|
13969
|
+
writeNonClaudeDefaults,
|
|
13508
13970
|
stripJsonComments,
|
|
13509
13971
|
copyWithPathReplacement,
|
|
13510
13972
|
};
|
|
@@ -13519,10 +13981,23 @@ if (require.main === module && !process.env.GSD_TEST_MODE) {
|
|
|
13519
13981
|
console.log('Dry run — no files will be modified.\n');
|
|
13520
13982
|
// cleanupLegacyGsdCc with dryRun:true is the single source of truth for
|
|
13521
13983
|
// both the legacy artifacts and the per-package cache path — no duplicate
|
|
13522
|
-
// printing here.
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13984
|
+
// printing here. #3799: the preview honors the SAME scope and skip the
|
|
13985
|
+
// real install would apply (--config-dir scopes; --no-legacy-cleanup
|
|
13986
|
+
// skips) — a preview that listed default-home paths a real install would
|
|
13987
|
+
// never touch misrepresents the run.
|
|
13988
|
+
if (parseNoLegacyCleanupArg()) {
|
|
13989
|
+
console.log(' (--no-legacy-cleanup — legacy scan skipped)');
|
|
13990
|
+
} else {
|
|
13991
|
+
const previewScope = (explicitConfigDir !== null)
|
|
13992
|
+
? [getGlobalConfigDir(DEFAULT_RUNTIME, explicitConfigDir)]
|
|
13993
|
+
: undefined;
|
|
13994
|
+
const { plan } = cleanupLegacyGsdCc({
|
|
13995
|
+
dryRun: true,
|
|
13996
|
+
...(previewScope ? { configDirs: previewScope } : {}),
|
|
13997
|
+
});
|
|
13998
|
+
if (plan.length === 0) {
|
|
13999
|
+
console.log(' (no legacy get-shit-done-cc artifacts found)');
|
|
14000
|
+
}
|
|
13526
14001
|
}
|
|
13527
14002
|
process.exit(0);
|
|
13528
14003
|
} else if (hasSkillsRoot) {
|