import { describe, expect, it, spyOn } from "bun:test"; import { mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { getAmpSessionsStreaming, parseAmpThreadFile } from "./amp"; describe("amp ingestion", () => { it("parses amp stream-json thread files and normalizes messages", async () => { const dir = await mkdtemp(join(tmpdir(), "scribe-amp-test-")); const threadFile = join(dir, "T-abc.jsonl"); try { const lines = [ JSON.stringify({ type: "system", subtype: "init", cwd: "/Users/satchmo/code/scribe", session_id: "T-abc", timestamp: "2026-02-09T12:00:00.000Z", }), JSON.stringify({ type: "user", message: { role: "user", content: [{ type: "text", text: "Run the parser tests" }], }, session_id: "T-abc", timestamp: "2026-02-09T12:00:01.000Z", }), JSON.stringify({ type: "assistant", message: { role: "assistant", content: [{ type: "text", text: "On it." }], usage: { input_tokens: 10, output_tokens: 3 }, }, session_id: "T-abc", timestamp: "2026-02-09T12:00:02.000Z", }), JSON.stringify({ type: "result", subtype: "success", session_id: "T-abc", timestamp: "2026-02-09T12:00:03.000Z", }), ]; await writeFile(threadFile, `${lines.join("\n")}\n`, "utf8"); const session = await parseAmpThreadFile(threadFile); expect(session?.providerId).toBe("amp"); expect(session?.externalThreadId).toBe("T-abc"); expect(session?.projectPath).toBe("/Users/satchmo/code/scribe"); expect(session?.messages).toHaveLength(2); expect(session?.messages[0]?.text).toBe("Run the parser tests"); expect(session?.messages[1]?.tokenIn).toBe(10); } finally { await rm(dir, { recursive: true, force: true }); } }); it("counts and reports skipped malformed lines instead of dropping them silently", async () => { const dir = await mkdtemp(join(tmpdir(), "scribe-amp-skip-test-")); const threadFile = join(dir, "T-skip.jsonl"); const warn = spyOn(console, "warn").mockImplementation(() => {}); try { const lines = [ "{ broken json", JSON.stringify({ type: "user", message: { role: "user", content: [{ type: "text", text: "survives the bad lines" }], }, session_id: "T-skip", timestamp: "2026-02-09T12:00:00.000Z", }), "also not json", ]; await writeFile(threadFile, `${lines.join("\n")}\n`, "utf8"); const session = await parseAmpThreadFile(threadFile); // the one valid message survives despite two malformed lines expect(session?.messages).toHaveLength(1); expect(session?.messages[0]?.text).toContain("survives the bad lines"); // and the skips are now SURFACED, not silently lost const warned = warn.mock.calls.map((c) => String(c[0])).join("\n"); expect(warned).toContain("[amp] skipped 2 malformed line(s)"); } finally { warn.mockRestore(); await rm(dir, { recursive: true, force: true }); } }); it("filters default-location sessions by cwd and keeps unresolved cwd", async () => { const dir = await mkdtemp(join(tmpdir(), "scribe-amp-filter-test-")); try { const fixtures = [ { id: "inside", cwd: "/Users/test/code/project" }, { id: "outside", cwd: "/Users/test/other/project" }, { id: "unresolved", cwd: undefined }, ]; for (const fixture of fixtures) { const records = [ { type: "system", subtype: "init", cwd: fixture.cwd, session_id: fixture.id, }, { type: "user", message: { role: "user", content: fixture.id }, session_id: fixture.id, timestamp: "2026-02-09T12:00:00.000Z", }, ]; await writeFile( join(dir, `${fixture.id}.jsonl`), records.map((record) => JSON.stringify(record)).join("\n"), "utf8", ); } const filtered = []; for await (const session of getAmpSessionsStreaming( ["/Users/test/code"], undefined, { dataRoots: [dir] }, )) { filtered.push(session.externalThreadId); } expect(filtered.sort()).toEqual(["inside", "unresolved"]); const unfiltered = []; for await (const session of getAmpSessionsStreaming([], undefined, { dataRoots: [dir], })) { unfiltered.push(session.externalThreadId); } expect(unfiltered.sort()).toEqual(["inside", "outside", "unresolved"]); } finally { await rm(dir, { recursive: true, force: true }); } }); });