@ia-qa/qa-discovery 0.3.0 → 0.5.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 (52) hide show
  1. package/README.md +109 -3
  2. package/ROADMAP.md +37 -4
  3. package/TUTORIAL.md +85 -2
  4. package/dist/ai/classify.d.ts +13 -36
  5. package/dist/ai/classify.js +23 -166
  6. package/dist/ai/classify.js.map +1 -1
  7. package/dist/ai/model.d.ts +93 -0
  8. package/dist/ai/model.js +192 -0
  9. package/dist/ai/model.js.map +1 -0
  10. package/dist/ai/plan.d.ts +224 -0
  11. package/dist/ai/plan.js +0 -0
  12. package/dist/ai/plan.js.map +1 -0
  13. package/dist/cli/args.js +2 -0
  14. package/dist/cli/args.js.map +1 -1
  15. package/dist/cli/generate.d.ts +56 -0
  16. package/dist/cli/generate.js +293 -0
  17. package/dist/cli/generate.js.map +1 -0
  18. package/dist/cli/index.d.ts +1 -1
  19. package/dist/cli/index.js +32 -0
  20. package/dist/cli/index.js.map +1 -1
  21. package/dist/cli/skill.d.ts +20 -0
  22. package/dist/cli/skill.js +63 -0
  23. package/dist/cli/skill.js.map +1 -0
  24. package/dist/cli-ai/index.d.ts +3 -2
  25. package/dist/cli-ai/index.js +268 -4
  26. package/dist/cli-ai/index.js.map +1 -1
  27. package/dist/coverage.d.ts +52 -2
  28. package/dist/coverage.js +67 -7
  29. package/dist/coverage.js.map +1 -1
  30. package/dist/coverageView.js +17 -1
  31. package/dist/coverageView.js.map +1 -1
  32. package/dist/generate.d.ts +62 -0
  33. package/dist/generate.js +163 -0
  34. package/dist/generate.js.map +1 -0
  35. package/dist/htmlReport.js +20 -4
  36. package/dist/htmlReport.js.map +1 -1
  37. package/dist/index.d.ts +6 -0
  38. package/dist/index.js +32 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/mcp/server.d.ts +49 -0
  41. package/dist/mcp/server.js +143 -20
  42. package/dist/mcp/server.js.map +1 -1
  43. package/dist/overview.d.ts +9 -0
  44. package/dist/overview.js +9 -4
  45. package/dist/overview.js.map +1 -1
  46. package/dist/overviewFile.js +5 -0
  47. package/dist/overviewFile.js.map +1 -1
  48. package/dist/planView.d.ts +141 -0
  49. package/dist/planView.js +345 -0
  50. package/dist/planView.js.map +1 -0
  51. package/package.json +3 -2
  52. package/skills/ia-qa-discover/SKILL.md +166 -0
