Skip to content

feat(worldbuilding): research knowledgebases + applied pattern libraries - #97

Merged
frankxai merged 27 commits into
mainfrom
claude/arcanea-lore-worldbuilding-552iy3
Aug 20, 2026
Merged

frankxai merged 27 commits into
mainfrom
claude/arcanea-lore-worldbuilding-552iy3

Conversation

@frankxai

@frankxai frankxai commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Summary

The prose half of the split. The tooling half — the lore-release-gate skill, the canon-evaluation rubric, lore-lint.mjs, its fixtures, and lore-canon.yml — shipped separately in #102 and is already on main. This PR carries the two content layers that gate reads from: research (what the best-built worlds do) and applied patterns (that research re-cut by craft problem so it is usable at a desk).

Everything is STAGING. CANON_LOCKED.md is untouched — it is not among the changed files. Promotion happens via /lock-decision, by Frank. IP discipline throughout: copy the architecture, never the bricks.

Title and description corrected before merge. The previous version described this PR as shipping the gate, which was true before the split and false after. It ships 14 files, none of them the gate.

What lands (14 files)

Applied pattern libraries — the seven knowledgebases are organized by world, which is useless when you sit down to design a specific thing. docs/worldbuilding/patterns/ re-cuts the same research by craft problem:

  • ARTIFACTS.md — 13 legendary-object patterns with reusable design rules, a minting checklist, 5 STAGING artifact slots left deliberately unnamed so the Registry coining procedure is not front-run, anti-patterns, IP red lines. Found that Tier 7 lists only nine Vael Crystals for ten Gates — Source has none, held consistently across four files, so it reads as deliberate rather than as an omission.
  • MAGIC_MECHANISMS.md — the hard/soft spectrum made practical, 13 mechanisms, 7 failure modes, and the U3 pressure test below. Three candidate Arcanean-register names coined with etymologies (the Narrowing Oath, the Held Word, the Forswearing).
  • ENCYCLOPEDIA_IA.md — 15 entity types with required fields and narrative sections, canon-tier representation, contribution flow, and the Name Ledger: identity as a stable ID with names as attributes, where a rename cannot be marked complete while any redirect-class occurrence is live. That structure makes the Thessara two-referent collision impossible rather than merely discouraged.
  • LANGUAGE_CRAFT.md — 8 registers with original Arcanean examples, six compression techniques, phonaesthetic tests, and an honest audit of our own corpus: the recent texts are franchise-grade, the older numbered legends are a tier below, and "not just X but Y" appears 19+ times.

Research layer — 7 per-world knowledgebases (Tolkien, Elder Scrolls, Final Fantasy, Marvel, HP/Fantastic Beasts, Warcraft, anime/modern-screen) plus SYNTHESIS.md with the 12-dimension scorecard and the Ten Upgrades.

Two vault-adjacent proposals.arcanea/lore/NAMING_REGISTRY.md and .arcanea/lore/THESSARA.md. Both additive, both STAGING; THESSARA.md is on the linter's superseded-name allowlist precisely because documenting a retired name is its purpose.

Verification

What building the gate turned up

Recorded here because these are findings about the corpus, not about this PR's files, and they outlive it:

