@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
@@ -1,480 +0,0 @@
1
- "use strict";
2
- /**
3
- * Phase tier — the single owner of how much ceremony a phase runs.
4
- *
5
- * A tier is a named preset over the existing gate booleans
6
- * `workflow.{research, pattern_mapper, plan_check, verifier,
7
- * nyquist_validation, code_review, post_planning_gaps, security_enforcement,
8
- * vault_review}`:
9
- *
10
- * light — planner and executor only; every gate off.
11
- * standard — verifier and code review (with its fix loop) on, plan checker
12
- * ONE pass and no bounce; everything else off. Schema default.
13
- * heavy — every gate on, the plan-checker loop up to its maximum
14
- * (`PLAN_CHECK_MAX_PASSES`, the plan-phase revision cap), plan
15
- * bounce on, and the fresh-install verification slot
16
- * (`install_verification`).
17
- *
18
- * `vault_review` is the agent practice review (`pp-vault-reviewer`): on only in
19
- * heavy, and spawned only where the project's vault practices apply
20
- * (`vaultReviewApplies`, src/vault.cts). The vault's load, trace and audit gates
21
- * are NOT tier gates: they are cheap code checks and run in every tier.
22
- *
23
- * ## Precedence (`resolvePhaseTier`)
24
- *
25
- * CLI `--tier` > the phase's ROADMAP entry `**Tier:**` field > project
26
- * `phase.tier` in `.planning/config.json` > schema default `standard`.
27
- *
28
- * The ROADMAP entry is the ONE owner of the phase field: it exists for every
29
- * phase before discuss runs, and a light phase may skip discuss (so a
30
- * CONTEXT.md field would be missing exactly where it matters).
31
- *
32
- * A gate boolean the project EXPLICITLY sets to `false` in its own config
33
- * wins over every tier (listed in `suppressed` when the tier wanted it on). A
34
- * boolean the project explicitly sets to `true` does NOT force it on: the tier
35
- * decides on-ness; the project can only switch a gate off. "Explicit" means
36
- * present in the project's own config.json (workstream, then root) — a value
37
- * inherited from the manifest default is not explicit.
38
- *
39
- * ## Proposal (`proposePhaseTier`)
40
- *
41
- * heavy — a plan touches a gate (the vault's gate, verdict and redaction
42
- * owners included), init, redaction, config-precedence or
43
- * hook-dispatch file (`HEAVY_PATH_RULES`), or — before any plan
44
- * exists — the requirement tags or ROADMAP text name one
45
- * (`HEAVY_TEXT_RULES`);
46
- * light — plans exist and none touches a source file (`isSourceFile`,
47
- * the plan-graph predicate);
48
- * standard — otherwise.
49
- *
50
- * Readers: `init plan-phase` / `init execute-phase` / `init verify-work`,
51
- * `loop render-hooks --phase`, `phase.dispatch-plan`, `phase.tier` and
52
- * `phase.tier-propose`. Workflows never resolve a tier themselves.
53
- */
54
- var __importDefault = (this && this.__importDefault) || function (mod) {
55
- return (mod && mod.__esModule) ? mod : { "default": mod };
56
- };
57
- Object.defineProperty(exports, "__esModule", { value: true });
58
- exports.HEAVY_TEXT_RULES = exports.HEAVY_PATH_RULES = exports.TIER_PRESETS = exports.DEFAULT_PLAN_BOUNCE_PASSES = exports.PLAN_CHECK_MAX_PASSES = exports.TIER_GATE_KEYS = exports.SCHEMA_DEFAULT_TIER = exports.PHASE_TIERS = void 0;
59
- exports.isPhaseTier = isPhaseTier;
60
- exports.resolvePhaseTier = resolvePhaseTier;
61
- exports.heavyCategoryForPath = heavyCategoryForPath;
62
- exports.proposePhaseTier = proposePhaseTier;
63
- exports.readProjectTierConfig = readProjectTierConfig;
64
- exports.parsePhaseTierField = parsePhaseTierField;
65
- exports.readPhaseRecord = readPhaseRecord;
66
- exports.resolvePhaseTierForProject = resolvePhaseTierForProject;
67
- exports.overlayTierGates = overlayTierGates;
68
- exports.recordPhaseTier = recordPhaseTier;
69
- exports.parseTierFlag = parseTierFlag;
70
- const node_fs_1 = __importDefault(require("node:fs"));
71
- const node_path_1 = __importDefault(require("node:path"));
72
- const plan_graph_cjs_1 = require("./plan-graph.cjs");
73
- // eslint-disable-next-line @typescript-eslint/no-require-imports
74
- const planningWorkspace = require("./planning-workspace.cjs");
75
- // eslint-disable-next-line @typescript-eslint/no-require-imports
76
- const roadmapParser = require("./roadmap-parser.cjs");
77
- // eslint-disable-next-line @typescript-eslint/no-require-imports
78
- const phaseIdModule = require("./phase-id.cjs");
79
- const { planningDir, planningRoot, withPlanningLock } = planningWorkspace;
80
- const { getRoadmapPhaseInternal } = roadmapParser;
81
- const { phaseMarkdownRegexSource } = phaseIdModule;
82
- exports.PHASE_TIERS = ['light', 'standard', 'heavy'];
83
- exports.SCHEMA_DEFAULT_TIER = 'standard';
84
- exports.TIER_GATE_KEYS = [
85
- 'research',
86
- 'pattern_mapper',
87
- 'plan_check',
88
- 'verifier',
89
- 'nyquist_validation',
90
- 'code_review',
91
- 'post_planning_gaps',
92
- 'security_enforcement',
93
- 'vault_review',
94
- ];
95
- /**
96
- * The plan-checker loop's current maximum: plan-phase §12 "Revision Loop (Max
97
- * 3 Iterations)" — the checker runs once after planning and at most twice more
98
- * after a revision (verify-work's gap-plan loop has the same cap).
99
- */
100
- exports.PLAN_CHECK_MAX_PASSES = 3;
101
- /** `workflow.plan_bounce_passes` schema default (config-defaults manifest). */
102
- exports.DEFAULT_PLAN_BOUNCE_PASSES = 2;
103
- function gates(on) {
104
- const out = {};
105
- for (const k of exports.TIER_GATE_KEYS)
106
- out[k] = on.includes(k);
107
- return out;
108
- }
109
- exports.TIER_PRESETS = Object.freeze({
110
- light: { gates: gates([]), plan_check_passes: 0, plan_bounce: false, install_verification: false },
111
- standard: {
112
- gates: gates(['plan_check', 'verifier', 'code_review']),
113
- plan_check_passes: 1,
114
- plan_bounce: false,
115
- install_verification: false,
116
- },
117
- heavy: {
118
- gates: gates([...exports.TIER_GATE_KEYS]),
119
- plan_check_passes: exports.PLAN_CHECK_MAX_PASSES,
120
- plan_bounce: true,
121
- install_verification: true,
122
- },
123
- });
124
- function isPhaseTier(value) {
125
- return typeof value === 'string' && exports.PHASE_TIERS.includes(value);
126
- }
127
- function isExplicitFalse(value) {
128
- return value === false || value === 'false';
129
- }
130
- function isExplicitTrue(value) {
131
- return value === true || value === 'true';
132
- }
133
- /**
134
- * Resolve the effective gate set for one phase.
135
- *
136
- * `cli` must already be a valid tier or null (the CLI rejects anything else).
137
- */
138
- function resolvePhaseTier(input) {
139
- const ignored = [];
140
- const candidates = [
141
- { source: 'cli', value: input.cli },
142
- { source: 'phase', value: input.phaseRecord?.tier },
143
- { source: 'project', value: input.config?.tier },
144
- ];
145
- let tier = exports.SCHEMA_DEFAULT_TIER;
146
- let source = 'default';
147
- for (const c of candidates) {
148
- if (c.value === null || c.value === undefined || c.value === '')
149
- continue;
150
- const v = String(c.value).trim().toLowerCase();
151
- if (isPhaseTier(v)) {
152
- tier = v;
153
- source = c.source;
154
- break;
155
- }
156
- ignored.push({ source: c.source, value: String(c.value) });
157
- }
158
- const preset = exports.TIER_PRESETS[tier];
159
- const explicit = input.config?.explicit ?? {};
160
- const suppressed = [];
161
- const notForced = [];
162
- const resolve = (key, wanted) => {
163
- const value = explicit[key];
164
- if (wanted && isExplicitFalse(value)) {
165
- suppressed.push(key);
166
- return false;
167
- }
168
- if (!wanted && isExplicitTrue(value))
169
- notForced.push(key);
170
- return wanted;
171
- };
172
- const out = {};
173
- for (const key of exports.TIER_GATE_KEYS)
174
- out[key] = resolve(key, preset.gates[key]);
175
- out.plan_check_passes = out.plan_check ? preset.plan_check_passes : 0;
176
- out.plan_bounce = resolve('plan_bounce', preset.plan_bounce);
177
- const passes = input.config?.plan_bounce_passes;
178
- out.plan_bounce_passes = out.plan_bounce
179
- ? (typeof passes === 'number' && Number.isInteger(passes) && passes > 0 ? passes : exports.DEFAULT_PLAN_BOUNCE_PASSES)
180
- : 0;
181
- out.install_verification = preset.install_verification;
182
- return { tier, source, gates: out, suppressed, not_forced: notForced, ignored };
183
- }
184
- /** `src/<name>.cts` or its compiled `pp-core/bin/lib/<name>.cjs`. */
185
- const LIB = String.raw `^(?:src|pp-core/bin/lib)/`;
186
- /**
187
- * Paths that make a phase heavy. Matched against a plan's `files_modified`
188
- * (normalised, repo-relative). A matching file must also be a source file by
189
- * the plan-graph predicate, so a test or doc named after a gate never counts.
190
- */
191
- exports.HEAVY_PATH_RULES = Object.freeze([
192
- { category: 'gate', example: 'src/ui-safety-gate.cts', pattern: new RegExp(`${LIB}(?:[^/]+/)*[^/]*gate[^/]*\\.c?[jt]s$`) },
193
- { category: 'gate', example: 'src/check-command-router.cts', pattern: new RegExp(`${LIB}(?:check-command-router|prohibition-enforcement|gap-checker|plan-graph|dispatch-plan)\\.c?[jt]s$`) },
194
- { category: 'gate', example: 'src/practices-review-check.cts', pattern: new RegExp(`${LIB}[^/]+-(?:check|guard)\\.c?[jt]s$`) },
195
- // The vault's gate, verdict and redaction owners (`vault`, `vault-trace`,
196
- // `vault-audit`, `vault-review`, `vault-practices`, …): a phase editing them
197
- // changes what the load, trace and audit gates decide and what remote text is
198
- // redacted, so it is proposed heavy.
199
- { category: 'gate', example: 'src/vault.cts', pattern: new RegExp(`${LIB}vault[^/]*\\.c?[jt]s$`) },
200
- { category: 'init', example: 'src/init.cts', pattern: new RegExp(`${LIB}init[^/]*\\.c?[jt]s$`) },
201
- { category: 'redaction', example: 'src/observability/redaction.cts', pattern: /(?:^|\/)[^/]*(?:redact|secret)[^/]*$/i },
202
- { category: 'config-precedence', example: 'src/config-loader.cts', pattern: new RegExp(`${LIB}(?:config[^/]*|configuration|federated-config|capability-activation|phase-tier)\\.c?[jt]s$`) },
203
- { category: 'config-precedence', example: 'pp-core/bin/shared/config-defaults.manifest.json', pattern: /^pp-core\/bin\/shared\/config-[^/]+\.json$/ },
204
- { category: 'hook-dispatch', example: 'hooks/pp-workflow-guard.js', pattern: /^hooks\// },
205
- { category: 'hook-dispatch', example: 'src/loop-resolver.cts', pattern: new RegExp(`${LIB}(?:loop-resolver|capability-state|runtime-hooks-surface)\\.c?[jt]s$`) },
206
- { category: 'hook-dispatch', example: 'capabilities/code-review/capability.json', pattern: /^capabilities\/[^/]+\/capability\.json$/ },
207
- { category: 'hook-dispatch', example: 'scripts/build-hooks.js', pattern: /^scripts\/build-hooks\.js$/ },
208
- ]);
209
- /** Words in requirement tags or ROADMAP text that make a not-yet-planned phase heavy. */
210
- exports.HEAVY_TEXT_RULES = Object.freeze([
211
- { category: 'gate', pattern: /\b(?:gates?|gating)\b/i },
212
- { category: 'init', pattern: /\b(?:init|pp-init|initiali[sz]ation)\b/i },
213
- { category: 'redaction', pattern: /\b(?:redact(?:s|ed|ion)?|secrets?)\b/i },
214
- { category: 'config-precedence', pattern: /\bconfig(?:uration)?\s+(?:precedence|resolution|loader|defaults)\b|\bprecedence\b/i },
215
- { category: 'hook-dispatch', pattern: /\bhooks?\b[^.\n]{0,40}\bdispatch\w*\b|\bloop[- ]hooks?\b|\bcapability registry\b/i },
216
- ]);
217
- /** Requirement-tag prefixes that name a heavy category (e.g. `INIT-02`). */
218
- const HEAVY_TAG_PREFIX = Object.freeze([
219
- { category: 'gate', pattern: /^GATES?-/i },
220
- { category: 'init', pattern: /^INIT-/i },
221
- { category: 'redaction', pattern: /^(?:REDACT|REDACTION|SECRETS?)-/i },
222
- { category: 'config-precedence', pattern: /^(?:CONFIG|CFG|PREC)-/i },
223
- { category: 'hook-dispatch', pattern: /^HOOKS?-/i },
224
- ]);
225
- function normalizePath(file) {
226
- return String(file).trim().replace(/\\/g, '/').replace(/^\.\//, '');
227
- }
228
- function heavyCategoryForPath(file) {
229
- const f = normalizePath(file);
230
- if (f === '' || !(0, plan_graph_cjs_1.isSourceFile)(f))
231
- return null;
232
- for (const rule of exports.HEAVY_PATH_RULES)
233
- if (rule.pattern.test(f))
234
- return rule;
235
- return null;
236
- }
237
- function proposePhaseTier(input) {
238
- const plans = input.plans ?? [];
239
- if (plans.length > 0) {
240
- const heavy = [];
241
- const source = [];
242
- for (const p of plans) {
243
- const files = (p.files_modified ?? []).map(normalizePath).filter((f) => f !== '');
244
- if (files.length === 0)
245
- source.push({ category: 'undeclared', plan: p.id });
246
- for (const f of files) {
247
- const rule = heavyCategoryForPath(f);
248
- if (rule)
249
- heavy.push({ category: rule.category, plan: p.id, file: f });
250
- else if ((0, plan_graph_cjs_1.isSourceFile)(f))
251
- source.push({ category: 'source', plan: p.id, file: f });
252
- }
253
- }
254
- if (heavy.length > 0) {
255
- const cats = [...new Set(heavy.map((m) => m.category))].join(', ');
256
- return {
257
- tier: 'heavy',
258
- basis: 'plans',
259
- reason: `a plan touches ${cats} code (${heavy.slice(0, 3).map((m) => `${m.plan}: ${m.file}`).join('; ')}${heavy.length > 3 ? '; …' : ''})`,
260
- matched: heavy,
261
- };
262
- }
263
- if (source.length === 0) {
264
- return { tier: 'light', basis: 'plans', reason: 'no plan touches a source file; only docs, tests, workflow and planning files', matched: [] };
265
- }
266
- const undeclared = source.filter((m) => m.category === 'undeclared').map((m) => m.plan);
267
- return {
268
- tier: 'standard',
269
- basis: 'plans',
270
- reason: undeclared.length > 0 && undeclared.length === source.length
271
- ? `plan(s) ${undeclared.join(', ')} declare no files_modified, so source is assumed`
272
- : 'plans touch source files, none of them gate, init, redaction, config-precedence or hook-dispatch code',
273
- matched: source,
274
- };
275
- }
276
- const matched = [];
277
- for (const tag of input.requirements ?? []) {
278
- const t = String(tag).trim();
279
- for (const r of HEAVY_TAG_PREFIX)
280
- if (r.pattern.test(t))
281
- matched.push({ category: r.category, text: t });
282
- }
283
- // The phase's own **Tier:** line (and a recorded proposal's reason) is never evidence.
284
- const text = (input.roadmapText ?? '').replace(/^\*\*Tier(?::\*\*|\*\*:)[^\n]*$/gim, '');
285
- for (const r of exports.HEAVY_TEXT_RULES) {
286
- const m = r.pattern.exec(text);
287
- if (m)
288
- matched.push({ category: r.category, text: m[0] });
289
- }
290
- if (matched.length > 0) {
291
- const cats = [...new Set(matched.map((m) => m.category))].join(', ');
292
- return { tier: 'heavy', basis: 'text', reason: `no plans yet; the requirements or ROADMAP entry name ${cats} work (${matched.slice(0, 3).map((m) => `"${m.text}"`).join(', ')})`, matched };
293
- }
294
- return { tier: 'standard', basis: 'text', reason: 'no plans yet and nothing names gate, init, redaction, config-precedence or hook-dispatch work; planning re-proposes from files_modified', matched: [] };
295
- }
296
- // ─── I/O: project config, phase record, recording ─────────────────────────────
297
- const FLAT_ALIASES = {
298
- research: 'research',
299
- plan_check: 'plan_checker',
300
- verifier: 'verifier',
301
- nyquist_validation: 'nyquist_validation',
302
- post_planning_gaps: 'post_planning_gaps',
303
- };
304
- function readJson(file) {
305
- try {
306
- const parsed = JSON.parse(node_fs_1.default.readFileSync(file, 'utf-8'));
307
- return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
308
- }
309
- catch {
310
- return null;
311
- }
312
- }
313
- function lookup(cfg, section, key, flat) {
314
- if (flat !== undefined && cfg[flat] !== undefined)
315
- return cfg[flat];
316
- const sec = cfg[section];
317
- if (sec && typeof sec === 'object' && !Array.isArray(sec)) {
318
- const v = sec[key];
319
- if (v !== undefined)
320
- return v;
321
- }
322
- return undefined;
323
- }
324
- /**
325
- * The project's own config (workstream config.json first, then the root
326
- * config.json): `phase.tier`, the gate booleans actually written there, and
327
- * `workflow.plan_bounce_passes`. Manifest defaults are never read here.
328
- */
329
- function readProjectTierConfig(cwd) {
330
- const files = [node_path_1.default.join(planningDir(cwd), 'config.json')];
331
- const root = node_path_1.default.join(planningRoot(cwd), 'config.json');
332
- if (root !== files[0])
333
- files.push(root);
334
- const configs = files.map(readJson).filter((c) => c !== null);
335
- const first = (section, key, flat) => {
336
- for (const c of configs) {
337
- const v = lookup(c, section, key, flat);
338
- if (v !== undefined)
339
- return v;
340
- }
341
- return undefined;
342
- };
343
- const explicit = {};
344
- for (const key of [...exports.TIER_GATE_KEYS, 'plan_bounce']) {
345
- const v = first('workflow', key, FLAT_ALIASES[key]);
346
- if (v !== undefined)
347
- explicit[key] = v;
348
- }
349
- const tier = first('phase', 'tier');
350
- const passes = first('workflow', 'plan_bounce_passes');
351
- const script = first('workflow', 'plan_bounce_script');
352
- return {
353
- tier: typeof tier === 'string' ? tier : null,
354
- explicit,
355
- plan_bounce_passes: typeof passes === 'number' ? passes : null,
356
- plan_bounce_script: typeof script === 'string' && script.trim() !== '' ? script : null,
357
- };
358
- }
359
- const TIER_FIELD_RE = /^\*\*Tier(?::\*\*|\*\*:)[^\S\n]*([A-Za-z-]+)([^\n]*)$/im;
360
- /** The `**Tier:**` field of a ROADMAP phase section, or null when absent. */
361
- function parsePhaseTierField(section) {
362
- if (!section)
363
- return null;
364
- const m = TIER_FIELD_RE.exec(section);
365
- if (!m)
366
- return null;
367
- return { tier: m[1].toLowerCase(), proposed: /\(\s*proposed\b/i.test(m[2]), line: m[0] };
368
- }
369
- function readPhaseRecord(cwd, phase) {
370
- const rp = getRoadmapPhaseInternal(cwd, phase);
371
- const section = rp?.section ?? null;
372
- const field = parsePhaseTierField(section);
373
- return {
374
- tier: field?.tier ?? null,
375
- proposed: field?.proposed ?? false,
376
- section,
377
- phase_name: rp?.phase_name ?? null,
378
- };
379
- }
380
- /** Resolve the tier of `phase` in the project at `cwd` (CLI value already validated). */
381
- function resolvePhaseTierForProject(cwd, phase, cli) {
382
- const config = readProjectTierConfig(cwd);
383
- const record = phase ? readPhaseRecord(cwd, phase) : null;
384
- const phaseRecord = record && record.tier !== null ? { tier: record.tier, proposed: record.proposed } : null;
385
- const resolved = resolvePhaseTier({ cli, phaseRecord, config });
386
- return { ...resolved, phase_record: phaseRecord, project_tier: config.tier ?? null, plan_bounce_script: config.plan_bounce_script };
387
- }
388
- /**
389
- * The gate booleans of a resolved tier as a config overlay
390
- * (`{ workflow: { research: …, … } }`), merged over `base` without mutating
391
- * it. Used by `loop render-hooks --phase` so capability `when` keys and
392
- * activation keys read the tier-resolved values first.
393
- */
394
- function overlayTierGates(base, resolved) {
395
- const wf = base['workflow'] && typeof base['workflow'] === 'object' && !Array.isArray(base['workflow'])
396
- ? { ...base['workflow'] }
397
- : {};
398
- for (const key of exports.TIER_GATE_KEYS)
399
- wf[key] = resolved.gates[key];
400
- wf['plan_bounce'] = resolved.gates.plan_bounce;
401
- const out = { ...base, workflow: wf };
402
- for (const [key, flat] of Object.entries(FLAT_ALIASES)) {
403
- if (flat !== undefined && flat in base)
404
- out[flat] = resolved.gates[key];
405
- }
406
- return out;
407
- }
408
- /**
409
- * Write `**Tier:** <tier>` into the phase's ROADMAP entry (current milestone
410
- * only). A proposal (`proposed: true`) is written as `**Tier:** <tier>
411
- * (proposed: <reason>)` and never replaces an operator choice (a field without
412
- * the `(proposed` marker). An operator choice always replaces the field.
413
- */
414
- function recordPhaseTier(cwd, phase, tier, opts) {
415
- const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
416
- if (!node_fs_1.default.existsSync(roadmapPath))
417
- return { recorded: false, tier: null, proposed: false, reason: 'ROADMAP.md not found' };
418
- let result = { recorded: false, tier: null, proposed: false, reason: 'phase entry not found in ROADMAP.md' };
419
- withPlanningLock(cwd, () => {
420
- const content = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
421
- const lastClose = content.lastIndexOf('</details>');
422
- const offset = lastClose === -1 ? 0 : lastClose + '</details>'.length;
423
- const scope = content.slice(offset);
424
- const header = new RegExp(`^#{2,4}\\s*(?:\\[[^\\]]+\\]\\s*)?Phase\\s+${phaseMarkdownRegexSource(phase)}\\s*:[^\\n]*$`, 'im');
425
- const hm = header.exec(scope);
426
- if (!hm)
427
- return;
428
- const start = hm.index;
429
- const after = scope.slice(start + hm[0].length);
430
- const next = /\n#{2,4}\s+(?:\[[^\]]+\]\s*)?Phase\s+[\w]|\n#{1,2}\s+(?!#)/.exec(after);
431
- const end = start + hm[0].length + (next ? next.index : after.length);
432
- const section = scope.slice(start, end);
433
- const existing = parsePhaseTierField(section);
434
- if (existing && opts.proposed && !existing.proposed) {
435
- result = { recorded: false, tier: existing.tier, proposed: false, reason: `kept the operator's choice (${existing.tier})` };
436
- return;
437
- }
438
- const reason = (opts.reason ?? '').replace(/\s+/g, ' ').trim();
439
- const line = opts.proposed ? `**Tier:** ${tier} (proposed${reason ? `: ${reason}` : ''})` : `**Tier:** ${tier}`;
440
- let newSection;
441
- if (existing) {
442
- newSection = section.replace(existing.line, line);
443
- }
444
- else {
445
- const goal = /^\*\*Goal(?:\*\*:|\*?\*?:\*\*)[^\n]*$/im.exec(section);
446
- const anchorEnd = goal ? goal.index + goal[0].length : hm[0].length;
447
- newSection = `${section.slice(0, anchorEnd)}\n${line}${section.slice(anchorEnd)}`;
448
- }
449
- if (newSection === section) {
450
- result = { recorded: false, tier, proposed: opts.proposed, reason: 'already recorded' };
451
- return;
452
- }
453
- const updated = content.slice(0, offset) + scope.slice(0, start) + newSection + scope.slice(end);
454
- node_fs_1.default.writeFileSync(roadmapPath, updated, 'utf-8');
455
- result = { recorded: true, tier, proposed: opts.proposed, reason: opts.proposed ? 'proposal recorded' : 'operator choice recorded' };
456
- });
457
- return result;
458
- }
459
- /** Parse a `--tier` value; returns the tier, null when absent, or an error message. */
460
- function parseTierFlag(args) {
461
- const rest = [];
462
- let tier = null;
463
- for (let i = 0; i < args.length; i++) {
464
- const a = args[i];
465
- let value;
466
- if (a === '--tier')
467
- value = args[++i];
468
- else if (a.startsWith('--tier='))
469
- value = a.slice('--tier='.length);
470
- else {
471
- rest.push(a);
472
- continue;
473
- }
474
- const v = (value ?? '').trim().toLowerCase();
475
- if (!isPhaseTier(v))
476
- return { error: `--tier needs one of ${exports.PHASE_TIERS.join(', ')}` };
477
- tier = v;
478
- }
479
- return { tier, rest };
480
- }