@pareto4/pp-core 1.13.0 → 1.18.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 (204) hide show
  1. package/.claude-plugin/plugin.json +3 -3
  2. package/README.md +31 -12
  3. package/agents/pp-code-fixer.md +0 -11
  4. package/agents/pp-code-reviewer.md +1 -77
  5. package/agents/pp-debug-session-manager.md +9 -6
  6. package/agents/pp-executor.md +12 -29
  7. package/agents/pp-plan-checker.md +4 -4
  8. package/agents/pp-planner.md +34 -20
  9. package/agents/pp-vault-reviewer.md +100 -23
  10. package/agents/pp-verifier.md +1 -3
  11. package/bin/install.js +10 -3
  12. package/commands/pp-execute-phase.md +1 -3
  13. package/commands/pp-ns-manage.md +1 -1
  14. package/commands/pp-ns-review.md +1 -1
  15. package/commands/pp-plan-phase.md +1 -2
  16. package/commands/pp-vault-promote.md +7 -8
  17. package/commands/pp-vault-review.md +6 -5
  18. package/commands/pp-verify-work.md +1 -2
  19. package/gemini-extension.json +1 -1
  20. package/hooks/dist/pp-check-update.js +0 -0
  21. package/hooks/dist/pp-cursor-session-start.js +1 -1
  22. package/hooks/dist/pp-statusline.js +1 -1
  23. package/hooks/dist/pp-update-banner.js +0 -0
  24. package/hooks/pp-check-update.js +0 -0
  25. package/hooks/pp-cursor-session-start.js +1 -1
  26. package/hooks/pp-statusline.js +1 -1
  27. package/hooks/pp-update-banner.js +0 -0
  28. package/package.json +10 -18
  29. package/pareto-skills/_shared/create-project-board.sh +0 -0
  30. package/pareto-skills/pp-project-setup/SKILL.md +1 -1
  31. package/pp-core/bin/check-latest-version.cjs +0 -0
  32. package/pp-core/bin/detect-build-commands.sh +2 -2
  33. package/pp-core/bin/lib/capability-activation.cjs +83 -9
  34. package/pp-core/bin/lib/capability-registry.cjs +201 -229
  35. package/pp-core/bin/lib/capability-state.cjs +93 -1
  36. package/pp-core/bin/lib/capability-writer.cjs +3 -3
  37. package/pp-core/bin/lib/check-command-router.cjs +1906 -1514
  38. package/pp-core/bin/lib/clusters.cjs +3 -0
  39. package/pp-core/bin/lib/command-aliases.cjs +0 -35
  40. package/pp-core/bin/lib/commands.cjs +9 -4
  41. package/pp-core/bin/lib/compose-bindings.cjs +251 -88
  42. package/pp-core/bin/lib/config-loader.cjs +7 -103
  43. package/pp-core/bin/lib/config-schema.cjs +1 -1
  44. package/pp-core/bin/lib/config.cjs +151 -62
  45. package/pp-core/bin/lib/decisions.cjs +4 -4
  46. package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
  47. package/pp-core/bin/lib/frontmatter.cjs +1 -38
  48. package/pp-core/bin/lib/git-base-branch.cjs +11 -12
  49. package/pp-core/bin/lib/git-provider.cjs +9 -10
  50. package/pp-core/bin/lib/init-command-router.cjs +5 -8
  51. package/pp-core/bin/lib/init-contract.cjs +620 -738
  52. package/pp-core/bin/lib/init-interactive.cjs +62 -4
  53. package/pp-core/bin/lib/init.cjs +24 -51
  54. package/pp-core/bin/lib/install-profiles.cjs +1 -1
  55. package/pp-core/bin/lib/io.cjs +4 -2
  56. package/pp-core/bin/lib/loop-resolver.cjs +165 -129
  57. package/pp-core/bin/lib/org-profile.cjs +402 -161
  58. package/pp-core/bin/lib/package-identity.cjs +3 -3
  59. package/pp-core/bin/lib/phase-command-router.cjs +1 -29
  60. package/pp-core/bin/lib/phase-scope.cjs +1 -1
  61. package/pp-core/bin/lib/phase.cjs +11 -253
  62. package/pp-core/bin/lib/platform-bindings.cjs +40 -19
  63. package/pp-core/bin/lib/profile-output.cjs +1 -1
  64. package/pp-core/bin/lib/shell-command-projection.cjs +104 -1
  65. package/pp-core/bin/lib/state.cjs +11 -85
  66. package/pp-core/bin/lib/template.cjs +1 -1
  67. package/pp-core/bin/lib/vault-command-router.cjs +1682 -477
  68. package/pp-core/bin/lib/vault-coordinates.cjs +16 -7
  69. package/pp-core/bin/lib/vault-index.cjs +656 -0
  70. package/pp-core/bin/lib/vault-seal.cjs +1851 -0
  71. package/pp-core/bin/lib/vault-standards-promote.cjs +577 -0
  72. package/pp-core/bin/lib/vault-standards-review.cjs +293 -0
  73. package/pp-core/bin/lib/vault-standards.cjs +1504 -0
  74. package/pp-core/bin/lib/vault-trace.cjs +155 -2778
  75. package/pp-core/bin/lib/vault-update.cjs +10 -59
  76. package/pp-core/bin/lib/vault.cjs +2570 -950
  77. package/pp-core/bin/lib/verify.cjs +0 -11
  78. package/pp-core/bin/portable-timeout.sh +1 -1
  79. package/pp-core/bin/pp-tools.cjs +157 -54
  80. package/pp-core/bin/shared/config-defaults.manifest.json +0 -7
  81. package/pp-core/bin/shared/config-schema.manifest.json +0 -3
  82. package/pp-core/bin/shared/model-catalog.json +2 -2
  83. package/pp-core/bin/verify-reapply-patches.cjs +0 -0
  84. package/pp-core/org-profile.json +26 -0
  85. package/pp-core/references/autonomous-best-effort.md +8 -7
  86. package/pp-core/references/loop-hook-dispatch.md +0 -25
  87. package/pp-core/references/planner-gap-closure.md +0 -1
  88. package/pp-core/references/planner-tdd.md +0 -1
  89. package/pp-core/references/planner-vault-standards.md +311 -0
  90. package/pp-core/references/planning-config.md +0 -8
  91. package/pp-core/references/tdd.md +0 -1
  92. package/pp-core/templates/README.md +1 -0
  93. package/pp-core/templates/VAULT-REVIEW.md +56 -0
  94. package/pp-core/templates/phase-prompt.md +2 -6
  95. package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
  96. package/pp-core/workflows/add-tests.md +55 -4
  97. package/pp-core/workflows/audit-fix.md +63 -2
  98. package/pp-core/workflows/audit-milestone.md +1 -1
  99. package/pp-core/workflows/autonomous.md +85 -11
  100. package/pp-core/workflows/check-todos.md +13 -2
  101. package/pp-core/workflows/code-review-fix.md +59 -3
  102. package/pp-core/workflows/code-review.md +10 -57
  103. package/pp-core/workflows/complete-milestone.md +1 -1
  104. package/pp-core/workflows/debug.md +54 -4
  105. package/pp-core/workflows/discovery-sprint/prefill.md +19 -7
  106. package/pp-core/workflows/discovery-sprint/prototype.md +46 -49
  107. package/pp-core/workflows/discovery-sprint/session-13-gates.md +2 -0
  108. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +1 -3
  109. package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +1 -3
  110. package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +1 -3
  111. package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +20 -24
  112. package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +1 -3
  113. package/pp-core/workflows/discovery-sprint/sessions/06-security.md +1 -3
  114. package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +1 -3
  115. package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +1 -3
  116. package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +1 -3
  117. package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +1 -6
  118. package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +1 -3
  119. package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +1 -3
  120. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +1 -3
  121. package/pp-core/workflows/discovery-sprint/templates/context-template.md +1 -1
  122. package/pp-core/workflows/discovery-sprint/vault-context.md +207 -0
  123. package/pp-core/workflows/discovery-sprint.md +7 -3
  124. package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +4 -4
  125. package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +43 -1
  126. package/pp-core/workflows/discovery-to-build.md +10 -2
  127. package/pp-core/workflows/discuss-phase.md +3 -10
  128. package/pp-core/workflows/execute-phase/steps/check-query-gate.md +327 -0
  129. package/pp-core/workflows/execute-phase/steps/client-context-gate.md +3 -2
  130. package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -1
  131. package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +78 -0
  132. package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
  133. package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +6 -8
  134. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +115 -14
  135. package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +219 -20
  136. package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +147 -0
  137. package/pp-core/workflows/execute-phase.md +152 -128
  138. package/pp-core/workflows/execute-plan.md +54 -6
  139. package/pp-core/workflows/fast.md +53 -4
  140. package/pp-core/workflows/help/modes/full.md +3 -3
  141. package/pp-core/workflows/init/steps/client-context-capture.md +101 -0
  142. package/pp-core/workflows/init/steps/gitlab-provisioning.md +130 -0
  143. package/pp-core/workflows/init/steps/project-md-deferral-narration.md +66 -0
  144. package/pp-core/workflows/init/steps/reachability-outcome.md +70 -0
  145. package/pp-core/workflows/init.md +232 -272
  146. package/pp-core/workflows/new-project.md +31 -23
  147. package/pp-core/workflows/plan-phase.md +98 -45
  148. package/pp-core/workflows/quick.md +117 -3
  149. package/pp-core/workflows/secure-phase.md +1 -1
  150. package/pp-core/workflows/ship.md +57 -28
  151. package/pp-core/workflows/undo.md +1 -1
  152. package/pp-core/workflows/update.md +3 -3
  153. package/pp-core/workflows/validate-phase.md +58 -3
  154. package/pp-core/workflows/vault-review.md +161 -23
  155. package/pp-core/workflows/vault-write-back.md +73 -110
  156. package/pp-core/workflows/verify-work.md +12 -14
  157. package/scripts/changeset/cli.cjs +6 -5
  158. package/scripts/changeset/lint.cjs +0 -0
  159. package/scripts/changeset/new.cjs +33 -50
  160. package/scripts/dev-env.cjs +25 -85
  161. package/scripts/gen-capability-registry.cjs +120 -13
  162. package/scripts/lib/allowlist-ratchet.cjs +1 -103
  163. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
  164. package/scripts/lint-docs-required.cjs +0 -0
  165. package/scripts/lint-pr-check-project-dir.cjs +1 -3
  166. package/scripts/lint-test-file-count.allowlist.json +3 -19
  167. package/scripts/lint-test-file-count.cjs +5 -6
  168. package/scripts/prompt-injection-scan.sh +19 -17
  169. package/scripts/release-channel.cjs +0 -0
  170. package/scripts/sync-manifest-versions.cjs +5 -4
  171. package/scripts/sync-rulesets.sh +1 -1
  172. package/scripts/update-size-baseline.cjs +30 -274
  173. package/scripts/verify-opengsd-sweep.cjs +3 -5
  174. package/scripts/workflow-size.cjs +9 -61
  175. package/pp-core/bin/lib/dispatch-plan.cjs +0 -483
  176. package/pp-core/bin/lib/install-run.cjs +0 -536
  177. package/pp-core/bin/lib/loop-gates.cjs +0 -213
  178. package/pp-core/bin/lib/phase-manifest.cjs +0 -527
  179. package/pp-core/bin/lib/phase-tier.cjs +0 -480
  180. package/pp-core/bin/lib/plan-graph.cjs +0 -368
  181. package/pp-core/bin/lib/practices-review-check.cjs +0 -938
  182. package/pp-core/bin/lib/vault-audit.cjs +0 -2070
  183. package/pp-core/bin/lib/vault-practices.cjs +0 -1990
  184. package/pp-core/bin/lib/vault-review.cjs +0 -2359
  185. package/pp-core/bin/lib/vault-write-back.cjs +0 -877
  186. package/pp-core/references/executor-vault-trace.md +0 -47
  187. package/pp-core/references/planner-plan-graph.md +0 -66
  188. package/pp-core/references/planner-vault-practices.md +0 -112
  189. package/pp-core/templates/practices/README.md +0 -251
  190. package/pp-core/templates/practices/error-handling.md +0 -18
  191. package/pp-core/templates/practices/minimal-comments.md +0 -14
  192. package/pp-core/templates/practices/naming-convention.md +0 -18
  193. package/pp-core/templates/practices/practice-template.md +0 -23
  194. package/pp-core/workflows/discovery-sprint/vault-fetch.md +0 -37
  195. package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +0 -45
  196. package/pp-core/workflows/execute-phase/steps/install-run.md +0 -29
  197. package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +0 -23
  198. package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +0 -73
  199. package/pp-core/workflows/execute-phase/steps/vault-door-gate.md +0 -19
  200. package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -43
  201. package/scripts/lib/size-budget.cjs +0 -311
  202. package/scripts/lint-neutrality-names.cjs +0 -694
  203. package/scripts/lint-org-coordinates.allowlist.json +0 -35
  204. package/scripts/lint-org-coordinates.cjs +0 -426
