Every spell surface shipped React's dev build and died when left open — fixed - #106
Merged
Merged
Conversation
…said otherwise Ruleset "Main Gate" (`22059514`) was created 2026-09-01T18:39:36-07:00 — the day after the backlog item asking for it was filed — is `enforcement: active` on `~DEFAULT_BRANCH`, and requires the status check `gate`. It ran and passed on both PRs that went into `main` for the 3.0.0 release (#104 4m38s, #105 4m25s). Nobody closed the item, so for two weeks the tree asserted the opposite. ⛔ AND IT REACHED A PUBLISHED RELEASE NOTE. The 3.0.0 note lists as a standing limit that "`gate` has to be marked required in GitHub's settings, and no agent can do that — until a human does, a red `gate` does not block a merge", and adds that a check which caught two real defects on its first outings "is not yet allowed to block a merge". Both are false, and they are frozen in the `develop`→`main` merge commit body. The note cites the backlog item by filename for the detail, so closing that item with the evidence is the only correction that reaches a reader once the merge commit is published. ⚠ THE REASONING ERROR, WHICH IS THE PART WORTH KEEPING. The note said of the workflow header's assertion: "nothing in this tree can confirm it — treat that sentence as an instruction to a human, not a statement of fact." The first half was true; the conclusion was not. `gh api repos/<owner>/<repo>/rulesets` reads repo configuration directly, and the same session used `gh` freely for PRs, runs and logs the whole time. **"Not verifiable from the tree" was silently upgraded to "not verifiable"**, and a stale backlog item was then taken as current state because nothing in the tree contradicted it. The investigation's clause that seeded it ("not something an agent can land or verify") is corrected in place rather than rewritten: landing is Cole's, verifying is one API call. `.github/workflows/ci.yml`'s header was right all along and is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BiZGj5ZTDSZi1mB8YtuRcx
…tall, and the doubled gate Filed on Cole's ruling that the `@base-ui` bump is not its own ticket: "maybe we just do a general dependencies check — like, should we be upgrading the other dependencies aside from that? If so, we can do that all as one scope of work." `2026-09-15-dependency-upgrade-sweep.md` — measured rather than proposed. Eleven root packages and two per-spell ones are behind, and the gaps are not uniform: `lucide-react` is 29 minors back, `cn` (0.2 → 0.3) and digestify's `marked` (12 → 18) are majors, and `@base-ui/react` 1.6 → 1.8 is the one that already has a reason. Written with the three traps that make this non-trivial: every surface hash moves so `dist-check` wants a rebuild commit across nine spells; `bun` is pinned in two places that must agree and moves bundler output repo-wide with no source change, so the toolchain is its own step or a real regression is indistinguishable from a bundler diff; and Biome 2.4 → 2.5 plus Prettier 3.8 → 3.9 can red the gate and reformat committed markdown. `2026-09-14-nested-node_modules-shadows-the-root-install.md` — renamed from `2026-09-14-base-ui-1-8-bump-and-the-nested-node_modules-footgun.md`, which carried two items in one file. The upgrade half moved to the sweep; what stays is the mechanism. ⚠ Commit `c3ede8ae` cites the old filename, so the split is announced in the file's own header rather than left for a reader to work out. `2026-09-15-ci-runs-gate-twice-on-every-push.md` — `ci.yml` triggers on both `pull_request` and `push`, so a push to `develop` with the release PR open starts two identical ~4m30s runs. Measured across all three pushes of this release. ⛔ The item leads with the constraint rather than the fix, because the obvious fix is the dangerous one: `pull_request` is what makes the REQUIRED check report, and dropping it hangs every PR into `main` forever on "waiting for status to be reported". It also records that the conventional `concurrency` block keyed on `github.ref` does NOT deduplicate the pair — `push` and `pull_request` carry different refs for the same commit — so the paste-ready answer is wrong here. No fix chosen, and doing nothing is named as defensible if the minutes are not actually scarce. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BiZGj5ZTDSZi1mB8YtuRcx
…n 4.4.0 → 8.1.0
Ran `update-project-docs`'s v2.6→v2.7 migration (project-docs 3.12.0). Committed
on its own, before the backfill, so this reads as "the layer was installed"
rather than that plus 273 hand-written descriptions.
WHAT ARRIVED: `docs/SCHEMA.md` (the contract), `docs/index.md` (the catalog
skeleton, ours from now on), the `pdocs` CLI under `scripts/pdocs/`, a new
`docs/cycles/` type, frontmatter on 272 documents, 19 templates refreshed, and
`.project-docs.json`. Both version markers moved 4.4.0 → 8.1.0 together.
⛔ THE GATE IS DELIBERATELY OFF — `lint.adopting: true`. The lint wants
`description` on every document and the codemod does not write one, so a fresh
migration cannot pass. `pdocs report`: 353 missing fields across 273 of 290
documents, 272 of them `description`. Turning it on is a later commit.
⚠ `docs/releases/` HAD TO BE DECLARED, and the stop that forced it is the script
working. An undeclared folder is read as LIBRARY — the strictest tier — with
every page of unknown type, so the preflight refuses rather than let that happen
quietly. Declared `workbench` with `types.releases: "release"`, matching
backlog/ and reports/: a release note is a dated artifact, not library reference.
⚠ `scripts/pdocs/` IS EXCLUDED FROM BIOME AND PRETTIER, and that is not
cosmetic. `docs/SCHEMA.md` classes it OWNED — "overwrites, every time" — and
Biome had already reformatted 16 of its 17 files. The consequence is a standing
fight: every migration's `refreshOwned()` copies the scaffold's bytes back over
them, and the formatter then wants to redo all 16 on the next commit, forever.
Biome also rejects the upstream `(tags[tag] ??= []).push(...)` outright. Same
reason `.prettierignore` already pins `.claude/skills/acc/`.
⛔ AN EARLIER VERSION OF THE PARAGRAPH ABOVE STATED THE MECHANISM BACKWARDS, and
the correction came from project-docs upstream on the `spellbook-upgrade`
channel. It claimed the refresh would read the formatted files as locally edited
and KEEP our formatting, silently skipping the upstream fixes. That is the
SEEDED reconcile — templates, compared by hash against `docs/.pdocs-seed.json`.
Owned files have no such branch: `migrate-v2.8-to-v2.9.ts:415` is a bare
`cpSync(..., { recursive: true })`, and `migrate-v2.9-to-v2.10.ts` compares
bytes only to REPORT "already identical" versus "replaced". Verified in both
scripts rather than accepted. Fixes are never skipped; formatting is always
clobbered. Recorded because the wrong version was the more alarming one, and an
alarming claim that is false costs more than a dull one that is true.
⛔ AND THE LAYER DID NOT TYPECHECK HERE UNTIL UPSTREAM FIXED IT. Seven errors in
`docs-lint/index.ts`, all one class — an indexed or destructured value widened
to `T | undefined` — because this repo runs `noUncheckedIndexedAccess` and the
scaffold's own tsconfig did not. Filed as
ichabodcole/project-docs-scaffold-template#176 with an isolation table (7 errors
with the flag, 0 without); fixed upstream at `cce845e` and copied in, which is
byte-for-byte what an Owned refresh does.
⚠ NOT TAKEN ON TRUST. The upstream agent named three files; `diff -rq` over the
whole directory confirms exactly those three differ and nothing else, the diffs
are only the narrowing (`?.[1]`, `charAt`, a destructure default, three
statements, one dropped import), and the claim "zero errors under your tsconfig"
was re-run here rather than believed. The repo's own ward now reports 0 errors
across 35/35 areas and 661/661 files.
⚠ THE OBVIOUS SHORTCUT WAS TRIED AND IS NOT AVAILABLE. Excluding
`scripts/pdocs` from `tsc` reds `type-check-ward`'s coverage cell (D42) —
644 examined of 661 — which is exactly what that ward exists to catch. The
justification comment written for it was also false: gate-honesty scans
`skills + src`, not `scripts/`, so the blindness would have been invisible
rather than printed. Reverted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BiZGj5ZTDSZi1mB8YtuRcx
… its 25 pages Step 2 and 3 of the v2.6→v2.7 guide's "After the script". `pdocs report` goes from 353 missing fields across 273 documents to ZERO, and `pdocs orphans` from 25 to none. The gate stays off (`lint.adopting: true`) — turning it on is the next commit, deliberately separate. WRITTEN BY SEVEN SUBAGENTS, ON COLE'S CALL that descriptions do not need heavy reasoning: haiku for the formulaic bulk (backlog, project artifacts, sessions, investigations, fragments, reports) and sonnet for the library tier (architecture, playbooks, memories, the root pages) where a wrong description misleads future recall rather than merely reading flatly. Sharded into disjoint file sets so nothing raced. ⛔ THEIR REPORTS WERE NOT TAKEN AT FACE VALUE, AND THAT FOUND SIX REAL DEFECTS. Every `lifecycle` VALUE CHANGE — as opposed to a filled-in blank — was checked individually against the repository, because a wrong one is silent: it reads as a considered judgement forever. - **Four shipped projects were marked `draft`** by the projects-a agent, whose stated reasoning was that "none were marked implemented, indicating ongoing or preparatory work" — inferring status from the ABSENCE of a marker rather than from the tree. glamour-acc-l0, glamour-conversion and grapevine-conversion all merged (`389d088f`, `e3d80dc5`, `8fe85c12`) and shipped in 3.0.0. Corrected to `implemented`, and glamour-conversion's plan to `completed`. - **Two fragments were marked `open`** with the reasoning "no `promoted` or `dropped` needed", when `mind-mapper-spell-concept` became `docs/projects/mind-mapper` and `spellbook-aesthetic-and-rebrand` became `docs/projects/spellbook-rebrand`. Both are `promoted`. The other five were checked the same way and are correctly `open` — no successor exists for any. ⚠ AND TWO OF MY OWN CLAIMS WERE WRONG, WHICH IS WHY THE CHECKS RAN TWICE. I first attributed eight lifecycle overwrites to the backlog-a agent and said it had exceeded its brief; it had not. The edits were backlog-b's, still in flight when I sampled the diff, and its seven `open`→`done` plus one `open`→`promoted` are all correct and sha-cited. Separately, a `lifecycle: open # …` I flagged as a leaked template comment appears only in `TEMPLATE.md` files, where the inline vocabulary is deliberate. ⛔ THE CATALOG BUG WAS MINE AND THE LINT CAUGHT IT IMMEDIATELY. Inserting entries after the last line starting with `- [` SPLIT an existing wrapped entry: `PROJECT_MANIFESTO`'s hook truncated to seven words and its orphaned tail was absorbed onto the end of the line I had just added. `STALE HOOK` named both within seconds — which is exactly the drift that check exists for, since nobody re-reads a catalog. Fixed by rewriting the section rather than inserting into it, and every later section was written wholesale for the same reason. Gate 2544 pass / 0 fail, `dist-check` 0, `pdocs check` clean across both tiers. ⚠ WHAT THIS DELIBERATELY DOES NOT FIX: twelve pre-existing dead cross-references the new lint surfaces for the first time (`MISSING FILE`). Five point at projects that merely moved into `_archive/`, three at a `spell-hardening/plan.md` that appears never to have existed at that path, four at genuinely deleted files. Separate work from this upgrade, and worth a backlog item rather than a silent widening of it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BiZGj5ZTDSZi1mB8YtuRcx
…carry `45345651` states the docs lint is clean, and that is only true with this exclusion present — `git add -A -- docs` does not reach a repo-root file, so it was left in the working tree. Checked out alone, that commit reds on `docs/projects/scriptorium/SKILL.draft.md`. ⚠ A FOLLOW-UP RATHER THAN AN AMEND, ON PURPOSE. `45345651` has already been cited to the project-docs upstream agent on the `spellbook-upgrade` channel, and amending would leave that citation pointing at nothing — the same failure that cost a correction earlier today when a backlog item named the sha of the commit it was folded into. WHY THE FILE IS EXCLUDED. `SKILL.draft.md` is a superseded draft of a SKILL.md, so it carries SKILL frontmatter (`name:`, `description:`) rather than project-docs frontmatter. `scripts/pdocs/lint/rules.ts` enforces UNKNOWN FIELD against a closed key set, so `name` is rejected outright — and giving it project-docs fields would not help, because the offending key is the one that makes it a skill draft. Excluding it keeps the history intact and the gate honest; the alternative was to strip the field that gives the file its meaning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BiZGj5ZTDSZi1mB8YtuRcx
…t arrived `pdocs check`'s `MISSING FILE` rule found twelve markdown links in `docs/` that point at files which do not exist — on its first run, in under a second. ⛔ THE FINDING IS NOT THE TWELVE LINKS. It is that nothing in this repository had ever checked one. They accumulated over months of moving projects into `_archive/` and renaming folders, and every one survived every review of the page it sits on, because a reader who does not click does not notice. Classified rather than listed, because each class wants a different repair: five targets merely MOVED into `_archive/` and can be re-pointed; three name `projects/spell-hardening/plan.md`, a path that appears never to have existed (that project has `sprints/<n>/plan.md`), so the fix has to decide which sprint was meant or delete the sentence; four are genuinely gone, one of them `.anthill/retro.md`, which is outside the docs root and gitignored — there the honest fix may be to stop linking it at all. ⚠ Filed rather than fixed, deliberately. It arrived inside the project-docs upgrade; folding twelve judgement calls across four folders into a 273-file commit would have made that commit unreviewable, and nine of the twelve sat in folders subagents were editing at the time. ⚠ One correction carried into the item itself: an earlier pass of the survey classified `./2026-08-05-cli-stdout-truncation-on-pipe.md` as archived, because the classifier tested the `.` in `./…` as a directory name. It is gone, not archived. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BiZGj5ZTDSZi1mB8YtuRcx
…ot undo #176 Ran `update-project-docs`'s v2.8→v2.9 migration. Net change on disk is one file: `docs/.pdocs-seed.json`, recording the sha256 of 21 templates. Phase 5 is explicit that it has to be committed — "a manifest that never reaches the repository cannot be read next time" — and it is what lets a later migration update a template only while nobody has edited it. ⛔ RUN WITH `--scaffold-dir`, BECAUSE THE OBVIOUS INVOCATION WOULD HAVE REVERTED UPSTREAM'S FIX. Phase 3 (`refreshOwned`) copies the scaffold's `scripts/pdocs/` over ours UNCONDITIONALLY — no comparison, by design. But it generates that scaffold from the PUBLISHED template, and the fix for ichabodcole/project-docs-scaffold-template#176 is on upstream's `develop`, unreleased. Verified rather than assumed: cce845e reachable from origin/main NO cce845e reachable from origin/develop YES main's docs-lint/index.ts 1 × `if (m) s.add(slug(m[1]))` develop's, and ours 0 × So a plain run today copies the PRE-FIX file over the fixed one and re-breaks the type-check ward — and would report `✓ scripts/pdocs/ refreshed` while doing it, because that phase's post-check is presence, not content. Generated a scaffold from upstream's `develop` with cookiecutter, confirmed it carried the fix and that its `scripts/pdocs` was byte-identical to ours, then passed `--scaffold-dir`. Reported upstream as a guide gap: `--scaffold-dir` is documented as a way to skip the network, not as a way to avoid a downgrade. VERIFIED WITH THIS REPO'S OWN INSTRUMENTS, not the migration's self-report, because the owned files had just been replaced: type-check ward **0 errors, 35 of 35 areas, 661 of 661 files examined**; gate **2544 pass / 0 fail**; `dist-check` 0; `pdocs check` clean; `pdocs report` 0 missing fields across 0 of 290 documents. ⚠ TWO PHASE-REPORT WORDINGS WORTH KNOWING, both reported upstream. Phase 3 says `✓ docs/SCHEMA.md refreshed (both new sections present)` — ours diffed ZERO lines, so nothing was refreshed; the check is presence. And phase 4 formatted 21 templates while 29 files here are template-shaped by name: the exact-name rule selects 21, which is the rule working rather than a gap. v2.9→v2.10 does not apply — `isSeeded` is already in `scripts/pdocs/lint/rules.ts`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BiZGj5ZTDSZi1mB8YtuRcx
…he same way the links were
The item filed an hour ago claimed twelve dead cross-references. It is sixteen:
twelve `MISSING FILE` plus **four `MISSING ANCHOR`** — links to a `#section`
that is not a heading. The first survey grepped only for `MISSING FILE`, so the
anchors were never in the population being counted.
⚠ THE MISCOUNT HAS THE SAME SHAPE AS THE DEFECT IT DESCRIBES. A survey that
greps for one problem kind reports one problem kind, and reads as complete. The
authoritative population is what the tool prints, not what a reader thought to
look for — which is the argument for the lint in the first place.
The four are one cluster in `sprints/03-what-close-takes-with-it/` (`#a5`, `#a7`,
`#b` in `decisions.md`, and `./decisions.md#b` from `plan.md`), reading like
headings renamed after the links were written.
AND THE ITEM NOW CARRIES WHAT IT BLOCKS, MEASURED. Flipping `lint.adopting` to
false locally and reverting:
lint.adopting: false → pdocs check exits 9, 16 problem(s)
So these sixteen are exactly what stands between this repository and a docs gate
that can fail. `adopting: true` is the only thing holding the gate at 0 today —
the project-docs upstream agent's warning, confirmed here rather than taken —
which means the wired check runs without teeth until they land. Deliberate, and
the guide's own advice, but not to be mistaken for the gate being live.
Renamed the file to match the count.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BiZGj5ZTDSZi1mB8YtuRcx
…y line
Step 4 of the v2.6→v2.7 guide's "After the script", and the last commit of the
upgrade. Approved by Cole, and read against its own guidance by the project-docs
upstream agent on the `spellbook-upgrade` channel ("approved as-is, all three
parts").
"check": biome check --error-on-warnings . && bun run docs:check
"docs:check": bun scripts/pdocs/cli.ts check --format text
⛔ COMPOSED, NEVER REPLACED — which is upstream's own issue #170. A `docs:check`
that REPLACES `check` silently drops biome from every gate run, and `gate` is
`build && check && test`, so it inherits the docs check through that line with
nothing to edit. CI runs `bun run gate`, so no workflow change either.
⛔ THE HUSKY LINE IS ITS OWN, NOT ROUTED THROUGH lint-staged. `pdocs check`
takes no path arguments, so lint-staged would append the staged paths as argv
and it would lint the whole docs root regardless — the paths are noise at best.
⚠ And the guide's snippet does not fit this repo: it probes with
`grep -qx 'npm run check'`, while this hook was the single line `bunx
lint-staged`, so following it would have added nothing at all, silently.
Reported upstream; the probe is being made conditional.
⚠ THE GATE IS WIRED BUT CANNOT YET FAIL, AND THAT IS NOT AN OVERSIGHT.
`lint.adopting: true` holds `pdocs check` at exit 0 while sixteen dead
references stand. Measured by flipping it locally and reverting:
lint.adopting: false → pdocs check exits 9, 16 problem(s)
So wiring it now makes the output a progress meter, which is the guide's advice,
and the sixteen are filed as their own work
(`docs/backlog/2026-09-15-the-docs-lint-found-sixteen-dead-references.md`).
**Flipping `adopting` is a separate commit that must not land until that is
clean** — with them open it would red every commit from that one on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BiZGj5ZTDSZi1mB8YtuRcx
Cole's call to fix them inside the upgrade rather than defer. Before:
`pdocs check` exit 9, 16 problems. After: exit 0, "OK — no problems."
4 anchors pointing at non-headings → the lint's OWN slugs, read out of
`headingSlugsOf` rather than guessed from GitHub's algorithm
6 targets moved into `_archive/` → path re-pointed
3 the split-up monolithic plan → `spell-hardening/roadmap.md`, the
overview that survived the split into `sprints/<n>/plan.md`
2 genuinely deleted → unlinked, wording kept
2 illustrative examples in a block quote → unlinked; they were teaching a
convention, not referencing a file, so inventing a target would be worse
⚠ THE COUNT WAS WRONG TWICE, BOTH TIMES TOO LOW, BOTH FOR THE SAME REASON.
"Twelve" came from grepping `MISSING FILE` and never seeing 4 `MISSING ANCHOR`.
"Five moved to `_archive/`" came from assuming
`2026-08-05-cli-stdout-truncation-on-pipe.md` was deleted when it sat in
`backlog/_archive/` — six. Both were corrected by asking the tool for its own
population instead of a regex, which is the lesson the item was filed to make.
⛔ AND FIXING THEM BROKE THE GATE ONCE, THROUGH THE FORMATTER. Normalising the
edited markdown with a blanket `bunx prettier --write docs` reformatted 19 files
that are NOT markdown — four `.report.json`, four `.ts`, eleven `.html` mockups —
every one of which Biome owns in this repo, which is the hazard
`grimoire/house-style.md` already records as "format with biome, not prettier".
Eight Biome errors and a red gate, on files this change never meant to touch.
Collateral reverted, the thirteen real edits kept, `biome check` back to exit 0.
Reported upstream as a guide gap: `docs/` is not a markdown-only directory, and
the migration's own phase 4 is scoped to templates for precisely this reason.
⚠ One claim I sent upstream about this was itself wrong and is corrected on the
channel: I reported that the lint's problem rows use inconsistent path origins
because `DEV_KICKOFF.md` printed without a `docs/` prefix. Every row is
repo-root-relative; that file simply IS at the repo root. What actually cost me
two wrong edits was two tracked files sharing a basename — the other is
`docs/projects/_archive/bounty-agent-usable/DEV_KICKOFF.md` — and my assuming
the reported path was docs-root-relative.
Gate 2544 pass / 0 fail, `dist-check` 0, `biome check` 0, `pdocs check` 0 — the
last two run sequentially after a concurrent run produced a false red (the build
rewrites the same `dist/` paths `dist-check` diffs).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BiZGj5ZTDSZi1mB8YtuRcx
The last commit of the upgrade. `lint.adopting: true` was installed by the
v2.6→v2.7 migration so a freshly-migrated tree could not fail its own gate; with
273 descriptions written, the catalog complete and all sixteen dead references
fixed, it has no remaining job.
before pdocs check → exit 9, 16 problem(s)
after pdocs check → exit 0, "OK — no problems."
⛔ THIS IS THE COMMIT THAT MAKES THE WIRING MEAN SOMETHING. `c2802a30` composed
`docs:check` into `check`, and `gate` inherits it — but under `adopting: true`
that check could only ever exit 0, so it reported without enforcing. From here a
dead link or a missing `description` reds `bun run check`, `bun run gate`, the
pre-commit hook and CI.
Committed separately from the fixes on the project-docs upstream agent's
instruction — "test the flip locally, and only commit the flip when that is
clean" — so a bisect can tell "the references were repaired" from "the gate
started enforcing".
Gate 2544 pass / 0 fail · dist-check 0 · biome 0 · pdocs check 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BiZGj5ZTDSZi1mB8YtuRcx
…d red
CI failed on `49b4d0b9` — the push that turned the docs gate on. `docs:check`
exit 9, three problems, all of them links that can only ever resolve here:
docs/investigations/2026-07-06-astryx-…: ../../../dreamwood/media-buffet/…/proposal.md
docs/investigations/2026-07-14-agent-co-presence-…: /Users/colereed/Projects/dreamwood/dream-flute
docs/investigations/2026-07-14-agent-co-presence-…: /Users/colereed/Projects/dreamwood/story-loom
Two are ABSOLUTE paths into a home directory, written as markdown links. All
three point outside the repository, so no checkout anywhere can satisfy them.
Unlinked, with the text naming the external project instead.
⛔ I PUSHED A RED GATE, AND THE LOCAL GREEN WAS WORTHLESS FOR EXACTLY THESE
LINKS. `pdocs check` passed here before the push and passes here now, because
`~/Projects/dreamwood/{dream-flute,story-loom,media-buffet}` exist on this
machine. That is the same defect class as `type-sentinel-probe.ts`'s hardcoded
`typescript` path, caught by CI this morning — and the third "green locally, red
in CI" of the day. Twice now I had the means to check and did not.
VERIFIED THE ONLY WAY THAT COUNTS: a clean clone with no sibling `dreamwood/`,
the fixes applied, `bun install --frozen-lockfile`, then `pdocs check` — exit 0,
`docs-lint: clean`, zero machine-bound problems. A local pass is not evidence
for a link whose target lives outside the repo.
⚠ AND SEVEN SIMILAR-LOOKING LINKS ARE CORRECTLY LEFT ALONE.
`docs/projects/glamour-conversion/plan/*.md` point at `../../../../.anthill/dev/`,
which escapes `docs/` but NOT the repository: `.anthill/` is tracked
(`git ls-files` lists `README.md`, `config.json`, `dev/README.md`), so those
targets exist in CI's checkout too — confirmed present in the clone. Escaping
the docs root is fine; escaping the repository is not. That is the discriminator,
and churning the seven would have been noise.
⛔ THE LINT EARNED ITS KEEP TWICE IN ONE DAY. It is the first instrument in this
repo that ever read `docs/`, so it inherited sixteen dead references (fixed in
`33f57b9b`) and these three machine-bound ones — none of which anything had
checked, all of which survived every review of the pages they sit on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BiZGj5ZTDSZi1mB8YtuRcx
`cited shas 0` read as "nothing cites a branch sha". It means "nothing I can
see": the check greps TRACKED MARKDOWN IN THIS REPO, so a sha cited on a
grapevine channel, in another repository's record, in a GitHub issue, or in a
COMMIT MESSAGE on the branch itself is structurally invisible to it.
Measured 2026-09-15 landing `chore/project-docs-upgrade`: it printed
`cited shas 0` and returned ✅ SQUASH-SAFE while four of that branch's SHAs had
been cited to another agent on a channel, and `046dd936`'s own message explained
itself by reference to `45345651`. The verdict was correct about what it could
see and wrong about the branch — a squash would have stranded every one of those
references. `--ff-only` was chosen on that reasoning rather than on the verdict.
⛔ THE SKILL ALREADY SAID THIS AND THE SCRIPT DID NOT. `.claude/skills/land/SKILL.md`
carries the caveat at §1 ("It greps THIS REPO ONLY… structurally invisible"), but
the script is what an agent reads mid-landing, and it printed a bare `0` with no
hint that the number had a boundary. The warning now sits beside the number,
matching the shape of the existing author-count caveat, and fires only when the
count is zero — where the misreading is possible. Suggested by the project-docs
upstream agent on the `spellbook-upgrade` channel, which is itself one of the
places the check cannot see.
Verified both directions: fires on a zero-citation range; silent on `main..develop`,
which has 187 citations and needs no such warning.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BiZGj5ZTDSZi1mB8YtuRcx
…estroyed
The shadcn skill from skills.sh (`shadcn-ui/ui`) is now tracked: its 15 files
under `.claude/skills/shadcn/` and its entry in `skills-lock.json`.
⛔ THE LOCK ENTRY NEEDED RESTORING BECAUSE I DELETED IT. Cleaning up a probe
commit, I ran `git reset --hard origin/develop` on a tree holding Cole's
uncommitted six-line shadcn entry — a file the standing instruction says not to
touch. `--hard` discards unstaged work with no reflog and no dangling blob; it
was not recoverable, and I checked all 55 dangling objects before saying so. The
right cleanup was to undo the one commit I had made, not to reset the tree.
RE-ADDED WITH THE TOOL RATHER THAN HAND-WRITTEN, because the lock records a
`computedHash` the CLI owns and a fabricated one is worse than a missing entry:
bunx skills add shadcn-ui/ui --skill shadcn -a claude-code -y
Scoped deliberately: `--skill shadcn` leaves the package's second skill
(`migrate-radix-to-base`) out, and `-a claude-code` matches the actual footprint
— `.claude` is the only agent directory in this repo. ⚠ `skills list` reporting
"Antigravity, Codex, Cursor, Gemini CLI +3 more" is the agents it SUPPORTS, not
places it had installed; I misread that as an eight-agent footprint at first.
⚠ AND THE CONTENT IS UNCHANGED, so "it's fine if it's newer" turned out moot.
`SKILL.md` hashes `deba6c51…` before and after — upstream has not moved since the
2026-09-05 install.
THE LAYOUT CHANGED, AND FOR THE BETTER. It was a symlink into `.agents/skills/`,
which is gitignored (`.gitignore:65`), so the skill existed only on this machine
and a fresh clone would have had a dangling link — the same machine-bound class
that reddened CI twice today. It is a real directory now, as `acc` is, and
survives a clone.
⚠ SUPPLY-CHAIN NOTE, surfaced rather than swallowed: the installer's assessment
reads Gen **Safe**, Snyk **Low Risk**, Socket **1 alert**
(https://skills.sh/shadcn-ui/ui). Tracking it puts that content in the
repository; the alert is unreviewed here and is Cole's call to weigh.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BiZGj5ZTDSZi1mB8YtuRcx
…e idle Unset, React resolves to its DEVELOPMENT build, and the development build carries the Performance Track instrumentation: `logComponentRender` writes a `performance.measure()` entry, with a structured-cloneable `detail`, on every component render — into a buffer nothing ever clears. That turns any surface with an interval into a slow leak with no user in it. scriptorium's `useNow()` re-renders the DocumentPane subtree every 30s so "5 min ago" stays true, so the buffer fills while the human does NOTHING, until the clone cannot allocate: `DataCloneError: ... out of memory`, thrown inside commitPassiveMountOnFiber, which abandons React's commit phase mid-flight and leaves the tab dead behind `Should not already be working`. Shipped this way in 3.0.0 on all nine surfaces — `buildSurface()` is the one shared build, so the omission was roster-wide rather than scriptorium's — and found by a consumer whose tab died after a few minutes idle, not by us. Time to crash scales with subtree size: a one-file test session survived an hour while a real document set died in minutes. `define` ONLY, not `minify`, though minify also strips it. Minify is deferred by Cole under register D7 behind a precondition this fix does not meet — the instruments that read emitted artifacts AS TEXT have never seen renamed identifiers. The two are separable and measured: `define` leaves the text shape intact (scriptorium, 57 module-boundary comments and `src/kit/lib/cn.ts` present before AND after; 0 of each after minify), so no ward needed calibrating. Surface bundles 13.33 → 9.18 MiB raw (-31%) as a side effect. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The React dev build and the minify lever are separable, and collapsing them would read as D7 moving when it has not. So the row now leads with the fix — NODE_ENV pinned, all nine surfaces, the idle crash gone — and then says plainly that the minify deferral is untouched and its ward-calibration precondition still unmet, because `define` needed none of it. It also keeps the near-miss. `_archive/backend-convergence/phase-1b-journal.md` and `phase-2-journal.md` both wrote down "the unminified DEV React graph" in September. The dev build was observed twice, recorded as a size fact, and read by nobody as a correctness defect — the evidence was in the register's own lineage and the bug still reached a consumer. And it retires a number before it misleads: D7's `13.21 → 7.25 MiB` was the surfaces on 2026-09-09, the tree has since gained scriptorium, and `define` alone has already taken them to 9.18 MiB. Minify's remaining headroom is whatever it buys on top of that, so the old pair must be re-measured rather than re-quoted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fix stops it happening again; this stops it happening again UNNOTICED. Nothing in the tree read a surface bundle for its build mode, which is why v3.0.0 shipped nine development builds and a consumer found it rather than an instrument. The ward greps every shipped `dist/index-<hash>.js` for the dev build's Performance Track writers and fails on any hit — run against the v3.0.0 artifact it reports all three markers, so it would have been red. It carries its own blind set, as the sibling wards do: markers not semantics (a renamed React internal passes), surfaces only, and — deliberately — it is NOT a minify check. `minify` strips the same markers, but minify is deferred under register D7, so a ward that conflated them would quietly become a minify gate the day D7 is decided. It must stay green under both. The scenario records the judgment that shaped the fix: Cole asked what the change would SUPERSEDE before it landed, which found D7's deferral and split one move into two — `define` shipped, `minify` stayed deferred. A small diff is not a small decision, and the one-line-ness is what stops anyone looking. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s the gotcha `carry-bun-gotchas-forward` is a TEMPORAL rule whose last reinforcement was its own seed, 2026-05-28 — the state in which a rule about carrying gotchas forward has stopped carrying any. The row now holds the one that cost us a release. The gotcha is not the flag, it is the DEFAULT: `Bun.build` leaves `process.env.NODE_ENV` unset, so React resolves to its development build, and an unset default emits an artifact that passes every short test and fails only on a clock. That is the shape to carry — Bun's bundler defaults differ from the ecosystem's, and the failures they produce are time-dependent rather than immediate. Also records the half that did not ship: `minify` strips the same markers and was deliberately left off under D7's deferral. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
If a Spellbook surface ever went blank on you after sitting idle, this is why, and this release fixes it. Nothing was lost when it happened — only the browser tab died; the local background process serving it, and your files on disk, were untouched. Reloading the tab brought it back.
A quick orientation for anyone reading this without the repo open: a spell is one tool in this plugin, and its surface is the local web page it opens in your browser. Every spell you can install was affected —
astrolabe,bounty,digestify,glamour,grapevine,imago,magpieandscriptorium. (A ninth,mind-mapper, is built but deliberately unpublished, so it was fixed too but nobody could have hit it. Where this note says "nine", it means bundles the build produces; where it says "eight", it means spells you can actually install.)How to get it, and how to tell
At the time of writing this is not released yet — it becomes 3.0.1 when this merges and release-please cuts it. Until then the latest published version is 3.0.0, which has the bug.
Once 3.0.1 is out, update the plugin as usual. Then restart any spell you had running. Each version installs into its own directory, so a daemon still running from the 3.0.0 install keeps serving the old bundle no matter what you have downloaded — updating alone is not enough. Reload the browser tab afterwards.
To confirm you are on the fixed build: the surface bundles are roughly a third smaller, so
plugins/spellbook/skills/<spell>/dist/index-*.jsinside a 3.0.1 install will be visibly smaller than the 3.0.0 one, and will not contain the stringlogComponentRender.The rest of this note is for contributors. If you just wanted the fix, you have it above.
What went wrong
v3.0.0 shipped React's development build on every surface, and a user found it rather than any check of ours.
Bun.buildleavesprocess.env.NODE_ENVunset, so React's export conditions resolve to the development entry. That build carries profiling instrumentation:logComponentRenderwrites aperformance.measure()entry, carrying a payload the browser must clone, on every component render — into a buffer nothing ever clears.So any surface that re-renders on a timer leaks while nobody is touching it.
scriptoriumre-renders every 30 seconds purely to keep a "5 min ago" label honest; the buffer fills with the human doing nothing, until the browser can no longer allocate for the clone. The tab then dies inside React's commit phase (DataCloneError: … out of memory, followed byShould not already be working).Time-to-crash scales with how much is on screen, and that is why the bug escaped us. A one-document test session survived an hour; a real working set died in minutes. Every short test we ran looked perfectly healthy.
buildSurface()is the single shared build for all nine surfaces, so one missing option was roster-wide rather than one spell's bug.What was delivered
The fix, and the rebuilt bundles —
ad995265.buildSurface()now pinsprocess.env.NODE_ENVto"production", and all nine shipped bundles are recompiled in the same commit — so an already-installed spell gets the fix, not just the next build. Surface bundles went 13.33 → 9.18 MiB (−31%) as a side effect of dropping the development build's dead weight.A check so it cannot recur unnoticed —
a4974d3c,grimoire/surface-build-mode-ward.test.ts. A ward, in this repo, is a test that guards a house rule rather than a feature. Nothing here had ever read a shipped bundle to see which build it was. This one scans every shipped surface bundle for three identifiers that exist only in React's development build —logComponentRender,logComponentEffect, and theComponents ⚛profiling track — and fails on any hit. It passes on everything in this release. Pointed at the old v3.0.0 bundle it finds all three markers and fails — so had it existed, this would never have shipped.A documentation-tooling layer — seven commits, and by volume the largest part of this release, though no user ever sees it. The project-docs scaffold went 4.4.0 → 8.1.0, bringing a schema, a document catalog, and a
pdocsCLI underscripts/pdocs/(a2740d20). 273 documents were backfilled with a description and 25 uncatalogued pages linked up, takingpdocs reportfrom 353 missing fields to zero (45345651, with046dd936carrying an exclusion it depended on). A docs lint was then composed into the repo'scheckscript and its pre-commit hook (c2802a30), sixteen dead cross-references were repaired (33f57b9b), and only then was the lint switched from warn-only to enforcing (49b4d0b9).A live erratum against the published v3.0.0 note —
55c7a566.gateis this repo's CI check — the one that runs the build, lint and tests. That note tells readersgateis not enforced onmainand that a red gate will not block a merge. That is false and has been since 2026-09-01: the branch ruleset "Main Gate" requiresgatetoday (re-verified against GitHub while writing this). The published note sits in a merge commit body and cannot be edited, so the correction lives in the tree instead. If you read the 3.0.0 note, disregard that paragraph.Two smaller corrections —
73f83dbdmakes the landing tool state that itscited shas: 0means "none that I can see", since it greps only tracked markdown in this repo.0db525b8tracks the vendoredshadcnskill as real files with a lockfile entry, where it had been a symlink into an ignored directory that existed on exactly one machine.Decisions worth knowing
defineonly — minification is still deliberately off. Minifying would strip the same instrumentation and shrink the bundles further, but it is deferred by a standing decision (entry D7 ofdocs/architecture/house-conformance-register.md, the repo's log of open architectural rulings) behind a precondition this fix does not meet: several checks read compiled output as text, and none has ever been calibrated against renamed identifiers. The two options were measured and proved separable — pinningNODE_ENVremoves the instrumentation while leaving the bundle's text shape untouched — so the crash fix ships in full and the deferred decision stays deferred, unbreached.a574e22erecords that.13.21 → 7.25 MiBas the minification estimate; this release measured13.33 → 9.18 MiBfrom pinningNODE_ENV. Different levers, and the tree has gained a spell since. The old pair has been retired rather than re-quoted.49b4d0b9passed locally and failed in CI: three markdown links pointed outside the repository — two of them absolute paths into a home directory — so they resolved on their author's machine and on no clone anywhere. The repair iseb012b7f, verified in a clean clone, which is the only place such a check means anything. Seven similar-looking links that escapedocs/into.anthill/are correct and were left alone, because that directory is tracked. CI is green on this branch.Known limits of what shipped
These are properties of the work in this release, not work left undone.
docs/projects/scriptorium/SKILL.draft.md, which holds a different kind of frontmatter.⚠ One unreviewed risk
Tracking the
shadcnskill puts vendored third-party content into the repository. Its published installer assessment reads Gen Safe and Snyk Low Risk but carries one Socket alert (https://skills.sh/shadcn-ui/ui). Nobody has reviewed that alert. It is surfaced here rather than quietly accepted, and it wants a decision.What this deliberately does not reach
cnpackage (unrelated to the repo's ownsrc/kit/lib/cn.tsof the same name) anddigestify'smarked— filed as one measured sweep rather than done piecemeal (889de410,docs/backlog/2026-09-15-dependency-upgrade-sweep.md).gatetwice per push, which is pre-existing and unchanged here. Filed rather than fixed, because the obvious fix is a trap: dropping thepull_requesttrigger would hang every PR intomainon a required check that never reports.Version: the conventional-commit types in this range are one
fix(build)and onefix(docs), with the restdocs(,chore(andtest(— so release-please will cut this as a patch, 3.0.1.🤖 Generated with Claude Code