1// @effect-diagnostics nodeBuiltinImport:off
2import * as NodePath from "node:path";
3
4import * as NodeServices from "@effect/platform-node/NodeServices";
5import { it } from "@effect/vitest";
6import { ThreadId, type VcsError } from "@t3tools/contracts";
7import * as HostProcess from "@t3tools/shared/HostProcess";
8import * as Effect from "effect/Effect";
9import * as FileSystem from "effect/FileSystem";
10import * as Layer from "effect/Layer";
11import * as PlatformError from "effect/PlatformError";
12import * as Scope from "effect/Scope";
13import { describe, expect } from "vite-plus/test";
14
15import { checkpointRefForThreadTurn } from "./Utils.ts";
16import { parseTurnDiffFilesFromNumstat } from "./Diffs.ts";
17import * as CheckpointStore from "./CheckpointStore.ts";
18import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
19import * as VcsProcess from "../vcs/VcsProcess.ts";
20import * as ServerConfig from "../config.ts";
21
22const layerServerConfig = ServerConfig.ServerConfig.layerTest(process.cwd(), {
23 prefix: "t3-checkpoint-store-test-",
24});
25const layerVcsProcessTest = VcsProcess.layer.pipe(Layer.provide(NodeServices.layer));
26const layerVcsDriverTest = VcsDriverRegistry.layer.pipe(Layer.provide(layerVcsProcessTest));
27const layerCheckpointStoreTest = CheckpointStore.layer.pipe(
28 Layer.provideMerge(layerVcsDriverTest),
29 Layer.provideMerge(NodeServices.layer),
30);
31const layerTest = layerCheckpointStoreTest.pipe(
32 Layer.provideMerge(layerVcsProcessTest),
33 Layer.provideMerge(layerVcsDriverTest),
34 Layer.provideMerge(layerServerConfig),
35 Layer.provideMerge(NodeServices.layer),
36);
37
38function makeTmpDir(
39 prefix = "checkpoint-store-test-",
40): Effect.Effect<string, PlatformError.PlatformError, FileSystem.FileSystem | Scope.Scope> {
41 return Effect.gen(function* () {
42 const fileSystem = yield* FileSystem.FileSystem;
43 return yield* fileSystem.makeTempDirectoryScoped({ prefix });
44 });
45}
46
47function writeTextFile(
48 filePath: string,
49 contents: string,
50): Effect.Effect<void, PlatformError.PlatformError, FileSystem.FileSystem> {
51 return Effect.gen(function* () {
52 const fileSystem = yield* FileSystem.FileSystem;
53 yield* fileSystem.writeFileString(filePath, contents);
54 });
55}
56
57function git(
58 cwd: string,
59 args: ReadonlyArray<string>,
60 options: { readonly committedAt?: string; readonly allowNonZeroExit?: boolean } = {},
61): Effect.Effect<string, VcsError, VcsProcess.VcsProcess> {
62 return Effect.gen(function* () {
63 const vcsProcess = yield* VcsProcess.VcsProcess;
64 const result = yield* vcsProcess.run({
65 operation: "CheckpointStore.test.git",
66 command: "git",
67 cwd,
68 args,
69 timeoutMs: 10_000,
70 ...(options.committedAt === undefined
71 ? {}
72 : { env: { ...process.env, GIT_COMMITTER_DATE: options.committedAt } }),
73 ...(options.allowNonZeroExit === undefined
74 ? {}
75 : { allowNonZeroExit: options.allowNonZeroExit }),
76 });
77 return result.stdout.trim();
78 });
79}
80
81function initRepoWithCommit(
82 cwd: string,
83): Effect.Effect<
84 void,
85 VcsError | PlatformError.PlatformError,
86 VcsProcess.VcsProcess | FileSystem.FileSystem
87> {
88 return Effect.gen(function* () {
89 yield* git(cwd, ["init"]);
90 yield* git(cwd, ["config", "user.email", "test@test.com"]);
91 yield* git(cwd, ["config", "user.name", "Test"]);
92 yield* writeTextFile(NodePath.join(cwd, "README.md"), "# test\n");
93 yield* git(cwd, ["add", "."]);
94 yield* git(cwd, ["commit", "-m", "initial commit"]);
95 });
96}
97
98function buildLargeText(lineCount = 5_000): string {
99 return Array.from({ length: lineCount }, (_, index) => `line ${String(index).padStart(5, "0")}`)
100 .join("\n")
101 .concat("\n");
102}
103
104it.layer(layerTest)("CheckpointStore.layer", (it) => {
105 describe("isGitRepository", () => {
106 it.effect("returns false when no Git repository is detected", () =>
107 Effect.gen(function* () {
108 const tmp = yield* makeTmpDir();
109 const checkpointStore = yield* CheckpointStore.CheckpointStore;
110
111 expect(yield* checkpointStore.isGitRepository(tmp)).toBe(false);
112 }),
113 );
114
115 it.effect("returns true when a Git repository is detected", () =>
116 Effect.gen(function* () {
117 const tmp = yield* makeTmpDir();
118 yield* initRepoWithCommit(tmp);
119 const checkpointStore = yield* CheckpointStore.CheckpointStore;
120
121 expect(yield* checkpointStore.isGitRepository(tmp)).toBe(true);
122 }),
123 );
124 });
125
126 it.effect("detects a nested workspace without its own .git entry", () =>
127 Effect.gen(function* () {
128 const tmp = yield* makeTmpDir();
129 yield* initRepoWithCommit(tmp);
130 const fileSystem = yield* FileSystem.FileSystem;
131 const nested = NodePath.join(tmp, "packages", "nested");
132 yield* fileSystem.makeDirectory(nested, { recursive: true });
133 const checkpointStore = yield* CheckpointStore.CheckpointStore;
134 expect(yield* checkpointStore.isGitRepository(nested)).toBe(true);
135 }),
136 );
137 describe("diffCheckpoints", () => {
138 it.effect("returns full oversized checkpoint diffs without truncation", () =>
139 Effect.gen(function* () {
140 const tmp = yield* makeTmpDir();
141 yield* initRepoWithCommit(tmp);
142 const checkpointStore = yield* CheckpointStore.CheckpointStore;
143 const threadId = ThreadId.make("thread-checkpoint-store");
144 const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0);
145 const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1);
146
147 yield* checkpointStore.captureCheckpoint({
148 cwd: tmp,
149 checkpointRef: fromCheckpointRef,
150 });
151 yield* writeTextFile(NodePath.join(tmp, "README.md"), buildLargeText());
152 yield* checkpointStore.captureCheckpoint({
153 cwd: tmp,
154 checkpointRef: toCheckpointRef,
155 });
156
157 const diff = yield* checkpointStore.diffCheckpoints({
158 cwd: tmp,
159 fromCheckpointRef,
160 toCheckpointRef,
161 ignoreWhitespace: true,
162 });
163
164 expect(diff).toContain("diff --git");
165 expect(diff).not.toContain("[truncated]");
166 expect(diff).toContain("+line 04999");
167 }),
168 );
169
170 it.effect("keeps a/ and b/ patch prefixes when the repository disables them", () =>
171 Effect.gen(function* () {
172 const tmp = yield* makeTmpDir();
173 yield* initRepoWithCommit(tmp);
174 yield* git(tmp, ["config", "diff.noprefix", "true"]);
175 const checkpointStore = yield* CheckpointStore.CheckpointStore;
176 const threadId = ThreadId.make("thread-checkpoint-store-noprefix");
177 const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0);
178 const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1);
179
180 yield* checkpointStore.captureCheckpoint({
181 cwd: tmp,
182 checkpointRef: fromCheckpointRef,
183 });
184 yield* writeTextFile(NodePath.join(tmp, "README.md"), "# changed\n");
185 yield* checkpointStore.captureCheckpoint({
186 cwd: tmp,
187 checkpointRef: toCheckpointRef,
188 });
189
190 const diff = yield* checkpointStore.diffCheckpoints({
191 cwd: tmp,
192 fromCheckpointRef,
193 toCheckpointRef,
194 ignoreWhitespace: false,
195 });
196
197 expect(diff).toContain("diff --git a/README.md b/README.md");
198 }),
199 );
200
201 it.effect("can hide indentation churn when changes wrap existing lines", () =>
202 Effect.gen(function* () {
203 const tmp = yield* makeTmpDir();
204 yield* initRepoWithCommit(tmp);
205 const checkpointStore = yield* CheckpointStore.CheckpointStore;
206 const threadId = ThreadId.make("thread-checkpoint-store-whitespace");
207 const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0);
208 const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1);
209
210 const componentPath = NodePath.join(tmp, "Component.tsx");
211 yield* writeTextFile(
212 componentPath,
213 [
214 "export function View() {",
215 " return (",
216 " <section>",
217 " <h1>Title</h1>",
218 " <p>Body</p>",
219 " </section>",
220 " );",
221 "}",
222 "",
223 ].join("\n"),
224 );
225 yield* checkpointStore.captureCheckpoint({
226 cwd: tmp,
227 checkpointRef: fromCheckpointRef,
228 });
229 yield* writeTextFile(
230 componentPath,
231 [
232 "export function View() {",
233 " return (",
234 " <section>",
235 " {isReady ? (",
236 " <div>",
237 " <h1>Title</h1>",
238 " <p>Body</p>",
239 " </div>",
240 " ) : null}",
241 " </section>",
242 " );",
243 "}",
244 "",
245 ].join("\n"),
246 );
247 yield* checkpointStore.captureCheckpoint({
248 cwd: tmp,
249 checkpointRef: toCheckpointRef,
250 });
251
252 const normalDiff = yield* checkpointStore.diffCheckpoints({
253 cwd: tmp,
254 fromCheckpointRef,
255 toCheckpointRef,
256 ignoreWhitespace: false,
257 });
258 const whitespaceIgnoredDiff = yield* checkpointStore.diffCheckpoints({
259 cwd: tmp,
260 fromCheckpointRef,
261 toCheckpointRef,
262 ignoreWhitespace: true,
263 });
264
265 expect(normalDiff).toContain("diff --git");
266 expect(normalDiff).toContain("- <h1>Title</h1>");
267 expect(normalDiff).toContain("+ <h1>Title</h1>");
268 expect(whitespaceIgnoredDiff).toContain("diff --git");
269 expect(whitespaceIgnoredDiff).toContain("+ {isReady ? (");
270 expect(whitespaceIgnoredDiff).toContain("+ <div>");
271 expect(whitespaceIgnoredDiff).not.toContain("- <h1>Title</h1>");
272 expect(whitespaceIgnoredDiff).not.toContain("+ <h1>Title</h1>");
273
274 for (const ignoreWhitespace of [false, true]) {
275 const numstat = yield* checkpointStore.diffCheckpoints({
276 cwd: tmp,
277 fromCheckpointRef,
278 toCheckpointRef,
279 ignoreWhitespace,
280 format: "numstat",
281 });
282 expect(parseTurnDiffFilesFromNumstat(numstat)).toEqual([
283 {
284 path: "Component.tsx",
285 additions: ignoreWhitespace ? 4 : 6,
286 deletions: ignoreWhitespace ? 0 : 2,
287 },
288 ]);
289 }
290 }),
291 );
292 });
293
294 describe("checkpoint file summaries", () => {
295 it.effect("counts changes whose full patch exceeds the output limit", () =>
296 Effect.gen(function* () {
297 const tmp = yield* makeTmpDir();
298 yield* initRepoWithCommit(tmp);
299 const checkpointStore = yield* CheckpointStore.CheckpointStore;
300 const threadId = ThreadId.make("large-checkpoint-summary");
301 const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0);
302 const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1);
303 const filePath = NodePath.join(tmp, "README.md");
304 const lineCount = 20_000;
305 yield* writeTextFile(filePath, `${"before".repeat(50)}\n`.repeat(lineCount));
306 yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: fromCheckpointRef });
307 yield* writeTextFile(filePath, `${"after".repeat(60)}\n`.repeat(lineCount));
308 yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: toCheckpointRef });
309
310 const numstat = yield* checkpointStore.diffCheckpoints({
311 cwd: tmp,
312 fromCheckpointRef,
313 toCheckpointRef,
314 ignoreWhitespace: false,
315 format: "numstat",
316 });
317
318 expect(parseTurnDiffFilesFromNumstat(numstat)).toEqual([
319 { path: "README.md", additions: lineCount, deletions: lineCount },
320 ]);
321 expect(numstat.length).toBeLessThan(100);
322 }),
323 );
324
325 it.effect("preserves file paths and turn ranges without changing the user index", () =>
326 Effect.gen(function* () {
327 const tmp = yield* makeTmpDir();
328 yield* initRepoWithCommit(tmp);
329 yield* git(tmp, ["config", "diff.renames", "copies"]);
330 const fileSystem = yield* FileSystem.FileSystem;
331 const checkpointStore = yield* CheckpointStore.CheckpointStore;
332 const threadId = ThreadId.make("checkpoint-summary-paths");
333 const baseline = checkpointRefForThreadTurn(threadId, 0);
334 const firstTurn = checkpointRefForThreadTurn(threadId, 1);
335 const secondTurn = checkpointRefForThreadTurn(threadId, 2);
336 const copiedText = Array.from({ length: 20 }, (_, index) => `copy line ${index}\n`).join(
337 "",
338 );
339 const platform = yield* HostProcess.Platform;
340 const renamedPath = platform === "win32" ? "renamed café.txt" : "renamed\tcafé\nname.txt";
341 const addedPath = platform === "win32" ? "new café.txt" : "new\tfile\n名.txt";
342 for (const [path, contents] of Object.entries({
343 "copy-source.txt": copiedText,
344 "deleted.txt": "delete me\n",
345 "rename-old.txt": "before\nkeep one\nkeep two\nkeep three\n",
346 "binary.bin": "\0before",
347 })) {
348 yield* writeTextFile(NodePath.join(tmp, path), contents);
349 }
350 yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: baseline });
351
352 yield* fileSystem.rename(
353 NodePath.join(tmp, "rename-old.txt"),
354 NodePath.join(tmp, renamedPath),
355 );
356 yield* fileSystem.remove(NodePath.join(tmp, "deleted.txt"));
357 for (const [path, contents] of Object.entries({
358 "copy-source.txt": `${copiedText}one more\n`,
359 "copied.txt": copiedText,
360 [renamedPath]: "after\nkeep one\nkeep two\nkeep three\n",
361 "binary.bin": "\0after",
362 "empty.txt": "",
363 [addedPath]: "first\nsecond\n",
364 })) {
365 yield* writeTextFile(NodePath.join(tmp, path), contents);
366 }
367 yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: firstTurn });
368 const userIndex = yield* fileSystem.readFile(NodePath.join(tmp, ".git/index"));
369 const input = {
370 cwd: tmp,
371 fromCheckpointRef: baseline,
372 toCheckpointRef: firstTurn,
373 ignoreWhitespace: false,
374 format: "numstat" as const,
375 };
376 const firstSummary = parseTurnDiffFilesFromNumstat(
377 yield* checkpointStore.diffCheckpoints(input),
378 );
379 const expectedFiles = [
380 { path: "binary.bin", additions: 0, deletions: 0 },
381 { path: "copied.txt", previousPath: "copy-source.txt", additions: 0, deletions: 0 },
382 { path: "copy-source.txt", additions: 1, deletions: 0 },
383 { path: "deleted.txt", additions: 0, deletions: 1 },
384 { path: "empty.txt", additions: 0, deletions: 0 },
385 { path: addedPath, additions: 2, deletions: 0 },
386 { path: renamedPath, previousPath: "rename-old.txt", additions: 1, deletions: 1 },
387 ].toSorted((left, right) => left.path.localeCompare(right.path));
388 expect(firstSummary).toEqual(expectedFiles);
389
390 yield* fileSystem.remove(NodePath.join(tmp, "empty.txt"));
391 yield* writeTextFile(NodePath.join(tmp, "copy-source.txt"), "replacement\n");
392 yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: secondTurn });
393 const secondSummary = parseTurnDiffFilesFromNumstat(
394 yield* checkpointStore.diffCheckpoints({
395 ...input,
396 fromCheckpointRef: firstTurn,
397 toCheckpointRef: secondTurn,
398 }),
399 );
400 expect(secondSummary).toEqual([
401 { path: "copy-source.txt", additions: 1, deletions: 21 },
402 { path: "empty.txt", additions: 0, deletions: 0 },
403 ]);
404
405 const inclusiveSummary = parseTurnDiffFilesFromNumstat(
406 yield* checkpointStore.diffCheckpoints({ ...input, toCheckpointRef: secondTurn }),
407 );
408 expect(inclusiveSummary).toEqual(
409 expectedFiles
410 .filter((file) => file.path !== "empty.txt")
411 .map((file) =>
412 file.path === "copy-source.txt" ? { ...file, additions: 1, deletions: 20 } : file,
413 ),
414 );
415 expect(
416 yield* checkpointStore.diffCheckpoints({ ...input, toCheckpointRef: baseline }),
417 ).toBe("");
418 expect(yield* fileSystem.readFile(NodePath.join(tmp, ".git/index"))).toEqual(userIndex);
419 }),
420 );
421
422 it.effect("uses HEAD for a missing baseline only when requested", () =>
423 Effect.gen(function* () {
424 const tmp = yield* makeTmpDir();
425 yield* initRepoWithCommit(tmp);
426 const checkpointStore = yield* CheckpointStore.CheckpointStore;
427 const threadId = ThreadId.make("checkpoint-summary-fallback");
428 const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0);
429 const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1);
430 yield* writeTextFile(NodePath.join(tmp, "README.md"), "changed\n");
431 yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: toCheckpointRef });
432 const input = {
433 cwd: tmp,
434 fromCheckpointRef,
435 toCheckpointRef,
436 ignoreWhitespace: false,
437 format: "numstat" as const,
438 };
439
440 const error = yield* Effect.flip(checkpointStore.diffCheckpoints(input));
441 expect(error._tag).toBe("VcsProcessExitError");
442 const numstat = yield* checkpointStore.diffCheckpoints({
443 ...input,
444 fallbackFromToHead: true,
445 });
446 expect(parseTurnDiffFilesFromNumstat(numstat)).toEqual([
447 { path: "README.md", additions: 1, deletions: 1 },
448 ]);
449 }),
450 );
451 it.effect("limits a diff to repository paths from a subdirectory workspace", () =>
452 Effect.gen(function* () {
453 const tmp = yield* makeTmpDir();
454 yield* initRepoWithCommit(tmp);
455 const fileSystem = yield* FileSystem.FileSystem;
456 const cwd = NodePath.join(tmp, "app");
457 yield* fileSystem.makeDirectory(cwd);
458 const checkpointStore = yield* CheckpointStore.CheckpointStore;
459 const threadId = ThreadId.make("checkpoint-file-paths-subdirectory");
460 const refs = {
461 cwd,
462 fromCheckpointRef: checkpointRefForThreadTurn(threadId, 0),
463 toCheckpointRef: checkpointRefForThreadTurn(threadId, 1),
464 };
465 yield* checkpointStore.captureCheckpoint({ cwd, checkpointRef: refs.fromCheckpointRef });
466 yield* writeTextFile(NodePath.join(cwd, "kept.txt"), "kept\n");
467 yield* writeTextFile(NodePath.join(tmp, "README.md"), "# hidden\n");
468 yield* checkpointStore.captureCheckpoint({ cwd, checkpointRef: refs.toCheckpointRef });
469
470 const patch = yield* checkpointStore.diffCheckpoints({
471 ...refs,
472 ignoreWhitespace: false,
473 filePaths: ["app/kept.txt"],
474 });
475 expect(patch).toContain("+kept");
476 expect(patch).not.toContain("hidden");
477 }),
478 );
479 });
480
481 describe("listAuthoredPaths", () => {
482 it.effect("leaves out paths that only a merged upstream changed", () =>
483 Effect.gen(function* () {
484 const tmp = yield* makeTmpDir();
485 yield* initRepoWithCommit(tmp);
486 const checkpointStore = yield* CheckpointStore.CheckpointStore;
487 const threadId = ThreadId.make("checkpoint-authored-paths");
488 const refs = {
489 cwd: tmp,
490 fromCheckpointRef: checkpointRefForThreadTurn(threadId, 0),
491 toCheckpointRef: checkpointRefForThreadTurn(threadId, 1),
492 };
493 const commitBeforeTurn = (file: string, contents: string) =>
494 Effect.gen(function* () {
495 yield* writeTextFile(NodePath.join(tmp, file), contents);
496 yield* git(tmp, ["add", "."]);
497 yield* git(tmp, ["commit", "-m", file], { committedAt: "2020-01-01T00:00:00Z" });
498 });
499
500 yield* git(tmp, ["checkout", "-b", "upstream"]);
501 yield* commitBeforeTurn("upstream.txt", "upstream\n");
502 yield* commitBeforeTurn("shared.txt", "upstream\n");
503 yield* git(tmp, ["checkout", "-"]);
504 yield* commitBeforeTurn("shared.txt", "ours\n");
505 yield* checkpointStore.captureCheckpoint({
506 cwd: tmp,
507 checkpointRef: refs.fromCheckpointRef,
508 });
509
510 yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: refs.toCheckpointRef });
511 expect(yield* checkpointStore.listAuthoredPaths(refs)).toBeNull();
512
513 // The turn merges upstream, fixes the conflict by taking upstream, and edits one more file.
514 yield* git(tmp, ["merge", "upstream", "--no-edit"], { allowNonZeroExit: true });
515 yield* writeTextFile(NodePath.join(tmp, "shared.txt"), "upstream\n");
516 yield* git(tmp, ["commit", "-am", "merge upstream"]);
517 yield* writeTextFile(NodePath.join(tmp, "README.md"), "# edited\n");
518 yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: refs.toCheckpointRef });
519
520 const numstat = yield* checkpointStore.diffCheckpoints({
521 ...refs,
522 ignoreWhitespace: false,
523 format: "numstat",
524 });
525 expect(parseTurnDiffFilesFromNumstat(numstat).map((file) => file.path)).toEqual([
526 "README.md",
527 "shared.txt",
528 "upstream.txt",
529 ]);
530 expect(yield* checkpointStore.listAuthoredPaths(refs)).toEqual(
531 new Set(["README.md", "shared.txt"]),
532 );
533 }),
534 );
535
536 it.effect("leaves out paths that only a rebase onto upstream brought in", () =>
537 Effect.gen(function* () {
538 const tmp = yield* makeTmpDir();
539 yield* initRepoWithCommit(tmp);
540 const checkpointStore = yield* CheckpointStore.CheckpointStore;
541 const threadId = ThreadId.make("checkpoint-authored-paths-rebase");
542 const refs = {
543 cwd: tmp,
544 fromCheckpointRef: checkpointRefForThreadTurn(threadId, 0),
545 toCheckpointRef: checkpointRefForThreadTurn(threadId, 1),
546 };
547 const commitBeforeTurn = (file: string) =>
548 Effect.gen(function* () {
549 yield* writeTextFile(NodePath.join(tmp, file), `${file}\n`);
550 yield* git(tmp, ["add", "."]);
551 yield* git(tmp, ["commit", "-m", file], { committedAt: "2020-01-01T00:00:00Z" });
552 });
553
554 yield* git(tmp, ["checkout", "-b", "upstream"]);
555 yield* commitBeforeTurn("upstream.txt");
556 yield* git(tmp, ["checkout", "-"]);
557 yield* commitBeforeTurn("feature.txt");
558 yield* checkpointStore.captureCheckpoint({
559 cwd: tmp,
560 checkpointRef: refs.fromCheckpointRef,
561 });
562 yield* git(tmp, ["rebase", "upstream"]);
563 yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: refs.toCheckpointRef });
564
565 expect(yield* checkpointStore.listAuthoredPaths(refs)).toEqual(new Set(["feature.txt"]));
566 }),
567 );
568 });
569});