@opengsd/gsd-core 1.7.0-rc.6 → 1.8.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 (195) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +14 -0
  4. package/README.md +2 -0
  5. package/agents/gsd-debug-session-manager.md +42 -4
  6. package/agents/gsd-debugger.md +87 -29
  7. package/agents/gsd-executor.md +31 -3
  8. package/agents/gsd-planner.md +29 -36
  9. package/agents/gsd-security-auditor.md +13 -15
  10. package/agents/gsd-verifier.md +2 -2
  11. package/bin/install.js +1157 -84
  12. package/commands/gsd/ai-integration-phase.md +1 -1
  13. package/commands/gsd/mempalace-capture.md +31 -1
  14. package/commands/gsd/new-milestone.md +1 -1
  15. package/commands/gsd/plan-phase.md +5 -3
  16. package/commands/gsd/plan-review-convergence.md +3 -2
  17. package/commands/gsd/surface.md +6 -6
  18. package/gsd-core/bin/gsd-tools.cjs +1866 -2434
  19. package/gsd-core/bin/lib/adapter-imperative.cjs +8 -1
  20. package/gsd-core/bin/lib/agent-command-router.cjs +20 -5
  21. package/gsd-core/bin/lib/api-coverage.cjs +341 -49
  22. package/gsd-core/bin/lib/audit.cjs +7 -6
  23. package/gsd-core/bin/lib/broken-windows.cjs +716 -0
  24. package/gsd-core/bin/lib/capability-command-router.cjs +733 -0
  25. package/gsd-core/bin/lib/capability-registry.cjs +157 -88
  26. package/gsd-core/bin/lib/capability-writer.cjs +6 -1
  27. package/gsd-core/bin/lib/check-command-router.cjs +129 -26
  28. package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +115 -27
  29. package/gsd-core/bin/lib/claude-orchestration.cjs +84 -9
  30. package/gsd-core/bin/lib/clock.cjs +19 -0
  31. package/gsd-core/bin/lib/command-aliases.cjs +14 -0
  32. package/gsd-core/bin/lib/commands.cjs +129 -13
  33. package/gsd-core/bin/lib/config-loader.cjs +20 -4
  34. package/gsd-core/bin/lib/config.cjs +81 -18
  35. package/gsd-core/bin/lib/core-utils.cjs +14 -3
  36. package/gsd-core/bin/lib/decisions.cjs +32 -8
  37. package/gsd-core/bin/lib/docs.cjs +6 -0
  38. package/gsd-core/bin/lib/drift.cjs +4 -4
  39. package/gsd-core/bin/lib/external-descriptor-trust.cjs +14 -2
  40. package/gsd-core/bin/lib/frontmatter.cjs +22 -0
  41. package/gsd-core/bin/lib/gap-checker.cjs +17 -2
  42. package/gsd-core/bin/lib/gsd2-import.cjs +2 -1
  43. package/gsd-core/bin/lib/init.cjs +138 -60
  44. package/gsd-core/bin/lib/install-engine.cjs +301 -25
  45. package/gsd-core/bin/lib/install-profiles.cjs +239 -1
  46. package/gsd-core/bin/lib/installer-migration-authoring.cjs +2 -1
  47. package/gsd-core/bin/lib/installer-migrations/005-opencode-baseline-commands-dir.cjs +146 -0
  48. package/gsd-core/bin/lib/installer-migrations/006-pi-extension-cjs-to-js.cjs +91 -0
  49. package/gsd-core/bin/lib/installer-migrations.cjs +45 -6
  50. package/gsd-core/bin/lib/markdown-sectionizer.cjs +449 -0
  51. package/gsd-core/bin/lib/markdown-table.cjs +698 -0
  52. package/gsd-core/bin/lib/milestone.cjs +463 -43
  53. package/gsd-core/bin/lib/model-catalog.cjs +19 -4
  54. package/gsd-core/bin/lib/model-resolver.cjs +189 -7
  55. package/gsd-core/bin/lib/onboard-projection.cjs +11 -8
  56. package/gsd-core/bin/lib/phase-command-router.cjs +50 -2
  57. package/gsd-core/bin/lib/phase-id.cjs +26 -4
  58. package/gsd-core/bin/lib/phase-lifecycle.cjs +62 -36
  59. package/gsd-core/bin/lib/phase-locator.cjs +23 -2
  60. package/gsd-core/bin/lib/phase.cjs +636 -72
  61. package/gsd-core/bin/lib/plan-scan.cjs +73 -2
  62. package/gsd-core/bin/lib/roadmap-parser.cjs +225 -17
  63. package/gsd-core/bin/lib/roadmap.cjs +113 -52
  64. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +14 -7
  65. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +3 -2
  66. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +24 -9
  67. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +41 -17
  68. package/gsd-core/bin/lib/schema-detect.cjs +2 -1
  69. package/gsd-core/bin/lib/security.cjs +1 -1
  70. package/gsd-core/bin/lib/shell-command-projection.cjs +61 -25
  71. package/gsd-core/bin/lib/smart-entry.cjs +73 -7
  72. package/gsd-core/bin/lib/state-document.cjs +7 -4
  73. package/gsd-core/bin/lib/state-transition.cjs +122 -46
  74. package/gsd-core/bin/lib/state.cjs +456 -137
  75. package/gsd-core/bin/lib/surface.cjs +53 -11
  76. package/gsd-core/bin/lib/template.cjs +2 -1
  77. package/gsd-core/bin/lib/uat.cjs +474 -13
  78. package/gsd-core/bin/lib/ui-safety-gate.cjs +23 -1
  79. package/gsd-core/bin/lib/validate.cjs +12 -8
  80. package/gsd-core/bin/lib/verification.cjs +112 -17
  81. package/gsd-core/bin/lib/verify.cjs +224 -25
  82. package/gsd-core/bin/lib/workstream.cjs +3 -2
  83. package/gsd-core/bin/lib/worktree-safety.cjs +1 -1
  84. package/gsd-core/bin/lib/write-set.cjs +38 -0
  85. package/gsd-core/bin/shared/config-schema.manifest.json +5 -2
  86. package/gsd-core/references/api-coverage.md +37 -7
  87. package/gsd-core/references/checkpoints.md +13 -1
  88. package/gsd-core/references/common-bug-patterns.md +13 -0
  89. package/gsd-core/references/debugger-bug-taxonomy.md +111 -0
  90. package/gsd-core/references/debugger-fix-acceptance.md +157 -0
  91. package/gsd-core/references/debugger-philosophy.md +1 -0
  92. package/gsd-core/references/debugger-prevention.md +98 -0
  93. package/gsd-core/references/debugger-rca-branching.md +98 -0
  94. package/gsd-core/references/debugger-repro-hardening.md +130 -0
  95. package/gsd-core/references/debugger-sbfl.md +110 -0
  96. package/gsd-core/references/debugger-semantic-recall.md +81 -0
  97. package/gsd-core/references/execute-phase-quota-recovery.md +55 -0
  98. package/gsd-core/references/execute-phase-requirement-revert.md +8 -0
  99. package/gsd-core/references/execute-phase-response-language.md +7 -0
  100. package/gsd-core/references/planner-antipatterns.md +6 -0
  101. package/gsd-core/references/planner-mvp-mode.md +12 -13
  102. package/gsd-core/references/planner-preconditions.md +156 -0
  103. package/gsd-core/references/planner-reversibility.md +132 -0
  104. package/gsd-core/references/reviewer-instances.md +9 -7
  105. package/gsd-core/references/skeleton-template.md +1 -1
  106. package/gsd-core/references/thinking-models-planning.md +3 -1
  107. package/gsd-core/templates/DEBUG.md +5 -3
  108. package/gsd-core/workflows/add-phase.md +2 -0
  109. package/gsd-core/workflows/add-tests.md +4 -2
  110. package/gsd-core/workflows/add-todo.md +32 -1
  111. package/gsd-core/workflows/ai-integration-phase.md +4 -2
  112. package/gsd-core/workflows/audit-fix.md +2 -2
  113. package/gsd-core/workflows/check-todos.md +3 -1
  114. package/gsd-core/workflows/cleanup.md +7 -1
  115. package/gsd-core/workflows/code-review.md +17 -5
  116. package/gsd-core/workflows/complete-milestone.md +3 -0
  117. package/gsd-core/workflows/debug.md +27 -5
  118. package/gsd-core/workflows/diagnose-issues.md +1 -1
  119. package/gsd-core/workflows/discovery-phase.md +7 -0
  120. package/gsd-core/workflows/discuss-phase/templates/context.md +16 -2
  121. package/gsd-core/workflows/discuss-phase-assumptions.md +3 -0
  122. package/gsd-core/workflows/do.md +7 -1
  123. package/gsd-core/workflows/docs-update.md +1 -0
  124. package/gsd-core/workflows/eval-review.md +3 -0
  125. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +4 -4
  126. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -2
  127. package/gsd-core/workflows/execute-phase.md +30 -37
  128. package/gsd-core/workflows/execute-plan.md +15 -4
  129. package/gsd-core/workflows/fast.md +8 -22
  130. package/gsd-core/workflows/graduation.md +3 -0
  131. package/gsd-core/workflows/health.md +7 -1
  132. package/gsd-core/workflows/help/modes/full.md +6 -2
  133. package/gsd-core/workflows/import.md +8 -2
  134. package/gsd-core/workflows/inbox.md +7 -0
  135. package/gsd-core/workflows/ingest-docs.md +15 -10
  136. package/gsd-core/workflows/manager.md +3 -1
  137. package/gsd-core/workflows/map-codebase.md +4 -4
  138. package/gsd-core/workflows/mvp-phase.md +3 -0
  139. package/gsd-core/workflows/new-milestone.md +69 -21
  140. package/gsd-core/workflows/new-project.md +17 -15
  141. package/gsd-core/workflows/new-workspace.md +3 -1
  142. package/gsd-core/workflows/onboard.md +3 -0
  143. package/gsd-core/workflows/plan-phase.md +14 -5
  144. package/gsd-core/workflows/plan-review-convergence.md +48 -3
  145. package/gsd-core/workflows/plant-seed.md +3 -0
  146. package/gsd-core/workflows/profile-user.md +7 -1
  147. package/gsd-core/workflows/progress.md +33 -5
  148. package/gsd-core/workflows/quick.md +21 -7
  149. package/gsd-core/workflows/remove-workspace.md +3 -0
  150. package/gsd-core/workflows/review.md +123 -68
  151. package/gsd-core/workflows/scan.md +1 -1
  152. package/gsd-core/workflows/secure-phase.md +4 -1
  153. package/gsd-core/workflows/settings-integrations.md +3 -0
  154. package/gsd-core/workflows/settings.md +3 -0
  155. package/gsd-core/workflows/ship.md +58 -5
  156. package/gsd-core/workflows/sketch.md +3 -0
  157. package/gsd-core/workflows/smart-entry.md +3 -0
  158. package/gsd-core/workflows/spec-phase.md +1 -1
  159. package/gsd-core/workflows/spike.md +7 -1
  160. package/gsd-core/workflows/transition.md +1 -1
  161. package/gsd-core/workflows/ui-phase.md +3 -1
  162. package/gsd-core/workflows/ui-review.md +3 -0
  163. package/gsd-core/workflows/undo.md +7 -0
  164. package/gsd-core/workflows/update.md +2 -0
  165. package/gsd-core/workflows/validate-phase.md +3 -0
  166. package/gsd-core/workflows/verify-phase.md +2 -2
  167. package/gsd-core/workflows/verify-work.md +7 -3
  168. package/hooks/dist/gsd-context-monitor.js +27 -9
  169. package/hooks/dist/gsd-statusline.js +252 -17
  170. package/hooks/gsd-context-monitor.js +27 -9
  171. package/hooks/gsd-statusline.js +252 -17
  172. package/package.json +8 -4
  173. package/pi/gsd.cjs +8 -2
  174. package/scripts/changeset/lint.cjs +1 -0
  175. package/scripts/changeset/parse.cjs +26 -0
  176. package/scripts/check-glossary-refs.cjs +220 -0
  177. package/scripts/ci-rebase-check.cjs +48 -4
  178. package/scripts/ci-test-scope.cjs +39 -1
  179. package/scripts/gen-adr-index.cjs +526 -0
  180. package/scripts/gen-golden-install-parity-zcode.cjs +35 -45
  181. package/scripts/gen-install-tree-fixtures.cjs +75 -0
  182. package/scripts/gen-test-timings.cjs +201 -0
  183. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -1
  184. package/scripts/lint-portable-timeout.cjs +140 -0
  185. package/scripts/lint-table-schema-drift.cjs +157 -0
  186. package/scripts/lint-test-file-count.allowlist.json +1 -0
  187. package/scripts/release-tarball-smoke.cjs +18 -11
  188. package/scripts/run-tests.cjs +420 -58
  189. package/skills/gsd-ai-integration-phase/SKILL.md +1 -1
  190. package/skills/gsd-mempalace-capture/SKILL.md +31 -1
  191. package/skills/gsd-new-milestone/SKILL.md +1 -1
  192. package/skills/gsd-plan-phase/SKILL.md +5 -3
  193. package/skills/gsd-plan-review-convergence/SKILL.md +3 -2
  194. package/skills/gsd-surface/SKILL.md +6 -6
  195. package/vscode/package.json +1 -1
