airgen-cli 0.8.0 → 0.9.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.
@@ -362,6 +362,50 @@ export function registerDiagramCommands(program, client) {
362
362
  await client.delete(`/architecture/diagrams/${tenant}/${project}/${id}`);
363
363
  console.log("Diagram deleted.");
364
364
  });
365
+ cmd
366
+ .command("deduplicate")
367
+ .description("Find and remove duplicate-named diagrams (keeps the one with more blocks)")
368
+ .argument("<tenant>", "Tenant slug")
369
+ .argument("<project>", "Project slug")
370
+ .option("--dry-run", "Show duplicates without deleting")
371
+ .action(async (tenant, project, opts) => {
372
+ const data = await client.get(`/architecture/diagrams/${tenant}/${project}`);
373
+ const diagrams = data.diagrams ?? [];
374
+ // Group by name
375
+ const byName = new Map();
376
+ for (const d of diagrams) {
377
+ const name = (d.name ?? "").toLowerCase().trim();
378
+ const group = byName.get(name) ?? [];
379
+ group.push(d);
380
+ byName.set(name, group);
381
+ }
382
+ const duplicates = [...byName.entries()].filter(([, group]) => group.length > 1);
383
+ if (duplicates.length === 0) {
384
+ console.log("No duplicate diagrams found.");
385
+ return;
386
+ }
387
+ let removed = 0;
388
+ for (const [name, group] of duplicates) {
389
+ // Keep the one with the most blocks, or the newest
390
+ const sorted = group.sort((a, b) => (b.blockCount ?? 0) - (a.blockCount ?? 0) || (b.id > a.id ? 1 : -1));
391
+ const keep = sorted[0];
392
+ const toRemove = sorted.slice(1);
393
+ console.log(`"${keep.name ?? name}": keeping ${keep.id} (${keep.blockCount ?? 0} blocks), removing ${toRemove.length} duplicate(s)`);
394
+ if (!opts.dryRun) {
395
+ for (const dup of toRemove) {
396
+ await client.delete(`/architecture/diagrams/${tenant}/${project}/${dup.id}`);
397
+ removed++;
398
+ }
399
+ }
400
+ else {
401
+ for (const dup of toRemove) {
402
+ console.log(` [dry-run] would remove ${dup.id} (${dup.blockCount ?? 0} blocks)`);
403
+ }
404
+ removed += toRemove.length;
405
+ }
406
+ }
407
+ console.log(`${opts.dryRun ? "Would remove" : "Removed"} ${removed} duplicate diagram(s).`);
408
+ });
365
409
  // Blocks sub-group
366
410
  const blocks = cmd.command("blocks").description("Manage blocks in diagrams");
367
411
  blocks
@@ -34,7 +34,7 @@ export function registerTraceabilityCommands(program, client) {
34
34
  .argument("<project-key>", "Project key")
35
35
  .requiredOption("--source <id>", "Source requirement ID")
36
36
  .requiredOption("--target <id>", "Target requirement ID")
37
- .requiredOption("--type <type>", "Link type: satisfies, derives, verifies, implements, refines, conflicts")
37
+ .requiredOption("--type <type>", "Link type: satisfies, derives, verifies, implements, refines, conflicts, motivates")
38
38
  .option("--description <desc>", "Short description for traceability matrices")
39
39
  .option("--rationale <text>", "Engineering justification for why this link exists")
40
40
  .action(async (tenant, projectKey, opts) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airgen-cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "AIRGen CLI — requirements engineering from the command line",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",