Skip to content

chore(tools): every tool lives with its reader - #778

Merged
stellarfeline merged 12 commits into
mainfrom
chore/tools-live-with-their-reader
Sep 16, 2026
Merged

stellarfeline merged 12 commits into
mainfrom
chore/tools-live-with-their-reader

Conversation

@stellarfeline

@stellarfeline stellarfeline commented Sep 16, 2026

Copy link
Copy Markdown
Owner

tools/ held 77 loose scripts at its top level and a table in docs/reference/tools.md said who each one was for. The classification is now the directory a tool lives in, and the top level holds no loose file.

The homes

directory who reads it files
tools/creator/ the 7 scripts the /new-delve page runs, plus skin/ 25
tools/ci/ the 49 gates a workflow invokes 50
tools/planner/ planner-state.sh (its hook binding moves with it), worktree-new.sh, worktree-reclaim.py, next-numbered-doc.py 4
tools/maintenance/ the 17 regenerators a pin bump calls, plus 2 Java dumpers 19
tools/lib/, tools/tests/, tools/spike-*/ shared modules and their tests, which have no reader of their own 20 / 84 / 36

7 + 49 + 4 + 17 = 77. check-patrol-types.py and derive-client-langs.py are maintenance: the page cites the Mojang manifest URL constant each carries and never invokes them.

Nothing is removed

Every one of the 77 has a reader that names it, checked against workflows, the page, .github/pins.toml's sites, crate comments pointing at the table a script regenerates, and specs that name a script as their instrument. next-numbered-doc.py implements the numbering rule and belongs to the planner; site-plan-scalars.py is spec-0059's own instrument.

What a pure path rewrite cannot reach

Five classes came out of running the gates, not out of git grep: a path written as a regex (which reds check-structure-emitters), a fake tree a test builds rather than names (10 fixtures), a frozen instrument that must not move, the {"kind": "tool"} carriers in docs/playtest-findings.json, and paths inside gallery stage documents, which are gallery source and so move the baseline.

Old-path references in this repository: 1117 across 284 files at 74c61b5a, 14 across 5 files here, over 1485 tracked files. All 14 are deliberate — dated friction logs that name their instrument by revision, the frozen check-publishable-d5908698.sh, two comments describing that frozen layout, and one reproduction record.

The delvec subcommand list, reported

block-appearance.py, staging-gate.py and check-storybook-version.py are subcommands: each reads only what the binary already holds. i18n-translate.py and refimg.py stay scripts (live third-party endpoints and per-provider keys; the compiler does no network I/O). refscore.py stays (its backends are Python ML packages). playtest-server.sh stays (container lifecycle, port mutex, rcon). skin/ stays Python (its tests assert byte-identical golden PNGs from skinpy-extended).

Init walked end to end

On a scratch HOME, dev mode, I0 through I8: python3 3.14.7, git, java; I1b exits 4 with no env.sh; cargo build --release -p delvec on toolchain 1.97.1; delvec 1.5.0, dsl 0.28.0, mc 1.21.11; the client jar downloads and its sha1 verifies; DELVEWRIGHT_PREFABS resolves 74 entries; all 9 lines of I8 answer. docker compose version is the one line this machine cannot answer, for want of the Compose plugin.

Gates (tree merged with 3598d8f8)

fmt 2 workspaces clean; clippy --workspace --all-targets -D warnings exit 0; cargo test --workspace --no-fail-fast 3564 passed, 0 failed over 264 result lines; pytest tools/tests 1355 passed, 1 skipped; check-dw-codes 363 codes, all covered; gallery coverage 36 refusal probes, 28 ledgers, 0 zero-bindings, baseline verified, stageable 8 of 8, render 70 of 70, whole-map render 3 bases and 230 scenes; check-required-contexts 16 of 16; check-ci-reach 15 jobs, 14 groups, 60 globs over 1492 files, 3 events; check-pins 41 pins against 44 entries; check-skill-page green against engine 70eea629; check-doc-dupes 163 documents; check-source-dupes 500 Rust files; check-json-canonical 438 of 439; check-effect-roots 218 files; check-generator-preservation 1024 planted keys; check-unsanctioned-identifiers OK.

