@opengsd/gsd-core 1.14.0 → 1.16.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/.opencode/plugins/gsd-core.js +85 -5
- package/README.ja-JP.md +3 -3
- package/README.ko-KR.md +3 -3
- package/README.pt-BR.md +3 -3
- package/README.zh-CN.md +3 -3
- package/agents/gsd-code-fixer.compact.md +7 -6
- package/agents/gsd-code-fixer.md +9 -8
- package/agents/gsd-code-reviewer.compact.md +5 -3
- package/agents/gsd-code-reviewer.md +8 -6
- package/agents/gsd-debug-session-manager.compact.md +17 -2
- package/agents/gsd-debug-session-manager.md +17 -2
- package/agents/gsd-debugger.md +3 -3
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +17 -12
- package/agents/gsd-intel-updater.compact.md +1 -1
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +2 -2
- package/agents/gsd-phase-researcher.md +19 -11
- package/agents/gsd-plan-checker.md +15 -9
- package/agents/gsd-planner.md +15 -11
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +1 -1
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-ui-auditor.compact.md +21 -30
- package/agents/gsd-ui-auditor.md +166 -37
- package/agents/gsd-ui-researcher.compact.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +37 -14
- package/bin/install.js +764 -287
- package/commands/gsd/add-tests.md +6 -1
- package/commands/gsd/ai-integration-phase.md +6 -1
- package/commands/gsd/audit-fix.md +5 -0
- package/commands/gsd/audit-milestone.md +6 -1
- package/commands/gsd/autonomous.md +7 -2
- package/commands/gsd/capture.md +9 -5
- package/commands/gsd/code-review.md +7 -2
- package/commands/gsd/complete-milestone.md +4 -0
- package/commands/gsd/config.md +7 -3
- package/commands/gsd/debug.md +11 -7
- package/commands/gsd/discuss-phase.md +7 -3
- package/commands/gsd/docs-update.md +12 -7
- package/commands/gsd/eval-review.md +6 -1
- package/commands/gsd/execute-phase.md +12 -7
- package/commands/gsd/extract-learnings.md +5 -0
- package/commands/gsd/fast.md +4 -0
- package/commands/gsd/forensics.md +5 -1
- package/commands/gsd/graphify.md +10 -6
- package/commands/gsd/health.md +5 -0
- package/commands/gsd/help.md +7 -2
- package/commands/gsd/import.md +7 -3
- package/commands/gsd/inbox.md +5 -0
- package/commands/gsd/ingest-docs.md +5 -1
- package/commands/gsd/manager.md +6 -1
- package/commands/gsd/map-codebase.md +7 -3
- package/commands/gsd/mempalace-capture.md +12 -4
- package/commands/gsd/mempalace-recall.md +5 -1
- package/commands/gsd/milestone-summary.md +5 -1
- package/commands/gsd/mvp-phase.md +8 -3
- package/commands/gsd/new-milestone.md +6 -1
- package/commands/gsd/new-project.md +5 -0
- package/commands/gsd/next.md +6 -1
- package/commands/gsd/ns-context.md +4 -0
- package/commands/gsd/ns-ideate.md +4 -0
- package/commands/gsd/ns-manage.md +4 -0
- package/commands/gsd/ns-project.md +4 -0
- package/commands/gsd/ns-review.md +4 -0
- package/commands/gsd/ns-workflow.md +4 -0
- package/commands/gsd/onboard.md +6 -1
- package/commands/gsd/pause-work.md +5 -1
- package/commands/gsd/phase.md +8 -4
- package/commands/gsd/plan-phase.md +6 -1
- package/commands/gsd/plan-review-convergence.md +11 -7
- package/commands/gsd/pr-branch.md +4 -0
- package/commands/gsd/profile-user.md +5 -1
- package/commands/gsd/progress.md +7 -2
- package/commands/gsd/quick-batch.md +21 -9
- package/commands/gsd/quick.md +12 -7
- package/commands/gsd/review.md +7 -4
- package/commands/gsd/secure-phase.md +6 -1
- package/commands/gsd/ship.md +5 -0
- package/commands/gsd/sketch.md +7 -2
- package/commands/gsd/spec-phase.md +5 -1
- package/commands/gsd/spike.md +8 -3
- package/commands/gsd/surface.md +5 -1
- package/commands/gsd/thread.md +4 -0
- package/commands/gsd/ui-phase.md +6 -1
- package/commands/gsd/ui-review.md +6 -1
- package/commands/gsd/ultraplan-phase.md +5 -1
- package/commands/gsd/undo.md +5 -1
- package/commands/gsd/update.md +6 -2
- package/commands/gsd/validate-phase.md +6 -1
- package/commands/gsd/verify-work.md +6 -1
- package/commands/gsd/workspace.md +7 -3
- package/gsd-core/bin/gsd-tools.cjs +477 -78
- package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
- package/gsd-core/bin/lib/adr-parser.cjs +3 -1
- package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
- package/gsd-core/bin/lib/audit.cjs +144 -42
- package/gsd-core/bin/lib/broken-windows.cjs +13 -13
- package/gsd-core/bin/lib/capability-activation.cjs +9 -4
- package/gsd-core/bin/lib/capability-registry.cjs +197 -222
- package/gsd-core/bin/lib/capability-validator.cjs +16 -1
- package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
- package/gsd-core/bin/lib/check-command-router.cjs +164 -1625
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
- package/gsd-core/bin/lib/cli-exit.cjs +12 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +32 -33
- package/gsd-core/bin/lib/command-aliases.cjs +7 -0
- package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
- package/gsd-core/bin/lib/commands.cjs +343 -207
- package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
- package/gsd-core/bin/lib/config-loader.cjs +65 -4
- package/gsd-core/bin/lib/config.cjs +76 -19
- package/gsd-core/bin/lib/core-utils.cjs +6 -1
- package/gsd-core/bin/lib/coverage.cjs +4 -8
- package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
- package/gsd-core/bin/lib/decisions.cjs +30 -14
- package/gsd-core/bin/lib/drift.cjs +177 -42
- package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
- package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
- package/gsd-core/bin/lib/frontmatter.cjs +426 -234
- package/gsd-core/bin/lib/gap-checker.cjs +72 -29
- package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
- package/gsd-core/bin/lib/gate-args.cjs +53 -0
- package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
- package/gsd-core/bin/lib/gate-config.cjs +46 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +141 -0
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
- package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
- package/gsd-core/bin/lib/gate-exit.cjs +27 -0
- package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
- package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
- package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
- package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
- package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
- package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
- package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
- package/gsd-core/bin/lib/gate-ui-safety.cjs +80 -0
- package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
- package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
- package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
- package/gsd-core/bin/lib/graphify.cjs +10 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
- package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
- package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
- package/gsd-core/bin/lib/init.cjs +364 -132
- package/gsd-core/bin/lib/install-engine.cjs +30 -33
- package/gsd-core/bin/lib/install-profiles.cjs +7 -4
- package/gsd-core/bin/lib/installer-migrations.cjs +8 -1
- package/gsd-core/bin/lib/io.cjs +122 -3
- package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
- package/gsd-core/bin/lib/milestone.cjs +37 -6
- package/gsd-core/bin/lib/model-resolver.cjs +171 -62
- package/gsd-core/bin/lib/observability/event.cjs +1 -1
- package/gsd-core/bin/lib/observability/logger.cjs +46 -1
- package/gsd-core/bin/lib/pattern.cjs +10 -0
- package/gsd-core/bin/lib/phase-command-router.cjs +20 -5
- package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
- package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
- package/gsd-core/bin/lib/phase-id.cjs +110 -8
- package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
- package/gsd-core/bin/lib/phase-locator.cjs +29 -10
- package/gsd-core/bin/lib/phase-status.cjs +360 -0
- package/gsd-core/bin/lib/phase.cjs +489 -88
- package/gsd-core/bin/lib/plan-document.cjs +142 -20
- package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
- package/gsd-core/bin/lib/planning-document.cjs +692 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +60 -9
- package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +83 -55
- package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
- package/gsd-core/bin/lib/probe-core.cjs +7 -1
- package/gsd-core/bin/lib/profile-output.cjs +6 -3
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
- package/gsd-core/bin/lib/project-root.cjs +41 -2
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +11 -8
- package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
- package/gsd-core/bin/lib/report-parser.cjs +269 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
- package/gsd-core/bin/lib/roadmap-command-router.cjs +25 -19
- package/gsd-core/bin/lib/roadmap-parser.cjs +242 -15
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +1653 -65
- package/gsd-core/bin/lib/roadmap.cjs +405 -88
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +373 -187
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +5 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +14 -7
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +522 -624
- package/gsd-core/bin/lib/runtime-name-policy.cjs +246 -21
- package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +47 -8
- package/gsd-core/bin/lib/smart-entry.cjs +19 -3
- package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
- package/gsd-core/bin/lib/state-contract.cjs +15 -18
- package/gsd-core/bin/lib/state-document.cjs +100 -22
- package/gsd-core/bin/lib/state-transition.cjs +39 -2
- package/gsd-core/bin/lib/state.cjs +256 -105
- package/gsd-core/bin/lib/surface.cjs +19 -2
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -79
- package/gsd-core/bin/lib/uat-predicate.cjs +359 -38
- package/gsd-core/bin/lib/uat.cjs +432 -7
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +167 -40
- package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
- package/gsd-core/bin/lib/vendor/README.md +31 -9
- package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
- package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
- package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
- package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +1378 -274
- package/gsd-core/bin/lib/verify-command-grounding.cjs +46 -2
- package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
- package/gsd-core/bin/lib/verify.cjs +357 -531
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
- package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
- package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
- package/gsd-core/bin/lib/workstream.cjs +11 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
- package/gsd-core/bin/lib/worktree-safety.cjs +784 -51
- package/gsd-core/bin/shared/config-defaults.manifest.json +8 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
- package/gsd-core/references/autonomous-smart-discuss.md +2 -1
- package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
- package/gsd-core/references/checkpoints.md +5 -3
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe.md +195 -21
- package/gsd-core/references/execute-mvp-tdd.md +5 -10
- package/gsd-core/references/execute-phase-between-wave-reset.md +10 -6
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +22 -11
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +7 -1
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/offer-next.md +1 -1
- package/gsd-core/references/phase-argument-parsing.md +9 -7
- package/gsd-core/references/phase-id-convention.md +28 -0
- package/gsd-core/references/planner-gap-closure.md +2 -0
- package/gsd-core/references/planner-load-graph-context.md +24 -13
- package/gsd-core/references/planner-verify-command-grounding.md +14 -0
- package/gsd-core/references/planning-config.md +12 -3
- package/gsd-core/references/spidr-splitting.md +1 -1
- package/gsd-core/references/tdd.md +37 -8
- package/gsd-core/references/ui-consideration-probe.md +10 -5
- package/gsd-core/references/verifier-phase-gates.md +5 -2
- package/gsd-core/references/verify-command-path-resolvability.md +10 -2
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -3
- package/gsd-core/references/worktree-path-safety.md +321 -0
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/UAT.md +17 -1
- package/gsd-core/templates/config.json +2 -11
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +8 -7
- package/gsd-core/workflows/add-tests.md +4 -3
- package/gsd-core/workflows/add-todo.md +6 -5
- package/gsd-core/workflows/ai-integration-phase.md +13 -4
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +4 -3
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
- package/gsd-core/workflows/autonomous.md +43 -23
- package/gsd-core/workflows/check-todos.md +7 -6
- package/gsd-core/workflows/cleanup.md +2 -2
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
- package/gsd-core/workflows/code-review-fix.md +112 -25
- package/gsd-core/workflows/code-review.md +146 -135
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
- package/gsd-core/workflows/complete-milestone.md +13 -8
- package/gsd-core/workflows/debug.md +32 -7
- package/gsd-core/workflows/diagnose-issues.md +3 -2
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
- package/gsd-core/workflows/discuss-phase.md +4 -3
- package/gsd-core/workflows/do.md +2 -2
- package/gsd-core/workflows/docs-update.md +6 -5
- package/gsd-core/workflows/edit-phase.md +4 -3
- package/gsd-core/workflows/eval-review.md +14 -5
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1019 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +37 -3
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
- 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 +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
- package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
- package/gsd-core/workflows/execute-phase.md +96 -178
- package/gsd-core/workflows/execute-plan.md +18 -23
- package/gsd-core/workflows/explore.md +4 -4
- package/gsd-core/workflows/extract-learnings.md +4 -2
- package/gsd-core/workflows/fast.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +3 -2
- package/gsd-core/workflows/help/modes/full.compact.md +3 -3
- package/gsd-core/workflows/help/modes/full.md +5 -5
- package/gsd-core/workflows/help/modes/topic.md +15 -5
- package/gsd-core/workflows/import.md +4 -3
- package/gsd-core/workflows/inbox.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -3
- package/gsd-core/workflows/insert-phase.md +4 -3
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +6 -4
- package/gsd-core/workflows/map-codebase.md +5 -4
- package/gsd-core/workflows/milestone-summary.md +3 -2
- package/gsd-core/workflows/mvp-phase.md +14 -14
- package/gsd-core/workflows/new-milestone.md +11 -11
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
- package/gsd-core/workflows/new-project.md +7 -7
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/note.md +1 -1
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +2 -2
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
- package/gsd-core/workflows/plan-phase.md +50 -24
- package/gsd-core/workflows/plan-review-convergence.md +21 -5
- package/gsd-core/workflows/plant-seed.md +62 -20
- package/gsd-core/workflows/pr-branch.md +113 -13
- package/gsd-core/workflows/profile-user.md +2 -2
- package/gsd-core/workflows/progress.md +19 -49
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +27 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +15 -10
- package/gsd-core/workflows/quick.md +62 -39
- package/gsd-core/workflows/reapply-patches.md +9 -3
- package/gsd-core/workflows/remove-phase.md +3 -2
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +3 -2
- package/gsd-core/workflows/review.md +33 -17
- package/gsd-core/workflows/scan.md +3 -2
- package/gsd-core/workflows/secure-phase.md +13 -13
- package/gsd-core/workflows/settings-advanced.md +30 -10
- package/gsd-core/workflows/settings-integrations.md +2 -3
- package/gsd-core/workflows/settings.md +4 -4
- package/gsd-core/workflows/ship.md +14 -13
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +2 -2
- package/gsd-core/workflows/spec-phase.md +15 -5
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +5 -5
- package/gsd-core/workflows/thread.md +2 -2
- package/gsd-core/workflows/transition.md +13 -23
- package/gsd-core/workflows/ui-phase.md +48 -11
- package/gsd-core/workflows/ui-review.md +21 -6
- package/gsd-core/workflows/ultraplan-phase.md +3 -2
- package/gsd-core/workflows/undo.md +339 -20
- package/gsd-core/workflows/update.md +7 -7
- package/gsd-core/workflows/validate-phase.md +12 -13
- package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
- package/gsd-core/workflows/verify-work.md +129 -55
- package/hooks/dist/gsd-agent-isolation-guard.js +32 -0
- package/hooks/dist/gsd-check-update-worker.js +8 -0
- package/hooks/dist/gsd-check-update.js +8 -0
- package/hooks/dist/gsd-context-monitor.js +31 -8
- package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
- package/hooks/dist/gsd-secret-read-guard.js +161 -4
- package/hooks/dist/gsd-statusline.js +85 -20
- package/hooks/dist/gsd-update-banner.js +8 -0
- package/hooks/dist/gsd-validate-commit.sh +63 -4
- package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
- package/hooks/dist/gsd-workflow-guard.js +5 -4
- package/hooks/dist/gsd-worktree-path-guard.js +6 -2
- package/hooks/dist/lib/cli-exit.js +12 -0
- package/hooks/dist/lib/git-probe.js +17 -1
- package/hooks/dist/lib/isolation-sentinel.js +2 -2
- package/hooks/gsd-agent-isolation-guard.js +32 -0
- package/hooks/gsd-check-update-worker.js +8 -0
- package/hooks/gsd-check-update.js +8 -0
- package/hooks/gsd-context-monitor.js +31 -8
- package/hooks/gsd-cursor-subagent-start.js +8 -0
- package/hooks/gsd-secret-read-guard.js +161 -4
- package/hooks/gsd-statusline.js +85 -20
- package/hooks/gsd-update-banner.js +8 -0
- package/hooks/gsd-validate-commit.sh +63 -4
- package/hooks/gsd-windsurf-pre-write.js +11 -2
- package/hooks/gsd-workflow-guard.js +5 -4
- package/hooks/gsd-worktree-path-guard.js +6 -2
- package/hooks/hooks.json +5 -5
- package/hooks/lib/cli-exit.js +12 -0
- package/hooks/lib/git-probe.js +17 -1
- package/hooks/lib/isolation-sentinel.js +2 -2
- package/package.json +22 -4
- package/scripts/build-hooks.js +15 -6
- package/scripts/changeset/parse.cjs +52 -4
- package/scripts/check-contract-drift.cjs +127 -11
- package/scripts/ci-timeout-report.cjs +770 -4
- package/scripts/command-contract-helpers.cjs +15 -8
- package/scripts/docs-guard-registry.cjs +34 -0
- package/scripts/gen-features.cjs +13 -8
- package/scripts/gen-hooks-cli-exit.cjs +12 -28
- package/scripts/gen-loop-host-contract.cjs +79 -1
- package/scripts/gen-platform-conformance-tier.cjs +187 -1
- package/scripts/gen-plugin-skills.cjs +87 -1
- package/scripts/gen-research-agents.cjs +24 -31
- package/scripts/gen-scripts-cli-exit.cjs +30 -3
- package/scripts/gen-test-timings.cjs +32 -7
- package/scripts/lib/cli-exit.cjs +12 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +34 -2
- package/scripts/lib/ndjson-reporter.cjs +31 -5
- package/scripts/lib/platform-conformance-tier.generated.cjs +45 -5
- package/scripts/lib/registration-ledger-preload.cjs +155 -0
- package/scripts/lib/vendor-bundle.cjs +59 -0
- package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-completion-predicate-drift.cjs +18 -19
- package/scripts/lint-descriptions.cjs +7 -3
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +38 -1
- package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
- package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
- package/scripts/lint-phase-arg-assignment.cjs +257 -0
- package/scripts/lint-phase-enumeration-drift.cjs +12 -8
- package/scripts/lint-phase-id-drift.cjs +319 -5
- package/scripts/lint-planning-document-positive-control.cjs +329 -0
- package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
- package/scripts/lint-response-language-coverage.cjs +3 -0
- package/scripts/lint-retired-runtime-name.cjs +619 -0
- package/scripts/lint-skill-deps.cjs +7 -3
- package/scripts/lint-state-write-path-drift.cjs +93 -0
- package/scripts/lint-test-file-count.allowlist.json +38 -9
- package/scripts/lint-test-file-count.cjs +34 -1
- package/scripts/lint-vendored-deps.cjs +41 -5
- package/scripts/lint-workflow-shellcheck-baseline.json +25 -10
- package/scripts/mutation-matrix.cjs +50 -5
- package/scripts/prompt-injection-scan.sh +4 -0
- package/scripts/release-tarball-smoke.cjs +194 -1
- package/scripts/require-issue-link-policy.cjs +6 -2
- package/scripts/sync-runtime-launcher.cjs +184 -2
- package/scripts/verify-npm-publish.cjs +76 -20
- package/skills/gsd-add-tests/SKILL.md +6 -1
- package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
- package/skills/gsd-audit-fix/SKILL.md +5 -0
- package/skills/gsd-audit-milestone/SKILL.md +6 -1
- package/skills/gsd-autonomous/SKILL.md +7 -2
- package/skills/gsd-capture/SKILL.md +9 -5
- package/skills/gsd-code-review/SKILL.md +7 -2
- package/skills/gsd-complete-milestone/SKILL.md +4 -0
- package/skills/gsd-config/SKILL.md +8 -4
- package/skills/gsd-debug/SKILL.md +11 -7
- package/skills/gsd-discuss-phase/SKILL.md +7 -3
- package/skills/gsd-docs-update/SKILL.md +12 -7
- package/skills/gsd-eval-review/SKILL.md +6 -1
- package/skills/gsd-execute-phase/SKILL.md +12 -7
- package/skills/gsd-extract-learnings/SKILL.md +5 -0
- package/skills/gsd-fast/SKILL.md +4 -0
- package/skills/gsd-forensics/SKILL.md +5 -1
- package/skills/gsd-graphify/SKILL.md +10 -6
- package/skills/gsd-health/SKILL.md +5 -0
- package/skills/gsd-help/SKILL.md +7 -2
- package/skills/gsd-import/SKILL.md +7 -3
- package/skills/gsd-inbox/SKILL.md +5 -0
- package/skills/gsd-ingest-docs/SKILL.md +5 -1
- package/skills/gsd-manager/SKILL.md +6 -1
- package/skills/gsd-map-codebase/SKILL.md +7 -3
- package/skills/gsd-mempalace-capture/SKILL.md +12 -4
- package/skills/gsd-mempalace-recall/SKILL.md +5 -1
- package/skills/gsd-milestone-summary/SKILL.md +5 -1
- package/skills/gsd-mvp-phase/SKILL.md +8 -3
- package/skills/gsd-new-milestone/SKILL.md +6 -1
- package/skills/gsd-new-project/SKILL.md +5 -0
- package/skills/gsd-next/SKILL.md +6 -1
- package/skills/gsd-ns-context/SKILL.md +4 -0
- package/skills/gsd-ns-ideate/SKILL.md +4 -0
- package/skills/gsd-ns-manage/SKILL.md +4 -0
- package/skills/gsd-ns-project/SKILL.md +4 -0
- package/skills/gsd-ns-review/SKILL.md +4 -0
- package/skills/gsd-ns-workflow/SKILL.md +4 -0
- package/skills/gsd-onboard/SKILL.md +6 -1
- package/skills/gsd-pause-work/SKILL.md +5 -1
- package/skills/gsd-phase/SKILL.md +8 -4
- package/skills/gsd-plan-phase/SKILL.md +6 -1
- package/skills/gsd-plan-review-convergence/SKILL.md +10 -6
- package/skills/gsd-pr-branch/SKILL.md +4 -0
- package/skills/gsd-profile-user/SKILL.md +5 -1
- package/skills/gsd-progress/SKILL.md +7 -2
- package/skills/gsd-quick/SKILL.md +16 -10
- package/skills/gsd-quick-batch/SKILL.md +21 -9
- package/skills/gsd-review/SKILL.md +7 -4
- package/skills/gsd-review-backlog/SKILL.md +3 -2
- package/skills/gsd-secure-phase/SKILL.md +6 -1
- package/skills/gsd-ship/SKILL.md +5 -0
- package/skills/gsd-sketch/SKILL.md +7 -2
- package/skills/gsd-spec-phase/SKILL.md +5 -1
- package/skills/gsd-spike/SKILL.md +8 -3
- package/skills/gsd-surface/SKILL.md +5 -1
- package/skills/gsd-thread/SKILL.md +4 -0
- package/skills/gsd-ui-phase/SKILL.md +6 -1
- package/skills/gsd-ui-review/SKILL.md +6 -1
- package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
- package/skills/gsd-undo/SKILL.md +5 -1
- package/skills/gsd-update/SKILL.md +6 -2
- package/skills/gsd-validate-phase/SKILL.md +6 -1
- package/skills/gsd-verify-work/SKILL.md +6 -1
- package/skills/gsd-workspace/SKILL.md +7 -3
- package/skills/gsd-workstreams/SKILL.md +6 -6
- package/vscode/package.json +1 -1
package/bin/install.js
CHANGED
|
@@ -49,7 +49,7 @@ const { isTestHomeGuardRefusal } = require('../gsd-core/bin/lib/real-home-guard.
|
|
|
49
49
|
// (getConfigDirFromHome and the runtime-content-rewrite loops below) — #2876
|
|
50
50
|
// retired the re-export; tests now import getDirName directly from
|
|
51
51
|
// gsd-core/bin/lib/runtime-name-policy.cjs.
|
|
52
|
-
const { getDirName, getRuntimeLabel, getGlobalConfigHomeFragment, runtimeFlags,
|
|
52
|
+
const { getDirName, getRuntimeLabel, getGlobalConfigHomeFragment, runtimeFlags, hostBehaviorsFor } = require('../gsd-core/bin/lib/runtime-name-policy.cjs');
|
|
53
53
|
const {
|
|
54
54
|
applyWorktreeBaseRef,
|
|
55
55
|
readBaseRefFromSettings,
|
|
@@ -104,7 +104,7 @@ const hooksSurface = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
|
|
104
104
|
*/
|
|
105
105
|
function shouldNormalizeHyphenNamespaceInAgentBody(runtime) {
|
|
106
106
|
if (typeof runtime !== 'string' || runtime === '') return false;
|
|
107
|
-
return
|
|
107
|
+
return hostBehaviorsFor(runtime).hyphenNameAgentBody === true;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
/**
|
|
@@ -436,7 +436,18 @@ const GSD_CHANGESET_FILES = [
|
|
|
436
436
|
'github-release-notes.cjs', 'lint.cjs', 'new.cjs',
|
|
437
437
|
'README.md', // documentation only — not user-authored
|
|
438
438
|
];
|
|
439
|
-
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', 'shellcheck-fetch.cjs', 'npm-version-check-diagnosis.cjs', 'platform-conformance-tier.generated.cjs', 'suite-detection.cjs', 'macos-conformance-tier.generated.cjs'];
|
|
439
|
+
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', 'shellcheck-fetch.cjs', 'npm-version-check-diagnosis.cjs', 'platform-conformance-tier.generated.cjs', 'suite-detection.cjs', 'macos-conformance-tier.generated.cjs', 'vendor-bundle.cjs', 'registration-ledger-preload.cjs'];
|
|
440
|
+
|
|
441
|
+
// #4544 — the Codex hook payload the install stages into <targetDir>/hooks/.
|
|
442
|
+
// Hoisted to module scope (the #3184 precedent above) so the rollback's
|
|
443
|
+
// incomplete-capture path can name exactly the files GSD owns without a
|
|
444
|
+
// second copy of the list drifting away from the staging site, which lives
|
|
445
|
+
// inside the Codex config block where the constant used to be declared.
|
|
446
|
+
const CODEX_HOOKS_TO_COPY = [
|
|
447
|
+
'gsd-check-update.js',
|
|
448
|
+
'gsd-check-update-worker.js',
|
|
449
|
+
'managed-hooks-registry.cjs',
|
|
450
|
+
];
|
|
440
451
|
|
|
441
452
|
/**
|
|
442
453
|
* Resolve a runtime's shared-hooks directory name from its descriptor.
|
|
@@ -462,7 +473,7 @@ const GSD_SCRIPTS_LIB_FILES = ['cli-exit.cjs', 'allowlist-ratchet.cjs', 'drift-s
|
|
|
462
473
|
* @returns {string}
|
|
463
474
|
*/
|
|
464
475
|
function resolveSharedHooksDirName(runtime) {
|
|
465
|
-
const raw =
|
|
476
|
+
const raw = hostBehaviorsFor(runtime).sharedHooksDirName;
|
|
466
477
|
if (typeof raw !== 'string') return SHARED_HOOKS_DIR_DEFAULT;
|
|
467
478
|
const name = raw.trim();
|
|
468
479
|
if (name === '') return SHARED_HOOKS_DIR_DEFAULT;
|
|
@@ -617,68 +628,6 @@ try {
|
|
|
617
628
|
_installedCapabilityRegistry = _capabilityRegistry;
|
|
618
629
|
}
|
|
619
630
|
|
|
620
|
-
// Fail-safe floor for the reference host's #338-privacy-critical behaviors, used
|
|
621
|
-
// ONLY when the first-party capability registry cannot be loaded (a broken bundle).
|
|
622
|
-
// Without it, a registry-load failure would make `_hostBehaviors('claude')` return
|
|
623
|
-
// {} and silently route a claude LOCAL install to the repo-shared, committed
|
|
624
|
-
// `settings.json` instead of the gitignored `settings.local.json` (#338) — leaking
|
|
625
|
-
// engineer-specific absolute paths. Keyed by runtime id (a DATA lookup, not a
|
|
626
|
-
// hardcoded string-equality branch) so behavior degrades CLOSED (safe), never open.
|
|
627
|
-
// The live descriptor (capabilities/claude/capability.json) remains the source of
|
|
628
|
-
// truth; this mirrors only the privacy-load-bearing subset. (ADR-1239 / #2086)
|
|
629
|
-
//
|
|
630
|
-
// #2870: NOT routed through the Install Scope Module (resolveScope,
|
|
631
|
-
// src/install-scope.cts) despite that module owning per-scope settings-file
|
|
632
|
-
// resolution elsewhere in this file. resolveScope's own descriptor lookup
|
|
633
|
-
// goes through the SAME capability registry require this floor exists to
|
|
634
|
-
// survive the failure of (see getRegistry() in install-scope.cts) — so on
|
|
635
|
-
// exactly the "registry failed to load" path this constant is for,
|
|
636
|
-
// resolveScope would throw too. Routing through it here would trade a
|
|
637
|
-
// graceful, documented degrade for a crash in the one case this floor was
|
|
638
|
-
// added to prevent. This hardcoded literal is the correct, honest answer,
|
|
639
|
-
// not an un-migrated leftover.
|
|
640
|
-
const FALLBACK_HOST_BEHAVIORS = Object.freeze({
|
|
641
|
-
claude: Object.freeze({
|
|
642
|
-
settingsFileByScope: Object.freeze({ local: 'settings.local.json', global: 'settings.json' }),
|
|
643
|
-
permissionsSchema: 'claude',
|
|
644
|
-
sourceMarkerFile: '.gsd-source',
|
|
645
|
-
hyphenNameAgentBody: true,
|
|
646
|
-
legacyCommandsGsdInstallMigration: true,
|
|
647
|
-
legacyCommandsGsdUninstall: 'global',
|
|
648
|
-
}),
|
|
649
|
-
// antigravity's global config dir is resolved dynamically (env-overridable,
|
|
650
|
-
// multi-segment) via resolveAntigravityGlobalDir in getConfigDirFromHome. If the
|
|
651
|
-
// registry fails to load, this floor keeps that routing intact instead of
|
|
652
|
-
// silently falling through to the generic getGlobalConfigHomeFragment default
|
|
653
|
-
// (which would return the wrong '.claude' fragment). (ADR-1239 / #2096)
|
|
654
|
-
antigravity: Object.freeze({ globalDirResolver: 'antigravity' }),
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
/**
|
|
658
|
-
* Resolve a runtime's host behaviors from a capability registry, with the
|
|
659
|
-
* #338-privacy fail-safe floor when the registry (or the runtime's descriptor)
|
|
660
|
-
* is unavailable. Registry is passed in so this is unit-testable under a
|
|
661
|
-
* simulated registry-load failure. (ADR-1239 / #2086)
|
|
662
|
-
*/
|
|
663
|
-
function _resolveHostBehaviors(runtime, registry) {
|
|
664
|
-
const cap = registry && registry.runtimes && registry.runtimes[runtime];
|
|
665
|
-
const declared = cap && cap.runtime && cap.runtime.hostBehaviors;
|
|
666
|
-
if (declared) return declared;
|
|
667
|
-
return FALLBACK_HOST_BEHAVIORS[runtime] || {};
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
/**
|
|
671
|
-
* Host-specific install behaviors, declared on the runtime descriptor
|
|
672
|
-
* (capabilities/<runtime>/capability.json -> runtime.hostBehaviors) instead of
|
|
673
|
-
* scattered `runtime === '<id>'` string checks (ADR-1239 / #2086). Returns {}
|
|
674
|
-
* for runtimes that declare none, so every behavior branch degrades to the
|
|
675
|
-
* generic path by default — EXCEPT the reference host's #338-critical keys, which
|
|
676
|
-
* fall back to FALLBACK_HOST_BEHAVIORS if the registry failed to load.
|
|
677
|
-
*/
|
|
678
|
-
function _hostBehaviors(runtime) {
|
|
679
|
-
return _resolveHostBehaviors(runtime, _capabilityRegistry);
|
|
680
|
-
}
|
|
681
|
-
|
|
682
631
|
/**
|
|
683
632
|
* Read a runtime's documentation-sourced `hostIntegration.dispatch` axes
|
|
684
633
|
* (ADR-1239 Phase A — `capabilities/<runtime>/capability.json`
|
|
@@ -686,7 +635,7 @@ function _hostBehaviors(runtime) {
|
|
|
686
635
|
* background, backgroundDispatch, subagentToolkit}`. These are validated,
|
|
687
636
|
* closed-vocabulary FACTS about what the runtime's real dispatch primitive
|
|
688
637
|
* supports (never inferred) — see `docs/reference/host-integration-capability-
|
|
689
|
-
* matrix.md` for citations. Unlike `
|
|
638
|
+
* matrix.md` for citations. Unlike `hostBehaviorsFor` (install *policy*), this is
|
|
690
639
|
* the negotiated *capability* surface; #2284 is its first content-projection
|
|
691
640
|
* consumer (previously read only by `shouldFlattenDispatch`). Returns `{}` if
|
|
692
641
|
* the registry or the runtime's descriptor is unavailable, so callers must
|
|
@@ -812,6 +761,7 @@ const {
|
|
|
812
761
|
applyInstallerMigrationPlan,
|
|
813
762
|
discoverInstallerMigrations,
|
|
814
763
|
MANIFEST_SCHEMA_VERSION,
|
|
764
|
+
readInstallManifest,
|
|
815
765
|
runInstallerMigrations,
|
|
816
766
|
} = require(path.join(_gsdLibDir, 'installer-migrations.cjs'));
|
|
817
767
|
const {
|
|
@@ -821,6 +771,7 @@ const {
|
|
|
821
771
|
} = require(path.join(_gsdLibDir, 'installer-migration-report.cjs'));
|
|
822
772
|
const {
|
|
823
773
|
resolveRuntimeArtifactLayout,
|
|
774
|
+
resolveAdvertisedNewProject,
|
|
824
775
|
} = require(path.join(_gsdLibDir, 'runtime-artifact-layout.cjs'));
|
|
825
776
|
const {
|
|
826
777
|
readSurface,
|
|
@@ -925,6 +876,25 @@ const hasSkillsRoot = args.includes('--skills-root');
|
|
|
925
876
|
const hasPortableHooks = args.includes('--portable-hooks') || process.env.GSD_PORTABLE_HOOKS === '1';
|
|
926
877
|
const hasMinimal = args.includes('--minimal') || args.includes('--core-only');
|
|
927
878
|
const hasDryRun = args.includes('--dry-run');
|
|
879
|
+
// #4377: emit project-relative `@` includes (`.claude/gsd-core/...`) for a
|
|
880
|
+
// LOCAL install instead of this checkout's absolute path.
|
|
881
|
+
//
|
|
882
|
+
// Opt-in, and it stays opt-in: absolute includes work for a single checkout,
|
|
883
|
+
// which is nearly everyone, and flipping the default would change every
|
|
884
|
+
// existing local install to solve a problem those users do not have. The
|
|
885
|
+
// people who need it know they do — they run the same repo from several git
|
|
886
|
+
// worktrees, where a baked absolute path means every worktree reads its
|
|
887
|
+
// workflow prose out of whichever checkout happened to run the installer, and
|
|
888
|
+
// updating that one checkout breaks all the others at once with no way to
|
|
889
|
+
// stage it.
|
|
890
|
+
//
|
|
891
|
+
// Exported through the environment rather than threaded as a parameter,
|
|
892
|
+
// exactly like --portable-hooks/GSD_PORTABLE_HOOKS above: five separate seams
|
|
893
|
+
// compute a path prefix (the install engine, both rewrite entry points, the
|
|
894
|
+
// install plan, and applySurface), and one variable they all read cannot fall
|
|
895
|
+
// out of sync the way five signatures can.
|
|
896
|
+
const hasRelativeIncludes = args.includes('--relative-includes') || process.env.GSD_RELATIVE_INCLUDES === '1';
|
|
897
|
+
if (hasRelativeIncludes) process.env.GSD_RELATIVE_INCLUDES = '1';
|
|
928
898
|
// #3031: opt-in reclaim of the GSD artifacts a PRE-#2755 `--kimi-code` install
|
|
929
899
|
// orphaned in Kimi CLI's `~/.kimi`. Opt-in and not automatic because the stale
|
|
930
900
|
// block is BYTE-IDENTICAL to a legitimate Kimi CLI one — both runtimes render
|
|
@@ -1147,13 +1117,13 @@ function getConfigDirFromHome(runtime, isGlobal) {
|
|
|
1147
1117
|
// !isGlobal returns at the top of this function.)
|
|
1148
1118
|
// Descriptor-driven (ADR-1239 / #2096): folded from a hardcoded
|
|
1149
1119
|
// `runtime === 'antigravity'` literal into a read of the runtime's
|
|
1150
|
-
// `hostBehaviors.globalDirResolver` descriptor field (via
|
|
1151
|
-
// also degrades to
|
|
1120
|
+
// `hostBehaviors.globalDirResolver` descriptor field (via hostBehaviorsFor, which
|
|
1121
|
+
// also degrades to its #338 floor on registry-load failure). This is
|
|
1152
1122
|
// antigravity-unique: unlike `configHome.kind === 'dot-home-nested'` (which
|
|
1153
1123
|
// windsurf also declares — see capabilities/windsurf/capability.json — and
|
|
1154
1124
|
// would wrongly route windsurf's global dir through
|
|
1155
1125
|
// resolveAntigravityGlobalDir), `globalDirResolver` is only set by antigravity.
|
|
1156
|
-
if (
|
|
1126
|
+
if (hostBehaviorsFor(runtime).globalDirResolver === 'antigravity') {
|
|
1157
1127
|
const antigravityDir = resolveAntigravityGlobalDir();
|
|
1158
1128
|
const rel = path.relative(os.homedir(), antigravityDir);
|
|
1159
1129
|
const segments = rel.split(path.sep).filter(Boolean);
|
|
@@ -1252,7 +1222,7 @@ if (hasUninstall) {
|
|
|
1252
1222
|
|
|
1253
1223
|
// Show help if requested
|
|
1254
1224
|
if (hasHelp) {
|
|
1255
|
-
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`);
|
|
1225
|
+
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}--relative-includes${reset} With --local: write project-relative @ includes\n (.claude/gsd-core/...) instead of this checkout's\n absolute path, so several git worktrees of one repo\n each read their own copy (also GSD_RELATIVE_INCLUDES=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}# Local install for a repo worked from several git worktrees${reset}\n npx ${pkg.name} --claude --local --relative-includes\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`);
|
|
1256
1226
|
process.exit(0);
|
|
1257
1227
|
}
|
|
1258
1228
|
|
|
@@ -1563,12 +1533,12 @@ function getCommitAttribution(runtime) {
|
|
|
1563
1533
|
|
|
1564
1534
|
let result;
|
|
1565
1535
|
|
|
1566
|
-
const _attrResolverKey =
|
|
1536
|
+
const _attrResolverKey = hostBehaviorsFor(runtime).attributionConfigResolver;
|
|
1567
1537
|
if (_attrResolverKey && ATTRIBUTION_CONFIG_RESOLVERS[_attrResolverKey]) {
|
|
1568
1538
|
const resolveConfigPath = ATTRIBUTION_CONFIG_RESOLVERS[_attrResolverKey];
|
|
1569
1539
|
const config = readSettings(resolveConfigPath(getGlobalConfigDir(runtime, null)));
|
|
1570
1540
|
result = (config && config.disable_ai_attribution === true) ? null : undefined;
|
|
1571
|
-
} else if (
|
|
1541
|
+
} else if (hostBehaviorsFor(runtime).attributionSource === 'settings-json-commit') {
|
|
1572
1542
|
// Claude Code
|
|
1573
1543
|
const settings = readSettings(path.join(getGlobalConfigDir(runtime, explicitConfigDir), 'settings.json'));
|
|
1574
1544
|
if (!settings || !settings.attribution || settings.attribution.commit === undefined) {
|
|
@@ -1625,19 +1595,20 @@ const claudeToOpencodeTools = {
|
|
|
1625
1595
|
WebSearch: 'websearch', // Plugin/MCP - keep for compatibility
|
|
1626
1596
|
};
|
|
1627
1597
|
|
|
1628
|
-
// Tool name mapping from Claude Code to
|
|
1629
|
-
//
|
|
1630
|
-
const
|
|
1631
|
-
|
|
1598
|
+
// Tool name mapping from Claude Code to Antigravity
|
|
1599
|
+
// Antigravity uses Gemini's snake_case built-in tool names
|
|
1600
|
+
const claudeToAntigravityTools = {
|
|
1601
|
+
// #4705: Antigravity-NATIVE tool names (see src/runtime-artifact-conversion.cts)
|
|
1602
|
+
Read: 'view_file',
|
|
1632
1603
|
Write: 'write_file',
|
|
1633
|
-
Edit: '
|
|
1634
|
-
Bash: '
|
|
1604
|
+
Edit: 'replace_file_content',
|
|
1605
|
+
Bash: 'run_command',
|
|
1635
1606
|
Glob: 'glob',
|
|
1636
|
-
Grep: '
|
|
1607
|
+
Grep: 'grep_search',
|
|
1637
1608
|
WebSearch: 'google_web_search',
|
|
1638
1609
|
WebFetch: 'web_fetch',
|
|
1639
1610
|
TodoWrite: 'write_todos',
|
|
1640
|
-
}
|
|
1611
|
+
}
|
|
1641
1612
|
|
|
1642
1613
|
// Tool name mapping from Claude/GSD agents to Kimi CLI module paths.
|
|
1643
1614
|
// Kimi custom agent YAML requires fully-qualified module paths.
|
|
@@ -1687,24 +1658,24 @@ function convertToolName(claudeTool) {
|
|
|
1687
1658
|
}
|
|
1688
1659
|
|
|
1689
1660
|
/**
|
|
1690
|
-
* Convert a Claude Code tool name to
|
|
1691
|
-
* - Applies Claude→
|
|
1692
|
-
* - Filters out MCP tools (mcp__*) — they are auto-discovered at runtime in
|
|
1693
|
-
* - Filters out Task/Agent — agents are auto-registered as tools in
|
|
1694
|
-
* @returns {string|null}
|
|
1661
|
+
* Convert a Claude Code tool name to Antigravity format
|
|
1662
|
+
* - Applies Claude→Antigravity mapping (Read→read_file, Bash→run_shell_command, etc.)
|
|
1663
|
+
* - Filters out MCP tools (mcp__*) — they are auto-discovered at runtime in Antigravity
|
|
1664
|
+
* - Filters out Task/Agent — agents are auto-registered as tools in Antigravity
|
|
1665
|
+
* @returns {string|null} Antigravity tool name, or null if tool should be excluded
|
|
1695
1666
|
*/
|
|
1696
|
-
function
|
|
1667
|
+
function convertAntigravityToolName(claudeTool) {
|
|
1697
1668
|
// MCP tools: exclude — auto-discovered from mcpServers config at runtime
|
|
1698
1669
|
if (claudeTool.startsWith('mcp__')) {
|
|
1699
1670
|
return null;
|
|
1700
1671
|
}
|
|
1701
1672
|
// Task/Agent: exclude — agents are auto-registered as callable tools.
|
|
1702
|
-
// AskUserQuestion: exclude — Gemini
|
|
1703
|
-
// emitting it causes frontmatter validation errors (#3362).
|
|
1704
|
-
// Skill/SlashCommand: exclude — Gemini
|
|
1705
|
-
// the lowercase fallback would emit an invalid
|
|
1706
|
-
// that fails frontmatter validation
|
|
1707
|
-
// the entire agent load (#1394).
|
|
1673
|
+
// AskUserQuestion: exclude — Antigravity (Gemini tool dialect) does not expose
|
|
1674
|
+
// an ask_user tool; emitting it causes frontmatter validation errors (#3362).
|
|
1675
|
+
// Skill/SlashCommand: exclude — Antigravity (Gemini tool dialect) has no 'skill'
|
|
1676
|
+
// built-in tool; the lowercase fallback would emit an invalid
|
|
1677
|
+
// 'skill'/'slashcommand' name that fails frontmatter validation
|
|
1678
|
+
// (tools.N: Invalid tool name) and aborts the entire agent load (#1394).
|
|
1708
1679
|
if (
|
|
1709
1680
|
claudeTool === 'Task' ||
|
|
1710
1681
|
claudeTool === 'Agent' ||
|
|
@@ -1716,8 +1687,8 @@ function convertGeminiToolName(claudeTool) {
|
|
|
1716
1687
|
return null;
|
|
1717
1688
|
}
|
|
1718
1689
|
// Check for explicit mapping
|
|
1719
|
-
if (
|
|
1720
|
-
return
|
|
1690
|
+
if (claudeToAntigravityTools[claudeTool]) {
|
|
1691
|
+
return claudeToAntigravityTools[claudeTool];
|
|
1721
1692
|
}
|
|
1722
1693
|
// Default: lowercase
|
|
1723
1694
|
return claudeTool.toLowerCase();
|
|
@@ -2023,7 +1994,12 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
|
|
|
2023
1994
|
const names = cmdNames || readGsdCommandNames();
|
|
2024
1995
|
const normalizedBody = transformContentToHyphen(body, names);
|
|
2025
1996
|
|
|
2026
|
-
|
|
1997
|
+
// #4324: the description is the text the host's skill picker renders, so it
|
|
1998
|
+
// needs the same hyphen normalisation the body gets — otherwise a `/gsd:<cmd>`
|
|
1999
|
+
// mention in a command description ships the retired colon form to the user.
|
|
2000
|
+
const description = transformContentToHyphen(
|
|
2001
|
+
extractFrontmatterField(frontmatter, 'description') || '', names,
|
|
2002
|
+
);
|
|
2027
2003
|
const argumentHint = extractFrontmatterField(frontmatter, 'argument-hint');
|
|
2028
2004
|
const agent = extractFrontmatterField(frontmatter, 'agent');
|
|
2029
2005
|
// #769: preserve context: from source command files so it is emitted into
|
|
@@ -2047,13 +2023,13 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
|
|
|
2047
2023
|
// Hermes' SKILL.md spec lists `version` as a required frontmatter field.
|
|
2048
2024
|
// Track GSD's package version so Hermes' skill_view() reports a stable
|
|
2049
2025
|
// identifier per install.
|
|
2050
|
-
if (
|
|
2026
|
+
if (hostBehaviorsFor(runtime).skillFrontmatterVersion) fm += `version: ${yamlQuote(pkg.version)}\n`;
|
|
2051
2027
|
// #778 (b) — numeric priority for /skills ordering, declared on the runtime
|
|
2052
2028
|
// descriptor (runtime.hostBehaviors.skillPriorityFrontmatter). Scoped to
|
|
2053
2029
|
// runtimes that declare the flag so Claude/Hermes skill frontmatter is
|
|
2054
2030
|
// unchanged (they ignore the field, but we keep their output byte-stable).
|
|
2055
2031
|
// skillName is the `gsd-<stem>` dir name. (ADR-1239 / #2086)
|
|
2056
|
-
if (
|
|
2032
|
+
if (hostBehaviorsFor(runtime).skillPriorityFrontmatter) {
|
|
2057
2033
|
const stem = typeof skillName === 'string' && skillName.startsWith('gsd-')
|
|
2058
2034
|
? skillName.slice(4)
|
|
2059
2035
|
: skillName;
|
|
@@ -2486,12 +2462,16 @@ function convertClaudeAgentToAntigravityAgent(content, isGlobal = false) {
|
|
|
2486
2462
|
const color = extractFrontmatterField(frontmatter, 'color');
|
|
2487
2463
|
const toolsRaw = extractFrontmatterField(frontmatter, 'tools') || '';
|
|
2488
2464
|
|
|
2489
|
-
// Map tools to
|
|
2465
|
+
// Map tools to Antigravity equivalents (reuse existing convertAntigravityToolName)
|
|
2490
2466
|
const claudeTools = toolsRaw.split(',').map(t => t.trim()).filter(Boolean);
|
|
2491
|
-
const mappedTools = claudeTools.map(t =>
|
|
2467
|
+
const mappedTools = claudeTools.map(t => convertAntigravityToolName(t)).filter(Boolean);
|
|
2492
2468
|
|
|
2493
2469
|
// #2876: quote description for the same reason as the skill variant.
|
|
2494
|
-
|
|
2470
|
+
// #4705: tools is a YAML SEQUENCE of native names (see the src twin).
|
|
2471
|
+
const toolsBlock = mappedTools.length > 0
|
|
2472
|
+
? `tools:\n${mappedTools.map((t) => `- ${t}`).join('\n')}\n`
|
|
2473
|
+
: 'tools: []\n';
|
|
2474
|
+
let fm = `---\nname: ${name}\ndescription: ${yamlQuote(description)}\n${toolsBlock}`;
|
|
2495
2475
|
if (color) fm += `color: ${color}\n`;
|
|
2496
2476
|
fm += '---';
|
|
2497
2477
|
|
|
@@ -2514,20 +2494,11 @@ function yamlIdentifier(value) {
|
|
|
2514
2494
|
return yamlQuote(text);
|
|
2515
2495
|
}
|
|
2516
2496
|
|
|
2497
|
+
// The frontmatter block is the one the one fence owner (`locateFrontmatterFence`) finds, read
|
|
2498
|
+
// through the conversion module's reader — never a local `indexOf('---', 3)` scan, which ended
|
|
2499
|
+
// the block at the first `---` anywhere, even inside a value (found while implementing #5105).
|
|
2517
2500
|
function extractFrontmatterAndBody(content) {
|
|
2518
|
-
|
|
2519
|
-
return { frontmatter: null, body: content };
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2522
|
-
const endIndex = content.indexOf('---', 3);
|
|
2523
|
-
if (endIndex === -1) {
|
|
2524
|
-
return { frontmatter: null, body: content };
|
|
2525
|
-
}
|
|
2526
|
-
|
|
2527
|
-
return {
|
|
2528
|
-
frontmatter: content.substring(3, endIndex).trim(),
|
|
2529
|
-
body: content.substring(endIndex + 3),
|
|
2530
|
-
};
|
|
2501
|
+
return runtimeArtifactConversion.extractFrontmatterAndBody(content);
|
|
2531
2502
|
}
|
|
2532
2503
|
|
|
2533
2504
|
function extractFrontmatterField(frontmatter, fieldName) {
|
|
@@ -2772,7 +2743,7 @@ function convertClaudeCommandToTraeSkill(content, skillName) {
|
|
|
2772
2743
|
// is not formally documented (thin SPA docs) — descriptor-driven, single
|
|
2773
2744
|
// fixed GSD-side value (runtime.hostBehaviors.soloStageMetadata), inferred/
|
|
2774
2745
|
// best-effort.
|
|
2775
|
-
const soloStage =
|
|
2746
|
+
const soloStage = hostBehaviorsFor('trae').soloStageMetadata;
|
|
2776
2747
|
if (soloStage) fm += `stage: ${soloStage}\n`;
|
|
2777
2748
|
fm += '---';
|
|
2778
2749
|
return `${fm}\n${body}`;
|
|
@@ -2947,6 +2918,8 @@ function convertClaudeCommandToClineSkill(content, skillName, runtime = null, cm
|
|
|
2947
2918
|
let description = extractFrontmatterField(frontmatter, 'description');
|
|
2948
2919
|
if (!description) description = `Run GSD workflow ${skillName}.`;
|
|
2949
2920
|
description = toSingleLine(description);
|
|
2921
|
+
// #4324: same reason as the Claude skill converter above.
|
|
2922
|
+
description = transformContentToHyphen(description, names);
|
|
2950
2923
|
// Cline documented max is 1024 code points (not UTF-16 code units).
|
|
2951
2924
|
// Use Array.from to iterate by code point so that multibyte characters
|
|
2952
2925
|
// (e.g. emoji, astral-plane chars) are never split, which would produce
|
|
@@ -3763,7 +3736,7 @@ const HERMES_DISPATCH_TOOL_CONFIG = Object.freeze({
|
|
|
3763
3736
|
*/
|
|
3764
3737
|
function convertClaudeToHermesMarkdown(content, ctx) {
|
|
3765
3738
|
const runtime = (ctx && ctx.runtime) || 'hermes';
|
|
3766
|
-
const b =
|
|
3739
|
+
const b = hostBehaviorsFor(runtime).brandingRewrites;
|
|
3767
3740
|
let converted = content;
|
|
3768
3741
|
if (b) {
|
|
3769
3742
|
converted = converted.replace(/CLAUDE\.md/g, b['CLAUDE.md']);
|
|
@@ -3921,6 +3894,22 @@ Typed mapping (agent_type-capable schema only):
|
|
|
3921
3894
|
never fabricate a manual worktree protocol — route through the negotiated
|
|
3922
3895
|
isolation adapter, which still fails closed for hosts declaring \`none\` (#3360).
|
|
3923
3896
|
|
|
3897
|
+
Foreground handoffs:
|
|
3898
|
+
- spawn_agent is asynchronous. When the source Agent(...) or Task(...) declares
|
|
3899
|
+
run_in_background=false, call collaboration.wait_agent(timeout_ms=...) immediately after
|
|
3900
|
+
spawn and keep the parent turn active until that child returns a terminal result.
|
|
3901
|
+
- collaboration.wait_agent is a mailbox wakeup, NOT a completion oracle: "Wait completed"
|
|
3902
|
+
can mean only that a child sent an interim MESSAGE or status update. After every wakeup,
|
|
3903
|
+
inspect the named child's update/status. Only a FINAL_ANSWER or a terminal agent status
|
|
3904
|
+
(completed, failed, or cancelled) ends the foreground handoff.
|
|
3905
|
+
- On an interim MESSAGE or any non-terminal status, do not report an outcome, send a
|
|
3906
|
+
continuation, start parent work, or end the parent turn. Call collaboration.wait_agent
|
|
3907
|
+
again for the same child. If a terminal response is absent after an abnormal end, reconcile
|
|
3908
|
+
the workflow's durable artifacts before classifying the child.
|
|
3909
|
+
- This applies to one foreground child as well as fan-out. The child retains its workflow's
|
|
3910
|
+
own checkpoint loop; do not report an outcome or start any further parent work before its
|
|
3911
|
+
terminal result is available.
|
|
3912
|
+
|
|
3924
3913
|
Generic-agent workaround (multi_agent_v1 schema — NO agent_type field):
|
|
3925
3914
|
When only the generic \`multi_agent_v1\` schema is available, typed GSD agent dispatch
|
|
3926
3915
|
(\`gsd-planner\`, \`gsd-executor\`, etc.) is NOT possible. This is a known Codex limitation
|
|
@@ -3948,6 +3937,9 @@ Spawn restriction:
|
|
|
3948
3937
|
defaulting to inline execution.
|
|
3949
3938
|
|
|
3950
3939
|
Parallel fan-out:
|
|
3940
|
+
- For each child, loop on collaboration.wait_agent(timeout_ms=...) until its own terminal
|
|
3941
|
+
result is observed. A mailbox update from one child never completes another child, and an
|
|
3942
|
+
interim MESSAGE never completes its sender.
|
|
3951
3943
|
- Spawn multiple agents → collect agent IDs → \`collaboration.wait_agent(timeout_ms=...)\` for each to complete
|
|
3952
3944
|
- Do NOT use \`functions.wait(cell_id=...)\` — that is an unrelated exec-cell tool, not the collaboration wait
|
|
3953
3945
|
|
|
@@ -6978,7 +6970,7 @@ function writeCopilotHookConfig(targetDir) {
|
|
|
6978
6970
|
* model aliases). Preserves an explicit `true` opt-in and existing values.
|
|
6979
6971
|
*/
|
|
6980
6972
|
function writeNonClaudeDefaults(runtime) {
|
|
6981
|
-
if (
|
|
6973
|
+
if (hostBehaviorsFor(runtime).nativeModelAliases || process.env.GSD_TEST_MODE) return;
|
|
6982
6974
|
const gsdDir = path.join(os.homedir(), '.gsd');
|
|
6983
6975
|
const defaultsPath = path.join(gsdDir, 'defaults.json');
|
|
6984
6976
|
let releaseLock = null;
|
|
@@ -7209,20 +7201,12 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOve
|
|
|
7209
7201
|
// Runtime-neutral agent name replacement (#766)
|
|
7210
7202
|
convertedContent = neutralizeAgentReferences(convertedContent, 'AGENTS.md');
|
|
7211
7203
|
|
|
7212
|
-
//
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
}
|
|
7216
|
-
|
|
7217
|
-
// Find the end of frontmatter
|
|
7218
|
-
const endIndex = convertedContent.indexOf('---', 3);
|
|
7219
|
-
if (endIndex === -1) {
|
|
7204
|
+
// The frontmatter block, as the one fence owner finds it (none → nothing to convert).
|
|
7205
|
+
const { frontmatter, body } = extractFrontmatterAndBody(convertedContent);
|
|
7206
|
+
if (frontmatter === null) {
|
|
7220
7207
|
return convertedContent;
|
|
7221
7208
|
}
|
|
7222
7209
|
|
|
7223
|
-
const frontmatter = convertedContent.substring(3, endIndex).trim();
|
|
7224
|
-
const body = convertedContent.substring(endIndex + 3);
|
|
7225
|
-
|
|
7226
7210
|
// Parse frontmatter line by line (simple YAML parsing)
|
|
7227
7211
|
const lines = frontmatter.split('\n');
|
|
7228
7212
|
const newLines = [];
|
|
@@ -7376,20 +7360,12 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false, modelOverrid
|
|
|
7376
7360
|
// Runtime-neutral agent name replacement (#766)
|
|
7377
7361
|
convertedContent = neutralizeAgentReferences(convertedContent, 'AGENTS.md');
|
|
7378
7362
|
|
|
7379
|
-
//
|
|
7380
|
-
|
|
7363
|
+
// The frontmatter block, as the one fence owner finds it (none → nothing to convert).
|
|
7364
|
+
const { frontmatter, body } = extractFrontmatterAndBody(convertedContent);
|
|
7365
|
+
if (frontmatter === null) {
|
|
7381
7366
|
return convertedContent;
|
|
7382
7367
|
}
|
|
7383
7368
|
|
|
7384
|
-
// Find the end of frontmatter
|
|
7385
|
-
const endIndex = convertedContent.indexOf('---', 3);
|
|
7386
|
-
if (endIndex === -1) {
|
|
7387
|
-
return convertedContent;
|
|
7388
|
-
}
|
|
7389
|
-
|
|
7390
|
-
const frontmatter = convertedContent.substring(3, endIndex).trim();
|
|
7391
|
-
const body = convertedContent.substring(endIndex + 3);
|
|
7392
|
-
|
|
7393
7369
|
// Parse frontmatter line by line (simple YAML parsing)
|
|
7394
7370
|
const lines = frontmatter.split('\n');
|
|
7395
7371
|
const newLines = [];
|
|
@@ -7763,7 +7739,7 @@ const RUNTIME_CONTENT_DISPATCH = {
|
|
|
7763
7739
|
},
|
|
7764
7740
|
},
|
|
7765
7741
|
// qwen/hermes: brand VALUES are descriptor-driven (ADR-1239 / #2092) via
|
|
7766
|
-
//
|
|
7742
|
+
// hostBehaviorsFor(ctx.runtime).brandingRewrites — EXACT regexes/ordering
|
|
7767
7743
|
// preserved from the prior hardcoded-literal versions (including the
|
|
7768
7744
|
// qwen-specific `.claude/skills/` -> `.qwen/skills/` pre-rewrite, whose
|
|
7769
7745
|
// target is derived as `${b['.claude/']}skills/`).
|
|
@@ -7771,7 +7747,7 @@ const RUNTIME_CONTENT_DISPATCH = {
|
|
|
7771
7747
|
md: (content, ctx) => {
|
|
7772
7748
|
// Guarded (post-review #2092): degrade closed to a no-op if the
|
|
7773
7749
|
// registry fails to load, instead of throwing on `b['CLAUDE.md']`.
|
|
7774
|
-
const b =
|
|
7750
|
+
const b = hostBehaviorsFor(ctx.runtime).brandingRewrites;
|
|
7775
7751
|
if (b) {
|
|
7776
7752
|
content = content.replace(/CLAUDE\.md/g, b['CLAUDE.md']);
|
|
7777
7753
|
// #2284(b): skips <runtime_compatibility> comparison-table content (protected region).
|
|
@@ -7781,7 +7757,7 @@ const RUNTIME_CONTENT_DISPATCH = {
|
|
|
7781
7757
|
return content;
|
|
7782
7758
|
},
|
|
7783
7759
|
js: (content, ctx) => {
|
|
7784
|
-
const b =
|
|
7760
|
+
const b = hostBehaviorsFor(ctx.runtime).brandingRewrites;
|
|
7785
7761
|
if (b) {
|
|
7786
7762
|
content = content.replace(/\.claude\/skills\//g, `${b['.claude/']}skills/`);
|
|
7787
7763
|
content = content.replace(/\.claude\//g, b['.claude/']);
|
|
@@ -7800,7 +7776,7 @@ const RUNTIME_CONTENT_DISPATCH = {
|
|
|
7800
7776
|
// hostIntegration.dispatch facts.
|
|
7801
7777
|
md: (content, ctx) => convertClaudeToHermesMarkdown(content, ctx),
|
|
7802
7778
|
js: (content, ctx) => {
|
|
7803
|
-
const b =
|
|
7779
|
+
const b = hostBehaviorsFor(ctx.runtime).brandingRewrites;
|
|
7804
7780
|
if (b) {
|
|
7805
7781
|
content = content.replace(/\.claude\/skills\//g, `${b['.claude/']}skills/`);
|
|
7806
7782
|
content = content.replace(/\.claude\//g, b['.claude/']);
|
|
@@ -7914,44 +7890,63 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
|
|
|
7914
7890
|
content = filterRuntimeNotesForTarget(content, runtime);
|
|
7915
7891
|
|
|
7916
7892
|
if (!dispatch.mdSkipGenericRewrite) {
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
//
|
|
7924
|
-
//
|
|
7925
|
-
// (
|
|
7926
|
-
//
|
|
7927
|
-
//
|
|
7928
|
-
//
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
content =
|
|
7954
|
-
|
|
7893
|
+
// #4377: with a project-relative prefix, mask `${VAR:-default}` shell
|
|
7894
|
+
// defaults out of the substitutions below and restore them after. The
|
|
7895
|
+
// runtime launcher snippet probes gsd-tools through a chain of those
|
|
7896
|
+
// (`${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/...`, one per
|
|
7897
|
+
// runtime); they are shell word expansions, not markdown @ includes,
|
|
7898
|
+
// and a relative value there resolves against the shell's cwd instead
|
|
7899
|
+
// of the project. Swapping an include that points at the wrong
|
|
7900
|
+
// checkout for a path that points at nothing is not a fix, and the
|
|
7901
|
+
// launcher already probes `$(git rev-parse --show-toplevel)/.claude`
|
|
7902
|
+
// first, so the multi-worktree case is handled before these defaults
|
|
7903
|
+
// are ever reached. The shared helper is the single owner of the
|
|
7904
|
+
// balanced masking grammar used by this path and the rewrite engine.
|
|
7905
|
+
const rewriteGenericPaths = (body) => {
|
|
7906
|
+
content = body;
|
|
7907
|
+
const globalClaudeRegex = /~\/\.claude\//g;
|
|
7908
|
+
const globalClaudeHomeRegex = /\$HOME\/\.claude\//g;
|
|
7909
|
+
const localClaudeRegex = /\.\/\.claude\//g;
|
|
7910
|
+
content = content.replace(globalClaudeRegex, pathPrefix);
|
|
7911
|
+
content = content.replace(globalClaudeHomeRegex, pathPrefix);
|
|
7912
|
+
content = content.replace(localClaudeRegex, `./${dirName}/`);
|
|
7913
|
+
// #3544 review (Finding 1 fallout): guarded with the SAME
|
|
7914
|
+
// negative-lookahead convention already used at ~:2859-2860 below
|
|
7915
|
+
// ("preserve .claude-plugin and .claudeignore"). A naive `\b` here
|
|
7916
|
+
// is satisfied by ANY non-word character, including '-' — so for a
|
|
7917
|
+
// --config-dir whose name EXTENDS '.claude' (e.g. '.claude-work',
|
|
7918
|
+
// pathPrefix '$HOME/.claude-work/'), this pass re-matched the
|
|
7919
|
+
// '$HOME/.claude' PREFIX of its own slash-form output (lines above)
|
|
7920
|
+
// and re-appended the full prefix, corrupting every emitted path to
|
|
7921
|
+
// '$HOME/.claude-work-work/...'. Harmless no-op for the literal
|
|
7922
|
+
// default '.claude' (self-replace with an identical string), which
|
|
7923
|
+
// is why this went undetected until a non-default config-dir name
|
|
7924
|
+
// was exercised.
|
|
7925
|
+
content = content.replace(/~\/\.claude(?![\w-])/g, pathPrefix.replace(/\/$/, ''));
|
|
7926
|
+
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, pathPrefix.replace(/\/$/, ''));
|
|
7927
|
+
content = content.replace(/\.\/\.claude\b/g, `./${dirName}`);
|
|
7928
|
+
content = content.replace(/~\/\.qwen\//g, pathPrefix);
|
|
7929
|
+
content = content.replace(/\$HOME\/\.qwen\//g, pathPrefix);
|
|
7930
|
+
content = content.replace(/\.\/\.qwen\//g, `./${dirName}/`);
|
|
7931
|
+
content = content.replace(/~\/\.hermes\//g, pathPrefix);
|
|
7932
|
+
content = content.replace(/\$HOME\/\.hermes\//g, pathPrefix);
|
|
7933
|
+
content = content.replace(/\.\/\.hermes\//g, `./${dirName}/`);
|
|
7934
|
+
// #3544: restore @-file-reference lines to the tilde form Claude Code
|
|
7935
|
+
// actually expands — the SAME correction #3133 already applies to
|
|
7936
|
+
// skill/command bodies via _applyRuntimeRewrites's 'claude' case (see
|
|
7937
|
+
// restoreClaudeGlobalAtRefTilde's doc comment in
|
|
7938
|
+
// runtime-artifact-conversion.cts). This is the gsd-core/ spec-tree
|
|
7939
|
+
// emit path, which never had it: every @~/.claude/gsd-core/… include
|
|
7940
|
+
// in a global install's workflows/references tree silently resolved
|
|
7941
|
+
// to nothing (54 includes across 22 files on a live install).
|
|
7942
|
+
if (hostBehaviorsFor(runtime).restoreAtRefTildeInSpecTree) {
|
|
7943
|
+
content = runtimeArtifactConversion._restoreClaudeGlobalAtRefTilde(content, pathPrefix);
|
|
7944
|
+
}
|
|
7945
|
+
return content;
|
|
7946
|
+
};
|
|
7947
|
+
content = runtimeArtifactConversion._isRelativePathPrefix(pathPrefix)
|
|
7948
|
+
? runtimeArtifactConversion._withShellDefaultsPreserved(content, rewriteGenericPaths)
|
|
7949
|
+
: rewriteGenericPaths(content);
|
|
7955
7950
|
}
|
|
7956
7951
|
content = processAttribution(content, getCommitAttribution(runtime));
|
|
7957
7952
|
|
|
@@ -7960,7 +7955,7 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
|
|
|
7960
7955
|
// copyWithPathReplacement is the emit path for gsd-core/workflows/*.md;
|
|
7961
7956
|
// _applyRuntimeRewrites is NOT invoked here, so this is what makes the fix
|
|
7962
7957
|
// live in real installs (it is a no-op for files without those lines).
|
|
7963
|
-
if (!
|
|
7958
|
+
if (!hostBehaviorsFor(runtime).authorsCanonicalWorkflow) {
|
|
7964
7959
|
content = _stampNonClaudeRuntimeDefaults(content, runtime);
|
|
7965
7960
|
}
|
|
7966
7961
|
|
|
@@ -8310,7 +8305,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8310
8305
|
// `runtime === 'cline'` branch into hostBehaviors.localTargetIsProjectRoot.
|
|
8311
8306
|
const targetDir = isGlobal
|
|
8312
8307
|
? getGlobalConfigDir(runtime, explicitConfigDir)
|
|
8313
|
-
:
|
|
8308
|
+
: hostBehaviorsFor(runtime).localTargetIsProjectRoot
|
|
8314
8309
|
? process.cwd()
|
|
8315
8310
|
: path.join(process.cwd(), dirName);
|
|
8316
8311
|
|
|
@@ -8418,7 +8413,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8418
8413
|
}
|
|
8419
8414
|
|
|
8420
8415
|
// 1a. Non-layout Codex side-effects: agent .toml files, config.toml sections, hooks.json
|
|
8421
|
-
if (
|
|
8416
|
+
if (hostBehaviorsFor(runtime).tomlConfigInstall) {
|
|
8422
8417
|
const codexAgentsDir = path.join(targetDir, 'agents');
|
|
8423
8418
|
if (fs.existsSync(codexAgentsDir)) {
|
|
8424
8419
|
const tomlFiles = fs.readdirSync(codexAgentsDir);
|
|
@@ -8532,7 +8527,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8532
8527
|
// global cross-tool ~/.agents/AGENTS.md target.
|
|
8533
8528
|
// Descriptor-driven (ADR-1239 / #2090): folded from `runtime === 'cline'`
|
|
8534
8529
|
// into hostBehaviors.clineRulesSurface.
|
|
8535
|
-
if (
|
|
8530
|
+
if (hostBehaviorsFor(runtime).clineRulesSurface) {
|
|
8536
8531
|
const clinerulesDir = path.join(targetDir, '.clinerules');
|
|
8537
8532
|
for (const rel of ['gsd.md', path.join('hooks', 'PreToolUse')]) {
|
|
8538
8533
|
const p = path.join(clinerulesDir, rel);
|
|
@@ -8582,7 +8577,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8582
8577
|
// GSD-managed hook entries from hooks.json and clean up the managed hook
|
|
8583
8578
|
// scripts. Gated by the hostBehaviors.hooksJsonSurface descriptor axis, not a
|
|
8584
8579
|
// hardcoded `isCursor` branch.
|
|
8585
|
-
if (
|
|
8580
|
+
if (hostBehaviorsFor(runtime).hooksJsonSurface) {
|
|
8586
8581
|
const hooksJsonCleanup = removeCursorHooksJson(targetDir);
|
|
8587
8582
|
if (hooksJsonCleanup.changed) {
|
|
8588
8583
|
removedCount++;
|
|
@@ -8642,7 +8637,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8642
8637
|
|
|
8643
8638
|
// 1c. Claude local: remove flat gsd-*.md commands from commands/ (current layout,
|
|
8644
8639
|
// #1367 fix). Also remove legacy commands/gsd/ subdirectory from prior installs.
|
|
8645
|
-
if (!isGlobal &&
|
|
8640
|
+
if (!isGlobal && hostBehaviorsFor(runtime).localInstallStyle === 'legacy-flat') {
|
|
8646
8641
|
const commandsDir = path.join(targetDir, 'commands');
|
|
8647
8642
|
// Remove flat gsd-*.md files (current layout after #1367 fix)
|
|
8648
8643
|
if (fs.existsSync(commandsDir)) {
|
|
@@ -8708,7 +8703,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8708
8703
|
// removes the directory; we must preserve/restore user artifacts before that path.
|
|
8709
8704
|
// This block runs AFTER uninstallRuntimeArtifacts, so we check if the directory
|
|
8710
8705
|
// was already removed and skip if so (idempotent).
|
|
8711
|
-
if (
|
|
8706
|
+
if (hostBehaviorsFor(runtime).legacyCommandsGsdCleanup === true) {
|
|
8712
8707
|
// dev-preferences may have survived in skills/ as SKILL.md — nothing to do for
|
|
8713
8708
|
// that case. If a stale commands/gsd/ still exists (e.g. legacy was not removed),
|
|
8714
8709
|
// attempt migration. In practice _runLegacyUninstallCleanup removes it first,
|
|
@@ -8923,7 +8918,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8923
8918
|
// that declares the block (OpenCode, Kilo, ...), not just OpenCode. Only
|
|
8924
8919
|
// GSD's own plugin file is removed; the plugins/ dir is pruned only if it
|
|
8925
8920
|
// becomes empty, preserving any user-authored plugins for that host.
|
|
8926
|
-
const _np =
|
|
8921
|
+
const _np = hostBehaviorsFor(runtime).nativePlugin;
|
|
8927
8922
|
if (_np) {
|
|
8928
8923
|
const pluginsDir = path.join(targetDir, _np.dir);
|
|
8929
8924
|
const pluginPath = path.join(pluginsDir, _np.file);
|
|
@@ -9088,7 +9083,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
9088
9083
|
// to preserve any user-added allow/deny entries.
|
|
9089
9084
|
// Uses a local flag to avoid the shared `settingsModified` producing a false
|
|
9090
9085
|
// "Removed GSD permissions" message when only hooks/statusline changed.
|
|
9091
|
-
if (
|
|
9086
|
+
if (hostBehaviorsFor(runtime).permissionsSchema === 'claude' && settings.permissions) {
|
|
9092
9087
|
let permissionsModified = false;
|
|
9093
9088
|
if (Array.isArray(settings.permissions.allow)) {
|
|
9094
9089
|
const before = settings.permissions.allow.length;
|
|
@@ -9159,7 +9154,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
9159
9154
|
// runtimes that host MCP there (Augment), symmetric to the mcp_config.json
|
|
9160
9155
|
// removal for Antigravity below. Only the GSD-owned mcpServers.gsd key is
|
|
9161
9156
|
// removed — any other user-configured MCP servers are preserved.
|
|
9162
|
-
if (
|
|
9157
|
+
if (hostBehaviorsFor(runtime).mcpCompanion === 'settings-json' &&
|
|
9163
9158
|
settings.mcpServers && typeof settings.mcpServers === 'object' &&
|
|
9164
9159
|
settings.mcpServers.gsd !== undefined) {
|
|
9165
9160
|
delete settings.mcpServers.gsd;
|
|
@@ -9848,7 +9843,7 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9848
9843
|
// #1367: Claude local now writes flat gsd-*.md files at commands/ (not commands/gsd/).
|
|
9849
9844
|
// Claude local uses flatCommandsDir instead for manifest recording.
|
|
9850
9845
|
const flatCommandsDir = path.join(configDir, 'commands');
|
|
9851
|
-
const opencodeCommandDir = path.join(configDir,
|
|
9846
|
+
const opencodeCommandDir = path.join(configDir, hostBehaviorsFor(runtime).flatCommandDir || 'command');
|
|
9852
9847
|
// Hermes nests GSD skills under skills/gsd/ as a single category (#2841) —
|
|
9853
9848
|
// already encoded in its layout descriptor's destSubpath ('skills/gsd').
|
|
9854
9849
|
// All other runtimes that use the Codex-style skills layout use a flat skills/ root.
|
|
@@ -9863,7 +9858,7 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9863
9858
|
// `options.scope` could drift; one cannot.
|
|
9864
9859
|
const resolvedScope = options.scope === 'local' ? 'local' : 'global';
|
|
9865
9860
|
const codexSkillsDir = _resolveSkillsRootDir(runtime, configDir, resolvedScope);
|
|
9866
|
-
const codexSkillsManifestPrefix =
|
|
9861
|
+
const codexSkillsManifestPrefix = hostBehaviorsFor(runtime).skillsManifestPrefix || 'skills/';
|
|
9867
9862
|
// #3738: resolve the ACTUAL agents-install dir honoring an agents-kind `home`
|
|
9868
9863
|
// override (antigravity global → $HOME/.gemini/config/agents), mirroring
|
|
9869
9864
|
// _resolveSkillsRootDir for skills. Hardcoding configDir/agents left the
|
|
@@ -9890,6 +9885,11 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9890
9885
|
// it from the directory it happened to be found in (#2872).
|
|
9891
9886
|
runtime,
|
|
9892
9887
|
scope: resolvedScope,
|
|
9888
|
+
// #4377: a surface re-apply is a separate process and cannot rely on the
|
|
9889
|
+
// installer's environment. Persist only a safe project-relative prefix.
|
|
9890
|
+
relativeIncludePrefix: resolvedScope === 'local' && hasRelativeIncludes
|
|
9891
|
+
? runtimeArtifactConversion._projectRelativePrefixFromProjectRoot(process.cwd(), configDir)
|
|
9892
|
+
: undefined,
|
|
9893
9893
|
files: {},
|
|
9894
9894
|
};
|
|
9895
9895
|
|
|
@@ -9910,26 +9910,26 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9910
9910
|
// Claude local (#1367): flat gsd-*.md files at commands/ level.
|
|
9911
9911
|
// Only claude local writes gsd-*.md here; global installs don't emit commands,
|
|
9912
9912
|
// so this branch is a no-op for global (no matching files to find).
|
|
9913
|
-
if (
|
|
9913
|
+
if (hostBehaviorsFor(runtime).localInstallStyle === 'legacy-flat' && fs.existsSync(flatCommandsDir)) {
|
|
9914
9914
|
for (const file of fs.readdirSync(flatCommandsDir)) {
|
|
9915
9915
|
if (file.startsWith('gsd-') && file.endsWith('.md')) {
|
|
9916
9916
|
manifest.files['commands/' + file] = fileHash(path.join(flatCommandsDir, file));
|
|
9917
9917
|
}
|
|
9918
9918
|
}
|
|
9919
9919
|
}
|
|
9920
|
-
if (
|
|
9920
|
+
if (hostBehaviorsFor(runtime).flatCommandDir && fs.existsSync(opencodeCommandDir)) {
|
|
9921
9921
|
// #2329: derive the manifest key prefix from the SAME descriptor value used
|
|
9922
9922
|
// to compute opencodeCommandDir above, instead of a separately-hardcoded
|
|
9923
9923
|
// literal — a divergence here would silently break the manifest even after
|
|
9924
9924
|
// the destSubpath descriptor is corrected (Generative Fix Divergence guard).
|
|
9925
|
-
const flatCommandDirPrefix =
|
|
9925
|
+
const flatCommandDirPrefix = hostBehaviorsFor(runtime).flatCommandDir || 'command';
|
|
9926
9926
|
for (const file of fs.readdirSync(opencodeCommandDir)) {
|
|
9927
9927
|
if (file.startsWith('gsd-') && file.endsWith('.md')) {
|
|
9928
9928
|
manifest.files[flatCommandDirPrefix + '/' + file] = fileHash(path.join(opencodeCommandDir, file));
|
|
9929
9929
|
}
|
|
9930
9930
|
}
|
|
9931
9931
|
}
|
|
9932
|
-
if (!
|
|
9932
|
+
if (!hostBehaviorsFor(runtime).skipCodexSkillsManifest && fs.existsSync(codexSkillsDir)) {
|
|
9933
9933
|
// All runtimes (including Hermes post-#947) use the canonical 'gsd-' prefix.
|
|
9934
9934
|
const skillListPrefix = 'gsd-';
|
|
9935
9935
|
for (const skillName of listCodexSkillNames(codexSkillsDir, skillListPrefix)) {
|
|
@@ -9940,14 +9940,14 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9940
9940
|
}
|
|
9941
9941
|
}
|
|
9942
9942
|
// Descriptor-driven (#2090): hash the category DESCRIPTION.md so reinstall detects drift.
|
|
9943
|
-
if (
|
|
9943
|
+
if (hostBehaviorsFor(runtime).trackCategoryDescription) {
|
|
9944
9944
|
const descPath = path.join(codexSkillsDir, 'DESCRIPTION.md');
|
|
9945
9945
|
if (fs.existsSync(descPath)) {
|
|
9946
9946
|
manifest.files['skills/gsd/DESCRIPTION.md'] = fileHash(descPath);
|
|
9947
9947
|
}
|
|
9948
9948
|
}
|
|
9949
9949
|
}
|
|
9950
|
-
if (
|
|
9950
|
+
if (hostBehaviorsFor(runtime).agentManifestStyle === 'kimi-nested' && fs.existsSync(agentsDir)) {
|
|
9951
9951
|
const agentHashes = generateManifest(agentsDir);
|
|
9952
9952
|
for (const [rel, hash] of Object.entries(agentHashes)) {
|
|
9953
9953
|
const isRootAgent = rel === 'gsd.yaml' || rel === 'gsd.md';
|
|
@@ -9968,7 +9968,7 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9968
9968
|
// marker block, not the per-configDir manifest, since it lives outside it.)
|
|
9969
9969
|
// Descriptor-driven (ADR-1239 / #2090): folded from `isCline` into
|
|
9970
9970
|
// hostBehaviors.clineRulesSurface.
|
|
9971
|
-
if (
|
|
9971
|
+
if (hostBehaviorsFor(runtime).clineRulesSurface) {
|
|
9972
9972
|
for (const rel of ['.clinerules/gsd.md', '.clinerules/hooks/PreToolUse']) {
|
|
9973
9973
|
const dest = path.join(configDir, rel);
|
|
9974
9974
|
if (fs.existsSync(dest)) {
|
|
@@ -9989,7 +9989,7 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9989
9989
|
// skipSharedHooksInstall:true) — the redundant `&& !isCopilot` was removed.
|
|
9990
9990
|
// #2100: Windsurf's exclusion is likewise descriptor-driven (windsurf declares
|
|
9991
9991
|
// skipSharedHooksInstall:true) — the redundant `&& !isWindsurf` was removed.
|
|
9992
|
-
if (!isCodex &&
|
|
9992
|
+
if (!isCodex && hostBehaviorsFor(runtime).skipSharedHooksInstall !== true) {
|
|
9993
9993
|
// #3023: manifest keys must track the bundle wherever the descriptor put it,
|
|
9994
9994
|
// or uninstall/saveLocalPatches silently orphan the tree.
|
|
9995
9995
|
const sharedHooksDirName = resolveSharedHooksDirName(runtime);
|
|
@@ -10056,7 +10056,7 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10056
10056
|
|
|
10057
10057
|
// Track the OpenCode native plugin adapter (#1914) so update/drift detection
|
|
10058
10058
|
// and uninstall can account for it.
|
|
10059
|
-
const _npM =
|
|
10059
|
+
const _npM = hostBehaviorsFor(runtime).nativePlugin;
|
|
10060
10060
|
if (_npM) {
|
|
10061
10061
|
const pluginInstallPath = path.join(configDir, _npM.dir, _npM.file);
|
|
10062
10062
|
if (fs.existsSync(pluginInstallPath)) {
|
|
@@ -10287,7 +10287,7 @@ function saveLocalPatches(configDir, pristineCtx) {
|
|
|
10287
10287
|
skillsRoot !== resolvedConfig &&
|
|
10288
10288
|
!skillsRoot.startsWith(resolvedConfig + path.sep)
|
|
10289
10289
|
) {
|
|
10290
|
-
const prefix =
|
|
10290
|
+
const prefix = hostBehaviorsFor(patchRuntime).skillsManifestPrefix || 'skills/';
|
|
10291
10291
|
skillsRedirect = { root: skillsRoot, prefix };
|
|
10292
10292
|
}
|
|
10293
10293
|
}
|
|
@@ -10510,7 +10510,7 @@ function reportLocalPatches(configDir, runtime = DEFAULT_RUNTIME) {
|
|
|
10510
10510
|
try { meta = JSON.parse(fs.readFileSync(metaPath, 'utf8')); } catch { return []; }
|
|
10511
10511
|
|
|
10512
10512
|
if (meta.files && meta.files.length > 0) {
|
|
10513
|
-
const reapplyCommand =
|
|
10513
|
+
const reapplyCommand = hostBehaviorsFor(runtime).reapplyCommand || '/gsd-update --reapply';
|
|
10514
10514
|
console.log('');
|
|
10515
10515
|
console.log(' ' + yellow + 'Local patches detected' + reset + ' (from v' + meta.from_version + '):');
|
|
10516
10516
|
for (const f of meta.files) {
|
|
@@ -10538,12 +10538,12 @@ function reportInstallerMigrationResult(result) {
|
|
|
10538
10538
|
|
|
10539
10539
|
function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
10540
10540
|
// #2093: isKilo dropped — Kilo's agent/model-override handling below reads
|
|
10541
|
-
//
|
|
10541
|
+
// hostBehaviorsFor(runtime).frontmatterDialect === 'kilo' instead of this flag.
|
|
10542
10542
|
// #2095: isKimi dropped — kimi is now a hooks/ consumer like every other
|
|
10543
10543
|
// settings-json-adjacent runtime; the two `&& !isKimi` hooks-copy guards
|
|
10544
10544
|
// below were removed, leaving isKimi unused in this function (the kimi
|
|
10545
10545
|
// local-install-deferred branch above already reads
|
|
10546
|
-
//
|
|
10546
|
+
// hostBehaviorsFor(runtime).localInstallDeferred instead of this flag).
|
|
10547
10547
|
// #2096: isAntigravity dropped — antigravity's agents were already
|
|
10548
10548
|
// descriptor-driven (installRuntimeArtifacts), so its two legacy-agent-loop
|
|
10549
10549
|
// branches (the path-rewrite skip and the converter dispatch) were
|
|
@@ -10583,7 +10583,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10583
10583
|
// re-running install()) can warn again if the same condition recurs.
|
|
10584
10584
|
_codexResolverModelOmittedWarned = false;
|
|
10585
10585
|
|
|
10586
|
-
if (
|
|
10586
|
+
if (hostBehaviorsFor(runtime).localInstallDeferred && !isGlobal) {
|
|
10587
10587
|
console.log(` ${yellow}⚠${reset} Kimi local install is deferred for Phase 2.`);
|
|
10588
10588
|
console.log(` No .kimi-code/skills or .agents/skills project artifacts were written.`);
|
|
10589
10589
|
console.log(` Project-level Kimi install semantics remain deferred.`);
|
|
@@ -10654,7 +10654,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10654
10654
|
// auto-removed on reinstall (dual-read fallback per issue #791 spec).
|
|
10655
10655
|
const targetDir = isGlobal
|
|
10656
10656
|
? getGlobalConfigDir(runtime, explicitConfigDir)
|
|
10657
|
-
:
|
|
10657
|
+
: hostBehaviorsFor(runtime).localTargetIsProjectRoot
|
|
10658
10658
|
? process.cwd()
|
|
10659
10659
|
: path.join(process.cwd(), dirName);
|
|
10660
10660
|
|
|
@@ -10769,10 +10769,15 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10769
10769
|
const isWindowsHost = process.platform === 'win32';
|
|
10770
10770
|
const pathPrefix = computePathPrefix({
|
|
10771
10771
|
isGlobal,
|
|
10772
|
-
isOpencode:
|
|
10772
|
+
isOpencode: hostBehaviorsFor(runtime).skipHomePrefixSubstitution === true,
|
|
10773
10773
|
isWindowsHost,
|
|
10774
10774
|
resolvedTarget,
|
|
10775
10775
|
homeDir,
|
|
10776
|
+
// #4377: the runtime's own localConfigDir. This is the prefix that reaches
|
|
10777
|
+
// copyWithPathReplacement, i.e. the one actually written into every
|
|
10778
|
+
// emitted command/skill/workflow body — the rewrite-engine seams below
|
|
10779
|
+
// handle re-applied surfaces, not the first install.
|
|
10780
|
+
localDirName: hostBehaviorsFor(runtime).localTargetIsProjectRoot === true ? undefined : getDirName(runtime),
|
|
10776
10781
|
});
|
|
10777
10782
|
|
|
10778
10783
|
// runtimeLabel is now the single-source getRuntimeLabel lookup (ADR-1239
|
|
@@ -10783,6 +10788,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10783
10788
|
|
|
10784
10789
|
// Track installation failures
|
|
10785
10790
|
const failures = [];
|
|
10791
|
+
const configuredEntrypoints = [];
|
|
10786
10792
|
let installerMigrationResult = null;
|
|
10787
10793
|
const rollbackInstallerMigrations = () => {
|
|
10788
10794
|
if (!installerMigrationResult || typeof installerMigrationResult.rollback !== 'function') return;
|
|
@@ -10835,7 +10841,48 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10835
10841
|
// Map<filename, Buffer> — content snapshot of each pre-existing gsd-* agent file.
|
|
10836
10842
|
const codexPreInstallAgentContents = new Map();
|
|
10837
10843
|
let codexPreInstallVersionBytes = null;
|
|
10838
|
-
|
|
10844
|
+
// #4544 — manifest-driven snapshot state (captured in the block below):
|
|
10845
|
+
// codexPreInstallManagedFiles — Map<normalizedRelPath, Buffer|null>; one
|
|
10846
|
+
// entry per path the PRIOR install's gsd-file-manifest.json recorded.
|
|
10847
|
+
// null means the path did not exist pre-install, so rollback re-deletes
|
|
10848
|
+
// whatever this install put there instead of resurrecting it.
|
|
10849
|
+
// codexPreInstallManifestBytes — Buffer (or null) of the prior manifest file
|
|
10850
|
+
// itself, which a reinstall rewrites.
|
|
10851
|
+
// codexPreInstallHooksTree — Map<relPath, Buffer>, a full recursive
|
|
10852
|
+
// snapshot of <targetDir>/hooks/. The Codex manifest deliberately omits
|
|
10853
|
+
// hooks/ (the !isCodex gate on shared-hooks tracking), and hooks/ is
|
|
10854
|
+
// shared space, so the restore is wholesale: user files that predate
|
|
10855
|
+
// the install are in the snapshot and come back; anything the failed
|
|
10856
|
+
// install staged does not.
|
|
10857
|
+
const codexPreInstallManagedFiles = new Map();
|
|
10858
|
+
let codexPreInstallManifestBytes = null;
|
|
10859
|
+
const codexPreInstallHooksTree = new Map();
|
|
10860
|
+
// #4544 (review) — capture-state flags the restore must consult:
|
|
10861
|
+
// codexManagedSnapshotCaptured — the capture gate ran at all. When
|
|
10862
|
+
// false (non-Codex runtimes, minimal mode) NO pre-install state was
|
|
10863
|
+
// recorded, and the only safe restore is no restore: an empty
|
|
10864
|
+
// snapshot must never be read as "hooks/ was absent".
|
|
10865
|
+
// codexPreInstallHooksDirPreExisted — hooks/ existed as a DIRECTORY
|
|
10866
|
+
// pre-install. A pre-existing hooks FILE is left alone on rollback
|
|
10867
|
+
// rather than deleted.
|
|
10868
|
+
// codexPreInstallHooksCaptureIncomplete — some part of the hooks/ tree
|
|
10869
|
+
// could not be read (permissions, special files). The restore
|
|
10870
|
+
// downgrades to per-file so an uncapturable user file is never
|
|
10871
|
+
// destroyed by a wholesale delete whose snapshot lacked it.
|
|
10872
|
+
let codexManagedSnapshotCaptured = false;
|
|
10873
|
+
// null = the gate never ran; true/false = the gate ran and hooks/ (did|did
|
|
10874
|
+
// not) exist as a directory pre-install. Two states are load-bearing: a
|
|
10875
|
+
// clean first install records false, so its rollback removes the staged
|
|
10876
|
+
// hooks/ tree entirely; a non-Codex runtime records null, so rollback does
|
|
10877
|
+
// nothing.
|
|
10878
|
+
let codexPreInstallHooksDirPreExisted = null;
|
|
10879
|
+
let codexPreInstallHooksCaptureIncomplete = false;
|
|
10880
|
+
// #4249 CR: not gated on install mode. restoreCodexSnapshot is reachable for
|
|
10881
|
+
// a core/--minimal install too (#2695), and its pass-2 sweeps remove every
|
|
10882
|
+
// gsd-* skill dir / agent file the snapshot does not claim — so an empty
|
|
10883
|
+
// minimal-mode snapshot deleted the whole surface with nothing to restore.
|
|
10884
|
+
if (hostBehaviorsFor(runtime).tomlConfigInstall) {
|
|
10885
|
+
codexManagedSnapshotCaptured = true;
|
|
10839
10886
|
const _preSkillsDir = _resolveSkillsRootDir(runtime, targetDir, _installScopeId);
|
|
10840
10887
|
if (fs.existsSync(_preSkillsDir)) {
|
|
10841
10888
|
for (const entry of fs.readdirSync(_preSkillsDir, { withFileTypes: true })) {
|
|
@@ -10877,19 +10924,213 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10877
10924
|
if (fs.existsSync(_preVersionPath)) {
|
|
10878
10925
|
try { codexPreInstallVersionBytes = fs.readFileSync(_preVersionPath); } catch (_) { /* best-effort */ }
|
|
10879
10926
|
}
|
|
10880
|
-
|
|
10927
|
+
// #4544 — capture the manifest-driven surfaces, same best-effort
|
|
10928
|
+
// conventions as the skills/ snapshot above. readInstallManifest is the
|
|
10929
|
+
// same hardened reader installer-migrations uses (array/ garbage shapes
|
|
10930
|
+
// degrade to an empty file set — rollback then simply covers less, never
|
|
10931
|
+
// crashes), and resolveInstallRelativePath keeps a hostile manifest key
|
|
10932
|
+
// from turning into a write outside the install root.
|
|
10933
|
+
const _priorManifest = readInstallManifest(targetDir);
|
|
10934
|
+
for (const rel of Object.keys(_priorManifest.files)) {
|
|
10935
|
+
const resolved = resolveInstallRelativePath(targetDir, rel);
|
|
10936
|
+
if (!resolved) continue;
|
|
10937
|
+
try {
|
|
10938
|
+
codexPreInstallManagedFiles.set(resolved.relPath, fs.readFileSync(resolved.fullPath));
|
|
10939
|
+
} catch (_) {
|
|
10940
|
+
// Listed but absent/unreadable pre-install: snapshot absence, so
|
|
10941
|
+
// rollback re-deletes instead of resurrecting.
|
|
10942
|
+
codexPreInstallManagedFiles.set(resolved.relPath, null);
|
|
10943
|
+
}
|
|
10944
|
+
}
|
|
10945
|
+
const _preManifestPath = path.join(targetDir, MANIFEST_NAME);
|
|
10946
|
+
if (fs.existsSync(_preManifestPath)) {
|
|
10947
|
+
try { codexPreInstallManifestBytes = fs.readFileSync(_preManifestPath); } catch (_) { /* best-effort */ }
|
|
10948
|
+
}
|
|
10949
|
+
// #4544 (review) — a clean FIRST install has no prior manifest, so nothing
|
|
10950
|
+
// above records the payload this install is about to write, and a failed
|
|
10951
|
+
// clean install would roll back to a half-written tree. Enumerate the SAME
|
|
10952
|
+
// source directories the installer copies (a directory walk tracks the
|
|
10953
|
+
// source tree automatically — no second file list to keep in parity) and
|
|
10954
|
+
// record every path as absent-pre-install. On a reinstall most of these
|
|
10955
|
+
// already carry entries from the prior manifest; any that do not (files
|
|
10956
|
+
// new in this version) snapshot their pre-install bytes or absence exactly
|
|
10957
|
+
// like the rest, which also closes the new-version-file residual.
|
|
10958
|
+
const _recordWritePlanTree = (srcDir, relPrefix) => {
|
|
10959
|
+
let children;
|
|
10960
|
+
try { children = fs.readdirSync(srcDir, { withFileTypes: true }); } catch (_) { return; }
|
|
10961
|
+
for (const child of children) {
|
|
10962
|
+
const rel = relPrefix ? `${relPrefix}/${child.name}` : child.name;
|
|
10963
|
+
if (child.isDirectory()) {
|
|
10964
|
+
_recordWritePlanTree(path.join(srcDir, child.name), rel);
|
|
10965
|
+
} else if (child.isFile()) {
|
|
10966
|
+
if (codexPreInstallManagedFiles.has(rel)) continue;
|
|
10967
|
+
// USER_OWNED_ARTIFACTS are manifest-relative to gsd-core/ (#2771):
|
|
10968
|
+
// they are durably staged across reinstalls and must never enter a
|
|
10969
|
+
// rollback delete-set.
|
|
10970
|
+
const manifestRel = rel.startsWith('gsd-core/') ? rel.slice('gsd-core/'.length) : rel;
|
|
10971
|
+
if (USER_OWNED_ARTIFACTS.includes(manifestRel)) continue;
|
|
10972
|
+
const resolved = resolveInstallRelativePath(targetDir, rel);
|
|
10973
|
+
if (!resolved) continue;
|
|
10974
|
+
try {
|
|
10975
|
+
codexPreInstallManagedFiles.set(rel, fs.existsSync(resolved.fullPath) ? fs.readFileSync(resolved.fullPath) : null);
|
|
10976
|
+
} catch (_) {
|
|
10977
|
+
codexPreInstallManagedFiles.set(rel, null);
|
|
10978
|
+
}
|
|
10979
|
+
}
|
|
10980
|
+
}
|
|
10981
|
+
};
|
|
10982
|
+
_recordWritePlanTree(path.join(src, 'gsd-core'), 'gsd-core');
|
|
10983
|
+
_recordWritePlanTree(path.join(src, 'scripts', 'changeset'), 'scripts/changeset');
|
|
10984
|
+
_recordWritePlanTree(path.join(src, 'scripts', 'lib'), 'scripts/lib');
|
|
10985
|
+
// gsd-core/CHANGELOG.md is sourced from the repo root (not src/gsd-core)
|
|
10986
|
+
// and gsd-core/.gsd-runtime is generated at install time — neither appears
|
|
10987
|
+
// in the directory walks, so record them explicitly.
|
|
10988
|
+
for (const standalone of ['gsd-core/CHANGELOG.md', 'gsd-core/.gsd-runtime', 'scripts/fix-slash-commands.cjs', 'scripts/gen-capability-registry.cjs', 'scripts/gen-loop-host-contract.cjs']) {
|
|
10989
|
+
if (codexPreInstallManagedFiles.has(standalone)) continue;
|
|
10990
|
+
const resolved = resolveInstallRelativePath(targetDir, standalone);
|
|
10991
|
+
if (!resolved) continue;
|
|
10992
|
+
try {
|
|
10993
|
+
codexPreInstallManagedFiles.set(standalone, fs.existsSync(resolved.fullPath) ? fs.readFileSync(resolved.fullPath) : null);
|
|
10994
|
+
} catch (_) {
|
|
10995
|
+
codexPreInstallManagedFiles.set(standalone, null);
|
|
10996
|
+
}
|
|
10997
|
+
}
|
|
10998
|
+
// hooks/ — full recursive snapshot, but never blind: lstat every entry so
|
|
10999
|
+
// a symlink under hooks/ is neither followed (a link to a FIFO would hang
|
|
11000
|
+
// the installer, /dev/zero would exhaust memory, and a link to private
|
|
11001
|
+
// data would copy that data into the snapshot — hooks/ is user-writable
|
|
11002
|
+
// shared space and, for local installs, repo-controllable) nor restored
|
|
11003
|
+
// as a link. Anything unreadable or special marks the capture INCOMPLETE
|
|
11004
|
+
// so the restore downgrades to per-file instead of wholesale-deleting a
|
|
11005
|
+
// tree it never fully saw. A pre-existing hooks FILE (not directory) is
|
|
11006
|
+
// recorded as such and left alone on rollback.
|
|
11007
|
+
const _preHooksPath = path.join(targetDir, 'hooks');
|
|
11008
|
+
let _preHooksStat = null;
|
|
11009
|
+
try { _preHooksStat = fs.lstatSync(_preHooksPath); } catch (_) { /* absent */ }
|
|
11010
|
+
codexPreInstallHooksDirPreExisted = Boolean(_preHooksStat && _preHooksStat.isDirectory());
|
|
11011
|
+
if (codexPreInstallHooksDirPreExisted) {
|
|
11012
|
+
const _snapshotHooksDir = (dir, relBase) => {
|
|
11013
|
+
let children;
|
|
11014
|
+
try { children = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) {
|
|
11015
|
+
codexPreInstallHooksCaptureIncomplete = true;
|
|
11016
|
+
return;
|
|
11017
|
+
}
|
|
11018
|
+
for (const child of children) {
|
|
11019
|
+
const relPath = relBase ? `${relBase}/${child.name}` : child.name;
|
|
11020
|
+
const fullPath = path.join(dir, child.name);
|
|
11021
|
+
let st = null;
|
|
11022
|
+
try { st = fs.lstatSync(fullPath); } catch (_) {
|
|
11023
|
+
codexPreInstallHooksCaptureIncomplete = true;
|
|
11024
|
+
continue;
|
|
11025
|
+
}
|
|
11026
|
+
if (st.isDirectory()) {
|
|
11027
|
+
_snapshotHooksDir(fullPath, relPath);
|
|
11028
|
+
} else if (st.isFile()) {
|
|
11029
|
+
try { codexPreInstallHooksTree.set(relPath, fs.readFileSync(fullPath)); } catch (_) {
|
|
11030
|
+
codexPreInstallHooksCaptureIncomplete = true;
|
|
11031
|
+
}
|
|
11032
|
+
} else {
|
|
11033
|
+
codexPreInstallHooksCaptureIncomplete = true;
|
|
11034
|
+
}
|
|
11035
|
+
}
|
|
11036
|
+
};
|
|
11037
|
+
_snapshotHooksDir(_preHooksPath, '');
|
|
11038
|
+
}
|
|
11039
|
+
}
|
|
11040
|
+
|
|
11041
|
+
// #4544 — shared restore for the manifest-driven surfaces. Called by BOTH
|
|
11042
|
+
// rollback paths: _codexPreConfigRollback (CHANGELOG.md, scripts/, the
|
|
11043
|
+
// initial manifest write AND — via installer migrations' stale-hook removal
|
|
11044
|
+
// — hooks/ itself are all mutated BEFORE config.toml is touched, so the
|
|
11045
|
+
// early path must cover them) and the full restoreCodexSnapshot() below.
|
|
11046
|
+
// Best-effort throughout, matching the #3245 convention: restore failures
|
|
11047
|
+
// never mask the original install error.
|
|
11048
|
+
const restoreCodexManagedSnapshot = () => {
|
|
11049
|
+
// #4544 (review) — if the capture never ran (non-Codex runtimes, minimal
|
|
11050
|
+
// mode), no pre-install state was recorded. The only safe action is NONE:
|
|
11051
|
+
// an empty snapshot must never be read as "hooks/ was absent", or a
|
|
11052
|
+
// minimal-mode rollback would delete the user's entire hooks/ tree.
|
|
11053
|
+
if (!codexManagedSnapshotCaptured) return;
|
|
11054
|
+
// hooks/ — the pre-install tree is restored wholesale: a user file that
|
|
11055
|
+
// predated the install is IN the snapshot and comes back; anything the
|
|
11056
|
+
// failed install staged is not, and goes away with the tree. When the
|
|
11057
|
+
// capture was INCOMPLETE, wholesale deletion would permanently destroy a
|
|
11058
|
+
// file whose bytes were never captured, so the restore downgrades to
|
|
11059
|
+
// per-file: put back what was captured and remove only the names GSD
|
|
11060
|
+
// itself stages (the hoisted CODEX_HOOKS_TO_COPY set plus the CommonJS
|
|
11061
|
+
// marker). hooks/lib/ is left untouched in that mode — its contents are
|
|
11062
|
+
// transitive and cannot be enumerated safely without the capture.
|
|
11063
|
+
if (codexPreInstallHooksDirPreExisted !== null) {
|
|
11064
|
+
const _hooksRestoreDir = path.join(targetDir, 'hooks');
|
|
11065
|
+
if (!codexPreInstallHooksDirPreExisted) {
|
|
11066
|
+
// Clean first install: nothing pre-existed under hooks/, so nothing
|
|
11067
|
+
// the failed install staged may survive either.
|
|
11068
|
+
try { fs.rmSync(_hooksRestoreDir, { recursive: true, force: true }); } catch (_) { /* best-effort */ }
|
|
11069
|
+
} else if (!codexPreInstallHooksCaptureIncomplete) {
|
|
11070
|
+
try { fs.rmSync(_hooksRestoreDir, { recursive: true, force: true }); } catch (_) { /* best-effort */ }
|
|
11071
|
+
for (const [relPath, buf] of codexPreInstallHooksTree) {
|
|
11072
|
+
const destFile = path.join(_hooksRestoreDir, relPath);
|
|
11073
|
+
try {
|
|
11074
|
+
fs.mkdirSync(path.dirname(destFile), { recursive: true });
|
|
11075
|
+
fs.writeFileSync(destFile, buf);
|
|
11076
|
+
} catch (_) { /* best-effort */ }
|
|
11077
|
+
}
|
|
11078
|
+
} else {
|
|
11079
|
+
// GSD-owned names are removed FIRST: several of them are also
|
|
11080
|
+
// legitimate pre-install files the snapshot just restored, and a
|
|
11081
|
+
// removal pass after the restore would delete the restored bytes.
|
|
11082
|
+
for (const hookName of CODEX_HOOKS_TO_COPY) {
|
|
11083
|
+
try { fs.rmSync(path.join(_hooksRestoreDir, hookName), { force: true }); } catch (_) { /* best-effort */ }
|
|
11084
|
+
}
|
|
11085
|
+
try { fs.rmSync(path.join(_hooksRestoreDir, 'package.json'), { force: true }); } catch (_) { /* best-effort */ }
|
|
11086
|
+
for (const [relPath, buf] of codexPreInstallHooksTree) {
|
|
11087
|
+
const destFile = path.join(_hooksRestoreDir, relPath);
|
|
11088
|
+
try {
|
|
11089
|
+
fs.mkdirSync(path.dirname(destFile), { recursive: true });
|
|
11090
|
+
fs.writeFileSync(destFile, buf);
|
|
11091
|
+
} catch (_) { /* best-effort */ }
|
|
11092
|
+
}
|
|
11093
|
+
}
|
|
11094
|
+
}
|
|
11095
|
+
// Every GSD-owned path the prior manifest recorded (plus the clean-install
|
|
11096
|
+
// write plan): restore bytes, or re-delete a path that was absent
|
|
11097
|
+
// pre-install.
|
|
11098
|
+
for (const [relPath, buf] of codexPreInstallManagedFiles) {
|
|
11099
|
+
const resolved = resolveInstallRelativePath(targetDir, relPath);
|
|
11100
|
+
if (!resolved) continue;
|
|
11101
|
+
try {
|
|
11102
|
+
if (buf !== null) {
|
|
11103
|
+
fs.mkdirSync(path.dirname(resolved.fullPath), { recursive: true });
|
|
11104
|
+
fs.writeFileSync(resolved.fullPath, buf);
|
|
11105
|
+
} else if (fs.existsSync(resolved.fullPath)) {
|
|
11106
|
+
fs.rmSync(resolved.fullPath, { force: true });
|
|
11107
|
+
}
|
|
11108
|
+
} catch (_) { /* best-effort */ }
|
|
11109
|
+
}
|
|
11110
|
+
// The prior manifest file itself: reinstall rewrites it; rollback returns
|
|
11111
|
+
// the previous install's manifest (or removes it on a clean first install).
|
|
11112
|
+
const _manifestRestorePath = path.join(targetDir, MANIFEST_NAME);
|
|
11113
|
+
if (codexPreInstallManifestBytes !== null) {
|
|
11114
|
+
try { fs.writeFileSync(_manifestRestorePath, codexPreInstallManifestBytes); } catch (_) { /* best-effort */ }
|
|
11115
|
+
} else if (fs.existsSync(_manifestRestorePath)) {
|
|
11116
|
+
try { fs.unlinkSync(_manifestRestorePath); } catch (_) { /* best-effort */ }
|
|
11117
|
+
}
|
|
11118
|
+
};
|
|
10881
11119
|
|
|
10882
11120
|
// #3245 CR finding 2 — Rollback coverage extends to ALL post-snapshot operations,
|
|
10883
11121
|
// not just the Codex config/hook error paths. Any throw between snapshot capture and
|
|
10884
11122
|
// the Codex config block (skills copy, agents copy, VERSION write, manifest write, etc.)
|
|
10885
11123
|
// must also trigger rollback so the caller is never left in a partially-installed state.
|
|
10886
11124
|
//
|
|
10887
|
-
// _codexPreConfigRollback covers the
|
|
10888
|
-
// config.toml is touched: skills/, agents/, gsd-core/VERSION,
|
|
11125
|
+
// _codexPreConfigRollback covers the surfaces that can be mutated before
|
|
11126
|
+
// config.toml is touched: skills/, agents/, gsd-core/VERSION, the manifest-
|
|
11127
|
+
// driven surfaces (#4544 — CHANGELOG.md, scripts/, .gsd-runtime and the
|
|
11128
|
+
// manifest itself are all rewritten in this window), and orphaned
|
|
10889
11129
|
// atomic-write temp files. It is safe to call before any writes have happened.
|
|
10890
11130
|
// The full restoreCodexSnapshot() (defined inside the config block) additionally
|
|
10891
|
-
// handles config.toml, which
|
|
10892
|
-
|
|
11131
|
+
// handles config.toml and the staged hooks/ tree, which are not yet touched
|
|
11132
|
+
// at this point in the pipeline.
|
|
11133
|
+
const _codexPreConfigRollback = !hostBehaviorsFor(runtime).tomlConfigInstall || isMinimalMode(_effectiveInstallMode) ? null : () => {
|
|
10893
11134
|
rollbackInstallerMigrations();
|
|
10894
11135
|
// skills/gsd-* — pass 1: restore snapshot entries (may be absent if deleted mid-install).
|
|
10895
11136
|
const _earlySkillsDir = _resolveSkillsRootDir(runtime, targetDir, _installScopeId);
|
|
@@ -10949,6 +11190,11 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10949
11190
|
} else if (fs.existsSync(_earlyVersionPath)) {
|
|
10950
11191
|
try { fs.unlinkSync(_earlyVersionPath); } catch (_) { /* best-effort */ }
|
|
10951
11192
|
}
|
|
11193
|
+
// #4544 — manifest-driven surfaces (CHANGELOG.md, scripts/, the initial
|
|
11194
|
+
// manifest write, and — via installer migrations' stale-hook removal —
|
|
11195
|
+
// hooks/ itself are all mutated in this window). The shared restore is
|
|
11196
|
+
// also idempotent against an untouched tree.
|
|
11197
|
+
restoreCodexManagedSnapshot();
|
|
10952
11198
|
// Orphaned atomic-write temp files.
|
|
10953
11199
|
const _earlyTmpPattern = /\.tmp-\d+-\d+$/;
|
|
10954
11200
|
function _earlyCleanTmpFiles(dir) {
|
|
@@ -11074,7 +11320,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11074
11320
|
// used to describe. Claude-local remains the one special-cased path
|
|
11075
11321
|
// (copyWithPathReplacement + stale-skills cleanup).
|
|
11076
11322
|
const _isSkillsRuntime = (() => {
|
|
11077
|
-
if (
|
|
11323
|
+
if (hostBehaviorsFor(runtime).localInstallStyle === 'legacy-flat' && !isGlobal) return false; // legacy flat local path (descriptor-driven; #2086)
|
|
11078
11324
|
// #2875 Part 2 defect fix: a runtime whose LOCAL commands are embedded in a
|
|
11079
11325
|
// rules file rather than materialized as files (hostBehaviors.localCommandsViaRules
|
|
11080
11326
|
// — cline is the only declarant, capabilities/cline/capability.json) must not
|
|
@@ -11085,7 +11331,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11085
11331
|
// spuriously fail; the `localCommandsViaRules` branch below (unchanged
|
|
11086
11332
|
// messaging) and the unconditional agents-materialization block further down
|
|
11087
11333
|
// (installAgentsKindStandalone) already cover this runtime/scope correctly.
|
|
11088
|
-
if (!isGlobal &&
|
|
11334
|
+
if (!isGlobal && hostBehaviorsFor(runtime).localCommandsViaRules) return false;
|
|
11089
11335
|
const cap = _capabilityRegistry && _capabilityRegistry.runtimes && _capabilityRegistry.runtimes[runtime];
|
|
11090
11336
|
const layout = cap && cap.runtime && cap.runtime.artifactLayout;
|
|
11091
11337
|
if (!layout) return false;
|
|
@@ -11124,13 +11370,13 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11124
11370
|
// marker. Write failure is non-fatal (install proceeds; warn so /gsd-surface breakage is
|
|
11125
11371
|
// diagnosable) — the same contract the late write had.
|
|
11126
11372
|
function _writeGsdSourceMarker(runtime, targetDir, src, isGlobal) {
|
|
11127
|
-
if (
|
|
11373
|
+
if (hostBehaviorsFor(runtime).sourceMarkerFile && isGlobal) {
|
|
11128
11374
|
const gsdSourceCommands = path.join(src, 'commands', 'gsd');
|
|
11129
11375
|
if (fs.existsSync(gsdSourceCommands)) {
|
|
11130
11376
|
try {
|
|
11131
11377
|
// ADR-1239 Phase B write-confinement: the descriptor-sourced marker filename
|
|
11132
11378
|
// must resolve under targetDir (parity with the other descriptor-driven writes).
|
|
11133
|
-
const _markerPath = assertDestWithinConfigHome(targetDir,
|
|
11379
|
+
const _markerPath = assertDestWithinConfigHome(targetDir, hostBehaviorsFor(runtime).sourceMarkerFile);
|
|
11134
11380
|
if (hasExistingSymlinkBetween(path.resolve(targetDir), _markerPath, { allowOptInFollow: isSymlinkedDestOptIn() })) {
|
|
11135
11381
|
throw new Error(`compatibility marker "${_markerPath}" contains an untrusted symlink`);
|
|
11136
11382
|
}
|
|
@@ -11205,7 +11451,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11205
11451
|
// index BOTH SKILL.md and the sidecar, causing each GSD skill to appear twice
|
|
11206
11452
|
// in autocomplete. Cleaning them up fixes the duplication; SKILL.md alone is
|
|
11207
11453
|
// sufficient for Codex discovery. User-owned dirs are never touched.
|
|
11208
|
-
if (
|
|
11454
|
+
if (hostBehaviorsFor(runtime).cleanupSkillSidecars) {
|
|
11209
11455
|
cleanupCodexSkillMetadataSidecars(_skillsRootDir);
|
|
11210
11456
|
}
|
|
11211
11457
|
|
|
@@ -11231,7 +11477,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11231
11477
|
// Descriptor-driven (ADR-1239 / #2100): folded from `isWindsurf` into
|
|
11232
11478
|
// hostBehaviors.legacyDevinSkillsCleanup (windsurf is the only runtime that
|
|
11233
11479
|
// declares it, so this is byte-parity).
|
|
11234
|
-
if (
|
|
11480
|
+
if (hostBehaviorsFor(runtime).legacyDevinSkillsCleanup && !isGlobal) {
|
|
11235
11481
|
const removedCount = cleanupWindsurfLegacyDevinSkills(process.cwd());
|
|
11236
11482
|
if (removedCount > 0) {
|
|
11237
11483
|
console.log(` ${green}✓${reset} Removed ${removedCount} legacy .devin/skills/gsd-* dir(s) (pre-#1615 Windsurf layout)`);
|
|
@@ -11239,12 +11485,12 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11239
11485
|
}
|
|
11240
11486
|
|
|
11241
11487
|
// Descriptor-driven (#2090): write DESCRIPTION.md for the gsd/ category after layout install
|
|
11242
|
-
if (
|
|
11488
|
+
if (hostBehaviorsFor(runtime).writeCategoryDescription) {
|
|
11243
11489
|
writeHermesCategoryDescription(path.join(targetDir, 'skills', 'gsd'));
|
|
11244
11490
|
}
|
|
11245
11491
|
|
|
11246
11492
|
// Verify installed artifacts and report
|
|
11247
|
-
if (
|
|
11493
|
+
if (hostBehaviorsFor(runtime).reportSkillsCount) {
|
|
11248
11494
|
const hermesSkillsDir = path.join(targetDir, 'skills', 'gsd');
|
|
11249
11495
|
if (fs.existsSync(hermesSkillsDir)) {
|
|
11250
11496
|
// Hermes layout uses prefix: 'gsd-' (#947) — skill dirs have gsd-<stem> names
|
|
@@ -11258,7 +11504,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11258
11504
|
} else {
|
|
11259
11505
|
failures.push('skills/gsd/*');
|
|
11260
11506
|
}
|
|
11261
|
-
} else if (
|
|
11507
|
+
} else if (hostBehaviorsFor(runtime).verificationStyle === 'kimi') {
|
|
11262
11508
|
const skillsDir = path.join(targetDir, 'skills');
|
|
11263
11509
|
const rootAgentPath = path.join(targetDir, 'agents', 'gsd.yaml');
|
|
11264
11510
|
if (fs.existsSync(skillsDir)) {
|
|
@@ -11282,7 +11528,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11282
11528
|
// hostBehaviors.verificationStyle === 'windsurf-workflows' (extends the
|
|
11283
11529
|
// same mechanism the 'kimi' verificationStyle branch above uses; windsurf
|
|
11284
11530
|
// is the only runtime that declares this value, so this is byte-parity).
|
|
11285
|
-
} else if (
|
|
11531
|
+
} else if (hostBehaviorsFor(runtime).verificationStyle === 'windsurf-workflows') {
|
|
11286
11532
|
if (isGlobal) {
|
|
11287
11533
|
console.log(` ${green}✓${reset} Windsurf global install skipped workflow artifacts (workspace-only)`);
|
|
11288
11534
|
} else {
|
|
@@ -11331,7 +11577,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11331
11577
|
// Descriptor-driven commands/ output report (currently CodeBuddy).
|
|
11332
11578
|
// Cursor retired this parallel surface in #2644 because its skills are
|
|
11333
11579
|
// already slash-menu entries as well as model-invocable context.
|
|
11334
|
-
if (
|
|
11580
|
+
if (hostBehaviorsFor(runtime).reportCommandsDir) {
|
|
11335
11581
|
const commandsDir = path.join(targetDir, 'commands');
|
|
11336
11582
|
if (fs.existsSync(commandsDir)) {
|
|
11337
11583
|
const cmdCount = fs.readdirSync(commandsDir)
|
|
@@ -11346,14 +11592,14 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11346
11592
|
}
|
|
11347
11593
|
}
|
|
11348
11594
|
}
|
|
11349
|
-
} else if (
|
|
11595
|
+
} else if (hostBehaviorsFor(runtime).localCommandsViaRules) {
|
|
11350
11596
|
// Cline local install: rules-based only — commands are embedded in .clinerules (generated below).
|
|
11351
11597
|
// No skills/commands directory needed for local installs.
|
|
11352
11598
|
// Global installs are handled above by _isSkillsRuntime (#782).
|
|
11353
11599
|
// Descriptor-driven (ADR-1239 / #2090): folded from `isCline` into
|
|
11354
11600
|
// hostBehaviors.localCommandsViaRules.
|
|
11355
11601
|
console.log(` ${green}✓${reset} Cline: commands will be available via .clinerules`);
|
|
11356
|
-
} else if (
|
|
11602
|
+
} else if (hostBehaviorsFor(runtime).pluginOnlyInstall) {
|
|
11357
11603
|
// pi (ADR-1239 / #2102 Stage 1): plugin-only install — pi's /gsd command is
|
|
11358
11604
|
// registered programmatically by the native extension (pi/gsd.cjs →
|
|
11359
11605
|
// extensions/gsd.js, staged separately below; the dest suffix must be
|
|
@@ -11461,8 +11707,8 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11461
11707
|
// double-stages their plugin file. A runtime like pi, whose artifactLayout is
|
|
11462
11708
|
// intentionally empty for both scopes (`_isSkillsRuntime` is false), still
|
|
11463
11709
|
// needs its declared hostBehaviors.nativePlugin file copied into targetDir.
|
|
11464
|
-
if (!_isSkillsRuntime &&
|
|
11465
|
-
_installNativePluginIfDeclared(runtime, targetDir,
|
|
11710
|
+
if (!_isSkillsRuntime && hostBehaviorsFor(runtime).nativePlugin) {
|
|
11711
|
+
_installNativePluginIfDeclared(runtime, targetDir, hostBehaviorsFor(runtime), src);
|
|
11466
11712
|
}
|
|
11467
11713
|
|
|
11468
11714
|
// #2624: the .gsd-source marker is now written by _writeGsdSourceMarker()
|
|
@@ -11483,7 +11729,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11483
11729
|
// hostBehaviors.installsCommandBodiesForWorkflowDelegation (windsurf is the
|
|
11484
11730
|
// only runtime that declares it, so this is byte-parity — the #1629 fix
|
|
11485
11731
|
// itself is unchanged).
|
|
11486
|
-
if (
|
|
11732
|
+
if (hostBehaviorsFor(runtime).installsCommandBodiesForWorkflowDelegation && !isGlobal) {
|
|
11487
11733
|
const commandsSrc = path.join(src, 'commands', 'gsd');
|
|
11488
11734
|
const commandsDest = path.join(skillDest, 'commands', 'gsd');
|
|
11489
11735
|
if (fs.existsSync(commandsSrc)) {
|
|
@@ -11547,7 +11793,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11547
11793
|
// scopes (programmatic dispatch, no named-dispatch subagent toolkit, no
|
|
11548
11794
|
// host-read markdown surface), so the resolved layout has no `agents` kind
|
|
11549
11795
|
// to stage and the function returns `null` without writing anything.
|
|
11550
|
-
if (
|
|
11796
|
+
if (hostBehaviorsFor(runtime).pluginOnlyInstall) {
|
|
11551
11797
|
console.log(` ${green}✓${reset} pi: no subagent files (programmatic dispatch, no named-dispatch toolkit)`);
|
|
11552
11798
|
} else if (_isSkillsRuntime) {
|
|
11553
11799
|
console.log(` ${dim}↳${reset} Agents installed via descriptor-driven layout (${runtime})`);
|
|
@@ -11582,7 +11828,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11582
11828
|
// advertising the old full agent surface even though the descriptor-driven
|
|
11583
11829
|
// write above already skipped writing the .md files for a minimal-tier
|
|
11584
11830
|
// resolvedProfile. Reuse the same helper that powers `--uninstall`.
|
|
11585
|
-
if (isMinimalMode(_effectiveInstallMode) &&
|
|
11831
|
+
if (isMinimalMode(_effectiveInstallMode) && hostBehaviorsFor(runtime).tomlConfigInstall) {
|
|
11586
11832
|
const codexConfigPath = path.join(targetDir, 'config.toml');
|
|
11587
11833
|
if (fs.existsSync(codexConfigPath)) {
|
|
11588
11834
|
const existing = fs.readFileSync(codexConfigPath, 'utf8');
|
|
@@ -11701,7 +11947,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11701
11947
|
// runtime.hostBehaviors.brandingRewrites. This site only
|
|
11702
11948
|
// rewrites the two brand-name keys (no `.claude/` here — the
|
|
11703
11949
|
// config-dir replace above already handled path fragments).
|
|
11704
|
-
const _b2 =
|
|
11950
|
+
const _b2 = hostBehaviorsFor(runtime).brandingRewrites;
|
|
11705
11951
|
if (_b2) {
|
|
11706
11952
|
content = content.replace(/CLAUDE\.md/g, _b2['CLAUDE.md']);
|
|
11707
11953
|
content = content.replace(/\bClaude Code\b/g, _b2['Claude Code']);
|
|
@@ -11809,7 +12055,14 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11809
12055
|
console.log(` ${green}✓${reset} Wrote ${sharedHooksDirName}/package.json (CommonJS mode)`);
|
|
11810
12056
|
break;
|
|
11811
12057
|
case 'preserved-foreign':
|
|
11812
|
-
|
|
12058
|
+
// #4759: the foreign file usually DOES declare "type": "commonjs" —
|
|
12059
|
+
// any hand-written or formatter-touched package.json does — and Node
|
|
12060
|
+
// then loads the staged .js hooks as CommonJS, so the old
|
|
12061
|
+
// unconditional "may not resolve" claim was usually false. The
|
|
12062
|
+
// sibling plugin path (src/install-engine.cts) words this same
|
|
12063
|
+
// outcome conditionally; match it and keep will-not-load conditional
|
|
12064
|
+
// on "type": "module", the only case where it is true.
|
|
12065
|
+
console.warn(` ${yellow}⚠${reset} Left existing ${sharedHooksDirName}/package.json untouched (not GSD's marker). If it declares "type": "module", the staged hooks will not load.`);
|
|
11813
12066
|
break;
|
|
11814
12067
|
case 'failed':
|
|
11815
12068
|
// Best-effort: a read-only or full config dir must not abort the
|
|
@@ -11853,7 +12106,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11853
12106
|
// skipSharedHooksInstall:true) — the redundant `&& !isCopilot` was removed.
|
|
11854
12107
|
// #2100: Windsurf's exclusion is likewise descriptor-driven (windsurf declares
|
|
11855
12108
|
// skipSharedHooksInstall:true) — the redundant `&& !isWindsurf` was removed.
|
|
11856
|
-
if (!isCodex &&
|
|
12109
|
+
if (!isCodex && hostBehaviorsFor(runtime).skipSharedHooksInstall !== true) {
|
|
11857
12110
|
if (!installSharedHooksBundle(targetDir)) {
|
|
11858
12111
|
failures.push('hooks');
|
|
11859
12112
|
}
|
|
@@ -12020,7 +12273,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12020
12273
|
}
|
|
12021
12274
|
|
|
12022
12275
|
// Verify no leaked .claude paths in non-Claude runtimes (manifest-scoped)
|
|
12023
|
-
if (!
|
|
12276
|
+
if (!hostBehaviorsFor(runtime).ownsClaudePaths) {
|
|
12024
12277
|
const leakedPaths = [];
|
|
12025
12278
|
// Only scan files that were written by this install (manifest-tracked).
|
|
12026
12279
|
// Scanning the entire targetDir can match user-authored content that
|
|
@@ -12041,6 +12294,42 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12041
12294
|
manifestFiles = null;
|
|
12042
12295
|
}
|
|
12043
12296
|
if (manifestFiles !== null) {
|
|
12297
|
+
// #4667: codex-installed artifacts must not keep `@~/.claude/gsd-core/…`
|
|
12298
|
+
// include references — the `@` form resolves into the CLAUDE install
|
|
12299
|
+
// (wrong copy on dual-runtime machines at divergent versions, nothing at
|
|
12300
|
+
// all on codex-only ones; #570 cause 2 residue). Every target ships in
|
|
12301
|
+
// the codex install, so rewriting the `@~/` include form to the codex
|
|
12302
|
+
// root is mechanical and correct. This runs after all .md emitters
|
|
12303
|
+
// (several bypass the per-runtime converters — that is how the leak
|
|
12304
|
+
// survived the per-emitter fixes; the agent .tomls are generated later
|
|
12305
|
+
// and prefix themselves), and before the scan below, which stays as the
|
|
12306
|
+
// verification backstop. The `_GSD_RUNTIME_ROOT`/`$PREFERRED_CONFIG_DIR`
|
|
12307
|
+
// fallback chains and prose `.claude` mentions carry no `@~/` prefix and
|
|
12308
|
+
// are deliberately untouched, as is CHANGELOG.md.
|
|
12309
|
+
if (hostBehaviorsFor(runtime).rewriteClaudeAtIncludes) {
|
|
12310
|
+
for (const relPath of manifestFiles) {
|
|
12311
|
+
const fileName = path.basename(relPath);
|
|
12312
|
+
if (!(fileName.endsWith('.md') || fileName.endsWith('.toml'))) continue;
|
|
12313
|
+
if (fileName === 'CHANGELOG.md') continue;
|
|
12314
|
+
const rewritePath = path.join(targetDir, relPath);
|
|
12315
|
+
let rewriteContent;
|
|
12316
|
+
try {
|
|
12317
|
+
rewriteContent = fs.readFileSync(rewritePath, 'utf8');
|
|
12318
|
+
} catch (rewriteErr) {
|
|
12319
|
+
continue; // inaccessible or missing — the scan below reports or skips it
|
|
12320
|
+
}
|
|
12321
|
+
const rewritten = rewriteContent
|
|
12322
|
+
.split('@~/.claude/gsd-core/').join('@~/.codex/gsd-core/')
|
|
12323
|
+
.split('@$HOME/.claude/gsd-core/').join('@$HOME/.codex/gsd-core/');
|
|
12324
|
+
if (rewritten !== rewriteContent) {
|
|
12325
|
+
try {
|
|
12326
|
+
fs.writeFileSync(rewritePath, rewritten);
|
|
12327
|
+
} catch (writeErr) {
|
|
12328
|
+
continue; // never fail the install over the rewrite; the scan still warns
|
|
12329
|
+
}
|
|
12330
|
+
}
|
|
12331
|
+
}
|
|
12332
|
+
}
|
|
12044
12333
|
for (const relPath of manifestFiles) {
|
|
12045
12334
|
const fileName = path.basename(relPath);
|
|
12046
12335
|
if (!(fileName.endsWith('.md') || fileName.endsWith('.toml'))) continue;
|
|
@@ -12249,6 +12538,11 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12249
12538
|
try { fs.unlinkSync(_rollbackVersionPath); } catch (_) { /* best-effort */ }
|
|
12250
12539
|
}
|
|
12251
12540
|
|
|
12541
|
+
// 4b. #4544 — manifest-driven surfaces: the staged hooks/ tree, every
|
|
12542
|
+
// GSD-owned path the prior manifest recorded (scripts/, gsd-core/
|
|
12543
|
+
// payload), and the prior manifest file itself.
|
|
12544
|
+
restoreCodexManagedSnapshot();
|
|
12545
|
+
|
|
12252
12546
|
// 5. Orphaned atomic-write temp files (<file>.tmp-<pid>-<n>) in targetDir.
|
|
12253
12547
|
// These can accumulate if an atomic write fails mid-rename. Best-effort scan.
|
|
12254
12548
|
//
|
|
@@ -12322,11 +12616,8 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12322
12616
|
// ENOENT -> allow(undefined), every invocation, every event, no exceptions).
|
|
12323
12617
|
// A pre-#2586 install's stale copy + hooks.json registrations are cleaned
|
|
12324
12618
|
// up below (see the CODEX_EXTENDED_HOOK_EVENTS loop), not re-added here.
|
|
12325
|
-
|
|
12326
|
-
|
|
12327
|
-
'gsd-check-update-worker.js',
|
|
12328
|
-
'managed-hooks-registry.cjs',
|
|
12329
|
-
];
|
|
12619
|
+
// CODEX_HOOKS_TO_COPY itself lives at module scope (#4544) — the rollback's
|
|
12620
|
+
// incomplete-capture path must name the same set without a second literal.
|
|
12330
12621
|
const codexHooksSrc = path.join(src, 'hooks', 'dist');
|
|
12331
12622
|
if (fs.existsSync(codexHooksSrc)) {
|
|
12332
12623
|
const codexHooksDest = path.join(targetDir, 'hooks');
|
|
@@ -12520,6 +12811,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12520
12811
|
absoluteRunner: codexNodeRunner,
|
|
12521
12812
|
platform: process.platform,
|
|
12522
12813
|
});
|
|
12814
|
+
configuredEntrypoints.push(...(hookWrite.configuredEntrypoints || []));
|
|
12523
12815
|
if (hookWrite.wrote) {
|
|
12524
12816
|
console.log(` ${green}✓${reset} Configured Codex hooks (SessionStart via hooks.json)`);
|
|
12525
12817
|
} else {
|
|
@@ -12599,7 +12891,21 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12599
12891
|
}
|
|
12600
12892
|
|
|
12601
12893
|
persistActiveProfileMarker();
|
|
12602
|
-
|
|
12894
|
+
// #4249: expose restoreCodexSnapshot (#3245) as a SECOND, separately named
|
|
12895
|
+
// rollback rather than rebinding `rollbackInstallerMigrations` to it. A
|
|
12896
|
+
// configured-entrypoint validation failure discovered later (outside this
|
|
12897
|
+
// function, after Codex's own hooks.json/config.toml write already
|
|
12898
|
+
// succeeded) previously had only the installer-migrations closure to call,
|
|
12899
|
+
// leaving the just-written config.toml/hooks.json broken on disk despite
|
|
12900
|
+
// Codex already owning a full pre-install snapshot/restore for exactly this.
|
|
12901
|
+
//
|
|
12902
|
+
// Every runtime's `rollbackInstallerMigrations` therefore still means what
|
|
12903
|
+
// it says — the installer-migrations-only closure, which is what a
|
|
12904
|
+
// finalize-stage failure that is NOT an entrypoint-validation failure gets
|
|
12905
|
+
// (the Phase 4 contract). `rollbackPreInstallSnapshot` is Codex-only and is
|
|
12906
|
+
// chosen only for entrypoint-validation failures. See the selection in
|
|
12907
|
+
// installAllRuntimes' rollbackFinalizedInstallerMigrations.
|
|
12908
|
+
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir, configuredEntrypoints, rollbackInstallerMigrations, rollbackPreInstallSnapshot: restoreCodexSnapshot };
|
|
12603
12909
|
}
|
|
12604
12910
|
|
|
12605
12911
|
if (plan.installSurface === 'copilot-instructions') {
|
|
@@ -12629,7 +12935,18 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12629
12935
|
writeCopilotHookConfig(targetDir);
|
|
12630
12936
|
console.log(` ${green}✓${reset} Configured Copilot lifecycle hook (sessionStart)`);
|
|
12631
12937
|
persistActiveProfileMarker();
|
|
12632
|
-
|
|
12938
|
+
// #4249: `[]`, not omitted — every Copilot hook is an inline `printf`
|
|
12939
|
+
// one-liner (GSD_COPILOT_*_HOOK_BASH/PWSH in src/runtime-hooks-surface.cts),
|
|
12940
|
+
// so this runtime genuinely launches no GSD-managed script and has no
|
|
12941
|
+
// interpreter to resolve. Stated explicitly like every other branch rather
|
|
12942
|
+
// than leaning on installAllRuntimes' `|| []` defence.
|
|
12943
|
+
// #4249 (antigravity review): `rollbackInstallerMigrations` was missing here
|
|
12944
|
+
// — every other branch returns it. This PR's own aggregate entrypoint gate
|
|
12945
|
+
// is what makes the gap reachable: an unrelated runtime's invalid entrypoint
|
|
12946
|
+
// now triggers rollbackFinalizedInstallerMigrations for every result in the
|
|
12947
|
+
// batch, and a Copilot result with no rollback function silently skips
|
|
12948
|
+
// reverting Copilot's own installer migrations.
|
|
12949
|
+
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir, configuredEntrypoints: [], rollbackInstallerMigrations };
|
|
12633
12950
|
}
|
|
12634
12951
|
|
|
12635
12952
|
if (plan.installSurface === 'cursor-hooks-json') {
|
|
@@ -12638,7 +12955,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12638
12955
|
// stop, subagentStart, subagentStop) via runtime-hooks-surface.cts, which reads
|
|
12639
12956
|
// the event list from the descriptor-driven adapter module.
|
|
12640
12957
|
const cursorHookResult = writeCursorHooksJson(targetDir, src, {
|
|
12641
|
-
managedHookEvents:
|
|
12958
|
+
managedHookEvents: hostBehaviorsFor(runtime).managedHookEvents,
|
|
12642
12959
|
});
|
|
12643
12960
|
if (cursorHookResult.changed) {
|
|
12644
12961
|
console.log(` ${green}✓${reset} Configured Cursor lifecycle hooks (sessionStart, postToolUse, preToolUse, stop, subagentStart, subagentStop)`);
|
|
@@ -12652,7 +12969,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12652
12969
|
// The re-run is retained for parity with the settings.json install path.
|
|
12653
12970
|
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode, scope: _installScopeId });
|
|
12654
12971
|
persistActiveProfileMarker();
|
|
12655
|
-
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir };
|
|
12972
|
+
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir, configuredEntrypoints: cursorHookResult.configuredEntrypoints, rollbackInstallerMigrations };
|
|
12656
12973
|
}
|
|
12657
12974
|
|
|
12658
12975
|
if (plan.installSurface === 'profile-marker-only') {
|
|
@@ -12708,6 +13025,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12708
13025
|
const kimiHookOpts = { portableHooks: hasPortableHooks, runtime };
|
|
12709
13026
|
const kimiHooksTomlPath = path.join(kimiHooksRoot, 'config.toml');
|
|
12710
13027
|
const kimiHooksResult = writeKimiHooksToml(kimiHooksTomlPath, kimiHooksRoot, { hookOpts: kimiHookOpts });
|
|
13028
|
+
configuredEntrypoints.push(...kimiHooksResult.configuredEntrypoints);
|
|
12711
13029
|
if (kimiHooksResult.changed) {
|
|
12712
13030
|
console.log(` ${green}✓${reset} Configured ${kimiHooksResult.entryCount} GSD hook(s) in ${kimiHooksTomlPath}`);
|
|
12713
13031
|
}
|
|
@@ -12730,9 +13048,9 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12730
13048
|
// reports success and the user is left with the very breakage the opt-in
|
|
12731
13049
|
// exists to prevent. Verified reproducible before this guard existed.
|
|
12732
13050
|
const kimiInstalledThisRun = selectedRuntimes.includes('kimi');
|
|
12733
|
-
if (hasReclaimKimiLegacy && runtime
|
|
13051
|
+
if (hasReclaimKimiLegacy && hostBehaviorsFor(runtime).reclaimsKimiLegacyHooksRoot && kimiInstalledThisRun) {
|
|
12734
13052
|
console.log(` ${dim}•${reset} Skipped --reclaim-kimi-legacy: this run also installs --kimi, so ${resolveKimiHooksTomlDir({ runtime: 'kimi' })} is a live Kimi CLI install`);
|
|
12735
|
-
} else if (hasReclaimKimiLegacy && runtime
|
|
13053
|
+
} else if (hasReclaimKimiLegacy && hostBehaviorsFor(runtime).reclaimsKimiLegacyHooksRoot) {
|
|
12736
13054
|
const legacyKimiRoot = resolveKimiHooksTomlDir({ runtime: 'kimi' });
|
|
12737
13055
|
// Both roots honor their own env override (KIMI_SHARE_DIR /
|
|
12738
13056
|
// KIMI_CODE_HOME). A user who points both at ONE directory collapses
|
|
@@ -12764,6 +13082,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12764
13082
|
const windsurfHookResult = writeWindsurfHooksJson(targetDir, src, {
|
|
12765
13083
|
platform: process.platform,
|
|
12766
13084
|
});
|
|
13085
|
+
configuredEntrypoints.push(...windsurfHookResult.configuredEntrypoints);
|
|
12767
13086
|
if (windsurfHookResult.changed) {
|
|
12768
13087
|
console.log(` ${green}✓${reset} Configured Windsurf lifecycle hooks (pre_write_code, pre_run_command)`);
|
|
12769
13088
|
} else {
|
|
@@ -12779,19 +13098,19 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12779
13098
|
}
|
|
12780
13099
|
|
|
12781
13100
|
persistActiveProfileMarker();
|
|
12782
|
-
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir };
|
|
13101
|
+
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir, configuredEntrypoints, rollbackInstallerMigrations };
|
|
12783
13102
|
}
|
|
12784
13103
|
|
|
12785
13104
|
if (plan.installSurface === 'cline-rules') {
|
|
12786
13105
|
// Cline uses the `.clinerules/` directory form (issue #787): GSD rules live
|
|
12787
13106
|
// at .clinerules/gsd.md and a PreToolUse lifecycle hook at
|
|
12788
13107
|
// .clinerules/hooks/PreToolUse. Global installs also get ~/.agents/AGENTS.md.
|
|
12789
|
-
writeClineArtifacts(targetDir, isGlobal);
|
|
13108
|
+
const clineArtifacts = writeClineArtifacts(targetDir, isGlobal);
|
|
12790
13109
|
// Re-run the manifest pass: these artifacts are written *after* the earlier
|
|
12791
13110
|
// writeManifest() call, so a second pass is needed to hash-track them.
|
|
12792
13111
|
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode, scope: _installScopeId });
|
|
12793
13112
|
persistActiveProfileMarker();
|
|
12794
|
-
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir };
|
|
13113
|
+
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir, configuredEntrypoints: clineArtifacts.configuredEntrypoints, rollbackInstallerMigrations };
|
|
12795
13114
|
}
|
|
12796
13115
|
|
|
12797
13116
|
// Configure statusline and hooks in settings.json (or settings.local.json for local Claude installs).
|
|
@@ -12807,15 +13126,15 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12807
13126
|
// #2870: the CURRENT scope's settings filename is sourced from the Install
|
|
12808
13127
|
// Scope Module (_installScope.settingsFile, resolveScope's per-scope field)
|
|
12809
13128
|
// instead of indexing _scopedSettings by hand. _scopedSettings itself is
|
|
12810
|
-
// retained unchanged as the #338-privacy fail-safe path:
|
|
12811
|
-
// already degrades to
|
|
12812
|
-
//
|
|
13129
|
+
// retained unchanged as the #338-privacy fail-safe path: hostBehaviorsFor
|
|
13130
|
+
// already degrades to its #338 floor (see runtime-name-policy.cts)
|
|
13131
|
+
// when the registry fails to load, whereas resolveScope's registry
|
|
12813
13132
|
// lookup throws in that same scenario (_installScope is null when it did).
|
|
12814
13133
|
// Falling back to _scopedSettings[_installScopeId] there — and keeping the
|
|
12815
13134
|
// non-local-claude branch's expression untouched — means this is
|
|
12816
13135
|
// byte-identical to the pre-migration computation in every case, including
|
|
12817
13136
|
// the broken-registry fail-safe floor.
|
|
12818
|
-
const _scopedSettings =
|
|
13137
|
+
const _scopedSettings = hostBehaviorsFor(runtime).settingsFileByScope || null;
|
|
12819
13138
|
const _currentScopeSettingsFile = _installScope
|
|
12820
13139
|
? _installScope.settingsFile
|
|
12821
13140
|
: (_scopedSettings ? (_scopedSettings[_installScopeId] ?? null) : null);
|
|
@@ -12916,8 +13235,11 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12916
13235
|
persistActiveProfileMarker();
|
|
12917
13236
|
// Callers index this result by `runtime` (installAllRuntimes' statusline
|
|
12918
13237
|
// lookup), so every early exit must return the full shape — a bare return
|
|
12919
|
-
// crashes the install rather than skipping one file.
|
|
12920
|
-
|
|
13238
|
+
// crashes the install rather than skipping one file. That includes
|
|
13239
|
+
// configuredEntrypoints/rollbackInstallerMigrations: rollbackFinalizedInstallerMigrations
|
|
13240
|
+
// reads result.rollbackInstallerMigrations unconditionally, and an omitted
|
|
13241
|
+
// field there silently skips this runtime's rollback on a finalize-stage failure.
|
|
13242
|
+
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir, configuredEntrypoints: [], rollbackInstallerMigrations };
|
|
12921
13243
|
}
|
|
12922
13244
|
const settings = validateHookFields(cleanupOrphanedHooks(rawSettings));
|
|
12923
13245
|
// #3002 CR / #3662: rewrite legacy `node .../gsd-*.js` command strings (pre-
|
|
@@ -12938,8 +13260,20 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12938
13260
|
// Descriptor-driven (ADR-1239 / #2096): hookPathStyle comes from the
|
|
12939
13261
|
// runtime's hostBehaviors instead of a hardcoded `runtime === 'antigravity'`
|
|
12940
13262
|
// check inside projectLocalHookPrefix.
|
|
12941
|
-
const localPrefix = projectLocalHookPrefix({ runtime, dirName, hookPathStyle:
|
|
12942
|
-
const
|
|
13263
|
+
const localPrefix = projectLocalHookPrefix({ runtime, dirName, hookPathStyle: hostBehaviorsFor(runtime).hookPathStyle });
|
|
13264
|
+
const settingsEntrypoints = [];
|
|
13265
|
+
const hookOpts = {
|
|
13266
|
+
portableHooks: hasPortableHooks,
|
|
13267
|
+
runtime,
|
|
13268
|
+
configPath: settingsPath,
|
|
13269
|
+
// #4249: track unconditionally. Gating on `plan.hooksSurface ===
|
|
13270
|
+
// 'settings-json'` made tracking depend on an unasserted
|
|
13271
|
+
// installSurface/hooksSurface coupling — a descriptor that broke it would
|
|
13272
|
+
// silently drop this runtime out of validation. Everything recorded here
|
|
13273
|
+
// lands in settings.json by construction, and the registered-command
|
|
13274
|
+
// filter below already discards entries no hook actually references.
|
|
13275
|
+
configuredEntrypoints: settingsEntrypoints,
|
|
13276
|
+
};
|
|
12943
13277
|
// #2979: local-install hook commands also use a runner GUI/minimal-PATH
|
|
12944
13278
|
// runtimes can resolve. Bare `node` fails when the host launches the
|
|
12945
13279
|
// runtime with a stripped PATH (Finder/Antigravity/etc) — #3662 replaces
|
|
@@ -12953,19 +13287,19 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12953
13287
|
// `node` command that recreates the #2979 failure.
|
|
12954
13288
|
const localCmd = (hookFile) => localNodeRunner === null
|
|
12955
13289
|
? null
|
|
12956
|
-
: projectShellCommandText({
|
|
13290
|
+
: hooksSurface.recordConfiguredHookCommand(projectShellCommandText({
|
|
12957
13291
|
runnerToken: localNodeRunner,
|
|
12958
13292
|
argTokens: [`${localPrefix}/hooks/${hookFile}`],
|
|
12959
13293
|
runtime,
|
|
12960
13294
|
platform: process.platform,
|
|
12961
|
-
});
|
|
12962
|
-
const localShellCmd = (hookFile) => buildLocalShellHookCommand({
|
|
13295
|
+
}), targetDir, hookFile, hookOpts);
|
|
13296
|
+
const localShellCmd = (hookFile) => hooksSurface.recordConfiguredHookCommand(buildLocalShellHookCommand({
|
|
12963
13297
|
localPrefix,
|
|
12964
13298
|
hookFile,
|
|
12965
13299
|
bashRunner: localBashRunner,
|
|
12966
13300
|
runtime,
|
|
12967
13301
|
platform: process.platform,
|
|
12968
|
-
});
|
|
13302
|
+
}), targetDir, hookFile, hookOpts);
|
|
12969
13303
|
const statuslineCommand = isGlobal
|
|
12970
13304
|
? buildHookCommand(targetDir, 'gsd-statusline.js', hookOpts)
|
|
12971
13305
|
: localCmd('gsd-statusline.js');
|
|
@@ -13029,12 +13363,37 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
13029
13363
|
// installAllRuntimes can register it at finalize time when the user opts
|
|
13030
13364
|
// in (#2795). Computed here (not in finishInstall) so the same buildHookCommand
|
|
13031
13365
|
// / localCmd resolution logic is shared with the other JS hooks.
|
|
13032
|
-
const updateBannerCommand =
|
|
13366
|
+
const updateBannerCommand = hostBehaviorsFor(runtime).skipUpdateBannerCommand
|
|
13033
13367
|
? null
|
|
13034
13368
|
: (isGlobal
|
|
13035
13369
|
? buildHookCommand(targetDir, 'gsd-update-banner.js', hookOpts)
|
|
13036
13370
|
: localCmd('gsd-update-banner.js'));
|
|
13037
13371
|
|
|
13372
|
+
const registeredHookCommands = Object.values(settings.hooks || {})
|
|
13373
|
+
.flatMap(groups => Array.isArray(groups) ? groups : [])
|
|
13374
|
+
.flatMap(group => Array.isArray(group && group.hooks) ? group.hooks : [])
|
|
13375
|
+
.map(hook => hook && hook.command)
|
|
13376
|
+
.filter(command => typeof command === 'string');
|
|
13377
|
+
// #4249: match by the managed script's `/hooks/<basename>` path segment, not
|
|
13378
|
+
// by exact command-string equality. The blocking-guard hooks above register
|
|
13379
|
+
// only-if-absent, so a hook already present from a prior install keeps its
|
|
13380
|
+
// OLD command untouched — but `track()` always records the FRESHLY computed
|
|
13381
|
+
// command for it, which never equals what's actually persisted. Matching on
|
|
13382
|
+
// the segment (present in the persisted command either way, since every
|
|
13383
|
+
// entry.scriptPath is <configDir>/hooks/<name> by construction) keeps an
|
|
13384
|
+
// already-registered, still-active hook in the validated set instead of
|
|
13385
|
+
// silently dropping it (#4154 Blocker) — anchored on `/hooks/` rather than a
|
|
13386
|
+
// bare basename so an unrelated user command that merely mentions the same
|
|
13387
|
+
// filename can't false-positive into GSD's validated set.
|
|
13388
|
+
configuredEntrypoints.push(
|
|
13389
|
+
...settingsEntrypoints.filter(entry => {
|
|
13390
|
+
const hooksSegment = '/hooks/' + path.basename(entry.scriptPath);
|
|
13391
|
+
return registeredHookCommands.some(command => command.includes(hooksSegment));
|
|
13392
|
+
}),
|
|
13393
|
+
);
|
|
13394
|
+
const statuslineEntrypoints = settingsEntrypoints.filter(entry => entry.command === statuslineCommand);
|
|
13395
|
+
const updateBannerEntrypoints = settingsEntrypoints.filter(entry => entry.command === updateBannerCommand);
|
|
13396
|
+
|
|
13038
13397
|
// #683: Set worktree.baseRef:"head" in settings.local.json for local Claude installs.
|
|
13039
13398
|
// Both fresh and upgrade paths apply only when worktrees are enabled for the project.
|
|
13040
13399
|
// Never applies to global installs, non-Claude runtimes, or when the user already
|
|
@@ -13103,21 +13462,71 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
13103
13462
|
settings,
|
|
13104
13463
|
statuslineCommand,
|
|
13105
13464
|
updateBannerCommand,
|
|
13465
|
+
statuslineEntrypoints,
|
|
13466
|
+
updateBannerEntrypoints,
|
|
13106
13467
|
runtime,
|
|
13107
13468
|
configDir: targetDir,
|
|
13108
13469
|
rollbackInstallerMigrations,
|
|
13470
|
+
configuredEntrypoints,
|
|
13109
13471
|
};
|
|
13110
13472
|
}
|
|
13111
13473
|
|
|
13112
|
-
|
|
13113
|
-
|
|
13114
|
-
|
|
13474
|
+
// #4249 (review, Major): rollback consequence differs by runtime surface —
|
|
13475
|
+
// see docs/how-to/update-gsd.md's rollback-matrix paragraph, which this
|
|
13476
|
+
// mirrors. Codex reverts (pre-install snapshot restore); Cursor/Windsurf/
|
|
13477
|
+
// Kimi/Kimi Code/Cline already wrote their config file inside install(),
|
|
13478
|
+
// ahead of this gate, with no revert path, so it is left on disk broken;
|
|
13479
|
+
// every other (settings.json-based) runtime writes strictly after this gate,
|
|
13480
|
+
// so a failure here means nothing new was persisted for it.
|
|
13481
|
+
const ENTRYPOINT_LEFT_UNREVERTED_RUNTIMES = new Set(['cursor', 'windsurf', 'kimi', 'kimi-code', 'cline']);
|
|
13482
|
+
function describeEntrypointConsequence(invalidRuntime) {
|
|
13483
|
+
if (invalidRuntime === 'codex') return 'reverted: its pre-install snapshot was restored';
|
|
13484
|
+
if (ENTRYPOINT_LEFT_UNREVERTED_RUNTIMES.has(invalidRuntime)) return 'NOT reverted: its config file is already written and was left on disk — fix the reported path and rerun install';
|
|
13485
|
+
return 'not persisted: this runtime writes its config after this check';
|
|
13486
|
+
}
|
|
13487
|
+
|
|
13488
|
+
function assertConfiguredEntrypoints(entries) {
|
|
13489
|
+
// #4249: some writers push the same (configPath, scriptPath) pair more than
|
|
13490
|
+
// once (e.g. Kimi's context-monitor hook registered under several events,
|
|
13491
|
+
// or the portable resolver script shared by every portable JS hook) — keep
|
|
13492
|
+
// one so a broken entry is reported once, not once per duplicate.
|
|
13493
|
+
const seen = new Set();
|
|
13494
|
+
const deduped = (entries || []).filter((entry) => {
|
|
13495
|
+
const key = JSON.stringify([entry.configPath, entry.scriptPath]);
|
|
13496
|
+
if (seen.has(key)) return false;
|
|
13497
|
+
seen.add(key);
|
|
13498
|
+
return true;
|
|
13499
|
+
});
|
|
13500
|
+
const validation = hooksSurface.validateConfiguredEntrypoints(deduped);
|
|
13501
|
+
if (validation.ok) return;
|
|
13502
|
+
|
|
13503
|
+
const error = new Error(
|
|
13504
|
+
// #4249: lead each entry with its runtime, and name the actual consequence
|
|
13505
|
+
// for that runtime (review, Major) — the aggregate gate is all-or-nothing
|
|
13506
|
+
// across every runtime being installed, and a failure here can revert a
|
|
13507
|
+
// runtime whose own entrypoints were fine (see
|
|
13508
|
+
// rollbackFinalizedInstallerMigrations) while leaving another runtime's
|
|
13509
|
+
// already-written config broken on disk with no revert at all, so an
|
|
13510
|
+
// operator reading only this message must be able to tell WHOSE
|
|
13511
|
+
// entrypoint broke and WHAT that means for their config, not just that
|
|
13512
|
+
// something did.
|
|
13513
|
+
`Configured entrypoint validation failed: ${validation.invalid.map(({ runtime: invalidRuntime, role, path: invalidPath, reason }) => `${invalidRuntime} ${role} ${invalidPath} (${reason}) [${describeEntrypointConsequence(invalidRuntime)}]`).join(', ')}`,
|
|
13514
|
+
);
|
|
13515
|
+
error.configuredEntrypointValidation = validation;
|
|
13516
|
+
throw error;
|
|
13517
|
+
}
|
|
13518
|
+
|
|
13519
|
+
// #4249: `bannerOpts.configuredEntrypoints` is the ONLY source assertConfiguredEntrypoints
|
|
13520
|
+
// checks below — a caller that omits it (or calls finishInstall directly instead of
|
|
13521
|
+
// through installAllRuntimes) gets zero entrypoint validation, silently. installAllRuntimes
|
|
13522
|
+
// always passes the full set (per-runtime entries plus statusline/updateBanner); any other
|
|
13523
|
+
// caller must do the same for this gate to mean anything.
|
|
13115
13524
|
function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline, runtime = DEFAULT_RUNTIME, isGlobal = true, configDir = null, bannerOpts = {}) {
|
|
13116
13525
|
// #2093: isKilo dropped — the Kilo permissions-writer call below is gated
|
|
13117
13526
|
// on plan.finishPermissionWriter === 'kilo' (descriptor-driven), not this flag.
|
|
13118
13527
|
// #2094: isTrae dropped — unused in this function.
|
|
13119
13528
|
// #2095: isKimi dropped — the Kimi "Done!" banner below reads
|
|
13120
|
-
//
|
|
13529
|
+
// hostBehaviorsFor(runtime).doneBannerStyle === 'kimi-agent-file' (descriptor-driven), not this flag.
|
|
13121
13530
|
// #2096: isAntigravity dropped — unused in this function.
|
|
13122
13531
|
// #2098: isCodebuddy dropped — unused in this function.
|
|
13123
13532
|
// #2099: isCopilot dropped — unused in this function.
|
|
@@ -13125,7 +13534,20 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
13125
13534
|
const { isOpencode, isCodex, isCursor, isAugment, isQwen, isHermes, isCline } = runtimeFlags(runtime);
|
|
13126
13535
|
const plan = resolveInstallPlan(runtime);
|
|
13127
13536
|
|
|
13128
|
-
|
|
13537
|
+
// #4249 Major: validate BEFORE this function's own settings.json write (and
|
|
13538
|
+
// before writeNonClaudeDefaults) instead of after. Cursor/Windsurf/Kimi/Cline
|
|
13539
|
+
// already persisted their config inside install() by this point, with no
|
|
13540
|
+
// rollback path covering those writes; Codex also persists inside install()
|
|
13541
|
+
// but its rollback binds to a full pre-install snapshot restore, so it IS
|
|
13542
|
+
// covered (see docs/how-to/update-gsd.md). For the settings-json surface
|
|
13543
|
+
// this ordering means a failing validation never reaches this function's
|
|
13544
|
+
// own write at all. On the production path this is a redundant backstop —
|
|
13545
|
+
// installAllRuntimes's own aggregate assertConfiguredEntrypoints call
|
|
13546
|
+
// already validates the superset before finishInstall runs for any
|
|
13547
|
+
// runtime — kept for a caller that invokes finishInstall directly.
|
|
13548
|
+
assertConfiguredEntrypoints(bannerOpts.configuredEntrypoints);
|
|
13549
|
+
|
|
13550
|
+
if (shouldInstallStatusline && plan.writesSharedSettings && !hostBehaviorsFor(runtime).skipSettingsUi) {
|
|
13129
13551
|
if (!isGlobal && !forceStatusline) {
|
|
13130
13552
|
// Local installs skip statusLine by default: repo settings.json takes precedence over
|
|
13131
13553
|
// profile-level settings.json in Claude Code, so writing here would silently clobber
|
|
@@ -13151,7 +13573,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
13151
13573
|
// settings.json hooks block — opencode/kilo/codex/cursor/windsurf/trae/
|
|
13152
13574
|
// cline either lack the surface or use a different config schema.
|
|
13153
13575
|
const { shouldInstallBanner, bannerCommand } = bannerOpts;
|
|
13154
|
-
if (shouldInstallBanner && settings && plan.writesSharedSettings && !
|
|
13576
|
+
if (shouldInstallBanner && settings && plan.writesSharedSettings && !hostBehaviorsFor(runtime).skipSettingsUi) {
|
|
13155
13577
|
if (!bannerCommand) {
|
|
13156
13578
|
console.warn(` ${yellow}⚠${reset} Skipped update banner registration — Node executable path unavailable. See #2979 / #3002.`);
|
|
13157
13579
|
} else {
|
|
@@ -13180,13 +13602,13 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
13180
13602
|
// Merges GSD-owned entries non-destructively (preserves existing user permissions).
|
|
13181
13603
|
// Scoped to Claude only: antigravity/qwen/hermes/codebuddy also write
|
|
13182
13604
|
// settings.json but use different runtimes and do not use these permission strings.
|
|
13183
|
-
if (
|
|
13605
|
+
if (hostBehaviorsFor(runtime).permissionsSchema === 'claude') {
|
|
13184
13606
|
mergeClaudePermissions(settings);
|
|
13185
13607
|
}
|
|
13186
13608
|
|
|
13187
13609
|
// #2097 UPGRADE 3 (transport:mcp): companion MCP server for runtimes that host
|
|
13188
13610
|
// MCP in settings.json (Augment). settings.json is golden-excluded, so no golden change.
|
|
13189
|
-
if (
|
|
13611
|
+
if (hostBehaviorsFor(runtime).mcpCompanion === 'settings-json' && settings && plan.writesSharedSettings) {
|
|
13190
13612
|
mergeGsdMcpServerIntoSettings(settings);
|
|
13191
13613
|
}
|
|
13192
13614
|
|
|
@@ -13226,18 +13648,29 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
13226
13648
|
// generation reads it). This call is idempotent (preserves existing values).
|
|
13227
13649
|
writeNonClaudeDefaults(runtime);
|
|
13228
13650
|
|
|
13229
|
-
// program
|
|
13230
|
-
//
|
|
13231
|
-
//
|
|
13651
|
+
// program is the runtime display label (ADR-1239 Phase B / #1679). The command
|
|
13652
|
+
// is generated from the surface this runtime registered in this install scope
|
|
13653
|
+
// (#5215, ADR-5057 §5 Phase 12) — a runtime that registers no new-project
|
|
13654
|
+
// trigger is told so instead of being sent to a command that does not exist (#4567).
|
|
13232
13655
|
const program = getRuntimeLabel(runtime);
|
|
13233
|
-
const
|
|
13656
|
+
const advertised = resolveAdvertisedNewProject(runtime, isGlobal ? 'global' : 'local');
|
|
13657
|
+
const command = advertised.kind === 'command' ? advertised.command : null;
|
|
13658
|
+
// Host-specific launch/restart steps below stay; only the new-project clause
|
|
13659
|
+
// changes when the runtime registered no such command.
|
|
13660
|
+
const noNewProject = advertised.kind === 'unregistered'
|
|
13661
|
+
? `${program} registers no new-project command in this scope${advertised.nativeCommand ? ` (its native extension registers ${cyan}${advertised.nativeCommand}${reset})` : ''}.`
|
|
13662
|
+
: '';
|
|
13234
13663
|
|
|
13235
13664
|
// Claude Code global installs use the skills/ format (CC 2.1.88+).
|
|
13236
13665
|
// Restart is required for CC to pick up newly-installed skills, and the
|
|
13237
13666
|
// slash-menu surface depends on CC version — so the instruction needs to
|
|
13238
13667
|
// cover both invocation paths to avoid #2957-style "no commands appear".
|
|
13239
|
-
if (
|
|
13240
|
-
console.log(`
|
|
13668
|
+
if (hostBehaviorsFor(runtime).skillsGlobalOnboarding && isGlobal) {
|
|
13669
|
+
console.log(command === null ? `
|
|
13670
|
+
${green}Done!${reset} Restart ${program}. ${noNewProject}
|
|
13671
|
+
|
|
13672
|
+
${cyan}Join the community:${reset} https://discord.gg/mYgfVNfA2r
|
|
13673
|
+
` : `
|
|
13241
13674
|
${green}Done!${reset} Restart ${program}, then in any directory either type ${cyan}${command}${reset} or ask Claude to run the ${cyan}gsd-new-project${reset} skill.
|
|
13242
13675
|
|
|
13243
13676
|
${cyan}Join the community:${reset} https://discord.gg/mYgfVNfA2r
|
|
@@ -13245,9 +13678,13 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
13245
13678
|
return;
|
|
13246
13679
|
}
|
|
13247
13680
|
|
|
13248
|
-
if (
|
|
13681
|
+
if (hostBehaviorsFor(runtime).doneBannerStyle === 'kimi-agent-file') {
|
|
13249
13682
|
const agentPath = configDir ? path.join(configDir, 'agents', 'gsd.yaml') : 'agents/gsd.yaml';
|
|
13250
|
-
console.log(`
|
|
13683
|
+
console.log(command === null ? `
|
|
13684
|
+
${green}Done!${reset} Start ${program} with ${cyan}kimi --agent-file ${agentPath}${reset}. ${noNewProject}
|
|
13685
|
+
|
|
13686
|
+
${cyan}Join the community:${reset} https://discord.gg/mYgfVNfA2r
|
|
13687
|
+
` : `
|
|
13251
13688
|
${green}Done!${reset} Start ${program} with ${cyan}kimi --agent-file ${agentPath}${reset}, then run ${cyan}${command}${reset}.
|
|
13252
13689
|
|
|
13253
13690
|
${cyan}Join the community:${reset} https://discord.gg/mYgfVNfA2r
|
|
@@ -13255,7 +13692,11 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
13255
13692
|
return;
|
|
13256
13693
|
}
|
|
13257
13694
|
|
|
13258
|
-
console.log(`
|
|
13695
|
+
console.log(command === null ? `
|
|
13696
|
+
${green}Done!${reset} GSD is installed for ${program}, which registers no new-project command in this scope${advertised.nativeCommand ? ` (its native extension registers ${cyan}${advertised.nativeCommand}${reset})` : ''}.
|
|
13697
|
+
|
|
13698
|
+
${cyan}Join the community:${reset} https://discord.gg/mYgfVNfA2r
|
|
13699
|
+
` : `
|
|
13259
13700
|
${green}Done!${reset} Open a blank directory in ${program} and run ${cyan}${command}${reset}.
|
|
13260
13701
|
|
|
13261
13702
|
${cyan}Join the community:${reset} https://discord.gg/mYgfVNfA2r
|
|
@@ -14022,10 +14463,32 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
|
|
14022
14463
|
|
|
14023
14464
|
const rollbackFinalizedInstallerMigrations = (error) => {
|
|
14024
14465
|
const rollbackFailures = [];
|
|
14466
|
+
// #4249: this discriminates on the error's KIND, never on which runtime
|
|
14467
|
+
// owns the failing entrypoint. `wide` is true for ANY entrypoint-validation
|
|
14468
|
+
// failure from ANY runtime, by design: the aggregate gate exists so a
|
|
14469
|
+
// multi-runtime install cannot report success while one of its entrypoints
|
|
14470
|
+
// is broken, so an invalid Cline entrypoint reverts Codex's pre-install
|
|
14471
|
+
// snapshot too — even though Codex itself was fine and its own "Done!"
|
|
14472
|
+
// summary already printed. tests/configured-entrypoint-validation.test.cjs
|
|
14473
|
+
// ('an aggregate entrypoint validation failure rolls the Codex install
|
|
14474
|
+
// back') exercises exactly that, and it is the all-or-nothing behaviour
|
|
14475
|
+
// docs/how-to/update-gsd.md documents.
|
|
14476
|
+
//
|
|
14477
|
+
// What this narrows is the OTHER axis: a finalize-stage exception that is
|
|
14478
|
+
// not an entrypoint-validation failure at all — e.g. a sibling runtime's
|
|
14479
|
+
// permission-config write dying with EACCES — gets only the
|
|
14480
|
+
// installer-migrations-only rollback that Phase 4 specifies
|
|
14481
|
+
// (docs/installer-migrations.md#phase-4-installupdate-integration).
|
|
14482
|
+
// Un-installing (and, on update, downgrading) an already-"Done!" Codex over
|
|
14483
|
+
// an unrelated error is not an outcome any doc promises, while the sibling
|
|
14484
|
+
// surfaces that write config inside install() would keep theirs regardless.
|
|
14485
|
+
const wide = !!(error && error.configuredEntrypointValidation);
|
|
14025
14486
|
for (const result of [...results].reverse()) {
|
|
14026
|
-
if (!result
|
|
14487
|
+
if (!result) continue;
|
|
14488
|
+
const rollback = (wide && result.rollbackPreInstallSnapshot) || result.rollbackInstallerMigrations;
|
|
14489
|
+
if (typeof rollback !== 'function') continue;
|
|
14027
14490
|
try {
|
|
14028
|
-
|
|
14491
|
+
rollback();
|
|
14029
14492
|
} catch (rollbackError) {
|
|
14030
14493
|
rollbackFailures.push({
|
|
14031
14494
|
runtime: result.runtime,
|
|
@@ -14053,6 +14516,19 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
|
|
14053
14516
|
|
|
14054
14517
|
const finalize = (shouldInstallStatusline, shouldInstallBanner) => {
|
|
14055
14518
|
try {
|
|
14519
|
+
const selectedConfiguredEntrypoints = (result) => {
|
|
14520
|
+
if (!result || result.skipped) return [];
|
|
14521
|
+
const useStatusline = statuslineRuntimes.includes(result.runtime)
|
|
14522
|
+
&& shouldInstallStatusline
|
|
14523
|
+
&& (isGlobal || forceStatusline);
|
|
14524
|
+
return [
|
|
14525
|
+
...(result.configuredEntrypoints || []),
|
|
14526
|
+
...(useStatusline ? (result.statuslineEntrypoints || []) : []),
|
|
14527
|
+
...(shouldInstallBanner ? (result.updateBannerEntrypoints || []) : []),
|
|
14528
|
+
];
|
|
14529
|
+
};
|
|
14530
|
+
assertConfiguredEntrypoints(results.flatMap(selectedConfiguredEntrypoints));
|
|
14531
|
+
|
|
14056
14532
|
const printSummaries = () => {
|
|
14057
14533
|
for (const result of results) {
|
|
14058
14534
|
if (result && result.skipped) continue;
|
|
@@ -14066,7 +14542,11 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
|
|
14066
14542
|
result.runtime,
|
|
14067
14543
|
isGlobal,
|
|
14068
14544
|
result.configDir,
|
|
14069
|
-
{
|
|
14545
|
+
{
|
|
14546
|
+
shouldInstallBanner: !!shouldInstallBanner,
|
|
14547
|
+
bannerCommand: result.updateBannerCommand,
|
|
14548
|
+
configuredEntrypoints: selectedConfiguredEntrypoints(result),
|
|
14549
|
+
}
|
|
14070
14550
|
);
|
|
14071
14551
|
}
|
|
14072
14552
|
};
|
|
@@ -14166,9 +14646,6 @@ module.exports = {
|
|
|
14166
14646
|
install,
|
|
14167
14647
|
installAllRuntimes,
|
|
14168
14648
|
uninstall,
|
|
14169
|
-
// #2086 — host-behavior resolution + the #338 privacy fail-safe floor (exported for tests)
|
|
14170
|
-
_resolveHostBehaviors,
|
|
14171
|
-
FALLBACK_HOST_BEHAVIORS,
|
|
14172
14649
|
// #3023 — shared hook bundle directory name, descriptor-driven
|
|
14173
14650
|
SHARED_HOOKS_DIR_DEFAULT,
|
|
14174
14651
|
resolveSharedHooksDirName,
|