@opengsd/gsd-core 1.14.0 → 1.15.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/README.ja-JP.md +3 -3
- package/README.ko-KR.md +3 -3
- package/README.pt-BR.md +3 -3
- package/README.zh-CN.md +3 -3
- package/agents/gsd-code-fixer.compact.md +7 -6
- package/agents/gsd-code-fixer.md +9 -8
- package/agents/gsd-debug-session-manager.compact.md +17 -2
- package/agents/gsd-debug-session-manager.md +17 -2
- package/agents/gsd-debugger.md +2 -2
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +13 -8
- package/agents/gsd-intel-updater.compact.md +1 -1
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-phase-researcher.md +19 -11
- package/agents/gsd-plan-checker.md +8 -7
- package/agents/gsd-planner.md +12 -8
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +1 -1
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-ui-auditor.md +155 -17
- package/agents/gsd-ui-researcher.compact.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +10 -9
- package/bin/install.js +642 -95
- package/commands/gsd/autonomous.md +2 -2
- package/commands/gsd/capture.md +1 -1
- package/commands/gsd/mempalace-capture.md +7 -3
- package/commands/gsd/plan-review-convergence.md +6 -6
- package/commands/gsd/progress.md +1 -1
- package/commands/gsd/quick-batch.md +1 -1
- package/commands/gsd/review.md +2 -3
- package/gsd-core/bin/gsd-tools.cjs +335 -22
- package/gsd-core/bin/lib/adr-parser.cjs +3 -1
- package/gsd-core/bin/lib/audit.cjs +81 -13
- package/gsd-core/bin/lib/capability-registry.cjs +82 -187
- package/gsd-core/bin/lib/capability-validator.cjs +0 -1
- package/gsd-core/bin/lib/check-command-router.cjs +101 -14
- package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
- package/gsd-core/bin/lib/commands.cjs +175 -42
- package/gsd-core/bin/lib/config-loader.cjs +65 -4
- package/gsd-core/bin/lib/config.cjs +33 -7
- package/gsd-core/bin/lib/decisions.cjs +30 -14
- package/gsd-core/bin/lib/frontmatter.cjs +13 -0
- package/gsd-core/bin/lib/graphify.cjs +10 -2
- package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
- package/gsd-core/bin/lib/init.cjs +207 -41
- package/gsd-core/bin/lib/install-engine.cjs +13 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +8 -1
- package/gsd-core/bin/lib/milestone.cjs +18 -5
- package/gsd-core/bin/lib/model-resolver.cjs +159 -50
- package/gsd-core/bin/lib/phase-command-router.cjs +9 -1
- package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
- package/gsd-core/bin/lib/phase-id.cjs +109 -7
- package/gsd-core/bin/lib/phase-locator.cjs +29 -10
- package/gsd-core/bin/lib/phase.cjs +227 -26
- package/gsd-core/bin/lib/plan-document.cjs +49 -1
- package/gsd-core/bin/lib/planning-document.cjs +459 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +18 -1
- package/gsd-core/bin/lib/planning-workspace.cjs +8 -3
- package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
- package/gsd-core/bin/lib/probe-core.cjs +7 -1
- package/gsd-core/bin/lib/project-root.cjs +41 -2
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
- package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
- package/gsd-core/bin/lib/roadmap-parser.cjs +163 -3
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
- package/gsd-core/bin/lib/roadmap.cjs +251 -31
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +283 -31
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +3 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +215 -33
- package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
- package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
- package/gsd-core/bin/lib/state-transition.cjs +39 -2
- package/gsd-core/bin/lib/state.cjs +42 -0
- package/gsd-core/bin/lib/surface.cjs +17 -1
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
- package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
- package/gsd-core/bin/lib/uat.cjs +8 -0
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
- package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
- package/gsd-core/bin/lib/verification.cjs +268 -15
- package/gsd-core/bin/lib/verify-command-grounding.cjs +46 -2
- package/gsd-core/bin/lib/verify.cjs +132 -25
- package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
- package/gsd-core/bin/lib/worktree-safety.cjs +784 -51
- package/gsd-core/bin/shared/config-defaults.manifest.json +3 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/checkpoints.md +5 -3
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe.md +195 -21
- package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
- package/gsd-core/references/execute-phase-wave-guard.md +22 -11
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/phase-argument-parsing.md +9 -7
- package/gsd-core/references/phase-id-convention.md +28 -0
- package/gsd-core/references/planner-gap-closure.md +2 -0
- package/gsd-core/references/planner-load-graph-context.md +24 -13
- package/gsd-core/references/planner-verify-command-grounding.md +14 -0
- package/gsd-core/references/planning-config.md +11 -2
- package/gsd-core/references/tdd.md +27 -4
- package/gsd-core/references/ui-consideration-probe.md +10 -5
- package/gsd-core/references/verify-command-path-resolvability.md +10 -2
- package/gsd-core/references/worktree-path-safety.md +321 -0
- package/gsd-core/templates/verification-report.md +1 -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 +1 -1
- package/gsd-core/workflows/add-tests.md +2 -2
- package/gsd-core/workflows/add-todo.md +3 -3
- package/gsd-core/workflows/ai-integration-phase.md +11 -3
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +1 -1
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
- package/gsd-core/workflows/autonomous.md +16 -6
- package/gsd-core/workflows/check-todos.md +2 -2
- package/gsd-core/workflows/cleanup.md +2 -2
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +1 -1
- package/gsd-core/workflows/code-review-fix.md +108 -22
- package/gsd-core/workflows/code-review.md +63 -46
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +1 -1
- package/gsd-core/workflows/complete-milestone.md +2 -2
- package/gsd-core/workflows/debug.md +3 -3
- package/gsd-core/workflows/diagnose-issues.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +2 -2
- package/gsd-core/workflows/docs-update.md +3 -3
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +10 -3
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +3 -3
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +37 -3
- 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 +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
- package/gsd-core/workflows/execute-phase.md +36 -26
- package/gsd-core/workflows/execute-plan.md +5 -4
- package/gsd-core/workflows/explore.md +3 -3
- package/gsd-core/workflows/extract-learnings.md +2 -1
- package/gsd-core/workflows/fast.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +2 -2
- package/gsd-core/workflows/help/modes/full.compact.md +3 -3
- package/gsd-core/workflows/help/modes/full.md +5 -5
- package/gsd-core/workflows/help/modes/topic.md +15 -5
- package/gsd-core/workflows/import.md +2 -2
- package/gsd-core/workflows/inbox.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -3
- package/gsd-core/workflows/insert-phase.md +1 -1
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +2 -2
- package/gsd-core/workflows/map-codebase.md +2 -2
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +2 -2
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
- package/gsd-core/workflows/new-project.md +7 -7
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/note.md +1 -1
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
- package/gsd-core/workflows/plan-phase.md +24 -7
- package/gsd-core/workflows/plan-review-convergence.md +21 -5
- package/gsd-core/workflows/plant-seed.md +62 -20
- package/gsd-core/workflows/pr-branch.md +113 -13
- package/gsd-core/workflows/profile-user.md +2 -2
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +1 -1
- package/gsd-core/workflows/quick.md +21 -9
- package/gsd-core/workflows/reapply-patches.md +9 -3
- package/gsd-core/workflows/remove-phase.md +1 -1
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +1 -1
- package/gsd-core/workflows/review.md +31 -16
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -2
- package/gsd-core/workflows/settings-advanced.md +30 -10
- package/gsd-core/workflows/settings-integrations.md +2 -3
- package/gsd-core/workflows/settings.md +4 -4
- package/gsd-core/workflows/ship.md +3 -2
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +2 -2
- package/gsd-core/workflows/spec-phase.md +15 -5
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +5 -5
- package/gsd-core/workflows/thread.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +44 -8
- package/gsd-core/workflows/ui-review.md +18 -4
- package/gsd-core/workflows/ultraplan-phase.md +1 -1
- package/gsd-core/workflows/undo.md +339 -20
- package/gsd-core/workflows/update.md +7 -7
- package/gsd-core/workflows/validate-phase.md +3 -2
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +81 -16
- package/hooks/dist/gsd-agent-isolation-guard.js +24 -0
- package/hooks/dist/gsd-secret-read-guard.js +27 -1
- package/hooks/dist/gsd-statusline.js +70 -13
- package/hooks/dist/gsd-validate-commit.sh +63 -4
- package/hooks/gsd-agent-isolation-guard.js +24 -0
- package/hooks/gsd-secret-read-guard.js +27 -1
- package/hooks/gsd-statusline.js +70 -13
- package/hooks/gsd-validate-commit.sh +63 -4
- package/package.json +3 -2
- package/scripts/build-hooks.js +15 -6
- package/scripts/check-contract-drift.cjs +127 -11
- package/scripts/command-contract-helpers.cjs +3 -0
- package/scripts/docs-guard-registry.cjs +28 -0
- package/scripts/gen-loop-host-contract.cjs +69 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +14 -0
- package/scripts/lib/ndjson-reporter.cjs +3 -2
- package/scripts/lib/platform-conformance-tier.generated.cjs +11 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +28 -1
- package/scripts/lint-phase-arg-assignment.cjs +257 -0
- package/scripts/lint-phase-id-drift.cjs +290 -5
- package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
- package/scripts/lint-retired-runtime-name.cjs +619 -0
- package/scripts/lint-state-write-path-drift.cjs +93 -0
- package/scripts/lint-test-file-count.allowlist.json +28 -9
- package/scripts/lint-workflow-shellcheck-baseline.json +15 -0
- package/scripts/prompt-injection-scan.sh +4 -0
- package/scripts/release-tarball-smoke.cjs +194 -1
- package/skills/gsd-autonomous/SKILL.md +2 -2
- package/skills/gsd-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +7 -3
- package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
- package/skills/gsd-progress/SKILL.md +1 -1
- package/skills/gsd-quick-batch/SKILL.md +1 -1
- package/skills/gsd-review/SKILL.md +2 -3
- package/vscode/package.json +1 -1
|
@@ -41,6 +41,8 @@ const { SCOPE } = planningScopeMod;
|
|
|
41
41
|
const secrets_cjs_1 = require("./secrets.cjs");
|
|
42
42
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-scan.cjs is an export= CommonJS module
|
|
43
43
|
const scanPhasePlans = require("./plan-scan.cjs");
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-document.cjs is an export= CommonJS module
|
|
45
|
+
const planDocument = require("./plan-document.cjs");
|
|
44
46
|
const state_document_cjs_1 = require("./state-document.cjs");
|
|
45
47
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
46
48
|
const host_runtime_detection_cjs_1 = require("./host-runtime-detection.cjs");
|
|
@@ -87,9 +89,9 @@ const { resolveModelInternal, resolveGranularityInternal, assertValidGranularity
|
|
|
87
89
|
const { findPhaseInternal, listMilestonePhaseDirs, listAllPhaseDirs } = phaseLocator;
|
|
88
90
|
const { getRoadmapPhaseInternal, getMilestoneInfo, stripShippedMilestones, extractCurrentMilestone, } = roadmapParser;
|
|
89
91
|
const { pathExistsInternal, generateSlugInternal, toPosixPath } = coreUtils;
|
|
90
|
-
const { comparePhaseNum, normalizePhaseName,
|
|
92
|
+
const { comparePhaseNum, normalizePhaseName, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, PHASE_DEP_REF_SOURCE, isForeignPrefixedPhaseQuery, isSentinelPhaseId, extractPhaseToken, scopeToPhase, renderPhaseBranchName, parsePhaseId, renderPhaseId, phaseHeadingPrefixSrcFor, PHASE_HEADING_BASELINE, buildPhaseHeadingScanRegex, } = phaseId;
|
|
91
93
|
const { pruneOrphanedWorktrees } = worktreeSafety;
|
|
92
|
-
const { planningPaths, planningDir, planningRoot, todosDir, listAvailableWorkstreams, peekActiveWorkstream, diagnoseUnresolvedActiveWorkstream, describeUnresolvedWorkstreamReason, findContextMdIn, } = planningWorkspace;
|
|
94
|
+
const { planningPaths, planningDir, planningRoot, todosDir, listAvailableWorkstreams, peekActiveWorkstream, resolveEnvWorkstream, diagnoseUnresolvedActiveWorkstream, describeUnresolvedWorkstreamReason, findContextMdIn, resolvePhaseIdConvention, } = planningWorkspace;
|
|
93
95
|
const { determinePhaseStatus } = commandsMod;
|
|
94
96
|
const { extractFrontmatter } = frontmatterMod;
|
|
95
97
|
const { isPhaseComplete, resolveVerificationFile, resolveUatFile } = verificationMod;
|
|
@@ -100,7 +102,6 @@ const { resolveCapabilityRuntimeState } = capabilityStateMod;
|
|
|
100
102
|
// Unused but imported for structural parity
|
|
101
103
|
void stripShippedMilestones;
|
|
102
104
|
// Accept all bold/colon variants of the Requirements header (#2769)
|
|
103
|
-
const REQUIREMENTS_HEADER_RE = /^\*\*Requirements:?\*\*[^\S\n]*:?[^\S\n]*([^\n]*)$/m;
|
|
104
105
|
// #2056/#2104: isForeignPrefixedPhaseQuery is imported from phase-id.cts
|
|
105
106
|
// (the canonical predicate). parsePhasePrefix is no longer needed locally.
|
|
106
107
|
// phaseInfoMatchesExactPrefix and roadmapPhaseMatchesExactPrefix are local
|
|
@@ -110,9 +111,18 @@ function phaseInfoMatchesExactPrefix(phaseInfo, phase) {
|
|
|
110
111
|
const numStr = typeof num === 'string' ? num : (typeof num === 'number' ? String(num) : '');
|
|
111
112
|
return numStr.toUpperCase() === phase.toUpperCase();
|
|
112
113
|
}
|
|
114
|
+
// #4906 Phase 5 (#4984): NOT migrated onto the heading-baseline selector —
|
|
115
|
+
// this site was never one of the five ADR-4910 §8 call sites this phase owns
|
|
116
|
+
// (init milestone/progress heading scans + milestone.cts's unstarted-phase
|
|
117
|
+
// guard, all via buildPhaseHeadingScanRegex), and adding a NEW direct
|
|
118
|
+
// any-bracket selector consumer here inflated init.cts's
|
|
119
|
+
// tests/adr-612-bracket-heading-selection.test.cjs census past its pinned
|
|
120
|
+
// count. Left bracket-blind deliberately, matching this call's pre-#4984
|
|
121
|
+
// behavior; folding it into the census is Phase 6 work.
|
|
113
122
|
function roadmapPhaseMatchesExactPrefix(roadmapPhase, phase) {
|
|
114
123
|
const sectionRaw = roadmapPhase?.['section'];
|
|
115
124
|
const section = typeof sectionRaw === 'string' ? sectionRaw : '';
|
|
125
|
+
// phase-id-owner: deliberately unmigrated (#4984 revert) — see comment above the function.
|
|
116
126
|
return new RegExp(`^#{2,4}\\s*Phase\\s+${(0, pattern_cjs_1.escapeRegex)(phase)}(?:\\b|\\s|:)`, 'i').test(section);
|
|
117
127
|
}
|
|
118
128
|
// #2104: shared helpers that wrap findPhaseInternal / getRoadmapPhaseInternal
|
|
@@ -584,6 +594,13 @@ function detectPhaseMvpMode(cwd, phaseNumber) {
|
|
|
584
594
|
const rawContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
585
595
|
const content = extractCurrentMilestone(rawContent, cwd);
|
|
586
596
|
const escapedPhase = (0, pattern_cjs_1.escapeRegex)(phaseNumber);
|
|
597
|
+
// #4906 Phase 5 (#4984): NOT migrated onto phaseHeadingPrefixSrcFor — same
|
|
598
|
+
// out-of-scope reasoning as roadmapPhaseMatchesExactPrefix above (only the
|
|
599
|
+
// five buildPhaseHeadingScanRegex sites in init.cts/milestone.cts are this
|
|
600
|
+
// phase's owned migration); a direct selector call here would have added
|
|
601
|
+
// a fourth uncounted ANY_BRACKET consumer to init.cts's pinned
|
|
602
|
+
// tests/adr-612-bracket-heading-selection.test.cjs census.
|
|
603
|
+
// phase-id-owner: deliberately unmigrated (#4984 revert) — see comment above.
|
|
587
604
|
const phaseHeader = new RegExp(`#{2,4}\\s*Phase\\s+${escapedPhase}(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:`, 'i');
|
|
588
605
|
const headerMatch = content.match(phaseHeader);
|
|
589
606
|
if (!headerMatch || headerMatch.index === undefined)
|
|
@@ -751,6 +768,54 @@ function buildSectionManifestField(cwd, phaseInfo, options, workflow, overrides
|
|
|
751
768
|
function milestoneRecord(cwd) {
|
|
752
769
|
return (getMilestoneInfo(cwd).value ?? {});
|
|
753
770
|
}
|
|
771
|
+
/**
|
|
772
|
+
* #4683 — threat IDs claimed by more than one of the phase's live PLAN files.
|
|
773
|
+
* `scanPhasePlans().planFiles` is the right input set twice over: it excludes
|
|
774
|
+
* derivative files (OUTLINE / PLAN-REVIEW / pre-bounce) AND `status: superseded`
|
|
775
|
+
* plans (#2349) — a superseded plan's IDs were deliberately reassigned to its
|
|
776
|
+
* replacement, so they must not hold against it. The per-document row parse is
|
|
777
|
+
* planDocument.extractThreatRegisterIds; only `<threat_model>` register rows
|
|
778
|
+
* count, and the reserved `T-{phase}-SC` shape is excluded there by grammar
|
|
779
|
+
* (every plan keeps that row by design). A missing/unreadable phase directory
|
|
780
|
+
* degrades to "no duplicates" — planning a brand-new phase has nothing to
|
|
781
|
+
* collide with.
|
|
782
|
+
*/
|
|
783
|
+
function findDuplicateThreatIds(cwd, phaseDirRel) {
|
|
784
|
+
if (!phaseDirRel)
|
|
785
|
+
return [];
|
|
786
|
+
const phaseDir = node_path_1.default.join(cwd, phaseDirRel);
|
|
787
|
+
let planFiles;
|
|
788
|
+
try {
|
|
789
|
+
planFiles = scanPhasePlans(phaseDir).planFiles;
|
|
790
|
+
}
|
|
791
|
+
catch {
|
|
792
|
+
return [];
|
|
793
|
+
}
|
|
794
|
+
const owners = new Map();
|
|
795
|
+
for (const planFile of planFiles) {
|
|
796
|
+
let content;
|
|
797
|
+
try {
|
|
798
|
+
content = node_fs_1.default.readFileSync(node_path_1.default.join(phaseDir, planFile), 'utf-8');
|
|
799
|
+
}
|
|
800
|
+
catch {
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
for (const id of planDocument.extractThreatRegisterIds(content)) {
|
|
804
|
+
const claimed = owners.get(id);
|
|
805
|
+
if (claimed) {
|
|
806
|
+
if (!claimed.includes(planFile))
|
|
807
|
+
claimed.push(planFile);
|
|
808
|
+
}
|
|
809
|
+
else {
|
|
810
|
+
owners.set(id, [planFile]);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
return [...owners.entries()]
|
|
815
|
+
.filter(([, claims]) => claims.length > 1)
|
|
816
|
+
.map(([id, plans]) => ({ id, plans: [...plans].sort() }))
|
|
817
|
+
.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
818
|
+
}
|
|
754
819
|
function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
755
820
|
if (!phase) {
|
|
756
821
|
error('phase required for init execute-phase');
|
|
@@ -788,9 +853,14 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
788
853
|
has_reviews: false,
|
|
789
854
|
};
|
|
790
855
|
});
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
856
|
+
// #4731: multiline-aware — the Requirements field may hard-wrap, so the
|
|
857
|
+
// value is extracted past the line break before the ID scan.
|
|
858
|
+
const phaseSection = roadmapPhase?.['section'];
|
|
859
|
+
const reqLine = phaseSection
|
|
860
|
+
? roadmapParser.extractPhaseFieldMultiline(phaseSection, 'Requirements')
|
|
861
|
+
: null;
|
|
862
|
+
const reqExtracted = reqLine
|
|
863
|
+
? reqLine.replace(/[\[\]]/g, '').split(',').map((s) => s.trim()).filter(Boolean).join(', ')
|
|
794
864
|
: null;
|
|
795
865
|
const phase_req_ids = reqExtracted && reqExtracted !== 'TBD' ? reqExtracted : null;
|
|
796
866
|
// #3188: these paths are null when the file is absent, matching the contract
|
|
@@ -800,6 +870,8 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
800
870
|
const statePath = node_path_1.default.join(planningDir(cwd), 'STATE.md');
|
|
801
871
|
const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
|
|
802
872
|
const requirementsPath = node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md');
|
|
873
|
+
// #4683: computed once — see the threat_id_duplicates fields in the payload.
|
|
874
|
+
const threatIdDuplicates = findDuplicateThreatIds(cwd, phaseInfo?.['directory']);
|
|
803
875
|
const result = {
|
|
804
876
|
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
|
|
805
877
|
verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
|
|
@@ -819,6 +891,13 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
819
891
|
? toPosixPath(node_path_1.default.join(cwd, phaseInfo['directory']))
|
|
820
892
|
: null,
|
|
821
893
|
phase_number: phaseInfo?.['phase_number'] || null,
|
|
894
|
+
// #4748: the disk path hands back the directory's padded number (`03A`)
|
|
895
|
+
// but the ROADMAP fallback above hands back the heading's bare one (`3A`),
|
|
896
|
+
// and execute-phase.md's review lookup needs the padded form for
|
|
897
|
+
// `{PADDED}-REVIEW.md`. It used to re-pad in shell with `printf "%02d"`,
|
|
898
|
+
// which cannot pad a letter id and reads an already-padded `08` as octal.
|
|
899
|
+
// Emit the canonical normalization, as the plan-phase/code-review inits do.
|
|
900
|
+
padded_phase: phaseInfo?.['phase_number'] ? normalizePhaseName(phaseInfo['phase_number']) : null,
|
|
822
901
|
// #3171: prefer the ROADMAP's curated display name for `phase_name`. When
|
|
823
902
|
// the phase directory already exists on disk, the disk-lookup path
|
|
824
903
|
// (searchPhaseInDir) derives phase_name from the directory-name remainder
|
|
@@ -837,6 +916,12 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
837
916
|
incomplete_plans: phaseInfo?.['incomplete_plans'] || [],
|
|
838
917
|
plan_count: phaseInfo?.['plans']?.length || 0,
|
|
839
918
|
incomplete_count: phaseInfo?.['incomplete_plans']?.length || 0,
|
|
919
|
+
// #4683: cross-plan threat-ID collisions (gap-closure plans renumbering
|
|
920
|
+
// from T-{phase}-01 again). execute-phase.md hard-stops on a non-empty
|
|
921
|
+
// list BEFORE any dispatch — SECURITY.md rows and VALIDATION.md's Threat
|
|
922
|
+
// Ref column key on this ID, so a reused ID is ambiguous downstream.
|
|
923
|
+
threat_id_duplicates: threatIdDuplicates,
|
|
924
|
+
threat_id_duplicate_count: threatIdDuplicates.length,
|
|
840
925
|
// #2830: the halt-aware view, forwarded from the shared computation in
|
|
841
926
|
// phase-locator. Additive — `incomplete_plans`/`incomplete_count` above keep
|
|
842
927
|
// their exact name, type and semantics. Without this passthrough the shared
|
|
@@ -930,9 +1015,14 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
930
1015
|
has_reviews: false,
|
|
931
1016
|
};
|
|
932
1017
|
});
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1018
|
+
// #4731: multiline-aware — the Requirements field may hard-wrap, so the
|
|
1019
|
+
// value is extracted past the line break before the ID scan.
|
|
1020
|
+
const phaseSection = roadmapPhase?.['section'];
|
|
1021
|
+
const reqLine = phaseSection
|
|
1022
|
+
? roadmapParser.extractPhaseFieldMultiline(phaseSection, 'Requirements')
|
|
1023
|
+
: null;
|
|
1024
|
+
const reqExtracted = reqLine
|
|
1025
|
+
? reqLine.replace(/[\[\]]/g, '').split(',').map((s) => s.trim()).filter(Boolean).join(', ')
|
|
936
1026
|
: null;
|
|
937
1027
|
const phase_req_ids = reqExtracted && reqExtracted !== 'TBD' ? reqExtracted : null;
|
|
938
1028
|
const phaseDirPlan = phaseInfo?.['directory'] || null;
|
|
@@ -952,6 +1042,8 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
952
1042
|
}
|
|
953
1043
|
const granularityOverride = options['granularity'];
|
|
954
1044
|
assertValidGranularityOverride(granularityOverride, error);
|
|
1045
|
+
// #4683: computed once — see the threat_id_duplicates fields in the payload.
|
|
1046
|
+
const threatIdDuplicatesPlan = findDuplicateThreatIds(cwd, phaseDirPlan);
|
|
955
1047
|
const granularity = resolveGranularityInternal(cwd, 'planning', granularityOverride || undefined);
|
|
956
1048
|
// #3188: see cmdInitExecutePhase — null when absent, parity with the
|
|
957
1049
|
// conditional sibling fields in this same result object.
|
|
@@ -996,6 +1088,11 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
996
1088
|
has_reviews: phaseInfo?.['has_reviews'] || false,
|
|
997
1089
|
has_plans: (phaseInfo?.['plans']?.length || 0) > 0,
|
|
998
1090
|
plan_count: phaseInfo?.['plans']?.length || 0,
|
|
1091
|
+
// #4683: the same cross-plan threat-ID duplicate list execute-phase gates
|
|
1092
|
+
// on, surfaced at PLAN time so the checker/reviewer catches the collision
|
|
1093
|
+
// before the plans are approved — not just before execution.
|
|
1094
|
+
threat_id_duplicates: threatIdDuplicatesPlan,
|
|
1095
|
+
threat_id_duplicate_count: threatIdDuplicatesPlan.length,
|
|
999
1096
|
planning_exists: node_fs_1.default.existsSync(planningDir(cwd)),
|
|
1000
1097
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1001
1098
|
// #2376: absolute — see comment on phase_dir above.
|
|
@@ -1336,7 +1433,7 @@ function cmdInitNewMilestone(cwd, raw, options = {}) {
|
|
|
1336
1433
|
// would otherwise silently delete a stale/invalid pointer as a side effect
|
|
1337
1434
|
// of building a JSON report field, and (per #3579) could change what a
|
|
1338
1435
|
// LATER resolution in the same process observes.
|
|
1339
|
-
const resolvedWorkstream =
|
|
1436
|
+
const resolvedWorkstream = resolveEnvWorkstream() ?? peekActiveWorkstream(cwd);
|
|
1340
1437
|
const workstreamActive = !!resolvedWorkstream;
|
|
1341
1438
|
const flatMode = !workstreamActive;
|
|
1342
1439
|
// #2992 (Phase 6.1): additive, optional field — degrades to null, never throws.
|
|
@@ -2225,14 +2322,16 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
2225
2322
|
const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
|
|
2226
2323
|
const roadmapRaw = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
2227
2324
|
const currentSection = extractCurrentMilestone(roadmapRaw, cwd);
|
|
2228
|
-
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag
|
|
2229
|
-
|
|
2325
|
+
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag, owned by
|
|
2326
|
+
// buildPhaseHeadingScanRegex (phase-id.cts) so this scan also recognizes
|
|
2327
|
+
// bracket-convention headings instead of hand-rolling a literal `Phase\s+`.
|
|
2328
|
+
const { regex: phasePattern, phaseNumGroup } = buildPhaseHeadingScanRegex(PHASE_HEADING_BASELINE.ANY_BRACKET, resolvePhaseIdConvention(cwd));
|
|
2230
2329
|
let m;
|
|
2231
2330
|
while ((m = phasePattern.exec(currentSection)) !== null) {
|
|
2232
2331
|
// #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
|
|
2233
|
-
if (isSentinelPhaseId(m[
|
|
2332
|
+
if (isSentinelPhaseId(m[phaseNumGroup]))
|
|
2234
2333
|
continue;
|
|
2235
|
-
roadmapPhaseNumbers.push(m[
|
|
2334
|
+
roadmapPhaseNumbers.push(m[phaseNumGroup]);
|
|
2236
2335
|
}
|
|
2237
2336
|
}
|
|
2238
2337
|
catch {
|
|
@@ -2375,6 +2474,10 @@ function cmdInitManager(cwd, raw) {
|
|
|
2375
2474
|
const config = loadConfig(cwd);
|
|
2376
2475
|
const milestone = milestoneRecord(cwd);
|
|
2377
2476
|
const _slashRuntime = (0, runtime_slash_cjs_1.resolveRuntime)(cwd);
|
|
2477
|
+
const phaseIdConvention = resolvePhaseIdConvention(cwd);
|
|
2478
|
+
const capturesBracketId = phaseIdConvention === 'bracket';
|
|
2479
|
+
const phaseHeadingPrefix = phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, phaseIdConvention, capturesBracketId);
|
|
2480
|
+
const phaseHeadingPrefixNoCapture = phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, phaseIdConvention);
|
|
2378
2481
|
const paths = planningPaths(cwd);
|
|
2379
2482
|
if (!node_fs_1.default.existsSync(paths.roadmap)) {
|
|
2380
2483
|
error(`No ROADMAP.md found. Run ${(0, runtime_slash_cjs_1.formatGsdSlash)('new-milestone', _slashRuntime)} first.`);
|
|
@@ -2384,38 +2487,48 @@ function cmdInitManager(cwd, raw) {
|
|
|
2384
2487
|
}
|
|
2385
2488
|
const rawContent = node_fs_1.default.readFileSync(paths.roadmap, 'utf-8');
|
|
2386
2489
|
const content = extractCurrentMilestone(rawContent, cwd);
|
|
2387
|
-
const phasesDir = paths.phases;
|
|
2388
2490
|
// #3185 (ADR-3180 Decision 1): "which phase directories belong to the
|
|
2389
2491
|
// CURRENT milestone" is the scoped question listMilestonePhaseDirs owns —
|
|
2390
2492
|
// routed through it instead of a hand-rolled readdirSync + a separate
|
|
2391
2493
|
// getMilestonePhaseFilter window check (which also never excluded
|
|
2392
2494
|
// sentinels, unlike the owner).
|
|
2393
|
-
const _phaseDirEntries = listMilestonePhaseDirs(phasesDir, { cwd }).value;
|
|
2394
2495
|
const _checkboxStates = new Map();
|
|
2395
|
-
const _cbPattern = new RegExp(`-\\s*\\[(x| )\\]\\s
|
|
2496
|
+
const _cbPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*${phaseHeadingPrefix}(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
|
|
2396
2497
|
let _cbMatch;
|
|
2397
2498
|
while ((_cbMatch = _cbPattern.exec(content)) !== null) {
|
|
2398
|
-
|
|
2499
|
+
const phaseGroup = capturesBracketId ? 3 : 2;
|
|
2500
|
+
_checkboxStates.set(_cbMatch[phaseGroup], _cbMatch[1].toLowerCase() === 'x');
|
|
2399
2501
|
}
|
|
2400
2502
|
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
2401
|
-
const phasePattern = new RegExp(`#{2,4}\\s
|
|
2503
|
+
const phasePattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefix}(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gi');
|
|
2402
2504
|
const phases = [];
|
|
2403
2505
|
let match;
|
|
2404
2506
|
while ((match = phasePattern.exec(content)) !== null) {
|
|
2405
|
-
const
|
|
2406
|
-
const
|
|
2507
|
+
const bracketId = capturesBracketId ? match[1] : undefined;
|
|
2508
|
+
const phaseNum = capturesBracketId ? match[2] : match[1];
|
|
2509
|
+
const phaseName = (capturesBracketId ? match[3] : match[2])
|
|
2510
|
+
.replace(/\(INSERTED\)/i, '')
|
|
2511
|
+
.trim();
|
|
2512
|
+
let displayId;
|
|
2513
|
+
if (bracketId) {
|
|
2514
|
+
try {
|
|
2515
|
+
displayId = renderPhaseId(parsePhaseId(`${bracketId}-${phaseNum}`));
|
|
2516
|
+
}
|
|
2517
|
+
catch {
|
|
2518
|
+
// The bracket selector is deliberately read-tolerant. If a heading is
|
|
2519
|
+
// non-canonical, retain the manager row without fabricating display_id.
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2407
2522
|
const sectionStart = match.index;
|
|
2408
2523
|
const restOfContent = content.slice(sectionStart);
|
|
2409
|
-
const nextHeader = restOfContent.match(
|
|
2524
|
+
const nextHeader = restOfContent.match(new RegExp(`\\n#{2,4}\\s+${phaseHeadingPrefixNoCapture}\\d[\\d.]*`, 'i'));
|
|
2410
2525
|
const sectionEnd = nextHeader
|
|
2411
2526
|
? sectionStart + nextHeader.index
|
|
2412
2527
|
: content.length;
|
|
2413
2528
|
const section = content.slice(sectionStart, sectionEnd);
|
|
2414
|
-
const
|
|
2415
|
-
const goal = goalMatch ? goalMatch[1].trim() : null;
|
|
2529
|
+
const goal = roadmapParser.extractPhaseFieldMultiline(section, 'Goal');
|
|
2416
2530
|
const dependsMatch = section.match(/\*\*Depends on(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
2417
2531
|
const depends_on = dependsMatch ? dependsMatch[1].trim() : null;
|
|
2418
|
-
const normalized = normalizePhaseName(phaseNum);
|
|
2419
2532
|
let diskStatus = 'no_directory';
|
|
2420
2533
|
let planCount = 0;
|
|
2421
2534
|
let summaryCount = 0;
|
|
@@ -2428,14 +2541,23 @@ function cmdInitManager(cwd, raw) {
|
|
|
2428
2541
|
let contextScope = SCOPE.COMPLETE;
|
|
2429
2542
|
let completion = buildPhaseCompletionProjection(cwd, phaseNum, null, planCount, summaryCount, _slashRuntime);
|
|
2430
2543
|
try {
|
|
2431
|
-
// #
|
|
2432
|
-
// milestone-
|
|
2433
|
-
//
|
|
2434
|
-
//
|
|
2435
|
-
|
|
2544
|
+
// #4801: resolve through the canonical locator instead of a private
|
|
2545
|
+
// current-milestone-only scan. findPhaseInternal searches the live
|
|
2546
|
+
// .planning/phases directory FIRST (same set the retired
|
|
2547
|
+
// matchPhaseDirs/_phaseDirEntries pair scanned — the #3185 physical-dir
|
|
2548
|
+
// scope choice is inherited by the locator's live arm) and then falls
|
|
2549
|
+
// back through listArchiveVersionDirs (workstream-scoped, #2855), so an
|
|
2550
|
+
// ARCHIVED phase directory with a passing verification resolves instead
|
|
2551
|
+
// of reporting no_directory/phase_complete:false. Five other init
|
|
2552
|
+
// commands already route through this same primitive; this was the
|
|
2553
|
+
// holdout private copy (#4793-family declared-owner drift).
|
|
2554
|
+
const located = findPhaseInternal(cwd, phaseNum, phaseIdConvention);
|
|
2555
|
+
const dirMatch = located && located['found']
|
|
2556
|
+
? node_path_1.default.posix.basename(String(located['directory']))
|
|
2557
|
+
: null;
|
|
2436
2558
|
if (dirMatch) {
|
|
2437
|
-
const fullDir = node_path_1.default.join(
|
|
2438
|
-
const phaseDirRel =
|
|
2559
|
+
const fullDir = node_path_1.default.join(cwd, String(located['directory']));
|
|
2560
|
+
const phaseDirRel = String(located['directory']);
|
|
2439
2561
|
// #4014 (epic #3473 B4-unreadable): this whole block used to swallow
|
|
2440
2562
|
// ANY readdirSync failure below into the bare `catch { /* empty */ }`
|
|
2441
2563
|
// at the bottom — an unreadable phase directory reported the exact
|
|
@@ -2509,6 +2631,7 @@ function cmdInitManager(cwd, raw) {
|
|
|
2509
2631
|
const roadmapComplete = _checkboxStates.get(phaseNum) || false;
|
|
2510
2632
|
phases.push({
|
|
2511
2633
|
number: phaseNum,
|
|
2634
|
+
...(displayId ? { display_id: displayId } : {}),
|
|
2512
2635
|
name: phaseName,
|
|
2513
2636
|
goal,
|
|
2514
2637
|
depends_on,
|
|
@@ -2549,7 +2672,7 @@ function cmdInitManager(cwd, raw) {
|
|
|
2549
2672
|
.filter((p) => p['phase_complete'] === true)
|
|
2550
2673
|
.map((p) => normalizePhaseNumber(p['number'])));
|
|
2551
2674
|
const phaseMap = new Map(phases.map((p) => [normalizePhaseNumber(p['number']), p]));
|
|
2552
|
-
const _allCompletedPattern = new RegExp(`-\\s*\\[x\\]\\s
|
|
2675
|
+
const _allCompletedPattern = new RegExp(`-\\s*\\[x\\]\\s*.*${phaseHeadingPrefixNoCapture}(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
|
|
2553
2676
|
let _allMatch;
|
|
2554
2677
|
while ((_allMatch = _allCompletedPattern.exec(rawContent)) !== null) {
|
|
2555
2678
|
const phaseNum = normalizePhaseNumber(_allMatch[1]);
|
|
@@ -2575,13 +2698,47 @@ function cmdInitManager(cwd, raw) {
|
|
|
2575
2698
|
function hasDepRelationship(numA, numB) {
|
|
2576
2699
|
return reaches(numA, numB) || reaches(numB, numA);
|
|
2577
2700
|
}
|
|
2701
|
+
// #4764: a phase reference in depends_on prose is a PHASE-SHAPED token in
|
|
2702
|
+
// context — directly following "Phase"/"Phases" — never a bare digit run.
|
|
2703
|
+
// The previous whole-field scrape matched the token grammar against every
|
|
2704
|
+
// digit run, so calendar dates ("2026-09-14" → 2026, 09, 14), git shas
|
|
2705
|
+
// ("8bf403100d" → 8b, 403100d, …), bracketed ledger ids (WINDOWS #1843) and
|
|
2706
|
+
// the row's OWN number all became "dependencies", and deps_satisfied came
|
|
2707
|
+
// back false for phases whose prose declares none (50 of 92 phases in the
|
|
2708
|
+
// reporter's milestone). The anchored grammar (owned by phase-id.cts as
|
|
2709
|
+
// PHASE_DEP_REF_SOURCE, shared with planning-inspect's dependencies) keeps
|
|
2710
|
+
// lists fully extracted ("Phases 601 and 602", "Phase 601, 602, and 603",
|
|
2711
|
+
// "Phase 1-3") — silently dropping a REAL dependency would clear
|
|
2712
|
+
// deps_satisfied prematurely, the dangerous direction. Negation prose
|
|
2713
|
+
// ("dropped the dependency on Phase 654") is NOT detected: the issue's own
|
|
2714
|
+
// minimum keeps such tokens.
|
|
2715
|
+
const depPhaseRefRe = new RegExp(`${PHASE_DEP_REF_SOURCE}`, 'gi');
|
|
2716
|
+
const depTokenRe = new RegExp(`${PHASE_NUMBER_TOKEN_SOURCE}`, 'gi');
|
|
2578
2717
|
for (const phase of phases) {
|
|
2579
2718
|
if (!phase['depends_on'] ||
|
|
2580
2719
|
/^none$/i.test(phase['depends_on'].trim())) {
|
|
2581
2720
|
phase['deps_satisfied'] = true;
|
|
2582
2721
|
}
|
|
2583
2722
|
else {
|
|
2584
|
-
const
|
|
2723
|
+
const prose = phase['depends_on'];
|
|
2724
|
+
const ownNumber = normalizePhaseNumber(phase['number']);
|
|
2725
|
+
const depNums = [];
|
|
2726
|
+
const seen = new Set();
|
|
2727
|
+
let refMatch;
|
|
2728
|
+
depPhaseRefRe.lastIndex = 0;
|
|
2729
|
+
while ((refMatch = depPhaseRefRe.exec(prose)) !== null) {
|
|
2730
|
+
let tok;
|
|
2731
|
+
depTokenRe.lastIndex = 0;
|
|
2732
|
+
while ((tok = depTokenRe.exec(refMatch[1])) !== null) {
|
|
2733
|
+
const normalized = normalizePhaseNumber(tok[0]);
|
|
2734
|
+
if (normalized === ownNumber)
|
|
2735
|
+
continue; // #4764: never the row's own phase
|
|
2736
|
+
if (seen.has(normalized))
|
|
2737
|
+
continue;
|
|
2738
|
+
seen.add(normalized);
|
|
2739
|
+
depNums.push(tok[0]);
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2585
2742
|
phase['deps_satisfied'] = depNums.every((n) => completedNums.has(normalizePhaseNumber(n)));
|
|
2586
2743
|
phase['dep_phases'] = depNums;
|
|
2587
2744
|
}
|
|
@@ -2921,7 +3078,7 @@ function cmdInitUpdate(cwd, raw, options = {}) {
|
|
|
2921
3078
|
function cmdInitTransition(cwd, raw, options = {}) {
|
|
2922
3079
|
// #3579 root-cause fix: read-only informational field — peek, don't
|
|
2923
3080
|
// self-heal (see cmdInitNewMilestone's identical rationale above).
|
|
2924
|
-
const resolvedWorkstream =
|
|
3081
|
+
const resolvedWorkstream = resolveEnvWorkstream() ?? peekActiveWorkstream(cwd);
|
|
2925
3082
|
const workstreamActive = !!resolvedWorkstream;
|
|
2926
3083
|
const result = {
|
|
2927
3084
|
other_active_workstreams: workstreamActive
|
|
@@ -3013,7 +3170,7 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
3013
3170
|
// non-mutating peek so an unresolvable pointer isn't self-healed (cleared)
|
|
3014
3171
|
// here and then found "absent" by diagnoseUnresolvedActiveWorkstream below,
|
|
3015
3172
|
// which would misreport a present-but-bad marker as no marker at all.
|
|
3016
|
-
const _resolvedWorkstream =
|
|
3173
|
+
const _resolvedWorkstream = resolveEnvWorkstream() ?? peekActiveWorkstream(cwd);
|
|
3017
3174
|
if (_availableWorkstreams.length > 0 && !_resolvedWorkstream) {
|
|
3018
3175
|
// #3579: getActiveWorkstream now inherits a pointer-less session's read
|
|
3019
3176
|
// from the shared .planning/active-workstream marker, so reaching this
|
|
@@ -3040,13 +3197,22 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
3040
3197
|
const roadmapCheckboxStates = new Map();
|
|
3041
3198
|
try {
|
|
3042
3199
|
const roadmapContent = extractCurrentMilestone(node_fs_1.default.readFileSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md'), 'utf-8'), cwd);
|
|
3043
|
-
|
|
3044
|
-
|
|
3200
|
+
const progressConvention = resolvePhaseIdConvention(cwd);
|
|
3201
|
+
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag, owned by
|
|
3202
|
+
// buildPhaseHeadingScanRegex (phase-id.cts) so this scan also recognizes
|
|
3203
|
+
// bracket-convention headings instead of hand-rolling a literal `Phase\s+`.
|
|
3204
|
+
const { regex: headingPattern, phaseNumGroup: hNumGroup, phaseNameGroup: hNameGroup } = buildPhaseHeadingScanRegex(PHASE_HEADING_BASELINE.ANY_BRACKET, progressConvention);
|
|
3045
3205
|
let hm;
|
|
3046
3206
|
while ((hm = headingPattern.exec(roadmapContent)) !== null) {
|
|
3047
|
-
roadmapPhaseNums.add(hm[
|
|
3048
|
-
roadmapPhaseNames.set(hm[
|
|
3049
|
-
}
|
|
3207
|
+
roadmapPhaseNums.add(hm[hNumGroup]);
|
|
3208
|
+
roadmapPhaseNames.set(hm[hNumGroup], hm[hNameGroup].replace(/\(INSERTED\)/i, '').trim());
|
|
3209
|
+
}
|
|
3210
|
+
// #4906 Phase 5 (#4984): NOT migrated onto phaseHeadingPrefixSrcFor — same
|
|
3211
|
+
// out-of-scope reasoning as roadmapPhaseMatchesExactPrefix/detectPhaseMvpMode
|
|
3212
|
+
// above; a direct LABEL_ONLY selector call here would have added a third
|
|
3213
|
+
// uncounted LABEL_ONLY consumer to init.cts's pinned
|
|
3214
|
+
// tests/adr-612-bracket-heading-selection.test.cjs census.
|
|
3215
|
+
// phase-id-owner: deliberately unmigrated (#4984 revert) — see comment above.
|
|
3050
3216
|
const cbPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
|
|
3051
3217
|
let cbm;
|
|
3052
3218
|
while ((cbm = cbPattern.exec(roadmapContent)) !== null) {
|
|
@@ -730,6 +730,16 @@ function _copyStaged(stagedDir, destDir, kind, configDir, runtime) {
|
|
|
730
730
|
continue;
|
|
731
731
|
if (!entry.name.endsWith('.md'))
|
|
732
732
|
continue;
|
|
733
|
+
// #4782: compact agent variants are consumed ONLY through the non-claude
|
|
734
|
+
// gate in init.cts's agent-skills persona fallback (`runtime !== 'claude'`
|
|
735
|
+
// — claude's contract is a skills-injection path, never a persona
|
|
736
|
+
// fallback). Staging them into Claude's agents directory shipped 29 dead
|
|
737
|
+
// files whose `name:` frontmatter is identical to their canonical
|
|
738
|
+
// sibling's, leaving the harness resolution unstated. Claude never
|
|
739
|
+
// selects compact, so claude is the one runtime whose agents kind skips
|
|
740
|
+
// them; every other runtime's emission is byte-identical.
|
|
741
|
+
if (kind.kind === 'agents' && runtime === 'claude' && entry.name.endsWith('.compact.md'))
|
|
742
|
+
continue;
|
|
733
743
|
const stem = entry.name.slice(0, -3); // strip .md
|
|
734
744
|
let destName;
|
|
735
745
|
if (kind.kind === 'agents') {
|
|
@@ -1881,6 +1891,9 @@ function installOpencodeFamilyArtifacts(runtime, configDir, scope, resolvedProfi
|
|
|
1881
1891
|
isWindowsHost: process.platform === 'win32',
|
|
1882
1892
|
resolvedTarget: (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.resolve(configDir)),
|
|
1883
1893
|
homeDir: (0, shell_command_projection_cjs_1.posixNormalize)(node_os_1.default.homedir()),
|
|
1894
|
+
// #4377: the runtime's own localConfigDir, so an opted-in local install
|
|
1895
|
+
// emits `<dir>/...` instead of this checkout's absolute path.
|
|
1896
|
+
localDirName: runtimeArtifactConversion._localIncludeDirName(runtime),
|
|
1884
1897
|
});
|
|
1885
1898
|
// #2329: destDir is derived from the SAME hostBehaviors.flatCommandDir
|
|
1886
1899
|
// descriptor value read by writeManifest's manifest-key prefix and by
|
|
@@ -280,7 +280,14 @@ function readInstallManifest(configDir) {
|
|
|
280
280
|
version: typeof m.version === 'string' ? m.version : null,
|
|
281
281
|
timestamp: typeof m.timestamp === 'string' ? m.timestamp : null,
|
|
282
282
|
mode: typeof m.mode === 'string' ? m.mode : null,
|
|
283
|
-
|
|
283
|
+
// #4544 (review): `typeof [] === 'object'` — a manifest whose `files` is a
|
|
284
|
+
// JSON array passed the object-shape guard, and Object.keys() then yielded
|
|
285
|
+
// "0","1",... as install-relative file paths. Consumers iterate these keys,
|
|
286
|
+
// so an array shape must degrade to the empty set exactly like a
|
|
287
|
+
// non-object shape does.
|
|
288
|
+
files: m.files && typeof m.files === 'object' && !Array.isArray(m.files)
|
|
289
|
+
? m.files
|
|
290
|
+
: {},
|
|
284
291
|
manifestVersion: normalizeManifestVersion(m.manifestVersion),
|
|
285
292
|
runtime: normalizeReportedRuntime(rawRuntime),
|
|
286
293
|
scope: (0, install_scope_cjs_1.isInstallScopeId)(m.scope) ? m.scope : null,
|
|
@@ -38,7 +38,7 @@ const stateContract = require("./state-contract.cjs");
|
|
|
38
38
|
const { publishStateContract } = stateContract;
|
|
39
39
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
40
40
|
const phaseIdMod = require("./phase-id.cjs");
|
|
41
|
-
const { normalizePhaseName, matchPhaseDirs,
|
|
41
|
+
const { normalizePhaseName, matchPhaseDirs, isSentinelPhaseId, isSentinelPhaseDir, buildPhaseHeadingScanRegex, PHASE_HEADING_BASELINE } = phaseIdMod;
|
|
42
42
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
43
43
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
44
44
|
const roadmapParserMod = require("./roadmap-parser.cjs");
|
|
@@ -642,8 +642,21 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
642
642
|
// windows converge to the same value regardless of which version drove
|
|
643
643
|
// the lookup.
|
|
644
644
|
const scopedContent = sliceMilestoneWindow(roadmapContent, version) ?? extractCurrentMilestone(roadmapContent, cwd);
|
|
645
|
-
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag
|
|
646
|
-
|
|
645
|
+
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag, owned
|
|
646
|
+
// by buildPhaseHeadingScanRegex (phase-id.cts) so this guard also
|
|
647
|
+
// recognizes bracket-convention headings instead of hand-rolling a
|
|
648
|
+
// literal `Phase\s+`.
|
|
649
|
+
// #4984 fix: resolved ONCE and threaded into BOTH the heading scan and
|
|
650
|
+
// the disk-side matchPhaseDirs check below — the heading scan alone
|
|
651
|
+
// recognizing `[GSD.02] 01:` and extracting the bare phase-number token
|
|
652
|
+
// "01" is only half the fix. Without also passing this convention to
|
|
653
|
+
// matchPhaseDirs, its bracket-qualified match arm never fires, "01"
|
|
654
|
+
// matches no bracket directory name ("GSD.02-01-setup"), and every
|
|
655
|
+
// properly-scaffolded bracket phase reads as disk_status: 'no_directory'
|
|
656
|
+
// — turning a real bracket project's own phases into false "ROADMAP
|
|
657
|
+
// lists N unstarted phase(s)" failures on `milestone complete`.
|
|
658
|
+
const convention = resolvePhaseIdConvention(cwd);
|
|
659
|
+
const { regex: phasePattern, phaseNumGroup } = buildPhaseHeadingScanRegex(PHASE_HEADING_BASELINE.ANY_BRACKET, convention);
|
|
647
660
|
const noDirectoryPhases = [];
|
|
648
661
|
let pm;
|
|
649
662
|
const phaseDirEntries = (() => {
|
|
@@ -658,7 +671,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
658
671
|
}
|
|
659
672
|
})();
|
|
660
673
|
while ((pm = phasePattern.exec(scopedContent)) !== null) {
|
|
661
|
-
const phaseNum = pm[
|
|
674
|
+
const phaseNum = pm[phaseNumGroup];
|
|
662
675
|
// Phase 0 (pre-milestone) and Phase 999 (backlog) are sentinels, not
|
|
663
676
|
// real phases — they legitimately have no directory and must not block
|
|
664
677
|
// milestone completion. Mirrors the engine-wide sentinel convention
|
|
@@ -672,7 +685,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
672
685
|
// with a matching token exists on disk. Use the same matchPhaseDirs
|
|
673
686
|
// owner that roadmap.analyze uses to avoid false positives on decimal
|
|
674
687
|
// (2.1) and letter-suffix (12A) phase IDs. (#2528)
|
|
675
|
-
const hasDirectory = matchPhaseDirs(phaseDirEntries, normalized).matches.length > 0;
|
|
688
|
+
const hasDirectory = matchPhaseDirs(phaseDirEntries, normalized, convention).matches.length > 0;
|
|
676
689
|
if (!hasDirectory) {
|
|
677
690
|
noDirectoryPhases.push(phaseNum);
|
|
678
691
|
}
|