@ncoderz/awa 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  rmDir,
17
17
  walkDirectory,
18
18
  writeTextFile
19
- } from "./chunk-3SSUJFKN.js";
19
+ } from "./chunk-ALEGCDAZ.js";
20
20
 
21
21
  // src/cli/index.ts
22
22
  import { Command } from "commander";
@@ -24,7 +24,7 @@ import { Command } from "commander";
24
24
  // src/_generated/package_info.ts
25
25
  var PACKAGE_INFO = {
26
26
  "name": "@ncoderz/awa",
27
- "version": "1.2.0",
27
+ "version": "1.4.0",
28
28
  "author": "Richard Sewell <richard.sewell@ncoderz.com>",
29
29
  "license": "MIT",
30
30
  "description": "awa is an Agent Workflow for AIs. It is also a CLI tool to powerfully manage agent workflow files using templates."
@@ -89,6 +89,38 @@ function checkCodeAgainstSpec(markers, specs, config) {
89
89
  });
90
90
  }
91
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
+ }
92
124
  return { findings };
93
125
  }
94
126
 
@@ -122,7 +154,7 @@ var MARKER_TYPE_MAP = {
122
154
  "@awa-test": "test",
123
155
  "@awa-component": "component"
124
156
  };
125
- var IGNORE_FILE_RE = /@awa-ignore-file\b/;
157
+ var IGNORE_FILE_RE = new RegExp("@awa-ignore-file\\b");
126
158
  var IGNORE_NEXT_LINE_RE = /@awa-ignore-next-line\b/;
127
159
  var IGNORE_LINE_RE = /@awa-ignore\b/;
128
160
  var IGNORE_START_RE = /@awa-ignore-start\b/;