@@ -522,6 +522,27 @@ const FRONTMATTER_SCHEMAS = {
522
522
  summary: { required: ['phase', 'plan', 'subsystem', 'tags', 'duration', 'completed'] },
523
523
  verification: { required: ['phase', 'verified', 'status', 'score'] },
524
524
  };
525
+ /**
526
+ * Strip ALL frontmatter blocks from the start of `content`.
527
+ *
528
+ * Handles CRLF line endings and multiple stacked blocks (corruption
529
+ * recovery): greedily strips consecutive `---...---` blocks separated by
530
+ * optional whitespace, so a doubled/tripled frontmatter header (e.g. from a
531
+ * botched merge) is fully removed, not just the first block.
532
+ *
533
+ * Canonical home for this primitive (#2143 audit dedup): previously
534
+ * duplicated byte-identically in both `state.cts` and `state-transition.cts`.
535
+ */
536
+ function stripFrontmatter(content) {
537
+ let result = content;
538
+ while (true) {
539
+ const stripped = result.replace(/^\s*---\r?\n[\s\S]*?\r?\n---\s*/, '');
540
+ if (stripped === result)
541
+ break;
542
+ result = stripped;
543
+ }
544
+ return result;
545
+ }
525
546
  function cmdFrontmatterGet(cwd, filePath, field, raw) {
526
547
  if (!filePath) {
527
548
  error('file path required');
@@ -654,6 +675,7 @@ module.exports = {
654
675
  parseFrontmatter: extractFrontmatter,
655
676
  reconstructFrontmatter,
656
677
  spliceFrontmatter,
678
+ stripFrontmatter,
657
679
  noOpObjectListSetError,
658
680
  parseMustHavesBlock,
659
681
  FRONTMATTER_SCHEMAS,
@@ -298,7 +298,16 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
298
298
  const mismatchMsg = '## Post-Planning Gap Analysis\n\nextracted 0 of N — possible format mismatch in CONTEXT.md decisions block.\n';
299
299
  // If there are also requirement items, include them in the return with the
300
300
  // mismatch summary appended, so the caller still sees requirement coverage.
301
- if (items.length > 0) {
301
+ // #2334 HIGH 1: gate on `ghostReqIds.length > 0` too — identical defect to
302
+ // the one fixed at #2316-6b (~34 lines below, at the `items.length === 0`
303
+ // early return): a phase whose EVERY cited REQ-ID is unregistered has
304
+ // `items.length === 0` (all its requirement items were filtered out at
305
+ // ~line 297) but still has real ghost rows to report. Without this guard,
306
+ // a single malformed `<decisions>` line in CONTEXT.md made an all-ghost
307
+ // phase's ghost rows silently vanish (this could-not-parse branch fell
308
+ // through to the bare `mismatchMsg`-only return below, dropping ghost
309
+ // rows that the general path further down correctly surfaces).
310
+ if (items.length > 0 || ghostReqIds.length > 0) {
302
311
  const rows = sortRows([
303
312
  ...detectCoverage(items, planText),
304
313
  ...ghostReqIds.map(id => ({ source: 'REQUIREMENTS.md', item: id, status: 'Missing from REQUIREMENTS.md' })),
@@ -325,7 +334,13 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
325
334
  };
326
335
  }
327
336
  // #1365: if no items at all, surface a clean no-check message.
328
- if (items.length === 0) {
337
+ // #2316-6b: this must NOT fire when `ghostReqIds` is non-empty — a phase
338
+ // whose EVERY cited REQ-ID is unregistered has `items.length === 0` (all
339
+ // its requirement items were filtered out at ~line 297) but still has real
340
+ // ghost rows to report below. Without this guard, an all-orphan phase
341
+ // reported LESS than a partially-orphan one (which falls through to the
342
+ // general path further down and correctly surfaces its ghost rows).
343
+ if (items.length === 0 && ghostReqIds.length === 0) {
329
344
  return {
330
345
  enabled: true,
331
346
  rows: [],
@@ -26,6 +26,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
26
26
  const node_path_1 = __importDefault(require("node:path"));
27
27
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
28
28
  const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
29
+ const clock_cjs_1 = require("./clock.cjs");
29
30
  // eslint-disable-next-line @typescript-eslint/no-require-imports
30
31
  const ioMod = require("./io.cjs");
31
32
  const { output } = ioMod;
@@ -276,7 +277,7 @@ function buildStateMd(phaseMap) {
276
277
  const status = currentEntry ? 'Ready to plan' : 'All phases complete';
277
278
  const filled = Math.round(pct / 10);
278
279
  const bar = `[${'█'.repeat(filled)}${'░'.repeat(10 - filled)}]`;
279
- const today = new Date().toISOString().split('T')[0];
280
+ const today = clock_cjs_1.realClock.localToday();
280
281
  return [
281
282
  '# Project State',
282
283
  '',
@@ -13,6 +13,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
13
13
  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
+ const clock_cjs_1 = require("./clock.cjs");
16
17
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- io.cjs is an export= CommonJS module
17
18
  const io = require("./io.cjs");
18
19
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- config-loader.cjs is an export= CommonJS module
@@ -203,7 +204,7 @@ function getInitGitState(cwd) {
203
204
  }
204
205
  resolved = node_path_1.default.resolve(resolved);
205
206
  if (process.platform === 'win32') {
206
- return resolved.replace(/\//g, '\\').toLowerCase();
207
+ return (0, shell_command_projection_cjs_1.toNativePath)(resolved).toLowerCase();
207
208
  }
208
209
  return resolved;
209
210
  };
@@ -213,7 +214,7 @@ function getInitGitState(cwd) {
213
214
  try {
214
215
  const prefixResult = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '--show-prefix'], { cwd, timeout: 5000 });
215
216
  if (prefixResult['exitCode'] === 0) {
216
- const prefix = (typeof prefixResult['stdout'] === 'string' ? prefixResult['stdout'] : '').trim().replace(/\\/g, '/');
217
+ const prefix = (0, shell_command_projection_cjs_1.posixNormalize)((typeof prefixResult['stdout'] === 'string' ? prefixResult['stdout'] : '').trim());
217
218
  inNestedSubdir = prefix.length > 0 && prefix !== '.' && prefix !== './';
218
219
  resolvedByGitPrefix = true;
219
220
  }
@@ -230,7 +231,7 @@ function getInitGitState(cwd) {
230
231
  }
231
232
  else {
232
233
  const rel = node_path_1.default.relative(rootNorm, cwdNorm);
233
- const relNorm = process.platform === 'win32' ? rel.replace(/\//g, '\\') : rel;
234
+ const relNorm = (0, shell_command_projection_cjs_1.toNativePath)(rel);
234
235
  inNestedSubdir =
235
236
  relNorm !== '' &&
236
237
  relNorm !== '.' &&
@@ -244,7 +245,7 @@ function getInitGitState(cwd) {
244
245
  }
245
246
  }
246
247
  if (inNestedSubdir && typeof worktreeRoot === 'string') {
247
- const toComparableRaw = (p) => p.replace(/\\/g, '/').replace(/\/+$/g, '').toLowerCase();
248
+ const toComparableRaw = (p) => (0, shell_command_projection_cjs_1.posixNormalize)(p).replace(/\/+$/g, '').toLowerCase();
248
249
  if (toComparableRaw(worktreeRoot) === toComparableRaw(String(cwd))) {
249
250
  inNestedSubdir = false;
250
251
  }
@@ -304,7 +305,11 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
304
305
  milestone_branch_template: config.milestone_branch_template,
305
306
  verifier_enabled: config.verifier,
306
307
  phase_found: !!phaseInfo,
307
- phase_dir: phaseInfo?.['directory'] || null,
308
+ // #2376: absolute (anchored on cwd/project_root), not orchestrator-cwd-relative —
309
+ // a spawned subagent's own cwd may differ from the orchestrator's.
310
+ phase_dir: phaseInfo?.['directory']
311
+ ? toPosixPath(node_path_1.default.join(cwd, phaseInfo['directory']))
312
+ : null,
308
313
  phase_number: phaseInfo?.['phase_number'] || null,
309
314
  phase_name: phaseInfo?.['phase_name'] || null,
310
315
  phase_slug: phaseInfo?.['phase_slug'] || null,
@@ -330,9 +335,13 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
330
335
  state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
331
336
  roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
332
337
  config_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'config.json')),
333
- state_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'STATE.md'))),
334
- roadmap_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'ROADMAP.md'))),
335
- config_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'config.json'))),
338
+ // #2376: emit absolute paths — see comment above on phase_dir.
339
+ state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
340
+ roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
341
+ config_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'config.json')),
342
+ // #2376: execute-phase.md's verify_phase_goal step reads this instead of
343
+ // hardcoding '.planning/REQUIREMENTS.md' into the gsd-verifier spawn prompt.
344
+ requirements_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md')),
336
345
  };
