@ia-qa/qa-discovery 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.
- package/README.md +378 -0
- package/ROADMAP.md +321 -0
- package/dist/ai/classify.d.ts +170 -0
- package/dist/ai/classify.js +431 -0
- package/dist/ai/classify.js.map +1 -0
- package/dist/browser/extractPage.d.ts +41 -0
- package/dist/browser/extractPage.js +427 -0
- package/dist/browser/extractPage.js.map +1 -0
- package/dist/browser/openables.d.ts +19 -0
- package/dist/browser/openables.js +127 -0
- package/dist/browser/openables.js.map +1 -0
- package/dist/capture/page.d.ts +128 -0
- package/dist/capture/page.js +78 -0
- package/dist/capture/page.js.map +1 -0
- package/dist/citations.d.ts +80 -0
- package/dist/citations.js +197 -0
- package/dist/citations.js.map +1 -0
- package/dist/classificationView.d.ts +118 -0
- package/dist/classificationView.js +178 -0
- package/dist/classificationView.js.map +1 -0
- package/dist/cli/args.d.ts +3 -0
- package/dist/cli/args.js +38 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/history.d.ts +1 -0
- package/dist/cli/history.js +35 -0
- package/dist/cli/history.js.map +1 -0
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +172 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/login.d.ts +29 -0
- package/dist/cli/login.js +179 -0
- package/dist/cli/login.js.map +1 -0
- package/dist/cli/scan.d.ts +11 -0
- package/dist/cli/scan.js +246 -0
- package/dist/cli/scan.js.map +1 -0
- package/dist/cli-ai/index.d.ts +7 -0
- package/dist/cli-ai/index.js +360 -0
- package/dist/cli-ai/index.js.map +1 -0
- package/dist/config.d.ts +112 -0
- package/dist/config.js +178 -0
- package/dist/config.js.map +1 -0
- package/dist/diff.d.ts +12 -0
- package/dist/diff.js +43 -0
- package/dist/diff.js.map +1 -0
- package/dist/explore.d.ts +59 -0
- package/dist/explore.js +119 -0
- package/dist/explore.js.map +1 -0
- package/dist/healingLink.d.ts +30 -0
- package/dist/healingLink.js +127 -0
- package/dist/healingLink.js.map +1 -0
- package/dist/history.d.ts +38 -0
- package/dist/history.js +120 -0
- package/dist/history.js.map +1 -0
- package/dist/htmlReport.d.ts +2 -0
- package/dist/htmlReport.js +371 -0
- package/dist/htmlReport.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/sandbox.d.ts +9 -0
- package/dist/mcp/sandbox.js +82 -0
- package/dist/mcp/sandbox.js.map +1 -0
- package/dist/mcp/server.d.ts +120 -0
- package/dist/mcp/server.js +315 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/network.d.ts +60 -0
- package/dist/network.js +99 -0
- package/dist/network.js.map +1 -0
- package/dist/overview.d.ts +40 -0
- package/dist/overview.js +370 -0
- package/dist/overview.js.map +1 -0
- package/dist/overviewFile.d.ts +31 -0
- package/dist/overviewFile.js +101 -0
- package/dist/overviewFile.js.map +1 -0
- package/dist/scan.d.ts +159 -0
- package/dist/scan.js +402 -0
- package/dist/scan.js.map +1 -0
- package/dist/sharedCalls.d.ts +28 -0
- package/dist/sharedCalls.js +61 -0
- package/dist/sharedCalls.js.map +1 -0
- package/dist/taxonomy.d.ts +77 -0
- package/dist/taxonomy.js +153 -0
- package/dist/taxonomy.js.map +1 -0
- package/package.json +61 -0
package/dist/history.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.HISTORY_LIMIT = void 0;
|
|
37
|
+
exports.historyPath = historyPath;
|
|
38
|
+
exports.appendHistory = appendHistory;
|
|
39
|
+
exports.readHistory = readHistory;
|
|
40
|
+
exports.gitStamp = gitStamp;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const config_1 = require("./config");
|
|
44
|
+
/**
|
|
45
|
+
* Append-only run history โ `.ia-qa-discovery/history.jsonl`. Mirrors
|
|
46
|
+
* `@ia-qa/self-healing`'s `history.ts`: small, deterministic, safe to commit,
|
|
47
|
+
* never influences anything (F1 produces no verdict to influence), a write
|
|
48
|
+
* failure is swallowed (a full disk must not crash a `scan`).
|
|
49
|
+
*/
|
|
50
|
+
exports.HISTORY_LIMIT = 500;
|
|
51
|
+
function historyPath(cwd = (0, config_1.baseDir)()) {
|
|
52
|
+
return path.join(cwd, config_1.CONFIG_DIR, config_1.HISTORY_FILENAME);
|
|
53
|
+
}
|
|
54
|
+
function appendHistory(entry) {
|
|
55
|
+
const file = historyPath();
|
|
56
|
+
const full = { ts: entry.ts ?? new Date().toISOString(), ...entry };
|
|
57
|
+
try {
|
|
58
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
59
|
+
fs.appendFileSync(file, JSON.stringify(full) + '\n', 'utf8');
|
|
60
|
+
truncate(file);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
/* history is a courtesy, never a gate */
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function truncate(file) {
|
|
67
|
+
try {
|
|
68
|
+
const lines = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean);
|
|
69
|
+
if (lines.length <= exports.HISTORY_LIMIT + 100)
|
|
70
|
+
return;
|
|
71
|
+
fs.writeFileSync(file, lines.slice(-exports.HISTORY_LIMIT).join('\n') + '\n', 'utf8');
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
/* as above */
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Read the history, oldest first. A malformed line is skipped, not fatal. */
|
|
78
|
+
function readHistory(limit = exports.HISTORY_LIMIT, cwd) {
|
|
79
|
+
const file = cwd ? path.join(cwd, config_1.CONFIG_DIR, config_1.HISTORY_FILENAME) : historyPath();
|
|
80
|
+
let raw;
|
|
81
|
+
try {
|
|
82
|
+
raw = fs.readFileSync(file, 'utf8');
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
const out = [];
|
|
88
|
+
for (const line of raw.split('\n')) {
|
|
89
|
+
if (!line.trim())
|
|
90
|
+
continue;
|
|
91
|
+
try {
|
|
92
|
+
const parsed = JSON.parse(line);
|
|
93
|
+
if (parsed && typeof parsed.ts === 'string' && parsed.verb === 'scan')
|
|
94
|
+
out.push(parsed);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
/* skip */
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return out.slice(-limit);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Best-effort git stamp โ never throws, never blocks. No git, no repo,
|
|
104
|
+
* detached HEAD: the entry is written without it.
|
|
105
|
+
*/
|
|
106
|
+
function gitStamp() {
|
|
107
|
+
const { execFileSync } = require('child_process');
|
|
108
|
+
const run = (args) => {
|
|
109
|
+
try {
|
|
110
|
+
const out = execFileSync('git', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 2000 });
|
|
111
|
+
const v = out.trim();
|
|
112
|
+
return v === '' ? undefined : v;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
return { commit: run(['rev-parse', '--short', 'HEAD']), branch: run(['rev-parse', '--abbrev-ref', 'HEAD']) };
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderScanReport = renderScanReport;
|
|
4
|
+
const classificationView_1 = require("./classificationView");
|
|
5
|
+
/**
|
|
6
|
+
* A branded, self-contained HTML report for one discovery scan.
|
|
7
|
+
*
|
|
8
|
+
* Adapted from `@ia-qa/self-healing`'s report, NOT copied: that one is built
|
|
9
|
+
* around a verdict (PASS/FIX/BLOCK) and rows waiting on a human decision.
|
|
10
|
+
* Discovery produces no verdict โ it is reconnaissance โ so the shape here is
|
|
11
|
+
* an **app dossier**: what was reached, what was NOT reached (first, because a
|
|
12
|
+
* page count is not coverage), the entry points, the API surface, and the
|
|
13
|
+
* testability of what a suite would have to locate.
|
|
14
|
+
*
|
|
15
|
+
* Zero-dependency, pure string: no browser, no network, nothing leaves the
|
|
16
|
+
* machine. CSS and links are inline, so the file is shareable as-is.
|
|
17
|
+
*/
|
|
18
|
+
const BRAND = {
|
|
19
|
+
home: 'https://www.ia-qa.com',
|
|
20
|
+
method: 'https://www.ia-qa.com/method',
|
|
21
|
+
tools: 'https://www.ia-qa.com/all-tools',
|
|
22
|
+
};
|
|
23
|
+
function esc(s) {
|
|
24
|
+
return String(s == null ? '' : s)
|
|
25
|
+
.replace(/&/g, '&')
|
|
26
|
+
.replace(/</g, '<')
|
|
27
|
+
.replace(/>/g, '>')
|
|
28
|
+
.replace(/"/g, '"')
|
|
29
|
+
.replace(/'/g, ''');
|
|
30
|
+
}
|
|
31
|
+
function submitTarget(form) {
|
|
32
|
+
if (form.action === null && form.method === null)
|
|
33
|
+
return 'not stated โ JS-handled submit';
|
|
34
|
+
const method = form.method ? form.method.toUpperCase() : 'GET (default)';
|
|
35
|
+
const action = form.action ? `<code>${esc(form.action)}</code>` : 'the page itself';
|
|
36
|
+
return `${method} โ ${action}`;
|
|
37
|
+
}
|
|
38
|
+
const REPORT_CSS = `
|
|
39
|
+
:root{--bg:#09090b;--surface:#18181b;--surface2:#111113;--border:#27272a;--text:#e4e4e7;--muted:#a1a1aa;
|
|
40
|
+
--orange:#f97316;--orange2:#fb923c;--pass:#10b981;--fail:#ef4444;--warn:#f59e0b;--info:#8b5cf6;}
|
|
41
|
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
42
|
+
body{background:var(--bg);color:var(--text);font-family:'Inter',system-ui,-apple-system,sans-serif;line-height:1.5;min-height:100vh;display:flex;flex-direction:column}
|
|
43
|
+
a{color:var(--orange2);text-decoration:none}a:hover{text-decoration:underline}
|
|
44
|
+
code{font-family:'JetBrains Mono','Fira Code',ui-monospace,monospace;font-size:12px}
|
|
45
|
+
.wrap{width:100%;max-width:980px;margin:0 auto;padding:0 20px}
|
|
46
|
+
header{background:linear-gradient(135deg,#7c2d12 0%,#ea580c 55%,#f97316 100%);padding:28px 0 26px;border-bottom:1px solid var(--border)}
|
|
47
|
+
.brand{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:14px}
|
|
48
|
+
.logo{display:inline-flex;align-items:center;gap:9px;font-weight:800;letter-spacing:.02em;font-size:19px;color:#fff}
|
|
49
|
+
.logo .mark{background:#fff;color:#ea580c;border-radius:8px;padding:3px 9px;font-size:15px;letter-spacing:.08em}
|
|
50
|
+
.logo:hover{text-decoration:none;opacity:.92}
|
|
51
|
+
.cta{background:rgba(255,255,255,.16);border:1px solid rgba(255,255,255,.35);color:#fff;padding:8px 15px;border-radius:99px;font-size:13px;font-weight:600}
|
|
52
|
+
.cta:hover{background:rgba(255,255,255,.26);text-decoration:none}
|
|
53
|
+
.title{color:#fff;margin-top:20px}
|
|
54
|
+
.title h1{font-size:24px;font-weight:700}
|
|
55
|
+
.title .meta{color:rgba(255,255,255,.85);font-size:12.5px;margin-top:8px}
|
|
56
|
+
.title .meta code{color:#fff;background:rgba(0,0,0,.28);padding:1px 7px;border-radius:6px}
|
|
57
|
+
main{flex:1;padding:28px 0 40px}
|
|
58
|
+
section{background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:20px;margin-bottom:18px}
|
|
59
|
+
section>h2{font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin-bottom:14px;font-weight:700}
|
|
60
|
+
.hero{border-left:3px solid var(--orange);display:flex;flex-direction:column;gap:14px}
|
|
61
|
+
.lede{color:var(--muted);font-size:13.5px;line-height:1.6;max-width:78ch}
|
|
62
|
+
.lede code{background:var(--surface2);border:1px solid var(--border);border-radius:6px;padding:1px 6px;color:var(--orange2)}
|
|
63
|
+
.stats{display:flex;flex-wrap:wrap;gap:10px}
|
|
64
|
+
.stat{background:var(--surface2);border:1px solid var(--border);border-radius:10px;padding:10px 14px;min-width:112px}
|
|
65
|
+
.stat .n{font-size:21px;font-weight:700;line-height:1.2}
|
|
66
|
+
.stat .l{font-size:10.5px;text-transform:uppercase;letter-spacing:.07em;color:var(--muted);margin-top:3px;font-weight:600}
|
|
67
|
+
.callout{background:var(--surface2);border:1px solid var(--border);border-left:3px solid var(--warn);border-radius:10px;padding:14px 16px;font-size:13.5px;line-height:1.65;max-width:82ch}
|
|
68
|
+
.callout.bad{border-left-color:var(--fail)}
|
|
69
|
+
.callout .cl-label{display:block;font-size:10.5px;text-transform:uppercase;letter-spacing:.08em;color:var(--warn);font-weight:700;margin-bottom:6px}
|
|
70
|
+
.callout.bad .cl-label{color:var(--fail)}
|
|
71
|
+
.callout code{background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:1px 6px;color:var(--orange2)}
|
|
72
|
+
.ok-note{color:var(--pass);font-size:13px}
|
|
73
|
+
table{width:100%;border-collapse:collapse;font-size:13px}
|
|
74
|
+
th{text-align:left;font-size:10.5px;text-transform:uppercase;letter-spacing:.07em;color:var(--muted);font-weight:700;padding:7px 10px;border-bottom:1px solid var(--border)}
|
|
75
|
+
td{padding:8px 10px;border-bottom:1px solid var(--surface2);vertical-align:top}
|
|
76
|
+
tr:last-child td{border-bottom:none}
|
|
77
|
+
.scroll{overflow-x:auto}
|
|
78
|
+
.pill{display:inline-block;padding:1px 8px;border-radius:99px;font-size:11px;font-weight:700;border:1px solid}
|
|
79
|
+
.pill.pass{color:var(--pass);border-color:rgba(16,185,129,.4);background:rgba(16,185,129,.1)}
|
|
80
|
+
.pill.warn{color:var(--warn);border-color:rgba(245,158,11,.4);background:rgba(245,158,11,.1)}
|
|
81
|
+
.pill.fail{color:var(--fail);border-color:rgba(239,68,68,.4);background:rgba(239,68,68,.1)}
|
|
82
|
+
.pill.info{color:var(--info);border-color:rgba(139,92,246,.4);background:rgba(139,92,246,.1)}
|
|
83
|
+
.muted{color:var(--muted)}
|
|
84
|
+
.form-card{background:var(--surface2);border:1px solid var(--border);border-radius:10px;padding:14px 16px;margin-bottom:12px}
|
|
85
|
+
.form-card h3{font-size:14px;font-weight:700;margin-bottom:6px}
|
|
86
|
+
.form-card .fmeta{font-size:12.5px;color:var(--muted);margin-bottom:10px}
|
|
87
|
+
.note{font-size:12.5px;color:var(--muted);line-height:1.6;margin-top:10px;max-width:82ch}
|
|
88
|
+
footer{border-top:1px solid var(--border);padding:18px 0 26px;color:var(--muted);font-size:12px}
|
|
89
|
+
footer a{color:var(--orange2)}
|
|
90
|
+
`;
|
|
91
|
+
function renderScanReport(input) {
|
|
92
|
+
const { manifest, pages, shared, loginWall, session, hrefBlind, droppedByCap, sitemapDeclared, classification } = input;
|
|
93
|
+
const formCount = pages.reduce((n, p) => n + p.forms.length, 0);
|
|
94
|
+
const fields = pages.flatMap((p) => [...p.forms.flatMap((f) => f.fields), ...p.looseFields]);
|
|
95
|
+
const unstable = fields.filter((f) => !f.stableSelector);
|
|
96
|
+
const pageCalls = pages.reduce((n, p) => n + p.apiCalls.length, 0);
|
|
97
|
+
const parts = [];
|
|
98
|
+
parts.push(`<!doctype html><html lang="en"><head><meta charset="utf-8">
|
|
99
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
100
|
+
<title>Discovery โ ${esc(manifest.baseUrl)}</title>
|
|
101
|
+
<style>${REPORT_CSS}</style></head><body>`);
|
|
102
|
+
parts.push(`<header><div class="wrap">
|
|
103
|
+
<div class="brand">
|
|
104
|
+
<a class="logo" href="${BRAND.home}"><span class="mark">IA-QA</span> qa-discovery</a>
|
|
105
|
+
<a class="cta" href="${BRAND.method}">Testing methods โ</a>
|
|
106
|
+
</div>
|
|
107
|
+
<div class="title">
|
|
108
|
+
<h1>App surface โ ${esc(manifest.baseUrl)}</h1>
|
|
109
|
+
<div class="meta">Captured <code>${esc(manifest.capturedAt)}</code>${session ? ' ยท with a saved session' : ' ยท anonymous'}</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div></header><main><div class="wrap">`);
|
|
112
|
+
// โโ Hero: what this is, and the coverage caveat FIRST โโโโโโโโโโโโโโโโโโโโ
|
|
113
|
+
parts.push(`<section class="hero">
|
|
114
|
+
<div class="lede">This is <strong>reconnaissance, not a test plan and not a verdict</strong>. Every figure below traces to
|
|
115
|
+
something observed in the DOM or on the network by a local headless browser โ nothing was inferred, and nothing
|
|
116
|
+
left this machine. Regenerate with <code>ia-qa-discover scan</code>.</div>
|
|
117
|
+
<div class="stats">
|
|
118
|
+
<div class="stat"><div class="n">${manifest.pagesCaptured}</div><div class="l">Pages captured</div></div>
|
|
119
|
+
<div class="stat"><div class="n">${formCount}</div><div class="l">Forms</div></div>
|
|
120
|
+
<div class="stat"><div class="n">${fields.length}</div><div class="l">Input fields</div></div>
|
|
121
|
+
<div class="stat"><div class="n">${pageCalls + shared.calls.length}</div><div class="l">API calls</div></div>
|
|
122
|
+
<div class="stat"><div class="n">${manifest.origins.length}</div><div class="l">Origins called</div></div>
|
|
123
|
+
</div>`);
|
|
124
|
+
const openable = pages.reduce((n, p) => n + (p.exploration?.openable ?? 0), 0);
|
|
125
|
+
const explored = pages.some((p) => p.exploration?.explored);
|
|
126
|
+
const revealedTotal = pages.reduce((n, p) => n + (p.exploration?.revealed ?? 0), 0);
|
|
127
|
+
const collidedTotal = pages.reduce((n, p) => n + (p.exploration?.collided ?? 0), 0);
|
|
128
|
+
if (!explored && openable > 0) {
|
|
129
|
+
parts.push(`<div class="callout"><span class="cl-label">Coverage โ the loaded state only</span>
|
|
130
|
+
These pages carry <strong>${openable} control(s) that can reveal more</strong> โ menus, tabs, accordions, a button that
|
|
131
|
+
opens a dialog. Only what the page shows on load was captured, so the ${fields.length} field(s) counted above are the
|
|
132
|
+
input surface <em>at rest</em>, not the whole one. On one measured app a single page had 64 such controls and 2
|
|
133
|
+
visible fields.<br>
|
|
134
|
+
Run <code>ia-qa-discover scan --deep</code> to open them and capture what they reveal.</div>`);
|
|
135
|
+
}
|
|
136
|
+
else if (explored) {
|
|
137
|
+
parts.push(`<div class="ok-note">๐ <code>--deep</code> opened ${openable} control(s) and found <strong>${revealedTotal}
|
|
138
|
+
field(s) not visible at load</strong>${collidedTotal > 0 ? ` ยท โ ${collidedTotal} reached but not captured (their selector already named another element in a different state)` : ''}.</div>`);
|
|
139
|
+
}
|
|
140
|
+
if (droppedByCap && droppedByCap > 0) {
|
|
141
|
+
parts.push(`<div class="callout"><span class="cl-label">Coverage โ the page cap was reached</span>
|
|
142
|
+
<strong>${droppedByCap} more page(s) were known and not visited</strong>, because <code>--max</code> stopped the run at
|
|
143
|
+
${manifest.pagesCaptured}.${sitemapDeclared ? ` The sitemap alone declared ${sitemapDeclared}.` : ''}
|
|
144
|
+
The count above is a budget, not a census โ raise <code>--max</code> to cover more.</div>`);
|
|
145
|
+
}
|
|
146
|
+
if (hrefBlind) {
|
|
147
|
+
parts.push(`<div class="callout bad"><span class="cl-label">Coverage โ discovery could not see past the entry URL</span>
|
|
148
|
+
<strong>No internal links were found.</strong> Discovery follows <code><a href></code>; an app that navigates
|
|
149
|
+
programmatically โ React Router's <code>useNavigate()</code>, a button with an onClick handler โ exposes no href for a
|
|
150
|
+
crawl to follow. So โ${manifest.pagesCaptured} of ${manifest.pagesFound} foundโ above describes <strong>one page</strong>,
|
|
151
|
+
not the application: the denominator is what the crawl could reach, not what exists.<br>
|
|
152
|
+
Declare the routes you know in <code>.ia-qa-discovery/config.json</code> โ <code>"pages"</code>, then scan again.</div>`);
|
|
153
|
+
}
|
|
154
|
+
else if (loginWall.length > 1) {
|
|
155
|
+
parts.push(`<div class="callout bad"><span class="cl-label">Coverage โ you are probably looking at the login wall</span>
|
|
156
|
+
${loginWall.length} of ${manifest.pagesCaptured} captured pages still show a login form
|
|
157
|
+
(${loginWall.map((p) => `<code>${esc(p)}</code>`).join(', ')}). An app whose catch-all route renders the login page
|
|
158
|
+
answers HTTP 200 at <em>every</em> URL, so the page count above looks like success while the same wall was
|
|
159
|
+
captured several times. <strong>A page count is not coverage.</strong><br>
|
|
160
|
+
${session
|
|
161
|
+
? 'The saved session did not get past it โ it may have expired. Re-run <code>ia-qa-discover login</code>.'
|
|
162
|
+
: 'Run <code>ia-qa-discover login</code> to log in by hand once, then scan again.'}</div>`);
|
|
163
|
+
}
|
|
164
|
+
else if (loginWall.length === 1 && !session) {
|
|
165
|
+
parts.push(`<div class="callout"><span class="cl-label">Coverage โ public surface only</span>
|
|
166
|
+
This app has a login (<code>${esc(loginWall[0])}</code>) and no session was used, so whatever is behind it was
|
|
167
|
+
never reached. What follows describes the <strong>public surface</strong>.<br>
|
|
168
|
+
Run <code>ia-qa-discover login</code> to log in by hand once, then scan again.</div>`);
|
|
169
|
+
}
|
|
170
|
+
else if (session) {
|
|
171
|
+
parts.push(`<div class="ok-note">๐ Scanned with a saved session โ authenticated pages were reachable.</div>`);
|
|
172
|
+
}
|
|
173
|
+
if (manifest.pagesFailed > 0) {
|
|
174
|
+
parts.push(`<div class="callout"><span class="cl-label">Incomplete</span>
|
|
175
|
+
${manifest.pagesFailed} page(s) could not be captured, so this describes ${manifest.pagesCaptured} of
|
|
176
|
+
${manifest.pagesFound} pages found.</div>`);
|
|
177
|
+
}
|
|
178
|
+
parts.push(`</section>`);
|
|
179
|
+
// โโ Pages โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
180
|
+
parts.push(`<section><h2>Pages (${pages.length})</h2><div class="scroll"><table>
|
|
181
|
+
<thead><tr><th>Page</th><th>Path</th><th>Title</th><th>Headings</th><th>Forms</th><th>Fields</th><th>API calls</th></tr></thead><tbody>`);
|
|
182
|
+
for (const p of pages) {
|
|
183
|
+
let pathname = p.url;
|
|
184
|
+
try {
|
|
185
|
+
pathname = new URL(p.url).pathname;
|
|
186
|
+
}
|
|
187
|
+
catch { /* keep raw */ }
|
|
188
|
+
const formed = p.forms.reduce((n, f) => n + f.fields.length, 0);
|
|
189
|
+
const fieldCell = formed + p.looseFields.length + (p.looseFields.length > 0 ? ` <span class="muted">(${p.looseFields.length} loose)</span>` : '');
|
|
190
|
+
parts.push(`<tr><td><strong>${esc(p.page)}</strong></td><td><code>${esc(pathname)}</code></td>
|
|
191
|
+
<td>${esc(p.meta.title || 'โ')}</td><td>${p.headings.length}</td><td>${p.forms.length}</td><td>${fieldCell}</td><td>${p.apiCalls.length}</td></tr>`);
|
|
192
|
+
}
|
|
193
|
+
parts.push(`</tbody></table></div>`);
|
|
194
|
+
const distinctTitles = new Set(pages.map((p) => p.meta.title).filter(Boolean));
|
|
195
|
+
if (pages.length > 2 && distinctTitles.size === 1) {
|
|
196
|
+
parts.push(`<div class="callout"><span class="cl-label">All ${pages.length} pages share one title</span>
|
|
197
|
+
Every captured page reports <code>${esc([...distinctTitles][0])}</code>. The browser tab, the history entry and a
|
|
198
|
+
screen reader announcing a route change cannot tell these pages apart.</div>`);
|
|
199
|
+
}
|
|
200
|
+
parts.push(`</section>`);
|
|
201
|
+
// โโ Forms โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
202
|
+
if (formCount > 0) {
|
|
203
|
+
parts.push(`<section><h2>Entry points โ forms (${formCount})</h2>
|
|
204
|
+
<div class="note">Where this app takes input: where a suite starts, and where validation, auth and payment
|
|
205
|
+
behaviour live. <strong>Locatable</strong> says whether a test could target the field reliably โ it is a fact
|
|
206
|
+
about the app, not about this scan.</div>`);
|
|
207
|
+
for (const p of pages) {
|
|
208
|
+
for (const form of p.forms) {
|
|
209
|
+
const required = form.fields.filter((f) => f.required).length;
|
|
210
|
+
parts.push(`<div class="form-card">
|
|
211
|
+
<h3>${esc(p.page)} <span class="muted">โ ${form.fields.length} field${form.fields.length === 1 ? '' : 's'}${required > 0 ? `, ${required} required` : ''}</span></h3>
|
|
212
|
+
<div class="fmeta">Selector <code>${esc(form.selector)}</code> ยท Submits to ${submitTarget(form)} ยท
|
|
213
|
+
Submit control ${form.hasSubmit ? 'present' : '<strong>absent</strong>'}</div>`);
|
|
214
|
+
if (form.fields.length > 0) {
|
|
215
|
+
parts.push(`<div class="scroll"><table><thead><tr><th>Field</th><th>Type</th><th>Required</th><th>Selector</th><th>Locatable</th></tr></thead><tbody>`);
|
|
216
|
+
for (const f of form.fields) {
|
|
217
|
+
const pill = f.ambiguousSelector
|
|
218
|
+
? '<span class="pill fail">ambiguous</span>'
|
|
219
|
+
: f.stableSelector
|
|
220
|
+
? '<span class="pill pass">stable</span>'
|
|
221
|
+
: '<span class="pill warn">positional</span>';
|
|
222
|
+
parts.push(`<tr><td>${esc(f.name || '(unnamed)')}</td><td>${esc(f.type)}</td>
|
|
223
|
+
<td>${f.required ? 'yes' : 'no'}</td><td><code>${esc(f.selector)}</code></td><td>${pill}</td></tr>`);
|
|
224
|
+
}
|
|
225
|
+
parts.push(`</tbody></table></div>`);
|
|
226
|
+
}
|
|
227
|
+
parts.push(`</div>`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
parts.push(`</section>`);
|
|
231
|
+
}
|
|
232
|
+
// โโ Loose fields โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
233
|
+
const looseTotal = pages.reduce((n, p) => n + p.looseFields.length, 0);
|
|
234
|
+
if (looseTotal > 0) {
|
|
235
|
+
parts.push(`<section><h2>Input fields outside a form (${looseTotal})</h2>
|
|
236
|
+
<div class="note">Inputs this app takes <strong>without</strong> a <code><form></code> wrapper โ submission is handled
|
|
237
|
+
in JS. They are entry points all the same, and for most React/Vue apps they are the <em>only</em> ones.</div>`);
|
|
238
|
+
for (const p of pages) {
|
|
239
|
+
if (p.looseFields.length === 0)
|
|
240
|
+
continue;
|
|
241
|
+
parts.push(`<div class="form-card">
|
|
242
|
+
<h3>${esc(p.page)} <span class="muted">โ ${p.looseFields.length} field${p.looseFields.length === 1 ? '' : 's'}</span></h3>
|
|
243
|
+
<div class="scroll"><table><thead><tr><th>Field</th><th>Type</th><th>Required</th><th>Where / how to reach</th><th>Selector</th><th>Locatable</th></tr></thead><tbody>`);
|
|
244
|
+
for (const f of p.looseFields) {
|
|
245
|
+
const pill = f.ambiguousSelector
|
|
246
|
+
? '<span class="pill fail">ambiguous</span>'
|
|
247
|
+
: f.stableSelector
|
|
248
|
+
? '<span class="pill pass">stable</span>'
|
|
249
|
+
: '<span class="pill warn">positional</span>';
|
|
250
|
+
parts.push(`<tr><td>${esc(f.name || '(unnamed)')}</td><td>${esc(f.type)}</td><td>${f.required ? 'yes' : 'no'}</td>
|
|
251
|
+
<td>${f.via?.length ? `๐ click ${f.via.map((v) => `โ${esc(v)}โ`).join(' โบ ')}` : esc(f.context || 'โ')}</td><td><code>${esc(f.selector)}</code></td><td>${pill}</td></tr>`);
|
|
252
|
+
}
|
|
253
|
+
parts.push(`</tbody></table></div></div>`);
|
|
254
|
+
}
|
|
255
|
+
parts.push(`</section>`);
|
|
256
|
+
}
|
|
257
|
+
// โโ API surface โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
258
|
+
parts.push(`<section><h2>API surface</h2>`);
|
|
259
|
+
if (manifest.origins.length === 0) {
|
|
260
|
+
parts.push(`<div class="note">No XHR/fetch traffic was observed โ either the app makes none, or this scan ran with <code>--no-network</code>.</div>`);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
parts.push(`<div class="scroll"><table><thead><tr><th>Origin</th><th>Calls</th><th>Pages</th><th>Same-origin as the app</th></tr></thead><tbody>`);
|
|
264
|
+
for (const o of manifest.origins) {
|
|
265
|
+
parts.push(`<tr><td><code>${esc(o.origin)}</code></td><td>${o.calls}</td><td>${o.pages}</td>
|
|
266
|
+
<td>${o.sameOrigin ? '<span class="pill pass">yes</span>' : '<span class="pill info">no</span>'}</td></tr>`);
|
|
267
|
+
}
|
|
268
|
+
parts.push(`</tbody></table></div>
|
|
269
|
+
<div class="note"><strong>โSame-origin: noโ is not the same as third-party.</strong> An app whose API lives on its
|
|
270
|
+
own separate host (an <code>api.*</code> subdomain, a platform backend) has every one of its <em>own</em> calls listed
|
|
271
|
+
here as cross-origin, right next to any analytics or ad host. Deciding which of these are first-party is a judgment
|
|
272
|
+
this stage does not make โ nothing in the DOM states it.</div>`);
|
|
273
|
+
}
|
|
274
|
+
if (shared.calls.length > 0) {
|
|
275
|
+
const minPages = Math.max(2, Math.ceil(shared.threshold * shared.sourcePages));
|
|
276
|
+
parts.push(`<h2 style="margin-top:22px">Shared calls โ on โฅ ${minPages} of ${shared.sourcePages} pages</h2>
|
|
277
|
+
<div class="note">Cross-cutting traffic: session refresh, telemetry, feature flags. Promoted out of the per-page
|
|
278
|
+
captures so one analytics beacon does not read as N page-specific calls.</div>
|
|
279
|
+
<div class="scroll"><table><thead><tr><th>Method</th><th>URL</th><th>Status</th><th>Pages</th></tr></thead><tbody>`);
|
|
280
|
+
for (const c of shared.calls) {
|
|
281
|
+
parts.push(`<tr><td>${esc(c.method)}</td><td><code>${esc(c.urlPattern)}</code></td><td>${c.status}</td><td>${c.count}</td></tr>`);
|
|
282
|
+
}
|
|
283
|
+
parts.push(`</tbody></table></div>`);
|
|
284
|
+
}
|
|
285
|
+
const perPage = pages.filter((p) => p.apiCalls.length > 0);
|
|
286
|
+
if (perPage.length > 0) {
|
|
287
|
+
parts.push(`<h2 style="margin-top:22px">Page-specific calls</h2><div class="scroll"><table>
|
|
288
|
+
<thead><tr><th>Page</th><th>Method</th><th>URL</th><th>Status</th></tr></thead><tbody>`);
|
|
289
|
+
for (const p of perPage) {
|
|
290
|
+
for (const c of p.apiCalls) {
|
|
291
|
+
parts.push(`<tr><td>${esc(p.page)}</td><td>${esc(c.method)}</td><td><code>${esc(c.urlPattern)}</code></td><td>${c.status}</td></tr>`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
parts.push(`</tbody></table></div>`);
|
|
295
|
+
}
|
|
296
|
+
parts.push(`</section>`);
|
|
297
|
+
// โโ Model-classified entry points โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
298
|
+
// Its own section, its own label, its own ink. Everything above is a literal
|
|
299
|
+
// observation; this is a reading of one, and a shared table would lend it a
|
|
300
|
+
// credibility it has not earned.
|
|
301
|
+
if (classification) {
|
|
302
|
+
const c = classification;
|
|
303
|
+
parts.push(`<section><h2>๐ค What these entry points are FOR <span class="pill info">model-classified</span></h2>
|
|
304
|
+
<div class="meta">${esc((0, classificationView_1.classificationHeadline)(c))} ยท <code>${esc(c.model)}</code> ยท <code>${esc(c.taxonomyVersion)}</code> ยท classified <code>${esc(c.classifiedAt)}</code></div>
|
|
305
|
+
<div class="note">${esc(classificationView_1.CLASSIFICATION_CAVEAT)}</div>`);
|
|
306
|
+
if (c.staleCount > 0) {
|
|
307
|
+
parts.push(`<div class="callout"><span class="cl-label">${c.staleCount} page${c.staleCount === 1 ? '' : 's'} below ${c.staleCount === 1 ? 'is' : 'are'} stale</span>${esc(classificationView_1.STALE_CAVEAT)}</div>`);
|
|
308
|
+
}
|
|
309
|
+
for (const p of c.pages) {
|
|
310
|
+
if (p.failed) {
|
|
311
|
+
parts.push(`<h3 style="margin-top:22px"><code>${esc(p.page)}</code> <span class="pill warn">not classified</span></h3>
|
|
312
|
+
<div class="note">The model call did not succeed: ${esc(p.failed)} Nothing is wrong with this page.</div>`);
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (p.kept.length === 0 && p.refused.length === 0)
|
|
316
|
+
continue;
|
|
317
|
+
parts.push(`<h3 style="margin-top:22px"><code>${esc(p.page)}</code>${p.stale ? ' <span class="pill warn">stale</span>' : ''}</h3>
|
|
318
|
+
<div class="scroll"><table><thead><tr><th>Entry point</th><th>Intent</th><th>Handles</th><th>Confidence</th><th>Why</th></tr></thead><tbody>`);
|
|
319
|
+
for (const k of p.kept) {
|
|
320
|
+
parts.push(`<tr><td><code>${esc(k.entryPoint.split('#')[1] ?? k.entryPoint)}</code></td><td><strong>${esc(k.intent)}</strong></td><td>${esc(k.sensitivity)}</td><td>${k.confidence.toFixed(2)}</td><td>${esc(k.why)}</td></tr>`);
|
|
321
|
+
}
|
|
322
|
+
// A refusal is an answer, shown as one โ never dropped, never styled as a failure.
|
|
323
|
+
for (const r of p.refused) {
|
|
324
|
+
parts.push(`<tr><td><code>${esc(r.entryPoint.split('#')[1] ?? r.entryPoint)}</code></td><td class="muted"><em>declined</em></td><td class="muted">โ</td><td>${r.confidence.toFixed(2)}</td><td>${esc(r.why)}</td></tr>`);
|
|
325
|
+
}
|
|
326
|
+
parts.push(`</tbody></table></div>`);
|
|
327
|
+
if (p.dropped > 0) {
|
|
328
|
+
parts.push(`<div class="note">${p.dropped} classification${p.dropped === 1 ? '' : 's'} on this page ${p.dropped === 1 ? 'was' : 'were'} dropped: the evidence did not check out. See <code>classification.json</code>.</div>`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (c.securityTools.length > 0) {
|
|
332
|
+
parts.push(`<h3 style="margin-top:22px">Security checks this selects</h3>
|
|
333
|
+
<div class="note">Chosen deterministically by the taxonomy from the intent above โ never by the model. Available on the ia-qa.com MCP server.</div>
|
|
334
|
+
<div class="scroll"><table><thead><tr><th>Tool</th><th>For</th></tr></thead><tbody>`);
|
|
335
|
+
for (const t of c.securityTools) {
|
|
336
|
+
parts.push(`<tr><td><code>${esc(t.tool)}</code></td><td>${t.pages.map((p) => `<code>${esc(p)}</code>`).join(' ')}</td></tr>`);
|
|
337
|
+
}
|
|
338
|
+
parts.push(`</tbody></table></div>`);
|
|
339
|
+
}
|
|
340
|
+
// The honesty counter, in the same place the deterministic sections put theirs.
|
|
341
|
+
if (c.unclassified.length > 0) {
|
|
342
|
+
parts.push(`<div class="callout"><span class="cl-label">${c.unclassified.length} page${c.unclassified.length === 1 ? '' : 's'} with entry points carry no classification</span>
|
|
343
|
+
Either they were not sent, or their call failed: ${c.unclassified.map((p) => `<code>${esc(p)}</code>`).join(' ')}. The counts above describe what was classified, not the app.</div>`);
|
|
344
|
+
}
|
|
345
|
+
if (c.noEntryPoints > 0) {
|
|
346
|
+
parts.push(`<div class="note">${c.noEntryPoints} captured page${c.noEntryPoints === 1 ? '' : 's'} take no input at all, so there was nothing on them to classify.</div>`);
|
|
347
|
+
}
|
|
348
|
+
parts.push(`</section>`);
|
|
349
|
+
}
|
|
350
|
+
// โโ Testability โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
351
|
+
if (unstable.length > 0) {
|
|
352
|
+
parts.push(`<section><h2>Testability</h2>
|
|
353
|
+
<div class="callout"><span class="cl-label">${unstable.length} of ${fields.length} field${fields.length === 1 ? '' : 's'} cannot be located durably</span>
|
|
354
|
+
They carry no <code>data-testid</code>, no <code>id</code> and no <code>name</code>, so the selectors above for them are
|
|
355
|
+
positional: they resolve today and break the moment a field is inserted above them. Adding a
|
|
356
|
+
<code>data-testid</code> to each is the cheapest durable fix โ and it is worth doing <em>before</em> a suite is written
|
|
357
|
+
against them, not after.</div></section>`);
|
|
358
|
+
}
|
|
359
|
+
parts.push(`</div></main><footer><div class="wrap">
|
|
360
|
+
Generated by <a href="${BRAND.home}">IA-QA</a> <code>qa-discovery</code> โ ${
|
|
361
|
+
// "no LLM" is true of the capture and false of this document the moment a
|
|
362
|
+
// classification section is in it. A footer that keeps saying it either way
|
|
363
|
+
// is the smallest possible version of the lie this package is built against.
|
|
364
|
+
classification
|
|
365
|
+
? `capture deterministic and local; the classified section came from <code>${esc(classification.model)}</code>.`
|
|
366
|
+
: 'deterministic, local, no LLM.'}
|
|
367
|
+
ยท <a href="${BRAND.method}">Methods</a> ยท <a href="${BRAND.tools}">Tools</a>
|
|
368
|
+
</div></footer></body></html>`);
|
|
369
|
+
return parts.join('\n');
|
|
370
|
+
}
|
|
371
|
+
//# sourceMappingURL=htmlReport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"htmlReport.js","sourceRoot":"","sources":["../src/htmlReport.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export { scanApp } from './scan';
|
|
2
|
+
export type { ScanOptions, ScanResult, SurfaceManifest } from './scan';
|
|
3
|
+
export { SURFACE_SCHEMA } from './scan';
|
|
4
|
+
export { readHistory, appendHistory, historyPath, HISTORY_LIMIT } from './history';
|
|
5
|
+
export type { ScanHistoryEntry } from './history';
|
|
6
|
+
export { capturePage, PAGE_CAPTURE_SCHEMA } from './capture/page';
|
|
7
|
+
export type { PageCapture, PageMeta, Heading, CapturedForm, FormField } from './capture/page';
|
|
8
|
+
export { SHARED_CALLS_SCHEMA } from './sharedCalls';
|
|
9
|
+
export type { SharedCallsFile } from './sharedCalls';
|
|
10
|
+
export { compareCaptures } from './diff';
|
|
11
|
+
export type { CaptureDiff } from './diff';
|
|
12
|
+
export { renderOverview, OVERVIEW_FILENAME } from './overview';
|
|
13
|
+
export type { OverviewInput } from './overview';
|
|
14
|
+
export { renderScanReport } from './htmlReport';
|
|
15
|
+
export { loadConfig, saveConfig, configExists, baseDir, setBaseDir, captureDir, configPath, sessionPath, sessionTarget, resolveSession, CONFIG_DIR, SESSION_FILENAME, } from './config';
|
|
16
|
+
export type { DiscoveryConfig, CaptureConfig, SessionChoice, SessionSource } from './config';
|
|
17
|
+
export { normalizeApiUrl, apiCallFingerprint, DEFAULT_SAFE_QUERY_PARAMS } from './network';
|
|
18
|
+
export type { ApiCall } from './network';
|
|
19
|
+
export { verifyCitation, verifyClaim, loadCaptureIndex, SHARED_REF } from './citations';
|
|
20
|
+
export type { Citation, CitationVerdict, CitationFailure, CaptureIndex, VerifiedClaim } from './citations';
|
|
21
|
+
export { INTENTS, SENSITIVITIES, INTENT_IDS, SENSITIVITY_IDS, UNKNOWN_INTENT, TAXONOMY_VERSION, isIntent, isSensitivity, securityToolsFor, taxonomyForPrompt } from './taxonomy';
|
|
22
|
+
export type { IntentDef, SensitivityDef, ToolTrigger } from './taxonomy';
|
|
23
|
+
export { classifyPage, entryPointsOf, buildPrompt, parseClassifications, validate, collect } from './ai/classify';
|
|
24
|
+
export type { ClassifyOptions, ClassifyResult, Classification, DroppedClassification, EntryPoint } from './ai/classify';
|