@pareto4/pp-core 1.13.0 → 1.17.1

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 (201) hide show
  1. package/.claude-plugin/plugin.json +3 -3
  2. package/README.md +26 -9
  3. package/agents/pp-code-reviewer.md +2 -1
  4. package/agents/pp-debug-session-manager.md +9 -6
  5. package/agents/pp-executor.md +10 -13
  6. package/agents/pp-plan-checker.md +4 -4
  7. package/agents/pp-planner.md +23 -9
  8. package/agents/pp-vault-reviewer.md +244 -21
  9. package/agents/pp-verifier.md +7 -3
  10. package/bin/install.js +10 -3
  11. package/commands/pp-execute-phase.md +1 -3
  12. package/commands/pp-ns-review.md +1 -1
  13. package/commands/pp-plan-phase.md +1 -2
  14. package/commands/pp-vault-review.md +5 -3
  15. package/commands/pp-verify-work.md +1 -2
  16. package/gemini-extension.json +1 -1
  17. package/hooks/dist/pp-check-update.js +0 -0
  18. package/hooks/dist/pp-cursor-session-start.js +1 -1
  19. package/hooks/dist/pp-statusline.js +1 -1
  20. package/hooks/dist/pp-update-banner.js +0 -0
  21. package/hooks/pp-check-update.js +0 -0
  22. package/hooks/pp-cursor-session-start.js +1 -1
  23. package/hooks/pp-statusline.js +1 -1
  24. package/hooks/pp-update-banner.js +0 -0
  25. package/package.json +14 -19
  26. package/pareto-skills/_shared/create-project-board.sh +0 -0
  27. package/pareto-skills/pp-project-setup/SKILL.md +1 -1
  28. package/pp-core/bin/check-latest-version.cjs +0 -0
  29. package/pp-core/bin/detect-build-commands.sh +2 -2
  30. package/pp-core/bin/lib/capability-activation.cjs +83 -9
  31. package/pp-core/bin/lib/capability-registry.cjs +396 -82
  32. package/pp-core/bin/lib/capability-state.cjs +160 -4
  33. package/pp-core/bin/lib/capability-writer.cjs +19 -5
  34. package/pp-core/bin/lib/check-command-router.cjs +3334 -946
  35. package/pp-core/bin/lib/clusters.cjs +3 -0
  36. package/pp-core/bin/lib/command-aliases.cjs +0 -35
  37. package/pp-core/bin/lib/commands.cjs +9 -4
  38. package/pp-core/bin/lib/compose-bindings.cjs +251 -88
  39. package/pp-core/bin/lib/config-loader.cjs +7 -103
  40. package/pp-core/bin/lib/config-schema.cjs +1 -1
  41. package/pp-core/bin/lib/config.cjs +154 -62
  42. package/pp-core/bin/lib/decisions.cjs +4 -4
  43. package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
  44. package/pp-core/bin/lib/frontmatter.cjs +1 -38
  45. package/pp-core/bin/lib/git-base-branch.cjs +13 -11
  46. package/pp-core/bin/lib/git-provider.cjs +4 -4
  47. package/pp-core/bin/lib/init-command-router.cjs +5 -8
  48. package/pp-core/bin/lib/init-contract.cjs +648 -738
  49. package/pp-core/bin/lib/init-interactive.cjs +76 -4
  50. package/pp-core/bin/lib/init.cjs +24 -51
  51. package/pp-core/bin/lib/install-profiles.cjs +1 -1
  52. package/pp-core/bin/lib/io.cjs +8 -0
  53. package/pp-core/bin/lib/loop-resolver.cjs +189 -129
  54. package/pp-core/bin/lib/org-profile.cjs +469 -152
  55. package/pp-core/bin/lib/package-identity.cjs +3 -3
  56. package/pp-core/bin/lib/phase-command-router.cjs +1 -29
  57. package/pp-core/bin/lib/phase-scope.cjs +1 -1
  58. package/pp-core/bin/lib/phase.cjs +11 -253
  59. package/pp-core/bin/lib/platform-bindings.cjs +40 -19
  60. package/pp-core/bin/lib/practices-review-check.cjs +15 -9
  61. package/pp-core/bin/lib/profile-output.cjs +1 -1
  62. package/pp-core/bin/lib/shell-command-projection.cjs +104 -1
  63. package/pp-core/bin/lib/state.cjs +11 -85
  64. package/pp-core/bin/lib/template.cjs +1 -1
  65. package/pp-core/bin/lib/vault-audit.cjs +650 -126
  66. package/pp-core/bin/lib/vault-command-router.cjs +2604 -367
  67. package/pp-core/bin/lib/vault-coordinates.cjs +13 -4
  68. package/pp-core/bin/lib/vault-index.cjs +699 -0
  69. package/pp-core/bin/lib/vault-mode.cjs +235 -0
  70. package/pp-core/bin/lib/vault-practices.cjs +139 -72
  71. package/pp-core/bin/lib/vault-review.cjs +194 -451
  72. package/pp-core/bin/lib/vault-seal.cjs +1936 -0
  73. package/pp-core/bin/lib/vault-standards.cjs +1505 -0
  74. package/pp-core/bin/lib/vault-trace.cjs +607 -254
  75. package/pp-core/bin/lib/vault-update.cjs +9 -33
  76. package/pp-core/bin/lib/vault-write-back.cjs +70 -42
  77. package/pp-core/bin/lib/vault.cjs +2734 -920
  78. package/pp-core/bin/lib/verify.cjs +0 -11
  79. package/pp-core/bin/portable-timeout.sh +1 -1
  80. package/pp-core/bin/pp-tools.cjs +184 -54
  81. package/pp-core/bin/shared/config-defaults.manifest.json +0 -7
  82. package/pp-core/bin/shared/config-schema.manifest.json +0 -3
  83. package/pp-core/bin/shared/model-catalog.json +2 -2
  84. package/pp-core/bin/verify-reapply-patches.cjs +0 -0
  85. package/pp-core/org-profile.json +26 -0
  86. package/pp-core/references/autonomous-best-effort.md +8 -7
  87. package/pp-core/references/loop-hook-dispatch.md +0 -25
  88. package/pp-core/references/planner-gap-closure.md +0 -1
  89. package/pp-core/references/planner-tdd.md +0 -1
  90. package/pp-core/references/planner-vault-practices.md +17 -34
  91. package/pp-core/references/planner-vault-standards.md +320 -0
  92. package/pp-core/references/planning-config.md +0 -8
  93. package/pp-core/references/practice-arithmetic-contract.md +121 -0
  94. package/pp-core/references/tdd.md +0 -1
  95. package/pp-core/references/verifier-practice-verdicts.md +263 -0
  96. package/pp-core/templates/README.md +1 -0
  97. package/pp-core/templates/VAULT-REVIEW.md +202 -0
  98. package/pp-core/templates/phase-prompt.md +2 -6
  99. package/pp-core/templates/practices/README.md +20 -23
  100. package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
  101. package/pp-core/workflows/add-tests.md +55 -4
  102. package/pp-core/workflows/audit-fix.md +63 -2
  103. package/pp-core/workflows/audit-milestone.md +1 -1
  104. package/pp-core/workflows/autonomous.md +85 -11
  105. package/pp-core/workflows/check-todos.md +13 -2
  106. package/pp-core/workflows/code-review-fix.md +59 -3
  107. package/pp-core/workflows/code-review.md +10 -1
  108. package/pp-core/workflows/complete-milestone.md +1 -1
  109. package/pp-core/workflows/debug.md +54 -4
  110. package/pp-core/workflows/discovery-sprint/prefill.md +19 -7
  111. package/pp-core/workflows/discovery-sprint/prototype.md +46 -49
  112. package/pp-core/workflows/discovery-sprint/session-13-gates.md +2 -0
  113. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +1 -3
  114. package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +1 -3
  115. package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +1 -3
  116. package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +20 -24
  117. package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +1 -3
  118. package/pp-core/workflows/discovery-sprint/sessions/06-security.md +1 -3
  119. package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +1 -3
  120. package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +1 -3
  121. package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +1 -3
  122. package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +1 -6
  123. package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +1 -3
  124. package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +1 -3
  125. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +1 -3
  126. package/pp-core/workflows/discovery-sprint/templates/context-template.md +1 -1
  127. package/pp-core/workflows/discovery-sprint/vault-context.md +213 -0
  128. package/pp-core/workflows/discovery-sprint.md +7 -3
  129. package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +4 -4
  130. package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +43 -1
  131. package/pp-core/workflows/discovery-to-build.md +10 -2
  132. package/pp-core/workflows/discuss-phase.md +3 -10
  133. package/pp-core/workflows/execute-phase/steps/check-query-gate.md +329 -0
  134. package/pp-core/workflows/execute-phase/steps/client-context-gate.md +3 -2
  135. package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -1
  136. package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +98 -0
  137. package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
  138. package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +6 -8
  139. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +115 -14
  140. package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +322 -20
  141. package/pp-core/workflows/execute-phase/steps/verifier-practice-payload.md +165 -0
  142. package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +163 -0
  143. package/pp-core/workflows/execute-phase.md +155 -128
  144. package/pp-core/workflows/execute-plan.md +56 -6
  145. package/pp-core/workflows/fast.md +53 -4
  146. package/pp-core/workflows/help/modes/full.md +3 -3
  147. package/pp-core/workflows/init/steps/client-context-capture.md +101 -0
  148. package/pp-core/workflows/init/steps/gitlab-provisioning.md +130 -0
  149. package/pp-core/workflows/init/steps/project-md-deferral-narration.md +66 -0
  150. package/pp-core/workflows/init/steps/reachability-outcome.md +70 -0
  151. package/pp-core/workflows/init.md +232 -272
  152. package/pp-core/workflows/new-project.md +31 -23
  153. package/pp-core/workflows/plan-phase.md +98 -45
  154. package/pp-core/workflows/quick.md +117 -3
  155. package/pp-core/workflows/secure-phase.md +1 -1
  156. package/pp-core/workflows/ship.md +57 -28
  157. package/pp-core/workflows/undo.md +1 -1
  158. package/pp-core/workflows/update.md +3 -3
  159. package/pp-core/workflows/validate-phase.md +58 -3
  160. package/pp-core/workflows/vault-review.md +282 -22
  161. package/pp-core/workflows/verify-work.md +12 -14
  162. package/scripts/changeset/cli.cjs +6 -5
  163. package/scripts/changeset/lint.cjs +0 -0
  164. package/scripts/changeset/new.cjs +33 -50
  165. package/scripts/dev-env.cjs +25 -85
  166. package/scripts/gen-capability-registry.cjs +199 -11
  167. package/scripts/gen-practice-arithmetic-contract.cjs +412 -0
  168. package/scripts/lib/allowlist-ratchet.cjs +1 -103
  169. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
  170. package/scripts/lint-docs-required.cjs +0 -0
  171. package/scripts/lint-pr-check-project-dir.cjs +1 -3
  172. package/scripts/lint-test-file-count.allowlist.json +12 -10
  173. package/scripts/practice-arithmetic-contract.allow.json +35 -0
  174. package/scripts/prompt-injection-scan.sh +22 -17
  175. package/scripts/record-vault-mode-golden.cjs +89 -0
  176. package/scripts/release-channel.cjs +0 -0
  177. package/scripts/sync-manifest-versions.cjs +5 -4
  178. package/scripts/sync-rulesets.sh +1 -1
  179. package/scripts/update-size-baseline.cjs +30 -274
  180. package/scripts/vault-calibrate.cjs +996 -0
  181. package/scripts/verify-opengsd-sweep.cjs +3 -5
  182. package/scripts/workflow-size.cjs +9 -61
  183. package/pp-core/bin/lib/dispatch-plan.cjs +0 -483
  184. package/pp-core/bin/lib/install-run.cjs +0 -536
  185. package/pp-core/bin/lib/loop-gates.cjs +0 -213
  186. package/pp-core/bin/lib/phase-manifest.cjs +0 -527
  187. package/pp-core/bin/lib/phase-tier.cjs +0 -480
  188. package/pp-core/bin/lib/plan-graph.cjs +0 -368
  189. package/pp-core/references/executor-vault-trace.md +0 -47
  190. package/pp-core/references/planner-plan-graph.md +0 -66
  191. package/pp-core/workflows/discovery-sprint/vault-fetch.md +0 -37
  192. package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +0 -45
  193. package/pp-core/workflows/execute-phase/steps/install-run.md +0 -29
  194. package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +0 -23
  195. package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +0 -73
  196. package/pp-core/workflows/execute-phase/steps/vault-door-gate.md +0 -19
  197. package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -43
  198. package/scripts/lib/size-budget.cjs +0 -311
  199. package/scripts/lint-neutrality-names.cjs +0 -694
  200. package/scripts/lint-org-coordinates.allowlist.json +0 -35
  201. package/scripts/lint-org-coordinates.cjs +0 -426
