@ia-qa/qa-discovery 0.4.0 → 0.5.2
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 +88 -4
- package/ROADMAP.md +26 -4
- package/TUTORIAL.md +98 -4
- package/dist/ai/classify.d.ts +13 -36
- package/dist/ai/classify.js +89 -169
- package/dist/ai/classify.js.map +1 -1
- package/dist/ai/model.d.ts +93 -0
- package/dist/ai/model.js +192 -0
- package/dist/ai/model.js.map +1 -0
- package/dist/ai/plan.d.ts +224 -0
- package/dist/ai/plan.js +0 -0
- package/dist/ai/plan.js.map +1 -0
- package/dist/cli/args.js +1 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +13 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/skill.d.ts +20 -0
- package/dist/cli/skill.js +63 -0
- package/dist/cli/skill.js.map +1 -0
- package/dist/cli-ai/index.d.ts +3 -2
- package/dist/cli-ai/index.js +268 -4
- package/dist/cli-ai/index.js.map +1 -1
- package/dist/htmlReport.js +20 -4
- package/dist/htmlReport.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +49 -0
- package/dist/mcp/server.js +143 -20
- package/dist/mcp/server.js.map +1 -1
- package/dist/overview.d.ts +9 -0
- package/dist/overview.js +9 -4
- package/dist/overview.js.map +1 -1
- package/dist/overviewFile.js +5 -0
- package/dist/overviewFile.js.map +1 -1
- package/dist/planView.d.ts +141 -0
- package/dist/planView.js +345 -0
- package/dist/planView.js.map +1 -0
- package/package.json +3 -2
- package/skills/ia-qa-discover/SKILL.md +167 -0
package/README.md
CHANGED
|
@@ -27,13 +27,23 @@ npx -p @ia-qa/qa-discovery ia-qa-discover scan https://your-app.example.com --sa
|
|
|
27
27
|
| **what does my test suite not test?** | **`coverage`** — the gap, ranked; needs `@ia-qa/self-healing` and one watched run |
|
|
28
28
|
| what changed since last time? | `history` |
|
|
29
29
|
| what is each entry point *for*, and how sensitive? | `ia-qa-discover-ai classify` (optional, your own key) |
|
|
30
|
+
| what does somebody come to this page to **do**? | `ia-qa-discover-ai plan` (optional, your own key) |
|
|
31
|
+
|
|
32
|
+
**Driving this with an AI agent?** One command gives it the whole doctrine — the verb for
|
|
33
|
+
each question, what every refusal means, and what it must never do:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
ia-qa-discover skill --print # read it
|
|
37
|
+
ia-qa-discover skill --install # drop it in .claude/skills/, where agents actually look
|
|
38
|
+
```
|
|
30
39
|
|
|
31
40
|
`ia-qa-discover <verb> --help` for flags. Nothing here emits a verdict and nothing here gates
|
|
32
41
|
CI: a page count is not coverage, and a gap is a decision for a human.
|
|
33
42
|
|
|
34
43
|
**Same operations for an AI agent** — one contract served twice, never a simplified view for
|
|
35
|
-
one of them. `ia-qa-discover-mcp` exposes `scan_app`, `coverage_map`, `classify_app
|
|
36
|
-
`discovery_history`. `login` is deliberately not a tool: it waits for a
|
|
44
|
+
one of them. `ia-qa-discover-mcp` exposes `scan_app`, `coverage_map`, `classify_app`,
|
|
45
|
+
`plan_pages` and `discovery_history`. `login` is deliberately not a tool: it waits for a
|
|
46
|
+
person at a browser.
|
|
37
47
|
|
|
38
48
|
> **New here?** The [step-by-step tutorial](https://www.ia-qa.com/devtools/qa-discovery/tutorial)
|
|
39
49
|
> walks the whole thing from an app you have never seen, with no jargon — first scan, login
|
|
@@ -414,6 +424,7 @@ A dependency-free JSON-RPC 2.0 stdio server exposing the **same** operations the
|
|
|
414
424
|
|
|
415
425
|
- `scan_app` — crawl and capture. Check `loginWall` and `hrefBlind` in the result before reporting coverage.
|
|
416
426
|
- `classify_app` — the optional BYOK layer below, for an agent. The key is read from an environment variable **named** in the call (`api_key_env`); a raw key is never a tool argument.
|
|
427
|
+
- `plan_pages` — the other BYOK question: what somebody comes to each page **to do**. Same key handling, and it sends **less** — no selectors, no observed API calls. Defaults to the pages `coverage_map` says no test visits, in that tool's order. Ranks nothing, gates nothing; a `failed` page is a failed call, never a finding about the app.
|
|
417
428
|
- `coverage_map` — what the suite does **not** test. Returns `measured: false` with a typed `reason` when no run was ever watched: report that as *no measurement*, never as "the suite covers nothing".
|
|
418
429
|
- `discovery_history` — the trend.
|
|
419
430
|
|
|
@@ -428,8 +439,15 @@ Everything above is deterministic and never leaves your machine. This one binary
|
|
|
428
439
|
```bash
|
|
429
440
|
ia-qa-discover-ai classify --dry-run # see exactly what would be sent, and to whom
|
|
430
441
|
ia-qa-discover-ai classify
|
|
442
|
+
|
|
443
|
+
ia-qa-discover-ai plan --dry-run # the other question, and a smaller payload
|
|
444
|
+
ia-qa-discover-ai plan --report --open
|
|
431
445
|
```
|
|
432
446
|
|
|
447
|
+
Two verbs, two questions, and **`plan` sends strictly less than `classify`**: no selectors and
|
|
448
|
+
no observed API calls ever leave for it. Each announces its own payload, derived from the
|
|
449
|
+
function that builds it, so neither can promise less than it sends.
|
|
450
|
+
|
|
433
451
|
It reads the capture **offline** — it opens no browser and never touches your app again — and asks a model what each entry point is *for* (`authentication`, `payment`, `search`, `data-entry`…) and how sensitive what it handles is. The vocabulary is closed and versioned: a label outside it is rejected before anything else is checked.
|
|
434
452
|
|
|
435
453
|
**Every claim cites the capture, and every citation is resolved and checked.** A classification whose evidence does not exist, or does not say what it was claimed to say, is dropped before you see it, and the drop is reported. That proves the *premise*, never the conclusion — a model can cite a real password field and still be wrong about what the page is for — so read the `why` and the confidence, not the label alone.
|
|
@@ -441,6 +459,12 @@ Configure it in `.ia-qa-discovery/config.json` — the file holds the *name* of
|
|
|
441
459
|
```json
|
|
442
460
|
"ai": { "provider": "anthropic", "model": "claude-haiku-4-5",
|
|
443
461
|
"apiKey": { "source": "env", "key": "ANTHROPIC_API_KEY" } }
|
|
462
|
+
|
|
463
|
+
// or any OpenAI-compatible endpoint — DeepSeek, Groq, Mistral, OpenRouter, vLLM,
|
|
464
|
+
// or a model on your own machine, in which case nothing leaves it at all:
|
|
465
|
+
"ai": { "provider": "openai-compatible", "model": "deepseek-chat",
|
|
466
|
+
"baseUrl": "https://api.deepseek.com/v1",
|
|
467
|
+
"apiKey": { "source": "keychain", "key": "DEEPSEEK_KEY" } }
|
|
444
468
|
```
|
|
445
469
|
|
|
446
470
|
Before the first request it prints what leaves and where it goes:
|
|
@@ -448,8 +472,12 @@ Before the first request it prints what leaves and where it goes:
|
|
|
448
472
|
```
|
|
449
473
|
🌐 Sending 3 pages to api.anthropic.com (anthropic · claude-haiku-4-5) — your key, your account.
|
|
450
474
|
Leaves this machine: each page's URL, title and description, up to 25 headings,
|
|
451
|
-
every observed API call (method, normalised path, status),
|
|
452
|
-
|
|
475
|
+
every observed API call (method, normalised path, status), each form's method and
|
|
476
|
+
action URL, and for every field its name, type, label, whether it is required, the
|
|
477
|
+
text around it and the click path that reveals it. Headings, labels, that surrounding
|
|
478
|
+
text and those click labels are live text from your app.
|
|
479
|
+
Does NOT leave: your CSS selectors. Measured on 57 real fields, a selector adds no
|
|
480
|
+
word that the name, label or type does not already carry — only its position.
|
|
453
481
|
Query values are stripped at capture EXCEPT an allowlist that includes `q` — a search
|
|
454
482
|
term reaches the model as typed (`capture.safeQueryParams` in config.json narrows it).
|
|
455
483
|
Does NOT leave: page HTML, screenshots, cookies or your session file, your test files,
|
|
@@ -467,6 +495,62 @@ rewrite — it is your file — so `scan` warns instead.
|
|
|
467
495
|
|
|
468
496
|
Read that list before pointing it at an authenticated or private app. The result lands in `.ia-qa-discovery/classification.json` **and** in `_overview.md`, which is rewritten so the reading appears next to the capture it was made from.
|
|
469
497
|
|
|
498
|
+
### `plan` — what somebody comes to this page to do
|
|
499
|
+
|
|
500
|
+
`classify` answers what an entry point *is*, against a closed vocabulary. `plan` answers a
|
|
501
|
+
different question, on a different axis: **what is a person trying to do here, and what can
|
|
502
|
+
they no longer do if it breaks** — in the words of whoever uses the app, not a developer's.
|
|
503
|
+
|
|
504
|
+
It exists because that axis is the one nothing else in this ecosystem can reach. The
|
|
505
|
+
deterministic half reports what an app *accepts* — fields, types, required, what moved.
|
|
506
|
+
Nothing in a DOM states that this form is how a locked-out customer gets back in. Neither
|
|
507
|
+
layer is a subset of the other, which is why one never ranks above the other.
|
|
508
|
+
|
|
509
|
+
```bash
|
|
510
|
+
ia-qa-discover-ai plan # the pages `coverage` says no test visits, in its order
|
|
511
|
+
ia-qa-discover-ai plan --all # every readable page, tested or not
|
|
512
|
+
ia-qa-discover-ai plan --report --open # the same branded dossier `scan --report` writes
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
By default it reads exactly the untested pages from `ia-qa-discover coverage`, **in the order
|
|
516
|
+
that verb ranked them** — so an interrupted run has read the ones that mattered most. With no
|
|
517
|
+
suite tracked here there is no untested list to narrow to, and it reads every page and says
|
|
518
|
+
so rather than refusing: a project with no tests is the one this package exists for.
|
|
519
|
+
|
|
520
|
+
Four properties, and each is tested:
|
|
521
|
+
|
|
522
|
+
- **It sends less than `classify`** — no selectors, no observed API calls. The most sensitive
|
|
523
|
+
half of a capture, and technical noise for a question that is not technical.
|
|
524
|
+
- **Every reading cites the capture** and is dropped if the citation does not resolve, exactly
|
|
525
|
+
like a classification. A path the prompt never offered is refused *before* resolution, so
|
|
526
|
+
citing an API call — which this verb does not send — cannot happen.
|
|
527
|
+
- **The shell is not a page's content.** Headings repeated across the app (a footer's `Legal`,
|
|
528
|
+
`Contact`, `Tools`) are dropped from the payload, using the same threshold `autoLayout`
|
|
529
|
+
uses. Measured before the change: a page whose whole interest was `🗂️ Environment Manager`
|
|
530
|
+
had its reading propped up by three footer links — citations that resolve, and support
|
|
531
|
+
nothing. A page whose *every* heading is shared keeps them all: the exclusion narrows the
|
|
532
|
+
evidence, it never makes a page unreadable.
|
|
533
|
+
- **At least one citation must carry the subject** — a level-1 or level-2 heading, a form, or
|
|
534
|
+
a field. A reading standing entirely on nav labels is dropped. The rule is skipped on a page
|
|
535
|
+
that offers no such anchor, because a rule nobody can satisfy would punish a page for its own
|
|
536
|
+
markup.
|
|
537
|
+
- **It ranks nothing and gates nothing.** The order is `coverage`'s measurement; this adds a
|
|
538
|
+
labelled line beside it, never a row to it.
|
|
539
|
+
- **The origin travels with the artifact.** `plan.json` carries the model and the timestamp,
|
|
540
|
+
one module renders the terminal, `_overview.md` and the HTML report, a page re-captured
|
|
541
|
+
after it was read is marked **stale**, and the report's footer stops saying "no LLM".
|
|
542
|
+
|
|
543
|
+
A model that cannot tell answers `unclear`: kept, labelled, never silently dropped — the
|
|
544
|
+
capture may genuinely not say, and a proposal nobody can anchor is still worth reading as
|
|
545
|
+
long as it is marked as one.
|
|
546
|
+
|
|
547
|
+
**Its honest limit**, measured on a real run of 8 pages: the reading is worth most where the
|
|
548
|
+
deterministic half is blind — a page with no form at all, whose headings name the stake — and
|
|
549
|
+
thins out on content pages, where "somebody comes here to read the articles" repeats the
|
|
550
|
+
capture back. Read the confidence and the citations, not the sentence alone. After the two
|
|
551
|
+
rules above, that same run produced **0 citations pointing at the footer or the nav**, against
|
|
552
|
+
three on a single page before them.
|
|
553
|
+
|
|
470
554
|
---
|
|
471
555
|
|
|
472
556
|
## Library
|
package/ROADMAP.md
CHANGED
|
@@ -84,7 +84,7 @@ be understood.
|
|
|
84
84
|
|
|
85
85
|
| Stage | The question it answers | Status |
|
|
86
86
|
|---|---|---|
|
|
87
|
-
| **Value and personas** (F0) | Who uses this app, for what, and where would it hurt most if it broke? |
|
|
87
|
+
| **Value and personas** (F0) | Who uses this app, for what, and where would it hurt most if it broke? | partly shipped — see `plan` below |
|
|
88
88
|
| **Surface reconnaissance** (F1) | What pages exist, what do they take as input, what do they call? | ✅ shipped (0.1.0) |
|
|
89
89
|
| **Component classification** (F2) | What is each entry point *for*, and how sensitive is what it handles? | in progress |
|
|
90
90
|
| **Zones and synthesis** (F2.5) | How do the pages group into functional areas, and what is the app as a whole? | not built |
|
|
@@ -218,9 +218,31 @@ navigation graph), then a grounded synthesis on top. Reconciles with the value s
|
|
|
218
218
|
|
|
219
219
|
## Value and personas (F0) — *the game changer, built with care*
|
|
220
220
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
221
|
+
**A first, deliberately narrow slice shipped: `ia-qa-discover-ai plan`.** It answers the
|
|
222
|
+
value question for **one page at a time** — what somebody comes here to do, and what they can
|
|
223
|
+
no longer do if it breaks — and nothing beyond that. No personas, no app-level framing, no
|
|
224
|
+
"moment of truth": those are the claims the validation protocol below exists to gate, and
|
|
225
|
+
none of them is made.
|
|
226
|
+
|
|
227
|
+
Why this slice was safe to ship first, in the terms this file uses everywhere else:
|
|
228
|
+
|
|
229
|
+
- It **produces no executable artifact**, so a wrong reading cannot become a broken test.
|
|
230
|
+
- Every reading **cites the capture** and is dropped when the citation does not resolve —
|
|
231
|
+
the same mechanism as classification, on a per-page claim small enough for that check to
|
|
232
|
+
mean something.
|
|
233
|
+
- It **ranks nothing**: the order is `coverage`'s measurement, and the model never adds a row
|
|
234
|
+
to it. That is the difference between commenting on a measurement and replacing one.
|
|
235
|
+
- It sends **less** than `classify` — no selectors, no observed API calls.
|
|
236
|
+
|
|
237
|
+
Measured limit, from a real 8-page run: the reading is worth most on a page the deterministic
|
|
238
|
+
half is blind to (no fields at all, the stake living in the headings) and is near-tautological
|
|
239
|
+
on content pages. That asymmetry is itself an argument for keeping this stage per-page until
|
|
240
|
+
the protocol below exists.
|
|
241
|
+
|
|
242
|
+
What remains unbuilt is the hard half — who the users are, and where the moment of truth is.
|
|
243
|
+
This is the stage that makes the output a senior's plan instead of a classified inventory, and
|
|
244
|
+
the one whose claims are hardest to check — so it is scheduled as **careful** work, not as
|
|
245
|
+
later work.
|
|
224
246
|
|
|
225
247
|
**It needs a validation protocol before it needs code.** Classification can be checked in seconds
|
|
226
248
|
against the capture; this cannot. Label a handful of real apps by hand, compare the stage's
|
package/TUTORIAL.md
CHANGED
|
@@ -30,7 +30,8 @@ suite, a ranked list of the pages nobody tests.
|
|
|
30
30
|
8. [What my suite does not test](#8-what-my-suite-does-not-test)
|
|
31
31
|
9. [Doing this every week](#9-doing-this-every-week)
|
|
32
32
|
10. [Optional: what is each entry point *for*?](#10-optional-what-is-each-entry-point-for)
|
|
33
|
-
11. [
|
|
33
|
+
11. [Optional: what does somebody come here to *do*?](#11-optional-what-does-somebody-come-here-to-do)
|
|
34
|
+
12. [Troubleshooting](#12-troubleshooting)
|
|
34
35
|
|
|
35
36
|
---
|
|
36
37
|
|
|
@@ -90,6 +91,20 @@ first run downloads Chromium once (~130 MB) into a shared cache and never again.
|
|
|
90
91
|
If you drive Claude Code, Cursor, Windsurf, VS Code + Copilot or any MCP-capable agent, it can
|
|
91
92
|
run all of this for you.
|
|
92
93
|
|
|
94
|
+
### Step 4.0 — Give it the instructions first
|
|
95
|
+
|
|
96
|
+
Before the server, one command. It costs nothing and removes most of the back-and-forth:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npx -p @ia-qa/qa-discovery ia-qa-discover skill --install
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
That writes `.claude/skills/ia-qa-discover/SKILL.md` — which verb answers which question, what
|
|
103
|
+
each refusal means (`no-surface`, `no-healing`, `no-run` are three different sentences), what
|
|
104
|
+
it must never do, and how to set an API key up without ever holding one. Agents read
|
|
105
|
+
`.claude/skills/`, never `node_modules`, so the file has to land there to exist at all.
|
|
106
|
+
Use `--print` to read it yourself first, or `--user` to install it for every project.
|
|
107
|
+
|
|
93
108
|
### Step 4.1 — Add the server to your agent
|
|
94
109
|
|
|
95
110
|
Claude Code:
|
|
@@ -402,8 +417,18 @@ model, with **your** API key, what each form and field is for and how sensitive
|
|
|
402
417
|
|
|
403
418
|
**It is the only thing in this package that sends anything anywhere, and it says so before it
|
|
404
419
|
does**, listing exactly what goes: the page URL, title, description, up to 25 headings, the
|
|
405
|
-
observed API calls,
|
|
406
|
-
|
|
420
|
+
observed API calls, each form's method and action URL, and for every field its name, type,
|
|
421
|
+
label, whether it is required, the text around it and the click path that reveals it.
|
|
422
|
+
|
|
423
|
+
**Your CSS selectors do not leave**, and that is a measurement rather than a courtesy: over 25
|
|
424
|
+
real pages and 57 fields, the number whose selector carried one word that the name, label or
|
|
425
|
+
type did not already carry was **zero**. What a selector adds is position. Read that notice
|
|
426
|
+
before running it on a private or authenticated app.
|
|
427
|
+
|
|
428
|
+
That list is not prose someone keeps in step by hand. The payload is built field by field, and
|
|
429
|
+
a test fails on any key the notice does not name. It used to be built by copying the capture
|
|
430
|
+
object whole — which meant a field added to the capture left for the model unannounced, and
|
|
431
|
+
three of them already had.
|
|
407
432
|
|
|
408
433
|
Two guarantees make the output usable:
|
|
409
434
|
|
|
@@ -420,7 +445,76 @@ Without a key, nothing changes: every command above behaves exactly as it does t
|
|
|
420
445
|
|
|
421
446
|
---
|
|
422
447
|
|
|
423
|
-
## 11.
|
|
448
|
+
## 11. Optional: what does somebody come here to *do*?
|
|
449
|
+
|
|
450
|
+
Section 10 asks what an entry point **is**. This asks something a DOM cannot answer at all:
|
|
451
|
+
**what is a person trying to do on this page, and what can they no longer do if it breaks.**
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
npx ia-qa-discover-ai plan
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
With no flags it reads exactly the pages section 8 said no test visits, **in the order that
|
|
458
|
+
verb ranked them**. If you stop it halfway, the pages it already read are the ones that
|
|
459
|
+
mattered most. No suite tracked yet? It reads every page and tells you that is what it did —
|
|
460
|
+
you are the reader this package was written for.
|
|
461
|
+
|
|
462
|
+
What you get, per page:
|
|
463
|
+
|
|
464
|
+
```
|
|
465
|
+
· devtools-environments
|
|
466
|
+
A person comes here to switch API keys and model configurations for different
|
|
467
|
+
environments like development, production and staging. · If it breaks: that person
|
|
468
|
+
cannot change or manage the API keys and model configurations. (confidence 0.90)
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
That page has **zero form fields**. The deterministic half could only say `nothing observed
|
|
472
|
+
says it matters (2 API calls)` — it had nothing to observe. This is the gap the layer exists
|
|
473
|
+
for, and the honest flip side is that on a page of articles the same model writes "somebody
|
|
474
|
+
comes here to read the articles", which repeats the capture back at you. **Read the
|
|
475
|
+
confidence and the citations, not the sentence.**
|
|
476
|
+
|
|
477
|
+
### It sends less than `classify`, deliberately
|
|
478
|
+
|
|
479
|
+
**No observed API calls** leave for this question — technical noise for a question about
|
|
480
|
+
people. Selectors leave for neither verb any more: they used to be the other half of this
|
|
481
|
+
sentence, until a measurement showed they carry no word the name, label or type does not, and
|
|
482
|
+
`classify` dropped them too. The notice each verb prints before its first request is derived
|
|
483
|
+
from the function that builds the payload, so it cannot promise less than it sends.
|
|
484
|
+
|
|
485
|
+
**Your footer does not leave either.** Headings that appear across your whole app — `Legal`,
|
|
486
|
+
`Contact`, a nav title — are the shell, not this page's content, and they are dropped from
|
|
487
|
+
what goes. That started as a privacy-shaped decision and turned out to be a quality one: on a
|
|
488
|
+
real run, the reading of a page whose whole interest was `🗂️ Environment Manager` had been
|
|
489
|
+
propped up by three footer links. They resolve, so nothing rejected them, and they support
|
|
490
|
+
nothing. On the same run after the change: **zero citations pointing at a footer or a nav.**
|
|
491
|
+
|
|
492
|
+
And a reading has to stand on something: at least one citation must be a level-1 or level-2
|
|
493
|
+
heading, a form, or a field. One built entirely out of nav labels is dropped — unless the page
|
|
494
|
+
offers no such anchor at all, in which case the rule is skipped rather than punishing the page
|
|
495
|
+
for its own markup.
|
|
496
|
+
|
|
497
|
+
### Three things it will not do
|
|
498
|
+
|
|
499
|
+
- **It will not rank anything.** The order is the one `coverage` measured. A model reordering
|
|
500
|
+
a measured list has stopped commenting on a measurement and started replacing it.
|
|
501
|
+
- **It will not gate.** Nothing here has an exit code that fails a build.
|
|
502
|
+
- **It will not hide its origin.** `plan.json` carries the model and the time it ran;
|
|
503
|
+
`_overview.md` and the HTML report show the section labelled `model-read`; a page
|
|
504
|
+
re-captured *after* it was read is marked **stale**; and the report's footer stops claiming
|
|
505
|
+
the document is LLM-free, because it no longer is.
|
|
506
|
+
|
|
507
|
+
If the capture genuinely does not say, the model answers `unclear` — kept and labelled, never
|
|
508
|
+
quietly dropped.
|
|
509
|
+
|
|
510
|
+
```bash
|
|
511
|
+
npx ia-qa-discover-ai plan --all # the tested pages too
|
|
512
|
+
npx ia-qa-discover-ai plan --report --open # the branded dossier, in a browser
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## 12. Troubleshooting
|
|
424
518
|
|
|
425
519
|
**"Found 1 page" on an app I know has twenty.** Section 7, case 2 — the app navigates without
|
|
426
520
|
links. Declare the routes in `config.json` → `pages`.
|
package/dist/ai/classify.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { AiProvider } from '@ia-qa/self-healing';
|
|
|
2
2
|
import type { PageCapture } from '../capture/page';
|
|
3
3
|
import { type ToolTrigger } from '../taxonomy';
|
|
4
4
|
import { type CaptureIndex, type Citation, type CitationVerdict } from '../citations';
|
|
5
|
+
import { providerError, type ModelOptions } from './model';
|
|
5
6
|
/**
|
|
6
7
|
* F2 — classify each entry point, offline, from F1's capture.
|
|
7
8
|
*
|
|
@@ -12,22 +13,18 @@ import { type CaptureIndex, type Citation, type CitationVerdict } from '../citat
|
|
|
12
13
|
* **On duplication, stated because this project punishes silent drift:** the
|
|
13
14
|
* provider/model catalogue is imported from `@ia-qa/self-healing` rather than
|
|
14
15
|
* copied — there must not be a third model list in this repo. The HTTP transport
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* now lives in `./model.ts`, which is a second implementation of something
|
|
17
|
+
* self-healing keeps internal (`callModel`); it moved out of this file when a
|
|
18
|
+
* second BYOK verb arrived, so the cost stays at two copies instead of three.
|
|
19
|
+
* Any change to a provider's request shape owes an edit there as well as in
|
|
20
|
+
* self-healing.
|
|
18
21
|
*/
|
|
19
22
|
/** The document writes, and the two strings a later stage keys on. */
|
|
20
23
|
export declare const CLASSIFICATION_SCHEMA = "qa-discovery-classification@1";
|
|
21
24
|
export declare const CLASSIFICATION_FILENAME = "classification.json";
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
apiKey: string;
|
|
26
|
-
/** Injected in tests; defaults to the global fetch. */
|
|
27
|
-
fetchFn?: typeof fetch;
|
|
28
|
-
timeoutMs?: number;
|
|
29
|
-
temperature?: number;
|
|
30
|
-
}
|
|
25
|
+
/** The transport's options, named for this verb. One shape for every BYOK caller. */
|
|
26
|
+
export type ClassifyOptions = ModelOptions;
|
|
27
|
+
export { providerError };
|
|
31
28
|
/** One thing on a page that takes input, addressed by a path a citation can resolve. */
|
|
32
29
|
export interface EntryPoint {
|
|
33
30
|
/** e.g. `pages/checkout.json#forms[0]` or `pages/checkout.json#looseFields[2]` */
|
|
@@ -125,9 +122,9 @@ export declare function collect(raws: Partial<Classification>[], index: CaptureI
|
|
|
125
122
|
* What leaves this machine, said before it leaves — the one thing that makes
|
|
126
123
|
* "everything is local except the model call" a promise rather than a claim.
|
|
127
124
|
*
|
|
128
|
-
* It
|
|
129
|
-
* `buildRequest` sends to, so a new provider cannot be reachable
|
|
130
|
-
* notice still lists three. And it is derived from `buildPrompt` — every line
|
|
125
|
+
* It resolves the host through `egressHost` rather than naming one of its own: the notice names the
|
|
126
|
+
* very constant `buildRequest` sends to, so a new provider cannot be reachable
|
|
127
|
+
* while the notice still lists three. And it is derived from `buildPrompt` — every line
|
|
131
128
|
* below is a field that function actually puts in the payload; adding one there
|
|
132
129
|
* owes a line here.
|
|
133
130
|
*
|
|
@@ -146,27 +143,7 @@ export declare function collect(raws: Partial<Classification>[], index: CaptureI
|
|
|
146
143
|
* the token here. The word "full" is gone from that line for the same reason the
|
|
147
144
|
* allowlist is named: the two statements must not contradict each other.
|
|
148
145
|
*/
|
|
149
|
-
export declare function egressNotice(provider: AiProvider, model: string, pages: number, planned?: boolean): string;
|
|
150
|
-
/**
|
|
151
|
-
* What the provider itself said went wrong.
|
|
152
|
-
*
|
|
153
|
-
* Found by execution, not review: a Gemini key that is simply wrong comes back
|
|
154
|
-
* **400**, not 401 — so the previous message read `refused the call with HTTP
|
|
155
|
-
* 400`, said nothing about the key, and left the one person who could fix it in
|
|
156
|
-
* sixty seconds with nothing to go on. Mapping more status codes by hand is the
|
|
157
|
-
* wrong shape of fix (the list rots, and it is different per provider); the
|
|
158
|
-
* providers already answer the question in the body, and all three happen to
|
|
159
|
-
* use the same `{error:{message}}` envelope.
|
|
160
|
-
*
|
|
161
|
-
* Two guards. The body is untrusted text that lands in a terminal and in
|
|
162
|
-
* `classification.json`, so it is capped and flattened. And anything shaped like
|
|
163
|
-
* a credential is redacted before it is printed — a provider that echoes the
|
|
164
|
-
* request back would otherwise put the user's own key in a file they commit.
|
|
165
|
-
*/
|
|
166
|
-
export declare function providerError(res: {
|
|
167
|
-
status: number;
|
|
168
|
-
text: () => Promise<string>;
|
|
169
|
-
}): Promise<string>;
|
|
146
|
+
export declare function egressNotice(provider: AiProvider, model: string, pages: number, planned?: boolean, baseUrl?: string): string;
|
|
170
147
|
/**
|
|
171
148
|
* One page in, validated classifications out. Returns an empty result rather
|
|
172
149
|
* than throwing on a network failure, an unparseable reply or an empty page:
|