@@ -0,0 +1,345 @@
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.PLAN_STALE_CAVEAT = exports.PLAN_CAVEAT = void 0;
37
+ exports.planPath = planPath;
38
+ exports.loadPlan = loadPlan;
39
+ exports.buildPlanView = buildPlanView;
40
+ exports.planHeadline = planHeadline;
41
+ exports.scopeLine = scopeLine;
42
+ exports.outsideScopeLine = outsideScopeLine;
43
+ exports.renderPlanTerminal = renderPlanTerminal;
44
+ exports.renderPlanMarkdown = renderPlanMarkdown;
45
+ exports.renderPlanHtml = renderPlanHtml;
46
+ exports.planFor = planFor;
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
49
+ const config_1 = require("./config");
50
+ const plan_1 = require("./ai/plan");
51
+ function planPath(cwd = (0, config_1.baseDir)()) {
52
+ return path.join(cwd, config_1.CONFIG_DIR, plan_1.PLAN_FILENAME);
53
+ }
54
+ /**
55
+ * Read the plan, or null when there is none.
56
+ *
57
+ * A file whose `schema` is not the one this build understands is treated as absent rather
58
+ * than parsed optimistically: a report rendering fields it guessed at is worse than a report
59
+ * with no plan section.
60
+ */
61
+ function loadPlan(file = planPath()) {
62
+ if (!fs.existsSync(file))
63
+ return null;
64
+ try {
65
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
66
+ if (parsed?.schema !== plan_1.PLAN_SCHEMA || typeof parsed.pages !== 'object' || !parsed.pages)
67
+ return null;
68
+ return parsed;
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ /** True when the page was captured after it was read. */
75
+ function isStale(page, readAt) {
76
+ if (!page?.capturedAt)
77
+ return false;
78
+ const captured = Date.parse(page.capturedAt);
79
+ const read = Date.parse(readAt);
80
+ if (Number.isNaN(captured) || Number.isNaN(read))
81
+ return false;
82
+ return captured > read;
83
+ }
84
+ /**
85
+ * Pair a plan document with the capture on disk now.
86
+ *
87
+ * `order` is the coverage map's ranking when there is one. Pages it does not name keep their
88
+ * alphabetical place after it — a page the model read and the ranking never mentioned is
89
+ * still shown, never dropped to make the list tidy.
90
+ */
91
+ function buildPlanView(doc, pages, order = []) {
92
+ const byName = new Map(pages.map((p) => [p.page, p]));
93
+ const rank = new Map(order.map((name, i) => [name, i]));
94
+ const out = [];
95
+ for (const [name, outcome] of Object.entries(doc.pages)) {
96
+ if (!byName.has(name))
97
+ continue; // a reading of a page that is no longer captured
98
+ const row = { page: name, stale: isStale(byName.get(name), doc.readAt) };
99
+ if (outcome?.status === 'read' || outcome?.status === 'unclear')
100
+ row.reading = outcome.reading;
101
+ else if (outcome?.status === 'dropped')
102
+ row.dropped = { reason: outcome.dropped.reason };
103
+ else if (outcome?.status === 'failed')
104
+ row.failed = outcome.reason;
105
+ out.push(row);
106
+ }
107
+ out.sort((a, b) => {
108
+ const ra = rank.has(a.page) ? rank.get(a.page) : Number.MAX_SAFE_INTEGER;
109
+ const rb = rank.has(b.page) ? rank.get(b.page) : Number.MAX_SAFE_INTEGER;
110
+ return ra - rb || a.page.localeCompare(b.page);
111
+ });
112
+ const candidates = pages.filter(plan_1.isReadable);
113
+ const seen = new Set(out.filter((p) => !p.failed).map((p) => p.page));
114
+ // A document written before `targeted` existed can only say what it holds.
115
+ const targeted = new Set(doc.targeted ?? Object.keys(doc.pages));
116
+ const notRead = candidates.filter((p) => targeted.has(p.page) && !seen.has(p.page)).map((p) => p.page).sort();
117
+ const outsideScope = candidates.filter((p) => !targeted.has(p.page)).map((p) => p.page).sort();
118
+ return {
119
+ model: doc.model,
120
+ readAt: doc.readAt,
121
+ scope: doc.scope,
122
+ pages: out,
123
+ notRead,
124
+ outsideScope,
125
+ nothingToRead: pages.length - candidates.length,
126
+ staleCount: out.filter((p) => p.stale).length,
127
+ readTotal: out.filter((p) => p.reading && !p.reading.unclear).length,
128
+ unclearTotal: out.filter((p) => p.reading?.unclear).length,
129
+ droppedTotal: out.filter((p) => p.dropped).length,
130
+ failedTotal: out.filter((p) => p.failed).length,
131
+ };
132
+ }
133
+ /**
134
+ * The one-line summary every renderer puts at the top.
135
+ *
136
+ * It leads with what was read and immediately qualifies it: a bare "12 pages read" is the
137
+ * confident number this package spends its design refusing to print.
138
+ */
139
+ function planHeadline(v) {
140
+ const bits = [`${v.readTotal} page${v.readTotal === 1 ? '' : 's'} read`];
141
+ if (v.unclearTotal > 0)
142
+ bits.push(`${v.unclearTotal} unclear`);
143
+ if (v.droppedTotal > 0)
144
+ bits.push(`${v.droppedTotal} dropped`);
145
+ if (v.failedTotal > 0)
146
+ bits.push(`${v.failedTotal} failed`);
147
+ if (v.notRead.length > 0)
148
+ bits.push(`${v.notRead.length} not read`);
149
+ if (v.staleCount > 0)
150
+ bits.push(`${v.staleCount} stale`);
151
+ return bits.join(' · ');
152
+ }
153
+ /**
154
+ * The caveat, deliberately narrow.
155
+ *
156
+ * The tool already drops a reading whose citation does not resolve, so telling the reader to
157
+ * re-check everything would be alarm fatigue — a warning that fires on the good rows is one
158
+ * people learn to skip. It names the residual risk instead, and the one thing a reader might
159
+ * otherwise assume: that the model had a say in the order.
160
+ */
161
+ exports.PLAN_CAVEAT = 'Each line below is a MODEL reading of the capture, not an observation of your app. Every one ' +
162
+ 'cites something this tool resolved in the capture and checked; a reading whose citation did not ' +
163
+ 'exist was dropped before you saw it. That proves the premise, never the conclusion — a model can ' +
164
+ 'cite a real password field and still be wrong about what the page is for. Nothing here ranks ' +
165
+ 'anything: the order is the one `ia-qa-discover coverage` measured, and this gates nothing.';
166
+ exports.PLAN_STALE_CAVEAT = 'These pages were re-captured after they were read, so the sentence describes a version of the ' +
167
+ 'page that is no longer on disk. Re-run `ia-qa-discover-ai plan` before relying on them.';
168
+ /** What the scope means, stated wherever the plan is shown. */
169
+ function scopeLine(v) {
170
+ return v.scope === 'uncovered'
171
+ ? 'Read: the pages `ia-qa-discover coverage` found that no test visits.'
172
+ : 'Read: every page in the capture — no tracked suite here, so there is no untested list to narrow to.';
173
+ }
174
+ /**
175
+ * What was left out, and why — never as a shortfall.
176
+ *
177
+ * Under `uncovered` the answer is good news (a test visits them), so wording it like the
178
+ * `notRead` list would turn a covered app into a report of a half-read one.
179
+ */
180
+ function outsideScopeLine(v) {
181
+ if (v.outsideScope.length === 0)
182
+ return null;
183
+ const names = v.outsideScope.join(', ');
184
+ return v.scope === 'uncovered'
185
+ ? `${v.outsideScope.length} page(s) your suite already visits were not read: ${names}. Add --all to read them too.`
186
+ : `${v.outsideScope.length} page(s) were not part of this run: ${names}.`;
187
+ }
188
+ function readingLine(p) {
189
+ if (p.failed)
190
+ return `✘ not read — ${p.failed}`;
191
+ if (p.dropped)
192
+ return `✘ reading dropped — ${p.dropped.reason}`;
193
+ if (!p.reading)
194
+ return '—';
195
+ const conf = ` (confidence ${p.reading.confidence.toFixed(2)})`;
196
+ if (p.reading.unclear)
197
+ return `🤔 unclear — ${p.reading.purpose}${conf}`;
198
+ return `${p.reading.purpose}${p.reading.impact ? ` · If it breaks: ${p.reading.impact}` : ''}${conf}`;
199
+ }
200
+ function renderPlanTerminal(v) {
201
+ const lines = [`\n🤖 ${planHeadline(v)} · ${v.model}`, ` ${scopeLine(v)}`];
202
+ if (v.pages.length === 0) {
203
+ lines.push(`\n Nothing was read.`);
204
+ }
205
+ else {
206
+ lines.push('');
207
+ for (const p of v.pages) {
208
+ lines.push(` · ${p.page}${p.stale ? ' ⏳ stale' : ''}`);
209
+ lines.push(` ${readingLine(p)}`);
210
+ }
211
+ }
212
+ if (v.notRead.length > 0) {
213
+ lines.push(`\n Not read (${v.notRead.length}): ${v.notRead.join(', ')}`);
214
+ }
215
+ const outside = outsideScopeLine(v);
216
+ if (outside)
217
+ lines.push(`\n ${outside}`);
218
+ if (v.nothingToRead > 0) {
219
+ lines.push(` ${v.nothingToRead} page(s) had nothing to cite — no title, no heading, no field. ` +
220
+ `Never sent, and not a gap.`);
221
+ }
222
+ if (v.staleCount > 0)
223
+ lines.push(`\n ⏳ ${exports.PLAN_STALE_CAVEAT}`);
224
+ lines.push(`\n ${exports.PLAN_CAVEAT}`);
225
+ return lines.join('\n');
226
+ }
227
+ function renderPlanMarkdown(v) {
228
+ const out = ['## What a model reads these pages as', '', `${planHeadline(v)} — \`${v.model}\`.`, '', scopeLine(v), ''];
229
+ if (v.pages.length > 0) {
230
+ out.push('| Page | A model\'s reading | Confidence |', '| --- | --- | --- |');
231
+ for (const p of v.pages) {
232
+ const conf = p.reading ? p.reading.confidence.toFixed(2) : '—';
233
+ const text = p.failed
234
+ ? `not read — ${p.failed}`
235
+ : p.dropped
236
+ ? `reading dropped — ${p.dropped.reason}`
237
+ : p.reading
238
+ ? `${p.reading.unclear ? '**unclear** — ' : ''}${p.reading.purpose}${p.reading.impact ? ` **If it breaks:** ${p.reading.impact}` : ''}`
239
+ : '—';
240
+ out.push(`| \`${p.page}\`${p.stale ? ' ⏳' : ''} | ${text.replace(/\|/g, '\\|')} | ${conf} |`);
241
+ }
242
+ out.push('');
243
+ }
244
+ if (v.notRead.length > 0)
245
+ out.push(`Not read: ${v.notRead.map((p) => `\`${p}\``).join(', ')}`, '');
246
+ const outsideMd = outsideScopeLine(v);
247
+ if (outsideMd)
248
+ out.push(outsideMd, '');
249
+ if (v.nothingToRead > 0) {
250
+ out.push(`${v.nothingToRead} page(s) had nothing to cite and were never sent — not a gap.`, '');
251
+ }
252
+ if (v.staleCount > 0)
253
+ out.push(`⏳ ${exports.PLAN_STALE_CAVEAT}`, '');
254
+ out.push(exports.PLAN_CAVEAT, '');
255
+ return out.join('\n');
256
+ }
257
+ const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
258
+ /**
259
+ * One counter tile, in the report's own language.
260
+ *
261
+ * Counts only — the rule `coverageView` states and this section inherits: no percentage
262
+ * anywhere, because the denominator here is what somebody chose to read, not something a
263
+ * compiler guarantees.
264
+ */
265
+ function stat(n, label) {
266
+ return `<div class="stat"><div class="n">${n}</div><div class="l">${esc(label)}</div></div>`;
267
+ }
268
+ function renderPlanHtml(v) {
269
+ const rows = v.pages
270
+ .map((p) => {
271
+ // The state of a row is a pill, not a prefix buried in prose: it is the first thing
272
+ // worth seeing, and `unclear` in particular must not read like an ordinary answer.
273
+ const pills = [
274
+ p.failed ? '<span class="pill fail">not read</span>' : '',
275
+ p.dropped ? '<span class="pill fail">dropped</span>' : '',
276
+ p.reading?.unclear ? '<span class="pill warn">unclear</span>' : '',
277
+ p.stale ? '<span class="pill warn">stale</span>' : '',
278
+ ]
279
+ .filter(Boolean)
280
+ .join(' ');
281
+ // Each branch escapes its own strings and returns finished markup. An earlier shape
282
+ // built the text first and escaped it afterwards "unless it already was" — one
283
+ // condition away from putting a model's sentence into the page as HTML.
284
+ const body = p.failed
285
+ ? esc(p.failed)
286
+ : p.dropped
287
+ ? esc(p.dropped.reason)
288
+ : p.reading
289
+ ? esc(p.reading.purpose) +
290
+ (p.reading.impact ? `<div class="note" style="margin-top:6px">If it breaks: ${esc(p.reading.impact)}</div>` : '')
291
+ : '—';
292
+ // Deliberately NOT coloured by band. A green 0.90 would read as "this page is fine",
293
+ // which is a verdict — and this section issues none.
294
+ const conf = p.reading ? p.reading.confidence.toFixed(2) : '—';
295
+ return (`<tr><td><code>${esc(p.page)}</code>${pills ? `<div style="margin-top:5px">${pills}</div>` : ''}</td>` +
296
+ `<td>${body}</td><td class="muted">${esc(conf)}</td></tr>`);
297
+ })
298
+ .join('\n');
299
+ const tiles = [
300
+ stat(v.readTotal, 'read'),
301
+ v.unclearTotal > 0 ? stat(v.unclearTotal, 'unclear') : '',
302
+ v.droppedTotal > 0 ? stat(v.droppedTotal, 'dropped') : '',
303
+ v.failedTotal > 0 ? stat(v.failedTotal, 'failed') : '',
304
+ v.notRead.length > 0 ? stat(v.notRead.length, 'not read') : '',
305
+ v.staleCount > 0 ? stat(v.staleCount, 'stale') : '',
306
+ ]
307
+ .filter(Boolean)
308
+ .join('');
309
+ return [
310
+ '<section class="plan">',
311
+ // The same label its sibling carries. Everything above this line in the report is a
312
+ // literal observation; the pill is what says this one is not.
313
+ '<h2>What a model reads these pages as <span class="pill info">model-read</span></h2>',
314
+ `<p class="headline">${esc(scopeLine(v))}</p>`,
315
+ `<div class="stats">${tiles}</div>`,
316
+ `<p class="note">Read by <code>${esc(v.model)}</code> · ${esc(v.readAt)}</p>`,
317
+ v.pages.length > 0
318
+ ? `<div class="scroll"><table><thead><tr><th>Page</th><th>What someone comes here to do</th><th>Confidence</th></tr></thead><tbody>\n${rows}\n</tbody></table></div>`
319
+ : '<p class="note">Nothing was read.</p>',
320
+ v.notRead.length > 0
321
+ ? `<p class="note">Not read: ${v.notRead.map((p) => `<code>${esc(p)}</code>`).join(', ')}</p>`
322
+ : '',
323
+ outsideScopeLine(v) ? `<p class="note">${esc(outsideScopeLine(v))}</p>` : '',
324
+ v.nothingToRead > 0
325
+ ? `<p class="note">${v.nothingToRead} page(s) had nothing to cite and were never sent — not a gap.</p>`
326
+ : '',
327
+ v.staleCount > 0 ? `<div class="callout"><span class="cl-label">stale</span>${esc(exports.PLAN_STALE_CAVEAT)}</div>` : '',
328
+ // A callout, not a footnote. It is the sentence that keeps this section from being read
329
+ // as a measurement, so it gets the weight the report gives a warning.
330
+ `<div class="callout" style="margin-top:14px"><span class="cl-label">model reading</span>${esc(exports.PLAN_CAVEAT)}</div>`,
331
+ '</section>',
332
+ ]
333
+ .filter(Boolean)
334
+ .join('\n');
335
+ }
336
+ /**
337
+ * The whole read-and-pair step, for a caller that has pages and wants the section. Returns
338
+ * null when there is no plan — the normal case, and every renderer must be complete without
339
+ * one.
340
+ */
341
+ function planFor(pages, order = [], file = planPath()) {
342
+ const doc = loadPlan(file);
343
+ return doc ? buildPlanView(doc, pages, order) : null;
344
+ }
345
+ //# sourceMappingURL=planView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planView.js","sourceRoot":"","sources":["../src/planView.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ia-qa/qa-discovery",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Deterministic surface reconnaissance for an unknown web app — crawl, capture, diff. No LLM, no hosting: runs entirely on your machine, with a local MCP server for agents.",
5
5
  "keywords": [
6
6
  "qa",
@@ -35,6 +35,7 @@
35
35
  },
36
36
  "files": [
37
37
  "dist",
38
+ "skills",
38
39
  "README.md",
39
40
  "TUTORIAL.md",
40
41
  "ROADMAP.md"
@@ -49,7 +50,7 @@
49
50
  "node": ">=18.0.0"
50
51
  },
51
52
  "dependencies": {
52
- "@ia-qa/self-healing": "^1.8.0"
53
+ "@ia-qa/self-healing": "^1.19.0"
53
54
  },
54
55
  "peerDependencies": {
55
56
  "playwright": ">=1.40.0"
@@ -0,0 +1,166 @@
1
+ ---
2
+ name: ia-qa-discover
3
+ description: >-
4
+ Find out what a web application contains when no test suite exists yet — its pages, forms,
5
+ fields and the API calls they make — and state what an existing suite does NOT test. Use
6
+ when asked to explore an unknown app, to plan testing for one, to list its entry points, or
7
+ to answer "which pages are untested". Requires @ia-qa/qa-discovery (`ia-qa-discover`). Runs
8
+ entirely on the user's machine; the optional `-ai` binary is the only part that sends
9
+ anything anywhere.
10
+ ---
11
+
12
+ # ia-qa-discover — read the capture, never re-crawl
13
+
14
+ ## 1. The capture is on disk. Read it before opening anything.
15
+
16
+ `ia-qa-discover scan` writes `.ia-qa-discovery/capture/<page>.json`, one file per page:
17
+
18
+ ```json
19
+ {
20
+ "page": "checkout", "url": "https://app/checkout", "capturedAt": "2026-09-19T…",
21
+ "meta": { "title": "Checkout", "description": "…" },
22
+ "headings": [{ "level": 1, "text": "Your order" }],
23
+ "forms": [{ "method": "post", "hasSubmit": true,
24
+ "fields": [{ "name": "card", "type": "text", "label": "Card number",
25
+ "required": true, "stableSelector": true }] }],
26
+ "looseFields": [], "apiCalls": [{ "method": "POST", "urlPattern": "/api/pay", "status": 200 }]
27
+ }
28
+ ```
29
+
30
+ Most questions are answered by reading those files and `capture/_overview.md`. Re-running a
31
+ scan costs minutes and a browser; reading costs nothing. Scan again only when the app has
32
+ changed since `capturedAt`.
33
+
34
+ | Path | What it is |
35
+ |---|---|
36
+ | `.ia-qa-discovery/capture/` | one JSON per page + `_overview.md` (the human index) |
37
+ | `.ia-qa-discovery/capture/surface.json` | the manifest, including **what the scan could not see** |
38
+ | `.ia-qa-discovery/coverage-map.json` | the gap, once `coverage` has run |
39
+ | `.ia-qa-discovery/classification.json`, `plan.json` | the optional model readings, if any |
40
+ | `.ia-qa-discovery/history.jsonl` | one line per scan |
41
+
42
+ ## 2. Which verb answers which question
43
+
44
+ | The user's question | Verb |
45
+ |---|---|
46
+ | "what is even in this app?" | `scan <url> --save` |
47
+ | "most of the inputs are behind a menu or a tab" | `scan --deep` — opens disclosures one level and captures what they reveal |
48
+ | "the app is behind a login" | the human runs `ia-qa-heal login` (from `@ia-qa/self-healing`), then `scan --session .ia-qa/session.json`. **You cannot log in** (§4.1) |
49
+ | **"what does my test suite not test?"** | `coverage` — needs `@ia-qa/self-healing` **and** one watched run (§3) |
50
+ | "what changed since last time?" | `history` |
51
+ | "what is each entry point FOR?" | `ia-qa-discover-ai classify` — optional, BYOK (§5) |
52
+ | "what does somebody come to this page to DO?" | `ia-qa-discover-ai plan` — optional, BYOK (§5) |
53
+ | "write me a starting spec for an untested page" | `generate` — scaffolding only; the assertions stay human |
54
+
55
+ Exact flags: `ia-qa-discover <verb> --help`. Do not guess them from this file.
56
+ `scan`, `coverage`, `history` and both `-ai` verbs take `--json`. Prefer it over parsing prose.
57
+
58
+ ## 3. This tool issues NO verdict, and that is the point
59
+
60
+ Nothing here exits non-zero because of what it found. A gap is a decision with a human's name
61
+ on it — an uncovered page may be deliberately out of scope.
62
+
63
+ **`coverage` refuses rather than guesses**, with a typed reason. Report the refusal as *no
64
+ measurement*, never as a finding:
65
+
66
+ | `reason` | What it means | What the human does |
67
+ |---|---|---|
68
+ | `no-surface` | nobody has scanned this app | `ia-qa-discover scan` |
69
+ | `no-healing` | no suite is tracked here | install `@ia-qa/self-healing`, then `map` |
70
+ | `no-run` | contracts exist, no run was ever watched | `ia-qa-heal run`, or `ia-qa-pal tour --suite` |
71
+
72
+ `measured: false` is **not** "the suite covers nothing". Saying that is the single worst
73
+ sentence this tool can cause to be written.
74
+
75
+ ## 4. Never do this
76
+
77
+ 1. **Never run `ia-qa-discover login` or `ia-qa-heal login`.** They open a visible browser and
78
+ wait for a person, twice over: in your shell that is a hung command. Give the human the
79
+ command instead. The same goes for `ia-qa-heal ui`.
80
+ 2. **Never present a contract, or a configured page, as proof a page is tested.** A contract
81
+ means somebody configured that page. Only capture-during-run proves a test went there.
82
+ 3. **Never report a page count as coverage.** Read `surface.json` → `coverage` first: a login
83
+ wall (`loginWall`), an app that navigates without `<a href>` (`hrefBlind`) and the page cap
84
+ (`droppedByCap`) each make the surface look smaller than it is. State those beside any total.
85
+ 4. **Never treat `writeCalls` as evidence a page writes.** A scan never submits anything, so a
86
+ non-GET it observed fired at page load — measured on a real corpus, 10 of 10 were analytics
87
+ beacons. It is in the output as a fact and is deliberately not a ranking factor.
88
+ 5. **`named` is not `asserted`.** The depth figure says the suite *names* N contracted elements.
89
+ A test that clicks a button names it without checking anything.
90
+ 6. **Never call the `-ai` layer a gate.** It emits no verdict, and `unclear` / `refused` are
91
+ answers needing a person, not a retry. A `failed` page means the model call did not succeed
92
+ — say so; it is not a finding about the app.
93
+ 7. **Never widen `capture.safeQueryParams`** to make a scan "work". The deny rule on
94
+ `token`, `password`, `session`, `auth`, `jwt`, `email`, `key` exists so a reset link pasted
95
+ into `scan` does not end up in files the user commits.
96
+ 8. **Never edit `config.json` to add a page you have not seen in a capture.** `discover --apply`
97
+ (heal) and the config are the human's; propose, do not write.
98
+
99
+ ## 5. What leaves the machine
100
+
101
+ Nothing, except: requests to the app the user asked you to scan (same origin, GET only, never
102
+ a `/logout` or `/delete` URL), and — only if the user opted in — `ia-qa-discover-ai` calling
103
+ **their own** LLM provider.
104
+
105
+ The two `-ai` verbs announce what they send *before* they send it, on stderr, in every mode.
106
+ Read that announcement to the user before running either on a private or authenticated app.
107
+ `plan` sends strictly less than `classify`: no selectors, no observed API calls, and not the
108
+ headings the whole app repeats.
109
+
110
+ ## 6. Setting up the BYOK key — ask, never invent
111
+
112
+ Both `-ai` verbs need a provider key. The key **never** goes in a file.
113
+
114
+ 1. Ask the user which provider they have a key for: `anthropic`, `openai`, `google`, or
115
+ `openai-compatible` — that last one is a *shape*, not a vendor: DeepSeek, Groq, Mistral,
116
+ OpenRouter, Together, vLLM and Ollama all serve the same endpoint, and it needs a
117
+ `baseUrl`. A `localhost` URL there means the call never leaves their machine.
118
+ `ia-qa-discover-ai models` lists the models each one takes. Never pick for them.
119
+ 2. **Prefer the machine's credential store.** Give them the command; they run it themselves,
120
+ in their own terminal — it asks for the value and refuses an argument or a pipe:
121
+
122
+ ```bash
123
+ npx -p @ia-qa/self-healing ia-qa-heal secret set MY_KEY_NAME
124
+ ```
125
+
126
+ You can confirm it resolves without ever seeing it: `ia-qa-heal secret check MY_KEY_NAME`.
127
+ **There is no command that prints a stored value, and you must not go looking for one.**
128
+ The point of the store is that the value stops being ambient — it is no longer in the
129
+ environment every process you launch inherits.
130
+
131
+ 3. Otherwise — or in CI, where there is no store and no person — ask them to export it in
132
+ their own shell and to tell you the **variable name**, never the key. If they paste the key
133
+ to you anyway, tell them to rotate it.
134
+
135
+ 4. Write only the reference into `.ia-qa-discovery/config.json`:
136
+
137
+ ```json
138
+ "ai": { "provider": "openai", "model": "gpt-4o-mini",
139
+ "apiKey": { "source": "keychain", "key": "MY_KEY_NAME" } }
140
+ ```
141
+
142
+ `"source": "env"` is the same shape with the variable name, for CI.
143
+
144
+ 5. `--dry-run` first: it shows exactly what would be sent, and to whom, without a key and
145
+ without calling anything. Show that to the user before the real run.
146
+
147
+ That file is committed. A value there is a leaked credential, and the CLI refuses it — it
148
+ detects a key-shaped string where a variable name belongs, prints it masked, and tells the
149
+ user to rotate it. Do not work around that refusal.
150
+
151
+ The CLI reads `process.env` only and never loads `.env` files. If their key lives in `.env`:
152
+ `npx dotenv-cli -- ia-qa-discover-ai plan`.
153
+
154
+ Through the MCP server the same rule holds: `api_key_env` takes the **name** of a variable on
155
+ that machine. A raw key is never a tool argument — a tool call can be logged and replayed.
156
+
157
+ ## 7. The two siblings, and where the line is
158
+
159
+ `@ia-qa/self-healing` (`ia-qa-heal`) repairs an **existing** suite's locators. This package
160
+ describes an app that may have **no** suite. They share the session file and the page-naming
161
+ rule, and they hand off in exactly two places: when `ingest` finds no test file, and when
162
+ `coverage` needs a watched run. `@ia-qa/pal` (`ia-qa-pal`) runs the whole round for a team.
163
+
164
+ `scan` and `map` are **not** the same operation: `map` writes a contract (every interactive
165
+ element, to repair broken locators); `scan` writes a surface (what exists and what it takes as
166
+ input, to decide what to test when there is no suite yet).