comber 0.1.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.
Files changed (158) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +732 -0
  3. package/action.yml +279 -0
  4. package/comber.config.example.json +64 -0
  5. package/dist/api/classify.js +99 -0
  6. package/dist/api/classify.js.map +1 -0
  7. package/dist/api/driver.js +235 -0
  8. package/dist/api/driver.js.map +1 -0
  9. package/dist/api/graphql-driver.js +150 -0
  10. package/dist/api/graphql-driver.js.map +1 -0
  11. package/dist/api/graphql.js +115 -0
  12. package/dist/api/graphql.js.map +1 -0
  13. package/dist/api/har-driver.js +167 -0
  14. package/dist/api/har-driver.js.map +1 -0
  15. package/dist/api/har.js +100 -0
  16. package/dist/api/har.js.map +1 -0
  17. package/dist/api/load.js +83 -0
  18. package/dist/api/load.js.map +1 -0
  19. package/dist/api/openapi.js +159 -0
  20. package/dist/api/openapi.js.map +1 -0
  21. package/dist/api/request.js +141 -0
  22. package/dist/api/request.js.map +1 -0
  23. package/dist/api/schema.js +126 -0
  24. package/dist/api/schema.js.map +1 -0
  25. package/dist/cli.js +352 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/comment.js +164 -0
  28. package/dist/comment.js.map +1 -0
  29. package/dist/config.js +664 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/connectors/builtin/discord.js +55 -0
  32. package/dist/connectors/builtin/github-checks.js +53 -0
  33. package/dist/connectors/builtin/junit.js +70 -0
  34. package/dist/connectors/builtin/linear.js +58 -0
  35. package/dist/connectors/builtin/slack.js +60 -0
  36. package/dist/connectors/builtin/webhook.js +39 -0
  37. package/dist/connectors/index.js +3 -0
  38. package/dist/connectors/loader.js +72 -0
  39. package/dist/connectors/registry.js +28 -0
  40. package/dist/connectors/types.js +23 -0
  41. package/dist/core/action-gate.js +48 -0
  42. package/dist/core/baseline.js +85 -0
  43. package/dist/core/baseline.js.map +1 -0
  44. package/dist/core/blind-spots.js +22 -0
  45. package/dist/core/blind-spots.js.map +1 -0
  46. package/dist/core/contract-extras.js +1 -0
  47. package/dist/core/contract-extras.js.map +1 -0
  48. package/dist/core/contract.js +1 -0
  49. package/dist/core/contract.js.map +1 -0
  50. package/dist/core/coverage.js +70 -0
  51. package/dist/core/coverage.js.map +1 -0
  52. package/dist/core/crawl.js +229 -0
  53. package/dist/core/crawl.js.map +1 -0
  54. package/dist/core/egress.js +128 -0
  55. package/dist/core/egress.js.map +1 -0
  56. package/dist/core/ledger.js +41 -0
  57. package/dist/core/ledger.js.map +1 -0
  58. package/dist/core/net.js +39 -0
  59. package/dist/core/pipeline.js +191 -0
  60. package/dist/core/pipeline.js.map +1 -0
  61. package/dist/core/plan.js +121 -0
  62. package/dist/core/plan.js.map +1 -0
  63. package/dist/core/redact.js +240 -0
  64. package/dist/core/redact.js.map +1 -0
  65. package/dist/core/replay.js +263 -0
  66. package/dist/core/replay.js.map +1 -0
  67. package/dist/core/report.js +253 -0
  68. package/dist/core/report.js.map +1 -0
  69. package/dist/core/safety-judge.js +116 -0
  70. package/dist/core/safety.js +92 -0
  71. package/dist/core/safety.js.map +1 -0
  72. package/dist/core/signal-normalize.js +190 -0
  73. package/dist/core/signal-normalize.js.map +1 -0
  74. package/dist/core/signals.js +5 -0
  75. package/dist/core/signals.js.map +1 -0
  76. package/dist/core/triage.js +104 -0
  77. package/dist/core/triage.js.map +1 -0
  78. package/dist/core/types.js +1 -0
  79. package/dist/core/types.js.map +1 -0
  80. package/dist/desktop/desktop-profile.js +6 -0
  81. package/dist/desktop/desktop-profile.js.map +1 -0
  82. package/dist/desktop/driver.js +433 -0
  83. package/dist/desktop/driver.js.map +1 -0
  84. package/dist/desktop/evidence.js +206 -0
  85. package/dist/desktop/evidence.js.map +1 -0
  86. package/dist/desktop/macos/accessibility-runner.js +118 -0
  87. package/dist/desktop/macos/accessibility-runner.js.map +1 -0
  88. package/dist/desktop/macos/types.js +1 -0
  89. package/dist/desktop/macos/types.js.map +1 -0
  90. package/dist/desktop/preflight.js +107 -0
  91. package/dist/desktop/preflight.js.map +1 -0
  92. package/dist/desktop/proxy-proof.js +185 -0
  93. package/dist/desktop/proxy-proof.js.map +1 -0
  94. package/dist/desktop/view.js +348 -0
  95. package/dist/desktop/view.js.map +1 -0
  96. package/dist/desktop/windows/types.js +1 -0
  97. package/dist/desktop/windows/types.js.map +1 -0
  98. package/dist/desktop/windows/uia-runner.js +127 -0
  99. package/dist/desktop/windows/uia-runner.js.map +1 -0
  100. package/dist/native/apple.js +14 -0
  101. package/dist/native/apple.js.map +1 -0
  102. package/dist/native/device-profile.js +6 -0
  103. package/dist/native/device-profile.js.map +1 -0
  104. package/dist/native/driver.js +416 -0
  105. package/dist/native/driver.js.map +1 -0
  106. package/dist/native/evidence.js +164 -0
  107. package/dist/native/evidence.js.map +1 -0
  108. package/dist/native/load.js +9 -0
  109. package/dist/native/load.js.map +1 -0
  110. package/dist/native/maestro.js +153 -0
  111. package/dist/native/maestro.js.map +1 -0
  112. package/dist/native/preflight.js +72 -0
  113. package/dist/native/preflight.js.map +1 -0
  114. package/dist/native/proxy-proof.js +182 -0
  115. package/dist/native/proxy-proof.js.map +1 -0
  116. package/dist/native/samsung.js +34 -0
  117. package/dist/native/samsung.js.map +1 -0
  118. package/dist/native/signals.js +50 -0
  119. package/dist/native/signals.js.map +1 -0
  120. package/dist/native/view.js +139 -0
  121. package/dist/native/view.js.map +1 -0
  122. package/dist/profile.js +237 -0
  123. package/dist/profile.js.map +1 -0
  124. package/dist/sink.js +134 -0
  125. package/dist/sink.js.map +1 -0
  126. package/dist/sinks/file.js +49 -0
  127. package/dist/sinks/file.js.map +1 -0
  128. package/dist/sinks/http.js +38 -0
  129. package/dist/sinks/http.js.map +1 -0
  130. package/dist/sinks/index.js +5 -0
  131. package/dist/sinks/index.js.map +1 -0
  132. package/dist/sinks/multi.js +40 -0
  133. package/dist/sinks/multi.js.map +1 -0
  134. package/dist/sinks/paths.js +32 -0
  135. package/dist/sinks/paths.js.map +1 -0
  136. package/dist/sinks/record.js +58 -0
  137. package/dist/sinks/record.js.map +1 -0
  138. package/dist/sinks/types.js +1 -0
  139. package/dist/sinks/types.js.map +1 -0
  140. package/dist/web/breakage.js +210 -0
  141. package/dist/web/breakage.js.map +1 -0
  142. package/dist/web/controls.js +102 -0
  143. package/dist/web/controls.js.map +1 -0
  144. package/dist/web/driver.js +803 -0
  145. package/dist/web/driver.js.map +1 -0
  146. package/dist/web/elements.js +151 -0
  147. package/dist/web/elements.js.map +1 -0
  148. package/dist/web/fingerprint.js +13 -0
  149. package/dist/web/fingerprint.js.map +1 -0
  150. package/dist/web/forms.js +74 -0
  151. package/dist/web/forms.js.map +1 -0
  152. package/dist/web/links.js +194 -0
  153. package/dist/web/links.js.map +1 -0
  154. package/dist/web/types.js +1 -0
  155. package/dist/web/types.js.map +1 -0
  156. package/package.json +80 -0
  157. package/runtime/macos-accessibility-runner.js +275 -0
  158. package/runtime/windows-uia-runner.ps1 +539 -0
