@ncoderz/awa 1.1.0 → 1.3.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
@@ -1,4 +1,22 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ ConfigError,
4
+ DiffError,
5
+ GenerationError,
6
+ TemplateError,
7
+ configLoader,
8
+ deleteFile,
9
+ ensureDir,
10
+ getCacheDir,
11
+ getTemplateDir,
12
+ logger,
13
+ pathExists,
14
+ readBinaryFile,
15
+ readTextFile,
16
+ rmDir,
17
+ walkDirectory,
18
+ writeTextFile
19
+ } from "./chunk-ALEGCDAZ.js";
2
20
 
3
21
  // src/cli/index.ts
4
22
  import { Command } from "commander";
@@ -6,7 +24,7 @@ import { Command } from "commander";
6
24
  // src/_generated/package_info.ts
7
25
  var PACKAGE_INFO = {
8
26
  "name": "@ncoderz/awa",
9
- "version": "1.1.0",
27
+ "version": "1.3.1",
10
28
  "author": "Richard Sewell <richard.sewell@ncoderz.com>",
11
29
  "license": "MIT",
12
30
  "description": "awa is an Agent Workflow for AIs. It is also a CLI tool to powerfully manage agent workflow files using templates."
@@ -71,6 +89,38 @@ function checkCodeAgainstSpec(markers, specs, config) {
71
89
  });
72
90
  }
73
91
  }
92
+ const implementedComponents = new Set(
93
+ markers.markers.filter((m) => m.type === "component").map((m) => m.id)
94
+ );
95
+ for (const componentName of specs.componentNames) {
96
+ if (!implementedComponents.has(componentName)) {
97
+ const loc = specs.idLocations.get(componentName);
98
+ const specFile = loc ? void 0 : specs.specFiles.find((sf) => sf.componentNames.includes(componentName));
99
+ findings.push({
100
+ severity: "warning",
101
+ code: "uncovered-component",
102
+ message: `Component '${componentName}' has no @awa-component reference`,
103
+ filePath: loc?.filePath ?? specFile?.filePath,
104
+ line: loc?.line,
105
+ id: componentName
106
+ });
107
+ }
108
+ }
109
+ const implementedIds = new Set(markers.markers.filter((m) => m.type === "impl").map((m) => m.id));
110
+ for (const acId of specs.acIds) {
111
+ if (!implementedIds.has(acId)) {
112
+ const loc = specs.idLocations.get(acId);
113
+ const specFile = loc ? void 0 : specs.specFiles.find((sf) => sf.acIds.includes(acId));
114
+ findings.push({
115
+ severity: "warning",
116
+ code: "unimplemented-ac",
117
+ message: `Acceptance criterion '${acId}' has no @awa-impl reference`,
118
+ filePath: loc?.filePath ?? specFile?.filePath,
119
+ line: loc?.line,
120
+ id: acId
121
+ });
122
+ }
123
+ }
74
124
  return { findings };
75
125
  }
76
126
 
@@ -104,13 +154,13 @@ var MARKER_TYPE_MAP = {
104
154
  "@awa-test": "test",
105
155
  "@awa-component": "component"
106
156
  };
107
- var IGNORE_FILE_RE = /@awa-ignore-file\b/;
157
+ var IGNORE_FILE_RE = new RegExp("@awa-ignore-file\\b");
108
158
  var IGNORE_NEXT_LINE_RE = /@awa-ignore-next-line\b/;
109
159
  var IGNORE_LINE_RE = /@awa-ignore\b/;
110
160
  var IGNORE_START_RE = /@awa-ignore-start\b/;
111
161
  var IGNORE_END_RE = /@awa-ignore-end\b/;
