sidecrew

Changelog

Unreleased — Phase 14d closed (STOP), ADR-0089 closed, and a new way of working

Phase 14d, session 1

v0.2.0 — the reach: a task can name a declaration, and a big file stops being refused (2026-09-24)

What changes for a user: a behaviour-preserving task can now name one declaration (symbols: [{ file, name }]), so a method inside a 4,000-line service is a 60-line task. Before, every file over ~22,700 characters was refused outright, and on a real Nest codebase those files were 48.1 % of the code. Measured on that codebase: the share a worker can be asked to change went 0.519 → 0.911, and one-declaration tasks survived 15/41 [0.221, 0.531] in big files and 10/42 [0.121, 0.395] in small ones, with no survivor making a file worse. The intervals overlap, and the big-file arm’s lower bound is under the 0.30 “usable” line, so it is a direction; the details and every caveat are below.

Also in this release: sidecrew now brings its own pinned Stryker (sidecrew tools install) and never installs anything into a project, and every run and fix checks that the project is byte-for-byte intact afterwards (ADR-0088). The entries below are the full record.

ADR-0082 D measured: Y = 2/20 [0.012, 0.317] → B is not affordable as it stands. Test-first stays accepted in principle; next comes a measurement of what raises Y. Where it breaks: of 14 tasks, 2 ever passed on the original code, and both killed a mutant in the region. The worker’s problem is writing a test that compiles and runs against a NestJS service, not writing a sensitive one. Found on the way: ADR-0089, a type-only assertion survives workload #1’s gate by killing the empty-body mutant (proposed, owner’s call), and one of D’s two survivors scored 0.015. Fixed: the verifier’s “no mutant killed” message said “passes against every changed version” when none had run. It now names compile errors, and the fixture test that hid this tells the truth.

Phase 14c′ measured: one declaration per task → PROCEED to 14d. S′_big 15/41 = 0.366 [0.221, 0.531], S′_small 10/42 = 0.238 [0.121, 0.395], and B’s safety check clean (0 combined regressions, 0 of 27 files worse). On the same 27 files where 14c’s file-sized tasks survived 1/27, one-declaration tasks survived 25/83. The intervals overlap and the big arm’s lower bound is under 0.30, so usable is a direction. The owner’s working checkout was identical before and after. prompts/phase-14c-prime.md §5.

ADR-0086 §6 option B is built and is the default: a symbol task is judged by its declaration. compile_ok on a symbol task now means zero errors inside the named declarations and no more errors outside them in the file than before. The tests must still pass and confinement is unchanged. symbol_gate: "file" reproduces 14c’s rule. The verdict records target_scope and outside_target, and the schema enforces them. Its control through the real gate: a fix that clears its own declaration by breaking a neighbour is refused. Also: 14c′ splits per declaration, not per error, because per-error tasks cannot pass a file-scoped gate (ADR-0087 amendment).

ADR-0088 is built: sidecrew brings its own Stryker, and a project is byte-for-byte intact after every job. sidecrew tools install puts a pinned Stryker 8.7.1 (~62 MB) into ~/.sidecrew/tools, never into a project. Each mutation run uses a throwaway copy-on-write copy of it, linked to the project’s own typescript and jest or vitest. run and fix fingerprint the project before and after (git status scoped to the project, package.json, every lockfile format, node_modules) and fail loudly on any difference. The first spike caught npm silently installing typescript 7.0.2 and vitest 4.1.11 into the cache, so the cache now refuses to hold any project-owned tool. Measured: the vitest verifier suite passes 10/10 against a fixture with no Stryker at all; on a clone of project-a, 7 mutants were killed and the working checkout was identical before and after.

ADR-0082 accepted in principle, as test-first development (owner, 23 Sep). Tests always come first, are frozen, and are written by a different invocation from the change. For a behaviour change they must go red → green; for a behaviour-preserving one they are pinned green → green. The order D → B → C stands; D is blocked on Stryker.

Owner decisions, 23 Sep: 14c′ is ADR-0087 A (one error per task), and v0.2.0 is held. The parent folder name of the client checkouts is redacted from the tree: two prose mentions and a redaction regex that now matches any parent folders.

Phase 14c measured: Reach 0.519 → 0.911 on project-a, and the frozen rule says INSERT 14c′. S_big 0/9 [0.000, 0.336] against S_small 1/18 [0.001, 0.273]. The intervals overlap almost entirely, so it is a direction, not a result. The funnel says why: the worker clears the same share of a task’s errors inside a big file as in a small one (0.304 vs 0.307), so the file’s size stopped mattering, and the change’s size is what binds. The splice never broke a file in 53 verdicts. The task set was declared, hashed and committed before the first token. Its two amendments came from pool sizes, never from a verdict. ADR-0087; experiments/reach/README.md §4.

