@pareto4/pp-core 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +31 -0
  3. package/agents/pp-code-fixer.md +1 -1
  4. package/agents/pp-debug-session-manager.md +1 -1
  5. package/agents/pp-debugger.md +1 -1
  6. package/agents/pp-executor.md +20 -10
  7. package/agents/pp-intel-updater.md +1 -1
  8. package/agents/pp-phase-researcher.md +1 -1
  9. package/agents/pp-plan-checker.md +5 -5
  10. package/agents/pp-planner.md +10 -24
  11. package/agents/pp-project-researcher.md +1 -1
  12. package/agents/pp-research-synthesizer.md +1 -1
  13. package/agents/pp-ui-researcher.md +1 -1
  14. package/agents/pp-verifier.md +8 -4
  15. package/bin/install.js +429 -82
  16. package/commands/pp-autonomous.md +8 -6
  17. package/commands/pp-discovery-to-build.md +1 -1
  18. package/commands/pp-discovery.md +3 -2
  19. package/commands/pp-discuss-phase.md +2 -2
  20. package/commands/pp-execute-phase.md +3 -1
  21. package/commands/pp-graphify.md +5 -5
  22. package/commands/pp-import.md +1 -1
  23. package/commands/pp-map-codebase.md +1 -1
  24. package/commands/pp-ns-project.md +2 -3
  25. package/commands/pp-ns-workflow.md +1 -1
  26. package/commands/pp-plan-phase.md +2 -1
  27. package/commands/pp-verify-work.md +2 -1
  28. package/gemini-extension.json +1 -1
  29. package/hooks/dist/pp-cursor-session-start.js +2 -2
  30. package/hooks/dist/pp-session-state.sh +1 -1
  31. package/hooks/dist/pp-statusline.js +21 -2
  32. package/hooks/pp-cursor-session-start.js +2 -2
  33. package/hooks/pp-session-state.sh +1 -1
  34. package/hooks/pp-statusline.js +21 -2
  35. package/package.json +18 -5
  36. package/pareto-skills/pp-project-setup/SKILL.md +5 -63
  37. package/pp-core/bin/detect-build-commands.sh +182 -0
  38. package/pp-core/bin/lib/capability-registry.cjs +65 -7
  39. package/pp-core/bin/lib/check-command-router.cjs +79 -38
  40. package/pp-core/bin/lib/clusters.cjs +2 -3
  41. package/pp-core/bin/lib/command-aliases.cjs +36 -0
  42. package/pp-core/bin/lib/command-arg-projection.cjs +99 -1
  43. package/pp-core/bin/lib/commands.cjs +21 -6
  44. package/pp-core/bin/lib/compose-bindings.cjs +128 -15
  45. package/pp-core/bin/lib/config-schema.cjs +16 -0
  46. package/pp-core/bin/lib/config.cjs +142 -27
  47. package/pp-core/bin/lib/core-utils.cjs +43 -7
  48. package/pp-core/bin/lib/discovery-input-check.cjs +218 -0
  49. package/pp-core/bin/lib/discovery-rebaseline.cjs +1372 -0
  50. package/pp-core/bin/lib/discovery-research-track.cjs +979 -0
  51. package/pp-core/bin/lib/discovery-rollup.cjs +476 -0
  52. package/pp-core/bin/lib/discovery-screens.cjs +1995 -0
  53. package/pp-core/bin/lib/discovery-sweep.cjs +390 -0
  54. package/pp-core/bin/lib/discovery-table.cjs +98 -0
  55. package/pp-core/bin/lib/dispatch-plan.cjs +472 -0
  56. package/pp-core/bin/lib/frontmatter.cjs +17 -2
  57. package/pp-core/bin/lib/init-command-router.cjs +8 -5
  58. package/pp-core/bin/lib/init-contract.cjs +213 -33
  59. package/pp-core/bin/lib/init-interactive.cjs +20 -1
  60. package/pp-core/bin/lib/init.cjs +124 -134
  61. package/pp-core/bin/lib/install-profiles.cjs +5 -0
  62. package/pp-core/bin/lib/install-run.cjs +536 -0
  63. package/pp-core/bin/lib/io.cjs +15 -0
  64. package/pp-core/bin/lib/loop-resolver.cjs +24 -0
  65. package/pp-core/bin/lib/milestone.cjs +345 -48
  66. package/pp-core/bin/lib/phase-command-router.cjs +23 -1
  67. package/pp-core/bin/lib/phase-locator.cjs +15 -6
  68. package/pp-core/bin/lib/phase-scope.cjs +91 -0
  69. package/pp-core/bin/lib/phase-tier.cjs +467 -0
  70. package/pp-core/bin/lib/phase.cjs +247 -21
  71. package/pp-core/bin/lib/phases-command-router.cjs +2 -0
  72. package/pp-core/bin/lib/plan-graph.cjs +368 -0
  73. package/pp-core/bin/lib/planning-workspace.cjs +53 -0
  74. package/pp-core/bin/lib/platform-bindings.cjs +32 -2
  75. package/pp-core/bin/lib/profile-output.cjs +11 -3
  76. package/pp-core/bin/lib/prohibition-enforcement.cjs +112 -0
  77. package/pp-core/bin/lib/roadmap-parser.cjs +366 -99
  78. package/pp-core/bin/lib/roadmap.cjs +141 -39
  79. package/pp-core/bin/lib/runtime-hooks-surface.cjs +1 -1
  80. package/pp-core/bin/lib/shell-command-projection.cjs +24 -0
  81. package/pp-core/bin/lib/state-command-router.cjs +82 -13
  82. package/pp-core/bin/lib/state-document.cjs +124 -7
  83. package/pp-core/bin/lib/state.cjs +784 -257
  84. package/pp-core/bin/lib/template.cjs +22 -6
  85. package/pp-core/bin/lib/vault-command-router.cjs +57 -6
  86. package/pp-core/bin/lib/vault-practices.cjs +6 -36
  87. package/pp-core/bin/lib/vault-write-back.cjs +20 -4
  88. package/pp-core/bin/lib/vault.cjs +555 -23
  89. package/pp-core/bin/lib/verify.cjs +53 -31
  90. package/pp-core/bin/lib/workstream.cjs +1 -1
  91. package/pp-core/bin/lib/worktree-safety.cjs +26 -15
  92. package/pp-core/bin/portable-timeout.sh +89 -0
  93. package/pp-core/bin/pp-tools.cjs +158 -8
  94. package/pp-core/bin/shared/config-defaults.manifest.json +7 -0
  95. package/pp-core/bin/shared/config-schema.manifest.json +3 -0
  96. package/pp-core/references/autonomous-best-effort.md +461 -0
  97. package/pp-core/references/autonomous-lifecycle.md +146 -0
  98. package/pp-core/references/autonomous-smart-discuss.md +16 -12
  99. package/pp-core/references/checkpoints.md +1 -1
  100. package/pp-core/references/discovery-id-conventions.md +202 -0
  101. package/pp-core/references/discovery-rebaseline.md +254 -0
  102. package/pp-core/references/discovery-research-track.md +283 -0
  103. package/pp-core/references/discovery-traceability.md +400 -0
  104. package/pp-core/references/loop-hook-dispatch.md +7 -0
  105. package/pp-core/references/mandatory-initial-read.md +1 -1
  106. package/pp-core/references/planner-gap-closure.md +1 -0
  107. package/pp-core/references/planner-human-verify-mode.md +1 -1
  108. package/pp-core/references/planner-load-graph-context.md +1 -1
  109. package/pp-core/references/planner-plan-graph.md +66 -0
  110. package/pp-core/references/planner-tdd.md +1 -0
  111. package/pp-core/references/planning-config.md +8 -0
  112. package/pp-core/references/sketch-tooling.md +4 -0
  113. package/pp-core/references/tdd.md +1 -0
  114. package/pp-core/templates/claude-md.md +1 -1
  115. package/pp-core/templates/phase-prompt.md +6 -2
  116. package/pp-core/templates/prototype-review/review.js +303 -0
  117. package/pp-core/templates/state.md +6 -0
  118. package/pp-core/workflows/_runtime-launcher.snippet.sh +1 -1
  119. package/pp-core/workflows/add-backlog.md +1 -1
  120. package/pp-core/workflows/add-phase.md +1 -1
  121. package/pp-core/workflows/add-tests.md +1 -1
  122. package/pp-core/workflows/add-todo.md +1 -1
  123. package/pp-core/workflows/ai-integration-phase.md +2 -2
  124. package/pp-core/workflows/audit-fix.md +12 -16
  125. package/pp-core/workflows/audit-milestone.md +1 -1
  126. package/pp-core/workflows/audit-uat.md +1 -1
  127. package/pp-core/workflows/autonomous.md +68 -306
  128. package/pp-core/workflows/check-todos.md +1 -1
  129. package/pp-core/workflows/cleanup.md +1 -1
  130. package/pp-core/workflows/code-review-fix.md +1 -1
  131. package/pp-core/workflows/code-review.md +19 -7
  132. package/pp-core/workflows/complete-milestone.md +41 -60
  133. package/pp-core/workflows/debug.md +1 -1
  134. package/pp-core/workflows/diagnose-issues.md +1 -1
  135. package/pp-core/workflows/discovery-sprint/gap-analysis.md +140 -0
  136. package/pp-core/workflows/discovery-sprint/input-package.md +140 -0
  137. package/pp-core/workflows/discovery-sprint/prefill.md +92 -0
  138. package/pp-core/workflows/discovery-sprint/prototype.md +257 -0
  139. package/pp-core/workflows/discovery-sprint/rebaseline.md +276 -0
  140. package/pp-core/workflows/discovery-sprint/session-13-gates.md +24 -0
  141. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +92 -0
  142. package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +98 -0
  143. package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +215 -0
  144. package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +146 -0
  145. package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +98 -0
  146. package/pp-core/workflows/discovery-sprint/sessions/06-security.md +107 -0
  147. package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +98 -0
  148. package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +85 -0
  149. package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +109 -0
  150. package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +119 -0
  151. package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +92 -0
  152. package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +98 -0
  153. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +205 -0
  154. package/pp-core/workflows/discovery-sprint/templates/context-template.md +266 -0
  155. package/pp-core/workflows/discovery-sprint.md +79 -1714
  156. package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +5 -5
  157. package/pp-core/workflows/discovery-to-build/steps/screen-inventory-gate.md +70 -0
  158. package/pp-core/workflows/discovery-to-build/templates/discovery-references.md +54 -0
  159. package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +184 -0
  160. package/pp-core/workflows/discovery-to-build.md +21 -238
  161. package/pp-core/workflows/discuss-phase/modes/advisor.md +1 -1
  162. package/pp-core/workflows/discuss-phase/modes/auto.md +1 -1
  163. package/pp-core/workflows/discuss-phase/modes/chain.md +1 -1
  164. package/pp-core/workflows/discuss-phase-assumptions.md +1 -1
  165. package/pp-core/workflows/discuss-phase.md +9 -2
  166. package/pp-core/workflows/do.md +4 -4
  167. package/pp-core/workflows/docs-update.md +1 -1
  168. package/pp-core/workflows/edit-phase.md +1 -1
  169. package/pp-core/workflows/eval-review.md +1 -1
  170. package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +45 -0
  171. package/pp-core/workflows/execute-phase/steps/client-context-gate.md +1 -1
  172. package/pp-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  173. package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +90 -0
  174. package/pp-core/workflows/execute-phase/steps/install-run.md +29 -0
  175. package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +23 -0
  176. package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +76 -77
  177. package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +72 -0
  178. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +5 -5
  179. package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +19 -112
  180. package/pp-core/workflows/execute-phase.md +102 -216
  181. package/pp-core/workflows/execute-plan.md +6 -2
  182. package/pp-core/workflows/explore.md +1 -1
  183. package/pp-core/workflows/extract-learnings.md +1 -1
  184. package/pp-core/workflows/forensics.md +4 -4
  185. package/pp-core/workflows/graduation.md +1 -1
  186. package/pp-core/workflows/health.md +1 -1
  187. package/pp-core/workflows/help/modes/brief.md +1 -0
  188. package/pp-core/workflows/help/modes/default.md +2 -1
  189. package/pp-core/workflows/help/modes/full.md +12 -5
  190. package/pp-core/workflows/import.md +1 -1
  191. package/pp-core/workflows/ingest-docs.md +1 -1
  192. package/pp-core/workflows/init.md +18 -2
  193. package/pp-core/workflows/insert-phase.md +1 -1
  194. package/pp-core/workflows/list-workspaces.md +1 -1
  195. package/pp-core/workflows/manager.md +3 -3
  196. package/pp-core/workflows/map-codebase.md +2 -2
  197. package/pp-core/workflows/milestone-summary.md +4 -4
  198. package/pp-core/workflows/mvp-phase.md +1 -1
  199. package/pp-core/workflows/new-milestone.md +7 -8
  200. package/pp-core/workflows/new-project.md +6 -1
  201. package/pp-core/workflows/new-workspace.md +1 -1
  202. package/pp-core/workflows/next.md +2 -2
  203. package/pp-core/workflows/note.md +2 -2
  204. package/pp-core/workflows/pause-work.md +1 -1
  205. package/pp-core/workflows/plan-milestone-gaps.md +1 -1
  206. package/pp-core/workflows/plan-phase/steps/chunked-planning.md +111 -0
  207. package/pp-core/workflows/plan-phase/steps/phase-tier.md +34 -0
  208. package/pp-core/workflows/plan-phase.md +43 -167
  209. package/pp-core/workflows/plan-review-convergence.md +2 -2
  210. package/pp-core/workflows/plant-seed.md +1 -1
  211. package/pp-core/workflows/pr-branch.md +1 -1
  212. package/pp-core/workflows/profile-user.md +1 -1
  213. package/pp-core/workflows/progress.md +3 -3
  214. package/pp-core/workflows/quick.md +3 -1
  215. package/pp-core/workflows/remove-phase.md +1 -1
  216. package/pp-core/workflows/remove-workspace.md +1 -1
  217. package/pp-core/workflows/resume-project.md +6 -12
  218. package/pp-core/workflows/review.md +1 -1
  219. package/pp-core/workflows/scan.md +1 -1
  220. package/pp-core/workflows/secure-phase.md +1 -1
  221. package/pp-core/workflows/settings-advanced.md +1 -1
  222. package/pp-core/workflows/settings-integrations.md +1 -1
  223. package/pp-core/workflows/settings.md +1 -1
  224. package/pp-core/workflows/ship.md +25 -8
  225. package/pp-core/workflows/sketch-wrap-up.md +1 -1
  226. package/pp-core/workflows/sketch.md +2 -1
  227. package/pp-core/workflows/spec-phase.md +1 -1
  228. package/pp-core/workflows/spike-wrap-up.md +1 -1
  229. package/pp-core/workflows/spike.md +1 -1
  230. package/pp-core/workflows/stats.md +2 -2
  231. package/pp-core/workflows/thread.md +1 -1
  232. package/pp-core/workflows/transition.md +5 -9
  233. package/pp-core/workflows/ui-phase.md +23 -2
  234. package/pp-core/workflows/ui-review.md +1 -1
  235. package/pp-core/workflows/ultraplan-phase.md +2 -2
  236. package/pp-core/workflows/validate-phase.md +1 -1
  237. package/pp-core/workflows/vault-write-back.md +1 -1
  238. package/pp-core/workflows/verify-phase.md +29 -26
  239. package/pp-core/workflows/verify-work.md +14 -10
  240. package/scripts/base64-scan.sh +7 -41
  241. package/scripts/changeset/cli.cjs +25 -5
  242. package/scripts/dev-env.cjs +462 -0
  243. package/scripts/lib/allowlist-ratchet.cjs +103 -1
  244. package/scripts/lib/size-budget.cjs +311 -0
  245. package/scripts/lint-neutrality-names.cjs +694 -0
  246. package/scripts/lint-org-coordinates.allowlist.json +35 -0
  247. package/scripts/lint-org-coordinates.cjs +426 -0
  248. package/scripts/lint-skill-deps.cjs +48 -9
  249. package/scripts/lint-test-file-count.cjs +17 -2
  250. package/scripts/prompt-injection-scan.sh +41 -20
  251. package/scripts/sync-manifest-versions.cjs +41 -7
  252. package/scripts/sync-runtime-launcher.cjs +106 -1
  253. package/scripts/update-size-baseline.cjs +274 -30
  254. package/scripts/workflow-size.cjs +61 -9
  255. package/commands/pp-build-handoff.md +0 -46
  256. package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +0 -94
