@pareto4/pp-core 1.12.1 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/agents/pp-code-reviewer.md +1 -1
  3. package/agents/pp-executor.md +2 -13
  4. package/agents/pp-planner.md +1 -1
  5. package/agents/pp-vault-reviewer.md +51 -0
  6. package/commands/pp-ns-review.md +2 -1
  7. package/commands/pp-vault-review.md +33 -0
  8. package/gemini-extension.json +1 -1
  9. package/package.json +1 -1
  10. package/pp-core/bin/lib/capability-registry.cjs +200 -10
  11. package/pp-core/bin/lib/check-command-router.cjs +1242 -37
  12. package/pp-core/bin/lib/clusters.cjs +1 -0
  13. package/pp-core/bin/lib/command-aliases.cjs +7 -0
  14. package/pp-core/bin/lib/compose-bindings.cjs +41 -27
  15. package/pp-core/bin/lib/decisions.cjs +104 -27
  16. package/pp-core/bin/lib/dispatch-plan.cjs +12 -1
  17. package/pp-core/bin/lib/frontmatter.cjs +37 -0
  18. package/pp-core/bin/lib/git-base-branch.cjs +136 -10
  19. package/pp-core/bin/lib/init-contract.cjs +180 -66
  20. package/pp-core/bin/lib/init-interactive.cjs +4 -20
  21. package/pp-core/bin/lib/loop-gates.cjs +213 -0
  22. package/pp-core/bin/lib/loop-resolver.cjs +107 -45
  23. package/pp-core/bin/lib/org-profile.cjs +218 -44
  24. package/pp-core/bin/lib/phase-command-router.cjs +7 -1
  25. package/pp-core/bin/lib/phase-manifest.cjs +527 -0
  26. package/pp-core/bin/lib/phase-tier.cjs +15 -2
  27. package/pp-core/bin/lib/phase.cjs +51 -1
  28. package/pp-core/bin/lib/vault-audit.cjs +2070 -0
  29. package/pp-core/bin/lib/vault-command-router.cjs +460 -1
  30. package/pp-core/bin/lib/vault-practices.cjs +1142 -104
  31. package/pp-core/bin/lib/vault-review.cjs +2359 -0
  32. package/pp-core/bin/lib/vault-trace.cjs +2831 -0
  33. package/pp-core/bin/lib/vault-update.cjs +266 -0
  34. package/pp-core/bin/lib/vault-write-back.cjs +19 -0
  35. package/pp-core/bin/lib/vault.cjs +277 -12
  36. package/pp-core/bin/pp-tools.cjs +30 -13
  37. package/pp-core/bin/shared/model-catalog.json +2 -1
  38. package/pp-core/references/executor-vault-trace.md +47 -0
  39. package/pp-core/references/loop-hook-dispatch.md +13 -2
  40. package/pp-core/references/planner-vault-practices.md +46 -1
  41. package/pp-core/references/planning-config.md +1 -1
  42. package/pp-core/templates/practices/README.md +140 -11
  43. package/pp-core/templates/project-charter.md +226 -0
  44. package/pp-core/workflows/add-tests.md +4 -0
  45. package/pp-core/workflows/audit-fix.md +2 -0
  46. package/pp-core/workflows/check-todos.md +1 -1
  47. package/pp-core/workflows/code-review-fix.md +2 -0
  48. package/pp-core/workflows/debug.md +4 -0
  49. package/pp-core/workflows/discovery-sprint/prefill.md +9 -0
  50. package/pp-core/workflows/discovery-sprint/prototype.md +2 -1
  51. package/pp-core/workflows/discovery-sprint/vault-fetch.md +37 -0
  52. package/pp-core/workflows/discovery-sprint.md +1 -1
  53. package/pp-core/workflows/discovery-to-build.md +1 -1
  54. package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +1 -0
  55. package/pp-core/workflows/execute-phase/steps/vault-door-gate.md +19 -0
  56. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +1 -1
  57. package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +10 -8
  58. package/pp-core/workflows/execute-phase.md +22 -5
  59. package/pp-core/workflows/execute-plan.md +4 -0
  60. package/pp-core/workflows/fast.md +4 -0
  61. package/pp-core/workflows/help/modes/full.md +3 -2
  62. package/pp-core/workflows/init.md +2 -2
  63. package/pp-core/workflows/new-project.md +1 -1
  64. package/pp-core/workflows/plan-phase/steps/phase-tier.md +9 -0
  65. package/pp-core/workflows/plan-phase.md +7 -4
  66. package/pp-core/workflows/quick.md +4 -0
  67. package/pp-core/workflows/ship.md +5 -1
  68. package/pp-core/workflows/undo.md +1 -1
  69. package/pp-core/workflows/validate-phase.md +4 -0
  70. package/pp-core/workflows/vault-review.md +65 -0
  71. package/pp-core/workflows/verify-work.md +3 -0
  72. package/scripts/gen-capability-registry.cjs +11 -0
  73. package/scripts/lint-test-file-count.allowlist.json +10 -1
  74. package/scripts/prompt-injection-scan.sh +2 -0
@@ -7,20 +7,22 @@ card verbatim and reads one field; it never re-derives any part of the verdict.
7
7
 
