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.
- package/CHANGELOG.md +187 -0
- package/README.md +104 -43
- package/docs/design-v2.md +275 -0
- package/docs/getting-started.md +295 -0
- package/docs/guards.md +226 -0
- package/docs/how-it-stays-stable.md +315 -0
- package/docs/how-v2-works.md +403 -0
- package/docs/mcp.md +286 -0
- package/docs/running-it-in-ci.md +306 -0
- package/docs/watching.md +190 -0
- package/package.json +3 -3
- package/src/cli/index.js +34 -7
- package/src/core/config.js +12 -2
- package/src/v2/adapters/isolate.js +89 -0
- package/src/v2/cause.js +151 -12
- package/src/v2/check.js +345 -3
- package/src/v2/cli.js +71 -12
- package/src/v2/cluster.js +20 -3
- package/src/v2/coverage.js +40 -5
- package/src/v2/detect.js +1413 -20
- package/src/v2/doctor.js +191 -35
- package/src/v2/init.js +470 -57
- package/src/v2/mcp/tools.js +124 -11
- package/src/v2/normalise.js +54 -7
- package/src/v2/observation.js +56 -10
- package/src/v2/rank.js +212 -43
- package/src/v2/run.js +216 -31
- package/src/v2/selfcheck.js +312 -7
- package/src/v2/store.js +269 -45
- package/src/v2/watch/index.js +96 -17
- package/src/v2/watch/window.js +138 -20
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,193 @@ All notable changes to this project are recorded here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the version
|
|
5
5
|
numbers follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.7.0] — 2026-08-30
|
|
8
|
+
|
|
9
|
+
The release where the front door was found to be locked.
|
|
10
|
+
|
|
11
|
+
Everything written about this tool over MCP was true of code that no client could
|
|
12
|
+
reach: `staysfixed mcp` served version 1's picture tools, and `staysfixed init`
|
|
13
|
+
wrote version 1's picture settings. The README, `docs/mcp.md`,
|
|
14
|
+
`docs/getting-started.md` and the tool's own `capabilities` reply all described
|
|
15
|
+
the difference engine. An agent that followed this project's own wiring block got
|
|
16
|
+
a tool set none of the documentation mentions, and never reached the engine at
|
|
17
|
+
all. Nothing was broken. Nothing was reachable either, which is worse, because it
|
|
18
|
+
looks like it works.
|
|
19
|
+
|
|
20
|
+
That is fixed, and so is the second half of the same mistake: `doctor` was
|
|
21
|
+
confidently wrong about this machine, in three different ways, all of them
|
|
22
|
+
invisible.
|
|
23
|
+
|
|
24
|
+
### Changed — the front door
|
|
25
|
+
|
|
26
|
+
- **`staysfixed mcp` now serves the difference engine.** Seven tools:
|
|
27
|
+
`staysfixed_capabilities`, `_intent`, `_check`, `_explain`, `_prove`, `_waive`,
|
|
28
|
+
`_coverage`. There is still no door marked approve.
|
|
29
|
+
- **`staysfixed mcp --v1` serves version 1's picture tools, unchanged.** Nobody who
|
|
30
|
+
wired those up is stranded, and a test holds them there.
|
|
31
|
+
- **`staysfixed init` now reads your project properly.** It works out what the
|
|
32
|
+
repository actually makes, writes settings with an explanation beside every
|
|
33
|
+
option, never overwrites a file you already have, and answers with
|
|
34
|
+
`plan.project`, `plan.readiness`, `plan.needs.agent`, `plan.needs.person`,
|
|
35
|
+
`plan.needs.impossible`, `plan.journeys`, `plan.covers.short` and `plan.wiring`
|
|
36
|
+
under `--json`. `docs/getting-started.md` was written entirely around those
|
|
37
|
+
fields against a command that did not produce any of them.
|
|
38
|
+
- **Settings for a product with no screen are no longer a puzzle.** `status`,
|
|
39
|
+
`walk`, `approve`, `mark`, `trace` and `check --pictures` all work by opening
|
|
40
|
+
something and photographing it, and a command-line tool has nothing to open —
|
|
41
|
+
which is the correct shape for its settings, not a mistake. They used to answer
|
|
42
|
+
"Stays Fixed does not know what to open" and tell you to add a web address you do
|
|
43
|
+
not have. They now say which half of the tool needs a screen, and name the half
|
|
44
|
+
that covers you without one.
|
|
45
|
+
|
|
46
|
+
### Added — the MCP surface an agent reads before it calls anything
|
|
47
|
+
|
|
48
|
+
- **Every tool carries a short title and the protocol's own flags for what it does
|
|
49
|
+
to your machine.** `staysfixed_capabilities`, `staysfixed_explain` and
|
|
50
|
+
`staysfixed_coverage` are read-only and idempotent; `staysfixed_check` and
|
|
51
|
+
`staysfixed_prove` open your product, so they are neither; and none of the seven
|
|
52
|
+
claims to reach the outside world, because none of them does. A client can now
|
|
53
|
+
tell a question from an action without reading any documentation.
|
|
54
|
+
- **`journeys: "suite"` and `journeys: "recorded"` are refused by name.** Both are
|
|
55
|
+
written and tested in `src/v2/journeys/`, and nothing on the check path calls
|
|
56
|
+
them, so both used to reach the engine as the name of a file and come back as
|
|
57
|
+
"there is no journeys file at .../suite" — an error that sends an agent looking
|
|
58
|
+
for a file it never asked for.
|
|
59
|
+
|
|
60
|
+
### Fixed — `doctor` was wrong about this machine
|
|
61
|
+
|
|
62
|
+
All three were found by running it on a real Mac and checking every line it
|
|
63
|
+
printed, which is the only way this kind of mistake is ever found.
|
|
64
|
+
|
|
65
|
+
- **It asked `command -v powershell.exe` over ssh to decide whether Windows sits
|
|
66
|
+
behind a machine.** That question answers "no" on a machine with a real Windows
|
|
67
|
+
desktop right there: the path is put on `PATH` by an interactive login shell, and
|
|
68
|
+
ssh does not run one. The one true Windows runner in this machine's ssh config
|
|
69
|
+
was reported as not Windows. It asks the filesystem now, for the three places
|
|
70
|
+
PowerShell actually lives, using the same list the code that later drives it
|
|
71
|
+
uses — one list, not two.
|
|
72
|
+
- **It read a refusal as an answer.** `ssh github-imza 'echo staysfixed-reachable'`
|
|
73
|
+
is refused by github.com with `Invalid command: echo staysfixed-reachable`, on
|
|
74
|
+
stderr, quoting the command back. A probe that looked for its own word anywhere
|
|
75
|
+
in either stream found it inside the refusal, so three git hosts were listed as
|
|
76
|
+
machines this tool could run checks on — and, by the same bug, as Windows
|
|
77
|
+
desktops. It reads standard output only now, and matches the whole line.
|
|
78
|
+
- **It named the first eight machines in an ssh config and dropped the rest without
|
|
79
|
+
a word.** It dials sixteen, and anything past that is named as not dialled rather
|
|
80
|
+
than left out. Undercount and say so; never quietly stop short.
|
|
81
|
+
- **It reported Docker as present because the command was on the path**, on a
|
|
82
|
+
machine where Docker Desktop was shut and nothing it promises would have worked.
|
|
83
|
+
It asks the engine for its version now.
|
|
84
|
+
- No probe in `doctor` uses a shell variable or a loop any more. One machine in
|
|
85
|
+
this config reaches Windows through an OpenSSH server that hands the command down
|
|
86
|
+
through a second shell, and every `$p` is expanded to nothing before the shell
|
|
87
|
+
meant to read it sees it — `for p in "A"; do echo "$p"; done` prints an empty
|
|
88
|
+
line there. Literal arguments only.
|
|
89
|
+
|
|
90
|
+
### Fixed — what an agent actually reads
|
|
91
|
+
|
|
92
|
+
- **`staysfixed_explain` printed `(SEALED: [object Object])`** on the one reply an
|
|
93
|
+
agent reads when it is trying to understand a difference it may not waive.
|
|
94
|
+
- **The same sentence arrived twice in one check reply** — "not everything was
|
|
95
|
+
checked", once under the headline and again inside the summary — and the values
|
|
96
|
+
of a one-address finding were printed twice in `explain`. Both are said once now.
|
|
97
|
+
- **`staysfixed_coverage` printed the same line three times.** Several of the
|
|
98
|
+
coverage caveats share one headline and differ entirely in their reason, so a
|
|
99
|
+
list of headlines was one sentence repeated and none of the three reasons. The
|
|
100
|
+
reason is printed with each.
|
|
101
|
+
- **"2 journeys were walked" no longer sits one line above "2 of the 2 ways into
|
|
102
|
+
this product have never been walked through."** Both numbers were right and the
|
|
103
|
+
two sentences contradicted each other on screen: a journey is one route through
|
|
104
|
+
the product, a door is one way into it, and they now have two different words.
|
|
105
|
+
- **The tool no longer advises you to do something it cannot do.** Three places
|
|
106
|
+
told you to harvest your own test suite as journeys. That is written and not
|
|
107
|
+
wired, so all three now name what does work today.
|
|
108
|
+
|
|
109
|
+
### Changed — what a fresh install pulls
|
|
110
|
+
|
|
111
|
+
- **`playwright` is out of `dependencies`.** Nothing in `src/` imports it — browsers
|
|
112
|
+
are driven over the debugging protocol by code in this repository — and having it
|
|
113
|
+
there made a fresh `npm install staysfixed` pull about 18MB against a README
|
|
114
|
+
promising two packages under a megabyte. It stays a `devDependency`, and it is
|
|
115
|
+
still what `doctor` tells you to install in YOUR project when you want a browser
|
|
116
|
+
of your own.
|
|
117
|
+
- **`docs/` ships with the package**, so an agent that installed this from npm can
|
|
118
|
+
read `docs/getting-started.md` without a network.
|
|
119
|
+
|
|
120
|
+
### Changed — everything else in this batch
|
|
121
|
+
|
|
122
|
+
- **A check gives your screen back.** An app the tool opens comes to the front once,
|
|
123
|
+
because that first appearance is how you see what is happening; from the moment
|
|
124
|
+
you pick something else, anything the tool launched loses the argument
|
|
125
|
+
permanently. The window it opens is placed beside the app rather than over it,
|
|
126
|
+
and a window that is going to be left up is waited for while one that is going
|
|
127
|
+
away is not.
|
|
128
|
+
- **The store writes whole lines or says plainly that it did not.** A full disk is
|
|
129
|
+
the one storage failure that happens to real people mid-run, and it now reads as
|
|
130
|
+
a full disk rather than as a corrupt record.
|
|
131
|
+
- **Ranking says which of two answers it has.** When distance from the edit cannot
|
|
132
|
+
be worked out, the finding is still reported and still counted, and the reply
|
|
133
|
+
says that its position is a guess rather than letting the order imply a
|
|
134
|
+
confidence nobody has.
|
|
135
|
+
- **The self-check corpus grew again**, including a case where the store itself
|
|
136
|
+
cannot be written to — a machine-shaped failure that has to come back as "no
|
|
137
|
+
answer" rather than as a clean run.
|
|
138
|
+
- **The scratch checkout a causal proof makes is put away, and says so plainly when
|
|
139
|
+
it will not go.** A file that vanished between git listing it and the copy
|
|
140
|
+
reaching it is not a failure; a directory that will not delete is, and the two
|
|
141
|
+
used to share one empty catch.
|
|
142
|
+
|
|
143
|
+
## [0.6.2] — 2026-08-30
|
|
144
|
+
|
|
145
|
+
Something to watch it with, and the first proof on a real product that somebody
|
|
146
|
+
else built.
|
|
147
|
+
|
|
148
|
+
### Added
|
|
149
|
+
|
|
150
|
+
- **A live panel showing what the tool now is.** Version 1's panel showed screens
|
|
151
|
+
and pictures, because that is what version 1 did. This one shows a product being
|
|
152
|
+
proven unchanged: which surface each journey walks, which build it is being
|
|
153
|
+
measured against and how that build was chosen, addresses ticking up as they are
|
|
154
|
+
watched, how much wobble was measured and subtracted, the findings that survived,
|
|
155
|
+
what was **not** checked, and the one or two things only a person may decide. The
|
|
156
|
+
wobble figure is given room on purpose — it is the number that explains why the
|
|
157
|
+
tool is quiet.
|
|
158
|
+
- **A focus guard, so watching it work never costs you your screen.** An app the
|
|
159
|
+
tool opens is allowed to come to the front once, because that first appearance is
|
|
160
|
+
how you see what is happening. From the moment you pick something else, anything
|
|
161
|
+
the tool launched loses the argument permanently. There is no flag for it and
|
|
162
|
+
nothing to configure: an Electron app calls `focus()` from its own main process, a
|
|
163
|
+
simulator activates when it boots, a browser activates when a window opens, and
|
|
164
|
+
none of that goes through us — so the only thing that works is watching who is in
|
|
165
|
+
front and handing the screen straight back. It learns which application is yours
|
|
166
|
+
by watching what you choose, never by being told, and it says nothing at all
|
|
167
|
+
unless it actually had to act. Measured on a real desktop app: five grabs over
|
|
168
|
+
five seconds, five handed straight back.
|
|
169
|
+
|
|
170
|
+
### Fixed
|
|
171
|
+
|
|
172
|
+
- Three rendering defects, each of which would have told somebody something untrue:
|
|
173
|
+
a verdict trimmed twice, so a run with four findings — two of them sealed —
|
|
174
|
+
announced *"Everything that worked still works"*; a build that lost its name on a
|
|
175
|
+
second trim and became "an unnamed build"; and a `\b` inside a template literal
|
|
176
|
+
eaten before the regular expression saw it, so permanent coverage gaps were
|
|
177
|
+
painted as though a person could act on them.
|
|
178
|
+
- **A check that finished before its window opened waited out a twenty-second
|
|
179
|
+
timeout.** The check never waited on the panel, but stopping did: a 2.2 second
|
|
180
|
+
check took 20.7 seconds of wall time. Opening is cancellable now and a window that
|
|
181
|
+
arrives late closes itself. A 100ms check ends in 277ms.
|
|
182
|
+
|
|
183
|
+
### Proven
|
|
184
|
+
|
|
185
|
+
- On a real product that somebody else built: a reference cut at **15,147
|
|
186
|
+
addresses** across 17 journeys, two agent-shaped breaks caught in 12.6 seconds
|
|
187
|
+
with the break far from the edit correctly outranking the one inside it, and
|
|
188
|
+
silence restored when both were undone. Its iPhone app built and driven in a
|
|
189
|
+
simulator of the tool's own; its Android app built and driven on a throwaway
|
|
190
|
+
emulator.
|
|
191
|
+
- With the panel open throughout: same verdict, same 1,776 addresses, 110 events
|
|
192
|
+
pushed, 104 delivered, none dropped. Closing it mid-run changes nothing.
|
|
193
|
+
|
|
7
194
|
## [0.6.1] — 2026-08-30
|
|
8
195
|
|
|
9
196
|
Documentation only, and it is a correction rather than a polish. The README
|
package/README.md
CHANGED
|
@@ -138,8 +138,10 @@ pretend otherwise.
|
|
|
138
138
|
|
|
139
139
|
| | State |
|
|
140
140
|
| --- | --- |
|
|
141
|
-
| Picture checks, guards, walk, markers, flake register
|
|
142
|
-
|
|
|
141
|
+
| Picture checks, guards, walk, markers, flake register | **Shipped.** Published as `staysfixed` 0.3.x and in use. Reached from `staysfixed check --pictures` and `--guards`, and over MCP with `staysfixed mcp --v1`. |
|
|
142
|
+
| The MCP server an agent wires up — seven tools, no door marked approve | **Works,** and it is what `staysfixed mcp` serves. It served version 1's picture tools until 0.7.0, which meant every word written about the engine over MCP was true of code no client could reach. |
|
|
143
|
+
| `staysfixed init` reading the repository and writing settings you can read | **Works,** and it is what `staysfixed init` runs. Also wired up in 0.7.0, for the same reason. |
|
|
144
|
+
| `doctor` describing this machine in plain English and as JSON | **Shipped.** Three of its probes were wrong about the machine it was run on until 0.7.0 — see [the silences](#the-silences-that-were-found-and-closed). |
|
|
143
145
|
| The difference engine — the address space, normalisation, wobble subtraction, clustering, ranking, causal proof, the store, the MCP tools, the self-check corpus | **Works.** |
|
|
144
146
|
| Command-line tools, libraries and HTTP servers | **Works.** |
|
|
145
147
|
| Reading the contract straight out of your source — routes, exports, message channels — without running anything | **Works.** 5,785 doors read out of one desktop app in 1.4 seconds. |
|
|
@@ -155,8 +157,14 @@ pretend otherwise.
|
|
|
155
157
|
|
|
156
158
|
`staysfixed check` is the front door for both. Version 1's flags still mean
|
|
157
159
|
exactly what they meant yesterday — `--pictures`, `--guards`, `--watch` and
|
|
158
|
-
`--only` reach the same code they always did
|
|
159
|
-
|
|
160
|
+
`--only` reach the same code they always did, on the settings you already have,
|
|
161
|
+
and `staysfixed mcp --v1` still serves the picture tools. Nobody who installed
|
|
162
|
+
this last week has to change anything.
|
|
163
|
+
|
|
164
|
+
The one thing that did change: `staysfixed init` now writes settings for the
|
|
165
|
+
difference engine. On a product with no screen those settings have no `app` block
|
|
166
|
+
in them, and the picture commands say so plainly rather than telling you to invent
|
|
167
|
+
a web address. Add an `app` block yourself if you want picture checks too.
|
|
160
168
|
|
|
161
169
|
## How it proves nothing changed
|
|
162
170
|
|
|
@@ -284,7 +292,7 @@ What this run did not check
|
|
|
284
292
|
|
|
285
293
|
391 of the 452 ways into this product have never been walked through.
|
|
286
294
|
A break behind any of them is invisible to this tool. Point a journey at
|
|
287
|
-
them
|
|
295
|
+
them - name the steps in a journeys file and pass it with --journeys.
|
|
288
296
|
```
|
|
289
297
|
|
|
290
298
|
A **door** is any way into your product the source declares: an HTTP route, an
|
|
@@ -332,11 +340,13 @@ ACTUALLY COMPARED`**, it is not a pass, and it exits non-zero.
|
|
|
332
340
|
|
|
333
341
|
### The silences that were found and closed
|
|
334
342
|
|
|
335
|
-
Every one of these
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
343
|
+
Every one of these left something invisible while the answer looked complete —
|
|
344
|
+
most of them a clean-looking run, and the last four an honest-looking description
|
|
345
|
+
of a machine. They are listed because a tool like this earns trust by naming the
|
|
346
|
+
ways it has been wrong, not by claiming it never was. All of them were found on
|
|
347
|
+
2026-08-29 and 2026-08-30 by reading the whole thing looking for the same shape
|
|
348
|
+
as the first one, and each has a case in the corpus, a test, or a probe rewritten
|
|
349
|
+
to ask the question a different way.
|
|
340
350
|
|
|
341
351
|
| It used to | Now |
|
|
342
352
|
| --- | --- |
|
|
@@ -350,6 +360,10 @@ one, and each has a case in the corpus or a test holding it shut.
|
|
|
350
360
|
| Subtract the new build's own wobble even when the wobble had swallowed the comparison — a second run that fell over makes almost every address unsteady, everything is dropped before it is compared, and the run ends "nothing that already worked has changed" | A build that disagrees with itself about most of its own addresses gets **no verdict**: the run says NO ANSWER FROM THIS RUN, in those words, and is not a pass |
|
|
351
361
|
| Keep the **first** of two facts written at one address and ignore the second, so a door that broke behind a duplicated address could never be compared with anything. The detector for this was written on day one and never called | Every walk is checked for it, and each clash is named in the coverage: which address, and what the ignored answer was |
|
|
352
362
|
| Skip a folder it could not open **while looking for routes**, and every route behind it — the same bug as the one above, in a second place, still silent | Names the folder, and the routes behind it are reported as unread rather than as absent |
|
|
363
|
+
| Serve version **1**'s picture tools when an agent wired up `npx staysfixed mcp` exactly as this page says to. Everything written about the difference engine over MCP was true of code no client could reach | `staysfixed mcp` serves the seven tools above. Version 1's are behind `--v1` |
|
|
364
|
+
| 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
|
+
| 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
|
+
| 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 |
|
|
353
367
|
|
|
354
368
|
---
|
|
355
369
|
|
|
@@ -522,6 +536,15 @@ opts in, so the agent never sees a door to push on. An agent that could bless it
|
|
|
522
536
|
own results would edit the code, notice something moved, approve it, and report
|
|
523
537
|
success, and your safety net would have become a rubber stamp.
|
|
524
538
|
|
|
539
|
+
Every tool in that list also carries a short title and the protocol's own flags
|
|
540
|
+
for what it does to your machine, so a client can tell a question from an action
|
|
541
|
+
without reading this page: `staysfixed_capabilities`, `staysfixed_explain` and
|
|
542
|
+
`staysfixed_coverage` are marked read-only, and none of the seven is marked as
|
|
543
|
+
reaching the outside world, because none of them does.
|
|
544
|
+
|
|
545
|
+
`staysfixed mcp` serves the tools above. The version 1 picture tools are still
|
|
546
|
+
there behind `staysfixed mcp --v1` for anybody who wired those up, unchanged.
|
|
547
|
+
|
|
525
548
|
Full wiring for every client: [docs/mcp.md](docs/mcp.md).
|
|
526
549
|
|
|
527
550
|
### Nothing here should need a human to read documentation
|
|
@@ -611,15 +634,35 @@ replays recorded traffic and never reaches the real world.
|
|
|
611
634
|
|
|
612
635
|
## Settings
|
|
613
636
|
|
|
614
|
-
|
|
615
|
-
`
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
637
|
+
**You should not have to write these.** `staysfixed init` reads the repository —
|
|
638
|
+
`package.json`, the lockfile, the framework config, the folder shapes, the built
|
|
639
|
+
artifacts, an `.app`, an `.xcodeproj`, a `gradlew`, a Dockerfile, the test runner,
|
|
640
|
+
and every route, exported name and message channel written in the source — works
|
|
641
|
+
out what the repository actually makes, and writes settings with an explanation
|
|
642
|
+
beside every option. It never overwrites a file you already have. `staysfixed init
|
|
643
|
+
--json` is the same answer as one object, which is what an agent should read.
|
|
644
|
+
|
|
645
|
+
A repository usually makes more than one thing, and a list of four is normal: one
|
|
646
|
+
repository producing a desktop app, an iPhone app, an Android app and a website is
|
|
647
|
+
the case this was built against.
|
|
648
|
+
|
|
649
|
+
What it writes names the product and one block per kind of thing it found —
|
|
650
|
+
`process` for commands, `http` for a server, `web` for a website, `electron`,
|
|
651
|
+
`android`, `ios`, `windows`. **A product with no screen has no `app` block, and
|
|
652
|
+
that is correct**: the picture commands (`status`, `walk`, `approve`, `mark`,
|
|
653
|
+
`trace`, `check --pictures`) work by opening something and photographing it, so
|
|
654
|
+
they need an `app` and say so plainly if there is not one. `staysfixed check`
|
|
655
|
+
needs no `app` at all.
|
|
656
|
+
|
|
657
|
+
A `staysfixed.config.json` works too, with a declarative `steps` form and no
|
|
658
|
+
functions — so a Rust, Python or Go project can use the tool without anybody
|
|
659
|
+
writing JavaScript.
|
|
660
|
+
|
|
661
|
+
Three fully commented examples are in [`examples/`](examples/):
|
|
620
662
|
[a web app](examples/staysfixed.config.web.js),
|
|
621
663
|
[an Electron app](examples/staysfixed.config.electron.js), and
|
|
622
|
-
[a guard](examples/guards/the-sidebar-still-collapses.js).
|
|
664
|
+
[a guard](examples/guards/the-sidebar-still-collapses.js). All three are written
|
|
665
|
+
for the picture check, which is the half that needs an `app`.
|
|
623
666
|
|
|
624
667
|
The full reference lives with the code it configures, and the design behind all
|
|
625
668
|
of it is in [docs/how-v2-works.md](docs/how-v2-works.md).
|
|
@@ -632,15 +675,26 @@ A tool that reports "nothing changed" looks exactly like a tool that is broken,
|
|
|
632
675
|
and there is no way to tell the two apart from the outside. So:
|
|
633
676
|
|
|
634
677
|
**It has to prove it still catches things.** `staysfixed check --selfcheck`
|
|
635
|
-
builds
|
|
636
|
-
uncommitted change on top, which is the shape an agent actually points this
|
|
637
|
-
at — and requires the engine to behave on every one.
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
678
|
+
builds seventeen tiny products — each a real repository with a working commit and
|
|
679
|
+
an uncommitted change on top, which is the shape an agent actually points this
|
|
680
|
+
tool at — and requires the engine to behave on every one.
|
|
681
|
+
|
|
682
|
+
- **Twelve are breaks it must catch**: a route that starts failing, a field
|
|
683
|
+
dropped from a reply, a different exit code, a file that is no longer written, a
|
|
684
|
+
door removed from a desktop app, a total quietly rounded, a break buried in the
|
|
685
|
+
middle of a huge output, a value that used to be steady and is now random, a
|
|
686
|
+
crash that only shows in what the program said, a charge that moved where
|
|
687
|
+
nothing in the address mentions money, a run that could only compare half of
|
|
688
|
+
itself and has to say which half, and a run whose own record could not be saved.
|
|
689
|
+
- **Four are the other half of the same promise**: pairs that must produce **no
|
|
690
|
+
findings at all** — two identical builds, a product that wobbles, a build that
|
|
691
|
+
takes ten times longer, and a journey nothing could walk, which has to be named
|
|
692
|
+
as a hole rather than counted as clean. A tool that cries wolf gets switched
|
|
693
|
+
off, and a tool that is switched off catches nothing.
|
|
694
|
+
- **One is the third kind**: a product so unsteady that the comparison is thrown
|
|
695
|
+
away before it happens, where the only correct answer is that this run says
|
|
696
|
+
**nothing** — and saying "nothing changed" there is the worst thing the tool can
|
|
697
|
+
do.
|
|
644
698
|
|
|
645
699
|
**And it has to be honest when it cannot tell.** A case that misbehaves is built
|
|
646
700
|
again from scratch and run again before that becomes an accusation. Fail twice
|
|
@@ -654,9 +708,10 @@ nothing on a busy laptop. The cause was found and removed — see
|
|
|
654
708
|
the next machine-shaped thing to creep in lands as "nobody knows" rather than as
|
|
655
709
|
a false accusation people learn to ignore. Measured on 2026-08-30: eleven of
|
|
656
710
|
eleven, three times running, with the project's own suite running in parallel and
|
|
657
|
-
the machine's load average between 227 and 334;
|
|
658
|
-
|
|
659
|
-
and the load average between 208 and 343
|
|
711
|
+
the machine's load average between 227 and 334; then twelve of twelve after the
|
|
712
|
+
second sweep of silences, three times running, with the suite in parallel again
|
|
713
|
+
and the load average between 208 and 343; and finally **seventeen of seventeen**,
|
|
714
|
+
with the suite in parallel, at the end of the same day.
|
|
660
715
|
|
|
661
716
|
**The unstable app.** `fixtures/unstable-app` is a page built to be impossible to
|
|
662
717
|
observe consistently: a clock ticking ten times a second, an endless spinner, a
|
|
@@ -729,26 +784,32 @@ Honestly, so you know before you invest an afternoon.
|
|
|
729
784
|
when you ship. What they cannot do is read the agent's mind. The five sealed
|
|
730
785
|
classes are the answer to that: in the places where being wrong is expensive,
|
|
731
786
|
no judgement is accepted from any agent at all.
|
|
732
|
-
- **Real phones cannot be paired
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
787
|
+
- **Real phones cannot be paired, and the phone surfaces are the newest thing
|
|
788
|
+
here.** No paired run is possible on a device in your hand: two builds cannot
|
|
789
|
+
exist on one handset at once. Real iPhones and real Android handsets fall back
|
|
790
|
+
to comparing against the stored record, and say so out loud on every run. The
|
|
791
|
+
emulator and the simulator are the honest answer, and both of them compare
|
|
792
|
+
against the stored record too — for Android because two emulator snapshots
|
|
793
|
+
restoring byte-identically is unproven, and for iOS because a paired run costs
|
|
794
|
+
two `xcodebuild` passes and belongs before a release rather than after every
|
|
795
|
+
edit. If you ship a phone app, ask `doctor` what it is actually covering before
|
|
796
|
+
you trust a clean result.
|
|
737
797
|
- **It is not every possible state.** "Deep" means every door the code exposes and
|
|
738
|
-
every journey
|
|
739
|
-
|
|
740
|
-
|
|
798
|
+
every journey it was given. Nothing can enumerate every state, and any tool
|
|
799
|
+
claiming otherwise is lying. The coverage ledger names the doors it has never
|
|
800
|
+
opened, so the hole is visible instead of pretended away.
|
|
801
|
+
- **Your own test suite is not walked for you yet.** The code that harvests a
|
|
802
|
+
project's existing tests as journeys is written and tested in
|
|
803
|
+
`src/v2/journeys/`, and nothing on the check path calls it. Ask for it —
|
|
804
|
+
`--journeys suite`, or `journeys: "suite"` over MCP — and you are told that, by
|
|
805
|
+
name, rather than being given a clean result about steps it silently chose
|
|
806
|
+
instead. Today the steps come from what each adapter reads out of your source
|
|
807
|
+
and from a journeys file you point it at.
|
|
741
808
|
- **No hosted service, no dashboard, no accounts, no teams, nothing paid.** It is
|
|
742
809
|
a command and a folder of files in your repository.
|
|
743
810
|
- **Pictures still do not travel between operating systems.** Text is drawn
|
|
744
811
|
differently on every system. Pixels are evidence now rather than the accusation,
|
|
745
812
|
which makes this matter far less than it did — but it has not gone away.
|
|
746
|
-
- **Phones cannot be paired, and the iPhone is not covered yet.** Android runs on
|
|
747
|
-
an emulator against the stored record, which is weaker than a paired run and
|
|
748
|
-
says so every time. The iOS simulator is designed and not built. If you ship a
|
|
749
|
-
phone app, ask `doctor` what it is actually covering before you trust a clean
|
|
750
|
-
result — it will tell you plainly rather than let a green run mean less than it
|
|
751
|
-
looks like.
|
|
752
813
|
- **Native Windows shows one desktop, so two builds cannot run at once even in
|
|
753
814
|
principle.** Runs are one after the other and the same-minute guarantee is
|
|
754
815
|
weaker there than on any platform. If your Windows product is Electron — most
|