--- name: strangler-fig-method description: "Use to REPLACE part of an existing brownfield system incrementally — behind a routing facade, one slice at a time, verified by contract equivalence against a green safety net — rather than restructuring it in place. Loaded internally by brownfield-refactorer when the human chooses replacement over Mikado in-place restructuring." disable-model-invocation: true --- # Strangler Fig Method Replaces a component by growing a new implementation alongside the old one, behind a facade that routes traffic, slice by slice, until the old implementation has no callers left and can be deleted. Unlike Mikado (restructure what exists), this method REPLACES it — appropriate when the existing code is too coupled to restructure safely, or when the target is a different stack/design entirely. **Precondition.** Same as Mikado: a green safety net (`characterize-with-contracts` / `brownfield-harness-builder`) must exist first. Here the safety net does double duty — it is replayed against BOTH the old and the new implementation; a slice only cuts over when the new implementation is contract-equivalent to the old one on every test in the harness. ## Procedure ### 1. Establish the facade Introduce (or confirm an existing) routing seam in front of the component being replaced — a gateway route, a feature-flagged branch, or a proxy layer — such that traffic can be switched per-slice between OLD and NEW without touching callers. If no seam exists, this is the first prerequisite: build the facade itself as slice zero, verified by the harness against the OLD implementation only (no NEW yet — this slice just proves the facade is transparent). ### 2. Slice the surface Partition the component's contract (the same contract discovered/reconstructed by `characterize-with-contracts`) into independently-cutover-able slices. Default granularity for v1: **one slice per route/endpoint** — smaller slices mean smaller blast radius per cutover and finer rollback granularity than a whole-component slice. ### 3. Build one slice's NEW implementation Implement the NEW version of one slice. Run the SAME characterization tests for that slice's contract against the NEW implementation (not just the old). This is the contract-equivalence check — Microcks/contract tests are the perfect fit here because they assert against the SAME `OPEN_API_SCHEMA`, regardless of which implementation answers. ### 4. Cutover gate (S4 — non-negotiable) A slice cuts over ONLY when: - The slice's characterization tests pass against NEW with the same assertions used against OLD (contract-equivalent — same status codes, same shapes; deliberate behavior CHANGES must be called out explicitly to the human as a decision, never silently absorbed as "equivalent"). - The full harness (all other slices, still routed to whichever implementation currently serves them) remains green. If the gate fails: do not cut over. Record the mismatch as a blocker (see terminal signals below) and keep routing that slice to OLD. ### 5. Repeat, then strangle Repeat steps 3-4 for each slice. Once every slice routes to NEW and the facade shows zero traffic to OLD (verify via routing logs/metrics if available, or via a final "OLD is unreachable" static check), the OLD implementation and the facade's OLD branch are dead code, removable as a final slice of their own. ## Plan persistence (the durable artifact) Persist the slice plan to `.copilot-tracking/skraft-plans/{projectSlug}/refactoring/{YYYY-MM-DD}/ strangler-.md` as a table, one row per slice: | Slice | Contract surface | Status | Cutover verdict | Notes | |---|---|---|---|---| | S1 | POST /orders | done (NEW live) | PASS (equivalent) | — | | S2 | GET /orders/:id | in-progress | CONCERNS | pagination shape differs — flagged to human | | S3 | DELETE /orders/:id | not-started | — | — | One writer on this file per run (`brownfield-refactorer`); reload before dispatching each slice worker, not from recall. ## Driving slices to completion (per-slice worker contract) Each slice is dispatched to `refactoring-worker` as a fresh, isolated unit. The dispatch packet MUST include: the current slice plan table, the specific slice to implement (contract surface + current status), the cutover acceptance criteria from step 4 verbatim, and the explicit instruction to flag (never silently resolve) any behavior difference between OLD and NEW. ## Terminal signals (worker -> orchestrator) - `ADVANCE` — this slice cut over cleanly; more slices remain. - `EXPAND` — this slice was too large to cut over as one unit; it was split into smaller slices (new rows added to the plan); no cutover happened for this dispatch. - `DONE` — all slices are on NEW, OLD is confirmed unreachable, and the OLD implementation + facade branch have been removed. - `BLOCKED` — a contract-equivalence mismatch needs a human decision (is the behavior difference intentional? acceptable? a bug in NEW?), or the facade itself cannot route cleanly. ## Common failure modes (reject these) - **Silently absorbing a behavior difference as "close enough"** — any NEW-vs-OLD difference is a human decision, not an implementation detail to smooth over. - **Cutting over a slice without replaying the FULL harness** — a slice can look correct in isolation while breaking a cross-slice interaction still routed to OLD. - **Skipping the facade-transparency check (slice zero)** — cutting traffic before proving the facade itself is invisible makes every later slice's signal unreliable. - **Deleting OLD before confirming zero traffic** — always verify unreachability before removal; a forgotten caller still pointed at OLD becomes a silent outage.