docs(tooling): T-1256 — adoption: the reach skill, the standing rule, the diagram

The consolidation has to be the only way to add tooling, or it re-fragments
the way the tree did — ~160 files and four naming conventions, because
nothing ruled out adding one more.

- Skill(reach) (T-1254), for both audiences. Using it: discover through
  --help, the domain index, the global flags an agent needs (--no-input,
  --detach with `jobs log --follow`), the stdout/stderr/verdict contract, and
  "read the exit status, never a field". Extending it: add a verb, add a
  domain, the seven invariants the conformance test enforces, a worked
  ReachError-with-fix example, the parity-first porting method, and the
  traps this repo has already paid for (the stamp registry, the canvas
  registry, archived generators that destroy data if run).
- CLAUDE.md (T-1255): no tooling is developed outside the CLI. The two
  exceptions are named so they aren't relitigated: Blender payloads and the
  Rust crates. Wording as confirmed by Jeroen, 2026-08-20.
- docs/diagrams/architecture/reach-cli.d2: the door, core, the domains, the
  carve-out, the one guarded exec, and the enforced invariants as a legend.
  D-263 now links to it; the amendment that had removed the dangling forward
  reference asked for exactly this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-23 20:21:27 +02:00
co-authored by Claude Opus 5.5
parent c597ec9131
commit 876cb8256e
7 changed files with 381 additions and 1 deletions
+68
View File
@@ -0,0 +1,68 @@
# reach — one CLI for all repo tooling (D-263)
#
# WHAT THIS ANSWERS: when something invokes a repo tool, what does the call
# pass through, and where is each kind of code allowed to live?
#
# Verified 2026-09-23 against the source (tooling/main.py, core/, domains/,
# test_conformance.py) — module names are real; the arrows are the import and
# call paths, not inferred. The conformance test enforces the rules in the
# legend; this picture does not.
#
# View: clide draw --file docs/diagrams/architecture/reach-cli.d2
direction: down
classes: {
caller: {style: {fill: "#1f6f43"; stroke: "#8fd9ae"; font-color: "#ffffff"; bold: true; border-radius: 4}}
door: {shape: hexagon; style: {fill: "#2b4c7e"; stroke: "#9dc0f0"; font-color: "#ffffff"; bold: true}}
core: {style: {fill: "#5c2d6e"; stroke: "#d3a8e6"; font-color: "#ffffff"; border-radius: 4}}
domain: {style: {fill: "#7a4b12"; stroke: "#e0b070"; font-color: "#ffffff"; border-radius: 4}}
outside: {style: {fill: "#3a3a42"; stroke: "#9a9aa4"; font-color: "#ffffff"; border-radius: 4; stroke-dash: 4}}
ext: {shape: cylinder; style: {fill: "#2a2a30"; stroke: "#9a9aa4"; font-color: "#ffffff"}}
}
# ---- callers ----
agent: "agents + humans\nreach <domain> <verb>" {class: caller}
hooks: "git hooks\nreach --no-input check …" {class: caller}
make: "make (orchestration only)\none-line delegates, e.g. regen-db" {class: caller}
# ---- the door ----
main: "tooling/main.py\nrouting only · DOMAINS registry\nlazy: --help imports no domain" {class: door}
# ---- core ----
command: "core/command.py @command\njob identity ⊃ handle_errors ⊃ logged" {class: core}
console: "core/console.py\nstdout = data · stderr = JSONL events + one verdict" {class: core}
errors: "core/errors.py ReachError\nevery raise carries fix=" {class: core}
process: "core/process.py — the ONE guarded exec\nargv lists · missing-binary remedy · cargo_binary" {class: core}
jobs: "core/jobs.py + domains/jobs\n--detach · log --follow · wait" {class: core}
# ---- domains ----
router: "domains/<d>/router.py\ntransport: args in, format out\nthe only place typer may be imported" {class: domain}
service: "domains/<d>/*.py (services)\nplain args in, data out\ncallable from tests without a CLI" {class: domain}
list: "14 domains\ncheck · validate · atlas {map, planet} · ledger · wiki\nassets · character · godot · visual · generate\nblender · pr · jobs · dev" {class: domain}
# ---- outside the package ----
payloads: "tooling/scripts/blender/ (41 payloads)\nrun under Blender's Python — never imported" {class: outside}
archive: "tooling/archive/\nprovenance — never run, not linted" {class: outside}
# ---- external programs ----
extbin: "cargo · godot · blender · ffmpeg · git · xvfb-run" {class: ext}
agent -> main
hooks -> main
make -> main
main -> router: "loads ONE domain, on use"
router -> command: "wraps every verb"
router -> service: "delegates"
router -> list {style.stroke-dash: 3}
command -> errors
command -> jobs
service -> console: "the only output path"
errors -> console: "verdict + fix"
service -> process: "any external program"
process -> extbin
process -> payloads: "reach blender run"
legend: "ENFORCED BY tooling/test_conformance.py\n\n1 typer only in router.py / main.py / core/cli.py\n2 no print(): everything through console\n3 every verb is @command, and has help\n4 every ReachError names a fix\n5 no subprocess outside core/process.py\n6 the Blender carve-out stays outside the package\n\nColour: green caller · blue the door · purple core\namber domains · grey dashed outside the package" {
style: {fill: "#1b1b22"; stroke: "#9a9aa4"; font-color: "#c8d0e0"}
}
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 37 KiB