From 2bf4ae43d105a45a95c1887204cd38ea330304c7 Mon Sep 17 00:00:00 2001 From: Phillip Tarrant Date: Sun, 12 Jul 2026 15:22:01 -0500 Subject: [PATCH] docs(plan): Duncarrow purge implementation plan Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01HuHRPE7VfppUJEaoGBEUqZ --- .../plans/2026-07-12-duncarrow-purge.md | 592 ++++++++++++++++++ 1 file changed, 592 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-12-duncarrow-purge.md diff --git a/docs/superpowers/plans/2026-07-12-duncarrow-purge.md b/docs/superpowers/plans/2026-07-12-duncarrow-purge.md new file mode 100644 index 0000000..869e4a7 --- /dev/null +++ b/docs/superpowers/plans/2026-07-12-duncarrow-purge.md @@ -0,0 +1,592 @@ +# 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. ✓ +```