@opengsd/gsd-core 1.11.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +17 -9
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +0 -1
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +3 -1
- package/agents/gsd-plan-checker.md +15 -55
- package/agents/gsd-planner.md +6 -4
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +2 -2
- package/agents/gsd-roadmapper.md +15 -11
- package/agents/gsd-ui-checker.md +63 -4
- package/agents/gsd-ui-researcher.md +41 -3
- package/agents/gsd-verifier.md +1 -1
- package/bin/install.js +609 -134
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/quick.md +8 -4
- package/gsd-core/bin/gsd-tools.cjs +567 -51
- package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
- package/gsd-core/bin/lib/api-coverage.cjs +30 -9
- package/gsd-core/bin/lib/artifacts.cjs +2 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
- package/gsd-core/bin/lib/audit.cjs +163 -41
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +336 -95
- package/gsd-core/bin/lib/capability-state.cjs +18 -3
- package/gsd-core/bin/lib/capability-validator.cjs +205 -18
- package/gsd-core/bin/lib/check-command-router.cjs +145 -5
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +543 -44
- package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
- package/gsd-core/bin/lib/config-loader.cjs +118 -29
- package/gsd-core/bin/lib/config.cjs +92 -2
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +84 -7
- package/gsd-core/bin/lib/edge-probe.cjs +9 -1
- package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
- package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
- package/gsd-core/bin/lib/frontmatter.cjs +840 -305
- package/gsd-core/bin/lib/gap-checker.cjs +27 -3
- package/gsd-core/bin/lib/git-base-branch.cjs +174 -39
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +6 -3
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
- package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
- package/gsd-core/bin/lib/host-integration.cjs +39 -6
- package/gsd-core/bin/lib/init-command-router.cjs +118 -21
- package/gsd-core/bin/lib/init.cjs +120 -41
- package/gsd-core/bin/lib/install-engine.cjs +68 -3
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -1
- package/gsd-core/bin/lib/install-profiles.cjs +78 -4
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +160 -15
- package/gsd-core/bin/lib/learnings.cjs +85 -14
- package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
- package/gsd-core/bin/lib/markdown-table.cjs +52 -4
- package/gsd-core/bin/lib/milestone.cjs +90 -5
- package/gsd-core/bin/lib/model-catalog.cjs +177 -19
- package/gsd-core/bin/lib/model-resolver.cjs +10 -28
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
- package/gsd-core/bin/lib/phase-id.cjs +70 -4
- package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
- package/gsd-core/bin/lib/phase-locator.cjs +138 -17
- package/gsd-core/bin/lib/phase.cjs +405 -84
- package/gsd-core/bin/lib/plan-document.cjs +263 -0
- package/gsd-core/bin/lib/plan-scan.cjs +13 -2
- package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +18 -14
- package/gsd-core/bin/lib/planning-workspace.cjs +56 -0
- package/gsd-core/bin/lib/probe-core.cjs +4 -1
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
- package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
- package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +9 -9
- package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
- package/gsd-core/bin/lib/roadmap-parser.cjs +79 -16
- package/gsd-core/bin/lib/roadmap.cjs +74 -19
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +96 -8
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +34 -1
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +287 -55
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +71 -8
- package/gsd-core/bin/lib/smart-entry.cjs +12 -22
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +47 -18
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +186 -0
- package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
- package/gsd-core/bin/lib/state-transition.cjs +517 -101
- package/gsd-core/bin/lib/state.cjs +946 -163
- package/gsd-core/bin/lib/surface.cjs +10 -2
- package/gsd-core/bin/lib/task-command-router.cjs +111 -1
- package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
- package/gsd-core/bin/lib/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
- package/gsd-core/bin/lib/uat.cjs +1376 -125
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
- package/gsd-core/bin/lib/unusable-input.cjs +13 -0
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/vendor/README.md +43 -5
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/verification.cjs +14 -1
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify.cjs +95 -40
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +177 -21
- package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
- package/gsd-core/bin/shared/exit-codes.json +8 -0
- package/gsd-core/bin/shared/exit-codes.sh +20 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -1
- package/gsd-core/references/agent-contracts.md +3 -2
- package/gsd-core/references/api-coverage.md +24 -2
- package/gsd-core/references/autonomous-smart-discuss.md +3 -3
- package/gsd-core/references/checkpoints.md +37 -19
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/edge-probe.md +8 -0
- package/gsd-core/references/execute-mvp-tdd.md +1 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
- package/gsd-core/references/execute-phase-wave-guard.md +11 -9
- package/gsd-core/references/failing-direction.md +78 -0
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-integration.md +5 -5
- package/gsd-core/references/git-planning-commit.md +3 -3
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +22 -0
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/nyquist-compliance.md +74 -0
- package/gsd-core/references/offer-next.md +3 -5
- package/gsd-core/references/phase-argument-parsing.md +3 -3
- package/gsd-core/references/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-revision.md +1 -1
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +37 -8
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/tdd.md +1 -3
- package/gsd-core/references/ui-brand.md +65 -21
- package/gsd-core/references/ui-consideration-probe.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +2 -2
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +11 -11
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/SECURITY.md +3 -3
- package/gsd-core/templates/UI-SPEC.md +25 -3
- package/gsd-core/templates/VALIDATION.md +3 -3
- package/gsd-core/templates/phase-prompt.md +3 -0
- package/gsd-core/templates/state.md +7 -0
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +3 -3
- package/gsd-core/workflows/add-tests.md +3 -8
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +4 -9
- package/gsd-core/workflows/audit-fix.md +12 -3
- package/gsd-core/workflows/audit-milestone.md +9 -9
- package/gsd-core/workflows/audit-uat.md +17 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +10 -26
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +2 -2
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +1 -1
- package/gsd-core/workflows/code-review-fix.md +1 -1
- package/gsd-core/workflows/code-review.md +121 -40
- package/gsd-core/workflows/complete-milestone.md +15 -10
- package/gsd-core/workflows/debug.md +5 -3
- package/gsd-core/workflows/diagnose-issues.md +12 -6
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +3 -7
- package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +2 -2
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +3 -6
- package/gsd-core/workflows/docs-update.md +5 -4
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +4 -9
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +113 -11
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +22 -4
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase.md +38 -54
- package/gsd-core/workflows/execute-plan.md +17 -12
- package/gsd-core/workflows/explore.md +1 -1
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/fast.md +2 -2
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +5 -5
- package/gsd-core/workflows/health.md +3 -6
- package/gsd-core/workflows/import.md +14 -11
- package/gsd-core/workflows/inbox.md +4 -5
- package/gsd-core/workflows/ingest-docs.md +44 -11
- package/gsd-core/workflows/insert-phase.md +5 -5
- package/gsd-core/workflows/list-seeds.md +5 -3
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +12 -23
- package/gsd-core/workflows/map-codebase.md +1 -1
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +2 -2
- package/gsd-core/workflows/new-milestone.md +9 -21
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +12 -26
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +2 -2
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
- package/gsd-core/workflows/plan-phase.md +121 -42
- package/gsd-core/workflows/plan-review-convergence.md +46 -9
- package/gsd-core/workflows/plant-seed.md +2 -2
- package/gsd-core/workflows/pr-branch.md +187 -51
- package/gsd-core/workflows/profile-user.md +16 -14
- package/gsd-core/workflows/progress.md +27 -12
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -3
- package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
- package/gsd-core/workflows/quick/steps/research-phase.md +2 -4
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick.md +20 -29
- package/gsd-core/workflows/remove-phase.md +4 -4
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +8 -12
- package/gsd-core/workflows/review.md +193 -15
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +2 -2
- package/gsd-core/workflows/settings-advanced.md +7 -9
- package/gsd-core/workflows/settings-integrations.md +64 -31
- package/gsd-core/workflows/settings.md +3 -5
- package/gsd-core/workflows/ship.md +12 -6
- package/gsd-core/workflows/sketch-wrap-up.md +11 -17
- package/gsd-core/workflows/sketch.md +12 -18
- package/gsd-core/workflows/smart-entry.md +3 -5
- package/gsd-core/workflows/spec-phase.md +23 -1
- package/gsd-core/workflows/spike-wrap-up.md +7 -11
- package/gsd-core/workflows/spike.md +20 -31
- package/gsd-core/workflows/stats.md +2 -2
- package/gsd-core/workflows/sync-skills.md +1 -1
- package/gsd-core/workflows/thread.md +11 -7
- package/gsd-core/workflows/transition.md +5 -5
- package/gsd-core/workflows/ui-phase.md +10 -16
- package/gsd-core/workflows/ui-review.md +6 -10
- package/gsd-core/workflows/ultraplan-phase.md +5 -13
- package/gsd-core/workflows/undo.md +8 -16
- package/gsd-core/workflows/update.md +6 -10
- package/gsd-core/workflows/validate-phase.md +2 -2
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +57 -18
- package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +19 -10
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
- package/hooks/dist/gsd-cursor-session-start.js +2 -1
- package/hooks/dist/gsd-cursor-stop.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
- package/hooks/dist/gsd-graphify-update.sh +22 -18
- package/hooks/dist/gsd-node-runner.sh +76 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +16 -7
- package/hooks/dist/gsd-read-guard.js +16 -7
- package/hooks/dist/gsd-read-injection-scanner.js +17 -8
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +215 -26
- package/hooks/dist/gsd-validate-commit.sh +80 -6
- package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
- package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
- package/hooks/dist/gsd-workflow-guard.js +34 -16
- package/hooks/dist/gsd-worktree-path-guard.js +36 -21
- package/hooks/dist/gsd-write-guard.js +35 -25
- package/hooks/dist/lib/cli-exit.js +560 -0
- package/hooks/dist/lib/exit-code-registry.js +98 -0
- package/hooks/dist/lib/git-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-agent-isolation-guard.js +77 -38
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +19 -10
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +3 -1
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +28 -23
- package/hooks/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +76 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +16 -7
- package/hooks/gsd-read-guard.js +16 -7
- package/hooks/gsd-read-injection-scanner.js +17 -8
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +215 -26
- package/hooks/gsd-validate-commit.sh +80 -6
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +34 -16
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +12 -7
- package/scripts/base64-scan.sh +74 -12
- package/scripts/build-hooks.js +5 -0
- package/scripts/check-glossary-refs.cjs +77 -15
- package/scripts/check-mutation-score-ratchet.cjs +156 -0
- package/scripts/ci-check-job-near-cap.cjs +49 -0
- package/scripts/ci-pr-mergeability.cjs +262 -0
- package/scripts/ci-test-scope.cjs +45 -12
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/docs-guard-registry.cjs +396 -0
- package/scripts/gen-capability-registry.cjs +8 -6
- package/scripts/gen-exit-code-docs.cjs +318 -0
- package/scripts/gen-exit-code-registry.cjs +891 -0
- package/scripts/gen-features.cjs +836 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-loop-host-contract.cjs +134 -1
- package/scripts/gen-scripts-cli-exit.cjs +185 -0
- package/scripts/gen-state-md-docs.cjs +727 -0
- package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +32 -2
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +21 -8
- package/scripts/lint-planning-prompt-drift.cjs +38 -1
- package/scripts/lint-removed-but-needed.cjs +184 -16
- package/scripts/lint-seam-enforcement.cjs +182 -0
- package/scripts/lint-slug-derivation-drift.cjs +921 -0
- package/scripts/lint-source-test-name-collision.cjs +241 -0
- package/scripts/lint-state-write-path-drift.cjs +337 -432
- package/scripts/lint-test-file-count.allowlist.json +122 -4
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +51 -64
- package/scripts/lint-vendored-deps.cjs +208 -35
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/prompt-injection-scan.sh +75 -14
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +22 -3
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-quick/SKILL.md +8 -4
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -109
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/state-write-path-drift-baseline.json +0 -19
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// hooks/lib/git-probe.js — classify a bounded spawnSync(git, ...) result,
|
|
3
|
+
// distinguishing a GENUINE negative answer (git ran and said "no") from
|
|
4
|
+
// "could not determine" (timeout / spawn failure / signal kill) — #3911.
|
|
5
|
+
//
|
|
6
|
+
// Proven defect: hooks/gsd-worktree-path-guard.js, hooks/gsd-workflow-guard.js,
|
|
7
|
+
// and hooks/gsd-windsurf-pre-write.js each treat spawnSync(git, ..., {timeout})
|
|
8
|
+
// returning a non-zero/empty result as "not applicable" and allow silently.
|
|
9
|
+
// A macOS CI run showed three deny cases land at 2084ms/2112ms/2177ms — just
|
|
10
|
+
// past the 2000ms budget — each returning exit 0 with EMPTY stdout AND EMPTY
|
|
11
|
+
// stderr: a timeout is INDISTINGUISHABLE, at the call site, from git cleanly
|
|
12
|
+
// answering "not a repo" / "not this worktree" unless the spawnSync() result
|
|
13
|
+
// itself is inspected for `error`/`signal`, not just `status`/`stdout`.
|
|
14
|
+
//
|
|
15
|
+
// This module does not change any hook's decision or exit code — it only
|
|
16
|
+
// answers "was this probe's answer real?" so a caller can emit a diagnostic
|
|
17
|
+
// on the undetermined branch while keeping the existing fail-open behavior.
|
|
18
|
+
//
|
|
19
|
+
// Classification mirrors tests/helpers/process-seam.cjs's OUTCOME vocabulary
|
|
20
|
+
// (EXITED vs TIMED_OUT/SPAWN_FAILED/KILLED) — same evidence-over-classification
|
|
21
|
+
// ordering (a populated `status` outranks an attached `error`, since Node can
|
|
22
|
+
// report `error.code === 'ETIMEDOUT'` on a result that also carried a real
|
|
23
|
+
// exit that beat the timer) — so a probe here and a subprocess outcome in the
|
|
24
|
+
// test suite never disagree on the same raw spawnSync() shape.
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {{error?: (Error & {code?: string})|null, status: number|null, signal: string|null}} result
|
|
28
|
+
* the raw return value of `spawnSync('git', ...)`.
|
|
29
|
+
* @returns {{determined: true} | {determined: false, reason: string}}
|
|
30
|
+
*/
|
|
31
|
+
function classifyGitProbe(result) {
|
|
32
|
+
const error = result && result.error;
|
|
33
|
+
const status = result ? result.status : null;
|
|
34
|
+
const signal = result ? result.signal : null;
|
|
35
|
+
|
|
36
|
+
if (!error && signal === null) {
|
|
37
|
+
// A real exit — status 0 or non-zero is a genuine, trustworthy answer.
|
|
38
|
+
return { determined: true };
|
|
39
|
+
}
|
|
40
|
+
if (status !== null) {
|
|
41
|
+
// `status` is only ever populated by a real exit; it outranks an attached
|
|
42
|
+
// `error` even at the exact timeout boundary (process-seam.cjs's own
|
|
43
|
+
// "evidence over classification" rule).
|
|
44
|
+
return { determined: true };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const code = error ? (error.code ?? null) : null;
|
|
48
|
+
let reason;
|
|
49
|
+
if (code === 'ETIMEDOUT' || signal !== null) {
|
|
50
|
+
reason = signal ? `timed out (signal ${signal})` : 'timed out';
|
|
51
|
+
} else if (code) {
|
|
52
|
+
reason = `spawn failed (${code})`;
|
|
53
|
+
} else {
|
|
54
|
+
reason = 'could not run (unknown reason)';
|
|
55
|
+
}
|
|
56
|
+
return { determined: false, reason };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* If `result` could not be determined (timeout / spawn failure / signal
|
|
61
|
+
* kill), write a stderr diagnostic naming the probe and why — never changes
|
|
62
|
+
* any exit code, never throws. A no-op when the probe genuinely ran and
|
|
63
|
+
* answered (status 0 or non-zero with no error/signal).
|
|
64
|
+
*
|
|
65
|
+
* @param {string} hookName - e.g. 'gsd-worktree-path-guard'
|
|
66
|
+
* @param {string} probeLabel - e.g. "git rev-parse --show-toplevel"
|
|
67
|
+
* @param {*} result - the raw spawnSync() return value
|
|
68
|
+
*/
|
|
69
|
+
function reportIfUndetermined(hookName, probeLabel, result) {
|
|
70
|
+
const classification = classifyGitProbe(result);
|
|
71
|
+
if (classification.determined) return;
|
|
72
|
+
try {
|
|
73
|
+
process.stderr.write(
|
|
74
|
+
`${hookName}: git probe '${probeLabel}' ${classification.reason} — ` +
|
|
75
|
+
`allowing this call because the probe's answer is unknown, not because ` +
|
|
76
|
+
`git answered "no" (#3911).\n`
|
|
77
|
+
);
|
|
78
|
+
} catch {
|
|
79
|
+
// A stderr write failing (EPIPE, closed fd) must never itself change the
|
|
80
|
+
// hook's fail-open outcome.
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = { classifyGitProbe, reportIfUndetermined };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// hooks/lib/hook-exit.js — hand-written, NOT generated. A tiny hook-facing
|
|
3
|
+
// vocabulary layered over hooks/lib/cli-exit.js's terminateNow (ADR-3889 §3),
|
|
4
|
+
// for the 19 enforcement hooks P7/#3911 is migrating off raw process.exit().
|
|
5
|
+
//
|
|
6
|
+
// WHY onCrash is a REQUIRED argument, with no default (this is the entire
|
|
7
|
+
// point of this module): every hook audited for #3911 ended its outer
|
|
8
|
+
// try/catch in ONE of two ways — some `process.exit(0)` (fail open: a hook
|
|
9
|
+
// error must never block a legitimate tool call), others `process.exit(2)`
|
|
10
|
+
// (fail closed: a hook whose whole job is a security/safety denial must not
|
|
11
|
+
// let its own bug wave the denial through). Both are legitimate policies for
|
|
12
|
+
// DIFFERENT hooks — but neither is the "obviously correct" one, and a shared
|
|
13
|
+
// helper that silently defaulted to either would let a future hook inherit
|
|
14
|
+
// the wrong policy by omission, which is exactly the "nothing fails with
|
|
15
|
+
// success" defect ADR-3889 exists to close. Requiring the caller to name its
|
|
16
|
+
// policy at every call site — not just once at module load — makes "I forgot
|
|
17
|
+
// to decide" a load-time/call-time crash instead of a silent behavior. A
|
|
18
|
+
// caller that truly wants "allow on crash" must still write ALLOW; there is
|
|
19
|
+
// no path that reaches terminateNow without a declared choice.
|
|
20
|
+
//
|
|
21
|
+
// crash() itself is total: it does not throw on a bad `onCrash`, because
|
|
22
|
+
// throwing would unwind into the CALLER's own outer catch — the exact
|
|
23
|
+
// fail-open-by-accident hazard this module removes. Instead an unrecognized
|
|
24
|
+
// policy terminates via terminateNow('INTERNAL', ...) with a diagnostic
|
|
25
|
+
// payload naming the offending value, so a typo'd/misspelled policy is
|
|
26
|
+
// debuggable on stderr rather than silently reinterpreted as ALLOW or DENY.
|
|
27
|
+
const { terminateNow } = require('./cli-exit.js');
|
|
28
|
+
|
|
29
|
+
/** Frozen enum of the two declarable crash policies. No third option. */
|
|
30
|
+
const HOOK_ON_CRASH = Object.freeze({
|
|
31
|
+
ALLOW: 'allow',
|
|
32
|
+
DENY: 'deny',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/** Terminate with the hook-protocol PASS outcome (exit 0). */
|
|
36
|
+
function allow(payload) {
|
|
37
|
+
terminateNow('PASS', payload);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Terminate with the hook-protocol deny outcome (exit 2, stdout+stderr).
|
|
42
|
+
*
|
|
43
|
+
* @param {*} payload - JSON-serializable value written to fd 1 (and, when
|
|
44
|
+
* `stderrPayload` is omitted, fd 2 too).
|
|
45
|
+
* @param {*} [stderrPayload] - optional distinct value for fd 2, forwarded
|
|
46
|
+
* verbatim to terminateNow's own `stderrPayload` param — a string is
|
|
47
|
+
* written raw, anything else is JSON-stringified.
|
|
48
|
+
*/
|
|
49
|
+
function deny(payload, stderrPayload) {
|
|
50
|
+
terminateNow('HOOK_DENY', payload, stderrPayload);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Dispatch to allow()/deny() per a DECLARED crash policy. `onCrash` is
|
|
55
|
+
* required — see the module header for why there is no default.
|
|
56
|
+
*
|
|
57
|
+
* @param {'allow'|'deny'} onCrash - one of HOOK_ON_CRASH's two values.
|
|
58
|
+
* @param {*} payload - JSON-serializable value forwarded to terminateNow.
|
|
59
|
+
*/
|
|
60
|
+
function crash(onCrash, payload) {
|
|
61
|
+
if (onCrash === HOOK_ON_CRASH.ALLOW) {
|
|
62
|
+
allow(payload);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (onCrash === HOOK_ON_CRASH.DENY) {
|
|
66
|
+
deny(payload);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Never guess a policy and never throw (a throw would unwind into the
|
|
70
|
+
// caller's own outer catch — the fail-open-by-accident defect this module
|
|
71
|
+
// exists to remove). Terminate deterministically with INTERNAL, naming the
|
|
72
|
+
// offending value so a misspelled/omitted policy is diagnosable.
|
|
73
|
+
terminateNow('INTERNAL', {
|
|
74
|
+
error: 'hook-exit: crash() called with an unrecognized onCrash policy',
|
|
75
|
+
receivedOnCrash: onCrash,
|
|
76
|
+
expectedOneOf: [HOOK_ON_CRASH.ALLOW, HOOK_ON_CRASH.DENY],
|
|
77
|
+
originalPayload: payload,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { HOOK_ON_CRASH, allow, deny, crash };
|
|
@@ -29,6 +29,9 @@ const MANAGED_HOOKS = [
|
|
|
29
29
|
'gsd-cursor-subagent-stop.js',
|
|
30
30
|
'gsd-ensure-canonical-path.js',
|
|
31
31
|
'gsd-graphify-update.sh',
|
|
32
|
+
// #3662: portable node resolver (helper staged in hooks/; managed JS hook
|
|
33
|
+
// commands route through it under --portable-hooks).
|
|
34
|
+
'gsd-node-runner.sh',
|
|
32
35
|
'gsd-phase-boundary.sh',
|
|
33
36
|
'gsd-prompt-guard.js',
|
|
34
37
|
'gsd-read-guard.js',
|
|
@@ -65,6 +65,23 @@ const path = require('path');
|
|
|
65
65
|
const os = require('os');
|
|
66
66
|
const { readSentinel, VALID_ISOLATION, extractDispatchIdentifiers, sentinelAppliesToDispatch } = require('./lib/isolation-sentinel.js');
|
|
67
67
|
const { REASON_CODE } = require('./lib/isolation-deny-reason.js');
|
|
68
|
+
const { HOOK_ON_CRASH, allow, deny, crash } = require('./lib/hook-exit.js');
|
|
69
|
+
|
|
70
|
+
// Required at module top, alongside the other ./lib requires — NOT behind
|
|
71
|
+
// ensureRuntimeBuild() below. Terminating on a parse/timeout failure must
|
|
72
|
+
// never depend on the gitignored build artifacts this hook self-heals for
|
|
73
|
+
// its own registry lookups (#3911).
|
|
74
|
+
//
|
|
75
|
+
// This guard's outer catch (main(), below) has always exited 0 (fail open):
|
|
76
|
+
// that outer catch only covers payload PARSING failing before applicability
|
|
77
|
+
// could even be determined (malformed stdin JSON, etc.) — the guard's real
|
|
78
|
+
// fail-closed logic (a GSD project whose dispatch-isolation configuration
|
|
79
|
+
// cannot be verified) is handled separately, inside evaluateDispatch/
|
|
80
|
+
// resolveIsolationState, and already returns a 'block' decision through the
|
|
81
|
+
// normal exit-2 path rather than through this catch. So an unparseable
|
|
82
|
+
// payload has nothing to enforce; allowing it preserves today's behavior
|
|
83
|
+
// exactly.
|
|
84
|
+
const ON_CRASH = HOOK_ON_CRASH.ALLOW;
|
|
68
85
|
// #3582: gsd-core/bin/lib/*.cjs (runtime-name-policy.cjs, capability-registry.cjs
|
|
69
86
|
// below) are tsc build artifacts (ADR-457), gitignored and absent on a raw
|
|
70
87
|
// plugin-marketplace / git-clone install that never ran `npm run build:lib`.
|
|
@@ -95,40 +112,40 @@ function parseHarnessFlag(flag) {
|
|
|
95
112
|
return { param: m[1], value: m[2] };
|
|
96
113
|
}
|
|
97
114
|
|
|
98
|
-
// ─── #
|
|
115
|
+
// ─── #3897 rung 2: per-install runtime marker, single canonical owner ────────
|
|
99
116
|
// bin/install.js writes `<install>/gsd-core/.gsd-runtime` for EVERY runtime
|
|
100
117
|
// install (#2297), co-located with VERSION. Unlike `~/.gsd/defaults.json` —
|
|
101
118
|
// which is host-wide and names whichever runtime's install ran LAST, the exact
|
|
102
119
|
// leakage #2840's config.cjs change exists to prevent — the marker describes
|
|
103
120
|
// THIS install, which is the property runtime identity needs on a machine
|
|
104
|
-
// with 2+ runtimes.
|
|
105
|
-
// (
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
// consolidating every marker reader into one shared seam.
|
|
110
|
-
let _installMarkerCache; // undefined = unread; null = known absent; string = value
|
|
111
|
-
|
|
121
|
+
// with 2+ runtimes. Previously this hook held its own private reader/cache
|
|
122
|
+
// (one of four #3897 found); it now delegates to the single canonical owner,
|
|
123
|
+
// `src/runtime-slash.cts` (compiled to gsd-core/bin/lib/runtime-slash.cjs),
|
|
124
|
+
// reached through `ensureRuntimeBuild()` like every other compiled-lib require
|
|
125
|
+
// in this file (`scripts/lint-hooks-runtime-build-seam.cjs`).
|
|
112
126
|
function readInstallRuntimeMarker() {
|
|
113
|
-
if (_installMarkerCache !== undefined) return _installMarkerCache;
|
|
114
127
|
try {
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
128
|
+
ensureRuntimeBuild();
|
|
129
|
+
const runtimeSlash = require('../gsd-core/bin/lib/runtime-slash.cjs');
|
|
130
|
+
return runtimeSlash.readInstallRuntimeMarker();
|
|
118
131
|
} catch {
|
|
119
|
-
//
|
|
120
|
-
// from this rung", never a resolution failure.
|
|
121
|
-
|
|
122
|
-
_installMarkerCache = null;
|
|
132
|
+
// Unbuilt runtime library, or any other failure reaching the canonical
|
|
133
|
+
// owner — "no signal from this rung" (N4), never a resolution failure.
|
|
134
|
+
return null;
|
|
123
135
|
}
|
|
124
|
-
return _installMarkerCache;
|
|
125
136
|
}
|
|
126
137
|
|
|
127
|
-
// Test seam for the marker rung
|
|
128
|
-
//
|
|
129
|
-
// model-resolver.cts's _setInstallRuntimeMarkerForTests, #2297).
|
|
138
|
+
// Test seam for the marker rung — forwards to the canonical owner's seam so
|
|
139
|
+
// this hook and runtime-slash.cjs always share one cache (#3897 rung 2).
|
|
130
140
|
function _setInstallRuntimeMarkerForTests(value) {
|
|
131
|
-
|
|
141
|
+
try {
|
|
142
|
+
ensureRuntimeBuild();
|
|
143
|
+
const runtimeSlash = require('../gsd-core/bin/lib/runtime-slash.cjs');
|
|
144
|
+
runtimeSlash._setInstallRuntimeMarkerForTests(value);
|
|
145
|
+
} catch {
|
|
146
|
+
// Test-only seam; an unbuilt library here means the test itself will fail
|
|
147
|
+
// downstream, which is a louder and more actionable signal than throwing here.
|
|
148
|
+
}
|
|
132
149
|
}
|
|
133
150
|
|
|
134
151
|
/**
|
|
@@ -282,18 +299,40 @@ function resolveRegistryIsolation(cwd, configPath) {
|
|
|
282
299
|
|
|
283
300
|
if (isolation === 'harness-worktree') {
|
|
284
301
|
let useWorktrees = true;
|
|
302
|
+
// #3972: the opt-out read shares the ONE owner every other
|
|
303
|
+
// isolation-deciding surface uses — planning-workspace's
|
|
304
|
+
// worktreesOptedOut ladder (scoped own-key wins; root inherited under
|
|
305
|
+
// the GSD_WORKSTREAM gate; strict === false). A flat single-file read
|
|
306
|
+
// here made a workstream-LOCAL opt-out invisible, so the sentinel-absent
|
|
307
|
+
// fallback denied a sequential dispatch the config explicitly allowed.
|
|
308
|
+
// Reached through ensureRuntimeBuild() like every other compiled-lib
|
|
309
|
+
// require in this file (scripts/lint-hooks-runtime-build-seam.cjs); if
|
|
310
|
+
// the library is unreachable the flat-root read below remains as the
|
|
311
|
+
// degraded fallback — today's behavior, never worse.
|
|
312
|
+
let ladderAnswered = false;
|
|
285
313
|
try {
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
useWorktrees = false;
|
|
291
|
-
}
|
|
314
|
+
ensureRuntimeBuild();
|
|
315
|
+
const { worktreesOptedOut } = require('../gsd-core/bin/lib/planning-workspace.cjs');
|
|
316
|
+
useWorktrees = !worktreesOptedOut(cwd);
|
|
317
|
+
ladderAnswered = true;
|
|
292
318
|
} catch {
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
|
|
296
|
-
|
|
319
|
+
// Unbuilt runtime library or a ladder failure — fall through to the
|
|
320
|
+
// legacy flat-root read (conservative: keep enforcing).
|
|
321
|
+
}
|
|
322
|
+
if (!ladderAnswered) {
|
|
323
|
+
try {
|
|
324
|
+
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
325
|
+
const parsedCfg = JSON.parse(raw);
|
|
326
|
+
if (parsedCfg && typeof parsedCfg === 'object' && parsedCfg.workflow &&
|
|
327
|
+
typeof parsedCfg.workflow === 'object' && parsedCfg.workflow.use_worktrees === false) {
|
|
328
|
+
useWorktrees = false;
|
|
329
|
+
}
|
|
330
|
+
} catch {
|
|
331
|
+
// Unreadable config already propagated to the outer caller's catch
|
|
332
|
+
// before this point in practice (resolveRuntimeIdentity reads it
|
|
333
|
+
// first); tolerate defensively and keep the conservative (enforce)
|
|
334
|
+
// default rather than silently disabling the guard.
|
|
335
|
+
}
|
|
297
336
|
}
|
|
298
337
|
if (!useWorktrees) isolation = 'none';
|
|
299
338
|
}
|
|
@@ -476,7 +515,7 @@ function evaluateDispatch(data, { clock = Date } = {}) {
|
|
|
476
515
|
/* istanbul ignore next -- stdin adapter, exercised via spawnSync in tests */
|
|
477
516
|
function main() {
|
|
478
517
|
let input = '';
|
|
479
|
-
const stdinTimeout = setTimeout(() =>
|
|
518
|
+
const stdinTimeout = setTimeout(() => allow(undefined), 3000);
|
|
480
519
|
process.stdin.setEncoding('utf8');
|
|
481
520
|
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
482
521
|
process.stdin.on('end', () => {
|
|
@@ -486,18 +525,18 @@ function main() {
|
|
|
486
525
|
const decision = evaluateDispatch(data);
|
|
487
526
|
if (decision.action === 'block') {
|
|
488
527
|
const out = { decision: 'block', reason: decision.reason, reason_code: decision.reasonCode };
|
|
489
|
-
process.stdout.write(JSON.stringify(out));
|
|
490
528
|
// Kimi feeds stderr (not stdout) back to the model on exit 2.
|
|
491
|
-
|
|
492
|
-
process.exit(2);
|
|
529
|
+
deny(out, decision.reason);
|
|
493
530
|
}
|
|
494
|
-
|
|
531
|
+
allow(undefined);
|
|
495
532
|
} catch {
|
|
496
533
|
// Silent fail — never block valid tool calls due to hook errors
|
|
497
534
|
// (malformed payload, etc.). This is distinct from resolveIsolationState's
|
|
498
535
|
// internal error handling, which DOES deny — this outer catch only
|
|
499
536
|
// covers payload parsing before applicability could even be determined.
|
|
500
|
-
|
|
537
|
+
// ON_CRASH is declared ALLOW at module top: this preserves today's
|
|
538
|
+
// exit(0) fail-open behavior exactly (#3911).
|
|
539
|
+
crash(ON_CRASH, undefined);
|
|
501
540
|
}
|
|
502
541
|
});
|
|
503
542
|
}
|
|
@@ -20,11 +20,17 @@
|
|
|
20
20
|
|
|
21
21
|
const fs = require('fs');
|
|
22
22
|
const path = require('path');
|
|
23
|
+
const { HOOK_ON_CRASH, allow, crash } = require('./lib/hook-exit.js');
|
|
24
|
+
|
|
25
|
+
// This hook only injects an advisory config-reload summary; a crash mid-parse
|
|
26
|
+
// must not block the session or the FileChanged event that triggered it — the
|
|
27
|
+
// agent simply keeps using the config context it already had (#3911).
|
|
28
|
+
const ON_CRASH = HOOK_ON_CRASH.ALLOW;
|
|
23
29
|
|
|
24
30
|
let input = '';
|
|
25
31
|
// Timeout guard: if stdin does not close within 8s exit silently rather than
|
|
26
32
|
// hanging until Claude Code kills the process and reports "hook error".
|
|
27
|
-
const stdinTimeout = setTimeout(() =>
|
|
33
|
+
const stdinTimeout = setTimeout(() => allow(undefined), 8000);
|
|
28
34
|
process.stdin.setEncoding('utf8');
|
|
29
35
|
process.stdin.on('data', chunk => (input += chunk));
|
|
30
36
|
process.stdin.on('end', () => {
|
|
@@ -42,24 +48,23 @@ process.stdin.on('end', () => {
|
|
|
42
48
|
// inject spurious additionalContext.
|
|
43
49
|
const basename = path.basename(filePath);
|
|
44
50
|
if (basename !== 'config.json') {
|
|
45
|
-
|
|
51
|
+
allow(undefined);
|
|
46
52
|
}
|
|
47
53
|
const expectedPath = path.resolve(cwd, '.planning', 'config.json');
|
|
48
54
|
if (path.resolve(filePath) !== expectedPath) {
|
|
49
|
-
|
|
55
|
+
allow(undefined);
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
// On unlink (deletion) emit a brief notice and exit
|
|
53
59
|
if (event === 'unlink') {
|
|
54
|
-
|
|
60
|
+
allow({
|
|
55
61
|
hookSpecificOutput: {
|
|
56
62
|
hookEventName: 'FileChanged',
|
|
57
63
|
additionalContext:
|
|
58
64
|
'GSD config (.planning/config.json) was deleted. ' +
|
|
59
65
|
'Falling back to built-in defaults for this session.',
|
|
60
66
|
},
|
|
61
|
-
})
|
|
62
|
-
process.exit(0);
|
|
67
|
+
});
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
// Read the updated config file
|
|
@@ -68,17 +73,16 @@ process.stdin.on('end', () => {
|
|
|
68
73
|
const raw = fs.readFileSync(filePath, 'utf8');
|
|
69
74
|
config = JSON.parse(raw);
|
|
70
75
|
} catch (e) {
|
|
71
|
-
if (e && e.code === 'ENOENT')
|
|
76
|
+
if (e && e.code === 'ENOENT') allow(undefined);
|
|
72
77
|
// Malformed JSON — inform the agent without crashing
|
|
73
|
-
|
|
78
|
+
allow({
|
|
74
79
|
hookSpecificOutput: {
|
|
75
80
|
hookEventName: 'FileChanged',
|
|
76
81
|
additionalContext:
|
|
77
82
|
'GSD config (.planning/config.json) was modified but could not be parsed. ' +
|
|
78
83
|
'Check the file for JSON syntax errors.',
|
|
79
84
|
},
|
|
80
|
-
})
|
|
81
|
-
process.exit(0);
|
|
85
|
+
});
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
// Build a concise summary of key config fields the agent cares about
|
|
@@ -127,7 +131,9 @@ process.stdin.on('end', () => {
|
|
|
127
131
|
},
|
|
128
132
|
}));
|
|
129
133
|
} catch (e) {
|
|
130
|
-
// Silent fail — never block the session on a config reload error
|
|
131
|
-
|
|
134
|
+
// Silent fail — never block the session on a config reload error.
|
|
135
|
+
// ON_CRASH is declared ALLOW at module top: this preserves today's
|
|
136
|
+
// exit(0) fail-open behavior exactly (#3911).
|
|
137
|
+
crash(ON_CRASH, undefined);
|
|
132
138
|
}
|
|
133
139
|
});
|
|
@@ -22,6 +22,13 @@ const fs = require('fs');
|
|
|
22
22
|
const os = require('os');
|
|
23
23
|
const path = require('path');
|
|
24
24
|
const { spawn } = require('child_process');
|
|
25
|
+
const { HOOK_ON_CRASH, allow, crash } = require('./lib/hook-exit.js');
|
|
26
|
+
|
|
27
|
+
// This hook only injects an advisory context-usage warning; it never blocks
|
|
28
|
+
// the tool call it rides in on. A crash here (e.g. a malformed bridge file)
|
|
29
|
+
// must not turn a PostToolUse advisory into a blocked tool call — losing a
|
|
30
|
+
// context warning is far cheaper than stalling the agent's work (#3911).
|
|
31
|
+
const ON_CRASH = HOOK_ON_CRASH.ALLOW;
|
|
25
32
|
|
|
26
33
|
const WARNING_THRESHOLD = 35; // remaining_percentage <= 35%
|
|
27
34
|
const CRITICAL_THRESHOLD = 25; // remaining_percentage <= 25%
|
|
@@ -33,7 +40,7 @@ let input = '';
|
|
|
33
40
|
// Windows/Git Bash, or slow Claude Code piping during large outputs),
|
|
34
41
|
// exit silently instead of hanging until Claude Code kills the process
|
|
35
42
|
// and reports "hook error". See #775, #1162.
|
|
36
|
-
const stdinTimeout = setTimeout(() =>
|
|
43
|
+
const stdinTimeout = setTimeout(() => allow(undefined), 10000);
|
|
37
44
|
process.stdin.setEncoding('utf8');
|
|
38
45
|
process.stdin.on('data', chunk => input += chunk);
|
|
39
46
|
process.stdin.on('end', () => {
|
|
@@ -43,14 +50,14 @@ process.stdin.on('end', () => {
|
|
|
43
50
|
const sessionId = data.session_id;
|
|
44
51
|
|
|
45
52
|
if (!sessionId) {
|
|
46
|
-
|
|
53
|
+
allow(undefined);
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
// Reject session IDs that contain path traversal sequences or path separators.
|
|
50
57
|
// session_id is used to construct file paths in /tmp — an unsanitized value
|
|
51
58
|
// could escape the temp directory and read or write arbitrary files.
|
|
52
59
|
if (/[/\\]|\.\./.test(sessionId)) {
|
|
53
|
-
|
|
60
|
+
allow(undefined);
|
|
54
61
|
}
|
|
55
62
|
|
|
56
63
|
// Check if context warnings are disabled via config.
|
|
@@ -61,7 +68,7 @@ process.stdin.on('end', () => {
|
|
|
61
68
|
const configPath = path.join(cwd, '.planning', 'config.json');
|
|
62
69
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
63
70
|
if (config.hooks?.context_warnings === false) {
|
|
64
|
-
|
|
71
|
+
allow(undefined);
|
|
65
72
|
}
|
|
66
73
|
} catch (e) {
|
|
67
74
|
// Missing or unparseable config → proceed with defaults (context warnings enabled)
|
|
@@ -77,7 +84,7 @@ process.stdin.on('end', () => {
|
|
|
77
84
|
try {
|
|
78
85
|
metricsRaw = fs.readFileSync(metricsPath, 'utf8');
|
|
79
86
|
} catch (e) {
|
|
80
|
-
if (e && e.code === 'ENOENT')
|
|
87
|
+
if (e && e.code === 'ENOENT') allow(undefined);
|
|
81
88
|
throw e;
|
|
82
89
|
}
|
|
83
90
|
const metrics = JSON.parse(metricsRaw);
|
|
@@ -85,7 +92,7 @@ process.stdin.on('end', () => {
|
|
|
85
92
|
|
|
86
93
|
// Ignore stale metrics
|
|
87
94
|
if (metrics.timestamp && (now - metrics.timestamp) > STALE_SECONDS) {
|
|
88
|
-
|
|
95
|
+
allow(undefined);
|
|
89
96
|
}
|
|
90
97
|
|
|
91
98
|
const remaining = metrics.remaining_percentage;
|
|
@@ -93,7 +100,7 @@ process.stdin.on('end', () => {
|
|
|
93
100
|
|
|
94
101
|
// No warning needed
|
|
95
102
|
if (remaining > WARNING_THRESHOLD) {
|
|
96
|
-
|
|
103
|
+
allow(undefined);
|
|
97
104
|
}
|
|
98
105
|
|
|
99
106
|
// Debounce: check if we warned recently
|
|
@@ -121,7 +128,7 @@ process.stdin.on('end', () => {
|
|
|
121
128
|
if (!firstWarn && warnData.callsSinceWarn < DEBOUNCE_CALLS && !severityEscalated) {
|
|
122
129
|
// Update counter and exit without warning
|
|
123
130
|
fs.writeFileSync(warnPath, JSON.stringify(warnData));
|
|
124
|
-
|
|
131
|
+
allow(undefined);
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
// Reset debounce counter
|
|
@@ -208,7 +215,9 @@ process.stdin.on('end', () => {
|
|
|
208
215
|
process.stdout.write(JSON.stringify(output));
|
|
209
216
|
}
|
|
210
217
|
} catch (e) {
|
|
211
|
-
// Silent fail -- never block tool execution
|
|
212
|
-
|
|
218
|
+
// Silent fail -- never block tool execution.
|
|
219
|
+
// ON_CRASH is declared ALLOW at module top: this preserves today's
|
|
220
|
+
// exit(0) fail-open behavior exactly (#3911).
|
|
221
|
+
crash(ON_CRASH, undefined);
|
|
213
222
|
}
|
|
214
223
|
});
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
|
|
23
23
|
'use strict';
|
|
24
24
|
|
|
25
|
+
const { allow } = require('./lib/hook-exit.js');
|
|
26
|
+
|
|
25
27
|
const WRITE_TOOL_RE = /write|edit|replace|create|delete|remove|append|apply|patch|insert|mkdir/i;
|
|
26
28
|
const PATH_KEY_RE = /^(path|file|file_?path|filepath|target_?path|target|dir|directory|uri|filename)$/i;
|
|
27
29
|
const PLANNING_PATH_RE = /(^|[\\/])\.planning([\\/]|$)/;
|
|
@@ -29,7 +31,7 @@ const PLANNING_PATH_RE = /(^|[\\/])\.planning([\\/]|$)/;
|
|
|
29
31
|
let raw = '';
|
|
30
32
|
const stdinTimeout = setTimeout(() => {
|
|
31
33
|
// Timeout guard: exit silently rather than hanging.
|
|
32
|
-
|
|
34
|
+
allow(undefined);
|
|
33
35
|
}, 10000);
|
|
34
36
|
|
|
35
37
|
process.stdin.setEncoding('utf8');
|
|
@@ -22,13 +22,15 @@
|
|
|
22
22
|
|
|
23
23
|
'use strict';
|
|
24
24
|
|
|
25
|
+
const { allow } = require('./lib/hook-exit.js');
|
|
26
|
+
|
|
25
27
|
const WRITE_TOOL_RE = /write|edit|replace|create|delete|remove|append|apply|patch|insert|mkdir/i;
|
|
26
28
|
const PATH_KEY_RE = /^(path|file|file_?path|filepath|target_?path|target|dir|directory|uri|filename)$/i;
|
|
27
29
|
const PLANNING_PATH_RE = /(^|[\\/])\.planning([\\/]|$)/;
|
|
28
30
|
|
|
29
31
|
let raw = '';
|
|
30
32
|
const stdinTimeout = setTimeout(() => {
|
|
31
|
-
|
|
33
|
+
allow(undefined);
|
|
32
34
|
}, 10000);
|
|
33
35
|
|
|
34
36
|
process.stdin.setEncoding('utf8');
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
'use strict';
|
|
24
24
|
|
|
25
25
|
const fs = require('fs');
|
|
26
|
+
const { allow } = require('./lib/hook-exit.js');
|
|
26
27
|
|
|
27
28
|
const MSG_PRESENT =
|
|
28
29
|
'GSD: .planning/STATE.md is present — review the current phase and any blockers before acting.';
|
|
@@ -37,7 +38,7 @@ const { resolveStatePath } = require('./lib/cursor-workspace.js');
|
|
|
37
38
|
let raw = '';
|
|
38
39
|
const stdinTimeout = setTimeout(() => {
|
|
39
40
|
// Timeout guard: exit silently rather than hanging.
|
|
40
|
-
|
|
41
|
+
allow(undefined);
|
|
41
42
|
}, 10000);
|
|
42
43
|
|
|
43
44
|
process.stdin.setEncoding('utf8');
|
package/hooks/gsd-cursor-stop.js
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
'use strict';
|
|
22
22
|
|
|
23
23
|
const fs = require('fs');
|
|
24
|
+
const { allow } = require('./lib/hook-exit.js');
|
|
24
25
|
|
|
25
26
|
// Workspace resolution is shared across the Cursor hooks (#2587) — see
|
|
26
27
|
// hooks/lib/cursor-workspace.js. Staged next to these scripts by
|
|
@@ -29,7 +30,7 @@ const { resolveStatePath } = require('./lib/cursor-workspace.js');
|
|
|
29
30
|
|
|
30
31
|
let raw = '';
|
|
31
32
|
const stdinTimeout = setTimeout(() => {
|
|
32
|
-
|
|
33
|
+
allow(undefined);
|
|
33
34
|
}, 10000);
|
|
34
35
|
|
|
35
36
|
process.stdin.setEncoding('utf8');
|