@opengsd/gsd-core 1.15.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +85 -5
- package/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 +1 -1
- package/agents/gsd-debug-session-manager.md +1 -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/install.js +126 -196
- 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 +142 -56
- package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
- 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 +183 -103
- package/gsd-core/bin/lib/capability-validator.cjs +16 -0
- 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 +173 -170
- 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 +141 -0
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
- package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
- package/gsd-core/bin/lib/gate-exit.cjs +27 -0
- package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
- package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
- package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
- package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
- package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
- package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
- package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
- package/gsd-core/bin/lib/gate-ui-safety.cjs +80 -0
- package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
- package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
- package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
- package/gsd-core/bin/lib/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 +162 -96
- package/gsd-core/bin/lib/install-engine.cjs +19 -35
- package/gsd-core/bin/lib/install-profiles.cjs +7 -4
- package/gsd-core/bin/lib/io.cjs +122 -3
- package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
- package/gsd-core/bin/lib/milestone.cjs +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/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 +280 -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 +11 -8
- package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
- package/gsd-core/bin/lib/report-parser.cjs +269 -0
- package/gsd-core/bin/lib/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 +91 -157
- 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 +307 -591
- package/gsd-core/bin/lib/runtime-name-policy.cjs +142 -27
- package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +37 -2
- package/gsd-core/bin/lib/smart-entry.cjs +19 -3
- package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
- package/gsd-core/bin/lib/state-contract.cjs +15 -18
- package/gsd-core/bin/lib/state-document.cjs +100 -22
- package/gsd-core/bin/lib/state.cjs +214 -105
- 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/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 +1165 -314
- 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 +84 -90
- 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 +1 -1
- 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 +78 -170
- 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 +3 -2
- 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 +8 -0
- package/hooks/dist/gsd-context-monitor.js +31 -8
- package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
- package/hooks/dist/gsd-secret-read-guard.js +147 -16
- package/hooks/dist/gsd-statusline.js +15 -7
- 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 +8 -0
- package/hooks/gsd-context-monitor.js +31 -8
- package/hooks/gsd-cursor-subagent-start.js +8 -0
- package/hooks/gsd-secret-read-guard.js +147 -16
- package/hooks/gsd-statusline.js +15 -7
- 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-features.cjs +13 -8
- package/scripts/gen-hooks-cli-exit.cjs +12 -28
- package/scripts/gen-loop-host-contract.cjs +10 -1
- package/scripts/gen-platform-conformance-tier.cjs +187 -1
- package/scripts/gen-plugin-skills.cjs +87 -1
- package/scripts/gen-research-agents.cjs +24 -31
- package/scripts/gen-scripts-cli-exit.cjs +30 -3
- package/scripts/gen-test-timings.cjs +32 -7
- package/scripts/lib/cli-exit.cjs +12 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +20 -2
- package/scripts/lib/ndjson-reporter.cjs +28 -3
- package/scripts/lib/platform-conformance-tier.generated.cjs +34 -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 +50 -5
- 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')
|
|
@@ -2118,608 +2126,268 @@ function writeCopilotHookConfig(targetDir) {
|
|
|
2118
2126
|
node_fs_1.default.writeFileSync(hookPath, JSON.stringify(buildCopilotHookConfig(), null, 2) + '\n');
|
|
2119
2127
|
return hookPath;
|
|
2120
2128
|
}
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2129
|
+
const BASH_UNAVAILABLE_MESSAGE = 'Bash executable path unavailable (#3393)';
|
|
2130
|
+
// The tables are exported as a contract; freeze every row (and its nested
|
|
2131
|
+
// objects) so no consumer can mutate a matcher or timeout at runtime.
|
|
2132
|
+
function deepFreeze(value) {
|
|
2133
|
+
for (const child of Object.values(value)) {
|
|
2134
|
+
if (child !== null && typeof child === 'object')
|
|
2135
|
+
deepFreeze(child);
|
|
2136
|
+
}
|
|
2137
|
+
return Object.freeze(value);
|
|
2138
|
+
}
|
|
2139
|
+
const SETTINGS_JSON_HOOK_ROWS = deepFreeze([
|
|
2140
|
+
// Guard: only register if the hook file was actually installed (#1754, #1817).
|
|
2141
|
+
{ file: 'gsd-check-update.js', event: 'SessionStart', command: { opts: 'updateCheckCommand' },
|
|
2142
|
+
configuredMessage: 'Configured update check hook', skipLabel: 'update check hook' },
|
|
2143
|
+
{ file: 'gsd-context-monitor.js', event: 'post', matcher: 'Bash|Edit|Write|MultiEdit|Agent|Task', timeout: 10,
|
|
2144
|
+
command: { opts: 'contextMonitorCommand' },
|
|
2145
|
+
configuredMessage: 'Configured context window monitor hook', skipLabel: 'context monitor hook',
|
|
2146
|
+
onPresent: { kind: 'migrate-matcher-timeout', message: 'Updated context monitor hook (added matcher + timeout)' } },
|
|
2147
|
+
{ file: 'gsd-prompt-guard.js', event: 'pre', matcher: 'Write|Edit', timeout: 'blocking',
|
|
2148
|
+
command: { opts: 'promptGuardCommand' },
|
|
2149
|
+
configuredMessage: 'Configured prompt injection guard hook', skipLabel: 'prompt guard hook' },
|
|
2150
|
+
// Read-before-edit guidance (#1628): advisory-only, does not block.
|
|
2151
|
+
{ file: 'gsd-read-guard.js', event: 'pre', matcher: 'Write|Edit', timeout: 5,
|
|
2152
|
+
command: { opts: 'readGuardCommand' },
|
|
2153
|
+
configuredMessage: 'Configured read-before-edit guard hook', skipLabel: 'read guard hook' },
|
|
2154
|
+
// Read-time prompt injection scanning (#2201).
|
|
2155
|
+
{ file: 'gsd-read-injection-scanner.js', event: 'post', matcher: 'Read', timeout: 5,
|
|
2156
|
+
command: { opts: 'readInjectionScannerCommand' },
|
|
2157
|
+
configuredMessage: 'Configured read injection scanner hook', skipLabel: 'read injection scanner hook' },
|
|
2158
|
+
// Community hooks — registered on install but opt-in at runtime via .planning/config.json.
|
|
2159
|
+
{ file: 'gsd-workflow-guard.js', event: 'pre', matcher: 'Bash|Edit|Write|MultiEdit', timeout: 'blocking',
|
|
2160
|
+
command: { build: 'js' },
|
|
2161
|
+
configuredMessage: 'Configured workflow guard hook (opt-in via hooks.workflow_guard)', skipLabel: 'workflow guard hook',
|
|
2162
|
+
onPresent: { kind: 'repair-matcher', message: 'Updated workflow guard hook matcher' } },
|
|
2163
|
+
// Worktree absolute-path safety (#260).
|
|
2164
|
+
{ file: 'gsd-worktree-path-guard.js', event: 'pre', matcher: 'Write|Edit|MultiEdit', timeout: 'blocking',
|
|
2165
|
+
command: { build: 'js' },
|
|
2166
|
+
configuredMessage: 'Configured worktree path guard hook', skipLabel: 'worktree path guard hook' },
|
|
2167
|
+
// Agent-dispatch isolation (#3045); matcher widened to Agent|Task (#3045 MAJOR 1).
|
|
2168
|
+
{ file: 'gsd-agent-isolation-guard.js', event: 'pre', matcher: 'Agent|Task', timeout: 'blocking',
|
|
2169
|
+
command: { build: 'js' },
|
|
2170
|
+
configuredMessage: 'Configured agent isolation dispatch guard hook', skipLabel: 'agent isolation guard hook' },
|
|
2171
|
+
// Catastrophic-shrink protection (#2255, fix 3 of #973).
|
|
2172
|
+
{ file: 'gsd-write-guard.js', event: 'pre', matcher: 'Write', timeout: 'blocking',
|
|
2173
|
+
command: { build: 'js' },
|
|
2174
|
+
configuredMessage: 'Configured write guard hook (catastrophic-shrink protection)', skipLabel: 'write guard hook' },
|
|
2175
|
+
// Secret-file read protection (#4221).
|
|
2176
|
+
{ file: 'gsd-secret-read-guard.js', event: 'pre', matcher: 'Read|Grep|Bash', timeout: 'blocking',
|
|
2177
|
+
command: { build: 'js' },
|
|
2178
|
+
configuredMessage: 'Configured secret read guard hook (.env / .secrets read protection)', skipLabel: 'secret read guard hook' },
|
|
2179
|
+
// The four `.sh` rows: their built commands also feed the #3329 reconcile.
|
|
2180
|
+
{ file: 'gsd-validate-commit.sh', event: 'pre', matcher: 'Bash', timeout: 'blocking',
|
|
2181
|
+
command: { build: 'sh' },
|
|
2182
|
+
configuredMessage: 'Configured commit validation hook (opt-in via config)', skipLabel: 'commit validation hook',
|
|
2183
|
+
noCommandMessage: BASH_UNAVAILABLE_MESSAGE },
|
|
2184
|
+
{ file: 'gsd-graphify-update.sh', event: 'post', matcher: 'Bash', timeout: 5,
|
|
2185
|
+
command: { build: 'sh' },
|
|
2186
|
+
configuredMessage: 'Configured graphify auto-update hook (opt-in via graphify.auto_update)', skipLabel: 'graphify auto-update hook',
|
|
2187
|
+
noCommandMessage: BASH_UNAVAILABLE_MESSAGE },
|
|
2188
|
+
{ file: 'gsd-session-state.sh', event: 'SessionStart',
|
|
2189
|
+
command: { build: 'sh' },
|
|
2190
|
+
configuredMessage: 'Configured session state orientation hook (opt-in via config)', skipLabel: 'session state hook',
|
|
2191
|
+
noCommandMessage: BASH_UNAVAILABLE_MESSAGE },
|
|
2192
|
+
{ file: 'gsd-phase-boundary.sh', event: 'post', matcher: 'Write|Edit', timeout: 5,
|
|
2193
|
+
command: { build: 'sh' },
|
|
2194
|
+
configuredMessage: 'Configured phase boundary detection hook (opt-in via config)', skipLabel: 'phase boundary hook',
|
|
2195
|
+
noCommandMessage: BASH_UNAVAILABLE_MESSAGE },
|
|
2196
|
+
]);
|
|
2197
|
+
// Extended lifecycle events are descriptor-gated (`extendedHookEvents`): a row
|
|
2198
|
+
// registers only for runtimes whose descriptor lists its event. SubagentStart
|
|
2199
|
+
// is qwen/codebuddy-only today; the Before/After Agent/Model rows are an inert
|
|
2200
|
+
// seam (no runtime declares them) that re-activates if one does (#776, #1928).
|
|
2201
|
+
// FileChanged hot-reloads the gsd config on .planning/config.json edits.
|
|
2202
|
+
function contextMonitorRow(event, runtimeLabelSuffix) {
|
|
2203
|
+
return {
|
|
2204
|
+
event,
|
|
2205
|
+
file: 'gsd-context-monitor.js',
|
|
2206
|
+
command: { opts: 'contextMonitorCommand' },
|
|
2207
|
+
timeout: 10,
|
|
2208
|
+
configuredMessage: `Configured ${event} context monitor hook`,
|
|
2209
|
+
...(runtimeLabelSuffix ? { runtimeLabelSuffix } : {}),
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
const SETTINGS_JSON_EXTENDED_ROWS = deepFreeze([
|
|
2213
|
+
contextMonitorRow('SubagentStop', true),
|
|
2214
|
+
contextMonitorRow('Stop', true),
|
|
2215
|
+
contextMonitorRow('PreCompact', true),
|
|
2216
|
+
contextMonitorRow('SubagentStart', true),
|
|
2217
|
+
contextMonitorRow('BeforeAgent', false),
|
|
2218
|
+
contextMonitorRow('AfterAgent', false),
|
|
2219
|
+
contextMonitorRow('BeforeModel', false),
|
|
2220
|
+
{ event: 'FileChanged', file: 'gsd-config-reload.js', command: { opts: 'configReloadCommand' }, matcher: 'config.json', timeout: 8,
|
|
2221
|
+
configuredMessage: 'Configured FileChanged config-reload hook (Claude Code)',
|
|
2222
|
+
noCommandMessage: 'Node executable path unavailable' },
|
|
2223
|
+
]);
|
|
2224
|
+
function hookStem(file) {
|
|
2225
|
+
return file.replace(/\.(?:js|sh)$/, '');
|
|
2226
|
+
}
|
|
2227
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access,
|
|
2228
|
+
@typescript-eslint/no-unsafe-call,
|
|
2229
|
+
@typescript-eslint/no-unsafe-assignment,
|
|
2230
|
+
@typescript-eslint/no-explicit-any */
|
|
2231
|
+
function groupReferencesHook(entry, stem) {
|
|
2232
|
+
return Boolean(entry.hooks && entry.hooks.some((h) => referencesHook(h, stem)));
|
|
2233
|
+
}
|
|
2234
|
+
function resolveRowTimeout(timeout) {
|
|
2235
|
+
return timeout === 'blocking' ? BLOCKING_GUARD_TIMEOUT_S : timeout;
|
|
2236
|
+
}
|
|
2237
|
+
function resolveRowCommand(row, opts) {
|
|
2238
|
+
if ('opts' in row.command)
|
|
2239
|
+
return opts[row.command.opts];
|
|
2240
|
+
if (opts.isGlobal)
|
|
2241
|
+
return buildHookCommand(opts.targetDir, row.file, opts.hookOpts);
|
|
2242
|
+
return row.command.build === 'sh' ? opts.localShellCmd(row.file) : opts.localCmd(row.file);
|
|
2243
|
+
}
|
|
2244
|
+
function reconcilePresentHook(row, entries, existing) {
|
|
2245
|
+
const present = row.onPresent;
|
|
2246
|
+
if (!present)
|
|
2247
|
+
return;
|
|
2248
|
+
if (present.kind === 'repair-matcher') {
|
|
2249
|
+
if (existing.matcher !== row.matcher) {
|
|
2250
|
+
existing.matcher = row.matcher;
|
|
2251
|
+
console.log(` ${green}✓${reset} ${present.message}`);
|
|
2252
|
+
}
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
// migrate-matcher-timeout: add the matcher and timeout an older installer omitted.
|
|
2256
|
+
const stem = hookStem(row.file);
|
|
2257
|
+
const timeout = resolveRowTimeout(row.timeout);
|
|
2258
|
+
for (const entry of entries) {
|
|
2259
|
+
if (!groupReferencesHook(entry, stem))
|
|
2260
|
+
continue;
|
|
2261
|
+
let migrated = false;
|
|
2262
|
+
if (!entry.matcher) {
|
|
2263
|
+
entry.matcher = row.matcher;
|
|
2264
|
+
migrated = true;
|
|
2265
|
+
}
|
|
2266
|
+
for (const h of entry.hooks) {
|
|
2267
|
+
if (referencesHook(h, stem) && !h.timeout) {
|
|
2268
|
+
h.timeout = timeout;
|
|
2269
|
+
migrated = true;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
if (migrated)
|
|
2273
|
+
console.log(` ${green}✓${reset} ${present.message}`);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
function pushHookGroup(entries, matcher, command, timeout) {
|
|
2277
|
+
const hook = { type: 'command', command };
|
|
2278
|
+
if (timeout !== undefined)
|
|
2279
|
+
hook['timeout'] = timeout;
|
|
2280
|
+
entries.push(matcher === undefined ? { hooks: [hook] } : { matcher, hooks: [hook] });
|
|
2281
|
+
}
|
|
2282
|
+
function applySettingsJsonHookTables(settings, opts, tables) {
|
|
2283
|
+
const { runtime, postToolEvent, hookEvents, extendedHookEvents, hooksSurface, targetDir, hookOpts } = opts;
|
|
2284
|
+
// ADR-857 phase 5f-3: extended hook events are driven by the registry
|
|
2128
2285
|
// descriptor field rather than hardcoded runtime-name checks.
|
|
2129
2286
|
const extendedEvents = Array.isArray(extendedHookEvents) ? extendedHookEvents : [];
|
|
2130
2287
|
// ADR-857 phase 5g drive 3: hook-skip guard is driven by the hooksSurface
|
|
2131
2288
|
// 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.
|
|
2289
|
+
// register settings.json hooks; 'none' (opencode, kilo) is skipped.
|
|
2135
2290
|
// #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))
|
|
2291
|
+
// its own native config.toml via writeKimiHooksToml and never writes
|
|
2292
|
+
// settings.json, so this guard must skip it too, or the console would log
|
|
2293
|
+
// "Configured ..." for a settings object finishInstall() never persists.
|
|
2294
|
+
if (hooksSurface === 'none' || hooksSurface === 'kimi-hooks-toml')
|
|
2295
|
+
return;
|
|
2296
|
+
if (!settings.hooks) {
|
|
2297
|
+
settings.hooks = {};
|
|
2298
|
+
}
|
|
2299
|
+
if (!settings.hooks.SessionStart) {
|
|
2300
|
+
settings.hooks.SessionStart = [];
|
|
2301
|
+
}
|
|
2302
|
+
// #3981: raise existing managed blocking-guard entries still on the old
|
|
2303
|
+
// 5 s budget to BLOCKING_GUARD_TIMEOUT_S (module-level; rationale there).
|
|
2304
|
+
for (const entries of Object.values(settings.hooks)) {
|
|
2305
|
+
if (!Array.isArray(entries))
|
|
2306
|
+
continue;
|
|
2307
|
+
for (const entry of entries) {
|
|
2308
|
+
if (!entry || !Array.isArray(entry.hooks))
|
|
2167
2309
|
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
|
-
}
|
|
2310
|
+
for (const h of entry.hooks) {
|
|
2311
|
+
if (BLOCKING_GUARD_NAMES.some((name) => referencesHook(h, name)) &&
|
|
2312
|
+
h.timeout === 5) {
|
|
2313
|
+
h.timeout = BLOCKING_GUARD_TIMEOUT_S;
|
|
2176
2314
|
}
|
|
2177
2315
|
}
|
|
2178
2316
|
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
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`);
|
|
2317
|
+
}
|
|
2318
|
+
// hookEvents='gemini' → BeforeTool/AfterTool; all others → PreToolUse/PostToolUse.
|
|
2319
|
+
const preToolEvent = hookEvents === 'gemini' ? 'BeforeTool' : 'PreToolUse';
|
|
2320
|
+
const eventKey = (event) => event === 'SessionStart' ? 'SessionStart' : event === 'post' ? postToolEvent : preToolEvent;
|
|
2321
|
+
// Built `.sh` commands, in row order, feed the #3329 reconcile below.
|
|
2322
|
+
const shellCommands = {};
|
|
2323
|
+
for (const row of tables.rows) {
|
|
2324
|
+
const key = eventKey(row.event);
|
|
2325
|
+
if (!settings.hooks[key]) {
|
|
2326
|
+
settings.hooks[key] = [];
|
|
2327
|
+
}
|
|
2328
|
+
const entries = settings.hooks[key];
|
|
2329
|
+
const command = resolveRowCommand(row, opts);
|
|
2330
|
+
if ('build' in row.command && row.command.build === 'sh')
|
|
2331
|
+
shellCommands[row.file] = command;
|
|
2332
|
+
const stem = hookStem(row.file);
|
|
2333
|
+
const existing = entries.find((entry) => groupReferencesHook(entry, stem));
|
|
2334
|
+
if (existing) {
|
|
2335
|
+
reconcilePresentHook(row, entries, existing);
|
|
2336
|
+
continue;
|
|
2217
2337
|
}
|
|
2218
|
-
|
|
2219
|
-
console.warn(` ${yellow}⚠${reset} Skipped
|
|
2338
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.join(targetDir, 'hooks', row.file))) {
|
|
2339
|
+
console.warn(` ${yellow}⚠${reset} Skipped ${row.skipLabel} — ${row.file} not found at target`);
|
|
2220
2340
|
}
|
|
2221
|
-
else {
|
|
2222
|
-
|
|
2223
|
-
|
|
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
|
-
}
|
|
2341
|
+
else if (command) {
|
|
2342
|
+
pushHookGroup(entries, row.matcher, command, resolveRowTimeout(row.timeout));
|
|
2343
|
+
console.log(` ${green}✓${reset} ${row.configuredMessage}`);
|
|
2241
2344
|
}
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
// hookEvents='gemini' → BeforeTool; all others → PreToolUse.
|
|
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
|
-
}
|
|
2345
|
+
else if (row.noCommandMessage) {
|
|
2346
|
+
console.warn(` ${yellow}⚠${reset} Skipped ${row.skipLabel} — ${row.noCommandMessage}`);
|
|
2626
2347
|
}
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
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
|
-
}
|
|
2348
|
+
}
|
|
2349
|
+
// #3329: `.sh` rows register only-if-absent, so an entry registered by an
|
|
2350
|
+
// older installer keeps its old command forever — /gsd-update (which
|
|
2351
|
+
// re-invokes the installer) never re-derived it. On Claude/win32 that left
|
|
2352
|
+
// the pre-#580/#3393 bash-runner-prefixed commands in settings.json
|
|
2353
|
+
// indefinitely. Reconcile existing managed `.sh` entries to the command this
|
|
2354
|
+
// install would generate today. Inert wherever the bash runner is still the
|
|
2355
|
+
// correct shape; scoped to exact managed basenames so user-authored hooks
|
|
2356
|
+
// are never touched.
|
|
2357
|
+
if (reconcileManagedShellHookCommands(settings, shellCommands, { platform: hookOpts.platform, runtime })) {
|
|
2358
|
+
console.log(` ${green}✓${reset} Reconciled managed .sh hook commands to current format (#3329)`);
|
|
2359
|
+
}
|
|
2360
|
+
// Descriptor-driven (ADR-1239 / #2092): the runtime's capability title.
|
|
2361
|
+
const runtimeLabel = _capabilityTitle(runtime);
|
|
2362
|
+
for (const row of tables.extendedRows) {
|
|
2363
|
+
if (!extendedEvents.includes(row.event))
|
|
2364
|
+
continue;
|
|
2365
|
+
if (!Array.isArray(settings.hooks[row.event])) {
|
|
2366
|
+
settings.hooks[row.event] = [];
|
|
2678
2367
|
}
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
if (!settings.hooks.FileChanged) {
|
|
2693
|
-
settings.hooks.FileChanged = [];
|
|
2694
|
-
}
|
|
2695
|
-
const configReloadFile = node_path_1.default.join(targetDir, 'hooks', 'gsd-config-reload.js');
|
|
2696
|
-
const alreadyHasConfigReload = settings.hooks.FileChanged.some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-config-reload')));
|
|
2697
|
-
if (!alreadyHasConfigReload && node_fs_1.default.existsSync(configReloadFile) && configReloadCommand) {
|
|
2698
|
-
settings.hooks.FileChanged.push({
|
|
2699
|
-
matcher: 'config.json',
|
|
2700
|
-
hooks: [
|
|
2701
|
-
{
|
|
2702
|
-
type: 'command',
|
|
2703
|
-
command: configReloadCommand,
|
|
2704
|
-
timeout: 8
|
|
2705
|
-
}
|
|
2706
|
-
]
|
|
2707
|
-
});
|
|
2708
|
-
console.log(` ${green}✓${reset} Configured FileChanged config-reload hook (Claude Code)`);
|
|
2709
|
-
}
|
|
2710
|
-
else if (!alreadyHasConfigReload && !node_fs_1.default.existsSync(configReloadFile)) {
|
|
2711
|
-
console.warn(` ${yellow}⚠${reset} Skipped FileChanged hook — gsd-config-reload.js not found at target`);
|
|
2712
|
-
}
|
|
2713
|
-
else if (!alreadyHasConfigReload && !configReloadCommand) {
|
|
2714
|
-
console.warn(` ${yellow}⚠${reset} Skipped FileChanged hook — Node executable path unavailable`);
|
|
2715
|
-
}
|
|
2368
|
+
const entries = settings.hooks[row.event];
|
|
2369
|
+
if (entries.some((entry) => groupReferencesHook(entry, hookStem(row.file))))
|
|
2370
|
+
continue;
|
|
2371
|
+
const command = opts[row.command.opts];
|
|
2372
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.join(targetDir, 'hooks', row.file))) {
|
|
2373
|
+
console.warn(` ${yellow}⚠${reset} Skipped ${row.event} hook — ${row.file} not found at target`);
|
|
2374
|
+
}
|
|
2375
|
+
else if (command) {
|
|
2376
|
+
pushHookGroup(entries, row.matcher, command, row.timeout);
|
|
2377
|
+
console.log(` ${green}✓${reset} ${row.configuredMessage}${row.runtimeLabelSuffix ? ` (${runtimeLabel})` : ''}`);
|
|
2378
|
+
}
|
|
2379
|
+
else if (row.noCommandMessage) {
|
|
2380
|
+
console.warn(` ${yellow}⚠${reset} Skipped ${row.event} hook — ${row.noCommandMessage}`);
|
|
2716
2381
|
}
|
|
2717
|
-
// ── end FileChanged hook ────────────────────────────────────────────────────
|
|
2718
2382
|
}
|
|
2719
|
-
/* eslint-enable @typescript-eslint/no-unsafe-member-access,
|
|
2720
|
-
@typescript-eslint/no-unsafe-call,
|
|
2721
|
-
@typescript-eslint/no-unsafe-assignment */
|
|
2722
2383
|
}
|
|
2384
|
+
function applySettingsJsonHooks(settings, opts) {
|
|
2385
|
+
applySettingsJsonHookTables(settings, opts, { rows: SETTINGS_JSON_HOOK_ROWS, extendedRows: SETTINGS_JSON_EXTENDED_ROWS });
|
|
2386
|
+
}
|
|
2387
|
+
/* eslint-enable @typescript-eslint/no-unsafe-member-access,
|
|
2388
|
+
@typescript-eslint/no-unsafe-call,
|
|
2389
|
+
@typescript-eslint/no-unsafe-assignment,
|
|
2390
|
+
@typescript-eslint/no-explicit-any */
|
|
2723
2391
|
// ---------------------------------------------------------------------------
|
|
2724
2392
|
// Kimi hooks.toml (#2095 EoS/kimi Upgrade 1 — native hook bus)
|
|
2725
2393
|
//
|
|
@@ -2792,13 +2460,13 @@ function buildKimiHooksTomlBlock(targetDir, opts) {
|
|
|
2792
2460
|
{ event: 'SessionStart', command: cmd('gsd-check-update.js') },
|
|
2793
2461
|
{ event: 'SessionStart', command: cmd('gsd-session-state.sh') },
|
|
2794
2462
|
// PreToolUse
|
|
2795
|
-
{ event: 'PreToolUse', command: cmd('gsd-prompt-guard.js'), matcher: 'WriteFile|StrReplaceFile', timeout:
|
|
2463
|
+
{ event: 'PreToolUse', command: cmd('gsd-prompt-guard.js'), matcher: 'WriteFile|StrReplaceFile', timeout: kimiBlockingGuardTimeout('gsd-prompt-guard.js') },
|
|
2796
2464
|
{ 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:
|
|
2465
|
+
{ event: 'PreToolUse', command: cmd('gsd-worktree-path-guard.js'), matcher: 'WriteFile|StrReplaceFile', timeout: kimiBlockingGuardTimeout('gsd-worktree-path-guard.js') },
|
|
2466
|
+
{ event: 'PreToolUse', command: cmd('gsd-write-guard.js'), matcher: 'WriteFile', timeout: kimiBlockingGuardTimeout('gsd-write-guard.js') },
|
|
2467
|
+
{ event: 'PreToolUse', command: cmd('gsd-secret-read-guard.js'), matcher: 'ReadFile|Grep|Shell', timeout: kimiBlockingGuardTimeout('gsd-secret-read-guard.js') },
|
|
2468
|
+
{ event: 'PreToolUse', command: cmd('gsd-workflow-guard.js'), matcher: 'Shell|WriteFile|StrReplaceFile', timeout: kimiBlockingGuardTimeout('gsd-workflow-guard.js') },
|
|
2469
|
+
{ event: 'PreToolUse', command: cmd('gsd-validate-commit.sh'), matcher: 'Shell', timeout: kimiBlockingGuardTimeout('gsd-validate-commit.sh') },
|
|
2802
2470
|
// PostToolUse
|
|
2803
2471
|
{ event: 'PostToolUse', command: cmd('gsd-context-monitor.js'), timeout: 10 },
|
|
2804
2472
|
{ event: 'PostToolUse', command: cmd('gsd-phase-boundary.sh'), matcher: 'WriteFile|StrReplaceFile', timeout: 5 },
|
|
@@ -2924,6 +2592,45 @@ function removeKimiHooksToml(configPath) {
|
|
|
2924
2592
|
return { changed: true };
|
|
2925
2593
|
}
|
|
2926
2594
|
// ---------------------------------------------------------------------------
|
|
2595
|
+
// Blocking-guard host budget (#3981, #5180)
|
|
2596
|
+
//
|
|
2597
|
+
// Claude Code treats a timed-out hook as NON-blocking — the tool call
|
|
2598
|
+
// continues through the normal permission flow. The blocking PreToolUse
|
|
2599
|
+
// guards therefore need a budget a host stall cannot exceed, not one sized to
|
|
2600
|
+
// the hook's own ~0.1 s runtime. Observed stalls reached 84.3 s; 120 s is the
|
|
2601
|
+
// top of the issue's prescribed 60–120 range and returns every observed
|
|
2602
|
+
// verdict. ONE constant and ONE name list, shared by every registration
|
|
2603
|
+
// surface that carries a host timeout (settings.json install + migration,
|
|
2604
|
+
// Kimi config.toml) so a future change cannot diverge between them.
|
|
2605
|
+
// ---------------------------------------------------------------------------
|
|
2606
|
+
const BLOCKING_GUARD_TIMEOUT_S = 120;
|
|
2607
|
+
const BLOCKING_GUARD_NAMES = [
|
|
2608
|
+
'gsd-prompt-guard',
|
|
2609
|
+
'gsd-workflow-guard',
|
|
2610
|
+
'gsd-worktree-path-guard',
|
|
2611
|
+
'gsd-agent-isolation-guard',
|
|
2612
|
+
'gsd-write-guard',
|
|
2613
|
+
'gsd-secret-read-guard',
|
|
2614
|
+
'gsd-validate-commit',
|
|
2615
|
+
];
|
|
2616
|
+
// Kimi registers every blocking guard EXCEPT the ones named here: Claude's
|
|
2617
|
+
// Agent|Task (subagent-dispatch) matcher segment has no confirmed Kimi tool
|
|
2618
|
+
// name, so gsd-agent-isolation-guard has nothing to attach to there (see the
|
|
2619
|
+
// buildKimiHooksTomlBlock doc). The divergence from the installer's set is
|
|
2620
|
+
// intentional and explicit, not an accident of two hand-kept lists.
|
|
2621
|
+
const KIMI_UNREGISTERED_BLOCKING_GUARDS = ['gsd-agent-isolation-guard'];
|
|
2622
|
+
const KIMI_BLOCKING_GUARD_NAMES = BLOCKING_GUARD_NAMES.filter((name) => !KIMI_UNREGISTERED_BLOCKING_GUARDS.includes(name));
|
|
2623
|
+
// Host budget for a Kimi blocking-guard entry, by script file name. Throws for
|
|
2624
|
+
// a script outside KIMI_BLOCKING_GUARD_NAMES so a spec cannot claim the
|
|
2625
|
+
// blocking budget for a hook the shared list does not name.
|
|
2626
|
+
function kimiBlockingGuardTimeout(scriptFile) {
|
|
2627
|
+
const name = scriptFile.replace(/\.(?:js|sh)$/, '');
|
|
2628
|
+
if (!KIMI_BLOCKING_GUARD_NAMES.includes(name)) {
|
|
2629
|
+
throw new Error(`runtime-hooks-surface: ${scriptFile} is not a Kimi blocking guard (KIMI_BLOCKING_GUARD_NAMES)`);
|
|
2630
|
+
}
|
|
2631
|
+
return BLOCKING_GUARD_TIMEOUT_S;
|
|
2632
|
+
}
|
|
2633
|
+
// ---------------------------------------------------------------------------
|
|
2927
2634
|
// referencesHook
|
|
2928
2635
|
//
|
|
2929
2636
|
// Pure predicate — checks whether a hook entry object references a managed
|
|
@@ -3071,11 +2778,19 @@ module.exports = {
|
|
|
3071
2778
|
removeKimiHooksToml,
|
|
3072
2779
|
KIMI_HOOKS_TOML_MARKER_BEGIN,
|
|
3073
2780
|
KIMI_HOOKS_TOML_MARKER_END,
|
|
2781
|
+
// Blocking-guard host budget (#3981, #5180)
|
|
2782
|
+
BLOCKING_GUARD_TIMEOUT_S,
|
|
2783
|
+
BLOCKING_GUARD_NAMES,
|
|
2784
|
+
KIMI_UNREGISTERED_BLOCKING_GUARDS,
|
|
2785
|
+
KIMI_BLOCKING_GUARD_NAMES,
|
|
3074
2786
|
// Shared
|
|
3075
2787
|
stageTransitiveHookLibs,
|
|
3076
2788
|
buildHookCommand,
|
|
3077
2789
|
recordConfiguredHookCommand,
|
|
3078
2790
|
applySettingsJsonHooks,
|
|
2791
|
+
applySettingsJsonHookTables,
|
|
2792
|
+
SETTINGS_JSON_HOOK_ROWS,
|
|
2793
|
+
SETTINGS_JSON_EXTENDED_ROWS,
|
|
3079
2794
|
validateConfiguredEntrypoints,
|
|
3080
2795
|
referencesHook,
|
|
3081
2796
|
rewriteLegacyManagedNodeHookCommands,
|
|
@@ -3083,6 +2798,7 @@ module.exports = {
|
|
|
3083
2798
|
normalizeNodePath,
|
|
3084
2799
|
resolveNodeRunner,
|
|
3085
2800
|
buildNodeRunnerChainToken,
|
|
2801
|
+
resolveBashExecutable,
|
|
3086
2802
|
resolveBashRunner,
|
|
3087
2803
|
NODE_RUNNER_RESOLVER_HOOK,
|
|
3088
2804
|
// Atomic write seam (shared with bin/install.js so all writes participate
|