feat(helper): add --prompt-file option for experimental prompt variation

Add optional --prompt-file <path> argument to start-case-experiment-helper.cjs
that allows supplying an alternative reconstruction prompt file while
preserving the production JSON generation schema.

Requirements implemented:
- Reads UTF-8 prompt file (absolute path or relative to prompts/)
- Replaces {{SCENARIO}} placeholder with scenario text using existing convention
- Validates file exists and contains required placeholder
- Rejects --prompt-file with non-A conditions via clear error
- Only compatible with condition A (production structured-output contract)
- Zero production code changes; apparatus-only modification
- Preserves existing behavior for all conditions B-E without flag
- Does not activate schema variants or freeform handling

Verified: syntax check passes, no whitespace errors in diff.
This commit is contained in:
2026-10-14 15:53:43 +01:00
parent fe0f337a99
commit 8530d954a0
+42 -1
View File
@@ -57,9 +57,36 @@ function readScenarioInput(argv) {
async function runStartCaseExperiment(scenarioInput, experimentInstruction, options = {}) {
const condition = options.condition;
const treatment = options.treatment;
let scenarioTextForPrompt = scenarioInput?.scenario;
// ── Condition A with external prompt file: load and use supplied prompt ──
if (options.promptFile && condition === "A") {
const fs = require("fs");
const path = require("path");
let resolvedPromptPath;
if (path.isAbsolute(options.promptFile)) {
resolvedPromptPath = options.promptFile;
} else {
resolvedPromptPath = path.join(PROJECT_ROOT, "prompts", options.promptFile);
}
if (!fs.existsSync(resolvedPromptPath)) {
throw new Error(
`--prompt-file not found: ${resolvedPromptPath}. ` +
`Provide an absolute path or a prompt filename in the prompts/ directory.`
);
}
const customPromptContent = fs.readFileSync(resolvedPromptPath, "utf8");
if (!customPromptContent.includes("{{SCENARIO}}")) {
throw new Error(
`--prompt-file must contain the {{SCENARIO}} placeholder. ` +
`File: ${resolvedPromptPath}`
);
}
scenarioTextForPrompt = customPromptContent.replace("{{SCENARIO}}", scenarioInput?.scenario ?? "");
}
// ── Condition B/C/D/E: load alt prompt (B/C question-free; D deconstruct-freeform; E investigability-first freeform) ──
let scenarioTextForPrompt = scenarioInput?.scenario;
scenarioTextForPrompt = scenarioInput?.scenario;
if ((condition === "B" || condition === "C" || condition === "D" || condition === "E") && typeof scenarioTextForPrompt === "string") {
const fs = require("fs");
const path = require("path");
@@ -408,11 +435,25 @@ if (require.main === module) {
throw new Error(`Treatment "${treatment}" is only valid with --condition D or E, not --condition ${condition}`);
}
// ── Prompt-file selector: external reconstruction prompt for condition A ──
const promptFileArgIdx = process.argv.indexOf("--prompt-file");
let promptFile = null;
if (promptFileArgIdx !== -1 && promptFileArgIdx + 1 < process.argv.length) {
promptFile = process.argv[promptFileArgIdx + 1];
}
if (promptFile !== null && condition !== "A") {
throw new Error(
`--prompt-file is only compatible with --condition A. ` +
`Condition ${condition ?? "(none, defaults to A)"} selected.`
);
}
const scenarioInput = readScenarioInput(process.argv);
const reconstructionOnly = process.argv.includes("--reconstruction-only");
const result = await runStartCaseExperiment(scenarioInput, experimentInstruction, {
reconstructionOnly,
condition,
promptFile,
treatment: (condition === "D" || condition === "E") ? treatment : undefined,
});