staysfixed 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,275 @@
1
+ # Stays Fixed v2 — the design
2
+
3
+ *Written 2026-08-29, from Asad's own statement of what the tool is for.*
4
+
5
+ > **What this page is, and what it is not.** This is the design and the reasoning behind it,
6
+ > kept as it was written so the arguments stay readable — including the ones that turned out
7
+ > to be wrong. **It is not a description of the current state.** For that, read
8
+ > [the README](../README.md), which says what works, what does not, and what never will; or
9
+ > run `staysfixed doctor`, which answers the same question about your actual machine.
10
+ >
11
+ > **Where it stands, as of 2026-08-30.** Phases 1 to 6 are built and in use: the seven
12
+ > observation channels flattened to one address space, the run-twice wobble floor with no
13
+ > tolerance setting anywhere, normalisation rules that each declare what they hide,
14
+ > clustering, distance-from-the-edit ranking, revert-and-re-run causal proof, the store, the
15
+ > reference cut by shipping, sealed classes, sealed intents, the waiver budget, the coverage
16
+ > ledger, the self-check corpus, and adapters for command-line tools, libraries, HTTP
17
+ > servers, source reading, the web, Electron, Android, iOS and native Windows over ssh. The
18
+ > MCP surface is seven tools and it is what `staysfixed mcp` serves.
19
+ >
20
+ > **What is written and not wired:** harvesting a project's own test suite as journeys, and
21
+ > replaying a recorded session. The code is in `src/v2/journeys/` with tests around it and
22
+ > nothing on the check path calls it. Asking for either is refused by name. Journeys today
23
+ > come from what each adapter reads out of your source, plus a journeys file you point it at.
24
+ >
25
+ > **What is permanent and will not change:** nothing irreversible is ever run — it is watched
26
+ > at the call and refused at the effect, and the refusal is reported as missing coverage;
27
+ > real phones cannot be paired; a race that already existed will not show; native Windows
28
+ > shows one desktop, so two builds cannot run there at once; pictures are tied to the machine
29
+ > that took them; and how long something took is recorded and never compared. The README
30
+ > carries the full list in plain words.
31
+
32
+ ## What it is
33
+
34
+ Stays Fixed stops being a photo album and becomes a difference machine. You point it at your product, it runs the last build you were happy with and the build you just changed through exactly the same steps on the same machine within the same minute, and it reports only the things that behave differently — what the screen says a control now does, what calls go out, what files get written, what errors appear, what the program prints, and only last of all what it looks like. Nobody approves pictures any more: the build you said "ship" to IS the definition of working, so the only thing you ever have to do is what you already do. The agent runs it through MCP, gets a short ranked list of real differences, proves which ones its own edit caused by undoing that edit and re-running, and fixes them. You hear about it only when a difference lands in a class no agent is allowed to wave through — money, sign-in, lost data, a crash, or a bug you already reported once.
35
+
36
+ ## How it proves nothing changed
37
+
38
+ Three ideas, in order of how much weight they carry.
39
+
40
+ FIRST — measure the wobble instead of guessing a tolerance. Every product disagrees with itself a little between runs: a timestamp, an animation frame, an id. So the tool runs the NEW build TWICE. Anything that differs between two runs of the same build was not caused by the change; it is the product's own wobble, and it is subtracted arithmetically. This deletes every tolerance setting in the current tool (`tolerance` in src/core/config.js, gone). It also catches a bug class no screenshot tool has ever caught: a path that was steady in the old build and wobbles in the new one means the change made something non-deterministic.
41
+
42
+ SECOND — cheap suspicion, expensive proof. The old build's observations are stored, content-addressed against the build artifact, from the last time it ran. A normal check compares NEW-twice against that stored record: fast, no rebuild. Every path that shows a difference then gets the OLD build BOOTED LIVE, in the same minute, on the same machine, and re-walked — and only differences that survive that live re-run are reported. That is what makes paired running affordable enough to leave switched on for iOS and Android, where a full paired run costs two xcodebuild passes. A `--paired` mode runs old live from the start, for pre-release and for the first run on a product with no stored record.
43
+
44
+ THIRD — sequential, not simultaneous. The brief's "same second" is the one part of the idea I reject. Two builds on one machine fight over ports, single-instance locks, user data dirs, databases and relay slots — his own two-hosts-fighting-over-one-relay-slot incident on 2026-08-28 is exactly this failure. The value was never in the same second; it is in the same machine, same fonts, same OS, same data, minutes apart. Runs are sequential with a full state reset between them, interleaved scenario by scenario so drift cannot accumulate.
45
+
46
+ WHAT IS COMPARED: seven channels, all flattened to one `path → value` shape so one comparison engine serves every platform. (1) The meaning tree — the accessibility tree, role + name + state, NOT the DOM, because the DOM changes when nothing did. (2) Effects out — network calls, files written, processes spawned, storage writes. (3) Complaints — console, crashes, stderr, exit codes. (4) Results — stdout, HTTP bodies, exported API surface. (5) The contract read statically out of the source: routes, exports, IPC channels. I verified this on Terminal Deck — 462 `ipcMain` registrations in src/, a complete door list obtained free and exactly, none of which any screenshot has ever seen. (6) Coarse counters and timing. (7) Pixels, last, and only as evidence for a finding another channel already made.
47
+
48
+ WHERE THE STEPS COME FROM, ranked, because this is the real workload answer: read the code (free, exact) → run the project's OWN existing test suite under instrumentation (Terminal Deck has 624 test files sitting there unused for this) → recorded real sessions → the agent exploring one named gap and freezing it into a replayable file → never a person.
49
+
50
+ NOISE CONTROL, four layers before anything reaches the agent: normalise volatile shapes by rules kept in git, so a version bump in a footer reports zero differences and not five hundred; cluster by signature; rank by DISTANCE FROM THE CHANGED CODE — a difference far from your edit sorts to the TOP, because that is the definition of a side effect; then let the agent prove causation by reverting the suspect hunk and re-running. That last step is the one the five designs only approximated with heuristics, and it is cheap, and it is a proof rather than a guess.
51
+
52
+ ## What survives from v0.3.1
53
+
54
+ - The freeze layer (~2,068 lines across src/freeze/) — frozen clock, killed motion, seeded randomness, pinned fonts, blocked network. Paired running does NOT make this redundant: it removes differences between the two builds, and this removes the product's own internal nondeterminism, which is what keeps the measured wobble small enough to be useful.
55
+ - `settle` (src/freeze/settle.js) — capture until two frames agree. It generalises past pixels to any observation, and it works on every platform because it only ever needs a picture. Best single algorithm in the repo.
56
+ - The network record/replay interceptor (src/freeze/network.js), promoted from a determinism trick to the SAFETY BOUNDARY. It is the answer to "what about a payment": the old build replays recorded traffic and never reaches the real world.
57
+ - Guards (src/guard/) and the plain-English naming rule — the only behavioural testing in the repo, and the encoded memory of bugs he already reported once. They are the third net for the case both engines are blind to: the old build was already wrong.
58
+ - Markers and trace (src/marker/) — promoted to load-bearing. A marker now DEFINES "old", and bisect runs over stored build artifacts, so the bisect actually works instead of needing every commit rebuilt.
59
+ - The flake register (src/core/history.js) — moved to the front door: a generated journey that does not reproduce twice on the old build is rejected at birth rather than admitted and condemned later.
60
+ - The dependency-free CDP driver (src/drive/) — kept for ELECTRON specifically, because it attaches to an already-running main process over the debug port, which is exactly Terminal Deck's shape and which Playwright handles badly.
61
+ - The MCP server (src/mcp/) — the delivery mechanism the whole requirement rests on.
62
+ - `doctor` (src/cli/doctor.js) — with seven platforms and a dozen external toolchains it matters more, not less.
63
+ - The writing voice throughout. Plain-English output is not decoration here: the agent reads it, and so does he.
64
+
65
+ ## What is thrown away
66
+
67
+ - Pixel comparison as the primary signal, and `pixelmatch` as the centre of gravity. He said it plainly: not UI/UX, functionality. Pixels drop to channel seven — used as evidence, never as the accusation.
68
+ - Every tolerance knob (`tolerance` in src/core/config.js). Replaced by a measured wobble floor. Tolerance settings are how tools like this die: too loose to catch the real thing, too tight to stay switched on.
69
+ - `approved/` pictures as the source of truth, and `src/cli/approve.js` with them. The reference becomes a COMMIT, cut automatically when he ships. This is the single biggest cut to his workload in the whole design.
70
+ - `src/watch/panel.js` — 3,218 lines, verified the largest file in the repository, a live watch panel built for a human who is explicitly not going to watch. Also `src/watch/window.js` (1,242) and `src/report/html.js` (579).
71
+ - `concurrency: 1` as a global posture, and the "two runtime dependencies, no build step, plain JS" constraint — he lifted it in writing.
72
+ - Triage's "second-opinion" gate that asks the host for a model via MCP sampling. Sampling is deprecated in the current spec and Claude Code does not implement it; do not build a safety gate on a capability that is not there.
73
+ - Differential's proposal to drive Windows through the NovaWindows Appium driver — an unverifiable third-party dependency cited from a blog post. A self-contained FlaUI .NET probe instead.
74
+ - Platforms' clean sweep of the hand-written CDP client in favour of Playwright everywhere. Playwright wins for the web; it loses for attaching to a live Electron main process. Keep both — the dependency budget that made this an either/or no longer exists.
75
+
76
+ ## Where the approval line sits
77
+
78
+ The word "approve" was hiding four different decisions. Split them and the line becomes obvious.
79
+
80
+ 1. WHAT COUNTS AS WORKING — Asad, and only Asad. But he never opens the tool to say it. The reference is cut by an act he already performs: saying ship. One line added to the `ship-everywhere` skill records that build as the new reference. He approves in bulk, retrospectively, by shipping.
81
+
82
+ 2. IS THIS DIFFERENCE REAL, OR IS IT NOISE — the machine, arithmetically, from running the new build twice. No judgement involved, nobody's opinion.
83
+
84
+ 3. IS THIS DIFFERENCE CAUSED BY WHAT I MEANT TO CHANGE — the agent. This is a CAUSAL claim, which is checkable, not an aesthetic one, which is not. The agent proves it by reverting the suspect hunk and re-running; if the difference survives the revert, the agent was wrong and it escalates.
85
+
86
+ 4. IS AN UNINTENDED DIFFERENCE ACCEPTABLE ANYWAY — Asad. This is the only thing that reaches him, and it should be a handful of items a month, arriving inside the closing summary he already reads, never as a report or a dashboard.
87
+
88
+ The anti-rubber-stamp mechanism, taken from the triage design because it is the strongest safeguard any of the five proposed: the agent CANNOT write a reference. It can only write a waiver, and only through four machine-checked gates. (a) Sealed classes are unwaivable — money, sign-in, data loss, a crash, or any difference touching a named guard go straight to him. (b) The waiver must be consistent with an INTENT the agent sealed BEFORE the check ran, so it has to say what it meant to change before it sees what broke; that makes the claim falsifiable rather than a rationalisation. (c) A budget of five waivers per change — past that it is not a side effect, it is a rewrite, and a person looks. (d) Every waiver is fingerprinted to the exact difference, expires when the reference moves, and is counted out loud in the summary. Waivers are provisional; they become the reference only when he ships.
89
+
90
+ Where I rejected the alternative: the differential design would let the agent declare a difference intended with a written reason and an expiry. That is unbounded — an agent under pressure to finish will declare the real regression intended, and the reason it writes will read perfectly plausible. Sealing the intent before the run, and sealing five classes off entirely, is what makes the same freedom safe.
91
+
92
+ ## Platforms, in build order
93
+
94
+ ### 1. CLI tools and libraries
95
+
96
+ - **Driven by:** Plain child-process I/O: stdout, stderr, exit code, files touched, processes spawned, plus the exported API surface read from the source.
97
+ - **Effort:** About 3 days
98
+ - **In the way:** None. This is the honest place to start, because the whole design stands or falls on whether wobble-subtraction really makes diffs quiet, and that can be found out here in days rather than months.
99
+
100
+ ### 2. Server / API
101
+
102
+ - **Driven by:** Two ports, two restored copies of a database snapshot, request-and-response diffing, plus a post-migration schema diff.
103
+ - **Effort:** About 4 days
104
+ - **In the way:** Needs a restorable database snapshot he does not currently keep for every product. Migrations that destroy data are REFUSED rather than run twice, and the refusal is reported as missing coverage, never silently passed.
105
+
106
+ ### 3. Web
107
+
108
+ - **Driven by:** Playwright (already installed on his Mac, 1.62.1) and its first-class ARIA snapshots for the meaning tree.
109
+ - **Effort:** About 4 days
110
+ - **In the way:** Only Chromium is downloaded locally; Firefox and WebKit need fetching. Nothing he has to do.
111
+
112
+ ### 4. Electron desktop (Mac and Windows builds)
113
+
114
+ - **Driven by:** The existing CDP driver attaching to the running main process, plus direct probing of the IPC channel list read from source — 462 of them in Terminal Deck.
115
+ - **Effort:** About 4 days
116
+ - **In the way:** Two instances of the same app fight over the single-instance lock, the user data dir and the relay slot. Each run needs its own `--user-data-dir` and its own relay identity, or the tool recreates the exact bug diagnosed on his box on 2026-08-28.
117
+
118
+ ### 5. Android APK
119
+
120
+ - **Driven by:** Appium 3 with the UiAutomator2 driver on an emulator; `emulator -read-only` for the second instance of one AVD.
121
+ - **Effort:** About 2 weeks
122
+ - **In the way:** His Mac has no Java runtime, `adb` and `emulator` are not on PATH, and Appium is not installed — all fixable without him. The real unknown, which none of the five verified: whether two emulator snapshots restore byte-identically. If they do not, Android falls back to stored-record comparison only, and must say so.
123
+
124
+ ### 6. iOS simulator
125
+
126
+ - **Driven by:** `xcrun simctl` for the machinery (Xcode 27 and runtimes 26.4 / 26.5 / 27.0 are present) plus WebDriverAgent's `/source?format=json` for the meaning tree, on a cloned device pair with a warm device pool.
127
+ - **Effort:** About 2 weeks
128
+ - **In the way:** Appium and WebDriverAgent historically lag a new Xcode by months, and Xcode 27 is new. `xcrun simctl` has hung on this Mac before — call CoreSimulator's binary directly. Two `xcodebuild` runs make paired mode pre-release-only, and it really wants a second machine. Real devices are out of reach entirely.
129
+
130
+ ### 7. Windows native (only if he ever ships one)
131
+
132
+ - **Driven by:** A small self-contained .NET probe built on FlaUI, shipped as one executable, speaking JSON over stdin/stdout, driven over SSH to his office box.
133
+ - **Effort:** About 1.5 weeks
134
+ - **In the way:** WinAppDriver has been dead since 2020 and Appium's Windows driver still wraps it. UI Automation reads the foreground desktop, so Windows cannot run two builds at once even in principle — sequential only, and the same-minute guarantee weakens. Mostly unnecessary: his Windows product is Electron and drives over CDP from the Mac. Build it last, or never. Verified this session: `ssh imza-pc` and `ssh imza-pc-linux` are the same machine (WSL Ubuntu on DESKTOP-DDGMNCV) with working `powershell.exe` interop onto a real logged-in Windows 11 desktop — so a usable runner exists today, no VM, no CI account.
135
+
136
+ ## The build, in order
137
+
138
+ ### Phase 1 — The engine, on CLI tools and libraries *(About a week)*
139
+
140
+ The whole difference machine end to end on the easiest surface: seven observation channels flattened to one shape, a JSONL store keyed by name, the run-twice wobble measurement, normalisation rules, clustering, distance-from-the-edit ranking, revert-and-re-run causal proof, and the MCP tools. Ships with a SELF-CHECK CORPUS — a set of deliberately broken builds it must detect — because a tool that reports "nothing changed" is indistinguishable from a broken tool, and none of the five designs noticed that. Useful alone: it guards its own repo and any CLI or library he ships.
141
+
142
+ ### Phase 2 — Web and Electron *(About a week)*
143
+
144
+ Playwright for browsers, the kept CDP driver for Electron. This is where he first sees it working on Terminal Deck's desktop app and on his live sites. Includes the per-run isolation recipe — separate user data dir, separate relay identity — that stops the tool recreating the two-hosts bug.
145
+
146
+ ### Phase 3 — The reference-at-ship hook and the waiver system *(About 3 days)*
147
+
148
+ The workload-down phase, and the first one that makes his life measurably easier rather than just his tooling better. `ship-everywhere` cuts the reference automatically. Sealed classes, sealed intent, waiver budget, expiry. Escalations land in the closing summary he already reads. After this phase he never approves a picture again.
149
+
150
+ ### Phase 4 — Read the code, use the suite *(About 4 days)*
151
+
152
+ The static contract channel and the coverage ledger — all 462 Terminal Deck IPC channels enumerated, with a visible count of how many have never been walked — plus running the project's own 624 test files as instrumented journeys. Turns "how deep is this really" from a claim into a number, and hands the agent its work queue.
153
+
154
+ ### Phase 5 — Android *(About 2 weeks)*
155
+
156
+ APK coverage on an emulator: the first real answer to "every platform". Starts by installing Java, adb and Appium on his Mac, then proves or disproves byte-identical snapshot restore. If that fails, Android lands as stored-record comparison — still useful, and it says so out loud.
157
+
158
+ ### Phase 6 — iOS simulator *(About 2 weeks)*
159
+
160
+ The iPhone app covered, pre-release only, on a warm cloned device pair. Wants to run on a second machine over SSH rather than on his laptop while he works.
161
+
162
+ ### Phase 7 — Windows native — only on demand *(About 1.5 weeks)*
163
+
164
+ The FlaUI probe over SSH to his office box. Built only if he ever ships a non-Electron Windows product. Listed here so it stays a visible decision rather than a silent gap.
165
+
166
+ ## Honest limits
167
+
168
+ WHERE "EVERY PLATFORM, NO COMPROMISE" DOES NOT HOLD. Web, Electron, CLI, libraries and servers get the full paired treatment. Android and the iOS SIMULATOR get it with real work and one unproven assumption each. Real iPhones and real Android handsets do not — no paired run is possible on a device he is holding. Windows native GUI cannot run two builds at once even in principle, because UI Automation reads whatever desktop is in front. And any product whose old build can no longer be compiled — a yanked dependency, a dead toolchain — falls back to comparing against the stored record from the last time it ran. That fallback is genuinely weaker: it reintroduces every cross-day difference the paired design exists to eliminate. It must announce itself in those words on every run, not degrade quietly.
169
+
170
+ WHAT "DEEP" HONESTLY MEANS. Every door the code exposes, and every journey the existing test suite already walks. It is NOT every possible state — nothing can enumerate that, and any tool claiming otherwise is lying. The defensible claim is: it catches breaks reachable from the journeys it has, and the coverage ledger names the doors it has never opened, so the hole is visible instead of pretended away.
171
+
172
+ WHAT IT CANNOT SEE AT ALL. Anything irreversible is observed at the CALL boundary — did the same charge get requested, for the same amount — never at the effect. If a bug only appears after the payment settles or the email lands, this tool is blind to it, by design and permanently. Data-destroying migrations are refused rather than run twice, and refusal is reported as missing coverage. And subtracting the wobble floor actively HIDES intermittent bugs: a race that already existed and got worse will not show. Running the new build twice recovers half of this — it flags anything newly unstable — but only half. That is the sharpest weakness in the whole architecture and I am not going to dress it up.
173
+
174
+ WHAT HE HAS TO ACCEPT OR PROVIDE. The word "ship", which he already gives — nothing new. A handful of escalated decisions a month, inside the closing summary. A second machine or his office box for the mobile and Windows runs; his laptop should not boot two simulators while he is working. Java on his Mac for Android. And the one unavoidable cold start: on any existing product there is no reference until he ships once with the hook in place, so the first week of any product is guards only.
175
+
176
+ THREE THINGS ALL FIVE DESIGNS MISSED. None costed the disk — stored build artifacts for a paired system run to tens of gigabytes a year; keep artifacts only at markers, keep the observation files forever since they are small. None handled one repo producing five artifacts through five toolchains, which is exactly Terminal Deck: a change in shared code breaks the phone, so blast radius has to cross artifact boundaries. And none proposed that the tool prove it still catches things — which is why the self-check corpus is in phase one and not an afterthought.
177
+
178
+ ---
179
+
180
+ ## Corrections after Asad's second statement (2026-08-29)
181
+
182
+ **The loop, in his words, and it is the design.** The agent calls the MCP tool. The tool runs. The
183
+ results come back. Everything unchanged is skipped and never reaches the agent's context. What
184
+ remains is what changed — and of that, the agent already knows what it meant to change, so the
185
+ targets are the differences it did NOT intend. It fixes those and runs again. He is not in this loop
186
+ at all, except for the small class of things no agent may wave through.
187
+
188
+ **On effort.** The fortnight estimates for iOS and Android were wrong in a way worth naming: they
189
+ priced the PAIRED machinery — two simulators, cloned devices, byte-identical snapshot restore — not
190
+ the act of driving the app. Driving an iOS or Android app and reading what is on its screen is three
191
+ or four days. Ship that against the stored record first; add pairing only where the stored record
192
+ proves too noisy. The same split applies everywhere: the cheap half of every platform lands first.
193
+
194
+ **On access.** He offered to install the tool on his PC and hand over an MCP endpoint for it. Not
195
+ needed: `ssh imza-pc-linux` is already a working shell on that machine, and it has `powershell.exe`
196
+ interop onto the real logged-in Windows desktop. The Windows runner uses access that already exists.
197
+ The rule generalises — **the tool reaches every platform through the access the agent already has**,
198
+ and it must never ask a person to wire something up that a credential or an SSH host already covers.
199
+
200
+ **A requirement I had not written down: the tool must describe itself to the AI that installs it.**
201
+ Every version ships knowing, in machine-readable form and in plain English:
202
+ - what it can check on this machine right now, and what it cannot, and why
203
+ - what is missing that would unlock more (a runtime, a device, a snapshot, an SSH host)
204
+ - every parameter, endpoint and link needed to make it seamless, filled in where it can be detected
205
+ - the shape of its own results, so an agent can act on them without being taught
206
+
207
+ `doctor` becomes the machine-readable version of this, exposed over MCP as the first call any agent
208
+ makes. Nothing about wiring this tool up should ever require a human to read documentation.
209
+
210
+ ## Designing for the stranger, not the owner (2026-08-29)
211
+
212
+ The owner made a point that changes what `capabilities` has to be:
213
+
214
+ > "Don't think about just my case, because you have access to everything of mine. Some people will
215
+ > not have the device access, so they will have to prepare it. In that case the readme file explains
216
+ > it to the AI, so the AI can explain to the person: you need this and you need that."
217
+
218
+ `capabilities` was being designed as detection — "here is what works on this machine" — which is the
219
+ right answer on a machine where everything is already wired. On a stranger's machine the useful half
220
+ is the opposite: **what is missing, and who has to fix it.**
221
+
222
+ So every platform reports one of four states, and the fourth is the one that matters:
223
+
224
+ 1. **Ready** — it works here now. Nothing to say.
225
+ 2. **The agent can fix this itself** — a package to install, a browser to download, a port to free.
226
+ The tool returns the exact command and the agent just does it. The person is never told.
227
+ 3. **Only a person can do this** — install Xcode, plug in a phone, log into an account, accept a
228
+ permission dialog, provide an SSH host for a Windows box. The tool returns **what to do, why it
229
+ is needed, and what it unlocks**, written for a person who is not a programmer, so the agent can
230
+ relay it in one clear sentence rather than inventing its own instructions.
231
+ 4. **Not possible here at all** — a real iPhone cannot run two builds; a Windows GUI cannot be driven
232
+ from a Mac without a Windows machine. Say so, say what the nearest honest alternative is, and stop
233
+ offering it.
234
+
235
+ The rule this encodes is the owner's own working rule, applied to his users: **never make a person do
236
+ what the machine can do.** The agent installs everything installable and hands the human only the
237
+ irreducible steps — the ones that need a licence, a device, a password or a pair of hands.
238
+
239
+ And it must degrade honestly. A project with only the web adapter available is still useful; it must
240
+ say plainly "this covers your website; your iPhone app is not being checked and here is why", rather
241
+ than reporting a green run that quietly means less than it appears to.
242
+
243
+ ---
244
+
245
+ ## What the plan got wrong (2026-08-30)
246
+
247
+ Written down because a design document that only records the parts that came true is a
248
+ sales page.
249
+
250
+ **The fortnight estimates for Android and iOS.** They priced the paired machinery — two
251
+ emulators, cloned devices, byte-identical snapshot restore — and not the act of driving the
252
+ app. Driving one and reading its screen took days. Both shipped against the stored record,
253
+ which each run says out loud.
254
+
255
+ **"Windows only if he ever ships one."** It was built, because a runner already existed: an
256
+ ssh host with a WSL shell on a real Windows desktop, nothing installed on it, the probe sent
257
+ down the connection each run. The lesson generalised into a rule — the tool reaches every
258
+ platform through access the agent already has, and must never ask a person to wire up
259
+ something a credential or an ssh host already covers.
260
+
261
+ **"`doctor` matters more, not less."** True, and understated. It was wrong about this
262
+ machine twice on the last night of the build: it asked `command -v powershell.exe` over ssh,
263
+ which answers "no" on a machine with Windows sitting right behind it, and it read a git
264
+ host's *refusal* as a reply and listed github.com as a machine to run checks on. Both are in
265
+ the README's list of silences. The rule that came out of it is worth more than the fix:
266
+ **a probe must ask the filesystem, not the path; read standard output, not whatever spoke;
267
+ and match the whole line, because a host that quotes your command back can otherwise answer
268
+ for itself.**
269
+
270
+ **The self-check corpus was the best decision in the whole design,** and the reason is not
271
+ the one given here. It was justified as proving the engine catches things. What it actually
272
+ did was catch the engine being *perturbable*: it came back "1 of 9 wrong" with the test suite
273
+ running alongside it, then passed five times on a quiet machine. That is how the load case
274
+ became part of the gate — the corpus and the suite are run at the same time, on purpose,
275
+ because that is how it will really be used.
@@ -0,0 +1,295 @@
1
+ # Installing Stays Fixed — for the agent doing it
2
+
3
+ You have been asked to add Stays Fixed to somebody's project. This page is written for you,
4
+ not for them. Follow it top to bottom and you will not need to read anything else, and you
5
+ will not need to ask them anything you could have found out yourself.
6
+
7
+ **The one rule.** Never make a person do what the machine can do. You install everything
8
+ installable, you work out everything readable, and you hand them only the steps that need a
9
+ licence, a device, a password, a real customer record or a pair of hands. Every one of those
10
+ comes back to you already written in plain words — say it to them the way it is written.
11
+
12
+ ---
13
+
14
+ ## What this tool is, in four sentences
15
+
16
+ It proves nothing that already worked has broken. It runs their product through the same
17
+ steps twice, compares that against the build they were last happy with, subtracts whatever
18
+ their product disagrees with itself about, and reports only what is left. Nothing that stayed
19
+ the same is ever mentioned — which is what keeps the answer short enough for you to read every
20
+ word of it. Nobody approves pictures: the build they say `staysfixed ship` about IS the
21
+ definition of working.
22
+
23
+ ---
24
+
25
+ ## The install, in order
26
+
27
+ ### 1. Ask the machine what it can do
28
+
29
+ ```
30
+ staysfixed doctor --json
31
+ ```
32
+
33
+ This is the first call. It comes back as one object saying what can be checked on this
34
+ machine right now, what is missing, and — the field that matters — **who has to fix each
35
+ missing thing**. Nothing in `needs` got there without failing a real probe first, so you never
36
+ have to check whether something is "really" missing.
37
+
38
+ Read `surfaces[].state`. It is one of four values and they mean exactly what they say:
39
+
40
+ | `state` | What you do |
41
+ |---|---|
42
+ | `ready` | Nothing. Do not mention it. |
43
+ | `the agent can fix this` | Run the commands in `needs[].fix`. Do not mention it. |
44
+ | `only a person can do this` | Tell them, using the words in `needs[].what/why/fix`. |
45
+ | `not possible here` | Do not offer it again. `instead` holds the nearest honest alternative — say that. |
46
+
47
+ Also read `covers.short`. It is one paragraph saying what a clean run on this machine would
48
+ and would not mean, written to be repeated to a person word for word. If you only quote one
49
+ thing from `doctor`, quote that.
50
+
51
+ ### 2. Set it up for the project
52
+
53
+ ```
54
+ staysfixed init --json
55
+ ```
56
+
57
+ This reads the repository — package.json, the lockfile, the framework config, the folder
58
+ shapes, built artifacts, an `.app`, an `.xcodeproj`, a `gradlew`, a Dockerfile, the test
59
+ runner, and every route, exported name and private channel written in the source — and works
60
+ out what the repository actually makes. Then it writes a settings file with an explanation
61
+ beside every option.
62
+
63
+ **A repository usually makes more than one thing.** `plan.project.products` is a list, and a
64
+ list of four is normal: one repository producing a desktop app, an iPhone app, an Android app
65
+ and a website is the case this was built against. Do not treat the first entry as the answer.
66
+
67
+ What comes back:
68
+
69
+ - `plan.project` — what it is, with `evidence` naming the actual file behind every claim, and
70
+ `confidence` on every product. Anything below `0.5` is a guess and says so. If it is wrong,
71
+ the evidence tells you why in one line and you fix it in the settings.
72
+ - `plan.readiness[]` — the same four states as above, but per **product** rather than per
73
+ platform.
74
+ - `plan.needs.agent` — clear this list yourself. Do not mention any of it.
75
+ - `plan.needs.person` — this is the ONLY thing you say out loud. Each item has `what`, `why`,
76
+ `unlocks` and `fix`, already written for somebody who is not a programmer.
77
+ - `plan.needs.impossible` — nothing to do. `fix` holds the honest alternative.
78
+ - `plan.journeys[]` — everything it can already walk, and where each came from.
79
+ - `plan.config` — where the settings go and their whole text. **If `config.exists` is true it
80
+ wrote nothing**, because it never overwrites somebody's file. The text is still there: read
81
+ their file, and merge across only what is missing.
82
+ - `plan.covers.short` — the paragraph to repeat.
83
+
84
+ `init` writes at most two files: the settings, and three lines appended to `.gitignore`. Add
85
+ `--dry-run` to work everything out and write nothing.
86
+
87
+ **Where the steps come from, today.** Each adapter reads their source and offers what it
88
+ finds there — routes, commands, screens, message channels — and `--journeys <file>` names
89
+ steps by hand. Harvesting their own test suite and replaying a recorded session are written
90
+ in `src/v2/journeys/` and are **not wired into a run**: ask for `--journeys suite` or
91
+ `--journeys recorded` and you are told so by name. Nothing quietly substitutes different
92
+ steps and hands you a clean answer about them.
93
+
94
+ ### 3. Take the first reading
95
+
96
+ ```
97
+ staysfixed check --paired
98
+ ```
99
+
100
+ `--paired` boots the old build live rather than trusting a stored record. It is slower and it
101
+ is the strongest answer there is. Use it the first time and whenever the run says the stored
102
+ record is thin.
103
+
104
+ On a project that has never shipped with this installed there is nothing on record as working
105
+ yet, so this first run **proves nothing** and says so in those words. That is not a failure;
106
+ it is the cold start, and it is over as soon as they ship once.
107
+
108
+ ### 4. Wire it into their agent
109
+
110
+ `plan.wiring.mcp` is the MCP block, with the project path already filled in. Paste it into
111
+ their agent's MCP settings. It runs `staysfixed mcp`, which serves the seven difference-engine
112
+ tools — capabilities, intent, check, explain, prove, waive, coverage. (`staysfixed mcp --v1`
113
+ serves the older picture-checking tools instead, for anybody who wired those up before.) From then on the loop is: you call `staysfixed_check`, you get
114
+ back only the differences, you already know what you meant to change, so the ones you did NOT
115
+ intend are your work queue. You fix those and run again. The person is not in that loop at
116
+ all.
117
+
118
+ ### 5. Put one line in their release script
119
+
120
+ ```
121
+ staysfixed ship
122
+ ```
123
+
124
+ At the very end, after the thing has actually gone out. That build becomes what "working"
125
+ means from then on. This is the whole approval mechanism, and it is why they never open the
126
+ tool. It never fails a release: if it cannot work something out it says so and exits 0.
127
+
128
+ ---
129
+
130
+ ## Reading a check
131
+
132
+ Every check answers with one object. The fields that decide what you do:
133
+
134
+ - **`ok`** — true when nothing unintended survived. False when something did, **or** when
135
+ something that used to give the same answer every time stopped doing so.
136
+ - **`blocked`** — the run could not happen. This is neither a pass nor a failure. Never
137
+ report a blocked run as "nothing changed".
138
+ - **`mode`** — `paired` means the old build was booted and walked here, in this minute.
139
+ `stored-record` means it was compared against what the old build wrote down last time,
140
+ which is genuinely weaker. When it is `stored-record`, `modeWarning` holds the sentence to
141
+ repeat.
142
+ - **`findings[]`** — ranked, worst first. This is the only part you need to read. Each one is
143
+ a cluster of differences that share a cause, not a single address: one missing stylesheet is
144
+ one finding, not four hundred differences.
145
+ - **`findings[].sealed`** — true means no agent may wave it through, whatever you believe you
146
+ meant to change. Money, sign-in, data loss, a crash, or a bug already reported once. It goes
147
+ to a person. Do not argue with it, do not work around it.
148
+ - **`newlyUnstable`** — addresses that were steady before the change and disagree with
149
+ themselves now. Treat these as findings even though no value "changed": the edit made
150
+ something unpredictable.
151
+ - **`coverage.gaps[]`** — everything that was NOT looked at, each with what would unlock it.
152
+ An unopened door is visible here rather than silently passing. Read this before telling
153
+ anybody a run was clean.
154
+ - **`summary`** — one paragraph covering all of the above, safe to quote word for word.
155
+
156
+ ### The three tools that go with it
157
+
158
+ - `staysfixed_intent` — say what you meant to change **before** you run the check. Sealing it
159
+ first is what makes the claim falsifiable rather than a story told afterwards.
160
+ - `staysfixed_prove` — proves a finding was caused by your edit, by undoing that edit and
161
+ re-running. A proof, not a guess. If the difference survives the revert, you were wrong.
162
+ - `staysfixed_waive` — mark a difference as intended. Four gates: sealed classes are refused,
163
+ it has to match the intent you sealed first, there is a budget of five per change, and every
164
+ waiver expires when the reference moves. You can never write a reference. Only shipping does
165
+ that.
166
+
167
+ ---
168
+
169
+ ## What to say to the person, and when
170
+
171
+ **Say nothing** about anything in `needs.agent`, anything `ready`, or any step you completed
172
+ yourself. A set-up that mentions work you already did reads as work they have to check.
173
+
174
+ **Say this, once, when everything is in place** — build it out of `plan.covers.short`:
175
+
176
+ > Stays Fixed is set up. It will now check *&lt;the things covered&gt;* every time I change
177
+ > anything, and tell me the moment something that used to work stops working. It is not
178
+ > checking *&lt;the things not covered&gt;* — *&lt;the reason&gt;*. You do not have to approve
179
+ > anything: when you release, one line in the release script records that build as what
180
+ > "working" means.
181
+
182
+ **Say this when something genuinely needs them** — one item per line, their words already
183
+ written for you in `needs.person`:
184
+
185
+ > One thing needs you: *&lt;what&gt;*. *&lt;why&gt;* It unlocks *&lt;unlocks&gt;*. To do it:
186
+ > *&lt;fix&gt;*.
187
+
188
+ The usual four, and they are usual because a machine genuinely cannot do them:
189
+
190
+ 1. **A real value for a changing part of an address** — an id or a slug that exists in their
191
+ data. Nobody but them knows one.
192
+ 2. **A way to put the data back** — a database dump or reset command. Both builds have to see
193
+ the same rows or every difference is really a data difference.
194
+ 3. **Confirming which routes must never really run** — the tool guesses from names like
195
+ `/charge` and puts them in the settings; only they know if the guess is right.
196
+ 4. **Xcode, a device, an account, a licence** — a download with an agreement on it, or a thing
197
+ with a cable.
198
+
199
+ **Never say** "install Tailscale", "give me SSH access" or anything else the tool did not put
200
+ in `needs.person`. If a host is already reachable, `doctor` found it and it is not a step.
201
+
202
+ ---
203
+
204
+ ## How to know it is working
205
+
206
+ Four checks, in order, and they take about a minute:
207
+
208
+ 1. `staysfixed doctor --json` returns an object with at least one surface not `unavailable`.
209
+ 2. `staysfixed init --json` returns `ok: true`, and `plan.readiness` has at least one product
210
+ whose state is `ready`.
211
+ 3. `staysfixed check --paired` finishes with `blocked` absent and `coverage.paths` above zero.
212
+ Zero addresses observed means it walked nothing, whatever else it says.
213
+ 4. Break something on purpose — change a line of printed text, delete a route — run
214
+ `staysfixed check`, and confirm it names it. Then put it back.
215
+
216
+ Step 4 is the one that matters. A tool that reports "nothing changed" is indistinguishable
217
+ from a tool that is broken, and the only way to tell them apart is to break something and
218
+ watch it notice. There is a built-in version of the same idea:
219
+
220
+ ```
221
+ staysfixed check --selfcheck
222
+ ```
223
+
224
+ which builds seventeen deliberately broken products and proves the engine still behaves on
225
+ every one — twelve breaks it must catch, four clean pairs it must stay silent about, and one
226
+ product so unsteady that the only correct answer is that the run says nothing at all. A case that
227
+ misbehaves is built again and run again before that counts: fail twice and it is a real
228
+ failure; behave the second time and it comes back as **could not tell**, which is exit code 2
229
+ and is not a pass. That exists because a corpus that can be perturbed by a busy machine is
230
+ worth nothing on a busy machine.
231
+
232
+ ---
233
+
234
+ ## When it says it cannot
235
+
236
+ Every one of these is a designed answer, not a fault. Repeat the reason; do not work around it.
237
+
238
+ - **"There is nothing on record as working yet."** The cold start. One `staysfixed ship` ends
239
+ it forever. Until then the run proves nothing and must not be reported as clean.
240
+ - **"This was not a full paired run."** It compared against a stored record. Weaker, and it
241
+ says so every single time by design. `--paired` is the fix.
242
+ - **"Nothing here knows how to drive a &lt;platform&gt; journey yet."** Missing coverage, and it
243
+ is counted as missing rather than passed.
244
+ - **A refused effect.** Anything irreversible is watched at the moment it is asked for and
245
+ never allowed to happen, and a migration that destroys data is not run at all. Both are
246
+ reported as gaps in coverage. Neither is ever a pass.
247
+ - **`blocked: true`.** No answer. Not a pass, not a failure. Say "the check could not run"
248
+ and why.
249
+
250
+ ---
251
+
252
+ ## Five things this tool will never see
253
+
254
+ Say these once, when someone asks how much it covers. They are permanent, they are in
255
+ `doctor`'s `limits`, and pretending otherwise is worse than the gap itself.
256
+
257
+ 1. **Anything after the call boundary.** It watches the same charge being requested for the
258
+ same amount, and stops it. A bug that only appears once the payment settles or the email
259
+ lands is invisible to it, by design and for good.
260
+ 2. **Intermittent bugs that already existed.** Subtracting a product's own wobble hides them.
261
+ Running the new build twice recovers half of this by flagging anything *newly* unstable —
262
+ only half. This is the sharpest weakness in the whole design.
263
+ 3. **States no journey reaches.** It checks the journeys it has, and `coverage.gaps` names the
264
+ doors it has never opened. It cannot enumerate every possible state, and anything claiming
265
+ to is lying.
266
+ 4. **Whether your product got slower.** How long something took is recorded, printed in the
267
+ sentence beside it, and never compared. A stopwatch on a shared machine measures how busy
268
+ the machine is at least as much as it measures the product — thirty runs of the same
269
+ one-line program on an idle Mac spread from 48ms to 96ms against a bucket at 100ms — so
270
+ comparing it invents a slowdown every time the machine is busy. A build that *hangs* is
271
+ still caught: it is stopped for taking too long, and how it finished is compared.
272
+ 5. **A change buried in the middle of an output over 64KB.** The two ends are kept and
273
+ compared, along with the exact number of bytes discarded, so a middle that grew or shrank
274
+ shows up. One that changed without changing length does not. The whole text is written to
275
+ the evidence folder, and the run says it only compared the ends.
276
+
277
+ ---
278
+
279
+ ## The commands, in one place
280
+
281
+ | Command | What it is for |
282
+ |---|---|
283
+ | `staysfixed doctor --json` | What this machine can drive, and who fixes what. Call it first. |
284
+ | `staysfixed init --json` | Read the project, write the settings, list what is left. |
285
+ | `staysfixed init --dry-run` | The same answer, writing nothing. |
286
+ | `staysfixed check --paired` | The strongest run. Boots the old build live. |
287
+ | `staysfixed check --json` | The everyday run. Only what changed comes back. |
288
+ | `staysfixed check --journeys <file>` | Walk exactly the steps a journeys file names. |
289
+ | `staysfixed check --against <ref>` | Compare against a tag, commit or marker. |
290
+ | `staysfixed check --selfcheck` | Prove the engine still catches deliberate breakage. |
291
+ | `staysfixed ship` | The build that went out is now what "working" means. |
292
+
293
+ And the three files, if you ever need to look: `src/v2/detect.js` works out what the project
294
+ is, `src/v2/doctor.js` works out what the machine can do, `src/v2/init.js` turns both into
295
+ settings and a list of what is left.