@ncoderz/awa 1.7.2 → 1.8.1
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/LICENSE +23 -16
- package/README.md +25 -27
- package/dist/chunk-EL7ZWFXO.js +875 -0
- package/dist/chunk-EL7ZWFXO.js.map +1 -0
- package/dist/{chunk-OQZTQ5ZI.js → chunk-LRQWZCYL.js} +1 -4
- package/dist/chunk-LRQWZCYL.js.map +1 -0
- package/dist/{config-WL3SLSP6.js → config-EJIXC7D7.js} +2 -2
- package/dist/index.js +1257 -452
- package/dist/index.js.map +1 -1
- package/dist/renumber-TLBGOWZM.js +9 -0
- package/dist/renumber-TLBGOWZM.js.map +1 -0
- package/package.json +13 -6
- package/templates/awa/.agent/skills/spec-merge/SKILL.md +3 -0
- package/templates/awa/.agent/skills/spec-tidy/SKILL.md +3 -0
- package/templates/awa/.agent/workflows/spec-merge.md +3 -0
- package/templates/awa/.agent/workflows/spec-tidy.md +3 -0
- package/templates/awa/.agents/skills/spec-merge/SKILL.md +3 -0
- package/templates/awa/.agents/skills/spec-tidy/SKILL.md +3 -0
- package/templates/awa/.awa/.agent/schemas/ALIGN_REPORT.schema.yaml +1 -1
- package/templates/awa/.awa/.agent/schemas/API.schema.yaml +1 -1
- package/templates/awa/.awa/.agent/schemas/ARCHITECTURE.schema.yaml +7 -0
- package/templates/awa/.awa/.agent/schemas/DESIGN.schema.yaml +1 -1
- package/templates/awa/.awa/.agent/schemas/{EXAMPLES.schema.yaml → EXAMPLE.schema.yaml} +4 -4
- package/templates/awa/.awa/.agent/schemas/FEAT.schema.yaml +8 -1
- package/templates/awa/.awa/.agent/schemas/PLAN.schema.yaml +1 -1
- package/templates/awa/.awa/.agent/schemas/README.schema.yaml +1 -1
- package/templates/awa/.awa/.agent/schemas/REQ.schema.yaml +1 -1
- package/templates/awa/.awa/.agent/schemas/TASK.schema.yaml +1 -1
- package/templates/awa/.claude/skills/spec-merge/SKILL.md +3 -0
- package/templates/awa/.claude/skills/spec-tidy/SKILL.md +3 -0
- package/templates/awa/.gemini/commands/spec-merge.md +3 -0
- package/templates/awa/.gemini/commands/spec-tidy.md +3 -0
- package/templates/awa/.gemini/skills/spec-merge/SKILL.md +3 -0
- package/templates/awa/.gemini/skills/spec-tidy/SKILL.md +3 -0
- package/templates/awa/.github/prompts/awa.spec-merge.prompt.md +8 -0
- package/templates/awa/.github/prompts/awa.spec-tidy.prompt.md +7 -0
- package/templates/awa/.github/skills/spec-merge/SKILL.md +3 -0
- package/templates/awa/.github/skills/spec-tidy/SKILL.md +3 -0
- package/templates/awa/.kilocode/skills/spec-merge/SKILL.md +3 -0
- package/templates/awa/.kilocode/skills/spec-tidy/SKILL.md +3 -0
- package/templates/awa/.kilocode/workflows/spec-merge.md +3 -0
- package/templates/awa/.kilocode/workflows/spec-tidy.md +3 -0
- package/templates/awa/.opencode/commands/spec-merge.md +3 -0
- package/templates/awa/.opencode/commands/spec-tidy.md +3 -0
- package/templates/awa/.opencode/skills/spec-merge/SKILL.md +3 -0
- package/templates/awa/.opencode/skills/spec-tidy/SKILL.md +3 -0
- package/templates/awa/.qwen/commands/spec-merge.md +3 -0
- package/templates/awa/.qwen/commands/spec-tidy.md +3 -0
- package/templates/awa/.qwen/skills/spec-merge/SKILL.md +3 -0
- package/templates/awa/.qwen/skills/spec-tidy/SKILL.md +3 -0
- package/templates/awa/.roo/skills/spec-merge/SKILL.md +3 -0
- package/templates/awa/.roo/skills/spec-tidy/SKILL.md +3 -0
- package/templates/awa/.windsurf/skills/spec-merge/SKILL.md +3 -0
- package/templates/awa/.windsurf/skills/spec-tidy/SKILL.md +3 -0
- package/templates/awa/_delete.txt +4 -0
- package/templates/awa/_partials/_cmd.spec-merge.md +6 -0
- package/templates/awa/_partials/_cmd.spec-tidy.md +5 -0
- package/templates/awa/_partials/_skill.spec-merge.md +6 -0
- package/templates/awa/_partials/_skill.spec-tidy.md +6 -0
- package/templates/awa/_partials/awa.align.md +1 -1
- package/templates/awa/_partials/awa.brainstorm.md +1 -1
- package/templates/awa/_partials/awa.code.md +1 -1
- package/templates/awa/_partials/awa.core.md +9 -4
- package/templates/awa/_partials/awa.design.md +3 -2
- package/templates/awa/_partials/awa.documentation.md +1 -1
- package/templates/awa/_partials/awa.examples.md +4 -4
- package/templates/awa/_partials/awa.feature.md +2 -1
- package/templates/awa/_partials/awa.plan.md +2 -2
- package/templates/awa/_partials/awa.requirements.md +4 -2
- package/templates/awa/_partials/awa.spec-merge.md +97 -0
- package/templates/awa/_partials/awa.spec.tidy.md +92 -0
- package/templates/awa/_partials/awa.tasks.md +1 -1
- package/templates/awa/_partials/awa.upgrade.md +3 -3
- package/templates/awa/_partials/awa.usage.md +77 -6
- package/templates/awa/_partials/awa.vibe.md +1 -1
- package/templates/awa/_tests/claude/.awa/.agent/awa.core.md +126 -0
- package/templates/awa/_tests/claude/.awa/.agent/schemas/ALIGN_REPORT.schema.yaml +83 -0
- package/templates/awa/_tests/claude/.awa/.agent/schemas/API.schema.yaml +7 -0
- package/templates/awa/_tests/claude/.awa/.agent/schemas/ARCHITECTURE.schema.yaml +257 -0
- package/templates/awa/_tests/claude/.awa/.agent/schemas/DESIGN.schema.yaml +351 -0
- package/templates/awa/_tests/claude/.awa/.agent/schemas/EXAMPLE.schema.yaml +89 -0
- package/templates/awa/_tests/claude/.awa/.agent/schemas/FEAT.schema.yaml +142 -0
- package/templates/awa/_tests/claude/.awa/.agent/schemas/PLAN.schema.yaml +146 -0
- package/templates/awa/_tests/claude/.awa/.agent/schemas/README.schema.yaml +137 -0
- package/templates/awa/_tests/claude/.awa/.agent/schemas/REQ.schema.yaml +160 -0
- package/templates/awa/_tests/claude/.awa/.agent/schemas/TASK.schema.yaml +204 -0
- package/templates/awa/_tests/claude/.claude/agents/awa.md +137 -0
- package/templates/awa/_tests/claude/.claude/skills/awa-align/SKILL.md +67 -0
- package/templates/awa/_tests/claude/.claude/skills/awa-architecture/SKILL.md +50 -0
- package/templates/awa/_tests/claude/.claude/skills/awa-brainstorm/SKILL.md +57 -0
- package/templates/awa/_tests/claude/.claude/skills/awa-check/SKILL.md +79 -0
- package/templates/awa/_tests/claude/.claude/skills/awa-code/SKILL.md +179 -0
- package/templates/awa/_tests/claude/.claude/skills/awa-design/SKILL.md +62 -0
- package/templates/awa/_tests/claude/.claude/skills/awa-documentation/SKILL.md +91 -0
- package/templates/awa/_tests/claude/.claude/skills/awa-examples/SKILL.md +58 -0
- package/templates/awa/_tests/claude/.claude/skills/awa-feature/SKILL.md +56 -0
- package/templates/awa/_tests/claude/.claude/skills/awa-plan/SKILL.md +53 -0
- package/templates/awa/_tests/claude/.claude/skills/awa-refactor/SKILL.md +53 -0
- package/templates/awa/_tests/claude/.claude/skills/awa-requirements/SKILL.md +58 -0
- package/templates/awa/_tests/claude/.claude/skills/awa-tasks/SKILL.md +158 -0
- package/templates/awa/_tests/claude/.claude/skills/awa-upgrade/SKILL.md +68 -0
- package/templates/awa/_tests/claude/.claude/skills/awa-usage/SKILL.md +368 -0
- package/templates/awa/_tests/claude/.claude/skills/awa-vibe/SKILL.md +72 -0
- package/templates/awa/_tests/claude/.claude/skills/spec-merge/SKILL.md +102 -0
- package/templates/awa/_tests/claude/.claude/skills/spec-tidy/SKILL.md +97 -0
- package/templates/awa/_tests/claude/CLAUDE.md +132 -0
- package/templates/awa/_tests/copilot/.awa/.agent/awa.core.md +126 -0
- package/templates/awa/_tests/copilot/.awa/.agent/schemas/ALIGN_REPORT.schema.yaml +83 -0
- package/templates/awa/_tests/copilot/.awa/.agent/schemas/API.schema.yaml +7 -0
- package/templates/awa/_tests/copilot/.awa/.agent/schemas/ARCHITECTURE.schema.yaml +257 -0
- package/templates/awa/_tests/copilot/.awa/.agent/schemas/DESIGN.schema.yaml +351 -0
- package/templates/awa/_tests/copilot/.awa/.agent/schemas/EXAMPLE.schema.yaml +89 -0
- package/templates/awa/_tests/copilot/.awa/.agent/schemas/FEAT.schema.yaml +142 -0
- package/templates/awa/_tests/copilot/.awa/.agent/schemas/PLAN.schema.yaml +146 -0
- package/templates/awa/_tests/copilot/.awa/.agent/schemas/README.schema.yaml +137 -0
- package/templates/awa/_tests/copilot/.awa/.agent/schemas/REQ.schema.yaml +160 -0
- package/templates/awa/_tests/copilot/.awa/.agent/schemas/TASK.schema.yaml +204 -0
- package/templates/awa/_tests/copilot/.github/agents/awa.agent.md +137 -0
- package/templates/awa/_tests/copilot/.github/prompts/awa.align.prompt.md +67 -0
- package/templates/awa/_tests/copilot/.github/prompts/awa.architecture.prompt.md +50 -0
- package/templates/awa/_tests/copilot/.github/prompts/awa.brainstorm.prompt.md +57 -0
- package/templates/awa/_tests/copilot/.github/prompts/awa.check.prompt.md +79 -0
- package/templates/awa/_tests/copilot/.github/prompts/awa.code.prompt.md +179 -0
- package/templates/awa/_tests/copilot/.github/prompts/awa.design.prompt.md +62 -0
- package/templates/awa/_tests/copilot/.github/prompts/awa.documentation.prompt.md +91 -0
- package/templates/awa/_tests/copilot/.github/prompts/awa.examples.prompt.md +58 -0
- package/templates/awa/_tests/copilot/.github/prompts/awa.feature.prompt.md +56 -0
- package/templates/awa/_tests/copilot/.github/prompts/awa.plan.prompt.md +53 -0
- package/templates/awa/_tests/copilot/.github/prompts/awa.refactor.prompt.md +53 -0
- package/templates/awa/_tests/copilot/.github/prompts/awa.requirements.prompt.md +58 -0
- package/templates/awa/_tests/copilot/.github/prompts/awa.spec-merge.prompt.md +102 -0
- package/templates/awa/_tests/copilot/.github/prompts/awa.spec-tidy.prompt.md +96 -0
- package/templates/awa/_tests/copilot/.github/prompts/awa.tasks.prompt.md +158 -0
- package/templates/awa/_tests/copilot/.github/prompts/awa.upgrade.prompt.md +68 -0
- package/templates/awa/_tests/copilot/.github/prompts/awa.vibe.prompt.md +72 -0
- package/templates/awa/_tests/copilot/.github/skills/awa-align/SKILL.md +67 -0
- package/templates/awa/_tests/copilot/.github/skills/awa-architecture/SKILL.md +50 -0
- package/templates/awa/_tests/copilot/.github/skills/awa-brainstorm/SKILL.md +57 -0
- package/templates/awa/_tests/copilot/.github/skills/awa-check/SKILL.md +79 -0
- package/templates/awa/_tests/copilot/.github/skills/awa-code/SKILL.md +179 -0
- package/templates/awa/_tests/copilot/.github/skills/awa-design/SKILL.md +62 -0
- package/templates/awa/_tests/copilot/.github/skills/awa-documentation/SKILL.md +91 -0
- package/templates/awa/_tests/copilot/.github/skills/awa-examples/SKILL.md +58 -0
- package/templates/awa/_tests/copilot/.github/skills/awa-feature/SKILL.md +56 -0
- package/templates/awa/_tests/copilot/.github/skills/awa-plan/SKILL.md +53 -0
- package/templates/awa/_tests/copilot/.github/skills/awa-refactor/SKILL.md +53 -0
- package/templates/awa/_tests/copilot/.github/skills/awa-requirements/SKILL.md +58 -0
- package/templates/awa/_tests/copilot/.github/skills/awa-tasks/SKILL.md +158 -0
- package/templates/awa/_tests/copilot/.github/skills/awa-upgrade/SKILL.md +68 -0
- package/templates/awa/_tests/copilot/.github/skills/awa-usage/SKILL.md +368 -0
- package/templates/awa/_tests/copilot/.github/skills/awa-vibe/SKILL.md +72 -0
- package/templates/awa/_tests/copilot/.github/skills/spec-merge/SKILL.md +102 -0
- package/templates/awa/_tests/copilot/.github/skills/spec-tidy/SKILL.md +97 -0
- package/dist/chunk-OQZTQ5ZI.js.map +0 -1
- /package/dist/{config-WL3SLSP6.js.map → config-EJIXC7D7.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_CHECK_CONFIG,
|
|
4
|
+
collectFiles,
|
|
5
|
+
findSpecFiles,
|
|
6
|
+
hasAnySpecFile,
|
|
7
|
+
matchSimpleGlob,
|
|
8
|
+
parseSpecs,
|
|
9
|
+
propagate,
|
|
10
|
+
renumberCommand,
|
|
11
|
+
scan,
|
|
12
|
+
scanMarkers
|
|
13
|
+
} from "./chunk-EL7ZWFXO.js";
|
|
2
14
|
import {
|
|
3
15
|
ConfigError,
|
|
4
16
|
DiffError,
|
|
@@ -16,7 +28,7 @@ import {
|
|
|
16
28
|
rmDir,
|
|
17
29
|
walkDirectory,
|
|
18
30
|
writeTextFile
|
|
19
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-LRQWZCYL.js";
|
|
20
32
|
|
|
21
33
|
// src/cli/index.ts
|
|
22
34
|
import { Command, Option } from "commander";
|
|
@@ -24,9 +36,9 @@ import { Command, Option } from "commander";
|
|
|
24
36
|
// src/_generated/package_info.ts
|
|
25
37
|
var PACKAGE_INFO = {
|
|
26
38
|
"name": "@ncoderz/awa",
|
|
27
|
-
"version": "1.
|
|
39
|
+
"version": "1.8.1",
|
|
28
40
|
"author": "Richard Sewell <richard.sewell@ncoderz.com>",
|
|
29
|
-
"license": "
|
|
41
|
+
"license": "BSD-3-Clause",
|
|
30
42
|
"description": "awa is an Agent Workflow for AIs. It is also a CLI tool to powerfully manage agent workflow files using templates."
|
|
31
43
|
};
|
|
32
44
|
|
|
@@ -201,154 +213,331 @@ function checkCodeAgainstSpec(markers, specs, config) {
|
|
|
201
213
|
return { findings };
|
|
202
214
|
}
|
|
203
215
|
|
|
204
|
-
// src/core/check/
|
|
205
|
-
import { readFile } from "fs/promises";
|
|
216
|
+
// src/core/check/codes-fixer.ts
|
|
217
|
+
import { readFile as readFile2, writeFile } from "fs/promises";
|
|
218
|
+
import { basename as basename2 } from "path";
|
|
206
219
|
|
|
207
|
-
// src/core/
|
|
208
|
-
import {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
for (const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
// src/core/codes/scanner.ts
|
|
221
|
+
import { readFile } from "fs/promises";
|
|
222
|
+
import { basename } from "path";
|
|
223
|
+
async function scanCodes(specFiles, specGlobs, specIgnore) {
|
|
224
|
+
const codeMap = /* @__PURE__ */ new Map();
|
|
225
|
+
const featFiles = await collectFiles(
|
|
226
|
+
specGlobs.filter((g) => g.includes("FEAT-")),
|
|
227
|
+
specIgnore
|
|
228
|
+
);
|
|
229
|
+
for (const fp of featFiles) {
|
|
230
|
+
const fileName = basename(fp);
|
|
231
|
+
const match = /^FEAT-([A-Z][A-Z0-9]*)-(.+)\.md$/.exec(fileName);
|
|
232
|
+
if (!match?.[1]) continue;
|
|
233
|
+
const code = match[1];
|
|
234
|
+
const existing = codeMap.get(code);
|
|
235
|
+
if (existing) {
|
|
236
|
+
existing.feat = true;
|
|
237
|
+
} else {
|
|
238
|
+
codeMap.set(code, {
|
|
239
|
+
feature: match[2] ?? code,
|
|
240
|
+
reqCount: 0,
|
|
241
|
+
feat: true,
|
|
242
|
+
req: false,
|
|
243
|
+
design: false,
|
|
244
|
+
api: false,
|
|
245
|
+
example: false
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
for (const sf of specFiles) {
|
|
250
|
+
if (!sf.code) continue;
|
|
251
|
+
const fileName = basename(sf.filePath);
|
|
252
|
+
const prefix = fileName.split("-")[0];
|
|
253
|
+
const existing = codeMap.get(sf.code);
|
|
254
|
+
if (existing) {
|
|
255
|
+
if (prefix === "REQ") {
|
|
256
|
+
existing.req = true;
|
|
257
|
+
existing.reqCount += sf.requirementIds.length;
|
|
258
|
+
if (existing.feature === sf.code || !existing.feature) {
|
|
259
|
+
existing.feature = extractFeatureName(fileName);
|
|
260
|
+
}
|
|
261
|
+
} else if (prefix === "DESIGN") {
|
|
262
|
+
existing.design = true;
|
|
263
|
+
} else if (prefix === "API") {
|
|
264
|
+
existing.api = true;
|
|
265
|
+
} else if (prefix === "EXAMPLE") {
|
|
266
|
+
existing.example = true;
|
|
267
|
+
} else if (prefix === "FEAT") {
|
|
268
|
+
existing.feat = true;
|
|
222
269
|
}
|
|
270
|
+
} else {
|
|
271
|
+
const feature = prefix === "REQ" ? extractFeatureName(fileName) : extractFeatureNameGeneric(fileName);
|
|
272
|
+
codeMap.set(sf.code, {
|
|
273
|
+
feature,
|
|
274
|
+
reqCount: prefix === "REQ" ? sf.requirementIds.length : 0,
|
|
275
|
+
feat: prefix === "FEAT",
|
|
276
|
+
req: prefix === "REQ",
|
|
277
|
+
design: prefix === "DESIGN",
|
|
278
|
+
api: prefix === "API",
|
|
279
|
+
example: prefix === "EXAMPLE"
|
|
280
|
+
});
|
|
223
281
|
}
|
|
224
282
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
283
|
+
const scopeMap = await extractScopeSummaries(specFiles, specGlobs, specIgnore);
|
|
284
|
+
const codes = [];
|
|
285
|
+
for (const [code, meta] of codeMap) {
|
|
286
|
+
const docs = {
|
|
287
|
+
feat: meta.feat,
|
|
288
|
+
req: meta.req,
|
|
289
|
+
design: meta.design,
|
|
290
|
+
api: meta.api,
|
|
291
|
+
example: meta.example
|
|
292
|
+
};
|
|
293
|
+
codes.push({
|
|
294
|
+
code,
|
|
295
|
+
feature: meta.feature,
|
|
296
|
+
reqCount: meta.reqCount,
|
|
297
|
+
scope: scopeMap.get(code) ?? "",
|
|
298
|
+
docs
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
codes.sort((a, b) => a.code.localeCompare(b.code));
|
|
302
|
+
return { codes };
|
|
303
|
+
}
|
|
304
|
+
function extractFeatureName(fileName) {
|
|
305
|
+
const name = basename(fileName, ".md");
|
|
306
|
+
const match = /^REQ-[A-Z][A-Z0-9]*-(.+)$/.exec(name);
|
|
307
|
+
return match?.[1] ?? name;
|
|
308
|
+
}
|
|
309
|
+
function extractFeatureNameGeneric(fileName) {
|
|
310
|
+
const name = basename(fileName).replace(/\.[^.]+$/, "");
|
|
311
|
+
const match = /^[A-Z]+-[A-Z][A-Z0-9]*-(.+)$/.exec(name);
|
|
312
|
+
return match?.[1] ?? name;
|
|
313
|
+
}
|
|
314
|
+
async function extractScopeSummaries(specFiles, specGlobs, specIgnore) {
|
|
315
|
+
const scopeMap = /* @__PURE__ */ new Map();
|
|
316
|
+
const featByCode = buildFileRefMapFromSpecFiles(specFiles, "FEAT");
|
|
317
|
+
const reqByCode = buildFileRefMapFromSpecFiles(specFiles, "REQ");
|
|
318
|
+
const designByCode = buildFileRefMapFromSpecFiles(specFiles, "DESIGN");
|
|
319
|
+
const scopeFeatFiles = await collectFiles(
|
|
320
|
+
specGlobs.filter((g) => g.includes("FEAT-")),
|
|
321
|
+
specIgnore
|
|
322
|
+
);
|
|
323
|
+
for (const fp of scopeFeatFiles) {
|
|
324
|
+
const name = basename(fp, ".md");
|
|
325
|
+
const match = /^FEAT-([A-Z][A-Z0-9]*)-/.exec(name);
|
|
326
|
+
if (match?.[1] && !featByCode.has(match[1])) {
|
|
327
|
+
featByCode.set(match[1], { filePath: fp });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const allCodes = /* @__PURE__ */ new Set();
|
|
331
|
+
for (const code of featByCode.keys()) allCodes.add(code);
|
|
332
|
+
for (const sf of specFiles) {
|
|
333
|
+
if (sf.code) {
|
|
334
|
+
allCodes.add(sf.code);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
for (const code of allCodes) {
|
|
338
|
+
const scope = await resolveScope(code, featByCode, reqByCode, designByCode);
|
|
339
|
+
if (scope) {
|
|
340
|
+
scopeMap.set(code, scope);
|
|
246
341
|
}
|
|
247
|
-
const result = await scanFile(filePath, config.markers);
|
|
248
|
-
markers.push(...result.markers);
|
|
249
|
-
findings.push(...result.findings);
|
|
250
342
|
}
|
|
251
|
-
return
|
|
343
|
+
return scopeMap;
|
|
252
344
|
}
|
|
253
|
-
function
|
|
254
|
-
const
|
|
255
|
-
|
|
345
|
+
async function resolveScope(code, featByCode, reqByCode, designByCode) {
|
|
346
|
+
const featRef = featByCode.get(code);
|
|
347
|
+
if (featRef) {
|
|
348
|
+
const content = await readContent(featRef);
|
|
349
|
+
if (content) {
|
|
350
|
+
const scopeBoundary = extractScopeBoundary(content);
|
|
351
|
+
if (scopeBoundary) return scopeBoundary;
|
|
352
|
+
const firstParagraph = extractFirstParagraph(content);
|
|
353
|
+
if (firstParagraph) return firstParagraph;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const reqRef = reqByCode.get(code);
|
|
357
|
+
if (reqRef) {
|
|
358
|
+
const content = await readContent(reqRef);
|
|
359
|
+
if (content) {
|
|
360
|
+
const firstParagraph = extractFirstParagraph(content);
|
|
361
|
+
if (firstParagraph) return firstParagraph;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const designRef = designByCode.get(code);
|
|
365
|
+
if (designRef) {
|
|
366
|
+
const content = await readContent(designRef);
|
|
367
|
+
if (content) {
|
|
368
|
+
const firstParagraph = extractFirstParagraph(content);
|
|
369
|
+
if (firstParagraph) return firstParagraph;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return "";
|
|
256
373
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
let content;
|
|
374
|
+
async function readContent(ref) {
|
|
375
|
+
if (ref.content != null) return ref.content;
|
|
260
376
|
try {
|
|
261
|
-
|
|
377
|
+
return await readFile(ref.filePath, "utf-8");
|
|
262
378
|
} catch {
|
|
263
|
-
return
|
|
379
|
+
return void 0;
|
|
264
380
|
}
|
|
265
|
-
|
|
266
|
-
|
|
381
|
+
}
|
|
382
|
+
function buildFileRefMapFromSpecFiles(specFiles, prefix) {
|
|
383
|
+
const map = /* @__PURE__ */ new Map();
|
|
384
|
+
for (const sf of specFiles) {
|
|
385
|
+
if (sf.code && basename(sf.filePath).startsWith(`${prefix}-`) && !map.has(sf.code)) {
|
|
386
|
+
map.set(sf.code, { filePath: sf.filePath, content: sf.content });
|
|
387
|
+
}
|
|
267
388
|
}
|
|
268
|
-
|
|
389
|
+
return map;
|
|
390
|
+
}
|
|
391
|
+
function extractScopeBoundary(content) {
|
|
269
392
|
const lines = content.split("\n");
|
|
270
|
-
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
continue;
|
|
278
|
-
}
|
|
279
|
-
if (IGNORE_END_RE.test(line)) {
|
|
280
|
-
ignoreBlock = false;
|
|
393
|
+
let inSection = false;
|
|
394
|
+
const paragraphLines = [];
|
|
395
|
+
for (const line of lines) {
|
|
396
|
+
if (!inSection) {
|
|
397
|
+
if (/^##\s+Scope Boundary\s*$/.test(line)) {
|
|
398
|
+
inSection = true;
|
|
399
|
+
}
|
|
281
400
|
continue;
|
|
282
401
|
}
|
|
283
|
-
|
|
402
|
+
const trimmed = line.trim();
|
|
403
|
+
if (paragraphLines.length === 0 && trimmed === "") {
|
|
284
404
|
continue;
|
|
285
405
|
}
|
|
286
|
-
if (
|
|
287
|
-
|
|
288
|
-
continue;
|
|
406
|
+
if (paragraphLines.length > 0 && (trimmed === "" || /^#/.test(trimmed))) {
|
|
407
|
+
break;
|
|
289
408
|
}
|
|
290
|
-
|
|
291
|
-
|
|
409
|
+
paragraphLines.push(trimmed);
|
|
410
|
+
}
|
|
411
|
+
const paragraph = paragraphLines.join(" ").trim();
|
|
412
|
+
if (paragraph.length > 120) {
|
|
413
|
+
return `${paragraph.slice(0, 117)}...`;
|
|
414
|
+
}
|
|
415
|
+
return paragraph;
|
|
416
|
+
}
|
|
417
|
+
function extractFirstParagraph(content) {
|
|
418
|
+
const lines = content.split("\n");
|
|
419
|
+
let foundHeading = false;
|
|
420
|
+
const paragraphLines = [];
|
|
421
|
+
for (const line of lines) {
|
|
422
|
+
if (!foundHeading) {
|
|
423
|
+
if (/^##\s/.test(line)) {
|
|
424
|
+
foundHeading = true;
|
|
425
|
+
}
|
|
292
426
|
continue;
|
|
293
427
|
}
|
|
294
|
-
|
|
428
|
+
const trimmed = line.trim();
|
|
429
|
+
if (paragraphLines.length === 0 && trimmed === "") {
|
|
295
430
|
continue;
|
|
296
431
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
while (match !== null) {
|
|
300
|
-
const markerName = match[1] ?? "";
|
|
301
|
-
const idsRaw = match[2] ?? "";
|
|
302
|
-
const type = resolveMarkerType(markerName, markerNames);
|
|
303
|
-
const ids = idsRaw.split(",").map((id) => id.trim()).filter(Boolean);
|
|
304
|
-
for (const id of ids) {
|
|
305
|
-
const tokenMatch = ID_TOKEN_RE.exec(id);
|
|
306
|
-
const cleanId = tokenMatch?.[1]?.trim() ?? "";
|
|
307
|
-
if (cleanId && tokenMatch) {
|
|
308
|
-
const remainder = id.slice(tokenMatch[0].length).trim();
|
|
309
|
-
if (remainder) {
|
|
310
|
-
findings.push({
|
|
311
|
-
severity: "error",
|
|
312
|
-
code: "marker-trailing-text",
|
|
313
|
-
message: `Marker has trailing text after ID '${cleanId}': '${remainder}' \u2014 use comma-separated IDs only`,
|
|
314
|
-
filePath,
|
|
315
|
-
line: i + 1,
|
|
316
|
-
id: cleanId
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
markers.push({ type, id: cleanId, filePath, line: i + 1 });
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
match = regex.exec(line);
|
|
432
|
+
if (paragraphLines.length > 0 && (trimmed === "" || /^#/.test(trimmed))) {
|
|
433
|
+
break;
|
|
323
434
|
}
|
|
435
|
+
paragraphLines.push(trimmed);
|
|
436
|
+
}
|
|
437
|
+
const paragraph = paragraphLines.join(" ").trim();
|
|
438
|
+
if (paragraph.length > 120) {
|
|
439
|
+
return `${paragraph.slice(0, 117)}...`;
|
|
324
440
|
}
|
|
325
|
-
return
|
|
441
|
+
return paragraph;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/core/check/codes-fixer.ts
|
|
445
|
+
function sanitizeCell(value) {
|
|
446
|
+
return value.replace(/\|/g, "\\|").replace(/[\r\n]+/g, " ").trim();
|
|
326
447
|
}
|
|
327
|
-
function
|
|
328
|
-
const
|
|
329
|
-
if (
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
448
|
+
async function fixCodesTable(specs, config) {
|
|
449
|
+
const archFile = specs.specFiles.find((sf) => basename2(sf.filePath) === "ARCHITECTURE.md");
|
|
450
|
+
if (!archFile) {
|
|
451
|
+
return { filesFixed: 0, fileResults: [], emptyScopeCodes: [] };
|
|
452
|
+
}
|
|
453
|
+
let content;
|
|
454
|
+
if (archFile.content != null) {
|
|
455
|
+
content = archFile.content;
|
|
456
|
+
} else {
|
|
457
|
+
try {
|
|
458
|
+
content = await readFile2(archFile.filePath, "utf-8");
|
|
459
|
+
} catch {
|
|
460
|
+
return { filesFixed: 0, fileResults: [], emptyScopeCodes: [] };
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
const lines = content.split("\n");
|
|
464
|
+
const sectionStart = lines.findIndex((l) => /^##\s+Feature Codes\s*$/.test(l));
|
|
465
|
+
if (sectionStart === -1) {
|
|
466
|
+
return { filesFixed: 0, fileResults: [], emptyScopeCodes: [] };
|
|
467
|
+
}
|
|
468
|
+
const codesResult = await scanCodes(specs.specFiles, config.specGlobs, config.specIgnore);
|
|
469
|
+
const tableLines = [];
|
|
470
|
+
tableLines.push("");
|
|
471
|
+
tableLines.push(
|
|
472
|
+
"Run `awa spec codes` for the live inventory. The table below defines scope boundaries."
|
|
473
|
+
);
|
|
474
|
+
tableLines.push("");
|
|
475
|
+
tableLines.push("| Code | Feature | Scope Boundary |");
|
|
476
|
+
tableLines.push("|------|---------|----------------|");
|
|
477
|
+
const emptyScopeCodes = [];
|
|
478
|
+
for (const code of codesResult.codes) {
|
|
479
|
+
const scope = sanitizeCell(code.scope || "");
|
|
480
|
+
if (!scope) {
|
|
481
|
+
emptyScopeCodes.push(code.code);
|
|
482
|
+
}
|
|
483
|
+
tableLines.push(`| ${sanitizeCell(code.code)} | ${sanitizeCell(code.feature)} | ${scope} |`);
|
|
484
|
+
}
|
|
485
|
+
const newSection = tableLines.join("\n");
|
|
486
|
+
const newContent = replaceFeatureCodesSection(content, sectionStart, newSection);
|
|
487
|
+
if (newContent === content) {
|
|
488
|
+
return {
|
|
489
|
+
filesFixed: 0,
|
|
490
|
+
fileResults: [{ filePath: archFile.filePath, changed: false }],
|
|
491
|
+
emptyScopeCodes
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
await writeFile(archFile.filePath, newContent, "utf-8");
|
|
495
|
+
return {
|
|
496
|
+
filesFixed: 1,
|
|
497
|
+
fileResults: [{ filePath: archFile.filePath, changed: true }],
|
|
498
|
+
emptyScopeCodes
|
|
499
|
+
};
|
|
334
500
|
}
|
|
335
|
-
|
|
336
|
-
|
|
501
|
+
function replaceFeatureCodesSection(content, sectionStart, newSection) {
|
|
502
|
+
const lines = content.split("\n");
|
|
503
|
+
let sectionEnd = lines.length;
|
|
504
|
+
for (let i = sectionStart + 1; i < lines.length; i++) {
|
|
505
|
+
const line = lines[i];
|
|
506
|
+
if (line !== void 0 && /^##\s/.test(line)) {
|
|
507
|
+
sectionEnd = i;
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
const before = lines.slice(0, sectionStart + 1);
|
|
512
|
+
const after = lines.slice(sectionEnd);
|
|
513
|
+
const result = [...before, newSection.trimEnd(), "", ...after];
|
|
514
|
+
return result.join("\n");
|
|
337
515
|
}
|
|
338
516
|
|
|
339
517
|
// src/core/check/matrix-fixer.ts
|
|
340
|
-
import { readFile as
|
|
341
|
-
import { basename } from "path";
|
|
518
|
+
import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
519
|
+
import { basename as basename3 } from "path";
|
|
342
520
|
async function fixMatrices(specs, crossRefPatterns) {
|
|
343
521
|
const reqFileMaps = buildReqFileMaps(specs.specFiles);
|
|
344
522
|
const fileResults = [];
|
|
345
523
|
for (const specFile of specs.specFiles) {
|
|
346
|
-
const fileName =
|
|
524
|
+
const fileName = basename3(specFile.filePath);
|
|
347
525
|
if (fileName.startsWith("DESIGN-")) {
|
|
348
|
-
const changed = await fixDesignMatrix(
|
|
526
|
+
const changed = await fixDesignMatrix(
|
|
527
|
+
specFile.filePath,
|
|
528
|
+
reqFileMaps,
|
|
529
|
+
crossRefPatterns,
|
|
530
|
+
specFile.content
|
|
531
|
+
);
|
|
349
532
|
fileResults.push({ filePath: specFile.filePath, changed });
|
|
350
533
|
} else if (fileName.startsWith("TASK-")) {
|
|
351
|
-
const changed = await fixTaskMatrix(
|
|
534
|
+
const changed = await fixTaskMatrix(
|
|
535
|
+
specFile.filePath,
|
|
536
|
+
reqFileMaps,
|
|
537
|
+
specs,
|
|
538
|
+
crossRefPatterns,
|
|
539
|
+
specFile.content
|
|
540
|
+
);
|
|
352
541
|
fileResults.push({ filePath: specFile.filePath, changed });
|
|
353
542
|
}
|
|
354
543
|
}
|
|
@@ -361,7 +550,7 @@ function buildReqFileMaps(specFiles) {
|
|
|
361
550
|
const idToReqFile = /* @__PURE__ */ new Map();
|
|
362
551
|
const codeToReqFilesSet = /* @__PURE__ */ new Map();
|
|
363
552
|
for (const sf of specFiles) {
|
|
364
|
-
const fileName =
|
|
553
|
+
const fileName = basename3(sf.filePath);
|
|
365
554
|
if (!/\bREQ-/.test(fileName)) continue;
|
|
366
555
|
for (const reqId of sf.requirementIds) {
|
|
367
556
|
idToReqFile.set(reqId, fileName);
|
|
@@ -395,12 +584,16 @@ function resolveReqFile(id, maps) {
|
|
|
395
584
|
}
|
|
396
585
|
return void 0;
|
|
397
586
|
}
|
|
398
|
-
async function fixDesignMatrix(filePath, reqFileMaps, crossRefPatterns) {
|
|
587
|
+
async function fixDesignMatrix(filePath, reqFileMaps, crossRefPatterns, cachedContent) {
|
|
399
588
|
let content;
|
|
400
|
-
|
|
401
|
-
content =
|
|
402
|
-
}
|
|
403
|
-
|
|
589
|
+
if (cachedContent != null) {
|
|
590
|
+
content = cachedContent;
|
|
591
|
+
} else {
|
|
592
|
+
try {
|
|
593
|
+
content = await readFile3(filePath, "utf-8");
|
|
594
|
+
} catch {
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
404
597
|
}
|
|
405
598
|
const { components, properties } = parseDesignFileData(content, crossRefPatterns);
|
|
406
599
|
const acToComponents = /* @__PURE__ */ new Map();
|
|
@@ -424,7 +617,7 @@ async function fixDesignMatrix(filePath, reqFileMaps, crossRefPatterns) {
|
|
|
424
617
|
const newSection = generateDesignSection(grouped, acToComponents, acToProperties);
|
|
425
618
|
const newContent = replaceTraceabilitySection(content, newSection);
|
|
426
619
|
if (newContent === content) return false;
|
|
427
|
-
await
|
|
620
|
+
await writeFile2(filePath, newContent, "utf-8");
|
|
428
621
|
return true;
|
|
429
622
|
}
|
|
430
623
|
function parseDesignFileData(content, crossRefPatterns) {
|
|
@@ -495,12 +688,16 @@ function generateDesignSection(grouped, acToComponents, acToProperties) {
|
|
|
495
688
|
}
|
|
496
689
|
return lines.join("\n");
|
|
497
690
|
}
|
|
498
|
-
async function fixTaskMatrix(filePath, reqFileMaps, specs, crossRefPatterns) {
|
|
691
|
+
async function fixTaskMatrix(filePath, reqFileMaps, specs, crossRefPatterns, cachedContent) {
|
|
499
692
|
let content;
|
|
500
|
-
|
|
501
|
-
content =
|
|
502
|
-
}
|
|
503
|
-
|
|
693
|
+
if (cachedContent != null) {
|
|
694
|
+
content = cachedContent;
|
|
695
|
+
} else {
|
|
696
|
+
try {
|
|
697
|
+
content = await readFile3(filePath, "utf-8");
|
|
698
|
+
} catch {
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
504
701
|
}
|
|
505
702
|
const { tasks, sourceDesigns } = parseTaskFileData(content, crossRefPatterns);
|
|
506
703
|
const acToTasks = /* @__PURE__ */ new Map();
|
|
@@ -523,7 +720,7 @@ async function fixTaskMatrix(filePath, reqFileMaps, specs, crossRefPatterns) {
|
|
|
523
720
|
for (const designName of sourceDesigns) {
|
|
524
721
|
const designCode = extractCodeFromFileName(designName);
|
|
525
722
|
for (const sf of specs.specFiles) {
|
|
526
|
-
if (sf.code === designCode && /\bDESIGN-/.test(
|
|
723
|
+
if (sf.code === designCode && /\bDESIGN-/.test(basename3(sf.filePath))) {
|
|
527
724
|
for (const propId of sf.propertyIds) sourcePropertyIds.add(propId);
|
|
528
725
|
}
|
|
529
726
|
}
|
|
@@ -533,7 +730,7 @@ async function fixTaskMatrix(filePath, reqFileMaps, specs, crossRefPatterns) {
|
|
|
533
730
|
const newSection = generateTaskSection(grouped, acToTasks, idToTestTasks, sourcePropertyIds);
|
|
534
731
|
const newContent = replaceTraceabilitySection(content, newSection);
|
|
535
732
|
if (newContent === content) return false;
|
|
536
|
-
await
|
|
733
|
+
await writeFile2(filePath, newContent, "utf-8");
|
|
537
734
|
return true;
|
|
538
735
|
}
|
|
539
736
|
function parseTaskFileData(content, crossRefPatterns) {
|
|
@@ -663,7 +860,7 @@ function extractIdsFromText(text) {
|
|
|
663
860
|
return ids;
|
|
664
861
|
}
|
|
665
862
|
function extractCodeFromFileName(fileName) {
|
|
666
|
-
const match = /^(?:REQ|DESIGN|FEAT|
|
|
863
|
+
const match = /^(?:REQ|DESIGN|FEAT|EXAMPLE|API|TASK)-([A-Z][A-Z0-9]*)-/.exec(fileName);
|
|
667
864
|
return match?.[1] ?? "";
|
|
668
865
|
}
|
|
669
866
|
function compareIds(a, b) {
|
|
@@ -745,7 +942,7 @@ function printRuleContext(f) {
|
|
|
745
942
|
}
|
|
746
943
|
|
|
747
944
|
// src/core/check/rule-loader.ts
|
|
748
|
-
import { readFile as
|
|
945
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
749
946
|
import { join } from "path";
|
|
750
947
|
import { parse as parseYaml } from "yaml";
|
|
751
948
|
async function loadRules(schemaDir) {
|
|
@@ -766,7 +963,7 @@ function matchesTargetGlob(filePath, targetGlob) {
|
|
|
766
963
|
async function loadRuleFile(filePath) {
|
|
767
964
|
let content;
|
|
768
965
|
try {
|
|
769
|
-
content = await
|
|
966
|
+
content = await readFile4(filePath, "utf-8");
|
|
770
967
|
} catch {
|
|
771
968
|
return null;
|
|
772
969
|
}
|
|
@@ -958,7 +1155,7 @@ var RuleValidationError = class extends Error {
|
|
|
958
1155
|
};
|
|
959
1156
|
|
|
960
1157
|
// src/core/check/schema-checker.ts
|
|
961
|
-
import { readFile as
|
|
1158
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
962
1159
|
import remarkGfm from "remark-gfm";
|
|
963
1160
|
import remarkParse from "remark-parse";
|
|
964
1161
|
import { unified } from "unified";
|
|
@@ -1000,14 +1197,18 @@ function formatProhibitedRule(pattern) {
|
|
|
1000
1197
|
async function checkSchemasAsync(specFiles, ruleSets) {
|
|
1001
1198
|
const findings = [];
|
|
1002
1199
|
const parser = unified().use(remarkParse).use(remarkGfm);
|
|
1003
|
-
for (const
|
|
1004
|
-
const matchingRules = ruleSets.filter((rs) => matchesTargetGlob(
|
|
1200
|
+
for (const spec2 of specFiles) {
|
|
1201
|
+
const matchingRules = ruleSets.filter((rs) => matchesTargetGlob(spec2.filePath, rs.targetGlob));
|
|
1005
1202
|
if (matchingRules.length === 0) continue;
|
|
1006
1203
|
let content;
|
|
1007
|
-
|
|
1008
|
-
content =
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1204
|
+
if (spec2.content != null) {
|
|
1205
|
+
content = spec2.content;
|
|
1206
|
+
} else {
|
|
1207
|
+
try {
|
|
1208
|
+
content = await readFile5(spec2.filePath, "utf-8");
|
|
1209
|
+
} catch {
|
|
1210
|
+
continue;
|
|
1211
|
+
}
|
|
1011
1212
|
}
|
|
1012
1213
|
const tree = parser.parse(content);
|
|
1013
1214
|
const sectionTree = buildSectionTree(tree);
|
|
@@ -1021,7 +1222,7 @@ async function checkSchemasAsync(specFiles, ruleSets) {
|
|
|
1021
1222
|
severity: "warning",
|
|
1022
1223
|
code: "schema-line-limit",
|
|
1023
1224
|
message: `File has ${lineCount} lines, exceeds limit of ${ruleSet.ruleFile["line-limit"]}`,
|
|
1024
|
-
filePath:
|
|
1225
|
+
filePath: spec2.filePath,
|
|
1025
1226
|
ruleSource,
|
|
1026
1227
|
rule: formatLineLimitRule(ruleSet.ruleFile["line-limit"])
|
|
1027
1228
|
});
|
|
@@ -1031,7 +1232,7 @@ async function checkSchemasAsync(specFiles, ruleSets) {
|
|
|
1031
1232
|
...checkRulesAgainstSections(
|
|
1032
1233
|
allSections,
|
|
1033
1234
|
ruleSet.ruleFile.sections,
|
|
1034
|
-
|
|
1235
|
+
spec2.filePath,
|
|
1035
1236
|
ruleSource
|
|
1036
1237
|
)
|
|
1037
1238
|
);
|
|
@@ -1040,7 +1241,7 @@ async function checkSchemasAsync(specFiles, ruleSets) {
|
|
|
1040
1241
|
...checkProhibited(
|
|
1041
1242
|
content,
|
|
1042
1243
|
ruleSet.ruleFile["sections-prohibited"],
|
|
1043
|
-
|
|
1244
|
+
spec2.filePath,
|
|
1044
1245
|
ruleSource
|
|
1045
1246
|
)
|
|
1046
1247
|
);
|
|
@@ -1449,131 +1650,6 @@ function collectAllCodeBlocks(section) {
|
|
|
1449
1650
|
return blocks;
|
|
1450
1651
|
}
|
|
1451
1652
|
|
|
1452
|
-
// src/core/check/spec-parser.ts
|
|
1453
|
-
import { readFile as readFile5 } from "fs/promises";
|
|
1454
|
-
import { basename as basename2 } from "path";
|
|
1455
|
-
async function parseSpecs(config) {
|
|
1456
|
-
const files = await collectSpecFiles(config.specGlobs, config.specIgnore);
|
|
1457
|
-
const specFiles = [];
|
|
1458
|
-
const requirementIds = /* @__PURE__ */ new Set();
|
|
1459
|
-
const acIds = /* @__PURE__ */ new Set();
|
|
1460
|
-
const propertyIds = /* @__PURE__ */ new Set();
|
|
1461
|
-
const componentNames = /* @__PURE__ */ new Set();
|
|
1462
|
-
const idLocations = /* @__PURE__ */ new Map();
|
|
1463
|
-
for (const filePath of files) {
|
|
1464
|
-
const specFile = await parseSpecFile(filePath, config.crossRefPatterns);
|
|
1465
|
-
if (specFile) {
|
|
1466
|
-
specFiles.push(specFile);
|
|
1467
|
-
for (const id of specFile.requirementIds) requirementIds.add(id);
|
|
1468
|
-
for (const id of specFile.acIds) acIds.add(id);
|
|
1469
|
-
for (const id of specFile.propertyIds) propertyIds.add(id);
|
|
1470
|
-
for (const name of specFile.componentNames) componentNames.add(name);
|
|
1471
|
-
for (const [id, loc] of specFile.idLocations ?? []) {
|
|
1472
|
-
idLocations.set(id, loc);
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
const allIds = /* @__PURE__ */ new Set([...requirementIds, ...acIds, ...propertyIds, ...componentNames]);
|
|
1477
|
-
return { requirementIds, acIds, propertyIds, componentNames, allIds, specFiles, idLocations };
|
|
1478
|
-
}
|
|
1479
|
-
async function parseSpecFile(filePath, crossRefPatterns) {
|
|
1480
|
-
let content;
|
|
1481
|
-
try {
|
|
1482
|
-
content = await readFile5(filePath, "utf-8");
|
|
1483
|
-
} catch {
|
|
1484
|
-
return null;
|
|
1485
|
-
}
|
|
1486
|
-
const code = extractCodePrefix(filePath);
|
|
1487
|
-
const lines = content.split("\n");
|
|
1488
|
-
const requirementIds = [];
|
|
1489
|
-
const acIds = [];
|
|
1490
|
-
const propertyIds = [];
|
|
1491
|
-
const componentNames = [];
|
|
1492
|
-
const crossRefs = [];
|
|
1493
|
-
const idLocations = /* @__PURE__ */ new Map();
|
|
1494
|
-
const componentImplements = /* @__PURE__ */ new Map();
|
|
1495
|
-
const reqIdRegex = /^###\s+([A-Z][A-Z0-9]*-\d+(?:\.\d+)?)\s*:/;
|
|
1496
|
-
const acIdRegex = /^-\s+(?:\[[ x]\]\s+)?([A-Z][A-Z0-9]*-\d+(?:\.\d+)?_AC-\d+)\s/;
|
|
1497
|
-
const propIdRegex = /^-\s+([A-Z][A-Z0-9]*_P-\d+)\s/;
|
|
1498
|
-
const componentRegex = /^###\s+([A-Z][A-Z0-9]*-[A-Za-z][A-Za-z0-9]*(?:[A-Z][a-z0-9]*)*)\s*$/;
|
|
1499
|
-
let currentComponent = null;
|
|
1500
|
-
for (const [i, line] of lines.entries()) {
|
|
1501
|
-
const lineNum = i + 1;
|
|
1502
|
-
const reqMatch = reqIdRegex.exec(line);
|
|
1503
|
-
if (reqMatch?.[1]) {
|
|
1504
|
-
requirementIds.push(reqMatch[1]);
|
|
1505
|
-
idLocations.set(reqMatch[1], { filePath, line: lineNum });
|
|
1506
|
-
}
|
|
1507
|
-
const acMatch = acIdRegex.exec(line);
|
|
1508
|
-
if (acMatch?.[1]) {
|
|
1509
|
-
acIds.push(acMatch[1]);
|
|
1510
|
-
idLocations.set(acMatch[1], { filePath, line: lineNum });
|
|
1511
|
-
}
|
|
1512
|
-
const propMatch = propIdRegex.exec(line);
|
|
1513
|
-
if (propMatch?.[1]) {
|
|
1514
|
-
propertyIds.push(propMatch[1]);
|
|
1515
|
-
idLocations.set(propMatch[1], { filePath, line: lineNum });
|
|
1516
|
-
}
|
|
1517
|
-
const compMatch = componentRegex.exec(line);
|
|
1518
|
-
if (compMatch?.[1]) {
|
|
1519
|
-
if (!reqIdRegex.test(line)) {
|
|
1520
|
-
componentNames.push(compMatch[1]);
|
|
1521
|
-
idLocations.set(compMatch[1], { filePath, line: lineNum });
|
|
1522
|
-
currentComponent = compMatch[1];
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
if (/^#{1,2}\s/.test(line) && !compMatch) {
|
|
1526
|
-
currentComponent = null;
|
|
1527
|
-
}
|
|
1528
|
-
for (const pattern of crossRefPatterns) {
|
|
1529
|
-
const patIdx = line.indexOf(pattern);
|
|
1530
|
-
if (patIdx !== -1) {
|
|
1531
|
-
const afterPattern = line.slice(patIdx + pattern.length);
|
|
1532
|
-
const ids = extractIdsFromText2(afterPattern);
|
|
1533
|
-
if (ids.length > 0) {
|
|
1534
|
-
const type = pattern.toLowerCase().includes("implements") ? "implements" : "validates";
|
|
1535
|
-
crossRefs.push({ type, ids, filePath, line: i + 1 });
|
|
1536
|
-
if (type === "implements" && currentComponent) {
|
|
1537
|
-
const existing = componentImplements.get(currentComponent) ?? [];
|
|
1538
|
-
existing.push(...ids);
|
|
1539
|
-
componentImplements.set(currentComponent, existing);
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
return {
|
|
1546
|
-
filePath,
|
|
1547
|
-
code,
|
|
1548
|
-
requirementIds,
|
|
1549
|
-
acIds,
|
|
1550
|
-
propertyIds,
|
|
1551
|
-
componentNames,
|
|
1552
|
-
crossRefs,
|
|
1553
|
-
idLocations,
|
|
1554
|
-
componentImplements
|
|
1555
|
-
};
|
|
1556
|
-
}
|
|
1557
|
-
function extractCodePrefix(filePath) {
|
|
1558
|
-
const name = basename2(filePath, ".md");
|
|
1559
|
-
const match = /^(?:REQ|DESIGN|FEAT|EXAMPLES|API)-([A-Z][A-Z0-9]*)-/.exec(name);
|
|
1560
|
-
if (match?.[1]) return match[1];
|
|
1561
|
-
return "";
|
|
1562
|
-
}
|
|
1563
|
-
function extractIdsFromText2(text) {
|
|
1564
|
-
const idRegex = /[A-Z][A-Z0-9]*-\d+(?:\.\d+)?(?:_AC-\d+)?|[A-Z][A-Z0-9]*_P-\d+/g;
|
|
1565
|
-
const ids = [];
|
|
1566
|
-
let match = idRegex.exec(text);
|
|
1567
|
-
while (match !== null) {
|
|
1568
|
-
ids.push(match[0]);
|
|
1569
|
-
match = idRegex.exec(text);
|
|
1570
|
-
}
|
|
1571
|
-
return ids;
|
|
1572
|
-
}
|
|
1573
|
-
async function collectSpecFiles(specGlobs, ignore) {
|
|
1574
|
-
return collectFiles(specGlobs, ignore);
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
1653
|
// src/core/check/spec-spec-checker.ts
|
|
1578
1654
|
function checkSpecAgainstSpec(specs, markers, config) {
|
|
1579
1655
|
const findings = [];
|
|
@@ -1658,44 +1734,6 @@ function checkSpecAgainstSpec(specs, markers, config) {
|
|
|
1658
1734
|
return { findings };
|
|
1659
1735
|
}
|
|
1660
1736
|
|
|
1661
|
-
// src/core/check/types.ts
|
|
1662
|
-
var DEFAULT_CHECK_CONFIG = {
|
|
1663
|
-
specGlobs: [
|
|
1664
|
-
".awa/specs/ARCHITECTURE.md",
|
|
1665
|
-
".awa/specs/FEAT-*.md",
|
|
1666
|
-
".awa/specs/REQ-*.md",
|
|
1667
|
-
".awa/specs/DESIGN-*.md",
|
|
1668
|
-
".awa/specs/EXAMPLES-*.md",
|
|
1669
|
-
".awa/specs/API-*.tsp",
|
|
1670
|
-
".awa/tasks/TASK-*.md",
|
|
1671
|
-
".awa/plans/PLAN-*.md",
|
|
1672
|
-
".awa/align/ALIGN-*.md"
|
|
1673
|
-
],
|
|
1674
|
-
codeGlobs: [
|
|
1675
|
-
"**/*.{ts,js,tsx,jsx,mts,mjs,cjs,py,go,rs,java,kt,kts,cs,c,h,cpp,cc,cxx,hpp,hxx,swift,rb,php,scala,ex,exs,dart,lua,zig}"
|
|
1676
|
-
],
|
|
1677
|
-
specIgnore: [],
|
|
1678
|
-
codeIgnore: [
|
|
1679
|
-
"node_modules/**",
|
|
1680
|
-
"dist/**",
|
|
1681
|
-
"vendor/**",
|
|
1682
|
-
"target/**",
|
|
1683
|
-
"build/**",
|
|
1684
|
-
"out/**",
|
|
1685
|
-
".awa/**"
|
|
1686
|
-
],
|
|
1687
|
-
ignoreMarkers: [],
|
|
1688
|
-
markers: ["@awa-impl", "@awa-test", "@awa-component"],
|
|
1689
|
-
idPattern: "([A-Z][A-Z0-9]*-\\d+(?:\\.\\d+)?(?:_AC-\\d+)?|[A-Z][A-Z0-9]*_P-\\d+)",
|
|
1690
|
-
crossRefPatterns: ["IMPLEMENTS:", "VALIDATES:"],
|
|
1691
|
-
format: "text",
|
|
1692
|
-
schemaDir: ".awa/.agent/schemas",
|
|
1693
|
-
schemaEnabled: true,
|
|
1694
|
-
allowWarnings: false,
|
|
1695
|
-
specOnly: false,
|
|
1696
|
-
fix: true
|
|
1697
|
-
};
|
|
1698
|
-
|
|
1699
1737
|
// src/commands/check.ts
|
|
1700
1738
|
async function checkCommand(cliOptions) {
|
|
1701
1739
|
try {
|
|
@@ -1712,6 +1750,21 @@ async function checkCommand(cliOptions) {
|
|
|
1712
1750
|
]);
|
|
1713
1751
|
const codeSpecResult = config.specOnly ? { findings: [] } : checkCodeAgainstSpec(markers, specs, config);
|
|
1714
1752
|
const specSpecResult = checkSpecAgainstSpec(specs, markers, config);
|
|
1753
|
+
if (config.fix) {
|
|
1754
|
+
const fixResult = await fixMatrices(specs, config.crossRefPatterns);
|
|
1755
|
+
if (fixResult.filesFixed > 0) {
|
|
1756
|
+
logger.info(`Fixed traceability matrices in ${fixResult.filesFixed} file(s)`);
|
|
1757
|
+
}
|
|
1758
|
+
const codesFixResult = await fixCodesTable(specs, config);
|
|
1759
|
+
if (codesFixResult.filesFixed > 0) {
|
|
1760
|
+
logger.info("Fixed Feature Codes table in ARCHITECTURE.md");
|
|
1761
|
+
}
|
|
1762
|
+
if (codesFixResult.emptyScopeCodes.length > 0) {
|
|
1763
|
+
logger.warn(
|
|
1764
|
+
`Feature codes missing Scope Boundary: ${codesFixResult.emptyScopeCodes.join(", ")}`
|
|
1765
|
+
);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1715
1768
|
const schemaResult = config.schemaEnabled && ruleSets.length > 0 ? await checkSchemasAsync(specs.specFiles, ruleSets) : { findings: [] };
|
|
1716
1769
|
const combinedFindings = [
|
|
1717
1770
|
...markers.findings,
|
|
@@ -1730,12 +1783,6 @@ async function checkCommand(cliOptions) {
|
|
|
1730
1783
|
} else {
|
|
1731
1784
|
report(allFindings, config.format);
|
|
1732
1785
|
}
|
|
1733
|
-
if (config.fix) {
|
|
1734
|
-
const fixResult = await fixMatrices(specs, config.crossRefPatterns);
|
|
1735
|
-
if (fixResult.filesFixed > 0) {
|
|
1736
|
-
logger.info(`Fixed traceability matrices in ${fixResult.filesFixed} file(s)`);
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
1786
|
const hasErrors = allFindings.some((f) => f.severity === "error");
|
|
1740
1787
|
return hasErrors ? 1 : 0;
|
|
1741
1788
|
} catch (error) {
|
|
@@ -1797,6 +1844,98 @@ function toStringArray(value) {
|
|
|
1797
1844
|
return null;
|
|
1798
1845
|
}
|
|
1799
1846
|
|
|
1847
|
+
// src/core/codes/reporter.ts
|
|
1848
|
+
import chalk2 from "chalk";
|
|
1849
|
+
function formatDocs(docs) {
|
|
1850
|
+
return [
|
|
1851
|
+
docs.feat ? "F" : "\xB7",
|
|
1852
|
+
docs.req ? "R" : "\xB7",
|
|
1853
|
+
docs.design ? "D" : "\xB7",
|
|
1854
|
+
docs.api ? "A" : "\xB7",
|
|
1855
|
+
docs.example ? "E" : "\xB7"
|
|
1856
|
+
].join("");
|
|
1857
|
+
}
|
|
1858
|
+
function buildJsonOutput(result) {
|
|
1859
|
+
return {
|
|
1860
|
+
codes: result.codes.map((c) => ({
|
|
1861
|
+
code: c.code,
|
|
1862
|
+
feature: c.feature,
|
|
1863
|
+
reqCount: c.reqCount,
|
|
1864
|
+
docs: formatDocs(c.docs),
|
|
1865
|
+
scope: c.scope
|
|
1866
|
+
})),
|
|
1867
|
+
totalCodes: result.codes.length
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
function formatJson(result) {
|
|
1871
|
+
return JSON.stringify(buildJsonOutput(result), null, 2);
|
|
1872
|
+
}
|
|
1873
|
+
function formatTable(result) {
|
|
1874
|
+
const { codes } = result;
|
|
1875
|
+
if (codes.length === 0) {
|
|
1876
|
+
return chalk2.yellow("No feature codes found.");
|
|
1877
|
+
}
|
|
1878
|
+
const codeWidth = Math.max(4, ...codes.map((c) => c.code.length));
|
|
1879
|
+
const featureWidth = Math.max(7, ...codes.map((c) => c.feature.length));
|
|
1880
|
+
const reqWidth = 4;
|
|
1881
|
+
const docsWidth = 5;
|
|
1882
|
+
const header = [
|
|
1883
|
+
"CODE".padEnd(codeWidth),
|
|
1884
|
+
"Feature".padEnd(featureWidth),
|
|
1885
|
+
"Reqs".padStart(reqWidth),
|
|
1886
|
+
"Docs".padEnd(docsWidth),
|
|
1887
|
+
"Scope"
|
|
1888
|
+
].join(" ");
|
|
1889
|
+
const separator = [
|
|
1890
|
+
"\u2500".repeat(codeWidth),
|
|
1891
|
+
"\u2500".repeat(featureWidth),
|
|
1892
|
+
"\u2500".repeat(reqWidth),
|
|
1893
|
+
"\u2500".repeat(docsWidth),
|
|
1894
|
+
"\u2500".repeat(40)
|
|
1895
|
+
].join(" ");
|
|
1896
|
+
const rows = codes.map((c) => {
|
|
1897
|
+
const docs = formatDocs(c.docs);
|
|
1898
|
+
return [
|
|
1899
|
+
chalk2.cyan(c.code.padEnd(codeWidth)),
|
|
1900
|
+
c.feature.padEnd(featureWidth),
|
|
1901
|
+
String(c.reqCount).padStart(reqWidth),
|
|
1902
|
+
docs,
|
|
1903
|
+
chalk2.dim(c.scope)
|
|
1904
|
+
].join(" ");
|
|
1905
|
+
});
|
|
1906
|
+
return [chalk2.bold(`Feature codes (${codes.length}):
|
|
1907
|
+
`), header, separator, ...rows].join("\n");
|
|
1908
|
+
}
|
|
1909
|
+
function formatSummary(result) {
|
|
1910
|
+
return `codes: ${result.codes.length}`;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
// src/commands/codes.ts
|
|
1914
|
+
async function codesCommand(options) {
|
|
1915
|
+
try {
|
|
1916
|
+
const { specs, config } = await scan(options.config);
|
|
1917
|
+
const result = await scanCodes(specs.specFiles, config.specGlobs, config.specIgnore);
|
|
1918
|
+
if (options.summary) {
|
|
1919
|
+
process.stdout.write(`${formatSummary(result)}
|
|
1920
|
+
`);
|
|
1921
|
+
} else if (options.json) {
|
|
1922
|
+
process.stdout.write(`${formatJson(result)}
|
|
1923
|
+
`);
|
|
1924
|
+
} else {
|
|
1925
|
+
process.stdout.write(`${formatTable(result)}
|
|
1926
|
+
`);
|
|
1927
|
+
}
|
|
1928
|
+
return 0;
|
|
1929
|
+
} catch (err) {
|
|
1930
|
+
if (err instanceof Error) {
|
|
1931
|
+
logger.error(err.message);
|
|
1932
|
+
} else {
|
|
1933
|
+
logger.error(String(err));
|
|
1934
|
+
}
|
|
1935
|
+
return 2;
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1800
1939
|
// src/commands/diff.ts
|
|
1801
1940
|
import { intro, outro } from "@clack/prompts";
|
|
1802
1941
|
|
|
@@ -1905,7 +2044,7 @@ import { join as join3, relative } from "path";
|
|
|
1905
2044
|
// src/core/resolver.ts
|
|
1906
2045
|
import { MultiSelectPrompt } from "@clack/core";
|
|
1907
2046
|
import { isCancel, multiselect } from "@clack/prompts";
|
|
1908
|
-
import
|
|
2047
|
+
import chalk3 from "chalk";
|
|
1909
2048
|
var _unicode = process.platform !== "win32";
|
|
1910
2049
|
var _s = (c, fb) => _unicode ? c : fb;
|
|
1911
2050
|
var _CHECKED = _s("\u25FC", "[+]");
|
|
@@ -1915,20 +2054,20 @@ var _BAR = _s("\u2502", "|");
|
|
|
1915
2054
|
var _BAR_END = _s("\u2514", "-");
|
|
1916
2055
|
function _renderDeleteItem(opt, state) {
|
|
1917
2056
|
const label = opt.label ?? opt.value;
|
|
1918
|
-
const hint = opt.hint ? ` ${
|
|
2057
|
+
const hint = opt.hint ? ` ${chalk3.dim(`(${opt.hint})`)}` : "";
|
|
1919
2058
|
switch (state) {
|
|
1920
2059
|
case "active":
|
|
1921
|
-
return `${
|
|
2060
|
+
return `${chalk3.cyan(_UNCHECKED_A)} ${label}${hint}`;
|
|
1922
2061
|
case "selected":
|
|
1923
|
-
return `${
|
|
2062
|
+
return `${chalk3.red(_CHECKED)} ${chalk3.dim(label)}${hint}`;
|
|
1924
2063
|
case "active-selected":
|
|
1925
|
-
return `${
|
|
2064
|
+
return `${chalk3.red(_CHECKED)} ${label}${hint}`;
|
|
1926
2065
|
case "cancelled":
|
|
1927
|
-
return
|
|
2066
|
+
return chalk3.strikethrough(chalk3.dim(label));
|
|
1928
2067
|
case "submitted":
|
|
1929
|
-
return
|
|
2068
|
+
return chalk3.dim(label);
|
|
1930
2069
|
default:
|
|
1931
|
-
return `${
|
|
2070
|
+
return `${chalk3.dim(_UNCHECKED)} ${chalk3.dim(label)}`;
|
|
1932
2071
|
}
|
|
1933
2072
|
}
|
|
1934
2073
|
async function deleteMultiselect(opts) {
|
|
@@ -1939,8 +2078,8 @@ async function deleteMultiselect(opts) {
|
|
|
1939
2078
|
required,
|
|
1940
2079
|
render() {
|
|
1941
2080
|
const self = this;
|
|
1942
|
-
const header = `${
|
|
1943
|
-
${
|
|
2081
|
+
const header = `${chalk3.gray(_BAR)}
|
|
2082
|
+
${chalk3.cyan(_BAR)} ${message}
|
|
1944
2083
|
`;
|
|
1945
2084
|
const getState = (opt, idx) => {
|
|
1946
2085
|
const active = idx === self.cursor;
|
|
@@ -1952,16 +2091,16 @@ ${chalk2.cyan(_BAR)} ${message}
|
|
|
1952
2091
|
};
|
|
1953
2092
|
switch (self.state) {
|
|
1954
2093
|
case "submit":
|
|
1955
|
-
return `${header}${
|
|
2094
|
+
return `${header}${chalk3.gray(_BAR)} ` + (self.options.filter((o) => self.value.includes(o.value)).map((o) => _renderDeleteItem(o, "submitted")).join(chalk3.dim(", ")) || chalk3.dim("none"));
|
|
1956
2095
|
case "cancel": {
|
|
1957
|
-
const cancelled = self.options.filter((o) => self.value.includes(o.value)).map((o) => _renderDeleteItem(o, "cancelled")).join(
|
|
1958
|
-
return `${header}${
|
|
1959
|
-
${
|
|
2096
|
+
const cancelled = self.options.filter((o) => self.value.includes(o.value)).map((o) => _renderDeleteItem(o, "cancelled")).join(chalk3.dim(", "));
|
|
2097
|
+
return `${header}${chalk3.gray(_BAR)} ${cancelled.trim() ? `${cancelled}
|
|
2098
|
+
${chalk3.gray(_BAR)}` : chalk3.dim("none")}`;
|
|
1960
2099
|
}
|
|
1961
2100
|
default:
|
|
1962
|
-
return `${header}${
|
|
1963
|
-
${
|
|
1964
|
-
${
|
|
2101
|
+
return `${header}${chalk3.cyan(_BAR)} ` + self.options.map((o, i) => _renderDeleteItem(o, getState(o, i))).join(`
|
|
2102
|
+
${chalk3.cyan(_BAR)} `) + `
|
|
2103
|
+
${chalk3.cyan(_BAR_END)}
|
|
1965
2104
|
`;
|
|
1966
2105
|
}
|
|
1967
2106
|
}
|
|
@@ -2057,11 +2196,9 @@ var EMPTY_FILE_MARKER = "<!-- AWA:EMPTY_FILE -->";
|
|
|
2057
2196
|
var TemplateEngine = class {
|
|
2058
2197
|
eta = null;
|
|
2059
2198
|
templateDir = null;
|
|
2060
|
-
compiledCache = /* @__PURE__ */ new Map();
|
|
2061
2199
|
// @awa-impl: TPL-8_AC-1, TPL-8_AC-2, TPL-8_AC-3, TPL-8_AC-4
|
|
2062
2200
|
configure(templateDir) {
|
|
2063
2201
|
this.templateDir = templateDir;
|
|
2064
|
-
this.compiledCache.clear();
|
|
2065
2202
|
this.eta = new Eta({
|
|
2066
2203
|
views: templateDir,
|
|
2067
2204
|
cache: true,
|
|
@@ -2882,7 +3019,7 @@ async function diffCommand(cliOptions) {
|
|
|
2882
3019
|
import { intro as intro2, outro as outro2 } from "@clack/prompts";
|
|
2883
3020
|
|
|
2884
3021
|
// src/core/features/reporter.ts
|
|
2885
|
-
import
|
|
3022
|
+
import chalk4 from "chalk";
|
|
2886
3023
|
var FeaturesReporter = class {
|
|
2887
3024
|
// @awa-impl: DISC-6_AC-1, DISC-7_AC-1
|
|
2888
3025
|
/** Render the features report to stdout. */
|
|
@@ -2917,25 +3054,25 @@ var FeaturesReporter = class {
|
|
|
2917
3054
|
reportTable(scanResult, presets) {
|
|
2918
3055
|
const { features, filesScanned } = scanResult;
|
|
2919
3056
|
if (features.length === 0) {
|
|
2920
|
-
console.log(
|
|
2921
|
-
console.log(
|
|
3057
|
+
console.log(chalk4.yellow("No feature flags found."));
|
|
3058
|
+
console.log(chalk4.dim(`(${filesScanned} files scanned)`));
|
|
2922
3059
|
return;
|
|
2923
3060
|
}
|
|
2924
|
-
console.log(
|
|
3061
|
+
console.log(chalk4.bold(`Feature flags (${features.length} found):
|
|
2925
3062
|
`));
|
|
2926
3063
|
for (const feature of features) {
|
|
2927
|
-
console.log(` ${
|
|
3064
|
+
console.log(` ${chalk4.cyan(feature.name)}`);
|
|
2928
3065
|
for (const file of feature.files) {
|
|
2929
|
-
console.log(` ${
|
|
3066
|
+
console.log(` ${chalk4.dim(file)}`);
|
|
2930
3067
|
}
|
|
2931
3068
|
}
|
|
2932
3069
|
console.log("");
|
|
2933
|
-
console.log(
|
|
3070
|
+
console.log(chalk4.dim(`${filesScanned} files scanned`));
|
|
2934
3071
|
if (presets && Object.keys(presets).length > 0) {
|
|
2935
3072
|
console.log("");
|
|
2936
|
-
console.log(
|
|
3073
|
+
console.log(chalk4.bold("Presets (from .awa.toml):\n"));
|
|
2937
3074
|
for (const [name, flags] of Object.entries(presets)) {
|
|
2938
|
-
console.log(` ${
|
|
3075
|
+
console.log(` ${chalk4.green(name)}: ${flags.join(", ")}`);
|
|
2939
3076
|
}
|
|
2940
3077
|
}
|
|
2941
3078
|
}
|
|
@@ -3168,25 +3305,663 @@ async function generateCommand(cliOptions) {
|
|
|
3168
3305
|
}
|
|
3169
3306
|
}
|
|
3170
3307
|
|
|
3308
|
+
// src/core/merge/content-merger.ts
|
|
3309
|
+
import { readFile as readFile7, rename, writeFile as writeFile3 } from "fs/promises";
|
|
3310
|
+
import { basename as basename4, dirname, extname, join as join8 } from "path";
|
|
3311
|
+
var MERGE_PREFIXES = ["FEAT", "REQ", "DESIGN", "API", "EXAMPLE", "TASK"];
|
|
3312
|
+
function resolveMovePath(sourceFilePath, prefix, sourceCode, targetCode, existingPaths, plannedPaths) {
|
|
3313
|
+
const dir = dirname(sourceFilePath);
|
|
3314
|
+
const name = basename4(sourceFilePath);
|
|
3315
|
+
const newName = name.replace(`${prefix}-${sourceCode}-`, `${prefix}-${targetCode}-`);
|
|
3316
|
+
const newPath = join8(dir, newName);
|
|
3317
|
+
if (!existingPaths.has(newPath) && !plannedPaths.has(newPath)) {
|
|
3318
|
+
return newPath;
|
|
3319
|
+
}
|
|
3320
|
+
const ext = extname(newName);
|
|
3321
|
+
const stem = newName.slice(0, -ext.length);
|
|
3322
|
+
for (let i = 1; i < 1e3; i++) {
|
|
3323
|
+
const indexed = join8(dir, `${stem}-${String(i).padStart(3, "0")}${ext}`);
|
|
3324
|
+
if (!existingPaths.has(indexed) && !plannedPaths.has(indexed)) {
|
|
3325
|
+
return indexed;
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
throw new Error(`Cannot resolve conflict for ${sourceFilePath}`);
|
|
3329
|
+
}
|
|
3330
|
+
function updateHeading(content, sourceCode, targetCode) {
|
|
3331
|
+
const lines = content.split("\n");
|
|
3332
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3333
|
+
const line = lines[i];
|
|
3334
|
+
if (/^#\s/.test(line)) {
|
|
3335
|
+
lines[i] = line.replaceAll(sourceCode, targetCode);
|
|
3336
|
+
break;
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
return lines.join("\n");
|
|
3340
|
+
}
|
|
3341
|
+
async function executeMoves(sourceCode, targetCode, specFiles, dryRun) {
|
|
3342
|
+
const moves = [];
|
|
3343
|
+
const sourceFilePaths = /* @__PURE__ */ new Set();
|
|
3344
|
+
for (const sf of specFiles) {
|
|
3345
|
+
const name = basename4(sf.filePath);
|
|
3346
|
+
for (const prefix of MERGE_PREFIXES) {
|
|
3347
|
+
if (name.startsWith(`${prefix}-${sourceCode}-`)) {
|
|
3348
|
+
sourceFilePaths.add(sf.filePath);
|
|
3349
|
+
break;
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
const existingPaths = new Set(
|
|
3354
|
+
specFiles.map((sf) => sf.filePath).filter((p) => !sourceFilePaths.has(p))
|
|
3355
|
+
);
|
|
3356
|
+
const plannedPaths = /* @__PURE__ */ new Set();
|
|
3357
|
+
for (const sf of specFiles) {
|
|
3358
|
+
const name = basename4(sf.filePath);
|
|
3359
|
+
let matchedPrefix = "";
|
|
3360
|
+
for (const prefix of MERGE_PREFIXES) {
|
|
3361
|
+
if (name.startsWith(`${prefix}-${sourceCode}-`)) {
|
|
3362
|
+
matchedPrefix = prefix;
|
|
3363
|
+
break;
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
if (!matchedPrefix) continue;
|
|
3367
|
+
const targetPath = resolveMovePath(
|
|
3368
|
+
sf.filePath,
|
|
3369
|
+
matchedPrefix,
|
|
3370
|
+
sourceCode,
|
|
3371
|
+
targetCode,
|
|
3372
|
+
existingPaths,
|
|
3373
|
+
plannedPaths
|
|
3374
|
+
);
|
|
3375
|
+
plannedPaths.add(targetPath);
|
|
3376
|
+
moves.push({
|
|
3377
|
+
sourceFile: sf.filePath,
|
|
3378
|
+
targetFile: targetPath,
|
|
3379
|
+
docType: matchedPrefix
|
|
3380
|
+
});
|
|
3381
|
+
if (!dryRun) {
|
|
3382
|
+
const content = await readFile7(sf.filePath, "utf-8");
|
|
3383
|
+
const updated = updateHeading(content, sourceCode, targetCode);
|
|
3384
|
+
await rename(sf.filePath, targetPath);
|
|
3385
|
+
if (updated !== content) {
|
|
3386
|
+
await writeFile3(targetPath, updated, "utf-8");
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
return moves;
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
// src/core/merge/reporter.ts
|
|
3394
|
+
function formatText(result, dryRun) {
|
|
3395
|
+
const lines = [];
|
|
3396
|
+
if (dryRun) {
|
|
3397
|
+
lines.push("DRY RUN \u2014 no files were modified\n");
|
|
3398
|
+
}
|
|
3399
|
+
if (result.noChange) {
|
|
3400
|
+
lines.push(`${result.sourceCode} \u2192 ${result.targetCode}: nothing to merge`);
|
|
3401
|
+
return lines.join("\n");
|
|
3402
|
+
}
|
|
3403
|
+
lines.push(
|
|
3404
|
+
`${result.sourceCode} \u2192 ${result.targetCode}: ${result.map.entries.size} ID(s) recoded
|
|
3405
|
+
`
|
|
3406
|
+
);
|
|
3407
|
+
if (result.map.entries.size > 0) {
|
|
3408
|
+
lines.push(" Old ID \u2192 New ID");
|
|
3409
|
+
lines.push(` ${"\u2500".repeat(40)}`);
|
|
3410
|
+
for (const [oldId, newId] of result.map.entries) {
|
|
3411
|
+
lines.push(` ${oldId} \u2192 ${newId}`);
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
if (result.moves.length > 0) {
|
|
3415
|
+
lines.push("");
|
|
3416
|
+
lines.push(` ${result.moves.length} file(s) moved:`);
|
|
3417
|
+
for (const m of result.moves) {
|
|
3418
|
+
lines.push(` ${m.sourceFile} \u2192 ${m.targetFile} (${m.docType})`);
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
if (result.affectedFiles.length > 0) {
|
|
3422
|
+
lines.push("");
|
|
3423
|
+
lines.push(
|
|
3424
|
+
` ${result.totalReplacements} replacement(s) in ${result.affectedFiles.length} file(s):`
|
|
3425
|
+
);
|
|
3426
|
+
for (const file of result.affectedFiles) {
|
|
3427
|
+
lines.push(` ${file.filePath} (${file.replacements.length})`);
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
if (result.staleRefs.length > 0) {
|
|
3431
|
+
lines.push("");
|
|
3432
|
+
lines.push(` \u2716 ${result.staleRefs.length} file(s) still reference ${result.sourceCode}:`);
|
|
3433
|
+
for (const ref of result.staleRefs) {
|
|
3434
|
+
lines.push(` ${ref}`);
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
return lines.join("\n");
|
|
3438
|
+
}
|
|
3439
|
+
function formatJson2(result) {
|
|
3440
|
+
const output = {
|
|
3441
|
+
sourceCode: result.sourceCode,
|
|
3442
|
+
targetCode: result.targetCode,
|
|
3443
|
+
noChange: result.noChange,
|
|
3444
|
+
map: Object.fromEntries(result.map.entries),
|
|
3445
|
+
moves: result.moves.map((m) => ({
|
|
3446
|
+
sourceFile: m.sourceFile,
|
|
3447
|
+
targetFile: m.targetFile,
|
|
3448
|
+
docType: m.docType
|
|
3449
|
+
})),
|
|
3450
|
+
staleRefs: result.staleRefs,
|
|
3451
|
+
affectedFiles: result.affectedFiles.map((f) => ({
|
|
3452
|
+
filePath: f.filePath,
|
|
3453
|
+
replacements: f.replacements.map((r) => ({
|
|
3454
|
+
line: r.line,
|
|
3455
|
+
oldId: r.oldId,
|
|
3456
|
+
newId: r.newId
|
|
3457
|
+
}))
|
|
3458
|
+
})),
|
|
3459
|
+
totalReplacements: result.totalReplacements
|
|
3460
|
+
};
|
|
3461
|
+
return JSON.stringify(output, null, 2);
|
|
3462
|
+
}
|
|
3463
|
+
|
|
3464
|
+
// src/core/merge/spec-mover.ts
|
|
3465
|
+
import { readFile as readFile8, rename as rename2 } from "fs/promises";
|
|
3466
|
+
import { basename as basename5, dirname as dirname2, join as join9 } from "path";
|
|
3467
|
+
|
|
3468
|
+
// src/core/merge/types.ts
|
|
3469
|
+
var MergeError = class extends Error {
|
|
3470
|
+
errorCode;
|
|
3471
|
+
constructor(errorCode, message) {
|
|
3472
|
+
super(message);
|
|
3473
|
+
this.name = "MergeError";
|
|
3474
|
+
this.errorCode = errorCode;
|
|
3475
|
+
}
|
|
3476
|
+
};
|
|
3477
|
+
|
|
3478
|
+
// src/core/merge/spec-mover.ts
|
|
3479
|
+
var CODE_PREFIXES = ["REQ", "DESIGN", "FEAT", "TASK", "EXAMPLE"];
|
|
3480
|
+
function resolveFeatureName(code, specFiles) {
|
|
3481
|
+
for (const sf of specFiles) {
|
|
3482
|
+
const name = basename5(sf.filePath, ".md");
|
|
3483
|
+
const match = new RegExp(`^REQ-${code}-(.+)$`).exec(name);
|
|
3484
|
+
if (match?.[1]) return match[1];
|
|
3485
|
+
}
|
|
3486
|
+
return void 0;
|
|
3487
|
+
}
|
|
3488
|
+
function planRenames(sourceCode, targetCode, specFiles) {
|
|
3489
|
+
const targetFeature = resolveFeatureName(targetCode, specFiles);
|
|
3490
|
+
const renames = [];
|
|
3491
|
+
for (const sf of specFiles) {
|
|
3492
|
+
const name = basename5(sf.filePath);
|
|
3493
|
+
for (const prefix of CODE_PREFIXES) {
|
|
3494
|
+
const oldPrefix = `${prefix}-${sourceCode}-`;
|
|
3495
|
+
if (name.startsWith(oldPrefix)) {
|
|
3496
|
+
const oldSuffix = name.slice(oldPrefix.length);
|
|
3497
|
+
const newSuffix = targetFeature ? replaceFeaturePart(oldSuffix, targetFeature) : oldSuffix;
|
|
3498
|
+
const newName = `${prefix}-${targetCode}-${newSuffix}`;
|
|
3499
|
+
const newPath = join9(dirname2(sf.filePath), newName);
|
|
3500
|
+
renames.push({ oldPath: sf.filePath, newPath });
|
|
3501
|
+
break;
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
return renames;
|
|
3506
|
+
}
|
|
3507
|
+
function replaceFeaturePart(suffix, targetFeature) {
|
|
3508
|
+
const numericMatch = /^(.+)-(\d+\.md)$/.exec(suffix);
|
|
3509
|
+
if (numericMatch) {
|
|
3510
|
+
return `${targetFeature}-${numericMatch[2]}`;
|
|
3511
|
+
}
|
|
3512
|
+
if (suffix.endsWith(".md")) {
|
|
3513
|
+
return `${targetFeature}.md`;
|
|
3514
|
+
}
|
|
3515
|
+
return suffix;
|
|
3516
|
+
}
|
|
3517
|
+
function detectConflicts(renames, specFiles) {
|
|
3518
|
+
const existingPaths = new Set(specFiles.map((sf) => sf.filePath));
|
|
3519
|
+
const conflicts = [];
|
|
3520
|
+
for (const r of renames) {
|
|
3521
|
+
if (existingPaths.has(r.newPath)) {
|
|
3522
|
+
conflicts.push(r.newPath);
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
return conflicts;
|
|
3526
|
+
}
|
|
3527
|
+
function updateHeading2(content, sourceCode, targetCode) {
|
|
3528
|
+
const lines = content.split("\n");
|
|
3529
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3530
|
+
const line = lines[i];
|
|
3531
|
+
if (/^#\s/.test(line)) {
|
|
3532
|
+
lines[i] = line.replaceAll(sourceCode, targetCode);
|
|
3533
|
+
break;
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
return lines.join("\n");
|
|
3537
|
+
}
|
|
3538
|
+
async function executeRenames(renames, sourceCode, targetCode, dryRun) {
|
|
3539
|
+
if (dryRun) return renames;
|
|
3540
|
+
for (const r of renames) {
|
|
3541
|
+
const content = await readFile8(r.oldPath, "utf-8");
|
|
3542
|
+
const updated = updateHeading2(content, sourceCode, targetCode);
|
|
3543
|
+
await rename2(r.oldPath, r.newPath);
|
|
3544
|
+
if (updated !== content) {
|
|
3545
|
+
const { writeFile: writeFile5 } = await import("fs/promises");
|
|
3546
|
+
await writeFile5(r.newPath, updated, "utf-8");
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
return renames;
|
|
3550
|
+
}
|
|
3551
|
+
async function findStaleRefs(sourceCode, specFiles) {
|
|
3552
|
+
const stale = [];
|
|
3553
|
+
const pattern = new RegExp(`\\b${sourceCode}-\\d`, "g");
|
|
3554
|
+
for (const sf of specFiles) {
|
|
3555
|
+
let content;
|
|
3556
|
+
try {
|
|
3557
|
+
content = await readFile8(sf.filePath, "utf-8");
|
|
3558
|
+
} catch {
|
|
3559
|
+
continue;
|
|
3560
|
+
}
|
|
3561
|
+
if (pattern.test(content)) {
|
|
3562
|
+
stale.push(sf.filePath);
|
|
3563
|
+
}
|
|
3564
|
+
pattern.lastIndex = 0;
|
|
3565
|
+
}
|
|
3566
|
+
return stale;
|
|
3567
|
+
}
|
|
3568
|
+
function validateMerge(sourceCode, targetCode) {
|
|
3569
|
+
if (sourceCode === targetCode) {
|
|
3570
|
+
throw new MergeError("SELF_MERGE", `Cannot merge a code into itself: ${sourceCode}`);
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
// src/core/recode/types.ts
|
|
3575
|
+
var RecodeError = class extends Error {
|
|
3576
|
+
errorCode;
|
|
3577
|
+
constructor(errorCode, message) {
|
|
3578
|
+
super(message);
|
|
3579
|
+
this.name = "RecodeError";
|
|
3580
|
+
this.errorCode = errorCode;
|
|
3581
|
+
}
|
|
3582
|
+
};
|
|
3583
|
+
|
|
3584
|
+
// src/core/recode/map-builder.ts
|
|
3585
|
+
function buildRecodeMap(sourceCode, targetCode, specs) {
|
|
3586
|
+
if (!hasAnySpecFile(specs.specFiles, sourceCode)) {
|
|
3587
|
+
throw new RecodeError("SOURCE_NOT_FOUND", `No spec files found for source code: ${sourceCode}`);
|
|
3588
|
+
}
|
|
3589
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3590
|
+
const sourceReqs = findSpecFiles(specs.specFiles, sourceCode, "REQ");
|
|
3591
|
+
if (sourceReqs.length > 0) {
|
|
3592
|
+
const targetReqs = findSpecFiles(specs.specFiles, targetCode, "REQ");
|
|
3593
|
+
const reqOffset = findHighestReqNumber(targetReqs);
|
|
3594
|
+
buildRequirementEntries(sourceCode, targetCode, sourceReqs, reqOffset, entries);
|
|
3595
|
+
}
|
|
3596
|
+
const sourceDesigns = findSpecFiles(specs.specFiles, sourceCode, "DESIGN");
|
|
3597
|
+
const targetDesigns = findSpecFiles(specs.specFiles, targetCode, "DESIGN");
|
|
3598
|
+
if (sourceDesigns.length > 0) {
|
|
3599
|
+
const propOffset = findHighestPropertyNumber(targetDesigns);
|
|
3600
|
+
buildPropertyEntries(sourceCode, targetCode, sourceDesigns, propOffset, entries);
|
|
3601
|
+
}
|
|
3602
|
+
if (sourceDesigns.length > 0) {
|
|
3603
|
+
buildComponentEntries(sourceCode, targetCode, sourceDesigns, entries);
|
|
3604
|
+
}
|
|
3605
|
+
const noChange = entries.size === 0;
|
|
3606
|
+
const map = { code: sourceCode, entries };
|
|
3607
|
+
return { map, noChange };
|
|
3608
|
+
}
|
|
3609
|
+
function buildRequirementEntries(_sourceCode, targetCode, sourceReqs, reqOffset, entries) {
|
|
3610
|
+
const topLevelReqs = [];
|
|
3611
|
+
const subReqsByParent = /* @__PURE__ */ new Map();
|
|
3612
|
+
const allAcIds = [];
|
|
3613
|
+
for (const sourceReq of sourceReqs) {
|
|
3614
|
+
for (const id of sourceReq.requirementIds) {
|
|
3615
|
+
if (id.includes(".")) {
|
|
3616
|
+
const dotIdx = id.lastIndexOf(".");
|
|
3617
|
+
const parent = id.slice(0, dotIdx);
|
|
3618
|
+
const subs = subReqsByParent.get(parent) ?? [];
|
|
3619
|
+
subs.push(id);
|
|
3620
|
+
subReqsByParent.set(parent, subs);
|
|
3621
|
+
} else {
|
|
3622
|
+
topLevelReqs.push(id);
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
for (const acId of sourceReq.acIds) {
|
|
3626
|
+
allAcIds.push(acId);
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
const reqNumberMap = /* @__PURE__ */ new Map();
|
|
3630
|
+
for (let i = 0; i < topLevelReqs.length; i++) {
|
|
3631
|
+
const oldId = topLevelReqs[i];
|
|
3632
|
+
const newNum = reqOffset + i + 1;
|
|
3633
|
+
const newId = `${targetCode}-${newNum}`;
|
|
3634
|
+
entries.set(oldId, newId);
|
|
3635
|
+
reqNumberMap.set(oldId, newNum);
|
|
3636
|
+
}
|
|
3637
|
+
for (const oldParentId of topLevelReqs) {
|
|
3638
|
+
const subs = subReqsByParent.get(oldParentId);
|
|
3639
|
+
if (!subs) continue;
|
|
3640
|
+
const newParentNum = reqNumberMap.get(oldParentId);
|
|
3641
|
+
if (newParentNum === void 0) continue;
|
|
3642
|
+
for (let j = 0; j < subs.length; j++) {
|
|
3643
|
+
const oldSubId = subs[j];
|
|
3644
|
+
const newSubNum = j + 1;
|
|
3645
|
+
const newSubId = `${targetCode}-${newParentNum}.${newSubNum}`;
|
|
3646
|
+
entries.set(oldSubId, newSubId);
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
const acsByParent = /* @__PURE__ */ new Map();
|
|
3650
|
+
for (const acId of allAcIds) {
|
|
3651
|
+
const parent = acId.split("_AC-")[0];
|
|
3652
|
+
const acs = acsByParent.get(parent) ?? [];
|
|
3653
|
+
acs.push(acId);
|
|
3654
|
+
acsByParent.set(parent, acs);
|
|
3655
|
+
}
|
|
3656
|
+
for (const [oldParentId, acs] of acsByParent) {
|
|
3657
|
+
const newParentId = entries.get(oldParentId) ?? oldParentId;
|
|
3658
|
+
for (let k = 0; k < acs.length; k++) {
|
|
3659
|
+
const oldAcId = acs[k];
|
|
3660
|
+
const newAcNum = k + 1;
|
|
3661
|
+
const newAcId = `${newParentId}_AC-${newAcNum}`;
|
|
3662
|
+
entries.set(oldAcId, newAcId);
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
function buildPropertyEntries(_sourceCode, targetCode, sourceDesigns, propOffset, entries) {
|
|
3667
|
+
let counter = 0;
|
|
3668
|
+
for (const sourceDesign of sourceDesigns) {
|
|
3669
|
+
for (const oldId of sourceDesign.propertyIds) {
|
|
3670
|
+
counter++;
|
|
3671
|
+
const newId = `${targetCode}_P-${propOffset + counter}`;
|
|
3672
|
+
entries.set(oldId, newId);
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
function buildComponentEntries(sourceCode, targetCode, sourceDesigns, entries) {
|
|
3677
|
+
for (const sourceDesign of sourceDesigns) {
|
|
3678
|
+
for (const compName of sourceDesign.componentNames) {
|
|
3679
|
+
const prefix = `${sourceCode}-`;
|
|
3680
|
+
if (compName.startsWith(prefix)) {
|
|
3681
|
+
const suffix = compName.slice(prefix.length);
|
|
3682
|
+
entries.set(compName, `${targetCode}-${suffix}`);
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
function findHighestReqNumber(reqFiles) {
|
|
3688
|
+
let max = 0;
|
|
3689
|
+
for (const reqFile of reqFiles) {
|
|
3690
|
+
for (const id of reqFile.requirementIds) {
|
|
3691
|
+
if (id.includes(".")) continue;
|
|
3692
|
+
const match = id.match(/-(\d+)$/);
|
|
3693
|
+
if (match) {
|
|
3694
|
+
const num = Number.parseInt(match[1], 10);
|
|
3695
|
+
if (num > max) max = num;
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
return max;
|
|
3700
|
+
}
|
|
3701
|
+
function findHighestPropertyNumber(designFiles) {
|
|
3702
|
+
let max = 0;
|
|
3703
|
+
for (const designFile of designFiles) {
|
|
3704
|
+
for (const id of designFile.propertyIds) {
|
|
3705
|
+
const match = id.match(/_P-(\d+)$/);
|
|
3706
|
+
if (match) {
|
|
3707
|
+
const num = Number.parseInt(match[1], 10);
|
|
3708
|
+
if (num > max) max = num;
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
}
|
|
3712
|
+
return max;
|
|
3713
|
+
}
|
|
3714
|
+
|
|
3715
|
+
// src/commands/merge.ts
|
|
3716
|
+
async function mergeCommand(options) {
|
|
3717
|
+
try {
|
|
3718
|
+
validateMerge(options.sourceCode, options.targetCode);
|
|
3719
|
+
const { markers, specs } = await scan(options.config);
|
|
3720
|
+
const dryRun = options.dryRun === true;
|
|
3721
|
+
if (!hasAnySpecFile(specs.specFiles, options.targetCode)) {
|
|
3722
|
+
throw new MergeError(
|
|
3723
|
+
"TARGET_NOT_FOUND",
|
|
3724
|
+
`No spec files found for target code: ${options.targetCode}. Use \`awa spec recode\` to rename a code to a new one.`
|
|
3725
|
+
);
|
|
3726
|
+
}
|
|
3727
|
+
const { map, noChange: recodeNoChange } = buildRecodeMap(
|
|
3728
|
+
options.sourceCode,
|
|
3729
|
+
options.targetCode,
|
|
3730
|
+
specs
|
|
3731
|
+
);
|
|
3732
|
+
let affectedFiles = [];
|
|
3733
|
+
let totalReplacements = 0;
|
|
3734
|
+
if (!recodeNoChange) {
|
|
3735
|
+
const result2 = await propagate(map, specs, markers, dryRun);
|
|
3736
|
+
affectedFiles = result2.affectedFiles;
|
|
3737
|
+
totalReplacements = result2.totalReplacements;
|
|
3738
|
+
}
|
|
3739
|
+
const moves = await executeMoves(
|
|
3740
|
+
options.sourceCode,
|
|
3741
|
+
options.targetCode,
|
|
3742
|
+
specs.specFiles,
|
|
3743
|
+
dryRun
|
|
3744
|
+
);
|
|
3745
|
+
const movedSourcePaths = new Set(moves.map((m) => m.sourceFile));
|
|
3746
|
+
const propagatedPaths = new Set(affectedFiles.map((af) => af.filePath));
|
|
3747
|
+
const nonSourceFiles = specs.specFiles.filter(
|
|
3748
|
+
(sf) => !movedSourcePaths.has(sf.filePath) && !propagatedPaths.has(sf.filePath)
|
|
3749
|
+
);
|
|
3750
|
+
const staleRefs = await findStaleRefs(options.sourceCode, nonSourceFiles);
|
|
3751
|
+
const noChange = recodeNoChange && moves.length === 0;
|
|
3752
|
+
const result = {
|
|
3753
|
+
sourceCode: options.sourceCode,
|
|
3754
|
+
targetCode: options.targetCode,
|
|
3755
|
+
map,
|
|
3756
|
+
affectedFiles,
|
|
3757
|
+
totalReplacements,
|
|
3758
|
+
moves,
|
|
3759
|
+
staleRefs,
|
|
3760
|
+
noChange
|
|
3761
|
+
};
|
|
3762
|
+
outputResult(result, dryRun, options.json === true);
|
|
3763
|
+
if (staleRefs.length > 0) {
|
|
3764
|
+
return 2;
|
|
3765
|
+
}
|
|
3766
|
+
if (options.renumber && !dryRun && !noChange) {
|
|
3767
|
+
const { renumberCommand: renumberCommand2 } = await import("./renumber-TLBGOWZM.js");
|
|
3768
|
+
await renumberCommand2({
|
|
3769
|
+
code: options.targetCode,
|
|
3770
|
+
dryRun: false,
|
|
3771
|
+
json: options.json,
|
|
3772
|
+
config: options.config
|
|
3773
|
+
});
|
|
3774
|
+
}
|
|
3775
|
+
if (!dryRun && !noChange) {
|
|
3776
|
+
const { specs: freshSpecs, config: scanConfig } = await scan(options.config);
|
|
3777
|
+
await fixCodesTable(freshSpecs, scanConfig);
|
|
3778
|
+
}
|
|
3779
|
+
return noChange ? 0 : 1;
|
|
3780
|
+
} catch (err) {
|
|
3781
|
+
if (err instanceof MergeError) {
|
|
3782
|
+
logger.error(err.message);
|
|
3783
|
+
return 2;
|
|
3784
|
+
}
|
|
3785
|
+
if (err instanceof RecodeError) {
|
|
3786
|
+
logger.error(err.message);
|
|
3787
|
+
return 2;
|
|
3788
|
+
}
|
|
3789
|
+
if (err instanceof Error) {
|
|
3790
|
+
logger.error(err.message);
|
|
3791
|
+
} else {
|
|
3792
|
+
logger.error(String(err));
|
|
3793
|
+
}
|
|
3794
|
+
return 2;
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
function outputResult(result, dryRun, json) {
|
|
3798
|
+
if (json) {
|
|
3799
|
+
process.stdout.write(`${formatJson2(result)}
|
|
3800
|
+
`);
|
|
3801
|
+
} else {
|
|
3802
|
+
const text = formatText(result, dryRun);
|
|
3803
|
+
logger.info(text);
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3807
|
+
// src/core/recode/reporter.ts
|
|
3808
|
+
function formatText2(result, dryRun) {
|
|
3809
|
+
const lines = [];
|
|
3810
|
+
if (dryRun) {
|
|
3811
|
+
lines.push("DRY RUN \u2014 no files were modified\n");
|
|
3812
|
+
}
|
|
3813
|
+
if (result.noChange) {
|
|
3814
|
+
lines.push(`${result.sourceCode} \u2192 ${result.targetCode}: no IDs to recode`);
|
|
3815
|
+
return lines.join("\n");
|
|
3816
|
+
}
|
|
3817
|
+
lines.push(
|
|
3818
|
+
`${result.sourceCode} \u2192 ${result.targetCode}: ${result.map.entries.size} ID(s) recoded
|
|
3819
|
+
`
|
|
3820
|
+
);
|
|
3821
|
+
lines.push(" Old ID \u2192 New ID");
|
|
3822
|
+
lines.push(` ${"\u2500".repeat(40)}`);
|
|
3823
|
+
for (const [oldId, newId] of result.map.entries) {
|
|
3824
|
+
lines.push(` ${oldId} \u2192 ${newId}`);
|
|
3825
|
+
}
|
|
3826
|
+
if (result.affectedFiles.length > 0) {
|
|
3827
|
+
lines.push("");
|
|
3828
|
+
lines.push(
|
|
3829
|
+
` ${result.totalReplacements} replacement(s) in ${result.affectedFiles.length} file(s):`
|
|
3830
|
+
);
|
|
3831
|
+
for (const file of result.affectedFiles) {
|
|
3832
|
+
lines.push(` ${file.filePath} (${file.replacements.length})`);
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
if (result.renames.length > 0) {
|
|
3836
|
+
lines.push("");
|
|
3837
|
+
lines.push(` ${result.renames.length} file(s) renamed:`);
|
|
3838
|
+
for (const r of result.renames) {
|
|
3839
|
+
lines.push(` ${r.oldPath} \u2192 ${r.newPath}`);
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
if (result.staleRefs.length > 0) {
|
|
3843
|
+
lines.push("");
|
|
3844
|
+
lines.push(` \u2716 ${result.staleRefs.length} file(s) still reference ${result.sourceCode}:`);
|
|
3845
|
+
for (const ref of result.staleRefs) {
|
|
3846
|
+
lines.push(` ${ref}`);
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
return lines.join("\n");
|
|
3850
|
+
}
|
|
3851
|
+
function formatJson3(result) {
|
|
3852
|
+
const output = {
|
|
3853
|
+
sourceCode: result.sourceCode,
|
|
3854
|
+
targetCode: result.targetCode,
|
|
3855
|
+
noChange: result.noChange,
|
|
3856
|
+
map: Object.fromEntries(result.map.entries),
|
|
3857
|
+
affectedFiles: result.affectedFiles.map((f) => ({
|
|
3858
|
+
filePath: f.filePath,
|
|
3859
|
+
replacements: f.replacements.map((r) => ({
|
|
3860
|
+
line: r.line,
|
|
3861
|
+
oldId: r.oldId,
|
|
3862
|
+
newId: r.newId
|
|
3863
|
+
}))
|
|
3864
|
+
})),
|
|
3865
|
+
totalReplacements: result.totalReplacements,
|
|
3866
|
+
renames: result.renames.map((r) => ({ oldPath: r.oldPath, newPath: r.newPath })),
|
|
3867
|
+
staleRefs: result.staleRefs
|
|
3868
|
+
};
|
|
3869
|
+
return JSON.stringify(output, null, 2);
|
|
3870
|
+
}
|
|
3871
|
+
|
|
3872
|
+
// src/commands/recode.ts
|
|
3873
|
+
async function recodeCommand(options) {
|
|
3874
|
+
try {
|
|
3875
|
+
const { markers, specs } = await scan(options.config);
|
|
3876
|
+
const dryRun = options.dryRun === true;
|
|
3877
|
+
const { map, noChange: recodeNoChange } = buildRecodeMap(
|
|
3878
|
+
options.sourceCode,
|
|
3879
|
+
options.targetCode,
|
|
3880
|
+
specs
|
|
3881
|
+
);
|
|
3882
|
+
let affectedFiles = [];
|
|
3883
|
+
let totalReplacements = 0;
|
|
3884
|
+
if (!recodeNoChange) {
|
|
3885
|
+
const result2 = await propagate(map, specs, markers, dryRun);
|
|
3886
|
+
affectedFiles = result2.affectedFiles;
|
|
3887
|
+
totalReplacements = result2.totalReplacements;
|
|
3888
|
+
}
|
|
3889
|
+
const renames = planRenames(options.sourceCode, options.targetCode, specs.specFiles);
|
|
3890
|
+
const conflicts = detectConflicts(renames, specs.specFiles);
|
|
3891
|
+
if (conflicts.length > 0) {
|
|
3892
|
+
throw new RecodeError(
|
|
3893
|
+
"RENAME_CONFLICT",
|
|
3894
|
+
`File rename conflict(s): ${conflicts.join(", ")}. Use \`awa spec merge\` to combine codes with overlapping files.`
|
|
3895
|
+
);
|
|
3896
|
+
}
|
|
3897
|
+
await executeRenames(renames, options.sourceCode, options.targetCode, dryRun);
|
|
3898
|
+
const propagatedPaths = new Set(affectedFiles.map((af) => af.filePath));
|
|
3899
|
+
const nonSourceFiles = specs.specFiles.filter(
|
|
3900
|
+
(sf) => !renames.some((r) => r.oldPath === sf.filePath) && !propagatedPaths.has(sf.filePath)
|
|
3901
|
+
);
|
|
3902
|
+
const staleRefs = await findStaleRefs(options.sourceCode, nonSourceFiles);
|
|
3903
|
+
const noChange = recodeNoChange && renames.length === 0;
|
|
3904
|
+
const result = {
|
|
3905
|
+
sourceCode: options.sourceCode,
|
|
3906
|
+
targetCode: options.targetCode,
|
|
3907
|
+
map,
|
|
3908
|
+
affectedFiles,
|
|
3909
|
+
totalReplacements,
|
|
3910
|
+
renames,
|
|
3911
|
+
staleRefs,
|
|
3912
|
+
noChange
|
|
3913
|
+
};
|
|
3914
|
+
outputResult2(result, dryRun, options.json === true);
|
|
3915
|
+
if (staleRefs.length > 0) {
|
|
3916
|
+
return 2;
|
|
3917
|
+
}
|
|
3918
|
+
if (!dryRun && !noChange) {
|
|
3919
|
+
const { specs: freshSpecs, config: scanConfig } = await scan(options.config);
|
|
3920
|
+
await fixCodesTable(freshSpecs, scanConfig);
|
|
3921
|
+
}
|
|
3922
|
+
return noChange ? 0 : 1;
|
|
3923
|
+
} catch (err) {
|
|
3924
|
+
if (err instanceof RecodeError) {
|
|
3925
|
+
logger.error(err.message);
|
|
3926
|
+
return 2;
|
|
3927
|
+
}
|
|
3928
|
+
if (err instanceof Error) {
|
|
3929
|
+
logger.error(err.message);
|
|
3930
|
+
} else {
|
|
3931
|
+
logger.error(String(err));
|
|
3932
|
+
}
|
|
3933
|
+
return 2;
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
function outputResult2(result, dryRun, json) {
|
|
3937
|
+
if (json) {
|
|
3938
|
+
process.stdout.write(`${formatJson3(result)}
|
|
3939
|
+
`);
|
|
3940
|
+
} else {
|
|
3941
|
+
const text = formatText2(result, dryRun);
|
|
3942
|
+
logger.info(text);
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3171
3946
|
// src/commands/test.ts
|
|
3172
3947
|
import { intro as intro4, outro as outro4 } from "@clack/prompts";
|
|
3173
3948
|
|
|
3174
3949
|
// src/core/template-test/fixture-loader.ts
|
|
3175
3950
|
import { readdir as readdir2 } from "fs/promises";
|
|
3176
|
-
import { basename as
|
|
3951
|
+
import { basename as basename6, extname as extname2, join as join10 } from "path";
|
|
3177
3952
|
import { parse } from "smol-toml";
|
|
3178
3953
|
async function discoverFixtures(templatePath) {
|
|
3179
|
-
const testsDir =
|
|
3954
|
+
const testsDir = join10(templatePath, "_tests");
|
|
3180
3955
|
let entries;
|
|
3181
3956
|
try {
|
|
3182
3957
|
const dirEntries = await readdir2(testsDir, { withFileTypes: true });
|
|
3183
|
-
entries = dirEntries.filter((e) => e.isFile() &&
|
|
3958
|
+
entries = dirEntries.filter((e) => e.isFile() && extname2(e.name) === ".toml").map((e) => e.name).sort();
|
|
3184
3959
|
} catch {
|
|
3185
3960
|
return [];
|
|
3186
3961
|
}
|
|
3187
3962
|
const fixtures = [];
|
|
3188
3963
|
for (const filename of entries) {
|
|
3189
|
-
const filePath =
|
|
3964
|
+
const filePath = join10(testsDir, filename);
|
|
3190
3965
|
const fixture = await parseFixture(filePath);
|
|
3191
3966
|
fixtures.push(fixture);
|
|
3192
3967
|
}
|
|
@@ -3195,7 +3970,7 @@ async function discoverFixtures(templatePath) {
|
|
|
3195
3970
|
async function parseFixture(filePath) {
|
|
3196
3971
|
const content = await readTextFile(filePath);
|
|
3197
3972
|
const parsed = parse(content);
|
|
3198
|
-
const name =
|
|
3973
|
+
const name = basename6(filePath, extname2(filePath));
|
|
3199
3974
|
const features = toStringArray2(parsed.features) ?? [];
|
|
3200
3975
|
const preset = toStringArray2(parsed.preset) ?? [];
|
|
3201
3976
|
const removeFeatures = toStringArray2(parsed["remove-features"]) ?? [];
|
|
@@ -3217,7 +3992,7 @@ function toStringArray2(value) {
|
|
|
3217
3992
|
}
|
|
3218
3993
|
|
|
3219
3994
|
// src/core/template-test/reporter.ts
|
|
3220
|
-
import
|
|
3995
|
+
import chalk5 from "chalk";
|
|
3221
3996
|
function report2(result, options) {
|
|
3222
3997
|
if (options?.json) {
|
|
3223
3998
|
reportJson2(result);
|
|
@@ -3228,11 +4003,11 @@ function report2(result, options) {
|
|
|
3228
4003
|
reportFixture(fixture);
|
|
3229
4004
|
}
|
|
3230
4005
|
console.log("");
|
|
3231
|
-
console.log(
|
|
4006
|
+
console.log(chalk5.bold("Test Summary:"));
|
|
3232
4007
|
console.log(` Total: ${result.total}`);
|
|
3233
|
-
console.log(
|
|
4008
|
+
console.log(chalk5.green(` Passed: ${result.passed}`));
|
|
3234
4009
|
if (result.failed > 0) {
|
|
3235
|
-
console.log(
|
|
4010
|
+
console.log(chalk5.red(` Failed: ${result.failed}`));
|
|
3236
4011
|
}
|
|
3237
4012
|
console.log("");
|
|
3238
4013
|
}
|
|
@@ -3258,27 +4033,27 @@ function reportJson2(result) {
|
|
|
3258
4033
|
console.log(JSON.stringify(output, null, 2));
|
|
3259
4034
|
}
|
|
3260
4035
|
function reportFixture(fixture) {
|
|
3261
|
-
const icon = fixture.passed ?
|
|
4036
|
+
const icon = fixture.passed ? chalk5.green("\u2714") : chalk5.red("\u2716");
|
|
3262
4037
|
console.log(`${icon} ${fixture.name}`);
|
|
3263
4038
|
if (fixture.error) {
|
|
3264
|
-
console.log(
|
|
4039
|
+
console.log(chalk5.red(` Error: ${fixture.error}`));
|
|
3265
4040
|
return;
|
|
3266
4041
|
}
|
|
3267
4042
|
const missingFiles = fixture.fileResults.filter((r) => !r.found);
|
|
3268
4043
|
for (const missing of missingFiles) {
|
|
3269
|
-
console.log(
|
|
4044
|
+
console.log(chalk5.red(` Missing file: ${missing.path}`));
|
|
3270
4045
|
}
|
|
3271
4046
|
const snapshotFailures = fixture.snapshotResults.filter((r) => r.status !== "match");
|
|
3272
4047
|
for (const failure of snapshotFailures) {
|
|
3273
4048
|
switch (failure.status) {
|
|
3274
4049
|
case "mismatch":
|
|
3275
|
-
console.log(
|
|
4050
|
+
console.log(chalk5.yellow(` Snapshot mismatch: ${failure.path}`));
|
|
3276
4051
|
break;
|
|
3277
4052
|
case "missing-snapshot":
|
|
3278
|
-
console.log(
|
|
4053
|
+
console.log(chalk5.yellow(` Missing snapshot: ${failure.path}`));
|
|
3279
4054
|
break;
|
|
3280
4055
|
case "extra-snapshot":
|
|
3281
|
-
console.log(
|
|
4056
|
+
console.log(chalk5.yellow(` Extra snapshot (not in output): ${failure.path}`));
|
|
3282
4057
|
break;
|
|
3283
4058
|
}
|
|
3284
4059
|
}
|
|
@@ -3287,16 +4062,16 @@ function reportFixture(fixture) {
|
|
|
3287
4062
|
// src/core/template-test/runner.ts
|
|
3288
4063
|
import { mkdir as mkdir2, rm as rm3 } from "fs/promises";
|
|
3289
4064
|
import { tmpdir as tmpdir3 } from "os";
|
|
3290
|
-
import { join as
|
|
4065
|
+
import { join as join12 } from "path";
|
|
3291
4066
|
|
|
3292
4067
|
// src/core/template-test/snapshot.ts
|
|
3293
4068
|
import { mkdir, readdir as readdir3, rm as rm2 } from "fs/promises";
|
|
3294
|
-
import { join as
|
|
4069
|
+
import { join as join11, relative as relative4 } from "path";
|
|
3295
4070
|
async function walkRelative(dir, base) {
|
|
3296
4071
|
const results = [];
|
|
3297
4072
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
3298
4073
|
for (const entry of entries) {
|
|
3299
|
-
const fullPath =
|
|
4074
|
+
const fullPath = join11(dir, entry.name);
|
|
3300
4075
|
if (entry.isDirectory()) {
|
|
3301
4076
|
const sub = await walkRelative(fullPath, base);
|
|
3302
4077
|
results.push(...sub);
|
|
@@ -3313,8 +4088,8 @@ async function compareSnapshots(renderedDir, snapshotDir) {
|
|
|
3313
4088
|
const snapshotSet = new Set(snapshotFiles);
|
|
3314
4089
|
const renderedSet = new Set(renderedFiles);
|
|
3315
4090
|
for (const file of renderedFiles) {
|
|
3316
|
-
const renderedPath =
|
|
3317
|
-
const snapshotPath =
|
|
4091
|
+
const renderedPath = join11(renderedDir, file);
|
|
4092
|
+
const snapshotPath = join11(snapshotDir, file);
|
|
3318
4093
|
if (!snapshotSet.has(file)) {
|
|
3319
4094
|
results.push({ path: file, status: "missing-snapshot" });
|
|
3320
4095
|
continue;
|
|
@@ -3340,8 +4115,8 @@ async function updateSnapshots(renderedDir, snapshotDir) {
|
|
|
3340
4115
|
await mkdir(snapshotDir, { recursive: true });
|
|
3341
4116
|
const files = await walkRelative(renderedDir, renderedDir);
|
|
3342
4117
|
for (const file of files) {
|
|
3343
|
-
const srcPath =
|
|
3344
|
-
const destPath =
|
|
4118
|
+
const srcPath = join11(renderedDir, file);
|
|
4119
|
+
const destPath = join11(snapshotDir, file);
|
|
3345
4120
|
const content = await readTextFile(srcPath);
|
|
3346
4121
|
await writeTextFile(destPath, content);
|
|
3347
4122
|
}
|
|
@@ -3349,7 +4124,7 @@ async function updateSnapshots(renderedDir, snapshotDir) {
|
|
|
3349
4124
|
|
|
3350
4125
|
// src/core/template-test/runner.ts
|
|
3351
4126
|
async function runFixture(fixture, templatePath, options, presetDefinitions = {}) {
|
|
3352
|
-
const tempDir =
|
|
4127
|
+
const tempDir = join12(tmpdir3(), `awa-test-${fixture.name}-${Date.now()}`);
|
|
3353
4128
|
try {
|
|
3354
4129
|
await mkdir2(tempDir, { recursive: true });
|
|
3355
4130
|
const features = featureResolver.resolve({
|
|
@@ -3368,12 +4143,12 @@ async function runFixture(fixture, templatePath, options, presetDefinitions = {}
|
|
|
3368
4143
|
});
|
|
3369
4144
|
const fileResults = [];
|
|
3370
4145
|
for (const expectedFile of fixture.expectedFiles) {
|
|
3371
|
-
const fullPath =
|
|
4146
|
+
const fullPath = join12(tempDir, expectedFile);
|
|
3372
4147
|
const found = await pathExists(fullPath);
|
|
3373
4148
|
fileResults.push({ path: expectedFile, found });
|
|
3374
4149
|
}
|
|
3375
4150
|
const missingFiles = fileResults.filter((r) => !r.found);
|
|
3376
|
-
const snapshotDir =
|
|
4151
|
+
const snapshotDir = join12(templatePath, "_tests", fixture.name);
|
|
3377
4152
|
let snapshotResults = [];
|
|
3378
4153
|
if (options.updateSnapshots) {
|
|
3379
4154
|
await updateSnapshots(tempDir, snapshotDir);
|
|
@@ -3490,7 +4265,7 @@ async function testCommand(options) {
|
|
|
3490
4265
|
}
|
|
3491
4266
|
|
|
3492
4267
|
// src/core/trace/content-assembler.ts
|
|
3493
|
-
import { readFile as
|
|
4268
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
3494
4269
|
var DEFAULT_BEFORE_CONTEXT = 5;
|
|
3495
4270
|
var DEFAULT_AFTER_CONTEXT = 20;
|
|
3496
4271
|
async function assembleContent(result, taskPath, contextOptions) {
|
|
@@ -3498,8 +4273,9 @@ async function assembleContent(result, taskPath, contextOptions) {
|
|
|
3498
4273
|
const afterCtx = contextOptions?.afterContext ?? DEFAULT_AFTER_CONTEXT;
|
|
3499
4274
|
const sections = [];
|
|
3500
4275
|
const seen = /* @__PURE__ */ new Set();
|
|
4276
|
+
const fileCache = /* @__PURE__ */ new Map();
|
|
3501
4277
|
if (taskPath) {
|
|
3502
|
-
const content = await
|
|
4278
|
+
const content = await cachedReadFile(fileCache, taskPath);
|
|
3503
4279
|
if (content) {
|
|
3504
4280
|
const lineCount = content.split("\n").length;
|
|
3505
4281
|
sections.push({
|
|
@@ -3518,6 +4294,7 @@ async function assembleContent(result, taskPath, contextOptions) {
|
|
|
3518
4294
|
if (!seen.has(key)) {
|
|
3519
4295
|
seen.add(key);
|
|
3520
4296
|
const section = await extractSpecSection(
|
|
4297
|
+
fileCache,
|
|
3521
4298
|
chain.requirement.location.filePath,
|
|
3522
4299
|
chain.requirement.id,
|
|
3523
4300
|
chain.requirement.location.line,
|
|
@@ -3533,6 +4310,7 @@ async function assembleContent(result, taskPath, contextOptions) {
|
|
|
3533
4310
|
seen.add(key);
|
|
3534
4311
|
if (!chain.requirement || ac.location.filePath !== chain.requirement.location.filePath) {
|
|
3535
4312
|
const section = await extractSpecSection(
|
|
4313
|
+
fileCache,
|
|
3536
4314
|
ac.location.filePath,
|
|
3537
4315
|
ac.id,
|
|
3538
4316
|
ac.location.line,
|
|
@@ -3548,6 +4326,7 @@ async function assembleContent(result, taskPath, contextOptions) {
|
|
|
3548
4326
|
if (!seen.has(key)) {
|
|
3549
4327
|
seen.add(key);
|
|
3550
4328
|
const section = await extractSpecSection(
|
|
4329
|
+
fileCache,
|
|
3551
4330
|
comp.location.filePath,
|
|
3552
4331
|
comp.id,
|
|
3553
4332
|
comp.location.line,
|
|
@@ -3562,6 +4341,7 @@ async function assembleContent(result, taskPath, contextOptions) {
|
|
|
3562
4341
|
if (!seen.has(key)) {
|
|
3563
4342
|
seen.add(key);
|
|
3564
4343
|
const section = await extractCodeSection(
|
|
4344
|
+
fileCache,
|
|
3565
4345
|
impl.location.filePath,
|
|
3566
4346
|
impl.location.line,
|
|
3567
4347
|
"implementation",
|
|
@@ -3577,6 +4357,7 @@ async function assembleContent(result, taskPath, contextOptions) {
|
|
|
3577
4357
|
if (!seen.has(key)) {
|
|
3578
4358
|
seen.add(key);
|
|
3579
4359
|
const section = await extractCodeSection(
|
|
4360
|
+
fileCache,
|
|
3580
4361
|
t.location.filePath,
|
|
3581
4362
|
t.location.line,
|
|
3582
4363
|
"test",
|
|
@@ -3592,6 +4373,7 @@ async function assembleContent(result, taskPath, contextOptions) {
|
|
|
3592
4373
|
if (!seen.has(key)) {
|
|
3593
4374
|
seen.add(key);
|
|
3594
4375
|
const section = await extractSpecSection(
|
|
4376
|
+
fileCache,
|
|
3595
4377
|
prop.location.filePath,
|
|
3596
4378
|
prop.id,
|
|
3597
4379
|
prop.location.line,
|
|
@@ -3605,8 +4387,8 @@ async function assembleContent(result, taskPath, contextOptions) {
|
|
|
3605
4387
|
sections.sort((a, b) => a.priority - b.priority);
|
|
3606
4388
|
return sections;
|
|
3607
4389
|
}
|
|
3608
|
-
async function extractSpecSection(filePath, _id, line, type, priority) {
|
|
3609
|
-
const content = await
|
|
4390
|
+
async function extractSpecSection(fileCache, filePath, _id, line, type, priority) {
|
|
4391
|
+
const content = await cachedReadFile(fileCache, filePath);
|
|
3610
4392
|
if (!content) return null;
|
|
3611
4393
|
const lines = content.split("\n");
|
|
3612
4394
|
let startIdx = line - 1;
|
|
@@ -3630,8 +4412,8 @@ async function extractSpecSection(filePath, _id, line, type, priority) {
|
|
|
3630
4412
|
priority
|
|
3631
4413
|
};
|
|
3632
4414
|
}
|
|
3633
|
-
async function extractCodeSection(filePath, line, type, priority, beforeContext = DEFAULT_BEFORE_CONTEXT, afterContext = DEFAULT_AFTER_CONTEXT) {
|
|
3634
|
-
const content = await
|
|
4415
|
+
async function extractCodeSection(fileCache, filePath, line, type, priority, beforeContext = DEFAULT_BEFORE_CONTEXT, afterContext = DEFAULT_AFTER_CONTEXT) {
|
|
4416
|
+
const content = await cachedReadFile(fileCache, filePath);
|
|
3635
4417
|
if (!content) return null;
|
|
3636
4418
|
const lines = content.split("\n");
|
|
3637
4419
|
const lineIdx = line - 1;
|
|
@@ -3687,12 +4469,16 @@ function findEnclosingBlock(lines, lineIdx, beforeContext = DEFAULT_BEFORE_CONTE
|
|
|
3687
4469
|
}
|
|
3688
4470
|
return { start, end };
|
|
3689
4471
|
}
|
|
3690
|
-
async function
|
|
4472
|
+
async function cachedReadFile(cache, filePath) {
|
|
4473
|
+
if (cache.has(filePath)) return cache.get(filePath) ?? null;
|
|
4474
|
+
let content;
|
|
3691
4475
|
try {
|
|
3692
|
-
|
|
4476
|
+
content = await readFile9(filePath, "utf-8");
|
|
3693
4477
|
} catch {
|
|
3694
|
-
|
|
4478
|
+
content = null;
|
|
3695
4479
|
}
|
|
4480
|
+
cache.set(filePath, content);
|
|
4481
|
+
return content;
|
|
3696
4482
|
}
|
|
3697
4483
|
|
|
3698
4484
|
// src/core/trace/content-formatter.ts
|
|
@@ -3855,7 +4641,7 @@ function formatList(result) {
|
|
|
3855
4641
|
}
|
|
3856
4642
|
return [...paths].join("\n");
|
|
3857
4643
|
}
|
|
3858
|
-
function
|
|
4644
|
+
function formatJson4(result) {
|
|
3859
4645
|
const output = {
|
|
3860
4646
|
chains: result.chains.map((chain) => ({
|
|
3861
4647
|
queryId: chain.queryId,
|
|
@@ -3921,10 +4707,6 @@ function buildTraceIndex(specs, markers) {
|
|
|
3921
4707
|
...specs.propertyIds,
|
|
3922
4708
|
...specs.componentNames
|
|
3923
4709
|
]);
|
|
3924
|
-
const idLocations = /* @__PURE__ */ new Map();
|
|
3925
|
-
for (const [id, loc] of specs.idLocations) {
|
|
3926
|
-
idLocations.set(id, loc);
|
|
3927
|
-
}
|
|
3928
4710
|
return {
|
|
3929
4711
|
reqToACs,
|
|
3930
4712
|
acToDesignComponents,
|
|
@@ -3935,7 +4717,7 @@ function buildTraceIndex(specs, markers) {
|
|
|
3935
4717
|
acToReq,
|
|
3936
4718
|
componentToACs,
|
|
3937
4719
|
propertyToACs,
|
|
3938
|
-
idLocations,
|
|
4720
|
+
idLocations: specs.idLocations,
|
|
3939
4721
|
allIds
|
|
3940
4722
|
};
|
|
3941
4723
|
}
|
|
@@ -4041,7 +4823,7 @@ function pushToMap(map, key, value) {
|
|
|
4041
4823
|
}
|
|
4042
4824
|
|
|
4043
4825
|
// src/core/trace/input-resolver.ts
|
|
4044
|
-
import { readFile as
|
|
4826
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
4045
4827
|
function resolveIds(ids, index) {
|
|
4046
4828
|
const resolved = [];
|
|
4047
4829
|
const warnings = [];
|
|
@@ -4057,7 +4839,7 @@ function resolveIds(ids, index) {
|
|
|
4057
4839
|
async function resolveTaskFile(taskPath, index) {
|
|
4058
4840
|
let content;
|
|
4059
4841
|
try {
|
|
4060
|
-
content = await
|
|
4842
|
+
content = await readFile10(taskPath, "utf-8");
|
|
4061
4843
|
} catch {
|
|
4062
4844
|
return { ids: [], warnings: [`Task file not found: ${taskPath}`] };
|
|
4063
4845
|
}
|
|
@@ -4108,7 +4890,7 @@ async function resolveTaskFile(taskPath, index) {
|
|
|
4108
4890
|
async function resolveSourceFile(filePath, index) {
|
|
4109
4891
|
let content;
|
|
4110
4892
|
try {
|
|
4111
|
-
content = await
|
|
4893
|
+
content = await readFile10(filePath, "utf-8");
|
|
4112
4894
|
} catch {
|
|
4113
4895
|
return { ids: [], warnings: [`Source file not found: ${filePath}`] };
|
|
4114
4896
|
}
|
|
@@ -4388,44 +5170,6 @@ function deduplicateNodes(nodes) {
|
|
|
4388
5170
|
return result;
|
|
4389
5171
|
}
|
|
4390
5172
|
|
|
4391
|
-
// src/core/trace/scanner.ts
|
|
4392
|
-
function buildScanConfig(fileConfig, overrides) {
|
|
4393
|
-
const section = fileConfig?.check;
|
|
4394
|
-
return {
|
|
4395
|
-
specGlobs: toStringArray3(section?.["spec-globs"]) ?? [...DEFAULT_CHECK_CONFIG.specGlobs],
|
|
4396
|
-
codeGlobs: toStringArray3(section?.["code-globs"]) ?? [...DEFAULT_CHECK_CONFIG.codeGlobs],
|
|
4397
|
-
specIgnore: toStringArray3(section?.["spec-ignore"]) ?? [...DEFAULT_CHECK_CONFIG.specIgnore],
|
|
4398
|
-
codeIgnore: toStringArray3(section?.["code-ignore"]) ?? [...DEFAULT_CHECK_CONFIG.codeIgnore],
|
|
4399
|
-
ignoreMarkers: toStringArray3(section?.["ignore-markers"]) ?? [
|
|
4400
|
-
...DEFAULT_CHECK_CONFIG.ignoreMarkers
|
|
4401
|
-
],
|
|
4402
|
-
markers: toStringArray3(section?.markers) ?? [...DEFAULT_CHECK_CONFIG.markers],
|
|
4403
|
-
idPattern: typeof section?.["id-pattern"] === "string" ? section["id-pattern"] : DEFAULT_CHECK_CONFIG.idPattern,
|
|
4404
|
-
crossRefPatterns: toStringArray3(section?.["cross-ref-patterns"]) ?? [
|
|
4405
|
-
...DEFAULT_CHECK_CONFIG.crossRefPatterns
|
|
4406
|
-
],
|
|
4407
|
-
format: DEFAULT_CHECK_CONFIG.format,
|
|
4408
|
-
schemaDir: DEFAULT_CHECK_CONFIG.schemaDir,
|
|
4409
|
-
schemaEnabled: false,
|
|
4410
|
-
allowWarnings: true,
|
|
4411
|
-
specOnly: false,
|
|
4412
|
-
fix: true,
|
|
4413
|
-
...overrides
|
|
4414
|
-
};
|
|
4415
|
-
}
|
|
4416
|
-
async function scan(configPath) {
|
|
4417
|
-
const fileConfig = await configLoader.load(configPath ?? null);
|
|
4418
|
-
const config = buildScanConfig(fileConfig);
|
|
4419
|
-
const [markers, specs] = await Promise.all([scanMarkers(config), parseSpecs(config)]);
|
|
4420
|
-
return { markers, specs, config };
|
|
4421
|
-
}
|
|
4422
|
-
function toStringArray3(value) {
|
|
4423
|
-
if (Array.isArray(value) && value.every((v) => typeof v === "string")) {
|
|
4424
|
-
return value;
|
|
4425
|
-
}
|
|
4426
|
-
return null;
|
|
4427
|
-
}
|
|
4428
|
-
|
|
4429
5173
|
// src/core/trace/token-estimator.ts
|
|
4430
5174
|
function estimateTokens(text) {
|
|
4431
5175
|
return Math.ceil(text.length / 4);
|
|
@@ -4543,7 +5287,7 @@ async function traceCommand(options) {
|
|
|
4543
5287
|
} else if (options.list) {
|
|
4544
5288
|
output = formatList(result);
|
|
4545
5289
|
} else if (options.json) {
|
|
4546
|
-
output =
|
|
5290
|
+
output = formatJson4(result);
|
|
4547
5291
|
} else {
|
|
4548
5292
|
output = formatTree(result);
|
|
4549
5293
|
}
|
|
@@ -4565,7 +5309,7 @@ async function traceCommand(options) {
|
|
|
4565
5309
|
}
|
|
4566
5310
|
|
|
4567
5311
|
// src/utils/update-check.ts
|
|
4568
|
-
import
|
|
5312
|
+
import chalk6 from "chalk";
|
|
4569
5313
|
function compareSemver(a, b) {
|
|
4570
5314
|
const partsA = a.split(".").map((s) => Number.parseInt(s, 10));
|
|
4571
5315
|
const partsB = b.split(".").map((s) => Number.parseInt(s, 10));
|
|
@@ -4606,28 +5350,28 @@ function printUpdateWarning(log, result) {
|
|
|
4606
5350
|
console.log("");
|
|
4607
5351
|
if (result.isMajorBump) {
|
|
4608
5352
|
log.warn(
|
|
4609
|
-
|
|
5353
|
+
chalk6.yellow(
|
|
4610
5354
|
`New major version available: ${result.current} \u2192 ${result.latest} (breaking changes)`
|
|
4611
5355
|
)
|
|
4612
5356
|
);
|
|
4613
|
-
log.warn(
|
|
5357
|
+
log.warn(chalk6.dim(" See https://github.com/ncoderz/awa/releases for details"));
|
|
4614
5358
|
} else {
|
|
4615
|
-
log.warn(
|
|
5359
|
+
log.warn(chalk6.yellow(`Update available: ${result.current} \u2192 ${result.latest}`));
|
|
4616
5360
|
}
|
|
4617
|
-
log.warn(
|
|
5361
|
+
log.warn(chalk6.dim(" Run `npm install -g @ncoderz/awa` to update"));
|
|
4618
5362
|
console.log("");
|
|
4619
5363
|
}
|
|
4620
5364
|
|
|
4621
5365
|
// src/utils/update-check-cache.ts
|
|
4622
|
-
import { mkdir as mkdir3, readFile as
|
|
5366
|
+
import { mkdir as mkdir3, readFile as readFile11, writeFile as writeFile4 } from "fs/promises";
|
|
4623
5367
|
import { homedir } from "os";
|
|
4624
|
-
import { dirname, join as
|
|
4625
|
-
var CACHE_DIR =
|
|
4626
|
-
var CACHE_FILE =
|
|
5368
|
+
import { dirname as dirname3, join as join13 } from "path";
|
|
5369
|
+
var CACHE_DIR = join13(homedir(), ".cache", "awa");
|
|
5370
|
+
var CACHE_FILE = join13(CACHE_DIR, "update-check.json");
|
|
4627
5371
|
var DEFAULT_INTERVAL_MS = 864e5;
|
|
4628
5372
|
async function shouldCheck(intervalMs = DEFAULT_INTERVAL_MS) {
|
|
4629
5373
|
try {
|
|
4630
|
-
const raw = await
|
|
5374
|
+
const raw = await readFile11(CACHE_FILE, "utf-8");
|
|
4631
5375
|
const data = JSON.parse(raw);
|
|
4632
5376
|
if (typeof data.timestamp !== "number" || typeof data.latestVersion !== "string") {
|
|
4633
5377
|
return true;
|
|
@@ -4639,12 +5383,12 @@ async function shouldCheck(intervalMs = DEFAULT_INTERVAL_MS) {
|
|
|
4639
5383
|
}
|
|
4640
5384
|
async function writeCache(latestVersion) {
|
|
4641
5385
|
try {
|
|
4642
|
-
await mkdir3(
|
|
5386
|
+
await mkdir3(dirname3(CACHE_FILE), { recursive: true });
|
|
4643
5387
|
const data = {
|
|
4644
5388
|
timestamp: Date.now(),
|
|
4645
5389
|
latestVersion
|
|
4646
5390
|
};
|
|
4647
|
-
await
|
|
5391
|
+
await writeFile4(CACHE_FILE, JSON.stringify(data), "utf-8");
|
|
4648
5392
|
} catch {
|
|
4649
5393
|
}
|
|
4650
5394
|
}
|
|
@@ -4771,29 +5515,90 @@ template.command("test").description("Run template test fixtures to verify expec
|
|
|
4771
5515
|
process.exit(exitCode);
|
|
4772
5516
|
});
|
|
4773
5517
|
program.addCommand(template);
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
5518
|
+
var spec = new Command("spec").description(
|
|
5519
|
+
"Spec operations (trace, renumber, recode, merge, codes)"
|
|
5520
|
+
);
|
|
5521
|
+
function configureTraceCommand(cmd) {
|
|
5522
|
+
return cmd.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 results as JSON", false).option("--summary", "Output compact one-line summary", 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) => {
|
|
5523
|
+
const traceOptions = {
|
|
5524
|
+
ids,
|
|
5525
|
+
all: options.all,
|
|
5526
|
+
task: options.task,
|
|
5527
|
+
file: options.file,
|
|
5528
|
+
content: options.content,
|
|
5529
|
+
list: options.list,
|
|
5530
|
+
json: options.json,
|
|
5531
|
+
summary: options.summary,
|
|
5532
|
+
maxTokens: options.maxTokens !== void 0 ? Number(options.maxTokens) : void 0,
|
|
5533
|
+
depth: options.depth !== void 0 ? Number(options.depth) : void 0,
|
|
5534
|
+
scope: options.scope,
|
|
5535
|
+
direction: options.direction,
|
|
5536
|
+
noCode: options.code === false,
|
|
5537
|
+
noTests: options.tests === false,
|
|
5538
|
+
beforeContext: options.C !== void 0 ? Number(options.C) : options.B !== void 0 ? Number(options.B) : void 0,
|
|
5539
|
+
afterContext: options.C !== void 0 ? Number(options.C) : options.A !== void 0 ? Number(options.A) : void 0,
|
|
5540
|
+
config: options.config
|
|
5541
|
+
};
|
|
5542
|
+
const exitCode = await traceCommand(traceOptions);
|
|
5543
|
+
process.exit(exitCode);
|
|
5544
|
+
});
|
|
5545
|
+
}
|
|
5546
|
+
configureTraceCommand(spec.command("trace"));
|
|
5547
|
+
function configureRenumberCommand(cmd) {
|
|
5548
|
+
return cmd.description("Renumber traceability IDs to match document order").argument("[code]", "Feature code to renumber (e.g. CHK, TRC)").option("--all", "Renumber all feature codes", false).option("--dry-run", "Preview changes without modifying files", false).option("--json", "Output results as JSON", false).option(
|
|
5549
|
+
"--expand-unambiguous-ids",
|
|
5550
|
+
"Expand unambiguous malformed ID shorthand (slash ranges, dot-dot ranges) before renumbering",
|
|
5551
|
+
false
|
|
5552
|
+
).option("-c, --config <path>", "Path to configuration file").action(async (code, options) => {
|
|
5553
|
+
const renumberOptions = {
|
|
5554
|
+
code,
|
|
5555
|
+
all: options.all,
|
|
5556
|
+
dryRun: options.dryRun,
|
|
5557
|
+
json: options.json,
|
|
5558
|
+
config: options.config,
|
|
5559
|
+
expandUnambiguousIds: options.expandUnambiguousIds
|
|
5560
|
+
};
|
|
5561
|
+
const exitCode = await renumberCommand(renumberOptions);
|
|
5562
|
+
process.exit(exitCode);
|
|
5563
|
+
});
|
|
5564
|
+
}
|
|
5565
|
+
configureRenumberCommand(spec.command("renumber"));
|
|
5566
|
+
spec.command("recode").description("Recode traceability IDs from one feature code to another and rename spec files").argument("<source>", "Source feature code to recode from (e.g. CHK)").argument("<target>", "Target feature code to recode into (e.g. CLI)").option("--dry-run", "Preview changes without modifying files", false).option("--json", "Output results as JSON", false).option("--renumber", "Renumber target code after recode", false).option("-c, --config <path>", "Path to configuration file").action(async (source, target, options) => {
|
|
5567
|
+
const recodeOptions = {
|
|
5568
|
+
sourceCode: source,
|
|
5569
|
+
targetCode: target,
|
|
5570
|
+
dryRun: options.dryRun,
|
|
5571
|
+
json: options.json,
|
|
5572
|
+
renumber: options.renumber,
|
|
5573
|
+
config: options.config
|
|
5574
|
+
};
|
|
5575
|
+
const exitCode = await recodeCommand(recodeOptions);
|
|
5576
|
+
process.exit(exitCode);
|
|
5577
|
+
});
|
|
5578
|
+
spec.command("merge").description("Merge one feature code into another (recode + content merge + cleanup)").argument("<source>", "Source feature code to merge from (e.g. CHK)").argument("<target>", "Target feature code to merge into (e.g. CLI)").option("--dry-run", "Preview changes without modifying files", false).option("--json", "Output results as JSON", false).option("--renumber", "Renumber target code after merge", false).option("-c, --config <path>", "Path to configuration file").action(async (source, target, options) => {
|
|
5579
|
+
const mergeOptions = {
|
|
5580
|
+
sourceCode: source,
|
|
5581
|
+
targetCode: target,
|
|
5582
|
+
dryRun: options.dryRun,
|
|
5583
|
+
json: options.json,
|
|
5584
|
+
renumber: options.renumber,
|
|
5585
|
+
config: options.config
|
|
5586
|
+
};
|
|
5587
|
+
const exitCode = await mergeCommand(mergeOptions);
|
|
5588
|
+
process.exit(exitCode);
|
|
5589
|
+
});
|
|
5590
|
+
spec.command("codes").description("List all feature codes with requirement counts and scope summaries").option("--json", "Output results as JSON", false).option("--summary", "Output compact one-line summary", false).option("-c, --config <path>", "Path to configuration file").action(async (options) => {
|
|
5591
|
+
const codesOptions = {
|
|
4782
5592
|
json: options.json,
|
|
4783
5593
|
summary: options.summary,
|
|
4784
|
-
maxTokens: options.maxTokens !== void 0 ? Number(options.maxTokens) : void 0,
|
|
4785
|
-
depth: options.depth !== void 0 ? Number(options.depth) : void 0,
|
|
4786
|
-
scope: options.scope,
|
|
4787
|
-
direction: options.direction,
|
|
4788
|
-
noCode: options.code === false,
|
|
4789
|
-
noTests: options.tests === false,
|
|
4790
|
-
beforeContext: options.C !== void 0 ? Number(options.C) : options.B !== void 0 ? Number(options.B) : void 0,
|
|
4791
|
-
afterContext: options.C !== void 0 ? Number(options.C) : options.A !== void 0 ? Number(options.A) : void 0,
|
|
4792
5594
|
config: options.config
|
|
4793
5595
|
};
|
|
4794
|
-
const exitCode = await
|
|
5596
|
+
const exitCode = await codesCommand(codesOptions);
|
|
4795
5597
|
process.exit(exitCode);
|
|
4796
5598
|
});
|
|
5599
|
+
program.addCommand(spec);
|
|
5600
|
+
configureTraceCommand(program.command("trace"));
|
|
5601
|
+
configureRenumberCommand(program.command("renumber", { hidden: true }));
|
|
4797
5602
|
var updateCheckPromise = null;
|
|
4798
5603
|
var isJsonOrSummary = process.argv.includes("--json") || process.argv.includes("--summary");
|
|
4799
5604
|
var isTTY = process.stdout.isTTY === true;
|
|
@@ -4801,7 +5606,7 @@ var isDisabledByEnv = !!process.env.NO_UPDATE_NOTIFIER;
|
|
|
4801
5606
|
if (!isJsonOrSummary && isTTY && !isDisabledByEnv) {
|
|
4802
5607
|
updateCheckPromise = (async () => {
|
|
4803
5608
|
try {
|
|
4804
|
-
const { configLoader: configLoader2 } = await import("./config-
|
|
5609
|
+
const { configLoader: configLoader2 } = await import("./config-EJIXC7D7.js");
|
|
4805
5610
|
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;
|
|
4806
5611
|
const fileConfig = await configLoader2.load(configPath ?? null);
|
|
4807
5612
|
const updateCheckConfig = fileConfig?.["update-check"];
|
|
@@ -4830,5 +5635,5 @@ program.hook("postAction", async () => {
|
|
|
4830
5635
|
} catch {
|
|
4831
5636
|
}
|
|
4832
5637
|
});
|
|
4833
|
-
program.parseAsync();
|
|
5638
|
+
void program.parseAsync();
|
|
4834
5639
|
//# sourceMappingURL=index.js.map
|