@xenonbyte/da-vinci-workflow 0.2.4 → 0.2.5
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 +19 -0
- package/README.md +7 -7
- package/README.zh-CN.md +7 -7
- package/SKILL.md +45 -704
- package/docs/dv-command-reference.md +15 -3
- package/docs/prompt-entrypoints.md +1 -0
- package/docs/skill-contract-maintenance.md +14 -0
- package/docs/zh-CN/dv-command-reference.md +15 -3
- package/docs/zh-CN/prompt-entrypoints.md +1 -0
- package/lib/cli/helpers.js +43 -0
- package/lib/cli/lint-family.js +56 -0
- package/lib/cli/verify-family.js +79 -0
- package/lib/cli.js +45 -172
- package/lib/planning-parsers.js +8 -1
- package/lib/scaffold.js +454 -23
- package/lib/utils.js +19 -0
- package/lib/verify.js +1160 -88
- package/package.json +1 -1
- package/references/skill-workflow-detail.md +66 -0
package/lib/verify.js
CHANGED
|
@@ -2,7 +2,6 @@ const fs = require("fs");
|
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const { STATUS } = require("./workflow-contract");
|
|
4
4
|
const {
|
|
5
|
-
normalizeText,
|
|
6
5
|
unique,
|
|
7
6
|
resolveImplementationLanding,
|
|
8
7
|
resolveChangeDir,
|
|
@@ -14,8 +13,22 @@ const {
|
|
|
14
13
|
readArtifactTexts
|
|
15
14
|
} = require("./planning-parsers");
|
|
16
15
|
const { readExecutionSignals, summarizeSignalsBySurface } = require("./execution-signals");
|
|
16
|
+
const { normalizeRelativePath, pathWithinRoot } = require("./utils");
|
|
17
17
|
|
|
18
|
-
const CODE_FILE_EXTENSIONS = new Set([
|
|
18
|
+
const CODE_FILE_EXTENSIONS = new Set([
|
|
19
|
+
".js",
|
|
20
|
+
".jsx",
|
|
21
|
+
".ts",
|
|
22
|
+
".tsx",
|
|
23
|
+
".html",
|
|
24
|
+
".css",
|
|
25
|
+
".scss",
|
|
26
|
+
".vue",
|
|
27
|
+
".svelte"
|
|
28
|
+
]);
|
|
29
|
+
const SYNTAX_AWARE_SCRIPT_EXTENSIONS = new Set([".js", ".jsx", ".ts", ".tsx"]);
|
|
30
|
+
const IMPLEMENTATION_MARKUP_EXTENSIONS = new Set([".html", ".vue", ".svelte"]);
|
|
31
|
+
const STRUCTURE_MARKUP_EXTENSIONS = new Set([".html", ".tsx", ".jsx", ".js", ".vue", ".svelte"]);
|
|
19
32
|
const NON_IMPLEMENTATION_DIR_NAMES = new Set([
|
|
20
33
|
".git",
|
|
21
34
|
".da-vinci",
|
|
@@ -92,6 +105,58 @@ function isNonImplementationFileName(name) {
|
|
|
92
105
|
return NON_IMPLEMENTATION_FILE_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
93
106
|
}
|
|
94
107
|
|
|
108
|
+
function normalizeCoverageText(value) {
|
|
109
|
+
return String(value || "")
|
|
110
|
+
.toLowerCase()
|
|
111
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
112
|
+
.replace(/\s+/g, " ")
|
|
113
|
+
.trim();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function tokenizeCoverage(value, minLength = 1) {
|
|
117
|
+
return normalizeCoverageText(value)
|
|
118
|
+
.split(" ")
|
|
119
|
+
.map((token) => token.trim())
|
|
120
|
+
.filter((token) => token.length >= minLength);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function safeRealpathSync(candidatePath) {
|
|
124
|
+
try {
|
|
125
|
+
if (typeof fs.realpathSync.native === "function") {
|
|
126
|
+
return fs.realpathSync.native(candidatePath);
|
|
127
|
+
}
|
|
128
|
+
return fs.realpathSync(candidatePath);
|
|
129
|
+
} catch (_error) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function canonicalizePath(candidatePath) {
|
|
135
|
+
const resolved = path.resolve(candidatePath);
|
|
136
|
+
return safeRealpathSync(resolved) || resolved;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function listSummary(items, max = 5) {
|
|
140
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
141
|
+
return "";
|
|
142
|
+
}
|
|
143
|
+
const head = items.slice(0, max).join(", ");
|
|
144
|
+
if (items.length <= max) {
|
|
145
|
+
return head;
|
|
146
|
+
}
|
|
147
|
+
return `${head} ... (+${items.length - max} more)`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function hasExcludedDirectory(relativePath) {
|
|
151
|
+
const normalized = normalizeRelativePath(relativePath);
|
|
152
|
+
if (!normalized) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
const segments = normalized.split("/");
|
|
156
|
+
const directorySegments = segments.slice(0, -1);
|
|
157
|
+
return directorySegments.some((segment) => isNonImplementationDirName(segment));
|
|
158
|
+
}
|
|
159
|
+
|
|
95
160
|
function collectCodeFiles(projectRoot) {
|
|
96
161
|
const files = [];
|
|
97
162
|
const scan = {
|
|
@@ -219,15 +284,6 @@ function readCodeFileForScan(filePath) {
|
|
|
219
284
|
}
|
|
220
285
|
}
|
|
221
286
|
|
|
222
|
-
function allCovered(checks) {
|
|
223
|
-
for (const check of checks) {
|
|
224
|
-
if (!check.covered) {
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
return true;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
287
|
function safeMtimeMs(filePath) {
|
|
232
288
|
try {
|
|
233
289
|
const stat = fs.statSync(filePath);
|
|
@@ -414,6 +470,547 @@ function commonSetup(surface, projectPathInput, options) {
|
|
|
414
470
|
};
|
|
415
471
|
}
|
|
416
472
|
|
|
473
|
+
function collectChangedFileEntries(projectRoot, options = {}) {
|
|
474
|
+
const requested = options.changedFilesProvided === true || Array.isArray(options.changedFiles);
|
|
475
|
+
const rawEntries = Array.isArray(options.changedFiles)
|
|
476
|
+
? options.changedFiles
|
|
477
|
+
: options.changedFiles === undefined
|
|
478
|
+
? []
|
|
479
|
+
: [options.changedFiles];
|
|
480
|
+
const resolvedRoot = path.resolve(projectRoot);
|
|
481
|
+
const canonicalRoot = safeRealpathSync(resolvedRoot) || resolvedRoot;
|
|
482
|
+
|
|
483
|
+
const response = {
|
|
484
|
+
requested,
|
|
485
|
+
rawEntries: rawEntries.map((value) => String(value || "").trim()).filter(Boolean),
|
|
486
|
+
entries: [],
|
|
487
|
+
invalidEntries: [],
|
|
488
|
+
duplicateEntries: [],
|
|
489
|
+
missingEntries: [],
|
|
490
|
+
directoryEntries: [],
|
|
491
|
+
unreadableEntries: []
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
if (!requested) {
|
|
495
|
+
return response;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const seenInputs = new Set();
|
|
499
|
+
const seenCanonical = new Set();
|
|
500
|
+
for (const rawEntry of response.rawEntries) {
|
|
501
|
+
const absolutePath = path.isAbsolute(rawEntry)
|
|
502
|
+
? path.resolve(rawEntry)
|
|
503
|
+
: path.resolve(projectRoot, rawEntry);
|
|
504
|
+
|
|
505
|
+
if (!pathWithinRoot(resolvedRoot, absolutePath)) {
|
|
506
|
+
response.invalidEntries.push({
|
|
507
|
+
input: rawEntry,
|
|
508
|
+
reason: "path escapes project root"
|
|
509
|
+
});
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const relativePath = normalizeRelativePath(path.relative(projectRoot, absolutePath));
|
|
514
|
+
const dedupeInputKey = relativePath.toLowerCase();
|
|
515
|
+
if (seenInputs.has(dedupeInputKey)) {
|
|
516
|
+
response.duplicateEntries.push(relativePath || rawEntry);
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
seenInputs.add(dedupeInputKey);
|
|
520
|
+
|
|
521
|
+
if (!fs.existsSync(absolutePath)) {
|
|
522
|
+
response.missingEntries.push(relativePath || rawEntry);
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
let stat;
|
|
527
|
+
try {
|
|
528
|
+
stat = fs.lstatSync(absolutePath);
|
|
529
|
+
} catch (_error) {
|
|
530
|
+
response.unreadableEntries.push(relativePath || rawEntry);
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (stat.isDirectory()) {
|
|
535
|
+
response.directoryEntries.push(relativePath || rawEntry);
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const canonicalAbsolutePath = safeRealpathSync(absolutePath);
|
|
540
|
+
if (!canonicalAbsolutePath) {
|
|
541
|
+
response.unreadableEntries.push(relativePath || rawEntry);
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
if (!pathWithinRoot(canonicalRoot, canonicalAbsolutePath)) {
|
|
545
|
+
response.invalidEntries.push({
|
|
546
|
+
input: rawEntry,
|
|
547
|
+
reason: "path escapes project root via symlink"
|
|
548
|
+
});
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const canonicalRelativePath = normalizeRelativePath(path.relative(canonicalRoot, canonicalAbsolutePath));
|
|
553
|
+
const effectiveRelativePath = canonicalRelativePath || relativePath;
|
|
554
|
+
const dedupeCanonicalKey = effectiveRelativePath.toLowerCase();
|
|
555
|
+
if (seenCanonical.has(dedupeCanonicalKey)) {
|
|
556
|
+
response.duplicateEntries.push(effectiveRelativePath || rawEntry);
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
seenCanonical.add(dedupeCanonicalKey);
|
|
560
|
+
|
|
561
|
+
response.entries.push({
|
|
562
|
+
input: rawEntry,
|
|
563
|
+
absolutePath,
|
|
564
|
+
canonicalPath: canonicalAbsolutePath,
|
|
565
|
+
relativePath: effectiveRelativePath,
|
|
566
|
+
extension: path.extname(effectiveRelativePath || relativePath).toLowerCase(),
|
|
567
|
+
isSymlink: stat.isSymbolicLink()
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return response;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function buildImplementationChecks(specRecords, tasksArtifact) {
|
|
575
|
+
const stateChecks = [];
|
|
576
|
+
let stateCounter = 0;
|
|
577
|
+
for (const record of specRecords) {
|
|
578
|
+
const states = record.parsed.sections.states.items || [];
|
|
579
|
+
for (const stateItem of states) {
|
|
580
|
+
const stateLabel = String(stateItem || "").split(":")[0];
|
|
581
|
+
const tokens = unique(tokenizeCoverage(stateLabel, 3));
|
|
582
|
+
if (tokens.length === 0) {
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
stateCounter += 1;
|
|
586
|
+
stateChecks.push(createImplementationCheck({
|
|
587
|
+
id: `state-${stateCounter}`,
|
|
588
|
+
type: "state",
|
|
589
|
+
label: String(stateItem || "").trim(),
|
|
590
|
+
recordPath: record.path,
|
|
591
|
+
tokens
|
|
592
|
+
}));
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const taskGroupChecks = [];
|
|
597
|
+
for (const group of tasksArtifact.taskGroups) {
|
|
598
|
+
const tokens = unique(tokenizeCoverage(group.title, 4));
|
|
599
|
+
if (tokens.length === 0) {
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
taskGroupChecks.push(createImplementationCheck({
|
|
603
|
+
id: `task-group-${group.id}`,
|
|
604
|
+
type: "task-group",
|
|
605
|
+
label: `${group.id}. ${group.title}`,
|
|
606
|
+
groupId: group.id,
|
|
607
|
+
tokens
|
|
608
|
+
}));
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
return {
|
|
612
|
+
stateChecks,
|
|
613
|
+
taskGroupChecks,
|
|
614
|
+
allChecks: [...stateChecks, ...taskGroupChecks]
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function createImplementationCheck(data) {
|
|
619
|
+
const tokens = Array.isArray(data.tokens) ? data.tokens.filter(Boolean) : [];
|
|
620
|
+
return {
|
|
621
|
+
id: data.id,
|
|
622
|
+
type: data.type,
|
|
623
|
+
label: data.label,
|
|
624
|
+
recordPath: data.recordPath || null,
|
|
625
|
+
groupId: data.groupId || null,
|
|
626
|
+
tokens,
|
|
627
|
+
requiredMatches: computeRequiredMatches(data.type, tokens),
|
|
628
|
+
covered: false,
|
|
629
|
+
evidence: null,
|
|
630
|
+
boundaries: []
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function computeRequiredMatches(type, tokens) {
|
|
635
|
+
if (!Array.isArray(tokens) || tokens.length === 0) {
|
|
636
|
+
return 0;
|
|
637
|
+
}
|
|
638
|
+
if (tokens.length === 1) {
|
|
639
|
+
return 1;
|
|
640
|
+
}
|
|
641
|
+
if (type === "task-group") {
|
|
642
|
+
return Math.min(2, tokens.length);
|
|
643
|
+
}
|
|
644
|
+
return Math.min(2, tokens.length);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function evaluateCheckAgainstTokenSet(check, tokenSet) {
|
|
648
|
+
const matchedTokens = [];
|
|
649
|
+
for (const token of check.tokens) {
|
|
650
|
+
if (tokenSet.has(token)) {
|
|
651
|
+
matchedTokens.push(token);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return {
|
|
655
|
+
matchedTokens,
|
|
656
|
+
covered: matchedTokens.length >= check.requiredMatches
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function addBoundary(check, boundary) {
|
|
661
|
+
if (!check || !boundary || !boundary.type) {
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
const key = `${boundary.type}|${boundary.file || ""}|${boundary.reason || ""}`;
|
|
665
|
+
if (!Array.isArray(check.boundaries)) {
|
|
666
|
+
check.boundaries = [];
|
|
667
|
+
}
|
|
668
|
+
if (check.boundaries.some((item) => `${item.type}|${item.file || ""}|${item.reason || ""}` === key)) {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
check.boundaries.push({
|
|
672
|
+
type: boundary.type,
|
|
673
|
+
file: boundary.file || null,
|
|
674
|
+
reason: boundary.reason || ""
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function evidenceScore(evidence) {
|
|
679
|
+
if (!evidence) {
|
|
680
|
+
return 0;
|
|
681
|
+
}
|
|
682
|
+
const confidenceScore =
|
|
683
|
+
evidence.confidence === "high"
|
|
684
|
+
? 30
|
|
685
|
+
: evidence.confidence === "medium"
|
|
686
|
+
? 20
|
|
687
|
+
: evidence.confidence === "low"
|
|
688
|
+
? 10
|
|
689
|
+
: 0;
|
|
690
|
+
const modeScore =
|
|
691
|
+
evidence.mode === "syntax-aware"
|
|
692
|
+
? 4
|
|
693
|
+
: evidence.mode === "markup"
|
|
694
|
+
? 3
|
|
695
|
+
: evidence.mode === "heuristic"
|
|
696
|
+
? 2
|
|
697
|
+
: 1;
|
|
698
|
+
return confidenceScore + modeScore;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function applyEvidence(check, evidence) {
|
|
702
|
+
if (!check || !evidence) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
if (!check.evidence || evidenceScore(evidence) > evidenceScore(check.evidence)) {
|
|
706
|
+
check.evidence = evidence;
|
|
707
|
+
check.covered = true;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const REGEX_PREFIX_CHARS = new Set([
|
|
712
|
+
"(",
|
|
713
|
+
"[",
|
|
714
|
+
"{",
|
|
715
|
+
",",
|
|
716
|
+
";",
|
|
717
|
+
":",
|
|
718
|
+
"=",
|
|
719
|
+
"!",
|
|
720
|
+
"&",
|
|
721
|
+
"|",
|
|
722
|
+
"?",
|
|
723
|
+
"+",
|
|
724
|
+
"-",
|
|
725
|
+
"*",
|
|
726
|
+
"%",
|
|
727
|
+
"^",
|
|
728
|
+
"~",
|
|
729
|
+
"<",
|
|
730
|
+
">",
|
|
731
|
+
"/"
|
|
732
|
+
]);
|
|
733
|
+
const REGEX_PREFIX_KEYWORD_PATTERN =
|
|
734
|
+
/(?:^|[^\w$])(return|throw|case|delete|void|typeof|instanceof|in|of|new|await|yield)\s*$/;
|
|
735
|
+
|
|
736
|
+
function previousNonWhitespaceChar(text, startIndex) {
|
|
737
|
+
for (let index = startIndex; index >= 0; index -= 1) {
|
|
738
|
+
const char = text[index];
|
|
739
|
+
if (char === " " || char === "\t" || char === "\n" || char === "\r" || char === "\f") {
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
return char;
|
|
743
|
+
}
|
|
744
|
+
return "";
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function canStartRegexLiteral(text, index) {
|
|
748
|
+
const previousChar = previousNonWhitespaceChar(text, index - 1);
|
|
749
|
+
if (!previousChar) {
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
if (REGEX_PREFIX_CHARS.has(previousChar)) {
|
|
753
|
+
return true;
|
|
754
|
+
}
|
|
755
|
+
return REGEX_PREFIX_KEYWORD_PATTERN.test(text.slice(0, index));
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function collectScriptEvidenceText(source) {
|
|
759
|
+
const text = String(source || "");
|
|
760
|
+
const out = [];
|
|
761
|
+
let state = "normal";
|
|
762
|
+
let regexInClass = false;
|
|
763
|
+
|
|
764
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
765
|
+
const char = text[index];
|
|
766
|
+
const next = text[index + 1];
|
|
767
|
+
|
|
768
|
+
if (state === "normal") {
|
|
769
|
+
if (char === "/" && next === "/") {
|
|
770
|
+
out.push(" ", " ");
|
|
771
|
+
state = "line-comment";
|
|
772
|
+
index += 1;
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
if (char === "/" && next === "*") {
|
|
776
|
+
out.push(" ", " ");
|
|
777
|
+
state = "block-comment";
|
|
778
|
+
index += 1;
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
if (char === "/" && canStartRegexLiteral(text, index)) {
|
|
782
|
+
out.push(" ");
|
|
783
|
+
state = "regex";
|
|
784
|
+
regexInClass = false;
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
if (char === "'") {
|
|
788
|
+
out.push(" ");
|
|
789
|
+
state = "single-quote";
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
if (char === '"') {
|
|
793
|
+
out.push(" ");
|
|
794
|
+
state = "double-quote";
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
if (char === "`") {
|
|
798
|
+
out.push(" ");
|
|
799
|
+
state = "template";
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
out.push(char);
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if (state === "line-comment") {
|
|
807
|
+
if (char === "\n") {
|
|
808
|
+
out.push("\n");
|
|
809
|
+
state = "normal";
|
|
810
|
+
} else {
|
|
811
|
+
out.push(" ");
|
|
812
|
+
}
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (state === "block-comment") {
|
|
817
|
+
if (char === "*" && next === "/") {
|
|
818
|
+
out.push(" ", " ");
|
|
819
|
+
state = "normal";
|
|
820
|
+
index += 1;
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
out.push(char === "\n" ? "\n" : " ");
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (state === "regex") {
|
|
828
|
+
if (char === "\n") {
|
|
829
|
+
out.push("\n");
|
|
830
|
+
state = "normal";
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
if (char === "\\") {
|
|
834
|
+
out.push(" ");
|
|
835
|
+
if (index + 1 < text.length) {
|
|
836
|
+
out.push(" ");
|
|
837
|
+
index += 1;
|
|
838
|
+
}
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
if (char === "[" && !regexInClass) {
|
|
842
|
+
out.push(" ");
|
|
843
|
+
regexInClass = true;
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
if (char === "]" && regexInClass) {
|
|
847
|
+
out.push(" ");
|
|
848
|
+
regexInClass = false;
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
if (char === "/" && !regexInClass) {
|
|
852
|
+
out.push(" ");
|
|
853
|
+
state = "regex-flags";
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
out.push(" ");
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
if (state === "regex-flags") {
|
|
861
|
+
if (/[a-z]/i.test(char)) {
|
|
862
|
+
out.push(" ");
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
state = "normal";
|
|
866
|
+
index -= 1;
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if (state === "single-quote") {
|
|
871
|
+
if (char === "\\") {
|
|
872
|
+
out.push(" ");
|
|
873
|
+
if (index + 1 < text.length) {
|
|
874
|
+
out.push(" ");
|
|
875
|
+
index += 1;
|
|
876
|
+
}
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
if (char === "'") {
|
|
880
|
+
out.push(" ");
|
|
881
|
+
state = "normal";
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
if (char === "\n") {
|
|
885
|
+
return {
|
|
886
|
+
ok: false,
|
|
887
|
+
text: "",
|
|
888
|
+
reason: "unterminated-string-literal"
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
out.push(" ");
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (state === "double-quote") {
|
|
896
|
+
if (char === "\\") {
|
|
897
|
+
out.push(" ");
|
|
898
|
+
if (index + 1 < text.length) {
|
|
899
|
+
out.push(" ");
|
|
900
|
+
index += 1;
|
|
901
|
+
}
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
if (char === '"') {
|
|
905
|
+
out.push(" ");
|
|
906
|
+
state = "normal";
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
if (char === "\n") {
|
|
910
|
+
return {
|
|
911
|
+
ok: false,
|
|
912
|
+
text: "",
|
|
913
|
+
reason: "unterminated-string-literal"
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
out.push(" ");
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (state === "template") {
|
|
921
|
+
if (char === "\\") {
|
|
922
|
+
out.push(" ");
|
|
923
|
+
if (index + 1 < text.length) {
|
|
924
|
+
out.push(" ");
|
|
925
|
+
index += 1;
|
|
926
|
+
}
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
if (char === "`") {
|
|
930
|
+
out.push(" ");
|
|
931
|
+
state = "normal";
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
out.push(char === "\n" ? "\n" : " ");
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
if (state === "single-quote" || state === "double-quote" || state === "template" || state === "block-comment") {
|
|
940
|
+
return {
|
|
941
|
+
ok: false,
|
|
942
|
+
text: "",
|
|
943
|
+
reason: "unterminated-comment-or-string"
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
return {
|
|
948
|
+
ok: true,
|
|
949
|
+
text: out.join(""),
|
|
950
|
+
reason: ""
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function stripMarkupComments(source) {
|
|
955
|
+
return String(source || "").replace(/<!--[\s\S]*?-->/g, (comment) => comment.replace(/[^\n]/g, " "));
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function collectStructureEvidenceText(source, extension) {
|
|
959
|
+
const markupFiltered = stripMarkupComments(source);
|
|
960
|
+
if (SYNTAX_AWARE_SCRIPT_EXTENSIONS.has(extension)) {
|
|
961
|
+
const scriptEvidence = collectScriptEvidenceText(markupFiltered);
|
|
962
|
+
if (scriptEvidence.ok) {
|
|
963
|
+
return {
|
|
964
|
+
text: scriptEvidence.text,
|
|
965
|
+
syntaxAvailable: true,
|
|
966
|
+
reason: ""
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
return {
|
|
970
|
+
text: "",
|
|
971
|
+
syntaxAvailable: false,
|
|
972
|
+
reason: scriptEvidence.reason || "syntax-unavailable"
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
return {
|
|
977
|
+
text: markupFiltered,
|
|
978
|
+
syntaxAvailable: true,
|
|
979
|
+
reason: ""
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function serializeCheck(check, noRelevantFiles = false) {
|
|
984
|
+
const evidence = check.evidence
|
|
985
|
+
? {
|
|
986
|
+
mode: check.evidence.mode,
|
|
987
|
+
confidence: check.evidence.confidence,
|
|
988
|
+
file: check.evidence.file || null,
|
|
989
|
+
reason: check.evidence.reason || "",
|
|
990
|
+
matchedTokens: check.evidence.matchedTokens || [],
|
|
991
|
+
degraded: check.evidence.degraded === true
|
|
992
|
+
}
|
|
993
|
+
: {
|
|
994
|
+
mode: noRelevantFiles ? "not-scanned" : "none",
|
|
995
|
+
confidence: "none",
|
|
996
|
+
file: null,
|
|
997
|
+
reason: noRelevantFiles ? "no-relevant-files-scanned" : "no-qualifying-evidence",
|
|
998
|
+
matchedTokens: [],
|
|
999
|
+
degraded: false
|
|
1000
|
+
};
|
|
1001
|
+
|
|
1002
|
+
return {
|
|
1003
|
+
id: check.id,
|
|
1004
|
+
type: check.type,
|
|
1005
|
+
label: check.label,
|
|
1006
|
+
covered: check.covered,
|
|
1007
|
+
tokens: check.tokens,
|
|
1008
|
+
requiredMatches: check.requiredMatches,
|
|
1009
|
+
evidence,
|
|
1010
|
+
boundaries: Array.isArray(check.boundaries) ? check.boundaries : []
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
|
|
417
1014
|
function verifyBindings(projectPathInput, options = {}) {
|
|
418
1015
|
const setup = commonSetup("verify-bindings", projectPathInput, options);
|
|
419
1016
|
const { result, artifacts } = setup;
|
|
@@ -457,13 +1054,6 @@ function verifyImplementation(projectPathInput, options = {}) {
|
|
|
457
1054
|
return result;
|
|
458
1055
|
}
|
|
459
1056
|
|
|
460
|
-
const codeScan = collectCodeFiles(result.projectRoot);
|
|
461
|
-
const codeFiles = codeScan.files;
|
|
462
|
-
if (codeFiles.length === 0) {
|
|
463
|
-
result.failures.push("No implementation files were found for verify-implementation.");
|
|
464
|
-
return finalize(result);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
1057
|
const specRecords = parseRuntimeSpecs(resolved.changeDir, result.projectRoot);
|
|
468
1058
|
const tasksArtifact = parseTasksArtifact(artifacts.tasks || "");
|
|
469
1059
|
|
|
@@ -472,46 +1062,158 @@ function verifyImplementation(projectPathInput, options = {}) {
|
|
|
472
1062
|
return finalize(result);
|
|
473
1063
|
}
|
|
474
1064
|
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
1065
|
+
const checks = buildImplementationChecks(specRecords, tasksArtifact);
|
|
1066
|
+
const allChecks = checks.allChecks;
|
|
1067
|
+
|
|
1068
|
+
const changedFiles = collectChangedFileEntries(result.projectRoot, options);
|
|
1069
|
+
if (changedFiles.requested && changedFiles.invalidEntries.length > 0) {
|
|
1070
|
+
for (const invalidEntry of changedFiles.invalidEntries) {
|
|
1071
|
+
result.failures.push(
|
|
1072
|
+
`Invalid --changed-files entry "${invalidEntry.input}": ${invalidEntry.reason}.`
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
return finalize(result);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
let codeFiles = [];
|
|
1079
|
+
let scan = {
|
|
1080
|
+
truncatedByFileLimit: false,
|
|
1081
|
+
truncatedByDirectoryLimit: false,
|
|
1082
|
+
depthLimitHits: 0,
|
|
1083
|
+
skippedSymlinks: 0,
|
|
1084
|
+
readErrors: 0,
|
|
1085
|
+
scannedDirectories: 0
|
|
1086
|
+
};
|
|
1087
|
+
const filteredChanged = {
|
|
1088
|
+
duplicates: changedFiles.duplicateEntries.length,
|
|
1089
|
+
missing: changedFiles.missingEntries.length,
|
|
1090
|
+
directories: changedFiles.directoryEntries.length,
|
|
1091
|
+
unreadable: changedFiles.unreadableEntries.length,
|
|
1092
|
+
unsupported: 0,
|
|
1093
|
+
excluded: 0,
|
|
1094
|
+
symlinks: 0
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
if (changedFiles.requested) {
|
|
1098
|
+
for (const entry of changedFiles.entries) {
|
|
1099
|
+
if (entry.isSymlink) {
|
|
1100
|
+
filteredChanged.symlinks += 1;
|
|
481
1101
|
continue;
|
|
482
1102
|
}
|
|
483
|
-
|
|
484
|
-
|
|
1103
|
+
if (!CODE_FILE_EXTENSIONS.has(entry.extension)) {
|
|
1104
|
+
filteredChanged.unsupported += 1;
|
|
485
1105
|
continue;
|
|
486
1106
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
});
|
|
1107
|
+
if (isNonImplementationFileName(path.basename(entry.relativePath)) || hasExcludedDirectory(entry.relativePath)) {
|
|
1108
|
+
filteredChanged.excluded += 1;
|
|
1109
|
+
continue;
|
|
1110
|
+
}
|
|
1111
|
+
codeFiles.push(entry.absolutePath);
|
|
493
1112
|
}
|
|
1113
|
+
codeFiles = unique(codeFiles).sort();
|
|
1114
|
+
} else {
|
|
1115
|
+
const codeScan = collectCodeFiles(result.projectRoot);
|
|
1116
|
+
codeFiles = codeScan.files;
|
|
1117
|
+
scan = codeScan.scan;
|
|
494
1118
|
}
|
|
495
1119
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
1120
|
+
let scannedBytes = 0;
|
|
1121
|
+
let skippedLargeFiles = 0;
|
|
1122
|
+
let readErrors = 0;
|
|
1123
|
+
let scannedFiles = 0;
|
|
1124
|
+
const degradedFallbackFiles = new Set();
|
|
1125
|
+
const markupHeuristicFiles = new Set();
|
|
1126
|
+
const unsupportedHeuristicFiles = new Set();
|
|
1127
|
+
|
|
1128
|
+
result.scan = {
|
|
1129
|
+
scanMode: changedFiles.requested ? "incremental" : "full",
|
|
1130
|
+
requestedChangedFiles: changedFiles.rawEntries.length,
|
|
1131
|
+
selectedFileCount: changedFiles.requested ? changedFiles.entries.length : codeFiles.length,
|
|
1132
|
+
relevantFileCount: codeFiles.length,
|
|
1133
|
+
scannedFileCount: 0,
|
|
1134
|
+
filtered: {
|
|
1135
|
+
...filteredChanged,
|
|
1136
|
+
total:
|
|
1137
|
+
filteredChanged.duplicates +
|
|
1138
|
+
filteredChanged.missing +
|
|
1139
|
+
filteredChanged.directories +
|
|
1140
|
+
filteredChanged.unreadable +
|
|
1141
|
+
filteredChanged.unsupported +
|
|
1142
|
+
filteredChanged.excluded +
|
|
1143
|
+
filteredChanged.symlinks
|
|
1144
|
+
},
|
|
1145
|
+
noRelevantFiles: false
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
if (changedFiles.requested) {
|
|
1149
|
+
if (changedFiles.missingEntries.length > 0) {
|
|
1150
|
+
result.notes.push(
|
|
1151
|
+
`Incremental input ignored missing files: ${listSummary(changedFiles.missingEntries)}.`
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
if (changedFiles.directoryEntries.length > 0) {
|
|
1155
|
+
result.notes.push(
|
|
1156
|
+
`Incremental input ignored directory paths: ${listSummary(changedFiles.directoryEntries)}.`
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
if (changedFiles.duplicateEntries.length > 0) {
|
|
1160
|
+
result.notes.push(
|
|
1161
|
+
`Incremental input deduplicated repeated paths: ${listSummary(changedFiles.duplicateEntries)}.`
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
if (filteredChanged.unsupported > 0) {
|
|
1165
|
+
result.notes.push(
|
|
1166
|
+
`Incremental input ignored ${filteredChanged.unsupported} unsupported implementation file(s).`
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
if (filteredChanged.excluded > 0) {
|
|
1170
|
+
result.notes.push(
|
|
1171
|
+
`Incremental input ignored ${filteredChanged.excluded} excluded test/fixture/spec file(s).`
|
|
1172
|
+
);
|
|
503
1173
|
}
|
|
1174
|
+
if (filteredChanged.symlinks > 0) {
|
|
1175
|
+
result.notes.push(
|
|
1176
|
+
`Incremental input ignored ${filteredChanged.symlinks} symlink path(s).`
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
if (changedFiles.unreadableEntries.length > 0) {
|
|
1180
|
+
result.notes.push(
|
|
1181
|
+
`Incremental input ignored unreadable files: ${listSummary(changedFiles.unreadableEntries)}.`
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
504
1185
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
1186
|
+
if (codeFiles.length === 0) {
|
|
1187
|
+
if (changedFiles.requested) {
|
|
1188
|
+
result.scan.noRelevantFiles = true;
|
|
1189
|
+
result.warnings.push(
|
|
1190
|
+
"Incremental verify-implementation scanned zero relevant implementation files; result is partial."
|
|
1191
|
+
);
|
|
1192
|
+
result.summary = {
|
|
1193
|
+
codeFiles: 0,
|
|
1194
|
+
specFiles: specRecords.length,
|
|
1195
|
+
taskGroups: tasksArtifact.taskGroups.length,
|
|
1196
|
+
scannedBytes: 0,
|
|
1197
|
+
scanMode: "incremental"
|
|
1198
|
+
};
|
|
1199
|
+
result.implementation = {
|
|
1200
|
+
stateChecks: checks.stateChecks.length,
|
|
1201
|
+
taskGroupChecks: checks.taskGroupChecks.length,
|
|
1202
|
+
degradedChecks: 0,
|
|
1203
|
+
checks: allChecks.map((check) => serializeCheck(check, true)),
|
|
1204
|
+
evidenceModeCounts: {
|
|
1205
|
+
"syntax-aware": 0,
|
|
1206
|
+
markup: 0,
|
|
1207
|
+
heuristic: 0,
|
|
1208
|
+
none: allChecks.length
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
return finalize(result);
|
|
1212
|
+
}
|
|
1213
|
+
result.failures.push("No implementation files were found for verify-implementation.");
|
|
1214
|
+
return finalize(result);
|
|
510
1215
|
}
|
|
511
1216
|
|
|
512
|
-
let scannedBytes = 0;
|
|
513
|
-
let skippedLargeFiles = 0;
|
|
514
|
-
let readErrors = 0;
|
|
515
1217
|
for (const codeFile of codeFiles) {
|
|
516
1218
|
const read = readCodeFileForScan(codeFile);
|
|
517
1219
|
scannedBytes += read.bytesRead;
|
|
@@ -524,67 +1226,201 @@ function verifyImplementation(projectPathInput, options = {}) {
|
|
|
524
1226
|
continue;
|
|
525
1227
|
}
|
|
526
1228
|
|
|
527
|
-
|
|
528
|
-
|
|
1229
|
+
scannedFiles += 1;
|
|
1230
|
+
const relativeFile = normalizeRelativePath(path.relative(result.projectRoot, codeFile));
|
|
1231
|
+
const extension = path.extname(codeFile).toLowerCase();
|
|
1232
|
+
const source = String(read.text || "");
|
|
1233
|
+
if (!source) {
|
|
529
1234
|
continue;
|
|
530
1235
|
}
|
|
531
1236
|
|
|
532
|
-
|
|
533
|
-
|
|
1237
|
+
const rawTokenSet = new Set(tokenizeCoverage(source, 1));
|
|
1238
|
+
|
|
1239
|
+
if (SYNTAX_AWARE_SCRIPT_EXTENSIONS.has(extension)) {
|
|
1240
|
+
const scriptEvidence = collectScriptEvidenceText(source);
|
|
1241
|
+
if (scriptEvidence.ok) {
|
|
1242
|
+
const syntaxTokenSet = new Set(tokenizeCoverage(scriptEvidence.text, 1));
|
|
1243
|
+
for (const check of allChecks) {
|
|
1244
|
+
if (!check.tokens.length) {
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
const syntaxMatch = evaluateCheckAgainstTokenSet(check, syntaxTokenSet);
|
|
1248
|
+
if (syntaxMatch.covered) {
|
|
1249
|
+
applyEvidence(check, {
|
|
1250
|
+
mode: "syntax-aware",
|
|
1251
|
+
confidence: "high",
|
|
1252
|
+
file: relativeFile,
|
|
1253
|
+
reason: "syntax-structure-match",
|
|
1254
|
+
matchedTokens: syntaxMatch.matchedTokens,
|
|
1255
|
+
degraded: false
|
|
1256
|
+
});
|
|
1257
|
+
continue;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
const rawMatch = evaluateCheckAgainstTokenSet(check, rawTokenSet);
|
|
1261
|
+
if (rawMatch.covered) {
|
|
1262
|
+
addBoundary(check, {
|
|
1263
|
+
type: "comment-or-string-only",
|
|
1264
|
+
file: relativeFile,
|
|
1265
|
+
reason: "raw token appeared only outside executable syntax"
|
|
1266
|
+
});
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
if (rawMatch.matchedTokens.length > 0 || syntaxMatch.matchedTokens.length > 0) {
|
|
1271
|
+
addBoundary(check, {
|
|
1272
|
+
type: "accidental-token-overlap",
|
|
1273
|
+
file: relativeFile,
|
|
1274
|
+
reason: "token overlap below required threshold"
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
534
1278
|
continue;
|
|
535
1279
|
}
|
|
536
|
-
|
|
1280
|
+
|
|
1281
|
+
degradedFallbackFiles.add(relativeFile);
|
|
1282
|
+
for (const check of allChecks) {
|
|
1283
|
+
if (!check.tokens.length) {
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
const rawMatch = evaluateCheckAgainstTokenSet(check, rawTokenSet);
|
|
1287
|
+
if (rawMatch.covered) {
|
|
1288
|
+
applyEvidence(check, {
|
|
1289
|
+
mode: "heuristic",
|
|
1290
|
+
confidence: "low",
|
|
1291
|
+
file: relativeFile,
|
|
1292
|
+
reason: `syntax-unavailable:${scriptEvidence.reason}`,
|
|
1293
|
+
matchedTokens: rawMatch.matchedTokens,
|
|
1294
|
+
degraded: true
|
|
1295
|
+
});
|
|
1296
|
+
} else if (rawMatch.matchedTokens.length > 0) {
|
|
1297
|
+
addBoundary(check, {
|
|
1298
|
+
type: "accidental-token-overlap",
|
|
1299
|
+
file: relativeFile,
|
|
1300
|
+
reason: "token overlap below required threshold"
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
continue;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
const mode = IMPLEMENTATION_MARKUP_EXTENSIONS.has(extension) ? "markup" : "heuristic";
|
|
1308
|
+
const confidence = mode === "markup" ? "medium" : "low";
|
|
1309
|
+
if (mode === "markup") {
|
|
1310
|
+
markupHeuristicFiles.add(relativeFile);
|
|
1311
|
+
} else {
|
|
1312
|
+
unsupportedHeuristicFiles.add(relativeFile);
|
|
537
1313
|
}
|
|
538
|
-
for (const check of
|
|
539
|
-
if (check.
|
|
1314
|
+
for (const check of allChecks) {
|
|
1315
|
+
if (!check.tokens.length) {
|
|
540
1316
|
continue;
|
|
541
1317
|
}
|
|
542
|
-
|
|
1318
|
+
const rawMatch = evaluateCheckAgainstTokenSet(check, rawTokenSet);
|
|
1319
|
+
if (rawMatch.covered) {
|
|
1320
|
+
applyEvidence(check, {
|
|
1321
|
+
mode,
|
|
1322
|
+
confidence,
|
|
1323
|
+
file: relativeFile,
|
|
1324
|
+
reason: mode === "markup" ? "markup-heuristic-match" : `unsupported-language:${extension || "(none)"}`,
|
|
1325
|
+
matchedTokens: rawMatch.matchedTokens,
|
|
1326
|
+
degraded: mode !== "markup"
|
|
1327
|
+
});
|
|
1328
|
+
} else if (rawMatch.matchedTokens.length > 0) {
|
|
1329
|
+
addBoundary(check, {
|
|
1330
|
+
type: "accidental-token-overlap",
|
|
1331
|
+
file: relativeFile,
|
|
1332
|
+
reason: "token overlap below required threshold"
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
543
1335
|
}
|
|
1336
|
+
}
|
|
544
1337
|
|
|
545
|
-
|
|
546
|
-
|
|
1338
|
+
result.scan.scannedFileCount = scannedFiles;
|
|
1339
|
+
|
|
1340
|
+
const degradedChecks = [];
|
|
1341
|
+
const evidenceModeCounts = {
|
|
1342
|
+
"syntax-aware": 0,
|
|
1343
|
+
markup: 0,
|
|
1344
|
+
heuristic: 0,
|
|
1345
|
+
none: 0
|
|
1346
|
+
};
|
|
1347
|
+
|
|
1348
|
+
for (const check of allChecks) {
|
|
1349
|
+
if (!check.evidence) {
|
|
1350
|
+
evidenceModeCounts.none += 1;
|
|
1351
|
+
continue;
|
|
1352
|
+
}
|
|
1353
|
+
const mode = check.evidence.mode;
|
|
1354
|
+
if (Object.prototype.hasOwnProperty.call(evidenceModeCounts, mode)) {
|
|
1355
|
+
evidenceModeCounts[mode] += 1;
|
|
1356
|
+
}
|
|
1357
|
+
if (check.evidence.degraded) {
|
|
1358
|
+
degradedChecks.push(check);
|
|
547
1359
|
}
|
|
548
1360
|
}
|
|
549
1361
|
|
|
550
|
-
for (const check of stateChecks) {
|
|
1362
|
+
for (const check of checks.stateChecks) {
|
|
551
1363
|
if (!check.covered) {
|
|
552
1364
|
result.warnings.push(
|
|
553
|
-
`State coverage may be missing in implementation: "${check.
|
|
1365
|
+
`State coverage may be missing in implementation: "${check.label}" (${check.recordPath}).`
|
|
554
1366
|
);
|
|
555
1367
|
}
|
|
556
1368
|
}
|
|
557
|
-
for (const check of taskGroupChecks) {
|
|
1369
|
+
for (const check of checks.taskGroupChecks) {
|
|
558
1370
|
if (!check.covered) {
|
|
559
1371
|
result.warnings.push(
|
|
560
|
-
`Task-group intent may be missing in implementation: "${check.
|
|
1372
|
+
`Task-group intent may be missing in implementation: "${check.label}".`
|
|
561
1373
|
);
|
|
562
1374
|
}
|
|
563
1375
|
}
|
|
564
1376
|
|
|
565
|
-
if (
|
|
1377
|
+
if (degradedFallbackFiles.size > 0) {
|
|
1378
|
+
result.warnings.push(
|
|
1379
|
+
`verify-implementation fell back to heuristic mode for unparseable script files: ${listSummary(Array.from(degradedFallbackFiles).sort())}.`
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
if (markupHeuristicFiles.size > 0) {
|
|
1383
|
+
result.notes.push(
|
|
1384
|
+
`verify-implementation used markup heuristic evidence for: ${listSummary(Array.from(markupHeuristicFiles).sort())}.`
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
1387
|
+
if (unsupportedHeuristicFiles.size > 0) {
|
|
1388
|
+
result.notes.push(
|
|
1389
|
+
`verify-implementation used unsupported-language heuristic evidence for: ${listSummary(
|
|
1390
|
+
Array.from(unsupportedHeuristicFiles).sort()
|
|
1391
|
+
)}.`
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
if (result.strict && degradedChecks.length > 0) {
|
|
1396
|
+
result.warnings.push(
|
|
1397
|
+
"Strict mode does not promote degraded heuristic evidence to full-confidence coverage."
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
if (scan.truncatedByFileLimit) {
|
|
566
1402
|
result.warnings.push(
|
|
567
1403
|
`verify-implementation hit file scan limit (${MAX_SCANNED_FILES}); deep coverage may be incomplete.`
|
|
568
1404
|
);
|
|
569
1405
|
}
|
|
570
|
-
if (
|
|
1406
|
+
if (scan.truncatedByDirectoryLimit) {
|
|
571
1407
|
result.warnings.push(
|
|
572
1408
|
`verify-implementation hit directory scan limit (${MAX_SCANNED_DIRECTORIES}); coverage may be incomplete.`
|
|
573
1409
|
);
|
|
574
1410
|
}
|
|
575
|
-
if (
|
|
1411
|
+
if (scan.readErrors + readErrors > 0) {
|
|
576
1412
|
result.warnings.push(
|
|
577
|
-
`verify-implementation skipped unreadable files/directories (${
|
|
1413
|
+
`verify-implementation skipped unreadable files/directories (${scan.readErrors + readErrors}).`
|
|
578
1414
|
);
|
|
579
1415
|
}
|
|
580
|
-
if (
|
|
1416
|
+
if (scan.depthLimitHits > 0) {
|
|
581
1417
|
result.notes.push(
|
|
582
|
-
`verify-implementation enforced max scan depth (${MAX_SCAN_DEPTH}); skipped deeper paths: ${
|
|
1418
|
+
`verify-implementation enforced max scan depth (${MAX_SCAN_DEPTH}); skipped deeper paths: ${scan.depthLimitHits}.`
|
|
583
1419
|
);
|
|
584
1420
|
}
|
|
585
|
-
if (
|
|
1421
|
+
if (scan.skippedSymlinks > 0) {
|
|
586
1422
|
result.notes.push(
|
|
587
|
-
`verify-implementation skipped symlink entries during scan: ${
|
|
1423
|
+
`verify-implementation skipped symlink entries during scan: ${scan.skippedSymlinks}.`
|
|
588
1424
|
);
|
|
589
1425
|
}
|
|
590
1426
|
if (skippedLargeFiles > 0) {
|
|
@@ -597,8 +1433,17 @@ function verifyImplementation(projectPathInput, options = {}) {
|
|
|
597
1433
|
codeFiles: codeFiles.length,
|
|
598
1434
|
specFiles: specRecords.length,
|
|
599
1435
|
taskGroups: tasksArtifact.taskGroups.length,
|
|
600
|
-
scannedBytes
|
|
1436
|
+
scannedBytes,
|
|
1437
|
+
scanMode: result.scan.scanMode
|
|
601
1438
|
};
|
|
1439
|
+
result.implementation = {
|
|
1440
|
+
stateChecks: checks.stateChecks.length,
|
|
1441
|
+
taskGroupChecks: checks.taskGroupChecks.length,
|
|
1442
|
+
degradedChecks: degradedChecks.length,
|
|
1443
|
+
checks: allChecks.map((check) => serializeCheck(check, false)),
|
|
1444
|
+
evidenceModeCounts
|
|
1445
|
+
};
|
|
1446
|
+
|
|
602
1447
|
return finalize(result);
|
|
603
1448
|
}
|
|
604
1449
|
|
|
@@ -622,6 +1467,32 @@ function verifyStructure(projectPathInput, options = {}) {
|
|
|
622
1467
|
return finalize(result);
|
|
623
1468
|
}
|
|
624
1469
|
|
|
1470
|
+
const changedFiles = collectChangedFileEntries(result.projectRoot, options);
|
|
1471
|
+
if (changedFiles.requested && changedFiles.invalidEntries.length > 0) {
|
|
1472
|
+
for (const invalidEntry of changedFiles.invalidEntries) {
|
|
1473
|
+
result.failures.push(
|
|
1474
|
+
`Invalid --changed-files entry "${invalidEntry.input}": ${invalidEntry.reason}.`
|
|
1475
|
+
);
|
|
1476
|
+
}
|
|
1477
|
+
return finalize(result);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
const changedFileSet = new Set(
|
|
1481
|
+
changedFiles.entries
|
|
1482
|
+
.filter((entry) => !entry.isSymlink)
|
|
1483
|
+
.map((entry) => canonicalizePath(entry.absolutePath))
|
|
1484
|
+
);
|
|
1485
|
+
|
|
1486
|
+
const scannedMappingFiles = new Set();
|
|
1487
|
+
let scannedMappings = 0;
|
|
1488
|
+
let skippedByIncremental = 0;
|
|
1489
|
+
let ignoredSymlinkEntries = 0;
|
|
1490
|
+
for (const entry of changedFiles.entries) {
|
|
1491
|
+
if (entry.isSymlink) {
|
|
1492
|
+
ignoredSymlinkEntries += 1;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
|
|
625
1496
|
for (const mapping of bindings.mappings) {
|
|
626
1497
|
const landing = resolveImplementationLanding(result.projectRoot, mapping.implementation);
|
|
627
1498
|
if (!landing) {
|
|
@@ -629,33 +1500,69 @@ function verifyStructure(projectPathInput, options = {}) {
|
|
|
629
1500
|
continue;
|
|
630
1501
|
}
|
|
631
1502
|
|
|
1503
|
+
const normalizedLanding = canonicalizePath(landing);
|
|
1504
|
+
if (changedFiles.requested && !changedFileSet.has(normalizedLanding)) {
|
|
1505
|
+
skippedByIncremental += 1;
|
|
1506
|
+
continue;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
scannedMappingFiles.add(normalizedLanding);
|
|
1510
|
+
scannedMappings += 1;
|
|
1511
|
+
|
|
632
1512
|
const ext = path.extname(landing).toLowerCase();
|
|
633
1513
|
const source = safeReadFile(landing);
|
|
634
|
-
const
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
1514
|
+
const structureEvidence = collectStructureEvidenceText(source, ext);
|
|
1515
|
+
const normalizedSource = normalizeCoverageText(structureEvidence.text);
|
|
1516
|
+
const pageTokens = unique(tokenizeCoverage(mapping.designPage, 3));
|
|
1517
|
+
|
|
1518
|
+
if (STRUCTURE_MARKUP_EXTENSIONS.has(ext)) {
|
|
1519
|
+
const hasMarkupIndicators = /<section|<main|<header|<footer|<div|<template|<article/.test(
|
|
1520
|
+
structureEvidence.text
|
|
1521
|
+
);
|
|
1522
|
+
if (!structureEvidence.syntaxAvailable) {
|
|
1523
|
+
confidence.push({
|
|
1524
|
+
mapping: mapping.implementation,
|
|
1525
|
+
mode: "heuristic",
|
|
1526
|
+
confidence: "medium",
|
|
1527
|
+
file: normalizeRelativePath(path.relative(result.projectRoot, landing))
|
|
1528
|
+
});
|
|
1529
|
+
result.warnings.push(
|
|
1530
|
+
`verify-structure used heuristic mode for "${mapping.implementation}" because syntax parsing was unavailable (${structureEvidence.reason}).`
|
|
1531
|
+
);
|
|
1532
|
+
} else if (hasMarkupIndicators) {
|
|
1533
|
+
confidence.push({
|
|
1534
|
+
mapping: mapping.implementation,
|
|
1535
|
+
mode: "markup",
|
|
1536
|
+
confidence: "high",
|
|
1537
|
+
file: normalizeRelativePath(path.relative(result.projectRoot, landing))
|
|
1538
|
+
});
|
|
643
1539
|
} else {
|
|
644
|
-
confidence.push({
|
|
1540
|
+
confidence.push({
|
|
1541
|
+
mapping: mapping.implementation,
|
|
1542
|
+
mode: "heuristic",
|
|
1543
|
+
confidence: "medium",
|
|
1544
|
+
file: normalizeRelativePath(path.relative(result.projectRoot, landing))
|
|
1545
|
+
});
|
|
645
1546
|
result.warnings.push(
|
|
646
1547
|
`verify-structure used heuristic mode for "${mapping.implementation}" because markup structure was limited.`
|
|
647
1548
|
);
|
|
648
1549
|
}
|
|
649
1550
|
} else {
|
|
650
|
-
confidence.push({
|
|
1551
|
+
confidence.push({
|
|
1552
|
+
mapping: mapping.implementation,
|
|
1553
|
+
mode: "heuristic",
|
|
1554
|
+
confidence: "low",
|
|
1555
|
+
file: normalizeRelativePath(path.relative(result.projectRoot, landing))
|
|
1556
|
+
});
|
|
651
1557
|
result.warnings.push(
|
|
652
1558
|
`verify-structure used heuristic mode for "${mapping.implementation}" due to unsupported file type ${ext || "(none)"}.`
|
|
653
1559
|
);
|
|
654
1560
|
}
|
|
655
1561
|
|
|
656
1562
|
if (pageTokens.length > 0) {
|
|
657
|
-
const
|
|
658
|
-
|
|
1563
|
+
const tokenSet = new Set(tokenizeCoverage(normalizedSource, 1));
|
|
1564
|
+
const matchedCount = pageTokens.filter((token) => tokenSet.has(token)).length;
|
|
1565
|
+
if (matchedCount === 0) {
|
|
659
1566
|
result.warnings.push(
|
|
660
1567
|
`Structural drift suspected: design page "${mapping.designPage}" tokens not found in "${mapping.implementation}".`
|
|
661
1568
|
);
|
|
@@ -663,8 +1570,72 @@ function verifyStructure(projectPathInput, options = {}) {
|
|
|
663
1570
|
}
|
|
664
1571
|
}
|
|
665
1572
|
|
|
1573
|
+
const unmatchedChangedFiles = changedFiles.entries
|
|
1574
|
+
.filter((entry) => !entry.isSymlink)
|
|
1575
|
+
.map((entry) => canonicalizePath(entry.absolutePath))
|
|
1576
|
+
.filter((absolutePath) => !scannedMappingFiles.has(absolutePath));
|
|
1577
|
+
|
|
1578
|
+
result.scan = {
|
|
1579
|
+
scanMode: changedFiles.requested ? "incremental" : "full",
|
|
1580
|
+
requestedChangedFiles: changedFiles.rawEntries.length,
|
|
1581
|
+
selectedFileCount: changedFiles.requested ? changedFiles.entries.length : bindings.mappings.length,
|
|
1582
|
+
scannedMappingCount: scannedMappings,
|
|
1583
|
+
skippedMappings: skippedByIncremental,
|
|
1584
|
+
filtered: {
|
|
1585
|
+
duplicates: changedFiles.duplicateEntries.length,
|
|
1586
|
+
missing: changedFiles.missingEntries.length,
|
|
1587
|
+
directories: changedFiles.directoryEntries.length,
|
|
1588
|
+
unreadable: changedFiles.unreadableEntries.length,
|
|
1589
|
+
symlinks: ignoredSymlinkEntries,
|
|
1590
|
+
noBindingMatch: unmatchedChangedFiles.length,
|
|
1591
|
+
total:
|
|
1592
|
+
changedFiles.duplicateEntries.length +
|
|
1593
|
+
changedFiles.missingEntries.length +
|
|
1594
|
+
changedFiles.directoryEntries.length +
|
|
1595
|
+
changedFiles.unreadableEntries.length +
|
|
1596
|
+
ignoredSymlinkEntries +
|
|
1597
|
+
unmatchedChangedFiles.length
|
|
1598
|
+
},
|
|
1599
|
+
noRelevantFiles: changedFiles.requested && scannedMappings === 0
|
|
1600
|
+
};
|
|
1601
|
+
|
|
1602
|
+
if (changedFiles.requested) {
|
|
1603
|
+
if (changedFiles.missingEntries.length > 0) {
|
|
1604
|
+
result.notes.push(
|
|
1605
|
+
`Incremental input ignored missing files: ${listSummary(changedFiles.missingEntries)}.`
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
if (changedFiles.directoryEntries.length > 0) {
|
|
1609
|
+
result.notes.push(
|
|
1610
|
+
`Incremental input ignored directory paths: ${listSummary(changedFiles.directoryEntries)}.`
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
if (changedFiles.duplicateEntries.length > 0) {
|
|
1614
|
+
result.notes.push(
|
|
1615
|
+
`Incremental input deduplicated repeated paths: ${listSummary(changedFiles.duplicateEntries)}.`
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1618
|
+
if (unmatchedChangedFiles.length > 0) {
|
|
1619
|
+
const unmatchedRelative = unmatchedChangedFiles
|
|
1620
|
+
.map((absolutePath) => normalizeRelativePath(path.relative(result.projectRoot, absolutePath)))
|
|
1621
|
+
.sort();
|
|
1622
|
+
result.notes.push(
|
|
1623
|
+
`Incremental input included files without binding coverage: ${listSummary(unmatchedRelative)}.`
|
|
1624
|
+
);
|
|
1625
|
+
}
|
|
1626
|
+
if (ignoredSymlinkEntries > 0) {
|
|
1627
|
+
result.notes.push(`Incremental input ignored ${ignoredSymlinkEntries} symlink path(s).`);
|
|
1628
|
+
}
|
|
1629
|
+
if (scannedMappings === 0) {
|
|
1630
|
+
result.warnings.push(
|
|
1631
|
+
"Incremental verify-structure scanned zero relevant implementation mappings; result is partial."
|
|
1632
|
+
);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
|
|
666
1636
|
result.structure = {
|
|
667
|
-
confidence
|
|
1637
|
+
confidence,
|
|
1638
|
+
scan: result.scan
|
|
668
1639
|
};
|
|
669
1640
|
return finalize(result);
|
|
670
1641
|
}
|
|
@@ -673,8 +1644,17 @@ function verifyCoverage(projectPathInput, options = {}) {
|
|
|
673
1644
|
const projectRoot = path.resolve(projectPathInput || process.cwd());
|
|
674
1645
|
const strict = options.strict === true;
|
|
675
1646
|
const changeId = options.changeId ? String(options.changeId).trim() : "";
|
|
1647
|
+
const changedFilesRequested = options.changedFilesProvided === true || Array.isArray(options.changedFiles);
|
|
1648
|
+
const changedFiles = Array.isArray(options.changedFiles) ? options.changedFiles : undefined;
|
|
1649
|
+
|
|
676
1650
|
const sharedSetup = createSharedSetup(projectRoot, { changeId, strict });
|
|
677
|
-
const sharedOptions = {
|
|
1651
|
+
const sharedOptions = {
|
|
1652
|
+
changeId,
|
|
1653
|
+
strict,
|
|
1654
|
+
sharedSetup,
|
|
1655
|
+
changedFiles,
|
|
1656
|
+
changedFilesProvided: changedFilesRequested
|
|
1657
|
+
};
|
|
678
1658
|
const bindingsResult = verifyBindings(projectRoot, sharedOptions);
|
|
679
1659
|
const implementationResult = verifyImplementation(projectRoot, sharedOptions);
|
|
680
1660
|
const structureResult = verifyStructure(projectRoot, sharedOptions);
|
|
@@ -715,6 +1695,30 @@ function verifyCoverage(projectPathInput, options = {}) {
|
|
|
715
1695
|
result.warnings.push("Missing `verification.md`; coverage evidence is incomplete.");
|
|
716
1696
|
}
|
|
717
1697
|
|
|
1698
|
+
const incrementalSurfaces = [];
|
|
1699
|
+
const partialSurfaces = [];
|
|
1700
|
+
if (implementationResult.scan && implementationResult.scan.scanMode === "incremental") {
|
|
1701
|
+
incrementalSurfaces.push("verify-implementation");
|
|
1702
|
+
if (implementationResult.scan.noRelevantFiles) {
|
|
1703
|
+
partialSurfaces.push("verify-implementation:no-relevant-files");
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
if (structureResult.scan && structureResult.scan.scanMode === "incremental") {
|
|
1707
|
+
incrementalSurfaces.push("verify-structure");
|
|
1708
|
+
if (structureResult.scan.noRelevantFiles) {
|
|
1709
|
+
partialSurfaces.push("verify-structure:no-relevant-files");
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
if (incrementalSurfaces.length > 0) {
|
|
1714
|
+
result.warnings.push(
|
|
1715
|
+
`verify-coverage aggregated incremental upstream verification (${incrementalSurfaces.join(", ")}); treat as partial freshness.`
|
|
1716
|
+
);
|
|
1717
|
+
result.notes.push(
|
|
1718
|
+
"Incremental verification scopes are useful for changed-file checks but do not replace full-project freshness."
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1721
|
+
|
|
718
1722
|
result.components = {
|
|
719
1723
|
bindings: {
|
|
720
1724
|
status: bindingsResult.status,
|
|
@@ -724,16 +1728,29 @@ function verifyCoverage(projectPathInput, options = {}) {
|
|
|
724
1728
|
implementation: {
|
|
725
1729
|
status: implementationResult.status,
|
|
726
1730
|
failures: implementationResult.failures,
|
|
727
|
-
warnings: implementationResult.warnings
|
|
1731
|
+
warnings: implementationResult.warnings,
|
|
1732
|
+
scan: implementationResult.scan || null,
|
|
1733
|
+
evidenceModeCounts:
|
|
1734
|
+
implementationResult.implementation && implementationResult.implementation.evidenceModeCounts
|
|
1735
|
+
? implementationResult.implementation.evidenceModeCounts
|
|
1736
|
+
: {}
|
|
728
1737
|
},
|
|
729
1738
|
structure: {
|
|
730
1739
|
status: structureResult.status,
|
|
731
1740
|
failures: structureResult.failures,
|
|
732
1741
|
warnings: structureResult.warnings,
|
|
733
|
-
confidence: structureResult.structure ? structureResult.structure.confidence : []
|
|
1742
|
+
confidence: structureResult.structure ? structureResult.structure.confidence : [],
|
|
1743
|
+
scan: structureResult.scan || null
|
|
734
1744
|
}
|
|
735
1745
|
};
|
|
736
1746
|
|
|
1747
|
+
result.scan = {
|
|
1748
|
+
scanMode: incrementalSurfaces.length > 0 ? "incremental" : "full",
|
|
1749
|
+
incrementalSurfaces,
|
|
1750
|
+
partialSurfaces,
|
|
1751
|
+
changedFilesRequested: changedFilesRequested
|
|
1752
|
+
};
|
|
1753
|
+
|
|
737
1754
|
const freshness = collectVerificationFreshness(projectRoot, {
|
|
738
1755
|
changeId: result.changeId,
|
|
739
1756
|
resolved: sharedSetup.resolved,
|
|
@@ -753,6 +1770,43 @@ function verifyCoverage(projectPathInput, options = {}) {
|
|
|
753
1770
|
return finalize(result);
|
|
754
1771
|
}
|
|
755
1772
|
|
|
1773
|
+
function formatBoundarySummary(boundaries) {
|
|
1774
|
+
if (!Array.isArray(boundaries) || boundaries.length === 0) {
|
|
1775
|
+
return "";
|
|
1776
|
+
}
|
|
1777
|
+
const compact = boundaries.map((item) => item.type).filter(Boolean);
|
|
1778
|
+
if (compact.length === 0) {
|
|
1779
|
+
return "";
|
|
1780
|
+
}
|
|
1781
|
+
return ` [boundaries: ${unique(compact).join(", ")}]`;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
function appendScanLines(lines, scan) {
|
|
1785
|
+
if (!scan) {
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
lines.push("", "Scan:");
|
|
1789
|
+
lines.push(`- mode: ${scan.scanMode || "full"}`);
|
|
1790
|
+
if (Number.isFinite(scan.selectedFileCount)) {
|
|
1791
|
+
lines.push(`- selected files: ${scan.selectedFileCount}`);
|
|
1792
|
+
}
|
|
1793
|
+
if (Number.isFinite(scan.relevantFileCount)) {
|
|
1794
|
+
lines.push(`- relevant files: ${scan.relevantFileCount}`);
|
|
1795
|
+
}
|
|
1796
|
+
if (Number.isFinite(scan.scannedFileCount)) {
|
|
1797
|
+
lines.push(`- scanned files: ${scan.scannedFileCount}`);
|
|
1798
|
+
}
|
|
1799
|
+
if (Number.isFinite(scan.scannedMappingCount)) {
|
|
1800
|
+
lines.push(`- scanned mappings: ${scan.scannedMappingCount}`);
|
|
1801
|
+
}
|
|
1802
|
+
if (scan.filtered && Number.isFinite(scan.filtered.total)) {
|
|
1803
|
+
lines.push(`- filtered entries: ${scan.filtered.total}`);
|
|
1804
|
+
}
|
|
1805
|
+
if (scan.noRelevantFiles) {
|
|
1806
|
+
lines.push("- no relevant files: yes");
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
|
|
756
1810
|
function formatVerifyReport(result, title = "Da Vinci verify") {
|
|
757
1811
|
const lines = [
|
|
758
1812
|
title,
|
|
@@ -766,6 +1820,9 @@ function formatVerifyReport(result, title = "Da Vinci verify") {
|
|
|
766
1820
|
lines.push(`${key}: ${value}`);
|
|
767
1821
|
}
|
|
768
1822
|
}
|
|
1823
|
+
|
|
1824
|
+
appendScanLines(lines, result.scan);
|
|
1825
|
+
|
|
769
1826
|
if (result.failures.length > 0) {
|
|
770
1827
|
lines.push("", "Failures:");
|
|
771
1828
|
for (const failure of result.failures) {
|
|
@@ -784,12 +1841,27 @@ function formatVerifyReport(result, title = "Da Vinci verify") {
|
|
|
784
1841
|
lines.push(`- ${note}`);
|
|
785
1842
|
}
|
|
786
1843
|
}
|
|
1844
|
+
|
|
1845
|
+
if (result.implementation && Array.isArray(result.implementation.checks) && result.implementation.checks.length > 0) {
|
|
1846
|
+
lines.push("", "Implementation evidence:");
|
|
1847
|
+
for (const check of result.implementation.checks) {
|
|
1848
|
+
const evidence = check.evidence || {};
|
|
1849
|
+
const state = check.covered ? "covered" : "missing";
|
|
1850
|
+
const location = evidence.file ? ` @ ${evidence.file}` : "";
|
|
1851
|
+
lines.push(
|
|
1852
|
+
`- ${check.type} "${check.label}": ${state} via ${evidence.mode || "none"} (${evidence.confidence || "none"})${location}${formatBoundarySummary(check.boundaries)}`
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
|
|
787
1857
|
if (result.structure && Array.isArray(result.structure.confidence) && result.structure.confidence.length > 0) {
|
|
788
1858
|
lines.push("", "Structure confidence:");
|
|
789
1859
|
for (const item of result.structure.confidence) {
|
|
790
|
-
|
|
1860
|
+
const location = item.file ? ` @ ${item.file}` : "";
|
|
1861
|
+
lines.push(`- ${item.mapping}: ${item.mode} (${item.confidence})${location}`);
|
|
791
1862
|
}
|
|
792
1863
|
}
|
|
1864
|
+
|
|
793
1865
|
return lines.join("\n");
|
|
794
1866
|
}
|
|
795
1867
|
|