/* commands-validate — static analysis validator for kernel command output consistency (RFC-0903). Does not execute commands — pure static analysis of return statements. Does not modify files — produces diagnostics only. Does not add itself to PACKAGES_CHECK_PIPELINE — gated adoption per RFC-0903. Does not detect dynamically constructed return objects (spread, conditional assignment) — regex-based scanning catches direct object literal returns only. RFC-1321: CMDGEN-06 — non-literal KernelCommandDefinition name fields (const refs, expressions) are blocking diagnostics via shared isStringLiteralSource; mask+anchor scan lazily shared with CMDGEN-04. ADR-0105: detector gap fix — isHandlerFile missed self-describing KernelCommandDefinition files whose type imports span multiple lines; FILE_DETECTION_PATTERNS now includes KernelCommandDefinition and the import regex uses [^;]* to cross newlines. RFC-1097: step 6 — compass.migrate codemod run Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order. RFC-1097: sweep — werkstatt-engine clean Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics. RFC-1248: VITE-CLIENT-DEP-02 lazy-import gate + SCAN-02 validator reads-root existence check (RFC-1248) RFC-1304: shared fail-loud scan — CMDGEN-05 forbidden-codepoint sweep on raw source, CMDGEN-04 per-definition suspect pairing over the site-src corpus via werkstatt-shared/kernel/command-source-scan.ts (line-anchored comment-aware pragma), CMD-SCAN-01 warnings on unreadable dirs/files and an error on a zero-file corpus; io is injectable for coverage-failure tests. RFC-0903 */ import { join, relative } from "node:path"; import type { Diagnostic, KernelCommandResult, KernelNextStep, } from "@warpgogol/werkstatt-shared/kernel"; import { CMDGEN_IGNORE_RE, fieldOffsets, findDefinitionAnchors, isStringLiteralSource, literalEnd, maskSource, NON_COMMAND_ALLOWLIST, scanForbiddenCodepoints, unpairedCommandSignals, } from "@warpgogol/werkstatt-shared/kernel"; import { createDefaultIO, type WorkspaceIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io"; import { applyValidationBaseline, type ValidationBaseline, } from "@warpgogol/werkstatt-shared/kernel"; export interface CommandsValidateResult { command: string; status: "pass" | "fail"; diagnostics: Diagnostic[]; scannedFiles: number; /** RFC-1316: baseline-known violation count (delta mode only). */ debtViolations?: number; /** RFC-1316: baseline file consulted, when delta mode ran. */ baselineRef?: string; } const COMMAND_NAME = "werkstatt.commands.validate"; const EXCLUDE_DIRS = new Set(["node_modules", "tests", "tests-handoff", "dist", "templates", "os"]); const EXCLUDE_SUFFIXES = [".test.ts", ".spec.ts"]; const HELPER_FUNCTIONS = new Set([ "passResult", "failResult", "diagnosticsResult", "resultFromViolations", "buildAuditResult", ]); const FILE_DETECTION_PATTERNS = [ /registry\.registerCommand\s*\(/, /\bALL_COMMANDS\b/, /\bmoduleLoaders\b/, // Self-describing KernelCommandDefinition files (RFC-1101) — a definition-bearing // module may import its result types via multi-line `import type` (invisible to // the single-line import regex below) and register no registry call. /\bKernelCommandDefinition\b/, ]; // The CMDGEN-04 suspect net only applies to the generator's corpus — site src. // Engine/shared commands register via registry.registerCommand (a different, // non-anchored pattern), so the signal would false-positive there. const SITE_SRC_PREFIX = "packages/werkstatt-site/src/"; const SITE_CHECKS_PREFIX = "packages/werkstatt-site/src/checks/"; const RETURN_OBJECT_PATTERN = /return\s*\{/g; const EXIT_CODE_PATTERN = /exitCode\s*:\s*(?:(\d+)|(\w+))/; const SUMMARY_KEY_PATTERN = /summary\s*(?::|,|})/; const SUMMARY_PATTERN = /summary\s*:\s*(?:"([^"]*)"|'([^']*)'|`([^`]*)`)/; const NEXT_STEPS_PATTERN = /nextSteps\s*:/; const HELPER_RETURN_PATTERN = new RegExp( `return\\s+(?:await\\s+)?(${[...HELPER_FUNCTIONS].join("|")})\\s*\\(`, ); function hasCommandResultKeys(returnText: string): boolean { let depth = 0; let inString: string | null = null; for (let i = 0; i < returnText.length; i++) { const ch = returnText[i]; if (inString) { if (ch === "\\") { i++; continue; } if (ch === inString) inString = null; continue; } if (ch === '"' || ch === "'" || ch === "`") { inString = ch; continue; } if (ch === "{") depth++; if (ch === "}") depth--; if (depth === 1 && ch === ":") { let j = i - 1; while (j >= 0 && /\s/.test(returnText[j])) j--; const keyEnd = j + 1; if (j >= 0 && (returnText[j] === '"' || returnText[j] === "'")) { const quote = returnText[j]; j--; while (j >= 0 && returnText[j] !== quote) j--; const key = returnText.slice(j + 1, keyEnd - 1); if (key === "exitCode" || key === "summary") return true; continue; } while (j >= 0 && /[\w$-]/.test(returnText[j])) j--; const key = returnText.slice(j + 1, keyEnd); if (key === "exitCode" || key === "summary") return true; } } return false; } function shouldExcludeFile(fileName: string): boolean { return EXCLUDE_SUFFIXES.some((suffix) => fileName.endsWith(suffix)); } function isHandlerFile(content: string): boolean { // [^;]* (not .*) so multi-line `import type {\n KernelCommandResult,\n}` is detected — // an import statement contains no `;` before its terminator (ADR-0105 detector gap). if (/import[^;]*KernelCommandResult|import[^;]*KernelNextStep/.test(content)) return true; for (const pattern of FILE_DETECTION_PATTERNS) { if (pattern.test(content)) return true; } return false; } interface ReturnViolation { exitCodePresent: boolean; exitCodeValue: string | null; exitCodeIsLiteral: boolean; summaryPresent: boolean; summaryValue: string | null; nextStepsPresent: boolean; isHelperReturn: boolean; } function findReturnStatements(content: string): { startIdx: number; line: number }[] { const results: { startIdx: number; line: number }[] = []; let match: RegExpExecArray | null; const pattern = new RegExp(RETURN_OBJECT_PATTERN.source, "g"); while ((match = pattern.exec(content)) !== null) { const line = content.slice(0, match.index).split("\n").length; results.push({ startIdx: match.index, line }); } return results; } function extractReturnObject(content: string, startIdx: number): string { let depth = 0; let endIdx = startIdx; let inString: string | null = null; for (let i = startIdx; i < content.length; i++) { const ch = content[i]; if (inString) { if (ch === "\\") { i++; continue; } if (ch === inString) inString = null; continue; } if (ch === '"' || ch === "'" || ch === "`") { inString = ch; continue; } if (ch === "{") depth++; if (ch === "}") { depth--; if (depth === 0) { endIdx = i + 1; break; } } } return content.slice(startIdx, endIdx); } function analyzeReturnObject( returnText: string, fullContent: string, returnStartIdx: number, ): ReturnViolation { const helperMatch = HELPER_RETURN_PATTERN.exec( fullContent.slice(returnStartIdx, returnStartIdx + 200), ); if (helperMatch) { return { exitCodePresent: true, exitCodeValue: null, exitCodeIsLiteral: false, summaryPresent: true, summaryValue: null, nextStepsPresent: true, isHelperReturn: true, }; } if (!hasCommandResultKeys(returnText)) { return { exitCodePresent: true, exitCodeValue: null, exitCodeIsLiteral: false, summaryPresent: true, summaryValue: null, nextStepsPresent: true, isHelperReturn: true, }; } const exitCodeMatch = EXIT_CODE_PATTERN.exec(returnText); const summaryKeyMatch = SUMMARY_KEY_PATTERN.test(returnText); const summaryMatch = SUMMARY_PATTERN.exec(returnText); const nextStepsMatch = NEXT_STEPS_PATTERN.test(returnText); const exitCodeValue = exitCodeMatch?.[1] ?? exitCodeMatch?.[2] ?? null; const exitCodeIsLiteral = exitCodeMatch?.[1] !== undefined; let summaryValue = summaryMatch?.[1] ?? summaryMatch?.[2] ?? summaryMatch?.[3] ?? null; if (summaryValue === null && summaryKeyMatch) { const ternaryMatch = returnText.match(/summary\s*:\s*[^"'`\n]*?\?\s*`([^`]*)`/s); if (ternaryMatch) { summaryValue = ternaryMatch[1]; } else { const anyStringMatch = returnText.match(/summary\s*:\s*[\s\S]*?`([^`]*)`/); if (anyStringMatch) { summaryValue = anyStringMatch[1]; } } } return { exitCodePresent: exitCodeMatch !== null, exitCodeValue, exitCodeIsLiteral, summaryPresent: summaryKeyMatch, summaryValue, nextStepsPresent: nextStepsMatch, isHelperReturn: false, }; } function inferCommandName(filePath: string, workspaceRoot: string): string { const relPath = relative(workspaceRoot, filePath); const parts = relPath.replace(/\.ts$/, "").split("/"); const fileName = parts[parts.length - 1]; return fileName.replace(/-/g, "."); } async function scanFile( filePath: string, workspaceRoot: string, io: WorkspaceIO = createDefaultIO().io, ): Promise { // RFC-1304 CMD-SCAN-01: an unreadable input is a coverage gap — warn loudly // instead of treating the file as empty and attesting it scanned clean. const content = await io.readFile(filePath).catch(() => undefined); if (content === undefined) { return [ { ruleId: "CMD-SCAN-01", severity: "warning", message: `File could not be read and was skipped: ${relative(workspaceRoot, filePath)}`, file: relative(workspaceRoot, filePath), fixHint: `Fix the unreadable file (permissions, encoding, dangling symlink) — the scanner cannot attest coverage over inputs it cannot open (RFC-1304).`, }, ]; } if (!content) return []; const diagnostics: Diagnostic[] = []; const commandName = inferCommandName(filePath, workspaceRoot); const relPath = relative(workspaceRoot, filePath); const posixRel = relPath.split("\\").join("/"); // RFC-1304 CMDGEN-05: invisible/control codepoints are never legitimate in // scanned sources — reported on the raw buffer, before the handler gate, // never suppressible. for (const hit of scanForbiddenCodepoints(content)) { diagnostics.push({ ruleId: "CMDGEN-05", severity: "error", message: `Forbidden ${hit.name} codepoint (U+${hit.codepoint.toString(16).toUpperCase().padStart(4, "0")}) at offset ${hit.offset} in ${relPath}`, file: relPath, line: hit.line, fixHint: `Remove the invisible byte — it desyncs the masked command scanner (RFC-1304).`, }); } // Lazily shared definition scan — CMDGEN-04 (site-src corpus) and CMDGEN-06 // (any scanned file) both consume the masked anchors; masking runs once. let maskedCached: string | undefined; let anchorsCached: ReturnType | undefined; const definitionScan = () => { if (maskedCached === undefined) { maskedCached = maskSource(content); anchorsCached = findDefinitionAnchors(maskedCached); } return { masked: maskedCached, anchors: anchorsCached ?? [] }; }; // RFC-1321 CMDGEN-06: a non-literal `name:` (const ref, expression) lands its // source text — not the resolved value — as the generated table key. Applies // to every extracted definition in the scanned corpus, whichever package // hosts it; the site generator fails closed on the same helper. if (content.includes("KernelCommandDefinition")) { const { masked, anchors } = definitionScan(); for (const anchor of anchors) { const end = literalEnd(masked, anchor.openIdx); if (end === -1) continue; // unbalanced — CMDGEN-04 reports the suspect file const litMasked = masked.slice(anchor.openIdx, end); const litRaw = content.slice(anchor.openIdx, end); const nameOff = fieldOffsets(litMasked, "name"); if (!nameOff) continue; const nameRaw = litRaw.slice(nameOff[0], nameOff[1]).trim(); if (!isStringLiteralSource(nameRaw, litMasked.slice(nameOff[0], nameOff[1]))) { const line = content.slice(0, anchor.openIdx + nameOff[0]).split("\n").length; diagnostics.push({ ruleId: "CMDGEN-06", severity: "error", message: `KernelCommandDefinition "${anchor.exportName}" declares a non-literal name field (${nameRaw}) in ${relPath}`, file: relPath, line, fixHint: `Declare name: "" literally — the generated command table keys off the declaration's source text, so a const ref lands its identifier as the key (RFC-1321).`, }); } } } // RFC-1304 CMDGEN-04: re-run the generator's suspect-file signal over its own // corpus, so a file unreachable during generation is still caught here. The // pairing is per-definition: unpaired annotation/satisfies evidence flags the // file even when earlier definitions extracted fine. if ( posixRel.startsWith(SITE_SRC_PREFIX) && !CMDGEN_IGNORE_RE.test(maskSource(content, { keepComments: true })) ) { const { masked, anchors } = definitionScan(); const signals = unpairedCommandSignals(masked, anchors); if ( signals.unpaired > 0 && !( posixRel.startsWith(SITE_CHECKS_PREFIX) && NON_COMMAND_ALLOWLIST.has(posixRel.slice(SITE_CHECKS_PREFIX.length)) ) ) { const line = signals.firstOffset >= 0 ? content.slice(0, signals.firstOffset).split("\n").length : undefined; diagnostics.push({ ruleId: "CMDGEN-04", severity: "error", message: `File appears to contain a command definition that was not extracted in ${relPath}`, file: relPath, line, fixHint: `Check for unmaskable constructs (nested template literals, control bytes) or add a // cmdgen-ignore pragma if the file is not a command source (RFC-1304).`, }); } } if (!isHandlerFile(content)) return diagnostics; const returnStarts = findReturnStatements(content); for (const { startIdx, line } of returnStarts) { const returnText = extractReturnObject(content, startIdx); if (!returnText || returnText === "{}") continue; const violation = analyzeReturnObject(returnText, content, startIdx); if (violation.isHelperReturn) continue; if (!violation.exitCodePresent) { diagnostics.push({ ruleId: "CMD-OUTPUT-01", severity: "error", message: `Return path missing explicit exitCode in ${relPath}:${line}`, file: relPath, line, fixHint: `Add exitCode: 0 (success) or exitCode: 1 (failure) to the return object.`, }); } if (!violation.summaryPresent) { diagnostics.push({ ruleId: "CMD-OUTPUT-02", severity: "error", message: `Return path missing summary in ${relPath}:${line}`, file: relPath, line, fixHint: `Add summary: "[${commandName}] " to the return object.`, }); } else if (violation.summaryValue && !violation.summaryValue.startsWith("[")) { diagnostics.push({ ruleId: "CMD-OUTPUT-02", severity: "error", message: `Summary does not start with [command.name] prefix in ${relPath}:${line}: "${violation.summaryValue.slice(0, 60)}"`, file: relPath, line, fixHint: `Prefix summary with "[${commandName}]" — e.g. summary: "[${commandName}] OK".`, }); } const isFailure = violation.exitCodeIsLiteral && violation.exitCodeValue === "1"; if (isFailure && (!violation.nextStepsPresent || returnText.includes("nextSteps: []"))) { diagnostics.push({ ruleId: "CMD-OUTPUT-03", severity: "error", message: `Failure return path (exitCode: 1) has no nextSteps in ${relPath}:${line}`, file: relPath, line, fixHint: `Add nextSteps: [{ action: "...", kind: "required" }] to the return object.`, }); } } return diagnostics; } async function scanDirectory( dir: string, workspaceRoot: string, io: WorkspaceIO = createDefaultIO().io, ): Promise<{ diagnostics: Diagnostic[]; scannedFiles: number }> { let scannedFiles = 0; const allDiagnostics: Diagnostic[] = []; const entries = await io.readdir(dir).catch(() => undefined); if (entries === undefined) { allDiagnostics.push({ ruleId: "CMD-SCAN-01", severity: "warning", message: `Directory could not be listed and was skipped: ${relative(workspaceRoot, dir)}`, file: relative(workspaceRoot, dir), fixHint: `Fix the unreadable directory — the scanner cannot attest coverage over inputs it cannot list (RFC-1304).`, }); return { diagnostics: allDiagnostics, scannedFiles }; } for (const entry of entries) { const fullPath = join(dir, entry.name); if (entry.isDirectory) { if (EXCLUDE_DIRS.has(entry.name)) continue; const subResult = await scanDirectory(fullPath, workspaceRoot, io); scannedFiles += subResult.scannedFiles; allDiagnostics.push(...subResult.diagnostics); } else if (entry.name.endsWith(".ts") && !shouldExcludeFile(entry.name)) { scannedFiles++; const fileDiagnostics = await scanFile(fullPath, workspaceRoot, io); allDiagnostics.push(...fileDiagnostics); } } return { diagnostics: allDiagnostics, scannedFiles }; } function defaultFailNextSteps(): KernelNextStep[] { return [ { action: `Fix the CMD-OUTPUT-* violations reported by werkstatt.commands.validate, then re-run the command`, kind: "required", }, ]; } export async function runCommandsValidate( workspaceRoot: string, mode: "error" | "warning" = "error", io: WorkspaceIO = createDefaultIO().io, options: { strict?: boolean; baseline?: ValidationBaseline } = {}, ): Promise> { const scanRoots = [ join(workspaceRoot, "packages", "werkstatt-engine", "src"), join(workspaceRoot, "packages", "werkstatt-site", "src"), join(workspaceRoot, "packages", "werkstatt-shared", "src"), ]; let allDiagnostics: Diagnostic[] = []; let totalScanned = 0; for (const root of scanRoots) { const result = await scanDirectory(root, workspaceRoot, io); allDiagnostics.push(...result.diagnostics); totalScanned += result.scannedFiles; } // RFC-1304 CMD-SCAN-01: a validator that scanned zero files would attest a // clean corpus while covering nothing — fail closed on an empty input set. if (totalScanned === 0) { allDiagnostics.push({ ruleId: "CMD-SCAN-01", severity: "error", message: `No source files were scanned under the expected package roots — workspace layout drift or glob failure`, fixHint: `Check packages/*/src exists and is readable; the validator cannot attest coverage over an empty corpus (RFC-1304).`, }); } if (mode === "warning") { allDiagnostics = allDiagnostics.map((d) => ({ ...d, severity: "warning" as const, })); } // RFC-1316 delta mode: baseline-listed errors downgrade to info (known-debt); // --strict or a missing/unreadable baseline leaves everything blocking. const partitioned = applyValidationBaseline(COMMAND_NAME, allDiagnostics, options); allDiagnostics = partitioned.diagnostics; const debtCount = partitioned.debt; const baselineRef = partitioned.baselineRef; const errorCount = allDiagnostics.filter((d) => d.severity === "error").length; const hasErrors = errorCount > 0; const status: CommandsValidateResult["status"] = hasErrors ? "fail" : "pass"; const result: CommandsValidateResult = { command: COMMAND_NAME, status, diagnostics: allDiagnostics, scannedFiles: totalScanned, ...(debtCount > 0 ? { debtViolations: debtCount } : {}), ...(baselineRef ? { baselineRef } : {}), }; const debtSuffix = debtCount > 0 ? `, ${debtCount} known-debt` : ""; return { data: result, exitCode: hasErrors ? 1 : 0, summary: `[${COMMAND_NAME}] ${allDiagnostics.length} violation${allDiagnostics.length === 1 ? "" : "s"} across ${totalScanned} files scanned${debtSuffix}`, nextSteps: hasErrors ? defaultFailNextSteps() : undefined, }; }