Skip to content

E2E contract-corpus pilot — convert scaffold's tests to a txtar corpus (Qwen3.6-27B) - #25

Closed
A wants to merge 57 commits into
masterfrom
refactor/e2e-contract-corpus-pilot-qwen
Closed

E2E contract-corpus pilot — convert scaffold's tests to a txtar corpus (Qwen3.6-27B)#25
A wants to merge 57 commits into
masterfrom
refactor/e2e-contract-corpus-pilot-qwen

Conversation

@A

@A A commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Overview

booping scaffold's pytest suite described the Python implementation — Node object graphs, exception fields, exact message text — so it broke on no-op refactors and would not survive a rewrite of the CLI in another language.

This PR replaces it with a contract corpus: one txtar file per case carrying its fixture tree, the invocation, and everything asserted about the result (stdout, stderr, exit code, files left behind), executed by a standalone runner that shells bin/booping as a black box. scaffold is the pilot; the harness and format are what later plans convert the remaining subcommands onto.

booping-python/e2e/

  • _txtar.py — vendored txtar parse/serialize (Go semantics, stdlib only).
  • README.md — the format contract, written as the document a future non-Python runner is built from: section vocabulary, sandbox and env bindings, multi-line cmd, {CWD}/{HOME}/{XDG} tokens and the [..] wildcard, exit codes, the --update loop.
  • run.py — fresh home//xdg//cwd/ sandbox per case, subprocess invocation, labeled unified diffs, {N} passed, {M} failed, exits 0 pass / 1 fail / 2 malformed.
  • 36 cases covering every CLI-observable scaffold behavior — destinations and receipt shape, seed rendering, filename keys, error paths, the real core config trees, the scaffold→scaffold→query chain, the vault log line — replacing both superseded test files (607 lines deleted).

Wired as just e2e [pattern…], added to just ci and the CI python job; basedpyright now includes e2e.

Model & Host — implementer worker

Milestones were coded by a local Qwen worker (llama-developer agent) rather than a hosted Claude model:

GGUF repo unsloth/Qwen3.6-27B-MTP-GGUF
Quant UD-Q6_K_XL · 24.23 GiB (+0.35 vs non-MTP)
Projector mmproj-F16 · 0.86 GiB, vision
Architecture dense 27B — all params active/token
Layers 64 = 48 linear + 16 full attn
KV geometry 4 KV heads · head_dim 256
Native ctx 262144 — served at 196608
KV cache cost 6.0 GiB @196608 q8_0 · 12.0 GiB at f16
Runtime image llama-swap v244-cuda-b10156, pinned
GPUs cuda0 3090 24G + cuda1 A4000 16G
Split --tensor-split 26,14 (not 24,16)
Served ID Qwen3.6-27B

Known issue — not fixed in this PR

Verify surfaced that --update is not a byte-level no-op on an already-green corpus: it silently downgrades real-logging.txtar's [..] timestamp wildcard to a literal value and drops a trailing blank line in real-playbook-authoring-scaffold.txtar. Contradicts M02's DoD ("--update on an already-green corpus is a byte-level no-op"). Opening as draft pending a fix milestone.

Sprint metrics

Development only — the grooming session is excluded, since the plan is shared with #24. The Qwen row is the four local milestone sessions the llama-developer wrappers drove; the two runs that died on a gateway 500 are excluded, they produced no work.

IN (uncached) is only the input that escaped prompt caching — the real input volume is the cached column.

Role Model IN (uncached) OUT cached in (write + read) Active
Orchestrator — develop driver claude-sonnet-5 310 81.4k 735.7k + 15.49M 67 min
llama-developer wrappers — 4 milestones claude-opus-5 138 8.5k 269.3k + 609.8k 83 min wall, mostly waiting on Qwen
Verify agent claude-sonnet-5 194 25.8k 215.5k + 6.00M 10 min
Claude subtotal 642 115.7k 1.22M + 22.10M
Milestone sessions — the implementer Qwen3.6-27B 285.8k 70.7k 0 + 14.63M 66 min, 307 turns

23.44M Claude tokens + 14.99M Qwen tokens = 38.42M total. Story points: 18 across 4 milestones.

The Qwen row's uncached IN is three orders of magnitude above the Claude rows because llama-swap reports a reused prefix as a cache read but bills each turn's new content as plain input — it has no cache-write bucket at all (cache_creation is 0 across all four sessions).

