airgen-cli 0.14.0 → 0.16.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.
@@ -225,9 +225,13 @@ export function registerDiagramCommands(program, client) {
225
225
  .description("List all diagrams in a project")
226
226
  .argument("<tenant>", "Tenant slug")
227
227
  .argument("<project>", "Project slug")
228
- .action(async (tenant, project) => {
228
+ .option("--name <name>", "Filter by exact name match")
229
+ .action(async (tenant, project, opts) => {
229
230
  const data = await client.get(`/architecture/diagrams/${tenant}/${project}`);
230
- const diagrams = data.diagrams ?? [];
231
+ let diagrams = data.diagrams ?? [];
232
+ if (opts.name) {
233
+ diagrams = diagrams.filter(d => d.name === opts.name);
234
+ }
231
235
  if (isJsonMode()) {
232
236
  output(diagrams);
233
237
  }
@@ -283,7 +287,11 @@ export function registerDiagramCommands(program, client) {
283
287
  if (opts.format === "mermaid") {
284
288
  rendered = renderMermaid(blocks, connectors, opts.direction);
285
289
  if (opts.clean) {
286
- rendered = rendered.split("\n").filter(l => !l.trim().startsWith("classDef ") && !l.trim().startsWith("class ") && !l.trim().startsWith("style ")).join("\n");
290
+ rendered = rendered
291
+ .split("\n")
292
+ .filter(l => !l.trim().startsWith("classDef ") && !l.trim().startsWith("class ") && !l.trim().startsWith("style "))
293
+ .map(l => l.replace(/<<[^>]+>><br\/>/g, "")) // Strip <<stereotype>><br/> from node labels
294
+ .join("\n");
287
295
  }
288
296
  if (isJsonMode()) {
289
297
  output({ mermaid: rendered, blocks: blocks.length, connectors: connectors.length });
@@ -1,6 +1,140 @@
1
1
  import { readFileSync, writeFileSync, existsSync } from "node:fs";
2
2
  import { UhtClient } from "../uht-client.js";
3
3
  import { isJsonMode } from "../output.js";
4
+ const TRAIT_CHECKS = [
5
+ // Physical Layer (1-8)
6
+ { trait: "Physical Object", bit: 1, severity: "high", inverted: true,
7
+ missingWhat: "physical embodiment",
8
+ keywords: /temperature|shock|vibrat|humidity|nbc|contamina|electromagnetic|emc|climatic/i,
9
+ recommendation: "Add a requirement defining the physical embodiment (housing, LRU, equipment rack)." },
10
+ { trait: "Synthetic", bit: 2, severity: "medium",
11
+ missingWhat: "manufacturing/material requirements",
12
+ keywords: /manufactur|assembl|tolerance|material|fabricat|finish|coating|process|mach[iu]n/i,
13
+ recommendation: "Add manufacturing, material, or assembly requirements." },
14
+ { trait: "Biological/Biomimetic", bit: 3, severity: "high",
15
+ missingWhat: "biocompatibility/sterilization requirements",
16
+ keywords: /biocompat|steril|cytotox|implant|tissue|ISO.?10993|clean.?room|contaminat|biolog/i,
17
+ recommendation: "Add biocompatibility, sterilization, or biological safety requirements." },
18
+ { trait: "Powered", bit: 4, severity: "high",
19
+ missingWhat: "power source/budget requirements",
20
+ keywords: /power|voltage|current|battery|watt|energy|consumption|supply|mains|generator/i,
21
+ recommendation: "Add power source, voltage range, and consumption requirements." },
22
+ { trait: "Structural", bit: 5, severity: "medium",
23
+ missingWhat: "structural integrity requirements",
24
+ keywords: /load|stress|fatigue|deform|yield|strength|FEA|resonan|torque|buckling|stiffness/i,
25
+ recommendation: "Add load-bearing, stress, or fatigue requirements." },
26
+ { trait: "Observable", bit: 6, severity: "low",
27
+ missingWhat: "monitoring/diagnostics requirements",
28
+ keywords: /monitor|diagnos|instrument|telemetry|log|alert|BIT|health|status|indicator/i,
29
+ recommendation: "Add monitoring, diagnostics, or built-in-test requirements." },
30
+ { trait: "Physical Medium", bit: 7, severity: "medium",
31
+ missingWhat: "material property requirements",
32
+ keywords: /material|density|viscosity|thermal.?conduct|corrosion|wear|hardness|porosity/i,
33
+ recommendation: "Add material property, corrosion, or wear requirements." },
34
+ { trait: "Active", bit: 8, severity: "medium",
35
+ missingWhat: "stimulus-response/timing requirements",
36
+ keywords: /response.?time|latency|react|trigger|within.*(?:ms|second)|timeout|watchdog|stimul/i,
37
+ recommendation: "Add response time, latency, or stimulus-response requirements." },
38
+ // Functional Layer (9-16)
39
+ { trait: "Intentionally Designed", bit: 9, severity: "low",
40
+ missingWhat: "verification/validation method",
41
+ keywords: /verif|validat|test|inspect|review|acceptance|qualif|demonstrat/i,
42
+ recommendation: "Specify a verification or validation method." },
43
+ { trait: "Outputs Effect", bit: 10, severity: "medium",
44
+ missingWhat: "output specification",
45
+ keywords: /output|emit|produce|generate|signal|transmit|radiat|illuminate|actuate/i,
46
+ recommendation: "Add output specification requirements (signal type, range, accuracy)." },
47
+ { trait: "Processes Signals/Logic", bit: 11, severity: "medium",
48
+ missingWhat: "I/O format/protocol requirements",
49
+ keywords: /protocol|format|interface|data|message|bus|bandwidth|encoding|API|input.*output/i,
50
+ recommendation: "Add data format, protocol, or interface requirements." },
51
+ { trait: "State-Transforming", bit: 12, severity: "medium",
52
+ missingWhat: "state machine/mode transition requirements",
53
+ keywords: /state|mode|transition|startup|shutdown|initializ|reset|idle|standby|operat.*mode/i,
54
+ recommendation: "Add state machine, mode transition, or initialization requirements." },
55
+ { trait: "Human-Interactive", bit: 13, severity: "medium",
56
+ missingWhat: "usability/HMI requirements",
57
+ keywords: /HMI|display|ergonomic|response.?time|feedback|indicator|touch|screen|readab|legib/i,
58
+ recommendation: "Add usability, HMI, or human factors requirements." },
59
+ { trait: "System-integrated", bit: 14, severity: "medium",
60
+ missingWhat: "interface/integration requirements",
61
+ keywords: /interface|integrat|compatible|interoperab|plug|connector|harness|mount|install/i,
62
+ recommendation: "Add interface, integration, or interoperability requirements." },
63
+ { trait: "Functionally Autonomous", bit: 15, severity: "high",
64
+ missingWhat: "safety/override constraints",
65
+ keywords: /safety|watchdog|override|kill.?switch|emergency.?stop|fail.?safe|interlock|manual.?override|human.?in.?the.?loop/i,
66
+ recommendation: "Add safety constraints: override mechanism, watchdog, fail-safe state, or human-in-the-loop." },
67
+ { trait: "System-Essential", bit: 16, severity: "medium",
68
+ missingWhat: "redundancy/failover requirements",
69
+ keywords: /redundan|failover|backup|graceful.?degrad|fault.?toleran|availability|single.?point.?of.?failure|hot.?standby/i,
70
+ recommendation: "Add redundancy, failover, or fault tolerance requirements." },
71
+ // Abstract Layer (17-24)
72
+ { trait: "Symbolic", bit: 17, severity: "low",
73
+ missingWhat: "representation fidelity requirements",
74
+ keywords: /accuracy|fidelity|resolution|precision|representation|model|symbol|semantic/i,
75
+ recommendation: "Add representation accuracy, fidelity, or resolution requirements." },
76
+ { trait: "Signalling", bit: 18, severity: "medium",
77
+ missingWhat: "signal integrity/protocol requirements",
78
+ keywords: /protocol|bandwidth|latency|throughput|packet|frame|bit.?rate|SNR|jitter|signal/i,
79
+ recommendation: "Add signal protocol, bandwidth, or integrity requirements." },
80
+ { trait: "Rule-governed", bit: 19, severity: "low",
81
+ missingWhat: "algorithm/logic validation requirements",
82
+ keywords: /algorithm|logic|rule|protocol|compliance|valid|verify|formal|deterministic/i,
83
+ recommendation: "Add algorithm validation or formal verification requirements." },
84
+ { trait: "Compositional", bit: 20, severity: "low",
85
+ missingWhat: "modularity/interface requirements",
86
+ keywords: /module|layer|component|partition|encapsulat|abstraction|API|decompos|architect/i,
87
+ recommendation: "Add modularity, decomposition, or internal interface requirements." },
88
+ { trait: "Normative", bit: 21, severity: "medium",
89
+ missingWhat: "constraint validation requirements",
90
+ keywords: /constraint|rule|limit|bound|threshold|policy|enforce|comply|govern/i,
91
+ recommendation: "Add constraint enforcement or policy compliance requirements." },
92
+ { trait: "Meta", bit: 22, severity: "low",
93
+ missingWhat: "configuration/metadata requirements",
94
+ keywords: /config|metadata|schema|version|self.?test|self.?diagnos|introspect|calibrat/i,
95
+ recommendation: "Add configuration management, versioning, or self-test requirements." },
96
+ { trait: "Temporal", bit: 23, severity: "medium",
97
+ missingWhat: "timing/scheduling requirements",
98
+ keywords: /timing|sequence|schedul|deadline|period|interval|cycle|synchron|clock|real.?time/i,
99
+ recommendation: "Add timing, scheduling, or real-time requirements." },
100
+ { trait: "Digital/Virtual", bit: 24, severity: "medium",
101
+ missingWhat: "cybersecurity requirements",
102
+ keywords: /security|authenticat|encrypt|access.?control|authoriz|cyber|firewall|TLS|SSL|certificate/i,
103
+ recommendation: "Add cybersecurity, authentication, or encryption requirements." },
104
+ // Social Layer (25-32)
105
+ { trait: "Social Construct", bit: 25, severity: "low",
106
+ missingWhat: "stakeholder/user requirements",
107
+ keywords: /stakeholder|user|role|persona|scenario|use.?case|workflow|actor/i,
108
+ recommendation: "Add stakeholder, user role, or use case requirements." },
109
+ { trait: "Institutionally Defined", bit: 26, severity: "medium",
110
+ missingWhat: "standard/specification references",
111
+ keywords: /standard|specification|definition|classif|categor|nomenclature|taxonomy|MIL.?STD|DEF.?STAN/i,
112
+ recommendation: "Add references to applicable standards or specifications." },
113
+ { trait: "Identity-Linked", bit: 27, severity: "low",
114
+ missingWhat: "access control/identity requirements",
115
+ keywords: /role|identity|credential|permission|access|privilege|authenticat|authoriz/i,
116
+ recommendation: "Add access control, role-based, or identity management requirements." },
117
+ { trait: "Regulated", bit: 28, severity: "medium",
118
+ missingWhat: "compliance/certification requirements",
119
+ keywords: /standard|compliance|certif|approv|regulat|accredit|qualif|homologat|type.?approv/i,
120
+ recommendation: "Add compliance, certification, or regulatory approval requirements." },
121
+ { trait: "Economically Significant", bit: 29, severity: "low",
122
+ missingWhat: "cost/lifecycle requirements",
123
+ keywords: /cost|budget|price|procurement|COTS|lifecycle|maintenance|obsolescence|sustainab/i,
124
+ recommendation: "Add cost, lifecycle, or obsolescence management requirements." },
125
+ { trait: "Politicised", bit: 30, severity: "low",
126
+ missingWhat: "export control/classification requirements",
127
+ keywords: /export.?control|ITAR|EAR|classification|restricted|controlled|clearance|secret|classified/i,
128
+ recommendation: "Add export control, security classification, or handling requirements." },
129
+ { trait: "Ritualised", bit: 31, severity: "low",
130
+ missingWhat: "procedure/protocol requirements",
131
+ keywords: /procedure|protocol|ceremony|ritual|formal|checklist|handover|commissioning/i,
132
+ recommendation: "Add procedural, commissioning, or handover requirements." },
133
+ { trait: "Ethically Significant", bit: 32, severity: "medium",
134
+ missingWhat: "ethical/safety requirements",
135
+ keywords: /ethical|moral|harm|consent|privacy|bias|proportional|rules.?of.?engagement|collateral/i,
136
+ recommendation: "Add ethical, proportionality, or rules-of-engagement requirements." },
137
+ ];
4
138
  // ── Constants ────────────────────────────────────────────────
5
139
  const PAGE_SIZE = 100;
6
140
  const MAX_PAGES = 50;
@@ -179,128 +313,46 @@ function topConcepts(conceptRefs, maxCount) {
179
313
  function analyzeFindings(concepts, comparisons, requirements, traceLinks, reqTierMap, conceptRefs, options) {
180
314
  const findings = [];
181
315
  const conceptMap = new Map(concepts.map(c => [c.name, c]));
182
- // Helper: get requirement texts for a concept
316
+ // Helper: get all requirement text for a concept (joined)
183
317
  const reqTextsFor = (c) => c.reqs.map(ref => requirements.find(r => r.ref === ref)?.text ?? "").join(" ");
184
- // ── 1a. Physical mismatch (existing) ──────────────────────
185
- const envKeywords = /temperature|shock|vibrat|humidity|nbc|contamina|electromagnetic|emc|climatic/i;
186
- for (const c of concepts) {
187
- if (c.isPhysical)
188
- continue;
189
- const envReqs = c.reqs.filter(ref => {
190
- const req = requirements.find(r => r.ref === ref);
191
- return req?.text && envKeywords.test(req.text);
192
- });
193
- if (envReqs.length > 0) {
194
- findings.push({
195
- severity: "high",
196
- category: "Ontological Mismatch",
197
- title: `"${c.name}" lacks Physical Object trait but has physical constraints`,
198
- description: `UHT classifies "${c.name}" (${c.hexCode}) without Physical Object, but ${envReqs.length} requirement(s) impose environmental constraints.`,
199
- affectedReqs: envReqs,
200
- recommendation: `Add a requirement defining the physical embodiment of "${c.name}" (housing, LRU, equipment rack).`,
201
- });
202
- }
203
- }
204
- // ── 1b. Powered without power budget ──────────────────────
205
- const powerKeywords = /power|voltage|current|battery|watt|energy|consumption|supply|mains|generator/i;
206
- for (const c of concepts) {
207
- if (!c.isPowered)
208
- continue;
209
- const allText = reqTextsFor(c);
210
- if (!powerKeywords.test(allText)) {
211
- findings.push({
212
- severity: "high",
213
- category: "Ontological Mismatch",
214
- title: `"${c.name}" is Powered but no requirements specify power source or budget`,
215
- description: `UHT classifies "${c.name}" (${c.hexCode}) with Powered trait, but no requirements mention power supply, voltage, current, or energy budget.`,
216
- affectedReqs: c.reqs,
217
- recommendation: `Add power source, voltage range, and consumption requirements for "${c.name}".`,
218
- });
219
- }
220
- }
221
- // ── 1c. Autonomous without safety constraints ─────────────
222
- const safetyKeywords = /safety|watchdog|override|kill.?switch|emergency.?stop|fail.?safe|interlock|manual.?override|human.?in.?the.?loop/i;
223
- for (const c of concepts) {
224
- if (!c.isAutonomous)
225
- continue;
226
- const allText = reqTextsFor(c);
227
- if (!safetyKeywords.test(allText)) {
228
- findings.push({
229
- severity: "high",
230
- category: "Ontological Mismatch",
231
- title: `"${c.name}" is Functionally Autonomous but has no safety constraints`,
232
- description: `UHT classifies "${c.name}" (${c.hexCode}) as autonomous, but no requirements specify safety constraints, watchdog, override, or emergency stop.`,
233
- affectedReqs: c.reqs,
234
- recommendation: `Add safety requirements: override mechanism, watchdog timer, fail-safe state, or human-in-the-loop constraints for "${c.name}".`,
235
- });
236
- }
237
- }
238
- // ── 1d. Human-Interactive without usability ────────────────
239
- const usabilityKeywords = /hmi|display|ergonomic|response.?time|feedback|indicator|user.?interface|touch|screen|readab|legib|contrast|font|icon/i;
240
- for (const c of concepts) {
241
- if (!c.isHumanInteractive)
242
- continue;
243
- const allText = reqTextsFor(c);
244
- if (!usabilityKeywords.test(allText)) {
245
- findings.push({
246
- severity: "medium",
247
- category: "Ontological Mismatch",
248
- title: `"${c.name}" is Human-Interactive but has no usability requirements`,
249
- description: `UHT classifies "${c.name}" (${c.hexCode}) as human-interactive, but no requirements address HMI, display, ergonomics, or user feedback.`,
250
- affectedReqs: c.reqs,
251
- recommendation: `Add usability requirements: display format, response time, ergonomic constraints, or feedback indicators for "${c.name}".`,
252
- });
253
- }
254
- }
255
- // ── 1e. Digital/Virtual without cybersecurity ──────────────
256
- const cyberKeywords = /security|authenticat|encrypt|access.?control|authoriz|cyber|firewall|certificate|tls|ssl|password|credential|privilege/i;
257
- for (const c of concepts) {
258
- if (!c.isDigitalVirtual)
259
- continue;
260
- const allText = reqTextsFor(c);
261
- if (!cyberKeywords.test(allText)) {
262
- findings.push({
263
- severity: "medium",
264
- category: "Ontological Mismatch",
265
- title: `"${c.name}" is Digital/Virtual but has no cybersecurity requirements`,
266
- description: `UHT classifies "${c.name}" (${c.hexCode}) as digital/virtual, but no requirements address security, authentication, encryption, or access control.`,
267
- affectedReqs: c.reqs,
268
- recommendation: `Add cybersecurity requirements: authentication, encryption, access control, or data protection for "${c.name}".`,
269
- });
270
- }
271
- }
272
- // ── 1f. Regulated without compliance ──────────────────────
273
- const complianceKeywords = /standard|compliance|certif|approv|regulat|accredit|qualif|homologat|type.?approv|conformance|audit/i;
274
- for (const c of concepts) {
275
- if (!c.isRegulated)
276
- continue;
277
- const allText = reqTextsFor(c);
278
- if (!complianceKeywords.test(allText)) {
279
- findings.push({
280
- severity: "medium",
281
- category: "Ontological Mismatch",
282
- title: `"${c.name}" is Regulated but has no compliance requirements`,
283
- description: `UHT classifies "${c.name}" (${c.hexCode}) as regulated, but no requirements reference standards, certification, or compliance.`,
284
- affectedReqs: c.reqs,
285
- recommendation: `Add compliance requirements: applicable standards, certification authority, or qualification criteria for "${c.name}".`,
286
- });
287
- }
288
- }
289
- // ── 1g. System-Essential without redundancy ────────────────
290
- const redundancyKeywords = /redundan|failover|backup|graceful.?degrad|hot.?standby|cold.?standby|replicate|fault.?toleran|availability|\d+\s*%.*uptime|single.?point.?of.?failure/i;
291
- for (const c of concepts) {
292
- if (!c.isSystemEssential)
293
- continue;
294
- const allText = reqTextsFor(c);
295
- if (!redundancyKeywords.test(allText)) {
296
- findings.push({
297
- severity: "medium",
298
- category: "Ontological Mismatch",
299
- title: `"${c.name}" is System-Essential but has no redundancy requirements`,
300
- description: `UHT classifies "${c.name}" (${c.hexCode}) as system-essential, but no requirements address redundancy, failover, or fault tolerance.`,
301
- affectedReqs: c.reqs,
302
- recommendation: `Add redundancy or availability requirements: failover mechanism, backup, or graceful degradation for "${c.name}".`,
303
- });
318
+ // ── UHT Trait Checks (all 32 traits, data-driven) ─────────
319
+ for (const check of TRAIT_CHECKS) {
320
+ for (const c of concepts) {
321
+ if (check.inverted) {
322
+ // Inverted check (Physical Object): fires when trait ABSENT but keywords PRESENT
323
+ if (c.traitSet.has(check.trait))
324
+ continue;
325
+ const matchingReqs = c.reqs.filter(ref => {
326
+ const req = requirements.find(r => r.ref === ref);
327
+ return req?.text && check.keywords.test(req.text);
328
+ });
329
+ if (matchingReqs.length > 0) {
330
+ findings.push({
331
+ severity: check.severity,
332
+ category: "Ontological Mismatch",
333
+ title: `"${c.name}" lacks ${check.trait} trait but has ${check.missingWhat}`,
334
+ description: `UHT classifies "${c.name}" (${c.hexCode}) without ${check.trait}, but ${matchingReqs.length} requirement(s) impose related constraints.`,
335
+ affectedReqs: matchingReqs,
336
+ recommendation: check.recommendation,
337
+ });
338
+ }
339
+ }
340
+ else {
341
+ // Standard check: fires when trait PRESENT but keywords ABSENT
342
+ if (!c.traitSet.has(check.trait))
343
+ continue;
344
+ const allText = reqTextsFor(c);
345
+ if (!check.keywords.test(allText)) {
346
+ findings.push({
347
+ severity: check.severity,
348
+ category: "Ontological Mismatch",
349
+ title: `"${c.name}" is ${check.trait} but has no ${check.missingWhat}`,
350
+ description: `UHT classifies "${c.name}" (${c.hexCode}) with ${check.trait} (bit ${check.bit}), but no requirements address this.`,
351
+ affectedReqs: c.reqs,
352
+ recommendation: check.recommendation,
353
+ });
354
+ }
355
+ }
304
356
  }
305
357
  }
306
358
  // ── 2. Abstract metrics without statistical parameters ────
@@ -368,12 +420,14 @@ function analyzeFindings(concepts, comparisons, requirements, traceLinks, reqTie
368
420
  const b = conceptMap.get(comp.candidate);
369
421
  if (!a || !b)
370
422
  continue;
371
- if (comp.jaccard_similarity > options.jaccardThreshold && a.isPhysical !== b.isPhysical) {
423
+ const aPhys = a.traitSet.has("Physical Object");
424
+ const bPhys = b.traitSet.has("Physical Object");
425
+ if (comp.jaccard_similarity > options.jaccardThreshold && aPhys !== bPhys) {
372
426
  findings.push({
373
427
  severity: "low",
374
428
  category: "Ontological Ambiguity",
375
- title: `"${a.name}" and "${b.name}" similar (${(comp.jaccard_similarity * 100).toFixed(0)}%) but differ in physical classification`,
376
- description: `"${a.name}" is ${a.isPhysical ? "" : "not "}Physical Object; "${b.name}" is ${b.isPhysical ? "" : "not "}. High Jaccard similarity suggests they should be treated consistently.`,
429
+ title: `"${a.name}" and "${b.name}" similar (${(comp.jaccard_similarity * 100).toFixed(0)}%) but differ in Physical Object trait`,
430
+ description: `"${a.name}" is ${aPhys ? "" : "not "}Physical Object; "${b.name}" is ${bPhys ? "" : "not "}. High Jaccard similarity suggests they should be treated consistently.`,
377
431
  affectedReqs: [...a.reqs, ...b.reqs],
378
432
  recommendation: "Review whether both concepts should have consistent physical classification.",
379
433
  });
@@ -676,18 +730,11 @@ export function registerLintCommands(program, client) {
676
730
  const [name, refs] = top[i];
677
731
  if (entry.result) {
678
732
  const traitNames = entry.result.traits.map(t => t.name).filter(Boolean);
679
- const has = (t) => traitNames.includes(t);
680
733
  concepts.push({
681
734
  name,
682
735
  hexCode: entry.result.hex_code,
683
736
  traits: traitNames,
684
- isPhysical: has("Physical Object"),
685
- isPowered: has("Powered"),
686
- isHumanInteractive: has("Human-Interactive"),
687
- isAutonomous: has("Functionally Autonomous"),
688
- isSystemEssential: has("System-Essential"),
689
- isDigitalVirtual: has("Digital/Virtual"),
690
- isRegulated: has("Regulated"),
737
+ traitSet: new Set(traitNames),
691
738
  reqs: refs,
692
739
  });
693
740
  console.error(` ✓ ${name} → ${entry.result.hex_code} (${traitNames.length} traits)`);
@@ -14,7 +14,29 @@ export function registerRequirementCommands(program, client) {
14
14
  .option("--order <dir>", "Sort order: asc, desc")
15
15
  .option("--tags <tags>", "Comma-separated tags to filter by (server-side)")
16
16
  .option("--document <slug>", "Filter by document slug (server-side)")
17
+ .option("--homeless", "Show only requirements not assigned to any document")
17
18
  .action(async (tenant, project, opts) => {
19
+ // Handle --homeless: fetch all and filter to unassigned
20
+ if (opts.homeless) {
21
+ const all = [];
22
+ for (let page = 1; page <= 50; page++) {
23
+ const data = await client.get(`/requirements/${tenant}/${project}`, { page: String(page), limit: "500" });
24
+ all.push(...(data.data ?? []));
25
+ if (page >= (data.meta?.totalPages ?? 1))
26
+ break;
27
+ }
28
+ const homeless = all.filter(r => !r.documentSlug);
29
+ if (isJsonMode()) {
30
+ output({ data: homeless, meta: { totalItems: homeless.length } });
31
+ }
32
+ else {
33
+ console.log(`Homeless requirements (no document): ${homeless.length}/${all.length}\n`);
34
+ if (homeless.length > 0) {
35
+ printTable(["Ref", "Text", "QA"], homeless.map(r => [r.ref ?? "?", truncate(r.text ?? "", 65), r.qaScore != null ? String(r.qaScore) : "-"]));
36
+ }
37
+ }
38
+ return;
39
+ }
18
40
  // Handle --limit all: fetch all pages
19
41
  if (opts.limit.toLowerCase() === "all") {
20
42
  const all = [];
@@ -116,6 +138,10 @@ export function registerRequirementCommands(program, client) {
116
138
  .option("--tags <tags>", "Comma-separated tags")
117
139
  .option("--idempotency-key <key>", "Prevent duplicates on retry — returns existing if key was already used")
118
140
  .action(async (tenant, projectKey, opts) => {
141
+ if (!opts.section && !opts.document) {
142
+ console.error("Warning: No --section or --document specified. Requirement will be project-level with a generic ref (e.g., REQ-PROJECTNAME-001).");
143
+ console.error(" Use --section <id> to assign to a document section for proper ref prefixing.");
144
+ }
119
145
  const data = await client.post("/requirements", {
120
146
  tenant,
121
147
  projectKey,
@@ -268,31 +268,61 @@ export function registerVerifyCommands(program, client) {
268
268
  // ── Engine ─────────────────────────────────────────────
269
269
  cmd
270
270
  .command("run")
271
- .description("Run the verification engine — check for gaps, conflicts, and drift")
271
+ .description("Run the verification engine — checks both verification activities and 'verifies' trace links")
272
272
  .argument("<tenant>", "Tenant slug")
273
273
  .argument("<project>", "Project slug")
274
274
  .action(async (tenant, project) => {
275
- const data = await client.get(`/verification/engine/${tenant}/${project}`);
276
- const report = data.report;
275
+ // Fetch engine report (activities-based) and trace links in parallel
276
+ const [engineData, linkData, reqData] = await Promise.all([
277
+ client.get(`/verification/engine/${tenant}/${project}`).catch(() => ({ report: null })),
278
+ client.get(`/trace-links/${tenant}/${project}`).catch(() => ({ traceLinks: [] })),
279
+ client.get(`/requirements/${tenant}/${project}`, { page: "1", limit: "1" }).catch(() => ({ meta: { totalItems: 0 } })),
280
+ ]);
281
+ const report = engineData.report;
282
+ const totalReqs = reqData.meta?.totalItems ?? 0;
283
+ // Compute trace-link-based coverage (requirements targeted by 'verifies' links)
284
+ const verifiedByLinks = new Set();
285
+ for (const link of linkData.traceLinks ?? []) {
286
+ if (link.linkType === "verifies" && link.targetRequirementId) {
287
+ verifiedByLinks.add(link.targetRequirementId);
288
+ }
289
+ }
290
+ const traceCoverage = totalReqs > 0 ? Math.round((verifiedByLinks.size / totalReqs) * 100) : 0;
277
291
  if (isJsonMode()) {
278
- output(report);
292
+ output({
293
+ activities: report,
294
+ traceLinkCoverage: {
295
+ totalRequirements: totalReqs,
296
+ verifiedByTraceLinks: verifiedByLinks.size,
297
+ coveragePercent: traceCoverage,
298
+ },
299
+ });
279
300
  return;
280
301
  }
281
- const s = report.summary;
282
- console.log(`Verification Report\n`);
283
- console.log(`Coverage: ${s.coveragePercent}% (${s.verified}/${s.totalRequirements} verified)`);
284
- console.log(`Unverified: ${s.unverified} | Incomplete: ${s.incomplete} | Drifted: ${s.driftedEvidence}\n`);
285
- if (report.findings.length === 0) {
286
- console.log("No findings. All clear.");
302
+ console.log("Verification Report\n");
303
+ // Trace link coverage (most common approach)
304
+ console.log(`Trace Link Coverage: ${traceCoverage}% (${verifiedByLinks.size}/${totalReqs} have 'verifies' links)`);
305
+ // Activities coverage (formal V&V workflow)
306
+ if (report) {
307
+ const s = report.summary;
308
+ console.log(`Activity Coverage: ${s.coveragePercent}% (${s.verified}/${s.totalRequirements} have verification activities)`);
309
+ console.log(` Unverified: ${s.unverified} | Incomplete: ${s.incomplete} | Drifted: ${s.driftedEvidence}`);
310
+ if (report.findings.length > 0) {
311
+ console.log(`\nFindings (${report.findings.length}):`);
312
+ printTable(["Severity", "Type", "Req", "Message"], report.findings.map(f => [
313
+ severityIcon(f.severity) + " " + f.severity,
314
+ f.type,
315
+ f.requirementRef ?? "",
316
+ truncate(f.message, 60),
317
+ ]));
318
+ }
287
319
  }
288
320
  else {
289
- printTable(["Severity", "Type", "Req", "Message"], report.findings.map(f => [
290
- severityIcon(f.severity) + " " + f.severity,
291
- f.type,
292
- f.requirementRef ?? "",
293
- truncate(f.message, 60),
294
- ]));
295
- console.log(`\n${report.findings.length} finding(s) total.`);
321
+ console.log("Activity Coverage: N/A (no verification activities configured)");
322
+ }
323
+ if (traceCoverage === 0 && (!report || report.summary.coveragePercent === 0)) {
324
+ console.log("\nTip: Create 'verifies' trace links from VER requirements to SYS/SUB requirements,");
325
+ console.log("or set up verification activities via 'airgen verify activities create'.");
296
326
  }
297
327
  });
298
328
  // ── Matrix ─────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airgen-cli",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "AIRGen CLI — requirements engineering from the command line",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",