337
346
  if (options['validate']) {
338
347
  try {
@@ -404,7 +413,8 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
404
413
  if (slug) {
405
414
  const prefix = rawProjectCodePlan ? `${rawProjectCodePlan}-` : '';
406
415
  const dirName = `${prefix}${paddedNum}-${slug}`;
407
- expectedPhaseDirPlan = toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningPaths(cwd).phases, dirName)));
416
+ // #2376: absolute — see comment on phase_dir below.
417
+ expectedPhaseDirPlan = toPosixPath(node_path_1.default.join(planningPaths(cwd).phases, dirName));
408
418
  }
409
419
  }
410
420
  const granularityOverride = options['granularity'];
@@ -426,7 +436,10 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
426
436
  auto_chain_active: !!(config._auto_chain_active),
427
437
  mode: config.mode || 'interactive',
428
438
  phase_found: !!phaseInfo,
429
- phase_dir: phaseDirPlan,
439
+ // #2376: absolute (anchored on cwd/project_root) — path.join(cwd, phaseDirPlan)
440
+ // handed to a spawned subagent must resolve regardless of that subagent's own cwd.
441
+ // phaseDirPlan itself stays relative — phase_status below still joins it against cwd.
442
+ phase_dir: phaseDirPlan ? toPosixPath(node_path_1.default.join(cwd, phaseDirPlan)) : null,
430
443
  expected_phase_dir: expectedPhaseDirPlan,
