airgen-cli 0.19.1 → 0.20.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.
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { output, printTable, isJsonMode } from "../output.js";
|
|
3
|
+
function sourceRef(l) {
|
|
4
|
+
return l.sourceRequirement?.ref ?? l.sourceRequirementId ?? "";
|
|
5
|
+
}
|
|
6
|
+
function targetRef(l) {
|
|
7
|
+
return l.targetRequirement?.ref ?? l.targetRequirementId ?? "";
|
|
8
|
+
}
|
|
3
9
|
export function registerTraceabilityCommands(program, client) {
|
|
4
10
|
const cmd = program.command("traces").alias("trace").description("Traceability links");
|
|
5
11
|
cmd
|
|
@@ -28,8 +34,8 @@ export function registerTraceabilityCommands(program, client) {
|
|
|
28
34
|
console.log(`Trace links: ${links.length}${total > links.length ? ` of ${total}` : ""}\n`);
|
|
29
35
|
printTable(["ID", "Source", "Target", "Type", "Description"], links.map(l => [
|
|
30
36
|
l.id,
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
sourceRef(l),
|
|
38
|
+
targetRef(l),
|
|
33
39
|
l.linkType ?? "",
|
|
34
40
|
l.description ?? "",
|
|
35
41
|
]));
|
|
@@ -164,6 +170,66 @@ export function registerTraceabilityCommands(program, client) {
|
|
|
164
170
|
}
|
|
165
171
|
console.log(`${opts.dryRun ? "Would create" : "Created"} ${created} trace links. Errors: ${errors}.`);
|
|
166
172
|
});
|
|
173
|
+
// ── trace validate: check link direction conventions ──
|
|
174
|
+
cmd
|
|
175
|
+
.command("validate")
|
|
176
|
+
.description("Validate trace link directions against SE conventions (derives: parent→child, verifies: VER→target)")
|
|
177
|
+
.argument("<tenant>", "Tenant slug")
|
|
178
|
+
.argument("<project>", "Project slug")
|
|
179
|
+
.option("--fix", "Reverse incorrectly directed links")
|
|
180
|
+
.action(async (tenant, project, opts) => {
|
|
181
|
+
const data = await client.get(`/trace-links/${tenant}/${project}`);
|
|
182
|
+
const links = data.traceLinks ?? [];
|
|
183
|
+
// Infer document tier from ref prefix
|
|
184
|
+
const tierOrder = { STK: 0, SYS: 1, IFC: 2, SUB: 3, ARC: 2, VER: 4, HAZ: 1 };
|
|
185
|
+
function refTier(ref) {
|
|
186
|
+
const prefix = ref.split("-")[0];
|
|
187
|
+
return tierOrder[prefix] ?? -1;
|
|
188
|
+
}
|
|
189
|
+
const issues = [];
|
|
190
|
+
for (const l of links) {
|
|
191
|
+
const src = sourceRef(l);
|
|
192
|
+
const tgt = targetRef(l);
|
|
193
|
+
const srcTier = refTier(src);
|
|
194
|
+
const tgtTier = refTier(tgt);
|
|
195
|
+
if (l.linkType === "derives" && srcTier > tgtTier && srcTier >= 0 && tgtTier >= 0) {
|
|
196
|
+
issues.push({ link: l, reason: `derives should flow parent→child (${src} tier ${srcTier} > ${tgt} tier ${tgtTier})` });
|
|
197
|
+
}
|
|
198
|
+
if (l.linkType === "verifies" && !src.startsWith("VER-") && tgt.startsWith("VER-")) {
|
|
199
|
+
issues.push({ link: l, reason: `verifies should flow VER→target, not ${src}→${tgt}` });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (issues.length === 0) {
|
|
203
|
+
console.log(`All ${links.length} trace links pass direction validation.`);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
console.log(`Found ${issues.length} direction issue(s):\n`);
|
|
207
|
+
for (const { link, reason } of issues) {
|
|
208
|
+
console.log(` ${sourceRef(link)} --${link.linkType}--> ${targetRef(link)}`);
|
|
209
|
+
console.log(` ${reason}`);
|
|
210
|
+
if (opts.fix) {
|
|
211
|
+
try {
|
|
212
|
+
// Delete old link, create reversed
|
|
213
|
+
await client.delete(`/trace-links/${tenant}/${project}/${link.id}`);
|
|
214
|
+
await client.post("/trace-links", {
|
|
215
|
+
tenant, projectKey: project,
|
|
216
|
+
sourceRequirementId: link.targetRequirementId,
|
|
217
|
+
targetRequirementId: link.sourceRequirementId,
|
|
218
|
+
linkType: link.linkType,
|
|
219
|
+
description: link.description,
|
|
220
|
+
rationale: link.rationale,
|
|
221
|
+
});
|
|
222
|
+
console.log(` Fixed: reversed to ${targetRef(link)} --${link.linkType}--> ${sourceRef(link)}`);
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
console.error(` Failed to fix: ${err.message}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (!opts.fix) {
|
|
230
|
+
console.log(`\nUse --fix to reverse these links.`);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
167
233
|
// Linksets
|
|
168
234
|
const linksets = cmd.command("linksets").description("Document linksets");
|
|
169
235
|
linksets
|