staysfixed 0.7.0 → 0.7.2
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 +42 -0
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/v2/doctor.js +60 -2
- package/src/v2/init.js +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,43 @@ 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.2] — 2026-08-30
|
|
8
|
+
|
|
9
|
+
Found the same way as 0.7.1, one step further along: install from npm into an
|
|
10
|
+
empty folder, run `init`, then run `doctor` on what `init` just wrote.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`doctor` read the commented-out examples in a settings file as settings.**
|
|
15
|
+
`staysfixed init` comments out every option that does not apply to your
|
|
16
|
+
project rather than leaving it out, so nothing is hidden from the person
|
|
17
|
+
reading the file — and `doctor` searched the raw text. On a folder holding one
|
|
18
|
+
script and nothing else it announced *"Electron desktop apps: **Covered.** It
|
|
19
|
+
opens release/mac-arm64/Your App.app"*, and an Android app beside it, both read
|
|
20
|
+
out of comments and both false.
|
|
21
|
+
|
|
22
|
+
A surface reported as covered when nothing will ever be walked on it is the
|
|
23
|
+
worst answer this tool can give, because every clean result after it is
|
|
24
|
+
believed. Comments are taken away before anything is read out of the file now,
|
|
25
|
+
with strings respected so an address keeps its two slashes — and the file is
|
|
26
|
+
still never loaded, because a settings file may be JavaScript and doctor must
|
|
27
|
+
not run somebody's code to answer a question about their machine.
|
|
28
|
+
|
|
29
|
+
## [0.7.1] — 2026-08-30
|
|
30
|
+
|
|
31
|
+
Found by installing 0.7.0 from npm into an empty folder and running it the way a
|
|
32
|
+
stranger would, which is the only way this was ever going to be found.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- **`init` asked for a build step on a script that runs straight from source.** A
|
|
37
|
+
plain Node command-line tool is recorded as "not built", because there is
|
|
38
|
+
nothing to build — and that was read as "it has not been built yet". So a fresh
|
|
39
|
+
install told its owner to go and name the command that builds a file sitting
|
|
40
|
+
right there, in the same breath as offering to run it. Being sent shopping for
|
|
41
|
+
nothing is the fastest way to make somebody stop reading the page, and this
|
|
42
|
+
project's README promises it never happens. A test holds it shut.
|
|
43
|
+
|
|
7
44
|
## [0.7.0] — 2026-08-30
|
|
8
45
|
|
|
9
46
|
The release where the front door was found to be locked.
|
|
@@ -81,6 +118,11 @@ printed, which is the only way this kind of mistake is ever found.
|
|
|
81
118
|
- **It reported Docker as present because the command was on the path**, on a
|
|
82
119
|
machine where Docker Desktop was shut and nothing it promises would have worked.
|
|
83
120
|
It asks the engine for its version now.
|
|
121
|
+
- **It said an iPhone app "cannot be done here at all" on a Mac with Xcode and
|
|
122
|
+
three simulator runtimes on it.** The real reason was that this repository has
|
|
123
|
+
no iPhone app in it, which is not a limit of the machine. Those two reasons are
|
|
124
|
+
two different sentences now, and the object carries
|
|
125
|
+
`surfaces[].notInThisProject` so an agent can tell them apart too.
|
|
84
126
|
- No probe in `doctor` uses a shell variable or a loop any more. One machine in
|
|
85
127
|
this config reaches Windows through an OpenSSH server that hands the command down
|
|
86
128
|
through a second shell, and every `$p` is expanded to nothing before the shell
|
package/README.md
CHANGED
|
@@ -364,6 +364,8 @@ to ask the question a different way.
|
|
|
364
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
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
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 |
|
|
367
|
+
| 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 |
|
|
368
|
+
| 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 |
|
|
367
369
|
|
|
368
370
|
---
|
|
369
371
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "staysfixed",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Prove that what already worked still works after an agent changed the code. Picture checks, guards for fixed bugs, a pre-release walkthrough, and known-good markers — as a CLI and as an MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/v2/doctor.js
CHANGED
|
@@ -668,6 +668,59 @@ function hasModule(cwd, name) {
|
|
|
668
668
|
}
|
|
669
669
|
}
|
|
670
670
|
|
|
671
|
+
/**
|
|
672
|
+
* The settings file with everything commented out taken away.
|
|
673
|
+
*
|
|
674
|
+
* `staysfixed init` writes a settings file whose whole point is that the options which do
|
|
675
|
+
* NOT apply to your project are commented out rather than left out, so nothing is hidden
|
|
676
|
+
* from the person reading it. Doctor reads that file as TEXT — never by loading it, because
|
|
677
|
+
* a settings file may be JavaScript and doctor must not run somebody's code to answer a
|
|
678
|
+
* question about their machine — and it used to search the raw text.
|
|
679
|
+
*
|
|
680
|
+
* Which means it found the examples. On a folder containing one `cli.js` and nothing else,
|
|
681
|
+
* doctor announced "Electron desktop apps: Covered. It opens release/mac-arm64/Your App.app"
|
|
682
|
+
* and "An Android app is here", both read out of commented-out lines, and both false. A
|
|
683
|
+
* surface reported as covered when nothing will ever be walked on it is the worst answer
|
|
684
|
+
* this tool can give.
|
|
685
|
+
*
|
|
686
|
+
* Strings are respected, so an address like "http://localhost:3000" survives: the two
|
|
687
|
+
* slashes inside it are not the start of a comment.
|
|
688
|
+
*
|
|
689
|
+
* @param {string} text
|
|
690
|
+
* @returns {string} The same text with comments blanked, line numbering unchanged.
|
|
691
|
+
*/
|
|
692
|
+
export function withoutComments(text) {
|
|
693
|
+
let out = '';
|
|
694
|
+
/** @type {"code"|"line"|"block"|"'"|'"'|'`'} */
|
|
695
|
+
let mode = 'code';
|
|
696
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
697
|
+
const c = text[i];
|
|
698
|
+
const next = text[i + 1];
|
|
699
|
+
if (mode === 'code') {
|
|
700
|
+
if (c === '/' && next === '/') { mode = 'line'; out += ' '; i += 1; continue; }
|
|
701
|
+
if (c === '/' && next === '*') { mode = 'block'; out += ' '; i += 1; continue; }
|
|
702
|
+
if (c === "'" || c === '"' || c === '`') mode = /** @type {any} */ (c);
|
|
703
|
+
out += c;
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
if (mode === 'line') {
|
|
707
|
+
if (c === '\n') { mode = 'code'; out += c; continue; }
|
|
708
|
+
out += ' ';
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
if (mode === 'block') {
|
|
712
|
+
if (c === '*' && next === '/') { mode = 'code'; out += ' '; i += 1; continue; }
|
|
713
|
+
out += c === '\n' ? c : ' ';
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
// Inside a string. A backslash escapes whatever comes next, quote included.
|
|
717
|
+
if (c === '\\') { out += c + (next ?? ''); i += 1; continue; }
|
|
718
|
+
if (c === mode) mode = 'code';
|
|
719
|
+
out += c;
|
|
720
|
+
}
|
|
721
|
+
return out;
|
|
722
|
+
}
|
|
723
|
+
|
|
671
724
|
/**
|
|
672
725
|
* Is there a desktop app in this project to check?
|
|
673
726
|
*
|
|
@@ -687,7 +740,10 @@ function findDesktopApp(cwd) {
|
|
|
687
740
|
const configFile = findConfigFile(cwd);
|
|
688
741
|
if (configFile) {
|
|
689
742
|
try {
|
|
690
|
-
|
|
743
|
+
// Comments taken away first. See `withoutComments`: the examples in a settings file
|
|
744
|
+
// are commented out on purpose, and reading them as settings reported a desktop app
|
|
745
|
+
// in a folder that contains one script.
|
|
746
|
+
const text = withoutComments(readFileSync(configFile, 'utf8'));
|
|
691
747
|
const named = /["']?binary["']?\s*:\s*["'`]([^"'`]+)["'`]/.exec(text);
|
|
692
748
|
if (named) return { where: named[1], how: 'your settings name it under app.binary' };
|
|
693
749
|
if (/["']?kind["']?\s*:\s*["'`]electron["'`]/.test(text)) {
|
|
@@ -761,7 +817,9 @@ async function phoneApps(root, configFile) {
|
|
|
761
817
|
let settings = '';
|
|
762
818
|
if (configFile) {
|
|
763
819
|
try {
|
|
764
|
-
|
|
820
|
+
// Comments taken away first, for the same reason `findDesktopApp` does it: a
|
|
821
|
+
// commented-out `apk:` line is an example, not an Android app.
|
|
822
|
+
settings = withoutComments(readFileSync(configFile, 'utf8'));
|
|
765
823
|
} catch {
|
|
766
824
|
settings = '';
|
|
767
825
|
}
|
package/src/v2/init.js
CHANGED
|
@@ -432,7 +432,17 @@ function productNeeds(product, project) {
|
|
|
432
432
|
// A command-line program that has to be built before it can be run. This is what a product
|
|
433
433
|
// nothing in package.json names looks like on a fresh clone: the source is there, the
|
|
434
434
|
// program is real, and the file that would be run does not exist yet.
|
|
435
|
-
|
|
435
|
+
//
|
|
436
|
+
// `built.found === false` is NOT that on its own, and reading it as though it were sent a
|
|
437
|
+
// plain Node command-line tool's owner shopping for a build step it does not have. A
|
|
438
|
+
// script that runs straight from source is recorded as `found: false, how: "nothing to
|
|
439
|
+
// build — it runs from source"`, and this file used to ask a person to "name the command
|
|
440
|
+
// that builds it" about a file that was sitting right there and that the very same run
|
|
441
|
+
// had already worked out how to run. Two signals rule it out, and either is enough: the
|
|
442
|
+
// detector saying there is nothing to build, and a command already worked out for it.
|
|
443
|
+
const nothingToBuild = /nothing to build/i.test(String(product.built.how ?? ''));
|
|
444
|
+
const alreadyRunnable = Array.isArray(suggest.commands) && suggest.commands.length > 0;
|
|
445
|
+
if (product.kind === 'cli' && !product.built.found && !nothingToBuild && !alreadyRunnable) {
|
|
436
446
|
const build = typeof suggest.buildWith === 'string' ? String(suggest.buildWith) : null;
|
|
437
447
|
needs.push({
|
|
438
448
|
what: `${product.name}, built`,
|