Repository navigation
teamwright 0.11.0
Added
-
scripts/tw-gate <area> [--fresh], installed into projects: the full gate once per state of the code. In an autonomous run a small task took about 90 minutes, and its ten-minute area gate ran five times on the same code — twice by the developer (it cut the output withtail -8and had to run it again to see the result), then by the flow's Gate step, the code-reviewer and the task-validator. tw-gate runs what the role files name as the gate (the area'sgenerate_command, then itsgate_commandthroughscripts/tw-run, or the top-levelgate_commandas written), writes the whole output to.teamwright/logs/gate/<area>-<time>-<commit>.log($TEAMWRIGHT_LOG_DIRrespected), records the run in<area>.jsonandruns.jsonl(area, head, clean, tree, exit, duration, times, log, command), and prints PASS/FAIL, the exit code, the duration, the last 30 lines of a failure and the log path; its exit code is the gate's. A PASS is reused when the working tree has the same content (a git tree id of HEAD plus uncommitted and untracked changes, ignored files left out), root and command, and the tree did not change during the run:PASS (cached: same commit <sha>, <when>, log <path>). So the developer's PASS before the commit answers the flow, the reviewer and the validator once that content is committed. A FAIL is never reused;--freshalways runs. Tests:tests/test-tw-gate.sh(also under bash 3.2). -
scripts/tw-mutants <area> <ID> --tree <copy>, installed into projects: the task's mutant catalog, in one runner start. Mutants are kept in the repository asdocs/tasks/<ID>.mutants/: one<NN>-<slug>.mutantfile each —guarantee:(the Acceptance/DoD line it breaks,-for none),file:,added_by:, optionaltest:andequivalent:, and the exact replacement between--- old/--- new/--- end— plustest-command, the command that runs only the task's tests. tw-mutants refuses the working tree, checks every entry against the throwaway copy (old text exactly once), and runs a baseline and every mutant in onescripts/tw-run <area>start with the copy as the project root (forrunner: docker, the copy is what the container mounts). One line per mutant (killed,SURVIVED … breaks: <guarantee>,survived, follow-up,equivalent), a report in.teamwright/logs/mutants/; exit 0 when no survivor breaks a guarantee, 1 when one does, 2 for a stale or broken entry, 3 for a red baseline. Tests:tests/test-tw-mutants.sh(with a fake docker that counts starts and checks the mount). -
tw-gate and tw-mutants outlive Claude Code's 10-minute Bash limit. In an autonomous run the gate was cut by the limit and started again; agents also killed the test container to restart, or polled the log in
until grep …; sleeploops. Both scripts now start the real run detached — its own session and process group (scripts/hooks/_job.sh, Python'sstart_new_session, bash 3.2 and macOS included), output to the log, a job directory keyed like the cache under.teamwright/logs/{gate,mutants}/jobs/— and wait in the foreground up toTW_WAIT_SECONDS(default 540). Done in time: the normal result and exit code. Not yet:RUNNING: gate <area> (pid N), started <when>, log <path> - run the same command again to keep waiting, exit 75. The same command again attaches to that run instead of starting another; a result that finished while nobody waited is printed by the next same call; a run of the same area (or task) on other content is waited for first;--freshattaches to a running run rather than starting a second; a run killed without a result (pid gone) is reported, cleaned up and started again; a killed caller leaves the run going;scripts/tw-gate --stop <area>ends one. Role files, AGENTS.md and the flow: never wrap them in sleep loops or background calls — onRUNNINGcall the same command again; other long test commands run once throughscripts/tw-runwith output to a file. Tests: fake slow gates and catalogs with tiny wait budgets (attach, uncollected FAIL,--fresh, killed run, other content, killed caller,--stop). -
scripts/tw-gate --for-task <ID>/--for-diff <base>and--top: gate the areas a task changed. In one project the validator ran the server suite for a CI-only change. The task's changed files (its own commits — on a branch, those since it left the default branch; everywhere, commits whose subject names the id first, so a commit that only mentions the task in its body does not count — plus uncommitted and untracked) map to the areas whosesource_dirs,test_dirsorworkdirhold them; a file outside every area means the top-level gate (--top, every area when there is none), except records, docs and Markdown, CI configuration and tool settings, which need no local gate ($TEAMWRIGHT_NO_GATE_RE). The flow's Gate step, the code-reviewer and the task-validator run exactly the listed gates. -
Throwaway copies get generated code and a warm build cache. In one project every clean copy for mutants ran
build_runnerfor ~5 minutes. New area keys:generated: [paths](the outputs ofgenerate_command) and optionalgenerate_inputs: [paths]— tw-mutants copies the outputs from the working tree when the copy's inputs are unchanged, and otherwise runsgenerate_commandin the copy inside the same runner start (exit 4 when it fails);copy_env: {VAR: value}— applied byscripts/tw-rununderTW_RUN_COPY=1,{cache}=.teamwright/cache/<area>/copyof the main checkout (/tw-copy-cachein docker), e.g.CARGO_TARGET_DIR: "{cache}/target", so a copy neither builds from cold nor tramples the working tree's target dir;TW_CACHE_ROOTmakes a copy's docker runs reuse the main checkout's package caches.scripts/tw-mutants <area> --tree <copy> --preparedoes only this step for tests run by hand. The installer validates the keys. Tests intest-tw-mutants.shandtest-installer.sh. -
scripts/tw-test <area> [tests ...]and the test guard: tests only through the wrapper. In one project's autonomous run the test-writer, after fixing one test, ran the same ~4-minute suite four times in a row: twice with the output cut by| grep … | tail -15(the failures were lost), once with-p no:logging, which removed thecaplogfixture and produced 25 false errors, once right — ~12 minutes, and with one worker instead of the configuredPYTEST_WORKERS; the developer ran the gate script directly twice. The role files already said "never cut output"; agents ignored it, so the tooling enforces it.scripts/tw-testruns the area's newtest_select_command({tests}= the selectors, shell-quoted; workers stay in the config string, e.g.pytest -q -n ${PYTEST_WORKERS:-4} {tests}), elsetest_commandwith the selectors appended, throughscripts/tw-run; the whole output goes to.teamwright/logs/test/, the screen gets PASS/FAIL, exit code, duration, the counts (pytest, cargo test, go test, flutter/dart test, jest, vitest), the failed tests with their first error line (at most 30) or the last 15 lines, and the log path. A repeat on the same area files (tw-gate's area-scoped key), command and selectors answers from the cache — a FAIL too: the answer to a failure is its log;--freshreruns. Detached like tw-gate (RUNNING, exit 75, the same call attaches);--tree <copy>runs in a throwaway copy. The new PreToolUse hookscripts/hooks/test-guard.sh(Bash) refuses test runners and gates called directly — built-in patterns (pytest,python -m pytest,make test|check,flutter|dart|go|cargo test,npm|pnpm|yarn test,vitest,jest,phpunit,gradlew test,mvn test, …, also behinduv run/npx,sh -c,docker runand insidescripts/tw-run), every area'sgate_command/test_command/test_select_commandand the top-levelgate_command(e.g. a project'stool/gate.sh), and the new top-leveltest_runner_patterns— and test or gate output piped intogrep/tail/head/sed/awk, wrappers included; the message names the command to run instead (scripts/tw-test server tests/pipeline,scripts/tw-gate server) and says to read the log. It has its own switch, because gate-first only warns:process.test_guard: block | warn | off(defaultblock, also in gate-first),TW_TEST_GUARDin the environment Claude Code starts with wins; the area's lint, codegen, API and UI test commands throughscripts/tw-runstay allowed. The installer validates the new keys. Roles: developer, test-writer, code-reviewer and task-validator,AGENTS.md,gates.md(new section Test guard),tools.md,plugin.md, the settings, adopt and init skills and the README carry one rule — tests only viatw-test, gates viatw-gate, mutations viatw-mutants; the test-writer's wrong implementations and the validator's hand-run tests in a copy usetw-test --tree. Tests:tests/test-tw-test.sh,tests/test-test-guard.sh,tests/test-installer.sh. A config command whose head is a script that only wraps another one (tool/gate.sh sh -c '…') is not itself a signature — its inner command is checked, so lint, formatting and migrations through the same wrapper still run. -
pre-push warns about a CI-skip marker on the pushed tip (
[skip ci],[ci skip],[no ci],[skip actions],***NO_CI***,skip-checks: true): CI decides by the tip's message, so the code under it and a pull request opened from it go unchecked — one project lost ~8 minutes on it. A warning, not a refusal. AGENTS.md, the flow's push step anddocs/usage.mdnow say: never[skip ci]in a commit message, least of all on the last commit before a push; record-only pushes are skipped by the CI configuration (paths-ignore,scripts/tw-ci-changes).
Changed
process.batch.parallelup to 10 (was 1–4): an owner running more parallel worktree sessions (and per-worktree tool slots, e.g. Serena) on one project hit the cap. Default stays 1 (off).- Roles run the gate through tw-gate. The developer runs only the task's tests and the touched modules while working (
{{TEST_COMMAND}} <files>, now in its parameters), and the full gate once before handing over, never cutting its output; the flow's Gate step, the code-reviewer and the task-validator runscripts/tw-gate <area>and accept a cached PASS on the same commit, still comparinggit statusbefore and after; their judgement stays independent.AGENTS.md, the role files, the flow, parallel and init skills,gates.md(new section Gate runs),lifecycle.md,sessions.md,tools.md,plugin.md,usage.md,design-decisions.mdand the README describe it. Projects pick it up after/plugin updateandtw-install.py apply. - A long gate is never run in the background. The developer gives it a long timeout instead: in a run without an owner the session ends with the turn, and a background gate dies with it (the same autonomous run: a background role ended the session at
ANALYSIS). - Mutation checks: mutants on the diff are review evidence, not a gate on every line (generalized from a project's decision after one task failed acceptance four times, each on a new gap in one CI step, and one sweep ran ~109 mutants × 40 s). Following Google's practice — mutants on changed lines shown in code review as hints (Petrović, Ivanković, "State of Mutation Testing at Google", ICSE-SEIP 2018; Petrović, Ivanković, Fraser, Just, "Practical Mutation Testing at Scale: A view from Google", IEEE TSE 2021, arXiv 2102.11378) — the default for every project is now: (1) acceptance FAILs only on a survivor that breaks a stated Acceptance/DoD guarantee, named by the mutant; other survivors are follow-ups (the flow files them in the sprint's follow-up task); (2) a
sampleround adds 15–30 mutants aimed at the guarantees and the changed lines (was 3–6), asweep— and the test-writer'schecker: truesweep, now in its second spawn — covers the task's changed lines, not the whole seam; a whole-seam sweep only for a task markedcritical: true(new task-template field: sync, recovery, data deletion); (3) the catalogdocs/tasks/<ID>.mutants/is re-run first every round and only added to; (4) after two sweep rounds withFAILthe next verdict isNEEDS_OWNER, not a third sweep — the verify gate now refuses anything else (counted since the lastPASSorNEEDS_OWNER; tests intests/test-verify-gate.sh), and the router says so (tests/test-tw-next.sh); (5) machine mutation tools (mutmut, PIT, Stryker) belong in an optional nightly CI job, off the acceptance path. test-writer, task-validator and code-reviewer role files, the task and validation templates,PRINCIPLES.mdP-005,AGENTS.md, the flow skill,gates.md(new section Mutation checks),lifecycle.md,roles.md,sessions.md,tools.md,plugin.mdand the README describe it. - Throwaway copies use
mktemp -d, never a hardcoded/tmp. The test-writer's and the validator's copy snippets say why: when an area's tests run in containers started by the host's Docker (runner: docker, or a project wrapper), the copy must be on a path the host can mount, andmktemp -dhonours$TMPDIR; a/tmpinside the agent's container is invisible to the host (an autonomous run lost ~6 minutes on a test container that got an empty directory frommktemp -d /tmp/...). - The gate cache keys on the area's files, not the whole tree. In one project a task's gate ran 5 times, 3 of them after docs-only changes that invalidated the cache (10–14 minutes each). The key is now the working tree restricted to the area's scope:
source_dirs,test_dirs,workdir, the dependency manifests and lockfiles at the root and the workdir,.teamwright/config.yml, the gate scripts and every repository file the area's commands name; a commit outside it answersPASS (cached: <area> files unchanged since the run at commit <sha>, …). An area withoutsource_dirs/test_dirs, an area running the top-level gate, and--topkeep the whole-tree key. Records gainkey,key_afterandscope. - test-writer rules from the field. (a) Every failure branch gets a mandatory "swallowed error / skipped failure path" mutant in the catalog — in one project a swallowed
?was caught only by the validator, after deploy; (b) wrong implementations are minimal sketches of the one wrong behaviour, never a full or reference implementation — the same project's test-writer wrote 23 of them for one task, work the developer then redid; (c) before handing over it runs the area'slint_commandand formatter on the files it wrote. Its copy snippets keep the copy when tw-mutants answers 75. - test-writer: one toolchain start for all mutants. The bite check, the
checker: truesweep and the failure-branch mutations run as one loop — apply a mutant, run only the task's tests, revert — in onescripts/tw-run <area>call, never the full suite per mutant. On a docker area each start loads the image and its models; per-mutant starts were most of a 22-minute first spawn.
Fixed
scripts/hooks/_job.shis committed executable. It shipped as 644 whileinstall-hooks.shmakesscripts/hooks/*.shexecutable, so every project's working tree showed a mode change that agents could commit. The kit now commits it as 755, so the installer (which copies the kit's mode) andinstall-hooks.shagree;tests/test-installer.shchecks that no shipped script is committed without its executable bit.
Full changelog: CHANGELOG.md