#!/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 */ // Use the standard environment loader already available to this Next repository. // loadEnvConfig mutates process.env in-place (same semantics as previous bespoke parser). const path = require("path"); const PROJECT_ROOT = path.resolve(__dirname, ".."); (function loadEnvironment() { try { const { loadEnvConfig } = require("@next/env"); loadEnvConfig(PROJECT_ROOT, undefined, { logOutput: "none" }, false); } catch { // loader unavailable — 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, experimentInstruction) { // ── Experiment seam bridge: supply instruction to production path ── const hadEnvVar = process.env.RECONSTRUCTION_EXPERIMENT_INSTRUCTION != null; const previousEnvValue = process.env.RECONSTRUCTION_EXPERIMENT_INSTRUCTION; if (experimentInstruction) { process.env.RECONSTRUCTION_EXPERIMENT_INSTRUCTION = experimentInstruction; } let startCase; const isMock = process.env.START_CASE_EXPERIMENT_HELPER_MOCK === "1"; if (isMock) { // Deterministic mode: skip environment checks and use inline test double. 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(PROJECT_ROOT + "/lib/graph/orchestrator.js"); startCase = _sc; } let result; const endToEndStartedAt = Date.now(); try { result = await startCase(scenarioInput); } finally { // Clean up experiment env var after execution regardless of outcome if (experimentInstruction && !hadEnvVar) { delete process.env.RECONSTRUCTION_EXPERIMENT_INSTRUCTION; } else if (!hadEnvVar) { // was not set before and not set during — ensure it stays unset delete process.env.RECONSTRUCTION_EXPERIMENT_INSTRUCTION; } else if (!experimentInstruction && hadEnvVar) { // restore original value that existed before if (previousEnvValue === undefined) { delete process.env.RECONSTRUCTION_EXPERIMENT_INSTRUCTION; } else { process.env.RECONSTRUCTION_EXPERIMENT_INSTRUCTION = previousEnvValue; } } } 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: Date.now() - endToEndStartedAt, diagnostics: result.diagnostics ?? null, error: result.success ? null : (result.error ?? "unknown"), statusCode: result.statusCode ?? (result.success ? 200 : 500), }; } (async () => { // Import-only mode: prove real production seam without invoking startCase. // Used only for deterministic apparatus verification of the import path. if (process.env.START_CASE_EXPERIMENT_HELPER_IMPORT_ONLY === "1") { try { const mod = await import(PROJECT_ROOT + "/lib/graph/orchestrator.js"); const startCaseResolved = typeof mod.startCase === "function"; const output = JSON.stringify({ success: true, mode: "import-only", startCaseResolved, }); console.log(output); process.exit(0); } catch (e) { let failureReason; if (e.code === "ERR_REQUIRE_ESM" || e.message.includes("ERR_REQUIRE_ESM")) { failureReason = "CJS cannot import ESM module"; } else if (e.code === "ERR_MODULE_NOT_FOUND" || e.code === "ERR_UNSUPPORTED_DIR_IMPORT") { failureReason = `Module resolution failed: ${e.message}`; } else { failureReason = `Import error: ${e.message}`; } const output = JSON.stringify({ success: false, mode: "import-only", startCaseResolved: false, failureReason }); console.log(output); process.exit(1); } } try { // ── Experiment seam: parse optional experiment instruction ── const experimentIdx = process.argv.indexOf("--experiment-instruction"); let experimentInstruction = null; if (experimentIdx !== -1 && experimentIdx + 1 < process.argv.length) { experimentInstruction = process.argv[experimentIdx + 1]; } const scenarioInput = readScenarioInput(process.argv); const result = await runStartCaseExperiment(scenarioInput, experimentInstruction); 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); } })();