@ncoderz/awa 1.6.0 → 1.7.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/index.js CHANGED
@@ -16,15 +16,15 @@ import {
16
16
  rmDir,
17
17
  walkDirectory,
18
18
  writeTextFile
19
- } from "./chunk-ALEGCDAZ.js";
19
+ } from "./chunk-OQZTQ5ZI.js";
20
20
 
21
21
  // src/cli/index.ts
22
- import { Command } from "commander";
22
+ import { Command, Option } from "commander";
23
23
 
24
24
  // src/_generated/package_info.ts
25
25
  var PACKAGE_INFO = {
26
26
  "name": "@ncoderz/awa",
27
- "version": "1.6.0",
27
+ "version": "1.7.0",
28
28
  "author": "Richard Sewell <richard.sewell@ncoderz.com>",
29
29
  "license": "MIT",
30
30
  "description": "awa is an Agent Workflow for AIs. It is also a CLI tool to powerfully manage agent workflow files using templates."
@@ -121,6 +121,83 @@ function checkCodeAgainstSpec(markers, specs, config) {
121
121
  });
122
122
  }
123
123
  }
124
+ for (const propId of specs.propertyIds) {
125
+ if (!testedIds.has(propId)) {
126
+ const loc = specs.idLocations.get(propId);
127
+ const specFile = loc ? void 0 : specs.specFiles.find((sf) => sf.propertyIds.includes(propId));
128
+ findings.push({
129
+ severity: "warning",
130
+ code: "uncovered-property",
131
+ message: `Property '${propId}' has no @awa-test reference`,
132
+ filePath: loc?.filePath ?? specFile?.filePath,
133
+ line: loc?.line,
134
+ id: propId
135
+ });
136
+ }
137
+ }
138
+ const componentFiles = /* @__PURE__ */ new Map();
139
+ for (const marker of markers.markers) {
140
+ if (marker.type === "component") {
141
+ if (!componentFiles.has(marker.id)) {
142
+ componentFiles.set(marker.id, /* @__PURE__ */ new Set());
143
+ }
144
+ }
145
+ }
146
+ const fileToComponents = /* @__PURE__ */ new Map();
147
+ for (const marker of markers.markers) {
148
+ if (marker.type === "component") {
149
+ const existing = fileToComponents.get(marker.filePath) ?? [];
150
+ existing.push(marker.id);
151
+ fileToComponents.set(marker.filePath, existing);
152
+ }
153
+ }
154
+ for (const marker of markers.markers) {
155
+ if (marker.type === "impl") {
156
+ const components = fileToComponents.get(marker.filePath);
157
+ if (components) {
158
+ for (const comp of components) {
159
+ componentFiles.get(comp)?.add(marker.id);
160
+ }
161
+ }
162
+ }
163
+ }
164
+ const designImplements = /* @__PURE__ */ new Map();
165
+ for (const specFile of specs.specFiles) {
166
+ if (specFile.componentImplements) {
167
+ for (const [comp, ids] of specFile.componentImplements) {
168
+ const existing = designImplements.get(comp) ?? /* @__PURE__ */ new Set();
169
+ for (const id of ids) existing.add(id);
170
+ designImplements.set(comp, existing);
171
+ }
172
+ }
173
+ }
174
+ for (const [compName, codeImplIds] of componentFiles) {
175
+ const designImplIds = designImplements.get(compName);
176
+ if (!designImplIds) continue;
177
+ for (const implId of codeImplIds) {
178
+ if (!designImplIds.has(implId)) {
179
+ findings.push({
180
+ severity: "warning",
181
+ code: "impl-not-in-implements",
182
+ message: `@awa-impl '${implId}' in component '${compName}' is not listed in its IMPLEMENTS`,
183
+ id: implId
184
+ });
185
+ }
186
+ }
187
+ for (const implId of designImplIds) {
188
+ if (!codeImplIds.has(implId)) {
189
+ const loc = specs.idLocations.get(compName);
190
+ findings.push({
191
+ severity: "warning",
192
+ code: "implements-not-in-impl",
193
+ message: `IMPLEMENTS '${implId}' in component '${compName}' has no @awa-impl in code`,
194
+ filePath: loc?.filePath,
195
+ line: loc?.line,
196
+ id: implId
197
+ });
198
+ }
199
+ }
200
+ }
124
201
  return { findings };
125
202
  }
126
203
 
@@ -259,6 +336,347 @@ async function collectCodeFiles(codeGlobs, ignore) {
259
336
  return collectFiles(codeGlobs, ignore);
260
337
  }
261
338
 
