@markjaquith/agency 2.49.0 → 2.51.0

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.
Files changed (50) hide show
  1. package/README.md +56 -9
  2. package/cli.ts +56 -1
  3. package/package.json +1 -1
  4. package/schemas/agency-graph-v1.schema.json +142 -38
  5. package/src/cli-parser.test.ts +81 -0
  6. package/src/cli-parser.ts +78 -1
  7. package/src/cli.test.ts +4 -4
  8. package/src/commands/init.test.ts +2 -0
  9. package/src/commands/review.ts +38 -0
  10. package/src/commands/task.ts +27 -5
  11. package/src/commands/vcs.test.ts +75 -0
  12. package/src/commands/vcs.ts +72 -0
  13. package/src/commands/work.test.ts +2 -0
  14. package/src/commands/work.ts +2 -2
  15. package/src/commands/worktree.ts +1 -1
  16. package/src/graph-schema.test.ts +1 -1
  17. package/src/graph-schema.ts +36 -13
  18. package/src/protocol.ts +1 -0
  19. package/src/services/ArchiveService.test.ts +2 -2
  20. package/src/services/ArchiveService.ts +24 -0
  21. package/src/services/ClaimService.ts +3 -2
  22. package/src/services/ContextService.ts +118 -17
  23. package/src/services/DoctorService.ts +58 -7
  24. package/src/services/GraphMutationService.ts +5 -0
  25. package/src/services/GraphService.ts +119 -54
  26. package/src/services/PhaseService.ts +196 -70
  27. package/src/services/PullRequestService.test.ts +2 -2
  28. package/src/services/PullRequestService.ts +37 -33
  29. package/src/services/ReadinessService.ts +7 -3
  30. package/src/services/RepositoryService.test.ts +31 -1
  31. package/src/services/RepositoryService.ts +63 -31
  32. package/src/services/ReviewService.test.ts +472 -0
  33. package/src/services/ReviewService.ts +427 -0
  34. package/src/services/SyncService.test.ts +69 -2
  35. package/src/services/SyncService.ts +161 -90
  36. package/src/services/TaskPhaseService.test.ts +80 -0
  37. package/src/services/TaskService.ts +82 -9
  38. package/src/services/VcsMigrationService.test.ts +211 -0
  39. package/src/services/VcsMigrationService.ts +816 -0
  40. package/src/services/VersionControlService.test.ts +100 -0
  41. package/src/services/VersionControlService.ts +479 -0
  42. package/src/services/WorkbaseService.ts +7 -2
  43. package/src/services/WorktreeService.test.ts +88 -17
  44. package/src/services/WorktreeService.ts +865 -329
  45. package/src/test-utils.ts +12 -0
  46. package/src/work-view.ts +14 -6
  47. package/src/workbase/AGENTS.md +2 -2
  48. package/src/workbase/schemas.test.ts +57 -0
  49. package/src/workbase/schemas.ts +57 -0
  50. package/src/workbase/version-control.ts +5 -0
@@ -1,6 +1,6 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from "bun:test"
2
2
  import { Effect } from "effect"
3
- import { mkdir, realpath, rename, rm } from "node:fs/promises"
3
+ import { mkdir, realpath, rename, rm, stat } from "node:fs/promises"
4
4
  import { join } from "node:path"
