@ia-qa/qa-discovery 0.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ia-qa/qa-discovery",
3
- "version": "0.4.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.17.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).