@pareto4/pp-core 1.20.0 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/agents/pp-nyquist-auditor.md +19 -14
  3. package/agents/pp-plan-checker.md +59 -36
  4. package/agents/pp-planner.md +34 -53
  5. package/agents/pp-verifier.md +3 -52
  6. package/commands/pp-autonomous.md +2 -1
  7. package/commands/pp-plan-phase.md +2 -1
  8. package/commands/pp-plan-review-convergence.md +2 -1
  9. package/gemini-extension.json +1 -1
  10. package/package.json +1 -1
  11. package/pp-core/bin/lib/capability-activation.cjs +77 -0
  12. package/pp-core/bin/lib/capability-registry.cjs +14 -14
  13. package/pp-core/bin/lib/capability-state.cjs +8 -18
  14. package/pp-core/bin/lib/ceremony.cjs +1342 -0
  15. package/pp-core/bin/lib/check-command-router.cjs +62 -1
  16. package/pp-core/bin/lib/commands.cjs +29 -3
  17. package/pp-core/bin/lib/config.cjs +5 -0
  18. package/pp-core/bin/lib/init.cjs +50 -1
  19. package/pp-core/bin/lib/io.cjs +2 -0
  20. package/pp-core/bin/lib/loop-resolver.cjs +26 -12
  21. package/pp-core/bin/lib/phase.cjs +39 -3
  22. package/pp-core/bin/lib/plan-sizing.cjs +346 -0
  23. package/pp-core/bin/lib/test-substance.cjs +3190 -0
  24. package/pp-core/bin/lib/timeline.cjs +605 -0
  25. package/pp-core/bin/pp-tools.cjs +71 -4
  26. package/pp-core/bin/shared/ceremony-gates.json +17 -0
  27. package/pp-core/bin/shared/config-schema.manifest.json +1 -0
  28. package/pp-core/references/ceremony.md +81 -0
  29. package/pp-core/references/few-shot-examples/plan-checker.md +8 -8
  30. package/pp-core/references/phase-timeline.md +107 -0
  31. package/pp-core/references/planner-antipatterns.md +32 -0
  32. package/pp-core/references/planner-guidance.md +17 -14
  33. package/pp-core/references/planner-revision.md +1 -1
  34. package/pp-core/references/planning-config.md +3 -2
  35. package/pp-core/references/test-substance.md +324 -0
  36. package/pp-core/references/verifier-stub-patterns.md +51 -0
  37. package/pp-core/templates/VALIDATION.md +1 -0
  38. package/pp-core/templates/phase-prompt.md +19 -7
  39. package/pp-core/templates/roadmap.md +1 -1
  40. package/pp-core/templates/verification-report.md +1 -1
  41. package/pp-core/workflows/audit-milestone.md +4 -1
  42. package/pp-core/workflows/autonomous.md +11 -8
  43. package/pp-core/workflows/code-review.md +15 -6
  44. package/pp-core/workflows/execute-phase/steps/check-query-gate.md +7 -7
  45. package/pp-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -2
  46. package/pp-core/workflows/execute-phase.md +14 -3
  47. package/pp-core/workflows/plan-phase/steps/chunked-planning.md +7 -3
  48. package/pp-core/workflows/plan-phase/steps/prd-express.md +104 -0
  49. package/pp-core/workflows/plan-phase.md +47 -114
  50. package/pp-core/workflows/plan-review-convergence.md +6 -3
  51. package/pp-core/workflows/ship.md +2 -2
  52. package/pp-core/workflows/stats.md +19 -4
  53. package/pp-core/workflows/validate-phase.md +19 -4
  54. package/pp-core/workflows/verify-phase.md +1 -1
  55. package/pp-core/workflows/verify-work.md +1 -1
  56. package/scripts/gen-capability-registry.cjs +43 -45
  57. package/scripts/run-tests.cjs +54 -30
@@ -183,7 +183,84 @@ function _resolveActivationValue(dotKey, config, cwd, registry) {
183
183
  const r = resolveConfigKey(dotKey, { config, cwd, registry });
184
184
  return r.found ? Boolean(r.value) : false;
185
185
  }