@@ -35,10 +35,13 @@ const phaseIdMod = require("./phase-id.cjs");
35
35
  const { escapeRegex, normalizePhaseName, phaseMarkdownRegexSource, comparePhaseNum, phaseTokenMatches } = phaseIdMod;
36
36
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-locator.cjs is an export= CommonJS module
37
37
  const phaseLocatorMod = require("./phase-locator.cjs");
38
- const { findPhaseInternal, getArchivedPhaseDirs } = phaseLocatorMod;
38
+ const { getArchivedPhaseDirs } = phaseLocatorMod;
39
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-scope.cjs is an export= CommonJS module
40
+ const phaseScopeMod = require("./phase-scope.cjs");
41
+ const { resolvePhaseForRead } = phaseScopeMod;
39
42
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- roadmap-parser.cjs is an export= CommonJS module
40
43
  const roadmapParserMod = require("./roadmap-parser.cjs");
41
- const { stripShippedMilestones, extractCurrentMilestone, getMilestonePhaseFilter } = roadmapParserMod;
44
+ const { stripShippedMilestones, extractCurrentMilestone, getMilestonePhaseFilter, buildPlansSummaryLinePattern } = roadmapParserMod;
42
45
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
43
46
  const planningWorkspace = require("./planning-workspace.cjs");
