staysfixed 0.7.2 → 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 +342 -0
- package/README.md +191 -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 +285 -45
- 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/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,348 @@ 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
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
Nothing yet.
|
|
10
|
+
|
|
11
|
+
## [0.8.0] — 2026-08-30
|
|
12
|
+
|
|
13
|
+
The night this was pointed at itself. Everything below was found the same way:
|
|
14
|
+
the published build was installed the way a stranger installs it, and used on
|
|
15
|
+
throwaway products — a plain-Node server, an Express API, a Flask app, a static
|
|
16
|
+
site, a Vite app, a bare Gradle project, an empty Xcode project, a CLI that
|
|
17
|
+
reaches the internet three ways — until it said something untrue. Reading the
|
|
18
|
+
code found none of it.
|
|
19
|
+
|
|
20
|
+
**Two of them were the tool lying about itself**, which is the worst thing this
|
|
21
|
+
product can do: it told every agent that asked that it could read web pages and
|
|
22
|
+
then could not open one, and it reported a clean run over an entire uncovered
|
|
23
|
+
HTTP surface. Both are closed.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **`staysfixed init` now runs version 2's setup.** It ran version 1's picture
|
|
28
|
+
setup until now. Version 2's `init` had been written and tested and was wired
|
|
29
|
+
to nothing — the command table deliberately left it out, on the grounds that
|
|
30
|
+
somebody might have `staysfixed init` in a setup script and would get a
|
|
31
|
+
different file. True, and not a reason: version 1's `init` writes settings for
|
|
32
|
+
photographing screens, which is not what this tool does any more, so a new
|
|
33
|
+
project got the wrong shape of file and was told it was set up. `init --json`
|
|
34
|
+
now answers with the fields `docs/getting-started.md` tells an agent to read —
|
|
35
|
+
`plan.project`, `plan.readiness`, `plan.needs.person`, `plan.journeys`,
|
|
36
|
+
`plan.covers.short`.
|
|
37
|
+
|
|
38
|
+
- **`staysfixed check --watch` opens version 2's panel, not version 1's.** This
|
|
39
|
+
is the one flag whose meaning moved, and it is written down here because
|
|
40
|
+
nothing else said so. The panel draws what a difference engine finds —
|
|
41
|
+
journeys, wobble, findings, a verdict — where version 1's drew approved
|
|
42
|
+
pictures, which this tool no longer has. `--pictures --watch` and `--guards
|
|
43
|
+
--watch` still open version 1's panel over version 1's run, so the only person
|
|
44
|
+
whose command changed meaning is the one who typed `--watch` on its own.
|
|
45
|
+
|
|
46
|
+
- **The scratch copy is cloned, not copied.** Every run works in a throwaway copy
|
|
47
|
+
of the project so it can write anywhere it likes without touching the real one,
|
|
48
|
+
and that copy used to be made byte by byte. On the project this was built
|
|
49
|
+
against — twelve gigabytes, most of it an iOS build folder — that is twelve
|
|
50
|
+
gigabytes of real disk per build and twenty-four per check, before a single
|
|
51
|
+
journey is walked. It now asks the filesystem to clone instead: on a Mac that is
|
|
52
|
+
one call per file that copies no bytes at all and shares the blocks until
|
|
53
|
+
something writes to them, and on Linux the same where the filesystem supports
|
|
54
|
+
it. Measured on that tree: **41.5 seconds and no bytes moved.** It falls back to
|
|
55
|
+
a real copy wherever cloning is not available, so a filesystem without it is
|
|
56
|
+
slower here and never wrong. Never a symlink and never a hardlink — both point
|
|
57
|
+
back at the real project, which is the one thing the copy exists to protect.
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
|
|
61
|
+
- **`process.skip` — folders to leave out of that copy.** A project can name
|
|
62
|
+
folders that are not worth copying, and a copy that takes over twenty seconds
|
|
63
|
+
now says so and points at the setting. **The bar is deliberately high: only
|
|
64
|
+
things regenerated on demand and read by nothing.** Anything skipped that turns
|
|
65
|
+
out to matter makes a run pass for the wrong reason, and a false pass is the one
|
|
66
|
+
failure this whole tool exists to prevent. It can only ever add to the built-in
|
|
67
|
+
list — `.git`, `.staysfixed`, `.turbo`, `.nyc_output`, `coverage`,
|
|
68
|
+
`.pytest_cache`, `__pycache__`, `.DS_Store` — because a setting that could
|
|
69
|
+
switch one of those back on would only ever make runs slower.
|
|
70
|
+
|
|
71
|
+
- **`docs/settings.md` — every option, and what each surface needs on the machine.**
|
|
72
|
+
None of the roughly ninety settings keys the adapters read was written down
|
|
73
|
+
anywhere: the README said the reference "lives with the code it configures",
|
|
74
|
+
which is no help at all to somebody who is not going to read the code. Nor was
|
|
75
|
+
anything the code needs installed: the exact commands for Xcode, the iOS
|
|
76
|
+
runtime, the Android SDK and its emulator, and the two separate ways a browser
|
|
77
|
+
can be missing were all sitting in the source as `howToGet` strings and in none
|
|
78
|
+
of the documentation. It also names the things that quietly cost you coverage —
|
|
79
|
+
an Android Play Store image, which refuses root forever so the files an app
|
|
80
|
+
writes cannot be seen; a `web.start` or `http.start` that hard-codes its port; a
|
|
81
|
+
Windows desktop nobody is signed in to.
|
|
82
|
+
|
|
83
|
+
- **The README now lists the silences that are still open,** beside the table of
|
|
84
|
+
the ones that were found and closed. A table of closed bugs on its own reads as
|
|
85
|
+
though nothing is left. Twelve rows, each with the file that would close it.
|
|
86
|
+
|
|
87
|
+
- **`staysfixed browsers` is reachable.** It was written, tested, given a finished
|
|
88
|
+
command entry with a comment saying "wiring it up is one line" — and that line
|
|
89
|
+
was never written. README.md told people to run `npx staysfixed browsers` and
|
|
90
|
+
`npx staysfixed browsers --clean` to clear up after an interrupted run, and both
|
|
91
|
+
answered *"There is no command called browsers"*. Somebody whose disk was
|
|
92
|
+
filling with abandoned browser profiles had no way to clear them and no reason
|
|
93
|
+
to doubt the page telling them there was.
|
|
94
|
+
|
|
95
|
+
### Fixed
|
|
96
|
+
|
|
97
|
+
- **`staysfixed ship --version 0.14.0` printed `0.7.2` and shipped nothing.**
|
|
98
|
+
`--version` is a global flag meaning "print the tool's version", and it is also
|
|
99
|
+
one of `ship`'s own options meaning "the release that went out was called this".
|
|
100
|
+
The two lists were concatenated, the parser reads switches before values, so the
|
|
101
|
+
global one won: the release script got the tool's version number on standard
|
|
102
|
+
output, exit code 0, and no reference cut. A command's own flags now win any name
|
|
103
|
+
they share with a global one.
|
|
104
|
+
|
|
105
|
+
- **`staysfixed walk --no-snap` answered "I do not know the option --no-snap".**
|
|
106
|
+
`--no-snap` — leave both windows where they are instead of putting them side by
|
|
107
|
+
side — was read by `walk` and declared by nobody. It is now in `walk`'s options
|
|
108
|
+
and in its help, so `docs/watching.md` saying both commands take every panel flag
|
|
109
|
+
is true again.
|
|
110
|
+
|
|
111
|
+
- **The very first run on a project answered `ok: true` to a machine.** With no
|
|
112
|
+
build on record there is nothing to compare against, so the run proves nothing.
|
|
113
|
+
It said exactly that, in words, in the summary, and the command line exited 2 on
|
|
114
|
+
it — but `--json` and the MCP reply both carried `ok: true`, with no `blocked`
|
|
115
|
+
and none of the `NOTHING WAS ACTUALLY COMPARED` wording. An agent reads the
|
|
116
|
+
fields, not the sentence, so the two interfaces that matter most reported a pass
|
|
117
|
+
over a run that compared nothing. That is the exact failure this tool exists to
|
|
118
|
+
prevent, produced by the tool itself. `comparedNothing` fired only on
|
|
119
|
+
per-journey gaps, which a true cold start never records; it now fires on an
|
|
120
|
+
empty `reference.id` as well, and the rest of the codebase was swept for the
|
|
121
|
+
same shape — `ok` computed from "were there findings" rather than from "was
|
|
122
|
+
anything compared at all".
|
|
123
|
+
|
|
124
|
+
- **The README said a fresh install pulls two packages under a megabyte.** It
|
|
125
|
+
pulls three: `playwright-core` became a runtime dependency, which is the browser
|
|
126
|
+
driver without a browser in it — about 13MB, and still nothing that takes
|
|
127
|
+
minutes. The install section now says what actually happens, including that a
|
|
128
|
+
browser already on the machine is found and used before anything is downloaded,
|
|
129
|
+
and that a project already using the full `playwright` is never asked for a
|
|
130
|
+
second copy of the same driver.
|
|
131
|
+
|
|
132
|
+
- **`staysfixed check --guards-only` does not exist and never did.**
|
|
133
|
+
`docs/guards.md` had told people to run it since it was written. The flag is
|
|
134
|
+
`--guards`.
|
|
135
|
+
|
|
136
|
+
- **`staysfixed rules` and `staysfixed browsers` were both named in the README and
|
|
137
|
+
neither existed.** `browsers` has been wired up; there is no `rules` command, so
|
|
138
|
+
the README now says where the rules actually are — one file,
|
|
139
|
+
`.staysfixed/rules.json`, read like any other file in a pull request.
|
|
140
|
+
|
|
141
|
+
- **`--watch-width 100` is refused, not quietly widened.** `docs/watching.md` said
|
|
142
|
+
anything under 240 or over 900 was brought back to the nearest of the two. Under
|
|
143
|
+
240 is an error with a message; over 900 is brought down.
|
|
144
|
+
|
|
145
|
+
- **`--profile` prints nothing on a difference-engine check.** It works on
|
|
146
|
+
`staysfixed walk` and on the version 1 picture check. The flag is accepted by the
|
|
147
|
+
new check and the timings are not kept, so `docs/watching.md` no longer claims
|
|
148
|
+
otherwise.
|
|
149
|
+
|
|
150
|
+
- **`staysfixed doctor --json` does not fill the project path into its MCP wiring
|
|
151
|
+
block** — `staysfixed init --json` does. `docs/mcp.md` had said both did.
|
|
152
|
+
|
|
153
|
+
- **`docs/design-v2.md` named four tools that were never used.** It is a plan
|
|
154
|
+
rather than a description and says so at the top, but it is also the only
|
|
155
|
+
per-platform prose in the repository, and it told anybody reading it that
|
|
156
|
+
Android needs Appium, UiAutomator2 and a Java runtime, iOS needs WebDriverAgent
|
|
157
|
+
and Windows needs a .NET probe built on FlaUI. None of those is used. What
|
|
158
|
+
shipped needs none of them, and the section now says so before the list rather
|
|
159
|
+
than after it.
|
|
160
|
+
|
|
161
|
+
- **`--color` was accepted by every command and did nothing.** Colour is settled
|
|
162
|
+
before anything else loads, so by the time a command is parsed the answer is
|
|
163
|
+
already fixed and nothing on the command line could turn it back on. `--no-color`
|
|
164
|
+
works and always did — it is handled first thing, before the logging module
|
|
165
|
+
decides. `--color` has been taken away rather than left as a switch that turns
|
|
166
|
+
nothing on.
|
|
167
|
+
|
|
168
|
+
### Added — products it could not see before
|
|
169
|
+
|
|
170
|
+
- **A hand-written `node:http` server is read.** A product with `createServer`, a
|
|
171
|
+
chain of `req.url === '/x'` and `.listen(PORT)` was reported as "one thing: the
|
|
172
|
+
`notes` command", found zero routes, left the `http` block commented out, and
|
|
173
|
+
said **"a check here covers the `notes` command in full. Nothing is being left
|
|
174
|
+
out."** over an entire uncovered surface. The same product written with Express
|
|
175
|
+
was handled perfectly, so the machinery worked and the reading stopped at
|
|
176
|
+
frameworks. Eight hand-rolled routing shapes are now read — `req.url === '/x'`,
|
|
177
|
+
`.startsWith`, a `switch` on a pathname, `new URL(req.url, base).pathname`,
|
|
178
|
+
`url.parse`, the `split('?')[0]` variant, `if`/`else if` chains, and a path held
|
|
179
|
+
in a constant — and a method is claimed only where the code checks one, `ANY`
|
|
180
|
+
otherwise, never `GET`.
|
|
181
|
+
|
|
182
|
+
- **Python products are checkable.** A Flask app was told "a Python project is not
|
|
183
|
+
being checked… in a language nothing here drives". The honesty was right and the
|
|
184
|
+
conclusion was wrong: the two adapters that would run it read no source at all.
|
|
185
|
+
The process adapter runs a command and compares what it printed, exited with and
|
|
186
|
+
touched; the http adapter boots a server and asks it for routes. Flask, FastAPI
|
|
187
|
+
(including an `APIRouter` prefix) and Django `urlpatterns` are now read, and
|
|
188
|
+
their path parameters become the same "somebody has to give a real value" flow
|
|
189
|
+
every other route uses. Go, Rust, Ruby and PHP get the boot and the command
|
|
190
|
+
without the routes — and every one of them says, **by name**, that the source
|
|
191
|
+
channel is blind for that language, so none can reach "covered in full".
|
|
192
|
+
|
|
193
|
+
- **`--journeys suite` walks the project's own test suite.** It was written,
|
|
194
|
+
tested and reachable by nothing. Each test file runs twice inside the scratch
|
|
195
|
+
copy with the same frozen clock, seed and watcher as everything else, and every
|
|
196
|
+
check is reported by name, along with why each failure failed and a fingerprint
|
|
197
|
+
of the test file itself — so an edited test says plainly that the change is
|
|
198
|
+
yours. It earns its keep: take the penny-rounding out of a `total()` and the
|
|
199
|
+
product's own output does not move by one character, the discovered journeys say
|
|
200
|
+
nothing has changed, and the harvest names the check that turned red. Off unless
|
|
201
|
+
asked for, held to `suite: { budgetMs }` (90 seconds by default, `0` for none),
|
|
202
|
+
and **every file the budget did not reach is named one by one** rather than
|
|
203
|
+
summarised as "some tests were skipped".
|
|
204
|
+
|
|
205
|
+
- **`keepBuilds` — how much of the record is kept.** The store grew for ever: one
|
|
206
|
+
build folder per commit, in your git history, with the function that would have
|
|
207
|
+
stopped it written and called by nothing. Three tiers now, capped by count and
|
|
208
|
+
not by age, and it refuses to remove anything at all if any record could not be
|
|
209
|
+
read.
|
|
210
|
+
|
|
211
|
+
### Fixed — the tool being wrong about itself
|
|
212
|
+
|
|
213
|
+
- **It could not open a single web page, and said it could.** `playwright` was
|
|
214
|
+
dropped from the dependencies on the reasonable-looking grounds that nothing
|
|
215
|
+
under `src/` imported it. Nothing does — the web adapter reaches it through
|
|
216
|
+
`await import()`, which no search for an import statement can see. So every
|
|
217
|
+
website check on a fresh install answered "Playwright is not installed, so no
|
|
218
|
+
web page can be opened", while `staysfixed_capabilities` told the calling agent
|
|
219
|
+
that web apps and sites could be checked *here and now*. The dependency is back
|
|
220
|
+
as `playwright-core` — the driver without a browser in it, 13MB and no download,
|
|
221
|
+
because this tool already knows how to find Chrome for Testing, Chrome, Edge or
|
|
222
|
+
Chromium and deliberately prefers one that is not the browser you use. A test
|
|
223
|
+
holds it in place by name, and holds `doctor` and the walk to the same answer.
|
|
224
|
+
|
|
225
|
+
- **An Android project with no APK built was reported as covered in full,** and
|
|
226
|
+
then could not be walked at all. iOS already named the missing build and the
|
|
227
|
+
exact `xcodebuild` line; Android now names the Gradle one. A Tauri app read as
|
|
228
|
+
ready on any machine with no Windows host, because `doctor` returns no Windows
|
|
229
|
+
needs precisely when there is no Windows to ask about.
|
|
230
|
+
|
|
231
|
+
- **Version 1 passed a page with a letter missing from its heading.** The picture
|
|
232
|
+
check's default allowed 0.05% of a picture through, described in a comment as
|
|
233
|
+
"enough for font hinting noise, nowhere near enough to hide a missing
|
|
234
|
+
stylesheet". On a 2880×1800 screenshot that is **2,592 pixels**; taking one
|
|
235
|
+
letter out of an `h1` moves **593**. The replacement was measured rather than
|
|
236
|
+
chosen — ten fresh takes of the same build differ by **zero** pixels — so the
|
|
237
|
+
default is now zero, and a project that sets `tolerance.pixels` is told, per
|
|
238
|
+
screen, how many pixels its setting just swallowed.
|
|
239
|
+
|
|
240
|
+
- **The network boundary let everything out.** The watcher injected into every run
|
|
241
|
+
read the host off the first argument, but Node normalises `Socket.connect` to
|
|
242
|
+
`[options, callback]` — so the host read as an empty string, which was in the
|
|
243
|
+
loopback allow-list. `fetch`, `http.get` and `net.connect` to the open internet
|
|
244
|
+
all succeeded and the report came back empty, while the tool's loudest safety
|
|
245
|
+
claim said every outbound connection had been refused. All three are now refused
|
|
246
|
+
and recorded by host and port.
|
|
247
|
+
|
|
248
|
+
- **`npm run start` died silently** — the default start command `init` writes for
|
|
249
|
+
every Node server. The watcher wrapped `process.env` in a Proxy with only `get`
|
|
250
|
+
and `has` traps, so `process.env.X = 'y'` quietly failed, npm could not pass
|
|
251
|
+
`npm_lifecycle_event` to the script, and it exited 1 without printing a word.
|
|
252
|
+
Every trap now forwards through `Reflect`, with `set` taking the target as the
|
|
253
|
+
receiver.
|
|
254
|
+
|
|
255
|
+
- **Every route, and every command, read as never walked.** A door is addressed
|
|
256
|
+
`route.<VERB>.<url>` and `cli.<the command>`; the adapters wrote `api.<journey>`
|
|
257
|
+
and `cli.<journey name>`. So the coverage ledger — the one part of this tool
|
|
258
|
+
that must never be optimistic — was instead permanently and wrongly pessimistic,
|
|
259
|
+
and a genuinely uncovered door was invisible in the noise.
|
|
260
|
+
|
|
261
|
+
- **A normalisation-rules caveat fired on every run, for ever.** A per-run
|
|
262
|
+
temporary folder was baked into a rule pattern and collided with the real
|
|
263
|
+
project-root rule, so the rule set contained a fresh random pattern every run —
|
|
264
|
+
and the real rule was silently deleted, meaning absolute paths under your
|
|
265
|
+
checkout were never normalised at all.
|
|
266
|
+
|
|
267
|
+
- **A committed change could not be ranked.** "The change" meant the working tree
|
|
268
|
+
and nothing else, so the moment an agent committed its work — which is what an
|
|
269
|
+
agent does at the end of a task — the distance measure went blind and every
|
|
270
|
+
finding carried "nothing in the working tree has changed, so there is no edit to
|
|
271
|
+
measure this against" over a change that was perfectly well known.
|
|
272
|
+
|
|
273
|
+
- **The scratch copy copied every byte.** On the project this was built against —
|
|
274
|
+
twelve gigabytes, most of it an iOS build folder — that is twelve gigabytes of
|
|
275
|
+
real disk per build and twenty-four per check. It now asks the filesystem to
|
|
276
|
+
clone: **41.5 seconds and no bytes moved**, measured on that tree. Never a
|
|
277
|
+
symlink and never a hardlink, because both point back at the project this exists
|
|
278
|
+
to protect.
|
|
279
|
+
|
|
280
|
+
### Fixed — caps that decided things quietly
|
|
281
|
+
|
|
282
|
+
- **A finding's nearby files were cut to five, and the sealed classes are searched
|
|
283
|
+
in that list.** A finding whose sixth file was `src/billing/refund.js` was
|
|
284
|
+
classified `ordinary` — precisely the class an agent may close on its own.
|
|
285
|
+
|
|
286
|
+
- **A waiver was pinned to the first 40 differences and 20 addresses of a
|
|
287
|
+
finding.** A waiver written over a three-hundred-address finding went on
|
|
288
|
+
covering it after the value at address 41, 150 or 299 became something else.
|
|
289
|
+
|
|
290
|
+
- **A wobble storm needed twelve addresses before it counted,** so ten of eleven
|
|
291
|
+
wobbling was not a storm: ten real differences subtracted, and the run passed.
|
|
292
|
+
|
|
293
|
+
- **Eight address truncations with no fingerprint,** across four files. Two long
|
|
294
|
+
labels, two journey names, two log lines or two checkpoints that agreed for the
|
|
295
|
+
first 60 to 200 characters landed on one address and one of the two answers was
|
|
296
|
+
thrown away at the door. Two of them were screenshot filenames, so the picture
|
|
297
|
+
offered as evidence for a finding could be a photograph of a different screen.
|
|
298
|
+
|
|
299
|
+
- **`trimForStorage` cut in characters against a byte limit.** On box-drawing or
|
|
300
|
+
CJK output, 90,000 bytes in produced 180,061 bytes stored — the whole text twice
|
|
301
|
+
— under a marker claiming 24,464 bytes had been left out of the middle.
|
|
302
|
+
|
|
303
|
+
- **Files over 8MB were compared by a size bracket, not a fingerprint,** so a
|
|
304
|
+
build that wrote a different 40MB file compared equal. They are stream-hashed
|
|
305
|
+
now, and the remaining ceiling is named in the ledger.
|
|
306
|
+
|
|
307
|
+
- **An adapter that did not answer in time made its surface READY.** The race
|
|
308
|
+
resolved an empty needs list, and no needs meant covered — so an Android adapter
|
|
309
|
+
hanging for sixteen seconds produced "Covered against the stored record."
|
|
310
|
+
|
|
311
|
+
### Changed — words a person reads
|
|
312
|
+
|
|
313
|
+
- **The watch window opened on a fifteen-line paragraph.** Its headline was right
|
|
314
|
+
and the engine's entire summary sat underneath it as one grey block. Nothing is
|
|
315
|
+
dropped — the summary is split at sentence ends, two are shown, and the rest sit
|
|
316
|
+
behind one control.
|
|
317
|
+
|
|
318
|
+
- **A finding titled with the tool's own word.** A renamed route read as
|
|
319
|
+
`"declared" is gone`. Twenty-seven such words are now recognised, so it reads
|
|
320
|
+
`"/notes / declared" is gone` instead.
|
|
321
|
+
|
|
322
|
+
- **A sentence that said a thing was now what it already was.** When both sides of
|
|
323
|
+
a change summarised to the same words, the summary was the wrong thing to print.
|
|
324
|
+
The flagship case now reads: *"GET /invoice / body" now has "line" reading "A
|
|
325
|
+
desk lamp ......... 49.99 GBP" where it read "A desk lamp ......... £49.99"*.
|
|
326
|
+
|
|
327
|
+
- **The first error anybody meets writing a guard was raw JavaScript** —
|
|
328
|
+
`page.goto is not a function`. It now says what the object it was handed
|
|
329
|
+
actually has, in one sentence, and suggests a name only when exactly one is
|
|
330
|
+
close.
|
|
331
|
+
|
|
332
|
+
- **The shipped examples were for a shape the tool no longer uses.** Both said
|
|
333
|
+
"copy this to the root of your project"; doing that today gets you a settings
|
|
334
|
+
file the difference engine rejects. Each now says which half it belongs to, and
|
|
335
|
+
points at `staysfixed init` — which beats any example, because it reads the
|
|
336
|
+
project in front of it.
|
|
337
|
+
|
|
338
|
+
### Removed
|
|
339
|
+
|
|
340
|
+
- **Fourteen exports nothing called.** Ten were genuinely dead. Two were missing
|
|
341
|
+
features and are now wired: `treeMovedSince`, the one thing `intent.js` promised
|
|
342
|
+
and nothing checked, and `RUNNER_KINDS`, which validated nothing while an unknown
|
|
343
|
+
runner kind was silently treated as posix. Two were kept and wired because
|
|
344
|
+
deleting them would have quietly broken a promise the documentation makes.
|
|
345
|
+
|
|
346
|
+
- **`ios.suite` was advertised in a message telling somebody how to switch
|
|
347
|
+
something on.** Nothing anywhere read it.
|
|
348
|
+
|
|
7
349
|
## [0.7.2] — 2026-08-30
|
|
8
350
|
|
|
9
351
|
Found the same way as 0.7.1, one step further along: install from npm into an
|