staysfixed 0.12.0 → 0.13.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 +32 -2
- package/docs/guards.md +18 -0
- package/docs/how-v2-works.md +10 -0
- package/package.json +1 -1
- package/src/cli/approve.js +4 -1
- package/src/cli/flake.js +4 -1
- package/src/cli/mark.js +5 -1
- package/src/cli/status.js +53 -1
- package/src/cli/trace.js +27 -2
- package/src/core/config.js +136 -25
- package/src/core/stop-tree.js +109 -0
- package/src/drive/browser.js +20 -31
- package/src/drive/page.js +74 -2
- package/src/guard/api.js +14 -9
- package/src/types.js +1 -1
- package/src/v2/adapters/child.js +15 -17
- package/src/v2/adapters/contract.js +122 -1
- package/src/v2/adapters/http.js +152 -30
- package/src/v2/adapters/isolate.js +169 -14
- package/src/v2/adapters/process.js +72 -8
- package/src/v2/adapters/source.js +254 -7
- package/src/v2/adapters/web.js +69 -19
- package/src/v2/browsers.js +136 -24
- package/src/v2/cause.js +46 -5
- package/src/v2/check.js +332 -34
- package/src/v2/cli.js +19 -1
- package/src/v2/coverage.js +555 -18
- package/src/v2/detect.js +737 -40
- package/src/v2/doctor.js +3 -3
- package/src/v2/escalate.js +57 -11
- package/src/v2/init.js +562 -21
- package/src/v2/journeys/answers-probe.js +376 -0
- package/src/v2/journeys/from-exports.js +456 -0
- package/src/v2/journeys/from-suite.js +9 -1
- package/src/v2/mcp/tools.js +185 -12
- package/src/v2/observation.js +145 -0
- package/src/v2/run.js +133 -9
- package/src/v2/selfcheck.js +297 -11
- package/src/v2/store.js +16 -1
package/src/v2/doctor.js
CHANGED
|
@@ -1757,7 +1757,7 @@ function describeSurfaces(tools, hosts, configured, browsers, desktopApp, driver
|
|
|
1757
1757
|
name: 'command-line tools and libraries',
|
|
1758
1758
|
status: wiredForCli ? 'ready' : 'partial',
|
|
1759
1759
|
summary: wiredForCli
|
|
1760
|
-
? '
|
|
1760
|
+
? 'What it printed, what it exited with, what it wrote, what it called out to, what it exports, and what each exported function answers when it is called with a fixed list of inputs. Not covered: any input outside that list, and any function whose name says it deletes, sends, publishes or charges — those are never called. A clean result covers what was walked; `staysfixed coverage` lists what was not.'
|
|
1761
1761
|
: configured
|
|
1762
1762
|
? 'This machine can cover it in full — what a command printed, what it exited with, what it wrote, what it called out to, what it exports — but these settings wire no command to run and nothing to import, so a check runs none of it and a clean result says nothing about any of it.'
|
|
1763
1763
|
: 'This machine can cover it in full, but nothing is set up in this folder yet, so a check cannot run here at all.',
|
|
@@ -2226,9 +2226,9 @@ function whatThisRunActuallyCovers(surfaces, setUpHere = true) {
|
|
|
2226
2226
|
if (!setUpHere) {
|
|
2227
2227
|
out.canRunHere = false;
|
|
2228
2228
|
parts.push('Nothing is set up in this folder, so a check cannot run here at all and would cover nothing. Run `staysfixed init` first.');
|
|
2229
|
-
parts.push(full.length > 0 ? `Once it is set up, this machine
|
|
2229
|
+
parts.push(full.length > 0 ? `Once it is set up, this machine can drive ${plainList(out.covered)}.` : 'Even set up, this machine can drive none of these.');
|
|
2230
2230
|
} else {
|
|
2231
|
-
parts.push(full.length > 0 ? `A check here
|
|
2231
|
+
parts.push(full.length > 0 ? `A check here can drive ${plainList(out.covered)}. That is what this machine is able to walk, not what any run has walked.` : 'A check here can drive none of these.');
|
|
2232
2232
|
}
|
|
2233
2233
|
if (some.length > 0) parts.push(`It covers ${plainList(some.map((s) => s.name))} only partly — read the summary for each before treating a clean result as proof.`);
|
|
2234
2234
|
if (missing.length > 0) {
|
package/src/v2/escalate.js
CHANGED
|
@@ -437,7 +437,7 @@ export async function rememberCheck(store, what) {
|
|
|
437
437
|
*
|
|
438
438
|
* @typedef {object} Escalation
|
|
439
439
|
* @property {string} id
|
|
440
|
-
* @property {'sealed'|'budget'|'unpredictable'|'blocked'|'no-reference'} kind
|
|
440
|
+
* @property {'sealed'|'difference'|'budget'|'unpredictable'|'blocked'|'no-reference'} kind
|
|
441
441
|
* @property {string} what What changed.
|
|
442
442
|
* @property {string} why Why no agent could wave it through.
|
|
443
443
|
* @property {string} todo What to do about it.
|
|
@@ -500,11 +500,26 @@ export async function escalationsFor(store, product) {
|
|
|
500
500
|
* @returns {Escalations}
|
|
501
501
|
*/
|
|
502
502
|
function buildEscalations(product, record, verdict) {
|
|
503
|
+
// TWO PILES, AND THE ORDER BETWEEN THEM IS THE FIX.
|
|
504
|
+
//
|
|
505
|
+
// `real` is everything that is the product behaving differently, or the check not having
|
|
506
|
+
// happened at all. `steadiness` is the one item that is neither: addresses that used to
|
|
507
|
+
// give the same answer every run and now do not. That is worth a person's attention and it
|
|
508
|
+
// is NOT a difference — nothing in it has a wrong value.
|
|
509
|
+
//
|
|
510
|
+
// WHAT WENT WRONG, 2026-08-31. A run caught one real change correctly and also measured 242
|
|
511
|
+
// addresses as newly unsteady. Only the second reached this block, because an ordinary
|
|
512
|
+
// difference has never been put in here at all — so the only sentence the owner read told
|
|
513
|
+
// him to hold the release over a wobble measurement, and never mentioned the change the
|
|
514
|
+
// tool had actually found. Piling them separately makes that ordering structural instead of
|
|
515
|
+
// accidental, and `crowdedOut` below makes sure a real change is never the thing left out.
|
|
503
516
|
/** @type {Escalation[]} */
|
|
504
|
-
const
|
|
517
|
+
const real = [];
|
|
518
|
+
/** @type {Escalation[]} */
|
|
519
|
+
const steadiness = [];
|
|
505
520
|
|
|
506
521
|
if (verdict.blocked === true) {
|
|
507
|
-
|
|
522
|
+
real.push({
|
|
508
523
|
id: 'blocked',
|
|
509
524
|
kind: 'blocked',
|
|
510
525
|
what: `Stays Fixed could not check ${product} at all on this run, so nothing about it has been proved either way.`,
|
|
@@ -512,7 +527,7 @@ function buildEscalations(product, record, verdict) {
|
|
|
512
527
|
todo: `Something is in the way and it needs clearing — the run said: ${oneLine(verdict.summary, 200)}`,
|
|
513
528
|
});
|
|
514
529
|
} else if (!verdict.reference || verdict.reference.id === '') {
|
|
515
|
-
|
|
530
|
+
real.push({
|
|
516
531
|
id: 'no-reference',
|
|
517
532
|
kind: 'no-reference',
|
|
518
533
|
what: `There is no build of ${product} on record as working yet, so this run had nothing to compare against.`,
|
|
@@ -527,7 +542,7 @@ function buildEscalations(product, record, verdict) {
|
|
|
527
542
|
|
|
528
543
|
for (const f of record.findings) {
|
|
529
544
|
if (f.unwaivable !== true) continue;
|
|
530
|
-
|
|
545
|
+
real.push({
|
|
531
546
|
id: f.id,
|
|
532
547
|
kind: 'sealed',
|
|
533
548
|
what: oneLine(f.title, 220),
|
|
@@ -542,7 +557,7 @@ function buildEscalations(product, record, verdict) {
|
|
|
542
557
|
}
|
|
543
558
|
|
|
544
559
|
if (record.accounting.left === 0 && record.accounting.reported > record.accounting.unwaivable) {
|
|
545
|
-
|
|
560
|
+
real.push({
|
|
546
561
|
id: 'budget',
|
|
547
562
|
kind: 'budget',
|
|
548
563
|
what: `The agent has used all ${record.accounting.budget} of the differences it is allowed to record as intended on ${product}, and there are still differences left over.`,
|
|
@@ -553,7 +568,7 @@ function buildEscalations(product, record, verdict) {
|
|
|
553
568
|
|
|
554
569
|
if (record.newlyUnstable.length > 0) {
|
|
555
570
|
const n = record.newlyUnstable.length;
|
|
556
|
-
|
|
571
|
+
steadiness.push({
|
|
557
572
|
id: 'unpredictable',
|
|
558
573
|
kind: 'unpredictable',
|
|
559
574
|
what: `${n} ${n === 1 ? 'thing in' : 'things in'} ${product} used to give the same answer every single run and now ${n === 1 ? 'does' : 'do'} not: ${record.newlyUnstable.slice(0, 3).join(', ')}${n > 3 ? ', and more' : ''}.`,
|
|
@@ -561,15 +576,36 @@ function buildEscalations(product, record, verdict) {
|
|
|
561
576
|
todo: 'Have it looked into before shipping. Something in the change made the product unpredictable.',
|
|
562
577
|
paths: record.newlyUnstable.slice(0, 6),
|
|
563
578
|
});
|
|
579
|
+
// A REAL CHANGE IS NEVER THE THING LEFT OUT.
|
|
580
|
+
//
|
|
581
|
+
// An ordinary difference is the agent's problem and does not get its own item — that is
|
|
582
|
+
// deliberate, and it stops a handful of items a month becoming a feed nobody reads. But it
|
|
583
|
+
// stops being right the moment the ONLY thing in this block is a wobble measurement,
|
|
584
|
+
// because then the sentence a person reads is an alarm about a non-difference with no
|
|
585
|
+
// mention of the difference the tool did find. So a real change gets one line here, and it
|
|
586
|
+
// goes first, exactly when it would otherwise have been the thing crowded out.
|
|
587
|
+
const crowdedOut = record.findings.filter((f) => f.waivedBy === undefined && f.unwaivable !== true);
|
|
588
|
+
if (real.length === 0 && crowdedOut.length > 0) {
|
|
589
|
+
const worst = crowdedOut[0];
|
|
590
|
+
real.push({
|
|
591
|
+
id: 'differences',
|
|
592
|
+
kind: 'difference',
|
|
593
|
+
what: `${product} behaves differently from the build you were happy with in ${crowdedOut.length} ${crowdedOut.length === 1 ? 'place' : 'places'}: ${oneLine(worst.title, 180)}${crowdedOut.length > 1 ? ', and more' : ''}.`,
|
|
594
|
+
why: 'This is a real change in the product, which is what the check is for — it is named before the steadiness note below so it cannot be read past.',
|
|
595
|
+
todo: 'The agent has to deal with each one: fix it, or record it as intended and say why. Nothing is the new normal until you ship.',
|
|
596
|
+
paths: (worst.paths ?? []).slice(0, 6),
|
|
597
|
+
});
|
|
598
|
+
}
|
|
564
599
|
}
|
|
565
600
|
|
|
601
|
+
const all = [...real, ...steadiness];
|
|
566
602
|
return {
|
|
567
603
|
product,
|
|
568
604
|
at: record.at,
|
|
569
|
-
items,
|
|
605
|
+
items: all,
|
|
570
606
|
waived: record.accounting.waived,
|
|
571
607
|
expiredWaivers: record.accounting.expiredWaivers,
|
|
572
|
-
note: summaryNote(product,
|
|
608
|
+
note: summaryNote(product, all, record),
|
|
573
609
|
};
|
|
574
610
|
}
|
|
575
611
|
|
|
@@ -595,11 +631,21 @@ function sealedTodo(f) {
|
|
|
595
631
|
* @returns {string}
|
|
596
632
|
*/
|
|
597
633
|
function summaryNote(product, items, record) {
|
|
634
|
+
// NOTHING NEEDING YOUR WORD IS NOT THE SAME AS NOTHING BEING WRONG.
|
|
635
|
+
//
|
|
636
|
+
// This line used to read "nothing on X needs your word" on a run that had found real
|
|
637
|
+
// differences the agent had not dealt with — true about who has to decide, and read by
|
|
638
|
+
// anybody skimming as an all-clear. One clause fixes it, and it is added rather than the
|
|
639
|
+
// sentence replaced, because who has to decide is still the thing this block is about.
|
|
640
|
+
const left = record.accounting.reported;
|
|
641
|
+
const outstanding = left > 0
|
|
642
|
+
? ` The agent still has ${left} ${left === 1 ? 'difference' : 'differences'} of its own to deal with on this build.`
|
|
643
|
+
: '';
|
|
598
644
|
if (items.length === 0) {
|
|
599
645
|
const waived = record.accounting.waived;
|
|
600
646
|
return waived > 0
|
|
601
|
-
? `Stays Fixed: nothing on ${product} needs your word
|
|
602
|
-
: `Stays Fixed: nothing on ${product} needs your word
|
|
647
|
+
? `Stays Fixed: nothing on ${product} needs your word.${outstanding} ${waived} ${waived === 1 ? 'difference was' : 'differences were'} recorded as intended by the agent and ${waived === 1 ? 'is' : 'are'} waiting on your next ship.`
|
|
648
|
+
: `Stays Fixed: nothing on ${product} needs your word.${outstanding}`;
|
|
603
649
|
}
|
|
604
650
|
return `Stays Fixed: ${items.length} ${items.length === 1 ? 'thing needs' : 'things need'} your word on ${product}.`;
|
|
605
651
|
}
|