airgen-cli 0.1.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.
- package/dist/client.d.ts +51 -0
- package/dist/client.js +263 -0
- package/dist/commands/activity.d.ts +3 -0
- package/dist/commands/activity.js +33 -0
- package/dist/commands/ai.d.ts +3 -0
- package/dist/commands/ai.js +86 -0
- package/dist/commands/baselines.d.ts +3 -0
- package/dist/commands/baselines.js +55 -0
- package/dist/commands/diagrams.d.ts +3 -0
- package/dist/commands/diagrams.js +196 -0
- package/dist/commands/documents.d.ts +3 -0
- package/dist/commands/documents.js +123 -0
- package/dist/commands/implementation.d.ts +3 -0
- package/dist/commands/implementation.js +76 -0
- package/dist/commands/import-export.d.ts +3 -0
- package/dist/commands/import-export.js +66 -0
- package/dist/commands/projects.d.ts +3 -0
- package/dist/commands/projects.js +56 -0
- package/dist/commands/quality.d.ts +3 -0
- package/dist/commands/quality.js +61 -0
- package/dist/commands/reports.d.ts +3 -0
- package/dist/commands/reports.js +114 -0
- package/dist/commands/requirements.d.ts +3 -0
- package/dist/commands/requirements.js +231 -0
- package/dist/commands/tenants.d.ts +3 -0
- package/dist/commands/tenants.js +17 -0
- package/dist/commands/traceability.d.ts +3 -0
- package/dist/commands/traceability.js +106 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +30 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +75 -0
- package/dist/output.d.ts +13 -0
- package/dist/output.js +46 -0
- package/package.json +39 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { output, printTable, isJsonMode } from "../output.js";
|
|
2
|
+
export function registerDiagramCommands(program, client) {
|
|
3
|
+
const cmd = program.command("diagrams").alias("diag").description("Architecture diagrams");
|
|
4
|
+
cmd
|
|
5
|
+
.command("list")
|
|
6
|
+
.description("List all diagrams in a project")
|
|
7
|
+
.argument("<tenant>", "Tenant slug")
|
|
8
|
+
.argument("<project>", "Project slug")
|
|
9
|
+
.action(async (tenant, project) => {
|
|
10
|
+
const data = await client.get(`/architecture/diagrams/${tenant}/${project}`);
|
|
11
|
+
const diagrams = data.diagrams ?? [];
|
|
12
|
+
if (isJsonMode()) {
|
|
13
|
+
output(diagrams);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
printTable(["ID", "Name", "View", "Blocks", "Connectors"], diagrams.map(d => [
|
|
17
|
+
d.id,
|
|
18
|
+
d.name ?? "",
|
|
19
|
+
d.view ?? "",
|
|
20
|
+
String(d.blockCount ?? 0),
|
|
21
|
+
String(d.connectorCount ?? 0),
|
|
22
|
+
]));
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
cmd
|
|
26
|
+
.command("get")
|
|
27
|
+
.description("Get full detail of a diagram (blocks + connectors)")
|
|
28
|
+
.argument("<tenant>", "Tenant slug")
|
|
29
|
+
.argument("<project>", "Project slug")
|
|
30
|
+
.argument("<id>", "Diagram ID")
|
|
31
|
+
.action(async (tenant, project, id) => {
|
|
32
|
+
const [blocks, connectors] = await Promise.all([
|
|
33
|
+
client.get(`/architecture/blocks/${tenant}/${project}/${id}`),
|
|
34
|
+
client.get(`/architecture/connectors/${tenant}/${project}/${id}`),
|
|
35
|
+
]);
|
|
36
|
+
output({ blocks, connectors });
|
|
37
|
+
});
|
|
38
|
+
cmd
|
|
39
|
+
.command("create")
|
|
40
|
+
.description("Create a new diagram")
|
|
41
|
+
.argument("<tenant>", "Tenant slug")
|
|
42
|
+
.argument("<project-key>", "Project key")
|
|
43
|
+
.requiredOption("--name <name>", "Diagram name")
|
|
44
|
+
.option("--view <view>", "View type: block, internal, deployment, requirements_schema")
|
|
45
|
+
.option("--description <desc>", "Description")
|
|
46
|
+
.action(async (tenant, projectKey, opts) => {
|
|
47
|
+
const data = await client.post("/architecture/diagrams", {
|
|
48
|
+
tenant,
|
|
49
|
+
projectKey,
|
|
50
|
+
name: opts.name,
|
|
51
|
+
view: opts.view,
|
|
52
|
+
description: opts.description,
|
|
53
|
+
});
|
|
54
|
+
if (isJsonMode()) {
|
|
55
|
+
output(data);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.log("Diagram created.");
|
|
59
|
+
output(data);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
cmd
|
|
63
|
+
.command("update")
|
|
64
|
+
.description("Update diagram properties")
|
|
65
|
+
.argument("<tenant>", "Tenant slug")
|
|
66
|
+
.argument("<project>", "Project slug")
|
|
67
|
+
.argument("<id>", "Diagram ID")
|
|
68
|
+
.option("--name <name>", "Diagram name")
|
|
69
|
+
.option("--description <desc>", "Description")
|
|
70
|
+
.option("--view <view>", "View type")
|
|
71
|
+
.action(async (tenant, project, id, opts) => {
|
|
72
|
+
const body = {};
|
|
73
|
+
if (opts.name)
|
|
74
|
+
body.name = opts.name;
|
|
75
|
+
if (opts.description)
|
|
76
|
+
body.description = opts.description;
|
|
77
|
+
if (opts.view)
|
|
78
|
+
body.view = opts.view;
|
|
79
|
+
await client.patch(`/architecture/diagrams/${tenant}/${project}/${id}`, body);
|
|
80
|
+
console.log("Diagram updated.");
|
|
81
|
+
});
|
|
82
|
+
cmd
|
|
83
|
+
.command("delete")
|
|
84
|
+
.description("Delete a diagram")
|
|
85
|
+
.argument("<tenant>", "Tenant slug")
|
|
86
|
+
.argument("<project>", "Project slug")
|
|
87
|
+
.argument("<id>", "Diagram ID")
|
|
88
|
+
.action(async (tenant, project, id) => {
|
|
89
|
+
await client.delete(`/architecture/diagrams/${tenant}/${project}/${id}`);
|
|
90
|
+
console.log("Diagram deleted.");
|
|
91
|
+
});
|
|
92
|
+
// Blocks sub-group
|
|
93
|
+
const blocks = cmd.command("blocks").description("Manage blocks in diagrams");
|
|
94
|
+
blocks
|
|
95
|
+
.command("library")
|
|
96
|
+
.description("Get all block definitions in the project")
|
|
97
|
+
.argument("<tenant>", "Tenant slug")
|
|
98
|
+
.argument("<project>", "Project slug")
|
|
99
|
+
.action(async (tenant, project) => {
|
|
100
|
+
const data = await client.get(`/architecture/block-library/${tenant}/${project}`);
|
|
101
|
+
output(data);
|
|
102
|
+
});
|
|
103
|
+
blocks
|
|
104
|
+
.command("create")
|
|
105
|
+
.description("Create a new block")
|
|
106
|
+
.argument("<tenant>", "Tenant slug")
|
|
107
|
+
.argument("<project-key>", "Project key")
|
|
108
|
+
.requiredOption("--diagram <id>", "Diagram ID")
|
|
109
|
+
.requiredOption("--name <name>", "Block name")
|
|
110
|
+
.requiredOption("--kind <kind>", "Kind: system, subsystem, component, actor, external, interface")
|
|
111
|
+
.option("--x <n>", "Position X", "0")
|
|
112
|
+
.option("--y <n>", "Position Y", "0")
|
|
113
|
+
.option("--width <n>", "Width")
|
|
114
|
+
.option("--height <n>", "Height")
|
|
115
|
+
.option("--stereotype <s>", "Stereotype")
|
|
116
|
+
.option("--description <desc>", "Description")
|
|
117
|
+
.action(async (tenant, projectKey, opts) => {
|
|
118
|
+
const data = await client.post("/architecture/blocks", {
|
|
119
|
+
tenant,
|
|
120
|
+
projectKey,
|
|
121
|
+
diagramId: opts.diagram,
|
|
122
|
+
name: opts.name,
|
|
123
|
+
kind: opts.kind,
|
|
124
|
+
positionX: parseInt(opts.x, 10),
|
|
125
|
+
positionY: parseInt(opts.y, 10),
|
|
126
|
+
sizeWidth: opts.width ? parseInt(opts.width, 10) : undefined,
|
|
127
|
+
sizeHeight: opts.height ? parseInt(opts.height, 10) : undefined,
|
|
128
|
+
stereotype: opts.stereotype,
|
|
129
|
+
description: opts.description,
|
|
130
|
+
});
|
|
131
|
+
if (isJsonMode()) {
|
|
132
|
+
output(data);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
console.log("Block created.");
|
|
136
|
+
output(data);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
blocks
|
|
140
|
+
.command("delete")
|
|
141
|
+
.description("Delete a block from the project")
|
|
142
|
+
.argument("<tenant>", "Tenant slug")
|
|
143
|
+
.argument("<project>", "Project slug")
|
|
144
|
+
.argument("<block-id>", "Block ID")
|
|
145
|
+
.action(async (tenant, project, blockId) => {
|
|
146
|
+
await client.delete(`/architecture/blocks/${tenant}/${project}/${blockId}`);
|
|
147
|
+
console.log("Block deleted.");
|
|
148
|
+
});
|
|
149
|
+
// Connectors sub-group
|
|
150
|
+
const connectors = cmd.command("connectors").alias("conn").description("Manage connectors");
|
|
151
|
+
connectors
|
|
152
|
+
.command("create")
|
|
153
|
+
.description("Create a connector between blocks")
|
|
154
|
+
.argument("<tenant>", "Tenant slug")
|
|
155
|
+
.argument("<project-key>", "Project key")
|
|
156
|
+
.requiredOption("--diagram <id>", "Diagram ID")
|
|
157
|
+
.requiredOption("--source <id>", "Source block ID")
|
|
158
|
+
.requiredOption("--target <id>", "Target block ID")
|
|
159
|
+
.requiredOption("--kind <kind>", "Kind: association, flow, dependency, composition")
|
|
160
|
+
.option("--label <text>", "Label")
|
|
161
|
+
.option("--source-port <id>", "Source port ID")
|
|
162
|
+
.option("--target-port <id>", "Target port ID")
|
|
163
|
+
.option("--line-style <style>", "Line style: straight, smoothstep, step, polyline, bezier")
|
|
164
|
+
.action(async (tenant, projectKey, opts) => {
|
|
165
|
+
const data = await client.post("/architecture/connectors", {
|
|
166
|
+
tenant,
|
|
167
|
+
projectKey,
|
|
168
|
+
diagramId: opts.diagram,
|
|
169
|
+
source: opts.source,
|
|
170
|
+
target: opts.target,
|
|
171
|
+
kind: opts.kind,
|
|
172
|
+
label: opts.label,
|
|
173
|
+
sourcePortId: opts.sourcePort,
|
|
174
|
+
targetPortId: opts.targetPort,
|
|
175
|
+
lineStyle: opts.lineStyle,
|
|
176
|
+
});
|
|
177
|
+
if (isJsonMode()) {
|
|
178
|
+
output(data);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
console.log("Connector created.");
|
|
182
|
+
output(data);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
connectors
|
|
186
|
+
.command("delete")
|
|
187
|
+
.description("Delete a connector")
|
|
188
|
+
.argument("<tenant>", "Tenant slug")
|
|
189
|
+
.argument("<project>", "Project slug")
|
|
190
|
+
.argument("<connector-id>", "Connector ID")
|
|
191
|
+
.requiredOption("--diagram <id>", "Diagram ID")
|
|
192
|
+
.action(async (tenant, project, connectorId, opts) => {
|
|
193
|
+
await client.delete(`/architecture/connectors/${tenant}/${project}/${connectorId}?diagramId=${opts.diagram}`);
|
|
194
|
+
console.log("Connector deleted.");
|
|
195
|
+
});
|
|
196
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { output, printTable, isJsonMode } from "../output.js";
|
|
2
|
+
export function registerDocumentCommands(program, client) {
|
|
3
|
+
const cmd = program.command("documents").alias("docs").description("Manage documents");
|
|
4
|
+
cmd
|
|
5
|
+
.command("list")
|
|
6
|
+
.description("List all documents in a project")
|
|
7
|
+
.argument("<tenant>", "Tenant slug")
|
|
8
|
+
.argument("<project>", "Project slug")
|
|
9
|
+
.action(async (tenant, project) => {
|
|
10
|
+
const data = await client.get(`/documents/${tenant}/${project}`);
|
|
11
|
+
const docs = data.documents ?? [];
|
|
12
|
+
if (isJsonMode()) {
|
|
13
|
+
output(docs);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
printTable(["Slug", "Name", "Kind", "Sections", "Reqs"], docs.map(d => [
|
|
17
|
+
d.slug,
|
|
18
|
+
d.name ?? d.slug,
|
|
19
|
+
d.kind ?? "",
|
|
20
|
+
String(d.sectionCount ?? 0),
|
|
21
|
+
String(d.requirementCount ?? 0),
|
|
22
|
+
]));
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
cmd
|
|
26
|
+
.command("get")
|
|
27
|
+
.description("Get document with all sections and content")
|
|
28
|
+
.argument("<tenant>", "Tenant slug")
|
|
29
|
+
.argument("<project>", "Project slug")
|
|
30
|
+
.argument("<slug>", "Document slug")
|
|
31
|
+
.action(async (tenant, project, slug) => {
|
|
32
|
+
const data = await client.get(`/sections/${tenant}/${project}/${slug}/full`);
|
|
33
|
+
output(data);
|
|
34
|
+
});
|
|
35
|
+
cmd
|
|
36
|
+
.command("create")
|
|
37
|
+
.description("Create a new document")
|
|
38
|
+
.argument("<tenant>", "Tenant slug")
|
|
39
|
+
.argument("<project-key>", "Project key")
|
|
40
|
+
.requiredOption("--name <name>", "Document name")
|
|
41
|
+
.option("--code <code>", "Document code")
|
|
42
|
+
.option("--description <desc>", "Description")
|
|
43
|
+
.action(async (tenant, projectKey, opts) => {
|
|
44
|
+
const data = await client.post("/documents", {
|
|
45
|
+
tenant,
|
|
46
|
+
projectKey,
|
|
47
|
+
name: opts.name,
|
|
48
|
+
shortCode: opts.code,
|
|
49
|
+
description: opts.description,
|
|
50
|
+
});
|
|
51
|
+
if (isJsonMode()) {
|
|
52
|
+
output(data);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.log("Document created.");
|
|
56
|
+
output(data);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
cmd
|
|
60
|
+
.command("delete")
|
|
61
|
+
.description("Delete a document")
|
|
62
|
+
.argument("<tenant>", "Tenant slug")
|
|
63
|
+
.argument("<project>", "Project slug")
|
|
64
|
+
.argument("<slug>", "Document slug")
|
|
65
|
+
.action(async (tenant, project, slug) => {
|
|
66
|
+
await client.delete(`/documents/${tenant}/${project}/${slug}`);
|
|
67
|
+
console.log(`Document "${slug}" deleted.`);
|
|
68
|
+
});
|
|
69
|
+
cmd
|
|
70
|
+
.command("export")
|
|
71
|
+
.description("Export document as markdown")
|
|
72
|
+
.argument("<tenant>", "Tenant slug")
|
|
73
|
+
.argument("<project>", "Project slug")
|
|
74
|
+
.argument("<slug>", "Document slug")
|
|
75
|
+
.action(async (tenant, project, slug) => {
|
|
76
|
+
const data = await client.get(`/markdown/${tenant}/${project}/${slug}/content`);
|
|
77
|
+
if (isJsonMode()) {
|
|
78
|
+
output(data);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
console.log(data.markdown ?? data.content ?? JSON.stringify(data, null, 2));
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
// Sections sub-group
|
|
85
|
+
const sections = cmd.command("sections").alias("sec").description("Manage document sections");
|
|
86
|
+
sections
|
|
87
|
+
.command("create")
|
|
88
|
+
.description("Create a section in a document")
|
|
89
|
+
.argument("<tenant>", "Tenant slug")
|
|
90
|
+
.argument("<project-key>", "Project key")
|
|
91
|
+
.argument("<document>", "Document slug")
|
|
92
|
+
.requiredOption("--title <title>", "Section title")
|
|
93
|
+
.option("--order <n>", "Order index")
|
|
94
|
+
.option("--description <desc>", "Description")
|
|
95
|
+
.option("--code <code>", "Short code")
|
|
96
|
+
.action(async (tenant, projectKey, document, opts) => {
|
|
97
|
+
const data = await client.post("/sections", {
|
|
98
|
+
tenant,
|
|
99
|
+
projectKey,
|
|
100
|
+
documentSlug: document,
|
|
101
|
+
name: opts.title,
|
|
102
|
+
order: opts.order ? parseInt(opts.order, 10) : undefined,
|
|
103
|
+
description: opts.description,
|
|
104
|
+
shortCode: opts.code,
|
|
105
|
+
});
|
|
106
|
+
if (isJsonMode()) {
|
|
107
|
+
output(data);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
console.log("Section created.");
|
|
111
|
+
output(data);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
sections
|
|
115
|
+
.command("delete")
|
|
116
|
+
.description("Delete a section")
|
|
117
|
+
.argument("<tenant>", "Tenant slug")
|
|
118
|
+
.argument("<section-id>", "Section ID")
|
|
119
|
+
.action(async (tenant, sectionId) => {
|
|
120
|
+
await client.delete(`/sections/${sectionId}`, { tenant });
|
|
121
|
+
console.log("Section deleted.");
|
|
122
|
+
});
|
|
123
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { output } from "../output.js";
|
|
2
|
+
export function registerImplementationCommands(program, client) {
|
|
3
|
+
const cmd = program.command("impl").description("Implementation tracking");
|
|
4
|
+
cmd
|
|
5
|
+
.command("status")
|
|
6
|
+
.description("Set implementation status on a requirement")
|
|
7
|
+
.argument("<tenant>", "Tenant slug")
|
|
8
|
+
.argument("<project>", "Project slug")
|
|
9
|
+
.argument("<requirement-id>", "Requirement ID")
|
|
10
|
+
.requiredOption("--status <s>", "Status: not_started, in_progress, implemented, verified, blocked")
|
|
11
|
+
.option("--notes <text>", "Notes")
|
|
12
|
+
.action(async (tenant, project, requirementId, opts) => {
|
|
13
|
+
// Implementation status is stored in requirement attributes
|
|
14
|
+
const data = await client.patch(`/requirements/${tenant}/${project}/${requirementId}`, {
|
|
15
|
+
attributes: {
|
|
16
|
+
implementationStatus: opts.status,
|
|
17
|
+
implementationNotes: opts.notes,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
output(data);
|
|
21
|
+
});
|
|
22
|
+
cmd
|
|
23
|
+
.command("link")
|
|
24
|
+
.description("Link a code artifact to a requirement")
|
|
25
|
+
.argument("<tenant>", "Tenant slug")
|
|
26
|
+
.argument("<project>", "Project slug")
|
|
27
|
+
.argument("<requirement-id>", "Requirement ID")
|
|
28
|
+
.requiredOption("--type <type>", "Artifact type: file, commit, pr, issue, test, url")
|
|
29
|
+
.requiredOption("--path <path>", "Artifact path/reference")
|
|
30
|
+
.option("--label <text>", "Label")
|
|
31
|
+
.option("--line <n>", "Line number (for file type)")
|
|
32
|
+
.action(async (tenant, project, requirementId, opts) => {
|
|
33
|
+
// Fetch current requirement to get existing artifacts
|
|
34
|
+
const reqData = await client.get(`/requirements/${tenant}/${project}`, { page: "1", limit: "500" });
|
|
35
|
+
const req = (reqData.data ?? []).find(r => r.id === requirementId);
|
|
36
|
+
if (!req) {
|
|
37
|
+
console.error(`Requirement ${requirementId} not found.`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
const attrs = req.attributes ?? {};
|
|
41
|
+
const artifacts = attrs.artifacts ?? [];
|
|
42
|
+
const newArtifact = { type: opts.type, path: opts.path };
|
|
43
|
+
if (opts.label)
|
|
44
|
+
newArtifact.label = opts.label;
|
|
45
|
+
if (opts.line)
|
|
46
|
+
newArtifact.line = parseInt(opts.line, 10);
|
|
47
|
+
artifacts.push(newArtifact);
|
|
48
|
+
await client.patch(`/requirements/${tenant}/${project}/${requirementId}`, {
|
|
49
|
+
attributes: { ...attrs, artifacts },
|
|
50
|
+
});
|
|
51
|
+
console.log("Artifact linked.");
|
|
52
|
+
});
|
|
53
|
+
cmd
|
|
54
|
+
.command("unlink")
|
|
55
|
+
.description("Remove an artifact link from a requirement")
|
|
56
|
+
.argument("<tenant>", "Tenant slug")
|
|
57
|
+
.argument("<project>", "Project slug")
|
|
58
|
+
.argument("<requirement-id>", "Requirement ID")
|
|
59
|
+
.requiredOption("--type <type>", "Artifact type")
|
|
60
|
+
.requiredOption("--path <path>", "Artifact path")
|
|
61
|
+
.action(async (tenant, project, requirementId, opts) => {
|
|
62
|
+
const reqData = await client.get(`/requirements/${tenant}/${project}`, { page: "1", limit: "500" });
|
|
63
|
+
const req = (reqData.data ?? []).find(r => r.id === requirementId);
|
|
64
|
+
if (!req) {
|
|
65
|
+
console.error(`Requirement ${requirementId} not found.`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
const attrs = req.attributes ?? {};
|
|
69
|
+
const artifacts = attrs.artifacts ?? [];
|
|
70
|
+
const filtered = artifacts.filter(a => !(a.type === opts.type && a.path === opts.path));
|
|
71
|
+
await client.patch(`/requirements/${tenant}/${project}/${requirementId}`, {
|
|
72
|
+
attributes: { ...attrs, artifacts: filtered },
|
|
73
|
+
});
|
|
74
|
+
console.log("Artifact unlinked.");
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { output, isJsonMode } from "../output.js";
|
|
3
|
+
export function registerImportExportCommands(program, client) {
|
|
4
|
+
const imp = program.command("import").description("Import requirements");
|
|
5
|
+
imp
|
|
6
|
+
.command("requirements")
|
|
7
|
+
.description("Import requirements from CSV file (one per line)")
|
|
8
|
+
.argument("<tenant>", "Tenant slug")
|
|
9
|
+
.argument("<project-key>", "Project key")
|
|
10
|
+
.requiredOption("--file <path>", "Path to CSV file")
|
|
11
|
+
.option("--document <slug>", "Target document slug")
|
|
12
|
+
.option("--section <id>", "Target section ID")
|
|
13
|
+
.option("--tags <tags>", "Comma-separated tags to apply to all")
|
|
14
|
+
.option("--dry-run", "Preview without importing")
|
|
15
|
+
.action(async (tenant, projectKey, opts) => {
|
|
16
|
+
const content = readFileSync(opts.file, "utf-8");
|
|
17
|
+
const lines = content.split("\n").map(l => l.trim()).filter(l => l.length > 0);
|
|
18
|
+
const tags = opts.tags?.split(",").map(t => t.trim());
|
|
19
|
+
if (opts.dryRun) {
|
|
20
|
+
console.log(`Would import ${lines.length} requirements.`);
|
|
21
|
+
for (const line of lines.slice(0, 5)) {
|
|
22
|
+
console.log(` - ${line.slice(0, 80)}`);
|
|
23
|
+
}
|
|
24
|
+
if (lines.length > 5)
|
|
25
|
+
console.log(` ... and ${lines.length - 5} more`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
let created = 0;
|
|
29
|
+
for (const text of lines) {
|
|
30
|
+
await client.post("/requirements", {
|
|
31
|
+
tenant,
|
|
32
|
+
projectKey,
|
|
33
|
+
text,
|
|
34
|
+
documentSlug: opts.document,
|
|
35
|
+
sectionId: opts.section,
|
|
36
|
+
tags,
|
|
37
|
+
});
|
|
38
|
+
created++;
|
|
39
|
+
}
|
|
40
|
+
console.log(`Imported ${created} requirements.`);
|
|
41
|
+
});
|
|
42
|
+
const exp = program.command("export").description("Export data");
|
|
43
|
+
exp
|
|
44
|
+
.command("requirements")
|
|
45
|
+
.description("Export requirements as markdown or JSON")
|
|
46
|
+
.argument("<tenant>", "Tenant slug")
|
|
47
|
+
.argument("<project>", "Project slug")
|
|
48
|
+
.option("--document <slug>", "Export specific document as markdown")
|
|
49
|
+
.option("--format <fmt>", "Format: json, markdown", "json")
|
|
50
|
+
.action(async (tenant, project, opts) => {
|
|
51
|
+
if (opts.document && opts.format === "markdown") {
|
|
52
|
+
const data = await client.get(`/markdown/${tenant}/${project}/${opts.document}/content`);
|
|
53
|
+
if (isJsonMode()) {
|
|
54
|
+
output(data);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.log(data.markdown ?? data.content ?? JSON.stringify(data, null, 2));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Export all as JSON
|
|
62
|
+
const data = await client.get(`/requirements/${tenant}/${project}`, { page: "1", limit: "1000" });
|
|
63
|
+
output(data);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { output, printTable, isJsonMode } from "../output.js";
|
|
2
|
+
export function registerProjectCommands(program, client) {
|
|
3
|
+
const cmd = program.command("projects").alias("proj").description("Manage projects");
|
|
4
|
+
cmd
|
|
5
|
+
.command("list")
|
|
6
|
+
.description("List all projects in a tenant")
|
|
7
|
+
.argument("<tenant>", "Tenant slug")
|
|
8
|
+
.action(async (tenant) => {
|
|
9
|
+
const data = await client.get(`/tenants/${tenant}/projects`);
|
|
10
|
+
const projects = data.projects ?? [];
|
|
11
|
+
if (isJsonMode()) {
|
|
12
|
+
output(projects);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
printTable(["Slug", "Name", "Code", "Reqs", "Docs"], projects.map(p => [
|
|
16
|
+
p.slug,
|
|
17
|
+
p.name ?? p.slug,
|
|
18
|
+
p.code ?? "",
|
|
19
|
+
String(p.requirementCount ?? 0),
|
|
20
|
+
String(p.documentCount ?? 0),
|
|
21
|
+
]));
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
cmd
|
|
25
|
+
.command("create")
|
|
26
|
+
.description("Create a new project")
|
|
27
|
+
.argument("<tenant>", "Tenant slug")
|
|
28
|
+
.requiredOption("--name <name>", "Project name")
|
|
29
|
+
.option("--key <key>", "Project key")
|
|
30
|
+
.option("--code <code>", "Project code")
|
|
31
|
+
.option("--description <desc>", "Description")
|
|
32
|
+
.action(async (tenant, opts) => {
|
|
33
|
+
const data = await client.post(`/tenants/${tenant}/projects`, {
|
|
34
|
+
name: opts.name,
|
|
35
|
+
key: opts.key,
|
|
36
|
+
code: opts.code,
|
|
37
|
+
description: opts.description,
|
|
38
|
+
});
|
|
39
|
+
if (isJsonMode()) {
|
|
40
|
+
output(data);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.log("Project created successfully.");
|
|
44
|
+
output(data);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
cmd
|
|
48
|
+
.command("delete")
|
|
49
|
+
.description("Delete a project")
|
|
50
|
+
.argument("<tenant>", "Tenant slug")
|
|
51
|
+
.argument("<project>", "Project slug")
|
|
52
|
+
.action(async (tenant, project) => {
|
|
53
|
+
await client.delete(`/tenants/${tenant}/projects/${project}`);
|
|
54
|
+
console.log(`Project "${project}" deleted.`);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { output } from "../output.js";
|
|
2
|
+
export function registerQualityCommands(program, client) {
|
|
3
|
+
const cmd = program.command("quality").alias("qa").description("Requirement quality analysis");
|
|
4
|
+
cmd
|
|
5
|
+
.command("analyze")
|
|
6
|
+
.description("Analyze requirement quality (ISO 29148 + EARS)")
|
|
7
|
+
.argument("<text>", "Requirement text to analyze")
|
|
8
|
+
.option("--auto-fix", "Suggest improved text")
|
|
9
|
+
.action(async (text, opts) => {
|
|
10
|
+
if (opts.autoFix) {
|
|
11
|
+
const data = await client.post("/apply-fix", { text });
|
|
12
|
+
output(data);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
const data = await client.post("/qa", { text });
|
|
16
|
+
output(data);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
cmd
|
|
20
|
+
.command("score")
|
|
21
|
+
.description("Start, check, or stop background QA scorer")
|
|
22
|
+
.argument("<action>", "Action: start, status, stop")
|
|
23
|
+
.option("--tenant <slug>", "Tenant slug (required for start)")
|
|
24
|
+
.option("--project <slug>", "Project slug (required for start)")
|
|
25
|
+
.action(async (action, opts) => {
|
|
26
|
+
if (action === "start") {
|
|
27
|
+
const data = await client.post("/workers/qa-scorer/start", {
|
|
28
|
+
tenant: opts.tenant,
|
|
29
|
+
project: opts.project,
|
|
30
|
+
});
|
|
31
|
+
output(data);
|
|
32
|
+
}
|
|
33
|
+
else if (action === "status") {
|
|
34
|
+
const data = await client.get("/workers/qa-scorer/status");
|
|
35
|
+
output(data);
|
|
36
|
+
}
|
|
37
|
+
else if (action === "stop") {
|
|
38
|
+
const data = await client.post("/workers/qa-scorer/stop", {});
|
|
39
|
+
output(data);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.error(`Unknown action "${action}". Use: start, status, stop`);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
cmd
|
|
46
|
+
.command("draft")
|
|
47
|
+
.description("Generate candidate requirement texts from natural language")
|
|
48
|
+
.argument("<input>", "Natural language description")
|
|
49
|
+
.option("--glossary <text>", "Domain glossary")
|
|
50
|
+
.option("--constraints <text>", "Constraints")
|
|
51
|
+
.option("-n, --count <n>", "Number of candidates")
|
|
52
|
+
.action(async (input, opts) => {
|
|
53
|
+
const data = await client.post("/draft/candidates", {
|
|
54
|
+
user_input: input,
|
|
55
|
+
glossary: opts.glossary,
|
|
56
|
+
constraints: opts.constraints,
|
|
57
|
+
n: opts.count ? parseInt(opts.count, 10) : undefined,
|
|
58
|
+
});
|
|
59
|
+
output(data);
|
|
60
|
+
});
|
|
61
|
+
}
|