/** * CheckpointDiffQuery - Query interface for computed checkpoint diffs. * * Provides read-only diff operations across checkpoint snapshots used by * orchestration APIs. * * @module CheckpointDiffQuery */import { type CheckpointRef, OrchestrationGetTurnDiffResult, type OrchestrationGetFullThreadDiffInput, type OrchestrationGetFullThreadDiffResult, type OrchestrationGetTurnDiffInput, type OrchestrationGetTurnDiffResult as OrchestrationGetTurnDiffResultType, type ThreadId,} from "@t3tools/contracts";import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Schema from "effect/Schema"; import * as ProjectionSnapshotQuery from "../orchestration/Services/ProjectionSnapshotQuery.ts";import { CheckpointDiffResultInvalidError, CheckpointRefUnavailableError, CheckpointThreadNotFoundError, CheckpointTurnRangeUnavailableError, CheckpointWorkspacePathMissingError,} from "./Errors.ts";import type { CheckpointServiceError } from "./Errors.ts";import { checkpointRefForThreadTurn } from "./Utils.ts";import { filterUnifiedDiffFiles, listUnifiedDiffPaths } from "./Diffs.ts";import * as CheckpointStore from "./CheckpointStore.ts"; /** Service tag for checkpoint diff queries. */export class CheckpointDiffQuery extends Context.Service< CheckpointDiffQuery, { /** * Read the patch diff for a single turn checkpoint transition. * * Verifies checkpoint availability in both projection state and filesystem. */ readonly getTurnDiff: ( input: OrchestrationGetTurnDiffInput, ) => Effect.Effect<OrchestrationGetTurnDiffResultType, CheckpointServiceError>; /** * Read the full patch diff across a thread range of checkpoints. * * Uses turn-diff semantics with `fromTurnCount = 0`. */ readonly getFullThreadDiff: ( input: OrchestrationGetFullThreadDiffInput, ) => Effect.Effect<OrchestrationGetFullThreadDiffResult, CheckpointServiceError>; }>()("t3/checkpointing/CheckpointDiffQuery") {} const isTurnDiffResult = Schema.is(OrchestrationGetTurnDiffResult); function buildTurnDiffResult( input: { readonly threadId: ThreadId; readonly fromTurnCount: number; readonly toTurnCount: number; }, diff: string, gitFileCount?: number,): OrchestrationGetTurnDiffResultType { return { threadId: input.threadId, fromTurnCount: input.fromTurnCount, toTurnCount: input.toTurnCount, diff, ...(gitFileCount !== undefined ? { gitFileCount } : {}), };} export const make = Effect.gen(function* () { const projectionSnapshotQuery = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery; const checkpointStore = yield* CheckpointStore.CheckpointStore; // Drop file sections attributed to pre-existing git history from a patch // unless the caller opted in. Attribution failures degrade to the // unfiltered patch — never block the diff on attribution. The returned // gitFileCount comes from the SAME attribution pass that produced the // patch, so UI driven by it always agrees with what is rendered; // undefined means attribution was unavailable and nothing was filtered. const applyAttributionFilter = Effect.fn("applyAttributionFilter")(function* (input: { readonly cwd: string; readonly fromCheckpointRef: CheckpointRef; readonly toCheckpointRef: CheckpointRef; readonly diff: string; readonly includeGitChanges: boolean; }): Effect.fn.Return<{ readonly diff: string; readonly gitFileCount: number | undefined }> { if (input.diff.trim().length === 0) { return { diff: input.diff, gitFileCount: undefined }; } const attribution = yield* checkpointStore .attributeCheckpointDiff({ cwd: input.cwd, fromCheckpointRef: input.fromCheckpointRef, toCheckpointRef: input.toCheckpointRef, }) .pipe(Effect.catch(() => Effect.succeed(null))); if (attribution === null) { return { diff: input.diff, gitFileCount: undefined }; } // Count only git-attributed paths with a section in THIS patch. The // attribution map can include paths absent from the patch (e.g. // whitespace-only changes elided by ignoreWhitespace) — counting those // would advertise hidden files the toggle can never reveal. let gitFileCount = 0; for (const path of listUnifiedDiffPaths(input.diff)) { if (attribution.get(path) === "git") { gitFileCount += 1; } } if (input.includeGitChanges || gitFileCount === 0) { return { diff: input.diff, gitFileCount }; } return { diff: filterUnifiedDiffFiles(input.diff, (path) => attribution.get(path) !== "git"), gitFileCount, }; }); const getTurnDiff: CheckpointDiffQuery["Service"]["getTurnDiff"] = Effect.fn("getTurnDiff")( function* (input) { const operation = "CheckpointDiffQuery.getTurnDiff"; const ignoreWhitespace = input.ignoreWhitespace ?? true; yield* Effect.annotateCurrentSpan({ "checkpoint.thread_id": input.threadId, "checkpoint.from_turn_count": input.fromTurnCount, "checkpoint.to_turn_count": input.toTurnCount, "checkpoint.ignore_whitespace": ignoreWhitespace, }); if (input.fromTurnCount === input.toTurnCount) { const emptyDiff: OrchestrationGetTurnDiffResultType = { threadId: input.threadId, fromTurnCount: input.fromTurnCount, toTurnCount: input.toTurnCount, diff: "", }; if (!isTurnDiffResult(emptyDiff)) { return yield* new CheckpointDiffResultInvalidError({ operation, threadId: input.threadId, }); } return emptyDiff; } const threadContext = yield* projectionSnapshotQuery .getThreadCheckpointContext(input.threadId) .pipe(Effect.withSpan("checkpoint.turnDiff.lookupContext")); if (Option.isNone(threadContext)) { return yield* new CheckpointThreadNotFoundError({ operation, threadId: input.threadId, }); } const maxTurnCount = threadContext.value.checkpoints.reduce( (max, checkpoint) => Math.max(max, checkpoint.checkpointTurnCount), 0, ); if (input.toTurnCount > maxTurnCount) { return yield* new CheckpointTurnRangeUnavailableError({ operation, threadId: input.threadId, requestedTurnCount: input.toTurnCount, availableTurnCount: maxTurnCount, }); } const workspaceCwd = threadContext.value.worktreePath ?? threadContext.value.workspaceRoot; if (!workspaceCwd) { return yield* new CheckpointWorkspacePathMissingError({ operation, threadId: input.threadId, }); } const fromCheckpointRef = input.fromTurnCount === 0 ? checkpointRefForThreadTurn(input.threadId, 0) : threadContext.value.checkpoints.find( (checkpoint) => checkpoint.checkpointTurnCount === input.fromTurnCount, )?.checkpointRef; if (!fromCheckpointRef) { return yield* new CheckpointRefUnavailableError({ operation, threadId: input.threadId, turnCount: input.fromTurnCount, checkpoint: "from", }); } const toCheckpointRef = threadContext.value.checkpoints.find( (checkpoint) => checkpoint.checkpointTurnCount === input.toTurnCount, )?.checkpointRef; if (!toCheckpointRef) { return yield* new CheckpointRefUnavailableError({ operation, threadId: input.threadId, turnCount: input.toTurnCount, checkpoint: "to", }); } const rawDiff = yield* checkpointStore .diffCheckpoints({ cwd: workspaceCwd, fromCheckpointRef, toCheckpointRef, fallbackFromToHead: false, ignoreWhitespace, }) .pipe(Effect.withSpan("checkpoint.turnDiff.diffCheckpoints")); const filtered = yield* applyAttributionFilter({ cwd: workspaceCwd, fromCheckpointRef, toCheckpointRef, diff: rawDiff, includeGitChanges: input.includeGitChanges ?? false, }); const turnDiff = buildTurnDiffResult(input, filtered.diff, filtered.gitFileCount); if (!isTurnDiffResult(turnDiff)) { return yield* new CheckpointDiffResultInvalidError({ operation, threadId: input.threadId, }); } return turnDiff; }, ); const getFullThreadDiff: CheckpointDiffQuery["Service"]["getFullThreadDiff"] = Effect.fn( "CheckpointDiffQuery.getFullThreadDiff", )(function* (input) { const operation = "CheckpointDiffQuery.getFullThreadDiff"; const ignoreWhitespace = input.ignoreWhitespace ?? true; yield* Effect.annotateCurrentSpan({ "checkpoint.thread_id": input.threadId, "checkpoint.from_turn_count": 0, "checkpoint.to_turn_count": input.toTurnCount, "checkpoint.ignore_whitespace": ignoreWhitespace, "checkpoint.diff_kind": "full-thread", }); if (input.toTurnCount === 0) { const emptyDiff = buildTurnDiffResult( { threadId: input.threadId, fromTurnCount: 0, toTurnCount: 0, }, "", ); if (!isTurnDiffResult(emptyDiff)) { return yield* new CheckpointDiffResultInvalidError({ operation, threadId: input.threadId, }); } return emptyDiff satisfies OrchestrationGetFullThreadDiffResult; } const threadContext = yield* projectionSnapshotQuery .getFullThreadDiffContext(input.threadId, input.toTurnCount) .pipe(Effect.withSpan("checkpoint.fullThread.lookupContext")); if (Option.isNone(threadContext)) { return yield* new CheckpointThreadNotFoundError({ operation, threadId: input.threadId, }); } if (input.toTurnCount > threadContext.value.latestCheckpointTurnCount) { return yield* new CheckpointTurnRangeUnavailableError({ operation, threadId: input.threadId, requestedTurnCount: input.toTurnCount, availableTurnCount: threadContext.value.latestCheckpointTurnCount, }); } const workspaceCwd = threadContext.value.worktreePath ?? threadContext.value.workspaceRoot; if (!workspaceCwd) { return yield* new CheckpointWorkspacePathMissingError({ operation, threadId: input.threadId, }); } if (!threadContext.value.toCheckpointRef) { return yield* new CheckpointRefUnavailableError({ operation, threadId: input.threadId, turnCount: input.toTurnCount, checkpoint: "to", }); } const fromCheckpointRef = checkpointRefForThreadTurn(input.threadId, 0); const toCheckpointRef = threadContext.value.toCheckpointRef as CheckpointRef; const rawDiff = yield* checkpointStore .diffCheckpoints({ cwd: workspaceCwd, fromCheckpointRef, toCheckpointRef, fallbackFromToHead: false, ignoreWhitespace, }) .pipe(Effect.withSpan("checkpoint.fullThread.diffCheckpoints")); const filtered = yield* applyAttributionFilter({ cwd: workspaceCwd, fromCheckpointRef, toCheckpointRef, diff: rawDiff, includeGitChanges: input.includeGitChanges ?? false, }); const turnDiff = buildTurnDiffResult( { threadId: input.threadId, fromTurnCount: 0, toTurnCount: input.toTurnCount, }, filtered.diff, filtered.gitFileCount, ); if (!isTurnDiffResult(turnDiff)) { return yield* new CheckpointDiffResultInvalidError({ operation, threadId: input.threadId, }); } return turnDiff satisfies OrchestrationGetFullThreadDiffResult; }); return CheckpointDiffQuery.of({ getTurnDiff, getFullThreadDiff, });}); export const layer = Layer.effect(CheckpointDiffQuery, make);
/**
* CheckpointDiffQuery - Query interface for computed checkpoint diffs.
*
* Provides read-only diff operations across checkpoint snapshots used by
* orchestration APIs.
*
* @module CheckpointDiffQuery
*/
import {
type CheckpointRef,
OrchestrationGetTurnDiffResult,
type OrchestrationGetFullThreadDiffInput,
type OrchestrationGetFullThreadDiffResult,
type OrchestrationGetTurnDiffInput,
type OrchestrationGetTurnDiffResult as OrchestrationGetTurnDiffResultType,
type ThreadId,
} from "@t3tools/contracts";
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
import * as ProjectionSnapshotQuery from "../orchestration/Services/ProjectionSnapshotQuery.ts";
import {
CheckpointDiffResultInvalidError,
CheckpointRefUnavailableError,
CheckpointThreadNotFoundError,
CheckpointTurnRangeUnavailableError,
CheckpointWorkspacePathMissingError,
} from "./Errors.ts";
import type { CheckpointServiceError } from "./Errors.ts";
import { checkpointRefForThreadTurn } from "./Utils.ts";
import { filterUnifiedDiffFiles, listUnifiedDiffPaths } from "./Diffs.ts";
import * as CheckpointStore from "./CheckpointStore.ts";
/** Service tag for checkpoint diff queries. */
export class CheckpointDiffQuery extends Context.Service<
CheckpointDiffQuery,
{
/**
* Read the patch diff for a single turn checkpoint transition.
*
* Verifies checkpoint availability in both projection state and filesystem.
*/
readonly getTurnDiff: (
input: OrchestrationGetTurnDiffInput,
) => Effect.Effect<OrchestrationGetTurnDiffResultType, CheckpointServiceError>;
/**
* Read the full patch diff across a thread range of checkpoints.
*
* Uses turn-diff semantics with `fromTurnCount = 0`.
*/
readonly getFullThreadDiff: (
input: OrchestrationGetFullThreadDiffInput,
) => Effect.Effect<OrchestrationGetFullThreadDiffResult, CheckpointServiceError>;
}
>()("t3/checkpointing/CheckpointDiffQuery") {}
const isTurnDiffResult = Schema.is(OrchestrationGetTurnDiffResult);
function buildTurnDiffResult(
input: {
readonly threadId: ThreadId;
readonly fromTurnCount: number;
readonly toTurnCount: number;
},
diff: string,
gitFileCount?: number,
): OrchestrationGetTurnDiffResultType {
return {
threadId: input.threadId,
fromTurnCount: input.fromTurnCount,
toTurnCount: input.toTurnCount,
diff,
...(gitFileCount !== undefined ? { gitFileCount } : {}),
};
}
export const make = Effect.gen(function* () {
const projectionSnapshotQuery = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery;
const checkpointStore = yield* CheckpointStore.CheckpointStore;
// Drop file sections attributed to pre-existing git history from a patch
// unless the caller opted in. Attribution failures degrade to the
// unfiltered patch — never block the diff on attribution. The returned
// gitFileCount comes from the SAME attribution pass that produced the
// patch, so UI driven by it always agrees with what is rendered;
// undefined means attribution was unavailable and nothing was filtered.
const applyAttributionFilter = Effect.fn("applyAttributionFilter")(function* (input: {
readonly cwd: string;
readonly fromCheckpointRef: CheckpointRef;
readonly toCheckpointRef: CheckpointRef;
readonly diff: string;
readonly includeGitChanges: boolean;
}): Effect.fn.Return<{ readonly diff: string; readonly gitFileCount: number | undefined }> {
if (input.diff.trim().length === 0) {
return { diff: input.diff, gitFileCount: undefined };
}
const attribution = yield* checkpointStore
.attributeCheckpointDiff({
cwd: input.cwd,
fromCheckpointRef: input.fromCheckpointRef,
toCheckpointRef: input.toCheckpointRef,
})
.pipe(Effect.catch(() => Effect.succeed(null)));
if (attribution === null) {
return { diff: input.diff, gitFileCount: undefined };
}
// Count only git-attributed paths with a section in THIS patch. The
// attribution map can include paths absent from the patch (e.g.
// whitespace-only changes elided by ignoreWhitespace) — counting those
// would advertise hidden files the toggle can never reveal.
let gitFileCount = 0;
for (const path of listUnifiedDiffPaths(input.diff)) {
if (attribution.get(path) === "git") {
gitFileCount += 1;
}
}
if (input.includeGitChanges || gitFileCount === 0) {
return { diff: input.diff, gitFileCount };
}
return {
diff: filterUnifiedDiffFiles(input.diff, (path) => attribution.get(path) !== "git"),
gitFileCount,
};
});
const getTurnDiff: CheckpointDiffQuery["Service"]["getTurnDiff"] = Effect.fn("getTurnDiff")(
function* (input) {
const operation = "CheckpointDiffQuery.getTurnDiff";
const ignoreWhitespace = input.ignoreWhitespace ?? true;
yield* Effect.annotateCurrentSpan({
"checkpoint.thread_id": input.threadId,
"checkpoint.from_turn_count": input.fromTurnCount,
"checkpoint.to_turn_count": input.toTurnCount,
"checkpoint.ignore_whitespace": ignoreWhitespace,
});
if (input.fromTurnCount === input.toTurnCount) {
const emptyDiff: OrchestrationGetTurnDiffResultType = {
threadId: input.threadId,
fromTurnCount: input.fromTurnCount,
toTurnCount: input.toTurnCount,
diff: "",
};
if (!isTurnDiffResult(emptyDiff)) {
return yield* new CheckpointDiffResultInvalidError({
operation,
threadId: input.threadId,
});
}
return emptyDiff;
}
const threadContext = yield* projectionSnapshotQuery
.getThreadCheckpointContext(input.threadId)
.pipe(Effect.withSpan("checkpoint.turnDiff.lookupContext"));
if (Option.isNone(threadContext)) {
return yield* new CheckpointThreadNotFoundError({
operation,
threadId: input.threadId,
});
}
const maxTurnCount = threadContext.value.checkpoints.reduce(
(max, checkpoint) => Math.max(max, checkpoint.checkpointTurnCount),
0,
);
if (input.toTurnCount > maxTurnCount) {
return yield* new CheckpointTurnRangeUnavailableError({
operation,
threadId: input.threadId,
requestedTurnCount: input.toTurnCount,
availableTurnCount: maxTurnCount,
});
}
const workspaceCwd = threadContext.value.worktreePath ?? threadContext.value.workspaceRoot;
if (!workspaceCwd) {
return yield* new CheckpointWorkspacePathMissingError({
operation,
threadId: input.threadId,
});
}
const fromCheckpointRef =
input.fromTurnCount === 0
? checkpointRefForThreadTurn(input.threadId, 0)
: threadContext.value.checkpoints.find(
(checkpoint) => checkpoint.checkpointTurnCount === input.fromTurnCount,
)?.checkpointRef;
if (!fromCheckpointRef) {
return yield* new CheckpointRefUnavailableError({
operation,
threadId: input.threadId,
turnCount: input.fromTurnCount,
checkpoint: "from",
});
}
const toCheckpointRef = threadContext.value.checkpoints.find(
(checkpoint) => checkpoint.checkpointTurnCount === input.toTurnCount,
)?.checkpointRef;
if (!toCheckpointRef) {
return yield* new CheckpointRefUnavailableError({
operation,
threadId: input.threadId,
turnCount: input.toTurnCount,
checkpoint: "to",
});
}
const rawDiff = yield* checkpointStore
.diffCheckpoints({
cwd: workspaceCwd,
fromCheckpointRef,
toCheckpointRef,
fallbackFromToHead: false,
ignoreWhitespace,
})
.pipe(Effect.withSpan("checkpoint.turnDiff.diffCheckpoints"));
const filtered = yield* applyAttributionFilter({
cwd: workspaceCwd,
fromCheckpointRef,
toCheckpointRef,
diff: rawDiff,
includeGitChanges: input.includeGitChanges ?? false,
});
const turnDiff = buildTurnDiffResult(input, filtered.diff, filtered.gitFileCount);
if (!isTurnDiffResult(turnDiff)) {
return yield* new CheckpointDiffResultInvalidError({
operation,
threadId: input.threadId,
});
}
return turnDiff;
},
);
const getFullThreadDiff: CheckpointDiffQuery["Service"]["getFullThreadDiff"] = Effect.fn(
"CheckpointDiffQuery.getFullThreadDiff",
)(function* (input) {
const operation = "CheckpointDiffQuery.getFullThreadDiff";
const ignoreWhitespace = input.ignoreWhitespace ?? true;
yield* Effect.annotateCurrentSpan({
"checkpoint.thread_id": input.threadId,
"checkpoint.from_turn_count": 0,
"checkpoint.to_turn_count": input.toTurnCount,
"checkpoint.ignore_whitespace": ignoreWhitespace,
"checkpoint.diff_kind": "full-thread",
});
if (input.toTurnCount === 0) {
const emptyDiff = buildTurnDiffResult(
{
threadId: input.threadId,
fromTurnCount: 0,
toTurnCount: 0,
},
"",
);
if (!isTurnDiffResult(emptyDiff)) {
return yield* new CheckpointDiffResultInvalidError({
operation,
threadId: input.threadId,
});
}
return emptyDiff satisfies OrchestrationGetFullThreadDiffResult;
}
const threadContext = yield* projectionSnapshotQuery
.getFullThreadDiffContext(input.threadId, input.toTurnCount)
.pipe(Effect.withSpan("checkpoint.fullThread.lookupContext"));
if (Option.isNone(threadContext)) {
return yield* new CheckpointThreadNotFoundError({
operation,
threadId: input.threadId,
});
}
if (input.toTurnCount > threadContext.value.latestCheckpointTurnCount) {
return yield* new CheckpointTurnRangeUnavailableError({
operation,
threadId: input.threadId,
requestedTurnCount: input.toTurnCount,
availableTurnCount: threadContext.value.latestCheckpointTurnCount,
});
}
const workspaceCwd = threadContext.value.worktreePath ?? threadContext.value.workspaceRoot;
if (!workspaceCwd) {
return yield* new CheckpointWorkspacePathMissingError({
operation,
threadId: input.threadId,
});
}
if (!threadContext.value.toCheckpointRef) {
return yield* new CheckpointRefUnavailableError({
operation,
threadId: input.threadId,
turnCount: input.toTurnCount,
checkpoint: "to",
});
}
const fromCheckpointRef = checkpointRefForThreadTurn(input.threadId, 0);
const toCheckpointRef = threadContext.value.toCheckpointRef as CheckpointRef;
const rawDiff = yield* checkpointStore
.diffCheckpoints({
cwd: workspaceCwd,
fromCheckpointRef,
toCheckpointRef,
fallbackFromToHead: false,
ignoreWhitespace,
})
.pipe(Effect.withSpan("checkpoint.fullThread.diffCheckpoints"));
const filtered = yield* applyAttributionFilter({
cwd: workspaceCwd,
fromCheckpointRef,
toCheckpointRef,
diff: rawDiff,
includeGitChanges: input.includeGitChanges ?? false,
});
const turnDiff = buildTurnDiffResult(
{
threadId: input.threadId,
fromTurnCount: 0,
toTurnCount: input.toTurnCount,
},
filtered.diff,
filtered.gitFileCount,
);
if (!isTurnDiffResult(turnDiff)) {
return yield* new CheckpointDiffResultInvalidError({
operation,
threadId: input.threadId,
});
}
return turnDiff satisfies OrchestrationGetFullThreadDiffResult;
});
return CheckpointDiffQuery.of({
getTurnDiff,
getFullThreadDiff,
});
});
export const layer = Layer.effect(CheckpointDiffQuery, make);