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