@xenonbyte/da-vinci-workflow 0.1.26 → 0.2.2
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/CHANGELOG.md +31 -0
- package/README.md +28 -65
- package/README.zh-CN.md +28 -65
- package/bin/da-vinci-tui.js +8 -0
- package/commands/claude/dv/continue.md +5 -0
- package/commands/codex/prompts/dv-continue.md +6 -1
- package/commands/gemini/dv/continue.toml +5 -0
- package/commands/templates/dv-continue.shared.md +33 -0
- package/docs/dv-command-reference.md +35 -0
- package/docs/execution-chain-migration.md +46 -0
- package/docs/execution-chain-plan.md +125 -0
- package/docs/prompt-entrypoints.md +8 -0
- package/docs/skill-usage.md +217 -0
- package/docs/workflow-examples.md +10 -0
- package/docs/workflow-overview.md +26 -0
- package/docs/zh-CN/dv-command-reference.md +35 -0
- package/docs/zh-CN/execution-chain-migration.md +46 -0
- package/docs/zh-CN/prompt-entrypoints.md +8 -0
- package/docs/zh-CN/skill-usage.md +217 -0
- package/docs/zh-CN/workflow-examples.md +10 -0
- package/docs/zh-CN/workflow-overview.md +26 -0
- package/lib/artifact-parsers.js +120 -0
- package/lib/audit.js +61 -0
- package/lib/cli.js +351 -13
- package/lib/diff-spec.js +242 -0
- package/lib/execution-signals.js +136 -0
- package/lib/lint-bindings.js +143 -0
- package/lib/lint-spec.js +408 -0
- package/lib/lint-tasks.js +176 -0
- package/lib/planning-parsers.js +567 -0
- package/lib/scaffold.js +193 -0
- package/lib/scope-check.js +603 -0
- package/lib/sidecars.js +369 -0
- package/lib/supervisor-review.js +28 -3
- package/lib/utils.js +10 -2
- package/lib/verify.js +652 -0
- package/lib/workflow-contract.js +107 -0
- package/lib/workflow-persisted-state.js +297 -0
- package/lib/workflow-state.js +785 -0
- package/package.json +13 -3
- package/references/artifact-templates.md +26 -0
- package/references/checkpoints.md +14 -0
- package/references/modes.md +10 -0
- package/tui/catalog.js +1190 -0
- package/tui/index.js +727 -0
package/lib/verify.js
ADDED
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { STATUS } = require("./workflow-contract");
|
|
4
|
+
const {
|
|
5
|
+
normalizeText,
|
|
6
|
+
unique,
|
|
7
|
+
resolveImplementationLanding,
|
|
8
|
+
resolveChangeDir,
|
|
9
|
+
parseBindingsArtifact,
|
|
10
|
+
parseTasksArtifact,
|
|
11
|
+
parseVerificationArtifact,
|
|
12
|
+
parseRuntimeSpecs,
|
|
13
|
+
readChangeArtifacts,
|
|
14
|
+
readArtifactTexts
|
|
15
|
+
} = require("./planning-parsers");
|
|
16
|
+
|
|
17
|
+
const CODE_FILE_EXTENSIONS = new Set([".js", ".jsx", ".ts", ".tsx", ".html", ".css", ".scss"]);
|
|
18
|
+
const NON_IMPLEMENTATION_DIR_NAMES = new Set([
|
|
19
|
+
".git",
|
|
20
|
+
".da-vinci",
|
|
21
|
+
"node_modules",
|
|
22
|
+
"test",
|
|
23
|
+
"tests",
|
|
24
|
+
"__tests__",
|
|
25
|
+
"__mocks__",
|
|
26
|
+
"fixtures",
|
|
27
|
+
"__fixtures__",
|
|
28
|
+
"scripts",
|
|
29
|
+
"coverage",
|
|
30
|
+
"dist",
|
|
31
|
+
"build",
|
|
32
|
+
".next",
|
|
33
|
+
".nuxt",
|
|
34
|
+
".storybook",
|
|
35
|
+
"storybook-static",
|
|
36
|
+
"cypress",
|
|
37
|
+
"playwright-report",
|
|
38
|
+
"e2e"
|
|
39
|
+
]);
|
|
40
|
+
const NON_IMPLEMENTATION_FILE_PATTERNS = [
|
|
41
|
+
/\.test\.[a-z0-9]+$/i,
|
|
42
|
+
/\.spec\.[a-z0-9]+$/i,
|
|
43
|
+
/\.stories\.[a-z0-9]+$/i,
|
|
44
|
+
/\.story\.[a-z0-9]+$/i
|
|
45
|
+
];
|
|
46
|
+
const MAX_SCANNED_FILES = 2000;
|
|
47
|
+
const MAX_SCANNED_BYTES_PER_FILE = 512 * 1024;
|
|
48
|
+
const MAX_SCANNED_DIRECTORIES = 10000;
|
|
49
|
+
const MAX_SCAN_DEPTH = 32;
|
|
50
|
+
|
|
51
|
+
function buildEnvelope(name, projectRoot, strict) {
|
|
52
|
+
return {
|
|
53
|
+
status: STATUS.PASS,
|
|
54
|
+
failures: [],
|
|
55
|
+
warnings: [],
|
|
56
|
+
notes: [],
|
|
57
|
+
projectRoot,
|
|
58
|
+
changeId: null,
|
|
59
|
+
strict,
|
|
60
|
+
surface: name
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function finalize(result, options = {}) {
|
|
65
|
+
result.failures = unique(result.failures);
|
|
66
|
+
result.warnings = unique(result.warnings);
|
|
67
|
+
result.notes = unique(result.notes);
|
|
68
|
+
|
|
69
|
+
if (result.failures.length > 0) {
|
|
70
|
+
result.status = STATUS.BLOCK;
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
if (result.warnings.length > 0) {
|
|
74
|
+
result.status = result.strict || options.strictWarnings ? STATUS.BLOCK : STATUS.WARN;
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
result.status = STATUS.PASS;
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isNonImplementationDirName(name) {
|
|
82
|
+
return NON_IMPLEMENTATION_DIR_NAMES.has(String(name || "").toLowerCase());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isNonImplementationFileName(name) {
|
|
86
|
+
const normalized = String(name || "").trim();
|
|
87
|
+
if (!normalized) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return NON_IMPLEMENTATION_FILE_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function collectCodeFiles(projectRoot) {
|
|
94
|
+
const files = [];
|
|
95
|
+
const scan = {
|
|
96
|
+
truncatedByFileLimit: false,
|
|
97
|
+
truncatedByDirectoryLimit: false,
|
|
98
|
+
depthLimitHits: 0,
|
|
99
|
+
skippedSymlinks: 0,
|
|
100
|
+
readErrors: 0,
|
|
101
|
+
scannedDirectories: 0
|
|
102
|
+
};
|
|
103
|
+
const queue = [{ dir: projectRoot, depth: 0 }];
|
|
104
|
+
const visited = new Set();
|
|
105
|
+
while (queue.length > 0 && files.length < MAX_SCANNED_FILES) {
|
|
106
|
+
const current = queue.pop();
|
|
107
|
+
if (current.depth > MAX_SCAN_DEPTH) {
|
|
108
|
+
scan.depthLimitHits += 1;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let resolvedCurrent;
|
|
113
|
+
try {
|
|
114
|
+
resolvedCurrent = fs.realpathSync(current.dir);
|
|
115
|
+
} catch (_error) {
|
|
116
|
+
scan.readErrors += 1;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (visited.has(resolvedCurrent)) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
visited.add(resolvedCurrent);
|
|
123
|
+
scan.scannedDirectories += 1;
|
|
124
|
+
if (scan.scannedDirectories > MAX_SCANNED_DIRECTORIES) {
|
|
125
|
+
scan.truncatedByDirectoryLimit = true;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let entries = [];
|
|
130
|
+
try {
|
|
131
|
+
entries = fs.readdirSync(current.dir, { withFileTypes: true });
|
|
132
|
+
} catch (_error) {
|
|
133
|
+
scan.readErrors += 1;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
for (const entry of entries) {
|
|
137
|
+
if (files.length >= MAX_SCANNED_FILES) {
|
|
138
|
+
scan.truncatedByFileLimit = true;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
if (entry.isDirectory() && isNonImplementationDirName(entry.name)) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (entry.isSymbolicLink()) {
|
|
145
|
+
scan.skippedSymlinks += 1;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const absolutePath = path.join(current.dir, entry.name);
|
|
149
|
+
if (entry.isDirectory()) {
|
|
150
|
+
if (current.depth >= MAX_SCAN_DEPTH) {
|
|
151
|
+
scan.depthLimitHits += 1;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
queue.push({
|
|
155
|
+
dir: absolutePath,
|
|
156
|
+
depth: current.depth + 1
|
|
157
|
+
});
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (!entry.isFile()) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (!CODE_FILE_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (isNonImplementationFileName(entry.name)) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
files.push(absolutePath);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (files.length >= MAX_SCANNED_FILES) {
|
|
173
|
+
scan.truncatedByFileLimit = true;
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
files: files.sort(),
|
|
177
|
+
scan
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function safeReadFile(filePath) {
|
|
182
|
+
try {
|
|
183
|
+
const stat = fs.statSync(filePath);
|
|
184
|
+
if (stat.size > MAX_SCANNED_BYTES_PER_FILE) {
|
|
185
|
+
return "";
|
|
186
|
+
}
|
|
187
|
+
return fs.readFileSync(filePath, "utf8");
|
|
188
|
+
} catch (_error) {
|
|
189
|
+
return "";
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function readCodeFileForScan(filePath) {
|
|
194
|
+
try {
|
|
195
|
+
const stat = fs.statSync(filePath);
|
|
196
|
+
if (stat.size > MAX_SCANNED_BYTES_PER_FILE) {
|
|
197
|
+
return {
|
|
198
|
+
text: "",
|
|
199
|
+
bytesRead: 0,
|
|
200
|
+
skippedLarge: true,
|
|
201
|
+
readError: false
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
text: fs.readFileSync(filePath, "utf8"),
|
|
206
|
+
bytesRead: stat.size,
|
|
207
|
+
skippedLarge: false,
|
|
208
|
+
readError: false
|
|
209
|
+
};
|
|
210
|
+
} catch (_error) {
|
|
211
|
+
return {
|
|
212
|
+
text: "",
|
|
213
|
+
bytesRead: 0,
|
|
214
|
+
skippedLarge: false,
|
|
215
|
+
readError: true
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function allCovered(checks) {
|
|
221
|
+
for (const check of checks) {
|
|
222
|
+
if (!check.covered) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function createSharedSetup(projectPathInput, options = {}) {
|
|
230
|
+
const projectRoot = path.resolve(projectPathInput || process.cwd());
|
|
231
|
+
const strict = options && options.strict === true;
|
|
232
|
+
const requestedChangeId = options && options.changeId ? String(options.changeId).trim() : "";
|
|
233
|
+
const resolved = resolveChangeDir(projectRoot, requestedChangeId);
|
|
234
|
+
|
|
235
|
+
let artifactPaths = null;
|
|
236
|
+
let artifacts = null;
|
|
237
|
+
if (resolved.changeDir) {
|
|
238
|
+
artifactPaths = readChangeArtifacts(projectRoot, resolved.changeId);
|
|
239
|
+
artifacts = readArtifactTexts(artifactPaths);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
projectRoot,
|
|
244
|
+
strict,
|
|
245
|
+
requestedChangeId,
|
|
246
|
+
resolved,
|
|
247
|
+
artifactPaths,
|
|
248
|
+
artifacts
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function commonSetup(surface, projectPathInput, options) {
|
|
253
|
+
const sharedSetup =
|
|
254
|
+
options && options.sharedSetup
|
|
255
|
+
? options.sharedSetup
|
|
256
|
+
: createSharedSetup(projectPathInput, options || {});
|
|
257
|
+
const strict =
|
|
258
|
+
(options && options.strict === true) || (sharedSetup && sharedSetup.strict === true);
|
|
259
|
+
const result = buildEnvelope(surface, sharedSetup.projectRoot, strict);
|
|
260
|
+
|
|
261
|
+
result.failures.push(...sharedSetup.resolved.failures);
|
|
262
|
+
result.notes.push(...sharedSetup.resolved.notes);
|
|
263
|
+
if (!sharedSetup.resolved.changeDir) {
|
|
264
|
+
return {
|
|
265
|
+
result: finalize(result),
|
|
266
|
+
resolved: sharedSetup.resolved,
|
|
267
|
+
artifacts: sharedSetup.artifacts,
|
|
268
|
+
artifactPaths: sharedSetup.artifactPaths,
|
|
269
|
+
sharedSetup
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
result.changeId = sharedSetup.resolved.changeId;
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
result,
|
|
276
|
+
resolved: sharedSetup.resolved,
|
|
277
|
+
artifacts: sharedSetup.artifacts,
|
|
278
|
+
artifactPaths: sharedSetup.artifactPaths,
|
|
279
|
+
sharedSetup
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function verifyBindings(projectPathInput, options = {}) {
|
|
284
|
+
const setup = commonSetup("verify-bindings", projectPathInput, options);
|
|
285
|
+
const { result, artifacts } = setup;
|
|
286
|
+
if (!artifacts) {
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (!artifacts.bindings) {
|
|
291
|
+
result.failures.push("Missing `pencil-bindings.md` for verify-bindings.");
|
|
292
|
+
return finalize(result);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const parsed = parseBindingsArtifact(artifacts.bindings);
|
|
296
|
+
if (parsed.mappings.length === 0) {
|
|
297
|
+
result.failures.push("No implementation mappings found in `pencil-bindings.md`.");
|
|
298
|
+
return finalize(result);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
for (const mapping of parsed.mappings) {
|
|
302
|
+
const landing = resolveImplementationLanding(result.projectRoot, mapping.implementation);
|
|
303
|
+
if (!landing) {
|
|
304
|
+
result.failures.push(
|
|
305
|
+
`Missing implementation landing for binding "${mapping.implementation}" -> "${mapping.designPage}".`
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (parsed.malformed.length > 0) {
|
|
311
|
+
for (const malformed of parsed.malformed) {
|
|
312
|
+
result.warnings.push(`Malformed binding mapping entry: "${malformed}".`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return finalize(result);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function verifyImplementation(projectPathInput, options = {}) {
|
|
320
|
+
const setup = commonSetup("verify-implementation", projectPathInput, options);
|
|
321
|
+
const { result, resolved, artifacts } = setup;
|
|
322
|
+
if (!artifacts) {
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const codeScan = collectCodeFiles(result.projectRoot);
|
|
327
|
+
const codeFiles = codeScan.files;
|
|
328
|
+
if (codeFiles.length === 0) {
|
|
329
|
+
result.failures.push("No implementation files were found for verify-implementation.");
|
|
330
|
+
return finalize(result);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const specRecords = parseRuntimeSpecs(resolved.changeDir, result.projectRoot);
|
|
334
|
+
const tasksArtifact = parseTasksArtifact(artifacts.tasks || "");
|
|
335
|
+
|
|
336
|
+
if (specRecords.length === 0) {
|
|
337
|
+
result.failures.push("Missing runtime specs for verify-implementation.");
|
|
338
|
+
return finalize(result);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const stateChecks = [];
|
|
342
|
+
for (const record of specRecords) {
|
|
343
|
+
const states = record.parsed.sections.states.items || [];
|
|
344
|
+
for (const stateItem of states) {
|
|
345
|
+
const state = normalizeText(String(stateItem || "").split(":")[0]);
|
|
346
|
+
if (!state) {
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
const stateTokens = state.split(" ").filter((token) => token.length >= 3);
|
|
350
|
+
if (stateTokens.length === 0) {
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
stateChecks.push({
|
|
354
|
+
recordPath: record.path,
|
|
355
|
+
stateItem,
|
|
356
|
+
tokens: stateTokens,
|
|
357
|
+
covered: false
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const taskGroupChecks = [];
|
|
363
|
+
for (const group of tasksArtifact.taskGroups) {
|
|
364
|
+
const titleTokens = normalizeText(group.title)
|
|
365
|
+
.split(" ")
|
|
366
|
+
.filter((token) => token.length >= 4);
|
|
367
|
+
if (titleTokens.length === 0) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
taskGroupChecks.push({
|
|
372
|
+
group,
|
|
373
|
+
tokens: titleTokens,
|
|
374
|
+
covered: false
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
let scannedBytes = 0;
|
|
379
|
+
let skippedLargeFiles = 0;
|
|
380
|
+
let readErrors = 0;
|
|
381
|
+
for (const codeFile of codeFiles) {
|
|
382
|
+
const read = readCodeFileForScan(codeFile);
|
|
383
|
+
scannedBytes += read.bytesRead;
|
|
384
|
+
if (read.skippedLarge) {
|
|
385
|
+
skippedLargeFiles += 1;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (read.readError) {
|
|
389
|
+
readErrors += 1;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const lower = String(read.text || "").toLowerCase();
|
|
394
|
+
if (!lower) {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
for (const check of stateChecks) {
|
|
399
|
+
if (check.covered) {
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
check.covered = check.tokens.some((token) => lower.includes(token));
|
|
403
|
+
}
|
|
404
|
+
for (const check of taskGroupChecks) {
|
|
405
|
+
if (check.covered) {
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
check.covered = check.tokens.some((token) => lower.includes(token));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (allCovered(stateChecks) && allCovered(taskGroupChecks)) {
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
for (const check of stateChecks) {
|
|
417
|
+
if (!check.covered) {
|
|
418
|
+
result.warnings.push(
|
|
419
|
+
`State coverage may be missing in implementation: "${check.stateItem}" (${check.recordPath}).`
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
for (const check of taskGroupChecks) {
|
|
424
|
+
if (!check.covered) {
|
|
425
|
+
result.warnings.push(
|
|
426
|
+
`Task-group intent may be missing in implementation: "${check.group.id}. ${check.group.title}".`
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (codeScan.scan.truncatedByFileLimit) {
|
|
432
|
+
result.warnings.push(
|
|
433
|
+
`verify-implementation hit file scan limit (${MAX_SCANNED_FILES}); deep coverage may be incomplete.`
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
if (codeScan.scan.truncatedByDirectoryLimit) {
|
|
437
|
+
result.warnings.push(
|
|
438
|
+
`verify-implementation hit directory scan limit (${MAX_SCANNED_DIRECTORIES}); coverage may be incomplete.`
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
if (codeScan.scan.readErrors + readErrors > 0) {
|
|
442
|
+
result.warnings.push(
|
|
443
|
+
`verify-implementation skipped unreadable files/directories (${codeScan.scan.readErrors + readErrors}).`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
if (codeScan.scan.depthLimitHits > 0) {
|
|
447
|
+
result.notes.push(
|
|
448
|
+
`verify-implementation enforced max scan depth (${MAX_SCAN_DEPTH}); skipped deeper paths: ${codeScan.scan.depthLimitHits}.`
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
if (codeScan.scan.skippedSymlinks > 0) {
|
|
452
|
+
result.notes.push(
|
|
453
|
+
`verify-implementation skipped symlink entries during scan: ${codeScan.scan.skippedSymlinks}.`
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
if (skippedLargeFiles > 0) {
|
|
457
|
+
result.notes.push(
|
|
458
|
+
`verify-implementation skipped files larger than ${MAX_SCANNED_BYTES_PER_FILE} bytes: ${skippedLargeFiles}.`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
result.summary = {
|
|
463
|
+
codeFiles: codeFiles.length,
|
|
464
|
+
specFiles: specRecords.length,
|
|
465
|
+
taskGroups: tasksArtifact.taskGroups.length,
|
|
466
|
+
scannedBytes
|
|
467
|
+
};
|
|
468
|
+
return finalize(result);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function verifyStructure(projectPathInput, options = {}) {
|
|
472
|
+
const setup = commonSetup("verify-structure", projectPathInput, options);
|
|
473
|
+
const { result, artifacts } = setup;
|
|
474
|
+
if (!artifacts) {
|
|
475
|
+
return result;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (!artifacts.bindings) {
|
|
479
|
+
result.failures.push("Missing `pencil-bindings.md` for verify-structure.");
|
|
480
|
+
return finalize(result);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const bindings = parseBindingsArtifact(artifacts.bindings);
|
|
484
|
+
const confidence = [];
|
|
485
|
+
|
|
486
|
+
if (bindings.mappings.length === 0) {
|
|
487
|
+
result.failures.push("No binding mappings available for structural comparison.");
|
|
488
|
+
return finalize(result);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
for (const mapping of bindings.mappings) {
|
|
492
|
+
const landing = resolveImplementationLanding(result.projectRoot, mapping.implementation);
|
|
493
|
+
if (!landing) {
|
|
494
|
+
result.failures.push(`Structural check missing implementation file for "${mapping.implementation}".`);
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const ext = path.extname(landing).toLowerCase();
|
|
499
|
+
const source = safeReadFile(landing);
|
|
500
|
+
const normalizedSource = normalizeText(source);
|
|
501
|
+
const pageTokens = normalizeText(mapping.designPage)
|
|
502
|
+
.split(" ")
|
|
503
|
+
.filter((token) => token.length >= 3);
|
|
504
|
+
|
|
505
|
+
if (ext === ".html" || ext === ".tsx" || ext === ".jsx" || ext === ".js") {
|
|
506
|
+
const hasMarkupIndicators = /<section|<main|<header|<footer|<div/.test(source);
|
|
507
|
+
if (hasMarkupIndicators) {
|
|
508
|
+
confidence.push({ mapping: mapping.implementation, mode: "markup", confidence: "high" });
|
|
509
|
+
} else {
|
|
510
|
+
confidence.push({ mapping: mapping.implementation, mode: "heuristic", confidence: "medium" });
|
|
511
|
+
result.warnings.push(
|
|
512
|
+
`verify-structure used heuristic mode for "${mapping.implementation}" because markup structure was limited.`
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
} else {
|
|
516
|
+
confidence.push({ mapping: mapping.implementation, mode: "heuristic", confidence: "low" });
|
|
517
|
+
result.warnings.push(
|
|
518
|
+
`verify-structure used heuristic mode for "${mapping.implementation}" due to unsupported file type ${ext || "(none)"}.`
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (pageTokens.length > 0) {
|
|
523
|
+
const covered = pageTokens.some((token) => normalizedSource.includes(token));
|
|
524
|
+
if (!covered) {
|
|
525
|
+
result.warnings.push(
|
|
526
|
+
`Structural drift suspected: design page "${mapping.designPage}" tokens not found in "${mapping.implementation}".`
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
result.structure = {
|
|
533
|
+
confidence
|
|
534
|
+
};
|
|
535
|
+
return finalize(result);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function verifyCoverage(projectPathInput, options = {}) {
|
|
539
|
+
const projectRoot = path.resolve(projectPathInput || process.cwd());
|
|
540
|
+
const strict = options.strict === true;
|
|
541
|
+
const changeId = options.changeId ? String(options.changeId).trim() : "";
|
|
542
|
+
const sharedSetup = createSharedSetup(projectRoot, { changeId, strict });
|
|
543
|
+
const sharedOptions = { changeId, strict, sharedSetup };
|
|
544
|
+
const bindingsResult = verifyBindings(projectRoot, sharedOptions);
|
|
545
|
+
const implementationResult = verifyImplementation(projectRoot, sharedOptions);
|
|
546
|
+
const structureResult = verifyStructure(projectRoot, sharedOptions);
|
|
547
|
+
|
|
548
|
+
const result = buildEnvelope("verify-coverage", projectRoot, strict);
|
|
549
|
+
result.changeId = bindingsResult.changeId || implementationResult.changeId || structureResult.changeId || null;
|
|
550
|
+
result.notes.push("verify-coverage aggregates requirement, design, binding, code, and test coverage signals.");
|
|
551
|
+
|
|
552
|
+
if (bindingsResult.status === STATUS.BLOCK) {
|
|
553
|
+
result.failures.push("verify-bindings reported BLOCK.");
|
|
554
|
+
} else if (bindingsResult.status === STATUS.WARN) {
|
|
555
|
+
result.warnings.push("verify-bindings reported WARN.");
|
|
556
|
+
}
|
|
557
|
+
if (implementationResult.status === STATUS.BLOCK) {
|
|
558
|
+
result.failures.push("verify-implementation reported BLOCK.");
|
|
559
|
+
} else if (implementationResult.status === STATUS.WARN) {
|
|
560
|
+
result.warnings.push("verify-implementation reported WARN.");
|
|
561
|
+
}
|
|
562
|
+
if (structureResult.status === STATUS.BLOCK) {
|
|
563
|
+
result.failures.push("verify-structure reported BLOCK.");
|
|
564
|
+
} else if (structureResult.status === STATUS.WARN) {
|
|
565
|
+
result.warnings.push("verify-structure reported WARN.");
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (sharedSetup.artifacts && sharedSetup.artifacts.verification) {
|
|
569
|
+
const verificationArtifact = parseVerificationArtifact(sharedSetup.artifacts.verification);
|
|
570
|
+
if (verificationArtifact.requirementCoverage.length === 0) {
|
|
571
|
+
result.warnings.push("`verification.md` is missing requirement-coverage evidence.");
|
|
572
|
+
}
|
|
573
|
+
if (verificationArtifact.designCoverage.length === 0) {
|
|
574
|
+
result.warnings.push("`verification.md` is missing design-coverage evidence.");
|
|
575
|
+
}
|
|
576
|
+
const hasTestEvidence = verificationArtifact.outcome.some((item) => /test|coverage|verify/i.test(item));
|
|
577
|
+
if (!hasTestEvidence) {
|
|
578
|
+
result.warnings.push("`verification.md` outcome lacks explicit test evidence.");
|
|
579
|
+
}
|
|
580
|
+
} else {
|
|
581
|
+
result.warnings.push("Missing `verification.md`; coverage evidence is incomplete.");
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
result.components = {
|
|
585
|
+
bindings: {
|
|
586
|
+
status: bindingsResult.status,
|
|
587
|
+
failures: bindingsResult.failures,
|
|
588
|
+
warnings: bindingsResult.warnings
|
|
589
|
+
},
|
|
590
|
+
implementation: {
|
|
591
|
+
status: implementationResult.status,
|
|
592
|
+
failures: implementationResult.failures,
|
|
593
|
+
warnings: implementationResult.warnings
|
|
594
|
+
},
|
|
595
|
+
structure: {
|
|
596
|
+
status: structureResult.status,
|
|
597
|
+
failures: structureResult.failures,
|
|
598
|
+
warnings: structureResult.warnings,
|
|
599
|
+
confidence: structureResult.structure ? structureResult.structure.confidence : []
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
return finalize(result);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function formatVerifyReport(result, title = "Da Vinci verify") {
|
|
607
|
+
const lines = [
|
|
608
|
+
title,
|
|
609
|
+
`Project: ${result.projectRoot}`,
|
|
610
|
+
`Change: ${result.changeId || "(not selected)"}`,
|
|
611
|
+
`Strict mode: ${result.strict ? "yes" : "no"}`,
|
|
612
|
+
`Status: ${result.status}`
|
|
613
|
+
];
|
|
614
|
+
if (result.summary) {
|
|
615
|
+
for (const [key, value] of Object.entries(result.summary)) {
|
|
616
|
+
lines.push(`${key}: ${value}`);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (result.failures.length > 0) {
|
|
620
|
+
lines.push("", "Failures:");
|
|
621
|
+
for (const failure of result.failures) {
|
|
622
|
+
lines.push(`- ${failure}`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (result.warnings.length > 0) {
|
|
626
|
+
lines.push("", "Warnings:");
|
|
627
|
+
for (const warning of result.warnings) {
|
|
628
|
+
lines.push(`- ${warning}`);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
if (result.notes.length > 0) {
|
|
632
|
+
lines.push("", "Notes:");
|
|
633
|
+
for (const note of result.notes) {
|
|
634
|
+
lines.push(`- ${note}`);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
if (result.structure && Array.isArray(result.structure.confidence) && result.structure.confidence.length > 0) {
|
|
638
|
+
lines.push("", "Structure confidence:");
|
|
639
|
+
for (const item of result.structure.confidence) {
|
|
640
|
+
lines.push(`- ${item.mapping}: ${item.mode} (${item.confidence})`);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return lines.join("\n");
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
module.exports = {
|
|
647
|
+
verifyBindings,
|
|
648
|
+
verifyImplementation,
|
|
649
|
+
verifyStructure,
|
|
650
|
+
verifyCoverage,
|
|
651
|
+
formatVerifyReport
|
|
652
|
+
};
|