feat(P04): verification intelligence — git-native coverage, npm audit, TS compilation

---ci---
project: ci
phase: 4
milestone: v0.5
status: complete
decisions:
  - id: D-028
    decision: Phase 4 Verification Intelligence complete
    rationale: All INTEL requirements covered; 31 suites, 355 tests
    confidence: 0.95
    alternatives: []
requirements:
  covered: [INTEL-01, INTEL-02, INTEL-03]
---/ci---
This commit is contained in:
Jon Chery
2026-05-29 16:46:17 +00:00
parent 5753e2dc96
commit b33431c1a6
3 changed files with 192 additions and 14 deletions
+93
View File
@@ -27,6 +27,7 @@ export class BehavioralVerification extends VerificationLayer {
checks.push(this.checkSpecificationRequirements(projectPath));
checks.push(this.checkPlanMustHaves(projectPath, phase));
checks.push(this.checkCodeHasExports(projectPath));
checks.push(this.checkRequirementTestCoverage(projectPath));
const passed = checks.every((c) => c.status !== "fail");
return {
@@ -219,6 +220,98 @@ export class BehavioralVerification extends VerificationLayer {
);
}
private checkRequirementTestCoverage(projectPath: string): VerificationCheck {
const isGitRepo = fs.existsSync(path.join(projectPath, ".git"));
if (!isGitRepo) {
return this.check(
"Requirement test coverage via git log",
"skipped",
"Not a git repository — cannot check requirement coverage from commit history"
);
}
try {
const raw = execSync(
`git log --all --max-count=100 --format="%B%x01"`,
{ cwd: projectPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }
);
const coveredReqs = new Set<string>();
const ciBlockRegex = /---ci---[\s\S]*?---\/ci---/g;
const entries = raw.split("\x01").filter(Boolean);
for (const entry of entries) {
let match;
while ((match = ciBlockRegex.exec(entry)) !== null) {
const reqMatch = match[0].match(/covered:\s*\[([^\]]*)\]/);
if (reqMatch) {
const reqs = reqMatch[1].split(",").map((r: string) => r.trim().replace(/['"]/g, "")).filter(Boolean);
for (const req of reqs) coveredReqs.add(req);
}
}
ciBlockRegex.lastIndex = 0;
}
const reqPath = path.join(projectPath, ".ci", "REQUIREMENTS.md");
if (!fs.existsSync(reqPath)) {
return this.check(
"Requirement test coverage via git log",
"skipped",
"No REQUIREMENTS.md found to check coverage against"
);
}
const content = fs.readFileSync(reqPath, "utf-8");
const allReqs = content
.split("\n")
.filter((line) => /^\|.*\|.*\|.*\|/.test(line) && !line.includes("REQ-ID") && !line.includes("---"))
.map((line) => {
const cols = line.split("|").map((c) => c.trim()).filter(Boolean);
return cols.length >= 1 ? cols[0] : "";
})
.filter(Boolean);
if (allReqs.length === 0) {
return this.check(
"Requirement test coverage via git log",
"skipped",
"No requirements with REQ-IDs found in REQUIREMENTS.md"
);
}
const covered = allReqs.filter((r) => coveredReqs.has(r));
const coveragePct = Math.round((covered.length / allReqs.length) * 100);
if (coveragePct >= 80) {
return this.check(
"Requirement test coverage via git log",
"pass",
`${covered.length}/${allReqs.length} requirements covered (${coveragePct}%)`
);
}
if (coveragePct >= 50) {
return this.check(
"Requirement test coverage via git log",
"warning",
`${covered.length}/${allReqs.length} requirements covered (${coveragePct}%) — target ≥80%`
);
}
return this.check(
"Requirement test coverage via git log",
"warning",
`${covered.length}/${allReqs.length} requirements covered (${coveragePct}%) — significant gaps`
);
} catch {
return this.check(
"Requirement test coverage via git log",
"skipped",
"Could not read git log for requirement coverage"
);
}
}
private checkCodeHasExports(projectPath: string): VerificationCheck {
const srcDir = path.join(projectPath, "src");
if (!fs.existsSync(srcDir)) {