@@ -10,8 +10,9 @@
10
10
  *
11
11
  * Explicit carve-outs (allowlist — intentional OpenGSD references):
12
12
  * D-07 provenance files: LICENSE, NOTICE, UPSTREAM_SYNC.md
13
- * D-06 historical corpus: docs/adr/, docs/proposals/, docs/discussions/,
14
- * docs/issueevidence/, docs/prd/, docs/research/
13
+ * D-06 historical corpus: docs/adr/, docs/prd/, docs/research/
14
+ * (docs/proposals, docs/discussions and docs/issueevidence
15
+ * were upstream working notes, removed from the fork)
15
16
  * D-06 notice file: docs/adr/README.md (contains the history-notice header)
16
17
  * Migration provenance: pp-core/bin/lib/installer-migrations/005-*.cjs
17
18
  * (comment accurately names the upstream source package)
@@ -79,9 +80,6 @@ const PROVENANCE_FILES = new Set([
79
80
  */
80
81
  const HISTORICAL_CORPUS_DIRS = [
81
82
  path.join(ROOT, 'docs', 'adr'),
82
- path.join(ROOT, 'docs', 'proposals'),
83
- path.join(ROOT, 'docs', 'discussions'),
84
- path.join(ROOT, 'docs', 'issueevidence'),
85
83
  path.join(ROOT, 'docs', 'prd'),
86
84
  path.join(ROOT, 'docs', 'research'),
87
85
  ];
@@ -36,10 +36,8 @@ function lfByteCount(filePath) {
36
36
 
37
37
  /**
38
38
  * List top-level workflow stems (filenames without the `.md` extension), sorted.
39
- * Top-level only on purpose: a stem is what the tier tables (XL/LARGE/DEFAULT)
40
- * classify, and only top-level files have a tier. Nested files under
41
- * `workflows/<name>/{steps,modes,templates}/` are still SIZE-guarded — see
42
- * `measureMdTree` / `measureWorkflows`, which recurse.
39
+ * Non-recursive by design: per-mode bodies under `workflows/<name>/modes/` and
40
+ * templates are NOT measured — only the always-loaded top-level workflows.
43
41
  *
44
42
  * @param {string} [dir] - Workflows directory (defaults to the canonical one).
45
43
  * @returns {string[]} Sorted stems, e.g. `['autonomous', 'plan-phase', ...]`.
@@ -54,10 +52,10 @@ function listWorkflowStems(dir = WORKFLOWS_DIR) {
54
52
 
55
53
  /**
56
54
  * Measure every top-level `.md` file in `dir`, keyed by filename, byte sizes.
57
- * Generic over directory and an optional filename predicate — used for agents
58
- * (`agents/pp-*.md`), which are a flat directory, so the agent guard and the
59
- * baseline generator share one measurement path (#1074).
60
- * Non-recursive by design; use `measureMdTree` for a nested tree.
55
+ * Generic over directory and an optional filename predicate — used for both
56
+ * workflows (`pp-core/workflows/*.md`) and agents (`agents/gsd-*.md`) so the
57
+ * size guards and the baseline generator share one measurement path (#1074).
58
+ * Non-recursive by design.
61
59
  *
62
60
  * @param {string} dir - Directory to scan.
63
61
  * @param {function(string): boolean} [predicate] - Filename filter (default: all `.md`).
@@ -74,69 +72,19 @@ function measureMdFiles(dir, predicate = () => true) {
74
72
  }
75
73
 
76
74
  /**
77
- * List every `.md` file under `dir`, recursively, as POSIX relative paths
78
- * (`plan-phase.md`, `execute-phase/steps/vault-fetch.md`), sorted.
79
- *
80
- * Keys are POSIX on every platform so the committed baseline is identical on a
81
- * Windows checkout. Top-level files keep their bare filename, exactly as the
82
- * non-recursive `measureMdFiles` keys them, so existing consumers that look up
83
- * `baseline['plan-phase.md']` keep working.
84
- *
85
- * @param {string} dir - Directory to walk.
86
- * @param {function(string): boolean} [predicate] - Filter on the POSIX relative path.
87
- * @returns {string[]} Sorted POSIX relative paths.
88
- */
89
- function listMdTree(dir, predicate = () => true) {
90
- const out = [];
91
- const walk = (abs, rel) => {
92
- for (const entry of fs.readdirSync(abs, { withFileTypes: true })) {
93
- const childRel = rel ? `${rel}/${entry.name}` : entry.name;
94
- const childAbs = path.join(abs, entry.name);
95
- if (entry.isDirectory()) walk(childAbs, childRel);
96
- else if (entry.isFile() && entry.name.endsWith('.md') && predicate(childRel)) {
97
- out.push(childRel);
98
- }
99
- }
100
- };
101
- walk(dir, '');
102
- return out.sort();
103
- }
104
-
105
- /**
106
- * Measure every `.md` file under `dir`, recursively, keyed by POSIX relative
107
- * path (see `listMdTree`), LF byte sizes.
108
- *
109
- * @param {string} dir - Directory to walk.
110
- * @param {function(string): boolean} [predicate] - Filter on the POSIX relative path.
111
- * @returns {Object<string, number>} Map of relative path → LF byte size, keys sorted.
112
- */
113
- function measureMdTree(dir, predicate = () => true) {
114
- const out = {};
115
- for (const rel of listMdTree(dir, predicate)) {
116
- out[rel] = lfByteCount(path.join(dir, ...rel.split('/')));
117
- }
118
- return out;
119
- }
120
-
121
- /**
122
- * Measure every workflow `.md` file — top-level AND nested (`steps/`, `modes/`,
123
- * `templates/`, ...) — keyed by POSIX relative path. Recursive since the
124
- * workflow size budget policy of 2026-10-11: a nested step file is loaded into
125
- * context just like its parent, so growth there must be visible too.
75
+ * Measure every top-level workflow file, keyed by filename (`<stem>.md`).
126
76
  *
127
77
  * @param {string} [dir] - Workflows directory (defaults to the canonical one).
128
- * @returns {Object<string, number>} Map of relative path → LF byte size, sorted.
78
+ * @returns {Object<string, number>} Map of `<stem>.md` → LF byte size, sorted.
129
79
  */
130
80
  function measureWorkflows(dir = WORKFLOWS_DIR) {
131
- return measureMdTree(dir);
81
+ return measureMdFiles(dir);
132
82
  }
133
83
 
134
84
  module.exports = {
135
85
  WORKFLOWS_DIR,
136
86
  lfByteCount,
137
87
  listWorkflowStems,
138
- listMdTree,
139
88
  measureMdFiles,
140
- measureMdTree,
141
89
  measureWorkflows,
142
90
  };
@@ -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
- }