8
8
  ```bash
9
9
  _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
10
- VAULT_CARD=$(pp_run check vault.status --card --point "${VAULT_GATE_POINT:-plan}")
11
- VAULT_ACTION=$(pp_run check vault.status --pick action)
10
+ VAULT_CARD=$(pp_run check vault.status --card --point "${VAULT_GATE_POINT:-plan}") || exit 1
11
+ VAULT_ACTION=$(pp_run check vault.status --pick action) || exit 1
12
+ [ -n "$VAULT_ACTION" ] || exit 1
13
+ [ -z "$VAULT_CARD" ] || printf '%s\n' "$VAULT_CARD"
14
+ echo "VAULT_ACTION=$VAULT_ACTION"
15
+ [ "$VAULT_ACTION" != stop ] || exit 1
12
16
  ```
13
17
 
14
- **If either command fails** (non-zero exit, or `VAULT_ACTION` is empty): the gate is
15
- declared `onError: halt` — STOP and report that the vault gate could not be evaluated. A pure
16
- read that crashed is never treated as a pass.
18
+ **If the fence exits non-zero** (a command failed, the action is empty, or it is `stop`): STOP —
19
+ do not plan or build, in any mode. The gate is `onError: halt`: a read that crashed is never a pass.
17
20
 
18
- **Otherwise relay `VAULT_CARD` to the user verbatim** (when non-empty), then act on
19
- `VAULT_ACTION` only:
21
+ **Otherwise relay the printed card to the user verbatim**, then act on `VAULT_ACTION` only:
20
22
 
21
23
  | `VAULT_ACTION` | What to do |
22
24
  |---|---|
23
25
  | `none` | The vault is disabled — continue silently. |
24
26
  | `proceed` | The client vault is loaded — show the card and continue. |
25
27
  | `confirm` | Degraded and `vault.required` is false. Interactive: ask for an explicit `confirmed` (`AskUserQuestion`; in TEXT_MODE — `--text` in `$ARGUMENTS` — a plain-text prompt accepting a typed `confirmed`), then continue. With `--auto` / `--chain` in `$ARGUMENTS`: log the card and continue without prompting. |
26
- | `stop` | `vault.required` is true and the client vault is not loaded. **STOP** — do not plan or build, in any mode (including `--auto` / `--chain`). The card names the remedy; the operator re-runs the command after fixing it. |
28
+ | `stop` | The fence already exited non-zero: `vault.required` is true and the client vault is not loaded. **STOP** in every mode (including `--auto` / `--chain`); the operator fixes what the card names and re-runs. |
@@ -35,6 +35,8 @@ Orchestrator coordinates, not executes. Each subagent loads the full execute-pla
35
35
  the orchestrator never receives the completion signal, treat it as successful based on spot-checks
36
36
  and continue to the next wave/plan. Never block indefinitely waiting for a signal — always verify
37
37
  via filesystem and git state.
38
+
39
+ **Executor contributions (execute:pre `into: executor`):** every spawn injects its dispatch-plan `contributions[].fragment` verbatim after `${AGENT_SKILLS}`. Every inline path (interactive, Copilot, no `Agent`, a stalled executor switched to inline) follows `pp_run loop render-hooks execute:pre --into executor --phase "${PHASE_NUMBER}" ${TIER_FLAG}` the same way.
38
40
  </runtime_compatibility>
39
41
 
40
42
  <required_reading>
@@ -348,6 +350,8 @@ Read and execute `pp-core/workflows/execute-phase/steps/client-context-gate.md`
348
350
 
349
351
  <step name="execute_pre_vault">
350
352
  Before any executor: `EXECUTE_PRE_HOOKS_JSON=$(pp_run loop render-hooks execute:pre --raw) || exit 1`. For an active `capId == "vault"` step, Read and execute `pp-core/workflows/execute-phase/steps/vault-fetch.md`; then, for an active `vault.status` gate, set `VAULT_GATE_POINT=execute` and Read and execute `pp-core/workflows/execute-phase/steps/vault-load-gate.md`. None active → skip.
353
+
354
+ Then anchor the phase for `/pp-undo --phase`: `pp_run phase manifest start "${PHASE_NUMBER}" --raw` — print its line and continue whatever it says (write-once; a refusal leaves undo on its git-log fallback).
351
355
  </step>
352
356
 
353
357
  <step name="discover_and_group_plans">
