@opengsd/gsd-core 1.13.0 → 1.15.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/README.ja-JP.md +3 -3
- package/README.ko-KR.md +3 -3
- package/README.pt-BR.md +3 -3
- package/README.zh-CN.md +3 -3
- package/agents/gsd-advisor-researcher.compact.md +85 -0
- package/agents/gsd-ai-researcher.compact.md +96 -0
- package/agents/gsd-assumptions-analyzer.compact.md +81 -0
- package/agents/gsd-code-fixer.compact.md +459 -0
- package/agents/gsd-code-fixer.md +9 -8
- package/agents/gsd-code-reviewer.compact.md +269 -0
- package/agents/gsd-code-reviewer.md +15 -3
- package/agents/gsd-codebase-mapper.compact.md +760 -0
- package/agents/gsd-debug-session-manager.compact.md +360 -0
- package/agents/gsd-debug-session-manager.md +17 -2
- package/agents/gsd-debugger.md +2 -2
- package/agents/gsd-doc-classifier.compact.md +192 -0
- package/agents/gsd-doc-synthesizer.compact.md +200 -0
- package/agents/gsd-doc-verifier.compact.md +143 -0
- package/agents/gsd-doc-writer.compact.md +440 -0
- package/agents/gsd-dom-verifier.compact.md +138 -0
- package/agents/gsd-domain-researcher.compact.md +141 -0
- package/agents/gsd-eval-auditor.compact.md +160 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-eval-planner.compact.md +137 -0
- package/agents/gsd-executor.md +13 -8
- package/agents/gsd-framework-selector.compact.md +82 -0
- package/agents/gsd-integration-checker.compact.md +245 -0
- package/agents/gsd-intel-updater.compact.md +226 -0
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.compact.md +45 -0
- package/agents/gsd-nyquist-auditor.compact.md +179 -0
- package/agents/gsd-pattern-mapper.compact.md +275 -0
- package/agents/gsd-phase-researcher.md +19 -11
- package/agents/gsd-plan-checker.md +8 -7
- package/agents/gsd-planner.md +12 -8
- package/agents/gsd-project-researcher.compact.md +587 -0
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +212 -0
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-roadmapper.compact.md +454 -0
- package/agents/gsd-roadmapper.md +13 -0
- package/agents/gsd-security-auditor.compact.md +162 -0
- package/agents/gsd-ui-auditor.compact.md +404 -0
- package/agents/gsd-ui-auditor.md +155 -17
- package/agents/gsd-ui-checker.compact.md +277 -0
- package/agents/gsd-ui-researcher.compact.md +282 -0
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-user-profiler.compact.md +108 -0
- package/agents/gsd-verifier.md +10 -9
- package/bin/install.js +848 -163
- package/commands/gsd/autonomous.md +2 -2
- package/commands/gsd/capture.md +1 -1
- package/commands/gsd/cleanup.md +1 -0
- package/commands/gsd/code-review.md +2 -1
- package/commands/gsd/complete-milestone.md +1 -0
- package/commands/gsd/config.md +1 -0
- package/commands/gsd/debug.md +1 -0
- package/commands/gsd/graphify.md +1 -0
- package/commands/gsd/health.md +1 -0
- package/commands/gsd/mempalace-capture.md +8 -3
- package/commands/gsd/mempalace-recall.md +1 -0
- package/commands/gsd/new-milestone.md +1 -0
- package/commands/gsd/new-project.md +1 -0
- package/commands/gsd/next.md +1 -0
- package/commands/gsd/pause-work.md +1 -0
- package/commands/gsd/phase.md +1 -0
- package/commands/gsd/plan-review-convergence.md +6 -6
- package/commands/gsd/pr-branch.md +1 -0
- package/commands/gsd/progress.md +1 -1
- package/commands/gsd/quick-batch.md +1 -1
- package/commands/gsd/resume-work.md +1 -0
- package/commands/gsd/review-backlog.md +1 -0
- package/commands/gsd/review.md +2 -3
- package/commands/gsd/settings.md +2 -1
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/thread.md +1 -0
- package/commands/gsd/workspace.md +1 -0
- package/commands/gsd/workstreams.md +1 -0
- package/gsd-core/bin/check-latest-version.cjs +8 -3
- package/gsd-core/bin/gsd-tools.cjs +672 -146
- package/gsd-core/bin/lib/adr-parser.cjs +4 -2
- package/gsd-core/bin/lib/artifacts.cjs +2 -1
- package/gsd-core/bin/lib/audit.cjs +119 -34
- package/gsd-core/bin/lib/broken-windows.cjs +168 -49
- package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
- package/gsd-core/bin/lib/capability-loader.cjs +135 -1
- package/gsd-core/bin/lib/capability-registry.cjs +96 -189
- package/gsd-core/bin/lib/capability-source.cjs +19 -2
- package/gsd-core/bin/lib/capability-validator.cjs +14 -2
- package/gsd-core/bin/lib/check-command-router.cjs +213 -49
- package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
- package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
- package/gsd-core/bin/lib/commands.cjs +823 -112
- package/gsd-core/bin/lib/config-loader.cjs +66 -4
- package/gsd-core/bin/lib/config.cjs +186 -45
- package/gsd-core/bin/lib/coverage.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +164 -45
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
- package/gsd-core/bin/lib/frontmatter.cjs +13 -0
- package/gsd-core/bin/lib/graphify.cjs +10 -2
- package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
- package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
- package/gsd-core/bin/lib/init.cjs +614 -86
- package/gsd-core/bin/lib/install-engine.cjs +29 -3
- package/gsd-core/bin/lib/install-profiles.cjs +14 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +41 -5
- package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
- package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
- package/gsd-core/bin/lib/milestone.cjs +37 -13
- package/gsd-core/bin/lib/model-resolver.cjs +253 -53
- package/gsd-core/bin/lib/phase-command-router.cjs +16 -2
- package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
- package/gsd-core/bin/lib/phase-id.cjs +268 -27
- package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
- package/gsd-core/bin/lib/phase-locator.cjs +29 -10
- package/gsd-core/bin/lib/phase.cjs +393 -88
- package/gsd-core/bin/lib/plan-document.cjs +49 -1
- package/gsd-core/bin/lib/planning-document.cjs +459 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +52 -19
- package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
- package/gsd-core/bin/lib/planning-workspace.cjs +57 -3
- package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
- package/gsd-core/bin/lib/probe-core.cjs +7 -1
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
- package/gsd-core/bin/lib/project-root.cjs +41 -2
- package/gsd-core/bin/lib/quick-batch.cjs +1 -1
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
- package/gsd-core/bin/lib/research-store.cjs +11 -12
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
- package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
- package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
- package/gsd-core/bin/lib/roadmap-parser.cjs +219 -18
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
- package/gsd-core/bin/lib/roadmap.cjs +356 -42
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -41
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +15 -4
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
- package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -37
- package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
- package/gsd-core/bin/lib/security.cjs +126 -7
- package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
- package/gsd-core/bin/lib/state-document.cjs +130 -28
- package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
- package/gsd-core/bin/lib/state-transition.cjs +181 -30
- package/gsd-core/bin/lib/state.cjs +265 -27
- package/gsd-core/bin/lib/surface.cjs +77 -3
- package/gsd-core/bin/lib/task-command-router.cjs +12 -6
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
- package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
- package/gsd-core/bin/lib/uat.cjs +9 -1
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
- package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
- package/gsd-core/bin/lib/update-context.cjs +30 -24
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
- package/gsd-core/bin/lib/verification.cjs +315 -30
- package/gsd-core/bin/lib/verify-command-grounding.cjs +47 -3
- package/gsd-core/bin/lib/verify.cjs +320 -48
- package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
- package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
- package/gsd-core/bin/lib/worktree-safety.cjs +797 -58
- package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +6 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
- package/gsd-core/references/checkpoints.md +5 -3
- package/gsd-core/references/compact-content-gate.md +66 -0
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe.md +195 -21
- package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
- package/gsd-core/references/execute-phase-wave-guard.md +22 -11
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +18 -0
- package/gsd-core/references/model-profiles.md +13 -4
- package/gsd-core/references/phase-argument-parsing.md +9 -7
- package/gsd-core/references/phase-id-convention.md +28 -0
- package/gsd-core/references/planner-gap-closure.md +2 -0
- package/gsd-core/references/planner-load-graph-context.md +24 -13
- package/gsd-core/references/planner-verify-command-grounding.md +14 -0
- package/gsd-core/references/planning-config.md +14 -2
- package/gsd-core/references/tdd.md +30 -4
- package/gsd-core/references/thinking-models-planning.md +18 -2
- package/gsd-core/references/ui-consideration-probe.md +10 -5
- package/gsd-core/references/verification-patterns.md +17 -4
- package/gsd-core/references/verify-command-path-resolvability.md +10 -2
- package/gsd-core/references/worktree-path-safety.md +433 -2
- package/gsd-core/templates/README.md +7 -1
- package/gsd-core/templates/state.md +6 -3
- package/gsd-core/templates/summary.compact.md +212 -0
- package/gsd-core/templates/user-setup.compact.md +199 -0
- package/gsd-core/templates/user-setup.md +0 -9
- 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 +1 -1
- package/gsd-core/workflows/add-tests.md +2 -2
- package/gsd-core/workflows/add-todo.md +6 -5
- package/gsd-core/workflows/ai-integration-phase.md +11 -3
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +1 -1
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
- package/gsd-core/workflows/autonomous.md +29 -16
- package/gsd-core/workflows/check-todos.md +6 -4
- package/gsd-core/workflows/cleanup.md +5 -3
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +8 -1
- package/gsd-core/workflows/code-review-fix.md +108 -22
- package/gsd-core/workflows/code-review.md +216 -73
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
- package/gsd-core/workflows/complete-milestone.md +41 -264
- package/gsd-core/workflows/debug.md +3 -3
- package/gsd-core/workflows/diagnose-issues.md +1 -1
- 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 +1 -1
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +2 -2
- package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
- package/gsd-core/workflows/docs-update.md +17 -158
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +10 -3
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +19 -4
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +43 -4
- package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
- 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 +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
- package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
- package/gsd-core/workflows/execute-phase.md +83 -172
- package/gsd-core/workflows/execute-plan.md +24 -10
- package/gsd-core/workflows/explore.md +3 -3
- package/gsd-core/workflows/extract-learnings.md +2 -1
- 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 -2
- package/gsd-core/workflows/help/modes/full.compact.md +398 -0
- package/gsd-core/workflows/help/modes/full.md +5 -5
- package/gsd-core/workflows/help/modes/topic.md +15 -5
- package/gsd-core/workflows/help.md +1 -1
- package/gsd-core/workflows/import.md +2 -2
- package/gsd-core/workflows/inbox.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -3
- package/gsd-core/workflows/insert-phase.md +1 -1
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +2 -2
- package/gsd-core/workflows/map-codebase.md +52 -5
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +56 -14
- package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
- package/gsd-core/workflows/new-project.md +39 -209
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/note.md +1 -1
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
- package/gsd-core/workflows/plan-phase.md +45 -187
- package/gsd-core/workflows/plan-review-convergence.md +21 -5
- package/gsd-core/workflows/plant-seed.md +62 -20
- package/gsd-core/workflows/pr-branch.md +132 -20
- package/gsd-core/workflows/profile-user.md +2 -2
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
- 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 +1 -1
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +1 -1
- package/gsd-core/workflows/quick.md +29 -10
- package/gsd-core/workflows/reapply-patches.md +86 -6
- package/gsd-core/workflows/remove-phase.md +1 -1
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +1 -1
- package/gsd-core/workflows/review.md +31 -16
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -2
- package/gsd-core/workflows/settings-advanced.md +30 -10
- package/gsd-core/workflows/settings-integrations.md +2 -3
- package/gsd-core/workflows/settings.md +22 -9
- package/gsd-core/workflows/ship.md +3 -2
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +2 -2
- package/gsd-core/workflows/spec-phase.md +15 -5
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +5 -5
- package/gsd-core/workflows/thread.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +44 -8
- package/gsd-core/workflows/ui-review.md +18 -4
- package/gsd-core/workflows/ultraplan-phase.md +1 -1
- package/gsd-core/workflows/undo.md +339 -20
- package/gsd-core/workflows/update.md +14 -12
- package/gsd-core/workflows/validate-phase.md +3 -2
- package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +101 -196
- package/hooks/dist/gsd-agent-isolation-guard.js +66 -16
- package/hooks/dist/gsd-context-monitor.js +88 -15
- package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
- package/hooks/dist/gsd-secret-read-guard.js +71 -19
- package/hooks/dist/gsd-statusline.js +81 -20
- package/hooks/dist/gsd-validate-commit.sh +97 -8
- package/hooks/dist/gsd-worktree-path-guard.js +25 -14
- package/hooks/dist/gsd-write-guard.js +46 -1
- package/hooks/dist/lib/dispatch-identity.js +187 -0
- package/hooks/dist/lib/filename-classification.js +64 -0
- package/hooks/dist/lib/isolation-deny-reason.js +53 -1
- package/hooks/dist/lib/isolation-sentinel.js +58 -19
- package/hooks/gsd-agent-isolation-guard.js +66 -16
- package/hooks/gsd-context-monitor.js +88 -15
- package/hooks/gsd-cursor-subagent-start.js +34 -14
- package/hooks/gsd-secret-read-guard.js +71 -19
- package/hooks/gsd-statusline.js +81 -20
- package/hooks/gsd-validate-commit.sh +97 -8
- package/hooks/gsd-worktree-path-guard.js +25 -14
- package/hooks/gsd-write-guard.js +46 -1
- package/hooks/lib/dispatch-identity.js +187 -0
- package/hooks/lib/filename-classification.js +64 -0
- package/hooks/lib/isolation-deny-reason.js +53 -1
- package/hooks/lib/isolation-sentinel.js +58 -19
- package/package.json +11 -6
- package/scripts/benchmark-compact-content-variants.cjs +298 -0
- package/scripts/benchmark-compact-content.cjs +368 -0
- package/scripts/build-hooks.js +15 -6
- package/scripts/check-contract-drift.cjs +131 -12
- package/scripts/check-env.cjs +36 -8
- package/scripts/check-glossary-refs.cjs +25 -21
- package/scripts/ci-next-health.cjs +271 -0
- package/scripts/ci-prepare-test-scope.cjs +7 -7
- package/scripts/ci-test-scope.cjs +126 -20
- package/scripts/ci-timeout-report.cjs +1 -1
- package/scripts/command-contract-helpers.cjs +3 -0
- package/scripts/diff-touches-shipped-paths.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +35 -2
- package/scripts/gen-adr-index.cjs +8 -2
- package/scripts/gen-inventory-manifest.cjs +12 -0
- package/scripts/gen-loop-host-contract.cjs +69 -0
- package/scripts/gen-platform-conformance-tier.cjs +557 -0
- package/scripts/lib/drift-scan.cjs +1 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +224 -0
- package/scripts/lib/ndjson-reporter.cjs +3 -2
- package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +287 -0
- package/scripts/lib/suite-detection.cjs +32 -0
- package/scripts/lint-allowed-tools-parity.cjs +221 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +47 -3
- package/scripts/lint-phase-arg-assignment.cjs +257 -0
- package/scripts/lint-phase-id-drift.cjs +623 -13
- package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
- package/scripts/lint-response-language-coverage.cjs +9 -3
- package/scripts/lint-retired-runtime-name.cjs +619 -0
- package/scripts/lint-source-test-name-collision.cjs +1 -1
- package/scripts/lint-state-write-path-drift.cjs +93 -0
- package/scripts/lint-test-file-count.allowlist.json +29 -9
- package/scripts/lint-vendored-deps.cjs +128 -17
- package/scripts/lint-workflow-shellcheck-baseline.json +100 -0
- package/scripts/prompt-injection-scan.sh +18 -0
- package/scripts/release-tarball-smoke.cjs +194 -1
- package/scripts/workflow-size.cjs +139 -0
- package/skills/gsd-autonomous/SKILL.md +2 -2
- package/skills/gsd-capture/SKILL.md +1 -1
- package/skills/gsd-cleanup/SKILL.md +1 -0
- package/skills/gsd-code-review/SKILL.md +2 -1
- package/skills/gsd-complete-milestone/SKILL.md +1 -0
- package/skills/gsd-config/SKILL.md +1 -0
- package/skills/gsd-debug/SKILL.md +1 -0
- package/skills/gsd-graphify/SKILL.md +1 -0
- package/skills/gsd-health/SKILL.md +1 -0
- package/skills/gsd-mempalace-capture/SKILL.md +8 -3
- package/skills/gsd-mempalace-recall/SKILL.md +1 -0
- package/skills/gsd-new-milestone/SKILL.md +1 -0
- package/skills/gsd-new-project/SKILL.md +1 -0
- package/skills/gsd-next/SKILL.md +1 -0
- package/skills/gsd-pause-work/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -0
- package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
- package/skills/gsd-pr-branch/SKILL.md +1 -0
- package/skills/gsd-progress/SKILL.md +1 -1
- package/skills/gsd-quick-batch/SKILL.md +1 -1
- package/skills/gsd-resume-work/SKILL.md +1 -0
- package/skills/gsd-review/SKILL.md +2 -3
- package/skills/gsd-review-backlog/SKILL.md +1 -0
- package/skills/gsd-settings/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +1 -0
- package/skills/gsd-thread/SKILL.md +1 -0
- package/skills/gsd-workspace/SKILL.md +1 -0
- package/skills/gsd-workstreams/SKILL.md +1 -0
- package/vscode/package.json +1 -1
- package/gsd-core/templates/claude-md.md +0 -145
- package/gsd-core/templates/codebase/concerns.md +0 -310
- package/gsd-core/templates/codebase/conventions.md +0 -307
- package/gsd-core/templates/codebase/integrations.md +0 -280
- package/gsd-core/templates/codebase/structure.md +0 -285
- package/gsd-core/templates/codebase/testing.md +0 -480
- package/gsd-core/templates/debug-subagent-prompt.md +0 -91
- package/gsd-core/templates/discovery.md +0 -146
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
|
|
57
57
|
const fs = require('fs');
|
|
58
58
|
const path = require('path');
|
|
59
|
+
const { parseDispatchIdentity } = require('./dispatch-identity.js');
|
|
59
60
|
|
|
60
61
|
// Isolation modes ADR-1239 declares (mirrors gsd-tools.cjs
|
|
61
62
|
// routeDispatchIsolation / routeRecordDispatchIsolation).
|
|
@@ -223,26 +224,40 @@ function readSentinel(cwd, { clock = Date } = {}) {
|
|
|
223
224
|
}
|
|
224
225
|
|
|
225
226
|
/**
|
|
226
|
-
* #3045 SECURITY F2: extract the `{plan, phase}` a specific
|
|
227
|
-
* dispatch is FOR
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
227
|
+
* #3045 SECURITY F2 / #4594: extract the `{plan, phase}` a specific
|
|
228
|
+
* Agent()/Task() dispatch is FOR. Variadic — accepts any number of text
|
|
229
|
+
* sources (short description, full prompt body, etc) and delegates to
|
|
230
|
+
* `hooks/lib/dispatch-identity.js::parseDispatchIdentity`, the one canonical
|
|
231
|
+
* owner of both the `[gsd:dispatch phase="…" plan="…"]` marker format and its
|
|
232
|
+
* prose fallback (see `.gsd/phase/fix-4594-dispatch-identity-seam/40-design.md`).
|
|
233
|
+
*
|
|
234
|
+
* MARKER-FIRST CONTRACT: producers embed a structured marker carrying the
|
|
235
|
+
* exact shell values the sentinel itself records (`$PHASE_NUMBER`,
|
|
236
|
+
* `$plan_id`), so producer and consumer agree by construction, independent
|
|
237
|
+
* of how the prose reads or whether a model paraphrases the dispatch
|
|
238
|
+
* sentence. Only when no marker is found anywhere in the supplied texts does
|
|
239
|
+
* this fall back to scanning for the prose frame "execute plan <token> of
|
|
240
|
+
* phase <PHASE TOKEN>".
|
|
241
|
+
*
|
|
242
|
+
* The prose fallback is now CORRECT-OR-ABSENT rather than possibly-wrong:
|
|
243
|
+
* the phase token is bounded by the same grammar `src/phase-id.cts` owns
|
|
244
|
+
* (ADR-2121), so a directory-name slug or trailing punctuation can no longer
|
|
245
|
+
* leak into the phase value, and the prose plan token is never reported at
|
|
246
|
+
* all (it lives in a different namespace than the sentinel's phase-prefixed,
|
|
247
|
+
* slugged `plan_id` — reporting it was the #4594 false-mismatch bug).
|
|
248
|
+
*
|
|
249
|
+
* This is still a best-effort, NOT a guaranteed, extraction: a dispatch that
|
|
250
|
+
* carries neither a marker nor a matching prose frame in ANY supplied text
|
|
251
|
+
* returns `{ plan: null, phase: null }`, and the caller MUST NEVER treat
|
|
252
|
+
* that as a mismatch — see `sentinelAppliesToDispatch`, whose whole
|
|
253
|
+
* contract depends on "missing" and "wrong" being distinguishable.
|
|
254
|
+
*
|
|
255
|
+
* Returns only the two-field `{ plan, phase }` shape existing callers
|
|
256
|
+
* depend on — `parseDispatchIdentity`'s `source` field is discarded here.
|
|
240
257
|
*/
|
|
241
|
-
function extractDispatchIdentifiers(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (!m) return { plan: null, phase: null };
|
|
245
|
-
return { plan: m[1], phase: m[2] };
|
|
258
|
+
function extractDispatchIdentifiers(...texts) {
|
|
259
|
+
const { phase, plan } = parseDispatchIdentity(...texts);
|
|
260
|
+
return { plan, phase };
|
|
246
261
|
}
|
|
247
262
|
|
|
248
263
|
/**
|
|
@@ -265,6 +280,29 @@ function sentinelAppliesToDispatch(sentinel, dispatchIds) {
|
|
|
265
280
|
return true;
|
|
266
281
|
}
|
|
267
282
|
|
|
283
|
+
/**
|
|
284
|
+
* #4594 F3: build the structured "a fresh sentinel was present but did not
|
|
285
|
+
* apply to this dispatch" descriptor, mirroring the exact comparison
|
|
286
|
+
* `sentinelAppliesToDispatch` performs. Returns `null` when the sentinel is
|
|
287
|
+
* absent, stale, malformed, or DOES apply — i.e. exactly when there is
|
|
288
|
+
* nothing to report as discarded. Otherwise returns the nested
|
|
289
|
+
* `{ sentinel: {phase, plan}, dispatch: {phase, plan} }` shape, reusing the
|
|
290
|
+
* `{phase, plan}` pair already flowing through this module end to end rather
|
|
291
|
+
* than renaming its fields into an ad hoc `sentinelPhase`/`dispatchPlan` bag
|
|
292
|
+
* (previously rebuilt identically at two call sites in the guard hooks).
|
|
293
|
+
*/
|
|
294
|
+
function buildSentinelDiscard(sentinel, dispatchIds) {
|
|
295
|
+
if (!sentinel || !sentinel.present || sentinel.stale) return null;
|
|
296
|
+
if (sentinelAppliesToDispatch(sentinel, dispatchIds)) return null;
|
|
297
|
+
return {
|
|
298
|
+
sentinel: { phase: sentinel.phase ?? null, plan: sentinel.plan ?? null },
|
|
299
|
+
dispatch: {
|
|
300
|
+
phase: dispatchIds ? (dispatchIds.phase ?? null) : null,
|
|
301
|
+
plan: dispatchIds ? (dispatchIds.plan ?? null) : null,
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
268
306
|
module.exports = {
|
|
269
307
|
VALID_ISOLATION,
|
|
270
308
|
SENTINEL_RELATIVE_PATH,
|
|
@@ -274,4 +312,5 @@ module.exports = {
|
|
|
274
312
|
readSentinel,
|
|
275
313
|
extractDispatchIdentifiers,
|
|
276
314
|
sentinelAppliesToDispatch,
|
|
315
|
+
buildSentinelDiscard,
|
|
277
316
|
};
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
const fs = require('fs');
|
|
64
64
|
const path = require('path');
|
|
65
65
|
const os = require('os');
|
|
66
|
-
const { readSentinel, VALID_ISOLATION, extractDispatchIdentifiers, sentinelAppliesToDispatch } = require('./lib/isolation-sentinel.js');
|
|
67
|
-
const { REASON_CODE } = require('./lib/isolation-deny-reason.js');
|
|
66
|
+
const { readSentinel, VALID_ISOLATION, extractDispatchIdentifiers, sentinelAppliesToDispatch, buildSentinelDiscard } = require('./lib/isolation-sentinel.js');
|
|
67
|
+
const { REASON_CODE, describeSentinelDiscard } = require('./lib/isolation-deny-reason.js');
|
|
68
68
|
const { HOOK_ON_CRASH, allow, deny, crash } = require('./lib/hook-exit.js');
|
|
69
69
|
|
|
70
70
|
// Required at module top, alongside the other ./lib requires — NOT behind
|
|
@@ -337,6 +337,30 @@ function resolveRegistryIsolation(cwd, configPath) {
|
|
|
337
337
|
if (!useWorktrees) isolation = 'none';
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
+
// #4734: a harness worktree can never be created in a directory that is not
|
|
341
|
+
// a git repository (or a repository with no commits) — git's definitive
|
|
342
|
+
// exit-128 answer on `rev-parse HEAD`. Demanding the isolation flag there
|
|
343
|
+
// blocked every flag-less dispatch the moment the sentinel went stale, on a
|
|
344
|
+
// root where no worktree can exist. Degrade to 'none' exactly like the
|
|
345
|
+
// use_worktrees opt-out above; the classification is owned by
|
|
346
|
+
// `classifyGitHead` (worktree-base-ref.cjs) — the same single owner the
|
|
347
|
+
// base-check's degrade decision uses — and ambiguous or failed resolutions
|
|
348
|
+
// keep the conservative (enforce) default, matching that check's own
|
|
349
|
+
// fail-closed treatment of the same classes.
|
|
350
|
+
if (isolation === 'harness-worktree') {
|
|
351
|
+
try {
|
|
352
|
+
ensureRuntimeBuild();
|
|
353
|
+
const { classifyGitHead } = require('../gsd-core/bin/lib/worktree-base-ref.cjs');
|
|
354
|
+
if (classifyGitHead({ cwd }).status === 'definitive-absence') {
|
|
355
|
+
isolation = 'none';
|
|
356
|
+
}
|
|
357
|
+
} catch {
|
|
358
|
+
// Unbuilt or unreadable runtime library — keep the conservative
|
|
359
|
+
// (enforce) default rather than silently disabling the guard, same
|
|
360
|
+
// posture as the ladder fallback above.
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
340
364
|
return { isolation, harnessFlag };
|
|
341
365
|
}
|
|
342
366
|
|
|
@@ -385,16 +409,20 @@ function resolveIsolationState(cwd, { clock = Date, dispatchIds = null } = {}) {
|
|
|
385
409
|
projectExists = false;
|
|
386
410
|
}
|
|
387
411
|
if (!projectExists) {
|
|
388
|
-
return { gsdProject: false, isolation: null, harnessFlag: null, error: null };
|
|
412
|
+
return { gsdProject: false, isolation: null, harnessFlag: null, error: null, sentinelDiscarded: null };
|
|
389
413
|
}
|
|
390
414
|
|
|
391
415
|
const sentinel = readSentinel(cwd, { clock });
|
|
392
|
-
|
|
416
|
+
// Hoisted so the "sentinel was present/fresh but did not apply" case below
|
|
417
|
+
// (#4594 row 15 — Postel's-Law finding) can distinguish itself from
|
|
418
|
+
// "absent"/"stale" without re-deriving applicability.
|
|
419
|
+
const applies = sentinelAppliesToDispatch(sentinel, dispatchIds);
|
|
420
|
+
if (sentinel.present && !sentinel.stale && applies) {
|
|
393
421
|
if (sentinel.isolation !== 'harness-worktree') {
|
|
394
|
-
return { gsdProject: true, isolation: sentinel.isolation, harnessFlag: null, error: null };
|
|
422
|
+
return { gsdProject: true, isolation: sentinel.isolation, harnessFlag: null, error: null, sentinelDiscarded: null };
|
|
395
423
|
}
|
|
396
424
|
if (sentinel.harnessFlag) {
|
|
397
|
-
return { gsdProject: true, isolation: 'harness-worktree', harnessFlag: sentinel.harnessFlag, error: null };
|
|
425
|
+
return { gsdProject: true, isolation: 'harness-worktree', harnessFlag: sentinel.harnessFlag, error: null, sentinelDiscarded: null };
|
|
398
426
|
}
|
|
399
427
|
// #3045 BLOCKER 2 fix: the sentinel already PROVED this dispatch requires
|
|
400
428
|
// isolation (it resolved harness-worktree) but carries no usable flag —
|
|
@@ -423,6 +451,7 @@ function resolveIsolationState(cwd, { clock = Date, dispatchIds = null } = {}) {
|
|
|
423
451
|
'dispatch-isolation sentinel resolved "harness-worktree" but recorded no harness_flag — ' +
|
|
424
452
|
'cannot verify what parameter the dispatch must carry.'
|
|
425
453
|
),
|
|
454
|
+
sentinelDiscarded: null,
|
|
426
455
|
};
|
|
427
456
|
}
|
|
428
457
|
|
|
@@ -430,11 +459,19 @@ function resolveIsolationState(cwd, { clock = Date, dispatchIds = null } = {}) {
|
|
|
430
459
|
// conservative fallback (#3045 finding — must still cover fail-closed case
|
|
431
460
|
// (a): a project that opted out of worktrees entirely via
|
|
432
461
|
// workflow.use_worktrees).
|
|
462
|
+
//
|
|
463
|
+
// #4594 row 15: a PRESENT, FRESH sentinel that simply does not apply to
|
|
464
|
+
// THIS dispatch (identifiers disagree) is a distinct case from "absent" or
|
|
465
|
+
// "stale" — record what was discarded so evaluateDispatch can name it in a
|
|
466
|
+
// block reason instead of silently falling through to a registry-resolution
|
|
467
|
+
// message that never mentions the sentinel existed.
|
|
468
|
+
const sentinelDiscarded = buildSentinelDiscard(sentinel, dispatchIds);
|
|
469
|
+
|
|
433
470
|
try {
|
|
434
471
|
const { isolation, harnessFlag } = resolveRegistryIsolation(cwd, configPath);
|
|
435
|
-
return { gsdProject: true, isolation, harnessFlag, error: null };
|
|
472
|
+
return { gsdProject: true, isolation, harnessFlag, error: null, sentinelDiscarded };
|
|
436
473
|
} catch (err) {
|
|
437
|
-
return { gsdProject: true, isolation: null, harnessFlag: null, error: err };
|
|
474
|
+
return { gsdProject: true, isolation: null, harnessFlag: null, error: err, sentinelDiscarded };
|
|
438
475
|
}
|
|
439
476
|
}
|
|
440
477
|
|
|
@@ -464,10 +501,16 @@ function evaluateDispatch(data, { clock = Date } = {}) {
|
|
|
464
501
|
}
|
|
465
502
|
|
|
466
503
|
const cwd = data.cwd || process.cwd();
|
|
467
|
-
// #3045 SECURITY F2: best-effort plan/phase extraction from this
|
|
468
|
-
// dispatch's own
|
|
469
|
-
//
|
|
470
|
-
|
|
504
|
+
// #3045 SECURITY F2 / #4594: best-effort plan/phase extraction from this
|
|
505
|
+
// dispatch's own text, so a fresh sentinel that disagrees with THIS
|
|
506
|
+
// dispatch is treated as inapplicable rather than trusted. PROMPT FIRST:
|
|
507
|
+
// `description` is short, model-authored free text that only carries usable
|
|
508
|
+
// identity when the model happens to reproduce the dispatch template
|
|
509
|
+
// verbatim, while the canonical `[gsd:dispatch phase="…" plan="…"]` marker
|
|
510
|
+
// (or, failing that, the prose fallback) lives in the prompt body itself —
|
|
511
|
+
// `description` is kept only as a fallback for a marker/prose match that
|
|
512
|
+
// exists solely in it.
|
|
513
|
+
const dispatchIds = extractDispatchIdentifiers(toolInput.prompt, toolInput.description);
|
|
471
514
|
const state = resolveIsolationState(cwd, { clock, dispatchIds });
|
|
472
515
|
|
|
473
516
|
if (!state.gsdProject) return { action: 'allow' };
|
|
@@ -493,7 +536,8 @@ function evaluateDispatch(data, { clock = Date } = {}) {
|
|
|
493
536
|
`required — a guard that cannot verify must not answer "safe" (#3050). Retry once the ` +
|
|
494
537
|
`project configuration is readable.`;
|
|
495
538
|
const reasonCode = isBuildFailure ? REASON_CODE.RUNTIME_BUILD_FAILED : REASON_CODE.CONFIG_UNREADABLE;
|
|
496
|
-
|
|
539
|
+
const fullReason = state.sentinelDiscarded ? reason + describeSentinelDiscard(state.sentinelDiscarded) : reason;
|
|
540
|
+
return { action: 'block', reason: fullReason, reasonCode, sentinelDiscarded: state.sentinelDiscarded };
|
|
497
541
|
}
|
|
498
542
|
|
|
499
543
|
if (state.isolation !== 'harness-worktree') return { action: 'allow' };
|
|
@@ -503,13 +547,14 @@ function evaluateDispatch(data, { clock = Date } = {}) {
|
|
|
503
547
|
|
|
504
548
|
if (toolInput[parsed.param] === parsed.value) return { action: 'allow' };
|
|
505
549
|
|
|
506
|
-
|
|
550
|
+
let reason =
|
|
507
551
|
`Agent isolation guard: this project's dispatch isolation resolves to "harness-worktree", ` +
|
|
508
552
|
`but the Agent() dispatch for subagent_type="${subagentType}" is missing ` +
|
|
509
553
|
`${parsed.param}="${parsed.value}". Add ${parsed.param}="${parsed.value}" to the Agent() ` +
|
|
510
554
|
`call so the executor runs in an isolated worktree instead of the primary checkout ` +
|
|
511
555
|
`(gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md).`;
|
|
512
|
-
|
|
556
|
+
if (state.sentinelDiscarded) reason += describeSentinelDiscard(state.sentinelDiscarded);
|
|
557
|
+
return { action: 'block', reason, reasonCode: REASON_CODE.HARNESS_FLAG_MISSING, sentinelDiscarded: state.sentinelDiscarded };
|
|
513
558
|
}
|
|
514
559
|
|
|
515
560
|
/* istanbul ignore next -- stdin adapter, exercised via spawnSync in tests */
|
|
@@ -524,7 +569,12 @@ function main() {
|
|
|
524
569
|
const data = JSON.parse(input);
|
|
525
570
|
const decision = evaluateDispatch(data);
|
|
526
571
|
if (decision.action === 'block') {
|
|
527
|
-
const out = {
|
|
572
|
+
const out = {
|
|
573
|
+
decision: 'block',
|
|
574
|
+
reason: decision.reason,
|
|
575
|
+
reason_code: decision.reasonCode,
|
|
576
|
+
sentinel_discarded: decision.sentinelDiscarded ?? null,
|
|
577
|
+
};
|
|
528
578
|
// Kimi feeds stderr (not stdout) back to the model on exit 2.
|
|
529
579
|
deny(out, decision.reason);
|
|
530
580
|
}
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
// Thresholds:
|
|
15
15
|
// WARNING (remaining <= 35%): Agent should wrap up current task
|
|
16
16
|
// CRITICAL (remaining <= 25%): Agent should stop immediately and save state
|
|
17
|
+
// Both fire-points are overridable per project via .planning/config.json
|
|
18
|
+
// (hooks.context_warning_threshold / hooks.context_critical_threshold, #4285);
|
|
19
|
+
// the values above are the defaults used when the keys are absent or unusable.
|
|
17
20
|
//
|
|
18
21
|
// Debounce: 5 tool uses between warnings to avoid spam
|
|
19
22
|
// Severity escalation bypasses debounce (WARNING -> CRITICAL fires immediately)
|
|
@@ -30,8 +33,8 @@ const { HOOK_ON_CRASH, allow, crash } = require('./lib/hook-exit.js');
|
|
|
30
33
|
// context warning is far cheaper than stalling the agent's work (#3911).
|
|
31
34
|
const ON_CRASH = HOOK_ON_CRASH.ALLOW;
|
|
32
35
|
|
|
33
|
-
const WARNING_THRESHOLD = 35; // remaining_percentage <= 35%
|
|
34
|
-
const CRITICAL_THRESHOLD = 25; // remaining_percentage <= 25%
|
|
36
|
+
const WARNING_THRESHOLD = 35; // remaining_percentage <= 35% (default, see resolveThresholds)
|
|
37
|
+
const CRITICAL_THRESHOLD = 25; // remaining_percentage <= 25% (default, see resolveThresholds)
|
|
35
38
|
const STALE_SECONDS = 60; // ignore metrics older than 60s
|
|
36
39
|
const DEBOUNCE_CALLS = 5; // min tool uses between warnings
|
|
37
40
|
// How long after a PreCompact readings stay suspect. The watermark records the
|
|
@@ -57,6 +60,41 @@ const COMPACT_GRACE_SECONDS = 60;
|
|
|
57
60
|
// watermark this far ahead pushes first recovery from +61 to +66 (measured).
|
|
58
61
|
const WATERMARK_SKEW_SECONDS = 5;
|
|
59
62
|
|
|
63
|
+
// Resolve the two fire-points from the project's `.planning/config.json`
|
|
64
|
+
// (#4285). The constants above are the DEFAULTS; a project overrides either one
|
|
65
|
+
// through `hooks.context_warning_threshold` / `hooks.context_critical_threshold`,
|
|
66
|
+
// which is what keeps a tuned fire-point alive across updates — this file is in
|
|
67
|
+
// the MANAGED registry, so an edit to the constants is re-staged away by the
|
|
68
|
+
// next install.
|
|
69
|
+
//
|
|
70
|
+
// TOTAL and never-throwing: this hook must not block the tool call it rides in
|
|
71
|
+
// on, so every unusable input degrades to the default instead of raising.
|
|
72
|
+
// Unusable is decided by Number.isFinite, which is type-strict (the string
|
|
73
|
+
// "30" and true are both rejected, unlike the global isFinite), plus the 0-100
|
|
74
|
+
// domain of the remaining_percentage these are compared against.
|
|
75
|
+
//
|
|
76
|
+
// The PAIR is validated too, and falls back TOGETHER. `critical >= warning` has
|
|
77
|
+
// no coherent reading — critical fires deeper into the window than warning —
|
|
78
|
+
// and honouring one side of an inconsistent pair silently picks which of the
|
|
79
|
+
// operator's two numbers to discard. This also rejects a single override that
|
|
80
|
+
// contradicts the OTHER key's default (warning 20 with critical absent, i.e.
|
|
81
|
+
// 25); the resulting pair is the same nonsense either way. Set-time validation
|
|
82
|
+
// cannot stand in for this check: `config-set` writes one key per call, so
|
|
83
|
+
// tuning both (warning first, then critical) is transiently inconsistent on
|
|
84
|
+
// disk, and refusing it there would block a legitimate configuration.
|
|
85
|
+
function resolveThresholds(hooks) {
|
|
86
|
+
const defaults = { warning: WARNING_THRESHOLD, critical: CRITICAL_THRESHOLD };
|
|
87
|
+
if (!hooks || typeof hooks !== 'object') return defaults;
|
|
88
|
+
|
|
89
|
+
const usable = (value, fallback) =>
|
|
90
|
+
(Number.isFinite(value) && value >= 0 && value <= 100) ? value : fallback;
|
|
91
|
+
|
|
92
|
+
const warning = usable(hooks.context_warning_threshold, WARNING_THRESHOLD);
|
|
93
|
+
const critical = usable(hooks.context_critical_threshold, CRITICAL_THRESHOLD);
|
|
94
|
+
|
|
95
|
+
return critical < warning ? { warning, critical } : defaults;
|
|
96
|
+
}
|
|
97
|
+
|
|
60
98
|
// One DEFINITION of what counts as a lifecycle event name, shared by the #3709
|
|
61
99
|
// PreCompact reset and the #2289 output-envelope allowlist. Two call sites, one
|
|
62
100
|
// rule — so the two cannot drift into disagreeing about what "no event name" is.
|
|
@@ -164,14 +202,11 @@ function writeSentinel(target, payload) {
|
|
|
164
202
|
}
|
|
165
203
|
|
|
166
204
|
let input = '';
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
process.stdin.setEncoding('utf8');
|
|
173
|
-
process.stdin.on('data', chunk => input += chunk);
|
|
174
|
-
process.stdin.on('end', () => {
|
|
205
|
+
// Assigned by main(); the handler below clears it. Declared out here rather
|
|
206
|
+
// than inside main() because the handler closes over it.
|
|
207
|
+
let stdinTimeout = null;
|
|
208
|
+
|
|
209
|
+
const handleStdinEnd = () => {
|
|
175
210
|
clearTimeout(stdinTimeout);
|
|
176
211
|
try {
|
|
177
212
|
const data = JSON.parse(input);
|
|
@@ -266,18 +301,26 @@ process.stdin.on('end', () => {
|
|
|
266
301
|
allow(undefined);
|
|
267
302
|
}
|
|
268
303
|
|
|
269
|
-
// Check if context warnings are disabled via config
|
|
304
|
+
// Check if context warnings are disabled via config, and resolve the two
|
|
305
|
+
// fire-points from the same read (#4285 — one config read, not two).
|
|
270
306
|
// Collapsed existsSync+readFileSync into a single read guarded by try/catch
|
|
271
307
|
// (ENOENT or parse error → use defaults, same as old "planningDir absent" branch).
|
|
272
308
|
const cwd = data.cwd || process.cwd();
|
|
309
|
+
let thresholds = { warning: WARNING_THRESHOLD, critical: CRITICAL_THRESHOLD };
|
|
273
310
|
try {
|
|
274
311
|
const configPath = path.join(cwd, '.planning', 'config.json');
|
|
275
312
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
276
313
|
if (config.hooks?.context_warnings === false) {
|
|
277
314
|
allow(undefined);
|
|
278
315
|
}
|
|
316
|
+
// After the disable check, not before: a disabled monitor exits above and
|
|
317
|
+
// never reaches a threshold, so resolving first would only add work to the
|
|
318
|
+
// path that does nothing. allow() exits the process (it does not throw),
|
|
319
|
+
// so this line is unreachable when warnings are off.
|
|
320
|
+
thresholds = resolveThresholds(config.hooks);
|
|
279
321
|
} catch (e) {
|
|
280
|
-
// Missing or unparseable config → proceed with defaults (context warnings
|
|
322
|
+
// Missing or unparseable config → proceed with defaults (context warnings
|
|
323
|
+
// enabled, thresholds at the constants above, which `thresholds` already holds)
|
|
281
324
|
}
|
|
282
325
|
|
|
283
326
|
// If no metrics file, this is a subagent or fresh session -- exit silently.
|
|
@@ -352,7 +395,7 @@ process.stdin.on('end', () => {
|
|
|
352
395
|
const usedPct = metrics.used_pct;
|
|
353
396
|
|
|
354
397
|
// No warning needed
|
|
355
|
-
if (remaining >
|
|
398
|
+
if (remaining > thresholds.warning) {
|
|
356
399
|
allow(undefined);
|
|
357
400
|
}
|
|
358
401
|
|
|
@@ -389,7 +432,7 @@ process.stdin.on('end', () => {
|
|
|
389
432
|
|
|
390
433
|
warnData.callsSinceWarn = (warnData.callsSinceWarn || 0) + 1;
|
|
391
434
|
|
|
392
|
-
const isCritical = remaining <=
|
|
435
|
+
const isCritical = remaining <= thresholds.critical;
|
|
393
436
|
const currentLevel = isCritical ? 'critical' : 'warning';
|
|
394
437
|
|
|
395
438
|
// Emit immediately on first warning, then debounce subsequent ones
|
|
@@ -491,4 +534,34 @@ process.stdin.on('end', () => {
|
|
|
491
534
|
// exit(0) fail-open behavior exactly (#3911).
|
|
492
535
|
crash(ON_CRASH, undefined);
|
|
493
536
|
}
|
|
494
|
-
}
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
// The stdin adapter is the only side-effecting statement in this file, so it is
|
|
540
|
+
// the only thing that must not run on `require()`. Gating it lets a test import
|
|
541
|
+
// `resolveThresholds` and drive it directly — the repo's own conclusion for a
|
|
542
|
+
// seam like this (CONTEXT-INDEX, on the ROADMAP Requirements parser: a closure
|
|
543
|
+
// reachable only by spawning the CLI is one "no fast-check property can do").
|
|
544
|
+
// A spawn-per-case property test is not the same test: it would exercise the
|
|
545
|
+
// resolver at whatever pairs survive to an observable severity, not over its
|
|
546
|
+
// whole numeric domain.
|
|
547
|
+
/* istanbul ignore next -- stdin adapter, exercised via spawnSync in tests */
|
|
548
|
+
function main() {
|
|
549
|
+
// Timeout guard: if stdin doesn't close within 10s (e.g. pipe issues on
|
|
550
|
+
// Windows/Git Bash, or slow Claude Code piping during large outputs),
|
|
551
|
+
// exit silently instead of hanging until Claude Code kills the process
|
|
552
|
+
// and reports "hook error". See #775, #1162.
|
|
553
|
+
stdinTimeout = setTimeout(() => allow(undefined), 10000);
|
|
554
|
+
process.stdin.setEncoding('utf8');
|
|
555
|
+
process.stdin.on('data', chunk => input += chunk);
|
|
556
|
+
process.stdin.on('end', handleStdinEnd);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (require.main === module) {
|
|
560
|
+
main();
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Exported for the #4285 property test only. The two constants ride along so a
|
|
564
|
+
// test asserts the fallback pair against the SOURCE of truth rather than
|
|
565
|
+
// re-hardcoding 35/25 — a test carrying its own copy of the defaults would stay
|
|
566
|
+
// green if the constants were edited.
|
|
567
|
+
module.exports = { resolveThresholds, WARNING_THRESHOLD, CRITICAL_THRESHOLD };
|
|
@@ -62,8 +62,8 @@ const { allow } = require('./lib/hook-exit.js');
|
|
|
62
62
|
// hooks/lib/cursor-workspace.js. Staged next to these scripts by
|
|
63
63
|
// writeCursorHooksJson so the require always resolves post-install.
|
|
64
64
|
const { resolveStatePath } = require('./lib/cursor-workspace.js');
|
|
65
|
-
const { readSentinel, VALID_ISOLATION, extractDispatchIdentifiers, sentinelAppliesToDispatch } = require('./lib/isolation-sentinel.js');
|
|
66
|
-
const { REASON_CODE } = require('./lib/isolation-deny-reason.js');
|
|
65
|
+
const { readSentinel, VALID_ISOLATION, extractDispatchIdentifiers, sentinelAppliesToDispatch, buildSentinelDiscard } = require('./lib/isolation-sentinel.js');
|
|
66
|
+
const { REASON_CODE, describeSentinelDiscard } = require('./lib/isolation-deny-reason.js');
|
|
67
67
|
// #3582: gsd-core/bin/lib/*.cjs (runtime-homes.cjs, worktree-safety.cjs,
|
|
68
68
|
// runtime-name-policy.cjs, capability-registry.cjs — required below, inside
|
|
69
69
|
// resolveIsolationEvidence and resolveFallbackIsolation) are tsc build
|
|
@@ -491,18 +491,25 @@ function evaluateRootIsolation(root, subagentType, { clock = Date, dispatchIds =
|
|
|
491
491
|
`Refusing to allow this subagent to spawn until the runtime library is built — a guard ` +
|
|
492
492
|
`that cannot verify must not answer "safe" (#3050).`,
|
|
493
493
|
reasonCode: REASON_CODE.RUNTIME_BUILD_FAILED,
|
|
494
|
+
sentinelDiscarded: null,
|
|
494
495
|
};
|
|
495
496
|
}
|
|
496
497
|
|
|
498
|
+
// #3045 BLOCKER fix: a fresh sentinel is authoritative for THIS dispatch's
|
|
499
|
+
// actual resolved isolation — see the doc comment above.
|
|
500
|
+
// #3045 SECURITY F2: a fresh sentinel that names a DIFFERENT plan/phase
|
|
501
|
+
// than this dispatch is not applicable to it — fall through to the
|
|
502
|
+
// conservative fallback exactly as a stale sentinel would.
|
|
503
|
+
// Hoisted (readSentinel never throws) so the "present, fresh, but did not
|
|
504
|
+
// apply" case (#4594 row 15) can be reported on every deny path below
|
|
505
|
+
// instead of silently discarded.
|
|
506
|
+
const sentinel = readSentinel(root, { clock });
|
|
507
|
+
const applies = sentinelAppliesToDispatch(sentinel, dispatchIds);
|
|
508
|
+
const sentinelDiscarded = buildSentinelDiscard(sentinel, dispatchIds);
|
|
509
|
+
|
|
497
510
|
let declaredIsolation;
|
|
498
511
|
try {
|
|
499
|
-
|
|
500
|
-
// dispatch's actual resolved isolation — see the doc comment above.
|
|
501
|
-
// #3045 SECURITY F2: a fresh sentinel that names a DIFFERENT
|
|
502
|
-
// plan/phase than this dispatch is not applicable to it — fall through
|
|
503
|
-
// to the conservative fallback exactly as a stale sentinel would.
|
|
504
|
-
const sentinel = readSentinel(root, { clock });
|
|
505
|
-
declaredIsolation = (sentinel.present && !sentinel.stale && sentinelAppliesToDispatch(sentinel, dispatchIds))
|
|
512
|
+
declaredIsolation = (sentinel.present && !sentinel.stale && applies)
|
|
506
513
|
? sentinel.isolation
|
|
507
514
|
: resolveFallbackIsolation(root, configPath);
|
|
508
515
|
} catch {
|
|
@@ -513,8 +520,10 @@ function evaluateRootIsolation(root, subagentType, { clock = Date, dispatchIds =
|
|
|
513
520
|
`dispatch-isolation configuration ('.planning/config.json' exists under "${root}"). ` +
|
|
514
521
|
`Refusing to allow this subagent to spawn without being able to verify whether ` +
|
|
515
522
|
`isolation is required — a guard that cannot verify must not answer "safe" (#3050). ` +
|
|
516
|
-
`Retry once the project configuration is readable
|
|
523
|
+
`Retry once the project configuration is readable.` +
|
|
524
|
+
(sentinelDiscarded ? describeSentinelDiscard(sentinelDiscarded) : ''),
|
|
517
525
|
reasonCode: REASON_CODE.CONFIG_UNREADABLE,
|
|
526
|
+
sentinelDiscarded,
|
|
518
527
|
};
|
|
519
528
|
}
|
|
520
529
|
|
|
@@ -530,8 +539,10 @@ function evaluateRootIsolation(root, subagentType, { clock = Date, dispatchIds =
|
|
|
530
539
|
`GSD subagent isolation guard: this project's dispatch isolation resolves to ` +
|
|
531
540
|
`"harness-worktree", but the subagentStart payload for this dispatch carries no usable ` +
|
|
532
541
|
`subagent_type. Refusing to allow it to spawn without being able to confirm whether it ` +
|
|
533
|
-
`is a GSD executor — a guard that cannot verify must not answer "safe" (#3050)
|
|
542
|
+
`is a GSD executor — a guard that cannot verify must not answer "safe" (#3050).` +
|
|
543
|
+
(sentinelDiscarded ? describeSentinelDiscard(sentinelDiscarded) : ''),
|
|
534
544
|
reasonCode: REASON_CODE.NO_SUBAGENT_TYPE,
|
|
545
|
+
sentinelDiscarded,
|
|
535
546
|
};
|
|
536
547
|
}
|
|
537
548
|
|
|
@@ -547,8 +558,10 @@ function evaluateRootIsolation(root, subagentType, { clock = Date, dispatchIds =
|
|
|
547
558
|
`"harness-worktree", but whether "${root}" is running in an isolated Cursor worktree ` +
|
|
548
559
|
`could not be determined (git did not respond). Refusing to allow subagent_type=` +
|
|
549
560
|
`"${subagentType}" to spawn without being able to verify isolation — a guard that ` +
|
|
550
|
-
`cannot verify must not answer "safe" (#3050). Retry once git is responsive
|
|
561
|
+
`cannot verify must not answer "safe" (#3050). Retry once git is responsive.` +
|
|
562
|
+
(sentinelDiscarded ? describeSentinelDiscard(sentinelDiscarded) : ''),
|
|
551
563
|
reasonCode: REASON_CODE.CANNOT_DETERMINE_ISOLATION,
|
|
564
|
+
sentinelDiscarded,
|
|
552
565
|
};
|
|
553
566
|
}
|
|
554
567
|
|
|
@@ -560,8 +573,10 @@ function evaluateRootIsolation(root, subagentType, { clock = Date, dispatchIds =
|
|
|
560
573
|
`which is not an isolated Cursor worktree — it would edit the user's primary checkout ` +
|
|
561
574
|
`directly, with no consent and no warning. Start an isolated session first (the ` +
|
|
562
575
|
`"--worktree" CLI flag or the "/worktree" chat command; Cursor manages these worktrees ` +
|
|
563
|
-
`under "~/.cursor/worktrees/") and retry
|
|
576
|
+
`under "~/.cursor/worktrees/") and retry.` +
|
|
577
|
+
(sentinelDiscarded ? describeSentinelDiscard(sentinelDiscarded) : ''),
|
|
564
578
|
reasonCode: REASON_CODE.NOT_ISOLATED_WORKTREE,
|
|
579
|
+
sentinelDiscarded,
|
|
565
580
|
};
|
|
566
581
|
}
|
|
567
582
|
|
|
@@ -611,7 +626,12 @@ function main() {
|
|
|
611
626
|
decision = { action: 'allow' };
|
|
612
627
|
}
|
|
613
628
|
if (decision.action === 'deny') {
|
|
614
|
-
const out = {
|
|
629
|
+
const out = {
|
|
630
|
+
permission: 'deny',
|
|
631
|
+
user_message: decision.reason,
|
|
632
|
+
reason_code: decision.reasonCode,
|
|
633
|
+
sentinel_discarded: decision.sentinelDiscarded ?? null,
|
|
634
|
+
};
|
|
615
635
|
if (additionalContext !== null) out.additional_context = additionalContext;
|
|
616
636
|
process.stdout.write(JSON.stringify(out));
|
|
617
637
|
return;
|