@opengsd/gsd-core 1.9.0 → 1.10.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.
Files changed (223) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -3
  3. package/.opencode/plugins/gsd-core.js +8 -1
  4. package/agents/gsd-code-fixer.md +131 -34
  5. package/agents/gsd-debugger.md +12 -246
  6. package/agents/gsd-executor.md +7 -5
  7. package/agents/gsd-integration-checker.md +3 -0
  8. package/agents/gsd-plan-checker.md +9 -0
  9. package/agents/gsd-planner.md +5 -8
  10. package/agents/gsd-roadmapper.md +21 -3
  11. package/agents/gsd-verifier.md +14 -70
  12. package/bin/install.js +503 -341
  13. package/commands/gsd/mempalace-capture.md +1 -1
  14. package/commands/gsd/new-milestone.md +1 -1
  15. package/commands/gsd/plan-phase.md +1 -1
  16. package/gsd-core/bin/gsd-tools.cjs +607 -63
  17. package/gsd-core/bin/lib/active-workstream-store.cjs +25 -0
  18. package/gsd-core/bin/lib/agent-install-check.cjs +38 -6
  19. package/gsd-core/bin/lib/api-coverage.cjs +120 -0
  20. package/gsd-core/bin/lib/audit.cjs +89 -1
  21. package/gsd-core/bin/lib/broken-windows.cjs +36 -6
  22. package/gsd-core/bin/lib/capability-registry.cjs +96 -110
  23. package/gsd-core/bin/lib/capability-validator.cjs +12 -2
  24. package/gsd-core/bin/lib/check-command-router.cjs +43 -1
  25. package/gsd-core/bin/lib/command-aliases.cjs +72 -0
  26. package/gsd-core/bin/lib/commands.cjs +26 -25
  27. package/gsd-core/bin/lib/commonjs-marker.cjs +136 -0
  28. package/gsd-core/bin/lib/config-loader.cjs +1 -0
  29. package/gsd-core/bin/lib/config.cjs +12 -1
  30. package/gsd-core/bin/lib/context-composer.cjs +278 -0
  31. package/gsd-core/bin/lib/context-predicates.cjs +506 -0
  32. package/gsd-core/bin/lib/core-utils.cjs +91 -12
  33. package/gsd-core/bin/lib/docs.cjs +3 -2
  34. package/gsd-core/bin/lib/external-job.cjs +19 -4
  35. package/gsd-core/bin/lib/frontmatter.cjs +84 -12
  36. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
  37. package/gsd-core/bin/lib/git-base-branch.cjs +58 -15
  38. package/gsd-core/bin/lib/graphify.cjs +142 -27
  39. package/gsd-core/bin/lib/gsd2-import.cjs +27 -4
  40. package/gsd-core/bin/lib/host-integration.cjs +13 -1
  41. package/gsd-core/bin/lib/init-command-router.cjs +83 -8
  42. package/gsd-core/bin/lib/init.cjs +1021 -57
  43. package/gsd-core/bin/lib/install-engine.cjs +64 -10
  44. package/gsd-core/bin/lib/install-profiles.cjs +27 -1
  45. package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
  46. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  47. package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
  48. package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
  49. package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
  50. package/gsd-core/bin/lib/installer-migrations.cjs +87 -1
  51. package/gsd-core/bin/lib/io.cjs +28 -3
  52. package/gsd-core/bin/lib/markdown-sectionizer.cjs +6 -0
  53. package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
  54. package/gsd-core/bin/lib/mcp-server.cjs +135 -3
  55. package/gsd-core/bin/lib/milestone.cjs +106 -51
  56. package/gsd-core/bin/lib/phase-id.cjs +63 -0
  57. package/gsd-core/bin/lib/phase-locator.cjs +138 -45
  58. package/gsd-core/bin/lib/phase.cjs +260 -25
  59. package/gsd-core/bin/lib/plan-dependency-graph.cjs +232 -0
  60. package/gsd-core/bin/lib/planning-workspace.cjs +4 -0
  61. package/gsd-core/bin/lib/project-root.cjs +48 -0
  62. package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
  63. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +80 -0
  64. package/gsd-core/bin/lib/review-lane-descriptor.cjs +99 -0
  65. package/gsd-core/bin/lib/review-lane-runner.cjs +30 -6
  66. package/gsd-core/bin/lib/roadmap-command-router.cjs +42 -9
  67. package/gsd-core/bin/lib/roadmap-parser.cjs +100 -18
  68. package/gsd-core/bin/lib/roadmap.cjs +37 -7
  69. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +195 -62
  70. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +15 -3
  71. package/gsd-core/bin/lib/runtime-homes.cjs +154 -41
  72. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +105 -41
  73. package/gsd-core/bin/lib/section-manifest.cjs +209 -0
  74. package/gsd-core/bin/lib/shell-command-projection.cjs +113 -27
  75. package/gsd-core/bin/lib/smart-entry.cjs +12 -0
  76. package/gsd-core/bin/lib/state-transition.cjs +73 -8
  77. package/gsd-core/bin/lib/state.cjs +151 -62
  78. package/gsd-core/bin/lib/surface.cjs +12 -1
  79. package/gsd-core/bin/lib/uat-predicate.cjs +11 -1
  80. package/gsd-core/bin/lib/uat.cjs +320 -21
  81. package/gsd-core/bin/lib/unusable-input.cjs +9 -0
  82. package/gsd-core/bin/lib/verification.cjs +29 -12
  83. package/gsd-core/bin/lib/verify.cjs +29 -5
  84. package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
  85. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +181 -18
  86. package/gsd-core/bin/lib/workstream-inventory.cjs +519 -27
  87. package/gsd-core/bin/lib/workstream.cjs +6 -0
  88. package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
  89. package/gsd-core/bin/lib/worktree-safety.cjs +276 -118
  90. package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
  91. package/gsd-core/references/artifact-types.md +10 -3
  92. package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
  93. package/gsd-core/references/debugger-techniques.md +255 -0
  94. package/gsd-core/references/research-documentation-lookup.md +5 -3
  95. package/gsd-core/references/specless-probe-fallback.md +7 -6
  96. package/gsd-core/references/verifier-wiring-patterns.md +100 -0
  97. package/gsd-core/references/worktree-branch-check.md +2 -2
  98. package/gsd-core/templates/summary-complex.md +2 -0
  99. package/gsd-core/templates/summary-minimal.md +2 -0
  100. package/gsd-core/templates/summary-standard.md +2 -0
  101. package/gsd-core/templates/summary.md +2 -0
  102. package/gsd-core/workflows/audit-milestone.md +3 -0
  103. package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
  104. package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
  105. package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
  106. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
  107. package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
  108. package/gsd-core/workflows/autonomous.md +32 -69
  109. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
  110. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +83 -0
  111. package/gsd-core/workflows/code-review.md +42 -145
  112. package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
  113. package/gsd-core/workflows/complete-milestone.md +23 -81
  114. package/gsd-core/workflows/debug.md +9 -12
  115. package/gsd-core/workflows/diagnose-issues.md +22 -0
  116. package/gsd-core/workflows/discovery-phase.md +4 -4
  117. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
  118. package/gsd-core/workflows/discuss-phase-assumptions.md +5 -16
  119. package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
  120. package/gsd-core/workflows/docs-update.md +8 -51
  121. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +34 -2
  122. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
  123. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
  124. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +19 -0
  125. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
  126. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
  127. package/gsd-core/workflows/execute-phase.md +65 -137
  128. package/gsd-core/workflows/execute-plan.md +1 -1
  129. package/gsd-core/workflows/help/modes/full.md +6 -1
  130. package/gsd-core/workflows/ingest-docs.md +2 -1
  131. package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
  132. package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
  133. package/gsd-core/workflows/new-milestone.md +21 -38
  134. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
  135. package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
  136. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
  137. package/gsd-core/workflows/new-project.md +13 -226
  138. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
  139. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
  140. package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
  141. package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
  142. package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
  143. package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
  144. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
  145. package/gsd-core/workflows/plan-phase.md +49 -193
  146. package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
  147. package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
  148. package/gsd-core/workflows/progress.md +11 -153
  149. package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
  150. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
  151. package/gsd-core/workflows/quick/steps/quick-verification.md +46 -0
  152. package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
  153. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
  154. package/gsd-core/workflows/quick.md +20 -390
  155. package/gsd-core/workflows/resume-project.md +3 -0
  156. package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
  157. package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
  158. package/gsd-core/workflows/review.md +15 -8
  159. package/gsd-core/workflows/section-manifest.json +219 -0
  160. package/gsd-core/workflows/sketch.md +1 -1
  161. package/gsd-core/workflows/spec-phase.md +17 -14
  162. package/gsd-core/workflows/spike-wrap-up.md +20 -5
  163. package/gsd-core/workflows/spike.md +50 -16
  164. package/gsd-core/workflows/sync-skills.md +49 -11
  165. package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
  166. package/gsd-core/workflows/transition.md +8 -21
  167. package/gsd-core/workflows/ui-phase.md +8 -7
  168. package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
  169. package/gsd-core/workflows/update.md +18 -7
  170. package/gsd-core/workflows/verify-phase.md +4 -7
  171. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
  172. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
  173. package/gsd-core/workflows/verify-work.md +8 -58
  174. package/hooks/dist/gsd-agent-isolation-guard.js +428 -0
  175. package/hooks/dist/gsd-check-update-worker.js +14 -5
  176. package/hooks/dist/gsd-cursor-subagent-start.js +532 -26
  177. package/hooks/dist/gsd-read-injection-scanner.js +7 -0
  178. package/hooks/dist/gsd-statusline.js +72 -6
  179. package/hooks/dist/gsd-worktree-path-guard.js +2 -1
  180. package/hooks/dist/gsd-write-guard.js +359 -0
  181. package/hooks/dist/lib/isolation-sentinel.js +268 -0
  182. package/hooks/dist/managed-hooks-registry.cjs +2 -0
  183. package/hooks/gsd-agent-isolation-guard.js +428 -0
  184. package/hooks/gsd-check-update-worker.js +14 -5
  185. package/hooks/gsd-cursor-subagent-start.js +532 -26
  186. package/hooks/gsd-read-injection-scanner.js +7 -0
  187. package/hooks/gsd-statusline.js +72 -6
  188. package/hooks/gsd-worktree-path-guard.js +2 -1
  189. package/hooks/gsd-write-guard.js +359 -0
  190. package/hooks/hooks.json +12 -0
  191. package/hooks/lib/isolation-sentinel.js +268 -0
  192. package/hooks/managed-hooks-registry.cjs +2 -0
  193. package/package.json +14 -5
  194. package/pi/gsd.cjs +57 -12
  195. package/scripts/build-hooks.js +9 -0
  196. package/scripts/changeset/lint.cjs +9 -2
  197. package/scripts/changeset/serialize.cjs +5 -1
  198. package/scripts/gen-capability-matrix.cjs +1 -1
  199. package/scripts/gen-context-index.cjs +448 -0
  200. package/scripts/gen-inventory-manifest.cjs +101 -1
  201. package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
  202. package/scripts/gen-registry.cjs +39 -15
  203. package/scripts/gen-section-manifest.cjs +638 -0
  204. package/scripts/generate-package-identity.cjs +4 -2
  205. package/scripts/lint-allow-test-rule-refs.allowlist.json +17 -31
  206. package/scripts/lint-compiled-artifact-sync.cjs +6 -1
  207. package/scripts/lint-docs-command-form.cjs +195 -0
  208. package/scripts/lint-docs-required.cjs +9 -1
  209. package/scripts/lint-emitted-drift-ack.cjs +215 -20
  210. package/scripts/lint-example-parser-parity.cjs +395 -0
  211. package/scripts/lint-test-file-count.allowlist.json +27 -1
  212. package/scripts/mutation-matrix.cjs +13 -0
  213. package/scripts/prompt-injection-scan.sh +27 -6
  214. package/scripts/registry-schema.cjs +323 -94
  215. package/scripts/run-tests.cjs +3 -2
  216. package/scripts/validate-registry.cjs +10 -6
  217. package/skills/gsd-autonomous/SKILL.md +1 -1
  218. package/skills/gsd-execute-phase/SKILL.md +1 -1
  219. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  220. package/skills/gsd-new-milestone/SKILL.md +1 -1
  221. package/skills/gsd-plan-phase/SKILL.md +2 -2
  222. package/vscode/package.json +1 -1
  223. package/scripts/gen-emitted-baseline.cjs +0 -145
