@markjaquith/agency 2.73.2 → 3.0.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 (59) hide show
  1. package/README.md +35 -134
  2. package/cli-main.ts +0 -26
  3. package/cli.ts +1 -17
  4. package/package.json +1 -2
  5. package/schemas/agency-graph-v1.schema.json +1 -1
  6. package/src/cli-parser.test.ts +0 -19
  7. package/src/cli-parser.ts +0 -23
  8. package/src/cli.test.ts +3 -3
  9. package/src/commands/context.test.ts +11 -71
  10. package/src/commands/context.ts +7 -7
  11. package/src/commands/init.test.ts +1 -2
  12. package/src/commands/pr.test.ts +2 -87
  13. package/src/commands/pr.ts +2 -79
  14. package/src/commands/push.test.ts +1 -2
  15. package/src/graph-schema.ts +1 -1
  16. package/src/services/ArchiveBulkService.test.ts +1 -142
  17. package/src/services/ArchiveService.test.ts +1 -172
  18. package/src/services/ArchiveService.ts +0 -26
  19. package/src/services/ContextService.ts +7 -50
  20. package/src/services/DoctorService.ts +8 -24
  21. package/src/services/GraphService.test.ts +1 -0
  22. package/src/services/GraphService.ts +2 -5
  23. package/src/services/IntegrationService.test.ts +4 -0
  24. package/src/services/PhaseService.ts +70 -191
  25. package/src/services/PullRequestService.test.ts +1 -45
  26. package/src/services/PullRequestService.ts +1 -22
  27. package/src/services/PushService.test.ts +29 -295
  28. package/src/services/PushService.ts +10 -239
  29. package/src/services/RepositoryService.test.ts +1 -61
  30. package/src/services/RepositoryService.ts +2 -13
  31. package/src/services/ReviewService.test.ts +1 -84
  32. package/src/services/ReviewService.ts +9 -48
  33. package/src/services/SyncService.test.ts +0 -81
  34. package/src/services/SyncService.ts +12 -39
  35. package/src/services/TaskPhaseService.test.ts +0 -80
  36. package/src/services/TaskService.ts +1 -9
  37. package/src/services/VersionControlService.test.ts +4 -117
  38. package/src/services/VersionControlService.ts +3 -426
  39. package/src/services/WorkbaseService.test.ts +0 -20
  40. package/src/services/WorkbaseService.ts +1 -18
  41. package/src/services/WorktreeService.test.ts +1 -705
  42. package/src/services/WorktreeService.ts +395 -1604
  43. package/src/services/push-validation.ts +0 -7
  44. package/src/test-utils.ts +0 -4
  45. package/src/workbase/AGENTS.md +7 -7
  46. package/src/workbase/checkout-command.test.ts +1 -1
  47. package/src/workbase/checkout-command.ts +1 -1
  48. package/src/workbase/delivery-command.test.ts +4 -35
  49. package/src/workbase/delivery-command.ts +2 -15
  50. package/src/workbase/schemas.test.ts +0 -19
  51. package/src/workbase/schemas.ts +1 -2
  52. package/src/commands/vcs.test.ts +0 -75
  53. package/src/commands/vcs.ts +0 -72
  54. package/src/services/VcsMigrationService.test.ts +0 -348
  55. package/src/services/VcsMigrationService.ts +0 -857
  56. package/src/vcs-status-fast.ts +0 -310
  57. package/src/workbase/version-control.ts +0 -5
  58. package/src/workbase/workspace-command.test.ts +0 -70
  59. package/src/workbase/workspace-command.ts +0 -63
@@ -13,7 +13,6 @@ import {
13
13
  } from "./WorktreeService"
14
14
  import {
15
15
  GitVersionControlService,
16
- JjVersionControlService,
17
16
  VersionControlService,
18
17
  } from "./VersionControlService"
