airgen-cli 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -191,6 +191,40 @@ airgen impl unlink <tenant> <project> <req> --artifact <id>
191
191
  ]
192
192
  ```
193
193
 
194
+ ### Verification
195
+
196
+ Manage verification activities, evidence, documents, and run the verification engine to detect gaps.
197
+
198
+ ```bash
199
+ # Run the verification engine
200
+ airgen verify run <tenant> <project> # Coverage report + findings
201
+ airgen verify matrix <tenant> <project> # Cross-reference matrix
202
+
203
+ # Activities (TADI: Test, Analysis, Demonstration, Inspection)
204
+ airgen verify act list <tenant> <project>
205
+ airgen verify act create <tenant> <project> <req-id> --method Test --title "..."
206
+ airgen verify act update <activity-id> --status passed
207
+
208
+ # Evidence
209
+ airgen verify ev list <tenant> <project>
210
+ airgen verify ev add <tenant> <project> <activity-id> --type test_result --title "..." --verdict pass --recorded-by "name"
211
+
212
+ # Verification documents
213
+ airgen verify docs list <tenant> <project>
214
+ airgen verify docs create <tenant> <project> --name "Test Plan" --kind test_plan
215
+ airgen verify docs status <vdoc-id> --status approved
216
+ airgen verify docs revisions <vdoc-id>
217
+ airgen verify docs revise <vdoc-id> --rev 1.0 --change "Final review" --by "name"
218
+ ```
219
+
220
+ **Activity statuses:** `planned`, `in_progress`, `executed`, `passed`, `failed`, `blocked`
221
+
222
+ **Evidence verdicts:** `pass`, `fail`, `inconclusive`, `not_applicable`
223
+
224
+ **Document kinds:** `test_plan`, `test_procedure`, `test_report`, `analysis_report`, `inspection_checklist`, `demonstration_protocol`
225
+
226
+ **Document statuses:** `draft`, `review`, `approved`, `superseded`
227
+
194
228
  ### Import / Export
195
229
 
196
230
  ```bash
@@ -262,6 +296,9 @@ airgen report compliance my-tenant my-project --json | jq '.summary'
262
296
  | `reports` | `report` |
263
297
  | `projects` | `proj` |
264
298
  | `sections` | `sec` |
299
+ | `verify activities` | `verify act` |
300
+ | `verify evidence` | `verify ev` |
301
+ | `verify documents` | `verify docs` |
265
302
 
266
303
  ## License