@@ -30,7 +30,67 @@ const { readSubdirectories, getPhaseFileStats, extractCanonicalPlanId, toPosixPa
30
30
  // eslint-disable-next-line @typescript-eslint/no-require-imports
31
31
  const planningWorkspace = require("./planning-workspace.cjs");
32
32
  const { planningDir } = planningWorkspace;
33
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
34
+ const frontmatterModule = require("./frontmatter.cjs");
35
+ const { extractFrontmatter } = frontmatterModule;
36
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
37
+ const planDependencyGraphModule = require("./plan-dependency-graph.cjs");
38
+ const { computeHaltPropagation, buildSummaryFileIndex, isSummaryFileHalted } = planDependencyGraphModule;
39
+ /**
40
+ * #2830: parse a plan file's `depends_on` frontmatter. Returns [] — never
41
+ * throws — on a missing/unreadable/malformed plan or absent field, matching
42
+ * this primitive's existing fail-safe posture (a plan directory this
43
+ * primitive can otherwise read must never throw here).
44
+ */
45
+ function parsePlanDependsOn(phaseDir, planFile) {
46
+ try {
47
+ const planPath = node_path_1.default.join(phaseDir, planFile);
48
+ const content = node_fs_1.default.readFileSync(planPath, 'utf-8');
49
+ const fm = extractFrontmatter(content, planPath);
50
+ const fmDeps = fm['depends_on'];
51
+ if (Array.isArray(fmDeps))
52
+ return fmDeps.map(String);
53
+ if (typeof fmDeps === 'string' && fmDeps.trim() !== '')
54
+ return [fmDeps];
55
+ return [];
56
+ }
57
+ catch {
58
+ return [];
59
+ }
60
+ }
33
61
  // ─── Phase search helpers ─────────────────────────────────────────────────────
62
+ /**
63
+ * #2855: single source of truth for resolving and enumerating a project's
64
+ * (or, when a workstream is active, that workstream's OWN) archived-milestone
65
+ * directories — `<planningDir(cwd)>/milestones/vX.Y-phases/`. Both
66
+ * `findPhaseInternal`'s archive fallback and `getArchivedPhaseDirs` used to
67
+ * carry independent copies of this resolve-then-enumerate logic, which is
68
+ * exactly the shape that let the original #2855 bug (hardcoded root path)
69
+ * exist in one copy and not the other. Sharing this seam means a future
70
+ * change to how the archive tree is located only needs to happen once.
71
+ * Most-recent-milestone-first order (reverse-sorted directory names).
72
+ * Never throws: an absent/unreadable milestones/ dir yields [].
73
+ */
74
+ function listArchiveVersionDirs(cwd) {
75
+ const milestonesDir = node_path_1.default.join(planningDir(cwd), 'milestones');
76
+ if (!node_fs_1.default.existsSync(milestonesDir))
77
+ return [];
78
+ try {
79
+ const milestoneEntries = node_fs_1.default.readdirSync(milestonesDir, { withFileTypes: true });
80
+ return milestoneEntries
81
+ .filter(e => e.isDirectory() && /^v[\d.]+-phases$/.test(e.name))
82
+ .map(e => e.name)
83
+ .sort()
84
+ .reverse()
85
+ .map(archiveName => ({
86
+ version: archiveName.match(/^(v[\d.]+)-phases$/)[1],
87
+ archivePath: node_path_1.default.join(milestonesDir, archiveName),
88
+ }));
89
+ }
90
+ catch {
91
+ return [];
92
+ }
93
+ }
34
94
  function searchPhaseInDir(baseDir, relBase, normalized) {
35
95
  try {
36
96
  const dirs = readSubdirectories(baseDir, true);
@@ -55,6 +115,9 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
55
115
  has_verification: false,
56
116
  has_reviews: false,
57
117
  ambiguous_matches: matches,
118
+ halted_plans: [],
119
+ blocked_by: {},
120
+ runnable_plans: [],
58
121
  };
59
122
  }
60
123
  const match = matches[0];
@@ -76,6 +139,50 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
76
139
  const canonical = extractCanonicalPlanId(p);
77
140
  return !completedPlanIds.has(planId) && !completedPlanIds.has(canonical);
78
141
  });
