staysfixed 0.7.1 → 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 CHANGED
@@ -4,6 +4,28 @@ 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
+
7
29
  ## [0.7.1] — 2026-08-30
8
30
 
9
31
  Found by installing 0.7.0 from npm into an empty folder and running it the way a
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.1",
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
- const text = readFileSync(configFile, 'utf8');
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
- settings = readFileSync(configFile, 'utf8');
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
  }