The baseline over 3598d8f8 moves one field, header.json's gallery_source_sha256, because a stage document's note names a gate and that note is gallery source. manifests.json and warnings.json are byte-identical to main's, and the delta is 0 added, 0 removed, 0 changed: this branch moves no emitted byte.

The layout survives the merge: creator 25, ci 50, planner 4, maintenance 19, lib 20, tests 84, and nothing loose at the top.

8 ADRs and 22 specs carry path re-points only: reversing the new paths gives back 74c61b5a's bytes exactly, so no decision text moved.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Vx4gBbZLG8Ecy9qzufa14L

stellarfeline and others added 8 commits September 15, 2026 19:48
`tools/` held 77 top-level scripts and classified them in a column of
`docs/reference/tools.md`. A creator opening the directory faced six
sevenths of it that was not for them, and the classification was data in a
table rather than structure on disk.

Four homes, each named for its reader:

- `tools/creator/` (7 scripts + `skin/`) — exactly what a `/new-delve` page
  file INVOKES. A tool the page only cites as the source of a constant is
  not a creator tool: `check-patrol-types.py` and `derive-client-langs.py`
  are named in `init.md` only as the carriers of the Mojang manifest URL,
  and each regenerates a committed table, so both live under maintenance.
- `tools/ci/` (49 scripts + `data/`) — one file per gate a workflow runs,
  plus `gallery_domain.py` / `gallery_units.py`, which only those gates
  import.
- `tools/planner/` (4) — `planner-state.sh` with its `SessionStart` and
  `UserPromptSubmit` hook binding, `worktree-new.sh`, `worktree-reclaim.py`,
  `next-numbered-doc.py`.
- `tools/maintenance/` (17 + the two Java dumpers) — the regenerators a
  Minecraft pin bump calls, and `site-plan-scalars.py`, spec-0059's
  instrument.

`tools/lib/`, `tools/tests/` and the `spike-*` rigs keep their homes: a
library has no reader of its own.

Nothing is removed. Every one of the 77 has a reader that names it — a
workflow, the page, a `sites` list in `.github/pins.toml`, a crate source
comment pointing at the table it regenerates, or a spec naming it as its
instrument.

Re-pointed in the same change: 1174 path references across 287 files,
`.github/workflows/*`, `.github/ci-reach.toml` (4 globs), `.github/pins.toml`
`sites` lists, `.github/actions/*`, `.claude/settings.json`'s hook command,
the `/new-delve` page, `tools/tests`, the crates' provenance notes, and
CLAUDE.md's repository-layout block. Every moved script's own repo-root
resolution moves with it: 53 `parent.parent` -> `parents[2]`, 15
`parent / "lib"` -> `parents[1] / "lib"`, 8 `parent` -> `parents[1]` where
the insert feeds `from lib import ...`, 11 shell `dirname/..` -> `../..`.

`docs/reference/tools.md` section 6 is one table per directory, so a row
naming a file no directory holds is visible by reading the tree beside the
page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vx4gBbZLG8Ecy9qzufa14L
…can cannot see

Five classes of reference a literal-path rewrite does not reach, each found by
running the gate rather than by grepping:

- A path spelled as a REGEX. `check-structure-emitters.py` looked for
  `tools/fmt-workspaces\.sh` and reported, correctly, that nothing establishes
  `cargo fmt` for the prefab packages.
- A path a TEST BUILDS rather than names. Ten fixtures assemble a throwaway
  tree and copy a script into it; the copy has to land where the script's own
  root resolution expects it, so `tools/ci/`, `tools/creator/` and
  `tools/planner/` are created in the fake trees too.
- A path this repository must NOT move. `check-publishable-d5908698.sh` is a
  frozen instrument, named by that revision, and at that revision the script
  lived at `tools/check-publishable.sh` and resolved its root one directory up.
  Its bytes are restored and its clone is laid out the way d590869 laid it out;
  running frozen bytes against today's layout would be judging that revision on
  a tree it was never written for.
- A LEDGER ROW. A `{"kind": "tool"}` carrier in `docs/playtest-findings.json`
  names its script relative to `tools/`, so the row now carries the home of the
  reader the tool is for.
