staysfixed 0.11.0 → 0.12.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 +102 -0
- package/README.md +77 -19
- package/docs/design-v2.md +8 -7
- package/docs/getting-started.md +5 -3
- package/docs/how-v2-works.md +33 -10
- package/docs/mcp.md +6 -4
- package/docs/settings.md +11 -2
- package/package.json +1 -1
- package/src/guard/api.js +115 -36
- package/src/v2/adapters/android.js +220 -11
- package/src/v2/adapters/extension.js +1988 -0
- package/src/v2/adapters/ios-driver.js +95 -12
- package/src/v2/adapters/ios.js +220 -10
- package/src/v2/adapters/linux-driver.js +1028 -0
- package/src/v2/adapters/linux.js +1324 -0
- package/src/v2/adapters/macos-driver.js +913 -0
- package/src/v2/adapters/macos.js +1374 -0
- package/src/v2/browsers.js +41 -2
- package/src/v2/check.js +133 -13
- package/src/v2/cli.js +2 -0
- package/src/v2/coverage.js +1 -1
- package/src/v2/detect.js +5 -2
- package/src/v2/doctor.js +164 -20
- package/src/v2/init.js +21 -3
- package/src/v2/journeys/index.js +3 -3
- package/src/v2/journeys/record-session.js +839 -0
- package/src/v2/journeys/record.js +12 -0
- package/src/v2/mcp/tools.js +8 -15
- package/src/v2/types.js +1 -1
- package/src/v2/watch/events.js +6 -0
|
@@ -35,6 +35,18 @@ import path from 'node:path';
|
|
|
35
35
|
/** The shape of a journeys file on disk. Version it, because a format nobody versioned is a format nobody can change. */
|
|
36
36
|
export const JOURNEY_FILE_VERSION = 2;
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Where recordings live, relative to the project.
|
|
40
|
+
*
|
|
41
|
+
* One name, because three readers used to spell it out for themselves — the gatherer, the
|
|
42
|
+
* check path and the recording command — and three copies of a folder name is three chances
|
|
43
|
+
* for a recording to be written where nothing looks for it, which reads afterwards exactly
|
|
44
|
+
* like a recording that was never made.
|
|
45
|
+
*
|
|
46
|
+
* It is committed, not ignored: a recording is the promise, not the evidence.
|
|
47
|
+
*/
|
|
48
|
+
export const RECORDINGS_DIR = path.join('.staysfixed', 'journeys');
|
|
49
|
+
|
|
38
50
|
// ---------------------------------------------------------------------------
|
|
39
51
|
// Keeping secrets out
|
|
40
52
|
// ---------------------------------------------------------------------------
|
package/src/v2/mcp/tools.js
CHANGED
|
@@ -493,13 +493,13 @@ export function toolDefinitions() {
|
|
|
493
493
|
journeys: {
|
|
494
494
|
type: 'string',
|
|
495
495
|
description:
|
|
496
|
-
"Where the steps come from. 'code' is the default and needs nothing: each adapter reads your source and offers what it finds - routes, commands, screens, message channels. 'suite' walks the project's own test suite as well: each test file runs twice inside the scratch copy, every check is reported by name, and it stops after 90 seconds naming each file it did not reach. It catches breaks nothing else can - a rounding change the product's own output never shows. It is opt-in because running a stranger's whole suite twice on every check is not something to do by default.
|
|
496
|
+
"Where the steps come from. 'code' is the default and needs nothing: each adapter reads your source and offers what it finds - routes, commands, screens, message channels. 'suite' walks the project's own test suite as well: each test file runs twice inside the scratch copy, every check is reported by name, and it stops after 90 seconds naming each file it did not reach. It catches breaks nothing else can - a rounding change the product's own output never shows. It is opt-in because running a stranger's whole suite twice on every check is not something to do by default. 'recorded' replays the sessions somebody recorded with `staysfixed record` and kept in .staysfixed/journeys - the only source that knows which four screens a person actually opens every morning, which reading the source cannot work out. You can also pass a path to a journeys file naming steps by hand.",
|
|
497
497
|
},
|
|
498
498
|
surface: {
|
|
499
499
|
type: 'string',
|
|
500
|
-
enum: ['auto', 'cli', 'library', 'server', 'web', 'electron', 'android', 'ios'],
|
|
500
|
+
enum: ['auto', 'cli', 'library', 'server', 'web', 'electron', 'android', 'ios', 'extension'],
|
|
501
501
|
description:
|
|
502
|
-
"What kind of product to aim at. Default 'auto', which uses the settings. 'web' opens the page in a browser of the tool's own — never yours — and reads what the screen says each control is and does. 'electron' opens the desktop app with its own scratch data folder and drives it over its own debugging port. 'android' installs the APK on a virtual device; 'ios' boots the built app on a simulator. Aim it at something this copy or this machine cannot drive and it refuses by name rather than checking something else and reporting that.",
|
|
502
|
+
"What kind of product to aim at. Default 'auto', which uses the settings. 'web' opens the page in a browser of the tool's own — never yours — and reads what the screen says each control is and does. 'electron' opens the desktop app with its own scratch data folder and drives it over its own debugging port. 'android' installs the APK on a virtual device; 'ios' boots the built app on a simulator. 'extension' loads a browser extension into a throwaway browser: its manifest is read as a contract, its popup and options pages are walked like any other page, and what its content scripts do to somebody else's page is measured by opening that page with the extension and without it and comparing the difference — point 'extension.dir' in the settings at the folder you would load unpacked. Aim it at something this copy or this machine cannot drive and it refuses by name rather than checking something else and reporting that.",
|
|
503
503
|
},
|
|
504
504
|
at: {
|
|
505
505
|
type: 'string',
|
|
@@ -934,19 +934,12 @@ async function toolCheck(ctx, input) {
|
|
|
934
934
|
const limit = positive(input.limit) ?? DEFAULT_LIMIT;
|
|
935
935
|
const offset = positive(input.offset) ?? 0;
|
|
936
936
|
|
|
937
|
-
//
|
|
938
|
-
//
|
|
939
|
-
//
|
|
940
|
-
//
|
|
941
|
-
//
|
|
942
|
-
// file it never asked for. Refusing it by name and saying why is the honest answer, and a
|
|
943
|
-
// clean result about the wrong steps would be worse than no result.
|
|
937
|
+
// Every word this tool offers now reaches something that walks. `suite` harvests the
|
|
938
|
+
// project's own tests; `recorded` replays the sessions in `.staysfixed/journeys`, which was
|
|
939
|
+
// written and wired to nothing until 2026-08-31 and was refused here by name because of it.
|
|
940
|
+
// A project with no recordings comes back BLOCKED from the engine, naming the folder and the
|
|
941
|
+
// command that makes one - never as a clean result about steps nobody walked.
|
|
944
942
|
const wantedJourneys = text(input.journeys);
|
|
945
|
-
if (wantedJourneys === 'recorded') {
|
|
946
|
-
return problem(
|
|
947
|
-
'Replaying a recorded session is written and not wired into a run yet, so nothing was checked. Leave journeys out to use the steps each adapter reads from your source, pass "suite" to walk your own test suite, or pass the path to a journeys file.'
|
|
948
|
-
);
|
|
949
|
-
}
|
|
950
943
|
|
|
951
944
|
const surface = text(input.surface);
|
|
952
945
|
const at = text(input.at);
|
package/src/v2/types.js
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Where an observation came from, when it matters which platform produced it.
|
|
42
|
-
* @typedef {'cli'|'library'|'server'|'web'|'electron'|'android'|'ios'|'windows'} Surface
|
|
42
|
+
* @typedef {'cli'|'library'|'server'|'web'|'electron'|'android'|'ios'|'windows'|'linux'|'macos'|'extension'} Surface
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
45
|
// ---------------------------------------------------------------------------
|
package/src/v2/watch/events.js
CHANGED
|
@@ -56,9 +56,12 @@ export const SURFACE_WORDS = Object.freeze({
|
|
|
56
56
|
server: 'Server',
|
|
57
57
|
web: 'Website',
|
|
58
58
|
electron: 'Desktop app',
|
|
59
|
+
extension: 'Browser extension',
|
|
59
60
|
android: 'Android phone',
|
|
60
61
|
ios: 'iPhone',
|
|
61
62
|
windows: 'Windows app',
|
|
63
|
+
linux: 'Linux desktop app',
|
|
64
|
+
macos: 'Mac app',
|
|
62
65
|
});
|
|
63
66
|
|
|
64
67
|
/**
|
|
@@ -71,9 +74,12 @@ export const SURFACE_NOTES = Object.freeze({
|
|
|
71
74
|
server: 'started on its own port, watched through what it answered',
|
|
72
75
|
web: 'opened in a browser, watched through what the page says its controls do',
|
|
73
76
|
electron: 'launched with its own data folder, watched through the app and its channels',
|
|
77
|
+
extension: 'loaded into a throwaway browser, watched through its manifest, its own pages, and what it does to somebody else\'s page',
|
|
74
78
|
android: 'installed on an emulator, watched through what is on the screen',
|
|
75
79
|
ios: 'installed on a simulator, watched through what is on the screen',
|
|
76
80
|
windows: 'driven on a real Windows desktop, watched through what is on the screen',
|
|
81
|
+
linux: 'opened on a real Linux desktop, watched through what the accessibility bus says its controls do',
|
|
82
|
+
macos: 'opened in the background on a Mac, watched through what is on the screen',
|
|
77
83
|
});
|
|
78
84
|
|
|
79
85
|
/**
|