19
18
  import {
@@ -368,7 +367,6 @@ const WorktreeLayer = Layer.mergeAll(
368
367
  FileSystemService.Default,
369
368
  WorkbaseService.Default,
370
369
  GitVersionControlService.Default,
371
- JjVersionControlService.Default,
372
370
  VersionControlService.Default,
373
371
  TaskService.Default,
374
372
  PhaseService.Default,
@@ -400,22 +398,6 @@ const restoreWorktreeSnapshots = async (
400
398
  continue
401
399
  } catch {}
402
400
  await mkdir(dirname(snapshot.path), { recursive: true })
403
- if (snapshot.vcs === "jj") {
404
- await runGit([
405
- "jj",
406
- "-R",
407
- snapshot.repositoryPath,
408
- "workspace",
409
- "add",
410
- "--name",
411
- snapshot.workspaceName!,
412
- "-r",
413
- snapshot.head,
414
- snapshot.path,
415
- ])
416
- await runGit(["jj", "-R", snapshot.path, "edit", snapshot.head])
417
- continue
418
- }
419
401
  await runGit(
420
402
  snapshot.branch
421
403
  ? [
@@ -724,7 +706,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
724
706
  removedWorktrees.push(
725
707
  ...(yield* worktrees.remove(unit.taskId, unit.phaseId, root, {
726
708
  dryRun: true,
727
- persistResume: false,
728
709
  })),
729
710
  )
730
711
  }
@@ -783,7 +764,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
783
764
  worktrees.remove(unit.taskId, unit.phaseId, root, {
784
765
  snapshots,
785
766
  lockHeld: true,
786
- persistResume: false,
787
767
  }),
788
768
  )
789
769
  } catch (cause) {
@@ -897,7 +877,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
897
877
  const result = yield* worktrees
898
878
  .remove(unit.taskId, executionPhaseId(unit), root, {
899
879
  dryRun: true,
900
- persistResume: false,
901
880
  task: context.task,
902
881
  phase: unit.phaseId
903
882
  ? context.phases.find((phase) => phase.id === unit.phaseId)
@@ -1075,7 +1054,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
1075
1054
  worktrees.remove(unit.taskId, unit.phaseId, root, {
1076
1055
  snapshots,
1077
1056
  lockHeld: true,
1078
- persistResume: false,
1079
1057
  task: contexts.get(unit.taskId)!.task,
1080
1058
  phase: unit.phaseId
1081
1059
  ? contexts
@@ -1232,7 +1210,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
1232
1210
  root,
1233
1211
  {
1234
1212
  dryRun: true,
1235
- persistResume: false,
1236
1213
  },
1237
1214
  )),
1238
1215
  )
@@ -1288,7 +1265,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
1288
1265
  {
1289
1266
  snapshots,
1290
1267
  lockHeld: true,
1291
- persistResume: false,
1292
1268
  },
1293
1269
  ),
1294
1270
  )
@@ -1381,7 +1357,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
1381
1357
  yield* rejectExistingDestination(destination, "Archive")
1382
1358
  const removedWorktrees = yield* worktrees.remove(taskId, id, root, {
1383
1359
  dryRun: true,
1384
- persistResume: false,
1385
1360
  })
1386
1361
  const at = new Date().toISOString()
1387
1362
  const content = yield* declarationContent(task, {
@@ -1430,7 +1405,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
1430
1405
  worktrees.remove(taskId, id, root, {
1431
1406
  snapshots,
1432
1407
  lockHeld: true,
1433
- persistResume: false,
1434
1408
  }),
1435
1409
  )
1436
1410
  },
@@ -350,7 +350,7 @@ export class ContextService extends Effect.Service<ContextService>()(
350
350
  root,
351
351
  configPath: join(root, "agency.json"),
352
352
  version: config.version,
353
- vcs: config.vcs ?? "git",
353
+ vcs: "git",
354
354
  },
355
355
  target: { kind: "workbase", path: root },
356
356
  hint: compact
@@ -1025,51 +1025,9 @@ export class ContextService extends Effect.Service<ContextService>()(
1025
1025
  const inspectCheckout = (
1026
1026
  repositoryPath: string,
1027
1027
  checkoutPath: string,
1028
- expectedBranch: string | null = null,
1029
- branchCommit: string | null = null,
1030
1028
  ) =>
1031
1029
  Effect.gen(function* (): Generator<any, CheckoutInspection, any> {
1032
1030
  const materialized = yield* fs.isDirectory(checkoutPath)
1033
- if (backend.kind === "jj") {
1034
- const listed = yield* Effect.either(
1035
- backend.listWorkspaces(repositoryPath),
1036
- )
1037
- const workspaces = Either.isRight(listed) ? listed.right : []
1038
- if (Either.isLeft(listed)) {
1039
- inspectionWarnings.push(
1040
- `Unable to inspect jj workspace registrations for ${repositoryPath}`,
1041
- )
1042
- }
1043
- const canonicalCheckoutPath = materialized
1044
- ? yield* fs.realPath(checkoutPath)
1045
- : resolve(checkoutPath)
1046
- const workspace = workspaces.find(
1047
- (workspace) => workspace.path === canonicalCheckoutPath,
1048
- )
1049
- const registered = workspace !== undefined
1050
- if (!materialized) {
1051
- return {
1052
- materialized: false,
1053
- registered,
1054
- checkoutCommit: null,
1055
- checkoutBranch: null,
1056
- detached: null,
1057
- dirty: null,
1058
- }
1059
- }
1060
- return {
1061
- materialized: true,
1062
- registered,
1063
- checkoutCommit: workspace?.commit ?? null,
1064
- checkoutBranch:
1065
- expectedBranch && branchCommit === workspace?.commit
1066
- ? expectedBranch
1067
- : null,
1068
- detached:
1069
- !expectedBranch || branchCommit !== workspace?.commit,
1070
- dirty: workspace ? false : null,
1071
- }
1072
- }
1073
1031
  const listed = yield* runGit(fs, repositoryPath, [
1074
1032
  "worktree",
1075
1033
  "list",
@@ -1136,7 +1094,7 @@ export class ContextService extends Effect.Service<ContextService>()(
1136
1094
  const repositoryPath =
1137
1095
  repository?.path ?? join(root, "repos", executionData.repo)
1138
1096
  const checkoutPath = join(codePath, executionData.repo)
1139
- let branchCommit = yield* backend.resolveRevision(
1097
+ const branchCommit = yield* backend.resolveRevision(
1140
1098
  repositoryPath,
1141
1099
  executionData.branch,
1142
1100
  )
@@ -1147,8 +1105,6 @@ export class ContextService extends Effect.Service<ContextService>()(
1147
1105
  const checkout = yield* inspectCheckout(
1148
1106
  repositoryPath,
1149
1107
  checkoutPath,
1150
- executionData.branch,
1151
- branchCommit,
1152
1108
  )
1153
1109
  if (branchCommit === null) {
1154
1110
  inspectionWarnings.push(
@@ -1239,9 +1195,10 @@ export class ContextService extends Effect.Service<ContextService>()(
1239
1195
  ? "complete"
1240
1196
  : "partial"
1241
1197
 
1198
+ const compact = !options.full
1242
1199
  const projectDocument = <T>(document: Document<T> | null) =>
1243
1200
  document
1244
- ? options.compact
1201
+ ? compact
1245
1202
  ? {
1246
1203
  id: document.id,
1247
1204
  path: document.path,
@@ -1255,12 +1212,12 @@ export class ContextService extends Effect.Service<ContextService>()(
1255
1212
  (task?.data && "repo" in task.data ? task.data : null)
1256
1213
 
1257
1214
  return {
1258
- projection: options.compact ? "compact" : "complete",
1215
+ projection: compact ? "compact" : "complete",
1259
1216
  workbase: {
1260
1217
  root,
1261
1218
  configPath: join(root, "agency.json"),
1262
1219
  version: config.version,
1263
- vcs: config.vcs ?? "git",
1220
+ vcs: "git",
1264
1221
  },
1265
1222
  target,
1266
1223
  documents: {
@@ -1308,7 +1265,7 @@ export class ContextService extends Effect.Service<ContextService>()(
1308
1265
  writable: writableDocuments,
1309
1266
  },
1310
1267
  },
1311
- workspace: options.compact
1268
+ workspace: compact
1312
1269
  ? {
1313
1270
  codePath,
1314
1271
  materialization,
@@ -125,20 +125,12 @@ export class DoctorService extends Effect.Service<DoctorService>()(
125
125
  return available
126
126
  })
127
127
 
128
- const [gitAvailable, jjAvailable] = yield* Effect.all(
129
- [
130
- tool("tool.git", "git", "error", "Git"),
131
- tool(
132
- "tool.jj",
133
- "jj",
134
- config.vcs === "jj" ? "error" : "optional",
135
- "Jujutsu",
136
- ),
137
- ],
138
- { concurrency: "unbounded", batching: true },
128
+ const versionControlAvailable = yield* tool(
129
+ "tool.git",
130
+ "git",
131
+ "error",
132
+ "Git",
139
133
  )
140
- const versionControlAvailable =
141
- gitAvailable && (config.vcs !== "jj" || jjAvailable)
142
134
  yield* Effect.all(
143
135
  [
144
136
  tool(
@@ -180,15 +172,6 @@ export class DoctorService extends Effect.Service<DoctorService>()(
180
172
  ] as const,
181
173
  ]
182
174
  : []),
183
- ...(config.workspaceCreateCommand
184
- ? [
185
- [
186
- "integration.workspace-create",
187
- config.workspaceCreateCommand,
188
- "Workspace creator",
189
- ] as const,
190
- ]
191
- : []),
192
175
  ...Object.entries(config.repositories ?? {}).flatMap(
193
176
  ([alias, repository]) =>
194
177
  repository.postCheckoutCommand
@@ -384,8 +367,9 @@ export class DoctorService extends Effect.Service<DoctorService>()(
384
367
  category: "repository",
385
368
  level: "warning",
386
369
  status: "fail",
387
- message: `Repository, ref, remote, and workspace checks were skipped because the ${config.vcs ?? "git"} backend is unavailable`,
388
- remediation: `Install '${config.vcs === "jj" ? "jj" : "git"}' and rerun 'agency doctor'.`,
370
+ message:
371
+ "Repository, ref, remote, and workspace checks were skipped because Git is unavailable",
372
+ remediation: "Install 'git' and rerun 'agency doctor'.",
389
373
  })
390
374
  }
391
375
  for (const repository of repositoryList) {
@@ -121,6 +121,7 @@ describe("GraphService", () => {
121
121
  const graph = await getGraph(root)
122
122
 
123
123
  expect(Schema.decodeUnknownSync(AgencyGraph)(graph)).toEqual(graph)
124
+ expect(graph.workbase.vcs).toBe("git")
124
125
  expect(graph.nodes.map((node) => node.id)).toEqual([
125
126
  "epic:delivery",
126
127
  "execution-unit:phase/ship/implement",
@@ -821,10 +821,7 @@ export class GraphService extends Effect.Service<GraphService>()(
821
821
  checkoutCommit: materialized
822
822
  ? yield* backend.workspaceHead(checkoutPath)
823
823
  : null,
824
- checkoutBranch:
825
- backend.kind === "jj" && materialized
826
- ? data.branch
827
- : actualBranch,
824
+ checkoutBranch: actualBranch,
828
825
  dirty: materialized
829
826
  ? yield* backend.workspaceDirty(checkoutPath)
830
827
  : null,
@@ -1022,7 +1019,7 @@ export class GraphService extends Effect.Service<GraphService>()(
1022
1019
  version: GRAPH_VERSION,
1023
1020
  workbase: {
1024
1021
  version: config.version,
1025
- vcs: config.vcs ?? "git",
1022
+ vcs: "git",
1026
1023
  ...(include.has("workspace") ? { root } : {}),
1027
1024
  },
1028
1025
  filters,
@@ -186,6 +186,10 @@ describe("IntegrationService", () => {
186
186
  }
187
187
  expect(managedWorkbaseAgents).toContain("agency push --json")
188
188
  expect(managedWorkbaseAgents).toContain("agency-execution-v1")
189
+ expect(managedWorkbaseAgents).toContain("agency context . --full --json")
190
+ expect(managedWorkbaseAgents).toContain(
191
+ "Workers use full context here because they need the assigned document prose",
192
+ )
189
193
  expect(managedWorkbaseAgents).toContain(
190
194
  "Never pass `--work` or `--auto` to `agency task create`",
191
195
  )
@@ -30,7 +30,6 @@ import {
30
30
  buildNonPrCompletion,
31
31
  type NonPrCompletionInput,
32
32
  } from "../workbase/completion"
33
- import { VersionControlService } from "./VersionControlService"
34
33
 
35
34
  class PhaseError extends Data.TaggedError("PhaseError")<{
36
35
  readonly message: string
@@ -93,10 +92,8 @@ export class PhaseService extends Effect.Service<PhaseService>()(
93
92
  Effect.gen(function* () {
94
93
  const fs = yield* FileSystemService
95
94
  const workbase = yield* WorkbaseService
96
- const versionControl = yield* VersionControlService
97
95
  const tasks = yield* TaskService
98
96
  const root = yield* workbase.discover(startPath)
99
- const backend = yield* versionControl.forWorkbase(root)
100
97
  const taskId = yield* decodeId(input.taskId, "task")
101
98
  const id = yield* decodeId(input.id, "phase")
102
99
  const task = yield* tasks.show(taskId, root)
@@ -274,213 +271,95 @@ export class PhaseService extends Effect.Service<PhaseService>()(
274
271
  firstData.repo,
275
272
  ...(firstData.repos ?? []).map((reference) => reference.repo),
276
273
  ]
277
- if (backend.kind === "jj") {
278
- const workspaces: {
279
- alias: string
280
- name: string
281
- head: string
282
- oldPath: string
283
- newPath: string
284
- }[] = []
285
- const runBackend = <A>(
286
- effect: Effect.Effect<A, unknown, any>,
287
- ) =>
288
- Effect.runPromise(
289
- effect.pipe(
290
- Effect.provideService(FileSystemService, fs),
291
- ) as Effect.Effect<A, unknown, never>,
292
- )
293
- steps.push({
294
- label: `move jj workspaces for ${taskId}/${firstPhaseId}`,
295
- preflight: async () => {
296
- for (const entry of await readdir(oldCodePath)) {
297
- if (!checkoutAliases.includes(entry))
298
- throw new Error(
299
- `Cannot convert task '${taskId}'; code contains unmanaged entry '${entry}'`,
300
- )
301
- }
302
- for (const alias of checkoutAliases) {
303
- const oldPath = join(oldCodePath, alias)
304
- try {
305
- await lstat(oldPath)
306
- } catch {
307
- continue
308
- }
309
- const expected = await realpath(oldPath)
310
- const registered = (
311
- await runBackend(
312
- backend.listWorkspaces(join(root, "repos", alias)),
313
- )
314
- ).find((workspace) => workspace.path === expected)
315
- if (!registered?.name)
316
- throw new Error(
317
- `Cannot convert task '${taskId}'; checkout '${alias}' is not registered as a jj workspace`,
318
- )
319
- if (await runBackend(backend.workspaceDirty(oldPath)))
320
- throw new Error(
321
- `Cannot convert task '${taskId}'; jj workspace '${alias}' has uncommitted changes`,
322
- )
323
- const head = await runBackend(
324
- backend.workspaceHead(oldPath),
325
- )
326
- if (!head)
327
- throw new Error(
328
- `Cannot resolve jj workspace '${alias}' before conversion`,
329
- )
330
- workspaces.push({
331
- alias,
332
- name: registered.name,
333
- head,
334
- oldPath,
335
- newPath: join(firstCodePath, alias),
336
- })
337
- }
338
- },
339
- apply: async () => {
340
- await mkdir(firstCodePath, { recursive: true })
341
- for (const workspace of workspaces) {
342
- const repositoryPath = join(
343
- root,
344
- "repos",
345
- workspace.alias,
346
- )
347
- await runBackend(
348
- backend.removeWorkspace({
349
- repositoryPath,
350
- workspacePath: workspace.oldPath,
351
- workspaceName: workspace.name,
352
- }),
353
- )
354
- await runBackend(
355
- backend.createWorkspace({
356
- repositoryPath,
357
- workspacePath: workspace.newPath,
358
- workspaceName: workspace.name,
359
- revision: workspace.head,
360
- }),
361
- )
362
- }
363
- await rm(oldCodePath, { recursive: true, force: true })
364
- },
365
- rollback: async () => {
366
- await mkdir(oldCodePath, { recursive: true })
367
- for (const workspace of [...workspaces].reverse()) {
368
- const repositoryPath = join(
369
- root,
370
- "repos",
371
- workspace.alias,
372
- )
373
- await runBackend(
374
- backend.removeWorkspace({
375
- repositoryPath,
376
- workspacePath: workspace.newPath,
377
- workspaceName: workspace.name,
378
- }),
379
- )
380
- await runBackend(
381
- backend.createWorkspace({
382
- repositoryPath,
383
- workspacePath: workspace.oldPath,
384
- workspaceName: workspace.name,
385
- revision: workspace.head,
386
- }),
274
+ const repair = async (basePath: string) => {
275
+ for (const alias of checkoutAliases) {
276
+ const checkoutPath = join(basePath, alias)
277
+ try {
278
+ await lstat(checkoutPath)
279
+ } catch {
280
+ continue
281
+ }
282
+ const result = Bun.spawnSync([
283
+ "git",
284
+ "-C",
285
+ join(root, "repos", alias),
286
+ "worktree",
287
+ "repair",
288
+ checkoutPath,
289
+ ])
290
+ if (result.exitCode !== 0) {
291
+ throw new Error(
292
+ `Failed to repair moved worktree for '${alias}': ${new TextDecoder().decode(result.stderr)}`,
293
+ )
294
+ }
295
+ }
296
+ }
297
+ steps.push({
298
+ label: `move and repair code for ${taskId}/${firstPhaseId}`,
299
+ preflight: async () => {
300
+ for (const entry of await readdir(oldCodePath)) {
301
+ if (!checkoutAliases.includes(entry))
302
+ throw new Error(
303
+ `Cannot convert task '${taskId}'; code contains unmanaged entry '${entry}'`,
387
304
  )
388
- }
389
- await rm(firstDirectory, { recursive: true, force: true })
390
- },
391
- manualRecovery: `Restore jj workspaces from ${firstCodePath} to ${oldCodePath}`,
392
- })
393
- } else {
394
- const repair = async (basePath: string) => {
305
+ }
395
306
  for (const alias of checkoutAliases) {
396
- const checkoutPath = join(basePath, alias)
307
+ const checkoutPath = join(oldCodePath, alias)
397
308
  try {
398
309
  await lstat(checkoutPath)
399
310
  } catch {
400
311
  continue
401
312
  }
402
- const result = Bun.spawnSync([
313
+ const listed = Bun.spawnSync([
403
314
  "git",
404
315
  "-C",
405
316
  join(root, "repos", alias),
406
317
  "worktree",
407
- "repair",
408
- checkoutPath,
318
+ "list",
319
+ "--porcelain",
409
320
  ])
410
- if (result.exitCode !== 0) {
321
+ if (listed.exitCode !== 0)
411
322
  throw new Error(
412
- `Failed to repair moved worktree for '${alias}': ${new TextDecoder().decode(result.stderr)}`,
323
+ `Failed to inspect worktrees for '${alias}'`,
413
324
  )
414
- }
415
- }
416
- }
417
- steps.push({
418
- label: `move and repair code for ${taskId}/${firstPhaseId}`,
419
- preflight: async () => {
420
- for (const entry of await readdir(oldCodePath)) {
421
- if (!checkoutAliases.includes(entry))
422
- throw new Error(
423
- `Cannot convert task '${taskId}'; code contains unmanaged entry '${entry}'`,
424
- )
425
- }
426
- for (const alias of checkoutAliases) {
427
- const checkoutPath = join(oldCodePath, alias)
325
+ const expected = await realpath(checkoutPath)
326
+ let registered = false
327
+ for (const line of new TextDecoder()
328
+ .decode(listed.stdout)
329
+ .split("\n")) {
330
+ if (!line.startsWith("worktree ")) continue
428
331
  try {
429
- await lstat(checkoutPath)
430
- } catch {
431
- continue
432
- }
433
- const listed = Bun.spawnSync([
434
- "git",
435
- "-C",
436
- join(root, "repos", alias),
437
- "worktree",
438
- "list",
439
- "--porcelain",
440
- ])
441
- if (listed.exitCode !== 0)
442
- throw new Error(
443
- `Failed to inspect worktrees for '${alias}'`,
444
- )
445
- const expected = await realpath(checkoutPath)
446
- let registered = false
447
- for (const line of new TextDecoder()
448
- .decode(listed.stdout)
449
- .split("\n")) {
450
- if (!line.startsWith("worktree ")) continue
451
- try {
452
- if ((await realpath(line.slice(9))) === expected) {
453
- registered = true
454
- break
455
- }
456
- } catch {}
457
- }
458
- if (!registered)
459
- throw new Error(
460
- `Cannot convert task '${taskId}'; checkout '${alias}' is not registered as a Git worktree`,
461
- )
462
- }
463
- },
464
- apply: async () => {
465
- await mkdir(firstDirectory, { recursive: true })
466
- await rename(oldCodePath, firstCodePath)
467
- try {
468
- await repair(firstCodePath)
469
- } catch (cause) {
470
- await rename(firstCodePath, oldCodePath)
471
- await repair(oldCodePath)
472
- await rm(firstDirectory, { recursive: true, force: true })
473
- throw cause
332
+ if ((await realpath(line.slice(9))) === expected) {
333
+ registered = true
334
+ break
335
+ }
336
+ } catch {}
474
337
  }
475
- },
476
- rollback: async () => {
338
+ if (!registered)
339
+ throw new Error(
340
+ `Cannot convert task '${taskId}'; checkout '${alias}' is not registered as a Git worktree`,
341
+ )
342
+ }
343
+ },
344
+ apply: async () => {
345
+ await mkdir(firstDirectory, { recursive: true })
346
+ await rename(oldCodePath, firstCodePath)
347
+ try {
348
+ await repair(firstCodePath)
349
+ } catch (cause) {
477
350
  await rename(firstCodePath, oldCodePath)
478
351
  await repair(oldCodePath)
479
352
  await rm(firstDirectory, { recursive: true, force: true })
480
- },
481
- manualRecovery: `Move ${firstCodePath} back to ${oldCodePath} and run git worktree repair`,
482
- })
483
- }
353
+ throw cause
354
+ }
355
+ },
356
+ rollback: async () => {
357
+ await rename(firstCodePath, oldCodePath)
358
+ await repair(oldCodePath)
359
+ await rm(firstDirectory, { recursive: true, force: true })
360
+ },
361
+ manualRecovery: `Move ${firstCodePath} back to ${oldCodePath} and run git worktree repair`,
362
+ })
484
363
  }
485
364
  steps.push(
486
365
  documentWriteStep(root, [
@@ -98,7 +98,7 @@ describe("PullRequestService", () => {
98
98
  await Bun.write(
99
99
  path,
100
100
  `#!/usr/bin/env bun
101
- await Bun.write(${JSON.stringify(ghCallPath)}, JSON.stringify({ args: Bun.argv.slice(2), cwd: process.cwd(), ...(process.env.GIT_DIR ? { gitDir: process.env.GIT_DIR } : {}) }))
101
+ await Bun.write(${JSON.stringify(ghCallPath)}, JSON.stringify({ args: Bun.argv.slice(2), cwd: process.cwd() }))
102
102
  process.stdout.write(${JSON.stringify(stdout)})
103
103
  process.stderr.write(${JSON.stringify(stderr)})
104
104
  process.exit(${exitCode})
@@ -111,7 +111,6 @@ process.exit(${exitCode})
111
111
  (await Bun.file(ghCallPath).json()) as {
112
112
  args: string[]
113
113
  cwd: string
114
- gitDir?: string
115
114
  }
116
115
 
117
116
  const expectRemoteBranch = async (branch: string, exists = true) => {
@@ -243,49 +242,6 @@ process.exit(${exitCode})
243
242
  expect(updated.endsWith(`${body}\n`)).toBe(true)
244
243
  })
245
244
 
246
- test("creates a PR from a non-colocated jj workspace with backing Git context", async () => {
247
- if (!Bun.which("jj")) return
248
- await rm(join(root, "repos/agency"), { recursive: true, force: true })
249
- await requireCommand([
250
- "jj",
251
- "git",
252
- "clone",
253
- "--no-colocate",
254
- remotePath,
255
- join(root, "repos/agency"),
256
- ])
257
- await requireCommand(
258
- ["jj", "config", "set", "--repo", "user.name", "Agency Test"],
259
- join(root, "repos/agency"),
260
- )
261
- await requireCommand(
262
- ["jj", "config", "set", "--repo", "user.email", "agency@example.com"],
263
- join(root, "repos/agency"),
264
- )
265
- await Bun.write(
266
- join(root, "agency.json"),
267
- JSON.stringify({ version: 2, vcs: "jj" }),
268
- )
269
- await createTask("jj-example", "task/jj-example")
270
- const workspace = await materialize("jj-example")
271
- await Bun.write(join(workspace.writablePath!, "CHANGE.md"), "jj change\n")
272
- await requireCommand(
273
- ["jj", "describe", "-m", "feat: add jj example"],
274
- workspace.writablePath!,
275
- )
276
- await requireCommand(["jj", "new"], workspace.writablePath!)
277
- const url = "https://github.com/example/agency/pull/44"
278
- await writeFakeGh({ stdout: `${url}\n` })
279
-
280
- expect(await createPullRequest("jj-example")).toBe(url)
281
- await expectRemoteBranch("task/jj-example")
282
- const ghCall = await readGhCall()
283
- expect(ghCall.args).toContain("--head")
284
- expect(ghCall.args).toContain("--repo")
285
- expect(ghCall.gitDir).toContain(".jj")
286
- expect(await Bun.file(join(root, "repos/agency/.git")).exists()).toBe(false)
287
- })
288
-
289
245
  test("passes --draft to gh", async () => {
290
246
  await createTask()
291
247
  await writeFakeGh({