186
+ // ─── Hook `when` evaluation (Phase 37, D-14) ──────────────────────────────────
187
+ // In .cts (CommonJS output) files `require` is a global; JSON candidates are
188
+ // loaded through it, like src/model-catalog.cts and src/ceremony.cts do.
189
+ const _require = require;
190
+ const _gateCandidates = [
191
+ node_path_1.default.resolve(__dirname, '..', 'shared', 'ceremony-gates.json'),
192
+ node_path_1.default.resolve(__dirname, '..', '..', 'pp-core', 'bin', 'shared', 'ceremony-gates.json'),
193
+ ];
194
+ /**
195
+ * The ceremony gate vocabulary (keys of pp-core/bin/shared/ceremony-gates.json),
196
+ * or null when the file cannot be read or parsed. A `ceremony.<gate>` conjunct
197
+ * is checked against this Set, never used to index an object (T-37-12), and a
198
+ * null vocabulary makes every such conjunct answer true (fail toward ceremony,
199
+ * T-37-13).
200
+ */
201
+ const CEREMONY_GATE_KEYS = (() => {
202
+ for (const candidate of _gateCandidates) {
203
+ try {
204
+ const vocab = _require(candidate);
205
+ if (!vocab || !Array.isArray(vocab.gates))
206
+ continue;
207
+ const keys = vocab.gates.map((g) => g && g.key).filter((k) => typeof k === 'string');
208
+ if (keys.length > 0)
209
+ return new Set(keys);
210
+ }
211
+ catch { /* try the next candidate */ }
212
+ }
213
+ return null;
214
+ })();
215
+ /**
216
+ * The one evaluator for a hook's `when`, shared by the loop resolver and the
217
+ * capability-state report so the two cannot drift (D-14).
218
+ *
219
+ * - undefined / null -> true (unconditional hook)
220
+ * - non-string or empty -> false (malformed registry data, FIX 3)
221
+ * - otherwise `when` is split on `&&`; every trimmed conjunct must hold and
222
+ * an empty conjunct (`a &&`, `&& a`) makes the whole condition false.
223
+ * - `ceremony.<gate>` -> true when the vocabulary is null, lacks the
224
+ * gate (also `__proto__`, `constructor`; the generator rejects those at
225
+ * build time) or no thunk was given; else the thunk's answer, where a
226
+ * throw answers true. Every doubt fails toward ceremony (T-37-13).
227
+ * - any other conjunct -> _resolveActivationValue (config precedence).
228
+ */
229
+ function evaluateWhen(when, ctx) {
230
+ if (when === undefined || when === null)
231
+ return true;
232
+ if (typeof when !== 'string' || when.length === 0)
233
+ return false;
234
+ const conjuncts = when.split('&&').map((c) => c.trim());
235
+ for (const conjunct of conjuncts) {
236
+ if (conjunct.length === 0)
237
+ return false;
238
+ if (conjunct.startsWith('ceremony.')) {
239
+ const gate = conjunct.slice('ceremony.'.length);
240
+ // WR-03: a gate the vocabulary does not know (a registry newer than the
241
+ // vocabulary, a typo past the generator) stays open, so the hook renders;
242
+ // it never reaches the thunk, so a prototype name never indexes a verdict.
243
+ if (CEREMONY_GATE_KEYS === null || !CEREMONY_GATE_KEYS.has(gate) || typeof ctx.ceremony !== 'function')
244
+ continue;
245
+ let open;
246
+ try {
247
+ open = ctx.ceremony(gate) !== false;
248
+ }
249
+ catch {
250
+ open = true;
251
+ }
252
+ if (!open)
253
+ return false;
254
+ continue;
255
+ }
256
+ if (!_resolveActivationValue(conjunct, ctx.config, ctx.cwd, ctx.registry))
257
+ return false;
258
+ }
259
+ return true;
260
+ }
186
261
  module.exports = {
262
+ evaluateWhen,
263
+ CEREMONY_GATE_KEYS,
187
264
  _getNestedConfigValue,
188
265
  // GATE-05: exported so src/config.cts's cmdConfigGet reuses this exact flat
189
266
  // lookup rather than growing a second, driftable copy of the rule.
@@ -1234,7 +1234,7 @@ const capabilities = {
1234
1234
  "consumes": [
1235
1235
  "SUMMARY.md"
1236
1236
  ],
1237
- "when": "workflow.nyquist_validation",
1237
+ "when": "workflow.nyquist_validation && ceremony.nyquist",
1238
1238
  "onError": "halt"
1239
1239
  }
1240
1240
  ],