- A path inside a `note` in a gallery stage document, which is gallery SOURCE:
  `gallery/baseline` is retaken over `74c61b5abd14` with a force-rebuilt
  instrument. manifests.json and warnings.json are unchanged and the review
  delta is 0 added / 0 removed / 0 changed over 3797 emitted paths — the move
  emits byte-identical output.

`gallery/README.md` and `test_gallery_domain.py` reach `gallery_domain` through
`tools/ci` now; the latter's denominator is the python tools of all five homes,
enumerated from the directories, because `TOOLS.glob("*.py")` matches nothing at
the top level any more and a zero there would have been a silent pass.

`init.md` named `check-patrol-types.py` and `derive-client-langs.py` under
`$DELVEWRIGHT_ENGINE/`, where the rewrite's own lookbehind could not see them.
Both are maintenance tools: the page cites them as the carriers of the Mojang
manifest URL and invokes neither.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vx4gBbZLG8Ecy9qzufa14L
`prefab-procedure-friction-log.md` and `skill-drill-friction-log.md` name their
instrument by revision and quote terminal sessions a person ran. Rewriting a
transcript changes what the record says happened, so both are restored to the
bytes they carried at 74c61b5. The other three notes under `docs/notes/` name
their tools in the present tense — what enforces a rule, what asserts a property
— and keep the re-point.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vx4gBbZLG8Ecy9qzufa14L
`tools/check-seating-agrees.py` moved here and changed there; git followed the
rename and the change landed at `tools/ci/check-seating-agrees.py`. Three
conflicts, each resolved toward what the other side is authority for:

- `docs/reference/compiler.md` and the `check-seating-agrees.py` row of
  `docs/reference/tools.md` — origin/main's sentence, re-pointed to the new home.
  This side is authority for the path only.
- `gallery/baseline/` — reset to origin/main's four documents wholesale. It is a
  generated artifact and is regenerated in the next commit, after this merge
  commit exists, against a force-rebuilt instrument.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vx4gBbZLG8Ecy9qzufa14L
Regenerated after the merge commit exists, against an instrument force-rebuilt
from the merged tree (`delvec 1.5.0, dsl 0.28.0, mc 1.21.11`, built from
`crates/delvec/tests/remedy_reachability.rs` at the newest of 803 tracked
compiler sources). header.json and delta.json move; manifests.json and
warnings.json are unchanged. The review delta against `9e101878b7aa` is
0 added, 0 removed, 0 changed over 3797 emitted paths, 0 warning rows at a
different count and 0 manifest values differing — this branch moves no emission
relative to main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vx4gBbZLG8Ecy9qzufa14L
One commit landed since the last merge of `9e101878`: the firework round
(spec-0068, `dsl_version` 0.29.0, `DW0899`). `git merge-tree --write-tree`
against this head named exactly two conflicting paths before the merge was
started, and the merge produced exactly those two: `gallery/baseline/delta.json`
and `gallery/baseline/header.json`. Everything else auto-merged —
`crates/delvec/src/compiler/emit.rs`, `crates/dsl/src/stages.rs`,
`crates/dsl/src/validate.rs`, `docs/playtest-findings.json`,
`docs/reference/compiler.md`, `docs/specs/README.md`, `versions.toml`.

`gallery/baseline/` is generated, so all four of its documents are reset to
origin/main's wholesale rather than three-way merged. It is regenerated in the
next commit, after this merge commit exists, against a force-rebuilt instrument.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vx4gBbZLG8Ecy9qzufa14L
The firework round wrote `tools/check-gallery-coverage.py` and
`tools/check-skill-page.py` while this branch was moving both into `tools/ci/`.
They are the only two paths the merge brought in that the move had invalidated:
a scan of the whole merged tree for every one of the 81 old paths returns 16
occurrences, and the other 14 are the deliberate ones — two dated friction logs,
the frozen `d5908698` instrument, the comments naming that revision's layout, and
one dated reproduction note in the findings ledger.

Nothing the round ADDED names a `tools/` path otherwise: `compiler/firework.rs`,
`dsl/src/firework.rs`, `dsl/src/color.rs`, both `v29_firework.rs` and
`gallery/probes/a-rocket-under-a-roof/probe.json` mention none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vx4gBbZLG8Ecy9qzufa14L
Regenerated after the merge commit exists, against an instrument force-rebuilt
with `cargo build -p delvec --bin delvec` at exit 0 (`delvec 1.5.0, dsl 0.29.0,
mc 1.21.11`, newest of 808 tracked compiler sources). 8 builds, 3798 emitted
paths — one more than before the merge, which is the firework round's
`valley-site.en:validation/firework-gate.json`.