267
304
 
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ import type { AirgenClient } from "../client.js";
3
+ export declare function registerVerifyCommands(program: Command, client: AirgenClient): void;
@@ -0,0 +1,325 @@
1
+ import { output, printTable, isJsonMode, truncate } from "../output.js";
2
+ const METHODS = ["Test", "Analysis", "Inspection", "Demonstration"];
3
+ const ACTIVITY_STATUSES = ["planned", "in_progress", "executed", "passed", "failed", "blocked"];
4
+ const EVIDENCE_TYPES = ["test_result", "analysis_report", "inspection_record", "demonstration_record"];
5
+ const VERDICTS = ["pass", "fail", "inconclusive", "not_applicable"];
6
+ const DOC_KINDS = ["test_plan", "test_procedure", "test_report", "analysis_report", "inspection_checklist", "demonstration_protocol"];
7
+ const DOC_STATUSES = ["draft", "review", "approved", "superseded"];
8
+ function severityIcon(s) {
9
+ switch (s) {
10
+ case "error": return "[!]";
11
+ case "warning": return "[~]";
12
+ case "info": return "[i]";
13
+ default: return "[ ]";
14
+ }
15
+ }
16
+ function statusIcon(s) {
17
+ switch (s) {
18
+ case "passed": return "[v]";
19
+ case "failed": return "[x]";
20
+ case "blocked": return "[!]";
21
+ case "executed": return "[>]";
22
+ case "in_progress": return "[~]";
23
+ case "planned": return "[ ]";
24
+ default: return "[-]";
25
+ }
26
+ }
27
+ // ── Command registration ─────────────────────────────────────
28
+ export function registerVerifyCommands(program, client) {
29
+ const cmd = program.command("verify").description("Verification management");
30
+ // ── Activities ──────────────────────────────────────────
31
+ const act = cmd.command("activities").alias("act").description("Verification activities");
32
+ act
33
+ .command("list")
34
+ .description("List verification activities")
35
+ .argument("<tenant>", "Tenant slug")
36
+ .argument("<project>", "Project slug")
37
+ .option("--status <s>", "Filter by status")
38
+ .option("--method <m>", "Filter by method")
39
+ .action(async (tenant, project, opts) => {
40
+ const query = {};
41
+ if (opts.status)
42
+ query.status = opts.status;
43
+ if (opts.method)
44
+ query.method = opts.method;
45
+ const data = await client.get(`/verification/activities/${tenant}/${project}`, query);
46
+ const activities = data.activities ?? [];
47
+ if (isJsonMode()) {
48
+ output(activities);
49
+ }
50
+ else {
51
+ printTable(["ID", "Method", "Status", "Title", "Req Ref"], activities.map(a => [
52
+ a.activityId,
53
+ a.method,
54
+ statusIcon(a.status) + " " + a.status,
55
+ truncate(a.title, 40),
56
+ a.requirementRef ?? "",
57
+ ]));
58
+ }
59
+ });
60
+ act
61
+ .command("create")
62
+ .description("Create a verification activity")
63
+ .argument("<tenant>", "Tenant slug")
64
+ .argument("<project>", "Project key")
65
+ .argument("<requirement-id>", "Requirement ID")
66
+ .requiredOption("--method <m>", `Method: ${METHODS.join(", ")}`)
67
+ .requiredOption("--title <t>", "Activity title")
68
+ .option("--description <d>", "Description")
69
+ .action(async (tenant, project, requirementId, opts) => {
70
+ if (!METHODS.includes(opts.method)) {
71
+ console.error(`Invalid method. Must be one of: ${METHODS.join(", ")}`);
72
+ process.exit(1);
73
+ }
74
+ const data = await client.post("/verification/activities", {
75
+ tenant,
76
+ projectKey: project,
77
+ requirementId,
78
+ method: opts.method,
79
+ title: opts.title,
80
+ description: opts.description,
81
+ });
82
+ if (isJsonMode()) {
83
+ output(data.activity);
84
+ }
85
+ else {
86
+ console.log(`Activity created: ${data.activity.activityId}`);
87
+ }
88
+ });
89
+ act
90
+ .command("update")
91
+ .description("Update a verification activity")
92
+ .argument("<activity-id>", "Activity ID")
93
+ .option("--status <s>", `Status: ${ACTIVITY_STATUSES.join(", ")}`)
94
+ .option("--title <t>", "Title")
95
+ .option("--description <d>", "Description")
96
+ .action(async (activityId, opts) => {
97
+ const data = await client.patch(`/verification/activities/${activityId}`, opts);
98
+ if (isJsonMode()) {
99
+ output(data.activity);
100
+ }
101
+ else {
102
+ console.log(`Activity updated: ${data.activity.activityId} (${data.activity.status})`);
103
+ }
104
+ });
105
+ // ── Evidence ────────────────────────────────────────────
106
+ const ev = cmd.command("evidence").alias("ev").description("Verification evidence");
107
+ ev
108
+ .command("list")
109
+ .description("List verification evidence")
110
+ .argument("<tenant>", "Tenant slug")
111
+ .argument("<project>", "Project slug")
112
+ .option("--activity <id>", "Filter by activity ID")
113
+ .action(async (tenant, project, opts) => {
114
+ const query = {};
115
+ if (opts.activity)
116
+ query.activityId = opts.activity;
117
+ const data = await client.get(`/verification/evidence/${tenant}/${project}`, query);
118
+ const evidence = data.evidence ?? [];
119
+ if (isJsonMode()) {
120
+ output(evidence);
121
+ }
122
+ else {
123
+ printTable(["ID", "Type", "Verdict", "Title", "Recorded By"], evidence.map(e => [
124
+ e.evidenceId,
125
+ e.type,
126
+ e.verdict,
127
+ truncate(e.title, 40),
128
+ e.recordedBy,
129
+ ]));
130
+ }
131
+ });
132
+ ev
133
+ .command("add")
134
+ .description("Add verification evidence to an activity")
135
+ .argument("<tenant>", "Tenant slug")
136
+ .argument("<project>", "Project key")
137
+ .argument("<activity-id>", "Activity ID")
138
+ .requiredOption("--type <t>", `Type: ${EVIDENCE_TYPES.join(", ")}`)
139
+ .requiredOption("--title <t>", "Evidence title")
140
+ .requiredOption("--verdict <v>", `Verdict: ${VERDICTS.join(", ")}`)
141
+ .requiredOption("--recorded-by <name>", "Who recorded this evidence")
142
+ .option("--summary <s>", "Summary text")
143
+ .action(async (tenant, project, activityId, opts) => {
144
+ const data = await client.post("/verification/evidence", {
145
+ tenant,
146
+ projectKey: project,
147
+ activityId,
148
+ type: opts.type,
149
+ title: opts.title,
150
+ verdict: opts.verdict,
151
+ recordedBy: opts.recordedBy,
152
+ summary: opts.summary,
153
+ });
154
+ if (isJsonMode()) {
155
+ output(data.evidence);
156
+ }
157
+ else {
158
+ console.log(`Evidence added: ${data.evidence.evidenceId} (${data.evidence.verdict})`);
159
+ }
160
+ });
161
+ // ── Verification Documents ─────────────────────────────
162
+ const docs = cmd.command("documents").alias("docs").description("Verification documents");
163
+ docs
164
+ .command("list")
165
+ .description("List verification documents")
166
+ .argument("<tenant>", "Tenant slug")
167
+ .argument("<project>", "Project slug")
168
+ .action(async (tenant, project) => {
169
+ const data = await client.get(`/verification/documents/${tenant}/${project}`);
170
+ const documents = data.documents ?? [];
171
+ if (isJsonMode()) {
172
+ output(documents);
173
+ }
174
+ else {
175
+ printTable(["ID", "Name", "Kind", "Status", "Rev"], documents.map(d => [
176
+ d.vdocId,
177
+ truncate(d.name, 40),
178
+ d.kind,
179
+ d.status,
180
+ d.currentRevision,
181
+ ]));
182
+ }
183
+ });
184
+ docs
185
+ .command("create")
186
+ .description("Create a verification document")
187
+ .argument("<tenant>", "Tenant slug")
188
+ .argument("<project>", "Project key")
189
+ .requiredOption("--name <n>", "Document name")
190
+ .requiredOption("--kind <k>", `Kind: ${DOC_KINDS.join(", ")}`)
191
+ .action(async (tenant, project, opts) => {
192
+ if (!DOC_KINDS.includes(opts.kind)) {
193
+ console.error(`Invalid kind. Must be one of: ${DOC_KINDS.join(", ")}`);
194
+ process.exit(1);
195
+ }
196
+ const data = await client.post("/verification/documents", {
197
+ tenant,
198
+ projectKey: project,
199
+ name: opts.name,
200
+ kind: opts.kind,
201
+ });
202
+ if (isJsonMode()) {
203
+ output(data.document);
204
+ }
205
+ else {
206
+ console.log(`Document created: ${data.document.vdocId}`);
207
+ }
208
+ });
209
+ docs
210
+ .command("status")
211
+ .description("Update verification document status")
212
+ .argument("<vdoc-id>", "Document ID")
213
+ .requiredOption("--status <s>", `Status: ${DOC_STATUSES.join(", ")}`)
214
+ .action(async (vdocId, opts) => {
215
+ if (!DOC_STATUSES.includes(opts.status)) {
216
+ console.error(`Invalid status. Must be one of: ${DOC_STATUSES.join(", ")}`);
217
+ process.exit(1);
218
+ }
219
+ const data = await client.patch(`/verification/documents/${vdocId}/status`, { status: opts.status });
220
+ if (isJsonMode()) {
221
+ output(data.document);
222
+ }
223
+ else {
224
+ console.log(`Document status updated to ${data.document.status}.`);
225
+ }
226
+ });
227
+ // ── Revisions ──────────────────────────────────────────
228
+ docs
229
+ .command("revisions")
230
+ .description("List revisions for a verification document")
231
+ .argument("<vdoc-id>", "Document ID")
232
+ .action(async (vdocId) => {
233
+ const data = await client.get(`/verification/documents/${vdocId}/revisions`);
234
+ const revisions = data.revisions ?? [];
235
+ if (isJsonMode()) {
236
+ output(revisions);
237
+ }
238
+ else {
239
+ printTable(["Rev ID", "Number", "Change", "By", "Date"], revisions.map(r => [
240
+ r.revisionId,
241
+ r.revisionNumber,
242
+ truncate(r.changeDescription, 40),
243
+ r.createdBy,
244
+ r.createdAt.split("T")[0],
245
+ ]));
246
+ }
247
+ });
248
+ docs
249
+ .command("revise")
250
+ .description("Create a new revision of a verification document")
251
+ .argument("<vdoc-id>", "Document ID")
252
+ .requiredOption("--rev <n>", "Revision number (e.g. 0.2, 1.0)")
253
+ .requiredOption("--change <desc>", "Change description")
254
+ .requiredOption("--by <name>", "Created by")
255
+ .action(async (vdocId, opts) => {
256
+ const data = await client.post(`/verification/documents/${vdocId}/revisions`, {
257
+ revisionNumber: opts.rev,
258
+ changeDescription: opts.change,
259
+ createdBy: opts.by,
260
+ });
261
+ if (isJsonMode()) {
262
+ output(data.revision);
263
+ }
264
+ else {
265
+ console.log(`Revision created: ${data.revision.revisionId} (${data.revision.revisionNumber})`);
266
+ }
267
+ });
268
+ // ── Engine ─────────────────────────────────────────────
269
+ cmd
270
+ .command("run")
271
+ .description("Run the verification engine — check for gaps, conflicts, and drift")
272
+ .argument("<tenant>", "Tenant slug")
273
+ .argument("<project>", "Project slug")
274
+ .action(async (tenant, project) => {
275
+ const data = await client.get(`/verification/engine/${tenant}/${project}`);
276
+ const report = data.report;
277
+ if (isJsonMode()) {
278
+ output(report);
279
+ return;
280
+ }
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.");
287
+ }
288
+ 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.`);
296
+ }
297
+ });
298
+ // ── Matrix ─────────────────────────────────────────────
299
+ cmd
300
+ .command("matrix")
301
+ .description("Show the verification cross-reference matrix")
302
+ .argument("<tenant>", "Tenant slug")
303
+ .argument("<project>", "Project slug")
304
+ .action(async (tenant, project) => {
305
+ const data = await client.get(`/verification/matrix/${tenant}/${project}`);
306
+ const matrix = data.matrix ?? [];
307
+ if (isJsonMode()) {
308
+ output(matrix);
309
+ return;
310
+ }
311
+ if (matrix.length === 0) {
312
+ console.log("No requirements found.");
313
+ return;
314
+ }
315
+ console.log("Verification Matrix\n");
316
+ for (const row of matrix) {
317
+ const actSummary = row.activities.length === 0
318
+ ? " (no activities)"
319
+ : row.activities.map(a => ` ${statusIcon(a.status)} ${a.method}: ${a.title} (${a.evidenceCount} evidence${a.hasPassingEvidence ? ", PASS" : ""})`).join("\n");
320
+ console.log(`${row.requirementRef} ${truncate(row.requirementText, 60)}`);
321
+ console.log(actSummary);
322
+ console.log();
323
+ }
324
+ });
325
+ }
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ import { registerActivityCommands } from "./commands/activity.js";
21
21
  import { registerImplementationCommands } from "./commands/implementation.js";
22
22
  import { registerLintCommands } from "./commands/lint.js";
23
23
  import { registerDiffCommand } from "./commands/diff.js";
24
+ import { registerVerifyCommands } from "./commands/verify.js";
24
25
  const program = new Command();
25
26
  // Lazy-init: only create client when a command actually runs
26
27
  let client = null;
@@ -71,6 +72,7 @@ registerActivityCommands(program, clientProxy);
71
72
  registerImplementationCommands(program, clientProxy);
72
73
  registerLintCommands(program, clientProxy);
73
74
  registerDiffCommand(program, clientProxy);
75
+ registerVerifyCommands(program, clientProxy);
74
76
  // Handle async errors from Commander action handlers
75
77
  process.on("uncaughtException", (err) => {
76
78
  console.error(`Error: ${err.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airgen-cli",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "AIRGen CLI — requirements engineering from the command line",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",