@@ -1421,7 +1421,7 @@ const capabilities = {
1421
1421
  "consumes": [
1422
1422
  "RESEARCH.md"
1423
1423
  ],
1424
- "when": "workflow.pattern_mapper",
1424
+ "when": "workflow.pattern_mapper && ceremony.pattern_mapper",
1425
1425
  "onError": "skip"
1426
1426
  }
1427
1427
  ],
@@ -1595,7 +1595,7 @@ const capabilities = {
1595
1595
  "consumes": [
1596
1596
  "CONTEXT.md"
1597
1597
  ],
1598
- "when": "workflow.research",
1598
+ "when": "workflow.research && ceremony.research",
1599
1599
  "onError": "skip"
1600
1600
  }
1601
1601
  ],
@@ -1700,7 +1700,7 @@ const capabilities = {
1700
1700
  "consumes": [
1701
1701
  "SUMMARY.md"
1702
1702
  ],
1703
- "when": "workflow.security_enforcement",
1703
+ "when": "workflow.security_enforcement && ceremony.security_review",
1704
1704
  "onError": "halt"
1705
1705
  }
1706
1706
  ],
@@ -1719,7 +1719,7 @@ const capabilities = {
1719
1719
  "consumes": [
1720
1720
  "CONTEXT.md"
1721
1721
  ],
1722
- "when": "workflow.security_enforcement"
1722
+ "when": "workflow.security_enforcement && ceremony.threat_model"
1723
1723
  }
1724
1724
  ],
1725
1725
  "gates": [
@@ -1733,7 +1733,7 @@ const capabilities = {
1733
1733
  "equals": 0
1734
1734
  }
1735
1735
  },
1736
- "when": "workflow.security_enforcement",
1736
+ "when": "workflow.security_enforcement && ceremony.security_review",
1737
1737
  "blocking": true,
1738
1738
  "onError": "halt"
1739
1739
  }
@@ -1901,7 +1901,7 @@ const capabilities = {
1901
1901
  "consumes": [
1902
1902
  "UI-SPEC.md"
1903
1903
  ],
1904
- "when": "workflow.ui_review",
1904
+ "when": "workflow.ui_review && ceremony.ui_review",
1905
1905
  "onError": "skip"
1906
1906
  }
1907
1907
  ],
@@ -2370,7 +2370,7 @@ const byLoopPoint = {
2370
2370
  "consumes": [
2371
2371
  "CONTEXT.md"
2372
2372
  ],
2373
- "when": "workflow.research",
2373
+ "when": "workflow.research && ceremony.research",
2374
2374
  "onError": "skip"
2375
2375
  },
2376
2376
  {
@@ -2417,7 +2417,7 @@ const byLoopPoint = {
2417
2417
  "consumes": [
2418
2418
  "RESEARCH.md"
2419
2419
  ],
2420
- "when": "workflow.pattern_mapper",
2420
+ "when": "workflow.pattern_mapper && ceremony.pattern_mapper",
2421
2421
  "onError": "skip"
2422
2422
  }
2423
2423
  ],
@@ -2452,7 +2452,7 @@ const byLoopPoint = {
2452
2452
  "consumes": [
2453
2453
  "CONTEXT.md"
2454
2454
  ],
2455
- "when": "workflow.security_enforcement"
2455
+ "when": "workflow.security_enforcement && ceremony.threat_model"
2456
2456
  },
2457
2457
  {
2458
2458
  "capId": "tdd",
@@ -2726,7 +2726,7 @@ const byLoopPoint = {
2726
2726
  "consumes": [
2727
2727
  "SUMMARY.md"
2728
2728
  ],
2729
- "when": "workflow.nyquist_validation",
2729
+ "when": "workflow.nyquist_validation && ceremony.nyquist",
2730
2730
  "onError": "halt"
2731
2731
  },
2732
2732
  {
@@ -2741,7 +2741,7 @@ const byLoopPoint = {
2741
2741
  "consumes": [
2742
2742
  "SUMMARY.md"
2743
2743
  ],
2744
- "when": "workflow.security_enforcement",
2744
+ "when": "workflow.security_enforcement && ceremony.security_review",
2745
2745
  "onError": "halt"
2746
2746
  },
2747
2747
  {
@@ -2756,7 +2756,7 @@ const byLoopPoint = {
2756
2756
  "consumes": [
2757
2757
  "UI-SPEC.md"
2758
2758
  ],
2759
- "when": "workflow.ui_review",
2759
+ "when": "workflow.ui_review && ceremony.ui_review",
2760
2760
  "onError": "skip"
2761
2761
  }
2762
2762
  ],
@@ -2809,7 +2809,7 @@ const byLoopPoint = {
2809
2809
  "equals": 0
2810
2810
  }
2811
2811
  },
2812
- "when": "workflow.security_enforcement",
2812
+ "when": "workflow.security_enforcement && ceremony.security_review",
2813
2813
  "blocking": true,
2814
2814
  "onError": "halt"
2815
2815
  },
