@jevitate/cli 0.12.0 → 0.14.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/dist/author-journey-cli.d.ts +9 -0
- package/dist/author-journey-cli.js +18 -1
- package/dist/author-journey-cli.js.map +1 -1
- package/dist/bin.js +25673 -24216
- package/dist/build-info.generated.js +2 -2
- package/dist/catalog-analysis.js +4 -2
- package/dist/catalog-analysis.js.map +1 -1
- package/dist/catalog-bundle-api.js +1 -3
- package/dist/catalog-bundle-api.js.map +1 -1
- package/dist/catalog-bundle-findings.js +1 -3
- package/dist/catalog-bundle-findings.js.map +1 -1
- package/dist/catalog-bundle.js +1 -4
- package/dist/catalog-bundle.js.map +1 -1
- package/dist/catalog-cli.js +48 -6
- package/dist/catalog-cli.js.map +1 -1
- package/dist/catalog-review.d.ts +8 -0
- package/dist/catalog-review.js +26 -0
- package/dist/catalog-review.js.map +1 -1
- package/dist/catalog.d.ts +42 -0
- package/dist/catalog.js +67 -4
- package/dist/catalog.js.map +1 -1
- package/dist/check-execute.js +9 -0
- package/dist/check-execute.js.map +1 -1
- package/dist/check-plan.d.ts +5 -2
- package/dist/check-plan.js +9 -5
- package/dist/check-plan.js.map +1 -1
- package/dist/check-suite.js +1 -3
- package/dist/check-suite.js.map +1 -1
- package/dist/cli-output.js +1 -3
- package/dist/cli-output.js.map +1 -1
- package/dist/cli-shared.d.ts +1 -1
- package/dist/cli-shared.js +2 -0
- package/dist/cli-shared.js.map +1 -1
- package/dist/defect-evidence.js +4 -8
- package/dist/defect-evidence.js.map +1 -1
- package/dist/environments.d.ts +1 -1
- package/dist/environments.js +1 -1
- package/dist/environments.js.map +1 -1
- package/dist/exit-codes.d.ts +0 -1
- package/dist/exit-codes.js +2 -0
- package/dist/exit-codes.js.map +1 -1
- package/dist/explore-adversarial.d.ts +10 -1
- package/dist/explore-adversarial.js +3 -0
- package/dist/explore-adversarial.js.map +1 -1
- package/dist/explore-cli/adversarial-strategy.d.ts +3 -0
- package/dist/explore-cli/adversarial-strategy.js +101 -0
- package/dist/explore-cli/adversarial-strategy.js.map +1 -0
- package/dist/explore-cli/coverage-strategy.d.ts +3 -0
- package/dist/explore-cli/coverage-strategy.js +97 -0
- package/dist/explore-cli/coverage-strategy.js.map +1 -0
- package/dist/explore-cli/feature-strategy.d.ts +3 -0
- package/dist/explore-cli/feature-strategy.js +60 -0
- package/dist/explore-cli/feature-strategy.js.map +1 -0
- package/dist/explore-cli/goal-strategy.d.ts +3 -0
- package/dist/explore-cli/goal-strategy.js +185 -0
- package/dist/explore-cli/goal-strategy.js.map +1 -0
- package/dist/explore-cli/options.d.ts +3 -0
- package/dist/explore-cli/options.js +157 -0
- package/dist/explore-cli/options.js.map +1 -0
- package/dist/explore-cli/run.d.ts +7 -0
- package/dist/explore-cli/run.js +702 -0
- package/dist/explore-cli/run.js.map +1 -0
- package/dist/explore-cli/scope-region.d.ts +17 -0
- package/dist/explore-cli/scope-region.js +24 -0
- package/dist/explore-cli/scope-region.js.map +1 -0
- package/dist/explore-cli/shared.d.ts +167 -0
- package/dist/explore-cli/shared.js +20 -0
- package/dist/explore-cli/shared.js.map +1 -0
- package/dist/explore-cli/usability-strategy.d.ts +3 -0
- package/dist/explore-cli/usability-strategy.js +137 -0
- package/dist/explore-cli/usability-strategy.js.map +1 -0
- package/dist/explore-cli.d.ts +2 -2
- package/dist/explore-cli.js +2 -1347
- package/dist/explore-cli.js.map +1 -1
- package/dist/explore-coverage.d.ts +10 -1
- package/dist/explore-coverage.js +3 -0
- package/dist/explore-coverage.js.map +1 -1
- package/dist/explore-feature.d.ts +6 -0
- package/dist/explore-feature.js +1 -0
- package/dist/explore-feature.js.map +1 -1
- package/dist/explore-goal.d.ts +6 -0
- package/dist/explore-goal.js +1 -0
- package/dist/explore-goal.js.map +1 -1
- package/dist/explore-specs.d.ts +3 -1
- package/dist/explore-specs.js +2 -1
- package/dist/explore-specs.js.map +1 -1
- package/dist/github-issue-filer.js +1 -4
- package/dist/github-issue-filer.js.map +1 -1
- package/dist/goal-run-flags.d.ts +0 -1
- package/dist/goal-run-flags.js.map +1 -1
- package/dist/inbox-cli.js +1 -3
- package/dist/inbox-cli.js.map +1 -1
- package/dist/init-skills.js +1 -4
- package/dist/init-skills.js.map +1 -1
- package/dist/journey-anchor-api.d.ts +10 -9
- package/dist/journey-anchor-api.js +12 -22
- package/dist/journey-anchor-api.js.map +1 -1
- package/dist/journey-anchor-cli.d.ts +1 -1
- package/dist/journey-anchor-cli.js +22 -8
- package/dist/journey-anchor-cli.js.map +1 -1
- package/dist/journey-cli/anchors.d.ts +4 -0
- package/dist/journey-cli/anchors.js +48 -0
- package/dist/journey-cli/anchors.js.map +1 -0
- package/dist/journey-cli/find.d.ts +4 -0
- package/dist/journey-cli/find.js +44 -0
- package/dist/journey-cli/find.js.map +1 -0
- package/dist/journey-cli/lint.d.ts +4 -0
- package/dist/journey-cli/lint.js +84 -0
- package/dist/journey-cli/lint.js.map +1 -0
- package/dist/journey-cli/list.d.ts +4 -0
- package/dist/journey-cli/list.js +48 -0
- package/dist/journey-cli/list.js.map +1 -0
- package/dist/journey-cli/migrate.d.ts +4 -0
- package/dist/journey-cli/migrate.js +101 -0
- package/dist/journey-cli/migrate.js.map +1 -0
- package/dist/journey-cli/promote.d.ts +4 -0
- package/dist/journey-cli/promote.js +181 -0
- package/dist/journey-cli/promote.js.map +1 -0
- package/dist/journey-cli/publish.d.ts +4 -0
- package/dist/journey-cli/publish.js +70 -0
- package/dist/journey-cli/publish.js.map +1 -0
- package/dist/journey-cli/review.d.ts +4 -0
- package/dist/journey-cli/review.js +108 -0
- package/dist/journey-cli/review.js.map +1 -0
- package/dist/journey-cli/run.d.ts +4 -0
- package/dist/journey-cli/run.js +244 -0
- package/dist/journey-cli/run.js.map +1 -0
- package/dist/journey-cli/shared.d.ts +9 -0
- package/dist/journey-cli/shared.js +12 -0
- package/dist/journey-cli/shared.js.map +1 -0
- package/dist/journey-cli/verify.d.ts +4 -0
- package/dist/journey-cli/verify.js +125 -0
- package/dist/journey-cli/verify.js.map +1 -0
- package/dist/journey-cli.d.ts +3 -2
- package/dist/journey-cli.js +25 -950
- package/dist/journey-cli.js.map +1 -1
- package/dist/journey-link-api.d.ts +40 -0
- package/dist/journey-link-api.js +90 -0
- package/dist/journey-link-api.js.map +1 -0
- package/dist/journey-link-cli.d.ts +7 -0
- package/dist/journey-link-cli.js +58 -0
- package/dist/journey-link-cli.js.map +1 -0
- package/dist/journey-link-hints.d.ts +8 -0
- package/dist/journey-link-hints.js +68 -0
- package/dist/journey-link-hints.js.map +1 -0
- package/dist/journey-lint-sarif.js +3 -0
- package/dist/journey-lint-sarif.js.map +1 -1
- package/dist/journey-network-checks.d.ts +9 -1
- package/dist/journey-network-checks.js +55 -12
- package/dist/journey-network-checks.js.map +1 -1
- package/dist/journey-prefix.d.ts +9 -1
- package/dist/journey-prefix.js +12 -0
- package/dist/journey-prefix.js.map +1 -1
- package/dist/journey-review/render.d.ts +43 -0
- package/dist/journey-review/render.js +461 -0
- package/dist/journey-review/render.js.map +1 -0
- package/dist/journey-review-api.d.ts +1 -0
- package/dist/journey-review-api.js +7 -0
- package/dist/journey-review-api.js.map +1 -1
- package/dist/journey-review-store.js +1 -0
- package/dist/journey-review-store.js.map +1 -1
- package/dist/journey-review.d.ts +6 -4
- package/dist/journey-review.js +18 -393
- package/dist/journey-review.js.map +1 -1
- package/dist/journey-verify.js +2 -1
- package/dist/journey-verify.js.map +1 -1
- package/dist/ledger-api.js +1 -4
- package/dist/ledger-api.js.map +1 -1
- package/dist/locator-health-api.js +1 -3
- package/dist/locator-health-api.js.map +1 -1
- package/dist/locator-health.d.ts +0 -1
- package/dist/locator-health.js +0 -3
- package/dist/locator-health.js.map +1 -1
- package/dist/log-classes.js +1 -3
- package/dist/log-classes.js.map +1 -1
- package/dist/log-sources.js +17 -1
- package/dist/log-sources.js.map +1 -1
- package/dist/mcp-api.js +16 -821
- package/dist/mcp-api.js.map +1 -1
- package/dist/mcp-cli-tools.js +48 -7
- package/dist/mcp-cli-tools.js.map +1 -1
- package/dist/mcp-tools/cli.d.ts +3 -0
- package/dist/mcp-tools/cli.js +20 -0
- package/dist/mcp-tools/cli.js.map +1 -0
- package/dist/mcp-tools/explore.d.ts +3 -0
- package/dist/mcp-tools/explore.js +141 -0
- package/dist/mcp-tools/explore.js.map +1 -0
- package/dist/mcp-tools/findings.d.ts +3 -0
- package/dist/mcp-tools/findings.js +277 -0
- package/dist/mcp-tools/findings.js.map +1 -0
- package/dist/mcp-tools/inbox.d.ts +3 -0
- package/dist/mcp-tools/inbox.js +106 -0
- package/dist/mcp-tools/inbox.js.map +1 -0
- package/dist/mcp-tools/journey.d.ts +3 -0
- package/dist/mcp-tools/journey.js +260 -0
- package/dist/mcp-tools/journey.js.map +1 -0
- package/dist/mcp-tools/shared.d.ts +39 -0
- package/dist/mcp-tools/shared.js +69 -0
- package/dist/mcp-tools/shared.js.map +1 -0
- package/dist/mission-fixtures.js +1 -8
- package/dist/mission-fixtures.js.map +1 -1
- package/dist/mission-queue-cli.js +1 -3
- package/dist/mission-queue-cli.js.map +1 -1
- package/dist/multi-run.js +1 -3
- package/dist/multi-run.js.map +1 -1
- package/dist/pre-approval.js +4 -4
- package/dist/pre-approval.js.map +1 -1
- package/dist/readiness.js +2 -2
- package/dist/readiness.js.map +1 -1
- package/dist/report-api.js +1 -4
- package/dist/report-api.js.map +1 -1
- package/dist/resource-preflight.js.map +1 -1
- package/dist/run-metadata.js +1 -3
- package/dist/run-metadata.js.map +1 -1
- package/dist/run-tags-cli.d.ts +0 -2
- package/dist/run-tags-cli.js +0 -4
- package/dist/run-tags-cli.js.map +1 -1
- package/dist/session-check.js +1 -3
- package/dist/session-check.js.map +1 -1
- package/dist/signal-triage.js +6 -2
- package/dist/signal-triage.js.map +1 -1
- package/dist/suite-explore-options.d.ts +8 -0
- package/dist/suite-explore-options.js +4 -0
- package/dist/suite-explore-options.js.map +1 -1
- package/dist/sweep-api.d.ts +9 -0
- package/dist/sweep-api.js +70 -4
- package/dist/sweep-api.js.map +1 -1
- package/dist/sweep-cli.d.ts +2 -0
- package/dist/sweep-cli.js +22 -5
- package/dist/sweep-cli.js.map +1 -1
- package/dist/transcript-file.d.ts +0 -3
- package/dist/transcript-file.js +0 -9
- package/dist/transcript-file.js.map +1 -1
- package/dist/ui-api.js +1 -4
- package/dist/ui-api.js.map +1 -1
- package/dist/ux-usability/analysis.d.ts +14 -0
- package/dist/ux-usability/analysis.js +106 -0
- package/dist/ux-usability/analysis.js.map +1 -0
- package/dist/ux-usability/done-adjudication.d.ts +15 -0
- package/dist/ux-usability/done-adjudication.js +56 -0
- package/dist/ux-usability/done-adjudication.js.map +1 -0
- package/dist/ux-usability/hang.d.ts +5 -0
- package/dist/ux-usability/hang.js +41 -0
- package/dist/ux-usability/hang.js.map +1 -0
- package/dist/ux-usability/loop.d.ts +15 -0
- package/dist/ux-usability/loop.js +104 -0
- package/dist/ux-usability/loop.js.map +1 -0
- package/dist/ux-usability/observe.d.ts +7 -0
- package/dist/ux-usability/observe.js +89 -0
- package/dist/ux-usability/observe.js.map +1 -0
- package/dist/ux-usability/result.d.ts +11 -0
- package/dist/ux-usability/result.js +113 -0
- package/dist/ux-usability/result.js.map +1 -0
- package/dist/ux-usability/setup.d.ts +76 -0
- package/dist/ux-usability/setup.js +204 -0
- package/dist/ux-usability/setup.js.map +1 -0
- package/dist/ux-usability/teardown.d.ts +3 -0
- package/dist/ux-usability/teardown.js +13 -0
- package/dist/ux-usability/teardown.js.map +1 -0
- package/dist/ux-usability.d.ts +20 -17
- package/dist/ux-usability.js +15 -589
- package/dist/ux-usability.js.map +1 -1
- package/dist/verify-fix-api.js +1 -4
- package/dist/verify-fix-api.js.map +1 -1
- package/package.json +5 -6
- package/skills/jevitate-explore/SKILL.md +9 -1
- package/skills/jevitate-getting-started/SKILL.md +1 -0
- package/skills/jevitate-promotion-ready/SKILL.md +76 -0
- package/skills/jevitate-record/SKILL.md +3 -0
- package/skills/jevitate-run-journey/SKILL.md +3 -0
- package/skills/jevitate-test-campaign/SKILL.md +5 -3
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// ux-usability/analysis.ts — phase 3 of `runUsabilityMission` (#500): signals, probes, evidence sidecar, claim analysis.
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { a11yChecks, analyzeClaims, detectFriction, detectSignals, loadV1Rubric, persistableScreen } from "@jevitate/ux";
|
|
5
|
+
import { captureFindingShots, planGuardProbes, runGuardProbes, skippedProbes, withProbePage } from "../ux-claim-probe.js";
|
|
6
|
+
import { DEFAULT_JUDGMENT_BUDGET, extractTypedValues } from "../ux-evidence.js";
|
|
7
|
+
/**
|
|
8
|
+
* Phase 3 — evidence & analysis: the run's signal findings and friction, the guard probes, the
|
|
9
|
+
* evidence sidecar (written before analysis) and the claim analysis with its finding screenshots.
|
|
10
|
+
*/
|
|
11
|
+
export async function analyzeUsabilityRun(ctx, loop) {
|
|
12
|
+
const { opts, collected, overflowFindings, capture, secrets, session, outDir, stamp, product } = ctx;
|
|
13
|
+
const { run } = loop;
|
|
14
|
+
// #85 item 1: the live run's own typed values (from its emitted Recording's fill/select
|
|
15
|
+
// steps — never a secret), so the vocabulary/jargon tier can tell the app's own copy apart
|
|
16
|
+
// from user-authored content it merely echoed back (same mechanism as the offline pass).
|
|
17
|
+
const typedValues = extractTypedValues(run.recording);
|
|
18
|
+
const screens = typedValues.length > 0 ? collected.map((ev) => ({ ...ev, typedValues })) : collected;
|
|
19
|
+
// #96: findings from the run's own measurements (hung request, duplicate write, internal id,
|
|
20
|
+
// inert control) — independent code, no model — reported alongside the rubric's.
|
|
21
|
+
const signalCapture = await capture.signalCapture(run.transcript, typedValues);
|
|
22
|
+
// A gRPC-web/Connect read is never a duplicate write (#110); `--read-rpc` marks more reads.
|
|
23
|
+
const readRequests = opts.target?.safety?.readRequests;
|
|
24
|
+
const signalFindings = [
|
|
25
|
+
...detectSignals(signalCapture, readRequests === undefined ? opts.signals : { ...opts.signals, readRequests }),
|
|
26
|
+
// #149: horizontal-overflow, computed live during the run (never from the captured timeline).
|
|
27
|
+
...overflowFindings,
|
|
28
|
+
];
|
|
29
|
+
// #132: the friction the run walked into — what grounds (or not) each rubric finding.
|
|
30
|
+
const friction = detectFriction(signalCapture, run.outcome);
|
|
31
|
+
// #198: every destructive control on an analyzed screen is clicked once with its writes blocked
|
|
32
|
+
// (ux-claim-probe.ts) — the code evidence `destructive-unguarded` claims are verified against.
|
|
33
|
+
// On a dedicated page in the run's context, after the loop: the run's oracles never see it.
|
|
34
|
+
// A screen that cannot be redacted means no probes at all (destructive claims: unverifiable).
|
|
35
|
+
let probes;
|
|
36
|
+
try {
|
|
37
|
+
const plan = planGuardProbes(screens, secrets, opts.target?.safety);
|
|
38
|
+
probes = [...plan.refused];
|
|
39
|
+
// Clicking is opt-in (--probe-guards): without it every destructive control is `skipped`, and
|
|
40
|
+
// its guard claim is reported unverifiable — never asserted, never silently dropped.
|
|
41
|
+
if (opts.probeGuards !== true)
|
|
42
|
+
probes.push(...skippedProbes(plan.targets));
|
|
43
|
+
else if (plan.targets.length > 0) {
|
|
44
|
+
try {
|
|
45
|
+
probes.push(...(await runGuardProbes(session.page, plan.targets, { allowlist: opts.allowlist, secrets })));
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
const why = `the guard probe could not open a page: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`;
|
|
49
|
+
probes.push(...plan.targets.map((t) => ({ screenId: t.screen.screenId, route: t.route, control: t.label, controlKey: t.key, status: "failed", detail: why })));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
probes = undefined;
|
|
55
|
+
}
|
|
56
|
+
// #134: the evidence sidecar, written through the redaction door BEFORE analysis (so it exists
|
|
57
|
+
// even when analysis fails). Fail-closed: if any screen cannot be redacted, no file is written.
|
|
58
|
+
const evidencePath = join(outDir, `usability-${stamp}.evidence.json`);
|
|
59
|
+
let evidenceWritten = null;
|
|
60
|
+
try {
|
|
61
|
+
const file = {
|
|
62
|
+
version: 1,
|
|
63
|
+
appContext: opts.appContext,
|
|
64
|
+
job: opts.job,
|
|
65
|
+
screens: screens.map((ev) => persistableScreen(ev, secrets)),
|
|
66
|
+
signals: signalCapture,
|
|
67
|
+
outcome: run.outcome,
|
|
68
|
+
...(probes === undefined ? {} : { probes }),
|
|
69
|
+
};
|
|
70
|
+
await mkdir(outDir, { recursive: true });
|
|
71
|
+
await writeFile(evidencePath, `${JSON.stringify(file, null, 2)}\n`, "utf8");
|
|
72
|
+
evidenceWritten = evidencePath;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
evidenceWritten = null;
|
|
76
|
+
}
|
|
77
|
+
// #198: findings are claims verified by code (claims.ts) — guard probes, product facts and the
|
|
78
|
+
// friction the run walked into — categorized and graded by Jev, written from templates.
|
|
79
|
+
const claimed = await analyzeClaims({
|
|
80
|
+
screens,
|
|
81
|
+
rubric: loadV1Rubric(),
|
|
82
|
+
appContext: opts.appContext,
|
|
83
|
+
secrets,
|
|
84
|
+
judgmentBudget: opts.judgmentBudget ?? DEFAULT_JUDGMENT_BUDGET,
|
|
85
|
+
friction,
|
|
86
|
+
steps: signalCapture.steps,
|
|
87
|
+
signalFindings,
|
|
88
|
+
...(probes === undefined ? {} : { probes }),
|
|
89
|
+
...(product.facts === undefined ? {} : { facts: product.facts }),
|
|
90
|
+
}, { judge: opts.judge, gen: opts.gen, a11yChecker: a11yChecks, ...(opts.polish === true ? { polish: true } : {}) });
|
|
91
|
+
// #198: a cropped, masked screenshot with the cited control boxed, per verified claim finding.
|
|
92
|
+
let outcome = claimed;
|
|
93
|
+
if (claimed.kind === "analyzed" && claimed.findings.some((f) => f.claim !== undefined)) {
|
|
94
|
+
const byScreen = new Map(screens.map((ev) => [ev.screenId, ev]));
|
|
95
|
+
const dir = join(outDir, `usability-${stamp}.findings`);
|
|
96
|
+
try {
|
|
97
|
+
const findings = await withProbePage(session.page, (p) => captureFindingShots(p, claimed.findings, byScreen, { dir, allowlist: opts.allowlist, secrets }));
|
|
98
|
+
outcome = { ...claimed, findings };
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
outcome = claimed; // presentation only: findings without screenshots are still the findings
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { signalFindings, friction, outcome, evidenceWritten };
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=analysis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analysis.js","sourceRoot":"","sources":["../../src/ux-usability/analysis.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type HangFinding } from "@jevitate/explore";
|
|
2
|
+
import { type MissionFailure } from "@jevitate/domain";
|
|
3
|
+
import type { UsabilitySetup } from "./setup.js";
|
|
4
|
+
import type { UsabilityLoop } from "./loop.js";
|
|
5
|
+
/**
|
|
6
|
+
* Phase 5 — done adjudication: the job's verdict from the loop's stop, its outcome (was the job
|
|
7
|
+
* completed / verified?) and its success checks, then the host-health fold. The ONE place the
|
|
8
|
+
* mission decides whether the job under review was done (#209/#213/#225).
|
|
9
|
+
*/
|
|
10
|
+
export declare function adjudicateUsabilityJob(ctx: UsabilitySetup, loop: UsabilityLoop, hang: HangFinding | undefined): Promise<{
|
|
11
|
+
host: import("../host-health-run.js").HostHealthVerdict<"clean" | "pending-review" | "defects-found" | "hang" | "intermittent" | "inconclusive" | "crashed">;
|
|
12
|
+
runOutcome: "clean" | "pending-review" | "defects-found" | "hang" | "intermittent" | "inconclusive" | "crashed";
|
|
13
|
+
jobFailure: MissionFailure | undefined;
|
|
14
|
+
}>;
|
|
15
|
+
export type UsabilityVerdict = Awaited<ReturnType<typeof adjudicateUsabilityJob>>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// ux-usability/done-adjudication.ts — phase 5 of `runUsabilityMission` (#500): was the job done? The mission's verdict.
|
|
2
|
+
import { hangOutcome } from "@jevitate/explore";
|
|
3
|
+
import { foldGoalOutcome } from "@jevitate/domain";
|
|
4
|
+
import { finishHostHealth } from "../host-health-run.js";
|
|
5
|
+
/**
|
|
6
|
+
* Phase 5 — done adjudication: the job's verdict from the loop's stop, its outcome (was the job
|
|
7
|
+
* completed / verified?) and its success checks, then the host-health fold. The ONE place the
|
|
8
|
+
* mission decides whether the job under review was done (#209/#213/#225).
|
|
9
|
+
*/
|
|
10
|
+
export async function adjudicateUsabilityJob(ctx, loop, hang) {
|
|
11
|
+
const { health } = ctx;
|
|
12
|
+
const { run, adjudication } = loop;
|
|
13
|
+
// #150 — a declared mission spend budget crossed (or a paid action was refused before crossing
|
|
14
|
+
// it): a clean, deliberate stop, never `crashed` — but never `clean` either (the run's own work
|
|
15
|
+
// past the stop is unproven), so it maps to `inconclusive` the same as `run.stop === "inconclusive"`.
|
|
16
|
+
const loopOutcome = run.stop === "crashed"
|
|
17
|
+
? "crashed"
|
|
18
|
+
: run.stop === "inconclusive" || run.stop === "budget"
|
|
19
|
+
? "inconclusive"
|
|
20
|
+
: run.stop === "hang"
|
|
21
|
+
? hangOutcome(hang?.reproduction.status ?? "inconclusive")
|
|
22
|
+
: "clean";
|
|
23
|
+
// #209: a review whose job was never completed (the loop gave up, ran out of budget, or its
|
|
24
|
+
// `done` was never verified) did not see what a user who finished it would: its silence about
|
|
25
|
+
// the rest proves nothing, so it is `inconclusive` (`job-incomplete`) — never `clean`, whatever
|
|
26
|
+
// the advisory UX findings say.
|
|
27
|
+
const jobIncomplete = loopOutcome === "clean" && run.outcome.status === "incomplete"
|
|
28
|
+
? { kind: "job-incomplete", message: `the job under review was not completed: ${run.outcome.reason}` }
|
|
29
|
+
: undefined;
|
|
30
|
+
// #225: with success checks, THEY decide whether the job was done — folded exactly like a goal
|
|
31
|
+
// run's ending (a failed check `defects-found`, a vacuous one `inconclusive`, all held `clean`).
|
|
32
|
+
const checked = adjudication === undefined ? undefined : foldGoalOutcome(adjudication.outcome);
|
|
33
|
+
const jobVerdict = loopOutcome !== "clean" ? loopOutcome : checked !== undefined ? checked : jobIncomplete === undefined ? "clean" : "inconclusive";
|
|
34
|
+
// #225/#209: a failed success check is named as such (never "job-incomplete"), as on a goal run.
|
|
35
|
+
const failedChecks = adjudication?.checks.filter((c) => !c.passed) ?? [];
|
|
36
|
+
const checkFailure = adjudication?.failure ??
|
|
37
|
+
(failedChecks.length === 0
|
|
38
|
+
? undefined
|
|
39
|
+
: { kind: "success-check-failed", message: `success check ${failedChecks.map((c) => `'${c.check}' ${c.detail}`).join("; ")}` });
|
|
40
|
+
const jobFailure = loopOutcome !== "clean" ? undefined : checked === undefined ? jobIncomplete : checked === "clean" ? undefined : (checkFailure ?? jobIncomplete);
|
|
41
|
+
// #203: most steps on a starved host → `inconclusive` (degraded-environment), never `clean`.
|
|
42
|
+
// #213: a job whose completion code verified (#225: its success checks held, or its done was
|
|
43
|
+
// adjudicated from save signals) is a positive proof — a starved host does not undo it. Only an
|
|
44
|
+
// unverified ending is downgraded, and it keeps its own reason (a failed check) in the degraded one.
|
|
45
|
+
// A model-only `grounded-judgment` is not code's proof, so it is still downgraded.
|
|
46
|
+
const jobVerified = jobVerdict === "clean" &&
|
|
47
|
+
(checked === "clean" || (checked === undefined && run.outcome.status === "completed" && run.outcome.verifiedBy !== "grounded-judgment"));
|
|
48
|
+
const host = await finishHostHealth(health, jobVerdict, {
|
|
49
|
+
verified: jobVerified,
|
|
50
|
+
...(run.failure === undefined ? {} : { failure: run.failure }),
|
|
51
|
+
...(jobFailure === undefined ? {} : { wouldHaveBeen: jobFailure.message }),
|
|
52
|
+
});
|
|
53
|
+
const runOutcome = host.outcome;
|
|
54
|
+
return { host, runOutcome, jobFailure };
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=done-adjudication.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"done-adjudication.js","sourceRoot":"","sources":["../../src/ux-usability/done-adjudication.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type HangFinding } from "@jevitate/explore";
|
|
2
|
+
import type { UsabilitySetup } from "./setup.js";
|
|
3
|
+
import type { UsabilityLoop } from "./loop.js";
|
|
4
|
+
/** Phase 4 — a run that stopped on a hang: reproduced in fresh contexts (`undefined` when it did not). */
|
|
5
|
+
export declare function reproduceUsabilityHang(ctx: UsabilitySetup, run: UsabilityLoop["run"]): Promise<HangFinding | undefined>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// ux-usability/hang.ts — phase 4 of `runUsabilityMission` (#500): hang reproduction.
|
|
2
|
+
import { journeyFixtureBaseDir } from "../journey-api.js";
|
|
3
|
+
import { CastActor, BrowseTheWeb } from "@jevitate/screenplay";
|
|
4
|
+
import { reproduceHang, hangFinding } from "@jevitate/explore";
|
|
5
|
+
/**
|
|
6
|
+
* Opens a FRESH browser session for replays (hang reproduction): a new context from the same port
|
|
7
|
+
* and options — same authenticated storageState, never the session the finding was made in.
|
|
8
|
+
*/
|
|
9
|
+
function freshSessionOpener(portFactory, launch, allowlist) {
|
|
10
|
+
return async () => {
|
|
11
|
+
const session = await portFactory().open(launch);
|
|
12
|
+
const actor = CastActor.named("replay").whoCan(new BrowseTheWeb(session, [...allowlist]));
|
|
13
|
+
return { page: session.page, actor, close: () => session.close() };
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** Phase 4 — a run that stopped on a hang: reproduced in fresh contexts (`undefined` when it did not). */
|
|
17
|
+
export async function reproduceUsabilityHang(ctx, run) {
|
|
18
|
+
const { opts, portFactory, launch } = ctx;
|
|
19
|
+
// #126: a run that stops on a hang is never `clean` — it is reproduced in fresh contexts (same
|
|
20
|
+
// as a goal mission) and mapped through the same hang/intermittent/inconclusive rule.
|
|
21
|
+
let hang;
|
|
22
|
+
if (run.stop === "hang" && run.hang !== undefined) {
|
|
23
|
+
const h = run.hang;
|
|
24
|
+
const reproduction = await reproduceHang({
|
|
25
|
+
recording: run.recording,
|
|
26
|
+
recordingStepIndex: h.recordingStepIndex,
|
|
27
|
+
hang: h.signal,
|
|
28
|
+
openSession: freshSessionOpener(portFactory, launch, opts.allowlist),
|
|
29
|
+
fixtureBaseDir: journeyFixtureBaseDir(),
|
|
30
|
+
...(opts.target?.safety === undefined ? {} : { safety: opts.target.safety }),
|
|
31
|
+
writtenBy: run.sideEffects.map((e) => e.control), // #181
|
|
32
|
+
perceive: {
|
|
33
|
+
...(opts.target?.settle === undefined ? {} : { settleConfig: opts.target.settle }),
|
|
34
|
+
...(opts.target?.hangs === undefined ? {} : { hangConfig: opts.target.hangs }),
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
hang = hangFinding(h.signal, run.transcript, h.recordingStepIndex, reproduction);
|
|
38
|
+
}
|
|
39
|
+
return hang;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=hang.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hang.js","sourceRoot":"","sources":["../../src/ux-usability/hang.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAwC,MAAM,mBAAmB,CAAC;AAIrG;;;GAGG;AACH,SAAS,kBAAkB,CACzB,WAA8B,EAC9B,MAA0C,EAC1C,SAA4B;IAE5B,OAAO,KAAK,IAAI,EAAE;QAChB,MAAM,OAAO,GAAG,MAAM,WAAW,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACjD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QAC1F,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;IACrE,CAAC,CAAC;AACJ,CAAC;AAED,0GAA0G;AAC1G,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,GAAmB,EAAE,GAAyB;IACzF,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IAC1C,+FAA+F;IAC/F,sFAAsF;IACtF,IAAI,IAA6B,CAAC;IAClC,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAClD,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC;QACnB,MAAM,YAAY,GAAG,MAAM,aAAa,CAAC;YACvC,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,kBAAkB,EAAE,CAAC,CAAC,kBAAkB;YACxC,IAAI,EAAE,CAAC,CAAC,MAAM;YACd,WAAW,EAAE,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC;YACpE,cAAc,EAAE,qBAAqB,EAAE;YACvC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YAC5E,SAAS,EAAE,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO;YACzD,QAAQ,EAAE;gBACR,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClF,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;aAC/E;SACF,CAAC,CAAC;QACH,IAAI,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,kBAAkB,EAAE,YAAY,CAAC,CAAC;IACnF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type GoalBasedResult, BudgetMonitor } from "@jevitate/explore";
|
|
2
|
+
import type { UsabilitySetup } from "./setup.js";
|
|
3
|
+
/**
|
|
4
|
+
* Phase 2 — the act loop: the Journey prefix (if any), the budget hooks, then the model-driven loop —
|
|
5
|
+
* through the goal mission's independent adjudication when `successChecks` were given, else
|
|
6
|
+
* `explore()` — and the drained server-log transcript, Recording and transcript written.
|
|
7
|
+
*/
|
|
8
|
+
export declare function runUsabilityLoop(ctx: UsabilitySetup): Promise<{
|
|
9
|
+
start: import("../journey-prefix.js").AnchoredStart;
|
|
10
|
+
budget: BudgetMonitor | null;
|
|
11
|
+
adjudication: GoalBasedResult | undefined;
|
|
12
|
+
run: import("@jevitate/explore").ExploreRun;
|
|
13
|
+
serverLogRun: import("../log-correlation.js").ServerLogRuntimeResult | undefined;
|
|
14
|
+
}>;
|
|
15
|
+
export type UsabilityLoop = Awaited<ReturnType<typeof runUsabilityLoop>>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// ux-usability/loop.ts — phase 2 of `runUsabilityMission` (#500): the model-driven act loop.
|
|
2
|
+
import { journeyFixtureBaseDir } from "../journey-api.js";
|
|
3
|
+
import { demoOverlayOf } from "../browser-run-options.js";
|
|
4
|
+
import { CastActor, BrowseTheWeb } from "@jevitate/screenplay";
|
|
5
|
+
import { explore, runGoalBasedMission, InvariantMonitor, BudgetMonitor } from "@jevitate/explore";
|
|
6
|
+
import { conversationConfig } from "../conversation-options.js";
|
|
7
|
+
import { startFromJourney } from "../journey-prefix.js";
|
|
8
|
+
import { usabilityOnSnapshot } from "./observe.js";
|
|
9
|
+
/**
|
|
10
|
+
* Phase 2 — the act loop: the Journey prefix (if any), the budget hooks, then the model-driven loop —
|
|
11
|
+
* through the goal mission's independent adjudication when `successChecks` were given, else
|
|
12
|
+
* `explore()` — and the drained server-log transcript, Recording and transcript written.
|
|
13
|
+
*/
|
|
14
|
+
export async function runUsabilityLoop(ctx) {
|
|
15
|
+
const { opts, session, origin, serverLog, journalListener, journal, health, uxSecretCommand, fixture, capture } = ctx;
|
|
16
|
+
// #293: a journey-anchored review first replays its Journey's prefix into this very session.
|
|
17
|
+
const start = await startFromJourney(opts.journeyPrefix, session, opts.url, opts.allowlist, opts.browser);
|
|
18
|
+
const actor = CastActor.named("usability-mission").whoCan(new BrowseTheWeb(session, [...opts.allowlist]));
|
|
19
|
+
// #150 — usability's own budget wiring: a plain `InvariantMonitor` reads a budget's declared
|
|
20
|
+
// observables (the same #86/#135 read/auth/redaction machinery), but this mission folds NO
|
|
21
|
+
// invariant defects — only a budget crossing can end the run early, via the same pre-action
|
|
22
|
+
// guard / post-settle hooks `explore()` offers every mission.
|
|
23
|
+
const budgetDecls = opts.invariants?.budget ?? [];
|
|
24
|
+
const invariantMonitor = opts.invariants === undefined || budgetDecls.length === 0
|
|
25
|
+
? null
|
|
26
|
+
: new InvariantMonitor(opts.invariants, {
|
|
27
|
+
allowlist: opts.allowlist,
|
|
28
|
+
baseUrl: opts.url,
|
|
29
|
+
...(opts.secrets === undefined ? {} : { secrets: opts.secrets }),
|
|
30
|
+
...(opts.invariantAuthTokens === undefined ? {} : { authTokens: opts.invariantAuthTokens }),
|
|
31
|
+
});
|
|
32
|
+
const budget = invariantMonitor === null ? null : new BudgetMonitor(budgetDecls, invariantMonitor);
|
|
33
|
+
let budgetSettledSteps = 0;
|
|
34
|
+
const exploreCfg = {
|
|
35
|
+
...(opts.target?.timing === undefined ? {} : { timingConfig: opts.target.timing }),
|
|
36
|
+
...(opts.target?.settle === undefined ? {} : { settle: opts.target.settle }),
|
|
37
|
+
...(opts.target?.hangs === undefined ? {} : { hangs: opts.target.hangs }),
|
|
38
|
+
...(opts.target?.safety === undefined ? {} : { safety: opts.target.safety }),
|
|
39
|
+
onTranscriptEntry: serverLog?.onTranscriptEntry ?? journalListener,
|
|
40
|
+
onRecording: journal.onRecording,
|
|
41
|
+
// #303 (opt-in): action deltas, with Jev's advisory relevance labels.
|
|
42
|
+
...(opts.actionDeltas === true ? { actionDeltas: { jev: true } } : {}),
|
|
43
|
+
hostHealth: health,
|
|
44
|
+
demoOverlay: demoOverlayOf(opts.browser),
|
|
45
|
+
...(opts.secretFields === undefined ? {} : { secretFields: opts.secretFields }),
|
|
46
|
+
...(uxSecretCommand === undefined ? {} : { secretCommand: uxSecretCommand }),
|
|
47
|
+
...(opts.secretCommandAttempts === undefined ? {} : { secretCommandAttempts: opts.secretCommandAttempts }),
|
|
48
|
+
actor,
|
|
49
|
+
judge: opts.judge,
|
|
50
|
+
gen: opts.gen,
|
|
51
|
+
goal: opts.job,
|
|
52
|
+
allowlist: opts.allowlist,
|
|
53
|
+
startUrl: start.url,
|
|
54
|
+
...(start.branch === undefined ? {} : { startInPlace: true }),
|
|
55
|
+
...(start.restart === undefined ? {} : { restartAtStart: start.restart.restartAtStart }),
|
|
56
|
+
bounds: opts.bounds,
|
|
57
|
+
secrets: opts.secrets,
|
|
58
|
+
site: origin,
|
|
59
|
+
fixture,
|
|
60
|
+
fixtureRoot: journeyFixtureBaseDir(),
|
|
61
|
+
...conversationConfig(opts.conversation),
|
|
62
|
+
onSnapshot: usabilityOnSnapshot(ctx),
|
|
63
|
+
...(budget === null
|
|
64
|
+
? {}
|
|
65
|
+
: {
|
|
66
|
+
onBeforeAction: async (info) => {
|
|
67
|
+
const g = await budget.guard(session.page, info);
|
|
68
|
+
return g.refuse ? { refuse: true, reason: g.reason ?? "budget guard refused the action" } : { refuse: false };
|
|
69
|
+
},
|
|
70
|
+
onSettled: async () => {
|
|
71
|
+
budgetSettledSteps += 1;
|
|
72
|
+
// The FIRST settled snapshot (before any action) is the budget's baseline.
|
|
73
|
+
if (budgetSettledSteps === 1) {
|
|
74
|
+
const b = await budget.baseline(session.page);
|
|
75
|
+
return b.crossed ? { stop: true, reason: b.reason ?? "budget observable unreadable at run start" } : { stop: false };
|
|
76
|
+
}
|
|
77
|
+
const r = await budget.afterSettle(session.page, budgetSettledSteps);
|
|
78
|
+
return r.crossed ? { stop: true, reason: r.reason ?? "mission budget crossed" } : { stop: false };
|
|
79
|
+
},
|
|
80
|
+
}),
|
|
81
|
+
};
|
|
82
|
+
const brief = "usability review: pursue the stated job as a plausible first-time user, using only what is on screen";
|
|
83
|
+
// #225: `--success` is never ignored — with checks the loop runs through the goal mission's
|
|
84
|
+
// independent adjudication (the same oracle, `held`/vacuous rules and verdict as a goal run).
|
|
85
|
+
const checks = opts.successChecks ?? [];
|
|
86
|
+
const adjudication = checks.length === 0
|
|
87
|
+
? undefined
|
|
88
|
+
: await runGoalBasedMission({
|
|
89
|
+
...exploreCfg,
|
|
90
|
+
missionBrief: brief,
|
|
91
|
+
successChecks: checks,
|
|
92
|
+
// #225: a `done` whose check failed on a job judged done ends the run — never the whole budget.
|
|
93
|
+
stopWhenJudgedDone: true,
|
|
94
|
+
...(opts.successWhen === undefined ? {} : { successWhen: opts.successWhen }),
|
|
95
|
+
...(opts.allowVacuousChecks === true ? { allowVacuousChecks: true } : {}),
|
|
96
|
+
});
|
|
97
|
+
const run = adjudication?.run ?? (await explore({ ...exploreCfg, missionContext: brief }));
|
|
98
|
+
// Never blocks the mission itself: the drain wait happens AFTER `explore()` returned.
|
|
99
|
+
const serverLogRun = serverLog === undefined ? undefined : await serverLog.finish(run.transcript);
|
|
100
|
+
journal.writeRecording(run.recording);
|
|
101
|
+
journal.writeTranscript(capture.withScreenshots(serverLogRun?.transcript ?? run.transcript));
|
|
102
|
+
return { start, budget, adjudication, run, serverLogRun };
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop.js","sourceRoot":"","sources":["../../src/ux-usability/loop.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ExploreConfig } from "@jevitate/explore";
|
|
2
|
+
import type { UsabilitySetup } from "./setup.js";
|
|
3
|
+
/**
|
|
4
|
+
* Phase 2a — the act loop's `onSnapshot` hook: one screen's evidence per observation, its
|
|
5
|
+
* (secret-masked) screenshot and the overflow/clipping hard signals. Never gates the loop.
|
|
6
|
+
*/
|
|
7
|
+
export declare function usabilityOnSnapshot(ctx: UsabilitySetup): NonNullable<ExploreConfig["onSnapshot"]>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// ux-usability/observe.ts — the usability act loop's per-screen observation hook (#500).
|
|
2
|
+
import { clippingSummary, detectClipping, detectOverflow, shouldCheckOverflow } from "@jevitate/explore";
|
|
3
|
+
import { makeSignalFinding } from "@jevitate/ux";
|
|
4
|
+
import { snapshotToEvidence } from "../ux-evidence.js";
|
|
5
|
+
/**
|
|
6
|
+
* Phase 2a — the act loop's `onSnapshot` hook: one screen's evidence per observation, its
|
|
7
|
+
* (secret-masked) screenshot and the overflow/clipping hard signals. Never gates the loop.
|
|
8
|
+
*/
|
|
9
|
+
export function usabilityOnSnapshot(ctx) {
|
|
10
|
+
const { opts, extract, session, collected, history, capture, seenOverflow, overflowFindings, secrets } = ctx;
|
|
11
|
+
return async (snap) => {
|
|
12
|
+
let visibleText = "";
|
|
13
|
+
try {
|
|
14
|
+
visibleText = await extract(session);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
visibleText = ""; // best-effort; the analyzer Skips visibleText items honestly
|
|
18
|
+
}
|
|
19
|
+
const ev = snapshotToEvidence(snap, visibleText, opts.appContext, opts.job, [...history]);
|
|
20
|
+
collected.push(ev);
|
|
21
|
+
history.push({ screenId: ev.screenId, url: ev.url });
|
|
22
|
+
// #98 the step's (secret-masked) screenshot; #96 the screen's facts for the signal oracles.
|
|
23
|
+
await capture.observe(snap, visibleText);
|
|
24
|
+
// #149: horizontal-overflow hard signal — pure DOM geometry, never a model judgment.
|
|
25
|
+
// Best-effort like visibleText extraction above: a detection failure never fails the mission.
|
|
26
|
+
try {
|
|
27
|
+
const vp = session.page.viewportSize();
|
|
28
|
+
if (shouldCheckOverflow(vp?.width, opts.overflow?.checkOverflow ?? false)) {
|
|
29
|
+
const overflow = await detectOverflow(session.page, {
|
|
30
|
+
viewport: vp ?? { width: 1280, height: 720 },
|
|
31
|
+
...(opts.emulation?.device === undefined ? {} : { device: opts.emulation.device }),
|
|
32
|
+
...(opts.overflow?.toleranceCss === undefined ? {} : { toleranceCss: opts.overflow.toleranceCss }),
|
|
33
|
+
...(opts.overflow?.ignoreSelectors === undefined ? {} : { ignoreSelectors: opts.overflow.ignoreSelectors }),
|
|
34
|
+
secrets,
|
|
35
|
+
});
|
|
36
|
+
if (overflow !== null && !seenOverflow.has(overflow.fingerprint)) {
|
|
37
|
+
seenOverflow.add(overflow.fingerprint);
|
|
38
|
+
overflowFindings.push(makeSignalFinding({
|
|
39
|
+
kind: "horizontal-overflow",
|
|
40
|
+
confidence: 0.9,
|
|
41
|
+
url: ev.url,
|
|
42
|
+
screenId: ev.screenId,
|
|
43
|
+
observation: `${overflow.element.descriptor} overflows the ${overflow.viewport.width}px viewport by ${overflow.overflowPx}px on ${overflow.route}.`,
|
|
44
|
+
userImpact: "Content extends past the visible viewport; a user on this device must discover and use horizontal scrolling to see it, and may miss it entirely.",
|
|
45
|
+
recommendation: `Constrain ${overflow.element.descriptor} to the viewport width (e.g. a responsive layout, or an explicit scroll container) at ${overflow.viewport.width}px.`,
|
|
46
|
+
controls: [overflow.element.descriptor],
|
|
47
|
+
evidence: {
|
|
48
|
+
kind: "horizontal-overflow",
|
|
49
|
+
steps: [history.length],
|
|
50
|
+
requests: [],
|
|
51
|
+
detail: `scrollWidth exceeds innerWidth by ${overflow.overflowPx}px at a ${overflow.viewport.width}x${overflow.viewport.height} viewport`,
|
|
52
|
+
},
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
// #302: text cut off vertically (a fixed-height box, or above the page top) — one finding per element.
|
|
56
|
+
const clipped = await detectClipping(session.page, {
|
|
57
|
+
viewport: vp ?? { width: 1280, height: 720 },
|
|
58
|
+
...(opts.emulation?.device === undefined ? {} : { device: opts.emulation.device }),
|
|
59
|
+
...(opts.overflow?.ignoreSelectors === undefined ? {} : { ignoreSelectors: opts.overflow.ignoreSelectors }),
|
|
60
|
+
secrets,
|
|
61
|
+
});
|
|
62
|
+
for (const c of clipped) {
|
|
63
|
+
if (seenOverflow.has(c.fingerprint))
|
|
64
|
+
continue;
|
|
65
|
+
seenOverflow.add(c.fingerprint);
|
|
66
|
+
overflowFindings.push(makeSignalFinding({
|
|
67
|
+
kind: "vertical-clipping",
|
|
68
|
+
confidence: 0.9,
|
|
69
|
+
url: ev.url,
|
|
70
|
+
screenId: ev.screenId,
|
|
71
|
+
observation: c.cause === "overflow-hidden"
|
|
72
|
+
? `${c.element.descriptor} cuts off its text by ${c.clippedPx}px on ${c.route}: the content is taller than the box and overflow is hidden.`
|
|
73
|
+
: `${c.element.descriptor} is cut off ${c.clippedPx}px above the top of the page on ${c.route}.`,
|
|
74
|
+
userImpact: "Part of the text is cut off and no scroll position shows it: a user on this device cannot read it.",
|
|
75
|
+
recommendation: c.cause === "overflow-hidden"
|
|
76
|
+
? `Let ${c.element.descriptor} grow with its content (min-height instead of height), make it scrollable, or truncate on purpose with line-clamp at ${c.viewport.width}px.`
|
|
77
|
+
: `Give the container of ${c.element.descriptor} room for its wrapped content (no fixed height, or no wrapping) at ${c.viewport.width}px.`,
|
|
78
|
+
controls: [c.element.descriptor],
|
|
79
|
+
evidence: { kind: "vertical-clipping", steps: [history.length], requests: [], detail: clippingSummary(c) },
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Best-effort: the run's own explore loop is never held up or failed by this check.
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=observe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observe.js","sourceRoot":"","sources":["../../src/ux-usability/observe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { HangFinding } from "@jevitate/explore";
|
|
2
|
+
import type { RunUsabilityMissionResult } from "../ux-usability.js";
|
|
3
|
+
import type { UsabilitySetup } from "./setup.js";
|
|
4
|
+
import type { UsabilityLoop } from "./loop.js";
|
|
5
|
+
import type { UsabilityAnalysis } from "./analysis.js";
|
|
6
|
+
import type { UsabilityVerdict } from "./done-adjudication.js";
|
|
7
|
+
/**
|
|
8
|
+
* Phase 6 — result: every context closed (videos finalized), the result's common fields, then the
|
|
9
|
+
* report (or the typed analysis-unavailable result), persisted and returned with its run evidence.
|
|
10
|
+
*/
|
|
11
|
+
export declare function writeUsabilityResult(ctx: UsabilitySetup, loop: UsabilityLoop, analysis: UsabilityAnalysis, hang: HangFinding | undefined, verdict: UsabilityVerdict): Promise<RunUsabilityMissionResult>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// ux-usability/result.ts — phase 6 of `runUsabilityMission` (#500): the report and the typed, persisted result.
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { resolve as resolvePath } from "node:path";
|
|
4
|
+
import { finalizeVideos } from "../browser-run-options.js";
|
|
5
|
+
import { evidenceOf, withRunEvidence } from "../defect-evidence.js";
|
|
6
|
+
import { buildReport, calibrationCaveat, claimsCaveat, groundFindings, withSignalFindings } from "@jevitate/ux";
|
|
7
|
+
import { NO_PRODUCT_FACTS_CAVEAT } from "../ux-product.js";
|
|
8
|
+
import { resultPathFor, writeMissionResult } from "../mission-journal.js";
|
|
9
|
+
import { MISSION_RESULT_SCHEMA_VERSION, advisoryDefects, defectFields } from "../result-schema.js";
|
|
10
|
+
import { missionExitCode } from "../mission-exit.js";
|
|
11
|
+
import { branchFields } from "../journey-prefix.js";
|
|
12
|
+
import { failureWithHostStarved } from "../host-health-run.js";
|
|
13
|
+
import { currentEngineInfo } from "../engine.js";
|
|
14
|
+
import { triageOf } from "../explore-shared.js";
|
|
15
|
+
import { serverLogResult } from "../explore-api.js";
|
|
16
|
+
import { UxAnalysisFailedError } from "../ux-review.js";
|
|
17
|
+
/**
|
|
18
|
+
* Phase 6 — result: every context closed (videos finalized), the result's common fields, then the
|
|
19
|
+
* report (or the typed analysis-unavailable result), persisted and returned with its run evidence.
|
|
20
|
+
*/
|
|
21
|
+
export async function writeUsabilityResult(ctx, loop, analysis, hang, verdict) {
|
|
22
|
+
const { opts, runCapture, videoDir, closeSession, journal, collected, screenshotDir, capture, runUsage, http5xx, secrets, minConfidence, quality, maxFindingsPerRoute, product, reportPath } = ctx;
|
|
23
|
+
const { start, run, serverLogRun, budget, adjudication } = loop;
|
|
24
|
+
const { signalFindings, friction, outcome, evidenceWritten } = analysis;
|
|
25
|
+
const { host, runOutcome, jobFailure } = verdict;
|
|
26
|
+
// #245: every context closed (videos finalized) before the result naming them is written.
|
|
27
|
+
const shotFields = await runCapture.finish();
|
|
28
|
+
const videos = await finalizeVideos(videoDir, closeSession);
|
|
29
|
+
const base = {
|
|
30
|
+
schemaVersion: MISSION_RESULT_SCHEMA_VERSION,
|
|
31
|
+
strategy: "usability",
|
|
32
|
+
target: {
|
|
33
|
+
seedUrl: start.persistUrl,
|
|
34
|
+
allowlist: [...opts.allowlist],
|
|
35
|
+
...(opts.storageState !== undefined ? { storageStatePath: resolvePath(opts.storageState) } : {}),
|
|
36
|
+
},
|
|
37
|
+
...branchFields(start),
|
|
38
|
+
recordingPaths: [journal.recordingPath],
|
|
39
|
+
...videos,
|
|
40
|
+
...shotFields,
|
|
41
|
+
resultPath: resultPathFor(journal.recordingPath),
|
|
42
|
+
hangs: hang === undefined ? [] : [hang],
|
|
43
|
+
timing: run.timing,
|
|
44
|
+
stop: run.stop,
|
|
45
|
+
outcome: run.outcome,
|
|
46
|
+
...(run.answer === undefined ? {} : { answer: run.answer }),
|
|
47
|
+
screensObserved: collected.length,
|
|
48
|
+
transcriptPath: journal.transcriptPath,
|
|
49
|
+
screenshotDir,
|
|
50
|
+
evidencePath: evidenceWritten,
|
|
51
|
+
screenshots: capture.screenshots(),
|
|
52
|
+
sideEffects: run.sideEffects,
|
|
53
|
+
...(run.sideEffectsTruncated === undefined ? {} : { sideEffectsTruncated: run.sideEffectsTruncated }),
|
|
54
|
+
...(run.safetyOverrides === undefined ? {} : { safetyOverrides: run.safetyOverrides }),
|
|
55
|
+
...(run.actionDeltas === undefined ? {} : { actionDeltas: run.actionDeltas }),
|
|
56
|
+
engine: currentEngineInfo(),
|
|
57
|
+
...(() => {
|
|
58
|
+
const f = failureWithHostStarved(host, run.failure ?? host.failure ?? jobFailure);
|
|
59
|
+
return f === undefined ? {} : { failure: f };
|
|
60
|
+
})(),
|
|
61
|
+
...(run.crash === undefined ? {} : { crash: run.crash }),
|
|
62
|
+
finalUrl: run.finalUrl,
|
|
63
|
+
decisions: run.decisions,
|
|
64
|
+
actions: run.actions,
|
|
65
|
+
depth: run.depth,
|
|
66
|
+
...(runUsage === undefined ? {} : { usage: runUsage.snapshot() }),
|
|
67
|
+
...(hang === undefined ? {} : { hang }),
|
|
68
|
+
// #142 follow-up: reported but never gates `missionOutcome`/`exitCode` — a UX finding is
|
|
69
|
+
// always advisory, and a `server-log` defect here is treated the same way. So is an HTTP 5xx
|
|
70
|
+
// hard-signal defect (#208): listed with its fingerprint (verify-fix replays it), advisory here.
|
|
71
|
+
...serverLogResult(serverLogRun),
|
|
72
|
+
...defectFields([
|
|
73
|
+
...http5xx.defects(run.transcript, run.recording.pages.flatMap((p) => p.steps)[0]?.step.kind === "navigate" ? 1 : 0).map((d) => ({ ...d, advisory: true })),
|
|
74
|
+
...advisoryDefects(serverLogRun?.defects),
|
|
75
|
+
]),
|
|
76
|
+
...(budget === null ? {} : { budget: budget.trajectory() }),
|
|
77
|
+
...(adjudication === undefined
|
|
78
|
+
? {}
|
|
79
|
+
: {
|
|
80
|
+
goalOutcome: adjudication.outcome,
|
|
81
|
+
checks: adjudication.checks,
|
|
82
|
+
...(adjudication.warnings === undefined ? {} : { checkWarnings: adjudication.warnings }),
|
|
83
|
+
}),
|
|
84
|
+
...host.fields,
|
|
85
|
+
};
|
|
86
|
+
if (outcome.kind === "failed") {
|
|
87
|
+
// The analysis is the review's product: without it the review is inconclusive (never a
|
|
88
|
+
// fabricated clean report) — but the run's transcript is kept, and this is a typed result.
|
|
89
|
+
const why = new UxAnalysisFailedError(outcome.reason, outcome.screenId, outcome.rubricItemId).message;
|
|
90
|
+
const missionOutcome = runOutcome === "clean" ? "inconclusive" : runOutcome;
|
|
91
|
+
const unavailable = {
|
|
92
|
+
...base,
|
|
93
|
+
report: null,
|
|
94
|
+
reportPath: null,
|
|
95
|
+
missionOutcome,
|
|
96
|
+
exitCode: missionExitCode(missionOutcome),
|
|
97
|
+
analysisUnavailable: why,
|
|
98
|
+
};
|
|
99
|
+
// Persisted like every other mission's typed result, so MCP `get_mission_result` can read it (#117).
|
|
100
|
+
return await withRunEvidence({ ...unavailable, resultPath: writeMissionResult(journal.recordingPath, missionOutcome, unavailable.exitCode, unavailable, runUsage) }, evidenceOf(opts, secrets), triageOf(opts, serverLogRun, secrets));
|
|
101
|
+
}
|
|
102
|
+
const report = buildReport(groundFindings(withSignalFindings(outcome, signalFindings), friction), {
|
|
103
|
+
minConfidence,
|
|
104
|
+
quality,
|
|
105
|
+
maxFindingsPerRoute,
|
|
106
|
+
...(product.facts === undefined ? { evidenceCaveats: [NO_PRODUCT_FACTS_CAVEAT] } : {}),
|
|
107
|
+
calibrationCaveats: [calibrationCaveat(opts.appContext.appClass), claimsCaveat()],
|
|
108
|
+
});
|
|
109
|
+
await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`, "utf8");
|
|
110
|
+
const reviewed = { ...base, report, reportPath, missionOutcome: runOutcome, exitCode: missionExitCode(runOutcome) };
|
|
111
|
+
return await withRunEvidence({ ...reviewed, resultPath: writeMissionResult(journal.recordingPath, runOutcome, reviewed.exitCode, reviewed, runUsage) }, evidenceOf(opts, secrets), triageOf(opts, serverLogRun, secrets));
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=result.js.map
|