import { CheckpointRef, ProjectId, ThreadId, TurnId } from "@t3tools/contracts";import { it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import { describe, expect } from "vite-plus/test"; import * as ProjectionSnapshotQuery from "../orchestration/Services/ProjectionSnapshotQuery.ts";import { checkpointRefForThreadTurn } from "./Utils.ts";import * as CheckpointDiffQuery from "./CheckpointDiffQuery.ts";import * as CheckpointStore from "./CheckpointStore.ts";import { CheckpointThreadNotFoundError } from "./Errors.ts"; function makeThreadCheckpointContext(input: { readonly projectId: ProjectId; readonly threadId: ThreadId; readonly workspaceRoot: string; readonly worktreePath: string | null; readonly checkpointTurnCount: number; readonly checkpointRef: CheckpointRef;}): ProjectionSnapshotQuery.ProjectionThreadCheckpointContext { return { threadId: input.threadId, projectId: input.projectId, workspaceRoot: input.workspaceRoot, worktreePath: input.worktreePath, checkpoints: [ { turnId: TurnId.make("turn-1"), checkpointTurnCount: input.checkpointTurnCount, checkpointRef: input.checkpointRef, status: "ready", files: [], assistantMessageId: null, completedAt: "2026-01-01T00:00:00.000Z", }, ], };} describe("CheckpointDiffQuery.layer", () => { it.effect("uses the narrow full-thread context lookup for all-turns diffs", () => Effect.gen(function* () { const projectId = ProjectId.make("project-full-thread"); const threadId = ThreadId.make("thread-full-thread"); const toCheckpointRef = checkpointRefForThreadTurn(threadId, 4); let getThreadCheckpointContextCalls = 0; let getFullThreadDiffContextCalls = 0; const diffCheckpointsCalls: Array<{ readonly fromCheckpointRef: CheckpointRef; readonly toCheckpointRef: CheckpointRef; readonly cwd: string; readonly ignoreWhitespace: boolean; }> = []; const checkpointStore: CheckpointStore.CheckpointStore["Service"] = { isGitRepository: () => Effect.succeed(true), captureCheckpoint: () => Effect.void, hasCheckpointRef: () => Effect.succeed(true), restoreCheckpoint: () => Effect.succeed(true), diffCheckpoints: ({ fromCheckpointRef, toCheckpointRef, cwd, ignoreWhitespace }) => Effect.sync(() => { diffCheckpointsCalls.push({ fromCheckpointRef, toCheckpointRef, cwd, ignoreWhitespace, }); return "full thread diff patch"; }), deleteCheckpointRefs: () => Effect.void, attributeCheckpointDiff: () => Effect.succeed(null), }; const layer = CheckpointDiffQuery.layer.pipe( Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), Layer.provideMerge( Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { getCommandReadModel: () => Effect.die("CheckpointDiffQuery should not request the command read model"), getSnapshot: () => Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), getShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), getArchivedShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), getProjectShellById: () => Effect.succeed(Option.none()), getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), getThreadCheckpointContext: () => Effect.sync(() => { getThreadCheckpointContextCalls += 1; return Option.none(); }), getFullThreadDiffContext: () => Effect.sync(() => { getFullThreadDiffContextCalls += 1; return Option.some({ threadId, projectId, workspaceRoot: "/tmp/workspace", worktreePath: "/tmp/worktree", latestCheckpointTurnCount: 4, toCheckpointRef, }); }), getThreadShellById: () => Effect.succeed(Option.none()), getThreadDetailById: () => Effect.succeed(Option.none()), getThreadDetailSnapshot: () => Effect.succeed(Option.none()), }), ), ); const result = yield* Effect.gen(function* () { const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; return yield* query.getFullThreadDiff({ threadId, toTurnCount: 4, ignoreWhitespace: true, }); }).pipe(Effect.provide(layer)); expect(getThreadCheckpointContextCalls).toBe(0); expect(getFullThreadDiffContextCalls).toBe(1); expect(diffCheckpointsCalls).toEqual([ { cwd: "/tmp/worktree", fromCheckpointRef: checkpointRefForThreadTurn(threadId, 0), toCheckpointRef, ignoreWhitespace: true, }, ]); expect(result).toEqual({ threadId, fromTurnCount: 0, toTurnCount: 4, diff: "full thread diff patch", }); }), ); it.effect("computes diffs using canonical turn-0 checkpoint refs", () => Effect.gen(function* () { const projectId = ProjectId.make("project-1"); const threadId = ThreadId.make("thread-1"); const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); const diffCheckpointsCalls: Array<{ readonly fromCheckpointRef: CheckpointRef; readonly toCheckpointRef: CheckpointRef; readonly cwd: string; readonly ignoreWhitespace: boolean; }> = []; const threadCheckpointContext = makeThreadCheckpointContext({ projectId, threadId, workspaceRoot: "/tmp/workspace", worktreePath: null, checkpointTurnCount: 1, checkpointRef: toCheckpointRef, }); const checkpointStore: CheckpointStore.CheckpointStore["Service"] = { isGitRepository: () => Effect.succeed(true), captureCheckpoint: () => Effect.void, hasCheckpointRef: () => Effect.succeed(true), restoreCheckpoint: () => Effect.succeed(true), diffCheckpoints: ({ fromCheckpointRef, toCheckpointRef, cwd, ignoreWhitespace }) => Effect.sync(() => { diffCheckpointsCalls.push({ fromCheckpointRef, toCheckpointRef, cwd, ignoreWhitespace, }); return "diff patch"; }), deleteCheckpointRefs: () => Effect.void, attributeCheckpointDiff: () => Effect.succeed(null), }; const layer = CheckpointDiffQuery.layer.pipe( Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), Layer.provideMerge( Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { getCommandReadModel: () => Effect.die("CheckpointDiffQuery should not request the command read model"), getSnapshot: () => Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), getShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), getArchivedShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), getProjectShellById: () => Effect.succeed(Option.none()), getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)), getFullThreadDiffContext: () => Effect.die("unused"), getThreadShellById: () => Effect.succeed(Option.none()), getThreadDetailById: () => Effect.succeed(Option.none()), getThreadDetailSnapshot: () => Effect.succeed(Option.none()), }), ), ); const result = yield* Effect.gen(function* () { const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; return yield* query.getTurnDiff({ threadId, fromTurnCount: 0, toTurnCount: 1, ignoreWhitespace: true, }); }).pipe(Effect.provide(layer)); const expectedFromRef = checkpointRefForThreadTurn(threadId, 0); expect(diffCheckpointsCalls).toEqual([ { cwd: "/tmp/workspace", fromCheckpointRef: expectedFromRef, toCheckpointRef, ignoreWhitespace: true, }, ]); expect(result).toEqual({ threadId, fromTurnCount: 0, toTurnCount: 1, diff: "diff patch", }); }), ); it.effect("defaults to hide whitespace changes", () => Effect.gen(function* () { const projectId = ProjectId.make("project-default-whitespace"); const threadId = ThreadId.make("thread-default-whitespace"); const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); const diffCheckpointsCalls: Array<{ readonly ignoreWhitespace: boolean }> = []; const threadCheckpointContext = makeThreadCheckpointContext({ projectId, threadId, workspaceRoot: "/tmp/workspace", worktreePath: null, checkpointTurnCount: 1, checkpointRef: toCheckpointRef, }); const checkpointStore: CheckpointStore.CheckpointStore["Service"] = { isGitRepository: () => Effect.succeed(true), captureCheckpoint: () => Effect.void, hasCheckpointRef: () => Effect.succeed(true), restoreCheckpoint: () => Effect.succeed(true), diffCheckpoints: ({ ignoreWhitespace }) => Effect.sync(() => { diffCheckpointsCalls.push({ ignoreWhitespace }); return "diff patch"; }), deleteCheckpointRefs: () => Effect.void, attributeCheckpointDiff: () => Effect.succeed(null), }; const layer = CheckpointDiffQuery.layer.pipe( Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), Layer.provideMerge( Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { getCommandReadModel: () => Effect.die("CheckpointDiffQuery should not request the command read model"), getSnapshot: () => Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), getShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), getArchivedShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), getProjectShellById: () => Effect.succeed(Option.none()), getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)), getFullThreadDiffContext: () => Effect.die("unused"), getThreadShellById: () => Effect.succeed(Option.none()), getThreadDetailById: () => Effect.succeed(Option.none()), getThreadDetailSnapshot: () => Effect.succeed(Option.none()), }), ), ); yield* Effect.gen(function* () { const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; return yield* query.getTurnDiff({ threadId, fromTurnCount: 0, toTurnCount: 1, }); }).pipe(Effect.provide(layer)); expect(diffCheckpointsCalls).toEqual([{ ignoreWhitespace: true }]); }), ); it.effect("filters git-attributed sections and reports gitFileCount from patch sections", () => Effect.gen(function* () { const projectId = ProjectId.make("project-attribution"); const threadId = ThreadId.make("thread-attribution"); const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); const patch = [ "diff --git a/agent.ts b/agent.ts", "index 1111111..2222222 100644", "--- a/agent.ts", "+++ b/agent.ts", "@@ -1,1 +1,1 @@", "-old", "+new", "diff --git a/pulled.ts b/pulled.ts", "index 3333333..4444444 100644", "--- a/pulled.ts", "+++ b/pulled.ts", "@@ -1,1 +1,1 @@", "-before", "+after", "", ].join("\n"); // Attribution also tags a path that has no section in this patch // (e.g. whitespace-only change elided by ignoreWhitespace) — it must // not count toward gitFileCount. const attribution = new Map<string, "agent" | "git">([ ["agent.ts", "agent"], ["pulled.ts", "git"], ["whitespace-only.ts", "git"], ]); const threadCheckpointContext = makeThreadCheckpointContext({ projectId, threadId, workspaceRoot: "/tmp/workspace", worktreePath: null, checkpointTurnCount: 1, checkpointRef: toCheckpointRef, }); const checkpointStore: CheckpointStore.CheckpointStore["Service"] = { isGitRepository: () => Effect.succeed(true), captureCheckpoint: () => Effect.void, hasCheckpointRef: () => Effect.succeed(true), restoreCheckpoint: () => Effect.succeed(true), diffCheckpoints: () => Effect.succeed(patch), deleteCheckpointRefs: () => Effect.void, attributeCheckpointDiff: () => Effect.succeed(attribution), }; const layer = CheckpointDiffQuery.layer.pipe( Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), Layer.provideMerge( Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { getCommandReadModel: () => Effect.die("unused"), getSnapshot: () => Effect.die("unused"), getShellSnapshot: () => Effect.die("unused"), getArchivedShellSnapshot: () => Effect.die("unused"), getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), getProjectShellById: () => Effect.succeed(Option.none()), getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)), getFullThreadDiffContext: () => Effect.die("unused"), getThreadShellById: () => Effect.succeed(Option.none()), getThreadDetailById: () => Effect.succeed(Option.none()), getThreadDetailSnapshot: () => Effect.succeed(Option.none()), }), ), ); const { filtered, revealed } = yield* Effect.gen(function* () { const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; const filtered = yield* query.getTurnDiff({ threadId, fromTurnCount: 0, toTurnCount: 1, }); const revealed = yield* query.getTurnDiff({ threadId, fromTurnCount: 0, toTurnCount: 1, includeGitChanges: true, }); return { filtered, revealed }; }).pipe(Effect.provide(layer)); expect(filtered.diff).toContain("agent.ts"); expect(filtered.diff).not.toContain("pulled.ts"); expect(filtered.gitFileCount).toBe(1); expect(revealed.diff).toBe(patch); expect(revealed.gitFileCount).toBe(1); }), ); it.effect("does not preflight checkpoint refs before diffing", () => Effect.gen(function* () { const projectId = ProjectId.make("project-no-preflight"); const threadId = ThreadId.make("thread-no-preflight"); const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); let hasCheckpointRefCallCount = 0; const threadCheckpointContext = makeThreadCheckpointContext({ projectId, threadId, workspaceRoot: "/tmp/workspace", worktreePath: null, checkpointTurnCount: 1, checkpointRef: toCheckpointRef, }); const checkpointStore: CheckpointStore.CheckpointStore["Service"] = { isGitRepository: () => Effect.succeed(true), captureCheckpoint: () => Effect.void, hasCheckpointRef: () => Effect.sync(() => { hasCheckpointRefCallCount += 1; return true; }), restoreCheckpoint: () => Effect.succeed(true), diffCheckpoints: () => Effect.succeed("diff patch"), deleteCheckpointRefs: () => Effect.void, attributeCheckpointDiff: () => Effect.succeed(null), }; const layer = CheckpointDiffQuery.layer.pipe( Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), Layer.provideMerge( Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { getCommandReadModel: () => Effect.die("CheckpointDiffQuery should not request the command read model"), getSnapshot: () => Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), getShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), getArchivedShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), getProjectShellById: () => Effect.succeed(Option.none()), getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)), getFullThreadDiffContext: () => Effect.die("unused"), getThreadShellById: () => Effect.succeed(Option.none()), getThreadDetailById: () => Effect.succeed(Option.none()), getThreadDetailSnapshot: () => Effect.succeed(Option.none()), }), ), ); yield* Effect.gen(function* () { const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; return yield* query.getTurnDiff({ threadId, fromTurnCount: 0, toTurnCount: 1, ignoreWhitespace: true, }); }).pipe(Effect.provide(layer)); expect(hasCheckpointRefCallCount).toBe(0); }), ); it.effect("fails when the thread is missing from the snapshot", () => Effect.gen(function* () { const threadId = ThreadId.make("thread-missing"); const checkpointStore: CheckpointStore.CheckpointStore["Service"] = { isGitRepository: () => Effect.succeed(true), captureCheckpoint: () => Effect.void, hasCheckpointRef: () => Effect.succeed(true), restoreCheckpoint: () => Effect.succeed(true), diffCheckpoints: () => Effect.succeed(""), deleteCheckpointRefs: () => Effect.void, attributeCheckpointDiff: () => Effect.succeed(null), }; const layer = CheckpointDiffQuery.layer.pipe( Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), Layer.provideMerge( Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { getCommandReadModel: () => Effect.die("CheckpointDiffQuery should not request the command read model"), getSnapshot: () => Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), getShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), getArchivedShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }),Lines 1–500 of 532. Load more
import { CheckpointRef, ProjectId, ThreadId, TurnId } from "@t3tools/contracts"; import { it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import { describe, expect } from "vite-plus/test"; import * as ProjectionSnapshotQuery from "../orchestration/Services/ProjectionSnapshotQuery.ts"; import { checkpointRefForThreadTurn } from "./Utils.ts"; import * as CheckpointDiffQuery from "./CheckpointDiffQuery.ts"; import * as CheckpointStore from "./CheckpointStore.ts"; import { CheckpointThreadNotFoundError } from "./Errors.ts"; function makeThreadCheckpointContext(input: { readonly projectId: ProjectId; readonly threadId: ThreadId; readonly workspaceRoot: string; readonly worktreePath: string | null; readonly checkpointTurnCount: number; readonly checkpointRef: CheckpointRef; }): ProjectionSnapshotQuery.ProjectionThreadCheckpointContext { return { threadId: input.threadId, projectId: input.projectId, workspaceRoot: input.workspaceRoot, worktreePath: input.worktreePath, checkpoints: [ { turnId: TurnId.make("turn-1"), checkpointTurnCount: input.checkpointTurnCount, checkpointRef: input.checkpointRef, status: "ready", files: [], assistantMessageId: null, completedAt: "2026-01-01T00:00:00.000Z", }, ], }; } describe("CheckpointDiffQuery.layer", () => { it.effect("uses the narrow full-thread context lookup for all-turns diffs", () => Effect.gen(function* () { const projectId = ProjectId.make("project-full-thread"); const threadId = ThreadId.make("thread-full-thread"); const toCheckpointRef = checkpointRefForThreadTurn(threadId, 4); let getThreadCheckpointContextCalls = 0; let getFullThreadDiffContextCalls = 0; const diffCheckpointsCalls: Array<{ readonly fromCheckpointRef: CheckpointRef; readonly toCheckpointRef: CheckpointRef; readonly cwd: string; readonly ignoreWhitespace: boolean; }> = []; const checkpointStore: CheckpointStore.CheckpointStore["Service"] = { isGitRepository: () => Effect.succeed(true), captureCheckpoint: () => Effect.void, hasCheckpointRef: () => Effect.succeed(true), restoreCheckpoint: () => Effect.succeed(true), diffCheckpoints: ({ fromCheckpointRef, toCheckpointRef, cwd, ignoreWhitespace }) => Effect.sync(() => { diffCheckpointsCalls.push({ fromCheckpointRef, toCheckpointRef, cwd, ignoreWhitespace, }); return "full thread diff patch"; }), deleteCheckpointRefs: () => Effect.void, attributeCheckpointDiff: () => Effect.succeed(null), }; const layer = CheckpointDiffQuery.layer.pipe( Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), Layer.provideMerge( Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { getCommandReadModel: () => Effect.die("CheckpointDiffQuery should not request the command read model"), getSnapshot: () => Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), getShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), getArchivedShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), getProjectShellById: () => Effect.succeed(Option.none()), getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), getThreadCheckpointContext: () => Effect.sync(() => { getThreadCheckpointContextCalls += 1; return Option.none(); }), getFullThreadDiffContext: () => Effect.sync(() => { getFullThreadDiffContextCalls += 1; return Option.some({ threadId, projectId, workspaceRoot: "/tmp/workspace", worktreePath: "/tmp/worktree", latestCheckpointTurnCount: 4, toCheckpointRef, }); }), getThreadShellById: () => Effect.succeed(Option.none()), getThreadDetailById: () => Effect.succeed(Option.none()), getThreadDetailSnapshot: () => Effect.succeed(Option.none()), }), ), ); const result = yield* Effect.gen(function* () { const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; return yield* query.getFullThreadDiff({ threadId, toTurnCount: 4, ignoreWhitespace: true, }); }).pipe(Effect.provide(layer)); expect(getThreadCheckpointContextCalls).toBe(0); expect(getFullThreadDiffContextCalls).toBe(1); expect(diffCheckpointsCalls).toEqual([ { cwd: "/tmp/worktree", fromCheckpointRef: checkpointRefForThreadTurn(threadId, 0), toCheckpointRef, ignoreWhitespace: true, }, ]); expect(result).toEqual({ threadId, fromTurnCount: 0, toTurnCount: 4, diff: "full thread diff patch", }); }), ); it.effect("computes diffs using canonical turn-0 checkpoint refs", () => Effect.gen(function* () { const projectId = ProjectId.make("project-1"); const threadId = ThreadId.make("thread-1"); const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); const diffCheckpointsCalls: Array<{ readonly fromCheckpointRef: CheckpointRef; readonly toCheckpointRef: CheckpointRef; readonly cwd: string; readonly ignoreWhitespace: boolean; }> = []; const threadCheckpointContext = makeThreadCheckpointContext({ projectId, threadId, workspaceRoot: "/tmp/workspace", worktreePath: null, checkpointTurnCount: 1, checkpointRef: toCheckpointRef, }); const checkpointStore: CheckpointStore.CheckpointStore["Service"] = { isGitRepository: () => Effect.succeed(true), captureCheckpoint: () => Effect.void, hasCheckpointRef: () => Effect.succeed(true), restoreCheckpoint: () => Effect.succeed(true), diffCheckpoints: ({ fromCheckpointRef, toCheckpointRef, cwd, ignoreWhitespace }) => Effect.sync(() => { diffCheckpointsCalls.push({ fromCheckpointRef, toCheckpointRef, cwd, ignoreWhitespace, }); return "diff patch"; }), deleteCheckpointRefs: () => Effect.void, attributeCheckpointDiff: () => Effect.succeed(null), }; const layer = CheckpointDiffQuery.layer.pipe( Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), Layer.provideMerge( Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { getCommandReadModel: () => Effect.die("CheckpointDiffQuery should not request the command read model"), getSnapshot: () => Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), getShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), getArchivedShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), getProjectShellById: () => Effect.succeed(Option.none()), getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)), getFullThreadDiffContext: () => Effect.die("unused"), getThreadShellById: () => Effect.succeed(Option.none()), getThreadDetailById: () => Effect.succeed(Option.none()), getThreadDetailSnapshot: () => Effect.succeed(Option.none()), }), ), ); const result = yield* Effect.gen(function* () { const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; return yield* query.getTurnDiff({ threadId, fromTurnCount: 0, toTurnCount: 1, ignoreWhitespace: true, }); }).pipe(Effect.provide(layer)); const expectedFromRef = checkpointRefForThreadTurn(threadId, 0); expect(diffCheckpointsCalls).toEqual([ { cwd: "/tmp/workspace", fromCheckpointRef: expectedFromRef, toCheckpointRef, ignoreWhitespace: true, }, ]); expect(result).toEqual({ threadId, fromTurnCount: 0, toTurnCount: 1, diff: "diff patch", }); }), ); it.effect("defaults to hide whitespace changes", () => Effect.gen(function* () { const projectId = ProjectId.make("project-default-whitespace"); const threadId = ThreadId.make("thread-default-whitespace"); const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); const diffCheckpointsCalls: Array<{ readonly ignoreWhitespace: boolean }> = []; const threadCheckpointContext = makeThreadCheckpointContext({ projectId, threadId, workspaceRoot: "/tmp/workspace", worktreePath: null, checkpointTurnCount: 1, checkpointRef: toCheckpointRef, }); const checkpointStore: CheckpointStore.CheckpointStore["Service"] = { isGitRepository: () => Effect.succeed(true), captureCheckpoint: () => Effect.void, hasCheckpointRef: () => Effect.succeed(true), restoreCheckpoint: () => Effect.succeed(true), diffCheckpoints: ({ ignoreWhitespace }) => Effect.sync(() => { diffCheckpointsCalls.push({ ignoreWhitespace }); return "diff patch"; }), deleteCheckpointRefs: () => Effect.void, attributeCheckpointDiff: () => Effect.succeed(null), }; const layer = CheckpointDiffQuery.layer.pipe( Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), Layer.provideMerge( Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { getCommandReadModel: () => Effect.die("CheckpointDiffQuery should not request the command read model"), getSnapshot: () => Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), getShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), getArchivedShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), getProjectShellById: () => Effect.succeed(Option.none()), getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)), getFullThreadDiffContext: () => Effect.die("unused"), getThreadShellById: () => Effect.succeed(Option.none()), getThreadDetailById: () => Effect.succeed(Option.none()), getThreadDetailSnapshot: () => Effect.succeed(Option.none()), }), ), ); yield* Effect.gen(function* () { const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; return yield* query.getTurnDiff({ threadId, fromTurnCount: 0, toTurnCount: 1, }); }).pipe(Effect.provide(layer)); expect(diffCheckpointsCalls).toEqual([{ ignoreWhitespace: true }]); }), ); it.effect("filters git-attributed sections and reports gitFileCount from patch sections", () => Effect.gen(function* () { const projectId = ProjectId.make("project-attribution"); const threadId = ThreadId.make("thread-attribution"); const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); const patch = [ "diff --git a/agent.ts b/agent.ts", "index 1111111..2222222 100644", "--- a/agent.ts", "+++ b/agent.ts", "@@ -1,1 +1,1 @@", "-old", "+new", "diff --git a/pulled.ts b/pulled.ts", "index 3333333..4444444 100644", "--- a/pulled.ts", "+++ b/pulled.ts", "@@ -1,1 +1,1 @@", "-before", "+after", "", ].join("\n"); // Attribution also tags a path that has no section in this patch // (e.g. whitespace-only change elided by ignoreWhitespace) — it must // not count toward gitFileCount. const attribution = new Map<string, "agent" | "git">([ ["agent.ts", "agent"], ["pulled.ts", "git"], ["whitespace-only.ts", "git"], ]); const threadCheckpointContext = makeThreadCheckpointContext({ projectId, threadId, workspaceRoot: "/tmp/workspace", worktreePath: null, checkpointTurnCount: 1, checkpointRef: toCheckpointRef, }); const checkpointStore: CheckpointStore.CheckpointStore["Service"] = { isGitRepository: () => Effect.succeed(true), captureCheckpoint: () => Effect.void, hasCheckpointRef: () => Effect.succeed(true), restoreCheckpoint: () => Effect.succeed(true), diffCheckpoints: () => Effect.succeed(patch), deleteCheckpointRefs: () => Effect.void, attributeCheckpointDiff: () => Effect.succeed(attribution), }; const layer = CheckpointDiffQuery.layer.pipe( Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), Layer.provideMerge( Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { getCommandReadModel: () => Effect.die("unused"), getSnapshot: () => Effect.die("unused"), getShellSnapshot: () => Effect.die("unused"), getArchivedShellSnapshot: () => Effect.die("unused"), getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), getProjectShellById: () => Effect.succeed(Option.none()), getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)), getFullThreadDiffContext: () => Effect.die("unused"), getThreadShellById: () => Effect.succeed(Option.none()), getThreadDetailById: () => Effect.succeed(Option.none()), getThreadDetailSnapshot: () => Effect.succeed(Option.none()), }), ), ); const { filtered, revealed } = yield* Effect.gen(function* () { const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; const filtered = yield* query.getTurnDiff({ threadId, fromTurnCount: 0, toTurnCount: 1, }); const revealed = yield* query.getTurnDiff({ threadId, fromTurnCount: 0, toTurnCount: 1, includeGitChanges: true, }); return { filtered, revealed }; }).pipe(Effect.provide(layer)); expect(filtered.diff).toContain("agent.ts"); expect(filtered.diff).not.toContain("pulled.ts"); expect(filtered.gitFileCount).toBe(1); expect(revealed.diff).toBe(patch); expect(revealed.gitFileCount).toBe(1); }), ); it.effect("does not preflight checkpoint refs before diffing", () => Effect.gen(function* () { const projectId = ProjectId.make("project-no-preflight"); const threadId = ThreadId.make("thread-no-preflight"); const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); let hasCheckpointRefCallCount = 0; const threadCheckpointContext = makeThreadCheckpointContext({ projectId, threadId, workspaceRoot: "/tmp/workspace", worktreePath: null, checkpointTurnCount: 1, checkpointRef: toCheckpointRef, }); const checkpointStore: CheckpointStore.CheckpointStore["Service"] = { isGitRepository: () => Effect.succeed(true), captureCheckpoint: () => Effect.void, hasCheckpointRef: () => Effect.sync(() => { hasCheckpointRefCallCount += 1; return true; }), restoreCheckpoint: () => Effect.succeed(true), diffCheckpoints: () => Effect.succeed("diff patch"), deleteCheckpointRefs: () => Effect.void, attributeCheckpointDiff: () => Effect.succeed(null), }; const layer = CheckpointDiffQuery.layer.pipe( Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), Layer.provideMerge( Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { getCommandReadModel: () => Effect.die("CheckpointDiffQuery should not request the command read model"), getSnapshot: () => Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), getShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), getArchivedShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), getProjectShellById: () => Effect.succeed(Option.none()), getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)), getFullThreadDiffContext: () => Effect.die("unused"), getThreadShellById: () => Effect.succeed(Option.none()), getThreadDetailById: () => Effect.succeed(Option.none()), getThreadDetailSnapshot: () => Effect.succeed(Option.none()), }), ), ); yield* Effect.gen(function* () { const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; return yield* query.getTurnDiff({ threadId, fromTurnCount: 0, toTurnCount: 1, ignoreWhitespace: true, }); }).pipe(Effect.provide(layer)); expect(hasCheckpointRefCallCount).toBe(0); }), ); it.effect("fails when the thread is missing from the snapshot", () => Effect.gen(function* () { const threadId = ThreadId.make("thread-missing"); const checkpointStore: CheckpointStore.CheckpointStore["Service"] = { isGitRepository: () => Effect.succeed(true), captureCheckpoint: () => Effect.void, hasCheckpointRef: () => Effect.succeed(true), restoreCheckpoint: () => Effect.succeed(true), diffCheckpoints: () => Effect.succeed(""), deleteCheckpointRefs: () => Effect.void, attributeCheckpointDiff: () => Effect.succeed(null), }; const layer = CheckpointDiffQuery.layer.pipe( Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), Layer.provideMerge( Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { getCommandReadModel: () => Effect.die("CheckpointDiffQuery should not request the command read model"), getSnapshot: () => Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), getShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), getArchivedShellSnapshot: () => Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }),