@opengsd/gsd-core 1.10.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-debug-session-manager.md +11 -0
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-executor.md +5 -5
- package/agents/gsd-mempalace-curator.md +5 -2
- package/agents/gsd-phase-researcher.md +20 -1
- package/agents/gsd-plan-checker.md +37 -0
- package/agents/gsd-planner.md +44 -46
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +12 -3
- package/bin/install.js +841 -971
- package/bin/lib/ui-safety-gate.cjs +2 -0
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/quick.md +1 -1
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +469 -88
- package/gsd-core/bin/lib/active-workstream-store.cjs +138 -22
- package/gsd-core/bin/lib/agent-install-check.cjs +230 -32
- package/gsd-core/bin/lib/api-coverage.cjs +3 -5
- package/gsd-core/bin/lib/artifacts.cjs +3 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +876 -240
- package/gsd-core/bin/lib/broken-windows.cjs +1 -1
- package/gsd-core/bin/lib/capability-consent.cjs +149 -15
- package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
- package/gsd-core/bin/lib/capability-registry.cjs +575 -101
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +495 -22
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +71 -37
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
- package/gsd-core/bin/lib/command-aliases.cjs +22 -0
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/commands.cjs +651 -86
- package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
- package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
- package/gsd-core/bin/lib/config-loader.cjs +75 -0
- package/gsd-core/bin/lib/config.cjs +10 -1
- package/gsd-core/bin/lib/core-utils.cjs +127 -29
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +155 -20
- package/gsd-core/bin/lib/gap-checker.cjs +68 -7
- package/gsd-core/bin/lib/git-base-branch.cjs +102 -0
- 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 +145 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init.cjs +321 -129
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +745 -258
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
- package/gsd-core/bin/lib/install-profiles.cjs +134 -57
- package/gsd-core/bin/lib/install-scope.cjs +270 -0
- package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
- package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +138 -31
- package/gsd-core/bin/lib/io.cjs +10 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
- package/gsd-core/bin/lib/markdown-table.cjs +133 -20
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +754 -70
- package/gsd-core/bin/lib/model-catalog.cjs +59 -1
- package/gsd-core/bin/lib/model-resolver.cjs +183 -40
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
- package/gsd-core/bin/lib/phase-id.cjs +444 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
- package/gsd-core/bin/lib/phase-locator.cjs +125 -18
- package/gsd-core/bin/lib/phase.cjs +646 -143
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +86 -2
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +56 -6
- package/gsd-core/bin/lib/probe-core.cjs +1 -1
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +13 -4
- 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 +34 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +943 -184
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +385 -94
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +608 -46
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +426 -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 +115 -3
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +275 -3
- package/gsd-core/bin/lib/smart-entry.cjs +142 -22
- package/gsd-core/bin/lib/state-command-router.cjs +5 -1
- package/gsd-core/bin/lib/state-document.cjs +152 -8
- package/gsd-core/bin/lib/state-transition.cjs +371 -117
- package/gsd-core/bin/lib/state.cjs +1794 -357
- package/gsd-core/bin/lib/surface.cjs +23 -9
- 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 +9 -3
- package/gsd-core/bin/lib/uat.cjs +399 -56
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
- package/gsd-core/bin/lib/unusable-input.cjs +24 -0
- package/gsd-core/bin/lib/update-context.cjs +8 -2
- package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +37 -0
- package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
- package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +258 -8
- package/gsd-core/bin/lib/verify.cjs +368 -888
- 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.cjs +2 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +176 -9
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +7 -1
- package/gsd-core/references/agent-contracts.md +43 -26
- package/gsd-core/references/checkpoints.md +2 -2
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/execute-mvp-tdd.md +3 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
- package/gsd-core/references/execute-phase-context-guard.md +1 -1
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +6 -2
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-planning-commit.md +2 -1
- package/gsd-core/references/loop-hook-dispatch.md +39 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planning-config.md +8 -6
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +3 -3
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +22 -6
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +2 -4
- package/gsd-core/templates/state.md +4 -4
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/ai-integration-phase.md +9 -11
- package/gsd-core/workflows/autonomous.md +1 -1
- package/gsd-core/workflows/cleanup.md +62 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +13 -3
- package/gsd-core/workflows/code-review-fix.md +37 -10
- package/gsd-core/workflows/code-review.md +38 -12
- package/gsd-core/workflows/complete-milestone.md +141 -18
- package/gsd-core/workflows/debug.md +7 -5
- package/gsd-core/workflows/diagnose-issues.md +35 -9
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +2 -1
- package/gsd-core/workflows/edit-phase.md +26 -1
- package/gsd-core/workflows/eval-review.md +3 -5
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +31 -6
- 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 +2 -0
- package/gsd-core/workflows/execute-phase.md +38 -50
- package/gsd-core/workflows/execute-plan.md +36 -4
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/health.md +73 -4
- package/gsd-core/workflows/import.md +4 -4
- package/gsd-core/workflows/ingest-docs.md +5 -5
- package/gsd-core/workflows/mvp-phase.md +6 -3
- package/gsd-core/workflows/new-milestone.md +14 -9
- package/gsd-core/workflows/new-project.md +14 -14
- package/gsd-core/workflows/next.md +12 -0
- package/gsd-core/workflows/plan-phase.md +41 -17
- package/gsd-core/workflows/plan-review-convergence.md +50 -2
- package/gsd-core/workflows/progress.md +34 -6
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -4
- package/gsd-core/workflows/quick/steps/quick-verification.md +27 -6
- package/gsd-core/workflows/quick/steps/research-phase.md +2 -2
- package/gsd-core/workflows/quick.md +35 -15
- package/gsd-core/workflows/review.md +26 -5
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings.md +66 -2
- package/gsd-core/workflows/ship.md +104 -44
- package/gsd-core/workflows/spec-phase.md +30 -12
- package/gsd-core/workflows/sync-skills.md +63 -8
- package/gsd-core/workflows/transition.md +46 -11
- package/gsd-core/workflows/ui-phase.md +5 -5
- package/gsd-core/workflows/ui-review.md +2 -2
- package/gsd-core/workflows/update.md +1 -1
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +9 -7
- package/hooks/dist/gsd-agent-isolation-guard.js +103 -14
- package/hooks/dist/gsd-check-update-worker.js +56 -13
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
- package/hooks/dist/gsd-cursor-subagent-start.js +77 -2
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/dist/gsd-prompt-guard.js +21 -20
- package/hooks/dist/gsd-read-injection-scanner.js +38 -24
- package/hooks/dist/gsd-statusline.js +18 -0
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-workflow-guard.js +134 -36
- package/hooks/dist/lib/git-cmd.js +92 -59
- package/hooks/dist/lib/injection-patterns.js +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +9 -0
- package/hooks/gsd-agent-isolation-guard.js +103 -14
- package/hooks/gsd-check-update-worker.js +56 -13
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-cursor-pre-tool.js +0 -3
- package/hooks/gsd-cursor-subagent-start.js +77 -2
- package/hooks/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/gsd-prompt-guard.js +21 -20
- package/hooks/gsd-read-injection-scanner.js +38 -24
- package/hooks/gsd-statusline.js +18 -0
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-workflow-guard.js +134 -36
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +9 -0
- package/package.json +21 -9
- package/pi/gsd.cjs +19 -5
- 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/changeset/lint.cjs +60 -5
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/ci-test-scope.cjs +19 -2
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-registry.cjs +3 -15
- package/scripts/gen-context-index.cjs +2 -11
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-inventory-manifest.cjs +50 -4
- package/scripts/gen-loop-host-contract.cjs +4 -24
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/drift-scan.cjs +278 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +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-eslint-glob-coverage.allowlist.json +34 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-phase-enumeration-drift.cjs +479 -0
- package/scripts/lint-plan-count-drift.cjs +318 -0
- package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
- package/scripts/lint-planning-prompt-drift.cjs +434 -0
- package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
- package/scripts/lint-regression-test-names.cjs +15 -13
- package/scripts/lint-removed-but-needed.cjs +320 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +1045 -0
- package/scripts/lint-test-file-count.allowlist.json +21 -10
- package/scripts/lint-unreachable-guard-drift.cjs +843 -0
- package/scripts/lint-vendored-deps.cjs +124 -0
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +25 -0
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/state-write-path-drift-baseline.json +19 -0
- package/scripts/sync-runtime-launcher.cjs +2 -4
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +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 +1 -1
- package/skills/gsd-review-backlog/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +0 -1
- package/skills/gsd-verify-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -574
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- 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,18 @@ 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
|
-
const { output, error } = io;
|
|
77
|
+
const { output, error, ERROR_REASON } = io;
|
|
76
78
|
const { loadConfig, loadConfigResolved } = configLoader;
|
|
77
79
|
const { resolveModelInternal, resolveGranularityInternal, assertValidGranularityOverride } = modelResolver;
|
|
78
|
-
const { findPhaseInternal } = phaseLocator;
|
|
79
|
-
const { getRoadmapPhaseInternal, getMilestoneInfo,
|
|
80
|
+
const { findPhaseInternal, listMilestonePhaseDirs } = phaseLocator;
|
|
81
|
+
const { getRoadmapPhaseInternal, getMilestoneInfo, stripShippedMilestones, extractCurrentMilestone, } = roadmapParser;
|
|
80
82
|
const { pathExistsInternal, generateSlugInternal, toPosixPath } = coreUtils;
|
|
81
|
-
const {
|
|
83
|
+
const { normalizePhaseName, matchPhaseDirs, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, isForeignPrefixedPhaseQuery, isSentinelPhaseId, extractPhaseToken, scopeToPhase } = phaseId;
|
|
82
84
|
const { pruneOrphanedWorktrees } = worktreeSafety;
|
|
83
|
-
const { planningPaths, planningDir, planningRoot, listAvailableWorkstreams,
|
|
85
|
+
const { planningPaths, planningDir, planningRoot, listAvailableWorkstreams, peekActiveWorkstream, diagnoseUnresolvedActiveWorkstream, describeUnresolvedWorkstreamReason, findContextMdIn, } = planningWorkspace;
|
|
84
86
|
const { determinePhaseStatus } = commandsMod;
|
|
85
87
|
const { extractFrontmatter } = frontmatterMod;
|
|
86
|
-
const {
|
|
88
|
+
const { isPhaseComplete, resolveVerificationFile, resolveUatFile } = verificationMod;
|
|
87
89
|
const { evaluateUatPassed } = uatPredicateMod;
|
|
88
90
|
const { resolveLoopHooks } = loopResolverMod;
|
|
89
91
|
const { loadRegistry } = capabilityLoaderMod;
|
|
@@ -104,7 +106,7 @@ function phaseInfoMatchesExactPrefix(phaseInfo, phase) {
|
|
|
104
106
|
function roadmapPhaseMatchesExactPrefix(roadmapPhase, phase) {
|
|
105
107
|
const sectionRaw = roadmapPhase?.['section'];
|
|
106
108
|
const section = typeof sectionRaw === 'string' ? sectionRaw : '';
|
|
107
|
-
return new RegExp(`^#{2,4}\\s*Phase\\s+${escapeRegex(phase)}(?:\\b|\\s|:)`, 'i').test(section);
|
|
109
|
+
return new RegExp(`^#{2,4}\\s*Phase\\s+${(0, pattern_cjs_1.escapeRegex)(phase)}(?:\\b|\\s|:)`, 'i').test(section);
|
|
108
110
|
}
|
|
109
111
|
// #2104: shared helpers that wrap findPhaseInternal / getRoadmapPhaseInternal
|
|
110
112
|
// with the #2056 foreign-prefix guard, so every init command gets the same
|
|
@@ -170,39 +172,49 @@ function listPhaseSummaryFiles(phaseDir) {
|
|
|
170
172
|
function listPhasePlanFiles(phaseDir) {
|
|
171
173
|
return scanPhasePlans(phaseDir)['planFiles'];
|
|
172
174
|
}
|
|
173
|
-
function projectCompletionStatus(implementationComplete,
|
|
174
|
-
if (
|
|
175
|
+
function projectCompletionStatus(implementationComplete, phaseComplete) {
|
|
176
|
+
if (phaseComplete)
|
|
175
177
|
return 'complete';
|
|
176
178
|
if (implementationComplete)
|
|
177
179
|
return 'executed';
|
|
178
180
|
return 'incomplete';
|
|
179
181
|
}
|
|
180
182
|
function buildPhaseCompletionProjection(cwd, phaseNumber, phaseDir, planCount, summaryCount, slashRuntime) {
|
|
183
|
+
// ADR-3180 §7.4 (issue #3186) / DO-NOT-MIGRATE exemption
|
|
184
|
+
// (scripts/lint-completion-predicate-drift.cjs FUNCTION_SCOPED_EXEMPTIONS,
|
|
185
|
+
// declared deviation): `implementation_complete` answers "are the plans
|
|
186
|
+
// done" (a `scanPhasePlans`-shaped different question, per the design's
|
|
187
|
+
// 0.x-split), NOT "is the phase complete" — it is kept for the
|
|
188
|
+
// 'executed'-vs-'planned' disk_status distinction downstream consumers
|
|
189
|
+
// still rely on, which `isPhaseComplete`'s locked `{ complete, verification
|
|
190
|
+
// }` return shape does not carry.
|
|
181
191
|
const implementationComplete = planCount > 0 && summaryCount >= planCount;
|
|
182
192
|
const phaseFullDir = phaseDir ? node_path_1.default.join(cwd, phaseDir) : '';
|
|
183
|
-
// #
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
193
|
+
// #3168 / ADR-3180 §7.4 (disk-strict, #2957): route through the canonical
|
|
194
|
+
// owner (`src/verification.cts` · `isPhaseComplete`), which calls
|
|
195
|
+
// readVerificationStatus UNCONDITIONALLY — plan count is NOT a
|
|
196
|
+
// precondition. A zero-plan phase with a passing `*-VERIFICATION.md` is
|
|
197
|
+
// complete; init used to gate the read on `implementationComplete` and
|
|
198
|
+
// synthesize a `not_required` sentinel instead, which is the #3168 defect.
|
|
199
|
+
// #2617: the router still owns both the message content and the runtime
|
|
200
|
+
// projection; init passes the phase number it already knows (its phaseDir
|
|
188
201
|
// is unresolved in some branches, where the router could not derive one).
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
: { status: 'not_required', next_action: '', next_command: '' };
|
|
202
|
+
const completionResult = isPhaseComplete(phaseFullDir, { runtime: slashRuntime, phaseNumber });
|
|
203
|
+
const verificationStatus = completionResult.value.verification;
|
|
192
204
|
const projectedVerificationStatus = verificationStatus.status;
|
|
193
205
|
const projectedVerificationAction = verificationStatus.next_action;
|
|
194
206
|
const verificationPassed = projectedVerificationStatus === 'passed';
|
|
195
|
-
const phaseComplete =
|
|
207
|
+
const phaseComplete = completionResult.value.complete;
|
|
196
208
|
return {
|
|
197
209
|
implementation_complete: implementationComplete,
|
|
198
210
|
verification_status: projectedVerificationStatus,
|
|
199
211
|
verification_passed: verificationPassed,
|
|
200
212
|
phase_complete: phaseComplete,
|
|
201
|
-
completion_status: projectCompletionStatus(implementationComplete,
|
|
213
|
+
completion_status: projectCompletionStatus(implementationComplete, phaseComplete),
|
|
202
214
|
verification_next_action: projectedVerificationAction,
|
|
203
215
|
verification_next_command: verificationStatus.next_command,
|
|
204
|
-
// #3057 B3:
|
|
205
|
-
//
|
|
216
|
+
// #3057 B3: readVerificationStatus's result carries this flag when its
|
|
217
|
+
// internal staleness check could not run to completion.
|
|
206
218
|
verification_stale_check_indeterminate: 'staleCheckIndeterminate' in verificationStatus
|
|
207
219
|
&& verificationStatus.staleCheckIndeterminate === true,
|
|
208
220
|
};
|
|
@@ -222,7 +234,8 @@ function getLatestCompletedMilestone(cwd) {
|
|
|
222
234
|
}
|
|
223
235
|
function withProjectRoot(cwd, result) {
|
|
224
236
|
result['project_root'] = cwd;
|
|
225
|
-
|
|
237
|
+
// #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).
|
|
238
|
+
const activeRuntime = (0, host_runtime_detection_cjs_1.resolveReportedRuntime)(cwd);
|
|
226
239
|
const agentStatus = checkAgentsInstalled(activeRuntime, cwd);
|
|
227
240
|
result['agents_installed'] = agentStatus.agents_installed;
|
|
228
241
|
result['missing_agents'] = agentStatus.missing_agents;
|
|
@@ -415,7 +428,12 @@ function detectHasPriorPhases(cwd, phaseInfo) {
|
|
|
415
428
|
catch {
|
|
416
429
|
continue;
|
|
417
430
|
}
|
|
418
|
-
|
|
431
|
+
// #3511-class: scope the raw listing to THIS entry's own phase artifacts
|
|
432
|
+
// before the bare `.some()` predicate runs, so a stray `07-VERIFICATION.md`
|
|
433
|
+
// physically sitting in another phase's directory cannot make that
|
|
434
|
+
// directory appear to have its own verification report.
|
|
435
|
+
const scopedFiles = scopeToPhase(files, entry.name);
|
|
436
|
+
if (scopedFiles.some((f) => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md')) {
|
|
419
437
|
return true;
|
|
420
438
|
}
|
|
421
439
|
}
|
|
@@ -537,7 +555,7 @@ function detectPhaseMvpMode(cwd, phaseNumber) {
|
|
|
537
555
|
return false;
|
|
538
556
|
const rawContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
539
557
|
const content = extractCurrentMilestone(rawContent, cwd);
|
|
540
|
-
const escapedPhase = escapeRegex(phaseNumber);
|
|
558
|
+
const escapedPhase = (0, pattern_cjs_1.escapeRegex)(phaseNumber);
|
|
541
559
|
const phaseHeader = new RegExp(`#{2,4}\\s*Phase\\s+${escapedPhase}(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:`, 'i');
|
|
542
560
|
const headerMatch = content.match(phaseHeader);
|
|
543
561
|
if (!headerMatch || headerMatch.index === undefined)
|
|
@@ -594,7 +612,11 @@ function detectUiPhaseActive(cwd, phaseInfo) {
|
|
|
594
612
|
// comments elsewhere in this file), same technique as detectHasPriorPhases above.
|
|
595
613
|
const dirName = node_path_1.default.basename(rawDir);
|
|
596
614
|
const files = node_fs_1.default.readdirSync(node_path_1.default.join(planningDir(cwd), 'phases', dirName));
|
|
597
|
-
|
|
615
|
+
// #3511-class: scope the raw listing to this phase dir before the
|
|
616
|
+
// phase-numbered -UI-SPEC.md predicate, so a stray cross-phase
|
|
617
|
+
// UI-SPEC file cannot flip this phase's ui-phase-active flag.
|
|
618
|
+
const scopedFiles = scopeToPhase(files, dirName);
|
|
619
|
+
hasUiSpecFile = scopedFiles.some((f) => f.endsWith('-UI-SPEC.md') || f === 'UI-SPEC.md');
|
|
598
620
|
}
|
|
599
621
|
catch {
|
|
600
622
|
hasUiSpecFile = false;
|
|
@@ -691,13 +713,32 @@ function buildSectionManifestField(cwd, phaseInfo, options, workflow, overrides
|
|
|
691
713
|
return null;
|
|
692
714
|
}
|
|
693
715
|
}
|
|
716
|
+
/**
|
|
717
|
+
* #3216 review Finding 1: `getMilestoneInfo(cwd).value` unwrap-and-cast was
|
|
718
|
+
* repeated identically (comment included) at five init call sites — factored
|
|
719
|
+
* out once so the cast and its `?? {}` "no milestone resolved" fallback live
|
|
720
|
+
* in exactly one place. Behavior-preserving: same call, same fallback, same
|
|
721
|
+
* cast, for every caller.
|
|
722
|
+
*/
|
|
723
|
+
function milestoneRecord(cwd) {
|
|
724
|
+
return (getMilestoneInfo(cwd).value ?? {});
|
|
725
|
+
}
|
|
694
726
|
function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
695
727
|
if (!phase) {
|
|
696
728
|
error('phase required for init execute-phase');
|
|
697
729
|
}
|
|
698
730
|
const config = loadConfig(cwd);
|
|
699
731
|
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
700
|
-
|
|
732
|
+
// #3216: getMilestoneInfo now returns a ScopedResult — `.value` carries the
|
|
733
|
+
// MilestoneInfo (or null on any non-COMPLETE scope). NOT display-only: when
|
|
734
|
+
// `branching_strategy === 'milestone'`, `milestone['version']`/`['name']`
|
|
735
|
+
// below feed `branch_name` construction (see the milestone_branch_template
|
|
736
|
+
// branch below), so an unresolved milestone changes the constructed branch
|
|
737
|
+
// name, not merely what gets printed. bracket-access below naturally reads
|
|
738
|
+
// `undefined` when unresolved; the `milestone_version`/`milestone_name`
|
|
739
|
+
// output fields below coerce that to an explicit `null` (#3216 review
|
|
740
|
+
// Finding 2) so the key is never silently omitted from the JSON bundle.
|
|
741
|
+
const milestone = milestoneRecord(cwd);
|
|
701
742
|
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
702
743
|
phaseInfo = applyRoadmapFallback(phaseInfo, roadmapPhase, (rp) => {
|
|
703
744
|
const phaseName = rp['phase_name'];
|
|
@@ -725,6 +766,13 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
725
766
|
: null;
|
|
726
767
|
const phase_req_ids = reqExtracted && reqExtracted !== 'TBD' ? reqExtracted : null;
|
|
727
768
|
const wf = (config.workflow ?? {});
|
|
769
|
+
// #3188: these paths are null when the file is absent, matching the contract
|
|
770
|
+
// the conditional sibling fields (context_path, patterns_path, ...) already
|
|
771
|
+
// honour and that ultraplan-phase.md / execute-phase.md gate on. Hoisted so
|
|
772
|
+
// the existence check and the emitted path share one source of truth.
|
|
773
|
+
const statePath = node_path_1.default.join(planningDir(cwd), 'STATE.md');
|
|
774
|
+
const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
|
|
775
|
+
const requirementsPath = node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md');
|
|
728
776
|
const result = {
|
|
729
777
|
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
|
|
730
778
|
verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
|
|
@@ -744,7 +792,17 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
744
792
|
? toPosixPath(node_path_1.default.join(cwd, phaseInfo['directory']))
|
|
745
793
|
: null,
|
|
746
794
|
phase_number: phaseInfo?.['phase_number'] || null,
|
|
747
|
-
|
|
795
|
+
// #3171: prefer the ROADMAP's curated display name for `phase_name`. When
|
|
796
|
+
// the phase directory already exists on disk, the disk-lookup path
|
|
797
|
+
// (searchPhaseInDir) derives phase_name from the directory-name remainder
|
|
798
|
+
// — itself an already-slugified value (`phase.add` writes `${num}-${slug}`
|
|
799
|
+
// dirs), so phase_name and phase_slug come out byte-identical. An
|
|
800
|
+
// orchestrator wiring this field into `state begin-phase --name` then
|
|
801
|
+
// lands a raw slug in STATE.md's current_phase_name. The ROADMAP carries
|
|
802
|
+
// the human-curated display name (`### Phase N: <Name>`); prefer it,
|
|
803
|
+
// matching the no-disk fallback above. phase_slug stays disk-derived — it
|
|
804
|
+
// correctly feeds branch-name construction below and is unchanged here.
|
|
805
|
+
phase_name: (roadmapPhase?.['phase_name']) || (phaseInfo?.['phase_name']) || null,
|
|
748
806
|
phase_slug: phaseInfo?.['phase_slug'] || null,
|
|
749
807
|
phase_req_ids,
|
|
750
808
|
plans: phaseInfo?.['plans'] || [],
|
|
@@ -768,22 +826,23 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
768
826
|
.replace('{slug}', phaseInfo['phase_slug'] || 'phase')
|
|
769
827
|
: config.branching_strategy === 'milestone'
|
|
770
828
|
? config.milestone_branch_template
|
|
771
|
-
.replace('{milestone}', milestone['version'])
|
|
829
|
+
.replace('{milestone}', milestone['version'] ?? '')
|
|
772
830
|
.replace('{slug}', generateSlugInternal(milestone['name']) || 'milestone')
|
|
773
831
|
: null,
|
|
774
|
-
milestone_version: milestone['version'],
|
|
775
|
-
milestone_name: milestone['name'],
|
|
832
|
+
milestone_version: milestone['version'] ?? null,
|
|
833
|
+
milestone_name: milestone['name'] ?? null,
|
|
776
834
|
milestone_slug: generateSlugInternal(milestone['name']),
|
|
777
835
|
state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
778
836
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
779
837
|
config_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'config.json')),
|
|
780
838
|
// #2376: emit absolute paths — see comment above on phase_dir.
|
|
781
|
-
|
|
782
|
-
|
|
839
|
+
// #3188: null when the file is absent (parity with patterns_path/context_path).
|
|
840
|
+
state_path: node_fs_1.default.existsSync(statePath) ? toPosixPath(statePath) : null,
|
|
841
|
+
roadmap_path: node_fs_1.default.existsSync(roadmapPath) ? toPosixPath(roadmapPath) : null,
|
|
783
842
|
config_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'config.json')),
|
|
784
843
|
// #2376: execute-phase.md's verify_phase_goal step reads this instead of
|
|
785
844
|
// hardcoding '.planning/REQUIREMENTS.md' into the gsd-verifier spawn prompt.
|
|
786
|
-
requirements_path:
|
|
845
|
+
requirements_path: node_fs_1.default.existsSync(requirementsPath) ? toPosixPath(requirementsPath) : null,
|
|
787
846
|
};
|
|
788
847
|
if (options['validate']) {
|
|
789
848
|
try {
|
|
@@ -860,6 +919,11 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
860
919
|
assertValidGranularityOverride(granularityOverride, error);
|
|
861
920
|
const granularity = resolveGranularityInternal(cwd, 'planning', granularityOverride || undefined);
|
|
862
921
|
const wf = (config.workflow ?? {});
|
|
922
|
+
// #3188: see cmdInitExecutePhase — null when absent, parity with the
|
|
923
|
+
// conditional sibling fields in this same result object.
|
|
924
|
+
const statePath = node_path_1.default.join(planningDir(cwd), 'STATE.md');
|
|
925
|
+
const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
|
|
926
|
+
const requirementsPath = node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md');
|
|
863
927
|
const result = {
|
|
864
928
|
researcher_model: resolveModelInternal(cwd, 'gsd-phase-researcher'),
|
|
865
929
|
planner_model: resolveModelInternal(cwd, 'gsd-planner'),
|
|
@@ -896,36 +960,70 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
896
960
|
planning_exists: node_fs_1.default.existsSync(planningDir(cwd)),
|
|
897
961
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
898
962
|
// #2376: absolute — see comment on phase_dir above.
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
963
|
+
// #3188: null when the file is absent (parity with patterns_path below).
|
|
964
|
+
state_path: node_fs_1.default.existsSync(statePath) ? toPosixPath(statePath) : null,
|
|
965
|
+
roadmap_path: node_fs_1.default.existsSync(roadmapPath) ? toPosixPath(roadmapPath) : null,
|
|
966
|
+
requirements_path: node_fs_1.default.existsSync(requirementsPath) ? toPosixPath(requirementsPath) : null,
|
|
902
967
|
patterns_path: null,
|
|
903
968
|
};
|
|
904
969
|
if (phaseInfo?.['directory']) {
|
|
905
970
|
const phaseDirFull = node_path_1.default.join(cwd, phaseInfo['directory']);
|
|
906
971
|
try {
|
|
907
972
|
const files = node_fs_1.default.readdirSync(phaseDirFull);
|
|
908
|
-
const
|
|
973
|
+
const phaseDirName = node_path_1.default.basename(phaseDirFull);
|
|
974
|
+
// #3511 BLOCKER-3: scope the raw listing to THIS phase's own artifacts
|
|
975
|
+
// before any bare `.find()` predicate runs, so a `04-UAT.md` (or
|
|
976
|
+
// `04-RESEARCH.md`/`04-REVIEWS.md`/`04-PATTERNS.md`) sitting in phase
|
|
977
|
+
// 03's directory cannot win a phase-03 lookup — the same
|
|
978
|
+
// `isPhaseArtifact` membership rule `resolveVerificationFile` already
|
|
979
|
+
// applies via `phaseDirName` below. `findContextMdIn` is passed the
|
|
980
|
+
// scoped array (rather than the raw directory path) so this call site
|
|
981
|
+
// alone is scoped; its other call sites are unaffected.
|
|
982
|
+
const scopedFiles = scopeToPhase(files, phaseDirName);
|
|
983
|
+
const contextFile = findContextMdIn(scopedFiles);
|
|
909
984
|
if (contextFile) {
|
|
910
985
|
result['context_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, contextFile));
|
|
911
986
|
}
|
|
912
|
-
const researchFile =
|
|
987
|
+
const researchFile = scopedFiles.find((f) => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
|
|
913
988
|
if (researchFile) {
|
|
914
989
|
result['research_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, researchFile));
|
|
915
990
|
}
|
|
916
|
-
|
|
991
|
+
// #3473 F2: routed through the shared resolver — readdir order is
|
|
992
|
+
// filesystem-dependent, so the prior hand-rolled `.find()` could pick
|
|
993
|
+
// either file when a phase held both a canonical report and an ad-hoc
|
|
994
|
+
// `-CORRECTION-VERIFICATION.md` worksheet (#3357).
|
|
995
|
+
// #3492: pin selection to THIS phase's own token so a stray cross-phase
|
|
996
|
+
// or sentinel-numbered canonically-shaped file cannot outrank this
|
|
997
|
+
// phase's own (possibly non-canonical) report.
|
|
998
|
+
const phaseToken = extractPhaseToken(phaseDirName);
|
|
999
|
+
const verificationFile = resolveVerificationFile(files, {
|
|
1000
|
+
allowBare: true,
|
|
1001
|
+
phaseToken,
|
|
1002
|
+
phaseDirName,
|
|
1003
|
+
});
|
|
917
1004
|
if (verificationFile) {
|
|
918
1005
|
result['verification_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, verificationFile));
|
|
919
1006
|
}
|
|
920
|
-
|
|
1007
|
+
// #3518: routed through the shared UAT resolver — the prior hand-rolled
|
|
1008
|
+
// `.find()` over unsorted readdir order had no phase check and no
|
|
1009
|
+
// ordering, so a stray cross-phase 02-UAT.md could become this phase's
|
|
1010
|
+
// uat_path, filesystem-dependently. Pinned to this phase's own token
|
|
1011
|
+
// (same rule as verification_path above), and phase-scoped via
|
|
1012
|
+
// phaseDirName (#3511) so the alphabetically-first fallback tier also
|
|
1013
|
+
// excludes cross-phase strays.
|
|
1014
|
+
const uatFile = resolveUatFile(files, {
|
|
1015
|
+
allowBare: true,
|
|
1016
|
+
phaseToken,
|
|
1017
|
+
phaseDirName,
|
|
1018
|
+
});
|
|
921
1019
|
if (uatFile) {
|
|
922
1020
|
result['uat_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, uatFile));
|
|
923
1021
|
}
|
|
924
|
-
const reviewsFile =
|
|
1022
|
+
const reviewsFile = scopedFiles.find((f) => f.endsWith('-REVIEWS.md') || f === 'REVIEWS.md');
|
|
925
1023
|
if (reviewsFile) {
|
|
926
1024
|
result['reviews_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, reviewsFile));
|
|
927
1025
|
}
|
|
928
|
-
const patternsFile =
|
|
1026
|
+
const patternsFile = scopedFiles.find((f) => f.endsWith('-PATTERNS.md') || f === 'PATTERNS.md');
|
|
929
1027
|
if (patternsFile) {
|
|
930
1028
|
result['patterns_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, patternsFile));
|
|
931
1029
|
}
|
|
@@ -1010,22 +1108,14 @@ function cmdInitNewProject(cwd, raw, options = {}) {
|
|
|
1010
1108
|
}
|
|
1011
1109
|
function cmdInitNewMilestone(cwd, raw, options = {}) {
|
|
1012
1110
|
const config = loadConfig(cwd);
|
|
1013
|
-
const milestone =
|
|
1111
|
+
const milestone = milestoneRecord(cwd);
|
|
1014
1112
|
const latestCompleted = getLatestCompletedMilestone(cwd);
|
|
1015
1113
|
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
|
-
}
|
|
1114
|
+
// #3185 (ADR-3180 Decision 1): "how many phase directories belong to the
|
|
1115
|
+
// CURRENT milestone" is exactly the scoped question listMilestonePhaseDirs
|
|
1116
|
+
// owns — routed through it instead of a local readdirSync + hand-rolled
|
|
1117
|
+
// window filter (which also never excluded sentinels, unlike the owner).
|
|
1118
|
+
const phaseDirCount = listMilestonePhaseDirs(phasesDir, { cwd }).value.length;
|
|
1029
1119
|
const wf = (config.workflow ?? {});
|
|
1030
1120
|
const result = {
|
|
1031
1121
|
researcher_model: resolveModelInternal(cwd, 'gsd-project-researcher'),
|
|
@@ -1033,8 +1123,12 @@ function cmdInitNewMilestone(cwd, raw, options = {}) {
|
|
|
1033
1123
|
roadmapper_model: resolveModelInternal(cwd, 'gsd-roadmapper'),
|
|
1034
1124
|
commit_docs: config.commit_docs,
|
|
1035
1125
|
research_enabled: wf['research'],
|
|
1036
|
-
|
|
1037
|
-
|
|
1126
|
+
// #3216 review Finding 2: `?? null` so an unresolved milestone still emits
|
|
1127
|
+
// the key with an explicit `null` rather than letting JSON.stringify drop
|
|
1128
|
+
// it — an omitted key reaches the prompt layer's `{current_milestone}`
|
|
1129
|
+
// placeholder as literal, un-substituted text.
|
|
1130
|
+
current_milestone: milestone['version'] ?? null,
|
|
1131
|
+
current_milestone_name: milestone['name'] ?? null,
|
|
1038
1132
|
latest_completed_milestone: latestCompleted?.version || null,
|
|
1039
1133
|
latest_completed_milestone_name: latestCompleted?.name || null,
|
|
1040
1134
|
phase_dir_count: phaseDirCount,
|
|
@@ -1070,7 +1164,13 @@ function cmdInitNewMilestone(cwd, raw, options = {}) {
|
|
|
1070
1164
|
// source as `cmdInitTransition`: `GSD_WORKSTREAM` env, falling back to the
|
|
1071
1165
|
// stored active-workstream pointer (mirrors `cmdInitProgress`'s own
|
|
1072
1166
|
// resolution above).
|
|
1073
|
-
|
|
1167
|
+
//
|
|
1168
|
+
// #3579 root-cause fix: this is a read-only informational field (no write
|
|
1169
|
+
// follows), so use the non-mutating peek — getActiveWorkstream's self-heal
|
|
1170
|
+
// would otherwise silently delete a stale/invalid pointer as a side effect
|
|
1171
|
+
// of building a JSON report field, and (per #3579) could change what a
|
|
1172
|
+
// LATER resolution in the same process observes.
|
|
1173
|
+
const resolvedWorkstream = process.env['GSD_WORKSTREAM'] || peekActiveWorkstream(cwd);
|
|
1074
1174
|
const workstreamActive = !!resolvedWorkstream;
|
|
1075
1175
|
const flatMode = !workstreamActive;
|
|
1076
1176
|
// #2992 (Phase 6.1): additive, optional field — degrades to null, never throws.
|
|
@@ -1082,7 +1182,7 @@ function cmdInitNewMilestone(cwd, raw, options = {}) {
|
|
|
1082
1182
|
}
|
|
1083
1183
|
function cmdInitQuick(cwd, description, raw, options = {}) {
|
|
1084
1184
|
const config = loadConfig(cwd);
|
|
1085
|
-
const now = new Date();
|
|
1185
|
+
const now = new Date(clock_cjs_1.realClock.now());
|
|
1086
1186
|
const slug = description ? generateSlugInternal(description)?.substring(0, 40) : null;
|
|
1087
1187
|
const yy = String(now.getFullYear()).slice(-2);
|
|
1088
1188
|
const mm = String(now.getMonth() + 1).padStart(2, '0');
|
|
@@ -1548,6 +1648,11 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
1548
1648
|
expectedPhaseDir = toPosixPath(node_path_1.default.join(planningPaths(cwd).phases, dirName));
|
|
1549
1649
|
}
|
|
1550
1650
|
}
|
|
1651
|
+
// #3188: see cmdInitExecutePhase — null when absent, parity with the
|
|
1652
|
+
// conditional sibling fields in this same result object.
|
|
1653
|
+
const statePath = node_path_1.default.join(planningDir(cwd), 'STATE.md');
|
|
1654
|
+
const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
|
|
1655
|
+
const requirementsPath = node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md');
|
|
1551
1656
|
const result = {
|
|
1552
1657
|
commit_docs: config.commit_docs,
|
|
1553
1658
|
brave_search: typeof config.brave_search === 'string'
|
|
@@ -1576,31 +1681,59 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
1576
1681
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1577
1682
|
planning_exists: node_fs_1.default.existsSync(planningDir(cwd)),
|
|
1578
1683
|
// #2376: absolute — see comment on phase_dir above.
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1684
|
+
// #3188: null when the file is absent (parity with context_path/research_path).
|
|
1685
|
+
state_path: node_fs_1.default.existsSync(statePath) ? toPosixPath(statePath) : null,
|
|
1686
|
+
roadmap_path: node_fs_1.default.existsSync(roadmapPath) ? toPosixPath(roadmapPath) : null,
|
|
1687
|
+
requirements_path: node_fs_1.default.existsSync(requirementsPath) ? toPosixPath(requirementsPath) : null,
|
|
1582
1688
|
};
|
|
1583
1689
|
if (phaseInfo?.['directory']) {
|
|
1584
1690
|
const phaseDirFull = node_path_1.default.join(cwd, phaseInfo['directory']);
|
|
1585
1691
|
try {
|
|
1586
1692
|
const files = node_fs_1.default.readdirSync(phaseDirFull);
|
|
1587
|
-
const
|
|
1693
|
+
const phaseDirName = node_path_1.default.basename(phaseDirFull);
|
|
1694
|
+
// #3511 BLOCKER-3: see the parallel site above — scope before any bare
|
|
1695
|
+
// `.find()` predicate so a misfiled cross-phase artifact cannot win.
|
|
1696
|
+
const scopedFiles = scopeToPhase(files, phaseDirName);
|
|
1697
|
+
const contextFile = findContextMdIn(scopedFiles);
|
|
1588
1698
|
if (contextFile) {
|
|
1589
1699
|
result['context_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, contextFile));
|
|
1590
1700
|
}
|
|
1591
|
-
const researchFile =
|
|
1701
|
+
const researchFile = scopedFiles.find((f) => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
|
|
1592
1702
|
if (researchFile) {
|
|
1593
1703
|
result['research_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, researchFile));
|
|
1594
1704
|
}
|
|
1595
|
-
|
|
1705
|
+
// #3473 F2: routed through the shared resolver — readdir order is
|
|
1706
|
+
// filesystem-dependent, so the prior hand-rolled `.find()` could pick
|
|
1707
|
+
// either file when a phase held both a canonical report and an ad-hoc
|
|
1708
|
+
// `-CORRECTION-VERIFICATION.md` worksheet (#3357).
|
|
1709
|
+
// #3492: pin selection to THIS phase's own token so a stray cross-phase
|
|
1710
|
+
// or sentinel-numbered canonically-shaped file cannot outrank this
|
|
1711
|
+
// phase's own (possibly non-canonical) report.
|
|
1712
|
+
const phaseToken = extractPhaseToken(phaseDirName);
|
|
1713
|
+
const verificationFile = resolveVerificationFile(files, {
|
|
1714
|
+
allowBare: true,
|
|
1715
|
+
phaseToken,
|
|
1716
|
+
phaseDirName,
|
|
1717
|
+
});
|
|
1596
1718
|
if (verificationFile) {
|
|
1597
1719
|
result['verification_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, verificationFile));
|
|
1598
1720
|
}
|
|
1599
|
-
|
|
1721
|
+
// #3518: routed through the shared UAT resolver — the prior hand-rolled
|
|
1722
|
+
// `.find()` over unsorted readdir order had no phase check and no
|
|
1723
|
+
// ordering, so a stray cross-phase 02-UAT.md could become this phase's
|
|
1724
|
+
// uat_path, filesystem-dependently. Pinned to this phase's own token
|
|
1725
|
+
// (same rule as verification_path above), and phase-scoped via
|
|
1726
|
+
// phaseDirName (#3511) so the alphabetically-first fallback tier also
|
|
1727
|
+
// excludes cross-phase strays.
|
|
1728
|
+
const uatFile = resolveUatFile(files, {
|
|
1729
|
+
allowBare: true,
|
|
1730
|
+
phaseToken,
|
|
1731
|
+
phaseDirName,
|
|
1732
|
+
});
|
|
1600
1733
|
if (uatFile) {
|
|
1601
1734
|
result['uat_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, uatFile));
|
|
1602
1735
|
}
|
|
1603
|
-
const reviewsFile =
|
|
1736
|
+
const reviewsFile = scopedFiles.find((f) => f.endsWith('-REVIEWS.md') || f === 'REVIEWS.md');
|
|
1604
1737
|
if (reviewsFile) {
|
|
1605
1738
|
result['reviews_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, reviewsFile));
|
|
1606
1739
|
}
|
|
@@ -1669,7 +1802,7 @@ function cmdInitTodos(cwd, area, raw) {
|
|
|
1669
1802
|
}
|
|
1670
1803
|
function cmdInitMilestoneOp(cwd, raw) {
|
|
1671
1804
|
const config = loadConfig(cwd);
|
|
1672
|
-
const milestone =
|
|
1805
|
+
const milestone = milestoneRecord(cwd);
|
|
1673
1806
|
let phaseCount = 0;
|
|
1674
1807
|
let completedPhases = 0;
|
|
1675
1808
|
const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
|
|
@@ -1682,7 +1815,8 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
1682
1815
|
const phasePattern = new RegExp(`#{2,4}\\s*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:`, 'gi');
|
|
1683
1816
|
let m;
|
|
1684
1817
|
while ((m = phasePattern.exec(currentSection)) !== null) {
|
|
1685
|
-
|
|
1818
|
+
// #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
|
|
1819
|
+
if (isSentinelPhaseId(m[1]))
|
|
1686
1820
|
continue;
|
|
1687
1821
|
roadmapPhaseNumbers.push(m[1]);
|
|
1688
1822
|
}
|
|
@@ -1727,8 +1861,12 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
1727
1861
|
}
|
|
1728
1862
|
else {
|
|
1729
1863
|
try {
|
|
1730
|
-
|
|
1731
|
-
|
|
1864
|
+
// #3185 (ADR-3180 Decision 1): the ROADMAP heading scan above found no
|
|
1865
|
+
// current-milestone phase headings — fall back to asking the canonical
|
|
1866
|
+
// owner "which phase directories belong to the current milestone"
|
|
1867
|
+
// directly, instead of a hand-rolled readdirSync over every directory
|
|
1868
|
+
// on disk (which also never excluded sentinels, unlike the owner).
|
|
1869
|
+
const dirs = listMilestonePhaseDirs(phasesDir, { cwd }).value;
|
|
1732
1870
|
phaseCount = dirs.length;
|
|
1733
1871
|
for (const dir of dirs) {
|
|
1734
1872
|
try {
|
|
@@ -1758,8 +1896,12 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
1758
1896
|
}
|
|
1759
1897
|
const result = {
|
|
1760
1898
|
commit_docs: config.commit_docs,
|
|
1761
|
-
|
|
1762
|
-
|
|
1899
|
+
// #3216 review Finding 2: `?? null` so an unresolved milestone still emits
|
|
1900
|
+
// the key with an explicit `null` rather than letting JSON.stringify drop
|
|
1901
|
+
// it — an omitted key reaches the prompt layer's `{milestone_version}`
|
|
1902
|
+
// placeholder as literal, un-substituted text.
|
|
1903
|
+
milestone_version: milestone['version'] ?? null,
|
|
1904
|
+
milestone_name: milestone['name'] ?? null,
|
|
1763
1905
|
milestone_slug: generateSlugInternal(milestone['name']),
|
|
1764
1906
|
phase_count: phaseCount,
|
|
1765
1907
|
completed_phases: completedPhases,
|
|
@@ -1803,7 +1945,7 @@ function cmdInitMapCodebase(cwd, raw) {
|
|
|
1803
1945
|
}
|
|
1804
1946
|
function cmdInitManager(cwd, raw) {
|
|
1805
1947
|
const config = loadConfig(cwd);
|
|
1806
|
-
const milestone =
|
|
1948
|
+
const milestone = milestoneRecord(cwd);
|
|
1807
1949
|
const _slashRuntime = (0, runtime_slash_cjs_1.resolveRuntime)(cwd);
|
|
1808
1950
|
const paths = planningPaths(cwd);
|
|
1809
1951
|
if (!node_fs_1.default.existsSync(paths.roadmap)) {
|
|
@@ -1815,18 +1957,12 @@ function cmdInitManager(cwd, raw) {
|
|
|
1815
1957
|
const rawContent = node_fs_1.default.readFileSync(paths.roadmap, 'utf-8');
|
|
1816
1958
|
const content = extractCurrentMilestone(rawContent, cwd);
|
|
1817
1959
|
const phasesDir = paths.phases;
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
.map((e) => e.name);
|
|
1825
|
-
}
|
|
1826
|
-
catch {
|
|
1827
|
-
return [];
|
|
1828
|
-
}
|
|
1829
|
-
})();
|
|
1960
|
+
// #3185 (ADR-3180 Decision 1): "which phase directories belong to the
|
|
1961
|
+
// CURRENT milestone" is the scoped question listMilestonePhaseDirs owns —
|
|
1962
|
+
// routed through it instead of a hand-rolled readdirSync + a separate
|
|
1963
|
+
// getMilestonePhaseFilter window check (which also never excluded
|
|
1964
|
+
// sentinels, unlike the owner).
|
|
1965
|
+
const _phaseDirEntries = listMilestonePhaseDirs(phasesDir, { cwd }).value;
|
|
1830
1966
|
const _checkboxStates = new Map();
|
|
1831
1967
|
const _cbPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
|
|
1832
1968
|
let _cbMatch;
|
|
@@ -1861,16 +1997,24 @@ function cmdInitManager(cwd, raw) {
|
|
|
1861
1997
|
let isActive = false;
|
|
1862
1998
|
let completion = buildPhaseCompletionProjection(cwd, phaseNum, null, planCount, summaryCount, _slashRuntime);
|
|
1863
1999
|
try {
|
|
1864
|
-
|
|
1865
|
-
|
|
2000
|
+
// #3185 (ADR-3180 Decision 2) moved this lookup off the
|
|
2001
|
+
// milestone-scoped set and onto the physical one; that scope choice is
|
|
2002
|
+
// kept. Only the matcher is this PR's: matchPhaseDirs resolves
|
|
2003
|
+
// digit-leading directory names the token predicate cannot (#2528).
|
|
2004
|
+
const dirMatch = matchPhaseDirs(_phaseDirEntries, normalized).matches[0];
|
|
1866
2005
|
if (dirMatch) {
|
|
1867
2006
|
const fullDir = node_path_1.default.join(phasesDir, dirMatch);
|
|
1868
2007
|
const phaseDirRel = toPosixPath(node_path_1.default.relative(cwd, fullDir));
|
|
1869
2008
|
const phaseFiles = node_fs_1.default.readdirSync(fullDir);
|
|
1870
2009
|
planCount = listPhasePlanFiles(fullDir).length;
|
|
1871
2010
|
summaryCount = listPhaseSummaryFiles(fullDir).length;
|
|
1872
|
-
|
|
1873
|
-
|
|
2011
|
+
// #3511-class: scope the raw listing to THIS phase's own artifacts
|
|
2012
|
+
// before the hasContext/hasResearch predicates run, so a stray
|
|
2013
|
+
// cross-phase `-CONTEXT.md`/`-RESEARCH.md` sitting in this directory
|
|
2014
|
+
// cannot win this phase's lookup.
|
|
2015
|
+
const scopedFiles = scopeToPhase(phaseFiles, dirMatch);
|
|
2016
|
+
hasContext = findContextMdIn(scopedFiles) !== null;
|
|
2017
|
+
hasResearch = scopedFiles.some((f) => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
|
|
1874
2018
|
completion = buildPhaseCompletionProjection(cwd, phaseNum, phaseDirRel, planCount, summaryCount, _slashRuntime);
|
|
1875
2019
|
if (completion.phase_complete)
|
|
1876
2020
|
diskStatus = 'complete';
|
|
@@ -1886,7 +2030,7 @@ function cmdInitManager(cwd, raw) {
|
|
|
1886
2030
|
diskStatus = 'discussed';
|
|
1887
2031
|
else
|
|
1888
2032
|
diskStatus = 'empty';
|
|
1889
|
-
const nowMs =
|
|
2033
|
+
const nowMs = clock_cjs_1.realClock.now();
|
|
1890
2034
|
let newestMtime = 0;
|
|
1891
2035
|
for (const f of phaseFiles) {
|
|
1892
2036
|
try {
|
|
@@ -1907,18 +2051,20 @@ function cmdInitManager(cwd, raw) {
|
|
|
1907
2051
|
catch {
|
|
1908
2052
|
/* intentionally empty */
|
|
1909
2053
|
}
|
|
2054
|
+
// ADR-3180 §7.4 (disk-strict, #2957, maintainer decision 2026-08-08):
|
|
2055
|
+
// `roadmapComplete` is reported below as metadata only — it carries NO
|
|
2056
|
+
// machine authority over `diskStatus`. The #3033 checkbox override that
|
|
2057
|
+
// used to live here (treating a zero-plan phase as complete whenever the
|
|
2058
|
+
// ROADMAP checkbox was ticked, layered on top of
|
|
2059
|
+
// buildPhaseCompletionProjection's own output) is DELETED, not
|
|
2060
|
+
// generalized: `diskStatus` now comes entirely from `completion`, which
|
|
2061
|
+
// already routes through the canonical owner (`isPhaseComplete`) and
|
|
2062
|
+
// itself resolves a zero-plan phase as complete whenever a passing
|
|
2063
|
+
// `*-VERIFICATION.md` exists (#3168) — with no dependency on the
|
|
2064
|
+
// checkbox. A zero-plan phase whose completion previously relied SOLELY
|
|
2065
|
+
// on a ticked checkbox (no passing verification) now reports incomplete;
|
|
2066
|
+
// this is the deliberate Tier-2 break (ADR-3180 §7.4 Decision 3).
|
|
1910
2067
|
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
2068
|
phases.push({
|
|
1923
2069
|
number: phaseNum,
|
|
1924
2070
|
name: phaseName,
|
|
@@ -2023,7 +2169,8 @@ function cmdInitManager(cwd, raw) {
|
|
|
2023
2169
|
for (const phase of phases) {
|
|
2024
2170
|
if (phase['disk_status'] === 'complete')
|
|
2025
2171
|
continue;
|
|
2026
|
-
|
|
2172
|
+
// #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
|
|
2173
|
+
if (isSentinelPhaseId(phase['number']))
|
|
2027
2174
|
continue;
|
|
2028
2175
|
if (phase['disk_status'] === 'executed') {
|
|
2029
2176
|
recommendedActions.push({
|
|
@@ -2077,7 +2224,8 @@ function cmdInitManager(cwd, raw) {
|
|
|
2077
2224
|
}
|
|
2078
2225
|
return true;
|
|
2079
2226
|
});
|
|
2080
|
-
|
|
2227
|
+
// #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
|
|
2228
|
+
const nonBacklogPhases = phases.filter((p) => !isSentinelPhaseId(p['number']));
|
|
2081
2229
|
const completedCount = nonBacklogPhases.filter((p) => p['phase_complete'] === true).length;
|
|
2082
2230
|
const sanitizeFlags = (rawVal) => {
|
|
2083
2231
|
const val = typeof rawVal === 'string' ? rawVal : '';
|
|
@@ -2100,8 +2248,12 @@ function cmdInitManager(cwd, raw) {
|
|
|
2100
2248
|
execute: sanitizeFlags(mgrFlags?.['execute']),
|
|
2101
2249
|
};
|
|
2102
2250
|
const result = {
|
|
2103
|
-
|
|
2104
|
-
|
|
2251
|
+
// #3216 review Finding 2: `?? null` so an unresolved milestone still emits
|
|
2252
|
+
// the key with an explicit `null` rather than letting JSON.stringify drop
|
|
2253
|
+
// it — an omitted key reaches the prompt layer's `{milestone_version}`
|
|
2254
|
+
// placeholder as literal, un-substituted text.
|
|
2255
|
+
milestone_version: milestone['version'] ?? null,
|
|
2256
|
+
milestone_name: milestone['name'] ?? null,
|
|
2105
2257
|
phases,
|
|
2106
2258
|
phase_count: phases.length,
|
|
2107
2259
|
completed_count: completedCount,
|
|
@@ -2212,7 +2364,7 @@ function cmdInitDocsUpdate(cwd, raw, options = {}) {
|
|
|
2212
2364
|
* `state:chunked-mode`/`state:plan-strategy-converge`). This DELIBERATELY
|
|
2213
2365
|
* does not replace `update.md`'s own `parse_update_channel` case-statement
|
|
2214
2366
|
* (`TAG="next"`/`TAG="latest"`) — issue #815's regression test
|
|
2215
|
-
* (`tests/
|
|
2367
|
+
* (`tests/update-workflow.test.cjs`) asserts that literal
|
|
2216
2368
|
* case-statement text stays in `update.md` verbatim (the npm dist-tag
|
|
2217
2369
|
* selection has to run in the workflow's own shell before any `gsd_run`
|
|
2218
2370
|
* round-trip), so `next_channel` exists purely to gate the `channel-banner`
|
|
@@ -2258,7 +2410,9 @@ function cmdInitUpdate(cwd, raw, options = {}) {
|
|
|
2258
2410
|
* pure JSON consumer with no `gsd_run` call of its own.
|
|
2259
2411
|
*/
|
|
2260
2412
|
function cmdInitTransition(cwd, raw, options = {}) {
|
|
2261
|
-
|
|
2413
|
+
// #3579 root-cause fix: read-only informational field — peek, don't
|
|
2414
|
+
// self-heal (see cmdInitNewMilestone's identical rationale above).
|
|
2415
|
+
const resolvedWorkstream = process.env['GSD_WORKSTREAM'] || peekActiveWorkstream(cwd);
|
|
2262
2416
|
const workstreamActive = !!resolvedWorkstream;
|
|
2263
2417
|
const result = {
|
|
2264
2418
|
other_active_workstreams: workstreamActive
|
|
@@ -2339,7 +2493,7 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2339
2493
|
/* intentionally empty */
|
|
2340
2494
|
}
|
|
2341
2495
|
const config = loadConfig(cwd);
|
|
2342
|
-
const milestone =
|
|
2496
|
+
const milestone = milestoneRecord(cwd);
|
|
2343
2497
|
const _slashRuntime = (0, runtime_slash_cjs_1.resolveRuntime)(cwd);
|
|
2344
2498
|
// #1912: fail safe in workstream mode with no active workstream. With no active
|
|
2345
2499
|
// workstream and no --ws, planningDir(cwd) resolves to root .planning — silently
|
|
@@ -2347,11 +2501,27 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2347
2501
|
// Mirror planningDir's resolution (GSD_WORKSTREAM env > stored active pointer) so
|
|
2348
2502
|
// an explicit --ws (which sets GSD_WORKSTREAM) satisfies the check.
|
|
2349
2503
|
const _availableWorkstreams = listAvailableWorkstreams(cwd);
|
|
2350
|
-
|
|
2504
|
+
// #3579 root-cause fix: this is a check, not a consuming read — use the
|
|
2505
|
+
// non-mutating peek so an unresolvable pointer isn't self-healed (cleared)
|
|
2506
|
+
// here and then found "absent" by diagnoseUnresolvedActiveWorkstream below,
|
|
2507
|
+
// which would misreport a present-but-bad marker as no marker at all.
|
|
2508
|
+
const _resolvedWorkstream = process.env['GSD_WORKSTREAM'] || peekActiveWorkstream(cwd);
|
|
2351
2509
|
if (_availableWorkstreams.length > 0 && !_resolvedWorkstream) {
|
|
2510
|
+
// #3579: getActiveWorkstream now inherits a pointer-less session's read
|
|
2511
|
+
// from the shared .planning/active-workstream marker, so reaching this
|
|
2512
|
+
// branch with a marker actually present means the marker EXISTED but
|
|
2513
|
+
// didn't resolve (invalid name, or its workstream dir is gone) — a
|
|
2514
|
+
// materially different situation from "nothing was ever set" and one
|
|
2515
|
+
// that deserves its own diagnostic instead of the generic message below.
|
|
2516
|
+
const _diagnosis = diagnoseUnresolvedActiveWorkstream(cwd);
|
|
2517
|
+
if (_diagnosis.present) {
|
|
2518
|
+
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. ` +
|
|
2519
|
+
`Pass --ws <name> or run ${(0, runtime_slash_cjs_1.formatGsdSlash)('workstream set', _slashRuntime)} to point it at an existing workstream. ` +
|
|
2520
|
+
`Available workstreams: ${_availableWorkstreams.join(', ')}`, ERROR_REASON.WORKSTREAM_MODE_MARKER_UNRESOLVED, { marker_value: _diagnosis.value, marker_reason: _diagnosis.reason });
|
|
2521
|
+
}
|
|
2352
2522
|
error(`init.progress requires a workstream in workstream mode — no active workstream is set, so root STATE.md (likely stale) would be reported. ` +
|
|
2353
2523
|
`Pass --ws <name> or run ${(0, runtime_slash_cjs_1.formatGsdSlash)('workstream set', _slashRuntime)} first. ` +
|
|
2354
|
-
`Available workstreams: ${_availableWorkstreams.join(', ')}
|
|
2524
|
+
`Available workstreams: ${_availableWorkstreams.join(', ')}`, ERROR_REASON.WORKSTREAM_MODE_NONE_ACTIVE);
|
|
2355
2525
|
}
|
|
2356
2526
|
const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
|
|
2357
2527
|
const phases = [];
|
|
@@ -2378,21 +2548,15 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2378
2548
|
catch {
|
|
2379
2549
|
/* intentionally empty */
|
|
2380
2550
|
}
|
|
2381
|
-
const isDirInMilestone = getMilestonePhaseFilter(cwd);
|
|
2382
2551
|
const seenPhaseNums = new Set();
|
|
2383
2552
|
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
|
-
});
|
|
2553
|
+
// #3185 (ADR-3180 Decision 1): "which phase directories belong to the
|
|
2554
|
+
// CURRENT milestone" — routed through the canonical owner instead of a
|
|
2555
|
+
// hand-rolled readdirSync + isDirInMilestone filter + local sort (which
|
|
2556
|
+
// also never excluded sentinels, unlike the owner; the final `phases`
|
|
2557
|
+
// array is re-sorted below anyway, so dropping the local sort here is
|
|
2558
|
+
// behavior-preserving).
|
|
2559
|
+
const dirs = listMilestonePhaseDirs(phasesDir, { cwd }).value;
|
|
2396
2560
|
for (const dir of dirs) {
|
|
2397
2561
|
const dirMatch = dir.match(new RegExp(`^(${PHASE_NUMBER_TOKEN_SOURCE})-?(.*)`, 'i'));
|
|
2398
2562
|
const phaseNumber = dirMatch ? dirMatch[1] : dir;
|
|
@@ -2402,7 +2566,12 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2402
2566
|
const phaseFiles = node_fs_1.default.readdirSync(phasePath);
|
|
2403
2567
|
const plans = listPhasePlanFiles(phasePath);
|
|
2404
2568
|
const summaries = listPhaseSummaryFiles(phasePath);
|
|
2405
|
-
|
|
2569
|
+
// #3511-class: scope the raw listing to THIS phase's own artifacts
|
|
2570
|
+
// before the hasResearch predicate runs, so a stray cross-phase
|
|
2571
|
+
// `-RESEARCH.md` sitting in this directory cannot win this phase's
|
|
2572
|
+
// lookup.
|
|
2573
|
+
const scopedPhaseFiles = scopeToPhase(phaseFiles, dir);
|
|
2574
|
+
const hasResearch = scopedPhaseFiles.some((f) => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
|
|
2406
2575
|
const phaseDirRel = toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'phases', dir)));
|
|
2407
2576
|
const completion = buildPhaseCompletionProjection(cwd, phaseNumber, phaseDirRel, plans.length, summaries.length, _slashRuntime);
|
|
2408
2577
|
const status = completion.phase_complete
|
|
@@ -2464,6 +2633,25 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2464
2633
|
}
|
|
2465
2634
|
}
|
|
2466
2635
|
phases.sort((a, b) => parseInt(a['number'], 10) - parseInt(b['number'], 10));
|
|
2636
|
+
// #3581: the frontier is ROADMAP ORDER, not artifact presence. The disk loop
|
|
2637
|
+
// above could claim nextPhase from a stray out-of-order artifact directory
|
|
2638
|
+
// (a phase-9 UAT evidence dir while roadmap phase 8 was pending and
|
|
2639
|
+
// unscaffolded), silently skipping 8 — and init.progress then disagreed with
|
|
2640
|
+
// roadmap.analyze on the same tree. Re-derive from the sorted union: the
|
|
2641
|
+
// first phase that has not begun ('pending' | 'not_started') and is not
|
|
2642
|
+
// roadmap-complete wins; artifacts still feed each entry's status and
|
|
2643
|
+
// completion (corroborating evidence) but no longer outrank the ordering.
|
|
2644
|
+
// Aligned trees derive the identical frontier as the loops above; an
|
|
2645
|
+
// all-complete milestone finds none and keeps nextPhase null for the
|
|
2646
|
+
// completion flow.
|
|
2647
|
+
{
|
|
2648
|
+
const frontier = phases.find((p) => {
|
|
2649
|
+
const st = p['status'];
|
|
2650
|
+
return (st === 'pending' || st === 'not_started') && p['roadmap_complete'] !== true;
|
|
2651
|
+
});
|
|
2652
|
+
if (frontier)
|
|
2653
|
+
nextPhase = frontier;
|
|
2654
|
+
}
|
|
2467
2655
|
let pausedAt = null;
|
|
2468
2656
|
const state = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(planningDir(cwd), 'STATE.md'));
|
|
2469
2657
|
if (state !== null) {
|
|
@@ -2488,8 +2676,12 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2488
2676
|
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
|
|
2489
2677
|
planner_model: resolveModelInternal(cwd, 'gsd-planner'),
|
|
2490
2678
|
commit_docs: config.commit_docs,
|
|
2491
|
-
|
|
2492
|
-
|
|
2679
|
+
// #3216 review Finding 2: `?? null` so an unresolved milestone still emits
|
|
2680
|
+
// the key with an explicit `null` rather than letting JSON.stringify drop
|
|
2681
|
+
// it — an omitted key reaches the prompt layer's `{milestone_version}`
|
|
2682
|
+
// placeholder as literal, un-substituted text.
|
|
2683
|
+
milestone_version: milestone['version'] ?? null,
|
|
2684
|
+
milestone_name: milestone['name'] ?? null,
|
|
2493
2685
|
phases,
|
|
2494
2686
|
phase_count: phases.length,
|
|
2495
2687
|
completed_count: phases.filter((p) => p['status'] === 'complete').length,
|
|
@@ -3043,7 +3235,7 @@ function buildSkillManifest(cwd, skillsDir = null) {
|
|
|
3043
3235
|
seenNamesInRoot.add(name);
|
|
3044
3236
|
const description = frontmatter['description'] || '';
|
|
3045
3237
|
const triggers = [];
|
|
3046
|
-
const bodyMatch = content.match(/^---[\s\S]*?---\s*\n([\s\S]*)$/);
|
|
3238
|
+
const bodyMatch = content.match(/^---[\s\S]*?---\s*\r?\n([\s\S]*)$/);
|
|
3047
3239
|
if (bodyMatch) {
|
|
3048
3240
|
const body = bodyMatch[1];
|
|
3049
3241
|
const triggerLines = body.match(/^TRIGGER\s+when:\s*(.+)$/gmi);
|