Every changed row attributed:

- `manifests.json` and `warnings.json` are BYTE-IDENTICAL to origin/main's. The
  77 rows the firework round moved and the one it added are all carried through
  unchanged: none of them is this branch's. This branch moves no emission at all.
- `header.json` differs from main in exactly one field, `gallery_source_sha256`.
  That is this branch's, and it has one cause: the `note` of
  `gallery/overlays/valley-site/overlay.json` names
  `tools/ci/check-gallery-stageable.py`, and a `note` in a stage document is
  gallery SOURCE. `gallery/README.md` is this branch's other gallery edit and
  the digest excludes it by name, so it contributes nothing.
- `delta.json` is re-based `9e101878b7aa` -> `3598d8f86732` and its lists empty:
  the 1 added and 77 changed paths it carried were the firework round's own,
  measured against the older base. Against `3598d8f86732` the review delta is
  0 added, 0 removed, 0 changed over 3798 emitted paths, 0 warning rows at a
  different count and 0 manifest values differing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vx4gBbZLG8Ecy9qzufa14L
@stellarfeline
stellarfeline marked this pull request as draft September 16, 2026 06:55
@stellarfeline
stellarfeline marked this pull request as ready for review September 16, 2026 10:14
stellarfeline and others added 4 commits September 16, 2026 06:20
Six merges landed since `3598d8f8`: spec-0058 (`delvec detail`), spec-0070
(`DW0900`), the CRLF work, `delvec prefab`/`playtest-server.sh` taking either
kind of artifact, the 1.6.0 version name, and the page re-pin to a release tag
(ADR-0029).

`git merge-tree --write-tree` named eleven conflicting paths before the merge
was started and the merge produced exactly those eleven. Nine of them are the
same shape: origin/main is the authority for the content and this branch is the
authority for the path alone, proved rather than assumed — this side's version
of six of them (`versions.toml`, `skill-page-objects/action.yml`, `pins.toml`,
`ci.yml`, `skill-workflow.md`, `spec-0063`) is byte-equal to the merge base once
the home directories are stripped out, so each takes origin/main's file with the
path mapping applied and nothing else.

Three carried more than a path, and each was resolved as origin/main's file plus
exactly what this branch adds to it, named:

- `tools/ci/check-skill-page.py` — 7 literal paths, 3 `REPO / "tools" / …`
  joins, and `REPO` resolving two directories up instead of one.
- `tools/creator/playtest-server.sh` — 8 literal paths and 3 `dirname "$0"`
  roots. origin/main's restructure of the script (either kind of artifact, and
  the staging gate moving with it) is taken whole.
- `docs/reference/tools.md` — origin/main's 100 section-6 rows, re-pointed and
  re-grouped into this branch's one-table-per-home structure. Its row set is
  this branch's 98 plus the two tools the round added, and nothing else.

`gallery/baseline/` is generated: all four documents reset to origin/main's
wholesale, regenerated in the next commit.

The two tools origin/main added land in the home of their reader in this same
commit, because they arrive at `tools/` root and nothing else would put them
anywhere: `check-eol-attributes.py` and `gallery-prefabs.py` are both run by a
CI job, so both go to `tools/ci/`, each with its own repo-root resolution moved
one level and `gallery-prefabs.py`'s `lib` import re-pointed; 7 references to
them across 6 files move with them.

The eol population is unchanged by any of this, measured on both trees rather
than argued: origin/main at `7bc14feb` reports 128 of 1520 tracked files run
through their first line (128 by shebang, 58 by the executable bit), and this
merged tree reports the same 128 of 1520, 128 and 58, every one pinned `eol=lf`
and 128 materialised with LF. `.gitattributes` matches by extension with no
leading slash, so a pattern reaches a file at any depth.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vx4gBbZLG8Ecy9qzufa14L
The page re-pin (ADR-0029) points `[engine].ref` at `delvec--v1.6.0`, which is
unborn, so `check-skill-page.py` materialises THIS tree and rule 21 holds every
`$DELVEWRIGHT_ENGINE/<path>` the page names against it. It reds on this branch
the moment the merge lands, which is the whole point of the rule.

