@opengsd/gsd-core 1.9.1 → 1.11.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 +2 -3
- package/.opencode/plugins/gsd-core.js +8 -1
- package/agents/gsd-code-fixer.md +27 -3
- package/agents/gsd-debug-session-manager.md +11 -0
- package/agents/gsd-debugger.md +12 -246
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-executor.md +12 -10
- package/agents/gsd-integration-checker.md +3 -0
- package/agents/gsd-mempalace-curator.md +5 -2
- package/agents/gsd-phase-researcher.md +20 -1
- package/agents/gsd-plan-checker.md +46 -0
- package/agents/gsd-planner.md +49 -54
- package/agents/gsd-roadmapper.md +21 -3
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +26 -73
- package/bin/install.js +1272 -1238
- package/bin/lib/ui-safety-gate.cjs +2 -0
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +2 -2
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +2 -2
- package/commands/gsd/plan-phase.md +1 -1
- package/commands/gsd/quick.md +1 -1
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +1009 -115
- package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
- package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
- package/gsd-core/bin/lib/api-coverage.cjs +123 -5
- package/gsd-core/bin/lib/artifacts.cjs +3 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +926 -202
- package/gsd-core/bin/lib/broken-windows.cjs +36 -6
- package/gsd-core/bin/lib/capability-consent.cjs +149 -15
- package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
- package/gsd-core/bin/lib/capability-registry.cjs +608 -148
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +507 -24
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +114 -38
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
- package/gsd-core/bin/lib/command-aliases.cjs +94 -0
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/commands.cjs +665 -99
- package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
- package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
- package/gsd-core/bin/lib/config-loader.cjs +76 -0
- package/gsd-core/bin/lib/config.cjs +22 -2
- package/gsd-core/bin/lib/context-composer.cjs +278 -0
- package/gsd-core/bin/lib/context-predicates.cjs +506 -0
- package/gsd-core/bin/lib/core-utils.cjs +217 -40
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/docs.cjs +3 -2
- package/gsd-core/bin/lib/external-job.cjs +19 -4
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +239 -32
- package/gsd-core/bin/lib/gap-checker.cjs +68 -7
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
- package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
- package/gsd-core/bin/lib/graphify.cjs +142 -27
- package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
- package/gsd-core/bin/lib/host-integration.cjs +13 -1
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init-command-router.cjs +83 -8
- package/gsd-core/bin/lib/init.cjs +1325 -169
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +805 -264
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
- package/gsd-core/bin/lib/install-profiles.cjs +160 -57
- package/gsd-core/bin/lib/install-scope.cjs +270 -0
- package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
- package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
- package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
- package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
- package/gsd-core/bin/lib/io.cjs +38 -3
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
- package/gsd-core/bin/lib/markdown-table.cjs +133 -20
- package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
- package/gsd-core/bin/lib/mcp-server.cjs +135 -3
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +821 -109
- package/gsd-core/bin/lib/model-catalog.cjs +59 -1
- package/gsd-core/bin/lib/model-resolver.cjs +183 -40
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
- package/gsd-core/bin/lib/phase-id.cjs +507 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
- package/gsd-core/bin/lib/phase-locator.cjs +258 -58
- package/gsd-core/bin/lib/phase.cjs +891 -156
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +86 -2
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +60 -6
- package/gsd-core/bin/lib/probe-core.cjs +1 -1
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +405 -84
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
- package/gsd-core/bin/lib/section-manifest.cjs +209 -0
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
- package/gsd-core/bin/lib/smart-entry.cjs +154 -22
- package/gsd-core/bin/lib/state-command-router.cjs +5 -1
- package/gsd-core/bin/lib/state-document.cjs +152 -8
- package/gsd-core/bin/lib/state-transition.cjs +424 -105
- package/gsd-core/bin/lib/state.cjs +1927 -401
- package/gsd-core/bin/lib/surface.cjs +35 -10
- package/gsd-core/bin/lib/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +20 -4
- package/gsd-core/bin/lib/uat.cjs +706 -64
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
- package/gsd-core/bin/lib/unusable-input.cjs +33 -0
- package/gsd-core/bin/lib/update-context.cjs +8 -2
- package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +37 -0
- package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
- package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +287 -20
- package/gsd-core/bin/lib/verify.cjs +368 -880
- package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
- package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
- package/gsd-core/bin/lib/workstream.cjs +8 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
- package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
- package/gsd-core/references/agent-contracts.md +43 -26
- package/gsd-core/references/artifact-types.md +10 -3
- package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
- package/gsd-core/references/checkpoints.md +2 -2
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/debugger-techniques.md +255 -0
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/execute-mvp-tdd.md +3 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
- package/gsd-core/references/execute-phase-context-guard.md +1 -1
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +6 -2
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-planning-commit.md +2 -1
- package/gsd-core/references/loop-hook-dispatch.md +39 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planning-config.md +8 -6
- package/gsd-core/references/research-documentation-lookup.md +5 -3
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +8 -7
- package/gsd-core/references/universal-anti-patterns.md +3 -3
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verifier-wiring-patterns.md +100 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +22 -6
- package/gsd-core/references/worktree-branch-check.md +2 -2
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +2 -4
- package/gsd-core/templates/state.md +4 -4
- package/gsd-core/templates/summary-complex.md +2 -0
- package/gsd-core/templates/summary-minimal.md +2 -0
- package/gsd-core/templates/summary-standard.md +2 -0
- package/gsd-core/templates/summary.md +2 -0
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/ai-integration-phase.md +9 -11
- package/gsd-core/workflows/audit-milestone.md +3 -0
- package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
- package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
- package/gsd-core/workflows/autonomous.md +33 -70
- package/gsd-core/workflows/cleanup.md +62 -3
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
- package/gsd-core/workflows/code-review-fix.md +37 -10
- package/gsd-core/workflows/code-review.md +74 -166
- package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
- package/gsd-core/workflows/complete-milestone.md +160 -95
- package/gsd-core/workflows/debug.md +16 -17
- package/gsd-core/workflows/diagnose-issues.md +56 -8
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
- package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
- package/gsd-core/workflows/docs-update.md +8 -51
- package/gsd-core/workflows/edit-phase.md +26 -1
- package/gsd-core/workflows/eval-review.md +3 -5
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +64 -7
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
- package/gsd-core/workflows/execute-phase.md +103 -187
- package/gsd-core/workflows/execute-plan.md +36 -4
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/health.md +73 -4
- package/gsd-core/workflows/help/modes/full.md +6 -1
- package/gsd-core/workflows/import.md +4 -4
- package/gsd-core/workflows/ingest-docs.md +7 -6
- package/gsd-core/workflows/mvp-phase.md +6 -3
- package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
- package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
- package/gsd-core/workflows/new-milestone.md +35 -47
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
- package/gsd-core/workflows/new-project.md +27 -240
- package/gsd-core/workflows/next.md +12 -0
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
- package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
- package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
- package/gsd-core/workflows/plan-phase.md +89 -209
- package/gsd-core/workflows/plan-review-convergence.md +50 -2
- package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
- package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
- package/gsd-core/workflows/progress.md +45 -159
- package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
- package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
- package/gsd-core/workflows/quick.md +55 -405
- package/gsd-core/workflows/resume-project.md +3 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
- package/gsd-core/workflows/review.md +41 -13
- package/gsd-core/workflows/section-manifest.json +219 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings.md +66 -2
- package/gsd-core/workflows/ship.md +104 -44
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/spec-phase.md +41 -20
- package/gsd-core/workflows/spike-wrap-up.md +20 -5
- package/gsd-core/workflows/spike.md +50 -16
- package/gsd-core/workflows/sync-skills.md +106 -13
- package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
- package/gsd-core/workflows/transition.md +53 -31
- package/gsd-core/workflows/ui-phase.md +13 -12
- package/gsd-core/workflows/ui-review.md +2 -2
- package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
- package/gsd-core/workflows/update.md +19 -8
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
- package/gsd-core/workflows/verify-work.md +17 -65
- package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
- package/hooks/dist/gsd-check-update-worker.js +64 -12
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
- package/hooks/dist/gsd-cursor-subagent-start.js +607 -26
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/dist/gsd-prompt-guard.js +21 -20
- package/hooks/dist/gsd-read-injection-scanner.js +45 -24
- package/hooks/dist/gsd-statusline.js +90 -6
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-workflow-guard.js +134 -36
- package/hooks/dist/gsd-worktree-path-guard.js +2 -1
- package/hooks/dist/gsd-write-guard.js +359 -0
- package/hooks/dist/lib/git-cmd.js +92 -59
- package/hooks/dist/lib/injection-patterns.js +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +277 -0
- package/hooks/dist/managed-hooks-registry.cjs +2 -0
- package/hooks/gsd-agent-isolation-guard.js +517 -0
- package/hooks/gsd-check-update-worker.js +64 -12
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-cursor-pre-tool.js +0 -3
- package/hooks/gsd-cursor-subagent-start.js +607 -26
- package/hooks/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/gsd-prompt-guard.js +21 -20
- package/hooks/gsd-read-injection-scanner.js +45 -24
- package/hooks/gsd-statusline.js +90 -6
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-workflow-guard.js +134 -36
- package/hooks/gsd-worktree-path-guard.js +2 -1
- package/hooks/gsd-write-guard.js +359 -0
- package/hooks/hooks.json +12 -0
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +277 -0
- package/hooks/managed-hooks-registry.cjs +2 -0
- package/package.json +31 -10
- package/pi/gsd.cjs +71 -12
- package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
- package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
- package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
- package/scripts/build-hooks.js +9 -0
- package/scripts/changeset/lint.cjs +68 -6
- package/scripts/changeset/serialize.cjs +5 -1
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/ci-test-scope.cjs +19 -2
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-matrix.cjs +1 -1
- package/scripts/gen-capability-registry.cjs +3 -15
- package/scripts/gen-context-index.cjs +439 -0
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-inventory-manifest.cjs +150 -4
- package/scripts/gen-loop-host-contract.cjs +4 -24
- package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/gen-section-manifest.cjs +638 -0
- package/scripts/generate-package-identity.cjs +4 -2
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/drift-scan.cjs +278 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +15 -54
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-compiled-artifact-sync.cjs +6 -1
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-docs-command-form.cjs +195 -0
- package/scripts/lint-docs-required.cjs +9 -1
- package/scripts/lint-emitted-drift-ack.cjs +215 -20
- package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/lint-example-parser-parity.cjs +395 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-phase-enumeration-drift.cjs +479 -0
- package/scripts/lint-plan-count-drift.cjs +318 -0
- package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
- package/scripts/lint-planning-prompt-drift.cjs +434 -0
- package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
- package/scripts/lint-regression-test-names.cjs +15 -13
- package/scripts/lint-removed-but-needed.cjs +320 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +1045 -0
- package/scripts/lint-test-file-count.allowlist.json +40 -3
- package/scripts/lint-unreachable-guard-drift.cjs +843 -0
- package/scripts/lint-vendored-deps.cjs +124 -0
- package/scripts/mutation-matrix.cjs +13 -0
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +52 -6
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/state-write-path-drift-baseline.json +19 -0
- package/scripts/sync-runtime-launcher.cjs +2 -4
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +2 -2
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +2 -2
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +1 -2
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +1 -1
- package/skills/gsd-review-backlog/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +0 -1
- package/skills/gsd-verify-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -577
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/gen-emitted-baseline.cjs +0 -145
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1050
|
@@ -21,13 +21,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
21
21
|
* BEHAVIOR-PRESERVING RELOCATION: all logic is copied verbatim from
|
|
22
22
|
* bin/install.js. No behavior change, no descriptor reads, no new IO.
|
|
23
23
|
*
|
|
24
|
-
* bin/install.js re-
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* #2876 (epic #2866 Phase 7): bin/install.js previously re-exported every
|
|
25
|
+
* symbol from this module, but a repo-wide audit found zero production
|
|
26
|
+
* consumers of those re-exports — no `require('../bin/install.js').
|
|
27
|
+
* writeCursorHooksJson` (or any sibling) exists outside a doc comment
|
|
28
|
+
* anywhere in the tree. The re-exports were test-suite-only pass-throughs;
|
|
29
|
+
* tests now require this module directly instead.
|
|
27
30
|
*/
|
|
28
31
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
29
32
|
const node_path_1 = __importDefault(require("node:path"));
|
|
30
33
|
const node_os_1 = __importDefault(require("node:os"));
|
|
34
|
+
// #2544: the single source of truth for the CommonJS module-type marker. The
|
|
35
|
+
// two helpers below are thin boolean-returning shims over these — see the
|
|
36
|
+
// marker section for why this file no longer carries its own copy.
|
|
37
|
+
const commonjs_marker_cjs_1 = require("./commonjs-marker.cjs");
|
|
31
38
|
const imperative_hook_bus_cjs_1 = require("./host-integration-adapters/imperative-hook-bus.cjs");
|
|
32
39
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
33
40
|
const shellCmdProjection = require("./shell-command-projection.cjs");
|
|
@@ -166,62 +173,51 @@ function atomicWriteFileSync(target, data, options) {
|
|
|
166
173
|
//
|
|
167
174
|
// The marker content is byte-identical to installSharedHooksBundle's
|
|
168
175
|
// (bin/install.js installSharedHooksBundle): {"type":"commonjs"}\n.
|
|
176
|
+
//
|
|
177
|
+
// #2544: the two helpers below no longer carry their own copy of the write and
|
|
178
|
+
// remove rules — they DELEGATE to src/commonjs-marker.cts, which #2544 makes the
|
|
179
|
+
// single place both rules are enforced. Keeping a second copy here was not
|
|
180
|
+
// merely redundant; the copies had drifted apart on exactly the two properties
|
|
181
|
+
// that matter:
|
|
182
|
+
//
|
|
183
|
+
// - ownership probe: `fs.existsSync` FOLLOWS symlinks and reports `false` for
|
|
184
|
+
// a DANGLING one, so a dangling `package.json` symlink classified as absent
|
|
185
|
+
// and the write below followed the link outside the directory GSD owns.
|
|
186
|
+
// `classifyMarker` uses `lstat` + `isFile()`, so a symlink or a directory at
|
|
187
|
+
// the marker path is classified `foreign` and left strictly alone.
|
|
188
|
+
// - create: a plain `writeFileSync` leaves the classify->write window open.
|
|
189
|
+
// `ensureCommonJsMarker` creates with `flag: 'wx'` (O_EXCL), so anything
|
|
190
|
+
// that appears at the path in between fails with EEXIST instead of being
|
|
191
|
+
// followed or overwritten.
|
|
192
|
+
//
|
|
193
|
+
// The exported signatures are unchanged (both still return a boolean), so every
|
|
194
|
+
// caller and the #2717 tests are unaffected.
|
|
169
195
|
// ---------------------------------------------------------------------------
|
|
170
|
-
/** The exact marker content GSD writes, matching installSharedHooksBundle. */
|
|
171
|
-
const COMMONJS_MARKER_CONTENT = '{"type":"commonjs"}\n';
|
|
172
196
|
/**
|
|
173
197
|
* Ensure a `package.json` forcing CommonJS mode exists in `dir` (the directory
|
|
174
198
|
* holding GSD-staged `.js` hook scripts). Idempotent: a no-op if the marker is
|
|
175
|
-
* already present with GSD's content.
|
|
176
|
-
*
|
|
177
|
-
* user-authored package.json (it leaves such a file in place; the user owns it).
|
|
199
|
+
* already present with GSD's content. Never clobbers a distinct user-authored
|
|
200
|
+
* package.json (it leaves such a file in place; the user owns it).
|
|
178
201
|
*
|
|
179
202
|
* @param dir - absolute path to the directory holding the staged .js hooks
|
|
180
|
-
* @returns `true` if
|
|
203
|
+
* @returns `true` if GSD's marker is present after the call (written or already there)
|
|
181
204
|
*/
|
|
182
205
|
function ensureCommonJsMarker(dir) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (existing.trim() === '{"type":"commonjs"}')
|
|
189
|
-
return true;
|
|
190
|
-
// A distinct package.json the user owns — do NOT clobber. The hook will
|
|
191
|
-
// load as whatever type the user declared; that is the user's choice.
|
|
192
|
-
return false;
|
|
193
|
-
}
|
|
194
|
-
node_fs_1.default.writeFileSync(markerPath, COMMONJS_MARKER_CONTENT);
|
|
195
|
-
return true;
|
|
196
|
-
}
|
|
197
|
-
catch {
|
|
198
|
-
// Best-effort: a marker write failure must not fail the whole install.
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
206
|
+
// 'written' | 'unchanged' -> the marker is ours and present.
|
|
207
|
+
// 'preserved-foreign' -> a file GSD does not own is there; left untouched.
|
|
208
|
+
// 'failed' -> environmental (EACCES/EROFS/ENOSPC); best-effort.
|
|
209
|
+
const outcome = (0, commonjs_marker_cjs_1.ensureCommonJsMarker)(dir);
|
|
210
|
+
return outcome === 'written' || outcome === 'unchanged';
|
|
201
211
|
}
|
|
202
212
|
/**
|
|
203
213
|
* Remove the CommonJS marker from `dir` on uninstall — but ONLY if it carries
|
|
204
214
|
* GSD's exact marker content. A user-authored package.json is never deleted.
|
|
205
|
-
* Mirrors the kimi uninstall guard in bin/install.js.
|
|
206
215
|
*
|
|
207
216
|
* @param dir - absolute path to the directory that held the staged .js hooks
|
|
208
217
|
* @returns `true` if a GSD-owned marker was removed
|
|
209
218
|
*/
|
|
210
219
|
function removeCommonJsMarkerIfGsdOwned(dir) {
|
|
211
|
-
|
|
212
|
-
try {
|
|
213
|
-
if (!node_fs_1.default.existsSync(markerPath))
|
|
214
|
-
return false;
|
|
215
|
-
const content = node_fs_1.default.readFileSync(markerPath, 'utf8').trim();
|
|
216
|
-
if (content === '{"type":"commonjs"}') {
|
|
217
|
-
node_fs_1.default.unlinkSync(markerPath);
|
|
218
|
-
return true;
|
|
219
|
-
}
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
catch {
|
|
223
|
-
return false;
|
|
224
|
-
}
|
|
220
|
+
return (0, commonjs_marker_cjs_1.removeCommonJsMarker)(dir);
|
|
225
221
|
}
|
|
226
222
|
// ---------------------------------------------------------------------------
|
|
227
223
|
// parseTomlValue + findMultilineBasicStringClose
|
|
@@ -495,6 +491,98 @@ function rewriteLegacyManagedNodeHookCommands(settings, absoluteRunner, opts) {
|
|
|
495
491
|
}
|
|
496
492
|
return changed;
|
|
497
493
|
}
|
|
494
|
+
// ---------------------------------------------------------------------------
|
|
495
|
+
// Shared: reconcileManagedShellHookCommands (#3329)
|
|
496
|
+
// ---------------------------------------------------------------------------
|
|
497
|
+
/**
|
|
498
|
+
* Rewrite already-registered managed `.sh` hook `command` strings to the shape
|
|
499
|
+
* the current installer would generate (#3329).
|
|
500
|
+
*
|
|
501
|
+
* applySettingsJsonHooks registers the four `.sh` managed hooks only-if-absent,
|
|
502
|
+
* so an entry registered by an older installer keeps its old command forever —
|
|
503
|
+
* `/gsd-update` (which re-invokes the installer) never re-derived it. On
|
|
504
|
+
* Claude/win32 that left the pre-#580/#3393 bash-runner-prefixed commands
|
|
505
|
+
* (`bash "<script>.sh"`, `"<git>/bash.exe" "<script>.sh"`) in place, spawning a
|
|
506
|
+
* nested bash on every hook fire. The #2979 rewriter above cannot help: it is
|
|
507
|
+
* Node-only by design (its basename gate contains only `.js` filenames).
|
|
508
|
+
*
|
|
509
|
+
* Scoping / safety:
|
|
510
|
+
* - Inert unless `shellHookOmitsBashRunner({ platform, runtime, isShellHook:
|
|
511
|
+
* true })` — the exact combination whose correct command shape changed. Where
|
|
512
|
+
* the bash runner is still correct (non-Windows, non-claude), nothing is
|
|
513
|
+
* rewritten, so the reconcile cannot churn unrelated installs.
|
|
514
|
+
* - Only entries whose parsed script token's basename exactly equals one of the
|
|
515
|
+
* expected managed `.sh` filenames are touched. A user hook would have to
|
|
516
|
+
* live at a path ending in exactly `gsd-session-state.sh` etc. — i.e. the
|
|
517
|
+
* GSD-installed file — to match. Extra-token commands (env prefixes, extra
|
|
518
|
+
* args) and args-form launcher entries (#976) never match the strict
|
|
519
|
+
* `[runner ]<script>` two-token shape and are left alone.
|
|
520
|
+
* - A null/empty expected command disables rewriting for that hook (a
|
|
521
|
+
* bash-runner-unavailable install must not have its entry nulled).
|
|
522
|
+
*
|
|
523
|
+
* @param settings settings.json-shaped object; mutated in place
|
|
524
|
+
* @param expected map of managed `.sh` filename → the command this install
|
|
525
|
+
* would register today (from buildHookCommand / buildLocalShellHookCommand)
|
|
526
|
+
* @param opts platform/runtime override (default: current process)
|
|
527
|
+
* @returns true when any command was rewritten
|
|
528
|
+
*/
|
|
529
|
+
function reconcileManagedShellHookCommands(settings, expected, opts) {
|
|
530
|
+
if (!settings || !settings.hooks || !expected)
|
|
531
|
+
return false;
|
|
532
|
+
if (!opts)
|
|
533
|
+
opts = {};
|
|
534
|
+
const platform = opts.platform || process.platform;
|
|
535
|
+
const runtime = opts.runtime || 'generic';
|
|
536
|
+
if (!shellHookOmitsBashRunner({ platform, runtime, isShellHook: true }))
|
|
537
|
+
return false;
|
|
538
|
+
const expectedByBasename = new Map();
|
|
539
|
+
for (const [hookFile, command] of Object.entries(expected)) {
|
|
540
|
+
if (typeof command === 'string' && command.length > 0) {
|
|
541
|
+
expectedByBasename.set(hookFile, command);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (expectedByBasename.size === 0)
|
|
545
|
+
return false;
|
|
546
|
+
let changed = false;
|
|
547
|
+
for (const entries of Object.values(settings.hooks)) {
|
|
548
|
+
if (!Array.isArray(entries))
|
|
549
|
+
continue;
|
|
550
|
+
for (const entry of entries) {
|
|
551
|
+
if (!entry || !Array.isArray(entry.hooks))
|
|
552
|
+
continue;
|
|
553
|
+
for (const h of entry.hooks) {
|
|
554
|
+
if (!h || typeof h.command !== 'string')
|
|
555
|
+
continue;
|
|
556
|
+
if (Array.isArray(h.args) && h.args.length > 0)
|
|
557
|
+
continue;
|
|
558
|
+
let trimmed = h.command.trim();
|
|
559
|
+
const hadPowerShellCallOperator = platform === 'win32' && /^&\s+/.test(trimmed);
|
|
560
|
+
if (hadPowerShellCallOperator) {
|
|
561
|
+
trimmed = trimmed.replace(/^&\s+/, '').trim();
|
|
562
|
+
}
|
|
563
|
+
// Strict `[runner ]<script>` shape: an optional single runner token
|
|
564
|
+
// (bare, 'single-quoted', or "double-quoted") followed by the script
|
|
565
|
+
// token. Anything else (env prefixes, extra flags, pipelines) does not
|
|
566
|
+
// match and is left untouched.
|
|
567
|
+
const m = trimmed.match(/^(?:(?:"([^"]+)"|'([^']+)'|(\S+))\s+)?(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/);
|
|
568
|
+
if (!m)
|
|
569
|
+
continue;
|
|
570
|
+
const scriptToken = m[4] || m[5] || m[6] || '';
|
|
571
|
+
if (!scriptToken)
|
|
572
|
+
continue;
|
|
573
|
+
const basename = shellCmdProjection.posixNormalize(scriptToken).split('/').pop() || '';
|
|
574
|
+
const expectedCommand = expectedByBasename.get(basename);
|
|
575
|
+
if (!expectedCommand)
|
|
576
|
+
continue;
|
|
577
|
+
if (h.command === expectedCommand)
|
|
578
|
+
continue;
|
|
579
|
+
h.command = expectedCommand;
|
|
580
|
+
changed = true;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return changed;
|
|
585
|
+
}
|
|
498
586
|
function buildCodexHookBlock(targetDir, opts) {
|
|
499
587
|
const absoluteRunner = opts && opts.absoluteRunner;
|
|
500
588
|
if (!absoluteRunner)
|
|
@@ -1072,7 +1160,15 @@ function writeCursorHooksJson(targetDir, src, opts) {
|
|
|
1072
1160
|
// installSharedHooksBundle (the only other writer of this marker); without
|
|
1073
1161
|
// it, a ~/.cursor/package.json declaring {"type":"module"} makes Node load
|
|
1074
1162
|
// these require()-using scripts as ESM and every Cursor hook fails silently.
|
|
1075
|
-
|
|
1163
|
+
//
|
|
1164
|
+
// #2544: gated on having actually staged a script, mirroring
|
|
1165
|
+
// installSharedHooksBundle's `stagedHooks` gate. hooks/ is shared space, and
|
|
1166
|
+
// this function mkdirs it unconditionally — so an ungated write drops a GSD
|
|
1167
|
+
// marker into a directory GSD created but did not fill, which is the same
|
|
1168
|
+
// write-into-someone-else's-territory this issue is about.
|
|
1169
|
+
if (installedScripts.size > 0) {
|
|
1170
|
+
ensureCommonJsMarker(hooksDir);
|
|
1171
|
+
}
|
|
1076
1172
|
const hookOpts = { runtime: 'cursor', platform: opts.platform || process.platform };
|
|
1077
1173
|
const commands = {};
|
|
1078
1174
|
for (const ev of events) {
|
|
@@ -1266,7 +1362,12 @@ function writeWindsurfHooksJson(targetDir, src, opts) {
|
|
|
1266
1362
|
// installSharedHooksBundle (the only other writer of this marker); without
|
|
1267
1363
|
// it, a config-root package.json declaring {"type":"module"} makes Node load
|
|
1268
1364
|
// these require()-using scripts as ESM and the Windsurf hooks fail silently.
|
|
1269
|
-
|
|
1365
|
+
//
|
|
1366
|
+
// #2544: gated on having actually staged a script — see the identical gate in
|
|
1367
|
+
// the Cursor writer above and `stagedHooks` in installSharedHooksBundle.
|
|
1368
|
+
if (installedScripts.size > 0) {
|
|
1369
|
+
ensureCommonJsMarker(hooksDir);
|
|
1370
|
+
}
|
|
1270
1371
|
const hookOpts = { runtime: 'windsurf', platform: opts.platform || process.platform };
|
|
1271
1372
|
const commands = {};
|
|
1272
1373
|
for (const ev of WINDSURF_HOOK_EVENTS) {
|
|
@@ -1597,6 +1698,63 @@ function applySettingsJsonHooks(settings, opts) {
|
|
|
1597
1698
|
else if (!hasWorktreePathGuardHook && !node_fs_1.default.existsSync(worktreePathGuardFile)) {
|
|
1598
1699
|
console.warn(` ${yellow}⚠${reset} Skipped worktree path guard hook — gsd-worktree-path-guard.js not found at target`);
|
|
1599
1700
|
}
|
|
1701
|
+
// Configure PreToolUse hook for Agent-dispatch isolation (#3045)
|
|
1702
|
+
// Hard-blocks an executor Agent() dispatch (subagent_type="gsd-executor")
|
|
1703
|
+
// missing its harness isolation parameter when this project's resolved
|
|
1704
|
+
// dispatch isolation is harness-worktree. Prevents the executor from
|
|
1705
|
+
// silently running and committing in the primary checkout when the
|
|
1706
|
+
// model-authored dispatch omits isolation="worktree".
|
|
1707
|
+
const agentIsolationGuardCommand = isGlobal
|
|
1708
|
+
? buildHookCommand(targetDir, 'gsd-agent-isolation-guard.js', hookOpts)
|
|
1709
|
+
: localCmd('gsd-agent-isolation-guard.js');
|
|
1710
|
+
const hasAgentIsolationGuardHook = settings.hooks[preToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-agent-isolation-guard')));
|
|
1711
|
+
const agentIsolationGuardFile = node_path_1.default.join(targetDir, 'hooks', 'gsd-agent-isolation-guard.js');
|
|
1712
|
+
if (!hasAgentIsolationGuardHook && node_fs_1.default.existsSync(agentIsolationGuardFile) && agentIsolationGuardCommand) {
|
|
1713
|
+
settings.hooks[preToolEvent].push({
|
|
1714
|
+
// #3045 MAJOR 1: widened from "Agent"-only — hooks.json's own
|
|
1715
|
+
// PostToolUse precedent (context-monitor) already hedges both names,
|
|
1716
|
+
// and the hook itself now accepts tool_name "Task" too.
|
|
1717
|
+
matcher: 'Agent|Task',
|
|
1718
|
+
hooks: [
|
|
1719
|
+
{
|
|
1720
|
+
type: 'command',
|
|
1721
|
+
command: agentIsolationGuardCommand,
|
|
1722
|
+
timeout: 5
|
|
1723
|
+
}
|
|
1724
|
+
]
|
|
1725
|
+
});
|
|
1726
|
+
console.log(` ${green}✓${reset} Configured agent isolation dispatch guard hook`);
|
|
1727
|
+
}
|
|
1728
|
+
else if (!hasAgentIsolationGuardHook && !node_fs_1.default.existsSync(agentIsolationGuardFile)) {
|
|
1729
|
+
console.warn(` ${yellow}⚠${reset} Skipped agent isolation guard hook — gsd-agent-isolation-guard.js not found at target`);
|
|
1730
|
+
}
|
|
1731
|
+
// Configure PreToolUse hook for catastrophic-shrink protection (#2255, fix 3 of #973)
|
|
1732
|
+
// Hard-blocks a whole-file Write that collapses a curated .planning/ artifact
|
|
1733
|
+
// (ROADMAP.md, milestone roadmaps, STATE.md) far below its on-disk size.
|
|
1734
|
+
// Escape hatches (both named in the block message): the single-use
|
|
1735
|
+
// sentinel .planning/.gsd-allow-shrink (workflow steps — a per-step env
|
|
1736
|
+
// cannot reach a hook) and GSD_ALLOW_PLANNING_SHRINK=1 (interactive).
|
|
1737
|
+
const writeGuardCommand = isGlobal
|
|
1738
|
+
? buildHookCommand(targetDir, 'gsd-write-guard.js', hookOpts)
|
|
1739
|
+
: localCmd('gsd-write-guard.js');
|
|
1740
|
+
const hasWriteGuardHook = settings.hooks[preToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-write-guard')));
|
|
1741
|
+
const writeGuardFile = node_path_1.default.join(targetDir, 'hooks', 'gsd-write-guard.js');
|
|
1742
|
+
if (!hasWriteGuardHook && node_fs_1.default.existsSync(writeGuardFile) && writeGuardCommand) {
|
|
1743
|
+
settings.hooks[preToolEvent].push({
|
|
1744
|
+
matcher: 'Write',
|
|
1745
|
+
hooks: [
|
|
1746
|
+
{
|
|
1747
|
+
type: 'command',
|
|
1748
|
+
command: writeGuardCommand,
|
|
1749
|
+
timeout: 5
|
|
1750
|
+
}
|
|
1751
|
+
]
|
|
1752
|
+
});
|
|
1753
|
+
console.log(` ${green}✓${reset} Configured write guard hook (catastrophic-shrink protection)`);
|
|
1754
|
+
}
|
|
1755
|
+
else if (!hasWriteGuardHook && !node_fs_1.default.existsSync(writeGuardFile)) {
|
|
1756
|
+
console.warn(` ${yellow}⚠${reset} Skipped write guard hook — gsd-write-guard.js not found at target`);
|
|
1757
|
+
}
|
|
1600
1758
|
// Configure commit validation hook (Conventional Commits enforcement, opt-in)
|
|
1601
1759
|
const validateCommitCommand = isGlobal
|
|
1602
1760
|
? buildHookCommand(targetDir, 'gsd-validate-commit.sh', hookOpts)
|
|
@@ -1701,6 +1859,22 @@ function applySettingsJsonHooks(settings, opts) {
|
|
|
1701
1859
|
else if (!hasPhaseBoundaryHook && !phaseBoundaryCommand) {
|
|
1702
1860
|
console.warn(` ${yellow}⚠${reset} Skipped phase boundary hook — Bash executable path unavailable (#3393)`);
|
|
1703
1861
|
}
|
|
1862
|
+
// #3329: the four `.sh` sites above register only-if-absent, so an entry
|
|
1863
|
+
// registered by an older installer keeps its old command forever —
|
|
1864
|
+
// /gsd-update (which re-invokes the installer) never re-derived it. On
|
|
1865
|
+
// Claude/win32 that left the pre-#580/#3393 bash-runner-prefixed commands
|
|
1866
|
+
// in settings.json indefinitely. Reconcile existing managed `.sh` entries
|
|
1867
|
+
// to the command this install would generate today. Inert wherever the
|
|
1868
|
+
// bash runner is still the correct shape; scoped to exact managed
|
|
1869
|
+
// basenames so user-authored hooks are never touched.
|
|
1870
|
+
if (reconcileManagedShellHookCommands(settings, {
|
|
1871
|
+
'gsd-validate-commit.sh': validateCommitCommand,
|
|
1872
|
+
'gsd-graphify-update.sh': graphifyUpdateCommand,
|
|
1873
|
+
'gsd-session-state.sh': sessionStateCommand,
|
|
1874
|
+
'gsd-phase-boundary.sh': phaseBoundaryCommand,
|
|
1875
|
+
}, { platform: hookOpts.platform, runtime })) {
|
|
1876
|
+
console.log(` ${green}✓${reset} Reconciled managed .sh hook commands to current format (#3329)`);
|
|
1877
|
+
}
|
|
1704
1878
|
// ── Extended hook events: SubagentStop / Stop / PreCompact / SubagentStart
|
|
1705
1879
|
// (#788 + #770 + #2092) ────────────────────────────────────────────────
|
|
1706
1880
|
// Claude Code (since #770) and Qwen Code (since #788) both support the
|
|
@@ -1925,6 +2099,7 @@ function buildKimiHooksTomlBlock(targetDir, opts) {
|
|
|
1925
2099
|
{ event: 'PreToolUse', command: cmd('gsd-prompt-guard.js'), matcher: 'WriteFile|StrReplaceFile', timeout: 5 },
|
|
1926
2100
|
{ event: 'PreToolUse', command: cmd('gsd-read-guard.js'), matcher: 'WriteFile|StrReplaceFile', timeout: 5 },
|
|
1927
2101
|
{ event: 'PreToolUse', command: cmd('gsd-worktree-path-guard.js'), matcher: 'WriteFile|StrReplaceFile', timeout: 5 },
|
|
2102
|
+
{ event: 'PreToolUse', command: cmd('gsd-write-guard.js'), matcher: 'WriteFile', timeout: 5 },
|
|
1928
2103
|
{ event: 'PreToolUse', command: cmd('gsd-workflow-guard.js'), matcher: 'Shell|WriteFile|StrReplaceFile', timeout: 5 },
|
|
1929
2104
|
{ event: 'PreToolUse', command: cmd('gsd-validate-commit.sh'), matcher: 'Shell', timeout: 5 },
|
|
1930
2105
|
// PostToolUse
|
|
@@ -2131,6 +2306,7 @@ module.exports = {
|
|
|
2131
2306
|
applySettingsJsonHooks,
|
|
2132
2307
|
referencesHook,
|
|
2133
2308
|
rewriteLegacyManagedNodeHookCommands,
|
|
2309
|
+
reconcileManagedShellHookCommands,
|
|
2134
2310
|
normalizeNodePath,
|
|
2135
2311
|
resolveNodeRunner,
|
|
2136
2312
|
resolveBashRunner,
|
|
@@ -193,7 +193,9 @@ exports.NO_LOCAL_CONFIG_DIR_SENTINEL = '(no-local-config-dir)';
|
|
|
193
193
|
* Pure runtime-identity projection. Relocated from `bin/install.js` per
|
|
194
194
|
* ADR-1508 (epic #1507, #1510 Phase 1) so the Runtime Artifact Conversion
|
|
195
195
|
* Module's rewrite engine can consume it without importing the installer.
|
|
196
|
-
* `bin/install.js` re-
|
|
196
|
+
* Consumers import it from here. `bin/install.js` used to re-export it for
|
|
197
|
+
* back-compat; that re-export was retired in #2876, once an audit found the
|
|
198
|
+
* compatibility spine had no production consumer at all.
|
|
197
199
|
*/
|
|
198
200
|
function getDirName(runtime) {
|
|
199
201
|
if (!runtime)
|
|
@@ -29,6 +29,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
29
29
|
};
|
|
30
30
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
31
|
exports.formatGsdSlash = formatGsdSlash;
|
|
32
|
+
exports.resolveExplicitRuntime = resolveExplicitRuntime;
|
|
32
33
|
exports.resolveRuntime = resolveRuntime;
|
|
33
34
|
exports.formatGsdSlashFor = formatGsdSlashFor;
|
|
34
35
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
@@ -75,19 +76,21 @@ function formatGsdSlash(commandName, runtime) {
|
|
|
75
76
|
return `/gsd-${token}${tail}`;
|
|
76
77
|
}
|
|
77
78
|
/**
|
|
78
|
-
* Resolve the
|
|
79
|
+
* Resolve the explicit runtime for a project directory, from the two
|
|
80
|
+
* explicit sources only — no default is applied.
|
|
79
81
|
*
|
|
80
|
-
*
|
|
82
|
+
* env.GSD_RUNTIME > config.runtime > null
|
|
81
83
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
84
|
+
* Returns null when neither explicit source is set, so a caller can
|
|
85
|
+
* distinguish "config said claude" from "nothing was set" (needed by
|
|
86
|
+
* #3245's host-detection rung, which must only run when this returns null).
|
|
85
87
|
*
|
|
86
88
|
* @param projectDir - path to the project directory, or null/undefined
|
|
87
|
-
* @
|
|
89
|
+
* @param env - environment variables to read GSD_RUNTIME from; defaults to process.env
|
|
90
|
+
* @returns the resolved runtime name, or null if neither source is set
|
|
88
91
|
*/
|
|
89
|
-
function
|
|
90
|
-
const envRuntime = (0, runtime_name_policy_cjs_1.resolveRuntimeNameFromCandidates)(
|
|
92
|
+
function resolveExplicitRuntime(projectDir, env = process.env) {
|
|
93
|
+
const envRuntime = (0, runtime_name_policy_cjs_1.resolveRuntimeNameFromCandidates)(env['GSD_RUNTIME']);
|
|
91
94
|
if (envRuntime)
|
|
92
95
|
return envRuntime;
|
|
93
96
|
if (projectDir) {
|
|
@@ -113,7 +116,22 @@ function resolveRuntime(projectDir) {
|
|
|
113
116
|
// runtime output formatting.
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
|
-
return
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Resolve the effective runtime for a project directory.
|
|
123
|
+
*
|
|
124
|
+
* process.env.GSD_RUNTIME > config.runtime > 'claude'
|
|
125
|
+
*
|
|
126
|
+
* Mirrors the precedence already used by profile-output.cjs and the rest of
|
|
127
|
+
* the runtime resolution chain. Returns a lowercased string so downstream
|
|
128
|
+
* comparisons can be case-blind.
|
|
129
|
+
*
|
|
130
|
+
* @param projectDir - path to the project directory, or null/undefined
|
|
131
|
+
* @returns the resolved runtime name
|
|
132
|
+
*/
|
|
133
|
+
function resolveRuntime(projectDir) {
|
|
134
|
+
return resolveExplicitRuntime(projectDir) ?? 'claude';
|
|
117
135
|
}
|
|
118
136
|
/**
|
|
119
137
|
* Convenience: format using the runtime resolved from a project directory.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Section Manifest — pure `when=` evaluator over `InvocationFacts`, mapping
|
|
4
|
+
* a document-order list of parsed `<!-- gsd:section -->` sections (Phase 3,
|
|
5
|
+
* `src/workflow-fragments.cts`) to an included/excluded partition for one
|
|
6
|
+
* concrete invocation (ADR-1671 epic #1671, Phase 5 / issue #2932,
|
|
7
|
+
* `.gsd/phase/chore-2932-init-section-manifest/40-design.md`).
|
|
8
|
+
*
|
|
9
|
+
* Pure module: no I/O, no dependency beyond node built-ins and the sibling
|
|
10
|
+
* compiled module `workflow-fragments.cjs`, whose {@link
|
|
11
|
+
* workflowFragments.WHEN_VOCABULARY} is imported and never redeclared here
|
|
12
|
+
* (DEFECT.GENERATIVE-FIX — a second frozen copy of the same 4 strings would
|
|
13
|
+
* silently desync from the source of truth the moment either side is edited
|
|
14
|
+
* without the other).
|
|
15
|
+
*
|
|
16
|
+
* ## The evaluator is a LOOKUP, not a parser
|
|
17
|
+
*
|
|
18
|
+
* Derived from Greenspun's Tenth Rule (ADR-1671:69 cites it by name) and
|
|
19
|
+
* binding on this implementation: `when=` is a closed vocabulary, widened
|
|
20
|
+
* from 4 to 14 entries via the ADR-1671 amendment for #2992 (epic #1671
|
|
21
|
+
* Phase 6.1; see `.gsd/phase/chore-2992-widen-when-vocabulary/
|
|
22
|
+
* 40-design.md`), then from 14 to 19 via the ADR-1671 amendment for #2993
|
|
23
|
+
* (epic #1671 Phase 6.2; see `.gsd/phase/chore-2993-fragmentize-plan-phase/
|
|
24
|
+
* 40-design.md`), then from 19 to 20 via the ADR-1671 amendment for #2994
|
|
25
|
+
* (epic #1671 Phase 6.3), then from 20 to 23 via a further #2994 amendment
|
|
26
|
+
* fragmentizing `code-review.md` and `complete-milestone.md`, then from 23
|
|
27
|
+
* to 24 via a still further #2994 amendment fragmentizing `autonomous.md`,
|
|
28
|
+
* then from 24 to 26 via a still further #2994 amendment fragmentizing
|
|
29
|
+
* `review.md` and `discuss-phase-assumptions.md`, then from 26 to 30 — and
|
|
30
|
+
* finally to 29, `flag:--full` having been retired as dead vocabulary — via the
|
|
31
|
+
* final #2994 slice fragmentizing `docs-update.md`, `update.md`,
|
|
32
|
+
* `transition.md`, and `new-milestone.md`.
|
|
33
|
+
* {@link WHEN_PREDICATES} is a total map from each frozen
|
|
34
|
+
* vocabulary entry to exactly one predicate over {@link InvocationFacts}.
|
|
35
|
+
* It MUST NOT tokenize, split on operators, or interpret structure in the
|
|
36
|
+
* `when=` string — the moment it parses, the ad-hoc language has begun.
|
|
37
|
+
* Every entry below is therefore a HAND-WRITTEN LITERAL: deriving a
|
|
38
|
+
* predicate's flag/state name from its atom string (e.g. slicing `--fix`
|
|
39
|
+
* out of `'flag:--fix'`) is tokenization relocated into this map and is
|
|
40
|
+
* forbidden even though it would be shorter — the redundancy between each
|
|
41
|
+
* key and its literal token is deliberate, and the bidirectional parity
|
|
42
|
+
* test below catches any desync a hand-written entry could introduce. An
|
|
43
|
+
* unrecognized `when=` value fails closed via {@link selectSections}
|
|
44
|
+
* throwing a `TypeError` carrying `.reason = REASON.UNKNOWN_WHEN`; it is
|
|
45
|
+
* never silently excluded (Postel's Law: liberal on FORMAT elsewhere in the
|
|
46
|
+
* pipeline, strict on this SEMANTIC boundary — matching the discipline
|
|
47
|
+
* Phase 3 already established for the same vocabulary at parse time).
|
|
48
|
+
*
|
|
49
|
+
* ## Totality over facts
|
|
50
|
+
*
|
|
51
|
+
* Every predicate treats an absent/missing fact key as falsy WITHOUT
|
|
52
|
+
* throwing — {@link InvocationFacts} is a plain data object handed in by a
|
|
53
|
+
* caller (the init CLI seam) that may not always populate every field, and
|
|
54
|
+
* this module must never surprise that caller with an exception for an
|
|
55
|
+
* omission rather than a malformed `when=` value.
|
|
56
|
+
*
|
|
57
|
+
* ## Partition invariant
|
|
58
|
+
*
|
|
59
|
+
* {@link selectSections} returns `included` and `excluded` id arrays that
|
|
60
|
+
* together contain every input section's `id` exactly once, in the SAME
|
|
61
|
+
* relative document order they appeared in the input — never mutating the
|
|
62
|
+
* input array or its elements.
|
|
63
|
+
*
|
|
64
|
+
* ADR-457 build-at-publish: compiled by tsc to
|
|
65
|
+
* gsd-core/bin/lib/section-manifest.cjs (gitignored).
|
|
66
|
+
*/
|
|
67
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
68
|
+
exports.WHEN_PREDICATES = exports.REASON = void 0;
|
|
69
|
+
exports.selectSections = selectSections;
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- workflow-fragments.cjs is a CommonJS module compiled from a sibling .cts source; `import x = require()` reads its module.exports namespace directly.
|
|
71
|
+
const workflowFragments = require("./workflow-fragments.cjs");
|
|
72
|
+
/**
|
|
73
|
+
* Frozen, stable reason codes for every `fail()` throw site in this module.
|
|
74
|
+
* Tests assert via `assert.equal(err.reason, REASON.X)` rather than
|
|
75
|
+
* regex-/substring-matching the human-readable message (CONTRIBUTING.md
|
|
76
|
+
* "Prohibited: Raw Text Matching on Test Outputs"; shape copied from
|
|
77
|
+
* `src/workflow-fragments.cts`'s own `REASON` export) — a message reword
|
|
78
|
+
* must never silently pass a test that exists to catch a behavior
|
|
79
|
+
* regression.
|
|
80
|
+
*
|
|
81
|
+
* Adding a new reason requires updating this map AND the test that locks
|
|
82
|
+
* `Object.keys(REASON).sort()` as a coordinated change.
|
|
83
|
+
*/
|
|
84
|
+
exports.REASON = Object.freeze({
|
|
85
|
+
UNKNOWN_WHEN: 'unknown_when',
|
|
86
|
+
});
|
|
87
|
+
/**
|
|
88
|
+
* Throws a `TypeError` naming the offending `when` value, carrying `reason`
|
|
89
|
+
* (one of {@link REASON}) as a typed property so callers/tests never need
|
|
90
|
+
* to pattern-match the message prose.
|
|
91
|
+
*/
|
|
92
|
+
function fail(reason, message) {
|
|
93
|
+
const err = new TypeError(`section-manifest: ${message}`);
|
|
94
|
+
err.reason = reason;
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Safely tests whether `facts.flags` contains `flag`, tolerating an absent,
|
|
99
|
+
* `null`, or non-`Set` (e.g. array) `flags` value without throwing —
|
|
100
|
+
* `.has` is checked to be callable before it is called, rather than
|
|
101
|
+
* assuming every {@link InvocationFacts.flags} is a real `Set` (totality
|
|
102
|
+
* over facts; not duck-typed — an array `flags` degrades to "not present",
|
|
103
|
+
* it is never iterated or `.includes`-checked).
|
|
104
|
+
*/
|
|
105
|
+
function hasFlag(facts, flag) {
|
|
106
|
+
return typeof facts.flags?.has === 'function' && facts.flags.has(flag) === true;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Total map from each frozen {@link workflowFragments.WHEN_VOCABULARY}
|
|
110
|
+
* entry to exactly one predicate over {@link InvocationFacts}. This is a
|
|
111
|
+
* LOOKUP, never a parser — see the module doc comment's "The evaluator is a
|
|
112
|
+
* LOOKUP, not a parser" section. Every entry is a hand-written literal; see
|
|
113
|
+
* the module doc comment for why deriving a predicate from its atom string
|
|
114
|
+
* is forbidden. Semantics confirmed against the section bodies themselves
|
|
115
|
+
* (design doc "Semantics confirmed against the section bodies themselves,
|
|
116
|
+
* not inferred from the id"):
|
|
117
|
+
*
|
|
118
|
+
* - `gap-closure-artifacts` — "For decimal/polish phases only (X.Y
|
|
119
|
+
* pattern) … Skip if phase number has no decimal" -> `state:gap-closure-phase`.
|
|
120
|
+
* - `regression-gate` — "Skip if: this is the first phase (no prior
|
|
121
|
+
* phases)" -> `state:has-prior-phases`.
|
|
122
|
+
* - `partial-wave` — "If `WAVE_FILTER` was used" -> `flag:--wave`.
|
|
123
|
+
*/
|
|
124
|
+
exports.WHEN_PREDICATES = Object.freeze(Object.assign(Object.create(null), {
|
|
125
|
+
always: () => true,
|
|
126
|
+
'flag:--wave': (facts) => hasFlag(facts, '--wave'),
|
|
127
|
+
'state:gap-closure-phase': (facts) => typeof facts.phaseNumber === 'string' && facts.phaseNumber.includes('.'),
|
|
128
|
+
'state:has-prior-phases': (facts) => facts.hasPriorPhases === true,
|
|
129
|
+
'flag:--auto': (facts) => hasFlag(facts, '--auto'),
|
|
130
|
+
'flag:--discuss': (facts) => hasFlag(facts, '--discuss'),
|
|
131
|
+
'flag:--fix': (facts) => hasFlag(facts, '--fix'),
|
|
132
|
+
'flag:--forensic': (facts) => hasFlag(facts, '--forensic'),
|
|
133
|
+
'flag:--ingest': (facts) => hasFlag(facts, '--ingest'),
|
|
134
|
+
'flag:--prd': (facts) => hasFlag(facts, '--prd'),
|
|
135
|
+
'flag:--research': (facts) => hasFlag(facts, '--research'),
|
|
136
|
+
'flag:--research-phase': (facts) => hasFlag(facts, '--research-phase'),
|
|
137
|
+
'flag:--reset-phase-numbers': (facts) => hasFlag(facts, '--reset-phase-numbers'),
|
|
138
|
+
'flag:--reviews': (facts) => hasFlag(facts, '--reviews'),
|
|
139
|
+
'flag:--validate': (facts) => hasFlag(facts, '--validate'),
|
|
140
|
+
'state:auto-advance-active': (facts) => facts.autoAdvanceActive === true,
|
|
141
|
+
'state:chunked-mode': (facts) => facts.chunkedMode === true,
|
|
142
|
+
'state:fallow-enabled': (facts) => facts.fallowEnabled === true,
|
|
143
|
+
'state:flat-mode': (facts) => facts.flatMode === true,
|
|
144
|
+
'state:git-create-tag': (facts) => facts.gitCreateTag === true,
|
|
145
|
+
'state:is-monorepo': (facts) => facts.isMonorepo === true,
|
|
146
|
+
'state:needs-codebase-map': (facts) => facts.needsCodebaseMap === true,
|
|
147
|
+
'state:next-channel': (facts) => facts.nextChannel === true,
|
|
148
|
+
'state:phase-mvp-mode': (facts) => facts.phaseMvpMode === true,
|
|
149
|
+
'state:plan-strategy-converge': (facts) => facts.planStrategyConverge === true,
|
|
150
|
+
'state:reviewer-instances-configured': (facts) => facts.reviewerInstancesConfigured === true,
|
|
151
|
+
'state:ui-phase-active': (facts) => facts.uiPhaseActive === true,
|
|
152
|
+
'state:workstream-active': (facts) => facts.workstreamActive === true,
|
|
153
|
+
'state:worktrees-enabled': (facts) => facts.worktreesEnabled === true,
|
|
154
|
+
}));
|
|
155
|
+
// Coordinated-change guard, checked at module load: every entry of the
|
|
156
|
+
// frozen WHEN_VOCABULARY (imported, never redeclared — see module doc
|
|
157
|
+
// comment) must have exactly one predicate here, and vice versa. This is
|
|
158
|
+
// the load-bearing half of the DEFECT.GENERATIVE-FIX parity contract; the
|
|
159
|
+
// test-level half (50-test-matrix.md rows 21-23) additionally asserts it
|
|
160
|
+
// from the vocabulary's own export so a 5th vocabulary entry added without
|
|
161
|
+
// a predicate fails loudly rather than silently falling through to
|
|
162
|
+
// REASON.UNKNOWN_WHEN only at run time.
|
|
163
|
+
for (const when of workflowFragments.WHEN_VOCABULARY) {
|
|
164
|
+
if (!Object.hasOwn(exports.WHEN_PREDICATES, when)) {
|
|
165
|
+
throw new Error(`section-manifest: WHEN_VOCABULARY entry "${when}" has no predicate in WHEN_PREDICATES`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Reverse half of the same coordinated-change guard: every own key of
|
|
169
|
+
// WHEN_PREDICATES must also appear in WHEN_VOCABULARY. Without this, a
|
|
170
|
+
// predicate key with no vocabulary entry would let the evaluator accept an
|
|
171
|
+
// atom that the parser (classifyMarker) rejects — a real divergence between
|
|
172
|
+
// the two shared-constant halves (DEFECT.GENERATIVE-FIX; B9/B10 in
|
|
173
|
+
// `.gsd/phase/chore-2992-widen-when-vocabulary/50-test-matrix.md`).
|
|
174
|
+
const VOCABULARY_SET = new Set(workflowFragments.WHEN_VOCABULARY);
|
|
175
|
+
for (const predicateKey of Object.keys(exports.WHEN_PREDICATES)) {
|
|
176
|
+
if (!VOCABULARY_SET.has(predicateKey)) {
|
|
177
|
+
throw new Error(`section-manifest: WHEN_PREDICATES entry "${predicateKey}" has no matching WHEN_VOCABULARY entry`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Partition `sections` (document order) into `included`/`excluded` id
|
|
182
|
+
* arrays for one set of `facts`, per {@link WHEN_PREDICATES}. Exact
|
|
183
|
+
* partition: every input id appears in exactly one of the two output
|
|
184
|
+
* arrays, in the same relative order it appeared in `sections`. Never
|
|
185
|
+
* mutates `sections` or its elements.
|
|
186
|
+
*
|
|
187
|
+
* @param sections - document-order sections carrying at least `{id, when}`
|
|
188
|
+
* @param facts - the concrete invocation's resolved facts
|
|
189
|
+
* @throws {SectionManifestError} with `.reason = REASON.UNKNOWN_WHEN` when a
|
|
190
|
+
* section's `when` value has no entry in {@link WHEN_PREDICATES} (fail
|
|
191
|
+
* closed — never silently excluded).
|
|
192
|
+
*/
|
|
193
|
+
function selectSections(sections, facts) {
|
|
194
|
+
const included = [];
|
|
195
|
+
const excluded = [];
|
|
196
|
+
for (const section of sections) {
|
|
197
|
+
if (!Object.hasOwn(exports.WHEN_PREDICATES, section.when)) {
|
|
198
|
+
fail(exports.REASON.UNKNOWN_WHEN, `section "${section.id}" has unrecognized when= value "${section.when}"`);
|
|
199
|
+
}
|
|
200
|
+
const predicate = exports.WHEN_PREDICATES[section.when];
|
|
201
|
+
if (predicate(facts)) {
|
|
202
|
+
included.push(section.id);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
excluded.push(section.id);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return { included, excluded };
|
|
209
|
+
}
|