@@ -424,7 +428,7 @@ cd "$ORCHESTRATOR_WT" || { echo "FATAL: cannot cd to orchestrator worktree '$ORC
424
428
  DISPATCH_PLAN=$(pp_run query phase.dispatch-plan "${PHASE_NUMBER}" --use-worktrees "${USE_WORKTREES:-true}" ${WAVE_FILTER:+--wave "$WAVE_FILTER"} ${GAPS_ONLY:+--gaps-only} ${TIER_FLAG})
425
429
  ```
426
430
 
427
- `stages.execute.waves[]` in order, each with `spawns[]` (`kind` `serial-group`|`plan`, `plans[]` in run order with `id`, `title`, `build`, `touched_tests`; `isolation` `main-checkout`|`worktree`; `build`; `checkpoint`), `run`, `merge`, `build`, `suite_runs` (1), `tracking_commits` (1), `tracking.requirements`, `defects[]`. Report `totals` (spawns, suite runs, tracking commits). Non-empty `filters.lower_wave_incomplete` → STOP: finish the earlier waves first.
431
+ `stages.execute.waves[]` in order, each with `spawns[]` (`kind` `serial-group`|`plan`, `plans[]` in run order with `id`, `title`, `build`, `touched_tests`; `isolation` `main-checkout`|`worktree`; `build`; `checkpoint`; `contributions`), `run`, `merge`, `build`, `suite_runs` (1), `tracking_commits` (1), `tracking.requirements`, `defects[]`. Report `totals` (spawns, suite runs, tracking commits). Non-empty `filters.lower_wave_incomplete` → STOP: finish the earlier waves first.
428
432
 
429
433
  **Stream-idle-timeout prevention — checkpoint heartbeats (#2410):**
430
434
 
@@ -607,6 +611,7 @@ increases monotonically across waves. `{status}` is `complete` (success),
607
611
  </files_to_read>
608
612
 
609
613
  ${AGENT_SKILLS}
614
+ {spawn.contributions[].fragment — each verbatim, blank-line separated; nothing when empty}
610
615
 
611
616
  <mcp_tools>
612
617
  If CLAUDE.md or project instructions reference MCP tools (e.g. jCodeMunch, context7,
@@ -844,6 +849,7 @@ increases monotonically across waves. `{status}` is `complete` (success),
844
849
  else
845
850
  echo "⚠ Skipping tracking update — post-merge tests failed (exit ${TEST_EXIT}). Plans remain in-progress until tests pass."
846
851
  fi
852
+ pp_run phase manifest record "${PHASE_NUMBER}" --raw # .phase-manifest.json for /pp-undo --phase; never stops the run
847
853
  ```
848
854
 
849
855
  5.75. **Execute:wave:post capability dispatch:**
@@ -1067,10 +1073,16 @@ Selected wave finished successfully. This phase still has incomplete plans, so p
1067
1073
  **If no incomplete plans remain after the selected wave finishes:**
1068
1074
  - continue with the normal phase-level verification and completion flow below
1069
1075
  - this means the selected wave happened to be the last remaining work in the phase
1076
+
1077
+ **verify:post gates** — every run that reaches this point (a partial run stopped above, so no planned plan is still waiting):
1078
+ ```bash
1079
+ VERIFY_POST_GATES_JSON=$(pp_run loop run-gates verify:post --phase "${PHASE_NUMBER}" ${TIER_FLAG}) || exit 1
1080
+ ```
1081
+ `loop run-gates` runs every active `verify:post` gate and owns the halt: exit 1 (a `⛔` line) stops the phase here — relay it; do not verify or mark the phase complete. `⚠` advisories: relay, continue.
1070
1082
  </step>
1071
1083
 
1072
1084
  <step name="code_review_gate" required="true">
1073
- **This step is REQUIRED to evaluate the capability hook.** When the code-review capability is active, auto-invoke code review on the phase's source changes. Advisory only — never blocks execution flow. Also dispatches advisory execute:post gate hooks (e.g. tdd.review-checkpoint).
1085
+ **This step is REQUIRED to evaluate the capability hook.** When the code-review capability is active, auto-invoke code review on the phase's source changes. The review is advisory — it never blocks execution flow. Also dispatches the execute:post gate hooks: advisory ones (e.g. tdd.review-checkpoint) and the blocking `vault.trace` gate.
1074
1086
 
1075
1087
  **Capability gate:**
1076
1088
  ```bash
@@ -1101,9 +1113,13 @@ Code review found issues. Consider running:
1101
1113
 
1102
1114
  **Error handling:** If the Skill invocation fails or throws, catch the error, display "Code review encountered an error (non-blocking): {error}" and proceed to gate dispatch. Review failures must never block execution.
1103
1115
 
1104
- **Execute:post gate hook dispatch.** After code review, dispatch all active gate hooks from `EXECUTE_POST_HOOKS_JSON` where `kind == "gate"`:
1116
+ **Execute:post gate hook dispatch.** After code review, run the blocking trace gate in code — exit 1 (a `⛔` line) stops the phase here; relay it and do not proceed:
1117
+ ```bash
1118
+ TRACE_GATE_JSON=$(pp_run loop run-gates execute:post --phase "${PHASE_NUMBER}" ${TIER_FLAG} --query vault.trace) || exit 1
1119
+ ```
1120
+ Then dispatch every other active gate hook from `EXECUTE_POST_HOOKS_JSON` where `kind == "gate"` (skip `check.query == "vault.trace"`):
1105
1121
 
1106
- For each active gate hook:
1122
+ For each such gate hook:
1107
1123
  ```bash
1108
1124
  GATE_RESULT=$(pp_run check ${hook.check.query} "${PHASE_NUMBER}" --raw)
1109
1125
  CHECK_EXIT=$?
@@ -1243,7 +1259,7 @@ If `TEXT_MODE` is true, present as a plain-text numbered list and ask the user t
1243
1259
  <step name="verify_phase_goal">
1244
1260
  Verify phase achieved its GOAL, not just completed tasks.
1245
1261
 
1246
- **Tier gate:** spawn the verifier only when `DISPATCH_PLAN.stages.verify` lists `pp-verifier` (= `verifier_enabled`). Otherwise print `Verifier skipped (tier {phase_tier.tier})`, treat the phase as `passed` and go to `update_roadmap`. When it lists `verify-install` (heavy tier), first Read and execute `pp-core/workflows/execute-phase/steps/install-run.md`: the installed-product run the verifier judges on.
1262
+ **Tier gate:** spawn the verifier only when `DISPATCH_PLAN.stages.verify` lists `pp-verifier` (= `verifier_enabled`). Otherwise print `Verifier skipped (tier {phase_tier.tier})`, treat the phase as `passed` and go to `update_roadmap`. When it lists `verify-install` (heavy tier), first Read and execute `pp-core/workflows/execute-phase/steps/install-run.md`: the installed-product run the verifier judges on. When it lists `verify-vault-review` (heavy tier, vault practices on), after the verifier returns, set `FORCE=1` and Read and execute `pp-core/workflows/vault-review.md` for this phase: it reports and never changes the route.
1247
1263
 
1248
1264
  ```bash
1249
1265
  VERIFIER_SKILLS=$(pp_run query agent-skills pp-verifier)
@@ -1416,6 +1432,7 @@ These items are tracked and will appear in `/pp:progress` and `/pp:audit-uat`.
1416
1432
 
1417
1433
  ```bash
1418
1434
  pp_run query commit "docs(phase-{X}): complete phase execution" --files .planning/ROADMAP.md .planning/STATE.md .planning/REQUIREMENTS.md {phase_dir}/*-VERIFICATION.md
1435
+ pp_run phase manifest record "${PHASE_NUMBER}" --raw # the completion commit joins the /pp-undo --phase list
1419
1436
  ```
1420
1437
  </step>
1421
1438
 
@@ -57,6 +57,10 @@ Extract from init JSON: `executor_model`, `commit_docs`, `sub_repos`, `phase_dir
57
57
  If `.planning/` missing: error.
58
58
  </step>
59
59
 
60
+ <step name="vault_door_gate">
61
+ Skipped under the `<dispatch>` tag: execute-phase gated execute:pre before spawning. Otherwise, before any task runs, Read and execute `pp-core/workflows/execute-phase/steps/vault-door-gate.md`; a stop ends this run with nothing executed.
62
+ </step>
63
+
60
64
  <step name="identify_plan">
61
65
  ```bash
62
66
  # Use plans/summaries from INIT JSON, or list files
@@ -41,6 +41,10 @@ This looks like it needs planning. Use /pp:quick instead:
41
41
  And stop.
42
42
  </step>
43
43
 
44
+ <step name="vault_door_gate">
45
+ Before any edit, Read and execute `pp-core/workflows/execute-phase/steps/vault-door-gate.md`. A stop ends this task: nothing is edited or committed.
46
+ </step>
47
+
44
48
  <step name="execute_inline">
45
49
  Do the work directly:
46
50
 
@@ -263,7 +263,7 @@ Modes:
263
263
  - **default** — progress report + intelligent routing
264
264
  - **`--next`** — auto-advance to the next logical step (use `--next --force` to bypass safety gates)
265
265
  - **`--next --auto`** — like `--next`, but chains steps automatically until milestone completion or a blocking decision
266
- - **`--next --converge`** — when the next action is planning, route it through `/pp:plan-review-convergence` instead of `/pp:plan-phase`; requires `workflow.plan_review_convergence=true`. `--cross-ai` is an alias. Reviewer flags (`--codex`, `--gemini`, `--claude`, `--opencode`, `--ollama`, `--lm-studio`, `--llama-cpp`, `--all`) and `--max-cycles N` forward to the convergence loop.
266
+ - **`--next --converge`** — when the next action is planning, route it through `/pp:plan-review-convergence` instead of `/pp:plan-phase`; requires `workflow.plan_review_convergence=true`. `--cross-ai` is an alias. Reviewer flags and `--max-cycles N` pass through to it.
267
267
  - **`--forensic`** — append a 6-check integrity audit after the progress report
268
268
  - **`--do "<text>"`** — smart router: dispatch freeform intent to the matching `/pp-*` command (see *Smart Router* above)
269
269
 
@@ -592,7 +592,7 @@ The commands above cover the most common day-to-day flows. Every command listed
592
592
 
593
593
  - **`/pp:mvp-phase <phase-number>`** — Plan a phase as a vertical MVP slice (user story + SPIDR splitting) before handing off to plan-phase. Same end-state as `/pp:plan-phase --mvp`, with a guided MVP-shaping intro.
594
594
  - **`/pp:ultraplan-phase [phase]`** — [BETA] Offload plan phase to Claude Code's ultraplan cloud; review in browser and import back.
595
- - **`/pp:plan-review-convergence <phase> [--codex] [--gemini] [--claude] [--opencode] [--ollama] [--lm-studio] [--llama-cpp] [--all] [--text] [--ws <name>] [--max-cycles N]`** — Cross-AI plan convergence loop — replan with review feedback until no HIGH concerns remain. Supports both cloud reviewers (Codex/Gemini/Claude/OpenCode) and local model runtimes (Ollama, LM Studio, llama.cpp).
595
+ - **`/pp:plan-review-convergence <phase> [--codex] [--gemini] [--claude] [--opencode] [--ollama] [--lm-studio] [--llama-cpp] [--all] [--text] [--ws <name>] [--max-cycles N]`** — Cross-AI plan convergence loop — replan with review feedback until no HIGH concerns remain.
596
596
  - **`/pp:autonomous [--from N] [--to N] [--only N] [--supervised] [--interactive] [--converge]`** — Run the next incomplete phase autonomously (discuss → plan → execute), best-effort with decisions recorded; `--from`/`--to` for a range, `--supervised` restores prompts. `--converge` routes planning through plan-review convergence; `--cross-ai` is an alias.
597
597
 
598
598
  ### Quality, Review & Verification
@@ -602,6 +602,7 @@ The commands above cover the most common day-to-day flows. Every command listed
602
602
  - **`/pp:validate-phase [phase]`** — Retroactively audit and fill Nyquist validation gaps for a completed phase.
603
603
  - **`/pp:ui-review [phase]`** — Retroactive 6-pillar visual audit of implemented frontend code.
604
604
  - **`/pp:eval-review [phase]`** — Audit an executed AI phase's evaluation coverage and produce an EVAL-REVIEW.md remediation plan.
605
+ - **`/pp:vault-review [phase]`** — Retroactive per-practice client-vault review of a phase; counts by verdict, never gates.
605
606
  - **`/pp:audit-fix --source <audit-uat> [--severity medium|high|all] [--max N] [--dry-run]`** — Autonomous audit-to-fix pipeline: find issues, classify, fix, test, commit.
606
607
  - **`/pp:add-tests <phase> [additional instructions]`** — Generate tests for a completed phase based on UAT criteria and implementation.
607
608
 
@@ -174,8 +174,8 @@ pp_run query init-contract --arm-vault
174
174
  pp_run vault probe
175
175
  ```
176
176
 
177
- Show both outputs verbatim. `--arm-vault` sets `vault.enabled: true` only where the key is
178
- absent (an explicit `false` is kept); `ok:false` means no client vault is bound — re-run
177
+ Show both outputs verbatim. `--arm-vault` sets `vault.enabled: true`, and then `vault.required:
178
+ true` and `vault.practices_enabled: true`, each only where the key is absent (an explicit value is kept); `ok:false` means no client vault is bound — re-run
179
179
  `pp-tools init`. The probe line says whether the client vault is reachable at its pinned ref;
180
180
  it prints nothing when the vault is disabled. Neither ever blocks this step.
181
181
 
@@ -107,7 +107,7 @@ hard-block initiation on an unreachable remote. For non-Pareto repos the block i
107
107
  a no-op and this step is skipped entirely.
108
108
 
109
109
  When the bindings file is present, also run `pp_run query init-contract --arm-vault`
110
- (sets `vault.enabled: true` only where absent; `ok:false` = no client vault bound — run
110
+ (sets `vault.enabled` and `vault.required` to true, each only where absent; `ok:false` = no client vault bound — run
111
111
  `/pp-init`) and `pp_run vault probe` (one reachability line; empty when the vault is
112
112
  disabled). Show both outputs verbatim; neither blocks.
113
113
 
@@ -25,6 +25,15 @@ Spawn nothing the stage does not list. The explicit flags still apply on top: `-
25
25
 
26
26
  ## After planning (§9, `## PLANNING COMPLETE`)
27
27
 
28
+ Threat-model presence gate first (active only when the tier resolves `security_enforcement` on, i.e. the step 5.55 banner showed):
29
+
30
+ ```bash
31
+ TM_BLOCK=$(pp_run check threat-model-presence "$PHASE" $TIER_FLAG --pick block) || exit 1
32
+ [ "$TM_BLOCK" != "true" ] || exit 1 # stderr names each new plan without <threat_model>
33
+ ```
34
+
35
+ If it stops: do not go on to step 10 or 13; re-spawn the planner (step 8) requiring the block in each plan named, then return here.
36
+
28
37
  Plans now exist, so re-propose from their `files_modified`:
29
38
 
30
39
  ```bash
@@ -686,6 +686,7 @@ Output this markdown directly (not as a code block):
686
686
  ───────────────────────────────────────────────
687
687
  Also available:
688
688
  - `/pp:plan-phase {N} --skip-ui ${GSD_WS}` — plan without UI-SPEC (not recommended for frontend phases)
689
+ - Not a UI phase? Matched: {GATE.tokens}. Add `**UI:** none` on its own line in the ROADMAP phase section.
689
690
  ```
690
691
 
691
692
  **Exit the plan-phase workflow. Do not continue.**
@@ -815,6 +816,7 @@ Continue to step 8.
815
816
  Run this Bash command to emit the branded Plan stage banner (renders on stderr, auto-degrades on non-TTY — do NOT print an inline ASCII banner, that would double-print):
816
817
  ```bash
817
818
  pp_run banner plan "PLANNING PHASE {X}"
819
+ touch "${PHASE_DIR}/.planner-run-mark" # the threat-model gate examines only newer plans
818
820
  ```
819
821
 
820
822
  Then announce the spawn:
@@ -1325,7 +1327,7 @@ After plans pass the checker (or checker is skipped), verify that all phase requ
1325
1327
  **Step 1: Extract requirement IDs claimed by plans**
1326
1328
  ```bash
1327
1329
  # Collect all requirement IDs from plan frontmatter
1328
- PLAN_REQS=$(grep -h "requirements_addressed\|requirements:" ${PHASE_DIR}/*-PLAN.md 2>/dev/null | tr -d '[]' | tr ',' '\n' | sed 's/^[[:space:]]*//' | sort -u)
1330
+ PLAN_REQS=$(grep -h "^requirements_addressed\|^requirements:" ${PHASE_DIR}/*-PLAN.md 2>/dev/null | tr -d '[]' | tr ',' '\n' | sed 's/^[[:space:]]*//' | sort -u)
1329
1331
  ```
1330
1332
 
1331
1333
  **Step 2: Compare against phase requirements from ROADMAP**
@@ -1476,13 +1478,14 @@ Proactive, non-blocking coverage report gated on `workflow.post_planning_gaps`
1476
1478
 
1477
1479
  ```bash
1478
1480
  PLAN_POST_HOOKS_JSON=$(pp_run loop render-hooks plan:post --raw --phase "$PHASE" $TIER_FLAG) || exit 1
1481
+ TRACE_GATE_JSON=$(pp_run loop run-gates plan:post --phase "$PHASE" $TIER_FLAG --query vault.trace) || exit 1
1479
1482
  PHASE_REQ_IDS=$(pp_run query init.plan-phase "$PHASE" --pick phase_req_ids 2>/dev/null || echo TBD)
1480
1483
  ```
1481
1484
 
1482
- Read the `activeHooks` array from `PLAN_POST_HOOKS_JSON` in-context. If the
1483
- `gap-analysis` gate hook is absent (capability inactive), skip this step.
1485
+ The `run-gates` line runs the blocking `vault.trace` gate (vault practices on) and owns its halt: exit 1 (a `⛔` line) stops planning here — relay it verbatim and do not present the plans as ready. Read the `activeHooks` array from `PLAN_POST_HOOKS_JSON` in-context. If the
1486
+ `gap-analysis` gate hook is absent (capability inactive), skip the rest of this step.
1484
1487
 
1485
- **For each active entry where `kind == "gate"`** (process in array order):
1488
+ **For each active entry where `kind == "gate"`** except `check.query == "vault.trace"` (run above), in array order:
1486
1489
 
1487
1490
  ```bash
1488
1491
  GATE_RESULT=$(pp_run check ${hook.check.query} "${PHASE_DIR}" "${PHASE_REQ_IDS}" --raw)
@@ -176,6 +176,10 @@ Quick tasks can run mid-phase - validation only checks ROADMAP.md exists, not ph
176
176
 
177
177
  ---
178
178
 
179
+ **Step 2.4: Vault door gate** — Step 2.5 is the first write. Read and execute `pp-core/workflows/execute-phase/steps/vault-door-gate.md` now; a stop ends the quick task before anything is written.
180
+
181
+ ---
182
+
179
183
  **Step 2.5: Handle quick-task branching**
180
184
 
181
185
  **If `branch_name` is empty/null:** Skip and continue on the current branch.
@@ -91,9 +91,13 @@ Verify the work is ready to ship:
91
91
 
92
92
  ```bash
93
93
  SHIP_PRE_HOOKS_JSON=$(pp_run loop render-hooks ship:pre --raw --phase "${phase_number}") || exit 1
94
+ TRACE_GATE_JSON=$(pp_run loop run-gates ship:pre --phase "${phase_number}" --query vault.trace) || exit 1
95
+ AUDIT_GATE_JSON=$(pp_run loop run-gates ship:pre --phase "${phase_number}" --query vault.audit) || exit 1
94
96
  SECURITY_FILE=$(ls "${PHASE_DIR}"/*-SECURITY.md 2>/dev/null | head -1)
95
97
  ```
96
98
 
99
+ The `run-gates` lines run the blocking `vault.trace` and `vault.audit` gates and own their halt: exit 1 (a `⛔` line) stops the ship — relay it verbatim; do not push or open the PR.
100
+
97
101
  Read the `activeHooks` array from `SHIP_PRE_HOOKS_JSON` in-context (do NOT pipe it through a shell parser).
98
102
 
99
103
  If an active entry exists with `kind == "gate"`, `capId == "security"`, and `blocking == true`, enforce its predicate (`SECURITY.md` frontmatter `threats_open == 0`) before shipping:
@@ -113,7 +117,7 @@ Verify the work is ready to ship:
113
117
 
114
118
  **Generic `check.query` gate enforcement (capability-driven).**
115
119
 
116
- For each active entry in `activeHooks` where `kind == "gate"` and a `check.query` is present (covers any capability-registered query gate, e.g. the `pareto-stack` platform-drift gates — skip entries already handled by the `security` predicate branch above):
120
+ For each active entry in `activeHooks` where `kind == "gate"` and a `check.query` is present (covers any capability-registered query gate, e.g. the `pareto-stack` platform-drift gates — skip entries already handled by the `security` predicate branch above, and `vault.trace` and `vault.audit`, run above):
117
121
 
118
122
  ```bash
119
123
  GATE_RESULT=$(pp_run check ${hook.check.query} --raw)
@@ -75,7 +75,7 @@ Parse the user's selection into COMMITS list.
75
75
 
76
76
  **MODE=phase:**
77
77
 
78
- Read `.planning/.phase-manifest.json` if it exists.
78
+ Read `.planning/.phase-manifest.json` if it exists (execute-phase writes it with `pp_run phase manifest start|record`).
79
79
 
80
80
  If the file exists and `manifest.phases?.[TARGET_PHASE]?.commits` is a non-empty array:
81
81
  - Use `manifest.phases[TARGET_PHASE].commits` entries as COMMITS (each entry is a commit hash)
@@ -97,6 +97,10 @@ Call AskUserQuestion with gap table and options:
97
97
  2. "Skip — mark manual-only" → add to Manual-Only, Step 6
98
98
  3. "Cancel" → exit
99
99
 
100
+ ## 4b. Vault door gate
101
+
102
+ Before the auditor writes tests, Read and execute `pp-core/workflows/execute-phase/steps/vault-door-gate.md`; a stop ends validation with no test written.
103
+
100
104
  ## 5. Spawn pp-nyquist-auditor
101
105
 
102
106
  Print: `◆ Spawning nyquist auditor... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
@@ -0,0 +1,65 @@
1
+ <purpose>
2
+ Retroactive client-vault practice review of a built phase: one agent-judged verdict (PASS / FAIL /
3
+ N-A / INCONCLUSIVE) per practice the vault audit cannot check, in `{padded}-VAULT-REVIEW.md`. It
4
+ reports and never gates: no verdict changes a status, a score, a route or a ship.
5
+ </purpose>
6
+
7
+ <available_agent_types>
8
+ Valid pp-core subagent types (use exact names — do not fall back to 'general-purpose'):
9
+ - pp-vault-reviewer — judges the practices the CLI could not settle
10
+ </available_agent_types>
11
+
12
+ <process>
13
+
14
+ ## 1. Scaffold
15
+
16
+ ```bash
17
+ _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
18
+ INIT=$(pp_run query init.phase-op "${PHASE_ARG}")
19
+ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
20
+ ```
21
+
22
+ Parse `phase_dir`, `phase_number`, `phase_name`, `padded_phase`, `text_mode`. A phase with no
23
+ `*-SUMMARY.md` is not built: say "Phase {N} not executed — run /pp-execute-phase {N} first." and stop.
24
+
25
+ ```bash
26
+ SCAFFOLD=$(pp_run vault review scaffold "${PHASE_DIR}" ${FORCE:+--force} --raw)
27
+ ```
28
+
29
+ Branch on its `status`:
30
+ - `inert`, `usage`, `no-argument`, `unresolved` — print its `statement` and stop; nothing is written.
31
+ - `exists` — ask "Re-review" (set `FORCE=1`, scaffold again) or "View" (show the file, stop), with
32
+ AskUserQuestion, or as a numbered list when `--text` is in `$ARGUMENTS` or `text_mode` is true.
33
+ - `written` — continue. With `toJudge` 0 there is nothing to judge: go to step 3.
34
+
35
+ ## 2. Judge
36
+
37
+ ```
38
+ ◆ Spawning vault reviewer... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
39
+ ```
40
+
41
+ ```
42
+ Agent(
43
+ subagent_type="pp-vault-reviewer",
44
+ model="{pp_run query resolve-model pp-vault-reviewer --raw}",
45
+ description="Vault practice review Phase {phase_number}",
46
+ prompt="Fill the TODO rows of {phase_dir}/{padded_phase}-VAULT-REVIEW.md (Phase {phase_number}: {phase_name})."
47
+ )
48
+ ```
49
+
50
+ ## 3. Finalize
51
+
52
+ ```bash
53
+ pp_run vault review finalize "${PHASE_DIR}"
54
+ ```
55
+
56
+ Print its output. If it lists repair items, send them to the reviewer once ("Fix these repair items
57
+ in {file}: …") and finalize again. Never clear an item by editing a computed line or a derived row.
58
+
59
+ Route the same way whatever the verdicts: `/pp-verify-work {N}` or `/pp-plan-phase {N+1}`. Commit:
60
+
61
+ ```bash
62
+ pp_run query commit "docs(${PADDED_PHASE}): vault practice review" --files "${PHASE_DIR}/${PADDED_PHASE}-VAULT-REVIEW.md"
63
+ ```
64
+
65
+ </process>
@@ -461,9 +461,12 @@ Present summary:
461
461
 
462
462
  ```bash
463
463
  VERIFY_POST_HOOKS_JSON=$(pp_run loop render-hooks verify:post --raw --phase "${phase_number}" ${GSD_WS} ${TIER_FLAG}) || exit 1
464
+ VERIFY_POST_GATES_JSON=$(pp_run loop run-gates verify:post --phase "${phase_number}" ${GSD_WS} ${TIER_FLAG}) || exit 1
464
465
  SECURITY_FILE=$(ls "${PHASE_DIR}"/*-SECURITY.md 2>/dev/null | head -1)
465
466
  ```
466
467
 
468
+ `loop run-gates` runs every active `verify:post` gate and owns the halt: exit 1 (a `⛔` line) means a blocking gate blocked — stop, relay it, do not mark the phase complete. `⚠` lines are advisories: relay and continue.
469
+
467
470
  Resolve active step hooks from `VERIFY_POST_HOOKS_JSON` where `kind == "step"` and `ref.skill == "secure-phase"`.
468
471
 
469
472
  If an active secure-phase step hook exists AND `SECURITY_FILE` is empty, dispatch the registry-provided skill stem:
@@ -1204,6 +1204,17 @@ function validateGate(gate, prefix) {
1204
1204
  if (count !== 1) {
1205
1205
  errors.push(prefix + '.check must have exactly one of: query, predicate, agentVerdict');
1206
1206
  }
1207
+ // check.args: literal tokens `loop run-gates` appends after the phase — query gates only,
1208
+ // at most 8, each a flag or a plain word (the runner refuses anything else at run time too).
1209
+ if (Object.prototype.hasOwnProperty.call(gate.check, 'args')) {
1210
+ const a = gate.check.args;
1211
+ if (!hasQuery) {
1212
+ errors.push(prefix + '.check.args is only valid beside check.query');
1213
+ } else if (!Array.isArray(a) || a.length > 8
1214
+ || !a.every((t) => typeof t === 'string' && /^(?:--[a-z][a-z0-9-]*|[a-z0-9][a-z0-9._-]*)$/i.test(t))) {
1215
+ errors.push(prefix + '.check.args must be an array of at most 8 flags (--name) or plain words');
1216
+ }
1217
+ }
1207
1218
  // agentVerdict forces blocking: false (advisory only)
1208
1219
  if (hasAgentVerdict && gate.blocking === true) {
1209
1220
  errors.push(
@@ -187,13 +187,22 @@
187
187
  ],
188
188
  "issue": "v1.7.0 Client Vault Load — four distinct concerns: vault.test (fetch/status/summary unit), vault-security (VSEC-02/03 write-guard + secret/fence), vault-read-path (VGATE-03 SUMMARY pointer), vault-status-check (VGATE-02 gate-query handler). Phase 24-02 added vault-practices.property.test.cjs (fast-check fuzz of the practices glob-fallback compiler + a practices-frontmatter round-trip, PRAC-02) — it is grouped under this bucket rather than \"vault-practices\" because the test-file-count lint's hyphen-prefix matcher treats \"vault-practices.property\" (period before \"property\") as starting with \"vault-\" before it can match the more specific \"vault-practices-\" prefix; the module it actually tests is pp-core/bin/lib/vault-practices.cjs, not vault.cjs."
189
189
  },
190
+ "vault-review": {
191
+ "files": [
192
+ "vault-review.integration.test.cjs",
193
+ "vault-review.security.test.cjs",
194
+ "vault-review.test.cjs"
195
+ ],
196
+ "issue": "Agent practice review (vault assurance R5) — three distinct concerns for pp-core/bin/lib/vault-review.cjs: vault-review.test (the pure derivations — vocabularies, both derived-N-A arms, the counts, the range, the clean-render refusal, the scaffold renderer and the line stamper — plus the require-graph barrier), vault-review.integration.test (the seams: the artifact validator and the check vault-review / vault review scaffold|finalize arms driven through the real pp-tools.cjs, where the exit-0 / no-block-key contract is proved), and vault-review.security.test (adversarial practice bodies: a spoofed fence marker, directive-shaped text, a client table inside the fence, an option-shaped base branch). The .security marker is what identifies the adversarial lane to scripts/run-tests.cjs; consolidating would blur three different questions into one file."
197
+ },
190
198
  "vault-practices": {
191
199
  "files": [
192
200
  "vault-practices-examples.test.cjs",
193
201
  "vault-practices-matcher.test.cjs",
202
+ "vault-practices.slow.test.cjs",
194
203
  "vault-practices.test.cjs"
195
204
  ],
196
- "issue": "Phase 24 (24-01/24-02/24-04) — three distinct concerns for pp-core/bin/lib/vault-practices.cjs: vault-practices.test (24-01 parser/validation/discovery unit tests), vault-practices-matcher.test (24-02 glob matcher + two-bucket result + report unit tests), vault-practices-examples.test (24-04 shipped-example drift guard + minimal-comments global-bucket smoke case, PRAC-03 Success Criterion 3). All three test the same production module and are each independently load-bearing; consolidating would blur distinct plan-level verification gates."
205
+ "issue": "Phase 24 (24-01/24-02/24-04) — three distinct concerns for pp-core/bin/lib/vault-practices.cjs: vault-practices.test (24-01 parser/validation/discovery unit tests), vault-practices-matcher.test (24-02 glob matcher + two-bucket result + report unit tests), vault-practices-examples.test (24-04 shipped-example drift guard + minimal-comments global-bucket smoke case, PRAC-03 Success Criterion 3). All three test the same production module and are each independently load-bearing; consolidating would blur distinct plan-level verification gates. The practice-file contract extensions added vault-practices.slow.test.cjs — the >5 s worst-case matcher corpus behind the matching cost bound, split out per the .slow suffix convention so the default suite stays fast."
197
206
  }
198
207
  }
199
208
  }
@@ -89,6 +89,8 @@ ALLOWLIST=(
89
89
  'tests/security.test.cjs::the adversarial suite for the library sanitisers, whose fixtures are real override and role-manipulation strings asserted to be neutralised; measured 54 pattern hits at HEAD on 2026-10-11'
90
90
  'tests/prompt-injection-scan.security.test.cjs::drives scanForInjection in the compiled security library with crafted payloads, and carries a second allowlist of its own for that separate scanner; measured 11 pattern hits at HEAD on 2026-10-11'
91
91
  'tests/vault-security.test.cjs::the vault fencing suite plants an instruction-override heading in a fixture vault file and asserts SUMMARY.md fences and defangs it rather than passing it to the agent as instructions; measured 1 pattern hit at HEAD on 2026-10-11'
92
+ 'tests/vault-practices-matcher.test.cjs::the vault practices matcher suite plants an instruction-override line inside a fixture practice body and a spoofed fence end, and asserts the matcher keeps both inside the untrusted fenced region, so the fixtures must carry a real injection phrase'
93
+ 'tests/vault-review.security.test.cjs::the adversarial suite of the agent practice review seeds a practice body with directive-shaped text aimed at the practice judge (the DIRECTIVE_TEXT constant) and asserts the rendered review entry carries it only between the fence markers, after the reference-data preamble, so the judge reads it as data; the text must be a literal for that assertion to mean anything; measured 1 pattern hit at HEAD on 2026-10-12'
92
94
  'tests/verify.test.cjs::the verification suite plants an instruction-override line in a phase artifact and asserts it is rejected, so the line is present as a negative fixture; measured 1 pattern hit at HEAD on 2026-10-11'
93
95
  'pp-core/bin/lib/security.cjs::gitignored build output compiled from src/security.cts per .gitignore line 117, so it is unreachable in diff mode and matters only to a local dir-mode run; it carries the library injection patterns inline; measured 8 pattern hits at HEAD on 2026-10-11'
94
96
  'tests/read-injection-scanner.security.test.cjs::exercises the read-time injection scanner hook with the payloads a hostile file would carry, each asserted to be blocked rather than obeyed; measured 14 pattern hits at HEAD on 2026-10-11'