@ncoderz/awa 1.6.0 → 1.7.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/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.1",
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,321 @@ 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, 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 sourcePropertyIds = /* @__PURE__ */ new Set();
503
+ for (const designName of sourceDesigns) {
504
+ const designCode = extractCodeFromFileName(designName);
505
+ for (const sf of specs.specFiles) {
506
+ if (sf.code === designCode && /\bDESIGN-/.test(basename(sf.filePath))) {
507
+ for (const propId of sf.propertyIds) sourcePropertyIds.add(propId);
508
+ }
509
+ }
510
+ }
511
+ const allIdsForMatrix = [.../* @__PURE__ */ new Set([...acToTasks.keys(), ...idToTestTasks.keys()])];
512
+ const grouped = groupByReqFile(allIdsForMatrix, codeToReqFile);
513
+ const newSection = generateTaskSection(grouped, acToTasks, idToTestTasks, sourcePropertyIds);
514
+ const newContent = replaceTraceabilitySection(content, newSection);
515
+ if (newContent === content) return false;
516
+ await writeFile(filePath, newContent, "utf-8");
517
+ return true;
518
+ }
519
+ function parseTaskFileData(content, crossRefPatterns) {
520
+ const lines = content.split("\n");
521
+ const tasks = [];
522
+ const sourceReqs = [];
523
+ const sourceDesigns = [];
524
+ const sourceRegex = /^SOURCE:\s*(.+)/;
525
+ const taskIdRegex = /^-\s+\[[ x]\]\s+(T-[A-Z][A-Z0-9]*-\d+)/;
526
+ let currentTaskId = null;
527
+ for (const line of lines) {
528
+ const sourceMatch = sourceRegex.exec(line);
529
+ if (sourceMatch?.[1]) {
530
+ const parts = sourceMatch[1].split(",").map((s) => s.trim());
531
+ for (const part of parts) {
532
+ if (part.startsWith("REQ-")) sourceReqs.push(part);
533
+ else if (part.startsWith("DESIGN-")) sourceDesigns.push(part);
534
+ }
535
+ continue;
536
+ }
537
+ const taskMatch = taskIdRegex.exec(line);
538
+ if (taskMatch?.[1]) {
539
+ currentTaskId = taskMatch[1];
540
+ tasks.push({ id: currentTaskId, implements: [], tests: [] });
541
+ continue;
542
+ }
543
+ if (/^##\s/.test(line)) {
544
+ currentTaskId = null;
545
+ continue;
546
+ }
547
+ if (/^---/.test(line)) {
548
+ currentTaskId = null;
549
+ continue;
550
+ }
551
+ if (currentTaskId) {
552
+ const task = tasks.find((t) => t.id === currentTaskId);
553
+ if (task) {
554
+ for (const pattern of crossRefPatterns) {
555
+ const patIdx = line.indexOf(pattern);
556
+ if (patIdx !== -1) {
557
+ const afterPattern = line.slice(patIdx + pattern.length);
558
+ const ids = extractIdsFromText(afterPattern);
559
+ if (ids.length > 0) {
560
+ const isImplements = pattern.toLowerCase().includes("implements");
561
+ if (isImplements) {
562
+ task.implements.push(...ids);
563
+ }
564
+ }
565
+ }
566
+ }
567
+ const testsIdx = line.indexOf("TESTS:");
568
+ if (testsIdx !== -1) {
569
+ const afterTests = line.slice(testsIdx + "TESTS:".length);
570
+ const ids = extractIdsFromText(afterTests);
571
+ if (ids.length > 0) {
572
+ task.tests.push(...ids);
573
+ }
574
+ }
575
+ }
576
+ }
577
+ }
578
+ return { tasks, sourceReqs, sourceDesigns };
579
+ }
580
+ function generateTaskSection(grouped, acToTasks, idToTestTasks, propertyIds) {
581
+ const lines = [];
582
+ const reqFiles = [...grouped.keys()].sort();
583
+ for (const reqFile of reqFiles) {
584
+ lines.push(`### ${reqFile}`);
585
+ lines.push("");
586
+ const ids = grouped.get(reqFile) ?? [];
587
+ const acIds = ids.filter((id) => !propertyIds.has(id));
588
+ const propIds = ids.filter((id) => propertyIds.has(id));
589
+ acIds.sort(compareIds);
590
+ propIds.sort(compareIds);
591
+ for (const acId of acIds) {
592
+ const tasks = acToTasks.get(acId) ?? [];
593
+ const testTasks = idToTestTasks.get(acId) ?? [];
594
+ const taskStr = tasks.length > 0 ? tasks.join(", ") : "(none)";
595
+ const testStr = testTasks.length > 0 ? ` (${testTasks.join(", ")})` : "";
596
+ lines.push(`- ${acId} \u2192 ${taskStr}${testStr}`);
597
+ }
598
+ for (const propId of propIds) {
599
+ const testTasks = idToTestTasks.get(propId) ?? [];
600
+ const testStr = testTasks.length > 0 ? testTasks.join(", ") : "(none)";
601
+ lines.push(`- ${propId} \u2192 ${testStr}`);
602
+ }
603
+ lines.push("");
604
+ }
605
+ return lines.join("\n").trimEnd();
606
+ }
607
+ function groupByReqFile(ids, codeToReqFile) {
608
+ const groups = /* @__PURE__ */ new Map();
609
+ for (const id of ids) {
610
+ const code = getCodePrefix(id);
611
+ const reqFile = codeToReqFile.get(code);
612
+ if (!reqFile) continue;
613
+ const existing = groups.get(reqFile) ?? [];
614
+ existing.push(id);
615
+ groups.set(reqFile, existing);
616
+ }
617
+ return groups;
618
+ }
619
+ function replaceTraceabilitySection(content, newSection) {
620
+ const lines = content.split("\n");
621
+ const sectionStart = lines.findIndex((l) => /^##\s+Requirements Traceability\s*$/.test(l));
622
+ if (sectionStart === -1) return content;
623
+ let sectionEnd = lines.length;
624
+ for (let i = sectionStart + 1; i < lines.length; i++) {
625
+ const line = lines[i];
626
+ if (line !== void 0 && /^##\s/.test(line)) {
627
+ sectionEnd = i;
628
+ break;
629
+ }
630
+ }
631
+ const before = lines.slice(0, sectionStart + 1);
632
+ const after = lines.slice(sectionEnd);
633
+ const result = [...before, "", newSection.trimEnd(), "", ...after];
634
+ return result.join("\n");
635
+ }
636
+ function extractIdsFromText(text) {
637
+ const idRegex = /[A-Z][A-Z0-9]*-\d+(?:\.\d+)?(?:_AC-\d+)?|[A-Z][A-Z0-9]*_P-\d+/g;
638
+ const ids = [];
639
+ let match = idRegex.exec(text);
640
+ while (match !== null) {
641
+ ids.push(match[0]);
642
+ match = idRegex.exec(text);
643
+ }
644
+ return ids;
645
+ }
646
+ function extractCodeFromFileName(fileName) {
647
+ const match = /^(?:REQ|DESIGN|FEAT|EXAMPLES|API|TASK)-([A-Z][A-Z0-9]*)-/.exec(fileName);
648
+ return match?.[1] ?? "";
649
+ }
650
+ function compareIds(a, b) {
651
+ return a.localeCompare(b, void 0, { numeric: true });
652
+ }
653
+
262
654
  // src/core/check/reporter.ts
263
655
  import chalk from "chalk";
264
656
  function report(findings, format) {
@@ -334,7 +726,7 @@ function printRuleContext(f) {
334
726
  }
335
727
 
336
728
  // src/core/check/rule-loader.ts
337
- import { readFile as readFile2 } from "fs/promises";
729
+ import { readFile as readFile3 } from "fs/promises";
338
730
  import { join } from "path";
339
731
  import { parse as parseYaml } from "yaml";
340
732
  async function loadRules(schemaDir) {
@@ -355,7 +747,7 @@ function matchesTargetGlob(filePath, targetGlob) {
355
747
  async function loadRuleFile(filePath) {
356
748
  let content;
357
749
  try {
358
- content = await readFile2(filePath, "utf-8");
750
+ content = await readFile3(filePath, "utf-8");
359
751
  } catch {
360
752
  return null;
361
753
  }
@@ -547,7 +939,7 @@ var RuleValidationError = class extends Error {
547
939
  };
548
940
 
549
941
  // src/core/check/schema-checker.ts
550
- import { readFile as readFile3 } from "fs/promises";
942
+ import { readFile as readFile4 } from "fs/promises";
551
943
  import remarkGfm from "remark-gfm";
552
944
  import remarkParse from "remark-parse";
553
945
  import { unified } from "unified";
@@ -594,7 +986,7 @@ async function checkSchemasAsync(specFiles, ruleSets) {
594
986
  if (matchingRules.length === 0) continue;
595
987
  let content;
596
988
  try {
597
- content = await readFile3(spec.filePath, "utf-8");
989
+ content = await readFile4(spec.filePath, "utf-8");
598
990
  } catch {
599
991
  continue;
600
992
  }
@@ -1039,8 +1431,8 @@ function collectAllCodeBlocks(section) {
1039
1431
  }
1040
1432
 
1041
1433
  // src/core/check/spec-parser.ts
1042
- import { readFile as readFile4 } from "fs/promises";
1043
- import { basename } from "path";
1434
+ import { readFile as readFile5 } from "fs/promises";
1435
+ import { basename as basename2 } from "path";
1044
1436
  async function parseSpecs(config) {
1045
1437
  const files = await collectSpecFiles(config.specGlobs, config.specIgnore);
1046
1438
  const specFiles = [];
@@ -1068,7 +1460,7 @@ async function parseSpecs(config) {
1068
1460
  async function parseSpecFile(filePath, crossRefPatterns) {
1069
1461
  let content;
1070
1462
  try {
1071
- content = await readFile4(filePath, "utf-8");
1463
+ content = await readFile5(filePath, "utf-8");
1072
1464
  } catch {
1073
1465
  return null;
1074
1466
  }
@@ -1080,10 +1472,12 @@ async function parseSpecFile(filePath, crossRefPatterns) {
1080
1472
  const componentNames = [];
1081
1473
  const crossRefs = [];
1082
1474
  const idLocations = /* @__PURE__ */ new Map();
1475
+ const componentImplements = /* @__PURE__ */ new Map();
1083
1476
  const reqIdRegex = /^###\s+([A-Z][A-Z0-9]*-\d+(?:\.\d+)?)\s*:/;
1084
1477
  const acIdRegex = /^-\s+(?:\[[ x]\]\s+)?([A-Z][A-Z0-9]*-\d+(?:\.\d+)?_AC-\d+)\s/;
1085
1478
  const propIdRegex = /^-\s+([A-Z][A-Z0-9]*_P-\d+)\s/;
1086
1479
  const componentRegex = /^###\s+([A-Z][A-Z0-9]*-[A-Za-z][A-Za-z0-9]*(?:[A-Z][a-z0-9]*)*)\s*$/;
1480
+ let currentComponent = null;
1087
1481
  for (const [i, line] of lines.entries()) {
1088
1482
  const lineNum = i + 1;
1089
1483
  const reqMatch = reqIdRegex.exec(line);
@@ -1106,16 +1500,25 @@ async function parseSpecFile(filePath, crossRefPatterns) {
1106
1500
  if (!reqIdRegex.test(line)) {
1107
1501
  componentNames.push(compMatch[1]);
1108
1502
  idLocations.set(compMatch[1], { filePath, line: lineNum });
1503
+ currentComponent = compMatch[1];
1109
1504
  }
1110
1505
  }
1506
+ if (/^#{1,2}\s/.test(line) && !compMatch) {
1507
+ currentComponent = null;
1508
+ }
1111
1509
  for (const pattern of crossRefPatterns) {
1112
1510
  const patIdx = line.indexOf(pattern);
1113
1511
  if (patIdx !== -1) {
1114
1512
  const afterPattern = line.slice(patIdx + pattern.length);
1115
- const ids = extractIdsFromText(afterPattern);
1513
+ const ids = extractIdsFromText2(afterPattern);
1116
1514
  if (ids.length > 0) {
1117
1515
  const type = pattern.toLowerCase().includes("implements") ? "implements" : "validates";
1118
1516
  crossRefs.push({ type, ids, filePath, line: i + 1 });
1517
+ if (type === "implements" && currentComponent) {
1518
+ const existing = componentImplements.get(currentComponent) ?? [];
1519
+ existing.push(...ids);
1520
+ componentImplements.set(currentComponent, existing);
1521
+ }
1119
1522
  }
1120
1523
  }
1121
1524
  }
@@ -1128,16 +1531,17 @@ async function parseSpecFile(filePath, crossRefPatterns) {
1128
1531
  propertyIds,
1129
1532
  componentNames,
1130
1533
  crossRefs,
1131
- idLocations
1534
+ idLocations,
1535
+ componentImplements
1132
1536
  };
1133
1537
  }
1134
1538
  function extractCodePrefix(filePath) {
1135
- const name = basename(filePath, ".md");
1539
+ const name = basename2(filePath, ".md");
1136
1540
  const match = /^(?:REQ|DESIGN|FEAT|EXAMPLES|API)-([A-Z][A-Z0-9]*)-/.exec(name);
1137
1541
  if (match?.[1]) return match[1];
1138
1542
  return "";
1139
1543
  }
1140
- function extractIdsFromText(text) {
1544
+ function extractIdsFromText2(text) {
1141
1545
  const idRegex = /[A-Z][A-Z0-9]*-\d+(?:\.\d+)?(?:_AC-\d+)?|[A-Z][A-Z0-9]*_P-\d+/g;
1142
1546
  const ids = [];
1143
1547
  let match = idRegex.exec(text);
@@ -1170,6 +1574,37 @@ function checkSpecAgainstSpec(specs, markers, config) {
1170
1574
  }
1171
1575
  }
1172
1576
  }
1577
+ const implementedAcIds = /* @__PURE__ */ new Set();
1578
+ for (const specFile of specs.specFiles) {
1579
+ for (const crossRef of specFile.crossRefs) {
1580
+ if (crossRef.type === "implements") {
1581
+ for (const id of crossRef.ids) {
1582
+ implementedAcIds.add(id);
1583
+ }
1584
+ }
1585
+ }
1586
+ }
1587
+ const reqAcIds = /* @__PURE__ */ new Set();
1588
+ for (const specFile of specs.specFiles) {
1589
+ if (/\bREQ-/.test(specFile.filePath)) {
1590
+ for (const acId of specFile.acIds) {
1591
+ reqAcIds.add(acId);
1592
+ }
1593
+ }
1594
+ }
1595
+ for (const acId of reqAcIds) {
1596
+ if (!implementedAcIds.has(acId)) {
1597
+ const loc = specs.idLocations.get(acId);
1598
+ findings.push({
1599
+ severity: "error",
1600
+ code: "unlinked-ac",
1601
+ message: `Acceptance criterion '${acId}' is not claimed by any DESIGN IMPLEMENTS`,
1602
+ filePath: loc?.filePath,
1603
+ line: loc?.line,
1604
+ id: acId
1605
+ });
1606
+ }
1607
+ }
1173
1608
  if (!config.specOnly) {
1174
1609
  const referencedCodes = /* @__PURE__ */ new Set();
1175
1610
  for (const marker of markers.markers) {
@@ -1238,7 +1673,8 @@ var DEFAULT_CHECK_CONFIG = {
1238
1673
  schemaDir: ".awa/.agent/schemas",
1239
1674
  schemaEnabled: true,
1240
1675
  allowWarnings: false,
1241
- specOnly: false
1676
+ specOnly: false,
1677
+ fix: true
1242
1678
  };
1243
1679
 
1244
1680
  // src/commands/check.ts
@@ -1267,7 +1703,20 @@ async function checkCommand(cliOptions) {
1267
1703
  const allFindings = config.allowWarnings ? combinedFindings : combinedFindings.map(
1268
1704
  (f) => f.severity === "warning" ? { ...f, severity: "error" } : f
1269
1705
  );
1270
- report(allFindings, config.format);
1706
+ const isSummary = cliOptions.summary === true;
1707
+ if (isSummary) {
1708
+ const errors = allFindings.filter((f) => f.severity === "error").length;
1709
+ const warnings = allFindings.filter((f) => f.severity === "warning").length;
1710
+ console.log(`errors: ${errors}, warnings: ${warnings}`);
1711
+ } else {
1712
+ report(allFindings, config.format);
1713
+ }
1714
+ if (config.fix) {
1715
+ const fixResult = await fixMatrices(specs, config.crossRefPatterns);
1716
+ if (fixResult.filesFixed > 0) {
1717
+ logger.info(`Fixed traceability matrices in ${fixResult.filesFixed} file(s)`);
1718
+ }
1719
+ }
1271
1720
  const hasErrors = allFindings.some((f) => f.severity === "error");
1272
1721
  return hasErrors ? 1 : 0;
1273
1722
  } catch (error) {
@@ -1299,11 +1748,12 @@ function buildCheckConfig(fileConfig, cliOptions) {
1299
1748
  const ignoreMarkers = toStringArray(section?.["ignore-markers"]) ?? [
1300
1749
  ...DEFAULT_CHECK_CONFIG.ignoreMarkers
1301
1750
  ];
1302
- const format = cliOptions.format === "json" ? "json" : section?.format === "json" ? "json" : DEFAULT_CHECK_CONFIG.format;
1751
+ const format = cliOptions.json === true ? "json" : cliOptions.format === "json" ? "json" : section?.format === "json" ? "json" : DEFAULT_CHECK_CONFIG.format;
1303
1752
  const schemaDir = typeof section?.["schema-dir"] === "string" ? section["schema-dir"] : DEFAULT_CHECK_CONFIG.schemaDir;
1304
1753
  const schemaEnabled = typeof section?.["schema-enabled"] === "boolean" ? section["schema-enabled"] : DEFAULT_CHECK_CONFIG.schemaEnabled;
1305
1754
  const allowWarnings = cliOptions.allowWarnings === true ? true : typeof section?.["allow-warnings"] === "boolean" ? section["allow-warnings"] : DEFAULT_CHECK_CONFIG.allowWarnings;
1306
1755
  const specOnly = cliOptions.specOnly === true ? true : typeof section?.["spec-only"] === "boolean" ? section["spec-only"] : DEFAULT_CHECK_CONFIG.specOnly;
1756
+ const fix = cliOptions.fix === false ? false : DEFAULT_CHECK_CONFIG.fix;
1307
1757
  return {
1308
1758
  specGlobs,
1309
1759
  codeGlobs,
@@ -1317,7 +1767,8 @@ function buildCheckConfig(fileConfig, cliOptions) {
1317
1767
  schemaDir,
1318
1768
  schemaEnabled,
1319
1769
  allowWarnings,
1320
- specOnly
1770
+ specOnly,
1771
+ fix
1321
1772
  };
1322
1773
  }
1323
1774
  function toStringArray(value) {
@@ -2341,8 +2792,8 @@ async function prepareDiff(options) {
2341
2792
  async function diffCommand(cliOptions) {
2342
2793
  try {
2343
2794
  const fileConfig = await configLoader.load(cliOptions.config ?? null);
2344
- if (cliOptions.all || cliOptions.target) {
2345
- const mode = cliOptions.all ? "all" : "single";
2795
+ if (cliOptions.allTargets || cliOptions.target) {
2796
+ const mode = cliOptions.allTargets ? "all" : "single";
2346
2797
  const targets = batchRunner.resolveTargets(cliOptions, fileConfig, mode, cliOptions.target);
2347
2798
  let hasDifferences = false;
2348
2799
  for (const { targetName, options: options2 } of targets) {
@@ -2473,7 +2924,7 @@ var FeaturesReporter = class {
2473
2924
  var featuresReporter = new FeaturesReporter();
2474
2925
 
2475
2926
  // src/core/features/scanner.ts
2476
- import { readdir, readFile as readFile5 } from "fs/promises";
2927
+ import { readdir, readFile as readFile6 } from "fs/promises";
2477
2928
  import { join as join7, relative as relative3 } from "path";
2478
2929
  var FEATURE_PATTERN = /it\.features\.(?:includes|indexOf)\(\s*['"]([^'"]+)['"]\s*\)/g;
2479
2930
  async function* walkAllFiles(dir) {
@@ -2507,7 +2958,7 @@ var FeatureScanner = class {
2507
2958
  for await (const filePath of walkAllFiles(templatePath)) {
2508
2959
  filesScanned++;
2509
2960
  try {
2510
- const content = await readFile5(filePath, "utf-8");
2961
+ const content = await readFile6(filePath, "utf-8");
2511
2962
  const flags = this.extractFlags(content);
2512
2963
  const relPath = relative3(templatePath, filePath);
2513
2964
  for (const flag of flags) {
@@ -2532,22 +2983,37 @@ var featureScanner = new FeatureScanner();
2532
2983
 
2533
2984
  // src/commands/features.ts
2534
2985
  async function featuresCommand(cliOptions) {
2986
+ let mergedDir = null;
2535
2987
  try {
2536
- if (!cliOptions.json) {
2988
+ const silent = cliOptions.json || cliOptions.summary;
2989
+ if (!silent) {
2537
2990
  intro2("awa CLI - Feature Discovery");
2538
2991
  }
2539
2992
  const fileConfig = await configLoader.load(cliOptions.config ?? null);
2540
2993
  const templateSource = cliOptions.template ?? fileConfig?.template ?? null;
2541
2994
  const refresh = cliOptions.refresh ?? fileConfig?.refresh ?? false;
2542
2995
  const template2 = await templateResolver.resolve(templateSource, refresh);
2543
- const scanResult = await featureScanner.scan(template2.localPath);
2996
+ const overlays = cliOptions.overlay ?? fileConfig?.overlay ?? [];
2997
+ let templatePath = template2.localPath;
2998
+ if (overlays.length > 0) {
2999
+ const overlayDirs = await resolveOverlays(overlays, refresh);
3000
+ mergedDir = await buildMergedDir(template2.localPath, overlayDirs);
3001
+ templatePath = mergedDir;
3002
+ }
3003
+ const scanResult = await featureScanner.scan(templatePath);
2544
3004
  const presets = fileConfig?.presets;
2545
- featuresReporter.report({
2546
- scanResult,
2547
- json: cliOptions.json ?? false,
2548
- presets
2549
- });
2550
- if (!cliOptions.json) {
3005
+ if (cliOptions.summary) {
3006
+ console.log(
3007
+ `features: ${scanResult.features.length}, files-scanned: ${scanResult.filesScanned}`
3008
+ );
3009
+ } else {
3010
+ featuresReporter.report({
3011
+ scanResult,
3012
+ json: cliOptions.json ?? false,
3013
+ presets
3014
+ });
3015
+ }
3016
+ if (!silent) {
2551
3017
  outro2("Feature discovery complete!");
2552
3018
  }
2553
3019
  return 0;
@@ -2558,6 +3024,10 @@ async function featuresCommand(cliOptions) {
2558
3024
  logger.error(String(error));
2559
3025
  }
2560
3026
  return 1;
3027
+ } finally {
3028
+ if (mergedDir) {
3029
+ await rmDir(mergedDir);
3030
+ }
2561
3031
  }
2562
3032
  }
2563
3033
 
@@ -2649,8 +3119,8 @@ async function generateCommand(cliOptions) {
2649
3119
  if (!cliOptions.config && fileConfig === null) {
2650
3120
  logger.info("Tip: create .awa.toml to save your options for next time.");
2651
3121
  }
2652
- if (cliOptions.all || cliOptions.target) {
2653
- const mode = cliOptions.all ? "all" : "single";
3122
+ if (cliOptions.allTargets || cliOptions.target) {
3123
+ const mode = cliOptions.allTargets ? "all" : "single";
2654
3124
  const targets = batchRunner.resolveTargets(cliOptions, fileConfig, mode, cliOptions.target);
2655
3125
  for (const { targetName, options: options2 } of targets) {
2656
3126
  batchRunner.logForTarget(targetName, "Starting generation...");
@@ -2684,7 +3154,7 @@ import { intro as intro4, outro as outro4 } from "@clack/prompts";
2684
3154
 
2685
3155
  // src/core/template-test/fixture-loader.ts
2686
3156
  import { readdir as readdir2 } from "fs/promises";
2687
- import { basename as basename2, extname, join as join8 } from "path";
3157
+ import { basename as basename3, extname, join as join8 } from "path";
2688
3158
  import { parse } from "smol-toml";
2689
3159
  async function discoverFixtures(templatePath) {
2690
3160
  const testsDir = join8(templatePath, "_tests");
@@ -2706,7 +3176,7 @@ async function discoverFixtures(templatePath) {
2706
3176
  async function parseFixture(filePath) {
2707
3177
  const content = await readTextFile(filePath);
2708
3178
  const parsed = parse(content);
2709
- const name = basename2(filePath, extname(filePath));
3179
+ const name = basename3(filePath, extname(filePath));
2710
3180
  const features = toStringArray2(parsed.features) ?? [];
2711
3181
  const preset = toStringArray2(parsed.preset) ?? [];
2712
3182
  const removeFeatures = toStringArray2(parsed["remove-features"]) ?? [];
@@ -2729,7 +3199,11 @@ function toStringArray2(value) {
2729
3199
 
2730
3200
  // src/core/template-test/reporter.ts
2731
3201
  import chalk4 from "chalk";
2732
- function report2(result) {
3202
+ function report2(result, options) {
3203
+ if (options?.json) {
3204
+ reportJson2(result);
3205
+ return;
3206
+ }
2733
3207
  console.log("");
2734
3208
  for (const fixture of result.results) {
2735
3209
  reportFixture(fixture);
@@ -2743,6 +3217,27 @@ function report2(result) {
2743
3217
  }
2744
3218
  console.log("");
2745
3219
  }
3220
+ function reportJson2(result) {
3221
+ const output = {
3222
+ total: result.total,
3223
+ passed: result.passed,
3224
+ failed: result.failed,
3225
+ results: result.results.map((r) => ({
3226
+ name: r.name,
3227
+ passed: r.passed,
3228
+ ...r.error ? { error: r.error } : {},
3229
+ fileResults: r.fileResults.map((f) => ({
3230
+ path: f.path,
3231
+ found: f.found
3232
+ })),
3233
+ snapshotResults: r.snapshotResults.map((s) => ({
3234
+ path: s.path,
3235
+ status: s.status
3236
+ }))
3237
+ }))
3238
+ };
3239
+ console.log(JSON.stringify(output, null, 2));
3240
+ }
2746
3241
  function reportFixture(fixture) {
2747
3242
  const icon = fixture.passed ? chalk4.green("\u2714") : chalk4.red("\u2716");
2748
3243
  console.log(`${icon} ${fixture.name}`);
@@ -2905,31 +3400,61 @@ async function runAll(fixtures, templatePath, options, presetDefinitions = {}) {
2905
3400
 
2906
3401
  // src/commands/test.ts
2907
3402
  async function testCommand(options) {
3403
+ let mergedDir = null;
2908
3404
  try {
2909
- intro4("awa CLI - Template Test");
3405
+ const isJson = options.json === true;
3406
+ const isSummary = options.summary === true;
3407
+ const silent = isJson || isSummary;
3408
+ if (!silent) {
3409
+ intro4("awa CLI - Template Test");
3410
+ }
2910
3411
  const fileConfig = await configLoader.load(options.config ?? null);
2911
3412
  const templateSource = options.template ?? fileConfig?.template ?? null;
2912
- const template2 = await templateResolver.resolve(templateSource, false);
2913
- const fixtures = await discoverFixtures(template2.localPath);
3413
+ const refresh = options.refresh ?? false;
3414
+ const template2 = await templateResolver.resolve(templateSource, refresh);
3415
+ const overlays = options.overlay ?? fileConfig?.overlay ?? [];
3416
+ let templatePath = template2.localPath;
3417
+ if (overlays.length > 0) {
3418
+ const overlayDirs = await resolveOverlays(overlays, refresh);
3419
+ mergedDir = await buildMergedDir(template2.localPath, overlayDirs);
3420
+ templatePath = mergedDir;
3421
+ }
3422
+ const fixtures = await discoverFixtures(templatePath);
2914
3423
  if (fixtures.length === 0) {
2915
- logger.warn("No test fixtures found in _tests/ directory");
2916
- outro4("No tests to run.");
3424
+ if (isSummary) {
3425
+ console.log("passed: 0, failed: 0, total: 0");
3426
+ } else if (isJson) {
3427
+ console.log(JSON.stringify({ total: 0, passed: 0, failed: 0, results: [] }, null, 2));
3428
+ } else {
3429
+ logger.warn("No test fixtures found in _tests/ directory");
3430
+ outro4("No tests to run.");
3431
+ }
2917
3432
  return 0;
2918
3433
  }
2919
- logger.info(`Found ${fixtures.length} fixture(s)`);
3434
+ if (!silent) {
3435
+ logger.info(`Found ${fixtures.length} fixture(s)`);
3436
+ }
2920
3437
  const presetDefinitions = fileConfig?.presets ?? {};
2921
3438
  const result = await runAll(
2922
3439
  fixtures,
2923
- template2.localPath,
3440
+ templatePath,
2924
3441
  { updateSnapshots: options.updateSnapshots },
2925
3442
  presetDefinitions
2926
3443
  );
2927
- report2(result);
3444
+ if (isSummary) {
3445
+ console.log(`passed: ${result.passed}, failed: ${result.failed}, total: ${result.total}`);
3446
+ } else {
3447
+ report2(result, { json: isJson });
3448
+ }
2928
3449
  if (result.failed > 0) {
2929
- outro4(`${result.failed} fixture(s) failed.`);
3450
+ if (!silent) {
3451
+ outro4(`${result.failed} fixture(s) failed.`);
3452
+ }
2930
3453
  return 1;
2931
3454
  }
2932
- outro4("All tests passed!");
3455
+ if (!silent) {
3456
+ outro4("All tests passed!");
3457
+ }
2933
3458
  return 0;
2934
3459
  } catch (error) {
2935
3460
  if (error instanceof Error) {
@@ -2938,11 +3463,15 @@ async function testCommand(options) {
2938
3463
  logger.error(String(error));
2939
3464
  }
2940
3465
  return 2;
3466
+ } finally {
3467
+ if (mergedDir) {
3468
+ await rmDir(mergedDir);
3469
+ }
2941
3470
  }
2942
3471
  }
2943
3472
 
2944
3473
  // src/core/trace/content-assembler.ts
2945
- import { readFile as readFile6 } from "fs/promises";
3474
+ import { readFile as readFile7 } from "fs/promises";
2946
3475
  var DEFAULT_BEFORE_CONTEXT = 5;
2947
3476
  var DEFAULT_AFTER_CONTEXT = 20;
2948
3477
  async function assembleContent(result, taskPath, contextOptions) {
@@ -3141,7 +3670,7 @@ function findEnclosingBlock(lines, lineIdx, beforeContext = DEFAULT_BEFORE_CONTE
3141
3670
  }
3142
3671
  async function safeReadFile(filePath) {
3143
3672
  try {
3144
- return await readFile6(filePath, "utf-8");
3673
+ return await readFile7(filePath, "utf-8");
3145
3674
  } catch {
3146
3675
  return null;
3147
3676
  }
@@ -3493,7 +4022,7 @@ function pushToMap(map, key, value) {
3493
4022
  }
3494
4023
 
3495
4024
  // src/core/trace/input-resolver.ts
3496
- import { readFile as readFile7 } from "fs/promises";
4025
+ import { readFile as readFile8 } from "fs/promises";
3497
4026
  function resolveIds(ids, index) {
3498
4027
  const resolved = [];
3499
4028
  const warnings = [];
@@ -3509,7 +4038,7 @@ function resolveIds(ids, index) {
3509
4038
  async function resolveTaskFile(taskPath, index) {
3510
4039
  let content;
3511
4040
  try {
3512
- content = await readFile7(taskPath, "utf-8");
4041
+ content = await readFile8(taskPath, "utf-8");
3513
4042
  } catch {
3514
4043
  return { ids: [], warnings: [`Task file not found: ${taskPath}`] };
3515
4044
  }
@@ -3560,7 +4089,7 @@ async function resolveTaskFile(taskPath, index) {
3560
4089
  async function resolveSourceFile(filePath, index) {
3561
4090
  let content;
3562
4091
  try {
3563
- content = await readFile7(filePath, "utf-8");
4092
+ content = await readFile8(filePath, "utf-8");
3564
4093
  } catch {
3565
4094
  return { ids: [], warnings: [`Source file not found: ${filePath}`] };
3566
4095
  }
@@ -3861,6 +4390,7 @@ function buildScanConfig(fileConfig, overrides) {
3861
4390
  schemaEnabled: false,
3862
4391
  allowWarnings: true,
3863
4392
  specOnly: false,
4393
+ fix: true,
3864
4394
  ...overrides
3865
4395
  };
3866
4396
  }
@@ -3964,6 +4494,13 @@ async function traceCommand(options) {
3964
4494
  }
3965
4495
  return 1;
3966
4496
  }
4497
+ if (options.summary) {
4498
+ const chainCount = result.chains.length;
4499
+ const notFoundCount = result.notFound.length;
4500
+ process.stdout.write(`chains: ${chainCount}, not-found: ${notFoundCount}
4501
+ `);
4502
+ return result.notFound.length > 0 && result.chains.length === 0 ? 1 : 0;
4503
+ }
3967
4504
  let output;
3968
4505
  const isContentMode = options.content || options.maxTokens !== void 0;
3969
4506
  const queryLabel = ids.join(", ");
@@ -4063,7 +4600,7 @@ function printUpdateWarning(log, result) {
4063
4600
  }
4064
4601
 
4065
4602
  // src/utils/update-check-cache.ts
4066
- import { mkdir as mkdir3, readFile as readFile8, writeFile } from "fs/promises";
4603
+ import { mkdir as mkdir3, readFile as readFile9, writeFile as writeFile2 } from "fs/promises";
4067
4604
  import { homedir } from "os";
4068
4605
  import { dirname, join as join11 } from "path";
4069
4606
  var CACHE_DIR = join11(homedir(), ".cache", "awa");
@@ -4071,7 +4608,7 @@ var CACHE_FILE = join11(CACHE_DIR, "update-check.json");
4071
4608
  var DEFAULT_INTERVAL_MS = 864e5;
4072
4609
  async function shouldCheck(intervalMs = DEFAULT_INTERVAL_MS) {
4073
4610
  try {
4074
- const raw = await readFile8(CACHE_FILE, "utf-8");
4611
+ const raw = await readFile9(CACHE_FILE, "utf-8");
4075
4612
  const data = JSON.parse(raw);
4076
4613
  if (typeof data.timestamp !== "number" || typeof data.latestVersion !== "string") {
4077
4614
  return true;
@@ -4088,7 +4625,7 @@ async function writeCache(latestVersion) {
4088
4625
  timestamp: Date.now(),
4089
4626
  latestVersion
4090
4627
  };
4091
- await writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
4628
+ await writeFile2(CACHE_FILE, JSON.stringify(data), "utf-8");
4092
4629
  } catch {
4093
4630
  }
4094
4631
  }
@@ -4108,7 +4645,7 @@ function configureGenerateCommand(cmd) {
4108
4645
  "--delete",
4109
4646
  "Enable deletion of files listed in the delete list (default: warn only)",
4110
4647
  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(
4648
+ ).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
4649
  "--overlay <path...>",
4113
4650
  "Overlay directory paths applied over base template (repeatable)"
4114
4651
  ).option("--json", "Output results as JSON (implies --dry-run)", false).option("--summary", "Output compact one-line summary", false).action(async (output, options) => {
@@ -4123,7 +4660,8 @@ function configureGenerateCommand(cmd) {
4123
4660
  delete: options.delete,
4124
4661
  config: options.config,
4125
4662
  refresh: options.refresh,
4126
- all: options.all,
4663
+ all: options.allTargets,
4664
+ allTargets: options.allTargets,
4127
4665
  target: options.target,
4128
4666
  overlay: options.overlay || [],
4129
4667
  json: options.json,
@@ -4137,7 +4675,7 @@ configureGenerateCommand(program.command("init"));
4137
4675
  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
4676
  "--remove-features <flag...>",
4139
4677
  "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) => {
4678
+ ).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
4679
  const cliOptions = {
4142
4680
  output: target,
4143
4681
  // Use target as output for consistency
@@ -4148,7 +4686,8 @@ template.command("diff").description("Compare template output with existing targ
4148
4686
  config: options.config,
4149
4687
  refresh: options.refresh,
4150
4688
  listUnknown: options.listUnknown,
4151
- all: options.all,
4689
+ all: options.allTargets,
4690
+ allTargets: options.allTargets,
4152
4691
  target: options.target,
4153
4692
  watch: options.watch,
4154
4693
  overlay: options.overlay || [],
@@ -4160,7 +4699,9 @@ template.command("diff").description("Compare template output with existing targ
4160
4699
  });
4161
4700
  program.command("check").description(
4162
4701
  "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(
4702
+ ).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(
4703
+ new Option("--format <format>", "Output format (text or json)").default("text").hideHelp()
4704
+ ).option("--summary", "Output compact one-line summary", false).option(
4164
4705
  "--allow-warnings",
4165
4706
  "Allow warnings without failing (default: warnings are errors)",
4166
4707
  false
@@ -4168,38 +4709,50 @@ program.command("check").description(
4168
4709
  "--spec-only",
4169
4710
  "Run only spec-level checks (schema and cross-refs); skip code-to-spec traceability",
4170
4711
  false
4712
+ ).option(
4713
+ "--no-fix",
4714
+ "Skip regeneration of Requirements Traceability sections in DESIGN and TASK files"
4171
4715
  ).action(async (options) => {
4172
4716
  const cliOptions = {
4173
4717
  config: options.config,
4174
4718
  specIgnore: options.specIgnore,
4175
4719
  codeIgnore: options.codeIgnore,
4176
4720
  format: options.format,
4721
+ json: options.json,
4722
+ summary: options.summary,
4177
4723
  allowWarnings: options.allowWarnings,
4178
- specOnly: options.specOnly
4724
+ specOnly: options.specOnly,
4725
+ fix: options.fix
4179
4726
  };
4180
4727
  const exitCode = await checkCommand(cliOptions);
4181
4728
  process.exit(exitCode);
4182
4729
  });
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) => {
4730
+ 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
4731
  const exitCode = await featuresCommand({
4185
4732
  template: options.template,
4186
4733
  config: options.config,
4187
4734
  refresh: options.refresh,
4188
- json: options.json
4735
+ json: options.json,
4736
+ summary: options.summary,
4737
+ overlay: options.overlay || []
4189
4738
  });
4190
4739
  process.exit(exitCode);
4191
4740
  });
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) => {
4741
+ 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
4742
  const testOptions = {
4194
4743
  template: options.template,
4195
4744
  config: options.config,
4196
- updateSnapshots: options.updateSnapshots
4745
+ updateSnapshots: options.updateSnapshots,
4746
+ refresh: options.refresh,
4747
+ json: options.json,
4748
+ summary: options.summary,
4749
+ overlay: options.overlay || []
4197
4750
  };
4198
4751
  const exitCode = await testCommand(testOptions);
4199
4752
  process.exit(exitCode);
4200
4753
  });
4201
4754
  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) => {
4755
+ 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
4756
  const traceOptions = {
4204
4757
  ids,
4205
4758
  all: options.all,
@@ -4208,6 +4761,7 @@ program.command("trace").description("Explore traceability chains and assemble c
4208
4761
  content: options.content,
4209
4762
  list: options.list,
4210
4763
  json: options.json,
4764
+ summary: options.summary,
4211
4765
  maxTokens: options.maxTokens !== void 0 ? Number(options.maxTokens) : void 0,
4212
4766
  depth: options.depth !== void 0 ? Number(options.depth) : void 0,
4213
4767
  scope: options.scope,
@@ -4228,7 +4782,7 @@ var isDisabledByEnv = !!process.env.NO_UPDATE_NOTIFIER;
4228
4782
  if (!isJsonOrSummary && isTTY && !isDisabledByEnv) {
4229
4783
  updateCheckPromise = (async () => {
4230
4784
  try {
4231
- const { configLoader: configLoader2 } = await import("./config-LWUO5EXL.js");
4785
+ const { configLoader: configLoader2 } = await import("./config-WL3SLSP6.js");
4232
4786
  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
4787
  const fileConfig = await configLoader2.load(configPath ?? null);
4234
4788
  const updateCheckConfig = fileConfig?.["update-check"];