@ia-qa/qa-discovery 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +378 -0
- package/ROADMAP.md +321 -0
- package/dist/ai/classify.d.ts +170 -0
- package/dist/ai/classify.js +431 -0
- package/dist/ai/classify.js.map +1 -0
- package/dist/browser/extractPage.d.ts +41 -0
- package/dist/browser/extractPage.js +427 -0
- package/dist/browser/extractPage.js.map +1 -0
- package/dist/browser/openables.d.ts +19 -0
- package/dist/browser/openables.js +127 -0
- package/dist/browser/openables.js.map +1 -0
- package/dist/capture/page.d.ts +128 -0
- package/dist/capture/page.js +78 -0
- package/dist/capture/page.js.map +1 -0
- package/dist/citations.d.ts +80 -0
- package/dist/citations.js +197 -0
- package/dist/citations.js.map +1 -0
- package/dist/classificationView.d.ts +118 -0
- package/dist/classificationView.js +178 -0
- package/dist/classificationView.js.map +1 -0
- package/dist/cli/args.d.ts +3 -0
- package/dist/cli/args.js +38 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/history.d.ts +1 -0
- package/dist/cli/history.js +35 -0
- package/dist/cli/history.js.map +1 -0
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +172 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/login.d.ts +29 -0
- package/dist/cli/login.js +179 -0
- package/dist/cli/login.js.map +1 -0
- package/dist/cli/scan.d.ts +11 -0
- package/dist/cli/scan.js +246 -0
- package/dist/cli/scan.js.map +1 -0
- package/dist/cli-ai/index.d.ts +7 -0
- package/dist/cli-ai/index.js +360 -0
- package/dist/cli-ai/index.js.map +1 -0
- package/dist/config.d.ts +112 -0
- package/dist/config.js +178 -0
- package/dist/config.js.map +1 -0
- package/dist/diff.d.ts +12 -0
- package/dist/diff.js +43 -0
- package/dist/diff.js.map +1 -0
- package/dist/explore.d.ts +59 -0
- package/dist/explore.js +119 -0
- package/dist/explore.js.map +1 -0
- package/dist/healingLink.d.ts +30 -0
- package/dist/healingLink.js +127 -0
- package/dist/healingLink.js.map +1 -0
- package/dist/history.d.ts +38 -0
- package/dist/history.js +120 -0
- package/dist/history.js.map +1 -0
- package/dist/htmlReport.d.ts +2 -0
- package/dist/htmlReport.js +371 -0
- package/dist/htmlReport.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/sandbox.d.ts +9 -0
- package/dist/mcp/sandbox.js +82 -0
- package/dist/mcp/sandbox.js.map +1 -0
- package/dist/mcp/server.d.ts +120 -0
- package/dist/mcp/server.js +315 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/network.d.ts +60 -0
- package/dist/network.js +99 -0
- package/dist/network.js.map +1 -0
- package/dist/overview.d.ts +40 -0
- package/dist/overview.js +370 -0
- package/dist/overview.js.map +1 -0
- package/dist/overviewFile.d.ts +31 -0
- package/dist/overviewFile.js +101 -0
- package/dist/overviewFile.js.map +1 -0
- package/dist/scan.d.ts +159 -0
- package/dist/scan.js +402 -0
- package/dist/scan.js.map +1 -0
- package/dist/sharedCalls.d.ts +28 -0
- package/dist/sharedCalls.js +61 -0
- package/dist/sharedCalls.js.map +1 -0
- package/dist/taxonomy.d.ts +77 -0
- package/dist/taxonomy.js +153 -0
- package/dist/taxonomy.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,178 @@
|
|
|
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.STALE_CAVEAT = exports.CLASSIFICATION_CAVEAT = void 0;
|
|
37
|
+
exports.classificationPath = classificationPath;
|
|
38
|
+
exports.loadClassification = loadClassification;
|
|
39
|
+
exports.buildClassificationView = buildClassificationView;
|
|
40
|
+
exports.classificationHeadline = classificationHeadline;
|
|
41
|
+
exports.classificationFor = classificationFor;
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const config_1 = require("./config");
|
|
45
|
+
const classify_1 = require("./ai/classify");
|
|
46
|
+
/** Where `ia-qa-discover-ai` writes. Never `capture/`: that directory is pages. */
|
|
47
|
+
function classificationPath(cwd = (0, config_1.baseDir)()) {
|
|
48
|
+
return path.join(cwd, config_1.CONFIG_DIR, classify_1.CLASSIFICATION_FILENAME);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Read the classification, or null when there is none.
|
|
52
|
+
*
|
|
53
|
+
* A file whose `schema` is not the one this build understands is treated as
|
|
54
|
+
* absent rather than parsed optimistically: a report that renders fields it
|
|
55
|
+
* guessed at is worse than a report with no classification section.
|
|
56
|
+
*/
|
|
57
|
+
function loadClassification(file = classificationPath()) {
|
|
58
|
+
if (!fs.existsSync(file))
|
|
59
|
+
return null;
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
62
|
+
if (parsed?.schema !== classify_1.CLASSIFICATION_SCHEMA || typeof parsed.pages !== 'object' || !parsed.pages)
|
|
63
|
+
return null;
|
|
64
|
+
return parsed;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** True when the page was captured after it was classified. */
|
|
71
|
+
function isStale(page, classifiedAt) {
|
|
72
|
+
if (!page?.capturedAt)
|
|
73
|
+
return false;
|
|
74
|
+
const captured = Date.parse(page.capturedAt);
|
|
75
|
+
const classified = Date.parse(classifiedAt);
|
|
76
|
+
if (Number.isNaN(captured) || Number.isNaN(classified))
|
|
77
|
+
return false;
|
|
78
|
+
return captured > classified;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Pair a classification document with the capture the reports are rendering.
|
|
82
|
+
*
|
|
83
|
+
* `pages` is the current capture, so every count below is relative to what is on
|
|
84
|
+
* disk NOW — a classification naming a page that no longer exists is dropped
|
|
85
|
+
* from the view entirely rather than shown against nothing.
|
|
86
|
+
*/
|
|
87
|
+
function buildClassificationView(doc, pages) {
|
|
88
|
+
// `entryPointsOf` is the one definition of "this page has something to
|
|
89
|
+
// classify" — the AI CLI decides what to send with it, so the report must
|
|
90
|
+
// count what was NOT classified with the same function or the two disagree.
|
|
91
|
+
const hasEntryPoints = (p) => (0, classify_1.entryPointsOf)(p).length > 0;
|
|
92
|
+
const byName = new Map(pages.map((p) => [p.page, p]));
|
|
93
|
+
const out = [];
|
|
94
|
+
for (const [name, result] of Object.entries(doc.pages)) {
|
|
95
|
+
if (!byName.has(name))
|
|
96
|
+
continue;
|
|
97
|
+
out.push({
|
|
98
|
+
page: name,
|
|
99
|
+
kept: result.kept ?? [],
|
|
100
|
+
refused: result.refused ?? [],
|
|
101
|
+
dropped: (result.dropped ?? []).length,
|
|
102
|
+
failed: result.failed?.reason,
|
|
103
|
+
stale: isStale(byName.get(name), doc.classifiedAt),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
out.sort((a, b) => a.page.localeCompare(b.page));
|
|
107
|
+
const candidates = pages.filter(hasEntryPoints);
|
|
108
|
+
const seen = new Set(out.filter((p) => !p.failed).map((p) => p.page));
|
|
109
|
+
const unclassified = candidates.filter((p) => !seen.has(p.page)).map((p) => p.page).sort();
|
|
110
|
+
const tools = new Map();
|
|
111
|
+
for (const p of out) {
|
|
112
|
+
for (const k of p.kept) {
|
|
113
|
+
for (const t of k.securityTools ?? []) {
|
|
114
|
+
if (!tools.has(t.tool))
|
|
115
|
+
tools.set(t.tool, new Set());
|
|
116
|
+
tools.get(t.tool).add(p.page);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
model: doc.model,
|
|
122
|
+
classifiedAt: doc.classifiedAt,
|
|
123
|
+
taxonomyVersion: doc.taxonomyVersion,
|
|
124
|
+
pages: out,
|
|
125
|
+
unclassified,
|
|
126
|
+
noEntryPoints: pages.length - candidates.length,
|
|
127
|
+
staleCount: out.filter((p) => p.stale).length,
|
|
128
|
+
keptTotal: out.reduce((n, p) => n + p.kept.length, 0),
|
|
129
|
+
refusedTotal: out.reduce((n, p) => n + p.refused.length, 0),
|
|
130
|
+
droppedTotal: out.reduce((n, p) => n + p.dropped, 0),
|
|
131
|
+
securityTools: [...tools.entries()].map(([tool, ps]) => ({ tool, pages: [...ps].sort() })).sort((a, b) => a.tool.localeCompare(b.tool)),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The one-line summary both renderers put at the top of the section.
|
|
136
|
+
*
|
|
137
|
+
* It leads with what was kept and immediately qualifies it, because a bare
|
|
138
|
+
* "12 classified" is precisely the confident number this package spends its
|
|
139
|
+
* design on refusing to print.
|
|
140
|
+
*/
|
|
141
|
+
function classificationHeadline(v) {
|
|
142
|
+
const bits = [`${v.keptTotal} classified`];
|
|
143
|
+
if (v.refusedTotal > 0)
|
|
144
|
+
bits.push(`${v.refusedTotal} refused`);
|
|
145
|
+
if (v.droppedTotal > 0)
|
|
146
|
+
bits.push(`${v.droppedTotal} dropped`);
|
|
147
|
+
if (v.unclassified.length > 0)
|
|
148
|
+
bits.push(`${v.unclassified.length} page(s) not classified`);
|
|
149
|
+
if (v.staleCount > 0)
|
|
150
|
+
bits.push(`${v.staleCount} stale`);
|
|
151
|
+
return bits.join(' · ');
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The reminder, deliberately softer than the deterministic one.
|
|
155
|
+
*
|
|
156
|
+
* The tool already refuses a claim whose citation does not resolve, so telling
|
|
157
|
+
* the reader to check everything again would be alarm fatigue — a warning that
|
|
158
|
+
* fires on the good rows too is a warning people learn to skip, and then it is
|
|
159
|
+
* not there when it matters. So it names the exact residual risk instead: the
|
|
160
|
+
* check proved the citation, never the reading built on it.
|
|
161
|
+
*/
|
|
162
|
+
exports.CLASSIFICATION_CAVEAT = 'Every line below cites something this tool resolved in the capture and checked — a claim whose ' +
|
|
163
|
+
'citation did not exist, or did not say what it was claimed to say, was dropped before you saw it. ' +
|
|
164
|
+
'That proves the premise, not the conclusion: a model can cite a real password field and still be ' +
|
|
165
|
+
'wrong about what the page is for. Read the `why` and the confidence, not the label alone.';
|
|
166
|
+
/** Why a stale row is not simply out of date. */
|
|
167
|
+
exports.STALE_CAVEAT = 'These pages were re-captured after they were classified, so the reading below describes a version ' +
|
|
168
|
+
'of the page that is no longer on disk. Re-run `ia-qa-discover-ai classify` before relying on them.';
|
|
169
|
+
/**
|
|
170
|
+
* The whole read-and-pair step, for a caller that has pages and wants the
|
|
171
|
+
* section. Returns null when there is no classification — the normal case, and
|
|
172
|
+
* every renderer must be complete without one.
|
|
173
|
+
*/
|
|
174
|
+
function classificationFor(pages, file = classificationPath()) {
|
|
175
|
+
const doc = loadClassification(file);
|
|
176
|
+
return doc ? buildClassificationView(doc, pages) : null;
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=classificationView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classificationView.js","sourceRoot":"","sources":["../src/classificationView.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/args.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KNOWN_FLAGS = void 0;
|
|
4
|
+
exports.rejectUnknownFlags = rejectUnknownFlags;
|
|
5
|
+
/** Flag validation for the hand-rolled CLI parser — mirrors `@ia-qa/self-healing`'s `cli/args.ts`. */
|
|
6
|
+
exports.KNOWN_FLAGS = {
|
|
7
|
+
scan: [
|
|
8
|
+
'--depth',
|
|
9
|
+
'--max',
|
|
10
|
+
'--no-reveal',
|
|
11
|
+
'--strict-host',
|
|
12
|
+
'--session',
|
|
13
|
+
'--no-network',
|
|
14
|
+
'--no-sitemap',
|
|
15
|
+
'--deep',
|
|
16
|
+
'--deep-budget',
|
|
17
|
+
'--network-threshold',
|
|
18
|
+
'--save',
|
|
19
|
+
'--json',
|
|
20
|
+
'--report',
|
|
21
|
+
'--open',
|
|
22
|
+
],
|
|
23
|
+
login: ['--url', '--session'],
|
|
24
|
+
history: ['--json', '--limit'],
|
|
25
|
+
};
|
|
26
|
+
function rejectUnknownFlags(command, args) {
|
|
27
|
+
const known = exports.KNOWN_FLAGS[command];
|
|
28
|
+
if (known === undefined)
|
|
29
|
+
return;
|
|
30
|
+
for (const a of args) {
|
|
31
|
+
if (!a.startsWith('-'))
|
|
32
|
+
continue;
|
|
33
|
+
if (known.includes(a))
|
|
34
|
+
continue;
|
|
35
|
+
throw new Error(`Unknown flag "${a}" for "ia-qa-discover ${command}". Run \`ia-qa-discover help\` to see its flags.`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=args.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":";;;AAsBA,gDAQC;AA9BD,sGAAsG;AACzF,QAAA,WAAW,GAAsC;IAC5D,IAAI,EAAE;QACJ,SAAS;QACT,OAAO;QACP,aAAa;QACb,eAAe;QACf,WAAW;QACX,cAAc;QACd,cAAc;QACd,QAAQ;QACR,eAAe;QACf,qBAAqB;QACrB,QAAQ;QACR,QAAQ;QACR,UAAU;QACV,QAAQ;KACT;IACD,KAAK,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC;IAC7B,OAAO,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC;CAC/B,CAAC;AAEF,SAAgB,kBAAkB,CAAC,OAAe,EAAE,IAAc;IAChE,MAAM,KAAK,GAAG,mBAAW,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO;IAChC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACjC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,SAAS;QAChC,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,yBAAyB,OAAO,kDAAkD,CAAC,CAAC;IACxH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runHistory(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runHistory = runHistory;
|
|
4
|
+
const history_1 = require("../history");
|
|
5
|
+
function intArg(args, flag, fallback, max) {
|
|
6
|
+
const i = args.indexOf(flag);
|
|
7
|
+
if (i === -1)
|
|
8
|
+
return fallback;
|
|
9
|
+
const raw = args[i + 1];
|
|
10
|
+
const n = Number(raw);
|
|
11
|
+
if (!raw || !Number.isInteger(n) || n < 1 || n > max) {
|
|
12
|
+
throw new Error(`${flag} needs a whole number between 1 and ${max}.`);
|
|
13
|
+
}
|
|
14
|
+
return n;
|
|
15
|
+
}
|
|
16
|
+
async function runHistory(args) {
|
|
17
|
+
const limit = intArg(args, '--limit', history_1.HISTORY_LIMIT, history_1.HISTORY_LIMIT);
|
|
18
|
+
const entries = (0, history_1.readHistory)(limit);
|
|
19
|
+
if (args.includes('--json')) {
|
|
20
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (entries.length === 0) {
|
|
24
|
+
console.log('No scan history yet — run `ia-qa-discover scan <url>` first.');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
for (const e of entries) {
|
|
28
|
+
const churn = e.added.length + e.removed.length + e.changed.length;
|
|
29
|
+
console.log(`${e.ts} ${e.pagesCaptured}/${e.pagesFound} pages ` +
|
|
30
|
+
`+${e.added.length} -${e.removed.length} ~${e.changed.length}` +
|
|
31
|
+
(churn === 0 ? ' (no change)' : '') +
|
|
32
|
+
(e.commit ? ` ${e.commit}${e.branch ? ` (${e.branch})` : ''}` : ''));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","sourceRoot":"","sources":["../../src/cli/history.ts"],"names":[],"mappings":";;AAaA,gCAuBC;AApCD,wCAAwD;AAExD,SAAS,MAAM,CAAC,IAAc,EAAE,IAAY,EAAE,QAAgB,EAAE,GAAW;IACzE,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACxB,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,uCAAuC,GAAG,GAAG,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAEM,KAAK,UAAU,UAAU,CAAC,IAAc;IAC7C,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,uBAAa,EAAE,uBAAa,CAAC,CAAC;IACpE,MAAM,OAAO,GAAG,IAAA,qBAAW,EAAC,KAAK,CAAC,CAAC;IAEnC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9C,OAAO;IACT,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAC;QAC5E,OAAO;IACT,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;QACnE,OAAO,CAAC,GAAG,CACT,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC,UAAU,UAAU;YACnD,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE;YAC9D,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;YACpC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACvE,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
declare const HELP = "ia-qa-discover \u2014 deterministic surface reconnaissance for a web app\n\nF1 of QA Discovery: crawl an app's same-origin pages and capture what each one\nis structurally about \u2014 title, headings, forms, and observed API calls. No\nLLM, no verdict: this is recon, not a gate. Runs entirely on your machine;\nnothing is uploaded anywhere.\n\n[FOR AI AGENTS] You are driving this for a human. Run `scan` to capture the\napp's surface, then read .ia-qa-discovery/capture/surface.json (the index) and\nthe per-page files it points at \u2014 that is the evidence for anything you\nreport about this app. Never assert a page's purpose or risk from the URL\nalone; ground it in the captured meta/headings/forms/apiCalls. Never read a\npage count as coverage: if scan warns that pages still show a login form, you\nmeasured the wall, not the app. You CANNOT run `login` \u2014 it waits for a human\nat a browser; ask them to run it, then scan again.\n\nUsage:\n ia-qa-discover login Open a visible browser, log in by hand, keep the\n session (.ia-qa-discovery/session.json) for scan\n to reuse. The general answer to a login wall: it\n models nothing, so SSO, MFA, a consent screen or\n a magic link all work \u2014 you do them.\n Needs a terminal, refuses under CI. The file holds\n live cookies: gitignored, stays on this machine.\n --url <path> page to open (default: your baseUrl)\n --session <f> where to write it\n Already have a storageState (a Playwright globalSetup\n writes one)? Skip this verb: point scan at it with\n --session <file> or \"session\" in config.json.\n ia-qa-discover scan [url] Crawl same-origin pages from <url> (or the saved\n baseUrl) and capture each page's structure.\n Writes .ia-qa-discovery/capture/, diffs against\n the previous run, appends history.jsonl.\n --depth <n> crawl link-depth (default 2)\n --max <n> page cap (default 60)\n --deep also open menus, tabs and dialogs one level\n down and capture the fields they reveal. Every\n non-GET request is blocked for the duration,\n so a click cannot mutate anything server-side.\n --deep-budget <n> max controls clicked per page (default 60)\n --no-sitemap do not read sitemap.xml / robots.txt\n --no-reveal do not open menus/dropdowns for hidden links\n --strict-host treat www./apex as different hosts\n --session <file> reuse an existing storageState (login) file\n --no-network skip API/network-call capture this run\n --network-threshold <n> shared-call promotion threshold 0-1 (default 0.6)\n --save persist this baseUrl to config.json\n --json machine-readable summary on stdout\n --report [file.html] branded ia-qa.com HTML dossier of the\n app (default ia-qa-discover-report.html)\n --open open that report in the browser\n ia-qa-discover history Recorded scan runs \u2014 pages/forms/API-surface trend.\n --json --limit <n> (default 500)\n ia-qa-discover version Print the installed version (also --version, -v)\n ia-qa-discover help Show this help\n\nGlobal:\n --config <dir> Resolve .ia-qa-discovery/ under <dir> instead of the\n current directory. Also IAQA_DISCOVERY_CONFIG_DIR=<dir>.\n";
|
|
3
|
+
declare function verbHelp(command: string | undefined): string | null;
|
|
4
|
+
declare function main(): Promise<void>;
|
|
5
|
+
export { HELP, verbHelp, main };
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.HELP = void 0;
|
|
5
|
+
exports.verbHelp = verbHelp;
|
|
6
|
+
exports.main = main;
|
|
7
|
+
const config_1 = require("../config");
|
|
8
|
+
const args_1 = require("./args");
|
|
9
|
+
const scan_1 = require("./scan");
|
|
10
|
+
const login_1 = require("./login");
|
|
11
|
+
const history_1 = require("./history");
|
|
12
|
+
// dist/cli/ → package root; npm ships package.json in every tarball.
|
|
13
|
+
const VERSION = require('../../package.json').version;
|
|
14
|
+
const HELP = `ia-qa-discover — deterministic surface reconnaissance for a web app
|
|
15
|
+
|
|
16
|
+
F1 of QA Discovery: crawl an app's same-origin pages and capture what each one
|
|
17
|
+
is structurally about — title, headings, forms, and observed API calls. No
|
|
18
|
+
LLM, no verdict: this is recon, not a gate. Runs entirely on your machine;
|
|
19
|
+
nothing is uploaded anywhere.
|
|
20
|
+
|
|
21
|
+
[FOR AI AGENTS] You are driving this for a human. Run \`scan\` to capture the
|
|
22
|
+
app's surface, then read .ia-qa-discovery/capture/surface.json (the index) and
|
|
23
|
+
the per-page files it points at — that is the evidence for anything you
|
|
24
|
+
report about this app. Never assert a page's purpose or risk from the URL
|
|
25
|
+
alone; ground it in the captured meta/headings/forms/apiCalls. Never read a
|
|
26
|
+
page count as coverage: if scan warns that pages still show a login form, you
|
|
27
|
+
measured the wall, not the app. You CANNOT run \`login\` — it waits for a human
|
|
28
|
+
at a browser; ask them to run it, then scan again.
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
ia-qa-discover login Open a visible browser, log in by hand, keep the
|
|
32
|
+
session (.ia-qa-discovery/session.json) for scan
|
|
33
|
+
to reuse. The general answer to a login wall: it
|
|
34
|
+
models nothing, so SSO, MFA, a consent screen or
|
|
35
|
+
a magic link all work — you do them.
|
|
36
|
+
Needs a terminal, refuses under CI. The file holds
|
|
37
|
+
live cookies: gitignored, stays on this machine.
|
|
38
|
+
--url <path> page to open (default: your baseUrl)
|
|
39
|
+
--session <f> where to write it
|
|
40
|
+
Already have a storageState (a Playwright globalSetup
|
|
41
|
+
writes one)? Skip this verb: point scan at it with
|
|
42
|
+
--session <file> or "session" in config.json.
|
|
43
|
+
ia-qa-discover scan [url] Crawl same-origin pages from <url> (or the saved
|
|
44
|
+
baseUrl) and capture each page's structure.
|
|
45
|
+
Writes .ia-qa-discovery/capture/, diffs against
|
|
46
|
+
the previous run, appends history.jsonl.
|
|
47
|
+
--depth <n> crawl link-depth (default 2)
|
|
48
|
+
--max <n> page cap (default 60)
|
|
49
|
+
--deep also open menus, tabs and dialogs one level
|
|
50
|
+
down and capture the fields they reveal. Every
|
|
51
|
+
non-GET request is blocked for the duration,
|
|
52
|
+
so a click cannot mutate anything server-side.
|
|
53
|
+
--deep-budget <n> max controls clicked per page (default 60)
|
|
54
|
+
--no-sitemap do not read sitemap.xml / robots.txt
|
|
55
|
+
--no-reveal do not open menus/dropdowns for hidden links
|
|
56
|
+
--strict-host treat www./apex as different hosts
|
|
57
|
+
--session <file> reuse an existing storageState (login) file
|
|
58
|
+
--no-network skip API/network-call capture this run
|
|
59
|
+
--network-threshold <n> shared-call promotion threshold 0-1 (default 0.6)
|
|
60
|
+
--save persist this baseUrl to config.json
|
|
61
|
+
--json machine-readable summary on stdout
|
|
62
|
+
--report [file.html] branded ia-qa.com HTML dossier of the
|
|
63
|
+
app (default ia-qa-discover-report.html)
|
|
64
|
+
--open open that report in the browser
|
|
65
|
+
ia-qa-discover history Recorded scan runs — pages/forms/API-surface trend.
|
|
66
|
+
--json --limit <n> (default 500)
|
|
67
|
+
ia-qa-discover version Print the installed version (also --version, -v)
|
|
68
|
+
ia-qa-discover help Show this help
|
|
69
|
+
|
|
70
|
+
Global:
|
|
71
|
+
--config <dir> Resolve .ia-qa-discovery/ under <dir> instead of the
|
|
72
|
+
current directory. Also IAQA_DISCOVERY_CONFIG_DIR=<dir>.
|
|
73
|
+
`;
|
|
74
|
+
exports.HELP = HELP;
|
|
75
|
+
function extractConfigDir(argv) {
|
|
76
|
+
const rest = [];
|
|
77
|
+
let dir;
|
|
78
|
+
for (let i = 0; i < argv.length; i++) {
|
|
79
|
+
const a = argv[i];
|
|
80
|
+
if (a === '--config') {
|
|
81
|
+
const next = argv[i + 1];
|
|
82
|
+
if (next === undefined || next.startsWith('-')) {
|
|
83
|
+
throw new Error('--config needs a directory argument, e.g. --config apps/xsp.');
|
|
84
|
+
}
|
|
85
|
+
dir = next;
|
|
86
|
+
i++;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (a.startsWith('--config=')) {
|
|
90
|
+
dir = a.slice('--config='.length);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
rest.push(a);
|
|
94
|
+
}
|
|
95
|
+
return { dir, rest };
|
|
96
|
+
}
|
|
97
|
+
function verbHelp(command) {
|
|
98
|
+
if (!command || command.startsWith('-'))
|
|
99
|
+
return null;
|
|
100
|
+
const lines = HELP.split('\n');
|
|
101
|
+
const start = lines.findIndex((l) => l.startsWith(` ia-qa-discover ${command} `) || l === ` ia-qa-discover ${command}`);
|
|
102
|
+
if (start === -1)
|
|
103
|
+
return null;
|
|
104
|
+
const block = [lines[start]];
|
|
105
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
106
|
+
const l = lines[i];
|
|
107
|
+
if (/^\s{4,}\S/.test(l))
|
|
108
|
+
block.push(l);
|
|
109
|
+
else
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
const flags = args_1.KNOWN_FLAGS[command];
|
|
113
|
+
const tail = flags?.length ? `\n\nFlags: ${flags.join(' ')}` : '\n\n(no flags)';
|
|
114
|
+
return `${block.join('\n')}${tail}\n\nFull help: ia-qa-discover help`;
|
|
115
|
+
}
|
|
116
|
+
async function main() {
|
|
117
|
+
const { dir: configDir, rest: argv } = extractConfigDir(process.argv.slice(2));
|
|
118
|
+
if (configDir !== undefined) {
|
|
119
|
+
if (!configDir)
|
|
120
|
+
throw new Error('--config needs a directory argument.');
|
|
121
|
+
(0, config_1.setBaseDir)(configDir);
|
|
122
|
+
}
|
|
123
|
+
const [command, ...rest] = argv;
|
|
124
|
+
if (rest.includes('--help') || rest.includes('-h')) {
|
|
125
|
+
console.log(verbHelp(command) ?? HELP);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (command !== undefined)
|
|
129
|
+
(0, args_1.rejectUnknownFlags)(command, rest);
|
|
130
|
+
switch (command) {
|
|
131
|
+
case 'scan':
|
|
132
|
+
await (0, scan_1.runScan)(rest);
|
|
133
|
+
break;
|
|
134
|
+
case 'login':
|
|
135
|
+
await (0, login_1.runLogin)(rest);
|
|
136
|
+
break;
|
|
137
|
+
case 'history':
|
|
138
|
+
await (0, history_1.runHistory)(rest);
|
|
139
|
+
break;
|
|
140
|
+
case 'version':
|
|
141
|
+
case '--version':
|
|
142
|
+
case '-v':
|
|
143
|
+
console.log(VERSION);
|
|
144
|
+
break;
|
|
145
|
+
case 'help':
|
|
146
|
+
case '--help':
|
|
147
|
+
case '-h':
|
|
148
|
+
case undefined:
|
|
149
|
+
console.log(HELP);
|
|
150
|
+
break;
|
|
151
|
+
default:
|
|
152
|
+
console.error(`Unknown command "${command}".\n\n${HELP}`);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Only when this file IS the process.
|
|
158
|
+
*
|
|
159
|
+
* `main()` reads argv and can `process.exit`, so an unconditional call made
|
|
160
|
+
* this module impossible to import and therefore impossible to test — and the
|
|
161
|
+
* verb dispatch is exactly where a CLI/MCP divergence would hide. Same guard,
|
|
162
|
+
* and same reason, as `@ia-qa/self-healing`'s MCP server.
|
|
163
|
+
*/
|
|
164
|
+
if (require.main === module)
|
|
165
|
+
run();
|
|
166
|
+
function run() {
|
|
167
|
+
main().catch((err) => {
|
|
168
|
+
console.error(`\n❌ ${err instanceof Error ? err.message : String(err)}`);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ia-qa-discover login` — open a browser, let a human log in, keep the session.
|
|
3
|
+
*
|
|
4
|
+
* Same reasoning as `ia-qa-heal login`, and for the same reason: any
|
|
5
|
+
* declarative `auth` block models ONE shape of login (a username field, a
|
|
6
|
+
* password field, a submit button), so federated SSO, MFA, a consent screen, a
|
|
7
|
+
* magic link or a device check are each out of reach of every selector list.
|
|
8
|
+
* This verb models nothing at all — the human does whatever the app asks — so
|
|
9
|
+
* it is the general answer to a login wall.
|
|
10
|
+
*
|
|
11
|
+
* Measured need: on a real target, an anonymous scan reached 3 of 11 routes;
|
|
12
|
+
* everything worth discovering was behind the wall, and the app's catch-all
|
|
13
|
+
* route served the login page at every unknown URL with HTTP 200 — so the scan
|
|
14
|
+
* reported "3 pages captured" and looked like a success.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately not automatic, and never part of a pipeline:
|
|
17
|
+
* - The only caller here that opens a VISIBLE browser. Every capture path
|
|
18
|
+
* stays headless: a window in CI is a hung build.
|
|
19
|
+
* - Refuses without a TTY and under CI — it waits for a person twice over (at
|
|
20
|
+
* the browser, then at the keyboard), so a pipeline that runs it just hangs.
|
|
21
|
+
* That refusal is aimed squarely at an agent driving this CLI: it cannot
|
|
22
|
+
* perform this step, and must ask the human to.
|
|
23
|
+
* - What it writes is a SECRET (live cookies). The path and that fact are
|
|
24
|
+
* announced BEFORE the browser opens, and `.ia-qa-discovery/.gitignore` is
|
|
25
|
+
* written on the way out. Nothing here uploads it.
|
|
26
|
+
*
|
|
27
|
+
* Not an MCP tool, on purpose. See `mcp/server.ts`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function runLogin(args: string[]): Promise<void>;
|