What it found, and what each repair is:

- `REFIMG` was the bare basename `refimg.py` and rule 15 resolved it as
  `engine / "tools" / REFIMG`. Against the older pin that was true of the
  engine it named; against this tree it is not. It is now the path relative to
  `tools/`, carrying the home of the reader the tool is for.
- The config the probe writes is `delvewright.local.toml` at the engine ROOT,
  which `refimg.py` resolves as `parents[2]` since the move. The gate wrote it
  at `refimg.parent.parent`, one directory short, so every provider probe would
  have run unconfigured.
- `container_names` globbed `engine/tools/*.sh`, which matches nothing now that
  no script sits at the root of `tools/`. It reads the tree recursively; the
  `^NAME="…"` lines it is after are unaffected by which directory they are in.

Rule 21 then binds 38 of 42 named paths on BOTH arms — the tree the page ships
from and the tree the pin names — 34 of the 38 present, 4 named as build output
this tree's own `.gitignore` covers with git judging, 1 never a tree entry
(`.git`) and 3 written with a placeholder segment. The twelve the page names
under `tools/` are all present at their new homes.

The rest of the merge's live references move with them: 36 literal paths and 2
`Path` joins across 11 files — ADR-0029, spec-0058, spec-0070, `compiler.md`,
the page's `tools-by-symptom.md` and `fetch-delvec.py`, and five test modules,
of which `test_check_skill_page.py` and `test_playtest_server_takes_either_kind.py`
would otherwise fail outright because they read the real index. A whole-tree
scan for all 83 old paths leaves 14, every one deliberate: two dated friction
logs, the frozen `d5908698` instrument, the comments naming that revision's
layout, and one dated reproduction note in the findings ledger.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vx4gBbZLG8Ecy9qzufa14L
Regenerated after the merge commit exists, against an instrument force-rebuilt
with `cargo build -p delvec --bin delvec` at exit 0 (`delvec 1.6.0, dsl 0.29.0,
mc 1.21.11`, newest of 810 tracked compiler sources), over pieces written by
`tools/ci/gallery-prefabs.py` — the step spec-0058 put in place of the
generator's own manifest. 8 builds, 3799 emitted paths.

Every changed row attributed:

- `manifests.json` and `warnings.json` are BYTE-IDENTICAL to origin/main's.
  Everything spec-0058, spec-0070 and the firework round moved in them is
  carried through unchanged; this branch moves no emission at all.
- `header.json` differs from main in exactly one field, `gallery_source_sha256`.
  That is this branch's, with one cause: the `note` of
  `gallery/overlays/valley-site/overlay.json` names
  `tools/ci/check-gallery-stageable.py`, and a `note` in a stage document is
  gallery SOURCE. `gallery/README.md` is this branch's other gallery edit and
  the digest excludes it by name.
- `delta.json` differs from main in exactly one line, `base_commit`, re-based
  `33e3dbe3e74f` -> `7bc14febcd4c`. Its path lists are unchanged. Against
  `7bc14febcd4c` the review delta is 0 added, 0 removed, 0 changed over 3799
  emitted paths, 0 warning rows at a different count, 0 manifest values
  differing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vx4gBbZLG8Ecy9qzufa14L
ADR-0029 gave `check-pins.py` an import of `release_tags` from `tools/lib`,
resolved as `<its parent>/lib`. From `tools/ci/` that is `tools/ci/lib`, which
does not exist, and the gate died with `ModuleNotFoundError` before it read a
single pin — found by running it rather than by reading it.

It is the last of this shape in the tree: no script under `tools/ci`,
`tools/creator`, `tools/planner` or `tools/maintenance` still inserts
`<its parent>/lib` on `sys.path`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vx4gBbZLG8Ecy9qzufa14L
@stellarfeline
stellarfeline merged commit 27caa8d into main Sep 16, 2026
17 checks passed
@stellarfeline
stellarfeline deleted the chore/tools-live-with-their-reader branch September 16, 2026 11:05
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.

1 participant