(value: string)
| 246 | } |
| 247 | |
| 248 | async function validateTypeScriptString(value: string): Promise<ValidationReport> { |
| 249 | // Models often emit multi-file artifacts as one block with `// path:` |
| 250 | // headers between sections. tsc parses that as a single source file and |
| 251 | // chokes on the file-path comments. Rather than fail the whole call we |
| 252 | // pick the LARGEST contiguous section between such headers and validate |
| 253 | // just that — the model can return a multi-file plan as long as at least |
| 254 | // one section is internally consistent. |
| 255 | // |
| 256 | // Models also commonly wrap their output in markdown code fences. We |
| 257 | // extract the fenced block first when present so the surrounding prose |
| 258 | // (which is never valid TS) doesn't drag down the validator. If multiple |
| 259 | // fences exist, we pick the largest typescript-tagged one. |
| 260 | const candidate = pickLargestTsSection(extractFencedTypeScript(value)); |
| 261 | let ts: typeof import("typescript") | null = null; |
| 262 | try { |
| 263 | // eslint-disable-next-line @typescript-eslint/no-require-imports |
| 264 | ts = require("typescript") as typeof import("typescript"); |
| 265 | } catch { |
| 266 | counter("cognition.validate.skipped", { mode: "ast_compiles" }); |
| 267 | return { ok: true, issues: ["typescript module not installed — ast_compiles skipped"] }; |
| 268 | } |
| 269 | |
| 270 | const sf = ts.createSourceFile("__model_output.ts", candidate, ts.ScriptTarget.Latest, true); |
| 271 | const issues: string[] = []; |
| 272 | |
| 273 | // Parse diagnostics — unrecoverable syntax errors. tsc's API is loose |
| 274 | // with naming; cast for the diagnostic array we care about. |
| 275 | const parseDiags = (sf as unknown as { parseDiagnostics?: ReadonlyArray<import("typescript").Diagnostic> }).parseDiagnostics ?? []; |
| 276 | for (const d of parseDiags) { |
| 277 | issues.push(ts.flattenDiagnosticMessageText(d.messageText, "\n")); |
| 278 | } |
| 279 | |
| 280 | if (issues.length === 0) { |
| 281 | // Run a real program over the source so we get semantic diagnostics |
| 282 | // (undeclared names, type-only-as-value, duplicate identifiers, etc.). |
| 283 | // We keep noResolve:true so missing-module errors are recoverable; we |
| 284 | // filter those out below and only fail on real bugs in the user's code. |
| 285 | try { |
| 286 | const compilerOptions: import("typescript").CompilerOptions = { |
| 287 | target: ts.ScriptTarget.ES2022, |
| 288 | module: ts.ModuleKind.CommonJS, |
| 289 | strict: false, |
| 290 | noResolve: true, |
| 291 | skipLibCheck: true, |
| 292 | skipDefaultLibCheck: true, |
| 293 | noEmit: true, |
| 294 | allowJs: true, |
| 295 | // Don't trip on incidental noise from one-pass model output: |
| 296 | noUnusedLocals: false, |
| 297 | noUnusedParameters: false, |
| 298 | noImplicitAny: false, |
| 299 | }; |
| 300 | // Synthesise a tiny lib.d.ts so global names like `console`, `Promise`, |
| 301 | // `process`, `Map`, `Array`, etc. resolve cleanly. Without this every |
| 302 | // builtin reference becomes a TS2304 false positive. |
| 303 | const stubLib = [ |
| 304 | // Common globals as values. |
| 305 | "declare var console: { log(...a: unknown[]): void; warn(...a: unknown[]): void; error(...a: unknown[]): void; info(...a: unknown[]): void; debug(...a: unknown[]): void };", |
no test coverage detected