Phase 14c’s build: a task may name a declaration, and the worker returns only that (ADR-0086, building ADR-0075 option C). A PlannedChange carrying symbols: [{ file, name }] is symbol-scoped. The worker is shown the declaration, the file’s imports and its class header, and answers with --- SYMBOL: file#name ---. sidecrew splices the answer back by the compiler’s own range before the gate, so every stage after generate still reads whole files. files_too_large_to_rewrite no longer applies to a symbol task. Its size clause is the declaration’s (symbols_too_large_to_rewrite), which is what brings a method inside a 4,000-line service into reach.

ADR-0084’s open caveat is closed — not by answering it, but by showing it does not bear on the decision. 50 runs of project-b’s unmodified suite: 9111/9118 passing every time, spread 0, zero non-deterministic tests.

ADR-0077 option B is built: a type error a change pushes into a test file is recorded, not fatal. Owner’s decision, 22 Sep — accepted with the scope that was measured (any test file, by the gate’s own isTestArtefact) rather than B’s narrower “a plan declares them”, which was never the rule behind the 14/15.

ADR-0084’s mitigation is decided and built: a regression must reproduce to count. Owner’s decision, 22 Sep — accepted behind a flag defaulting on, and sequenced before Phase 14c, because 14c ends in an overnight measurement and every later number is taken with this instrument. ADR-0077 option B is accepted to land after it, for the same reason in reverse: B leans the gate harder on the suite.

ADR-0085 — a sandbox teardown race throws away the verdict it was cleaning up after. Two 25-run measurements, on two different projects, both died at run 9. rm(dir, { recursive: true, force: true }) throws ENOTEMPTY when something is still writing into the tree as it is unlinked — a jest worker outliving its run — and force suppresses ENOENT and nothing else.

project-b’s suite was measured too, and it settles nothing — which is the point of saying so. 9111/9118 passing in all 15 runs, spread 0, zero non-deterministic tests. But 0/15 is [0.000, 0.218] against project-a’s 3/50 [0.013, 0.165], and if project-b had project-a’s rate exactly, seeing zero bad runs in 15 happens 40 % of the time — the modal outcome. ADR-0084’s caveat, that a 6 % floor is a property of this project, is still open. It needs the same ~50 runs, about three and a half hours.

ADR-0084 — D is the suite’s flake rate, not the gate’s error rate. Measured overnight: 50 runs of project-a’s unmodified suite, fresh clone each, no change applied, in a window crossing no calendar boundary of either kind. 11 tests in 6 suites each failed in exactly one of the 50 runs, clustered into three runs (4, 6 and 1 tests short). That is the harmful direction — a test that passed becoming one that fails is a regression, and tests_ok is false.

ADR-0083 is corrected by it. That ADR said the floor in the candidate-failing direction was 0 across 25 runs. True of those 25 and false in general — 25 runs simply caught none of these. The date-boundary finding stands; the reassurance did not, and both paragraphs now say so.

ADR-0083 — the stale-baseline guard watched local midnight, and the tests move at UTC midnight. Found by measurement, overnight, on an idle machine. 25 runs of project-a’s unmodified suite, fresh clone each, no change applied: three tests in three suites moved at 00:00 UTC — runs 1–8 gave 6317 passing, run 9 straddled the boundary and gave 6319, runs 10–25 gave 6320 and never moved again. Strictly monotonic, machine normal and swap flat throughout. Local time was 01:55 → 02:02 CEST, so the local day never changed.

ADR-0082 (proposed) — sidecrew writes the oracle it is judged by, before the change exists. The owner’s proposal, 21 Sep. Every step of it but two is already VISION.md’s loop; the delta is that the oracle stops being borrowed and starts being manufactured — the worker writes tests covering the region about to change, gated by workload #1, before the change exists. Nothing is built and nothing is agreed; option D (measure first) is the recommendation.

ADR-0077 option D is measured: 14 of 15. The counterfactual re-gates probe 1’s 15 clean-target tasks with test-file type errors demoted to observations. All 15 reached the suite and 14 passed it — 14/15, 95 % [0.681, 0.998], against S₁₄ = 2/30, [0.008, 0.221]. The intervals do not overlap. changeSurvives and verifyChange are untouched; the harness applies its own compile rule and the production tests_ok verbatim, and the result is labelled a counterfactual, not a rate.