431
444
  phase_number: phaseNumberPlan,
432
445
  phase_name: phaseNamePlan,
@@ -443,9 +456,10 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
443
456
  plan_count: phaseInfo?.['plans']?.length || 0,
444
457
  planning_exists: node_fs_1.default.existsSync(planningDir(cwd)),
445
458
  roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
446
- state_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'STATE.md'))),
447
- roadmap_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'ROADMAP.md'))),
448
- requirements_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md'))),
459
+ // #2376: absolute — see comment on phase_dir above.
460
+ state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
461
+ roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
462
+ requirements_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md')),
449
463
  patterns_path: null,
450
464
  };
451
465
  if (phaseInfo?.['directory']) {
@@ -454,27 +468,27 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
454
468
  const files = node_fs_1.default.readdirSync(phaseDirFull);
455
469
  const contextFile = findContextMdIn(phaseDirFull);
456
470
  if (contextFile) {
457
- result['context_path'] = toPosixPath(node_path_1.default.join(phaseInfo['directory'], contextFile));
471
+ result['context_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, contextFile));
458
472
  }
459
473
  const researchFile = files.find((f) => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
460
474
  if (researchFile) {
461
- result['research_path'] = toPosixPath(node_path_1.default.join(phaseInfo['directory'], researchFile));
475
+ result['research_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, researchFile));
462
476
  }
463
477
  const verificationFile = files.find((f) => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md');
464
478
  if (verificationFile) {
465
- result['verification_path'] = toPosixPath(node_path_1.default.join(phaseInfo['directory'], verificationFile));
479
+ result['verification_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, verificationFile));
466
480
  }
467
481
  const uatFile = files.find((f) => f.endsWith('-UAT.md') || f === 'UAT.md');
468
482
  if (uatFile) {
469
- result['uat_path'] = toPosixPath(node_path_1.default.join(phaseInfo['directory'], uatFile));
483
+ result['uat_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, uatFile));
470
484
  }
471
485
  const reviewsFile = files.find((f) => f.endsWith('-REVIEWS.md') || f === 'REVIEWS.md');
472
486
  if (reviewsFile) {
473
- result['reviews_path'] = toPosixPath(node_path_1.default.join(phaseInfo['directory'], reviewsFile));
487
+ result['reviews_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, reviewsFile));
474
488
  }
475
489
  const patternsFile = files.find((f) => f.endsWith('-PATTERNS.md') || f === 'PATTERNS.md');
476
490
  if (patternsFile) {
477
- result['patterns_path'] = toPosixPath(node_path_1.default.join(phaseInfo['directory'], patternsFile));
491
+ result['patterns_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, patternsFile));
478
492
  }
479
493
  }
480
494
  catch {
@@ -535,7 +549,14 @@ function cmdInitNewProject(cwd, raw) {
535
549
  brave_search_available: hasBraveSearch,
536
550
  firecrawl_available: hasFirecrawl,
537
551
  exa_search_available: hasExaSearch,
538
- project_path: '.planning/PROJECT.md',
552
+ // #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
553
+ project_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'PROJECT.md')),
554
+ // #2376: new-project.md's research-synthesizer/roadmapper spawn prompts
555
+ // read these instead of hardcoding '.planning/...' literals.
556
+ requirements_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md')),
557
+ roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
558
+ config_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'config.json')),
559
+ research_dir: toPosixPath(node_path_1.default.join(planningRoot(cwd), 'research')),
539
560
  };
540
561
  output(withProjectRoot(cwd, result), raw);
541
562
  }
@@ -569,15 +590,22 @@ function cmdInitNewMilestone(cwd, raw) {
569
590
  latest_completed_milestone: latestCompleted?.version || null,
570
591
  latest_completed_milestone_name: latestCompleted?.name || null,
571
592
  phase_dir_count: phaseDirCount,
593
+ // #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
572
594
  phase_archive_path: latestCompleted
573
- ? toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningRoot(cwd), 'milestones', `${latestCompleted.version}-phases`)))
595
+ ? toPosixPath(node_path_1.default.join(planningRoot(cwd), 'milestones', `${latestCompleted.version}-phases`))
574
596
  : null,
575
597
  project_exists: pathExistsInternal(cwd, '.planning/PROJECT.md'),
576
598
  roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
577
599
  state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
578
- project_path: '.planning/PROJECT.md',
579
- roadmap_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'ROADMAP.md'))),
580
- state_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'STATE.md'))),
600
+ project_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'PROJECT.md')),
601
+ roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
602
+ state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
603
+ // #2376: new-milestone.md's research-synthesizer/roadmapper spawn prompts
604
+ // read these instead of hardcoding '.planning/...' literals.
605
+ requirements_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md')),
606
+ config_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'config.json')),
607
+ research_dir: toPosixPath(node_path_1.default.join(planningRoot(cwd), 'research')),
608
+ milestones_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'MILESTONES.md')),
581
609
  };
