From cadc48e995026371537eff1b47dcc98b8a110c58 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 23:47:30 +0700 Subject: [PATCH 1/2] skills: tri-pipeline fast path (t27b first, frontier --only seal reuse) and a t27o skill (Closes #10541) tri-pipeline: t27b test for the inner loop (rust_std.t27 0.89 s against 94 s for t27c test-report), t27c frontier --only for unchanged specs (REUSE = the sealed verdict stands; 5 specs in about 5 s), test-report once as the final gate, symlinked release binaries instead of rebuilds, t27b exit codes, the sandbox ZIG cache trap, git add -f for gen files; drop the stale "tri test does not yet fail on vacuous" (#10507 landed). t27o: where its truth lives, making a refusal class convertible and measuring it, --check, the loop tick for #10522, tonight's traps. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/t27o/SKILL.md | 67 ++++++++++++++++++++++++++++ .claude/skills/tri-pipeline/SKILL.md | 28 ++++++++---- 2 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 .claude/skills/t27o/SKILL.md diff --git a/.claude/skills/t27o/SKILL.md b/.claude/skills/t27o/SKILL.md new file mode 100644 index 0000000000..481fd5c57c --- /dev/null +++ b/.claude/skills/t27o/SKILL.md @@ -0,0 +1,67 @@ +--- +id: t27o +name: t27o +description: Change, test and measure t27o (Ouroboros), the converter of foreign code into .t27 (Rust front end today, SPICE beside it for #10486). Use when a refusal class should become convertible, when quoting t27o coverage, when running `tri t27o --check`, or when a loop tick works epic #10522. +argument-hint: "[refusal class | measure | check ]" +--- + +# /t27o -- foreign code into .t27, measured + + +> **Only t27: everything is written in `.t27`.** (Owner hard rule 2026-10-05; budget #7371; restated in every skill per #7588.) +> - New logic starts as a `.t27` spec with `test` / `invariant` blocks. It reaches Rust, C, Zig, JS or Python only through `t27c gen-*`, and generated files are never hand-edited (L2). +> - Hand-written code in any other language is denied by the hooks and by CI: `.rs .py .ts .js .sh .zig .c .go .v .yml .toml`, Dockerfile, Makefile; the full list is in `specs/policy/own_language.t27`. +> - Existing foreign code is debt that only shrinks. Delete it, or replace it with a spec and its generated output. +> - Thin hand-written glue is capped at 40 added lines per file and 80 per PR. No label lifts the cap. Never split a PR to slip under it. +> - What gen cannot express yet is a compiler defect. File it on #5980 with a minimal `.t27` repro instead of writing the code by hand. +> - This rule overrides any step below that says to write a script or code in another language. The full text is in `AGENTS.md`, "Only t27". + +## Where the truth lives + +| What | Where | +|---|---| +| The Rust front end: what converts, what is refused and why | `specs/tools/t27o/rust_convert.t27` (header comment, lines 1-110) | +| Rust std calls mapped to existing t27 fns | `specs/tools/t27o/rust_std.t27` | +| What a run's evidence is worth; which runner's exit code is a verdict | `specs/tools/t27o/ledger.t27` | +| Golden criteria, the service, the installer | `golden.t27`, `service.t27`, `install.t27` in the same directory | +| The SPICE front end (analog M2a) | `specs/tools/t27o/spice_convert.t27`, #10509 -- owned by the analog loop | +| Baseline numbers, the field, ten weaknesses | `docs/nona-03-manifest/T27O-COMPETITORS-AND-DOGFOOD.md` | +| The overnight loop's state | epic #10522, its "Loop ledger" comment | +| The CLI glue (reads the file, runs rustc, t27b and t27c, writes) | `cmd_t27o` in `cli/tri/src/main.rs`; every rule it applies is gen-rust of the specs above | + +## Make a refusal class convertible + +1. Find it in the numbers, not by intuition: the first-refusal table in the doc above, or a fresh measurement (below). Pick the class with the most first refusals that needs no new type in the subset. +2. Reproduce on a real file: `./target/release/tri t27o | grep -n "refused fn "`. +3. Write the test first, in `rust_convert.t27`: the tests near the end call `body(...)` / `convert(...)` on a Rust text and compare the output text. Add the rule; add a negative control (a near-miss form that must stay refused, or a mutated copy that must fail). +4. Inner loop: `./target/release/t27b test specs/tools/t27o/rust_convert.t27` (66 tests in about 14 s). The final gate once: `t27c test-report` on it, `vacuous passes` must be 0. +5. Regenerate, never hand-edit: `./target/release/t27c gen-rust specs/tools/t27o/rust_convert.t27 > gen/rust/tools/t27o/rust_convert.rs`, check it compiles with `rustc --edition 2021 --crate-type lib`, then `t27c seal ... --save` and `--verify`. +6. Measure the gain on the same files and commits, before and after, and put both numbers in the PR. + +DRY: a std call goes through `rust_std.t27`, which names an existing t27 fn with the same I/O; search `t27c dupes --name ` before writing a new one. + +## Measure coverage + +- One file: the last line of `tri t27o ` is its count: `t27o: N fn seen, M converted (a of b tests), R refused (S kept as stubs); coverage P%`. +- Many files: the same command per file, with a time cap, each output kept, then the counts summed and the `// t27o: refused ... : \`\`` lines tallied. Cap every file (`perl -e 'alarm 30; exec @ARGV' ./target/release/tri t27o `): run time grows faster than file size, and `bootstrap/src/compiler.rs` (1.9 MB) does not finish in 5 min. +- Count first refusals as what they are: the cascade (`uses refused ...`) is 13% of them and moves when a root refusal is fixed, so rank work by root reasons. +- Quote a number only with its repository, commit and binary. + +## `tri t27o --check` + +`--check` builds the oracle harness with rustc, writes `specs/t27o_check/.t27` with the oracle's vectors as tests, and judges it. Since #10525 it runs `t27b test` first and `t27c test-report` only when t27b's exit code is not a verdict (`t27b_outcome` in `ledger.t27`); the fallback says which code caused it. Golden means equivalent on the recorded vectors, never a proof for all inputs. + +## The loop tick (epic #10522) + +1. Read the ledger comment on #10522; `CronList` (delete duplicates after a restart); list running subagents. A lane a row calls running is not started again. The analog loop keeps its own ledger on #10486 and owns #10509 and `specs/analog/`: never start work there. +2. Anomalies before new work: open PRs of the epic that are red, conflicting or stale; a lane that reported done with no PR (`git ls-remote origin | grep t27o`); a seal that no longer verifies. +3. Opus plans and reviews, Sonnet implements (`specs/claw/model_route.t27`). Two lanes at most from this loop while the analog loop runs on the same 8 GB Mac; one lane at a time edits `rust_convert.t27`. +4. Merge only green; enable auto-merge after reading the diff. To change a PR under auto-merge, disable it, push, enable it again. +5. Edit the ledger row last; add a trap here when it cost more than one try. + +## Traps met on this Mac + +- `.claude/skills/**` is not writable from the session's own worktree in the sandbox; a checkout that touches it half-applies (index changed, file unchanged). Work in a worktree under the scratchpad: `git worktree add --no-track -b $SCRATCH/wt-x origin/master`, then `git -C $SCRATCH/wt-x config --worktree core.hooksPath .githooks`. +- `--no-track` matters: the shared `.git/config` cannot be locked from the sandbox, so `-B` with tracking fails after creating the branch. +- `gh` fails on TLS in the sandbox: use the REST and GraphQL APIs with `curl -H "Authorization: Bearer $(gh auth token)"`; write bodies to a file and build JSON with `python3`, never with backticks inside double quotes. +- Auto-merge by API: the GraphQL mutation `enablePullRequestAutoMerge` with the PR's `node_id` and `mergeMethod: SQUASH`. diff --git a/.claude/skills/tri-pipeline/SKILL.md b/.claude/skills/tri-pipeline/SKILL.md index 631f3093a2..5f7c127405 100644 --- a/.claude/skills/tri-pipeline/SKILL.md +++ b/.claude/skills/tri-pipeline/SKILL.md @@ -25,21 +25,30 @@ description: The exact commands of the t27 toolchain (tri, t27c, t27b, tri t27o) Use `./target/release/tri` and `./target/release/t27c` by path when the difference matters. -## One spec, end to end +## One spec, end to end -- fast path first + +The native JIT for the inner loop, the seal to skip what did not change, the Zig reference once at the end. Measured on the bench Mac, 2026-10-18, under load: + +| Step | Command | Cost | +|---|---|---| +| parse + typecheck | `./target/release/t27c check ` | under 1 s | +| inner loop, after every edit | `./target/release/t27b test ` | rust_std.t27 0.89 s; rust_convert.t27 (66 tests) 13.5 s | +| specs you did not change | `./target/release/t27c frontier --only --list` | about 5 s for 3 specs; REUSE = seal, closure and toolchain still match, so the sealed verdict stands: do not re-run its tests | +| final gate, once | `./target/release/t27c test-report ` and `tri test ` | rust_std.t27 94 s; per-test assert counts and `vacuous passes N of M` | +| seal | `./target/release/t27c seal --save`, then `--verify` | writes `.trinity/seals/_.json`; must print "all hashes MATCH"; the seal is also the REUSE key | ```bash -export ZIG_GLOBAL_CACHE_DIR=$TMPDIR/zig-cache ZIG_LOCAL_CACHE_DIR=$TMPDIR/zig-local # sandboxed sessions -./target/release/t27c check specs//.t27 # parse + typecheck, fast -./target/release/t27c test-report specs//.t27 # runs every test; per-test assert counts; "vacuous passes N of M" -./target/release/tri test specs//.t27 # exit code gate over test-report (does not yet fail on vacuous: #10497) -./target/release/t27c seal specs//.t27 --save # writes .trinity/seals/_.json -./target/release/t27c seal specs//.t27 --verify # must print "all hashes MATCH" +export ZIG_GLOBAL_CACHE_DIR=$TMPDIR/zig-cache ZIG_LOCAL_CACHE_DIR=$TMPDIR/zig-local # every sandboxed shell, every time ``` +- **Do not build what already exists.** If another worktree of this checkout has release binaries, symlink them: `mkdir -p target/release && ln -sf /target/release/{t27c,t27b,tri} target/release/`. Build only the crate you changed, into one shared `CARGO_TARGET_DIR` (cargo's lock serializes builds), and copy the binary out right after. +- **t27b's exit code** (`cli/t27b/README.md`): 0 pass, 1 a test failed, 2 unsupported construct, 3 front end, 4 JIT and interpreter disagree (`--check`), 5 codegen limit. Only 0 and 1 are a verdict; on any other code run `test-report` (the rule is `t27b_outcome` in `specs/tools/t27o/ledger.t27`, #10525). +- Without the two `ZIG_*` variables, `test-report` says BLOCKED and `seal --save` refuses with "does not compile: failed to create output directory ... PermissionDenied". That is the sandbox, not the spec. - `t27c test ` only LISTS the test blocks; it runs nothing. Never quote it as a test result. -- A pass with 0 runtime asserts is vacuous. Read the `vacuous passes` line of `test-report` yourself until #10497 lands. -- Negative control: copy the spec to `$TMPDIR`, break one constant, run `test-report`, and see a FAIL before you claim the tests mean anything. +- A pass with 0 runtime asserts is vacuous; `tri test` fails on it since #10507. t27b prints only the total assert count, so the vacuity number comes from `test-report`. +- Negative control: copy the spec to `$TMPDIR` (or beside it when it has a `use`), break one constant, run `t27b test`, and see a FAIL before you claim the tests mean anything. - `t27c test-report` exits 1 on FAIL and 2 on BLOCKED (did not compile). +- `gen/` is in `.gitignore`: a generated file a crate includes by `#[path]` is committed with `git add -f`. ## Generate and lower @@ -51,6 +60,7 @@ export ZIG_GLOBAL_CACHE_DIR=$TMPDIR/zig-cache ZIG_LOCAL_CACHE_DIR=$TMPDIR/zig-lo ## Foreign code in - `tri t27o [--name N] [--oracle] [--check]` turns a Rust file into a `.t27` skeleton; refused bodies keep their signature and a comment naming the refused construct. Rules: `specs/tools/t27o/rust_convert.t27`. Rust is the only front end today; a new language is a new `specs/tools/t27o/_convert.t27` beside it (the SPICE front end for #10486 goes there). +- The last line of every conversion is the file's count (`t27o: N fn seen, M converted ...`). Coverage on the roadmap repositories, the refusal kinds and the ten weaknesses: `docs/nona-03-manifest/T27O-COMPETITORS-AND-DOGFOOD.md`. How to change the converter and measure it: the `t27o` skill. ## Is someone already on it? From 70b844febbd0bf1de9df9c462f3af8a11e59e630 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 00:40:10 +0700 Subject: [PATCH 2/2] skills: the net-zero hand-line rule of own-language, and $TMPDIR over the scratchpad (Closes #10541) #10538 failed the required own-language check with +9/-0 hand lines in cli/tri/src/main.rs: check_budget refuses a diff whose counted foreign lines gain more than they lose, on top of the 40/80 cap. tri-pipeline says so under PRs; the t27o skill adds the numstat one-liner. The scratchpad was wiped on a session resume (worktrees, cargo target, measurements); $TMPDIR survived, so loop worktrees live there. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/t27o/SKILL.md | 13 ++++++++++++- .claude/skills/tri-pipeline/SKILL.md | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.claude/skills/t27o/SKILL.md b/.claude/skills/t27o/SKILL.md index 481fd5c57c..5a487d0036 100644 --- a/.claude/skills/t27o/SKILL.md +++ b/.claude/skills/t27o/SKILL.md @@ -59,9 +59,20 @@ DRY: a std call goes through `rust_std.t27`, which names an existing t27 fn with 4. Merge only green; enable auto-merge after reading the diff. To change a PR under auto-merge, disable it, push, enable it again. 5. Edit the ledger row last; add a trap here when it cost more than one try. +## Hand-written glue must net to zero + +The required check `own-language` (`check_budget` in `specs/policy/own_language.t27`) fails a pull request when the counted hand-written foreign files gain more lines than they lose over the whole diff -- on top of the 40-per-file / 80-per-PR cap. Specs and `gen/` do not count. A `#[path]` mount plus a few lines in `cmd_t27o` therefore needs an equal deletion in the same diff: dead code in `cli/tri/src` (`cargo check -p tri` lists it), or hand logic moved into a spec and replaced by a call. Check before the first push: + +```bash +git diff --numstat origin/master...HEAD -- cli bootstrap/src tools scripts | awk '{a+=$1; d+=$2} END {print "added", a, "deleted", d}' +``` + +PR #10538 failed it with +9 / -0 at 1e20cfb34. + ## Traps met on this Mac -- `.claude/skills/**` is not writable from the session's own worktree in the sandbox; a checkout that touches it half-applies (index changed, file unchanged). Work in a worktree under the scratchpad: `git worktree add --no-track -b $SCRATCH/wt-x origin/master`, then `git -C $SCRATCH/wt-x config --worktree core.hooksPath .githooks`. +- `.claude/skills/**` is not writable from the session's own worktree in the sandbox; a checkout that touches it half-applies (index changed, file unchanged). Work in a separate worktree: `git worktree add --no-track -b $TMPDIR/t27o-loop/wt-x origin/master`, then `git -C $TMPDIR/t27o-loop/wt-x config --worktree core.hooksPath .githooks`. +- The session scratchpad is wiped when the session resumes (2026-10-18 23:53: lane worktrees, the cargo target and raw measurements gone; pushed branches survived). `$TMPDIR` survived: keep worktrees and data under `$TMPDIR/t27o-loop/`, and commit + push after every working step. - `--no-track` matters: the shared `.git/config` cannot be locked from the sandbox, so `-B` with tracking fails after creating the branch. - `gh` fails on TLS in the sandbox: use the REST and GraphQL APIs with `curl -H "Authorization: Bearer $(gh auth token)"`; write bodies to a file and build JSON with `python3`, never with backticks inside double quotes. - Auto-merge by API: the GraphQL mutation `enablePullRequestAutoMerge` with the PR's `node_id` and `mergeMethod: SQUASH`. diff --git a/.claude/skills/tri-pipeline/SKILL.md b/.claude/skills/tri-pipeline/SKILL.md index 5f7c127405..4a62839197 100644 --- a/.claude/skills/tri-pipeline/SKILL.md +++ b/.claude/skills/tri-pipeline/SKILL.md @@ -69,6 +69,7 @@ export ZIG_GLOBAL_CACHE_DIR=$TMPDIR/zig-cache ZIG_LOCAL_CACHE_DIR=$TMPDIR/zig-lo ## Pull requests and CI +- The required check `own-language` fails a PR whose hand-written foreign lines (Rust, Python, shell, ...; not specs, not `gen/`) gain more than they lose over the whole diff, beside the 40-per-file / 80-per-PR cap: glue is paid for by deleting as much hand code in the same diff. Check with `git diff --numstat origin/master...HEAD` before the first push (the `t27o` skill has the one-liner). - `tri pr ready [--wait --required-only]` prints one `VERDICT:` line (safe to merge, DO NOT MERGE, WAIT, CANNOT TELL, NEW REASON) after classifying every failing check. - `tri pr landed --probe S --file P` -- did the content actually reach the default branch. - `tri night`, `tri red now|why`, `tri ci baseline`, `tri gates ...`, `tri merging`, `tri lanes --repo t27 --base master`.