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