v0.2.0: Git-native architecture (#1)

This commit was merged in pull request #1.
This commit is contained in:
2026-05-29 12:59:45 +00:00
parent 9cf5c000d9
commit 6e637e4af0
50 changed files with 5852 additions and 135 deletions
+220 -12
View File
@@ -1,5 +1,52 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { VerificationLayer, VerificationResult, VerificationCheck } from "./types.js";
interface CodeFinding {
severity: "P0" | "P1" | "P2" | "P3";
category: string;
message: string;
file?: string;
}
const CODE_QUALITY_PATTERNS: Array<{
pattern: RegExp;
severity: "P0" | "P1" | "P2" | "P3";
category: string;
message: string;
}> = [
{
pattern: /catch\s*\(\w*\)\s*\{\s*\}/g,
severity: "P0",
category: "error_handling",
message: "Empty catch block — errors silently swallowed",
},
{
pattern: /console\.(log|warn|error)\s*\(/g,
severity: "P2",
category: "logging",
message: "Direct console.log usage — consider structured logging",
},
{
pattern: /any\b/g,
severity: "P1",
category: "type_safety",
message: "Use of 'any' type — loses type safety",
},
{
pattern: /TODO|FIXME|HACK|XXX/g,
severity: "P2",
category: "tech_debt",
message: "Technical debt marker found",
},
{
pattern: /\bvar\s+/g,
severity: "P1",
category: "modern_js",
message: "Use of 'var' — prefer 'const' or 'let'",
},
];
export class QualityVerification extends VerificationLayer {
readonly layer = 4;
readonly name = "Code Quality";
@@ -8,25 +55,186 @@ export class QualityVerification extends VerificationLayer {
const start = Date.now();
const checks: VerificationCheck[] = [];
checks.push({
name: "P0 findings auto-applied",
status: "skipped",
message: "Code review auto-fix not yet implemented",
});
const findings = this.scanForFindings(projectPath);
checks.push({
name: "P1+ findings flagged for review",
status: "skipped",
message: "Multi-persona review not yet implemented",
});
const p0Findings = findings.filter((f) => f.severity === "P0");
const p1Findings = findings.filter((f) => f.severity === "P1");
const p2p3Findings = findings.filter((f) => f.severity === "P2" || f.severity === "P3");
checks.push(this.checkP0Findings(p0Findings));
checks.push(this.checkP1Findings(p1Findings));
checks.push(this.checkP2P3Findings(p2p3Findings));
checks.push(this.checkTypeScriptStrictness(projectPath));
checks.push(this.checkConsistentNaming(projectPath));
const hasP0Fail = p0Findings.length > 3;
const passed = !hasP0Fail;
return {
layer: this.layer,
name: this.name,
passed: true,
passed,
checks,
summary: `Code quality verification layer (placeholder)`,
summary: `${findings.length} findings (P0: ${p0Findings.length}, P1: ${p1Findings.length}, P2/P3: ${p2p3Findings.length})`,
duration_ms: Date.now() - start,
};
}
private scanForFindings(projectPath: string): CodeFinding[] {
const findings: CodeFinding[] = [];
const srcDir = path.join(projectPath, "src");
if (!fs.existsSync(srcDir)) {
return findings;
}
this.scanDirectory(srcDir, projectPath, findings);
return findings;
}
private scanDirectory(dir: string, projectPath: string, findings: CodeFinding[]): void {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory() && entry.name !== "node_modules") {
this.scanDirectory(fullPath, projectPath, findings);
} else if (
entry.isFile() &&
entry.name.endsWith(".ts") &&
!entry.name.endsWith(".test.ts") &&
!entry.name.endsWith(".d.ts")
) {
const content = fs.readFileSync(fullPath, "utf-8");
for (const { pattern, severity, category, message } of CODE_QUALITY_PATTERNS) {
pattern.lastIndex = 0;
const matches = pattern.test(content);
if (matches) {
findings.push({
severity,
category,
message: `${message} (${path.relative(projectPath, fullPath)})`,
file: path.relative(projectPath, fullPath),
});
}
}
}
}
}
private checkP0Findings(p0Findings: CodeFinding[]): VerificationCheck {
if (p0Findings.length === 0) {
return this.check(
"P0 findings (auto-fix)",
"pass",
"No critical code quality issues found"
);
}
return this.check(
"P0 findings (auto-fix)",
p0Findings.length > 3 ? "fail" : "warning",
`${p0Findings.length} P0 finding(s) — should be auto-fixed`,
p0Findings.map((f) => `[${f.category}] ${f.message}`).join("\n")
);
}
private checkP1Findings(p1Findings: CodeFinding[]): VerificationCheck {
if (p1Findings.length === 0) {
return this.check(
"P1 findings (review)",
"pass",
"No P1 findings"
);
}
return this.check(
"P1 findings (review)",
"pass",
`${p1Findings.length} P1 finding(s) flagged for post-hoc review`,
p1Findings.map((f) => `[${f.category}] ${f.message}`).join("\n")
);
}
private checkP2P3Findings(findings: CodeFinding[]): VerificationCheck {
if (findings.length === 0) {
return this.check(
"P2/P3 findings (informational)",
"pass",
"No P2/P3 findings"
);
}
return this.check(
"P2/P3 findings (informational)",
"pass",
`${findings.length} informational finding(s)`,
findings.map((f) => `[${f.category}] ${f.message}`).join("\n")
);
}
private checkTypeScriptStrictness(projectPath: string): VerificationCheck {
const tsconfigPath = path.join(projectPath, "tsconfig.json");
if (!fs.existsSync(tsconfigPath)) {
return this.check("TypeScript strictness", "warning", "No tsconfig.json found");
}
const content = fs.readFileSync(tsconfigPath, "utf-8");
const jsonContent = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
try {
const tsconfig = JSON.parse(jsonContent);
const strict = tsconfig.compilerOptions?.strict;
const noImplicitAny = tsconfig.compilerOptions?.noImplicitAny;
if (strict) {
return this.check("TypeScript strictness", "pass", "TypeScript strict mode is enabled");
}
if (noImplicitAny) {
return this.check("TypeScript strictness", "pass", "noImplicitAny is enabled");
}
return this.check(
"TypeScript strictness",
"warning",
"TypeScript strict mode is not enabled — consider enabling for better type safety"
);
} catch {
return this.check("TypeScript strictness", "warning", "Could not parse tsconfig.json");
}
}
private checkConsistentNaming(projectPath: string): VerificationCheck {
const srcDir = path.join(projectPath, "src");
if (!fs.existsSync(srcDir)) {
return this.check("Consistent naming", "skipped", "No src/ directory found");
}
const tsFiles: string[] = [];
this.collectFiles(srcDir, tsFiles);
const kebabCaseFiles = tsFiles.filter((f) => path.basename(f).includes("-") && !path.basename(f).includes(".test."));
const camelCaseFiles = tsFiles.filter((f) => /^[a-z][a-zA-Z0-9]*\.ts$/.test(path.basename(f)) && !path.basename(f).includes("-"));
const pascalCaseFiles = tsFiles.filter((f) => /^[A-Z]/.test(path.basename(f)));
const conventions: string[] = [];
if (kebabCaseFiles.length > camelCaseFiles.length && kebabCaseFiles.length > pascalCaseFiles.length) {
conventions.push("kebab-case dominant");
} else if (camelCaseFiles.length > 0) {
conventions.push("camelCase files present");
}
return this.check(
"Consistent naming",
"pass",
`${tsFiles.length} source files, naming conventions: ${conventions.join(", ") || "mixed"}`
);
}
private collectFiles(dir: string, files: string[]): void {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory() && entry.name !== "node_modules") {
this.collectFiles(fullPath, files);
} else if (entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
files.push(fullPath);
}
}
}
}