@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
|
@@ -576,6 +576,14 @@ function buildBakedNodeToken(opts) {
|
|
|
576
576
|
* guarantee), ahead of `command -v node` and the well-known fallback list.
|
|
577
577
|
*/
|
|
578
578
|
const NODE_RUNNER_RESOLVER_HOOK = 'gsd-node-runner.sh';
|
|
579
|
+
/**
|
|
580
|
+
* The one Git Bash policy: `bash` off win32; on win32 GSD_BASH_PATH, then the
|
|
581
|
+
* well-known Git for Windows install locations, never a PATH lookup (which
|
|
582
|
+
* can reach WSL's System32 launcher). null when win32 has no Git Bash.
|
|
583
|
+
* #5082: the test process seam (tests/helpers/process-seam.cjs) resolves
|
|
584
|
+
* `bash` through this too, so hooks and bash-driven tests run under the same
|
|
585
|
+
* bash.
|
|
586
|
+
*/
|
|
579
587
|
function resolveBashExecutable(opts) {
|
|
580
588
|
const platform = (opts && opts.platform) || process.platform;
|
|
581
589
|
if (platform !== 'win32')
|
|
@@ -984,7 +992,16 @@ function buildCodexHookWindowsShimIR(scriptAbsPath, absoluteRunnerToken) {
|
|
|
984
992
|
const scriptQuoted = JSON.stringify(targetAbs);
|
|
985
993
|
const cmdPath = scriptAbsPath.replace(/\.js$/, '.cmd');
|
|
986
994
|
const hookCommand = JSON.stringify(shellCmdProjection.posixNormalize(cmdPath));
|
|
987
|
-
|
|
995
|
+
// #4838: node is resolved when the hook fires. The install-time path dies when a
|
|
996
|
+
// version manager (WinGet) removes the versioned directory on upgrade, and a
|
|
997
|
+
// shim that only knew that path failed silently until the installer re-ran.
|
|
998
|
+
// Order: explicit GSD_NODE_PATH; the install-time path while it still exists
|
|
999
|
+
// (keeps the #3662 minimal-PATH guarantee); node.exe on PATH. `where $PATH:`
|
|
1000
|
+
// searches PATH only, never the current directory, so repo content cannot
|
|
1001
|
+
// become the runner. `%` is doubled so cmd.exe cannot expand a variable out
|
|
1002
|
+
// of an install path.
|
|
1003
|
+
const bakedBat = JSON.stringify(interpreter).slice(1, -1).replace(/%/g, '%%');
|
|
1004
|
+
const scriptBat = scriptQuoted.replace(/%/g, '%%');
|
|
988
1005
|
return {
|
|
989
1006
|
invocation: { interpreter, target: scriptAbsPath },
|
|
990
1007
|
cmdPath,
|
|
@@ -992,7 +1009,16 @@ function buildCodexHookWindowsShimIR(scriptAbsPath, absoluteRunnerToken) {
|
|
|
992
1009
|
eol: { cmd: '\r\n' },
|
|
993
1010
|
passthroughArgs: true,
|
|
994
1011
|
render: {
|
|
995
|
-
cmd: () =>
|
|
1012
|
+
cmd: () => [
|
|
1013
|
+
'@ECHO OFF',
|
|
1014
|
+
'@SETLOCAL',
|
|
1015
|
+
'@SET "GSD_NODE="',
|
|
1016
|
+
'@IF DEFINED GSD_NODE_PATH IF EXIST "%GSD_NODE_PATH%" SET "GSD_NODE=%GSD_NODE_PATH%"',
|
|
1017
|
+
`@IF NOT DEFINED GSD_NODE IF EXIST "${bakedBat}" SET "GSD_NODE=${bakedBat}"`,
|
|
1018
|
+
'@IF NOT DEFINED GSD_NODE FOR /F "delims=" %%N IN (\'where $PATH:node.exe 2^>NUL\') DO @IF NOT DEFINED GSD_NODE SET "GSD_NODE=%%N"',
|
|
1019
|
+
`@"%GSD_NODE%" ${scriptBat} %*`,
|
|
1020
|
+
'',
|
|
1021
|
+
].join('\r\n'),
|
|
996
1022
|
},
|
|
997
1023
|
};
|
|
998
1024
|
}
|
|
@@ -2118,608 +2144,303 @@ function writeCopilotHookConfig(targetDir) {
|
|
|
2118
2144
|
node_fs_1.default.writeFileSync(hookPath, JSON.stringify(buildCopilotHookConfig(), null, 2) + '\n');
|
|
2119
2145
|
return hookPath;
|
|
2120
2146
|
}
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2147
|
+
const BASH_UNAVAILABLE_MESSAGE = 'Bash executable path unavailable (#3393)';
|
|
2148
|
+
// The tables are exported as a contract; freeze every row (and its nested
|
|
2149
|
+
// objects) so no consumer can mutate a matcher or timeout at runtime.
|
|
2150
|
+
function deepFreeze(value) {
|
|
2151
|
+
for (const child of Object.values(value)) {
|
|
2152
|
+
if (child !== null && typeof child === 'object')
|
|
2153
|
+
deepFreeze(child);
|
|
2154
|
+
}
|
|
2155
|
+
return Object.freeze(value);
|
|
2156
|
+
}
|
|
2157
|
+
const SETTINGS_JSON_HOOK_ROWS = deepFreeze([
|
|
2158
|
+
// Guard: only register if the hook file was actually installed (#1754, #1817).
|
|
2159
|
+
{ file: 'gsd-check-update.js', event: 'SessionStart', command: { opts: 'updateCheckCommand' },
|
|
2160
|
+
configuredMessage: 'Configured update check hook', skipLabel: 'update check hook' },
|
|
2161
|
+
{ file: 'gsd-context-monitor.js', event: 'post', matcher: 'Bash|Edit|Write|MultiEdit|Agent|Task', timeout: 10,
|
|
2162
|
+
command: { opts: 'contextMonitorCommand' },
|
|
2163
|
+
configuredMessage: 'Configured context window monitor hook', skipLabel: 'context monitor hook',
|
|
2164
|
+
onPresent: { kind: 'migrate-matcher-timeout', message: 'Updated context monitor hook (added matcher + timeout)' } },
|
|
2165
|
+
{ file: 'gsd-prompt-guard.js', event: 'pre', matcher: 'Write|Edit', timeout: 'blocking',
|
|
2166
|
+
command: { opts: 'promptGuardCommand' },
|
|
2167
|
+
configuredMessage: 'Configured prompt injection guard hook', skipLabel: 'prompt guard hook' },
|
|
2168
|
+
// Read-before-edit guidance (#1628): advisory-only, does not block.
|
|
2169
|
+
{ file: 'gsd-read-guard.js', event: 'pre', matcher: 'Write|Edit', timeout: 5,
|
|
2170
|
+
command: { opts: 'readGuardCommand' },
|
|
2171
|
+
configuredMessage: 'Configured read-before-edit guard hook', skipLabel: 'read guard hook' },
|
|
2172
|
+
// Read-time prompt injection scanning (#2201).
|
|
2173
|
+
{ file: 'gsd-read-injection-scanner.js', event: 'post', matcher: 'Read', timeout: 5,
|
|
2174
|
+
command: { opts: 'readInjectionScannerCommand' },
|
|
2175
|
+
configuredMessage: 'Configured read injection scanner hook', skipLabel: 'read injection scanner hook' },
|
|
2176
|
+
// Community hooks — registered on install but opt-in at runtime via .planning/config.json.
|
|
2177
|
+
{ file: 'gsd-workflow-guard.js', event: 'pre', matcher: 'Bash|Edit|Write|MultiEdit', timeout: 'blocking',
|
|
2178
|
+
command: { build: 'js' },
|
|
2179
|
+
configuredMessage: 'Configured workflow guard hook (opt-in via hooks.workflow_guard)', skipLabel: 'workflow guard hook',
|
|
2180
|
+
onPresent: { kind: 'repair-matcher', message: 'Updated workflow guard hook matcher' } },
|
|
2181
|
+
// Worktree absolute-path safety (#260).
|
|
2182
|
+
{ file: 'gsd-worktree-path-guard.js', event: 'pre', matcher: 'Write|Edit|MultiEdit', timeout: 'blocking',
|
|
2183
|
+
command: { build: 'js' },
|
|
2184
|
+
configuredMessage: 'Configured worktree path guard hook', skipLabel: 'worktree path guard hook' },
|
|
2185
|
+
// Agent-dispatch isolation (#3045); matcher widened to Agent|Task (#3045 MAJOR 1).
|
|
2186
|
+
{ file: 'gsd-agent-isolation-guard.js', event: 'pre', matcher: 'Agent|Task', timeout: 'blocking',
|
|
2187
|
+
command: { build: 'js' },
|
|
2188
|
+
configuredMessage: 'Configured agent isolation dispatch guard hook', skipLabel: 'agent isolation guard hook' },
|
|
2189
|
+
// Catastrophic-shrink protection (#2255, fix 3 of #973).
|
|
2190
|
+
{ file: 'gsd-write-guard.js', event: 'pre', matcher: 'Write', timeout: 'blocking',
|
|
2191
|
+
command: { build: 'js' },
|
|
2192
|
+
configuredMessage: 'Configured write guard hook (catastrophic-shrink protection)', skipLabel: 'write guard hook' },
|
|
2193
|
+
// Secret-file read protection (#4221).
|
|
2194
|
+
{ file: 'gsd-secret-read-guard.js', event: 'pre', matcher: 'Read|Grep|Bash', timeout: 'blocking',
|
|
2195
|
+
command: { build: 'js' },
|
|
2196
|
+
configuredMessage: 'Configured secret read guard hook (.env / .secrets read protection)', skipLabel: 'secret read guard hook' },
|
|
2197
|
+
// The four `.sh` rows: their built commands also feed the #3329 reconcile.
|
|
2198
|
+
{ file: 'gsd-validate-commit.sh', event: 'pre', matcher: 'Bash', timeout: 'blocking',
|
|
2199
|
+
command: { build: 'sh' },
|
|
2200
|
+
configuredMessage: 'Configured commit validation hook (opt-in via config)', skipLabel: 'commit validation hook',
|
|
2201
|
+
noCommandMessage: BASH_UNAVAILABLE_MESSAGE },
|
|
2202
|
+
{ file: 'gsd-graphify-update.sh', event: 'post', matcher: 'Bash', timeout: 5,
|
|
2203
|
+
command: { build: 'sh' },
|
|
2204
|
+
configuredMessage: 'Configured graphify auto-update hook (opt-in via graphify.auto_update)', skipLabel: 'graphify auto-update hook',
|
|
2205
|
+
noCommandMessage: BASH_UNAVAILABLE_MESSAGE },
|
|
2206
|
+
{ file: 'gsd-session-state.sh', event: 'SessionStart',
|
|
2207
|
+
command: { build: 'sh' },
|
|
2208
|
+
configuredMessage: 'Configured session state orientation hook (opt-in via config)', skipLabel: 'session state hook',
|
|
2209
|
+
noCommandMessage: BASH_UNAVAILABLE_MESSAGE },
|
|
2210
|
+
{ file: 'gsd-phase-boundary.sh', event: 'post', matcher: 'Write|Edit', timeout: 5,
|
|
2211
|
+
command: { build: 'sh' },
|
|
2212
|
+
configuredMessage: 'Configured phase boundary detection hook (opt-in via config)', skipLabel: 'phase boundary hook',
|
|
2213
|
+
noCommandMessage: BASH_UNAVAILABLE_MESSAGE },
|
|
2214
|
+
]);
|
|
2215
|
+
// Extended lifecycle events are descriptor-gated (`extendedHookEvents`): a row
|
|
2216
|
+
// registers only for runtimes whose descriptor lists its event. SubagentStart
|
|
2217
|
+
// is qwen/codebuddy-only today; the Before/After Agent/Model rows are an inert
|
|
2218
|
+
// seam (no runtime declares them) that re-activates if one does (#776, #1928).
|
|
2219
|
+
// FileChanged hot-reloads the gsd config on .planning/config.json edits.
|
|
2220
|
+
function contextMonitorRow(event, runtimeLabelSuffix) {
|
|
2221
|
+
return {
|
|
2222
|
+
event,
|
|
2223
|
+
file: 'gsd-context-monitor.js',
|
|
2224
|
+
command: { opts: 'contextMonitorCommand' },
|
|
2225
|
+
timeout: 10,
|
|
2226
|
+
configuredMessage: `Configured ${event} context monitor hook`,
|
|
2227
|
+
...(runtimeLabelSuffix ? { runtimeLabelSuffix } : {}),
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
const SETTINGS_JSON_EXTENDED_ROWS = deepFreeze([
|
|
2231
|
+
contextMonitorRow('SubagentStop', true),
|
|
2232
|
+
contextMonitorRow('Stop', true),
|
|
2233
|
+
contextMonitorRow('PreCompact', true),
|
|
2234
|
+
contextMonitorRow('SubagentStart', true),
|
|
2235
|
+
contextMonitorRow('BeforeAgent', false),
|
|
2236
|
+
contextMonitorRow('AfterAgent', false),
|
|
2237
|
+
contextMonitorRow('BeforeModel', false),
|
|
2238
|
+
{ event: 'FileChanged', file: 'gsd-config-reload.js', command: { opts: 'configReloadCommand' }, matcher: 'config.json', timeout: 8,
|
|
2239
|
+
configuredMessage: 'Configured FileChanged config-reload hook (Claude Code)',
|
|
2240
|
+
noCommandMessage: 'Node executable path unavailable' },
|
|
2241
|
+
]);
|
|
2242
|
+
function hookStem(file) {
|
|
2243
|
+
return file.replace(/\.(?:js|sh)$/, '');
|
|
2244
|
+
}
|
|
2245
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access,
|
|
2246
|
+
@typescript-eslint/no-unsafe-call,
|
|
2247
|
+
@typescript-eslint/no-unsafe-assignment,
|
|
2248
|
+
@typescript-eslint/no-explicit-any */
|
|
2249
|
+
function groupReferencesHook(entry, stem) {
|
|
2250
|
+
return Boolean(entry.hooks && entry.hooks.some((h) => referencesHook(h, stem)));
|
|
2251
|
+
}
|
|
2252
|
+
function resolveRowTimeout(timeout) {
|
|
2253
|
+
return timeout === 'blocking' ? BLOCKING_GUARD_TIMEOUT_S : timeout;
|
|
2254
|
+
}
|
|
2255
|
+
function resolveRowCommand(row, opts) {
|
|
2256
|
+
if ('opts' in row.command)
|
|
2257
|
+
return opts[row.command.opts];
|
|
2258
|
+
if (opts.isGlobal)
|
|
2259
|
+
return buildHookCommand(opts.targetDir, row.file, opts.hookOpts);
|
|
2260
|
+
return row.command.build === 'sh' ? opts.localShellCmd(row.file) : opts.localCmd(row.file);
|
|
2261
|
+
}
|
|
2262
|
+
function reconcilePresentHook(row, entries, existing) {
|
|
2263
|
+
const present = row.onPresent;
|
|
2264
|
+
if (!present)
|
|
2265
|
+
return;
|
|
2266
|
+
if (present.kind === 'repair-matcher') {
|
|
2267
|
+
if (existing.matcher !== row.matcher) {
|
|
2268
|
+
existing.matcher = row.matcher;
|
|
2269
|
+
console.log(` ${green}✓${reset} ${present.message}`);
|
|
2270
|
+
}
|
|
2271
|
+
return;
|
|
2272
|
+
}
|
|
2273
|
+
// migrate-matcher-timeout: add the matcher and timeout an older installer omitted.
|
|
2274
|
+
const stem = hookStem(row.file);
|
|
2275
|
+
const timeout = resolveRowTimeout(row.timeout);
|
|
2276
|
+
for (const entry of entries) {
|
|
2277
|
+
if (!groupReferencesHook(entry, stem))
|
|
2278
|
+
continue;
|
|
2279
|
+
let migrated = false;
|
|
2280
|
+
if (!entry.matcher) {
|
|
2281
|
+
entry.matcher = row.matcher;
|
|
2282
|
+
migrated = true;
|
|
2283
|
+
}
|
|
2284
|
+
for (const h of entry.hooks) {
|
|
2285
|
+
if (referencesHook(h, stem) && !h.timeout) {
|
|
2286
|
+
h.timeout = timeout;
|
|
2287
|
+
migrated = true;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
if (migrated)
|
|
2291
|
+
console.log(` ${green}✓${reset} ${present.message}`);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
function pushHookGroup(entries, matcher, command, timeout) {
|
|
2295
|
+
const hook = { type: 'command', command };
|
|
2296
|
+
if (timeout !== undefined)
|
|
2297
|
+
hook['timeout'] = timeout;
|
|
2298
|
+
entries.push(matcher === undefined ? { hooks: [hook] } : { matcher, hooks: [hook] });
|
|
2299
|
+
}
|
|
2300
|
+
/** #4840: the hook that reads the statusline's metrics bridge file. */
|
|
2301
|
+
const CONTEXT_MONITOR_HOOK = 'gsd-context-monitor.js';
|
|
2302
|
+
/**
|
|
2303
|
+
* #4840: remove GSD-managed registrations of `stem` from one event's groups,
|
|
2304
|
+
* leaving user-authored hooks (even in the same group) untouched. Returns
|
|
2305
|
+
* whether anything was removed. A group left with no hooks is dropped.
|
|
2306
|
+
*/
|
|
2307
|
+
function stripManagedHookRegistrations(entries, stem) {
|
|
2308
|
+
let removed = false;
|
|
2309
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
2310
|
+
const entry = entries[i];
|
|
2311
|
+
if (!entry || !Array.isArray(entry.hooks))
|
|
2312
|
+
continue;
|
|
2313
|
+
const kept = entry.hooks.filter((h) => {
|
|
2314
|
+
const managed = referencesHook(h, stem)
|
|
2315
|
+
&& isManagedHookCommand(h.command, { surface: 'settings-json', includeLegacyAliases: true });
|
|
2316
|
+
if (managed)
|
|
2317
|
+
removed = true;
|
|
2318
|
+
return !managed;
|
|
2319
|
+
});
|
|
2320
|
+
if (kept.length === entry.hooks.length)
|
|
2321
|
+
continue;
|
|
2322
|
+
if (kept.length === 0)
|
|
2323
|
+
entries.splice(i, 1);
|
|
2324
|
+
else
|
|
2325
|
+
entry.hooks = kept;
|
|
2326
|
+
}
|
|
2327
|
+
return removed;
|
|
2328
|
+
}
|
|
2329
|
+
function applySettingsJsonHookTables(settings, opts, tables) {
|
|
2330
|
+
const { runtime, postToolEvent, hookEvents, extendedHookEvents, hooksSurface, targetDir, hookOpts } = opts;
|
|
2331
|
+
// ADR-857 phase 5f-3: extended hook events are driven by the registry
|
|
2128
2332
|
// descriptor field rather than hardcoded runtime-name checks.
|
|
2129
2333
|
const extendedEvents = Array.isArray(extendedHookEvents) ? extendedHookEvents : [];
|
|
2130
2334
|
// ADR-857 phase 5g drive 3: hook-skip guard is driven by the hooksSurface
|
|
2131
2335
|
// descriptor field. Only runtimes with hooksSurface === 'settings-json'
|
|
2132
|
-
// register settings.json hooks;
|
|
2133
|
-
// (opencode, kilo) are skipped. Equivalence: hooksSurface !== 'none' iff
|
|
2134
|
-
// the old !isOpencode && !isKilo check.
|
|
2336
|
+
// register settings.json hooks; 'none' (opencode, kilo) is skipped.
|
|
2135
2337
|
// #2095: kimi's hooksSurface is 'kimi-hooks-toml' — it registers hooks into
|
|
2136
|
-
// its own native config.toml via writeKimiHooksToml
|
|
2137
|
-
//
|
|
2138
|
-
//
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
if (
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
// and the migration pass right here raises existing managed entries.
|
|
2155
|
-
const BLOCKING_GUARD_TIMEOUT_S = 120;
|
|
2156
|
-
const blockingGuardNames = [
|
|
2157
|
-
'gsd-prompt-guard',
|
|
2158
|
-
'gsd-workflow-guard',
|
|
2159
|
-
'gsd-worktree-path-guard',
|
|
2160
|
-
'gsd-agent-isolation-guard',
|
|
2161
|
-
'gsd-write-guard',
|
|
2162
|
-
'gsd-secret-read-guard',
|
|
2163
|
-
'gsd-validate-commit',
|
|
2164
|
-
];
|
|
2165
|
-
for (const entries of Object.values(settings.hooks)) {
|
|
2166
|
-
if (!Array.isArray(entries))
|
|
2338
|
+
// its own native config.toml via writeKimiHooksToml and never writes
|
|
2339
|
+
// settings.json, so this guard must skip it too, or the console would log
|
|
2340
|
+
// "Configured ..." for a settings object finishInstall() never persists.
|
|
2341
|
+
if (hooksSurface === 'none' || hooksSurface === 'kimi-hooks-toml')
|
|
2342
|
+
return;
|
|
2343
|
+
if (!settings.hooks) {
|
|
2344
|
+
settings.hooks = {};
|
|
2345
|
+
}
|
|
2346
|
+
if (!settings.hooks.SessionStart) {
|
|
2347
|
+
settings.hooks.SessionStart = [];
|
|
2348
|
+
}
|
|
2349
|
+
// #3981: raise existing managed blocking-guard entries still on the old
|
|
2350
|
+
// 5 s budget to BLOCKING_GUARD_TIMEOUT_S (module-level; rationale there).
|
|
2351
|
+
for (const entries of Object.values(settings.hooks)) {
|
|
2352
|
+
if (!Array.isArray(entries))
|
|
2353
|
+
continue;
|
|
2354
|
+
for (const entry of entries) {
|
|
2355
|
+
if (!entry || !Array.isArray(entry.hooks))
|
|
2167
2356
|
continue;
|
|
2168
|
-
for (const
|
|
2169
|
-
if (
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
if (blockingGuardNames.some((name) => referencesHook(h, name)) &&
|
|
2173
|
-
h.timeout === 5) {
|
|
2174
|
-
h.timeout = BLOCKING_GUARD_TIMEOUT_S;
|
|
2175
|
-
}
|
|
2357
|
+
for (const h of entry.hooks) {
|
|
2358
|
+
if (BLOCKING_GUARD_NAMES.some((name) => referencesHook(h, name)) &&
|
|
2359
|
+
h.timeout === 5) {
|
|
2360
|
+
h.timeout = BLOCKING_GUARD_TIMEOUT_S;
|
|
2176
2361
|
}
|
|
2177
2362
|
}
|
|
2178
2363
|
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
else if (!hasGsdUpdateHook && !node_fs_1.default.existsSync(checkUpdateFile)) {
|
|
2197
|
-
console.warn(` ${yellow}⚠${reset} Skipped update check hook — gsd-check-update.js not found at target`);
|
|
2198
|
-
}
|
|
2199
|
-
// Configure post-tool hook for context window monitoring
|
|
2200
|
-
if (!settings.hooks[postToolEvent]) {
|
|
2201
|
-
settings.hooks[postToolEvent] = [];
|
|
2202
|
-
}
|
|
2203
|
-
const hasContextMonitorHook = settings.hooks[postToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-context-monitor')));
|
|
2204
|
-
const contextMonitorFile = node_path_1.default.join(targetDir, 'hooks', 'gsd-context-monitor.js');
|
|
2205
|
-
if (!hasContextMonitorHook && node_fs_1.default.existsSync(contextMonitorFile) && contextMonitorCommand) {
|
|
2206
|
-
settings.hooks[postToolEvent].push({
|
|
2207
|
-
matcher: 'Bash|Edit|Write|MultiEdit|Agent|Task',
|
|
2208
|
-
hooks: [
|
|
2209
|
-
{
|
|
2210
|
-
type: 'command',
|
|
2211
|
-
command: contextMonitorCommand,
|
|
2212
|
-
timeout: 10
|
|
2213
|
-
}
|
|
2214
|
-
]
|
|
2215
|
-
});
|
|
2216
|
-
console.log(` ${green}✓${reset} Configured context window monitor hook`);
|
|
2364
|
+
}
|
|
2365
|
+
// hookEvents='gemini' → BeforeTool/AfterTool; all others → PreToolUse/PostToolUse.
|
|
2366
|
+
const preToolEvent = hookEvents === 'gemini' ? 'BeforeTool' : 'PreToolUse';
|
|
2367
|
+
const eventKey = (event) => event === 'SessionStart' ? 'SessionStart' : event === 'post' ? postToolEvent : preToolEvent;
|
|
2368
|
+
// Built `.sh` commands, in row order, feed the #3329 reconcile below.
|
|
2369
|
+
const shellCommands = {};
|
|
2370
|
+
for (const row of tables.rows) {
|
|
2371
|
+
const key = eventKey(row.event);
|
|
2372
|
+
if (!settings.hooks[key]) {
|
|
2373
|
+
settings.hooks[key] = [];
|
|
2374
|
+
}
|
|
2375
|
+
const entries = settings.hooks[key];
|
|
2376
|
+
if (row.file === CONTEXT_MONITOR_HOOK && opts.contextWarnings === false) {
|
|
2377
|
+
if (stripManagedHookRegistrations(entries, hookStem(row.file))) {
|
|
2378
|
+
console.log(` ${green}✓${reset} Removed stale context monitor hook (${_capabilityTitle(runtime)} has no context metrics writer, #4840)`);
|
|
2379
|
+
}
|
|
2380
|
+
continue;
|
|
2217
2381
|
}
|
|
2218
|
-
|
|
2219
|
-
|
|
2382
|
+
const command = resolveRowCommand(row, opts);
|
|
2383
|
+
if ('build' in row.command && row.command.build === 'sh')
|
|
2384
|
+
shellCommands[row.file] = command;
|
|
2385
|
+
const stem = hookStem(row.file);
|
|
2386
|
+
const existing = entries.find((entry) => groupReferencesHook(entry, stem));
|
|
2387
|
+
if (existing) {
|
|
2388
|
+
reconcilePresentHook(row, entries, existing);
|
|
2389
|
+
continue;
|
|
2220
2390
|
}
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
for (const entry of settings.hooks[postToolEvent]) {
|
|
2224
|
-
if (entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-context-monitor'))) {
|
|
2225
|
-
let migrated = false;
|
|
2226
|
-
if (!entry.matcher) {
|
|
2227
|
-
entry.matcher = 'Bash|Edit|Write|MultiEdit|Agent|Task';
|
|
2228
|
-
migrated = true;
|
|
2229
|
-
}
|
|
2230
|
-
for (const h of entry.hooks) {
|
|
2231
|
-
if (referencesHook(h, 'gsd-context-monitor') && !h.timeout) {
|
|
2232
|
-
h.timeout = 10;
|
|
2233
|
-
migrated = true;
|
|
2234
|
-
}
|
|
2235
|
-
}
|
|
2236
|
-
if (migrated) {
|
|
2237
|
-
console.log(` ${green}✓${reset} Updated context monitor hook (added matcher + timeout)`);
|
|
2238
|
-
}
|
|
2239
|
-
}
|
|
2240
|
-
}
|
|
2391
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.join(targetDir, 'hooks', row.file))) {
|
|
2392
|
+
console.warn(` ${yellow}⚠${reset} Skipped ${row.skipLabel} — ${row.file} not found at target`);
|
|
2241
2393
|
}
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
// Equivalence: hookEvents='gemini' iff runtime===antigravity (same as old check).
|
|
2246
|
-
const preToolEvent = hookEvents === 'gemini' ? 'BeforeTool' : 'PreToolUse';
|
|
2247
|
-
if (!settings.hooks[preToolEvent]) {
|
|
2248
|
-
settings.hooks[preToolEvent] = [];
|
|
2249
|
-
}
|
|
2250
|
-
const hasPromptGuardHook = settings.hooks[preToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-prompt-guard')));
|
|
2251
|
-
const promptGuardFile = node_path_1.default.join(targetDir, 'hooks', 'gsd-prompt-guard.js');
|
|
2252
|
-
if (!hasPromptGuardHook && node_fs_1.default.existsSync(promptGuardFile) && promptGuardCommand) {
|
|
2253
|
-
settings.hooks[preToolEvent].push({
|
|
2254
|
-
matcher: 'Write|Edit',
|
|
2255
|
-
hooks: [
|
|
2256
|
-
{
|
|
2257
|
-
type: 'command',
|
|
2258
|
-
command: promptGuardCommand,
|
|
2259
|
-
timeout: BLOCKING_GUARD_TIMEOUT_S
|
|
2260
|
-
}
|
|
2261
|
-
]
|
|
2262
|
-
});
|
|
2263
|
-
console.log(` ${green}✓${reset} Configured prompt injection guard hook`);
|
|
2264
|
-
}
|
|
2265
|
-
else if (!hasPromptGuardHook && !node_fs_1.default.existsSync(promptGuardFile)) {
|
|
2266
|
-
console.warn(` ${yellow}⚠${reset} Skipped prompt guard hook — gsd-prompt-guard.js not found at target`);
|
|
2267
|
-
}
|
|
2268
|
-
// Configure PreToolUse hook for read-before-edit guidance (#1628)
|
|
2269
|
-
// Prevents infinite retry loops when non-Claude models attempt to edit
|
|
2270
|
-
// files without reading them first. Advisory-only — does not block.
|
|
2271
|
-
const hasReadGuardHook = settings.hooks[preToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-read-guard')));
|
|
2272
|
-
const readGuardFile = node_path_1.default.join(targetDir, 'hooks', 'gsd-read-guard.js');
|
|
2273
|
-
if (!hasReadGuardHook && node_fs_1.default.existsSync(readGuardFile) && readGuardCommand) {
|
|
2274
|
-
settings.hooks[preToolEvent].push({
|
|
2275
|
-
matcher: 'Write|Edit',
|
|
2276
|
-
hooks: [
|
|
2277
|
-
{
|
|
2278
|
-
type: 'command',
|
|
2279
|
-
command: readGuardCommand,
|
|
2280
|
-
timeout: 5
|
|
2281
|
-
}
|
|
2282
|
-
]
|
|
2283
|
-
});
|
|
2284
|
-
console.log(` ${green}✓${reset} Configured read-before-edit guard hook`);
|
|
2285
|
-
}
|
|
2286
|
-
else if (!hasReadGuardHook && !node_fs_1.default.existsSync(readGuardFile)) {
|
|
2287
|
-
console.warn(` ${yellow}⚠${reset} Skipped read guard hook — gsd-read-guard.js not found at target`);
|
|
2288
|
-
}
|
|
2289
|
-
// Configure PostToolUse hook for read-time prompt injection scanning (#2201)
|
|
2290
|
-
// Scans content returned by the Read tool for injection patterns, including
|
|
2291
|
-
// summarisation-specific patterns that survive context compression.
|
|
2292
|
-
const hasReadInjectionScannerHook = settings.hooks[postToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-read-injection-scanner')));
|
|
2293
|
-
const readInjectionScannerFile = node_path_1.default.join(targetDir, 'hooks', 'gsd-read-injection-scanner.js');
|
|
2294
|
-
if (!hasReadInjectionScannerHook && node_fs_1.default.existsSync(readInjectionScannerFile) && readInjectionScannerCommand) {
|
|
2295
|
-
settings.hooks[postToolEvent].push({
|
|
2296
|
-
matcher: 'Read',
|
|
2297
|
-
hooks: [
|
|
2298
|
-
{
|
|
2299
|
-
type: 'command',
|
|
2300
|
-
command: readInjectionScannerCommand,
|
|
2301
|
-
timeout: 5
|
|
2302
|
-
}
|
|
2303
|
-
]
|
|
2304
|
-
});
|
|
2305
|
-
console.log(` ${green}✓${reset} Configured read injection scanner hook`);
|
|
2306
|
-
}
|
|
2307
|
-
else if (!hasReadInjectionScannerHook && !node_fs_1.default.existsSync(readInjectionScannerFile)) {
|
|
2308
|
-
console.warn(` ${yellow}⚠${reset} Skipped read injection scanner hook — gsd-read-injection-scanner.js not found at target`);
|
|
2309
|
-
}
|
|
2310
|
-
// Community hooks — registered on install but opt-in at runtime.
|
|
2311
|
-
// Each hook checks .planning/config.json for hooks.community: true
|
|
2312
|
-
// and exits silently (no-op) if not enabled. This lets users enable
|
|
2313
|
-
// them per-project by adding: "hooks": { "community": true }
|
|
2314
|
-
// Configure workflow guard hook (opt-in via hooks.workflow_guard: true)
|
|
2315
|
-
// Detects file edits outside GSD workflow context and advises using
|
|
2316
|
-
// /gsd-quick or /gsd-fast for state-tracked changes. Also hard-blocks
|
|
2317
|
-
// unsafe Bash commands that violate worktree-agent isolation.
|
|
2318
|
-
const workflowGuardCommand = isGlobal
|
|
2319
|
-
? buildHookCommand(targetDir, 'gsd-workflow-guard.js', hookOpts)
|
|
2320
|
-
: localCmd('gsd-workflow-guard.js');
|
|
2321
|
-
const workflowGuardMatcher = 'Bash|Edit|Write|MultiEdit';
|
|
2322
|
-
const workflowGuardHookEntry = settings.hooks[preToolEvent].find((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-workflow-guard')));
|
|
2323
|
-
const hasWorkflowGuardHook = Boolean(workflowGuardHookEntry);
|
|
2324
|
-
const workflowGuardFile = node_path_1.default.join(targetDir, 'hooks', 'gsd-workflow-guard.js');
|
|
2325
|
-
if (hasWorkflowGuardHook && workflowGuardHookEntry.matcher !== workflowGuardMatcher) {
|
|
2326
|
-
workflowGuardHookEntry.matcher = workflowGuardMatcher;
|
|
2327
|
-
console.log(` ${green}✓${reset} Updated workflow guard hook matcher`);
|
|
2328
|
-
}
|
|
2329
|
-
else if (!hasWorkflowGuardHook && node_fs_1.default.existsSync(workflowGuardFile) && workflowGuardCommand) {
|
|
2330
|
-
settings.hooks[preToolEvent].push({
|
|
2331
|
-
matcher: workflowGuardMatcher,
|
|
2332
|
-
hooks: [
|
|
2333
|
-
{
|
|
2334
|
-
type: 'command',
|
|
2335
|
-
command: workflowGuardCommand,
|
|
2336
|
-
timeout: BLOCKING_GUARD_TIMEOUT_S
|
|
2337
|
-
}
|
|
2338
|
-
]
|
|
2339
|
-
});
|
|
2340
|
-
console.log(` ${green}✓${reset} Configured workflow guard hook (opt-in via hooks.workflow_guard)`);
|
|
2341
|
-
}
|
|
2342
|
-
else if (!hasWorkflowGuardHook && !node_fs_1.default.existsSync(workflowGuardFile)) {
|
|
2343
|
-
console.warn(` ${yellow}⚠${reset} Skipped workflow guard hook — gsd-workflow-guard.js not found at target`);
|
|
2344
|
-
}
|
|
2345
|
-
// Configure PreToolUse hook for worktree absolute-path safety (#260)
|
|
2346
|
-
// Hard-blocks Edit/Write/MultiEdit tool calls with absolute paths that resolve
|
|
2347
|
-
// outside the current worktree root. Prevents executor agents from
|
|
2348
|
-
// accidentally writing to the main checkout when running in isolation="worktree".
|
|
2349
|
-
const worktreePathGuardCommand = isGlobal
|
|
2350
|
-
? buildHookCommand(targetDir, 'gsd-worktree-path-guard.js', hookOpts)
|
|
2351
|
-
: localCmd('gsd-worktree-path-guard.js');
|
|
2352
|
-
const hasWorktreePathGuardHook = settings.hooks[preToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-worktree-path-guard')));
|
|
2353
|
-
const worktreePathGuardFile = node_path_1.default.join(targetDir, 'hooks', 'gsd-worktree-path-guard.js');
|
|
2354
|
-
if (!hasWorktreePathGuardHook && node_fs_1.default.existsSync(worktreePathGuardFile) && worktreePathGuardCommand) {
|
|
2355
|
-
settings.hooks[preToolEvent].push({
|
|
2356
|
-
matcher: 'Write|Edit|MultiEdit',
|
|
2357
|
-
hooks: [
|
|
2358
|
-
{
|
|
2359
|
-
type: 'command',
|
|
2360
|
-
command: worktreePathGuardCommand,
|
|
2361
|
-
timeout: BLOCKING_GUARD_TIMEOUT_S
|
|
2362
|
-
}
|
|
2363
|
-
]
|
|
2364
|
-
});
|
|
2365
|
-
console.log(` ${green}✓${reset} Configured worktree path guard hook`);
|
|
2366
|
-
}
|
|
2367
|
-
else if (!hasWorktreePathGuardHook && !node_fs_1.default.existsSync(worktreePathGuardFile)) {
|
|
2368
|
-
console.warn(` ${yellow}⚠${reset} Skipped worktree path guard hook — gsd-worktree-path-guard.js not found at target`);
|
|
2369
|
-
}
|
|
2370
|
-
// Configure PreToolUse hook for Agent-dispatch isolation (#3045)
|
|
2371
|
-
// Hard-blocks an executor Agent() dispatch (subagent_type="gsd-executor")
|
|
2372
|
-
// missing its harness isolation parameter when this project's resolved
|
|
2373
|
-
// dispatch isolation is harness-worktree. Prevents the executor from
|
|
2374
|
-
// silently running and committing in the primary checkout when the
|
|
2375
|
-
// model-authored dispatch omits isolation="worktree".
|
|
2376
|
-
const agentIsolationGuardCommand = isGlobal
|
|
2377
|
-
? buildHookCommand(targetDir, 'gsd-agent-isolation-guard.js', hookOpts)
|
|
2378
|
-
: localCmd('gsd-agent-isolation-guard.js');
|
|
2379
|
-
const hasAgentIsolationGuardHook = settings.hooks[preToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-agent-isolation-guard')));
|
|
2380
|
-
const agentIsolationGuardFile = node_path_1.default.join(targetDir, 'hooks', 'gsd-agent-isolation-guard.js');
|
|
2381
|
-
if (!hasAgentIsolationGuardHook && node_fs_1.default.existsSync(agentIsolationGuardFile) && agentIsolationGuardCommand) {
|
|
2382
|
-
settings.hooks[preToolEvent].push({
|
|
2383
|
-
// #3045 MAJOR 1: widened from "Agent"-only — hooks.json's own
|
|
2384
|
-
// PostToolUse precedent (context-monitor) already hedges both names,
|
|
2385
|
-
// and the hook itself now accepts tool_name "Task" too.
|
|
2386
|
-
matcher: 'Agent|Task',
|
|
2387
|
-
hooks: [
|
|
2388
|
-
{
|
|
2389
|
-
type: 'command',
|
|
2390
|
-
command: agentIsolationGuardCommand,
|
|
2391
|
-
timeout: BLOCKING_GUARD_TIMEOUT_S
|
|
2392
|
-
}
|
|
2393
|
-
]
|
|
2394
|
-
});
|
|
2395
|
-
console.log(` ${green}✓${reset} Configured agent isolation dispatch guard hook`);
|
|
2396
|
-
}
|
|
2397
|
-
else if (!hasAgentIsolationGuardHook && !node_fs_1.default.existsSync(agentIsolationGuardFile)) {
|
|
2398
|
-
console.warn(` ${yellow}⚠${reset} Skipped agent isolation guard hook — gsd-agent-isolation-guard.js not found at target`);
|
|
2399
|
-
}
|
|
2400
|
-
// Configure PreToolUse hook for catastrophic-shrink protection (#2255, fix 3 of #973)
|
|
2401
|
-
// Hard-blocks a whole-file Write that collapses a curated .planning/ artifact
|
|
2402
|
-
// (ROADMAP.md, milestone roadmaps, STATE.md) far below its on-disk size.
|
|
2403
|
-
// Escape hatches (both named in the block message): the single-use
|
|
2404
|
-
// sentinel .planning/.gsd-allow-shrink (workflow steps — a per-step env
|
|
2405
|
-
// cannot reach a hook) and GSD_ALLOW_PLANNING_SHRINK=1 (interactive).
|
|
2406
|
-
const writeGuardCommand = isGlobal
|
|
2407
|
-
? buildHookCommand(targetDir, 'gsd-write-guard.js', hookOpts)
|
|
2408
|
-
: localCmd('gsd-write-guard.js');
|
|
2409
|
-
const hasWriteGuardHook = settings.hooks[preToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-write-guard')));
|
|
2410
|
-
const writeGuardFile = node_path_1.default.join(targetDir, 'hooks', 'gsd-write-guard.js');
|
|
2411
|
-
if (!hasWriteGuardHook && node_fs_1.default.existsSync(writeGuardFile) && writeGuardCommand) {
|
|
2412
|
-
settings.hooks[preToolEvent].push({
|
|
2413
|
-
matcher: 'Write',
|
|
2414
|
-
hooks: [
|
|
2415
|
-
{
|
|
2416
|
-
type: 'command',
|
|
2417
|
-
command: writeGuardCommand,
|
|
2418
|
-
timeout: BLOCKING_GUARD_TIMEOUT_S
|
|
2419
|
-
}
|
|
2420
|
-
]
|
|
2421
|
-
});
|
|
2422
|
-
console.log(` ${green}✓${reset} Configured write guard hook (catastrophic-shrink protection)`);
|
|
2423
|
-
}
|
|
2424
|
-
else if (!hasWriteGuardHook && !node_fs_1.default.existsSync(writeGuardFile)) {
|
|
2425
|
-
console.warn(` ${yellow}⚠${reset} Skipped write guard hook — gsd-write-guard.js not found at target`);
|
|
2426
|
-
}
|
|
2427
|
-
// Configure PreToolUse hook for secret-file read protection (#4221).
|
|
2428
|
-
// Hard-blocks Read/Grep/Bash reads of .env, .env.<suffix> and .secrets.
|
|
2429
|
-
// Replaces the Read(.env*) permission deny rules the installer used to
|
|
2430
|
-
// write (#768): on Claude Code >= 2.1.259 ANY Read() deny rule makes every
|
|
2431
|
-
// `cd DIR && grep …` compound prompt for approval, even in auto mode; a
|
|
2432
|
-
// hook denial is not a permission rule and never arms that check.
|
|
2433
|
-
const secretReadGuardCommand = isGlobal
|
|
2434
|
-
? buildHookCommand(targetDir, 'gsd-secret-read-guard.js', hookOpts)
|
|
2435
|
-
: localCmd('gsd-secret-read-guard.js');
|
|
2436
|
-
const hasSecretReadGuardHook = settings.hooks[preToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-secret-read-guard')));
|
|
2437
|
-
const secretReadGuardFile = node_path_1.default.join(targetDir, 'hooks', 'gsd-secret-read-guard.js');
|
|
2438
|
-
if (!hasSecretReadGuardHook && node_fs_1.default.existsSync(secretReadGuardFile) && secretReadGuardCommand) {
|
|
2439
|
-
settings.hooks[preToolEvent].push({
|
|
2440
|
-
matcher: 'Read|Grep|Bash',
|
|
2441
|
-
hooks: [
|
|
2442
|
-
{
|
|
2443
|
-
type: 'command',
|
|
2444
|
-
command: secretReadGuardCommand,
|
|
2445
|
-
timeout: BLOCKING_GUARD_TIMEOUT_S
|
|
2446
|
-
}
|
|
2447
|
-
]
|
|
2448
|
-
});
|
|
2449
|
-
console.log(` ${green}✓${reset} Configured secret read guard hook (.env / .secrets read protection)`);
|
|
2450
|
-
}
|
|
2451
|
-
else if (!hasSecretReadGuardHook && !node_fs_1.default.existsSync(secretReadGuardFile)) {
|
|
2452
|
-
console.warn(` ${yellow}⚠${reset} Skipped secret read guard hook — gsd-secret-read-guard.js not found at target`);
|
|
2453
|
-
}
|
|
2454
|
-
// Configure commit validation hook (Conventional Commits enforcement, opt-in)
|
|
2455
|
-
const validateCommitCommand = isGlobal
|
|
2456
|
-
? buildHookCommand(targetDir, 'gsd-validate-commit.sh', hookOpts)
|
|
2457
|
-
: localShellCmd('gsd-validate-commit.sh');
|
|
2458
|
-
const hasValidateCommitHook = settings.hooks[preToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-validate-commit')));
|
|
2459
|
-
// Guard: only register if the .sh file was actually installed. If the npm package
|
|
2460
|
-
// omitted the file (as happened in v1.32.0, bug #1817), registering a missing hook
|
|
2461
|
-
// causes a hook error on every Bash tool invocation.
|
|
2462
|
-
const validateCommitFile = node_path_1.default.join(targetDir, 'hooks', 'gsd-validate-commit.sh');
|
|
2463
|
-
if (!hasValidateCommitHook && node_fs_1.default.existsSync(validateCommitFile) && validateCommitCommand) {
|
|
2464
|
-
settings.hooks[preToolEvent].push({
|
|
2465
|
-
matcher: 'Bash',
|
|
2466
|
-
hooks: [
|
|
2467
|
-
{
|
|
2468
|
-
type: 'command',
|
|
2469
|
-
command: validateCommitCommand,
|
|
2470
|
-
timeout: BLOCKING_GUARD_TIMEOUT_S
|
|
2471
|
-
}
|
|
2472
|
-
]
|
|
2473
|
-
});
|
|
2474
|
-
console.log(` ${green}✓${reset} Configured commit validation hook (opt-in via config)`);
|
|
2475
|
-
}
|
|
2476
|
-
else if (!hasValidateCommitHook && !node_fs_1.default.existsSync(validateCommitFile)) {
|
|
2477
|
-
console.warn(` ${yellow}⚠${reset} Skipped commit validation hook — gsd-validate-commit.sh not found at target`);
|
|
2478
|
-
}
|
|
2479
|
-
else if (!hasValidateCommitHook && !validateCommitCommand) {
|
|
2480
|
-
console.warn(` ${yellow}⚠${reset} Skipped commit validation hook — Bash executable path unavailable (#3393)`);
|
|
2481
|
-
}
|
|
2482
|
-
// Configure graphify auto-update hook (opt-in via graphify.auto_update; default false, #3347).
|
|
2483
|
-
// PostToolUse Bash matcher — fires after git commit/merge/pull/rebase --continue/cherry-pick
|
|
2484
|
-
// on the default branch, dispatches `graphify update .` in a detached subprocess. No-op unless
|
|
2485
|
-
// .planning/config.json has BOTH graphify.enabled=true AND graphify.auto_update=true.
|
|
2486
|
-
const graphifyUpdateCommand = isGlobal
|
|
2487
|
-
? buildHookCommand(targetDir, 'gsd-graphify-update.sh', hookOpts)
|
|
2488
|
-
: localShellCmd('gsd-graphify-update.sh');
|
|
2489
|
-
const hasGraphifyUpdateHook = settings.hooks[postToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-graphify-update')));
|
|
2490
|
-
const graphifyUpdateFile = node_path_1.default.join(targetDir, 'hooks', 'gsd-graphify-update.sh');
|
|
2491
|
-
if (!hasGraphifyUpdateHook && node_fs_1.default.existsSync(graphifyUpdateFile) && graphifyUpdateCommand) {
|
|
2492
|
-
settings.hooks[postToolEvent].push({
|
|
2493
|
-
matcher: 'Bash',
|
|
2494
|
-
hooks: [
|
|
2495
|
-
{
|
|
2496
|
-
type: 'command',
|
|
2497
|
-
command: graphifyUpdateCommand,
|
|
2498
|
-
timeout: 5
|
|
2499
|
-
}
|
|
2500
|
-
]
|
|
2501
|
-
});
|
|
2502
|
-
console.log(` ${green}✓${reset} Configured graphify auto-update hook (opt-in via graphify.auto_update)`);
|
|
2503
|
-
}
|
|
2504
|
-
else if (!hasGraphifyUpdateHook && !node_fs_1.default.existsSync(graphifyUpdateFile)) {
|
|
2505
|
-
console.warn(` ${yellow}⚠${reset} Skipped graphify auto-update hook — gsd-graphify-update.sh not found at target`);
|
|
2506
|
-
}
|
|
2507
|
-
else if (!hasGraphifyUpdateHook && !graphifyUpdateCommand) {
|
|
2508
|
-
console.warn(` ${yellow}⚠${reset} Skipped graphify auto-update hook — Bash executable path unavailable (#3393)`);
|
|
2509
|
-
}
|
|
2510
|
-
// Configure session state orientation hook (opt-in)
|
|
2511
|
-
const sessionStateCommand = isGlobal
|
|
2512
|
-
? buildHookCommand(targetDir, 'gsd-session-state.sh', hookOpts)
|
|
2513
|
-
: localShellCmd('gsd-session-state.sh');
|
|
2514
|
-
const hasSessionStateHook = settings.hooks.SessionStart.some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-session-state')));
|
|
2515
|
-
const sessionStateFile = node_path_1.default.join(targetDir, 'hooks', 'gsd-session-state.sh');
|
|
2516
|
-
if (!hasSessionStateHook && node_fs_1.default.existsSync(sessionStateFile) && sessionStateCommand) {
|
|
2517
|
-
settings.hooks.SessionStart.push({
|
|
2518
|
-
hooks: [
|
|
2519
|
-
{
|
|
2520
|
-
type: 'command',
|
|
2521
|
-
command: sessionStateCommand
|
|
2522
|
-
}
|
|
2523
|
-
]
|
|
2524
|
-
});
|
|
2525
|
-
console.log(` ${green}✓${reset} Configured session state orientation hook (opt-in via config)`);
|
|
2526
|
-
}
|
|
2527
|
-
else if (!hasSessionStateHook && !node_fs_1.default.existsSync(sessionStateFile)) {
|
|
2528
|
-
console.warn(` ${yellow}⚠${reset} Skipped session state hook — gsd-session-state.sh not found at target`);
|
|
2529
|
-
}
|
|
2530
|
-
else if (!hasSessionStateHook && !sessionStateCommand) {
|
|
2531
|
-
console.warn(` ${yellow}⚠${reset} Skipped session state hook — Bash executable path unavailable (#3393)`);
|
|
2532
|
-
}
|
|
2533
|
-
// Configure phase boundary detection hook (opt-in)
|
|
2534
|
-
const phaseBoundaryCommand = isGlobal
|
|
2535
|
-
? buildHookCommand(targetDir, 'gsd-phase-boundary.sh', hookOpts)
|
|
2536
|
-
: localShellCmd('gsd-phase-boundary.sh');
|
|
2537
|
-
const hasPhaseBoundaryHook = settings.hooks[postToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-phase-boundary')));
|
|
2538
|
-
const phaseBoundaryFile = node_path_1.default.join(targetDir, 'hooks', 'gsd-phase-boundary.sh');
|
|
2539
|
-
if (!hasPhaseBoundaryHook && node_fs_1.default.existsSync(phaseBoundaryFile) && phaseBoundaryCommand) {
|
|
2540
|
-
settings.hooks[postToolEvent].push({
|
|
2541
|
-
matcher: 'Write|Edit',
|
|
2542
|
-
hooks: [
|
|
2543
|
-
{
|
|
2544
|
-
type: 'command',
|
|
2545
|
-
command: phaseBoundaryCommand,
|
|
2546
|
-
timeout: 5
|
|
2547
|
-
}
|
|
2548
|
-
]
|
|
2549
|
-
});
|
|
2550
|
-
console.log(` ${green}✓${reset} Configured phase boundary detection hook (opt-in via config)`);
|
|
2551
|
-
}
|
|
2552
|
-
else if (!hasPhaseBoundaryHook && !node_fs_1.default.existsSync(phaseBoundaryFile)) {
|
|
2553
|
-
console.warn(` ${yellow}⚠${reset} Skipped phase boundary hook — gsd-phase-boundary.sh not found at target`);
|
|
2554
|
-
}
|
|
2555
|
-
else if (!hasPhaseBoundaryHook && !phaseBoundaryCommand) {
|
|
2556
|
-
console.warn(` ${yellow}⚠${reset} Skipped phase boundary hook — Bash executable path unavailable (#3393)`);
|
|
2557
|
-
}
|
|
2558
|
-
// #3329: the four `.sh` sites above register only-if-absent, so an entry
|
|
2559
|
-
// registered by an older installer keeps its old command forever —
|
|
2560
|
-
// /gsd-update (which re-invokes the installer) never re-derived it. On
|
|
2561
|
-
// Claude/win32 that left the pre-#580/#3393 bash-runner-prefixed commands
|
|
2562
|
-
// in settings.json indefinitely. Reconcile existing managed `.sh` entries
|
|
2563
|
-
// to the command this install would generate today. Inert wherever the
|
|
2564
|
-
// bash runner is still the correct shape; scoped to exact managed
|
|
2565
|
-
// basenames so user-authored hooks are never touched.
|
|
2566
|
-
if (reconcileManagedShellHookCommands(settings, {
|
|
2567
|
-
'gsd-validate-commit.sh': validateCommitCommand,
|
|
2568
|
-
'gsd-graphify-update.sh': graphifyUpdateCommand,
|
|
2569
|
-
'gsd-session-state.sh': sessionStateCommand,
|
|
2570
|
-
'gsd-phase-boundary.sh': phaseBoundaryCommand,
|
|
2571
|
-
}, { platform: hookOpts.platform, runtime })) {
|
|
2572
|
-
console.log(` ${green}✓${reset} Reconciled managed .sh hook commands to current format (#3329)`);
|
|
2573
|
-
}
|
|
2574
|
-
// ── Extended hook events: SubagentStop / Stop / PreCompact / SubagentStart
|
|
2575
|
-
// (#788 + #770 + #2092) ────────────────────────────────────────────────
|
|
2576
|
-
// Claude Code (since #770) and Qwen Code (since #788) both support the
|
|
2577
|
-
// SubagentStop / Stop / PreCompact lifecycle events. Qwen Code additionally
|
|
2578
|
-
// supports SubagentStart (#2092 Phase B, Upgrade 2). Wire gsd-context-
|
|
2579
|
-
// monitor so agents get context-headroom warnings at subagent start,
|
|
2580
|
-
// subagent completion, model stop, and pre-compaction (the most critical
|
|
2581
|
-
// moment to surface headroom info).
|
|
2582
|
-
//
|
|
2583
|
-
// SubagentStart — subagent lifecycle start (context headroom tracking;
|
|
2584
|
-
// qwen-only today — no other runtime declares it in
|
|
2585
|
-
// extendedHookEvents)
|
|
2586
|
-
// SubagentStop — subagent lifecycle completion (context headroom tracking)
|
|
2587
|
-
// Stop — model stop / final-response moment (context headroom)
|
|
2588
|
-
// PreCompact — fires before conversation compaction (most critical
|
|
2589
|
-
// moment to surface context headroom warnings)
|
|
2590
|
-
//
|
|
2591
|
-
// Note: UserPromptSubmit is NOT wired here. That event carries the raw
|
|
2592
|
-
// user prompt text, not a tool invocation, so gsd-prompt-guard (which
|
|
2593
|
-
// exits unless tool_name is Write/Edit) would be a silent no-op. A
|
|
2594
|
-
// dedicated handler for UserPromptSubmit is deferred to a follow-on issue.
|
|
2595
|
-
// SubagentStart, SubagentStop, Stop, PreCompact — route through the context monitor.
|
|
2596
|
-
// Guard is descriptor-driven: only events present in extendedEvents are wired,
|
|
2597
|
-
// so this loop is a no-op for every runtime that doesn't list SubagentStart.
|
|
2598
|
-
{
|
|
2599
|
-
// Descriptor-driven (ADR-1239 / #2092): folded from a hardcoded
|
|
2600
|
-
// `runtime === 'qwen' ? ... : ...` ternary into a capability-title
|
|
2601
|
-
// lookup (see _capabilityTitle above).
|
|
2602
|
-
const runtimeLabel = _capabilityTitle(runtime);
|
|
2603
|
-
for (const event of ['SubagentStop', 'Stop', 'PreCompact', 'SubagentStart']) {
|
|
2604
|
-
if (!extendedEvents.includes(event))
|
|
2605
|
-
continue;
|
|
2606
|
-
if (!settings.hooks[event]) {
|
|
2607
|
-
settings.hooks[event] = [];
|
|
2608
|
-
}
|
|
2609
|
-
const alreadyHasContextMonitor = settings.hooks[event].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-context-monitor')));
|
|
2610
|
-
if (!alreadyHasContextMonitor && node_fs_1.default.existsSync(contextMonitorFile) && contextMonitorCommand) {
|
|
2611
|
-
settings.hooks[event].push({
|
|
2612
|
-
hooks: [
|
|
2613
|
-
{
|
|
2614
|
-
type: 'command',
|
|
2615
|
-
command: contextMonitorCommand,
|
|
2616
|
-
timeout: 10
|
|
2617
|
-
}
|
|
2618
|
-
]
|
|
2619
|
-
});
|
|
2620
|
-
console.log(` ${green}✓${reset} Configured ${event} context monitor hook (${runtimeLabel})`);
|
|
2621
|
-
}
|
|
2622
|
-
else if (!alreadyHasContextMonitor && !node_fs_1.default.existsSync(contextMonitorFile)) {
|
|
2623
|
-
console.warn(` ${yellow}⚠${reset} Skipped ${event} hook — gsd-context-monitor.js not found at target`);
|
|
2624
|
-
}
|
|
2625
|
-
}
|
|
2394
|
+
else if (command) {
|
|
2395
|
+
pushHookGroup(entries, row.matcher, command, resolveRowTimeout(row.timeout));
|
|
2396
|
+
console.log(` ${green}✓${reset} ${row.configuredMessage}`);
|
|
2626
2397
|
}
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
// The Gemini-3-backend dialect exposes several hook events beyond
|
|
2630
|
-
// BeforeTool/AfterTool. These were added for the now-removed Gemini CLI
|
|
2631
|
-
// runtime (#776). No currently supported runtime declares them —
|
|
2632
|
-
// Antigravity's descriptor carries `extendedHookEvents: []` — so this loop
|
|
2633
|
-
// is an inert, descriptor-driven seam: it no-ops for every present runtime
|
|
2634
|
-
// and re-activates automatically if a future runtime declares any of them.
|
|
2635
|
-
// Three high-value events would be wired here:
|
|
2636
|
-
//
|
|
2637
|
-
// BeforeAgent — fires after user submits a prompt, before the agent
|
|
2638
|
-
// plans. Wire gsd-context-monitor for context headroom
|
|
2639
|
-
// awareness at prompt time.
|
|
2640
|
-
// AfterAgent — fires once per turn after the model generates its final
|
|
2641
|
-
// response. Wire gsd-context-monitor to track headroom
|
|
2642
|
-
// after each agent turn completes.
|
|
2643
|
-
// BeforeModel — fires before each LLM call (per-turn, not per-session).
|
|
2644
|
-
// Wire gsd-context-monitor for per-turn context injection
|
|
2645
|
-
// — more precise than session-start-only injection.
|
|
2646
|
-
//
|
|
2647
|
-
// All three reuse gsd-context-monitor.js — no new hook files needed.
|
|
2648
|
-
// The `decision:"deny"` retry capability of AfterAgent is intentionally
|
|
2649
|
-
// left to the hook script to implement when triggered (gsd-context-monitor
|
|
2650
|
-
// exits 0 / advisory-only today; an active quality gate is a follow-on).
|
|
2651
|
-
//
|
|
2652
|
-
// Note: BeforeToolSelection is NOT wired. That event does not map to a
|
|
2653
|
-
// gsd hook use case at this time; deferred to a follow-on issue.
|
|
2654
|
-
//
|
|
2655
|
-
// Guard is now descriptor-driven: only events present in extendedEvents are wired.
|
|
2656
|
-
for (const extendedEvent of ['BeforeAgent', 'AfterAgent', 'BeforeModel']) {
|
|
2657
|
-
if (!extendedEvents.includes(extendedEvent))
|
|
2658
|
-
continue;
|
|
2659
|
-
if (!Array.isArray(settings.hooks[extendedEvent])) {
|
|
2660
|
-
settings.hooks[extendedEvent] = [];
|
|
2661
|
-
}
|
|
2662
|
-
const alreadyHasContextMonitor = settings.hooks[extendedEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-context-monitor')));
|
|
2663
|
-
if (!alreadyHasContextMonitor && node_fs_1.default.existsSync(contextMonitorFile) && contextMonitorCommand) {
|
|
2664
|
-
settings.hooks[extendedEvent].push({
|
|
2665
|
-
hooks: [
|
|
2666
|
-
{
|
|
2667
|
-
type: 'command',
|
|
2668
|
-
command: contextMonitorCommand,
|
|
2669
|
-
timeout: 10
|
|
2670
|
-
}
|
|
2671
|
-
]
|
|
2672
|
-
});
|
|
2673
|
-
console.log(` ${green}✓${reset} Configured ${extendedEvent} context monitor hook`);
|
|
2674
|
-
}
|
|
2675
|
-
else if (!alreadyHasContextMonitor && !node_fs_1.default.existsSync(contextMonitorFile)) {
|
|
2676
|
-
console.warn(` ${yellow}⚠${reset} Skipped ${extendedEvent} hook — gsd-context-monitor.js not found at target`);
|
|
2677
|
-
}
|
|
2398
|
+
else if (row.noCommandMessage) {
|
|
2399
|
+
console.warn(` ${yellow}⚠${reset} Skipped ${row.skipLabel} — ${row.noCommandMessage}`);
|
|
2678
2400
|
}
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
}
|
|
2713
|
-
else if (!alreadyHasConfigReload && !configReloadCommand) {
|
|
2714
|
-
console.warn(` ${yellow}⚠${reset} Skipped FileChanged hook — Node executable path unavailable`);
|
|
2715
|
-
}
|
|
2401
|
+
}
|
|
2402
|
+
// #3329: `.sh` rows register only-if-absent, so an entry registered by an
|
|
2403
|
+
// older installer keeps its old command forever — /gsd-update (which
|
|
2404
|
+
// re-invokes the installer) never re-derived it. On Claude/win32 that left
|
|
2405
|
+
// the pre-#580/#3393 bash-runner-prefixed commands in settings.json
|
|
2406
|
+
// indefinitely. Reconcile existing managed `.sh` entries to the command this
|
|
2407
|
+
// install would generate today. Inert wherever the bash runner is still the
|
|
2408
|
+
// correct shape; scoped to exact managed basenames so user-authored hooks
|
|
2409
|
+
// are never touched.
|
|
2410
|
+
if (reconcileManagedShellHookCommands(settings, shellCommands, { platform: hookOpts.platform, runtime })) {
|
|
2411
|
+
console.log(` ${green}✓${reset} Reconciled managed .sh hook commands to current format (#3329)`);
|
|
2412
|
+
}
|
|
2413
|
+
// Descriptor-driven (ADR-1239 / #2092): the runtime's capability title.
|
|
2414
|
+
const runtimeLabel = _capabilityTitle(runtime);
|
|
2415
|
+
for (const row of tables.extendedRows) {
|
|
2416
|
+
if (!extendedEvents.includes(row.event))
|
|
2417
|
+
continue;
|
|
2418
|
+
if (!Array.isArray(settings.hooks[row.event])) {
|
|
2419
|
+
settings.hooks[row.event] = [];
|
|
2420
|
+
}
|
|
2421
|
+
const entries = settings.hooks[row.event];
|
|
2422
|
+
if (entries.some((entry) => groupReferencesHook(entry, hookStem(row.file))))
|
|
2423
|
+
continue;
|
|
2424
|
+
const command = opts[row.command.opts];
|
|
2425
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.join(targetDir, 'hooks', row.file))) {
|
|
2426
|
+
console.warn(` ${yellow}⚠${reset} Skipped ${row.event} hook — ${row.file} not found at target`);
|
|
2427
|
+
}
|
|
2428
|
+
else if (command) {
|
|
2429
|
+
pushHookGroup(entries, row.matcher, command, row.timeout);
|
|
2430
|
+
console.log(` ${green}✓${reset} ${row.configuredMessage}${row.runtimeLabelSuffix ? ` (${runtimeLabel})` : ''}`);
|
|
2431
|
+
}
|
|
2432
|
+
else if (row.noCommandMessage) {
|
|
2433
|
+
console.warn(` ${yellow}⚠${reset} Skipped ${row.event} hook — ${row.noCommandMessage}`);
|
|
2716
2434
|
}
|
|
2717
|
-
// ── end FileChanged hook ────────────────────────────────────────────────────
|
|
2718
2435
|
}
|
|
2719
|
-
/* eslint-enable @typescript-eslint/no-unsafe-member-access,
|
|
2720
|
-
@typescript-eslint/no-unsafe-call,
|
|
2721
|
-
@typescript-eslint/no-unsafe-assignment */
|
|
2722
2436
|
}
|
|
2437
|
+
function applySettingsJsonHooks(settings, opts) {
|
|
2438
|
+
applySettingsJsonHookTables(settings, opts, { rows: SETTINGS_JSON_HOOK_ROWS, extendedRows: SETTINGS_JSON_EXTENDED_ROWS });
|
|
2439
|
+
}
|
|
2440
|
+
/* eslint-enable @typescript-eslint/no-unsafe-member-access,
|
|
2441
|
+
@typescript-eslint/no-unsafe-call,
|
|
2442
|
+
@typescript-eslint/no-unsafe-assignment,
|
|
2443
|
+
@typescript-eslint/no-explicit-any */
|
|
2723
2444
|
// ---------------------------------------------------------------------------
|
|
2724
2445
|
// Kimi hooks.toml (#2095 EoS/kimi Upgrade 1 — native hook bus)
|
|
2725
2446
|
//
|
|
@@ -2792,15 +2513,16 @@ function buildKimiHooksTomlBlock(targetDir, opts) {
|
|
|
2792
2513
|
{ event: 'SessionStart', command: cmd('gsd-check-update.js') },
|
|
2793
2514
|
{ event: 'SessionStart', command: cmd('gsd-session-state.sh') },
|
|
2794
2515
|
// PreToolUse
|
|
2795
|
-
{ event: 'PreToolUse', command: cmd('gsd-prompt-guard.js'), matcher: 'WriteFile|StrReplaceFile', timeout:
|
|
2516
|
+
{ event: 'PreToolUse', command: cmd('gsd-prompt-guard.js'), matcher: 'WriteFile|StrReplaceFile', timeout: kimiBlockingGuardTimeout('gsd-prompt-guard.js') },
|
|
2796
2517
|
{ event: 'PreToolUse', command: cmd('gsd-read-guard.js'), matcher: 'WriteFile|StrReplaceFile', timeout: 5 },
|
|
2797
|
-
{ event: 'PreToolUse', command: cmd('gsd-worktree-path-guard.js'), matcher: 'WriteFile|StrReplaceFile', timeout:
|
|
2798
|
-
{ event: 'PreToolUse', command: cmd('gsd-write-guard.js'), matcher: 'WriteFile', timeout:
|
|
2799
|
-
{ event: 'PreToolUse', command: cmd('gsd-secret-read-guard.js'), matcher: 'ReadFile|Grep|Shell', timeout:
|
|
2800
|
-
{ event: 'PreToolUse', command: cmd('gsd-workflow-guard.js'), matcher: 'Shell|WriteFile|StrReplaceFile', timeout:
|
|
2801
|
-
{ event: 'PreToolUse', command: cmd('gsd-validate-commit.sh'), matcher: 'Shell', timeout:
|
|
2518
|
+
{ event: 'PreToolUse', command: cmd('gsd-worktree-path-guard.js'), matcher: 'WriteFile|StrReplaceFile', timeout: kimiBlockingGuardTimeout('gsd-worktree-path-guard.js') },
|
|
2519
|
+
{ event: 'PreToolUse', command: cmd('gsd-write-guard.js'), matcher: 'WriteFile', timeout: kimiBlockingGuardTimeout('gsd-write-guard.js') },
|
|
2520
|
+
{ event: 'PreToolUse', command: cmd('gsd-secret-read-guard.js'), matcher: 'ReadFile|Grep|Shell', timeout: kimiBlockingGuardTimeout('gsd-secret-read-guard.js') },
|
|
2521
|
+
{ event: 'PreToolUse', command: cmd('gsd-workflow-guard.js'), matcher: 'Shell|WriteFile|StrReplaceFile', timeout: kimiBlockingGuardTimeout('gsd-workflow-guard.js') },
|
|
2522
|
+
{ event: 'PreToolUse', command: cmd('gsd-validate-commit.sh'), matcher: 'Shell', timeout: kimiBlockingGuardTimeout('gsd-validate-commit.sh') },
|
|
2802
2523
|
// PostToolUse
|
|
2803
|
-
|
|
2524
|
+
// #4840: no metrics writer on kimi, so the warning registration is skipped (a block from an earlier install is replaced wholesale).
|
|
2525
|
+
{ event: 'PostToolUse', command: opts.contextWarnings === false ? null : cmd('gsd-context-monitor.js'), timeout: 10 },
|
|
2804
2526
|
{ event: 'PostToolUse', command: cmd('gsd-phase-boundary.sh'), matcher: 'WriteFile|StrReplaceFile', timeout: 5 },
|
|
2805
2527
|
{ event: 'PostToolUse', command: cmd('gsd-read-injection-scanner.js'), matcher: 'ReadFile', timeout: 5 },
|
|
2806
2528
|
{ event: 'PostToolUse', command: cmd('gsd-graphify-update.sh'), matcher: 'Shell', timeout: 5 },
|
|
@@ -2873,6 +2595,7 @@ function stripKimiHooksTomlBlock(content) {
|
|
|
2873
2595
|
function writeKimiHooksToml(configPath, targetDir, opts) {
|
|
2874
2596
|
const configuredEntrypoints = [];
|
|
2875
2597
|
const trackedOpts = {
|
|
2598
|
+
contextWarnings: opts.contextWarnings,
|
|
2876
2599
|
hookOpts: {
|
|
2877
2600
|
...opts.hookOpts,
|
|
2878
2601
|
configPath,
|
|
@@ -2924,6 +2647,45 @@ function removeKimiHooksToml(configPath) {
|
|
|
2924
2647
|
return { changed: true };
|
|
2925
2648
|
}
|
|
2926
2649
|
// ---------------------------------------------------------------------------
|
|
2650
|
+
// Blocking-guard host budget (#3981, #5180)
|
|
2651
|
+
//
|
|
2652
|
+
// Claude Code treats a timed-out hook as NON-blocking — the tool call
|
|
2653
|
+
// continues through the normal permission flow. The blocking PreToolUse
|
|
2654
|
+
// guards therefore need a budget a host stall cannot exceed, not one sized to
|
|
2655
|
+
// the hook's own ~0.1 s runtime. Observed stalls reached 84.3 s; 120 s is the
|
|
2656
|
+
// top of the issue's prescribed 60–120 range and returns every observed
|
|
2657
|
+
// verdict. ONE constant and ONE name list, shared by every registration
|
|
2658
|
+
// surface that carries a host timeout (settings.json install + migration,
|
|
2659
|
+
// Kimi config.toml) so a future change cannot diverge between them.
|
|
2660
|
+
// ---------------------------------------------------------------------------
|
|
2661
|
+
const BLOCKING_GUARD_TIMEOUT_S = 120;
|
|
2662
|
+
const BLOCKING_GUARD_NAMES = [
|
|
2663
|
+
'gsd-prompt-guard',
|
|
2664
|
+
'gsd-workflow-guard',
|
|
2665
|
+
'gsd-worktree-path-guard',
|
|
2666
|
+
'gsd-agent-isolation-guard',
|
|
2667
|
+
'gsd-write-guard',
|
|
2668
|
+
'gsd-secret-read-guard',
|
|
2669
|
+
'gsd-validate-commit',
|
|
2670
|
+
];
|
|
2671
|
+
// Kimi registers every blocking guard EXCEPT the ones named here: Claude's
|
|
2672
|
+
// Agent|Task (subagent-dispatch) matcher segment has no confirmed Kimi tool
|
|
2673
|
+
// name, so gsd-agent-isolation-guard has nothing to attach to there (see the
|
|
2674
|
+
// buildKimiHooksTomlBlock doc). The divergence from the installer's set is
|
|
2675
|
+
// intentional and explicit, not an accident of two hand-kept lists.
|
|
2676
|
+
const KIMI_UNREGISTERED_BLOCKING_GUARDS = ['gsd-agent-isolation-guard'];
|
|
2677
|
+
const KIMI_BLOCKING_GUARD_NAMES = BLOCKING_GUARD_NAMES.filter((name) => !KIMI_UNREGISTERED_BLOCKING_GUARDS.includes(name));
|
|
2678
|
+
// Host budget for a Kimi blocking-guard entry, by script file name. Throws for
|
|
2679
|
+
// a script outside KIMI_BLOCKING_GUARD_NAMES so a spec cannot claim the
|
|
2680
|
+
// blocking budget for a hook the shared list does not name.
|
|
2681
|
+
function kimiBlockingGuardTimeout(scriptFile) {
|
|
2682
|
+
const name = scriptFile.replace(/\.(?:js|sh)$/, '');
|
|
2683
|
+
if (!KIMI_BLOCKING_GUARD_NAMES.includes(name)) {
|
|
2684
|
+
throw new Error(`runtime-hooks-surface: ${scriptFile} is not a Kimi blocking guard (KIMI_BLOCKING_GUARD_NAMES)`);
|
|
2685
|
+
}
|
|
2686
|
+
return BLOCKING_GUARD_TIMEOUT_S;
|
|
2687
|
+
}
|
|
2688
|
+
// ---------------------------------------------------------------------------
|
|
2927
2689
|
// referencesHook
|
|
2928
2690
|
//
|
|
2929
2691
|
// Pure predicate — checks whether a hook entry object references a managed
|
|
@@ -3071,11 +2833,19 @@ module.exports = {
|
|
|
3071
2833
|
removeKimiHooksToml,
|
|
3072
2834
|
KIMI_HOOKS_TOML_MARKER_BEGIN,
|
|
3073
2835
|
KIMI_HOOKS_TOML_MARKER_END,
|
|
2836
|
+
// Blocking-guard host budget (#3981, #5180)
|
|
2837
|
+
BLOCKING_GUARD_TIMEOUT_S,
|
|
2838
|
+
BLOCKING_GUARD_NAMES,
|
|
2839
|
+
KIMI_UNREGISTERED_BLOCKING_GUARDS,
|
|
2840
|
+
KIMI_BLOCKING_GUARD_NAMES,
|
|
3074
2841
|
// Shared
|
|
3075
2842
|
stageTransitiveHookLibs,
|
|
3076
2843
|
buildHookCommand,
|
|
3077
2844
|
recordConfiguredHookCommand,
|
|
3078
2845
|
applySettingsJsonHooks,
|
|
2846
|
+
applySettingsJsonHookTables,
|
|
2847
|
+
SETTINGS_JSON_HOOK_ROWS,
|
|
2848
|
+
SETTINGS_JSON_EXTENDED_ROWS,
|
|
3079
2849
|
validateConfiguredEntrypoints,
|
|
3080
2850
|
referencesHook,
|
|
3081
2851
|
rewriteLegacyManagedNodeHookCommands,
|
|
@@ -3083,6 +2853,7 @@ module.exports = {
|
|
|
3083
2853
|
normalizeNodePath,
|
|
3084
2854
|
resolveNodeRunner,
|
|
3085
2855
|
buildNodeRunnerChainToken,
|
|
2856
|
+
resolveBashExecutable,
|
|
3086
2857
|
resolveBashRunner,
|
|
3087
2858
|
NODE_RUNNER_RESOLVER_HOOK,
|
|
3088
2859
|
// Atomic write seam (shared with bin/install.js so all writes participate
|