@pareto4/pp-core 1.11.1 → 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 (255) 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 +70 -6
  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 +93 -4
  59. package/pp-core/bin/lib/init.cjs +124 -134
  60. package/pp-core/bin/lib/install-profiles.cjs +5 -0
  61. package/pp-core/bin/lib/install-run.cjs +536 -0
  62. package/pp-core/bin/lib/io.cjs +15 -0
  63. package/pp-core/bin/lib/loop-resolver.cjs +24 -0
  64. package/pp-core/bin/lib/milestone.cjs +345 -48
  65. package/pp-core/bin/lib/phase-command-router.cjs +23 -1
  66. package/pp-core/bin/lib/phase-locator.cjs +15 -6
  67. package/pp-core/bin/lib/phase-scope.cjs +91 -0
  68. package/pp-core/bin/lib/phase-tier.cjs +467 -0
  69. package/pp-core/bin/lib/phase.cjs +247 -21
  70. package/pp-core/bin/lib/phases-command-router.cjs +2 -0
  71. package/pp-core/bin/lib/plan-graph.cjs +368 -0
  72. package/pp-core/bin/lib/planning-workspace.cjs +53 -0
  73. package/pp-core/bin/lib/platform-bindings.cjs +32 -2
  74. package/pp-core/bin/lib/profile-output.cjs +11 -3
  75. package/pp-core/bin/lib/prohibition-enforcement.cjs +112 -0
  76. package/pp-core/bin/lib/roadmap-parser.cjs +366 -99
  77. package/pp-core/bin/lib/roadmap.cjs +141 -39
  78. package/pp-core/bin/lib/runtime-hooks-surface.cjs +1 -1
  79. package/pp-core/bin/lib/shell-command-projection.cjs +24 -0
  80. package/pp-core/bin/lib/state-command-router.cjs +82 -13
  81. package/pp-core/bin/lib/state-document.cjs +124 -7
  82. package/pp-core/bin/lib/state.cjs +784 -257
  83. package/pp-core/bin/lib/template.cjs +22 -6
  84. package/pp-core/bin/lib/vault-command-router.cjs +57 -6
  85. package/pp-core/bin/lib/vault-practices.cjs +6 -36
  86. package/pp-core/bin/lib/vault-write-back.cjs +20 -4
  87. package/pp-core/bin/lib/vault.cjs +555 -23
  88. package/pp-core/bin/lib/verify.cjs +53 -31
  89. package/pp-core/bin/lib/workstream.cjs +1 -1
  90. package/pp-core/bin/lib/worktree-safety.cjs +26 -15
  91. package/pp-core/bin/portable-timeout.sh +89 -0
  92. package/pp-core/bin/pp-tools.cjs +149 -9
  93. package/pp-core/bin/shared/config-defaults.manifest.json +7 -0
  94. package/pp-core/bin/shared/config-schema.manifest.json +3 -0
  95. package/pp-core/references/autonomous-best-effort.md +461 -0
  96. package/pp-core/references/autonomous-lifecycle.md +146 -0
  97. package/pp-core/references/autonomous-smart-discuss.md +16 -12
  98. package/pp-core/references/checkpoints.md +1 -1
  99. package/pp-core/references/discovery-id-conventions.md +202 -0
  100. package/pp-core/references/discovery-rebaseline.md +254 -0
  101. package/pp-core/references/discovery-research-track.md +283 -0
  102. package/pp-core/references/discovery-traceability.md +400 -0
  103. package/pp-core/references/loop-hook-dispatch.md +7 -0
  104. package/pp-core/references/mandatory-initial-read.md +1 -1
  105. package/pp-core/references/planner-gap-closure.md +1 -0
  106. package/pp-core/references/planner-human-verify-mode.md +1 -1
  107. package/pp-core/references/planner-load-graph-context.md +1 -1
  108. package/pp-core/references/planner-plan-graph.md +66 -0
  109. package/pp-core/references/planner-tdd.md +1 -0
  110. package/pp-core/references/planning-config.md +8 -0
  111. package/pp-core/references/sketch-tooling.md +4 -0
  112. package/pp-core/references/tdd.md +1 -0
  113. package/pp-core/templates/claude-md.md +1 -1
  114. package/pp-core/templates/phase-prompt.md +6 -2
  115. package/pp-core/templates/prototype-review/review.js +303 -0
  116. package/pp-core/templates/state.md +6 -0
  117. package/pp-core/workflows/_runtime-launcher.snippet.sh +1 -1
  118. package/pp-core/workflows/add-backlog.md +1 -1
  119. package/pp-core/workflows/add-phase.md +1 -1
  120. package/pp-core/workflows/add-tests.md +1 -1
  121. package/pp-core/workflows/add-todo.md +1 -1
  122. package/pp-core/workflows/ai-integration-phase.md +2 -2
  123. package/pp-core/workflows/audit-fix.md +12 -16
  124. package/pp-core/workflows/audit-milestone.md +1 -1
  125. package/pp-core/workflows/audit-uat.md +1 -1
  126. package/pp-core/workflows/autonomous.md +68 -306
  127. package/pp-core/workflows/check-todos.md +1 -1
  128. package/pp-core/workflows/cleanup.md +1 -1
  129. package/pp-core/workflows/code-review-fix.md +1 -1
  130. package/pp-core/workflows/code-review.md +19 -7
  131. package/pp-core/workflows/complete-milestone.md +41 -60
  132. package/pp-core/workflows/debug.md +1 -1
  133. package/pp-core/workflows/diagnose-issues.md +1 -1
  134. package/pp-core/workflows/discovery-sprint/gap-analysis.md +140 -0
  135. package/pp-core/workflows/discovery-sprint/input-package.md +140 -0
  136. package/pp-core/workflows/discovery-sprint/prefill.md +92 -0
  137. package/pp-core/workflows/discovery-sprint/prototype.md +257 -0
  138. package/pp-core/workflows/discovery-sprint/rebaseline.md +276 -0
  139. package/pp-core/workflows/discovery-sprint/session-13-gates.md +24 -0
  140. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +92 -0
  141. package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +98 -0
  142. package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +215 -0
  143. package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +146 -0
  144. package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +98 -0
  145. package/pp-core/workflows/discovery-sprint/sessions/06-security.md +107 -0
  146. package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +98 -0
  147. package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +85 -0
  148. package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +109 -0
  149. package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +119 -0
  150. package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +92 -0
  151. package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +98 -0
  152. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +205 -0
  153. package/pp-core/workflows/discovery-sprint/templates/context-template.md +266 -0
  154. package/pp-core/workflows/discovery-sprint.md +79 -1714
  155. package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +5 -5
  156. package/pp-core/workflows/discovery-to-build/steps/screen-inventory-gate.md +70 -0
  157. package/pp-core/workflows/discovery-to-build/templates/discovery-references.md +54 -0
  158. package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +184 -0
  159. package/pp-core/workflows/discovery-to-build.md +21 -238
  160. package/pp-core/workflows/discuss-phase/modes/advisor.md +1 -1
  161. package/pp-core/workflows/discuss-phase/modes/auto.md +1 -1
  162. package/pp-core/workflows/discuss-phase/modes/chain.md +1 -1
  163. package/pp-core/workflows/discuss-phase-assumptions.md +1 -1
  164. package/pp-core/workflows/discuss-phase.md +9 -2
  165. package/pp-core/workflows/do.md +4 -4
  166. package/pp-core/workflows/docs-update.md +1 -1
  167. package/pp-core/workflows/edit-phase.md +1 -1
  168. package/pp-core/workflows/eval-review.md +1 -1
  169. package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +45 -0
  170. package/pp-core/workflows/execute-phase/steps/client-context-gate.md +1 -1
  171. package/pp-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  172. package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +90 -0
  173. package/pp-core/workflows/execute-phase/steps/install-run.md +29 -0
  174. package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +23 -0
  175. package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +76 -77
  176. package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +72 -0
  177. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +5 -5
  178. package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +19 -112
  179. package/pp-core/workflows/execute-phase.md +102 -216
  180. package/pp-core/workflows/execute-plan.md +6 -2
  181. package/pp-core/workflows/explore.md +1 -1
  182. package/pp-core/workflows/extract-learnings.md +1 -1
  183. package/pp-core/workflows/forensics.md +4 -4
  184. package/pp-core/workflows/graduation.md +1 -1
  185. package/pp-core/workflows/health.md +1 -1
  186. package/pp-core/workflows/help/modes/brief.md +1 -0
  187. package/pp-core/workflows/help/modes/default.md +2 -1
  188. package/pp-core/workflows/help/modes/full.md +12 -5
  189. package/pp-core/workflows/import.md +1 -1
  190. package/pp-core/workflows/ingest-docs.md +1 -1
  191. package/pp-core/workflows/init.md +18 -2
  192. package/pp-core/workflows/insert-phase.md +1 -1
  193. package/pp-core/workflows/list-workspaces.md +1 -1
  194. package/pp-core/workflows/manager.md +3 -3
  195. package/pp-core/workflows/map-codebase.md +2 -2
  196. package/pp-core/workflows/milestone-summary.md +4 -4
  197. package/pp-core/workflows/mvp-phase.md +1 -1
  198. package/pp-core/workflows/new-milestone.md +7 -8
  199. package/pp-core/workflows/new-project.md +6 -1
  200. package/pp-core/workflows/new-workspace.md +1 -1
  201. package/pp-core/workflows/next.md +2 -2
  202. package/pp-core/workflows/note.md +2 -2
  203. package/pp-core/workflows/pause-work.md +1 -1
  204. package/pp-core/workflows/plan-milestone-gaps.md +1 -1
  205. package/pp-core/workflows/plan-phase/steps/chunked-planning.md +111 -0
  206. package/pp-core/workflows/plan-phase/steps/phase-tier.md +34 -0
  207. package/pp-core/workflows/plan-phase.md +43 -167
  208. package/pp-core/workflows/plan-review-convergence.md +2 -2
  209. package/pp-core/workflows/plant-seed.md +1 -1
  210. package/pp-core/workflows/pr-branch.md +1 -1
  211. package/pp-core/workflows/profile-user.md +1 -1
  212. package/pp-core/workflows/progress.md +3 -3
  213. package/pp-core/workflows/quick.md +3 -1
  214. package/pp-core/workflows/remove-phase.md +1 -1
  215. package/pp-core/workflows/remove-workspace.md +1 -1
  216. package/pp-core/workflows/resume-project.md +6 -12
  217. package/pp-core/workflows/review.md +1 -1
  218. package/pp-core/workflows/scan.md +1 -1
  219. package/pp-core/workflows/secure-phase.md +1 -1
  220. package/pp-core/workflows/settings-advanced.md +1 -1
  221. package/pp-core/workflows/settings-integrations.md +1 -1
  222. package/pp-core/workflows/settings.md +1 -1
  223. package/pp-core/workflows/ship.md +25 -8
  224. package/pp-core/workflows/sketch-wrap-up.md +1 -1
  225. package/pp-core/workflows/sketch.md +2 -1
  226. package/pp-core/workflows/spec-phase.md +1 -1
  227. package/pp-core/workflows/spike-wrap-up.md +1 -1
  228. package/pp-core/workflows/spike.md +1 -1
  229. package/pp-core/workflows/stats.md +2 -2
  230. package/pp-core/workflows/thread.md +1 -1
  231. package/pp-core/workflows/transition.md +5 -9
  232. package/pp-core/workflows/ui-phase.md +23 -2
  233. package/pp-core/workflows/ui-review.md +1 -1
  234. package/pp-core/workflows/ultraplan-phase.md +2 -2
  235. package/pp-core/workflows/validate-phase.md +1 -1
  236. package/pp-core/workflows/vault-write-back.md +1 -1
  237. package/pp-core/workflows/verify-phase.md +29 -26
  238. package/pp-core/workflows/verify-work.md +14 -10
  239. package/scripts/base64-scan.sh +7 -41
  240. package/scripts/changeset/cli.cjs +25 -5
  241. package/scripts/dev-env.cjs +462 -0
  242. package/scripts/lib/allowlist-ratchet.cjs +103 -1
  243. package/scripts/lib/size-budget.cjs +311 -0
  244. package/scripts/lint-neutrality-names.cjs +694 -0
  245. package/scripts/lint-org-coordinates.allowlist.json +35 -0
  246. package/scripts/lint-org-coordinates.cjs +426 -0
  247. package/scripts/lint-skill-deps.cjs +48 -9
  248. package/scripts/lint-test-file-count.cjs +17 -2
  249. package/scripts/prompt-injection-scan.sh +41 -20
  250. package/scripts/sync-manifest-versions.cjs +41 -7
  251. package/scripts/sync-runtime-launcher.cjs +106 -1
  252. package/scripts/update-size-baseline.cjs +274 -30
  253. package/scripts/workflow-size.cjs +61 -9
  254. package/commands/pp-build-handoff.md +0 -46
  255. package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +0 -94
