@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/README.md +31 -191
- package/dist/index.js +1149 -49
- 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/_README.md +7 -7
- package/templates/awa/_partials/awa.core.md +1 -1
- package/templates/awa/_partials/awa.usage.md +12 -12
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.
|
|
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
|
|
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 =
|
|
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(
|
|
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 &&
|
|
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 ${
|
|
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:
|
|
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
|
|
2543
|
-
const scanResult = await featureScanner.scan(
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
2913
|
-
const fixtures = await discoverFixtures(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
3034
|
-
"
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
"--
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
).option("
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|