@opengsd/gsd-core 1.9.1 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -3
- package/.opencode/plugins/gsd-core.js +8 -1
- package/agents/gsd-code-fixer.md +27 -3
- package/agents/gsd-debug-session-manager.md +11 -0
- package/agents/gsd-debugger.md +12 -246
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-executor.md +12 -10
- package/agents/gsd-integration-checker.md +3 -0
- package/agents/gsd-mempalace-curator.md +5 -2
- package/agents/gsd-phase-researcher.md +20 -1
- package/agents/gsd-plan-checker.md +46 -0
- package/agents/gsd-planner.md +49 -54
- package/agents/gsd-roadmapper.md +21 -3
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +26 -73
- package/bin/install.js +1272 -1238
- package/bin/lib/ui-safety-gate.cjs +2 -0
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +2 -2
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +2 -2
- package/commands/gsd/plan-phase.md +1 -1
- package/commands/gsd/quick.md +1 -1
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +1009 -115
- package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
- package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
- package/gsd-core/bin/lib/api-coverage.cjs +123 -5
- package/gsd-core/bin/lib/artifacts.cjs +3 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +926 -202
- package/gsd-core/bin/lib/broken-windows.cjs +36 -6
- package/gsd-core/bin/lib/capability-consent.cjs +149 -15
- package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
- package/gsd-core/bin/lib/capability-registry.cjs +608 -148
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +507 -24
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +114 -38
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
- package/gsd-core/bin/lib/command-aliases.cjs +94 -0
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/commands.cjs +665 -99
- package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
- package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
- package/gsd-core/bin/lib/config-loader.cjs +76 -0
- package/gsd-core/bin/lib/config.cjs +22 -2
- package/gsd-core/bin/lib/context-composer.cjs +278 -0
- package/gsd-core/bin/lib/context-predicates.cjs +506 -0
- package/gsd-core/bin/lib/core-utils.cjs +217 -40
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/docs.cjs +3 -2
- package/gsd-core/bin/lib/external-job.cjs +19 -4
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +239 -32
- package/gsd-core/bin/lib/gap-checker.cjs +68 -7
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
- package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
- package/gsd-core/bin/lib/graphify.cjs +142 -27
- package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
- package/gsd-core/bin/lib/host-integration.cjs +13 -1
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init-command-router.cjs +83 -8
- package/gsd-core/bin/lib/init.cjs +1325 -169
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +805 -264
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
- package/gsd-core/bin/lib/install-profiles.cjs +160 -57
- package/gsd-core/bin/lib/install-scope.cjs +270 -0
- package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
- package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
- package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
- package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
- package/gsd-core/bin/lib/io.cjs +38 -3
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
- package/gsd-core/bin/lib/markdown-table.cjs +133 -20
- package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
- package/gsd-core/bin/lib/mcp-server.cjs +135 -3
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +821 -109
- package/gsd-core/bin/lib/model-catalog.cjs +59 -1
- package/gsd-core/bin/lib/model-resolver.cjs +183 -40
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
- package/gsd-core/bin/lib/phase-id.cjs +507 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
- package/gsd-core/bin/lib/phase-locator.cjs +258 -58
- package/gsd-core/bin/lib/phase.cjs +891 -156
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +86 -2
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +60 -6
- package/gsd-core/bin/lib/probe-core.cjs +1 -1
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +405 -84
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
- package/gsd-core/bin/lib/section-manifest.cjs +209 -0
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
- package/gsd-core/bin/lib/smart-entry.cjs +154 -22
- package/gsd-core/bin/lib/state-command-router.cjs +5 -1
- package/gsd-core/bin/lib/state-document.cjs +152 -8
- package/gsd-core/bin/lib/state-transition.cjs +424 -105
- package/gsd-core/bin/lib/state.cjs +1927 -401
- package/gsd-core/bin/lib/surface.cjs +35 -10
- package/gsd-core/bin/lib/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +20 -4
- package/gsd-core/bin/lib/uat.cjs +706 -64
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
- package/gsd-core/bin/lib/unusable-input.cjs +33 -0
- package/gsd-core/bin/lib/update-context.cjs +8 -2
- package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +37 -0
- package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
- package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +287 -20
- package/gsd-core/bin/lib/verify.cjs +368 -880
- package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
- package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
- package/gsd-core/bin/lib/workstream.cjs +8 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
- package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
- package/gsd-core/references/agent-contracts.md +43 -26
- package/gsd-core/references/artifact-types.md +10 -3
- package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
- package/gsd-core/references/checkpoints.md +2 -2
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/debugger-techniques.md +255 -0
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/execute-mvp-tdd.md +3 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
- package/gsd-core/references/execute-phase-context-guard.md +1 -1
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +6 -2
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-planning-commit.md +2 -1
- package/gsd-core/references/loop-hook-dispatch.md +39 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planning-config.md +8 -6
- package/gsd-core/references/research-documentation-lookup.md +5 -3
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +8 -7
- package/gsd-core/references/universal-anti-patterns.md +3 -3
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verifier-wiring-patterns.md +100 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +22 -6
- package/gsd-core/references/worktree-branch-check.md +2 -2
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +2 -4
- package/gsd-core/templates/state.md +4 -4
- package/gsd-core/templates/summary-complex.md +2 -0
- package/gsd-core/templates/summary-minimal.md +2 -0
- package/gsd-core/templates/summary-standard.md +2 -0
- package/gsd-core/templates/summary.md +2 -0
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/ai-integration-phase.md +9 -11
- package/gsd-core/workflows/audit-milestone.md +3 -0
- package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
- package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
- package/gsd-core/workflows/autonomous.md +33 -70
- package/gsd-core/workflows/cleanup.md +62 -3
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
- package/gsd-core/workflows/code-review-fix.md +37 -10
- package/gsd-core/workflows/code-review.md +74 -166
- package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
- package/gsd-core/workflows/complete-milestone.md +160 -95
- package/gsd-core/workflows/debug.md +16 -17
- package/gsd-core/workflows/diagnose-issues.md +56 -8
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
- package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
- package/gsd-core/workflows/docs-update.md +8 -51
- package/gsd-core/workflows/edit-phase.md +26 -1
- package/gsd-core/workflows/eval-review.md +3 -5
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +64 -7
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
- package/gsd-core/workflows/execute-phase.md +103 -187
- package/gsd-core/workflows/execute-plan.md +36 -4
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/health.md +73 -4
- package/gsd-core/workflows/help/modes/full.md +6 -1
- package/gsd-core/workflows/import.md +4 -4
- package/gsd-core/workflows/ingest-docs.md +7 -6
- package/gsd-core/workflows/mvp-phase.md +6 -3
- package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
- package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
- package/gsd-core/workflows/new-milestone.md +35 -47
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
- package/gsd-core/workflows/new-project.md +27 -240
- package/gsd-core/workflows/next.md +12 -0
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
- package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
- package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
- package/gsd-core/workflows/plan-phase.md +89 -209
- package/gsd-core/workflows/plan-review-convergence.md +50 -2
- package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
- package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
- package/gsd-core/workflows/progress.md +45 -159
- package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
- package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
- package/gsd-core/workflows/quick.md +55 -405
- package/gsd-core/workflows/resume-project.md +3 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
- package/gsd-core/workflows/review.md +41 -13
- package/gsd-core/workflows/section-manifest.json +219 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings.md +66 -2
- package/gsd-core/workflows/ship.md +104 -44
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/spec-phase.md +41 -20
- package/gsd-core/workflows/spike-wrap-up.md +20 -5
- package/gsd-core/workflows/spike.md +50 -16
- package/gsd-core/workflows/sync-skills.md +106 -13
- package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
- package/gsd-core/workflows/transition.md +53 -31
- package/gsd-core/workflows/ui-phase.md +13 -12
- package/gsd-core/workflows/ui-review.md +2 -2
- package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
- package/gsd-core/workflows/update.md +19 -8
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
- package/gsd-core/workflows/verify-work.md +17 -65
- package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
- package/hooks/dist/gsd-check-update-worker.js +64 -12
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
- package/hooks/dist/gsd-cursor-subagent-start.js +607 -26
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/dist/gsd-prompt-guard.js +21 -20
- package/hooks/dist/gsd-read-injection-scanner.js +45 -24
- package/hooks/dist/gsd-statusline.js +90 -6
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-workflow-guard.js +134 -36
- package/hooks/dist/gsd-worktree-path-guard.js +2 -1
- package/hooks/dist/gsd-write-guard.js +359 -0
- package/hooks/dist/lib/git-cmd.js +92 -59
- package/hooks/dist/lib/injection-patterns.js +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +277 -0
- package/hooks/dist/managed-hooks-registry.cjs +2 -0
- package/hooks/gsd-agent-isolation-guard.js +517 -0
- package/hooks/gsd-check-update-worker.js +64 -12
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-cursor-pre-tool.js +0 -3
- package/hooks/gsd-cursor-subagent-start.js +607 -26
- package/hooks/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/gsd-prompt-guard.js +21 -20
- package/hooks/gsd-read-injection-scanner.js +45 -24
- package/hooks/gsd-statusline.js +90 -6
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-workflow-guard.js +134 -36
- package/hooks/gsd-worktree-path-guard.js +2 -1
- package/hooks/gsd-write-guard.js +359 -0
- package/hooks/hooks.json +12 -0
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +277 -0
- package/hooks/managed-hooks-registry.cjs +2 -0
- package/package.json +31 -10
- package/pi/gsd.cjs +71 -12
- package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
- package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
- package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
- package/scripts/build-hooks.js +9 -0
- package/scripts/changeset/lint.cjs +68 -6
- package/scripts/changeset/serialize.cjs +5 -1
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/ci-test-scope.cjs +19 -2
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-matrix.cjs +1 -1
- package/scripts/gen-capability-registry.cjs +3 -15
- package/scripts/gen-context-index.cjs +439 -0
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-inventory-manifest.cjs +150 -4
- package/scripts/gen-loop-host-contract.cjs +4 -24
- package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/gen-section-manifest.cjs +638 -0
- package/scripts/generate-package-identity.cjs +4 -2
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/drift-scan.cjs +278 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +15 -54
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-compiled-artifact-sync.cjs +6 -1
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-docs-command-form.cjs +195 -0
- package/scripts/lint-docs-required.cjs +9 -1
- package/scripts/lint-emitted-drift-ack.cjs +215 -20
- package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/lint-example-parser-parity.cjs +395 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-phase-enumeration-drift.cjs +479 -0
- package/scripts/lint-plan-count-drift.cjs +318 -0
- package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
- package/scripts/lint-planning-prompt-drift.cjs +434 -0
- package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
- package/scripts/lint-regression-test-names.cjs +15 -13
- package/scripts/lint-removed-but-needed.cjs +320 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +1045 -0
- package/scripts/lint-test-file-count.allowlist.json +40 -3
- package/scripts/lint-unreachable-guard-drift.cjs +843 -0
- package/scripts/lint-vendored-deps.cjs +124 -0
- package/scripts/mutation-matrix.cjs +13 -0
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +52 -6
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/state-write-path-drift-baseline.json +19 -0
- package/scripts/sync-runtime-launcher.cjs +2 -4
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +2 -2
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +2 -2
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +1 -2
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +1 -1
- package/skills/gsd-review-backlog/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +0 -1
- package/skills/gsd-verify-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -577
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/gen-emitted-baseline.cjs +0 -145
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1050
|
@@ -22,8 +22,12 @@
|
|
|
22
22
|
*/
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
24
|
exports.deriveProgressFromRoadmap = deriveProgressFromRoadmap;
|
|
25
|
+
exports.clampPercentFromFraction = clampPercentFromFraction;
|
|
25
26
|
exports.clampPercent = clampPercent;
|
|
26
27
|
const markdown_table_cjs_1 = require("./markdown-table.cjs");
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id.cjs is an export= CommonJS module
|
|
29
|
+
const phaseIdMod = require("./phase-id.cjs");
|
|
30
|
+
const { isSentinelPhaseId } = phaseIdMod;
|
|
27
31
|
/**
|
|
28
32
|
* Derive completed_phases, total_phases, and total_plans from ROADMAP content.
|
|
29
33
|
* Root cause fix for issue #4 — see gen-phase-lifecycle.mjs for full documentation.
|
|
@@ -81,10 +85,11 @@ function deriveProgressFromRoadmap(roadmapContent) {
|
|
|
81
85
|
const allRows = table.rows;
|
|
82
86
|
const completed = allRows.filter((r) => /^complete$/i.test((r['Status'] ?? '').trim())).length;
|
|
83
87
|
completedPhases = completed > 0 ? completed : null;
|
|
84
|
-
// Data rows only (exclude
|
|
88
|
+
// Data rows only (exclude sentinel phases 0 and 999.x).
|
|
89
|
+
// #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
|
|
85
90
|
const dataRows = allRows.filter((r) => {
|
|
86
91
|
const phase = (r['Phase'] ?? '').trim();
|
|
87
|
-
return /^\d/.test(phase) &&
|
|
92
|
+
return /^\d/.test(phase) && !isSentinelPhaseId(phase);
|
|
88
93
|
});
|
|
89
94
|
totalPhases = dataRows.length > 0 ? dataRows.length : null;
|
|
90
95
|
let totalPlansSum = 0;
|
|
@@ -98,12 +103,32 @@ function deriveProgressFromRoadmap(roadmapContent) {
|
|
|
98
103
|
}
|
|
99
104
|
return { completedPhases, totalPhases, totalPlans };
|
|
100
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Compute progress percent clamped to 100 from an already-computed FRACTION.
|
|
108
|
+
*
|
|
109
|
+
* ADR-3180 Decision 7 (#3180): the completion-RATIO derivation has exactly one
|
|
110
|
+
* owner, and this is its kernel — the single place the `fraction -> integer
|
|
111
|
+
* percent` rounding and the 100 ceiling are expressed. `clampPercent` below is
|
|
112
|
+
* the count-shaped entry point and delegates here; a caller that already holds a
|
|
113
|
+
* fraction (rather than a completed/total pair) calls this directly instead of
|
|
114
|
+
* re-deriving `Math.min(100, Math.round(f * 100))` locally.
|
|
115
|
+
*
|
|
116
|
+
* Enforced by `scripts/lint-completion-ratio-drift.cjs`.
|
|
117
|
+
*/
|
|
118
|
+
function clampPercentFromFraction(fraction) {
|
|
119
|
+
return Math.min(100, Math.round(fraction * 100));
|
|
120
|
+
}
|
|
101
121
|
/**
|
|
102
122
|
* Compute progress percent clamped to 100.
|
|
103
123
|
* Root cause fix for issue #4 — see gen-phase-lifecycle.mjs for full documentation.
|
|
124
|
+
*
|
|
125
|
+
* A non-positive (or absent) denominator yields `0` — "nothing to complete" is
|
|
126
|
+
* reported as 0%, never as 100%. Every `.planning/` completion percentage in this
|
|
127
|
+
* codebase routes through here (ADR-3180 Decision 7); the `total > 0 ? ... : 0`
|
|
128
|
+
* ternary that used to precede each inline copy IS this function's first line.
|
|
104
129
|
*/
|
|
105
130
|
function clampPercent(completed, total) {
|
|
106
131
|
if (!total || total <= 0)
|
|
107
132
|
return 0;
|
|
108
|
-
return
|
|
133
|
+
return clampPercentFromFraction(completed / total);
|
|
109
134
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Dependencies (leaf modules only — no loadConfig):
|
|
14
14
|
* - node:fs / node:path (stdlib)
|
|
15
|
-
* - ./phase-id.cjs (normalizePhaseName,
|
|
15
|
+
* - ./phase-id.cjs (normalizePhaseName, matchPhaseDirs, phaseNumberForMatch)
|
|
16
16
|
* - ./core-utils.cjs (readSubdirectories, getPhaseFileStats, extractCanonicalPlanId, toPosixPath)
|
|
17
17
|
* - ./planning-workspace.cjs (planningDir)
|
|
18
18
|
*/
|
|
@@ -23,18 +23,104 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
23
23
|
const node_path_1 = __importDefault(require("node:path"));
|
|
24
24
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
25
25
|
const phaseIdModule = require("./phase-id.cjs");
|
|
26
|
-
const { normalizePhaseName,
|
|
26
|
+
const { normalizePhaseName, matchPhaseDirs, phaseNumberForMatch, isSentinelPhaseId, comparePhaseNum } = phaseIdModule;
|
|
27
27
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
28
28
|
const coreUtilsModule = require("./core-utils.cjs");
|
|
29
|
-
const { readSubdirectories, getPhaseFileStats, extractCanonicalPlanId, toPosixPath } = coreUtilsModule;
|
|
29
|
+
const { readSubdirectories, getPhaseFileStats, extractCanonicalPlanId, toPosixPath, findUnsummarizedPlans } = coreUtilsModule;
|
|
30
30
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
31
31
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
32
32
|
const { planningDir } = planningWorkspace;
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
34
|
+
const frontmatterModule = require("./frontmatter.cjs");
|
|
35
|
+
const { extractFrontmatter } = frontmatterModule;
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
37
|
+
const planDependencyGraphModule = require("./plan-dependency-graph.cjs");
|
|
38
|
+
const { computeHaltPropagation, buildSummaryFileIndex, isSummaryFileHalted } = planDependencyGraphModule;
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
40
|
+
const roadmapParserModule = require("./roadmap-parser.cjs");
|
|
41
|
+
const { getMilestonePhaseFilter } = roadmapParserModule;
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
43
|
+
const planningScopeMod = require("./planning-scope.cjs");
|
|
44
|
+
const { SCOPE } = planningScopeMod;
|
|
45
|
+
/**
|
|
46
|
+
* #2830: parse a plan file's `depends_on` frontmatter. Returns [] — never
|
|
47
|
+
* throws — on a missing/unreadable/malformed plan or absent field, matching
|
|
48
|
+
* this primitive's existing fail-safe posture (a plan directory this
|
|
49
|
+
* primitive can otherwise read must never throw here).
|
|
50
|
+
*/
|
|
51
|
+
function parsePlanDependsOn(phaseDir, planFile) {
|
|
52
|
+
try {
|
|
53
|
+
const planPath = node_path_1.default.join(phaseDir, planFile);
|
|
54
|
+
const content = node_fs_1.default.readFileSync(planPath, 'utf-8');
|
|
55
|
+
const fm = extractFrontmatter(content, planPath);
|
|
56
|
+
const fmDeps = fm['depends_on'];
|
|
57
|
+
if (Array.isArray(fmDeps))
|
|
58
|
+
return fmDeps.map(String);
|
|
59
|
+
if (typeof fmDeps === 'string' && fmDeps.trim() !== '')
|
|
60
|
+
return [fmDeps];
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
33
67
|
// ─── Phase search helpers ─────────────────────────────────────────────────────
|
|
68
|
+
/**
|
|
69
|
+
* #2855: single source of truth for resolving and enumerating a project's
|
|
70
|
+
* (or, when a workstream is active, that workstream's OWN) archived-milestone
|
|
71
|
+
* directories — `<planningDir(cwd)>/milestones/vX.Y-phases/`. Both
|
|
72
|
+
* `findPhaseInternal`'s archive fallback and `getArchivedPhaseDirs` used to
|
|
73
|
+
* carry independent copies of this resolve-then-enumerate logic, which is
|
|
74
|
+
* exactly the shape that let the original #2855 bug (hardcoded root path)
|
|
75
|
+
* exist in one copy and not the other. Sharing this seam means a future
|
|
76
|
+
* change to how the archive tree is located only needs to happen once.
|
|
77
|
+
* Most-recent-milestone-first order, compared numerically segment-by-segment
|
|
78
|
+
* on the version (e.g. `v1.10` before `v1.9`) — NOT lexicographically. A
|
|
79
|
+
* lexicographic `.sort().reverse()` (the prior implementation) ranks `v1.9`
|
|
80
|
+
* ahead of `v1.10` because the string `"1.9"` sorts after `"1.10"`; that is
|
|
81
|
+
* deterministic but wrong for every double-digit-or-higher minor/patch
|
|
82
|
+
* version, and #3458 is what first surfaces archived phases in audit output
|
|
83
|
+
* where the misordering becomes user-visible.
|
|
84
|
+
* Never throws: an absent/unreadable milestones/ dir yields [].
|
|
85
|
+
*/
|
|
86
|
+
function compareArchiveVersionDesc(aName, bName) {
|
|
87
|
+
const aParts = (aName.match(/^v([\d.]+)-phases$/)?.[1] ?? '').split('.').map(Number);
|
|
88
|
+
const bParts = (bName.match(/^v([\d.]+)-phases$/)?.[1] ?? '').split('.').map(Number);
|
|
89
|
+
const len = Math.max(aParts.length, bParts.length);
|
|
90
|
+
for (let i = 0; i < len; i++) {
|
|
91
|
+
const a = aParts[i] ?? 0;
|
|
92
|
+
const b = bParts[i] ?? 0;
|
|
93
|
+
if (a !== b)
|
|
94
|
+
return b - a; // descending: newest (numerically largest) first
|
|
95
|
+
}
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
function listArchiveVersionDirs(cwd) {
|
|
99
|
+
const milestonesDir = node_path_1.default.join(planningDir(cwd), 'milestones');
|
|
100
|
+
if (!node_fs_1.default.existsSync(milestonesDir))
|
|
101
|
+
return [];
|
|
102
|
+
try {
|
|
103
|
+
const milestoneEntries = node_fs_1.default.readdirSync(milestonesDir, { withFileTypes: true });
|
|
104
|
+
return milestoneEntries
|
|
105
|
+
.filter(e => e.isDirectory() && /^v[\d.]+-phases$/.test(e.name))
|
|
106
|
+
.map(e => e.name)
|
|
107
|
+
.sort(compareArchiveVersionDesc)
|
|
108
|
+
.map(archiveName => ({
|
|
109
|
+
version: archiveName.match(/^(v[\d.]+)-phases$/)[1],
|
|
110
|
+
archivePath: node_path_1.default.join(milestonesDir, archiveName),
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
34
117
|
function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
35
118
|
try {
|
|
36
119
|
const dirs = readSubdirectories(baseDir, true);
|
|
37
|
-
|
|
120
|
+
// #2528: canonical two-pass selection (exact token match, then the
|
|
121
|
+
// bare-integer leading-digit-run fallback) shared with the find-phase and
|
|
122
|
+
// phase-plan-index scans — see phase-id.cts::matchPhaseDirs.
|
|
123
|
+
const { matches, usedBareFallback } = matchPhaseDirs(dirs, normalized);
|
|
38
124
|
if (matches.length === 0)
|
|
39
125
|
return null;
|
|
40
126
|
// #2237: fail loud when multiple directories match the same bare phase
|
|
@@ -55,10 +141,13 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
|
55
141
|
has_verification: false,
|
|
56
142
|
has_reviews: false,
|
|
57
143
|
ambiguous_matches: matches,
|
|
144
|
+
halted_plans: [],
|
|
145
|
+
blocked_by: {},
|
|
146
|
+
runnable_plans: [],
|
|
58
147
|
};
|
|
59
148
|
}
|
|
60
149
|
const match = matches[0];
|
|
61
|
-
const phaseToken =
|
|
150
|
+
const phaseToken = phaseNumberForMatch(match, usedBareFallback);
|
|
62
151
|
const phaseNumber = phaseToken || normalized;
|
|
63
152
|
const afterToken = match.slice(phaseToken ? phaseToken.length : 0).replace(/^-/, '');
|
|
64
153
|
const phaseName = afterToken || null;
|
|
@@ -66,16 +155,57 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
|
66
155
|
const { plans: unsortedPlans, summaries: unsortedSummaries, hasResearch, hasContext, hasVerification, hasReviews } = getPhaseFileStats(phaseDir);
|
|
67
156
|
const plans = unsortedPlans.sort();
|
|
68
157
|
const summaries = unsortedSummaries.sort();
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
158
|
+
// #3183 (ADR-3180 Decision 2): the summary→plan pairing used to be a
|
|
159
|
+
// bespoke rule local to this function (a third pairing rule alongside
|
|
160
|
+
// scanPhasePlans's completion check and countMatchedSummaries). Routed
|
|
161
|
+
// through the canonical core-utils.findUnsummarizedPlans instead, which
|
|
162
|
+
// shares its `summaryCandidates` matching rule with countMatchedSummaries
|
|
163
|
+
// so the count and this named list can never disagree.
|
|
164
|
+
const incompletePlans = findUnsummarizedPlans(plans, summaries);
|
|
165
|
+
// #2830: reverse lookup from a completed plan's id (exact or canonical) to
|
|
166
|
+
// its actual summary filename. Shared builder (also used by phase.cts's
|
|
167
|
+
// cmdPhasePlanIndex) so the two can never disagree about which summary
|
|
168
|
+
// belongs to which plan.
|
|
169
|
+
const summaryFileByPlanId = buildSummaryFileIndex(summaries, extractCanonicalPlanId);
|
|
170
|
+
// #2830: this primitive previously never parsed depends_on at all — see
|
|
171
|
+
// src/plan-dependency-graph.cts's file header. Build the same
|
|
172
|
+
// PlanHaltNode[] shape phase.cts's cmdPhasePlanIndex builds (id resolution
|
|
173
|
+
// mirrors its planMap/canonicalToId pattern) and hand it to the ONE
|
|
174
|
+
// shared halt-propagation traversal so this reader and the wave-grouping
|
|
175
|
+
// reader can never diverge on the halt rule again.
|
|
176
|
+
const planIds = plans.map(p => p.replace('-PLAN.md', '').replace('PLAN.md', ''));
|
|
177
|
+
const planIdByLower = new Map(planIds.map(id => [id.toLowerCase(), id]));
|
|
178
|
+
const canonicalToPlanId = new Map(plans.map((p, i) => [extractCanonicalPlanId(p).toLowerCase(), planIds[i]]));
|
|
179
|
+
const haltNodes = plans.map((p, i) => {
|
|
180
|
+
const planId = planIds[i];
|
|
76
181
|
const canonical = extractCanonicalPlanId(p);
|
|
77
|
-
|
|
182
|
+
const summaryFile = summaryFileByPlanId.get(planId) ?? summaryFileByPlanId.get(canonical);
|
|
183
|
+
const halted = summaryFile !== undefined && isSummaryFileHalted(node_path_1.default.join(phaseDir, summaryFile));
|
|
184
|
+
const resolvedDependsOn = parsePlanDependsOn(phaseDir, p)
|
|
185
|
+
.map((dep) => {
|
|
186
|
+
const lower = dep.toLowerCase();
|
|
187
|
+
return planIdByLower.get(lower) ?? canonicalToPlanId.get(lower) ?? null;
|
|
188
|
+
})
|
|
189
|
+
.filter((id) => id !== null);
|
|
190
|
+
return { id: planId, resolvedDependsOn, halted };
|
|
78
191
|
});
|
|
192
|
+
const { blockedBy } = computeHaltPropagation(haltNodes);
|
|
193
|
+
const haltedPlans = plans.filter((_, i) => haltNodes[i].halted);
|
|
194
|
+
const incompletePlanSet = new Set(incompletePlans);
|
|
195
|
+
const blockedByFiles = {};
|
|
196
|
+
const runnablePlans = [];
|
|
197
|
+
for (let i = 0; i < plans.length; i++) {
|
|
198
|
+
const p = plans[i];
|
|
199
|
+
if (!incompletePlanSet.has(p))
|
|
200
|
+
continue;
|
|
201
|
+
const causes = blockedBy.get(planIds[i]) ?? [];
|
|
202
|
+
if (causes.length > 0) {
|
|
203
|
+
blockedByFiles[p] = causes;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
runnablePlans.push(p);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
79
209
|
return {
|
|
80
210
|
found: true,
|
|
81
211
|
directory: toPosixPath(node_path_1.default.join(relBase, match)),
|
|
@@ -89,6 +219,9 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
|
89
219
|
has_context: hasContext,
|
|
90
220
|
has_verification: hasVerification,
|
|
91
221
|
has_reviews: hasReviews,
|
|
222
|
+
halted_plans: haltedPlans,
|
|
223
|
+
blocked_by: blockedByFiles,
|
|
224
|
+
runnable_plans: runnablePlans,
|
|
92
225
|
};
|
|
93
226
|
}
|
|
94
227
|
catch {
|
|
@@ -104,63 +237,130 @@ function findPhaseInternal(cwd, phase) {
|
|
|
104
237
|
const current = searchPhaseInDir(phasesDir, relPhasesDir, normalized);
|
|
105
238
|
if (current)
|
|
106
239
|
return current;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const result = searchPhaseInDir(archivePath, relBase, normalized);
|
|
123
|
-
if (result) {
|
|
124
|
-
result.archived = version;
|
|
125
|
-
return result;
|
|
126
|
-
}
|
|
240
|
+
// #2855: scope the archived-milestone fallback to the SAME workstream as the
|
|
241
|
+
// active-phase search above (planningDir(cwd) resolves GSD_WORKSTREAM/GSD_PROJECT
|
|
242
|
+
// the identical way both places), not the hardcoded project-root tree. Archived
|
|
243
|
+
// phases genuinely live under a workstream's own `.planning/workstreams/<ws>/
|
|
244
|
+
// milestones/` — that is where archivePhaseDirectories (milestone.cts) writes
|
|
245
|
+
// them via the same planningDir(cwd) resolution. Hardcoding root here let a
|
|
246
|
+
// pending workstream phase resolve to an unrelated workstream's (or a flat-mode
|
|
247
|
+
// project's) archived phase that merely shares a phase number. Shared with
|
|
248
|
+
// getArchivedPhaseDirs via listArchiveVersionDirs (see its doc comment).
|
|
249
|
+
for (const { version, archivePath } of listArchiveVersionDirs(cwd)) {
|
|
250
|
+
const relBase = toPosixPath(node_path_1.default.relative(cwd, archivePath));
|
|
251
|
+
const result = searchPhaseInDir(archivePath, relBase, normalized);
|
|
252
|
+
if (result) {
|
|
253
|
+
result.archived = version;
|
|
254
|
+
return result;
|
|
127
255
|
}
|
|
128
256
|
}
|
|
129
|
-
catch { /* intentionally empty */ }
|
|
130
257
|
return null;
|
|
131
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* #3185 (epic #3180 Phase 3, ADR-3180 Decision 1 row "Phase enumeration"):
|
|
261
|
+
* the SINGLE canonical owner of "which phase directories belong to the current
|
|
262
|
+
* milestone". Applies the milestone window AND the sentinel filter, in that
|
|
263
|
+
* order, and returns the surviving directory names.
|
|
264
|
+
*
|
|
265
|
+
* Before this existed the derivation had four independent implementations and
|
|
266
|
+
* only `cmdRoadmapAnalyze` carried both halves; `cmdProgressRender`,
|
|
267
|
+
* `cmdStats` and `cmdPhasesList` each carried neither or one.
|
|
268
|
+
*
|
|
269
|
+
* TWO THINGS THIS GETS RIGHT THAT A HEADING-SIDE FILTER CANNOT:
|
|
270
|
+
*
|
|
271
|
+
* 1. The sentinel test runs against DIRECTORY NAMES and is UNCONDITIONAL.
|
|
272
|
+
* `getMilestonePhaseFilter` excludes sentinels from its ROADMAP HEADING
|
|
273
|
+
* set, but when that set is empty it degrades to a literal `() => true`
|
|
274
|
+
* pass-all predicate and never consults the heading set at all — so its
|
|
275
|
+
* own sentinel exclusion becomes unreachable exactly when it is needed,
|
|
276
|
+
* and every directory on disk (backlog included) is reported as a
|
|
277
|
+
* current-milestone phase. That degrade is the #3167 symptom path.
|
|
278
|
+
*
|
|
279
|
+
* 2. The sentinel predicate is the canonical `isSentinelPhaseId`
|
|
280
|
+
* (`src/phase-id.cts`, SENTINEL_RANGES [0, 999]), not a local literal.
|
|
281
|
+
* The rule had five copies and three different regexes before this phase,
|
|
282
|
+
* and they disagreed about Phase 0.
|
|
283
|
+
*
|
|
284
|
+
* The pass-all degrade is narrowed MINIMALLY: it stays over-inclusive for
|
|
285
|
+
* non-sentinel directories, so a project whose window declares no phases
|
|
286
|
+
* still sees its real phase directories. Only sentinels are refused.
|
|
287
|
+
*
|
|
288
|
+
* `scope` distinguishes a REAL empty from a NON-answer (ADR-3180 Decision 2):
|
|
289
|
+
* an absent `phasesDir` is a real empty (a new project genuinely has no
|
|
290
|
+
* phases) and inherits the window's scope, whereas a `phasesDir` that exists
|
|
291
|
+
* but cannot be read is UNREADABLE.
|
|
292
|
+
*
|
|
293
|
+
* `opts.ws` is tri-state, matching `planningDir`'s own contract: `undefined`
|
|
294
|
+
* (the default — do not pass `ws` at all) resolves the AMBIENT workstream
|
|
295
|
+
* from `GSD_WORKSTREAM`; `null` FORCES the project root regardless of any
|
|
296
|
+
* ambient workstream; a string forces that specific workstream.
|
|
297
|
+
*/
|
|
298
|
+
function listMilestonePhaseDirs(phasesDir, opts = {}) {
|
|
299
|
+
// #3597: `ws` must default to `undefined`, NOT `null`. `undefined` means
|
|
300
|
+
// "resolve the ambient workstream" (mirrors planningDir's own contract,
|
|
301
|
+
// src/planning-workspace.cts:124); `null` means "force the project root".
|
|
302
|
+
// Every cwd-bearing caller derives `phasesDir` ambiently (planningPaths(cwd)
|
|
303
|
+
// / planningDir(cwd) with no explicit ws), so defaulting `ws` to `null` here
|
|
304
|
+
// forced the milestone WINDOW to the root ROADMAP while the caller's
|
|
305
|
+
// `phasesDir` stayed workstream-scoped — numerator and denominator drawn
|
|
306
|
+
// from different scoped sets (ADR-3180 §7.6 rule 3 violation). That is what
|
|
307
|
+
// made `--ws <name> progress` read `phase_scope: "unreadable"` and withhold
|
|
308
|
+
// `percent` once `workstream create` migrated the root ROADMAP away.
|
|
309
|
+
const { cwd, ws, versionOverride = null, phaseIdConvention = null } = opts;
|
|
310
|
+
// Without a cwd there is nothing to scope AGAINST — the caller asked for an
|
|
311
|
+
// unscoped read, which is a real answer (mirrors extractCurrentMilestoneScoped's
|
|
312
|
+
// row 1). Sentinels are still refused: they are never milestone phases.
|
|
313
|
+
let inWindow = () => true;
|
|
314
|
+
let scope = SCOPE.COMPLETE;
|
|
315
|
+
if (cwd) {
|
|
316
|
+
const filter = getMilestonePhaseFilter(cwd, versionOverride, phaseIdConvention, ws);
|
|
317
|
+
inWindow = filter;
|
|
318
|
+
scope = filter.scope;
|
|
319
|
+
}
|
|
320
|
+
// An ABSENT phases dir is a real empty, not a failure: a freshly-created
|
|
321
|
+
// project genuinely has no phase directories yet. Distinguishing this from
|
|
322
|
+
// the unreadable case below is the whole point of the scope discriminator.
|
|
323
|
+
if (!node_fs_1.default.existsSync(phasesDir))
|
|
324
|
+
return { value: [], scope };
|
|
325
|
+
let names;
|
|
326
|
+
try {
|
|
327
|
+
names = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true })
|
|
328
|
+
.filter((e) => e.isDirectory())
|
|
329
|
+
.map((e) => e.name);
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
// The directory EXISTS but could not be read (EACCES/EIO). An empty list
|
|
333
|
+
// here is a NON-answer and must not be reported as "this milestone has no
|
|
334
|
+
// phases" — that collapse is the defect class this epic removes.
|
|
335
|
+
return { value: [], scope: SCOPE.UNREADABLE };
|
|
336
|
+
}
|
|
337
|
+
const value = names
|
|
338
|
+
.filter((name) => inWindow(name) && !isSentinelPhaseId(name, phaseIdConvention ?? undefined))
|
|
339
|
+
.sort((a, b) => comparePhaseNum(a, b));
|
|
340
|
+
return { value, scope };
|
|
341
|
+
}
|
|
132
342
|
function getArchivedPhaseDirs(cwd) {
|
|
133
|
-
|
|
343
|
+
// #2855: same workstream-scoped resolution as findPhaseInternal above, via
|
|
344
|
+
// the shared listArchiveVersionDirs helper. `phase.list --include-archived`
|
|
345
|
+
// (the primary non-init consumer) must not leak a different workstream's
|
|
346
|
+
// archive either.
|
|
134
347
|
const results = [];
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
for (const archiveName of phaseDirs) {
|
|
145
|
-
const versionMatch = archiveName.match(/^(v[\d.]+)-phases$/);
|
|
146
|
-
const version = versionMatch[1];
|
|
147
|
-
const archivePath = node_path_1.default.join(milestonesDir, archiveName);
|
|
148
|
-
const dirs = readSubdirectories(archivePath, true);
|
|
149
|
-
for (const dir of dirs) {
|
|
150
|
-
results.push({
|
|
151
|
-
name: dir,
|
|
152
|
-
milestone: version,
|
|
153
|
-
basePath: node_path_1.default.join('.planning', 'milestones', archiveName),
|
|
154
|
-
fullPath: node_path_1.default.join(archivePath, dir),
|
|
155
|
-
});
|
|
156
|
-
}
|
|
348
|
+
for (const { version, archivePath } of listArchiveVersionDirs(cwd)) {
|
|
349
|
+
const dirs = readSubdirectories(archivePath, true);
|
|
350
|
+
for (const dir of dirs) {
|
|
351
|
+
results.push({
|
|
352
|
+
name: dir,
|
|
353
|
+
milestone: version,
|
|
354
|
+
basePath: toPosixPath(node_path_1.default.relative(cwd, archivePath)),
|
|
355
|
+
fullPath: node_path_1.default.join(archivePath, dir),
|
|
356
|
+
});
|
|
157
357
|
}
|
|
158
358
|
}
|
|
159
|
-
catch { /* intentionally empty */ }
|
|
160
359
|
return results;
|
|
161
360
|
}
|
|
162
361
|
module.exports = {
|
|
163
362
|
searchPhaseInDir,
|
|
164
363
|
findPhaseInternal,
|
|
165
364
|
getArchivedPhaseDirs,
|
|
365
|
+
listMilestonePhaseDirs,
|
|
166
366
|
};
|