@ncoderz/awa 1.3.1 → 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
@@ -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.3.1",
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."
@@ -2313,7 +2313,7 @@ async function prepareDiff(options) {
2313
2313
  throw new DiffError(`Target directory does not exist: ${options.output}`);
2314
2314
  }
2315
2315
  const targetPath = options.output;
2316
- const template = await templateResolver.resolve(options.template, options.refresh);
2316
+ const template2 = await templateResolver.resolve(options.template, options.refresh);
2317
2317
  const features = featureResolver.resolve({
2318
2318
  baseFeatures: [...options.features],
2319
2319
  presetNames: [...options.preset],
@@ -2321,10 +2321,10 @@ async function prepareDiff(options) {
2321
2321
  presetDefinitions: options.presets
2322
2322
  });
2323
2323
  let mergedDir = null;
2324
- let templatePath = template.localPath;
2324
+ let templatePath = template2.localPath;
2325
2325
  if (options.overlay.length > 0) {
2326
2326
  const overlayDirs = await resolveOverlays([...options.overlay], options.refresh);
2327
- mergedDir = await buildMergedDir(template.localPath, overlayDirs);
2327
+ mergedDir = await buildMergedDir(template2.localPath, overlayDirs);
2328
2328
  templatePath = mergedDir;
2329
2329
  }
2330
2330
  return {
@@ -2334,7 +2334,7 @@ async function prepareDiff(options) {
2334
2334
  features,
2335
2335
  listUnknown: options.listUnknown
2336
2336
  },
2337
- template,
2337
+ template: template2,
2338
2338
  mergedDir
2339
2339
  };
2340
2340
  }
@@ -2362,8 +2362,8 @@ async function diffCommand(cliOptions) {
2362
2362
  if (!silent) {
2363
2363
  intro("awa CLI - Template Diff");
2364
2364
  }
2365
- const { diffOptions, template, mergedDir } = await prepareDiff(options);
2366
- 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") {
2367
2367
  throw new DiffError("--watch is only supported with local template sources");
2368
2368
  }
2369
2369
  const result = await runDiff(diffOptions, options, mergedDir);
@@ -2373,11 +2373,11 @@ async function diffCommand(cliOptions) {
2373
2373
  }
2374
2374
  return result;
2375
2375
  }
