@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.
Files changed (84) hide show
  1. package/README.md +378 -0
  2. package/ROADMAP.md +321 -0
  3. package/dist/ai/classify.d.ts +170 -0
  4. package/dist/ai/classify.js +431 -0
  5. package/dist/ai/classify.js.map +1 -0
  6. package/dist/browser/extractPage.d.ts +41 -0
  7. package/dist/browser/extractPage.js +427 -0
  8. package/dist/browser/extractPage.js.map +1 -0
  9. package/dist/browser/openables.d.ts +19 -0
  10. package/dist/browser/openables.js +127 -0
  11. package/dist/browser/openables.js.map +1 -0
  12. package/dist/capture/page.d.ts +128 -0
  13. package/dist/capture/page.js +78 -0
  14. package/dist/capture/page.js.map +1 -0
  15. package/dist/citations.d.ts +80 -0
  16. package/dist/citations.js +197 -0
  17. package/dist/citations.js.map +1 -0
  18. package/dist/classificationView.d.ts +118 -0
  19. package/dist/classificationView.js +178 -0
  20. package/dist/classificationView.js.map +1 -0
  21. package/dist/cli/args.d.ts +3 -0
  22. package/dist/cli/args.js +38 -0
  23. package/dist/cli/args.js.map +1 -0
  24. package/dist/cli/history.d.ts +1 -0
  25. package/dist/cli/history.js +35 -0
  26. package/dist/cli/history.js.map +1 -0
  27. package/dist/cli/index.d.ts +5 -0
  28. package/dist/cli/index.js +172 -0
  29. package/dist/cli/index.js.map +1 -0
  30. package/dist/cli/login.d.ts +29 -0
  31. package/dist/cli/login.js +179 -0
  32. package/dist/cli/login.js.map +1 -0
  33. package/dist/cli/scan.d.ts +11 -0
  34. package/dist/cli/scan.js +246 -0
  35. package/dist/cli/scan.js.map +1 -0
  36. package/dist/cli-ai/index.d.ts +7 -0
  37. package/dist/cli-ai/index.js +360 -0
  38. package/dist/cli-ai/index.js.map +1 -0
  39. package/dist/config.d.ts +112 -0
  40. package/dist/config.js +178 -0
  41. package/dist/config.js.map +1 -0
  42. package/dist/diff.d.ts +12 -0
  43. package/dist/diff.js +43 -0
  44. package/dist/diff.js.map +1 -0
  45. package/dist/explore.d.ts +59 -0
  46. package/dist/explore.js +119 -0
  47. package/dist/explore.js.map +1 -0
  48. package/dist/healingLink.d.ts +30 -0
  49. package/dist/healingLink.js +127 -0
  50. package/dist/healingLink.js.map +1 -0
  51. package/dist/history.d.ts +38 -0
  52. package/dist/history.js +120 -0
  53. package/dist/history.js.map +1 -0
  54. package/dist/htmlReport.d.ts +2 -0
  55. package/dist/htmlReport.js +371 -0
  56. package/dist/htmlReport.js.map +1 -0
  57. package/dist/index.d.ts +24 -0
  58. package/dist/index.js +65 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/mcp/sandbox.d.ts +9 -0
  61. package/dist/mcp/sandbox.js +82 -0
  62. package/dist/mcp/sandbox.js.map +1 -0
  63. package/dist/mcp/server.d.ts +120 -0
  64. package/dist/mcp/server.js +315 -0
  65. package/dist/mcp/server.js.map +1 -0
  66. package/dist/network.d.ts +60 -0
  67. package/dist/network.js +99 -0
  68. package/dist/network.js.map +1 -0
  69. package/dist/overview.d.ts +40 -0
  70. package/dist/overview.js +370 -0
  71. package/dist/overview.js.map +1 -0
  72. package/dist/overviewFile.d.ts +31 -0
  73. package/dist/overviewFile.js +101 -0
  74. package/dist/overviewFile.js.map +1 -0
  75. package/dist/scan.d.ts +159 -0
  76. package/dist/scan.js +402 -0
  77. package/dist/scan.js.map +1 -0
  78. package/dist/sharedCalls.d.ts +28 -0
  79. package/dist/sharedCalls.js +61 -0
  80. package/dist/sharedCalls.js.map +1 -0
  81. package/dist/taxonomy.d.ts +77 -0
  82. package/dist/taxonomy.js +153 -0
  83. package/dist/taxonomy.js.map +1 -0
  84. package/package.json +61 -0
