airgen-cli 0.1.8 → 0.2.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 +268 -0
- package/dist/commands/diff.d.ts +3 -0
- package/dist/commands/diff.js +185 -0
- package/dist/index.js +2 -0
- package/package.json +9 -3
package/README.md
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# airgen-cli
|
|
2
|
+
|
|
3
|
+
Requirements engineering from the command line. Manage requirements, architecture diagrams, traceability, baselines, and more — all from your terminal.
|
|
4
|
+
|
|
5
|
+
Pairs with [AIRGen Studio](https://airgen.studio) and the AIRGen MCP server.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g airgen-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Configuration
|
|
14
|
+
|
|
15
|
+
Set credentials via environment variables or `~/.airgenrc`:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Environment variables
|
|
19
|
+
export AIRGEN_API_URL=https://api.airgen.studio/api
|
|
20
|
+
export AIRGEN_EMAIL=you@example.com
|
|
21
|
+
export AIRGEN_PASSWORD=your-password
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or create `~/.airgenrc`:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"apiUrl": "https://api.airgen.studio/api",
|
|
29
|
+
"email": "you@example.com",
|
|
30
|
+
"password": "your-password"
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For semantic linting, also set a UHT token:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
export UHT_API_KEY=your-token # or UHT_TOKEN
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# List your tenants and projects
|
|
44
|
+
airgen tenants list
|
|
45
|
+
airgen projects list my-tenant
|
|
46
|
+
|
|
47
|
+
# List requirements
|
|
48
|
+
airgen reqs list my-tenant my-project
|
|
49
|
+
|
|
50
|
+
# Render a diagram in the terminal
|
|
51
|
+
airgen diag list my-tenant my-project
|
|
52
|
+
airgen diag render my-tenant my-project diagram-123
|
|
53
|
+
|
|
54
|
+
# Run semantic lint
|
|
55
|
+
airgen lint my-tenant my-project
|
|
56
|
+
|
|
57
|
+
# Get a compliance report
|
|
58
|
+
airgen report compliance my-tenant my-project
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Global options
|
|
62
|
+
|
|
63
|
+
| Flag | Description |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `--json` | Output as JSON (works with any command) |
|
|
66
|
+
| `-V, --version` | Print version |
|
|
67
|
+
| `-h, --help` | Show help |
|
|
68
|
+
|
|
69
|
+
## Commands
|
|
70
|
+
|
|
71
|
+
### Tenants & Projects
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
airgen tenants list # List all tenants
|
|
75
|
+
airgen projects list <tenant> # List projects in a tenant
|
|
76
|
+
airgen projects create <tenant> --name "X" # Create a project
|
|
77
|
+
airgen projects delete <tenant> <project> # Delete a project
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Requirements
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
airgen reqs list <tenant> <project> # List (paginated)
|
|
84
|
+
airgen reqs list <tenant> <project> --page 2 --limit 50
|
|
85
|
+
airgen reqs get <tenant> <project> <ref> # Full detail
|
|
86
|
+
airgen reqs create <tenant> <project> --text "The system shall..."
|
|
87
|
+
airgen reqs update <tenant> <project> <id> --text "..." --tags safety,critical
|
|
88
|
+
airgen reqs delete <tenant> <project> <id> # Soft-delete
|
|
89
|
+
airgen reqs history <tenant> <project> <id> # Version history
|
|
90
|
+
airgen reqs search <tenant> <project> --query "thermal" --mode semantic
|
|
91
|
+
airgen reqs filter <tenant> <project> --pattern functional --tag safety
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Architecture Diagrams
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
airgen diag list <tenant> <project> # List diagrams
|
|
98
|
+
airgen diag get <tenant> <project> <id> # Blocks + connectors JSON
|
|
99
|
+
airgen diag render <tenant> <project> <id> # Terminal display (default)
|
|
100
|
+
airgen diag render <tenant> <project> <id> --format mermaid # Mermaid syntax
|
|
101
|
+
airgen diag render <tenant> <project> <id> --format mermaid --wrap -o diagram.md
|
|
102
|
+
airgen diag create <tenant> <project> --name "X" --view block
|
|
103
|
+
airgen diag update <tenant> <project> <id> --name "Y"
|
|
104
|
+
airgen diag delete <tenant> <project> <id>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Blocks:**
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
airgen diag blocks library <tenant> <project>
|
|
111
|
+
airgen diag blocks create <tenant> <project> --diagram <id> --name "X" --kind subsystem
|
|
112
|
+
airgen diag blocks delete <tenant> <project> <block-id>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Connectors:**
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
airgen diag conn create <tenant> <project> --diagram <id> --source <id> --target <id> --kind flow --label "data"
|
|
119
|
+
airgen diag conn delete <tenant> <project> <conn-id> --diagram <id>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Traceability
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
airgen trace list <tenant> <project> # List trace links
|
|
126
|
+
airgen trace create <tenant> <project> --source <id> --target <id> --type derives
|
|
127
|
+
airgen trace delete <tenant> <project> <link-id>
|
|
128
|
+
airgen trace linksets list <tenant> <project> # Document linksets
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Baselines & Diff
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
airgen bl list <tenant> <project>
|
|
135
|
+
airgen bl create <tenant> <project> --name "v1.0"
|
|
136
|
+
airgen bl compare <tenant> <project> --from <id1> --to <id2>
|
|
137
|
+
|
|
138
|
+
# Rich diff between baselines
|
|
139
|
+
airgen diff <tenant> <project> --from <bl1> --to <bl2> # Pretty terminal output
|
|
140
|
+
airgen diff <tenant> <project> --from <bl1> --to <bl2> --json # Structured JSON
|
|
141
|
+
airgen diff <tenant> <project> --from <bl1> --to <bl2> --format markdown -o diff.md
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`diff` shows added, modified, and removed requirements with full text, plus a summary of changes to documents, trace links, diagrams, blocks, and connectors.
|
|
145
|
+
|
|
146
|
+
### Quality & AI
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
airgen qa analyze "The system shall..." # Analyze single requirement
|
|
150
|
+
airgen qa score start <tenant> <project> # Background QA scoring
|
|
151
|
+
airgen qa draft "user needs thermal imaging" # Draft requirements from NL
|
|
152
|
+
|
|
153
|
+
airgen ai generate <tenant> <project> --prompt "..." # Generate candidates
|
|
154
|
+
airgen ai candidates <tenant> <project> # List pending candidates
|
|
155
|
+
airgen ai accept <candidate-id> # Promote to requirement
|
|
156
|
+
airgen ai reject <candidate-id> # Reject candidate
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Reports
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
airgen report stats <tenant> <project> # Overview statistics
|
|
163
|
+
airgen report quality <tenant> <project> # QA score summary
|
|
164
|
+
airgen report compliance <tenant> <project> # Compliance + impl status
|
|
165
|
+
airgen report orphans <tenant> <project> # Untraced requirements
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
All report commands auto-paginate through the full requirement set (up to 5000).
|
|
169
|
+
|
|
170
|
+
### Implementation Tracking
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
airgen impl status <tenant> <project> <req> --status implemented --notes "done in v2"
|
|
174
|
+
airgen impl summary <tenant> <project> # Coverage breakdown
|
|
175
|
+
airgen impl list <tenant> <project> --status blocked # Filter by status
|
|
176
|
+
airgen impl bulk-update <tenant> <project> --file updates.json
|
|
177
|
+
|
|
178
|
+
# Artifact linking
|
|
179
|
+
airgen impl link <tenant> <project> <req> --type file --path src/engine.ts
|
|
180
|
+
airgen impl unlink <tenant> <project> <req> --artifact <id>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Statuses:** `not_started`, `in_progress`, `implemented`, `verified`, `blocked`
|
|
184
|
+
|
|
185
|
+
**Bulk update file format:**
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
[
|
|
189
|
+
{ "ref": "REQ-001", "status": "implemented", "notes": "shipped" },
|
|
190
|
+
{ "ref": "REQ-002", "status": "in_progress" }
|
|
191
|
+
]
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Import / Export
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
airgen import requirements <tenant> <project> --file reqs.csv
|
|
198
|
+
airgen export requirements <tenant> <project> # Markdown
|
|
199
|
+
airgen export requirements <tenant> <project> --json # JSON
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Activity
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
airgen activity list <tenant> <project> # Recent activity
|
|
206
|
+
airgen activity list <tenant> <project> --limit 50
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Documents
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
airgen docs list <tenant> <project>
|
|
213
|
+
airgen docs get <tenant> <project> <slug>
|
|
214
|
+
airgen docs create <tenant> <project> --title "X" --kind structured
|
|
215
|
+
airgen docs delete <tenant> <project> <slug>
|
|
216
|
+
airgen docs export <tenant> <project> <slug> # Markdown export
|
|
217
|
+
airgen docs sec list <tenant> <project> <slug> # List sections
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Semantic Lint
|
|
221
|
+
|
|
222
|
+
Classifies domain concepts from your requirements using the [Universal Hex Taxonomy](https://universalhex.org) and flags ontological issues, structural problems, and coverage gaps.
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
airgen lint <tenant> <project> # Full lint (top 15 concepts)
|
|
226
|
+
airgen lint <tenant> <project> --concepts 20 # Classify more concepts
|
|
227
|
+
airgen lint <tenant> <project> --format markdown -o lint-report.md
|
|
228
|
+
airgen lint <tenant> <project> --format json # Machine-readable
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**What it detects:**
|
|
232
|
+
|
|
233
|
+
- Ontological mismatches (e.g., non-physical entity with physical constraints)
|
|
234
|
+
- Abstract metrics missing statistical parameters
|
|
235
|
+
- Verification requirements mixed with functional requirements
|
|
236
|
+
- Degraded modes without performance criteria
|
|
237
|
+
- Ontological ambiguity between similar concepts
|
|
238
|
+
- Requirements lacking "shall" keyword
|
|
239
|
+
|
|
240
|
+
**Requires:** `UHT_TOKEN` or `UHT_API_KEY` environment variable. Get a token at [universalhex.org](https://universalhex.org).
|
|
241
|
+
|
|
242
|
+
## JSON mode
|
|
243
|
+
|
|
244
|
+
Append `--json` to any command for machine-readable output:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
airgen reqs list my-tenant my-project --json | jq '.[].ref'
|
|
248
|
+
airgen report compliance my-tenant my-project --json | jq '.summary'
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Aliases
|
|
252
|
+
|
|
253
|
+
| Full command | Alias |
|
|
254
|
+
|---|---|
|
|
255
|
+
| `requirements` | `reqs` |
|
|
256
|
+
| `diagrams` | `diag` |
|
|
257
|
+
| `documents` | `docs` |
|
|
258
|
+
| `connectors` | `conn` |
|
|
259
|
+
| `baselines` | `bl` |
|
|
260
|
+
| `traces` | `trace` |
|
|
261
|
+
| `quality` | `qa` |
|
|
262
|
+
| `reports` | `report` |
|
|
263
|
+
| `projects` | `proj` |
|
|
264
|
+
| `sections` | `sec` |
|
|
265
|
+
|
|
266
|
+
## License
|
|
267
|
+
|
|
268
|
+
MIT
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { isJsonMode, truncate } from "../output.js";
|
|
3
|
+
// ── Helpers ───────────────────────────────────────────────────
|
|
4
|
+
/** Extract short ref from "tenant:project:REQ-001" → "REQ-001" */
|
|
5
|
+
function shortRef(id) {
|
|
6
|
+
return id?.split(":").pop() ?? "?";
|
|
7
|
+
}
|
|
8
|
+
function counts(comp) {
|
|
9
|
+
return {
|
|
10
|
+
added: comp?.added?.length ?? 0,
|
|
11
|
+
removed: comp?.removed?.length ?? 0,
|
|
12
|
+
modified: comp?.modified?.length ?? 0,
|
|
13
|
+
unchanged: comp?.unchanged?.length ?? 0,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
// ── Structured output ────────────────────────────────────────
|
|
17
|
+
function buildStructured(data) {
|
|
18
|
+
const reqs = data.requirements ?? { added: [], removed: [], modified: [], unchanged: [] };
|
|
19
|
+
const r = counts(reqs);
|
|
20
|
+
return {
|
|
21
|
+
summary: {
|
|
22
|
+
from: data.fromBaseline?.ref ?? "?",
|
|
23
|
+
to: data.toBaseline?.ref ?? "?",
|
|
24
|
+
requirements: r,
|
|
25
|
+
},
|
|
26
|
+
added: reqs.added.map(v => ({ ref: shortRef(v.requirementId), text: v.text ?? "" })),
|
|
27
|
+
removed: reqs.removed.map(v => ({ ref: shortRef(v.requirementId), text: v.text ?? "" })),
|
|
28
|
+
modified: reqs.modified.map(v => ({ ref: shortRef(v.requirementId), text: v.text ?? "" })),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// ── Pretty text ──────────────────────────────────────────────
|
|
32
|
+
function formatPretty(data) {
|
|
33
|
+
const from = data.fromBaseline?.ref ?? "?";
|
|
34
|
+
const to = data.toBaseline?.ref ?? "?";
|
|
35
|
+
const reqs = data.requirements ?? { added: [], removed: [], modified: [], unchanged: [] };
|
|
36
|
+
const r = counts(reqs);
|
|
37
|
+
const lines = [];
|
|
38
|
+
lines.push(` Baseline Diff: ${from} → ${to}`);
|
|
39
|
+
lines.push(` ${"═".repeat(20 + from.length + to.length)}`);
|
|
40
|
+
lines.push(` ${r.added} added, ${r.modified} modified, ${r.removed} removed, ${r.unchanged} unchanged`);
|
|
41
|
+
lines.push("");
|
|
42
|
+
if (reqs.added.length > 0) {
|
|
43
|
+
lines.push(" ┄┄ Added ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄");
|
|
44
|
+
for (const v of reqs.added) {
|
|
45
|
+
lines.push(` + ${shortRef(v.requirementId)}`);
|
|
46
|
+
lines.push(` ${truncate(v.text ?? "", 100)}`);
|
|
47
|
+
}
|
|
48
|
+
lines.push("");
|
|
49
|
+
}
|
|
50
|
+
if (reqs.modified.length > 0) {
|
|
51
|
+
lines.push(" ┄┄ Modified ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄");
|
|
52
|
+
for (const v of reqs.modified) {
|
|
53
|
+
lines.push(` ~ ${shortRef(v.requirementId)}`);
|
|
54
|
+
lines.push(` ${truncate(v.text ?? "", 100)}`);
|
|
55
|
+
}
|
|
56
|
+
lines.push("");
|
|
57
|
+
}
|
|
58
|
+
if (reqs.removed.length > 0) {
|
|
59
|
+
lines.push(" ┄┄ Removed ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄");
|
|
60
|
+
for (const v of reqs.removed) {
|
|
61
|
+
lines.push(` - ${shortRef(v.requirementId)}`);
|
|
62
|
+
lines.push(` ${truncate(v.text ?? "", 100)}`);
|
|
63
|
+
}
|
|
64
|
+
lines.push("");
|
|
65
|
+
}
|
|
66
|
+
// Non-requirement entity summary
|
|
67
|
+
const others = [
|
|
68
|
+
["Documents", data.documents],
|
|
69
|
+
["Trace Links", data.traceLinks],
|
|
70
|
+
["Diagrams", data.diagrams],
|
|
71
|
+
["Blocks", data.blocks],
|
|
72
|
+
["Connectors", data.connectors],
|
|
73
|
+
];
|
|
74
|
+
const changed = others.filter(([, c]) => {
|
|
75
|
+
const n = counts(c);
|
|
76
|
+
return n.added + n.modified + n.removed > 0;
|
|
77
|
+
});
|
|
78
|
+
if (changed.length > 0) {
|
|
79
|
+
lines.push(" ┄┄ Other Changes ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄");
|
|
80
|
+
for (const [label, comp] of changed) {
|
|
81
|
+
const n = counts(comp);
|
|
82
|
+
lines.push(` ${label}: +${n.added} ~${n.modified} -${n.removed}`);
|
|
83
|
+
}
|
|
84
|
+
lines.push("");
|
|
85
|
+
}
|
|
86
|
+
if (r.added + r.modified + r.removed === 0 && changed.length === 0) {
|
|
87
|
+
lines.push(" No changes between baselines.");
|
|
88
|
+
lines.push("");
|
|
89
|
+
}
|
|
90
|
+
return lines.join("\n");
|
|
91
|
+
}
|
|
92
|
+
// ── Markdown ─────────────────────────────────────────────────
|
|
93
|
+
function formatMarkdown(data) {
|
|
94
|
+
const from = data.fromBaseline?.ref ?? "?";
|
|
95
|
+
const to = data.toBaseline?.ref ?? "?";
|
|
96
|
+
const reqs = data.requirements ?? { added: [], removed: [], modified: [], unchanged: [] };
|
|
97
|
+
const r = counts(reqs);
|
|
98
|
+
const lines = [];
|
|
99
|
+
lines.push(`## Baseline Diff: ${from} → ${to}`);
|
|
100
|
+
lines.push("");
|
|
101
|
+
lines.push(`**${r.added}** added, **${r.modified}** modified, **${r.removed}** removed, **${r.unchanged}** unchanged`);
|
|
102
|
+
lines.push("");
|
|
103
|
+
if (reqs.added.length > 0) {
|
|
104
|
+
lines.push("### Added");
|
|
105
|
+
lines.push("| Ref | Text |");
|
|
106
|
+
lines.push("|---|---|");
|
|
107
|
+
for (const v of reqs.added) {
|
|
108
|
+
lines.push(`| ${shortRef(v.requirementId)} | ${truncate(v.text ?? "", 120)} |`);
|
|
109
|
+
}
|
|
110
|
+
lines.push("");
|
|
111
|
+
}
|
|
112
|
+
if (reqs.modified.length > 0) {
|
|
113
|
+
lines.push("### Modified");
|
|
114
|
+
lines.push("| Ref | Text (current) |");
|
|
115
|
+
lines.push("|---|---|");
|
|
116
|
+
for (const v of reqs.modified) {
|
|
117
|
+
lines.push(`| ${shortRef(v.requirementId)} | ${truncate(v.text ?? "", 120)} |`);
|
|
118
|
+
}
|
|
119
|
+
lines.push("");
|
|
120
|
+
}
|
|
121
|
+
if (reqs.removed.length > 0) {
|
|
122
|
+
lines.push("### Removed");
|
|
123
|
+
lines.push("| Ref | Text |");
|
|
124
|
+
lines.push("|---|---|");
|
|
125
|
+
for (const v of reqs.removed) {
|
|
126
|
+
lines.push(`| ${shortRef(v.requirementId)} | ${truncate(v.text ?? "", 120)} |`);
|
|
127
|
+
}
|
|
128
|
+
lines.push("");
|
|
129
|
+
}
|
|
130
|
+
// Non-requirement entity summary
|
|
131
|
+
const others = [
|
|
132
|
+
["Documents", data.documents],
|
|
133
|
+
["Trace Links", data.traceLinks],
|
|
134
|
+
["Diagrams", data.diagrams],
|
|
135
|
+
["Blocks", data.blocks],
|
|
136
|
+
["Connectors", data.connectors],
|
|
137
|
+
];
|
|
138
|
+
const changed = others.filter(([, c]) => {
|
|
139
|
+
const n = counts(c);
|
|
140
|
+
return n.added + n.modified + n.removed > 0;
|
|
141
|
+
});
|
|
142
|
+
if (changed.length > 0) {
|
|
143
|
+
lines.push("### Other Changes");
|
|
144
|
+
lines.push("| Entity | Added | Modified | Removed |");
|
|
145
|
+
lines.push("|---|---|---|---|");
|
|
146
|
+
for (const [label, comp] of changed) {
|
|
147
|
+
const n = counts(comp);
|
|
148
|
+
lines.push(`| ${label} | ${n.added} | ${n.modified} | ${n.removed} |`);
|
|
149
|
+
}
|
|
150
|
+
lines.push("");
|
|
151
|
+
}
|
|
152
|
+
return lines.join("\n");
|
|
153
|
+
}
|
|
154
|
+
// ── Command registration ─────────────────────────────────────
|
|
155
|
+
export function registerDiffCommand(program, client) {
|
|
156
|
+
program
|
|
157
|
+
.command("diff")
|
|
158
|
+
.description("Show what changed between two baselines")
|
|
159
|
+
.argument("<tenant>", "Tenant slug")
|
|
160
|
+
.argument("<project>", "Project slug")
|
|
161
|
+
.requiredOption("--from <ref>", "Source baseline ref (earlier)")
|
|
162
|
+
.requiredOption("--to <ref>", "Target baseline ref (later)")
|
|
163
|
+
.option("--format <fmt>", "Output format: text, markdown", "text")
|
|
164
|
+
.option("-o, --output <file>", "Write report to file")
|
|
165
|
+
.action(async (tenant, project, opts) => {
|
|
166
|
+
const data = await client.get(`/baselines/${tenant}/${project}/compare`, { from: opts.from, to: opts.to });
|
|
167
|
+
let result;
|
|
168
|
+
if (isJsonMode()) {
|
|
169
|
+
result = JSON.stringify(buildStructured(data), null, 2);
|
|
170
|
+
}
|
|
171
|
+
else if (opts.format === "markdown") {
|
|
172
|
+
result = formatMarkdown(data);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
result = formatPretty(data);
|
|
176
|
+
}
|
|
177
|
+
if (opts.output) {
|
|
178
|
+
writeFileSync(opts.output, result + "\n", "utf-8");
|
|
179
|
+
console.log(`Diff written to ${opts.output}`);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
console.log(result);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import { registerImportExportCommands } from "./commands/import-export.js";
|
|
|
20
20
|
import { registerActivityCommands } from "./commands/activity.js";
|
|
21
21
|
import { registerImplementationCommands } from "./commands/implementation.js";
|
|
22
22
|
import { registerLintCommands } from "./commands/lint.js";
|
|
23
|
+
import { registerDiffCommand } from "./commands/diff.js";
|
|
23
24
|
const program = new Command();
|
|
24
25
|
// Lazy-init: only create client when a command actually runs
|
|
25
26
|
let client = null;
|
|
@@ -69,6 +70,7 @@ registerImportExportCommands(program, clientProxy);
|
|
|
69
70
|
registerActivityCommands(program, clientProxy);
|
|
70
71
|
registerImplementationCommands(program, clientProxy);
|
|
71
72
|
registerLintCommands(program, clientProxy);
|
|
73
|
+
registerDiffCommand(program, clientProxy);
|
|
72
74
|
// Handle async errors from Commander action handlers
|
|
73
75
|
process.on("uncaughtException", (err) => {
|
|
74
76
|
console.error(`Error: ${err.message}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "airgen-cli",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "AIRGen CLI — requirements engineering from the command line",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
|
-
"package.json"
|
|
12
|
+
"package.json",
|
|
13
|
+
"README.md"
|
|
13
14
|
],
|
|
14
15
|
"scripts": {
|
|
15
16
|
"build": "tsc -p tsconfig.json",
|
|
@@ -21,7 +22,12 @@
|
|
|
21
22
|
"requirements",
|
|
22
23
|
"engineering",
|
|
23
24
|
"cli",
|
|
24
|
-
"requirements-management"
|
|
25
|
+
"requirements-management",
|
|
26
|
+
"systems-engineering",
|
|
27
|
+
"traceability",
|
|
28
|
+
"sysml",
|
|
29
|
+
"mbse",
|
|
30
|
+
"uht"
|
|
25
31
|
],
|
|
26
32
|
"license": "MIT",
|
|
27
33
|
"repository": {
|