44
47
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- frontmatter.cjs is an export= CommonJS module
@@ -48,11 +51,16 @@ const stateMod = require("./state.cjs");
48
51
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
49
52
  const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
50
53
  const phase_lifecycle_cjs_1 = require("./phase-lifecycle.cjs");
54
+ const state_document_cjs_1 = require("./state-document.cjs");
51
55
  const clock_cjs_1 = require("./clock.cjs");
52
56
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- uat-predicate.cjs is an export= CommonJS module
53
57
  const uatPredicate = require("./uat-predicate.cjs");
54
58
  const { evaluateUatPassed } = uatPredicate;
55
- const { planningDir, withPlanningLock } = planningWorkspace;
59
+ const plan_graph_cjs_1 = require("./plan-graph.cjs");
60
+ const dispatch_plan_cjs_1 = require("./dispatch-plan.cjs");
61
+ const phase_tier_cjs_1 = require("./phase-tier.cjs");
62
+ const install_run_cjs_1 = require("./install-run.cjs");
63
+ const { planningDir, withPlanningLock, milestonePathsFor } = planningWorkspace;
56
64
  const { extractFrontmatter } = frontmatterMod;
57
65
  const { readModifyWriteStateMd, stateExtractField, stateReplaceField, stateReplaceFieldWithFallback, syncStateFrontmatter, withStateLock, updatePerformanceMetricsSection, } = stateMod;