582
610
  output(withProjectRoot(cwd, result), raw);
583
611
  }
@@ -613,10 +641,13 @@ function cmdInitQuick(cwd, description, raw) {
613
641
  quick_id: quickId,
614
642
  slug: slug,
615
643
  description: description || null,
616
- date: now.toISOString().split('T')[0],
617
- timestamp: now.toISOString(),
618
- quick_dir: '.planning/quick',
619
- task_dir: slug ? `.planning/quick/${quickId}-${slug}` : null,
644
+ date: clock_cjs_1.realClock.localToday(),
645
+ timestamp: clock_cjs_1.realClock.nowIso(),
646
+ // #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
647
+ quick_dir: toPosixPath(node_path_1.default.join(planningDir(cwd), 'quick')),
648
+ task_dir: slug
649
+ ? toPosixPath(node_path_1.default.join(planningDir(cwd), 'quick', `${quickId}-${slug}`))
650
+ : null,
620
651
  roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
621
652
  planning_exists: node_fs_1.default.existsSync(planningRoot(cwd)),
622
653
  };
@@ -628,7 +659,18 @@ function cmdInitIngestDocs(cwd, raw) {
628
659
  project_exists: pathExistsInternal(cwd, '.planning/PROJECT.md'),
629
660
  planning_exists: node_fs_1.default.existsSync(planningRoot(cwd)),
630
661
  ...getInitGitState(cwd),
631
- project_path: '.planning/PROJECT.md',
662
+ // #2376: absolute — see comment on phase_dir in cmdInitExecutePhase. The
663
+ // classify_parallel/synthesize/route_new_mode spawns in ingest-docs.md
664
+ // (gsd-doc-classifier, gsd-doc-synthesizer, gsd-roadmapper) previously
665
+ // hardcoded bare '.planning/intel/...', '.planning/PROJECT.md', etc.
666
+ // literals into their Agent(prompt=...) blocks; those now interpolate
667
+ // these fields instead.
668
+ project_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'PROJECT.md')),
669
+ requirements_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md')),
670
+ roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
671
+ state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
672
+ intel_dir: toPosixPath(node_path_1.default.join(planningDir(cwd), 'intel')),
673
+ conflicts_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'INGEST-CONFLICTS.md')),
632
674
  commit_docs: config.commit_docs,
