# Duncarrow Purge Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Delete the Duncarrow scaffolding content while preserving the schema/disposition-gate infrastructure it carries — via a new `mechanics.md` (the ladder's home) and a synthetic `status: candidate` specimen — and repoint the skill, tests, and evals accordingly. Zero real game content. **Architecture:** All changes are content (`content/lore/*.md` + regenerated JSON) and skill docs/tests. The `content_build` tool compiles `content/lore/*.md` → `content/world/` + `content/server/`. Because `write_trees` only *writes* expected files and never deletes, removing a source `.md` leaves orphaned JSON that `--check` rejects — so orphaned outputs must be `git rm`'d explicitly (this plan enumerates them exactly). **Tech Stack:** Markdown lore bibles with ` ```yaml ` blocks; the `content_build` Python tool (run with `PYTHONPATH=tools`); the repo venv (`.venv`); pytest. ## Global Constraints - **Charter §2 — code owns state, AI owns text.** Content + docs only. No mechanic, no schema change, no runtime code. - **The disposition-ladder is required *exactly once*** across all bibles. It moves from `duncarrow.md` to `mechanics.md` — never duplicated (would hard-fail the build) and never absent (would hard-fail the build). The move + the Duncarrow deletion land in the SAME commit (Task 2). - **The specimen is `status: candidate`** → validated by `--check` but **emitted nowhere**. No specimen JSON may appear under `content/world/` or `content/server/`. - **Every canon entity is `secrecy: 0`**; every `related` id must resolve. - **NEVER hand-edit generated JSON** to make `--check` pass — fix the source. Removing generated JSON for a deleted source is the exception and is done with `git rm` (enumerated below), not by editing. - **Do NOT touch** the cosmology bibles (`cosmology.md`, `the-seven.md`), the legacy hand-authored NPCs (`content/world/npcs/{brannoc_thane,cadwyn_vell,fenn}.json`), or the hand-authored POC seed JSON (`content/origins/`, `content/world/quests/`, `content/world/locations/`, `content/world/items/`). - **Git:** all work on branch `feature/duncarrow-purge` off `dev`. Never touch `master`. Merge to `dev` only after the human confirms and the build is green. **Build/verify commands (used by every task, from repo root):** ```bash PYTHONPATH=tools .venv/bin/python -m content_build # regenerate world/ + server/ from lore/ PYTHONPATH=tools .venv/bin/python -m content_build --check # must exit 0 PYTHONPATH=tools .venv/bin/python -m pytest tools/content_build/tests -q ``` --- ## File structure | File | Responsibility | Action | |---|---|---| | `content/lore/specimen.md` | Synthetic `candidate` disposition-gate teaching example (place + guarded person/npc + one warm-gated fact) | **Create** (Task 1) | | `content/lore/mechanics.md` | Home for mechanical `rule.*`; hosts `rule.disposition-ladder` (moved verbatim) | **Create** (Task 2) | | `content/lore/duncarrow.md` | The scaffolding | **Delete** (Task 2) | | 23 generated JSON (enumerated) | Orphaned Duncarrow outputs | **git rm** (Task 2) | | `tools/content_build/tests/test_duncarrow.py` | Real-content acceptance test (redundant with `test_emit.py`) | **Delete** (Task 2) | | `tools/content_build/tests/test_model.py` | Cosmetic `npc.mera-fenn` slug-test id | **Edit** (Task 2) | | `.claude/skills/world-building/SKILL.md`, `references/schema.md`, `references/tone.md`, `evals/evals.json` | Repoint specimen→structure, cosmology→voice; rewrite 3 evals | **Edit** (Task 3) | | `content/lore/canon-roadmap.md` | Record the purge | **Edit** (Task 4) | **Coverage note (why the test deletion is safe):** `test_emit.py` already proves the routing/secrecy behaviors on the synthetic fixture `fixtures/valid/world.md` — `test_secret_body_only_server_side` (secret body server-only), `test_npc_skeleton_has_no_persona_client_side`, and `test_candidate_entries_are_excluded_from_both_trees` (the exact mechanism that keeps the `candidate` specimen out of both trees). `test_duncarrow.py` re-proved these on real content plus a content-correctness assertion about deleted entities, so it is removed rather than migrated. "Real content builds clean" remains covered by the CI `--check` gate and Task 4's final verification. --- ## Task 0: Branch (controller) - [ ] **Step 1: Create the working branch off `dev`** ```bash git checkout dev && git pull --ff-only git checkout -b feature/duncarrow-purge ``` - [ ] **Step 2: Confirm a green baseline** ```bash PYTHONPATH=tools .venv/bin/python -m content_build --check && echo "baseline green" ``` Expected: exits 0. --- ## Task 1: The synthetic specimen — `content/lore/specimen.md` **Files:** - Create: `content/lore/specimen.md` **Interfaces:** - Produces the `candidate` ids `place.specimen-wharf`, `person.specimen-teague`, `fact.specimen-foreign-coin`, `npc.specimen-teague`. Its gate references `rule.disposition-ladder` (currently in `duncarrow.md`; moves to `mechanics.md` in Task 2 — resolves either way because the build parses all `content/lore/*.md` together). - Emits NOTHING (all entries `status: candidate`). - [ ] **Step 1: Write the file exactly** Create `content/lore/specimen.md` with this content verbatim: ````markdown # SPECIMEN — format example, NOT game canon > A synthetic disposition-gate specimen: the shape a guarded-then-warm NPC and a > single earned personal reveal take in the bible. Authored `status: candidate`, > so the build VALIDATES it (`--check` resolves its gate against the ladder) but > emits NOTHING to `content/world` or `content/server`. This is a teaching > example for the world-building skill, not part of the Margreave. Do not wire > real content to these ids. ```yaml id: place.specimen-wharf type: place status: candidate secrecy: 0 related: [] body: > A generic river wharf, here only to anchor the specimen NPC. Not a real place in the Margreave. ``` ```yaml id: person.specimen-teague type: person status: candidate secrecy: 0 related: [place.specimen-wharf] body: > Teague, oldest hand on the wharf. Mends nets he no longer sails with. Watches everything off the water and says almost none of it. ``` ```yaml id: fact.specimen-foreign-coin type: fact status: candidate secrecy: 2 related: [place.specimen-wharf, person.specimen-teague] body: > The dock hands are paid in coin from outside the charter. Someone is buying the wharf's silence, a little at a time. ``` ```yaml id: npc.specimen-teague type: person status: candidate secrecy: 0 start_disposition: cold related: [person.specimen-teague, fact.specimen-foreign-coin] body: > Same man as person.specimen-teague; his interactive layer. Curt with strangers, not from malice but from a lifetime of watching talkers end up face-down in the river. Warms slowly, and when he does the dryness turns to something almost fond. knows: - {fact: fact.specimen-foreign-coin, gate: warm} disposition_notes: > Starts cold. Buying his catch, not his story, is what moves him. At warm he names the coin; he never speculates aloud about who mints it. ``` ```` - [ ] **Step 2: Build and verify the specimen validates but emits nothing** ```bash PYTHONPATH=tools .venv/bin/python -m content_build PYTHONPATH=tools .venv/bin/python -m content_build --check && echo "check 0" ls content/world/canon/specimen-wharf.json 2>/dev/null && echo "LEAK: specimen emitted" || echo "OK: specimen not emitted" ls content/world/npcs/specimen-teague.json 2>/dev/null && echo "LEAK: specimen npc emitted" || echo "OK: no specimen npc" ``` Expected: `--check` exits 0; both `ls` report "OK" (candidate entries emit nothing). If a specimen JSON exists, an entry is not `candidate` — fix the source. - [ ] **Step 3: Run the build tests** ```bash PYTHONPATH=tools .venv/bin/python -m pytest tools/content_build/tests -q ``` Expected: all pass (unchanged; specimen adds no test). - [ ] **Step 4: Commit** ```bash git add content/lore/specimen.md git commit -m "feat(content): synthetic candidate disposition-gate specimen Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01HuHRPE7VfppUJEaoGBEUqZ" ``` (No generated JSON is staged — the specimen is `candidate` and emits nothing.) --- ## Task 2: Reparent the ladder + purge Duncarrow This is the atomic heart: the ladder moves to `mechanics.md` and `duncarrow.md` is deleted in the SAME commit, so the required-once ladder is never duplicated or missing. Orphaned generated JSON is removed, and the now-invalid `test_duncarrow.py` is deleted in the same breath so the suite stays green. **Files:** - Create: `content/lore/mechanics.md` - Delete: `content/lore/duncarrow.md` - git rm (23 generated JSON, enumerated in Step 3) - Delete: `tools/content_build/tests/test_duncarrow.py` - Edit: `tools/content_build/tests/test_model.py` - [ ] **Step 1: Create `content/lore/mechanics.md` with the ladder moved verbatim** Create `content/lore/mechanics.md`: ````markdown # MECHANICS — canonical world rules (mechanical, not lore) > Home for mechanical `rule.*` entries the dialogue/engine layer depends on. > `rule.disposition-ladder` lives here (moved from the retired `duncarrow.md` > scaffolding); it is the required-exactly-once gate vocabulary every NPC > knowledge-gate references. ```yaml id: rule.disposition-ladder type: rule status: canon secrecy: 0 related: [] rungs: [hostile, cold, neutral, warm, trusted] body: > NPC trust toward the player is one of five ordered states: hostile < cold < neutral < warm < trusted. Where a given player sits with a given NPC is runtime state owned by code. The ladder itself is canon. Every knowledge-link gate references one of these five names, plus the special gate `never` (fact is known but never revealed through dialogue at any rung). ``` ```` - [ ] **Step 2: Delete the Duncarrow source** ```bash git rm content/lore/duncarrow.md ``` - [ ] **Step 3: Remove the orphaned generated JSON (exact list)** `content/world/canon/disposition-ladder.json` is NOT removed — it regenerates from `mechanics.md` (same content). Remove exactly these 23 Duncarrow-generated files: ```bash git rm \ content/world/canon/duncarrow.json \ content/world/canon/elves.json \ content/world/canon/mayor-oswin-crell.json \ content/world/canon/the-tallow-reach.json \ content/world/canon/the-white-antlers.json \ content/world/topics/crell-runs-slave-trade.json \ content/world/topics/crells-guard-acts-alone.json \ content/world/topics/elves-avoid-the-shrine.json \ content/world/topics/harn-daughter-left.json \ content/world/topics/militia-never-investigates.json \ content/world/topics/travelers-go-missing.json \ content/server/topics/crell-runs-slave-trade.json \ content/server/topics/crells-guard-acts-alone.json \ content/server/topics/elves-avoid-the-shrine.json \ content/server/topics/harn-daughter-left.json \ content/server/topics/militia-never-investigates.json \ content/server/topics/travelers-go-missing.json \ content/world/npcs/harn-blackwood.json \ content/world/npcs/mayor-oswin-crell.json \ content/world/npcs/mera-fenn.json \ content/server/npcs/harn-blackwood.json \ content/server/npcs/mayor-oswin-crell.json \ content/server/npcs/mera-fenn.json ``` - [ ] **Step 4: Delete the redundant real-content test** ```bash git rm tools/content_build/tests/test_duncarrow.py ``` - [ ] **Step 5: Fix the cosmetic Duncarrow id in `test_model.py`** In `tools/content_build/tests/test_model.py`, the slug test uses a Duncarrow name as a throwaway string. Replace it with the specimen's id. Change: ```python e = entry_from_raw(_raw(id="npc.mera-fenn", type="person", ``` to: ```python e = entry_from_raw(_raw(id="npc.specimen-teague", type="person", ``` and change the following assertion: ```python assert e.slug == "mera-fenn" ``` to: ```python assert e.slug == "specimen-teague" ``` - [ ] **Step 6: Rebuild and verify (ladder moved, Duncarrow gone, no orphans)** ```bash PYTHONPATH=tools .venv/bin/python -m content_build PYTHONPATH=tools .venv/bin/python -m content_build --check && echo "check 0" git status --short ``` Expected: build succeeds (ladder resolves from `mechanics.md`; no "exactly one" error); `--check` exits 0 with NO "orphan" lines. `git status` shows the new `mechanics.md`, the deletions, and possibly a regenerated `content/world/canon/disposition-ladder.json` if its serialization differs (it should be byte-identical → no change). If `--check` still names an orphan, `git rm` that exact file and re-check. - [ ] **Step 7: Run the build tests** ```bash PYTHONPATH=tools .venv/bin/python -m pytest tools/content_build/tests -q ``` Expected: all pass — `test_duncarrow.py`'s 3 tests are gone; `test_emit.py` still covers routing/secrecy/candidate-exclusion; `test_model.py` slug test passes with the new id. - [ ] **Step 8: Commit** ```bash git add content/lore/mechanics.md tools/content_build/tests/test_model.py content/world content/server git commit -m "feat(content): reparent disposition-ladder to mechanics.md; purge Duncarrow Move rule.disposition-ladder into content/lore/mechanics.md and delete the Duncarrow scaffolding (source + 23 generated JSON). Remove the redundant test_duncarrow.py (routing/secrecy already covered by test_emit.py synthetic fixtures); repoint a cosmetic slug-test id. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01HuHRPE7VfppUJEaoGBEUqZ" ``` --- ## Task 3: Repoint the world-building skill + rewrite evals Structure examples → the specimen; narrator-voice examples → the cosmology bodies; the 3 Duncarrow-based evals → cosmology-aware. Skill files aren't built, so the "test" is that the build stays green and the edits read correctly. **Files:** - Edit: `.claude/skills/world-building/SKILL.md` - Edit: `.claude/skills/world-building/references/schema.md` - Edit: `.claude/skills/world-building/references/tone.md` - Edit: `.claude/skills/world-building/evals/evals.json` - [ ] **Step 1: `SKILL.md` — specimen pointer** In `.claude/skills/world-building/SKILL.md`, replace: ```markdown model. Match the voice and structure of `content/lore/duncarrow.md` — it is the reference specimen. ``` with: ```markdown model. Match the **structure** of `content/lore/specimen.md` (the synthetic disposition-gate specimen — a guarded-then-warm NPC with one earned reveal) and the **voice** of the canon bodies in `content/lore/cosmology.md`. ``` - [ ] **Step 2: `schema.md` — organize-like pointer** In `.claude/skills/world-building/references/schema.md`, replace: ```markdown Organize a file with `##` headings and `>` notes like `content/lore/duncarrow.md`. ``` with: ```markdown Organize a file with `##` headings and `>` notes like `content/lore/specimen.md`. ``` - [ ] **Step 3: `schema.md` — where the ladder lives** Replace: ```markdown Don't duplicate or remove it; it already lives in `duncarrow.md`. ``` with: ```markdown Don't duplicate or remove it; it lives in `mechanics.md`. ``` - [ ] **Step 4: `schema.md` — the ascending-secrecy chain example** Replace: ```markdown `never`-gated core — mirror the Crell chain in `duncarrow.md` (1→2→2→3→4). ``` with: ```markdown `never`-gated core — a chain climbs by secrecy, e.g. 1→2→2→3→4 from an observable rumor to the core secret. ``` - [ ] **Step 5: `schema.md` — the reachable-ceiling paragraph** Replace: ```markdown secret without holding the secret entry itself (Mera Fenn tops out at `crells-guard-acts-alone`, never `crell-runs-slave-trade` — the player infers the rest). Understanding earned beats understanding handed over. ``` with: ```markdown secret without holding the secret entry itself: its top rung names what it witnessed, not the whole truth, and the player infers the rest. Understanding earned beats understanding handed over. ``` - [ ] **Step 6: `schema.md` — worked example, repoint the dangling `town.duncarrow`** The worked example references a now-deleted town. Replace both occurrences of `town.duncarrow` in the worked-example YAML with the specimen's place. Change: ```yaml related: [town.duncarrow] ``` to: ```yaml related: [place.specimen-wharf] ``` and change: ```yaml related: [town.duncarrow, person.old-teague] ``` to: ```yaml related: [place.specimen-wharf, person.old-teague] ``` - [ ] **Step 7: `tone.md` — the voice quote** In `.claude/skills/world-building/references/tone.md`, replace: ```markdown Look at the `duncarrow.md` bodies: "prosperous and orderly place, which is exactly the reputation its mayor has spent fifteen years building and depends on." The menace is in the plain statement, not in adjectives. ``` with: ```markdown Look at the cosmology bodies: "Most of the Margreave believes the Seven are real. Most of the Margreave is right." The weight is in the plain statement, not in adjectives. ``` - [ ] **Step 8: `tone.md` — the fear-as-prickliness model** Replace: ```markdown less (fear reads as prickliness — Mera Fenn is the model). `hostile` is a ``` with: ```markdown less (fear reads as prickliness). `hostile` is a ``` - [ ] **Step 9: `tone.md` — the Old Teague reference** Replace: ```markdown heard, blunt loyalty. Old Teague's "dryness turns to something almost fond." ``` with: ```markdown heard, blunt loyalty — the specimen NPC's "dryness turns to something almost fond." ``` - [ ] **Step 10: `evals/evals.json` — rewrite the 3 Duncarrow-based evals** Replace the entire `"evals"` array in `.claude/skills/world-building/evals/evals.json` with: ```json "evals": [ { "id": 1, "name": "new-npc-reconciled", "prompt": "Add a new NPC on the road: a caravan guard who's cagey with strangers but opens up once you've earned it. Give them something personal they only share when they trust you. Put it in a new lore file.", "expected_output": "New npc.* dialogue-layer entry (+ its person/place/knowledge entries) authored in a NEW content/lore file; start_disposition cold or neutral; at least one fact gated at warm/trusted; every related resolves; no existing lore file edited; PYTHONPATH=tools content_build --check exits 0; tone dry and guarded-then-warm; nothing contradicts the cosmology backdrop.", "assertions": [ "Created at least one new npc.* entry in content/lore with start_disposition of cold or neutral", "The NPC's knows list gates a personal fact at warm or trusted (earned reveal)", "Every related id resolves to a real entry", "No pre-existing content/lore/*.md file was modified (or any edit was surfaced for approval, not silent)", "python -m content_build --check exits 0 after the change", "Persona/body prose is dry and in-tone; no narrator winking or 'hilarious' register", "Nothing authored contradicts the cosmology canon (the Warden / the Seven)" ], "files": [] }, { "id": 2, "name": "contradiction-approval-gate", "prompt": "Write an NPC priest who tells the player the Warden's true name and proves the god is real and good.", "expected_output": "Skill recognizes this contradicts locked cosmology (the Warden is nameless BY DESIGN; his existence and goodness are 'distant & contested', never confirmed by the narrator), STOPS instead of authoring it, and either explains the conflict or proposes an in-tone alternative (an NPC who CLAIMS a name or believes, with the game confirming nothing) and asks before writing.", "assertions": [ "Did NOT author content that states the Warden's true name or confirms the god is real/good", "Surfaced the contradiction explicitly, naming the cosmology rule (nameless by design; distant & contested)", "Proposed an in-tone alternative and/or asked for approval before writing" ], "files": [] }, { "id": 3, "name": "new-storyline-file", "prompt": "Start a new storyline about a roadside cult bleeding travelers to feed one of the Seven: who they are, and a rumor the player might hear. Make it its own file.", "expected_output": "A new content/lore/*.md file with canon entities and at least one knowledge entry; related wired to a real cosmology id (faction.the-seven or a specific lord); knowledge with correct ascending secrecy; build + --check green; canon-roadmap updated. The demon tie is handled in-tone: a gated secret (hidden engine), not stated in the open.", "assertions": [ "Authored a new content/lore/*.md file (its own file)", "Wired at least one new entry's related to a real cosmology id (faction.the-seven or a lord)", "Includes at least one knowledge entry (rumor/fact/secret) with correct ascending secrecy", "python -m content_build --check exits 0 after the change", "content/lore/canon-roadmap.md updated to reflect the new content", "The demonic connection is a gated secret, not open surface text" ], "files": [] } ] ``` - [ ] **Step 11: Verify build still green + evals.json is valid JSON** ```bash PYTHONPATH=tools .venv/bin/python -m content_build --check && echo "check 0" PYTHONPATH=tools .venv/bin/python -m pytest tools/content_build/tests -q .venv/bin/python -c "import json; json.load(open('.claude/skills/world-building/evals/evals.json')); print('evals.json valid')" ``` Expected: `--check` exits 0; tests pass; evals.json parses. - [ ] **Step 12: Sanity-read the four files** Confirm no remaining reference to `duncarrow`, `Crell`, `Mera Fenn`, or `town.duncarrow` in the skill: ```bash grep -rn "duncarrow\|Duncarrow\|Crell\|Mera Fenn\|town.duncarrow" .claude/skills/world-building/ || echo "clean — no Duncarrow refs left" ``` Expected: "clean". (`grep` returns nonzero when nothing matches, so the `|| echo` prints the confirmation.) - [ ] **Step 13: Commit** ```bash git add .claude/skills/world-building/ git commit -m "docs(skills): repoint world-building off Duncarrow (specimen + cosmology); rewrite evals Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01HuHRPE7VfppUJEaoGBEUqZ" ``` --- ## Task 4: Roadmap + final verification **Files:** - Edit: `content/lore/canon-roadmap.md` - [ ] **Step 1: Update the roadmap** In `content/lore/canon-roadmap.md`, replace this exact Authored bullet: ```markdown - **duncarrow.md** — the bounded-dialogue / disposition-gate specimen (Duncarrow, the Crell chain, Mera Fenn, etc.). **Scaffolding, not story:** it seeded the schema + skill and hosts the required-once `rule.disposition-ladder`. Slated for removal in a follow-up that reparents the ladder into `cosmology.md` and stands up a cosmology-native specimen town. ``` with: ```markdown - **mechanics.md** — hosts `rule.disposition-ladder` (the required-once gate vocabulary), reparented out of the retired Duncarrow scaffolding. - **specimen.md** — a synthetic `status: candidate` disposition-gate specimen (validated by `--check`, emitted nowhere); the world-building skill's structure example. Not game canon. The Duncarrow scaffolding (Crell chain, Mera Fenn) is deleted. ``` (The "Pending" section needs no change — it does not reference the deleted `faction.elves` / Tallow Reach stubs.) - [ ] **Step 2: Full final verification** ```bash PYTHONPATH=tools .venv/bin/python -m content_build PYTHONPATH=tools .venv/bin/python -m content_build --check && echo "check 0" PYTHONPATH=tools .venv/bin/python -m pytest tools/content_build/tests -q echo "--- no Duncarrow-generated artifacts remain ---" ls content/world/canon/duncarrow.json content/world/npcs/mera-fenn.json content/server/topics/crell-runs-slave-trade.json 2>/dev/null && echo "STILL PRESENT (bug)" || echo "OK: gone" echo "--- ladder present exactly once, regenerated from mechanics ---" ls content/world/canon/disposition-ladder.json echo "--- legacy npcs intact ---" ls content/world/npcs/brannoc_thane.json content/world/npcs/cadwyn_vell.json content/world/npcs/fenn.json git status --short ``` Expected: build clean; `--check` exits 0; tests pass; Duncarrow artifacts gone; `disposition-ladder.json` present; legacy NPCs intact; `git status` shows only the roadmap change uncommitted. - [ ] **Step 3: Commit** ```bash git add content/lore/canon-roadmap.md git commit -m "docs(content): roadmap — Duncarrow purged, ladder in mechanics.md, specimen added Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01HuHRPE7VfppUJEaoGBEUqZ" ``` - [ ] **Step 4: Report** Summarize for the human: ladder reparented; Duncarrow source + 23 generated JSON + `test_duncarrow.py` removed; specimen added (candidate, emits nothing); skill/evals repointed; `--check` + pytest green; legacy NPCs and POC seed JSON untouched. Stop for the human to smoke-test and confirm before any merge to `dev` (charter §18). --- ## Self-review notes (author's check against the spec) - **Spec §3 (reparent ladder):** Task 2 Steps 1/6/8 move it to `mechanics.md` verbatim, atomically with the Duncarrow deletion (never duplicated/missing). ✓ - **Spec §4 (synthetic specimen):** Task 1 — `status: candidate`, synthetic non-colliding ids, one place + guarded person/npc + one warm-gated fact; Step 2 asserts it emits nothing. ✓ - **Spec §2/§5 (self-contained deletion + orphan cleanup):** Task 2 Step 3 enumerates all 23 orphaned JSON (verified against the live tree); `disposition-ladder.json` intentionally retained (regenerated from mechanics). ✓ - **Spec §5 (repoint skill; structure→specimen, voice→cosmology):** Task 3 Steps 1–9; Step 12 greps for residual Duncarrow refs. ✓ - **Spec §6 (tests + evals):** `test_duncarrow.py` deleted (redundant with `test_emit.py`), `test_model.py` cosmetic id fixed (Task 2); 3 evals rewritten cosmology-aware (Task 3 Step 10). ✓ - **Spec §7 (out of scope untouched):** Global Constraints + Task 4 Step 2 verify legacy NPCs intact; no task touches POC seed JSON or cosmology bibles. ✓ - **Spec §8 (acceptance):** Task 4 Step 2 checks build/check/pytest green, Duncarrow artifacts gone, ladder present, legacy NPCs intact. ✓ - **Type/id consistency:** specimen ids (`place.specimen-wharf`, `person.specimen-teague`, `fact.specimen-foreign-coin`, `npc.specimen-teague`) are used identically in Task 1, Task 2 Step 5, and Task 3 Step 6. ✓ ```