Files
benilla-octo/scripts/genmap.sh
T
SamandClaude Fable 5.1 3e12fa1d7f benilla is one repo: the working tree moves in, restructured
Until now this repo was an export of a private tree, pushed as squashed snapshots. From here on
it is the working tree itself, and the private repo is archived.

The root holds AGENTS.md and the Rust and GitHub files only. Everything an agent needs to orient
is in docs/: METHOD.md (the rules and how the project is built, 123 lines, project method only),
MAP.md (generated from the code at every land) and CONTRIBUTING.md (what gets in and how a
change is judged). One skill, `probe`, holds the procedure for running the client unattended
against the local server.

Brought over because the project needs it: reference/ (the 1.12 name catalogues 34 source
files read; the export failed its own tests without them), the gate chain and the pool
(scripts/), the hooks that enforce the install rule and the worktree rule, the full .gitignore,
the `cargo play` alias, and the original wording of eleven source files the export had reworded.

Left out: the decision records (archived with the private repo), the export tooling, the
operator's Discord and triage loop, the lab-laptop scripts and the instruments nothing
references, which live outside the repo now. What read the records is gone from the tooling:
no corpus lint, no records index, no `reserve` verb, no number-collision check.

Three hooks are gone: fmt-at-commit and the file-size nudge were redundant with the gates, and
the assets/ui guard is now a test (`assets_ui_does_not_grow`) that fails for everyone, not only
for a Claude Code session. The remaining pool hooks stand down on a machine without a pool.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-23 15:56:57 +07:00