@@ -1,483 +0,0 @@
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, nyquist auditor and — heavy tier, vault
38
- * practices on — the agent practice review. `totals.spawns` counts every agent spawn of every stage, as an upper
39
- * bound: entries marked `conditional` run only when the step before them asks
40
- * for it (a revision after a failed check, a fix after review findings).
41
- */
42
- var __importDefault = (this && this.__importDefault) || function (mod) {
43
- return (mod && mod.__esModule) ? mod : { "default": mod };
44
- };
45
- Object.defineProperty(exports, "__esModule", { value: true });
46
- exports.STAGE_AGENTS = void 0;
47
- exports.computeTierStages = computeTierStages;
48
- exports.submoduleIntersection = submoduleIntersection;
49
- exports.parseGitmodulesPaths = parseGitmodulesPaths;
50
- exports.computeDispatchPlan = computeDispatchPlan;
51
- exports.parseDispatchPlanArgs = parseDispatchPlanArgs;
52
- exports.resolveDispatchPolicy = resolveDispatchPolicy;
53
- const node_fs_1 = __importDefault(require("node:fs"));
54
- const node_path_1 = __importDefault(require("node:path"));
55
- const plan_graph_cjs_1 = require("./plan-graph.cjs");
56
- // eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
57
- const planningWorkspace = require("./planning-workspace.cjs");
58
- // eslint-disable-next-line @typescript-eslint/no-require-imports -- config-loader.cjs is an export= CommonJS module
59
- const configLoaderMod = require("./config-loader.cjs");
60
- const { planningDir } = planningWorkspace;
61
- const { loadConfig } = configLoaderMod;
62
- /** Agent type names the stages spawn (each is a file under agents/). */
63
- exports.STAGE_AGENTS = Object.freeze({
64
- researcher: 'pp-phase-researcher',
65
- patternMapper: 'pp-pattern-mapper',
66
- planner: 'pp-planner',
67
- planChecker: 'pp-plan-checker',
68
- verifier: 'pp-verifier',
69
- codeReviewer: 'pp-code-reviewer',
70
- codeFixer: 'pp-code-fixer',
71
- nyquistAuditor: 'pp-nyquist-auditor',
72
- securityAuditor: 'pp-security-auditor',
73
- vaultReviewer: 'pp-vault-reviewer',
74
- });
75
- function spawn(id, agent, gate, reason, conditional = false) {
76
- return { id, kind: 'spawn', agent, gate, reason, conditional };
77
- }
78
- function step(id, gate, reason) {
79
- return { id, kind: 'step', agent: null, gate, reason, conditional: false };
80
- }
81
- /**
82
- * The planning and verification stages of a phase, from its resolved tier.
83
- * `planBounceScript`: bounce runs only with a configured script.
84
- * `vaultReview`: the project's vault practices apply (`vaultReviewApplies`,
85
- * src/vault.cts — the caller reads it); the agent practice review is spawned
86
- * only when that AND the tier's `vault_review` gate are both on.
87
- */
88
- function computeTierStages(tier, opts = {}) {
89
- const g = tier.gates;
90
- const plan = [];
91
- if (g.research)
92
- plan.push(spawn('plan-research', exports.STAGE_AGENTS.researcher, 'research', 'workflow.research is on for this tier'));
93
- if (g.pattern_mapper)
94
- plan.push(spawn('plan-patterns', exports.STAGE_AGENTS.patternMapper, 'pattern_mapper', 'workflow.pattern_mapper is on for this tier'));
95
- plan.push(spawn('plan-planner', exports.STAGE_AGENTS.planner, 'planner', 'the planner always runs'));
96
- if (g.plan_check) {
97
- for (let pass = 1; pass <= g.plan_check_passes; pass++) {
98
- if (pass > 1) {
99
- 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));
100
- }
101
- 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));
102
- }
103
- }
104
- if (g.plan_bounce && opts.planBounceScript) {
105
- plan.push(step('plan-bounce', 'plan_bounce', `refine plans through workflow.plan_bounce_script, ${g.plan_bounce_passes} pass(es)`));
106
- if (g.plan_check)
107
- plan.push(spawn('plan-bounce-check', exports.STAGE_AGENTS.planChecker, 'plan_bounce', 're-check bounced plans; a plan that fails is restored', true));
108
- }
109
- if (g.post_planning_gaps)
110
- plan.push(step('plan-gaps', 'post_planning_gaps', 'post-planning gap report (plan:post), non-blocking'));
111
- const verify = [];
112
- if (g.code_review) {
113
- verify.push(spawn('verify-code-review', exports.STAGE_AGENTS.codeReviewer, 'code_review', 'code review of the phase source changes'));
114
- verify.push(spawn('verify-code-fix', exports.STAGE_AGENTS.codeFixer, 'code_review', 'fix loop: only when the review finds issues', true));
115
- }
116
- // The install run goes first: the verifier judges the phase on its results file.
117
- if (g.install_verification)
118
- 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)'));
119
- if (g.verifier)
120
- verify.push(spawn('verify-goal', exports.STAGE_AGENTS.verifier, 'verifier', 'goal-backward phase verification (VERIFICATION.md)'));
121
- if (g.security_enforcement)
122
- verify.push(spawn('verify-security', exports.STAGE_AGENTS.securityAuditor, 'security_enforcement', 'threat-model verification (SECURITY.md)'));
123
- if (g.nyquist_validation)
124
- verify.push(spawn('verify-nyquist', exports.STAGE_AGENTS.nyquistAuditor, 'nyquist_validation', 'validation-gap audit (VALIDATION.md)'));
125
- if (g.vault_review && opts.vaultReview === true) {
126
- verify.push(spawn('verify-vault-review', exports.STAGE_AGENTS.vaultReviewer, 'vault_review', 'agent practice review of the client vault (VAULT-REVIEW.md); reports, never gates'));
127
- }
128
- return { plan, verify };
129
- }
130
- function normalizePath(file) {
131
- return String(file).trim().replace(/\\/g, '/').replace(/^\.\//, '');
132
- }
133
- function stripEnds(p) {
134
- return p.replace(/^\.\//, '').replace(/\/$/, '');
135
- }
136
- /**
137
- * Planned paths that touch a submodule (the per-plan worktree rule, #2772):
138
- * either side normalised (leading `./`, one trailing `/`), matched both ways
139
- * (planned path inside the submodule, or submodule inside the planned path),
140
- * and a globbed planned path matched by its literal prefix before the first
141
- * `*`, `?` or `[`.
142
- */
143
- function submoduleIntersection(planFiles, submodulePaths) {
144
- const hits = new Set();
145
- const inside = (a, b) => a === b || a.startsWith(`${b}/`);
146
- for (const smRaw of submodulePaths) {
147
- const sm = stripEnds(smRaw.trim());
148
- if (sm === '')
149
- continue;
150
- for (const pfRaw of planFiles) {
151
- const pf = stripEnds(String(pfRaw).trim());
152
- if (pf === '')
153
- continue;
154
- let matched = inside(pf, sm) || inside(sm, pf);
155
- if (!matched && /[*?[]/.test(pf)) {
156
- const prefix = pf.slice(0, pf.search(/[*?[]/)).replace(/\/$/, '');
157
- matched = prefix !== '' && (inside(sm, prefix) || inside(prefix, sm));
158
- }
159
- if (matched)
160
- hits.add(String(pfRaw).trim());
161
- }
162
- }
163
- return [...hits];
164
- }
165
- /** Submodule paths declared in a `.gitmodules` file's text. */
166
- function parseGitmodulesPaths(text) {
167
- const paths = [];
168
- let inSubmodule = false;
169
- for (const line of text.split(/\r?\n/)) {
170
- const section = /^\s*\[\s*([^\s\]]+)/.exec(line);
171
- if (section) {
172
- inSubmodule = section[1].toLowerCase() === 'submodule';
173
- continue;
174
- }
175
- const kv = /^\s*path\s*=\s*(.+?)\s*$/i.exec(line);
176
- if (inSubmodule && kv)
177
- paths.push(kv[1].replace(/^"(.*)"$/, '$1'));
178
- }
179
- return paths;
180
- }
181
- function isolationFor(plan, policy) {
182
- if (!policy.useWorktrees) {
183
- return { isolation: 'main-checkout', reason: `worktrees off (workflow.use_worktrees=false, from ${policy.useWorktreesSource})` };
184
- }
185
- if (policy.submodulePaths.length > 0) {
186
- const files = plan.files_modified.map((f) => String(f).trim()).filter((f) => f !== '');
187
- if (files.length === 0) {
188
- return { isolation: 'main-checkout', reason: 'files_modified missing/unparseable in a submodule project — no worktree, as a safety fallback' };
189
- }
190
- const hits = submoduleIntersection(files, policy.submodulePaths);
191
- if (hits.length > 0) {
192
- return { isolation: 'main-checkout', reason: `planned paths intersect submodule paths (${hits.join(' ')})` };
193
- }
194
- }
195
- return { isolation: 'worktree', reason: 'independent plan: its own worktree' };
196
- }
197
- function summaryPath(phaseDir, id) {
198
- return `${phaseDir}/${id === '' ? '' : `${id}-`}SUMMARY.md`;
199
- }
200
- function planEntry(plan, phaseDir) {
201
- const files = plan.files_modified.map(normalizePath).filter((f) => f !== '');
202
- return {
203
- id: plan.id,
204
- title: plan.title,
205
- build: files.length === 0 || files.some(plan_graph_cjs_1.isSourceFile),
206
- touched_tests: files.filter((f) => (0, plan_graph_cjs_1.classifyPlanFile)(f) === 'test'),
207
- summary: summaryPath(phaseDir, plan.id),
208
- };
209
- }
210
- function buildReason(plans) {
211
- const reasons = [];
212
- for (const p of plans) {
213
- const files = p.files_modified.map(normalizePath).filter((f) => f !== '');
214
- if (files.length === 0)
215
- reasons.push(`${p.id}: files_modified not declared`);
216
- else {
217
- const src = files.filter(plan_graph_cjs_1.isSourceFile);
218
- if (src.length > 0)
219
- reasons.push(`${p.id} touches ${src.join(', ')}`);
220
- }
221
- }
222
- return reasons.length > 0 ? reasons.join('; ') : 'no plan touches a source file';
223
- }
224
- function waveDefects(spawns, byId) {
225
- const members = [];
226
- for (const s of spawns) {
227
- for (const p of s.plans) {
228
- const plan = byId.get(p.id);
229
- members.push({ id: p.id, spawn: s.id, files: new Set(plan.files_modified.map(normalizePath).filter((f) => f !== '')) });
230
- }
231
- }
232
- const defects = [];
233
- for (let i = 0; i < members.length; i++) {
234
- for (let j = i + 1; j < members.length; j++) {
235
- const a = members[i];
236
- const b = members[j];
237
- const shared = [...a.files].filter((f) => b.files.has(f)).sort();
238
- if (shared.length === 0)
239
- continue;
240
- const source = shared.filter(plan_graph_cjs_1.isSourceFile);
241
- if (source.length > 0) {
242
- defects.push({
243
- kind: 'source-file-overlap',
244
- plans: [a.id, b.id],
245
- files: source,
246
- 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).`,
247
- });
248
- }
249
- else if (a.spawn !== b.spawn) {
250
- defects.push({
251
- kind: 'cross-spawn-overlap',
252
- plans: [a.id, b.id],
253
- files: shared,
254
- 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).`,
255
- });
256
- }
257
- }
258
- }
259
- return defects;
260
- }
261
- /**
262
- * Compute the dispatch plan. `plans` are `phase-plan-index` rows in plan file
263
- * order; `phaseDir` is the phase directory relative to the project root.
264
- */
265
- function computeDispatchPlan(phase, phaseDir, plans, policy, tier = null, tierOpts = {}) {
266
- const contributions = (tierOpts.executorContributions ?? []).map((c) => ({ capId: c.capId, fragment: c.fragment }));
267
- const byId = new Map(plans.map((p) => [p.id, p]));
268
- const skippedComplete = plans.filter((p) => p.has_summary).map((p) => p.id);
269
- const incomplete = plans.filter((p) => !p.has_summary && (!policy.gapsOnly || p.gap_closure === true));
270
- const selected = incomplete.filter((p) => policy.waveFilter === null || p.wave === policy.waveFilter);
271
- const selectedIds = new Set(selected.map((p) => p.id));
272
- const skippedByFilter = plans.filter((p) => !p.has_summary && !selectedIds.has(p.id)).map((p) => p.id);
273
- const lowerWaveIncomplete = policy.waveFilter === null
274
- ? []
275
- : incomplete.filter((p) => p.wave < policy.waveFilter).map((p) => p.id);
276
- const waveNumbers = [...new Set(selected.map((p) => p.wave))].sort((a, b) => a - b);
277
- const waves = [];
278
- for (const n of waveNumbers) {
279
- const inWave = selected.filter((p) => p.wave === n);
280
- const groupCounts = new Map();
281
- for (const p of inWave)
282
- if (p.serial_group)
283
- groupCounts.set(p.serial_group, (groupCounts.get(p.serial_group) ?? 0) + 1);
284
- const spawns = [];
285
- const placedGroups = new Set();
286
- for (const p of inWave) {
287
- const group = p.serial_group && (groupCounts.get(p.serial_group) ?? 0) >= 2 ? p.serial_group : null;
288
- if (group !== null) {
289
- if (placedGroups.has(group))
290
- continue;
291
- placedGroups.add(group);
292
- const members = inWave.filter((q) => q.serial_group === group);
293
- const entries = members.map((q) => planEntry(q, phaseDir));
294
- spawns.push({
295
- id: `w${n}-s${spawns.length + 1}`,
296
- agent: 'pp-executor',
297
- kind: 'serial-group',
298
- serial_group: group,
299
- plans: entries,
300
- isolation: 'main-checkout',
301
- isolation_reason: `serial group ${group}: one executor runs its ${members.length} plans in order on the main checkout`,
302
- build: entries.some((e) => e.build),
303
- build_reason: buildReason(members),
304
- checkpoint: members.some((q) => !q.autonomous),
305
- expected_summaries: entries.map((e) => e.summary),
306
- contributions: contributions.map((c) => ({ ...c })),
307
- });
308
- continue;
309
- }
310
- const entry = planEntry(p, phaseDir);
311
- const iso = isolationFor(p, policy);
312
- spawns.push({
313
- id: `w${n}-s${spawns.length + 1}`,
314
- agent: 'pp-executor',
315
- kind: 'plan',
316
- serial_group: null,
317
- plans: [entry],
318
- isolation: iso.isolation,
319
- isolation_reason: iso.reason,
320
- build: entry.build,
321
- build_reason: buildReason([p]),
322
- checkpoint: !p.autonomous,
323
- expected_summaries: [entry.summary],
324
- contributions: contributions.map((c) => ({ ...c })),
325
- });
326
- }
327
- const defects = waveDefects(spawns, byId);
328
- const worktreeSpawns = spawns.filter((s) => s.isolation === 'worktree').length;
329
- const run = defects.length > 0 || !policy.parallelization || spawns.length < 2 ? 'sequential' : 'parallel';
330
- const runReason = defects.length > 0
331
- ? 'planning defect: overlapping files_modified in this wave — run every spawn one at a time'
332
- : !policy.parallelization
333
- ? 'parallelization is off'
334
- : spawns.length < 2
335
- ? 'one spawn'
336
- : 'worktree spawns run alongside each other; main-checkout spawns run one at a time';
337
- const requirements = [...new Set(inWave.flatMap((p) => p.requirements ?? []))];
338
- const expected = spawns.flatMap((s) => s.expected_summaries);
339
- waves.push({
340
- wave: n,
341
- run,
342
- run_reason: runReason,
343
- spawns,
344
- merge: { worktree_spawns: worktreeSpawns, cleanup: worktreeSpawns > 0 },
345
- build: spawns.some((s) => s.build),
346
- suite_runs: 1,
347
- suite: { by: 'orchestrator', after: 'merge' },
348
- tracking_commits: 1,
349
- tracking: { by: 'orchestrator', plans: inWave.map((p) => p.id), requirements },
350
- expected_summaries: expected,
351
- defects,
352
- });
353
- }
354
- const stages = tier ? computeTierStages(tier, tierOpts) : { plan: [], verify: [] };
355
- const stagePlan = stages.plan;
356
- const stageVerify = stages.verify;
357
- const planSpawns = stagePlan.filter((e) => e.kind === 'spawn').length;
358
- const verifySpawns = stageVerify.filter((e) => e.kind === 'spawn').length;
359
- const executorSpawns = waves.flatMap((w) => w.spawns);
360
- return {
361
- phase,
362
- phase_dir: phaseDir,
363
- tier: tier ? { tier: tier.tier, source: tier.source, suppressed: tier.suppressed, gates: tier.gates } : null,
364
- policy: {
365
- use_worktrees: policy.useWorktrees,
366
- use_worktrees_source: policy.useWorktreesSource,
367
- parallelization: policy.parallelization,
368
- submodule_paths: policy.submodulePaths,
369
- },
370
- filters: {
371
- wave: policy.waveFilter,
372
- gaps_only: policy.gapsOnly,
373
- skipped_complete: skippedComplete,
374
- skipped_by_filter: skippedByFilter,
375
- lower_wave_incomplete: lowerWaveIncomplete,
376
- },
377
- stages: { plan: stagePlan, execute: { waves }, verify: stageVerify },
378
- totals: {
379
- waves: waves.length,
380
- spawns: planSpawns + executorSpawns.length + verifySpawns,
381
- plan_spawns: planSpawns,
382
- verify_spawns: verifySpawns,
383
- executor_spawns: executorSpawns.length,
384
- main_checkout_spawns: executorSpawns.filter((s) => s.isolation === 'main-checkout').length,
385
- isolated_spawns: executorSpawns.filter((s) => s.isolation === 'worktree').length,
386
- builds: executorSpawns.filter((s) => s.build).length,
387
- suite_runs: waves.reduce((sum, w) => sum + w.suite_runs, 0),
388
- tracking_commits: waves.reduce((sum, w) => sum + w.tracking_commits, 0),
389
- expected_summaries: waves.flatMap((w) => w.expected_summaries),
390
- defects: waves.reduce((sum, w) => sum + w.defects.length, 0),
391
- },
392
- };
393
- }
394
- /** `<phase> [--wave N] [--gaps-only] [--use-worktrees true|false] [--tier light|standard|heavy]`. */
395
- function parseDispatchPlanArgs(args) {
396
- const usage = 'Usage: phase dispatch-plan <phase> [--wave N] [--gaps-only] [--use-worktrees true|false] [--tier light|standard|heavy]';
397
- let phase = null;
398
- let waveFilter = null;
399
- let gapsOnly = false;
400
- let useWorktrees = null;
401
- let tier = null;
402
- for (let i = 0; i < args.length; i++) {
403
- const a = args[i];
404
- if (a === '--raw')
405
- continue;
406
- if (a === '--gaps-only') {
407
- gapsOnly = true;
408
- continue;
409
- }
410
- if (a === '--wave') {
411
- const v = args[++i];
412
- if (v === undefined || !/^\d+$/.test(v))
413
- return { error: `--wave needs a wave number. ${usage}` };
414
- waveFilter = parseInt(v, 10);
415
- continue;
416
- }
417
- if (a === '--use-worktrees') {
418
- const v = args[++i];
419
- if (v !== 'true' && v !== 'false')
420
- return { error: `--use-worktrees needs true or false. ${usage}` };
421
- useWorktrees = v === 'true';
422
- continue;
423
- }
424
- if (a === '--tier') {
425
- const v = args[++i];
426
- if (v !== 'light' && v !== 'standard' && v !== 'heavy')
427
- return { error: `--tier needs light, standard or heavy. ${usage}` };
428
- tier = v;
429
- continue;
430
- }
431
- if (a.startsWith('--'))
432
- return { error: `phase dispatch-plan does not support ${a}. ${usage}` };
433
- if (phase !== null)
434
- return { error: `phase dispatch-plan takes one phase. ${usage}` };
435
- phase = a;
436
- }
437
- if (!phase)
438
- return { error: `phase required for phase dispatch-plan. ${usage}` };
439
- return { phase, waveFilter, gapsOnly, useWorktrees, tier };
440
- }
441
- /** Isolation and concurrency settings: CLI flag, then `.planning/config.json`, then defaults. */
442
- function resolveDispatchPolicy(cwd, args) {
443
- let raw = {};
444
- try {
445
- raw = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(planningDir(cwd), 'config.json'), 'utf-8'));
446
- }
447
- catch {
448
- raw = {};
449
- }
450
- const workflow = (raw['workflow'] && typeof raw['workflow'] === 'object' ? raw['workflow'] : {});
451
- let useWorktrees = true;
452
- let useWorktreesSource = 'default';
453
- if (args.useWorktrees !== null) {
454
- useWorktrees = args.useWorktrees;
455
- useWorktreesSource = 'cli';
456
- }
457
- else if (workflow['use_worktrees'] !== undefined) {
458
- useWorktrees = workflow['use_worktrees'] !== false && workflow['use_worktrees'] !== 'false';
459
- useWorktreesSource = 'config';
460
- }
461
- let parallelization = true;
462
- try {
463
- parallelization = loadConfig(cwd)['parallelization'] !== false;
464
- }
465
- catch {
466
- parallelization = true;
467
- }
468
- let submodulePaths = [];
469
- try {
470
- submodulePaths = parseGitmodulesPaths(node_fs_1.default.readFileSync(node_path_1.default.join(cwd, '.gitmodules'), 'utf-8'));
471
- }
472
- catch {
473
- submodulePaths = [];
474
- }
475
- return {
476
- useWorktrees,
477
- useWorktreesSource,
478
- parallelization,
479
- submodulePaths,
480
- waveFilter: args.waveFilter,
481
- gapsOnly: args.gapsOnly,
482
- };
483
- }