@opengsd/gsd-core 1.14.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +85 -5
- package/README.ja-JP.md +3 -3
- package/README.ko-KR.md +3 -3
- package/README.pt-BR.md +3 -3
- package/README.zh-CN.md +3 -3
- package/agents/gsd-code-fixer.compact.md +7 -6
- package/agents/gsd-code-fixer.md +9 -8
- package/agents/gsd-code-reviewer.compact.md +5 -3
- package/agents/gsd-code-reviewer.md +8 -6
- package/agents/gsd-debug-session-manager.compact.md +17 -2
- package/agents/gsd-debug-session-manager.md +17 -2
- package/agents/gsd-debugger.md +3 -3
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +17 -12
- package/agents/gsd-intel-updater.compact.md +1 -1
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +2 -2
- package/agents/gsd-phase-researcher.md +19 -11
- package/agents/gsd-plan-checker.md +15 -9
- package/agents/gsd-planner.md +15 -11
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +1 -1
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-ui-auditor.compact.md +21 -30
- package/agents/gsd-ui-auditor.md +166 -37
- package/agents/gsd-ui-researcher.compact.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +37 -14
- package/bin/install.js +764 -287
- package/commands/gsd/add-tests.md +6 -1
- package/commands/gsd/ai-integration-phase.md +6 -1
- package/commands/gsd/audit-fix.md +5 -0
- package/commands/gsd/audit-milestone.md +6 -1
- package/commands/gsd/autonomous.md +7 -2
- package/commands/gsd/capture.md +9 -5
- package/commands/gsd/code-review.md +7 -2
- package/commands/gsd/complete-milestone.md +4 -0
- package/commands/gsd/config.md +7 -3
- package/commands/gsd/debug.md +11 -7
- package/commands/gsd/discuss-phase.md +7 -3
- package/commands/gsd/docs-update.md +12 -7
- package/commands/gsd/eval-review.md +6 -1
- package/commands/gsd/execute-phase.md +12 -7
- package/commands/gsd/extract-learnings.md +5 -0
- package/commands/gsd/fast.md +4 -0
- package/commands/gsd/forensics.md +5 -1
- package/commands/gsd/graphify.md +10 -6
- package/commands/gsd/health.md +5 -0
- package/commands/gsd/help.md +7 -2
- package/commands/gsd/import.md +7 -3
- package/commands/gsd/inbox.md +5 -0
- package/commands/gsd/ingest-docs.md +5 -1
- package/commands/gsd/manager.md +6 -1
- package/commands/gsd/map-codebase.md +7 -3
- package/commands/gsd/mempalace-capture.md +12 -4
- package/commands/gsd/mempalace-recall.md +5 -1
- package/commands/gsd/milestone-summary.md +5 -1
- package/commands/gsd/mvp-phase.md +8 -3
- package/commands/gsd/new-milestone.md +6 -1
- package/commands/gsd/new-project.md +5 -0
- package/commands/gsd/next.md +6 -1
- package/commands/gsd/ns-context.md +4 -0
- package/commands/gsd/ns-ideate.md +4 -0
- package/commands/gsd/ns-manage.md +4 -0
- package/commands/gsd/ns-project.md +4 -0
- package/commands/gsd/ns-review.md +4 -0
- package/commands/gsd/ns-workflow.md +4 -0
- package/commands/gsd/onboard.md +6 -1
- package/commands/gsd/pause-work.md +5 -1
- package/commands/gsd/phase.md +8 -4
- package/commands/gsd/plan-phase.md +6 -1
- package/commands/gsd/plan-review-convergence.md +11 -7
- package/commands/gsd/pr-branch.md +4 -0
- package/commands/gsd/profile-user.md +5 -1
- package/commands/gsd/progress.md +7 -2
- package/commands/gsd/quick-batch.md +21 -9
- package/commands/gsd/quick.md +12 -7
- package/commands/gsd/review.md +7 -4
- package/commands/gsd/secure-phase.md +6 -1
- package/commands/gsd/ship.md +5 -0
- package/commands/gsd/sketch.md +7 -2
- package/commands/gsd/spec-phase.md +5 -1
- package/commands/gsd/spike.md +8 -3
- package/commands/gsd/surface.md +5 -1
- package/commands/gsd/thread.md +4 -0
- package/commands/gsd/ui-phase.md +6 -1
- package/commands/gsd/ui-review.md +6 -1
- package/commands/gsd/ultraplan-phase.md +5 -1
- package/commands/gsd/undo.md +5 -1
- package/commands/gsd/update.md +6 -2
- package/commands/gsd/validate-phase.md +6 -1
- package/commands/gsd/verify-work.md +6 -1
- package/commands/gsd/workspace.md +7 -3
- package/gsd-core/bin/gsd-tools.cjs +477 -78
- package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
- package/gsd-core/bin/lib/adr-parser.cjs +3 -1
- package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
- package/gsd-core/bin/lib/audit.cjs +144 -42
- package/gsd-core/bin/lib/broken-windows.cjs +13 -13
- package/gsd-core/bin/lib/capability-activation.cjs +9 -4
- package/gsd-core/bin/lib/capability-registry.cjs +197 -222
- package/gsd-core/bin/lib/capability-validator.cjs +16 -1
- package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
- package/gsd-core/bin/lib/check-command-router.cjs +164 -1625
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
- package/gsd-core/bin/lib/cli-exit.cjs +12 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +32 -33
- package/gsd-core/bin/lib/command-aliases.cjs +7 -0
- package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
- package/gsd-core/bin/lib/commands.cjs +343 -207
- package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
- package/gsd-core/bin/lib/config-loader.cjs +65 -4
- package/gsd-core/bin/lib/config.cjs +76 -19
- package/gsd-core/bin/lib/core-utils.cjs +6 -1
- package/gsd-core/bin/lib/coverage.cjs +4 -8
- package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
- package/gsd-core/bin/lib/decisions.cjs +30 -14
- package/gsd-core/bin/lib/drift.cjs +177 -42
- package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
- package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
- package/gsd-core/bin/lib/frontmatter.cjs +426 -234
- package/gsd-core/bin/lib/gap-checker.cjs +72 -29
- package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
- package/gsd-core/bin/lib/gate-args.cjs +53 -0
- package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
- package/gsd-core/bin/lib/gate-config.cjs +46 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +141 -0
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
- package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
- package/gsd-core/bin/lib/gate-exit.cjs +27 -0
- package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
- package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
- package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
- package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
- package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
- package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
- package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
- package/gsd-core/bin/lib/gate-ui-safety.cjs +80 -0
- package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
- package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
- package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
- package/gsd-core/bin/lib/graphify.cjs +10 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
- package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
- package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
- package/gsd-core/bin/lib/init.cjs +364 -132
- package/gsd-core/bin/lib/install-engine.cjs +30 -33
- package/gsd-core/bin/lib/install-profiles.cjs +7 -4
- package/gsd-core/bin/lib/installer-migrations.cjs +8 -1
- package/gsd-core/bin/lib/io.cjs +122 -3
- package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
- package/gsd-core/bin/lib/milestone.cjs +37 -6
- package/gsd-core/bin/lib/model-resolver.cjs +171 -62
- package/gsd-core/bin/lib/observability/event.cjs +1 -1
- package/gsd-core/bin/lib/observability/logger.cjs +46 -1
- package/gsd-core/bin/lib/pattern.cjs +10 -0
- package/gsd-core/bin/lib/phase-command-router.cjs +20 -5
- package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
- package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
- package/gsd-core/bin/lib/phase-id.cjs +110 -8
- package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
- package/gsd-core/bin/lib/phase-locator.cjs +29 -10
- package/gsd-core/bin/lib/phase-status.cjs +360 -0
- package/gsd-core/bin/lib/phase.cjs +489 -88
- package/gsd-core/bin/lib/plan-document.cjs +142 -20
- package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
- package/gsd-core/bin/lib/planning-document.cjs +692 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +60 -9
- package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +83 -55
- package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
- package/gsd-core/bin/lib/probe-core.cjs +7 -1
- package/gsd-core/bin/lib/profile-output.cjs +6 -3
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
- package/gsd-core/bin/lib/project-root.cjs +41 -2
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +11 -8
- package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
- package/gsd-core/bin/lib/report-parser.cjs +269 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
- package/gsd-core/bin/lib/roadmap-command-router.cjs +25 -19
- package/gsd-core/bin/lib/roadmap-parser.cjs +242 -15
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +1653 -65
- package/gsd-core/bin/lib/roadmap.cjs +405 -88
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +373 -187
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +5 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +14 -7
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +522 -624
- package/gsd-core/bin/lib/runtime-name-policy.cjs +246 -21
- package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +47 -8
- package/gsd-core/bin/lib/smart-entry.cjs +19 -3
- package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
- package/gsd-core/bin/lib/state-contract.cjs +15 -18
- package/gsd-core/bin/lib/state-document.cjs +100 -22
- package/gsd-core/bin/lib/state-transition.cjs +39 -2
- package/gsd-core/bin/lib/state.cjs +256 -105
- package/gsd-core/bin/lib/surface.cjs +19 -2
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -79
- package/gsd-core/bin/lib/uat-predicate.cjs +359 -38
- package/gsd-core/bin/lib/uat.cjs +432 -7
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +167 -40
- package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
- package/gsd-core/bin/lib/vendor/README.md +31 -9
- package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
- package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
- package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
- package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +1378 -274
- package/gsd-core/bin/lib/verify-command-grounding.cjs +46 -2
- package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
- package/gsd-core/bin/lib/verify.cjs +357 -531
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
- package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
- package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
- package/gsd-core/bin/lib/workstream.cjs +11 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
- package/gsd-core/bin/lib/worktree-safety.cjs +784 -51
- package/gsd-core/bin/shared/config-defaults.manifest.json +8 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
- package/gsd-core/references/autonomous-smart-discuss.md +2 -1
- package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
- package/gsd-core/references/checkpoints.md +5 -3
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe.md +195 -21
- package/gsd-core/references/execute-mvp-tdd.md +5 -10
- package/gsd-core/references/execute-phase-between-wave-reset.md +10 -6
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +22 -11
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +7 -1
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/offer-next.md +1 -1
- package/gsd-core/references/phase-argument-parsing.md +9 -7
- package/gsd-core/references/phase-id-convention.md +28 -0
- package/gsd-core/references/planner-gap-closure.md +2 -0
- package/gsd-core/references/planner-load-graph-context.md +24 -13
- package/gsd-core/references/planner-verify-command-grounding.md +14 -0
- package/gsd-core/references/planning-config.md +12 -3
- package/gsd-core/references/spidr-splitting.md +1 -1
- package/gsd-core/references/tdd.md +37 -8
- package/gsd-core/references/ui-consideration-probe.md +10 -5
- package/gsd-core/references/verifier-phase-gates.md +5 -2
- package/gsd-core/references/verify-command-path-resolvability.md +10 -2
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -3
- package/gsd-core/references/worktree-path-safety.md +321 -0
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/UAT.md +17 -1
- package/gsd-core/templates/config.json +2 -11
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +8 -7
- package/gsd-core/workflows/add-tests.md +4 -3
- package/gsd-core/workflows/add-todo.md +6 -5
- package/gsd-core/workflows/ai-integration-phase.md +13 -4
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +4 -3
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
- package/gsd-core/workflows/autonomous.md +43 -23
- package/gsd-core/workflows/check-todos.md +7 -6
- package/gsd-core/workflows/cleanup.md +2 -2
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
- package/gsd-core/workflows/code-review-fix.md +112 -25
- package/gsd-core/workflows/code-review.md +146 -135
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
- package/gsd-core/workflows/complete-milestone.md +13 -8
- package/gsd-core/workflows/debug.md +32 -7
- package/gsd-core/workflows/diagnose-issues.md +3 -2
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
- package/gsd-core/workflows/discuss-phase.md +4 -3
- package/gsd-core/workflows/do.md +2 -2
- package/gsd-core/workflows/docs-update.md +6 -5
- package/gsd-core/workflows/edit-phase.md +4 -3
- package/gsd-core/workflows/eval-review.md +14 -5
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1019 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +37 -3
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
- package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
- package/gsd-core/workflows/execute-phase.md +96 -178
- package/gsd-core/workflows/execute-plan.md +18 -23
- package/gsd-core/workflows/explore.md +4 -4
- package/gsd-core/workflows/extract-learnings.md +4 -2
- package/gsd-core/workflows/fast.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +3 -2
- package/gsd-core/workflows/help/modes/full.compact.md +3 -3
- package/gsd-core/workflows/help/modes/full.md +5 -5
- package/gsd-core/workflows/help/modes/topic.md +15 -5
- package/gsd-core/workflows/import.md +4 -3
- package/gsd-core/workflows/inbox.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -3
- package/gsd-core/workflows/insert-phase.md +4 -3
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +6 -4
- package/gsd-core/workflows/map-codebase.md +5 -4
- package/gsd-core/workflows/milestone-summary.md +3 -2
- package/gsd-core/workflows/mvp-phase.md +14 -14
- package/gsd-core/workflows/new-milestone.md +11 -11
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
- package/gsd-core/workflows/new-project.md +7 -7
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/note.md +1 -1
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +2 -2
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
- package/gsd-core/workflows/plan-phase.md +50 -24
- package/gsd-core/workflows/plan-review-convergence.md +21 -5
- package/gsd-core/workflows/plant-seed.md +62 -20
- package/gsd-core/workflows/pr-branch.md +113 -13
- package/gsd-core/workflows/profile-user.md +2 -2
- package/gsd-core/workflows/progress.md +19 -49
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +27 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +15 -10
- package/gsd-core/workflows/quick.md +62 -39
- package/gsd-core/workflows/reapply-patches.md +9 -3
- package/gsd-core/workflows/remove-phase.md +3 -2
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +3 -2
- package/gsd-core/workflows/review.md +33 -17
- package/gsd-core/workflows/scan.md +3 -2
- package/gsd-core/workflows/secure-phase.md +13 -13
- package/gsd-core/workflows/settings-advanced.md +30 -10
- package/gsd-core/workflows/settings-integrations.md +2 -3
- package/gsd-core/workflows/settings.md +4 -4
- package/gsd-core/workflows/ship.md +14 -13
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +2 -2
- package/gsd-core/workflows/spec-phase.md +15 -5
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +5 -5
- package/gsd-core/workflows/thread.md +2 -2
- package/gsd-core/workflows/transition.md +13 -23
- package/gsd-core/workflows/ui-phase.md +48 -11
- package/gsd-core/workflows/ui-review.md +21 -6
- package/gsd-core/workflows/ultraplan-phase.md +3 -2
- package/gsd-core/workflows/undo.md +339 -20
- package/gsd-core/workflows/update.md +7 -7
- package/gsd-core/workflows/validate-phase.md +12 -13
- package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
- package/gsd-core/workflows/verify-work.md +129 -55
- package/hooks/dist/gsd-agent-isolation-guard.js +32 -0
- package/hooks/dist/gsd-check-update-worker.js +8 -0
- package/hooks/dist/gsd-check-update.js +8 -0
- package/hooks/dist/gsd-context-monitor.js +31 -8
- package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
- package/hooks/dist/gsd-secret-read-guard.js +161 -4
- package/hooks/dist/gsd-statusline.js +85 -20
- package/hooks/dist/gsd-update-banner.js +8 -0
- package/hooks/dist/gsd-validate-commit.sh +63 -4
- package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
- package/hooks/dist/gsd-workflow-guard.js +5 -4
- package/hooks/dist/gsd-worktree-path-guard.js +6 -2
- package/hooks/dist/lib/cli-exit.js +12 -0
- package/hooks/dist/lib/git-probe.js +17 -1
- package/hooks/dist/lib/isolation-sentinel.js +2 -2
- package/hooks/gsd-agent-isolation-guard.js +32 -0
- package/hooks/gsd-check-update-worker.js +8 -0
- package/hooks/gsd-check-update.js +8 -0
- package/hooks/gsd-context-monitor.js +31 -8
- package/hooks/gsd-cursor-subagent-start.js +8 -0
- package/hooks/gsd-secret-read-guard.js +161 -4
- package/hooks/gsd-statusline.js +85 -20
- package/hooks/gsd-update-banner.js +8 -0
- package/hooks/gsd-validate-commit.sh +63 -4
- package/hooks/gsd-windsurf-pre-write.js +11 -2
- package/hooks/gsd-workflow-guard.js +5 -4
- package/hooks/gsd-worktree-path-guard.js +6 -2
- package/hooks/hooks.json +5 -5
- package/hooks/lib/cli-exit.js +12 -0
- package/hooks/lib/git-probe.js +17 -1
- package/hooks/lib/isolation-sentinel.js +2 -2
- package/package.json +22 -4
- package/scripts/build-hooks.js +15 -6
- package/scripts/changeset/parse.cjs +52 -4
- package/scripts/check-contract-drift.cjs +127 -11
- package/scripts/ci-timeout-report.cjs +770 -4
- package/scripts/command-contract-helpers.cjs +15 -8
- package/scripts/docs-guard-registry.cjs +34 -0
- package/scripts/gen-features.cjs +13 -8
- package/scripts/gen-hooks-cli-exit.cjs +12 -28
- package/scripts/gen-loop-host-contract.cjs +79 -1
- package/scripts/gen-platform-conformance-tier.cjs +187 -1
- package/scripts/gen-plugin-skills.cjs +87 -1
- package/scripts/gen-research-agents.cjs +24 -31
- package/scripts/gen-scripts-cli-exit.cjs +30 -3
- package/scripts/gen-test-timings.cjs +32 -7
- package/scripts/lib/cli-exit.cjs +12 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +34 -2
- package/scripts/lib/ndjson-reporter.cjs +31 -5
- package/scripts/lib/platform-conformance-tier.generated.cjs +45 -5
- package/scripts/lib/registration-ledger-preload.cjs +155 -0
- package/scripts/lib/vendor-bundle.cjs +59 -0
- package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-completion-predicate-drift.cjs +18 -19
- package/scripts/lint-descriptions.cjs +7 -3
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +38 -1
- package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
- package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
- package/scripts/lint-phase-arg-assignment.cjs +257 -0
- package/scripts/lint-phase-enumeration-drift.cjs +12 -8
- package/scripts/lint-phase-id-drift.cjs +319 -5
- package/scripts/lint-planning-document-positive-control.cjs +329 -0
- package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
- package/scripts/lint-response-language-coverage.cjs +3 -0
- package/scripts/lint-retired-runtime-name.cjs +619 -0
- package/scripts/lint-skill-deps.cjs +7 -3
- package/scripts/lint-state-write-path-drift.cjs +93 -0
- package/scripts/lint-test-file-count.allowlist.json +38 -9
- package/scripts/lint-test-file-count.cjs +34 -1
- package/scripts/lint-vendored-deps.cjs +41 -5
- package/scripts/lint-workflow-shellcheck-baseline.json +25 -10
- package/scripts/mutation-matrix.cjs +50 -5
- package/scripts/prompt-injection-scan.sh +4 -0
- package/scripts/release-tarball-smoke.cjs +194 -1
- package/scripts/require-issue-link-policy.cjs +6 -2
- package/scripts/sync-runtime-launcher.cjs +184 -2
- package/scripts/verify-npm-publish.cjs +76 -20
- package/skills/gsd-add-tests/SKILL.md +6 -1
- package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
- package/skills/gsd-audit-fix/SKILL.md +5 -0
- package/skills/gsd-audit-milestone/SKILL.md +6 -1
- package/skills/gsd-autonomous/SKILL.md +7 -2
- package/skills/gsd-capture/SKILL.md +9 -5
- package/skills/gsd-code-review/SKILL.md +7 -2
- package/skills/gsd-complete-milestone/SKILL.md +4 -0
- package/skills/gsd-config/SKILL.md +8 -4
- package/skills/gsd-debug/SKILL.md +11 -7
- package/skills/gsd-discuss-phase/SKILL.md +7 -3
- package/skills/gsd-docs-update/SKILL.md +12 -7
- package/skills/gsd-eval-review/SKILL.md +6 -1
- package/skills/gsd-execute-phase/SKILL.md +12 -7
- package/skills/gsd-extract-learnings/SKILL.md +5 -0
- package/skills/gsd-fast/SKILL.md +4 -0
- package/skills/gsd-forensics/SKILL.md +5 -1
- package/skills/gsd-graphify/SKILL.md +10 -6
- package/skills/gsd-health/SKILL.md +5 -0
- package/skills/gsd-help/SKILL.md +7 -2
- package/skills/gsd-import/SKILL.md +7 -3
- package/skills/gsd-inbox/SKILL.md +5 -0
- package/skills/gsd-ingest-docs/SKILL.md +5 -1
- package/skills/gsd-manager/SKILL.md +6 -1
- package/skills/gsd-map-codebase/SKILL.md +7 -3
- package/skills/gsd-mempalace-capture/SKILL.md +12 -4
- package/skills/gsd-mempalace-recall/SKILL.md +5 -1
- package/skills/gsd-milestone-summary/SKILL.md +5 -1
- package/skills/gsd-mvp-phase/SKILL.md +8 -3
- package/skills/gsd-new-milestone/SKILL.md +6 -1
- package/skills/gsd-new-project/SKILL.md +5 -0
- package/skills/gsd-next/SKILL.md +6 -1
- package/skills/gsd-ns-context/SKILL.md +4 -0
- package/skills/gsd-ns-ideate/SKILL.md +4 -0
- package/skills/gsd-ns-manage/SKILL.md +4 -0
- package/skills/gsd-ns-project/SKILL.md +4 -0
- package/skills/gsd-ns-review/SKILL.md +4 -0
- package/skills/gsd-ns-workflow/SKILL.md +4 -0
- package/skills/gsd-onboard/SKILL.md +6 -1
- package/skills/gsd-pause-work/SKILL.md +5 -1
- package/skills/gsd-phase/SKILL.md +8 -4
- package/skills/gsd-plan-phase/SKILL.md +6 -1
- package/skills/gsd-plan-review-convergence/SKILL.md +10 -6
- package/skills/gsd-pr-branch/SKILL.md +4 -0
- package/skills/gsd-profile-user/SKILL.md +5 -1
- package/skills/gsd-progress/SKILL.md +7 -2
- package/skills/gsd-quick/SKILL.md +16 -10
- package/skills/gsd-quick-batch/SKILL.md +21 -9
- package/skills/gsd-review/SKILL.md +7 -4
- package/skills/gsd-review-backlog/SKILL.md +3 -2
- package/skills/gsd-secure-phase/SKILL.md +6 -1
- package/skills/gsd-ship/SKILL.md +5 -0
- package/skills/gsd-sketch/SKILL.md +7 -2
- package/skills/gsd-spec-phase/SKILL.md +5 -1
- package/skills/gsd-spike/SKILL.md +8 -3
- package/skills/gsd-surface/SKILL.md +5 -1
- package/skills/gsd-thread/SKILL.md +4 -0
- package/skills/gsd-ui-phase/SKILL.md +6 -1
- package/skills/gsd-ui-review/SKILL.md +6 -1
- package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
- package/skills/gsd-undo/SKILL.md +5 -1
- package/skills/gsd-update/SKILL.md +6 -2
- package/skills/gsd-validate-phase/SKILL.md +6 -1
- package/skills/gsd-verify-work/SKILL.md +6 -1
- package/skills/gsd-workspace/SKILL.md +7 -3
- package/skills/gsd-workstreams/SKILL.md +6 -6
- package/vscode/package.json +1 -1
|
@@ -32,7 +32,7 @@ bracketMilestoneIntroSrcFor, } = phaseIdMod;
|
|
|
32
32
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
33
33
|
const roadmapParserMod = require("./roadmap-parser.cjs");
|
|
34
34
|
// #3642: hasMilestoneSectioning no longer consumed here — its >=2 semantics answered sibling conflation, but this branch asks asserted-vs-section (>=1). It stays exported from roadmap-parser.cjs for its unit pins.
|
|
35
|
-
const { getMilestoneInfo, extractCurrentMilestone, isMilestoneBoundedInRoadmap, hasAnyMilestoneSection } = roadmapParserMod;
|
|
35
|
+
const { getMilestoneInfo, extractCurrentMilestone, isMilestoneBoundedInRoadmap, hasAnyMilestoneSection, classifyMilestoneScalar } = roadmapParserMod;
|
|
36
36
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
37
37
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
38
38
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
@@ -60,7 +60,7 @@ const coreUtilsMod = require("./core-utils.cjs");
|
|
|
60
60
|
const planDependencyGraphMod = require("./plan-dependency-graph.cjs");
|
|
61
61
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
62
62
|
const verificationMod = require("./verification.cjs");
|
|
63
|
-
const { isPhaseComplete } = verificationMod;
|
|
63
|
+
const { isPhaseComplete, VerificationStatusError } = verificationMod;
|
|
64
64
|
// #4129: the single owner of "count the ROADMAP's milestone Complete rows"
|
|
65
65
|
// (phase-lifecycle.cts) — reused for the completed-phases numerator floor so
|
|
66
66
|
// this scan cannot grow a second ROADMAP parser. Pure computation module (no
|
|
@@ -1080,7 +1080,7 @@ function computeUpdateProgressPreview(statePath, cwd) {
|
|
|
1080
1080
|
const existingFm = extractFrontmatter(preContent, statePath);
|
|
1081
1081
|
const preBody = stripFrontmatter(preContent);
|
|
1082
1082
|
const storedMilestone = typeof existingFm['milestone'] === 'string' ? existingFm['milestone'] : null;
|
|
1083
|
-
const builtFm = buildStateFrontmatter(preBody, cwd, storedMilestone, readStoredTotalPhases(existingFm), readStoredCompletedPhases(existingFm), readStoredTotalPlans(existingFm), readStoredCompletedPlans(existingFm));
|
|
1083
|
+
const builtFm = buildStateFrontmatter(preBody, cwd, storedMilestone, readStoredTotalPhases(existingFm), readStoredCompletedPhases(existingFm), readStoredTotalPlans(existingFm), readStoredCompletedPlans(existingFm), isMilestoneExplicitlyNull(existingFm));
|
|
1084
1084
|
const progress = builtFm['progress'];
|
|
1085
1085
|
const percent = progress && typeof progress['percent'] === 'number' ? progress['percent'] : null;
|
|
1086
1086
|
const completedPlans = progress && typeof progress['completed_plans'] === 'number' ? progress['completed_plans'] : null;
|
|
@@ -1609,10 +1609,31 @@ function cmdStateRecordSession(cwd, options, raw) {
|
|
|
1609
1609
|
const updated = [];
|
|
1610
1610
|
let sessionCreated = false;
|
|
1611
1611
|
const divergedFields = [];
|
|
1612
|
+
// #4763 (1): last-writer-wins stays (the recorded single-slot handoff design),
|
|
1613
|
+
// but a displaced record is no longer silent. The pre-write session record is
|
|
1614
|
+
// captured below and surfaced in the payload under `replacedRecord` whenever a
|
|
1615
|
+
// replacement actually changes a non-empty prior value.
|
|
1616
|
+
const priorRecord = {};
|
|
1612
1617
|
// ADR-3473 §8.7 (#3872): caller-allocated out-param, filled with the
|
|
1613
1618
|
// transaction's own pre-write snapshot + body by `applyPostSyncPreservation`.
|
|
1614
1619
|
const preWriteState = {};
|
|
1615
1620
|
readModifyWriteStateMd(statePath, (content) => {
|
|
1621
|
+
// #4763 (1): read the pre-write session record. The capture mirrors the
|
|
1622
|
+
// WRITER, not the snapshot reader: stateReplaceField replaces the FIRST
|
|
1623
|
+
// case-insensitive label match anywhere in the document, so the capture is
|
|
1624
|
+
// document-wide too — scoping it to ## Session would miss an archive-section
|
|
1625
|
+
// line the writer actually displaced. Continuation lines join via
|
|
1626
|
+
// stateFieldContinuation (the sanctioned joiner — stateExtractField alone is
|
|
1627
|
+
// first-line-only), so a wrapped multi-line handoff is surfaced whole.
|
|
1628
|
+
const capturePrior = (fieldName) => {
|
|
1629
|
+
const first = (0, state_document_cjs_1.stateExtractField)(content, fieldName);
|
|
1630
|
+
if (first === null)
|
|
1631
|
+
return undefined;
|
|
1632
|
+
const cont = (0, state_document_cjs_1.stateFieldContinuation)(content, fieldName);
|
|
1633
|
+
return cont ? `${first}\n${cont}` : first;
|
|
1634
|
+
};
|
|
1635
|
+
priorRecord.stoppedAt = capturePrior('Stopped At');
|
|
1636
|
+
priorRecord.resumeFile = capturePrior('Resume File');
|
|
1616
1637
|
// Update Last session / Last Date
|
|
1617
1638
|
let result = (0, state_document_cjs_1.stateReplaceField)(content, 'Last session', now);
|
|
1618
1639
|
if (result) {
|
|
@@ -1836,6 +1857,27 @@ function cmdStateRecordSession(cwd, options, raw) {
|
|
|
1836
1857
|
const result = { recorded: true, updated: reconciledUpdated };
|
|
1837
1858
|
if (sessionCreated)
|
|
1838
1859
|
result['created'] = true;
|
|
1860
|
+
// #4763 (1): surface any non-empty prior record the write displaced. Gated
|
|
1861
|
+
// on reconciledUpdated (the fields that actually persisted, post-#3957
|
|
1862
|
+
// reconcile) rather than the pre-reconciliation updated[]. Only fields with
|
|
1863
|
+
// real prior content differing from the caller's value count — same-value
|
|
1864
|
+
// rewrites, the insert path (no prior label), and the #944 template-default
|
|
1865
|
+
// DWIM (defaults match case-insensitively, so a case-variant 'none' →
|
|
1866
|
+
// 'None' rewrite is normalization, not displacement) are excluded.
|
|
1867
|
+
const isResumeTemplateDefault = priorRecord.resumeFile !== undefined
|
|
1868
|
+
&& state_document_cjs_1.KNOWN_TEMPLATE_DEFAULTS['Resume File'].some((d) => d.toLowerCase() === priorRecord.resumeFile?.toLowerCase());
|
|
1869
|
+
const replacedRecord = {};
|
|
1870
|
+
if (reconciledUpdated.includes('Stopped At') && priorRecord.stoppedAt
|
|
1871
|
+
&& priorRecord.stoppedAt !== options.stopped_at) {
|
|
1872
|
+
replacedRecord['Stopped At'] = priorRecord.stoppedAt;
|
|
1873
|
+
}
|
|
1874
|
+
if (reconciledUpdated.includes('Resume File') && priorRecord.resumeFile
|
|
1875
|
+
&& !isResumeTemplateDefault
|
|
1876
|
+
&& priorRecord.resumeFile !== (options.resume_file ?? undefined)) {
|
|
1877
|
+
replacedRecord['Resume File'] = priorRecord.resumeFile;
|
|
1878
|
+
}
|
|
1879
|
+
if (Object.keys(replacedRecord).length > 0)
|
|
1880
|
+
result['replacedRecord'] = replacedRecord;
|
|
1839
1881
|
output(result, raw, 'true');
|
|
1840
1882
|
}
|
|
1841
1883
|
else if (updated.length === 0) {
|
|
@@ -2350,6 +2392,112 @@ function countRoadmapPhaseHeadings(scope, convention, retiredPhaseNums, applyCon
|
|
|
2350
2392
|
}
|
|
2351
2393
|
return count;
|
|
2352
2394
|
}
|
|
2395
|
+
/**
|
|
2396
|
+
* The phase directories every STATE.md frontmatter rebuild scans, and the
|
|
2397
|
+
* ROADMAP facts derived alongside them — the ONE owner of that set (#5118).
|
|
2398
|
+
*
|
|
2399
|
+
* `phaseDirs` is milestone-scoped (`listMilestonePhaseDirs`), drops retired
|
|
2400
|
+
* phases (#1514) and holds exactly one directory per normalized phase key
|
|
2401
|
+
* (Bug #2445, deterministic tie-break #3355). `buildStateFrontmatter` counts
|
|
2402
|
+
* from it, and `statePhaseDirsToScan` hands it to `phase remove`'s pre-write
|
|
2403
|
+
* validation, so the two cannot disagree about which reports a rebuild reads.
|
|
2404
|
+
*/
|
|
2405
|
+
function scanStatePhaseDirs(cwd, phasesDir, phaseConvention, storedMilestone,
|
|
2406
|
+
// `quiet` suppresses ONLY the duplicate-phase-key stderr warning. A pre-write
|
|
2407
|
+
// validation pass that is followed by the real rebuild scan sets it, so the
|
|
2408
|
+
// one command prints the warning once (the rebuild scan owns the warning).
|
|
2409
|
+
options = {}) {
|
|
2410
|
+
// Read the current-milestone ROADMAP scope once: it feeds both the
|
|
2411
|
+
// heading-based phase count and the retired/folded-phase
|
|
2412
|
+
// exclusion (#1514). Computed before the disk scan so retired phases
|
|
2413
|
+
// can be dropped from the dir set too.
|
|
2414
|
+
let roadmapScope = null;
|
|
2415
|
+
let roadmapRaw = null;
|
|
2416
|
+
let retiredPhaseNums = new Set();
|
|
2417
|
+
try {
|
|
2418
|
+
const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
|
|
2419
|
+
roadmapRaw = (0, shell_command_projection_cjs_1.platformReadSync)(roadmapPath);
|
|
2420
|
+
if (roadmapRaw !== null) {
|
|
2421
|
+
roadmapScope = extractCurrentMilestone(roadmapRaw, cwd);
|
|
2422
|
+
retiredPhaseNums = extractRetiredPhaseNumbers(roadmapScope, phaseConvention);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
catch { /* fall through: no roadmap scope → no retired exclusion */ }
|
|
2426
|
+
// #3017: scope the milestone filter to the STORED milestone when available,
|
|
2427
|
+
// so a state.* write doesn't auto-derive (and mis-bind) to a different
|
|
2428
|
+
// milestone's heading and clobber the stored value + progress counts.
|
|
2429
|
+
// #3185 (ADR-3180 Decision 1): "which phase directories belong to the
|
|
2430
|
+
// CURRENT (stored) milestone" — routed through the canonical owner
|
|
2431
|
+
// instead of a hand-rolled readdirSync + isDirInMilestone filter
|
|
2432
|
+
// (which also never excluded sentinels, unlike the owner).
|
|
2433
|
+
const { value: allMatchingDirs, scope: phaseDirScope } = listMilestonePhaseDirs(phasesDir, {
|
|
2434
|
+
cwd,
|
|
2435
|
+
versionOverride: storedMilestone ?? null,
|
|
2436
|
+
phaseIdConvention: phaseConvention,
|
|
2437
|
+
});
|
|
2438
|
+
// Bug #2445: when stale phase dirs from a prior milestone remain in
|
|
2439
|
+
// .planning/phases/ alongside new dirs with the same phase number,
|
|
2440
|
+
// de-duplicate by normalized phase number keeping exactly one dir
|
|
2441
|
+
// per key (deterministic tie-break: see #3355 below). This prevents
|
|
2442
|
+
// double-counting (e.g. two "Phase 1" dirs).
|
|
2443
|
+
const seenPhaseNums = new Map(); // normalizedNum -> dirName
|
|
2444
|
+
for (const dir of allMatchingDirs) {
|
|
2445
|
+
// #1514: a retired/folded phase keeps a directory but no completion
|
|
2446
|
+
// artifact; drop it from the disk phase set so it counts toward
|
|
2447
|
+
// neither the denominator nor the numerator (mirrors the heading
|
|
2448
|
+
// exclusion below). Project-code-aware via phaseKeyFromDir.
|
|
2449
|
+
if (retiredPhaseNums.size > 0 && retiredPhaseNums.has(phaseKeyFromDir(dir, phaseConvention)))
|
|
2450
|
+
continue;
|
|
2451
|
+
// #3185: dedup grouping routed through the canonical phaseKeyFromDir
|
|
2452
|
+
// (src/phase-id.cts) instead of a local leading-digits regex that
|
|
2453
|
+
// diverged from extractPhaseToken/phaseKeyFromDir on
|
|
2454
|
+
// project-code-prefixed dirs (whole dirname fell through as the key,
|
|
2455
|
+
// so a `PROJ-05`/`PROJ-05-slug` pair never deduped) and on
|
|
2456
|
+
// multi-segment milestone dirs. Same key surface used two lines
|
|
2457
|
+
// above for the retiredPhaseNums exclusion, so both filters agree.
|
|
2458
|
+
const key = phaseKeyFromDir(dir, phaseConvention);
|
|
2459
|
+
if (!seenPhaseNums.has(key)) {
|
|
2460
|
+
seenPhaseNums.set(key, dir);
|
|
2461
|
+
}
|
|
2462
|
+
else {
|
|
2463
|
+
// #3355: the survivor of a same-milestone collision must be
|
|
2464
|
+
// chosen from repository CONTENT, never from filesystem state.
|
|
2465
|
+
// The pre-#3355 tie-break was `mtimeMs` — a checkout-order
|
|
2466
|
+
// signal — so two byte-identical checkouts of the same commit
|
|
2467
|
+
// that wrote the colliding dirs in a different order picked
|
|
2468
|
+
// different survivors, and progress.total_plans /
|
|
2469
|
+
// completed_plans drifted across clones and CI runs. The
|
|
2470
|
+
// directory NAME is git-tracked content and a total order, so
|
|
2471
|
+
// the lexicographically-first dir wins deterministically. The
|
|
2472
|
+
// collision is still a project-level defect (duplicate phase
|
|
2473
|
+
// number in scope), so it is surfaced on stderr instead of
|
|
2474
|
+
// being silently resolved. The Bug #2445 invariant — exactly
|
|
2475
|
+
// one survivor per normalized phase number — is unchanged.
|
|
2476
|
+
const incumbent = seenPhaseNums.get(key);
|
|
2477
|
+
const survivor = dir < incumbent ? dir : incumbent;
|
|
2478
|
+
seenPhaseNums.set(key, survivor);
|
|
2479
|
+
if (options.quiet !== true)
|
|
2480
|
+
process.stderr.write(`gsd: warning — phase directories '${incumbent}' and '${dir}' both normalize to phase key '${key}' (duplicate phase number in .planning/phases/); keeping '${survivor}' by deterministic lexicographic order. (#3355)\n`);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
return {
|
|
2484
|
+
phaseDirs: [...seenPhaseNums.values()],
|
|
2485
|
+
phaseDirScope,
|
|
2486
|
+
roadmapScope,
|
|
2487
|
+
roadmapRaw,
|
|
2488
|
+
retiredPhaseNums,
|
|
2489
|
+
};
|
|
2490
|
+
}
|
|
2491
|
+
/**
|
|
2492
|
+
* #5038: does existing frontmatter assert an explicit null milestone, as
|
|
2493
|
+
* opposed to omitting the `milestone:` key? `extractFrontmatter` parses under
|
|
2494
|
+
* FAILSAFE_SCHEMA, so YAML null arrives as a string; classification is
|
|
2495
|
+
* delegated to classifyMilestoneScalar.
|
|
2496
|
+
*/
|
|
2497
|
+
function isMilestoneExplicitlyNull(existingFm) {
|
|
2498
|
+
const raw = existingFm['milestone'];
|
|
2499
|
+
return typeof raw === 'string' && classifyMilestoneScalar(raw).explicitNull;
|
|
2500
|
+
}
|
|
2353
2501
|
/**
|
|
2354
2502
|
* Extract machine-readable fields from STATE.md markdown body and build
|
|
2355
2503
|
* a YAML frontmatter object. Allows hooks and scripts to read state
|
|
@@ -2361,7 +2509,10 @@ function buildStateFrontmatter(bodyContent, cwd, storedMilestone, storedTotalPha
|
|
|
2361
2509
|
// #3354/#3573 withhold condition the disk scan returns null for all four
|
|
2362
2510
|
// counters, and these stored values are what the progress block falls back
|
|
2363
2511
|
// to (else the keys are omitted).
|
|
2364
|
-
storedCompletedPhases, storedTotalPlans, storedCompletedPlans
|
|
2512
|
+
storedCompletedPhases, storedTotalPlans, storedCompletedPlans,
|
|
2513
|
+
// #5038: explicit `milestone: null` (vs an absent key); withholds progress
|
|
2514
|
+
// counters on a sectioned ROADMAP without the unbound warning.
|
|
2515
|
+
milestoneExplicitlyNull) {
|
|
2365
2516
|
// #2956: scope `Phase` extraction to ## Current Position (mirrors the read
|
|
2366
2517
|
// path in cmdStateSnapshot and the Stopped At / Paused At ## Session scoping
|
|
2367
2518
|
// below). Phase canonically lives in ## Current Position (templates/state.md);
|
|
@@ -2463,79 +2614,12 @@ storedCompletedPhases, storedTotalPlans, storedCompletedPlans) {
|
|
|
2463
2614
|
// on repeated buildStateFrontmatter invocations within the same process (#1967)
|
|
2464
2615
|
let cached = _diskScanCache.get(cwd);
|
|
2465
2616
|
if (!cached) {
|
|
2466
|
-
//
|
|
2467
|
-
//
|
|
2468
|
-
//
|
|
2469
|
-
//
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
let retiredPhaseNums = new Set();
|
|
2473
|
-
try {
|
|
2474
|
-
const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
|
|
2475
|
-
roadmapRaw = (0, shell_command_projection_cjs_1.platformReadSync)(roadmapPath);
|
|
2476
|
-
if (roadmapRaw !== null) {
|
|
2477
|
-
roadmapScope = extractCurrentMilestone(roadmapRaw, cwd);
|
|
2478
|
-
retiredPhaseNums = extractRetiredPhaseNumbers(roadmapScope, phaseConvention);
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
|
-
catch { /* fall through: no roadmap scope → no retired exclusion */ }
|
|
2482
|
-
// #3017: scope the milestone filter to the STORED milestone when available,
|
|
2483
|
-
// so a state.* write doesn't auto-derive (and mis-bind) to a different
|
|
2484
|
-
// milestone's heading and clobber the stored value + progress counts.
|
|
2485
|
-
// #3185 (ADR-3180 Decision 1): "which phase directories belong to the
|
|
2486
|
-
// CURRENT (stored) milestone" — routed through the canonical owner
|
|
2487
|
-
// instead of a hand-rolled readdirSync + isDirInMilestone filter
|
|
2488
|
-
// (which also never excluded sentinels, unlike the owner).
|
|
2489
|
-
const { value: allMatchingDirs, scope: phaseDirScope } = listMilestonePhaseDirs(phasesDir, {
|
|
2490
|
-
cwd,
|
|
2491
|
-
versionOverride: storedMilestone ?? null,
|
|
2492
|
-
phaseIdConvention: phaseConvention,
|
|
2493
|
-
});
|
|
2494
|
-
// Bug #2445: when stale phase dirs from a prior milestone remain in
|
|
2495
|
-
// .planning/phases/ alongside new dirs with the same phase number,
|
|
2496
|
-
// de-duplicate by normalized phase number keeping exactly one dir
|
|
2497
|
-
// per key (deterministic tie-break: see #3355 below). This prevents
|
|
2498
|
-
// double-counting (e.g. two "Phase 1" dirs).
|
|
2499
|
-
const seenPhaseNums = new Map(); // normalizedNum -> dirName
|
|
2500
|
-
for (const dir of allMatchingDirs) {
|
|
2501
|
-
// #1514: a retired/folded phase keeps a directory but no completion
|
|
2502
|
-
// artifact; drop it from the disk phase set so it counts toward
|
|
2503
|
-
// neither the denominator nor the numerator (mirrors the heading
|
|
2504
|
-
// exclusion below). Project-code-aware via phaseKeyFromDir.
|
|
2505
|
-
if (retiredPhaseNums.size > 0 && retiredPhaseNums.has(phaseKeyFromDir(dir, phaseConvention)))
|
|
2506
|
-
continue;
|
|
2507
|
-
// #3185: dedup grouping routed through the canonical phaseKeyFromDir
|
|
2508
|
-
// (src/phase-id.cts) instead of a local leading-digits regex that
|
|
2509
|
-
// diverged from extractPhaseToken/phaseKeyFromDir on
|
|
2510
|
-
// project-code-prefixed dirs (whole dirname fell through as the key,
|
|
2511
|
-
// so a `PROJ-05`/`PROJ-05-slug` pair never deduped) and on
|
|
2512
|
-
// multi-segment milestone dirs. Same key surface used two lines
|
|
2513
|
-
// above for the retiredPhaseNums exclusion, so both filters agree.
|
|
2514
|
-
const key = phaseKeyFromDir(dir, phaseConvention);
|
|
2515
|
-
if (!seenPhaseNums.has(key)) {
|
|
2516
|
-
seenPhaseNums.set(key, dir);
|
|
2517
|
-
}
|
|
2518
|
-
else {
|
|
2519
|
-
// #3355: the survivor of a same-milestone collision must be
|
|
2520
|
-
// chosen from repository CONTENT, never from filesystem state.
|
|
2521
|
-
// The pre-#3355 tie-break was `mtimeMs` — a checkout-order
|
|
2522
|
-
// signal — so two byte-identical checkouts of the same commit
|
|
2523
|
-
// that wrote the colliding dirs in a different order picked
|
|
2524
|
-
// different survivors, and progress.total_plans /
|
|
2525
|
-
// completed_plans drifted across clones and CI runs. The
|
|
2526
|
-
// directory NAME is git-tracked content and a total order, so
|
|
2527
|
-
// the lexicographically-first dir wins deterministically. The
|
|
2528
|
-
// collision is still a project-level defect (duplicate phase
|
|
2529
|
-
// number in scope), so it is surfaced on stderr instead of
|
|
2530
|
-
// being silently resolved. The Bug #2445 invariant — exactly
|
|
2531
|
-
// one survivor per normalized phase number — is unchanged.
|
|
2532
|
-
const incumbent = seenPhaseNums.get(key);
|
|
2533
|
-
const survivor = dir < incumbent ? dir : incumbent;
|
|
2534
|
-
seenPhaseNums.set(key, survivor);
|
|
2535
|
-
process.stderr.write(`gsd: warning — phase directories '${incumbent}' and '${dir}' both normalize to phase key '${key}' (duplicate phase number in .planning/phases/); keeping '${survivor}' by deterministic lexicographic order. (#3355)\n`);
|
|
2536
|
-
}
|
|
2537
|
-
}
|
|
2538
|
-
const phaseDirs = [...seenPhaseNums.values()];
|
|
2617
|
+
// The ROADMAP scope + the deduped, milestone-scoped disk phase set:
|
|
2618
|
+
// one owner (scanStatePhaseDirs, above buildStateFrontmatter) so the
|
|
2619
|
+
// #5118 pre-write validation in `phase remove` reads exactly this set.
|
|
2620
|
+
// #5038: an explicit-null `storedMilestone` is text, not a version;
|
|
2621
|
+
// pass a real null so it never reaches the phase-dir version filter.
|
|
2622
|
+
const { phaseDirs, phaseDirScope, roadmapScope, roadmapRaw, retiredPhaseNums, } = scanStatePhaseDirs(cwd, phasesDir, phaseConvention, milestoneExplicitlyNull ? null : storedMilestone);
|
|
2539
2623
|
let diskTotalPlans = 0;
|
|
2540
2624
|
let diskTotalSummaries = 0;
|
|
2541
2625
|
let diskCompletedPhases = 0;
|
|
@@ -2555,7 +2639,15 @@ storedCompletedPhases, storedTotalPlans, storedCompletedPlans) {
|
|
|
2555
2639
|
// #612: `phaseConvention` threaded so a bracket phase dir resolves
|
|
2556
2640
|
// and scopes its verification report like its legacy twin — the
|
|
2557
2641
|
// read-side half of the same thread cmdStateSync gets below.
|
|
2558
|
-
|
|
2642
|
+
const completion = isPhaseComplete(phaseDir, { convention: phaseConvention });
|
|
2643
|
+
// #5118 (no write before the error): every STATE.md write rebuilds
|
|
2644
|
+
// this frontmatter BEFORE its write, so a report whose `status` is
|
|
2645
|
+
// outside the closed set fails the write here — the owner's error,
|
|
2646
|
+
// rethrown past the best-effort catch below, nothing persisted and
|
|
2647
|
+
// nothing cached.
|
|
2648
|
+
if (completion.value.statusError)
|
|
2649
|
+
throw completion.value.statusError;
|
|
2650
|
+
if (completion.value.complete)
|
|
2559
2651
|
diskCompletedPhases++;
|
|
2560
2652
|
}
|
|
2561
2653
|
// Count phase headings from ROADMAP — single source of truth for
|
|
@@ -2576,6 +2668,21 @@ storedCompletedPhases, storedTotalPlans, storedCompletedPlans) {
|
|
|
2576
2668
|
// phase-dir count only, and mark unbounded so percent is skipped
|
|
2577
2669
|
// downstream (mirrors the sync write-path guard).
|
|
2578
2670
|
let milestoneBounded = true;
|
|
2671
|
+
// #2828/#3642: distinguish a FLAT unmilestoned roadmap (no milestone
|
|
2672
|
+
// sectioning at all — only Phase headings) from a MILESTONED one
|
|
2673
|
+
// (milestone/version headings exist). Computed before the
|
|
2674
|
+
// boundedness gate so the explicit-null branch can use it (#5038).
|
|
2675
|
+
// #3642: the flat test uses the >=1 sibling (hasAnyMilestoneSection),
|
|
2676
|
+
// not the >=2 predicate. >=2 under-answers the question this branch
|
|
2677
|
+
// asks: with EXACTLY ONE milestone section and an asserted milestone
|
|
2678
|
+
// absent from the ROADMAP, >=2 read "flat" and the whole-document
|
|
2679
|
+
// count — which IS that single section's phases — was written as the
|
|
2680
|
+
// asserted milestone's total, silently clobbering the stored value.
|
|
2681
|
+
// The >=2 threshold governs SIBLING conflation; asserted-vs-section
|
|
2682
|
+
// needs only one section to go wrong. Zero sections (genuinely flat)
|
|
2683
|
+
// keeps the whole-document count, per #2828.
|
|
2684
|
+
const roadmapHasAnyMilestoneSection = roadmapRaw !== null
|
|
2685
|
+
&& hasAnyMilestoneSection(roadmapRaw);
|
|
2579
2686
|
// #3216 fix (#1761 regression): use `assertedMilestoneVersion` —
|
|
2580
2687
|
// the version STATE.md actually asserts — not the scope-gated
|
|
2581
2688
|
// `milestone`. `milestone` is null on any non-COMPLETE identity
|
|
@@ -2593,27 +2700,19 @@ storedCompletedPhases, storedTotalPlans, storedCompletedPlans) {
|
|
|
2593
2700
|
// defect, design row 17).
|
|
2594
2701
|
milestoneBounded = isMilestoneBounded(roadmapRaw, String(assertedMilestoneVersion).trim(), phaseConvention);
|
|
2595
2702
|
}
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2703
|
+
else if (milestoneExplicitlyNull && roadmapRaw !== null && roadmapHasAnyMilestoneSection) {
|
|
2704
|
+
// #5038: explicit null on a sectioned ROADMAP: no section owns
|
|
2705
|
+
// the counts, so treat as unbound for the withhold gate (the
|
|
2706
|
+
// warning below is skipped, null is deliberate).
|
|
2707
|
+
milestoneBounded = false;
|
|
2708
|
+
}
|
|
2709
|
+
// #2828: on a flat roadmap the whole-doc count is correct (no
|
|
2710
|
+
// sibling milestones to conflate); on a sectioned-but-unbounded one
|
|
2711
|
+
// it conflates siblings (#1761), so fall back to phaseDirs.length.
|
|
2602
2712
|
// #3184: routed through the single owner (roadmap-parser.cjs) —
|
|
2603
2713
|
// deliberately weaker than isMilestoneBoundedInRoadmap above (no
|
|
2604
2714
|
// version-token requirement); see hasMilestoneSectioning's own
|
|
2605
2715
|
// doc comment for why that distinction is load-bearing.
|
|
2606
|
-
// #3642: the flat test uses the >=1 sibling (hasAnyMilestoneSection),
|
|
2607
|
-
// not the >=2 predicate. >=2 under-answers the question this branch
|
|
2608
|
-
// asks: with EXACTLY ONE milestone section and an asserted milestone
|
|
2609
|
-
// absent from the ROADMAP, >=2 read "flat" and the whole-document
|
|
2610
|
-
// count — which IS that single section's phases — was written as the
|
|
2611
|
-
// asserted milestone's total, silently clobbering the stored value.
|
|
2612
|
-
// The >=2 threshold governs SIBLING conflation; asserted-vs-section
|
|
2613
|
-
// needs only one section to go wrong. Zero sections (genuinely flat)
|
|
2614
|
-
// keeps the whole-document count, per #2828.
|
|
2615
|
-
const roadmapHasAnyMilestoneSection = roadmapRaw !== null
|
|
2616
|
-
&& hasAnyMilestoneSection(roadmapRaw);
|
|
2617
2716
|
const safeToUseRoadmapCount = milestoneBounded
|
|
2618
2717
|
|| (roadmapPhaseCount > 0 && !roadmapHasAnyMilestoneSection);
|
|
2619
2718
|
// #3354: the milestoned-but-unbounded sibling of the #2828/#3204
|
|
@@ -2631,7 +2730,8 @@ storedCompletedPhases, storedTotalPlans, storedCompletedPlans) {
|
|
|
2631
2730
|
// The degenerate un-sectioned zero-heading case keeps the
|
|
2632
2731
|
// phaseDirs.length fallback — with nothing declared anywhere else,
|
|
2633
2732
|
// the disk count is the only source and remains correct.
|
|
2634
|
-
|
|
2733
|
+
// #5038: explicit null is withheld silently (explicitNullWithheld).
|
|
2734
|
+
const milestonedButUnbounded = !milestoneBounded && roadmapHasAnyMilestoneSection && !milestoneExplicitlyNull;
|
|
2635
2735
|
if (milestonedButUnbounded) {
|
|
2636
2736
|
process.stderr.write(`gsd: warning — milestone '${String(assertedMilestoneVersion ?? '').trim()}' is asserted in STATE.md but matches no ROADMAP heading, and the ROADMAP carries milestone section(s) — one (#3642) or several (#3354) — none matching it; the whole-document count would attribute a foreign section's phases to this milestone and the on-disk phase-directory count would understate the declared total, so the progress counters (total_phases, completed_phases, total_plans, completed_plans) are left at their stored values. (#3354/#3642/#4094)\n`);
|
|
2637
2737
|
}
|
|
@@ -2646,9 +2746,11 @@ storedCompletedPhases, storedTotalPlans, storedCompletedPlans) {
|
|
|
2646
2746
|
// value instead. Without an asserted milestone (fresh project,
|
|
2647
2747
|
// pre-roadmap) the disk count is still the only source and stays
|
|
2648
2748
|
// authoritative (the #3354 doctrine's degenerate case).
|
|
2749
|
+
// #5038: an explicit null is not an asserted milestone.
|
|
2649
2750
|
const roadmapAbsentWithAssertedMilestone = roadmapRaw === null &&
|
|
2650
2751
|
typeof storedMilestone === 'string' &&
|
|
2651
|
-
storedMilestone.trim() !== ''
|
|
2752
|
+
storedMilestone.trim() !== '' &&
|
|
2753
|
+
!milestoneExplicitlyNull;
|
|
2652
2754
|
if (roadmapAbsentWithAssertedMilestone) {
|
|
2653
2755
|
process.stderr.write(`gsd: warning — milestone '${storedMilestone.trim()}' is asserted in STATE.md but ROADMAP.md is absent or unreadable, so the phase-heading total cannot be derived; the on-disk phase-directory count would understate the declared total, so the progress counters (total_phases, completed_phases, total_plans, completed_plans) are left at their stored values. (#3573) (#4094)\n`);
|
|
2654
2756
|
}
|
|
@@ -2661,7 +2763,9 @@ storedCompletedPhases, storedTotalPlans, storedCompletedPlans) {
|
|
|
2661
2763
|
// Pre-#4094 only totalPhases was nulled here, so every resyncing
|
|
2662
2764
|
// write silently clobbered the three stored siblings with the
|
|
2663
2765
|
// under-scoped disk numbers.
|
|
2664
|
-
|
|
2766
|
+
// #5038: silent sibling of milestonedButUnbounded.
|
|
2767
|
+
const explicitNullWithheld = milestoneExplicitlyNull && !milestoneBounded && roadmapHasAnyMilestoneSection;
|
|
2768
|
+
const diskCountsWithheld = milestonedButUnbounded || roadmapAbsentWithAssertedMilestone || explicitNullWithheld;
|
|
2665
2769
|
// #4129: floor the completed-phases numerator at the ROADMAP's own
|
|
2666
2770
|
// milestone Complete-row count. The disk numerator counts ONLY
|
|
2667
2771
|
// phase dirs whose *-VERIFICATION.md routes `passed` (isPhaseComplete,
|
|
@@ -2754,9 +2858,13 @@ storedCompletedPhases, storedTotalPlans, storedCompletedPlans) {
|
|
|
2754
2858
|
* crash `state show`; on failure this simply keeps whatever
|
|
2755
2859
|
* frontmatter-derived totals/completedPhases/etc. were already set
|
|
2756
2860
|
* above, a graceful degrade rather than a corrupted write (nothing is
|
|
2757
|
-
* persisted from this block).
|
|
2861
|
+
* persisted from this block). #5118: an out-of-set verification
|
|
2862
|
+
* report status is not a read failure to degrade over — rethrown. */
|
|
2863
|
+
}
|
|
2864
|
+
catch (err) {
|
|
2865
|
+
if (err instanceof VerificationStatusError)
|
|
2866
|
+
throw err;
|
|
2758
2867
|
}
|
|
2759
|
-
catch { /* intentionally empty */ }
|
|
2760
2868
|
}
|
|
2761
2869
|
// Derive percent from disk counts when available (ground truth).
|
|
2762
2870
|
// Uses min(plan_fraction, phase_fraction) via computeProgressPercent so that
|
|
@@ -3173,7 +3281,7 @@ function syncStateFrontmatter(content, cwd, authoritativeFm, sanctionedPermanent
|
|
|
3173
3281
|
// milestoned-but-unbounded withhold can preserve it across the write
|
|
3174
3282
|
// (the derived progress sub-block replaces the stored one wholesale below,
|
|
3175
3283
|
// so an omitted key would otherwise DELETE the stored value).
|
|
3176
|
-
const derivedFm = buildStateFrontmatter(body, cwd, storedMilestone, readStoredTotalPhases(existingFm), readStoredCompletedPhases(existingFm), readStoredTotalPlans(existingFm), readStoredCompletedPlans(existingFm));
|
|
3284
|
+
const derivedFm = buildStateFrontmatter(body, cwd, storedMilestone, readStoredTotalPhases(existingFm), readStoredCompletedPhases(existingFm), readStoredTotalPlans(existingFm), readStoredCompletedPlans(existingFm), isMilestoneExplicitlyNull(existingFm));
|
|
3177
3285
|
// Preserve existing frontmatter status when body-derived status is 'unknown'.
|
|
3178
3286
|
// This prevents a missing Status: field in the body from overwriting a
|
|
3179
3287
|
// previously valid status (e.g., 'executing' → 'unknown').
|
|
@@ -4525,7 +4633,7 @@ function cmdStateJson(cwd, raw) {
|
|
|
4525
4633
|
// reports the phase-directory count while the persisted file preserves the
|
|
4526
4634
|
// stored total, exactly the write/read divergence #3354 closed for its shape.
|
|
4527
4635
|
const storedMilestoneJson = typeof existingFm['milestone'] === 'string' ? existingFm['milestone'] : null;
|
|
4528
|
-
const built = buildStateFrontmatter(body, cwd, storedMilestoneJson, readStoredTotalPhases(existingFm), readStoredCompletedPhases(existingFm), readStoredTotalPlans(existingFm), readStoredCompletedPlans(existingFm));
|
|
4636
|
+
const built = buildStateFrontmatter(body, cwd, storedMilestoneJson, readStoredTotalPhases(existingFm), readStoredCompletedPhases(existingFm), readStoredTotalPlans(existingFm), readStoredCompletedPlans(existingFm), isMilestoneExplicitlyNull(existingFm));
|
|
4529
4637
|
// ADR-3408 §8.5 / D3: route stopped_at / paused_at / status / current_phase /
|
|
4530
4638
|
// current_phase_name / current_plan through the SAME `preserve-when-unchanged`
|
|
4531
4639
|
// executor the write path uses (`applyPreserveWhenUnchanged`), instead of a
|
|
@@ -5521,7 +5629,13 @@ function cmdStateSync(cwd, options, raw) {
|
|
|
5521
5629
|
// #612: `syncConvention` threaded — the write-side half of
|
|
5522
5630
|
// buildStateFrontmatter's thread above, so `state sync` and `state json`
|
|
5523
5631
|
// keep agreeing on completed_phases under the bracket convention.
|
|
5524
|
-
|
|
5632
|
+
// #5118 (no write before the error): `state sync` writes STATE.md only
|
|
5633
|
+
// after this scan, so an out-of-set report status fails it here, having
|
|
5634
|
+
// written nothing.
|
|
5635
|
+
const completion = isPhaseComplete(dirPath, { convention: syncConvention });
|
|
5636
|
+
if (completion.value.statusError)
|
|
5637
|
+
throw completion.value.statusError;
|
|
5638
|
+
if (completion.value.complete)
|
|
5525
5639
|
diskCompletedPhases++;
|
|
5526
5640
|
// Track the highest phase with incomplete plans (or any plans)
|
|
5527
5641
|
const phaseMatch = dir.match(new RegExp(`^(${PHASE_NUMBER_TOKEN_SOURCE})`, 'i'));
|
|
@@ -5568,8 +5682,10 @@ function cmdStateSync(cwd, options, raw) {
|
|
|
5568
5682
|
// set — leave Progress untouched (percent=null) rather than silently writing
|
|
5569
5683
|
// fallback-derived wrong values. Projects without a milestone version (the common
|
|
5570
5684
|
// sync-test shape) are unaffected: the gate only fires when a version is asserted.
|
|
5571
|
-
const
|
|
5572
|
-
const
|
|
5685
|
+
const syncFm = extractFrontmatter(content, statePath);
|
|
5686
|
+
const fmVersion = syncFm.milestone;
|
|
5687
|
+
const milestoneExplicitlyNull = isMilestoneExplicitlyNull(syncFm);
|
|
5688
|
+
const versionStr = typeof fmVersion === 'string' && fmVersion.trim() && !milestoneExplicitlyNull ? fmVersion.trim() : null;
|
|
5573
5689
|
let milestoneBounded = true;
|
|
5574
5690
|
if (versionStr !== null && syncRoadmapRaw !== null) {
|
|
5575
5691
|
// #3184: routed through the single owner (roadmap-parser.cjs) instead of
|
|
@@ -5578,6 +5694,10 @@ function cmdStateSync(cwd, options, raw) {
|
|
|
5578
5694
|
// extension on top inside isMilestoneBounded.
|
|
5579
5695
|
milestoneBounded = isMilestoneBounded(syncRoadmapRaw, versionStr, syncConvention);
|
|
5580
5696
|
}
|
|
5697
|
+
else if (milestoneExplicitlyNull && syncRoadmapRaw !== null && hasAnyMilestoneSection(syncRoadmapRaw)) {
|
|
5698
|
+
// #5038: mirror buildStateFrontmatter's silent explicit-null withhold.
|
|
5699
|
+
milestoneBounded = false;
|
|
5700
|
+
}
|
|
5581
5701
|
let percent = null;
|
|
5582
5702
|
if (!milestoneBounded) {
|
|
5583
5703
|
changes.push(`Progress: skipped — milestone ${versionStr} cannot be bounded to a versioned ROADMAP phase set (#1761)`);
|
|
@@ -6127,6 +6247,35 @@ function cmdStateCompletePhase(cwd, raw, overridePhase) {
|
|
|
6127
6247
|
if (wrote)
|
|
6128
6248
|
publishStateContract(cwd);
|
|
6129
6249
|
}
|
|
6250
|
+
/**
|
|
6251
|
+
* #5118 (no write before the error): throws `VerificationStatusError` when a
|
|
6252
|
+
* verification report in the set every STATE.md write rebuilds its frontmatter
|
|
6253
|
+
* from carries a `status` outside the closed set — and returns having done
|
|
6254
|
+
* nothing else. It runs `buildStateFrontmatter` itself (the SAME disk scan,
|
|
6255
|
+
* milestone scoping and dedupe every write performs), so a caller that writes
|
|
6256
|
+
* OTHER files before STATE.md (`phase complete`, `phase remove`, `quick
|
|
6257
|
+
* archive`, `validate health --repair`) refuses for exactly the phases the
|
|
6258
|
+
* STATE.md write would refuse for — the set cannot drift from a second copy.
|
|
6259
|
+
*/
|
|
6260
|
+
function assertVerificationReportsReadable(bodyContent, cwd) {
|
|
6261
|
+
buildStateFrontmatter(bodyContent, cwd);
|
|
6262
|
+
}
|
|
6263
|
+
/**
|
|
6264
|
+
* The phase directory NAMES (under `.planning/phases/`) a STATE.md frontmatter
|
|
6265
|
+
* rebuild scans — milestone-scoped, retired phases dropped, one per phase key
|
|
6266
|
+
* (`scanStatePhaseDirs`). For a caller that must validate the reports a rebuild
|
|
6267
|
+
* WILL read after it has removed a directory of its own (`phase remove`): it
|
|
6268
|
+
* takes this set and drops the directory it is about to delete (#5118).
|
|
6269
|
+
*/
|
|
6270
|
+
function statePhaseDirsToScan(cwd) {
|
|
6271
|
+
const phasesDir = planningPaths(cwd).phases;
|
|
6272
|
+
if (!node_fs_1.default.existsSync(phasesDir))
|
|
6273
|
+
return [];
|
|
6274
|
+
// quiet: this is a pre-write validation pass, always followed by the real
|
|
6275
|
+
// rebuild scan (buildStateFrontmatter), which owns the duplicate-phase-key
|
|
6276
|
+
// stderr warning — so the command prints it once, not twice.
|
|
6277
|
+
return scanStatePhaseDirs(cwd, phasesDir, resolvePhaseIdConvention(cwd), undefined, { quiet: true }).phaseDirs;
|
|
6278
|
+
}
|
|
6130
6279
|
module.exports = {
|
|
6131
6280
|
stateExtractField: state_document_cjs_1.stateExtractField,
|
|
6132
6281
|
stateReplaceField: state_document_cjs_1.stateReplaceField,
|
|
@@ -6136,6 +6285,8 @@ module.exports = {
|
|
|
6136
6285
|
writeStateMd,
|
|
6137
6286
|
readModifyWriteStateMd,
|
|
6138
6287
|
syncStateFrontmatter,
|
|
6288
|
+
assertVerificationReportsReadable,
|
|
6289
|
+
statePhaseDirsToScan,
|
|
6139
6290
|
// #3374: the shared post-sync preservation pass (snapshots + table-driven
|
|
6140
6291
|
// applyStatePreservation + #2736 re-assert).
|
|
6141
6292
|
applyPostSyncPreservation,
|
|
@@ -35,6 +35,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
35
35
|
const node_os_1 = __importDefault(require("node:os"));
|
|
36
36
|
const node_path_1 = __importDefault(require("node:path"));
|
|
37
37
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
38
|
+
const runtime_name_policy_cjs_1 = require("./runtime-name-policy.cjs");
|
|
38
39
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
39
40
|
const installProfiles = require("./install-profiles.cjs");
|
|
40
41
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -61,6 +62,18 @@ const runtimeArtifactInstallPlan = require("./runtime-artifact-install-plan.cjs"
|
|
|
61
62
|
const retiredArtifactCleanup = require("./retired-artifact-cleanup.cjs");
|
|
62
63
|
const { assertDestWithinConfigHome } = runtimeArtifactInstallPlan;
|
|
63
64
|
const SURFACE_FILE_NAME = '.gsd-surface.json';
|
|
65
|
+
const INSTALL_MANIFEST_FILE_NAME = 'gsd-file-manifest.json';
|
|
66
|
+
/** Read only the installer-owned relative-include style, failing closed. */
|
|
67
|
+
function readRelativeIncludePrefix(runtimeConfigDir) {
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(runtimeConfigDir, INSTALL_MANIFEST_FILE_NAME), 'utf8'));
|
|
70
|
+
const prefix = parsed?.['relativeIncludePrefix'];
|
|
71
|
+
return typeof prefix === 'string' ? prefix : undefined;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
64
77
|
/**
|
|
65
78
|
* Read the surface state from a runtime config directory.
|
|
66
79
|
*
|
|
@@ -339,9 +352,13 @@ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry,
|
|
|
339
352
|
// then projects the defaulted value to the boolean `_computePathPrefix`'s
|
|
340
353
|
// existing `isGlobal: boolean` API requires.
|
|
341
354
|
const _isGlobal = (0, install_scope_cjs_1.isGlobalScope)(layout.scope ?? 'global');
|
|
342
|
-
const _isOpencode = layout.runtime ===
|
|
355
|
+
const _isOpencode = (0, runtime_name_policy_cjs_1.hostBehaviorsFor)(layout.runtime).opencodePathPrefix === true;
|
|
343
356
|
const _isWindowsHost = (opts?.platform ?? process.platform) === 'win32';
|
|
344
|
-
|
|
357
|
+
// #4377: style is an install-time fact, not a process environment setting.
|
|
358
|
+
// A later gsd-tools surface apply runs in another process, so it must reuse
|
|
359
|
+
// the persisted, root-relative prefix the installer actually emitted.
|
|
360
|
+
const _relativeIncludePrefix = readRelativeIncludePrefix(runtimeConfigDir);
|
|
361
|
+
const _pathPrefix = runtimeArtifactConversion._computePathPrefix({ isGlobal: _isGlobal, isOpencode: _isOpencode, isWindowsHost: _isWindowsHost, resolvedTarget: _resolvedTarget, homeDir: _homeDir, projectRelative: !_isGlobal && typeof _relativeIncludePrefix === 'string', projectRelativePath: _relativeIncludePrefix, localDirName: runtimeArtifactConversion._localIncludeDirName(layout.runtime) });
|
|
345
362
|
const _attribution = opts?.resolveAttribution ? opts.resolveAttribution(layout.runtime) : undefined;
|
|
346
363
|
// #2875 Part 2 (row I1): layout.configDir is this call's install root.
|
|
347
364
|
const agentCtx = { runtime: layout.runtime, pathPrefix: _pathPrefix, attribution: _attribution, targetDir: layout.configDir };
|