633
675
  };
634
676
  output(withProjectRoot(cwd, result), raw);
@@ -657,9 +699,10 @@ function cmdInitResume(cwd, raw) {
657
699
  roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
658
700
  project_exists: pathExistsInternal(cwd, '.planning/PROJECT.md'),
659
701
  planning_exists: node_fs_1.default.existsSync(planningRoot(cwd)),
660
- state_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'STATE.md'))),
661
- roadmap_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'ROADMAP.md'))),
662
- project_path: '.planning/PROJECT.md',
702
+ // #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
703
+ state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
704
+ roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
705
+ project_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'PROJECT.md')),
663
706
  has_interrupted_agent: !!interruptedAgentId,
664
707
  interrupted_agent_id: interruptedAgentId,
665
708
  commit_docs: config.commit_docs,
@@ -714,9 +757,15 @@ function cmdInitVerifyWork(cwd, phase, raw) {
714
757
  checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
715
758
  commit_docs: config.commit_docs,
716
759
  phase_found: !!phaseInfo,
717
- phase_dir: phaseDir,
760
+ // #2376: absolute — see comment on phase_dir in cmdInitExecutePhase. phaseDir
761
+ // itself stays relative — evaluateUatPassed above still joins it against cwd.
762
+ phase_dir: phaseDir ? toPosixPath(node_path_1.default.join(cwd, phaseDir)) : null,
718
763
  phase_number: phaseInfo?.['phase_number'] || null,
719
764
  phase_name: phaseInfo?.['phase_name'] || null,
765
+ // #2376: verify-work.md's plan_gap_closure step reads these instead of
766
+ // hardcoding '.planning/STATE.md' / '.planning/ROADMAP.md' literals.
767
+ state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
768
+ roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
720
769
  has_verification: phaseInfo?.['has_verification'] || false,
721
770
  phase_completion: {
722
771
  ...completion,
@@ -730,6 +779,21 @@ function cmdInitVerifyWork(cwd, phase, raw) {
730
779
  function cmdInitPhaseOp(cwd, phase, raw) {
731
780
  const config = loadConfig(cwd);
732
781
  let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
782
+ // #2237: surface ambiguous phase-directory collisions instead of silently
783
+ // taking the first match when unrelated projects share a .planning/phases/ tree.
784
+ if (phaseInfo?.['ambiguous_matches']) {
785
+ const matches = phaseInfo['ambiguous_matches'];
786
+ const result = {
787
+ phase_found: false,
788
+ phase_dir: null,
789
+ phase_number: null,
790
+ phase_name: null,
791
+ ambiguous_matches: matches,
792
+ warning: `Phase ${phase} is ambiguous: ${matches.length} directories match (${matches.map((m) => `"${m}"`).join(', ')}). Set a distinct project_code in .planning/config.json to scope resolution.`,
793
+ };
794
+ output(withProjectRoot(cwd, result), raw);
795
+ return;
796
+ }
733
797
  if (phaseInfo?.['archived']) {
734
798
  const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
735
799
  if (roadmapPhase?.['found']) {
@@ -783,7 +847,8 @@ function cmdInitPhaseOp(cwd, phase, raw) {
783
847
  if (slug) {
784
848
  const prefix = rawProjectCode ? `${rawProjectCode}-` : '';
785
849
  const dirName = `${prefix}${paddedNum}-${slug}`;
786
- expectedPhaseDir = toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningPaths(cwd).phases, dirName)));
850
+ // #2376: absolute — see comment on phase_dir below.
851
+ expectedPhaseDir = toPosixPath(node_path_1.default.join(planningPaths(cwd).phases, dirName));
787
852
  }
788
853
  }
789
854
  const result = {
@@ -798,7 +863,8 @@ function cmdInitPhaseOp(cwd, phase, raw) {
798
863
  ? (0, secrets_cjs_1.maskIfSecret)('exa_search', config.exa_search)
799
864
  : config.exa_search,
800
865
  phase_found: !!phaseInfo,
801
- phase_dir: phaseDir,
866
+ // #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
867
+ phase_dir: phaseDir ? toPosixPath(node_path_1.default.join(cwd, phaseDir)) : null,
802
868
  expected_phase_dir: expectedPhaseDir,
803
869
  phase_number: phaseNumber,
804
870
  phase_name: phaseName,
@@ -812,9 +878,10 @@ function cmdInitPhaseOp(cwd, phase, raw) {
812
878
  plan_count: phaseInfo?.['plans']?.length || 0,
813
879
  roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
814
880
  planning_exists: node_fs_1.default.existsSync(planningDir(cwd)),
815
- state_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'STATE.md'))),
816
- roadmap_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'ROADMAP.md'))),
817
- requirements_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md'))),
881
+ // #2376: absolute — see comment on phase_dir above.
882
+ state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
883
+ roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
884
+ requirements_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md')),
818
885
  };
