#!/usr/bin/env node /** * Minimal startCase (initial decomposition) experiment harness. * * Loads .env.local, invokes startCase() through the real production path, * and prints a structured result for semantic evaluation. * * Usage: * node scripts/start-case-experiment-helper.cjs "" * node scripts/start-case-experiment-helper.cjs --file scenario.json * * Exit codes: * 0 — success (structured result printed to stdout) * 1 — execution failure or invalid input */ // Minimal .env.local loader — no external dependency required. // Parses KEY=VALUE lines, skips comments (#) and blank lines. (function loadDotEnvLocal() { const fs = require("fs"); const path = require("path"); const envPath = path.resolve(__dirname, "..", ".env.local"); try { const raw = fs.readFileSync(envPath, "utf-8"); for (const line of raw.split("\n")) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith("#")) continue; const eqIdx = trimmed.indexOf("="); if (eqIdx <= 0) continue; const key = trimmed.slice(0, eqIdx).trim(); const value = trimmed.slice(eqIdx + 1).trim(); if (!(key in process.env)) { process.env[key] = value; } } } catch { // .env.local missing — proceed with whatever is already set. } })(); function assertRequiredEnv(name) { const value = process.env[name]; if (!value) { throw new Error( `Live experiment requires ${name}. Set it in .env.local.\n` + `Found: OLLAMA_BASE_URL=${process.env.OLLAMA_BASE_URL ?? "(missing)"}, ` + `OLLAMA_MODEL=${process.env.OLLAMA_MODEL ?? "(missing)"}` ); } return value; } function readScenarioInput(argv) { if (argv.includes("--file")) { const idx = argv.indexOf("--file"); if (idx + 1 >= argv.length) throw new Error("--file requires a path argument"); const fs = require("fs"); const path = argv[idx + 1]; return JSON.parse(fs.readFileSync(path, "utf-8")); } // Use all positional args as the scenario text (join with space) const startIdx = argv.findIndex(a => a !== "" && !a.startsWith("-") && a !== "--"); if (startIdx === -1 || startIdx >= argv.length) throw new Error("Usage: node scripts/start-case-experiment-helper.cjs \"\" [--file path.json]"); return { scenario: argv.slice(startIdx).join(" ") }; } async function runStartCaseExperiment(scenarioInput) { // Deterministic mode: skip environment checks and use inline test double. // Controlled via START_CASE_EXPERIMENT_HELPER_MOCK=1 for standalone apparatus tests. const isMock = process.env.START_CASE_EXPERIMENT_HELPER_MOCK === "1"; let startCase; if (isMock) { startCase = async (body) => { // Deterministic inline test double — no live model calls. const shouldFail = process.env.START_CASE_EXPERIMENT_HELPER_FORCE_FAIL === "1"; if (shouldFail) { return { success: false, error: "deterministic mock failure", statusCode: 500 }; } return { success: true, situationGraph: { nodes: [], edges: [], activeUnknownNodeId: null, resolvedNodeIds: [], currentSummary: "test" }, assessment: { phase: "initial", progress: 0 }, selectedQuestion: null, summary: null, diagnostics: { validationStatus: "mock", modelName: "inline-test-double", graphReferenceValidation: { valid: true, errors: [] } }, }; }; } else { const baseUrl = assertRequiredEnv("OLLAMA_BASE_URL"); const model = assertRequiredEnv("OLLAMA_MODEL"); if (baseUrl === "http://localhost:11434" || baseUrl === "http://127.0.0.1:11434") { throw new Error( `Live experiment harness refuses to use localhost fallback. ` + `OLLAMA_BASE_URL=${baseUrl}. Configure a real host in .env.local.` ); } const { startCase: _sc } = await import("../../lib/graph/orchestrator.js"); startCase = _sc; } const endToEndStartedAt = Date.now(); const result = await startCase(scenarioInput); const endToEndElapsedMs = Date.now() - endToEndStartedAt; return { success: result.success, summary: result.summary ?? null, situationGraphNodeCount: result.situationGraph?.nodes?.length ?? 0, situationGraphEdgeCount: result.situationGraph?.edges?.length ?? 0, selectedQuestion: result.selectedQuestion?.question ?? null, assessmentPhase: result.assessment?.phase ?? null, assessmentProgress: result.assessment?.progress ?? null, endToEndElapsedMs, diagnostics: result.diagnostics ?? null, error: result.success ? null : (result.error ?? "unknown"), statusCode: result.statusCode ?? (result.success ? 200 : 500), }; } (async () => { try { const scenarioInput = readScenarioInput(process.argv); const result = await runStartCaseExperiment(scenarioInput); console.log(JSON.stringify(result, null, 2)); process.exit(result.success ? 0 : 1); } catch (e) { const failure = { success: false, error: e.message ?? "unknown", statusCode: 1, }; console.log(JSON.stringify(failure)); process.exit(1); } })();