airgen-cli 0.2.2 → 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.
@@ -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.0",
4
4
  "description": "AIRGen CLI — requirements engineering from the command line",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",