@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.
- package/.claude-plugin/plugin.json +3 -3
- package/README.md +26 -9
- package/agents/pp-code-reviewer.md +3 -2
- package/agents/pp-debug-session-manager.md +9 -6
- package/agents/pp-executor.md +7 -21
- package/agents/pp-plan-checker.md +4 -4
- package/agents/pp-planner.md +23 -9
- package/agents/pp-vault-reviewer.md +274 -0
- 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 +2 -1
- package/commands/pp-plan-phase.md +1 -2
- package/commands/pp-vault-review.md +35 -0
- 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 +526 -22
- 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 +3867 -274
- package/pp-core/bin/lib/clusters.cjs +4 -0
- package/pp-core/bin/lib/command-aliases.cjs +0 -28
- package/pp-core/bin/lib/commands.cjs +9 -4
- package/pp-core/bin/lib/compose-bindings.cjs +253 -76
- 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 +104 -27
- package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
- package/pp-core/bin/lib/frontmatter.cjs +1 -1
- package/pp-core/bin/lib/git-base-branch.cjs +138 -10
- 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 +653 -629
- package/pp-core/bin/lib/init-interactive.cjs +77 -21
- 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 +169 -47
- package/pp-core/bin/lib/org-profile.cjs +544 -53
- package/pp-core/bin/lib/package-identity.cjs +3 -3
- package/pp-core/bin/lib/phase-command-router.cjs +1 -23
- package/pp-core/bin/lib/phase-scope.cjs +1 -1
- package/pp-core/bin/lib/phase.cjs +11 -203
- 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 +2594 -0
- package/pp-core/bin/lib/vault-command-router.cjs +2756 -60
- 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 +1194 -89
- package/pp-core/bin/lib/vault-review.cjs +2102 -0
- 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 +3184 -0
- package/pp-core/bin/lib/vault-update.cjs +242 -0
- package/pp-core/bin/lib/vault-write-back.cjs +78 -31
- package/pp-core/bin/lib/vault.cjs +2713 -634
- 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 -37
- 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 +1 -0
- 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 -14
- 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 +31 -3
- 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 +137 -11
- package/pp-core/templates/project-charter.md +226 -0
- package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
- package/pp-core/workflows/add-tests.md +55 -0
- package/pp-core/workflows/audit-fix.md +63 -0
- 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 -1
- package/pp-core/workflows/code-review.md +10 -1
- package/pp-core/workflows/complete-milestone.md +1 -1
- package/pp-core/workflows/debug.md +56 -2
- package/pp-core/workflows/discovery-sprint/prefill.md +21 -0
- package/pp-core/workflows/discovery-sprint/prototype.md +46 -48
- 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 +9 -1
- 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 -18
- 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 +154 -110
- package/pp-core/workflows/execute-plan.md +58 -4
- package/pp-core/workflows/fast.md +53 -0
- package/pp-core/workflows/help/modes/full.md +1 -0
- 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 +99 -43
- package/pp-core/workflows/quick.md +120 -2
- package/pp-core/workflows/secure-phase.md +1 -1
- package/pp-core/workflows/ship.md +57 -24
- package/pp-core/workflows/update.md +3 -3
- package/pp-core/workflows/validate-phase.md +60 -1
- package/pp-core/workflows/vault-review.md +325 -0
- package/pp-core/workflows/verify-work.md +13 -12
- 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 -0
- 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 +13 -2
- package/scripts/practice-arithmetic-contract.allow.json +35 -0
- package/scripts/prompt-injection-scan.sh +22 -15
- 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 -472
- package/pp-core/bin/lib/install-run.cjs +0 -536
- package/pp-core/bin/lib/phase-tier.cjs +0 -467
- package/pp-core/bin/lib/plan-graph.cjs +0 -368
- package/pp-core/references/planner-plan-graph.md +0 -66
- 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 -72
- package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -34
- 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
|
@@ -118,15 +118,88 @@ function renderComposeConfirm(prompter, info) {
|
|
|
118
118
|
async function runInitInteractive(cwd, prompter, opts = {}) {
|
|
119
119
|
const sessionOpts = { offline: opts.offline === true, _globalRoot: opts._globalRoot };
|
|
120
120
|
let wroteBinding = false;
|
|
121
|
+
// PBI 74299: answers the session filled in itself are shown once each, as
|
|
122
|
+
// soon as they are decided, so the operator sees every value before the
|
|
123
|
+
// compose confirmation.
|
|
124
|
+
const shownAutoAnswers = new Set();
|
|
121
125
|
for (let steps = 1; steps <= MAX_SESSION_STEPS; steps++) {
|
|
122
126
|
const state = (0, init_contract_cjs_1.initSessionNext)(cwd, sessionOpts);
|
|
123
127
|
if (state.error !== undefined) {
|
|
124
128
|
prompter.print(`Error: ${state.message ?? state.error}`);
|
|
125
129
|
return { ok: false, wroteBinding, steps, violations: [] };
|
|
126
130
|
}
|
|
131
|
+
for (const auto of state.autoAnswered ?? []) {
|
|
132
|
+
if (shownAutoAnswers.has(auto.id))
|
|
133
|
+
continue;
|
|
134
|
+
shownAutoAnswers.add(auto.id);
|
|
135
|
+
const why = auto.source === 'derived' ? 'derived' : 'set by the org profile';
|
|
136
|
+
prompter.print(` ${auto.id} = ${auto.value} (${why})`);
|
|
137
|
+
}
|
|
127
138
|
if (state.done) {
|
|
128
139
|
if (wroteBinding) {
|
|
129
|
-
|
|
140
|
+
// OD-1: the done line names the capability the session just armed and
|
|
141
|
+
// the step that is now live, so an operator is not left to discover
|
|
142
|
+
// either from the config file.
|
|
143
|
+
//
|
|
144
|
+
// 07-09 (WR-07): a READING, not a claim. The arming write happens
|
|
145
|
+
// only on an ABSENT key, so an operator's explicit `false` — and a
|
|
146
|
+
// hand-edited non-boolean — are still there when the session ends.
|
|
147
|
+
// Printing the armed line over either of those would make the last
|
|
148
|
+
// line `pp-tools init` prints contradict the config file it just
|
|
149
|
+
// finished writing, which is precisely the
|
|
150
|
+
// tool-reports-a-state-that-isn't-real failure this phase exists to
|
|
151
|
+
// end.
|
|
152
|
+
const vaultState = (0, init_contract_cjs_1.readVaultEnabledState)(cwd).state;
|
|
153
|
+
if (vaultState === 'off') {
|
|
154
|
+
prompter.print('Done — .platform-bindings.json was composed and written; vault.enabled was left at false ' +
|
|
155
|
+
'as this project set it, so the vault capability stays off and /pp-init Step 2.6 stays ' +
|
|
156
|
+
'inert (arm it with pp-tools config-set vault.enabled true).');
|
|
157
|
+
}
|
|
158
|
+
else if (vaultState === 'malformed') {
|
|
159
|
+
prompter.print('Done — .platform-bindings.json was composed and written; vault.enabled holds a non-boolean ' +
|
|
160
|
+
'value and was left untouched — pp-tools query init-contract --verify reports it as ' +
|
|
161
|
+
'INIT_VAULT_ENABLED_MALFORMED; store the boolean with pp-tools config-set vault.enabled true.');
|
|
162
|
+
}
|
|
163
|
+
else if (vaultState === 'armed') {
|
|
164
|
+
// Quick 260915-lmc: vault.practices_enabled is armed beside it under
|
|
165
|
+
// the same absent-only rule, so the line names it only when the
|
|
166
|
+
// READING says true — an explicit false is the project's choice and
|
|
167
|
+
// is not contradicted here.
|
|
168
|
+
const practicesArmed = (0, init_contract_cjs_1.readVaultPracticesEnabledState)(cwd).state === 'armed';
|
|
169
|
+
// Quick 260916-s5n: vault.required is named only when the READING
|
|
170
|
+
// says armed, by the same rule — an explicit false is the project's
|
|
171
|
+
// choice and the last line printed must not contradict it.
|
|
172
|
+
const requiredArmed = (0, init_contract_cjs_1.readVaultRequiredState)(cwd).state === 'armed';
|
|
173
|
+
// Built as a head plus two OPTIONAL clauses carrying no terminator
|
|
174
|
+
// of their own, with exactly one full stop appended at the end. The
|
|
175
|
+
// stop used to live inside the practices branch, which made every
|
|
176
|
+
// further clause a sentence continuing past its own period.
|
|
177
|
+
const head = 'Done — .platform-bindings.json was composed and written; vault.enabled is now true ' +
|
|
178
|
+
'(the vault capability is armed — /pp-init Step 2.6 probes reachability next)';
|
|
179
|
+
// 15-REVIEW WR-09: in standards mode no phase records practices (the
|
|
180
|
+
// practices planner fragment and trace gates are filtered out), so the
|
|
181
|
+
// clause names what IS recorded and checked there, whatever the
|
|
182
|
+
// practices switch reads.
|
|
183
|
+
const practicesClause = (0, init_contract_cjs_1.readVaultStandardsModeActive)(cwd)
|
|
184
|
+
? '; vault.mode is standards, so each plan records which client standards documents governed it ' +
|
|
185
|
+
'(<vault_standards>), checked by the vault.trace gate'
|
|
186
|
+
: practicesArmed
|
|
187
|
+
? '; vault.practices_enabled is true, so each phase records which client vault practices it applied'
|
|
188
|
+
: '';
|
|
189
|
+
const requiredClause = requiredArmed
|
|
190
|
+
? '; vault.required is true, so a client vault miss, a failed fetch or an unwritable vault summary ' +
|
|
191
|
+
'stops the build at plan:pre and execute:pre'
|
|
192
|
+
: '';
|
|
193
|
+
prompter.print(head + practicesClause + requiredClause + '.');
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
// 'absent' — unreachable after an approved compose (the arming
|
|
197
|
+
// write fires on exactly this state), kept defensive for the case
|
|
198
|
+
// where that write itself failed. It says only what is certain:
|
|
199
|
+
// the binding was written, and it does NOT claim the capability is
|
|
200
|
+
// on, because the key is not there to say so.
|
|
201
|
+
prompter.print('Done — .platform-bindings.json was composed and written.');
|
|
202
|
+
}
|
|
130
203
|
}
|
|
131
204
|
else if (typeof state.info === 'string') {
|
|
132
205
|
prompter.print(state.info);
|
|
@@ -168,26 +241,9 @@ async function runInitInteractive(cwd, prompter, opts = {}) {
|
|
|
168
241
|
preselectedLabel: state.preselectedLabel,
|
|
169
242
|
optionalAnswer: state.optionalAnswer,
|
|
170
243
|
});
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
// package was detected. `provider` also carries a `preselected` but is
|
|
175
|
-
// NOT `optionalAnswer`, so the `state.optionalAnswer === true` guard
|
|
176
|
-
// below leaves it untouched: a blank answer there still falls through
|
|
177
|
-
// to `initSessionAnswer` verbatim, which still rejects it and re-asks
|
|
178
|
-
// exactly as today.
|
|
179
|
-
const hasUsableDefault = state.optionalAnswer === true && typeof state.preselected === 'string' && state.preselected.trim() !== '';
|
|
180
|
-
let submittedAnswer = answer;
|
|
181
|
-
if (hasUsableDefault) {
|
|
182
|
-
const trimmedLower = answer.trim().toLowerCase();
|
|
183
|
-
if (answer.trim() === '') {
|
|
184
|
-
submittedAnswer = state.preselected;
|
|
185
|
-
}
|
|
186
|
-
else if (trimmedLower === 'none') {
|
|
187
|
-
submittedAnswer = '';
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
const result = (0, init_contract_cjs_1.initSessionAnswer)(cwd, stepId, submittedAnswer, sessionOpts);
|
|
244
|
+
// UAT-13-F2: the driver has no default-acceptance path, on purpose — the
|
|
245
|
+
// answer goes to initSessionAnswer verbatim, which owns the none/blank decline.
|
|
246
|
+
const result = (0, init_contract_cjs_1.initSessionAnswer)(cwd, stepId, answer, sessionOpts);
|
|
191
247
|
if (result.ok) {
|
|
192
248
|
if (stepId === 'compose_confirm') {
|
|
193
249
|
wroteBinding = readBindingsContent(cwd) !== preBindings;
|
package/pp-core/bin/lib/init.cjs
CHANGED
|
@@ -32,7 +32,6 @@ const worktreeSafety = require("./worktree-safety.cjs");
|
|
|
32
32
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
|
|
33
33
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
34
34
|
const secrets_cjs_1 = require("./secrets.cjs");
|
|
35
|
-
const phase_tier_cjs_1 = require("./phase-tier.cjs");
|
|
36
35
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-scan.cjs is an export= CommonJS module
|
|
37
36
|
const scanPhasePlans = require("./plan-scan.cjs");
|
|
38
37
|
const state_document_cjs_1 = require("./state-document.cjs");
|
|
@@ -189,7 +188,8 @@ function getInitGitState(cwd) {
|
|
|
189
188
|
* Quick 261007-jkl, D2 (todo "init execute-phase returns null phase_dir for an
|
|
190
189
|
* archived phase") put the "is this archived match the current milestone's?"
|
|
191
190
|
* compare HERE, for these four verbs only; every other consumer of the raw
|
|
192
|
-
* locator kept the archive fall-through with no compare
|
|
191
|
+
* locator kept the archive fall-through with no compare (PBI 75323, quick
|
|
192
|
+
* 261009-gvf). The decision now lives in ONE owner, src/phase-scope.cts, which
|
|
193
193
|
* every consumer shares: a phase archived in the CURRENT milestone keeps its
|
|
194
194
|
* match, directory and artifacts; a PRIOR milestone's archived phase whose
|
|
195
195
|
* number the current ROADMAP reuses is dropped (the pins in tests/init.test.cjs
|
|
@@ -222,26 +222,6 @@ function resolvePhaseForInit(cwd, phase) {
|
|
|
222
222
|
}
|
|
223
223
|
return { phaseInfo, roadmapPhase };
|
|
224
224
|
}
|
|
225
|
-
/**
|
|
226
|
-
* The phase's resolved ceremony tier for an init payload (src/phase-tier.cts).
|
|
227
|
-
* Init's gate booleans (`research_enabled`, `plan_checker_enabled`,
|
|
228
|
-
* `verifier_enabled`, …) are read from this, never from raw config, so a
|
|
229
|
-
* workflow that reads init reads the tier-resolved set.
|
|
230
|
-
*/
|
|
231
|
-
function initPhaseTier(cwd, phase, options) {
|
|
232
|
-
const flag = options['tier'];
|
|
233
|
-
let cli = null;
|
|
234
|
-
if (typeof flag === 'string') {
|
|
235
|
-
const v = flag.trim().toLowerCase();
|
|
236
|
-
if (!(0, phase_tier_cjs_1.isPhaseTier)(v))
|
|
237
|
-
error(`--tier needs one of ${phase_tier_cjs_1.PHASE_TIERS.join(', ')}`);
|
|
238
|
-
cli = v;
|
|
239
|
-
}
|
|
240
|
-
return (0, phase_tier_cjs_1.resolvePhaseTierForProject)(cwd, phase, cli);
|
|
241
|
-
}
|
|
242
|
-
function phaseTierSummary(t) {
|
|
243
|
-
return { tier: t.tier, source: t.source, gates: t.gates, suppressed: t.suppressed };
|
|
244
|
-
}
|
|
245
225
|
function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
246
226
|
if (!phase) {
|
|
247
227
|
error('phase required for init execute-phase');
|
|
@@ -255,7 +235,6 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
255
235
|
: null;
|
|
256
236
|
const phase_req_ids = reqExtracted && reqExtracted !== 'TBD' ? reqExtracted : null;
|
|
257
237
|
const wf = (config.workflow ?? {});
|
|
258
|
-
const tier = initPhaseTier(cwd, phase, options);
|
|
259
238
|
const result = {
|
|
260
239
|
executor_model: resolveModelInternal(cwd, 'pp-executor'),
|
|
261
240
|
verifier_model: resolveModelInternal(cwd, 'pp-verifier'),
|
|
@@ -267,12 +246,7 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
267
246
|
branching_strategy: config.branching_strategy,
|
|
268
247
|
phase_branch_template: config.phase_branch_template,
|
|
269
248
|
milestone_branch_template: config.milestone_branch_template,
|
|
270
|
-
verifier_enabled:
|
|
271
|
-
code_review_enabled: tier.gates.code_review,
|
|
272
|
-
security_enforcement_enabled: tier.gates.security_enforcement,
|
|
273
|
-
nyquist_validation_enabled: tier.gates.nyquist_validation,
|
|
274
|
-
install_verification_enabled: tier.gates.install_verification,
|
|
275
|
-
phase_tier: phaseTierSummary(tier),
|
|
249
|
+
verifier_enabled: config.verifier,
|
|
276
250
|
phase_found: !!phaseInfo,
|
|
277
251
|
phase_dir: phaseInfo?.['directory'] || null,
|
|
278
252
|
phase_number: phaseInfo?.['phase_number'] || null,
|
|
@@ -357,22 +331,15 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
357
331
|
assertValidGranularityOverride(granularityOverride, error);
|
|
358
332
|
const granularity = resolveGranularityInternal(cwd, 'planning', granularityOverride || undefined);
|
|
359
333
|
const wf = (config.workflow ?? {});
|
|
360
|
-
const tier = initPhaseTier(cwd, phase, options);
|
|
361
334
|
const result = {
|
|
362
335
|
researcher_model: resolveModelInternal(cwd, 'pp-phase-researcher'),
|
|
363
336
|
planner_model: resolveModelInternal(cwd, 'pp-planner'),
|
|
364
337
|
checker_model: resolveModelInternal(cwd, 'pp-plan-checker'),
|
|
365
338
|
tdd_mode: options['tdd'] || Boolean(wf['tdd_mode']) || false,
|
|
366
339
|
granularity,
|
|
367
|
-
research_enabled:
|
|
368
|
-
plan_checker_enabled:
|
|
369
|
-
|
|
370
|
-
nyquist_validation_enabled: tier.gates.nyquist_validation,
|
|
371
|
-
pattern_mapper_enabled: tier.gates.pattern_mapper,
|
|
372
|
-
post_planning_gaps_enabled: tier.gates.post_planning_gaps,
|
|
373
|
-
security_enforcement_enabled: tier.gates.security_enforcement,
|
|
374
|
-
plan_bounce_enabled: tier.gates.plan_bounce,
|
|
375
|
-
phase_tier: phaseTierSummary(tier),
|
|
340
|
+
research_enabled: wf['research'],
|
|
341
|
+
plan_checker_enabled: config.plan_checker,
|
|
342
|
+
nyquist_validation_enabled: wf['nyquist_validation'],
|
|
376
343
|
commit_docs: config.commit_docs,
|
|
377
344
|
text_mode: config.text_mode,
|
|
378
345
|
auto_advance: !!(config.auto_advance),
|
|
@@ -603,10 +570,14 @@ function cmdInitNewMilestone(cwd, raw) {
|
|
|
603
570
|
};
|
|
604
571
|
output(withProjectRoot(cwd, result), raw);
|
|
605
572
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
573
|
+
/**
|
|
574
|
+
* The quick-task id for a moment: `yymmdd` + `-` + the two-second block of the
|
|
575
|
+
* local day in base 36, padded to three (`261011-8yt`). A pure function of
|
|
576
|
+
* `now`, extracted verbatim from `cmdInitQuick` (16-05, D-29) so the plan-id
|
|
577
|
+
* owner (`SCAFFOLD_PLAN_ID_RE`, src/vault-standards.cts) is tested over ids the
|
|
578
|
+
* engine itself builds, all-digit and lettered, rather than over hand-typed ones.
|
|
579
|
+
*/
|
|
580
|
+
function buildQuickId(now) {
|
|
610
581
|
const yy = String(now.getFullYear()).slice(-2);
|
|
611
582
|
const mm = String(now.getMonth() + 1).padStart(2, '0');
|
|
612
583
|
const dd = String(now.getDate()).padStart(2, '0');
|
|
@@ -614,7 +585,13 @@ function cmdInitQuick(cwd, description, raw) {
|
|
|
614
585
|
const secondsSinceMidnight = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds();
|
|
615
586
|
const timeBlocks = Math.floor(secondsSinceMidnight / 2);
|
|
616
587
|
const timeEncoded = timeBlocks.toString(36).padStart(3, '0');
|
|
617
|
-
|
|
588
|
+
return dateStr + '-' + timeEncoded;
|
|
589
|
+
}
|
|
590
|
+
function cmdInitQuick(cwd, description, raw) {
|
|
591
|
+
const config = loadConfig(cwd);
|
|
592
|
+
const now = new Date();
|
|
593
|
+
const slug = description ? generateSlugInternal(description)?.substring(0, 40) : null;
|
|
594
|
+
const quickId = buildQuickId(now);
|
|
618
595
|
const branchSlug = slug || 'quick';
|
|
619
596
|
const quickBranchName = config.quick_branch_template
|
|
620
597
|
? config.quick_branch_template
|
|
@@ -676,22 +653,16 @@ function cmdInitResume(cwd, raw) {
|
|
|
676
653
|
};
|
|
677
654
|
output(withProjectRoot(cwd, result), raw);
|
|
678
655
|
}
|
|
679
|
-
function cmdInitVerifyWork(cwd, phase, raw
|
|
656
|
+
function cmdInitVerifyWork(cwd, phase, raw) {
|
|
680
657
|
if (!phase) {
|
|
681
658
|
error('phase required for init verify-work');
|
|
682
659
|
}
|
|
683
660
|
const config = loadConfig(cwd);
|
|
684
661
|
const { phaseInfo } = resolvePhaseForInit(cwd, phase);
|
|
685
|
-
const tier = initPhaseTier(cwd, phase, options);
|
|
686
662
|
const result = {
|
|
687
663
|
planner_model: resolveModelInternal(cwd, 'pp-planner'),
|
|
688
664
|
checker_model: resolveModelInternal(cwd, 'pp-plan-checker'),
|
|
689
665
|
commit_docs: config.commit_docs,
|
|
690
|
-
plan_checker_enabled: tier.gates.plan_check,
|
|
691
|
-
plan_check_passes: tier.gates.plan_check_passes,
|
|
692
|
-
security_enforcement_enabled: tier.gates.security_enforcement,
|
|
693
|
-
nyquist_validation_enabled: tier.gates.nyquist_validation,
|
|
694
|
-
phase_tier: phaseTierSummary(tier),
|
|
695
666
|
phase_found: !!phaseInfo,
|
|
696
667
|
phase_dir: phaseInfo?.['directory'] || null,
|
|
697
668
|
phase_number: phaseInfo?.['phase_number'] || null,
|
|
@@ -1882,6 +1853,8 @@ module.exports = {
|
|
|
1882
1853
|
cmdInitNewProject,
|
|
1883
1854
|
cmdInitNewMilestone,
|
|
1884
1855
|
cmdInitQuick,
|
|
1856
|
+
// 16-05 (D-29): the pure quick-id builder, so the plan-id owner is tested over real ids.
|
|
1857
|
+
buildQuickId,
|
|
1885
1858
|
cmdInitIngestDocs,
|
|
1886
1859
|
cmdInitResume,
|
|
1887
1860
|
cmdInitVerifyWork,
|
|
@@ -27,7 +27,7 @@ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs")
|
|
|
27
27
|
*/
|
|
28
28
|
const PROFILES = Object.freeze({
|
|
29
29
|
core: Object.freeze([
|
|
30
|
-
// init is the front door every installer/hook/no-project message
|
|
30
|
+
// PBI 75232: init is the front door every installer/hook/no-project message
|
|
31
31
|
// names, so it ships in every profile — a minimal install must not print a
|
|
32
32
|
// start command it did not install.
|
|
33
33
|
'init',
|
package/pp-core/bin/lib/io.cjs
CHANGED
|
@@ -187,6 +187,12 @@ const ERROR_REASON = Object.freeze({
|
|
|
187
187
|
VAULT_PROMOTE_TARGET_REFUSED: 'vault_promote_target_refused',
|
|
188
188
|
VAULT_PROMOTE_CONCURRENT_EDIT: 'vault_promote_concurrent_edit',
|
|
189
189
|
VAULT_PROMOTE_TRANSPORT_FAILED: 'vault_promote_transport_failed',
|
|
190
|
+
// vault.mode (Vault Assurance 13-03, D-08/D-14): an unrecognised mode value,
|
|
191
|
+
// and the standards-mode promote refusal
|
|
192
|
+
VAULT_MODE_INVALID: 'vault_mode_invalid',
|
|
193
|
+
// 13-REVIEW WR-01: the mode resolver failed; refused like an invalid value
|
|
194
|
+
VAULT_MODE_UNRESOLVED: 'vault_mode_unresolved',
|
|
195
|
+
VAULT_PROMOTE_NOT_APPLICABLE: 'vault_promote_not_applicable',
|
|
190
196
|
// discovery rollup (discovery-v2 Phase 1: D-07/D-08 gate)
|
|
191
197
|
DISCOVERY_ROLLUP_PARSE_FAILED: 'discovery_rollup_parse_failed',
|
|
192
198
|
DISCOVERY_ROLLUP_MISMATCH: 'discovery_rollup_mismatch',
|
|
@@ -201,6 +207,8 @@ const ERROR_REASON = Object.freeze({
|
|
|
201
207
|
DISCOVERY_ELEMENT_SWEEP_FAILED: 'discovery_element_sweep_failed',
|
|
202
208
|
// discovery scope re-baselining (discovery-v2 Phase 5: D-72/D-73 gate)
|
|
203
209
|
DISCOVERY_REBASELINE_CHECK_FAILED: 'discovery_rebaseline_check_failed',
|
|
210
|
+
// discovery vault context record (PBI 75336)
|
|
211
|
+
DISCOVERY_VAULT_CHECK_FAILED: 'discovery_vault_check_failed',
|
|
204
212
|
// generic
|
|
205
213
|
USAGE: 'usage',
|
|
206
214
|
UNKNOWN: 'unknown',
|
|
@@ -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;
|
|
@@ -396,20 +476,39 @@ function renderLoopHooks(resolved) {
|
|
|
396
476
|
* pre-cutover keys like `workflow.ui_phase` that live in config.json but are not
|
|
397
477
|
* yet exposed through loadConfig's whitelist.
|
|
398
478
|
*
|
|
399
|
-
* --active-cap <capId>: when present, resolves hooks for <point>
|
|
400
|
-
*
|
|
401
|
-
*
|
|
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.
|
|
402
488
|
* No JSON envelope is emitted — output is clean for shell $(…) capture.
|
|
403
489
|
* Missing <capId> value → coreError + non-zero exit.
|
|
404
490
|
* Unknown/inactive capId → `false` (not an error).
|
|
405
491
|
*
|
|
406
|
-
* --
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
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.
|
|
499
|
+
*
|
|
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.
|
|
413
512
|
*/
|
|
414
513
|
function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
|
|
415
514
|
if (!point) {
|
|
@@ -422,18 +521,10 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
|
|
|
422
521
|
coreError('--active-cap requires a <capId> value (e.g. --active-cap tdd)');
|
|
423
522
|
return;
|
|
424
523
|
}
|
|
425
|
-
//
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
// among them — and exits 0. A MISSING config is not refused: that is a fresh
|
|
430
|
-
// project, and the defaults are its real settings. stdout stays empty so a
|
|
431
|
-
// host capturing `$(…)` never reads a refusal as "no hooks".
|
|
432
|
-
const unreadable = unreadableConfigFiles(cwd);
|
|
433
|
-
if (unreadable.length > 0) {
|
|
434
|
-
coreError(`loop render-hooks ${point} refused: the project config cannot be read, so no hook can be resolved ` +
|
|
435
|
-
`(rendering anyway would silently drop every hook the config switches on, a required client vault among them). ` +
|
|
436
|
-
`${describeUnreadableConfig(unreadable)}. Repair or remove the file, then re-run. Stop this workflow here.`, ERROR_REASON.CONFIG_PARSE_FAILED);
|
|
524
|
+
// --into <role> mode: emit matching contribution fragment text only
|
|
525
|
+
const intoRole = typeof options['into'] === 'string' ? options['into'] : undefined;
|
|
526
|
+
if (activeCapId === undefined && intoRole !== undefined && intoRole === '') {
|
|
527
|
+
coreError('--into requires a <role> value (e.g. --into executor)');
|
|
437
528
|
return;
|
|
438
529
|
}
|
|
439
530
|
const runtimeConfigDir = typeof options['configDir'] === 'string'
|
|
@@ -453,17 +544,6 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
|
|
|
453
544
|
catch {
|
|
454
545
|
config = {};
|
|
455
546
|
}
|
|
456
|
-
const tierPhase = typeof options['phase'] === 'string' && options['phase'] !== '' ? options['phase'] : null;
|
|
457
|
-
const tierCli = typeof options['tier'] === 'string' && options['tier'] !== '' ? options['tier'] : null;
|
|
458
|
-
let phaseTier = null;
|
|
459
|
-
if (tierPhase !== null || tierCli !== null) {
|
|
460
|
-
if (tierCli !== null && !['light', 'standard', 'heavy'].includes(tierCli)) {
|
|
461
|
-
coreError('--tier needs one of light, standard, heavy');
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
phaseTier = (0, phase_tier_cjs_1.resolvePhaseTierForProject)(cwd, tierPhase, tierCli);
|
|
465
|
-
config = (0, phase_tier_cjs_1.overlayTierGates)(config, phaseTier);
|
|
466
|
-
}
|
|
467
547
|
const state = resolveCapabilityRuntimeState(cwd, runtimeConfigDir, config);
|
|
468
548
|
// Registry is the static generated module — same object capability-state uses internally.
|
|
469
549
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -472,9 +552,27 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
|
|
|
472
552
|
for (const cap of state.capabilities || []) {
|
|
473
553
|
capabilityStatesById.set(cap.id, cap);
|
|
474
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);
|
|
475
568
|
let resolved;
|
|
476
569
|
try {
|
|
477
|
-
|
|
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
|
+
});
|
|
478
576
|
}
|
|
479
577
|
catch (err) {
|
|
480
578
|
const msg = (err instanceof Error) ? err.message : String(err);
|
|
@@ -483,19 +581,37 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
|
|
|
483
581
|
}
|
|
484
582
|
// --active-cap mode: print exactly 'true' or 'false' with no envelope
|
|
485
583
|
if (activeCapId !== undefined) {
|
|
486
|
-
|
|
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;
|
|
487
592
|
process.stdout.write(isActive ? 'true\n' : 'false\n');
|
|
488
593
|
return;
|
|
489
594
|
}
|
|
595
|
+
// --into mode: fragment text of the active contributions targeting <role>
|
|
596
|
+
if (intoRole !== undefined) {
|
|
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
|
+
}
|
|
605
|
+
if (texts.length > 0)
|
|
606
|
+
process.stdout.write(texts.join('\n\n') + '\n');
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
490
609
|
const rendered = renderLoopHooks(resolved);
|
|
491
610
|
const envelope = {
|
|
492
611
|
point: resolved.point,
|
|
493
612
|
activeHooks: resolved.activeHooks,
|
|
494
613
|
rendered,
|
|
495
614
|
};
|
|
496
|
-
if (phaseTier) {
|
|
497
|
-
envelope.phase_tier = { phase: tierPhase, tier: phaseTier.tier, source: phaseTier.source, suppressed: phaseTier.suppressed };
|
|
498
|
-
}
|
|
499
615
|
if (state.warnings && state.warnings.length > 0) {
|
|
500
616
|
envelope.warnings = state.warnings;
|
|
501
617
|
}
|
|
@@ -518,6 +634,12 @@ module.exports = {
|
|
|
518
634
|
// Re-exported for identity parity guard (FIX 2: resolveConfigValues in this module
|
|
519
635
|
// calls resolveConfigKey; exporting it here makes the single-owner contract testable).
|
|
520
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,
|
|
521
643
|
CANONICAL_POINTS_FALLBACK,
|
|
522
644
|
CANONICAL_POINTS,
|
|
523
645
|
};
|