@ivorycanvas/qamap 0.3.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/CHANGELOG.md +93 -0
- package/LICENSE +21 -0
- package/README.md +636 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +879 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +114 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +1 -0
- package/dist/context.js +126 -0
- package/dist/context.js.map +1 -0
- package/dist/doctor.d.ts +32 -0
- package/dist/doctor.js +200 -0
- package/dist/doctor.js.map +1 -0
- package/dist/domain-language.d.ts +25 -0
- package/dist/domain-language.js +655 -0
- package/dist/domain-language.js.map +1 -0
- package/dist/domains.d.ts +33 -0
- package/dist/domains.js +258 -0
- package/dist/domains.js.map +1 -0
- package/dist/e2e.d.ts +326 -0
- package/dist/e2e.js +6869 -0
- package/dist/e2e.js.map +1 -0
- package/dist/eval.d.ts +40 -0
- package/dist/eval.js +374 -0
- package/dist/eval.js.map +1 -0
- package/dist/flows.d.ts +32 -0
- package/dist/flows.js +246 -0
- package/dist/flows.js.map +1 -0
- package/dist/fs.d.ts +7 -0
- package/dist/fs.js +132 -0
- package/dist/fs.js.map +1 -0
- package/dist/github.d.ts +34 -0
- package/dist/github.js +183 -0
- package/dist/github.js.map +1 -0
- package/dist/history.d.ts +173 -0
- package/dist/history.js +247 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest-suggestions.d.ts +67 -0
- package/dist/manifest-suggestions.js +673 -0
- package/dist/manifest-suggestions.js.map +1 -0
- package/dist/manifest.d.ts +212 -0
- package/dist/manifest.js +1607 -0
- package/dist/manifest.js.map +1 -0
- package/dist/qa.d.ts +53 -0
- package/dist/qa.js +361 -0
- package/dist/qa.js.map +1 -0
- package/dist/report.d.ts +5 -0
- package/dist/report.js +164 -0
- package/dist/report.js.map +1 -0
- package/dist/review.d.ts +31 -0
- package/dist/review.js +383 -0
- package/dist/review.js.map +1 -0
- package/dist/scanner.d.ts +3 -0
- package/dist/scanner.js +797 -0
- package/dist/scanner.js.map +1 -0
- package/dist/severity.d.ts +3 -0
- package/dist/severity.js +9 -0
- package/dist/severity.js.map +1 -0
- package/dist/test-evidence.d.ts +39 -0
- package/dist/test-evidence.js +303 -0
- package/dist/test-evidence.js.map +1 -0
- package/dist/test-plan.d.ts +35 -0
- package/dist/test-plan.js +573 -0
- package/dist/test-plan.js.map +1 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/verify.d.ts +27 -0
- package/dist/verify.js +244 -0
- package/dist/verify.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/docs/adoption.md +166 -0
- package/docs/agent-skill.md +94 -0
- package/docs/api-contracts.md +30 -0
- package/docs/assets/qamap-30s-demo.gif +0 -0
- package/docs/configuration.md +277 -0
- package/docs/e2e-output-examples.md +464 -0
- package/docs/ecosystem.md +41 -0
- package/docs/eval.md +52 -0
- package/docs/github-action.md +89 -0
- package/docs/manifest.md +350 -0
- package/docs/quickstart-demo.md +268 -0
- package/docs/release-validation.md +247 -0
- package/docs/releasing.md +112 -0
- package/docs/roadmap.md +67 -0
- package/docs/rules.md +28 -0
- package/docs/verify.md +44 -0
- package/package.json +80 -0
- package/schema/qamap-manifest.schema.json +323 -0
- package/schema/qamap.schema.json +58 -0
- package/skills/qamap-pr-qa/SKILL.md +81 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,879 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { loadConfig, writeDefaultConfig } from "./config.js";
|
|
5
|
+
import { generateAgentContext } from "./context.js";
|
|
6
|
+
import { defaultDomainManifestPath, writeDefaultDomainManifest } from "./domains.js";
|
|
7
|
+
import { buildDoctorResult, formatDoctorReport, formatMarkdownDoctorReport } from "./doctor.js";
|
|
8
|
+
import { formatMarkdownE2eDraft, formatMarkdownE2ePlan, formatMarkdownE2eSetup, generateE2eDraft, generateE2ePlan, setupE2eRunner, } from "./e2e.js";
|
|
9
|
+
import { evaluateChangeReadiness, formatEvalReport, formatMarkdownEvalReport } from "./eval.js";
|
|
10
|
+
import { defaultFlowManifestPath, writeDefaultCoreFlowManifest } from "./flows.js";
|
|
11
|
+
import { runGitHubAction } from "./github.js";
|
|
12
|
+
import { formatLocalHistoryInitResult, initializeLocalHistory, recordE2ePlanHistory } from "./history.js";
|
|
13
|
+
import { defaultSuggestedDomainManifestPath, defaultSuggestedFlowManifestPath, formatDomainManifestSuggestion, formatFlowManifestSuggestion, generateDomainManifestSuggestion, generateFlowManifestSuggestion, writeSuggestedManifest, } from "./manifest-suggestions.js";
|
|
14
|
+
import { analyzeVerificationManifestContext, defaultVerificationManifestPath, explainVerificationManifest, formatVerificationManifestContextResult, formatVerificationManifestExplainResult, formatVerificationManifestInitResult, formatVerificationManifestValidationResult, validateVerificationManifest, writeVerificationManifestBaseline, } from "./manifest.js";
|
|
15
|
+
import { formatMarkdownReport, formatSarifReport, formatTextReport, hasFindingsAtOrAbove } from "./report.js";
|
|
16
|
+
import { formatAgentQaDraft, formatMarkdownQaDraft, generateQaDraft } from "./qa.js";
|
|
17
|
+
import { formatMarkdownReviewReport, formatReviewReport, reviewProject } from "./review.js";
|
|
18
|
+
import { scanProject } from "./scanner.js";
|
|
19
|
+
import { isAtLeastSeverity, isSeverity } from "./severity.js";
|
|
20
|
+
import { formatMarkdownTestPlan, generateTestPlan } from "./test-plan.js";
|
|
21
|
+
import { formatMarkdownVerifyReport, formatVerifyReport, verifyChange } from "./verify.js";
|
|
22
|
+
import { VERSION } from "./version.js";
|
|
23
|
+
async function main(argv) {
|
|
24
|
+
const [command, ...rest] = argv;
|
|
25
|
+
if (!command || command === "--help" || command === "-h") {
|
|
26
|
+
printHelp();
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
if (command === "--version" || command === "-v") {
|
|
30
|
+
console.log(VERSION);
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
if (command === "scan") {
|
|
34
|
+
const options = parseOptions(rest);
|
|
35
|
+
const loadedConfig = await loadOptionsConfig(options);
|
|
36
|
+
const result = await scanProject(options.path, buildScanOptions(options, loadedConfig));
|
|
37
|
+
const output = formatOutput(result, options.format ?? (options.json ? "json" : "text"));
|
|
38
|
+
await printOrWrite(output, options.output);
|
|
39
|
+
const failOn = options.failOn ?? loadedConfig.config.failOn;
|
|
40
|
+
return failOn && hasFindingsAtOrAbove(result, failOn) ? 1 : 0;
|
|
41
|
+
}
|
|
42
|
+
if (command === "report") {
|
|
43
|
+
const options = parseOptions(rest);
|
|
44
|
+
const loadedConfig = await loadOptionsConfig(options);
|
|
45
|
+
const result = await scanProject(options.path, buildScanOptions(options, loadedConfig));
|
|
46
|
+
const output = formatOutput(result, options.format ?? (options.json ? "json" : "markdown"));
|
|
47
|
+
await printOrWrite(output, options.output);
|
|
48
|
+
const failOn = options.failOn ?? loadedConfig.config.failOn;
|
|
49
|
+
return failOn && hasFindingsAtOrAbove(result, failOn) ? 1 : 0;
|
|
50
|
+
}
|
|
51
|
+
if (command === "doctor") {
|
|
52
|
+
const options = parseOptions(rest);
|
|
53
|
+
const loadedConfig = await loadOptionsConfig(options);
|
|
54
|
+
const result = await scanProject(options.path, buildScanOptions(options, loadedConfig));
|
|
55
|
+
const output = formatDoctorOutput(result, options.format ?? (options.json ? "json" : "text"));
|
|
56
|
+
await printOrWrite(output, options.output);
|
|
57
|
+
const failOn = options.failOn ?? loadedConfig.config.failOn;
|
|
58
|
+
return failOn && hasFindingsAtOrAbove(result, failOn) ? 1 : 0;
|
|
59
|
+
}
|
|
60
|
+
if (command === "review") {
|
|
61
|
+
const options = parseOptions(rest);
|
|
62
|
+
const loadedConfig = await loadOptionsConfig(options);
|
|
63
|
+
const result = await reviewProject(options.path, {
|
|
64
|
+
base: options.base,
|
|
65
|
+
head: options.head,
|
|
66
|
+
scanOptions: buildScanOptions(options, loadedConfig),
|
|
67
|
+
});
|
|
68
|
+
const output = formatReviewOutput(result, options.format ?? (options.json ? "json" : "text"));
|
|
69
|
+
await printOrWrite(output, options.output);
|
|
70
|
+
const failOn = options.failOn ?? loadedConfig.config.failOn;
|
|
71
|
+
const reviewFindings = [...result.newFindings, ...result.changedRiskyFindings];
|
|
72
|
+
return failOn && reviewFindings.some((finding) => isAtLeastSeverity(finding.severity, failOn)) ? 1 : 0;
|
|
73
|
+
}
|
|
74
|
+
if (command === "verify") {
|
|
75
|
+
const options = parseOptions(rest);
|
|
76
|
+
const loadedConfig = await loadOptionsConfig(options);
|
|
77
|
+
const result = await verifyChange(options.path, {
|
|
78
|
+
base: options.base,
|
|
79
|
+
head: options.head,
|
|
80
|
+
scanOptions: buildScanOptions(options, loadedConfig),
|
|
81
|
+
includeWorkingTree: options.includeWorkingTree,
|
|
82
|
+
prBodyFile: options.prBodyFile,
|
|
83
|
+
validationCommands: loadedConfig.config.validationCommands,
|
|
84
|
+
manifestPath: options.manifestPath,
|
|
85
|
+
});
|
|
86
|
+
const output = formatVerifyOutput(result, options.format ?? (options.json ? "json" : "markdown"));
|
|
87
|
+
await printOrWrite(output, options.output);
|
|
88
|
+
const failOn = options.failOn ?? loadedConfig.config.failOn;
|
|
89
|
+
const reviewFindings = [...result.review.newFindings, ...result.review.changedRiskyFindings];
|
|
90
|
+
return failOn && reviewFindings.some((finding) => isAtLeastSeverity(finding.severity, failOn)) ? 1 : 0;
|
|
91
|
+
}
|
|
92
|
+
if (command === "github-action") {
|
|
93
|
+
const options = parseOptions(rest);
|
|
94
|
+
const loadedConfig = await loadOptionsConfig(options);
|
|
95
|
+
const result = await runGitHubAction(options.path, {
|
|
96
|
+
mode: options.mode,
|
|
97
|
+
base: options.base,
|
|
98
|
+
head: options.head,
|
|
99
|
+
scanOptions: buildScanOptions(options, loadedConfig),
|
|
100
|
+
failOn: options.failOn ?? loadedConfig.config.failOn,
|
|
101
|
+
reportFile: options.reportFile,
|
|
102
|
+
commentFile: options.commentFile,
|
|
103
|
+
annotations: options.annotations,
|
|
104
|
+
stepSummary: options.stepSummary,
|
|
105
|
+
testPlan: options.testPlan,
|
|
106
|
+
testPlanFile: options.testPlanFile,
|
|
107
|
+
evaluation: options.evaluation,
|
|
108
|
+
evalFile: options.evalFile,
|
|
109
|
+
prBodyFile: options.prBodyFile,
|
|
110
|
+
includeWorkingTree: options.includeWorkingTree,
|
|
111
|
+
validationCommands: loadedConfig.config.validationCommands,
|
|
112
|
+
});
|
|
113
|
+
return result.exitCode;
|
|
114
|
+
}
|
|
115
|
+
if (command === "eval") {
|
|
116
|
+
const options = parseOptions(rest);
|
|
117
|
+
const loadedConfig = await loadOptionsConfig(options);
|
|
118
|
+
const result = await evaluateChangeReadiness(options.path, {
|
|
119
|
+
base: options.base,
|
|
120
|
+
head: options.head,
|
|
121
|
+
workspaceRoot: options.workspaceRoot,
|
|
122
|
+
includeWorkingTree: options.includeWorkingTree,
|
|
123
|
+
prBodyFile: options.prBodyFile,
|
|
124
|
+
validationCommands: loadedConfig.config.validationCommands,
|
|
125
|
+
});
|
|
126
|
+
const output = formatEvalOutput(result, options.format ?? (options.json ? "json" : "markdown"));
|
|
127
|
+
await printOrWrite(output, options.output);
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
if (command === "test-plan") {
|
|
131
|
+
const options = parseOptions(rest);
|
|
132
|
+
const loadedConfig = await loadOptionsConfig(options);
|
|
133
|
+
const result = await generateTestPlan(options.path, {
|
|
134
|
+
base: options.base,
|
|
135
|
+
head: options.head,
|
|
136
|
+
workspaceRoot: options.workspaceRoot,
|
|
137
|
+
includeWorkingTree: options.includeWorkingTree,
|
|
138
|
+
validationCommands: loadedConfig.config.validationCommands,
|
|
139
|
+
});
|
|
140
|
+
const output = formatTestPlanOutput(result, options.format ?? (options.json ? "json" : "markdown"));
|
|
141
|
+
await printOrWrite(output, options.output);
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
if (command === "e2e") {
|
|
145
|
+
const [subcommand, ...subcommandRest] = rest;
|
|
146
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
147
|
+
printE2eHelp();
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
if (subcommand !== "plan" && subcommand !== "draft" && subcommand !== "setup") {
|
|
151
|
+
throw new Error(`Unknown e2e subcommand: ${subcommand}`);
|
|
152
|
+
}
|
|
153
|
+
const options = parseOptions(subcommandRest);
|
|
154
|
+
const loadedConfig = await loadOptionsConfig(options);
|
|
155
|
+
const e2eOptions = {
|
|
156
|
+
base: options.base,
|
|
157
|
+
head: options.head,
|
|
158
|
+
workspaceRoot: options.workspaceRoot,
|
|
159
|
+
includeWorkingTree: options.includeWorkingTree,
|
|
160
|
+
validationCommands: loadedConfig.config.validationCommands,
|
|
161
|
+
runner: options.e2eRunner,
|
|
162
|
+
manifestPath: options.manifestPath,
|
|
163
|
+
};
|
|
164
|
+
if (subcommand === "plan") {
|
|
165
|
+
const result = await generateE2ePlan(options.path, e2eOptions);
|
|
166
|
+
if (options.recordHistory) {
|
|
167
|
+
result.localHistory = await recordE2ePlanHistory(options.workspaceRoot ?? options.path, result);
|
|
168
|
+
}
|
|
169
|
+
const output = formatE2ePlanOutput(result, options.format ?? (options.json ? "json" : "markdown"));
|
|
170
|
+
await printOrWrite(output, options.output);
|
|
171
|
+
return 0;
|
|
172
|
+
}
|
|
173
|
+
if (subcommand === "setup") {
|
|
174
|
+
const result = await setupE2eRunner(options.path, {
|
|
175
|
+
...e2eOptions,
|
|
176
|
+
force: options.force,
|
|
177
|
+
});
|
|
178
|
+
const output = formatE2eSetupOutput(result, options.format ?? (options.json ? "json" : "markdown"));
|
|
179
|
+
await printOrWrite(output, options.output);
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
const result = await generateE2eDraft(options.path, {
|
|
183
|
+
...e2eOptions,
|
|
184
|
+
output: options.output,
|
|
185
|
+
force: options.force,
|
|
186
|
+
dryRun: options.dryRun,
|
|
187
|
+
});
|
|
188
|
+
const output = formatE2eDraftOutput(result, options.format ?? (options.json ? "json" : "markdown"));
|
|
189
|
+
console.log(output.trimEnd());
|
|
190
|
+
return 0;
|
|
191
|
+
}
|
|
192
|
+
if (command === "qa") {
|
|
193
|
+
const options = parseOptions(rest);
|
|
194
|
+
const loadedConfig = await loadOptionsConfig(options);
|
|
195
|
+
const result = await generateQaDraft(options.path, {
|
|
196
|
+
base: options.base,
|
|
197
|
+
head: options.head,
|
|
198
|
+
workspaceRoot: options.workspaceRoot,
|
|
199
|
+
includeWorkingTree: options.includeWorkingTree,
|
|
200
|
+
validationCommands: loadedConfig.config.validationCommands,
|
|
201
|
+
runner: options.e2eRunner,
|
|
202
|
+
manifestPath: options.manifestPath,
|
|
203
|
+
});
|
|
204
|
+
const output = formatQaDraftOutput(result, options.format ?? (options.json ? "json" : "markdown"));
|
|
205
|
+
await printOrWrite(output, options.output);
|
|
206
|
+
return 0;
|
|
207
|
+
}
|
|
208
|
+
if (command === "history") {
|
|
209
|
+
const [subcommand, ...subcommandRest] = rest;
|
|
210
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
211
|
+
printHistoryHelp();
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
if (subcommand !== "init") {
|
|
215
|
+
throw new Error(`Unknown history subcommand: ${subcommand}`);
|
|
216
|
+
}
|
|
217
|
+
const options = parseOptions(subcommandRest);
|
|
218
|
+
const result = await initializeLocalHistory(options.path);
|
|
219
|
+
if (options.json || options.format === "json") {
|
|
220
|
+
await printOrWrite(`${JSON.stringify(result, null, 2)}\n`, options.output);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
await printOrWrite(formatLocalHistoryInitResult(result), options.output);
|
|
224
|
+
}
|
|
225
|
+
return 0;
|
|
226
|
+
}
|
|
227
|
+
if (command === "flows") {
|
|
228
|
+
const [subcommand, ...subcommandRest] = rest;
|
|
229
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
230
|
+
printFlowsHelp();
|
|
231
|
+
return 0;
|
|
232
|
+
}
|
|
233
|
+
if (subcommand !== "init" && subcommand !== "suggest") {
|
|
234
|
+
throw new Error(`Unknown flows subcommand: ${subcommand}`);
|
|
235
|
+
}
|
|
236
|
+
const options = parseOptions(subcommandRest);
|
|
237
|
+
if (subcommand === "suggest") {
|
|
238
|
+
const loadedConfig = await loadOptionsConfig(options);
|
|
239
|
+
const result = await generateFlowManifestSuggestion(options.path, {
|
|
240
|
+
base: options.base,
|
|
241
|
+
head: options.head,
|
|
242
|
+
workspaceRoot: options.workspaceRoot,
|
|
243
|
+
includeWorkingTree: options.includeWorkingTree,
|
|
244
|
+
validationCommands: loadedConfig.config.validationCommands,
|
|
245
|
+
});
|
|
246
|
+
const format = options.format ?? (options.json ? "json" : "text");
|
|
247
|
+
const output = formatFlowManifestSuggestion(result, manifestSuggestionFormat(format, "flows"));
|
|
248
|
+
if (options.write) {
|
|
249
|
+
const manifestRoot = options.workspaceRoot ?? options.path;
|
|
250
|
+
const writePath = await writeSuggestedManifest(manifestRoot, manifestWritePath(options.write, defaultSuggestedFlowManifestPath), result.yaml, options.force);
|
|
251
|
+
await printOrWrite(`Wrote ${writePath}\nReview this generated core flow manifest before committing it.\n`, options.output);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
await printOrWrite(output, options.output);
|
|
255
|
+
}
|
|
256
|
+
return 0;
|
|
257
|
+
}
|
|
258
|
+
const outputPath = await writeDefaultCoreFlowManifest(options.path, options.write ?? defaultFlowManifestPath, options.force);
|
|
259
|
+
if (options.json || options.format === "json") {
|
|
260
|
+
await printOrWrite(`${JSON.stringify({ path: outputPath }, null, 2)}\n`, options.output);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
await printOrWrite(`Wrote ${outputPath}\nCommit this file when the flow definitions should become team policy.\n`, options.output);
|
|
264
|
+
}
|
|
265
|
+
return 0;
|
|
266
|
+
}
|
|
267
|
+
if (command === "domains") {
|
|
268
|
+
const [subcommand, ...subcommandRest] = rest;
|
|
269
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
270
|
+
printDomainsHelp();
|
|
271
|
+
return 0;
|
|
272
|
+
}
|
|
273
|
+
if (subcommand !== "init" && subcommand !== "suggest") {
|
|
274
|
+
throw new Error(`Unknown domains subcommand: ${subcommand}`);
|
|
275
|
+
}
|
|
276
|
+
const options = parseOptions(subcommandRest);
|
|
277
|
+
if (subcommand === "suggest") {
|
|
278
|
+
const loadedConfig = await loadOptionsConfig(options);
|
|
279
|
+
const result = await generateDomainManifestSuggestion(options.path, {
|
|
280
|
+
base: options.base,
|
|
281
|
+
head: options.head,
|
|
282
|
+
workspaceRoot: options.workspaceRoot,
|
|
283
|
+
includeWorkingTree: options.includeWorkingTree,
|
|
284
|
+
validationCommands: loadedConfig.config.validationCommands,
|
|
285
|
+
});
|
|
286
|
+
const format = options.format ?? (options.json ? "json" : "text");
|
|
287
|
+
const output = formatDomainManifestSuggestion(result, manifestSuggestionFormat(format, "domains"));
|
|
288
|
+
if (options.write) {
|
|
289
|
+
const manifestRoot = options.workspaceRoot ?? options.path;
|
|
290
|
+
const writePath = await writeSuggestedManifest(manifestRoot, manifestWritePath(options.write, defaultSuggestedDomainManifestPath), result.yaml, options.force);
|
|
291
|
+
await printOrWrite(`Wrote ${writePath}\nReview this generated domain manifest before committing it.\n`, options.output);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
await printOrWrite(output, options.output);
|
|
295
|
+
}
|
|
296
|
+
return 0;
|
|
297
|
+
}
|
|
298
|
+
const outputPath = await writeDefaultDomainManifest(options.path, options.write ?? defaultDomainManifestPath, options.force);
|
|
299
|
+
if (options.json || options.format === "json") {
|
|
300
|
+
await printOrWrite(`${JSON.stringify({ path: outputPath }, null, 2)}\n`, options.output);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
await printOrWrite(`Wrote ${outputPath}\nCommit this file when the domain definitions should become team policy.\n`, options.output);
|
|
304
|
+
}
|
|
305
|
+
return 0;
|
|
306
|
+
}
|
|
307
|
+
if (command === "manifest") {
|
|
308
|
+
const [subcommand, ...subcommandRest] = rest;
|
|
309
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
310
|
+
printManifestHelp();
|
|
311
|
+
return 0;
|
|
312
|
+
}
|
|
313
|
+
if (subcommand !== "init" && subcommand !== "validate" && subcommand !== "explain" && subcommand !== "context") {
|
|
314
|
+
throw new Error(`Unknown manifest subcommand: ${subcommand}`);
|
|
315
|
+
}
|
|
316
|
+
const options = parseOptions(subcommandRest);
|
|
317
|
+
if (subcommand === "validate") {
|
|
318
|
+
const result = await validateVerificationManifest(options.path, options.workspaceRoot, options.manifestPath);
|
|
319
|
+
const format = manifestCommandFormat(options.format ?? (options.json ? "json" : "text"));
|
|
320
|
+
await printOrWrite(formatVerificationManifestValidationResult(result, format), options.output);
|
|
321
|
+
return result.status === "invalid" || result.status === "missing" ? 1 : 0;
|
|
322
|
+
}
|
|
323
|
+
if (subcommand === "explain") {
|
|
324
|
+
const loadedConfig = await loadOptionsConfig(options);
|
|
325
|
+
const result = await explainVerificationManifest(options.path, {
|
|
326
|
+
base: options.base,
|
|
327
|
+
head: options.head,
|
|
328
|
+
workspaceRoot: options.workspaceRoot,
|
|
329
|
+
includeWorkingTree: options.includeWorkingTree,
|
|
330
|
+
validationCommands: loadedConfig.config.validationCommands,
|
|
331
|
+
manifestPath: options.manifestPath,
|
|
332
|
+
});
|
|
333
|
+
const format = manifestCommandFormat(options.format ?? (options.json ? "json" : "markdown"));
|
|
334
|
+
await printOrWrite(formatVerificationManifestExplainResult(result, format), options.output);
|
|
335
|
+
return 0;
|
|
336
|
+
}
|
|
337
|
+
if (subcommand === "context") {
|
|
338
|
+
const result = await analyzeVerificationManifestContext(options.path, {
|
|
339
|
+
workspaceRoot: options.workspaceRoot,
|
|
340
|
+
maxFiles: options.maxFiles,
|
|
341
|
+
});
|
|
342
|
+
const format = manifestCommandFormat(options.format ?? (options.json ? "json" : "markdown"));
|
|
343
|
+
await printOrWrite(formatVerificationManifestContextResult(result, format), options.output);
|
|
344
|
+
return 0;
|
|
345
|
+
}
|
|
346
|
+
const result = await writeVerificationManifestBaseline(options.path, {
|
|
347
|
+
workspaceRoot: options.workspaceRoot,
|
|
348
|
+
write: options.write ?? defaultVerificationManifestPath,
|
|
349
|
+
force: options.force,
|
|
350
|
+
maxFiles: options.maxFiles,
|
|
351
|
+
});
|
|
352
|
+
if (options.json || options.format === "json") {
|
|
353
|
+
await printOrWrite(`${JSON.stringify(result, null, 2)}\n`, options.output);
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
await printOrWrite(formatVerificationManifestInitResult(result), options.output);
|
|
357
|
+
}
|
|
358
|
+
return 0;
|
|
359
|
+
}
|
|
360
|
+
if (command === "context") {
|
|
361
|
+
const options = parseOptions(rest);
|
|
362
|
+
const context = await generateAgentContext(options.path);
|
|
363
|
+
if (options.write) {
|
|
364
|
+
const outputPath = path.resolve(options.path, options.write);
|
|
365
|
+
if (!options.force) {
|
|
366
|
+
try {
|
|
367
|
+
await fs.access(outputPath);
|
|
368
|
+
throw new Error(`Refusing to overwrite ${outputPath}. Pass --force to replace it.`);
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
if (error instanceof Error && error.message.startsWith("Refusing")) {
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
await fs.writeFile(outputPath, context, "utf8");
|
|
377
|
+
console.log(`Wrote ${outputPath}`);
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
console.log(context);
|
|
381
|
+
}
|
|
382
|
+
return 0;
|
|
383
|
+
}
|
|
384
|
+
if (command === "init") {
|
|
385
|
+
const options = parseOptions(rest);
|
|
386
|
+
const outputPath = await writeDefaultConfig(options.path, options.write ?? "qamap.config.json", options.force);
|
|
387
|
+
console.log(`Wrote ${outputPath}`);
|
|
388
|
+
return 0;
|
|
389
|
+
}
|
|
390
|
+
throw new Error(`Unknown command: ${command}`);
|
|
391
|
+
}
|
|
392
|
+
function parseOptions(args) {
|
|
393
|
+
const options = {
|
|
394
|
+
path: ".",
|
|
395
|
+
json: false,
|
|
396
|
+
force: false,
|
|
397
|
+
};
|
|
398
|
+
let sawPath = false;
|
|
399
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
400
|
+
const arg = args[index];
|
|
401
|
+
if (arg === "--json") {
|
|
402
|
+
options.json = true;
|
|
403
|
+
options.format = "json";
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (arg === "--force") {
|
|
407
|
+
options.force = true;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if (arg === "--dry-run") {
|
|
411
|
+
options.dryRun = true;
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (arg === "--output" || arg === "-o") {
|
|
415
|
+
options.output = readValue(args, ++index, arg);
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
if (arg === "--format") {
|
|
419
|
+
const value = readValue(args, ++index, arg);
|
|
420
|
+
if (!isOutputFormat(value)) {
|
|
421
|
+
throw new Error(`Invalid format for --format: ${value}`);
|
|
422
|
+
}
|
|
423
|
+
options.format = value;
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
if (arg === "--config") {
|
|
427
|
+
options.config = readValue(args, ++index, arg);
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (arg === "--workspace-root") {
|
|
431
|
+
options.workspaceRoot = readValue(args, ++index, arg);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (arg === "--manifest") {
|
|
435
|
+
options.manifestPath = readValue(args, ++index, arg);
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
if (arg === "--write") {
|
|
439
|
+
const next = args[index + 1];
|
|
440
|
+
if (next && !next.startsWith("-")) {
|
|
441
|
+
options.write = next;
|
|
442
|
+
index += 1;
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
options.write = "AGENTS.md";
|
|
446
|
+
}
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (arg === "--fail-on") {
|
|
450
|
+
const value = readValue(args, ++index, arg);
|
|
451
|
+
if (!isSeverity(value)) {
|
|
452
|
+
throw new Error(`Invalid severity for --fail-on: ${value}`);
|
|
453
|
+
}
|
|
454
|
+
options.failOn = value;
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (arg === "--base") {
|
|
458
|
+
options.base = readValue(args, ++index, arg);
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (arg === "--head") {
|
|
462
|
+
options.head = readValue(args, ++index, arg);
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
if (arg === "--mode") {
|
|
466
|
+
const value = readValue(args, ++index, arg);
|
|
467
|
+
if (!isGitHubActionMode(value)) {
|
|
468
|
+
throw new Error(`Invalid mode for --mode: ${value}`);
|
|
469
|
+
}
|
|
470
|
+
options.mode = value;
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
if (arg === "--runner") {
|
|
474
|
+
const value = readValue(args, ++index, arg);
|
|
475
|
+
if (!isE2eRunnerName(value)) {
|
|
476
|
+
throw new Error(`Invalid runner for --runner: ${value}`);
|
|
477
|
+
}
|
|
478
|
+
options.e2eRunner = value;
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
if (arg === "--report-file") {
|
|
482
|
+
options.reportFile = readValue(args, ++index, arg);
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
if (arg === "--comment-file") {
|
|
486
|
+
options.commentFile = readValue(args, ++index, arg);
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
if (arg === "--test-plan-file") {
|
|
490
|
+
options.testPlanFile = readValue(args, ++index, arg);
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
if (arg === "--eval-file") {
|
|
494
|
+
options.evalFile = readValue(args, ++index, arg);
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
if (arg === "--pr-body-file") {
|
|
498
|
+
options.prBodyFile = readValue(args, ++index, arg);
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
if (arg === "--no-annotations") {
|
|
502
|
+
options.annotations = false;
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
if (arg === "--no-step-summary") {
|
|
506
|
+
options.stepSummary = false;
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (arg === "--test-plan") {
|
|
510
|
+
options.testPlan = true;
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
if (arg === "--no-test-plan") {
|
|
514
|
+
options.testPlan = false;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (arg === "--eval") {
|
|
518
|
+
options.evaluation = true;
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
if (arg === "--no-eval") {
|
|
522
|
+
options.evaluation = false;
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
if (arg === "--include-working-tree") {
|
|
526
|
+
options.includeWorkingTree = true;
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
if (arg === "--record-history") {
|
|
530
|
+
options.recordHistory = true;
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
if (arg === "--max-files") {
|
|
534
|
+
const value = Number.parseInt(readValue(args, ++index, arg), 10);
|
|
535
|
+
if (!Number.isFinite(value) || value < 1) {
|
|
536
|
+
throw new Error("--max-files must be a positive integer");
|
|
537
|
+
}
|
|
538
|
+
options.maxFiles = value;
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
if (arg === "--help" || arg === "-h") {
|
|
542
|
+
printHelp();
|
|
543
|
+
process.exit(0);
|
|
544
|
+
}
|
|
545
|
+
if (arg.startsWith("-")) {
|
|
546
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
547
|
+
}
|
|
548
|
+
if (sawPath) {
|
|
549
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
550
|
+
}
|
|
551
|
+
options.path = arg;
|
|
552
|
+
sawPath = true;
|
|
553
|
+
}
|
|
554
|
+
return options;
|
|
555
|
+
}
|
|
556
|
+
function buildScanOptions(options, loadedConfig) {
|
|
557
|
+
return {
|
|
558
|
+
configPath: loadedConfig.path,
|
|
559
|
+
ignoreRules: loadedConfig.config.ignoreRules,
|
|
560
|
+
maxFiles: options.maxFiles ?? loadedConfig.config.maxFiles,
|
|
561
|
+
workspaceRoot: options.workspaceRoot,
|
|
562
|
+
severityOverrides: loadedConfig.config.severity,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
async function loadOptionsConfig(options) {
|
|
566
|
+
return loadConfig(options.workspaceRoot ?? options.path, options.config);
|
|
567
|
+
}
|
|
568
|
+
function formatOutput(result, format) {
|
|
569
|
+
if (format === "json") {
|
|
570
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
571
|
+
}
|
|
572
|
+
if (format === "markdown") {
|
|
573
|
+
return formatMarkdownReport(result);
|
|
574
|
+
}
|
|
575
|
+
if (format === "sarif") {
|
|
576
|
+
return formatSarifReport(result);
|
|
577
|
+
}
|
|
578
|
+
if (format !== "text") {
|
|
579
|
+
throw new Error(`Scan supports text, json, markdown, or sarif output, not ${format}`);
|
|
580
|
+
}
|
|
581
|
+
return formatTextReport(result);
|
|
582
|
+
}
|
|
583
|
+
function formatDoctorOutput(result, format) {
|
|
584
|
+
if (format === "json") {
|
|
585
|
+
return `${JSON.stringify(buildDoctorResult(result), null, 2)}\n`;
|
|
586
|
+
}
|
|
587
|
+
if (format === "markdown") {
|
|
588
|
+
return formatMarkdownDoctorReport(result);
|
|
589
|
+
}
|
|
590
|
+
if (format !== "text") {
|
|
591
|
+
throw new Error(`Doctor supports text, json, or markdown output, not ${format}`);
|
|
592
|
+
}
|
|
593
|
+
return formatDoctorReport(result);
|
|
594
|
+
}
|
|
595
|
+
function formatReviewOutput(result, format) {
|
|
596
|
+
if (format === "json") {
|
|
597
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
598
|
+
}
|
|
599
|
+
if (format === "markdown") {
|
|
600
|
+
return formatMarkdownReviewReport(result);
|
|
601
|
+
}
|
|
602
|
+
if (format !== "text") {
|
|
603
|
+
throw new Error(`Review supports text, json, or markdown output, not ${format}`);
|
|
604
|
+
}
|
|
605
|
+
return formatReviewReport(result);
|
|
606
|
+
}
|
|
607
|
+
function formatTestPlanOutput(result, format) {
|
|
608
|
+
if (format === "json") {
|
|
609
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
610
|
+
}
|
|
611
|
+
if (format !== "markdown" && format !== "text") {
|
|
612
|
+
throw new Error(`Test plan supports text, json, or markdown output, not ${format}`);
|
|
613
|
+
}
|
|
614
|
+
return formatMarkdownTestPlan(result);
|
|
615
|
+
}
|
|
616
|
+
function formatE2ePlanOutput(result, format) {
|
|
617
|
+
if (format === "json") {
|
|
618
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
619
|
+
}
|
|
620
|
+
if (format !== "markdown" && format !== "text") {
|
|
621
|
+
throw new Error(`E2E plan supports text, json, or markdown output, not ${format}`);
|
|
622
|
+
}
|
|
623
|
+
return formatMarkdownE2ePlan(result);
|
|
624
|
+
}
|
|
625
|
+
function formatE2eDraftOutput(result, format) {
|
|
626
|
+
if (format === "json") {
|
|
627
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
628
|
+
}
|
|
629
|
+
if (format !== "markdown" && format !== "text") {
|
|
630
|
+
throw new Error(`E2E draft supports text, json, or markdown output, not ${format}`);
|
|
631
|
+
}
|
|
632
|
+
return formatMarkdownE2eDraft(result);
|
|
633
|
+
}
|
|
634
|
+
function formatE2eSetupOutput(result, format) {
|
|
635
|
+
if (format === "json") {
|
|
636
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
637
|
+
}
|
|
638
|
+
if (format !== "markdown" && format !== "text") {
|
|
639
|
+
throw new Error(`E2E setup supports text, json, or markdown output, not ${format}`);
|
|
640
|
+
}
|
|
641
|
+
return formatMarkdownE2eSetup(result);
|
|
642
|
+
}
|
|
643
|
+
function formatQaDraftOutput(result, format) {
|
|
644
|
+
if (format === "json") {
|
|
645
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
646
|
+
}
|
|
647
|
+
if (format === "agent") {
|
|
648
|
+
return formatAgentQaDraft(result);
|
|
649
|
+
}
|
|
650
|
+
if (format !== "markdown" && format !== "text") {
|
|
651
|
+
throw new Error(`QA draft supports text, json, markdown, or agent output, not ${format}`);
|
|
652
|
+
}
|
|
653
|
+
return formatMarkdownQaDraft(result);
|
|
654
|
+
}
|
|
655
|
+
function manifestSuggestionFormat(format, command) {
|
|
656
|
+
if (format === "sarif" || format === "agent") {
|
|
657
|
+
throw new Error(`${command} suggest supports text, json, or markdown output, not ${format}`);
|
|
658
|
+
}
|
|
659
|
+
return format;
|
|
660
|
+
}
|
|
661
|
+
function manifestCommandFormat(format) {
|
|
662
|
+
if (format === "sarif" || format === "agent") {
|
|
663
|
+
throw new Error(`manifest supports text, json, or markdown output, not ${format}`);
|
|
664
|
+
}
|
|
665
|
+
return format;
|
|
666
|
+
}
|
|
667
|
+
function manifestWritePath(writeOption, defaultPath) {
|
|
668
|
+
return writeOption === "AGENTS.md" ? defaultPath : writeOption;
|
|
669
|
+
}
|
|
670
|
+
function formatEvalOutput(result, format) {
|
|
671
|
+
if (format === "json") {
|
|
672
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
673
|
+
}
|
|
674
|
+
if (format === "markdown") {
|
|
675
|
+
return formatMarkdownEvalReport(result);
|
|
676
|
+
}
|
|
677
|
+
if (format !== "text") {
|
|
678
|
+
throw new Error(`Eval supports text, json, or markdown output, not ${format}`);
|
|
679
|
+
}
|
|
680
|
+
return formatEvalReport(result);
|
|
681
|
+
}
|
|
682
|
+
function formatVerifyOutput(result, format) {
|
|
683
|
+
if (format === "json") {
|
|
684
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
685
|
+
}
|
|
686
|
+
if (format === "markdown") {
|
|
687
|
+
return formatMarkdownVerifyReport(result);
|
|
688
|
+
}
|
|
689
|
+
if (format !== "text") {
|
|
690
|
+
throw new Error(`Verify supports text, json, or markdown output, not ${format}`);
|
|
691
|
+
}
|
|
692
|
+
return formatVerifyReport(result);
|
|
693
|
+
}
|
|
694
|
+
async function printOrWrite(output, outputPath) {
|
|
695
|
+
if (outputPath) {
|
|
696
|
+
const resolvedOutputPath = path.resolve(outputPath);
|
|
697
|
+
await fs.writeFile(resolvedOutputPath, output, "utf8");
|
|
698
|
+
console.log(`Wrote ${resolvedOutputPath}`);
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
console.log(output.trimEnd());
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
function isOutputFormat(value) {
|
|
705
|
+
return value === "text" || value === "json" || value === "markdown" || value === "sarif" || value === "agent";
|
|
706
|
+
}
|
|
707
|
+
function isGitHubActionMode(value) {
|
|
708
|
+
return value === "auto" || value === "scan" || value === "review";
|
|
709
|
+
}
|
|
710
|
+
function isE2eRunnerName(value) {
|
|
711
|
+
return value === "maestro" || value === "playwright" || value === "manual";
|
|
712
|
+
}
|
|
713
|
+
function readValue(args, index, flag) {
|
|
714
|
+
const value = args[index];
|
|
715
|
+
if (!value || value.startsWith("-")) {
|
|
716
|
+
throw new Error(`Missing value for ${flag}`);
|
|
717
|
+
}
|
|
718
|
+
return value;
|
|
719
|
+
}
|
|
720
|
+
function printHelp() {
|
|
721
|
+
console.log(`QAMap ${VERSION}
|
|
722
|
+
|
|
723
|
+
Local-first PR QA drafts and guardrails for AI-assisted changes.
|
|
724
|
+
|
|
725
|
+
Usage:
|
|
726
|
+
qamap scan [path] [--format <format>] [--fail-on <severity>] [--max-files <n>]
|
|
727
|
+
qamap report [path] [--format <format>] [--output <file>] [--fail-on <severity>]
|
|
728
|
+
qamap doctor [path] [--format <format>] [--output <file>] [--fail-on <severity>]
|
|
729
|
+
qamap review [path] [--base <ref>] [--head <ref>] [--format <format>] [--fail-on <severity>]
|
|
730
|
+
qamap verify [path] [--workspace-root <path>] [--manifest <file>] [--base <ref>] [--head <ref>] [--include-working-tree] [--pr-body-file <file>] [--fail-on <severity>]
|
|
731
|
+
qamap eval [path] [--workspace-root <path>] [--base <ref>] [--head <ref>] [--include-working-tree] [--pr-body-file <file>] [--format <format>]
|
|
732
|
+
qamap github-action [path] [--mode auto|scan|review] [--base <ref>] [--head <ref>] [--fail-on <severity>]
|
|
733
|
+
qamap test-plan [path] [--workspace-root <path>] [--base <ref>] [--head <ref>] [--include-working-tree] [--format <format>] [--output <file>]
|
|
734
|
+
qamap qa [path] [--workspace-root <path>] [--manifest <file>] [--base <ref>] [--head <ref>] [--include-working-tree] [--runner maestro|playwright|manual] [--format <format>] [--output <file>]
|
|
735
|
+
qamap e2e plan [path] [--workspace-root <path>] [--manifest <file>] [--base <ref>] [--head <ref>] [--include-working-tree] [--record-history] [--format <format>]
|
|
736
|
+
qamap e2e setup [path] [--workspace-root <path>] [--runner maestro|playwright] [--force]
|
|
737
|
+
qamap e2e draft [path] [--workspace-root <path>] [--manifest <file>] [--base <ref>] [--head <ref>] [--runner maestro|playwright|manual] [--output <dir>] [--dry-run] [--force]
|
|
738
|
+
qamap manifest init [path] [--workspace-root <path>] [--write <file>] [--max-files <n>] [--force]
|
|
739
|
+
qamap manifest validate [path] [--workspace-root <path>] [--manifest <file>] [--format <format>]
|
|
740
|
+
qamap manifest explain [path] [--workspace-root <path>] [--manifest <file>] [--base <ref>] [--head <ref>] [--include-working-tree] [--format <format>]
|
|
741
|
+
qamap flows init [path] [--write <file>] [--force]
|
|
742
|
+
qamap flows suggest [path] [--workspace-root <path>] [--base <ref>] [--head <ref>] [--include-working-tree] [--format <format>] [--output <file>] [--write <file>] [--force]
|
|
743
|
+
qamap domains init [path] [--write <file>] [--force]
|
|
744
|
+
qamap domains suggest [path] [--workspace-root <path>] [--base <ref>] [--head <ref>] [--include-working-tree] [--format <format>] [--output <file>] [--write <file>] [--force]
|
|
745
|
+
qamap history init [path]
|
|
746
|
+
qamap context [path] [--write [file]] [--force]
|
|
747
|
+
qamap init [path] [--write <file>] [--force]
|
|
748
|
+
|
|
749
|
+
Severities:
|
|
750
|
+
info, low, medium, high
|
|
751
|
+
|
|
752
|
+
Formats:
|
|
753
|
+
text, json, markdown, sarif
|
|
754
|
+
agent (qa only: compact machine-readable summary for coding agents)
|
|
755
|
+
|
|
756
|
+
Examples:
|
|
757
|
+
qamap scan .
|
|
758
|
+
qamap scan services/offer --workspace-root .
|
|
759
|
+
qamap scan . --format sarif --output qamap.sarif
|
|
760
|
+
qamap scan . --fail-on medium
|
|
761
|
+
qamap report . --output QAMAP_REPORT.md
|
|
762
|
+
qamap doctor .
|
|
763
|
+
qamap review . --base origin/main --head HEAD
|
|
764
|
+
qamap verify . --base origin/main --head HEAD --pr-body-file pr-body.md
|
|
765
|
+
qamap eval . --base origin/main --head HEAD --pr-body-file pr-body.md
|
|
766
|
+
qamap github-action . --mode review --base origin/main --head HEAD --fail-on high
|
|
767
|
+
qamap test-plan . --base origin/main --head HEAD
|
|
768
|
+
qamap qa . --base origin/main --head HEAD
|
|
769
|
+
qamap qa . --manifest /tmp/qamap-manifest.yaml --base origin/main --head HEAD --output QAMAP_QA.md
|
|
770
|
+
qamap e2e plan . --base origin/main --head HEAD
|
|
771
|
+
qamap e2e plan . --base origin/main --head HEAD --record-history
|
|
772
|
+
qamap e2e setup . --runner playwright
|
|
773
|
+
qamap e2e draft . --base origin/main --head HEAD --dry-run
|
|
774
|
+
qamap e2e draft . --manifest /tmp/qamap-manifest.yaml --base origin/main --head HEAD --dry-run
|
|
775
|
+
qamap manifest init .
|
|
776
|
+
qamap manifest explain . --base origin/main --head HEAD
|
|
777
|
+
qamap flows init .
|
|
778
|
+
qamap flows suggest . --base origin/main --head HEAD
|
|
779
|
+
qamap domains init .
|
|
780
|
+
qamap domains suggest . --base origin/main --head HEAD
|
|
781
|
+
qamap history init .
|
|
782
|
+
qamap test-plan services/offer --workspace-root . --base origin/main --head HEAD --include-working-tree
|
|
783
|
+
qamap context . --write AGENTS.md
|
|
784
|
+
qamap init .
|
|
785
|
+
`);
|
|
786
|
+
}
|
|
787
|
+
function printManifestHelp() {
|
|
788
|
+
console.log(`QAMap ${VERSION}
|
|
789
|
+
|
|
790
|
+
Repository-level verification manifest.
|
|
791
|
+
|
|
792
|
+
Usage:
|
|
793
|
+
qamap manifest init [path] [--workspace-root <path>] [--write <file>] [--max-files <n>] [--force] [--format json] [--output <file>]
|
|
794
|
+
qamap manifest validate [path] [--workspace-root <path>] [--manifest <file>] [--format text|json|markdown] [--output <file>]
|
|
795
|
+
qamap manifest context [path] [--workspace-root <path>] [--max-files <n>] [--format text|json|markdown] [--output <file>]
|
|
796
|
+
qamap manifest explain [path] [--workspace-root <path>] [--manifest <file>] [--base <ref>] [--head <ref>] [--include-working-tree] [--format text|json|markdown] [--output <file>]
|
|
797
|
+
|
|
798
|
+
Examples:
|
|
799
|
+
qamap manifest init .
|
|
800
|
+
qamap manifest init services/offer --workspace-root .
|
|
801
|
+
qamap manifest init . --write .qamap/manifest.yaml --force
|
|
802
|
+
qamap manifest init . --write /tmp/qamap-manifest.yaml
|
|
803
|
+
qamap manifest validate .
|
|
804
|
+
qamap manifest validate . --manifest /tmp/qamap-manifest.yaml
|
|
805
|
+
qamap manifest context .
|
|
806
|
+
qamap manifest explain . --manifest /tmp/qamap-manifest.yaml --base origin/main --head HEAD
|
|
807
|
+
`);
|
|
808
|
+
}
|
|
809
|
+
function printE2eHelp() {
|
|
810
|
+
console.log(`QAMap ${VERSION}
|
|
811
|
+
|
|
812
|
+
E2E planning for AI-assisted changes.
|
|
813
|
+
|
|
814
|
+
Usage:
|
|
815
|
+
qamap e2e plan [path] [--workspace-root <path>] [--base <ref>] [--head <ref>] [--include-working-tree] [--record-history] [--format <format>] [--output <file>]
|
|
816
|
+
qamap e2e setup [path] [--workspace-root <path>] [--runner maestro|playwright] [--force] [--format <format>] [--output <file>]
|
|
817
|
+
qamap e2e draft [path] [--workspace-root <path>] [--base <ref>] [--head <ref>] [--include-working-tree] [--runner maestro|playwright|manual] [--output <dir>] [--dry-run] [--force]
|
|
818
|
+
|
|
819
|
+
Examples:
|
|
820
|
+
qamap e2e plan . --base origin/main --head HEAD
|
|
821
|
+
qamap e2e plan . --base origin/main --head HEAD --record-history
|
|
822
|
+
qamap e2e setup . --runner playwright
|
|
823
|
+
qamap e2e setup apps/mobile --workspace-root . --runner maestro
|
|
824
|
+
qamap e2e draft . --base origin/main --head HEAD --dry-run
|
|
825
|
+
qamap e2e plan apps/mobile --workspace-root . --include-working-tree
|
|
826
|
+
`);
|
|
827
|
+
}
|
|
828
|
+
function printHistoryHelp() {
|
|
829
|
+
console.log(`QAMap ${VERSION}
|
|
830
|
+
|
|
831
|
+
Local history for QAMap analysis runs.
|
|
832
|
+
|
|
833
|
+
Usage:
|
|
834
|
+
qamap history init [path] [--json] [--output <file>]
|
|
835
|
+
|
|
836
|
+
Examples:
|
|
837
|
+
qamap history init .
|
|
838
|
+
`);
|
|
839
|
+
}
|
|
840
|
+
function printFlowsHelp() {
|
|
841
|
+
console.log(`QAMap ${VERSION}
|
|
842
|
+
|
|
843
|
+
Core flow definitions for project-specific E2E planning.
|
|
844
|
+
|
|
845
|
+
Usage:
|
|
846
|
+
qamap flows init [path] [--write <file>] [--force]
|
|
847
|
+
qamap flows suggest [path] [--workspace-root <path>] [--base <ref>] [--head <ref>] [--include-working-tree] [--format text|json|markdown] [--output <file>] [--write <file>] [--force]
|
|
848
|
+
|
|
849
|
+
Examples:
|
|
850
|
+
qamap flows init .
|
|
851
|
+
qamap flows suggest . --base origin/main --head HEAD
|
|
852
|
+
qamap flows suggest services/offer --workspace-root . --include-working-tree
|
|
853
|
+
`);
|
|
854
|
+
}
|
|
855
|
+
function printDomainsHelp() {
|
|
856
|
+
console.log(`QAMap ${VERSION}
|
|
857
|
+
|
|
858
|
+
Domain definitions for project-specific E2E naming and route hints.
|
|
859
|
+
|
|
860
|
+
Usage:
|
|
861
|
+
qamap domains init [path] [--write <file>] [--force]
|
|
862
|
+
qamap domains suggest [path] [--workspace-root <path>] [--base <ref>] [--head <ref>] [--include-working-tree] [--format text|json|markdown] [--output <file>] [--write <file>] [--force]
|
|
863
|
+
|
|
864
|
+
Examples:
|
|
865
|
+
qamap domains init .
|
|
866
|
+
qamap domains suggest . --base origin/main --head HEAD
|
|
867
|
+
qamap domains suggest services/offer --workspace-root . --include-working-tree
|
|
868
|
+
`);
|
|
869
|
+
}
|
|
870
|
+
main(process.argv.slice(2))
|
|
871
|
+
.then((code) => {
|
|
872
|
+
process.exitCode = code;
|
|
873
|
+
})
|
|
874
|
+
.catch((error) => {
|
|
875
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
876
|
+
console.error(`QAMap error: ${message}`);
|
|
877
|
+
process.exitCode = 1;
|
|
878
|
+
});
|
|
879
|
+
//# sourceMappingURL=cli.js.map
|