@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
|
@@ -114,6 +114,68 @@ function buildSummaryFileIndex(summaryFiles, extractCanonicalPlanId) {
|
|
|
114
114
|
}
|
|
115
115
|
return index;
|
|
116
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* #3345: the one place "does this SUMMARY status value mean blocked" is
|
|
119
|
+
* decided, sibling to `isHaltedStatus` above. A SUMMARY declaring
|
|
120
|
+
* `status: blocked` records a plan that could NOT finish — it is a failure
|
|
121
|
+
* record, not a completion record — so it must not count toward
|
|
122
|
+
* `completed_plans` (scanPhasePlans's summaryCount) nor read as
|
|
123
|
+
* `has_summary: true` in phase-plan-index's `incomplete` construction.
|
|
124
|
+
*
|
|
125
|
+
* `halted` is deliberately NOT matched here: a designed stop still writes a
|
|
126
|
+
* completion record (#2830's model — its dependents get `blocked_by` halt
|
|
127
|
+
* propagation), so a `status: halted` SUMMARY keeps counting as summarized.
|
|
128
|
+
* Case-insensitive, trims whitespace, and strips an unquoted trailing YAML
|
|
129
|
+
* comment exactly like `isHaltedStatus` (same #2830 review defect-2 rule).
|
|
130
|
+
*/
|
|
131
|
+
function isBlockedStatus(status) {
|
|
132
|
+
if (typeof status !== 'string')
|
|
133
|
+
return false;
|
|
134
|
+
const withoutTrailingComment = status.replace(/\s+#.*$/, '');
|
|
135
|
+
return withoutTrailingComment.trim().toLowerCase() === 'blocked';
|
|
136
|
+
}
|
|
137
|
+
// #3345: SUMMARY frontmatter sits at byte 0 and closes well before the body,
|
|
138
|
+
// so only a bounded prefix is ever needed to read the `status` marker — the
|
|
139
|
+
// same cap discipline as plan-scan.cts's PLAN_FRONTMATTER_READ_CAP (#2349),
|
|
140
|
+
// kept here because this predicate's primary caller (scanPhasePlans) loops
|
|
141
|
+
// over every phase directory on hot paths (state sync, roadmap progress).
|
|
142
|
+
const SUMMARY_FRONTMATTER_READ_CAP = 64 * 1024;
|
|
143
|
+
/**
|
|
144
|
+
* #3345: read a SUMMARY file's frontmatter `status` (bounded-prefix read) and
|
|
145
|
+
* report whether it declares `status: blocked`. Returns false — never throws —
|
|
146
|
+
* on a missing/unreadable/malformed/non-regular SUMMARY, so an unreadable file
|
|
147
|
+
* degrades to the pre-#3345 filename-existence behaviour rather than breaking
|
|
148
|
+
* either caller. Fail-open by design, mirroring `isPlanSuperseded`'s posture.
|
|
149
|
+
*
|
|
150
|
+
* Callers: plan-scan.cts's `scanPhasePlans` (the count side) and phase.cts's
|
|
151
|
+
* `cmdPhasePlanIndex` (the read side) BOTH filter their summary lists through
|
|
152
|
+
* this one predicate, so the count and the `incomplete` list can never
|
|
153
|
+
* re-diverge on the blocked rule.
|
|
154
|
+
*/
|
|
155
|
+
function isSummaryFileBlocked(summaryPath) {
|
|
156
|
+
let content;
|
|
157
|
+
try {
|
|
158
|
+
const st = node_fs_1.default.statSync(summaryPath); // follows symlinks → resolves to the target's real type
|
|
159
|
+
if (!st.isFile())
|
|
160
|
+
return false;
|
|
161
|
+
const length = Math.min(st.size, SUMMARY_FRONTMATTER_READ_CAP);
|
|
162
|
+
if (length === 0)
|
|
163
|
+
return false;
|
|
164
|
+
const fd = node_fs_1.default.openSync(summaryPath, 'r');
|
|
165
|
+
try {
|
|
166
|
+
const buf = Buffer.allocUnsafe(length);
|
|
167
|
+
const bytesRead = node_fs_1.default.readSync(fd, buf, 0, length, 0);
|
|
168
|
+
content = buf.toString('utf8', 0, bytesRead);
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
node_fs_1.default.closeSync(fd);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
return isBlockedStatus(extractFrontmatter(content, summaryPath)['status']);
|
|
178
|
+
}
|
|
117
179
|
/**
|
|
118
180
|
* Computes halt-propagation over a plan dependency DAG.
|
|
119
181
|
*
|
|
@@ -229,4 +291,13 @@ function computeHaltPropagation(nodes, precomputedOrder) {
|
|
|
229
291
|
}
|
|
230
292
|
return { order, visited, blockedBy };
|
|
231
293
|
}
|
|
232
|
-
module.exports = {
|
|
294
|
+
module.exports = {
|
|
295
|
+
computeHaltPropagation,
|
|
296
|
+
isHaltedStatus,
|
|
297
|
+
buildSummaryFileIndex,
|
|
298
|
+
isSummaryFileHalted,
|
|
299
|
+
// #3345: blocked-SUMMARY detection, shared by the count side (scanPhasePlans)
|
|
300
|
+
// and the read side (phase-plan-index) so the two cannot diverge.
|
|
301
|
+
isBlockedStatus,
|
|
302
|
+
isSummaryFileBlocked,
|
|
303
|
+
};
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Plan Document Module — the single parser for a `*-PLAN.md` document BODY.
|
|
4
|
+
*
|
|
5
|
+
* Owns: objective extraction, the task-block grammar (`<task>` elements, with
|
|
6
|
+
* the legacy `## Task N` heading fallback — including the optional `tracker-id`
|
|
7
|
+
* attribute, ADR-3646 Phase 1, read verbatim and never split here), planned-file
|
|
8
|
+
* extraction, and the frontmatter-derived scheduling metadata (`wave`,
|
|
9
|
+
* `depends_on`, `autonomous`, `agent_hint`, `files_modified`).
|
|
10
|
+
*
|
|
11
|
+
* WHY THIS IS A LEAF MODULE. This logic was written inline inside
|
|
12
|
+
* `cmdPhasePlanIndex` (`src/phase.cts`). Two commands in two different families
|
|
13
|
+
* now need it — `phase.plan-index` and `planning.inspect` (#2790) — so leaving
|
|
14
|
+
* it in `phase.cts` would force `planning` to depend on `phase`, and copying it
|
|
15
|
+
* would be the *Generative Fix Divergence* class `CLAUDE.md` names. A leaf owned
|
|
16
|
+
* by neither family is the seam that matches the actual usage (Conway's Law).
|
|
17
|
+
*
|
|
18
|
+
* NOT an ADR-3180 §7 derivation. §6 puts the document-parsing layer explicitly
|
|
19
|
+
* out of that epic's scope (#2143); this module answers "what does this plan
|
|
20
|
+
* document say", never "how many plans are outstanding" (that is
|
|
21
|
+
* `scanPhasePlans`, §7.5) or "is this phase complete" (`isPhaseComplete`, §7.4).
|
|
22
|
+
*
|
|
23
|
+
* BEHAVIOUR IS PRESERVED BYTE-FOR-BEHAVIOUR from the prior inline code. In
|
|
24
|
+
* particular `tasks.length` is exactly the legacy `taskCount`
|
|
25
|
+
* (`xmlTasks.length || mdTasks.length`), including its known fence-blindness —
|
|
26
|
+
* a `## Task 1` inside a fenced code block still counts, exactly as it does
|
|
27
|
+
* today. That is a characterised limit, not an endorsement: changing it would
|
|
28
|
+
* silently change `phase.plan-index`'s output for existing projects, which is a
|
|
29
|
+
* Hyrum's-Law break that belongs in its own issue rather than riding along on a
|
|
30
|
+
* read-only query addition.
|
|
31
|
+
*
|
|
32
|
+
* ADR-457 build-at-publish: source in src/plan-document.cts, compiled to
|
|
33
|
+
* gsd-core/bin/lib/plan-document.cjs (gitignored).
|
|
34
|
+
*/
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
36
|
+
const frontmatterMod = require("./frontmatter.cjs");
|
|
37
|
+
const { extractFrontmatter } = frontmatterMod;
|
|
38
|
+
// ─── Frozen vocabularies ──────────────────────────────────────────────────────
|
|
39
|
+
/**
|
|
40
|
+
* How a task row was expressed in the document. `auto` is the ordinary
|
|
41
|
+
* executable task; `checkpoint` is a `<task type="checkpoint:*">` block, which
|
|
42
|
+
* carries an entirely different element set (`<decision>`/`<what-built>`, no
|
|
43
|
+
* `<name>`/`<files>`/`<acceptance_criteria>`). Distinguishing them is what
|
|
44
|
+
* stops a checkpoint from being reported as a malformed auto task.
|
|
45
|
+
*/
|
|
46
|
+
const TASK_KIND = Object.freeze({
|
|
47
|
+
AUTO: 'auto',
|
|
48
|
+
CHECKPOINT: 'checkpoint',
|
|
49
|
+
});
|
|
50
|
+
// ─── Task grammar ─────────────────────────────────────────────────────────────
|
|
51
|
+
// The legacy counting rule, preserved verbatim from cmdPhasePlanIndex. `g` is
|
|
52
|
+
// required (we count every occurrence) and these are rebuilt per call rather
|
|
53
|
+
// than hoisted to module scope: a global regex carries mutable `lastIndex`
|
|
54
|
+
// state, and a shared instance is a cross-call contamination bug.
|
|
55
|
+
function xmlTaskOpenings(content) {
|
|
56
|
+
return [...content.matchAll(/<task(?=[\s>])[^>]*>/gi)];
|
|
57
|
+
}
|
|
58
|
+
function markdownTaskHeadings(content) {
|
|
59
|
+
return [...content.matchAll(/##\s*Task\s*\d+[^\n]*/gi)];
|
|
60
|
+
}
|
|
61
|
+
/** Extract the value of one attribute from a `<task ...>` opening tag. */
|
|
62
|
+
function tagAttribute(openTag, attr) {
|
|
63
|
+
const re = new RegExp(`\\b${attr}\\s*=\\s*"([^"]*)"|\\b${attr}\\s*=\\s*'([^']*)'`, 'i');
|
|
64
|
+
const m = re.exec(openTag);
|
|
65
|
+
if (!m)
|
|
66
|
+
return null;
|
|
67
|
+
const value = (m[1] ?? m[2] ?? '').trim();
|
|
68
|
+
return value.length > 0 ? value : null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Body of the first `<tag>…</tag>` inside `block`, or null. Non-greedy and
|
|
72
|
+
* case-insensitive; a tag that is opened but never closed yields null rather
|
|
73
|
+
* than swallowing the rest of the document.
|
|
74
|
+
*/
|
|
75
|
+
function elementBody(block, tag) {
|
|
76
|
+
const re = new RegExp(`<${tag}\\s*>([\\s\\S]*?)</${tag}\\s*>`, 'i');
|
|
77
|
+
const m = re.exec(block);
|
|
78
|
+
return m ? m[1] : null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Split a `<files>` body into paths. Comma-separated per the shipped
|
|
82
|
+
* `templates/phase-prompt.md` grammar; newline-separated forms are tolerated
|
|
83
|
+
* too (Postel — liberal in what we accept), and the caller records nothing
|
|
84
|
+
* special for them because a path list is a path list either way.
|
|
85
|
+
*/
|
|
86
|
+
function splitFileList(body) {
|
|
87
|
+
if (body === null)
|
|
88
|
+
return [];
|
|
89
|
+
return body
|
|
90
|
+
.split(/[,\n]/)
|
|
91
|
+
.map((part) => part.trim())
|
|
92
|
+
.filter((part) => part.length > 0);
|
|
93
|
+
}
|
|
94
|
+
/** `<acceptance_criteria>` carries `- ` bullets, one criterion per line. */
|
|
95
|
+
function splitCriteria(body) {
|
|
96
|
+
if (body === null)
|
|
97
|
+
return [];
|
|
98
|
+
return body
|
|
99
|
+
.split(/\r?\n/)
|
|
100
|
+
.map((line) => line.trim())
|
|
101
|
+
.filter((line) => line.length > 0)
|
|
102
|
+
.map((line) => line.replace(/^[-*]\s*/, ''))
|
|
103
|
+
.filter((line) => line.length > 0);
|
|
104
|
+
}
|
|
105
|
+
function collapseWhitespace(value) {
|
|
106
|
+
if (value === null)
|
|
107
|
+
return null;
|
|
108
|
+
const trimmed = value.replace(/\s+/g, ' ').trim();
|
|
109
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Parse the `<task>` blocks. Each opening tag found by `xmlTaskOpenings` yields
|
|
113
|
+
* exactly one row — the block runs from that tag to its `</task>`, or to the
|
|
114
|
+
* next opening tag, or to end-of-document. Bounding on the NEXT OPENING rather
|
|
115
|
+
* than only on `</task>` is what keeps an unclosed block from consuming its
|
|
116
|
+
* siblings, so the row count still equals the opening count.
|
|
117
|
+
*/
|
|
118
|
+
function parseXmlTasks(content) {
|
|
119
|
+
const openings = xmlTaskOpenings(content);
|
|
120
|
+
return openings.map((match, i) => {
|
|
121
|
+
const start = match.index ?? 0;
|
|
122
|
+
const openTag = match[0];
|
|
123
|
+
const nextStart = i + 1 < openings.length ? (openings[i + 1].index ?? content.length) : content.length;
|
|
124
|
+
const window = content.slice(start, nextStart);
|
|
125
|
+
const closeIdx = window.search(/<\/task\s*>/i);
|
|
126
|
+
const block = closeIdx === -1 ? window : window.slice(0, closeIdx);
|
|
127
|
+
const type = tagAttribute(openTag, 'type');
|
|
128
|
+
const kind = type !== null && type.toLowerCase().startsWith('checkpoint')
|
|
129
|
+
? TASK_KIND.CHECKPOINT
|
|
130
|
+
: TASK_KIND.AUTO;
|
|
131
|
+
// A checkpoint block has no <name>/<files>/<acceptance_criteria>/<done> in
|
|
132
|
+
// the shipped grammar. Reading them anyway would be harmless but dishonest:
|
|
133
|
+
// the caller must be able to tell "this element is absent because this kind
|
|
134
|
+
// of task has no such element" from "this element is missing and should not
|
|
135
|
+
// be".
|
|
136
|
+
if (kind === TASK_KIND.CHECKPOINT) {
|
|
137
|
+
return {
|
|
138
|
+
index: i + 1,
|
|
139
|
+
kind,
|
|
140
|
+
type,
|
|
141
|
+
name: null,
|
|
142
|
+
plannedFiles: [],
|
|
143
|
+
acceptanceCriteria: [],
|
|
144
|
+
done: null,
|
|
145
|
+
trackerId: null,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
index: i + 1,
|
|
150
|
+
kind,
|
|
151
|
+
type,
|
|
152
|
+
name: collapseWhitespace(elementBody(block, 'name')),
|
|
153
|
+
plannedFiles: splitFileList(elementBody(block, 'files')),
|
|
154
|
+
acceptanceCriteria: splitCriteria(elementBody(block, 'acceptance_criteria')),
|
|
155
|
+
done: collapseWhitespace(elementBody(block, 'done')),
|
|
156
|
+
trackerId: tagAttribute(openTag, 'tracker-id'),
|
|
157
|
+
};
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Legacy fallback: `## Task N` headings, used ONLY when the document carries no
|
|
162
|
+
* `<task>` blocks at all. Deliberately fence-blind, matching the counting rule
|
|
163
|
+
* `cmdPhasePlanIndex` has always used — see this module's header comment.
|
|
164
|
+
*/
|
|
165
|
+
function parseMarkdownTasks(content) {
|
|
166
|
+
return markdownTaskHeadings(content).map((match, i) => ({
|
|
167
|
+
index: i + 1,
|
|
168
|
+
kind: TASK_KIND.AUTO,
|
|
169
|
+
type: null,
|
|
170
|
+
name: collapseWhitespace(match[0].replace(/^##\s*/, '')),
|
|
171
|
+
plannedFiles: [],
|
|
172
|
+
acceptanceCriteria: [],
|
|
173
|
+
done: null,
|
|
174
|
+
trackerId: null,
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
// ─── Objective ────────────────────────────────────────────────────────────────
|
|
178
|
+
/**
|
|
179
|
+
* Preserved verbatim from `cmdPhasePlanIndex`: the first line following an
|
|
180
|
+
* `<objective>` tag. Deliberately NOT widened to the full element body — that
|
|
181
|
+
* would change `phase.plan-index`'s existing output for any multi-line
|
|
182
|
+
* objective.
|
|
183
|
+
*/
|
|
184
|
+
function extractObjective(content) {
|
|
185
|
+
const m = content.match(/<objective>\s*\n?\s*(.+)/);
|
|
186
|
+
return m ? m[1].trim() : null;
|
|
187
|
+
}
|
|
188
|
+
// ─── Entry point ──────────────────────────────────────────────────────────────
|
|
189
|
+
/**
|
|
190
|
+
* The plan id for a plan FILE ENTRY, exactly as `scanPhasePlans` stores it
|
|
191
|
+
* (root entries bare, nested entries `plans/`-prefixed).
|
|
192
|
+
*
|
|
193
|
+
* This is the established derivation from `cmdPhasePlanIndex`, moved here
|
|
194
|
+
* VERBATIM (#2790) so `phase.plan-index` and `planning.inspect` cannot report
|
|
195
|
+
* different ids for the same plan — a consumer correlating the two surfaces
|
|
196
|
+
* needs them to join. Deliberately NOT "improved": it is a display/lookup key
|
|
197
|
+
* with existing callers, and changing what it returns would be a Hyrum's-Law
|
|
198
|
+
* break on `phase-plan-index`.
|
|
199
|
+
*/
|
|
200
|
+
function planIdFromFile(planFile) {
|
|
201
|
+
return planFile.replace('-PLAN.md', '').replace('PLAN.md', '');
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Parse one plan document.
|
|
205
|
+
*
|
|
206
|
+
* @param content Raw `*-PLAN.md` text.
|
|
207
|
+
* @param planPath Optional path, used only to name the file in `extractFrontmatter`'s
|
|
208
|
+
* truncated-frontmatter diagnostic (#1882). Callers that do not have
|
|
209
|
+
* one omit it — this default IS the shape production uses from the
|
|
210
|
+
* read-only query path.
|
|
211
|
+
*/
|
|
212
|
+
function parsePlanDocument(content, planPath = '') {
|
|
213
|
+
const fm = extractFrontmatter(content, planPath);
|
|
214
|
+
const xmlTasks = parseXmlTasks(content);
|
|
215
|
+
const tasks = xmlTasks.length > 0 ? xmlTasks : parseMarkdownTasks(content);
|
|
216
|
+
const parsedWave = parseInt(fm['wave'], 10);
|
|
217
|
+
const declaredWave = Number.isNaN(parsedWave) ? null : parsedWave;
|
|
218
|
+
let dependsOn = [];
|
|
219
|
+
const fmDeps = fm['depends_on'];
|
|
220
|
+
if (Array.isArray(fmDeps)) {
|
|
221
|
+
dependsOn = fmDeps.map(String);
|
|
222
|
+
}
|
|
223
|
+
else if (typeof fmDeps === 'string' && fmDeps.trim() !== '') {
|
|
224
|
+
dependsOn = [fmDeps];
|
|
225
|
+
}
|
|
226
|
+
let autonomous = true;
|
|
227
|
+
if (fm['autonomous'] !== undefined) {
|
|
228
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- FrontmatterValue comparison
|
|
229
|
+
autonomous = fm['autonomous'] === 'true' || String(fm['autonomous']) === 'true';
|
|
230
|
+
}
|
|
231
|
+
let filesModified = [];
|
|
232
|
+
const fmFiles = fm['files_modified'] || fm['files-modified'];
|
|
233
|
+
if (fmFiles) {
|
|
234
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- FrontmatterValue scalar-to-string
|
|
235
|
+
filesModified = Array.isArray(fmFiles) ? fmFiles.map(String) : [String(fmFiles)];
|
|
236
|
+
}
|
|
237
|
+
let filesDeleted = [];
|
|
238
|
+
const fmDeleted = fm['files_deleted'] || fm['files-deleted'];
|
|
239
|
+
if (fmDeleted) {
|
|
240
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- FrontmatterValue scalar-to-string
|
|
241
|
+
filesDeleted = Array.isArray(fmDeleted) ? fmDeleted.map(String) : [String(fmDeleted)];
|
|
242
|
+
}
|
|
243
|
+
let agentHint = null;
|
|
244
|
+
const fmAgentHint = fm['agent_hint'];
|
|
245
|
+
if (fmAgentHint !== undefined) {
|
|
246
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- FrontmatterValue scalar-to-string
|
|
247
|
+
const hintStr = String(fmAgentHint).trim();
|
|
248
|
+
agentHint = hintStr !== '' ? hintStr : null;
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
objective: extractObjective(content) || fm['objective'] || null,
|
|
252
|
+
declaredWave,
|
|
253
|
+
dependsOn,
|
|
254
|
+
autonomous,
|
|
255
|
+
agentHint,
|
|
256
|
+
filesModified,
|
|
257
|
+
filesDeleted,
|
|
258
|
+
tasks,
|
|
259
|
+
taskCount: tasks.length,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
const planDocument = { TASK_KIND, parsePlanDocument, planIdFromFile };
|
|
263
|
+
module.exports = planDocument;
|
|
@@ -20,6 +20,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
20
20
|
exports.AUTHORITY_RUNGS = void 0;
|
|
21
21
|
exports.getEffectiveAuthority = getEffectiveAuthority;
|
|
22
22
|
exports.classifyDriftSeverity = classifyDriftSeverity;
|
|
23
|
+
exports.comparePhaseStatus = comparePhaseStatus;
|
|
23
24
|
/**
|
|
24
25
|
* Frozen map from authority name to its rung number.
|
|
25
26
|
*
|
|
@@ -115,3 +116,122 @@ function classifyDriftSeverity({ status, authority, }) {
|
|
|
115
116
|
return { severity: 'INFO', hardBlock: false };
|
|
116
117
|
}
|
|
117
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Frozen map from lowercased phase-status text to a shared ordinal rank,
|
|
121
|
+
* covering the union of the STATE.md "Current Position" vocabulary
|
|
122
|
+
* (gsd-core/templates/state.md) and the FULL ROADMAP.md "## Progress" table
|
|
123
|
+
* Status column vocabulary declared by gsd-core/templates/roadmap.md:133 —
|
|
124
|
+
* `Not started | In progress | Complete | Deferred`.
|
|
125
|
+
*
|
|
126
|
+
* The two vocabularies overlap on 'in progress', which is rank 1 in both —
|
|
127
|
+
* no conflict. 'deferred' is rank 0 (no work done) — see comparePhaseStatus's
|
|
128
|
+
* doc comment for how a deferred/non-rank-0 mismatch is classified; it is NOT
|
|
129
|
+
* simply numeric distance from rank 0 like an ordinary lag.
|
|
130
|
+
*/
|
|
131
|
+
const PHASE_STATUS_RANKS = Object.freeze({
|
|
132
|
+
// STATE.md "Current Position" vocabulary
|
|
133
|
+
'ready to plan': 0,
|
|
134
|
+
'planning': 0,
|
|
135
|
+
'ready to execute': 1,
|
|
136
|
+
'in progress': 1,
|
|
137
|
+
'phase complete': 2,
|
|
138
|
+
// ROADMAP.md "## Progress" table Status column vocabulary
|
|
139
|
+
'not started': 0,
|
|
140
|
+
'complete': 2,
|
|
141
|
+
'deferred': 0,
|
|
142
|
+
});
|
|
143
|
+
/** Rank at which a status asserts work is DONE (terminal, not comparative). */
|
|
144
|
+
const TERMINAL_RANK = 2;
|
|
145
|
+
/**
|
|
146
|
+
* Normalize a raw phase-status string for lookup/comparison: trims
|
|
147
|
+
* surrounding whitespace and lowercases. Returns null for missing/empty
|
|
148
|
+
* values. Single owner of this normalization so `resolvePhaseStatusRank` and
|
|
149
|
+
* the 'deferred' declared-intent check in `comparePhaseStatus` cannot drift
|
|
150
|
+
* apart on what counts as "empty".
|
|
151
|
+
*/
|
|
152
|
+
function normalizePhaseStatusText(value) {
|
|
153
|
+
if (value === null || value === undefined)
|
|
154
|
+
return null;
|
|
155
|
+
const normalized = value.trim().toLowerCase();
|
|
156
|
+
return normalized === '' ? null : normalized;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Resolve a raw phase-status string to its shared ordinal rank, or null when
|
|
160
|
+
* the value is missing/empty/unrecognized. Case-insensitive, trims
|
|
161
|
+
* surrounding whitespace.
|
|
162
|
+
*
|
|
163
|
+
* @param value - raw status text from STATE.md or ROADMAP.md
|
|
164
|
+
* @returns the resolved rank, or null if unresolvable
|
|
165
|
+
*/
|
|
166
|
+
function resolvePhaseStatusRank(value) {
|
|
167
|
+
const normalized = normalizePhaseStatusText(value);
|
|
168
|
+
if (normalized === null)
|
|
169
|
+
return null;
|
|
170
|
+
if (!Object.prototype.hasOwnProperty.call(PHASE_STATUS_RANKS, normalized))
|
|
171
|
+
return null;
|
|
172
|
+
return PHASE_STATUS_RANKS[normalized];
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Compare a phase's status as reported by STATE.md against the same phase's
|
|
176
|
+
* status as reported by ROADMAP.md's "## Progress" table, and classify the
|
|
177
|
+
* result.
|
|
178
|
+
*
|
|
179
|
+
* Unlike classifyDriftSeverity, this never throws for an unrecognized
|
|
180
|
+
* status: the inputs are user document text (prose a human or agent typed
|
|
181
|
+
* into STATE.md/ROADMAP.md), not config, so an unrecognized value is data —
|
|
182
|
+
* surfaced as 'uncheckable' — not a programming error.
|
|
183
|
+
*
|
|
184
|
+
* Rank 2 ('phase complete' / 'Complete') is TERMINAL: it asserts the work is
|
|
185
|
+
* DONE. If exactly one side reports rank 2 and the other does not, that is
|
|
186
|
+
* always 'drifted', regardless of numeric distance — a document claiming
|
|
187
|
+
* "done" while another claims "still going" is a direct contradiction, not
|
|
188
|
+
* mere lag. This is the issue's canonical example: complete in STATE.md but
|
|
189
|
+
* in progress in ROADMAP.md.
|
|
190
|
+
*
|
|
191
|
+
* 'Deferred' is a second declared-intent rank-0 status (gsd-core/templates/
|
|
192
|
+
* roadmap.md:133's full vocabulary: `Not started | In progress | Complete |
|
|
193
|
+
* Deferred`) that is NOT ordinary lag from rank 0: it is an explicit
|
|
194
|
+
* decision to STOP work, not merely "hasn't started yet". If exactly one
|
|
195
|
+
* side declares 'deferred' and the other side's rank is >= 1 (work is
|
|
196
|
+
* reported as in progress or complete), that is always 'drifted' — a phase
|
|
197
|
+
* declared deferred while the other document says work is happening is a
|
|
198
|
+
* direct contradiction, checked here (like the terminal-completeness rule
|
|
199
|
+
* above) BEFORE the numeric distance comparison. 'Deferred' against a
|
|
200
|
+
* rank-0 status on the other side (e.g. 'Not started') stays 'consistent' —
|
|
201
|
+
* both agree no work has happened.
|
|
202
|
+
*
|
|
203
|
+
* Otherwise, ranks are compared numerically: equal → 'consistent';
|
|
204
|
+
* off-by-one → 'lag'; off-by-two-or-more → 'drifted'.
|
|
205
|
+
*
|
|
206
|
+
* @param opts.stateStatus - raw Status value from STATE.md's Current Position
|
|
207
|
+
* @param opts.roadmapStatus - raw Status cell from ROADMAP.md's Progress table
|
|
208
|
+
* @returns { verdict, stateRank, roadmapRank }
|
|
209
|
+
*/
|
|
210
|
+
function comparePhaseStatus({ stateStatus, roadmapStatus, }) {
|
|
211
|
+
const stateRank = resolvePhaseStatusRank(stateStatus);
|
|
212
|
+
const roadmapRank = resolvePhaseStatusRank(roadmapStatus);
|
|
213
|
+
if (stateRank === null || roadmapRank === null) {
|
|
214
|
+
return { verdict: 'uncheckable', stateRank, roadmapRank };
|
|
215
|
+
}
|
|
216
|
+
const stateIsTerminal = stateRank === TERMINAL_RANK;
|
|
217
|
+
const roadmapIsTerminal = roadmapRank === TERMINAL_RANK;
|
|
218
|
+
if (stateIsTerminal !== roadmapIsTerminal) {
|
|
219
|
+
return { verdict: 'drifted', stateRank, roadmapRank };
|
|
220
|
+
}
|
|
221
|
+
const stateIsDeferred = normalizePhaseStatusText(stateStatus) === 'deferred';
|
|
222
|
+
const roadmapIsDeferred = normalizePhaseStatusText(roadmapStatus) === 'deferred';
|
|
223
|
+
if (stateIsDeferred !== roadmapIsDeferred) {
|
|
224
|
+
const otherRank = stateIsDeferred ? roadmapRank : stateRank;
|
|
225
|
+
if (otherRank >= 1) {
|
|
226
|
+
return { verdict: 'drifted', stateRank, roadmapRank };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const diff = Math.abs(stateRank - roadmapRank);
|
|
230
|
+
if (diff === 0) {
|
|
231
|
+
return { verdict: 'consistent', stateRank, roadmapRank };
|
|
232
|
+
}
|
|
233
|
+
if (diff === 1) {
|
|
234
|
+
return { verdict: 'lag', stateRank, roadmapRank };
|
|
235
|
+
}
|
|
236
|
+
return { verdict: 'drifted', stateRank, roadmapRank };
|
|
237
|
+
}
|
|
@@ -15,6 +15,12 @@ const { countMatchedSummaries } = coreUtils;
|
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
16
16
|
const frontmatterMod = require("./frontmatter.cjs");
|
|
17
17
|
const { extractFrontmatter } = frontmatterMod;
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
|
+
const planningScopeMod = require("./planning-scope.cjs");
|
|
20
|
+
const { SCOPE } = planningScopeMod;
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
22
|
+
const planDependencyGraphMod = require("./plan-dependency-graph.cjs");
|
|
23
|
+
const { isSummaryFileBlocked } = planDependencyGraphMod;
|
|
18
24
|
// Excluded derivative files
|
|
19
25
|
const PLAN_OUTLINE_RE = /-OUTLINE\.md$/i;
|
|
20
26
|
const PLAN_PRE_BOUNCE_RE = /\.pre-bounce\.md$/i;
|
|
@@ -82,7 +88,18 @@ function isRootPlanFile(fileName) {
|
|
|
82
88
|
// substring "PLAN") are not double-counted as plans. (#500 RC2)
|
|
83
89
|
if (isRootSummaryFile(fileName))
|
|
84
90
|
return false;
|
|
85
|
-
|
|
91
|
+
// #3741: the PLAN token must be DELIMITED — anchored at the start or after
|
|
92
|
+
// a hyphen, and followed only by an optional `-<digits>…` suffix before
|
|
93
|
+
// `.md` (the `…` keeps the legacy slug form `3-PLAN-01-setup.md` that
|
|
94
|
+
// gsd-plan-phase writes, per #3128). A bare substring test counted
|
|
95
|
+
// REPLAN-INPUTS / PLANNING-INPUTS / PLANNING-NOTES as plans, inflating
|
|
96
|
+
// planCount and STATE.md's derived total_plans. Delimited keeps the
|
|
97
|
+
// fallback's deliberate permissiveness for legacy single-token names
|
|
98
|
+
// (`plan.md`, `Plan.md`, `01-PLAN-02.md`, `3-PLAN-01-setup.md`) while
|
|
99
|
+
// excluding any name where PLAN is merely embedded in a larger word
|
|
100
|
+
// (REPLAN, PLANNING) — the same anchoring discipline isNestedPlanFile
|
|
101
|
+
// already applies.
|
|
102
|
+
return /(^|-)PLAN(-\d+.*)?\.md$/i.test(fileName);
|
|
86
103
|
}
|
|
87
104
|
function isNestedPlanFile(fileName) {
|
|
88
105
|
if (PLAN_OUTLINE_RE.test(fileName))
|
|
@@ -97,6 +114,43 @@ function isRootSummaryFile(fileName) {
|
|
|
97
114
|
function isNestedSummaryFile(fileName) {
|
|
98
115
|
return /^SUMMARY-\d+.*\.md$/i.test(fileName) || /-SUMMARY-\d+.*\.md$/i.test(fileName);
|
|
99
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Strict canonical-naming predicate over a `scanPhasePlans` `planFiles`/
|
|
119
|
+
* `allPlanFiles` ENTRY (root form bare, nested form `plans/`-prefixed, exactly
|
|
120
|
+
* as those arrays store them) — root `<phase>-<NN>-PLAN.md`/bare `PLAN.md`,
|
|
121
|
+
* or nested `plans/PLAN-<NN>....md`/`plans/<x>-PLAN-<NN>....md` — WITHOUT
|
|
122
|
+
* `isRootPlanFile`'s loose delimited-PLAN fallback.
|
|
123
|
+
*
|
|
124
|
+
* The `plans/` prefix check is load-bearing, not cosmetic: `isNestedPlanFile`
|
|
125
|
+
* matches ANY basename containing `-PLAN-<digits>...md` with no anchor
|
|
126
|
+
* requiring an actual `plans/` directory — that shape is exactly the #2893
|
|
127
|
+
* reporter's non-canonical example, `01-PLAN-01-foundation.md`. Applying
|
|
128
|
+
* `isNestedPlanFile` directly to a bare root-level name would therefore
|
|
129
|
+
* misclassify that exact offender as canonical. Only entries scanPhasePlans
|
|
130
|
+
* itself produced with the `plans/` prefix (i.e. read from the real nested
|
|
131
|
+
* subdirectory) are eligible for the nested check.
|
|
132
|
+
*
|
|
133
|
+
* #2893/#3183: `isRootPlanFile`'s loose fallback is deliberately permissive
|
|
134
|
+
* for live-plan COUNTING (a lowercase `plan.md` still counts toward
|
|
135
|
+
* completion — see plan-count-single-owner.test.cjs's pinned case-sensitivity
|
|
136
|
+
* asymmetry). But the #2893 "non-canonical filename" diagnostic (phase.cts's
|
|
137
|
+
* `describeNonCanonicalPlans`, used by find-phase/phase-plan-index/phases
|
|
138
|
+
* list --type plans) exists specifically to CATCH a plan-shaped file that
|
|
139
|
+
* does NOT match the canonical contract and warn instead of silently
|
|
140
|
+
* scheduling it. Feeding that diagnostic (and the `plans`/`files` lists those
|
|
141
|
+
* commands return) the loose `allPlanFiles`/`planFiles` set defeats the
|
|
142
|
+
* diagnostic entirely, since the loose fallback already recognizes the
|
|
143
|
+
* non-canonical file as "matched". This predicate is the STRICT filter those
|
|
144
|
+
* three call sites intersect against so the diagnostic (and what counts as a
|
|
145
|
+
* schedulable plan for those commands specifically) stays canonical-only,
|
|
146
|
+
* while scanPhasePlans's own planCount/summaryCount/completed stay on the
|
|
147
|
+
* loose, permissive rule.
|
|
148
|
+
*/
|
|
149
|
+
function isCanonicalPlanFile(fileEntry) {
|
|
150
|
+
if (fileEntry.startsWith('plans/'))
|
|
151
|
+
return isNestedPlanFile(fileEntry.slice('plans/'.length));
|
|
152
|
+
return fileEntry.endsWith('-PLAN.md') || fileEntry === 'PLAN.md';
|
|
153
|
+
}
|
|
100
154
|
function scanPhasePlans(phaseDir) {
|
|
101
155
|
let rootFiles;
|
|
102
156
|
try {
|
|
@@ -109,7 +163,9 @@ function scanPhasePlans(phaseDir) {
|
|
|
109
163
|
completed: false,
|
|
110
164
|
hasNestedPlans: false,
|
|
111
165
|
planFiles: [],
|
|
166
|
+
allPlanFiles: [],
|
|
112
167
|
summaryFiles: [],
|
|
168
|
+
scope: SCOPE.UNREADABLE,
|
|
113
169
|
};
|
|
114
170
|
}
|
|
115
171
|
const rootPlanFiles = rootFiles.filter(isRootPlanFile);
|
|
@@ -117,6 +173,7 @@ function scanPhasePlans(phaseDir) {
|
|
|
117
173
|
let nestedPlanFiles = [];
|
|
118
174
|
let nestedSummaryFiles = [];
|
|
119
175
|
let hasNestedPlans = false;
|
|
176
|
+
let scope = SCOPE.COMPLETE;
|
|
120
177
|
const nestedDir = (0, node_path_1.join)(phaseDir, 'plans');
|
|
121
178
|
if ((0, node_fs_1.existsSync)(nestedDir)) {
|
|
122
179
|
try {
|
|
@@ -125,7 +182,12 @@ function scanPhasePlans(phaseDir) {
|
|
|
125
182
|
nestedSummaryFiles = nestedFiles.filter(isNestedSummaryFile).map((file) => `plans/${file}`);
|
|
126
183
|
hasNestedPlans = nestedPlanFiles.length > 0;
|
|
127
184
|
}
|
|
128
|
-
catch {
|
|
185
|
+
catch {
|
|
186
|
+
// #3183 (ADR-3180 Decision 2): the nested plans/ dir exists but could not
|
|
187
|
+
// be read — this scan cannot see plans it knows are there, so zero is
|
|
188
|
+
// NOT a reliable answer; mark TRUNCATED rather than COMPLETE.
|
|
189
|
+
scope = SCOPE.TRUNCATED;
|
|
190
|
+
}
|
|
129
191
|
}
|
|
130
192
|
const allPlanFiles = rootPlanFiles.concat(nestedPlanFiles);
|
|
131
193
|
// #2349: drop plans explicitly marked `status: superseded` from the plan set
|
|
@@ -144,7 +206,18 @@ function scanPhasePlans(phaseDir) {
|
|
|
144
206
|
// 30-GAPCLOSURE-SUMMARY.md) must not inflate summary_count or flip a phase to
|
|
145
207
|
// Complete when plans are still missing summaries. summaryFiles (the array)
|
|
146
208
|
// still holds every summary on disk for callers that read/list them.
|
|
147
|
-
|
|
209
|
+
//
|
|
210
|
+
// #3345: a SUMMARY whose frontmatter declares `status: blocked` is a failure
|
|
211
|
+
// record, not a completion record — it is dropped from the COUNTABLE pairing
|
|
212
|
+
// set before matching. The bounded-prefix status read is the SHARED predicate
|
|
213
|
+
// (plan-dependency-graph.cjs's isSummaryFileBlocked) that phase.cts's read
|
|
214
|
+
// path also filters through, so the count and the `incomplete` list cannot
|
|
215
|
+
// diverge. Fail-open: a SUMMARY with no `status` key, or one that cannot be read,
|
|
216
|
+
// keeps its pre-#3345 filename-existence meaning — untouched projects are
|
|
217
|
+
// byte-for-behaviour identical. `status: halted` stays counted (#2830: a
|
|
218
|
+
// designed stop still writes a completion record).
|
|
219
|
+
const countableSummaryFiles = summaryFiles.filter((f) => !isSummaryFileBlocked((0, node_path_1.join)(phaseDir, f)));
|
|
220
|
+
const summaryCount = countMatchedSummaries(planFiles, countableSummaryFiles);
|
|
148
221
|
return {
|
|
149
222
|
planCount,
|
|
150
223
|
summaryCount,
|
|
@@ -155,10 +228,31 @@ function scanPhasePlans(phaseDir) {
|
|
|
155
228
|
// (0 >= 0) rather than being pinned below 100% forever, which is the very
|
|
156
229
|
// failure this fix removes. A genuinely empty phase (no plans authored)
|
|
157
230
|
// still has allPlanFiles.length 0 and stays not-completed, exactly as before.
|
|
231
|
+
//
|
|
232
|
+
// ADR-3180 §7.4 (issue #3186) — DELIBERATELY NOT routed through
|
|
233
|
+
// `isPhaseComplete` (src/verification.cts). This field answers "are all
|
|
234
|
+
// plans summarized?", NOT "is the phase complete?" — completion
|
|
235
|
+
// additionally requires a passing `*-VERIFICATION.md`, which is the
|
|
236
|
+
// whole point of that owner's unconditional readVerificationStatus call.
|
|
237
|
+
// Folding this field onto `isPhaseComplete` would either over-report
|
|
238
|
+
// completion (a phase whose plans are done but never verified) or drag a
|
|
239
|
+
// verification read into this module, inverting the dependency
|
|
240
|
+
// direction between this Phase-1 owner (plan counting) and the Phase-4
|
|
241
|
+
// owner (completion) — the owner must consume plan counts, never the
|
|
242
|
+
// reverse. Kept as its own, differently-scoped answer per the design's
|
|
243
|
+
// "0.x split" and exempted (function-scoped, not file-scoped) in
|
|
244
|
+
// scripts/lint-completion-predicate-drift.cjs's FUNCTION_SCOPED_EXEMPTIONS.
|
|
245
|
+
// The field name is left unchanged (not renamed to e.g.
|
|
246
|
+
// `summariesMeetPlanCount`) — scanPhasePlans has 11 direct callers, and a
|
|
247
|
+
// rename's blast radius is out of this phase's declared scope; noted
|
|
248
|
+
// here as a deliberate, considered-and-declined option rather than an
|
|
249
|
+
// oversight.
|
|
158
250
|
completed: allPlanFiles.length > 0 && summaryCount >= planCount,
|
|
159
251
|
hasNestedPlans,
|
|
160
252
|
planFiles,
|
|
253
|
+
allPlanFiles,
|
|
161
254
|
summaryFiles,
|
|
255
|
+
scope,
|
|
162
256
|
};
|
|
163
257
|
}
|
|
164
258
|
module.exports = Object.assign(scanPhasePlans, {
|
|
@@ -167,4 +261,5 @@ module.exports = Object.assign(scanPhasePlans, {
|
|
|
167
261
|
isNestedPlanFile,
|
|
168
262
|
isRootSummaryFile,
|
|
169
263
|
isNestedSummaryFile,
|
|
264
|
+
isCanonicalPlanFile,
|
|
170
265
|
});
|