airgen-cli 0.13.0 → 0.14.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/dist/commands/diagrams.js +4 -0
- package/dist/commands/lint.js +11 -0
- package/dist/commands/requirements.js +137 -1
- package/package.json +1 -1
|
@@ -264,6 +264,7 @@ export function registerDiagramCommands(program, client) {
|
|
|
264
264
|
.option("--direction <dir>", "Layout direction for mermaid: TB, LR, BT, RL", "TB")
|
|
265
265
|
.option("-o, --output <file>", "Write to file instead of stdout")
|
|
266
266
|
.option("--wrap", "Wrap mermaid in markdown fenced code block")
|
|
267
|
+
.option("--clean", "Strip classDef/class/style lines from mermaid output (maximum compatibility)")
|
|
267
268
|
.action(async (tenant, project, id, opts) => {
|
|
268
269
|
// Fetch diagram metadata + blocks + connectors in parallel
|
|
269
270
|
const [diagramData, blocksData, connectorsData] = await Promise.all([
|
|
@@ -281,6 +282,9 @@ export function registerDiagramCommands(program, client) {
|
|
|
281
282
|
let rendered;
|
|
282
283
|
if (opts.format === "mermaid") {
|
|
283
284
|
rendered = renderMermaid(blocks, connectors, opts.direction);
|
|
285
|
+
if (opts.clean) {
|
|
286
|
+
rendered = rendered.split("\n").filter(l => !l.trim().startsWith("classDef ") && !l.trim().startsWith("class ") && !l.trim().startsWith("style ")).join("\n");
|
|
287
|
+
}
|
|
284
288
|
if (isJsonMode()) {
|
|
285
289
|
output({ mermaid: rendered, blocks: blocks.length, connectors: connectors.length });
|
|
286
290
|
return;
|
package/dist/commands/lint.js
CHANGED
|
@@ -638,6 +638,7 @@ export function registerLintCommands(program, client) {
|
|
|
638
638
|
.option("--save-baseline <file>", "Write current finding titles to a baseline file for future suppression")
|
|
639
639
|
.option("--threshold <n>", "Jaccard similarity threshold (0.0-1.0)", "0.6")
|
|
640
640
|
.option("--spray-threshold <n>", "Outgoing trace link count for spray pattern detection", "8")
|
|
641
|
+
.option("--min-severity <level>", "Only show findings at this severity or above: low, medium, high", "low")
|
|
641
642
|
.action(async (tenant, project, opts) => {
|
|
642
643
|
const uht = new UhtClient();
|
|
643
644
|
if (!uht.isConfigured) {
|
|
@@ -744,6 +745,16 @@ export function registerLintCommands(program, client) {
|
|
|
744
745
|
if (suppressed > 0)
|
|
745
746
|
console.error(`Suppressed ${suppressed} known finding(s).`);
|
|
746
747
|
}
|
|
748
|
+
// Step 5d: Apply severity filter
|
|
749
|
+
const sevOrder = { high: 0, medium: 1, low: 2 };
|
|
750
|
+
const minSev = sevOrder[opts.minSeverity] ?? 2;
|
|
751
|
+
if (minSev < 2) {
|
|
752
|
+
const before = findings.length;
|
|
753
|
+
findings = findings.filter(f => sevOrder[f.severity] <= minSev);
|
|
754
|
+
const filtered = before - findings.length;
|
|
755
|
+
if (filtered > 0)
|
|
756
|
+
console.error(`Filtered ${filtered} finding(s) below ${opts.minSeverity} severity.`);
|
|
757
|
+
}
|
|
747
758
|
// Step 6: Output report
|
|
748
759
|
let report;
|
|
749
760
|
if (opts.format === "json" || isJsonMode()) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import { output, printTable, isJsonMode, truncate } from "../output.js";
|
|
2
3
|
import { resolveRequirementId } from "../resolve.js";
|
|
3
4
|
export function registerRequirementCommands(program, client) {
|
|
@@ -8,12 +9,38 @@ export function registerRequirementCommands(program, client) {
|
|
|
8
9
|
.argument("<tenant>", "Tenant slug")
|
|
9
10
|
.argument("<project>", "Project slug")
|
|
10
11
|
.option("-p, --page <n>", "Page number", "1")
|
|
11
|
-
.option("-l, --limit <n>", "Items per page", "
|
|
12
|
+
.option("-l, --limit <n>", "Items per page (use 'all' to fetch everything)", "50")
|
|
12
13
|
.option("--sort <field>", "Sort by: ref, createdAt, qaScore")
|
|
13
14
|
.option("--order <dir>", "Sort order: asc, desc")
|
|
14
15
|
.option("--tags <tags>", "Comma-separated tags to filter by (server-side)")
|
|
15
16
|
.option("--document <slug>", "Filter by document slug (server-side)")
|
|
16
17
|
.action(async (tenant, project, opts) => {
|
|
18
|
+
// Handle --limit all: fetch all pages
|
|
19
|
+
if (opts.limit.toLowerCase() === "all") {
|
|
20
|
+
const all = [];
|
|
21
|
+
for (let page = 1; page <= 50; page++) {
|
|
22
|
+
const params = {
|
|
23
|
+
page: String(page), limit: "500",
|
|
24
|
+
sortBy: opts.sort, sortOrder: opts.order,
|
|
25
|
+
};
|
|
26
|
+
if (opts.tags)
|
|
27
|
+
params.tags = opts.tags;
|
|
28
|
+
if (opts.document)
|
|
29
|
+
params.documentSlug = opts.document;
|
|
30
|
+
const data = await client.get(`/requirements/${tenant}/${project}`, params);
|
|
31
|
+
all.push(...(data.data ?? []));
|
|
32
|
+
if (page >= (data.meta?.totalPages ?? 1))
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
if (isJsonMode()) {
|
|
36
|
+
output({ data: all, meta: { totalItems: all.length } });
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.log(`All requirements: ${all.length}\n`);
|
|
40
|
+
printTable(["Ref", "Text", "Pattern", "QA", "Tags"], all.map(r => [r.ref ?? "?", truncate(r.text ?? "", 60), r.pattern ?? "", r.qaScore != null ? String(r.qaScore) : "-", (r.tags ?? []).join(", ")]));
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
17
44
|
const params = {
|
|
18
45
|
page: opts.page,
|
|
19
46
|
limit: opts.limit,
|
|
@@ -258,4 +285,113 @@ export function registerRequirementCommands(program, client) {
|
|
|
258
285
|
]));
|
|
259
286
|
}
|
|
260
287
|
});
|
|
288
|
+
// ── reassign: move requirement to a different document/section ──
|
|
289
|
+
cmd
|
|
290
|
+
.command("reassign")
|
|
291
|
+
.description("Move a requirement to a different document/section (preserves ID and trace links)")
|
|
292
|
+
.argument("<tenant>", "Tenant slug")
|
|
293
|
+
.argument("<project>", "Project slug")
|
|
294
|
+
.argument("<id>", "Requirement ref, ID, or hashId")
|
|
295
|
+
.requiredOption("--section <id>", "Target section ID (determines the target document)")
|
|
296
|
+
.action(async (tenant, project, id, opts) => {
|
|
297
|
+
const resolvedId = await resolveRequirementId(client, tenant, project, id);
|
|
298
|
+
await client.patch(`/requirements/${tenant}/${project}/${resolvedId}`, {
|
|
299
|
+
sectionId: opts.section,
|
|
300
|
+
});
|
|
301
|
+
if (isJsonMode()) {
|
|
302
|
+
output({ ok: true, movedTo: opts.section });
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
console.log(`Requirement reassigned to section ${opts.section}. Ref will update to match new document prefix.`);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
// ── bulk-create: create multiple requirements from JSON array ──
|
|
309
|
+
cmd
|
|
310
|
+
.command("bulk-create")
|
|
311
|
+
.description("Create multiple requirements from a JSON file (array of objects)")
|
|
312
|
+
.argument("<tenant>", "Tenant slug")
|
|
313
|
+
.argument("<project-key>", "Project key")
|
|
314
|
+
.requiredOption("--file <path>", "Path to JSON file (array of {text, document?, section?, verification?, rationale?, tags?, idempotencyKey?})")
|
|
315
|
+
.option("--dry-run", "Validate without creating")
|
|
316
|
+
.action(async (tenant, projectKey, opts) => {
|
|
317
|
+
const content = readFileSync(opts.file, "utf-8");
|
|
318
|
+
let items;
|
|
319
|
+
try {
|
|
320
|
+
items = JSON.parse(content);
|
|
321
|
+
if (!Array.isArray(items))
|
|
322
|
+
throw new Error("File must contain a JSON array");
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
console.error(`Invalid JSON: ${err.message}`);
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
console.error(`Processing ${items.length} requirement(s)...`);
|
|
329
|
+
let created = 0, skipped = 0, errors = 0;
|
|
330
|
+
for (let i = 0; i < items.length; i++) {
|
|
331
|
+
const item = items[i];
|
|
332
|
+
const text = String(item.text ?? "").trim();
|
|
333
|
+
if (!text || text.length < 10) {
|
|
334
|
+
console.error(` [${i}] Skipped: text too short`);
|
|
335
|
+
skipped++;
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (opts.dryRun) {
|
|
339
|
+
console.log(` [dry-run] ${truncate(text, 80)}`);
|
|
340
|
+
created++;
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
const body = { tenant, projectKey, text };
|
|
345
|
+
if (item.document || item.documentSlug)
|
|
346
|
+
body.documentSlug = item.document ?? item.documentSlug;
|
|
347
|
+
if (item.section || item.sectionId)
|
|
348
|
+
body.sectionId = item.section ?? item.sectionId;
|
|
349
|
+
if (item.verification)
|
|
350
|
+
body.verification = item.verification;
|
|
351
|
+
if (item.rationale)
|
|
352
|
+
body.rationale = item.rationale;
|
|
353
|
+
if (item.pattern)
|
|
354
|
+
body.pattern = item.pattern;
|
|
355
|
+
if (item.compliance)
|
|
356
|
+
body.complianceStatus = item.compliance;
|
|
357
|
+
if (item.idempotencyKey)
|
|
358
|
+
body.idempotencyKey = item.idempotencyKey;
|
|
359
|
+
if (Array.isArray(item.tags))
|
|
360
|
+
body.tags = item.tags;
|
|
361
|
+
else if (typeof item.tags === "string")
|
|
362
|
+
body.tags = item.tags.split(",").map(t => t.trim());
|
|
363
|
+
await client.post("/requirements", body);
|
|
364
|
+
created++;
|
|
365
|
+
}
|
|
366
|
+
catch (err) {
|
|
367
|
+
console.error(` [${i}] Error: ${err.message}`);
|
|
368
|
+
errors++;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
console.log(`${opts.dryRun ? "Would create" : "Created"} ${created}. Skipped: ${skipped}. Errors: ${errors}.`);
|
|
372
|
+
});
|
|
373
|
+
// ── text-search: search requirements by text content ──
|
|
374
|
+
cmd
|
|
375
|
+
.command("text-search")
|
|
376
|
+
.description("Search requirements by text content (case-insensitive substring match)")
|
|
377
|
+
.argument("<tenant>", "Tenant slug")
|
|
378
|
+
.argument("<project>", "Project slug")
|
|
379
|
+
.requiredOption("--text <query>", "Text to search for")
|
|
380
|
+
.option("-l, --limit <n>", "Max results", "50")
|
|
381
|
+
.action(async (tenant, project, opts) => {
|
|
382
|
+
const data = await client.get(`/requirements/${tenant}/${project}`, { textContains: opts.text, limit: opts.limit, sortBy: "ref", sortOrder: "asc" });
|
|
383
|
+
const reqs = data.data ?? [];
|
|
384
|
+
if (isJsonMode()) {
|
|
385
|
+
output(reqs);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
console.log(`Found ${reqs.length} requirement(s) matching "${opts.text}":\n`);
|
|
389
|
+
printTable(["Ref", "Text", "Document", "QA"], reqs.map(r => [
|
|
390
|
+
r.ref ?? "?",
|
|
391
|
+
truncate(r.text ?? "", 55),
|
|
392
|
+
r.documentSlug ?? "",
|
|
393
|
+
r.qaScore != null ? String(r.qaScore) : "-",
|
|
394
|
+
]));
|
|
395
|
+
}
|
|
396
|
+
});
|
|
261
397
|
}
|