@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.
- package/.claude-plugin/plugin.json +3 -3
- package/README.md +26 -9
- package/agents/pp-code-reviewer.md +2 -1
- package/agents/pp-debug-session-manager.md +9 -6
- package/agents/pp-executor.md +10 -13
- package/agents/pp-plan-checker.md +4 -4
- package/agents/pp-planner.md +23 -9
- package/agents/pp-vault-reviewer.md +244 -21
- package/agents/pp-verifier.md +7 -3
- package/bin/install.js +10 -3
- package/commands/pp-execute-phase.md +1 -3
- package/commands/pp-ns-review.md +1 -1
- package/commands/pp-plan-phase.md +1 -2
- package/commands/pp-vault-review.md +5 -3
- package/commands/pp-verify-work.md +1 -2
- package/gemini-extension.json +1 -1
- package/hooks/dist/pp-check-update.js +0 -0
- package/hooks/dist/pp-cursor-session-start.js +1 -1
- package/hooks/dist/pp-statusline.js +1 -1
- package/hooks/dist/pp-update-banner.js +0 -0
- package/hooks/pp-check-update.js +0 -0
- package/hooks/pp-cursor-session-start.js +1 -1
- package/hooks/pp-statusline.js +1 -1
- package/hooks/pp-update-banner.js +0 -0
- package/package.json +14 -19
- package/pareto-skills/_shared/create-project-board.sh +0 -0
- package/pareto-skills/pp-project-setup/SKILL.md +1 -1
- package/pp-core/bin/check-latest-version.cjs +0 -0
- package/pp-core/bin/detect-build-commands.sh +2 -2
- package/pp-core/bin/lib/capability-activation.cjs +83 -9
- package/pp-core/bin/lib/capability-registry.cjs +396 -82
- package/pp-core/bin/lib/capability-state.cjs +160 -4
- package/pp-core/bin/lib/capability-writer.cjs +19 -5
- package/pp-core/bin/lib/check-command-router.cjs +3334 -946
- package/pp-core/bin/lib/clusters.cjs +3 -0
- package/pp-core/bin/lib/command-aliases.cjs +0 -35
- package/pp-core/bin/lib/commands.cjs +9 -4
- package/pp-core/bin/lib/compose-bindings.cjs +251 -88
- package/pp-core/bin/lib/config-loader.cjs +7 -103
- package/pp-core/bin/lib/config-schema.cjs +1 -1
- package/pp-core/bin/lib/config.cjs +154 -62
- package/pp-core/bin/lib/decisions.cjs +4 -4
- package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
- package/pp-core/bin/lib/frontmatter.cjs +1 -38
- package/pp-core/bin/lib/git-base-branch.cjs +13 -11
- package/pp-core/bin/lib/git-provider.cjs +4 -4
- package/pp-core/bin/lib/init-command-router.cjs +5 -8
- package/pp-core/bin/lib/init-contract.cjs +648 -738
- package/pp-core/bin/lib/init-interactive.cjs +76 -4
- package/pp-core/bin/lib/init.cjs +24 -51
- package/pp-core/bin/lib/install-profiles.cjs +1 -1
- package/pp-core/bin/lib/io.cjs +8 -0
- package/pp-core/bin/lib/loop-resolver.cjs +189 -129
- package/pp-core/bin/lib/org-profile.cjs +469 -152
- package/pp-core/bin/lib/package-identity.cjs +3 -3
- package/pp-core/bin/lib/phase-command-router.cjs +1 -29
- package/pp-core/bin/lib/phase-scope.cjs +1 -1
- package/pp-core/bin/lib/phase.cjs +11 -253
- package/pp-core/bin/lib/platform-bindings.cjs +40 -19
- package/pp-core/bin/lib/practices-review-check.cjs +15 -9
- package/pp-core/bin/lib/profile-output.cjs +1 -1
- package/pp-core/bin/lib/shell-command-projection.cjs +104 -1
- package/pp-core/bin/lib/state.cjs +11 -85
- package/pp-core/bin/lib/template.cjs +1 -1
- package/pp-core/bin/lib/vault-audit.cjs +650 -126
- package/pp-core/bin/lib/vault-command-router.cjs +2604 -367
- package/pp-core/bin/lib/vault-coordinates.cjs +13 -4
- package/pp-core/bin/lib/vault-index.cjs +699 -0
- package/pp-core/bin/lib/vault-mode.cjs +235 -0
- package/pp-core/bin/lib/vault-practices.cjs +139 -72
- package/pp-core/bin/lib/vault-review.cjs +194 -451
- package/pp-core/bin/lib/vault-seal.cjs +1936 -0
- package/pp-core/bin/lib/vault-standards.cjs +1505 -0
- package/pp-core/bin/lib/vault-trace.cjs +607 -254
- package/pp-core/bin/lib/vault-update.cjs +9 -33
- package/pp-core/bin/lib/vault-write-back.cjs +70 -42
- package/pp-core/bin/lib/vault.cjs +2734 -920
- package/pp-core/bin/lib/verify.cjs +0 -11
- package/pp-core/bin/portable-timeout.sh +1 -1
- package/pp-core/bin/pp-tools.cjs +184 -54
- package/pp-core/bin/shared/config-defaults.manifest.json +0 -7
- package/pp-core/bin/shared/config-schema.manifest.json +0 -3
- package/pp-core/bin/shared/model-catalog.json +2 -2
- package/pp-core/bin/verify-reapply-patches.cjs +0 -0
- package/pp-core/org-profile.json +26 -0
- package/pp-core/references/autonomous-best-effort.md +8 -7
- package/pp-core/references/loop-hook-dispatch.md +0 -25
- package/pp-core/references/planner-gap-closure.md +0 -1
- package/pp-core/references/planner-tdd.md +0 -1
- package/pp-core/references/planner-vault-practices.md +17 -34
- package/pp-core/references/planner-vault-standards.md +320 -0
- package/pp-core/references/planning-config.md +0 -8
- package/pp-core/references/practice-arithmetic-contract.md +121 -0
- package/pp-core/references/tdd.md +0 -1
- package/pp-core/references/verifier-practice-verdicts.md +263 -0
- package/pp-core/templates/README.md +1 -0
- package/pp-core/templates/VAULT-REVIEW.md +202 -0
- package/pp-core/templates/phase-prompt.md +2 -6
- package/pp-core/templates/practices/README.md +20 -23
- package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
- package/pp-core/workflows/add-tests.md +55 -4
- package/pp-core/workflows/audit-fix.md +63 -2
- package/pp-core/workflows/audit-milestone.md +1 -1
- package/pp-core/workflows/autonomous.md +85 -11
- package/pp-core/workflows/check-todos.md +13 -2
- package/pp-core/workflows/code-review-fix.md +59 -3
- package/pp-core/workflows/code-review.md +10 -1
- package/pp-core/workflows/complete-milestone.md +1 -1
- package/pp-core/workflows/debug.md +54 -4
- package/pp-core/workflows/discovery-sprint/prefill.md +19 -7
- package/pp-core/workflows/discovery-sprint/prototype.md +46 -49
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +2 -0
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +20 -24
- package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/06-security.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +1 -6
- package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +1 -3
- package/pp-core/workflows/discovery-sprint/templates/context-template.md +1 -1
- package/pp-core/workflows/discovery-sprint/vault-context.md +213 -0
- package/pp-core/workflows/discovery-sprint.md +7 -3
- package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +4 -4
- package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +43 -1
- package/pp-core/workflows/discovery-to-build.md +10 -2
- package/pp-core/workflows/discuss-phase.md +3 -10
- package/pp-core/workflows/execute-phase/steps/check-query-gate.md +329 -0
- package/pp-core/workflows/execute-phase/steps/client-context-gate.md +3 -2
- package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -1
- package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +98 -0
- package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
- package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +6 -8
- package/pp-core/workflows/execute-phase/steps/vault-fetch.md +115 -14
- package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +322 -20
- package/pp-core/workflows/execute-phase/steps/verifier-practice-payload.md +165 -0
- package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +163 -0
- package/pp-core/workflows/execute-phase.md +155 -128
- package/pp-core/workflows/execute-plan.md +56 -6
- package/pp-core/workflows/fast.md +53 -4
- package/pp-core/workflows/help/modes/full.md +3 -3
- package/pp-core/workflows/init/steps/client-context-capture.md +101 -0
- package/pp-core/workflows/init/steps/gitlab-provisioning.md +130 -0
- package/pp-core/workflows/init/steps/project-md-deferral-narration.md +66 -0
- package/pp-core/workflows/init/steps/reachability-outcome.md +70 -0
- package/pp-core/workflows/init.md +232 -272
- package/pp-core/workflows/new-project.md +31 -23
- package/pp-core/workflows/plan-phase.md +98 -45
- package/pp-core/workflows/quick.md +117 -3
- package/pp-core/workflows/secure-phase.md +1 -1
- package/pp-core/workflows/ship.md +57 -28
- package/pp-core/workflows/undo.md +1 -1
- package/pp-core/workflows/update.md +3 -3
- package/pp-core/workflows/validate-phase.md +58 -3
- package/pp-core/workflows/vault-review.md +282 -22
- package/pp-core/workflows/verify-work.md +12 -14
- package/scripts/changeset/cli.cjs +6 -5
- package/scripts/changeset/lint.cjs +0 -0
- package/scripts/changeset/new.cjs +33 -50
- package/scripts/dev-env.cjs +25 -85
- package/scripts/gen-capability-registry.cjs +199 -11
- package/scripts/gen-practice-arithmetic-contract.cjs +412 -0
- package/scripts/lib/allowlist-ratchet.cjs +1 -103
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
- package/scripts/lint-docs-required.cjs +0 -0
- package/scripts/lint-pr-check-project-dir.cjs +1 -3
- package/scripts/lint-test-file-count.allowlist.json +12 -10
- package/scripts/practice-arithmetic-contract.allow.json +35 -0
- package/scripts/prompt-injection-scan.sh +22 -17
- package/scripts/record-vault-mode-golden.cjs +89 -0
- package/scripts/release-channel.cjs +0 -0
- package/scripts/sync-manifest-versions.cjs +5 -4
- package/scripts/sync-rulesets.sh +1 -1
- package/scripts/update-size-baseline.cjs +30 -274
- package/scripts/vault-calibrate.cjs +996 -0
- package/scripts/verify-opengsd-sweep.cjs +3 -5
- package/scripts/workflow-size.cjs +9 -61
- package/pp-core/bin/lib/dispatch-plan.cjs +0 -483
- package/pp-core/bin/lib/install-run.cjs +0 -536
- package/pp-core/bin/lib/loop-gates.cjs +0 -213
- package/pp-core/bin/lib/phase-manifest.cjs +0 -527
- package/pp-core/bin/lib/phase-tier.cjs +0 -480
- package/pp-core/bin/lib/plan-graph.cjs +0 -368
- package/pp-core/references/executor-vault-trace.md +0 -47
- package/pp-core/references/planner-plan-graph.md +0 -66
- package/pp-core/workflows/discovery-sprint/vault-fetch.md +0 -37
- package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +0 -45
- package/pp-core/workflows/execute-phase/steps/install-run.md +0 -29
- package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +0 -23
- package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +0 -73
- package/pp-core/workflows/execute-phase/steps/vault-door-gate.md +0 -19
- package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -43
- package/scripts/lib/size-budget.cjs +0 -311
- package/scripts/lint-neutrality-names.cjs +0 -694
- package/scripts/lint-org-coordinates.allowlist.json +0 -35
- package/scripts/lint-org-coordinates.cjs +0 -426
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Command surface: pp-tools loop render-hooks <point>
|
|
14
14
|
*
|
|
15
15
|
* Exports (three things):
|
|
16
|
-
* resolveLoopHooks({ point, registry, config }) → { point, activeHooks }
|
|
16
|
+
* resolveLoopHooks({ point, registry, config, vaultMode?, recordedArtifacts? }) → { point, activeHooks }
|
|
17
17
|
* renderLoopHooks(resolved) → markdown string
|
|
18
18
|
* cmdLoopRenderHooks(cwd, point, raw, options) — I/O entry point
|
|
19
19
|
*
|
|
@@ -30,19 +30,17 @@
|
|
|
30
30
|
*/
|
|
31
31
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
32
32
|
const ioMod = require("./io.cjs");
|
|
33
|
-
const { output: coreOutput, error: coreError
|
|
33
|
+
const { output: coreOutput, error: coreError } = ioMod;
|
|
34
34
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
35
35
|
const configLoaderModule = require("./config-loader.cjs");
|
|
36
|
-
const { loadConfig
|
|
36
|
+
const { loadConfig } = configLoaderModule;
|
|
37
37
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
38
38
|
const capabilityStateModule = require("./capability-state.cjs");
|
|
39
|
-
const { resolveCapabilityRuntimeState } = capabilityStateModule;
|
|
39
|
+
const { resolveCapabilityRuntimeState, vaultModeFilterVerdict, resolveInjectedVaultMode, SEAL_FIELD, sealRenderVerdict, resolveInjectedRecordedArtifacts, resolveInjectedPlatformBindingsPresent, } = capabilityStateModule;
|
|
40
40
|
// ─── Capability-activation engine (single owner for config-key precedence) ────
|
|
41
41
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
42
42
|
const capabilityActivationModule = require("./capability-activation.cjs");
|
|
43
43
|
const { _getNestedConfigValue, _readRawConfigKey, _resolveActivationValue, resolveConfigKey } = capabilityActivationModule;
|
|
44
|
-
// ─── Phase ceremony tier (pack 9): `--phase` / `--tier` overlay ───────────────
|
|
45
|
-
const phase_tier_cjs_1 = require("./phase-tier.cjs");
|
|
46
44
|
// ─── Canonical points (derived from LOOP_HOST_CONTRACT — authoritative 12) ───
|
|
47
45
|
// FIX 2: Derive the authoritative canonical set from LOOP_HOST_CONTRACT so it
|
|
48
46
|
// cannot drift from the host contract. CANONICAL_POINTS_FALLBACK is kept as an
|
|
@@ -105,7 +103,7 @@ function _getCanonicalPoints(_registry) {
|
|
|
105
103
|
* resolved against `config`; active iff truthy. Inactive hooks are filtered out.
|
|
106
104
|
*/
|
|
107
105
|
function resolveLoopHooks(input) {
|
|
108
|
-
const { point, registry, config, cwd, capabilityStatesById } = input;
|
|
106
|
+
const { point, registry, config, cwd, capabilityStatesById, vaultMode, recordedArtifacts, platformBindingsPresent, } = input;
|
|
109
107
|
// Validate point
|
|
110
108
|
const canonicalPoints = _getCanonicalPoints(registry);
|
|
111
109
|
if (!canonicalPoints.includes(point)) {
|
|
@@ -124,6 +122,12 @@ function resolveLoopHooks(input) {
|
|
|
124
122
|
}
|
|
125
123
|
const entryMap = entry;
|
|
126
124
|
const activeHooks = [];
|
|
125
|
+
// Phase 15 D-02/D-28: the per-hook `vaultMode` filter, one rule owned by
|
|
126
|
+
// capability-state (`vaultModeFilterVerdict`), evaluated against the injected
|
|
127
|
+
// mode VALUE. The field is read here and never copied into an ActiveHook.
|
|
128
|
+
function modeOk(hook, kind) {
|
|
129
|
+
return vaultModeFilterVerdict(hook['vaultMode'], kind, vaultMode);
|
|
130
|
+
}
|
|
127
131
|
// Helper: check activation using single-key precedence resolver (FIX 1 + FIX 3)
|
|
128
132
|
function isActive(hook) {
|
|
129
133
|
const when = hook['when'];
|
|
@@ -150,6 +154,59 @@ function resolveLoopHooks(input) {
|
|
|
150
154
|
// `=== true` is the correct fail-closed check (not `!== false`).
|
|
151
155
|
return state.active === true;
|
|
152
156
|
}
|
|
157
|
+
// Phase 16 D-19: the state the seal rule reads for a gate's capability. With
|
|
158
|
+
// no state map at all, every capability is treated as active, exactly as
|
|
159
|
+
// `isCapabilityActive` does: normal gates keep today's behaviour and a seal
|
|
160
|
+
// gate drops (it never renders while its capability is active). With a map,
|
|
161
|
+
// a missing entry is `undefined`, which the rule drops (fail closed).
|
|
162
|
+
function gateStateOf(capId) {
|
|
163
|
+
if (!capabilityStatesById)
|
|
164
|
+
return { enabled: true, active: true };
|
|
165
|
+
return capabilityStatesById instanceof Map
|
|
166
|
+
? capabilityStatesById.get(capId)
|
|
167
|
+
: capabilityStatesById[capId];
|
|
168
|
+
}
|
|
169
|
+
// Phase 16 D-19 / T-16-33: the injected predicate is asked at most once per
|
|
170
|
+
// resolve, however many seal gates the point declares. An absent thunk is
|
|
171
|
+
// the inert answer (no seal renders).
|
|
172
|
+
let recordedArtifactsMemo;
|
|
173
|
+
function recordedArtifactsOnce() {
|
|
174
|
+
if (recordedArtifactsMemo === undefined) {
|
|
175
|
+
recordedArtifactsMemo = resolveInjectedRecordedArtifacts(recordedArtifacts);
|
|
176
|
+
}
|
|
177
|
+
return recordedArtifactsMemo;
|
|
178
|
+
}
|
|
179
|
+
// Quick 261015-ect (QD-13): the bindings measurement, asked at most once per
|
|
180
|
+
// resolve and only for a capability that declares it needs the file. A
|
|
181
|
+
// missing thunk is `null` (unmeasured), which drops nothing.
|
|
182
|
+
let bindingsPresentMemo;
|
|
183
|
+
function bindingsPresentOnce() {
|
|
184
|
+
if (bindingsPresentMemo === undefined) {
|
|
185
|
+
bindingsPresentMemo = resolveInjectedPlatformBindingsPresent(platformBindingsPresent);
|
|
186
|
+
}
|
|
187
|
+
return bindingsPresentMemo;
|
|
188
|
+
}
|
|
189
|
+
function requiresBindings(capId) {
|
|
190
|
+
// Prototype-pollution guard (inline literal, CodeQL barrier), as the
|
|
191
|
+
// registry generator guards capability ids.
|
|
192
|
+
if (capId === '' || capId === '__proto__' || capId === 'constructor' || capId === 'prototype')
|
|
193
|
+
return false;
|
|
194
|
+
const caps = registry['capabilities'];
|
|
195
|
+
if (!caps || typeof caps !== 'object' || Array.isArray(caps))
|
|
196
|
+
return false;
|
|
197
|
+
if (!Object.prototype.hasOwnProperty.call(caps, capId))
|
|
198
|
+
return false;
|
|
199
|
+
const cap = caps[capId];
|
|
200
|
+
if (!cap || typeof cap !== 'object')
|
|
201
|
+
return false;
|
|
202
|
+
return cap['requiresPlatformBindings'] === true;
|
|
203
|
+
}
|
|
204
|
+
// A capability that requires bindings renders nothing where the file is
|
|
205
|
+
// MEASURED absent: the project is not governed by it (its own description says
|
|
206
|
+
// it is inert unless the bindings exist).
|
|
207
|
+
function droppedForBindings(capId) {
|
|
208
|
+
return requiresBindings(capId) && bindingsPresentOnce() === false;
|
|
209
|
+
}
|
|
153
210
|
// Helper: safe string array
|
|
154
211
|
function toStringArray(v) {
|
|
155
212
|
if (!Array.isArray(v))
|
|
@@ -201,6 +258,10 @@ function resolveLoopHooks(input) {
|
|
|
201
258
|
const capId = typeof hook['capId'] === 'string' ? hook['capId'] : '';
|
|
202
259
|
if (!isCapabilityActive(capId))
|
|
203
260
|
continue;
|
|
261
|
+
if (droppedForBindings(capId))
|
|
262
|
+
continue;
|
|
263
|
+
if (!modeOk(hook, 'step'))
|
|
264
|
+
continue;
|
|
204
265
|
if (!isActive(hook))
|
|
205
266
|
continue;
|
|
206
267
|
const ref = (typeof hook['ref'] === 'object' && hook['ref'] !== null)
|
|
@@ -235,6 +296,10 @@ function resolveLoopHooks(input) {
|
|
|
235
296
|
const capId = typeof hook['capId'] === 'string' ? hook['capId'] : '';
|
|
236
297
|
if (!isCapabilityActive(capId))
|
|
237
298
|
continue;
|
|
299
|
+
if (droppedForBindings(capId))
|
|
300
|
+
continue;
|
|
301
|
+
if (!modeOk(hook, 'contribution'))
|
|
302
|
+
continue;
|
|
238
303
|
if (!isActive(hook))
|
|
239
304
|
continue;
|
|
240
305
|
const into = typeof hook['into'] === 'string' ? hook['into'] : undefined;
|
|
@@ -268,9 +333,24 @@ function resolveLoopHooks(input) {
|
|
|
268
333
|
if (!hook || typeof hook !== 'object')
|
|
269
334
|
continue;
|
|
270
335
|
const capId = typeof hook['capId'] === 'string' ? hook['capId'] : '';
|
|
271
|
-
|
|
336
|
+
// Quick 261015-ect (QD-13): gates too, seal gates included. A project with
|
|
337
|
+
// no bindings holds no engine-written vault records (D-37/D-42), and every
|
|
338
|
+
// vault gate already answers inert there, so nothing is left ungated.
|
|
339
|
+
if (droppedForBindings(capId))
|
|
272
340
|
continue;
|
|
273
|
-
|
|
341
|
+
// Phase 16 D-19 / research C-1: the mode filter runs FIRST, so a
|
|
342
|
+
// mode-filtered seal gate never consults the recorded-artifacts predicate
|
|
343
|
+
// (a default standards project pays no planning-tree read for a seal it
|
|
344
|
+
// cannot render). `modeOk` is pure, so the order changes no other result.
|
|
345
|
+
if (!modeOk(hook, 'gate'))
|
|
346
|
+
continue;
|
|
347
|
+
const verdict = sealRenderVerdict(hook[SEAL_FIELD], gateStateOf(capId), recordedArtifactsOnce);
|
|
348
|
+
if (verdict === 'drop')
|
|
349
|
+
continue;
|
|
350
|
+
// 'normal': today's path, `when` evaluated. 'seal': the `when` IS the
|
|
351
|
+
// switch that is off (D-18), so it is not read; the seal rule already
|
|
352
|
+
// required an enabled, config-inactive capability and recorded artifacts.
|
|
353
|
+
if (verdict === 'normal' && !isActive(hook))
|
|
274
354
|
continue;
|
|
275
355
|
const when = typeof hook['when'] === 'string' ? hook['when'] : undefined;
|
|
276
356
|
const check = hook['check'] !== undefined ? hook['check'] : undefined;
|
|
@@ -382,80 +462,6 @@ function renderLoopHooks(resolved) {
|
|
|
382
462
|
return lines.join('\n');
|
|
383
463
|
}
|
|
384
464
|
// ─── I/O command handler ───────────────────────────────────────────────────────
|
|
385
|
-
/**
|
|
386
|
-
* The refusal text for a caller that resolves hooks over an unreadable config.
|
|
387
|
-
* One wording for every such caller (`loop render-hooks`, `phase.dispatch-plan`).
|
|
388
|
-
*/
|
|
389
|
-
function unreadableConfigRefusal(what, unreadable) {
|
|
390
|
-
return (`${what} refused: the project config cannot be read, so no hook can be resolved ` +
|
|
391
|
-
`(rendering anyway would silently drop every hook the config switches on, a required client vault among them). ` +
|
|
392
|
-
`${describeUnreadableConfig(unreadable)}. Repair or remove the file, then re-run. Stop this workflow here.`);
|
|
393
|
-
}
|
|
394
|
-
/**
|
|
395
|
-
* Resolve the active hooks at `point` for the project at `cwd`, exactly as
|
|
396
|
-
* `loop render-hooks` does: refuse an unreadable config, load the config
|
|
397
|
-
* snapshot once, overlay the phase tier's gate booleans when one is given,
|
|
398
|
-
* resolve capability state over the same snapshot, then resolve the hooks.
|
|
399
|
-
* The ONE owner of "which hooks are active here" for every caller that needs
|
|
400
|
-
* the answer in code (`loop render-hooks`, `phase.dispatch-plan`'s executor
|
|
401
|
-
* contributions). Never exits; the caller decides how to report a refusal.
|
|
402
|
-
*/
|
|
403
|
-
function resolveProjectLoopHooks(cwd, point, opts = {}) {
|
|
404
|
-
// Fail closed on a config that exists but cannot be read. Every hook's `when`
|
|
405
|
-
// and activation key is read from it, and both readers below swallow a parse
|
|
406
|
-
// failure into the defaults, so rendering anyway drops exactly the hooks the
|
|
407
|
-
// project switched on — a required client vault's fetch and blocking gate
|
|
408
|
-
// among them. A MISSING config is not refused: that is a fresh project, and
|
|
409
|
-
// the defaults are its real settings.
|
|
410
|
-
const unreadable = unreadableConfigFiles(cwd);
|
|
411
|
-
if (unreadable.length > 0) {
|
|
412
|
-
return { ok: false, reason: 'config_unreadable', message: unreadableConfigRefusal(opts.what ?? `loop render-hooks ${point}`, unreadable) };
|
|
413
|
-
}
|
|
414
|
-
// Load the config snapshot ONCE and share it with both the capability-state
|
|
415
|
-
// resolver (via configOverride) and loop-hook resolution, so federated keys
|
|
416
|
-
// present in loadConfig resolve identically for `active` and for hook when/
|
|
417
|
-
// configValues. Keys absent from loadConfig still fall through to raw
|
|
418
|
-
// .planning/config.json reads (precedence levels 2-3) in each pass.
|
|
419
|
-
let config;
|
|
420
|
-
try {
|
|
421
|
-
config = loadConfig(cwd);
|
|
422
|
-
}
|
|
423
|
-
catch {
|
|
424
|
-
config = {};
|
|
425
|
-
}
|
|
426
|
-
if (opts.phaseTier)
|
|
427
|
-
config = (0, phase_tier_cjs_1.overlayTierGates)(config, opts.phaseTier);
|
|
428
|
-
const state = resolveCapabilityRuntimeState(cwd, opts.configDir, config);
|
|
429
|
-
// Registry is the static generated module — same object capability-state uses internally.
|
|
430
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
431
|
-
const registry = require('./capability-registry.cjs');
|
|
432
|
-
const capabilityStatesById = new Map();
|
|
433
|
-
for (const cap of state.capabilities || []) {
|
|
434
|
-
capabilityStatesById.set(cap.id, cap);
|
|
435
|
-
}
|
|
436
|
-
try {
|
|
437
|
-
const resolved = resolveLoopHooks({ point, registry, config, cwd, capabilityStatesById });
|
|
438
|
-
return { ok: true, resolved, warnings: state.warnings ?? [] };
|
|
439
|
-
}
|
|
440
|
-
catch (err) {
|
|
441
|
-
return { ok: false, reason: 'invalid_point', message: (err instanceof Error) ? err.message : String(err) };
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
/**
|
|
445
|
-
* The `fragment.inline` text of every active contribution into `role`, in
|
|
446
|
-
* resolved order, with the capability that contributed it.
|
|
447
|
-
*/
|
|
448
|
-
function contributionsInto(resolved, role) {
|
|
449
|
-
const out = [];
|
|
450
|
-
for (const h of resolved.activeHooks) {
|
|
451
|
-
if (h.kind !== 'contribution' || h.into !== role)
|
|
452
|
-
continue;
|
|
453
|
-
if (typeof h.fragment?.inline !== 'string')
|
|
454
|
-
continue;
|
|
455
|
-
out.push({ capId: h.capId, fragment: h.fragment.inline });
|
|
456
|
-
}
|
|
457
|
-
return out;
|
|
458
|
-
}
|
|
459
465
|
/**
|
|
460
466
|
* Command entry point: load registry + config, resolve + render, emit envelope.
|
|
461
467
|
*
|
|
@@ -470,31 +476,39 @@ function contributionsInto(resolved, role) {
|
|
|
470
476
|
* pre-cutover keys like `workflow.ui_phase` that live in config.json but are not
|
|
471
477
|
* yet exposed through loadConfig's whitelist.
|
|
472
478
|
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
479
|
+
* --active-cap <capId>: when present, resolves hooks for <point> as the normal
|
|
480
|
+
* path does EXCEPT for one input — the bindings measurement (quick 261015-ect,
|
|
481
|
+
* QD-13) is not applied — then prints exactly `true` (if any resolved
|
|
482
|
+
* activeHook has capId === <capId>) or `false` followed by a single newline,
|
|
483
|
+
* and exits 0. So on a project with no `.platform-bindings.json` it still
|
|
484
|
+
* answers the config switch alone: /pp-init Step 2.6's unbound arm ("a
|
|
485
|
+
* resolving profile with no bindings halts loudly") is reachable only because
|
|
486
|
+
* this read ignores bindings (16-16 D-81), and the D-28 parity test states
|
|
487
|
+
* parity on bindings-present shapes only.
|
|
480
488
|
* No JSON envelope is emitted — output is clean for shell $(…) capture.
|
|
481
489
|
* Missing <capId> value → coreError + non-zero exit.
|
|
482
490
|
* Unknown/inactive capId → `false` (not an error).
|
|
483
491
|
*
|
|
484
|
-
* --into <role
|
|
485
|
-
* `fragment.inline` text of each active contribution whose
|
|
486
|
-
* <role>, in resolved order, separated by one blank line, with a
|
|
487
|
-
* newline when anything printed. Nothing at all (empty stdout, exit
|
|
488
|
-
* contribution matches. No JSON envelope; `--raw` is ignored.
|
|
489
|
-
*
|
|
492
|
+
* --into <role> (Phase 15 D-12/D-30): resolves exactly as the normal path does,
|
|
493
|
+
* then prints the `fragment.inline` text of each active contribution whose
|
|
494
|
+
* `into` equals <role>, in resolved order, separated by one blank line, with a
|
|
495
|
+
* trailing newline when anything printed. Nothing at all (empty stdout, exit
|
|
496
|
+
* 0) when no contribution matches. No JSON envelope; `--raw` is ignored. This
|
|
497
|
+
* is the text execute-phase interpolates verbatim into a spawn prompt (15-09).
|
|
498
|
+
* `--active-cap` takes precedence: given both, `--into` is ignored.
|
|
490
499
|
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
*
|
|
496
|
-
*
|
|
497
|
-
*
|
|
500
|
+
* options.resolveVaultMode: an injected `() => mode | null` (pp-tools builds it
|
|
501
|
+
* on vaultModeVerdict). This module never imports vault code (D-02).
|
|
502
|
+
*
|
|
503
|
+
* options.resolveRecordedArtifacts: an injected `() => boolean` (pp-tools builds
|
|
504
|
+
* it on the seal leaf's hasRecordedVaultArtifacts); only literal `true` lets a
|
|
505
|
+
* seal gate render (Phase 16 D-19). `--active-cap <cap>` also requires the
|
|
506
|
+
* capability's own state to be active (D-27).
|
|
507
|
+
*
|
|
508
|
+
* options.resolvePlatformBindingsPresent: an injected `() => boolean` (pp-tools
|
|
509
|
+
* builds it on the vault leaf's hasPlatformBindings); only literal `false`
|
|
510
|
+
* drops a `requiresPlatformBindings` capability's hooks (QD-13). `--into` and
|
|
511
|
+
* the envelope read the filtered set; `--active-cap` reads the unfiltered one.
|
|
498
512
|
*/
|
|
499
513
|
function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
|
|
500
514
|
if (!point) {
|
|
@@ -509,40 +523,85 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
|
|
|
509
523
|
}
|
|
510
524
|
// --into <role> mode: emit matching contribution fragment text only
|
|
511
525
|
const intoRole = typeof options['into'] === 'string' ? options['into'] : undefined;
|
|
512
|
-
if (activeCapId === undefined && intoRole !== undefined && intoRole
|
|
513
|
-
coreError('
|
|
526
|
+
if (activeCapId === undefined && intoRole !== undefined && intoRole === '') {
|
|
527
|
+
coreError('--into requires a <role> value (e.g. --into executor)');
|
|
514
528
|
return;
|
|
515
529
|
}
|
|
516
530
|
const runtimeConfigDir = typeof options['configDir'] === 'string'
|
|
517
531
|
? options['configDir']
|
|
518
532
|
: undefined;
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
phaseTier = (0, phase_tier_cjs_1.resolvePhaseTierForProject)(cwd, tierPhase, tierCli);
|
|
533
|
+
// Load the config snapshot ONCE and share it with both the capability-state
|
|
534
|
+
// resolver (via configOverride) and loop-hook resolution, so federated keys
|
|
535
|
+
// present in loadConfig resolve identically for `active` and for hook when/
|
|
536
|
+
// configValues — eliminating the previous double loadConfig() call. Note: keys
|
|
537
|
+
// absent from loadConfig still fall through to raw .planning/config.json reads
|
|
538
|
+
// (precedence levels 2-3) in each pass; that residual re-read window is
|
|
539
|
+
// pre-existing (unchanged by this consolidation), not introduced here.
|
|
540
|
+
let config;
|
|
541
|
+
try {
|
|
542
|
+
config = loadConfig(cwd);
|
|
530
543
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
544
|
+
catch {
|
|
545
|
+
config = {};
|
|
546
|
+
}
|
|
547
|
+
const state = resolveCapabilityRuntimeState(cwd, runtimeConfigDir, config);
|
|
548
|
+
// Registry is the static generated module — same object capability-state uses internally.
|
|
549
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
550
|
+
const registry = require('./capability-registry.cjs');
|
|
551
|
+
const capabilityStatesById = new Map();
|
|
552
|
+
for (const cap of state.capabilities || []) {
|
|
553
|
+
capabilityStatesById.set(cap.id, cap);
|
|
554
|
+
}
|
|
555
|
+
// Phase 15 D-02: the vault mode arrives through an injected resolver (pp-tools
|
|
556
|
+
// passes one built on vaultModeVerdict). A missing resolver, a throw or a
|
|
557
|
+
// non-mode answer is `null`, the fail-closed undefined mode (D-28).
|
|
558
|
+
const vaultMode = resolveInjectedVaultMode(options['resolveVaultMode']);
|
|
559
|
+
// Phase 16 D-19: the recorded-artifacts predicate arrives the same way (pp-tools
|
|
560
|
+
// passes one built on the seal leaf's hasRecordedVaultArtifacts). The resolver
|
|
561
|
+
// memoises it and asks it only for an enabled, config-inactive seal gate.
|
|
562
|
+
const resolveRecordedArtifacts = options['resolveRecordedArtifacts'];
|
|
563
|
+
const recordedArtifacts = () => resolveInjectedRecordedArtifacts(resolveRecordedArtifacts);
|
|
564
|
+
// Quick 261015-ect (QD-13): the bindings measurement arrives the same way
|
|
565
|
+
// (pp-tools passes one built on the vault leaf's hasPlatformBindings).
|
|
566
|
+
const resolvePlatformBindingsPresent = options['resolvePlatformBindingsPresent'];
|
|
567
|
+
const platformBindingsPresent = () => resolveInjectedPlatformBindingsPresent(resolvePlatformBindingsPresent);
|
|
568
|
+
let resolved;
|
|
569
|
+
try {
|
|
570
|
+
// --active-cap reads the UNFILTERED resolution (16-16 D-81, see above):
|
|
571
|
+
// without the bindings thunk, so it keeps answering the config switch.
|
|
572
|
+
resolved = resolveLoopHooks({
|
|
573
|
+
point, registry, config, cwd, capabilityStatesById, vaultMode, recordedArtifacts,
|
|
574
|
+
...(activeCapId === undefined ? { platformBindingsPresent } : {}),
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
catch (err) {
|
|
578
|
+
const msg = (err instanceof Error) ? err.message : String(err);
|
|
579
|
+
coreError(msg);
|
|
534
580
|
return;
|
|
535
581
|
}
|
|
536
|
-
const resolved = outcome.resolved;
|
|
537
582
|
// --active-cap mode: print exactly 'true' or 'false' with no envelope
|
|
538
583
|
if (activeCapId !== undefined) {
|
|
539
|
-
|
|
584
|
+
// Phase 16 D-27 (research C-3): a hook rendering is no longer enough — the
|
|
585
|
+
// capability must itself be active. A seal gate renders for a config-
|
|
586
|
+
// inactive vault-practices, and the doors read `--active-cap` as their
|
|
587
|
+
// switch, so a seal must never make an inactive capability answer `true`.
|
|
588
|
+
// Equivalent to the old answer on every input without a seal: a normal
|
|
589
|
+
// hook renders only for an active capability.
|
|
590
|
+
const isActive = resolved.activeHooks.some((h) => h.capId === activeCapId)
|
|
591
|
+
&& capabilityStatesById.get(activeCapId)?.active === true;
|
|
540
592
|
process.stdout.write(isActive ? 'true\n' : 'false\n');
|
|
541
593
|
return;
|
|
542
594
|
}
|
|
543
595
|
// --into mode: fragment text of the active contributions targeting <role>
|
|
544
596
|
if (intoRole !== undefined) {
|
|
545
|
-
const texts =
|
|
597
|
+
const texts = [];
|
|
598
|
+
for (const h of resolved.activeHooks) {
|
|
599
|
+
if (h.kind !== 'contribution' || h.into !== intoRole)
|
|
600
|
+
continue;
|
|
601
|
+
if (typeof h.fragment?.inline !== 'string')
|
|
602
|
+
continue;
|
|
603
|
+
texts.push(h.fragment.inline);
|
|
604
|
+
}
|
|
546
605
|
if (texts.length > 0)
|
|
547
606
|
process.stdout.write(texts.join('\n\n') + '\n');
|
|
548
607
|
return;
|
|
@@ -553,11 +612,8 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
|
|
|
553
612
|
activeHooks: resolved.activeHooks,
|
|
554
613
|
rendered,
|
|
555
614
|
};
|
|
556
|
-
if (
|
|
557
|
-
envelope.
|
|
558
|
-
}
|
|
559
|
-
if (outcome.warnings.length > 0) {
|
|
560
|
-
envelope.warnings = outcome.warnings;
|
|
615
|
+
if (state.warnings && state.warnings.length > 0) {
|
|
616
|
+
envelope.warnings = state.warnings;
|
|
561
617
|
}
|
|
562
618
|
// No rawValue third argument, so --raw emits the JSON envelope rather than
|
|
563
619
|
// plain text. This is deliberate and load-bearing: 23 workflow call sites do
|
|
@@ -571,8 +627,6 @@ module.exports = {
|
|
|
571
627
|
resolveLoopHooks,
|
|
572
628
|
renderLoopHooks,
|
|
573
629
|
cmdLoopRenderHooks,
|
|
574
|
-
resolveProjectLoopHooks,
|
|
575
|
-
contributionsInto,
|
|
576
630
|
// Exported for tests
|
|
577
631
|
_getNestedConfigValue,
|
|
578
632
|
_resolveActivationValue,
|
|
@@ -580,6 +634,12 @@ module.exports = {
|
|
|
580
634
|
// Re-exported for identity parity guard (FIX 2: resolveConfigValues in this module
|
|
581
635
|
// calls resolveConfigKey; exporting it here makes the single-owner contract testable).
|
|
582
636
|
resolveConfigKey,
|
|
637
|
+
// Re-exported for the same identity pin: the mode-filter rule has one owner,
|
|
638
|
+
// capability-state.cjs (Phase 15 D-02/D-28).
|
|
639
|
+
vaultModeFilterVerdict,
|
|
640
|
+
// Re-exported for the same identity pin: the seal rule has one owner,
|
|
641
|
+
// capability-state.cjs (Phase 16 D-19).
|
|
642
|
+
sealRenderVerdict,
|
|
583
643
|
CANONICAL_POINTS_FALLBACK,
|
|
584
644
|
CANONICAL_POINTS,
|
|
585
645
|
};
|