819
886
  if (phaseInfo?.['directory']) {
820
887
  const phaseDirFull = node_path_1.default.join(cwd, phaseInfo['directory']);
@@ -822,23 +889,23 @@ function cmdInitPhaseOp(cwd, phase, raw) {
822
889
  const files = node_fs_1.default.readdirSync(phaseDirFull);
823
890
  const contextFile = findContextMdIn(phaseDirFull);
824
891
  if (contextFile) {
825
- result['context_path'] = toPosixPath(node_path_1.default.join(phaseInfo['directory'], contextFile));
892
+ result['context_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, contextFile));
826
893
  }
827
894
  const researchFile = files.find((f) => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
828
895
  if (researchFile) {
829
- result['research_path'] = toPosixPath(node_path_1.default.join(phaseInfo['directory'], researchFile));
896
+ result['research_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, researchFile));
830
897
  }
831
898
  const verificationFile = files.find((f) => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md');
832
899
  if (verificationFile) {
833
- result['verification_path'] = toPosixPath(node_path_1.default.join(phaseInfo['directory'], verificationFile));
900
+ result['verification_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, verificationFile));
834
901
  }
835
902
  const uatFile = files.find((f) => f.endsWith('-UAT.md') || f === 'UAT.md');
836
903
  if (uatFile) {
837
- result['uat_path'] = toPosixPath(node_path_1.default.join(phaseInfo['directory'], uatFile));
904
+ result['uat_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, uatFile));
838
905
  }
839
906
  const reviewsFile = files.find((f) => f.endsWith('-REVIEWS.md') || f === 'REVIEWS.md');
840
907
  if (reviewsFile) {
841
- result['reviews_path'] = toPosixPath(node_path_1.default.join(phaseInfo['directory'], reviewsFile));
908
+ result['reviews_path'] = toPosixPath(node_path_1.default.join(phaseDirFull, reviewsFile));
842
909
  }
843
910
  }
844
911
  catch {
@@ -849,7 +916,6 @@ function cmdInitPhaseOp(cwd, phase, raw) {
849
916
  }
850
917
  function cmdInitTodos(cwd, area, raw) {
851
918
  const config = loadConfig(cwd);
852
- const now = new Date();
853
919
  const pendingDir = node_path_1.default.join(planningDir(cwd), 'todos', 'pending');
854
920
  let count = 0;
855
921
  const todos = [];
@@ -863,6 +929,9 @@ function cmdInitTodos(cwd, area, raw) {
863
929
  const createdMatch = content.match(/^created:\s*(.+)$/m);
864
930
  const titleMatch = content.match(/^title:\s*(.+)$/m);
865
931
  const areaMatch = content.match(/^area:\s*(.+)$/m);
932
+ // #2337: kept in parity with cmdListTodos — surface severity when
933
+ // present, omit the key entirely for todos with no severity line.
934
+ const severityMatch = content.match(/^severity:\s*(.+)$/m);
866
935
  const todoArea = areaMatch ? areaMatch[1].trim() : 'general';
867
936
  if (area && todoArea !== area)
868
937
  continue;
@@ -872,7 +941,9 @@ function cmdInitTodos(cwd, area, raw) {
872
941
  created: createdMatch ? createdMatch[1].trim() : 'unknown',
873
942
  title: titleMatch ? titleMatch[1].trim() : 'Untitled',
874
943
  area: todoArea,
875
- path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'todos', 'pending', file))),
944
+ // #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
945
+ path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'todos', 'pending', file)),
946
+ ...(severityMatch ? { severity: severityMatch[1].trim() } : {}),
876
947
  });
877
948
  }
878
949
  catch {
@@ -885,13 +956,14 @@ function cmdInitTodos(cwd, area, raw) {
885
956
  }
886
957
  const result = {
887
958
  commit_docs: config.commit_docs,
888
- date: now.toISOString().split('T')[0],
889
- timestamp: now.toISOString(),
959
+ date: clock_cjs_1.realClock.localToday(),
960
+ timestamp: clock_cjs_1.realClock.nowIso(),
890
961
  todo_count: count,
891
962
  todos,
892
963
  area_filter: area || null,
893
- pending_dir: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'todos', 'pending'))),
894
- completed_dir: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'todos', 'completed'))),
964
+ // #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
965
+ pending_dir: toPosixPath(node_path_1.default.join(planningDir(cwd), 'todos', 'pending')),
966
+ completed_dir: toPosixPath(node_path_1.default.join(planningDir(cwd), 'todos', 'completed')),
895
967
  planning_exists: node_fs_1.default.existsSync(planningDir(cwd)),
