@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
|
@@ -57,10 +57,16 @@ const phaseLifecycle = require("./phase-lifecycle.cjs");
|
|
|
57
57
|
const { deriveProgressFromRoadmap } = phaseLifecycle;
|
|
58
58
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
59
59
|
const stateDocument = require("./state-document.cjs");
|
|
60
|
-
const {
|
|
60
|
+
const { stateFieldValue } = stateDocument;
|
|
61
61
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
62
62
|
const phaseId = require("./phase-id.cjs");
|
|
63
|
-
const { comparePhaseNum, extractPhaseToken, normalizePhaseName,
|
|
63
|
+
const { comparePhaseNum, extractPhaseToken, matchPhaseDirs, normalizePhaseName, stripProjectCodePrefix } = phaseId;
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
65
|
+
const stateMod = require("./state.cjs");
|
|
66
|
+
const { readStateHeadFreshness } = stateMod;
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
68
|
+
const unusableInput = require("./unusable-input.cjs");
|
|
69
|
+
const { warnUnusableInput, UNUSABLE_REASON } = unusableInput;
|
|
64
70
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
65
71
|
/**
|
|
66
72
|
* Staleness threshold for idle-stranded detection. A clean tree whose last
|
|
@@ -83,15 +89,6 @@ exports.SITUATIONS = Object.freeze([
|
|
|
83
89
|
'unknown',
|
|
84
90
|
]);
|
|
85
91
|
// ─── Detection ─────────────────────────────────────────────────────────────────
|
|
86
|
-
/** Frontmatter scalar helper: prefer YAML frontmatter, fall back to body field. */
|
|
87
|
-
function fmScalar(fm, body, key, bodyField) {
|
|
88
|
-
const v = fm[key];
|
|
89
|
-
if (typeof v === 'string' && v.trim())
|
|
90
|
-
return v.trim();
|
|
91
|
-
if (typeof v === 'number' || typeof v === 'boolean')
|
|
92
|
-
return String(v);
|
|
93
|
-
return stateExtractField(body, bodyField);
|
|
94
|
-
}
|
|
95
92
|
/** Read a scalar value from a nested frontmatter object (e.g. progress.total_phases). */
|
|
96
93
|
function fmScalarKey(obj, key) {
|
|
97
94
|
if (!obj || typeof obj !== 'object')
|
|
@@ -112,17 +109,115 @@ function parseIntOrNull(s) {
|
|
|
112
109
|
}
|
|
113
110
|
function phaseTokenFromDirName(name) {
|
|
114
111
|
const token = extractPhaseToken(name);
|
|
115
|
-
|
|
112
|
+
// #2528: the shape probe runs on the PROJECT-CODE-STRIPPED token. A prefixed
|
|
113
|
+
// directory tokenizes to `MEM-05-80-20`, which does not start with a digit,
|
|
114
|
+
// so the unstripped probe rejected it and the entry was dropped before any
|
|
115
|
+
// resolution ran — every phase in a project-coded plan was invisible here.
|
|
116
|
+
// The full token is still what is returned: `comparePhaseNum` strips the
|
|
117
|
+
// prefix itself, so the sort is unaffected, and `matchPhaseDirs` needs the
|
|
118
|
+
// real directory name.
|
|
119
|
+
const probe = stripProjectCodePrefix(token);
|
|
120
|
+
return /^\d+(?:[A-Z])?(?:\.\d+)*(?:-|$)/i.test(probe) ? token : null;
|
|
116
121
|
}
|
|
117
122
|
/**
|
|
118
123
|
* Parse a `last_activity` value that may be an ISO date or a free-form string
|
|
119
124
|
* into an epoch-ms timestamp. Returns null when unparseable.
|
|
125
|
+
*
|
|
126
|
+
* #2570: `last_activity` routinely carries a trailing " — <description>" — the
|
|
127
|
+
* shape `templates/state.md` itself prescribes (`Last activity: [YYYY-MM-DD] —
|
|
128
|
+
* [What happened]`), which gsd-core's own STATE.md mirrors into frontmatter.
|
|
129
|
+
* `Date.parse` on the whole string returns NaN, and because `staleActivity`
|
|
130
|
+
* treats null as "not stale" (fails open), the ONLY idle/staleness detector
|
|
131
|
+
* never fired on any project whose last_activity retained its description.
|
|
132
|
+
* Be liberal in what we accept (Postel): read the leading ISO date/time token
|
|
133
|
+
* when the value carries one, so the description suffix — whatever separator
|
|
134
|
+
* (em dash or hyphen) it uses — no longer silently blinds the detector; fall
|
|
135
|
+
* back to a whole-string parse for any other shape a hand edit might use.
|
|
120
136
|
*/
|
|
137
|
+
/** Leading ISO date, with an optional time-of-day and offset. */
|
|
138
|
+
const ISO_LEADING_RE = /^(\d{4})-(\d{2})-(\d{2})((?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?)/;
|
|
139
|
+
/**
|
|
140
|
+
* A NAMED timezone designator at the start of the un-reconstructable remainder
|
|
141
|
+
* (#2571). ISO_LEADING_RE's offset group captures only `Z` / `±HH:MM`, so a
|
|
142
|
+
* named zone (GMT, EST, ...) is not in the leading token — it sits here.
|
|
143
|
+
* Reconstructing the token without it would let Date.parse read the time as
|
|
144
|
+
* LOCAL, shifting the instant by the host's offset (a wrong, host-dependent
|
|
145
|
+
* value), so a zone-shaped remainder must fail open (ADR-227).
|
|
146
|
+
*
|
|
147
|
+
* The shape is a short all-caps run (2–5 letters) that stands alone — the
|
|
148
|
+
* negative lookahead excludes the first letter of a Capitalised word like
|
|
149
|
+
* "Milestone", and an optional trailing offset is subsumed because the leading
|
|
150
|
+
* all-caps run already matches. Everything else — a lowercase or Capitalised
|
|
151
|
+
* description, a separator — is describable text and is reconstructed from the
|
|
152
|
+
* leading date.
|
|
153
|
+
*
|
|
154
|
+
* Consulted ONLY when the leading token captured a time-of-day (see the caller):
|
|
155
|
+
* a zone designator qualifies a clock time, so a BARE date can carry no zone
|
|
156
|
+
* hazard — reconstructing it is always just that date's UTC midnight, whatever
|
|
157
|
+
* trails it. Gating on the time keeps a description that merely opens with a
|
|
158
|
+
* tech acronym ("2026-06-08 CI green", "API refactor") on the reconstruct path
|
|
159
|
+
* instead of failing open. The prior "any letter" guard was too liberal — it
|
|
160
|
+
* failed open on every letter-led description and re-opened #2570.
|
|
161
|
+
*/
|
|
162
|
+
const ZONE_DESIGNATOR_RE = /^\s*[A-Z]{2,5}(?![A-Za-z])/;
|
|
163
|
+
/**
|
|
164
|
+
* True only when y/m/d name a date that actually exists on the calendar.
|
|
165
|
+
*
|
|
166
|
+
* `Date.parse` validates shape but not value: it rolls an out-of-range day
|
|
167
|
+
* FORWARD rather than rejecting it (`2026-02-30` -> `2026-03-02`,
|
|
168
|
+
* `2026-04-31` -> `2026-05-01`). Shape-only validation would therefore
|
|
169
|
+
* propagate a different, wrong instant instead of failing safe — precisely
|
|
170
|
+
* what ADR-227 ("validate shape AND value; on failure of either layer coerce
|
|
171
|
+
* to the contract's safe default, never propagate") exists to prevent. A
|
|
172
|
+
* round-trip through Date.UTC detects the rollover: any component the
|
|
173
|
+
* constructor normalised comes back changed.
|
|
174
|
+
*/
|
|
175
|
+
function isRealCalendarDate(year, month, day) {
|
|
176
|
+
if (month < 1 || month > 12 || day < 1 || day > 31)
|
|
177
|
+
return false;
|
|
178
|
+
const probe = new Date(Date.UTC(year, month - 1, day));
|
|
179
|
+
return (probe.getUTCFullYear() === year &&
|
|
180
|
+
probe.getUTCMonth() === month - 1 &&
|
|
181
|
+
probe.getUTCDate() === day);
|
|
182
|
+
}
|
|
121
183
|
function parseActivityTimestamp(raw) {
|
|
122
184
|
if (!raw)
|
|
123
185
|
return null;
|
|
124
|
-
const
|
|
125
|
-
|
|
186
|
+
const trimmed = raw.trim();
|
|
187
|
+
const iso = trimmed.match(ISO_LEADING_RE);
|
|
188
|
+
if (iso) {
|
|
189
|
+
const [, year, month, day, time] = iso;
|
|
190
|
+
// Reject an impossible calendar date outright rather than letting
|
|
191
|
+
// Date.parse substitute a rolled-forward one. null = "no activity signal",
|
|
192
|
+
// the safe default staleActivity already fails open on.
|
|
193
|
+
if (!isRealCalendarDate(Number(year), Number(month), Number(day)))
|
|
194
|
+
return null;
|
|
195
|
+
// The date is real, so stay as liberal as before (Postel): a whole-string
|
|
196
|
+
// parse still wins when the engine can make sense of the value. Reading the
|
|
197
|
+
// token first would silently DROP a trailing zone name -- "2026-06-08
|
|
198
|
+
// 12:34:56 GMT" parses whole as 12:34:56Z but as local time from the token,
|
|
199
|
+
// shifting the instant by the host's UTC offset.
|
|
200
|
+
const whole = Date.parse(trimmed);
|
|
201
|
+
if (!Number.isNaN(whole))
|
|
202
|
+
return whole;
|
|
203
|
+
// Whole-string failed: the value carries a suffix the engine can't read as
|
|
204
|
+
// one instant (#2570). Reconstruct from the leading token UNLESS the remainder
|
|
205
|
+
// is a named zone the token dropped (GMT, EST, ...): reconstructing without it
|
|
206
|
+
// reads the time as LOCAL and shifts the instant by the host's offset, so a
|
|
207
|
+
// zone-shaped remainder fails open (ADR-227: never propagate a wrong instant;
|
|
208
|
+
// null is the base's not-stale default). An ordinary description -- the #2570
|
|
209
|
+
// template's " -- description", or a hand edit's bare-space/tab/colon suffix --
|
|
210
|
+
// carries no zone and IS reconstructed. See ZONE_DESIGNATOR_RE for the shape;
|
|
211
|
+
// the earlier "any letter" guard failed open on every description and re-opened
|
|
212
|
+
// #2570 for whitespace-separated suffixes.
|
|
213
|
+
const rest = trimmed.slice(iso[0].length);
|
|
214
|
+
if (time && ZONE_DESIGNATOR_RE.test(rest))
|
|
215
|
+
return null;
|
|
216
|
+
const ms = Date.parse(`${year}-${month}-${day}${time}`);
|
|
217
|
+
return Number.isNaN(ms) ? null : ms;
|
|
218
|
+
}
|
|
219
|
+
const direct = Date.parse(trimmed);
|
|
220
|
+
return Number.isNaN(direct) ? null : direct;
|
|
126
221
|
}
|
|
127
222
|
/** Read-only git signals. Any git error is swallowed → "no git signal". */
|
|
128
223
|
function readGitSignals(cwd) {
|
|
@@ -134,6 +229,7 @@ function readGitSignals(cwd) {
|
|
|
134
229
|
maxBuffer: 4 * 1024 * 1024,
|
|
135
230
|
windowsHide: true,
|
|
136
231
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
232
|
+
timeout: 10_000,
|
|
137
233
|
});
|
|
138
234
|
}
|
|
139
235
|
catch {
|
|
@@ -183,7 +279,16 @@ function detectVerifyFailed(cwd, currentPhaseRaw) {
|
|
|
183
279
|
let targetDir;
|
|
184
280
|
if (phaseToken) {
|
|
185
281
|
const normalized = normalizePhaseName(phaseToken);
|
|
186
|
-
|
|
282
|
+
// #2528: the fourth directory-resolution site, and the one where a miss is
|
|
283
|
+
// silent — a phase whose directory cannot be found reports "not failed",
|
|
284
|
+
// which reads identically to a healthy phase. It must therefore apply the
|
|
285
|
+
// same canonical selection as the locator and the two command scans, or a
|
|
286
|
+
// dir like `05-80-20-cleanup` (phase 5 named "80/20 Cleanup") never
|
|
287
|
+
// surfaces its own failed verification. `entries` is already sorted, and
|
|
288
|
+
// `matchPhaseDirs` filters without reordering, so taking the first match
|
|
289
|
+
// preserves the previous `.find()` selection exactly.
|
|
290
|
+
const { matches } = matchPhaseDirs(entries, normalized);
|
|
291
|
+
targetDir = matches[0];
|
|
187
292
|
if (!targetDir)
|
|
188
293
|
return false;
|
|
189
294
|
}
|
|
@@ -250,6 +355,9 @@ function detectSignals(cwd, now = Date.now) {
|
|
|
250
355
|
stale_activity: false,
|
|
251
356
|
roadmap_total_phases: null,
|
|
252
357
|
roadmap_completed_phases: null,
|
|
358
|
+
// No STATE.md (or unreadable) → no stamp to compare. Unknown, not fresh.
|
|
359
|
+
state_commits_behind: null,
|
|
360
|
+
state_commit_stale: null,
|
|
253
361
|
};
|
|
254
362
|
if (!hasPlanning)
|
|
255
363
|
return empty;
|
|
@@ -263,21 +371,29 @@ function detectSignals(cwd, now = Date.now) {
|
|
|
263
371
|
// - body prose: `Phase: 3`, `**Status:** verifying`, `Total Phases: 5`
|
|
264
372
|
// Read each field across every form, scalar-first then nested then body, so
|
|
265
373
|
// the classifier works on real STATE.md files written by current GSD.
|
|
266
|
-
const statusRaw =
|
|
267
|
-
const pausedAtRaw =
|
|
268
|
-
const lastActivityRaw =
|
|
374
|
+
const statusRaw = stateFieldValue(fm, body, 'status', 'Status').value;
|
|
375
|
+
const pausedAtRaw = stateFieldValue(fm, body, 'paused_at', 'Paused At').value;
|
|
376
|
+
const lastActivityRaw = stateFieldValue(fm, body, 'last_activity', 'Last Activity').value;
|
|
269
377
|
// current_phase: scalar fm → nested (none) → body "Current Phase" → body "Phase".
|
|
270
378
|
// The body `Phase:` field is the canonical location in prose-form STATE.md
|
|
271
379
|
// (e.g. "Phase: 3" or "Phase: 3 — ui-review"); parse the leading number.
|
|
272
|
-
|
|
273
|
-
|
|
380
|
+
// #3187 / ADR-3180 Amendment 3 ("0.x split"): this read is DELIBERATELY
|
|
381
|
+
// unscoped — smart-entry classifies `gsd next` routing over the whole body,
|
|
382
|
+
// whereas state.cts's copies of this same field scope it to `## Current
|
|
383
|
+
// Position` (#1776/#2956). Those are two different questions sharing a
|
|
384
|
+
// name; folding the scoped read in here would silently change smart-entry's
|
|
385
|
+
// routing, an undisclosed Tier-2 change (design's Rejected #3). Both owner
|
|
386
|
+
// calls below intentionally pass the unscoped `body`, never a Current-
|
|
387
|
+
// Position slice.
|
|
388
|
+
const currentPhaseRaw = stateFieldValue(fm, body, 'current_phase', 'Current Phase').value ??
|
|
389
|
+
stateFieldValue(fm, body, null, 'Phase').value;
|
|
274
390
|
// total_phases & percent: nested `progress:` object takes precedence in the
|
|
275
391
|
// nested schema; scalar fm / body fields cover the flat schema.
|
|
276
392
|
const progressFm = typeof fm.progress === 'object' ? fm.progress : null;
|
|
277
393
|
const totalPhasesRaw = fmScalarKey(progressFm, 'total_phases') ??
|
|
278
|
-
|
|
394
|
+
stateFieldValue(fm, body, 'total_phases', 'Total Phases').value;
|
|
279
395
|
const progressRaw = fmScalarKey(progressFm, 'percent') ??
|
|
280
|
-
|
|
396
|
+
stateFieldValue(fm, body, 'progress', 'Progress').value;
|
|
281
397
|
// Blockers list: `- <text>` items under a `## Blockers` heading.
|
|
282
398
|
const blockers = [];
|
|
283
399
|
const blockersSection = (0, markdown_sectionizer_cjs_1.collectSection)(body, (h) => h.level === 2 && h.text.trim().toLowerCase() === 'blockers', { levelBounded: true });
|
|
@@ -290,7 +406,16 @@ function detectSignals(cwd, now = Date.now) {
|
|
|
290
406
|
// Stale = no recorded activity for IDLE_STALE_MS. Used only by idle-stranded.
|
|
291
407
|
// Computed here (with the clock seam) so the pure classify() stays a function
|
|
292
408
|
// of (signals, staleActivity) and detectSignals owns all disk reads.
|
|
409
|
+
// #3099 (ADR-1411 amendment): if last_activity is present but unparseable,
|
|
410
|
+
// emit a diagnostic so the silent fallback (stale_activity: false) is visible.
|
|
411
|
+
// The fallback itself stays — continuity is correct, the silence was the defect.
|
|
293
412
|
const lastActivityMs = parseActivityTimestamp(lastActivityRaw);
|
|
413
|
+
if (lastActivityRaw && lastActivityMs === null) {
|
|
414
|
+
warnUnusableInput({
|
|
415
|
+
reason: UNUSABLE_REASON.LAST_ACTIVITY_UNPARSEABLE,
|
|
416
|
+
source: paths.state,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
294
419
|
const staleActivity = lastActivityMs !== null && now() - lastActivityMs > IDLE_STALE_MS;
|
|
295
420
|
// Verify-failed may be signalled either by STATE.md status or by a failed
|
|
296
421
|
// STATUS: marker on the current phase's summary/verify artifact.
|
|
@@ -314,6 +439,11 @@ function detectSignals(cwd, now = Date.now) {
|
|
|
314
439
|
/* ROADMAP.md unreadable — leave null; isComplete falls back to legacy. */
|
|
315
440
|
}
|
|
316
441
|
}
|
|
442
|
+
// #2573: commit-age freshness proxy. Derived through state.cjs's
|
|
443
|
+
// readStateHeadFreshness so the tri-state and the hash fence stay identical
|
|
444
|
+
// to validate.health's W024 — one derivation, two surfaces.
|
|
445
|
+
const stateHeadRaw = stateFieldValue(fm, body, 'state_head', 'State Head').value;
|
|
446
|
+
const freshness = readStateHeadFreshness(cwd, stateHeadRaw);
|
|
317
447
|
return {
|
|
318
448
|
current_phase: parseIntOrNull(currentPhaseRaw),
|
|
319
449
|
total_phases: parseIntOrNull(totalPhasesRaw),
|
|
@@ -330,6 +460,8 @@ function detectSignals(cwd, now = Date.now) {
|
|
|
330
460
|
stale_activity: staleActivity,
|
|
331
461
|
roadmap_total_phases: roadmapTotalPhases,
|
|
332
462
|
roadmap_completed_phases: roadmapCompletedPhases,
|
|
463
|
+
state_commits_behind: freshness.commits_behind,
|
|
464
|
+
state_commit_stale: freshness.commit_stale,
|
|
333
465
|
};
|
|
334
466
|
}
|
|
335
467
|
// ─── Situation classification ─────────────────────────────────────────────────
|
|
@@ -144,7 +144,11 @@ function routeStateCommand({ state, args, cwd, raw, error }) {
|
|
|
144
144
|
'signal-resume': () => state.cmdSignalResume(cwd, raw),
|
|
145
145
|
'planned-phase': () => {
|
|
146
146
|
const a = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, ['phase', 'name', 'plans']);
|
|
147
|
-
|
|
147
|
+
// #3395: --name was parsed here but never forwarded (the StateModule
|
|
148
|
+
// signature had no channel for it), so the argument was silently
|
|
149
|
+
// dropped. It now persists into the Current Position `Phase:` line and
|
|
150
|
+
// the authoritative current_phase_name, mirroring begin-phase.
|
|
151
|
+
state.cmdStatePlannedPhase(cwd, strArg(a, 'phase'), strArg(a, 'name'), parsePlans(strArg(a, 'plans')), raw);
|
|
148
152
|
},
|
|
149
153
|
validate: () => state.cmdStateValidate(cwd, raw),
|
|
150
154
|
sync: () => {
|
|
@@ -10,8 +10,11 @@
|
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.KNOWN_STATUS_PATTERNS = exports.KNOWN_TEMPLATE_DEFAULTS = void 0;
|
|
12
12
|
exports.stateExtractField = stateExtractField;
|
|
13
|
+
exports.stateFieldValue = stateFieldValue;
|
|
14
|
+
exports.stateCurrentPositionSlice = stateCurrentPositionSlice;
|
|
13
15
|
exports.stateReplaceField = stateReplaceField;
|
|
14
16
|
exports.stateReplaceFieldWithFallback = stateReplaceFieldWithFallback;
|
|
17
|
+
exports.stateReplaceFieldInSession = stateReplaceFieldInSession;
|
|
15
18
|
exports.normalizeStateStatus = normalizeStateStatus;
|
|
16
19
|
exports.computeProgressPercent = computeProgressPercent;
|
|
17
20
|
exports.shouldPreserveExistingProgress = shouldPreserveExistingProgress;
|
|
@@ -19,10 +22,12 @@ exports.normalizeProgressNumbers = normalizeProgressNumbers;
|
|
|
19
22
|
exports.isStateTemplateDefault = isStateTemplateDefault;
|
|
20
23
|
exports.stateReplaceFieldIfTemplate = stateReplaceFieldIfTemplate;
|
|
21
24
|
const markdown_table_cjs_1 = require("./markdown-table.cjs");
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
const phase_lifecycle_cjs_1 = require("./phase-lifecycle.cjs");
|
|
26
|
+
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
27
|
+
const pattern_cjs_1 = require("./pattern.cjs");
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-scope.cjs is an export= CommonJS module
|
|
29
|
+
const planningScopeMod = require("./planning-scope.cjs");
|
|
30
|
+
const { SCOPE } = planningScopeMod;
|
|
26
31
|
function toFiniteNumber(value) {
|
|
27
32
|
const number = Number(value);
|
|
28
33
|
return Number.isFinite(number) ? number : null;
|
|
@@ -204,7 +209,7 @@ function locateFieldRow(content, fieldName) {
|
|
|
204
209
|
return null;
|
|
205
210
|
}
|
|
206
211
|
function stateExtractField(content, fieldName) {
|
|
207
|
-
const escaped = escapeRegex(fieldName);
|
|
212
|
+
const escaped = (0, pattern_cjs_1.escapeRegex)(fieldName);
|
|
208
213
|
// Bold inline format: **FieldName:** value
|
|
209
214
|
const boldPattern = new RegExp(`\\*\\*${escaped}:\\*\\*[ \\t]*(.+)`, 'i');
|
|
210
215
|
const boldMatch = content.match(boldPattern);
|
|
@@ -222,8 +227,107 @@ function stateExtractField(content, fieldName) {
|
|
|
222
227
|
return hit.rawValue.trim();
|
|
223
228
|
return null;
|
|
224
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Single owner of the #1760 STATE.md field-extraction fallback chain: "read
|
|
232
|
+
* field F, preferring the YAML frontmatter scalar, falling back to the body
|
|
233
|
+
* field." Added for #3187 (epic #3180, ADR-3180 §7.7) to collapse three
|
|
234
|
+
* independent re-derivations of this chain — `src/smart-entry.cts`'s
|
|
235
|
+
* `fmScalar` closure, and `src/state.cts`'s `cmdStateSnapshot` and
|
|
236
|
+
* `cmdStatePrune` — onto one function, per ADR-3180 Decision 1 ("keep N
|
|
237
|
+
* copies with a parity test" is rejected: a parity test proves today's
|
|
238
|
+
* agreement, not that copy N+1 won't happen).
|
|
239
|
+
*
|
|
240
|
+
* Takes ALREADY-PARSED `fm` and `body` rather than raw STATE.md content: the
|
|
241
|
+
* heaviest caller, `cmdStateSnapshot`, reads roughly ten fields off one parse
|
|
242
|
+
* and must not re-parse frontmatter per field.
|
|
243
|
+
*
|
|
244
|
+
* `stateExtractField` (above) is deliberately left untouched — it has 20
|
|
245
|
+
* direct callers and a CRITICAL blast radius (ADR-3180 §7.7's Rejected #1) —
|
|
246
|
+
* so this function is additive: it calls `stateExtractField` rather than
|
|
247
|
+
* replacing it or changing its signature.
|
|
248
|
+
*
|
|
249
|
+
* Fallback ladder (unchanged from every prior copy this replaces):
|
|
250
|
+
* 1. `fm[fmKey]` is a non-empty (post-`.trim()`) string → that trimmed
|
|
251
|
+
* string.
|
|
252
|
+
* 2. `fm[fmKey]` is a `number` or `boolean` → `String(fm[fmKey])`, so `0`
|
|
253
|
+
* and `false` are VALUES, not absence.
|
|
254
|
+
* 3. Anything else (`null`, `undefined`, an object, an array, or an
|
|
255
|
+
* empty/whitespace-only string) → fall through to
|
|
256
|
+
* `stateExtractField(body, bodyField)`.
|
|
257
|
+
*
|
|
258
|
+
* `fmKey === null` skips steps 1–2 outright: for a caller whose chain has no
|
|
259
|
+
* frontmatter side for this particular field (e.g. `state.cts`'s body-only
|
|
260
|
+
* `Last Activity` / `Last activity` case-variant pair, which sits inside a
|
|
261
|
+
* function that DOES own a ladder for its other fields, so per this phase's
|
|
262
|
+
* function-scoped guard it must still route through this owner).
|
|
263
|
+
* `bodyField === null` skips step 3: for a caller whose "no frontmatter
|
|
264
|
+
* value" case falls through to an already-computed value instead of a fresh
|
|
265
|
+
* extractor call (e.g. `cmdStateSnapshot`'s `last_activity`, which falls to
|
|
266
|
+
* its already-parsed prose date rather than re-extracting the body).
|
|
267
|
+
*
|
|
268
|
+
* `scope` reports whether the chain ran over inputs it could actually
|
|
269
|
+
* consult (ADR-3180 Decision 2/§7.7 — mirrors `scanPhasePlans`'s
|
|
270
|
+
* scope-carrying result in `plan-scan.cts`; see `planning-scope.cjs`). This
|
|
271
|
+
* function's own ladder always runs to completion on whatever `fm`/`body` it
|
|
272
|
+
* is given, INCLUDING when the answer is `null` — a genuinely absent field is
|
|
273
|
+
* a real answer, not a failure to look (§7.7 behavior table row 4). So
|
|
274
|
+
* `scope` defaults to `SCOPE.COMPLETE` and is only ever something else when
|
|
275
|
+
* the CALLER passes `opts.scope`, because only the caller knows whether an
|
|
276
|
+
* input it handed in was itself degraded — e.g. `fm` came back `{}` from an
|
|
277
|
+
* unterminated frontmatter fence (`extractFrontmatter` swallows that parse
|
|
278
|
+
* failure), or `body` is an unscoped whole-document fallback because a
|
|
279
|
+
* required `## Current Position` section was not found (#2956). This
|
|
280
|
+
* function never invents a new `SCOPE` member — the enum is frozen at
|
|
281
|
+
* COMPLETE/TRUNCATED/UNSCOPED/UNREADABLE (`planning-scope.cjs`).
|
|
282
|
+
*
|
|
283
|
+
* #1760 is the fallback chain's origin.
|
|
284
|
+
*/
|
|
285
|
+
function stateFieldValue(fm, body, fmKey, bodyField, opts) {
|
|
286
|
+
const v = fmKey === null ? undefined : fm[fmKey];
|
|
287
|
+
let value;
|
|
288
|
+
if (typeof v === 'string' && v.trim()) {
|
|
289
|
+
value = v.trim();
|
|
290
|
+
}
|
|
291
|
+
else if (typeof v === 'number' || typeof v === 'boolean') {
|
|
292
|
+
value = String(v);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
value = bodyField === null ? null : stateExtractField(body, bodyField);
|
|
296
|
+
}
|
|
297
|
+
return { value, scope: opts?.scope ?? SCOPE.COMPLETE };
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Match the "Current Position" section body from a STATE.md body. #2956: this
|
|
301
|
+
* is the Phase analogue of state.cts's matchSessionSection. `Phase` canonically
|
|
302
|
+
* lives under `## Current Position` (gsd-core/templates/state.md), so — like
|
|
303
|
+
* Stopped At / Paused At under `## Session` — it must be extracted from THAT
|
|
304
|
+
* section, not from the first `Phase:` / `**Phase:**` line anywhere in the
|
|
305
|
+
* body. Without the scope, a historical `Phase:` line in an archive section
|
|
306
|
+
* silently shadows the real one on every read/write, and because callers use
|
|
307
|
+
* this for routing (state.cts's current_phase) and for drift detection
|
|
308
|
+
* (gsd-tools.cjs's `drift-guard phase-status` CLI seam), a stale match either
|
|
309
|
+
* routes work to the wrong phase or fabricates a drift finding.
|
|
310
|
+
*
|
|
311
|
+
* Level-flexible: the canonical template uses an h2 `## Current Position`, the
|
|
312
|
+
* bootstrap template an h3 `### Current Position` (templates/state.md). Both
|
|
313
|
+
* must match — mirroring how matchSessionSection recognises `## Session` and
|
|
314
|
+
* `## Session Continuity`. Exact 'current position' text match (case-
|
|
315
|
+
* insensitive) excludes unrelated headings. Built on the `collectSection`
|
|
316
|
+
* seam, so it inherits that seam's CRLF tolerance (#2444 fix).
|
|
317
|
+
*
|
|
318
|
+
* This is the single owner of the scope — state.cts's private
|
|
319
|
+
* `matchCurrentPositionSection` delegates here rather than duplicating the
|
|
320
|
+
* logic, so the two consumers cannot drift apart.
|
|
321
|
+
*
|
|
322
|
+
* Returns the section body, or null (caller falls back to full-body search).
|
|
323
|
+
*/
|
|
324
|
+
function stateCurrentPositionSlice(body) {
|
|
325
|
+
const isCurrentPosition = (h) => (h.level === 2 || h.level === 3) && h.text.trim().toLowerCase() === 'current position';
|
|
326
|
+
const section = (0, markdown_sectionizer_cjs_1.collectSection)(body, isCurrentPosition, { levelBounded: true });
|
|
327
|
+
return section ? section.body : null;
|
|
328
|
+
}
|
|
225
329
|
function stateReplaceField(content, fieldName, newValue) {
|
|
226
|
-
const escaped = escapeRegex(fieldName);
|
|
330
|
+
const escaped = (0, pattern_cjs_1.escapeRegex)(fieldName);
|
|
227
331
|
// Bold inline format: **FieldName:** value
|
|
228
332
|
const boldPattern = new RegExp(`(\\*\\*${escaped}:\\*\\*\\s*)(.*)`, 'i');
|
|
229
333
|
if (boldPattern.test(content)) {
|
|
@@ -253,6 +357,33 @@ function stateReplaceFieldWithFallback(content, primary, fallback, value) {
|
|
|
253
357
|
}
|
|
254
358
|
return content;
|
|
255
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* #3374: session-scoped variant of stateReplaceFieldWithFallback for the
|
|
362
|
+
* `## Session` continuity fields. The post-sync harvest (state.cts's
|
|
363
|
+
* matchSessionSection → buildStateFrontmatter) reads these fields ONLY from
|
|
364
|
+
* the session section, so a writer that refreshes one must target the same
|
|
365
|
+
* scope — a whole-body replace lets a decoy `**Stopped at:**` line in an
|
|
366
|
+
* unrelated (e.g. archive) section absorb the refresh while the harvested
|
|
367
|
+
* session value stays stale.
|
|
368
|
+
*
|
|
369
|
+
* Section preference mirrors the reader exactly: the normalized `## Session`
|
|
370
|
+
* block wins over the bootstrap `## Session Continuity` heading when both
|
|
371
|
+
* exist (legacy duplicate files); the continuity heading is only consulted
|
|
372
|
+
* when no canonical `## Session` section exists. `levelBounded` heading
|
|
373
|
+
* matching also excludes `## Session Continuity Archive` (the #2444 scoping).
|
|
374
|
+
*
|
|
375
|
+
* Replace-only (no insertion): returns `content` unchanged when no session
|
|
376
|
+
* section exists or the field is absent from it, so a STATE.md layout without
|
|
377
|
+
* the line keeps its shape and the post-sync preservation pass decides the
|
|
378
|
+
* frontmatter value (see #3374).
|
|
379
|
+
*/
|
|
380
|
+
function stateReplaceFieldInSession(content, primary, fallback, value) {
|
|
381
|
+
const isSession = (h) => h.level === 2 && h.text.trim().toLowerCase() === 'session';
|
|
382
|
+
const isSessionContinuity = (h) => h.level === 2 && h.text.trim().toLowerCase() === 'session continuity';
|
|
383
|
+
const hasCanonicalSession = (0, markdown_sectionizer_cjs_1.collectSection)(content, isSession, { levelBounded: true }) !== null;
|
|
384
|
+
const target = hasCanonicalSession ? isSession : isSessionContinuity;
|
|
385
|
+
return (0, markdown_sectionizer_cjs_1.withSection)(content, target, (sectionBody) => stateReplaceFieldWithFallback(sectionBody, primary, fallback, value));
|
|
386
|
+
}
|
|
256
387
|
function normalizeStateStatus(status, pausedAt) {
|
|
257
388
|
let normalizedStatus = status || 'unknown';
|
|
258
389
|
const statusLower = (status || '').toLowerCase();
|
|
@@ -279,7 +410,20 @@ function normalizeStateStatus(status, pausedAt) {
|
|
|
279
410
|
}
|
|
280
411
|
return normalizedStatus;
|
|
281
412
|
}
|
|
282
|
-
|
|
413
|
+
/**
|
|
414
|
+
* ADR-3180 §7.6 rule 4 (#3217): `scope` is the `listMilestonePhaseDirs`-owner
|
|
415
|
+
* discriminator for the phase/plan set these four counts were derived from.
|
|
416
|
+
* A caller that cannot vouch for `scope === SCOPE.COMPLETE` must pass the
|
|
417
|
+
* scope it actually has — this function refuses to compose a percentage
|
|
418
|
+
* from counts whose scope says they are not a trustworthy answer, returning
|
|
419
|
+
* `null` (never `0`; see the module's already-existing "no data" `null`
|
|
420
|
+
* below, which this generalizes) exactly like its pre-existing "no data"
|
|
421
|
+
* case. `scope` is REQUIRED (no default) so a caller cannot silently opt out
|
|
422
|
+
* of rule 4 by omission.
|
|
423
|
+
*/
|
|
424
|
+
function computeProgressPercent(completedPlans, totalPlans, completedPhases, totalPhases, scope) {
|
|
425
|
+
if (scope !== SCOPE.COMPLETE)
|
|
426
|
+
return null;
|
|
283
427
|
const hasPlanData = totalPlans !== null && totalPlans > 0 && completedPlans !== null;
|
|
284
428
|
const hasPhaseData = totalPhases !== null && totalPhases > 0 && completedPhases !== null;
|
|
285
429
|
if (!hasPlanData && !hasPhaseData)
|
|
@@ -288,7 +432,7 @@ function computeProgressPercent(completedPlans, totalPlans, completedPhases, tot
|
|
|
288
432
|
// cannot track through intermediate boolean variables).
|
|
289
433
|
const planFraction = hasPlanData ? (completedPlans ?? 0) / (totalPlans ?? 1) : 1;
|
|
290
434
|
const phaseFraction = hasPhaseData ? (completedPhases ?? 0) / (totalPhases ?? 1) : 1;
|
|
291
|
-
return
|
|
435
|
+
return (0, phase_lifecycle_cjs_1.clampPercentFromFraction)(Math.min(planFraction, phaseFraction));
|
|
292
436
|
}
|
|
293
437
|
function shouldPreserveExistingProgress(existingProgress, derivedProgress) {
|
|
294
438
|
if (!existingProgress || typeof existingProgress !== 'object')
|