112
162
  async function scanMarkers(config) {
113
- const files = await collectCodeFiles(config.codeGlobs, config.ignore);
163
+ const files = await collectCodeFiles(config.codeGlobs, config.codeIgnore);
114
164
  const markers = [];
115
165
  const findings = [];
116
166
  for (const filePath of files) {
@@ -992,7 +1042,7 @@ function collectAllCodeBlocks(section) {
992
1042
  import { readFile as readFile4 } from "fs/promises";
993
1043
  import { basename } from "path";
994
1044
  async function parseSpecs(config) {
995
- const files = await collectSpecFiles(config.specGlobs, config.ignore);
1045
+ const files = await collectSpecFiles(config.specGlobs, config.specIgnore);
996
1046
  const specFiles = [];
997
1047
  const requirementIds = /* @__PURE__ */ new Set();
998
1048
  const acIds = /* @__PURE__ */ new Set();
@@ -1031,7 +1081,7 @@ async function parseSpecFile(filePath, crossRefPatterns) {
1031
1081
  const crossRefs = [];
1032
1082
  const idLocations = /* @__PURE__ */ new Map();
1033
1083
  const reqIdRegex = /^###\s+([A-Z][A-Z0-9]*-\d+(?:\.\d+)?)\s*:/;
1034
- const acIdRegex = /^-\s+\[[ x]\]\s+([A-Z][A-Z0-9]*-\d+(?:\.\d+)?_AC-\d+)\s/;
1084
+ const acIdRegex = /^-\s+(?:\[[ x]\]\s+)?([A-Z][A-Z0-9]*-\d+(?:\.\d+)?_AC-\d+)\s/;
1035
1085
  const propIdRegex = /^-\s+([A-Z][A-Z0-9]*_P-\d+)\s/;
1036
1086
  const componentRegex = /^###\s+([A-Z][A-Z0-9]*-[A-Za-z][A-Za-z0-9]*(?:[A-Z][a-z0-9]*)*)\s*$/;
1037
1087
  for (const [i, line] of lines.entries()) {
@@ -1156,9 +1206,30 @@ function checkSpecAgainstSpec(specs, markers, config) {
1156
1206
 
1157
1207
  // src/core/check/types.ts
1158
1208
  var DEFAULT_CHECK_CONFIG = {
1159
- specGlobs: [".awa/specs/**/*.md"],
1160
- codeGlobs: ["src/**/*.{ts,js,tsx,jsx,py,go,rs,java,cs}"],
1161
- ignore: ["node_modules/**", "dist/**"],
1209
+ specGlobs: [
1210
+ ".awa/specs/ARCHITECTURE.md",
1211
+ ".awa/specs/FEAT-*.md",
1212
+ ".awa/specs/REQ-*.md",
1213
+ ".awa/specs/DESIGN-*.md",
1214
+ ".awa/specs/EXAMPLES-*.md",
1215
+ ".awa/specs/API-*.tsp",
1216
+ ".awa/tasks/TASK-*.md",
1217
+ ".awa/plans/PLAN-*.md",
1218
+ ".awa/align/ALIGN-*.md"
1219
+ ],
1220
+ codeGlobs: [
1221
+ "**/*.{ts,js,tsx,jsx,mts,mjs,cjs,py,go,rs,java,kt,kts,cs,c,h,cpp,cc,cxx,hpp,hxx,swift,rb,php,scala,ex,exs,dart,lua,zig}"
1222
+ ],
1223
+ specIgnore: [],
1224
+ codeIgnore: [
1225
+ "node_modules/**",
1226
+ "dist/**",
1227
+ "vendor/**",
1228
+ "target/**",
1229
+ "build/**",
1230
+ "out/**",
1231
+ ".awa/**"
1232
+ ],
1162
1233
  ignoreMarkers: [],
1163
1234
  markers: ["@awa-impl", "@awa-test", "@awa-component"],
1164
1235
  idPattern: "([A-Z][A-Z0-9]*-\\d+(?:\\.\\d+)?(?:_AC-\\d+)?|[A-Z][A-Z0-9]*_P-\\d+)",
@@ -1170,576 +1241,6 @@ var DEFAULT_CHECK_CONFIG = {
1170
1241
  specOnly: false
1171
1242
  };
1172
1243
 
1173
- // src/core/config.ts
1174
- import { parse } from "smol-toml";
1175
-
1176
- // src/types/index.ts
1177
- var DiffError = class extends Error {
1178
- constructor(message) {
1179
- super(message);
1180
- this.name = "DiffError";
1181
- }
1182
- };
1183
- var ConfigError = class extends Error {
1184
- constructor(message, code, filePath) {
1185
- super(message);
1186
- this.code = code;
1187
- this.filePath = filePath;
1188
- this.name = "ConfigError";
1189
- }
1190
- };
1191
- var TemplateError = class extends Error {
1192
- constructor(message, code, source) {
1193
- super(message);
1194
- this.code = code;
1195
- this.source = source;
1196
- this.name = "TemplateError";
1197
- }
1198
- };
1199
- var GenerationError = class extends Error {
1200
- constructor(message, code) {
1201
- super(message);
1202
- this.code = code;
1203
- this.name = "GenerationError";
1204
- }
1205
- };
1206
-
1207
- // src/utils/fs.ts
1208
- import { mkdir, readdir, readFile as readFile5, rm, stat, writeFile } from "fs/promises";
1209
- import { homedir } from "os";
1210
- import { dirname, join as join2 } from "path";
1211
- import { fileURLToPath } from "url";
1212
- async function ensureDir(dirPath) {
1213
- await mkdir(dirPath, { recursive: true });
1214
- }
1215
- async function pathExists(path) {
1216
- try {
1217
- await stat(path);
1218
- return true;
1219
- } catch {
1220
- return false;
1221
- }
1222
- }
1223
- async function readTextFile(path) {
1224
- return readFile5(path, "utf-8");
1225
- }
1226
- async function readBinaryFile(path) {
1227
- return readFile5(path);
1228
- }
1229
- async function writeTextFile(path, content) {
1230
- await ensureDir(dirname(path));
1231
- await writeFile(path, content, "utf-8");
1232
- }
1233
- async function* walkDirectory(dir) {
1234
- const entries = await readdir(dir, { withFileTypes: true });
1235
- for (const entry of entries) {
1236
- const fullPath = join2(dir, entry.name);
1237
- if (entry.isDirectory()) {
1238
- if (entry.name.startsWith("_")) {
1239
- continue;
1240
- }
1241
- yield* walkDirectory(fullPath);
1242
- } else if (entry.isFile()) {
1243
- if (entry.name.startsWith("_")) {
1244
- continue;
1245
- }
1246
- yield fullPath;
1247
- }
1248
- }
1249
- }
1250
- function getCacheDir() {
1251
- return join2(homedir(), ".cache", "awa", "templates");
1252
- }
1253
- function getTemplateDir() {
1254
- const currentFile = fileURLToPath(import.meta.url);
1255
- const currentDir = dirname(currentFile);
1256
- if (currentDir.includes("/dist")) {
1257
- return join2(dirname(currentDir), "templates");
1258
- }
1259
- return join2(currentDir, "..", "..", "templates");
1260
- }
1261
- async function rmDir(dirPath) {
1262
- await rm(dirPath, { recursive: true, force: true });
1263
- }
1264
- async function deleteFile(filePath) {
1265
- await rm(filePath, { force: true });
1266
- }
1267
-
1268
- // src/utils/logger.ts
1269
- import chalk2 from "chalk";
1270
- var Logger = class {
1271
- info(message) {
1272
- console.log(chalk2.blue("\u2139"), message);
1273
- }
1274
- success(message) {
1275
- console.log(chalk2.green("\u2714"), message);
1276
- }
1277
- warn(message) {
1278
- console.warn(chalk2.yellow("\u26A0"), message);
1279
- }
1280
- error(message) {
1281
- console.error(chalk2.red("\u2716"), message);
1282
- }
1283
- fileAction(action) {
1284
- const { type, outputPath } = action;
1285
- switch (type) {
1286
- case "create":
1287
- console.log(chalk2.green(" + "), chalk2.dim(outputPath));
1288
- break;
1289
- case "overwrite":
1290
- console.log(chalk2.yellow(" ~ "), chalk2.dim(outputPath));
1291
- break;
1292
- case "skip-user":
1293
- console.log(chalk2.blue(" - "), chalk2.dim(outputPath), chalk2.dim("(skipped)"));
1294
- break;
1295
- case "skip-empty":
1296
- console.log(chalk2.dim(" \xB7 "), chalk2.dim(outputPath), chalk2.dim("(empty)"));
1297
- break;
1298
- case "skip-equal":
1299
- console.log(chalk2.dim(" = "), chalk2.dim(outputPath), chalk2.dim("(unchanged)"));
1300
- break;
1301
- case "delete":
1302
- console.log(chalk2.red(" \u2716 "), chalk2.dim(outputPath), chalk2.red("(deleted)"));
1303
- break;
1304
- }
1305
- }
1306
- // @awa-impl: GEN-9_AC-1, GEN-9_AC-2, GEN-9_AC-3, GEN-9_AC-4, GEN-9_AC-5, GEN-9_AC-6
1307
- summary(result) {
1308
- console.log("");
1309
- console.log(chalk2.bold("Summary:"));
1310
- if (result.created === 0 && result.overwritten === 0 && result.deleted === 0) {
1311
- console.log(chalk2.yellow(" \u26A0 No files were created, overwritten, or deleted"));
1312
- }
1313
- if (result.created > 0) {
1314
- console.log(chalk2.green(` Created: ${result.created}`));
1315
- }
1316
- if (result.overwritten > 0) {
1317
- console.log(chalk2.yellow(` Overwritten: ${result.overwritten}`));
1318
- }
1319
- if (result.deleted > 0) {
1320
- console.log(chalk2.red(` Deleted: ${result.deleted}`));
1321
- }
1322
- if (result.skippedEqual > 0) {
1323
- console.log(chalk2.dim(` Skipped (equal): ${result.skippedEqual}`));
1324
- }
1325
- if (result.skippedUser > 0) {
1326
- console.log(chalk2.blue(` Skipped (user): ${result.skippedUser}`));
1327
- }
1328
- if (result.skippedEmpty > 0) {
1329
- console.log(chalk2.dim(` Skipped (empty): ${result.skippedEmpty}`));
1330
- }
1331
- console.log("");
1332
- }
1333
- // @awa-impl: DIFF-4_AC-3
1334
- diffLine(line, type) {
1335
- switch (type) {
1336
- case "add":
1337
- console.log(chalk2.green(line));
1338
- break;
1339
- case "remove":
1340
- console.log(chalk2.red(line));
1341
- break;
1342
- case "context":
1343
- console.log(chalk2.dim(line));
1344
- break;
1345
- }
1346
- }
1347
- // @awa-impl: DIFF-4_AC-4, DIFF-4_AC-5
1348
- diffSummary(result) {
1349
- console.log("");
1350
- const filesCompared = result.identical + result.modified + result.newFiles + result.extraFiles + result.binaryDiffers + result.deleteListed;
1351
- const differences = result.modified + result.newFiles + result.extraFiles + result.binaryDiffers + result.deleteListed;
1352
- console.log(chalk2.bold(`${filesCompared} files compared, ${differences} differences`));
1353
- if (!result.hasDifferences) {
1354
- console.log(chalk2.green("\u2714 No differences found"));
1355
- }
1356
- console.log(chalk2.bold("Summary:"));
1357
- console.log(chalk2.dim(` Identical: ${result.identical}`));
1358
- if (result.modified > 0) {
1359
- console.log(chalk2.yellow(` Modified: ${result.modified}`));
1360
- }
1361
- if (result.newFiles > 0) {
1362
- console.log(chalk2.green(` New: ${result.newFiles}`));
1363
- }
1364
- if (result.extraFiles > 0) {
1365
- console.log(chalk2.red(` Extra: ${result.extraFiles}`));
1366
- }
1367
- if (result.binaryDiffers > 0) {
1368
- console.log(chalk2.red(` Binary differs: ${result.binaryDiffers}`));
1369
- }
1370
- if (result.deleteListed > 0) {
1371
- console.log(chalk2.red(` Delete listed: ${result.deleteListed}`));
1372
- }
1373
- console.log("");
1374
- }
1375
- };
1376
- var logger = new Logger();
1377
-
1378
- // src/core/config.ts
1379
- var DEFAULT_CONFIG_PATH = ".awa.toml";
1380
- var ConfigLoader = class {
1381
- // @awa-impl: CFG-1_AC-1, CFG-1_AC-2, CFG-1_AC-3, CFG-1_AC-4
1382
- async load(configPath) {
1383
- const pathToLoad = configPath ?? DEFAULT_CONFIG_PATH;
1384
- const exists = await pathExists(pathToLoad);
1385
- if (configPath && !exists) {
1386
- throw new ConfigError(
1387
- `Configuration file not found: ${configPath}`,
1388
- "FILE_NOT_FOUND",
1389
- configPath
1390
- );
1391
- }
1392
- if (!configPath && !exists) {
1393
- return null;
1394
- }
1395
- try {
1396
- const content = await readTextFile(pathToLoad);
1397
- const parsed = parse(content);
1398
- const config = {};
1399
- if (parsed.output !== void 0) {
1400
- if (typeof parsed.output !== "string") {
1401
- throw new ConfigError(
1402
- `Invalid type for 'output': expected string, got ${typeof parsed.output}`,
1403
- "INVALID_TYPE",
1404
- pathToLoad
1405
- );
1406
- }
1407
- config.output = parsed.output;
1408
- }
1409
- if (parsed.template !== void 0) {
1410
- if (typeof parsed.template !== "string") {
1411
- throw new ConfigError(
1412
- `Invalid type for 'template': expected string, got ${typeof parsed.template}`,
1413
- "INVALID_TYPE",
1414
- pathToLoad
1415
- );
1416
- }
1417
- config.template = parsed.template;
1418
- }
1419
- if (parsed.features !== void 0) {
1420
- if (!Array.isArray(parsed.features) || !parsed.features.every((f) => typeof f === "string")) {
1421
- throw new ConfigError(
1422
- `Invalid type for 'features': expected array of strings`,
1423
- "INVALID_TYPE",
1424
- pathToLoad
1425
- );
1426
- }
1427
- config.features = parsed.features;
1428
- }
1429
- if (parsed.preset !== void 0) {
1430
- if (!Array.isArray(parsed.preset) || !parsed.preset.every((p) => typeof p === "string")) {
1431
- throw new ConfigError(
1432
- `Invalid type for 'preset': expected array of strings`,
1433
- "INVALID_TYPE",
1434
- pathToLoad
1435
- );
1436
- }
1437
- config.preset = parsed.preset;
1438
- }
1439
- if (parsed["remove-features"] !== void 0) {
1440
- if (!Array.isArray(parsed["remove-features"]) || !parsed["remove-features"].every((f) => typeof f === "string")) {
1441
- throw new ConfigError(
1442
- `Invalid type for 'remove-features': expected array of strings`,
1443
- "INVALID_TYPE",
1444
- pathToLoad
1445
- );
1446
- }
1447
- config["remove-features"] = parsed["remove-features"];
1448
- }
1449
- if (parsed.force !== void 0) {
1450
- if (typeof parsed.force !== "boolean") {
1451
- throw new ConfigError(
1452
- `Invalid type for 'force': expected boolean, got ${typeof parsed.force}`,
1453
- "INVALID_TYPE",
1454
- pathToLoad
1455
- );
1456
- }
1457
- config.force = parsed.force;
1458
- }
1459
- if (parsed["dry-run"] !== void 0) {
1460
- if (typeof parsed["dry-run"] !== "boolean") {
1461
- throw new ConfigError(
1462
- `Invalid type for 'dry-run': expected boolean, got ${typeof parsed["dry-run"]}`,
1463
- "INVALID_TYPE",
1464
- pathToLoad
1465
- );
1466
- }
1467
- config["dry-run"] = parsed["dry-run"];
1468
- }
1469
- if (parsed.delete !== void 0) {
1470
- if (typeof parsed.delete !== "boolean") {
1471
- throw new ConfigError(
1472
- `Invalid type for 'delete': expected boolean, got ${typeof parsed.delete}`,
1473
- "INVALID_TYPE",
1474
- pathToLoad
1475
- );
1476
- }
1477
- config.delete = parsed.delete;
1478
- }
1479
- if (parsed.refresh !== void 0) {
1480
- if (typeof parsed.refresh !== "boolean") {
1481
- throw new ConfigError(
1482
- `Invalid type for 'refresh': expected boolean, got ${typeof parsed.refresh}`,
1483
- "INVALID_TYPE",
1484
- pathToLoad
1485
- );
1486
- }
1487
- config.refresh = parsed.refresh;
1488
- }
1489
- if (parsed.presets !== void 0) {
1490
- if (parsed.presets === null || typeof parsed.presets !== "object" || Array.isArray(parsed.presets)) {
1491
- throw new ConfigError(
1492
- `Invalid type for 'presets': expected table of string arrays`,
1493
- "INVALID_PRESET",
1494
- pathToLoad
1495
- );
1496
- }
1497
- const defs = {};
1498
- for (const [presetName, value] of Object.entries(
1499
- parsed.presets
1500
- )) {
1501
- if (!Array.isArray(value) || !value.every((v) => typeof v === "string")) {
1502
- throw new ConfigError(
1503
- `Invalid preset '${presetName}': expected array of strings`,
1504
- "INVALID_PRESET",
1505
- pathToLoad
1506
- );
1507
- }
1508
- defs[presetName] = value;
1509
- }
1510
- config.presets = defs;
1511
- }
1512
- if (parsed["list-unknown"] !== void 0) {
1513
- if (typeof parsed["list-unknown"] !== "boolean") {
1514
- throw new ConfigError(
1515
- `Invalid type for 'list-unknown': expected boolean, got ${typeof parsed["list-unknown"]}`,
1516
- "INVALID_TYPE",
1517
- pathToLoad
1518
- );
1519
- }
1520
- config["list-unknown"] = parsed["list-unknown"];
1521
- }
1522
- if (parsed.check !== void 0) {
1523
- if (parsed.check === null || typeof parsed.check !== "object" || Array.isArray(parsed.check)) {
1524
- throw new ConfigError(
1525
- `Invalid type for 'check': expected table`,
1526
- "INVALID_TYPE",
1527
- pathToLoad
1528
- );
1529
- }
1530
- config.check = parsed.check;
1531
- }
1532
- if (parsed.overlay !== void 0) {
1533
- if (!Array.isArray(parsed.overlay) || !parsed.overlay.every((o) => typeof o === "string")) {
1534
- throw new ConfigError(
1535
- `Invalid type for 'overlay': expected array of strings`,
1536
- "INVALID_TYPE",
1537
- pathToLoad
1538
- );
1539
- }
1540
- config.overlay = parsed.overlay;
1541
- }
1542
- if (parsed.targets !== void 0) {
1543
- if (parsed.targets === null || typeof parsed.targets !== "object" || Array.isArray(parsed.targets)) {
1544
- throw new ConfigError(
1545
- `Invalid type for 'targets': expected table of target sections`,
1546
- "INVALID_TYPE",
1547
- pathToLoad
1548
- );
1549
- }
1550
- const targets = {};
1551
- for (const [targetName, targetValue] of Object.entries(
1552
- parsed.targets
1553
- )) {
1554
- if (targetValue === null || typeof targetValue !== "object" || Array.isArray(targetValue)) {
1555
- throw new ConfigError(
1556
- `Invalid target '${targetName}': expected table`,
1557
- "INVALID_TYPE",
1558
- pathToLoad
1559
- );
1560
- }
1561
- targets[targetName] = this.parseTargetSection(
1562
- targetValue,
1563
- targetName,
1564
- pathToLoad
1565
- );
1566
- }
1567
- config.targets = targets;
1568
- }
1569
- const knownKeys = /* @__PURE__ */ new Set([
1570
- "output",
1571
- "template",
1572
- "features",
1573
- "preset",
1574
- "remove-features",
1575
- "presets",
1576
- "force",
1577
- "dry-run",
1578
- "delete",
1579
- "refresh",
1580
- "list-unknown",
1581
- "check",
1582
- "targets",
1583
- "overlay"
1584
- ]);
1585
- for (const key of Object.keys(parsed)) {
1586
- if (!knownKeys.has(key)) {
1587
- logger.warn(`Unknown configuration option: '${key}'`);
1588
- }
1589
- }
1590
- return config;
1591
- } catch (error) {
1592
- if (error instanceof ConfigError) {
1593
- throw error;
1594
- }
1595
- throw new ConfigError(
1596
- `Failed to parse TOML configuration: ${error instanceof Error ? error.message : String(error)}`,
1597
- "PARSE_ERROR",
1598
- pathToLoad
1599
- );
1600
- }
1601
- }
1602
- // @awa-impl: CFG-4_AC-1, CFG-4_AC-2, CFG-4_AC-3, CFG-4_AC-4
1603
- // @awa-impl: CLI-2_AC-2, CLI-2_AC-3, CLI-2_AC-4
1604
- merge(cli, file) {
1605
- const output = cli.output ?? file?.output;
1606
- if (!output) {
1607
- throw new ConfigError(
1608
- "Output directory is required. Provide it as a positional argument or in the config file.",
1609
- "MISSING_OUTPUT",
1610
- null
1611
- );
1612
- }
1613
- const template = cli.template ?? file?.template ?? null;
1614
- const features = cli.features ?? file?.features ?? [];
1615
- const preset = cli.preset ?? file?.preset ?? [];
1616
- const removeFeatures = cli.removeFeatures ?? file?.["remove-features"] ?? [];
1617
- const presets = file?.presets ?? {};
1618
- const force = cli.force ?? file?.force ?? false;
1619
- const dryRun = cli.dryRun ?? file?.["dry-run"] ?? false;
1620
- const enableDelete = cli.delete ?? file?.delete ?? false;
1621
- const refresh = cli.refresh ?? file?.refresh ?? false;
1622
- const listUnknown = cli.listUnknown ?? file?.["list-unknown"] ?? false;
1623
- const overlay = cli.overlay ?? file?.overlay ?? [];
1624
- const json = cli.json ?? false;
1625
- const summary = cli.summary ?? false;
1626
- return {
1627
- output,
1628
- template,
1629
- features,
1630
- preset,
1631
- removeFeatures,
1632
- force,
1633
- dryRun,
1634
- delete: enableDelete,
1635
- refresh,
1636
- presets,
1637
- listUnknown,
1638
- overlay,
1639
- json,
1640
- summary
1641
- };
1642
- }
1643
- // Parse a [targets.<name>] section, validating allowed keys and types
1644
- parseTargetSection(section, targetName, configPath) {
1645
- const target = {};
1646
- const allowedKeys = /* @__PURE__ */ new Set(["output", "template", "features", "preset", "remove-features"]);
1647
- for (const key of Object.keys(section)) {
1648
- if (!allowedKeys.has(key)) {
1649
- logger.warn(`Unknown option in target '${targetName}': '${key}'`);
1650
- }
1651
- }
1652
- if (section.output !== void 0) {
1653
- if (typeof section.output !== "string") {
1654
- throw new ConfigError(
1655
- `Invalid type for 'targets.${targetName}.output': expected string, got ${typeof section.output}`,
1656
- "INVALID_TYPE",
1657
- configPath
1658
- );
1659
- }
1660
- target.output = section.output;
1661
- }
1662
- if (section.template !== void 0) {
1663
- if (typeof section.template !== "string") {
1664
- throw new ConfigError(
1665
- `Invalid type for 'targets.${targetName}.template': expected string, got ${typeof section.template}`,
1666
- "INVALID_TYPE",
1667
- configPath
1668
- );
1669
- }
1670
- target.template = section.template;
1671
- }
1672
- if (section.features !== void 0) {
1673
- if (!Array.isArray(section.features) || !section.features.every((f) => typeof f === "string")) {
1674
- throw new ConfigError(
1675
- `Invalid type for 'targets.${targetName}.features': expected array of strings`,
1676
- "INVALID_TYPE",
1677
- configPath
1678
- );
1679
- }
1680
- target.features = section.features;
1681
- }
1682
- if (section.preset !== void 0) {
1683
- if (!Array.isArray(section.preset) || !section.preset.every((p) => typeof p === "string")) {
1684
- throw new ConfigError(
1685
- `Invalid type for 'targets.${targetName}.preset': expected array of strings`,
1686
- "INVALID_TYPE",
1687
- configPath
1688
- );
1689
- }
1690
- target.preset = section.preset;
1691
- }
1692
- if (section["remove-features"] !== void 0) {
1693
- if (!Array.isArray(section["remove-features"]) || !section["remove-features"].every((f) => typeof f === "string")) {
1694
- throw new ConfigError(
1695
- `Invalid type for 'targets.${targetName}.remove-features': expected array of strings`,
1696
- "INVALID_TYPE",
1697
- configPath
1698
- );
1699
- }
1700
- target["remove-features"] = section["remove-features"];
1701
- }
1702
- return target;
1703
- }
1704
- // Resolve a target by merging target config with root config (target overrides root via nullish coalescing)
1705
- resolveTarget(targetName, fileConfig) {
1706
- const targets = fileConfig.targets;
1707
- if (!targets || Object.keys(targets).length === 0) {
1708
- throw new ConfigError(
1709
- "No targets defined in configuration. Add [targets.<name>] sections to .awa.toml.",
1710
- "NO_TARGETS",
1711
- null
1712
- );
1713
- }
1714
- const target = targets[targetName];
1715
- if (!target) {
1716
- throw new ConfigError(
1717
- `Unknown target: '${targetName}'. Available targets: ${Object.keys(targets).join(", ")}`,
1718
- "UNKNOWN_TARGET",
1719
- null
1720
- );
1721
- }
1722
- return {
1723
- ...fileConfig,
1724
- output: target.output ?? fileConfig.output,
1725
- template: target.template ?? fileConfig.template,
1726
- features: target.features ?? fileConfig.features,
1727
- preset: target.preset ?? fileConfig.preset,
1728
- "remove-features": target["remove-features"] ?? fileConfig["remove-features"],
1729
- targets: void 0
1730
- // Don't propagate targets into resolved config
1731
- };
1732
- }
1733
- // Get all target names from config
1734
- getTargetNames(fileConfig) {
1735
- if (!fileConfig?.targets) {
1736
- return [];
1737
- }
1738
- return Object.keys(fileConfig.targets);
1739
- }
1740
- };
1741
- var configLoader = new ConfigLoader();
1742
-
1743
1244
  // src/commands/check.ts
1744
1245
  async function checkCommand(cliOptions) {
1745
1246
  try {
@@ -1787,9 +1288,14 @@ function buildCheckConfig(fileConfig, cliOptions) {
1787
1288
  ...DEFAULT_CHECK_CONFIG.crossRefPatterns
1788
1289
  ];
1789
1290
  const idPattern = typeof section?.["id-pattern"] === "string" ? section["id-pattern"] : DEFAULT_CHECK_CONFIG.idPattern;
1790
- const configIgnore = toStringArray(section?.ignore) ?? [...DEFAULT_CHECK_CONFIG.ignore];
1791
- const cliIgnore = cliOptions.ignore ?? [];
1792
- const ignore = [...configIgnore, ...cliIgnore];
1291
+ const configSpecIgnore = toStringArray(section?.["spec-ignore"]) ?? [
1292
+ ...DEFAULT_CHECK_CONFIG.specIgnore
1293
+ ];
1294
+ const configCodeIgnore = toStringArray(section?.["code-ignore"]) ?? [
1295
+ ...DEFAULT_CHECK_CONFIG.codeIgnore
1296
+ ];
1297
+ const specIgnore = [...configSpecIgnore, ...cliOptions.specIgnore ?? []];
1298
+ const codeIgnore = [...configCodeIgnore, ...cliOptions.codeIgnore ?? []];
1793
1299
  const ignoreMarkers = toStringArray(section?.["ignore-markers"]) ?? [
1794
1300
  ...DEFAULT_CHECK_CONFIG.ignoreMarkers
1795
1301
  ];
@@ -1801,7 +1307,8 @@ function buildCheckConfig(fileConfig, cliOptions) {
1801
1307
  return {
1802
1308
  specGlobs,
1803
1309
  codeGlobs,
1804
- ignore,
1310
+ specIgnore,
1311
+ codeIgnore,
1805
1312
  ignoreMarkers,
1806
1313
  markers,
1807
1314
  idPattern,
@@ -1882,12 +1389,12 @@ var batchRunner = new BatchRunner();
1882
1389
 
1883
1390
  // src/core/differ.ts
1884
1391
  import { tmpdir } from "os";
1885
- import { join as join5, relative as relative2 } from "path";
1392
+ import { join as join4, relative as relative2 } from "path";
1886
1393
  import { structuredPatch } from "diff";
1887
1394
  import { isBinaryFile as detectBinaryFile } from "isbinaryfile";
1888
1395
 
1889
1396
  // src/core/delete-list.ts
1890
- import { join as join3 } from "path";
1397
+ import { join as join2 } from "path";
1891
1398
  var DELETE_LIST_FILENAME = "_delete.txt";
1892
1399
  function parseDeleteList(content) {
1893
1400
  const entries = [];
@@ -1914,7 +1421,7 @@ function resolveDeleteList(entries, activeFeatures) {
1914
1421
  return entries.filter((e) => e.features === void 0 || !e.features.some((f) => activeSet.has(f))).map((e) => e.path);
1915
1422
  }
1916
1423
  async function loadDeleteList(templatePath) {
1917
- const deleteListPath = join3(templatePath, DELETE_LIST_FILENAME);
1424
+ const deleteListPath = join2(templatePath, DELETE_LIST_FILENAME);
1918
1425
  if (!await pathExists(deleteListPath)) {
1919
1426
  return [];
1920
1427
  }
@@ -1923,12 +1430,12 @@ async function loadDeleteList(templatePath) {
1923
1430
  }
1924
1431
 
1925
1432
  // src/core/generator.ts
1926
- import { join as join4, relative } from "path";
1433
+ import { join as join3, relative } from "path";
1927
1434
 
1928
1435
  // src/core/resolver.ts
1929
1436
  import { MultiSelectPrompt } from "@clack/core";
1930
1437
  import { isCancel, multiselect } from "@clack/prompts";
1931
- import chalk3 from "chalk";
1438
+ import chalk2 from "chalk";
1932
1439
  var _unicode = process.platform !== "win32";
1933
1440
  var _s = (c, fb) => _unicode ? c : fb;
1934
1441
  var _CHECKED = _s("\u25FC", "[+]");
@@ -1938,20 +1445,20 @@ var _BAR = _s("\u2502", "|");
1938
1445
  var _BAR_END = _s("\u2514", "-");
1939
1446
  function _renderDeleteItem(opt, state) {
1940
1447
  const label = opt.label ?? opt.value;
1941
- const hint = opt.hint ? ` ${chalk3.dim(`(${opt.hint})`)}` : "";
1448
+ const hint = opt.hint ? ` ${chalk2.dim(`(${opt.hint})`)}` : "";
1942
1449
  switch (state) {
1943
1450
  case "active":
1944
- return `${chalk3.cyan(_UNCHECKED_A)} ${label}${hint}`;
1451
+ return `${chalk2.cyan(_UNCHECKED_A)} ${label}${hint}`;
1945
1452
  case "selected":
1946
- return `${chalk3.red(_CHECKED)} ${chalk3.dim(label)}${hint}`;
1453
+ return `${chalk2.red(_CHECKED)} ${chalk2.dim(label)}${hint}`;
1947
1454
  case "active-selected":
1948
- return `${chalk3.red(_CHECKED)} ${label}${hint}`;
1455
+ return `${chalk2.red(_CHECKED)} ${label}${hint}`;
1949
1456
  case "cancelled":
1950
- return chalk3.strikethrough(chalk3.dim(label));
1457
+ return chalk2.strikethrough(chalk2.dim(label));
1951
1458
  case "submitted":
1952
- return chalk3.dim(label);
1459
+ return chalk2.dim(label);
1953
1460
  default:
1954
- return `${chalk3.dim(_UNCHECKED)} ${chalk3.dim(label)}`;
1461
+ return `${chalk2.dim(_UNCHECKED)} ${chalk2.dim(label)}`;
1955
1462
  }
1956
1463
  }
1957
1464
  async function deleteMultiselect(opts) {
@@ -1962,8 +1469,8 @@ async function deleteMultiselect(opts) {
1962
1469
  required,
1963
1470
  render() {
1964
1471
  const self = this;
1965
- const header = `${chalk3.gray(_BAR)}
1966
- ${chalk3.cyan(_BAR)} ${message}
1472
+ const header = `${chalk2.gray(_BAR)}
1473
+ ${chalk2.cyan(_BAR)} ${message}
1967
1474
  `;
1968
1475
  const getState = (opt, idx) => {
1969
1476
  const active = idx === self.cursor;
@@ -1975,16 +1482,16 @@ ${chalk3.cyan(_BAR)} ${message}
1975
1482
  };
1976
1483
  switch (self.state) {
1977
1484
  case "submit":
1978
- return `${header}${chalk3.gray(_BAR)} ` + (self.options.filter((o) => self.value.includes(o.value)).map((o) => _renderDeleteItem(o, "submitted")).join(chalk3.dim(", ")) || chalk3.dim("none"));
1485
+ return `${header}${chalk2.gray(_BAR)} ` + (self.options.filter((o) => self.value.includes(o.value)).map((o) => _renderDeleteItem(o, "submitted")).join(chalk2.dim(", ")) || chalk2.dim("none"));
1979
1486
  case "cancel": {
1980
- const cancelled = self.options.filter((o) => self.value.includes(o.value)).map((o) => _renderDeleteItem(o, "cancelled")).join(chalk3.dim(", "));
1981
- return `${header}${chalk3.gray(_BAR)} ${cancelled.trim() ? `${cancelled}
1982
- ${chalk3.gray(_BAR)}` : chalk3.dim("none")}`;
1487
+ const cancelled = self.options.filter((o) => self.value.includes(o.value)).map((o) => _renderDeleteItem(o, "cancelled")).join(chalk2.dim(", "));
1488
+ return `${header}${chalk2.gray(_BAR)} ${cancelled.trim() ? `${cancelled}
1489
+ ${chalk2.gray(_BAR)}` : chalk2.dim("none")}`;
1983
1490
  }
1984
1491
  default:
1985
- return `${header}${chalk3.cyan(_BAR)} ` + self.options.map((o, i) => _renderDeleteItem(o, getState(o, i))).join(`
1986
- ${chalk3.cyan(_BAR)} `) + `
1987
- ${chalk3.cyan(_BAR_END)}
1492
+ return `${header}${chalk2.cyan(_BAR)} ` + self.options.map((o, i) => _renderDeleteItem(o, getState(o, i))).join(`
1493
+ ${chalk2.cyan(_BAR)} `) + `
1494
+ ${chalk2.cyan(_BAR_END)}
1988
1495
  `;
1989
1496
  }
1990
1497
  }
@@ -2254,7 +1761,7 @@ var FileGenerator = class {
2254
1761
  const generatedOutputPaths = new Set(filesToProcess.map((f) => f.outputFile));
2255
1762
  const deleteCandidates = [];
2256
1763
  for (const relPath of deleteList) {
2257
- const absPath = join4(outputPath, relPath);
1764
+ const absPath = join3(outputPath, relPath);
2258
1765
  if (generatedOutputPaths.has(absPath)) {
2259
1766
  logger.warn(
2260
1767
  `Delete list entry '${relPath}' conflicts with generated file \u2014 skipping deletion`
@@ -2316,7 +1823,7 @@ var FileGenerator = class {
2316
1823
  // @awa-impl: GEN-1_AC-1, GEN-1_AC-2, GEN-1_AC-3
2317
1824
  computeOutputPath(templatePath, templateRoot, outputRoot) {
2318
1825
  const relativePath = relative(templateRoot, templatePath);
2319
- return join4(outputRoot, relativePath);
1826
+ return join3(outputRoot, relativePath);
2320
1827
  }
2321
1828
  };
2322
1829
  var fileGenerator = new FileGenerator();
@@ -2358,8 +1865,8 @@ var DiffEngine = class {
2358
1865
  }
2359
1866
  const files = [];
2360
1867
  for (const relPath of generatedFiles) {
2361
- const generatedFilePath = join5(tempPath, relPath);
2362
- const targetFilePath = join5(targetPath, relPath);
1868
+ const generatedFilePath = join4(tempPath, relPath);
1869
+ const targetFilePath = join4(targetPath, relPath);
2363
1870
  if (targetFiles.has(relPath)) {
2364
1871
  const fileDiff = await this.compareFiles(generatedFilePath, targetFilePath, relPath);
2365
1872
  files.push(fileDiff);
@@ -2423,7 +1930,7 @@ var DiffEngine = class {
2423
1930
  const systemTemp = tmpdir();
2424
1931
  const timestamp = Date.now();
2425
1932
  const random = Math.random().toString(36).substring(2, 8);
2426
- const tempPath = join5(systemTemp, `awa-diff-${timestamp}-${random}`);
1933
+ const tempPath = join4(systemTemp, `awa-diff-${timestamp}-${random}`);
2427
1934
  await ensureDir(tempPath);
2428
1935
  return tempPath;
2429
1936
  }
@@ -2576,18 +2083,18 @@ function writeJsonOutput(data) {
2576
2083
  // src/core/overlay.ts
2577
2084
  import { cp } from "fs/promises";
2578
2085
  import { tmpdir as tmpdir2 } from "os";
2579
- import { join as join7 } from "path";
2086
+ import { join as join6 } from "path";
2580
2087
 
2581
2088
  // src/core/template-resolver.ts
2582
2089
  import { createHash } from "crypto";
2583
- import { rm as rm2 } from "fs/promises";
2584
- import { isAbsolute, join as join6, resolve } from "path";
2090
+ import { rm } from "fs/promises";
2091
+ import { isAbsolute, join as join5, resolve } from "path";
2585
2092
  import degit from "degit";
2586
2093
  var TemplateResolver = class {
2587
2094
  // @awa-impl: CLI-3_AC-2, TPL-10_AC-1
2588
2095
  async resolve(source, refresh) {
2589
2096
  if (!source) {
2590
- const bundledPath = join6(getTemplateDir(), "awa");
2097
+ const bundledPath = join5(getTemplateDir(), "awa");
2591
2098
  return {
2592
2099
  type: "bundled",
2593
2100
  localPath: bundledPath,
@@ -2624,7 +2131,7 @@ var TemplateResolver = class {
2624
2131
  try {
2625
2132
  if (cacheExists && refresh) {
2626
2133
  logger.info(`Refreshing template: ${source}`);
2627
- await rm2(cachePath, { recursive: true, force: true });
2134
+ await rm(cachePath, { recursive: true, force: true });
2628
2135
  } else {
2629
2136
  logger.info(`Fetching template: ${source}`);
2630
2137
  }
@@ -2664,7 +2171,7 @@ var TemplateResolver = class {
2664
2171
  getCachePath(source) {
2665
2172
  const hash = createHash("sha256").update(source).digest("hex").substring(0, 16);
2666
2173
  const cacheDir = getCacheDir();
2667
- return join6(cacheDir, hash);
2174
+ return join5(cacheDir, hash);
2668
2175
  }
2669
2176
  };
2670
2177
  var templateResolver = new TemplateResolver();
@@ -2681,7 +2188,7 @@ async function resolveOverlays(overlays, refresh) {
2681
2188
  async function buildMergedDir(baseDir, overlayDirs) {
2682
2189
  const timestamp = Date.now();
2683
2190
  const random = Math.random().toString(36).substring(2, 8);
2684
- const tempPath = join7(tmpdir2(), `awa-overlay-${timestamp}-${random}`);
2191
+ const tempPath = join6(tmpdir2(), `awa-overlay-${timestamp}-${random}`);
2685
2192
  await ensureDir(tempPath);
2686
2193
  await cp(baseDir, tempPath, { recursive: true });
2687
2194
  for (const overlayDir of overlayDirs) {
@@ -2905,7 +2412,7 @@ async function diffCommand(cliOptions) {
2905
2412
  import { intro as intro2, outro as outro2 } from "@clack/prompts";
2906
2413
 
2907
2414
  // src/core/features/reporter.ts
2908
- import chalk4 from "chalk";
2415
+ import chalk3 from "chalk";
2909
2416
  var FeaturesReporter = class {
2910
2417
  // @awa-impl: DISC-6_AC-1, DISC-7_AC-1
2911
2418
  /** Render the features report to stdout. */
@@ -2940,25 +2447,25 @@ var FeaturesReporter = class {
2940
2447
  reportTable(scanResult, presets) {
2941
2448
  const { features, filesScanned } = scanResult;
2942
2449
  if (features.length === 0) {
2943
- console.log(chalk4.yellow("No feature flags found."));
2944
- console.log(chalk4.dim(`(${filesScanned} files scanned)`));
2450
+ console.log(chalk3.yellow("No feature flags found."));
2451
+ console.log(chalk3.dim(`(${filesScanned} files scanned)`));
2945
2452
  return;
2946
2453
  }
2947
- console.log(chalk4.bold(`Feature flags (${features.length} found):
2454
+ console.log(chalk3.bold(`Feature flags (${features.length} found):
2948
2455
  `));
2949
2456
  for (const feature of features) {
2950
- console.log(` ${chalk4.cyan(feature.name)}`);
2457
+ console.log(` ${chalk3.cyan(feature.name)}`);
2951
2458
  for (const file of feature.files) {
2952
- console.log(` ${chalk4.dim(file)}`);
2459
+ console.log(` ${chalk3.dim(file)}`);
2953
2460
  }
2954
2461
  }
2955
2462
  console.log("");
2956
- console.log(chalk4.dim(`${filesScanned} files scanned`));
2463
+ console.log(chalk3.dim(`${filesScanned} files scanned`));
2957
2464
  if (presets && Object.keys(presets).length > 0) {
2958
2465
  console.log("");
2959
- console.log(chalk4.bold("Presets (from .awa.toml):\n"));
2466
+ console.log(chalk3.bold("Presets (from .awa.toml):\n"));
2960
2467
  for (const [name, flags] of Object.entries(presets)) {
2961
- console.log(` ${chalk4.green(name)}: ${flags.join(", ")}`);
2468
+ console.log(` ${chalk3.green(name)}: ${flags.join(", ")}`);
2962
2469
  }
2963
2470
  }
2964
2471
  }
@@ -2966,13 +2473,13 @@ var FeaturesReporter = class {
2966
2473
  var featuresReporter = new FeaturesReporter();
2967
2474
 
2968
2475
  // src/core/features/scanner.ts
2969
- import { readdir as readdir2, readFile as readFile6 } from "fs/promises";
2970
- import { join as join8, relative as relative3 } from "path";
2476
+ import { readdir, readFile as readFile5 } from "fs/promises";
2477
+ import { join as join7, relative as relative3 } from "path";
2971
2478
  var FEATURE_PATTERN = /it\.features\.(?:includes|indexOf)\(\s*['"]([^'"]+)['"]\s*\)/g;
2972
2479
  async function* walkAllFiles(dir) {
2973
- const entries = await readdir2(dir, { withFileTypes: true });
2480
+ const entries = await readdir(dir, { withFileTypes: true });
2974
2481
  for (const entry of entries) {
2975
- const fullPath = join8(dir, entry.name);
2482
+ const fullPath = join7(dir, entry.name);
2976
2483
  if (entry.isDirectory()) {
2977
2484
  yield* walkAllFiles(fullPath);
2978
2485
  } else if (entry.isFile()) {
@@ -3000,7 +2507,7 @@ var FeatureScanner = class {
3000
2507
  for await (const filePath of walkAllFiles(templatePath)) {
3001
2508
  filesScanned++;
3002
2509
  try {
3003
- const content = await readFile6(filePath, "utf-8");
2510
+ const content = await readFile5(filePath, "utf-8");
3004
2511
  const flags = this.extractFlags(content);
3005
2512
  const relPath = relative3(templatePath, filePath);
3006
2513
  for (const flag of flags) {
@@ -3176,21 +2683,21 @@ async function generateCommand(cliOptions) {
3176
2683
  import { intro as intro4, outro as outro4 } from "@clack/prompts";
3177
2684
 
3178
2685
  // src/core/template-test/fixture-loader.ts
3179
- import { readdir as readdir3 } from "fs/promises";
3180
- import { basename as basename2, extname, join as join9 } from "path";
3181
- import { parse as parse2 } from "smol-toml";
2686
+ import { readdir as readdir2 } from "fs/promises";
2687
+ import { basename as basename2, extname, join as join8 } from "path";
2688
+ import { parse } from "smol-toml";
3182
2689
  async function discoverFixtures(templatePath) {
3183
- const testsDir = join9(templatePath, "_tests");
2690
+ const testsDir = join8(templatePath, "_tests");
3184
2691
  let entries;
3185
2692
  try {
3186
- const dirEntries = await readdir3(testsDir, { withFileTypes: true });
2693
+ const dirEntries = await readdir2(testsDir, { withFileTypes: true });
3187
2694
  entries = dirEntries.filter((e) => e.isFile() && extname(e.name) === ".toml").map((e) => e.name).sort();
3188
2695
  } catch {
3189
2696
  return [];
3190
2697
  }
3191
2698
  const fixtures = [];
3192
2699
  for (const filename of entries) {
3193
- const filePath = join9(testsDir, filename);
2700
+ const filePath = join8(testsDir, filename);
3194
2701
  const fixture = await parseFixture(filePath);
3195
2702
  fixtures.push(fixture);
3196
2703
  }
@@ -3198,7 +2705,7 @@ async function discoverFixtures(templatePath) {
3198
2705
  }
3199
2706
  async function parseFixture(filePath) {
3200
2707
  const content = await readTextFile(filePath);
3201
- const parsed = parse2(content);
2708
+ const parsed = parse(content);
3202
2709
  const name = basename2(filePath, extname(filePath));
3203
2710
  const features = toStringArray2(parsed.features) ?? [];
3204
2711
  const preset = toStringArray2(parsed.preset) ?? [];
@@ -3221,61 +2728,61 @@ function toStringArray2(value) {
3221
2728
  }
3222
2729
 
3223
2730
  // src/core/template-test/reporter.ts
3224
- import chalk5 from "chalk";
2731
+ import chalk4 from "chalk";
3225
2732
  function report2(result) {
3226
2733
  console.log("");
3227
2734
  for (const fixture of result.results) {
3228
2735
  reportFixture(fixture);
3229
2736
  }
3230
2737
  console.log("");
3231
- console.log(chalk5.bold("Test Summary:"));
2738
+ console.log(chalk4.bold("Test Summary:"));
3232
2739
  console.log(` Total: ${result.total}`);
3233
- console.log(chalk5.green(` Passed: ${result.passed}`));
2740
+ console.log(chalk4.green(` Passed: ${result.passed}`));
3234
2741
  if (result.failed > 0) {
3235
- console.log(chalk5.red(` Failed: ${result.failed}`));
2742
+ console.log(chalk4.red(` Failed: ${result.failed}`));
3236
2743
  }
3237
2744
  console.log("");
3238
2745
  }
3239
2746
  function reportFixture(fixture) {
3240
- const icon = fixture.passed ? chalk5.green("\u2714") : chalk5.red("\u2716");
2747
+ const icon = fixture.passed ? chalk4.green("\u2714") : chalk4.red("\u2716");
3241
2748
  console.log(`${icon} ${fixture.name}`);
3242
2749
  if (fixture.error) {
3243
- console.log(chalk5.red(` Error: ${fixture.error}`));
2750
+ console.log(chalk4.red(` Error: ${fixture.error}`));
3244
2751
  return;
3245
2752
  }
3246
2753
  const missingFiles = fixture.fileResults.filter((r) => !r.found);
3247
2754
  for (const missing of missingFiles) {
3248
- console.log(chalk5.red(` Missing file: ${missing.path}`));
2755
+ console.log(chalk4.red(` Missing file: ${missing.path}`));
3249
2756
  }
3250
2757
  const snapshotFailures = fixture.snapshotResults.filter((r) => r.status !== "match");
3251
2758
  for (const failure of snapshotFailures) {
3252
2759
  switch (failure.status) {
3253
2760
  case "mismatch":
3254
- console.log(chalk5.yellow(` Snapshot mismatch: ${failure.path}`));
2761
+ console.log(chalk4.yellow(` Snapshot mismatch: ${failure.path}`));
3255
2762
  break;
3256
2763
  case "missing-snapshot":
3257
- console.log(chalk5.yellow(` Missing snapshot: ${failure.path}`));
2764
+ console.log(chalk4.yellow(` Missing snapshot: ${failure.path}`));
3258
2765
  break;
3259
2766
  case "extra-snapshot":
3260
- console.log(chalk5.yellow(` Extra snapshot (not in output): ${failure.path}`));
2767
+ console.log(chalk4.yellow(` Extra snapshot (not in output): ${failure.path}`));
3261
2768
  break;
3262
2769
  }
3263
2770
  }
3264
2771
  }
3265
2772
 
3266
2773
  // src/core/template-test/runner.ts
3267
- import { mkdir as mkdir3, rm as rm4 } from "fs/promises";
2774
+ import { mkdir as mkdir2, rm as rm3 } from "fs/promises";
3268
2775
  import { tmpdir as tmpdir3 } from "os";
3269
- import { join as join11 } from "path";
2776
+ import { join as join10 } from "path";
3270
2777
 
3271
2778
  // src/core/template-test/snapshot.ts
3272
- import { mkdir as mkdir2, readdir as readdir4, rm as rm3 } from "fs/promises";
3273
- import { join as join10, relative as relative4 } from "path";
2779
+ import { mkdir, readdir as readdir3, rm as rm2 } from "fs/promises";
2780
+ import { join as join9, relative as relative4 } from "path";
3274
2781
  async function walkRelative(dir, base) {
3275
2782
  const results = [];
3276
- const entries = await readdir4(dir, { withFileTypes: true });
2783
+ const entries = await readdir3(dir, { withFileTypes: true });
3277
2784
  for (const entry of entries) {
3278
- const fullPath = join10(dir, entry.name);
2785
+ const fullPath = join9(dir, entry.name);
3279
2786
  if (entry.isDirectory()) {
3280
2787
  const sub = await walkRelative(fullPath, base);
3281
2788
  results.push(...sub);
@@ -3292,8 +2799,8 @@ async function compareSnapshots(renderedDir, snapshotDir) {
3292
2799
  const snapshotSet = new Set(snapshotFiles);
3293
2800
  const renderedSet = new Set(renderedFiles);
3294
2801
  for (const file of renderedFiles) {
3295
- const renderedPath = join10(renderedDir, file);
3296
- const snapshotPath = join10(snapshotDir, file);
2802
+ const renderedPath = join9(renderedDir, file);
2803
+ const snapshotPath = join9(snapshotDir, file);
3297
2804
  if (!snapshotSet.has(file)) {
3298
2805
  results.push({ path: file, status: "missing-snapshot" });
3299
2806
  continue;
@@ -3314,13 +2821,13 @@ async function compareSnapshots(renderedDir, snapshotDir) {
3314
2821
  }
3315
2822
  async function updateSnapshots(renderedDir, snapshotDir) {
3316
2823
  if (await pathExists(snapshotDir)) {
3317
- await rm3(snapshotDir, { recursive: true, force: true });
2824
+ await rm2(snapshotDir, { recursive: true, force: true });
3318
2825
  }
3319
- await mkdir2(snapshotDir, { recursive: true });
2826
+ await mkdir(snapshotDir, { recursive: true });
3320
2827
  const files = await walkRelative(renderedDir, renderedDir);
3321
2828
  for (const file of files) {
3322
- const srcPath = join10(renderedDir, file);
3323
- const destPath = join10(snapshotDir, file);
2829
+ const srcPath = join9(renderedDir, file);
2830
+ const destPath = join9(snapshotDir, file);
3324
2831
  const content = await readTextFile(srcPath);
3325
2832
  await writeTextFile(destPath, content);
3326
2833
  }
@@ -3328,9 +2835,9 @@ async function updateSnapshots(renderedDir, snapshotDir) {
3328
2835
 
3329
2836
  // src/core/template-test/runner.ts
3330
2837
  async function runFixture(fixture, templatePath, options, presetDefinitions = {}) {
3331
- const tempDir = join11(tmpdir3(), `awa-test-${fixture.name}-${Date.now()}`);
2838
+ const tempDir = join10(tmpdir3(), `awa-test-${fixture.name}-${Date.now()}`);
3332
2839
  try {
3333
- await mkdir3(tempDir, { recursive: true });
2840
+ await mkdir2(tempDir, { recursive: true });
3334
2841
  const features = featureResolver.resolve({
3335
2842
  baseFeatures: [...fixture.features],
3336
2843
  presetNames: [...fixture.preset],
@@ -3347,12 +2854,12 @@ async function runFixture(fixture, templatePath, options, presetDefinitions = {}
3347
2854
  });
3348
2855
  const fileResults = [];
3349
2856
  for (const expectedFile of fixture.expectedFiles) {
3350
- const fullPath = join11(tempDir, expectedFile);
2857
+ const fullPath = join10(tempDir, expectedFile);
3351
2858
  const found = await pathExists(fullPath);
3352
2859
  fileResults.push({ path: expectedFile, found });
3353
2860
  }
3354
2861
  const missingFiles = fileResults.filter((r) => !r.found);
3355
- const snapshotDir = join11(templatePath, "_tests", fixture.name);
2862
+ const snapshotDir = join10(templatePath, "_tests", fixture.name);
3356
2863
  let snapshotResults = [];
3357
2864
  if (options.updateSnapshots) {
3358
2865
  await updateSnapshots(tempDir, snapshotDir);
@@ -3376,7 +2883,7 @@ async function runFixture(fixture, templatePath, options, presetDefinitions = {}
3376
2883
  error: error instanceof Error ? error.message : String(error)
3377
2884
  };
3378
2885
  } finally {
3379
- await rm4(tempDir, { recursive: true, force: true }).catch(() => {
2886
+ await rm3(tempDir, { recursive: true, force: true }).catch(() => {
3380
2887
  });
3381
2888
  }
3382
2889
  }
@@ -3434,6 +2941,91 @@ async function testCommand(options) {
3434
2941
  }
3435
2942
  }
3436
2943
 
2944
+ // src/utils/update-check.ts
2945
+ import chalk5 from "chalk";
2946
+ function compareSemver(a, b) {
2947
+ const partsA = a.split(".").map((s) => Number.parseInt(s, 10));
2948
+ const partsB = b.split(".").map((s) => Number.parseInt(s, 10));
2949
+ for (let i = 0; i < 3; i++) {
2950
+ const diff = (partsA[i] ?? 0) - (partsB[i] ?? 0);
2951
+ if (diff !== 0) return diff;
2952
+ }
2953
+ return 0;
2954
+ }
2955
+ function isMajorVersionBump(current, latest) {
2956
+ const currentMajor = Number.parseInt(current.split(".")[0] ?? "0", 10);
2957
+ const latestMajor = Number.parseInt(latest.split(".")[0] ?? "0", 10);
2958
+ return latestMajor > currentMajor;
2959
+ }
2960
+ async function checkForUpdate() {
2961
+ try {
2962
+ const response = await fetch("https://registry.npmjs.org/@ncoderz/awa/latest", {
2963
+ signal: AbortSignal.timeout(5e3)
2964
+ });
2965
+ if (!response.ok) return null;
2966
+ const data = await response.json();
2967
+ const latest = data.version;
2968
+ if (!latest || typeof latest !== "string") return null;
2969
+ const current = PACKAGE_INFO.version;
2970
+ const isOutdated = compareSemver(current, latest) < 0;
2971
+ return {
2972
+ current,
2973
+ latest,
2974
+ isOutdated,
2975
+ isMajorBump: isOutdated && isMajorVersionBump(current, latest)
2976
+ };
2977
+ } catch {
2978
+ return null;
2979
+ }
2980
+ }
2981
+ function printUpdateWarning(log, result) {
2982
+ if (!result.isOutdated) return;
2983
+ console.log("");
2984
+ if (result.isMajorBump) {
2985
+ log.warn(
2986
+ chalk5.yellow(
2987
+ `New major version available: ${result.current} \u2192 ${result.latest} (breaking changes)`
2988
+ )
2989
+ );
2990
+ log.warn(chalk5.dim(" See https://github.com/ncoderz/awa/releases for details"));
2991
+ } else {
2992
+ log.warn(chalk5.yellow(`Update available: ${result.current} \u2192 ${result.latest}`));
2993
+ }
2994
+ log.warn(chalk5.dim(" Run `npm install -g @ncoderz/awa` to update"));
2995
+ console.log("");
2996
+ }
2997
+
2998
+ // src/utils/update-check-cache.ts
2999
+ import { mkdir as mkdir3, readFile as readFile6, writeFile } from "fs/promises";
3000
+ import { homedir } from "os";
3001
+ import { dirname, join as join11 } from "path";
3002
+ var CACHE_DIR = join11(homedir(), ".cache", "awa");
3003
+ var CACHE_FILE = join11(CACHE_DIR, "update-check.json");
3004
+ var DEFAULT_INTERVAL_MS = 864e5;
3005
+ async function shouldCheck(intervalMs = DEFAULT_INTERVAL_MS) {
3006
+ try {
3007
+ const raw = await readFile6(CACHE_FILE, "utf-8");
3008
+ const data = JSON.parse(raw);
3009
+ if (typeof data.timestamp !== "number" || typeof data.latestVersion !== "string") {
3010
+ return true;
3011
+ }
3012
+ return Date.now() - data.timestamp >= intervalMs;
3013
+ } catch {
3014
+ return true;
3015
+ }
3016
+ }
3017
+ async function writeCache(latestVersion) {
3018
+ try {
3019
+ await mkdir3(dirname(CACHE_FILE), { recursive: true });
3020
+ const data = {
3021
+ timestamp: Date.now(),
3022
+ latestVersion
3023
+ };
3024
+ await writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
3025
+ } catch {
3026
+ }
3027
+ }
3028
+
3437
3029
  // src/cli/index.ts
3438
3030
  var version = PACKAGE_INFO.version;
3439
3031
  var program = new Command();
@@ -3491,7 +3083,7 @@ program.command("diff").description("Compare template output with existing targe
3491
3083
  });
3492
3084
  program.command("check").description(
3493
3085
  "Validate spec files against schemas and check traceability between code markers and specs"
3494
- ).option("-c, --config <path>", "Path to configuration file").option("--ignore <pattern...>", "Glob patterns for paths to exclude").option("--format <format>", "Output format (text or json)", "text").option(
3086
+ ).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(
3495
3087
  "--allow-warnings",
3496
3088
  "Allow warnings without failing (default: warnings are errors)",
3497
3089
  false
@@ -3502,7 +3094,8 @@ program.command("check").description(
3502
3094
  ).action(async (options) => {
3503
3095
  const cliOptions = {
3504
3096
  config: options.config,
3505
- ignore: options.ignore,
3097
+ specIgnore: options.specIgnore,
3098
+ codeIgnore: options.codeIgnore,
3506
3099
  format: options.format,
3507
3100
  allowWarnings: options.allowWarnings,
3508
3101
  specOnly: options.specOnly
@@ -3528,5 +3121,41 @@ program.command("test").description("Run template test fixtures to verify expect
3528
3121
  const exitCode = await testCommand(testOptions);
3529
3122
  process.exit(exitCode);
3530
3123
  });
3531
- program.parse();
3124
+ var updateCheckPromise = null;
3125
+ var isJsonOrSummary = process.argv.includes("--json") || process.argv.includes("--summary");
3126
+ var isTTY = process.stdout.isTTY === true;
3127
+ var isDisabledByEnv = !!process.env.NO_UPDATE_NOTIFIER;
3128
+ if (!isJsonOrSummary && isTTY && !isDisabledByEnv) {
3129
+ updateCheckPromise = (async () => {
3130
+ try {
3131
+ const { configLoader: configLoader2 } = await import("./config-LWUO5EXL.js");
3132
+ 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;
3133
+ const fileConfig = await configLoader2.load(configPath ?? null);
3134
+ const updateCheckConfig = fileConfig?.["update-check"];
3135
+ if (updateCheckConfig?.enabled === false) return null;
3136
+ const intervalSeconds = updateCheckConfig?.interval ?? 86400;
3137
+ const intervalMs = intervalSeconds * 1e3;
3138
+ const needsCheck = await shouldCheck(intervalMs);
3139
+ if (!needsCheck) return null;
3140
+ const result = await checkForUpdate();
3141
+ if (result) {
3142
+ await writeCache(result.latest);
3143
+ }
3144
+ return result;
3145
+ } catch {
3146
+ return null;
3147
+ }
3148
+ })();
3149
+ }
3150
+ program.hook("postAction", async () => {
3151
+ if (!updateCheckPromise) return;
3152
+ try {
3153
+ const result = await updateCheckPromise;
3154
+ if (result?.isOutdated) {
3155
+ printUpdateWarning(logger, result);
3156
+ }
3157
+ } catch {
3158
+ }
3159
+ });
3160
+ program.parseAsync();
3532
3161
  //# sourceMappingURL=index.js.map