espalier-migrate · diff
git:20260903.910585f to git:20260909.2de0a59
121 added, 29 removed. Audit A to A.
---
name: espalier-migrate
- description: Migrate an existing harness/espalier install to the current Espalier version — auto-detects which of v0.1→v0.2, v0.3→v0.4, v0.4→v0.5, the v0.5.3 coder-agent patch, v0.5→v0.6 (Stage 1 grill), v0.6→v0.7 (read-only /espalier-ask lane), v0.7→v0.8 (requirements approval gate), the v0.8.1 impact-analysis agent patch, the v0.8.2 re-review fixpoint loop, the v0.9.0 security audit, the v0.9.1 configurable escalation caps, the v0.9.2 correctness patch, the v0.9.3 skill-clarity patch, the v0.9.4 security-skill patch, the v0.9.6→v0.10.0 push-gate reshape, the v0.10.0→v0.11.0 hook exit-code release, the v0.11.0→v0.12.0 grill blind-spot pass (Stage 1 rules/wiki cross-check), the v0.12.0→v0.13.0 minimalism release (coder Solution Selection Ladder + reviewer advisory Minimalism Review), the v0.13.0→v0.13.1 polish patch (stage-conditional coding guidance + reviewer step-7 abuse-test duty + code-review gate line), the v0.13.1→v0.13.2 readability release (coder clarity-then-brevity tie-break + comment-convention discovery + reviewer advisory Readability Review with the cryptic-public-name P1), the v0.13.2→v0.14.0 Codex platform release (platform-neutral hook wrappers + optional .agents/skills + AGENTS.md + .codex wiring), the v0.14.0→v0.15.0 Copilot platform release (camelCase hook adapter + optional .github/skills + copilot-instructions + .github/agents + .github/hooks wiring), the v0.15.0→v0.16.0 multi-dev maintenance floor (conv_fold conventions reader + maintenance lanes + canonical-ref keys + .gitattributes union entry + optional CODEOWNERS), the v0.16.0→v0.17.0 B-team release (weekly gardener rota + tracked single-line .doctor-stamp with clean/dirty semantics + conventions file-per-key), the v0.17.0→v0.18.0 map-lane release (/espalier-map multi-session planning + map-guard plan-don't-do hook + grill decision mode + greenfield decide-then-bind init), the v0.18.0→v0.19.0 run-lane release (/espalier-maprun map executor — interactive master + headless stage-1–6 workers in push-blocked worktrees + the maprun state engine), the v0.19.0→v0.20.0 slice-PR release (opt-in maprun-pr.sh forge surface — one reviewable slice PR per ticket + final assembly PR + the worktree-scoped push-block fix), and the v0.20.0→v0.21.0 pipeline-speed release (per-change context pack reused by every spawn + delta-scoped re-review rounds + parallel disjoint sub-tasks + push-target pre-authorization + light-tier grill batching — same gates, fewer redundant reads and waits), the v0.21.1 comment-brevity patch (coder/reviewer/coding-standards gain a short-comments default — one plain line, project conventions still outrank it), and the v0.21.1→v0.22.0 pipeline-speed release (speculative Stage 5 — the test-coder rides the round-1 panel, quarantine-on-FAIL keeps every later gate tree clean — + Stage 6 delta re-review rounds + stage-duration stats + the non-critical pre-flight fold + Stage 9 deploy pre-authorization + the Stage 8 CI wait protocol + opt-in parallel push-gate sections and a dependency-audit cache — same gates, fewer waits), and the v0.22.0→v0.22.1 comment-diet patch (coder/reviewer/coding-standards move to a default-NO-comment stance — one plain line only for what the code cannot show; project conventions still outrank it), and the v0.22.1→v0.23.0 round-economy release (the Stage 3/5 fold — tests ride the coder under `test-mode: folded`, the Stage 3 exit gate runs build+lint+scoped tests, the 2-agent panel reviews code+tests in one verdict, Stages 5/6 become the security-contract phase or SKIPPED rows with stage numbers intact; contract-FAIL code fixes route back to a full panel; findings digest + map crispness gate + grill tier verdicts; the speculative dispatch is retired and `test-mode: serial` keeps the pre-v0.22 flow), and the v0.23.0→v0.23.1 class-sweep patch (fix rounds fix the defect CLASS — the coder enumerates and fixes every in-scope sibling of each P0/P1 and reports a `### Class Sweep` block that both panel agents re-verify; `FIX ROUND {n}:` header on every Stage 4 re-spawn), and the v0.23.1→v0.24.0 simplify-lane release (/espalier-simplify — a read-only, evidence-first simplification survey of the existing code that files proven cuts as refactor skeletons the pipeline runs; the coder retires the whole boundary, the reviewer re-proves every retired name, and Completion flags the docs that described it for /espalier-prune) you need and applies them in order.
+ description: Migrate an existing harness/espalier install to the current Espalier version — auto-detects which of v0.1→v0.2, v0.3→v0.4, v0.4→v0.5, the v0.5.3 coder-agent patch, v0.5→v0.6 (Stage 1 grill), v0.6→v0.7 (read-only /espalier-ask lane), v0.7→v0.8 (requirements approval gate), the v0.8.1 impact-analysis agent patch, the v0.8.2 re-review fixpoint loop, the v0.9.0 security audit, the v0.9.1 configurable escalation caps, the v0.9.2 correctness patch, the v0.9.3 skill-clarity patch, the v0.9.4 security-skill patch, the v0.9.6→v0.10.0 push-gate reshape, the v0.10.0→v0.11.0 hook exit-code release, the v0.11.0→v0.12.0 grill blind-spot pass (Stage 1 rules/wiki cross-check), the v0.12.0→v0.13.0 minimalism release (coder Solution Selection Ladder + reviewer advisory Minimalism Review), the v0.13.0→v0.13.1 polish patch (stage-conditional coding guidance + reviewer step-7 abuse-test duty + code-review gate line), the v0.13.1→v0.13.2 readability release (coder clarity-then-brevity tie-break + comment-convention discovery + reviewer advisory Readability Review with the cryptic-public-name P1), the v0.13.2→v0.14.0 Codex platform release (platform-neutral hook wrappers + optional .agents/skills + AGENTS.md + .codex wiring), the v0.14.0→v0.15.0 Copilot platform release (camelCase hook adapter + optional .github/skills + copilot-instructions + .github/agents + .github/hooks wiring), the v0.15.0→v0.16.0 multi-dev maintenance floor (conv_fold conventions reader + maintenance lanes + canonical-ref keys + .gitattributes union entry + optional CODEOWNERS), the v0.16.0→v0.17.0 B-team release (weekly gardener rota + tracked single-line .doctor-stamp with clean/dirty semantics + conventions file-per-key), the v0.17.0→v0.18.0 map-lane release (/espalier-map multi-session planning + map-guard plan-don't-do hook + grill decision mode + greenfield decide-then-bind init), the v0.18.0→v0.19.0 run-lane release (/espalier-maprun map executor — interactive master + headless stage-1–6 workers in push-blocked worktrees + the maprun state engine), the v0.19.0→v0.20.0 slice-PR release (opt-in maprun-pr.sh forge surface — one reviewable slice PR per ticket + final assembly PR + the worktree-scoped push-block fix), and the v0.20.0→v0.21.0 pipeline-speed release (per-change context pack reused by every spawn + delta-scoped re-review rounds + parallel disjoint sub-tasks + push-target pre-authorization + light-tier grill batching — same gates, fewer redundant reads and waits), the v0.21.1 comment-brevity patch (coder/reviewer/coding-standards gain a short-comments default — one plain line, project conventions still outrank it), and the v0.21.1→v0.22.0 pipeline-speed release (speculative Stage 5 — the test-coder rides the round-1 panel, quarantine-on-FAIL keeps every later gate tree clean — + Stage 6 delta re-review rounds + stage-duration stats + the non-critical pre-flight fold + Stage 9 deploy pre-authorization + the Stage 8 CI wait protocol + opt-in parallel push-gate sections and a dependency-audit cache — same gates, fewer waits), and the v0.22.0→v0.22.1 comment-diet patch (coder/reviewer/coding-standards move to a default-NO-comment stance — one plain line only for what the code cannot show; project conventions still outrank it), and the v0.22.1→v0.23.0 round-economy release (the Stage 3/5 fold — tests ride the coder under `test-mode: folded`, the Stage 3 exit gate runs build+lint+scoped tests, the 2-agent panel reviews code+tests in one verdict, Stages 5/6 become the security-contract phase or SKIPPED rows with stage numbers intact; contract-FAIL code fixes route back to a full panel; findings digest + map crispness gate + grill tier verdicts; the speculative dispatch is retired and `test-mode: serial` keeps the pre-v0.22 flow), and the v0.23.0→v0.23.1 class-sweep patch (fix rounds fix the defect CLASS — the coder enumerates and fixes every in-scope sibling of each P0/P1 and reports a `### Class Sweep` block that both panel agents re-verify; `FIX ROUND {n}:` header on every Stage 4 re-spawn), and the v0.23.1→v0.24.0 simplify-lane release (/espalier-simplify — a read-only, evidence-first simplification survey of the existing code that files proven cuts as refactor skeletons the pipeline runs; the coder retires the whole boundary, the reviewer re-proves every retired name, and Completion flags the docs that described it for /espalier-prune), and the v0.24.0→v0.25.0 quality-first context release (every agent works with its instructions at full strength on current inputs, no budget anywhere — coders hand off at clean points with a cited `## Handoff`, the orchestrator archives prior reports to `coding-log/`, offers a fresh session at the Stage 2 / Stage 4 boundaries, runs the exit gate as one `exit_gate` call, extracts the security contract, and names Grep-only files and scoped docs in the pack; the grill's code reads are unlimited; rules get a Writing Contract, a Removed-rules ledger at the prune gate, a doctor contract-drift line, and a `## Not Precedent` anchor; `espalier-stats.sh` reports spawn shape) you need and applies them in order.
---
# Espalier Migration Runner
## When to Use
- "Migrate to Espalier" / "Upgrade my Espalier install"
- "Upgrade my harness to the latest version"
- "Rename harness/ to espalier/"
- "/espalier-migrate"
## When NOT to Use
- Fresh project with no existing `harness/` or `espalier/` dir → use `/espalier-init` instead.
- Already fully up to date — `/espalier-migrate` detects this itself and exits cleanly with no changes.
## Instructions
You are running a migration of an existing install to the current Espalier
- version. Up to THIRTY-FOUR migrations may apply, always in this order:
+ version. Up to THIRTY-FIVE migrations may apply, always in this order:
1. **v0.1.x → v0.2.x** — typed `harness/changes/{type}/{slug}/` layout,
`/harness-fix` lane, squash-merge decision. Mechanical:
`scripts/migrate-v0.1-to-v0.2.sh`.
2. **v0.3.x → v0.4.0** — `harness/` → `espalier/` directory + skill rename
(`harness-run` → `espalier`, `harness-{coding,…}` → `espalier-*`).
Mechanical: `scripts/migrate-v0.3-to-v0.4.sh`.
3. **v0.4.x → v0.5.0** — doc-drift detection: drift hooks, the
`/espalier-prune` + `/espalier-doctor` skills, the post-merge dispatcher,
`.doctor-cadence`. Mechanical: `scripts/migrate-v0.4-to-v0.5.sh`.
4. **v0.5.0–v0.5.2 → v0.5.3** — appends the `## Editing Discipline` section to
the coder sub-agent (`espalier/agents/harness-coder.md`). Mechanical:
`scripts/migrate-v0.5.2-to-v0.5.3.sh`.
5. **v0.5.x → v0.6.0** — Stage 1 grilling: installs the `espalier-grill` skill,
refreshes the four changed pipeline templates (which also date-prefix change
folders). Mechanical: `scripts/migrate-v0.5-to-v0.6.sh`.
6. **v0.6.x → v0.7.0** — read-only ask lane: installs the `espalier-ask` skill,
patches CLAUDE.md + `espalier/agent.md` to mention `/espalier-ask`. Purely
additive — no pipeline change. Mechanical: `scripts/migrate-v0.6-to-v0.7.sh`.
7. **v0.7.x → v0.8.0** — requirements approval gate: refreshes the three changed
pipeline templates (`pipeline.md`, `espalier`, `espalier-fix`) so both
pipelines STOP for explicit user sign-off after the requirement is written +
reviewed, before any code is written (no-TTY runs auto-approve). Mechanical:
`scripts/migrate-v0.7-to-v0.8.sh`.
8. **v0.8.0 → v0.8.1** — impact-analysis agent patch: appends a
`## Change Impact Analysis` section to the coder sub-agent
(`espalier/agents/harness-coder.md`) and a `## Runtime-Surface Review` section
to the reviewer sub-agent (`espalier/agents/harness-reviewer.md`). Both make
the agents reason about every surface a change touches (admin UIs, API
validation, client forms, persisted data, other callers) — not just the
programmatic happy path — so a now-derived value left user-required on a UI is
caught at coding/review instead of returning as a fix round. Mechanical:
`scripts/migrate-v0.8-to-v0.8.1.sh`.
9. **v0.8.1 → v0.8.2** — re-review fixpoint loop + push-gate certificate: code
review becomes a loop — every coder fix is re-reviewed until a fresh review of
the current code returns zero P0 — and the push gate blocks unless the pushed
code matches the fingerprint the last review saw. Refreshes the two pure-copy
pipeline files (`pipeline.md`, `espalier-fix`), appends a `## Re-review Rounds`
section to `harness-reviewer.md`, and inserts the certificate check into
`pre-push-gate.sh`. Mechanical: `scripts/migrate-v0.8.1-to-v0.8.2.sh`.
10. **v0.8.2 → v0.9.0** — security audit + production hardening (one release):
a new `harness-security` agent joins Stage 4 as a review panel; a
`security-standards` rule + `espalier-security` skill are added, and the
coder/reviewer/testing/push-gate gain security sections. Ships
`/espalier-audit`, the repo-wide audit lane (`espalier-audit` skill + a
Repo-Audit Mode section). Adds an always-loaded `production-standards` rule
(resilience / observability / data-safety, tiered severity) the coder writes
to and the reviewer enforces; per-round `VERDICT:` sentinels + dual-record
freshness; a fail-closed push gate (cwd guard) + programmatic build/lint gate;
a deploy-aware Stage 9; the TTY→explicit-signal gate fix; and a single shipped
`scout-prompts` file for prune/doctor. Creates the new per-project files,
`bootstrap --force` refreshes the pure-copy pipeline/grill/hooks + copies
scout-prompts + symlinks both new rules + runs the 37-check validation, and
surgical appends patch the per-project files. Mechanical:
`scripts/migrate-v0.8.2-to-v0.9.0.sh`.
11. **v0.9.0 → v0.9.1** — configurable escalation caps: the review-round +
rollback hard stops move from hardcoded prose into a tracked
`espalier/.espalier-config` (`max-req-rounds`, `max-code-rounds`,
`max-test-rounds`, `max-rollbacks` — all default 3) the orchestrator reads at
runtime. Creates the config file if absent (preserved on re-run) and refreshes
the three pure-copy pipeline files (`pipeline.md`,
`espalier/skills/espalier/SKILL.md`, `espalier-fix`) so their prose reads the
config; a customised file is backed up to `<file>.pre-v0.9.1.bak`. Mechanical:
`scripts/migrate-v0.9.0-to-v0.9.1.sh`.
12. **v0.9.1 → v0.9.2** — correctness patch: the fix lane's late-escalation
gates detect via a marker line (the old text called a shell helper that was
never shipped), its back-link reads the real `# Bug:` title, and its
regression verification is scoped + dep-linked (no more false `true` from a
suite that couldn't run); `/espalier-audit`'s fix handoff and
`/espalier-prune`'s unattended path key off `interactivity_mode`; the push
gate gates the ACTIVE change (completed changes stop blocking later manual
pushes) and parses mocha/rspec/go test counts, preserving the substituted
test command; scout 1.11 ships in `.scout-prompts.md` and prune/doctor map
the security/production rules. Backs up customised pure-copy files to
`<file>.pre-v0.9.2.bak`. Mechanical: `scripts/migrate-v0.9.1-to-v0.9.2.sh`.
13. **v0.9.2 → v0.9.3** — skill-clarity patch; no new lane, no new stage, no
behaviour change to any gate or verdict. `espalier-grill` (pure-copy) gains an
explicit user tier-override, a coverage guard so every counted ambiguity signal
ends resolved / scoped-out / recorded in `## Open Questions`, and records a
non-answer with a named safe default instead of dropping it. `espalier-review`
(substituted) gets its two review loops rewritten as an explicit
when / input / do / output / who workflow, stops restating severities in the
production-readiness checklist (`production-standards.md` is the single source),
and gains when-to-use + trigger phrases in its frontmatter. Backs both skills up
to `<file>.pre-v0.9.3.bak`. Mechanical: `scripts/migrate-v0.9.2-to-v0.9.3.sh`.
14. **v0.9.3 → v0.9.4** — security-skill patch; no new lane, no new stage, no
behaviour change to any gate or verdict (the security auditor's catch /
false-positive rates are unchanged). The per-project `harness-security` AGENT
and `espalier-security` SKILL gain when-to-use + the five sensitive axes as
frontmatter trigger context, and the agent's P0 rubric and repo-audit
"findings do not block" delta stop restating each other's semantics,
cross-referencing one source instead. Both are per-project files that
`bootstrap --force` never re-copies, so v0.9.4 shipped these template
improvements with no way to reach an existing install. Frontmatter is edited
LINE-WISE (an `inherit` tools-mode install has had its `tools:` line stripped;
re-copying the template's frontmatter would reintroduce it). A body span the
project has customised is left alone and reported warn-only. Backs both up to
`<file>.pre-v0.9.4.bak`. Mechanical: `scripts/migrate-v0.9.3-to-v0.9.4.sh`.
15. **v0.9.6 → v0.10.0** — push-gate reshape. `{build_command}` / `{lint_command}` /
`{test_command}` are now substituted into FUNCTION BODIES (`run_build` /
`run_lint` / `run_tests`), so a value may be a single command OR a multi-line
block — a repo that must run several suites (one per container in a Docker-first
stack, one per workspace in a monorepo) can finally be expressed. Before,
`{test_command}` landed inside a command substitution and had to be one
expression. Build and lint also stop discarding stderr: the old template ran
`<cmd> 2>/dev/null` and, on failure, printed `BLOCKED: Build fails` and nothing
else; output is now captured and its tail printed. The migration rewrites those
three spans in the installed gate, PRESERVING the commands substituted at init,
and handles each span independently (a gate half-migrated by running the v0.9.2
step against a v0.10.0 plugin has `run_tests` but old build/lint). A gate
customised at init past the template shape is left completely untouched and
reported, never mangled. Backs up to `<file>.pre-v0.10.0.bak`. Mechanical:
`scripts/migrate-v0.9.6-to-v0.10.0.sh`.
16. **v0.10.0 → v0.11.0** — hook exit-code release. Claude Code PreToolUse /
PostToolUse hooks block ONLY on exit code 2 with the message on stderr —
exit 1 + stdout is a silent no-op, so the old push gate never actually
blocked. Rebuilds the gate from the v0.11.0 template preserving the three
substituted command bodies (customised gates: skip marker + manual
contract), refreshes both wrappers (fail-closed python probe, broader push
match), patches `check-layer-boundaries.sh` to exit 2 + stderr, gives the
reviewer/security agents their record-file Write tool, and refreshes
`pipeline.md` + the pure-copy pipeline skills so the Stage 4/6 gates read
the verdict WORD (`FAIL`/`ESCALATION_REQUIRED` no longer advance as PASS),
plus `espalier-review`/`espalier-security` with `{project}` re-substitution.
Backs up to `<file>.pre-v0.11.bak`. Mechanical:
`scripts/migrate-v0.10.0-to-v0.11.0.sh`.
17. **v0.11.0 → v0.12.0** — grill blind-spot pass. Stage 1 grilling now
cross-references the requirement against the project's own `espalier/rules/`
and `espalier/wiki/`, not just the requirement text: a new Step 1.5 surfaces a
collision — an approach that violates an encoded rule (`throw` where the repo
standardised on `Result<T>`), a capability the wiki already documents, an
unstated downstream ripple — as a Stage 1 question citing the exact
`rules/<file>#section`, and floors the tier so a crisp-but-colliding
requirement can't skip grilling. It verifies each doc claim against the code
first — a stale doc is flagged (`mark_stale`), never raised as a false
collision — and stays silent when there is no map to collide with. The change
is confined to one pure-copy file, so the migration is a single backup-and-
refresh of `espalier/skills/espalier-grill/SKILL.md`. Backs up to
`<file>.pre-v0.12.bak`. Mechanical: `scripts/migrate-v0.11.0-to-v0.12.0.sh`.
18. **v0.12.0 → v0.13.0** — convention-bounded minimalism. The coder gains a
Solution Selection Ladder (conventions first, correctness within them,
brevity only breaks ties: reuse what the project has → the convention-named
mechanism → stdlib/native/installed dep → leanest compliant implementation;
a NEW dependency needs a requirements.md line). The reviewer gains an
advisory Minimalism Review (`delete:`/`stdlib:`/`native:`/`yagni:` findings
capped at P2/P3 so they never re-open the Stage 4 fixpoint loop; ONE P1
exception: a new dependency covering what stdlib / a native feature / an
installed dep provides), binds the sentinel's `p1=` to the P1 row count,
and restores the fan-out P1 bullet that had drifted from
production-standards.md. All four touched files are per-project
(substituted/scout-filled), so the migration is SURGICAL — anchored section
inserts, never a template overwrite. Backs up to `<file>.pre-v0.13.bak`.
Mechanical: `scripts/migrate-v0.12.0-to-v0.13.0.sh`.
19. **v0.13.0 → v0.13.1** — minimalism polish patch; no gate math, sentinel
vocabulary, round-cap, or review-dimension change. The coding skill gains
`## How This Skill Applies by Stage` (Stage 3 / Stage 5 testing / fix
rounds — including fix-round findings-only scope and "a test-only
dependency is still a NEW dependency"), the reviewer's Review Process gains
the Stage 6 abuse-test check as numbered step 7 (explicitly skipped on
code-review rounds; "Produce findings" renumbers to 8), and the review
skill's code-review loop gains the Gate line its plan-review loop got in
v0.13.0 (PASS/PASS_WITH_FIXES with p0=0 p1=0; rounds owned by Stage 4
`max-code-rounds`). All three touched files are per-project — surgical
anchored inserts, the By-Stage body extracted from the plugin template at
runtime. Backs up to `<file>.pre-v0.13.1.bak`. Mechanical:
`scripts/migrate-v0.13.0-to-v0.13.1.sh`.
20. **v0.13.1 → v0.13.2** — readability release: "human readable" stops being
an accident of discovered conventions and becomes an explicit duty on both
agents. The coder's ladder tie-break becomes clarity then brevity (same
correctness → the more readable → only then the shorter); coding-standards
gains a naming-intent floor ("a name STATES what it holds or does") and a
`## Comments & Docstrings` section (scout 1.3 now discovers comment
conventions); the reviewer gains an advisory Readability Review
(`naming:`/`nesting:`/`magic:`/`comments:` findings capped at P2/P3, judged
against the project's own conventions never taste; ONE P1 exception: a
cryptic EXPORTED/public name — public names freeze into contracts callers
bind to), with the sentinel's `p1=` counting that P1 like any other. All
six touched files are per-project — surgical anchored splices (requires
python3), the refreshed spans extracted from the plugin templates at
runtime. The new coding-standards sections carry `{observed ...}`
placeholders until `/espalier-prune` re-scouts. Backs up to
`<file>.pre-v0.13.2.bak`. Mechanical:
`scripts/migrate-v0.13.1-to-v0.13.2.sh`.
21. **v0.13.2 → v0.14.0** — Codex platform release. Always: refreshes the two
plugin-owned hook wrappers (`post-edit-wrapper.sh` now parses Codex
apply_patch input and resolves the repo root without
`$CLAUDE_PROJECT_DIR`; `pre-push-gate-wrapper.sh` now joins argv-array
commands before the git-push match — both inert on Claude Code). Optional
(`--with-codex`, asked in Step 4b): wires the codex platform additively —
`.agents/skills/` symlinks, `AGENTS.md` Espalier section + platform
mapping, `.codex/config.toml` hook block, `.codex/agents/harness-*.toml`
sub-agents, `espalier/.platforms`. Never unwires claude. Backs up to
`<wrapper>.pre-v0.14.bak`. Mechanical:
`scripts/migrate-v0.13.2-to-v0.14.0.sh`.
22. **v0.14.0 → v0.15.0** — Copilot platform release. Always: installs the new
plugin-owned Copilot hook shim `espalier/hooks/copilot-hook-adapter.sh`
(translates Copilot's camelCase `toolName`/`toolArgs` payload into the
Claude/Codex `tool_name`/`tool_input` shape and dispatches to the shared
wrappers; exit codes pass through — Copilot treats non-zero `preToolUse`
exits as DENY). Optional (`--with-copilot`, asked in Step 4c): wires the
copilot platform additively — `.github/skills/` symlinks,
`.github/copilot-instructions.md` Espalier section,
`.github/agents/harness-*.agent.md` custom agents,
`.github/hooks/espalier-gates.json`. Never unwires claude/codex. New file
only — no backups needed. Mechanical:
`scripts/migrate-v0.14.0-to-v0.15.0.sh`.
23. **v0.15.0 → v0.16.0** — multi-dev maintenance floor (Release A). Always:
refreshes the pure-copy pipeline files + `drift-helpers.sh` (the
`conv_fold`/`conv_observations` conventions reader, per-mechanism
maintenance lanes, promotion race guard, unattended Stage 0 continuation,
conflict + slug-collision recipes) with backup-on-diff
(`<file>.pre-v0.16.bak`); surgically appends the `## Maintenance Commits`
section to the per-project `espalier/rules/development-process.md`; then
`bootstrap --wire-only` appends the `.gitattributes` union entry for
`espalier/.ask-gaps.tsv`, the `canonical-remote`/`canonical-branch` config
keys, and runs the 48/53/58-check validation (side-effect: settings.json
backup). Optional: `--codeowners-rules`/`--codeowners-wiki` (asked in
Step 4d) write the CODEOWNERS marker block. Mechanical:
`scripts/migrate-v0.15.0-to-v0.16.0.sh`.
24. **v0.16.0 → v0.17.0** — multi-dev maintenance B-team. Pure-copy only:
refreshes `pipeline.md`, the espalier / espalier-fix / espalier-prune /
espalier-doctor SKILL files, and `drift-helpers.sh` (doctor_due v2 with
clean/dirty shared-stamp semantics + skew rejection, `doctor_stamp_shared`,
`conv_slug`, per-key `append_convention`) with backup-on-diff
(`<file>.pre-v0.17.bak`). No config change, no attribute change, no data
migration — `espalier/conventions/` and `espalier/.doctor-stamp` appear on
first write; the legacy `.conventions.tsv` is read forever, written never.
Run-time assert: `.doctor-stamp` must not be gitignored. Mechanical:
`scripts/migrate-v0.16.0-to-v0.17.0.sh`.
25. **v0.17.0 → v0.18.0** — map-lane release. New files: the `espalier-map`
lane skill (multi-session planning — decision maps under `espalier/maps/`)
+ symlinks on every platform in `espalier/.platforms`, and the
`map-guard.sh` plan-don't-do PreToolUse hook. Refreshes `pipeline.md`, the
espalier + espalier-grill SKILL files (grill gains `mode=decision`) with
backup-on-diff (`<file>.pre-v0.18.bak`). Wiring: settings.json gains the
map-guard PreToolUse entry (additive), `.codex/config.toml` the
marker-guarded `ESPALIER MAP GUARD v1` block, the copilot gates json a
targeted map-guard insert (never clobbered). Config: appends
`max-open-tickets: 9`. One grep-guarded `/espalier-map` line lands in each
platform instruction file's existing `## Espalier` section. Mechanical:
`scripts/migrate-v0.17.0-to-v0.18.0.sh`.
26. **v0.18.0 → v0.19.0** — run-lane release. New files: the `espalier-maprun`
lane skill (batch executor for a CLEARED map — interactive master +
headless stage-1–6 workers in isolated push-blocked worktrees) + symlinks
on every platform in `espalier/.platforms`, and the run engine
`espalier/hooks/maprun.py` + `maprun-{dispatch,merge,integration,verify}.sh`.
Engine files are WRITE-IF-ABSENT — a repo already carrying a
locally-adapted engine keeps it. Refreshes `pipeline.md` and the
espalier-map SKILL (handoff now names the batch executor) with
backup-on-diff (`<file>.pre-v0.19.bak`). One grep-guarded `/espalier-maprun`
line lands in each platform instruction file's existing `## Espalier`
section. No config keys, no hook wiring. Mechanical:
`scripts/migrate-v0.18.0-to-v0.19.0.sh`.
27. **v0.19.0 → v0.20.0** — slice-PR release. Run-lane topology unchanged;
the lane gains an OPT-IN forge surface (a `pr` key in a map's plan.json):
one reviewable slice PR per ticket, opened before the local merge and
auto-closed as merged when the integration branch is pushed, plus a final
assembly PR. New engine file `espalier/hooks/maprun-pr.sh`
(WRITE-IF-ABSENT). Refreshes the espalier-maprun SKILL with backup-on-diff
(`SKILL.md.pre-v0.20.bak`). Bug fix: patches
`maprun-integration.sh`'s push block to `--worktree` scope (the stock v0.19
lines poisoned the SHARED `.git/config`, blocking the operator's own
pushes), heals an already-poisoned shared `remote.origin.pushurl`, and
re-blocks existing `_integration` worktrees worktree-scoped. No config
keys, no hook wiring, no new symlinks. Mechanical:
`scripts/migrate-v0.19.0-to-v0.20.0.sh`.
28. **v0.20.0 → v0.21.0** — pipeline-speed release. Same gates, round caps,
and review contract; the run just reads (and waits) less. Refreshes 4
pure-copy files from templates with backup-on-diff (`<file>.pre-v0.21.bak`):
`pipeline.md` (delta-scope doctrine, parallel-dispatch constraint,
pre-authorized push checkpoint) and the espalier / espalier-fix /
espalier-grill SKILLs (context-pack assembly at Stage 3 entry, spawn
prompts carrying it, push-target question at the approval gate, grill
light-tier independent-question batching). Anchored inserts into the 3
per-project agent files (substituted at init, so never overwritten): the
coder's context-pack step 0, the reviewer's step 0 + delta read-scope
paragraph, the security agent's step 0 + delta-mode paragraph — a
customised agent file missing its anchor is skipped with a
`espalier/.migrations-skipped` record and manual instructions, never
mangled. No config keys, no hook wiring, no new symlinks. Mechanical:
`scripts/migrate-v0.20.0-to-v0.21.0.sh`.
29. **v0.21.0 → v0.21.1** — comment-brevity polish patch (field feedback:
generated comments ran too long). Anchored edits to 3 per-project files:
the coder gains a "Comments: SHORT, clear, and few" constraint bullet,
`coding-standards.md` a "Keep comments SHORT" bullet under Comments &
Docstrings, and the reviewer's Readability `comments:` tag now also flags
an OVERLONG comment. A documented project convention requiring fuller
docstrings outranks the new default. Customised files are skipped with a
`.migrations-skipped` record, never mangled; backups at
`<file>.pre-v0.21.1.bak`. Mechanical: `scripts/migrate-v0.21.0-to-v0.21.1.sh`.
30. **v0.21.1 → v0.22.0** — second pipeline-speed release. Same gates,
sentinels, round caps, certificates, and human checkpoints; the changes
are dispatch-order, read-scope, and wait-scheduling only. Refreshes 4
pure-copy files with backup-on-diff (`<file>.pre-v0.22.bak`):
`pipeline.md` (speculative Stage 5 trigger, Stage 6 delta note, CI wait
protocol, Deploy-Target checkpoint + unattended posture) and the
espalier / espalier-fix SKILLs (3-agent round-1 dispatch, Stage 4→5
handoff with quarantine-on-FAIL, pre-flight fold, deploy pre-auth
question, concurrent build+lint gate, one-invocation Stage 7
bookkeeping) plus `espalier/hooks/espalier-stats.sh` (stage-duration
report). Anchored edits into the 4 SUBSTITUTED per-project files: the
coder's Speculative/Contract entry points, the reviewer's and the
security agent's in-flight exclusion paragraphs, and
`pre-push-gate.sh`'s opt-in parallel gate sections + dependency-audit
cache (span-splice; `bash -n` verified after) + its ANCHORED
certificate read (a Stage History note quoting `Base-Ref:` /
`Reviewed-Diff:` in prose must never outrank the real Status-block
line — a field-found false-BLOCK) — customised files are
skipped with a `.migrations-skipped` record, never mangled. Appends
`espalier/.dep-audit-cache` to `.gitignore`. Writes NO config keys:
`speculative-tests` defaults on when absent; `hook-parallel-gates`
absent = serial (init-time opt-in only). Mechanical:
`scripts/migrate-v0.21.1-to-v0.22.0.sh`.
31. **v0.22.0 → v0.22.1** — comment-diet polish patch (field feedback:
agents still write too MANY comment lines, not just long ones). Anchored
edits to 3 per-project files: the coder's Comments constraint becomes
default-NO-comment with a delete-before-reporting re-scan (no banners,
no signature doc-blocks, no narration), `coding-standards.md` gains a
"Default to NO comment" bullet, and the reviewer's Readability
`comments:` tag now also flags EXCESS comments naming the exact lines
to delete. A documented project convention mandating fuller docs still
outranks the default. Customised files are skipped with a
`.migrations-skipped` record; backups at `<file>.pre-v0.22.1.bak`.
Mechanical: `scripts/migrate-v0.22.0-to-v0.22.1.sh`.
32. **v0.22.1 → v0.23.0** — round-economy release (the Stage 3/5 fold). Same
gates, sentinels, caps, certificates, and human checkpoints; the dispatch
restructures so tests ride the coder: under `test-mode: folded` (the
default) the Stage 3 exit gate runs build+lint+scoped tests every coder
return, the 2-agent Stage 4 panel reviews code AND tests in one verdict,
and Stages 5/6 are the security-contract phase (contract spawn → delta
review) or SKIPPED rows — stage numbers stay 3→4→5→6→7 for the push
gate, stats, and maprun parsers. Contract-FAIL fixes touching non-test
files route back to a FULL panel round. Fix lane: regression
verification joins the Stage 3 exit gate (`REGRESSION_VERIFIED_SCOPE`
skip hash, last-line-wins); scope detectors count non-test files only.
Findings digest: FAIL rounds snapshot bracketed finding lines; charted
changes write `espalier/maps/{slug}/findings/` files (Completion, or
the maprun worker before PASSED); adoption folds the newest 12 P0/P1
lines. Map handoff gains the crispness gate (grill `mode=score`;
CLEARED flips LAST); grill verdicts carry tiers
(`GRILLED (light)`/`(full)`). Readable-by-default coder guidance: the
"Write It Readable" section (named constants over magic values —
never inlined, declaration-comment when the name can't carry the
meaning — intent names, guard clauses, small functions, comments
last) plus coding-standards' "Readable by Default"; the reviewer's
Readability Review gains `structure:` and the magic-constant
declaration-comment check. Refreshes 11
pure-copy files (backups `.pre-v0.23.bak`, incl. the mawk-safe
rebuild-commit-index squash extraction) and anchored-edits
coder/reviewer/security, the 3
rules files, the espalier-coding/espalier-testing SKILLs, and
`pre-push-gate.sh` (anchored `Current Stage:` read + last-match test
count); customised files skip-with-record. Writes NO config keys —
`test-mode` absent = folded, legacy `speculative-tests` honored at
read time (`off` → serial). Mechanical:
`scripts/migrate-v0.22.1-to-v0.23.0.sh`.
33. **v0.23.0 → v0.23.1** — class-sweep patch. Field data (48 changes on a
real v0.23.0 install): most second/third Stage 4 rounds re-found the
SAME defect one hop from the line just fixed — a full 2-agent round per
hop. A fix round now closes the CLASS: on a `FIX ROUND {n}:` prompt the
coder names the violated property, enumerates every sibling inside the
change's scope (touched layers + the generated surfaces they feed),
fixes each on its own read, and reports one `### Class Sweep` block per
P0/P1 (out-of-scope siblings listed for a follow-up, never a repo-wide
refactor); both panel agents re-verify the sweep independently on the
next round — a gap is a P1 `[class-sweep]`. Refreshes 3 pure copies
(`pipeline.md`, espalier + espalier-fix SKILLs — the re-spawn header);
anchored-inserts the 3 agent sections, extracted from the templates so a
migrated install is byte-identical to a fresh one; customised files
skip-with-record; backups `<file>.pre-v0.23.1.bak`. No config keys.
Mechanical: `scripts/migrate-v0.23.0-to-v0.23.1.sh`.
34. **v0.23.1 → v0.24.0** — simplify-lane release. New pure-copy skill
`espalier-simplify` (`/espalier-simplify [scope]`): an evidence-first
simplification survey of the EXISTING code, method adapted from
`tt-a1i/simplify-codebase` (MIT). Read-only scouts partition the repo by
the discovered layers and hunt accidental complexity (dormant contracts,
split truth, ownerless flexibility, relay layers, feature fossils …);
every candidate carries a proof record (consumer map, cut boundary,
consequence, decisive check, net effect) and is classed HIGH / LEAD /
JUSTIFIED into `espalier/wiki/simplify-survey.md`; selected HIGH cuts are
filed as `Status: FILED` skeletons under `espalier/changes/refactor/`
(`simplify_from:` frontmatter) that `/espalier` adopts and runs through
the normal coder + two-agent panel. The coder gains "Simplification
Changes: Retire the Whole Obligation" (cut the full boundary, never
relocate, a missed consumer is a STOP, residue search, `### Retired
Surface` report); the reviewer gains "Simplification Review" (independent
re-search of every retired name — a live consumer is a P0
`[simplify-consumer]`; residue / relocation / proof P1s; a retired
protected control the requirement never named is a P0); the Stage 3/4
spawn prompts carry a `SIMPLIFICATION CHANGE:` header and a missed
consumer the coder cannot prove dead ABORTS the change (row → LEAD)
instead of looping fix rounds. Completion flags
the docs that still describe retired surface (`mark_stale`, reason
`simplify: …`) for `/espalier-prune`, and points at `/espalier-doctor
--since <survey commit>` after a batch. Retirement maps
(`/espalier-map retire: … (simplify-survey #n)`) carry `simplify_from`
into every slice; `espalier-stats.sh` gains the simplify-lane echo
(simplify-filed vs hand-written refactors, withdrawn cuts, tags);
`/espalier-ask` reads the survey page as a `why` source. Installs the
skill + symlinks on every wired platform; refreshes 5 pure copies
(`pipeline.md`, the espalier / espalier-map / espalier-ask SKILLs,
`espalier-stats.sh` — backups `.pre-v0.24.bak`); anchored-inserts the coder / reviewer
sections and the `agent.md` Config Index row, EXTRACTED from the
templates; adds the instruction-file lane line; customised files
skip-with-record. No config keys, no hooks, no gitignore entries.
Mechanical: `scripts/migrate-v0.23.1-to-v0.24.0.sh`.
+ 35. **v0.24.0 → v0.25.0** — quality-first context release. Every gate,
+ rubric, sentinel, round cap, and escalation path is contract-equal to
+ v0.24.0; NOTHING sets a budget or refuses on size. The coder gains the
+ Handoff protocol (finish bounded work at a clean point — build green,
+ report written fresh with a `## Handoff` block whose Facts carry
+ `path:line`, sentinel `- HANDOFF: true`; the orchestrator archives the
+ report and re-spawns a fresh coder with a `CONTINUATION:` line — the
+ panel still reviews the combined diff once), the Docs clause (edit the
+ false claim in place, one line), read-once / Grep-only / Scoped-docs
+ reading lines, the `- Spec applied:` report line (the reviewer opens the
+ cited section; `[spec-unread]` is an advisory row), report hygiene under
+ overwrite semantics, and the rules reword lines (auto-loaded on Claude
+ Code). The reviewer gains the same reading lines, the spec-citation
+ check, one-row advisory findings (no count), the `docs:` tag; the
+ security agent the reading lines + Read-tool evidence. The orchestrator
+ (espalier + fix SKILLs, pure-copy): sentinel → archive (`report_archive`
+ into `coding-log/`, so `coding-report.md` is always the CURRENT spawn's
+ report) → `exit_gate` (build + lint concurrent, scoped tests, from the
+ installed pre-push gate's own `run_*` bodies; exit 2 / 3 = by hand as
+ before) → continuation; the context pack gains `- Facts established:`,
+ `- Grep-only:` (`grep_only_files`, from the new `grep-only-paths:`
+ config key) and `- Scoped docs:` (`scoped_docs`); `contract_extract`
+ writes `security-contract.md` for the Stage 5/6 spawns; a stage-boundary
+ offer after Stage 2 approval and after the Stage 4 PASS (`Continue here`
+ / `Continue in a fresh session`, never unattended) and a `RESUMED` row on
+ resumption; `req_shape_check` prints requirement headings outside the
+ contract set (information only). Requirements are contract + notes
+ (`requirements-notes.md`; the `≤ 5 files` rule becomes a sizing
+ paragraph); the grill's code reads are unlimited (map lane too). Rules:
+ Writing Contract in `.scout-prompts.md`, Removed-rules ledger
+ (`rule_bullets` + `comm`) at the prune gate, doctor `contract drift`
+ line (`contract_drift_lines`), `## Not Precedent` anchor in
+ `engineering-structure.md` refreshed from scout 1.2's `not_precedent`
+ array. `espalier-stats.sh` gains spawn shape + workspace docs;
+ `maprun.py` parses stage names by a literal `### N. ` (the `8.5` heading
+ no longer overwrites stage 8) with fallback labels. Refreshes 15 pure
+ copies (backups `.pre-v0.25.bak`, gitignored as `*.pre-v0.*.bak` from
+ now on); anchored edits EXTRACTED from the templates for the three agent
+ bodies, the testing SKILL, `agent.md`, `engineering-structure.md`
+ (customised files skip-with-record; already-reworded lines no-op);
+ appends `grep-only-paths:`; prints report-only lines (requirement shape
+ per IN_PROGRESS change, rules contract drift) and edits no rule or
+ requirement. The `/espalier` skill becomes a ROUTER: each stage's
+ procedure is a pure-copy file under `espalier/skills/espalier/stages/`
+ (`1-2-requirements`, `3-coding`, `4-panel`, `5-6-contract`,
+ `7-10-delivery`) read at stage entry; `pipeline.md` becomes the stage
+ CONTRACT (trigger / load / gate / output / limit + a Procedure line per
+ stage, eleven headings frozen; the maintenance conflict recipes move to
+ `/espalier-prune`); the agents' mode-only sections (coder Fix Rounds +
+ Simplification Changes; reviewer Re-review Rounds + Simplification Review
+ + Security Abuse-Test Coverage; security Re-review Rounds + Repo-Audit
+ Mode) become heading + one-line pointer in the body and live verbatim in
+ `espalier/agents/modes/{fix-round, simplification, re-review, repo-audit,
+ stage6-abuse-coverage}.md`, named first on the mode prompt lines (`FIX
+ ROUND {n}: read espalier/agents/modes/fix-round.md first …`). Commit
+ Discipline: the coder commits one bounded unit per commit at a clean
+ point (message per `development-process.md` → Commit Conventions,
+ staged by path, never under PARALLEL DISPATCH — the orchestrator commits
+ each part after the wave), lists them under `- Commits:`; Stage 7 commits
+ only what is left, by path, never squashes, and records every commit in
+ `Base-Ref..HEAD`; the reviewer's advisory `commits:` tag; the fix lane's
+ late-escalation revert covers the range; `development-process.md` gains
+ the atomic-commits bullet in its managed block. Validation
+ checks 64 `context-helpers`, 65 `spawn-protocols`, 66 `stage-procedures`;
+ totals 56 / 61 / 66 by platform set.
+ Mechanical: `scripts/migrate-v0.24.0-to-v0.25.0.sh`.
Your job: detect which one(s) apply, locate the scripts, preview, get
- confirmation, apply in order. A v0.1.x install needs ALL THIRTY-FOUR; a v0.3.x
- install needs the last thirty-three; a v0.4.x install needs the last thirty-two; a
- v0.5.0–v0.5.2 install needs the v0.5.3 patch then v0.6 … v0.24.0; a
- v0.5.3–v0.5.x install needs v0.6 … v0.24.0; a v0.6.x install needs
- v0.7 … v0.24.0; a v0.7.x install needs v0.8 … v0.24.0; a v0.8.0 install needs
- v0.8.1 … v0.24.0; a v0.8.1 install needs v0.8.2 … v0.24.0; a v0.8.2 install
- needs v0.9.0 … v0.24.0; a v0.9.0 install needs v0.9.1 … v0.24.0; a v0.9.1
- install needs v0.9.2 … v0.24.0; a v0.9.2 install needs v0.9.3, v0.9.4,
- v0.10.0, v0.11.0, v0.12.0, v0.13.0, v0.13.1, v0.13.2, v0.14.0, v0.15.0, v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a v0.9.3 install
- needs v0.9.4, v0.10.0, v0.11.0, v0.12.0, v0.13.0, v0.13.1, v0.13.2, v0.14.0, v0.15.0, v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a
+ confirmation, apply in order. A v0.1.x install needs ALL THIRTY-FIVE; a v0.3.x
+ install needs the last thirty-four; a v0.4.x install needs the last thirty-three; a
+ v0.5.0–v0.5.2 install needs the v0.5.3 patch then v0.6 … v0.25.0; a
+ v0.5.3–v0.5.x install needs v0.6 … v0.25.0; a v0.6.x install needs
+ v0.7 … v0.24.0; a v0.7.x install needs v0.8 … v0.25.0; a v0.8.0 install needs
+ v0.8.1 … v0.24.0; a v0.8.1 install needs v0.8.2 … v0.25.0; a v0.8.2 install
+ needs v0.9.0 … v0.24.0; a v0.9.0 install needs v0.9.1 … v0.25.0; a v0.9.1
+ install needs v0.9.2 … v0.25.0; a v0.9.2 install needs v0.9.3, v0.9.4,
+ v0.10.0, v0.11.0, v0.12.0, v0.13.0, v0.13.1, v0.13.2, v0.14.0, v0.15.0, v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a v0.9.3 install
+ needs v0.9.4, v0.10.0, v0.11.0, v0.12.0, v0.13.0, v0.13.1, v0.13.2, v0.14.0, v0.15.0, v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a
v0.9.4, v0.9.5, or v0.9.6 install needs v0.10.0, v0.11.0, v0.12.0, v0.13.0,
- v0.13.1, v0.13.2, v0.14.0, v0.15.0, v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a v0.10.0 install needs v0.11.0, v0.12.0, v0.13.0,
- v0.13.1, v0.13.2, v0.14.0, v0.15.0, v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a v0.11.0 install needs v0.12.0, v0.13.0, v0.13.1,
- v0.13.2, v0.14.0, v0.15.0, v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a v0.12.0 install needs v0.13.0, v0.13.1, v0.13.2, v0.14.0, v0.15.0,
- v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a v0.13.0 install needs v0.13.1, v0.13.2, v0.14.0, v0.15.0, v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a v0.13.1
- install needs v0.13.2, v0.14.0, v0.15.0, v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a v0.13.2 install needs v0.14.0,
- v0.15.0, v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a v0.14.0 install needs v0.15.0,
- v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a v0.15.0 install needs v0.16.0, v0.17.0, v0.18.0,
- v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a v0.16.0 install needs v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a
- v0.17.0 install needs v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a v0.18.0 install needs
- v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a v0.19.0 install needs v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a v0.20.0 install needs v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a v0.21.0 install needs v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a v0.21.1 install needs v0.22.0, v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a v0.22.0 install needs v0.22.1, v0.23.0, v0.23.1, then v0.24.0; a v0.22.1 install needs v0.23.0, v0.23.1, then v0.24.0; a v0.23.0 install needs v0.23.1 then v0.24.0; a v0.23.1 install needs only v0.24.0.
+ v0.13.1, v0.13.2, v0.14.0, v0.15.0, v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a v0.10.0 install needs v0.11.0, v0.12.0, v0.13.0,
+ v0.13.1, v0.13.2, v0.14.0, v0.15.0, v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a v0.11.0 install needs v0.12.0, v0.13.0, v0.13.1,
+ v0.13.2, v0.14.0, v0.15.0, v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a v0.12.0 install needs v0.13.0, v0.13.1, v0.13.2, v0.14.0, v0.15.0,
+ v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a v0.13.0 install needs v0.13.1, v0.13.2, v0.14.0, v0.15.0, v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a v0.13.1
+ install needs v0.13.2, v0.14.0, v0.15.0, v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a v0.13.2 install needs v0.14.0,
+ v0.15.0, v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a v0.14.0 install needs v0.15.0,
+ v0.16.0, v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a v0.15.0 install needs v0.16.0, v0.17.0, v0.18.0,
+ v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a v0.16.0 install needs v0.17.0, v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a
+ v0.17.0 install needs v0.18.0, v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a v0.18.0 install needs
+ v0.19.0, v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a v0.19.0 install needs v0.20.0, v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a v0.20.0 install needs v0.21.0, v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a v0.21.0 install needs v0.21.1, v0.22.0, v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a v0.21.1 install needs v0.22.0, v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a v0.22.0 install needs v0.22.1, v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a v0.22.1 install needs v0.23.0, v0.23.1, v0.24.0, then v0.25.0; a v0.23.0 install needs v0.23.1, v0.24.0, then v0.25.0; a v0.23.1 install needs v0.24.0 then v0.25.0; a v0.24.0 install needs only v0.25.0.
Two gaps in the script names are deliberate, not missing steps: there is no
v0.2→v0.3 script because v0.2/v0.3 detection is lumped — the v0.3→v0.4 step
handles both layouts (verified benign). There is no v0.9.4→v0.9.6 script
because v0.9.5 and v0.9.6 were plugin-side releases only — nothing in a target
install changed.
Note: `migrate-v0.9.2-to-v0.9.3.sh` requires the installed `espalier-review`
SKILL to carry a `## Production-Readiness Checks` section. Fresh v0.9.0+ inits
have it from the template, and `migrate-v0.8.2-to-v0.9.0.sh` now appends it to
migrated installs. An install migrated by an older build of that script lacks the
section — re-running the v0.9.0 step (idempotent) adds it, which is why the chain
below never skips ahead.
### Step 0: Migration barrier (ENFORCED pre-flight — before any detection or apply)
Migrations rewrite installed skills and hooks; running one over uncommitted
work or a half-finished pipeline change destroys context. Three checks, all
enforced — not recommendations:
```bash
# 1. Clean working tree — abort otherwise (nothing may be applied over
# uncommitted changes; migrations must be their own commit).
if [ -n "$(git status --porcelain)" ]; then
echo "BARRIER: working tree not clean — commit or stash first, then re-run /espalier-migrate."
fi
# 2. No in-flight pipeline change. A change is in-flight unless its
# `- Status:` matches the terminal-status vocabulary pre-push-gate.sh uses
# (COMPLETE|ABORTED|ABORTED_LATE|ESCALATED|ESCALATED_LATE|FILED). A state
# file with a MISSING or unrecognized status counts as ACTIVE — fail
# closed, exactly like the push gate.
ACTIVE=""
for f in espalier/changes/*/*/pipeline-state.md; do
[ -f "$f" ] || continue
grep -qE '^- Status:[[:space:]]*(COMPLETE|ABORTED|ABORTED_LATE|ESCALATED|ESCALATED_LATE|FILED)\b' "$f" \
|| ACTIVE="$ACTIVE $f"
done
[ -n "$ACTIVE" ] && echo "BARRIER: in-flight change(s):$ACTIVE — finish or abandon first."
# 3. Current branch must equal the canonical branch. Read the key when the
# install has it (v0.16.0+); older installs fall back to the remote HEAD
# detection, then `main`.
B=$(grep '^canonical-branch:' espalier/.espalier-config 2>/dev/null | awk '{print $2}')
if [ -z "$B" ]; then
R=origin; git remote 2>/dev/null | grep -qx upstream && R=upstream
REF=$(git symbolic-ref --short "refs/remotes/$R/HEAD" 2>/dev/null)
B=${REF#"$R"/}; [ -n "$B" ] || B=main
fi
CUR=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
[ "$CUR" = "$B" ] || echo "BARRIER: on branch '$CUR', canonical is '$B'."
```
Any `BARRIER:` line on stdout means STOP:
- Checks 1-2 are hard aborts — surface the line and end the migration run.
- Check 3 may be acknowledged: ask the user via `AskUserQuestion`
("Migrate on branch '{CUR}' instead of the canonical '{B}'? A migration on
a feature branch strands the upgrade until that branch merges and every
other branch keeps the old install."). Proceed ONLY on an explicit
"migrate here anyway" acknowledgment; on an unattended run there is no one
to acknowledge — abort.
### Step 1: Preflight + detect install version
Run from the current working directory (must be the target project root):
```bash
NEEDS_V01_V02=no
NEEDS_V03_V04=no
NEEDS_V04_V05=no
NEEDS_V05_PATCH=no
NEEDS_V05_V06=no
NEEDS_V06_V07=no
NEEDS_V07_V08=no
NEEDS_V08_PATCH=no
NEEDS_V082_PATCH=no
NEEDS_V09_MINOR=no
NEEDS_V091_PATCH=no
NEEDS_V092_PATCH=no
NEEDS_V093_PATCH=no
NEEDS_V094_PATCH=no
NEEDS_V0100_PATCH=no
NEEDS_V0110_PATCH=no
NEEDS_V0120_PATCH=no
NEEDS_V0130_PATCH=no
NEEDS_V0131_PATCH=no
NEEDS_V0132_PATCH=no
NEEDS_V0140_PATCH=no
NEEDS_V0150_PATCH=no
NEEDS_V0160_PATCH=no
NEEDS_V0170_PATCH=no
NEEDS_V0180_PATCH=no
NEEDS_V0190_PATCH=no
NEEDS_V0200_PATCH=no
NEEDS_V0210_PATCH=no
NEEDS_V0211_PATCH=no
NEEDS_V0220_PATCH=no
NEEDS_V0221_PATCH=no
NEEDS_V0230_PATCH=no
NEEDS_V0231_PATCH=no
NEEDS_V0240_PATCH=no
+ NEEDS_V0250_PATCH=no
if [ ! -d "harness" ] && [ ! -d "espalier" ]; then
echo "ERROR: no harness/ or espalier/ dir found — not a target install."
echo "Use /espalier-init to set up Espalier in a fresh project."
exit 1
fi
if [ -d "harness" ]; then
# Pre-rename install. v0.1.x has no .merge-hook-decision; v0.2/v0.3.x has one.
if [ ! -f "harness/.merge-hook-decision" ]; then
NEEDS_V01_V02=yes
fi
NEEDS_V03_V04=yes # harness/ always needs the rename
NEEDS_V04_V05=yes # ...then the doc-drift upgrade
NEEDS_V05_PATCH=yes # ...then the v0.5.3 coder-agent patch
NEEDS_V05_V06=yes # ...then the v0.6 Stage 1 grill
NEEDS_V06_V07=yes # ...then the v0.7 ask lane
NEEDS_V07_V08=yes # ...then the v0.8 approval gate
NEEDS_V08_PATCH=yes # ...then the v0.8.1 impact-analysis agent patch
NEEDS_V082_PATCH=yes # ...then the v0.8.2 re-review fixpoint loop
NEEDS_V09_MINOR=yes # ...then the v0.9.0 security audit
NEEDS_V091_PATCH=yes # ...then the v0.9.1 configurable escalation caps
NEEDS_V092_PATCH=yes # ...then the v0.9.2 correctness patch
NEEDS_V093_PATCH=yes # ...then the v0.9.3 skill-clarity patch
NEEDS_V094_PATCH=yes # ...then the v0.9.4 security-skill patch
NEEDS_V0100_PATCH=yes # ...then the v0.10.0 push-gate reshape
NEEDS_V0110_PATCH=yes # ...then the v0.11.0 hook exit-code release
NEEDS_V0120_PATCH=yes # ...then the v0.12.0 grill blind-spot pass
NEEDS_V0130_PATCH=yes # ...then the v0.13.0 minimalism release
NEEDS_V0131_PATCH=yes # ...then the v0.13.1 minimalism polish patch
NEEDS_V0132_PATCH=yes # ...then the v0.13.2 readability release
NEEDS_V0140_PATCH=yes # ...then the v0.14.0 codex platform release
NEEDS_V0150_PATCH=yes # ...then the v0.15.0 copilot platform release
NEEDS_V0160_PATCH=yes # ...then the v0.16.0 multi-dev maintenance floor
NEEDS_V0170_PATCH=yes # ...then the v0.17.0 gardener/per-key release
NEEDS_V0180_PATCH=yes # ...then the v0.18.0 map-lane release
NEEDS_V0190_PATCH=yes # ...then the v0.19.0 run-lane release
NEEDS_V0200_PATCH=yes # ...then the v0.20.0 slice-PR release
NEEDS_V0210_PATCH=yes # ...then the v0.21.0 pipeline-speed release
NEEDS_V0211_PATCH=yes # ...then the v0.21.1 comment-brevity patch
NEEDS_V0220_PATCH=yes # ...then the v0.22.0 pipeline-speed release
NEEDS_V0221_PATCH=yes # ...then the v0.22.1 comment-diet patch
NEEDS_V0230_PATCH=yes # ...then the v0.23.0 round-economy fold
NEEDS_V0231_PATCH=yes # ...then the v0.23.1 class-sweep patch
NEEDS_V0240_PATCH=yes # ...then the v0.24.0 simplify-lane release
+ NEEDS_V0250_PATCH=yes # ...then the v0.25.0 quality-first context release
elif [ -d "espalier" ]; then
# Already renamed. v0.4.x still needs the doc-drift upgrade.
if [ ! -f "espalier/hooks/drift-detect.sh" ] || [ ! -f "espalier/.doctor-cadence" ]; then
NEEDS_V04_V05=yes # v0.4.x → doc-drift upgrade
fi
# v0.5.3: harness-coder.md gains an "## Editing Discipline" section. It is a
# per-project file, so a plugin update never reaches an existing install.
if ! grep -qF "## Editing Discipline" espalier/agents/harness-coder.md 2>/dev/null; then
NEEDS_V05_PATCH=yes
fi
# v0.6.0: the espalier-grill skill is new AND must be wired into Stage 1 of
# espalier-requirements. Absence of either signals a pre-v0.6 install.
if [ ! -f "espalier/skills/espalier-grill/SKILL.md" ] \
|| ! grep -q "Grill the requirement" espalier/skills/espalier-requirements/SKILL.md 2>/dev/null; then
NEEDS_V05_V06=yes
fi
# v0.7.0: the espalier-ask skill is new. Its absence signals a pre-v0.7
# install (independent of grill — migrations run in order, so v0.6 installs
# grill before v0.7 installs ask).
if [ ! -f "espalier/skills/espalier-ask/SKILL.md" ]; then
NEEDS_V06_V07=yes
fi
# v0.8.0: both pipelines gain a requirements approval gate. It is wired into
# the pure-copy espalier skill; absence of the gate text signals pre-v0.8.
if ! grep -q "Requirements Approval Gate" espalier/skills/espalier/SKILL.md 2>/dev/null; then
NEEDS_V07_V08=yes
fi
# v0.8.1: the two sub-agents gain change-impact / runtime-surface guidance.
# Per-project files, so a plugin update never reaches an existing install.
# Either section missing signals a pre-v0.8.1 install.
if ! grep -qF "## Change Impact Analysis" espalier/agents/harness-coder.md 2>/dev/null \
|| ! grep -qF "## Runtime-Surface Review" espalier/agents/harness-reviewer.md 2>/dev/null; then
NEEDS_V08_PATCH=yes
fi
# v0.8.2: code review becomes a fixpoint loop + push-gate certificate. The loop
# rides the pure-copy pipeline files; the reviewer gains a Re-review Rounds
# section and the push gate gains a Reviewed-Diff certificate (both per-project
# files a plugin update cannot reach). Any of the three missing ⇒ pre-v0.8.2.
if ! grep -qF "fixpoint loop" espalier/pipeline.md 2>/dev/null \
|| ! grep -qF "## Re-review Rounds" espalier/agents/harness-reviewer.md 2>/dev/null \
|| ! grep -qF "Reviewed-Diff:" espalier/hooks/pre-push-gate.sh 2>/dev/null; then
NEEDS_V082_PATCH=yes
fi
# v0.9.0: the security audit. A new harness-security agent joins Stage 4 as a
# review panel; a security-standards rule + espalier-security skill are added and
# the coder/reviewer/gate gain security sections. v0.9.0 also ships the
# /espalier-audit repo-wide lane (espalier-audit skill + a Repo-Audit Mode
# section in the security agent). The "review panel" text rides the pure-copy
# pipeline; harness-security.md is a new per-project file a plugin update
# cannot reach — any absent ⇒ pre-v0.9.0 (or a pre-fold v0.9.0 the script
# completes idempotently).
#
# The espalier-review SKILL's "## Production-Readiness Checks" is in this set on
# purpose. An install migrated by an older build of migrate-v0.8.2-to-v0.9.0.sh
# never received it (that script declared the path but never wrote to it), and
# migrate-v0.9.2-to-v0.9.3.sh dies without it. Flagging it here re-runs the
# (idempotent) v0.9.0 step, which appends the section before v0.9.3 needs it.
if ! grep -qF "review panel" espalier/pipeline.md 2>/dev/null \
|| [ ! -f espalier/agents/harness-security.md ] \
|| [ ! -f espalier/skills/espalier-audit/SKILL.md ] \
|| ! grep -qF "## Repo-Audit Mode" espalier/agents/harness-security.md 2>/dev/null \
|| [ ! -f espalier/rules/production-standards.md ] \
|| ! grep -qF "## Production-Aware Coding" espalier/agents/harness-coder.md 2>/dev/null \
|| ! grep -qF "## Production-Readiness Checks" espalier/skills/espalier-review/SKILL.md 2>/dev/null \
|| ! grep -qF "cd defensively too" espalier/hooks/pre-push-gate.sh 2>/dev/null; then
NEEDS_V09_MINOR=yes
fi
# v0.9.1: the review-round + rollback caps become configurable via a tracked
# espalier/.espalier-config, and the pure-copy pipeline prose is refreshed to
# read it. A missing config file OR a pipeline.md that still hardcodes the limit
# (no "max-code-rounds" reference) ⇒ pre-v0.9.1.
if [ ! -f espalier/.espalier-config ] \
|| ! grep -qF "max-code-rounds" espalier/pipeline.md 2>/dev/null; then
NEEDS_V091_PATCH=yes
fi
# v0.9.2: correctness patch. Pre-v0.9.2 signals: the fix lane still calls
# the never-shipped _fire_late_escalation_prompt helper (pure-copy file), OR
# the per-project push gate still selects by mtime alone (no active-change
# text), OR the shipped scout-prompts lacks scout 1.11. Any ⇒ needed.
if grep -qF "_fire_late_escalation_prompt" espalier/skills/espalier-fix/SKILL.md 2>/dev/null \
|| ! grep -qF "Find the ACTIVE change" espalier/hooks/pre-push-gate.sh 2>/dev/null \
|| ! grep -qF "Scout 1.11" espalier/.scout-prompts.md 2>/dev/null; then
NEEDS_V092_PATCH=yes
fi
# v0.9.3: skill-clarity patch. Same two markers migrate-v0.9.2-to-v0.9.3.sh uses
# for its own idempotency check — keep them in sync with that script:
# grill → "Coverage guard (before returning"
# review → "SINGLE SOURCE for these checks"
# Either missing ⇒ pre-v0.9.3 (or a half-applied run the script finishes).
if ! grep -qF "Coverage guard (before returning" espalier/skills/espalier-grill/SKILL.md 2>/dev/null \
|| ! grep -qF "SINGLE SOURCE for these checks" espalier/skills/espalier-review/SKILL.md 2>/dev/null; then
NEEDS_V093_PATCH=yes
fi
# v0.9.4: security-skill patch. Same two markers migrate-v0.9.3-to-v0.9.4.sh uses
# for its own idempotency check — keep them in sync with that script:
# harness-security AGENT → "self-noops on changes with no sensitive surface"
# espalier-security SKILL → "abuse-test recipe for"
# Both are per-project files a plugin update cannot reach. Either missing ⇒
# pre-v0.9.4. Guarded on the files existing: a pre-v0.9.0 install has neither,
# and the v0.9.0 step creates them before this step runs.
if [ -f espalier/agents/harness-security.md ] && [ -f espalier/skills/espalier-security/SKILL.md ]; then
if ! grep -qF "self-noops on changes with no sensitive surface" espalier/agents/harness-security.md 2>/dev/null \
|| ! grep -qF "abuse-test recipe for" espalier/skills/espalier-security/SKILL.md 2>/dev/null; then
NEEDS_V094_PATCH=yes
fi
else
NEEDS_V094_PATCH=yes # v0.9.0 will create them; v0.9.4 then sharpens them
fi
# v0.10.0: the gate's three check steps become run_build/run_lint/run_tests, so a
# {..._command} may be a multi-line block, and build/lint stop discarding stderr.
# ALL THREE functions must be present — running the v0.9.2 step against a v0.10.0
# plugin re-splices only the test span, leaving run_tests with old build/lint.
# A gate customised past the template shape is detected here too; the script
# recognises it and exits 0 without touching it.
# A `v0.10.0-gate:` marker in espalier/.migrations-skipped means the script
# already declined this customised gate once — treat the version as handled
# (manual): report it, do not re-propose the patch every run.
if grep -qF 'v0.10.0-gate:' espalier/.migrations-skipped 2>/dev/null; then
NEEDS_V0100_PATCH=no
elif [ -f espalier/hooks/pre-push-gate.sh ] && { \
! grep -qF 'run_build() {' espalier/hooks/pre-push-gate.sh 2>/dev/null \
|| ! grep -qF 'run_lint() {' espalier/hooks/pre-push-gate.sh 2>/dev/null \
|| ! grep -qF 'run_tests() {' espalier/hooks/pre-push-gate.sh 2>/dev/null; }; then
NEEDS_V0100_PATCH=yes
fi
# v0.11.0: hooks must block with exit code 2 (exit 1 is a silent no-op in
# Claude Code) and the Stage 4/6 gates must read the verdict WORD. The gate
# marker is the v0.11 header comment — NOT a bare `exit 2` grep: a gate
# chain-migrated by the v0.9.2/v0.10.0 span splices already carries exit-2
# spans while its stage/cert/secret sections still exit 1. Same E5 rule: a
# `v0.11.0-gate:` marker means the customised gate was handled manually —
# the script itself skips the gate step on it, so re-running here only
# refreshes the skill/agent side.
if ! grep -qF 'Exit-code contract (Claude Code PreToolUse semantics)' espalier/hooks/pre-push-gate.sh 2>/dev/null \
|| ! grep -qF 'Advance ONLY when EVERY record' espalier/skills/espalier/SKILL.md 2>/dev/null; then
NEEDS_V0110_PATCH=yes
fi
if grep -qF 'v0.11.0-gate:' espalier/.migrations-skipped 2>/dev/null \
&& grep -qF 'Advance ONLY when EVERY record' espalier/skills/espalier/SKILL.md 2>/dev/null; then
NEEDS_V0110_PATCH=no # handled (manual) — reported once, not re-proposed
fi
# v0.12.0: the grill skill gains Step 1.5 (the rules/wiki blind-spot pass). It
# is a pure-copy pipeline file, so a plugin update never reaches an existing
# install — absence of Step 1.5 in the installed grill skill signals pre-v0.12.
if ! grep -qF 'Step 1.5 — Blind-spot pass' espalier/skills/espalier-grill/SKILL.md 2>/dev/null; then
NEEDS_V0120_PATCH=yes
fi
# v0.13.0: the coder gains the Solution Selection Ladder and the reviewer the
# advisory Minimalism Review. All four touched files are per-project files a
# plugin update cannot reach — any of the four markers missing ⇒ pre-v0.13.
# Keep these in sync with migrate-v0.12.0-to-v0.13.0.sh's own idempotency check.
if ! grep -qF '## Solution Selection Ladder' espalier/agents/harness-coder.md 2>/dev/null \
|| ! grep -qF '## Minimalism Review' espalier/agents/harness-reviewer.md 2>/dev/null \
|| ! grep -qF -- '## Solution Selection (keep it lean)' espalier/skills/espalier-coding/SKILL.md 2>/dev/null \
|| ! grep -qF -- 'Gate (pass condition)' espalier/skills/espalier-review/SKILL.md 2>/dev/null; then
NEEDS_V0130_PATCH=yes
fi
# v0.13.1: post-release polish on v0.13.0. The coding skill gains the
# By-Stage section, the reviewer's Review Process gains the Stage 6
# abuse-test step (findings renumber to 8), and the review skill's
# code-review loop gains its Gate line. All three are per-project files a
# plugin update cannot reach — any marker missing ⇒ pre-v0.13.1. Keep these
# in sync with migrate-v0.13.0-to-v0.13.1.sh's own idempotency check.
if ! grep -qF -- '## How This Skill Applies by Stage' espalier/skills/espalier-coding/SKILL.md 2>/dev/null \
|| ! grep -qF -- 'run the **Security Abuse-Test Coverage**' espalier/agents/harness-reviewer.md 2>/dev/null \
|| ! grep -qF -- 'max-code-rounds' espalier/skills/espalier-review/SKILL.md 2>/dev/null; then
NEEDS_V0131_PATCH=yes
fi
# v0.13.2: the readability release. The coder ladder tie-break becomes
# clarity-then-brevity, coding-standards gains the naming-intent floor +
# Comments & Docstrings, and the reviewer gains the advisory Readability
# Review (ONE P1: a cryptic EXPORTED/public name). All touched files are
# per-project files a plugin update cannot reach — any marker missing ⇒
# pre-v0.13.2. Keep these in sync with migrate-v0.13.1-to-v0.13.2.sh's own
# idempotency check.
if ! grep -qF -- 'clarity then brevity break ties' espalier/agents/harness-coder.md 2>/dev/null \
|| ! grep -qF -- '## Readability Review' espalier/agents/harness-reviewer.md 2>/dev/null \
|| ! grep -qF -- '## Comments & Docstrings' espalier/rules/coding-standards.md 2>/dev/null \
|| ! grep -qF -- 'clarity then brevity break ties' espalier/skills/espalier-coding/SKILL.md 2>/dev/null \
|| ! grep -qF -- 'Readability: names state intent' espalier/skills/espalier-review/SKILL.md 2>/dev/null; then
NEEDS_V0132_PATCH=yes
fi
# v0.14.0: the codex platform release. The two plugin-owned hook wrappers
# become platform-neutral (Codex apply_patch parsing; argv-array join). Any
# marker missing ⇒ pre-v0.14.0. Keep in sync with
# migrate-v0.13.2-to-v0.14.0.sh's own idempotency check. (Codex WIRING is
# opt-in and never a detection signal — a claude-only install is complete.)
if ! grep -qF -- 'apply_patch' espalier/hooks/post-edit-wrapper.sh 2>/dev/null \
|| ! grep -qF -- 'isinstance(c, list)' espalier/hooks/pre-push-gate-wrapper.sh 2>/dev/null; then
NEEDS_V0140_PATCH=yes
fi
# v0.15.0: the copilot platform release. The always-delta is one NEW
# plugin-owned file — the camelCase hook shim. Keep in sync with
# migrate-v0.14.0-to-v0.15.0.sh's own idempotency check. (Copilot WIRING is
# opt-in and never a detection signal.)
if [ ! -f espalier/hooks/copilot-hook-adapter.sh ]; then
NEEDS_V0150_PATCH=yes
fi
# v0.16.0: the multi-dev maintenance floor. Detection mirrors the script's
# own all-marker check (a --force bootstrap from a newer plugin can
# pre-install the config/attribute artifacts, so a subset detector would
# skip the still-missing surgical rule edit — keep in sync with
# migrate-v0.15.0-to-v0.16.0.sh). Any marker missing ⇒ pre-v0.16.0.
if ! grep -qxF 'espalier/.ask-gaps.tsv merge=union' .gitattributes 2>/dev/null \
|| ! grep -qE '^canonical-remote: .+' espalier/.espalier-config 2>/dev/null \
|| ! grep -qE '^canonical-branch: .+' espalier/.espalier-config 2>/dev/null \
|| ! grep -qF 'ESPALIER MAINTENANCE COMMITS v1' espalier/rules/development-process.md 2>/dev/null \
|| ! grep -qF 'Multi-Developer Maintenance' espalier/pipeline.md 2>/dev/null \
|| ! grep -qF 'Multi-Developer Discipline' espalier/skills/espalier-prune/SKILL.md 2>/dev/null \
|| ! grep -qF 'Multi-Developer Discipline' espalier/skills/espalier-doctor/SKILL.md 2>/dev/null \
|| ! grep -qF 'conv_fold' espalier/skills/espalier/SKILL.md 2>/dev/null \
|| ! grep -qF 'conv_fold' espalier/skills/espalier-fix/SKILL.md 2>/dev/null \
|| ! grep -qF 'conv_fold' espalier/hooks/drift-helpers.sh 2>/dev/null; then
NEEDS_V0160_PATCH=yes
fi
# v0.17.0: the B-team release rides pure-copy files only. Keep these
# markers in sync with migrate-v0.16.0-to-v0.17.0.sh's own idempotency
# check. Any missing ⇒ pre-v0.17.0.
if ! grep -qF 'doctor_stamp_shared' espalier/hooks/drift-helpers.sh 2>/dev/null \
|| ! grep -qF 'doctor_stamp_shared' espalier/skills/espalier-doctor/SKILL.md 2>/dev/null \
|| ! grep -qiF 'gardener' espalier/skills/espalier-prune/SKILL.md 2>/dev/null \
|| ! grep -qiF 'gardener rota' espalier/skills/espalier/SKILL.md 2>/dev/null \
|| ! grep -qiF 'gardener rota' espalier/skills/espalier-fix/SKILL.md 2>/dev/null \
|| ! grep -qiF 'keep both lines' espalier/pipeline.md 2>/dev/null; then
NEEDS_V0170_PATCH=yes
fi
# v0.18.0: the map-lane release. Keep these markers in sync with
# migrate-v0.17.0-to-v0.18.0.sh's own idempotency check (platform-scoped
# wiring markers live in the script — the core file markers suffice for
# detection here). Any missing ⇒ pre-v0.18.0.
if [ ! -f espalier/skills/espalier-map/SKILL.md ] \
|| [ ! -x espalier/hooks/map-guard.sh ] \
|| ! grep -qF 'mode=decision' espalier/skills/espalier-grill/SKILL.md 2>/dev/null \
|| ! grep -qF 'charted_from' espalier/skills/espalier/SKILL.md 2>/dev/null \
|| ! grep -qF '/espalier-map' espalier/pipeline.md 2>/dev/null \
|| ! grep -q '^max-open-tickets:' espalier/.espalier-config 2>/dev/null; then
NEEDS_V0180_PATCH=yes
fi
# v0.19.0: the run-lane release. Keep these markers in sync with
# migrate-v0.18.0-to-v0.19.0.sh's own idempotency check (the script itself
# rechecks the full engine-file set). Any missing ⇒ pre-v0.19.0.
if [ ! -f espalier/skills/espalier-maprun/SKILL.md ] \
|| [ ! -f espalier/hooks/maprun.py ] \
|| ! grep -qF '/espalier-maprun' espalier/pipeline.md 2>/dev/null \
|| ! grep -qF 'espalier-maprun' espalier/skills/espalier-map/SKILL.md 2>/dev/null; then
NEEDS_V0190_PATCH=yes
fi
# v0.20.0: the slice-PR release. Keep in sync with
# migrate-v0.19.0-to-v0.20.0.sh's own idempotency check.
if [ ! -f espalier/hooks/maprun-pr.sh ] \
|| ! grep -qF 'maprun-pr.sh' espalier/skills/espalier-maprun/SKILL.md 2>/dev/null; then
NEEDS_V0200_PATCH=yes
fi
# v0.21.0: the pipeline-speed release. Keep in sync with
# migrate-v0.20.0-to-v0.21.0.sh's own idempotency check. An agent marker
# counts as handled when present OR when the script recorded a customised
# skip for it in espalier/.migrations-skipped (same E5 rule as v0.10/v0.11:
# a declined customised file is handled-manual, never re-proposed).
ok21() { grep -qF "$1" "$2" 2>/dev/null \
- || grep -qF "v0.21.0-$3" espalier/.migrations-skipped 2>/dev/null; }
+ || grep -qsF "$1" espalier/agents/modes/*.md 2>/dev/null \
+ || grep -qF "v0.21.0-$3" espalier/.migrations-skipped 2>/dev/null; } # v0.25+: mode text lives in espalier/agents/modes/
if ! grep -qF 'DELTA SCOPE' espalier/pipeline.md 2>/dev/null \
|| ! grep -qF 'Stage 3 Entry: Context Pack' espalier/skills/espalier/SKILL.md 2>/dev/null \
|| ! grep -qF 'context-pack.md' espalier/skills/espalier-fix/SKILL.md 2>/dev/null \
|| ! grep -qF 'pairwise INDEPENDENT' espalier/skills/espalier-grill/SKILL.md 2>/dev/null \
|| ! ok21 'CONTEXT PACK' espalier/agents/harness-coder.md coder-context-pack \
|| ! ok21 'names a CONTEXT PACK' espalier/agents/harness-reviewer.md reviewer-context-pack \
|| ! ok21 'Delta read scope' espalier/agents/harness-reviewer.md reviewer-delta-scope \
|| ! ok21 'names a CONTEXT PACK' espalier/agents/harness-security.md security-context-pack \
|| ! ok21 'Delta mode (when YOUR prior round was clean)' espalier/agents/harness-security.md security-delta-mode; then
NEEDS_V0210_PATCH=yes
fi
# v0.21.1: the comment-brevity patch. Keep in sync with
# migrate-v0.21.0-to-v0.21.1.sh's own idempotency check; same
# skip-record-counts-as-handled rule (labels are v0.21.1-*).
ok211() { grep -qF "$1" "$2" 2>/dev/null \
|| grep -qF "v0.21.1-$3" espalier/.migrations-skipped 2>/dev/null; }
if ! ok211 'Comments: SHORT' espalier/agents/harness-coder.md coder-comment-brevity \
|| ! ok211 'OVERLONG comment' espalier/agents/harness-reviewer.md reviewer-comment-brevity \
|| ! ok211 'Keep comments SHORT' espalier/rules/coding-standards.md standards-comment-brevity; then
NEEDS_V0211_PATCH=yes
fi
# v0.22.0: the second pipeline-speed release. Keep in sync with
# migrate-v0.21.1-to-v0.22.0.sh's own idempotency check; same
# skip-record-counts-as-handled rule (labels are v0.22.0-*).
ok220() { grep -qF "$1" "$2" 2>/dev/null \
|| grep -qF "v0.22.0-$3" espalier/.migrations-skipped 2>/dev/null; }
if ! grep -qF 'dispatched SPECULATIVELY' espalier/pipeline.md 2>/dev/null \
- || ! grep -qF 'Stage 4 → Stage 5 handoff' espalier/skills/espalier/SKILL.md 2>/dev/null \
+ || ! { grep -qF 'Stage 4 → Stage 5 handoff' espalier/skills/espalier/SKILL.md 2>/dev/null \
+ || grep -qF 'Stage 5/6 (folded)' espalier/skills/espalier/SKILL.md 2>/dev/null; } \
|| ! grep -qF 'coding-report.part-test.md' espalier/skills/espalier-fix/SKILL.md 2>/dev/null \
|| ! grep -qF 'Stage durations' espalier/hooks/espalier-stats.sh 2>/dev/null \
|| ! ok220 'Speculative & Contract entry points' espalier/agents/harness-coder.md coder-speculative \
|| ! ok220 'SPECULATIVE TESTS IN FLIGHT' espalier/agents/harness-reviewer.md reviewer-inflight \
|| ! ok220 'SPECULATIVE TESTS IN FLIGHT' espalier/agents/harness-security.md security-inflight \
|| ! ok220 'hook-parallel-gates' espalier/hooks/pre-push-gate.sh hook-parallel \
|| ! ok220 'dep-audit-cache' espalier/hooks/pre-push-gate.sh hook-audit-cache \
|| ! ok220 '(- )?Reviewed-Diff:' espalier/hooks/pre-push-gate.sh hook-cert-anchor; then
NEEDS_V0220_PATCH=yes
fi
# v0.22.1: the comment-diet patch. Keep in sync with
# migrate-v0.22.0-to-v0.22.1.sh's own idempotency check; same
# skip-record-counts-as-handled rule (labels are v0.22.1-*).
ok2211() { grep -qF "$1" "$2" 2>/dev/null \
|| grep -qF "v0.22.1-$3" espalier/.migrations-skipped 2>/dev/null; }
if ! ok2211 'the default is NO comment' espalier/agents/harness-coder.md coder-comment-diet \
|| ! ok2211 'Default to NO comment' espalier/rules/coding-standards.md standards-comment-diet \
|| ! ok2211 'EXCESS comments' espalier/agents/harness-reviewer.md reviewer-excess-tag; then
NEEDS_V0221_PATCH=yes
fi
# v0.23.0: the round-economy fold. Keep in sync with
# migrate-v0.22.1-to-v0.23.0.sh's own idempotency check; same
# skip-record-counts-as-handled rule (labels are v0.23.0-*).
ok230() { grep -qF "$1" "$2" 2>/dev/null \
|| grep -qF "v0.23.0-$3" espalier/.migrations-skipped 2>/dev/null; }
if ! grep -qF 'Contract Phase (folded)' espalier/pipeline.md 2>/dev/null \
|| ! grep -qF 'Stage 5/6 (folded)' espalier/skills/espalier/SKILL.md 2>/dev/null \
|| ! grep -qF 'REGRESSION_VERIFIED_SCOPE' espalier/skills/espalier-fix/SKILL.md 2>/dev/null \
|| ! grep -qF 'mode=score' espalier/skills/espalier-grill/SKILL.md 2>/dev/null \
|| ! grep -qF 'untiered' espalier/hooks/espalier-stats.sh 2>/dev/null \
|| ! ok230 'Contract entry point (post-panel dispatch mode)' espalier/agents/harness-coder.md coder-fold \
|| ! ok230 '## Test Review (folded Stage 4 / serial Stage 6)' espalier/agents/harness-reviewer.md reviewer-fold \
|| ! ok230 'fixture-data leakage' espalier/agents/harness-security.md security-fold \
|| ! ok230 '(- )?Current Stage:' espalier/hooks/pre-push-gate.sh gate-fold; then
NEEDS_V0230_PATCH=yes
fi
# v0.23.1: the class-sweep patch. Keep in sync with
# migrate-v0.23.0-to-v0.23.1.sh's own idempotency check; same
# skip-record-counts-as-handled rule (labels are v0.23.1-*).
ok231() { grep -qF "$1" "$2" 2>/dev/null \
- || grep -qF "v0.23.1-$3" espalier/.migrations-skipped 2>/dev/null; }
+ || grep -qsF "$1" espalier/agents/modes/*.md 2>/dev/null \
+ || grep -qF "v0.23.1-$3" espalier/.migrations-skipped 2>/dev/null; } # v0.25+: mode text lives in espalier/agents/modes/
if ! grep -qF 'FIX ROUND {n}:' espalier/pipeline.md 2>/dev/null \
|| ! grep -qF 'FIX ROUND {n}:' espalier/skills/espalier/SKILL.md 2>/dev/null \
|| ! grep -qF 'FIX ROUND {n}:' espalier/skills/espalier-fix/SKILL.md 2>/dev/null \
|| ! ok231 '## Fix Rounds: Fix the Class, Not the Instance' espalier/agents/harness-coder.md coder-class-sweep \
|| ! ok231 '**Class-sweep verification.**' espalier/agents/harness-reviewer.md reviewer-class-sweep \
|| ! ok231 '**Class-sweep verification (your own findings).**' espalier/agents/harness-security.md security-class-sweep; then
NEEDS_V0231_PATCH=yes
fi
# v0.24.0: the simplify lane. Keep in sync with
# migrate-v0.23.1-to-v0.24.0.sh's own idempotency check; same
# skip-record-counts-as-handled rule (labels are v0.24.0-*).
ok240() { grep -qF "$1" "$2" 2>/dev/null \
|| grep -qF "v0.24.0-$3" espalier/.migrations-skipped 2>/dev/null; }
if ! grep -q '^name: espalier-simplify' espalier/skills/espalier-simplify/SKILL.md 2>/dev/null \
|| ! grep -qF '/espalier-simplify' espalier/pipeline.md 2>/dev/null \
|| ! grep -qF 'simplify_from' espalier/skills/espalier/SKILL.md 2>/dev/null \
|| ! grep -qF 'simplify_from' espalier/skills/espalier-map/SKILL.md 2>/dev/null \
|| ! grep -qF 'simplify-survey' espalier/skills/espalier-ask/SKILL.md 2>/dev/null \
|| ! grep -qF 'simplify-lane echo' espalier/hooks/espalier-stats.sh 2>/dev/null \
|| ! ok240 '## Simplification Changes: Retire the Whole Obligation' espalier/agents/harness-coder.md coder-simplify \
|| ! ok240 '## Simplification Review' espalier/agents/harness-reviewer.md reviewer-simplify; then
NEEDS_V0240_PATCH=yes
fi
+ # v0.25.0: quality-first context. Probe = the grep-only-paths key AND the
+ # scoped_docs helper (the two surfaces every install gains) plus the coder
+ # Handoff section; keep in sync with migrate-v0.24.0-to-v0.25.0.sh's own
+ # idempotency check (labels are v0.25.0-*; skip records count as handled).
+ ok250() { grep -qF "$1" "$2" 2>/dev/null \
+ || grep -qF "v0.25.0-$3" espalier/.migrations-skipped 2>/dev/null; }
+ if ! grep -q '^grep-only-paths:' espalier/.espalier-config 2>/dev/null \
+ || ! grep -qF 'scoped_docs()' espalier/hooks/drift-helpers.sh 2>/dev/null \
+ || ! grep -qF 'HANDOFF: true' espalier/skills/espalier/SKILL.md 2>/dev/null \
+ || ! grep -qF 'stages/3-coding.md' espalier/skills/espalier/SKILL.md 2>/dev/null \
+ || [ ! -f espalier/skills/espalier/stages/3-coding.md ] \
+ || [ ! -f espalier/agents/modes/fix-round.md ] \
+ || ! grep -qF 'HANDOFF: true' espalier/skills/espalier-fix/SKILL.md 2>/dev/null \
+ || ! grep -qF 'Spawn shape (per change)' espalier/hooks/espalier-stats.sh 2>/dev/null \
+ || ! ok250 '## Handoff: Finish Bounded, Hand Off Clean' espalier/agents/harness-coder.md coder-handoff \
+ || ! ok250 '[spec-unread]' espalier/agents/harness-reviewer.md reviewer-spec \
+ || ! ok250 '7. Scoped docs named in the pack' espalier/agents/harness-security.md security-before; then
+ NEEDS_V0250_PATCH=yes
+ fi
fi
if [ "$NEEDS_V01_V02" = no ] && [ "$NEEDS_V03_V04" = no ] \
&& [ "$NEEDS_V04_V05" = no ] && [ "$NEEDS_V05_PATCH" = no ] \
&& [ "$NEEDS_V05_V06" = no ] && [ "$NEEDS_V06_V07" = no ] \
&& [ "$NEEDS_V07_V08" = no ] && [ "$NEEDS_V08_PATCH" = no ] \
&& [ "$NEEDS_V082_PATCH" = no ] && [ "$NEEDS_V09_MINOR" = no ] \
&& [ "$NEEDS_V091_PATCH" = no ] && [ "$NEEDS_V092_PATCH" = no ] \
&& [ "$NEEDS_V093_PATCH" = no ] && [ "$NEEDS_V094_PATCH" = no ] \
&& [ "$NEEDS_V0100_PATCH" = no ] && [ "$NEEDS_V0110_PATCH" = no ] \
&& [ "$NEEDS_V0120_PATCH" = no ] && [ "$NEEDS_V0130_PATCH" = no ] \
&& [ "$NEEDS_V0131_PATCH" = no ] && [ "$NEEDS_V0132_PATCH" = no ] \
&& [ "$NEEDS_V0140_PATCH" = no ] && [ "$NEEDS_V0150_PATCH" = no ] \
&& [ "$NEEDS_V0160_PATCH" = no ] && [ "$NEEDS_V0170_PATCH" = no ] \
&& [ "$NEEDS_V0180_PATCH" = no ] && [ "$NEEDS_V0190_PATCH" = no ] \
&& [ "$NEEDS_V0200_PATCH" = no ] && [ "$NEEDS_V0210_PATCH" = no ] \
&& [ "$NEEDS_V0211_PATCH" = no ] \
&& [ "$NEEDS_V0220_PATCH" = no ] && [ "$NEEDS_V0221_PATCH" = no ] \
&& [ "$NEEDS_V0230_PATCH" = no ] && [ "$NEEDS_V0231_PATCH" = no ] \
- && [ "$NEEDS_V0240_PATCH" = no ]; then
+ && [ "$NEEDS_V0240_PATCH" = no ] && [ "$NEEDS_V0250_PATCH" = no ]; then
echo "Already fully up to date. Nothing to do."
exit 0
fi
```
Report the detected plan to the user (which migration(s) will run).
### Step 2: Locate migration scripts
The skill resolves its own plugin root — no path guessing. `${CLAUDE_SKILL_DIR}`
is set by Claude Code to the directory of the running skill
(`<plugin>/skills/espalier-migrate`); the plugin root, where `scripts/` lives,
is two levels up. This resolves the *installed* plugin in every layout —
marketplace cache (`~/.claude/plugins/cache/...`), dev checkout, or symlink —
never a stray `$HOME` checkout that merely shares the name.
```bash
PLUGIN_DIR=""
# Primary: derive the plugin root from the skill's own location.
if [ -n "${CLAUDE_SKILL_DIR:-}" ] \
- && [ -f "${CLAUDE_SKILL_DIR}/../../scripts/migrate-v0.23.1-to-v0.24.0.sh" ]; then
+ && [ -f "${CLAUDE_SKILL_DIR}/../../scripts/migrate-v0.24.0-to-v0.25.0.sh" ]; then
PLUGIN_DIR="$(cd "${CLAUDE_SKILL_DIR}/../.." && pwd)"
fi
# Fallback (rare — e.g. CLAUDE_SKILL_DIR unset): explicit override, then a
# known dev-checkout location.
if [ -z "$PLUGIN_DIR" ]; then
for candidate in "${ESPALIER_PLUGIN_DIR:-}" "$HOME/repos/espalier-engineering"; do
[ -n "$candidate" ] || continue
- if [ -f "$candidate/scripts/migrate-v0.23.1-to-v0.24.0.sh" ]; then
+ if [ -f "$candidate/scripts/migrate-v0.24.0-to-v0.25.0.sh" ]; then
PLUGIN_DIR="$candidate"
break
fi
done
fi
if [ -z "$PLUGIN_DIR" ]; then
echo "ERROR: couldn't locate the Espalier plugin." >&2
echo "If it is installed, update it: /plugin update espalier-engineering" >&2
echo "Or set ESPALIER_PLUGIN_DIR to your espalier-engineering checkout." >&2
exit 1
fi
```
The probe is the NEWEST migration script, so a plugin that predates the current
chain fails to resolve rather than resolving and then dying on a missing script
mid-apply. If the primary path misses and the fallback fires, the plugin install
- is likely stale (no `migrate-v0.23.1-to-v0.24.0.sh`) — tell the user to
+ is likely stale (no `migrate-v0.24.0-to-v0.25.0.sh`) — tell the user to
`/plugin update espalier-engineering` first. Bump this probe whenever a new
migration script is added.
### Step 3: Show dry-run preview for each applicable migration
Run the dry-run for each needed migration, in order, and surface the output
verbatim:
```bash
[ "$NEEDS_V01_V02" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.1-to-v0.2.sh" --dry-run --plugin-dir="$PLUGIN_DIR/skills/espalier-init"
[ "$NEEDS_V03_V04" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.3-to-v0.4.sh" --dry-run
[ "$NEEDS_V04_V05" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.4-to-v0.5.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V05_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.5.2-to-v0.5.3.sh" --dry-run
[ "$NEEDS_V05_V06" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.5-to-v0.6.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V06_V07" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.6-to-v0.7.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V07_V08" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.7-to-v0.8.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V08_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.8-to-v0.8.1.sh" --dry-run
[ "$NEEDS_V082_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.8.1-to-v0.8.2.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V09_MINOR" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.8.2-to-v0.9.0.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V091_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.9.0-to-v0.9.1.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V092_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.9.1-to-v0.9.2.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V093_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.9.2-to-v0.9.3.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V094_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.9.3-to-v0.9.4.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0100_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.9.6-to-v0.10.0.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0110_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.10.0-to-v0.11.0.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0120_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.11.0-to-v0.12.0.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0130_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.12.0-to-v0.13.0.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0131_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.13.0-to-v0.13.1.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0132_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.13.1-to-v0.13.2.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0140_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.13.2-to-v0.14.0.sh" --dry-run --plugin-dir="$PLUGIN_DIR" $WITH_CODEX_FLAG
[ "$NEEDS_V0150_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.14.0-to-v0.15.0.sh" --dry-run --plugin-dir="$PLUGIN_DIR" $WITH_COPILOT_FLAG
[ "$NEEDS_V0160_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.15.0-to-v0.16.0.sh" --dry-run --plugin-dir="$PLUGIN_DIR" $CODEOWNERS_FLAGS
[ "$NEEDS_V0170_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.16.0-to-v0.17.0.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0180_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.17.0-to-v0.18.0.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0190_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.18.0-to-v0.19.0.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0200_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.19.0-to-v0.20.0.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0210_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.20.0-to-v0.21.0.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0211_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.21.0-to-v0.21.1.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0220_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.21.1-to-v0.22.0.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0221_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.22.0-to-v0.22.1.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0230_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.22.1-to-v0.23.0.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0231_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.23.0-to-v0.23.1.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0240_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.23.1-to-v0.24.0.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
+ [ "$NEEDS_V0250_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.24.0-to-v0.25.0.sh" --dry-run --plugin-dir="$PLUGIN_DIR"
```
A dry-run for a step whose prerequisite has not been applied yet may refuse with
an error (e.g. the v0.9.2 preview needs a `pipeline.md` that the v0.9.1 step
rewrites). That is expected in a multi-step chain — the refusal is the script
guarding its own precondition, not a failure. Surface it and continue; Step 6
applies the steps in order, so the prerequisite is satisfied by then.
### Step 4: If v0.4→v0.5 applies, pick the doctor cadence
The v0.4→v0.5 upgrade installs `/espalier-doctor`, a periodic drift scan. Ask
the user how often it should run (`AskUserQuestion`):
```
How often should /espalier-doctor re-scout the codebase for artifact drift?
A scan is activity-gated — an idle repo never triggers one.
1. Every change → every-change
2. Weekly → weekly (recommended)
3. Monthly → monthly
4. On-demand only → manual
```
Cache the answer as `$DOCTOR_CADENCE` (default `weekly`). It is editable later
in `espalier/.doctor-cadence`.
### Step 4b: If v0.14.0 applies, ask about Codex wiring
The wrapper refresh always runs; the Codex WIRING is opt-in. Skip the question
entirely if `grep -q codex espalier/.platforms` already succeeds (wired — set
`WITH_CODEX_FLAG=""`). Otherwise ask (`AskUserQuestion`):
```
v0.14.0 can additionally wire this repo for Codex (OpenAI's coding agent):
.agents/skills/ symlinks, an AGENTS.md Espalier section, .codex/config.toml
quality-gate hooks, and .codex/agents/ sub-agents. Claude wiring is untouched.
1. Yes — teammates (or I) use Codex here → WITH_CODEX_FLAG=--with-codex
2. No — Claude Code only (default) → WITH_CODEX_FLAG=""
```
Cache the literal flag string as `$WITH_CODEX_FLAG` and substitute it into the
Step 3 dry-run and Step 6 apply commands (it expands to nothing when empty).
Wiring later is one command: `bash <plugin>/scripts/migrate-v0.13.2-to-v0.14.0.sh --with-codex --yes`.
### Step 4c: If v0.15.0 applies, ask about Copilot wiring
The adapter install always runs; the Copilot WIRING is opt-in. Skip the
question entirely if `grep -q copilot espalier/.platforms` already succeeds
(wired — set `WITH_COPILOT_FLAG=""`). Otherwise ask (`AskUserQuestion`):
```
v0.15.0 can additionally wire this repo for GitHub Copilot: .github/skills/
symlinks (Agent Skills for VS Code / CLI / cloud agent), a
.github/copilot-instructions.md Espalier section, @harness-* custom agents,
and .github/hooks quality gates. Claude/Codex wiring is untouched.
1. Yes — teammates (or I) use Copilot here → WITH_COPILOT_FLAG=--with-copilot
2. No — not needed (default) → WITH_COPILOT_FLAG=""
```
Cache the literal flag string as `$WITH_COPILOT_FLAG` and substitute it into
the Step 3 dry-run and Step 6 apply commands (it expands to nothing when
empty). Wiring later is one command:
`bash <plugin>/scripts/migrate-v0.14.0-to-v0.15.0.sh --with-copilot --yes`.
### Step 4d: If v0.16.0 applies, ask about CODEOWNERS ownership routing
The maintenance-floor refresh always runs; the CODEOWNERS block is opt-in.
The SCRIPT never prompts (the runner invokes every script `--yes`) — collect
the handles HERE, before apply. Skip the question entirely when a
`>>> ESPALIER OWNERS v1 >>>` marker already exists in `.github/CODEOWNERS`,
`CODEOWNERS`, or `docs/CODEOWNERS` (set `CODEOWNERS_FLAGS=""`), and on an
unattended run (skipped — wiring later is one
`bootstrap --wire-only --codeowners-rules=… --codeowners-wiki=…` run).
Otherwise ask (`AskUserQuestion`):
```
v0.16.0 can route espalier rule/wiki changes to owners for review via a
CODEOWNERS block (advisory until "Require review from Code Owners" branch
protection is on). Add it?
1. Skip (default) → CODEOWNERS_FLAGS=""
2. Yes — provide handles via Other → e.g. "@platform-team @docs-team"
(first = espalier/rules/ owner, second = espalier/wiki/ owner; either
may be omitted) → CODEOWNERS_FLAGS="--codeowners-rules=<h1> --codeowners-wiki=<h2>"
(drop the flag for an omitted handle)
```
Cache the literal flag string as `$CODEOWNERS_FLAGS` and substitute it into
the Step 3 dry-run and Step 6 apply commands (it expands to nothing when
empty).
### Step 5: Confirm with the user
Use `AskUserQuestion`:
```
The dry-run preview above shows what will change. Proceed?
Options:
1. Apply all needed migrations, in order
2. Apply only the next one (stop after that)
3. Cancel — don't apply anything
```
### Step 6: Apply migration(s) in order
Apply each needed migration IN ORDER. Never reorder — each assumes the prior
completed.
```bash
[ "$NEEDS_V01_V02" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.1-to-v0.2.sh" --yes --plugin-dir="$PLUGIN_DIR/skills/espalier-init"
[ "$NEEDS_V03_V04" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.3-to-v0.4.sh" --yes
[ "$NEEDS_V04_V05" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.4-to-v0.5.sh" --yes --plugin-dir="$PLUGIN_DIR" --doctor-cadence="$DOCTOR_CADENCE"
[ "$NEEDS_V05_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.5.2-to-v0.5.3.sh" --yes
[ "$NEEDS_V05_V06" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.5-to-v0.6.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V06_V07" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.6-to-v0.7.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V07_V08" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.7-to-v0.8.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V08_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.8-to-v0.8.1.sh" --yes
[ "$NEEDS_V082_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.8.1-to-v0.8.2.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V09_MINOR" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.8.2-to-v0.9.0.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V091_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.9.0-to-v0.9.1.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V092_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.9.1-to-v0.9.2.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V093_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.9.2-to-v0.9.3.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V094_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.9.3-to-v0.9.4.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0100_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.9.6-to-v0.10.0.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0110_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.10.0-to-v0.11.0.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0120_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.11.0-to-v0.12.0.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0130_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.12.0-to-v0.13.0.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0131_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.13.0-to-v0.13.1.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0132_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.13.1-to-v0.13.2.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0140_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.13.2-to-v0.14.0.sh" --yes --plugin-dir="$PLUGIN_DIR" $WITH_CODEX_FLAG
[ "$NEEDS_V0150_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.14.0-to-v0.15.0.sh" --yes --plugin-dir="$PLUGIN_DIR" $WITH_COPILOT_FLAG
[ "$NEEDS_V0160_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.15.0-to-v0.16.0.sh" --yes --plugin-dir="$PLUGIN_DIR" $CODEOWNERS_FLAGS
[ "$NEEDS_V0170_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.16.0-to-v0.17.0.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0180_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.17.0-to-v0.18.0.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0190_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.18.0-to-v0.19.0.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0200_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.19.0-to-v0.20.0.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0210_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.20.0-to-v0.21.0.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0211_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.21.0-to-v0.21.1.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0220_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.21.1-to-v0.22.0.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0221_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.22.0-to-v0.22.1.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0230_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.22.1-to-v0.23.0.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0231_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.23.0-to-v0.23.1.sh" --yes --plugin-dir="$PLUGIN_DIR"
[ "$NEEDS_V0240_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.23.1-to-v0.24.0.sh" --yes --plugin-dir="$PLUGIN_DIR"
+ [ "$NEEDS_V0250_PATCH" = yes ] && bash "$PLUGIN_DIR/scripts/migrate-v0.24.0-to-v0.25.0.sh" --yes --plugin-dir="$PLUGIN_DIR"
```
Each script's verification block prints `X passed, Y failed`. Surface every
script's output to the user.
Mid-chain, an intermediate script's `bootstrap --force` health check may WARN
about missing artifacts from a NEWER version ("expected mid-chain") — that is
normal, not a failure: a later migration in the chain installs them and the
final step re-validates everything. Only treat a script as failed on a nonzero
exit or a `✗` in its own verification block.
### Step 7: Report verification + next steps
If anything failed:
```
1. Inspect the diff: git diff
2. Restore from backup: .claude/settings.json.v0.3.bak (v0.3→v0.4 only)
3. Re-run /espalier-migrate (every script is idempotent)
```
On success:
```
Migration applied. Recommended next steps:
1. Review the diff:
git diff --stat
git status
2. Commit — stage EXACTLY the files each script reported touching (every
script prints its touched paths, incl. backups and the settings-backup
side-effect); never `git add -A`, which would sweep unrelated files
into the migration commit:
git add <each reported path>
git commit -m "chore: migrate to Espalier vX.Y.Z" # the version the chain just reached
3. If you upgraded to v0.5.0, scan for drift that accrued before the upgrade:
/espalier-doctor
4. Try a real flow:
/espalier feat: <some small feature>
```
## Flags (forwarded to scripts)
**v0.1→v0.2 (`migrate-v0.1-to-v0.2.sh`):**
| Flag | Effect |
|------|--------|
| `--dry-run` | Show actions only |
| `--yes` | Skip interactive prompt; defaults merge-strategy to `ask-later` |
| `--backfill=best-effort` | Try to recover historic commit SHAs via `git log --grep <slug>` |
| `--plugin-dir=<path>` | Override auto-detected plugin location |
**v0.3→v0.4 (`migrate-v0.3-to-v0.4.sh`):**
| Flag | Effect |
|------|--------|
| `--dry-run` | Show actions only |
| `--yes` | Skip apply confirmation prompt |
| `--rewrite-history` | Also rewrite text refs inside `espalier/changes/*/pipeline-state.md` bodies |
**v0.4→v0.5 (`migrate-v0.4-to-v0.5.sh`):**
| Flag | Effect |
|------|--------|
| `--dry-run` | Show actions only |
| `--yes` | Skip apply confirmation prompt |
| `--doctor-cadence=<val>` | `/espalier-doctor` cadence: `every-change\|weekly\|monthly\|manual` (default `weekly`) |
| `--plugin-dir=<path>` | Path to the espalier-engineering plugin checkout |
**v0.5.2→v0.5.3 (`migrate-v0.5.2-to-v0.5.3.sh`):**
| Flag | Effect |
|------|--------|
| `--dry-run` | Show the section that would be appended |
| `--yes` | Skip the apply confirmation prompt |
**v0.5→v0.6 (`migrate-v0.5-to-v0.6.sh`):**
| Flag | Effect |
|------|--------|
| `--dry-run` | Show actions only |
| `--yes` | Skip the apply confirmation prompt |
| `--plugin-dir=<path>` | Path to the espalier-engineering plugin checkout |
Backs up any customised pipeline skill on diff (`<file>.pre-v0.6.bak`), then
`bootstrap --force` installs the `espalier-grill` skill and refreshes the four
changed pipeline templates (which also date-prefix new change folders).
Idempotent — re-running detects an already-v0.6 install and no-ops.
**v0.6→v0.7 (`migrate-v0.6-to-v0.7.sh`):**
| Flag | Effect |
|------|--------|
| `--dry-run` | Show actions only |
| `--yes` | Skip the apply confirmation prompt |
| `--plugin-dir=<path>` | Path to the espalier-engineering plugin checkout |
Backs up any customised pure-copy pipeline file on diff (`<file>.pre-v0.7.bak`),
then `bootstrap --force` installs the `espalier-ask` skill and symlinks it.
Also patches CLAUDE.md + `espalier/agent.md` to mention `/espalier-ask` (the
bootstrap CLAUDE.md writer is append-once and never touches agent.md).
Purely additive — no pipeline change. Idempotent — re-running detects an
already-v0.7 install and no-ops.
**v0.7→v0.8 (`migrate-v0.7-to-v0.8.sh`):**
| Flag | Effect |
|------|--------|
| `--dry-run` | Show actions only |
| `--yes` | Skip the apply confirmation prompt |
| `--plugin-dir=<path>` | Path to the espalier-engineering plugin checkout |
Backs up any customised pure-copy pipeline file on diff (`<file>.pre-v0.8.bak`),
then `bootstrap --force` refreshes the three changed pipeline templates
(`pipeline.md`, `espalier`, `espalier-fix`) — adding the requirements approval
gate that stops both pipelines for explicit user sign-off before any code is
written (no-TTY runs auto-approve). No new skill, no pipeline stage added.
Idempotent — re-running detects an already-v0.8 install and no-ops.
**v0.8→v0.8.1 (`migrate-v0.8-to-v0.8.1.sh`):**
| Flag | Effect |
|------|--------|
| `--dry-run` | Show the sections that would be appended |
| `--yes` | Skip the apply confirmation prompt |
Appends a `## Change Impact Analysis` section to `harness-coder.md` and a
`## Runtime-Surface Review` section to `harness-reviewer.md` (both per-project
files a plugin update cannot reach). Patches each file independently and is
idempotent per file — re-running detects already-patched agents and no-ops. No
`--plugin-dir` needed (the patch appends inline text; the flag is accepted and
ignored for a uniform call signature).
**v0.8.1→v0.8.2 (`migrate-v0.8.1-to-v0.8.2.sh`):**
| Flag | Effect |
|------|--------|
| `--dry-run` | Show actions only |
| `--yes` | Skip the apply confirmation prompt |
| `--plugin-dir=<path>` | Path to the espalier-engineering plugin checkout |
Backs up any customised pure-copy pipeline file on diff (`<file>.pre-v0.8.2.bak`),
then `bootstrap --force` refreshes the two changed pure-copy files (`pipeline.md`,
`espalier-fix`) with the re-review fixpoint loop + `Reviewed-Diff` certificate
writes. Surgically appends a `## Re-review Rounds` section to the per-project
`harness-reviewer.md` and inserts the certificate check into the per-project
`pre-push-gate.sh` (graceful warn + manual snippet if the gate was customised past
the anchor). Idempotent — re-running detects an already-v0.8.2 install and no-ops.
**v0.8.2→v0.9.0 (`migrate-v0.8.2-to-v0.9.0.sh`):**
| Flag | Effect |
|------|--------|
| `--dry-run` | Show actions only |
| `--yes` | Skip the apply confirmation prompt |
| `--plugin-dir=<path>` | Path to the espalier-engineering plugin checkout |
Creates the new per-project files (security-standards + production-standards
rules, harness-security agent, espalier-security skill) with project-name +
tools-mode substitution, then `bootstrap --force` refreshes the pure-copy
pipeline/grill files + drift-helpers/wrapper/lookup hooks + copies scout-prompts
+ symlinks both new rules + the audit skill + runs the 37-check validation.
Surgically appends the security AND production sections to the per-project
`harness-coder.md` / `harness-reviewer.md` / `espalier-testing` SKILL /
`espalier-review` SKILL (and the Repo-Audit Mode section to a pre-fold
`harness-security.md`), inserts the secret/dependency scan + the cwd fail-closed
guard into `pre-push-gate.sh`, and patches CLAUDE.md + `espalier/agent.md`.
Idempotent — the already-v0.9.0 check requires all eleven artifacts present
(security + audit + production), so a crash mid-run is completed on re-run.
The `espalier-review` SKILL's `## Production-Readiness Checks` section is part of
that set. Fresh v0.9.0+ inits get it from the template; earlier builds of this
script declared the path but never wrote to it, so installs migrated by them lack
the section and `migrate-v0.9.2-to-v0.9.3.sh` refuses to patch them. Because the
section is in the already-v0.9.0 check, re-running this step on such an install
adds it rather than reporting "Nothing to do".
**v0.9.0→v0.9.1 (`migrate-v0.9.0-to-v0.9.1.sh`):**
| Flag | Effect |
|------|--------|
| `--dry-run` | Show actions only |
| `--yes` | Skip the apply confirmation prompt |
| `--plugin-dir=<path>` | Path to the espalier-engineering plugin checkout |
Creates `espalier/.espalier-config` if absent (extracted from the plugin's
bootstrap heredoc — single source of truth; an existing file is preserved),
then refreshes the three pure-copy pipeline files (`pipeline.md`,
`espalier/skills/espalier/SKILL.md`, `espalier-fix`) so their prose reads the
config keys. A customised file is backed up to `<file>.pre-v0.9.1.bak`.
Idempotent — config present + prose referencing it ⇒ no-op.
**v0.9.1→v0.9.2 (`migrate-v0.9.1-to-v0.9.2.sh`):**
| Flag | Effect |
|------|--------|
| `--dry-run` | Show actions only |
| `--yes` | Skip the apply confirmation prompt |
| `--plugin-dir=<path>` | Path to the espalier-engineering plugin checkout |
Backs up any customised pure-copy file on diff (`<file>.pre-v0.9.2.bak`), then
`bootstrap --force` refreshes the pure-copy skills + pipeline + scout-prompts +
hook templates, and two anchored surgical patches update the per-project
`pre-push-gate.sh` (active-change selection; portable test-count parse — the
substituted test command is read out of the old gate and carried forward).
Warn + manual snippet if the gate was customised past the anchors. Idempotent —
re-running detects an already-v0.9.2 install and no-ops.
The `^TEST_OUTPUT=` check assumes the gate runs ONE test command on the host, the
shape `hook-templates/pre-push-gate.sh` generates. A project whose gate was
customised at init to run tests another way (e.g. per-container `docker compose
exec`) cannot satisfy it: the surgical patch warns and prints a manual snippet,
and that one check reports `✗`. That is a template-shape mismatch, not a failed
migration — the guard it really enforces is that no `{test_command}` placeholder
was left unsubstituted.
**v0.9.2→v0.9.3 (`migrate-v0.9.2-to-v0.9.3.sh`):**
| Flag | Effect |
|------|--------|
| `--dry-run` | Show actions only |
| `--yes` | Skip the apply confirmation prompt |
| `--plugin-dir=<path>` | Path to the espalier-engineering plugin checkout |
Backs up `espalier-grill` + `espalier-review` (`<file>.pre-v0.9.3.bak`), refreshes
the pure-copy `espalier-grill` SKILL from the template, then re-splices the three
changed sections of the substituted `espalier-review` SKILL (frontmatter
description, `## Two Review Loops`, `## Production-Readiness Checks`), preserving
every init-substituted section — notably the `{detected convention}` checklist
line. Requires `python3`. Idempotent — re-running detects both v0.9.3 markers and
no-ops.
Fails closed with `ERROR: install missing section` if the installed
`espalier-review` lacks `## Production-Readiness Checks`. That means the v0.9.0
step never ran (or ran from a build predating its review-skill patch) — re-run the
v0.9.0 step, which is idempotent and adds the section.
**v0.9.3→v0.9.4 (`migrate-v0.9.3-to-v0.9.4.sh`):**
| Flag | Effect |
|------|--------|
| `--dry-run` | Show actions only |
| `--yes` | Skip the apply confirmation prompt |
| `--plugin-dir=<path>` | Path to the espalier-engineering plugin checkout |
Backs up `harness-security.md` + `espalier-security` SKILL (`<file>.pre-v0.9.4.bak`),
then replaces the `description:` frontmatter line of each from the v0.9.4 template
(substituting this project's name into the skill's), and re-splices the two changed
body spans of the agent (P0 rubric, repo-audit delta 5). Requires `python3`.
Idempotent — re-running detects both v0.9.4 markers and no-ops.
Frontmatter is edited **line-wise, never rewritten**: an install created in
`inherit` tools-mode has had its `tools:` line stripped, and copying the template's
frontmatter would silently reintroduce it. The verification asserts the install's
original tools-mode is preserved.
A body span whose anchor the project has customised away is left untouched and
reported **warn-only** (not `✗`), because the v0.9.4 wording is a clarity
improvement, not a behaviour change. The script still exits 0 and still applies
every span it could match.
**v0.9.6→v0.10.0 (`migrate-v0.9.6-to-v0.10.0.sh`):**
| Flag | Effect |
|------|--------|
| `--dry-run` | Show actions only |
| `--yes` | Skip the apply confirmation prompt |
| `--plugin-dir=<path>` | Path to the espalier-engineering plugin checkout |
Backs up `espalier/hooks/pre-push-gate.sh` (`<file>.pre-v0.10.0.bak`), then rewrites
its build / lint / test spans from the v0.10.0 template, **preserving the commands
this project substituted at init**. Requires `python3`.
Each span is handled independently. A gate can be half-migrated — running the v0.9.2
step against a v0.10.0 plugin re-splices only the test span, leaving `run_tests` with
old build/lint — so the idempotency guard requires all three functions, and a span
already function-shaped is skipped rather than re-read. The test command is never
read back from `TEST_OUTPUT=$(run_tests 2>&1)`; that yields the function name.
A gate customised at init past the template shape (e.g. a Docker-first gate running
each suite in its own container) is **left completely untouched**, reported with the
manual change list, and the script exits 0. It is never mangled. The decline is
recorded once in `espalier/.migrations-skipped` (`v0.10.0-gate: customised,
manual port needed`), so detection reports the version as handled (manual)
instead of re-proposing the patch every run.
**v0.10.0→v0.11.0 (`migrate-v0.10.0-to-v0.11.0.sh`):**
| Flag | Effect |
|------|--------|
| `--dry-run` | Show actions only |
| `--yes` | Skip the apply confirmation prompt |
| `--plugin-dir=<path>` | Path to the espalier-engineering plugin checkout |
Rebuilds `espalier/hooks/pre-push-gate.sh` from the v0.11.0 template, preserving
the three command bodies substituted at init (`run_build` / `run_lint` /
`run_tests` — single commands or multi-line blocks). Blocking paths now exit 2
with the reason on stderr — the Claude Code PreToolUse contract; the old exit-1
gate never actually blocked. A customised gate is left untouched, recorded in
`espalier/.migrations-skipped` (`v0.11.0-gate: customised, manual port needed`)
and the exact manual contract is printed. Overwrites both wrappers and
`pipeline.md` + the 8 pure-copy pipeline skills from templates
(backups `<file>.pre-v0.11.bak`), patches `check-layer-boundaries.sh` to
exit 2 + stderr via anchored sed, appends `, Write` to the reviewer/security
agents' `tools:` lines (inherit-mode installs — no `tools:` line — are logged
and skipped, never failed), and refreshes `espalier-review` /
`espalier-security` SKILL.md re-substituting `{project}` with the name
recovered from the installed file (recovery failure → skip marker + manual
instructions). Requires `python3`. Idempotent — re-running detects the
v0.11-shaped gate (or its skip marker) plus the verdict-gate line and no-ops.
**v0.11.0→v0.12.0 (`migrate-v0.11.0-to-v0.12.0.sh`):**
| Flag | Effect |
|------|--------|
| `--dry-run` | Show actions only |
| `--yes` | Skip the apply confirmation prompt |
| `--plugin-dir=<path>` | Path to the espalier-engineering plugin checkout |
The v0.12.0 change is confined to one pure-copy file — the grill skill gains
Step 1.5, the rules/wiki blind-spot pass — so this migration is a single
backup-and-refresh of `espalier/skills/espalier-grill/SKILL.md` from the v0.12.0
template (backup `<file>.pre-v0.12.bak`). It refuses to run against a stale
plugin whose grill template lacks Step 1.5, and it never touches user-customised
files (the grill skill is plugin-owned, copied verbatim, never substituted).
Verifies the refreshed skill carries Step 1.5 and still cross-checks
`espalier/rules/` + `espalier/wiki/`. Idempotent — re-running detects Step 1.5
in the installed grill skill and no-ops. No `python3` needed.
## Anti-Patterns
- NEVER skip the dry-run preview — users should see what will change before applying.
- NEVER pass `--yes` to any script without asking the user first.
- NEVER reorder: v0.1→v0.2 before v0.3→v0.4 before v0.4→v0.5. Each migration
assumes the previous layout.
- NEVER modify a migration script from this skill — if a bug surfaces, file an
issue against the plugin.