58
66
  // #2893 — strict canonical filter: `{padded_phase}-{NN}-PLAN.md` or `PLAN.md`.
@@ -287,7 +295,8 @@ function cmdFindPhase(cwd, phase, raw) {
287
295
  if (node_fs_1.default.existsSync(flatPhasesDir))
288
296
  searchDirs.push(flatPhasesDir);
289
297
  try {
290
- const milestonesDir = node_path_1.default.join(planBase, 'milestones');
298
+ // CARRY-05: archive location from the owner, not a local join.
299
+ const milestonesDir = milestonePathsFor(planBase).milestonesArchive;
291
300
  const entries = node_fs_1.default
292
301
  .readdirSync(milestonesDir, { withFileTypes: true })
293
302
  .filter((e) => e.isDirectory() && /^v\d+.*-phases$/.test(e.name))
@@ -399,6 +408,140 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
399
408
  if (!phase) {
400
409
  error('phase required for phase-plan-index');
401
410
  }
411
+ const built = buildPhasePlanIndex(cwd, phase);
412
+ if (built.fatal !== undefined) {
413
+ error(built.fatal);
414
+ return;
415
+ }
416
+ output(built.result, raw);
417
+ }
418
+ /**
419
+ * `phase.dispatch-plan <phase> [--wave N] [--gaps-only] [--use-worktrees true|false]`:
420
+ * the executor spawns, builds, suite runs and tracking commits for the phase's
421
+ * incomplete plans (computed by src/dispatch-plan.cts from the plan inventory).
422
+ */
423
+ function cmdPhaseDispatchPlan(cwd, args, raw) {
424
+ const parsed = (0, dispatch_plan_cjs_1.parseDispatchPlanArgs)(args);
425
+ if ('error' in parsed) {
426
+ error(parsed.error);
427
+ return;
428
+ }
429
+ const built = buildPhasePlanIndex(cwd, parsed.phase);
430
+ if (built.fatal !== undefined) {
431
+ error(built.fatal);
432
+ return;
433
+ }
434
+ if (built.phaseDir === null) {
435
+ output({ phase: built.result['phase'], error: 'Phase not found' }, raw);
436
+ return;
437
+ }
438
+ const policy = (0, dispatch_plan_cjs_1.resolveDispatchPolicy)(cwd, parsed);
439
+ const plans = built.result['plans'].map((p) => {
440
+ const extra = built.extras.get(p['id']);
441
+ return {
442
+ id: p['id'],
443
+ title: p['title'] ?? null,
444
+ wave: p['wave'],
445
+ serial_group: p['serial_group'] ?? null,
446
+ files_modified: p['files_modified'],
447
+ autonomous: p['autonomous'],
448
+ has_summary: p['has_summary'],
449
+ requirements: extra?.requirements ?? [],
450
+ gap_closure: extra?.gapClosure ?? false,
451
+ };
452
+ });
453
+ const phaseDirRel = node_path_1.default.relative(cwd, built.phaseDir).split(node_path_1.default.sep).join('/');
454
+ const tier = (0, phase_tier_cjs_1.resolvePhaseTierForProject)(cwd, parsed.phase, parsed.tier);
455
+ output((0, dispatch_plan_cjs_1.computeDispatchPlan)(built.result['phase'], phaseDirRel, plans, policy, tier, { planBounceScript: tier.plan_bounce_script }), raw);
456
+ }
457
+ /**
458
+ * `phase.install-run <phase> [--tarball <tgz>] [--package-dir <dir>] [--no-build]`:
459
+ * pack, install into a temp project, init and run the phase's install checks
460
+ * against the installed copy; writes `<NN>-INSTALL-RUN.json` (src/install-run.cts).
461
+ */
462
+ function cmdPhaseInstallRun(cwd, args, raw) {
463
+ const parsed = (0, install_run_cjs_1.parseInstallRunArgs)(args);
464
+ if ('error' in parsed) {
465
+ error(parsed.error, ERROR_REASON.USAGE);
466
+ return;
467
+ }
468
+ let out;
469
+ try {
470
+ out = (0, install_run_cjs_1.runInstallRun)({ cwd, phase: parsed.phase, tarball: parsed.tarball, packageDir: parsed.packageDir, build: parsed.build });
471
+ }
472
+ catch (e) {
473
+ error(e.message);
474
+ return;
475
+ }
476
+ output(out, raw);
477
+ }
478
+ /**
479
+ * `phase.tier <phase> [--tier light|standard|heavy]`: the phase's resolved
480
+ * ceremony tier — tier, source, gate set, suppressed gates (src/phase-tier.cts).
481
+ */
482
+ function cmdPhaseTier(cwd, args, raw) {
483
+ const usage = 'Usage: phase tier <phase> [--tier light|standard|heavy]';
484
+ const parsed = (0, phase_tier_cjs_1.parseTierFlag)(args.filter((a) => a !== '--raw'));
485
+ if ('error' in parsed) {
486
+ error(`${parsed.error}. ${usage}`, ERROR_REASON.USAGE);
487
+ return;
488
+ }
489
+ if (parsed.rest.length !== 1 || parsed.rest[0].startsWith('--')) {
490
+ error(parsed.rest.length === 0 ? `phase required for phase tier. ${usage}` : `phase tier takes one phase and --tier. ${usage}`, ERROR_REASON.USAGE);
491
+ return;
492
+ }
493
+ const phaseArg = parsed.rest[0];
494
+ output({ phase: phaseArg, ...(0, phase_tier_cjs_1.resolvePhaseTierForProject)(cwd, phaseArg, parsed.tier) }, raw);
495
+ }
496
+ /**
497
+ * `phase.tier-propose <phase> [--record] [--tier X]`: propose a tier from the
498
+ * phase's plans (`files_modified`) or, before planning, its requirement tags
499
+ * and ROADMAP text. `--record` writes it to the phase's ROADMAP `**Tier:**`
500
+ * field, marked `(proposed: …)`, unless the operator already chose one;
501
+ * `--record --tier X` records X as the operator's choice.
502
+ */
503
+ function cmdPhaseTierPropose(cwd, args, raw) {
504
+ const usage = 'Usage: phase tier-propose <phase> [--record] [--tier light|standard|heavy]';
505
+ const parsed = (0, phase_tier_cjs_1.parseTierFlag)(args.filter((a) => a !== '--raw'));
506
+ if ('error' in parsed) {
507
+ error(`${parsed.error}. ${usage}`, ERROR_REASON.USAGE);
508
+ return;
509
+ }
510
+ const doRecord = parsed.rest.includes('--record');
511
+ const positional = parsed.rest.filter((a) => a !== '--record');
512
+ if (positional.length !== 1 || positional[0].startsWith('--')) {
513
+ error(positional.length === 0 ? `phase required for phase tier-propose. ${usage}` : `phase tier-propose does not support ${positional.find((a) => a.startsWith('--')) ?? positional.join(' ')}. ${usage}`, ERROR_REASON.USAGE);
514
+ return;
515
+ }
516
+ const phaseArg = positional[0];
517
+ const record = (0, phase_tier_cjs_1.readPhaseRecord)(cwd, phaseArg);
518
+ if (record.section === null) {
519
+ output({ phase: phaseArg, error: 'Phase not found in ROADMAP.md' }, raw);
520
+ return;
521
+ }
522
+ const built = buildPhasePlanIndex(cwd, phaseArg);
523
+ const planRows = built.fatal === undefined && built.phaseDir !== null
524
+ ? built.result['plans'].map((p) => ({ id: p['id'], files_modified: p['files_modified'] ?? [] }))
525
+ : [];
526
+ const reqMatch = record.section.match(/^\*\*Requirements:?\*\*[^\S\n]*:?[^\S\n]*([^\n]*)$/m);
527
+ const requirements = reqMatch ? reqMatch[1].replace(/[[\]]/g, '').split(',').map((s) => s.trim()).filter((s) => s !== '' && s !== 'TBD') : [];
528
+ const proposal = (0, phase_tier_cjs_1.proposePhaseTier)({ plans: planRows, requirements, roadmapText: record.section });
529
+ let recorded = null;
530
+ if (doRecord) {
531
+ recorded = parsed.tier !== null
532
+ ? (0, phase_tier_cjs_1.recordPhaseTier)(cwd, phaseArg, parsed.tier, { proposed: false })
533
+ : (0, phase_tier_cjs_1.recordPhaseTier)(cwd, phaseArg, proposal.tier, { proposed: true, reason: proposal.reason });
534
+ }
535
+ const after = (0, phase_tier_cjs_1.resolvePhaseTierForProject)(cwd, phaseArg, parsed.tier);
536
+ output({
537
+ phase: phaseArg,
538
+ proposal,
539
+ phase_record: record.tier !== null ? { tier: record.tier, proposed: record.proposed } : null,
540
+ recorded,
541
+ effective: { tier: after.tier, source: after.source, suppressed: after.suppressed },
542
+ }, raw);
543
+ }
544
+ function buildPhasePlanIndex(cwd, phase) {
402
545
  const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
403
546
  const normalized = normalizePhaseName(phase);
404
547
  let phaseDir = null;
@@ -419,8 +562,11 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
419
562
  // phases dir doesn't exist
420
563
  }
421
564
  if (!phaseDir) {
422
- output({ phase: normalized, error: 'Phase not found', plans: [], waves: {}, incomplete: [], has_checkpoints: false }, raw);
423
- return;
565
+ return {
566
+ result: { phase: normalized, error: 'Phase not found', plans: [], waves: {}, incomplete: [], has_checkpoints: false },
567
+ phaseDir: null,
568
+ extras: new Map(),
569
+ };
424
570
  }
425
571
  void phaseDirName; // used only to set phaseDir above
426
572
  const phaseFiles = node_fs_1.default.readdirSync(phaseDir);
@@ -442,6 +588,13 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
442
588
  const xmlTasks = content.match(/<task[\s>]/gi) || [];
443
589
  const mdTasks = content.match(/##\s*Task\s*\d+/gi) || [];
444
590
  const taskCount = xmlTasks.length || mdTasks.length;
591
+ const taskTypes = xmlTasks.length > 0
592
+ ? [...content.matchAll(/<task(\s[^>]*)?>/gi)].map((m) => /type=["']?([\w:-]+)/i.exec(m[1] ?? '')?.[1] ?? 'auto')
593
+ : mdTasks.map(() => 'auto');
594
+ const fmString = (key) => {
595
+ const value = fm[key];
596
+ return typeof value === 'string' && value.trim() !== '' ? value.trim() : null;
597
+ };
445
598
  const parsedWave = parseInt(fm['wave'], 10);
446
599
  const declaredWave = Number.isNaN(parsedWave) ? null : parsedWave;
447
600
  let dependsOn = [];
@@ -464,6 +617,10 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
464
617
  filesModified = Array.isArray(fmFiles) ? fmFiles.map(String) : [String(fmFiles)];
465
618
  }
466
619
  const hasSummary = completedPlanIds.has(planId) || completedPlanIds.has(extractCanonicalPlanId(planFile));
620
+ const fmReqs = fm['requirements'];
621
+ const requirements = (Array.isArray(fmReqs) ? fmReqs.map(String) : typeof fmReqs === 'string' && fmReqs.trim() !== '' ? fmReqs.split(/[\s,]+/) : [])
622
+ .map((r) => r.trim())
623
+ .filter((r) => r !== '' && r !== '[]');
467
624
  rawPlans.push({
468
625
  id: planId,
469
626
  declaredWave,
@@ -473,6 +630,13 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
473
630
  filesModified,
474
631
  taskCount,
475
632
  hasSummary,
633
+ title: (0, plan_graph_cjs_1.readPlanTitle)(fm['title']),
634
+ serialGroup: fmString('serial_group'),
635
+ chainReason: fmString('chain_reason'),
636
+ taskTypes,
637
+ requirements,
638
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string -- FrontmatterValue scalar-to-string
639
+ gapClosure: String(fm['gap_closure'] ?? '').trim() === 'true',
476
640
  });
477
641
  }
478
642
  // ── Pass 2: topological level assignment via depends_on DAG ──────────────
@@ -481,18 +645,31 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
481
645
  const lower = p.id.toLowerCase();
482
646
  const existing = seenLower.get(lower);
483
647
  if (existing !== undefined) {
484
- error(`depends_on index collision in phase ${normalized}: plan IDs '${existing}' and '${p.id}' are identical when case-folded. Rename one file to avoid ambiguous dependency resolution.`);
485
- return;
648
+ return {
649
+ fatal: `depends_on index collision in phase ${normalized}: plan IDs '${existing}' and '${p.id}' are identical when case-folded. Rename one file to avoid ambiguous dependency resolution.`,
650
+ };
486
651
  }
487
652
  seenLower.set(lower, p.id);
488
653
  }
489
654
  const planMap = new Map(rawPlans.map((p) => [p.id.toLowerCase(), p]));
490
655
  const canonicalToId = new Map(rawPlans.map((p) => [extractCanonicalPlanId(p.id).toLowerCase(), p.id]));
491
- const { level, visited } = computeDependencyLevels(rawPlans, planMap, canonicalToId);
492
- if (visited < rawPlans.length) {
493
- const cycleNodes = rawPlans.filter((p) => !level.has(p.id)).map((p) => p.id);
494
- error(`depends_on cycle detected in phase ${normalized} — cycle involves: ${cycleNodes.join(', ')}`);
495
- return;
656
+ // Wave + serial-group assignment and chain metrics: single owner is plan-graph.cts.
657
+ const resolveDep = (dep) => {
658
+ const lower = String(dep).toLowerCase();
659
+ return planMap.has(lower) ? planMap.get(lower).id : canonicalToId.get(lower);
660
+ };
661
+ const graph = (0, plan_graph_cjs_1.computePlanGraph)(rawPlans.map((p) => ({
662
+ id: p.id,
663
+ title: p.title,
664
+ dependsOn: p.dependsOn.map(resolveDep).filter((d) => d !== undefined),
665
+ filesModified: p.filesModified,
666
+ serialGroup: p.serialGroup,
667
+ chainReason: p.chainReason,
668
+ taskTypes: p.taskTypes,
669
+ })));
670
+ if (graph.cycle) {
671
+ const cycleNodes = graph.cycle;
672
+ return { fatal: `depends_on cycle detected in phase ${normalized} — cycle involves: ${cycleNodes.join(', ')}` };
496
673
  }
497
674
  // ── Pass 3: determine lowest bucket key and build output ─────────────────
498
675
  const anyWaveZero = rawPlans.some((p) => p.declaredWave === 0);
@@ -509,14 +686,16 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
509
686
  if (!rawPlan.hasSummary) {
510
687
  incomplete.push(rawPlan.id);
511
688
  }
512
- const computedWave = (level.get(rawPlan.id) ?? 0) + levelOffset;
689
+ const computedWave = (graph.level[rawPlan.id] ?? 0) + levelOffset;
513
690
  const effectiveWave = computedWave;
514
691
  if (rawPlan.declaredWave !== null && rawPlan.declaredWave !== computedWave) {
515
692
  warnings.push(`Plan ${rawPlan.id}: declared wave: ${rawPlan.declaredWave} but depends_on DAG places it in wave ${computedWave}`);
516
693
  }
517
694
  const plan = {
518
695
  id: rawPlan.id,
696
+ title: rawPlan.title,
519
697
  wave: effectiveWave,
698
+ serial_group: graph.serialGroupOf[rawPlan.id] ?? null,
520
699
  depends_on: rawPlan.dependsOn.map((dep) => {
521
700
  const lower = String(dep).toLowerCase();
522
701
  return planMap.has(lower) ? planMap.get(lower).id : dep;
@@ -538,14 +717,28 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
538
717
  phase: normalized,
539
718
  plans,
540
719
  waves,
720
+ serial_groups: Object.fromEntries(graph.serialGroups.map((g) => [g.name, { wave: g.level + levelOffset, plans: g.plans, shared_files: g.shared_files }])),
721
+ graph: {
722
+ plan_count: graph.plan_count,
723
+ wave_count: graph.wave_count,
724
+ longest_chain: graph.longest_chain,
725
+ chain_check: graph.chain_check,
726
+ implicit_edges: graph.implicitEdges,
727
+ merge_candidates: graph.merge_candidates,
728
+ },
541
729
  incomplete,
542
730
  has_checkpoints: hasCheckpoints,
543
731
  };
732
+ warnings.push(...graph.warnings);
544
733
  if (planNamingWarning)
545
734
  result['warning'] = planNamingWarning;
546
735
  if (warnings.length > 0)
547
736
  result['warnings'] = warnings;
548
- output(result, raw);
737
+ return {
738
+ result,
739
+ phaseDir,
740
+ extras: new Map(rawPlans.map((p) => [p.id, { requirements: p.requirements, gapClosure: p.gapClosure }])),
741
+ };
549
742
  }
550
743
  function cmdPhaseAdd(cwd, description, raw, customId) {
551
744
  if (!description) {
@@ -986,8 +1179,10 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
986
1179
  }
987
1180
  updateRoadmapAfterPhaseRemoval(roadmapPath, targetPhase, isDecimal, parseInt(normalized, 10), cwd);
988
1181
  const statePath = node_path_1.default.join(planningDir(cwd), 'STATE.md');
1182
+ // 261007-ixt D-3: name every frontmatter key the shared writer changed.
1183
+ let stateFrontmatterUpdated = [];
989
1184
  if (node_fs_1.default.existsSync(statePath)) {
990
- readModifyWriteStateMd(statePath, (stateContent) => {
1185
+ stateFrontmatterUpdated = readModifyWriteStateMd(statePath, (stateContent) => {
991
1186
  const totalRaw = stateExtractField(stateContent, 'Total Phases');
992
1187
  if (totalRaw) {
993
1188
  stateContent =
@@ -999,7 +1194,7 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
999
1194
  stateContent = stateContent.replace(/(\bof\s+)(\d+)(\s*(?:\(|phases?))/i, `$1${parseInt(ofMatch[2], 10) - 1}$3`);
1000
1195
  }
1001
1196
  return stateContent;
1002
- }, cwd);
1197
+ }, cwd).frontmatter_updated;
1003
1198
  }
1004
1199
  output({
1005
1200
  removed: targetPhase,
@@ -1008,6 +1203,7 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
1008
1203
  renamed_files: renamedFiles,
1009
1204
  roadmap_updated: true,
1010
1205
  state_updated: node_fs_1.default.existsSync(statePath),
1206
+ state_frontmatter_updated: stateFrontmatterUpdated,
1011
1207
  }, raw);
1012
1208
  }
1013
1209
  function writePlanningFileSet(writes) {
@@ -1064,7 +1260,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
1064
1260
  const statePath = node_path_1.default.join(planningDir(cwd), 'STATE.md');
1065
1261
  const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
1066
1262
  const today = clock_cjs_1.realClock.today();
1067
- const phaseInfoRaw = findPhaseInternal(cwd, phaseNum);
1263
+ const phaseInfoRaw = resolvePhaseForRead(cwd, phaseNum);
1068
1264
  if (!phaseInfoRaw) {
1069
1265
  error(`Phase ${phaseNum} not found`);
1070
1266
  }
@@ -1113,6 +1309,8 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
1113
1309
  let nextPhaseNum = null;
1114
1310
  let nextPhaseName = null;
1115
1311
  let isLastPhase = true;
1312
+ // 261007-ixt D-3: every STATE.md frontmatter key this transaction changes.
1313
+ let stateFrontmatterUpdated = [];
1116
1314
  withPlanningLock(cwd, () => {
1117
1315
  const runPhaseCompleteTransaction = () => {
1118
1316
  const writes = [];
@@ -1143,7 +1341,16 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
1143
1341
  }
1144
1342
  return '|' + cells.join('|') + '|';
1145
1343
  });
1146
- const planCountPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`, 'i');
1344
+ // D-08-33: the Plans summary line is resolved through the ONE builder in
1345
+ // src/roadmap-parser.cjs. The inline pattern that stood here knew a
1346
+ // single spelling (`**Plans:**`), had no phase-number boundary and
1347
+ // scanned without a bound — so on a CANONICAL roadmap (`**Plans**:`,
1348
+ // colon outside the bold) it walked out of this phase's section and
1349
+ // wrote `N/N plans complete` into a LATER phase, asserting a build that
1350
+ // had not happened (UAT F2, measured on the live fixture). Only the
1351
+ // pattern moved: the replacement string, the unscoped `.replace` and
1352
+ // this transaction's write set are unchanged (D-08-13/14).
1353
+ const planCountPattern = buildPlansSummaryLinePattern(phaseNum);
1147
1354
  roadmapContent = roadmapContent.replace(planCountPattern, `$1${summaryCount}/${planCount} plans complete`);
1148
1355
  const phaseInfoSummaries = phaseInfo['summaries'];
1149
1356
  for (const summaryFile of phaseInfoSummaries) {
@@ -1370,7 +1577,21 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
1370
1577
  }
1371
1578
  }
1372
1579
  stateContent = updatePerformanceMetricsSection(stateContent, cwd, phaseNum, planCount, summaryCount);
1373
- stateContent = syncStateFrontmatter(stateContent, cwd);
1580
+ // 261007-ixt D-1: completed_phases is capped by the ROADMAP progress table,
1581
+ // and this transaction is flipping this phase's row to Complete in the same
1582
+ // file set — so the writer counts from the roadmap being written, not the
1583
+ // one still on disk.
1584
+ // D-10: this transaction sets Last activity itself; that explicit date flows.
1585
+ const activityFieldSet = (0, state_document_cjs_1.extractPositionalField)(stateContent, 'Last Activity', 'Last activity') !==
1586
+ (0, state_document_cjs_1.extractPositionalField)(originalStateContent, 'Last Activity', 'Last activity');
1587
+ // 261007-rly D-11: phase complete opts OUT of the stopped_at hold and keeps
1588
+ // re-deriving the key from the Session line, as it did before the hold moved
1589
+ // into syncStateFrontmatter: bug #3517's pin (tests/phase.test.cjs) needs a
1590
+ // newer Session line to replace a stale stored value here, the opposite of
1591
+ // the operator-note shape the hold protects. Filed, not decided here:
1592
+ // todos/pending/phase-complete-rederives-stopped-at-from-the-session-line.md.
1593
+ stateContent = syncStateFrontmatter(stateContent, cwd, roadmapContent, activityFieldSet, true);
1594
+ stateFrontmatterUpdated = (0, state_document_cjs_1.diffFrontmatterFields)(extractFrontmatter(originalStateContent), extractFrontmatter(stateContent));
1374
1595
  writes.push({ filePath: statePath, before: originalStateContent, after: stateContent });
1375
1596
  }
1376
1597
  writePlanningFileSet(writes);
@@ -1410,6 +1631,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
1410
1631
  date: today,
1411
1632
  roadmap_updated: node_fs_1.default.existsSync(roadmapPath),
1412
1633
  state_updated: node_fs_1.default.existsSync(statePath),
1634
+ state_frontmatter_updated: stateFrontmatterUpdated,
1413
1635
  requirements_updated: requirementsUpdated,
1414
1636
  auto_pruned: autoPruned,
1415
1637
  warnings,
@@ -1421,7 +1643,7 @@ function cmdPhaseUatPassed(cwd, phaseNum, raw, opts = {}) {
1421
1643
  if (!phaseNum) {
1422
1644
  error('phase number required for phase uat-passed');
1423
1645
  }
1424
- const phaseInfoRaw = findPhaseInternal(cwd, phaseNum);
1646
+ const phaseInfoRaw = resolvePhaseForRead(cwd, phaseNum);
1425
1647
  if (!phaseInfoRaw) {
1426
1648
  error(`Phase ${phaseNum} not found`);
1427
1649
  }
@@ -1435,6 +1657,10 @@ module.exports = {
1435
1657
  cmdPhaseNextDecimal,
1436
1658
  cmdFindPhase,
1437
1659
  cmdPhasePlanIndex,
1660
+ cmdPhaseDispatchPlan,
1661
+ cmdPhaseInstallRun,
1662
+ cmdPhaseTier,
1663
+ cmdPhaseTierPropose,
1438
1664
  cmdPhaseAdd,
1439
1665
  cmdPhaseAddBatch,
1440
1666
  cmdPhaseMvpMode,
@@ -35,6 +35,8 @@ function routePhasesCommand({ phase, milestone, args, cwd, raw, error }) {
35
35
  phase.cmdPhasesList(cwd, options, raw);
36
36
  },
37
37
  clear: () => milestone.cmdPhasesClear(cwd, raw, args.slice(2)),
38
+ // 261007-mmz: the closed-milestone mover new-milestone.md --reset-phase-numbers calls.
39
+ 'archive-closed': () => milestone.cmdPhasesArchiveClosed(cwd, raw, args.slice(2)),
38
40
  },
39
41
  });
40
42
  }