airgen-cli 0.4.0 → 0.5.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.
|
@@ -1,29 +1,41 @@
|
|
|
1
1
|
import { writeFileSync } from "node:fs";
|
|
2
2
|
import { output, printTable, isJsonMode } from "../output.js";
|
|
3
3
|
// ── Mermaid rendering helpers ─────────────────────────────────
|
|
4
|
+
// Counter for generating short, readable Mermaid node IDs
|
|
5
|
+
let mermaidNodeCounter = 0;
|
|
6
|
+
const mermaidIdMap = new Map();
|
|
7
|
+
function resetMermaidIds() {
|
|
8
|
+
mermaidNodeCounter = 0;
|
|
9
|
+
mermaidIdMap.clear();
|
|
10
|
+
}
|
|
4
11
|
function sanitizeId(id) {
|
|
5
|
-
|
|
12
|
+
let short = mermaidIdMap.get(id);
|
|
13
|
+
if (!short) {
|
|
14
|
+
short = `n${mermaidNodeCounter++}`;
|
|
15
|
+
mermaidIdMap.set(id, short);
|
|
16
|
+
}
|
|
17
|
+
return short;
|
|
6
18
|
}
|
|
7
19
|
function mermaidNodeShape(block) {
|
|
8
20
|
const id = sanitizeId(block.id);
|
|
9
21
|
const label = block.name.replace(/"/g, "'");
|
|
10
22
|
const stereo = block.stereotype?.replace(/[«»<>]/g, "") ?? block.kind ?? "block";
|
|
11
|
-
const display = `"
|
|
23
|
+
const display = `"<<${stereo}>><br/>${label}"`;
|
|
12
24
|
switch (block.kind) {
|
|
13
25
|
case "system": return `${id}[${display}]`;
|
|
14
26
|
case "subsystem": return `${id}[${display}]`;
|
|
15
27
|
case "actor": return `${id}([${display}])`;
|
|
16
|
-
case "external": return `${id}
|
|
28
|
+
case "external": return `${id}[${display}]`;
|
|
17
29
|
case "interface": return `${id}{{${display}}}`;
|
|
18
|
-
default: return `${id}[${display}]`;
|
|
30
|
+
default: return `${id}[${display}]`;
|
|
19
31
|
}
|
|
20
32
|
}
|
|
21
33
|
function mermaidArrow(kind) {
|
|
22
34
|
switch (kind) {
|
|
23
35
|
case "flow": return "==>";
|
|
24
36
|
case "dependency": return "-.->";
|
|
25
|
-
case "composition": return "
|
|
26
|
-
default: return "-->";
|
|
37
|
+
case "composition": return "-->";
|
|
38
|
+
default: return "-->";
|
|
27
39
|
}
|
|
28
40
|
}
|
|
29
41
|
function mermaidStyle(block) {
|
|
@@ -174,6 +186,7 @@ function renderTerminal(blocks, connectors) {
|
|
|
174
186
|
return lines.join("\n");
|
|
175
187
|
}
|
|
176
188
|
function renderMermaid(blocks, connectors, direction) {
|
|
189
|
+
resetMermaidIds();
|
|
177
190
|
const lines = [`flowchart ${direction}`];
|
|
178
191
|
// Nodes
|
|
179
192
|
for (const b of blocks) {
|
|
@@ -185,7 +198,8 @@ function renderMermaid(blocks, connectors, direction) {
|
|
|
185
198
|
const tgt = sanitizeId(c.target);
|
|
186
199
|
const arrow = mermaidArrow(c.kind);
|
|
187
200
|
if (c.label) {
|
|
188
|
-
|
|
201
|
+
const edgeLabel = c.label.replace(/"/g, "'");
|
|
202
|
+
lines.push(` ${src} ${arrow}|${edgeLabel}| ${tgt}`);
|
|
189
203
|
}
|
|
190
204
|
else {
|
|
191
205
|
lines.push(` ${src} ${arrow} ${tgt}`);
|
|
@@ -346,6 +360,32 @@ export function registerDiagramCommands(program, client) {
|
|
|
346
360
|
});
|
|
347
361
|
// Blocks sub-group
|
|
348
362
|
const blocks = cmd.command("blocks").description("Manage blocks in diagrams");
|
|
363
|
+
blocks
|
|
364
|
+
.command("list")
|
|
365
|
+
.description("List blocks in a diagram")
|
|
366
|
+
.argument("<tenant>", "Tenant slug")
|
|
367
|
+
.argument("<project>", "Project slug")
|
|
368
|
+
.argument("<diagram-id>", "Diagram ID")
|
|
369
|
+
.action(async (tenant, project, diagramId) => {
|
|
370
|
+
const data = await client.get(`/architecture/blocks/${tenant}/${project}/${diagramId}`);
|
|
371
|
+
const list = data.blocks ?? [];
|
|
372
|
+
if (isJsonMode()) {
|
|
373
|
+
output(list);
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
if (list.length === 0) {
|
|
377
|
+
console.log("No blocks in this diagram.");
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
printTable(["ID", "Name", "Kind", "Stereotype", "Ports"], list.map(b => [
|
|
381
|
+
b.id,
|
|
382
|
+
b.name,
|
|
383
|
+
b.kind ?? "",
|
|
384
|
+
b.stereotype ?? "",
|
|
385
|
+
String(b.ports?.length ?? 0),
|
|
386
|
+
]));
|
|
387
|
+
}
|
|
388
|
+
});
|
|
349
389
|
blocks
|
|
350
390
|
.command("library")
|
|
351
391
|
.description("Get all block definitions in the project")
|
package/dist/commands/lint.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { writeFileSync } from "node:fs";
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
2
2
|
import { UhtClient } from "../uht-client.js";
|
|
3
3
|
import { isJsonMode } from "../output.js";
|
|
4
4
|
// ── Constants ────────────────────────────────────────────────
|
|
@@ -320,6 +320,9 @@ export function registerLintCommands(program, client) {
|
|
|
320
320
|
.option("--concepts <n>", "Max concepts to classify", "15")
|
|
321
321
|
.option("--format <fmt>", "Output format: text, markdown, json", "text")
|
|
322
322
|
.option("-o, --output <file>", "Write report to file")
|
|
323
|
+
.option("--suppress <titles...>", "Suppress findings by title substring (repeatable)")
|
|
324
|
+
.option("--baseline <file>", "Suppress findings listed in a baseline file (one title per line)")
|
|
325
|
+
.option("--save-baseline <file>", "Write current finding titles to a baseline file for future suppression")
|
|
323
326
|
.action(async (tenant, project, opts) => {
|
|
324
327
|
const uht = new UhtClient();
|
|
325
328
|
if (!uht.isConfigured) {
|
|
@@ -391,7 +394,28 @@ export function registerLintCommands(program, client) {
|
|
|
391
394
|
}
|
|
392
395
|
// Step 5: Analyze findings
|
|
393
396
|
console.error("Analyzing...");
|
|
394
|
-
|
|
397
|
+
let findings = analyzeFindings(concepts, comparisons, requirements);
|
|
398
|
+
// Step 5b: Save baseline if requested (before suppression)
|
|
399
|
+
if (opts.saveBaseline) {
|
|
400
|
+
const titles = findings.map(f => f.title);
|
|
401
|
+
writeFileSync(opts.saveBaseline, titles.join("\n") + "\n", "utf-8");
|
|
402
|
+
console.error(`Baseline saved to ${opts.saveBaseline} (${titles.length} findings)`);
|
|
403
|
+
}
|
|
404
|
+
// Step 5c: Apply suppression
|
|
405
|
+
const suppressions = [...(opts.suppress ?? [])];
|
|
406
|
+
if (opts.baseline && existsSync(opts.baseline)) {
|
|
407
|
+
const baselineContent = readFileSync(opts.baseline, "utf-8");
|
|
408
|
+
const baselineLines = baselineContent.split("\n").map(l => l.trim()).filter(Boolean);
|
|
409
|
+
suppressions.push(...baselineLines);
|
|
410
|
+
}
|
|
411
|
+
if (suppressions.length > 0) {
|
|
412
|
+
const before = findings.length;
|
|
413
|
+
findings = findings.filter(f => !suppressions.some(s => f.title.includes(s)));
|
|
414
|
+
const suppressed = before - findings.length;
|
|
415
|
+
if (suppressed > 0) {
|
|
416
|
+
console.error(`Suppressed ${suppressed} known finding(s).`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
395
419
|
// Step 6: Output report
|
|
396
420
|
let report;
|
|
397
421
|
if (opts.format === "json" || isJsonMode()) {
|
package/dist/commands/reports.js
CHANGED
|
@@ -44,10 +44,10 @@ export function registerReportCommands(program, client) {
|
|
|
44
44
|
]);
|
|
45
45
|
const stats = {
|
|
46
46
|
requirements: reqs.meta?.totalItems ?? 0,
|
|
47
|
-
traceLinks: (links.
|
|
47
|
+
traceLinks: (links.traceLinks ?? []).length,
|
|
48
48
|
documents: (docs.documents ?? []).length,
|
|
49
49
|
diagrams: (diagrams.diagrams ?? []).length,
|
|
50
|
-
baselines: (baselines.
|
|
50
|
+
baselines: (baselines.items ?? []).length,
|
|
51
51
|
};
|
|
52
52
|
if (isJsonMode()) {
|
|
53
53
|
output(stats);
|
|
@@ -131,7 +131,7 @@ export function registerReportCommands(program, client) {
|
|
|
131
131
|
fetchAllRequirements(client, tenant, project),
|
|
132
132
|
client.get(`/trace-links/${tenant}/${project}`),
|
|
133
133
|
]);
|
|
134
|
-
const links = linkData.
|
|
134
|
+
const links = linkData.traceLinks ?? [];
|
|
135
135
|
const linked = new Set();
|
|
136
136
|
for (const l of links) {
|
|
137
137
|
if (l.sourceRequirementId)
|
|
@@ -12,7 +12,7 @@ export function registerTraceabilityCommands(program, client) {
|
|
|
12
12
|
? `/trace-links/${tenant}/${project}/${opts.requirement}`
|
|
13
13
|
: `/trace-links/${tenant}/${project}`;
|
|
14
14
|
const data = await client.get(path);
|
|
15
|
-
const links = data.
|
|
15
|
+
const links = data.traceLinks ?? [];
|
|
16
16
|
if (isJsonMode()) {
|
|
17
17
|
output(links);
|
|
18
18
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "airgen-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "AIRGen CLI — requirements engineering from the command line",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|
|
35
|
-
"url": "https://github.com/
|
|
35
|
+
"url": "https://github.com/Hollando78/airgen.git",
|
|
36
36
|
"directory": "packages/cli"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|