142
+ // #2830: reverse lookup from a completed plan's id (exact or canonical) to
143
+ // its actual summary filename. Shared builder (also used by phase.cts's
144
+ // cmdPhasePlanIndex) so the two can never disagree about which summary
145
+ // belongs to which plan.
146
+ const summaryFileByPlanId = buildSummaryFileIndex(summaries, extractCanonicalPlanId);
147
+ // #2830: this primitive previously never parsed depends_on at all — see
148
+ // src/plan-dependency-graph.cts's file header. Build the same
149
+ // PlanHaltNode[] shape phase.cts's cmdPhasePlanIndex builds (id resolution
150
+ // mirrors its planMap/canonicalToId pattern) and hand it to the ONE
151
+ // shared halt-propagation traversal so this reader and the wave-grouping
152
+ // reader can never diverge on the halt rule again.
153
+ const planIds = plans.map(p => p.replace('-PLAN.md', '').replace('PLAN.md', ''));
154
+ const planIdByLower = new Map(planIds.map(id => [id.toLowerCase(), id]));
155
+ const canonicalToPlanId = new Map(plans.map((p, i) => [extractCanonicalPlanId(p).toLowerCase(), planIds[i]]));
156
+ const haltNodes = plans.map((p, i) => {
157
+ const planId = planIds[i];
158
+ const canonical = extractCanonicalPlanId(p);
159
+ const summaryFile = summaryFileByPlanId.get(planId) ?? summaryFileByPlanId.get(canonical);
160
+ const halted = summaryFile !== undefined && isSummaryFileHalted(node_path_1.default.join(phaseDir, summaryFile));
161
+ const resolvedDependsOn = parsePlanDependsOn(phaseDir, p)
162
+ .map((dep) => {
163
+ const lower = dep.toLowerCase();
164
+ return planIdByLower.get(lower) ?? canonicalToPlanId.get(lower) ?? null;
165
+ })
166
+ .filter((id) => id !== null);
167
+ return { id: planId, resolvedDependsOn, halted };
168
+ });
169
+ const { blockedBy } = computeHaltPropagation(haltNodes);
170
+ const haltedPlans = plans.filter((_, i) => haltNodes[i].halted);
171
+ const incompletePlanSet = new Set(incompletePlans);
172
+ const blockedByFiles = {};
173
+ const runnablePlans = [];
174
+ for (let i = 0; i < plans.length; i++) {
175
+ const p = plans[i];
176
+ if (!incompletePlanSet.has(p))
177
+ continue;
178
+ const causes = blockedBy.get(planIds[i]) ?? [];
179
+ if (causes.length > 0) {
180
+ blockedByFiles[p] = causes;
181
+ }
182
+ else {
183
+ runnablePlans.push(p);
184
+ }
185
+ }
79
186
  return {
80
187
  found: true,
81
188
  directory: toPosixPath(node_path_1.default.join(relBase, match)),
@@ -89,6 +196,9 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
89
196
  has_context: hasContext,
90
197
  has_verification: hasVerification,
91
198
  has_reviews: hasReviews,
199
+ halted_plans: haltedPlans,
200
+ blocked_by: blockedByFiles,
201
+ runnable_plans: runnablePlans,
92
202
  };
