import { describe, expect, it } from "bun:test"; import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { getGrokDefaultScanRoots, getGrokSessionsStreaming, parseGrokSessionDir, } from "./grok"; async function writeSession( sessionDir: string, opts: { summary?: unknown; history: unknown[]; cwdSidecar?: string; }, ) { await mkdir(sessionDir, { recursive: true }); if (opts.summary !== undefined) { await writeFile( join(sessionDir, "summary.json"), JSON.stringify(opts.summary), "utf8", ); } await writeFile( join(sessionDir, "chat_history.jsonl"), `${opts.history.map((m) => JSON.stringify(m)).join("\n")}\n`, "utf8", ); } describe("grok ingestion", () => { it("has a sensible default scan root", () => { const roots = getGrokDefaultScanRoots(); expect(roots).toHaveLength(1); expect(roots[0]).toMatch(/\.grok\/sessions$/); }); it("parses chat_history.jsonl messages and synthesizes timestamps", async () => { const dir = await mkdtemp(join(tmpdir(), "scribe-grok-test-")); const sessionDir = join(dir, "session-abc"); try { await writeSession(sessionDir, { summary: { sessionId: "session-abc", title: "Refactor the parser", // Real Grok writes `current_model_id`, not `model` — the field the // adapter must read (regression guard for the model=undefined bug). current_model_id: "grok-4.5", createdAt: "2026-02-09T12:00:00.000Z", updatedAt: "2026-02-09T12:10:00.000Z", }, history: [ // Real Grok tags the role in `type`, not `role`. Reading `role` made // every message default to "system", so no user messages were seen // and grok produced zero events — this fixture guards that regression. { type: "system", content: "You are Grok." }, { type: "user", content: [{ type: "text", text: "Run the parser tests" }], }, { type: "assistant", content: "On it." }, { type: "tool", content: "" }, // empty text → dropped ], }); const session = await parseGrokSessionDir( sessionDir, "/Users/satchmo/code/scribe", ); expect(session?.providerId).toBe("grok"); expect(session?.externalThreadId).toBe("session-abc"); expect(session?.title).toBe("Refactor the parser"); expect(session?.projectPath).toBe("/Users/satchmo/code/scribe"); expect(session?.metadata?.model).toBe("grok-4.5"); // system + user + assistant kept; empty tool message dropped expect(session?.messages).toHaveLength(3); expect(session?.messages[0]?.role).toBe("system"); expect(session?.messages[1]?.role).toBe("user"); expect(session?.messages[1]?.text).toBe("Run the parser tests"); expect(session?.messages[2]?.role).toBe("assistant"); // timestamps interpolated within the summary window and monotonic expect(session?.messages[0]?.timestamp.toISOString()).toBe( "2026-02-09T12:00:00.000Z", ); expect(session?.messages[2]?.timestamp.toISOString()).toBe( "2026-02-09T12:10:00.000Z", ); expect(session?.messages[0]?.externalMessageId).not.toBe( session?.messages[1]?.externalMessageId, ); } finally { await rm(dir, { recursive: true, force: true }); } }); it("skips malformed lines and returns null for empty sessions", async () => { const dir = await mkdtemp(join(tmpdir(), "scribe-grok-test-")); const sessionDir = join(dir, "session-empty"); try { await mkdir(sessionDir, { recursive: true }); await writeFile( join(sessionDir, "chat_history.jsonl"), "not json\n{bad}\n", "utf8", ); const session = await parseGrokSessionDir(sessionDir); expect(session).toBeNull(); } finally { await rm(dir, { recursive: true, force: true }); } }); it("walks the / tree and resolves cwd", async () => { const root = await mkdtemp(join(tmpdir(), "scribe-grok-root-")); try { // URL-encoded working directory as the group directory name const encoded = encodeURIComponent("/Users/satchmo/code/scribe"); const groupDir = join(root, encoded); await writeSession(join(groupDir, "s1"), { summary: { title: "First", createdAt: "2026-02-09T09:00:00.000Z", updatedAt: "2026-02-09T09:05:00.000Z", }, history: [{ type: "user", content: "hi from s1" }], }); const sessions = []; for await (const s of getGrokSessionsStreaming( ["/Users/satchmo/code"], undefined, { dataRoots: [root] }, )) { sessions.push(s); } expect(sessions).toHaveLength(1); expect(sessions[0]?.externalThreadId).toBe("s1"); expect(sessions[0]?.projectPath).toBe("/Users/satchmo/code/scribe"); expect(sessions[0]?.messages[0]?.text).toBe("hi from s1"); } finally { await rm(root, { recursive: true, force: true }); } }); it("uses scan roots as project filters and includes unresolved cwd", async () => { const root = await mkdtemp(join(tmpdir(), "scribe-grok-filter-test-")); try { const fixtures = [ { group: encodeURIComponent("/Users/test/code/inside"), id: "inside", }, { group: encodeURIComponent("/Users/test/other/outside"), id: "outside", }, { group: "%", id: "unresolved" }, ]; for (const fixture of fixtures) { await writeSession(join(root, fixture.group, fixture.id), { history: [{ type: "user", content: fixture.id }], }); } const filtered = []; for await (const session of getGrokSessionsStreaming( ["/Users/test/code"], undefined, { dataRoots: [root] }, )) { filtered.push(session.externalThreadId); } expect(filtered.sort()).toEqual(["inside", "unresolved"]); const unfiltered = []; for await (const session of getGrokSessionsStreaming([], undefined, { dataRoots: [root], })) { unfiltered.push(session.externalThreadId); } expect(unfiltered.sort()).toEqual(["inside", "outside", "unresolved"]); } finally { await rm(root, { recursive: true, force: true }); } }); it("prefers the .cwd sidecar when present", async () => { const root = await mkdtemp(join(tmpdir(), "scribe-grok-root-")); try { const groupDir = join(root, "slug-abc123"); await mkdir(groupDir, { recursive: true }); await writeFile( join(groupDir, ".cwd"), "/very/long/original/path\n", "utf8", ); await writeSession(join(groupDir, "s2"), { history: [{ type: "user", content: "hi from s2" }], }); const sessions = []; for await (const s of getGrokSessionsStreaming( ["/very/long"], undefined, { dataRoots: [root] }, )) { sessions.push(s); } expect(sessions).toHaveLength(1); expect(sessions[0]?.projectPath).toBe("/very/long/original/path"); } finally { await rm(root, { recursive: true, force: true }); } }); it("filters by since using message timestamps", async () => { const root = await mkdtemp(join(tmpdir(), "scribe-grok-root-")); try { const groupDir = join(root, encodeURIComponent("/tmp/proj")); await writeSession(join(groupDir, "s3"), { summary: { createdAt: "2026-02-09T08:00:00.000Z", updatedAt: "2026-02-09T08:00:00.000Z", }, history: [{ type: "user", content: "old message" }], }); const sessions = []; for await (const s of getGrokSessionsStreaming( [], new Date("2026-03-01T00:00:00.000Z"), { dataRoots: [root] }, )) { sessions.push(s); } // The single message predates `since`; the whole session is filtered out. expect(sessions).toHaveLength(0); } finally { await rm(root, { recursive: true, force: true }); } }); });