package/dist/scan.js ADDED
@@ -0,0 +1,402 @@
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.SHARED_CALLS_SCHEMA = exports.SURFACE_SCHEMA = void 0;
37
+ exports.loginWallPages = loginWallPages;
38
+ exports.scanApp = scanApp;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const self_healing_1 = require("@ia-qa/self-healing");
42
+ const config_1 = require("./config");
43
+ const page_1 = require("./capture/page");
44
+ const sharedCalls_1 = require("./sharedCalls");
45
+ Object.defineProperty(exports, "SHARED_CALLS_SCHEMA", { enumerable: true, get: function () { return sharedCalls_1.SHARED_CALLS_SCHEMA; } });
46
+ const diff_1 = require("./diff");
47
+ const history_1 = require("./history");
48
+ const network_1 = require("./network");
49
+ const overview_1 = require("./overview");
50
+ const classificationView_1 = require("./classificationView");
51
+ /**
52
+ * F1 orchestrator — the one verb (`scan`) both the CLI and the MCP server call.
53
+ *
54
+ * Two-phase by necessity: `crawlApp` (from `@ia-qa/self-healing`, imported not
55
+ * reimplemented — it is the safety-critical same-origin BFS crawler) discovers
56
+ * the page list with its own `Page`, then this function does a second
57
+ * `page.goto()` per confirmed URL to capture meta/headings/forms/API calls.
58
+ * Every page loads twice; the tradeoff is accepted in the plan for this slice.
59
+ */
60
+ exports.SURFACE_SCHEMA = 'qa-discovery-surface@1';
61
+ /**
62
+ * Run `fn` with `console.log` redirected to `onLine` (or silenced entirely
63
+ * when no `onLine` is given), restored in a `finally`. Guards against any
64
+ * third-party code called from here (`crawlApp`) writing straight to stdout —
65
+ * see the call site in `scanApp`.
66
+ */
67
+ async function withRedirectedConsoleLog(onLine, fn) {
68
+ const original = console.log;
69
+ console.log = onLine ? (...args) => onLine(args.map(String).join(' ')) : () => { };
70
+ try {
71
+ return await fn();
72
+ }
73
+ finally {
74
+ console.log = original;
75
+ }
76
+ }
77
+ function pageFileName(name) {
78
+ return `${name.replace(/[^a-zA-Z0-9._-]/g, '-')}.json`;
79
+ }
80
+ /**
81
+ * Pages that still show a login form.
82
+ *
83
+ * The signal is a password field — observed in the DOM, never inferred. It
84
+ * exists because of a failure this tool met on a real app: a SPA whose
85
+ * catch-all route renders the login page at EVERY unknown URL, with HTTP 200
86
+ * and no redirect. Nothing in the status code, the URL or the page count says
87
+ * anything is wrong — the scan happily reports "3 pages captured" while having
88
+ * captured the same wall three times. A page-count without this check is the
89
+ * false green this package exists to avoid.
90
+ *
91
+ * One such page is ordinary (an app has a login page). Several means the wall.
92
+ */
93
+ function loginWallPages(pages) {
94
+ // Both halves: a React login that submits in JS often has no `<form>` at all,
95
+ // so checking only `forms` would miss exactly the wall this exists to catch.
96
+ return pages
97
+ .filter((p) => p.forms.some((f) => f.fields.some((fl) => fl.type === 'password')) ||
98
+ p.looseFields.some((fl) => fl.type === 'password'))
99
+ .map((p) => p.page);
100
+ }
101
+ /** Distinct origins observed across every page's calls plus the promoted shared ones. */
102
+ function summarizeOrigins(pages, shared) {
103
+ const acc = new Map();
104
+ const add = (origin, sameOrigin, calls, pageName) => {
105
+ let entry = acc.get(origin);
106
+ if (!entry) {
107
+ entry = { origin, calls: 0, pages: new Set(), sameOrigin };
108
+ acc.set(origin, entry);
109
+ }
110
+ entry.calls += calls;
111
+ entry.pages.add(pageName);
112
+ };
113
+ for (const p of pages) {
114
+ for (const c of p.apiCalls)
115
+ add(c.origin, c.sameOrigin, c.count, p.page);
116
+ }
117
+ // A promoted call was stripped from its pages; `count` there is how many pages carried it.
118
+ for (const c of shared.calls)
119
+ add(c.origin, c.sameOrigin, c.count, `_shared:${c.urlPattern}`);
120
+ return Array.from(acc.values())
121
+ .map((e) => ({ origin: e.origin, calls: e.calls, pages: e.pages.size, sameOrigin: e.sameOrigin }))
122
+ .sort((a, b) => b.calls - a.calls || a.origin.localeCompare(b.origin));
123
+ }
124
+ function loadExistingCaptures(cwd) {
125
+ const dir = (0, config_1.captureDir)(cwd);
126
+ if (!fs.existsSync(dir))
127
+ return [];
128
+ const out = [];
129
+ for (const entry of fs.readdirSync(dir)) {
130
+ if (!entry.endsWith('.json') || entry.startsWith('_') || entry === 'surface.json')
131
+ continue;
132
+ try {
133
+ const raw = JSON.parse(fs.readFileSync(path.join(dir, entry), 'utf8'));
134
+ if (raw && typeof raw.page === 'string' && Array.isArray(raw.headings))
135
+ out.push(raw);
136
+ }
137
+ catch {
138
+ /* skip unparseable */
139
+ }
140
+ }
141
+ return out;
142
+ }
143
+ /** Overwrite capture/ entirely with this run's pages — a stale file for a page that vanished must not survive. */
144
+ /** Built whether or not anything is written, so `--report` and `write:false` see the same document. */
145
+ function buildManifest(pages, shared, meta) {
146
+ return {
147
+ schema: exports.SURFACE_SCHEMA,
148
+ baseUrl: meta.baseUrl,
149
+ capturedAt: new Date().toISOString(),
150
+ pagesFound: meta.pagesFound,
151
+ pagesCaptured: pages.length,
152
+ pagesFailed: meta.pagesFailed,
153
+ pages: pages.map((p) => ({ name: p.page, url: p.url, source: p.source, file: pageFileName(p.page) })),
154
+ sharedCallsFile: '_shared-calls.json',
155
+ origins: summarizeOrigins(pages, shared),
156
+ coverage: meta.coverage,
157
+ };
158
+ }
159
+ function saveCaptures(cwd, pages, shared, manifest, coverage) {
160
+ const dir = (0, config_1.captureDir)(cwd);
161
+ fs.mkdirSync(dir, { recursive: true });
162
+ for (const entry of fs.readdirSync(dir)) {
163
+ if (entry.endsWith('.json') || entry === overview_1.OVERVIEW_FILENAME)
164
+ fs.unlinkSync(path.join(dir, entry));
165
+ }
166
+ for (const p of pages) {
167
+ fs.writeFileSync(path.join(dir, pageFileName(p.page)), JSON.stringify(p, null, 2) + '\n', 'utf8');
168
+ }
169
+ fs.writeFileSync(path.join(dir, '_shared-calls.json'), JSON.stringify(shared, null, 2) + '\n', 'utf8');
170
+ const surfaceFile = path.join(dir, 'surface.json');
171
+ fs.writeFileSync(surfaceFile, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
172
+ // The human-readable half, regenerated on every scan from the same data the
173
+ // JSON carries — never a second source of truth.
174
+ //
175
+ // An existing classification is included, and after a scan it is stale by
176
+ // construction: these captures were written seconds ago. Showing it anyway,
177
+ // marked, is the honest option — dropping it would make a previous reading
178
+ // vanish from the one file people open, and silence here would read as "this
179
+ // app has nothing to classify".
180
+ // Resolved from the `cwd` this function was handed, never from the ambient
181
+ // baseDir(): they are the same today, and a caller passing a different root
182
+ // would otherwise read one project's classification into another's overview.
183
+ const classification = (0, classificationView_1.classificationFor)(pages, (0, classificationView_1.classificationPath)(cwd));
184
+ fs.writeFileSync(path.join(dir, overview_1.OVERVIEW_FILENAME), (0, overview_1.renderOverview)({ manifest, pages, shared, ...coverage, ...(classification ? { classification } : {}) }), 'utf8');
185
+ return surfaceFile;
186
+ }
187
+ async function scanApp(options = {}) {
188
+ const cwd = (0, config_1.baseDir)();
189
+ const hasConfig = (0, config_1.configExists)(cwd);
190
+ const existing = hasConfig ? (0, config_1.loadConfig)(cwd) : undefined;
191
+ const baseUrl = options.url ?? existing?.baseUrl;
192
+ if (!baseUrl) {
193
+ throw new Error(`No URL given and no ${config_1.CONFIG_DIR}/config.json found. Run \`ia-qa-discover scan <url> --save\` first.`);
194
+ }
195
+ const maxDepth = options.maxDepth ?? existing?.crawl?.maxDepth;
196
+ const maxPages = options.maxPages ?? existing?.crawl?.maxPages;
197
+ const reveal = options.reveal ?? existing?.crawl?.reveal;
198
+ const strictHost = options.strictHost ?? existing?.crawl?.strictHost;
199
+ const useSitemap = options.sitemap ?? existing?.crawl?.sitemap ?? true;
200
+ const deep = options.deep ?? existing?.capture?.deep ?? false;
201
+ const network = options.network ?? existing?.capture?.network ?? true;
202
+ const networkThreshold = options.networkThreshold ?? existing?.capture?.networkThreshold ?? 0.6;
203
+ const safeQueryParams = options.safeQueryParams ?? existing?.capture?.safeQueryParams ?? network_1.DEFAULT_SAFE_QUERY_PARAMS;
204
+ // Throws when a session the user NAMED cannot be read — never falls through
205
+ // to "no session", which would capture the login page under their pages' names.
206
+ const sessionChoice = (0, config_1.resolveSession)(existing, options.session, cwd);
207
+ const session = sessionChoice?.file;
208
+ const locale = options.locale ?? existing?.locale;
209
+ const write = options.write !== false;
210
+ if (options.save && write) {
211
+ (0, config_1.saveConfig)({
212
+ baseUrl,
213
+ ...(session ? { session } : {}),
214
+ ...(locale ? { locale } : {}),
215
+ crawl: { maxDepth, maxPages, reveal, strictHost, sitemap: useSitemap },
216
+ capture: { network, networkThreshold, safeQueryParams, deep },
217
+ }, cwd);
218
+ }
219
+ // Minimal shim — this package has its own DiscoveryConfig, not self-healing's
220
+ // IaQaConfig; crawlApp needs the latter's shape, so we build only what it reads.
221
+ const shimConfig = {
222
+ framework: 'playwright',
223
+ baseUrl,
224
+ secrets: {},
225
+ pages: [],
226
+ ...(locale ? { locale } : {}),
227
+ ...(session ? { session } : {}),
228
+ };
229
+ if (sessionChoice)
230
+ options.onProgress?.(`🔓 Reusing the saved session (${sessionChoice.file}).`);
231
+ // Sitemap first: one HTTP GET, no browser, and it is the source that does not
232
+ // care how the app navigates — a React app that exposes not one <a href> still
233
+ // declares its pages here. It is also the only source that can name pages the
234
+ // crawl would never reach, so its hits double as crawl seeds below.
235
+ let sitemapCandidates = [];
236
+ let sitemapStats = null;
237
+ if (useSitemap) {
238
+ try {
239
+ // Deliberately NOT capped at `maxPages`: capping the sitemap at the source
240
+ // makes "5 of 5 found" out of a site that declared 40, because the pages we
241
+ // chose not to ask for never enter the denominator. Read what the app
242
+ // declares, then let the merge cap drop the excess and SAY how many.
243
+ const res = await (0, self_healing_1.discoverFromSitemap)(baseUrl, { strictHost, maxUrls: Math.max(maxPages ?? 60, 200) });
244
+ sitemapCandidates = res.candidates;
245
+ sitemapStats = {
246
+ declared: res.stats.parsed,
247
+ retained: res.stats.retained,
248
+ skippedHost: res.stats.skippedHost,
249
+ otherHosts: res.stats.otherHosts,
250
+ tried: res.stats.tried,
251
+ };
252
+ options.onProgress?.(res.stats.parsed > 0
253
+ ? `🗺 Sitemap: ${res.stats.retained} page(s) declared${res.stats.skippedHost > 0 ? ` (${res.stats.skippedHost} on another host: ${res.stats.otherHosts.join(', ')})` : ''}.`
254
+ : `🗺 No sitemap found (tried ${res.stats.tried.join(', ') || 'the usual paths'}).`);
255
+ }
256
+ catch (err) {
257
+ // A sitemap is an optimisation, never a requirement: the crawl still runs.
258
+ options.onProgress?.(`🗺 Sitemap unreadable (${err instanceof Error ? err.message : String(err)}) — continuing with the crawl.`);
259
+ }
260
+ }
261
+ options.onProgress?.(`Discovering pages from ${baseUrl}…`);
262
+ // How many internal links the crawl actually harvested. Zero is the signal
263
+ // that discovery is structurally blind here — see `hrefBlind` below.
264
+ let linksHarvested = 0;
265
+ // `crawlApp` (self-healing) writes its own progress line straight to
266
+ // console.log/stdout ("🌐 Using bundled Chromium…"). That's fine for its own
267
+ // CLI, but here it would corrupt this package's own contracts: the MCP
268
+ // server's stdout must carry JSON-RPC only, and `scan --json` must be one
269
+ // parseable document. Route it through our own onProgress (→ stderr in
270
+ // --json mode, silent for MCP) instead of letting it hit stdout directly.
271
+ const candidates = await withRedirectedConsoleLog(options.onProgress, () => (0, self_healing_1.crawlApp)(shimConfig, {
272
+ maxDepth,
273
+ maxPages,
274
+ reveal,
275
+ strictHost,
276
+ session,
277
+ // Every sitemap hit is a place the crawl can start from, so a site whose
278
+ // nav is JS-driven still gets its pages walked for links.
279
+ seeds: sitemapCandidates.map((c) => new URL(c.url, baseUrl).toString()),
280
+ onVisit: (url, found) => {
281
+ linksHarvested += found;
282
+ options.onProgress?.(` visited ${url} (${found} links)`);
283
+ },
284
+ }));
285
+ // Declared pages are scanned IN ADDITION to what the crawl found, deduped by
286
+ // path — declaring a route must not turn discovery off, and a crawl that can
287
+ // see nothing (an href-less SPA) must not veto what the user knows exists.
288
+ const declared = (existing?.pages ?? []).map((p) => {
289
+ const abs = new URL(p.url, baseUrl).toString();
290
+ return { name: p.name || (0, self_healing_1.pageNameFromUrl)(abs, baseUrl), url: p.url, source: 'declared' };
291
+ });
292
+ // Declared first, then whatever the crawl actually reached, then the sitemap's
293
+ // declarations — first writer wins the path, so a page you named keeps your name.
294
+ const byPath = new Map();
295
+ for (const c of [...declared, ...candidates, ...sitemapCandidates]) {
296
+ const key = (0, self_healing_1.normalizedPath)(new URL(c.url, baseUrl).toString(), baseUrl);
297
+ if (!byPath.has(key))
298
+ byPath.set(key, c);
299
+ }
300
+ const merged = Array.from(byPath.values());
301
+ // The cap is a budget, not a statement about the app. Applying it silently
302
+ // would turn "we visited 60" into "the app has 60", which is the same lie as
303
+ // every other count in this package.
304
+ const cap = maxPages ?? 60;
305
+ const targets = merged.slice(0, cap);
306
+ const droppedByCap = merged.length - targets.length;
307
+ const bySource = (s) => targets.filter((t) => t.source === s).length;
308
+ const breakdown = [
309
+ bySource('declared') > 0 ? `${bySource('declared')} declared` : null,
310
+ bySource('crawl') > 0 ? `${bySource('crawl')} crawled` : null,
311
+ bySource('sitemap') > 0 ? `${bySource('sitemap')} from the sitemap` : null,
312
+ ].filter(Boolean);
313
+ options.onProgress?.(`Found ${targets.length} page(s)` +
314
+ (breakdown.length > 0 ? ` (${breakdown.join(', ')})` : '') +
315
+ (droppedByCap > 0 ? ` — ${droppedByCap} more known but over the --max ${cap} cap` : '') +
316
+ `. Capturing…`);
317
+ const previous = loadExistingCaptures(cwd);
318
+ const { browser } = await (0, self_healing_1.launchBrowser)();
319
+ const captured = [];
320
+ const failed = [];
321
+ try {
322
+ const context = await browser.newContext({
323
+ ...(locale ? { locale } : {}),
324
+ ...(session ? { storageState: session } : {}),
325
+ });
326
+ const page = await context.newPage();
327
+ for (const candidate of targets) {
328
+ try {
329
+ const capture = await (0, page_1.capturePage)(page, candidate, baseUrl, { network, safeQueryParams, deep, deepBudget: options.deepBudget });
330
+ captured.push(capture);
331
+ options.onProgress?.(` ✔ ${candidate.name}`);
332
+ }
333
+ catch (err) {
334
+ const message = err instanceof Error ? err.message : String(err);
335
+ failed.push({ name: candidate.name, url: candidate.url, error: message });
336
+ options.onProgress?.(` ✘ ${candidate.name}: ${message}`);
337
+ }
338
+ }
339
+ }
340
+ finally {
341
+ await browser.close();
342
+ }
343
+ const { shared, pages } = (0, sharedCalls_1.promoteSharedCalls)(captured, networkThreshold);
344
+ const diffResult = (0, diff_1.compareCaptures)(previous, pages);
345
+ const loginWall = loginWallPages(pages);
346
+ const hrefBlind = linksHarvested === 0 && pages.length <= 1;
347
+ // One object, used both to render this run's overview and to let a later
348
+ // consumer rebuild it — so the two can never state different coverage.
349
+ const coverage = {
350
+ loginWall,
351
+ hrefBlind,
352
+ droppedByCap,
353
+ sitemapDeclared: sitemapStats?.retained ?? null,
354
+ ...(session ? { session } : {}),
355
+ };
356
+ const manifest = buildManifest(pages, shared, {
357
+ baseUrl,
358
+ pagesFound: targets.length,
359
+ pagesFailed: failed.length,
360
+ coverage,
361
+ });
362
+ const surfacePath = write ? saveCaptures(cwd, pages, shared, manifest, coverage) : '';
363
+ const entry = {
364
+ verb: 'scan',
365
+ baseUrl,
366
+ pagesFound: targets.length,
367
+ pagesCaptured: pages.length,
368
+ pagesFailed: failed.length,
369
+ added: diffResult.added,
370
+ removed: diffResult.removed,
371
+ changed: diffResult.changed,
372
+ formsTotal: pages.reduce((n, p) => n + p.forms.length, 0),
373
+ fieldsTotal: pages.reduce((n, p) => n + p.forms.reduce((m, f) => m + f.fields.length, 0) + p.looseFields.length, 0),
374
+ apiCallsTotal: pages.reduce((n, p) => n + p.apiCalls.length, 0) + shared.calls.length,
375
+ ...(0, history_1.gitStamp)(),
376
+ };
377
+ if (write)
378
+ (0, history_1.appendHistory)(entry);
379
+ return {
380
+ baseUrl,
381
+ pagesFound: targets.length,
382
+ pagesCaptured: pages.length,
383
+ failed,
384
+ diff: diffResult,
385
+ history: { ts: new Date().toISOString(), ...entry },
386
+ surfacePath,
387
+ pages,
388
+ sharedCalls: shared,
389
+ manifest,
390
+ ...(session ? { session } : {}),
391
+ loginWall,
392
+ hrefBlind,
393
+ discovery: {
394
+ sitemap: sitemapStats,
395
+ crawled: candidates.length,
396
+ declared: declared.length,
397
+ merged: merged.length,
398
+ droppedByCap,
399
+ },
400
+ };
401
+ }
402
+ //# sourceMappingURL=scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { type ApiCall } from './network';
2
+ import type { PageCapture } from './capture/page';
3
+ /**
4
+ * Shared vs. page-specific API calls — the network-call analogue of
5
+ * `@ia-qa/self-healing`'s `layout.ts` (fingerprint + threshold promotion).
6
+ * `minPagesForLayout` is imported directly from that package rather than
7
+ * reimplemented: the "floor of 2" reasoning (a low threshold on few pages must
8
+ * not let a unique call clear the ratio on its own) applies identically here.
9
+ */
10
+ export declare const SHARED_CALLS_SCHEMA = "qa-discovery-shared-calls@1";
11
+ export interface SharedCallsFile {
12
+ schema: typeof SHARED_CALLS_SCHEMA;
13
+ capturedAt: string;
14
+ sourcePages: number;
15
+ threshold: number;
16
+ calls: ApiCall[];
17
+ }
18
+ export interface PromoteResult {
19
+ shared: SharedCallsFile;
20
+ /** Same pages, with any promoted call fingerprint stripped from `apiCalls`. */
21
+ pages: PageCapture[];
22
+ }
23
+ /**
24
+ * Promote any API-call fingerprint appearing on `>= minPagesForLayout(pages.length, threshold)`
25
+ * pages into a single shared-calls file, and strip it from each page's own `apiCalls`.
26
+ * Does not mutate the input pages.
27
+ */
28
+ export declare function promoteSharedCalls(pages: PageCapture[], threshold: number): PromoteResult;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SHARED_CALLS_SCHEMA = void 0;
4
+ exports.promoteSharedCalls = promoteSharedCalls;
5
+ const self_healing_1 = require("@ia-qa/self-healing");
6
+ const network_1 = require("./network");
7
+ /**
8
+ * Shared vs. page-specific API calls — the network-call analogue of
9
+ * `@ia-qa/self-healing`'s `layout.ts` (fingerprint + threshold promotion).
10
+ * `minPagesForLayout` is imported directly from that package rather than
11
+ * reimplemented: the "floor of 2" reasoning (a low threshold on few pages must
12
+ * not let a unique call clear the ratio on its own) applies identically here.
13
+ */
14
+ exports.SHARED_CALLS_SCHEMA = 'qa-discovery-shared-calls@1';
15
+ /**
16
+ * Promote any API-call fingerprint appearing on `>= minPagesForLayout(pages.length, threshold)`
17
+ * pages into a single shared-calls file, and strip it from each page's own `apiCalls`.
18
+ * Does not mutate the input pages.
19
+ */
20
+ function promoteSharedCalls(pages, threshold) {
21
+ const capturedAt = new Date().toISOString();
22
+ if (pages.length === 0) {
23
+ return {
24
+ shared: { schema: exports.SHARED_CALLS_SCHEMA, capturedAt, sourcePages: 0, threshold, calls: [] },
25
+ pages: [],
26
+ };
27
+ }
28
+ const counts = new Map();
29
+ for (const p of pages) {
30
+ for (const call of p.apiCalls) {
31
+ const fp = (0, network_1.apiCallFingerprint)(call);
32
+ let entry = counts.get(fp);
33
+ if (!entry) {
34
+ entry = { call, pages: new Set() };
35
+ counts.set(fp, entry);
36
+ }
37
+ entry.pages.add(p.page);
38
+ }
39
+ }
40
+ const minPages = (0, self_healing_1.minPagesForLayout)(pages.length, threshold);
41
+ const sharedFps = new Set();
42
+ const calls = [];
43
+ for (const [fp, entry] of counts) {
44
+ if (entry.pages.size < minPages)
45
+ continue;
46
+ sharedFps.add(fp);
47
+ // `count` here means "pages carrying this call", not per-page occurrences —
48
+ // the meaningful number once a call has been promoted out of any one page.
49
+ calls.push({ ...entry.call, count: entry.pages.size });
50
+ }
51
+ calls.sort((a, b) => a.urlPattern.localeCompare(b.urlPattern) || a.method.localeCompare(b.method));
52
+ const strippedPages = pages.map((p) => ({
53
+ ...p,
54
+ apiCalls: p.apiCalls.filter((c) => !sharedFps.has((0, network_1.apiCallFingerprint)(c))),
55
+ }));
56
+ return {
57
+ shared: { schema: exports.SHARED_CALLS_SCHEMA, capturedAt, sourcePages: pages.length, threshold, calls },
58
+ pages: strippedPages,
59
+ };
60
+ }
61
+ //# sourceMappingURL=sharedCalls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sharedCalls.js","sourceRoot":"","sources":["../src/sharedCalls.ts"],"names":[],"mappings":";;;AAiCA,gDA2CC;AA5ED,sDAAwD;AACxD,uCAA6D;AAG7D;;;;;;GAMG;AAEU,QAAA,mBAAmB,GAAG,6BAA6B,CAAC;AAgBjE;;;;GAIG;AACH,SAAgB,kBAAkB,CAAC,KAAoB,EAAE,SAAiB;IACxE,MAAM,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO;YACL,MAAM,EAAE,EAAE,MAAM,EAAE,2BAAmB,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE;YACzF,KAAK,EAAE,EAAE;SACV,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAAiD,CAAC;IACxE,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;YAC9B,MAAM,EAAE,GAAG,IAAA,4BAAkB,EAAC,IAAI,CAAC,CAAC;YACpC,IAAI,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC3B,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,KAAK,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;gBACnC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YACxB,CAAC;YACD,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,IAAA,gCAAiB,EAAC,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC5D,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,MAAM,KAAK,GAAc,EAAE,CAAC;IAC5B,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;QACjC,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,QAAQ;YAAE,SAAS;QAC1C,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClB,4EAA4E;QAC5E,2EAA2E;QAC3E,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACzD,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAEnG,MAAM,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtC,GAAG,CAAC;QACJ,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,IAAA,4BAAkB,EAAC,CAAC,CAAC,CAAC,CAAC;KAC1E,CAAC,CAAC,CAAC;IAEJ,OAAO;QACL,MAAM,EAAE,EAAE,MAAM,EAAE,2BAAmB,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE;QAChG,KAAK,EAAE,aAAa;KACrB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The closed, versioned referentials F2 classifies against.
3
+ *
4
+ * Closed on purpose: a model that may invent a label produces a vocabulary that
5
+ * drifts between runs, and two scans of one app stop being comparable — which
6
+ * kills the diff this package is built around. The model picks from these lists
7
+ * or refuses; there is no third option.
8
+ *
9
+ * THE ADMISSION RULE, and it is what keeps these lists from bloating into
10
+ * uselessness:
11
+ *
12
+ * > An intent earns its place only if it changes **what you would test**.
13
+ *
14
+ * Two labels that lead to the same test plan are one label. This mirrors the
15
+ * rule `@ia-qa/self-healing`'s `check` uses to refuse new checks ("it must be
16
+ * able to fail for a reason the diff would not already catch") — without such a
17
+ * rule every plausible noun becomes a category and the classification stops
18
+ * discriminating anything.
19
+ *
20
+ * TWO AXES, NOT ONE. The original spec asks for "what intention, what data
21
+ * sensitivity" — those are different questions and collapsing them makes both
22
+ * wrong. `admin` is not an intent, it is an access dimension: an admin page can
23
+ * be data entry, configuration or navigation. Sensitivity is likewise
24
+ * orthogonal — a login form and a payment form are both high-sensitivity and
25
+ * need completely different tests.
26
+ */
27
+ export declare const TAXONOMY_VERSION = "qa-discovery-taxonomy@1";
28
+ export interface IntentDef {
29
+ id: string;
30
+ /** One line, written for the model's prompt as much as for a human. */
31
+ means: string;
32
+ /** Why it is its own category — the admission rule, made explicit per entry. */
33
+ testsDifferently: string;
34
+ }
35
+ /**
36
+ * What an entry point is FOR.
37
+ *
38
+ * `content` looks like the least useful entry and is one of the most: it is what
39
+ * lets F4 *deprioritise*. A terms page and a checkout page both being "a page"
40
+ * is how a test plan ends up spending its budget on the wrong half of an app.
41
+ */
42
+ export declare const INTENTS: IntentDef[];
43
+ export interface SensitivityDef {
44
+ id: string;
45
+ means: string;
46
+ /** What this level changes about the risk, not about the tests. */
47
+ raises: string;
48
+ }
49
+ /** What an entry point HANDLES. Orthogonal to intent, and it drives risk, not test shape. */
50
+ export declare const SENSITIVITIES: SensitivityDef[];
51
+ export declare const INTENT_IDS: string[];
52
+ export declare const SENSITIVITY_IDS: string[];
53
+ /**
54
+ * The refusal. Guardrail 5 made concrete: a model asked to classify will always
55
+ * classify, so "I cannot tell from this evidence" has to be a value it can
56
+ * return, not a state it has to invent a label to escape.
57
+ */
58
+ export declare const UNKNOWN_INTENT = "unknown";
59
+ export declare function isIntent(value: unknown): boolean;
60
+ export declare function isSensitivity(value: unknown): boolean;
61
+ /**
62
+ * Security tools to run when a classification lands, keyed on the two axes.
63
+ *
64
+ * This is the deterministic payoff of F2 and the reason it is worth building
65
+ * first: rather than asking a model to imagine security scenarios in F3, a
66
+ * classification *selects* checks that already exist and already work. Every
67
+ * tool named here was confirmed present on the ia-qa.com MCP server — a mapping
68
+ * that points at a tool which does not exist costs more than no mapping at all,
69
+ * because the caller cannot tell a broken server from a bad plan.
70
+ */
71
+ export interface ToolTrigger {
72
+ tool: string;
73
+ why: string;
74
+ }
75
+ export declare function securityToolsFor(intent: string, sensitivity: string): ToolTrigger[];
76
+ /** Everything a prompt needs to state the closed vocabulary, without duplicating it. */
77
+ export declare function taxonomyForPrompt(): string;