@pareto4/pp-core 1.13.0 → 1.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +3 -3
- package/README.md +26 -9
- package/agents/pp-code-reviewer.md +2 -1
- package/agents/pp-debug-session-manager.md +9 -6
- package/agents/pp-executor.md +10 -13
- package/agents/pp-plan-checker.md +4 -4
- package/agents/pp-planner.md +23 -9
- package/agents/pp-vault-reviewer.md +244 -21
- package/agents/pp-verifier.md +7 -3
- package/bin/install.js +10 -3
- package/commands/pp-execute-phase.md +1 -3
- package/commands/pp-ns-review.md +1 -1
- package/commands/pp-plan-phase.md +1 -2
- package/commands/pp-vault-review.md +5 -3
- package/commands/pp-verify-work.md +1 -2
- package/gemini-extension.json +1 -1
- package/hooks/dist/pp-check-update.js +0 -0
- package/hooks/dist/pp-cursor-session-start.js +1 -1
- package/hooks/dist/pp-statusline.js +1 -1
- package/hooks/dist/pp-update-banner.js +0 -0
- package/hooks/pp-check-update.js +0 -0
- package/hooks/pp-cursor-session-start.js +1 -1
- package/hooks/pp-statusline.js +1 -1
- package/hooks/pp-update-banner.js +0 -0
- package/package.json +14 -19
- package/pareto-skills/_shared/create-project-board.sh +0 -0
- package/pareto-skills/pp-project-setup/SKILL.md +1 -1
- package/pp-core/bin/check-latest-version.cjs +0 -0
- package/pp-core/bin/detect-build-commands.sh +2 -2
- package/pp-core/bin/lib/capability-activation.cjs +83 -9
- package/pp-core/bin/lib/capability-registry.cjs +396 -82
- package/pp-core/bin/lib/capability-state.cjs +160 -4
- package/pp-core/bin/lib/capability-writer.cjs +19 -5
- package/pp-core/bin/lib/check-command-router.cjs +3334 -946
- package/pp-core/bin/lib/clusters.cjs +3 -0
- package/pp-core/bin/lib/command-aliases.cjs +0 -35
- package/pp-core/bin/lib/commands.cjs +9 -4
- package/pp-core/bin/lib/compose-bindings.cjs +251 -88
- package/pp-core/bin/lib/config-loader.cjs +7 -103
- package/pp-core/bin/lib/config-schema.cjs +1 -1
- package/pp-core/bin/lib/config.cjs +154 -62
- package/pp-core/bin/lib/decisions.cjs +4 -4
- package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
- package/pp-core/bin/lib/frontmatter.cjs +1 -38
- package/pp-core/bin/lib/git-base-branch.cjs +13 -11
- package/pp-core/bin/lib/git-provider.cjs +4 -4
- package/pp-core/bin/lib/init-command-router.cjs +5 -8
- package/pp-core/bin/lib/init-contract.cjs +648 -738
- package/pp-core/bin/lib/init-interactive.cjs +76 -4
- package/pp-core/bin/lib/init.cjs +24 -51
- package/pp-core/bin/lib/install-profiles.cjs +1 -1
- package/pp-core/bin/lib/io.cjs +8 -0
- package/pp-core/bin/lib/loop-resolver.cjs +189 -129
- package/pp-core/bin/lib/org-profile.cjs +469 -152
- package/pp-core/bin/lib/package-identity.cjs +3 -3
- package/pp-core/bin/lib/phase-command-router.cjs +1 -29
- package/pp-core/bin/lib/phase-scope.cjs +1 -1
- package/pp-core/bin/lib/phase.cjs +11 -253
- package/pp-core/bin/lib/platform-bindings.cjs +40 -19
- package/pp-core/bin/lib/practices-review-check.cjs +15 -9
- package/pp-core/bin/lib/profile-output.cjs +1 -1
- package/pp-core/bin/lib/shell-command-projection.cjs +104 -1
- package/pp-core/bin/lib/state.cjs +11 -85
- package/pp-core/bin/lib/template.cjs +1 -1
- package/pp-core/bin/lib/vault-audit.cjs +650 -126
- package/pp-core/bin/lib/vault-command-router.cjs +2604 -367
- package/pp-core/bin/lib/vault-coordinates.cjs +13 -4
- package/pp-core/bin/lib/vault-index.cjs +699 -0
- package/pp-core/bin/lib/vault-mode.cjs +235 -0
- package/pp-core/bin/lib/vault-practices.cjs +139 -72
- package/pp-core/bin/lib/vault-review.cjs +194 -451
- package/pp-core/bin/lib/vault-seal.cjs +1936 -0
- package/pp-core/bin/lib/vault-standards.cjs +1505 -0
- package/pp-core/bin/lib/vault-trace.cjs +607 -254
- package/pp-core/bin/lib/vault-update.cjs +9 -33
- package/pp-core/bin/lib/vault-write-back.cjs +70 -42
- package/pp-core/bin/lib/vault.cjs +2734 -920
- package/pp-core/bin/lib/verify.cjs +0 -11
- package/pp-core/bin/portable-timeout.sh +1 -1
- package/pp-core/bin/pp-tools.cjs +184 -54
- package/pp-core/bin/shared/config-defaults.manifest.json +0 -7
- package/pp-core/bin/shared/config-schema.manifest.json +0 -3
- package/pp-core/bin/shared/model-catalog.json +2 -2
- package/pp-core/bin/verify-reapply-patches.cjs +0 -0
- package/pp-core/org-profile.json +26 -0
- package/pp-core/references/autonomous-best-effort.md +8 -7
- package/pp-core/references/loop-hook-dispatch.md +0 -25
- package/pp-core/references/planner-gap-closure.md +0 -1
- package/pp-core/references/planner-tdd.md +0 -1
- package/pp-core/references/planner-vault-practices.md +17 -34
- package/pp-core/references/planner-vault-standards.md +320 -0
- package/pp-core/references/planning-config.md +0 -8
- package/pp-core/references/practice-arithmetic-contract.md +121 -0
- package/pp-core/references/tdd.md +0 -1
- package/pp-core/references/verifier-practice-verdicts.md +263 -0
- package/pp-core/templates/README.md +1 -0
- package/pp-core/templates/VAULT-REVIEW.md +202 -0
- package/pp-core/templates/phase-prompt.md +2 -6
- package/pp-core/templates/practices/README.md +20 -23
- package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
- package/pp-core/workflows/add-tests.md +55 -4
- package/pp-core/workflows/audit-fix.md +63 -2
- package/pp-core/workflows/audit-milestone.md +1 -1
- package/pp-core/workflows/autonomous.md +85 -11
- package/pp-core/workflows/check-todos.md +13 -2
- package/pp-core/workflows/code-review-fix.md +59 -3
- package/pp-core/workflows/code-review.md +10 -1
- package/pp-core/workflows/complete-milestone.md +1 -1
- package/pp-core/workflows/debug.md +54 -4
- package/pp-core/workflows/discovery-sprint/prefill.md +19 -7
- package/pp-core/workflows/discovery-sprint/prototype.md +46 -49
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +2 -0
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +20 -24
- package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/06-security.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +1 -6
- package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +1 -3
- package/pp-core/workflows/discovery-sprint/templates/context-template.md +1 -1
- package/pp-core/workflows/discovery-sprint/vault-context.md +213 -0
- package/pp-core/workflows/discovery-sprint.md +7 -3
- package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +4 -4
- package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +43 -1
- package/pp-core/workflows/discovery-to-build.md +10 -2
- package/pp-core/workflows/discuss-phase.md +3 -10
- package/pp-core/workflows/execute-phase/steps/check-query-gate.md +329 -0
- package/pp-core/workflows/execute-phase/steps/client-context-gate.md +3 -2
- package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -1
- package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +98 -0
- package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
- package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +6 -8
- package/pp-core/workflows/execute-phase/steps/vault-fetch.md +115 -14
- package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +322 -20
- package/pp-core/workflows/execute-phase/steps/verifier-practice-payload.md +165 -0
- package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +163 -0
- package/pp-core/workflows/execute-phase.md +155 -128
- package/pp-core/workflows/execute-plan.md +56 -6
- package/pp-core/workflows/fast.md +53 -4
- package/pp-core/workflows/help/modes/full.md +3 -3
- package/pp-core/workflows/init/steps/client-context-capture.md +101 -0
- package/pp-core/workflows/init/steps/gitlab-provisioning.md +130 -0
- package/pp-core/workflows/init/steps/project-md-deferral-narration.md +66 -0
- package/pp-core/workflows/init/steps/reachability-outcome.md +70 -0
- package/pp-core/workflows/init.md +232 -272
- package/pp-core/workflows/new-project.md +31 -23
- package/pp-core/workflows/plan-phase.md +98 -45
- package/pp-core/workflows/quick.md +117 -3
- package/pp-core/workflows/secure-phase.md +1 -1
- package/pp-core/workflows/ship.md +57 -28
- package/pp-core/workflows/undo.md +1 -1
- package/pp-core/workflows/update.md +3 -3
- package/pp-core/workflows/validate-phase.md +58 -3
- package/pp-core/workflows/vault-review.md +282 -22
- package/pp-core/workflows/verify-work.md +12 -14
- package/scripts/changeset/cli.cjs +6 -5
- package/scripts/changeset/lint.cjs +0 -0
- package/scripts/changeset/new.cjs +33 -50
- package/scripts/dev-env.cjs +25 -85
- package/scripts/gen-capability-registry.cjs +199 -11
- package/scripts/gen-practice-arithmetic-contract.cjs +412 -0
- package/scripts/lib/allowlist-ratchet.cjs +1 -103
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
- package/scripts/lint-docs-required.cjs +0 -0
- package/scripts/lint-pr-check-project-dir.cjs +1 -3
- package/scripts/lint-test-file-count.allowlist.json +12 -10
- package/scripts/practice-arithmetic-contract.allow.json +35 -0
- package/scripts/prompt-injection-scan.sh +22 -17
- package/scripts/record-vault-mode-golden.cjs +89 -0
- package/scripts/release-channel.cjs +0 -0
- package/scripts/sync-manifest-versions.cjs +5 -4
- package/scripts/sync-rulesets.sh +1 -1
- package/scripts/update-size-baseline.cjs +30 -274
- package/scripts/vault-calibrate.cjs +996 -0
- package/scripts/verify-opengsd-sweep.cjs +3 -5
- package/scripts/workflow-size.cjs +9 -61
- package/pp-core/bin/lib/dispatch-plan.cjs +0 -483
- package/pp-core/bin/lib/install-run.cjs +0 -536
- package/pp-core/bin/lib/loop-gates.cjs +0 -213
- package/pp-core/bin/lib/phase-manifest.cjs +0 -527
- package/pp-core/bin/lib/phase-tier.cjs +0 -480
- package/pp-core/bin/lib/plan-graph.cjs +0 -368
- package/pp-core/references/executor-vault-trace.md +0 -47
- package/pp-core/references/planner-plan-graph.md +0 -66
- package/pp-core/workflows/discovery-sprint/vault-fetch.md +0 -37
- package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +0 -45
- package/pp-core/workflows/execute-phase/steps/install-run.md +0 -29
- package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +0 -23
- package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +0 -73
- package/pp-core/workflows/execute-phase/steps/vault-door-gate.md +0 -19
- package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -43
- package/scripts/lib/size-budget.cjs +0 -311
- package/scripts/lint-neutrality-names.cjs +0 -694
- package/scripts/lint-org-coordinates.allowlist.json +0 -35
- package/scripts/lint-org-coordinates.cjs +0 -426
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# Serial-group dispatch — execute-phase step 3 (`kind: serial-group` spawns)
|
|
2
|
-
|
|
3
|
-
A serial group is a set of plans in one wave that share only non-source files (a test file, a size
|
|
4
|
-
baseline, a changeset, a doc). `phase.dispatch-plan` gives the group ONE spawn with
|
|
5
|
-
`isolation: main-checkout` and its `plans[]` in run order. Spawn exactly one executor for it — never
|
|
6
|
-
one per plan, never in a worktree (no `isolation=` parameter). Main-checkout spawns run one at a time;
|
|
7
|
-
the wave's worktree spawns may run alongside.
|
|
8
|
-
|
|
9
|
-
Emit the plan-start heartbeat for the group's first plan, then dispatch (runs in a subagent — no output until the whole group returns; expected, not a freeze):
|
|
10
|
-
|
|
11
|
-
```text
|
|
12
|
-
Agent(
|
|
13
|
-
subagent_type="pp-executor",
|
|
14
|
-
description="Execute serial group {serial_group} of phase {phase_number}: {plan count} plans",
|
|
15
|
-
model="{executor_model}", # omit this line when executor_model == "inherit"
|
|
16
|
-
prompt="
|
|
17
|
-
<objective>
|
|
18
|
-
Execute these plans of phase {phase_number}-{phase_name} on the main checkout, one after another, in this order:
|
|
19
|
-
{for each entry of spawn.plans, numbered:}
|
|
20
|
-
{n}. {plan.id} — {plan.title} — ${PROJECT_ROOT}/{phase_dir}/{plan file} — touched tests: {plan.touched_tests or "none"} — build: {plan.build}
|
|
21
|
-
For each plan in turn: execute its tasks with their own atomic commits, write and commit its own SUMMARY.md, run its touched test files, then start the next plan.
|
|
22
|
-
</objective>
|
|
23
|
-
|
|
24
|
-
<dispatch tracking="orchestrator" suite="orchestrator" build="{spawn.build}" />
|
|
25
|
-
|
|
26
|
-
<serial_group_rules>
|
|
27
|
-
- One plan at a time, in the listed order. Each plan gets its own task commits and its own `{plan_id}-SUMMARY.md`, committed before the next plan starts.
|
|
28
|
-
- Tests: per plan, run only the test files that plan touches (listed above) and its narrower `<verify>` commands. Never the full suite — the orchestrator runs it once after the wave.
|
|
29
|
-
- Build: only before the tests of a plan whose `build` is true. When the group's `build` is false, run no build at all.
|
|
30
|
-
- Never call `state.*`, `roadmap.update-plan-progress` or `requirements.mark-complete`, and never stage STATE.md, ROADMAP.md or REQUIREMENTS.md: the orchestrator advances them once per wave.
|
|
31
|
-
- Never run `size:baseline`; the phase's closing commit re-baselines.
|
|
32
|
-
- Halt: when a plan cannot finish (verification still failing after repair, a Rule 4 stop, a checkpoint), stop the group at that plan — do not start the next one — and return `## GROUP HALTED` with `**Halted at:** {plan_id}`, the reason, the completed plans with their SUMMARY paths, and the plans not started.
|
|
33
|
-
- Otherwise return `## GROUP COMPLETE` with one `## PLAN COMPLETE` block per plan, in order.
|
|
34
|
-
</serial_group_rules>
|
|
35
|
-
|
|
36
|
-
<sequential_execution>
|
|
37
|
-
You are running as a SEQUENTIAL executor agent on the main working tree.
|
|
38
|
-
Use normal git commits (with hooks). Do NOT use --no-verify.
|
|
39
|
-
REQUIRED ORDER, per plan: Write SUMMARY.md → commit → only then any narration.
|
|
40
|
-
</sequential_execution>
|
|
41
|
-
|
|
42
|
-
<execution_context>
|
|
43
|
-
@~/.claude/pp-core/workflows/execute-plan.md
|
|
44
|
-
@~/.claude/pp-core/templates/summary.md
|
|
45
|
-
@~/.claude/pp-core/references/checkpoints.md
|
|
46
|
-
@~/.claude/pp-core/references/tdd.md
|
|
47
|
-
</execution_context>
|
|
48
|
-
|
|
49
|
-
<files_to_read>
|
|
50
|
-
MANDATORY: load every file listed here with the Read tool before you start the work — this block is your required reading.
|
|
51
|
-
First resolve repo root: `PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)`
|
|
52
|
-
- Every plan file listed in <objective> (read the next one again before starting it)
|
|
53
|
-
- ${PROJECT_ROOT}/.planning/PROJECT.md, ${PROJECT_ROOT}/.planning/STATE.md, ${PROJECT_ROOT}/.planning/config.json (if exists)
|
|
54
|
-
- ${PROJECT_ROOT}/${phase_dir}/*-CONTEXT.md and *-RESEARCH.md (if they exist)
|
|
55
|
-
- ${PROJECT_ROOT}/CLAUDE.md or ${PROJECT_ROOT}/.claude/CLAUDE.md, and project skills (if any)
|
|
56
|
-
</files_to_read>
|
|
57
|
-
|
|
58
|
-
${AGENT_SKILLS}
|
|
59
|
-
{spawn.contributions[].fragment — each verbatim, blank-line separated; nothing when empty}
|
|
60
|
-
|
|
61
|
-
<success_criteria>
|
|
62
|
-
- [ ] Every listed plan executed in order, or the group halted with the halting plan named
|
|
63
|
-
- [ ] Each task committed individually; one committed SUMMARY.md per completed plan
|
|
64
|
-
- [ ] No tracking-file writes (the orchestrator advances them once per wave)
|
|
65
|
-
</success_criteria>
|
|
66
|
-
"
|
|
67
|
-
)
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
**On return:**
|
|
71
|
-
- `## GROUP COMPLETE` → emit a plan-complete heartbeat per plan and go on to step 4 (spot-checks use the spawn's `expected_summaries`).
|
|
72
|
-
- `## GROUP HALTED` → the plans before the halt are done (their SUMMARY.md counts for this wave's suite run and tracking commit). Route the halting plan to step 7 (handle failures). The plans after it are not started; they stay incomplete, so a re-run of `phase.dispatch-plan --wave {N}` lists only them.
|
|
73
|
-
- A checkpoint at a plan → `checkpoint_handling`; once resolved, re-run `phase.dispatch-plan --wave {N}` and dispatch the remaining spawn.
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# Vault door gate (the side doors)
|
|
2
|
-
|
|
3
|
-
A workflow that writes code outside `/pp-execute-phase` runs the same vault gate as execute:pre
|
|
4
|
-
before its first write. The host Reads this file at the point it names and restates none of it.
|
|
5
|
-
|
|
6
|
-
```bash
|
|
7
|
-
_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
|
|
8
|
-
DOOR_VAULT=$(pp_run loop render-hooks execute:pre --active-cap vault) || exit 1
|
|
9
|
-
echo "DOOR_VAULT=$DOOR_VAULT"
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
- **The fence exits non-zero:** render-hooks refused, and its message names the unreadable config
|
|
13
|
-
file. STOP: write nothing. The operator repairs the file and re-runs the command.
|
|
14
|
-
- **`DOOR_VAULT=false`:** the vault capability is not active for this project. Continue silently.
|
|
15
|
-
- **`DOOR_VAULT=true`:** the execute:pre vault step and gate are active. Read and execute
|
|
16
|
-
`pp-core/workflows/execute-phase/steps/vault-fetch.md`, then set `VAULT_GATE_POINT=execute` and
|
|
17
|
-
Read and execute `pp-core/workflows/execute-phase/steps/vault-load-gate.md`. Its fence prints
|
|
18
|
-
the card and exits non-zero on `stop`; a stop ends the host run before anything is written, in
|
|
19
|
-
every mode and every tier.
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# Plan-phase: ceremony tier
|
|
2
|
-
|
|
3
|
-
Read at §1, right after init. The phase's tier decides which planning agents run. Never re-derive it from config: `src/phase-tier.cts` resolves it (`--tier` > the phase's ROADMAP `**Tier:**` line > `phase.tier` > `standard`; an explicit project `false` wins).
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
_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
|
|
7
|
-
TIER_PROPOSAL=$(pp_run query phase.tier-propose "$PHASE" --record $TIER_FLAG)
|
|
8
|
-
DISPATCH_PLAN=$(pp_run query phase.dispatch-plan "$PHASE" $TIER_FLAG)
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Print one line from `TIER_PROPOSAL`: `Tier: {effective.tier} ({effective.source}) — proposed {proposal.tier}: {proposal.reason}`. If `effective.suppressed` is non-empty, append `— kept off by your config: {effective.suppressed}`. Do not ask; the operator overrides with `--tier`. `--record` writes the proposal as `**Tier:** <tier> (proposed: …)` and never replaces a tier the operator chose; `--record --tier X` records X as the operator's choice.
|
|
12
|
-
|
|
13
|
-
`DISPATCH_PLAN.stages.plan` lists what this run spawns, in order. The init booleans come from the same resolver, so they agree:
|
|
14
|
-
|
|
15
|
-
| Stage entry | Agent | Init key / hook |
|
|
16
|
-
|---|---|---|
|
|
17
|
-
| `plan-research` | `pp-phase-researcher` | `research_enabled` |
|
|
18
|
-
| `plan-patterns` | `pp-pattern-mapper` | pattern-mapper `plan:pre` hook (render-hooks `--phase`) |
|
|
19
|
-
| `plan-planner` | `pp-planner` | always |
|
|
20
|
-
| `plan-check-N` / `plan-revise-N` | `pp-plan-checker` / `pp-planner` | `plan_checker_enabled`; §12 caps checker passes at `plan_check_passes` |
|
|
21
|
-
| `plan-bounce` | (script) | `plan_bounce_enabled` (§12.5) |
|
|
22
|
-
| `plan-gaps` | (report) | gap-analysis `plan:post` hook (§13e) |
|
|
23
|
-
|
|
24
|
-
Spawn nothing the stage does not list. The explicit flags still apply on top: `--research` forces research, `--skip-research`, `--skip-verify`, `--bounce` / `--skip-bounce`.
|
|
25
|
-
|
|
26
|
-
## After planning (§9, `## PLANNING COMPLETE`)
|
|
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
|
-
|
|
37
|
-
Plans now exist, so re-propose from their `files_modified`:
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
TIER_PROPOSAL=$(pp_run query phase.tier-propose "$PHASE" --record)
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Print the same line. If `proposal.tier` differs from `effective.tier` (an operator choice is kept), add: `Plans suggest {proposal.tier} ({proposal.reason}); pass --tier {proposal.tier} to /pp:execute-phase and /pp:verify-work to apply it.` Execute-phase and verify-work read the recorded tier.
|
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @file size-budget.cjs
|
|
5
|
-
*
|
|
6
|
-
* The per-commit size budget for workflow and agent markdown (pack 9, item 4).
|
|
7
|
-
*
|
|
8
|
-
* ## What is measured
|
|
9
|
-
*
|
|
10
|
-
* Every CHANGE since the baseline file was last committed, one at a time:
|
|
11
|
-
* - each commit in `<last baseline commit>..HEAD` (at most MAX_COMMITS), against
|
|
12
|
-
* its parent — for a merge commit, against the larger of each file's sizes in
|
|
13
|
-
* its parents, so a merge adds nothing of its own and each plan commit is
|
|
14
|
-
* judged on its own branch;
|
|
15
|
-
* - the uncommitted edits under the guarded directory, against HEAD.
|
|
16
|
-
* A file's allowance base is the LARGER of its size before the change and its
|
|
17
|
-
* size in the committed baseline file. A change may grow one file by at most
|
|
18
|
-
* `per_file_bytes` and all files together by at most `per_commit_bytes`. So:
|
|
19
|
-
* - a plan that grows a few files a little passes without touching the
|
|
20
|
-
* baseline, however much the phase has grown in earlier plans;
|
|
21
|
-
* - growth already recorded in the baseline (a justified re-baseline) passes;
|
|
22
|
-
* - one change growing past the budget fails, by file and by total, and stays
|
|
23
|
-
* red until it is shrunk or recorded — a later commit does not hide it.
|
|
24
|
-
* A file that is in neither the baseline nor the parent (a new file) is not
|
|
25
|
-
* budgeted; the tier, nested and new-file hard caps in the guard tests bound it.
|
|
26
|
-
*
|
|
27
|
-
* The cumulative growth of a phase is recorded once, at phase close:
|
|
28
|
-
* `npm run size:baseline -- --reason "phase NN close"` in the phase's closing
|
|
29
|
-
* commit, which also starts the next window. The tier hard caps stay absolute
|
|
30
|
-
* red lines.
|
|
31
|
-
*
|
|
32
|
-
* ## Why per commit, not against the baseline alone
|
|
33
|
-
*
|
|
34
|
-
* Measured against the committed baseline alone, the slack is spent cumulatively:
|
|
35
|
-
* a long phase whose plans each grow a different file by a few hundred bytes goes
|
|
36
|
-
* red part-way through, and from then on every plan re-baselines — the baseline
|
|
37
|
-
* becomes a file every plan touches, which serialises plans that are otherwise
|
|
38
|
-
* independent.
|
|
39
|
-
*
|
|
40
|
-
* ## Fallback
|
|
41
|
-
*
|
|
42
|
-
* Not a git checkout, or no HEAD: the check is the previous one — growth
|
|
43
|
-
* against the committed baseline with the same two numbers (`assertDeltaBudget`),
|
|
44
|
-
* new and removed files included. In a shallow clone, commits whose parent is
|
|
45
|
-
* missing are skipped (CI's `test` job clones with GitLab's default depth).
|
|
46
|
-
*
|
|
47
|
-
* ## The numbers
|
|
48
|
-
*
|
|
49
|
-
* `size_budget.per_file_bytes` (default 1024) and `size_budget.per_commit_bytes`
|
|
50
|
-
* (default 4096) — the schema defaults in
|
|
51
|
-
* `pp-core/bin/shared/config-defaults.manifest.json`, overridable in the
|
|
52
|
-
* repository's `.planning/config.json`. Nothing else holds them.
|
|
53
|
-
*/
|
|
54
|
-
|
|
55
|
-
const fs = require('fs');
|
|
56
|
-
const path = require('path');
|
|
57
|
-
const { execFileSync } = require('child_process');
|
|
58
|
-
const { assertDeltaBudget } = require('./allowlist-ratchet.cjs');
|
|
59
|
-
|
|
60
|
-
const REPO_ROOT = path.join(__dirname, '..', '..');
|
|
61
|
-
const DEFAULTS_MANIFEST = path.join(REPO_ROOT, 'pp-core', 'bin', 'shared', 'config-defaults.manifest.json');
|
|
62
|
-
|
|
63
|
-
function isPositiveInt(v) {
|
|
64
|
-
return Number.isInteger(v) && v > 0;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* The budget numbers: project `.planning/config.json` `size_budget.*` when set to
|
|
69
|
-
* positive integers, else the schema defaults from the manifest.
|
|
70
|
-
*
|
|
71
|
-
* @param {{ repoRoot?: string, manifestPath?: string }} [opts]
|
|
72
|
-
* @returns {{ perFileBytes: number, perCommitBytes: number, source: { perFileBytes: string, perCommitBytes: string } }}
|
|
73
|
-
*/
|
|
74
|
-
function readSizeBudget({ repoRoot = REPO_ROOT, manifestPath = DEFAULTS_MANIFEST } = {}) {
|
|
75
|
-
const defaults = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')).size_budget;
|
|
76
|
-
if (!defaults || !isPositiveInt(defaults.per_file_bytes) || !isPositiveInt(defaults.per_commit_bytes)) {
|
|
77
|
-
throw new Error(`${manifestPath} must define size_budget.per_file_bytes and size_budget.per_commit_bytes as positive integers`);
|
|
78
|
-
}
|
|
79
|
-
let project = {};
|
|
80
|
-
try {
|
|
81
|
-
const cfg = JSON.parse(fs.readFileSync(path.join(repoRoot, '.planning', 'config.json'), 'utf-8'));
|
|
82
|
-
if (cfg && typeof cfg.size_budget === 'object' && cfg.size_budget !== null) project = cfg.size_budget;
|
|
83
|
-
} catch {
|
|
84
|
-
// no project config: schema defaults apply
|
|
85
|
-
}
|
|
86
|
-
const pick = (key) =>
|
|
87
|
-
isPositiveInt(project[key])
|
|
88
|
-
? { value: project[key], source: '.planning/config.json' }
|
|
89
|
-
: { value: defaults[key], source: 'schema default' };
|
|
90
|
-
const perFile = pick('per_file_bytes');
|
|
91
|
-
const perCommit = pick('per_commit_bytes');
|
|
92
|
-
return {
|
|
93
|
-
perFileBytes: perFile.value,
|
|
94
|
-
perCommitBytes: perCommit.value,
|
|
95
|
-
source: { perFileBytes: perFile.source, perCommitBytes: perCommit.source },
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// A GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE inherited from a hook would point git at
|
|
100
|
-
// another repository; the guarded directory decides which repository is meant.
|
|
101
|
-
function git(cwd, args) {
|
|
102
|
-
const env = Object.fromEntries(Object.entries(process.env).filter(([k]) => !k.startsWith('GIT_')));
|
|
103
|
-
return execFileSync('git', args, { cwd, env, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Cap on how many commits back the guard looks (a re-baseline resets the window). */
|
|
107
|
-
const MAX_COMMITS = 300;
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* The changes to judge, oldest first, or null when git cannot provide them (see
|
|
111
|
-
* the Fallback section above). A change is one commit since the baseline file
|
|
112
|
-
* was last committed — `before` is, per file, the larger of its sizes in the
|
|
113
|
-
* commit's parents (so a merge commit adds nothing of its own) — plus the
|
|
114
|
-
* uncommitted edits under `dir` against HEAD.
|
|
115
|
-
*
|
|
116
|
-
* @param {{ dir: string, include: (rel: string) => boolean, baselinePath?: string }} opts
|
|
117
|
-
* `dir` the guarded directory; `include` filters POSIX paths relative to it
|
|
118
|
-
* (the same keys the measurer produces); `baselinePath` the committed baseline
|
|
119
|
-
* (its last commit starts the window; untracked → the last MAX_COMMITS commits).
|
|
120
|
-
* @returns {{ commits: Array<{ label: string, before: Object<string, number>, after: Object<string, number> }>,
|
|
121
|
-
* head: Object<string, number>, dirty: boolean, since: string | null } | null}
|
|
122
|
-
*/
|
|
123
|
-
function commitChanges({ dir, include, baselinePath }) {
|
|
124
|
-
let top;
|
|
125
|
-
try {
|
|
126
|
-
top = git(dir, ['rev-parse', '--show-toplevel']).trim();
|
|
127
|
-
git(top, ['rev-parse', '--verify', '--quiet', 'HEAD^{commit}']);
|
|
128
|
-
} catch {
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
const realTop = fs.realpathSync(top);
|
|
132
|
-
const rel = path.relative(realTop, fs.realpathSync(dir)).split(path.sep).join('/');
|
|
133
|
-
const pathspec = rel === '' ? '.' : rel;
|
|
134
|
-
const prefix = rel === '' ? '' : `${rel}/`;
|
|
135
|
-
|
|
136
|
-
const cache = new Map();
|
|
137
|
-
const sizesAt = (rev) => {
|
|
138
|
-
if (cache.has(rev)) return cache.get(rev);
|
|
139
|
-
let listing = null;
|
|
140
|
-
try {
|
|
141
|
-
listing = git(top, ['ls-tree', '-r', '-l', '--full-tree', rev, '--', pathspec]);
|
|
142
|
-
} catch {
|
|
143
|
-
listing = null; // object missing (shallow boundary)
|
|
144
|
-
}
|
|
145
|
-
let sizes = null;
|
|
146
|
-
if (listing !== null) {
|
|
147
|
-
sizes = {};
|
|
148
|
-
for (const line of listing.split('\n')) {
|
|
149
|
-
const m = /^\d+ blob [0-9a-f]+\s+(\d+)\t(.+)$/.exec(line);
|
|
150
|
-
if (!m || !m[2].startsWith(prefix)) continue;
|
|
151
|
-
const key = m[2].slice(prefix.length);
|
|
152
|
-
if (include(key)) sizes[key] = Number(m[1]);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
cache.set(rev, sizes);
|
|
156
|
-
return sizes;
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
let since = null;
|
|
160
|
-
if (baselinePath) {
|
|
161
|
-
try {
|
|
162
|
-
const relBaseline = path.relative(realTop, fs.realpathSync(baselinePath)).split(path.sep).join('/');
|
|
163
|
-
since = git(top, ['log', '-1', '--format=%H', 'HEAD', '--', relBaseline]).trim() || null;
|
|
164
|
-
} catch {
|
|
165
|
-
since = null; // baseline outside the repository or not there
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
const range = since ? [`${since}..HEAD`] : ['HEAD'];
|
|
169
|
-
// No pathspec here: path-limited rev-list rewrites parents and prunes merges.
|
|
170
|
-
const lines = git(top, ['rev-list', '--reverse', `--max-count=${MAX_COMMITS}`, '--parents', ...range])
|
|
171
|
-
.split('\n')
|
|
172
|
-
.filter(Boolean);
|
|
173
|
-
const commits = [];
|
|
174
|
-
for (const line of lines) {
|
|
175
|
-
const [sha, ...parents] = line.split(' ');
|
|
176
|
-
const after = sizesAt(sha);
|
|
177
|
-
const parentSizes = parents.map(sizesAt);
|
|
178
|
-
if (after === null || parents.length === 0 || parentSizes.some((p) => p === null)) continue;
|
|
179
|
-
const before = {};
|
|
180
|
-
for (const p of parentSizes) {
|
|
181
|
-
for (const [k, v] of Object.entries(p)) before[k] = Math.max(before[k] ?? -1, v);
|
|
182
|
-
}
|
|
183
|
-
const subject = git(top, ['log', '-1', '--format=%s', sha]).trim();
|
|
184
|
-
commits.push({ label: `commit ${sha.slice(0, 9)} "${subject}"`, before, after });
|
|
185
|
-
}
|
|
186
|
-
const dirty = git(top, ['status', '--porcelain', '--untracked-files=all', '--', pathspec]).trim() !== '';
|
|
187
|
-
return { commits, head: sizesAt('HEAD') ?? {}, dirty, since };
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* The per-commit budget check. Reports through `fail`; returns the measurement.
|
|
192
|
-
*
|
|
193
|
-
* @param {object} o
|
|
194
|
-
* @param {string} o.label - prefix for messages, e.g. 'workflow-size'
|
|
195
|
-
* @param {Object<string, number>} o.current - sizes now (working tree)
|
|
196
|
-
* @param {Object<string, number>} o.baseline - committed baseline sizes
|
|
197
|
-
* @param {ReturnType<typeof commitChanges>} o.changes - from `commitChanges`; null → baseline-only fallback
|
|
198
|
-
* @param {{ perFileBytes: number, perCommitBytes: number }} o.budget - from `readSizeBudget`
|
|
199
|
-
* @param {(msg: string) => void} o.fail
|
|
200
|
-
* @param {string} [o.updateHint]
|
|
201
|
-
*/
|
|
202
|
-
function assertCommitDeltaBudget({ label, current, baseline, changes, budget, fail, updateHint }) {
|
|
203
|
-
const hint = updateHint ? `\n${updateHint}` : '';
|
|
204
|
-
if (changes === null) {
|
|
205
|
-
return {
|
|
206
|
-
mode: 'baseline',
|
|
207
|
-
...assertDeltaBudget({
|
|
208
|
-
label,
|
|
209
|
-
current,
|
|
210
|
-
baseline,
|
|
211
|
-
perFileSlack: budget.perFileBytes,
|
|
212
|
-
totalSlack: budget.perCommitBytes,
|
|
213
|
-
fail,
|
|
214
|
-
updateHint: `(No git history available: measured against the committed baseline only.)${hint}`,
|
|
215
|
-
}),
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (Object.keys(baseline).length === 0) {
|
|
220
|
-
fail(
|
|
221
|
-
`[${label}] the committed baseline has no size entries — an empty baseline would make ` +
|
|
222
|
-
`every growth check pass vacuously. Regenerate it with \`npm run size:baseline\`.`
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
for (const [name, v] of Object.entries(baseline)) {
|
|
226
|
-
if (!Number.isInteger(v) || v < 0) fail(`[${label}] baseline entry ${name} is not a non-negative integer byte size: ${JSON.stringify(v)}`);
|
|
227
|
-
}
|
|
228
|
-
const base = (name) => (Number.isInteger(baseline[name]) && baseline[name] >= 0 ? baseline[name] : -1);
|
|
229
|
-
const todo =
|
|
230
|
-
`Shrink it — move content into a lazily-read steps/ or references/ file — or, if the growth is needed, ` +
|
|
231
|
-
`record it: npm run size:baseline -- --reason "<why this growth is needed>" and commit the baseline. ` +
|
|
232
|
-
`Growth within the budget needs no baseline commit: the phase's closing commit re-baselines once ` +
|
|
233
|
-
`with --reason "phase NN close".`;
|
|
234
|
-
const fmt = (o) => ` - ${o.name}: ${o.from} → ${o.to} (+${o.delta})`;
|
|
235
|
-
|
|
236
|
-
const judged = [...changes.commits];
|
|
237
|
-
if (changes.dirty) judged.push({ label: 'the uncommitted changes', before: changes.head, after: current });
|
|
238
|
-
const results = [];
|
|
239
|
-
for (const change of judged) {
|
|
240
|
-
const overages = [];
|
|
241
|
-
for (const name of Object.keys(change.after).sort()) {
|
|
242
|
-
const from = Math.max(base(name), change.before[name] ?? -1);
|
|
243
|
-
if (from < 0) continue; // new file: bounded by the hard caps, recorded at phase close
|
|
244
|
-
const to = change.after[name];
|
|
245
|
-
if (to > from) overages.push({ name, from, to, delta: to - from });
|
|
246
|
-
}
|
|
247
|
-
const totalOverage = overages.reduce((sum, o) => sum + o.delta, 0);
|
|
248
|
-
const where = `in ${change.label} (measured against its parent, or the committed baseline when larger)`;
|
|
249
|
-
const overFile = overages.filter((o) => o.delta > budget.perFileBytes);
|
|
250
|
-
if (overFile.length > 0) {
|
|
251
|
-
fail(
|
|
252
|
-
`[${label}] ${overFile.length} file(s) grew more than the per-file slack of ${budget.perFileBytes} bytes ` +
|
|
253
|
-
`(size_budget.per_file_bytes) ${where}. ${todo}\n${overFile.map(fmt).join('\n')}${hint}`
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
if (totalOverage > budget.perCommitBytes) {
|
|
257
|
-
fail(
|
|
258
|
-
`[${label}] total growth ${where} is ${totalOverage} bytes, over the total slack of ` +
|
|
259
|
-
`${budget.perCommitBytes} bytes (size_budget.per_commit_bytes). ${todo}\n${overages.map(fmt).join('\n')}${hint}`
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
results.push({ change: change.label, overages, totalOverage });
|
|
263
|
-
}
|
|
264
|
-
const unrecorded = Object.keys(current).filter((n) => base(n) < 0).sort();
|
|
265
|
-
const removed = Object.keys(baseline).filter((n) => !Object.prototype.hasOwnProperty.call(current, n)).sort();
|
|
266
|
-
return { mode: 'commit', since: changes.since, results, unrecorded, removed };
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* The whole guard for one tree, as the size-budget tests run it: read the
|
|
271
|
-
* committed baseline, measure, list the changes, check the budget and the
|
|
272
|
-
* `_rebaselines` log. Returns failure messages (empty = green).
|
|
273
|
-
*
|
|
274
|
-
* @param {object} o
|
|
275
|
-
* @param {string} o.label - 'workflow-size' / 'agent-size'
|
|
276
|
-
* @param {string} o.dir - guarded directory
|
|
277
|
-
* @param {string} o.baselinePath - committed baseline JSON
|
|
278
|
-
* @param {(dir: string) => Object<string, number>} o.measure - the measurer
|
|
279
|
-
* @param {(rel: string) => boolean} o.include - which repository paths (relative to dir) the measurer covers
|
|
280
|
-
* @param {{ perFileBytes: number, perCommitBytes: number }} [o.budget] - default: readSizeBudget()
|
|
281
|
-
* @param {object|null} [o.changes] - default: commitChanges({ dir, include, baselinePath }); null = baseline-only
|
|
282
|
-
* @param {string} [o.updateHint]
|
|
283
|
-
* @returns {string[]}
|
|
284
|
-
*/
|
|
285
|
-
function checkSizeBudget({ label, dir, baselinePath, measure, include, budget, changes, updateHint }) {
|
|
286
|
-
// Lazy: update-size-baseline.cjs is the baseline file format's owner.
|
|
287
|
-
const { readBaseline, splitBaseline, validateRebaselineLog, REBASELINES_KEY } = require('../update-size-baseline.cjs');
|
|
288
|
-
const failures = [];
|
|
289
|
-
const raw = readBaseline(baselinePath);
|
|
290
|
-
if (raw === null) {
|
|
291
|
-
failures.push(`baseline ${baselinePath} does not exist — run \`npm run size:baseline\``);
|
|
292
|
-
} else if (!(REBASELINES_KEY in raw)) {
|
|
293
|
-
failures.push(`baseline ${baselinePath} has no ${REBASELINES_KEY} log — run \`npm run size:baseline\``);
|
|
294
|
-
}
|
|
295
|
-
const { sizes, rebaselines } = splitBaseline(raw);
|
|
296
|
-
assertCommitDeltaBudget({
|
|
297
|
-
label,
|
|
298
|
-
current: measure(dir),
|
|
299
|
-
baseline: sizes,
|
|
300
|
-
changes: changes === undefined ? commitChanges({ dir, include, baselinePath }) : changes,
|
|
301
|
-
budget: budget ?? readSizeBudget(),
|
|
302
|
-
fail: (m) => failures.push(m),
|
|
303
|
-
updateHint,
|
|
304
|
-
});
|
|
305
|
-
for (const problem of validateRebaselineLog(rebaselines)) {
|
|
306
|
-
failures.push(`[${label}] invalid ${REBASELINES_KEY} log: ${problem}`);
|
|
307
|
-
}
|
|
308
|
-
return failures;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
module.exports = { readSizeBudget, commitChanges, assertCommitDeltaBudget, checkSizeBudget, DEFAULTS_MANIFEST, MAX_COMMITS };
|