93
203
  }
94
204
  catch {
@@ -104,59 +214,42 @@ function findPhaseInternal(cwd, phase) {
104
214
  const current = searchPhaseInDir(phasesDir, relPhasesDir, normalized);
105
215
  if (current)
106
216
  return current;
107
- const milestonesDir = node_path_1.default.join(cwd, '.planning', 'milestones');
108
- if (!node_fs_1.default.existsSync(milestonesDir))
109
- return null;
110
- try {
111
- const milestoneEntries = node_fs_1.default.readdirSync(milestonesDir, { withFileTypes: true });
112
- const archiveDirs = milestoneEntries
113
- .filter(e => e.isDirectory() && /^v[\d.]+-phases$/.test(e.name))
114
- .map(e => e.name)
115
- .sort()
116
- .reverse();
117
- for (const archiveName of archiveDirs) {
118
- const versionMatch = archiveName.match(/^(v[\d.]+)-phases$/);
119
- const version = versionMatch[1];
120
- const archivePath = node_path_1.default.join(milestonesDir, archiveName);
121
- const relBase = '.planning/milestones/' + archiveName;
122
- const result = searchPhaseInDir(archivePath, relBase, normalized);
123
- if (result) {
124
- result.archived = version;
125
- return result;
126
- }
217
+ // #2855: scope the archived-milestone fallback to the SAME workstream as the
218
+ // active-phase search above (planningDir(cwd) resolves GSD_WORKSTREAM/GSD_PROJECT
219
+ // the identical way both places), not the hardcoded project-root tree. Archived
220
+ // phases genuinely live under a workstream's own `.planning/workstreams/<ws>/
221
+ // milestones/` — that is where archivePhaseDirectories (milestone.cts) writes
222
+ // them via the same planningDir(cwd) resolution. Hardcoding root here let a
223
+ // pending workstream phase resolve to an unrelated workstream's (or a flat-mode
224
+ // project's) archived phase that merely shares a phase number. Shared with
225
+ // getArchivedPhaseDirs via listArchiveVersionDirs (see its doc comment).
226
+ for (const { version, archivePath } of listArchiveVersionDirs(cwd)) {
227
+ const relBase = toPosixPath(node_path_1.default.relative(cwd, archivePath));
228
+ const result = searchPhaseInDir(archivePath, relBase, normalized);
229
+ if (result) {
230
+ result.archived = version;
231
+ return result;
127
232
  }
128
233
  }
129
- catch { /* intentionally empty */ }
130
234
  return null;
131
235
  }
132
236
  function getArchivedPhaseDirs(cwd) {
133
- const milestonesDir = node_path_1.default.join(cwd, '.planning', 'milestones');
237
+ // #2855: same workstream-scoped resolution as findPhaseInternal above, via
238
+ // the shared listArchiveVersionDirs helper. `phase.list --include-archived`
239
+ // (the primary non-init consumer) must not leak a different workstream's
240
+ // archive either.
134
241
  const results = [];
135
- if (!node_fs_1.default.existsSync(milestonesDir))
136
- return results;
137
- try {
138
- const milestoneEntries = node_fs_1.default.readdirSync(milestonesDir, { withFileTypes: true });
139
- const phaseDirs = milestoneEntries
140
- .filter(e => e.isDirectory() && /^v[\d.]+-phases$/.test(e.name))
141
- .map(e => e.name)
142
- .sort()
143
- .reverse();
144
- for (const archiveName of phaseDirs) {
145
- const versionMatch = archiveName.match(/^(v[\d.]+)-phases$/);
146
- const version = versionMatch[1];
147
- const archivePath = node_path_1.default.join(milestonesDir, archiveName);
148
- const dirs = readSubdirectories(archivePath, true);
149
- for (const dir of dirs) {
150
- results.push({
151
- name: dir,
152
- milestone: version,
153
- basePath: node_path_1.default.join('.planning', 'milestones', archiveName),
154
- fullPath: node_path_1.default.join(archivePath, dir),
155
- });
156
- }
242
+ for (const { version, archivePath } of listArchiveVersionDirs(cwd)) {
243
+ const dirs = readSubdirectories(archivePath, true);
244
+ for (const dir of dirs) {
245
+ results.push({
246
+ name: dir,
247
+ milestone: version,
248
+ basePath: toPosixPath(node_path_1.default.relative(cwd, archivePath)),
249
+ fullPath: node_path_1.default.join(archivePath, dir),
250
+ });
157
251
  }
158
252
  }
159
- catch { /* intentionally empty */ }
160
253
  return results;
161
254
  }
162
255
  module.exports = {