@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/README.md +31 -191
- package/dist/{chunk-3SSUJFKN.js → chunk-ALEGCDAZ.js} +1 -1
- package/dist/chunk-ALEGCDAZ.js.map +1 -0
- package/dist/{config-2TOQATI3.js → config-LWUO5EXL.js} +2 -2
- package/dist/index.js +1185 -53
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/templates/awa/.awa/.agent/schemas/EXAMPLES.schema.yaml +2 -2
- package/templates/awa/.awa/.agent/schemas/FEAT.schema.yaml +3 -3
- package/templates/awa/.awa/.agent/schemas/README.schema.yaml +4 -4
- package/templates/awa/.awa/.agent/schemas/REQ.schema.yaml +2 -2
- package/templates/awa/_README.md +7 -7
- package/templates/awa/_partials/awa.core.md +1 -1
- package/templates/awa/_partials/awa.usage.md +12 -12
- package/dist/chunk-3SSUJFKN.js.map +0 -1
- /package/dist/{config-2TOQATI3.js.map → config-LWUO5EXL.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
rmDir,
|
|
17
17
|
walkDirectory,
|
|
18
18
|
writeTextFile
|
|
19
|
-
} from "./chunk-
|
|
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.
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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 &&
|
|
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 ${
|
|
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:
|
|
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
|
|
2511
|
-
const scanResult = await featureScanner.scan(
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
2881
|
-
const fixtures = await discoverFixtures(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
3002
|
-
"
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
"--
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
).option("
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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"];
|