@@ -0,0 +1,263 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { CoverageAccumulator } from "./coverage.js";
4
+ import { fallbackBlindSpots } from "./blind-spots.js";
5
+ import { ExhaustivenessLedger } from "./ledger.js";
6
+ import { restoreBaseline } from "./pipeline.js";
7
+ import { evaluateAction } from "./action-gate.js";
8
+ import { actionKey, elementSignature, invariants, surfaceFingerprint, traversalIntentOf } from "./safety.js";
9
+ import { compareFindings, normalizeBreakageSignals } from "./signal-normalize.js";
10
+ /**
11
+ * DETERMINISTIC replay — the cheap half of Comber's "discover once, replay cheaply"
12
+ * promise. It does NOT re-discover the frontier and NEVER calls the LLM action-picker:
13
+ * it walks the recorded plan in order, and for each recorded state re-establishes it
14
+ * (`goto`), snapshots, matches the recorded `SurfaceNode.id`s against the CURRENT surface
15
+ * by id, and exercises the matches in the recorded order. Because there is no discovery
16
+ * and no model, the exercised-node sequence is byte-reproducible for the same plan +
17
+ * unchanged target — which is exactly what makes replay a regression monitor.
18
+ *
19
+ * Drift is a SIGNAL, not a crash: a recorded id that is gone from the current surface
20
+ * emits a `plan-drift` warning and replay continues. New states/nodes that appeared since
21
+ * recording are intentionally NOT explored — replay is bounded to the known plan. The
22
+ * global caps stay live as backstops. The result is a normal `CrawlResult`, so
23
+ * baseline-diff / report / redaction all work unchanged.
24
+ */
25
+ export async function replay(driver, cfg, runDir, plan) {
26
+ const startedAt = Date.now();
27
+ const spend = { input: 0, output: 0 }; // no LLM in replay — always zero
28
+ const rawSignals = [];
29
+ const visitedUrls = [];
30
+ const triedSignatures = new Set();
31
+ const performedMutations = new Set();
32
+ const coverage = new CoverageAccumulator();
33
+ const ledger = new ExhaustivenessLedger();
34
+ let states = 0;
35
+ let actions = 0;
36
+ let cappedBy;
37
+ driver.attachCoverage?.(coverage);
38
+ // Driver-declared probes by lifecycle slot (contract v2). after-goto fires every goto
39
+ // (capability-gated, like the old session probe); after-snapshot / after-exercise re-run
40
+ // only the probes the plan RECORDED (replay is bounded to the known plan); finalize fires
41
+ // capability-gated. Names map 1:1 to the crawl recording, so a plan replays unchanged.
42
+ const probesAt = (slot) => driver.capabilities.probes.filter((p) => p.slot === slot);
43
+ const runGotoSlot = async () => {
44
+ if (!driver.probe)
45
+ return [];
46
+ const out = [];
47
+ for (const reg of probesAt("after-goto"))
48
+ out.push(...(await driver.probe(reg.name)));
49
+ return out;
50
+ };
51
+ const runRecordedSlot = async (slot, recorded) => {
52
+ if (!driver.probe)
53
+ return [];
54
+ const out = [];
55
+ for (const reg of probesAt(slot))
56
+ if (recorded.includes(reg.name))
57
+ out.push(...(await driver.probe(reg.name)));
58
+ return out;
59
+ };
60
+ const runFinalizeSlot = async () => {
61
+ if (!driver.probe)
62
+ return [];
63
+ const out = [];
64
+ for (const reg of probesAt("finalize"))
65
+ out.push(...(await driver.probe(reg.name)));
66
+ return out;
67
+ };
68
+ const overBudget = () => {
69
+ if (states >= cfg.maxStates)
70
+ return `maxStates(${cfg.maxStates})`;
71
+ if (actions >= cfg.maxActions)
72
+ return `maxActions(${cfg.maxActions})`;
73
+ if (Date.now() - startedAt >= cfg.maxWallClockMs)
74
+ return "maxWallClockMs";
75
+ return undefined;
76
+ };
77
+ // start() inside the try so a mid-start failure still closes the driver (no leaked browser).
78
+ try {
79
+ await driver.start();
80
+ for (const ps of plan.states) {
81
+ cappedBy = overBudget();
82
+ if (cappedBy) {
83
+ coverage.cappedNotExhaustive = true;
84
+ break;
85
+ }
86
+ invariants({ actions, cfg });
87
+ // A recorded plan is operator-trusted, but re-assert scope before navigating: a tampered
88
+ // or stale plan.json must not steer replay to an out-of-scope address (egress is still
89
+ // gated at the wire, but replay should never even attempt an off-plan destination).
90
+ if (!driver.inScope(ps.address)) {
91
+ coverage.skip("plan-out-of-scope");
92
+ ledger.recordMissingNode(ps.address, ps.address.id, "plan-out-of-scope");
93
+ continue;
94
+ }
95
+ await driver.goto(ps.address);
96
+ rawSignals.push(...driver.drainSignals());
97
+ // AFTER-GOTO slot fires every goto in a crawl (capability-gated); mirror that.
98
+ rawSignals.push(...(await runGotoSlot()));
99
+ const snap = await driver.snapshot();
100
+ states++;
101
+ coverage.noteState();
102
+ coverage.blind(snap.blindSpots, snap.blindSpotDetails, snap.address.scope);
103
+ const blindSpotDetails = snap.blindSpotDetails ?? fallbackBlindSpots(snap.blindSpots, snap.address.scope, "surface could not be inspected");
104
+ for (const blindSpot of blindSpotDetails)
105
+ ledger.recordBlindSpot(snap.address, blindSpot);
106
+ visitedUrls.push(snap.address.label ?? snap.address.id);
107
+ if (cfg.screenshots) {
108
+ const shot = await driver.screenshot();
109
+ const shotPath = join(runDir, `state-${states}.png`);
110
+ if (shot.length)
111
+ await writeFile(shotPath, shot).catch(() => { });
112
+ }
113
+ // AFTER-SNAPSHOT slot: re-run the probes this state RECORDED (web: axe/dom/links).
114
+ rawSignals.push(...(await runRecordedSlot("after-snapshot", ps.probes)));
115
+ // Index the CURRENT surface by stable node id — the deterministic match key.
116
+ const byId = new Map();
117
+ for (const n of snap.nodes)
118
+ byId.set(n.id, n);
119
+ const scope = ps.address.scope;
120
+ const baselineFp = surfaceFingerprint(snap);
121
+ let regotoCount = 0;
122
+ for (const nodeId of ps.exercised) {
123
+ if (overBudget())
124
+ break;
125
+ const node = byId.get(nodeId);
126
+ // DRIFT — the recorded node is gone from the current surface (the app changed).
127
+ if (!node) {
128
+ coverage.skip("plan-drift");
129
+ ledger.recordMissingNode(ps.address, nodeId, "plan-drift");
130
+ rawSignals.push({
131
+ kind: "plan-drift",
132
+ severity: "warn",
133
+ message: `expected node "${nodeId}" at state "${ps.address.label ?? ps.address.id}", not found on current surface`,
134
+ url: ps.address.label ?? ps.address.id,
135
+ signature: `plan-drift-${scope}-${nodeId}`.slice(0, 80),
136
+ });
137
+ continue;
138
+ }
139
+ // Defense-in-depth safety re-partition: recorded nodes were vetted at record time,
140
+ // but the same id may now be disabled / denied / a navigation under a changed app.
141
+ const sig = elementSignature(node, scope);
142
+ if (triedSignatures.has(sig))
143
+ continue;
144
+ triedSignatures.add(sig);
145
+ coverage.discover(1);
146
+ // Full effect-floor re-partition (not just the name/id denylist): a recorded node whose
147
+ // app has DRIFTED to a destructive/off-origin effect must be re-caught, and an ambiguous
148
+ // mutation resolved without a judge — replay is deterministic + LLM-free by design, so
149
+ // advisory proceeds and strict fails closed, mirroring the pipeline's no-judge branch.
150
+ const gate = evaluateAction(node, cfg);
151
+ if (gate.verdict === "deny") {
152
+ coverage.skipElement(gate.reason);
153
+ ledger.recordNode(ps.address, node, traversalIntentOf(node), "skipped", { reason: gate.reason });
154
+ continue;
155
+ }
156
+ if (gate.verdict === "escalate" && cfg.actionGateMode === "strict") {
157
+ coverage.skipElement("escalate-no-judge-fail-closed");
158
+ ledger.recordNode(ps.address, node, traversalIntentOf(node), "skipped", { reason: "escalate-no-judge-fail-closed" });
159
+ continue;
160
+ }
161
+ if (node.disabled) {
162
+ coverage.skipElement("disabled");
163
+ ledger.recordNode(ps.address, node, traversalIntentOf(node), "skipped", { reason: "disabled" });
164
+ continue;
165
+ }
166
+ const intent = traversalIntentOf(node);
167
+ if (intent === "follow") {
168
+ // A link now lives where an action was recorded — follow-never-click still holds.
169
+ coverage.bumpExtra("followTargetsDiscovered");
170
+ coverage.bumpExtra("linksDiscovered");
171
+ ledger.recordNode(ps.address, node, intent, "followed");
172
+ continue;
173
+ }
174
+ if (intent === "branch")
175
+ coverage.bumpExtra("branchesDiscovered");
176
+ if (cfg.dryRun) {
177
+ coverage.skipElement("dry-run");
178
+ ledger.recordNode(ps.address, node, intent, "skipped", { reason: "dry-run" });
179
+ continue;
180
+ }
181
+ const akey = actionKey(node, scope);
182
+ if (performedMutations.has(akey)) {
183
+ coverage.skipElement("idempotent-skip");
184
+ ledger.recordNode(ps.address, node, intent, "skipped", { reason: "idempotent-skip" });
185
+ continue;
186
+ }
187
+ try {
188
+ const res = await driver.exercise(node);
189
+ if (res && res.signals)
190
+ res.signals.forEach((s) => rawSignals.push(s));
191
+ performedMutations.add(akey);
192
+ actions++;
193
+ coverage.exercise();
194
+ if (intent === "branch")
195
+ coverage.bumpExtra("branchesExercised");
196
+ ledger.recordNode(ps.address, node, intent, intent === "branch" ? "branched" : "exercised");
197
+ driver.drainSignals().forEach((s) => rawSignals.push(s));
198
+ }
199
+ catch (e) {
200
+ rawSignals.push({
201
+ kind: "interaction-threw",
202
+ severity: "warn",
203
+ message: `${node.kind} "${node.label}" threw: ${String(e).slice(0, 120)}`,
204
+ url: ps.address.label ?? ps.address.id,
205
+ signature: `threw-${node.id}`.slice(0, 80),
206
+ });
207
+ ledger.recordNode(ps.address, node, intent, "blocked", { reason: "interaction-threw" });
208
+ }
209
+ // Restore baseline between exercises (web handles can stale after a state change).
210
+ const r = await restoreBaseline(driver, { address: snap.address, fingerprint: baselineFp }, cfg);
211
+ if (r.mode === "regoto" && ++regotoCount > cfg.maxRegotosPerState) {
212
+ coverage.cappedNotExhaustive = true;
213
+ break;
214
+ }
215
+ if (!r.ok) {
216
+ rawSignals.push({
217
+ kind: "irrecoverable-state",
218
+ severity: "warn",
219
+ message: `could not restore baseline after "${node.label}" (${node.kind})`,
220
+ url: ps.address.label ?? ps.address.id,
221
+ signature: `restore-fail-${scope}`,
222
+ });
223
+ break;
224
+ }
225
+ }
226
+ // AFTER-EXERCISE slot (web: form testing — submits are egress-gated), mirroring the crawl.
227
+ if (!cfg.dryRun && !overBudget())
228
+ rawSignals.push(...(await runRecordedSlot("after-exercise", ps.probes)));
229
+ }
230
+ // FINALIZE slot + the finalize extras — end-of-run passes that need the session live
231
+ // (native: a final crash/ANR log scan; web: validate every collected link).
232
+ rawSignals.push(...(await runFinalizeSlot()));
233
+ if (driver.finalize)
234
+ rawSignals.push(...(await driver.finalize()));
235
+ }
236
+ finally {
237
+ await driver.close();
238
+ }
239
+ const extra = driver.auditExtra ? driver.auditExtra() : undefined;
240
+ // Dedupe by signature → Finding[] with counts (identical to the crawl path).
241
+ const bySig = new Map();
242
+ for (const s of normalizeBreakageSignals(rawSignals)) {
243
+ const existing = bySig.get(s.signature);
244
+ if (existing)
245
+ existing.count++;
246
+ else
247
+ bySig.set(s.signature, { ...s, count: 1 });
248
+ }
249
+ return {
250
+ startUrl: cfg.startUrl,
251
+ visitedStates: states,
252
+ visitedUrls,
253
+ actionsTaken: actions,
254
+ findings: [...bySig.values()].sort(compareFindings),
255
+ durationMs: Date.now() - startedAt,
256
+ tokenSpend: spend,
257
+ cappedBy,
258
+ frameBlindSpots: coverage.frameBlindSpots,
259
+ coverage: coverage.summary(),
260
+ exhaustivenessLedger: ledger.snapshot(),
261
+ ...(extra ? { extra } : {}),
262
+ };
263
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.js","sourceRoot":"","sources":["../../src/core/replay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,253 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ /** Read a coverage sub-metric out of the per-driver `extra` bag (0 when absent). */
4
+ function ex(cov, key) {
5
+ return cov?.extra?.[key] ?? 0;
6
+ }
7
+ function esc(s) {
8
+ return String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
9
+ }
10
+ function gauge(label, value, sub = "", tone = "") {
11
+ return `<div class="card ${tone}"><div class="n">${esc(value)}</div><div class="l">${esc(label)}</div>${sub ? `<div class="s">${esc(sub)}</div>` : ""}</div>`;
12
+ }
13
+ function recordValue(value) {
14
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
15
+ }
16
+ function stringValue(record, key) {
17
+ const value = record[key];
18
+ return typeof value === "string" && value.length > 0 ? value : undefined;
19
+ }
20
+ function numberValue(record, key) {
21
+ const value = record[key];
22
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
23
+ }
24
+ function stringRows(record, key, label) {
25
+ const value = record[key];
26
+ if (!Array.isArray(value))
27
+ return "";
28
+ const rows = [];
29
+ for (const entry of value) {
30
+ if (typeof entry === "string" && entry.length > 0) {
31
+ rows.push(`<tr><td>${esc(label)}</td><td><code>${esc(entry)}</code></td></tr>`);
32
+ }
33
+ }
34
+ return rows.join("");
35
+ }
36
+ function issueRows(value) {
37
+ if (!Array.isArray(value))
38
+ return "";
39
+ const rows = [];
40
+ for (const issue of value) {
41
+ const record = recordValue(issue);
42
+ if (!record)
43
+ continue;
44
+ const severity = stringValue(record, "severity") ?? "error";
45
+ const code = stringValue(record, "code") ?? "unknown";
46
+ const message = stringValue(record, "message") ?? "";
47
+ const tone = severity === "blocker" || severity === "error" ? "warn" : "";
48
+ rows.push(`<tr class="${tone}"><td>${esc(severity)}</td><td><code>${esc(code)}</code></td><td>${esc(message)}</td></tr>`);
49
+ }
50
+ return rows.join("");
51
+ }
52
+ function nativeEvidenceBlock(extra) {
53
+ const evidence = recordValue(extra?.deviceEvidence);
54
+ if (!evidence)
55
+ return "";
56
+ const metadataRows = [
57
+ ["Platform", stringValue(evidence, "platform")],
58
+ ["App", stringValue(evidence, "appId")],
59
+ ["Device", stringValue(evidence, "deviceId")],
60
+ ["Provider", stringValue(evidence, "provider")],
61
+ ["Provider run", stringValue(evidence, "providerRunId")],
62
+ ["Verification command", stringValue(evidence, "verificationCommand")],
63
+ ]
64
+ .filter((entry) => entry[1] !== undefined)
65
+ .map(([label, value]) => `<tr><td>${esc(label)}</td><td><code>${esc(value)}</code></td></tr>`)
66
+ .join("");
67
+ const artifactRows = stringRows(evidence, "screenshots", "Screenshot") +
68
+ stringRows(evidence, "videos", "Video") +
69
+ stringRows(evidence, "logs", "Log") +
70
+ stringRows(evidence, "crashLogs", "Crash log");
71
+ const validationRows = issueRows(evidence.validationErrors);
72
+ return `<h2>Native/device evidence</h2><table><thead><tr><th>Field</th><th>Value</th></tr></thead><tbody>${metadataRows}${artifactRows || `<tr><td>Artifacts</td><td>None declared</td></tr>`}</tbody></table>${validationRows
73
+ ? `<h2>Native evidence validation</h2><table><thead><tr><th>Severity</th><th>Code</th><th>Message</th></tr></thead><tbody>${validationRows}</tbody></table>`
74
+ : ""}`;
75
+ }
76
+ function nativeReadinessBlock(extra) {
77
+ const readiness = recordValue(extra?.nativeReadiness);
78
+ if (!readiness)
79
+ return "";
80
+ const status = stringValue(readiness, "status") ?? "unknown";
81
+ const blockerCount = numberValue(readiness, "blockerCount");
82
+ const warningCount = numberValue(readiness, "warningCount");
83
+ const rows = issueRows(readiness.issues);
84
+ return `<h2>Native readiness preflight</h2><div class="banner ${status === "ready" ? "ok" : "warn"}">Status: ${esc(status)} · ${blockerCount} blocker(s) · ${warningCount} warning(s)</div>${rows
85
+ ? `<table><thead><tr><th>Severity</th><th>Code</th><th>Message</th></tr></thead><tbody>${rows}</tbody></table>`
86
+ : ""}`;
87
+ }
88
+ function desktopEvidenceBlock(extra) {
89
+ const evidence = recordValue(extra?.desktopEvidence);
90
+ if (!evidence)
91
+ return "";
92
+ const metadataRows = [
93
+ ["Platform", stringValue(evidence, "platform")],
94
+ ["App", stringValue(evidence, "appId")],
95
+ ["App path", stringValue(evidence, "appPath")],
96
+ ["Process", stringValue(evidence, "processName")],
97
+ ["Automation backend", stringValue(evidence, "automationBackend")],
98
+ ["Provider", stringValue(evidence, "provider")],
99
+ ["Provider run", stringValue(evidence, "providerRunId")],
100
+ ["Launch command", stringValue(evidence, "launchCommand")],
101
+ ["Verification command", stringValue(evidence, "verificationCommand")],
102
+ ]
103
+ .filter((entry) => entry[1] !== undefined)
104
+ .map(([label, value]) => `<tr><td>${esc(label)}</td><td><code>${esc(value)}</code></td></tr>`)
105
+ .join("");
106
+ const artifactRows = stringRows(evidence, "screenshots", "Screenshot") +
107
+ stringRows(evidence, "videos", "Video") +
108
+ stringRows(evidence, "logs", "Log") +
109
+ stringRows(evidence, "crashLogs", "Crash log") +
110
+ stringRows(evidence, "accessibilityTrees", "Accessibility tree") +
111
+ stringRows(evidence, "automationTrees", "Automation tree");
112
+ const validationRows = issueRows(evidence.validationErrors);
113
+ return `<h2>Desktop/app evidence</h2><table><thead><tr><th>Field</th><th>Value</th></tr></thead><tbody>${metadataRows}${artifactRows || `<tr><td>Artifacts</td><td>None declared</td></tr>`}</tbody></table>${validationRows
114
+ ? `<h2>Desktop evidence validation</h2><table><thead><tr><th>Severity</th><th>Code</th><th>Message</th></tr></thead><tbody>${validationRows}</tbody></table>`
115
+ : ""}`;
116
+ }
117
+ function desktopReadinessBlock(extra) {
118
+ const readiness = recordValue(extra?.desktopReadiness);
119
+ if (!readiness)
120
+ return "";
121
+ const status = stringValue(readiness, "status") ?? "unknown";
122
+ const blockerCount = numberValue(readiness, "blockerCount");
123
+ const warningCount = numberValue(readiness, "warningCount");
124
+ const rows = issueRows(readiness.issues);
125
+ return `<h2>Desktop readiness preflight</h2><div class="banner ${status === "ready" ? "ok" : "warn"}">Status: ${esc(status)} · ${blockerCount} blocker(s) · ${warningCount} warning(s)</div>${rows
126
+ ? `<table><thead><tr><th>Severity</th><th>Code</th><th>Message</th></tr></thead><tbody>${rows}</tbody></table>`
127
+ : ""}`;
128
+ }
129
+ /** Self-contained HTML report (no external assets) — house style, opens offline. */
130
+ export async function writeReport(result, runDir, stampIso) {
131
+ const fails = result.findings.filter((f) => f.severity === "fail");
132
+ const warns = result.findings.filter((f) => f.severity === "warn");
133
+ const infos = result.findings.filter((f) => f.severity === "info");
134
+ const cov = result.coverage;
135
+ const links = result.extra?.linkAudit ?? [];
136
+ const brokenLinks = links.filter((l) => !l.ok);
137
+ const baseline = result.baseline;
138
+ const followTargets = ex(cov, "followTargetsDiscovered") || ex(cov, "linksDiscovered");
139
+ const ledgerSummary = result.exhaustivenessLedger.reduce((acc, entry) => {
140
+ acc[entry.disposition] = (acc[entry.disposition] ?? 0) + 1;
141
+ return acc;
142
+ }, {});
143
+ const row = (f) => `
144
+ <tr class="${f.severity}">
145
+ <td><span class="sev ${f.severity}">${f.severity.toUpperCase()}</span></td>
146
+ <td><code>${esc(f.kind)}</code></td>
147
+ <td class="num">${f.count}</td>
148
+ <td class="u">${esc(f.url)}</td>
149
+ <td>${esc(f.message)}${f.verdict ? `<div class="verdict ${f.verdict.real ? "real" : "noise"}">judge: ${f.verdict.real ? "real" : "noise"} — ${esc(f.verdict.reason)}</div>` : ""}</td>
150
+ </tr>`;
151
+ const findingsTable = (rows, title) => rows.length === 0
152
+ ? ""
153
+ : `<h2>${esc(title)} (${rows.length})</h2><table><thead><tr><th>Sev</th><th>Kind</th><th>×</th><th>URL</th><th>Detail</th></tr></thead><tbody>${rows.map(row).join("")}</tbody></table>`;
154
+ const brokenTable = brokenLinks.length
155
+ ? `<h2>Broken links (${brokenLinks.length})</h2><table><thead><tr><th>Kind</th><th>Status</th><th>URL</th><th>Found on</th><th>×</th><th>Reason</th></tr></thead><tbody>${brokenLinks
156
+ .map((l) => `<tr class="${l.kind === "same-origin" ? "fail" : "warn"}"><td><code>${esc(l.kind)}</code></td><td class="num">${l.status ?? "—"}</td><td class="u">${esc(l.url)}</td><td class="u">${esc(l.foundOnUrl)}</td><td class="num">${l.occurrences}</td><td>${esc(l.reason ?? "")}</td></tr>`)
157
+ .join("")}</tbody></table>`
158
+ : "";
159
+ const skipRows = cov
160
+ ? Object.entries(cov.skipLedger)
161
+ .sort((a, b) => b[1] - a[1])
162
+ .map(([k, v]) => `<li><code>${esc(k)}</code> — ${v}</li>`)
163
+ .join("")
164
+ : "";
165
+ const blindSpotRows = cov
166
+ ? Object.entries(cov.blindSpotSummary)
167
+ .sort((a, b) => b[1] - a[1])
168
+ .map(([k, v]) => `<li><code>${esc(k)}</code> — ${v}</li>`)
169
+ .join("")
170
+ : "";
171
+ const ledgerRows = Object.entries(ledgerSummary)
172
+ .sort((a, b) => b[1] - a[1])
173
+ .map(([k, v]) => `<li><code>${esc(k)}</code> — ${v}</li>`)
174
+ .join("");
175
+ const nativeEvidence = nativeEvidenceBlock(result.extra);
176
+ const nativeReadiness = nativeReadinessBlock(result.extra);
177
+ const desktopEvidence = desktopEvidenceBlock(result.extra);
178
+ const desktopReadiness = desktopReadinessBlock(result.extra);
179
+ const notCovered = cov &&
180
+ `<h2>What was NOT covered (honesty)</h2>
181
+ ${cov.cappedNotExhaustive ? `<div class="banner warn">⚠️ CAPPED — a hard cap tripped, so this run is NOT exhaustive. Raise --max-states / --max-per-state to go deeper.</div>` : `<div class="banner ok">✅ Frontier exhausted — no cap tripped.</div>`}
182
+ ${cov.frameBlindSpots ? `<div class="banner warn">${cov.frameBlindSpots} frame blind-spot(s): closed shadow roots / cross-origin iframes Comber cannot pierce (e.g. Stripe Elements, reCAPTCHA). Observed, not exercised.</div>` : ""}
183
+ ${blindSpotRows ? `<p class="sub">Blind spots by kind:</p><ul class="skips">${blindSpotRows}</ul>` : ""}
184
+ ${skipRows ? `<p class="sub">Skipped (a partition, never silently dropped):</p><ul class="skips">${skipRows}</ul>` : ""}`;
185
+ const baselineBlock = baseline &&
186
+ `<h2>Baseline diff</h2>
187
+ ${baseline.baselineFound ? `<div class="banner ok">Compared against ${esc(baseline.baselinePath ?? "baseline")}.</div>` : `<div class="banner warn">No prior baseline found${baseline.baselinePath ? ` at ${esc(baseline.baselinePath)}` : ""}; all current findings are treated as new.</div>`}
188
+ <div class="cards">
189
+ ${gauge("new fail", String(baseline.summary.newFail), "", baseline.summary.newFail ? "fail" : "good")}
190
+ ${gauge("new warn", String(baseline.summary.newWarn), "", baseline.summary.newWarn ? "warn" : "good")}
191
+ ${gauge("resolved", String(baseline.summary.resolved), "")}
192
+ ${gauge("unchanged", String(baseline.summary.unchanged), "")}
193
+ </div>`;
194
+ const html = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1">
195
+ <title>Comber report — ${esc(result.startUrl)}</title><style>
196
+ body{font-family:-apple-system,'Segoe UI',sans-serif;background:#0a0e1a;color:#e8ecf5;margin:0;padding:32px;line-height:1.5}
197
+ .wrap{max-width:1040px;margin:0 auto}
198
+ h1{font-size:20px;margin:0 0 4px} h2{font-size:14px;margin:26px 0 8px;border-bottom:1px solid #2a3354;padding-bottom:5px}
199
+ .sub{color:#8893b0;font-size:13px;margin-bottom:14px}
200
+ .cards{display:flex;gap:10px;margin:16px 0;flex-wrap:wrap}
201
+ .card{background:#131829;border:1px solid #2a3354;border-radius:8px;padding:11px 15px;flex:1;min-width:120px}
202
+ .card .n{font-size:23px;font-weight:700} .card.fail .n{color:#e0556b} .card.warn .n{color:#e0a44b} .card.info .n{color:#65b7e8} .card.good .n{color:#46c585}
203
+ .card .l{color:#8893b0;font-size:12px} .card .s{color:#5a6478;font-size:11px;margin-top:2px}
204
+ table{width:100%;border-collapse:collapse;font-size:13px;margin-top:8px}
205
+ th,td{border:1px solid #2a3354;padding:7px 9px;text-align:left;vertical-align:top}
206
+ th{background:#131829} tr.fail td{background:rgba(224,85,107,.06)} tr.info td{background:rgba(101,183,232,.05)}
207
+ td.num{text-align:right;font-variant-numeric:tabular-nums} td.u{word-break:break-all;max-width:280px;font-size:12px;color:#aeb8d0}
208
+ code{background:#131829;padding:1px 5px;border-radius:4px;font-size:12px}
209
+ .sev{font-size:10px;font-weight:700;padding:2px 6px;border-radius:10px}
210
+ .sev.fail{background:#e0556b;color:#0a0e1a} .sev.warn{background:#e0a44b;color:#0a0e1a} .sev.info{background:#65b7e8;color:#0a0e1a}
211
+ .verdict{font-size:11px;margin-top:4px;color:#8893b0} .verdict.real{color:#e0556b} .verdict.noise{color:#6c768f}
212
+ .banner{border-radius:7px;padding:9px 13px;margin:8px 0;font-size:13px;border:1px solid #2a3354}
213
+ .banner.warn{background:rgba(224,164,75,.08);border-color:#5a4a2a;color:#e0c490}
214
+ .banner.ok{background:rgba(70,197,133,.07);border-color:#2a5a44;color:#8fe0b5}
215
+ .skips{font-size:13px;color:#b8c0d4;columns:2}
216
+ ol.cov{font-size:12.5px;color:#b8c0d4;line-height:1.7;padding-left:22px}
217
+ footer{color:#5a6478;font-size:11px;margin-top:28px;border-top:1px solid #2a3354;padding-top:12px}
218
+ </style></head><body><div class="wrap">
219
+ <h1>🦞 Comber — exhaustive crawl report</h1>
220
+ <div class="sub">${esc(result.startUrl)} · ${esc(stampIso)}</div>
221
+ <div class="cards">
222
+ ${gauge("fail findings", String(fails.length), "", fails.length ? "fail" : "good")}
223
+ ${gauge("warn findings", String(warns.length), "", warns.length ? "warn" : "good")}
224
+ ${gauge("info findings", String(infos.length), "non-gating", infos.length ? "info" : "good")}
225
+ ${gauge("states", String(result.visitedStates), `${result.actionsTaken} actions`)}
226
+ ${cov ? gauge("element coverage", `${cov.elementCoveragePct}%`, `${cov.elementsExercised}/${Math.max(cov.elementsExercised, cov.elementsDiscovered - cov.elementsSkipped - followTargets)} exercised`) : ""}
227
+ ${cov ? gauge("controls", String(ex(cov, "controlsExercised")), `${ex(cov, "modalsOpened")} modals`) : ""}
228
+ ${cov ? gauge("links", String(ex(cov, "linksChecked")), `${ex(cov, "linksBroken")} broken / ${ex(cov, "linksDiscovered")} found`, ex(cov, "linksBroken") ? "fail" : "") : ""}
229
+ ${cov ? gauge("branches", String(ex(cov, "branchesExercised")), `${ex(cov, "branchTargetsDiscovered")} child states`) : ""}
230
+ ${gauge("ledger rows", String(result.exhaustivenessLedger.length), "disposition audit")}
231
+ ${cov ? gauge("forms tested", String(ex(cov, "formsTested")), "") : ""}
232
+ ${gauge("duration", `${(result.durationMs / 1000).toFixed(1)}s`, `${result.tokenSpend.output} out-tok`)}
233
+ </div>
234
+ ${result.findings.length === 0 && brokenLinks.length === 0 ? "<div class='banner ok'>No breakage detected across the exercised surface. 🎉</div>" : ""}
235
+ ${findingsTable(fails, "Fail findings")}
236
+ ${findingsTable(warns, "Warn findings")}
237
+ ${findingsTable(infos, "Info findings")}
238
+ ${baselineBlock || ""}
239
+ ${brokenTable}
240
+ ${nativeEvidence}
241
+ ${nativeReadiness}
242
+ ${desktopEvidence}
243
+ ${desktopReadiness}
244
+ ${notCovered || ""}
245
+ ${ledgerRows ? `<h2>Exhaustiveness ledger summary</h2><ul class="skips">${ledgerRows}</ul>` : ""}
246
+ <h2>States visited (${result.visitedUrls.length})</h2>
247
+ <ol class="cov">${result.visitedUrls.map((u) => `<li><code>${esc(u)}</code></li>`).join("")}</ol>
248
+ <footer>Comber (codecomber.io) · generated ${esc(stampIso)} · ${esc(runDir)}${result.cappedBy ? ` · stopped by ${esc(result.cappedBy)}` : " · frontier exhausted"}</footer>
249
+ </div></body></html>`;
250
+ const path = join(runDir, "report.html");
251
+ await writeFile(path, html);
252
+ return path;
253
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/core/report.ts"],"names":[],"mappings":"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"}