@@ -0,0 +1,472 @@
1
+ "use strict";
2
+ /**
3
+ * Dispatch plan — the single owner of what execute-phase spawns and runs.
4
+ *
5
+ * From the plan inventory (`phase-plan-index`: waves and serial groups from
6
+ * src/plan-graph.cts) and the project's isolation settings, it computes, per
7
+ * wave and in order:
8
+ *
9
+ * - the executor spawns: ONE spawn per serial group, on the main checkout
10
+ * (no worktree), listing its plans in run order; one spawn per other plan,
11
+ * isolated in a worktree unless worktrees are off or the plan touches a
12
+ * git submodule path (the rule execute-phase applied per plan before);
13
+ * - whether each spawn needs a build: only when one of its plans touches a
14
+ * source file (`isSourceFile`, the plan-graph predicate);
15
+ * - the merge step, then exactly ONE full-suite run by the orchestrator after
16
+ * the merge, then exactly ONE STATE/ROADMAP/REQUIREMENTS tracking commit by
17
+ * the orchestrator;
18
+ * - planning defects: a source file shared by two plans of one wave, or any
19
+ * file shared by two plans in different spawns. A wave with a defect runs
20
+ * its spawns one at a time.
21
+ *
22
+ * Totals: spawn count, suite-run count, tracking-commit count, build count and
23
+ * the SUMMARY.md files the phase is expected to produce.
24
+ *
25
+ * There is no spawn trace: workflows are markdown a model follows. This plan
26
+ * is what execute-phase spawns ("spawn exactly the listed spawns"), so it is
27
+ * the dry run that tests count.
28
+ *
29
+ * ## Stages
30
+ *
31
+ * Output is shaped as `stages: { plan, execute, verify }`. `stages.plan` and
32
+ * `stages.verify` come from the phase's resolved ceremony tier
33
+ * (`resolvePhaseTier` in src/phase-tier.cts, laid out by `computeTierStages`
34
+ * here): researcher, pattern mapper,
35
+ * planner (always) and plan checker passes; then code reviewer and fixer,
36
+ * the heavy-tier install run (`phase.install-run`, src/install-run.cts),
37
+ * verifier, security auditor and nyquist auditor. `totals.spawns` counts every agent spawn of every stage, as an upper
38
+ * bound: entries marked `conditional` run only when the step before them asks
39
+ * for it (a revision after a failed check, a fix after review findings).
40
+ */
41
+ var __importDefault = (this && this.__importDefault) || function (mod) {
42
+ return (mod && mod.__esModule) ? mod : { "default": mod };
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.STAGE_AGENTS = void 0;
46
+ exports.computeTierStages = computeTierStages;
47
+ exports.submoduleIntersection = submoduleIntersection;
48
+ exports.parseGitmodulesPaths = parseGitmodulesPaths;
49
+ exports.computeDispatchPlan = computeDispatchPlan;
50
+ exports.parseDispatchPlanArgs = parseDispatchPlanArgs;
51
+ exports.resolveDispatchPolicy = resolveDispatchPolicy;
52
+ const node_fs_1 = __importDefault(require("node:fs"));
53
+ const node_path_1 = __importDefault(require("node:path"));
54
+ const plan_graph_cjs_1 = require("./plan-graph.cjs");
55
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
56
+ const planningWorkspace = require("./planning-workspace.cjs");
57
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- config-loader.cjs is an export= CommonJS module
58
+ const configLoaderMod = require("./config-loader.cjs");
59
+ const { planningDir } = planningWorkspace;
60
+ const { loadConfig } = configLoaderMod;
61
+ /** Agent type names the stages spawn (each is a file under agents/). */
62
+ exports.STAGE_AGENTS = Object.freeze({
63
+ researcher: 'pp-phase-researcher',
64
+ patternMapper: 'pp-pattern-mapper',
65
+ planner: 'pp-planner',
66
+ planChecker: 'pp-plan-checker',
67
+ verifier: 'pp-verifier',
68
+ codeReviewer: 'pp-code-reviewer',
69
+ codeFixer: 'pp-code-fixer',
70
+ nyquistAuditor: 'pp-nyquist-auditor',
71
+ securityAuditor: 'pp-security-auditor',
72
+ });
73
+ function spawn(id, agent, gate, reason, conditional = false) {
74
+ return { id, kind: 'spawn', agent, gate, reason, conditional };
75
+ }
76
+ function step(id, gate, reason) {
77
+ return { id, kind: 'step', agent: null, gate, reason, conditional: false };
78
+ }
79
+ /**
80
+ * The planning and verification stages of a phase, from its resolved tier.
81
+ * `planBounceScript`: bounce runs only with a configured script.
82
+ */
83
+ function computeTierStages(tier, opts = {}) {
84
+ const g = tier.gates;
85
+ const plan = [];
86
+ if (g.research)
87
+ plan.push(spawn('plan-research', exports.STAGE_AGENTS.researcher, 'research', 'workflow.research is on for this tier'));
88
+ if (g.pattern_mapper)
89
+ plan.push(spawn('plan-patterns', exports.STAGE_AGENTS.patternMapper, 'pattern_mapper', 'workflow.pattern_mapper is on for this tier'));
90
+ plan.push(spawn('plan-planner', exports.STAGE_AGENTS.planner, 'planner', 'the planner always runs'));
91
+ if (g.plan_check) {
92
+ for (let pass = 1; pass <= g.plan_check_passes; pass++) {
93
+ if (pass > 1) {
94
+ plan.push(spawn(`plan-revise-${pass - 1}`, exports.STAGE_AGENTS.planner, 'plan_check', `revision ${pass - 1}: only when check pass ${pass - 1} found issues`, true));
95
+ }
96
+ plan.push(spawn(`plan-check-${pass}`, exports.STAGE_AGENTS.planChecker, 'plan_check', pass === 1 ? `check pass 1 of at most ${g.plan_check_passes}` : `check pass ${pass} of at most ${g.plan_check_passes}: after revision ${pass - 1}`, pass > 1));
97
+ }
98
+ }
99
+ if (g.plan_bounce && opts.planBounceScript) {
100
+ plan.push(step('plan-bounce', 'plan_bounce', `refine plans through workflow.plan_bounce_script, ${g.plan_bounce_passes} pass(es)`));
101
+ if (g.plan_check)
102
+ plan.push(spawn('plan-bounce-check', exports.STAGE_AGENTS.planChecker, 'plan_bounce', 're-check bounced plans; a plan that fails is restored', true));
103
+ }
104
+ if (g.post_planning_gaps)
105
+ plan.push(step('plan-gaps', 'post_planning_gaps', 'post-planning gap report (plan:post), non-blocking'));
106
+ const verify = [];
107
+ if (g.code_review) {
108
+ verify.push(spawn('verify-code-review', exports.STAGE_AGENTS.codeReviewer, 'code_review', 'code review of the phase source changes'));
109
+ verify.push(spawn('verify-code-fix', exports.STAGE_AGENTS.codeFixer, 'code_review', 'fix loop: only when the review finds issues', true));
110
+ }
111
+ // The install run goes first: the verifier judges the phase on its results file.
112
+ if (g.install_verification)
113
+ verify.push(step('verify-install', 'install_verification', 'pack, install into a temp project, init and run the phase install checks against the installed copy (phase.install-run → NN-INSTALL-RUN.json)'));
114
+ if (g.verifier)
115
+ verify.push(spawn('verify-goal', exports.STAGE_AGENTS.verifier, 'verifier', 'goal-backward phase verification (VERIFICATION.md)'));
116
+ if (g.security_enforcement)
117
+ verify.push(spawn('verify-security', exports.STAGE_AGENTS.securityAuditor, 'security_enforcement', 'threat-model verification (SECURITY.md)'));
118
+ if (g.nyquist_validation)
119
+ verify.push(spawn('verify-nyquist', exports.STAGE_AGENTS.nyquistAuditor, 'nyquist_validation', 'validation-gap audit (VALIDATION.md)'));
120
+ return { plan, verify };
121
+ }
122
+ function normalizePath(file) {
123
+ return String(file).trim().replace(/\\/g, '/').replace(/^\.\//, '');
124
+ }
125
+ function stripEnds(p) {
126
+ return p.replace(/^\.\//, '').replace(/\/$/, '');
127
+ }
128
+ /**
129
+ * Planned paths that touch a submodule (the per-plan worktree rule, #2772):
130
+ * either side normalised (leading `./`, one trailing `/`), matched both ways
131
+ * (planned path inside the submodule, or submodule inside the planned path),
132
+ * and a globbed planned path matched by its literal prefix before the first
133
+ * `*`, `?` or `[`.
134
+ */
135
+ function submoduleIntersection(planFiles, submodulePaths) {
136
+ const hits = new Set();
137
+ const inside = (a, b) => a === b || a.startsWith(`${b}/`);
138
+ for (const smRaw of submodulePaths) {
139
+ const sm = stripEnds(smRaw.trim());
140
+ if (sm === '')
141
+ continue;
142
+ for (const pfRaw of planFiles) {
143
+ const pf = stripEnds(String(pfRaw).trim());
144
+ if (pf === '')
145
+ continue;
146
+ let matched = inside(pf, sm) || inside(sm, pf);
147
+ if (!matched && /[*?[]/.test(pf)) {
148
+ const prefix = pf.slice(0, pf.search(/[*?[]/)).replace(/\/$/, '');
149
+ matched = prefix !== '' && (inside(sm, prefix) || inside(prefix, sm));
150
+ }
151
+ if (matched)
152
+ hits.add(String(pfRaw).trim());
153
+ }
154
+ }
155
+ return [...hits];
156
+ }
157
+ /** Submodule paths declared in a `.gitmodules` file's text. */
158
+ function parseGitmodulesPaths(text) {
159
+ const paths = [];
160
+ let inSubmodule = false;
161
+ for (const line of text.split(/\r?\n/)) {
162
+ const section = /^\s*\[\s*([^\s\]]+)/.exec(line);
163
+ if (section) {
164
+ inSubmodule = section[1].toLowerCase() === 'submodule';
165
+ continue;
166
+ }
167
+ const kv = /^\s*path\s*=\s*(.+?)\s*$/i.exec(line);
168
+ if (inSubmodule && kv)
169
+ paths.push(kv[1].replace(/^"(.*)"$/, '$1'));
170
+ }
171
+ return paths;
172
+ }
173
+ function isolationFor(plan, policy) {
174
+ if (!policy.useWorktrees) {
175
+ return { isolation: 'main-checkout', reason: `worktrees off (workflow.use_worktrees=false, from ${policy.useWorktreesSource})` };
176
+ }
177
+ if (policy.submodulePaths.length > 0) {
178
+ const files = plan.files_modified.map((f) => String(f).trim()).filter((f) => f !== '');
179
+ if (files.length === 0) {
180
+ return { isolation: 'main-checkout', reason: 'files_modified missing/unparseable in a submodule project — no worktree, as a safety fallback' };
181
+ }
182
+ const hits = submoduleIntersection(files, policy.submodulePaths);
183
+ if (hits.length > 0) {
184
+ return { isolation: 'main-checkout', reason: `planned paths intersect submodule paths (${hits.join(' ')})` };
185
+ }
186
+ }
187
+ return { isolation: 'worktree', reason: 'independent plan: its own worktree' };
188
+ }
189
+ function summaryPath(phaseDir, id) {
190
+ return `${phaseDir}/${id === '' ? '' : `${id}-`}SUMMARY.md`;
191
+ }
192
+ function planEntry(plan, phaseDir) {
193
+ const files = plan.files_modified.map(normalizePath).filter((f) => f !== '');
194
+ return {
195
+ id: plan.id,
196
+ title: plan.title,
197
+ build: files.length === 0 || files.some(plan_graph_cjs_1.isSourceFile),
198
+ touched_tests: files.filter((f) => (0, plan_graph_cjs_1.classifyPlanFile)(f) === 'test'),
199
+ summary: summaryPath(phaseDir, plan.id),
200
+ };
201
+ }
202
+ function buildReason(plans) {
203
+ const reasons = [];
204
+ for (const p of plans) {
205
+ const files = p.files_modified.map(normalizePath).filter((f) => f !== '');
206
+ if (files.length === 0)
207
+ reasons.push(`${p.id}: files_modified not declared`);
208
+ else {
209
+ const src = files.filter(plan_graph_cjs_1.isSourceFile);
210
+ if (src.length > 0)
211
+ reasons.push(`${p.id} touches ${src.join(', ')}`);
212
+ }
213
+ }
214
+ return reasons.length > 0 ? reasons.join('; ') : 'no plan touches a source file';
215
+ }
216
+ function waveDefects(spawns, byId) {
217
+ const members = [];
218
+ for (const s of spawns) {
219
+ for (const p of s.plans) {
220
+ const plan = byId.get(p.id);
221
+ members.push({ id: p.id, spawn: s.id, files: new Set(plan.files_modified.map(normalizePath).filter((f) => f !== '')) });
222
+ }
223
+ }
224
+ const defects = [];
225
+ for (let i = 0; i < members.length; i++) {
226
+ for (let j = i + 1; j < members.length; j++) {
227
+ const a = members[i];
228
+ const b = members[j];
229
+ const shared = [...a.files].filter((f) => b.files.has(f)).sort();
230
+ if (shared.length === 0)
231
+ continue;
232
+ const source = shared.filter(plan_graph_cjs_1.isSourceFile);
233
+ if (source.length > 0) {
234
+ defects.push({
235
+ kind: 'source-file-overlap',
236
+ plans: [a.id, b.id],
237
+ files: source,
238
+ message: `Plan ${a.id} and Plan ${b.id} both modify source file(s) ${source.join(', ')} in one wave — a shared source file must order the plans into separate waves (planning defect).`,
239
+ });
240
+ }
241
+ else if (a.spawn !== b.spawn) {
242
+ defects.push({
243
+ kind: 'cross-spawn-overlap',
244
+ plans: [a.id, b.id],
245
+ files: shared,
246
+ message: `Plan ${a.id} and Plan ${b.id} both modify ${shared.join(', ')} but run in different spawns — they belong in one serial group (planning defect).`,
247
+ });
248
+ }
249
+ }
250
+ }
251
+ return defects;
252
+ }
253
+ /**
254
+ * Compute the dispatch plan. `plans` are `phase-plan-index` rows in plan file
255
+ * order; `phaseDir` is the phase directory relative to the project root.
256
+ */
257
+ function computeDispatchPlan(phase, phaseDir, plans, policy, tier = null, tierOpts = {}) {
258
+ const byId = new Map(plans.map((p) => [p.id, p]));
259
+ const skippedComplete = plans.filter((p) => p.has_summary).map((p) => p.id);
260
+ const incomplete = plans.filter((p) => !p.has_summary && (!policy.gapsOnly || p.gap_closure === true));
261
+ const selected = incomplete.filter((p) => policy.waveFilter === null || p.wave === policy.waveFilter);
262
+ const selectedIds = new Set(selected.map((p) => p.id));
263
+ const skippedByFilter = plans.filter((p) => !p.has_summary && !selectedIds.has(p.id)).map((p) => p.id);
264
+ const lowerWaveIncomplete = policy.waveFilter === null
265
+ ? []
266
+ : incomplete.filter((p) => p.wave < policy.waveFilter).map((p) => p.id);
267
+ const waveNumbers = [...new Set(selected.map((p) => p.wave))].sort((a, b) => a - b);
268
+ const waves = [];
269
+ for (const n of waveNumbers) {
270
+ const inWave = selected.filter((p) => p.wave === n);
271
+ const groupCounts = new Map();
272
+ for (const p of inWave)
273
+ if (p.serial_group)
274
+ groupCounts.set(p.serial_group, (groupCounts.get(p.serial_group) ?? 0) + 1);
275
+ const spawns = [];
276
+ const placedGroups = new Set();
277
+ for (const p of inWave) {
278
+ const group = p.serial_group && (groupCounts.get(p.serial_group) ?? 0) >= 2 ? p.serial_group : null;
279
+ if (group !== null) {
280
+ if (placedGroups.has(group))
281
+ continue;
282
+ placedGroups.add(group);
283
+ const members = inWave.filter((q) => q.serial_group === group);
284
+ const entries = members.map((q) => planEntry(q, phaseDir));
285
+ spawns.push({
286
+ id: `w${n}-s${spawns.length + 1}`,
287
+ agent: 'pp-executor',
288
+ kind: 'serial-group',
289
+ serial_group: group,
290
+ plans: entries,
291
+ isolation: 'main-checkout',
292
+ isolation_reason: `serial group ${group}: one executor runs its ${members.length} plans in order on the main checkout`,
293
+ build: entries.some((e) => e.build),
294
+ build_reason: buildReason(members),
295
+ checkpoint: members.some((q) => !q.autonomous),
296
+ expected_summaries: entries.map((e) => e.summary),
297
+ });
298
+ continue;
299
+ }
300
+ const entry = planEntry(p, phaseDir);
301
+ const iso = isolationFor(p, policy);
302
+ spawns.push({
303
+ id: `w${n}-s${spawns.length + 1}`,
304
+ agent: 'pp-executor',
305
+ kind: 'plan',
306
+ serial_group: null,
307
+ plans: [entry],
308
+ isolation: iso.isolation,
309
+ isolation_reason: iso.reason,
310
+ build: entry.build,
311
+ build_reason: buildReason([p]),
312
+ checkpoint: !p.autonomous,
313
+ expected_summaries: [entry.summary],
314
+ });
315
+ }
316
+ const defects = waveDefects(spawns, byId);
317
+ const worktreeSpawns = spawns.filter((s) => s.isolation === 'worktree').length;
318
+ const run = defects.length > 0 || !policy.parallelization || spawns.length < 2 ? 'sequential' : 'parallel';
319
+ const runReason = defects.length > 0
320
+ ? 'planning defect: overlapping files_modified in this wave — run every spawn one at a time'
321
+ : !policy.parallelization
322
+ ? 'parallelization is off'
323
+ : spawns.length < 2
324
+ ? 'one spawn'
325
+ : 'worktree spawns run alongside each other; main-checkout spawns run one at a time';
326
+ const requirements = [...new Set(inWave.flatMap((p) => p.requirements ?? []))];
327
+ const expected = spawns.flatMap((s) => s.expected_summaries);
328
+ waves.push({
329
+ wave: n,
330
+ run,
331
+ run_reason: runReason,
332
+ spawns,
333
+ merge: { worktree_spawns: worktreeSpawns, cleanup: worktreeSpawns > 0 },
334
+ build: spawns.some((s) => s.build),
335
+ suite_runs: 1,
336
+ suite: { by: 'orchestrator', after: 'merge' },
337
+ tracking_commits: 1,
338
+ tracking: { by: 'orchestrator', plans: inWave.map((p) => p.id), requirements },
339
+ expected_summaries: expected,
340
+ defects,
341
+ });
342
+ }
343
+ const stages = tier ? computeTierStages(tier, tierOpts) : { plan: [], verify: [] };
344
+ const stagePlan = stages.plan;
345
+ const stageVerify = stages.verify;
346
+ const planSpawns = stagePlan.filter((e) => e.kind === 'spawn').length;
347
+ const verifySpawns = stageVerify.filter((e) => e.kind === 'spawn').length;
348
+ const executorSpawns = waves.flatMap((w) => w.spawns);
349
+ return {
350
+ phase,
351
+ phase_dir: phaseDir,
352
+ tier: tier ? { tier: tier.tier, source: tier.source, suppressed: tier.suppressed, gates: tier.gates } : null,
353
+ policy: {
354
+ use_worktrees: policy.useWorktrees,
355
+ use_worktrees_source: policy.useWorktreesSource,
356
+ parallelization: policy.parallelization,
357
+ submodule_paths: policy.submodulePaths,
358
+ },
359
+ filters: {
360
+ wave: policy.waveFilter,
361
+ gaps_only: policy.gapsOnly,
362
+ skipped_complete: skippedComplete,
363
+ skipped_by_filter: skippedByFilter,
364
+ lower_wave_incomplete: lowerWaveIncomplete,
365
+ },
366
+ stages: { plan: stagePlan, execute: { waves }, verify: stageVerify },
367
+ totals: {
368
+ waves: waves.length,
369
+ spawns: planSpawns + executorSpawns.length + verifySpawns,
370
+ plan_spawns: planSpawns,
371
+ verify_spawns: verifySpawns,
372
+ executor_spawns: executorSpawns.length,
373
+ main_checkout_spawns: executorSpawns.filter((s) => s.isolation === 'main-checkout').length,
374
+ isolated_spawns: executorSpawns.filter((s) => s.isolation === 'worktree').length,
375
+ builds: executorSpawns.filter((s) => s.build).length,
376
+ suite_runs: waves.reduce((sum, w) => sum + w.suite_runs, 0),
377
+ tracking_commits: waves.reduce((sum, w) => sum + w.tracking_commits, 0),
378
+ expected_summaries: waves.flatMap((w) => w.expected_summaries),
379
+ defects: waves.reduce((sum, w) => sum + w.defects.length, 0),
380
+ },
381
+ };
382
+ }
383
+ /** `<phase> [--wave N] [--gaps-only] [--use-worktrees true|false] [--tier light|standard|heavy]`. */
384
+ function parseDispatchPlanArgs(args) {
385
+ const usage = 'Usage: phase dispatch-plan <phase> [--wave N] [--gaps-only] [--use-worktrees true|false] [--tier light|standard|heavy]';
386
+ let phase = null;
387
+ let waveFilter = null;
388
+ let gapsOnly = false;
389
+ let useWorktrees = null;
390
+ let tier = null;
391
+ for (let i = 0; i < args.length; i++) {
392
+ const a = args[i];
393
+ if (a === '--raw')
394
+ continue;
395
+ if (a === '--gaps-only') {
396
+ gapsOnly = true;
397
+ continue;
398
+ }
399
+ if (a === '--wave') {
400
+ const v = args[++i];
401
+ if (v === undefined || !/^\d+$/.test(v))
402
+ return { error: `--wave needs a wave number. ${usage}` };
403
+ waveFilter = parseInt(v, 10);
404
+ continue;
405
+ }
406
+ if (a === '--use-worktrees') {
407
+ const v = args[++i];
408
+ if (v !== 'true' && v !== 'false')
409
+ return { error: `--use-worktrees needs true or false. ${usage}` };
410
+ useWorktrees = v === 'true';
411
+ continue;
412
+ }
413
+ if (a === '--tier') {
414
+ const v = args[++i];
415
+ if (v !== 'light' && v !== 'standard' && v !== 'heavy')
416
+ return { error: `--tier needs light, standard or heavy. ${usage}` };
417
+ tier = v;
418
+ continue;
419
+ }
420
+ if (a.startsWith('--'))
421
+ return { error: `phase dispatch-plan does not support ${a}. ${usage}` };
422
+ if (phase !== null)
423
+ return { error: `phase dispatch-plan takes one phase. ${usage}` };
424
+ phase = a;
425
+ }
426
+ if (!phase)
427
+ return { error: `phase required for phase dispatch-plan. ${usage}` };
428
+ return { phase, waveFilter, gapsOnly, useWorktrees, tier };
429
+ }
430
+ /** Isolation and concurrency settings: CLI flag, then `.planning/config.json`, then defaults. */
431
+ function resolveDispatchPolicy(cwd, args) {
432
+ let raw = {};
433
+ try {
434
+ raw = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(planningDir(cwd), 'config.json'), 'utf-8'));
435
+ }
436
+ catch {
437
+ raw = {};
438
+ }
439
+ const workflow = (raw['workflow'] && typeof raw['workflow'] === 'object' ? raw['workflow'] : {});
440
+ let useWorktrees = true;
441
+ let useWorktreesSource = 'default';
442
+ if (args.useWorktrees !== null) {
443
+ useWorktrees = args.useWorktrees;
444
+ useWorktreesSource = 'cli';
445
+ }
446
+ else if (workflow['use_worktrees'] !== undefined) {
447
+ useWorktrees = workflow['use_worktrees'] !== false && workflow['use_worktrees'] !== 'false';
448
+ useWorktreesSource = 'config';
449
+ }
450
+ let parallelization = true;
451
+ try {
452
+ parallelization = loadConfig(cwd)['parallelization'] !== false;
453
+ }
454
+ catch {
455
+ parallelization = true;
456
+ }
457
+ let submodulePaths = [];
458
+ try {
459
+ submodulePaths = parseGitmodulesPaths(node_fs_1.default.readFileSync(node_path_1.default.join(cwd, '.gitmodules'), 'utf-8'));
460
+ }
461
+ catch {
462
+ submodulePaths = [];
463
+ }
464
+ return {
465
+ useWorktrees,
466
+ useWorktreesSource,
467
+ parallelization,
468
+ submodulePaths,
469
+ waveFilter: args.waveFilter,
470
+ gapsOnly: args.gapsOnly,
471
+ };
472
+ }
@@ -62,6 +62,11 @@ function extractFrontmatter(content) {
62
62
  const yaml = match[1];
63
63
  const lines = yaml.split(/\r?\n/);
64
64
  const stack = [{ obj: frontmatter, key: null, indent: -1 }];
65
+ // Indent of the last key that took a scalar value. Lines indented deeper than it
66
+ // are orphaned content of that scalar (a block scalar body, or children YAML would
67
+ // reject). Before this guard they fell through to whatever stack frame was open —
68
+ // often the root — so a nested `status:` overwrote the top-level one (260929-iyi G4).
69
+ let scalarIndent = null;
65
70
  for (const line of lines) {
66
71
  // Skip empty lines
67
72
  if (line.trim() === '')
@@ -69,6 +74,11 @@ function extractFrontmatter(content) {
69
74
  // Calculate indentation (number of leading spaces)
70
75
  const indentMatch = line.match(/^(\s*)/);
71
76
  const indent = indentMatch ? indentMatch[1].length : 0;
77
+ if (scalarIndent !== null) {
78
+ if (indent > scalarIndent)
79
+ continue;
80
+ scalarIndent = null;
81
+ }
72
82
  // Pop stack back to appropriate level
73
83
  while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
74
84
  stack.pop();
@@ -78,7 +88,10 @@ function extractFrontmatter(content) {
78
88
  const keyMatch = line.match(/^(\s*)([a-zA-Z0-9_-]+):\s*(.*)/);
79
89
  if (keyMatch) {
80
90
  const key = keyMatch[2];
81
- const value = keyMatch[3].trim();
91
+ // A value that is only a YAML comment (`key: # note`) is an empty value, so
92
+ // the key opens a nested block like a bare `key:` does (260929-iyi G4).
93
+ const rawValue = keyMatch[3].trim();
94
+ const value = rawValue.startsWith('#') ? '' : rawValue;
82
95
  if (value === '' || value === '[') {
83
96
  // Key with no value or opening bracket — could be nested object or array
84
97
  const newObj = value === '[' ? [] : {};
@@ -91,11 +104,13 @@ function extractFrontmatter(content) {
91
104
  // Inline array: key: [a, b, c] — quote-aware split (REG-04 fix)
92
105
  current.obj[key] = splitInlineArray(value.slice(1, -1));
93
106
  current.key = null;
107
+ scalarIndent = indent;
94
108
  }
95
109
  else {
96
110
  // Simple key: value
97
111
  current.obj[key] = value.replace(/^["']|["']$/g, '');
98
112
  current.key = null;
113
+ scalarIndent = indent;
99
114
  }
100
115
  }
101
116
  else if (line.trim().startsWith('- ')) {
@@ -375,7 +390,7 @@ function parseMustHavesBlock(content, blockName) {
375
390
  }
376
391
  // ─── Frontmatter CRUD commands ────────────────────────────────────────────────
377
392
  const FRONTMATTER_SCHEMAS = {
378
- plan: { required: ['phase', 'plan', 'type', 'wave', 'depends_on', 'files_modified', 'autonomous', 'must_haves'] },
393
+ plan: { required: ['phase', 'plan', 'title', 'type', 'wave', 'depends_on', 'files_modified', 'autonomous', 'must_haves'] },
379
394
  summary: { required: ['phase', 'plan', 'subsystem', 'tags', 'duration', 'completed'] },
380
395
  verification: { required: ['phase', 'verified', 'status', 'score'] },
381
396
  };
@@ -30,19 +30,22 @@ function routeInitCommand({ init, args, cwd, raw, error }) {
30
30
  unknownMessage: (_subcommand, available) => `Unknown init workflow: ${_subcommand}\nAvailable: ${available.join(', ')}`,
31
31
  handlers: {
32
32
  'execute-phase': () => {
33
- const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, [], ['validate', 'tdd']);
34
- init.cmdInitExecutePhase(cwd, args[2], raw, { validate: namedArgs['validate'], tdd: namedArgs['tdd'] });
33
+ const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, ['tier'], ['validate', 'tdd']);
34
+ init.cmdInitExecutePhase(cwd, args[2], raw, { validate: namedArgs['validate'], tdd: namedArgs['tdd'], tier: namedArgs['tier'] });
35
35
  },
36
36
  'plan-phase': () => {
37
- const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, ['granularity'], ['validate', 'tdd']);
38
- init.cmdInitPlanPhase(cwd, args[2], raw, { validate: namedArgs['validate'], tdd: namedArgs['tdd'], granularity: namedArgs['granularity'] });
37
+ const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, ['granularity', 'tier'], ['validate', 'tdd']);
38
+ init.cmdInitPlanPhase(cwd, args[2], raw, { validate: namedArgs['validate'], tdd: namedArgs['tdd'], granularity: namedArgs['granularity'], tier: namedArgs['tier'] });
39
39
  },
40
40
  'new-project': () => init.cmdInitNewProject(cwd, raw),
41
41
  'new-milestone': () => init.cmdInitNewMilestone(cwd, raw),
42
42
  quick: () => init.cmdInitQuick(cwd, args.slice(2).join(' '), raw),
43
43
  'ingest-docs': () => init.cmdInitIngestDocs(cwd, raw),
44
44
  resume: () => init.cmdInitResume(cwd, raw),
45
- 'verify-work': () => init.cmdInitVerifyWork(cwd, args[2], raw),
45
+ 'verify-work': () => {
46
+ const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, ['tier'], []);
47
+ init.cmdInitVerifyWork(cwd, args[2], raw, { tier: namedArgs['tier'] });
48
+ },
46
49
  'phase-op': () => init.cmdInitPhaseOp(cwd, args[2], raw),
47
50
  todos: () => init.cmdInitTodos(cwd, args[2], raw),
48
51
  'milestone-op': () => init.cmdInitMilestoneOp(cwd, raw),