app.e2e-spec.ts was not a test file to the gate — ADR-0081. Found by ADR-0077’s counterfactual, which demotes type errors in test files and asks the gate’s own predicate which files those are. The first task came back with nothing demoted; reading the recorded verdicts, 14 of the 15 sinking files are *.e2e-spec.ts and isTestArtefact called none of them a test.

v0.1.2 — mcpName, and the MCP Registry entry (2026-09-21)

0.1.1 proved the pipeline and could not claim the registry entry. It published to npm with a SLSA provenance attestation over Trusted Publishing, and the registry’s login github-oidc succeeded — all three previously unproven flows, confirmed. publish then failed twice: once on the registry’s own database being down (dial tcp …:5432 connection refused, not ours, fixed by gh run rerun --failed on the one job), and once for real.

v0.1.1 — the release pipeline, proved against the systems it publishes to (2026-09-21)

The v0.1.0 release runs, and the three defects they found. The tag was moved onto the tarball-smoke fix (it had been cut one commit early, so a re-run would have replayed the same failure), and the run reached registry for the first time. gate ✓, npm ✓, registry ✗ — and only two of those three marks were true. ADR-0080. No product code changed.

CI is green again. ci.yml had failed on main on every push since 18 Sep and nobody looked: the local suite was green and release.yml had never run. It blocked the release outright, because the release gate runs npm test. Two independent causes, both found by tagging v0.1.0-rc.2, which is what the release candidate was for. No product code changed.

Phase 14b — the editing ceiling. Measurement only: no product code, no version. S₁₄ = 2/30 = 0.067, 95 % [0.008, 0.221], best of two probes on §2.2’s same 30 declared null_guard tasks. Against the rule frozen in PHASES.md before either probe ran, that is S₁₄ < 0.10 → PROCEED to 14c, and write the ceiling down as a product fact — a branch the rule declared in advance to be a complete result.

The ceiling is real and it is not the model’s

Reported the way the rule requires

v0.1.0 — the first public release (2026-09-20)

Phase 14. The three hardening items it owed, the publish mechanics, and the numbers re-stated the only way a frozen rule allows.

deriveLineRange asks the compiler — ADR-0076

doctor learns the pre-flight questions

Five rows that ask “will it do the thing”, not “is it installed”. Each was a run that failed, or worse, one that quietly succeeded: the tsconfig’s include covering the directory candidates go in (ADR-0037, the only blocker that ever failed open), the ts-jest shim on this layout (ADR-0038), the heap tsc needs read out of the project’s own scripts (ADR-0032), which engine finds line ranges (ADR-0076), and a fourth found this week — jest ok on a project whose suite collects zero tests. The jest row distinguishes its three silences, because calling all of them “no tests” is the confidently wrong answer it exists to prevent.

ADR-0042, option 1 — a candidate wrong on its own terms

Published

Every rate is now an interval, and D travels with it

D = 2/19 = 0.105 [0.013, 0.331] triggered the frozen rule’s UNACCEPTABLE clause: no survival rate may be published as a point estimate until the cause is diagnosed. The README and the site therefore publish k/n with its exact Clopper–Pearson interval everywhere, with D beside every #2a rate and an explicit note that D was measured on #2a’s gate and says nothing about #1’s. The withdrawal is stated in the rule’s own words rather than softened: the gate admits nothing that fails stands; the gate refuses only things that fail is measured false.

One thing this cost, because it was silent

doctor reaching into plan.ts closed an import cycle — concurrency → serve → doctor → plan → verifier/ts → concurrency. Under ESM that does not throw. It made DEFAULT_STRYKER_CONCURRENCY undefined, and the only thing that noticed was one assertion in test/concurrency.test.ts written for an entirely different reason. testDirFor and jestConfigEntry moved down to verifier/shared.ts, which imports nothing of ours.

710 → 769 tests passing, 1 skipped.

Unreleased — a standing handoff, and a rule that it stays current (2026-09-20)

Unreleased — every phase now ends with an exit check whose fork is named in advance (2026-09-20)

Unreleased — the road to the 90 % bar is phased, with version cuts and overnight runs marked (2026-09-20)

Unreleased — the gate judges intent, and the vision gets the owner’s own words (2026-09-20)

Unreleased — three ADRs decided and implemented, and the O8 diagnostic finds a missing field (2026-09-20)

Unreleased — sidecrew is a 24 GB+ tool, and the docs tell both halves of the ledger (2026-09-20)

Unreleased — §4.4’s curve: planning is a big fixed cost, not an expensive per-task one (2026-09-20)

