@ia-qa/self-healing 1.6.7 → 1.6.8
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 +94 -0
- package/TUTORIAL.md +112 -2
- package/assets/logoKawaiiGreenTiny.png +0 -0
- package/dist/cli/args.js +4 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/diff.d.ts +6 -1
- package/dist/cli/diff.js +114 -4
- package/dist/cli/diff.js.map +1 -1
- package/dist/cli/emit.d.ts +71 -0
- package/dist/cli/emit.js +150 -0
- package/dist/cli/emit.js.map +1 -0
- package/dist/cli/fix.js +36 -0
- package/dist/cli/fix.js.map +1 -1
- package/dist/cli/history.d.ts +12 -0
- package/dist/cli/history.js +95 -0
- package/dist/cli/history.js.map +1 -0
- package/dist/cli/index.js +53 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +32 -0
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/map.js +60 -0
- package/dist/cli/map.js.map +1 -1
- package/dist/cli/run.js +77 -7
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/ui.d.ts +16 -0
- package/dist/cli/ui.js +275 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/evidence.d.ts +130 -0
- package/dist/evidence.js +232 -0
- package/dist/evidence.js.map +1 -0
- package/dist/history.d.ts +145 -0
- package/dist/history.js +230 -0
- package/dist/history.js.map +1 -0
- package/dist/htmlReport.d.ts +47 -0
- package/dist/htmlReport.js +76 -1
- package/dist/htmlReport.js.map +1 -1
- package/dist/junit.d.ts +53 -0
- package/dist/junit.js +150 -0
- package/dist/junit.js.map +1 -0
- package/dist/mcp/server.js +41 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/ui/icon.d.ts +41 -0
- package/dist/ui/icon.js +115 -0
- package/dist/ui/icon.js.map +1 -0
- package/dist/ui/lock.d.ts +40 -0
- package/dist/ui/lock.js +138 -0
- package/dist/ui/lock.js.map +1 -0
- package/dist/ui/page.d.ts +1 -0
- package/dist/ui/page.js +584 -0
- package/dist/ui/page.js.map +1 -0
- package/dist/ui/registry.d.ts +62 -0
- package/dist/ui/registry.js +139 -0
- package/dist/ui/registry.js.map +1 -0
- package/dist/ui/server.d.ts +67 -0
- package/dist/ui/server.js +604 -0
- package/dist/ui/server.js.map +1 -0
- package/dist/ui/shortcut.d.ts +90 -0
- package/dist/ui/shortcut.js +176 -0
- package/dist/ui/shortcut.js.map +1 -0
- package/package.json +2 -1
- package/skills/ia-qa-heal/SKILL.md +27 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The list of projects this machine's console may switch between —
|
|
3
|
+
* `~/.ia-qa/projects.json`.
|
|
4
|
+
*
|
|
5
|
+
* A single console that can reach several projects is a bigger thing than one
|
|
6
|
+
* bound to a directory: its token now unlocks the contracts of *every* app in
|
|
7
|
+
* the list, and its actions can edit test files in any of them. That is a real
|
|
8
|
+
* widening, and it is contained by exactly one rule, which the rest of this
|
|
9
|
+
* file exists to enforce:
|
|
10
|
+
*
|
|
11
|
+
* **A project is reachable only if the user opened a console in it.**
|
|
12
|
+
*
|
|
13
|
+
* Nothing scans the disk, nothing walks parent directories, nothing accepts a
|
|
14
|
+
* path from an HTTP request. The registry is append-on-launch: running
|
|
15
|
+
* `ia-qa-heal ui` in a folder is the act of consent, and `--forget` is how it is
|
|
16
|
+
* withdrawn. So the console can reach precisely the set of projects the user
|
|
17
|
+
* already had consoles for — the switcher removes the walking between them, not
|
|
18
|
+
* a boundary.
|
|
19
|
+
*
|
|
20
|
+
* Entries are verified on read: an entry whose `.ia-qa/config.json` is gone is
|
|
21
|
+
* dropped rather than offered, so a deleted or moved project cannot linger as a
|
|
22
|
+
* reachable path.
|
|
23
|
+
*/
|
|
24
|
+
export interface ProjectEntry {
|
|
25
|
+
/** Absolute, normalized. The identity of a project — there is no other id. */
|
|
26
|
+
dir: string;
|
|
27
|
+
/** Last known baseUrl, shown in the switcher so two localhost projects are distinguishable. */
|
|
28
|
+
baseUrl?: string;
|
|
29
|
+
lastOpened: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `IAQA_HOME` relocates the registry. It exists so the console's own tests can
|
|
33
|
+
* exercise the real code path — including the HTTP endpoint, which takes no
|
|
34
|
+
* home argument — without writing to the machine's actual project list. It is
|
|
35
|
+
* also the lever for a shared or throwaway machine.
|
|
36
|
+
*/
|
|
37
|
+
export declare function registryDir(home?: string): string;
|
|
38
|
+
export declare function registryPath(home?: string): string;
|
|
39
|
+
/** Does this directory actually hold a project? The only thing that makes one. */
|
|
40
|
+
export declare function isProjectDir(dir: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Every registered project that still exists, most recently opened first.
|
|
43
|
+
*
|
|
44
|
+
* The existence check is not tidiness — it is the second half of the allowlist.
|
|
45
|
+
* A stale entry is a path the console would otherwise still be willing to read.
|
|
46
|
+
*/
|
|
47
|
+
export declare function listProjects(home?: string): ProjectEntry[];
|
|
48
|
+
/** Record that a console was opened here. The one act that grants reachability. */
|
|
49
|
+
export declare function rememberProject(dir: string, baseUrl?: string, home?: string): void;
|
|
50
|
+
/** Withdraw consent — `ia-qa-heal ui --forget [dir]`. */
|
|
51
|
+
export declare function forgetProject(dir: string | null, home?: string): number;
|
|
52
|
+
/**
|
|
53
|
+
* Resolve a directory the *client* asked for.
|
|
54
|
+
*
|
|
55
|
+
* This is the security boundary of the whole feature, so it is deliberately
|
|
56
|
+
* dull: the candidate must match a registered entry exactly after
|
|
57
|
+
* normalization. It never joins, never resolves relative to anything, and never
|
|
58
|
+
* falls back to "close enough" — a request cannot name a directory that is not
|
|
59
|
+
* already in the list, which makes path traversal not a thing to defend against
|
|
60
|
+
* but a thing that cannot be expressed.
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveRequestedProject(requested: unknown, home?: string): string | null;
|
|
@@ -0,0 +1,139 @@
|
|
|
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.registryDir = registryDir;
|
|
37
|
+
exports.registryPath = registryPath;
|
|
38
|
+
exports.isProjectDir = isProjectDir;
|
|
39
|
+
exports.listProjects = listProjects;
|
|
40
|
+
exports.rememberProject = rememberProject;
|
|
41
|
+
exports.forgetProject = forgetProject;
|
|
42
|
+
exports.resolveRequestedProject = resolveRequestedProject;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const os = __importStar(require("os"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const config_1 = require("../config");
|
|
47
|
+
/**
|
|
48
|
+
* `IAQA_HOME` relocates the registry. It exists so the console's own tests can
|
|
49
|
+
* exercise the real code path — including the HTTP endpoint, which takes no
|
|
50
|
+
* home argument — without writing to the machine's actual project list. It is
|
|
51
|
+
* also the lever for a shared or throwaway machine.
|
|
52
|
+
*/
|
|
53
|
+
function registryDir(home) {
|
|
54
|
+
return path.join(home ?? process.env.IAQA_HOME ?? os.homedir(), config_1.CONFIG_DIR);
|
|
55
|
+
}
|
|
56
|
+
function registryPath(home) {
|
|
57
|
+
return path.join(registryDir(home), 'projects.json');
|
|
58
|
+
}
|
|
59
|
+
function readRaw(home) {
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(fs.readFileSync(registryPath(home), 'utf8'));
|
|
62
|
+
return Array.isArray(parsed.projects) ? parsed.projects : [];
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Does this directory actually hold a project? The only thing that makes one. */
|
|
69
|
+
function isProjectDir(dir) {
|
|
70
|
+
try {
|
|
71
|
+
return fs.statSync(path.join(dir, config_1.CONFIG_DIR, 'config.json')).isFile();
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Every registered project that still exists, most recently opened first.
|
|
79
|
+
*
|
|
80
|
+
* The existence check is not tidiness — it is the second half of the allowlist.
|
|
81
|
+
* A stale entry is a path the console would otherwise still be willing to read.
|
|
82
|
+
*/
|
|
83
|
+
function listProjects(home) {
|
|
84
|
+
return readRaw(home)
|
|
85
|
+
.filter((p) => p && typeof p.dir === 'string' && isProjectDir(p.dir))
|
|
86
|
+
.sort((a, b) => String(b.lastOpened).localeCompare(String(a.lastOpened)));
|
|
87
|
+
}
|
|
88
|
+
/** Record that a console was opened here. The one act that grants reachability. */
|
|
89
|
+
function rememberProject(dir, baseUrl, home) {
|
|
90
|
+
const abs = path.resolve(dir);
|
|
91
|
+
if (!isProjectDir(abs))
|
|
92
|
+
return;
|
|
93
|
+
try {
|
|
94
|
+
const others = readRaw(home).filter((p) => path.resolve(p.dir ?? '') !== abs);
|
|
95
|
+
const next = [{ dir: abs, baseUrl, lastOpened: new Date().toISOString() }, ...others];
|
|
96
|
+
fs.mkdirSync(registryDir(home), { recursive: true });
|
|
97
|
+
fs.writeFileSync(registryPath(home), JSON.stringify({ projects: next }, null, 2) + '\n', {
|
|
98
|
+
encoding: 'utf8',
|
|
99
|
+
mode: 0o600,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
/* an unregisterable project simply cannot be switched to; nothing else breaks */
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Withdraw consent — `ia-qa-heal ui --forget [dir]`. */
|
|
107
|
+
function forgetProject(dir, home) {
|
|
108
|
+
const before = readRaw(home);
|
|
109
|
+
const keep = dir === null ? [] : before.filter((p) => path.resolve(p.dir ?? '') !== path.resolve(dir));
|
|
110
|
+
try {
|
|
111
|
+
fs.mkdirSync(registryDir(home), { recursive: true });
|
|
112
|
+
fs.writeFileSync(registryPath(home), JSON.stringify({ projects: keep }, null, 2) + '\n', {
|
|
113
|
+
encoding: 'utf8',
|
|
114
|
+
mode: 0o600,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
return before.length - keep.length;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Resolve a directory the *client* asked for.
|
|
124
|
+
*
|
|
125
|
+
* This is the security boundary of the whole feature, so it is deliberately
|
|
126
|
+
* dull: the candidate must match a registered entry exactly after
|
|
127
|
+
* normalization. It never joins, never resolves relative to anything, and never
|
|
128
|
+
* falls back to "close enough" — a request cannot name a directory that is not
|
|
129
|
+
* already in the list, which makes path traversal not a thing to defend against
|
|
130
|
+
* but a thing that cannot be expressed.
|
|
131
|
+
*/
|
|
132
|
+
function resolveRequestedProject(requested, home) {
|
|
133
|
+
if (typeof requested !== 'string' || requested === '')
|
|
134
|
+
return null;
|
|
135
|
+
const want = path.resolve(requested);
|
|
136
|
+
const hit = listProjects(home).find((p) => path.resolve(p.dir) === want);
|
|
137
|
+
return hit ? path.resolve(hit.dir) : null;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/ui/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The local console — `ia-qa-heal ui`.
|
|
3
|
+
*
|
|
4
|
+
* What it is for: the HTML report is a *read* artefact. It can now show a human
|
|
5
|
+
* which of five identical buttons the test meant, but the moment they have
|
|
6
|
+
* decided, the loop leaves the browser — back to a terminal, by hand. The
|
|
7
|
+
* console closes that: the same evidence, plus the verbs, plus the run history
|
|
8
|
+
* the CLI has no place to display.
|
|
9
|
+
*
|
|
10
|
+
* ## Why a server, and what that costs
|
|
11
|
+
*
|
|
12
|
+
* A static file cannot run `fix` and cannot re-diff after the app changed. That
|
|
13
|
+
* is the entire justification, and it is worth being explicit that a server on a
|
|
14
|
+
* QA's machine is not free. Four rules hold the line:
|
|
15
|
+
*
|
|
16
|
+
* 1. **Loopback only.** Bound to `127.0.0.1`, never `0.0.0.0` — in a container
|
|
17
|
+
* or on a shared network the second one is a published port.
|
|
18
|
+
* 2. **A per-session token.** Without it, *any* page open in that browser could
|
|
19
|
+
* `fetch('http://127.0.0.1:7333/api/state')` and read the contracts of the
|
|
20
|
+
* user's authenticated app, or POST an action. The token is random per
|
|
21
|
+
* process, required on every request, and lives only in the URL we open.
|
|
22
|
+
* 3. **Host header pinning.** A token in a URL does not stop DNS rebinding, in
|
|
23
|
+
* which an attacker's domain resolves to 127.0.0.1 and the browser treats the
|
|
24
|
+
* origin as theirs. Requests whose `Host` is not loopback are refused before
|
|
25
|
+
* anything is read.
|
|
26
|
+
* 4. **No new authority.** Actions spawn this package's own CLI with a fixed
|
|
27
|
+
* argv from an allowlist — never a string from the request. The console can
|
|
28
|
+
* therefore do nothing the user could not do at their shell, and every
|
|
29
|
+
* prohibition the CLI enforces (`fix` refusing an `unattributable` locator,
|
|
30
|
+
* refusing to guess an `ambiguous` one) holds without being restated here.
|
|
31
|
+
* A button that could bypass them would be the worst thing in this package.
|
|
32
|
+
*
|
|
33
|
+
* Nothing is uploaded, no telemetry, no external asset: the page is inline HTML,
|
|
34
|
+
* CSS and JS, and every byte it displays came off this disk. The server dies
|
|
35
|
+
* with the process.
|
|
36
|
+
*/
|
|
37
|
+
export interface UiOptions {
|
|
38
|
+
port?: number;
|
|
39
|
+
open?: boolean;
|
|
40
|
+
/** Stay up after the browser goes away. Off by default — see `IDLE_*` below. */
|
|
41
|
+
keepAlive?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/** How often the page says it is still there. Must stay well under `HEARTBEAT_IDLE_MS`. */
|
|
44
|
+
export declare const HEARTBEAT_INTERVAL_MS = 15000;
|
|
45
|
+
export declare function actionCatalogue(): Array<{
|
|
46
|
+
id: string;
|
|
47
|
+
label: string;
|
|
48
|
+
writes: boolean;
|
|
49
|
+
means: string;
|
|
50
|
+
}>;
|
|
51
|
+
/**
|
|
52
|
+
* Loopback check on the `Host` header — the defence a token cannot provide.
|
|
53
|
+
* An IPv6 literal arrives bracketed (`[::1]:7333`), which is why this is not a
|
|
54
|
+
* bare `startsWith`.
|
|
55
|
+
*/
|
|
56
|
+
export declare function isLoopbackHost(host: string | undefined): boolean;
|
|
57
|
+
export declare function setActiveProject(dir: string): void;
|
|
58
|
+
export declare function getActiveProject(): string;
|
|
59
|
+
export declare function startUi(opts?: UiOptions): Promise<{
|
|
60
|
+
url: string;
|
|
61
|
+
close: () => void;
|
|
62
|
+
onIdle: (fn: () => void) => void;
|
|
63
|
+
}>;
|
|
64
|
+
export declare function interpret(output: string, code: number): {
|
|
65
|
+
tone: string;
|
|
66
|
+
headline: string;
|
|
67
|
+
};
|