@ncoderz/awa 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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-3SSUJFKN.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.2.0",
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."
@@ -110,7 +128,7 @@ var IGNORE_LINE_RE = /@awa-ignore\b/;
110
128
  var IGNORE_START_RE = /@awa-ignore-start\b/;
111
129
  var IGNORE_END_RE = /@awa-ignore-end\b/;
112
130
  async function scanMarkers(config) {
113
- const files = await collectCodeFiles(config.codeGlobs, config.ignore);
131
+ const files = await collectCodeFiles(config.codeGlobs, config.codeIgnore);
114
132
  const markers = [];
115
133
  const findings = [];
116
134
  for (const filePath of files) {
@@ -992,7 +1010,7 @@ function collectAllCodeBlocks(section) {
992
1010
  import { readFile as readFile4 } from "fs/promises";
993
1011
  import { basename } from "path";
994
1012
  async function parseSpecs(config) {
995
- const files = await collectSpecFiles(config.specGlobs, config.ignore);
1013
+ const files = await collectSpecFiles(config.specGlobs, config.specIgnore);
996
1014
  const specFiles = [];
997
1015
  const requirementIds = /* @__PURE__ */ new Set();
998
1016
  const acIds = /* @__PURE__ */ new Set();
@@ -1156,9 +1174,30 @@ function checkSpecAgainstSpec(specs, markers, config) {
1156
1174
 
1157
1175
  // src/core/check/types.ts
1158
1176
  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/**"],
1177
+ specGlobs: [
1178
+ ".awa/specs/ARCHITECTURE.md",
1179
+ ".awa/specs/FEAT-*.md",
1180
+ ".awa/specs/REQ-*.md",
1181
+ ".awa/specs/DESIGN-*.md",
1182
+ ".awa/specs/EXAMPLES-*.md",
1183
+ ".awa/specs/API-*.tsp",
1184
+ ".awa/tasks/TASK-*.md",
1185
+ ".awa/plans/PLAN-*.md",
1186
+ ".awa/align/ALIGN-*.md"
1187
+ ],
1188
+ codeGlobs: [
1189
+ "**/*.{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}"
1190
+ ],
1191
+ specIgnore: [],
1192
+ codeIgnore: [
1193
+ "node_modules/**",
1194
+ "dist/**",
1195
+ "vendor/**",
1196
+ "target/**",
1197
+ "build/**",
1198
+ "out/**",
1199
+ ".awa/**"
1200
+ ],
1162
1201
  ignoreMarkers: [],
1163
1202
  markers: ["@awa-impl", "@awa-test", "@awa-component"],
1164
1203
  idPattern: "([A-Z][A-Z0-9]*-\\d+(?:\\.\\d+)?(?:_AC-\\d+)?|[A-Z][A-Z0-9]*_P-\\d+)",
@@ -1170,576 +1209,6 @@ var DEFAULT_CHECK_CONFIG = {
1170
1209
  specOnly: false
1171
1210
  };
1172
1211
 
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
1212
  // src/commands/check.ts
1744
1213
  async function checkCommand(cliOptions) {
1745
1214
  try {
@@ -1787,9 +1256,14 @@ function buildCheckConfig(fileConfig, cliOptions) {
1787
1256
  ...DEFAULT_CHECK_CONFIG.crossRefPatterns
1788
1257
  ];
1789
1258
  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];
1259
+ const configSpecIgnore = toStringArray(section?.["spec-ignore"]) ?? [
1260
+ ...DEFAULT_CHECK_CONFIG.specIgnore
1261
+ ];
1262
+ const configCodeIgnore = toStringArray(section?.["code-ignore"]) ?? [
1263
+ ...DEFAULT_CHECK_CONFIG.codeIgnore
1264
+ ];
1265
+ const specIgnore = [...configSpecIgnore, ...cliOptions.specIgnore ?? []];
1266
+ const codeIgnore = [...configCodeIgnore, ...cliOptions.codeIgnore ?? []];
1793
1267
  const ignoreMarkers = toStringArray(section?.["ignore-markers"]) ?? [
1794
1268
  ...DEFAULT_CHECK_CONFIG.ignoreMarkers
1795
1269
  ];
@@ -1801,7 +1275,8 @@ function buildCheckConfig(fileConfig, cliOptions) {
1801
1275
  return {
1802
1276
  specGlobs,
1803
1277
  codeGlobs,
1804
- ignore,
1278
+ specIgnore,
1279
+ codeIgnore,
1805
1280
  ignoreMarkers,
1806
1281
  markers,
1807
1282
  idPattern,
@@ -1882,12 +1357,12 @@ var batchRunner = new BatchRunner();
1882
1357
 
1883
1358
  // src/core/differ.ts
1884
1359
  import { tmpdir } from "os";
1885
- import { join as join5, relative as relative2 } from "path";
1360
+ import { join as join4, relative as relative2 } from "path";
1886
1361
  import { structuredPatch } from "diff";
1887
1362
  import { isBinaryFile as detectBinaryFile } from "isbinaryfile";
1888
1363
 
1889
1364
  // src/core/delete-list.ts
1890
- import { join as join3 } from "path";
1365
+ import { join as join2 } from "path";
1891
1366
  var DELETE_LIST_FILENAME = "_delete.txt";
1892
1367
  function parseDeleteList(content) {
1893
1368
  const entries = [];
@@ -1914,7 +1389,7 @@ function resolveDeleteList(entries, activeFeatures) {
1914
1389
  return entries.filter((e) => e.features === void 0 || !e.features.some((f) => activeSet.has(f))).map((e) => e.path);
1915
1390
  }
1916
1391
  async function loadDeleteList(templatePath) {
1917
- const deleteListPath = join3(templatePath, DELETE_LIST_FILENAME);
1392
+ const deleteListPath = join2(templatePath, DELETE_LIST_FILENAME);
1918
1393
  if (!await pathExists(deleteListPath)) {
1919
1394
  return [];
1920
1395
  }
@@ -1923,12 +1398,12 @@ async function loadDeleteList(templatePath) {
1923
1398
  }
1924
1399
 
1925
1400
  // src/core/generator.ts
1926
- import { join as join4, relative } from "path";
1401
+ import { join as join3, relative } from "path";
1927
1402
 
1928
1403
  // src/core/resolver.ts
1929
1404
  import { MultiSelectPrompt } from "@clack/core";
1930
1405
  import { isCancel, multiselect } from "@clack/prompts";
1931
- import chalk3 from "chalk";
1406
+ import chalk2 from "chalk";
1932
1407
  var _unicode = process.platform !== "win32";
1933
1408
  var _s = (c, fb) => _unicode ? c : fb;
1934
1409
  var _CHECKED = _s("\u25FC", "[+]");
@@ -1938,20 +1413,20 @@ var _BAR = _s("\u2502", "|");
1938
1413
  var _BAR_END = _s("\u2514", "-");
1939
1414
  function _renderDeleteItem(opt, state) {
1940
1415
  const label = opt.label ?? opt.value;
1941
- const hint = opt.hint ? ` ${chalk3.dim(`(${opt.hint})`)}` : "";
1416
+ const hint = opt.hint ? ` ${chalk2.dim(`(${opt.hint})`)}` : "";
1942
1417
  switch (state) {
1943
1418
  case "active":
1944
- return `${chalk3.cyan(_UNCHECKED_A)} ${label}${hint}`;
1419
+ return `${chalk2.cyan(_UNCHECKED_A)} ${label}${hint}`;
1945
1420
  case "selected":
1946
- return `${chalk3.red(_CHECKED)} ${chalk3.dim(label)}${hint}`;
1421
+ return `${chalk2.red(_CHECKED)} ${chalk2.dim(label)}${hint}`;
1947
1422
  case "active-selected":
1948
- return `${chalk3.red(_CHECKED)} ${label}${hint}`;
1423
+ return `${chalk2.red(_CHECKED)} ${label}${hint}`;
1949
1424
  case "cancelled":
1950
- return chalk3.strikethrough(chalk3.dim(label));
1425
+ return chalk2.strikethrough(chalk2.dim(label));
1951
1426
  case "submitted":
1952
- return chalk3.dim(label);
1427
+ return chalk2.dim(label);
1953
1428
  default:
1954
- return `${chalk3.dim(_UNCHECKED)} ${chalk3.dim(label)}`;
1429
+ return `${chalk2.dim(_UNCHECKED)} ${chalk2.dim(label)}`;
1955
1430
  }
1956
1431
  }
1957
1432
  async function deleteMultiselect(opts) {
@@ -1962,8 +1437,8 @@ async function deleteMultiselect(opts) {
1962
1437
  required,
1963
1438
  render() {
1964
1439
  const self = this;
1965
- const header = `${chalk3.gray(_BAR)}
1966
- ${chalk3.cyan(_BAR)} ${message}
1440
+ const header = `${chalk2.gray(_BAR)}
1441
+ ${chalk2.cyan(_BAR)} ${message}
1967
1442
  `;
1968
1443
  const getState = (opt, idx) => {
1969
1444
  const active = idx === self.cursor;
@@ -1975,16 +1450,16 @@ ${chalk3.cyan(_BAR)} ${message}
1975
1450
  };
1976
1451
  switch (self.state) {
1977
1452
  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"));
1453
+ 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
1454
  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")}`;
1455
+ const cancelled = self.options.filter((o) => self.value.includes(o.value)).map((o) => _renderDeleteItem(o, "cancelled")).join(chalk2.dim(", "));
1456
+ return `${header}${chalk2.gray(_BAR)} ${cancelled.trim() ? `${cancelled}
1457
+ ${chalk2.gray(_BAR)}` : chalk2.dim("none")}`;
1983
1458
  }
1984
1459
  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)}
1460
+ return `${header}${chalk2.cyan(_BAR)} ` + self.options.map((o, i) => _renderDeleteItem(o, getState(o, i))).join(`
1461
+ ${chalk2.cyan(_BAR)} `) + `
1462
+ ${chalk2.cyan(_BAR_END)}
1988
1463
  `;
1989
1464
  }
1990
1465
  }
@@ -2254,7 +1729,7 @@ var FileGenerator = class {
2254
1729
  const generatedOutputPaths = new Set(filesToProcess.map((f) => f.outputFile));
2255
1730
  const deleteCandidates = [];
2256
1731
  for (const relPath of deleteList) {
2257
- const absPath = join4(outputPath, relPath);
1732
+ const absPath = join3(outputPath, relPath);
2258
1733
  if (generatedOutputPaths.has(absPath)) {
2259
1734
  logger.warn(
2260
1735
  `Delete list entry '${relPath}' conflicts with generated file \u2014 skipping deletion`
@@ -2316,7 +1791,7 @@ var FileGenerator = class {
2316
1791
  // @awa-impl: GEN-1_AC-1, GEN-1_AC-2, GEN-1_AC-3
2317
1792
  computeOutputPath(templatePath, templateRoot, outputRoot) {
2318
1793
  const relativePath = relative(templateRoot, templatePath);
2319
- return join4(outputRoot, relativePath);
1794
+ return join3(outputRoot, relativePath);
2320
1795
  }
2321
1796
  };
2322
1797
  var fileGenerator = new FileGenerator();
@@ -2358,8 +1833,8 @@ var DiffEngine = class {
2358
1833
  }
2359
1834
  const files = [];
2360
1835
  for (const relPath of generatedFiles) {
2361
- const generatedFilePath = join5(tempPath, relPath);
2362
- const targetFilePath = join5(targetPath, relPath);
1836
+ const generatedFilePath = join4(tempPath, relPath);
1837
+ const targetFilePath = join4(targetPath, relPath);
2363
1838
  if (targetFiles.has(relPath)) {
2364
1839
  const fileDiff = await this.compareFiles(generatedFilePath, targetFilePath, relPath);
2365
1840
  files.push(fileDiff);
@@ -2423,7 +1898,7 @@ var DiffEngine = class {
2423
1898
  const systemTemp = tmpdir();
2424
1899
  const timestamp = Date.now();
2425
1900
  const random = Math.random().toString(36).substring(2, 8);
2426
- const tempPath = join5(systemTemp, `awa-diff-${timestamp}-${random}`);
1901
+ const tempPath = join4(systemTemp, `awa-diff-${timestamp}-${random}`);
2427
1902
  await ensureDir(tempPath);
2428
1903
  return tempPath;
2429
1904
  }
@@ -2576,18 +2051,18 @@ function writeJsonOutput(data) {
2576
2051
  // src/core/overlay.ts
2577
2052
  import { cp } from "fs/promises";
2578
2053
  import { tmpdir as tmpdir2 } from "os";
2579
- import { join as join7 } from "path";
2054
+ import { join as join6 } from "path";
2580
2055
 
2581
2056
  // src/core/template-resolver.ts
2582
2057
  import { createHash } from "crypto";
2583
- import { rm as rm2 } from "fs/promises";
2584
- import { isAbsolute, join as join6, resolve } from "path";
2058
+ import { rm } from "fs/promises";
2059
+ import { isAbsolute, join as join5, resolve } from "path";
2585
2060
  import degit from "degit";
2586
2061
  var TemplateResolver = class {
2587
2062
  // @awa-impl: CLI-3_AC-2, TPL-10_AC-1
2588
2063
  async resolve(source, refresh) {
2589
2064
  if (!source) {
2590
- const bundledPath = join6(getTemplateDir(), "awa");
2065
+ const bundledPath = join5(getTemplateDir(), "awa");
2591
2066
  return {
2592
2067
  type: "bundled",
2593
2068
  localPath: bundledPath,
@@ -2624,7 +2099,7 @@ var TemplateResolver = class {
2624
2099
  try {
2625
2100
  if (cacheExists && refresh) {
2626
2101
  logger.info(`Refreshing template: ${source}`);
2627
- await rm2(cachePath, { recursive: true, force: true });
2102
+ await rm(cachePath, { recursive: true, force: true });
2628
2103
  } else {
2629
2104
  logger.info(`Fetching template: ${source}`);
2630
2105
  }
@@ -2664,7 +2139,7 @@ var TemplateResolver = class {
2664
2139
  getCachePath(source) {
2665
2140
  const hash = createHash("sha256").update(source).digest("hex").substring(0, 16);
2666
2141
  const cacheDir = getCacheDir();
2667
- return join6(cacheDir, hash);
2142
+ return join5(cacheDir, hash);
2668
2143
  }
2669
2144
  };
2670
2145
  var templateResolver = new TemplateResolver();
@@ -2681,7 +2156,7 @@ async function resolveOverlays(overlays, refresh) {
2681
2156
  async function buildMergedDir(baseDir, overlayDirs) {
2682
2157
  const timestamp = Date.now();
2683
2158
  const random = Math.random().toString(36).substring(2, 8);
2684
- const tempPath = join7(tmpdir2(), `awa-overlay-${timestamp}-${random}`);
2159
+ const tempPath = join6(tmpdir2(), `awa-overlay-${timestamp}-${random}`);
2685
2160
  await ensureDir(tempPath);
2686
2161
  await cp(baseDir, tempPath, { recursive: true });
2687
2162
  for (const overlayDir of overlayDirs) {
@@ -2905,7 +2380,7 @@ async function diffCommand(cliOptions) {
2905
2380
  import { intro as intro2, outro as outro2 } from "@clack/prompts";
2906
2381
 
2907
2382
  // src/core/features/reporter.ts
2908
- import chalk4 from "chalk";
2383
+ import chalk3 from "chalk";
2909
2384
  var FeaturesReporter = class {
2910
2385
  // @awa-impl: DISC-6_AC-1, DISC-7_AC-1
2911
2386
  /** Render the features report to stdout. */
@@ -2940,25 +2415,25 @@ var FeaturesReporter = class {
2940
2415
  reportTable(scanResult, presets) {
2941
2416
  const { features, filesScanned } = scanResult;
2942
2417
  if (features.length === 0) {
2943
- console.log(chalk4.yellow("No feature flags found."));
2944
- console.log(chalk4.dim(`(${filesScanned} files scanned)`));
2418
+ console.log(chalk3.yellow("No feature flags found."));
2419
+ console.log(chalk3.dim(`(${filesScanned} files scanned)`));
2945
2420
  return;
2946
2421
  }
2947
- console.log(chalk4.bold(`Feature flags (${features.length} found):
2422
+ console.log(chalk3.bold(`Feature flags (${features.length} found):
2948
2423
  `));
2949
2424
  for (const feature of features) {
2950
- console.log(` ${chalk4.cyan(feature.name)}`);
2425
+ console.log(` ${chalk3.cyan(feature.name)}`);
2951
2426
  for (const file of feature.files) {
2952
- console.log(` ${chalk4.dim(file)}`);
2427
+ console.log(` ${chalk3.dim(file)}`);
2953
2428
  }
2954
2429
  }
2955
2430
  console.log("");
2956
- console.log(chalk4.dim(`${filesScanned} files scanned`));
2431
+ console.log(chalk3.dim(`${filesScanned} files scanned`));
2957
2432
  if (presets && Object.keys(presets).length > 0) {
2958
2433
  console.log("");
2959
- console.log(chalk4.bold("Presets (from .awa.toml):\n"));
2434
+ console.log(chalk3.bold("Presets (from .awa.toml):\n"));
2960
2435
  for (const [name, flags] of Object.entries(presets)) {
2961
- console.log(` ${chalk4.green(name)}: ${flags.join(", ")}`);
2436
+ console.log(` ${chalk3.green(name)}: ${flags.join(", ")}`);
2962
2437
  }
2963
2438
  }
2964
2439
  }
@@ -2966,13 +2441,13 @@ var FeaturesReporter = class {
2966
2441
  var featuresReporter = new FeaturesReporter();
2967
2442
 
2968
2443
  // 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";
2444
+ import { readdir, readFile as readFile5 } from "fs/promises";
2445
+ import { join as join7, relative as relative3 } from "path";
2971
2446
  var FEATURE_PATTERN = /it\.features\.(?:includes|indexOf)\(\s*['"]([^'"]+)['"]\s*\)/g;
2972
2447
  async function* walkAllFiles(dir) {
2973
- const entries = await readdir2(dir, { withFileTypes: true });
2448
+ const entries = await readdir(dir, { withFileTypes: true });
2974
2449
  for (const entry of entries) {
2975
- const fullPath = join8(dir, entry.name);
2450
+ const fullPath = join7(dir, entry.name);
2976
2451
  if (entry.isDirectory()) {
2977
2452
  yield* walkAllFiles(fullPath);
2978
2453
  } else if (entry.isFile()) {
@@ -3000,7 +2475,7 @@ var FeatureScanner = class {
3000
2475
  for await (const filePath of walkAllFiles(templatePath)) {
3001
2476
  filesScanned++;
3002
2477
  try {
3003
- const content = await readFile6(filePath, "utf-8");
2478
+ const content = await readFile5(filePath, "utf-8");
3004
2479
  const flags = this.extractFlags(content);
3005
2480
  const relPath = relative3(templatePath, filePath);
3006
2481
  for (const flag of flags) {
@@ -3176,21 +2651,21 @@ async function generateCommand(cliOptions) {
3176
2651
  import { intro as intro4, outro as outro4 } from "@clack/prompts";
3177
2652
 
3178
2653
  // 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";
2654
+ import { readdir as readdir2 } from "fs/promises";
2655
+ import { basename as basename2, extname, join as join8 } from "path";
2656
+ import { parse } from "smol-toml";
3182
2657
  async function discoverFixtures(templatePath) {
3183
- const testsDir = join9(templatePath, "_tests");
2658
+ const testsDir = join8(templatePath, "_tests");
3184
2659
  let entries;
3185
2660
  try {
3186
- const dirEntries = await readdir3(testsDir, { withFileTypes: true });
2661
+ const dirEntries = await readdir2(testsDir, { withFileTypes: true });
3187
2662
  entries = dirEntries.filter((e) => e.isFile() && extname(e.name) === ".toml").map((e) => e.name).sort();
3188
2663
  } catch {
3189
2664
  return [];
3190
2665
  }
3191
2666
  const fixtures = [];
3192
2667
  for (const filename of entries) {
3193
- const filePath = join9(testsDir, filename);
2668
+ const filePath = join8(testsDir, filename);
3194
2669
  const fixture = await parseFixture(filePath);
3195
2670
  fixtures.push(fixture);
3196
2671
  }
@@ -3198,7 +2673,7 @@ async function discoverFixtures(templatePath) {
3198
2673
  }
3199
2674
  async function parseFixture(filePath) {
3200
2675
  const content = await readTextFile(filePath);
3201
- const parsed = parse2(content);
2676
+ const parsed = parse(content);
3202
2677
  const name = basename2(filePath, extname(filePath));
3203
2678
  const features = toStringArray2(parsed.features) ?? [];
3204
2679
  const preset = toStringArray2(parsed.preset) ?? [];
@@ -3221,61 +2696,61 @@ function toStringArray2(value) {
3221
2696
  }
3222
2697
 
3223
2698
  // src/core/template-test/reporter.ts
3224
- import chalk5 from "chalk";
2699
+ import chalk4 from "chalk";
3225
2700
  function report2(result) {
3226
2701
  console.log("");
3227
2702
  for (const fixture of result.results) {
3228
2703
  reportFixture(fixture);
3229
2704
  }
3230
2705
  console.log("");
3231
- console.log(chalk5.bold("Test Summary:"));
2706
+ console.log(chalk4.bold("Test Summary:"));
3232
2707
  console.log(` Total: ${result.total}`);
3233
- console.log(chalk5.green(` Passed: ${result.passed}`));
2708
+ console.log(chalk4.green(` Passed: ${result.passed}`));
3234
2709
  if (result.failed > 0) {
3235
- console.log(chalk5.red(` Failed: ${result.failed}`));
2710
+ console.log(chalk4.red(` Failed: ${result.failed}`));
3236
2711
  }
3237
2712
  console.log("");
3238
2713
  }
3239
2714
  function reportFixture(fixture) {
3240
- const icon = fixture.passed ? chalk5.green("\u2714") : chalk5.red("\u2716");
2715
+ const icon = fixture.passed ? chalk4.green("\u2714") : chalk4.red("\u2716");
3241
2716
  console.log(`${icon} ${fixture.name}`);
3242
2717
  if (fixture.error) {
3243
- console.log(chalk5.red(` Error: ${fixture.error}`));
2718
+ console.log(chalk4.red(` Error: ${fixture.error}`));
3244
2719
  return;
3245
2720
  }
3246
2721
  const missingFiles = fixture.fileResults.filter((r) => !r.found);
3247
2722
  for (const missing of missingFiles) {
3248
- console.log(chalk5.red(` Missing file: ${missing.path}`));
2723
+ console.log(chalk4.red(` Missing file: ${missing.path}`));
3249
2724
  }
3250
2725
  const snapshotFailures = fixture.snapshotResults.filter((r) => r.status !== "match");
3251
2726
  for (const failure of snapshotFailures) {
3252
2727
  switch (failure.status) {
3253
2728
  case "mismatch":
3254
- console.log(chalk5.yellow(` Snapshot mismatch: ${failure.path}`));
2729
+ console.log(chalk4.yellow(` Snapshot mismatch: ${failure.path}`));
3255
2730
  break;
3256
2731
  case "missing-snapshot":
3257
- console.log(chalk5.yellow(` Missing snapshot: ${failure.path}`));
2732
+ console.log(chalk4.yellow(` Missing snapshot: ${failure.path}`));
3258
2733
  break;
3259
2734
  case "extra-snapshot":
3260
- console.log(chalk5.yellow(` Extra snapshot (not in output): ${failure.path}`));
2735
+ console.log(chalk4.yellow(` Extra snapshot (not in output): ${failure.path}`));
3261
2736
  break;
3262
2737
  }
3263
2738
  }
3264
2739
  }
3265
2740
 
3266
2741
  // src/core/template-test/runner.ts
3267
- import { mkdir as mkdir3, rm as rm4 } from "fs/promises";
2742
+ import { mkdir as mkdir2, rm as rm3 } from "fs/promises";
3268
2743
  import { tmpdir as tmpdir3 } from "os";
3269
- import { join as join11 } from "path";
2744
+ import { join as join10 } from "path";
3270
2745
 
3271
2746
  // 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";
2747
+ import { mkdir, readdir as readdir3, rm as rm2 } from "fs/promises";
2748
+ import { join as join9, relative as relative4 } from "path";
3274
2749
  async function walkRelative(dir, base) {
3275
2750
  const results = [];
3276
- const entries = await readdir4(dir, { withFileTypes: true });
2751
+ const entries = await readdir3(dir, { withFileTypes: true });
3277
2752
  for (const entry of entries) {
3278
- const fullPath = join10(dir, entry.name);
2753
+ const fullPath = join9(dir, entry.name);
3279
2754
  if (entry.isDirectory()) {
3280
2755
  const sub = await walkRelative(fullPath, base);
3281
2756
  results.push(...sub);
@@ -3292,8 +2767,8 @@ async function compareSnapshots(renderedDir, snapshotDir) {
3292
2767
  const snapshotSet = new Set(snapshotFiles);
3293
2768
  const renderedSet = new Set(renderedFiles);
3294
2769
  for (const file of renderedFiles) {
3295
- const renderedPath = join10(renderedDir, file);
3296
- const snapshotPath = join10(snapshotDir, file);
2770
+ const renderedPath = join9(renderedDir, file);
2771
+ const snapshotPath = join9(snapshotDir, file);
3297
2772
  if (!snapshotSet.has(file)) {
3298
2773
  results.push({ path: file, status: "missing-snapshot" });
3299
2774
  continue;
@@ -3314,13 +2789,13 @@ async function compareSnapshots(renderedDir, snapshotDir) {
3314
2789
  }
3315
2790
  async function updateSnapshots(renderedDir, snapshotDir) {
3316
2791
  if (await pathExists(snapshotDir)) {
3317
- await rm3(snapshotDir, { recursive: true, force: true });
2792
+ await rm2(snapshotDir, { recursive: true, force: true });
3318
2793
  }
3319
- await mkdir2(snapshotDir, { recursive: true });
2794
+ await mkdir(snapshotDir, { recursive: true });
3320
2795
  const files = await walkRelative(renderedDir, renderedDir);
3321
2796
  for (const file of files) {
3322
- const srcPath = join10(renderedDir, file);
3323
- const destPath = join10(snapshotDir, file);
2797
+ const srcPath = join9(renderedDir, file);
2798
+ const destPath = join9(snapshotDir, file);
3324
2799
  const content = await readTextFile(srcPath);
3325
2800
  await writeTextFile(destPath, content);
3326
2801
  }
@@ -3328,9 +2803,9 @@ async function updateSnapshots(renderedDir, snapshotDir) {
3328
2803
 
3329
2804
  // src/core/template-test/runner.ts
3330
2805
  async function runFixture(fixture, templatePath, options, presetDefinitions = {}) {
3331
- const tempDir = join11(tmpdir3(), `awa-test-${fixture.name}-${Date.now()}`);
2806
+ const tempDir = join10(tmpdir3(), `awa-test-${fixture.name}-${Date.now()}`);
3332
2807
  try {
3333
- await mkdir3(tempDir, { recursive: true });
2808
+ await mkdir2(tempDir, { recursive: true });
3334
2809
  const features = featureResolver.resolve({
3335
2810
  baseFeatures: [...fixture.features],
3336
2811
  presetNames: [...fixture.preset],
@@ -3347,12 +2822,12 @@ async function runFixture(fixture, templatePath, options, presetDefinitions = {}
3347
2822
  });
3348
2823
  const fileResults = [];
3349
2824
  for (const expectedFile of fixture.expectedFiles) {
3350
- const fullPath = join11(tempDir, expectedFile);
2825
+ const fullPath = join10(tempDir, expectedFile);
3351
2826
  const found = await pathExists(fullPath);
3352
2827
  fileResults.push({ path: expectedFile, found });
3353
2828
  }
3354
2829
  const missingFiles = fileResults.filter((r) => !r.found);
3355
- const snapshotDir = join11(templatePath, "_tests", fixture.name);
2830
+ const snapshotDir = join10(templatePath, "_tests", fixture.name);
3356
2831
  let snapshotResults = [];
3357
2832
  if (options.updateSnapshots) {
3358
2833
  await updateSnapshots(tempDir, snapshotDir);
@@ -3376,7 +2851,7 @@ async function runFixture(fixture, templatePath, options, presetDefinitions = {}
3376
2851
  error: error instanceof Error ? error.message : String(error)
3377
2852
  };
3378
2853
  } finally {
3379
- await rm4(tempDir, { recursive: true, force: true }).catch(() => {
2854
+ await rm3(tempDir, { recursive: true, force: true }).catch(() => {
3380
2855
  });
3381
2856
  }
3382
2857
  }
@@ -3434,6 +2909,91 @@ async function testCommand(options) {
3434
2909
  }
3435
2910
  }
3436
2911
 
2912
+ // src/utils/update-check.ts
2913
+ import chalk5 from "chalk";
2914
+ function compareSemver(a, b) {
2915
+ const partsA = a.split(".").map((s) => Number.parseInt(s, 10));
2916
+ const partsB = b.split(".").map((s) => Number.parseInt(s, 10));
2917
+ for (let i = 0; i < 3; i++) {
2918
+ const diff = (partsA[i] ?? 0) - (partsB[i] ?? 0);
2919
+ if (diff !== 0) return diff;
2920
+ }
2921
+ return 0;
2922
+ }
2923
+ function isMajorVersionBump(current, latest) {
2924
+ const currentMajor = Number.parseInt(current.split(".")[0] ?? "0", 10);
2925
+ const latestMajor = Number.parseInt(latest.split(".")[0] ?? "0", 10);
2926
+ return latestMajor > currentMajor;
2927
+ }
2928
+ async function checkForUpdate() {
2929
+ try {
2930
+ const response = await fetch("https://registry.npmjs.org/@ncoderz/awa/latest", {
2931
+ signal: AbortSignal.timeout(5e3)
2932
+ });
2933
+ if (!response.ok) return null;
2934
+ const data = await response.json();
2935
+ const latest = data.version;
2936
+ if (!latest || typeof latest !== "string") return null;
2937
+ const current = PACKAGE_INFO.version;
2938
+ const isOutdated = compareSemver(current, latest) < 0;
2939
+ return {
2940
+ current,
2941
+ latest,
2942
+ isOutdated,
2943
+ isMajorBump: isOutdated && isMajorVersionBump(current, latest)
2944
+ };
2945
+ } catch {
2946
+ return null;
2947
+ }
2948
+ }
2949
+ function printUpdateWarning(log, result) {
2950
+ if (!result.isOutdated) return;
2951
+ console.log("");
2952
+ if (result.isMajorBump) {
2953
+ log.warn(
2954
+ chalk5.yellow(
2955
+ `New major version available: ${result.current} \u2192 ${result.latest} (breaking changes)`
2956
+ )
2957
+ );
2958
+ log.warn(chalk5.dim(" See https://github.com/ncoderz/awa/releases for details"));
2959
+ } else {
2960
+ log.warn(chalk5.yellow(`Update available: ${result.current} \u2192 ${result.latest}`));
2961
+ }
2962
+ log.warn(chalk5.dim(" Run `npm install -g @ncoderz/awa` to update"));
2963
+ console.log("");
2964
+ }
2965
+
2966
+ // src/utils/update-check-cache.ts
2967
+ import { mkdir as mkdir3, readFile as readFile6, writeFile } from "fs/promises";
2968
+ import { homedir } from "os";
2969
+ import { dirname, join as join11 } from "path";
2970
+ var CACHE_DIR = join11(homedir(), ".cache", "awa");
2971
+ var CACHE_FILE = join11(CACHE_DIR, "update-check.json");
2972
+ var DEFAULT_INTERVAL_MS = 864e5;
2973
+ async function shouldCheck(intervalMs = DEFAULT_INTERVAL_MS) {
2974
+ try {
2975
+ const raw = await readFile6(CACHE_FILE, "utf-8");
2976
+ const data = JSON.parse(raw);
2977
+ if (typeof data.timestamp !== "number" || typeof data.latestVersion !== "string") {
2978
+ return true;
2979
+ }
2980
+ return Date.now() - data.timestamp >= intervalMs;
2981
+ } catch {
2982
+ return true;
2983
+ }
2984
+ }
2985
+ async function writeCache(latestVersion) {
2986
+ try {
2987
+ await mkdir3(dirname(CACHE_FILE), { recursive: true });
2988
+ const data = {
2989
+ timestamp: Date.now(),
2990
+ latestVersion
2991
+ };
2992
+ await writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
2993
+ } catch {
2994
+ }
2995
+ }
2996
+
3437
2997
  // src/cli/index.ts
3438
2998
  var version = PACKAGE_INFO.version;
3439
2999
  var program = new Command();
@@ -3491,7 +3051,7 @@ program.command("diff").description("Compare template output with existing targe
3491
3051
  });
3492
3052
  program.command("check").description(
3493
3053
  "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(
3054
+ ).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
3055
  "--allow-warnings",
3496
3056
  "Allow warnings without failing (default: warnings are errors)",
3497
3057
  false
@@ -3502,7 +3062,8 @@ program.command("check").description(
3502
3062
  ).action(async (options) => {
3503
3063
  const cliOptions = {
3504
3064
  config: options.config,
3505
- ignore: options.ignore,
3065
+ specIgnore: options.specIgnore,
3066
+ codeIgnore: options.codeIgnore,
3506
3067
  format: options.format,
3507
3068
  allowWarnings: options.allowWarnings,
3508
3069
  specOnly: options.specOnly
@@ -3528,5 +3089,41 @@ program.command("test").description("Run template test fixtures to verify expect
3528
3089
  const exitCode = await testCommand(testOptions);
3529
3090
  process.exit(exitCode);
3530
3091
  });
3531
- program.parse();
3092
+ var updateCheckPromise = null;
3093
+ var isJsonOrSummary = process.argv.includes("--json") || process.argv.includes("--summary");
3094
+ var isTTY = process.stdout.isTTY === true;
3095
+ var isDisabledByEnv = !!process.env.NO_UPDATE_NOTIFIER;
3096
+ if (!isJsonOrSummary && isTTY && !isDisabledByEnv) {
3097
+ updateCheckPromise = (async () => {
3098
+ try {
3099
+ const { configLoader: configLoader2 } = await import("./config-2TOQATI3.js");
3100
+ 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;
3101
+ const fileConfig = await configLoader2.load(configPath ?? null);
3102
+ const updateCheckConfig = fileConfig?.["update-check"];
3103
+ if (updateCheckConfig?.enabled === false) return null;
3104
+ const intervalSeconds = updateCheckConfig?.interval ?? 86400;
3105
+ const intervalMs = intervalSeconds * 1e3;
3106
+ const needsCheck = await shouldCheck(intervalMs);
3107
+ if (!needsCheck) return null;
3108
+ const result = await checkForUpdate();
3109
+ if (result) {
3110
+ await writeCache(result.latest);
3111
+ }
3112
+ return result;
3113
+ } catch {
3114
+ return null;
3115
+ }
3116
+ })();
3117
+ }
3118
+ program.hook("postAction", async () => {
3119
+ if (!updateCheckPromise) return;
3120
+ try {
3121
+ const result = await updateCheckPromise;
3122
+ if (result?.isOutdated) {
3123
+ printUpdateWarning(logger, result);
3124
+ }
3125
+ } catch {
3126
+ }
3127
+ });
3128
+ program.parseAsync();
3532
3129
  //# sourceMappingURL=index.js.map