@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
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# `execute:pre` capability dispatch — fetch, then decide (GATE-03)
|
|
2
|
+
|
|
3
|
+
Extracted from `execute-phase.md` so the host loop body keeps shrinking, per ADR-857 Phase 6.
|
|
4
|
+
`steps/` files are outside `tests/workflow-size-baseline.json` by design; the host is not.
|
|
5
|
+
|
|
6
|
+
This step is dispatched from `execute-phase.md`'s `execute_pre_dispatch` step, which sits *before*
|
|
7
|
+
`safe_resume_gate` deliberately: that is the first step that can spawn an executor, and the gate must
|
|
8
|
+
run before any code is written, not after.
|
|
9
|
+
|
|
10
|
+
Read the `activeHooks` array from the `EXECUTE_PRE_HOOKS_JSON=` line the host's render fence printed, in-context (do NOT pipe it through a
|
|
11
|
+
shell parser).
|
|
12
|
+
|
|
13
|
+
**Admission first (D-28, D-05).** `execute-phase.md` Reads this file unconditionally, so this is the
|
|
14
|
+
first point on the execute path that can refuse a config the gate cannot read. It precedes the inert
|
|
15
|
+
branch below because that branch is exactly what a broken config reaches: no hook renders at all.
|
|
16
|
+
Without `.platform-bindings.json` it is inert and prints nothing.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
_PP_SHIM_NAME="pp-tools.cjs"; _pp_root() { ( for _v in $(env | sed -n 's/^\(GIT_[A-Za-z0-9_]*\)=.*/\1/p'); do unset "$_v"; done; _c=$(git rev-parse --git-common-dir 2>/dev/null); if [ "${_c##*/}" = .git ] && cd "$_c/.." 2>/dev/null; then pwd -P; else git rev-parse --show-toplevel 2>/dev/null || pwd; fi ); }; pp_run() { case "$PP_TOOLS" in *.cjs) node "$PP_TOOLS" "$@";; *) "$PP_TOOLS" "$@";; esac; }; _PP_RUNTIME_ROOT="${RUNTIME_DIR:-$(_pp_root)}"; PP_TOOLS="${_PP_RUNTIME_ROOT}/pp-core/bin/${_PP_SHIM_NAME}"; if [ -f "$PP_TOOLS" ]; then :; elif [ -f "${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}"; elif command -v pp-tools >/dev/null 2>&1; then PP_TOOLS="$(command -v pp-tools)"; else echo "ERROR: pp-tools.cjs not found at $PP_TOOLS and pp-tools is not on PATH. Run: npx -y @pareto4/pp-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${PP_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${PP_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
20
|
+
VAULT_ADMIT_PRESENT=$(pp_run query platform-bindings --pick present 2>/dev/null || echo true)
|
|
21
|
+
VAULT_ADMIT=inert
|
|
22
|
+
if [ "$VAULT_ADMIT_PRESENT" = true ]; then VAULT_ADMIT=$(pp_run vault admission 2>/dev/null || echo unrecognised); fi
|
|
23
|
+
case "$VAULT_ADMIT" in on) echo "VAULT_ADMIT=on" ;; off|inert) : ;; *) pp_run vault admission --explain; echo "PARETO_GATE_BLOCKED:vault.admission"; exit 1 ;; esac
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**If `activeHooks` is empty or absent:** skip the rest of this file silently. This branch is what
|
|
27
|
+
keeps a project with no active `execute:pre` capability structurally inert.
|
|
28
|
+
|
|
29
|
+
**Otherwise, in this order — the order is the requirement:**
|
|
30
|
+
|
|
31
|
+
1. **Fetch first.** For any active entry where `kind == "step"` and `capId == "vault"`, set
|
|
32
|
+
`VAULT_FETCH_HOOKS_JSON=$EXECUTE_PRE_HOOKS_JSON`, then Read and
|
|
33
|
+
execute `pp-core/workflows/execute-phase/steps/vault-fetch.md`. The fetch is attempted *before*
|
|
34
|
+
any block decision so a fresh clone or a CI runner with a reachable vault fetches and passes,
|
|
35
|
+
rather than being blocked for a cache it never had a chance to populate (GATE-03).
|
|
36
|
+
|
|
37
|
+
The binding is not decoration and it is not the same shape as step 2's: `vault-fetch.md` is
|
|
38
|
+
Read from three call sites at two loop points, so it guards on the **parameter**
|
|
39
|
+
`VAULT_FETCH_HOOKS_JSON` and names no caller's own payload variable. Until 02-10 it named
|
|
40
|
+
`plan-phase.md`'s, which this dispatcher never sets and never aliased — and the instruction
|
|
41
|
+
immediately after the guard is *"if no active vault step hook exists: skip"*. A literal
|
|
42
|
+
reading therefore skipped the `execute:pre` fetch outright and let step 2 decide on whatever
|
|
43
|
+
cache happened to exist, which is exactly the fresh-clone false block this ordering exists to
|
|
44
|
+
prevent (02-VERIFICATION, G-2). Binding it here is the whole fix at this call site.
|
|
45
|
+
|
|
46
|
+
2. **Then gate.** Set `GATE_POINT=execute:pre`, `GATE_HOOKS_JSON=$EXECUTE_PRE_HOOKS_JSON`,
|
|
47
|
+
`GATE_POSITIONAL_ARG="${PHASE_NUMBER}"` and `GATE_WS` to the active workstream name (empty in flat
|
|
48
|
+
mode) — a phase-number positional cannot resolve without it on a partitioned project — then Read and follow
|
|
49
|
+
`pp-core/workflows/execute-phase/steps/check-query-gate.md` — the single generic `check.query`
|
|
50
|
+
gate enforcer (GATE-02, D-VH-7). It owns the halt decision; do not restate its loop here.
|
|
51
|
+
|
|
52
|
+
3. **Then render.** If the workflow is still running, set `VAULT_GATE_POINT=execute` and Read and
|
|
53
|
+
execute `pp-core/workflows/execute-phase/steps/vault-load-gate.md`. It reports the load state for
|
|
54
|
+
the operator and decides nothing — the decision was step 2's.
|
|
55
|
+
|
|
56
|
+
This line is what makes the load-gate file's `execute:pre` message column live. Until this phase
|
|
57
|
+
that column was written and never dispatched: the execute half of a file that claimed to serve
|
|
58
|
+
both call sites was dead code.
|
|
59
|
+
|
|
60
|
+
4. **Then anchor the phase.** If the workflow is still running, run
|
|
61
|
+
`pp_run vault manifest start "${PHASE_DIR}" --raw` (the host's initialize step already binds
|
|
62
|
+
`pp_run` and parses `phase_dir` from `INIT`) and print its one-line result to the operator.
|
|
63
|
+
|
|
64
|
+
It records `phases[<phase>].startCommit = HEAD` in `.phase-manifest.json` — the anchor both
|
|
65
|
+
`check vault.audit` and `check vault.review` scope the phase from on a project that commits
|
|
66
|
+
straight to its base branch (UAT-6-F1). It writes only when no anchor exists, so a resume or a
|
|
67
|
+
`--gaps-only` run keeps the original one. On a phase that was already executed without an anchor
|
|
68
|
+
(its directory holds a `*-SUMMARY.md`) it refuses with `phase-already-executed` and writes nothing:
|
|
69
|
+
HEAD there sits after the phase's own commits, and the audit scopes such a phase from its SUMMARY
|
|
70
|
+
trace digests instead. An entry left under this phase number by a different phase directory (a
|
|
71
|
+
number reused after a milestone reset) is stale, and `start` replaces it with a fresh anchor.
|
|
72
|
+
The verb is inert without the practices capability,
|
|
73
|
+
exits 0 on every path, and reports a failure as `ok: false` with a reason that is printed and
|
|
74
|
+
never halts — the audit's SUMMARY trace-digest fallback and its stated fallback ground are the
|
|
75
|
+
degraded path.
|
|
76
|
+
|
|
77
|
+
4b. **Then record the mode-neutral start anchor (D-31).** If the workflow is still running and the
|
|
78
|
+
`activeHooks` array holds an entry with `capId == "vault"` (the same mechanical check step 1 uses
|
|
79
|
+
for the fetch), run `pp_run vault anchor start "${PHASE_DIR}" --raw` and print its one-line result
|
|
80
|
+
to the operator. Otherwise skip it.
|
|
81
|
+
|
|
82
|
+
It writes `${PHASE_DIR}/.vault-anchors.json` with `start` = HEAD. Every record the phase's
|
|
83
|
+
executors write later (the execute-stage `vault trace scaffold` ledger, the `vault standards
|
|
84
|
+
consulted` anchor line) reads it as its START anchor, beside the END anchor (HEAD at write time)
|
|
85
|
+
the CLI records itself. A commit is then covered only when it falls inside some record's
|
|
86
|
+
start..end range, so an ungoverned commit followed by any record can no longer hide behind that
|
|
87
|
+
record's end. The practices-only manifest start in step 4 is not enough: it exists only when
|
|
88
|
+
`vault.practices_enabled` is on, so a standards-mode phase would have no start at all. Re-running
|
|
89
|
+
at a new HEAD (a resumed run, `--wave N`, `--gaps-only`) records that HEAD as the start of every
|
|
90
|
+
record written from now on (D-98), so nothing committed between two runs is claimed by a later
|
|
91
|
+
record; the earlier start stays in the file's history, where a worktree executor's end-only record
|
|
92
|
+
written before the resume still finds the start that preceded its own end. The phase's own commits
|
|
93
|
+
made between its last record and the resume are then uncovered, and the strict gate names them with
|
|
94
|
+
their repair: `vault anchor backfill <summary> --from <a recorded start> --attest <commit>`, the
|
|
95
|
+
operator's attestation of each commit by its own id; nothing is widened, and every check lists it.
|
|
96
|
+
The verb prints nothing and writes nothing unless `vault admission` answers
|
|
97
|
+
`on`, exits 0 on every path, and reports a refusal as `ok: false` with a reason that is printed
|
|
98
|
+
and never halts. A phase that has no start anchor fails at the strict points instead.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Per-plan worktree decision (#2772)
|
|
2
|
+
|
|
3
|
+
Run this for **each plan in the current wave** before its `Agent()` dispatch. The output `USE_WORKTREES_FOR_PLAN` gates the dispatch branch (worktree mode vs sequential mode) for that plan only — other plans in the same wave can still take the worktree path.
|
|
4
|
+
|
|
5
|
+
`SUBMODULE_PATHS` is computed once in the `initialize` step (parsed from `.gitmodules`).
|
|
6
|
+
|
|
7
|
+
`PLAN_FILES` is the whitespace-separated list of paths the plan declared it will touch, extracted from the `phase-plan-index` JSON loaded in `discover_and_group_plans`:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# plan_json is the JSON object for this plan from PLAN_INDEX.plans[]
|
|
11
|
+
# files_modified is an array of strings (repo-relative paths or globs)
|
|
12
|
+
PLAN_FILES=$(jq -r '.files_modified // [] | join(" ")' <<<"$plan_json")
|
|
13
|
+
plan_id=$(jq -r '.id' <<<"$plan_json")
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Then run the per-plan gate:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
USE_WORKTREES_FOR_PLAN="$USE_WORKTREES"
|
|
20
|
+
|
|
21
|
+
if [ -n "$SUBMODULE_PATHS" ] && [ "$USE_WORKTREES_FOR_PLAN" != "false" ]; then
|
|
22
|
+
if [ -z "$PLAN_FILES" ]; then
|
|
23
|
+
# Fallback: planned paths are unknown/unparseable — fall back to the safe
|
|
24
|
+
# behavior (disable worktree isolation for this plan) and log why.
|
|
25
|
+
echo "[worktree] Plan ${plan_id}: files_modified missing/unparseable — disabling worktree isolation as a safety fallback (submodule project)"
|
|
26
|
+
USE_WORKTREES_FOR_PLAN=false
|
|
27
|
+
else
|
|
28
|
+
# Compute intersection with glob-safe normalization. Both sides are
|
|
29
|
+
# normalized (strip leading "./", strip trailing "/") and matched
|
|
30
|
+
# bidirectionally so a globby planned path like "vendor/**/*.c" still
|
|
31
|
+
# matches submodule "vendor/foo", and "./vendor/foo/bar.c" matches
|
|
32
|
+
# submodule "vendor/foo".
|
|
33
|
+
INTERSECT=""
|
|
34
|
+
set -f # disable globbing while iterating literal patterns
|
|
35
|
+
for sm_raw in $SUBMODULE_PATHS; do
|
|
36
|
+
# Normalize submodule path: strip ./ prefix and trailing /
|
|
37
|
+
sm="${sm_raw#./}"
|
|
38
|
+
sm="${sm%/}"
|
|
39
|
+
[ -z "$sm" ] && continue
|
|
40
|
+
for pf_raw in $PLAN_FILES; do
|
|
41
|
+
# Normalize planned path the same way
|
|
42
|
+
pf="${pf_raw#./}"
|
|
43
|
+
pf="${pf%/}"
|
|
44
|
+
[ -z "$pf" ] && continue
|
|
45
|
+
matched=0
|
|
46
|
+
# Direction 1: planned path is the submodule or lies inside it
|
|
47
|
+
case "$pf" in
|
|
48
|
+
"$sm"|"$sm"/*) matched=1 ;;
|
|
49
|
+
esac
|
|
50
|
+
# Direction 2: submodule lies inside the planned path (e.g. plan
|
|
51
|
+
# declares "vendor" or a glob expanding to a directory containing
|
|
52
|
+
# the submodule).
|
|
53
|
+
if [ "$matched" -eq 0 ]; then
|
|
54
|
+
case "$sm" in
|
|
55
|
+
"$pf"|"$pf"/*) matched=1 ;;
|
|
56
|
+
esac
|
|
57
|
+
fi
|
|
58
|
+
# Direction 3: planned path uses a glob — strip glob wildcards
|
|
59
|
+
# and check whether the resulting prefix overlaps the submodule
|
|
60
|
+
# path in either direction.
|
|
61
|
+
if [ "$matched" -eq 0 ]; then
|
|
62
|
+
case "$pf" in
|
|
63
|
+
*'*'*|*'?'*|*'['*)
|
|
64
|
+
# Take the literal prefix before the first glob metachar.
|
|
65
|
+
prefix="${pf%%[*?[]*}"
|
|
66
|
+
prefix="${prefix%/}"
|
|
67
|
+
if [ -n "$prefix" ]; then
|
|
68
|
+
case "$sm" in
|
|
69
|
+
"$prefix"|"$prefix"/*) matched=1 ;;
|
|
70
|
+
esac
|
|
71
|
+
if [ "$matched" -eq 0 ]; then
|
|
72
|
+
case "$prefix" in
|
|
73
|
+
"$sm"|"$sm"/*) matched=1 ;;
|
|
74
|
+
esac
|
|
75
|
+
fi
|
|
76
|
+
fi
|
|
77
|
+
;;
|
|
78
|
+
esac
|
|
79
|
+
fi
|
|
80
|
+
if [ "$matched" -eq 1 ]; then
|
|
81
|
+
INTERSECT="$INTERSECT $pf_raw"
|
|
82
|
+
fi
|
|
83
|
+
done
|
|
84
|
+
done
|
|
85
|
+
set +f
|
|
86
|
+
if [ -n "$INTERSECT" ]; then
|
|
87
|
+
echo "[worktree] Plan ${plan_id}: planned paths intersect submodule paths (${INTERSECT# }) — disabling worktree isolation for this plan"
|
|
88
|
+
USE_WORKTREES_FOR_PLAN=false
|
|
89
|
+
fi
|
|
90
|
+
fi
|
|
91
|
+
fi
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
After running this for the plan, the dispatch branches in `execute_waves` step 3 MUST gate on `USE_WORKTREES_FOR_PLAN` for the current plan, not on the project-level `USE_WORKTREES`. Track which plans in this wave actually used worktrees (append `plan_id` to a `WAVE_WORKTREE_PLANS` accumulator when `USE_WORKTREES_FOR_PLAN != false`) — the post-wave cleanup step (5.5) uses this to decide whether worktree-merge cleanup is needed at all.
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
# Step: post_merge_gate
|
|
2
2
|
|
|
3
|
-
Post-merge build & test gate. Runs
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
**Step A — Build gate:**
|
|
9
|
-
When it is false (no plan in the wave touches source), print
|
|
10
|
-
`⚠ Post-merge build gate skipped — no plan in this wave touches source` and go to Step B.
|
|
3
|
+
Post-merge build & test gate. Runs after all worktrees in a wave are merged
|
|
4
|
+
(parallel mode), or after the last plan completes (serial mode). Catches
|
|
5
|
+
cross-plan integration failures that individual worktree self-checks cannot
|
|
6
|
+
detect.
|
|
7
|
+
|
|
8
|
+
**Step A — Build gate:**
|
|
11
9
|
|
|
12
10
|
```bash
|
|
13
11
|
_PP_SHIM_NAME="pp-tools.cjs"; _pp_root() { ( for _v in $(env | sed -n 's/^\(GIT_[A-Za-z0-9_]*\)=.*/\1/p'); do unset "$_v"; done; _c=$(git rev-parse --git-common-dir 2>/dev/null); if [ "${_c##*/}" = .git ] && cd "$_c/.." 2>/dev/null; then pwd -P; else git rev-parse --show-toplevel 2>/dev/null || pwd; fi ); }; pp_run() { case "$PP_TOOLS" in *.cjs) node "$PP_TOOLS" "$@";; *) "$PP_TOOLS" "$@";; esac; }; _PP_RUNTIME_ROOT="${RUNTIME_DIR:-$(_pp_root)}"; PP_TOOLS="${_PP_RUNTIME_ROOT}/pp-core/bin/${_PP_SHIM_NAME}"; if [ -f "$PP_TOOLS" ]; then :; elif [ -f "${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}"; elif command -v pp-tools >/dev/null 2>&1; then PP_TOOLS="$(command -v pp-tools)"; else echo "ERROR: pp-tools.cjs not found at $PP_TOOLS and pp-tools is not on PATH. Run: npx -y @pareto4/pp-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${PP_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${PP_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
@@ -1,25 +1,126 @@
|
|
|
1
|
-
#
|
|
1
|
+
# vault fetch + summary — the bespoke step dispatch, served at `plan:pre` and `execute:pre`
|
|
2
2
|
|
|
3
|
-
Capability-driven dispatch for an active step hook where `kind == "step"` and `capId == "vault"
|
|
3
|
+
Capability-driven dispatch for an active vault step hook where `kind == "step"` and `capId == "vault"`. The `vault` capability declares that step at **two** loop points — `plan:pre` and `execute:pre` — and this one file serves both, Read from ten call sites — nine hook-mode callers at those two points and one direct-mode caller with no loop point (see the Direct mode paragraph below); the caller table below names them. Runs `vault fetch`, which now writes `.planning/vault/SUMMARY.md` itself (STRICT-05). What that buys differs by point and both reasons are real: at `plan:pre` the summary exists on the researcher and planner read paths before the researcher `Agent()` spawn; at `execute:pre` the fetch is attempted *before* the gate decides, so a fresh clone or a CI runner with a reachable vault fetches and passes rather than being blocked for a cache it never had a chance to populate (GATE-03). The generic step contract dispatches only `ref.skill`/`ref.agent` hooks — not `ref.command` — so this bespoke block owns the `vault fetch` command run. The registry evaluates `when: vault.enabled`; an inert capability yields no hook.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Callers — bind `VAULT_FETCH_HOOKS_JSON` (hook mode) or set `VAULT_FETCH_DIRECT` (direct mode) before Reading this file
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`VAULT_FETCH_HOOKS_JSON` is a **parameter**, in exactly the sense `check-query-gate.md`'s
|
|
8
|
+
`GATE_HOOKS_JSON` is one: this file never renders hooks itself, and the caller sets it to the
|
|
9
|
+
`activeHooks` payload the caller already rendered for **its own** loop point. This file therefore
|
|
10
|
+
names no caller's payload variable anywhere, deliberately — it is Read from ten places, nine at
|
|
11
|
+
two loop points and one with no loop point at all, so any one variable name is dead at the other
|
|
12
|
+
nine, and the guard's next instruction is *skip*. A step file that hard-names one caller's variable
|
|
13
|
+
does not merely mis-document the other callers; it silently turns their fetch off
|
|
14
|
+
(02-VERIFICATION, G-2).
|
|
8
15
|
|
|
9
|
-
|
|
16
|
+
| Caller | Loop point | Binding |
|
|
17
|
+
|--------|------------|---------|
|
|
18
|
+
| `pp-core/workflows/plan-phase.md`, `### plan:pre vault fetch + summary` | `plan:pre` | `VAULT_FETCH_HOOKS_JSON` ← the `plan:pre` payload that section already has in scope |
|
|
19
|
+
| `pp-core/workflows/autonomous.md`, §3a.5-i, the `ref.command` + `capId == "vault"` branch | `plan:pre` | `VAULT_FETCH_HOOKS_JSON` ← the `plan:pre` payload §3a.5 rendered |
|
|
20
|
+
| `pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md`, step 1 "Fetch first" | `execute:pre` | `VAULT_FETCH_HOOKS_JSON` ← the `execute:pre` payload that dispatcher already reads its `activeHooks` from |
|
|
21
|
+
| `pp-core/workflows/quick.md`, Step 2.4 (16-11) | `plan:pre` | `VAULT_FETCH_HOOKS_JSON` ← quick's `plan:pre` payload, filtered in the fence to the `vault` and `vault-practices` capabilities, after its own admission fence |
|
|
22
|
+
| `pp-core/workflows/debug.md`, § 0b Vault admission and load gate (16-14) | `plan:pre` | `VAULT_FETCH_HOOKS_JSON` ← debug's `plan:pre` payload, filtered in the fence to the `vault` and `vault-practices` capabilities, after its own admission fence |
|
|
23
|
+
| `pp-core/workflows/code-review-fix.md`, `vault_admission_gate` (16-15) | `execute:pre` | `VAULT_FETCH_HOOKS_JSON` ← the door's `execute:pre` payload, filtered in the fence to the `vault` and `vault-practices` capabilities, after its own admission fence |
|
|
24
|
+
| `pp-core/workflows/validate-phase.md`, § 0.5 (16-15) | `execute:pre` | `VAULT_FETCH_HOOKS_JSON` ← the door's `execute:pre` payload, filtered in the fence to the `vault` and `vault-practices` capabilities, after its own admission fence |
|
|
25
|
+
| `pp-core/workflows/add-tests.md`, `vault_admission_gate` (16-15) | `execute:pre` | `VAULT_FETCH_HOOKS_JSON` ← the door's `execute:pre` payload, filtered in the fence to the `vault` and `vault-practices` capabilities, after its own admission fence |
|
|
26
|
+
| `pp-core/workflows/audit-fix.md`, `vault_admission_gate` (16-15) | `execute:pre` | `VAULT_FETCH_HOOKS_JSON` ← the door's `execute:pre` payload, filtered in the fence to the `vault` and `vault-practices` capabilities, after its own admission fence |
|
|
27
|
+
| `pp-core/workflows/discovery-sprint/vault-context.md`, § 2 "Fetch" (Read from Flow 2 Step 1 and prefill Step 0 of the discovery sprint) | none — direct mode (D-11-09) | `VAULT_FETCH_DIRECT=true`; the payload parameter is left empty; the two activation guards run first, in order |
|
|
28
|
+
|
|
29
|
+
A **next caller** (the eleventh) inherits one obligation and one only: bind this parameter from the payload it
|
|
30
|
+
rendered for its own point, before the Read — or, when it has no loop point, run the two activation
|
|
31
|
+
guards and set `VAULT_FETCH_DIRECT` (Direct mode below). Nothing in this file needs to change to
|
|
32
|
+
accommodate it — that is the whole reason both parameters exist instead of a sentence naming a
|
|
33
|
+
caller's variable.
|
|
34
|
+
|
|
35
|
+
The step's static `onError: skip` remains the **registry-level dispatch policy** and is unchanged. Layered on top of it is a **runtime** decision this block makes for itself: when `vault.required` is true, a failed fetch or an unwritten summary hard-stops with `PARETO_GATE_BLOCKED:vault.fetch` and a non-zero exit (STRICT-03, GATE-09). A static `onError` cannot depend on a config value, which is why the arm lives here rather than in the capability declaration. With `vault.required` false — the default — every path below behaves exactly as it did before: a warning, and the run continues.
|
|
36
|
+
|
|
37
|
+
**Admission first (D-28, D-05).** This check runs before the hook-presence check below because a loop-point host renders no vault hook at all on a config the gate cannot read, so without it a direct or unconditional caller would skip the fetch silently. It is inert without `.platform-bindings.json` (a probe that cannot answer, such as on a malformed bindings file, defers to the verb, which checks bindings first itself); `on`, `off` and `inert` continue to the hook check exactly as before, and anything else (`config-unreadable`, or no answer) halts naming the file and the repair (research C-4).
|
|
10
38
|
|
|
11
39
|
```bash
|
|
12
40
|
_PP_SHIM_NAME="pp-tools.cjs"; _pp_root() { ( for _v in $(env | sed -n 's/^\(GIT_[A-Za-z0-9_]*\)=.*/\1/p'); do unset "$_v"; done; _c=$(git rev-parse --git-common-dir 2>/dev/null); if [ "${_c##*/}" = .git ] && cd "$_c/.." 2>/dev/null; then pwd -P; else git rev-parse --show-toplevel 2>/dev/null || pwd; fi ); }; pp_run() { case "$PP_TOOLS" in *.cjs) node "$PP_TOOLS" "$@";; *) "$PP_TOOLS" "$@";; esac; }; _PP_RUNTIME_ROOT="${RUNTIME_DIR:-$(_pp_root)}"; PP_TOOLS="${_PP_RUNTIME_ROOT}/pp-core/bin/${_PP_SHIM_NAME}"; if [ -f "$PP_TOOLS" ]; then :; elif [ -f "${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}"; elif command -v pp-tools >/dev/null 2>&1; then PP_TOOLS="$(command -v pp-tools)"; else echo "ERROR: pp-tools.cjs not found at $PP_TOOLS and pp-tools is not on PATH. Run: npx -y @pareto4/pp-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${PP_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${PP_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
13
|
-
|
|
14
|
-
|
|
41
|
+
VAULT_ADMIT_PRESENT=$(pp_run query platform-bindings --pick present 2>/dev/null || echo true)
|
|
42
|
+
VAULT_ADMIT=inert
|
|
43
|
+
if [ "$VAULT_ADMIT_PRESENT" = true ]; then VAULT_ADMIT=$(pp_run vault admission 2>/dev/null || echo unrecognised); fi
|
|
44
|
+
case "$VAULT_ADMIT" in on) echo "VAULT_ADMIT=on" ;; off|inert) : ;; *) pp_run vault admission --explain; echo "PARETO_GATE_BLOCKED:vault.admission"; exit 1 ;; esac
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Read the active vault step hook from `VAULT_FETCH_HOOKS_JSON` (in-context) where `kind == "step"` and `capId == "vault"`. A caller binding this payload to `$X` means the JSON on the `X=` line the caller's render fence PRINTED (D-99): a payload a fence only assigned is invisible in the next call.
|
|
48
|
+
|
|
49
|
+
**If no active vault step hook exists** and `VAULT_FETCH_DIRECT` is not the literal `true`: skip (non-Pareto project, `vault.enabled` false, or vault bindings absent).
|
|
50
|
+
|
|
51
|
+
**Direct mode — `VAULT_FETCH_DIRECT` (D-11-09):** consulted **only** when `VAULT_FETCH_HOOKS_JSON` is empty, mirroring the `GATE_DIRECT_QUERY` parameter `check-query-gate.md` already ships for build entry points that declare no loop-host frontmatter. A caller with no loop point cannot render a payload at all: the `vault` capability declares this step at exactly `plan:pre` and `execute:pre`, and the twelve canonical loop points are frozen by the loop-host contract, so there is no third point such a caller could render and no payload for it to bind. Setting this parameter to the literal `true` makes the block below run exactly as it does for an active hook — the same `vault fetch`, the same `.planning/vault/SUMMARY.md` write, the same three arms and the same required-miss halt token and non-zero exit — so a direct caller inherits the first of the two `plan:pre` halt surfaces from its owner rather than authoring a fourth copy of it (D-11-11). A direct caller MUST run the two activation guards itself first, in this order: `pp_run query platform-bindings --pick present` (when not `true`, do not Read this file at all — non-Pareto project, fully inert), then `pp_run vault admission` (16-04, D-28: on `on`, Read this file; on `off` or `inert`, do not Read it; on anything else, `config-unreadable` or no answer, print `pp_run vault admission --explain` and halt with `PARETO_GATE_BLOCKED:vault.admission` and a non-zero exit, because a config the gate cannot read is not a vault that is off). A direct dispatch has not been through the registry's `when` evaluation and therefore carries none of its inertness, which is the same obligation `check-query-gate.md` § Direct-query mode places on a direct caller of the gate. Hook-mode callers are unaffected: with a non-empty payload this parameter is never read, so all hook-mode callers behave exactly as they did before it existed.
|
|
52
|
+
|
|
53
|
+
The block below also records the verdict before and after the fetch and says so when the fetch changed it (D-08-08): the plan:pre / execute:pre ordering is fetch-then-gate (`plan-phase.md` §5.1 then §5.6; `execute-pre-dispatch.md` steps 1-2), so a by-hand `check vault.status` taken earlier can legitimately disagree with the gate — the disagreement is temporal, and this line is what makes it explicable (D-08-05). Both snapshots are pure reads (D-08-04): they report the manifest, they never fetch, and they add nothing at all on a run that does not reach the block — an inactive hook with no direct-mode caller — because they live inside the run condition above and nowhere else.
|
|
54
|
+
|
|
55
|
+
**If an active vault step hook exists** (recognizable by `capId == "vault"` + `ref.command == "vault fetch"`) — or `VAULT_FETCH_DIRECT` is `true` with an empty payload:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# vault.required — read ONCE, with an explicit default (STRICT-03). The key is
|
|
59
|
+
# declared in `capabilities/vault/capability.json`, so this read returns the
|
|
60
|
+
# project's configured value; with it set true the hard-stop arm below is live.
|
|
61
|
+
VAULT_REQUIRED=$(pp_run query config-get vault.required --raw --default false 2>/dev/null || echo false)
|
|
62
|
+
|
|
63
|
+
# vault fetch: populates .planning/vault/ cache + manifest AND writes
|
|
64
|
+
# .planning/vault/SUMMARY.md itself. The separate `vault summary` subcommand
|
|
65
|
+
# call that used to live here is deliberately GONE (STRICT-05): the fetch and the
|
|
66
|
+
# summary were paired only by this markdown, so a direct CLI fetch, an --auto run
|
|
67
|
+
# that never dispatched this step, or an agent skipping the second instruction
|
|
68
|
+
# each produced a populated cache with no SUMMARY.md. The pairing is now code.
|
|
69
|
+
# D-08-08: a pure read of the manifest as it stands BEFORE this fetch rewrites
|
|
70
|
+
# it. This is the reading an operator's own by-hand `check vault.status` would
|
|
71
|
+
# have produced a moment ago, captured here because nothing downstream can
|
|
72
|
+
# reconstruct it once the fetch has landed.
|
|
73
|
+
VAULT_PRE_VERDICT=$(pp_run check vault.status --raw 2>/dev/null || echo '{}')
|
|
74
|
+
VAULT_FETCH_JSON=$(pp_run vault fetch --raw 2>/dev/null || echo '{}')
|
|
75
|
+
VAULT_SOURCE=$(printf '%s' "$VAULT_FETCH_JSON" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(JSON.parse(d).source||'');}catch{process.stdout.write('');}")
|
|
76
|
+
VAULT_SUMMARY_WRITTEN=$(printf '%s' "$VAULT_FETCH_JSON" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(String(JSON.parse(d).summaryWritten===true));}catch{process.stdout.write('false');}")
|
|
77
|
+
VAULT_SUMMARY_ERROR=$(printf '%s' "$VAULT_FETCH_JSON" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(String(JSON.parse(d).summaryError||''));}catch{process.stdout.write('');}")
|
|
78
|
+
VAULT_FETCH_REASON=$(printf '%s' "$VAULT_FETCH_JSON" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const p=JSON.parse(d);process.stdout.write(String(p.fallbackReason||p.warning||''));}catch{process.stdout.write('');}")
|
|
79
|
+
|
|
80
|
+
# D-08-08: the same pure read AFTER the fetch, and a comparison of the five
|
|
81
|
+
# fields that decide the gate. Values are envelope fields this file's siblings
|
|
82
|
+
# already print; `reason` is a frozen code. Pipes and newlines are stripped and
|
|
83
|
+
# each summary is cut at 300 characters, because the two summaries are carried
|
|
84
|
+
# through a single pipe-delimited string that `cut` then splits.
|
|
85
|
+
VAULT_POST_VERDICT=$(pp_run check vault.status --raw 2>/dev/null || echo '{}')
|
|
86
|
+
VAULT_VERDICT_DIFF=$(PRE="$VAULT_PRE_VERDICT" POST="$VAULT_POST_VERDICT" node -e "const F=['tier','ref','stale','block','reason'];const g=(s)=>{let p={};try{p=JSON.parse(s)||{};}catch{p={};}return F.map((k)=>k+'='+String(p[k]===undefined||p[k]===null?'':p[k])).join(' ').replace(/[\r\n|]/g,'').slice(0,300);};const a=g(process.env.PRE||'');const b=g(process.env.POST||'');process.stdout.write(a===b?'same||':'changed|'+a+'|'+b);")
|
|
87
|
+
VAULT_VERDICT_CHANGED=$(printf '%s' "$VAULT_VERDICT_DIFF" | cut -d'|' -f1)
|
|
88
|
+
VAULT_VERDICT_BEFORE=$(printf '%s' "$VAULT_VERDICT_DIFF" | cut -d'|' -f2)
|
|
89
|
+
VAULT_VERDICT_AFTER=$(printf '%s' "$VAULT_VERDICT_DIFF" | cut -d'|' -f3)
|
|
90
|
+
if [ "$VAULT_VERDICT_CHANGED" = "changed" ]; then
|
|
91
|
+
echo "ℹ vault fetch changed the gate verdict: before — ${VAULT_VERDICT_BEFORE}; after — ${VAULT_VERDICT_AFTER}. A check vault.status read taken before this fetch described the earlier state (D-08-08)."
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# Read the OUTCOME, never the exit code: `pp-tools vault ...` exits 0 on every
|
|
95
|
+
# path by design (D-VH-7 — the command reports, the workflow halts), so the old
|
|
96
|
+
# `if ! pp_run vault fetch` guard could not take its failure branch.
|
|
97
|
+
VAULT_FETCH_FAILED=false
|
|
98
|
+
if [ -z "$VAULT_SOURCE" ] || [ "$VAULT_SOURCE" = "miss" ]; then VAULT_FETCH_FAILED=true; fi
|
|
99
|
+
VAULT_LOAD_OK=true
|
|
100
|
+
if [ "$VAULT_FETCH_FAILED" = "true" ] || [ "$VAULT_SUMMARY_WRITTEN" != "true" ]; then VAULT_LOAD_OK=false; fi
|
|
101
|
+
|
|
102
|
+
if [ "$VAULT_REQUIRED" = "true" ] && [ "$VAULT_LOAD_OK" = "false" ]; then
|
|
103
|
+
# Arm 3 — vault.required is on: a miss, a failed fetch and an unwritable
|
|
104
|
+
# summary all hard-stop (D-VH-4). Print the failure verbatim, then the named
|
|
105
|
+
# halt token and a non-zero exit — the same two halves the extracted enforcer
|
|
106
|
+
# uses, so an agent stops and an unattended runner sees the exit code.
|
|
107
|
+
echo "✗ vault could not be loaded and vault.required is true (STRICT-03)" >&2
|
|
108
|
+
if [ -n "$VAULT_FETCH_REASON" ]; then echo " fetch: $VAULT_FETCH_REASON" >&2; fi
|
|
109
|
+
if [ -n "$VAULT_SUMMARY_ERROR" ]; then echo " summary: $VAULT_SUMMARY_ERROR" >&2; fi
|
|
110
|
+
echo "PARETO_GATE_BLOCKED:vault.fetch"
|
|
111
|
+
exit 1
|
|
112
|
+
elif [ "$VAULT_FETCH_FAILED" = "true" ]; then
|
|
113
|
+
# Arm 1 — fetch failed, vault.required off. Same effect as before this plan.
|
|
15
114
|
echo "⚠ vault fetch failed — continuing without vault context (onError:skip)" >&2
|
|
115
|
+
if [ -n "$VAULT_FETCH_REASON" ]; then echo " reason: $VAULT_FETCH_REASON" >&2; fi
|
|
116
|
+
elif [ "$VAULT_SUMMARY_WRITTEN" != "true" ]; then
|
|
117
|
+
# Arm 2 — fetch succeeded, the summary did not reach disk. STRICT-05: this is
|
|
118
|
+
# the branch the confirmation line used to fall through to unconditionally,
|
|
119
|
+
# pointing agents at a SUMMARY.md that was never written.
|
|
120
|
+
echo "⚠ vault summary not written — .planning/vault/SUMMARY.md is absent (onError:skip)" >&2
|
|
121
|
+
if [ -n "$VAULT_SUMMARY_ERROR" ]; then echo " reason: $VAULT_SUMMARY_ERROR" >&2; fi
|
|
16
122
|
else
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
if ! pp_run vault summary 2>&1; then
|
|
20
|
-
echo "⚠ vault summary failed — SUMMARY.md may be absent (onError:skip)" >&2
|
|
21
|
-
else
|
|
22
|
-
echo "✓ vault fetch + summary complete: .planning/vault/SUMMARY.md"
|
|
23
|
-
fi
|
|
123
|
+
# The claim is now reachable ONLY when summaryWritten is true.
|
|
124
|
+
echo "✓ vault fetch + summary complete: .planning/vault/SUMMARY.md"
|
|
24
125
|
fi
|
|
25
126
|
```
|