#!/usr/bin/env node // Unit tests for the pure helpers in lib/. These run without the pi binary, // so they catch regressions close to the source instead of only through the // end-to-end integration test. import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import { countJsonlRows, flagOn, parseOptions, readJsonl, rel, shellSplit } from "../lib/cli.ts"; import { buildForkPrompt, formatObservedListing, latestForkableRow, observedTurnText, redactSecrets, runMachineCli, } from "../lib/fork.ts"; import { buildCorrectionPrompt, buildTaggedExampleRow, displayRowId, findRowById, newMachineRowId, } from "../lib/examples.ts"; import { machineDataFile, machineProjectDataDir } from "../lib/storage.ts"; import { isHarnessInjectedUserText, lastTaggedTurnFromLetta, lastUserTurnLettaMessages, recordsFromLettaMessages, } from "../lib/letta-trajectory.ts"; import { applyAssistantRewrite, extractMessageText, isoTimestamp, lastTaggedTurn, lastUserTurnEntries, recordsFromSessionEntries, trajectoryToMessages, } from "../lib/trajectory.ts"; import { formatMachineStatus, joinMachineStatusLine, resolveLastTurnStatus } from "../lib/statusline.ts"; import { builtinTinkerChatModels, isTinkerNativeId, listTinkerChatModels } from "../lib/tinker-catalog.ts"; let checks = 0; function assert(condition, message) { if (!condition) throw new Error(message); checks++; } function eq(actual, expected, message) { const a = JSON.stringify(actual); const b = JSON.stringify(expected); assert(a === b, `${message}: expected ${b}, got ${a}`); } // --- shellSplit --- eq(shellSplit(`--as "Go to Settings → Security."`), ["--as", "Go to Settings → Security."], "double-quoted value should keep spaces"); eq(shellSplit("--note 'too terse'"), ["--note", "too terse"], "single-quoted value should keep spaces"); eq(shellSplit("a\\ b c"), ["a b", "c"], "backslash should escape spaces"); eq(shellSplit("it's 'fine'"), ["its fine"], "apostrophes open quoting, matching POSIX shell behavior"); eq(shellSplit(""), [], "empty input should split to nothing"); eq(shellSplit(" "), [], "whitespace-only input should split to nothing"); // --- parseOptions --- eq(parseOptions(["file.jsonl"]), { positional: ["file.jsonl"], options: {} }, "plain tokens are positional"); eq(parseOptions(["--force"]), { positional: [], options: { force: true } }, "lone flag becomes true"); eq(parseOptions(["--note", "too", "terse"]), { positional: ["terse"], options: { note: "too" } }, "flag consumes the next non-option token"); eq(parseOptions(["--steps=20", "--next-flag"]), { positional: [], options: { steps: "20", "next-flag": true } }, "equals form and flag-followed-by-flag"); eq(parseOptions(["--trace", "why", "--reasoning"]), { positional: [], options: { trace: "why", reasoning: true } }, "a flag consumes the next non-option token, even as a stray value"); // --- flagOn --- eq(flagOn({ trace: true }, "trace"), { on: true }, "true value is on"); eq(flagOn({ trace: "false" }, "trace"), { on: false }, "string false is off"); eq(flagOn({ trace: "why" }, "trace"), { on: true, leftover: "why" }, "stray value is returned as leftover"); eq(flagOn({}, "trace"), { on: false }, "missing value is off"); // --- rel --- eq(rel("/a/b", "/a/b/c/train.jsonl"), "c/train.jsonl", "paths under cwd are relative"); eq(rel("/a/b", "/elsewhere/train.jsonl"), "/elsewhere/train.jsonl", "paths outside cwd are absolute"); // --- readJsonl / countJsonlRows --- const tmp = mkdtempSync(path.join(os.tmpdir(), "machine-unit-")); const jsonl = path.join(tmp, "rows.jsonl"); writeFileSync(jsonl, [ JSON.stringify({ n: 1 }), "", JSON.stringify({ n: 2 }), "not json {", JSON.stringify({ n: 3 }), "", ].join("\n") + "\n"); eq(readJsonl(jsonl).map((r) => r.n), [1, 2, 3], "corrupt lines are skipped, not fatal"); eq(readJsonl(path.join(tmp, "missing.jsonl")), [], "missing files read as empty"); assert(countJsonlRows(jsonl) === 4, "row counts should not require parseable JSON"); // --- extractMessageText --- eq(extractMessageText("plain"), "plain", "string content passes through"); eq(extractMessageText([{ type: "text", text: "a" }, { type: "thinking", thinking: "b" }, { type: "text", text: "c" }]), "a\nc", "only text blocks are joined"); eq(extractMessageText([{ type: "toolCall", name: "bash" }]), "", "non-text blocks contribute nothing"); eq(extractMessageText(undefined), "", "missing content is empty"); eq(extractMessageText({ text: "nested" }), "nested", "object text fields unwrap"); // --- isoTimestamp --- eq(isoTimestamp("2026-01-01T00:00:01.000Z"), "2026-01-01T00:00:01.000Z", "ISO strings normalize"); eq(isoTimestamp(2), "1970-01-01T00:00:00.002Z", "numeric epochs (milliseconds) normalize"); eq(isoTimestamp("garbage", "fallback"), "fallback", "unparseable values fall back"); assert(isoTimestamp(undefined).endsWith("Z"), "missing timestamps default to now"); // --- session fixtures (same shape as real session JSONL) --- const session = [ { type: "session", id: "s", timestamp: "2026-01-01T00:00:00.000Z" }, { type: "message", id: "u1", timestamp: "2026-01-01T00:00:01.000Z", message: { role: "user", content: [{ type: "text", text: "How do I reset my password?" }], timestamp: 1 } }, { type: "message", id: "a1", timestamp: "2026-01-01T00:00:02.000Z", message: { role: "assistant", content: [{ type: "thinking", thinking: "Check docs." }, { type: "text", text: "Checking." }, { type: "toolCall", id: "call_1", name: "bash", arguments: { command: "which julia" } }], timestamp: 2 } }, { type: "message", id: "t1", timestamp: "2026-01-01T00:00:03.000Z", message: { role: "toolResult", toolCallId: "call_1", content: [{ type: "text", text: "/usr/bin/julia" }], isError: false, timestamp: 3 } }, { type: "message", id: "a2", timestamp: "2026-01-01T00:00:04.000Z", message: { role: "assistant", content: [{ type: "text", text: "Julia is installed." }], timestamp: 4 } }, ]; // --- lastTaggedTurn / lastUserTurnEntries --- eq(lastTaggedTurn(session), { userId: "u1", assistantId: "a2", user: "How do I reset my password?", assistant: "Julia is installed." }, "last user/assistant pair is found"); eq(lastTaggedTurn([{ type: "message", id: "u1", message: { role: "user", content: "hi" } }]), undefined, "no pair without an assistant"); eq(lastUserTurnEntries(session).map((e) => e.id), ["u1", "a1", "t1", "a2"], "the turn span starts at the last user message"); // --- recordsFromSessionEntries --- const records = recordsFromSessionEntries(session); eq(records.map((r) => [r.role, r.content === null ? "tool_calls" : typeof r.content === "string" ? "text" : "?"]), [ ["user", "text"], ["reasoning", "text"], ["assistant", "text"], ["assistant", "tool_calls"], ["tool", "text"], ["assistant", "text"], ], "records preserve the full trajectory shape"); const toolCallRecord = records.find((r) => r.role === "assistant" && r.content === null); eq(toolCallRecord.tool_calls, [{ id: "call_1", name: "bash", args: JSON.stringify({ command: "which julia" }) }], "tool call arguments are serialized to a JSON string"); const toolRecord = records.find((r) => r.role === "tool"); eq(toolRecord, { role: "tool", tool_call_id: "call_1", content: "/usr/bin/julia", timestamp: "2026-01-01T00:00:03.000Z" }, "tool results keep their call id and text"); const failing = recordsFromSessionEntries([ { type: "message", id: "t2", timestamp: "2026-01-01T00:00:05.000Z", message: { role: "toolResult", toolCallId: "call_1", content: [{ type: "text", text: "boom" }], isError: true, timestamp: 5 } }, ]); assert(failing[0].content.startsWith("Error:"), "error tool results are prefixed"); eq(recordsFromSessionEntries([{ type: "message", id: "u9", message: { role: "user", content: [{ type: "text", text: "/machine data" }], timestamp: 9 } }]).length, 1, "slash commands are still user records"); // --- applyAssistantRewrite --- const rewritten = applyAssistantRewrite(records, "Rewritten answer."); assert(rewritten.at(-1).content === "Rewritten answer.", "the last assistant text is replaced"); assert(rewritten.length === records.length && records.at(-1).content === "Julia is installed.", "the input records are not mutated"); eq(applyAssistantRewrite(records, ""), records, "empty rewrites are no-ops"); // --- trajectoryToMessages --- const messages = trajectoryToMessages(records); const toolCallMessage = messages.find((m) => Array.isArray(m.tool_calls)); eq(toolCallMessage, { role: "assistant", content: "", tool_calls: [{ id: "call_1", type: "function", function: { name: "bash", arguments: JSON.stringify({ command: "which julia" }) } }], }, "OpenAI-style tool_calls are emitted"); eq(messages.find((m) => m.role === "tool"), { role: "tool", content: "/usr/bin/julia", tool_call_id: "call_1" }, "tool results become role:tool messages"); assert(!messages.some((m) => m.role === "reasoning"), "reasoning is dropped from chat messages"); eq(messages[0], { role: "user", content: "How do I reset my password?" }, "the opening user message is preserved"); // --- Letta Code getHistory() reconstruction --- const lettaHistory = [ { id: "u1", date: "2026-01-01T00:00:01.000Z", message_type: "user_message", content: [{ type: "text", text: "list files" }] }, { id: "r1", date: "2026-01-01T00:00:02.000Z", message_type: "reasoning_message", reasoning: "I should inspect the directory." }, { id: "t1", date: "2026-01-01T00:00:03.000Z", message_type: "approval_request_message", tool_call: { tool_call_id: "call_1", name: "Bash", arguments: "{\"command\":\"ls\"}" } }, { id: "tr1", date: "2026-01-01T00:00:04.000Z", message_type: "tool_return_message", tool_call_id: "call_1", tool_return: "README.md", status: "success" }, { id: "a1", date: "2026-01-01T00:00:05.000Z", message_type: "assistant_message", content: [{ type: "text", text: "There is a README." }] }, ]; eq(lastTaggedTurnFromLetta(lettaHistory), { userId: "u1", assistantId: "a1", user: "list files", assistant: "There is a README." }, "Letta history finds the last user/assistant pair"); eq(lastUserTurnLettaMessages(lettaHistory).map((m) => m.id), ["u1", "r1", "t1", "tr1", "a1"], "Letta turn span starts at the last user message"); const lettaRecords = recordsFromLettaMessages(lettaHistory); eq(lettaRecords.map((r) => r.role), ["user", "reasoning", "assistant", "tool", "assistant"], "Letta records preserve the full trajectory shape"); eq(lettaRecords[2].tool_calls, [{ id: "call_1", name: "Bash", args: "{\"command\":\"ls\"}" }], "Letta approval_request_message becomes a tool-call record"); eq(lettaRecords[3], { role: "tool", tool_call_id: "call_1", content: "README.md", timestamp: "2026-01-01T00:00:04.000Z" }, "Letta tool_return_message becomes a tool record"); assert(isHarnessInjectedUserText("\ntask_5"), "task notifications are harness-injected"); assert(isHarnessInjectedUserText("\nThis is an automated message"), "system reminders are harness-injected"); assert(!isHarnessInjectedUserText("list files"), "ordinary user text is not harness-injected"); const lettaHistoryWithNotification = [ { id: "u0", date: "2026-01-01T00:00:00.000Z", message_type: "user_message", content: "review the release" }, { id: "a0", date: "2026-01-01T00:00:00.500Z", message_type: "assistant_message", content: "I will review it." }, { id: "uN", date: "2026-01-01T00:00:06.000Z", message_type: "user_message", content: "\ntask_5\nAgent completed" }, { id: "aN", date: "2026-01-01T00:00:07.000Z", message_type: "assistant_message", content: "The review is done." }, ]; eq(lastTaggedTurnFromLetta(lettaHistoryWithNotification), { userId: "u0", assistantId: "aN", user: "review the release", assistant: "The review is done." }, "Letta last turn skips task-notification user rows"); eq(lastUserTurnLettaMessages(lettaHistoryWithNotification).map((m) => m.id), ["u0", "a0", "uN", "aN"], "Letta turn span starts at the last real user message"); // --- SFT scaffold contract --- // The /train sft scaffold is rendered by string substitution: each host // (extensions/train.ts for Pi, mods/machine.ts for Letta) passes a fixed // replacement set to templates/train_sft.py. These checks pin the three // sides together so a new template placeholder cannot ship without both // hosts substituting it (the original num_epochs bug, inverted). import { readFileSync } from "node:fs"; const repoRoot = path.resolve(import.meta.dirname, ".."); const templatePath = path.join(repoRoot, "templates", "train_sft.py"); const template = readFileSync(templatePath, "utf8"); const templatePlaceholders = [...new Set([...template.matchAll(/__[A-Z_]+__/g)].map((m) => m[0]))].sort(); function renderKeysFor(hostFile) { const source = readFileSync(path.join(repoRoot, hostFile), "utf8"); const match = source.match(/renderTemplate\(\s*"train_sft\.py"\s*,\s*\{([\s\S]*?)\}\s*\)/); assert(match, `${hostFile} should call renderTemplate("train_sft.py", {...})`); return [...new Set([...match[1].matchAll(/__[A-Z_]+__/g)].map((m) => m[0]))].sort(); } eq(templatePlaceholders.length >= 8, true, "train_sft template should have its expected placeholders"); eq(renderKeysFor("extensions/train.ts"), templatePlaceholders, "Pi /train sft substitutes exactly the template placeholders"); eq(renderKeysFor("mods/machine.ts"), templatePlaceholders, "Letta /train sft substitutes exactly the template placeholders"); assert(readFileSync(path.join(repoRoot, "extensions", "train.ts"), "utf8").includes("num_epochs: ${numEpochs}"), "Pi tinker.yaml should record num_epochs"); assert(readFileSync(path.join(repoRoot, "mods", "machine.ts"), "utf8").includes("num_epochs: ${numEpochs}"), "Letta tinker.yaml should record num_epochs"); // Smoke-render the template the way both hosts do and require a clean result. const replacements = Object.fromEntries(templatePlaceholders.map((key) => [key, { __MODEL__: '"m"', __MAX_LENGTH__: "32768", __BATCH_SIZE__: "8", __DATA_FILE__: '"d.jsonl"', __TEST_SIZE__: "0", __LOG_PATH__: '"logs/x"', __LEARNING_RATE__: "2e-4", __NUM_EPOCHS__: "10", __MAX_STEPS__: "60" }[key]])); let smokeRendered = template; for (const [key, value] of Object.entries(replacements)) smokeRendered = smokeRendered.replaceAll(key, value); eq(/[A-Z]__[A-Z_]+__|__[A-Z_]+__/.test(smokeRendered), false, "rendered scaffold should contain no unsubstituted placeholders"); assert(smokeRendered.includes('"num_epochs": 10,'), "rendered scaffold should carry the requested epoch count"); // --- statusline --- function statusText(input) { return joinMachineStatusLine(formatMachineStatus(input), (_tone, text) => text); } const noTurn = { state: "none" }; eq(statusText({ autoTrace: false, student: false, lastTurn: noTurn, train: 0, observed: 0 }), undefined, "empty state hides the footer"); eq(statusText({ autoTrace: false, student: false, lastTurn: noTurn, train: 3, observed: 0 }), "⚙ 3 train", "quiet train count when nothing else is pending"); eq(statusText({ autoTrace: true, student: false, lastTurn: noTurn, train: 3, observed: 4 }), "⚙ auto · 3 train · 4 obs", "auto headline plus quiet counts"); eq(statusText({ autoTrace: true, student: false, lastTurn: { state: "untagged" }, train: 3, observed: 0 }), "⚙ last turn untagged · 3 train", "untagged last turn wins over auto"); eq(statusText({ autoTrace: false, student: false, lastTurn: { state: "good" }, train: 4, observed: 0 }), "⚙ last: good · 4 train", "just-tagged good example"); eq(statusText({ autoTrace: false, student: false, lastTurn: { state: "good", trace: true }, train: 4, observed: 0 }), "⚙ last: good trace · 4 train", "just-tagged good trace"); eq(statusText({ autoTrace: false, student: false, lastTurn: { state: "rejected" }, train: 3, observed: 0 }), "⚙ last: rejected · 3 train", "just-tagged rejected example"); eq(statusText({ autoTrace: true, student: true, lastTurn: noTurn, train: 3, observed: 4 }), "⚙ student · 3 train · 4 obs", "student lane wins over auto"); eq(statusText({ autoTrace: false, student: true, lastTurn: noTurn, train: 0, observed: 0 }), "⚙ student", "student lane alone is still worth showing"); eq(statusText({ autoTrace: true, student: false, lastTurn: noTurn, train: 0, observed: 0 }), "⚙ auto", "auto alone is still worth showing"); eq(statusText({ autoTrace: false, student: false, lastTurn: { state: "untagged" }, train: 0, observed: 4 }), "⚙ last turn untagged · 4 obs", "untagged with only observed rows"); eq(resolveLastTurnStatus({ hasTurn: false }), { state: "none" }, "no turn means no last-turn headline"); eq(resolveLastTurnStatus({ hasTurn: true }), { state: "untagged" }, "a live turn without a judgment is untagged"); eq(resolveLastTurnStatus({ hasTurn: true, label: "good" }), { state: "none" }, "a historical good label is not a just-tagged headline"); eq(resolveLastTurnStatus({ hasTurn: true, assistantId: "a2", lastSave: { assistantId: "a2", tag: "good", trace: true } }), { state: "good", trace: true }, "a matching save is a just-tagged good trace"); eq(resolveLastTurnStatus({ hasTurn: true, assistantId: "a2", lastSave: { assistantId: "a2", tag: "bad", trace: false } }), { state: "rejected" }, "a matching save is a just-tagged rejection"); eq(resolveLastTurnStatus({ hasTurn: true, assistantId: "a3", lastSave: { assistantId: "a2", tag: "good", trace: true } }), { state: "untagged" }, "a save for an older turn does not cover a new one"); eq(resolveLastTurnStatus({ hasTurn: true, assistantId: "a3", label: "good", lastSave: { assistantId: "a2", tag: "good", trace: true } }), { state: "none" }, "a labeled historical turn stays quiet"); // --- tinker catalog --- assert(isTinkerNativeId("Qwen/Qwen3.8-27B:peft:262144"), "Qwen ids are Tinker-native"); assert(isTinkerNativeId("thinkingmachines/Inkling-Small"), "Inkling ids are Tinker-native"); assert(isTinkerNativeId("tinker://abc/sampler_weights/final"), "checkpoint paths are Tinker-native"); assert(!isTinkerNativeId("zai/glm-5.3"), "non-Tinker providers stay prefixed"); const builtinIds = builtinTinkerChatModels().map((model) => model.id); assert(builtinIds.includes("Qwen/Qwen3.8-27B"), "catalog includes Qwen3.8-27B"); assert(builtinIds.includes("Qwen/Qwen3.8-27B:peft:262144"), "catalog includes Qwen3.8-27B 256K"); assert(builtinIds.includes("thinkingmachines/Inkling-Small:peft:262144"), "catalog includes Inkling-Small 256K"); const withCheckpoint = listTinkerChatModels([{ id: "tinker://ckpt", name: "my-sft", contextWindow: 32768, maxTokens: 4096 }]); assert(withCheckpoint.some((model) => model.id === "tinker://ckpt"), "registered checkpoints join the chat catalog"); // --- row ids --- const generatedIds = new Set(Array.from({ length: 40 }, () => newMachineRowId())); assert(generatedIds.size === 40, "new row ids should be unique"); assert([...generatedIds].every((id) => /^[0-9a-f]{12}$/.test(id)), "row ids are 12-character hex"); eq(displayRowId({}), "(no id)", "missing id displays as (no id)"); eq(displayRowId({ id: "c0ffee123456" }), "c0ffee123456", "present id is shown"); const built = buildTaggedExampleRow({ tag: "observed", messages: [{ role: "user", content: "hi" }, { role: "assistant", content: "hello" }] }); assert(typeof built.id === "string" && /^[0-9a-f]{12}$/.test(built.id), "new tagged rows get a stable id"); eq(buildCorrectionPrompt("Write the helper", "use Julia"), "Write the helper\n\nCorrection: use Julia", "correction prompt is original turn plus correction"); // --- machine CLI / fork helpers --- eq( latestForkableRow([ { kind: "auto-compact", tag: "observed", messages: [{ role: "user", content: "old compact" }] }, { kind: "auto-headless", tag: "observed", messages: [{ role: "user", content: "headless" }] }, { kind: "auto-compact", tag: "observed", messages: [{ role: "user", content: "later compact" }] }, ])?.kind, "auto-headless", "fork prefers the latest auto-headless row over a later compact observation", ); eq( observedTurnText({ messages: [ { role: "user", content: "first" }, { role: "assistant", content: "draft" }, { role: "user", content: "real request" }, { role: "assistant", content: [{ type: "text", text: "final answer" }] }, ], }), { user: "real request", assistant: "final answer" }, "fork uses the last user turn and last spoken assistant answer", ); const forkPrompt = buildForkPrompt({ user: "Write the helper", assistant: "I rewrote the file.", correction: "use the existing helper", kind: "auto-headless", note: "auto headless (print)", }); assert(forkPrompt.includes("Write the helper"), "fork prompt includes the original user turn"); assert(forkPrompt.includes("I rewrote the file."), "fork prompt includes the previous assistant answer"); assert(forkPrompt.includes("use the existing helper"), "fork prompt includes the correction"); assert(forkPrompt.includes("not a /good example"), "fork prompt must not treat observed rows as /good"); eq(redactSecrets("token=sk-abcdefghijklmnopqrstuvwxyz and api_key=secret-value"), "token=[redacted] and api_key=[redacted]", "observed listings redact secret-shaped tokens"); const previousDataDir = process.env.MACHINE_DATA_DIR; const cliTmp = mkdtempSync(path.join(os.tmpdir(), "machine-cli-")); const cliCwd = path.join(cliTmp, "demo"); const cliStore = path.join(cliTmp, "store"); mkdirSync(cliCwd, { recursive: true }); process.env.MACHINE_DATA_DIR = cliStore; try { mkdirSync(machineProjectDataDir(cliCwd), { recursive: true, mode: 0o700 }); writeFileSync( machineDataFile(cliCwd, "observed"), [ JSON.stringify({ timestamp: "2026-01-01T00:00:01.000Z", kind: "auto-compact", tag: "observed", note: "auto compact", messages: [{ role: "user", content: "older compact" }, { role: "assistant", content: "compact answer" }], }), JSON.stringify({ timestamp: "2026-01-01T00:00:02.000Z", kind: "auto-headless", tag: "observed", note: "auto headless (print)", messages: [ { role: "user", content: "Write the helper. token=sk-abcdefghijklmnopqrstuvwxyz" }, { role: "assistant", content: "I rewrote everything." }, ], }), ].join("\n") + "\n", { mode: 0o600 }, ); async function captureCli(argv, extra = {}) { let out = ""; let err = ""; const code = await runMachineCli(argv, { cwd: cliCwd, env: { ...process.env, PATH: "" }, stdout: { write(chunk) { out += chunk; return true; } }, stderr: { write(chunk) { err += chunk; return true; } }, spawnPi: false, ...extra, }); return { code, out, err }; } const help = await captureCli(["help"]); assert(help.code === 0 && help.out.includes("machine fork"), "machine help lists fork"); assert(help.out.includes("machine correction --id"), "machine help lists correction --id"); const data = await captureCli(["data"]); assert(data.code === 0 && data.out.includes(machineProjectDataDir(cliCwd)), "machine data prints the project store"); assert(data.out.includes("observed") && data.out.includes("2 rows"), "machine data counts observed rows"); const observed = await captureCli(["observed"]); assert(observed.code === 0, "machine observed succeeds"); assert(observed.out.includes("auto-headless"), "machine observed shows kind"); assert(observed.out.includes("Write the helper"), "machine observed shows truncated user text"); assert(!observed.out.includes("sk-abcdefghijklmnopqrstuvwxyz"), "machine observed does not print secrets"); assert(!observed.out.includes("I rewrote everything"), "machine observed does not dump the assistant answer"); assert(observed.out.includes("(no id)"), "machine observed shows (no id) for legacy rows"); const listing = formatObservedListing(cliCwd, 1); assert(listing.includes("auto-headless") && !listing.includes("older compact"), "observed listing honors n"); const missingPi = await captureCli(["fork", "use", "the", "existing", "helper"], { spawnPi: undefined }); assert(missingPi.code === 1, "machine fork exits 1 when pi is not on PATH"); assert(missingPi.err.includes("use the existing helper"), "fallback prompt includes the correction"); assert(missingPi.err.includes("MACHINE_FORK=1 pi -p"), "fallback prints the pi -p command"); assert(missingPi.err.includes("does not write train.jsonl"), "fork must not treat observed rows as /good"); let spawned; const fakeSpawn = await captureCli(["fork", "use Julia"], { env: { ...process.env, PATH: "/usr/bin" }, spawnPi: async (request) => { spawned = request; return { code: 0, stdout: "ok", stderr: "" }; }, }); assert(fakeSpawn.code === 0, "machine fork succeeds when pi spawn is injected"); assert(spawned?.env.MACHINE_FORK === "1", "child env sets MACHINE_FORK=1"); assert(spawned?.prompt.includes("use Julia"), "spawned pi -p prompt includes the correction"); assert(spawned?.cwd === cliCwd, "spawned pi -p uses process cwd"); assert(!fakeSpawn.out.includes('"tag":"good"'), "fork does not write a /good row"); writeFileSync(machineDataFile(cliCwd, "observed"), [ JSON.stringify({ id: "obsid0000001", timestamp: "2026-01-01T00:00:03.000Z", kind: "auto-headless", tag: "observed", messages: [{ role: "user", content: "from observed" }, { role: "assistant", content: "obs answer" }], }), ].join("\n") + "\n", { mode: 0o600 }); writeFileSync(machineDataFile(cliCwd, "traces"), [ JSON.stringify({ id: "traceid000001", kind: "trace", tag: "good", messages: [{ role: "user", content: "from traces" }, { role: "assistant", content: "trace answer" }], }), JSON.stringify({ id: "sharedid00001", kind: "trace", tag: "good", messages: [{ role: "user", content: "from traces shared" }, { role: "assistant", content: "trace shared" }], }), ].join("\n") + "\n", { mode: 0o600 }); writeFileSync(machineDataFile(cliCwd, "train"), [ JSON.stringify({ id: "sharedid00001", kind: "final", tag: "good", messages: [{ role: "user", content: "from train shared" }, { role: "assistant", content: "train shared" }], }), ].join("\n") + "\n", { mode: 0o600 }); eq(findRowById(cliCwd, "obsid0000001")?.kind, "observed", "lookup prefers observed.jsonl"); eq(findRowById(cliCwd, "traceid000001")?.kind, "traces", "lookup falls through to traces.jsonl"); eq(findRowById(cliCwd, "sharedid00001")?.kind, "traces", "duplicate ids resolve in observed/traces/train order"); eq(findRowById(cliCwd, "missing"), undefined, "unknown ids are not found"); const observedWithId = await captureCli(["observed"]); assert(observedWithId.out.includes("obsid0000001"), "machine observed shows row ids when present"); const usage = await captureCli(["correction", "fix it"]); assert(usage.code === 2 && usage.err.includes("--id"), "correction without --id is a usage error"); const missingId = await captureCli(["correct", "--id", "nope", "fix it"]); assert(missingId.code === 1 && missingId.err.includes("nope"), "correction reports unknown ids"); const printed = await captureCli(["correction", "--id", "obsid0000001", "use Julia"], { spawnPi: false }); assert(printed.code === 0, "correction prints the prompt when pi is not spawned"); assert(printed.out.includes("from observed"), "printed correction includes the original turn"); assert(printed.out.includes("Correction: use Julia"), "printed correction includes the correction"); let corrected; const correctionSpawn = await captureCli(["correct", "--id", "traceid000001", "keep the helper"], { env: { ...process.env, PATH: "/usr/bin" }, spawnPi: async (request) => { corrected = request; return { code: 0, stdout: "ok", stderr: "" }; }, }); assert(correctionSpawn.code === 0, "machine correct --id succeeds when pi spawn is injected"); assert(corrected?.env.MACHINE_FORK === "1", "correction sets MACHINE_FORK=1"); assert(corrected?.env.MACHINE_ROW_ID === "traceid000001", "correction sets MACHINE_ROW_ID"); assert(corrected?.prompt.includes("from traces"), "correction prompt includes the original turn"); assert(corrected?.prompt.includes("keep the helper"), "correction prompt includes the correction text"); } finally { if (previousDataDir === undefined) delete process.env.MACHINE_DATA_DIR; else process.env.MACHINE_DATA_DIR = previousDataDir; } console.log(`unit tests passed (${checks} checks)`);