339
+ // src/core/check/matrix-fixer.ts
340
+ import { readFile as readFile2, writeFile } from "fs/promises";
341
+ import { basename } from "path";
342
+ async function fixMatrices(specs, crossRefPatterns) {
343
+ const codeToReqFile = buildCodeToReqFileMap(specs.specFiles);
344
+ const fileResults = [];
345
+ for (const specFile of specs.specFiles) {
346
+ const fileName = basename(specFile.filePath);
347
+ if (fileName.startsWith("DESIGN-")) {
348
+ const changed = await fixDesignMatrix(specFile.filePath, codeToReqFile, crossRefPatterns);
349
+ fileResults.push({ filePath: specFile.filePath, changed });
350
+ } else if (fileName.startsWith("TASK-")) {
351
+ const changed = await fixTaskMatrix(
352
+ specFile.filePath,
353
+ codeToReqFile,
354
+ specs,
355
+ crossRefPatterns
356
+ );
357
+ fileResults.push({ filePath: specFile.filePath, changed });
358
+ }
359
+ }
360
+ return {
361
+ filesFixed: fileResults.filter((r) => r.changed).length,
362
+ fileResults
363
+ };
364
+ }
365
+ function buildCodeToReqFileMap(specFiles) {
366
+ const map = /* @__PURE__ */ new Map();
367
+ for (const sf of specFiles) {
368
+ if (/\bREQ-/.test(basename(sf.filePath)) && sf.code) {
369
+ map.set(sf.code, basename(sf.filePath));
370
+ }
371
+ }
372
+ return map;
373
+ }
374
+ function getCodePrefix(id) {
375
+ const match = /^([A-Z][A-Z0-9]*)[-_]/.exec(id);
376
+ return match?.[1] ?? "";
377
+ }
378
+ async function fixDesignMatrix(filePath, codeToReqFile, crossRefPatterns) {
379
+ let content;
380
+ try {
381
+ content = await readFile2(filePath, "utf-8");
382
+ } catch {
383
+ return false;
384
+ }
385
+ const { components, properties } = parseDesignFileData(content, crossRefPatterns);
386
+ const acToComponents = /* @__PURE__ */ new Map();
387
+ for (const comp of components) {
388
+ for (const acId of comp.implements) {
389
+ const existing = acToComponents.get(acId) ?? [];
390
+ existing.push(comp.name);
391
+ acToComponents.set(acId, existing);
392
+ }
393
+ }
394
+ const acToProperties = /* @__PURE__ */ new Map();
395
+ for (const prop of properties) {
396
+ for (const acId of prop.validates) {
397
+ const existing = acToProperties.get(acId) ?? [];
398
+ existing.push(prop.id);
399
+ acToProperties.set(acId, existing);
400
+ }
401
+ }
402
+ const allAcIds = [...acToComponents.keys()];
403
+ const grouped = groupByReqFile(allAcIds, codeToReqFile);
404
+ const newSection = generateDesignSection(grouped, acToComponents, acToProperties);
405
+ const newContent = replaceTraceabilitySection(content, newSection);
406
+ if (newContent === content) return false;
407
+ await writeFile(filePath, newContent, "utf-8");
408
+ return true;
409
+ }
410
+ function parseDesignFileData(content, crossRefPatterns) {
411
+ const lines = content.split("\n");
412
+ const components = [];
413
+ const properties = [];
414
+ const componentRegex = /^###\s+([A-Z][A-Z0-9]*-[A-Za-z][A-Za-z0-9]*(?:[A-Z][a-z0-9]*)*)\s*$/;
415
+ const reqIdRegex = /^###\s+([A-Z][A-Z0-9]*-\d+(?:\.\d+)?)\s*:/;
416
+ const propIdRegex = /^-\s+([A-Z][A-Z0-9]*_P-\d+)\s/;
417
+ let currentComponent = null;
418
+ let lastPropertyId = null;
419
+ for (const line of lines) {
420
+ const compMatch = componentRegex.exec(line);
421
+ if (compMatch?.[1] && !reqIdRegex.test(line)) {
422
+ currentComponent = compMatch[1];
423
+ lastPropertyId = null;
424
+ components.push({ name: currentComponent, implements: [] });
425
+ continue;
426
+ }
427
+ if (/^#{1,2}\s/.test(line) && !compMatch) {
428
+ currentComponent = null;
429
+ lastPropertyId = null;
430
+ continue;
431
+ }
432
+ const propMatch = propIdRegex.exec(line);
433
+ if (propMatch?.[1]) {
434
+ lastPropertyId = propMatch[1];
435
+ properties.push({ id: lastPropertyId, validates: [] });
436
+ continue;
437
+ }
438
+ for (const pattern of crossRefPatterns) {
439
+ const patIdx = line.indexOf(pattern);
440
+ if (patIdx !== -1) {
441
+ const afterPattern = line.slice(patIdx + pattern.length);
442
+ const ids = extractIdsFromText(afterPattern);
443
+ if (ids.length > 0) {
444
+ const isImplements = pattern.toLowerCase().includes("implements");
445
+ if (isImplements && currentComponent) {
446
+ const comp = components.find((c) => c.name === currentComponent);
447
+ if (comp) comp.implements.push(...ids);
448
+ } else if (!isImplements && lastPropertyId) {
449
+ const prop = properties.find((p) => p.id === lastPropertyId);
450
+ if (prop) prop.validates.push(...ids);
451
+ }
452
+ }
453
+ }
454
+ }
455
+ }
456
+ return { components, properties };
457
+ }
458
+ function generateDesignSection(grouped, acToComponents, acToProperties) {
459
+ const lines = [];
460
+ const reqFiles = [...grouped.keys()].sort();
461
+ for (const reqFile of reqFiles) {
462
+ lines.push(`### ${reqFile}`);
463
+ lines.push("");
464
+ const acIds = grouped.get(reqFile) ?? [];
465
+ acIds.sort(compareIds);
466
+ for (const acId of acIds) {
467
+ const components = acToComponents.get(acId) ?? [];
468
+ const props = acToProperties.get(acId) ?? [];
469
+ for (const comp of components) {
470
+ const propStr = props.length > 0 ? ` (${props.join(", ")})` : "";
471
+ lines.push(`- ${acId} \u2192 ${comp}${propStr}`);
472
+ }
473
+ }
474
+ lines.push("");
475
+ }
476
+ return lines.join("\n");
477
+ }
478
+ async function fixTaskMatrix(filePath, codeToReqFile, specs, crossRefPatterns) {
479
+ let content;
480
+ try {
481
+ content = await readFile2(filePath, "utf-8");
482
+ } catch {
483
+ return false;
484
+ }
485
+ const { tasks, sourceReqs, sourceDesigns } = parseTaskFileData(content, crossRefPatterns);
486
+ const acToTasks = /* @__PURE__ */ new Map();
487
+ for (const task of tasks) {
488
+ for (const acId of task.implements) {
489
+ const existing = acToTasks.get(acId) ?? [];
490
+ existing.push(task.id);
491
+ acToTasks.set(acId, existing);
492
+ }
493
+ }
494
+ const idToTestTasks = /* @__PURE__ */ new Map();
495
+ for (const task of tasks) {
496
+ for (const testId of task.tests) {
497
+ const existing = idToTestTasks.get(testId) ?? [];
498
+ existing.push(task.id);
499
+ idToTestTasks.set(testId, existing);
500
+ }
501
+ }
502
+ const sourceAcIds = /* @__PURE__ */ new Set();
503
+ for (const reqName of sourceReqs) {
504
+ const reqCode = extractCodeFromFileName(reqName);
505
+ for (const sf of specs.specFiles) {
506
+ if (sf.code === reqCode && /\bREQ-/.test(basename(sf.filePath))) {
507
+ for (const acId of sf.acIds) sourceAcIds.add(acId);
508
+ }
509
+ }
510
+ }
511
+ const sourcePropertyIds = /* @__PURE__ */ new Set();
512
+ for (const designName of sourceDesigns) {
513
+ const designCode = extractCodeFromFileName(designName);
514
+ for (const sf of specs.specFiles) {
515
+ if (sf.code === designCode && /\bDESIGN-/.test(basename(sf.filePath))) {
516
+ for (const propId of sf.propertyIds) sourcePropertyIds.add(propId);
517
+ }
518
+ }
519
+ }
520
+ const allRefIds = /* @__PURE__ */ new Set([...acToTasks.keys(), ...idToTestTasks.keys()]);
521
+ const uncovered = [];
522
+ for (const acId of sourceAcIds) {
523
+ if (!allRefIds.has(acId)) uncovered.push(acId);
524
+ }
525
+ for (const propId of sourcePropertyIds) {
526
+ if (!allRefIds.has(propId)) uncovered.push(propId);
527
+ }
528
+ uncovered.sort(compareIds);
529
+ const allIdsForMatrix = [.../* @__PURE__ */ new Set([...acToTasks.keys(), ...idToTestTasks.keys()])];
530
+ const grouped = groupByReqFile(allIdsForMatrix, codeToReqFile);
531
+ const newSection = generateTaskSection(
532
+ grouped,
533
+ acToTasks,
534
+ idToTestTasks,
535
+ sourcePropertyIds,
536
+ uncovered
537
+ );
538
+ const newContent = replaceTraceabilitySection(content, newSection);
539
+ if (newContent === content) return false;
540
+ await writeFile(filePath, newContent, "utf-8");
541
+ return true;
542
+ }
543
+ function parseTaskFileData(content, crossRefPatterns) {
544
+ const lines = content.split("\n");
545
+ const tasks = [];
546
+ const sourceReqs = [];
547
+ const sourceDesigns = [];
548
+ const sourceRegex = /^SOURCE:\s*(.+)/;
549
+ const taskIdRegex = /^-\s+\[[ x]\]\s+(T-[A-Z][A-Z0-9]*-\d+)/;
550
+ let currentTaskId = null;
551
+ for (const line of lines) {
552
+ const sourceMatch = sourceRegex.exec(line);
553
+ if (sourceMatch?.[1]) {
554
+ const parts = sourceMatch[1].split(",").map((s) => s.trim());
555
+ for (const part of parts) {
556
+ if (part.startsWith("REQ-")) sourceReqs.push(part);
557
+ else if (part.startsWith("DESIGN-")) sourceDesigns.push(part);
558
+ }
559
+ continue;
560
+ }
561
+ const taskMatch = taskIdRegex.exec(line);
562
+ if (taskMatch?.[1]) {
563
+ currentTaskId = taskMatch[1];
564
+ tasks.push({ id: currentTaskId, implements: [], tests: [] });
565
+ continue;
566
+ }
567
+ if (/^##\s/.test(line)) {
568
+ currentTaskId = null;
569
+ continue;
570
+ }
571
+ if (/^---/.test(line)) {
572
+ currentTaskId = null;
573
+ continue;
574
+ }
575
+ if (currentTaskId) {
576
+ const task = tasks.find((t) => t.id === currentTaskId);
577
+ if (task) {
578
+ for (const pattern of crossRefPatterns) {
579
+ const patIdx = line.indexOf(pattern);
580
+ if (patIdx !== -1) {
581
+ const afterPattern = line.slice(patIdx + pattern.length);
582
+ const ids = extractIdsFromText(afterPattern);
583
+ if (ids.length > 0) {
584
+ const isImplements = pattern.toLowerCase().includes("implements");
585
+ if (isImplements) {
586
+ task.implements.push(...ids);
587
+ }
588
+ }
589
+ }
590
+ }
591
+ const testsIdx = line.indexOf("TESTS:");
592
+ if (testsIdx !== -1) {
593
+ const afterTests = line.slice(testsIdx + "TESTS:".length);
594
+ const ids = extractIdsFromText(afterTests);
595
+ if (ids.length > 0) {
596
+ task.tests.push(...ids);
597
+ }
598
+ }
599
+ }
600
+ }
601
+ }
602
+ return { tasks, sourceReqs, sourceDesigns };
603
+ }
604
+ function generateTaskSection(grouped, acToTasks, idToTestTasks, propertyIds, uncovered) {
605
+ const lines = [];
606
+ const reqFiles = [...grouped.keys()].sort();
607
+ for (const reqFile of reqFiles) {
608
+ lines.push(`### ${reqFile}`);
609
+ lines.push("");
610
+ const ids = grouped.get(reqFile) ?? [];
611
+ const acIds = ids.filter((id) => !propertyIds.has(id));
612
+ const propIds = ids.filter((id) => propertyIds.has(id));
613
+ acIds.sort(compareIds);
614
+ propIds.sort(compareIds);
615
+ for (const acId of acIds) {
616
+ const tasks = acToTasks.get(acId) ?? [];
617
+ const testTasks = idToTestTasks.get(acId) ?? [];
618
+ const taskStr = tasks.length > 0 ? tasks.join(", ") : "(none)";
619
+ const testStr = testTasks.length > 0 ? ` (${testTasks.join(", ")})` : "";
620
+ lines.push(`- ${acId} \u2192 ${taskStr}${testStr}`);
621
+ }
622
+ for (const propId of propIds) {
623
+ const testTasks = idToTestTasks.get(propId) ?? [];
624
+ const testStr = testTasks.length > 0 ? testTasks.join(", ") : "(none)";
625
+ lines.push(`- ${propId} \u2192 ${testStr}`);
626
+ }
627
+ lines.push("");
628
+ }
629
+ const uncoveredStr = uncovered.length > 0 ? uncovered.join(", ") : "(none)";
630
+ lines.push(`UNCOVERED: ${uncoveredStr}`);
631
+ return lines.join("\n");
632
+ }
633
+ function groupByReqFile(ids, codeToReqFile) {
634
+ const groups = /* @__PURE__ */ new Map();
635
+ for (const id of ids) {
636
+ const code = getCodePrefix(id);
637
+ const reqFile = codeToReqFile.get(code);
638
+ if (!reqFile) continue;
639
+ const existing = groups.get(reqFile) ?? [];
640
+ existing.push(id);
641
+ groups.set(reqFile, existing);
642
+ }
643
+ return groups;
644
+ }
645
+ function replaceTraceabilitySection(content, newSection) {
646
+ const lines = content.split("\n");
647
+ const sectionStart = lines.findIndex((l) => /^##\s+Requirements Traceability\s*$/.test(l));
648
+ if (sectionStart === -1) return content;
649
+ let sectionEnd = lines.length;
650
+ for (let i = sectionStart + 1; i < lines.length; i++) {
651
+ const line = lines[i];
652
+ if (line !== void 0 && /^##\s/.test(line)) {
653
+ sectionEnd = i;
654
+ break;
655
+ }
656
+ }
657
+ const before = lines.slice(0, sectionStart + 1);
658
+ const after = lines.slice(sectionEnd);
659
+ const result = [...before, "", newSection.trimEnd(), "", ...after];
660
+ return result.join("\n");
661
+ }
662
+ function extractIdsFromText(text) {
663
+ const idRegex = /[A-Z][A-Z0-9]*-\d+(?:\.\d+)?(?:_AC-\d+)?|[A-Z][A-Z0-9]*_P-\d+/g;
664
+ const ids = [];
665
+ let match = idRegex.exec(text);
666
+ while (match !== null) {
667
+ ids.push(match[0]);
668
+ match = idRegex.exec(text);
669
+ }
670
+ return ids;
671
+ }
672
+ function extractCodeFromFileName(fileName) {
673
+ const match = /^(?:REQ|DESIGN|FEAT|EXAMPLES|API|TASK)-([A-Z][A-Z0-9]*)-/.exec(fileName);
674
+ return match?.[1] ?? "";
675
+ }
676
+ function compareIds(a, b) {
677
+ return a.localeCompare(b, void 0, { numeric: true });
678
+ }
679
+
262
680
  // src/core/check/reporter.ts
263
681
  import chalk from "chalk";
264
682
  function report(findings, format) {
@@ -334,7 +752,7 @@ function printRuleContext(f) {
334
752
  }
335
753
 
336
754
  // src/core/check/rule-loader.ts
337
- import { readFile as readFile2 } from "fs/promises";
755
+ import { readFile as readFile3 } from "fs/promises";
338
756
  import { join } from "path";
339
757
  import { parse as parseYaml } from "yaml";
340
758
  async function loadRules(schemaDir) {
@@ -355,7 +773,7 @@ function matchesTargetGlob(filePath, targetGlob) {
355
773
  async function loadRuleFile(filePath) {
356
774
  let content;
357
775
  try {
358
- content = await readFile2(filePath, "utf-8");
776
+ content = await readFile3(filePath, "utf-8");
359
777
  } catch {
360
778
  return null;
361
779
  }
@@ -547,7 +965,7 @@ var RuleValidationError = class extends Error {
547
965
  };
548
966
 
549
967
  // src/core/check/schema-checker.ts
550
- import { readFile as readFile3 } from "fs/promises";
968
+ import { readFile as readFile4 } from "fs/promises";
551
969
  import remarkGfm from "remark-gfm";
552
970
  import remarkParse from "remark-parse";
553
971
  import { unified } from "unified";
@@ -594,7 +1012,7 @@ async function checkSchemasAsync(specFiles, ruleSets) {
594
1012
  if (matchingRules.length === 0) continue;
595
1013
  let content;
596
1014
  try {
597
- content = await readFile3(spec.filePath, "utf-8");
1015
+ content = await readFile4(spec.filePath, "utf-8");
598
1016
  } catch {
599
1017
  continue;
600
1018
  }
@@ -1039,8 +1457,8 @@ function collectAllCodeBlocks(section) {
1039
1457
  }
1040
1458
 
1041
1459
  // src/core/check/spec-parser.ts
1042
- import { readFile as readFile4 } from "fs/promises";
1043
- import { basename } from "path";
1460
+ import { readFile as readFile5 } from "fs/promises";
1461
+ import { basename as basename2 } from "path";
1044
1462
  async function parseSpecs(config) {
1045
1463
  const files = await collectSpecFiles(config.specGlobs, config.specIgnore);
1046
1464
  const specFiles = [];
@@ -1068,7 +1486,7 @@ async function parseSpecs(config) {
1068
1486
  async function parseSpecFile(filePath, crossRefPatterns) {
1069
1487
  let content;
1070
1488
  try {
1071
- content = await readFile4(filePath, "utf-8");
1489
+ content = await readFile5(filePath, "utf-8");
1072
1490
  } catch {
1073
1491
  return null;
1074
1492
  }
@@ -1080,10 +1498,12 @@ async function parseSpecFile(filePath, crossRefPatterns) {
1080
1498
  const componentNames = [];
1081
1499
  const crossRefs = [];
1082
1500
  const idLocations = /* @__PURE__ */ new Map();
1501
+ const componentImplements = /* @__PURE__ */ new Map();
1083
1502
  const reqIdRegex = /^###\s+([A-Z][A-Z0-9]*-\d+(?:\.\d+)?)\s*:/;
1084
1503
  const acIdRegex = /^-\s+(?:\[[ x]\]\s+)?([A-Z][A-Z0-9]*-\d+(?:\.\d+)?_AC-\d+)\s/;
1085
1504
  const propIdRegex = /^-\s+([A-Z][A-Z0-9]*_P-\d+)\s/;
1086
1505
  const componentRegex = /^###\s+([A-Z][A-Z0-9]*-[A-Za-z][A-Za-z0-9]*(?:[A-Z][a-z0-9]*)*)\s*$/;
1506
+ let currentComponent = null;
1087
1507
  for (const [i, line] of lines.entries()) {
1088
1508
  const lineNum = i + 1;
1089
1509
  const reqMatch = reqIdRegex.exec(line);
@@ -1106,16 +1526,25 @@ async function parseSpecFile(filePath, crossRefPatterns) {
1106
1526
  if (!reqIdRegex.test(line)) {
1107
1527
  componentNames.push(compMatch[1]);
1108
1528
  idLocations.set(compMatch[1], { filePath, line: lineNum });
1529
+ currentComponent = compMatch[1];
1109
1530
  }
1110
1531
  }
1532
+ if (/^#{1,2}\s/.test(line) && !compMatch) {
1533
+ currentComponent = null;
1534
+ }
1111
1535
  for (const pattern of crossRefPatterns) {
1112
1536
  const patIdx = line.indexOf(pattern);
1113
1537
  if (patIdx !== -1) {
1114
1538
  const afterPattern = line.slice(patIdx + pattern.length);
1115
- const ids = extractIdsFromText(afterPattern);
1539
+ const ids = extractIdsFromText2(afterPattern);
1116
1540
  if (ids.length > 0) {
1117
1541
  const type = pattern.toLowerCase().includes("implements") ? "implements" : "validates";
1118
1542
  crossRefs.push({ type, ids, filePath, line: i + 1 });
1543
+ if (type === "implements" && currentComponent) {
1544
+ const existing = componentImplements.get(currentComponent) ?? [];
1545
+ existing.push(...ids);
1546
+ componentImplements.set(currentComponent, existing);
1547
+ }
1119
1548
  }
1120
1549
  }
1121
1550
  }
@@ -1128,16 +1557,17 @@ async function parseSpecFile(filePath, crossRefPatterns) {
1128
1557
  propertyIds,
1129
1558
  componentNames,
1130
1559
  crossRefs,
1131
- idLocations
1560
+ idLocations,
1561
+ componentImplements
1132
1562
  };
1133
1563
  }
1134
1564
  function extractCodePrefix(filePath) {
1135
- const name = basename(filePath, ".md");
1565
+ const name = basename2(filePath, ".md");
1136
1566
  const match = /^(?:REQ|DESIGN|FEAT|EXAMPLES|API)-([A-Z][A-Z0-9]*)-/.exec(name);
1137
1567
  if (match?.[1]) return match[1];
1138
1568
  return "";
1139
1569
  }
1140
- function extractIdsFromText(text) {
1570
+ function extractIdsFromText2(text) {
1141
1571
  const idRegex = /[A-Z][A-Z0-9]*-\d+(?:\.\d+)?(?:_AC-\d+)?|[A-Z][A-Z0-9]*_P-\d+/g;
1142
1572
  const ids = [];
1143
1573
  let match = idRegex.exec(text);
@@ -1170,6 +1600,37 @@ function checkSpecAgainstSpec(specs, markers, config) {
1170
1600
  }
1171
1601
  }
1172
1602
  }
1603
+ const implementedAcIds = /* @__PURE__ */ new Set();
1604
+ for (const specFile of specs.specFiles) {
1605
+ for (const crossRef of specFile.crossRefs) {
1606
+ if (crossRef.type === "implements") {
1607
+ for (const id of crossRef.ids) {
1608
+ implementedAcIds.add(id);
1609
+ }
1610
+ }
1611
+ }
1612
+ }
1613
+ const reqAcIds = /* @__PURE__ */ new Set();
1614
+ for (const specFile of specs.specFiles) {
1615
+ if (/\bREQ-/.test(specFile.filePath)) {
1616
+ for (const acId of specFile.acIds) {
1617
+ reqAcIds.add(acId);
1618
+ }
1619
+ }
1620
+ }
1621
+ for (const acId of reqAcIds) {
1622
+ if (!implementedAcIds.has(acId)) {
1623
+ const loc = specs.idLocations.get(acId);
1624
+ findings.push({
1625
+ severity: "error",
1626
+ code: "unlinked-ac",
1627
+ message: `Acceptance criterion '${acId}' is not claimed by any DESIGN IMPLEMENTS`,
1628
+ filePath: loc?.filePath,
1629
+ line: loc?.line,
1630
+ id: acId
1631
+ });
1632
+ }
1633
+ }
1173
1634
  if (!config.specOnly) {
1174
1635
  const referencedCodes = /* @__PURE__ */ new Set();
1175
1636
  for (const marker of markers.markers) {
@@ -1238,7 +1699,8 @@ var DEFAULT_CHECK_CONFIG = {
1238
1699
  schemaDir: ".awa/.agent/schemas",
1239
1700
  schemaEnabled: true,
1240
1701
  allowWarnings: false,
1241
- specOnly: false
1702
+ specOnly: false,
1703
+ fix: true
1242
1704
  };
1243
1705
 
1244
1706
  // src/commands/check.ts
@@ -1267,7 +1729,20 @@ async function checkCommand(cliOptions) {
1267
1729
  const allFindings = config.allowWarnings ? combinedFindings : combinedFindings.map(
1268
1730
  (f) => f.severity === "warning" ? { ...f, severity: "error" } : f
1269
1731
  );
1270
- report(allFindings, config.format);
1732
+ const isSummary = cliOptions.summary === true;
1733
+ if (isSummary) {
1734
+ const errors = allFindings.filter((f) => f.severity === "error").length;
1735
+ const warnings = allFindings.filter((f) => f.severity === "warning").length;
1736
+ console.log(`errors: ${errors}, warnings: ${warnings}`);
1737
+ } else {
1738
+ report(allFindings, config.format);
1739
+ }
1740
+ if (config.fix) {
1741
+ const fixResult = await fixMatrices(specs, config.crossRefPatterns);
1742
+ if (fixResult.filesFixed > 0) {
1743
+ logger.info(`Fixed traceability matrices in ${fixResult.filesFixed} file(s)`);
1744
+ }
1745
+ }
1271
1746
  const hasErrors = allFindings.some((f) => f.severity === "error");
1272
1747
  return hasErrors ? 1 : 0;
1273
1748
  } catch (error) {
@@ -1299,11 +1774,12 @@ function buildCheckConfig(fileConfig, cliOptions) {
1299
1774
  const ignoreMarkers = toStringArray(section?.["ignore-markers"]) ?? [
1300
1775
  ...DEFAULT_CHECK_CONFIG.ignoreMarkers
1301
1776
  ];
1302
- const format = cliOptions.format === "json" ? "json" : section?.format === "json" ? "json" : DEFAULT_CHECK_CONFIG.format;
1777
+ const format = cliOptions.json === true ? "json" : cliOptions.format === "json" ? "json" : section?.format === "json" ? "json" : DEFAULT_CHECK_CONFIG.format;
1303
1778
  const schemaDir = typeof section?.["schema-dir"] === "string" ? section["schema-dir"] : DEFAULT_CHECK_CONFIG.schemaDir;
1304
1779
  const schemaEnabled = typeof section?.["schema-enabled"] === "boolean" ? section["schema-enabled"] : DEFAULT_CHECK_CONFIG.schemaEnabled;
1305
1780
  const allowWarnings = cliOptions.allowWarnings === true ? true : typeof section?.["allow-warnings"] === "boolean" ? section["allow-warnings"] : DEFAULT_CHECK_CONFIG.allowWarnings;
1306
1781
  const specOnly = cliOptions.specOnly === true ? true : typeof section?.["spec-only"] === "boolean" ? section["spec-only"] : DEFAULT_CHECK_CONFIG.specOnly;
1782
+ const fix = cliOptions.fix === false ? false : DEFAULT_CHECK_CONFIG.fix;
1307
1783
  return {
1308
1784
  specGlobs,
1309
1785
  codeGlobs,
@@ -1317,7 +1793,8 @@ function buildCheckConfig(fileConfig, cliOptions) {
1317
1793
  schemaDir,
1318
1794
  schemaEnabled,
1319
1795
  allowWarnings,
1320
- specOnly
1796
+ specOnly,
1797
+ fix
1321
1798
  };
1322
1799
  }
1323
1800
  function toStringArray(value) {
@@ -2341,8 +2818,8 @@ async function prepareDiff(options) {
2341
2818
  async function diffCommand(cliOptions) {
2342
2819
  try {
2343
2820
  const fileConfig = await configLoader.load(cliOptions.config ?? null);
2344
- if (cliOptions.all || cliOptions.target) {
2345
- const mode = cliOptions.all ? "all" : "single";
2821
+ if (cliOptions.allTargets || cliOptions.target) {
2822
+ const mode = cliOptions.allTargets ? "all" : "single";
2346
2823
  const targets = batchRunner.resolveTargets(cliOptions, fileConfig, mode, cliOptions.target);
2347
2824
  let hasDifferences = false;
2348
2825
  for (const { targetName, options: options2 } of targets) {
@@ -2473,7 +2950,7 @@ var FeaturesReporter = class {
2473
2950
  var featuresReporter = new FeaturesReporter();
2474
2951
 
2475
2952
  // src/core/features/scanner.ts
2476
- import { readdir, readFile as readFile5 } from "fs/promises";
2953
+ import { readdir, readFile as readFile6 } from "fs/promises";
2477
2954
  import { join as join7, relative as relative3 } from "path";
2478
2955
  var FEATURE_PATTERN = /it\.features\.(?:includes|indexOf)\(\s*['"]([^'"]+)['"]\s*\)/g;
2479
2956
  async function* walkAllFiles(dir) {
@@ -2507,7 +2984,7 @@ var FeatureScanner = class {
2507
2984
  for await (const filePath of walkAllFiles(templatePath)) {
2508
2985
  filesScanned++;
2509
2986
  try {
2510
- const content = await readFile5(filePath, "utf-8");
2987
+ const content = await readFile6(filePath, "utf-8");
2511
2988
  const flags = this.extractFlags(content);
2512
2989
  const relPath = relative3(templatePath, filePath);
2513
2990
  for (const flag of flags) {
@@ -2532,22 +3009,37 @@ var featureScanner = new FeatureScanner();
2532
3009
 
2533
3010
  // src/commands/features.ts
2534
3011
  async function featuresCommand(cliOptions) {
3012
+ let mergedDir = null;
2535
3013
  try {
2536
- if (!cliOptions.json) {
3014
+ const silent = cliOptions.json || cliOptions.summary;
3015
+ if (!silent) {
2537
3016
  intro2("awa CLI - Feature Discovery");
2538
3017
  }
2539
3018
  const fileConfig = await configLoader.load(cliOptions.config ?? null);
2540
3019
  const templateSource = cliOptions.template ?? fileConfig?.template ?? null;
2541
3020
  const refresh = cliOptions.refresh ?? fileConfig?.refresh ?? false;
2542
3021
  const template2 = await templateResolver.resolve(templateSource, refresh);
2543
- const scanResult = await featureScanner.scan(template2.localPath);
3022
+ const overlays = cliOptions.overlay ?? fileConfig?.overlay ?? [];
3023
+ let templatePath = template2.localPath;
3024
+ if (overlays.length > 0) {
3025
+ const overlayDirs = await resolveOverlays(overlays, refresh);
3026
+ mergedDir = await buildMergedDir(template2.localPath, overlayDirs);
3027
+ templatePath = mergedDir;
3028
+ }
3029
+ const scanResult = await featureScanner.scan(templatePath);
2544
3030
  const presets = fileConfig?.presets;
2545
- featuresReporter.report({
2546
- scanResult,
2547
- json: cliOptions.json ?? false,
2548
- presets
2549
- });
2550
- if (!cliOptions.json) {
3031
+ if (cliOptions.summary) {
3032
+ console.log(
3033
+ `features: ${scanResult.features.length}, files-scanned: ${scanResult.filesScanned}`
3034
+ );
3035
+ } else {
3036
+ featuresReporter.report({
3037
+ scanResult,
3038
+ json: cliOptions.json ?? false,
3039
+ presets
3040
+ });
3041
+ }
3042
+ if (!silent) {
2551
3043
  outro2("Feature discovery complete!");
2552
3044
  }
2553
3045
  return 0;
@@ -2558,6 +3050,10 @@ async function featuresCommand(cliOptions) {
2558
3050
  logger.error(String(error));
2559
3051
  }
2560
3052
  return 1;
3053
+ } finally {
3054
+ if (mergedDir) {
3055
+ await rmDir(mergedDir);
3056
+ }
2561
3057
  }
2562
3058
  }
2563
3059
 
@@ -2649,8 +3145,8 @@ async function generateCommand(cliOptions) {
2649
3145
  if (!cliOptions.config && fileConfig === null) {
2650
3146
  logger.info("Tip: create .awa.toml to save your options for next time.");
2651
3147
  }
2652
- if (cliOptions.all || cliOptions.target) {
2653
- const mode = cliOptions.all ? "all" : "single";
3148
+ if (cliOptions.allTargets || cliOptions.target) {
3149
+ const mode = cliOptions.allTargets ? "all" : "single";
2654
3150
  const targets = batchRunner.resolveTargets(cliOptions, fileConfig, mode, cliOptions.target);
2655
3151
  for (const { targetName, options: options2 } of targets) {
2656
3152
  batchRunner.logForTarget(targetName, "Starting generation...");
@@ -2684,7 +3180,7 @@ import { intro as intro4, outro as outro4 } from "@clack/prompts";
2684
3180
 
2685
3181
  // src/core/template-test/fixture-loader.ts
2686
3182
  import { readdir as readdir2 } from "fs/promises";
2687
- import { basename as basename2, extname, join as join8 } from "path";
3183
+ import { basename as basename3, extname, join as join8 } from "path";
2688
3184
  import { parse } from "smol-toml";
2689
3185
  async function discoverFixtures(templatePath) {
2690
3186
  const testsDir = join8(templatePath, "_tests");
@@ -2706,7 +3202,7 @@ async function discoverFixtures(templatePath) {
2706
3202
  async function parseFixture(filePath) {
2707
3203
  const content = await readTextFile(filePath);
2708
3204
  const parsed = parse(content);
2709
- const name = basename2(filePath, extname(filePath));
3205
+ const name = basename3(filePath, extname(filePath));
2710
3206
  const features = toStringArray2(parsed.features) ?? [];
2711
3207
  const preset = toStringArray2(parsed.preset) ?? [];
2712
3208
  const removeFeatures = toStringArray2(parsed["remove-features"]) ?? [];
@@ -2729,7 +3225,11 @@ function toStringArray2(value) {
2729
3225
 
2730
3226
  // src/core/template-test/reporter.ts
2731
3227
  import chalk4 from "chalk";
2732
- function report2(result) {
3228
+ function report2(result, options) {
3229
+ if (options?.json) {
3230
+ reportJson2(result);
3231
+ return;
3232
+ }
2733
3233
  console.log("");
2734
3234
  for (const fixture of result.results) {
2735
3235
  reportFixture(fixture);
@@ -2743,6 +3243,27 @@ function report2(result) {
2743
3243
  }
2744
3244
  console.log("");
2745
3245
  }
3246
+ function reportJson2(result) {
3247
+ const output = {
3248
+ total: result.total,
3249
+ passed: result.passed,
3250
+ failed: result.failed,
3251
+ results: result.results.map((r) => ({
3252
+ name: r.name,
3253
+ passed: r.passed,
3254
+ ...r.error ? { error: r.error } : {},
3255
+ fileResults: r.fileResults.map((f) => ({
3256
+ path: f.path,
3257
+ found: f.found
3258
+ })),
3259
+ snapshotResults: r.snapshotResults.map((s) => ({
3260
+ path: s.path,
3261
+ status: s.status
3262
+ }))
3263
+ }))
3264
+ };
3265
+ console.log(JSON.stringify(output, null, 2));
3266
+ }
2746
3267
  function reportFixture(fixture) {
2747
3268
  const icon = fixture.passed ? chalk4.green("\u2714") : chalk4.red("\u2716");
2748
3269
  console.log(`${icon} ${fixture.name}`);
@@ -2905,31 +3426,61 @@ async function runAll(fixtures, templatePath, options, presetDefinitions = {}) {
2905
3426
 
2906
3427
  // src/commands/test.ts
2907
3428
  async function testCommand(options) {
3429
+ let mergedDir = null;
2908
3430
  try {
2909
- intro4("awa CLI - Template Test");
3431
+ const isJson = options.json === true;
3432
+ const isSummary = options.summary === true;
3433
+ const silent = isJson || isSummary;
3434
+ if (!silent) {
3435
+ intro4("awa CLI - Template Test");
3436
+ }
2910
3437
  const fileConfig = await configLoader.load(options.config ?? null);
2911
3438
  const templateSource = options.template ?? fileConfig?.template ?? null;
2912
- const template2 = await templateResolver.resolve(templateSource, false);
2913
- const fixtures = await discoverFixtures(template2.localPath);
3439
+ const refresh = options.refresh ?? false;
3440
+ const template2 = await templateResolver.resolve(templateSource, refresh);
3441
+ const overlays = options.overlay ?? fileConfig?.overlay ?? [];
3442
+ let templatePath = template2.localPath;
3443
+ if (overlays.length > 0) {
3444
+ const overlayDirs = await resolveOverlays(overlays, refresh);
3445
+ mergedDir = await buildMergedDir(template2.localPath, overlayDirs);
3446
+ templatePath = mergedDir;
3447
+ }
3448
+ const fixtures = await discoverFixtures(templatePath);
2914
3449
  if (fixtures.length === 0) {
2915
- logger.warn("No test fixtures found in _tests/ directory");
2916
- outro4("No tests to run.");
3450
+ if (isSummary) {
3451
+ console.log("passed: 0, failed: 0, total: 0");
3452
+ } else if (isJson) {
3453
+ console.log(JSON.stringify({ total: 0, passed: 0, failed: 0, results: [] }, null, 2));
3454
+ } else {
3455
+ logger.warn("No test fixtures found in _tests/ directory");
3456
+ outro4("No tests to run.");
3457
+ }
2917
3458
  return 0;
2918
3459
  }
2919
- logger.info(`Found ${fixtures.length} fixture(s)`);
3460
+ if (!silent) {
3461
+ logger.info(`Found ${fixtures.length} fixture(s)`);
3462
+ }
2920
3463
  const presetDefinitions = fileConfig?.presets ?? {};
2921
3464
  const result = await runAll(
2922
3465
  fixtures,
2923
- template2.localPath,
3466
+ templatePath,
2924
3467
  { updateSnapshots: options.updateSnapshots },
2925
3468
  presetDefinitions
2926
3469
  );
2927
- report2(result);
3470
+ if (isSummary) {
3471
+ console.log(`passed: ${result.passed}, failed: ${result.failed}, total: ${result.total}`);
3472
+ } else {
3473
+ report2(result, { json: isJson });
3474
+ }
2928
3475
  if (result.failed > 0) {
2929
- outro4(`${result.failed} fixture(s) failed.`);
3476
+ if (!silent) {
3477
+ outro4(`${result.failed} fixture(s) failed.`);
3478
+ }
2930
3479
  return 1;
2931
3480
  }
2932
- outro4("All tests passed!");
3481
+ if (!silent) {
3482
+ outro4("All tests passed!");
3483
+ }
2933
3484
  return 0;
2934
3485
  } catch (error) {
2935
3486
  if (error instanceof Error) {
@@ -2938,11 +3489,15 @@ async function testCommand(options) {
2938
3489
  logger.error(String(error));
2939
3490
  }
2940
3491
  return 2;
3492
+ } finally {
3493
+ if (mergedDir) {
3494
+ await rmDir(mergedDir);
3495
+ }
2941
3496
  }
2942
3497
  }
2943
3498
 
2944
3499
  // src/core/trace/content-assembler.ts
2945
- import { readFile as readFile6 } from "fs/promises";
3500
+ import { readFile as readFile7 } from "fs/promises";
2946
3501
  var DEFAULT_BEFORE_CONTEXT = 5;
2947
3502
  var DEFAULT_AFTER_CONTEXT = 20;
2948
3503
  async function assembleContent(result, taskPath, contextOptions) {
@@ -3141,7 +3696,7 @@ function findEnclosingBlock(lines, lineIdx, beforeContext = DEFAULT_BEFORE_CONTE
3141
3696
  }
3142
3697
  async function safeReadFile(filePath) {
3143
3698
  try {
3144
- return await readFile6(filePath, "utf-8");
3699
+ return await readFile7(filePath, "utf-8");
3145
3700
  } catch {
3146
3701
  return null;
3147
3702
  }
@@ -3493,7 +4048,7 @@ function pushToMap(map, key, value) {
3493
4048
  }
3494
4049
 
3495
4050
  // src/core/trace/input-resolver.ts
3496
- import { readFile as readFile7 } from "fs/promises";
4051
+ import { readFile as readFile8 } from "fs/promises";
3497
4052
  function resolveIds(ids, index) {
3498
4053
  const resolved = [];
3499
4054
  const warnings = [];
@@ -3509,7 +4064,7 @@ function resolveIds(ids, index) {
3509
4064
  async function resolveTaskFile(taskPath, index) {
3510
4065
  let content;
3511
4066
  try {
3512
- content = await readFile7(taskPath, "utf-8");
4067
+ content = await readFile8(taskPath, "utf-8");
3513
4068
  } catch {
3514
4069
  return { ids: [], warnings: [`Task file not found: ${taskPath}`] };
3515
4070
  }
@@ -3560,7 +4115,7 @@ async function resolveTaskFile(taskPath, index) {
3560
4115
  async function resolveSourceFile(filePath, index) {
3561
4116
  let content;
3562
4117
  try {
3563
- content = await readFile7(filePath, "utf-8");
4118
+ content = await readFile8(filePath, "utf-8");
3564
4119
  } catch {
3565
4120
  return { ids: [], warnings: [`Source file not found: ${filePath}`] };
3566
4121
  }
@@ -3861,6 +4416,7 @@ function buildScanConfig(fileConfig, overrides) {
3861
4416
  schemaEnabled: false,
3862
4417
  allowWarnings: true,
3863
4418
  specOnly: false,
4419
+ fix: true,
3864
4420
  ...overrides
3865
4421
  };
3866
4422
  }
@@ -3964,6 +4520,13 @@ async function traceCommand(options) {
3964
4520
  }
3965
4521
  return 1;
3966
4522
  }
4523
+ if (options.summary) {
4524
+ const chainCount = result.chains.length;
4525
+ const notFoundCount = result.notFound.length;
4526
+ process.stdout.write(`chains: ${chainCount}, not-found: ${notFoundCount}
4527
+ `);
4528
+ return result.notFound.length > 0 && result.chains.length === 0 ? 1 : 0;
4529
+ }
3967
4530
  let output;
3968
4531
  const isContentMode = options.content || options.maxTokens !== void 0;
3969
4532
  const queryLabel = ids.join(", ");
@@ -4063,7 +4626,7 @@ function printUpdateWarning(log, result) {
4063
4626
  }
4064
4627
 
4065
4628
  // src/utils/update-check-cache.ts
4066
- import { mkdir as mkdir3, readFile as readFile8, writeFile } from "fs/promises";
4629
+ import { mkdir as mkdir3, readFile as readFile9, writeFile as writeFile2 } from "fs/promises";
4067
4630
  import { homedir } from "os";
4068
4631
  import { dirname, join as join11 } from "path";
4069
4632
  var CACHE_DIR = join11(homedir(), ".cache", "awa");
@@ -4071,7 +4634,7 @@ var CACHE_FILE = join11(CACHE_DIR, "update-check.json");
4071
4634
  var DEFAULT_INTERVAL_MS = 864e5;
4072
4635
  async function shouldCheck(intervalMs = DEFAULT_INTERVAL_MS) {
4073
4636
  try {
4074
- const raw = await readFile8(CACHE_FILE, "utf-8");
4637
+ const raw = await readFile9(CACHE_FILE, "utf-8");
4075
4638
  const data = JSON.parse(raw);
4076
4639
  if (typeof data.timestamp !== "number" || typeof data.latestVersion !== "string") {
4077
4640
  return true;
@@ -4088,7 +4651,7 @@ async function writeCache(latestVersion) {
4088
4651
  timestamp: Date.now(),
4089
4652
  latestVersion
4090
4653
  };
4091
- await writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
4654
+ await writeFile2(CACHE_FILE, JSON.stringify(data), "utf-8");
4092
4655
  } catch {
4093
4656
  }
4094
4657
  }
@@ -4108,7 +4671,7 @@ function configureGenerateCommand(cmd) {
4108
4671
  "--delete",
4109
4672
  "Enable deletion of files listed in the delete list (default: warn only)",
4110
4673
  false
4111
- ).option("-c, --config <path>", "Path to configuration file").option("--refresh", "Force refresh of cached Git templates", false).option("--all", "Process all named targets from config", false).option("--target <name>", "Process a specific named target from config").option(
4674
+ ).option("-c, --config <path>", "Path to configuration file").option("--refresh", "Force refresh of cached Git templates", false).option("--all-targets", "Process all named targets from config", false).option("--target <name>", "Process a specific named target from config").option(
4112
4675
  "--overlay <path...>",
4113
4676
  "Overlay directory paths applied over base template (repeatable)"
4114
4677
  ).option("--json", "Output results as JSON (implies --dry-run)", false).option("--summary", "Output compact one-line summary", false).action(async (output, options) => {
@@ -4123,7 +4686,8 @@ function configureGenerateCommand(cmd) {
4123
4686
  delete: options.delete,
4124
4687
  config: options.config,
4125
4688
  refresh: options.refresh,
4126
- all: options.all,
4689
+ all: options.allTargets,
4690
+ allTargets: options.allTargets,
4127
4691
  target: options.target,
4128
4692
  overlay: options.overlay || [],
4129
4693
  json: options.json,
@@ -4137,7 +4701,7 @@ configureGenerateCommand(program.command("init"));
4137
4701
  template.command("diff").description("Compare template output with existing target directory").argument("[target]", "Target directory to compare against (optional if specified in config)").option("-t, --template <source>", "Template source (local path or Git repository)").option("-f, --features <flag...>", "Feature flags (can be specified multiple times)").option("--preset <name...>", "Preset names to enable (can be specified multiple times)").option(
4138
4702
  "--remove-features <flag...>",
4139
4703
  "Feature flags to remove (can be specified multiple times)"
4140
- ).option("-c, --config <path>", "Path to configuration file").option("--refresh", "Force refresh of cached Git templates", false).option("--list-unknown", "Include target-only files in diff results", false).option("--all", "Process all named targets from config", false).option("--target <name>", "Process a specific named target from config").option("-w, --watch", "Watch template directory for changes and re-run diff", false).option("--overlay <path...>", "Overlay directory paths applied over base template (repeatable)").option("--json", "Output results as JSON", false).option("--summary", "Output compact one-line summary", false).action(async (target, options) => {
4704
+ ).option("-c, --config <path>", "Path to configuration file").option("--refresh", "Force refresh of cached Git templates", false).option("--list-unknown", "Include target-only files in diff results", false).option("--all-targets", "Process all named targets from config", false).option("--target <name>", "Process a specific named target from config").option("-w, --watch", "Watch template directory for changes and re-run diff", false).option("--overlay <path...>", "Overlay directory paths applied over base template (repeatable)").option("--json", "Output results as JSON", false).option("--summary", "Output compact one-line summary", false).action(async (target, options) => {
4141
4705
  const cliOptions = {
4142
4706
  output: target,
4143
4707
  // Use target as output for consistency
@@ -4148,7 +4712,8 @@ template.command("diff").description("Compare template output with existing targ
4148
4712
  config: options.config,
4149
4713
  refresh: options.refresh,
4150
4714
  listUnknown: options.listUnknown,
4151
- all: options.all,
4715
+ all: options.allTargets,
4716
+ allTargets: options.allTargets,
4152
4717
  target: options.target,
4153
4718
  watch: options.watch,
4154
4719
  overlay: options.overlay || [],
@@ -4160,7 +4725,9 @@ template.command("diff").description("Compare template output with existing targ
4160
4725
  });
4161
4726
  program.command("check").description(
4162
4727
  "Validate spec files against schemas and check traceability between code markers and specs"
4163
- ).option("-c, --config <path>", "Path to configuration file").option("--spec-ignore <pattern...>", "Glob patterns to exclude from spec file scanning").option("--code-ignore <pattern...>", "Glob patterns to exclude from code file scanning").option("--format <format>", "Output format (text or json)", "text").option(
4728
+ ).option("-c, --config <path>", "Path to configuration file").option("--spec-ignore <pattern...>", "Glob patterns to exclude from spec file scanning").option("--code-ignore <pattern...>", "Glob patterns to exclude from code file scanning").option("--json", "Output results as JSON", false).addOption(
4729
+ new Option("--format <format>", "Output format (text or json)").default("text").hideHelp()
4730
+ ).option("--summary", "Output compact one-line summary", false).option(
4164
4731
  "--allow-warnings",
4165
4732
  "Allow warnings without failing (default: warnings are errors)",
4166
4733
  false
@@ -4168,38 +4735,50 @@ program.command("check").description(
4168
4735
  "--spec-only",
4169
4736
  "Run only spec-level checks (schema and cross-refs); skip code-to-spec traceability",
4170
4737
  false
4738
+ ).option(
4739
+ "--no-fix",
4740
+ "Skip regeneration of Requirements Traceability sections in DESIGN and TASK files"
4171
4741
  ).action(async (options) => {
4172
4742
  const cliOptions = {
4173
4743
  config: options.config,
4174
4744
  specIgnore: options.specIgnore,
4175
4745
  codeIgnore: options.codeIgnore,
4176
4746
  format: options.format,
4747
+ json: options.json,
4748
+ summary: options.summary,
4177
4749
  allowWarnings: options.allowWarnings,
4178
- specOnly: options.specOnly
4750
+ specOnly: options.specOnly,
4751
+ fix: options.fix
4179
4752
  };
4180
4753
  const exitCode = await checkCommand(cliOptions);
4181
4754
  process.exit(exitCode);
4182
4755
  });
4183
- template.command("features").description("Discover feature flags available in a template").option("-t, --template <source>", "Template source (local path or Git repository)").option("-c, --config <path>", "Path to configuration file").option("--refresh", "Force refresh of cached Git templates", false).option("--json", "Output results as JSON", false).action(async (options) => {
4756
+ template.command("features").description("Discover feature flags available in a template").option("-t, --template <source>", "Template source (local path or Git repository)").option("-c, --config <path>", "Path to configuration file").option("--refresh", "Force refresh of cached Git templates", false).option("--overlay <path...>", "Overlay directory paths applied over base template (repeatable)").option("--json", "Output results as JSON", false).option("--summary", "Output compact one-line summary", false).action(async (options) => {
4184
4757
  const exitCode = await featuresCommand({
4185
4758
  template: options.template,
4186
4759
  config: options.config,
4187
4760
  refresh: options.refresh,
4188
- json: options.json
4761
+ json: options.json,
4762
+ summary: options.summary,
4763
+ overlay: options.overlay || []
4189
4764
  });
4190
4765
  process.exit(exitCode);
4191
4766
  });
4192
- template.command("test").description("Run template test fixtures to verify expected output").option("-t, --template <source>", "Template source (local path or Git repository)").option("-c, --config <path>", "Path to configuration file").option("--update-snapshots", "Update stored snapshots with current rendered output", false).action(async (options) => {
4767
+ template.command("test").description("Run template test fixtures to verify expected output").option("-t, --template <source>", "Template source (local path or Git repository)").option("-c, --config <path>", "Path to configuration file").option("--update-snapshots", "Update stored snapshots with current rendered output", false).option("--refresh", "Force refresh of cached Git templates", false).option("--overlay <path...>", "Overlay directory paths applied over base template (repeatable)").option("--json", "Output results as JSON", false).option("--summary", "Output compact one-line summary", false).action(async (options) => {
4193
4768
  const testOptions = {
4194
4769
  template: options.template,
4195
4770
  config: options.config,
4196
- updateSnapshots: options.updateSnapshots
4771
+ updateSnapshots: options.updateSnapshots,
4772
+ refresh: options.refresh,
4773
+ json: options.json,
4774
+ summary: options.summary,
4775
+ overlay: options.overlay || []
4197
4776
  };
4198
4777
  const exitCode = await testCommand(testOptions);
4199
4778
  process.exit(exitCode);
4200
4779
  });
4201
4780
  program.addCommand(template);
4202
- program.command("trace").description("Explore traceability chains and assemble context from specs, code, and tests").argument("[ids...]", "Traceability ID(s) to trace").option("--all", "Trace all known IDs in the project", false).option("--task <path>", "Resolve IDs from a task file").option("--file <path>", "Resolve IDs from a source file's markers").option("--content", "Output actual file sections instead of locations", false).option("--list", "Output file paths only (no content or tree)", false).option("--max-tokens <n>", "Cap content output size (implies --content)").option("--depth <n>", "Maximum traversal depth").option("--scope <code>", "Limit results to a feature code").option("--direction <dir>", "Traversal direction: both, forward, reverse", "both").option("--no-code", "Exclude source code (spec-only context)").option("--no-tests", "Exclude test files").option("--json", "Output as JSON", false).option("-A <n>", "Lines of context after a code marker (--content only; default: 20)").option("-B <n>", "Lines of context before a code marker (--content only; default: 5)").option("-C <n>", "Lines of context before and after (--content only; overrides -A and -B)").option("-c, --config <path>", "Path to configuration file").action(async (ids, options) => {
4781
+ program.command("trace").description("Explore traceability chains and assemble context from specs, code, and tests").argument("[ids...]", "Traceability ID(s) to trace").option("--all", "Trace all known IDs in the project", false).option("--task <path>", "Resolve IDs from a task file").option("--file <path>", "Resolve IDs from a source file's markers").option("--content", "Output actual file sections instead of locations", false).option("--list", "Output file paths only (no content or tree)", false).option("--max-tokens <n>", "Cap content output size (implies --content)").option("--depth <n>", "Maximum traversal depth").option("--scope <code>", "Limit results to a feature code").option("--direction <dir>", "Traversal direction: both, forward, reverse", "both").option("--no-code", "Exclude source code (spec-only context)").option("--no-tests", "Exclude test files").option("--json", "Output results as JSON", false).option("--summary", "Output compact one-line summary", false).option("-A <n>", "Lines of context after a code marker (--content only; default: 20)").option("-B <n>", "Lines of context before a code marker (--content only; default: 5)").option("-C <n>", "Lines of context before and after (--content only; overrides -A and -B)").option("-c, --config <path>", "Path to configuration file").action(async (ids, options) => {
4203
4782
  const traceOptions = {
4204
4783
  ids,
4205
4784
  all: options.all,
@@ -4208,6 +4787,7 @@ program.command("trace").description("Explore traceability chains and assemble c
4208
4787
  content: options.content,
4209
4788
  list: options.list,
4210
4789
  json: options.json,
4790
+ summary: options.summary,
4211
4791
  maxTokens: options.maxTokens !== void 0 ? Number(options.maxTokens) : void 0,
4212
4792
  depth: options.depth !== void 0 ? Number(options.depth) : void 0,
4213
4793
  scope: options.scope,
@@ -4228,7 +4808,7 @@ var isDisabledByEnv = !!process.env.NO_UPDATE_NOTIFIER;
4228
4808
  if (!isJsonOrSummary && isTTY && !isDisabledByEnv) {
4229
4809
  updateCheckPromise = (async () => {
4230
4810
  try {
4231
- const { configLoader: configLoader2 } = await import("./config-LWUO5EXL.js");
4811
+ const { configLoader: configLoader2 } = await import("./config-WL3SLSP6.js");
4232
4812
  const configPath = process.argv.indexOf("-c") !== -1 ? process.argv[process.argv.indexOf("-c") + 1] : process.argv.indexOf("--config") !== -1 ? process.argv[process.argv.indexOf("--config") + 1] : void 0;
4233
4813
  const fileConfig = await configLoader2.load(configPath ?? null);
4234
4814
  const updateCheckConfig = fileConfig?.["update-check"];