🤖 Generated with Claude Code

A and others added 30 commits August 9, 2026 13:52
…nts the same diff receipt and types its scalars
`type:` rendered raw, so a missing `--set type=` produced a YAML null with no
error and the run continued with a plan carrying no task type. It now goes
through the same `default('') | tojson` seam `title:` already used.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t suites

Scalar typing, frontmatter-update diff receipts and scaffold stdout receipts
were one function per case. Each block is now a single parametrized test over
an input/expected table, so a new case is a row. Assertions got stronger, not
weaker: scalar rows check the reloaded value's exact type, and the append and
removal rows assert the full `--- / +++ / @@` header.

`import yaml as pyyaml` moves to module level; the eight function-local copies
are gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The review artifact, the plan's `code_reviews:` back-link, and two lessons the
run produced — top-level imports over lazy ones, and code-only review scope.

The back-link is hand-corrected: `frontmatter-update --append` into an empty
inline `[]` list emitted the old `[]` as the first element.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… list

The exit hook parses plan frontmatter with a regex — it is stdlib-only by
design — and its inline-value branch knew only `null` and `~`. A plan carrying
`code_reviews: []` had that `[]` inserted as the list's first element; a
populated `[a]` would have landed as one element literally named `[a]`.

`_inline_items()` now reads the flow form: `[]` yields nothing, `[a, b]` yields
its elements, `null`/`~` mean unset, anything else is a lone scalar. The hook
has no tests, so the new module drives it as a subprocess the way
`playbook-transition` does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`0014` broadens from lazy imports alone to the project's code style practices,
picking up the parametrized-tests preference the same review raised.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tree keys go through the same Jinja env and --set globals as seed bodies,
with the filesystem-safety check re-run on the rendered name so an unsafe
render fails before any write.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
core.groom_playbook.milestone_scaffold seeds one file per milestone with a
fixed identity frontmatter and the three required headings; the shared
core.plans.milestones key describes where they live and how index.md's
table projects them. Ids are quoted so zero-padded values do not read back
as octal ints and scramble sorting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each template's Milestones section is now a generated table and a new
Milestone files section giving the per-file body shape for that surface;
checklists check the plan directory instead of a single file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
draft-plan scaffolds one file per milestone and writes its body in place,
then generates index.md's table with booping query; present and
cross-review read the same source. sp moves to develop's refresh script.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A milestone state machine addressed per instance writes status via
playbook-transition only; each edge runs refresh-milestone-table, which
rewrites index.md's Milestones table and re-sums the plan's sp from the
milestone files. develop-loop moves into a one-step milestones subgraph,
which is what makes the {instance} artifact legal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A and others added 27 commits August 10, 2026 18:15
develop-loop composes one literal briefing block naming the milestone
files as contract and index.md as context; provision groups from a query
over milestone frontmatter and verify reads the milestone files. The
worker contract states which path binds it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Uncommitted output of the docs-refresh run, captured before the
milestone-file sprint rewrites the same surfaces.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
retro, code-review and the lesson-check partial read index.md plus the
milestone files; the vault, develop and groom pages, README and CLAUDE.md
describe the plan directory, the milestone file contract and the
generated table.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each hermetic fixture plan gains a milestones/ directory with two files
and an index table generated by refresh-milestone-table; cache-warmup
keeps none so the missing-sp branch stays covered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A milestone becomes a directory whose file is named after it, so a
runner-written feedback.md can sit beside the contract and a bare
wikilink still resolves. The worker now runs its own Verify and commits;
the runner validates the diff, keeps the bookkeeping and records failed
attempts in feedback.md, routing fixes to a fresh fallback_agent.
playbook-state strips the matched segment cleanly so instances key the
same in filename and directory positions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…via local pi worker wire pi-developer agent into develop via vault config
Plan: scaffold tests become a txtar contract corpus under booping-python/e2e/
with a standalone runner; superseded unit tests deleted. Drops the retired
reshape lesson pair (0008/0009).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ar cases covering destination semantics, seed rendering, error paths, real config trees, multi-command chain, vault logging, and declaration-order preservation
Remove tests/commands/scaffold_test.py and tests/context/scaffold_test.py
(superseded by e2e corpus). Clean stale scaffold_test references from two
e2e case descriptions. Update CLAUDE.md: add just e2e to Commands, note
e2e/ in Layout.
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