From 8530d954a0b9389ea48bacdbf8b3d3dda504ebdc Mon Sep 17 00:00:00 2001 From: robbond Date: Wed, 14 Oct 2026 15:53:43 +0100 Subject: [PATCH] feat(helper): add --prompt-file option for experimental prompt variation Add optional --prompt-file 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. --- scripts/start-case-experiment-helper.cjs | 43 +++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/scripts/start-case-experiment-helper.cjs b/scripts/start-case-experiment-helper.cjs index 1fde765..7b1dbf5 100755 --- a/scripts/start-case-experiment-helper.cjs +++ b/scripts/start-case-experiment-helper.cjs @@ -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, });