148 lines
7.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# Generate docs/MAP.md — the "what's built" map — derived entirely from what's on disk so it cannot
# drift (docs/METHOD.md / docs/METHOD.md: "the generated map … regenerated from the code, never
# hand-written"). `wt.sh land` runs it on the rebased tree and commits the file (decision 2049);
# run it by hand only to LOOK, never commit its output yourself, and never edit it by hand.
# Deterministic: identical tree → identical output (no timestamps), so a diff means the structure
# actually changed.
# No `pipefail`/`-e`: greps that legitimately find nothing (a single-file lib, a crate with no bins)
# must not abort the generator.
set -u
cd "$(dirname "$0")/.."
# Extract a quoted `key = "value"` field from a Cargo.toml.
field() { grep -m1 "^$2" "$1" 2>/dev/null | sed -E "s/^$2[[:space:]]*=[[:space:]]*\"//; s/\".*$//"; }
{
echo "# benilla — generated map"
echo
echo "> **GENERATED** by \`scripts/genmap.sh\` from the code — do not edit by hand; rerun the script"
echo "> when what's built changes. *What-is* only: the *what-changed* is in git. No timestamp —"
echo "> identical code yields an identical map."
echo
echo "## Crates"
echo
for ct in crates/*/Cargo.toml; do
name=$(field "$ct" name)
desc=$(field "$ct" description)
# `bevy.workspace = true` is the form every crate here actually uses, and the old pattern
# (`^bevy[[:space:]]*=`) matched only `bevy =` — so workspace-inherited deps read "no Bevy".
# benilla-assets shipped mislabelled long enough to say "(no Bevy) — Bevy AssetSource +
# AssetLoaders" in one line. The trailing class accepts `.`, whitespace and `=`, which covers
# `bevy.workspace =`, `bevy = { … }` and `bevy="…"`, while still rejecting a `bevyfoo` crate.
# Load-bearing beyond tidiness: "does this crate need Bevy" is the seam question 0068 cut
# `benilla-ui` on and 1160 cuts `benilla-world` on, so a map that lies about it misleads
# exactly the work that reads it.
if grep -qE '^(bevy|bevy_egui|avian3d)[.[:space:]=]' "$ct"; then bevy="Bevy"; else bevy="no Bevy"; fi
echo "- **$name** ($bevy) — ${desc:-—}"
done
echo
echo "## App subsystems — Bevy plugins in load order (\`crates/benilla-app/src/lib.rs\`)"
echo
# Accept bare (`add_plugins(FooPlugin)`) and path-qualified (`add_plugins(foo::FooPlugin)`)
# registrations; print the plugin type name either way.
grep -oE 'add_plugins\(([a-z_]+::)*[A-Za-z_]+Plugins?' crates/benilla-app/src/lib.rs \
| sed -E 's/add_plugins\(//; s/([a-z_]+::)+//' | awk '!seen[$0]++ {print "- " $0}'
echo
# The two dev groups are one `add_plugins` line each in `lib.rs` (decisions 1173/1174), so the
# list above names the group and not what is in it. Read their members out of `dev.rs` — a map
# that stops naming the instruments the moment they move behind the `dev` feature is exactly the
# drift this file exists to prevent.
echo "### Behind the \`dev\` feature (\`crates/benilla-app/src/dev.rs\` — compiled out by \`--no-default-features\`)"
echo
grep -oE 'add_plugins\(([a-z_]+::)*[A-Za-z_]+Plugins?' crates/benilla-app/src/dev.rs \
| sed -E 's/add_plugins\(//; s/([a-z_]+::)+//' | awk '!seen[$0]++ {print "- " $0}'
echo
echo "## Modules (top-level, per crate)"
echo
for lib in crates/*/src/lib.rs crates/*/src/main.rs; do
[ -f "$lib" ] || continue
crate=$(echo "$lib" | sed -E 's@crates/([^/]+)/.*@\1@')
mods=$(grep -E '^[[:space:]]*(pub(\([a-z]+\))? )?mod [a-z_0-9]+;' "$lib" \
| sed -E 's/.*mod ([a-z_0-9]+);.*/\1/' | sort | tr '\n' ' ')
[ -n "$mods" ] && echo "- **$crate** ($(basename "$lib")): $mods"
done
echo
echo "## CLI binaries"
echo
# Both spellings cargo accepts: `src/bin/<name>.rs` and the directory form `src/bin/<name>/main.rs`
# (benilla-extract, benilla-world) — the file-only glob listed three of five for months.
for b in crates/*/src/bin/*.rs crates/*/src/bin/*/main.rs; do
[ -f "$b" ] || continue
crate=$(echo "$b" | sed -E 's@crates/([^/]+)/.*@\1@')
case "$b" in
*/main.rs) name=$(basename "$(dirname "$b")") ;;
*) name=$(basename "$b" .rs) ;;
esac
echo "- \`$name\` (in $crate)"
done | LC_ALL=C sort
echo
# Every shader in the tree is compiled into the binary and addressed by the crate that owns it
# (decision 1175), so the crate is part of the shader's name now — list them all, per crate.
echo "## WGSL shaders (\`crates/*/src/shaders/\`, embedded)"
echo
for s in crates/*/src/shaders/*.wgsl; do
[ -f "$s" ] || continue
crate=$(echo "$s" | sed -E 's@crates/([^/]+)/.*@\1@' | tr - _)
echo "- \`embedded://$crate/shaders/$(basename "$s")\`"
done
echo
echo "## Instruments — \`\$WOW_*\` switches (env-var read sites)"
echo
echo "> The headless/dev instrument fleet, discovered from the code: every \`WOW_*\` env var"
echo "> some \`.rs\` reads, and where it's read — the doc comment at the read site is the"
# Don't name individual dev keys here — this line has gone stale twice (it still said 'P perf'
# after 0585 moved P onto the chord, and 'backtick panel' after 1043 moved the last two). The
# keys live in one place, \`debug_panel::DEV_CHORD\` and the panel footer; name the plane only.
echo "> semantics. (The in-window surfaces — the Ctrl+Shift dev-chord overlays — are plugins"
echo "> above; this indexes the switches that don't announce themselves.)"
echo
# Any quoted "WOW_*" literal in code — reads go through env::var but also through helpers
# (`knob("WOW_FX_AGE", …)`), so match the literal, not the call. Doc comments write the
# backticked/`$`-prefixed form, so they don't false-positive.
# The probe registry (`capture/probe_env.rs`, 2266 §A5) names every WOW_PROBE* variable as a
# quoted literal too; it is the table, not a read site, so it is not a place a switch is used.
grep -rHoE '"WOW_[A-Z0-9_]+"' crates --include='*.rs' 2>/dev/null \
| grep -v '^crates/benilla-app/src/capture/probe_env\.rs:' \
| sed -E 's@^crates/@@; s/:"/\t/; s/"$//' \
| awk -F'\t' '{print $2 "\t" $1}' | sort -u \
| awk -F'\t' '$1 != v { if (v) print line; v = $1; line = "- `" $1 "` — " $2; next }
{ line = line ", " $2 }
END { if (v) print line }'
echo
# Every script, with the first sentence of its own header (2331). The scripts are the
# instruments the WOW_* switches are read WITH — a trace reader, a two-client probe, a corpus
# census — and a 2026-09-22 sweep found eight of them referenced by nothing but the record that
# built them: findable only by someone who remembered the name. This is the index; the header
# is the source, so it cannot drift. (`winlab/` is the Windows lab laptop's, 2205/2211.)
echo "## Scripts (\`scripts/\`)"
echo
for f in scripts/*.py scripts/*.sh scripts/*.swift scripts/winlab/*; do
[ -f "$f" ] || continue
# The summary: the header's first non-empty content after the shebang, comment or docstring
# markers stripped, joined until its first blank line, cut at a sentence end or ~150 chars.
summary=$(LC_ALL=C awk '
NR == 1 && /^#!/ { next }
/^@echo off/ { next }
{ line = $0
sub(/^[[:space:]]*(r?"""|#|\/\/|rem)[[:space:]]?/, "", line)
if (line ~ /^[[:space:]]*$/) { if (got) exit; else next }
if (line ~ /^"""/) exit
got = 1; out = out (out ? " " : "") line }
END { print out }' "$f" | sed -E 's/\*\*//g; s/^[A-Za-z0-9_.\/-]+ (—|--) //; s/^([^.]*[.!?])([[:space:]]|$).*/\1/; s/^(.{150}).+/\1…/')
echo "- \`${f#scripts/}\` — $summary"
done
echo
} > docs/MAP.md
echo "genmap: wrote docs/MAP.md"