5
5
  import {
6
6
  captureErrors,
@@ -23,6 +23,17 @@ const git = async (args: string[], cwd?: string) => {
23
23
  throw new Error(await new Response(process.stderr).text())
24
24
  }
25
25
 
26
+ const jj = async (args: string[], cwd?: string) => {
27
+ const process = Bun.spawn(["jj", ...args], {
28
+ cwd,
29
+ stdout: "pipe",
30
+ stderr: "pipe",
31
+ })
32
+ await process.exited
33
+ if (process.exitCode !== 0)
34
+ throw new Error(await new Response(process.stderr).text())
35
+ }
36
+
26
37
  describe("WorktreeService", () => {
27
38
  let root: string
28
39
  let source: string
@@ -81,18 +92,78 @@ describe("WorktreeService", () => {
81
92
  )
82
93
 
83
94
  expect(
84
- await Bun.file(join(workspace.writablePath, "README.md")).text(),
95
+ await Bun.file(join(workspace.writablePath!, "README.md")).text(),
85
96
  ).toBe("example\n")
86
97
  expect(
87
98
  await Bun.file(join(workspace.codePath, "effect/README.md")).text(),
88
99
  ).toBe("example\n")
89
100
  const branch = Bun.spawnSync(
90
- ["git", "-C", workspace.writablePath, "branch", "--show-current"],
101
+ ["git", "-C", workspace.writablePath!, "branch", "--show-current"],
91
102
  { stdout: "pipe" },
92
103
  )
93
104
  expect(new TextDecoder().decode(branch.stdout).trim()).toBe("task/example")
94
105
  })
95
106
 
107
+ test("materializes and removes a jj workspace for a jj workbase", async () => {
108
+ if (!Bun.which("jj")) return
109
+ const repository = join(root, "repos/agency")
110
+ await rm(repository, { recursive: true, force: true })
111
+ await git(["clone", source, repository])
112
+ await jj(["git", "init", "--colocate", repository])
113
+ await Bun.write(
114
+ join(root, "agency.json"),
115
+ JSON.stringify({ version: 2, vcs: "jj" }),
116
+ )
117
+ await runTestEffect(
118
+ TaskService.pipe(
119
+ Effect.flatMap((service) =>
120
+ service.create(
121
+ {
122
+ id: "jj-example",
123
+ ticketUrl: null,
124
+ repo: "agency",
125
+ branch: "task/jj-example",
126
+ base: "main",
127
+ },
128
+ root,
129
+ ),
130
+ ),
131
+ ),
132
+ )
133
+
134
+ const workspace = await runTestEffect(
135
+ WorktreeService.pipe(
136
+ Effect.flatMap((service) =>
137
+ service.materialize("jj-example", undefined, root),
138
+ ),
139
+ ),
140
+ )
141
+ const jjMetadata = await stat(join(workspace.writablePath!, ".jj"))
142
+ expect(jjMetadata.isFile() || jjMetadata.isDirectory()).toBe(true)
143
+ const inspection = await runTestEffect(
144
+ WorktreeService.pipe(
145
+ Effect.flatMap((service) =>
146
+ service.inspect("jj-example", undefined, root),
147
+ ),
148
+ ),
149
+ )
150
+ expect(inspection.conflicts).toEqual([])
151
+ expect(inspection.checkouts[0]).toMatchObject({
152
+ exists: true,
153
+ registered: true,
154
+ actualBranch: "task/jj-example",
155
+ })
156
+
157
+ await runTestEffect(
158
+ WorktreeService.pipe(
159
+ Effect.flatMap((service) =>
160
+ service.remove("jj-example", undefined, root),
161
+ ),
162
+ ),
163
+ )
164
+ expect(await Bun.file(workspace.writablePath!).exists()).toBe(false)
165
+ })
166
+
96
167
  test("does not fetch the origin for an existing writable worktree", async () => {
97
168
  await runTestEffect(
98
169
  TaskService.pipe(
@@ -272,7 +343,7 @@ describe("WorktreeService", () => {
272
343
  ),
273
344
  )
274
345
  expect(
275
- await Bun.file(join(workspace.writablePath, "README.md")).text(),
346
+ await Bun.file(join(workspace.writablePath!, "README.md")).text(),
276
347
  ).toBe("example\n")
277
348
  })
278
349
 
@@ -599,7 +670,7 @@ pr: null
599
670
 
600
671
  expect(
601
672
  await Bun.file(
602
- join(originalWorkspace.writablePath, "README.md"),
673
+ join(originalWorkspace.writablePath!, "README.md"),
603
674
  ).exists(),
604
675
  ).toBe(false)
605
676
  const movedWorkspace = await runTestEffect(
@@ -613,10 +684,10 @@ pr: null
613
684
  join(root, "tasks/promoted/phases/implementation/code/agency"),
614
685
  )
615
686
  expect(
616
- await Bun.file(join(movedWorkspace.writablePath, "README.md")).text(),
687
+ await Bun.file(join(movedWorkspace.writablePath!, "README.md")).text(),
617
688
  ).toBe("example\n")
618
689
  const status = Bun.spawnSync(
619
- ["git", "-C", movedWorkspace.writablePath, "status", "--porcelain"],
690
+ ["git", "-C", movedWorkspace.writablePath!, "status", "--porcelain"],
620
691
  { stdout: "pipe", stderr: "pipe" },
621
692
  )
622
693
  expect(status.exitCode).toBe(0)
@@ -1072,7 +1143,7 @@ pr: null
1072
1143
  ),
1073
1144
  )
1074
1145
  await Bun.write(
1075
- join(workspace.writablePath, "uncommitted.txt"),
1146
+ join(workspace.writablePath!, "uncommitted.txt"),
1076
1147
  "keep me\n",
1077
1148
  )
1078
1149
 
@@ -1084,7 +1155,7 @@ pr: null
1084
1155
  ),
1085
1156
  ).rejects.toThrow("Failed to remove worktree for 'agency'")
