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
package/src/v2/init.js
CHANGED
|
@@ -1522,14 +1522,20 @@ export function configText(project) {
|
|
|
1522
1522
|
const androidHere = android?.adapter === 'android';
|
|
1523
1523
|
w(' // ───────────────────────────────────────────────────────────────────────');
|
|
1524
1524
|
w(' // Android apps. Installed on an emulator of its own, walked, then removed.');
|
|
1525
|
-
w(' // Compared against the stored record
|
|
1526
|
-
w(' //
|
|
1525
|
+
w(' // Compared against the stored record, or paired if you name the old build.');
|
|
1526
|
+
w(' // A snapshot restore was measured on 2026-08-31 and repeats: 301 of 309');
|
|
1527
|
+
w(" // addresses agreed across five pairs, and the eight that moved were the app's");
|
|
1528
|
+
w(' // own identity code, which the wobble subtraction already removes.');
|
|
1527
1529
|
w(' // ───────────────────────────────────────────────────────────────────────');
|
|
1528
1530
|
w(androidHere ? ' android: {' : ' // android: {');
|
|
1529
1531
|
const andOn = androidHere ? ' ' : ' // ';
|
|
1530
1532
|
const apk = android?.suggest?.apk;
|
|
1531
1533
|
w(`${andOn}// The built package. Left out, it looks in the usual build folders.`);
|
|
1532
1534
|
w(apk ? `${andOn}apk: ${JSON.stringify(String(apk))},` : `${andOn}// apk: 'app/build/outputs/apk/debug/app-debug.apk',`);
|
|
1535
|
+
w(`${andOn}// A kept copy of the OLD build's package, so a paired run has two builds to compare.`);
|
|
1536
|
+
w(`${andOn}// A checkout of the old commit contains no APK — an APK is a build output — so`);
|
|
1537
|
+
w(`${andOn}// without this a paired run falls back to the stored record and says so.`);
|
|
1538
|
+
w(`${andOn}// reference: 'builds/0.14.0.apk',`);
|
|
1533
1539
|
w(`${andOn}// Which emulator to use. Left out, it takes the first one that is not a Play Store image.`);
|
|
1534
1540
|
w(`${andOn}// avd: 'Pixel_7_API_35',`);
|
|
1535
1541
|
w(`${andOn}// Or a device already plugged in or already running.`);
|
|
@@ -1577,6 +1583,10 @@ export function configText(project) {
|
|
|
1577
1583
|
w(`${iosOn}// The built app bundle for the simulator. Left out, it looks where builds land.`);
|
|
1578
1584
|
w(iosApp ? `${iosOn}app: ${JSON.stringify(String(iosApp))},` : `${iosOn}// app: 'build/Debug-iphonesimulator/YourApp.app',`);
|
|
1579
1585
|
w(`${iosOn}// Which simulator to use, and which system to run it on. Left out, it takes a sensible`);
|
|
1586
|
+
w(`${iosOn}// A kept copy of the OLD build's bundle, so a paired run has two builds to compare.`);
|
|
1587
|
+
w(`${iosOn}// A checkout of the old commit contains no .app — a bundle is a build output — so`);
|
|
1588
|
+
w(`${iosOn}// without this a paired run falls back to the stored record and says so.`);
|
|
1589
|
+
w(`${iosOn}// reference: 'builds/0.14.0.app',`);
|
|
1580
1590
|
w(`${iosOn}// one and says which.`);
|
|
1581
1591
|
w(`${iosOn}// deviceType: 'iPhone 17', runtime: 'iOS 26.4',`);
|
|
1582
1592
|
w(`${iosOn}// Walks through the app. Left out, it opens the app and reads the first screen.`);
|
|
@@ -1893,7 +1903,15 @@ export async function run(ctx) {
|
|
|
1893
1903
|
const others = (result.plan?.readiness ?? [])
|
|
1894
1904
|
.map((/** @type {any} */ r) => String(r.product ?? ''))
|
|
1895
1905
|
.filter((/** @type {string} */ n, /** @type {number} */ i, /** @type {string[]} */ all) => n !== '' && all.indexOf(n) === i);
|
|
1896
|
-
if (result.written.length > 0)
|
|
1906
|
+
if (result.written.length > 0) {
|
|
1907
|
+
ok(`Written: ${result.written.map((f) => shortPath(f)).join(', ')}`);
|
|
1908
|
+
// Worth one line, because of what happens if it is not done. These files are part of the
|
|
1909
|
+
// build now: until they are committed the working tree is not what git has, so the first
|
|
1910
|
+
// reference gets cut from a tree that has no commit of its own — and a later check cannot
|
|
1911
|
+
// put that build back on the machine to walk it live. It falls back to the stored record,
|
|
1912
|
+
// says so, and is weaker for it. One `git add` avoids the whole thing.
|
|
1913
|
+
say('Commit them before you ship. Settings that are not committed leave the first reference tied to a build git does not have, and a later check can then only compare against the record rather than running the old build live.');
|
|
1914
|
+
}
|
|
1897
1915
|
if (others.length > 1) {
|
|
1898
1916
|
warn(
|
|
1899
1917
|
`Those settings describe ONE product. ${others.length} were found here (${others.join(', ')}), and the others are not covered by this file. ` +
|
package/src/v2/journeys/index.js
CHANGED
|
@@ -33,7 +33,7 @@ import path from 'node:path';
|
|
|
33
33
|
import { measureWobble } from '../observation.js';
|
|
34
34
|
import { journeysFromCode } from './from-routes.js';
|
|
35
35
|
import { DEFAULT_HARVEST_BUDGET_MS, harvestJourneys, testsNear } from './from-suite.js';
|
|
36
|
-
import { loadJourneyFolder, whatWillNotReplay } from './record.js';
|
|
36
|
+
import { RECORDINGS_DIR, loadJourneyFolder, whatWillNotReplay } from './record.js';
|
|
37
37
|
|
|
38
38
|
/** @typedef {import('../types.js').Journey} Journey */
|
|
39
39
|
/** @typedef {import('../types.js').JourneySource} JourneySource */
|
|
@@ -47,7 +47,7 @@ import { loadJourneyFolder, whatWillNotReplay } from './record.js';
|
|
|
47
47
|
|
|
48
48
|
export { journeysFromCode, journeysFromDoors, irreversibility } from './from-routes.js';
|
|
49
49
|
export { detectRunner, harvestJourneys, listTestFiles, testsNear, DEFAULT_HARVEST_BUDGET_MS } from './from-suite.js';
|
|
50
|
-
export { startRecording, recordSession, saveJourneys, loadJourneys, loadJourneyFolder, redact } from './record.js';
|
|
50
|
+
export { startRecording, recordSession, saveJourneys, loadJourneys, loadJourneyFolder, redact, RECORDINGS_DIR } from './record.js';
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* A journey with everything this folder knows about where it came from.
|
|
@@ -508,7 +508,7 @@ export async function gather(opts) {
|
|
|
508
508
|
|
|
509
509
|
// ---- recorded sessions ---------------------------------------------------
|
|
510
510
|
if (opts.recorded !== false) {
|
|
511
|
-
const dir = opts.recorded?.dir ?? path.join(root,
|
|
511
|
+
const dir = opts.recorded?.dir ?? path.join(root, RECORDINGS_DIR);
|
|
512
512
|
const loaded = await loadJourneyFolder(dir);
|
|
513
513
|
for (const journey of loaded.journeys) {
|
|
514
514
|
const willNotReplay = whatWillNotReplay(journey);
|