2376
- logger.info(`Watching for changes in ${template.localPath}...`);
2376
+ logger.info(`Watching for changes in ${template2.localPath}...`);
2377
2377
  return new Promise((resolve2) => {
2378
2378
  let running = false;
2379
2379
  const watcher = new FileWatcher({
2380
- directory: template.localPath,
2380
+ directory: template2.localPath,
2381
2381
  onChange: async () => {
2382
2382
  if (running) return;
2383
2383
  running = true;
@@ -2539,8 +2539,8 @@ async function featuresCommand(cliOptions) {
2539
2539
  const fileConfig = await configLoader.load(cliOptions.config ?? null);
2540
2540
  const templateSource = cliOptions.template ?? fileConfig?.template ?? null;
2541
2541
  const refresh = cliOptions.refresh ?? fileConfig?.refresh ?? false;
2542
- const template = await templateResolver.resolve(templateSource, refresh);
2543
- const scanResult = await featureScanner.scan(template.localPath);
2542
+ const template2 = await templateResolver.resolve(templateSource, refresh);
2543
+ const scanResult = await featureScanner.scan(template2.localPath);
2544
2544
  const presets = fileConfig?.presets;
2545
2545
  featuresReporter.report({
2546
2546
  scanResult,
@@ -2580,7 +2580,7 @@ var TOOL_FEATURES = [
2580
2580
  var TOOL_FEATURE_VALUES = new Set(TOOL_FEATURES.map((t) => t.value));
2581
2581
  async function runGenerate(options, batchMode) {
2582
2582
  const silent = options.json || options.summary;
2583
- const template = await templateResolver.resolve(options.template, options.refresh);
2583
+ const template2 = await templateResolver.resolve(options.template, options.refresh);
2584
2584
  const features = featureResolver.resolve({
2585
2585
  baseFeatures: [...options.features],
2586
2586
  presetNames: [...options.preset],
@@ -2612,10 +2612,10 @@ async function runGenerate(options, batchMode) {
2612
2612
  }
2613
2613
  }
2614
2614
  let mergedDir = null;
2615
- let templatePath = template.localPath;
2615
+ let templatePath = template2.localPath;
2616
2616
  if (options.overlay.length > 0) {
2617
2617
  const overlayDirs = await resolveOverlays([...options.overlay], options.refresh);
2618
- mergedDir = await buildMergedDir(template.localPath, overlayDirs);
2618
+ mergedDir = await buildMergedDir(template2.localPath, overlayDirs);
2619
2619
  templatePath = mergedDir;
2620
2620
  }
2621
2621
  try {
@@ -2909,8 +2909,8 @@ async function testCommand(options) {
2909
2909
  intro4("awa CLI - Template Test");
2910
2910
  const fileConfig = await configLoader.load(options.config ?? null);
2911
2911
  const templateSource = options.template ?? fileConfig?.template ?? null;
2912
- const template = await templateResolver.resolve(templateSource, false);
2913
- const fixtures = await discoverFixtures(template.localPath);
2912
+ const template2 = await templateResolver.resolve(templateSource, false);
2913
+ const fixtures = await discoverFixtures(template2.localPath);
2914
2914
  if (fixtures.length === 0) {
2915
2915
  logger.warn("No test fixtures found in _tests/ directory");
2916
2916
  outro4("No tests to run.");
@@ -2920,7 +2920,7 @@ async function testCommand(options) {
2920
2920
  const presetDefinitions = fileConfig?.presets ?? {};
2921
2921
  const result = await runAll(
2922
2922
  fixtures,
2923
- template.localPath,
2923
+ template2.localPath,
2924
2924
  { updateSnapshots: options.updateSnapshots },
2925
2925
  presetDefinitions
2926
2926
  );
@@ -2941,6 +2941,1073 @@ async function testCommand(options) {
2941
2941
  }
2942
2942
  }
2943
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
+
2944
4011
  // src/utils/update-check.ts
2945
4012
  import chalk5 from "chalk";
2946
4013
  function compareSemver(a, b) {
@@ -2996,7 +4063,7 @@ function printUpdateWarning(log, result) {
2996
4063
  }
2997
4064
 
2998
4065
  // src/utils/update-check-cache.ts
2999
- import { mkdir as mkdir3, readFile as readFile6, writeFile } from "fs/promises";
4066
+ import { mkdir as mkdir3, readFile as readFile8, writeFile } from "fs/promises";
3000
4067
  import { homedir } from "os";
3001
4068
  import { dirname, join as join11 } from "path";
3002
4069
  var CACHE_DIR = join11(homedir(), ".cache", "awa");
@@ -3004,7 +4071,7 @@ var CACHE_FILE = join11(CACHE_DIR, "update-check.json");
3004
4071
  var DEFAULT_INTERVAL_MS = 864e5;
3005
4072
  async function shouldCheck(intervalMs = DEFAULT_INTERVAL_MS) {
3006
4073
  try {
3007
- const raw = await readFile6(CACHE_FILE, "utf-8");
4074
+ const raw = await readFile8(CACHE_FILE, "utf-8");
3008
4075
  const data = JSON.parse(raw);
3009
4076
  if (typeof data.timestamp !== "number" || typeof data.latestVersion !== "string") {
3010
4077
  return true;
@@ -3030,34 +4097,44 @@ async function writeCache(latestVersion) {
3030
4097
  var version = PACKAGE_INFO.version;
3031
4098
  var program = new Command();
3032
4099
  program.name("awa").description("awa - tool for generating AI coding agent configuration files").version(version, "-v, --version", "Display version number");
3033
- 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(
3034
- "--remove-features <flag...>",
3035
- "Feature flags to remove (can be specified multiple times)"
3036
- ).option("--force", "Force overwrite existing files without prompting", false).option("--dry-run", "Preview changes without modifying files", false).option(
3037
- "--delete",
3038
- "Enable deletion of files listed in the delete list (default: warn only)",
3039
- false
3040
- ).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) => {
3041
- const cliOptions = {
3042
- output,
3043
- template: options.template,
3044
- features: options.features,
3045
- preset: options.preset,
3046
- removeFeatures: options.removeFeatures,
3047
- force: options.force,
3048
- dryRun: options.dryRun,
3049
- delete: options.delete,
3050
- config: options.config,
3051
- refresh: options.refresh,
3052
- all: options.all,
3053
- target: options.target,
3054
- overlay: options.overlay || [],
3055
- json: options.json,
3056
- summary: options.summary
3057
- };
3058
- await generateCommand(cliOptions);
3059
- });
3060
- 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(
3061
4138
  "--remove-features <flag...>",
3062
4139
  "Feature flags to remove (can be specified multiple times)"
3063
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) => {
@@ -3103,7 +4180,7 @@ program.command("check").description(
3103
4180
  const exitCode = await checkCommand(cliOptions);
3104
4181
  process.exit(exitCode);
3105
4182
  });
3106
- 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) => {
3107
4184
  const exitCode = await featuresCommand({
3108
4185
  template: options.template,
3109
4186
  config: options.config,
@@ -3112,7 +4189,7 @@ program.command("features").description("Discover feature flags available in a t
3112
4189
  });
3113
4190
  process.exit(exitCode);
3114
4191
  });
3115
- 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) => {
3116
4193
  const testOptions = {
3117
4194
  template: options.template,
3118
4195
  config: options.config,
@@ -3121,6 +4198,29 @@ program.command("test").description("Run template test fixtures to verify expect
3121
4198
  const exitCode = await testCommand(testOptions);
3122
4199
  process.exit(exitCode);
3123
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
+ });
3124
4224
  var updateCheckPromise = null;
3125
4225
  var isJsonOrSummary = process.argv.includes("--json") || process.argv.includes("--summary");
3126
4226
  var isTTY = process.stdout.isTTY === true;