Unreleased — §2.2 measured: the correction round does not pay, and the reason is the worker (2026-09-20)

Unreleased — the gate’s own error rate, measured (2026-09-19)

Unreleased — §2.1 measured, and it fails its own frozen rule (2026-09-19)

Unreleased — the verdict records what invalidates it (2026-09-19)

Unreleased — Phase 11b completes on both projects (2026-09-19)

Unreleased — Phase 11b measures sidecrew against not using it (2026-09-18)

Unreleased — Phase 13 builds the api tier (2026-09-18)

Unreleased — Phase 12 builds the management side (2026-09-18)

Unreleased — the roadmap is reordered around the science, and Phase 12 is written (2026-09-18)

Unreleased — Phase 11: workload #2a measured against a rule frozen first (2026-09-18)

Unreleased — workload #2a: local models making behaviour-preserving changes (2026-09-16)

Unreleased — the first React measurement, and the gate’s hole measured on real output (2026-09-15)

The sixth trial confirmed there is no regression on project-a (module 1 reproduced attempt-for-attempt) and produced project-b at 4/10 = 0.40, median survivor mutation score 0.871 — the first survival rate anyone has measured on a React project.

ADR-0037’s hole is now measured on real worker output, not a planted control. isSubsetDeep:happy_path:0 — a candidate the worker actually wrote — fails tsc with TS2345 on a genuine generic-inference trap, and passes the project’s own jest 7/7. Under the old gate it would have been compile ok · pass ok, gone to mutation, and killed >= 1 was near-certain: the probe’s far weaker test already killed 13 of that function’s 16 mutants. So the broken gate would have printed 5/10 where the fixed gate prints 4/10 — one task in ten, on the first real plan. Withholding the rate last time was load-bearing rather than cautious.

Unreleased — the gate could pass a candidate it never checked (2026-09-15)

The fifth real-world trial produced the number the project has been working towards — 3/8 on an project-a with nothing changed in it, median mutation score 1.00, and a second module independently at 3/8 for 6/16 across two modules and two export styles — and, on the other project, found the worst defect sidecrew has had.

Unreleased — the fifth trial: a number on an unmodified project, and a gate that fails open (2026-09-15)

Two trials, one per stack, both against projects with nothing changed in them. Reports: experiments/real-world/results/project-a-2026-09-15-unmodified.md and experiments/real-world/results/project-b-2026-09-15-unmodified.md.

Unreleased — the sandbox stops deleting the project’s source (2026-09-15)

The project-a re-run confirmed, by observation on the project that motivated them, every fix ADR-0033 and ADR-0034 made — deriveLineRange from 1.5 % to 99.2 % of that codebase’s declarations, the import hint from 2/20 to 16/16, src/modules/reports/ intact, the TS2883 clone firing unprompted — and still reported 0/8, because of one line ADR-0033 left as a footnote. With that one line fixed the same run gives 3/8 at a median mutation score of 1.00, sidecrew’s own verdicts, no candidate edited, against the baseline’s hand-derived 4/10 at 0.774. The survival rate is flat; the survivors are not (ADR-0035).

Unreleased — the sandbox repairs itself when TypeScript says it must (2026-09-15)

Unreleased — what the third real project broke (2026-09-15)

The project-a trial produced the first candidates sidecrew has ever had on somebody else’s code, and scored 0/10, every one “did not compile” — with not one of the twenty failing because of anything in the candidate. Six blockers, four of them sidecrew deciding what a project looks like (ADR-0033). With them neutralised and only the import line rewritten, the same twenty give 20/20 compile and 0.40 survival at a median mutation score of 0.774.

Unreleased — clearing the way for the third trial (2026-09-15)

Unreleased — workload #2 written down as a proposal (2026-09-15)

Unreleased — the second real project, and the first one’s diagnosis was wrong (2026-09-15)

Unreleased — what the first real project broke (2026-09-15)

Unreleased — Phase 9, runners (2026-09-15)

Unreleased — Phase 7, hardening (2026-09-14)

Unreleased — after the go/no-go, before Phase 7 (2026-09-14)

Unreleased — Phase 6: the go/no-go (2026-09-14)

Unreleased — Phase 6 prerequisites (2026-09-14)

Unreleased — Phase 5 (2026-09-14)

Unreleased — Phase 4 (2026-09-14)

Unreleased — Phase 3 (2026-09-12)

Unreleased — Phase 2 (2026-09-12)

Unreleased — Phase 1 (2026-09-11)

0.0.1 — 2026-09-10