@markjaquith/agency 2.65.0 → 2.65.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "2.65.0",
3
+ "version": "2.65.1",
4
4
  "description": "Manage agentic work across repositories with durable workbases",
5
5
  "keywords": [
6
6
  "agents",
@@ -17,6 +17,13 @@ const git = (args: readonly string[], cwd?: string) => {
17
17
  }
18
18
  }
19
19
 
20
+ const jj = (args: readonly string[], cwd?: string) => {
21
+ const result = Bun.spawnSync(["jj", ...args], { cwd })
22
+ if (result.exitCode !== 0) {
23
+ throw new Error(new TextDecoder().decode(result.stderr))
24
+ }
25
+ }
26
+
20
27
  describe("ArchiveService bulk task archive", () => {
21
28
  let root: string
22
29
  let source: string
@@ -436,6 +443,98 @@ claim:
436
443
  ).toBe(true)
437
444
  })
438
445
 
446
+ test("archives a jj working-copy commit preserved by its task bookmark", async () => {
447
+ if (!Bun.which("jj")) return
448
+ const repository = join(root, "repos/agency")
449
+ await rm(repository, { recursive: true, force: true })
450
+ git(["clone", source, repository])
451
+ jj(["git", "init", "--colocate", repository])
452
+ await Bun.write(
453
+ join(root, "agency.json"),
454
+ JSON.stringify({ version: 2, vcs: "jj" }),
455
+ )
456
+ await createTask("jj-bookmarked")
457
+ await dropTask("jj-bookmarked")
458
+ const workspace = await runTestEffect(
459
+ WorktreeService.pipe(
460
+ Effect.flatMap((service) =>
461
+ service.materialize("jj-bookmarked", undefined, root),
462
+ ),
463
+ ),
464
+ )
465
+ await Bun.write(join(workspace.writablePath!, "preserved.txt"), "keep\n")
466
+ jj(
467
+ ["bookmark", "set", "task/jj-bookmarked", "-r", "@"],
468
+ workspace.writablePath!,
469
+ )
470
+
471
+ const preview = await archiveTasks(true)
472
+ expect(preview.tasks[0]).toMatchObject({
473
+ id: "jj-bookmarked",
474
+ disposition: "planned",
475
+ removedWorktrees: [workspace.writablePath!],
476
+ })
477
+ expect(
478
+ await Bun.file(join(workspace.writablePath!, "preserved.txt")).text(),
479
+ ).toBe("keep\n")
480
+
481
+ const result = await archiveTasks()
482
+ expect(result.tasks[0]).toMatchObject({
483
+ id: "jj-bookmarked",
484
+ disposition: "archived",
485
+ })
486
+ expect(await Bun.file(workspace.writablePath!).exists()).toBe(false)
487
+ const preserved = Bun.spawnSync([
488
+ "jj",
489
+ "-R",
490
+ repository,
491
+ "file",
492
+ "show",
493
+ "-r",
494
+ "task/jj-bookmarked",
495
+ 'root:"preserved.txt"',
496
+ ])
497
+ if (preserved.exitCode !== 0) {
498
+ throw new Error(preserved.stderr.toString())
499
+ }
500
+ expect(preserved.stdout.toString()).toBe("keep\n")
501
+ })
502
+
503
+ test("archives a forgotten jj workspace preserved by its task bookmark", async () => {
504
+ if (!Bun.which("jj")) return
505
+ const repository = join(root, "repos/agency")
506
+ await rm(repository, { recursive: true, force: true })
507
+ git(["clone", source, repository])
508
+ jj(["git", "init", "--colocate", repository])
509
+ await Bun.write(
510
+ join(root, "agency.json"),
511
+ JSON.stringify({ version: 2, vcs: "jj" }),
512
+ )
513
+ await createTask("jj-stale")
514
+ await dropTask("jj-stale")
515
+ const workspace = await runTestEffect(
516
+ WorktreeService.pipe(
517
+ Effect.flatMap((service) =>
518
+ service.materialize("jj-stale", undefined, root),
519
+ ),
520
+ ),
521
+ )
522
+ await Bun.write(join(workspace.writablePath!, "preserved.txt"), "keep\n")
523
+ jj(["bookmark", "set", "task/jj-stale", "-r", "@"], workspace.writablePath!)
524
+ jj(["-R", repository, "workspace", "forget", "agency-jj-stale-task-agency"])
525
+
526
+ const result = await archiveTasks(true)
527
+
528
+ expect(result.tasks[0]).toMatchObject({
529
+ id: "jj-stale",
530
+ disposition: "planned",
531
+ removedWorktrees: [workspace.writablePath!],
532
+ })
533
+ expect(
534
+ await Bun.file(join(workspace.writablePath!, "preserved.txt")).text(),
535
+ ).toBe("keep\n")
536
+ })
537
+
439
538
  test("rolls back the entire cohort when application fails", async () => {
440
539
  await runTestEffect(
441
540
  EpicService.pipe(
@@ -474,7 +474,9 @@ const inspectExecution = (
474
474
  : yield* backend.workspaceHead(checkoutPath)
475
475
  : null
476
476
  const dirty =
477
- exists && atPath ? yield* backend.workspaceDirty(checkoutPath) : null
477
+ exists && atPath
478
+ ? yield* backend.workspaceDirty(checkoutPath)
479
+ : (atPath?.dirty ?? null)
478
480
  const expectedCommit =
479
481
  "branch" in checkout && context?.skipWritableRevisionResolution
480
482
  ? actualCommit
@@ -1659,10 +1661,26 @@ const removeJj = (
1659
1661
  bookmark: string
1660
1662
  }[] = []
1661
1663
  for (const checkout of inspection.checkouts) {
1664
+ const repositoryPath = join(root, "repos", checkout.repo)
1665
+ const registered = checkout.registeredPath
1666
+ ? (yield* backend.listWorkspaces(repositoryPath)).find(
1667
+ (workspace) => workspace.path === checkout.registeredPath,
1668
+ )
1669
+ : undefined
1662
1670
  if (checkout.dirty === true && options.persistResume === false) {
1663
- return yield* new WorktreeError({
1664
- message: `Failed to remove workspace for '${checkout.repo}': checkout has uncommitted changes`,
1665
- })
1671
+ const preservedByBookmark =
1672
+ checkout.kind === "writable" &&
1673
+ registered?.commit !== null &&
1674
+ registered?.commit !== undefined &&
1675
+ (yield* backend.resolveRevision(
1676
+ repositoryPath,
1677
+ checkout.requestedRef,
1678
+ )) === registered.commit
1679
+ if (!preservedByBookmark) {
1680
+ return yield* new WorktreeError({
1681
+ message: `Failed to remove workspace for '${checkout.repo}': checkout has uncommitted changes`,
1682
+ })
1683
+ }
1666
1684
  }
1667
1685
  if (
1668
1686
  checkout.exists &&
@@ -1673,7 +1691,6 @@ const removeJj = (
1673
1691
  message: `Failed to remove workspace for '${checkout.repo}': checkout cleanliness could not be verified`,
1674
1692
  })
1675
1693
  }
1676
- const repositoryPath = join(root, "repos", checkout.repo)
1677
1694
  if (!checkout.registeredPath) {
1678
1695
  const recoveryRevision = recoverable.get(checkout.path)
1679
1696
  if (!recoveryRevision) continue
@@ -1696,9 +1713,6 @@ const removeJj = (
1696
1713
  })
1697
1714
  continue
1698
1715
  }
1699
- const registered = (yield* backend.listWorkspaces(repositoryPath)).find(
1700
- (workspace) => workspace.path === checkout.registeredPath,
1701
- )
1702
1716
  if (!registered?.name || !checkout.actualCommit || !registered.commit) {
1703
1717
  return yield* new WorktreeError({
1704
1718
  message: `Cannot identify jj workspace at ${checkout.registeredPath}`,