896
968
  todos_dir_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'todos')),
897
969
  pending_dir_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'todos', 'pending')),
@@ -1007,7 +1079,6 @@ function cmdInitMilestoneOp(cwd, raw) {
1007
1079
  }
1008
1080
  function cmdInitMapCodebase(cwd, raw) {
1009
1081
  const config = loadConfig(cwd);
1010
- const now = new Date();
1011
1082
  const codebaseDir = node_path_1.default.join(planningRoot(cwd), 'codebase');
1012
1083
  let existingMaps = [];
1013
1084
  try {
@@ -1022,9 +1093,10 @@ function cmdInitMapCodebase(cwd, raw) {
1022
1093
  search_gitignored: config.search_gitignored,
1023
1094
  parallelization: config.parallelization,
1024
1095
  subagent_timeout: config.subagent_timeout,
1025
- date: now.toISOString().split('T')[0],
1026
- timestamp: now.toISOString(),
1027
- codebase_dir: '.planning/codebase',
1096
+ date: clock_cjs_1.realClock.localToday(),
1097
+ timestamp: clock_cjs_1.realClock.nowIso(),
1098
+ // #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
1099
+ codebase_dir: toPosixPath(node_path_1.default.join(planningRoot(cwd), 'codebase')),
1028
1100
  existing_maps: existingMaps,
1029
1101
  has_maps: existingMaps.length > 0,
1030
1102
  planning_exists: pathExistsInternal(cwd, '.planning'),
@@ -1425,7 +1497,10 @@ function cmdInitProgress(cwd, raw) {
1425
1497
  const phaseInfo = {
1426
1498
  number: phaseNumber,
1427
1499
  name: phaseName,
1428
- directory: phaseDirRel,
1500
+ // #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
1501
+ // phaseDirRel itself stays relative — buildPhaseCompletionProjection
1502
+ // above still joins it against cwd.
1503
+ directory: toPosixPath(node_path_1.default.join(cwd, phaseDirRel)),
1429
1504
  status,
1430
1505
  plan_count: plans.length,
1431
1506
  summary_count: summaries.length,
@@ -1493,10 +1568,11 @@ function cmdInitProgress(cwd, raw) {
1493
1568
  project_exists: pathExistsInternal(cwd, '.planning/PROJECT.md'),
1494
1569
  roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
1495
1570
  state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
1496
- state_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'STATE.md'))),
1497
- roadmap_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'ROADMAP.md'))),
1498
- project_path: '.planning/PROJECT.md',
1499
- config_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'config.json'))),
1571
+ // #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
1572
+ state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
1573
+ roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
1574
+ project_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'PROJECT.md')),
1575
+ config_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'config.json')),
1500
1576
  };
1501
1577
  output(withProjectRoot(cwd, result), raw);
1502
1578
  }
@@ -1654,7 +1730,9 @@ function cmdInitRemoveWorkspace(cwd, name, raw) {
1654
1730
  dirty_repos: dirtyRepos,
1655
1731
  has_dirty_repos: dirtyRepos.length > 0,
1656
1732
  };
1657
- output(result, raw);
1733
+ // #2402: sibling init commands route through withProjectRoot so response_language
1734
+ // (and project_root/agents_installed) reach the workflow; this one didn't.
1735
+ output(withProjectRoot(cwd, result), raw);
1658
1736
  }
1659
1737
  function buildAgentSkillsBlock(config, agentType, projectRoot, diagnostics) {
1660
1738
  const warn = (message) => {
@@ -1763,7 +1841,7 @@ function buildAgentSkillsBlock(config, agentType, projectRoot, diagnostics) {
1763
1841
  if (entry.kind === 'directive') {
1764
1842
  return `- Load the \`${entry.name}\` skill via the Skill tool before proceeding (plugin-provided).`;
1765
1843
  }
1766
- return `- @${String(entry.ref).replace(/\\/g, '/')}`;
1844
+ return `- @${(0, shell_command_projection_cjs_1.posixNormalize)(String(entry.ref))}`;
1767
1845
  }).join('\n');
1768
1846
  return `<agent_skills>\nRead these user-configured skills:\n${lines}\n</agent_skills>`;
1769
1847
  }