@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
|
@@ -14,6 +14,7 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
14
14
|
const node_os_1 = __importDefault(require("node:os"));
|
|
15
15
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
16
16
|
const clock_cjs_1 = require("./clock.cjs");
|
|
17
|
+
const pattern_cjs_1 = require("./pattern.cjs");
|
|
17
18
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- io.cjs is an export= CommonJS module
|
|
18
19
|
const io = require("./io.cjs");
|
|
19
20
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- config-loader.cjs is an export= CommonJS module
|
|
@@ -38,6 +39,7 @@ const secrets_cjs_1 = require("./secrets.cjs");
|
|
|
38
39
|
const scanPhasePlans = require("./plan-scan.cjs");
|
|
39
40
|
const state_document_cjs_1 = require("./state-document.cjs");
|
|
40
41
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
42
|
+
const host_runtime_detection_cjs_1 = require("./host-runtime-detection.cjs");
|
|
41
43
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- commands.cjs is an export= CommonJS module
|
|
42
44
|
const commandsMod = require("./commands.cjs");
|
|
43
45
|
const security_cjs_1 = require("./security.cjs");
|
|
@@ -72,18 +74,21 @@ const resolution_cjs_1 = require("./resolution.cjs");
|
|
|
72
74
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- onboard-projection.cjs is an export= CommonJS module
|
|
73
75
|
const onboardProjection = require("./onboard-projection.cjs");
|
|
74
76
|
const { REQUIRED_CODEBASE_MAP_FILES, buildOnboardProjection, hasCodeFilesInternal, hasPackageFileInternal, listCodebaseMapFiles, } = onboardProjection;
|
|
75
|
-
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- verify-command-grounding.cjs is an export= CommonJS module
|
|
78
|
+
const verifyCommandGrounding = require("./verify-command-grounding.cjs");
|
|
79
|
+
const { harvestPriorVerifyCommands } = verifyCommandGrounding;
|
|
80
|
+
const { output, error, ERROR_REASON, formatDiagnosticToken } = io;
|
|
76
81
|
const { loadConfig, loadConfigResolved } = configLoader;
|
|
77
82
|
const { resolveModelInternal, resolveGranularityInternal, assertValidGranularityOverride } = modelResolver;
|
|
78
|
-
const { findPhaseInternal } = phaseLocator;
|
|
79
|
-
const { getRoadmapPhaseInternal, getMilestoneInfo,
|
|
83
|
+
const { findPhaseInternal, listMilestonePhaseDirs, listAllPhaseDirs } = phaseLocator;
|
|
84
|
+
const { getRoadmapPhaseInternal, getMilestoneInfo, stripShippedMilestones, extractCurrentMilestone, } = roadmapParser;
|
|
80
85
|
const { pathExistsInternal, generateSlugInternal, toPosixPath } = coreUtils;
|
|
81
|
-
const {
|
|
86
|
+
const { normalizePhaseName, matchPhaseDirs, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, isForeignPrefixedPhaseQuery, isSentinelPhaseId, extractPhaseToken, scopeToPhase } = phaseId;
|
|
82
87
|
const { pruneOrphanedWorktrees } = worktreeSafety;
|
|
83
|
-
const { planningPaths, planningDir, planningRoot, listAvailableWorkstreams,
|
|
88
|
+
const { planningPaths, planningDir, planningRoot, listAvailableWorkstreams, peekActiveWorkstream, diagnoseUnresolvedActiveWorkstream, describeUnresolvedWorkstreamReason, findContextMdIn, } = planningWorkspace;
|
|
84
89
|
const { determinePhaseStatus } = commandsMod;
|
|
85
90
|
const { extractFrontmatter } = frontmatterMod;
|
|
86
|
-
const {
|
|
91
|
+
const { isPhaseComplete, resolveVerificationFile, resolveUatFile } = verificationMod;
|
|
87
92
|
const { evaluateUatPassed } = uatPredicateMod;
|
|
88
93
|
const { resolveLoopHooks } = loopResolverMod;
|
|
89
94
|
const { loadRegistry } = capabilityLoaderMod;
|
|
@@ -104,7 +109,7 @@ function phaseInfoMatchesExactPrefix(phaseInfo, phase) {
|
|
|
104
109
|
function roadmapPhaseMatchesExactPrefix(roadmapPhase, phase) {
|
|
105
110
|
const sectionRaw = roadmapPhase?.['section'];
|
|
106
111
|
const section = typeof sectionRaw === 'string' ? sectionRaw : '';
|
|
107
|
-
return new RegExp(`^#{2,4}\\s*Phase\\s+${escapeRegex(phase)}(?:\\b|\\s|:)`, 'i').test(section);
|
|
112
|
+
return new RegExp(`^#{2,4}\\s*Phase\\s+${(0, pattern_cjs_1.escapeRegex)(phase)}(?:\\b|\\s|:)`, 'i').test(section);
|
|
108
113
|
}
|
|
109
114
|
// #2104: shared helpers that wrap findPhaseInternal / getRoadmapPhaseInternal
|
|
110
115
|
// with the #2056 foreign-prefix guard, so every init command gets the same
|
|
@@ -127,9 +132,12 @@ function guardedGetRoadmapPhase(cwd, phase, projectCode) {
|
|
|
127
132
|
// directory exists yet) identically at every synthetic-fallback call site
|
|
128
133
|
// below — factored out once so the slugification formula itself cannot drift.
|
|
129
134
|
function slugifyPhaseName(phaseName) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
135
|
+
// #3883 (ADR-3473 §8.3): delegate to the canonical slug formula
|
|
136
|
+
// (generateSlugInternal, core-utils.cts) rather than re-implementing it.
|
|
137
|
+
// `maxLen: null` preserves this site's pre-migration untruncated contract —
|
|
138
|
+
// the 60-char default would collapse two distinct >60-char phase names onto
|
|
139
|
+
// the same reported phase_slug.
|
|
140
|
+
return phaseName ? coreUtils.generateSlugInternal(phaseName, null) : null;
|
|
133
141
|
}
|
|
134
142
|
/**
|
|
135
143
|
* #2994 (review finding, DEFECT.GENERATIVE-FIX): shared archived/not-found
|
|
@@ -170,39 +178,49 @@ function listPhaseSummaryFiles(phaseDir) {
|
|
|
170
178
|
function listPhasePlanFiles(phaseDir) {
|
|
171
179
|
return scanPhasePlans(phaseDir)['planFiles'];
|
|
172
180
|
}
|
|
173
|
-
function projectCompletionStatus(implementationComplete,
|
|
174
|
-
if (
|
|
181
|
+
function projectCompletionStatus(implementationComplete, phaseComplete) {
|
|
182
|
+
if (phaseComplete)
|
|
175
183
|
return 'complete';
|
|
176
184
|
if (implementationComplete)
|
|
177
185
|
return 'executed';
|
|
178
186
|
return 'incomplete';
|
|
179
187
|
}
|
|
180
188
|
function buildPhaseCompletionProjection(cwd, phaseNumber, phaseDir, planCount, summaryCount, slashRuntime) {
|
|
189
|
+
// ADR-3180 §7.4 (issue #3186) / DO-NOT-MIGRATE exemption
|
|
190
|
+
// (scripts/lint-completion-predicate-drift.cjs FUNCTION_SCOPED_EXEMPTIONS,
|
|
191
|
+
// declared deviation): `implementation_complete` answers "are the plans
|
|
192
|
+
// done" (a `scanPhasePlans`-shaped different question, per the design's
|
|
193
|
+
// 0.x-split), NOT "is the phase complete" — it is kept for the
|
|
194
|
+
// 'executed'-vs-'planned' disk_status distinction downstream consumers
|
|
195
|
+
// still rely on, which `isPhaseComplete`'s locked `{ complete, verification
|
|
196
|
+
// }` return shape does not carry.
|
|
181
197
|
const implementationComplete = planCount > 0 && summaryCount >= planCount;
|
|
182
198
|
const phaseFullDir = phaseDir ? node_path_1.default.join(cwd, phaseDir) : '';
|
|
183
|
-
// #
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
199
|
+
// #3168 / ADR-3180 §7.4 (disk-strict, #2957): route through the canonical
|
|
200
|
+
// owner (`src/verification.cts` · `isPhaseComplete`), which calls
|
|
201
|
+
// readVerificationStatus UNCONDITIONALLY — plan count is NOT a
|
|
202
|
+
// precondition. A zero-plan phase with a passing `*-VERIFICATION.md` is
|
|
203
|
+
// complete; init used to gate the read on `implementationComplete` and
|
|
204
|
+
// synthesize a `not_required` sentinel instead, which is the #3168 defect.
|
|
205
|
+
// #2617: the router still owns both the message content and the runtime
|
|
206
|
+
// projection; init passes the phase number it already knows (its phaseDir
|
|
188
207
|
// is unresolved in some branches, where the router could not derive one).
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
: { status: 'not_required', next_action: '', next_command: '' };
|
|
208
|
+
const completionResult = isPhaseComplete(phaseFullDir, { runtime: slashRuntime, phaseNumber });
|
|
209
|
+
const verificationStatus = completionResult.value.verification;
|
|
192
210
|
const projectedVerificationStatus = verificationStatus.status;
|
|
193
211
|
const projectedVerificationAction = verificationStatus.next_action;
|
|
194
212
|
const verificationPassed = projectedVerificationStatus === 'passed';
|
|
195
|
-
const phaseComplete =
|
|
213
|
+
const phaseComplete = completionResult.value.complete;
|
|
196
214
|
return {
|
|
197
215
|
implementation_complete: implementationComplete,
|
|
198
216
|
verification_status: projectedVerificationStatus,
|
|
199
217
|
verification_passed: verificationPassed,
|
|
200
218
|
phase_complete: phaseComplete,
|
|
201
|
-
completion_status: projectCompletionStatus(implementationComplete,
|
|
219
|
+
completion_status: projectCompletionStatus(implementationComplete, phaseComplete),
|
|
202
220
|
verification_next_action: projectedVerificationAction,
|
|
203
221
|
verification_next_command: verificationStatus.next_command,
|
|
204
|
-
// #3057 B3:
|
|
205
|
-
//
|
|
222
|
+
// #3057 B3: readVerificationStatus's result carries this flag when its
|
|
223
|
+
// internal staleness check could not run to completion.
|
|
206
224
|
verification_stale_check_indeterminate: 'staleCheckIndeterminate' in verificationStatus
|
|
207
225
|
&& verificationStatus.staleCheckIndeterminate === true,
|
|
208
226
|
};
|
|
@@ -222,7 +240,8 @@ function getLatestCompletedMilestone(cwd) {
|
|
|
222
240
|
}
|
|
223
241
|
function withProjectRoot(cwd, result) {
|
|
224
242
|
result['project_root'] = cwd;
|
|
225
|
-
|
|
243
|
+
// #3245: the reported agent_runtime gets a host-detection rung below the two explicit sources; every other resolveRuntime caller keeps the old ladder (ADR-2313 scope boundary).
|
|
244
|
+
const activeRuntime = (0, host_runtime_detection_cjs_1.resolveReportedRuntime)(cwd);
|
|
226
245
|
const agentStatus = checkAgentsInstalled(activeRuntime, cwd);
|
|
227
246
|
result['agents_installed'] = agentStatus.agents_installed;
|
|
228
247
|
result['missing_agents'] = agentStatus.missing_agents;
|
|
@@ -415,7 +434,12 @@ function detectHasPriorPhases(cwd, phaseInfo) {
|
|
|
415
434
|
catch {
|
|
416
435
|
continue;
|
|
417
436
|
}
|
|
418
|
-
|
|
437
|
+
// #3511-class: scope the raw listing to THIS entry's own phase artifacts
|
|
438
|
+
// before the bare `.some()` predicate runs, so a stray `07-VERIFICATION.md`
|
|
439
|
+
// physically sitting in another phase's directory cannot make that
|
|
440
|
+
// directory appear to have its own verification report.
|
|
441
|
+
const scopedFiles = scopeToPhase(files, entry.name);
|
|
442
|
+
if (scopedFiles.some((f) => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md')) {
|
|
419
443
|
return true;
|
|
420
444
|
}
|
|
421
445
|
}
|
|
@@ -537,7 +561,7 @@ function detectPhaseMvpMode(cwd, phaseNumber) {
|
|
|
537
561
|
return false;
|
|
538
562
|
const rawContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
539
563
|
const content = extractCurrentMilestone(rawContent, cwd);
|
|
540
|
-
const escapedPhase = escapeRegex(phaseNumber);
|
|
564
|
+
const escapedPhase = (0, pattern_cjs_1.escapeRegex)(phaseNumber);
|
|
541
565
|
const phaseHeader = new RegExp(`#{2,4}\\s*Phase\\s+${escapedPhase}(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:`, 'i');
|
|
542
566
|
const headerMatch = content.match(phaseHeader);
|
|
543
567
|
if (!headerMatch || headerMatch.index === undefined)
|
|
@@ -594,7 +618,11 @@ function detectUiPhaseActive(cwd, phaseInfo) {
|
|
|
594
618
|
// comments elsewhere in this file), same technique as detectHasPriorPhases above.
|
|
595
619
|
const dirName = node_path_1.default.basename(rawDir);
|
|
596
620
|
const files = node_fs_1.default.readdirSync(node_path_1.default.join(planningDir(cwd), 'phases', dirName));
|
|
597
|
-
|
|
621
|
+
// #3511-class: scope the raw listing to this phase dir before the
|
|
622
|
+
// phase-numbered -UI-SPEC.md predicate, so a stray cross-phase
|
|
623
|
+
// UI-SPEC file cannot flip this phase's ui-phase-active flag.
|
|
624
|
+
const scopedFiles = scopeToPhase(files, dirName);
|
|
625
|
+
hasUiSpecFile = scopedFiles.some((f) => f.endsWith('-UI-SPEC.md') || f === 'UI-SPEC.md');
|
|
598
626
|
}
|
|
599
627
|
catch {
|
|
600
628
|
hasUiSpecFile = false;
|
|
@@ -691,13 +719,32 @@ function buildSectionManifestField(cwd, phaseInfo, options, workflow, overrides
|
|
|
691
719
|
return null;
|
|
692
720
|
}
|
|
693
721
|
}
|
|
722
|
+
/**
|
|
723
|
+
* #3216 review Finding 1: `getMilestoneInfo(cwd).value` unwrap-and-cast was
|
|
724
|
+
* repeated identically (comment included) at five init call sites — factored
|
|
725
|
+
* out once so the cast and its `?? {}` "no milestone resolved" fallback live
|
|
726
|
+
* in exactly one place. Behavior-preserving: same call, same fallback, same
|
|
727
|
+
* cast, for every caller.
|
|
728
|
+
*/
|
|
729
|
+
function milestoneRecord(cwd) {
|
|
730
|
+
return (getMilestoneInfo(cwd).value ?? {});
|
|
731
|
+
}
|
|
694
732
|
function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
695
733
|
if (!phase) {
|
|
696
734
|
error('phase required for init execute-phase');
|
|
697
735
|
}
|
|
698
736
|
const config = loadConfig(cwd);
|
|
699
737
|
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
700
|
-
|
|
738
|
+
// #3216: getMilestoneInfo now returns a ScopedResult — `.value` carries the
|
|
739
|
+
// MilestoneInfo (or null on any non-COMPLETE scope). NOT display-only: when
|
|
740
|
+
// `branching_strategy === 'milestone'`, `milestone['version']`/`['name']`
|
|
741
|
+
// below feed `branch_name` construction (see the milestone_branch_template
|
|
742
|
+
// branch below), so an unresolved milestone changes the constructed branch
|
|
743
|
+
// name, not merely what gets printed. bracket-access below naturally reads
|
|
744
|
+
// `undefined` when unresolved; the `milestone_version`/`milestone_name`
|
|
745
|
+
// output fields below coerce that to an explicit `null` (#3216 review
|
|
746
|
+
// Finding 2) so the key is never silently omitted from the JSON bundle.
|
|
747
|
+
const milestone = milestoneRecord(cwd);
|
|
701
748
|
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
702
749
|
phaseInfo = applyRoadmapFallback(phaseInfo, roadmapPhase, (rp) => {
|
|
703
750
|
const phaseName = rp['phase_name'];
|
|
@@ -725,6 +772,13 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
725
772
|
: null;
|
|
726
773
|
const phase_req_ids = reqExtracted && reqExtracted !== 'TBD' ? reqExtracted : null;
|
|
727
774
|
const wf = (config.workflow ?? {});
|
|
775
|
+
// #3188: these paths are null when the file is absent, matching the contract
|
|
776
|
+
// the conditional sibling fields (context_path, patterns_path, ...) already
|
|
777
|
+
// honour and that ultraplan-phase.md / execute-phase.md gate on. Hoisted so
|
|
778
|
+
// the existence check and the emitted path share one source of truth.
|
|
779
|
+
const statePath = node_path_1.default.join(planningDir(cwd), 'STATE.md');
|
|
780
|
+
const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
|
|
781
|
+
const requirementsPath = node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md');
|
|
728
782
|
const result = {
|
|
729
783
|
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
|
|
730
784
|
verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
|
|
@@ -744,7 +798,17 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
744
798
|
? toPosixPath(node_path_1.default.join(cwd, phaseInfo['directory']))
|
|
745
799
|
: null,
|
|
746
800
|
phase_number: phaseInfo?.['phase_number'] || null,
|
|
747
|
-
|
|
801
|
+
// #3171: prefer the ROADMAP's curated display name for `phase_name`. When
|
|
802
|
+
// the phase directory already exists on disk, the disk-lookup path
|
|
803
|
+
// (searchPhaseInDir) derives phase_name from the directory-name remainder
|
|
804
|
+
// — itself an already-slugified value (`phase.add` writes `${num}-${slug}`
|
|
805
|
+
// dirs), so phase_name and phase_slug come out byte-identical. An
|
|
806
|
+
// orchestrator wiring this field into `state begin-phase --name` then
|
|
807
|
+
// lands a raw slug in STATE.md's current_phase_name. The ROADMAP carries
|
|
808
|
+
// the human-curated display name (`### Phase N: <Name>`); prefer it,
|
|
809
|
+
// matching the no-disk fallback above. phase_slug stays disk-derived — it
|
|
810
|
+
// correctly feeds branch-name construction below and is unchanged here.
|
|
811
|
+
phase_name: (roadmapPhase?.['phase_name']) || (phaseInfo?.['phase_name']) || null,
|
|
748
812
|
phase_slug: phaseInfo?.['phase_slug'] || null,
|
|
749
813
|
phase_req_ids,
|
|
750
814
|
plans: phaseInfo?.['plans'] || [],
|
|
@@ -768,22 +832,23 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
768
832
|
.replace('{slug}', phaseInfo['phase_slug'] || 'phase')
|
|
769
833
|
: config.branching_strategy === 'milestone'
|
|
770
834
|
? config.milestone_branch_template
|
|
771
|
-
.replace('{milestone}', milestone['version'])
|
|
835
|
+
.replace('{milestone}', milestone['version'] ?? '')
|
|
772
836
|
.replace('{slug}', generateSlugInternal(milestone['name']) || 'milestone')
|
|
773
837
|
: null,
|
|
774
|
-
milestone_version: milestone['version'],
|
|
775
|
-
milestone_name: milestone['name'],
|
|
838
|
+
milestone_version: milestone['version'] ?? null,
|
|
839
|
+
milestone_name: milestone['name'] ?? null,
|
|
776
840
|
milestone_slug: generateSlugInternal(milestone['name']),
|
|
777
841
|
state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
778
842
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
779
843
|
config_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'config.json')),
|
|
780
844
|
// #2376: emit absolute paths — see comment above on phase_dir.
|
|
781
|
-
|
|
782
|
-
|
|
845
|
+
// #3188: null when the file is absent (parity with patterns_path/context_path).
|
|
846
|
+
state_path: node_fs_1.default.existsSync(statePath) ? toPosixPath(statePath) : null,
|
|
847
|
+
roadmap_path: node_fs_1.default.existsSync(roadmapPath) ? toPosixPath(roadmapPath) : null,
|
|
783
848
|
config_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'config.json')),
|
|
784
849
|
// #2376: execute-phase.md's verify_phase_goal step reads this instead of
|
|
785
850
|
// hardcoding '.planning/REQUIREMENTS.md' into the gsd-verifier spawn prompt.
|
|
786
|
-
requirements_path:
|
|
851
|
+
requirements_path: node_fs_1.default.existsSync(requirementsPath) ? toPosixPath(requirementsPath) : null,
|
|
787
852
|
};
|
|
788
853
|
if (options['validate']) {
|
|
789
854
|
try {
|
|
@@ -860,6 +925,11 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
860
925
|
assertValidGranularityOverride(granularityOverride, error);
|
|
861
926
|
const granularity = resolveGranularityInternal(cwd, 'planning', granularityOverride || undefined);
|
|
862
927
|
const wf = (config.workflow ?? {});
|
|
928
|
+
// #3188: see cmdInitExecutePhase — null when absent, parity with the
|
|
929
|
+
// conditional sibling fields in this same result object.
|
|
930
|
+
const statePath = node_path_1.default.join(planningDir(cwd), 'STATE.md');
|
|
931
|
+
const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
|
|
932
|
+
const requirementsPath = node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md');
|
|
863
933
|
const result = {
|
|
864
934
|
researcher_model: resolveModelInternal(cwd, 'gsd-phase-researcher'),
|
|
865
935
|
planner_model: resolveModelInternal(cwd, 'gsd-planner'),
|
|
@@ -896,42 +966,89 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
896
966
|
planning_exists: node_fs_1.default.existsSync(planningDir(cwd)),
|
|
897
967
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
898
968
|
// #2376: absolute — see comment on phase_dir above.
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
969
|
+
// #3188: null when the file is absent (parity with patterns_path below).
|
|
970
|
+
state_path: node_fs_1.default.existsSync(statePath) ? toPosixPath(statePath) : null,
|
|
971
|
+
roadmap_path: node_fs_1.default.existsSync(roadmapPath) ? toPosixPath(roadmapPath) : null,
|
|
972
|
+
requirements_path: node_fs_1.default.existsSync(requirementsPath) ? toPosixPath(requirementsPath) : null,
|
|
902
973
|
patterns_path: null,
|
|
903
974
|
};
|
|
904
975
|
if (phaseInfo?.['directory']) {
|
|
905
976
|
const phaseDirFull = node_path_1.default.join(cwd, phaseInfo['directory']);
|
|
906
977
|
try {
|
|
907
978
|
const files = node_fs_1.default.readdirSync(phaseDirFull);
|
|
908
|
-
const
|
|
979
|
+
const phaseDirName = node_path_1.default.basename(phaseDirFull);
|
|
980
|
+
// #3511 BLOCKER-3: scope the raw listing to THIS phase's own artifacts
|
|
981
|
+
// before any bare `.find()` predicate runs, so a `04-UAT.md` (or
|
|
982
|
+
// `04-RESEARCH.md`/`04-REVIEWS.md`/`04-PATTERNS.md`) sitting in phase
|
|
983
|
+
// 03's directory cannot win a phase-03 lookup — the same
|
|
984
|
+
// `isPhaseArtifact` membership rule `resolveVerificationFile` already
|
|
985
|
+
// applies via `phaseDirName` below. `findContextMdIn` is passed the
|
|
986
|
+
// scoped array (rather than the raw directory path) so this call site
|
|
987
|
+
// alone is scoped; its other call sites are unaffected.
|
|
988
|
+
const scopedFiles = scopeToPhase(files, phaseDirName);
|
|
989
|
+
const contextFile = findContextMdIn(scopedFiles);
|
|
909
990
|
if (contextFile) {
|
|
910
991
|
result['context_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, contextFile));
|
|
911
992
|
}
|
|
912
|
-
const researchFile =
|
|
993
|
+
const researchFile = scopedFiles.find((f) => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
|
|
913
994
|
if (researchFile) {
|
|
914
995
|
result['research_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, researchFile));
|
|
915
996
|
}
|
|
916
|
-
|
|
997
|
+
// #3473 F2: routed through the shared resolver — readdir order is
|
|
998
|
+
// filesystem-dependent, so the prior hand-rolled `.find()` could pick
|
|
999
|
+
// either file when a phase held both a canonical report and an ad-hoc
|
|
1000
|
+
// `-CORRECTION-VERIFICATION.md` worksheet (#3357).
|
|
1001
|
+
// #3492: pin selection to THIS phase's own token so a stray cross-phase
|
|
1002
|
+
// or sentinel-numbered canonically-shaped file cannot outrank this
|
|
1003
|
+
// phase's own (possibly non-canonical) report.
|
|
1004
|
+
const phaseToken = extractPhaseToken(phaseDirName);
|
|
1005
|
+
const verificationFile = resolveVerificationFile(files, {
|
|
1006
|
+
allowBare: true,
|
|
1007
|
+
phaseToken,
|
|
1008
|
+
phaseDirName,
|
|
1009
|
+
});
|
|
917
1010
|
if (verificationFile) {
|
|
918
1011
|
result['verification_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, verificationFile));
|
|
919
1012
|
}
|
|
920
|
-
|
|
1013
|
+
// #3518: routed through the shared UAT resolver — the prior hand-rolled
|
|
1014
|
+
// `.find()` over unsorted readdir order had no phase check and no
|
|
1015
|
+
// ordering, so a stray cross-phase 02-UAT.md could become this phase's
|
|
1016
|
+
// uat_path, filesystem-dependently. Pinned to this phase's own token
|
|
1017
|
+
// (same rule as verification_path above), and phase-scoped via
|
|
1018
|
+
// phaseDirName (#3511) so the alphabetically-first fallback tier also
|
|
1019
|
+
// excludes cross-phase strays.
|
|
1020
|
+
const uatFile = resolveUatFile(files, {
|
|
1021
|
+
allowBare: true,
|
|
1022
|
+
phaseToken,
|
|
1023
|
+
phaseDirName,
|
|
1024
|
+
});
|
|
921
1025
|
if (uatFile) {
|
|
922
1026
|
result['uat_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, uatFile));
|
|
923
1027
|
}
|
|
924
|
-
const reviewsFile =
|
|
1028
|
+
const reviewsFile = scopedFiles.find((f) => f.endsWith('-REVIEWS.md') || f === 'REVIEWS.md');
|
|
925
1029
|
if (reviewsFile) {
|
|
926
1030
|
result['reviews_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, reviewsFile));
|
|
927
1031
|
}
|
|
928
|
-
const patternsFile =
|
|
1032
|
+
const patternsFile = scopedFiles.find((f) => f.endsWith('-PATTERNS.md') || f === 'PATTERNS.md');
|
|
929
1033
|
if (patternsFile) {
|
|
930
1034
|
result['patterns_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, patternsFile));
|
|
931
1035
|
}
|
|
932
1036
|
}
|
|
933
|
-
catch {
|
|
934
|
-
|
|
1037
|
+
catch (err) {
|
|
1038
|
+
// #3885 (ADR-3473 §8.5): this branch means `phaseInfo['directory']` was
|
|
1039
|
+
// set (the phase was already resolved to an on-disk directory) yet
|
|
1040
|
+
// `readdirSync` still failed — ENOENT here would be a genuine race
|
|
1041
|
+
// (the directory vanished between resolution and this read) and stays
|
|
1042
|
+
// a silent degrade like the prior behavior; any other errno
|
|
1043
|
+
// (EACCES/EIO/...) is an unreadable-not-absent directory and must be
|
|
1044
|
+
// named, or every conditional field this block sets (context_path,
|
|
1045
|
+
// research_path, verification_path, uat_path, reviews_path,
|
|
1046
|
+
// patterns_path) silently reads as "none of these exist".
|
|
1047
|
+
const code = err?.code;
|
|
1048
|
+
if (code !== 'ENOENT') {
|
|
1049
|
+
result['context_read_error'] =
|
|
1050
|
+
`Could not read phase directory ${formatDiagnosticToken(phaseDirFull)}: ${formatDiagnosticToken(err?.message ?? String(err))}`;
|
|
1051
|
+
}
|
|
935
1052
|
}
|
|
936
1053
|
}
|
|
937
1054
|
if (options['validate']) {
|
|
@@ -958,6 +1075,29 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
958
1075
|
}
|
|
959
1076
|
// #2992 (Phase 6.1): additive, optional field — degrades to null, never throws.
|
|
960
1077
|
result['section_manifest'] = buildSectionManifestField(cwd, phaseInfo, options, 'plan-phase');
|
|
1078
|
+
// #2401: prior-phase verify commands, surfaced UNGATED — additive field, never
|
|
1079
|
+
// conditioned on context_window. Before this, the planner only inherited
|
|
1080
|
+
// prior-phase verify-command context when context_window >= 500000, so at
|
|
1081
|
+
// lower context windows it re-invented (and mis-resolved) the command. The
|
|
1082
|
+
// harvest already degrades to `{commands: [], readError}` rather than
|
|
1083
|
+
// throwing; the try/catch is defense-in-depth so init never breaks on this.
|
|
1084
|
+
let priorVerifyCommands = [];
|
|
1085
|
+
try {
|
|
1086
|
+
// #2401 review fix: harvestPriorVerifyCommands accepts a phase-id token
|
|
1087
|
+
// (string) directly, so a decimal phase like '2.1' is no longer silently
|
|
1088
|
+
// dropped by `Number('2.1')` producing a value the old `number`-only
|
|
1089
|
+
// parameter mishandled for lettered/decimal tokens.
|
|
1090
|
+
if (phaseNumberPlan !== null) {
|
|
1091
|
+
priorVerifyCommands = harvestPriorVerifyCommands({
|
|
1092
|
+
planningDir: planningPaths(cwd).phases,
|
|
1093
|
+
beforePhase: phaseNumberPlan,
|
|
1094
|
+
}).commands;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
catch {
|
|
1098
|
+
priorVerifyCommands = [];
|
|
1099
|
+
}
|
|
1100
|
+
result['prior_verify_commands'] = priorVerifyCommands;
|
|
961
1101
|
output(withProjectRoot(cwd, result), raw);
|
|
962
1102
|
}
|
|
963
1103
|
function cmdInitNewProject(cwd, raw, options = {}) {
|
|
@@ -979,7 +1119,7 @@ function cmdInitNewProject(cwd, raw, options = {}) {
|
|
|
979
1119
|
synthesizer_model: resolveModelInternal(cwd, 'gsd-research-synthesizer'),
|
|
980
1120
|
roadmapper_model: resolveModelInternal(cwd, 'gsd-roadmapper'),
|
|
981
1121
|
commit_docs: config.commit_docs,
|
|
982
|
-
project_exists: pathExistsInternal(cwd, '
|
|
1122
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'PROJECT.md')))),
|
|
983
1123
|
has_codebase_map: hasCodebaseMap,
|
|
984
1124
|
planning_exists: pathExistsInternal(cwd, '.planning'),
|
|
985
1125
|
has_existing_code: hasCode,
|
|
@@ -1010,22 +1150,14 @@ function cmdInitNewProject(cwd, raw, options = {}) {
|
|
|
1010
1150
|
}
|
|
1011
1151
|
function cmdInitNewMilestone(cwd, raw, options = {}) {
|
|
1012
1152
|
const config = loadConfig(cwd);
|
|
1013
|
-
const milestone =
|
|
1153
|
+
const milestone = milestoneRecord(cwd);
|
|
1014
1154
|
const latestCompleted = getLatestCompletedMilestone(cwd);
|
|
1015
1155
|
const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
.readdirSync(phasesDir, { withFileTypes: true })
|
|
1022
|
-
.filter((entry) => entry.isDirectory() && isDirInMilestone(entry.name))
|
|
1023
|
-
.length;
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
catch {
|
|
1027
|
-
/* intentionally empty */
|
|
1028
|
-
}
|
|
1156
|
+
// #3185 (ADR-3180 Decision 1): "how many phase directories belong to the
|
|
1157
|
+
// CURRENT milestone" is exactly the scoped question listMilestonePhaseDirs
|
|
1158
|
+
// owns — routed through it instead of a local readdirSync + hand-rolled
|
|
1159
|
+
// window filter (which also never excluded sentinels, unlike the owner).
|
|
1160
|
+
const phaseDirCount = listMilestonePhaseDirs(phasesDir, { cwd }).value.length;
|
|
1029
1161
|
const wf = (config.workflow ?? {});
|
|
1030
1162
|
const result = {
|
|
1031
1163
|
researcher_model: resolveModelInternal(cwd, 'gsd-project-researcher'),
|
|
@@ -1033,8 +1165,12 @@ function cmdInitNewMilestone(cwd, raw, options = {}) {
|
|
|
1033
1165
|
roadmapper_model: resolveModelInternal(cwd, 'gsd-roadmapper'),
|
|
1034
1166
|
commit_docs: config.commit_docs,
|
|
1035
1167
|
research_enabled: wf['research'],
|
|
1036
|
-
|
|
1037
|
-
|
|
1168
|
+
// #3216 review Finding 2: `?? null` so an unresolved milestone still emits
|
|
1169
|
+
// the key with an explicit `null` rather than letting JSON.stringify drop
|
|
1170
|
+
// it — an omitted key reaches the prompt layer's `{current_milestone}`
|
|
1171
|
+
// placeholder as literal, un-substituted text.
|
|
1172
|
+
current_milestone: milestone['version'] ?? null,
|
|
1173
|
+
current_milestone_name: milestone['name'] ?? null,
|
|
1038
1174
|
latest_completed_milestone: latestCompleted?.version || null,
|
|
1039
1175
|
latest_completed_milestone_name: latestCompleted?.name || null,
|
|
1040
1176
|
phase_dir_count: phaseDirCount,
|
|
@@ -1042,7 +1178,7 @@ function cmdInitNewMilestone(cwd, raw, options = {}) {
|
|
|
1042
1178
|
phase_archive_path: latestCompleted
|
|
1043
1179
|
? toPosixPath(node_path_1.default.join(planningRoot(cwd), 'milestones', `${latestCompleted.version}-phases`))
|
|
1044
1180
|
: null,
|
|
1045
|
-
project_exists: pathExistsInternal(cwd, '
|
|
1181
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'PROJECT.md')))),
|
|
1046
1182
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1047
1183
|
state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
1048
1184
|
project_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'PROJECT.md')),
|
|
@@ -1070,7 +1206,13 @@ function cmdInitNewMilestone(cwd, raw, options = {}) {
|
|
|
1070
1206
|
// source as `cmdInitTransition`: `GSD_WORKSTREAM` env, falling back to the
|
|
1071
1207
|
// stored active-workstream pointer (mirrors `cmdInitProgress`'s own
|
|
1072
1208
|
// resolution above).
|
|
1073
|
-
|
|
1209
|
+
//
|
|
1210
|
+
// #3579 root-cause fix: this is a read-only informational field (no write
|
|
1211
|
+
// follows), so use the non-mutating peek — getActiveWorkstream's self-heal
|
|
1212
|
+
// would otherwise silently delete a stale/invalid pointer as a side effect
|
|
1213
|
+
// of building a JSON report field, and (per #3579) could change what a
|
|
1214
|
+
// LATER resolution in the same process observes.
|
|
1215
|
+
const resolvedWorkstream = process.env['GSD_WORKSTREAM'] || peekActiveWorkstream(cwd);
|
|
1074
1216
|
const workstreamActive = !!resolvedWorkstream;
|
|
1075
1217
|
const flatMode = !workstreamActive;
|
|
1076
1218
|
// #2992 (Phase 6.1): additive, optional field — degrades to null, never throws.
|
|
@@ -1082,7 +1224,7 @@ function cmdInitNewMilestone(cwd, raw, options = {}) {
|
|
|
1082
1224
|
}
|
|
1083
1225
|
function cmdInitQuick(cwd, description, raw, options = {}) {
|
|
1084
1226
|
const config = loadConfig(cwd);
|
|
1085
|
-
const now = new Date();
|
|
1227
|
+
const now = new Date(clock_cjs_1.realClock.now());
|
|
1086
1228
|
const slug = description ? generateSlugInternal(description)?.substring(0, 40) : null;
|
|
1087
1229
|
const yy = String(now.getFullYear()).slice(-2);
|
|
1088
1230
|
const mm = String(now.getMonth() + 1).padStart(2, '0');
|
|
@@ -1142,7 +1284,7 @@ function cmdInitQuick(cwd, description, raw, options = {}) {
|
|
|
1142
1284
|
function cmdInitIngestDocs(cwd, raw) {
|
|
1143
1285
|
const config = loadConfig(cwd);
|
|
1144
1286
|
const result = {
|
|
1145
|
-
project_exists: pathExistsInternal(cwd, '
|
|
1287
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'PROJECT.md')))),
|
|
1146
1288
|
planning_exists: node_fs_1.default.existsSync(planningRoot(cwd)),
|
|
1147
1289
|
...getInitGitState(cwd),
|
|
1148
1290
|
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase. The
|
|
@@ -1183,7 +1325,7 @@ function cmdInitResume(cwd, raw) {
|
|
|
1183
1325
|
const result = {
|
|
1184
1326
|
state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
1185
1327
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1186
|
-
project_exists: pathExistsInternal(cwd, '
|
|
1328
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'PROJECT.md')))),
|
|
1187
1329
|
planning_exists: node_fs_1.default.existsSync(planningRoot(cwd)),
|
|
1188
1330
|
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
|
|
1189
1331
|
state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
@@ -1500,9 +1642,11 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
1500
1642
|
directory: null,
|
|
1501
1643
|
phase_number: roadmapPhase['phase_number'],
|
|
1502
1644
|
phase_name: phaseName,
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1645
|
+
// #3883 (ADR-3473 §8.3): delegate to the canonical slug formula
|
|
1646
|
+
// (generateSlugInternal, core-utils.cts) rather than re-implementing
|
|
1647
|
+
// it. `maxLen: null` preserves this site's pre-migration untruncated
|
|
1648
|
+
// contract.
|
|
1649
|
+
phase_slug: phaseName ? coreUtils.generateSlugInternal(phaseName, null) : null,
|
|
1506
1650
|
plans: [],
|
|
1507
1651
|
summaries: [],
|
|
1508
1652
|
incomplete_plans: [],
|
|
@@ -1521,9 +1665,11 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
1521
1665
|
directory: null,
|
|
1522
1666
|
phase_number: roadmapPhase['phase_number'],
|
|
1523
1667
|
phase_name: phaseName,
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1668
|
+
// #3883 (ADR-3473 §8.3): delegate to the canonical slug formula
|
|
1669
|
+
// (generateSlugInternal, core-utils.cts) rather than re-implementing
|
|
1670
|
+
// it. `maxLen: null` preserves this site's pre-migration untruncated
|
|
1671
|
+
// contract.
|
|
1672
|
+
phase_slug: phaseName ? coreUtils.generateSlugInternal(phaseName, null) : null,
|
|
1527
1673
|
plans: [],
|
|
1528
1674
|
summaries: [],
|
|
1529
1675
|
incomplete_plans: [],
|
|
@@ -1548,6 +1694,11 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
1548
1694
|
expectedPhaseDir = toPosixPath(node_path_1.default.join(planningPaths(cwd).phases, dirName));
|
|
1549
1695
|
}
|
|
1550
1696
|
}
|
|
1697
|
+
// #3188: see cmdInitExecutePhase — null when absent, parity with the
|
|
1698
|
+
// conditional sibling fields in this same result object.
|
|
1699
|
+
const statePath = node_path_1.default.join(planningDir(cwd), 'STATE.md');
|
|
1700
|
+
const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
|
|
1701
|
+
const requirementsPath = node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md');
|
|
1551
1702
|
const result = {
|
|
1552
1703
|
commit_docs: config.commit_docs,
|
|
1553
1704
|
brave_search: typeof config.brave_search === 'string'
|
|
@@ -1576,37 +1727,75 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
1576
1727
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1577
1728
|
planning_exists: node_fs_1.default.existsSync(planningDir(cwd)),
|
|
1578
1729
|
// #2376: absolute — see comment on phase_dir above.
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1730
|
+
// #3188: null when the file is absent (parity with context_path/research_path).
|
|
1731
|
+
state_path: node_fs_1.default.existsSync(statePath) ? toPosixPath(statePath) : null,
|
|
1732
|
+
roadmap_path: node_fs_1.default.existsSync(roadmapPath) ? toPosixPath(roadmapPath) : null,
|
|
1733
|
+
requirements_path: node_fs_1.default.existsSync(requirementsPath) ? toPosixPath(requirementsPath) : null,
|
|
1582
1734
|
};
|
|
1583
1735
|
if (phaseInfo?.['directory']) {
|
|
1584
1736
|
const phaseDirFull = node_path_1.default.join(cwd, phaseInfo['directory']);
|
|
1585
1737
|
try {
|
|
1586
1738
|
const files = node_fs_1.default.readdirSync(phaseDirFull);
|
|
1587
|
-
const
|
|
1739
|
+
const phaseDirName = node_path_1.default.basename(phaseDirFull);
|
|
1740
|
+
// #3511 BLOCKER-3: see the parallel site above — scope before any bare
|
|
1741
|
+
// `.find()` predicate so a misfiled cross-phase artifact cannot win.
|
|
1742
|
+
const scopedFiles = scopeToPhase(files, phaseDirName);
|
|
1743
|
+
const contextFile = findContextMdIn(scopedFiles);
|
|
1588
1744
|
if (contextFile) {
|
|
1589
1745
|
result['context_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, contextFile));
|
|
1590
1746
|
}
|
|
1591
|
-
const researchFile =
|
|
1747
|
+
const researchFile = scopedFiles.find((f) => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
|
|
1592
1748
|
if (researchFile) {
|
|
1593
1749
|
result['research_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, researchFile));
|
|
1594
1750
|
}
|
|
1595
|
-
|
|
1751
|
+
// #3473 F2: routed through the shared resolver — readdir order is
|
|
1752
|
+
// filesystem-dependent, so the prior hand-rolled `.find()` could pick
|
|
1753
|
+
// either file when a phase held both a canonical report and an ad-hoc
|
|
1754
|
+
// `-CORRECTION-VERIFICATION.md` worksheet (#3357).
|
|
1755
|
+
// #3492: pin selection to THIS phase's own token so a stray cross-phase
|
|
1756
|
+
// or sentinel-numbered canonically-shaped file cannot outrank this
|
|
1757
|
+
// phase's own (possibly non-canonical) report.
|
|
1758
|
+
const phaseToken = extractPhaseToken(phaseDirName);
|
|
1759
|
+
const verificationFile = resolveVerificationFile(files, {
|
|
1760
|
+
allowBare: true,
|
|
1761
|
+
phaseToken,
|
|
1762
|
+
phaseDirName,
|
|
1763
|
+
});
|
|
1596
1764
|
if (verificationFile) {
|
|
1597
1765
|
result['verification_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, verificationFile));
|
|
1598
1766
|
}
|
|
1599
|
-
|
|
1767
|
+
// #3518: routed through the shared UAT resolver — the prior hand-rolled
|
|
1768
|
+
// `.find()` over unsorted readdir order had no phase check and no
|
|
1769
|
+
// ordering, so a stray cross-phase 02-UAT.md could become this phase's
|
|
1770
|
+
// uat_path, filesystem-dependently. Pinned to this phase's own token
|
|
1771
|
+
// (same rule as verification_path above), and phase-scoped via
|
|
1772
|
+
// phaseDirName (#3511) so the alphabetically-first fallback tier also
|
|
1773
|
+
// excludes cross-phase strays.
|
|
1774
|
+
const uatFile = resolveUatFile(files, {
|
|
1775
|
+
allowBare: true,
|
|
1776
|
+
phaseToken,
|
|
1777
|
+
phaseDirName,
|
|
1778
|
+
});
|
|
1600
1779
|
if (uatFile) {
|
|
1601
1780
|
result['uat_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, uatFile));
|
|
1602
1781
|
}
|
|
1603
|
-
const reviewsFile =
|
|
1782
|
+
const reviewsFile = scopedFiles.find((f) => f.endsWith('-REVIEWS.md') || f === 'REVIEWS.md');
|
|
1604
1783
|
if (reviewsFile) {
|
|
1605
1784
|
result['reviews_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, reviewsFile));
|
|
1606
1785
|
}
|
|
1607
1786
|
}
|
|
1608
|
-
catch {
|
|
1609
|
-
|
|
1787
|
+
catch (err) {
|
|
1788
|
+
// #3885 (ADR-3473 §8.5): see the parallel site in cmdInitPlanPhase —
|
|
1789
|
+
// ENOENT here is a genuine race (directory vanished after resolution)
|
|
1790
|
+
// and stays a silent degrade; any other errno (EACCES/EIO/...) means
|
|
1791
|
+
// the directory exists but could not be read, and must be named rather
|
|
1792
|
+
// than silently reported the same as "none of context_path/
|
|
1793
|
+
// research_path/verification_path/uat_path/reviews_path exist".
|
|
1794
|
+
const code = err?.code;
|
|
1795
|
+
if (code !== 'ENOENT') {
|
|
1796
|
+
result['context_read_error'] =
|
|
1797
|
+
`Could not read phase directory ${formatDiagnosticToken(phaseDirFull)}: ${formatDiagnosticToken(err?.message ?? String(err))}`;
|
|
1798
|
+
}
|
|
1610
1799
|
}
|
|
1611
1800
|
}
|
|
1612
1801
|
output(withProjectRoot(cwd, result), raw);
|
|
@@ -1669,7 +1858,7 @@ function cmdInitTodos(cwd, area, raw) {
|
|
|
1669
1858
|
}
|
|
1670
1859
|
function cmdInitMilestoneOp(cwd, raw) {
|
|
1671
1860
|
const config = loadConfig(cwd);
|
|
1672
|
-
const milestone =
|
|
1861
|
+
const milestone = milestoneRecord(cwd);
|
|
1673
1862
|
let phaseCount = 0;
|
|
1674
1863
|
let completedPhases = 0;
|
|
1675
1864
|
const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
|
|
@@ -1682,7 +1871,8 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
1682
1871
|
const phasePattern = new RegExp(`#{2,4}\\s*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:`, 'gi');
|
|
1683
1872
|
let m;
|
|
1684
1873
|
while ((m = phasePattern.exec(currentSection)) !== null) {
|
|
1685
|
-
|
|
1874
|
+
// #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
|
|
1875
|
+
if (isSentinelPhaseId(m[1]))
|
|
1686
1876
|
continue;
|
|
1687
1877
|
roadmapPhaseNumbers.push(m[1]);
|
|
1688
1878
|
}
|
|
@@ -1694,20 +1884,23 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
1694
1884
|
const m = tok.match(/^(\d+)([A-Z]?(?:\.\d+)*)$/);
|
|
1695
1885
|
return m ? String(parseInt(m[1], 10)) + m[2] : tok;
|
|
1696
1886
|
};
|
|
1887
|
+
// #3882 (ADR-3473 §8.2): this used to hand-roll a readdirSync over the
|
|
1888
|
+
// phases directory (a heading->directory LOOKUP INDEX, same role as
|
|
1889
|
+
// cmdRoadmapAnalyze's `_phaseDirNames` — `roadmapPhaseNumbers` above is
|
|
1890
|
+
// already scoped/sentinel-excluded, so this map must see the PHYSICAL set
|
|
1891
|
+
// to resolve each heading's phase number to its actual directory name;
|
|
1892
|
+
// scoping it again would look up inside an already-scoped set for no
|
|
1893
|
+
// benefit). Routed through the named "physical set, sentinels included"
|
|
1894
|
+
// axis instead: every `num` looked up below came from `roadmapPhaseNumbers`
|
|
1895
|
+
// (sentinels already excluded there), so a sentinel entry surviving in
|
|
1896
|
+
// this map is never read — inclusion is output-invariant, this only
|
|
1897
|
+
// removes the re-derivation.
|
|
1697
1898
|
const diskPhaseDirs = new Map();
|
|
1698
|
-
|
|
1699
|
-
const
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
const m = stripProjectCodePrefix(e.name).match(new RegExp(`^(${PHASE_NUMBER_TOKEN_SOURCE})`));
|
|
1704
|
-
if (!m)
|
|
1705
|
-
continue;
|
|
1706
|
-
diskPhaseDirs.set(canonicalizePhase(m[1]), e.name);
|
|
1707
|
-
}
|
|
1708
|
-
}
|
|
1709
|
-
catch {
|
|
1710
|
-
/* intentionally empty */
|
|
1899
|
+
for (const name of listAllPhaseDirs(phasesDir, { includeSentinels: true }).value) {
|
|
1900
|
+
const m = stripProjectCodePrefix(name).match(new RegExp(`^(${PHASE_NUMBER_TOKEN_SOURCE})`));
|
|
1901
|
+
if (!m)
|
|
1902
|
+
continue;
|
|
1903
|
+
diskPhaseDirs.set(canonicalizePhase(m[1]), name);
|
|
1711
1904
|
}
|
|
1712
1905
|
if (roadmapPhaseNumbers.length > 0) {
|
|
1713
1906
|
phaseCount = roadmapPhaseNumbers.length;
|
|
@@ -1727,8 +1920,12 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
1727
1920
|
}
|
|
1728
1921
|
else {
|
|
1729
1922
|
try {
|
|
1730
|
-
|
|
1731
|
-
|
|
1923
|
+
// #3185 (ADR-3180 Decision 1): the ROADMAP heading scan above found no
|
|
1924
|
+
// current-milestone phase headings — fall back to asking the canonical
|
|
1925
|
+
// owner "which phase directories belong to the current milestone"
|
|
1926
|
+
// directly, instead of a hand-rolled readdirSync over every directory
|
|
1927
|
+
// on disk (which also never excluded sentinels, unlike the owner).
|
|
1928
|
+
const dirs = listMilestonePhaseDirs(phasesDir, { cwd }).value;
|
|
1732
1929
|
phaseCount = dirs.length;
|
|
1733
1930
|
for (const dir of dirs) {
|
|
1734
1931
|
try {
|
|
@@ -1758,15 +1955,19 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
1758
1955
|
}
|
|
1759
1956
|
const result = {
|
|
1760
1957
|
commit_docs: config.commit_docs,
|
|
1761
|
-
|
|
1762
|
-
|
|
1958
|
+
// #3216 review Finding 2: `?? null` so an unresolved milestone still emits
|
|
1959
|
+
// the key with an explicit `null` rather than letting JSON.stringify drop
|
|
1960
|
+
// it — an omitted key reaches the prompt layer's `{milestone_version}`
|
|
1961
|
+
// placeholder as literal, un-substituted text.
|
|
1962
|
+
milestone_version: milestone['version'] ?? null,
|
|
1963
|
+
milestone_name: milestone['name'] ?? null,
|
|
1763
1964
|
milestone_slug: generateSlugInternal(milestone['name']),
|
|
1764
1965
|
phase_count: phaseCount,
|
|
1765
1966
|
completed_phases: completedPhases,
|
|
1766
1967
|
all_phases_complete: phaseCount > 0 && phaseCount === completedPhases,
|
|
1767
1968
|
archived_milestones: archivedMilestones,
|
|
1768
1969
|
archive_count: archivedMilestones.length,
|
|
1769
|
-
project_exists: pathExistsInternal(cwd, '
|
|
1970
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'PROJECT.md')))),
|
|
1770
1971
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1771
1972
|
state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
1772
1973
|
archive_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningRoot(cwd), 'archive')),
|
|
@@ -1776,7 +1977,12 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
1776
1977
|
}
|
|
1777
1978
|
function cmdInitMapCodebase(cwd, raw) {
|
|
1778
1979
|
const config = loadConfig(cwd);
|
|
1779
|
-
|
|
1980
|
+
// #3964: scoped like the payload's own codebase_dir/codebase_dir_exists
|
|
1981
|
+
// below (and verify.cts's codebase drift check) — has_maps/existing_maps
|
|
1982
|
+
// reading the flat root made the same payload claim a scoped codebase dir
|
|
1983
|
+
// exists while reporting zero maps, so map-codebase's Refresh/Skip gate
|
|
1984
|
+
// always forced a re-map under GSD_PROJECT.
|
|
1985
|
+
const codebaseDir = node_path_1.default.join(planningDir(cwd), 'codebase');
|
|
1780
1986
|
let existingMaps = [];
|
|
1781
1987
|
try {
|
|
1782
1988
|
existingMaps = node_fs_1.default.readdirSync(codebaseDir).filter((f) => f.endsWith('.md'));
|
|
@@ -1793,17 +1999,20 @@ function cmdInitMapCodebase(cwd, raw) {
|
|
|
1793
1999
|
date: clock_cjs_1.realClock.localToday(),
|
|
1794
2000
|
timestamp: clock_cjs_1.realClock.nowIso(),
|
|
1795
2001
|
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
|
|
1796
|
-
|
|
2002
|
+
// #3964: scoped like verify.cts's codebase drift check (planningDir, not
|
|
2003
|
+
// the flat planningRoot) so the two surfaces cannot disagree under
|
|
2004
|
+
// GSD_PROJECT.
|
|
2005
|
+
codebase_dir: toPosixPath(node_path_1.default.join(planningDir(cwd), 'codebase')),
|
|
1797
2006
|
existing_maps: existingMaps,
|
|
1798
2007
|
has_maps: existingMaps.length > 0,
|
|
1799
2008
|
planning_exists: pathExistsInternal(cwd, '.planning'),
|
|
1800
|
-
codebase_dir_exists: pathExistsInternal(cwd, '
|
|
2009
|
+
codebase_dir_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'codebase')))),
|
|
1801
2010
|
};
|
|
1802
2011
|
output(withProjectRoot(cwd, result), raw);
|
|
1803
2012
|
}
|
|
1804
2013
|
function cmdInitManager(cwd, raw) {
|
|
1805
2014
|
const config = loadConfig(cwd);
|
|
1806
|
-
const milestone =
|
|
2015
|
+
const milestone = milestoneRecord(cwd);
|
|
1807
2016
|
const _slashRuntime = (0, runtime_slash_cjs_1.resolveRuntime)(cwd);
|
|
1808
2017
|
const paths = planningPaths(cwd);
|
|
1809
2018
|
if (!node_fs_1.default.existsSync(paths.roadmap)) {
|
|
@@ -1815,18 +2024,12 @@ function cmdInitManager(cwd, raw) {
|
|
|
1815
2024
|
const rawContent = node_fs_1.default.readFileSync(paths.roadmap, 'utf-8');
|
|
1816
2025
|
const content = extractCurrentMilestone(rawContent, cwd);
|
|
1817
2026
|
const phasesDir = paths.phases;
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
.map((e) => e.name);
|
|
1825
|
-
}
|
|
1826
|
-
catch {
|
|
1827
|
-
return [];
|
|
1828
|
-
}
|
|
1829
|
-
})();
|
|
2027
|
+
// #3185 (ADR-3180 Decision 1): "which phase directories belong to the
|
|
2028
|
+
// CURRENT milestone" is the scoped question listMilestonePhaseDirs owns —
|
|
2029
|
+
// routed through it instead of a hand-rolled readdirSync + a separate
|
|
2030
|
+
// getMilestonePhaseFilter window check (which also never excluded
|
|
2031
|
+
// sentinels, unlike the owner).
|
|
2032
|
+
const _phaseDirEntries = listMilestonePhaseDirs(phasesDir, { cwd }).value;
|
|
1830
2033
|
const _checkboxStates = new Map();
|
|
1831
2034
|
const _cbPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
|
|
1832
2035
|
let _cbMatch;
|
|
@@ -1861,16 +2064,24 @@ function cmdInitManager(cwd, raw) {
|
|
|
1861
2064
|
let isActive = false;
|
|
1862
2065
|
let completion = buildPhaseCompletionProjection(cwd, phaseNum, null, planCount, summaryCount, _slashRuntime);
|
|
1863
2066
|
try {
|
|
1864
|
-
|
|
1865
|
-
|
|
2067
|
+
// #3185 (ADR-3180 Decision 2) moved this lookup off the
|
|
2068
|
+
// milestone-scoped set and onto the physical one; that scope choice is
|
|
2069
|
+
// kept. Only the matcher is this PR's: matchPhaseDirs resolves
|
|
2070
|
+
// digit-leading directory names the token predicate cannot (#2528).
|
|
2071
|
+
const dirMatch = matchPhaseDirs(_phaseDirEntries, normalized).matches[0];
|
|
1866
2072
|
if (dirMatch) {
|
|
1867
2073
|
const fullDir = node_path_1.default.join(phasesDir, dirMatch);
|
|
1868
2074
|
const phaseDirRel = toPosixPath(node_path_1.default.relative(cwd, fullDir));
|
|
1869
2075
|
const phaseFiles = node_fs_1.default.readdirSync(fullDir);
|
|
1870
2076
|
planCount = listPhasePlanFiles(fullDir).length;
|
|
1871
2077
|
summaryCount = listPhaseSummaryFiles(fullDir).length;
|
|
1872
|
-
|
|
1873
|
-
|
|
2078
|
+
// #3511-class: scope the raw listing to THIS phase's own artifacts
|
|
2079
|
+
// before the hasContext/hasResearch predicates run, so a stray
|
|
2080
|
+
// cross-phase `-CONTEXT.md`/`-RESEARCH.md` sitting in this directory
|
|
2081
|
+
// cannot win this phase's lookup.
|
|
2082
|
+
const scopedFiles = scopeToPhase(phaseFiles, dirMatch);
|
|
2083
|
+
hasContext = findContextMdIn(scopedFiles) !== null;
|
|
2084
|
+
hasResearch = scopedFiles.some((f) => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
|
|
1874
2085
|
completion = buildPhaseCompletionProjection(cwd, phaseNum, phaseDirRel, planCount, summaryCount, _slashRuntime);
|
|
1875
2086
|
if (completion.phase_complete)
|
|
1876
2087
|
diskStatus = 'complete';
|
|
@@ -1886,7 +2097,7 @@ function cmdInitManager(cwd, raw) {
|
|
|
1886
2097
|
diskStatus = 'discussed';
|
|
1887
2098
|
else
|
|
1888
2099
|
diskStatus = 'empty';
|
|
1889
|
-
const nowMs =
|
|
2100
|
+
const nowMs = clock_cjs_1.realClock.now();
|
|
1890
2101
|
let newestMtime = 0;
|
|
1891
2102
|
for (const f of phaseFiles) {
|
|
1892
2103
|
try {
|
|
@@ -1907,18 +2118,20 @@ function cmdInitManager(cwd, raw) {
|
|
|
1907
2118
|
catch {
|
|
1908
2119
|
/* intentionally empty */
|
|
1909
2120
|
}
|
|
2121
|
+
// ADR-3180 §7.4 (disk-strict, #2957, maintainer decision 2026-08-08):
|
|
2122
|
+
// `roadmapComplete` is reported below as metadata only — it carries NO
|
|
2123
|
+
// machine authority over `diskStatus`. The #3033 checkbox override that
|
|
2124
|
+
// used to live here (treating a zero-plan phase as complete whenever the
|
|
2125
|
+
// ROADMAP checkbox was ticked, layered on top of
|
|
2126
|
+
// buildPhaseCompletionProjection's own output) is DELETED, not
|
|
2127
|
+
// generalized: `diskStatus` now comes entirely from `completion`, which
|
|
2128
|
+
// already routes through the canonical owner (`isPhaseComplete`) and
|
|
2129
|
+
// itself resolves a zero-plan phase as complete whenever a passing
|
|
2130
|
+
// `*-VERIFICATION.md` exists (#3168) — with no dependency on the
|
|
2131
|
+
// checkbox. A zero-plan phase whose completion previously relied SOLELY
|
|
2132
|
+
// on a ticked checkbox (no passing verification) now reports incomplete;
|
|
2133
|
+
// this is the deliberate Tier-2 break (ADR-3180 §7.4 Decision 3).
|
|
1910
2134
|
const roadmapComplete = _checkboxStates.get(phaseNum) || false;
|
|
1911
|
-
// #3033: a zero-plan phase (split parent — intentionally plan-less, holds
|
|
1912
|
-
// shared context for sub-phases) whose roadmap checkbox is marked complete
|
|
1913
|
-
// must resolve as complete. The original gate required completion.phase_complete
|
|
1914
|
-
// (derived from plan/summary counts), which is always false for zero-plan
|
|
1915
|
-
// phases — so the checkbox override never fired and the parent was permanently
|
|
1916
|
-
// stuck as 'researched' (an in-progress state eligible for current-phase
|
|
1917
|
-
// selection). Now: when the roadmap marks it complete AND it has zero plans,
|
|
1918
|
-
// treat it as complete regardless of the plan-count derivation.
|
|
1919
|
-
if (roadmapComplete && (completion.phase_complete || planCount === 0) && diskStatus !== 'complete') {
|
|
1920
|
-
diskStatus = 'complete';
|
|
1921
|
-
}
|
|
1922
2135
|
phases.push({
|
|
1923
2136
|
number: phaseNum,
|
|
1924
2137
|
name: phaseName,
|
|
@@ -2010,7 +2223,13 @@ function cmdInitManager(cwd, raw) {
|
|
|
2010
2223
|
}
|
|
2011
2224
|
let waitingSignal = null;
|
|
2012
2225
|
try {
|
|
2013
|
-
|
|
2226
|
+
// #3964: mirror cmdSignalWaiting's write locations exactly — `.gsd/`
|
|
2227
|
+
// first when it exists, else the project-aware planning dir — so the
|
|
2228
|
+
// signal is read from the project (and location) it is written to.
|
|
2229
|
+
const gsdWaiting = node_path_1.default.join(cwd, '.gsd', 'WAITING.json');
|
|
2230
|
+
const waitingPath = node_fs_1.default.existsSync(node_path_1.default.join(cwd, '.gsd'))
|
|
2231
|
+
? gsdWaiting
|
|
2232
|
+
: node_path_1.default.join(planningDir(cwd), 'WAITING.json');
|
|
2014
2233
|
const waitingRaw = (0, shell_command_projection_cjs_1.platformReadSync)(waitingPath);
|
|
2015
2234
|
if (waitingRaw !== null) {
|
|
2016
2235
|
waitingSignal = JSON.parse(waitingRaw);
|
|
@@ -2023,7 +2242,8 @@ function cmdInitManager(cwd, raw) {
|
|
|
2023
2242
|
for (const phase of phases) {
|
|
2024
2243
|
if (phase['disk_status'] === 'complete')
|
|
2025
2244
|
continue;
|
|
2026
|
-
|
|
2245
|
+
// #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
|
|
2246
|
+
if (isSentinelPhaseId(phase['number']))
|
|
2027
2247
|
continue;
|
|
2028
2248
|
if (phase['disk_status'] === 'executed') {
|
|
2029
2249
|
recommendedActions.push({
|
|
@@ -2077,7 +2297,8 @@ function cmdInitManager(cwd, raw) {
|
|
|
2077
2297
|
}
|
|
2078
2298
|
return true;
|
|
2079
2299
|
});
|
|
2080
|
-
|
|
2300
|
+
// #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
|
|
2301
|
+
const nonBacklogPhases = phases.filter((p) => !isSentinelPhaseId(p['number']));
|
|
2081
2302
|
const completedCount = nonBacklogPhases.filter((p) => p['phase_complete'] === true).length;
|
|
2082
2303
|
const sanitizeFlags = (rawVal) => {
|
|
2083
2304
|
const val = typeof rawVal === 'string' ? rawVal : '';
|
|
@@ -2100,8 +2321,12 @@ function cmdInitManager(cwd, raw) {
|
|
|
2100
2321
|
execute: sanitizeFlags(mgrFlags?.['execute']),
|
|
2101
2322
|
};
|
|
2102
2323
|
const result = {
|
|
2103
|
-
|
|
2104
|
-
|
|
2324
|
+
// #3216 review Finding 2: `?? null` so an unresolved milestone still emits
|
|
2325
|
+
// the key with an explicit `null` rather than letting JSON.stringify drop
|
|
2326
|
+
// it — an omitted key reaches the prompt layer's `{milestone_version}`
|
|
2327
|
+
// placeholder as literal, un-substituted text.
|
|
2328
|
+
milestone_version: milestone['version'] ?? null,
|
|
2329
|
+
milestone_name: milestone['name'] ?? null,
|
|
2105
2330
|
phases,
|
|
2106
2331
|
phase_count: phases.length,
|
|
2107
2332
|
completed_count: completedCount,
|
|
@@ -2109,7 +2334,7 @@ function cmdInitManager(cwd, raw) {
|
|
|
2109
2334
|
recommended_actions: filteredActions,
|
|
2110
2335
|
waiting_signal: waitingSignal,
|
|
2111
2336
|
all_complete: completedCount === nonBacklogPhases.length && nonBacklogPhases.length > 0,
|
|
2112
|
-
project_exists: pathExistsInternal(cwd, '
|
|
2337
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'PROJECT.md')))),
|
|
2113
2338
|
roadmap_exists: true,
|
|
2114
2339
|
state_exists: true,
|
|
2115
2340
|
manager_flags: managerFlags,
|
|
@@ -2212,7 +2437,7 @@ function cmdInitDocsUpdate(cwd, raw, options = {}) {
|
|
|
2212
2437
|
* `state:chunked-mode`/`state:plan-strategy-converge`). This DELIBERATELY
|
|
2213
2438
|
* does not replace `update.md`'s own `parse_update_channel` case-statement
|
|
2214
2439
|
* (`TAG="next"`/`TAG="latest"`) — issue #815's regression test
|
|
2215
|
-
* (`tests/
|
|
2440
|
+
* (`tests/update-workflow.test.cjs`) asserts that literal
|
|
2216
2441
|
* case-statement text stays in `update.md` verbatim (the npm dist-tag
|
|
2217
2442
|
* selection has to run in the workflow's own shell before any `gsd_run`
|
|
2218
2443
|
* round-trip), so `next_channel` exists purely to gate the `channel-banner`
|
|
@@ -2258,7 +2483,9 @@ function cmdInitUpdate(cwd, raw, options = {}) {
|
|
|
2258
2483
|
* pure JSON consumer with no `gsd_run` call of its own.
|
|
2259
2484
|
*/
|
|
2260
2485
|
function cmdInitTransition(cwd, raw, options = {}) {
|
|
2261
|
-
|
|
2486
|
+
// #3579 root-cause fix: read-only informational field — peek, don't
|
|
2487
|
+
// self-heal (see cmdInitNewMilestone's identical rationale above).
|
|
2488
|
+
const resolvedWorkstream = process.env['GSD_WORKSTREAM'] || peekActiveWorkstream(cwd);
|
|
2262
2489
|
const workstreamActive = !!resolvedWorkstream;
|
|
2263
2490
|
const result = {
|
|
2264
2491
|
other_active_workstreams: workstreamActive
|
|
@@ -2339,7 +2566,7 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2339
2566
|
/* intentionally empty */
|
|
2340
2567
|
}
|
|
2341
2568
|
const config = loadConfig(cwd);
|
|
2342
|
-
const milestone =
|
|
2569
|
+
const milestone = milestoneRecord(cwd);
|
|
2343
2570
|
const _slashRuntime = (0, runtime_slash_cjs_1.resolveRuntime)(cwd);
|
|
2344
2571
|
// #1912: fail safe in workstream mode with no active workstream. With no active
|
|
2345
2572
|
// workstream and no --ws, planningDir(cwd) resolves to root .planning — silently
|
|
@@ -2347,11 +2574,27 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2347
2574
|
// Mirror planningDir's resolution (GSD_WORKSTREAM env > stored active pointer) so
|
|
2348
2575
|
// an explicit --ws (which sets GSD_WORKSTREAM) satisfies the check.
|
|
2349
2576
|
const _availableWorkstreams = listAvailableWorkstreams(cwd);
|
|
2350
|
-
|
|
2577
|
+
// #3579 root-cause fix: this is a check, not a consuming read — use the
|
|
2578
|
+
// non-mutating peek so an unresolvable pointer isn't self-healed (cleared)
|
|
2579
|
+
// here and then found "absent" by diagnoseUnresolvedActiveWorkstream below,
|
|
2580
|
+
// which would misreport a present-but-bad marker as no marker at all.
|
|
2581
|
+
const _resolvedWorkstream = process.env['GSD_WORKSTREAM'] || peekActiveWorkstream(cwd);
|
|
2351
2582
|
if (_availableWorkstreams.length > 0 && !_resolvedWorkstream) {
|
|
2583
|
+
// #3579: getActiveWorkstream now inherits a pointer-less session's read
|
|
2584
|
+
// from the shared .planning/active-workstream marker, so reaching this
|
|
2585
|
+
// branch with a marker actually present means the marker EXISTED but
|
|
2586
|
+
// didn't resolve (invalid name, or its workstream dir is gone) — a
|
|
2587
|
+
// materially different situation from "nothing was ever set" and one
|
|
2588
|
+
// that deserves its own diagnostic instead of the generic message below.
|
|
2589
|
+
const _diagnosis = diagnoseUnresolvedActiveWorkstream(cwd);
|
|
2590
|
+
if (_diagnosis.present) {
|
|
2591
|
+
error(`init.progress requires a workstream in workstream mode — the active-workstream marker names '${_diagnosis.value}', but it did not resolve: ${describeUnresolvedWorkstreamReason(_diagnosis.reason)}. Root STATE.md (likely stale) would be reported otherwise. ` +
|
|
2592
|
+
`Pass --ws <name> or run ${(0, runtime_slash_cjs_1.formatGsdSlash)('workstream set', _slashRuntime)} to point it at an existing workstream. ` +
|
|
2593
|
+
`Available workstreams: ${_availableWorkstreams.join(', ')}`, ERROR_REASON.WORKSTREAM_MODE_MARKER_UNRESOLVED, { marker_value: _diagnosis.value, marker_reason: _diagnosis.reason });
|
|
2594
|
+
}
|
|
2352
2595
|
error(`init.progress requires a workstream in workstream mode — no active workstream is set, so root STATE.md (likely stale) would be reported. ` +
|
|
2353
2596
|
`Pass --ws <name> or run ${(0, runtime_slash_cjs_1.formatGsdSlash)('workstream set', _slashRuntime)} first. ` +
|
|
2354
|
-
`Available workstreams: ${_availableWorkstreams.join(', ')}
|
|
2597
|
+
`Available workstreams: ${_availableWorkstreams.join(', ')}`, ERROR_REASON.WORKSTREAM_MODE_NONE_ACTIVE);
|
|
2355
2598
|
}
|
|
2356
2599
|
const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
|
|
2357
2600
|
const phases = [];
|
|
@@ -2378,21 +2621,15 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2378
2621
|
catch {
|
|
2379
2622
|
/* intentionally empty */
|
|
2380
2623
|
}
|
|
2381
|
-
const isDirInMilestone = getMilestonePhaseFilter(cwd);
|
|
2382
2624
|
const seenPhaseNums = new Set();
|
|
2383
2625
|
try {
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
const pb = b.match(new RegExp(`^(${PHASE_NUMBER_TOKEN_SOURCE})`, 'i'));
|
|
2392
|
-
if (!pa || !pb)
|
|
2393
|
-
return a.localeCompare(b);
|
|
2394
|
-
return parseInt(pa[1], 10) - parseInt(pb[1], 10);
|
|
2395
|
-
});
|
|
2626
|
+
// #3185 (ADR-3180 Decision 1): "which phase directories belong to the
|
|
2627
|
+
// CURRENT milestone" — routed through the canonical owner instead of a
|
|
2628
|
+
// hand-rolled readdirSync + isDirInMilestone filter + local sort (which
|
|
2629
|
+
// also never excluded sentinels, unlike the owner; the final `phases`
|
|
2630
|
+
// array is re-sorted below anyway, so dropping the local sort here is
|
|
2631
|
+
// behavior-preserving).
|
|
2632
|
+
const dirs = listMilestonePhaseDirs(phasesDir, { cwd }).value;
|
|
2396
2633
|
for (const dir of dirs) {
|
|
2397
2634
|
const dirMatch = dir.match(new RegExp(`^(${PHASE_NUMBER_TOKEN_SOURCE})-?(.*)`, 'i'));
|
|
2398
2635
|
const phaseNumber = dirMatch ? dirMatch[1] : dir;
|
|
@@ -2402,7 +2639,12 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2402
2639
|
const phaseFiles = node_fs_1.default.readdirSync(phasePath);
|
|
2403
2640
|
const plans = listPhasePlanFiles(phasePath);
|
|
2404
2641
|
const summaries = listPhaseSummaryFiles(phasePath);
|
|
2405
|
-
|
|
2642
|
+
// #3511-class: scope the raw listing to THIS phase's own artifacts
|
|
2643
|
+
// before the hasResearch predicate runs, so a stray cross-phase
|
|
2644
|
+
// `-RESEARCH.md` sitting in this directory cannot win this phase's
|
|
2645
|
+
// lookup.
|
|
2646
|
+
const scopedPhaseFiles = scopeToPhase(phaseFiles, dir);
|
|
2647
|
+
const hasResearch = scopedPhaseFiles.some((f) => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
|
|
2406
2648
|
const phaseDirRel = toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'phases', dir)));
|
|
2407
2649
|
const completion = buildPhaseCompletionProjection(cwd, phaseNumber, phaseDirRel, plans.length, summaries.length, _slashRuntime);
|
|
2408
2650
|
const status = completion.phase_complete
|
|
@@ -2448,7 +2690,11 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2448
2690
|
const status = 'not_started';
|
|
2449
2691
|
const phaseInfo = {
|
|
2450
2692
|
number: num,
|
|
2451
|
-
|
|
2693
|
+
// #3883 (ADR-3473 §8.3): delegate to the canonical slug formula
|
|
2694
|
+
// (generateSlugInternal, core-utils.cts) rather than re-implementing
|
|
2695
|
+
// it. `maxLen: null` preserves this site's pre-migration untruncated
|
|
2696
|
+
// contract.
|
|
2697
|
+
name: coreUtils.generateSlugInternal(name, null) ?? '',
|
|
2452
2698
|
directory: null,
|
|
2453
2699
|
status,
|
|
2454
2700
|
plan_count: 0,
|
|
@@ -2464,6 +2710,25 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2464
2710
|
}
|
|
2465
2711
|
}
|
|
2466
2712
|
phases.sort((a, b) => parseInt(a['number'], 10) - parseInt(b['number'], 10));
|
|
2713
|
+
// #3581: the frontier is ROADMAP ORDER, not artifact presence. The disk loop
|
|
2714
|
+
// above could claim nextPhase from a stray out-of-order artifact directory
|
|
2715
|
+
// (a phase-9 UAT evidence dir while roadmap phase 8 was pending and
|
|
2716
|
+
// unscaffolded), silently skipping 8 — and init.progress then disagreed with
|
|
2717
|
+
// roadmap.analyze on the same tree. Re-derive from the sorted union: the
|
|
2718
|
+
// first phase that has not begun ('pending' | 'not_started') and is not
|
|
2719
|
+
// roadmap-complete wins; artifacts still feed each entry's status and
|
|
2720
|
+
// completion (corroborating evidence) but no longer outrank the ordering.
|
|
2721
|
+
// Aligned trees derive the identical frontier as the loops above; an
|
|
2722
|
+
// all-complete milestone finds none and keeps nextPhase null for the
|
|
2723
|
+
// completion flow.
|
|
2724
|
+
{
|
|
2725
|
+
const frontier = phases.find((p) => {
|
|
2726
|
+
const st = p['status'];
|
|
2727
|
+
return (st === 'pending' || st === 'not_started') && p['roadmap_complete'] !== true;
|
|
2728
|
+
});
|
|
2729
|
+
if (frontier)
|
|
2730
|
+
nextPhase = frontier;
|
|
2731
|
+
}
|
|
2467
2732
|
let pausedAt = null;
|
|
2468
2733
|
const state = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(planningDir(cwd), 'STATE.md'));
|
|
2469
2734
|
if (state !== null) {
|
|
@@ -2488,8 +2753,12 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2488
2753
|
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
|
|
2489
2754
|
planner_model: resolveModelInternal(cwd, 'gsd-planner'),
|
|
2490
2755
|
commit_docs: config.commit_docs,
|
|
2491
|
-
|
|
2492
|
-
|
|
2756
|
+
// #3216 review Finding 2: `?? null` so an unresolved milestone still emits
|
|
2757
|
+
// the key with an explicit `null` rather than letting JSON.stringify drop
|
|
2758
|
+
// it — an omitted key reaches the prompt layer's `{milestone_version}`
|
|
2759
|
+
// placeholder as literal, un-substituted text.
|
|
2760
|
+
milestone_version: milestone['version'] ?? null,
|
|
2761
|
+
milestone_name: milestone['name'] ?? null,
|
|
2493
2762
|
phases,
|
|
2494
2763
|
phase_count: phases.length,
|
|
2495
2764
|
completed_count: phases.filter((p) => p['status'] === 'complete').length,
|
|
@@ -2499,7 +2768,7 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2499
2768
|
paused_at: pausedAt,
|
|
2500
2769
|
has_work_in_progress: !!currentPhase,
|
|
2501
2770
|
phase_mvp_mode: phaseMvpMode,
|
|
2502
|
-
project_exists: pathExistsInternal(cwd, '
|
|
2771
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'PROJECT.md')))),
|
|
2503
2772
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
2504
2773
|
state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
2505
2774
|
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
|
|
@@ -3043,7 +3312,7 @@ function buildSkillManifest(cwd, skillsDir = null) {
|
|
|
3043
3312
|
seenNamesInRoot.add(name);
|
|
3044
3313
|
const description = frontmatter['description'] || '';
|
|
3045
3314
|
const triggers = [];
|
|
3046
|
-
const bodyMatch = content.match(/^---[\s\S]*?---\s*\n([\s\S]*)$/);
|
|
3315
|
+
const bodyMatch = content.match(/^---[\s\S]*?---\s*\r?\n([\s\S]*)$/);
|
|
3047
3316
|
if (bodyMatch) {
|
|
3048
3317
|
const body = bodyMatch[1];
|
|
3049
3318
|
const triggerLines = body.match(/^TRIGGER\s+when:\s*(.+)$/gmi);
|
|
@@ -3134,7 +3403,9 @@ function cmdSkillManifest(cwd, args, raw) {
|
|
|
3134
3403
|
const skillsDir = skillsDirIdx >= 0 && args[skillsDirIdx + 1] ? args[skillsDirIdx + 1] : null;
|
|
3135
3404
|
const manifest = buildSkillManifest(cwd, skillsDir);
|
|
3136
3405
|
if (args.includes('--write')) {
|
|
3137
|
-
|
|
3406
|
+
// #3964: write beside the project's own artifacts (planningDir is
|
|
3407
|
+
// project- and workstream-aware), not the flat root.
|
|
3408
|
+
const planDir = planningDir(cwd);
|
|
3138
3409
|
if (node_fs_1.default.existsSync(planDir)) {
|
|
3139
3410
|
const manifestPath = node_path_1.default.join(planDir, 'skill-manifest.json');
|
|
3140
3411
|
(0, shell_command_projection_cjs_1.platformWriteSync)(manifestPath, JSON.stringify(manifest, null, 2));
|