@@ -21,7 +21,7 @@
21
21
  * Dependencies (leaf modules only — no circular risk):
22
22
  * - node:path
23
23
  * - ./io.cjs (output, error)
24
- * - ./capability-activation.cjs (_resolveActivationValue)
24
+ * - ./capability-activation.cjs (_resolveActivationValue, evaluateWhen)
25
25
  * - ./install-profiles.cjs (readActiveProfile, loadSkillsManifest, resolveProfile)
26
26
  * - ./surface.cjs (resolveSurface)
27
27
  * - ./config-loader.cjs (loadConfig)
@@ -39,7 +39,7 @@ const ioMod = require("./io.cjs");
39
39
  const { output: coreOutput, error: coreError } = ioMod;
40
40
  // eslint-disable-next-line @typescript-eslint/no-require-imports
41
41
  const activationMod = require("./capability-activation.cjs");
42
- const { _resolveActivationValue } = activationMod;
42
+ const { _resolveActivationValue, evaluateWhen } = activationMod;
43
43
  // eslint-disable-next-line @typescript-eslint/no-require-imports
44
44
  const configLoaderMod = require("./config-loader.cjs");
45
45
  const { loadConfig } = configLoaderMod;
@@ -269,9 +269,9 @@ function resolveCapabilityState(input) {
269
269
  const active = baseActive && requiresGateOpen;
270
270
  // ── hooks ──────────────────────────────────────────────────────────────────
271
271
  // Collect from steps, gates, contributions. Each may have a `when` key.
272
- // Activation semantics (mirrors loop-resolver.isActive exactly):
272
+ // Activation semantics (shared with loop-resolver.isActive through evaluateWhen):
273
273
  // - No `when` field present (undefined/null) → unconditional, active=true
274
- // - Non-empty string `when` → resolve via _resolveActivationValue
274
+ // - Non-empty string `when` → every `&&` conjunct must hold
275
275
  // - Present-but-empty-string or non-string `when` → malformed, active=false
276
276
  // The original `when` value is carried through to the output for visibility.
277
277
  const hooks = [];
@@ -283,20 +283,10 @@ function resolveCapabilityState(input) {
283
283
  const point = typeof h['point'] === 'string' ? h['point'] : '';
284
284
  // Carry the raw `when` value through for visibility
285
285
  const whenRaw = h['when'];
286
- let configured;
287
- if (whenRaw === undefined || whenRaw === null) {
288
- // No `when` field → unconditional, always active
289
- configured = true;
290
- }
291
- else if (typeof whenRaw === 'string' && whenRaw.length > 0) {
292
- // Non-empty string `when` → resolve via _resolveActivationValue
293
- configured = _resolveActivationValue(whenRaw, config, cwd, registry);
294
- }
295
- else {
296
- // Present-but-empty-string or non-string `when` → malformed, inactive
297
- // (mirrors loop-resolver.isActive: `typeof when !== 'string' || when.length === 0` → false)
298
- configured = false;
299
- }
286
+ // One shared evaluator (Phase 37 D-14), called WITHOUT a ceremony thunk so
287
+ // this report is unchanged: it answers for the config switch, never for a
288
+ // phase verdict, and a `ceremony.<gate>` conjunct counts as open here.
289
+ const configured = evaluateWhen(whenRaw, { config, cwd, registry });
300
290
  // Hook active = capability-level active AND hook's own config gate.
301
291
  // The capability's `active` constant (= enabled && configActivation) is
302
292
  // used here so that a config-disabled capability (active=false) cannot