@ncoderz/awa 1.7.2 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -16
- package/README.md +25 -27
- package/dist/{chunk-OQZTQ5ZI.js → chunk-LRQWZCYL.js} +1 -4
- package/dist/chunk-LRQWZCYL.js.map +1 -0
- package/dist/chunk-WGGMDWBE.js +760 -0
- package/dist/chunk-WGGMDWBE.js.map +1 -0
- package/dist/{config-WL3SLSP6.js → config-EJIXC7D7.js} +2 -2
- package/dist/index.js +1252 -452
- package/dist/index.js.map +1 -1
- package/dist/renumber-ZCI2H5HZ.js +9 -0
- package/dist/renumber-ZCI2H5HZ.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 +8 -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 +74 -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
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
configLoader,
|
|
4
|
+
logger
|
|
5
|
+
} from "./chunk-LRQWZCYL.js";
|
|
6
|
+
|
|
7
|
+
// src/commands/renumber.ts
|
|
8
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
9
|
+
|
|
10
|
+
// src/core/renumber/malformed-detector.ts
|
|
11
|
+
var VALID_ID_RE = /^[A-Z][A-Z0-9]*(?:-\d+(?:\.\d+)?(?:_AC-\d+)?|_P-\d+|-[A-Z][a-zA-Z0-9]*)$/;
|
|
12
|
+
function detectMalformed(code, fileContents) {
|
|
13
|
+
const warnings = [];
|
|
14
|
+
const esc = escapeRegex(code);
|
|
15
|
+
const tokenRegex = new RegExp(
|
|
16
|
+
`(?<![A-Za-z0-9_.-])(?:${esc}-[A-Z0-9][A-Za-z0-9_./-]*|${esc}_P-[A-Za-z0-9_./-]+)`,
|
|
17
|
+
"g"
|
|
18
|
+
);
|
|
19
|
+
for (const [filePath, content] of fileContents) {
|
|
20
|
+
const lines = content.split("\n");
|
|
21
|
+
for (let i = 0; i < lines.length; i++) {
|
|
22
|
+
const line = lines[i];
|
|
23
|
+
let match;
|
|
24
|
+
while ((match = tokenRegex.exec(line)) !== null) {
|
|
25
|
+
const token = match[0];
|
|
26
|
+
if (!VALID_ID_RE.test(token)) {
|
|
27
|
+
warnings.push({ filePath, line: i + 1, token });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return warnings;
|
|
33
|
+
}
|
|
34
|
+
function escapeRegex(str) {
|
|
35
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/core/renumber/map-builder.ts
|
|
39
|
+
import { basename } from "path";
|
|
40
|
+
|
|
41
|
+
// src/core/renumber/types.ts
|
|
42
|
+
var RenumberError = class extends Error {
|
|
43
|
+
errorCode;
|
|
44
|
+
constructor(errorCode, message) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = "RenumberError";
|
|
47
|
+
this.errorCode = errorCode;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// src/core/renumber/map-builder.ts
|
|
52
|
+
function buildRenumberMap(code, specs) {
|
|
53
|
+
const reqFile = findSpecFile(specs.specFiles, code, "REQ");
|
|
54
|
+
if (!reqFile) {
|
|
55
|
+
throw new RenumberError("CODE_NOT_FOUND", `No REQ file found for feature code: ${code}`);
|
|
56
|
+
}
|
|
57
|
+
const entries = /* @__PURE__ */ new Map();
|
|
58
|
+
buildRequirementEntries(code, reqFile, entries);
|
|
59
|
+
const designFile = findSpecFile(specs.specFiles, code, "DESIGN");
|
|
60
|
+
if (designFile) {
|
|
61
|
+
buildPropertyEntries(code, designFile, entries);
|
|
62
|
+
}
|
|
63
|
+
for (const [oldId, newId] of entries) {
|
|
64
|
+
if (oldId === newId) {
|
|
65
|
+
entries.delete(oldId);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const noChange = entries.size === 0;
|
|
69
|
+
const map = { code, entries };
|
|
70
|
+
return { map, noChange };
|
|
71
|
+
}
|
|
72
|
+
function buildRequirementEntries(code, reqFile, entries) {
|
|
73
|
+
const topLevelReqs = [];
|
|
74
|
+
const subReqsByParent = /* @__PURE__ */ new Map();
|
|
75
|
+
for (const id of reqFile.requirementIds) {
|
|
76
|
+
if (id.includes(".")) {
|
|
77
|
+
const dotIdx = id.lastIndexOf(".");
|
|
78
|
+
const parent = id.slice(0, dotIdx);
|
|
79
|
+
const subs = subReqsByParent.get(parent) ?? [];
|
|
80
|
+
subs.push(id);
|
|
81
|
+
subReqsByParent.set(parent, subs);
|
|
82
|
+
} else {
|
|
83
|
+
topLevelReqs.push(id);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const reqNumberMap = /* @__PURE__ */ new Map();
|
|
87
|
+
for (let i = 0; i < topLevelReqs.length; i++) {
|
|
88
|
+
const oldId = topLevelReqs[i];
|
|
89
|
+
const newNum = i + 1;
|
|
90
|
+
const newId = `${code}-${newNum}`;
|
|
91
|
+
entries.set(oldId, newId);
|
|
92
|
+
reqNumberMap.set(oldId, newNum);
|
|
93
|
+
}
|
|
94
|
+
for (const oldParentId of topLevelReqs) {
|
|
95
|
+
const subs = subReqsByParent.get(oldParentId);
|
|
96
|
+
if (!subs) continue;
|
|
97
|
+
const newParentNum = reqNumberMap.get(oldParentId);
|
|
98
|
+
if (newParentNum === void 0) continue;
|
|
99
|
+
for (let j = 0; j < subs.length; j++) {
|
|
100
|
+
const oldSubId = subs[j];
|
|
101
|
+
const newSubNum = j + 1;
|
|
102
|
+
const newSubId = `${code}-${newParentNum}.${newSubNum}`;
|
|
103
|
+
entries.set(oldSubId, newSubId);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const acsByParent = /* @__PURE__ */ new Map();
|
|
107
|
+
for (const acId of reqFile.acIds) {
|
|
108
|
+
const parent = acId.split("_AC-")[0];
|
|
109
|
+
const acs = acsByParent.get(parent) ?? [];
|
|
110
|
+
acs.push(acId);
|
|
111
|
+
acsByParent.set(parent, acs);
|
|
112
|
+
}
|
|
113
|
+
for (const [oldParentId, acs] of acsByParent) {
|
|
114
|
+
const newParentId = entries.get(oldParentId) ?? oldParentId;
|
|
115
|
+
for (let k = 0; k < acs.length; k++) {
|
|
116
|
+
const oldAcId = acs[k];
|
|
117
|
+
const newAcNum = k + 1;
|
|
118
|
+
const newAcId = `${newParentId}_AC-${newAcNum}`;
|
|
119
|
+
entries.set(oldAcId, newAcId);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function buildPropertyEntries(code, designFile, entries) {
|
|
124
|
+
for (let i = 0; i < designFile.propertyIds.length; i++) {
|
|
125
|
+
const oldId = designFile.propertyIds[i];
|
|
126
|
+
const newNum = i + 1;
|
|
127
|
+
const newId = `${code}_P-${newNum}`;
|
|
128
|
+
entries.set(oldId, newId);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function findSpecFile(specFiles, code, prefix) {
|
|
132
|
+
return specFiles.find((sf) => {
|
|
133
|
+
const name = basename(sf.filePath);
|
|
134
|
+
return name.startsWith(`${prefix}-${code}-`);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/core/renumber/propagator.ts
|
|
139
|
+
import { readFile, writeFile } from "fs/promises";
|
|
140
|
+
async function propagate(map, specs, markers, dryRun) {
|
|
141
|
+
if (map.entries.size === 0) {
|
|
142
|
+
return { affectedFiles: [], totalReplacements: 0 };
|
|
143
|
+
}
|
|
144
|
+
const filePaths = collectFilePaths(map, specs, markers);
|
|
145
|
+
const affectedFiles = [];
|
|
146
|
+
let totalReplacements = 0;
|
|
147
|
+
for (const filePath of filePaths) {
|
|
148
|
+
let content;
|
|
149
|
+
try {
|
|
150
|
+
content = await readFile(filePath, "utf-8");
|
|
151
|
+
} catch {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const result = applyReplacements(content, map);
|
|
155
|
+
if (result.replacements.length === 0) continue;
|
|
156
|
+
affectedFiles.push({ filePath, replacements: result.replacements });
|
|
157
|
+
totalReplacements += result.replacements.length;
|
|
158
|
+
if (!dryRun) {
|
|
159
|
+
try {
|
|
160
|
+
await writeFile(filePath, result.newContent, "utf-8");
|
|
161
|
+
} catch (err) {
|
|
162
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
163
|
+
throw new RenumberError("WRITE_FAILED", `Failed to write ${filePath}: ${msg}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return { affectedFiles, totalReplacements };
|
|
168
|
+
}
|
|
169
|
+
function collectFilePaths(map, specs, markers) {
|
|
170
|
+
const paths = /* @__PURE__ */ new Set();
|
|
171
|
+
const code = map.code;
|
|
172
|
+
for (const specFile of specs.specFiles) {
|
|
173
|
+
paths.add(specFile.filePath);
|
|
174
|
+
}
|
|
175
|
+
const affectedIds = new Set(map.entries.keys());
|
|
176
|
+
for (const marker of markers.markers) {
|
|
177
|
+
if (affectedIds.has(marker.id) || hasCodePrefix(marker.id, code)) {
|
|
178
|
+
paths.add(marker.filePath);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return [...paths];
|
|
182
|
+
}
|
|
183
|
+
function applyReplacements(content, map) {
|
|
184
|
+
const replacements = [];
|
|
185
|
+
const lines = content.split("\n");
|
|
186
|
+
const sortedEntries = [...map.entries].sort(([a], [b]) => b.length - a.length);
|
|
187
|
+
const placeholders = /* @__PURE__ */ new Map();
|
|
188
|
+
const placeholderToNew = /* @__PURE__ */ new Map();
|
|
189
|
+
for (const [oldId, newId] of sortedEntries) {
|
|
190
|
+
const placeholder = `__RENUM_${placeholders.size}__`;
|
|
191
|
+
placeholders.set(oldId, placeholder);
|
|
192
|
+
placeholderToNew.set(placeholder, newId);
|
|
193
|
+
}
|
|
194
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
195
|
+
let modified = lines[idx];
|
|
196
|
+
const origLine = modified;
|
|
197
|
+
for (const [oldId, placeholder] of placeholders) {
|
|
198
|
+
const regex = buildWholeIdRegex(oldId);
|
|
199
|
+
const trackRegex = buildWholeIdRegex(oldId);
|
|
200
|
+
while (trackRegex.exec(origLine) !== null) {
|
|
201
|
+
replacements.push({
|
|
202
|
+
line: idx + 1,
|
|
203
|
+
oldId,
|
|
204
|
+
newId: map.entries.get(oldId) ?? oldId
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
modified = modified.replace(regex, placeholder);
|
|
208
|
+
}
|
|
209
|
+
for (const [placeholder, newId] of placeholderToNew) {
|
|
210
|
+
modified = modified.replaceAll(placeholder, newId);
|
|
211
|
+
}
|
|
212
|
+
lines[idx] = modified;
|
|
213
|
+
}
|
|
214
|
+
const seen = /* @__PURE__ */ new Set();
|
|
215
|
+
const dedupedReplacements = replacements.filter((r) => {
|
|
216
|
+
const key = `${r.line}:${r.oldId}`;
|
|
217
|
+
if (seen.has(key)) return false;
|
|
218
|
+
seen.add(key);
|
|
219
|
+
return true;
|
|
220
|
+
});
|
|
221
|
+
return { newContent: lines.join("\n"), replacements: dedupedReplacements };
|
|
222
|
+
}
|
|
223
|
+
function buildWholeIdRegex(id) {
|
|
224
|
+
const escaped = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
225
|
+
return new RegExp(`(?<![A-Za-z0-9_.-])${escaped}(?![A-Za-z0-9_.-])`, "g");
|
|
226
|
+
}
|
|
227
|
+
function hasCodePrefix(id, code) {
|
|
228
|
+
return id.startsWith(`${code}-`) || id.startsWith(`${code}_`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/core/renumber/reporter.ts
|
|
232
|
+
function formatText(result, dryRun) {
|
|
233
|
+
const lines = [];
|
|
234
|
+
if (dryRun) {
|
|
235
|
+
lines.push("DRY RUN \u2014 no files were modified\n");
|
|
236
|
+
}
|
|
237
|
+
if (result.noChange) {
|
|
238
|
+
lines.push(`${result.code}: no changes needed (IDs already sequential)`);
|
|
239
|
+
return lines.join("\n");
|
|
240
|
+
}
|
|
241
|
+
lines.push(`${result.code}: ${result.map.entries.size} ID(s) renumbered
|
|
242
|
+
`);
|
|
243
|
+
lines.push(" Old ID \u2192 New ID");
|
|
244
|
+
lines.push(` ${"\u2500".repeat(40)}`);
|
|
245
|
+
for (const [oldId, newId] of result.map.entries) {
|
|
246
|
+
lines.push(` ${oldId} \u2192 ${newId}`);
|
|
247
|
+
}
|
|
248
|
+
if (result.affectedFiles.length > 0) {
|
|
249
|
+
lines.push("");
|
|
250
|
+
lines.push(
|
|
251
|
+
` ${result.totalReplacements} replacement(s) in ${result.affectedFiles.length} file(s):`
|
|
252
|
+
);
|
|
253
|
+
for (const file of result.affectedFiles) {
|
|
254
|
+
lines.push(` ${file.filePath} (${file.replacements.length})`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (result.malformedWarnings.length > 0) {
|
|
258
|
+
lines.push("");
|
|
259
|
+
lines.push(" Malformed ID warnings:");
|
|
260
|
+
for (const w of result.malformedWarnings) {
|
|
261
|
+
lines.push(` ${w.filePath}:${w.line} \u2014 ${w.token}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return lines.join("\n");
|
|
265
|
+
}
|
|
266
|
+
function formatJson(result) {
|
|
267
|
+
const output = {
|
|
268
|
+
code: result.code,
|
|
269
|
+
noChange: result.noChange,
|
|
270
|
+
map: Object.fromEntries(result.map.entries),
|
|
271
|
+
affectedFiles: result.affectedFiles.map((f) => ({
|
|
272
|
+
filePath: f.filePath,
|
|
273
|
+
replacements: f.replacements.map((r) => ({
|
|
274
|
+
line: r.line,
|
|
275
|
+
oldId: r.oldId,
|
|
276
|
+
newId: r.newId
|
|
277
|
+
}))
|
|
278
|
+
})),
|
|
279
|
+
totalReplacements: result.totalReplacements,
|
|
280
|
+
malformedWarnings: result.malformedWarnings.map((w) => ({
|
|
281
|
+
filePath: w.filePath,
|
|
282
|
+
line: w.line,
|
|
283
|
+
token: w.token
|
|
284
|
+
}))
|
|
285
|
+
};
|
|
286
|
+
return JSON.stringify(output, null, 2);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/core/check/marker-scanner.ts
|
|
290
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
291
|
+
|
|
292
|
+
// src/core/check/glob.ts
|
|
293
|
+
import { glob } from "fs/promises";
|
|
294
|
+
function matchSimpleGlob(path, pattern) {
|
|
295
|
+
const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "<<GLOBSTAR>>").replace(/\*/g, "[^/]*").replace(/<<GLOBSTAR>>/g, ".*");
|
|
296
|
+
return new RegExp(`(^|/)${regex}($|/)`).test(path);
|
|
297
|
+
}
|
|
298
|
+
async function collectFiles(globs, ignore) {
|
|
299
|
+
const files = [];
|
|
300
|
+
const dirPrefixes = ignore.filter((ig) => ig.endsWith("/**")).map((ig) => ig.slice(0, -3));
|
|
301
|
+
const compiledIgnore = ignore.map((ig) => {
|
|
302
|
+
const regex = ig.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "<<GLOBSTAR>>").replace(/\*/g, "[^/]*").replace(/<<GLOBSTAR>>/g, ".*");
|
|
303
|
+
return new RegExp(`(^|/)${regex}($|/)`);
|
|
304
|
+
});
|
|
305
|
+
for (const pattern of globs) {
|
|
306
|
+
for await (const filePath of glob(pattern, {
|
|
307
|
+
exclude: (p) => dirPrefixes.includes(p) || compiledIgnore.some((re) => re.test(p))
|
|
308
|
+
})) {
|
|
309
|
+
if (!compiledIgnore.some((re) => re.test(filePath))) {
|
|
310
|
+
files.push(filePath);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return [...new Set(files)];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/core/check/marker-scanner.ts
|
|
318
|
+
var MARKER_TYPE_MAP = {
|
|
319
|
+
"@awa-impl": "impl",
|
|
320
|
+
"@awa-test": "test",
|
|
321
|
+
"@awa-component": "component"
|
|
322
|
+
};
|
|
323
|
+
var IGNORE_FILE_RE = new RegExp("@awa-ignore-file\\b");
|
|
324
|
+
var IGNORE_NEXT_LINE_RE = /@awa-ignore-next-line\b/;
|
|
325
|
+
var IGNORE_LINE_RE = /@awa-ignore\b/;
|
|
326
|
+
var IGNORE_START_RE = /@awa-ignore-start\b/;
|
|
327
|
+
var IGNORE_END_RE = /@awa-ignore-end\b/;
|
|
328
|
+
async function scanMarkers(config) {
|
|
329
|
+
const files = await collectCodeFiles(config.codeGlobs, config.codeIgnore);
|
|
330
|
+
const markers = [];
|
|
331
|
+
const findings = [];
|
|
332
|
+
for (const filePath of files) {
|
|
333
|
+
if (config.ignoreMarkers.some((ig) => matchSimpleGlob(filePath, ig))) {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
const result = await scanFile(filePath, config.markers);
|
|
337
|
+
markers.push(...result.markers);
|
|
338
|
+
findings.push(...result.findings);
|
|
339
|
+
}
|
|
340
|
+
return { markers, findings };
|
|
341
|
+
}
|
|
342
|
+
function buildMarkerRegex(markerNames) {
|
|
343
|
+
const escaped = markerNames.map((m) => m.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
344
|
+
return new RegExp(`(${escaped.join("|")}):\\s*(.+)`, "g");
|
|
345
|
+
}
|
|
346
|
+
var ID_TOKEN_RE = /^([A-Z][A-Z0-9]*(?:[-_][A-Za-z0-9]+)*(?:\.\d+)?(?:[-_][A-Za-z0-9]+)*)/;
|
|
347
|
+
async function scanFile(filePath, markerNames) {
|
|
348
|
+
let content;
|
|
349
|
+
try {
|
|
350
|
+
content = await readFile2(filePath, "utf-8");
|
|
351
|
+
} catch {
|
|
352
|
+
return { markers: [], findings: [] };
|
|
353
|
+
}
|
|
354
|
+
if (IGNORE_FILE_RE.test(content)) {
|
|
355
|
+
return { markers: [], findings: [] };
|
|
356
|
+
}
|
|
357
|
+
const regex = buildMarkerRegex(markerNames);
|
|
358
|
+
const lines = content.split("\n");
|
|
359
|
+
const markers = [];
|
|
360
|
+
const findings = [];
|
|
361
|
+
let ignoreNextLine = false;
|
|
362
|
+
let ignoreBlock = false;
|
|
363
|
+
for (const [i, line] of lines.entries()) {
|
|
364
|
+
if (IGNORE_START_RE.test(line)) {
|
|
365
|
+
ignoreBlock = true;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (IGNORE_END_RE.test(line)) {
|
|
369
|
+
ignoreBlock = false;
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (ignoreBlock) {
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (ignoreNextLine) {
|
|
376
|
+
ignoreNextLine = false;
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (IGNORE_NEXT_LINE_RE.test(line)) {
|
|
380
|
+
ignoreNextLine = true;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (IGNORE_LINE_RE.test(line)) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
regex.lastIndex = 0;
|
|
387
|
+
let match = regex.exec(line);
|
|
388
|
+
while (match !== null) {
|
|
389
|
+
const markerName = match[1] ?? "";
|
|
390
|
+
const idsRaw = match[2] ?? "";
|
|
391
|
+
const type = resolveMarkerType(markerName, markerNames);
|
|
392
|
+
const ids = idsRaw.split(",").map((id) => id.trim()).filter(Boolean);
|
|
393
|
+
for (const id of ids) {
|
|
394
|
+
const tokenMatch = ID_TOKEN_RE.exec(id);
|
|
395
|
+
const cleanId = tokenMatch?.[1]?.trim() ?? "";
|
|
396
|
+
if (cleanId && tokenMatch) {
|
|
397
|
+
const remainder = id.slice(tokenMatch[0].length).trim();
|
|
398
|
+
if (remainder) {
|
|
399
|
+
findings.push({
|
|
400
|
+
severity: "error",
|
|
401
|
+
code: "marker-trailing-text",
|
|
402
|
+
message: `Marker has trailing text after ID '${cleanId}': '${remainder}' \u2014 use comma-separated IDs only`,
|
|
403
|
+
filePath,
|
|
404
|
+
line: i + 1,
|
|
405
|
+
id: cleanId
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
markers.push({ type, id: cleanId, filePath, line: i + 1 });
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
match = regex.exec(line);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return { markers, findings };
|
|
415
|
+
}
|
|
416
|
+
function resolveMarkerType(markerName, configuredMarkers) {
|
|
417
|
+
const mapped = MARKER_TYPE_MAP[markerName];
|
|
418
|
+
if (mapped) return mapped;
|
|
419
|
+
const idx = configuredMarkers.indexOf(markerName);
|
|
420
|
+
if (idx === 1) return "test";
|
|
421
|
+
if (idx === 2) return "component";
|
|
422
|
+
return "impl";
|
|
423
|
+
}
|
|
424
|
+
async function collectCodeFiles(codeGlobs, ignore) {
|
|
425
|
+
return collectFiles(codeGlobs, ignore);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// src/core/check/spec-parser.ts
|
|
429
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
430
|
+
import { basename as basename2 } from "path";
|
|
431
|
+
async function parseSpecs(config) {
|
|
432
|
+
const files = await collectSpecFiles(config.specGlobs, config.specIgnore);
|
|
433
|
+
const specFiles = [];
|
|
434
|
+
const requirementIds = /* @__PURE__ */ new Set();
|
|
435
|
+
const acIds = /* @__PURE__ */ new Set();
|
|
436
|
+
const propertyIds = /* @__PURE__ */ new Set();
|
|
437
|
+
const componentNames = /* @__PURE__ */ new Set();
|
|
438
|
+
const idLocations = /* @__PURE__ */ new Map();
|
|
439
|
+
for (const filePath of files) {
|
|
440
|
+
const specFile = await parseSpecFile(filePath, config.crossRefPatterns);
|
|
441
|
+
if (specFile) {
|
|
442
|
+
specFiles.push(specFile);
|
|
443
|
+
for (const id of specFile.requirementIds) requirementIds.add(id);
|
|
444
|
+
for (const id of specFile.acIds) acIds.add(id);
|
|
445
|
+
for (const id of specFile.propertyIds) propertyIds.add(id);
|
|
446
|
+
for (const name of specFile.componentNames) componentNames.add(name);
|
|
447
|
+
for (const [id, loc] of specFile.idLocations ?? []) {
|
|
448
|
+
idLocations.set(id, loc);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const allIds = /* @__PURE__ */ new Set([...requirementIds, ...acIds, ...propertyIds, ...componentNames]);
|
|
453
|
+
return { requirementIds, acIds, propertyIds, componentNames, allIds, specFiles, idLocations };
|
|
454
|
+
}
|
|
455
|
+
async function parseSpecFile(filePath, crossRefPatterns) {
|
|
456
|
+
let content;
|
|
457
|
+
try {
|
|
458
|
+
content = await readFile3(filePath, "utf-8");
|
|
459
|
+
} catch {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
const code = extractCodePrefix(filePath);
|
|
463
|
+
const lines = content.split("\n");
|
|
464
|
+
const requirementIds = [];
|
|
465
|
+
const acIds = [];
|
|
466
|
+
const propertyIds = [];
|
|
467
|
+
const componentNames = [];
|
|
468
|
+
const crossRefs = [];
|
|
469
|
+
const idLocations = /* @__PURE__ */ new Map();
|
|
470
|
+
const componentImplements = /* @__PURE__ */ new Map();
|
|
471
|
+
const reqIdRegex = /^###\s+([A-Z][A-Z0-9]*-\d+(?:\.\d+)?)\s*:/;
|
|
472
|
+
const acIdRegex = /^-\s+(?:\[[ x]\]\s+)?([A-Z][A-Z0-9]*-\d+(?:\.\d+)?_AC-\d+)\s/;
|
|
473
|
+
const propIdRegex = /^-\s+([A-Z][A-Z0-9]*_P-\d+)\s/;
|
|
474
|
+
const componentRegex = /^###\s+([A-Z][A-Z0-9]*-[A-Za-z][A-Za-z0-9]*(?:[A-Z][a-z0-9]*)*)\s*$/;
|
|
475
|
+
let currentComponent = null;
|
|
476
|
+
for (const [i, line] of lines.entries()) {
|
|
477
|
+
const lineNum = i + 1;
|
|
478
|
+
const reqMatch = reqIdRegex.exec(line);
|
|
479
|
+
if (reqMatch?.[1]) {
|
|
480
|
+
requirementIds.push(reqMatch[1]);
|
|
481
|
+
idLocations.set(reqMatch[1], { filePath, line: lineNum });
|
|
482
|
+
}
|
|
483
|
+
const acMatch = acIdRegex.exec(line);
|
|
484
|
+
if (acMatch?.[1]) {
|
|
485
|
+
acIds.push(acMatch[1]);
|
|
486
|
+
idLocations.set(acMatch[1], { filePath, line: lineNum });
|
|
487
|
+
}
|
|
488
|
+
const propMatch = propIdRegex.exec(line);
|
|
489
|
+
if (propMatch?.[1]) {
|
|
490
|
+
propertyIds.push(propMatch[1]);
|
|
491
|
+
idLocations.set(propMatch[1], { filePath, line: lineNum });
|
|
492
|
+
}
|
|
493
|
+
const compMatch = componentRegex.exec(line);
|
|
494
|
+
if (compMatch?.[1]) {
|
|
495
|
+
if (!reqIdRegex.test(line)) {
|
|
496
|
+
componentNames.push(compMatch[1]);
|
|
497
|
+
idLocations.set(compMatch[1], { filePath, line: lineNum });
|
|
498
|
+
currentComponent = compMatch[1];
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (/^#{1,2}\s/.test(line) && !compMatch) {
|
|
502
|
+
currentComponent = null;
|
|
503
|
+
}
|
|
504
|
+
for (const pattern of crossRefPatterns) {
|
|
505
|
+
const patIdx = line.indexOf(pattern);
|
|
506
|
+
if (patIdx !== -1) {
|
|
507
|
+
const afterPattern = line.slice(patIdx + pattern.length);
|
|
508
|
+
const ids = extractIdsFromText(afterPattern);
|
|
509
|
+
if (ids.length > 0) {
|
|
510
|
+
const type = pattern.toLowerCase().includes("implements") ? "implements" : "validates";
|
|
511
|
+
crossRefs.push({ type, ids, filePath, line: i + 1 });
|
|
512
|
+
if (type === "implements" && currentComponent) {
|
|
513
|
+
const existing = componentImplements.get(currentComponent) ?? [];
|
|
514
|
+
existing.push(...ids);
|
|
515
|
+
componentImplements.set(currentComponent, existing);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
filePath,
|
|
523
|
+
code,
|
|
524
|
+
requirementIds,
|
|
525
|
+
acIds,
|
|
526
|
+
propertyIds,
|
|
527
|
+
componentNames,
|
|
528
|
+
crossRefs,
|
|
529
|
+
idLocations,
|
|
530
|
+
componentImplements,
|
|
531
|
+
content
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
function extractCodePrefix(filePath) {
|
|
535
|
+
const name = basename2(filePath, ".md");
|
|
536
|
+
const match = /^(?:REQ|DESIGN|FEAT|EXAMPLE|API)-([A-Z][A-Z0-9]*)-/.exec(name);
|
|
537
|
+
if (match?.[1]) return match[1];
|
|
538
|
+
return "";
|
|
539
|
+
}
|
|
540
|
+
function extractIdsFromText(text) {
|
|
541
|
+
const idRegex = /[A-Z][A-Z0-9]*-\d+(?:\.\d+)?(?:_AC-\d+)?|[A-Z][A-Z0-9]*_P-\d+/g;
|
|
542
|
+
const ids = [];
|
|
543
|
+
let match = idRegex.exec(text);
|
|
544
|
+
while (match !== null) {
|
|
545
|
+
ids.push(match[0]);
|
|
546
|
+
match = idRegex.exec(text);
|
|
547
|
+
}
|
|
548
|
+
return ids;
|
|
549
|
+
}
|
|
550
|
+
async function collectSpecFiles(specGlobs, ignore) {
|
|
551
|
+
return collectFiles(specGlobs, ignore);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// src/core/check/types.ts
|
|
555
|
+
var DEFAULT_CHECK_CONFIG = {
|
|
556
|
+
specGlobs: [
|
|
557
|
+
".awa/specs/ARCHITECTURE.md",
|
|
558
|
+
".awa/specs/FEAT-*.md",
|
|
559
|
+
".awa/specs/REQ-*.md",
|
|
560
|
+
".awa/specs/DESIGN-*.md",
|
|
561
|
+
".awa/specs/EXAMPLE-*.md",
|
|
562
|
+
".awa/specs/API-*.tsp",
|
|
563
|
+
".awa/tasks/TASK-*.md",
|
|
564
|
+
".awa/plans/PLAN-*.md",
|
|
565
|
+
".awa/align/ALIGN-*.md"
|
|
566
|
+
],
|
|
567
|
+
codeGlobs: [
|
|
568
|
+
"**/*.{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}"
|
|
569
|
+
],
|
|
570
|
+
specIgnore: [],
|
|
571
|
+
codeIgnore: [
|
|
572
|
+
"node_modules/**",
|
|
573
|
+
"dist/**",
|
|
574
|
+
"vendor/**",
|
|
575
|
+
"target/**",
|
|
576
|
+
"build/**",
|
|
577
|
+
"out/**",
|
|
578
|
+
".awa/**"
|
|
579
|
+
],
|
|
580
|
+
ignoreMarkers: [],
|
|
581
|
+
markers: ["@awa-impl", "@awa-test", "@awa-component"],
|
|
582
|
+
idPattern: "([A-Z][A-Z0-9]*-\\d+(?:\\.\\d+)?(?:_AC-\\d+)?|[A-Z][A-Z0-9]*_P-\\d+)",
|
|
583
|
+
crossRefPatterns: ["IMPLEMENTS:", "VALIDATES:"],
|
|
584
|
+
format: "text",
|
|
585
|
+
schemaDir: ".awa/.agent/schemas",
|
|
586
|
+
schemaEnabled: true,
|
|
587
|
+
allowWarnings: false,
|
|
588
|
+
specOnly: false,
|
|
589
|
+
fix: true
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
// src/core/trace/scanner.ts
|
|
593
|
+
function buildScanConfig(fileConfig, overrides) {
|
|
594
|
+
const section = fileConfig?.check;
|
|
595
|
+
return {
|
|
596
|
+
specGlobs: toStringArray(section?.["spec-globs"]) ?? [...DEFAULT_CHECK_CONFIG.specGlobs],
|
|
597
|
+
codeGlobs: toStringArray(section?.["code-globs"]) ?? [...DEFAULT_CHECK_CONFIG.codeGlobs],
|
|
598
|
+
specIgnore: toStringArray(section?.["spec-ignore"]) ?? [...DEFAULT_CHECK_CONFIG.specIgnore],
|
|
599
|
+
codeIgnore: toStringArray(section?.["code-ignore"]) ?? [...DEFAULT_CHECK_CONFIG.codeIgnore],
|
|
600
|
+
ignoreMarkers: toStringArray(section?.["ignore-markers"]) ?? [
|
|
601
|
+
...DEFAULT_CHECK_CONFIG.ignoreMarkers
|
|
602
|
+
],
|
|
603
|
+
markers: toStringArray(section?.markers) ?? [...DEFAULT_CHECK_CONFIG.markers],
|
|
604
|
+
idPattern: typeof section?.["id-pattern"] === "string" ? section["id-pattern"] : DEFAULT_CHECK_CONFIG.idPattern,
|
|
605
|
+
crossRefPatterns: toStringArray(section?.["cross-ref-patterns"]) ?? [
|
|
606
|
+
...DEFAULT_CHECK_CONFIG.crossRefPatterns
|
|
607
|
+
],
|
|
608
|
+
format: DEFAULT_CHECK_CONFIG.format,
|
|
609
|
+
schemaDir: DEFAULT_CHECK_CONFIG.schemaDir,
|
|
610
|
+
schemaEnabled: false,
|
|
611
|
+
allowWarnings: true,
|
|
612
|
+
specOnly: false,
|
|
613
|
+
fix: true,
|
|
614
|
+
...overrides
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
async function scan(configPath) {
|
|
618
|
+
const fileConfig = await configLoader.load(configPath ?? null);
|
|
619
|
+
const config = buildScanConfig(fileConfig);
|
|
620
|
+
const [markers, specs] = await Promise.all([scanMarkers(config), parseSpecs(config)]);
|
|
621
|
+
return { markers, specs, config };
|
|
622
|
+
}
|
|
623
|
+
function toStringArray(value) {
|
|
624
|
+
if (Array.isArray(value) && value.every((v) => typeof v === "string")) {
|
|
625
|
+
return value;
|
|
626
|
+
}
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// src/commands/renumber.ts
|
|
631
|
+
async function renumberCommand(options) {
|
|
632
|
+
try {
|
|
633
|
+
if (!options.code && !options.all) {
|
|
634
|
+
logger.error("No feature code or --all specified");
|
|
635
|
+
return 2;
|
|
636
|
+
}
|
|
637
|
+
const { markers, specs } = await scan(options.config);
|
|
638
|
+
let codes;
|
|
639
|
+
if (options.all) {
|
|
640
|
+
codes = discoverFeatureCodes(specs.specFiles);
|
|
641
|
+
if (codes.length === 0) {
|
|
642
|
+
logger.warn("No feature codes discovered from REQ files");
|
|
643
|
+
return 0;
|
|
644
|
+
}
|
|
645
|
+
} else {
|
|
646
|
+
codes = [options.code];
|
|
647
|
+
}
|
|
648
|
+
const dryRun = options.dryRun === true;
|
|
649
|
+
const results = [];
|
|
650
|
+
let hasChanges = false;
|
|
651
|
+
for (const code of codes) {
|
|
652
|
+
try {
|
|
653
|
+
const result = await runRenumberPipeline(code, specs, markers, dryRun);
|
|
654
|
+
results.push(result);
|
|
655
|
+
if (!result.noChange) {
|
|
656
|
+
hasChanges = true;
|
|
657
|
+
}
|
|
658
|
+
} catch (err) {
|
|
659
|
+
if (err instanceof RenumberError && err.errorCode === "CODE_NOT_FOUND") {
|
|
660
|
+
logger.error(err.message);
|
|
661
|
+
return 2;
|
|
662
|
+
}
|
|
663
|
+
throw err;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (options.json) {
|
|
667
|
+
if (results.length === 1) {
|
|
668
|
+
const first = results[0];
|
|
669
|
+
process.stdout.write(`${formatJson(first)}
|
|
670
|
+
`);
|
|
671
|
+
} else {
|
|
672
|
+
const jsonArray = results.map((r) => JSON.parse(formatJson(r)));
|
|
673
|
+
process.stdout.write(`${JSON.stringify(jsonArray, null, 2)}
|
|
674
|
+
`);
|
|
675
|
+
}
|
|
676
|
+
} else {
|
|
677
|
+
for (const result of results) {
|
|
678
|
+
const text = formatText(result, dryRun);
|
|
679
|
+
logger.info(text);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return hasChanges ? 1 : 0;
|
|
683
|
+
} catch (err) {
|
|
684
|
+
if (err instanceof RenumberError) {
|
|
685
|
+
logger.error(err.message);
|
|
686
|
+
return 2;
|
|
687
|
+
}
|
|
688
|
+
if (err instanceof Error) {
|
|
689
|
+
logger.error(err.message);
|
|
690
|
+
} else {
|
|
691
|
+
logger.error(String(err));
|
|
692
|
+
}
|
|
693
|
+
return 2;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
async function runRenumberPipeline(code, specs, markers, dryRun) {
|
|
697
|
+
const { map, noChange } = buildRenumberMap(code, specs);
|
|
698
|
+
if (noChange) {
|
|
699
|
+
return {
|
|
700
|
+
code,
|
|
701
|
+
map,
|
|
702
|
+
affectedFiles: [],
|
|
703
|
+
totalReplacements: 0,
|
|
704
|
+
malformedWarnings: [],
|
|
705
|
+
noChange: true
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
const { affectedFiles, totalReplacements } = await propagate(map, specs, markers, dryRun);
|
|
709
|
+
const fileContents = await collectFileContents(specs, markers, code);
|
|
710
|
+
const malformedWarnings = detectMalformed(code, fileContents);
|
|
711
|
+
return {
|
|
712
|
+
code,
|
|
713
|
+
map,
|
|
714
|
+
affectedFiles,
|
|
715
|
+
totalReplacements,
|
|
716
|
+
malformedWarnings,
|
|
717
|
+
noChange: false
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
function discoverFeatureCodes(specFiles) {
|
|
721
|
+
const codes = /* @__PURE__ */ new Set();
|
|
722
|
+
for (const sf of specFiles) {
|
|
723
|
+
if (sf.code && /^REQ-/.test(sf.filePath.split("/").pop() ?? "")) {
|
|
724
|
+
codes.add(sf.code);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return [...codes].sort();
|
|
728
|
+
}
|
|
729
|
+
async function collectFileContents(specs, markers, code) {
|
|
730
|
+
const paths = /* @__PURE__ */ new Set();
|
|
731
|
+
for (const sf of specs.specFiles) {
|
|
732
|
+
paths.add(sf.filePath);
|
|
733
|
+
}
|
|
734
|
+
for (const m of markers.markers) {
|
|
735
|
+
if (m.id.startsWith(`${code}-`) || m.id.startsWith(`${code}_`)) {
|
|
736
|
+
paths.add(m.filePath);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
const contents = /* @__PURE__ */ new Map();
|
|
740
|
+
for (const p of paths) {
|
|
741
|
+
try {
|
|
742
|
+
const content = await readFile4(p, "utf-8");
|
|
743
|
+
contents.set(p, content);
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return contents;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
export {
|
|
751
|
+
matchSimpleGlob,
|
|
752
|
+
collectFiles,
|
|
753
|
+
scanMarkers,
|
|
754
|
+
parseSpecs,
|
|
755
|
+
DEFAULT_CHECK_CONFIG,
|
|
756
|
+
scan,
|
|
757
|
+
propagate,
|
|
758
|
+
renumberCommand
|
|
759
|
+
};
|
|
760
|
+
//# sourceMappingURL=chunk-WGGMDWBE.js.map
|