@aarwitz/tapp 0.15.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/AGENTS.md +123 -0
- package/Harness/OCQAHarness/AppDelegate.swift +21 -0
- package/Harness/OCQAHarness/Info.plist +26 -0
- package/Harness/OCQAHarness.xcodeproj/project.pbxproj +199 -0
- package/Harness/OCQAHarness.xcodeproj/xcshareddata/xcschemes/OCQAHarnessUITests.xcscheme +22 -0
- package/Harness/OCQAHarnessUITests/ExplorerTests.swift +4526 -0
- package/Harness/OCQAHarnessUITests/Info.plist +22 -0
- package/Harness/generate-harness-xcodeproj.rb +254 -0
- package/LICENSE +21 -0
- package/README.md +374 -0
- package/bin/tapp.js +1382 -0
- package/browser/app.css +227 -0
- package/browser/app.js +675 -0
- package/browser/index.html +195 -0
- package/browser/product-contract.js +25 -0
- package/browser/view-model.js +16 -0
- package/docs/BROWSER-PRODUCT.md +72 -0
- package/docs/PRODUCT-ENGINE.md +102 -0
- package/docs/application-model.md +276 -0
- package/docs/scenarios.md +95 -0
- package/mcp-server/src/android-driver.js +287 -0
- package/mcp-server/src/android-explorer.js +197 -0
- package/mcp-server/src/android-flow.js +89 -0
- package/mcp-server/src/application-model.js +1597 -0
- package/mcp-server/src/browser-product.js +659 -0
- package/mcp-server/src/browser-workspaces.js +234 -0
- package/mcp-server/src/ci-report.js +557 -0
- package/mcp-server/src/ci-setup.js +359 -0
- package/mcp-server/src/contract-authoring.js +10 -0
- package/mcp-server/src/enrich.js +57 -0
- package/mcp-server/src/flow-runtime.js +127 -0
- package/mcp-server/src/html-report.js +124 -0
- package/mcp-server/src/index.js +3775 -0
- package/mcp-server/src/maintenance-proposal.js +178 -0
- package/mcp-server/src/managed-operation.js +61 -0
- package/mcp-server/src/pr-selection.js +841 -0
- package/mcp-server/src/product-execution.js +155 -0
- package/mcp-server/src/product-operations.js +526 -0
- package/mcp-server/src/project-config.js +101 -0
- package/mcp-server/src/release-contract.d.ts +81 -0
- package/mcp-server/src/release-contract.js +226 -0
- package/mcp-server/src/report.js +363 -0
- package/mcp-server/src/scenario-runtime.js +139 -0
- package/mcp-server/src/static-server.js +44 -0
- package/mcp-server/src/task-runtime.js +266 -0
- package/mcp-server/src/ui-map.js +661 -0
- package/mcp-server/src/web-explorer.js +493 -0
- package/mcp-server/src/web-flow.js +238 -0
- package/package.json +82 -0
- package/scripts/android-corpus-e2e.sh +30 -0
- package/scripts/ci-gate.sh +323 -0
- package/scripts/cleanup-xcode.sh +157 -0
- package/scripts/compile-contract.js +27 -0
- package/scripts/compile-flow.js +18 -0
- package/scripts/corpus-apps.txt +9 -0
- package/scripts/corpus-sweep.sh +121 -0
- package/scripts/coverage-eval.sh +92 -0
- package/scripts/coverage_eval_parse.py +95 -0
- package/scripts/deploy-and-build.sh +99 -0
- package/scripts/flow-platform.js +18 -0
- package/scripts/flow_ai_judge.py +102 -0
- package/scripts/flow_lib.py +154 -0
- package/scripts/mutation-recall-desktop.sh +186 -0
- package/scripts/mutation-recall.sh +121 -0
- package/scripts/mutation_lib.py +128 -0
- package/scripts/mutation_operators.py +144 -0
- package/scripts/platform-gate.js +186 -0
- package/scripts/pr-plan.js +68 -0
- package/scripts/quick-capture.sh +419 -0
- package/scripts/run-android-flow.js +27 -0
- package/scripts/run-flow.sh +90 -0
- package/scripts/run-web-flow.js +28 -0
- package/scripts/run-web-scenario.js +23 -0
- package/scripts/validation-matrix.sh +146 -0
- package/scripts/vision-fp-eval.sh +206 -0
- package/scripts/vision_escalation_responder.py +147 -0
- package/scripts/vision_fp_probe.py +221 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Static HTML evidence report for a capture — the human-shareable artifact of a QA run.
|
|
2
|
+
// One self-contained page written INTO the capture dir, referencing the sibling
|
|
3
|
+
// screenshots by relative path, so the whole dir travels as a unit (zip it, attach it,
|
|
4
|
+
// open it). No dependencies, no network, works from file://.
|
|
5
|
+
//
|
|
6
|
+
// This is the "champion artifact": the free-tool user forwards it to the person who can
|
|
7
|
+
// buy the gate. Keep it honest — it renders exactly what the verdict pipeline produced.
|
|
8
|
+
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { buildQaReport } from "./report.js";
|
|
12
|
+
|
|
13
|
+
const BADGE = { ready: "🟢 SHIP-READY", caution: "🟡 CAUTION", blocked: "🔴 BLOCKED" };
|
|
14
|
+
const SEV_COLOR = { critical: "#cf222e", high: "#bc4c00", medium: "#9a6700", low: "#57606a" };
|
|
15
|
+
|
|
16
|
+
const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
|
|
17
|
+
|
|
18
|
+
// Two capture layouts exist: web runs write state_*.png at the capture root; iOS runs
|
|
19
|
+
// export XCUITest attachments into screenshots/ as UUID files with a manifest carrying
|
|
20
|
+
// the human-readable state_N_<Screen> names.
|
|
21
|
+
function collectShots(captureDir) {
|
|
22
|
+
const root = fs
|
|
23
|
+
.readdirSync(captureDir)
|
|
24
|
+
.filter((f) => f.endsWith(".png") && f.startsWith("state_"))
|
|
25
|
+
.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))
|
|
26
|
+
.map((f) => ({ src: f, caption: f.replace(/^state_\d+_/, "").replace(/\.png$/, "").replace(/[-_]/g, " ") }));
|
|
27
|
+
if (root.length) return root;
|
|
28
|
+
|
|
29
|
+
const maniPath = path.join(captureDir, "screenshots", "manifest.json");
|
|
30
|
+
if (!fs.existsSync(maniPath)) return [];
|
|
31
|
+
try {
|
|
32
|
+
const mani = JSON.parse(fs.readFileSync(maniPath, "utf8"));
|
|
33
|
+
return mani
|
|
34
|
+
.flatMap((m) => m.attachments || [])
|
|
35
|
+
.filter((a) => /^(state|final_state)_/.test(a.suggestedHumanReadableName || ""))
|
|
36
|
+
.sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0))
|
|
37
|
+
.map((a) => ({
|
|
38
|
+
src: "screenshots/" + a.exportedFileName,
|
|
39
|
+
caption:
|
|
40
|
+
(a.suggestedHumanReadableName || "")
|
|
41
|
+
.replace(/^final_state_\d*_?/, "final state ")
|
|
42
|
+
.replace(/^state_\d+_/, "")
|
|
43
|
+
.replace(/_?\d*_?[0-9A-F]{8}-[0-9A-F-]{27}\.png$/i, "")
|
|
44
|
+
.replace(/\.png$/, "")
|
|
45
|
+
.replace(/[-_]/g, " ")
|
|
46
|
+
.trim() || "screen",
|
|
47
|
+
}));
|
|
48
|
+
} catch {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function writeHtmlReport(captureDir, { report, label = "" } = {}) {
|
|
54
|
+
const r = report || buildQaReport(path.join(captureDir, "ocqa-markers.txt"));
|
|
55
|
+
if (!r) return null;
|
|
56
|
+
|
|
57
|
+
const shots = collectShots(captureDir);
|
|
58
|
+
const video = ["exploration.webm", "exploration.mov"].find((f) => fs.existsSync(path.join(captureDir, f)));
|
|
59
|
+
|
|
60
|
+
const findingsHtml = r.findings.length
|
|
61
|
+
? r.findings
|
|
62
|
+
.map(
|
|
63
|
+
(f) => `<li>
|
|
64
|
+
<span class="sev" style="background:${SEV_COLOR[f.severity] || "#57606a"}">${esc(f.severity)}</span>
|
|
65
|
+
${esc(f.title)}${f.screen ? ` <span class="dim">— on ${esc(f.screen)}</span>` : ""}
|
|
66
|
+
${f.aiAnalysis ? `<div class="ai">why: ${esc(f.aiAnalysis)}</div>` : ""}
|
|
67
|
+
${f.suggestedFix ? `<div class="ai">fix: ${esc(f.suggestedFix)}</div>` : ""}
|
|
68
|
+
</li>`
|
|
69
|
+
)
|
|
70
|
+
.join("\n")
|
|
71
|
+
: "<li>None found ✨</li>";
|
|
72
|
+
|
|
73
|
+
const shotsHtml = shots
|
|
74
|
+
.map(
|
|
75
|
+
(s) =>
|
|
76
|
+
`<figure><a href="${esc(s.src)}"><img loading="lazy" src="${esc(s.src)}" alt="${esc(s.caption)}"></a><figcaption>${esc(s.caption)}</figcaption></figure>`
|
|
77
|
+
)
|
|
78
|
+
.join("\n");
|
|
79
|
+
|
|
80
|
+
const videoHtml = video
|
|
81
|
+
? `<h2>Recording — the full exploration</h2>\n<video controls preload="metadata" style="width:100%;border:1px solid #d0d7de;border-radius:8px" src="${esc(video)}"></video>`
|
|
82
|
+
: "";
|
|
83
|
+
|
|
84
|
+
const html = `<!doctype html>
|
|
85
|
+
<html lang="en">
|
|
86
|
+
<head>
|
|
87
|
+
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
88
|
+
<title>Tapp report — ${esc(label || path.basename(captureDir))}</title>
|
|
89
|
+
<style>
|
|
90
|
+
body { font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; max-width: 900px; margin: 2rem auto; padding: 0 1rem; color: #1f2328; }
|
|
91
|
+
h1 { font-size: 1.6rem; margin-bottom: 0.2rem; }
|
|
92
|
+
.meta { color: #57606a; margin-bottom: 1.2rem; }
|
|
93
|
+
.headline { background: #f6f8fa; border-radius: 8px; padding: 0.9rem 1.1rem; margin: 1rem 0; }
|
|
94
|
+
.sev { color: #fff; border-radius: 4px; padding: 0.05rem 0.45rem; font-size: 0.78rem; font-weight: 600; margin-right: 0.4rem; }
|
|
95
|
+
ul.findings { padding-left: 1.1rem; } ul.findings li { margin-bottom: 0.6rem; }
|
|
96
|
+
.ai { color: #57606a; font-size: 0.88rem; margin: 0.15rem 0 0 0.2rem; }
|
|
97
|
+
.dim { color: #57606a; }
|
|
98
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 0.8rem; }
|
|
99
|
+
figure { margin: 0; } figure img { width: 100%; border: 1px solid #d0d7de; border-radius: 6px; }
|
|
100
|
+
figcaption { font-size: 0.8rem; color: #57606a; text-align: center; padding-top: 0.2rem; }
|
|
101
|
+
footer { margin: 2.5rem 0 1rem; color: #57606a; font-size: 0.85rem; border-top: 1px solid #d0d7de; padding-top: 0.8rem; }
|
|
102
|
+
</style>
|
|
103
|
+
</head>
|
|
104
|
+
<body>
|
|
105
|
+
<h1>${BADGE[r.verdict] || esc(r.verdict)} <span class="dim">· release score ${r.releaseScore ?? r.confidence}/100</span></h1>
|
|
106
|
+
<div class="meta">${esc(label)} · ${r.screensExplored} screens · ${r.actionsPerformed} actions · ${r.findingCounts.total} finding(s)</div>
|
|
107
|
+
<div class="headline">${esc(r.headline)}</div>
|
|
108
|
+
<h2>Findings</h2>
|
|
109
|
+
<ul class="findings">
|
|
110
|
+
${findingsHtml}
|
|
111
|
+
</ul>
|
|
112
|
+
<h2>Evidence — every screen explored</h2>
|
|
113
|
+
<div class="grid">
|
|
114
|
+
${shotsHtml || "<p class='dim'>No screenshots captured.</p>"}
|
|
115
|
+
</div>
|
|
116
|
+
${videoHtml}
|
|
117
|
+
<footer>Generated by <a href="https://github.com/aarwitz/tapp">Tapp</a> — autonomous QA with a deterministic ship/no-ship verdict. Ship with proof.</footer>
|
|
118
|
+
</body>
|
|
119
|
+
</html>
|
|
120
|
+
`;
|
|
121
|
+
const out = path.join(captureDir, "report.html");
|
|
122
|
+
fs.writeFileSync(out, html);
|
|
123
|
+
return out;
|
|
124
|
+
}
|