staysfixed 0.7.1 → 0.8.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.
- package/CHANGELOG.md +364 -0
- package/README.md +193 -55
- package/docs/design-v2.md +24 -4
- package/docs/getting-started.md +18 -5
- package/docs/guards.md +2 -2
- package/docs/how-v2-works.md +12 -11
- package/docs/mcp.md +17 -8
- package/docs/settings.md +549 -0
- package/docs/watching.md +10 -4
- package/examples/staysfixed.config.electron.js +17 -6
- package/examples/staysfixed.config.web.js +22 -5
- package/package.json +2 -1
- package/src/cli/index.js +55 -46
- package/src/cli/watch-flags.js +54 -0
- package/src/core/config.js +23 -3
- package/src/guard/run.js +49 -1
- package/src/report/console.js +15 -2
- package/src/v2/adapters/android-driver.js +6 -1
- package/src/v2/adapters/android.js +97 -2
- package/src/v2/adapters/contract.js +42 -5
- package/src/v2/adapters/electron.js +72 -6
- package/src/v2/adapters/http.js +11 -2
- package/src/v2/adapters/ios-driver.js +64 -14
- package/src/v2/adapters/ios.js +247 -25
- package/src/v2/adapters/process.js +728 -66
- package/src/v2/adapters/python.js +495 -0
- package/src/v2/adapters/source.js +373 -18
- package/src/v2/adapters/web-driver.js +94 -24
- package/src/v2/adapters/web.js +142 -9
- package/src/v2/adapters/windows.js +18 -1
- package/src/v2/browsers.js +9 -1
- package/src/v2/cause.js +61 -17
- package/src/v2/check.js +530 -66
- package/src/v2/ci.js +130 -35
- package/src/v2/cli.js +42 -24
- package/src/v2/cluster.js +164 -13
- package/src/v2/coverage.js +43 -176
- package/src/v2/detect.js +308 -60
- package/src/v2/doctor.js +345 -47
- package/src/v2/init.js +162 -61
- package/src/v2/intent.js +9 -23
- package/src/v2/journeys/from-suite.js +336 -30
- package/src/v2/journeys/index.js +99 -6
- package/src/v2/mcp/tools.js +10 -11
- package/src/v2/normalise.js +169 -23
- package/src/v2/observation.js +19 -33
- package/src/v2/rank.js +216 -23
- package/src/v2/reference.js +40 -10
- package/src/v2/remote.js +113 -18
- package/src/v2/run.js +103 -14
- package/src/v2/sealed.js +0 -20
- package/src/v2/selfcheck.js +190 -13
- package/src/v2/ship.js +29 -5
- package/src/v2/store.js +67 -1
- package/src/v2/types.js +12 -2
- package/src/v2/waiver.js +64 -54
- package/src/v2/watch/events.js +60 -215
- package/src/v2/watch/focus.js +14 -4
- package/src/v2/watch/panel.js +167 -17
package/README.md
CHANGED
|
@@ -52,14 +52,22 @@ npx staysfixed check
|
|
|
52
52
|
No account, no sign-up, no server anywhere, nothing uploaded. It works in any
|
|
53
53
|
project in any language — it only needs to be able to run your product.
|
|
54
54
|
|
|
55
|
-
Requirements: **Node 22 or newer
|
|
56
|
-
|
|
55
|
+
Requirements: **Node 22 or newer**, and **git** — your project has to be a git
|
|
56
|
+
repository. Not for history's sake: git is how an old build is put back on the
|
|
57
|
+
machine to be walked live, and how a difference is measured against the code you
|
|
58
|
+
just changed. A folder with no git in it is refused outright rather than checked
|
|
59
|
+
against a guess. Everything else depends on what you are watching, and the tool
|
|
60
|
+
works out what it has:
|
|
57
61
|
|
|
58
62
|
```
|
|
59
63
|
npx staysfixed doctor
|
|
60
64
|
npx staysfixed doctor --json # the same answer, for an agent
|
|
61
65
|
```
|
|
62
66
|
|
|
67
|
+
Everything each kind of product needs — the exact programs, the exact install
|
|
68
|
+
commands, the settings keys and the licences only a person can accept — is in
|
|
69
|
+
[docs/settings.md](docs/settings.md).
|
|
70
|
+
|
|
63
71
|
`doctor` is the first thing you should run and the first thing an agent should
|
|
64
72
|
call. It says what it can check on this machine, what it cannot, what is missing,
|
|
65
73
|
and the exact command that would fix each gap — and it never suggests setting up
|
|
@@ -94,23 +102,27 @@ phone app here to check.
|
|
|
94
102
|
|
|
95
103
|
### What a fresh install downloads, and what it does not
|
|
96
104
|
|
|
97
|
-
`npm install staysfixed` pulls **
|
|
98
|
-
`
|
|
99
|
-
|
|
105
|
+
`npm install staysfixed` pulls **three packages and no browser** — `pixelmatch`,
|
|
106
|
+
`pngjs` and `playwright-core`, about 13MB together on a Mac. `playwright-core` is
|
|
107
|
+
the part that drives a browser; it is deliberately the version that **downloads
|
|
108
|
+
none**, so nothing here takes minutes and nothing lands in a shared cache behind
|
|
109
|
+
your back.
|
|
100
110
|
|
|
101
|
-
Checking a **website** needs
|
|
102
|
-
|
|
103
|
-
|
|
111
|
+
Checking a **website** needs an actual browser, and the tool looks for one you
|
|
112
|
+
already have before asking for anything: Chrome for Testing, Chromium, Edge or
|
|
113
|
+
Chrome. If there is none on the machine at all, that is one command:
|
|
104
114
|
|
|
105
115
|
```
|
|
106
|
-
|
|
116
|
+
npx playwright install chromium
|
|
107
117
|
```
|
|
108
118
|
|
|
109
|
-
Measured on a Mac in August 2026
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
119
|
+
Measured on a Mac in August 2026 that is about **570MB** in a shared cache
|
|
120
|
+
outside your project — 371MB for Chrome for Testing and 196MB for its headless
|
|
121
|
+
shell — downloaded once per machine, not once per project. `doctor` tells you if
|
|
122
|
+
you need it, and it is one of the things an agent can simply do without asking
|
|
123
|
+
you. A project that already drives its own tests with the full `playwright`
|
|
124
|
+
package is used as it is and never asked to install a second copy of the same
|
|
125
|
+
driver.
|
|
114
126
|
|
|
115
127
|
Checking a **desktop app** needs no browser at all and no download: the app is
|
|
116
128
|
its own Chromium, and the tool drives it over its own debugging port.
|
|
@@ -150,21 +162,33 @@ pretend otherwise.
|
|
|
150
162
|
| The reference cut when you ship, sealed intents, the waiver budget, and escalations in your closing summary | **Works.** This page describes what it actually does. |
|
|
151
163
|
| The coverage ledger — every door counted, the unopened ones named, and the sentence saying so on every reply | **Works.** See [what it did not check](#what-it-did-not-check). |
|
|
152
164
|
| Aiming a check at one kind of product, and refusing by name rather than checking something else | **Works.** |
|
|
153
|
-
| Steps taken from your own test suite
|
|
165
|
+
| Steps taken from your own test suite | **Works.** `--journeys suite` runs each test file twice inside the scratch copy, reports every check by name and why each failure failed, and stops after 90 seconds naming every file it did not reach. It is opt-in: running a stranger's whole suite twice on every check is not a thing to do by default. It catches what nothing else can — remove the penny-rounding from a `total()` and the product's own output does not move by one character, the discovered journeys say "nothing has changed", and the harvest names the check that turned red. |
|
|
166
|
+
| Steps taken from a recorded session, or rejected at birth for not repeating twice | **Written, not wired.** The code is in `src/v2/journeys/` with tests around it, and nothing on the check path calls it yet. Ask for `--journeys recorded` and you are told so by name. |
|
|
154
167
|
| Android APKs on an emulator | **The adapter is here.** It reads everything the APK declares with nothing installed and no Java, and where there is an emulator it installs one build at a time and walks it. Whether *this* machine can run one is a separate question, and `doctor` asks the adapter itself rather than keeping a second opinion — most of what it wants installs with a command; accepting Google's licence, once, needs a person. Two emulator snapshots restoring byte-identically is unproven, so Android compares against the stored record and says which mode it used. |
|
|
155
168
|
| The iOS simulator | **The adapter is here.** It reads what the app bundle declares with nothing running, and where Xcode and a simulator runtime are present it installs one build at a time, boots it and reads what is on the screen. It is new. Paired running costs two `xcodebuild` passes, so it is for before a release rather than for every edit, and like Android it compares against the stored record and says which mode it used. Ask `doctor` what it is actually covering on your machine before trusting a clean run. |
|
|
156
169
|
| Native Windows GUI (a real Win32 app, not an Electron one) | **The probe is here**, driven over ssh to any machine that reaches a Windows desktop — a WSL shell on one counts, and nothing is installed on it. Windows shows one desktop, so two builds can never run at once: the comparison is genuinely weaker here than anywhere else. |
|
|
157
170
|
|
|
158
171
|
`staysfixed check` is the front door for both. Version 1's flags still mean
|
|
159
|
-
exactly what they meant yesterday — `--pictures`, `--guards
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
172
|
+
exactly what they meant yesterday — `--pictures`, `--guards` and `--only` reach
|
|
173
|
+
the same code they always did, on the settings you already have, and `staysfixed
|
|
174
|
+
mcp --v1` still serves the picture tools. Nobody who installed this last week has
|
|
175
|
+
to change anything.
|
|
176
|
+
|
|
177
|
+
**Two things did change, and they are worth reading if you installed this before
|
|
178
|
+
today.**
|
|
179
|
+
|
|
180
|
+
`--watch` moved. `staysfixed check --watch` opens **version 2's** panel — the one
|
|
181
|
+
built for a difference engine, which draws journeys, wobble, findings and a
|
|
182
|
+
verdict. Version 1's panel drew approved pictures side by side, which this tool no
|
|
183
|
+
longer has. `--pictures --watch` and `--guards --watch` still open version 1's
|
|
184
|
+
panel over version 1's run, so the only person whose command changed meaning is
|
|
185
|
+
the one who typed `--watch` on its own and got a picture check they did not ask
|
|
186
|
+
for. Everything about the panel is in [docs/watching.md](docs/watching.md).
|
|
187
|
+
|
|
188
|
+
`staysfixed init` now writes settings for the difference engine. On a product with
|
|
189
|
+
no screen those settings have no `app` block in them, and the picture commands say
|
|
190
|
+
so plainly rather than telling you to invent a web address. Add an `app` block
|
|
191
|
+
yourself if you want picture checks too.
|
|
168
192
|
|
|
169
193
|
## How it proves nothing changed
|
|
170
194
|
|
|
@@ -181,6 +205,18 @@ There is no tolerance setting in version 2 and there is not going to be one.
|
|
|
181
205
|
Tolerance knobs are how tools like this die — too loose to catch the real thing,
|
|
182
206
|
too tight to leave switched on.
|
|
183
207
|
|
|
208
|
+
Version 1's picture check is the proof, and it was caught doing exactly that.
|
|
209
|
+
Its default allowed 0.05% of a picture through, with a comment saying that was
|
|
210
|
+
"enough for font hinting noise, nowhere near enough to hide a missing
|
|
211
|
+
stylesheet". On a 2880×1800 screenshot, 0.05% is **2,592 pixels**. Taking one
|
|
212
|
+
letter out of a page's main heading moves **593** — so the check reported
|
|
213
|
+
"Everything that worked still works" over a page anybody could see was wrong.
|
|
214
|
+
Ten fresh takes of that same build differ by **zero** pixels, because the freeze
|
|
215
|
+
layer underneath is thorough enough to make that true. So version 1's default is
|
|
216
|
+
now zero as well: a project whose product genuinely wobbles can set
|
|
217
|
+
`tolerance.pixels`, and every run then says in words how many pixels that
|
|
218
|
+
setting just swallowed.
|
|
219
|
+
|
|
184
220
|
It also catches a bug class no screenshot tool has ever caught. A path that was
|
|
185
221
|
**steady** in the old build and **wobbles** in the new one means the change made
|
|
186
222
|
something unpredictable. That is a finding, even though no single value can be
|
|
@@ -241,13 +277,15 @@ under instrumentation → recorded real sessions → the agent exploring one nam
|
|
|
241
277
|
gap and freezing it into a replayable file → never a person clicking through an
|
|
242
278
|
app.
|
|
243
279
|
|
|
244
|
-
What is
|
|
245
|
-
|
|
246
|
-
journeys it can walk
|
|
247
|
-
`--journeys <file>` names steps by hand.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
280
|
+
What is wired into `staysfixed check` today is the first of those, a journeys file
|
|
281
|
+
you point it at, and — when you ask for it — the project's own test suite. Each
|
|
282
|
+
adapter reads your source and offers the journeys it can walk: routes, commands,
|
|
283
|
+
screens, message channels. `--journeys <file>` names steps by hand. `--journeys
|
|
284
|
+
suite` runs each test file twice inside the scratch copy, reports every check by
|
|
285
|
+
name, and stops after 90 seconds naming every file it did not reach. Recorded
|
|
286
|
+
sessions and the flake register are written and tested in `src/v2/journeys/`, and
|
|
287
|
+
**nothing on the check path calls them yet**. Saying so is the point: a feature
|
|
288
|
+
that exists in the repository and not in the run is not a feature you have.
|
|
251
289
|
|
|
252
290
|
## Keeping it quiet
|
|
253
291
|
|
|
@@ -338,6 +376,18 @@ verdict reading *nothing that worked has changed*. It is arithmetically true and
|
|
|
338
376
|
it would let a real regression through. That run comes back as **`NOTHING WAS
|
|
339
377
|
ACTUALLY COMPARED`**, it is not a pass, and it exits non-zero.
|
|
340
378
|
|
|
379
|
+
**The cold start is the same thing, and it is now marked the same way.** The very
|
|
380
|
+
first run on a project — before anybody has shipped once with the hook in place —
|
|
381
|
+
has no reference at all, rather than a reference with nothing in it. For a while
|
|
382
|
+
only the command line caught that: it said *"Nothing to compare against yet… this
|
|
383
|
+
run proves nothing about what still works"* and exited 2, while `--json` and
|
|
384
|
+
`staysfixed_check` over MCP both carried `ok: true` with none of those words. An
|
|
385
|
+
agent reads the fields, not the sentence, so the two interfaces that matter most
|
|
386
|
+
reported a pass over a run that compared nothing — the exact failure this tool
|
|
387
|
+
exists to prevent, produced by the tool itself. Both now answer `ok: false` with
|
|
388
|
+
**`NOTHING WAS ACTUALLY COMPARED`**. `reference.id` is still worth reading — an
|
|
389
|
+
empty string means nothing was compared, whatever else a reply says.
|
|
390
|
+
|
|
341
391
|
### The silences that were found and closed
|
|
342
392
|
|
|
343
393
|
Every one of these left something invisible while the answer looked complete —
|
|
@@ -364,6 +414,35 @@ to ask the question a different way.
|
|
|
364
414
|
| Ask a machine `command -v powershell.exe` over ssh to find out whether Windows sits behind it — a question that answers "no" on a machine with Windows right there, because that path is added by an interactive login shell and ssh does not run one. It also read a **refusal** as an answer, so `github.com` was listed as a machine to run checks on, and as a Windows desktop | Asks the filesystem for the three places PowerShell actually lives, using the one list the code that later drives it uses. Reads standard output only, and matches the whole line, so a host that quotes your command back cannot answer for itself |
|
|
365
415
|
| Name only the first eight machines in an ssh config and drop the rest without a word | Dials sixteen, and anything past that is named as not dialled rather than left out |
|
|
366
416
|
| Report Docker as present because the command is on the path, on a Mac where Docker Desktop is shut and nothing it promises would work | Asks the engine for its version, and says "installed but not answering" when that is the truth |
|
|
417
|
+
| Read the **commented-out examples** in a settings file as settings. `staysfixed init` comments out every option that does not apply to your project, so nothing is hidden from you — and doctor searched the raw text. On a folder holding one script it announced "Electron desktop apps: **Covered.** It opens release/mac-arm64/Your App.app", and an Android app beside it. A surface called covered when nothing will ever be walked on it is the worst answer this tool can give | Comments are taken away before anything is read out of the file, with strings respected so an address keeps its two slashes. The file is still never loaded — doctor must not run your code to answer a question about your machine |
|
|
418
|
+
| Ask for a build step on a plain command-line tool. A script that runs from source is recorded as "not built", because there is nothing to build, and that was read as "it has not been built yet" — so a fresh install told its owner to name the command that builds a file sitting right there, in the same breath as offering to run it | Nothing is asked for when there is nothing to build, or when a command to run it has already been worked out |
|
|
419
|
+
|
|
420
|
+
### The silences that are still open
|
|
421
|
+
|
|
422
|
+
The table above is the honest half of a habit, and it would be a dishonest half
|
|
423
|
+
on its own: it lists what was found and closed, which reads as though nothing is
|
|
424
|
+
left. These are the ones that are **still open today**, found the same way, by
|
|
425
|
+
reading the code rather than the documentation. Every one of them can end in a
|
|
426
|
+
clean-looking answer, and the file that would close each one is named so nobody
|
|
427
|
+
has to take this on trust.
|
|
428
|
+
|
|
429
|
+
Seven rows left this table in one night. They are in the closed table above now:
|
|
430
|
+
a wobble storm that needed twelve addresses before it counted, so ten of eleven
|
|
431
|
+
wobbling passed; a desktop control compared to its first 200 characters; an
|
|
432
|
+
unreadable folder losing every page of a website behind it; two screens with one
|
|
433
|
+
name collapsing into one walk; a waiver pinned to the first 40 differences of a
|
|
434
|
+
finding rather than all of them; and three iOS ceilings that stopped counting
|
|
435
|
+
without saying so. What is below is what is genuinely left.
|
|
436
|
+
|
|
437
|
+
| What is invisible | Where |
|
|
438
|
+
| --- | --- |
|
|
439
|
+
| **What the scratch copy leaves out is left out of *both* builds.** A product that actually needs one of the folders named under `process.skip` fails the same way twice, so the difference engine sees no difference and the run reads clean. The defaults are only caches and reports, and the bar is written down — but a project that adds to that list can hide a real break from itself. | `src/v2/adapters/process.js` — `SKIP_BY_DEFAULT` |
|
|
440
|
+
| **Working out what a repository makes reads at most 300 files, four folders deep, and skips any file over 2MB.** A product it never notices is never configured and never walked, so it is missing from the coverage ledger rather than named in it — the one kind of gap the ledger cannot show you. | `src/v2/detect.js` |
|
|
441
|
+
| **Values nested more than 64 levels deep are compared as "too deep" rather than by their contents.** It will never go quiet about one, but it cannot say what inside it moved. This only ever applies to a value read back off the disk; observations made in this run refuse that depth at the door. | `src/v2/observation.js` |
|
|
442
|
+
| **A change buried in the middle of an output larger than 64KB can be missed.** Both ends are kept and compared along with the exact number of bytes dropped, so a middle that grew or shrank shows up. One that changed without changing length does not. | `src/v2/adapters/contract.js` — `trimForStorage` |
|
|
443
|
+
| **How long anything took is recorded and never compared.** A build that got twice as slow is not a finding. This one is deliberate and permanent — a stopwatch on a shared machine measures the machine at least as much as the product — and a build that *hangs* is still caught, because it is stopped for taking too long and how it finished is compared exactly. | by design |
|
|
444
|
+
| **On Windows and as root, one self-check case cannot run.** It takes away the permission to write, to prove the tool still answers when the disk gives out; neither honours that. The case reports itself as untested rather than as a pass, and the closing line counts it separately. | `src/v2/selfcheck.js` |
|
|
445
|
+
| **The distance from a finding to your edit has three ceilings: 4,000 source files, 8 imports out, and 400KB per file.** Past any of them the ranking for that finding is a guess. It is not silent — a run that hit a ceiling says so and names the files it could not read — but the order is worth less than the rest of the list. | `src/v2/rank.js` |
|
|
367
446
|
|
|
368
447
|
---
|
|
369
448
|
|
|
@@ -422,9 +501,12 @@ An agent's only door is a waiver, and it passes four machine-checked gates:
|
|
|
422
501
|
3. **Five waivers between one ship and the next.** Past five it is not a change
|
|
423
502
|
with side effects, it is a rewrite, and a person looks at a rewrite. Sealing
|
|
424
503
|
another intent does not buy five more.
|
|
425
|
-
4. **Every waiver is fingerprinted to
|
|
426
|
-
the reference moves. Change
|
|
427
|
-
|
|
504
|
+
4. **Every waiver is fingerprinted to the difference it was written about** and
|
|
505
|
+
dies the moment the reference moves. Change that value and it stops covering
|
|
506
|
+
anything. The pin is the finding's title, up to twenty of its addresses and up
|
|
507
|
+
to forty of its differences — see [the silences that are still
|
|
508
|
+
open](#the-silences-that-are-still-open) for what that means on a very large
|
|
509
|
+
finding.
|
|
428
510
|
|
|
429
511
|
Every waiver is counted out loud in the reply. "Nothing changed", "nothing ran"
|
|
430
512
|
and "everything was waived" read identically otherwise, and two of those three
|
|
@@ -654,18 +736,69 @@ that is correct**: the picture commands (`status`, `walk`, `approve`, `mark`,
|
|
|
654
736
|
they need an `app` and say so plainly if there is not one. `staysfixed check`
|
|
655
737
|
needs no `app` at all.
|
|
656
738
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
739
|
+
### `process.skip` — what to leave out of the scratch copy
|
|
740
|
+
|
|
741
|
+
Every run works in a **throwaway copy of your project**, so a build can write
|
|
742
|
+
wherever it likes without touching the folder you are working in. That copy is
|
|
743
|
+
cloned rather than copied: on a Mac and on most Linux filesystems it is one call
|
|
744
|
+
per file that moves no bytes at all and shares the blocks until something writes
|
|
745
|
+
to them. Measured on a twelve-gigabyte project, most of it an iOS build folder:
|
|
746
|
+
**41.5 seconds and no disk used.** Where the filesystem cannot clone, it falls
|
|
747
|
+
back to a real copy — slower, never wrong.
|
|
748
|
+
|
|
749
|
+
If that copy still takes over twenty seconds the run says so and points you here.
|
|
750
|
+
You can name folders to leave behind:
|
|
660
751
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
752
|
+
```js
|
|
753
|
+
export default {
|
|
754
|
+
product: 'your-app',
|
|
755
|
+
process: {
|
|
756
|
+
// Folders not worth copying into the scratch build.
|
|
757
|
+
skip: ['DerivedData', '.gradle'],
|
|
758
|
+
},
|
|
759
|
+
};
|
|
760
|
+
```
|
|
666
761
|
|
|
667
|
-
The
|
|
668
|
-
|
|
762
|
+
**The bar for putting something on that list is deliberately high: only things
|
|
763
|
+
that are regenerated on demand and read by nothing.** Caches, coverage reports,
|
|
764
|
+
scratch output. Anything you skip that turns out to matter makes a run pass for
|
|
765
|
+
the wrong reason — and a run that passes for the wrong reason is the single
|
|
766
|
+
failure this whole tool exists to prevent. Build output, `node_modules`,
|
|
767
|
+
lockfiles, fixtures and configuration are all copied, every time, because a check
|
|
768
|
+
that runs against a different set of files than the real product is not checking
|
|
769
|
+
the real product. If you are not sure, leave it in and let the run be slower.
|
|
770
|
+
|
|
771
|
+
`skip` can only ever **add** to what is already left behind — `.git`,
|
|
772
|
+
`.staysfixed`, `.turbo`, `.nyc_output`, `coverage`, `.pytest_cache`,
|
|
773
|
+
`__pycache__` and `.DS_Store`. There is no way to switch one of those back on,
|
|
774
|
+
because doing so would only ever make runs slower.
|
|
775
|
+
|
|
776
|
+
A `staysfixed.config.json` works too, with a declarative `steps` form and no
|
|
777
|
+
functions — so a Rust, Python or Go project can use the tool without anybody
|
|
778
|
+
writing JavaScript. It may also live at `.staysfixed/config.js` (or `.mjs`, or
|
|
779
|
+
`.json`) if you would rather not have another file in the root.
|
|
780
|
+
|
|
781
|
+
**Every option, and what each kind of product needs on the machine**, is in
|
|
782
|
+
[docs/settings.md](docs/settings.md) — every settings key per block, and for each
|
|
783
|
+
surface the exact programs, environment variables, install commands and
|
|
784
|
+
permissions, rather than "you may need Xcode".
|
|
785
|
+
|
|
786
|
+
**Do not copy an example to set the difference engine up.** Run `staysfixed
|
|
787
|
+
init`: it reads what is actually in your project and writes a settings file with
|
|
788
|
+
every option in it — the ones that do not apply commented out rather than left
|
|
789
|
+
out — filled in from your own code. That is better than any example, because it
|
|
790
|
+
is about your project rather than somebody else's.
|
|
791
|
+
|
|
792
|
+
The three fully commented examples in [`examples/`](examples/) — [a web
|
|
793
|
+
app](examples/staysfixed.config.web.js), [an Electron
|
|
794
|
+
app](examples/staysfixed.config.electron.js) and [a
|
|
795
|
+
guard](examples/guards/the-sidebar-still-collapses.js) — are all **version 1's
|
|
796
|
+
shape**, for the picture check, which is the half that needs an `app`. Each file
|
|
797
|
+
says so at the top now. Copy one into a project and a plain `staysfixed check`
|
|
798
|
+
will tell you the settings do not name anything to open, which is correct and
|
|
799
|
+
confusing if you were not expecting it.
|
|
800
|
+
|
|
801
|
+
The design behind all of it is in [docs/how-v2-works.md](docs/how-v2-works.md).
|
|
669
802
|
|
|
670
803
|
---
|
|
671
804
|
|
|
@@ -764,10 +897,11 @@ Honestly, so you know before you invest an afternoon.
|
|
|
764
897
|
normalisation rule for whatever is moving, not by trusting the clean-looking run
|
|
765
898
|
underneath it.
|
|
766
899
|
- **What normalisation rubbed out is not itemised on every run.** The rules are in
|
|
767
|
-
your repository
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
900
|
+
your repository — one file, `.staysfixed/rules.json`, which you read like any
|
|
901
|
+
other file in a pull request — and the capture is stamped with which set was
|
|
902
|
+
used, so a run comparing against a record tidied by a different set says so.
|
|
903
|
+
There is no command that lists them and no per-run report of what each one
|
|
904
|
+
rewrote. Anything a rule covers is not being watched, and that is the point
|
|
771
905
|
of the rule; just know that adding a broad one is how you go blind on purpose.
|
|
772
906
|
- **Ranking reads your source, and it gives up on very large trees.** Distance
|
|
773
907
|
from the code you just changed is what sorts the list, and it reads up to 4,000
|
|
@@ -798,13 +932,17 @@ Honestly, so you know before you invest an afternoon.
|
|
|
798
932
|
every journey it was given. Nothing can enumerate every state, and any tool
|
|
799
933
|
claiming otherwise is lying. The coverage ledger names the doors it has never
|
|
800
934
|
opened, so the hole is visible instead of pretended away.
|
|
801
|
-
- **Your own test suite is
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
935
|
+
- **Your own test suite is walked only when you ask.** `--journeys suite`, or
|
|
936
|
+
`journeys: "suite"` over MCP, runs each test file twice inside the scratch copy
|
|
937
|
+
and reports every check by name, why each failure failed, and a fingerprint of
|
|
938
|
+
the test file itself — so an edited test says plainly that the change is yours.
|
|
939
|
+
It is off by default because running a stranger's whole suite twice on every
|
|
940
|
+
check would make this too slow to leave switched on, and a check nobody can
|
|
941
|
+
afford to run is a check nobody runs. It is held to 90 seconds and **every
|
|
942
|
+
file it did not reach is named**, one by one, never "some tests were skipped".
|
|
943
|
+
It earns the ask: take the penny-rounding out of a `total()` and the product's
|
|
944
|
+
own output does not move by one character — the discovered journeys say nothing
|
|
945
|
+
has changed, and the harvest names the check that turned red.
|
|
808
946
|
- **No hosted service, no dashboard, no accounts, no teams, nothing paid.** It is
|
|
809
947
|
a command and a folder of files in your repository.
|
|
810
948
|
- **Pictures still do not travel between operating systems.** Text is drawn
|
package/docs/design-v2.md
CHANGED
|
@@ -17,10 +17,12 @@
|
|
|
17
17
|
> servers, source reading, the web, Electron, Android, iOS and native Windows over ssh. The
|
|
18
18
|
> MCP surface is seven tools and it is what `staysfixed mcp` serves.
|
|
19
19
|
>
|
|
20
|
-
> **What is written and not wired:**
|
|
21
|
-
>
|
|
22
|
-
>
|
|
23
|
-
>
|
|
20
|
+
> **What is written and not wired:** replaying a recorded session. The code is in
|
|
21
|
+
> `src/v2/journeys/` with tests around it and nothing on the check path calls it; asking for
|
|
22
|
+
> it is refused by name. Harvesting a project's own test suite was in this list and is now
|
|
23
|
+
> wired — `--journeys suite`, opt-in, held to a 90-second budget, every file it did not reach
|
|
24
|
+
> named. Journeys otherwise come from what each adapter reads out of your source, plus a
|
|
25
|
+
> journeys file you point it at.
|
|
24
26
|
>
|
|
25
27
|
> **What is permanent and will not change:** nothing irreversible is ever run — it is watched
|
|
26
28
|
> at the call and refused at the effect, and the refusal is reported as missing coverage;
|
|
@@ -91,6 +93,13 @@ Where I rejected the alternative: the differential design would let the agent de
|
|
|
91
93
|
|
|
92
94
|
## Platforms, in build order
|
|
93
95
|
|
|
96
|
+
> **Do not install anything off this section.** These were the *plans* for driving each
|
|
97
|
+
> platform, and four of them were replaced during the build — Appium, UiAutomator2,
|
|
98
|
+
> WebDriverAgent, FlaUI and a Java runtime are named below and **none of them is used**. What
|
|
99
|
+
> each surface actually needs, with the exact command, is in
|
|
100
|
+
> [settings.md](settings.md); what this machine actually has is
|
|
101
|
+
> `staysfixed doctor`. See [what the plan got wrong](#what-the-plan-got-wrong-2026-08-30).
|
|
102
|
+
|
|
94
103
|
### 1. CLI tools and libraries
|
|
95
104
|
|
|
96
105
|
- **Driven by:** Plain child-process I/O: stdout, stderr, exit code, files touched, processes spawned, plus the exported API surface read from the source.
|
|
@@ -267,6 +276,17 @@ the README's list of silences. The rule that came out of it is worth more than t
|
|
|
267
276
|
and match the whole line, because a host that quotes your command back can otherwise answer
|
|
268
277
|
for itself.**
|
|
269
278
|
|
|
279
|
+
**Four of the tools named for driving the phones and Windows were never used.** The plan
|
|
280
|
+
said Appium 3 with UiAutomator2 for Android, WebDriverAgent for the iOS meaning tree, a Java
|
|
281
|
+
runtime for both, and a .NET probe built on FlaUI for Windows. What shipped needs none of
|
|
282
|
+
them. Android reads the APK directly — **no Java at all** — and drives the emulator through
|
|
283
|
+
`adb`. iOS uses `xcrun simctl` and a small reader compiled at run time with `clang` from the
|
|
284
|
+
Xcode command line tools. Windows sends a PowerShell script down an ssh connection each run
|
|
285
|
+
and installs nothing on the far machine. Every one of those replacements removed a
|
|
286
|
+
dependency rather than adding one, which is why they happened; the cost is that this page,
|
|
287
|
+
left alone, would have an agent installing four things nobody needs. The list that is kept
|
|
288
|
+
current is [settings.md](settings.md).
|
|
289
|
+
|
|
270
290
|
**The self-check corpus was the best decision in the whole design,** and the reason is not
|
|
271
291
|
the one given here. It was justified as proving the engine catches things. What it actually
|
|
272
292
|
did was catch the engine being *perturbable*: it came back "1 of 9 wrong" with the test suite
|
package/docs/getting-started.md
CHANGED
|
@@ -86,10 +86,11 @@ What comes back:
|
|
|
86
86
|
|
|
87
87
|
**Where the steps come from, today.** Each adapter reads their source and offers what it
|
|
88
88
|
finds there — routes, commands, screens, message channels — and `--journeys <file>` names
|
|
89
|
-
steps by hand.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
steps by hand. `--journeys suite` adds their own test suite: each test file runs twice inside
|
|
90
|
+
the scratch copy, every check is reported by name, and it stops after 90 seconds naming every
|
|
91
|
+
file it did not reach. Replaying a recorded session is written in `src/v2/journeys/` and is
|
|
92
|
+
**not wired into a run**: ask for `--journeys recorded` and you are told so by name. Nothing
|
|
93
|
+
quietly substitutes different steps and hands you a clean answer about them.
|
|
93
94
|
|
|
94
95
|
### 3. Take the first reading
|
|
95
96
|
|
|
@@ -135,6 +136,11 @@ Every check answers with one object. The fields that decide what you do:
|
|
|
135
136
|
something that used to give the same answer every time stopped doing so.
|
|
136
137
|
- **`blocked`** — the run could not happen. This is neither a pass nor a failure. Never
|
|
137
138
|
report a blocked run as "nothing changed".
|
|
139
|
+
- **`reference.id`** — check this before you believe `ok`. An **empty string** means there was
|
|
140
|
+
nothing on record to compare this build against, so the run proved nothing at all. It comes
|
|
141
|
+
back with `ok: true`, because arithmetically nothing came back different — and it is not a
|
|
142
|
+
pass. It happens on every project until somebody has run `staysfixed ship` once. The command
|
|
143
|
+
line exits 2 on it; the JSON does not say so in a field yet, so read this one.
|
|
138
144
|
- **`mode`** — `paired` means the old build was booted and walked here, in this minute.
|
|
139
145
|
`stored-record` means it was compared against what the old build wrote down last time,
|
|
140
146
|
which is genuinely weaker. When it is `stored-record`, `modeWarning` holds the sentence to
|
|
@@ -209,7 +215,9 @@ Four checks, in order, and they take about a minute:
|
|
|
209
215
|
2. `staysfixed init --json` returns `ok: true`, and `plan.readiness` has at least one product
|
|
210
216
|
whose state is `ready`.
|
|
211
217
|
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.
|
|
218
|
+
Zero addresses observed means it walked nothing, whatever else it says. On a project that
|
|
219
|
+
has never shipped, `reference.id` will be empty and that run proves nothing — expected, and
|
|
220
|
+
over as soon as they ship once.
|
|
213
221
|
4. Break something on purpose — change a line of printed text, delete a route — run
|
|
214
222
|
`staysfixed check`, and confirm it names it. Then put it back.
|
|
215
223
|
|
|
@@ -290,6 +298,11 @@ Say these once, when someone asks how much it covers. They are permanent, they a
|
|
|
290
298
|
| `staysfixed check --selfcheck` | Prove the engine still catches deliberate breakage. |
|
|
291
299
|
| `staysfixed ship` | The build that went out is now what "working" means. |
|
|
292
300
|
|
|
301
|
+
If you have to write a settings block by hand — something `init` could not know, a second
|
|
302
|
+
product, a journey through a screen — every option is in
|
|
303
|
+
[settings.md](settings.md), together with what each kind of product needs installed on the
|
|
304
|
+
machine and the exact command that installs it.
|
|
305
|
+
|
|
293
306
|
And the three files, if you ever need to look: `src/v2/detect.js` works out what the project
|
|
294
307
|
is, `src/v2/doctor.js` works out what the machine can do, `src/v2/init.js` turns both into
|
|
295
308
|
settings and a list of what is left.
|
package/docs/guards.md
CHANGED
|
@@ -34,8 +34,8 @@ export default {
|
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
They run inside `staysfixed check`, in the same real app the pictures are taken
|
|
37
|
-
from. `staysfixed check --guards
|
|
38
|
-
|
|
37
|
+
from. `staysfixed check --guards` runs just the guards, which is much faster and
|
|
38
|
+
is what you want when your edit could not possibly change how anything looks.
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
package/docs/how-v2-works.md
CHANGED
|
@@ -126,15 +126,16 @@ Ranked, because this is the real workload question:
|
|
|
126
126
|
4. **The agent exploring one named gap** and freezing it into a replayable file.
|
|
127
127
|
5. Never a person clicking through an app.
|
|
128
128
|
|
|
129
|
-
`--journeys <source>` picks between them — and **
|
|
130
|
-
not wired.**
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
feature
|
|
129
|
+
`--journeys <source>` picks between them — and **one of those five is written and
|
|
130
|
+
not wired.** A run walks (1) by default: each adapter reads your source and offers
|
|
131
|
+
what it finds there. `--journeys <file>` names steps by hand, and `--journeys
|
|
132
|
+
suite` adds (2), the project's own test suite — each file run twice inside the
|
|
133
|
+
scratch copy, every check reported by name, held to a 90-second budget with every
|
|
134
|
+
file it did not reach named. Session replay lives in `src/v2/journeys/` with tests
|
|
135
|
+
around it and nothing on the check path calls it; ask for `--journeys recorded` and
|
|
136
|
+
you are told that by name rather than handed a clean result about steps something
|
|
137
|
+
else chose. Saying so is the point: a feature that exists in the repository and not
|
|
138
|
+
in the run is not a feature you have.
|
|
138
139
|
|
|
139
140
|
---
|
|
140
141
|
|
|
@@ -343,12 +344,12 @@ release through on the strength of it.
|
|
|
343
344
|
| `src/v2/observation.js` | The address space, the channels, `diffCaptures`, `measureWobble`, `subtractWobble`. |
|
|
344
345
|
| `src/v2/normalise.js` | The rules, as data, each carrying `wouldHide` in plain English, and `explain()` so a normalisation can be answered for. |
|
|
345
346
|
| `src/v2/cluster.js` | Many differences, one cause, one finding. |
|
|
346
|
-
| `src/v2/rank.js` | `rankFindings` — furthest from the edit first, sealed classes above everything — plus `
|
|
347
|
+
| `src/v2/rank.js` | `rankFindings` — furthest from the edit first, sealed classes above everything — plus `classOf`, `whatChanged`, `importGraph`. Which classes are sealed, and the words used to refuse one, live in `src/v2/sealed.js`. |
|
|
347
348
|
| `src/v2/cause.js` | `proveCause` — revert the suspect hunk, run again, and find out. |
|
|
348
349
|
| `src/v2/store.js` | The append-only capture files, build records, references, and reading a torn file without losing the rest. |
|
|
349
350
|
| `src/v2/run.js` | `runCheck` — the loop, over any `CheckEngine`. |
|
|
350
351
|
| `src/v2/check.js` | `check` — the assembled front door. The command line, the MCP server and the self-check corpus all look for it here, on purpose: if they found the engine in different places they would be checking different things and reporting it as one. |
|
|
351
|
-
| `src/v2/selfcheck.js` | The corpus of deliberately broken builds
|
|
352
|
+
| `src/v2/selfcheck.js` | The corpus of deliberately broken builds — `CASES` — and `selfcheck`. |
|
|
352
353
|
| `src/v2/adapters/` | One per surface. The only place that knows what a browser or a child process is. |
|
|
353
354
|
| `src/v2/mcp/` | The tools an agent calls. |
|
|
354
355
|
| `src/v2/cli.js` | `V2_COMMANDS`, `run`, `doctorRun`, `checkOptions`, `report`. |
|
package/docs/mcp.md
CHANGED
|
@@ -91,9 +91,14 @@ Every client that speaks MCP over stdio wants the same three things — a comman
|
|
|
91
91
|
its arguments, and somewhere to run it. The JSON block above is the shape almost
|
|
92
92
|
all of them use; translate it into whatever your client's config file looks like.
|
|
93
93
|
|
|
94
|
-
`staysfixed
|
|
95
|
-
`wiring.mcp
|
|
96
|
-
|
|
94
|
+
`staysfixed init --json` returns that block **with the project path already filled
|
|
95
|
+
in**, under `plan.wiring.mcp` — so an agent setting this up for somebody can paste
|
|
96
|
+
it straight into the client's config without copying anything out of this page.
|
|
97
|
+
|
|
98
|
+
`staysfixed doctor --json` carries the same block under `wiring.mcp`, but its
|
|
99
|
+
`cwd` is still the placeholder `"/absolute/path/to/your/project"`: `doctor`
|
|
100
|
+
describes the machine rather than the project, so put the project's real path in
|
|
101
|
+
yourself, or take the block from `init --json` instead.
|
|
97
102
|
|
|
98
103
|
### If you have it installed locally
|
|
99
104
|
|
|
@@ -168,11 +173,15 @@ stronger, and the right thing before a release), `against` (compare with a named
|
|
|
168
173
|
marker or commit), `only` (a list of journey names), `limit` and `offset` (paging
|
|
169
174
|
through the *last* run without running anything again), and `format: "json"`.
|
|
170
175
|
|
|
171
|
-
`journeys` names where the steps come from.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
+
`journeys` names where the steps come from. The default is what each adapter reads
|
|
177
|
+
out of your source. `"suite"` walks the project's own test suite as well — each
|
|
178
|
+
test file run twice inside the scratch copy, every check reported by name and why
|
|
179
|
+
each failure failed, stopping after 90 seconds with every file it did not reach
|
|
180
|
+
named. It is worth asking for when a change could break behaviour the product's own
|
|
181
|
+
output would never show. You can also pass a path to a journeys file. `"recorded"`
|
|
182
|
+
is written in `src/v2/journeys/` and not yet wired into a run: ask for it and you
|
|
183
|
+
are told so by name, rather than given a clean result about steps something quietly
|
|
184
|
+
chose instead.
|
|
176
185
|
|
|
177
186
|
---
|
|
178
187
|
|