@opengsd/gsd-core 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +12 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +22 -14
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +5 -3
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +23 -2
- package/agents/gsd-plan-checker.md +50 -53
- package/agents/gsd-planner.md +50 -50
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +2 -2
- package/agents/gsd-roadmapper.md +15 -11
- package/agents/gsd-ui-checker.md +63 -4
- package/agents/gsd-ui-researcher.md +41 -3
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +13 -4
- package/bin/install.js +1448 -1103
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/quick.md +9 -5
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +1035 -138
- package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
- package/gsd-core/bin/lib/api-coverage.cjs +33 -14
- package/gsd-core/bin/lib/artifacts.cjs +5 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +1026 -268
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- 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-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +845 -130
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-state.cjs +18 -3
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +700 -40
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +216 -42
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +735 -0
- package/gsd-core/bin/lib/command-aliases.cjs +22 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +1172 -108
- package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
- package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
- package/gsd-core/bin/lib/config-loader.cjs +187 -23
- package/gsd-core/bin/lib/config.cjs +102 -3
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +208 -33
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/edge-probe.cjs +9 -1
- package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
- package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +899 -229
- package/gsd-core/bin/lib/gap-checker.cjs +95 -10
- package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
- package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
- 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 +149 -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 +268 -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 +187 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
- package/gsd-core/bin/lib/host-integration.cjs +39 -6
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init-command-router.cjs +118 -21
- package/gsd-core/bin/lib/init.cjs +439 -168
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +811 -259
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
- package/gsd-core/bin/lib/install-profiles.cjs +212 -61
- 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-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +148 -38
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +170 -15
- package/gsd-core/bin/lib/learnings.cjs +85 -14
- package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
- package/gsd-core/bin/lib/markdown-table.cjs +183 -22
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +842 -73
- package/gsd-core/bin/lib/model-catalog.cjs +232 -16
- package/gsd-core/bin/lib/model-resolver.cjs +193 -68
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
- package/gsd-core/bin/lib/phase-id.cjs +514 -40
- package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
- package/gsd-core/bin/lib/phase-locator.cjs +262 -34
- package/gsd-core/bin/lib/phase.cjs +1038 -214
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
- package/gsd-core/bin/lib/plan-document.cjs +263 -0
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +98 -3
- package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
- package/gsd-core/bin/lib/probe-core.cjs +5 -2
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
- package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
- package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +766 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
- package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +442 -96
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
- package/gsd-core/bin/lib/smart-entry.cjs +133 -23
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +52 -19
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +338 -8
- package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
- package/gsd-core/bin/lib/state-transition.cjs +846 -176
- package/gsd-core/bin/lib/state.cjs +2589 -369
- package/gsd-core/bin/lib/surface.cjs +33 -11
- package/gsd-core/bin/lib/task-command-router.cjs +111 -1
- package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
- package/gsd-core/bin/lib/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
- package/gsd-core/bin/lib/uat.cjs +1761 -167
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
- package/gsd-core/bin/lib/unusable-input.cjs +37 -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-command-router.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +75 -0
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -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 +272 -9
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify.cjs +453 -918
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
- package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/workstream.cjs +2 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
- package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
- package/gsd-core/bin/shared/exit-codes.json +8 -0
- package/gsd-core/bin/shared/exit-codes.sh +20 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -1
- package/gsd-core/references/agent-contracts.md +44 -26
- package/gsd-core/references/api-coverage.md +24 -2
- package/gsd-core/references/autonomous-smart-discuss.md +3 -3
- package/gsd-core/references/checkpoints.md +39 -21
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/edge-probe.md +8 -0
- package/gsd-core/references/execute-mvp-tdd.md +4 -6
- package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
- 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 +17 -11
- package/gsd-core/references/failing-direction.md +78 -0
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-integration.md +5 -5
- package/gsd-core/references/git-planning-commit.md +5 -4
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +61 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/nyquist-compliance.md +74 -0
- package/gsd-core/references/offer-next.md +3 -5
- package/gsd-core/references/phase-argument-parsing.md +3 -3
- package/gsd-core/references/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planner-revision.md +1 -1
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +44 -13
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +1 -1
- package/gsd-core/references/tdd.md +1 -3
- package/gsd-core/references/ui-brand.md +65 -21
- package/gsd-core/references/ui-consideration-probe.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +5 -5
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -17
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/SECURITY.md +3 -3
- package/gsd-core/templates/UI-SPEC.md +25 -3
- package/gsd-core/templates/VALIDATION.md +3 -3
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +5 -4
- package/gsd-core/templates/state.md +11 -4
- package/gsd-core/templates/verification-report.md +9 -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 +3 -3
- package/gsd-core/workflows/add-tests.md +3 -8
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +13 -20
- package/gsd-core/workflows/audit-fix.md +12 -3
- package/gsd-core/workflows/audit-milestone.md +9 -9
- package/gsd-core/workflows/audit-uat.md +17 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +11 -27
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +64 -5
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
- package/gsd-core/workflows/code-review-fix.md +38 -11
- package/gsd-core/workflows/code-review.md +159 -52
- package/gsd-core/workflows/complete-milestone.md +151 -23
- package/gsd-core/workflows/debug.md +12 -8
- package/gsd-core/workflows/diagnose-issues.md +47 -15
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +3 -6
- package/gsd-core/workflows/docs-update.md +5 -4
- package/gsd-core/workflows/edit-phase.md +27 -2
- package/gsd-core/workflows/eval-review.md +7 -14
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -4
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase.md +72 -100
- package/gsd-core/workflows/execute-plan.md +52 -15
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +5 -5
- package/gsd-core/workflows/health.md +76 -10
- package/gsd-core/workflows/import.md +18 -15
- package/gsd-core/workflows/inbox.md +4 -5
- package/gsd-core/workflows/ingest-docs.md +49 -16
- package/gsd-core/workflows/insert-phase.md +5 -5
- package/gsd-core/workflows/list-seeds.md +5 -3
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +12 -23
- package/gsd-core/workflows/map-codebase.md +1 -1
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +8 -5
- package/gsd-core/workflows/new-milestone.md +22 -29
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +26 -40
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +14 -2
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
- package/gsd-core/workflows/plan-phase.md +162 -59
- package/gsd-core/workflows/plan-review-convergence.md +96 -11
- package/gsd-core/workflows/plant-seed.md +2 -2
- package/gsd-core/workflows/pr-branch.md +187 -51
- package/gsd-core/workflows/profile-user.md +16 -14
- package/gsd-core/workflows/progress.md +61 -18
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
- package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
- package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick.md +55 -44
- package/gsd-core/workflows/remove-phase.md +4 -4
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +8 -12
- package/gsd-core/workflows/review.md +219 -20
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -3
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings-advanced.md +7 -9
- package/gsd-core/workflows/settings-integrations.md +64 -31
- package/gsd-core/workflows/settings.md +69 -7
- package/gsd-core/workflows/ship.md +116 -50
- package/gsd-core/workflows/sketch-wrap-up.md +11 -17
- package/gsd-core/workflows/sketch.md +12 -18
- package/gsd-core/workflows/smart-entry.md +3 -5
- package/gsd-core/workflows/spec-phase.md +53 -13
- package/gsd-core/workflows/spike-wrap-up.md +7 -11
- package/gsd-core/workflows/spike.md +20 -31
- package/gsd-core/workflows/stats.md +2 -2
- package/gsd-core/workflows/sync-skills.md +64 -9
- package/gsd-core/workflows/thread.md +11 -7
- package/gsd-core/workflows/transition.md +49 -14
- package/gsd-core/workflows/ui-phase.md +15 -21
- package/gsd-core/workflows/ui-review.md +8 -12
- package/gsd-core/workflows/ultraplan-phase.md +5 -13
- package/gsd-core/workflows/undo.md +8 -16
- package/gsd-core/workflows/update.md +7 -11
- package/gsd-core/workflows/validate-phase.md +3 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +66 -25
- package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
- package/hooks/dist/gsd-check-update-worker.js +56 -13
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +19 -10
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +2 -3
- package/hooks/dist/gsd-cursor-session-start.js +2 -1
- package/hooks/dist/gsd-cursor-stop.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +83 -3
- package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
- package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
- package/hooks/dist/gsd-graphify-update.sh +22 -18
- package/hooks/dist/gsd-node-runner.sh +76 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +37 -27
- package/hooks/dist/gsd-read-guard.js +16 -7
- package/hooks/dist/gsd-read-injection-scanner.js +55 -32
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +231 -24
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-validate-commit.sh +80 -6
- package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
- package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
- package/hooks/dist/gsd-workflow-guard.js +162 -46
- package/hooks/dist/gsd-worktree-path-guard.js +36 -21
- package/hooks/dist/gsd-write-guard.js +35 -25
- package/hooks/dist/lib/cli-exit.js +560 -0
- package/hooks/dist/lib/exit-code-registry.js +98 -0
- package/hooks/dist/lib/git-cmd.js +92 -59
- package/hooks/dist/lib/git-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- 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 +9 -0
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-agent-isolation-guard.js +158 -30
- package/hooks/gsd-check-update-worker.js +56 -13
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +19 -10
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +2 -3
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +83 -3
- package/hooks/gsd-cursor-subagent-stop.js +6 -3
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +76 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +37 -27
- package/hooks/gsd-read-guard.js +16 -7
- package/hooks/gsd-read-injection-scanner.js +55 -32
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +231 -24
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-validate-commit.sh +80 -6
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +162 -46
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +9 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +28 -11
- package/pi/gsd.cjs +19 -5
- package/scripts/base64-scan.sh +74 -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 +5 -0
- package/scripts/changeset/lint.cjs +60 -5
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/check-glossary-refs.cjs +77 -15
- package/scripts/check-mutation-score-ratchet.cjs +156 -0
- package/scripts/ci-check-job-near-cap.cjs +49 -0
- package/scripts/ci-pr-mergeability.cjs +262 -0
- package/scripts/ci-test-scope.cjs +64 -14
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/docs-guard-registry.cjs +396 -0
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-registry.cjs +11 -21
- package/scripts/gen-context-index.cjs +2 -11
- package/scripts/gen-exit-code-docs.cjs +318 -0
- package/scripts/gen-exit-code-registry.cjs +891 -0
- package/scripts/gen-features.cjs +836 -0
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-inventory-manifest.cjs +50 -4
- package/scripts/gen-loop-host-contract.cjs +138 -25
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/gen-scripts-cli-exit.cjs +185 -0
- package/scripts/gen-state-md-docs.cjs +727 -0
- package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +308 -0
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
- 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-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-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +38 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +492 -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 +471 -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 +488 -0
- package/scripts/lint-seam-enforcement.cjs +182 -0
- package/scripts/lint-slug-derivation-drift.cjs +921 -0
- package/scripts/lint-source-test-name-collision.cjs +241 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +950 -0
- package/scripts/lint-test-file-count.allowlist.json +137 -8
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +830 -0
- package/scripts/lint-vendored-deps.cjs +297 -0
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +100 -14
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +24 -7
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +0 -1
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +9 -5
- 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/bin/lib/ui-safety-gate.cjs +0 -107
- 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 -574
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1051
|
@@ -9,9 +9,17 @@
|
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.KNOWN_STATUS_PATTERNS = exports.KNOWN_TEMPLATE_DEFAULTS = void 0;
|
|
12
|
+
exports.toFiniteNumber = toFiniteNumber;
|
|
13
|
+
exports.isUnfilledFieldValue = isUnfilledFieldValue;
|
|
14
|
+
exports.leadingCalendarDate = leadingCalendarDate;
|
|
15
|
+
exports.isRealCalendarDate = isRealCalendarDate;
|
|
16
|
+
exports.stateFieldContinuation = stateFieldContinuation;
|
|
12
17
|
exports.stateExtractField = stateExtractField;
|
|
18
|
+
exports.stateFieldValue = stateFieldValue;
|
|
19
|
+
exports.stateCurrentPositionSlice = stateCurrentPositionSlice;
|
|
13
20
|
exports.stateReplaceField = stateReplaceField;
|
|
14
21
|
exports.stateReplaceFieldWithFallback = stateReplaceFieldWithFallback;
|
|
22
|
+
exports.stateReplaceFieldInSession = stateReplaceFieldInSession;
|
|
15
23
|
exports.normalizeStateStatus = normalizeStateStatus;
|
|
16
24
|
exports.computeProgressPercent = computeProgressPercent;
|
|
17
25
|
exports.shouldPreserveExistingProgress = shouldPreserveExistingProgress;
|
|
@@ -19,10 +27,21 @@ exports.normalizeProgressNumbers = normalizeProgressNumbers;
|
|
|
19
27
|
exports.isStateTemplateDefault = isStateTemplateDefault;
|
|
20
28
|
exports.stateReplaceFieldIfTemplate = stateReplaceFieldIfTemplate;
|
|
21
29
|
const markdown_table_cjs_1 = require("./markdown-table.cjs");
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
const phase_lifecycle_cjs_1 = require("./phase-lifecycle.cjs");
|
|
31
|
+
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
32
|
+
const pattern_cjs_1 = require("./pattern.cjs");
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-scope.cjs is an export= CommonJS module
|
|
34
|
+
const planningScopeMod = require("./planning-scope.cjs");
|
|
35
|
+
const { SCOPE } = planningScopeMod;
|
|
36
|
+
/**
|
|
37
|
+
* Coerce an arbitrary frontmatter scalar to a finite number, or `null` if it
|
|
38
|
+
* is not one. Exported per ADR-3473 §8.6: `state-transition.cts`'s
|
|
39
|
+
* progress-ratchet unmeasured-scan check ("is this derived total a real
|
|
40
|
+
* measurement?") must ask through the SAME coercion this module already uses
|
|
41
|
+
* for `existingProgressExceedsDerived`, rather than growing a second private
|
|
42
|
+
* copy. This matters because frontmatter scalars arrive as STRINGS
|
|
43
|
+
* (`"0"`, not `0`) — a raw `=== 0` test is wrong at both call sites.
|
|
44
|
+
*/
|
|
26
45
|
function toFiniteNumber(value) {
|
|
27
46
|
const number = Number(value);
|
|
28
47
|
return Number.isFinite(number) ? number : null;
|
|
@@ -203,8 +222,180 @@ function locateFieldRow(content, fieldName) {
|
|
|
203
222
|
}
|
|
204
223
|
return null;
|
|
205
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* True only when y/m/d name a date that actually exists on the calendar.
|
|
227
|
+
*
|
|
228
|
+
* `Date.parse` validates shape but not value: it rolls an out-of-range day
|
|
229
|
+
* FORWARD rather than rejecting it (`2026-02-30` -> `2026-03-02`,
|
|
230
|
+
* `2026-04-31` -> `2026-05-01`). Shape-only validation would therefore
|
|
231
|
+
* propagate a different, wrong instant instead of failing safe — precisely
|
|
232
|
+
* what ADR-227 ("validate shape AND value; on failure of either layer coerce
|
|
233
|
+
* to the contract's safe default, never propagate") exists to prevent. A
|
|
234
|
+
* round-trip through Date.UTC detects the rollover: any component the
|
|
235
|
+
* constructor normalised comes back changed.
|
|
236
|
+
*
|
|
237
|
+
* #3696: this predicate previously lived privately inside `smart-entry.cts`,
|
|
238
|
+
* where it gated `parseActivityTimestamp`. `state validate` needed the same
|
|
239
|
+
* answer to assert the `last_activity` invariant (S008), and a second copy is
|
|
240
|
+
* the "generative fix divergence" class outright — two surfaces that disagree
|
|
241
|
+
* about whether a STATE.md is usable is the defect #3696 opens with, so a
|
|
242
|
+
* parity test over two copies would be codifying the bug rather than fixing
|
|
243
|
+
* it. It moves here because this module is already the designated owner of
|
|
244
|
+
* STATE.md field semantics (ADR-3180 §7.7) and `smart-entry.cts` imports no
|
|
245
|
+
* peer that would make the reverse direction a cycle.
|
|
246
|
+
*/
|
|
247
|
+
/**
|
|
248
|
+
* True when a field carries no value a writer ever supplied: absent, blank, or
|
|
249
|
+
* still holding the shipped template's bracket placeholder.
|
|
250
|
+
*
|
|
251
|
+
* `templates/state.md:35` ships `Last activity: [YYYY-MM-DD] — [What happened]`,
|
|
252
|
+
* so EVERY freshly-initialized project has this exact string until something
|
|
253
|
+
* records activity. #3696's first cut only spared the ABSENT form, which made
|
|
254
|
+
* S008 fire on the shipped template itself — caught by the pre-existing
|
|
255
|
+
* "template-equivalent phase identities remain clean without disk drift" test,
|
|
256
|
+
* which is precisely what it is there for.
|
|
257
|
+
*
|
|
258
|
+
* The placeholder test is anchored at the START rather than "contains a bracket
|
|
259
|
+
* anywhere", so a real description that happens to cite one — `2026-08-19 — fixed
|
|
260
|
+
* [#123] parsing` — is still a filled-in value. That keeps the rule from
|
|
261
|
+
* silently swallowing genuine drift.
|
|
262
|
+
*
|
|
263
|
+
* Distinct from `isStateTemplateDefault`, which answers a different question
|
|
264
|
+
* ("may a later handler overwrite this?") and deliberately returns true for a
|
|
265
|
+
* bare ISO date — a perfectly valid value here.
|
|
266
|
+
*/
|
|
267
|
+
function isUnfilledFieldValue(value) {
|
|
268
|
+
if (value === null || value === undefined)
|
|
269
|
+
return true;
|
|
270
|
+
const trimmed = value.trim();
|
|
271
|
+
return trimmed === '' || trimmed.startsWith('[');
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* The `YYYY-MM-DD` prefix of `value`, but only when it names a date that
|
|
275
|
+
* actually exists. `null` for anything else — no leading date token at all, or
|
|
276
|
+
* a token that is shape-valid and calendar-impossible.
|
|
277
|
+
*
|
|
278
|
+
* #3696 review: this is deliberately a LEADING-TOKEN test, not the fully
|
|
279
|
+
* anchored prose grammar `parseProseLastActivityField` uses. That function
|
|
280
|
+
* requires the whole value to be `date` or `date <separator> description`, and
|
|
281
|
+
* returns `{date: <the entire raw string>}` when it does not match — a shape
|
|
282
|
+
* that reads like success. Asserting the S008 invariant through it therefore
|
|
283
|
+
* rejected values the real reader accepts: `smart-entry`'s
|
|
284
|
+
* `parseActivityTimestamp` needs only a leading date and reconstructs the
|
|
285
|
+
* instant even when the suffix carries no dash, so
|
|
286
|
+
* `Last activity: 2026-08-24 Shipped feature X` parses fine there while S008
|
|
287
|
+
* called it unreadable. That is the same two-surfaces-disagree defect #3696
|
|
288
|
+
* exists to close, merely pointing the other way.
|
|
289
|
+
*
|
|
290
|
+
* So the invariant asserted is the one the readers actually share: a leading
|
|
291
|
+
* ISO date token that is a real calendar date.
|
|
292
|
+
*/
|
|
293
|
+
function leadingCalendarDate(value) {
|
|
294
|
+
if (!value)
|
|
295
|
+
return null;
|
|
296
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})(?![\d-])/.exec(value.trim());
|
|
297
|
+
if (!match)
|
|
298
|
+
return null;
|
|
299
|
+
return isRealCalendarDate(Number(match[1]), Number(match[2]), Number(match[3]))
|
|
300
|
+
? `${match[1]}-${match[2]}-${match[3]}`
|
|
301
|
+
: null;
|
|
302
|
+
}
|
|
303
|
+
function isRealCalendarDate(year, month, day) {
|
|
304
|
+
if (month < 1 || month > 12 || day < 1 || day > 31)
|
|
305
|
+
return false;
|
|
306
|
+
const probe = new Date(Date.UTC(year, month - 1, day));
|
|
307
|
+
return (probe.getUTCFullYear() === year &&
|
|
308
|
+
probe.getUTCMonth() === month - 1 &&
|
|
309
|
+
probe.getUTCDate() === day);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Markdown structure that can legitimately follow a single-line field. A line
|
|
313
|
+
* matching any of these is the NEXT construct, never a continuation of the
|
|
314
|
+
* field above it.
|
|
315
|
+
*
|
|
316
|
+
* BREADTH IS THE POINT, and the failure direction is deliberate: a missed
|
|
317
|
+
* truncation costs a diagnostic nobody sees, while a false S009 reports drift on
|
|
318
|
+
* a well-formed STATE.md — a gate that fires on valid documents is worse than no
|
|
319
|
+
* gate. When a shape is ambiguous, it belongs here.
|
|
320
|
+
*
|
|
321
|
+
* #3696 review round 2 added the last three arms after all three were shown to
|
|
322
|
+
* produce false S009 fires on well-formed content: an indented code block, an
|
|
323
|
+
* HTML block, and a setext underline (`===`, which the `[-*_]{3,}` rule does not
|
|
324
|
+
* cover — it only knows `-`, `*` and `_`).
|
|
325
|
+
*/
|
|
326
|
+
const MD_STRUCTURE_LINE_RE = /^(?:#{1,6}\s|\||>|```|~~~|[-*_]{3,}\s*$|=+\s*$|[-*+]\s|\d+[.)]\s|\[[^\]]+\]:|<|(?: {4}|\t))/;
|
|
327
|
+
/**
|
|
328
|
+
* A setext heading's underline — `===` or `---` on its own line. The line ABOVE
|
|
329
|
+
* one of these is a heading TITLE, which is indistinguishable from prose on its
|
|
330
|
+
* own, so the scan must look ahead by one line rather than consume it. Without
|
|
331
|
+
* this, `Last activity: …\nMy Heading\n===` reported "My Heading ===" as dropped
|
|
332
|
+
* continuation text (#3696 review round 2).
|
|
333
|
+
*/
|
|
334
|
+
const SETEXT_UNDERLINE_RE = /^(?:=+|-+)\s*$/;
|
|
335
|
+
const STATE_SIBLING_FIELD_LINE_RE = /^\*{0,2}[A-Za-z][A-Za-z0-9 _-]*\*{0,2}:{1,2}\*{0,2}(?:\s|$)/;
|
|
336
|
+
/**
|
|
337
|
+
* Return the prose that FOLLOWS a single-line field but plainly belongs to it —
|
|
338
|
+
* i.e. the remainder `stateExtractField` silently drops when a writer emits a
|
|
339
|
+
* value long enough to wrap.
|
|
340
|
+
*
|
|
341
|
+
* `stateExtractField`'s `(.+)` is newline-excluding, so
|
|
342
|
+
*
|
|
343
|
+
* Last activity: 2026-08-19 — Project initialized from ingest; PROJECT.md,
|
|
344
|
+
* REQUIREMENTS.md, ROADMAP.md written
|
|
345
|
+
*
|
|
346
|
+
* yields only the first line and the rest is lost with no diagnostic (#3696).
|
|
347
|
+
* `templates/state.md` prescribes a single-line field, so the DOCUMENT is what
|
|
348
|
+
* is wrong here, not the reader — this function exists so `state validate` can
|
|
349
|
+
* SAY so, not so the reader can start guessing at a multi-line grammar the
|
|
350
|
+
* template does not sanction.
|
|
351
|
+
*
|
|
352
|
+
* That is also why the fix is not in `stateExtractField` itself: it has 20
|
|
353
|
+
* direct callers and a CRITICAL blast radius (ADR-3180 §7.7, Rejected #1), and
|
|
354
|
+
* joining continuations there would apply to every field — `Status:` would
|
|
355
|
+
* swallow the line beneath it.
|
|
356
|
+
*
|
|
357
|
+
* Returns `null` when the field is absent, is a pipe-table row (a table cell
|
|
358
|
+
* cannot wrap), or is followed by end-of-file, a blank line, Markdown
|
|
359
|
+
* structure, or a sibling field.
|
|
360
|
+
*/
|
|
361
|
+
function stateFieldContinuation(content, fieldName) {
|
|
362
|
+
const escaped = (0, pattern_cjs_1.escapeRegex)(fieldName);
|
|
363
|
+
// Same two single-line grammars stateExtractField uses, in the same order, so
|
|
364
|
+
// this locates exactly the line whose value it returned. The pipe-table rung
|
|
365
|
+
// is deliberately absent: a `| Field | value |` row is bounded by its closing
|
|
366
|
+
// pipe and cannot wrap.
|
|
367
|
+
const match = new RegExp(`\\*\\*${escaped}:\\*\\*[ \\t]*(.+)`, 'i').exec(content) ??
|
|
368
|
+
new RegExp(`^${escaped}:[ \\t]*(.+)`, 'im').exec(content);
|
|
369
|
+
if (!match)
|
|
370
|
+
return null;
|
|
371
|
+
// `(.+)` stops at the line terminator, so the field's line ends where the
|
|
372
|
+
// match does. JS `.` excludes \r as well as \n, so on a CRLF document the \r
|
|
373
|
+
// sits just AFTER the match rather than inside it — hence the strip below
|
|
374
|
+
// before testing for the newline.
|
|
375
|
+
const afterValue = match.index + match[0].length;
|
|
376
|
+
const rest = content.slice(afterValue).replace(/^\r/, '');
|
|
377
|
+
if (!rest.startsWith('\n'))
|
|
378
|
+
return null; // end of file: nothing follows
|
|
379
|
+
const lines = rest.slice(1).split('\n').map((line) => line.replace(/\r$/, ''));
|
|
380
|
+
const continuation = [];
|
|
381
|
+
for (let i = 0; i < lines.length; i++) {
|
|
382
|
+
const line = lines[i];
|
|
383
|
+
if (!line.trim())
|
|
384
|
+
break;
|
|
385
|
+
if (MD_STRUCTURE_LINE_RE.test(line))
|
|
386
|
+
break;
|
|
387
|
+
if (STATE_SIBLING_FIELD_LINE_RE.test(line))
|
|
388
|
+
break;
|
|
389
|
+
// Look ahead one line: a setext underline below makes THIS line a heading
|
|
390
|
+
// title, so stop before consuming it rather than after.
|
|
391
|
+
if (i + 1 < lines.length && SETEXT_UNDERLINE_RE.test(lines[i + 1]))
|
|
392
|
+
break;
|
|
393
|
+
continuation.push(line.trim());
|
|
394
|
+
}
|
|
395
|
+
return continuation.length ? continuation.join(' ') : null;
|
|
396
|
+
}
|
|
206
397
|
function stateExtractField(content, fieldName) {
|
|
207
|
-
const escaped = escapeRegex(fieldName);
|
|
398
|
+
const escaped = (0, pattern_cjs_1.escapeRegex)(fieldName);
|
|
208
399
|
// Bold inline format: **FieldName:** value
|
|
209
400
|
const boldPattern = new RegExp(`\\*\\*${escaped}:\\*\\*[ \\t]*(.+)`, 'i');
|
|
210
401
|
const boldMatch = content.match(boldPattern);
|
|
@@ -222,8 +413,107 @@ function stateExtractField(content, fieldName) {
|
|
|
222
413
|
return hit.rawValue.trim();
|
|
223
414
|
return null;
|
|
224
415
|
}
|
|
416
|
+
/**
|
|
417
|
+
* Single owner of the #1760 STATE.md field-extraction fallback chain: "read
|
|
418
|
+
* field F, preferring the YAML frontmatter scalar, falling back to the body
|
|
419
|
+
* field." Added for #3187 (epic #3180, ADR-3180 §7.7) to collapse three
|
|
420
|
+
* independent re-derivations of this chain — `src/smart-entry.cts`'s
|
|
421
|
+
* `fmScalar` closure, and `src/state.cts`'s `cmdStateSnapshot` and
|
|
422
|
+
* `cmdStatePrune` — onto one function, per ADR-3180 Decision 1 ("keep N
|
|
423
|
+
* copies with a parity test" is rejected: a parity test proves today's
|
|
424
|
+
* agreement, not that copy N+1 won't happen).
|
|
425
|
+
*
|
|
426
|
+
* Takes ALREADY-PARSED `fm` and `body` rather than raw STATE.md content: the
|
|
427
|
+
* heaviest caller, `cmdStateSnapshot`, reads roughly ten fields off one parse
|
|
428
|
+
* and must not re-parse frontmatter per field.
|
|
429
|
+
*
|
|
430
|
+
* `stateExtractField` (above) is deliberately left untouched — it has 20
|
|
431
|
+
* direct callers and a CRITICAL blast radius (ADR-3180 §7.7's Rejected #1) —
|
|
432
|
+
* so this function is additive: it calls `stateExtractField` rather than
|
|
433
|
+
* replacing it or changing its signature.
|
|
434
|
+
*
|
|
435
|
+
* Fallback ladder (unchanged from every prior copy this replaces):
|
|
436
|
+
* 1. `fm[fmKey]` is a non-empty (post-`.trim()`) string → that trimmed
|
|
437
|
+
* string.
|
|
438
|
+
* 2. `fm[fmKey]` is a `number` or `boolean` → `String(fm[fmKey])`, so `0`
|
|
439
|
+
* and `false` are VALUES, not absence.
|
|
440
|
+
* 3. Anything else (`null`, `undefined`, an object, an array, or an
|
|
441
|
+
* empty/whitespace-only string) → fall through to
|
|
442
|
+
* `stateExtractField(body, bodyField)`.
|
|
443
|
+
*
|
|
444
|
+
* `fmKey === null` skips steps 1–2 outright: for a caller whose chain has no
|
|
445
|
+
* frontmatter side for this particular field (e.g. `state.cts`'s body-only
|
|
446
|
+
* `Last Activity` / `Last activity` case-variant pair, which sits inside a
|
|
447
|
+
* function that DOES own a ladder for its other fields, so per this phase's
|
|
448
|
+
* function-scoped guard it must still route through this owner).
|
|
449
|
+
* `bodyField === null` skips step 3: for a caller whose "no frontmatter
|
|
450
|
+
* value" case falls through to an already-computed value instead of a fresh
|
|
451
|
+
* extractor call (e.g. `cmdStateSnapshot`'s `last_activity`, which falls to
|
|
452
|
+
* its already-parsed prose date rather than re-extracting the body).
|
|
453
|
+
*
|
|
454
|
+
* `scope` reports whether the chain ran over inputs it could actually
|
|
455
|
+
* consult (ADR-3180 Decision 2/§7.7 — mirrors `scanPhasePlans`'s
|
|
456
|
+
* scope-carrying result in `plan-scan.cts`; see `planning-scope.cjs`). This
|
|
457
|
+
* function's own ladder always runs to completion on whatever `fm`/`body` it
|
|
458
|
+
* is given, INCLUDING when the answer is `null` — a genuinely absent field is
|
|
459
|
+
* a real answer, not a failure to look (§7.7 behavior table row 4). So
|
|
460
|
+
* `scope` defaults to `SCOPE.COMPLETE` and is only ever something else when
|
|
461
|
+
* the CALLER passes `opts.scope`, because only the caller knows whether an
|
|
462
|
+
* input it handed in was itself degraded — e.g. `fm` came back `{}` from an
|
|
463
|
+
* unterminated frontmatter fence (`extractFrontmatter` swallows that parse
|
|
464
|
+
* failure), or `body` is an unscoped whole-document fallback because a
|
|
465
|
+
* required `## Current Position` section was not found (#2956). This
|
|
466
|
+
* function never invents a new `SCOPE` member — the enum is frozen at
|
|
467
|
+
* COMPLETE/TRUNCATED/UNSCOPED/UNREADABLE (`planning-scope.cjs`).
|
|
468
|
+
*
|
|
469
|
+
* #1760 is the fallback chain's origin.
|
|
470
|
+
*/
|
|
471
|
+
function stateFieldValue(fm, body, fmKey, bodyField, opts) {
|
|
472
|
+
const v = fmKey === null ? undefined : fm[fmKey];
|
|
473
|
+
let value;
|
|
474
|
+
if (typeof v === 'string' && v.trim()) {
|
|
475
|
+
value = v.trim();
|
|
476
|
+
}
|
|
477
|
+
else if (typeof v === 'number' || typeof v === 'boolean') {
|
|
478
|
+
value = String(v);
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
value = bodyField === null ? null : stateExtractField(body, bodyField);
|
|
482
|
+
}
|
|
483
|
+
return { value, scope: opts?.scope ?? SCOPE.COMPLETE };
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Match the "Current Position" section body from a STATE.md body. #2956: this
|
|
487
|
+
* is the Phase analogue of state.cts's matchSessionSection. `Phase` canonically
|
|
488
|
+
* lives under `## Current Position` (gsd-core/templates/state.md), so — like
|
|
489
|
+
* Stopped At / Paused At under `## Session` — it must be extracted from THAT
|
|
490
|
+
* section, not from the first `Phase:` / `**Phase:**` line anywhere in the
|
|
491
|
+
* body. Without the scope, a historical `Phase:` line in an archive section
|
|
492
|
+
* silently shadows the real one on every read/write, and because callers use
|
|
493
|
+
* this for routing (state.cts's current_phase) and for drift detection
|
|
494
|
+
* (gsd-tools.cjs's `drift-guard phase-status` CLI seam), a stale match either
|
|
495
|
+
* routes work to the wrong phase or fabricates a drift finding.
|
|
496
|
+
*
|
|
497
|
+
* Level-flexible: the canonical template uses an h2 `## Current Position`, the
|
|
498
|
+
* bootstrap template an h3 `### Current Position` (templates/state.md). Both
|
|
499
|
+
* must match — mirroring how matchSessionSection recognises `## Session` and
|
|
500
|
+
* `## Session Continuity`. Exact 'current position' text match (case-
|
|
501
|
+
* insensitive) excludes unrelated headings. Built on the `collectSection`
|
|
502
|
+
* seam, so it inherits that seam's CRLF tolerance (#2444 fix).
|
|
503
|
+
*
|
|
504
|
+
* This is the single owner of the scope — state.cts's private
|
|
505
|
+
* `matchCurrentPositionSection` delegates here rather than duplicating the
|
|
506
|
+
* logic, so the two consumers cannot drift apart.
|
|
507
|
+
*
|
|
508
|
+
* Returns the section body, or null (caller falls back to full-body search).
|
|
509
|
+
*/
|
|
510
|
+
function stateCurrentPositionSlice(body) {
|
|
511
|
+
const isCurrentPosition = (h) => (h.level === 2 || h.level === 3) && h.text.trim().toLowerCase() === 'current position';
|
|
512
|
+
const section = (0, markdown_sectionizer_cjs_1.collectSection)(body, isCurrentPosition, { levelBounded: true });
|
|
513
|
+
return section ? section.body : null;
|
|
514
|
+
}
|
|
225
515
|
function stateReplaceField(content, fieldName, newValue) {
|
|
226
|
-
const escaped = escapeRegex(fieldName);
|
|
516
|
+
const escaped = (0, pattern_cjs_1.escapeRegex)(fieldName);
|
|
227
517
|
// Bold inline format: **FieldName:** value
|
|
228
518
|
const boldPattern = new RegExp(`(\\*\\*${escaped}:\\*\\*\\s*)(.*)`, 'i');
|
|
229
519
|
if (boldPattern.test(content)) {
|
|
@@ -253,6 +543,33 @@ function stateReplaceFieldWithFallback(content, primary, fallback, value) {
|
|
|
253
543
|
}
|
|
254
544
|
return content;
|
|
255
545
|
}
|
|
546
|
+
/**
|
|
547
|
+
* #3374: session-scoped variant of stateReplaceFieldWithFallback for the
|
|
548
|
+
* `## Session` continuity fields. The post-sync harvest (state.cts's
|
|
549
|
+
* matchSessionSection → buildStateFrontmatter) reads these fields ONLY from
|
|
550
|
+
* the session section, so a writer that refreshes one must target the same
|
|
551
|
+
* scope — a whole-body replace lets a decoy `**Stopped at:**` line in an
|
|
552
|
+
* unrelated (e.g. archive) section absorb the refresh while the harvested
|
|
553
|
+
* session value stays stale.
|
|
554
|
+
*
|
|
555
|
+
* Section preference mirrors the reader exactly: the normalized `## Session`
|
|
556
|
+
* block wins over the bootstrap `## Session Continuity` heading when both
|
|
557
|
+
* exist (legacy duplicate files); the continuity heading is only consulted
|
|
558
|
+
* when no canonical `## Session` section exists. `levelBounded` heading
|
|
559
|
+
* matching also excludes `## Session Continuity Archive` (the #2444 scoping).
|
|
560
|
+
*
|
|
561
|
+
* Replace-only (no insertion): returns `content` unchanged when no session
|
|
562
|
+
* section exists or the field is absent from it, so a STATE.md layout without
|
|
563
|
+
* the line keeps its shape and the post-sync preservation pass decides the
|
|
564
|
+
* frontmatter value (see #3374).
|
|
565
|
+
*/
|
|
566
|
+
function stateReplaceFieldInSession(content, primary, fallback, value) {
|
|
567
|
+
const isSession = (h) => h.level === 2 && h.text.trim().toLowerCase() === 'session';
|
|
568
|
+
const isSessionContinuity = (h) => h.level === 2 && h.text.trim().toLowerCase() === 'session continuity';
|
|
569
|
+
const hasCanonicalSession = (0, markdown_sectionizer_cjs_1.collectSection)(content, isSession, { levelBounded: true }) !== null;
|
|
570
|
+
const target = hasCanonicalSession ? isSession : isSessionContinuity;
|
|
571
|
+
return (0, markdown_sectionizer_cjs_1.withSection)(content, target, (sectionBody) => stateReplaceFieldWithFallback(sectionBody, primary, fallback, value));
|
|
572
|
+
}
|
|
256
573
|
function normalizeStateStatus(status, pausedAt) {
|
|
257
574
|
let normalizedStatus = status || 'unknown';
|
|
258
575
|
const statusLower = (status || '').toLowerCase();
|
|
@@ -279,7 +596,20 @@ function normalizeStateStatus(status, pausedAt) {
|
|
|
279
596
|
}
|
|
280
597
|
return normalizedStatus;
|
|
281
598
|
}
|
|
282
|
-
|
|
599
|
+
/**
|
|
600
|
+
* ADR-3180 §7.6 rule 4 (#3217): `scope` is the `listMilestonePhaseDirs`-owner
|
|
601
|
+
* discriminator for the phase/plan set these four counts were derived from.
|
|
602
|
+
* A caller that cannot vouch for `scope === SCOPE.COMPLETE` must pass the
|
|
603
|
+
* scope it actually has — this function refuses to compose a percentage
|
|
604
|
+
* from counts whose scope says they are not a trustworthy answer, returning
|
|
605
|
+
* `null` (never `0`; see the module's already-existing "no data" `null`
|
|
606
|
+
* below, which this generalizes) exactly like its pre-existing "no data"
|
|
607
|
+
* case. `scope` is REQUIRED (no default) so a caller cannot silently opt out
|
|
608
|
+
* of rule 4 by omission.
|
|
609
|
+
*/
|
|
610
|
+
function computeProgressPercent(completedPlans, totalPlans, completedPhases, totalPhases, scope) {
|
|
611
|
+
if (scope !== SCOPE.COMPLETE)
|
|
612
|
+
return null;
|
|
283
613
|
const hasPlanData = totalPlans !== null && totalPlans > 0 && completedPlans !== null;
|
|
284
614
|
const hasPhaseData = totalPhases !== null && totalPhases > 0 && completedPhases !== null;
|
|
285
615
|
if (!hasPlanData && !hasPhaseData)
|
|
@@ -288,7 +618,7 @@ function computeProgressPercent(completedPlans, totalPlans, completedPhases, tot
|
|
|
288
618
|
// cannot track through intermediate boolean variables).
|
|
289
619
|
const planFraction = hasPlanData ? (completedPlans ?? 0) / (totalPlans ?? 1) : 1;
|
|
290
620
|
const phaseFraction = hasPhaseData ? (completedPhases ?? 0) / (totalPhases ?? 1) : 1;
|
|
291
|
-
return
|
|
621
|
+
return (0, phase_lifecycle_cjs_1.clampPercentFromFraction)(Math.min(planFraction, phaseFraction));
|
|
292
622
|
}
|
|
293
623
|
function shouldPreserveExistingProgress(existingProgress, derivedProgress) {
|
|
294
624
|
if (!existingProgress || typeof existingProgress !== 'object')
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* STATE.md Field Schema — the one declaration (ADR-3473 §8.8, issue #3873).
|
|
4
|
+
*
|
|
5
|
+
* Phase 3 substrate. Before this module, "which STATE.md keys exist and what
|
|
6
|
+
* they carry" was declared in THREE hand-maintained places that were already
|
|
7
|
+
* observed to disagree (see the `last_activity` docstring below):
|
|
8
|
+
*
|
|
9
|
+
* - `FIELD_CLASSIFICATION` (`src/state-transition.cts`) — source/preservation/
|
|
10
|
+
* guard/mergeStrategy per frontmatter key (ADR-1769 §4 / ADR-3408).
|
|
11
|
+
* - `FRONTMATTER_BODY_SOURCE` (`src/state-transition.cts`) — which BODY field
|
|
12
|
+
* a frontmatter key derives from.
|
|
13
|
+
* - `FRONTMATTER_KEY_TO_BODY_LABEL` (`src/state.cts`) — the Title-Case label
|
|
14
|
+
* a report speaks a preserved field in (ADR-3408 §8.4/§8.5).
|
|
15
|
+
*
|
|
16
|
+
* This module is the single row-per-key declaration those three now PROJECT
|
|
17
|
+
* from at load time (`state-transition.cts` / `state.cts`), rather than
|
|
18
|
+
* hand-maintaining a fourth copy of the same knowledge. Every exported shape
|
|
19
|
+
* of the three original tables is unchanged — same keys, same key ORDER, same
|
|
20
|
+
* frozen/null-prototype-ness — so every existing consumer (the preservation
|
|
21
|
+
* dispatch loop, `getFieldClassification`, `getPreserveWhenUnchangedFields`,
|
|
22
|
+
* `bodyLabelFor`, and issue #3872's `declaredLeavesOf`) keeps working without
|
|
23
|
+
* an edit. See `.gsd/phase/feat-3873-state-md-schema/40-design.md`.
|
|
24
|
+
*
|
|
25
|
+
* LEAF MODULE, DELIBERATELY. This file imports from neither `state-transition.cts`
|
|
26
|
+
* nor `state.cts` — both of those import THIS module, and either importing
|
|
27
|
+
* back would be the exact CJS require-cycle `src/health-diagnostic-types.cts`'s
|
|
28
|
+
* own docstring describes breaking for the health-diagnostic rule tables
|
|
29
|
+
* (`module.exports` read before it is assigned, so a destructured value comes
|
|
30
|
+
* back `undefined`). `FieldSource` / `FieldPreservation` / `FieldGuard` /
|
|
31
|
+
* `FieldMergeStrategy` therefore live HERE now and are re-exported (by the same
|
|
32
|
+
* name, so no importer of `state-transition.cts` needs to change) from
|
|
33
|
+
* `state-transition.cts`.
|
|
34
|
+
*
|
|
35
|
+
* ADR-457 build-at-publish: source in `src/state-md-schema.cts`, compiled to
|
|
36
|
+
* `gsd-core/bin/lib/state-md-schema.cjs` (gitignored).
|
|
37
|
+
*
|
|
38
|
+
* Design: .gsd/phase/feat-3873-state-md-schema/40-design.md
|
|
39
|
+
* Test matrix: .gsd/phase/feat-3873-state-md-schema/50-test-matrix.md
|
|
40
|
+
*/
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.STATE_FIELD_SCHEMA = exports.STATUS_LIFECYCLE_ENUM = void 0;
|
|
43
|
+
/**
|
|
44
|
+
* The seven CANONICAL values `normalizeStateStatus` (`src/state-document.cts`)
|
|
45
|
+
* maps recognized raw status prose TO — the function's default fallback plus
|
|
46
|
+
* each branch's literal output, in the order the function tests them. This is
|
|
47
|
+
* NOT the raw body prose vocabulary `CONTEXT.md`'s "STATE.md Status Lifecycle
|
|
48
|
+
* (ADR-2207)" entry documents (`Ready to plan` → `All phases complete` →
|
|
49
|
+
* `<version> milestone complete` → `Awaiting next milestone`, plus the
|
|
50
|
+
* handler-authored strings in `KNOWN_TEMPLATE_DEFAULTS['Status']`) — that is
|
|
51
|
+
* free-form prose `normalizeStateStatus` READS.
|
|
52
|
+
*
|
|
53
|
+
* CORRECTED (#3873 phase-3 test-matrix row 26 — verified by executing
|
|
54
|
+
* `normalizeStateStatus`, not by reading this docstring's prior claim):
|
|
55
|
+
* this is NOT a closed set the `status` frontmatter key is restricted to at
|
|
56
|
+
* runtime. `normalizeStateStatus` is deliberately LENIENT: its fallback is
|
|
57
|
+
* `normalizedStatus = status || 'unknown'`, and when none of its
|
|
58
|
+
* substring-match branches recognize the raw input, that fallback — the
|
|
59
|
+
* caller's raw, UNRECOGNIZED prose — is returned unchanged. A status value
|
|
60
|
+
* outside this seven-member set is not rejected, coerced, or normalized; it
|
|
61
|
+
* passes straight through into the frontmatter. `STATUS_LIFECYCLE_ENUM` is
|
|
62
|
+
* therefore the set of values the normalizer maps recognized input ONTO, not
|
|
63
|
+
* a runtime-enforced closed vocabulary for the field.
|
|
64
|
+
*/
|
|
65
|
+
exports.STATUS_LIFECYCLE_ENUM = Object.freeze([
|
|
66
|
+
'unknown',
|
|
67
|
+
'paused',
|
|
68
|
+
'executing',
|
|
69
|
+
'planning',
|
|
70
|
+
'discussing',
|
|
71
|
+
'verifying',
|
|
72
|
+
'completed',
|
|
73
|
+
]);
|
|
74
|
+
// ─── The one declaration ────────────────────────────────────────────────────
|
|
75
|
+
//
|
|
76
|
+
// Row order below is `FIELD_CLASSIFICATION`'s (`src/state-transition.cts`,
|
|
77
|
+
// pre-#3873) ORIGINAL literal order, verified by direct read and preserved
|
|
78
|
+
// deliberately: the `FIELD_CLASSIFICATION` projection built from this table
|
|
79
|
+
// (`state-transition.cts`) walks `Object.keys(STATE_FIELD_SCHEMA)` directly,
|
|
80
|
+
// so this row order IS that projection's key order, and key order is
|
|
81
|
+
// observable (the preservation dispatch loop iterates it). The two other
|
|
82
|
+
// projections (`FRONTMATTER_BODY_SOURCE`, `FRONTMATTER_KEY_TO_BODY_LABEL`) do
|
|
83
|
+
// NOT reuse this same order — their pre-#3873 literals were independently
|
|
84
|
+
// hand-written and already disagreed with each other and with this order (see
|
|
85
|
+
// each projection's own ordering constant in its home module) — so each
|
|
86
|
+
// projection module declares its OWN explicit key-order list rather than
|
|
87
|
+
// re-deriving order from this table's iteration, which would silently change
|
|
88
|
+
// two of the three tables' observable order out from under every consumer.
|
|
89
|
+
exports.STATE_FIELD_SCHEMA = Object.freeze(Object.assign(Object.create(null), {
|
|
90
|
+
// Schema
|
|
91
|
+
gsd_state_version: {
|
|
92
|
+
type: 'string', cardinality: 'one', source: 'free', preservation: 'derive', emitted: 'always',
|
|
93
|
+
},
|
|
94
|
+
// Milestone (external — from ROADMAP.md)
|
|
95
|
+
milestone: {
|
|
96
|
+
type: 'string', cardinality: 'optional', source: 'external', preservation: 'preserve-if-placeholder', emitted: 'when-present',
|
|
97
|
+
},
|
|
98
|
+
milestone_name: {
|
|
99
|
+
type: 'string', cardinality: 'optional', source: 'external', preservation: 'preserve-if-placeholder', emitted: 'when-present',
|
|
100
|
+
},
|
|
101
|
+
// Phase / plan position (body-derived)
|
|
102
|
+
current_phase: {
|
|
103
|
+
type: 'string', cardinality: 'optional', source: 'body', preservation: 'preserve-when-unchanged',
|
|
104
|
+
bodySource: Object.freeze(['Current Phase']), bodyLabel: 'Current Phase', emitted: 'when-present',
|
|
105
|
+
},
|
|
106
|
+
current_phase_name: {
|
|
107
|
+
type: 'string', cardinality: 'optional', source: 'curated', preservation: 'preserve-when-unchanged',
|
|
108
|
+
bodySource: Object.freeze(['Current Phase Name']), bodyLabel: 'Current Phase Name', emitted: 'when-present',
|
|
109
|
+
},
|
|
110
|
+
current_plan: {
|
|
111
|
+
type: 'string', cardinality: 'optional', source: 'body', preservation: 'preserve-when-unchanged',
|
|
112
|
+
bodySource: Object.freeze(['Current Plan']), bodyLabel: 'Current Plan',
|
|
113
|
+
// #3873 phase-3 test-matrix row 25 (verified by executing
|
|
114
|
+
// `advancePlanCore`, `src/state-transition.cts:1306`, not by reading
|
|
115
|
+
// its docstring): TODAY the `Current Plan` body field parses in
|
|
116
|
+
// exactly ONE shape — a bare number `N`, paired with a separate
|
|
117
|
+
// `Total Plans in Phase` field. The hybrid compound `N of M` written
|
|
118
|
+
// directly into `Current Plan` (no `Total Plans in Phase` sibling)
|
|
119
|
+
// does NOT parse: `legacyTotal` is absent, `planField` reads the
|
|
120
|
+
// DIFFERENT `Plan` field (also absent), so the function falls to its
|
|
121
|
+
// NaN/NaN error branch. Feeding `Current Plan: 2 of 5` WITH a
|
|
122
|
+
// `Total Plans in Phase` sibling present does not change this — it
|
|
123
|
+
// "succeeds" only because `parseInt("2 of 5", 10)` truncates to `2`
|
|
124
|
+
// and the sibling supplies the total; the `of 5` half is silently
|
|
125
|
+
// discarded, which is `parseInt` coincidence, not shape recognition.
|
|
126
|
+
// `Plan: N of M` (a DIFFERENT field name) DOES parse the hybrid shape,
|
|
127
|
+
// but `buildStateFrontmatter` never reads `Plan` into `current_plan`
|
|
128
|
+
// (verified: it calls `stateExtractField(bodyContent, 'Current Plan')`
|
|
129
|
+
// only), so that shape is out of scope for this row regardless.
|
|
130
|
+
//
|
|
131
|
+
// #3784 is the open issue for teaching `Current Plan` to read the
|
|
132
|
+
// hybrid shape; **PR #3791** ("fix(#3784): read the hybrid
|
|
133
|
+
// `Current Plan: N of M` shape, keep zero-padding, and name the
|
|
134
|
+
// accepted shapes on failure") is the in-flight fix. Do NOT widen
|
|
135
|
+
// this row speculatively — that would assert a shape the shipped
|
|
136
|
+
// parser does not accept, which is the exact defect class §8.8
|
|
137
|
+
// exists to make impossible. When #3791 merges, `acceptedShapes`
|
|
138
|
+
// MUST widen to `['N', 'N of M']` — until then, the row 23/24/25
|
|
139
|
+
// parser-shape tests (`tests/state-transition.test.cjs`) will go RED
|
|
140
|
+
// the moment the parser changes underneath it. That failure is the
|
|
141
|
+
// forcing function working as designed, not a broken test: it is
|
|
142
|
+
// what stops the schema and the parser from drifting apart silently.
|
|
143
|
+
acceptedShapes: Object.freeze(['N']),
|
|
144
|
+
emitted: 'when-present',
|
|
145
|
+
},
|
|
146
|
+
// Status / lifecycle (body-derived; #1230 delta heuristic applies)
|
|
147
|
+
// guard: the 'unknown' sentinel is the ONLY true executor-side guard in
|
|
148
|
+
// this table (stopped_at's `## Session` scoping is caller-side delta
|
|
149
|
+
// extraction, not an executor condition) — ADR-3408 Decision 1.
|
|
150
|
+
status: {
|
|
151
|
+
type: 'string', enum: exports.STATUS_LIFECYCLE_ENUM, cardinality: 'one', source: 'body', preservation: 'preserve-when-unchanged',
|
|
152
|
+
guard: 'non-sentinel-unknown', bodySource: Object.freeze(['Status']), bodyLabel: 'Status', emitted: 'always',
|
|
153
|
+
},
|
|
154
|
+
stopped_at: {
|
|
155
|
+
type: 'string', cardinality: 'optional', source: 'body', preservation: 'preserve-when-unchanged',
|
|
156
|
+
bodySource: Object.freeze(['Stopped At', 'Stopped at']), bodyLabel: 'Stopped At', emitted: 'when-present',
|
|
157
|
+
},
|
|
158
|
+
paused_at: {
|
|
159
|
+
type: 'string', cardinality: 'optional', source: 'body', preservation: 'preserve-when-unchanged',
|
|
160
|
+
bodySource: Object.freeze(['Paused At']), bodyLabel: 'Paused At', emitted: 'when-present',
|
|
161
|
+
},
|
|
162
|
+
// Activity log
|
|
163
|
+
last_updated: {
|
|
164
|
+
type: 'string', cardinality: 'one', source: 'free', preservation: 'derive', emitted: 'always',
|
|
165
|
+
}, // realClock.nowIso()
|
|
166
|
+
// #3873: THE LIVE DISAGREEMENT. Pre-schema, `FRONTMATTER_BODY_SOURCE`
|
|
167
|
+
// carried this key (`last_activity: ['Last Activity', 'Last activity']`)
|
|
168
|
+
// while `FRONTMATTER_KEY_TO_BODY_LABEL` did NOT — same field, two
|
|
169
|
+
// tables, two different answers to "does this key have a reportable
|
|
170
|
+
// body label". Resolved by DECLARATION, not by picking whichever table
|
|
171
|
+
// "looks right": `bodySource` is present below (this key IS derived from
|
|
172
|
+
// a body field and `buildStateFrontmatter` — `src/state.cts` — reads it
|
|
173
|
+
// via that exact two-case-variant fallback), and `bodyLabel` is
|
|
174
|
+
// deliberately ABSENT, because that is what ships TODAY —
|
|
175
|
+
// `last_activity`'s `preservation` is `'derive'`, never
|
|
176
|
+
// `'preserve-when-unchanged'`, so it can never reach `bodyLabelFor`'s
|
|
177
|
+
// (`src/state.cts`) `STATE_BODY_LABEL_UNWIRED_ROW` throw in the first
|
|
178
|
+
// place; the absent label is inert, not a latent bug. Pinned by
|
|
179
|
+
// `tests/state.test.cjs`'s pre-existing
|
|
180
|
+
// `lastActivityLabelResolutionMatchesShippedBehavior`. Do NOT "tidy"
|
|
181
|
+
// this by adding a label — that would be shipping a policy change
|
|
182
|
+
// disguised as a consolidation, exactly the #3427 failure this epic is
|
|
183
|
+
// named after.
|
|
184
|
+
last_activity: {
|
|
185
|
+
type: 'string', cardinality: 'optional', source: 'body', preservation: 'derive',
|
|
186
|
+
bodySource: Object.freeze(['Last Activity', 'Last activity']), emitted: 'when-present',
|
|
187
|
+
}, // always refresh on transition
|
|
188
|
+
last_activity_desc: {
|
|
189
|
+
type: 'string', cardinality: 'optional', source: 'body', preservation: 'preserve-when-unchanged',
|
|
190
|
+
bodySource: Object.freeze(['Last Activity Description']), bodyLabel: 'Last Activity Description', emitted: 'when-present',
|
|
191
|
+
},
|
|
192
|
+
// Commit provenance (#2573) — ambient git read, recomputed on every write,
|
|
193
|
+
// exactly like last_updated. Never preserved: a stale stamp would claim
|
|
194
|
+
// STATE.md was written against a commit it wasn't.
|
|
195
|
+
state_head: {
|
|
196
|
+
type: 'string', cardinality: 'optional', source: 'free', preservation: 'derive', emitted: 'when-present',
|
|
197
|
+
}, // #2573
|
|
198
|
+
// Progress block (disk-derived, except the curated progress ratchet)
|
|
199
|
+
// mergeStrategy: 'progress-ratchet' — completed_plans/completed_phases
|
|
200
|
+
// only ever ratchet UP toward the derived value (#2969); everything
|
|
201
|
+
// else in the merge is either always-derived (#2440) or always-curated.
|
|
202
|
+
progress: {
|
|
203
|
+
type: 'object', cardinality: 'optional', source: 'curated', preservation: 'preserve-always',
|
|
204
|
+
mergeStrategy: 'progress-ratchet', emitted: 'when-present',
|
|
205
|
+
}, // #3242, #1446
|
|
206
|
+
'progress.total_phases': {
|
|
207
|
+
type: 'number', cardinality: 'optional', source: 'disk', preservation: 'derive', emitted: 'when-present',
|
|
208
|
+
},
|
|
209
|
+
'progress.completed_phases': {
|
|
210
|
+
type: 'number', cardinality: 'optional', source: 'disk', preservation: 'derive', emitted: 'when-present',
|
|
211
|
+
},
|
|
212
|
+
'progress.total_plans': {
|
|
213
|
+
type: 'number', cardinality: 'optional', source: 'disk', preservation: 'derive', emitted: 'when-present',
|
|
214
|
+
},
|
|
215
|
+
'progress.completed_plans': {
|
|
216
|
+
type: 'number', cardinality: 'optional', source: 'disk', preservation: 'derive', emitted: 'when-present',
|
|
217
|
+
},
|
|
218
|
+
'progress.percent': {
|
|
219
|
+
type: 'number', cardinality: 'optional', source: 'disk', preservation: 'derive', emitted: 'when-present',
|
|
220
|
+
},
|
|
221
|
+
}));
|