1086
1157
  expect(
1087
- await Bun.file(join(workspace.writablePath, "uncommitted.txt")).text(),
1158
+ await Bun.file(join(workspace.writablePath!, "uncommitted.txt")).text(),
1088
1159
  ).toBe("keep me\n")
1089
1160
  })
1090
1161
 
@@ -1131,7 +1202,7 @@ pr: null
1131
1202
  "--porcelain",
1132
1203
  ])
1133
1204
  expect(new TextDecoder().decode(worktrees.stdout)).not.toContain(
1134
- workspace.writablePath,
1205
+ workspace.writablePath!,
1135
1206
  )
1136
1207
  expect(
1137
1208
  Bun.spawnSync([
@@ -1325,7 +1396,7 @@ pr: null
1325
1396
  ),
1326
1397
  ).rejects.toThrow("multiple Agency owners")
1327
1398
  expect(
1328
- await Bun.file(join(workspace.writablePath, "README.md")).exists(),
1399
+ await Bun.file(join(workspace.writablePath!, "README.md")).exists(),
1329
1400
  ).toBe(true)
1330
1401
  })
1331
1402
 
@@ -1371,7 +1442,7 @@ pr: null
1371
1442
  ]),
1372
1443
  )
1373
1444
  expect(
1374
- await Bun.file(join(original.writablePath, "README.md")).exists(),
1445
+ await Bun.file(join(original.writablePath!, "README.md")).exists(),
1375
1446
  ).toBe(true)
1376
1447
 
1377
1448
  const rebuilt = await runTestEffect(
@@ -1383,7 +1454,7 @@ pr: null
1383
1454
  )
1384
1455
  expect(rebuilt.inspection.conflicts).toEqual([])
1385
1456
  expect(
1386
- await Bun.file(join(original.writablePath, "README.md")).exists(),
1457
+ await Bun.file(join(original.writablePath!, "README.md")).exists(),
1387
1458
  ).toBe(true)
1388
1459
  })
1389
1460
 
@@ -1435,7 +1506,7 @@ pr: null
1435
1506
  ),
1436
1507
  ).rejects.toThrow("original worktrees were restored")
1437
1508
  expect(
1438
- await Bun.file(join(workspace.writablePath, "README.md")).exists(),
1509
+ await Bun.file(join(workspace.writablePath!, "README.md")).exists(),
1439
1510
  ).toBe(true)
1440
1511
  })
1441
1512
 
@@ -1473,7 +1544,7 @@ pr: null
1473
1544
  ),
1474
1545
  )
1475
1546
  expect(plan.actions.join("\n")).toContain("worktree prune --expire now")
1476
- expect(plan.actions).toContain(`prepare ${workspace.writablePath}`)
1547
+ expect(plan.actions).toContain(`prepare ${workspace.writablePath!}`)
1477
1548
 
1478
1549
  const repaired = await runTestEffect(
1479
1550
  WorktreeService.pipe(
@@ -1484,7 +1555,7 @@ pr: null
1484
1555
  )
1485
1556
  expect(repaired.inspection.conflicts).toEqual([])
1486
1557
  expect(
1487
- await Bun.file(join(workspace.writablePath, "README.md")).exists(),
1558
+ await Bun.file(join(workspace.writablePath!, "README.md")).exists(),
1488
1559
  ).toBe(true)
1489
1560
  expect(
1490
1561
  Bun.spawnSync([
@@ -1648,7 +1719,7 @@ pr: null
1648
1719
  ),
1649
1720
  )
1650
1721
  expect(
1651
- await Bun.file(join(workspace.writablePath, "README.md")).text(),
1722
+ await Bun.file(join(workspace.writablePath!, "README.md")).text(),
1652
1723
  ).toBe("example\n")
1653
1724
  })
1654
1725
  })