@@ -1049,7 +1081,7 @@ async function parseSpecFile(filePath, crossRefPatterns) {
1049
1081
  const crossRefs = [];
1050
1082
  const idLocations = /* @__PURE__ */ new Map();
1051
1083
  const reqIdRegex = /^###\s+([A-Z][A-Z0-9]*-\d+(?:\.\d+)?)\s*:/;
1052
- 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/;
1053
1085
  const propIdRegex = /^-\s+([A-Z][A-Z0-9]*_P-\d+)\s/;
1054
1086
  const componentRegex = /^###\s+([A-Z][A-Z0-9]*-[A-Za-z][A-Za-z0-9]*(?:[A-Z][a-z0-9]*)*)\s*$/;
1055
1087
  for (const [i, line] of lines.entries()) {
@@ -2281,7 +2313,7 @@ async function prepareDiff(options) {
2281
2313
  throw new DiffError(`Target directory does not exist: ${options.output}`);
2282
2314
  }
2283
2315
  const targetPath = options.output;
2284
- const template = await templateResolver.resolve(options.template, options.refresh);
2316
+ const template2 = await templateResolver.resolve(options.template, options.refresh);
2285
2317
  const features = featureResolver.resolve({
2286
2318
  baseFeatures: [...options.features],
2287
2319
  presetNames: [...options.preset],
@@ -2289,10 +2321,10 @@ async function prepareDiff(options) {
2289
2321
  presetDefinitions: options.presets
2290
2322
  });
2291
2323
  let mergedDir = null;
2292
- let templatePath = template.localPath;
2324
+ let templatePath = template2.localPath;
2293
2325
  if (options.overlay.length > 0) {
2294
2326
  const overlayDirs = await resolveOverlays([...options.overlay], options.refresh);
2295
- mergedDir = await buildMergedDir(template.localPath, overlayDirs);
2327
+ mergedDir = await buildMergedDir(template2.localPath, overlayDirs);
2296
2328
  templatePath = mergedDir;
2297
2329
  }
2298
2330
  return {
@@ -2302,7 +2334,7 @@ async function prepareDiff(options) {
2302
2334
  features,
2303
2335
  listUnknown: options.listUnknown
2304
2336
  },
2305
- template,
2337
+ template: template2,
2306
2338
  mergedDir
2307
2339
  };
2308
2340
  }
@@ -2330,8 +2362,8 @@ async function diffCommand(cliOptions) {
2330
2362
  if (!silent) {
2331
2363
  intro("awa CLI - Template Diff");
2332
2364
  }
2333
- const { diffOptions, template, mergedDir } = await prepareDiff(options);
2334
- if (cliOptions.watch && template.type !== "local" && template.type !== "bundled") {
2365
+ const { diffOptions, template: template2, mergedDir } = await prepareDiff(options);
2366
+ if (cliOptions.watch && template2.type !== "local" && template2.type !== "bundled") {
2335
2367
  throw new DiffError("--watch is only supported with local template sources");
2336
2368
  }
2337
2369
  const result = await runDiff(diffOptions, options, mergedDir);
@@ -2341,11 +2373,11 @@ async function diffCommand(cliOptions) {
2341
2373
  }
2342
2374
  return result;
2343
2375
  }
2344
- logger.info(`Watching for changes in ${template.localPath}...`);
2376
+ logger.info(`Watching for changes in ${template2.localPath}...`);
2345
2377
  return new Promise((resolve2) => {
2346
2378
  let running = false;
2347
2379
  const watcher = new FileWatcher({
2348
- directory: template.localPath,
2380
+ directory: template2.localPath,
2349
2381
  onChange: async () => {
2350
2382
  if (running) return;
2351
2383
  running = true;
@@ -2507,8 +2539,8 @@ async function featuresCommand(cliOptions) {
2507
2539
  const fileConfig = await configLoader.load(cliOptions.config ?? null);
2508
2540
  const templateSource = cliOptions.template ?? fileConfig?.template ?? null;
2509
2541
  const refresh = cliOptions.refresh ?? fileConfig?.refresh ?? false;
2510
- const template = await templateResolver.resolve(templateSource, refresh);
2511
- const scanResult = await featureScanner.scan(template.localPath);
2542
+ const template2 = await templateResolver.resolve(templateSource, refresh);
2543
+ const scanResult = await featureScanner.scan(template2.localPath);
2512
2544
  const presets = fileConfig?.presets;
2513
2545
  featuresReporter.report({
2514
2546
  scanResult,
@@ -2548,7 +2580,7 @@ var TOOL_FEATURES = [
2548
2580
  var TOOL_FEATURE_VALUES = new Set(TOOL_FEATURES.map((t) => t.value));
2549
2581
  async function runGenerate(options, batchMode) {
2550
2582
  const silent = options.json || options.summary;
2551
- const template = await templateResolver.resolve(options.template, options.refresh);
2583
+ const template2 = await templateResolver.resolve(options.template, options.refresh);
2552
2584
  const features = featureResolver.resolve({
2553
2585
  baseFeatures: [...options.features],
2554
2586
  presetNames: [...options.preset],
@@ -2580,10 +2612,10 @@ async function runGenerate(options, batchMode) {
2580
2612
  }
2581
2613
  }
2582
2614
  let mergedDir = null;
2583
- let templatePath = template.localPath;
2615
+ let templatePath = template2.localPath;
2584
2616
  if (options.overlay.length > 0) {
2585
2617
  const overlayDirs = await resolveOverlays([...options.overlay], options.refresh);
2586
- mergedDir = await buildMergedDir(template.localPath, overlayDirs);
2618
+ mergedDir = await buildMergedDir(template2.localPath, overlayDirs);
2587
2619
  templatePath = mergedDir;
2588
2620
  }
2589
2621
  try {
@@ -2877,8 +2909,8 @@ async function testCommand(options) {
2877
2909
  intro4("awa CLI - Template Test");
2878
2910
  const fileConfig = await configLoader.load(options.config ?? null);
2879
2911
  const templateSource = options.template ?? fileConfig?.template ?? null;
2880
- const template = await templateResolver.resolve(templateSource, false);
2881
- const fixtures = await discoverFixtures(template.localPath);
2912
+ const template2 = await templateResolver.resolve(templateSource, false);
2913
+ const fixtures = await discoverFixtures(template2.localPath);
2882
2914
  if (fixtures.length === 0) {
2883
2915
  logger.warn("No test fixtures found in _tests/ directory");
2884
2916
  outro4("No tests to run.");
@@ -2888,7 +2920,7 @@ async function testCommand(options) {
2888
2920
  const presetDefinitions = fileConfig?.presets ?? {};
2889
2921
  const result = await runAll(
2890
2922
  fixtures,
2891
- template.localPath,
2923
+ template2.localPath,
2892
2924
  { updateSnapshots: options.updateSnapshots },
2893
2925
  presetDefinitions
2894
2926
  );
@@ -2909,6 +2941,1073 @@ async function testCommand(options) {
2909
2941
  }
2910
2942
  }
2911
2943
 
2944
+ // src/core/trace/content-assembler.ts
2945
+ import { readFile as readFile6 } from "fs/promises";
2946
+ var DEFAULT_BEFORE_CONTEXT = 5;
2947
+ var DEFAULT_AFTER_CONTEXT = 20;
2948
+ async function assembleContent(result, taskPath, contextOptions) {
2949
+ const beforeCtx = contextOptions?.beforeContext ?? DEFAULT_BEFORE_CONTEXT;
2950
+ const afterCtx = contextOptions?.afterContext ?? DEFAULT_AFTER_CONTEXT;
2951
+ const sections = [];
2952
+ const seen = /* @__PURE__ */ new Set();
2953
+ if (taskPath) {
2954
+ const content = await safeReadFile(taskPath);
2955
+ if (content) {
2956
+ const lineCount = content.split("\n").length;
2957
+ sections.push({
2958
+ type: "task",
2959
+ filePath: taskPath,
2960
+ startLine: 1,
2961
+ endLine: lineCount,
2962
+ content,
2963
+ priority: 1
2964
+ });
2965
+ }
2966
+ }
2967
+ for (const chain of result.chains) {
2968
+ if (chain.requirement) {
2969
+ const key = `req:${chain.requirement.location.filePath}:${chain.requirement.id}`;
2970
+ if (!seen.has(key)) {
2971
+ seen.add(key);
2972
+ const section = await extractSpecSection(
2973
+ chain.requirement.location.filePath,
2974
+ chain.requirement.id,
2975
+ chain.requirement.location.line,
2976
+ "requirement",
2977
+ 2
2978
+ );
2979
+ if (section) sections.push(section);
2980
+ }
2981
+ }
2982
+ for (const ac of chain.acs) {
2983
+ const key = `ac:${ac.location.filePath}:${ac.id}`;
2984
+ if (!seen.has(key)) {
2985
+ seen.add(key);
2986
+ if (!chain.requirement || ac.location.filePath !== chain.requirement.location.filePath) {
2987
+ const section = await extractSpecSection(
2988
+ ac.location.filePath,
2989
+ ac.id,
2990
+ ac.location.line,
2991
+ "requirement",
2992
+ 2
2993
+ );
2994
+ if (section) sections.push(section);
2995
+ }
2996
+ }
2997
+ }
2998
+ for (const comp of chain.designComponents) {
2999
+ const key = `design:${comp.location.filePath}:${comp.id}`;
3000
+ if (!seen.has(key)) {
3001
+ seen.add(key);
3002
+ const section = await extractSpecSection(
3003
+ comp.location.filePath,
3004
+ comp.id,
3005
+ comp.location.line,
3006
+ "design",
3007
+ 3
3008
+ );
3009
+ if (section) sections.push(section);
3010
+ }
3011
+ }
3012
+ for (const impl of chain.implementations) {
3013
+ const key = `impl:${impl.location.filePath}:${impl.location.line}`;
3014
+ if (!seen.has(key)) {
3015
+ seen.add(key);
3016
+ const section = await extractCodeSection(
3017
+ impl.location.filePath,
3018
+ impl.location.line,
3019
+ "implementation",
3020
+ 5,
3021
+ beforeCtx,
3022
+ afterCtx
3023
+ );
3024
+ if (section) sections.push(section);
3025
+ }
3026
+ }
3027
+ for (const t of chain.tests) {
3028
+ const key = `test:${t.location.filePath}:${t.location.line}`;
3029
+ if (!seen.has(key)) {
3030
+ seen.add(key);
3031
+ const section = await extractCodeSection(
3032
+ t.location.filePath,
3033
+ t.location.line,
3034
+ "test",
3035
+ 6,
3036
+ beforeCtx,
3037
+ afterCtx
3038
+ );
3039
+ if (section) sections.push(section);
3040
+ }
3041
+ }
3042
+ for (const prop of chain.properties) {
3043
+ const key = `prop:${prop.location.filePath}:${prop.id}`;
3044
+ if (!seen.has(key)) {
3045
+ seen.add(key);
3046
+ const section = await extractSpecSection(
3047
+ prop.location.filePath,
3048
+ prop.id,
3049
+ prop.location.line,
3050
+ "design",
3051
+ 3
3052
+ );
3053
+ if (section) sections.push(section);
3054
+ }
3055
+ }
3056
+ }
3057
+ sections.sort((a, b) => a.priority - b.priority);
3058
+ return sections;
3059
+ }
3060
+ async function extractSpecSection(filePath, _id, line, type, priority) {
3061
+ const content = await safeReadFile(filePath);
3062
+ if (!content) return null;
3063
+ const lines = content.split("\n");
3064
+ let startIdx = line - 1;
3065
+ while (startIdx >= 0 && !/^###\s/.test(lines[startIdx] ?? "")) {
3066
+ startIdx--;
3067
+ }
3068
+ if (startIdx < 0) startIdx = Math.max(0, line - 1);
3069
+ let endIdx = startIdx + 1;
3070
+ while (endIdx < lines.length) {
3071
+ const l = lines[endIdx] ?? "";
3072
+ if (/^#{2,3}\s/.test(l) && endIdx > startIdx) break;
3073
+ endIdx++;
3074
+ }
3075
+ const sectionContent = lines.slice(startIdx, endIdx).join("\n").trimEnd();
3076
+ return {
3077
+ type,
3078
+ filePath,
3079
+ startLine: startIdx + 1,
3080
+ endLine: endIdx,
3081
+ content: sectionContent,
3082
+ priority
3083
+ };
3084
+ }
3085
+ async function extractCodeSection(filePath, line, type, priority, beforeContext = DEFAULT_BEFORE_CONTEXT, afterContext = DEFAULT_AFTER_CONTEXT) {
3086
+ const content = await safeReadFile(filePath);
3087
+ if (!content) return null;
3088
+ const lines = content.split("\n");
3089
+ const lineIdx = line - 1;
3090
+ const range = findEnclosingBlock(lines, lineIdx, beforeContext, afterContext);
3091
+ const sectionContent = lines.slice(range.start, range.end + 1).join("\n").trimEnd();
3092
+ return {
3093
+ type,
3094
+ filePath,
3095
+ startLine: range.start + 1,
3096
+ endLine: range.end + 1,
3097
+ content: sectionContent,
3098
+ priority
3099
+ };
3100
+ }
3101
+ function findEnclosingBlock(lines, lineIdx, beforeContext = DEFAULT_BEFORE_CONTEXT, afterContext = DEFAULT_AFTER_CONTEXT) {
3102
+ let start = lineIdx;
3103
+ for (let i = lineIdx; i >= 0; i--) {
3104
+ const l = lines[i] ?? "";
3105
+ if (/^\s*(export\s+)?(async\s+)?function\s/.test(l) || /^\s*(export\s+)?(const|let|var)\s+\w+\s*=\s*(async\s+)?\(/.test(l) || /^\s*(describe|test|it)\s*\(/.test(l) || /^\s*(export\s+)?class\s/.test(l) || /^\s*(export\s+)?(const|let|var)\s+\w+\s*=\s*(async\s+)?function/.test(l)) {
3106
+ start = i;
3107
+ break;
3108
+ }
3109
+ if (lineIdx - i > 50) {
3110
+ start = Math.max(0, lineIdx - beforeContext);
3111
+ break;
3112
+ }
3113
+ }
3114
+ let braceCount = 0;
3115
+ let foundOpen = false;
3116
+ let end = lineIdx;
3117
+ for (let i = start; i < lines.length; i++) {
3118
+ const l = lines[i] ?? "";
3119
+ for (const ch of l) {
3120
+ if (ch === "{") {
3121
+ braceCount++;
3122
+ foundOpen = true;
3123
+ } else if (ch === "}") {
3124
+ braceCount--;
3125
+ }
3126
+ }
3127
+ if (foundOpen && braceCount <= 0) {
3128
+ end = i;
3129
+ break;
3130
+ }
3131
+ if (i - start > 100) {
3132
+ end = Math.min(lines.length - 1, lineIdx + afterContext);
3133
+ break;
3134
+ }
3135
+ }
3136
+ if (!foundOpen) {
3137
+ start = Math.max(0, lineIdx - beforeContext);
3138
+ end = Math.min(lines.length - 1, lineIdx + afterContext);
3139
+ }
3140
+ return { start, end };
3141
+ }
3142
+ async function safeReadFile(filePath) {
3143
+ try {
3144
+ return await readFile6(filePath, "utf-8");
3145
+ } catch {
3146
+ return null;
3147
+ }
3148
+ }
3149
+
3150
+ // src/core/trace/content-formatter.ts
3151
+ function langId(filePath) {
3152
+ if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
3153
+ if (filePath.endsWith(".js") || filePath.endsWith(".jsx")) return "javascript";
3154
+ if (filePath.endsWith(".md")) return "markdown";
3155
+ if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) return "yaml";
3156
+ if (filePath.endsWith(".json")) return "json";
3157
+ if (filePath.endsWith(".tsp")) return "typespec";
3158
+ return "";
3159
+ }
3160
+ function sectionHeading(type) {
3161
+ switch (type) {
3162
+ case "task":
3163
+ return "Task";
3164
+ case "requirement":
3165
+ return "Requirement";
3166
+ case "design":
3167
+ return "Design";
3168
+ case "implementation":
3169
+ return "Implementation";
3170
+ case "test":
3171
+ return "Test";
3172
+ default:
3173
+ return "Other";
3174
+ }
3175
+ }
3176
+ function formatContentMarkdown(sections, queryLabel, footer) {
3177
+ const lines = [];
3178
+ lines.push(`# Context: ${queryLabel}`);
3179
+ lines.push("");
3180
+ let lastType = null;
3181
+ for (const section of sections) {
3182
+ if (section.type !== lastType) {
3183
+ lines.push(`## ${sectionHeading(section.type)}`);
3184
+ lines.push("");
3185
+ lastType = section.type;
3186
+ }
3187
+ lines.push(`> From: ${section.filePath} (lines ${section.startLine}-${section.endLine})`);
3188
+ lines.push("");
3189
+ const isCode = section.type === "implementation" || section.type === "test";
3190
+ if (isCode) {
3191
+ const lang = langId(section.filePath);
3192
+ lines.push(`\`\`\`${lang}`);
3193
+ lines.push(section.content);
3194
+ lines.push("```");
3195
+ } else {
3196
+ lines.push(section.content);
3197
+ }
3198
+ lines.push("");
3199
+ }
3200
+ if (footer) {
3201
+ lines.push(`---`);
3202
+ lines.push(footer);
3203
+ lines.push("");
3204
+ }
3205
+ return lines.join("\n");
3206
+ }
3207
+ function formatContentJson(sections, queryLabel, footer) {
3208
+ const result = {
3209
+ query: queryLabel,
3210
+ sections: sections.map((s) => ({
3211
+ type: s.type,
3212
+ filePath: s.filePath,
3213
+ startLine: s.startLine,
3214
+ endLine: s.endLine,
3215
+ content: s.content,
3216
+ priority: s.priority
3217
+ })),
3218
+ estimatedTokens: Math.ceil(sections.reduce((sum, s) => sum + s.content.length, 0) / 4),
3219
+ filesIncluded: new Set(sections.map((s) => s.filePath)).size,
3220
+ ...footer ? { truncated: true, truncationMessage: footer } : {}
3221
+ };
3222
+ return JSON.stringify(result, null, 2);
3223
+ }
3224
+
3225
+ // src/core/trace/formatter.ts
3226
+ function formatTree(result) {
3227
+ const lines = [];
3228
+ for (const chain of result.chains) {
3229
+ lines.push(...formatChainTree(chain));
3230
+ lines.push("");
3231
+ }
3232
+ for (const id of result.notFound) {
3233
+ lines.push(`\u2717 ${id}: not found`);
3234
+ }
3235
+ return lines.join("\n").trimEnd();
3236
+ }
3237
+ function formatChainTree(chain) {
3238
+ const lines = [];
3239
+ const queryId = chain.queryId;
3240
+ lines.push(`${queryId}`);
3241
+ if (chain.requirement) {
3242
+ lines.push("");
3243
+ lines.push(" \u25B2 Requirement");
3244
+ const r = chain.requirement;
3245
+ lines.push(` \u2502 ${r.id} (${r.location.filePath}:${r.location.line})`);
3246
+ }
3247
+ if (chain.acs.length > 0) {
3248
+ lines.push("");
3249
+ lines.push(" \u25BC Acceptance Criteria");
3250
+ for (const ac of chain.acs) {
3251
+ lines.push(` \u2502 ${ac.id} (${ac.location.filePath}:${ac.location.line})`);
3252
+ }
3253
+ }
3254
+ if (chain.designComponents.length > 0) {
3255
+ lines.push("");
3256
+ lines.push(" \u25BC Design");
3257
+ for (const comp of chain.designComponents) {
3258
+ lines.push(` \u2502 ${comp.id} (${comp.location.filePath}:${comp.location.line})`);
3259
+ }
3260
+ }
3261
+ if (chain.properties.length > 0) {
3262
+ lines.push("");
3263
+ lines.push(" \u25BC Properties");
3264
+ for (const prop of chain.properties) {
3265
+ lines.push(` \u2502 ${prop.id} (${prop.location.filePath}:${prop.location.line})`);
3266
+ }
3267
+ }
3268
+ if (chain.implementations.length > 0) {
3269
+ lines.push("");
3270
+ lines.push(" \u25BC Implementation");
3271
+ for (const impl of chain.implementations) {
3272
+ lines.push(
3273
+ ` \u2502 ${impl.location.filePath}:${impl.location.line} (@awa-${impl.type === "implementation" ? "impl" : "component"}: ${impl.id})`
3274
+ );
3275
+ }
3276
+ }
3277
+ if (chain.tests.length > 0) {
3278
+ lines.push("");
3279
+ lines.push(" \u25BC Tests");
3280
+ for (const t of chain.tests) {
3281
+ lines.push(` \u2502 ${t.location.filePath}:${t.location.line} (@awa-test: ${t.id})`);
3282
+ }
3283
+ }
3284
+ return lines;
3285
+ }
3286
+ function formatList(result) {
3287
+ const paths = /* @__PURE__ */ new Set();
3288
+ for (const chain of result.chains) {
3289
+ if (chain.requirement) {
3290
+ paths.add(`${chain.requirement.location.filePath}:${chain.requirement.location.line}`);
3291
+ }
3292
+ for (const ac of chain.acs) {
3293
+ paths.add(`${ac.location.filePath}:${ac.location.line}`);
3294
+ }
3295
+ for (const comp of chain.designComponents) {
3296
+ paths.add(`${comp.location.filePath}:${comp.location.line}`);
3297
+ }
3298
+ for (const prop of chain.properties) {
3299
+ paths.add(`${prop.location.filePath}:${prop.location.line}`);
3300
+ }
3301
+ for (const impl of chain.implementations) {
3302
+ paths.add(`${impl.location.filePath}:${impl.location.line}`);
3303
+ }
3304
+ for (const t of chain.tests) {
3305
+ paths.add(`${t.location.filePath}:${t.location.line}`);
3306
+ }
3307
+ }
3308
+ return [...paths].join("\n");
3309
+ }
3310
+ function formatJson(result) {
3311
+ const output = {
3312
+ chains: result.chains.map((chain) => ({
3313
+ queryId: chain.queryId,
3314
+ requirement: chain.requirement ? {
3315
+ id: chain.requirement.id,
3316
+ filePath: chain.requirement.location.filePath,
3317
+ line: chain.requirement.location.line
3318
+ } : null,
3319
+ acs: chain.acs.map((ac) => ({
3320
+ id: ac.id,
3321
+ filePath: ac.location.filePath,
3322
+ line: ac.location.line
3323
+ })),
3324
+ designComponents: chain.designComponents.map((c) => ({
3325
+ id: c.id,
3326
+ filePath: c.location.filePath,
3327
+ line: c.location.line
3328
+ })),
3329
+ implementations: chain.implementations.map((i) => ({
3330
+ id: i.id,
3331
+ filePath: i.location.filePath,
3332
+ line: i.location.line
3333
+ })),
3334
+ tests: chain.tests.map((t) => ({
3335
+ id: t.id,
3336
+ filePath: t.location.filePath,
3337
+ line: t.location.line
3338
+ })),
3339
+ properties: chain.properties.map((p) => ({
3340
+ id: p.id,
3341
+ filePath: p.location.filePath,
3342
+ line: p.location.line
3343
+ }))
3344
+ })),
3345
+ notFound: result.notFound
3346
+ };
3347
+ return JSON.stringify(output, null, 2);
3348
+ }
3349
+
3350
+ // src/core/trace/index-builder.ts
3351
+ function buildTraceIndex(specs, markers) {
3352
+ const reqToACs = /* @__PURE__ */ new Map();
3353
+ const acToDesignComponents = /* @__PURE__ */ new Map();
3354
+ const acToCodeLocations = /* @__PURE__ */ new Map();
3355
+ const acToTestLocations = /* @__PURE__ */ new Map();
3356
+ const propertyToTestLocations = /* @__PURE__ */ new Map();
3357
+ const componentToCodeLocations = /* @__PURE__ */ new Map();
3358
+ const acToReq = /* @__PURE__ */ new Map();
3359
+ const componentToACs = /* @__PURE__ */ new Map();
3360
+ const propertyToACs = /* @__PURE__ */ new Map();
3361
+ buildRequirementMaps(specs, reqToACs, acToReq);
3362
+ buildDesignMaps(specs, acToDesignComponents, componentToACs, propertyToACs);
3363
+ buildMarkerMaps(
3364
+ markers.markers,
3365
+ acToCodeLocations,
3366
+ acToTestLocations,
3367
+ propertyToTestLocations,
3368
+ componentToCodeLocations
3369
+ );
3370
+ const allIds = /* @__PURE__ */ new Set([
3371
+ ...specs.requirementIds,
3372
+ ...specs.acIds,
3373
+ ...specs.propertyIds,
3374
+ ...specs.componentNames
3375
+ ]);
3376
+ const idLocations = /* @__PURE__ */ new Map();
3377
+ for (const [id, loc] of specs.idLocations) {
3378
+ idLocations.set(id, loc);
3379
+ }
3380
+ return {
3381
+ reqToACs,
3382
+ acToDesignComponents,
3383
+ acToCodeLocations,
3384
+ acToTestLocations,
3385
+ propertyToTestLocations,
3386
+ componentToCodeLocations,
3387
+ acToReq,
3388
+ componentToACs,
3389
+ propertyToACs,
3390
+ idLocations,
3391
+ allIds
3392
+ };
3393
+ }
3394
+ function buildRequirementMaps(specs, reqToACs, acToReq) {
3395
+ for (const acId of specs.acIds) {
3396
+ const parentReq = extractParentRequirement(acId);
3397
+ if (parentReq && specs.requirementIds.has(parentReq)) {
3398
+ pushToMap(reqToACs, parentReq, acId);
3399
+ acToReq.set(acId, parentReq);
3400
+ }
3401
+ }
3402
+ for (const reqId of specs.requirementIds) {
3403
+ const dotIdx = reqId.lastIndexOf(".");
3404
+ if (dotIdx !== -1) {
3405
+ const parentReq = reqId.slice(0, dotIdx);
3406
+ if (specs.requirementIds.has(parentReq)) {
3407
+ }
3408
+ }
3409
+ }
3410
+ }
3411
+ function buildDesignMaps(specs, acToDesignComponents, componentToACs, propertyToACs) {
3412
+ for (const specFile of specs.specFiles) {
3413
+ if (specFile.componentNames.length === 0 && specFile.crossRefs.length === 0) {
3414
+ continue;
3415
+ }
3416
+ const componentsByLine = specFile.componentNames.map((name) => ({
3417
+ name,
3418
+ line: specFile.idLocations?.get(name)?.line ?? 0
3419
+ })).sort((a, b) => a.line - b.line);
3420
+ for (const crossRef of specFile.crossRefs) {
3421
+ const ownerComponent = findOwnerComponent(componentsByLine, crossRef.line);
3422
+ if (crossRef.type === "implements" && ownerComponent) {
3423
+ for (const acId of crossRef.ids) {
3424
+ pushToMap(acToDesignComponents, acId, ownerComponent);
3425
+ pushToMap(componentToACs, ownerComponent, acId);
3426
+ }
3427
+ }
3428
+ if (crossRef.type === "validates") {
3429
+ const ownerProperty = findOwnerProperty(specFile, crossRef.line);
3430
+ if (ownerProperty) {
3431
+ for (const acId of crossRef.ids) {
3432
+ pushToMap(propertyToACs, ownerProperty, acId);
3433
+ }
3434
+ }
3435
+ }
3436
+ }
3437
+ }
3438
+ }
3439
+ function buildMarkerMaps(markers, acToCodeLocations, acToTestLocations, propertyToTestLocations, componentToCodeLocations) {
3440
+ for (const marker of markers) {
3441
+ const loc = { filePath: marker.filePath, line: marker.line };
3442
+ switch (marker.type) {
3443
+ case "impl":
3444
+ pushToMap(acToCodeLocations, marker.id, loc);
3445
+ break;
3446
+ case "test":
3447
+ if (marker.id.includes("_P-")) {
3448
+ pushToMap(propertyToTestLocations, marker.id, loc);
3449
+ } else {
3450
+ pushToMap(acToTestLocations, marker.id, loc);
3451
+ }
3452
+ break;
3453
+ case "component":
3454
+ pushToMap(componentToCodeLocations, marker.id, loc);
3455
+ break;
3456
+ }
3457
+ }
3458
+ }
3459
+ function extractParentRequirement(acId) {
3460
+ const idx = acId.indexOf("_AC-");
3461
+ return idx !== -1 ? acId.slice(0, idx) : null;
3462
+ }
3463
+ function findOwnerComponent(componentsByLine, line) {
3464
+ let owner = null;
3465
+ for (const comp of componentsByLine) {
3466
+ if (comp.line <= line) {
3467
+ owner = comp.name;
3468
+ } else {
3469
+ break;
3470
+ }
3471
+ }
3472
+ return owner;
3473
+ }
3474
+ function findOwnerProperty(specFile, validateLine) {
3475
+ let owner = null;
3476
+ let closestLine = 0;
3477
+ for (const propId of specFile.propertyIds) {
3478
+ const loc = specFile.idLocations?.get(propId);
3479
+ if (loc && loc.line <= validateLine && loc.line > closestLine) {
3480
+ closestLine = loc.line;
3481
+ owner = propId;
3482
+ }
3483
+ }
3484
+ return owner;
3485
+ }
3486
+ function pushToMap(map, key, value) {
3487
+ const existing = map.get(key);
3488
+ if (existing) {
3489
+ existing.push(value);
3490
+ } else {
3491
+ map.set(key, [value]);
3492
+ }
3493
+ }
3494
+
3495
+ // src/core/trace/input-resolver.ts
3496
+ import { readFile as readFile7 } from "fs/promises";
3497
+ function resolveIds(ids, index) {
3498
+ const resolved = [];
3499
+ const warnings = [];
3500
+ for (const id of ids) {
3501
+ if (index.allIds.has(id)) {
3502
+ resolved.push(id);
3503
+ } else {
3504
+ warnings.push(`ID '${id}' not found in any spec or code`);
3505
+ }
3506
+ }
3507
+ return { ids: resolved, warnings };
3508
+ }
3509
+ async function resolveTaskFile(taskPath, index) {
3510
+ let content;
3511
+ try {
3512
+ content = await readFile7(taskPath, "utf-8");
3513
+ } catch {
3514
+ return { ids: [], warnings: [`Task file not found: ${taskPath}`] };
3515
+ }
3516
+ const ids = /* @__PURE__ */ new Set();
3517
+ const warnings = [];
3518
+ const lines = content.split("\n");
3519
+ const idRegex = /[A-Z][A-Z0-9]*-\d+(?:\.\d+)?(?:_AC-\d+)?|[A-Z][A-Z0-9]*_P-\d+/g;
3520
+ for (const line of lines) {
3521
+ if (/^\s*IMPLEMENTS:/.test(line) || /^\s*TESTS:/.test(line)) {
3522
+ let match = idRegex.exec(line);
3523
+ while (match !== null) {
3524
+ ids.add(match[0]);
3525
+ match = idRegex.exec(line);
3526
+ }
3527
+ }
3528
+ }
3529
+ const traceLineRegex = /^- ([A-Z][A-Z0-9]*(?:-\d+(?:\.\d+)?(?:_AC-\d+)?|_P-\d+))/;
3530
+ let inTraceSection = false;
3531
+ for (const line of lines) {
3532
+ if (/^## Requirements Traceability/.test(line)) {
3533
+ inTraceSection = true;
3534
+ continue;
3535
+ }
3536
+ if (inTraceSection && /^## /.test(line)) {
3537
+ inTraceSection = false;
3538
+ continue;
3539
+ }
3540
+ if (inTraceSection) {
3541
+ const traceMatch = traceLineRegex.exec(line);
3542
+ if (traceMatch?.[1]) {
3543
+ ids.add(traceMatch[1]);
3544
+ }
3545
+ }
3546
+ }
3547
+ const resolved = [];
3548
+ for (const id of ids) {
3549
+ if (index.allIds.has(id)) {
3550
+ resolved.push(id);
3551
+ } else {
3552
+ warnings.push(`ID '${id}' from task file not found in specs`);
3553
+ }
3554
+ }
3555
+ if (resolved.length === 0 && warnings.length === 0) {
3556
+ warnings.push(`No traceability IDs found in task file: ${taskPath}`);
3557
+ }
3558
+ return { ids: resolved, warnings };
3559
+ }
3560
+ async function resolveSourceFile(filePath, index) {
3561
+ let content;
3562
+ try {
3563
+ content = await readFile7(filePath, "utf-8");
3564
+ } catch {
3565
+ return { ids: [], warnings: [`Source file not found: ${filePath}`] };
3566
+ }
3567
+ const ids = /* @__PURE__ */ new Set();
3568
+ const warnings = [];
3569
+ const markerRegex = /@awa-(?:impl|test|component):\s*(.+)/g;
3570
+ const idTokenRegex = /[A-Z][A-Z0-9]*(?:[-_][A-Za-z0-9]+)*(?:\.\d+)?/g;
3571
+ const lines = content.split("\n");
3572
+ for (const line of lines) {
3573
+ let markerMatch = markerRegex.exec(line);
3574
+ while (markerMatch !== null) {
3575
+ const idsText = markerMatch[1] ?? "";
3576
+ let idMatch = idTokenRegex.exec(idsText);
3577
+ while (idMatch !== null) {
3578
+ ids.add(idMatch[0]);
3579
+ idMatch = idTokenRegex.exec(idsText);
3580
+ }
3581
+ markerMatch = markerRegex.exec(line);
3582
+ }
3583
+ }
3584
+ const resolved = [];
3585
+ for (const id of ids) {
3586
+ if (index.allIds.has(id)) {
3587
+ resolved.push(id);
3588
+ } else {
3589
+ warnings.push(`Marker ID '${id}' not found in specs`);
3590
+ }
3591
+ }
3592
+ if (resolved.length === 0 && warnings.length === 0) {
3593
+ warnings.push(`No traceability markers found in file: ${filePath}`);
3594
+ }
3595
+ return { ids: resolved, warnings };
3596
+ }
3597
+
3598
+ // src/core/trace/resolver.ts
3599
+ function resolveTrace(index, ids, options) {
3600
+ const chains = [];
3601
+ const notFound = [];
3602
+ for (const id of ids) {
3603
+ if (!index.allIds.has(id)) {
3604
+ notFound.push(id);
3605
+ continue;
3606
+ }
3607
+ const chain = resolveChain(index, id, options);
3608
+ if (chain) {
3609
+ chains.push(chain);
3610
+ }
3611
+ }
3612
+ return { chains, notFound };
3613
+ }
3614
+ function resolveChain(index, id, options) {
3615
+ const idType = detectIdType(id);
3616
+ const scope = options.scope;
3617
+ let requirement;
3618
+ let acs = [];
3619
+ let designComponents = [];
3620
+ let implementations = [];
3621
+ let tests = [];
3622
+ let properties = [];
3623
+ switch (idType) {
3624
+ case "requirement":
3625
+ resolveFromRequirement(index, id, options, {
3626
+ requirement: (n) => {
3627
+ requirement = n;
3628
+ },
3629
+ acs,
3630
+ designComponents,
3631
+ implementations,
3632
+ tests,
3633
+ properties
3634
+ });
3635
+ break;
3636
+ case "ac":
3637
+ resolveFromAC(index, id, options, {
3638
+ requirement: (n) => {
3639
+ requirement = n;
3640
+ },
3641
+ acs,
3642
+ designComponents,
3643
+ implementations,
3644
+ tests,
3645
+ properties
3646
+ });
3647
+ break;
3648
+ case "property":
3649
+ resolveFromProperty(index, id, options, {
3650
+ requirement: (n) => {
3651
+ requirement = n;
3652
+ },
3653
+ acs,
3654
+ designComponents,
3655
+ implementations,
3656
+ tests,
3657
+ properties
3658
+ });
3659
+ break;
3660
+ case "component":
3661
+ resolveFromComponent(index, id, options, {
3662
+ requirement: (n) => {
3663
+ requirement = n;
3664
+ },
3665
+ acs,
3666
+ designComponents,
3667
+ implementations,
3668
+ tests,
3669
+ properties
3670
+ });
3671
+ break;
3672
+ }
3673
+ if (scope) {
3674
+ acs = acs.filter((n) => n.id.startsWith(scope));
3675
+ designComponents = designComponents.filter((n) => n.id.startsWith(scope));
3676
+ implementations = implementations.filter((n) => matchesScope(n, scope, index));
3677
+ tests = tests.filter((n) => matchesScope(n, scope, index));
3678
+ properties = properties.filter((n) => n.id.startsWith(scope));
3679
+ if (requirement && !requirement.id.startsWith(scope)) {
3680
+ requirement = void 0;
3681
+ }
3682
+ }
3683
+ if (options.noCode) {
3684
+ implementations = [];
3685
+ }
3686
+ if (options.noTests) {
3687
+ tests = [];
3688
+ properties = [];
3689
+ }
3690
+ return {
3691
+ queryId: id,
3692
+ requirement,
3693
+ acs: deduplicateNodes(acs),
3694
+ designComponents: deduplicateNodes(designComponents),
3695
+ implementations: deduplicateNodes(implementations),
3696
+ tests: deduplicateNodes(tests),
3697
+ properties: deduplicateNodes(properties)
3698
+ };
3699
+ }
3700
+ function resolveFromRequirement(index, reqId, options, collectors) {
3701
+ const loc = index.idLocations.get(reqId);
3702
+ if (loc) {
3703
+ collectors.requirement({ id: reqId, type: "requirement", location: loc });
3704
+ }
3705
+ if (options.direction === "reverse") return;
3706
+ const acIds = index.reqToACs.get(reqId) ?? [];
3707
+ for (const acId of acIds) {
3708
+ const acLoc = index.idLocations.get(acId);
3709
+ if (acLoc) {
3710
+ collectors.acs.push({ id: acId, type: "ac", location: acLoc });
3711
+ }
3712
+ if (withinDepth(options.depth, 1)) {
3713
+ resolveACDownstream(index, acId, options, collectors, 1);
3714
+ }
3715
+ }
3716
+ }
3717
+ function resolveFromAC(index, acId, options, collectors) {
3718
+ const acLoc = index.idLocations.get(acId);
3719
+ if (acLoc) {
3720
+ collectors.acs.push({ id: acId, type: "ac", location: acLoc });
3721
+ }
3722
+ if (options.direction !== "forward") {
3723
+ const reqId = index.acToReq.get(acId);
3724
+ if (reqId) {
3725
+ const reqLoc = index.idLocations.get(reqId);
3726
+ if (reqLoc) {
3727
+ collectors.requirement({ id: reqId, type: "requirement", location: reqLoc });
3728
+ }
3729
+ }
3730
+ }
3731
+ if (options.direction !== "reverse") {
3732
+ resolveACDownstream(index, acId, options, collectors, 0);
3733
+ }
3734
+ }
3735
+ function resolveFromProperty(index, propId, options, collectors) {
3736
+ const propLoc = index.idLocations.get(propId);
3737
+ if (propLoc) {
3738
+ collectors.properties.push({ id: propId, type: "property", location: propLoc });
3739
+ }
3740
+ if (options.direction !== "reverse") {
3741
+ const testLocs = index.propertyToTestLocations.get(propId) ?? [];
3742
+ for (const loc of testLocs) {
3743
+ collectors.tests.push({ id: propId, type: "test", location: loc });
3744
+ }
3745
+ }
3746
+ if (options.direction !== "forward") {
3747
+ const acIds = index.propertyToACs.get(propId) ?? [];
3748
+ for (const acId of acIds) {
3749
+ const acLoc = index.idLocations.get(acId);
3750
+ if (acLoc) {
3751
+ collectors.acs.push({ id: acId, type: "ac", location: acLoc });
3752
+ }
3753
+ if (withinDepth(options.depth, 1)) {
3754
+ const reqId = index.acToReq.get(acId);
3755
+ if (reqId) {
3756
+ const reqLoc = index.idLocations.get(reqId);
3757
+ if (reqLoc) {
3758
+ collectors.requirement({ id: reqId, type: "requirement", location: reqLoc });
3759
+ }
3760
+ }
3761
+ }
3762
+ }
3763
+ }
3764
+ }
3765
+ function resolveFromComponent(index, componentId, options, collectors) {
3766
+ const compLoc = index.idLocations.get(componentId);
3767
+ if (compLoc) {
3768
+ collectors.designComponents.push({ id: componentId, type: "component", location: compLoc });
3769
+ }
3770
+ if (options.direction !== "reverse") {
3771
+ const codeLocs = index.componentToCodeLocations.get(componentId) ?? [];
3772
+ for (const loc of codeLocs) {
3773
+ collectors.implementations.push({
3774
+ id: componentId,
3775
+ type: "implementation",
3776
+ location: loc
3777
+ });
3778
+ }
3779
+ }
3780
+ if (options.direction !== "forward") {
3781
+ const acIds = index.componentToACs.get(componentId) ?? [];
3782
+ for (const acId of acIds) {
3783
+ const acLoc = index.idLocations.get(acId);
3784
+ if (acLoc) {
3785
+ collectors.acs.push({ id: acId, type: "ac", location: acLoc });
3786
+ }
3787
+ if (withinDepth(options.depth, 1)) {
3788
+ const reqId = index.acToReq.get(acId);
3789
+ if (reqId) {
3790
+ const reqLoc = index.idLocations.get(reqId);
3791
+ if (reqLoc) {
3792
+ collectors.requirement({ id: reqId, type: "requirement", location: reqLoc });
3793
+ }
3794
+ }
3795
+ }
3796
+ }
3797
+ }
3798
+ }
3799
+ function resolveACDownstream(index, acId, options, collectors, currentDepth) {
3800
+ const components = index.acToDesignComponents.get(acId) ?? [];
3801
+ for (const comp of components) {
3802
+ const compLoc = index.idLocations.get(comp);
3803
+ if (compLoc) {
3804
+ collectors.designComponents.push({ id: comp, type: "component", location: compLoc });
3805
+ }
3806
+ }
3807
+ if (!withinDepth(options.depth, currentDepth + 1)) return;
3808
+ const codeLocs = index.acToCodeLocations.get(acId) ?? [];
3809
+ for (const loc of codeLocs) {
3810
+ collectors.implementations.push({ id: acId, type: "implementation", location: loc });
3811
+ }
3812
+ const testLocs = index.acToTestLocations.get(acId) ?? [];
3813
+ for (const loc of testLocs) {
3814
+ collectors.tests.push({ id: acId, type: "test", location: loc });
3815
+ }
3816
+ }
3817
+ function detectIdType(id) {
3818
+ if (id.includes("_AC-")) return "ac";
3819
+ if (id.includes("_P-")) return "property";
3820
+ if (/^[A-Z][A-Z0-9]*-\d+/.test(id)) return "requirement";
3821
+ return "component";
3822
+ }
3823
+ function withinDepth(maxDepth, current) {
3824
+ if (maxDepth === void 0) return true;
3825
+ return current < maxDepth;
3826
+ }
3827
+ function matchesScope(node, scope, _index) {
3828
+ return node.id.startsWith(scope);
3829
+ }
3830
+ function deduplicateNodes(nodes) {
3831
+ const seen = /* @__PURE__ */ new Set();
3832
+ const result = [];
3833
+ for (const node of nodes) {
3834
+ const key = `${node.id}:${node.location.filePath}:${node.location.line}`;
3835
+ if (!seen.has(key)) {
3836
+ seen.add(key);
3837
+ result.push(node);
3838
+ }
3839
+ }
3840
+ return result;
3841
+ }
3842
+
3843
+ // src/core/trace/scanner.ts
3844
+ function buildScanConfig(fileConfig, overrides) {
3845
+ const section = fileConfig?.check;
3846
+ return {
3847
+ specGlobs: toStringArray3(section?.["spec-globs"]) ?? [...DEFAULT_CHECK_CONFIG.specGlobs],
3848
+ codeGlobs: toStringArray3(section?.["code-globs"]) ?? [...DEFAULT_CHECK_CONFIG.codeGlobs],
3849
+ specIgnore: toStringArray3(section?.["spec-ignore"]) ?? [...DEFAULT_CHECK_CONFIG.specIgnore],
3850
+ codeIgnore: toStringArray3(section?.["code-ignore"]) ?? [...DEFAULT_CHECK_CONFIG.codeIgnore],
3851
+ ignoreMarkers: toStringArray3(section?.["ignore-markers"]) ?? [
3852
+ ...DEFAULT_CHECK_CONFIG.ignoreMarkers
3853
+ ],
3854
+ markers: toStringArray3(section?.markers) ?? [...DEFAULT_CHECK_CONFIG.markers],
3855
+ idPattern: typeof section?.["id-pattern"] === "string" ? section["id-pattern"] : DEFAULT_CHECK_CONFIG.idPattern,
3856
+ crossRefPatterns: toStringArray3(section?.["cross-ref-patterns"]) ?? [
3857
+ ...DEFAULT_CHECK_CONFIG.crossRefPatterns
3858
+ ],
3859
+ format: DEFAULT_CHECK_CONFIG.format,
3860
+ schemaDir: DEFAULT_CHECK_CONFIG.schemaDir,
3861
+ schemaEnabled: false,
3862
+ allowWarnings: true,
3863
+ specOnly: false,
3864
+ ...overrides
3865
+ };
3866
+ }
3867
+ async function scan(configPath) {
3868
+ const fileConfig = await configLoader.load(configPath ?? null);
3869
+ const config = buildScanConfig(fileConfig);
3870
+ const [markers, specs] = await Promise.all([scanMarkers(config), parseSpecs(config)]);
3871
+ return { markers, specs, config };
3872
+ }
3873
+ function toStringArray3(value) {
3874
+ if (Array.isArray(value) && value.every((v) => typeof v === "string")) {
3875
+ return value;
3876
+ }
3877
+ return null;
3878
+ }
3879
+
3880
+ // src/core/trace/token-estimator.ts
3881
+ function estimateTokens(text) {
3882
+ return Math.ceil(text.length / 4);
3883
+ }
3884
+ function applyTokenBudget(sections, maxTokens) {
3885
+ if (maxTokens <= 0) {
3886
+ return { sections: [], truncated: sections.length > 0, footer: null };
3887
+ }
3888
+ const result = [];
3889
+ let usedTokens = 0;
3890
+ let omittedCount = 0;
3891
+ for (const section of sections) {
3892
+ const sectionTokens = estimateTokens(section.content);
3893
+ if (usedTokens + sectionTokens <= maxTokens) {
3894
+ result.push(section);
3895
+ usedTokens += sectionTokens;
3896
+ } else if (result.length === 0) {
3897
+ const availableChars = maxTokens * 4;
3898
+ const truncatedContent = section.content.slice(0, availableChars);
3899
+ result.push({ ...section, content: truncatedContent });
3900
+ usedTokens = maxTokens;
3901
+ omittedCount = sections.length - 1;
3902
+ break;
3903
+ } else {
3904
+ omittedCount++;
3905
+ }
3906
+ }
3907
+ if (omittedCount === 0) {
3908
+ omittedCount = sections.length - result.length;
3909
+ }
3910
+ const truncated = omittedCount > 0;
3911
+ const footer = truncated ? `... ${omittedCount} more section${omittedCount === 1 ? "" : "s"} omitted (use --max-tokens to increase)` : null;
3912
+ return { sections: result, truncated, footer };
3913
+ }
3914
+
3915
+ // src/commands/trace.ts
3916
+ async function traceCommand(options) {
3917
+ try {
3918
+ const { markers, specs } = await scan(options.config);
3919
+ const index = buildTraceIndex(specs, markers);
3920
+ let ids;
3921
+ let warnings = [];
3922
+ if (options.all) {
3923
+ ids = [...index.allIds];
3924
+ } else if (options.task) {
3925
+ const resolved = await resolveTaskFile(options.task, index);
3926
+ ids = resolved.ids;
3927
+ warnings = resolved.warnings;
3928
+ } else if (options.file) {
3929
+ const resolved = await resolveSourceFile(options.file, index);
3930
+ ids = resolved.ids;
3931
+ warnings = resolved.warnings;
3932
+ } else if (options.ids.length > 0) {
3933
+ const resolved = resolveIds([...options.ids], index);
3934
+ ids = resolved.ids;
3935
+ warnings = resolved.warnings;
3936
+ } else {
3937
+ logger.error("No IDs, --all, --task, or --file specified");
3938
+ return 1;
3939
+ }
3940
+ for (const w of warnings) {
3941
+ logger.warn(w);
3942
+ }
3943
+ if (ids.length === 0) {
3944
+ if (options.file) {
3945
+ logger.error("No traceability markers found in file");
3946
+ } else if (options.task) {
3947
+ logger.error("No traceability IDs found in task file");
3948
+ } else {
3949
+ logger.error("No valid IDs found");
3950
+ }
3951
+ return 1;
3952
+ }
3953
+ const traceOptions = {
3954
+ direction: options.direction,
3955
+ depth: options.depth,
3956
+ scope: options.scope,
3957
+ noCode: options.noCode,
3958
+ noTests: options.noTests
3959
+ };
3960
+ const result = resolveTrace(index, ids, traceOptions);
3961
+ if (result.chains.length === 0 && result.notFound.length > 0) {
3962
+ for (const id of result.notFound) {
3963
+ logger.error(`ID not found: ${id}`);
3964
+ }
3965
+ return 1;
3966
+ }
3967
+ let output;
3968
+ const isContentMode = options.content || options.maxTokens !== void 0;
3969
+ const queryLabel = ids.join(", ");
3970
+ if (isContentMode) {
3971
+ const sections = await assembleContent(result, options.task, {
3972
+ beforeContext: options.beforeContext,
3973
+ afterContext: options.afterContext
3974
+ });
3975
+ let finalSections = sections;
3976
+ let footer = null;
3977
+ if (options.maxTokens !== void 0) {
3978
+ const budgeted = applyTokenBudget(sections, options.maxTokens);
3979
+ finalSections = budgeted.sections;
3980
+ footer = budgeted.footer;
3981
+ }
3982
+ if (options.json) {
3983
+ output = formatContentJson(finalSections, queryLabel, footer);
3984
+ } else {
3985
+ output = formatContentMarkdown(finalSections, queryLabel, footer);
3986
+ }
3987
+ } else if (options.list) {
3988
+ output = formatList(result);
3989
+ } else if (options.json) {
3990
+ output = formatJson(result);
3991
+ } else {
3992
+ output = formatTree(result);
3993
+ }
3994
+ process.stdout.write(output);
3995
+ if (result.notFound.length > 0) {
3996
+ for (const id of result.notFound) {
3997
+ logger.warn(`ID not found: ${id}`);
3998
+ }
3999
+ }
4000
+ return 0;
4001
+ } catch (error) {
4002
+ if (error instanceof Error) {
4003
+ logger.error(error.message);
4004
+ } else {
4005
+ logger.error(String(error));
4006
+ }
4007
+ return 2;
4008
+ }
4009
+ }
4010
+
2912
4011
  // src/utils/update-check.ts
2913
4012
  import chalk5 from "chalk";
2914
4013
  function compareSemver(a, b) {
@@ -2964,7 +4063,7 @@ function printUpdateWarning(log, result) {
2964
4063
  }
2965
4064
 
2966
4065
  // src/utils/update-check-cache.ts
2967
- import { mkdir as mkdir3, readFile as readFile6, writeFile } from "fs/promises";
4066
+ import { mkdir as mkdir3, readFile as readFile8, writeFile } from "fs/promises";
2968
4067
  import { homedir } from "os";
2969
4068
  import { dirname, join as join11 } from "path";
2970
4069
  var CACHE_DIR = join11(homedir(), ".cache", "awa");
@@ -2972,7 +4071,7 @@ var CACHE_FILE = join11(CACHE_DIR, "update-check.json");
2972
4071
  var DEFAULT_INTERVAL_MS = 864e5;
2973
4072
  async function shouldCheck(intervalMs = DEFAULT_INTERVAL_MS) {
2974
4073
  try {
2975
- const raw = await readFile6(CACHE_FILE, "utf-8");
4074
+ const raw = await readFile8(CACHE_FILE, "utf-8");
2976
4075
  const data = JSON.parse(raw);
2977
4076
  if (typeof data.timestamp !== "number" || typeof data.latestVersion !== "string") {
2978
4077
  return true;
@@ -2998,34 +4097,44 @@ async function writeCache(latestVersion) {
2998
4097
  var version = PACKAGE_INFO.version;
2999
4098
  var program = new Command();
3000
4099
  program.name("awa").description("awa - tool for generating AI coding agent configuration files").version(version, "-v, --version", "Display version number");
3001
- program.command("generate").alias("init").description("Generate AI agent configuration files from templates").argument("[output]", "Output directory (optional if specified in config)").option("-t, --template <source>", "Template source (local path or Git repository)").option("-f, --features <flag...>", "Feature flags (can be specified multiple times)").option("--preset <name...>", "Preset names to enable (can be specified multiple times)").option(
3002
- "--remove-features <flag...>",
3003
- "Feature flags to remove (can be specified multiple times)"
3004
- ).option("--force", "Force overwrite existing files without prompting", false).option("--dry-run", "Preview changes without modifying files", false).option(
3005
- "--delete",
3006
- "Enable deletion of files listed in the delete list (default: warn only)",
3007
- false
3008
- ).option("-c, --config <path>", "Path to configuration file").option("--refresh", "Force refresh of cached Git templates", false).option("--all", "Process all named targets from config", false).option("--target <name>", "Process a specific named target from config").option("--overlay <path...>", "Overlay directory paths applied over base template (repeatable)").option("--json", "Output results as JSON (implies --dry-run)", false).option("--summary", "Output compact one-line summary", false).action(async (output, options) => {
3009
- const cliOptions = {
3010
- output,
3011
- template: options.template,
3012
- features: options.features,
3013
- preset: options.preset,
3014
- removeFeatures: options.removeFeatures,
3015
- force: options.force,
3016
- dryRun: options.dryRun,
3017
- delete: options.delete,
3018
- config: options.config,
3019
- refresh: options.refresh,
3020
- all: options.all,
3021
- target: options.target,
3022
- overlay: options.overlay || [],
3023
- json: options.json,
3024
- summary: options.summary
3025
- };
3026
- await generateCommand(cliOptions);
3027
- });
3028
- program.command("diff").description("Compare template output with existing target directory").argument("[target]", "Target directory to compare against (optional if specified in config)").option("-t, --template <source>", "Template source (local path or Git repository)").option("-f, --features <flag...>", "Feature flags (can be specified multiple times)").option("--preset <name...>", "Preset names to enable (can be specified multiple times)").option(
4100
+ var template = new Command("template").description(
4101
+ "Template operations (generate, diff, features, test)"
4102
+ );
4103
+ function configureGenerateCommand(cmd) {
4104
+ return cmd.description("Generate AI agent configuration files from templates").argument("[output]", "Output directory (optional if specified in config)").option("-t, --template <source>", "Template source (local path or Git repository)").option("-f, --features <flag...>", "Feature flags (can be specified multiple times)").option("--preset <name...>", "Preset names to enable (can be specified multiple times)").option(
4105
+ "--remove-features <flag...>",
4106
+ "Feature flags to remove (can be specified multiple times)"
4107
+ ).option("--force", "Force overwrite existing files without prompting", false).option("--dry-run", "Preview changes without modifying files", false).option(
4108
+ "--delete",
4109
+ "Enable deletion of files listed in the delete list (default: warn only)",
4110
+ false
4111
+ ).option("-c, --config <path>", "Path to configuration file").option("--refresh", "Force refresh of cached Git templates", false).option("--all", "Process all named targets from config", false).option("--target <name>", "Process a specific named target from config").option(
4112
+ "--overlay <path...>",
4113
+ "Overlay directory paths applied over base template (repeatable)"
4114
+ ).option("--json", "Output results as JSON (implies --dry-run)", false).option("--summary", "Output compact one-line summary", false).action(async (output, options) => {
4115
+ const cliOptions = {
4116
+ output,
4117
+ template: options.template,
4118
+ features: options.features,
4119
+ preset: options.preset,
4120
+ removeFeatures: options.removeFeatures,
4121
+ force: options.force,
4122
+ dryRun: options.dryRun,
4123
+ delete: options.delete,
4124
+ config: options.config,
4125
+ refresh: options.refresh,
4126
+ all: options.all,
4127
+ target: options.target,
4128
+ overlay: options.overlay || [],
4129
+ json: options.json,
4130
+ summary: options.summary
4131
+ };
4132
+ await generateCommand(cliOptions);
4133
+ });
4134
+ }
4135
+ configureGenerateCommand(template.command("generate"));
4136
+ configureGenerateCommand(program.command("init"));
4137
+ template.command("diff").description("Compare template output with existing target directory").argument("[target]", "Target directory to compare against (optional if specified in config)").option("-t, --template <source>", "Template source (local path or Git repository)").option("-f, --features <flag...>", "Feature flags (can be specified multiple times)").option("--preset <name...>", "Preset names to enable (can be specified multiple times)").option(
3029
4138
  "--remove-features <flag...>",
3030
4139
  "Feature flags to remove (can be specified multiple times)"
3031
4140
  ).option("-c, --config <path>", "Path to configuration file").option("--refresh", "Force refresh of cached Git templates", false).option("--list-unknown", "Include target-only files in diff results", false).option("--all", "Process all named targets from config", false).option("--target <name>", "Process a specific named target from config").option("-w, --watch", "Watch template directory for changes and re-run diff", false).option("--overlay <path...>", "Overlay directory paths applied over base template (repeatable)").option("--json", "Output results as JSON", false).option("--summary", "Output compact one-line summary", false).action(async (target, options) => {
@@ -3071,7 +4180,7 @@ program.command("check").description(
3071
4180
  const exitCode = await checkCommand(cliOptions);
3072
4181
  process.exit(exitCode);
3073
4182
  });
3074
- program.command("features").description("Discover feature flags available in a template").option("-t, --template <source>", "Template source (local path or Git repository)").option("-c, --config <path>", "Path to configuration file").option("--refresh", "Force refresh of cached Git templates", false).option("--json", "Output results as JSON", false).action(async (options) => {
4183
+ template.command("features").description("Discover feature flags available in a template").option("-t, --template <source>", "Template source (local path or Git repository)").option("-c, --config <path>", "Path to configuration file").option("--refresh", "Force refresh of cached Git templates", false).option("--json", "Output results as JSON", false).action(async (options) => {
3075
4184
  const exitCode = await featuresCommand({
3076
4185
  template: options.template,
3077
4186
  config: options.config,
@@ -3080,7 +4189,7 @@ program.command("features").description("Discover feature flags available in a t
3080
4189
  });
3081
4190
  process.exit(exitCode);
3082
4191
  });
3083
- program.command("test").description("Run template test fixtures to verify expected output").option("-t, --template <source>", "Template source (local path or Git repository)").option("-c, --config <path>", "Path to configuration file").option("--update-snapshots", "Update stored snapshots with current rendered output", false).action(async (options) => {
4192
+ template.command("test").description("Run template test fixtures to verify expected output").option("-t, --template <source>", "Template source (local path or Git repository)").option("-c, --config <path>", "Path to configuration file").option("--update-snapshots", "Update stored snapshots with current rendered output", false).action(async (options) => {
3084
4193
  const testOptions = {
3085
4194
  template: options.template,
3086
4195
  config: options.config,
@@ -3089,6 +4198,29 @@ program.command("test").description("Run template test fixtures to verify expect
3089
4198
  const exitCode = await testCommand(testOptions);
3090
4199
  process.exit(exitCode);
3091
4200
  });
4201
+ program.addCommand(template);
4202
+ program.command("trace").description("Explore traceability chains and assemble context from specs, code, and tests").argument("[ids...]", "Traceability ID(s) to trace").option("--all", "Trace all known IDs in the project", false).option("--task <path>", "Resolve IDs from a task file").option("--file <path>", "Resolve IDs from a source file's markers").option("--content", "Output actual file sections instead of locations", false).option("--list", "Output file paths only (no content or tree)", false).option("--max-tokens <n>", "Cap content output size (implies --content)").option("--depth <n>", "Maximum traversal depth").option("--scope <code>", "Limit results to a feature code").option("--direction <dir>", "Traversal direction: both, forward, reverse", "both").option("--no-code", "Exclude source code (spec-only context)").option("--no-tests", "Exclude test files").option("--json", "Output as JSON", false).option("-A <n>", "Lines of context after a code marker (--content only; default: 20)").option("-B <n>", "Lines of context before a code marker (--content only; default: 5)").option("-C <n>", "Lines of context before and after (--content only; overrides -A and -B)").option("-c, --config <path>", "Path to configuration file").action(async (ids, options) => {
4203
+ const traceOptions = {
4204
+ ids,
4205
+ all: options.all,
4206
+ task: options.task,
4207
+ file: options.file,
4208
+ content: options.content,
4209
+ list: options.list,
4210
+ json: options.json,
4211
+ maxTokens: options.maxTokens !== void 0 ? Number(options.maxTokens) : void 0,
4212
+ depth: options.depth !== void 0 ? Number(options.depth) : void 0,
4213
+ scope: options.scope,
4214
+ direction: options.direction,
4215
+ noCode: options.code === false,
4216
+ noTests: options.tests === false,
4217
+ beforeContext: options.C !== void 0 ? Number(options.C) : options.B !== void 0 ? Number(options.B) : void 0,
4218
+ afterContext: options.C !== void 0 ? Number(options.C) : options.A !== void 0 ? Number(options.A) : void 0,
4219
+ config: options.config
4220
+ };
4221
+ const exitCode = await traceCommand(traceOptions);
4222
+ process.exit(exitCode);
4223
+ });
3092
4224
  var updateCheckPromise = null;
3093
4225
  var isJsonOrSummary = process.argv.includes("--json") || process.argv.includes("--summary");
3094
4226
  var isTTY = process.stdout.isTTY === true;
@@ -3096,7 +4228,7 @@ var isDisabledByEnv = !!process.env.NO_UPDATE_NOTIFIER;
3096
4228
  if (!isJsonOrSummary && isTTY && !isDisabledByEnv) {
3097
4229
  updateCheckPromise = (async () => {
3098
4230
  try {
3099
- const { configLoader: configLoader2 } = await import("./config-2TOQATI3.js");
4231
+ const { configLoader: configLoader2 } = await import("./config-LWUO5EXL.js");
3100
4232
  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
4233
  const fileConfig = await configLoader2.load(configPath ?? null);
3102
4234
  const updateCheckConfig = fileConfig?.["update-check"];