Finding Evidence
The instruction file every agent loads is drifted .claude/CLAUDE.md — 17 errors + 3 warnings: names superseded Amaterasu, and carries the entire Gate frequency ladder shifted one position, in two duplicate tables
The canon validator certifies the drift packages/os/src/canon-validator.ts — asserts both superseded godbeast names as current and normalizes misspellings toward them (one annotated // correct)
A second copy of the shifted ladder, in JS packages/chrome-extension/tests/chrome-extension.test.mjs — both superseded names plus two shifted frequencies, asserted as expected test values
U3 violates locked canon "Ranks measure accumulated binding" contradicts CANON_LOCKED.md:74, which defines rank by Gates Open and is LOCKED; universal witnessed vows contradict the three unoathed Luminors in GATE_TOUCHED_UNDERGROUND.md:122. Repair recorded: reframe to a payment ledger — negotiated / imposed / refused
Lore is documents, not entities Three divergent copies of CANON_LOCKED.md; each guardian in four files; godbeasts/ holds both amaterasu.md and source.md, unlinked; nothing has a stable ID. This is the root cause of #98 rather than a symptom of it

Deliberately not swept piecemeal: a half-swept repo, where the instruction file disagrees with the remaining stale files, is worse for the next agent than uniform drift. That wants one atomic change — issue #98, which now has a machine-checkable completion test.

Promotion gates (hard preconditions for any /lock-decision)

  1. Issue Repo-wide superseded-name sweep: Thessara→Vaelith, Amaterasu→Source #98 — repo-wide superseded-name sweep. Required before the Thessara redeployment. Completion test: a full-repo lore-lint.mjs run coming back clean.
  2. Issue Citation-verification pass for docs/worldbuilding/research/ knowledgebases #99 — citation verification across the 7 research files.
  3. U3 — the canon repair above, plus IP differentiation (Arcanean-register name, structural divergences, IP-risk sign-off).
  4. Open canon question — Starweave or Shift at 852 Hzresolved 2026-08-14: the Gate is Starweave. The vault always said so; the ~186 occurrences of "Shift" are drift, tracked under Repo-wide superseded-name sweep: Thessara→Vaelith, Amaterasu→Source #98.

Type

  • Docs (documentation only)
  • Lore (universe content, Library texts)

Checklist

  • No TypeScript changed
  • Aligns with CANON_LOCKED.md — all additive STAGING; the vault is untouched and is now machine-enforced as the source of truth
  • No new any types introduced
  • Tested — canon gate clean over all 14 files; merges cleanly; no packages/ overlap

🤖 Generated with Claude Code

https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT

claude added 2 commits August 8, 2026 00:44
…nt proposal (STAGING)

- .arcanea/lore/NAMING_REGISTRY.md — ten naming registers with sound-spaces
  and morphology, tiered spell-suffix system, machine-checkable collision
  rules, earnable/losable name elements, locked + superseded inventories
- .arcanea/lore/THESSARA.md — Thessara redeployed as the Drowned Academy of
  the Vantara March (+ founder Archmage Thessara), filling the unnamed slot
  in the merged Stilling legend; alternates + mystery-ledger entries recorded

World-research knowledgebases (docs/worldbuilding/research/) follow in the
next commit when the research swarm completes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT
…benchmark + naming registry + Thessara canon proposal

Research swarm output — one knowledgebase per benchmark franchise
(docs/worldbuilding/research/): Tolkien/Middle-earth, Elder Scrolls,
Final Fantasy, Marvel, Harry Potter/Fantastic Beasts, Warcraft, and
anime/modern screen worlds (Avatar TLA, Arcane/Runeterra, JJK, FMA,
Demon Slayer, AoT, Frieren, Ghibli). ~21k words, each with cosmology /
magic / naming / bestiary / villain / canon-management extraction, a
'What Arcanea Should Steal (Architecture, Never Bricks)' section, and
IP red lines.

SYNTHESIS.md — honest 12-dimension scorecard of Arcanea vs the
benchmarks, the Ten Upgrades (STAGING proposals U1-U10: First Duet
cosmology, Shadow-as-marring, Binding Vows hard subsystem,
Element-Cultures, World Registry + canon tiers, Shattered Verse,
Villain Doctrine, naming roots, era machinery, Library-as-transmission),
a 10-item trap list, and a build order.

.arcanea/lore/NAMING_REGISTRY.md (STAGING) — full articulation system:
ten registers with sound-spaces + morphology, tiered spell-naming,
machine-checkable collision rules, earnable/losable name elements,
locked + superseded inventories.

.arcanea/lore/THESSARA.md (STAGING) — redeploys the Thessara name as
the Drowned Academy of the Vantara March + founder Archmage Thessara,
filling the existing unnamed slot in the Stilling legend; alternates
recorded; mystery-ledger entries minted.

All STAGING; CANON_LOCKED.md untouched; promotion via /lock-decision.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 86d0100e-c14b-4639-80cc-d3794d402e8c


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review

This is a docs/lore-only PR (9 markdown files, ~9,000 lines / ~23k words: SYNTHESIS.md, 7 per-franchise research knowledgebases, NAMING_REGISTRY.md, THESSARA.md). No source code changes, so I've focused the standard review dimensions on what actually applies here: content integrity, canon consistency, and IP/legal hygiene.

Verification performed

  • Cross-referenced every internal PR citation (#75, #76, #77, #81, #96) against actual PR state — all exist and match the claimed titles/status (77, 81, 96 merged; 75, 76 open). No hallucinated references.
  • Verified referenced file paths exist: packages/os/src/canon-validator.ts, docs/worldbuilding/BEST_PRACTICES.md, .arcanea/lore/STORY_ENGINE.md, book/legends-of-arcanea/the-stilling-and-the-first-proof.md.
  • Checked the THESSARA.md quote against the source legend file — accurate, faithfully trimmed with an ellipsis.
  • Checked the Thessara name-reuse claim against CANON_LOCKED.md directly — confirmed Elara's godbeast is locked as Vaelith (renamed from Amaterasu 2026-03-30) and CANON_LOCKED.md no longer references "Thessara" anywhere, so redeploying the name as the Drowned Academy is legitimately safe against the canonical source. Good catch by the PR to explicitly log this as a "superseded" name in the registry rather than silently reusing it.
  • Structural consistency: all 7 per-world files carry the same 10-section shape ("What Arcanea Should Steal" + "IP Red Lines"); NAMING_REGISTRY.md's Locked Inventory (§6) matches CANON_LOCKED.md's Gods/Godbeasts tables exactly.

Observations

IP/legal hygiene (the main "security" analogue here) — well handled. Every research file separates transferable architecture from protected expression and ends with an explicit "IP Red Lines" section (e.g., the HP file correctly flags Warner Bros.' aggressive enforcement history — the RDR Books case, the 2024 fan-festival takedowns — and draws the safe/unsafe line at mechanism-vs-expression). This is the right discipline for content that studies existing IP.

One thing I couldn't verify: the external web citations (WordsRated, HP Lexicon, Wizarding World, etc. — dozens across the 7 files) weren't checked against live URLs since WebFetch wasn't authorized in this session. The internal citations (PR numbers, file paths, in-repo quotes) all check out, which is a good signal, but if these knowledgebases are meant to be durably cited references, it'd be worth an independent pass confirming the external URLs resolve and support the specific claims attributed to them (stats like "600 million copies," "Gamp's Law" mechanics, etc.) before treating them as load-bearing.

Minor / non-blocking nits:

  • THESSARA.md line 5 labels the register as "Reaches covenant / high-antiquity," a blend of two registers from NAMING_REGISTRY.md §2, while the registry table itself only lists Thessara under "Reaches / covenant register." Given the doc explicitly frames Thessara as a person+place doubling (Archmage Thessara being a Seventh-Age antiquity figure, the Academy being a Reaches place), this is probably intentional — but if it isn't, worth a one-line clarification in the registry so the register table stays the single source of truth for register assignment.
  • SYNTHESIS.md cites ../BEST_PRACTICES.md for the Elden Ring/Dr. Stone research it says was "absorbed" — that file exists, but I didn't verify Elden Ring/Dr. Stone content is actually present there; if it isn't yet, the sentence overclaims slightly.
  • All ten upgrade proposals (U1–U10) point at files that don't exist yet (BINDING_VOWS.md, ELEMENT_CULTURES.md, etc.) — expected for STAGING proposals, just flagging that none of this is wired up yet; it's a proposal document, not an implementation.

Process / checklist

  • PR is still marked DRAFT — presumably intentional given the size and the explicit "STAGING, awaiting /lock-decision" framing on both NAMING_REGISTRY.md and THESSARA.md.
  • The checklist's "TypeScript compiles / no new any types" boxes are correctly self-annotated as N/A for a markdown-only change.
  • CANON_LOCKED.md is correctly left untouched, consistent with the PR's stated intent.

Overall: well-scoped, internally consistent, and appropriately cautious about both IP risk and canon-lock discipline. No blocking issues found; the external-citation verification gap above is the only thing I'd want closed out before treating these knowledgebases as authoritative references.

…Thessara)

Review nit on #97: THESSARA.md labels its register 'Reaches covenant /
high-antiquity' while the registry table listed only the covenant
register. The blend is intentional (Archmage Thessara = Seventh-Age
antiquity figure; the Academy = Reaches place) — the registry table now
states the dual-register rule explicitly so it stays the single source
of truth.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT
@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review — PR #97 (docs/lore only, no source code)

This PR adds 1,006 lines across 10 new markdown files (7 franchise-research knowledgebases + SYNTHESIS.md under docs/worldbuilding/research/, plus .arcanea/lore/NAMING_REGISTRY.md and .arcanea/lore/THESSARA.md). No app code, no tests, no config — so the usual "code quality / performance / test coverage" lenses don't apply directly. I reviewed it as a canon-consistency and IP-discipline change instead, and specifically re-verified the claims made in the PR description under "Review notes," since those are asserted as already-checked.

🔴 Blocking: the PR's own safety claim doesn't hold

THESSARA.md §1 states:

Thessara entered the corpus as a candidate godbeast name for Elara's companion and was superseded by Vaelith when the Ten were locked; the conflict-cleanup (#96) removed its last stray references.

This is not accurate. Live references to Thessara as Elara's current godbeast (not Vaelith) still exist in at least: guardians.md:200 ("Godbeast | Thessara - The Void Wolf of Possibility"), .claude/agents/guardians/elara.md:5 ("Godbeast: Thessara"), plus world-building.md, lore/guardians.md, lore/world.md, packages/aios/agents/guardians/elara.md, .claude/skills/arcanea-canon.md, .arcanea/gemini/guardian-prompts/elara.md, and others.

Same issue for "Amaterasu": NAMING_REGISTRY.md §6 lists it under Superseded ("→ Source, 2026-03-30"), but it's still live as Shinkami's godbeast name in .arcanea/lore/guardians/production/shinkami.md, book/legends-of-arcanea/VII_THE_GODBEAST_CODEX.md, sync/aios/lore/CANON_LOCKED.md, sync/aios/lore/godbeasts/amaterasu.md, and several agent-prompt files.

Since the whole point of THESSARA.md is to redeploy the name "Thessara" for a new entity (the Drowned Academy) on the premise that the old godbeast usage is dead, merging this while ~15 files still describe Thessara as Elara's live godbeast creates an active two-referent collision, not a resolved one. Recommend either: (a) scope this PR to also clean up the stray references (or file a tracked follow-up before promotion), or (b) soften the THESSARA.md/NAMING_REGISTRY.md language so it doesn't assert something the repo doesn't currently support.

🟡 Unverifiable citation

THESSARA.md §2.5 and the naming-adjacency reasoning in §4 lean on "Nethyssa" (a "PR #76" Leviathan) as an established sound-family peer. A repo-wide search finds "Nethyssa" nowhere except this PR's own two new files — there's no bestiary entry or other canon establishing it. If Nethyssa is real but unmerged/staged elsewhere, worth a pointer; if not, the "deliberate echo" reasoning in §2.5 is resting on nothing yet.

✅ What checks out

Minor

  • The PR description itself already flags the unverified external web citations across the 7 research files as a known follow-up — agreed this should happen before those files are treated as authoritative rather than working research.

Nothing here blocks the staging nature of this PR (it doesn't touch locked canon), but the Thessara/Amaterasu superseded-reference claim should be fixed before this is used as the basis for a /lock-decision promotion, since promoting on a false "cleanup already happened" premise would lock in a real naming collision.

… PR #76 staging

Review round 2 fixes:
- THESSARA.md no longer claims #96 removed the last Thessara-as-godbeast
  references; stray references persist in mirrors, agent prompts, and
  packages, and a repo-wide sweep is now stated as a precondition for
  promoting the redeployment.
- NAMING_REGISTRY.md superseded list gains a cleanup-status warning
  (Amaterasu and Thessara both still referenced repo-wide).
- All Nethyssa citations now note it is proposed in open, unmerged PR #76
  rather than reading as in-tree canon.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT

frankxai commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

Review addressed in aef2f43:

🔴 Blocking finding — confirmed and fixed. You're right: #96 only scrubbed the 33 conflict-marked files, and a fresh grep confirms Thessara-as-Elara's-godbeast plus Amaterasu both persist across mirrors (arcanea-lore/, sync/aios/), agent prompts, packages/, and book texts. Went with your option (b) plus tracking: THESSARA.md §1 and NAMING_REGISTRY.md §6 now state the residual references plainly, and both declare the repo-wide superseded-name sweep a precondition for any /lock-decision promotion of the redeployment — so a promotion can't happen on the false "cleanup already done" premise. The sweep itself stays a separate follow-up rather than ballooning this docs PR by ~15 files.

🟡 Nethyssa — real, but unmerged. It's the flagship of open PR #76 (Monster System + T3 Leviathan tier, .arcanea/lore/leviathans/nethyssa.md on that branch), which is why a working-tree search finds nothing. All citations now carry an explicit "proposed in open PR #76, unmerged" qualifier, and the sound-echo is marked contingent on both proposals being approved.

PR body updated with a Round 2 review-notes section.


Generated by Claude Code

The aef2f43 run failed on memory.test.mjs 'memories.json should have
valid structure when present' with 'Unexpected end of JSON input' —
a cross-process race where the test reads ~/.arcanea/memories.json on
the runner while concurrent suites write it. Unrelated to this PR's
markdown-only diff; quality-gate was green on the prior head.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT
@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review — PR #97 (worldbuilding research + Naming Registry + Thessara)

This is a docs/lore-only PR (10 new markdown files, 1010 additions, 0 deletions, no source touched), so the usual code-review lenses don't map 1:1 — I've adapted them below. I read NAMING_REGISTRY.md, THESSARA.md, and SYNTHESIS.md in full, skimmed the six per-world knowledgebases, and independently spot-checked several factual claims against the repo rather than taking the PR body at its word.

Correctness / "bugs"

  • Verified: the self-reported cleanup gap is real, and arguably understated. NAMING_REGISTRY.md and THESSARA.md both say "stray Amaterasu/Thessara references persist." I checked — book/legends-of-arcanea/VII_THE_GODBEAST_CODEX.md doesn't just have a stray mention, it has a full active section (## X. Amaterasu — The Source-Light) presenting Amaterasu as the in-fiction godbeast, contradicting CANON_LOCKED.md's rename to Vaelith/Source (2026-03-30). Same for guardians.md and .claude/agents/guardians/elara.md re: Thessara-as-godbeast. Good that this PR is honest about it and gates promotion on a sweep — but I'd make that sweep a tracked issue now rather than a someday-precondition, since two in-tree files are actively telling a different story than the locked canon today, independent of this PR.
  • The internal references I could check resolve correctly: packages/os/src/canon-validator.ts and docs/worldbuilding/BEST_PRACTICES.md both exist as cited; PR feat(lore): Arcanea Monster System + Nethyssa Leviathan + Hermes pipeline #76 (Nethyssa) is indeed open/unmerged, matching the "STAGING, contingent" framing used throughout.
  • NAMING_REGISTRY.md §4 points to canon-validator.ts as "the natural home" for the new collision rules (edit-distance guard, cross-register borrowing, G-test) — confirmed none of that is implemented there yet. That's fine as a proposal, but worth being explicit in the doc that this is aspirational, not wired up, so a future reader doesn't assume the lint already runs.

Consistency / best practices

  • Good discipline throughout: every research file separates "architecture to steal" from "protected bricks" with explicit IP red lines, and the STAGING status banners + STAGING LOG tables make provenance and revision history easy to audit. This is the pattern the rest of docs/worldbuilding/ should keep using.
  • THESSARA.md §2.5 and the registry's adjacency rule both correctly gate the Nethyssa sound-echo behind "only if PR feat(lore): Arcanea Monster System + Nethyssa Leviathan + Hermes pipeline #76 also lands" — no premature coupling to unmerged content.
  • Minor: THESSARA.md leans on the Elrond/Rivendell person+place doubling twice (main text + option D commentary) and the registry's "Reaches / covenant register" row also calls out the same dual-register case — slightly repetitive across files, not a real problem, just something to dedupe if these get merged into canon docs later.

Security

  • Not application security in the usual sense, but the closest analogue here is IP/trademark exposure: this PR explicitly extracts patterns from actively-licensed franchises (Marvel, Harry Potter, Warcraft, Tolkien estate). The "architecture never bricks" discipline and per-file red-line sections are the right mitigation and are present consistently — no named characters, no verbatim magic-system terms, no copied taxonomies that I saw. Worth having whoever owns legal/IP risk for Arcanea give SYNTHESIS.md's Ten Upgrades a pass before any of U1–U10 gets implemented, since "architecture, never bricks" is a judgment call that gets harder to defend the closer an implementation hews to a specific source (e.g. U3 Binding Vows reads very close to JJK's binding-vow mechanic specifically, not just "magic has costs" generically).

Test coverage / verification

  • The PR body already flags this, and I confirmed it's still open: the external web citations across the seven knowledgebase files (dozens of URLs) have not had an independent link-resolution/claim-support pass. Given how citation-heavy anime-modern-worlds.md in particular is, I'd treat "citations verified" as a hard blocker before anyone treats these files as authoritative reference material, not just a nice-to-have follow-up.
  • No lore-consistency lint exists yet to catch exactly the kind of drift found above (Amaterasu/Thessara stale references) automatically — this PR is a good forcing function to actually build the canon-validator.ts checks it proposes, rather than relying on manual review catching the next one.

Performance

  • N/A — no runtime code in this diff.

Overall

Solid, well-organized staging content with unusually honest self-critique already baked into the PR description (both review rounds are documented in-PR, which is good practice). The one thing I'd push on before merge: turn "repo-wide superseded-name sweep" from a documented precondition into an actual tracked issue/PR, since the drift it's flagging is already live in main-adjacent files, not just a risk introduced by this PR.


🤖 Automated review via Claude Code

… validator lint as proposed

- Superseded-name sweep now tracked as issue #98 (referenced from
  THESSARA.md and NAMING_REGISTRY.md); citation verification tracked
  as issue #99.
- SYNTHESIS.md U3 gains an explicit IP-differentiation requirement:
  the sworn-restriction mechanic hews closest to a single source and
  its common English name is that source's exact term, so the
  implementation must coin an Arcanean-register name via the registry
  coining procedure and keep the structural divergences.
- NAMING_REGISTRY.md validation hook now explicitly marked proposed /
  not yet implemented so no reader assumes the lint runs today.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT

frankxai commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

Round 3 addressed in ccb70d4:

  • Sweep is now a tracked issue, not a someday-precondition: Repo-wide superseded-name sweep: Thessara→Vaelith, Amaterasu→Source #98 (repo-wide Thessara→Vaelith / Amaterasu→Source sweep, including the active VII_THE_GODBEAST_CODEX.md Amaterasu section you found, agent prompts, package constants, mirrors, and the canon.test.mjs assertions that hardcode the old names). THESSARA.md and NAMING_REGISTRY.md now reference it directly.
  • Citation verification is Citation-verification pass for docs/worldbuilding/research/ knowledgebases #99 — treated as a hard gate before the seven knowledgebases count as authoritative rather than working research.
  • U3 IP concern — agreed and strengthened: SYNTHESIS.md U3 now carries an explicit implementation requirement — the mechanic's common English name is the source's exact localized term (a brick, not architecture), so the implementation must coin an Arcanean-register name via the registry's coining procedure, keep the structural divergences (Guardian-witnessed vows, ranks-as-accumulated-binding, the eleventh vow against the Gates), and pass an IP-risk review before promotion.
  • Validator lint marked aspirational: NAMING_REGISTRY.md §4 now says plainly the canon-validator.ts rules are proposed, not wired up — enforcement is manual until then.

The Elrond/Rivendell dedupe nit is noted for whenever these staging docs consolidate into canon files.


Generated by Claude Code

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review

This is a markdown-only worldbuilding/lore PR (10 new files, 0 deletions, no source code), so the usual "code quality / performance / test coverage" lenses don't apply directly. I reviewed it against the axes that matter for this kind of change: factual/citation accuracy, internal consistency with locked canon, IP discipline, and structural completeness. I independently re-verified the claims in the PR body rather than taking them at face value.

Verified correct:

Minor observations (non-blocking):

  • U3 (Binding Vows) is correctly flagged in-PR as the one upgrade closest to a single source (JJK's "Binding Vow" terminology) — the mitigation (coin an Arcanean-register name before implementation, keep structural divergences, IP-risk owner sign-off) is the right call and should be treated as a hard gate when U3 is actually implemented, not just a note.
  • The external web citations in the 7 research files are explicitly flagged in the PR body as not yet link-verified. That's honestly disclosed rather than hidden, but since this content will inform locked canon later, I'd treat the citation-verification sweep as a prerequisite for /lock-decision promotion, not just a nice-to-have follow-up — same tier as the issue Repo-wide superseded-name sweep: Thessara→Vaelith, Amaterasu→Source #98 sweep that's already a hard precondition for the Thessara redeployment.
  • Nice discipline overall: STAGING banners, /lock-decision gating, and the "architecture never bricks" framing are applied consistently across all new files, and the two rounds of review notes in the PR description show real fixes (not just re-assertions) between rounds.

No blocking issues found. The lore/canon discipline and citation hygiene are noticeably more careful than a typical docs PR — nice work tightening the claims between review rounds.

claude added 2 commits August 8, 2026 03:48
…ne lint, CI

Turns the worldbuilding research from documentation nothing reads into a
pipeline that runs on every lore change. Mirrors the proven web-excellence
pattern already load-bearing in this repo (skill + lint + CI ratchet).

- .claude/skills/lore-release-gate/SKILL.md — the sequencing contract:
  ground in the vault, choose a pattern deliberately, draft in register,
  cross-check (canon/naming/IP/continuity), evaluate, stage, promote.
  Registered as an auto-activating skill on lore paths.
- .claude/skills/canon-evaluation/SKILL.md — ten-dimension rubric with
  anchored 1-5 scales, hard gates, composite bands, and a four-lens
  adversarial pass that must be survived rather than argued with.
- .claude/ci/lore-lint.mjs — the model-free half: superseded names, Gate
  frequencies, godbeast pairings, canon-tier banners. Verified clean on 8
  legitimate canon files (zero false positives; changelog rows exempted
  since they must record superseded decisions verbatim) and catching 12
  real errors across the known-drifted files.
- .github/workflows/lore-canon.yml — runs the lint on every lore-touching
  PR as a ratchet on newly added lines.
- CLAUDE.md — makes the gate discoverable, per the web-gate precedent.

The lint reproduces issue #98 automatically and found more: guardians.md
carries the entire Gate frequency ladder shifted one position.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT
…s + U3 canon repair

Re-cuts the seven world knowledgebases from vertical (by world) to
horizontal (by craft problem), so the research is usable at the moment
someone designs a specific thing. ~21k words.

- patterns/ARTIFACTS.md — 13 legendary-object patterns, minting checklist,
  5 unnamed STAGING slots (names withheld so the Registry coining
  procedure is not front-run), anti-patterns, IP red lines.
- patterns/MAGIC_MECHANISMS.md — hard/soft spectrum made practical, 13
  mechanisms, 7 failure modes, Arcanea application, 3 coined candidate
  names with etymologies.
- patterns/ENCYCLOPEDIA_IA.md — 15 entity types with required fields and
  sections, canon-tier representation, and the Name Ledger: identity as
  stable ID with names as attributes, so a rename cannot half-land.
- patterns/LANGUAGE_CRAFT.md — 8 registers with original examples, six
  compression techniques, phonaesthetic tests, and an audit of our own
  corpus prose tics.

Lint corrections found by the pass itself:
- The vault names the 852 Hz Gate Starweave (CANON_LOCKED.md:62); the
  linter had encoded the drifted name Shift from the instruction file.
  Now canonical, with an alias that still resolves for the frequency
  check and warns on the name — no rename is recorded in the approval
  log, so this is an unrecorded divergence, not a supersession.
- packages/os/src/canon-validator.ts removed from the allowlist. Its
  714 Hz entry is a real typo detector, but the file also asserts both
  superseded godbeast names as current (lines 164, 166) and normalizes
  misspellings toward them. It does not miss the drift; it certifies it.
- Allowlist narrowed from docs/worldbuilding/ to research/ only, so
  pattern libraries quoting canon are checked.

SYNTHESIS U3 canon repair: 'ranks measure accumulated binding'
contradicts CANON_LOCKED.md:74, which defines rank by Gates Open and is
LOCKED; and universal witnessed vows contradict the three unoathed
Luminors in GATE_TOUCHED_UNDERGROUND.md:122. Repair recorded on the
proposal — reframe to a payment ledger (negotiated / imposed / refused).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT
@frankxai frankxai changed the title feat(worldbuilding): seven-world research knowledgebases + Arcanea benchmark synthesis + Naming Registry + Thessara feat(worldbuilding): research knowledgebases + applied pattern libraries + the lore canon gate Aug 8, 2026
@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review: lore canon gate (.claude/ci/lore-lint.mjs, lore-canon.yml, the two new skills)

Scoped this review to the actual code — lore-lint.mjs, the CI workflow, and the two SKILL.md files — since the other 18 files are prose/lore content. Overall this is careful, well-commented work: the assignment-vs-prose distinction in checkSupersededNames is a good design choice to keep false positives near zero, execFileSync is called with array args (no shell-injection surface), the workflow declares minimal permissions: contents: read, and the ratchet (--changed, only newly-added lines) is the right call for a repo with known pre-existing drift.

A few things worth a look before/after merge:

1. Latent false positive in checkGodbeastPairing
For a single line/table row that legitimately names two or more god+godbeast pairs together (a comparison row, a multi-pair ceremony table, etc.), the check flags any other canonical beast present in the row as wrong for every god found in that row — it does not verify column/adjacency correspondence. The relevant code:

const wrong = Object.values(GODBEASTS).find(
(b) => b.toLowerCase() !== beast.toLowerCase() && lowered.includes(b.toLowerCase())
);

E.g. a row like '| Aiyami | Sol | Ino | Kyuro |' would report 'aiyami is bonded to Sol, not Kyuro' — a false positive. I confirmed this does not currently fire anywhere in this PR's content, but it cuts against the stated 'near-zero false positive by design' goal, and per the file's own framing, one bad false positive is how a linter like this gets switched off.

2. Allowlist is broader than its own comment implies
isAllowlisted() is applied once in main() before all checks run, so it exempts a path from tier-banner, gate-frequency, godbeast-pairing, and lock-claim checks too — not just the superseded-name check the surrounding comment describes ('Naming a superseded entity here is correct, not drift'). Concretely, NAMING_REGISTRY.md, THESSARA.md, and the entire docs/worldbuilding/research/ tree are now fully exempt from gate-frequency/godbeast-pairing checks, not just name-supersession. Doesn't misfire today (spot-checked SYNTHESIS.md), but worth either narrowing the allowlist application to the specific check it is meant for, or updating the comment to match the broader behavior so the next person is not surprised.

3. Silent skip on git diff failure
In addedLinesFor, 'try { diff = git([...]) } catch { continue; }' swallows any diff error and leaves that file's entry out of the added map entirely — which means it is silently excluded from --changed checking with zero log output. For a linter whose entire value proposition is 'catch drift reliably,' a file that silently drops out of the check with no diagnostic seems worth at least a console.error so a real failure does not masquerade as 'nothing to check.'

4. No automated tests for the linter itself
Verification is manual per the PR description (checked clean/dirty against ~7 files by hand). Given the subtlety of the assignment-vs-prose logic and the multi-pair-row edge case above, a small fixture-based test (feed synthetic lines through checkSupersededNames/checkGodbeastPairing/checkGateFrequency and assert on findings) would lock in the false-positive guarantee as this evolves, rather than relying on repeated one-off manual passes.

5. Minor/nit
canon-evaluation/SKILL.md's own example evidence row uses 'Uses locked Shift Gate (852 Hz)' — which is exactly the alias lore-lint.mjs (added in this same PR) flags with a WARN pending /lock-decision (GATE_ALIASES = { shift: starweave }). Harmless since WARN doesn't fail CI, but a little ironic for the example in a skill about un-recorded naming drift to use the disputed name. Might as well use 'Starweave' there to match the vault.

None of these block the gate from doing real, useful work — CI wiring, node --check, and the smoke test against CANON_LOCKED.md are all sound — but 1-3 are worth a follow-up pass given how central 'near-zero false positive' is to this linter staying trusted long-term.

… into CI

Review round 5 on the canon gate. All five findings verified before fixing;
the linter's whole value is being trusted enough to stay switched on, and
that rests on near-zero false positives.

- Multi-pair table rows no longer misreport pairings. A row like
  '| Aiyami | Sol | Ino | Kyuro |' reported BOTH pairs wrong, since the
  check looked for any other canonical beast in the row without column
  correspondence. Now skips rows naming more than one god, matching the
  single-subject rule checkGateFrequency already used. Reproduced first.
- Gate names in ordinary English no longer fire. Nearly every Gate name is
  also a common word; 'one legend per House voice ... (174→1111 Hz)' in
  SYNTHESIS.md reported the Voice Gate as misnumbered. A name now counts
  only in Gate context ('the Crown Gate', 'Gate of Crown') or alone in a
  table cell. This vector was hidden while research/ was blanket-exempt.
- Allowlist narrowed to the check it was written for. It was applied
  globally in main(), so NAMING_REGISTRY.md, THESSARA.md and the whole
  research tree were also exempt from frequency, pairing and tier checks
  they can still get wrong. Now superseded-names only; the linter's own
  source stays fully excluded, since it states every name as data.
- git diff failures now log. A file dropping out of --changed silently
  looked exactly like 'nothing to check'.
- LORE_FILENAME renamed LORE_HINT: it matches anywhere in the path, not
  just the basename. Caught by a fixture whose temp dir contained 'lore'.
- canon-evaluation's example evidence row cited the disputed 'Shift' name
  in a rubric about unrecorded naming drift. Now Starweave, per the vault.

lore-lint.test.mjs: 14 black-box fixtures through the real CLI, covering
both must-not-fire and must-fire cases. Wired into lore-canon.yml ahead of
the vault smoke test.

Verified: 14/14 fixtures pass; 19 legitimate canon, research and pattern
files clean; 19 real errors still caught across the known-drifted files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT

frankxai commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

Round 5 addressed in f32c2bf — all five findings were real, and chasing them surfaced a sixth. Each reproduced before fixing.

1. Multi-pair rows — confirmed, fixed. Reproduced exactly as you described:

| Crown | Aiyami | Sol | Unity | Ino | Kyuro |
→ ERROR aiyami is bonded to Sol, not Kyuro.
→ ERROR ino is bonded to Kyuro, not Sol.

Two confident false errors on a correct row. Fixed with the single-subject rule checkGateFrequency already used: a row naming more than one god is skipped, since column correspondence isn't answerable line-by-line. Locked in by a fixture.

2. Allowlist broader than its comment — fixed by narrowing, not by documenting. You were right that it exempted four checks when it was written for one. Rather than update the comment, I scoped it: SUPERSEDED_ALLOWLIST now applies inside checkSupersededNames only, so NAMING_REGISTRY.md, THESSARA.md and the research tree are checked for frequencies, pairings and tier banners again. The linter's own source stays globally excluded — it states every canonical and superseded name as data.

That narrowing immediately caught a sixth vector, which the blanket exemption had been hiding: SYNTHESIS.md:37"one legend per House voice ... (174→1111 Hz)" — reported the Voice Gate as misnumbered. Nearly every Gate name is also an ordinary English word (voice, source, flow, heart, crown, shift, sight, fire, unity). A name now counts only in Gate context — the Crown Gate, Gate of Crown — or alone in a table cell. Four prose fixtures guard it.

3. Silent skip — fixed. addedLinesFor now logs which file was dropped, against which base, and why, with a pointer to re-run explicitly. Agreed on the reasoning: for a drift linter, "silently not checked" and "nothing to check" must never look alike.

4. No tests — fixed. .claude/ci/lore-lint.test.mjs, 14 black-box fixtures through the real CLI (so argument handling and filtering are covered, not just the check functions). Both directions: must-not-fire (multi-pair row, full ten-gate table, prose naming a retired entity, dated changelog rows, bare frequencies, Gate-words-as-English) and must-fire (assignment-position superseded names, superseded section headings, single-god wrong pairing, wrong frequency, the shifted ladder). Wired into lore-canon.yml ahead of the vault smoke test.

Writing them caught the sixth item: my own LORE_FILENAME matches anywhere in the path, not the basename — a fixture in a temp dir containing "lore" looked lore-bearing. Behavior is right, name was wrong; renamed LORE_HINT and documented.

5. The ironic example — fixed. Fair catch. canon-evaluation's example evidence row now cites Starweave per the vault, rather than the disputed name, in a rubric about unrecorded naming drift.

Verification: 14/14 fixtures pass · 19 legitimate canon, research and pattern files clean (zero false positives) · 19 real errors still caught across .claude/CLAUDE.md, guardians.md, elara.md, VII_THE_GODBEAST_CODEX.md.


Generated by Claude Code

The first CI run after the fixtures landed reported the test suite as canon
drift — 5 errors, all of them fixture data. The test file deliberately
contains a superseded godbeast in assignment position, a mispaired god, and
a shifted frequency ladder, precisely so the checks can be proven to fire;
its filename also matches the lore-file hint, so --changed picked it up.

SELF widened to the .claude/ci/lore-lint prefix so both the linter and its
fixtures are excluded from every check.

Verified: CI's exact command (--changed --base origin/main) now clean across
5 files; 14/14 fixtures still pass; 17 real errors still caught in
guardians.md and .claude/CLAUDE.md; the fixture file stays excluded even
when named explicitly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT
@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review

Read through the actual diff (18 files: two skills, the linter + its test suite, the CI workflow, two .arcanea/lore/ staging docs, and docs/worldbuilding/ research/pattern content). Focused the code review on .claude/ci/lore-lint.mjs since that's the only executable code in the PR; skimmed the lore/skill docs for internal consistency.

Code quality

  • lore-lint.mjs is well-structured: pure functions per check, execFileSync (array args, not shell strings) so there's no command-injection surface from PR filenames, and permissions: contents: read on the new workflow. Good security hygiene throughout.
  • The comments earn their keep — they explain why (e.g. the single-god-per-row guard, the \bgate\b context requirement, the self-referential-fixture exclusion) rather than restating the code, which matches this repo's own commenting convention.
  • I hand-verified GATE_FREQUENCIES and GODBEASTS in the linter against .arcanea/lore/CANON_LOCKED.md:52-63 on main — they match exactly, including the Starweave/852 Hz row. That matters more than usual here since a linter whose own constants drift from the vault "certifies the drift" (as the file's own header comment says).
  • As a nice bit of external validation: .claude/CLAUDE.md on main really does currently list Amaterasu as Shinkami's godbeast and calls the 852 Hz Gate Shift — exactly the drift class this PR's gate is designed to catch (issue Repo-wide superseded-name sweep: Thessara→Vaelith, Amaterasu→Source #98). This PR doesn't fix that file, which is fine per the PR description (it's tracked as a precondition, not silently left).

Potential bugs

None found in the check logic. I traced checkSupersededNames, checkGateFrequency, checkGodbeastPairing, checkTierBanner, and checkLockClaim against the fixtures and against the assignment-position/table/heading matching logic by hand and didn't find a case where the described behavior and the regex diverge.

Test coverage

  • lore-lint.test.mjs is genuinely good — black-box against the real CLI, covers both the false-positive guarantees (multi-pair rows, the full ten-gate table, prose mentions, changelog rows, Gate names used as ordinary words) and the true positives (superseded name in assignment/heading, wrong pairing, wrong frequency, the exact shifted-ladder defect from .claude/CLAUDE.md).
  • Gap: checkLockClaim (the lock-claim rule — "only CANON_LOCKED.md may declare content LOCKED") has zero test coverage. It's WARN-only so it can't fail CI even if broken, but the regex is narrow (/\bLOCKED\s*(✅|:)/ and, independently, /this (document|file|section) is/i on the same line) and it's untested, so there's no lock-in for it the way the other rules have. Given the PR's own stated design rule ("near-zero false positive... verified by hand once is verified until the next edit"), this one rule doesn't currently meet that bar. Worth a follow-up test or two (a true-positive and a near-miss).

Performance

addedLinesFor shells out to git diff once per changed file rather than one batched diff. Fine at lore-PR scale (a handful of files), and CI's own comment claims ~1s total, but it's a minor inefficiency if a PR ever touches dozens of lore-adjacent files at once. Not blocking.

Security

Nothing concerning — read-only permissions on the workflow, no eval/dynamic code execution, no network calls, args passed as arrays to execFileSync. The workflow's paths: trigger is intentionally broad (**/*.md, **/*.ts, etc.) but that's explained inline and isLoreFile() cheaply short-circuits, so it doesn't run expensive work on unrelated PRs.

Everything else

The skill docs (lore-release-gate, canon-evaluation) and NAMING_REGISTRY.md are internally consistent with each other and with what's already on main (verified docs/worldbuilding/{BEST_PRACTICES,SYSTEM,TASTE,...}.md, referenced throughout, already exist). The PR description is unusually thorough about what it found and what's deliberately deferred (issues #98/#99, the Starweave/Shift naming question) — that kind of honesty about scope is exactly what this kind of infra PR should have.

Overall: solid, well-tested tooling PR with clear rationale and good security practice. Only real ask is closing the lock-claim test gap before or shortly after merge.

Review round 6: checkLockClaim was the one rule with zero fixtures. Being
WARN-only makes that worse rather than better — it cannot fail CI even if
it silently breaks, so nothing would notice.

Probing it to write the tests turned up a real inaccuracy in its own
comment. It claimed 'only CANON_LOCKED.md may declare content LOCKED', but
the repo contradicts that: .arcanea/lore/MAGIC_SYSTEM.md:3 carries
'Status: LOCKED ✅ — Approved by Frank (Creator) 2026-06-23' and is
correct. The narrow regex was already skipping that case, so behavior was
right for a reason the comment did not state.

Corrected the rationale and the warning text: a status banner records a
lock decision that was made; the rule targets running prose that asserts
one. Behavior unchanged.

Four fixtures: prose assertion warns without failing the build, an
approved status banner does not warn, a tier-vocabulary listing does not
warn, and the vault is exempt by path.

Not changed: addedLinesFor shells out per changed file rather than
batching one diff. Real, but the lore-PR file counts make it invisible
(~1s runs) and the batched form is harder to attribute per file.

Verified: 18/18 fixtures pass; MAGIC_SYSTEM.md clean; CI's --changed
command clean across 5 files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT

frankxai commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

Round 6 addressed in 45155d6. You were right that lock-claim was the one rule below the bar the rest of the file sets — and being WARN-only makes that worse, not better: it can't fail CI even if it silently breaks, so nothing would ever notice.

Probing it to write the tests turned up an inaccuracy in the rule's own comment. It claimed "Only CANON_LOCKED.md may declare content LOCKED" — but the repo contradicts that. .arcanea/lore/MAGIC_SYSTEM.md:3 carries:

Status: LOCKED ✅ — Canonical magic grammar. Approved by Frank (Creator) 2026-06-23.

That's legitimate: locked content lives outside the vault once the Creator has approved it. The narrow regex was already skipping it, so the behavior was right — for a reason the comment didn't state. Corrected both the rationale and the warning text: a status banner records a lock decision that was made; the rule targets running prose that asserts one. No behavior change.

Four fixtures now cover it — one true positive (prose assertion warns, exits 0), and three near-misses that must stay silent (an approved status banner, a tier-vocabulary listing, and the vault itself by path exemption). 18/18 pass.

On the addedLinesFor per-file git diff — real, and I left it. At lore-PR file counts it's invisible (~1s runs), and the batched form makes per-file line attribution harder for a marginal win. Noting it here so the next person finds the reasoning rather than rediscovering the tradeoff.

Thanks for hand-verifying GATE_FREQUENCIES/GODBEASTS against the vault — that's exactly the check that matters most, since a linter whose constants have drifted certifies the drift instead of catching it.


Generated by Claude Code

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review: #97 — worldbuilding research/patterns + lore canon gate

This is a large, mostly docs/lore PR; I focused the code review on the one piece of actual logic — .claude/ci/lore-lint.mjs, its test suite, and .github/workflows/lore-canon.yml — plus a scan of the rest for structural issues.

Code quality

lore-lint.mjs is well-built: pure pattern matching, no model dependency, and every heuristic (assignment-position matching, single-subject-row guards, historical-log exemption) is explained inline with the false positive it was written to avoid. execFileSync('git', [...]) is used instead of a shell string, so there's no command-injection surface even though --base gets interpolated into an argv array. The test file (lore-lint.test.mjs) is black-box against the real CLI (not just the internal check functions), which is the right call since it also exercises arg parsing and file filtering — and it encodes real regressions ("House voice" near a frequency, a two-pair table row), not just happy paths.

Bug / gap found

.github/workflows/lore-canon.yml's path filter doesn't match what the linter itself checks. LORE_EXT in lore-lint.mjs:113 includes js and mjs:
```js
const LORE_EXT = /.(md|mdx|ts|tsx|js|mjs|json|yaml|yml)$/;
```
but the workflow's on.pull_request.paths only triggers on **/*.md, **/*.mdx, **/*.yaml, **/*.yml, **/*.json, **/*.ts, **/*.tsx, plus the two workflow/lint files themselves — no generic **/*.js or **/*.mjs. The workflow's own comment ("lore lives in .md, .ts, .json and .yaml across this repo") already tacitly drops js/mjs, so this may be intentional, but it means a lore-bearing .js/.mjs file changing anywhere else in the repo will never trigger the canon check at all — silent, not caught by --changed, not even attempted. Given the whole point of this gate is "a godbeast name hardcoded in a TypeScript constant is exactly as wrong as one in a markdown table" (the comment's own words), it's worth either dropping js/mjs from LORE_EXT to match reality, or adding **/*.js/**/*.mjs to the trigger paths for consistency.

Minor / non-blocking

  • checkGateFrequency's alias warning (gate-name, e.g. "Shift" vs. the vault's "Starweave") only fires when a Hz value is on the same line as the gate name (the function returns early if HZ doesn't match). A bare "the Shift Gate" mention with no frequency nearby stays silent. Probably fine given the near-zero-false-positive design goal, but worth knowing this check has narrower reach than "every unrecorded gate-name divergence."
  • No test exercises addedLinesFor() / the --changed --base diff-hunk cursor parsing directly — arguably the most bug-prone function in the file (manual cursor tracking through @@ -x,y +a,b @@ hunks). The other checks are thoroughly covered; this one only gets indirect coverage via the "explicitly named file is checked" test, which bypasses --changed entirely.

Security

No concerns. execFileSync avoids shell injection, the workflow uses permissions: contents: read (least privilege), and \${{ github.base_ref }} interpolated into the final run: step is a real branch name constrained by the target repo rather than attacker-controlled PR input, so the usual Actions script-injection risk doesn't really apply here.

Test coverage

Good for the four canon checks (superseded names, gate frequency, godbeast pairing, tier banner, lock-claim) — both the "must fire" and "must not fire" cases are exercised, which is exactly right for a linter whose entire value proposition is staying trusted. See the gap noted above for the diff-parsing path.

Content (lore/docs)

Didn't do a full canon audit of the 21k words of research/pattern docs — that's what the new canon-evaluation rubric and adversarial pass are for, and the PR description says it already went through 4 rounds of review with that in mind. Skimmed the diff for stray TODOs/dead links; found none. The .claude/skills/*/SKILL.md files reference docs/worldbuilding/{SYSTEM,BEST_PRACTICES,TASTE,...}.md and .arcanea/lore/* paths — confirmed all of those already exist in the repo, so the gate doesn't point at anything missing.

Overall: solid, well-reasoned tooling addition that mirrors the existing web-excellence gate pattern. The one real fix worth making is the workflow-trigger vs. LORE_EXT mismatch above.

Review round 7. The workflow's paths filter omitted **/*.js and **/*.mjs
while LORE_EXT includes them, so lore-bearing JS anywhere else in the repo
would never trigger the check — not caught, not even attempted.

Resolved by widening the trigger rather than narrowing LORE_EXT, because
the repo really does carry lore in those files:
  packages/chrome-extension/tests/chrome-extension.test.mjs
    → 2 superseded godbeast names + 2 shifted-ladder frequencies
  scripts/generate-arcanea-visuals.js → 2 gate-name warnings
Dropping js/mjs would have discarded real coverage to fix a mismatch.

The trigger list is now a verified superset of LORE_EXT, and the comment
says why it must stay one: a type the linter checks but the trigger omits
is drift nothing ever looks at, which is worse than a rule that fires
wrongly — a false positive announces itself, a missing trigger does not.

Also covers addedLinesFor(), previously the least-tested and most
bug-prone function (hand-tracked cursor through @@ hunks). Two tests build
a real temp git repo and assert the ratchet property in both directions:
newly added drift is reported, pre-existing drift on the base commit is
not. Backwards either way and the linter either fails every PR on
inherited debt or silently passes new drift.

Documented the gate-name alias check's narrow reach: it returns early
without a Hz value on the line, so a bare 'the Shift Gate' is not
reported. Widening means matching a common word in prose for a WARN
nobody can act on until /lock-decision settles the name.

Verified: 20/20 fixtures; trigger/LORE_EXT consistency checked
programmatically; CI --changed command clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT

frankxai commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

Round 7 addressed in 71abf38. The trigger/LORE_EXT mismatch was real, and checking which way to resolve it settled the question decisively.

You offered two fixes — the evidence picks the wider trigger. The repo genuinely carries lore in JS:

packages/chrome-extension/tests/chrome-extension.test.mjs
  ERROR:61  "thessara" is superseded canon
  ERROR:63  "amaterasu" is superseded canon
  ERROR:196 foundation Gate is 174 Hz, not 396 Hz
  ERROR:205 fire Gate is 396 Hz, not 528 Hz
scripts/generate-arcanea-visuals.js
  WARN:38,89 gate-name (Shift vs the vault's Starweave)

Dropping js/mjs from LORE_EXT would have discarded real coverage — including a second copy of the shifted-ladder defect — to fix a consistency complaint. Added **/*.js and **/*.mjs to the trigger instead, and the list is now a programmatically verified superset of LORE_EXT.

The comment now says why it must stay a superset, which I think is the durable point: a false positive announces itself; a missing trigger doesn't. A rule that fires wrongly gets reported and fixed. A file type the linter checks but the workflow never hands it produces silence that is indistinguishable from cleanliness.

addedLinesFor coverage — agreed, and it was the right thing to point at. Hand-tracked cursor through @@ hunks, and it was carrying the ratchet guarantee with only indirect coverage. Two new tests build a real temp git repo and assert both directions: newly added drift is reported, pre-existing drift on the base commit is not. Getting that backwards fails every PR on inherited debt (and the linter gets switched off) or silently passes new drift.

Gate-name reach — confirmed and documented, not widened. You're right that it returns early without a Hz value, so a bare "the Shift Gate" stays silent. Widening means matching a common English word in prose to emit a WARN nobody can act on until /lock-decision settles Starweave vs Shift. Recorded in the comment with a note to revisit once that's decided.

20/20 fixtures pass; CI's --changed command clean.


Generated by Claude Code

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review: lore canon gate + worldbuilding libraries (PR #97)

I focused verification on the mechanical/tooling pieces (.claude/ci/lore-lint.mjs, its test suite, and the CI workflow) since those have objectively checkable correctness, security, and performance properties. The lore content itself (research knowledgebases, pattern libraries, NAMING_REGISTRY.md, THESSARA.md) is already covered by the 4 documented review rounds in the PR description, so I didn't re-litigate register/canon judgment calls there.

Code quality

lore-lint.mjs is well-structured: single-purpose check functions, and comments that consistently explain why a regex is shaped the way it is (e.g. the \bgate\b context-gating to avoid flagging ordinary words like "voice"/"source"/"shift"/"crown"). It follows the existing web-guidelines-lint.mjs sibling pattern the repo already uses for the web-excellence gate, which is good consistency.

Bugs

None found. I specifically traced the trickiest paths against the new content added in this same PR, since a linter that flags the prose describing its own target problem would be an obvious own-goal:

  • ENCYCLOPEDIA_IA.md:1734 ("id: godbeast-thessara", "godbeasts/thessara.md", "...Source-Gate godbeast, and carries...") — none of these trip checkSupersededNames. The \b(godbeast|beast|companion|bonded)\b assignment pattern requires an immediate [:=] after only whitespace/*/quotes, so godbeast-thessara (hyphen breaks it) and godbeasts/… (plural breaks the trailing \b) both correctly fall through. ENCYCLOPEDIA_IA.md isn't on SUPERSEDED_ALLOWLIST, so this wasn't a free pass — it held up on its own.
  • The GATE_ALIASES substitution order (aliases resolved into lower before the frequency loop runs) is correct and covered by the --all-lines alias test.
  • addedLinesFor's hand-rolled hunk-cursor arithmetic (the part the comments flag as most bug-prone) has direct temp-git-repo coverage for both "new drift caught / pre-existing drift ignored" and "clean when added lines carry no drift" — good, since that's exactly the kind of off-by-one that's easy to get wrong and easy to not notice.

Performance

Fine — pure line-scan regex checks over only the files/lines the ratchet selects, no network or model calls, no obvious quadratic blowup. Matches the PR's stated ~1s CI runtime.

Security

  • git() uses execFileSync with an argv array rather than a shell string, so the --base value (origin/${{ github.base_ref }} in CI) can't be used for shell injection even though it's templated from workflow context.
  • .github/workflows/lore-canon.yml sets permissions: contents: read — correctly minimal, no write scope needed for a read-only lint job.
  • No secrets, no third-party actions beyond actions/checkout and actions/setup-node.

Test coverage

Above average for a CI lint script: fixtures run through the real CLI as a black box (not just internal functions), explicit regression tests for previously-found false positives (the two-pair table row, "House voice" near a frequency range), and dedicated coverage for the --changed ratchet using a real temp git repo rather than mocked diffs. The WARN-only checks (lock-claim, missing-tier) are tested too, which is easy to skip since they can't fail CI.

Minor / non-blocking

  • actions/checkout@v4 and actions/setup-node@v4 are pinned by major-version tag, not commit SHA. Only worth raising if that's this repo's convention elsewhere for supply-chain hardening — not a regression this PR introduces.
  • .claude/ci/lore-lint.test.mjs isn't picked up by npm run test:all (packages/*/tests/*.test.mjs); it only runs via the explicit node --test step in lore-canon.yml. That's consistent with how the sibling web-guidelines lint is wired, so flagging for awareness rather than requesting a change.

Nothing blocking. This is solid, carefully defended tooling.

Round 8 raised action pinning. Verified the convention before acting: zero
of the ~78 'uses:' lines across .github/workflows/ are SHA-pinned, so
SHA-pinning this one file alone would be inconsistent rather than safer.
Supply-chain hardening is a repo-wide decision; left as-is with the
reasoning recorded inline.

What the check did surface: this workflow was on checkout@v4 /
setup-node@v4 (inherited from the web-excellence workflow it mirrors)
while 35 and 17 other workflows respectively are already on v5, and the v4
actions emit a Node-20 deprecation warning in this workflow's own logs.
Bumped to match the majority and clear the warning.

No change for the second note: .claude/ci/lore-lint.test.mjs is outside
the 'packages/*/tests/*.test.mjs' glob of test:all, which is correct — it
runs via the explicit node --test step in this workflow, and the sibling
web-guidelines lint has no in-repo test file at all.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT
@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review

This is an unusually well-engineered PR — the review notes (11 rounds, documented in the PR body) show real adversarial iteration, and it shows in the code. Focused mainly on the mechanical parts (lore-lint.mjs, its tests, the CI workflow) since that's the actual "code" in an otherwise doc/lore-heavy PR.

Code quality

  • .claude/ci/lore-lint.mjs is genuinely good: every heuristic (namesGate, checkSupersededNames, checkGateFrequency) documents why it's shaped the way it is, with real false-positive/false-negative incidents cited inline (e.g. the guardians.md regression at line ~334-ish comment, the CONTINUITY_AUDIT.md:329 ambiguity case). That context will save the next editor a lot of rediscovery.
  • Uses execFileSync with argv arrays throughout (git(...) helper) rather than shelling out with string interpolation — no injection surface there.
  • The CI workflow correctly passes github.base_ref through env: rather than interpolating into the run: string, with a comment explaining why it did so even though the risk is low here. Good habit to model even when not strictly required.

Potential bugs / edge cases

  • checkGateFrequency's ambiguity bail-out (lore-lint.mjs:334, gatesNamed.length > 1) counts bare gate-name words, not gate-context ones, specifically to fix a real false positive. But because most Gate names double as ordinary English words ("source", "voice", "fire", "crown"), a line like "The Crown Gate resonates at 963 Hz, though the source remains unclear" would now bail out and silently miss the real Crown-frequency error, since "source" appears bare. This looks like a known, deliberate trade-off (there's a test locking in the narrower fix), but it's worth a one-line comment or a follow-up fixture acknowledging the residual false-negative class, so it doesn't read as an oversight to the next person who hits it.
  • checkLockClaim (lore-lint.mjs:412) matches LOCKED\s*(✅|:) and this (document|file|section) is independently on the same line rather than requiring adjacency. Very unlikely to misfire in practice (no test breaks it), but it's a slightly looser match than the "assignment position only" discipline applied elsewhere in the file — not blocking, just noting the inconsistency in rigor.

Performance

  • lore-lint.mjs itself is cheap (single-pass regex checks, only over files git reports as changed). Not a concern.
  • The CI trigger (.github/workflows/lore-canon.yml) fires on **/*.md|.mdx|.yaml|.yml|.json|.ts|.tsx|.js|.mjs repo-wide — i.e., nearly every PR in this monorepo will spawn this job, relying on isLoreFile()'s content probe to short-circuit cheaply for non-lore files. That's a reasonable and explicitly-justified trade-off (breadth over precision, documented in the workflow comments and locked in by a test), but worth being aware of as a small tax on CI queue time/minutes across the whole repo, not just lore PRs.

Security

  • No secrets, no shell injection, permissions: contents: read is correctly scoped down on the new workflow. Nothing to flag.

Test coverage

  • .claude/ci/lore-lint.test.mjs is excellent — 28 black-box fixtures through the real CLI (not re-implemented regex copies), explicit must-fire/must-not-fire pairs, a real temp-git-repo test for the --changed ratchet (including the "cannot diff against base → fail loudly, not exit 0" regression), and a coupling test asserting lore-canon.yml's trigger paths stay a superset of LORE_EXT. That last one is a nice bit of defense against exactly the kind of silent drift this whole PR is about.
  • One gap: no fixture directly exercises the ambiguity-bailout false-negative scenario noted above (a Gate-context name with a wrong frequency plus a second Gate name used only as an ordinary English word on the same line). Given how much care went into locking in the other edge cases, this one seems worth a fixture too — even if the answer is "still WARN-free, documented limitation."

Non-code (lore/docs)

Didn't attempt to fact-check the ~21k words of research/pattern content against CANON_LOCKED.md — that's precisely what the canon-evaluation rubric and adversarial pass this PR introduces are for. Spot-checked that the docs/worldbuilding/{SYSTEM,BEST_PRACTICES,TASTE,...}.md files referenced by the new SKILL.mds actually exist in the repo already, so the skill's cross-references resolve correctly.

Overall: solid, well-tested infrastructure change with unusually thorough self-documentation of its own edge cases. The two notes above are minor and non-blocking.

…m adjacency

Review round 13, two findings, both real. The first is a regression I
introduced one round earlier.

1. Round 12 fixed a false positive by counting BARE gate names for the
   ambiguity bail-out. That created a false-negative class: "The Crown
   Gate resonates at 963 Hz, though the source remains unclear" bails on
   the ordinary word "source" and misses a real Crown error. Reproduced
   before fixing.

   Both blunt answers are wrong and both were tried — counting
   Gate-context names blames the wrong gate (CONTINUITY_AUDIT.md:329),
   counting bare names suppresses real errors. The actual signal is
   proximity: a frequency belongs to whichever gate is quoted next to it
   ("Voice (528 Hz"), not to one mentioned elsewhere in the sentence. Bail
   only when a DIFFERENT gate name sits in the 25-char window immediately
   before the number. Resolves both directions.

2. checkLockClaim matched "LOCKED :" and "this document is" independently
   on one line. Flagged as an inconsistency in rigor rather than a bug —
   probed it, and it does misfire: "Status: LOCKED — this document is
   superseded by the vault" warned that the file was claiming to be
   locked, which is the opposite of what it says. Now requires adjacency,
   with sentence breaks excluded from the gap.

Not changed: the reviewer suggested a fixture documenting the
ambiguity-bailout false negative as a limitation. Fixed the limitation
instead, and pinned both directions with fixtures.

Verified: FN case now errors; CONTINUITY_AUDIT.md and MAGIC_SYSTEM.md
still clean; .claude/CLAUDE.md still 17+3 and guardians.md 8+1 (no
detection lost); full FP sweep over research/patterns/lore = 0 errors;
31/31 fixtures; --changed clean across 18 files.

Note: the first attempt at this commit message used backticks, which the
shell expanded as command substitution and silently deleted two quoted
phrases. Amended via a message file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT
@frankxai
frankxai force-pushed the claude/arcanea-lore-worldbuilding-552iy3 branch from 3b83bca to 6f6d150 Compare August 8, 2026 05:17

frankxai commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

Round 13 addressed in 6f6d150. Both findings were real, and the first is a regression I introduced one round earlier.

1. The ambiguity bail-out — you called it a deliberate tradeoff; it was a bug I'd just created. Reproduced your exact example:

The Crown Gate resonates at 963 Hz, though the source remains unclear.
→ clean   (Crown is 741 Hz — a real error, silently suppressed by the stray word "source")

Round 12 had swung the bail-out from counting Gate-context names to counting bare names, to kill a false positive. Both extremes are wrong, and I'd now shipped each in turn:

Bail-out counts Failure
Gate-context names FP — CONTINUITY_AUDIT.md:329 blames Foundation for Voice's 528 Hz
Bare names FN — your Crown example, suppressed by an ordinary English word

The real signal is neither: it's proximity. A frequency belongs to whichever gate is quoted next to itVoice (528 Hz — not to one mentioned elsewhere in the sentence. Now bails only when a different gate name sits in the 25-character window immediately before the number. Both directions resolve, with no detection lost.

2. checkLockClaim's loose matching — you flagged it as an inconsistency in rigor rather than a bug. It misfires. Probed rather than assumed:

"Status: LOCKED ✅ — this document is superseded by the vault."  → WARNS

A banner saying the document is superseded was flagged as claiming it's locked — the opposite of what it says. Now requires adjacency, with sentence breaks excluded from the gap. The three near-miss cases stay silent and both true positives still fire.

On your suggested fixture — you proposed documenting the ambiguity-bailout false negative as a known limitation. I fixed the limitation instead, and pinned both directions with regression fixtures. A documented false negative in a drift linter still reads as "clean" to whoever runs it.

One process note against myself: the first push of this commit used backticks in the message, which the shell expanded as command substitution and silently deleted two quoted phrases — leaving "checkLockClaim matched and independently". Amended via a message file (6f6d150, force-with-lease on this feature branch). Worth flagging given the subject matter: a commit that garbles its own explanation of a subtle regex fix costs the next reader exactly the time the comment was written to save.

Verified: FN case now errors · CONTINUITY_AUDIT.md and MAGIC_SYSTEM.md clean · .claude/CLAUDE.md still 17+3, guardians.md still 8+1 · full FP sweep across research/patterns/lore = 0 errors · 31/31 fixtures · --changed clean across 18 files.


Generated by Claude Code

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review

I read through the tooling/code portions in detail (.claude/ci/lore-lint.mjs, .claude/ci/lore-lint.test.mjs, .github/workflows/lore-canon.yml, the two skills, and the CLAUDE.md addition), and skimmed the research/pattern docs.

Code quality

The linter and its tests are unusually well engineered for a CI script. A few things stood out positively:

  • Every non-obvious design decision (why assignment-position matching, why proximity-based frequency attribution, why the content probe, why --changed fails loudly instead of exiting 0 on a bad base ref) is documented inline with a regression it fixes, not just asserted.
  • The false-positive guarantee ("near-zero FP or it gets switched off") is treated as a first-class property with dedicated must-not-fire fixtures, not just must-fire ones.
  • lore-lint.test.mjs avoids the classic self-testing trap — the file-selection test drives the real CLI through a temp git repo instead of re-declaring copies of LORE_EXT/LORE_PATH/etc. and asserting against those (the PR body notes this was an actual bug it caught: editing the real regexes left copies-based assertions green).
  • checkGodbeastPairing correctly bails on multi-god table rows instead of guessing column correspondence — good instinct to prefer a false negative over a confident false positive there.

Potential bugs / gaps (minor, worth a look)

  1. checkLockClaim punctuation coupling (lore-lint.mjs, ASSIGNMENT_PATTERNS/lock-claim regex): the pattern requires LOCKED to be immediately followed by or :. A sentence like "This document is now LOCKED." (period, no emoji/colon) won't trigger the warning. Since this rule is WARN-only, the blast radius is low, but if the intent is to catch any inline lock assertion, this leaves a narrow gap — worth a fixture either confirming it's intentional or tightening it.
  2. Hand-maintained sync between GATE_FREQUENCIES/GODBEASTS/SUPERSEDED and the vault: the file is explicit that "if these disagree with the vault, the vault wins and this file is the bug," which is the right call to document, but there's no automated check that the transcribed constants match CANON_LOCKED.md itself (only the "smoke-test against the vault" CI step, which runs the linter over the vault, not a diff of the constants against the vault's table). A future locked rename (like the Starweave/Shift question this PR already surfaces) will again require a human to remember to update this file. Not blocking — just flagging as the next likely source of the exact drift class this tool exists to catch.
  3. addedLinesFor's hand-rolled hunk-cursor tracking is, as the code comments themselves acknowledge, the most bug-prone piece here. Coverage looks solid (ratchet-in/ratchet-out tests, the fatal-base-ref test), but this is worth extra scrutiny in future diffs since a cursor-tracking bug would silently under- or over-report without any obvious symptom.

Performance

Non-issue at this scale — --changed limits full-file reads to the diff's touched files, and looksLoreBearing's content probe only runs for files that miss the cheap path-based filters. Fine for CI.

Security

  • lore-canon.yml correctly passes github.base_ref through env: rather than interpolating into the run script — right call even though the PR body itself notes the practical risk is low (target branch, not attacker-controlled head ref).
  • permissions: contents: read is appropriately minimal for a read-only lint job.
  • No secrets, tokens, or credentials found anywhere in the diff (verified via grep across the full diff).

Test coverage

Strong. 28 fixtures covering must-fire, must-not-fire, alias resolution, the workflow/linter extension-coupling guard, and the --changed ratchet semantics via real temp git repos. The PR body's own account of 11 review rounds — including catching a false-positive fix that silently introduced a false negative (guardians.md regression) — is exactly the kind of adversarial iteration this class of tool needs, and it shows in the fixture set.

Scope note (not a blocker)

This PR bundles three fairly different things: ~21k words of research/pattern documentation, two new skills, and the lint/CI tooling. Given the tooling is the part with actual failure modes (bugs, false positives/negatives, CI behavior), a split where the lint script + tests + workflow could be reviewed in isolation might have been easier to vet — though given the extensive review history already documented in the PR description, this is a minor process note rather than something to act on now.

Overall: this is a careful, self-critical piece of tooling with genuinely good test discipline. The findings above are minor refinements, not blockers.

Review round 14, two actionable findings.

1. The lock-claim regex required LOCKED to be followed by a check-mark or
   a colon, which is punctuation coupling rather than meaning: "This
   document is now LOCKED." went unflagged. Verified, then widened.

   Widening naively would have inverted the rule — without a guard,
   "This section is not LOCKED" starts firing. The gap is now captured and
   rejected when it contains not / never / no longer. Both directions
   pinned with fixtures.

2. Nothing verified the linter's transcribed GATE_FREQUENCIES and
   GODBEASTS against the vault. The file states "if these disagree with
   the vault, the vault wins and this file is the bug", but that was an
   assertion with nothing behind it, and the CI smoke test does not close
   the gap: it runs the linter OVER the vault, so it only catches drift
   the checks already model. A locked rename the linter has never heard of
   passes silently — which is the exact drift class this tool exists to
   catch, in the tool itself.

   Added a test that parses the vault's Gate table and diffs it against
   the constants, asserting count and every frequency and god-to-godbeast
   pairing. This is the same machine-enforced-coupling move already
   applied to the workflow trigger and LORE_EXT; the constants deserved it
   more, since they are the reference every check depends on.

   Verified the guard can actually fail: setting crown to 999 produced
   "GATE_FREQUENCIES.crown must be 741 per the vault" and 3 failing tests.
   A guard that cannot go red is worse than none.

Acknowledged without action: addedLinesFor remains the most bug-prone
piece and is worth extra scrutiny in future diffs; the three-in-one scope
of this PR (research, skills, tooling) would have been easier to vet split
apart, which is a fair process note for next time rather than something to
restructure now.

Verified: 33/33 fixtures; FP sweep over research/patterns/lore = 0 errors;
.claude/CLAUDE.md still 17+3; --changed clean across 18 files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT

frankxai commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

Round 14 addressed in 636bfcb. Both findings were real; the second is the best structural catch since round 10.

1. Lock-claim punctuation coupling — confirmed and widened, with a wrinkle.

This document is now LOCKED.     → fires=0   (real gap)
This file is LOCKED ✅            → fires=1

Requiring or : after LOCKED was coupling to punctuation rather than meaning. But widening naively inverts the rule: without a guard, "This section is not LOCKED" starts firing. The gap is now captured and rejected when it contains not / never / no longer. Both directions have fixtures.

2. Nothing enforced "the vault wins" — and the smoke test doesn't close it. This is the one worth dwelling on. The linter's header says "if these disagree with the vault, the vault wins and this file is the bug." That was an assertion with nothing behind it. And you're right that the CI smoke step doesn't help: it runs the linter over the vault, so it only catches drift the checks already model. A locked rename the linter has never heard of passes in silence — the exact drift class this tool exists to catch, occurring inside the tool.

Added a test that parses CANON_LOCKED.md's Gate table and diffs it against the constants — count, every frequency, every god→godbeast pairing. Same machine-enforced-coupling move already applied to the workflow trigger and LORE_EXT; the constants deserved it more, since every check depends on them.

And I verified the guard can fail, because a green test that can't go red is worse than none:

$ sed -i 's/  crown: 741,/  crown: 999,/' .claude/ci/lore-lint.mjs
$ node --test .claude/ci/lore-lint.test.mjs
  GATE_FREQUENCIES.crown must be 741 per the vault
  # pass 30  # fail 3

This also has a direct bearing on the open Starweave vs Shift decision: whichever way /lock-decision goes, the vault changes and this test now fails until the linter is updated to match. That coupling was previously carried by human memory.

Acknowledged without action: addedLinesFor remains the most bug-prone piece and warrants extra scrutiny in future diffs — agreed, and it's why it got the temp-git-repo tests. On the three-in-one scope: fair, and I'd split it next time. The tooling is genuinely the only part with failure modes; the research and skills are inert by comparison. Not worth restructuring now given the review history is attached here.

Verified: 33/33 fixtures · FP sweep across research/patterns/lore = 0 errors · .claude/CLAUDE.md still 17+3 · --changed clean across 18 files.


Generated by Claude Code

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review

Reviewed the diff directly (gh pr diff 97), focusing on the code/tooling surface: .claude/ci/lore-lint.mjs, .claude/ci/lore-lint.test.mjs, .github/workflows/lore-canon.yml, and the two new SKILL.md files, with lighter verification of the lore/docs content.

Code quality

Strong. lore-lint.mjs is unusually well-commented for a linter — every heuristic explains why it's shaped the way it is (proximity-based frequency attribution, single-god-per-row pairing checks, the alias-resolution pass for Starweave/Shift), which will save the next editor from re-discovering the false-positive cases by hand. The self-exemption (SELF_PREFIX) and allowlist (SUPERSEDED_ALLOWLIST) are narrowly scoped and justified inline rather than left as unexplained special cases.

Test coverage

This is the standout part of the PR. lore-lint.test.mjs is black-box against the real CLI (not against copied regexes), and it specifically tests for regressions that are easy to reintroduce silently — e.g. the "FP fix that quietly creates an FN" case where tightening namesGate() for one false positive took guardians.md from 10 findings to clean undetected. The --changed ratchet is tested through a real temp git repo (not mocked diff strings), and there's a guard test that mechanically enforces the workflow trigger paths stay a superset of LORE_EXT — nice, because that coupling is exactly the kind of thing that silently drifts.

One gap: the linter's own constants are diff-checked against CANON_LOCKED.md (good — catches future drift), but I didn't see a test asserting GATE_ALIASES stays in sync if the vault ever renames Starweave back or adds a second unresolved alias. Low priority given it's a single hardcoded entry today.

Potential bug (minor, low severity)

checkLockClaim's negation guard:

const claim = line.match(/this (document|file|section) is([^.;]{0,15})LOCKED\b/i);
if (claim && !/\b(not|never|no longer)\b/i.test(claim[2])) { ... }

This won't catch contracted negation. For "This document isn't LOCKED, still evolving.", the outer regex matches with claim[2] capturing "n't, still " (or similar) — but the negation test looks for the literal word not (or never/no longer), and "isn't" doesn't contain that substring (i-s-n-'-t has no o). So a contracted negation would fire a false lock-claim WARN. Since this rule is WARN-only (never fails the build) and the phrasing is a fairly narrow edge case, this is cosmetic rather than blocking — but worth a follow-up if you want to keep the "near-zero false positive" bar the linter otherwise holds itself to.

Security

No concerns. execFileSync is used with argument arrays throughout (no shell interpolation), the workflow uses permissions: contents: read, pull_request (not pull_request_target) so it never runs with elevated token against untrusted forked code, and the one place a GitHub context value flows into a shell command (github.base_ref in the "Check changed lore files" step) is passed via env: rather than string-interpolated into the run: block, which is the correct pattern.

Performance

No concerns — the linter is pure regex/line-scanning over already-filtered file lists, --changed mode limits the check to added lines only, and the workflow itself is dependency-free (package-manager-cache: false is correctly set since the job installs nothing).

Scope note

This PR is explicitly diagnostic/tooling — it documents real drift (superseded names, shifted Gate frequencies) in .claude/CLAUDE.md, packages/os/src/canon-validator.ts, and several other files, but deliberately doesn't fix them here, tracking that as issue #98 instead. That's a reasonable call given the PR description's reasoning (a half-swept repo is worse than uniform drift), but worth flagging for anyone skimming: the drift the gate discovered is still live on main after this merges.

I didn't independently deep-audit the ~21k words of lore content in docs/worldbuilding/research/ and patterns/ for canon accuracy — that's better verified by actually running lore-lint.mjs and the canon-evaluation rubric this PR introduces, which per the PR description has already been done (28/28 fixtures passing, clean run against the vault).

Overall: a well-scoped, defensively-coded, thoroughly-tested addition that mirrors the existing web-excellence gate pattern in this repo. Nice work.

…t the vault

Review round 15, both findings actioned.

1. The lock-claim negation guard missed contractions. "This document
   isn't LOCKED, still evolving." leaves "n't " in the captured gap, which
   contains no literal "not", so the word-boundary test passed it through
   and warned that the file was claiming to be locked — the inverse of
   what it says. Reproduced, then matched the apostrophe form explicitly.

   Behaviour matrix now: "isn't LOCKED" silent, "is not LOCKED" silent,
   "is now LOCKED." fires, "is LOCKED check-mark" fires.

2. GATE_ALIASES was maintained by memory. An alias exists to resolve a
   name the vault does NOT use onto one it does; if a lock-decision ever
   settles Starweave vs Shift the other way, "shift" becomes canonical and
   the alias silently starts rewriting a correct name into a wrong one.

   Added a test asserting every alias key is absent from the vault's Gate
   names and every alias target is present. Verified it fires on exactly
   the scenario that matters: renaming the vault row to Shift produced
   '"shift" is an alias but the vault now names a Gate that' plus the
   constants-drift failure, 2 tests red. Vault restored and confirmed
   untouched afterwards.

This closes the loop the previous round opened. The constants, the alias
table, and the workflow trigger are now all machine-checked against their
sources rather than against someone remembering.

Verified: 35/35 fixtures; lock-claim matrix as above; vault byte-identical
after the destructive probe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT

frankxai commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

Round 15 addressed in e06ceea. Both findings actioned; you were right on the contraction, and the alias gap turned out to matter more than "low priority."

1. Contracted negation — reproduced exactly as you predicted.

This document isn't LOCKED, still evolving.   → fires=1   (false positive)
This document is not LOCKED.                  → fires=0

"isn't" leaves n't in the captured gap, which contains no literal not, so the word-boundary guard passed it through and warned that the file was claiming to be locked — the inverse of what it says. Now matches the apostrophe form explicitly. Matrix: isn't LOCKED silent · is not LOCKED silent · is now LOCKED. fires · is LOCKED ✅ fires.

2. GATE_ALIASES sync — you flagged this as low priority given the single entry. It's the highest-stakes entry in the file. An alias exists to resolve a name the vault does not use onto one it does. If /lock-decision settles Starweave vs Shift the other way, shift becomes canonical — and the alias silently starts rewriting a correct name into a wrong one, while emitting a warning telling authors to change correct text. That's not a stale constant; it's the linter actively arguing against the vault.

Added a test asserting every alias key is absent from the vault's Gate names and every target is present. Verified it fires on precisely that scenario — I renamed the vault row to Shift and got:

"shift" is an alias but the vault now names a Gate that — the alias would rewrite a correct name
GATE_FREQUENCIES.shift must be 852 per the vault
# pass 33  # fail 2

Vault restored and confirmed byte-identical afterwards (git diff --quiet clean) — I wasn't going to leave a destructive probe on the one file agents are forbidden to touch.

Together with last round, the loop is closed: the constants, the alias table, and the workflow trigger are all now machine-checked against their sources rather than against someone remembering. Whichever way the Starweave/Shift call goes, the tooling fails loudly until it matches.

On your scope note — agreed and worth amplifying for anyone skimming: the drift this gate discovered is still live on main after this merges. 17 errors in .claude/CLAUDE.md, 8 in guardians.md, 3 in canon-validator.ts, 4 in chrome-extension.test.mjs. That's issue #98, deliberately not bundled here, and the sweep now has a completion test rather than a judgement call.

Verified: 35/35 fixtures.


Generated by Claude Code

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review

Scope: this PR is almost entirely additive docs/lore (research + pattern libraries, ~21k words) plus one real piece of executable code — .claude/ci/lore-lint.mjs, its test suite, and .github/workflows/lore-canon.yml. I focused there, since that's what actually runs; the docs/skills read as well-organized and internally consistent (spot-checked the TASTE.md reference and the Starweave/Shift divergence note — both resolve correctly — and CLAUDE.md's new section correctly stops short of "fixing" the pre-existing drift it documents, deferring to #98 as stated).

Bugs / correctness

  1. checkGateFrequency only ever inspects the first NNN Hz match on a line (const hz = line.match(HZ) is non-global). A line/table-row naming two Gate/Hz pairs — the same shape checkGodbeastPairing already special-cases via its "multi-pair table row" guard — only has its first pair checked; the function returns once that's resolved. Example: | Unity | 963 Hz | Crown | 400 Hz | (Crown should be 741 Hz) goes fully undetected: the first Hz match (963, correct for Unity) gets bound to crown on the dictionary-order pass, the proximity/"hijacker" check correctly sees unity in the preceding text and bails, and the function returns without ever looking at 400 Hz. No fixture covers two Hz values on one line, so this gap isn't caught by the otherwise-thorough suite.

  2. Index bookkeeping across the alias rewrite goes stale. hz.index is captured once from line/lower before the GATE_ALIASES loop runs, which then does lower = lower.replace(/\bshift\b/g, 'starweave') — changing the string's length whenever "shift" appears earlier in the line than the Hz value. The later before = lower.slice(hz.index - 25, hz.index) proximity window is then sliced against the post-mutation string using the pre-mutation index, so it no longer lines up with the text actually preceding the number. Hand-tracing a couple of lines through this mostly lands on the right answer by luck (either the shift lands outside where a hijacker name would sit, or the current gate's own value check short-circuits before the hijacker logic runs), but the window is objectively wrong, and a line combining the "shift" alias with a second nearby Gate name could plausibly flip a bail/fire decision. Cheap fix: re-derive hz/hz.index from lower after the alias substitution instead of reusing the pre-substitution match.

Both are narrow rather than exotic, but worth a couple more fixtures (two Hz values on one line; the alias combined with a neighboring gate name) given how much the PR leans on "near-zero false positive, verified by fixtures" to justify trusting this linter as the mechanical backstop.

Code quality / best practices

  • git()/execFileSync usage throughout (array argv, no shell) and the workflow's env: BASE_REF indirection instead of interpolating ${{ github.base_ref }} directly into run: are the right call, even though the current input (target branch name) isn't attacker-controlled.
  • --changed failing loudly (exit 1) when the base ref can't be resolved, instead of silently reporting "clean," is a good call and is explicitly tested.
  • The self-referential guards (SELF_PREFIX, SUPERSEDED_ALLOWLIST) are well-justified with concrete prior incidents rather than speculative hardening.
  • Minor: looksLoreBearing() does a synchronous full-file read for every candidate path that doesn't already match the cheap name/path heuristics. Fine at current scale; worth knowing it's O(unmatched files) full reads if the repo grows a lot.

Performance

No concerns — a --changed-scoped regex linter over a bounded diff, ~1s per the workflow's own comment, no dependency install.

Security

Nothing found. permissions: contents: read is scoped down on the new workflow, git invocations avoid shell interpolation, and the one workflow-context value that flows into a shell command (BASE_REF) goes through env: rather than direct interpolation.

Test coverage

The 28-fixture black-box suite (real CLI invocations, not re-implemented regex copies) is genuinely strong — especially the --changed ratchet tests (pre-existing vs. newly-added drift) and the guard that the workflow's trigger paths stay a superset of LORE_EXT. The clearest gaps are the two above: a fixture with two Gate/Hz pairs on one line, and one combining the "shift" alias with a second nearby Gate name. Everything else — assignment-position detection, historical/changelog-row exemption, lock-claim negation handling (including the contraction edge case), tier-banner detection — is well exercised.

Overall

Solid, unusually well-documented tooling PR (the review-rounds log in the description is a good practice worth keeping up). The two checkGateFrequency findings reduce detection recall in a narrow multi-value-per-line scenario rather than introducing false positives, so they don't block merge, but they're worth closing before leaning on this linter as the sole mechanical backstop for canon drift.

Review round 16, both findings reproduced first, then actioned.

1. checkGateFrequency only ever inspected the first NNN Hz match on a
   line. "| Unity | 963 Hz | Crown | 400 Hz |" came back clean — Crown is
   741 Hz and nothing looked. Multi-pair table rows are exactly the shape
   the vault and its mirrors use, so this was a silent miss in the most
   common layout.

2. The window used to attribute a frequency was derived from the raw
   line, but the alias substitution rewrites the string in place and
   moves every later index. The offsets survived the rewrite, so the
   attribution window slid by four characters per replacement.

Both fall out of one fix. The function now walks every Hz on the line
with matchAll and, for each, finds the nearest Gate mentioned before it
in the rewritten string — so there are no stale indices left to go
stale. gateMentionIndex() collects the position of the earliest Gate
reference in any of the forms the corpus actually uses (X Gate, Gate of
X, Gate N: X, Gate X, and a bare table cell) rather than a bare name
match, which is what kept the false-positive rate at zero across the
previous rounds. The hijacker check now looks at the text between the
owning Gate and its number instead of a fixed window behind the number.

Two fixtures added, and both were checked against a mutant rather than
assumed: reintroducing first-match-only turns the multi-pair fixture red,
and flipping owner selection from nearest-preceding to nearest-following
turns both new fixtures red along with eight existing ones.

Verified: 37/37; CONTINUITY_AUDIT clean; .claude/CLAUDE.md 17 errors /
3 warnings and guardians.md 8 / 1, both unchanged; --changed clean over
18 files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT

frankxai commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

Round 16 actioned in 2bc68fe. Both findings reproduced before touching anything.

1. Only the first NNN Hz per line was checked. Confirmed exactly as described:

$ printf '# T (STAGING)\n\n| Unity | 963 Hz | Crown | 400 Hz |\n' > /tmp/b1.md
$ node .claude/ci/lore-lint.mjs /tmp/b1.md
lore-lint: clean (1 file checked)

Crown is 741 Hz. This matters more than the example suggests — multi-pair rows are the layout the vault's own tables and the mirrors use, so the most common shape in the corpus had a blind spot from the second column onward.

2. Stale indices across the alias rewrite. Also confirmed: hz was captured from line at 293, the alias substitution rewrote lower in place at 316, and the attribution window was sliced out of lower at hz.index at 337 — four characters of slide per Shiftstarweave replacement.

Both collapse into one fix rather than two. The check now walks every frequency with matchAll over the rewritten string and attributes each to the nearest Gate mentioned before it, so there is no surviving pre-rewrite index left to go stale — your "re-derive after the substitution" suggestion, taken structurally instead of as a patch. The hijacker guard moved with it: it now inspects the text between the owning Gate and its number rather than a fixed 25-char window behind the number.

The recall/precision tension you flagged is why gateMentionIndex() exists rather than a bare name match. It collects the earliest position among the four forms the corpus actually writes (X Gate, Gate of X, Gate N: X, Gate X) plus a bare table cell. Bare-name matching is what created the false negative back in R12 — "The Crown Gate resonates at 963 Hz, though the source remains unclear" was suppressed by the ordinary English word source.

On the two fixtures you named — I checked they can actually fail rather than assuming it, since twice in this PR a change to this function silently destroyed detection while every test stayed green:

Mutant Result
matchAll → single match (the reported bug) multi-pair fixture red, 36 pass / 1 fail
owner selection nearest-preceding → nearest-following both new fixtures red, plus 8 existing, 27 pass / 10 fail

Source restored from backup and re-verified after each probe.

Verification: 37/37 fixtures. CONTINUITY_AUDIT.md still clean (that file has a known pre-existing FP vector, so it's the one I watch). .claude/CLAUDE.md still 17 errors / 3 warnings, guardians.md still 8 / 1 — both unchanged, which is the check that catches an FP fix quietly becoming an FN. --changed --base origin/main clean over 18 files. Lore canon green on 2bc68fe.

PR body refreshed — it still said 11 rounds / 28 fixtures.


Generated by Claude Code

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review

Reviewed the full diff (20 files, +3886/-0). This is the lore-release-gate counterpart to the existing web-excellence gate, and it's built with the same discipline: a machine-checkable ratchet plus two judgment-phase skills, wired into CI. Overall this is unusually high-quality work — I verified several of its own factual claims against the repo and they all held up. Details below.

Correctness — verified, not just read

One real bug (low severity)

checkLockClaim's negation guard:

const negated = (gap) => /\b(not|never|no longer)\b/i.test(gap) || /^n[''`]?t\b/i.test(gap.trim());

I checked the actual bytes of the character class — it contains the straight apostrophe (0x27) twice plus a backtick, not a straight-and-curly (U+2019) pair. So "isn't" typed with a smart/curly right-single-quote (common from word processors or autocorrect) won't be recognized as a contraction, and checkLockClaim will fire a lock-claim warning on a sentence that's actually negating lock status — the exact inversion bug the surrounding comments describe having already fixed for the straight-quote case. Impact is small since lock-claim is WARN-only and never fails the build, but it's a one-character fix ([''’\]`) and worth a fixture using a curly apostrophe, given how carefully every other edge case in this function was tested.

Design observations (not blocking)

  • CANON_TOKENS (the content-probe fallback for isLoreFile) includes some very ordinary English words as godbeast values — Sol, Source, Otome. This is called out and accepted in the comments ("over-selecting is cheap and safe"), and I agree it's safe given how narrow the actual check functions are — but it does mean the content probe will read and pattern-match a large fraction of the repo's .md/.ts/.js/... files on every --changed run (anything mentioning "source"). Worth an eye on CI wall-clock as the repo grows; today it's described as ~1s so it's a non-issue.
  • SUPERSEDED_ALLOWLIST covers docs/worldbuilding/research/ but not docs/worldbuilding/patterns/, even though the new ENCYCLOPEDIA_IA.md (a patterns file) discusses superseded Thessara/Amaterasu extensively in illustrative YAML (form: "Thessara", reason: "... Source replaces Amaterasu"). It doesn't trip the linter today only because those examples use keys (form, reason) outside ASSIGNMENT_PATTERNS — that works, but is incidental rather than by design. If a future edit in patterns/ illustrates a godbeast:-keyed YAML example with a superseded name, it'll fire. Not asking for a change, just flagging the coupling.
  • Nice detail: lore-canon.yml passes base_ref through env: rather than interpolating into the run string — the comment even explains why it did this despite the low actual risk (target branch, not attacker-controlled head ref). Good security hygiene and a good example for future workflows in this repo.

Test coverage

Exceptional for a CI linter of this scope: 37 black-box fixtures driven through the real CLI (not the internal functions), explicit regression tests for every documented false positive that was found and fixed, a real-temp-git-repo test for the --changed ratchet (added-drift-caught / pre-existing-drift-ignored), a guard that the workflow trigger stays a superset of LORE_EXT, and a guard that the linter's transcribed canon constants still match the vault. That last one closes a real gap — "if these disagree with the vault, the vault wins and this file is the bug" is now enforced, not just asserted in a comment.

Content (the lore additions)

NAMING_REGISTRY.md and THESSARA.md both correctly self-apply the gate's own rules — STAGING banner, STAGING LOG, mystery-ledger entries marked oblique/never-answer, alternates-considered table. CANON_LOCKED.md itself is untouched, matching the PR description. The CLAUDE.md addition mirrors the existing "Web design gate" section's structure closely, which keeps the two load-bearing gates easy to read side by side.

Nothing here blocks merge; the one real fix (curly-apostrophe negation) is small and well-contained. Nice work — the comment density in lore-lint.mjs reads like a changelog of every false positive that was found and killed, which is exactly what keeps a linter like this trustworthy long-term.

Review round 17. The lock-claim negation guard's apostrophe class held two
ASCII 0x27 and a backtick — the duplicate is almost certainly a U+2019 that
got normalised to straight at some point, which is how the gap survived
round 15 looking correct while only covering half the cases.

Consequence: prose typed in a word processor or through autocorrect writes
"isn't" with U+2019, the contraction is not recognised as a negation, and
the file gets warned for claiming to be LOCKED when the sentence says the
opposite. Reproduced before fixing:

  This document isn’t LOCKED, still evolving.
  WARN [lock-claim] Prose declaring this file LOCKED.

WARN-only, so it never failed a build — but an inverted reading is the
failure mode this check is least able to afford, since the whole point is
telling apart a file that claims lock status from one that disclaims it.

Fixture added and verified to guard: reverting the class to ASCII-only
turns it red, 37 pass / 1 fail.

Verified: 38/38; the curly sentence clean; --changed clean over 18 files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT

frankxai commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

Good catch on the apostrophe class — fixed in 0a03631.

Confirmed the bytes before touching it, since that's the kind of claim that's easy to get backwards:

$ grep -n "const negated" .claude/ci/lore-lint.mjs | cat -A
469:  ... /^n[''`]?t\b/i.test(gap.trim());$

cat -A renders U+2019 as M-bM-^@M-^Y, so both really are ASCII 0x27. My read is that the duplicate was a curly quote originally and got normalised to straight somewhere between writing and committing — which is why round 15 looked like it covered both forms while covering one. That's a slightly worse failure than a plain omission, because the class looks deliberate.

Reproduced:

$ printf '# T (STAGING)\n\nThis document isn\xe2\x80\x99t LOCKED, still evolving.\n' > /tmp/curly.md
$ node .claude/ci/lore-lint.mjs /tmp/curly.md
WARN /tmp/curly.md:3 [lock-claim] Prose declaring this file LOCKED.

Fixed to ['’], which both adds the curly form and drops the redundant entry. Fixture added and checked against a mutant — reverting the class to ASCII-only turns it red (37 pass / 1 fail), so it's a real guard rather than a passing assertion. 38/38 now; --changed` still clean over 18 files.

Agreed it's low severity — lock-claim is WARN-only and never fails the build. Worth fixing anyway because an inverted reading is the one thing this particular check can't afford: its whole job is separating a file claiming lock status from one disclaiming it, and it was doing the opposite on a common input.

On the two design observations, both correct, and I'm deliberately not changing either:

  • CANON_TOKENS over-selection — agreed, Sol/Source/Otome pull in a wide slice of the repo. That's the intended trade: R10 established that path heuristics cannot enumerate where canon hides (13 of this PR's own 20 files were invisible), and over-selection is cheap because the check functions themselves are narrow. Worth watching wall-clock; ~1s today.
  • SUPERSEDED_ALLOWLIST covers research/ but not patterns/ — you're right that ENCYCLOPEDIA_IA.md escapes because its illustrative YAML uses form:/reason: keys outside ASSIGNMENT_PATTERNS, not because the allowlist exempts it. I think that's the behaviour I want rather than an accident to paper over: a godbeast:-keyed assignment carrying a superseded name in patterns/ would be a genuine error worth firing on, even in a craft-documentation file. Widening the allowlist would buy quiet at the cost of the one case that should still bite. Leaving the coupling as-is and noting it here so the next person hits the reasoning rather than the surprise.

Also confirming your spot-checks match mine: the vault cross-check of GATE_FREQUENCIES/GODBEASTS is now machine-enforced by a test rather than left to hand-verification (added R15), so if those ever diverge from CANON_LOCKED.md the suite fails rather than the linter quietly asserting stale canon.

Still open for Frank, unchanged: Starweave vs Shift for the 852 Hz Gate, the #98 sweep greenlight, and U3 as a payment ledger.


Generated by Claude Code

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review

Reviewed the diff against main (20 files, +3900). This is an unusually well-engineered PR — the description already documents 16 rounds of self-review with reproduced findings, and it shows. I focused on the mechanical artifacts (lore-lint.mjs, its test suite, the workflow, the two skills) since that's where correctness/security/perf actually live; the research and pattern-library prose is Frank's editorial call and is explicitly gated behind /lock-decision before it becomes canon.

Potential bug: checkGateFrequency's "hijacker" heuristic can suppress real errors

In .claude/ci/lore-lint.mjs, owner selection is correctly restricted to Gate-context mentions (namesGate/gateMentionIndex require "X Gate", "Gate of X", etc. — precisely to avoid false positives from Gate names that are also ordinary English words, per the file's own extensive commentary on this).

But the hijacker check that runs after owner/value are matched does not apply that same Gate-context restriction:

const between = lower.slice(ownerIndex, m.index).slice(-25);
const hijacker = Object.keys(GATE_FREQUENCIES).find(
  (g) => g !== owner && new RegExp(`\\b${g}\\b`).test(between)
);
if (hijacker) continue;

This does a bare \b${g}\b match — no Gate-context requirement. So a genuinely wrong frequency can be silently swallowed if any other Gate name happens to appear as an ordinary word in the preceding ~25 characters. Traced by hand:

The Foundation Gate resonates like a heart at 285 Hz.

Foundation's real frequency is 174 Hz, so 285 Hz (Flow's) should be a gate-frequency ERROR. But owner = foundation, and the 25-char window before 285 Hz is ...s like a heart at heart matches the bare hijacker regex even though it's being used as an ordinary word ("like a heart"), not as a Gate reference. The result: the real error is suppressed.

This is exactly the failure mode the rest of the file is explicit about caring most about ("a silent miss ... is the failure mode that matters most" — see the checkGodbeastPairing comment and the R12–14 test). None of the existing fixtures exercise a bare, non-Gate-context Gate-name-as-ordinary-word sitting between the owner and the value (the closest test, "a stray Gate word elsewhere in the sentence does not suppress a real error," places the stray word after the Hz match, which the hijacker slice never sees).

Suggested fix: require the hijacker candidate to appear in Gate context too (reuse namesGate-style matching on the between slice, or just check gateMentionIndex positions within the window) rather than a bare word-boundary match. Given how carefully everything else here treats "Gate name that's also an English word," this looks like an oversight rather than a deliberate tradeoff.

Minor: duplication between gateMentionIndex and namesGate

Both encode the same four Gate-context regex forms almost verbatim (one returns earliest index, one returns boolean). Worth collapsing into one function that returns an index (or -1), with the caller doing !== -1 where a boolean is wanted — reduces the risk of the two drifting apart on a future edit (which is the exact kind of drift this whole PR is about preventing).

What's solid

  • Ratchet design (--changed): correctly scoped to added lines only, fails loudly (exit 1) rather than silently passing when the base ref can't be diffed — good instinct, and it's tested.
  • Assignment-position matching for superseded names and table-row godbeast pairing requires exact cell equality after trim, which avoids the false-positive class you'd expect from naive substring matching (e.g., "Source" being both a Gate name and Shinkami's godbeast name doesn't appear to create false positives in practice, because of the exact-match requirement).
  • Self-verifying tests: the "linter constants match the vault" and "workflow trigger is a superset of LORE_EXT" tests are genuinely good ideas — they turn "someone remembered to keep two files in sync" into a CI-enforced invariant.
  • Workflow security: permissions: contents: read (least privilege), and github.base_ref is passed through env: rather than interpolated into the run: string, avoiding shell-injection via a crafted branch name. Reasonable call to not SHA-pin given the repo's existing convention is 100% tag-pinned.
  • Skill docs cross-references (docs/worldbuilding/{TASTE,BEST_PRACTICES,SYSTEM,SKILL,AGENTS,README,COLLABORATIONS}.md, .arcanea/lore/{FACTIONS,STARLIGHT_CORPS_CODEX,MAGIC_SYSTEM}.md) all resolve on main — checked directly, nothing dangling.
  • Docs/prose I sampled (ARTIFACTS.md) matches the described voice and cites its own sources consistently.

Process note (not a blocker)

This bundles CI/tooling infrastructure with ~21k words of new prose content across research + pattern libraries. The PR body's rationale for building all three layers together is reasonable, but it does mean a reviewer has to either trust the prose wholesale or spend real time on content neither the linter nor tests can check. Given everything here is additive STAGING content with CANON_LOCKED.md untouched and promotion gated behind /lock-decision, that's a reasonable tradeoff — just flagging it so it's a conscious choice, not a default.

Test coverage

Excellent — 37 fixtures, black-box through the real CLI, covering the ratchet, alias resolution, workflow/linter coupling, and several real regressions with comments explaining what each one caught. The one gap is the hijacker false-negative above.

No security concerns beyond what's already addressed above. Nothing else stood out as a correctness or performance issue in the linter, tests, or workflow.

…sGate in

Review round 18. Both findings reproduced first.

1. Owner selection requires a Gate name to appear in Gate context, because
   every Gate name is also an ordinary English word. The hijacker check
   that can override an owner did a bare word-boundary match, which
   contradicted that premise and silently ate real errors:

     The Foundation Gate resonates like a heart at 285 Hz.
     lore-lint: clean

   Foundation is 174 Hz. The simile "like a heart" suppressed a genuine
   mistake. Candidates now have to name a Gate the same way an owner does.

   The window widened from the last 25 characters to the whole span from
   owner to number. A character budget can bisect "voice gate" and hide
   the context the check now depends on, and the span is short by
   construction: the owner is the nearest preceding mention, so anything
   further back already lost the ownership race.

2. gateMentionIndex and namesGate encoded the same four Gate-context forms
   verbatim, one returning an index and one a boolean. Two regex lists
   that must stay identical are the drift this linter exists to catch, so
   namesGate is now a one-line wrapper. The per-form rationale moved into
   the surviving list rather than being dropped — those comments record
   which real errors each form was added to catch.

Writing the first fixture exposed a third, unreported gap. With the
hijacker check disabled entirely the suite stayed green and the corpus
was unchanged, so nothing tested the only case it exists for: a Gate named
BOTH before the owner and again between owner and number. gateMentionIndex
returns a Gate's earliest mention, so that Gate loses the ownership race
even while sitting immediately before the number, and only the hijacker
saves it. Now pinned:

     The Voice Gate and the Foundation Gate differ; the Voice Gate holds 528 Hz.

528 Hz is Voice's and correct; without the check the line is blamed on
Foundation. A heuristic no test can kill is one nobody can safely edit.

Both fixtures checked against their own mutants: disabling the hijacker
turns the second red, reverting it to a bare-name match turns the first
red. 39 pass / 1 fail in each case.

Verified: 40/40; CONTINUITY_AUDIT and the vault clean; .claude/CLAUDE.md
17/3, guardians.md 8/1, canon-validator.ts 3/1, all unchanged; --changed
clean over 18 files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT

frankxai commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

Round 18 actioned in 66264ed. The hijacker finding is correct, and chasing it turned up a third gap neither of us named.

The reported bug. Reproduced exactly as traced:

$ printf '# T (STAGING)\n\nThe Foundation Gate resonates like a heart at 285 Hz.\n' > /tmp/hj.md
$ node .claude/ci/lore-lint.mjs /tmp/hj.md
lore-lint: clean (1 file checked)

Foundation is 174 Hz. A simile ate a real error. You put your finger on the actual inconsistency: owner selection requires Gate context because every Gate name is an ordinary English word, and then the check that can override an owner threw that requirement away. Fixed by reusing gateMentionIndex on the between-span.

I also widened the window from .slice(-25) to the whole owner→number span. A character budget can bisect voice gate and hide the very context the check now depends on, and the span is short by construction — the owner is the nearest preceding mention, so anything further back already lost the ownership race.

The third gap, which is the interesting one. Before adding the fixture I checked whether the hijacker was still reachable at all after R16 replaced presence-based ownership with proximity. Disabled it outright:

if (false && hijacker) continue;
→ 38 pass / 0 fail; CONTINUITY_AUDIT.md clean; guardians.md 8 errors / 1 warning

Green suite, unchanged corpus. So nothing tested the one case it exists for. It isn't dead, though — gateMentionIndex returns a Gate's earliest mention, so a Gate named before the owner loses the ownership race even when it is also named immediately before the number, and only the hijacker rescues it:

The Voice Gate and the Foundation Gate differ; the Voice Gate holds 528 Hz.
  hijacker disabled → 1 error   (blamed on Foundation — a false positive)
  hijacker enabled  → clean     (528 Hz is Voice's, and correct)

That's now a fixture. A heuristic no test can kill is one nobody can safely edit — and this PR has twice had a change to this function silently destroy detection, so an untested guard was going to be deleted eventually by someone reasoning exactly as I did five minutes earlier.

Both fixtures checked against their own mutants rather than assumed:

Mutant Result
hijacker disabled "named both before and between" red — 39/1
hijacker reverted to bare-name match "ordinary English Gate-word" red — 39/1

On the duplication. Agreed, and I checked equivalence before collapsing rather than assuming it: namesGate and gateMentionIndex encoded the same four forms plus the same table-cell rule, differing only in return type. namesGate is now gateMentionIndex(...) !== -1. The per-form rationale moved into the surviving list rather than being deleted — those comments record which real errors each form was added to catch (the labelled-list form is the one whose absence silently took guardians.md from 10 findings to clean in R5), and losing them is how the forms get "simplified" back out later.

On the process note — fair, and I'd rather it be a conscious choice than a default. The bundling was deliberate for the reason in the body: the research alone was documentation nothing reads. If you'd prefer it split, the clean seam is .claude/ci/ + .github/workflows/ + the two skills as one PR, and docs/worldbuilding/ + the two .arcanea/lore/ proposals as another — say the word and I'll split it, since the prose half is what actually needs Frank's editorial eye and it's currently sharing a review surface with a linter.

Verified: 40/40 fixtures. CONTINUITY_AUDIT.md and the vault clean. .claude/CLAUDE.md 17/3, guardians.md 8/1, canon-validator.ts 3/1 — all unchanged. --changed clean over 18 files.

Still open for Frank: Starweave vs Shift for the 852 Hz Gate, the #98 sweep greenlight, and U3 as a payment ledger.


Generated by Claude Code

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review

This PR is unusually well self-documented (17 review rounds logged in the description, each with the bug found and the fixture that pins it), which made this an easier review than the diff size suggests. I focused on the actual code — .claude/ci/lore-lint.mjs, its test suite, and the CI workflow — since the bulk of the diff is markdown content (research/pattern docs, skill definitions) that isn't really reviewable against "bugs/performance/security" criteria.

Code quality — lore-lint.mjs

Genuinely good. Every non-obvious regex or design choice has a comment explaining why, usually anchored to a specific false positive/negative it once produced (e.g. the "House voice" false-positive, the guardians.md labelled-list regression in R5). The gateMentionIndex/namesGate split avoids a duplicated regex list — a nice bit of self-awareness given the linter's entire purpose is catching duplication drift. checkGateFrequency's proximity-based ownership (nearest preceding Gate mention wins, hijacker check for an intervening Gate) is more sophisticated than a first pass would produce, and the comments show it was earned through real false negatives, not speculative hardening.

Potential issues

  • checkGateFrequency's global alias replace (lore-lint.mjs:331) rewrites every bare occurrence of shiftstarweave in the lowercased line before frequency matching, not just the Gate-context one. In practice this is harmless because gateMentionIndex only counts occurrences adjacent to the literal word "gate", so a stray prose "shift" surviving the rewrite won't manufacture a false Gate mention — but it's a bit more "rewrite the world and hope the guard downstream saves you" than a targeted substitution would be. Not asking for a change, just flagging it as the one part of the file that leans on a second layer of protection rather than being narrow by construction like everything else.
  • Godbeast pairing check is table-only (checkGodbeastPairing, lore-lint.mjs:394): a wrong pairing stated in prose ("Elara's godbeast is Kaelith") won't be caught — only | Elara | Kaelith |-shaped rows fire. Given the file's explicit near-zero-false-positive design goal this is a reasonable and clearly intentional trade-off, but worth knowing as a coverage gap if issue Repo-wide superseded-name sweep: Thessara→Vaelith, Amaterasu→Source #98's full sweep relies on this linter to catch every instance.
  • checkTierBanner runs against the full file contents regardless of --changed/added-lines filtering (it's called once per file before the per-line loop, not gated on added). So a file that's merely modified — not newly created — will get re-warned for a pre-existing missing tier banner even under the "ratchet, not audit" contract described in the file header. Since it's WARN-only (never fails the build) this doesn't break anything, just a minor inconsistency with the stated ratchet philosophy for the one check that isn't purely line-based.

Security

The CI workflow is careful in exactly the place that matters: base_ref is passed through env: rather than interpolated into the run: string, with a comment explaining why even though the risk is low (target branch, not attacker-controlled head ref). execFileSync (not exec) is used throughout the linter, so there's no shell-injection surface even if a path or ref did contain metacharacters. permissions: contents: read is correctly minimal for a read-only lint job. Nothing to flag here.

Test coverage

38 fixtures, and they cover the right shape of thing for a linter: not just "does it catch drift" but "does it catch drift while staying silent on the specific phrasings that look similar" (multi-pair table rows, Gate names as ordinary English words, historical changelog rows, curly-apostrophe contractions). The self-referential guards are the standout part — the linter constants match the vault they claim to mirror and every GATE_ALIAS is a non-canonical name mapping to a canonical one mean the fixtures can't silently drift from CANON_LOCKED.md the same way the rest of the repo did. The --changed-with-unresolvable-base test (exit 1, not a silent pass) is exactly the right thing to pin given the stated thesis of the whole tool.

One gap: I didn't see a fixture for two Gate aliases firing on the same line, or for the GATE_ALIASES replace interacting with a second, unrelated use of "shift" later in the same line (the case in the first bullet above) — low value given the current single-entry alias map, but worth a fixture if GATE_ALIASES ever grows past one entry, since that's exactly the kind of interaction this linter's own history shows gets missed until it's real drift.

Performance

Non-issue at this scale — line-by-line regex over markdown/source files, --changed scoped to a small file set per PR. looksLoreBearing's content probe reads full file contents for files that don't match the path/name heuristics, which is fine for a repo this size and is explicitly justified as "over-selection is cheap" in the comments.

Everything else

CLAUDE.md's new section mirrors the existing "Web design gate" section's structure and tone closely — good consistency, easy for future agents to find both gates the same way. The workflow's trigger path list is asserted (via test) to stay a superset of LORE_EXT, which closes off the exact failure mode (.mjs files invisible to CI) that R7/R10 found.

Nothing here blocks merge. The two coverage-gap notes (prose-form godbeast pairing, tier-banner ratchet scope) are worth a follow-up issue if not already folded into #98, but neither is a regression or a correctness bug in what's shipped.


🤖 Generated with Claude Code

…irings

Review round 19. Two of the three observations were coverage gaps worth
closing; both reproduced before acting.

1. checkTierBanner ran once per file outside the added-lines gate, so a
   file that was merely edited got re-warned about a banner the edit never
   touched — the "forced to fix everything you touch" behaviour this
   tool's header explicitly promises it does not have. WARN-only, so it
   never failed a build, but warning about untouched debt is how a linter
   earns being switched off.

   My first attempt to reproduce this came back clean and nearly refuted a
   correct finding: the fixture was at lore/, and the check only applies
   under .arcanea/lore/. Retested at the right path and it fired exactly
   as reported.

   The banner is a whole-file property, so it cannot be attributed to an
   added line the way every other check can. In ratchet mode it now applies
   to files the change CREATED: a new lore file must declare its tier, an
   existing one without a banner is pre-existing debt (#98), visible in a
   full audit but not on an unrelated edit. If the diff call fails the set
   stays null and every file is checked — over-warning is recoverable,
   silence is not.

2. The pairing check was table-only, so "Elara's godbeast is Kaelith" read
   clean. That matters more than a normal coverage gap because #98's
   completion test is a full-repo lore-lint run: a pairing written as a
   sentence would have survived the sweep and reported as swept.

   Added a narrow prose form — possessive, the literal word "godbeast",
   present-tense copula, and a named beast that is itself canonical. It
   does not reach past tense ("was Thessara" is a true sentence about a
   rename), unknown creatures, or questions. Checked against the whole
   repo before keeping it: zero hits across every file the linter selects,
   so it adds a guard without moving today's numbers.

The third observation — the alias rewrite being global rather than
targeted — is already pinned by the "alias appearing twice" fixture and
needs no change.

Mutants, each verified to turn the matching fixture red: removing the
prose check, moving the banner back outside the ratchet, and disabling the
banner check entirely. That last one matters — it means the ratchet fix
cannot be quietly degraded into never checking at all.

Verified: 43/43; CONTINUITY_AUDIT and the vault clean; .claude/CLAUDE.md
17/3, guardians.md 8/1, canon-validator.ts 3/1, all unchanged; --changed
clean over 18 files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT

frankxai commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

Round 19 actioned in 4319e74. Two of the three were worth closing; one needs no change.

Note this review landed against 0a03631 (it counts 38 fixtures and describes gateMentionIndex/namesGate as a split that "avoids a duplicated regex list" — at that head they were in fact duplicated verbatim, which the round-18 reviewer caught and 66264ed fixed by collapsing them). Doesn't affect the findings below.

1. The tier banner sat outside the ratchet — correct, and I nearly refuted it with a bad test. My first reproduction came back clean, and for a moment that looked like a refutation. The fixture was at lore/; checkTierBanner only applies under .arcanea/lore/. Retested at the right path:

$ node lore-lint.mjs --changed --base HEAD~1        # edited .arcanea/lore/x.md, banner untouched
WARN .arcanea/lore/x.md:1 [missing-tier] No canon tier declared in the first 15 lines.

Exactly the "forced to fix everything you touch" behaviour the header promises this tool doesn't have. The banner is a whole-file property, so it can't be attributed to an added line the way every other check can — in ratchet mode it now applies to files the change created. A new lore file must declare its tier; an existing one without a banner is pre-existing debt (#98), visible in a full audit but not on an unrelated edit. If the diff call fails the set stays null and every file gets checked: over-warning is recoverable, silence is the failure this file exists to prevent.

2. Prose-form pairings — closed, because of what it does to #98. Confirmed:

Elara's godbeast is Kaelith, bound at the Starweave Gate.   →  clean
| Elara | Kaelith |                                        →  1 error

You flagged this as a reasonable trade-off "if #98 relies on this linter" — it does, and that's the part that changes the calculus. #98's completion test is a full-repo lore-lint run, so a pairing written as a sentence would have survived the sweep and reported as swept. A completion test quieter than it sounds is worse than no completion test.

Added one narrow form: possessive + the literal word godbeast + present-tense copula + a named beast that is itself canonical. It doesn't reach past tense (was Thessara is a true sentence about a rename), unknown creatures, or questions — all four pinned as negative fixtures. Before keeping it I swept every file the linter selects across the whole repo: zero hits, so it adds a guard without moving today's numbers.

3. The global alias rewrite — no change. You reached the same conclusion I did, and it's already pinned: the fixture "an alias appearing twice, once as an ordinary word, resolves safely" exists precisely because the rewrite is broad and the downstream guard is what makes it safe. Your instinct that this is the one spot leaning on a second layer rather than being narrow by construction is right; the comment there says as much, and the fixture is what keeps that honest.

On the GATE_ALIASES-grows-past-one fixture: agreed in principle, but I'd rather not write a test against a hypothetical second entry whose shape I'd be inventing. The alias map has a machine-checked invariant against the vault (every key absent from it, every target present), so if it grows, that guard forces a look at this code. Happy to add the multi-alias fixture the moment there's a real second entry.

Mutants for the two new fixtures, each verified to fail:

Mutant Result
prose check removed prose fixture red — 42/1
banner back outside the ratchet banner fixture red — 42/1
banner check never runs banner fixture red — 42/1

That third one is the one I cared about: it means this fix can't be quietly degraded into never checking at all, which is the shape the previous three rounds kept finding.

Verified: 43/43 fixtures. CONTINUITY_AUDIT.md and the vault clean. .claude/CLAUDE.md 17/3, guardians.md 8/1, canon-validator.ts 3/1 — all unchanged. --changed clean over 18 files.

Still open for Frank, unchanged: Starweave vs Shift for the 852 Hz Gate, the #98 sweep greenlight, U3 as a payment ledger, and whether to split this PR at the tooling/prose seam.


Generated by Claude Code

…ts; keep Starweave ruling text

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLQUyDFhK9taQ7mBLht4aD
@frankxai
frankxai marked this pull request as ready for review August 20, 2026 20:08
@frankxai frankxai changed the title feat(worldbuilding): research knowledgebases + applied pattern libraries + the lore canon gate feat(worldbuilding): research knowledgebases + applied pattern libraries Aug 20, 2026
@frankxai
frankxai merged commit eb5c0dd into main Aug 20, 2026
10 of 13 checks passed
@frankxai
frankxai deleted the claude/arcanea-lore-worldbuilding-552iy3 branch August 20, 2026 20:11
@claude

claude Bot commented Aug 20, 2026

Copy link
Copy Markdown

Review

This PR is purely additive documentation/lore (14 new files, 0 deletions, no code): seven franchise research files, four cross-cutting pattern libraries, a synthesis doc, and two .arcanea/lore/ STAGING proposals (NAMING_REGISTRY.md, THESSARA.md). No packages/, app, or test code is touched, so the usual "run tests / check perf / security scan" categories don't really apply here — I've focused the review on correctness (factual accuracy, internal consistency, cross-reference integrity, IP discipline) since that's the equivalent of "bugs" for this kind of change.

What holds up well

  • NAMING_REGISTRY.md and THESSARA.md cross-checked cleanly against .arcanea/lore/CANON_LOCKED.md: Vaelith as Elara's godbeast, the Amaterasu→Source rename (2026-03-30), and the Starweave/852 Hz Gate name all match the vault exactly.
  • Both STAGING files are unusually disciplined about not overclaiming — they explicitly flag the repo-wide superseded-name cleanup (issue Repo-wide superseded-name sweep: Thessara→Vaelith, Amaterasu→Source #98) as a precondition for promotion rather than asserting it's done, and every proposal is clearly marked STAGING with a /lock-decision gate.
  • Across all 12 research/pattern files, relative markdown links resolve correctly, and spot-checked pattern→research citations matched their sources. No verbatim-copying/IP red flags — quotes are short, attributed, and used analytically; each file's own "IP Red Lines" section is more conservative than the prose actually needs.

Issues found

1. patterns/ARTIFACTS.md — STAGING content described as locked canon (most significant).
Lines 15, 39, 87, 160, and 243 refer to the materials system as "Tier 7 of the locked canon file" / "locked-file Tier 7" / part of the "Canon vault (read-only)." But CANON_LOCKED.md:247 headers that exact section TIER 7: ARCANEAN MATERIALS — THE RESONANCE SUBSTRATE (STAGING ⏳), with its own **STAGING TRUTH:** line and a ⏳ STAGING row in the approval log at line 361 — it is not locked. This matters because §0 and §3 of ARTIFACTS.md build the S1–S5 artifact proposals on Tier 7 as settled ground. It's also inconsistent with the PR's own standard: MAGIC_MECHANISMS.md gets this right (cites MAGIC_SYSTEM.md correctly as "LOCKED 2026-06-23" and never mislabels Tier 7/8), and the two .arcanea/lore/ files in this same PR are meticulous about the STAGING/LOCKED distinction. Worth a pass to reword "locked canon file" → "canon vault" and "locked-file Tier 7" → "Tier 7 (STAGING)" throughout.

2. patterns/ARTIFACTS.md:39 — mis-attributed citation.
The Warcraft runeblade line ("grants exactly the power its bearer wanted and spends him to do it") is cited to research/warcraft.md §Raid Boss & Encounter Lore, but that content actually lives under §Villain Craft (line 45; the Arthas/Frostmourne paragraph). §Raid Boss & Encounter Lore (line 53) only mentions Frostmourne in passing as loot lore. The fact itself is accurate, just under the wrong section pointer — minor, but worth fixing since these docs cite so precisely elsewhere.

3. Inconsistent citation link formatting.
ARTIFACTS.md hyperlinks every research citation as a proper markdown link. ENCYCLOPEDIA_IA.md, LANGUAGE_CRAFT.md, MAGIC_MECHANISMS.md, and research/SYNTHESIS.md cite the same files as plain backtick text with no link syntax. Nothing is broken, just a minor polish/navigability inconsistency within one PR.

Nothing here blocks merge on its own — #1 is the one I'd actually fix before staging further work on top of ARTIFACTS.md, since later agents/contributors may inherit the "Tier 7 is locked" framing and build on it without checking the vault themselves.

frankxai pushed a commit that referenced this pull request Aug 20, 2026
… adapters

Two follow-ups on this branch.

packages-ci.yml failed at setup on every single run, on both matrix legs, with
"Unable to locate executable file: pnpm" — before it built or tested anything.
setup-node@v5 defaults package-manager-cache to true and reads packageManager
from package.json, so it shells out to pnpm to resolve the cache directory.
This job ran setup-node BEFORE pnpm/action-setup, so pnpm was not yet on PATH.
test.yml already had the two steps the right way round; this one did not. Steps
swapped, and cache: 'pnpm' now stated explicitly rather than left to a default
whose behaviour depends on a file elsewhere in the repo.

This is the third appearance of the same setup-node@v5 default in this repo.
lore-canon.yml hit it during #97 and fixed it with package-manager-cache: false
because that job installs nothing. Here the job does install, so the fix is to
give it pnpm first rather than to switch the cache off.

Verified the job's own commands against the built tree: core + overlay-claude
296 pass 0 fail, cli integration 137 pass 0 fail.

Second: restore CRLF line endings on packages/aios/src/adapters/index.ts. The
file is CRLF on main. The scripted edit that replaced the four execute() bodies
wrote it back as LF, so every one of its 122 lines showed as changed and a
four-line semantic change read as a full-file rewrite. Caught in review. The
diff for that file is now 27 insertions and 9 deletions, which is what actually
changed. No behaviour difference — rebuilt and re-ran aios: 68 pass, 0 fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AomsLJvgAwwzuWph4bD7YT
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants