Files
confidence-engine/scripts/start-case-experiment-helper.cjs
T

140 lines
5.0 KiB
JavaScript
Executable File

#!/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 "<scenario text>"
* 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 \"<scenario>\" [--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);
}
})();