vibe-coding-master 0.8.12 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/backend/cli/install-vcm-harness.js +57 -18
- package/dist/backend/gateway/gateway-service.js +5 -3
- package/dist/backend/runtime/terminal-submit.js +1 -0
- package/dist/backend/server.js +14 -4
- package/dist/backend/services/app-settings-service.js +3 -2
- package/dist/backend/services/auto-memory-service.js +6 -4
- package/dist/backend/services/claude-hook-service.js +6 -0
- package/dist/backend/services/claude-transcript-service.js +2 -13
- package/dist/backend/services/claude-version-check.js +46 -0
- package/dist/backend/services/gate-review-service.js +47 -8
- package/dist/backend/services/harness-service.js +41 -20
- package/dist/backend/services/message-service.js +30 -6
- package/dist/backend/services/pm-user-question.js +2 -1
- package/dist/backend/services/project-service.js +8 -2
- package/dist/backend/services/runtime-recovery-service.js +36 -9
- package/dist/backend/services/session-service.js +10 -19
- package/dist/backend/services/task-close-service.js +1 -0
- package/dist/backend/services/terminal-process-exit-service.js +4 -1
- package/dist/backend/services/workflow-control-service.js +55 -11
- package/dist/backend/templates/harness/architect-agent.js +2 -1
- package/dist/backend/templates/harness/architect-evidence-worker-agent.js +2 -1
- package/dist/backend/templates/harness/architect-scaffold-worker-agent.js +8 -2
- package/dist/backend/templates/harness/architect-validation-worker-agent.js +2 -1
- package/dist/backend/templates/harness/architect-worker-frontmatter.js +33 -0
- package/dist/backend/templates/harness/check-scaffold-ledger.js +480 -511
- package/dist/backend/templates/harness/coder-worker-agent.js +1 -0
- package/dist/backend/templates/harness/gate-review.js +676 -643
- package/dist/backend/templates/harness/node-tool-runtime.js +102 -0
- package/dist/backend/templates/harness/resolve-durable-doc-assignment.js +49 -55
- package/dist/backend/templates/harness/restart-architect-skill.js +49 -58
- package/dist/backend/templates/harness/vcm-ask-user-skill.js +57 -59
- package/dist/backend/templates/harness/vcm-propose-memory-skill.js +4 -0
- package/dist/backend/templates/harness/vcm-report-harness-issue-skill.js +6 -2
- package/dist/backend/templates/harness/vcm-task-state-skill.js +66 -72
- package/dist/shared/types/app-settings.js +31 -0
- package/dist-frontend/assets/index-CShYYQQa.css +32 -0
- package/dist-frontend/assets/{index-DqANomt1.js → index-DOvvS76Q.js} +41 -41
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/scripts/harness-tools/check-durable-docs +332 -298
- package/scripts/harness-tools/generate-module-index +1047 -512
- package/scripts/harness-tools/generate-public-surface +1231 -1069
- package/scripts/harness-tools/package.json +3 -0
- package/scripts/harness-tools/run-long-check +608 -531
- package/scripts/harness-tools/vcm-artifact +181 -113
- package/scripts/harness-tools/vcm-bash-guard +839 -605
- package/scripts/harness-tools/vcm-subagent-guard +67 -63
- package/scripts/harness-tools/watch-job +313 -238
- package/dist-frontend/assets/index-xgiQeUD5.css +0 -32
|
@@ -1,298 +1,332 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
// Check VCM durable project documentation contracts.
|
|
4
|
+
|
|
5
|
+
const childProcess = require("node:child_process");
|
|
6
|
+
const fs = require("node:fs");
|
|
7
|
+
const path = require("node:path");
|
|
8
|
+
|
|
9
|
+
const EXCLUDED_PARTS = new Set([".git", ".claude", ".ai", "target", "node_modules", "dist", "build"]);
|
|
10
|
+
const KNOWN_ISSUE_FIELDS = [
|
|
11
|
+
"status",
|
|
12
|
+
"category",
|
|
13
|
+
"affected modules/surfaces",
|
|
14
|
+
"current gap",
|
|
15
|
+
"impact",
|
|
16
|
+
"mitigation or workaround",
|
|
17
|
+
"resolution condition",
|
|
18
|
+
"related issues"
|
|
19
|
+
];
|
|
20
|
+
const TERMINAL_PLAN_WORDS = ["complete", "completed", "shipped", "delivered", "closed", "done", "superseded", "obsolete"];
|
|
21
|
+
|
|
22
|
+
function parseArgs(argv) {
|
|
23
|
+
const args = { jsonOutput: false };
|
|
24
|
+
for (const argument of argv) {
|
|
25
|
+
if (argument === "--json") {
|
|
26
|
+
args.jsonOutput = true;
|
|
27
|
+
} else if (argument === "-h" || argument === "--help") {
|
|
28
|
+
process.stdout.write("usage: check-durable-docs [-h] [--json]\n\nCheck VCM durable project documentation contracts.\n");
|
|
29
|
+
process.exit(0);
|
|
30
|
+
} else {
|
|
31
|
+
process.stderr.write("usage: check-durable-docs [-h] [--json]\ncheck-durable-docs: error: unrecognized arguments: " + argument + "\n");
|
|
32
|
+
process.exit(2);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return args;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function repositoryRoot() {
|
|
39
|
+
const result = childProcess.spawnSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8" });
|
|
40
|
+
if (!result.error && result.status === 0 && result.stdout.trim()) {
|
|
41
|
+
return fs.realpathSync(result.stdout.trim());
|
|
42
|
+
}
|
|
43
|
+
return fs.realpathSync(process.cwd());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function relative(target, root) {
|
|
47
|
+
return path.relative(root, fs.realpathSync(target)).split(path.sep).join("/");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function readLines(target) {
|
|
51
|
+
return fs.readFileSync(target, "utf8").split(/\r\n|\n|\r/).filter((line, index, all) => index < all.length - 1 || line !== "");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isFile(target) {
|
|
55
|
+
try {
|
|
56
|
+
return fs.statSync(target).isFile();
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function markdownHeadings(lines, pattern) {
|
|
63
|
+
const headings = [];
|
|
64
|
+
let fenced = false;
|
|
65
|
+
lines.forEach((line, index) => {
|
|
66
|
+
if (/^\s*(```|~~~)/.test(line)) {
|
|
67
|
+
fenced = !fenced;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (fenced) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const match = pattern.exec(line);
|
|
74
|
+
if (match) {
|
|
75
|
+
headings.push({ index, match });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
return headings;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function firstMatchLine(lines, pattern, start = 0, end = undefined) {
|
|
82
|
+
const stop = end === undefined ? lines.length : Math.min(end, lines.length);
|
|
83
|
+
for (let index = start; index < stop; index += 1) {
|
|
84
|
+
if (pattern.test(lines[index])) {
|
|
85
|
+
return index + 1;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function finding(code, file, line, message) {
|
|
92
|
+
return { code, path: file, line, message };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function checkKnownIssues(root, findings) {
|
|
96
|
+
const file = path.join(root, "docs/known-issues.md");
|
|
97
|
+
if (!isFile(file)) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const lines = readLines(file);
|
|
101
|
+
const headings = markdownHeadings(lines, /^##\s+(?:\[)?KI-(\d+)(?:\])?\b/i);
|
|
102
|
+
headings.forEach(({ index: start, match }, headingIndex) => {
|
|
103
|
+
const end = headingIndex + 1 < headings.length ? headings[headingIndex + 1].index : lines.length;
|
|
104
|
+
const issueId = "KI-" + match[1];
|
|
105
|
+
const fields = {};
|
|
106
|
+
for (const line of lines.slice(start + 1, end)) {
|
|
107
|
+
const fieldMatch = /^\s*-\s*([^:]+):\s*(.*)$/.exec(line);
|
|
108
|
+
if (fieldMatch) {
|
|
109
|
+
fields[fieldMatch[1].trim().toLowerCase()] = fieldMatch[2].trim();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const missing = KNOWN_ISSUE_FIELDS.filter((field) => !(field in fields));
|
|
113
|
+
if (missing.length > 0) {
|
|
114
|
+
findings.push(finding("DD_KI_FIELDS", relative(file, root), start + 1,
|
|
115
|
+
issueId + " is missing required current-state fields: " + missing.join(", ") + "."));
|
|
116
|
+
}
|
|
117
|
+
const status = (fields.status || "").toLowerCase();
|
|
118
|
+
if (status && !["open", "planned", "accepted"].includes(status)) {
|
|
119
|
+
findings.push(finding("DD_KI_STATUS", relative(file, root), start + 1,
|
|
120
|
+
issueId + " has non-current status '" + fields.status + "'. Remove terminal issues instead of retaining history."));
|
|
121
|
+
}
|
|
122
|
+
const resolvedLine = firstMatchLine(lines, /(?:\bRESOLVED\b|\bFIXED\b|\bCLOSED\b|\bOBSOLETE\b|nothing remains open)/i, start, end);
|
|
123
|
+
if (resolvedLine !== null) {
|
|
124
|
+
findings.push(finding("DD_KI_RESOLVED_HISTORY", relative(file, root), resolvedLine,
|
|
125
|
+
issueId + " retains resolved or terminal history. Rewrite the entry around only the current unresolved gap."));
|
|
126
|
+
}
|
|
127
|
+
const provenanceLine = firstMatchLine(
|
|
128
|
+
lines,
|
|
129
|
+
/\bROUND-?\d+\b|\b(?:reviewer|tester)[ -]?(?:confirmed|approved|proven)|\bcommit\s+`?[0-9a-f]{7,40}\b/i,
|
|
130
|
+
start,
|
|
131
|
+
end
|
|
132
|
+
);
|
|
133
|
+
if (provenanceLine !== null) {
|
|
134
|
+
findings.push(finding("DD_KI_TASK_HISTORY", relative(file, root), provenanceLine,
|
|
135
|
+
issueId + " contains task or validation provenance that belongs in Git, PR, or task evidence."));
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function walkFiles(directory, accept, results = []) {
|
|
141
|
+
let entries;
|
|
142
|
+
try {
|
|
143
|
+
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
144
|
+
} catch {
|
|
145
|
+
return results;
|
|
146
|
+
}
|
|
147
|
+
for (const entry of entries) {
|
|
148
|
+
const target = path.join(directory, entry.name);
|
|
149
|
+
if (entry.isDirectory()) {
|
|
150
|
+
walkFiles(target, accept, results);
|
|
151
|
+
} else if (entry.isFile() && accept(entry.name)) {
|
|
152
|
+
results.push(target);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return results;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function checkActivePlans(root, findings) {
|
|
159
|
+
const plansRoot = path.join(root, "docs/plans");
|
|
160
|
+
try {
|
|
161
|
+
if (!fs.statSync(plansRoot).isDirectory()) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const terminalPattern = new RegExp("\\b(?:" + TERMINAL_PLAN_WORDS.join("|") + ")\\b", "i");
|
|
168
|
+
const statusPattern = /^\s*(?:>\s*)?(?:\*\*)?(?:status|stability)(?:\*\*)?\s*[:\-—]/i;
|
|
169
|
+
const closedDocPattern = /\b(?:plan|task|document|doc|phase|work)\b.{0,40}\b(?:is\s+)?(?:closed|complete|completed|shipped|delivered|done)\b/i;
|
|
170
|
+
for (const file of walkFiles(plansRoot, (name) => name.endsWith(".md")).sort()) {
|
|
171
|
+
const lines = readLines(file);
|
|
172
|
+
for (const [index, line] of lines.slice(0, 60).entries()) {
|
|
173
|
+
if ((statusPattern.test(line) && terminalPattern.test(line)) || (index < 15 && closedDocPattern.test(line))) {
|
|
174
|
+
findings.push(finding("DD_ACTIVE_PLAN_TERMINAL", relative(file, root), index + 1,
|
|
175
|
+
"Completed or terminal plans must leave the active plans collection; Git and PR history preserve execution history."));
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function architectureDocs(root) {
|
|
183
|
+
const paths = new Set();
|
|
184
|
+
const projectDoc = path.join(root, "docs/ARCHITECTURE.md");
|
|
185
|
+
if (isFile(projectDoc)) {
|
|
186
|
+
paths.add(projectDoc);
|
|
187
|
+
}
|
|
188
|
+
const visit = (directory) => {
|
|
189
|
+
let entries;
|
|
190
|
+
try {
|
|
191
|
+
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
192
|
+
} catch {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
for (const entry of entries) {
|
|
196
|
+
if (EXCLUDED_PARTS.has(entry.name)) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const target = path.join(directory, entry.name);
|
|
200
|
+
if (entry.isDirectory()) {
|
|
201
|
+
visit(target);
|
|
202
|
+
} else if (entry.isFile() && entry.name === "ARCHITECTURE.md") {
|
|
203
|
+
paths.add(target);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
visit(root);
|
|
208
|
+
return [...paths].sort();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function checkArchitectureHistory(root, findings) {
|
|
212
|
+
const patterns = [
|
|
213
|
+
["DD_ARCH_TASK_LABEL", /\bSCF-[A-Za-z0-9.-]+\b|\bROUND-?\d+\b/i, "task labels"],
|
|
214
|
+
["DD_ARCH_COMMIT_HISTORY", /\bcommit\s+`?[0-9a-f]{7,40}\b/i, "commit history"],
|
|
215
|
+
["DD_ARCH_VALIDATION_HISTORY", /\b(?:reviewer|tester).{0,40}(?:PASS|confirmed|approved|proven)\b/i, "role validation history"]
|
|
216
|
+
];
|
|
217
|
+
for (const file of architectureDocs(root)) {
|
|
218
|
+
const lines = readLines(file);
|
|
219
|
+
for (const [code, pattern, label] of patterns) {
|
|
220
|
+
const line = firstMatchLine(lines, pattern);
|
|
221
|
+
if (line !== null) {
|
|
222
|
+
findings.push(finding(code, relative(file, root), line,
|
|
223
|
+
"Architecture documentation contains " + label + "; keep only current architecture and use Git, PR, or task evidence for history."));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function checkTestingHistory(root, findings) {
|
|
230
|
+
const file = path.join(root, "docs/TESTING.md");
|
|
231
|
+
if (!isFile(file)) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const lines = readLines(file);
|
|
235
|
+
const patterns = [
|
|
236
|
+
["DD_TEST_TASK_HISTORY", /\bROUND-?\d+\b|\bcommit\s+`?[0-9a-f]{7,40}\b/i, "task or commit history"],
|
|
237
|
+
["DD_TEST_VERDICT_HISTORY", /\b(?:reviewer|tester)[ -]?(?:confirmed|approved|proven)|\bL[0-4]\s+(?:PASS|FAIL)\b/i, "past validation verdicts"],
|
|
238
|
+
["DD_TEST_LEGACY_OWNER", /\breviewer-owned\b|\brun by the reviewer\b/i, "legacy Reviewer ownership"]
|
|
239
|
+
];
|
|
240
|
+
for (const [code, pattern, label] of patterns) {
|
|
241
|
+
const line = firstMatchLine(lines, pattern);
|
|
242
|
+
if (line !== null) {
|
|
243
|
+
findings.push(finding(code, relative(file, root), line,
|
|
244
|
+
"Testing documentation contains " + label + "; keep current strategy, runnable commands, case definitions, and current gaps only."));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function loadModuleIndex(root) {
|
|
250
|
+
const file = path.join(root, ".ai/generated/module-index.json");
|
|
251
|
+
if (!isFile(file)) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
const value = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
256
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
257
|
+
} catch {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function checkGeneratedArchitectureFacts(root, findings) {
|
|
263
|
+
const index = loadModuleIndex(root);
|
|
264
|
+
if (index === null || !Array.isArray(index.layers)) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const layerCounts = new Map();
|
|
268
|
+
for (const layer of index.layers) {
|
|
269
|
+
if (!layer || typeof layer !== "object" || typeof layer.name !== "string" || !Array.isArray(layer.modules)) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
layerCounts.set(layer.name, layer.modules.length);
|
|
273
|
+
for (const module of layer.modules) {
|
|
274
|
+
if (!module || typeof module !== "object") {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const architectureDoc = module.architectureDoc;
|
|
278
|
+
if (typeof architectureDoc === "string" && architectureDoc && !isFile(path.join(root, architectureDoc))) {
|
|
279
|
+
findings.push(finding("DD_MODULE_ARCH_MISSING", architectureDoc, 1,
|
|
280
|
+
"Module index names a missing architecture document for module '" + (module.name ?? "unknown") + "'."));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const projectDoc = path.join(root, "docs/ARCHITECTURE.md");
|
|
285
|
+
if (!isFile(projectDoc)) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
readLines(projectDoc).forEach((line, lineIndex) => {
|
|
289
|
+
const match = /^\|\s*`?([^`|]+)`?\s*\|\s*(\d+)\s*\|/.exec(line);
|
|
290
|
+
if (!match) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const name = match[1].trim();
|
|
294
|
+
if (!layerCounts.has(name)) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const documented = Number(match[2]);
|
|
298
|
+
const generated = layerCounts.get(name);
|
|
299
|
+
if (documented !== generated) {
|
|
300
|
+
findings.push(finding("DD_ARCH_GENERATED_COUNT", relative(projectDoc, root), lineIndex + 1,
|
|
301
|
+
"Layer '" + name + "' documents " + documented + " modules, but .ai/generated/module-index.json contains " + generated + "."));
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function main(argv) {
|
|
307
|
+
const args = parseArgs(argv);
|
|
308
|
+
const root = repositoryRoot();
|
|
309
|
+
const findings = [];
|
|
310
|
+
checkKnownIssues(root, findings);
|
|
311
|
+
checkActivePlans(root, findings);
|
|
312
|
+
checkArchitectureHistory(root, findings);
|
|
313
|
+
checkTestingHistory(root, findings);
|
|
314
|
+
checkGeneratedArchitectureFacts(root, findings);
|
|
315
|
+
findings.sort((left, right) =>
|
|
316
|
+
left.path < right.path ? -1 : left.path > right.path ? 1
|
|
317
|
+
: left.line !== right.line ? left.line - right.line
|
|
318
|
+
: left.code < right.code ? -1 : left.code > right.code ? 1 : 0);
|
|
319
|
+
if (args.jsonOutput) {
|
|
320
|
+
process.stdout.write(JSON.stringify({ ok: findings.length === 0, findings }, null, 2) + "\n");
|
|
321
|
+
} else if (findings.length > 0) {
|
|
322
|
+
for (const item of findings) {
|
|
323
|
+
process.stdout.write(item.path + ":" + item.line + ": [" + item.code + "] " + item.message + "\n");
|
|
324
|
+
}
|
|
325
|
+
process.stdout.write("Durable docs audit failed with " + findings.length + " finding(s).\n");
|
|
326
|
+
} else {
|
|
327
|
+
process.stdout.write("Durable docs audit passed.\n");
|
|
328
|
+
}
|
|
329
|
+
return findings.length > 0 ? 1 : 0;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
process.exitCode = main(process.argv.slice(2));
|