tc-team
Feed it a spec, get test cases. The LLM only writes sentences; deterministic code owns every structure and gate.
Point it at a spec document and it fills a live spreadsheet tab with a test-case set. Start to finish, one invocation, unattended.
The interesting part isn’t “an LLM writes test cases.” Anyone can do that badly. The interesting part is the split.
Two lanes — the LLM owns sentences and judgment; deterministic code owns structure and fact.
Why split at all
Early on the model drove the whole pipeline. It drifted.
Row counts changed between stages, coverage quietly dropped, and two runs never produced the same shape. Models are excellent at approximately right. They cannot be used for same-input-same-output.
The fix wasn’t better prompting. It was confiscation.
| Owner | What it holds |
|---|---|
| Deterministic code | stage order, row skeleton, numbering, formatting, merging, coverage ledger, sheet writes |
| LLM | design judgment, sentences, review findings, verdicts |
Eight stages
| Stage | Job | Owner |
|---|---|---|
| S0 | fetch source, take the run lock, self-check the input | code |
| S1 | analyse the spec → design skeleton | LLM |
| S2 | design isolation gate + source slicing | code |
| S3 | build skeleton → fan out for prose → merge | code + LLM |
| S4 | adversarial review, verdicts, coverage ledger | LLM |
| S5 | apply fixes + four gates | code |
| S6 | write to the live sheet | code |
| S7 | finalise, dashboard, notify | code |
S1 through S7 are written up individually. Each stage cost something to learn, and a table row doesn’t hold it.
S3 shows the architecture best. Code builds the skeleton first, cuts it into 25-row chunks, lets the model fill in only the prose, and code merges the results back. On merge it checks index, echo, hash and count — so if the model shifted a row or touched somebody else’s, only that chunk re-runs. Every other chunk’s work survives.
Gates — it stops rather than continues
This is what defines the tool’s character. It does not “carry on doing its best.”
| Gate | Stage | On failure |
|---|---|---|
| input self-check | S0 | one refetch → still bad, refuse to start |
| design gate | S2 | design defect → loop back to S1 |
| merge verification | S3 | re-run only the mismatched chunk |
| apply before-mismatch | S5 | rejection is correct behaviour — regenerate the fix plan |
| content gate | S5 | block vague phrasing and unsupported deferrals |
| duplicate gate | S5 | force a merge when identical cases remain |
| traceability | S5 | uncovered rule → stitch in a row, reapply |
The before-mismatch gate matters most. Before applying a fix it asks “is the place I’m about to edit still what I read?” and refuses if not. A mismatch means a human touched the sheet or an earlier stage shifted — and pushing through would overwrite the wrong row.
There is exactly one place the run halts: an integrity violation. Everything else completes unattended.
Review is adversarial, not collaborative
Generated cases are read by several lenses in parallel — structure, quality, and comparison against the source. They cannot see each other’s findings. If they could, they would converge, and convergence means missing the same things together.
A separate judge cross-examines the findings, drops the false positives, and produces a fix plan in a form a machine can apply. Surviving findings are applied by code. Nobody asks the model to please go fix it.
The same stage builds the coverage ledger: each rule extracted from the spec is mapped to the cases that cover it, and anything uncovered gets a row stitched in at S5.
The sheet is touched exactly once
S6 contacts the live spreadsheet one time, and decides by ownership marker.
- Our tab → wipe and rewrite in full (idempotent)
- Somebody else’s tab → don’t touch it; create a
_v2alongside - Every other tab → off limits, unconditionally
Run it five times and the result is identical, and it never overwrites another person’s work. For automation that runs next to humans, those two properties come before everything else.
Two lessons that cost something
Inserting a row renumbers everything below it. The coverage ledger points at cases by number, so stitching in a row makes the whole ledger stale. My first instinct was to remap arithmetically. Wrong. It now joins by content, not position — category, verification stage, and the reproduction step. Positions move; content doesn’t.
Hand-driving costs half the clock. I once ran the stages manually one at a time. Wall clock was 2h23m; the machine actually worked for 1h12m. The rest was the pipeline waiting for me to type the next command. That number is the reason unattended completion exists.
Things that were rejected
The version history is mostly a list of things that looked like progress and weren’t.
Vocabulary-based progress detection — rejected after two measurements. Unifying the thresholds — rejected. The confidence heatmap ended up as pure deterministic output with zero model calls, after the model-scored version failed to reproduce.
Every rejection got a written resume condition. Without one you re-propose the same idea six months later.
An honest limit
A thin spec produces thin output. This tool will not invent design that isn’t there — with no basis for a case, it flags “needs spec clarification” and moves on. I think that’s correct, but in an organisation where specs are thin it yields less than people expect.