qa-engineer 0.10.0 → 0.11.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 +167 -59
- package/package.json +1 -1
- package/packages/engine/bin/qa-engine.mjs +232 -8
- package/packages/engine/lib/analysis/har.mjs +34 -0
- package/packages/engine/lib/analysis/network.mjs +237 -0
- package/packages/engine/lib/analysis/report-html.mjs +47 -734
- package/packages/engine/lib/artifacts/manager.mjs +453 -0
- package/packages/engine/lib/artifacts/mime.mjs +109 -0
- package/packages/engine/lib/artifacts/zip-write.mjs +143 -0
- package/packages/engine/lib/report/components/charts.mjs +424 -0
- package/packages/engine/lib/report/components/evidence.mjs +207 -0
- package/packages/engine/lib/report/components/findings.mjs +258 -0
- package/packages/engine/lib/report/components/nav.mjs +99 -0
- package/packages/engine/lib/report/components/primitives.mjs +246 -0
- package/packages/engine/lib/report/components/runtime.mjs +246 -0
- package/packages/engine/lib/report/components/timeline.mjs +65 -0
- package/packages/engine/lib/report/core/model.mjs +270 -0
- package/packages/engine/lib/report/core/normalize.mjs +226 -0
- package/packages/engine/lib/report/core/sections.mjs +978 -0
- package/packages/engine/lib/report/export/bundle.mjs +293 -0
- package/packages/engine/lib/report/export/html.mjs +183 -0
- package/packages/engine/lib/report/export/machine.mjs +290 -0
- package/packages/engine/lib/report/export/markdown.mjs +323 -0
- package/packages/engine/lib/report/schemas/qa-report.schema.json +555 -0
- package/packages/engine/lib/report/theme/css.mjs +529 -0
- package/packages/engine/lib/report/theme/tokens.mjs +137 -0
- package/packages/engine/lib/report/version.mjs +78 -0
- package/packages/engine/package.json +2 -2
- package/packages/installer/lib/agents/targets.mjs +90 -0
- package/packages/installer/lib/agents/user-level.mjs +80 -0
- package/packages/installer/lib/cli/flags.mjs +20 -2
- package/packages/installer/lib/commands/doctor.mjs +6 -4
- package/packages/installer/lib/commands/install.mjs +134 -93
- package/packages/installer/lib/commands/repair.mjs +10 -6
- package/packages/installer/lib/commands/self-test.mjs +4 -3
- package/packages/installer/lib/commands/uninstall.mjs +14 -8
- package/packages/installer/lib/commands/update.mjs +9 -4
- package/packages/installer/lib/commands/verify.mjs +13 -12
- package/packages/installer/lib/constants.mjs +13 -0
- package/packages/installer/lib/core/conflict.mjs +5 -4
- package/packages/installer/lib/core/fs-safe.mjs +146 -6
- package/packages/installer/lib/core/integrity.mjs +59 -0
- package/packages/installer/lib/core/lockfile.mjs +19 -3
- package/packages/installer/lib/core/plan.mjs +213 -0
- package/packages/installer/lib/core/qa-home.mjs +145 -0
- package/packages/installer/lib/core/scope.mjs +274 -0
- package/packages/installer/lib/core/validate-install.mjs +37 -12
- package/packages/installer/package.json +1 -1
- package/packages/installer/schemas/qa-lock.schema.json +119 -21
- package/shared/tooling/qa-tool.mjs +41 -5
- package/skills/qa-api/scripts/qa-tool.mjs +41 -5
- package/skills/qa-audit/scripts/qa-tool.mjs +41 -5
- package/skills/qa-debug/scripts/qa-tool.mjs +41 -5
- package/skills/qa-explore/SKILL.md +26 -10
- package/skills/qa-explore/contracts/explore-result.schema.json +517 -11
- package/skills/qa-explore/references/api-replay.md +40 -1
- package/skills/qa-explore/references/report-pipeline.md +265 -95
- package/skills/qa-explore/scripts/qa-tool.mjs +41 -5
- package/skills/qa-fix/scripts/qa-tool.mjs +41 -5
- package/skills/qa-flaky/scripts/qa-tool.mjs +41 -5
- package/skills/qa-init/scripts/qa-tool.mjs +41 -5
- package/skills/qa-report/scripts/qa-tool.mjs +41 -5
- package/skills/qa-run/scripts/qa-tool.mjs +41 -5
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
// `node qa-tool.mjs …`, which needs none.
|
|
30
30
|
|
|
31
31
|
import fs from 'node:fs';
|
|
32
|
+
import os from 'node:os';
|
|
32
33
|
import path from 'node:path';
|
|
33
34
|
import { spawnSync } from 'node:child_process';
|
|
34
35
|
import { fileURLToPath } from 'node:url';
|
|
@@ -38,7 +39,7 @@ const here = path.dirname(fileURLToPath(import.meta.url));
|
|
|
38
39
|
const USAGE = `usage: node qa-tool.mjs <tool> <subcommand> [args]
|
|
39
40
|
|
|
40
41
|
analysis parse artifacts, classify errors, validate contracts, diff-guard,
|
|
41
|
-
|
|
42
|
+
read .qa/context.md, render an HTML report, print the footer
|
|
42
43
|
diagnostics root cause, timeline, priority, repair plans, release readiness
|
|
43
44
|
playwright normalize a Playwright report or summarize a trace
|
|
44
45
|
|
|
@@ -53,15 +54,26 @@ examples:
|
|
|
53
54
|
/**
|
|
54
55
|
* Where the engine is, and how we found it.
|
|
55
56
|
*
|
|
56
|
-
* Ordered by cost: a bundled copy needs no resolution and no network, a
|
|
57
|
-
* node_modules copy needs no network, and npx needs both
|
|
58
|
-
* *which* one answered matters when a skill degrades — "the
|
|
59
|
-
* "the engine is being fetched" are different problems.
|
|
57
|
+
* Ordered by cost: a bundled copy needs no resolution and no network, a shared copy
|
|
58
|
+
* needs one stat per ancestor, a node_modules copy needs no network, and npx needs both
|
|
59
|
+
* on first use. Reporting *which* one answered matters when a skill degrades — "the
|
|
60
|
+
* engine is missing" and "the engine is being fetched" are different problems.
|
|
61
|
+
*
|
|
62
|
+
* The shared lookup is what lets one skill directory serve a project, a workspace, and a
|
|
63
|
+
* machine-wide install without knowing which installed it. Walking up for
|
|
64
|
+
* `.qa-engineer/engine` finds a workspace install from a skill inside the repository,
|
|
65
|
+
* and finds a global install from a skill linked into an agent's user-level directory —
|
|
66
|
+
* `~/.claude/skills/qa-explore` walks up to `~`, where `~/.qa-engineer/engine` is.
|
|
60
67
|
*/
|
|
61
68
|
function resolveEngine() {
|
|
62
69
|
const bundled = path.join(here, 'lib', 'bin', 'qa-engine.mjs');
|
|
63
70
|
if (fs.existsSync(bundled)) return { kind: 'bundled', command: [process.execPath, bundled] };
|
|
64
71
|
|
|
72
|
+
for (const root of candidateSharedRoots()) {
|
|
73
|
+
const shared = path.join(root, 'bin', 'qa-engine.mjs');
|
|
74
|
+
if (fs.existsSync(shared)) return { kind: 'shared', command: [process.execPath, shared] };
|
|
75
|
+
}
|
|
76
|
+
|
|
65
77
|
for (const base of candidateModuleRoots()) {
|
|
66
78
|
const installed = path.join(base, 'qa-engineer', 'packages', 'engine', 'bin', 'qa-engine.mjs');
|
|
67
79
|
if (fs.existsSync(installed)) {
|
|
@@ -75,6 +87,30 @@ function resolveEngine() {
|
|
|
75
87
|
};
|
|
76
88
|
}
|
|
77
89
|
|
|
90
|
+
/** Shared engine directories worth checking, most specific first. */
|
|
91
|
+
function candidateSharedRoots() {
|
|
92
|
+
const roots = [];
|
|
93
|
+
|
|
94
|
+
// An explicit home wins over anything discovered, so a user who moved the install can
|
|
95
|
+
// rely on it rather than on whatever the walk happens to find first.
|
|
96
|
+
const override = process.env.QA_ENGINEER_HOME;
|
|
97
|
+
if (override && override.trim()) roots.push(path.join(path.resolve(override.trim()), 'engine'));
|
|
98
|
+
|
|
99
|
+
let dir = here;
|
|
100
|
+
for (let depth = 0; depth < 12; depth += 1) {
|
|
101
|
+
roots.push(path.join(dir, '.qa-engineer', 'engine'));
|
|
102
|
+
const parent = path.dirname(dir);
|
|
103
|
+
if (parent === dir) break;
|
|
104
|
+
dir = parent;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// The default machine home, for the case where the skill lives outside it entirely.
|
|
108
|
+
const home = os.homedir();
|
|
109
|
+
if (home) roots.push(path.join(home, '.qa-engineer', 'engine'));
|
|
110
|
+
|
|
111
|
+
return roots;
|
|
112
|
+
}
|
|
113
|
+
|
|
78
114
|
/** node_modules directories worth checking, nearest first. */
|
|
79
115
|
function candidateModuleRoots() {
|
|
80
116
|
const roots = [];
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
// `node qa-tool.mjs …`, which needs none.
|
|
30
30
|
|
|
31
31
|
import fs from 'node:fs';
|
|
32
|
+
import os from 'node:os';
|
|
32
33
|
import path from 'node:path';
|
|
33
34
|
import { spawnSync } from 'node:child_process';
|
|
34
35
|
import { fileURLToPath } from 'node:url';
|
|
@@ -38,7 +39,7 @@ const here = path.dirname(fileURLToPath(import.meta.url));
|
|
|
38
39
|
const USAGE = `usage: node qa-tool.mjs <tool> <subcommand> [args]
|
|
39
40
|
|
|
40
41
|
analysis parse artifacts, classify errors, validate contracts, diff-guard,
|
|
41
|
-
|
|
42
|
+
read .qa/context.md, render an HTML report, print the footer
|
|
42
43
|
diagnostics root cause, timeline, priority, repair plans, release readiness
|
|
43
44
|
playwright normalize a Playwright report or summarize a trace
|
|
44
45
|
|
|
@@ -53,15 +54,26 @@ examples:
|
|
|
53
54
|
/**
|
|
54
55
|
* Where the engine is, and how we found it.
|
|
55
56
|
*
|
|
56
|
-
* Ordered by cost: a bundled copy needs no resolution and no network, a
|
|
57
|
-
* node_modules copy needs no network, and npx needs both
|
|
58
|
-
* *which* one answered matters when a skill degrades — "the
|
|
59
|
-
* "the engine is being fetched" are different problems.
|
|
57
|
+
* Ordered by cost: a bundled copy needs no resolution and no network, a shared copy
|
|
58
|
+
* needs one stat per ancestor, a node_modules copy needs no network, and npx needs both
|
|
59
|
+
* on first use. Reporting *which* one answered matters when a skill degrades — "the
|
|
60
|
+
* engine is missing" and "the engine is being fetched" are different problems.
|
|
61
|
+
*
|
|
62
|
+
* The shared lookup is what lets one skill directory serve a project, a workspace, and a
|
|
63
|
+
* machine-wide install without knowing which installed it. Walking up for
|
|
64
|
+
* `.qa-engineer/engine` finds a workspace install from a skill inside the repository,
|
|
65
|
+
* and finds a global install from a skill linked into an agent's user-level directory —
|
|
66
|
+
* `~/.claude/skills/qa-explore` walks up to `~`, where `~/.qa-engineer/engine` is.
|
|
60
67
|
*/
|
|
61
68
|
function resolveEngine() {
|
|
62
69
|
const bundled = path.join(here, 'lib', 'bin', 'qa-engine.mjs');
|
|
63
70
|
if (fs.existsSync(bundled)) return { kind: 'bundled', command: [process.execPath, bundled] };
|
|
64
71
|
|
|
72
|
+
for (const root of candidateSharedRoots()) {
|
|
73
|
+
const shared = path.join(root, 'bin', 'qa-engine.mjs');
|
|
74
|
+
if (fs.existsSync(shared)) return { kind: 'shared', command: [process.execPath, shared] };
|
|
75
|
+
}
|
|
76
|
+
|
|
65
77
|
for (const base of candidateModuleRoots()) {
|
|
66
78
|
const installed = path.join(base, 'qa-engineer', 'packages', 'engine', 'bin', 'qa-engine.mjs');
|
|
67
79
|
if (fs.existsSync(installed)) {
|
|
@@ -75,6 +87,30 @@ function resolveEngine() {
|
|
|
75
87
|
};
|
|
76
88
|
}
|
|
77
89
|
|
|
90
|
+
/** Shared engine directories worth checking, most specific first. */
|
|
91
|
+
function candidateSharedRoots() {
|
|
92
|
+
const roots = [];
|
|
93
|
+
|
|
94
|
+
// An explicit home wins over anything discovered, so a user who moved the install can
|
|
95
|
+
// rely on it rather than on whatever the walk happens to find first.
|
|
96
|
+
const override = process.env.QA_ENGINEER_HOME;
|
|
97
|
+
if (override && override.trim()) roots.push(path.join(path.resolve(override.trim()), 'engine'));
|
|
98
|
+
|
|
99
|
+
let dir = here;
|
|
100
|
+
for (let depth = 0; depth < 12; depth += 1) {
|
|
101
|
+
roots.push(path.join(dir, '.qa-engineer', 'engine'));
|
|
102
|
+
const parent = path.dirname(dir);
|
|
103
|
+
if (parent === dir) break;
|
|
104
|
+
dir = parent;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// The default machine home, for the case where the skill lives outside it entirely.
|
|
108
|
+
const home = os.homedir();
|
|
109
|
+
if (home) roots.push(path.join(home, '.qa-engineer', 'engine'));
|
|
110
|
+
|
|
111
|
+
return roots;
|
|
112
|
+
}
|
|
113
|
+
|
|
78
114
|
/** node_modules directories worth checking, nearest first. */
|
|
79
115
|
function candidateModuleRoots() {
|
|
80
116
|
const roots = [];
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
// `node qa-tool.mjs …`, which needs none.
|
|
30
30
|
|
|
31
31
|
import fs from 'node:fs';
|
|
32
|
+
import os from 'node:os';
|
|
32
33
|
import path from 'node:path';
|
|
33
34
|
import { spawnSync } from 'node:child_process';
|
|
34
35
|
import { fileURLToPath } from 'node:url';
|
|
@@ -38,7 +39,7 @@ const here = path.dirname(fileURLToPath(import.meta.url));
|
|
|
38
39
|
const USAGE = `usage: node qa-tool.mjs <tool> <subcommand> [args]
|
|
39
40
|
|
|
40
41
|
analysis parse artifacts, classify errors, validate contracts, diff-guard,
|
|
41
|
-
|
|
42
|
+
read .qa/context.md, render an HTML report, print the footer
|
|
42
43
|
diagnostics root cause, timeline, priority, repair plans, release readiness
|
|
43
44
|
playwright normalize a Playwright report or summarize a trace
|
|
44
45
|
|
|
@@ -53,15 +54,26 @@ examples:
|
|
|
53
54
|
/**
|
|
54
55
|
* Where the engine is, and how we found it.
|
|
55
56
|
*
|
|
56
|
-
* Ordered by cost: a bundled copy needs no resolution and no network, a
|
|
57
|
-
* node_modules copy needs no network, and npx needs both
|
|
58
|
-
* *which* one answered matters when a skill degrades — "the
|
|
59
|
-
* "the engine is being fetched" are different problems.
|
|
57
|
+
* Ordered by cost: a bundled copy needs no resolution and no network, a shared copy
|
|
58
|
+
* needs one stat per ancestor, a node_modules copy needs no network, and npx needs both
|
|
59
|
+
* on first use. Reporting *which* one answered matters when a skill degrades — "the
|
|
60
|
+
* engine is missing" and "the engine is being fetched" are different problems.
|
|
61
|
+
*
|
|
62
|
+
* The shared lookup is what lets one skill directory serve a project, a workspace, and a
|
|
63
|
+
* machine-wide install without knowing which installed it. Walking up for
|
|
64
|
+
* `.qa-engineer/engine` finds a workspace install from a skill inside the repository,
|
|
65
|
+
* and finds a global install from a skill linked into an agent's user-level directory —
|
|
66
|
+
* `~/.claude/skills/qa-explore` walks up to `~`, where `~/.qa-engineer/engine` is.
|
|
60
67
|
*/
|
|
61
68
|
function resolveEngine() {
|
|
62
69
|
const bundled = path.join(here, 'lib', 'bin', 'qa-engine.mjs');
|
|
63
70
|
if (fs.existsSync(bundled)) return { kind: 'bundled', command: [process.execPath, bundled] };
|
|
64
71
|
|
|
72
|
+
for (const root of candidateSharedRoots()) {
|
|
73
|
+
const shared = path.join(root, 'bin', 'qa-engine.mjs');
|
|
74
|
+
if (fs.existsSync(shared)) return { kind: 'shared', command: [process.execPath, shared] };
|
|
75
|
+
}
|
|
76
|
+
|
|
65
77
|
for (const base of candidateModuleRoots()) {
|
|
66
78
|
const installed = path.join(base, 'qa-engineer', 'packages', 'engine', 'bin', 'qa-engine.mjs');
|
|
67
79
|
if (fs.existsSync(installed)) {
|
|
@@ -75,6 +87,30 @@ function resolveEngine() {
|
|
|
75
87
|
};
|
|
76
88
|
}
|
|
77
89
|
|
|
90
|
+
/** Shared engine directories worth checking, most specific first. */
|
|
91
|
+
function candidateSharedRoots() {
|
|
92
|
+
const roots = [];
|
|
93
|
+
|
|
94
|
+
// An explicit home wins over anything discovered, so a user who moved the install can
|
|
95
|
+
// rely on it rather than on whatever the walk happens to find first.
|
|
96
|
+
const override = process.env.QA_ENGINEER_HOME;
|
|
97
|
+
if (override && override.trim()) roots.push(path.join(path.resolve(override.trim()), 'engine'));
|
|
98
|
+
|
|
99
|
+
let dir = here;
|
|
100
|
+
for (let depth = 0; depth < 12; depth += 1) {
|
|
101
|
+
roots.push(path.join(dir, '.qa-engineer', 'engine'));
|
|
102
|
+
const parent = path.dirname(dir);
|
|
103
|
+
if (parent === dir) break;
|
|
104
|
+
dir = parent;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// The default machine home, for the case where the skill lives outside it entirely.
|
|
108
|
+
const home = os.homedir();
|
|
109
|
+
if (home) roots.push(path.join(home, '.qa-engineer', 'engine'));
|
|
110
|
+
|
|
111
|
+
return roots;
|
|
112
|
+
}
|
|
113
|
+
|
|
78
114
|
/** node_modules directories worth checking, nearest first. */
|
|
79
115
|
function candidateModuleRoots() {
|
|
80
116
|
const roots = [];
|