docs(plan): Duncarrow purge implementation plan
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HuHRPE7VfppUJEaoGBEUqZ
This commit is contained in:
592
docs/superpowers/plans/2026-07-12-duncarrow-purge.md
Normal file
592
docs/superpowers/plans/2026-07-12-duncarrow-purge.md
Normal file
@@ -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) <noreply@anthropic.com>
|
||||
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) <noreply@anthropic.com>
|
||||
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) <noreply@anthropic.com>
|
||||
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) <noreply@anthropic.com>
|
||||
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. ✓
|
||||
```
|
||||
Reference in New Issue
Block a user