@opengsd/gsd-core 1.15.0 → 1.17.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 -2
- package/.opencode/plugins/gsd-core.js +336 -6
- package/.opencode/plugins/gsd-statusline/tui.tsx +55 -0
- package/GEMINI.md +1 -1
- package/agents/gsd-code-fixer.compact.md +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- 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 +7 -1
- package/agents/gsd-debug-session-manager.md +7 -1
- package/agents/gsd-debugger.md +2 -2
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +4 -4
- 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 +1 -1
- package/agents/gsd-plan-checker.md +7 -2
- package/agents/gsd-planner.md +3 -3
- 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 +42 -51
- package/agents/gsd-ui-researcher.compact.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +31 -9
- package/bin/gsd-mcp-server.js +3 -1
- package/bin/install.js +487 -290
- 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 +5 -0
- package/commands/gsd/capture.md +8 -4
- 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 +5 -1
- 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 +5 -1
- package/commands/gsd/pr-branch.md +4 -0
- package/commands/gsd/profile-user.md +5 -1
- package/commands/gsd/progress.md +6 -1
- package/commands/gsd/quick-batch.md +20 -8
- package/commands/gsd/quick.md +12 -7
- package/commands/gsd/review.md +5 -1
- 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 +159 -58
- package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
- package/gsd-core/bin/lib/adr-parser.cjs +37 -5
- package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
- package/gsd-core/bin/lib/audit.cjs +78 -44
- 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 +249 -117
- package/gsd-core/bin/lib/capability-validator.cjs +18 -1
- package/gsd-core/bin/lib/capability-writer.cjs +2 -1
- package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
- package/gsd-core/bin/lib/check-command-router.cjs +164 -1712
- 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 +11 -8
- 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 +201 -178
- package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
- package/gsd-core/bin/lib/config.cjs +43 -12
- 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/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 +413 -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 +142 -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 +81 -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 +3 -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/init.cjs +194 -108
- package/gsd-core/bin/lib/install-engine.cjs +30 -35
- package/gsd-core/bin/lib/install-profiles.cjs +7 -4
- package/gsd-core/bin/lib/install-shadow-report.cjs +12 -0
- package/gsd-core/bin/lib/installer-migrations/011-opencode-mcp-no-prompts.cjs +99 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +54 -1
- package/gsd-core/bin/lib/io.cjs +122 -3
- package/gsd-core/bin/lib/jsonc-edit.cjs +418 -0
- package/gsd-core/bin/lib/loop-host-contract.cjs +2 -1
- 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/mcp-server.cjs +5 -1
- package/gsd-core/bin/lib/milestone.cjs +19 -1
- package/gsd-core/bin/lib/model-resolver.cjs +18 -18
- 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/opencode-mcp-entry.cjs +39 -0
- package/gsd-core/bin/lib/opencode-version.cjs +486 -0
- package/gsd-core/bin/lib/pattern.cjs +10 -0
- package/gsd-core/bin/lib/phase-command-router.cjs +11 -4
- package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
- package/gsd-core/bin/lib/phase-id.cjs +1 -1
- package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
- package/gsd-core/bin/lib/phase-status.cjs +360 -0
- package/gsd-core/bin/lib/phase.cjs +301 -80
- package/gsd-core/bin/lib/plan-document.cjs +93 -19
- package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
- package/gsd-core/bin/lib/planning-document.cjs +273 -40
- package/gsd-core/bin/lib/planning-inspect.cjs +42 -8
- package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +76 -53
- package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
- 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/quick-batch-command-router.cjs +35 -9
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +13 -9
- 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/roadmap-command-router.cjs +13 -15
- package/gsd-core/bin/lib/roadmap-parser.cjs +82 -15
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +127 -65
- package/gsd-core/bin/lib/roadmap.cjs +169 -72
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +186 -161
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +2 -1
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +13 -10
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +365 -594
- package/gsd-core/bin/lib/runtime-name-policy.cjs +142 -27
- package/gsd-core/bin/lib/runtime-slash.cjs +60 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +37 -2
- package/gsd-core/bin/lib/smart-entry.cjs +111 -56
- 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 +67 -9
- package/gsd-core/bin/lib/state.cjs +668 -171
- package/gsd-core/bin/lib/surface.cjs +2 -1
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -152
- package/gsd-core/bin/lib/uat-predicate.cjs +313 -35
- package/gsd-core/bin/lib/uat.cjs +424 -7
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +94 -58
- package/gsd-core/bin/lib/update-context.cjs +18 -1
- 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 +1265 -324
- package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
- package/gsd-core/bin/lib/verify.cjs +286 -567
- 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/shared/config-defaults.manifest.json +5 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +4 -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/execute-mvp-tdd.md +5 -10
- package/gsd-core/references/execute-phase-between-wave-reset.md +3 -0
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +7 -1
- package/gsd-core/references/offer-next.md +1 -1
- package/gsd-core/references/planning-config.md +1 -1
- package/gsd-core/references/spidr-splitting.md +1 -1
- package/gsd-core/references/tdd.md +10 -4
- package/gsd-core/references/verifier-phase-gates.md +5 -2
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -3
- 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 +3 -2
- package/gsd-core/workflows/add-todo.md +4 -3
- package/gsd-core/workflows/ai-integration-phase.md +3 -2
- 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.md +28 -18
- package/gsd-core/workflows/check-todos.md +6 -5
- package/gsd-core/workflows/cleanup.md +1 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
- package/gsd-core/workflows/code-review-fix.md +5 -4
- package/gsd-core/workflows/code-review.md +100 -91
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
- package/gsd-core/workflows/complete-milestone.md +12 -7
- package/gsd-core/workflows/debug.md +30 -5
- 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 +1 -1
- package/gsd-core/workflows/docs-update.md +4 -3
- package/gsd-core/workflows/edit-phase.md +4 -3
- package/gsd-core/workflows/eval-review.md +5 -3
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +4 -2
- 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 +2 -2
- 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 +1 -1
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- 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/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.md +81 -171
- package/gsd-core/workflows/execute-plan.md +13 -19
- package/gsd-core/workflows/explore.md +2 -2
- package/gsd-core/workflows/extract-learnings.md +3 -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 +2 -1
- package/gsd-core/workflows/import.md +3 -2
- package/gsd-core/workflows/inbox.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- 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 +5 -3
- package/gsd-core/workflows/map-codebase.md +4 -3
- 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 +10 -10
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +1 -1
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/pause-work.md +2 -2
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
- 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 +1 -1
- package/gsd-core/workflows/plan-phase.md +27 -18
- package/gsd-core/workflows/plan-review-convergence.md +1 -1
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +1 -1
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress.md +19 -49
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -2
- 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 +42 -31
- package/gsd-core/workflows/remove-phase.md +3 -2
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +17 -12
- package/gsd-core/workflows/review.md +3 -2
- package/gsd-core/workflows/scan.md +3 -2
- package/gsd-core/workflows/secure-phase.md +11 -12
- package/gsd-core/workflows/settings-advanced.md +1 -1
- package/gsd-core/workflows/settings-integrations.md +1 -1
- package/gsd-core/workflows/settings.md +1 -1
- package/gsd-core/workflows/ship.md +12 -12
- 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 +1 -1
- package/gsd-core/workflows/spec-phase.md +1 -1
- 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 +1 -1
- package/gsd-core/workflows/thread.md +2 -2
- package/gsd-core/workflows/transition.md +13 -23
- package/gsd-core/workflows/ui-phase.md +5 -4
- package/gsd-core/workflows/ui-review.md +4 -3
- package/gsd-core/workflows/ultraplan-phase.md +3 -2
- package/gsd-core/workflows/undo.md +1 -1
- package/gsd-core/workflows/validate-phase.md +10 -12
- 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 +68 -59
- package/hooks/dist/gsd-agent-isolation-guard.js +8 -0
- package/hooks/dist/gsd-check-update-worker.js +8 -0
- package/hooks/dist/gsd-check-update.js +34 -20
- package/hooks/dist/gsd-context-monitor.js +45 -13
- package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
- package/hooks/dist/gsd-secret-read-guard.js +263 -16
- package/hooks/dist/gsd-statusline.js +336 -29
- package/hooks/dist/gsd-update-banner.js +8 -0
- 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 +8 -0
- package/hooks/gsd-check-update-worker.js +8 -0
- package/hooks/gsd-check-update.js +34 -20
- package/hooks/gsd-context-monitor.js +45 -13
- package/hooks/gsd-cursor-subagent-start.js +8 -0
- package/hooks/gsd-secret-read-guard.js +263 -16
- package/hooks/gsd-statusline.js +336 -29
- package/hooks/gsd-update-banner.js +8 -0
- 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 +21 -4
- package/scripts/changeset/parse.cjs +52 -4
- package/scripts/ci-timeout-report.cjs +770 -4
- package/scripts/command-contract-helpers.cjs +12 -8
- package/scripts/docs-guard-registry.cjs +6 -0
- package/scripts/gen-capability-registry.cjs +7 -1
- package/scripts/gen-features.cjs +13 -8
- package/scripts/gen-hooks-cli-exit.cjs +12 -28
- package/scripts/gen-loop-host-contract.cjs +14 -2
- 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 +24 -2
- package/scripts/lib/ndjson-reporter.cjs +28 -3
- package/scripts/lib/platform-conformance-tier.generated.cjs +44 -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 +10 -0
- 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-enumeration-drift.cjs +12 -8
- package/scripts/lint-phase-id-drift.cjs +29 -0
- package/scripts/lint-planning-document-positive-control.cjs +329 -0
- package/scripts/lint-response-language-coverage.cjs +3 -0
- package/scripts/lint-skill-deps.cjs +7 -3
- package/scripts/lint-test-file-count.allowlist.json +11 -1
- package/scripts/lint-test-file-count.cjs +34 -1
- package/scripts/lint-vendored-deps.cjs +41 -5
- package/scripts/lint-workflow-shellcheck-baseline.json +10 -10
- package/scripts/mutation-matrix.cjs +53 -6
- package/scripts/release-tarball-smoke.cjs +14 -7
- 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 +5 -0
- package/skills/gsd-capture/SKILL.md +8 -4
- 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 +5 -1
- 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 +5 -1
- package/skills/gsd-pr-branch/SKILL.md +4 -0
- package/skills/gsd-profile-user/SKILL.md +5 -1
- package/skills/gsd-progress/SKILL.md +6 -1
- package/skills/gsd-quick/SKILL.md +16 -10
- package/skills/gsd-quick-batch/SKILL.md +20 -8
- package/skills/gsd-review/SKILL.md +5 -1
- 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/gsd-core/workflows/execute-phase/steps/stale-reverification.md +0 -24
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
|
/**
|
|
@@ -190,7 +190,7 @@ function isCodexHooksFeatureKey(key) {
|
|
|
190
190
|
// GSD_CLAUDE_LEGACY_DENY_PERMISSIONS below.)
|
|
191
191
|
//
|
|
192
192
|
// Format: each string uses Claude Code's documented permission rule syntax \u2014
|
|
193
|
-
// "Tool(pattern)" e.g. "
|
|
193
|
+
// "Tool(pattern)" e.g. "Read(.planning/*)", "Edit(STATE.md)"
|
|
194
194
|
// "Tool" (bare tool name, no pattern)
|
|
195
195
|
//
|
|
196
196
|
// Merge policy: additive, non-destructive \u2014 existing user entries are preserved;
|
|
@@ -199,7 +199,6 @@ function isCodexHooksFeatureKey(key) {
|
|
|
199
199
|
// instead of scattered literal 'claude' defaults/rosters (#2086).
|
|
200
200
|
const DEFAULT_RUNTIME = 'claude';
|
|
201
201
|
const GSD_CLAUDE_ALLOW_PERMISSIONS = Object.freeze([
|
|
202
|
-
'Bash(npx gsd-core *)',
|
|
203
202
|
'Read(.planning/*)',
|
|
204
203
|
'Edit(.planning/*)',
|
|
205
204
|
'Read(STATE.md)',
|
|
@@ -232,9 +231,14 @@ const GSD_CLAUDE_LEGACY_DENY_PERMISSIONS = Object.freeze([
|
|
|
232
231
|
// mergeClaudePermissions and uninstall cleanup retire those stale entries on
|
|
233
232
|
// existing installs while the current GSD_CLAUDE_ALLOW_PERMISSIONS above
|
|
234
233
|
// carries the working `Edit(...)` forms.
|
|
234
|
+
// #5054 — `Bash(npx gsd-core *)` is retired the same way. The project ships
|
|
235
|
+
// as `@opengsd/gsd-core`; the unscoped `gsd-core` npm name belongs to someone
|
|
236
|
+
// else, so the rule pre-authorized whatever that name's owner publishes next.
|
|
237
|
+
// GSD never runs the bare form (update.md uses `--package=@opengsd/gsd-core`).
|
|
235
238
|
const GSD_CLAUDE_LEGACY_ALLOW_PERMISSIONS = Object.freeze([
|
|
236
239
|
'Write(.planning/*)',
|
|
237
240
|
'Write(STATE.md)',
|
|
241
|
+
'Bash(npx gsd-core *)',
|
|
238
242
|
]);
|
|
239
243
|
|
|
240
244
|
/**
|
|
@@ -249,6 +253,7 @@ const GSD_CLAUDE_LEGACY_ALLOW_PERMISSIONS = Object.freeze([
|
|
|
249
253
|
* `Write(...)` forms from before the fix) is removed from permissions.allow,
|
|
250
254
|
* so existing installs end up with the working `Edit(...)` forms instead of
|
|
251
255
|
* both the dead legacy entry and its replacement sitting side by side.
|
|
256
|
+
* The same filter retires the unscoped `Bash(npx gsd-core *)` rule (#5054).
|
|
252
257
|
*
|
|
253
258
|
* Migration (#4221): the retired GSD_CLAUDE_LEGACY_DENY_PERMISSIONS entries
|
|
254
259
|
* are removed from permissions.deny (byte-equal only). Nothing is added to
|
|
@@ -408,6 +413,26 @@ const GSD_WINDSURF_HOOK_SCRIPTS = hooksSurface.GSD_WINDSURF_HOOK_SCRIPTS;
|
|
|
408
413
|
// two guards require so their copies cannot drift.
|
|
409
414
|
const GSD_HOOK_LIB_FILES = ['git-cmd.js', 'gsd-graphify-rebuild.sh', 'cursor-workspace.js', 'injection-patterns.js'];
|
|
410
415
|
|
|
416
|
+
/**
|
|
417
|
+
* Every file GSD stages under `hooks/lib/` (#5335). The install copies `hooks/dist/lib/` wholesale (and the
|
|
418
|
+
* allow-listed names from `hooks/lib/`), so a fixed list drifts the moment a helper is added: the new file is
|
|
419
|
+
* staged but never removed, which also keeps `hooks/lib/` non-empty and blocks its rmdir. The set is the
|
|
420
|
+
* allow-list plus the top-level files of the directories the installer copies from, so uninstall and the
|
|
421
|
+
* manifest cannot disagree with install. Only these names are ever removed; a user's own helper beside them
|
|
422
|
+
* is left alone.
|
|
423
|
+
*/
|
|
424
|
+
function gsdHookLibFiles() {
|
|
425
|
+
const names = new Set(GSD_HOOK_LIB_FILES);
|
|
426
|
+
for (const parts of [['hooks', 'dist', 'lib'], ['hooks', 'lib']]) {
|
|
427
|
+
try {
|
|
428
|
+
for (const entry of fs.readdirSync(path.join(__dirname, '..', ...parts), { withFileTypes: true })) {
|
|
429
|
+
if (entry.isFile()) names.add(entry.name);
|
|
430
|
+
}
|
|
431
|
+
} catch { /* the directory is absent from this package layout */ }
|
|
432
|
+
}
|
|
433
|
+
return [...names];
|
|
434
|
+
}
|
|
435
|
+
|
|
411
436
|
/**
|
|
412
437
|
* Directory name GSD stages its shared hook bundle under, inside a runtime's
|
|
413
438
|
* install root. Defaults to 'hooks' — the name every runtime used before #3023.
|
|
@@ -436,7 +461,7 @@ const GSD_CHANGESET_FILES = [
|
|
|
436
461
|
'github-release-notes.cjs', 'lint.cjs', 'new.cjs',
|
|
437
462
|
'README.md', // documentation only — not user-authored
|
|
438
463
|
];
|
|
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'];
|
|
464
|
+
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
465
|
|
|
441
466
|
// #4544 — the Codex hook payload the install stages into <targetDir>/hooks/.
|
|
442
467
|
// Hoisted to module scope (the #3184 precedent above) so the rollback's
|
|
@@ -473,7 +498,7 @@ const CODEX_HOOKS_TO_COPY = [
|
|
|
473
498
|
* @returns {string}
|
|
474
499
|
*/
|
|
475
500
|
function resolveSharedHooksDirName(runtime) {
|
|
476
|
-
const raw =
|
|
501
|
+
const raw = hostBehaviorsFor(runtime).sharedHooksDirName;
|
|
477
502
|
if (typeof raw !== 'string') return SHARED_HOOKS_DIR_DEFAULT;
|
|
478
503
|
const name = raw.trim();
|
|
479
504
|
if (name === '') return SHARED_HOOKS_DIR_DEFAULT;
|
|
@@ -628,68 +653,6 @@ try {
|
|
|
628
653
|
_installedCapabilityRegistry = _capabilityRegistry;
|
|
629
654
|
}
|
|
630
655
|
|
|
631
|
-
// Fail-safe floor for the reference host's #338-privacy-critical behaviors, used
|
|
632
|
-
// ONLY when the first-party capability registry cannot be loaded (a broken bundle).
|
|
633
|
-
// Without it, a registry-load failure would make `_hostBehaviors('claude')` return
|
|
634
|
-
// {} and silently route a claude LOCAL install to the repo-shared, committed
|
|
635
|
-
// `settings.json` instead of the gitignored `settings.local.json` (#338) — leaking
|
|
636
|
-
// engineer-specific absolute paths. Keyed by runtime id (a DATA lookup, not a
|
|
637
|
-
// hardcoded string-equality branch) so behavior degrades CLOSED (safe), never open.
|
|
638
|
-
// The live descriptor (capabilities/claude/capability.json) remains the source of
|
|
639
|
-
// truth; this mirrors only the privacy-load-bearing subset. (ADR-1239 / #2086)
|
|
640
|
-
//
|
|
641
|
-
// #2870: NOT routed through the Install Scope Module (resolveScope,
|
|
642
|
-
// src/install-scope.cts) despite that module owning per-scope settings-file
|
|
643
|
-
// resolution elsewhere in this file. resolveScope's own descriptor lookup
|
|
644
|
-
// goes through the SAME capability registry require this floor exists to
|
|
645
|
-
// survive the failure of (see getRegistry() in install-scope.cts) — so on
|
|
646
|
-
// exactly the "registry failed to load" path this constant is for,
|
|
647
|
-
// resolveScope would throw too. Routing through it here would trade a
|
|
648
|
-
// graceful, documented degrade for a crash in the one case this floor was
|
|
649
|
-
// added to prevent. This hardcoded literal is the correct, honest answer,
|
|
650
|
-
// not an un-migrated leftover.
|
|
651
|
-
const FALLBACK_HOST_BEHAVIORS = Object.freeze({
|
|
652
|
-
claude: Object.freeze({
|
|
653
|
-
settingsFileByScope: Object.freeze({ local: 'settings.local.json', global: 'settings.json' }),
|
|
654
|
-
permissionsSchema: 'claude',
|
|
655
|
-
sourceMarkerFile: '.gsd-source',
|
|
656
|
-
hyphenNameAgentBody: true,
|
|
657
|
-
legacyCommandsGsdInstallMigration: true,
|
|
658
|
-
legacyCommandsGsdUninstall: 'global',
|
|
659
|
-
}),
|
|
660
|
-
// antigravity's global config dir is resolved dynamically (env-overridable,
|
|
661
|
-
// multi-segment) via resolveAntigravityGlobalDir in getConfigDirFromHome. If the
|
|
662
|
-
// registry fails to load, this floor keeps that routing intact instead of
|
|
663
|
-
// silently falling through to the generic getGlobalConfigHomeFragment default
|
|
664
|
-
// (which would return the wrong '.claude' fragment). (ADR-1239 / #2096)
|
|
665
|
-
antigravity: Object.freeze({ globalDirResolver: 'antigravity' }),
|
|
666
|
-
});
|
|
667
|
-
|
|
668
|
-
/**
|
|
669
|
-
* Resolve a runtime's host behaviors from a capability registry, with the
|
|
670
|
-
* #338-privacy fail-safe floor when the registry (or the runtime's descriptor)
|
|
671
|
-
* is unavailable. Registry is passed in so this is unit-testable under a
|
|
672
|
-
* simulated registry-load failure. (ADR-1239 / #2086)
|
|
673
|
-
*/
|
|
674
|
-
function _resolveHostBehaviors(runtime, registry) {
|
|
675
|
-
const cap = registry && registry.runtimes && registry.runtimes[runtime];
|
|
676
|
-
const declared = cap && cap.runtime && cap.runtime.hostBehaviors;
|
|
677
|
-
if (declared) return declared;
|
|
678
|
-
return FALLBACK_HOST_BEHAVIORS[runtime] || {};
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
/**
|
|
682
|
-
* Host-specific install behaviors, declared on the runtime descriptor
|
|
683
|
-
* (capabilities/<runtime>/capability.json -> runtime.hostBehaviors) instead of
|
|
684
|
-
* scattered `runtime === '<id>'` string checks (ADR-1239 / #2086). Returns {}
|
|
685
|
-
* for runtimes that declare none, so every behavior branch degrades to the
|
|
686
|
-
* generic path by default — EXCEPT the reference host's #338-critical keys, which
|
|
687
|
-
* fall back to FALLBACK_HOST_BEHAVIORS if the registry failed to load.
|
|
688
|
-
*/
|
|
689
|
-
function _hostBehaviors(runtime) {
|
|
690
|
-
return _resolveHostBehaviors(runtime, _capabilityRegistry);
|
|
691
|
-
}
|
|
692
|
-
|
|
693
656
|
/**
|
|
694
657
|
* Read a runtime's documentation-sourced `hostIntegration.dispatch` axes
|
|
695
658
|
* (ADR-1239 Phase A — `capabilities/<runtime>/capability.json`
|
|
@@ -697,7 +660,7 @@ function _hostBehaviors(runtime) {
|
|
|
697
660
|
* background, backgroundDispatch, subagentToolkit}`. These are validated,
|
|
698
661
|
* closed-vocabulary FACTS about what the runtime's real dispatch primitive
|
|
699
662
|
* supports (never inferred) — see `docs/reference/host-integration-capability-
|
|
700
|
-
* matrix.md` for citations. Unlike `
|
|
663
|
+
* matrix.md` for citations. Unlike `hostBehaviorsFor` (install *policy*), this is
|
|
701
664
|
* the negotiated *capability* surface; #2284 is its first content-projection
|
|
702
665
|
* consumer (previously read only by `shouldFlattenDispatch`). Returns `{}` if
|
|
703
666
|
* the registry or the runtime's descriptor is unavailable, so callers must
|
|
@@ -824,8 +787,11 @@ const {
|
|
|
824
787
|
discoverInstallerMigrations,
|
|
825
788
|
MANIFEST_SCHEMA_VERSION,
|
|
826
789
|
readInstallManifest,
|
|
790
|
+
recordHostGeneration,
|
|
827
791
|
runInstallerMigrations,
|
|
828
792
|
} = require(path.join(_gsdLibDir, 'installer-migrations.cjs'));
|
|
793
|
+
const { setJsoncValue, removeJsoncValue, hasJsoncFeatures, parseJsonc } = require(path.join(_gsdLibDir, 'jsonc-edit.cjs'));
|
|
794
|
+
const { isGsdMcpEntry, GSD_MCP_NO_PROMPTS_FLAG } = require(path.join(_gsdLibDir, 'opencode-mcp-entry.cjs'));
|
|
829
795
|
const {
|
|
830
796
|
assertInstallerMigrationsUnblocked,
|
|
831
797
|
resolveInstallerMigrationPromptsForNonTty,
|
|
@@ -833,6 +799,7 @@ const {
|
|
|
833
799
|
} = require(path.join(_gsdLibDir, 'installer-migration-report.cjs'));
|
|
834
800
|
const {
|
|
835
801
|
resolveRuntimeArtifactLayout,
|
|
802
|
+
resolveAdvertisedNewProject,
|
|
836
803
|
} = require(path.join(_gsdLibDir, 'runtime-artifact-layout.cjs'));
|
|
837
804
|
const {
|
|
838
805
|
readSurface,
|
|
@@ -1178,13 +1145,13 @@ function getConfigDirFromHome(runtime, isGlobal) {
|
|
|
1178
1145
|
// !isGlobal returns at the top of this function.)
|
|
1179
1146
|
// Descriptor-driven (ADR-1239 / #2096): folded from a hardcoded
|
|
1180
1147
|
// `runtime === 'antigravity'` literal into a read of the runtime's
|
|
1181
|
-
// `hostBehaviors.globalDirResolver` descriptor field (via
|
|
1182
|
-
// also degrades to
|
|
1148
|
+
// `hostBehaviors.globalDirResolver` descriptor field (via hostBehaviorsFor, which
|
|
1149
|
+
// also degrades to its #338 floor on registry-load failure). This is
|
|
1183
1150
|
// antigravity-unique: unlike `configHome.kind === 'dot-home-nested'` (which
|
|
1184
1151
|
// windsurf also declares — see capabilities/windsurf/capability.json — and
|
|
1185
1152
|
// would wrongly route windsurf's global dir through
|
|
1186
1153
|
// resolveAntigravityGlobalDir), `globalDirResolver` is only set by antigravity.
|
|
1187
|
-
if (
|
|
1154
|
+
if (hostBehaviorsFor(runtime).globalDirResolver === 'antigravity') {
|
|
1188
1155
|
const antigravityDir = resolveAntigravityGlobalDir();
|
|
1189
1156
|
const rel = path.relative(os.homedir(), antigravityDir);
|
|
1190
1157
|
const segments = rel.split(path.sep).filter(Boolean);
|
|
@@ -1317,6 +1284,17 @@ const applySettingsJsonHooks = hooksSurface.applySettingsJsonHooks;
|
|
|
1317
1284
|
// writeKimiHooksToml / removeKimiHooksToml: kimi's native config.toml [[hooks]]
|
|
1318
1285
|
// surface (#2095 EoS/kimi Upgrade 1) — separate from settings.json entirely.
|
|
1319
1286
|
const writeKimiHooksToml = hooksSurface.writeKimiHooksToml;
|
|
1287
|
+
|
|
1288
|
+
// #4840: false when the runtime declares `context-warnings` unsupported in its
|
|
1289
|
+
// descriptor (`hostBehaviors.unsupportedFeatures`). gsd-context-monitor.js reads
|
|
1290
|
+
// the metrics bridge file only the statusline hook writes, and the statusline
|
|
1291
|
+
// is wired for Claude Code alone, so on any other host the hook fires on every
|
|
1292
|
+
// tool event, finds no file and exits silently — forever. Such a host gets no
|
|
1293
|
+
// monitor registration (stale ones are stripped), as Codex does since #2586.
|
|
1294
|
+
function runtimeSupportsContextWarnings(runtime) {
|
|
1295
|
+
const unsupported = hostBehaviorsFor(runtime).unsupportedFeatures;
|
|
1296
|
+
return !(Array.isArray(unsupported) && unsupported.includes('context-warnings'));
|
|
1297
|
+
}
|
|
1320
1298
|
const removeKimiHooksToml = hooksSurface.removeKimiHooksToml;
|
|
1321
1299
|
// processAttribution: pure Co-Authored-By content transform, relocated to the
|
|
1322
1300
|
// conversion module (ADR-1508 / #1510 Phase 1). Bound here so install.js
|
|
@@ -1594,12 +1572,12 @@ function getCommitAttribution(runtime) {
|
|
|
1594
1572
|
|
|
1595
1573
|
let result;
|
|
1596
1574
|
|
|
1597
|
-
const _attrResolverKey =
|
|
1575
|
+
const _attrResolverKey = hostBehaviorsFor(runtime).attributionConfigResolver;
|
|
1598
1576
|
if (_attrResolverKey && ATTRIBUTION_CONFIG_RESOLVERS[_attrResolverKey]) {
|
|
1599
1577
|
const resolveConfigPath = ATTRIBUTION_CONFIG_RESOLVERS[_attrResolverKey];
|
|
1600
1578
|
const config = readSettings(resolveConfigPath(getGlobalConfigDir(runtime, null)));
|
|
1601
1579
|
result = (config && config.disable_ai_attribution === true) ? null : undefined;
|
|
1602
|
-
} else if (
|
|
1580
|
+
} else if (hostBehaviorsFor(runtime).attributionSource === 'settings-json-commit') {
|
|
1603
1581
|
// Claude Code
|
|
1604
1582
|
const settings = readSettings(path.join(getGlobalConfigDir(runtime, explicitConfigDir), 'settings.json'));
|
|
1605
1583
|
if (!settings || !settings.attribution || settings.attribution.commit === undefined) {
|
|
@@ -2084,13 +2062,13 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
|
|
|
2084
2062
|
// Hermes' SKILL.md spec lists `version` as a required frontmatter field.
|
|
2085
2063
|
// Track GSD's package version so Hermes' skill_view() reports a stable
|
|
2086
2064
|
// identifier per install.
|
|
2087
|
-
if (
|
|
2065
|
+
if (hostBehaviorsFor(runtime).skillFrontmatterVersion) fm += `version: ${yamlQuote(pkg.version)}\n`;
|
|
2088
2066
|
// #778 (b) — numeric priority for /skills ordering, declared on the runtime
|
|
2089
2067
|
// descriptor (runtime.hostBehaviors.skillPriorityFrontmatter). Scoped to
|
|
2090
2068
|
// runtimes that declare the flag so Claude/Hermes skill frontmatter is
|
|
2091
2069
|
// unchanged (they ignore the field, but we keep their output byte-stable).
|
|
2092
2070
|
// skillName is the `gsd-<stem>` dir name. (ADR-1239 / #2086)
|
|
2093
|
-
if (
|
|
2071
|
+
if (hostBehaviorsFor(runtime).skillPriorityFrontmatter) {
|
|
2094
2072
|
const stem = typeof skillName === 'string' && skillName.startsWith('gsd-')
|
|
2095
2073
|
? skillName.slice(4)
|
|
2096
2074
|
: skillName;
|
|
@@ -2555,20 +2533,11 @@ function yamlIdentifier(value) {
|
|
|
2555
2533
|
return yamlQuote(text);
|
|
2556
2534
|
}
|
|
2557
2535
|
|
|
2536
|
+
// The frontmatter block is the one the one fence owner (`locateFrontmatterFence`) finds, read
|
|
2537
|
+
// through the conversion module's reader — never a local `indexOf('---', 3)` scan, which ended
|
|
2538
|
+
// the block at the first `---` anywhere, even inside a value (found while implementing #5105).
|
|
2558
2539
|
function extractFrontmatterAndBody(content) {
|
|
2559
|
-
|
|
2560
|
-
return { frontmatter: null, body: content };
|
|
2561
|
-
}
|
|
2562
|
-
|
|
2563
|
-
const endIndex = content.indexOf('---', 3);
|
|
2564
|
-
if (endIndex === -1) {
|
|
2565
|
-
return { frontmatter: null, body: content };
|
|
2566
|
-
}
|
|
2567
|
-
|
|
2568
|
-
return {
|
|
2569
|
-
frontmatter: content.substring(3, endIndex).trim(),
|
|
2570
|
-
body: content.substring(endIndex + 3),
|
|
2571
|
-
};
|
|
2540
|
+
return runtimeArtifactConversion.extractFrontmatterAndBody(content);
|
|
2572
2541
|
}
|
|
2573
2542
|
|
|
2574
2543
|
function extractFrontmatterField(frontmatter, fieldName) {
|
|
@@ -2813,7 +2782,7 @@ function convertClaudeCommandToTraeSkill(content, skillName) {
|
|
|
2813
2782
|
// is not formally documented (thin SPA docs) — descriptor-driven, single
|
|
2814
2783
|
// fixed GSD-side value (runtime.hostBehaviors.soloStageMetadata), inferred/
|
|
2815
2784
|
// best-effort.
|
|
2816
|
-
const soloStage =
|
|
2785
|
+
const soloStage = hostBehaviorsFor('trae').soloStageMetadata;
|
|
2817
2786
|
if (soloStage) fm += `stage: ${soloStage}\n`;
|
|
2818
2787
|
fm += '---';
|
|
2819
2788
|
return `${fm}\n${body}`;
|
|
@@ -3806,7 +3775,7 @@ const HERMES_DISPATCH_TOOL_CONFIG = Object.freeze({
|
|
|
3806
3775
|
*/
|
|
3807
3776
|
function convertClaudeToHermesMarkdown(content, ctx) {
|
|
3808
3777
|
const runtime = (ctx && ctx.runtime) || 'hermes';
|
|
3809
|
-
const b =
|
|
3778
|
+
const b = hostBehaviorsFor(runtime).brandingRewrites;
|
|
3810
3779
|
let converted = content;
|
|
3811
3780
|
if (b) {
|
|
3812
3781
|
converted = converted.replace(/CLAUDE\.md/g, b['CLAUDE.md']);
|
|
@@ -7029,6 +6998,82 @@ function writeCopilotHookConfig(targetDir) {
|
|
|
7029
6998
|
* Reads agent .md files from source, extracts metadata, writes .toml configs.
|
|
7030
6999
|
*/
|
|
7031
7000
|
|
|
7001
|
+
// #5335: ~/.gsd/defaults.json is machine-wide, so uninstalling one runtime must not strip a default another
|
|
7002
|
+
// still needs, nor one the user chose. A sidecar records which keys an install SET (not ones it found), the
|
|
7003
|
+
// value it set, and which non-Claude runtimes have installed since; uninstall reverts a key only when its
|
|
7004
|
+
// last such runtime is gone and the value is still the one the installer wrote.
|
|
7005
|
+
const DEFAULTS_PROVENANCE_NAME = 'defaults-provenance.json';
|
|
7006
|
+
|
|
7007
|
+
function readDefaultsProvenance(gsdDir) {
|
|
7008
|
+
try {
|
|
7009
|
+
const p = JSON.parse(fs.readFileSync(path.join(gsdDir, DEFAULTS_PROVENANCE_NAME), 'utf8'));
|
|
7010
|
+
if (p && typeof p === 'object' && p.keys && typeof p.keys === 'object') return p;
|
|
7011
|
+
} catch { /* absent or unreadable */ }
|
|
7012
|
+
return { version: 1, keys: {} };
|
|
7013
|
+
}
|
|
7014
|
+
|
|
7015
|
+
function recordDefaultsProvenance(gsdDir, runtime, defaults, setNow) {
|
|
7016
|
+
const prov = readDefaultsProvenance(gsdDir);
|
|
7017
|
+
let changed = false;
|
|
7018
|
+
for (const key of ['resolve_model_ids', 'runtime']) {
|
|
7019
|
+
let entry = prov.keys[key];
|
|
7020
|
+
if (setNow[key]) {
|
|
7021
|
+
entry = { setValue: defaults[key], runtimes: [] };
|
|
7022
|
+
prov.keys[key] = entry;
|
|
7023
|
+
changed = true;
|
|
7024
|
+
}
|
|
7025
|
+
if (entry && Array.isArray(entry.runtimes) && !entry.runtimes.includes(runtime)) {
|
|
7026
|
+
entry.runtimes.push(runtime);
|
|
7027
|
+
changed = true;
|
|
7028
|
+
}
|
|
7029
|
+
}
|
|
7030
|
+
if (changed) atomicWriteFileSync(path.join(gsdDir, DEFAULTS_PROVENANCE_NAME), JSON.stringify(prov, null, 2) + '\n', 'utf8');
|
|
7031
|
+
}
|
|
7032
|
+
|
|
7033
|
+
/** @returns {boolean} true when it removed or reverted something */
|
|
7034
|
+
function revertNonClaudeDefaults(runtime) {
|
|
7035
|
+
if (hostBehaviorsFor(runtime).nativeModelAliases || process.env.GSD_TEST_MODE) return false;
|
|
7036
|
+
const gsdDir = path.join(os.homedir(), '.gsd');
|
|
7037
|
+
const provPath = path.join(gsdDir, DEFAULTS_PROVENANCE_NAME);
|
|
7038
|
+
if (!fs.existsSync(provPath)) return false;
|
|
7039
|
+
let releaseLock = null;
|
|
7040
|
+
try {
|
|
7041
|
+
releaseLock = acquireInstallMigrationLock(gsdDir);
|
|
7042
|
+
const prov = readDefaultsProvenance(gsdDir);
|
|
7043
|
+
const defaultsPath = path.join(gsdDir, 'defaults.json');
|
|
7044
|
+
let defaults = null;
|
|
7045
|
+
try { defaults = JSON.parse(fs.readFileSync(defaultsPath, 'utf8')); } catch { /* absent or corrupt */ }
|
|
7046
|
+
const reverted = [];
|
|
7047
|
+
for (const [key, entry] of Object.entries(prov.keys)) {
|
|
7048
|
+
if (!entry || !Array.isArray(entry.runtimes)) { delete prov.keys[key]; continue; }
|
|
7049
|
+
entry.runtimes = entry.runtimes.filter((r) => r !== runtime);
|
|
7050
|
+
if (entry.runtimes.length > 0) continue;
|
|
7051
|
+
if (defaults && typeof defaults === 'object' && defaults[key] === entry.setValue) {
|
|
7052
|
+
delete defaults[key];
|
|
7053
|
+
reverted.push(key);
|
|
7054
|
+
}
|
|
7055
|
+
delete prov.keys[key];
|
|
7056
|
+
}
|
|
7057
|
+
if (reverted.length > 0 && defaults) {
|
|
7058
|
+
if (Object.keys(defaults).length === 0) fs.rmSync(defaultsPath, { force: true });
|
|
7059
|
+
else atomicWriteFileSync(defaultsPath, JSON.stringify(defaults, null, 2) + '\n', 'utf8');
|
|
7060
|
+
console.log(` ${green}✓${reset} Reverted ${reverted.join(', ')} in ~/.gsd/defaults.json (set by the ${runtime} install)`);
|
|
7061
|
+
}
|
|
7062
|
+
if (Object.keys(prov.keys).length === 0) fs.rmSync(provPath, { force: true });
|
|
7063
|
+
else atomicWriteFileSync(provPath, JSON.stringify(prov, null, 2) + '\n', 'utf8');
|
|
7064
|
+
return reverted.length > 0;
|
|
7065
|
+
} catch (e) {
|
|
7066
|
+
console.log(` ${yellow}⚠${reset} Could not update ~/.gsd/defaults.json: ${e.message}`);
|
|
7067
|
+
return false;
|
|
7068
|
+
} finally {
|
|
7069
|
+
if (releaseLock) {
|
|
7070
|
+
try { releaseLock(); } catch { /* the stale-lock reaper clears it */ }
|
|
7071
|
+
}
|
|
7072
|
+
// ~/.gsd is GSD's own directory; drop it once the lock is released and nothing else lives in it.
|
|
7073
|
+
try { fs.rmdirSync(gsdDir); } catch { /* other GSD state lives here */ }
|
|
7074
|
+
}
|
|
7075
|
+
}
|
|
7076
|
+
|
|
7032
7077
|
/**
|
|
7033
7078
|
* #2834: Write ~/.gsd/defaults.json for non-Claude runtimes — sets
|
|
7034
7079
|
* resolve_model_ids="omit" (so resolveModelInternal() returns '' instead of
|
|
@@ -7040,7 +7085,7 @@ function writeCopilotHookConfig(targetDir) {
|
|
|
7040
7085
|
* model aliases). Preserves an explicit `true` opt-in and existing values.
|
|
7041
7086
|
*/
|
|
7042
7087
|
function writeNonClaudeDefaults(runtime) {
|
|
7043
|
-
if (
|
|
7088
|
+
if (hostBehaviorsFor(runtime).nativeModelAliases || process.env.GSD_TEST_MODE) return;
|
|
7044
7089
|
const gsdDir = path.join(os.homedir(), '.gsd');
|
|
7045
7090
|
const defaultsPath = path.join(gsdDir, 'defaults.json');
|
|
7046
7091
|
let releaseLock = null;
|
|
@@ -7075,6 +7120,10 @@ function writeNonClaudeDefaults(runtime) {
|
|
|
7075
7120
|
atomicWriteFileSync(defaultsPath, JSON.stringify(defaults, null, 2) + '\n', 'utf8');
|
|
7076
7121
|
for (const message of applied) console.log(` ${green}✓${reset} ${message}`);
|
|
7077
7122
|
}
|
|
7123
|
+
recordDefaultsProvenance(gsdDir, runtime, defaults, {
|
|
7124
|
+
resolve_model_ids: shouldDefaultToOmit,
|
|
7125
|
+
runtime: applied.some((m) => m.startsWith('Set runtime:')),
|
|
7126
|
+
});
|
|
7078
7127
|
} catch (e) {
|
|
7079
7128
|
console.log(` ${yellow}⚠${reset} Could not write ~/.gsd/defaults.json: ${e.message}`);
|
|
7080
7129
|
} finally {
|
|
@@ -7271,26 +7320,22 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOve
|
|
|
7271
7320
|
// Runtime-neutral agent name replacement (#766)
|
|
7272
7321
|
convertedContent = neutralizeAgentReferences(convertedContent, 'AGENTS.md');
|
|
7273
7322
|
|
|
7274
|
-
//
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
}
|
|
7278
|
-
|
|
7279
|
-
// Find the end of frontmatter
|
|
7280
|
-
const endIndex = convertedContent.indexOf('---', 3);
|
|
7281
|
-
if (endIndex === -1) {
|
|
7323
|
+
// The frontmatter block, as the one fence owner finds it (none → nothing to convert).
|
|
7324
|
+
const { frontmatter, body } = extractFrontmatterAndBody(convertedContent);
|
|
7325
|
+
if (frontmatter === null) {
|
|
7282
7326
|
return convertedContent;
|
|
7283
7327
|
}
|
|
7284
7328
|
|
|
7285
|
-
const frontmatter = convertedContent.substring(3, endIndex).trim();
|
|
7286
|
-
const body = convertedContent.substring(endIndex + 3);
|
|
7287
|
-
|
|
7288
7329
|
// Parse frontmatter line by line (simple YAML parsing)
|
|
7289
7330
|
const lines = frontmatter.split('\n');
|
|
7290
7331
|
const newLines = [];
|
|
7291
7332
|
let inAllowedTools = false;
|
|
7292
7333
|
let inSkippedArray = false;
|
|
7334
|
+
let inAgentTools = false;
|
|
7335
|
+
let inDisallowedTools = false;
|
|
7293
7336
|
const allowedTools = [];
|
|
7337
|
+
const agentTools = [];
|
|
7338
|
+
const agentDisallowed = [];
|
|
7294
7339
|
|
|
7295
7340
|
for (const line of lines) {
|
|
7296
7341
|
const trimmed = line.trim();
|
|
@@ -7306,11 +7351,31 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOve
|
|
|
7306
7351
|
continue;
|
|
7307
7352
|
}
|
|
7308
7353
|
|
|
7354
|
+
// Agents: collect the `tools:` allowlist and `disallowedTools:` denylist (block-list continuation
|
|
7355
|
+
// lines) so they can be rendered as `permission:` entries below (mirrors
|
|
7356
|
+
// src/runtime-artifact-conversion.cts's convertClaudeToOpencodeFrontmatter, #5338).
|
|
7357
|
+
if (isAgent && (inAgentTools || inDisallowedTools)) {
|
|
7358
|
+
if (trimmed.startsWith('- ')) {
|
|
7359
|
+
const tool = runtimeArtifactConversion._decodeToolScalar(trimmed.substring(2));
|
|
7360
|
+
if (tool !== null) (inAgentTools ? agentTools : agentDisallowed).push(tool);
|
|
7361
|
+
continue;
|
|
7362
|
+
}
|
|
7363
|
+
if (trimmed && !trimmed.startsWith('-')) {
|
|
7364
|
+
inAgentTools = false;
|
|
7365
|
+
inDisallowedTools = false;
|
|
7366
|
+
}
|
|
7367
|
+
}
|
|
7368
|
+
|
|
7309
7369
|
// Detect inline tools: field (comma-separated string)
|
|
7310
7370
|
if (trimmed.startsWith('tools:')) {
|
|
7311
7371
|
if (isAgent) {
|
|
7312
|
-
|
|
7313
|
-
|
|
7372
|
+
const toolsValue = trimmed.substring(6).trim();
|
|
7373
|
+
// A comment-only value (`tools: # note`) is not inline content: read the block list instead.
|
|
7374
|
+
if (toolsValue && !toolsValue.startsWith('#')) {
|
|
7375
|
+
agentTools.push(...runtimeArtifactConversion._splitToolScalars(toolsValue).map(runtimeArtifactConversion._decodeToolScalar).filter((tool) => tool !== null));
|
|
7376
|
+
} else {
|
|
7377
|
+
inAgentTools = true;
|
|
7378
|
+
}
|
|
7314
7379
|
continue;
|
|
7315
7380
|
}
|
|
7316
7381
|
const toolsValue = trimmed.substring(6).trim();
|
|
@@ -7322,8 +7387,18 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOve
|
|
|
7322
7387
|
continue;
|
|
7323
7388
|
}
|
|
7324
7389
|
|
|
7325
|
-
|
|
7326
|
-
|
|
7390
|
+
if (isAgent && trimmed.startsWith('disallowedTools:')) {
|
|
7391
|
+
const value = trimmed.substring(16).trim();
|
|
7392
|
+
if (value && !value.startsWith('#')) {
|
|
7393
|
+
agentDisallowed.push(...runtimeArtifactConversion._splitToolScalars(value).map(runtimeArtifactConversion._decodeToolScalar).filter((tool) => tool !== null));
|
|
7394
|
+
} else {
|
|
7395
|
+
inDisallowedTools = true;
|
|
7396
|
+
}
|
|
7397
|
+
continue;
|
|
7398
|
+
}
|
|
7399
|
+
|
|
7400
|
+
// For agents: strip skills:, color:, memory:, maxTurns:, permissionMode:
|
|
7401
|
+
if (isAgent && /^(skills|color|memory|maxTurns|permissionMode):/.test(trimmed)) {
|
|
7327
7402
|
inSkippedArray = true;
|
|
7328
7403
|
continue;
|
|
7329
7404
|
}
|
|
@@ -7396,6 +7471,7 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOve
|
|
|
7396
7471
|
if (modelOverride) {
|
|
7397
7472
|
newLines.push(`model: ${modelOverride}`);
|
|
7398
7473
|
}
|
|
7474
|
+
newLines.push(...runtimeArtifactConversion._buildOpencodeAgentPermissionBlock(agentTools, agentDisallowed));
|
|
7399
7475
|
}
|
|
7400
7476
|
|
|
7401
7477
|
// For commands: add tools object if we had allowed-tools or tools
|
|
@@ -7438,20 +7514,12 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false, modelOverrid
|
|
|
7438
7514
|
// Runtime-neutral agent name replacement (#766)
|
|
7439
7515
|
convertedContent = neutralizeAgentReferences(convertedContent, 'AGENTS.md');
|
|
7440
7516
|
|
|
7441
|
-
//
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
}
|
|
7445
|
-
|
|
7446
|
-
// Find the end of frontmatter
|
|
7447
|
-
const endIndex = convertedContent.indexOf('---', 3);
|
|
7448
|
-
if (endIndex === -1) {
|
|
7517
|
+
// The frontmatter block, as the one fence owner finds it (none → nothing to convert).
|
|
7518
|
+
const { frontmatter, body } = extractFrontmatterAndBody(convertedContent);
|
|
7519
|
+
if (frontmatter === null) {
|
|
7449
7520
|
return convertedContent;
|
|
7450
7521
|
}
|
|
7451
7522
|
|
|
7452
|
-
const frontmatter = convertedContent.substring(3, endIndex).trim();
|
|
7453
|
-
const body = convertedContent.substring(endIndex + 3);
|
|
7454
|
-
|
|
7455
7523
|
// Parse frontmatter line by line (simple YAML parsing)
|
|
7456
7524
|
const lines = frontmatter.split('\n');
|
|
7457
7525
|
const newLines = [];
|
|
@@ -7825,7 +7893,7 @@ const RUNTIME_CONTENT_DISPATCH = {
|
|
|
7825
7893
|
},
|
|
7826
7894
|
},
|
|
7827
7895
|
// qwen/hermes: brand VALUES are descriptor-driven (ADR-1239 / #2092) via
|
|
7828
|
-
//
|
|
7896
|
+
// hostBehaviorsFor(ctx.runtime).brandingRewrites — EXACT regexes/ordering
|
|
7829
7897
|
// preserved from the prior hardcoded-literal versions (including the
|
|
7830
7898
|
// qwen-specific `.claude/skills/` -> `.qwen/skills/` pre-rewrite, whose
|
|
7831
7899
|
// target is derived as `${b['.claude/']}skills/`).
|
|
@@ -7833,7 +7901,7 @@ const RUNTIME_CONTENT_DISPATCH = {
|
|
|
7833
7901
|
md: (content, ctx) => {
|
|
7834
7902
|
// Guarded (post-review #2092): degrade closed to a no-op if the
|
|
7835
7903
|
// registry fails to load, instead of throwing on `b['CLAUDE.md']`.
|
|
7836
|
-
const b =
|
|
7904
|
+
const b = hostBehaviorsFor(ctx.runtime).brandingRewrites;
|
|
7837
7905
|
if (b) {
|
|
7838
7906
|
content = content.replace(/CLAUDE\.md/g, b['CLAUDE.md']);
|
|
7839
7907
|
// #2284(b): skips <runtime_compatibility> comparison-table content (protected region).
|
|
@@ -7843,7 +7911,7 @@ const RUNTIME_CONTENT_DISPATCH = {
|
|
|
7843
7911
|
return content;
|
|
7844
7912
|
},
|
|
7845
7913
|
js: (content, ctx) => {
|
|
7846
|
-
const b =
|
|
7914
|
+
const b = hostBehaviorsFor(ctx.runtime).brandingRewrites;
|
|
7847
7915
|
if (b) {
|
|
7848
7916
|
content = content.replace(/\.claude\/skills\//g, `${b['.claude/']}skills/`);
|
|
7849
7917
|
content = content.replace(/\.claude\//g, b['.claude/']);
|
|
@@ -7862,7 +7930,7 @@ const RUNTIME_CONTENT_DISPATCH = {
|
|
|
7862
7930
|
// hostIntegration.dispatch facts.
|
|
7863
7931
|
md: (content, ctx) => convertClaudeToHermesMarkdown(content, ctx),
|
|
7864
7932
|
js: (content, ctx) => {
|
|
7865
|
-
const b =
|
|
7933
|
+
const b = hostBehaviorsFor(ctx.runtime).brandingRewrites;
|
|
7866
7934
|
if (b) {
|
|
7867
7935
|
content = content.replace(/\.claude\/skills\//g, `${b['.claude/']}skills/`);
|
|
7868
7936
|
content = content.replace(/\.claude\//g, b['.claude/']);
|
|
@@ -8025,7 +8093,7 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
|
|
|
8025
8093
|
// emit path, which never had it: every @~/.claude/gsd-core/… include
|
|
8026
8094
|
// in a global install's workflows/references tree silently resolved
|
|
8027
8095
|
// to nothing (54 includes across 22 files on a live install).
|
|
8028
|
-
if (runtime
|
|
8096
|
+
if (hostBehaviorsFor(runtime).restoreAtRefTildeInSpecTree) {
|
|
8029
8097
|
content = runtimeArtifactConversion._restoreClaudeGlobalAtRefTilde(content, pathPrefix);
|
|
8030
8098
|
}
|
|
8031
8099
|
return content;
|
|
@@ -8041,7 +8109,7 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
|
|
|
8041
8109
|
// copyWithPathReplacement is the emit path for gsd-core/workflows/*.md;
|
|
8042
8110
|
// _applyRuntimeRewrites is NOT invoked here, so this is what makes the fix
|
|
8043
8111
|
// live in real installs (it is a no-op for files without those lines).
|
|
8044
|
-
if (!
|
|
8112
|
+
if (!hostBehaviorsFor(runtime).authorsCanonicalWorkflow) {
|
|
8045
8113
|
content = _stampNonClaudeRuntimeDefaults(content, runtime);
|
|
8046
8114
|
}
|
|
8047
8115
|
|
|
@@ -8325,7 +8393,7 @@ function reclaimKimiHooksRoot(kimiHooksRoot) {
|
|
|
8325
8393
|
const kimiHooksLibDir = path.join(kimiHooksDir, 'lib');
|
|
8326
8394
|
if (fs.existsSync(kimiHooksLibDir)) {
|
|
8327
8395
|
let removedKimiLibFiles = 0;
|
|
8328
|
-
for (const file of
|
|
8396
|
+
for (const file of gsdHookLibFiles()) {
|
|
8329
8397
|
try {
|
|
8330
8398
|
fs.unlinkSync(path.join(kimiHooksLibDir, file));
|
|
8331
8399
|
removedKimiLibFiles++;
|
|
@@ -8391,7 +8459,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8391
8459
|
// `runtime === 'cline'` branch into hostBehaviors.localTargetIsProjectRoot.
|
|
8392
8460
|
const targetDir = isGlobal
|
|
8393
8461
|
? getGlobalConfigDir(runtime, explicitConfigDir)
|
|
8394
|
-
:
|
|
8462
|
+
: hostBehaviorsFor(runtime).localTargetIsProjectRoot
|
|
8395
8463
|
? process.cwd()
|
|
8396
8464
|
: path.join(process.cwd(), dirName);
|
|
8397
8465
|
|
|
@@ -8499,7 +8567,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8499
8567
|
}
|
|
8500
8568
|
|
|
8501
8569
|
// 1a. Non-layout Codex side-effects: agent .toml files, config.toml sections, hooks.json
|
|
8502
|
-
if (
|
|
8570
|
+
if (hostBehaviorsFor(runtime).tomlConfigInstall) {
|
|
8503
8571
|
const codexAgentsDir = path.join(targetDir, 'agents');
|
|
8504
8572
|
if (fs.existsSync(codexAgentsDir)) {
|
|
8505
8573
|
const tomlFiles = fs.readdirSync(codexAgentsDir);
|
|
@@ -8613,7 +8681,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8613
8681
|
// global cross-tool ~/.agents/AGENTS.md target.
|
|
8614
8682
|
// Descriptor-driven (ADR-1239 / #2090): folded from `runtime === 'cline'`
|
|
8615
8683
|
// into hostBehaviors.clineRulesSurface.
|
|
8616
|
-
if (
|
|
8684
|
+
if (hostBehaviorsFor(runtime).clineRulesSurface) {
|
|
8617
8685
|
const clinerulesDir = path.join(targetDir, '.clinerules');
|
|
8618
8686
|
for (const rel of ['gsd.md', path.join('hooks', 'PreToolUse')]) {
|
|
8619
8687
|
const p = path.join(clinerulesDir, rel);
|
|
@@ -8663,7 +8731,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8663
8731
|
// GSD-managed hook entries from hooks.json and clean up the managed hook
|
|
8664
8732
|
// scripts. Gated by the hostBehaviors.hooksJsonSurface descriptor axis, not a
|
|
8665
8733
|
// hardcoded `isCursor` branch.
|
|
8666
|
-
if (
|
|
8734
|
+
if (hostBehaviorsFor(runtime).hooksJsonSurface) {
|
|
8667
8735
|
const hooksJsonCleanup = removeCursorHooksJson(targetDir);
|
|
8668
8736
|
if (hooksJsonCleanup.changed) {
|
|
8669
8737
|
removedCount++;
|
|
@@ -8723,7 +8791,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8723
8791
|
|
|
8724
8792
|
// 1c. Claude local: remove flat gsd-*.md commands from commands/ (current layout,
|
|
8725
8793
|
// #1367 fix). Also remove legacy commands/gsd/ subdirectory from prior installs.
|
|
8726
|
-
if (!isGlobal &&
|
|
8794
|
+
if (!isGlobal && hostBehaviorsFor(runtime).localInstallStyle === 'legacy-flat') {
|
|
8727
8795
|
const commandsDir = path.join(targetDir, 'commands');
|
|
8728
8796
|
// Remove flat gsd-*.md files (current layout after #1367 fix)
|
|
8729
8797
|
if (fs.existsSync(commandsDir)) {
|
|
@@ -8789,7 +8857,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8789
8857
|
// removes the directory; we must preserve/restore user artifacts before that path.
|
|
8790
8858
|
// This block runs AFTER uninstallRuntimeArtifacts, so we check if the directory
|
|
8791
8859
|
// was already removed and skip if so (idempotent).
|
|
8792
|
-
if (
|
|
8860
|
+
if (hostBehaviorsFor(runtime).legacyCommandsGsdCleanup === true) {
|
|
8793
8861
|
// dev-preferences may have survived in skills/ as SKILL.md — nothing to do for
|
|
8794
8862
|
// that case. If a stale commands/gsd/ still exists (e.g. legacy was not removed),
|
|
8795
8863
|
// attempt migration. In practice _runLegacyUninstallCleanup removes it first,
|
|
@@ -8961,7 +9029,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8961
9029
|
const hooksLibDir = path.join(hooksDir, 'lib');
|
|
8962
9030
|
if (fs.existsSync(hooksLibDir)) {
|
|
8963
9031
|
let removedLibFiles = 0;
|
|
8964
|
-
for (const file of
|
|
9032
|
+
for (const file of gsdHookLibFiles()) {
|
|
8965
9033
|
const filePath = path.join(hooksLibDir, file);
|
|
8966
9034
|
try {
|
|
8967
9035
|
fs.unlinkSync(filePath);
|
|
@@ -8997,6 +9065,9 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8997
9065
|
console.log(` ${green}✓${reset} Removed GSD hooks/package.json (CommonJS marker)`);
|
|
8998
9066
|
}
|
|
8999
9067
|
} catch { /* best-effort */ }
|
|
9068
|
+
// Once GSD's hooks, helpers and marker are gone, an empty hooks dir is GSD's own leftover (#5335).
|
|
9069
|
+
// rmdir fails on a non-empty dir, so a user's own hook scripts keep it in place.
|
|
9070
|
+
try { fs.rmdirSync(hooksDir); } catch { /* not empty or absent */ }
|
|
9000
9071
|
}
|
|
9001
9072
|
|
|
9002
9073
|
// 4z. Remove the native plugin adapter (#1914, extended to Kilo by #2093).
|
|
@@ -9004,7 +9075,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
9004
9075
|
// that declares the block (OpenCode, Kilo, ...), not just OpenCode. Only
|
|
9005
9076
|
// GSD's own plugin file is removed; the plugins/ dir is pruned only if it
|
|
9006
9077
|
// becomes empty, preserving any user-authored plugins for that host.
|
|
9007
|
-
const _np =
|
|
9078
|
+
const _np = hostBehaviorsFor(runtime).nativePlugin;
|
|
9008
9079
|
if (_np) {
|
|
9009
9080
|
const pluginsDir = path.join(targetDir, _np.dir);
|
|
9010
9081
|
const pluginPath = path.join(pluginsDir, _np.file);
|
|
@@ -9030,6 +9101,20 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
9030
9101
|
// unlink above failed, pulling the marker out from under a still-present
|
|
9031
9102
|
// CommonJS adapter would leave it unloadable. The exact content match
|
|
9032
9103
|
// still leaves any user-authored package.json in place.
|
|
9104
|
+
// The terminal-client plugin directory (OpenCode 2.x statusline): GSD's file, then its now-empty directory.
|
|
9105
|
+
const _cliU = hostBehaviorsFor(runtime).nativeCliPlugin;
|
|
9106
|
+
if (_cliU) {
|
|
9107
|
+
const cliFile = path.join(targetDir, _cliU.dir, _cliU.file);
|
|
9108
|
+
if (fs.existsSync(cliFile)) {
|
|
9109
|
+
try {
|
|
9110
|
+
fs.unlinkSync(cliFile);
|
|
9111
|
+
removedCount++;
|
|
9112
|
+
removedFromPluginsDir = true;
|
|
9113
|
+
console.log(` ${green}✓${reset} Removed terminal-client plugin (${runtime})`);
|
|
9114
|
+
} catch (_) { /* best-effort */ }
|
|
9115
|
+
}
|
|
9116
|
+
try { fs.rmdirSync(path.join(targetDir, _cliU.dir)); } catch (_) { /* not empty or absent */ }
|
|
9117
|
+
}
|
|
9033
9118
|
if (!fs.existsSync(pluginPath) && removeCommonJsMarker(pluginsDir)) {
|
|
9034
9119
|
removedCount++;
|
|
9035
9120
|
removedFromPluginsDir = true;
|
|
@@ -9169,7 +9254,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
9169
9254
|
// to preserve any user-added allow/deny entries.
|
|
9170
9255
|
// Uses a local flag to avoid the shared `settingsModified` producing a false
|
|
9171
9256
|
// "Removed GSD permissions" message when only hooks/statusline changed.
|
|
9172
|
-
if (
|
|
9257
|
+
if (hostBehaviorsFor(runtime).permissionsSchema === 'claude' && settings.permissions) {
|
|
9173
9258
|
let permissionsModified = false;
|
|
9174
9259
|
if (Array.isArray(settings.permissions.allow)) {
|
|
9175
9260
|
const before = settings.permissions.allow.length;
|
|
@@ -9240,7 +9325,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
9240
9325
|
// runtimes that host MCP there (Augment), symmetric to the mcp_config.json
|
|
9241
9326
|
// removal for Antigravity below. Only the GSD-owned mcpServers.gsd key is
|
|
9242
9327
|
// removed — any other user-configured MCP servers are preserved.
|
|
9243
|
-
if (
|
|
9328
|
+
if (hostBehaviorsFor(runtime).mcpCompanion === 'settings-json' &&
|
|
9244
9329
|
settings.mcpServers && typeof settings.mcpServers === 'object' &&
|
|
9245
9330
|
settings.mcpServers.gsd !== undefined) {
|
|
9246
9331
|
delete settings.mcpServers.gsd;
|
|
@@ -9257,40 +9342,56 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
9257
9342
|
}
|
|
9258
9343
|
}
|
|
9259
9344
|
|
|
9260
|
-
// 6. For OpenCode, clean up permissions from opencode.json or opencode.jsonc
|
|
9345
|
+
// 6. For OpenCode, clean up permissions and the companion MCP entry from opencode.json or opencode.jsonc
|
|
9261
9346
|
if (resolveInstallPlan(runtime).finishPermissionWriter === 'opencode') {
|
|
9262
9347
|
const configPath = resolveOpencodeConfigPath(targetDir);
|
|
9263
9348
|
if (fs.existsSync(configPath)) {
|
|
9264
9349
|
try {
|
|
9265
|
-
const
|
|
9266
|
-
|
|
9350
|
+
const originalText = fs.readFileSync(configPath, 'utf8');
|
|
9351
|
+
const config = parseJsonc(originalText);
|
|
9352
|
+
const edits = [];
|
|
9267
9353
|
|
|
9268
9354
|
// Remove GSD permission entries
|
|
9269
|
-
if (config.permission) {
|
|
9355
|
+
if (config.permission && typeof config.permission === 'object') {
|
|
9270
9356
|
for (const permType of ['read', 'external_directory']) {
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
}
|
|
9278
|
-
}
|
|
9279
|
-
// Clean up empty objects
|
|
9280
|
-
if (Object.keys(config.permission[permType]).length === 0) {
|
|
9281
|
-
delete config.permission[permType];
|
|
9357
|
+
const section = config.permission[permType];
|
|
9358
|
+
if (!section || typeof section !== 'object') continue;
|
|
9359
|
+
for (const key of Object.keys(section)) {
|
|
9360
|
+
if (key.includes('gsd-core')) {
|
|
9361
|
+
delete section[key];
|
|
9362
|
+
edits.push({ path: ['permission', permType, key], remove: true });
|
|
9282
9363
|
}
|
|
9283
9364
|
}
|
|
9365
|
+
// Clean up empty objects
|
|
9366
|
+
if (Object.keys(section).length === 0) delete config.permission[permType];
|
|
9284
9367
|
}
|
|
9285
|
-
if (Object.keys(config.permission).length === 0)
|
|
9286
|
-
|
|
9368
|
+
if (Object.keys(config.permission).length === 0) delete config.permission;
|
|
9369
|
+
}
|
|
9370
|
+
|
|
9371
|
+
// #5333: remove the companion MCP entry, but only while it is still the entry GSD wrote — a
|
|
9372
|
+
// user-edited or user-defined `gsd` server stays. Both OpenCode generations' shapes: the 1.x
|
|
9373
|
+
// `mcp.gsd` and the 2.x native `mcp.servers.gsd`.
|
|
9374
|
+
let mcpRemoved = false;
|
|
9375
|
+
if (config.mcp && typeof config.mcp === 'object') {
|
|
9376
|
+
if (isGsdMcpEntry(config.mcp.gsd)) {
|
|
9377
|
+
delete config.mcp.gsd;
|
|
9378
|
+
edits.push({ path: ['mcp', 'gsd'], remove: true });
|
|
9379
|
+
mcpRemoved = true;
|
|
9380
|
+
}
|
|
9381
|
+
const servers = config.mcp.servers;
|
|
9382
|
+
if (servers && typeof servers === 'object' && isGsdMcpEntry(servers.gsd)) {
|
|
9383
|
+
delete servers.gsd;
|
|
9384
|
+
edits.push({ path: ['mcp', 'servers', 'gsd'], remove: true });
|
|
9385
|
+
if (Object.keys(servers).length === 0) delete config.mcp.servers;
|
|
9386
|
+
mcpRemoved = true;
|
|
9287
9387
|
}
|
|
9388
|
+
if (Object.keys(config.mcp).length === 0) delete config.mcp;
|
|
9288
9389
|
}
|
|
9289
9390
|
|
|
9290
|
-
if (
|
|
9291
|
-
|
|
9391
|
+
if (edits.length > 0) {
|
|
9392
|
+
writeJsoncConfig(configPath, originalText, config, edits);
|
|
9292
9393
|
removedCount++;
|
|
9293
|
-
console.log(` ${green}✓${reset} Removed GSD permissions from ${path.basename(configPath)}`);
|
|
9394
|
+
console.log(` ${green}✓${reset} Removed GSD permissions${mcpRemoved ? ' and MCP server' : ''} from ${path.basename(configPath)}`);
|
|
9294
9395
|
}
|
|
9295
9396
|
} catch (e) {
|
|
9296
9397
|
// Ignore JSON parse errors
|
|
@@ -9305,8 +9406,9 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
9305
9406
|
const configPath = resolveKiloConfigPath(targetDir);
|
|
9306
9407
|
if (fs.existsSync(configPath)) {
|
|
9307
9408
|
try {
|
|
9308
|
-
const
|
|
9309
|
-
|
|
9409
|
+
const originalText = fs.readFileSync(configPath, 'utf8');
|
|
9410
|
+
const config = parseJsonc(originalText);
|
|
9411
|
+
const edits = [];
|
|
9310
9412
|
|
|
9311
9413
|
// Remove GSD permission entries
|
|
9312
9414
|
if (config.permission) {
|
|
@@ -9316,7 +9418,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
9316
9418
|
for (const key of keys) {
|
|
9317
9419
|
if (key.includes('gsd-core')) {
|
|
9318
9420
|
delete config.permission[permType][key];
|
|
9319
|
-
|
|
9421
|
+
edits.push({ path: ['permission', permType, key], remove: true });
|
|
9320
9422
|
}
|
|
9321
9423
|
}
|
|
9322
9424
|
// Clean up empty objects
|
|
@@ -9330,8 +9432,8 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
9330
9432
|
}
|
|
9331
9433
|
}
|
|
9332
9434
|
|
|
9333
|
-
if (
|
|
9334
|
-
|
|
9435
|
+
if (edits.length > 0) {
|
|
9436
|
+
writeJsoncConfig(configPath, originalText, config, edits);
|
|
9335
9437
|
removedCount++;
|
|
9336
9438
|
console.log(` ${green}✓${reset} Removed GSD permissions from ${path.basename(configPath)}`);
|
|
9337
9439
|
}
|
|
@@ -9373,6 +9475,67 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
9373
9475
|
console.log(` ${green}✓${reset} Removed ${MANIFEST_NAME}`);
|
|
9374
9476
|
}
|
|
9375
9477
|
|
|
9478
|
+
// #5335: the installer's own bookkeeping goes with it — the applied-migrations state and the migration
|
|
9479
|
+
// journal. The journal's `-backups/` folders hold copies of files the USER had modified, so a folder that
|
|
9480
|
+
// still has content is kept; everything GSD generated for itself (run records, rollback snapshots, empty
|
|
9481
|
+
// folders) is removed.
|
|
9482
|
+
const installStatePath = path.join(targetDir, 'gsd-install-state.json');
|
|
9483
|
+
if (fs.existsSync(installStatePath)) {
|
|
9484
|
+
fs.rmSync(installStatePath, { force: true });
|
|
9485
|
+
removedCount++;
|
|
9486
|
+
console.log(` ${green}✓${reset} Removed gsd-install-state.json`);
|
|
9487
|
+
}
|
|
9488
|
+
const journalDir = path.join(targetDir, 'gsd-migration-journal');
|
|
9489
|
+
if (fs.existsSync(journalDir)) {
|
|
9490
|
+
let journalEntriesRemoved = 0;
|
|
9491
|
+
try {
|
|
9492
|
+
for (const entry of fs.readdirSync(journalDir, { withFileTypes: true })) {
|
|
9493
|
+
const full = path.join(journalDir, entry.name);
|
|
9494
|
+
if (entry.isSymbolicLink()) continue;
|
|
9495
|
+
if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
9496
|
+
fs.rmSync(full, { force: true });
|
|
9497
|
+
journalEntriesRemoved++;
|
|
9498
|
+
} else if (entry.isDirectory() && entry.name.endsWith('-rollback')) {
|
|
9499
|
+
fs.rmSync(full, { recursive: true, force: true });
|
|
9500
|
+
journalEntriesRemoved++;
|
|
9501
|
+
} else if (entry.isDirectory() && entry.name.endsWith('-backups')) {
|
|
9502
|
+
try { fs.rmdirSync(full); journalEntriesRemoved++; } catch { /* holds the user's backed-up files: keep */ }
|
|
9503
|
+
}
|
|
9504
|
+
}
|
|
9505
|
+
fs.rmdirSync(journalDir);
|
|
9506
|
+
} catch { /* a non-empty journal dir (kept backups) stays */ }
|
|
9507
|
+
if (journalEntriesRemoved > 0) {
|
|
9508
|
+
removedCount++;
|
|
9509
|
+
console.log(` ${green}✓${reset} Removed gsd-migration-journal/ records`);
|
|
9510
|
+
}
|
|
9511
|
+
}
|
|
9512
|
+
|
|
9513
|
+
// #5335: the installer's user-artifact staging area is empty once the install it served is over. rmdir only
|
|
9514
|
+
// succeeds on an empty directory, so anything still staged there is left for its owner.
|
|
9515
|
+
try {
|
|
9516
|
+
fs.rmdirSync(path.join(targetDir, '.gsd-staging', 'user-artifacts'));
|
|
9517
|
+
fs.rmdirSync(path.join(targetDir, '.gsd-staging'));
|
|
9518
|
+
} catch { /* absent or not empty */ }
|
|
9519
|
+
|
|
9520
|
+
// #5335: OpenCode never reads settings.json, but the shared-settings install step creates it as `{}`.
|
|
9521
|
+
// An empty one carries nothing of the user's; a populated one is left alone.
|
|
9522
|
+
if (resolveInstallPlan(runtime).finishPermissionWriter === 'opencode') {
|
|
9523
|
+
const emptySettingsPath = path.join(targetDir, 'settings.json');
|
|
9524
|
+
try {
|
|
9525
|
+
const parsed = JSON.parse(fs.readFileSync(emptySettingsPath, 'utf8'));
|
|
9526
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && Object.keys(parsed).length === 0) {
|
|
9527
|
+
fs.rmSync(emptySettingsPath, { force: true });
|
|
9528
|
+
removedCount++;
|
|
9529
|
+
console.log(` ${green}✓${reset} Removed empty settings.json`);
|
|
9530
|
+
}
|
|
9531
|
+
} catch { /* absent, unreadable or populated: leave it */ }
|
|
9532
|
+
}
|
|
9533
|
+
|
|
9534
|
+
// #5335: the machine-wide defaults the install wrote for this runtime, when nothing else needs them.
|
|
9535
|
+
if (revertNonClaudeDefaults(runtime)) {
|
|
9536
|
+
removedCount++;
|
|
9537
|
+
}
|
|
9538
|
+
|
|
9376
9539
|
if (removedCount === 0) {
|
|
9377
9540
|
console.log(` ${yellow}⚠${reset} No GSD files found to remove.`);
|
|
9378
9541
|
}
|
|
@@ -9384,64 +9547,30 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
9384
9547
|
}
|
|
9385
9548
|
|
|
9386
9549
|
/**
|
|
9387
|
-
*
|
|
9388
|
-
*
|
|
9389
|
-
*
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
9405
|
-
if (
|
|
9406
|
-
|
|
9407
|
-
|
|
9408
|
-
if (char === '\\' && i + 1 < content.length) {
|
|
9409
|
-
result += next;
|
|
9410
|
-
i += 2;
|
|
9411
|
-
continue;
|
|
9412
|
-
}
|
|
9413
|
-
if (char === '"') {
|
|
9414
|
-
inString = false;
|
|
9415
|
-
}
|
|
9416
|
-
i++;
|
|
9417
|
-
} else {
|
|
9418
|
-
if (char === '"') {
|
|
9419
|
-
inString = true;
|
|
9420
|
-
result += char;
|
|
9421
|
-
i++;
|
|
9422
|
-
} else if (char === '/' && next === '/') {
|
|
9423
|
-
// Skip single-line comment until end of line
|
|
9424
|
-
while (i < content.length && content[i] !== '\n') {
|
|
9425
|
-
i++;
|
|
9426
|
-
}
|
|
9427
|
-
} else if (char === '/' && next === '*') {
|
|
9428
|
-
// Skip block comment
|
|
9429
|
-
i += 2;
|
|
9430
|
-
while (i < content.length - 1 && !(content[i] === '*' && content[i + 1] === '/')) {
|
|
9431
|
-
i++;
|
|
9432
|
-
}
|
|
9433
|
-
i += 2; // Skip closing */
|
|
9434
|
-
} else {
|
|
9435
|
-
result += char;
|
|
9436
|
-
i++;
|
|
9437
|
-
}
|
|
9550
|
+
* Write back an OpenCode/Kilo config the installer changed (#5334).
|
|
9551
|
+
*
|
|
9552
|
+
* A file that carries JSONC syntax (a `.jsonc` name, comments or trailing commas) is edited as text, so
|
|
9553
|
+
* only the keys GSD owns change and the user's annotations and layout survive. Anything the text editor
|
|
9554
|
+
* cannot handle, and plain JSON, keeps the previous `JSON.stringify` rewrite.
|
|
9555
|
+
*
|
|
9556
|
+
* @param {string} configPath
|
|
9557
|
+
* @param {string|null} originalText current file text, or null when the file is new
|
|
9558
|
+
* @param {object} config the mutated parsed config (used for the fallback)
|
|
9559
|
+
* @param {Array<{path: string[], value?: unknown, remove?: boolean}>} edits
|
|
9560
|
+
*/
|
|
9561
|
+
function writeJsoncConfig(configPath, originalText, config, edits) {
|
|
9562
|
+
if (typeof originalText === 'string' && (configPath.endsWith('.jsonc') || hasJsoncFeatures(originalText))) {
|
|
9563
|
+
let text = originalText;
|
|
9564
|
+
for (const edit of edits) {
|
|
9565
|
+
text = edit.remove ? removeJsoncValue(text, edit.path) : setJsoncValue(text, edit.path, edit.value);
|
|
9566
|
+
if (text === null) break;
|
|
9567
|
+
}
|
|
9568
|
+
if (text !== null) {
|
|
9569
|
+
fs.writeFileSync(configPath, text);
|
|
9570
|
+
return;
|
|
9438
9571
|
}
|
|
9439
9572
|
}
|
|
9440
|
-
|
|
9441
|
-
// Remove trailing commas before } or ]
|
|
9442
|
-
result = result.replace(/,(\s*[}\]])/g, '$1');
|
|
9443
|
-
|
|
9444
|
-
return JSON.parse(result);
|
|
9573
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
9445
9574
|
}
|
|
9446
9575
|
|
|
9447
9576
|
/**
|
|
@@ -9463,9 +9592,11 @@ function configureOpencodePermissions(isGlobal = true, configDir = null) {
|
|
|
9463
9592
|
|
|
9464
9593
|
// Read existing config or create empty object
|
|
9465
9594
|
let config = {};
|
|
9595
|
+
let originalText = null;
|
|
9466
9596
|
if (fs.existsSync(configPath)) {
|
|
9467
9597
|
try {
|
|
9468
9598
|
const content = fs.readFileSync(configPath, 'utf8');
|
|
9599
|
+
originalText = content;
|
|
9469
9600
|
config = parseJsonc(content);
|
|
9470
9601
|
} catch (e) {
|
|
9471
9602
|
// Cannot parse - DO NOT overwrite user's config
|
|
@@ -9496,6 +9627,7 @@ function configureOpencodePermissions(isGlobal = true, configDir = null) {
|
|
|
9496
9627
|
: `${opencodeConfigDir.replace(/\\/g, '/')}/gsd-core/*`;
|
|
9497
9628
|
|
|
9498
9629
|
let modified = false;
|
|
9630
|
+
const edits = [];
|
|
9499
9631
|
|
|
9500
9632
|
// Configure read permission
|
|
9501
9633
|
if (!config.permission.read || typeof config.permission.read !== 'object') {
|
|
@@ -9503,6 +9635,7 @@ function configureOpencodePermissions(isGlobal = true, configDir = null) {
|
|
|
9503
9635
|
}
|
|
9504
9636
|
if (config.permission.read[gsdPath] !== 'allow') {
|
|
9505
9637
|
config.permission.read[gsdPath] = 'allow';
|
|
9638
|
+
edits.push({ path: ['permission', 'read', gsdPath], value: 'allow' });
|
|
9506
9639
|
modified = true;
|
|
9507
9640
|
}
|
|
9508
9641
|
|
|
@@ -9512,6 +9645,7 @@ function configureOpencodePermissions(isGlobal = true, configDir = null) {
|
|
|
9512
9645
|
}
|
|
9513
9646
|
if (config.permission.external_directory[gsdPath] !== 'allow') {
|
|
9514
9647
|
config.permission.external_directory[gsdPath] = 'allow';
|
|
9648
|
+
edits.push({ path: ['permission', 'external_directory', gsdPath], value: 'allow' });
|
|
9515
9649
|
modified = true;
|
|
9516
9650
|
}
|
|
9517
9651
|
|
|
@@ -9528,9 +9662,10 @@ function configureOpencodePermissions(isGlobal = true, configDir = null) {
|
|
|
9528
9662
|
if (config.mcp.gsd === undefined) {
|
|
9529
9663
|
config.mcp.gsd = {
|
|
9530
9664
|
type: 'local',
|
|
9531
|
-
command: ['npx', '-y', '-p', PACKAGE_NAME, 'gsd-mcp-server'],
|
|
9665
|
+
command: ['npx', '-y', '-p', PACKAGE_NAME, 'gsd-mcp-server', GSD_MCP_NO_PROMPTS_FLAG],
|
|
9532
9666
|
enabled: true,
|
|
9533
9667
|
};
|
|
9668
|
+
edits.push({ path: ['mcp', 'gsd'], value: config.mcp.gsd });
|
|
9534
9669
|
modified = true;
|
|
9535
9670
|
}
|
|
9536
9671
|
|
|
@@ -9539,7 +9674,7 @@ function configureOpencodePermissions(isGlobal = true, configDir = null) {
|
|
|
9539
9674
|
}
|
|
9540
9675
|
|
|
9541
9676
|
// Write config back
|
|
9542
|
-
|
|
9677
|
+
writeJsoncConfig(configPath, originalText, config, edits);
|
|
9543
9678
|
console.log(` ${green}✓${reset} Configured read permission for GSD docs`);
|
|
9544
9679
|
}
|
|
9545
9680
|
|
|
@@ -9562,9 +9697,11 @@ function configureKiloPermissions(isGlobal = true, configDir = null) {
|
|
|
9562
9697
|
|
|
9563
9698
|
// Read existing config or create empty object
|
|
9564
9699
|
let config = {};
|
|
9700
|
+
let originalText = null;
|
|
9565
9701
|
if (fs.existsSync(configPath)) {
|
|
9566
9702
|
try {
|
|
9567
9703
|
const content = fs.readFileSync(configPath, 'utf8');
|
|
9704
|
+
originalText = content;
|
|
9568
9705
|
config = parseJsonc(content);
|
|
9569
9706
|
} catch (e) {
|
|
9570
9707
|
// Cannot parse - DO NOT overwrite user's config
|
|
@@ -9589,6 +9726,7 @@ function configureKiloPermissions(isGlobal = true, configDir = null) {
|
|
|
9589
9726
|
: `${kiloConfigDir.replace(/\\/g, '/')}/gsd-core/*`;
|
|
9590
9727
|
|
|
9591
9728
|
let modified = false;
|
|
9729
|
+
const edits = [];
|
|
9592
9730
|
|
|
9593
9731
|
// Configure read permission
|
|
9594
9732
|
if (!config.permission.read || typeof config.permission.read !== 'object') {
|
|
@@ -9596,6 +9734,7 @@ function configureKiloPermissions(isGlobal = true, configDir = null) {
|
|
|
9596
9734
|
}
|
|
9597
9735
|
if (config.permission.read[gsdPath] !== 'allow') {
|
|
9598
9736
|
config.permission.read[gsdPath] = 'allow';
|
|
9737
|
+
edits.push({ path: ['permission', 'read', gsdPath], value: 'allow' });
|
|
9599
9738
|
modified = true;
|
|
9600
9739
|
}
|
|
9601
9740
|
|
|
@@ -9605,6 +9744,7 @@ function configureKiloPermissions(isGlobal = true, configDir = null) {
|
|
|
9605
9744
|
}
|
|
9606
9745
|
if (config.permission.external_directory[gsdPath] !== 'allow') {
|
|
9607
9746
|
config.permission.external_directory[gsdPath] = 'allow';
|
|
9747
|
+
edits.push({ path: ['permission', 'external_directory', gsdPath], value: 'allow' });
|
|
9608
9748
|
modified = true;
|
|
9609
9749
|
}
|
|
9610
9750
|
|
|
@@ -9613,7 +9753,7 @@ function configureKiloPermissions(isGlobal = true, configDir = null) {
|
|
|
9613
9753
|
}
|
|
9614
9754
|
|
|
9615
9755
|
// Write config back
|
|
9616
|
-
|
|
9756
|
+
writeJsoncConfig(configPath, originalText, config, edits);
|
|
9617
9757
|
console.log(` ${green}✓${reset} Configured read permission for GSD docs`);
|
|
9618
9758
|
}
|
|
9619
9759
|
|
|
@@ -9929,7 +10069,7 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9929
10069
|
// #1367: Claude local now writes flat gsd-*.md files at commands/ (not commands/gsd/).
|
|
9930
10070
|
// Claude local uses flatCommandsDir instead for manifest recording.
|
|
9931
10071
|
const flatCommandsDir = path.join(configDir, 'commands');
|
|
9932
|
-
const opencodeCommandDir = path.join(configDir,
|
|
10072
|
+
const opencodeCommandDir = path.join(configDir, hostBehaviorsFor(runtime).flatCommandDir || 'command');
|
|
9933
10073
|
// Hermes nests GSD skills under skills/gsd/ as a single category (#2841) —
|
|
9934
10074
|
// already encoded in its layout descriptor's destSubpath ('skills/gsd').
|
|
9935
10075
|
// All other runtimes that use the Codex-style skills layout use a flat skills/ root.
|
|
@@ -9944,7 +10084,7 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9944
10084
|
// `options.scope` could drift; one cannot.
|
|
9945
10085
|
const resolvedScope = options.scope === 'local' ? 'local' : 'global';
|
|
9946
10086
|
const codexSkillsDir = _resolveSkillsRootDir(runtime, configDir, resolvedScope);
|
|
9947
|
-
const codexSkillsManifestPrefix =
|
|
10087
|
+
const codexSkillsManifestPrefix = hostBehaviorsFor(runtime).skillsManifestPrefix || 'skills/';
|
|
9948
10088
|
// #3738: resolve the ACTUAL agents-install dir honoring an agents-kind `home`
|
|
9949
10089
|
// override (antigravity global → $HOME/.gemini/config/agents), mirroring
|
|
9950
10090
|
// _resolveSkillsRootDir for skills. Hardcoding configDir/agents left the
|
|
@@ -9996,26 +10136,26 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9996
10136
|
// Claude local (#1367): flat gsd-*.md files at commands/ level.
|
|
9997
10137
|
// Only claude local writes gsd-*.md here; global installs don't emit commands,
|
|
9998
10138
|
// so this branch is a no-op for global (no matching files to find).
|
|
9999
|
-
if (
|
|
10139
|
+
if (hostBehaviorsFor(runtime).localInstallStyle === 'legacy-flat' && fs.existsSync(flatCommandsDir)) {
|
|
10000
10140
|
for (const file of fs.readdirSync(flatCommandsDir)) {
|
|
10001
10141
|
if (file.startsWith('gsd-') && file.endsWith('.md')) {
|
|
10002
10142
|
manifest.files['commands/' + file] = fileHash(path.join(flatCommandsDir, file));
|
|
10003
10143
|
}
|
|
10004
10144
|
}
|
|
10005
10145
|
}
|
|
10006
|
-
if (
|
|
10146
|
+
if (hostBehaviorsFor(runtime).flatCommandDir && fs.existsSync(opencodeCommandDir)) {
|
|
10007
10147
|
// #2329: derive the manifest key prefix from the SAME descriptor value used
|
|
10008
10148
|
// to compute opencodeCommandDir above, instead of a separately-hardcoded
|
|
10009
10149
|
// literal — a divergence here would silently break the manifest even after
|
|
10010
10150
|
// the destSubpath descriptor is corrected (Generative Fix Divergence guard).
|
|
10011
|
-
const flatCommandDirPrefix =
|
|
10151
|
+
const flatCommandDirPrefix = hostBehaviorsFor(runtime).flatCommandDir || 'command';
|
|
10012
10152
|
for (const file of fs.readdirSync(opencodeCommandDir)) {
|
|
10013
10153
|
if (file.startsWith('gsd-') && file.endsWith('.md')) {
|
|
10014
10154
|
manifest.files[flatCommandDirPrefix + '/' + file] = fileHash(path.join(opencodeCommandDir, file));
|
|
10015
10155
|
}
|
|
10016
10156
|
}
|
|
10017
10157
|
}
|
|
10018
|
-
if (!
|
|
10158
|
+
if (!hostBehaviorsFor(runtime).skipCodexSkillsManifest && fs.existsSync(codexSkillsDir)) {
|
|
10019
10159
|
// All runtimes (including Hermes post-#947) use the canonical 'gsd-' prefix.
|
|
10020
10160
|
const skillListPrefix = 'gsd-';
|
|
10021
10161
|
for (const skillName of listCodexSkillNames(codexSkillsDir, skillListPrefix)) {
|
|
@@ -10026,14 +10166,14 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10026
10166
|
}
|
|
10027
10167
|
}
|
|
10028
10168
|
// Descriptor-driven (#2090): hash the category DESCRIPTION.md so reinstall detects drift.
|
|
10029
|
-
if (
|
|
10169
|
+
if (hostBehaviorsFor(runtime).trackCategoryDescription) {
|
|
10030
10170
|
const descPath = path.join(codexSkillsDir, 'DESCRIPTION.md');
|
|
10031
10171
|
if (fs.existsSync(descPath)) {
|
|
10032
10172
|
manifest.files['skills/gsd/DESCRIPTION.md'] = fileHash(descPath);
|
|
10033
10173
|
}
|
|
10034
10174
|
}
|
|
10035
10175
|
}
|
|
10036
|
-
if (
|
|
10176
|
+
if (hostBehaviorsFor(runtime).agentManifestStyle === 'kimi-nested' && fs.existsSync(agentsDir)) {
|
|
10037
10177
|
const agentHashes = generateManifest(agentsDir);
|
|
10038
10178
|
for (const [rel, hash] of Object.entries(agentHashes)) {
|
|
10039
10179
|
const isRootAgent = rel === 'gsd.yaml' || rel === 'gsd.md';
|
|
@@ -10054,7 +10194,7 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10054
10194
|
// marker block, not the per-configDir manifest, since it lives outside it.)
|
|
10055
10195
|
// Descriptor-driven (ADR-1239 / #2090): folded from `isCline` into
|
|
10056
10196
|
// hostBehaviors.clineRulesSurface.
|
|
10057
|
-
if (
|
|
10197
|
+
if (hostBehaviorsFor(runtime).clineRulesSurface) {
|
|
10058
10198
|
for (const rel of ['.clinerules/gsd.md', '.clinerules/hooks/PreToolUse']) {
|
|
10059
10199
|
const dest = path.join(configDir, rel);
|
|
10060
10200
|
if (fs.existsSync(dest)) {
|
|
@@ -10075,7 +10215,7 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10075
10215
|
// skipSharedHooksInstall:true) — the redundant `&& !isCopilot` was removed.
|
|
10076
10216
|
// #2100: Windsurf's exclusion is likewise descriptor-driven (windsurf declares
|
|
10077
10217
|
// skipSharedHooksInstall:true) — the redundant `&& !isWindsurf` was removed.
|
|
10078
|
-
if (!isCodex &&
|
|
10218
|
+
if (!isCodex && hostBehaviorsFor(runtime).skipSharedHooksInstall !== true) {
|
|
10079
10219
|
// #3023: manifest keys must track the bundle wherever the descriptor put it,
|
|
10080
10220
|
// or uninstall/saveLocalPatches silently orphan the tree.
|
|
10081
10221
|
const sharedHooksDirName = resolveSharedHooksDirName(runtime);
|
|
@@ -10098,8 +10238,9 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10098
10238
|
// to git-cmd.js (validate-commit classifier) and gsd-graphify-rebuild.sh.
|
|
10099
10239
|
const hooksLibDir = path.join(hooksDir, 'lib');
|
|
10100
10240
|
if (fs.existsSync(hooksLibDir)) {
|
|
10241
|
+
const hookLibNames = new Set(gsdHookLibFiles());
|
|
10101
10242
|
for (const file of fs.readdirSync(hooksLibDir)) {
|
|
10102
|
-
if (
|
|
10243
|
+
if (hookLibNames.has(file)) {
|
|
10103
10244
|
manifest.files[sharedHooksDirName + '/lib/' + file] = fileHash(path.join(hooksLibDir, file));
|
|
10104
10245
|
}
|
|
10105
10246
|
}
|
|
@@ -10142,13 +10283,21 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10142
10283
|
|
|
10143
10284
|
// Track the OpenCode native plugin adapter (#1914) so update/drift detection
|
|
10144
10285
|
// and uninstall can account for it.
|
|
10145
|
-
const _npM =
|
|
10286
|
+
const _npM = hostBehaviorsFor(runtime).nativePlugin;
|
|
10146
10287
|
if (_npM) {
|
|
10147
10288
|
const pluginInstallPath = path.join(configDir, _npM.dir, _npM.file);
|
|
10148
10289
|
if (fs.existsSync(pluginInstallPath)) {
|
|
10149
10290
|
manifest.files[`${_npM.dir}/${_npM.file}`] = fileHash(pluginInstallPath);
|
|
10150
10291
|
}
|
|
10151
10292
|
}
|
|
10293
|
+
// The OpenCode 2.x terminal-client plugin (statusline / update banner) is tracked beside it.
|
|
10294
|
+
const _cliM = hostBehaviorsFor(runtime).nativeCliPlugin;
|
|
10295
|
+
if (_cliM) {
|
|
10296
|
+
const cliInstallPath = path.join(configDir, _cliM.dir, _cliM.file);
|
|
10297
|
+
if (fs.existsSync(cliInstallPath)) {
|
|
10298
|
+
manifest.files[`${_cliM.dir}/${_cliM.file}`] = fileHash(cliInstallPath);
|
|
10299
|
+
}
|
|
10300
|
+
}
|
|
10152
10301
|
|
|
10153
10302
|
fs.writeFileSync(path.join(configDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2));
|
|
10154
10303
|
return manifest;
|
|
@@ -10373,7 +10522,7 @@ function saveLocalPatches(configDir, pristineCtx) {
|
|
|
10373
10522
|
skillsRoot !== resolvedConfig &&
|
|
10374
10523
|
!skillsRoot.startsWith(resolvedConfig + path.sep)
|
|
10375
10524
|
) {
|
|
10376
|
-
const prefix =
|
|
10525
|
+
const prefix = hostBehaviorsFor(patchRuntime).skillsManifestPrefix || 'skills/';
|
|
10377
10526
|
skillsRedirect = { root: skillsRoot, prefix };
|
|
10378
10527
|
}
|
|
10379
10528
|
}
|
|
@@ -10596,7 +10745,7 @@ function reportLocalPatches(configDir, runtime = DEFAULT_RUNTIME) {
|
|
|
10596
10745
|
try { meta = JSON.parse(fs.readFileSync(metaPath, 'utf8')); } catch { return []; }
|
|
10597
10746
|
|
|
10598
10747
|
if (meta.files && meta.files.length > 0) {
|
|
10599
|
-
const reapplyCommand =
|
|
10748
|
+
const reapplyCommand = hostBehaviorsFor(runtime).reapplyCommand || '/gsd-update --reapply';
|
|
10600
10749
|
console.log('');
|
|
10601
10750
|
console.log(' ' + yellow + 'Local patches detected' + reset + ' (from v' + meta.from_version + '):');
|
|
10602
10751
|
for (const f of meta.files) {
|
|
@@ -10624,12 +10773,12 @@ function reportInstallerMigrationResult(result) {
|
|
|
10624
10773
|
|
|
10625
10774
|
function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
10626
10775
|
// #2093: isKilo dropped — Kilo's agent/model-override handling below reads
|
|
10627
|
-
//
|
|
10776
|
+
// hostBehaviorsFor(runtime).frontmatterDialect === 'kilo' instead of this flag.
|
|
10628
10777
|
// #2095: isKimi dropped — kimi is now a hooks/ consumer like every other
|
|
10629
10778
|
// settings-json-adjacent runtime; the two `&& !isKimi` hooks-copy guards
|
|
10630
10779
|
// below were removed, leaving isKimi unused in this function (the kimi
|
|
10631
10780
|
// local-install-deferred branch above already reads
|
|
10632
|
-
//
|
|
10781
|
+
// hostBehaviorsFor(runtime).localInstallDeferred instead of this flag).
|
|
10633
10782
|
// #2096: isAntigravity dropped — antigravity's agents were already
|
|
10634
10783
|
// descriptor-driven (installRuntimeArtifacts), so its two legacy-agent-loop
|
|
10635
10784
|
// branches (the path-rewrite skip and the converter dispatch) were
|
|
@@ -10669,7 +10818,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10669
10818
|
// re-running install()) can warn again if the same condition recurs.
|
|
10670
10819
|
_codexResolverModelOmittedWarned = false;
|
|
10671
10820
|
|
|
10672
|
-
if (
|
|
10821
|
+
if (hostBehaviorsFor(runtime).localInstallDeferred && !isGlobal) {
|
|
10673
10822
|
console.log(` ${yellow}⚠${reset} Kimi local install is deferred for Phase 2.`);
|
|
10674
10823
|
console.log(` No .kimi-code/skills or .agents/skills project artifacts were written.`);
|
|
10675
10824
|
console.log(` Project-level Kimi install semantics remain deferred.`);
|
|
@@ -10740,7 +10889,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10740
10889
|
// auto-removed on reinstall (dual-read fallback per issue #791 spec).
|
|
10741
10890
|
const targetDir = isGlobal
|
|
10742
10891
|
? getGlobalConfigDir(runtime, explicitConfigDir)
|
|
10743
|
-
:
|
|
10892
|
+
: hostBehaviorsFor(runtime).localTargetIsProjectRoot
|
|
10744
10893
|
? process.cwd()
|
|
10745
10894
|
: path.join(process.cwd(), dirName);
|
|
10746
10895
|
|
|
@@ -10855,7 +11004,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10855
11004
|
const isWindowsHost = process.platform === 'win32';
|
|
10856
11005
|
const pathPrefix = computePathPrefix({
|
|
10857
11006
|
isGlobal,
|
|
10858
|
-
isOpencode:
|
|
11007
|
+
isOpencode: hostBehaviorsFor(runtime).skipHomePrefixSubstitution === true,
|
|
10859
11008
|
isWindowsHost,
|
|
10860
11009
|
resolvedTarget,
|
|
10861
11010
|
homeDir,
|
|
@@ -10863,7 +11012,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10863
11012
|
// copyWithPathReplacement, i.e. the one actually written into every
|
|
10864
11013
|
// emitted command/skill/workflow body — the rewrite-engine seams below
|
|
10865
11014
|
// handle re-applied surfaces, not the first install.
|
|
10866
|
-
localDirName:
|
|
11015
|
+
localDirName: hostBehaviorsFor(runtime).localTargetIsProjectRoot === true ? undefined : getDirName(runtime),
|
|
10867
11016
|
});
|
|
10868
11017
|
|
|
10869
11018
|
// runtimeLabel is now the single-source getRuntimeLabel lookup (ADR-1239
|
|
@@ -10967,7 +11116,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10967
11116
|
// a core/--minimal install too (#2695), and its pass-2 sweeps remove every
|
|
10968
11117
|
// gsd-* skill dir / agent file the snapshot does not claim — so an empty
|
|
10969
11118
|
// minimal-mode snapshot deleted the whole surface with nothing to restore.
|
|
10970
|
-
if (
|
|
11119
|
+
if (hostBehaviorsFor(runtime).tomlConfigInstall) {
|
|
10971
11120
|
codexManagedSnapshotCaptured = true;
|
|
10972
11121
|
const _preSkillsDir = _resolveSkillsRootDir(runtime, targetDir, _installScopeId);
|
|
10973
11122
|
if (fs.existsSync(_preSkillsDir)) {
|
|
@@ -11216,7 +11365,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11216
11365
|
// The full restoreCodexSnapshot() (defined inside the config block) additionally
|
|
11217
11366
|
// handles config.toml and the staged hooks/ tree, which are not yet touched
|
|
11218
11367
|
// at this point in the pipeline.
|
|
11219
|
-
const _codexPreConfigRollback = !
|
|
11368
|
+
const _codexPreConfigRollback = !hostBehaviorsFor(runtime).tomlConfigInstall || isMinimalMode(_effectiveInstallMode) ? null : () => {
|
|
11220
11369
|
rollbackInstallerMigrations();
|
|
11221
11370
|
// skills/gsd-* — pass 1: restore snapshot entries (may be absent if deleted mid-install).
|
|
11222
11371
|
const _earlySkillsDir = _resolveSkillsRootDir(runtime, targetDir, _installScopeId);
|
|
@@ -11406,7 +11555,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11406
11555
|
// used to describe. Claude-local remains the one special-cased path
|
|
11407
11556
|
// (copyWithPathReplacement + stale-skills cleanup).
|
|
11408
11557
|
const _isSkillsRuntime = (() => {
|
|
11409
|
-
if (
|
|
11558
|
+
if (hostBehaviorsFor(runtime).localInstallStyle === 'legacy-flat' && !isGlobal) return false; // legacy flat local path (descriptor-driven; #2086)
|
|
11410
11559
|
// #2875 Part 2 defect fix: a runtime whose LOCAL commands are embedded in a
|
|
11411
11560
|
// rules file rather than materialized as files (hostBehaviors.localCommandsViaRules
|
|
11412
11561
|
// — cline is the only declarant, capabilities/cline/capability.json) must not
|
|
@@ -11417,7 +11566,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11417
11566
|
// spuriously fail; the `localCommandsViaRules` branch below (unchanged
|
|
11418
11567
|
// messaging) and the unconditional agents-materialization block further down
|
|
11419
11568
|
// (installAgentsKindStandalone) already cover this runtime/scope correctly.
|
|
11420
|
-
if (!isGlobal &&
|
|
11569
|
+
if (!isGlobal && hostBehaviorsFor(runtime).localCommandsViaRules) return false;
|
|
11421
11570
|
const cap = _capabilityRegistry && _capabilityRegistry.runtimes && _capabilityRegistry.runtimes[runtime];
|
|
11422
11571
|
const layout = cap && cap.runtime && cap.runtime.artifactLayout;
|
|
11423
11572
|
if (!layout) return false;
|
|
@@ -11456,13 +11605,13 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11456
11605
|
// marker. Write failure is non-fatal (install proceeds; warn so /gsd-surface breakage is
|
|
11457
11606
|
// diagnosable) — the same contract the late write had.
|
|
11458
11607
|
function _writeGsdSourceMarker(runtime, targetDir, src, isGlobal) {
|
|
11459
|
-
if (
|
|
11608
|
+
if (hostBehaviorsFor(runtime).sourceMarkerFile && isGlobal) {
|
|
11460
11609
|
const gsdSourceCommands = path.join(src, 'commands', 'gsd');
|
|
11461
11610
|
if (fs.existsSync(gsdSourceCommands)) {
|
|
11462
11611
|
try {
|
|
11463
11612
|
// ADR-1239 Phase B write-confinement: the descriptor-sourced marker filename
|
|
11464
11613
|
// must resolve under targetDir (parity with the other descriptor-driven writes).
|
|
11465
|
-
const _markerPath = assertDestWithinConfigHome(targetDir,
|
|
11614
|
+
const _markerPath = assertDestWithinConfigHome(targetDir, hostBehaviorsFor(runtime).sourceMarkerFile);
|
|
11466
11615
|
if (hasExistingSymlinkBetween(path.resolve(targetDir), _markerPath, { allowOptInFollow: isSymlinkedDestOptIn() })) {
|
|
11467
11616
|
throw new Error(`compatibility marker "${_markerPath}" contains an untrusted symlink`);
|
|
11468
11617
|
}
|
|
@@ -11537,7 +11686,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11537
11686
|
// index BOTH SKILL.md and the sidecar, causing each GSD skill to appear twice
|
|
11538
11687
|
// in autocomplete. Cleaning them up fixes the duplication; SKILL.md alone is
|
|
11539
11688
|
// sufficient for Codex discovery. User-owned dirs are never touched.
|
|
11540
|
-
if (
|
|
11689
|
+
if (hostBehaviorsFor(runtime).cleanupSkillSidecars) {
|
|
11541
11690
|
cleanupCodexSkillMetadataSidecars(_skillsRootDir);
|
|
11542
11691
|
}
|
|
11543
11692
|
|
|
@@ -11563,7 +11712,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11563
11712
|
// Descriptor-driven (ADR-1239 / #2100): folded from `isWindsurf` into
|
|
11564
11713
|
// hostBehaviors.legacyDevinSkillsCleanup (windsurf is the only runtime that
|
|
11565
11714
|
// declares it, so this is byte-parity).
|
|
11566
|
-
if (
|
|
11715
|
+
if (hostBehaviorsFor(runtime).legacyDevinSkillsCleanup && !isGlobal) {
|
|
11567
11716
|
const removedCount = cleanupWindsurfLegacyDevinSkills(process.cwd());
|
|
11568
11717
|
if (removedCount > 0) {
|
|
11569
11718
|
console.log(` ${green}✓${reset} Removed ${removedCount} legacy .devin/skills/gsd-* dir(s) (pre-#1615 Windsurf layout)`);
|
|
@@ -11571,12 +11720,12 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11571
11720
|
}
|
|
11572
11721
|
|
|
11573
11722
|
// Descriptor-driven (#2090): write DESCRIPTION.md for the gsd/ category after layout install
|
|
11574
|
-
if (
|
|
11723
|
+
if (hostBehaviorsFor(runtime).writeCategoryDescription) {
|
|
11575
11724
|
writeHermesCategoryDescription(path.join(targetDir, 'skills', 'gsd'));
|
|
11576
11725
|
}
|
|
11577
11726
|
|
|
11578
11727
|
// Verify installed artifacts and report
|
|
11579
|
-
if (
|
|
11728
|
+
if (hostBehaviorsFor(runtime).reportSkillsCount) {
|
|
11580
11729
|
const hermesSkillsDir = path.join(targetDir, 'skills', 'gsd');
|
|
11581
11730
|
if (fs.existsSync(hermesSkillsDir)) {
|
|
11582
11731
|
// Hermes layout uses prefix: 'gsd-' (#947) — skill dirs have gsd-<stem> names
|
|
@@ -11590,7 +11739,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11590
11739
|
} else {
|
|
11591
11740
|
failures.push('skills/gsd/*');
|
|
11592
11741
|
}
|
|
11593
|
-
} else if (
|
|
11742
|
+
} else if (hostBehaviorsFor(runtime).verificationStyle === 'kimi') {
|
|
11594
11743
|
const skillsDir = path.join(targetDir, 'skills');
|
|
11595
11744
|
const rootAgentPath = path.join(targetDir, 'agents', 'gsd.yaml');
|
|
11596
11745
|
if (fs.existsSync(skillsDir)) {
|
|
@@ -11614,7 +11763,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11614
11763
|
// hostBehaviors.verificationStyle === 'windsurf-workflows' (extends the
|
|
11615
11764
|
// same mechanism the 'kimi' verificationStyle branch above uses; windsurf
|
|
11616
11765
|
// is the only runtime that declares this value, so this is byte-parity).
|
|
11617
|
-
} else if (
|
|
11766
|
+
} else if (hostBehaviorsFor(runtime).verificationStyle === 'windsurf-workflows') {
|
|
11618
11767
|
if (isGlobal) {
|
|
11619
11768
|
console.log(` ${green}✓${reset} Windsurf global install skipped workflow artifacts (workspace-only)`);
|
|
11620
11769
|
} else {
|
|
@@ -11663,7 +11812,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11663
11812
|
// Descriptor-driven commands/ output report (currently CodeBuddy).
|
|
11664
11813
|
// Cursor retired this parallel surface in #2644 because its skills are
|
|
11665
11814
|
// already slash-menu entries as well as model-invocable context.
|
|
11666
|
-
if (
|
|
11815
|
+
if (hostBehaviorsFor(runtime).reportCommandsDir) {
|
|
11667
11816
|
const commandsDir = path.join(targetDir, 'commands');
|
|
11668
11817
|
if (fs.existsSync(commandsDir)) {
|
|
11669
11818
|
const cmdCount = fs.readdirSync(commandsDir)
|
|
@@ -11678,14 +11827,14 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11678
11827
|
}
|
|
11679
11828
|
}
|
|
11680
11829
|
}
|
|
11681
|
-
} else if (
|
|
11830
|
+
} else if (hostBehaviorsFor(runtime).localCommandsViaRules) {
|
|
11682
11831
|
// Cline local install: rules-based only — commands are embedded in .clinerules (generated below).
|
|
11683
11832
|
// No skills/commands directory needed for local installs.
|
|
11684
11833
|
// Global installs are handled above by _isSkillsRuntime (#782).
|
|
11685
11834
|
// Descriptor-driven (ADR-1239 / #2090): folded from `isCline` into
|
|
11686
11835
|
// hostBehaviors.localCommandsViaRules.
|
|
11687
11836
|
console.log(` ${green}✓${reset} Cline: commands will be available via .clinerules`);
|
|
11688
|
-
} else if (
|
|
11837
|
+
} else if (hostBehaviorsFor(runtime).pluginOnlyInstall) {
|
|
11689
11838
|
// pi (ADR-1239 / #2102 Stage 1): plugin-only install — pi's /gsd command is
|
|
11690
11839
|
// registered programmatically by the native extension (pi/gsd.cjs →
|
|
11691
11840
|
// extensions/gsd.js, staged separately below; the dest suffix must be
|
|
@@ -11793,8 +11942,8 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11793
11942
|
// double-stages their plugin file. A runtime like pi, whose artifactLayout is
|
|
11794
11943
|
// intentionally empty for both scopes (`_isSkillsRuntime` is false), still
|
|
11795
11944
|
// needs its declared hostBehaviors.nativePlugin file copied into targetDir.
|
|
11796
|
-
if (!_isSkillsRuntime &&
|
|
11797
|
-
_installNativePluginIfDeclared(runtime, targetDir,
|
|
11945
|
+
if (!_isSkillsRuntime && hostBehaviorsFor(runtime).nativePlugin) {
|
|
11946
|
+
_installNativePluginIfDeclared(runtime, targetDir, hostBehaviorsFor(runtime), src);
|
|
11798
11947
|
}
|
|
11799
11948
|
|
|
11800
11949
|
// #2624: the .gsd-source marker is now written by _writeGsdSourceMarker()
|
|
@@ -11815,7 +11964,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11815
11964
|
// hostBehaviors.installsCommandBodiesForWorkflowDelegation (windsurf is the
|
|
11816
11965
|
// only runtime that declares it, so this is byte-parity — the #1629 fix
|
|
11817
11966
|
// itself is unchanged).
|
|
11818
|
-
if (
|
|
11967
|
+
if (hostBehaviorsFor(runtime).installsCommandBodiesForWorkflowDelegation && !isGlobal) {
|
|
11819
11968
|
const commandsSrc = path.join(src, 'commands', 'gsd');
|
|
11820
11969
|
const commandsDest = path.join(skillDest, 'commands', 'gsd');
|
|
11821
11970
|
if (fs.existsSync(commandsSrc)) {
|
|
@@ -11879,7 +12028,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11879
12028
|
// scopes (programmatic dispatch, no named-dispatch subagent toolkit, no
|
|
11880
12029
|
// host-read markdown surface), so the resolved layout has no `agents` kind
|
|
11881
12030
|
// to stage and the function returns `null` without writing anything.
|
|
11882
|
-
if (
|
|
12031
|
+
if (hostBehaviorsFor(runtime).pluginOnlyInstall) {
|
|
11883
12032
|
console.log(` ${green}✓${reset} pi: no subagent files (programmatic dispatch, no named-dispatch toolkit)`);
|
|
11884
12033
|
} else if (_isSkillsRuntime) {
|
|
11885
12034
|
console.log(` ${dim}↳${reset} Agents installed via descriptor-driven layout (${runtime})`);
|
|
@@ -11914,7 +12063,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11914
12063
|
// advertising the old full agent surface even though the descriptor-driven
|
|
11915
12064
|
// write above already skipped writing the .md files for a minimal-tier
|
|
11916
12065
|
// resolvedProfile. Reuse the same helper that powers `--uninstall`.
|
|
11917
|
-
if (isMinimalMode(_effectiveInstallMode) &&
|
|
12066
|
+
if (isMinimalMode(_effectiveInstallMode) && hostBehaviorsFor(runtime).tomlConfigInstall) {
|
|
11918
12067
|
const codexConfigPath = path.join(targetDir, 'config.toml');
|
|
11919
12068
|
if (fs.existsSync(codexConfigPath)) {
|
|
11920
12069
|
const existing = fs.readFileSync(codexConfigPath, 'utf8');
|
|
@@ -12033,7 +12182,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12033
12182
|
// runtime.hostBehaviors.brandingRewrites. This site only
|
|
12034
12183
|
// rewrites the two brand-name keys (no `.claude/` here — the
|
|
12035
12184
|
// config-dir replace above already handled path fragments).
|
|
12036
|
-
const _b2 =
|
|
12185
|
+
const _b2 = hostBehaviorsFor(runtime).brandingRewrites;
|
|
12037
12186
|
if (_b2) {
|
|
12038
12187
|
content = content.replace(/CLAUDE\.md/g, _b2['CLAUDE.md']);
|
|
12039
12188
|
content = content.replace(/\bClaude Code\b/g, _b2['Claude Code']);
|
|
@@ -12192,7 +12341,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12192
12341
|
// skipSharedHooksInstall:true) — the redundant `&& !isCopilot` was removed.
|
|
12193
12342
|
// #2100: Windsurf's exclusion is likewise descriptor-driven (windsurf declares
|
|
12194
12343
|
// skipSharedHooksInstall:true) — the redundant `&& !isWindsurf` was removed.
|
|
12195
|
-
if (!isCodex &&
|
|
12344
|
+
if (!isCodex && hostBehaviorsFor(runtime).skipSharedHooksInstall !== true) {
|
|
12196
12345
|
if (!installSharedHooksBundle(targetDir)) {
|
|
12197
12346
|
failures.push('hooks');
|
|
12198
12347
|
}
|
|
@@ -12359,7 +12508,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12359
12508
|
}
|
|
12360
12509
|
|
|
12361
12510
|
// Verify no leaked .claude paths in non-Claude runtimes (manifest-scoped)
|
|
12362
|
-
if (!
|
|
12511
|
+
if (!hostBehaviorsFor(runtime).ownsClaudePaths) {
|
|
12363
12512
|
const leakedPaths = [];
|
|
12364
12513
|
// Only scan files that were written by this install (manifest-tracked).
|
|
12365
12514
|
// Scanning the entire targetDir can match user-authored content that
|
|
@@ -12392,7 +12541,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12392
12541
|
// verification backstop. The `_GSD_RUNTIME_ROOT`/`$PREFERRED_CONFIG_DIR`
|
|
12393
12542
|
// fallback chains and prose `.claude` mentions carry no `@~/` prefix and
|
|
12394
12543
|
// are deliberately untouched, as is CHANGELOG.md.
|
|
12395
|
-
if (runtime
|
|
12544
|
+
if (hostBehaviorsFor(runtime).rewriteClaudeAtIncludes) {
|
|
12396
12545
|
for (const relPath of manifestFiles) {
|
|
12397
12546
|
const fileName = path.basename(relPath);
|
|
12398
12547
|
if (!(fileName.endsWith('.md') || fileName.endsWith('.toml'))) continue;
|
|
@@ -13041,7 +13190,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
13041
13190
|
// stop, subagentStart, subagentStop) via runtime-hooks-surface.cts, which reads
|
|
13042
13191
|
// the event list from the descriptor-driven adapter module.
|
|
13043
13192
|
const cursorHookResult = writeCursorHooksJson(targetDir, src, {
|
|
13044
|
-
managedHookEvents:
|
|
13193
|
+
managedHookEvents: hostBehaviorsFor(runtime).managedHookEvents,
|
|
13045
13194
|
});
|
|
13046
13195
|
if (cursorHookResult.changed) {
|
|
13047
13196
|
console.log(` ${green}✓${reset} Configured Cursor lifecycle hooks (sessionStart, postToolUse, preToolUse, stop, subagentStart, subagentStop)`);
|
|
@@ -13110,7 +13259,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
13110
13259
|
}
|
|
13111
13260
|
const kimiHookOpts = { portableHooks: hasPortableHooks, runtime };
|
|
13112
13261
|
const kimiHooksTomlPath = path.join(kimiHooksRoot, 'config.toml');
|
|
13113
|
-
const kimiHooksResult = writeKimiHooksToml(kimiHooksTomlPath, kimiHooksRoot, { hookOpts: kimiHookOpts });
|
|
13262
|
+
const kimiHooksResult = writeKimiHooksToml(kimiHooksTomlPath, kimiHooksRoot, { hookOpts: kimiHookOpts, contextWarnings: runtimeSupportsContextWarnings(runtime) });
|
|
13114
13263
|
configuredEntrypoints.push(...kimiHooksResult.configuredEntrypoints);
|
|
13115
13264
|
if (kimiHooksResult.changed) {
|
|
13116
13265
|
console.log(` ${green}✓${reset} Configured ${kimiHooksResult.entryCount} GSD hook(s) in ${kimiHooksTomlPath}`);
|
|
@@ -13134,9 +13283,9 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
13134
13283
|
// reports success and the user is left with the very breakage the opt-in
|
|
13135
13284
|
// exists to prevent. Verified reproducible before this guard existed.
|
|
13136
13285
|
const kimiInstalledThisRun = selectedRuntimes.includes('kimi');
|
|
13137
|
-
if (hasReclaimKimiLegacy && runtime
|
|
13286
|
+
if (hasReclaimKimiLegacy && hostBehaviorsFor(runtime).reclaimsKimiLegacyHooksRoot && kimiInstalledThisRun) {
|
|
13138
13287
|
console.log(` ${dim}•${reset} Skipped --reclaim-kimi-legacy: this run also installs --kimi, so ${resolveKimiHooksTomlDir({ runtime: 'kimi' })} is a live Kimi CLI install`);
|
|
13139
|
-
} else if (hasReclaimKimiLegacy && runtime
|
|
13288
|
+
} else if (hasReclaimKimiLegacy && hostBehaviorsFor(runtime).reclaimsKimiLegacyHooksRoot) {
|
|
13140
13289
|
const legacyKimiRoot = resolveKimiHooksTomlDir({ runtime: 'kimi' });
|
|
13141
13290
|
// Both roots honor their own env override (KIMI_SHARE_DIR /
|
|
13142
13291
|
// KIMI_CODE_HOME). A user who points both at ONE directory collapses
|
|
@@ -13212,15 +13361,15 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
13212
13361
|
// #2870: the CURRENT scope's settings filename is sourced from the Install
|
|
13213
13362
|
// Scope Module (_installScope.settingsFile, resolveScope's per-scope field)
|
|
13214
13363
|
// instead of indexing _scopedSettings by hand. _scopedSettings itself is
|
|
13215
|
-
// retained unchanged as the #338-privacy fail-safe path:
|
|
13216
|
-
// already degrades to
|
|
13217
|
-
//
|
|
13364
|
+
// retained unchanged as the #338-privacy fail-safe path: hostBehaviorsFor
|
|
13365
|
+
// already degrades to its #338 floor (see runtime-name-policy.cts)
|
|
13366
|
+
// when the registry fails to load, whereas resolveScope's registry
|
|
13218
13367
|
// lookup throws in that same scenario (_installScope is null when it did).
|
|
13219
13368
|
// Falling back to _scopedSettings[_installScopeId] there — and keeping the
|
|
13220
13369
|
// non-local-claude branch's expression untouched — means this is
|
|
13221
13370
|
// byte-identical to the pre-migration computation in every case, including
|
|
13222
13371
|
// the broken-registry fail-safe floor.
|
|
13223
|
-
const _scopedSettings =
|
|
13372
|
+
const _scopedSettings = hostBehaviorsFor(runtime).settingsFileByScope || null;
|
|
13224
13373
|
const _currentScopeSettingsFile = _installScope
|
|
13225
13374
|
? _installScope.settingsFile
|
|
13226
13375
|
: (_scopedSettings ? (_scopedSettings[_installScopeId] ?? null) : null);
|
|
@@ -13346,7 +13495,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
13346
13495
|
// Descriptor-driven (ADR-1239 / #2096): hookPathStyle comes from the
|
|
13347
13496
|
// runtime's hostBehaviors instead of a hardcoded `runtime === 'antigravity'`
|
|
13348
13497
|
// check inside projectLocalHookPrefix.
|
|
13349
|
-
const localPrefix = projectLocalHookPrefix({ runtime, dirName, hookPathStyle:
|
|
13498
|
+
const localPrefix = projectLocalHookPrefix({ runtime, dirName, hookPathStyle: hostBehaviorsFor(runtime).hookPathStyle });
|
|
13350
13499
|
const settingsEntrypoints = [];
|
|
13351
13500
|
const hookOpts = {
|
|
13352
13501
|
portableHooks: hasPortableHooks,
|
|
@@ -13392,6 +13541,9 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
13392
13541
|
const updateCheckCommand = isGlobal
|
|
13393
13542
|
? buildHookCommand(targetDir, 'gsd-check-update.js', hookOpts)
|
|
13394
13543
|
: localCmd('gsd-check-update.js');
|
|
13544
|
+
// #4840: no metrics writer on this host -> no monitor to register (see runtimeSupportsContextWarnings).
|
|
13545
|
+
const contextWarnings = runtimeSupportsContextWarnings(runtime);
|
|
13546
|
+
// The command stays built: the extended lifecycle events (SubagentStop, PreCompact, ...) still route to it.
|
|
13395
13547
|
const contextMonitorCommand = isGlobal
|
|
13396
13548
|
? buildHookCommand(targetDir, 'gsd-context-monitor.js', hookOpts)
|
|
13397
13549
|
: localCmd('gsd-context-monitor.js');
|
|
@@ -13436,6 +13588,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
13436
13588
|
hooksSurface: plan.hooksSurface,
|
|
13437
13589
|
updateCheckCommand,
|
|
13438
13590
|
contextMonitorCommand,
|
|
13591
|
+
contextWarnings,
|
|
13439
13592
|
promptGuardCommand,
|
|
13440
13593
|
readGuardCommand,
|
|
13441
13594
|
readInjectionScannerCommand,
|
|
@@ -13444,12 +13597,15 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
13444
13597
|
localCmd,
|
|
13445
13598
|
localShellCmd,
|
|
13446
13599
|
});
|
|
13600
|
+
if (!contextWarnings) {
|
|
13601
|
+
console.log(` ${dim}↳${reset} ${getRuntimeLabel(runtime)}: agent-facing context warnings are unsupported (no statusline writes the context metrics the monitor reads), so the context monitor hook is not registered`);
|
|
13602
|
+
}
|
|
13447
13603
|
|
|
13448
13604
|
// Compute the update-banner hook command alongside the others so
|
|
13449
13605
|
// installAllRuntimes can register it at finalize time when the user opts
|
|
13450
13606
|
// in (#2795). Computed here (not in finishInstall) so the same buildHookCommand
|
|
13451
13607
|
// / localCmd resolution logic is shared with the other JS hooks.
|
|
13452
|
-
const updateBannerCommand =
|
|
13608
|
+
const updateBannerCommand = hostBehaviorsFor(runtime).skipUpdateBannerCommand
|
|
13453
13609
|
? null
|
|
13454
13610
|
: (isGlobal
|
|
13455
13611
|
? buildHookCommand(targetDir, 'gsd-update-banner.js', hookOpts)
|
|
@@ -13612,7 +13768,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
13612
13768
|
// on plan.finishPermissionWriter === 'kilo' (descriptor-driven), not this flag.
|
|
13613
13769
|
// #2094: isTrae dropped — unused in this function.
|
|
13614
13770
|
// #2095: isKimi dropped — the Kimi "Done!" banner below reads
|
|
13615
|
-
//
|
|
13771
|
+
// hostBehaviorsFor(runtime).doneBannerStyle === 'kimi-agent-file' (descriptor-driven), not this flag.
|
|
13616
13772
|
// #2096: isAntigravity dropped — unused in this function.
|
|
13617
13773
|
// #2098: isCodebuddy dropped — unused in this function.
|
|
13618
13774
|
// #2099: isCopilot dropped — unused in this function.
|
|
@@ -13633,7 +13789,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
13633
13789
|
// runtime — kept for a caller that invokes finishInstall directly.
|
|
13634
13790
|
assertConfiguredEntrypoints(bannerOpts.configuredEntrypoints);
|
|
13635
13791
|
|
|
13636
|
-
if (shouldInstallStatusline && plan.writesSharedSettings && !
|
|
13792
|
+
if (shouldInstallStatusline && plan.writesSharedSettings && !hostBehaviorsFor(runtime).skipSettingsUi) {
|
|
13637
13793
|
if (!isGlobal && !forceStatusline) {
|
|
13638
13794
|
// Local installs skip statusLine by default: repo settings.json takes precedence over
|
|
13639
13795
|
// profile-level settings.json in Claude Code, so writing here would silently clobber
|
|
@@ -13659,7 +13815,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
13659
13815
|
// settings.json hooks block — opencode/kilo/codex/cursor/windsurf/trae/
|
|
13660
13816
|
// cline either lack the surface or use a different config schema.
|
|
13661
13817
|
const { shouldInstallBanner, bannerCommand } = bannerOpts;
|
|
13662
|
-
if (shouldInstallBanner && settings && plan.writesSharedSettings && !
|
|
13818
|
+
if (shouldInstallBanner && settings && plan.writesSharedSettings && !hostBehaviorsFor(runtime).skipSettingsUi) {
|
|
13663
13819
|
if (!bannerCommand) {
|
|
13664
13820
|
console.warn(` ${yellow}⚠${reset} Skipped update banner registration — Node executable path unavailable. See #2979 / #3002.`);
|
|
13665
13821
|
} else {
|
|
@@ -13688,13 +13844,13 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
13688
13844
|
// Merges GSD-owned entries non-destructively (preserves existing user permissions).
|
|
13689
13845
|
// Scoped to Claude only: antigravity/qwen/hermes/codebuddy also write
|
|
13690
13846
|
// settings.json but use different runtimes and do not use these permission strings.
|
|
13691
|
-
if (
|
|
13847
|
+
if (hostBehaviorsFor(runtime).permissionsSchema === 'claude') {
|
|
13692
13848
|
mergeClaudePermissions(settings);
|
|
13693
13849
|
}
|
|
13694
13850
|
|
|
13695
13851
|
// #2097 UPGRADE 3 (transport:mcp): companion MCP server for runtimes that host
|
|
13696
13852
|
// MCP in settings.json (Augment). settings.json is golden-excluded, so no golden change.
|
|
13697
|
-
if (
|
|
13853
|
+
if (hostBehaviorsFor(runtime).mcpCompanion === 'settings-json' && settings && plan.writesSharedSettings) {
|
|
13698
13854
|
mergeGsdMcpServerIntoSettings(settings);
|
|
13699
13855
|
}
|
|
13700
13856
|
|
|
@@ -13712,6 +13868,31 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
13712
13868
|
// Configure OpenCode permissions
|
|
13713
13869
|
if (plan.finishPermissionWriter === 'opencode' && !process.env.GSD_TEST_MODE) {
|
|
13714
13870
|
configureOpencodePermissions(isGlobal, configDir);
|
|
13871
|
+
// ADR-5331: say which OpenCode generation this install will run under. Informational only —
|
|
13872
|
+
// a failed probe never affects the install.
|
|
13873
|
+
try {
|
|
13874
|
+
const ocv = require('../gsd-core/bin/lib/opencode-version.cjs');
|
|
13875
|
+
const gen = ocv.detectOpencodeGeneration();
|
|
13876
|
+
if (gen.generation !== null && typeof configDir === 'string' && configDir) {
|
|
13877
|
+
recordHostGeneration(configDir, { generation: gen.generation, version: gen.version });
|
|
13878
|
+
}
|
|
13879
|
+
if (gen.generation === 2) {
|
|
13880
|
+
console.log(` ${green}✓${reset} OpenCode ${gen.version || '2.x'} detected — GSD loads its OpenCode 2.x plugin entrypoint`);
|
|
13881
|
+
} else if (gen.generation === 1) {
|
|
13882
|
+
console.log(` ${green}✓${reset} OpenCode ${gen.version || '1.x'} detected — GSD loads its OpenCode 1.x plugin entrypoint`);
|
|
13883
|
+
}
|
|
13884
|
+
// ADR-5331 D6 / E8: OpenCode 2.x migrates its data directory one way. Say so before it happens, and say
|
|
13885
|
+
// when 1.x is installed over a data directory 2.x already migrated (1.x cannot open it).
|
|
13886
|
+
const dataDir = ocv.resolveOpencodeDataDir();
|
|
13887
|
+
const notice = ocv.describeDataDirNotice(gen.generation, ocv.classifyOpencodeDatabase(path.join(dataDir, 'opencode.db')), dataDir);
|
|
13888
|
+
if (notice) {
|
|
13889
|
+
console.log(notice.level === 'warn' ? ` ${yellow}⚠${reset} ${notice.text}` : ` ${dim}${notice.text}${reset}`);
|
|
13890
|
+
}
|
|
13891
|
+
// Phase 3 (#5339): OpenCode 1.x is bug-fix-only until the freeze date. The `opencode` runtime only —
|
|
13892
|
+
// this block does not run for Kilo.
|
|
13893
|
+
const deprecation = ocv.describeDeprecationNotice(gen.generation);
|
|
13894
|
+
if (deprecation) console.log(` ${yellow}⚠${reset} ${deprecation}`);
|
|
13895
|
+
} catch { /* detection is advisory */ }
|
|
13715
13896
|
}
|
|
13716
13897
|
|
|
13717
13898
|
// Configure Kilo permissions
|
|
@@ -13734,18 +13915,29 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
13734
13915
|
// generation reads it). This call is idempotent (preserves existing values).
|
|
13735
13916
|
writeNonClaudeDefaults(runtime);
|
|
13736
13917
|
|
|
13737
|
-
// program
|
|
13738
|
-
//
|
|
13739
|
-
//
|
|
13918
|
+
// program is the runtime display label (ADR-1239 Phase B / #1679). The command
|
|
13919
|
+
// is generated from the surface this runtime registered in this install scope
|
|
13920
|
+
// (#5215, ADR-5057 §5 Phase 12) — a runtime that registers no new-project
|
|
13921
|
+
// trigger is told so instead of being sent to a command that does not exist (#4567).
|
|
13740
13922
|
const program = getRuntimeLabel(runtime);
|
|
13741
|
-
const
|
|
13923
|
+
const advertised = resolveAdvertisedNewProject(runtime, isGlobal ? 'global' : 'local');
|
|
13924
|
+
const command = advertised.kind === 'command' ? advertised.command : null;
|
|
13925
|
+
// Host-specific launch/restart steps below stay; only the new-project clause
|
|
13926
|
+
// changes when the runtime registered no such command.
|
|
13927
|
+
const noNewProject = advertised.kind === 'unregistered'
|
|
13928
|
+
? `${program} registers no new-project command in this scope${advertised.nativeCommand ? ` (its native extension registers ${cyan}${advertised.nativeCommand}${reset})` : ''}.`
|
|
13929
|
+
: '';
|
|
13742
13930
|
|
|
13743
13931
|
// Claude Code global installs use the skills/ format (CC 2.1.88+).
|
|
13744
13932
|
// Restart is required for CC to pick up newly-installed skills, and the
|
|
13745
13933
|
// slash-menu surface depends on CC version — so the instruction needs to
|
|
13746
13934
|
// cover both invocation paths to avoid #2957-style "no commands appear".
|
|
13747
|
-
if (
|
|
13748
|
-
console.log(`
|
|
13935
|
+
if (hostBehaviorsFor(runtime).skillsGlobalOnboarding && isGlobal) {
|
|
13936
|
+
console.log(command === null ? `
|
|
13937
|
+
${green}Done!${reset} Restart ${program}. ${noNewProject}
|
|
13938
|
+
|
|
13939
|
+
${cyan}Join the community:${reset} https://discord.gg/mYgfVNfA2r
|
|
13940
|
+
` : `
|
|
13749
13941
|
${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.
|
|
13750
13942
|
|
|
13751
13943
|
${cyan}Join the community:${reset} https://discord.gg/mYgfVNfA2r
|
|
@@ -13753,9 +13945,13 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
13753
13945
|
return;
|
|
13754
13946
|
}
|
|
13755
13947
|
|
|
13756
|
-
if (
|
|
13948
|
+
if (hostBehaviorsFor(runtime).doneBannerStyle === 'kimi-agent-file') {
|
|
13757
13949
|
const agentPath = configDir ? path.join(configDir, 'agents', 'gsd.yaml') : 'agents/gsd.yaml';
|
|
13758
|
-
console.log(`
|
|
13950
|
+
console.log(command === null ? `
|
|
13951
|
+
${green}Done!${reset} Start ${program} with ${cyan}kimi --agent-file ${agentPath}${reset}. ${noNewProject}
|
|
13952
|
+
|
|
13953
|
+
${cyan}Join the community:${reset} https://discord.gg/mYgfVNfA2r
|
|
13954
|
+
` : `
|
|
13759
13955
|
${green}Done!${reset} Start ${program} with ${cyan}kimi --agent-file ${agentPath}${reset}, then run ${cyan}${command}${reset}.
|
|
13760
13956
|
|
|
13761
13957
|
${cyan}Join the community:${reset} https://discord.gg/mYgfVNfA2r
|
|
@@ -13763,7 +13959,11 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
13763
13959
|
return;
|
|
13764
13960
|
}
|
|
13765
13961
|
|
|
13766
|
-
console.log(`
|
|
13962
|
+
console.log(command === null ? `
|
|
13963
|
+
${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})` : ''}.
|
|
13964
|
+
|
|
13965
|
+
${cyan}Join the community:${reset} https://discord.gg/mYgfVNfA2r
|
|
13966
|
+
` : `
|
|
13767
13967
|
${green}Done!${reset} Open a blank directory in ${program} and run ${cyan}${command}${reset}.
|
|
13768
13968
|
|
|
13769
13969
|
${cyan}Join the community:${reset} https://discord.gg/mYgfVNfA2r
|
|
@@ -14713,9 +14913,6 @@ module.exports = {
|
|
|
14713
14913
|
install,
|
|
14714
14914
|
installAllRuntimes,
|
|
14715
14915
|
uninstall,
|
|
14716
|
-
// #2086 — host-behavior resolution + the #338 privacy fail-safe floor (exported for tests)
|
|
14717
|
-
_resolveHostBehaviors,
|
|
14718
|
-
FALLBACK_HOST_BEHAVIORS,
|
|
14719
14916
|
// #3023 — shared hook bundle directory name, descriptor-driven
|
|
14720
14917
|
SHARED_HOOKS_DIR_DEFAULT,
|
|
14721
14918
|
resolveSharedHooksDirName,
|