@markjaquith/agency 3.1.1 → 3.2.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/README.md CHANGED
@@ -1028,7 +1028,7 @@ until restored.
1028
1028
 
1029
1029
  ```text
1030
1030
  agency work [<directory> | --epic <epic-id>] [--agent <name>] [--auto] [--print-command]
1031
- agency work prepare [target] [--evidence <json-or-path>] [--dry-run] [--json]
1031
+ agency work prepare [target] [--evidence <json-or-path>] [--force] [--dry-run] [--json]
1032
1032
  agency worktree <list|inspect|prepare|remove|rebuild|repair>
1033
1033
  agency push [--json]
1034
1034
  agency pr create <task-id> [phase-id] [--draft] [--title <title>] [--head <branch>] [--base <branch>] [--label <label>] [--force] [--json]
@@ -1062,9 +1062,11 @@ work and reading context. It does not prescribe how callers present or execute
1062
1062
  prepared work.
1063
1063
  The evidence argument may be an evidence object, task-creation JSON, or a path to
1064
1064
  either. Use `--dry-run` to report planned fetch, branch, and worktree changes
1065
- without applying them. Validation reuse never skips readiness, active-claim,
1066
- repository, ownership, reference-drift, dirty-workspace, or worktree safety
1067
- checks.
1065
+ without applying them. Use `--force` to prepare work blocked by readiness, such
1066
+ as a follow-on phase whose dependency is still active; preparation still does
1067
+ not launch an agent or change lifecycle status. Validation reuse never skips
1068
+ readiness, active-claim, repository, ownership, reference-drift, dirty-workspace,
1069
+ or worktree safety checks.
1068
1070
 
1069
1071
  The authoritative implementation locations for this contract are
1070
1072
  `src/commands/task.ts` (creation output),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "3.1.1",
3
+ "version": "3.2.1",
4
4
  "description": "Manage agentic work across repositories with durable workbases",
5
5
  "keywords": [
6
6
  "agents",
@@ -397,9 +397,11 @@ describe("strict CLI parsing", () => {
397
397
  commandName: "work",
398
398
  values: { force: true },
399
399
  })
400
- expect(() => parseCli(["work", "prepare", "example", "--force"])).toThrow(
401
- "cannot be combined",
402
- )
400
+ expect(parseCli(["work", "prepare", "example", "--force"])).toMatchObject({
401
+ commandName: "work",
402
+ args: ["prepare", "example"],
403
+ values: { force: true },
404
+ })
403
405
  })
404
406
 
405
407
  test("preserves every argument after pr without parsing it", () => {
package/src/cli-parser.ts CHANGED
@@ -917,7 +917,7 @@ const commands = {
917
917
  },
918
918
  work: {
919
919
  usage:
920
- "agency work [<directory-or-task-id> | --epic <epic-id>] [--agent <name>] [--auto] | agency work prepare [target] [--dry-run] [--json]",
920
+ "agency work [<directory-or-task-id> | --epic <epic-id>] [--agent <name>] [--auto] | agency work prepare [target] [--force] [--dry-run] [--json]",
921
921
  options: {
922
922
  ...commonOptions,
923
923
  ...entitySelectorOptions,
@@ -933,7 +933,7 @@ const commands = {
933
933
  },
934
934
  command: {
935
935
  usage:
936
- "agency work [<directory-or-task-id> | --epic <epic-id>] [--agent <name>] [--auto] | agency work prepare [target] [--dry-run] [--json]",
936
+ "agency work [<directory-or-task-id> | --epic <epic-id>] [--agent <name>] [--auto] | agency work prepare [target] [--force] [--dry-run] [--json]",
937
937
  minArgs: 0,
938
938
  maxArgs: 2,
939
939
  options: [
@@ -1691,8 +1691,7 @@ export function parseCli(args: readonly string[]): ParsedCli {
1691
1691
  parsed.values.claude ||
1692
1692
  parsed.values.agent ||
1693
1693
  parsed.values.auto ||
1694
- parsed.values["print-command"] ||
1695
- parsed.values.force))
1694
+ parsed.values["print-command"]))
1696
1695
  ) {
1697
1696
  throw usageError(
1698
1697
  preparing
package/src/cli.test.ts CHANGED
@@ -1310,7 +1310,12 @@ status: open
1310
1310
  ...process.env,
1311
1311
  ...contract.environment,
1312
1312
  XDG_CONFIG_HOME: isolatedConfigHome,
1313
+ OPENCODE_DISABLE_AUTOUPDATE: "1",
1313
1314
  OPENCODE_DISABLE_EXTERNAL_SKILLS: "1",
1315
+ OPENCODE_DISABLE_MODELS_FETCH: "1",
1316
+ OPENCODE_CONFIG_CONTENT: JSON.stringify({
1317
+ model: "opencode/big-pickle",
1318
+ }),
1314
1319
  }
1315
1320
  const probe = Bun.spawnSync(["opencode", "debug", "agent", "build"], {
1316
1321
  cwd: contract.cwd,
@@ -1409,7 +1414,9 @@ status: open
1409
1414
  ...process.env,
1410
1415
  PATH: `${agencyBin}:${process.env.PATH ?? ""}`,
1411
1416
  XDG_CONFIG_HOME: isolatedConfigHome,
1417
+ OPENCODE_DISABLE_AUTOUPDATE: "1",
1412
1418
  OPENCODE_DISABLE_EXTERNAL_SKILLS: "1",
1419
+ OPENCODE_DISABLE_MODELS_FETCH: "1",
1413
1420
  }
1414
1421
  delete directEnvironment.OPENCODE_CONFIG
1415
1422
  delete directEnvironment.OPENCODE_CONFIG_CONTENT
@@ -1480,6 +1487,7 @@ status: open
1480
1487
  env: {
1481
1488
  ...directEnvironment,
1482
1489
  OPENCODE_CONFIG_CONTENT: JSON.stringify({
1490
+ model: "opencode/big-pickle",
1483
1491
  permission: { external_directory: { "*": "deny" } },
1484
1492
  }),
1485
1493
  },
@@ -1578,7 +1586,7 @@ status: open
1578
1586
  graph.nodes.find((node: any) => node.id === "task:pipeline").status,
1579
1587
  ).toBe("working")
1580
1588
  },
1581
- 180_000,
1589
+ 360_000,
1582
1590
  )
1583
1591
 
1584
1592
  test("envelopes help and version output in machine mode", async () => {
@@ -282,6 +282,20 @@ status: dropped
282
282
  })
283
283
 
284
284
  test("reads each workbase document at most once per invocation", async () => {
285
+ const unrelatedArchive = join(root, "archive/tasks/unrelated/TASK.md")
286
+ await write(
287
+ root,
288
+ "archive/tasks/unrelated/TASK.md",
289
+ `---
290
+ ticketUrl: null
291
+ repo: agency
292
+ branch: unrelated
293
+ base: main
294
+ pr: null
295
+ status: done
296
+ ---
297
+ `,
298
+ )
285
299
  const reads = new Map<string, number>()
286
300
  const originalFile = Bun.file
287
301
  Bun.file = mock((path: string) => {
@@ -304,6 +318,48 @@ status: dropped
304
318
  )
305
319
  expect(documents.length).toBeGreaterThan(0)
306
320
  expect(documents.every(([, count]) => count === 1)).toBe(true)
321
+ expect(reads.has(unrelatedArchive)).toBe(false)
322
+ })
323
+
324
+ test("limits repository inspection and overlaps independent Git observations", async () => {
325
+ const commands: string[][] = []
326
+ let activeGitCommands = 0
327
+ let maxActiveGitCommands = 0
328
+ const originalSpawn = Bun.spawn
329
+ ;(Bun as { spawn: unknown }).spawn = mock(((
330
+ args: string[],
331
+ options: Parameters<typeof Bun.spawn>[1],
332
+ ) => {
333
+ const command = [...args]
334
+ const child = originalSpawn(command, options)
335
+ if (command[0] !== "git") return child
336
+ commands.push(command)
337
+ activeGitCommands += 1
338
+ maxActiveGitCommands = Math.max(maxActiveGitCommands, activeGitCommands)
339
+ const exited = child.exited.finally(() => {
340
+ activeGitCommands -= 1
341
+ })
342
+ return new Proxy(child, {
343
+ get(target, property) {
344
+ return property === "exited"
345
+ ? exited
346
+ : Reflect.get(target, property, target)
347
+ },
348
+ })
349
+ }) as unknown as typeof Bun.spawn)
350
+ try {
351
+ await readContext(root, "tasks/agent-contract/phases/context-command")
352
+ } finally {
353
+ ;(Bun as { spawn: unknown }).spawn = originalSpawn
354
+ }
355
+
356
+ expect(
357
+ commands.some(
358
+ (command) =>
359
+ command.includes("config") && command.includes("--show-origin"),
360
+ ),
361
+ ).toBe(false)
362
+ expect(maxActiveGitCommands).toBeGreaterThanOrEqual(3)
307
363
  })
308
364
 
309
365
  test("resolves a bare task ID and returns root discovery context", async () => {
@@ -569,6 +569,49 @@ describe("work command", () => {
569
569
  ).rejects.toThrow("Recalled repository conflicts")
570
570
  })
571
571
 
572
+ test("force prepares a phase blocked by an active dependency without launching or changing status", async () => {
573
+ const blocked = createHarness({
574
+ workspace: multiPhaseWorkspace,
575
+ multiPhaseTasks: ["example"],
576
+ guardError: new Error("Phase dependency is working"),
577
+ })
578
+ await expect(
579
+ blocked.runPrepare({
580
+ cwd: "/workbase",
581
+ taskId: "example",
582
+ phaseId: "implementation",
583
+ }),
584
+ ).rejects.toThrow("Phase dependency is working")
585
+ expect(blocked.events).toEqual(["guard"])
586
+
587
+ const forced = createHarness({
588
+ workspace: multiPhaseWorkspace,
589
+ multiPhaseTasks: ["example"],
590
+ guardError: new Error("Phase dependency is working"),
591
+ })
592
+ await forced.runPrepare({
593
+ cwd: "/workbase",
594
+ taskId: "example",
595
+ phaseId: "implementation",
596
+ force: true,
597
+ silent: true,
598
+ })
599
+
600
+ expect(forced.guards).toEqual([
601
+ {
602
+ target: "execution-unit:phase/example/implementation",
603
+ override: true,
604
+ },
605
+ ])
606
+ expect(forced.events).toEqual(["guard", "materialize"])
607
+ expect(forced.materializeOptions[0]).toMatchObject({
608
+ force: true,
609
+ validationAlreadyPerformed: true,
610
+ })
611
+ expect(forced.launches).toEqual([])
612
+ expect(forced.statusUpdates).toEqual([])
613
+ })
614
+
572
615
  test("launches an epic agent from an epic directory", async () => {
573
616
  const harness = createHarness()
574
617
 
@@ -577,7 +577,7 @@ export const workPrepare = (options: WorkOptions = {}) =>
577
577
 
578
578
  export const help = `
579
579
  Usage: agency work [<directory-or-task-id> | --epic <epic-id>] [--agent <name>] [--auto]
580
- agency work prepare [target] [--evidence <json-or-path>] [--dry-run] [--json]
580
+ agency work prepare [target] [--evidence <json-or-path>] [--force] [--dry-run] [--json]
581
581
 
582
582
  Launch an agent for an epic, task, or phase. With no directory, select one
583
583
  interactively. A positional argument resolves as a directory first, then as a task
@@ -592,7 +592,8 @@ changes without fetching, creating branches, or creating worktrees.
592
592
  It emits revision-bound validation evidence and an idempotent external-orchestrator
593
593
  contract. Evidence is reused only while the target, workbase, configuration, and
594
594
  repository mapping remain unchanged. Dynamic readiness and workspace safety checks
595
- always run.
595
+ always run. With prepare, --force overrides readiness without launching or changing
596
+ lifecycle status.
596
597
 
597
598
  Options:
598
599
  --epic <id> Work on an epic
@@ -605,8 +606,8 @@ Options:
605
606
  --print-command Print cwd, argv, and non-secret environment without launch
606
607
  --opencode Require the OpenCode preset
607
608
  --claude Require the Claude Code preset
608
- --force Override readiness; reopen terminal execution units
609
- --evidence <value> Validation evidence JSON or a path to JSON (prepare only)
609
+ --force Override readiness; launched terminal work is reopened
610
+ --evidence <value> Validation evidence JSON or a path to JSON (prepare only)
610
611
  --no-input Never open an interactive selector
611
612
 
612
613
  Without interactive input, provide an explicit workbase or cwd and an entity
@@ -271,78 +271,57 @@ export class ContextService extends Effect.Service<ContextService>()(
271
271
 
272
272
  if (relative(root, candidate) === "") {
273
273
  const compact = !options.full
274
- const discover = <S extends Schema.Schema.AnyNoContext>(
275
- id: string,
276
- path: string,
277
- schema: S,
274
+ const validation = yield* provideContextServices(
275
+ workbase.validate(root, { includeDocuments: true }),
276
+ )
277
+ const projectDiscoveryDocument = <T>(
278
+ document: {
279
+ readonly id: string
280
+ readonly path: string
281
+ readonly content: string
282
+ readonly revision: string
283
+ readonly data: T
284
+ },
278
285
  extra: Record<string, string> = {},
279
286
  ) =>
280
287
  Effect.gen(function* () {
281
- if (!(yield* fs.exists(path))) return null
282
- const content = yield* fs.readFile(path)
283
- const parsed = yield* Effect.either(
284
- parseFrontmatter(content, path),
285
- )
286
- if (Either.isLeft(parsed)) return null
287
- const decoded = decode(schema, parsed.right.data)
288
- if (!decoded.ok) return null
288
+ const body = compact
289
+ ? {}
290
+ : {
291
+ body: (yield* parseFrontmatter(
292
+ document.content,
293
+ document.path,
294
+ )).body,
295
+ }
289
296
  return {
290
297
  ...extra,
291
- id,
292
- path,
293
- sha256: documentRevision(content),
294
- data: decoded.value,
295
- ...(compact ? {} : { body: parsed.right.body }),
298
+ id: document.id,
299
+ path: document.path,
300
+ sha256: document.revision,
301
+ data: document.data,
302
+ ...body,
296
303
  }
297
304
  })
298
-
299
- const epics: unknown[] = []
300
- const tasks: unknown[] = []
301
- const phases: unknown[] = []
302
- const epicRoot = join(root, "epics")
303
- if (yield* fs.isDirectory(epicRoot)) {
304
- for (const entry of (yield* fs.readDirectory(epicRoot))
305
- .filter((item) => item.isDirectory)
306
- .sort((a, b) => a.name.localeCompare(b.name))) {
307
- const document = yield* discover(
308
- entry.name,
309
- join(epicRoot, entry.name, "EPIC.md"),
310
- EpicFrontmatter,
311
- )
312
- if (document) epics.push(document)
313
- }
314
- }
315
-
316
- const taskRoot = join(root, "tasks")
317
- if (yield* fs.isDirectory(taskRoot)) {
318
- for (const entry of (yield* fs.readDirectory(taskRoot))
319
- .filter((item) => item.isDirectory)
320
- .sort((a, b) => a.name.localeCompare(b.name))) {
321
- const document = yield* discover(
322
- entry.name,
323
- join(taskRoot, entry.name, "TASK.md"),
324
- TaskFrontmatter,
325
- )
326
- if (document) tasks.push(document)
327
-
328
- const phaseRoot = join(taskRoot, entry.name, "phases")
329
- if (!(yield* fs.isDirectory(phaseRoot))) continue
330
- for (const phaseEntry of (yield* fs.readDirectory(phaseRoot))
331
- .filter((item) => item.isDirectory)
332
- .sort((a, b) => a.name.localeCompare(b.name))) {
333
- const phase = yield* discover(
334
- phaseEntry.name,
335
- join(phaseRoot, phaseEntry.name, "PHASE.md"),
336
- PhaseFrontmatter,
337
- { taskId: entry.name },
338
- )
339
- if (phase) phases.push(phase)
340
- }
341
- }
342
- }
343
-
344
- const validation = yield* provideContextServices(
345
- workbase.validate(root),
305
+ const documents = validation.documents!
306
+ const epics = yield* Effect.all(
307
+ documents.epics.map((document) =>
308
+ projectDiscoveryDocument(document),
309
+ ),
310
+ { concurrency: "unbounded" },
311
+ )
312
+ const tasks = yield* Effect.all(
313
+ documents.tasks.map((document) =>
314
+ projectDiscoveryDocument(document),
315
+ ),
316
+ { concurrency: "unbounded" },
317
+ )
318
+ const phases = yield* Effect.all(
319
+ [...documents.phasesByTask].flatMap(([taskId, records]) =>
320
+ records.map((document) =>
321
+ projectDiscoveryDocument(document, { taskId }),
322
+ ),
323
+ ),
324
+ { concurrency: "unbounded" },
346
325
  )
347
326
  return {
348
327
  projection: compact ? "compact" : "complete",
@@ -556,104 +535,74 @@ export class ContextService extends Effect.Service<ContextService>()(
556
535
  })
557
536
  }
558
537
 
559
- const taskDocuments = new Map<string, Document<TaskData>>()
538
+ const validation = yield* provideContextServices(
539
+ workbase.validate(root, { includeDocuments: true }),
540
+ )
541
+ const validationDocuments = validation.documents!
542
+ const taskDocuments = new Map<string, Document<TaskData>>(
543
+ validationDocuments.tasks.map((document) => [
544
+ document.id,
545
+ {
546
+ id: document.id,
547
+ path: document.path,
548
+ sha256: document.revision,
549
+ data: document.data,
550
+ body: "",
551
+ },
552
+ ]),
553
+ )
560
554
  const phaseDocuments = new Map<string, Document<PhaseData>>()
561
- for (const taskRoot of [
562
- join(root, "tasks"),
563
- join(root, "archive", "tasks"),
564
- ]) {
565
- if (!(yield* fs.isDirectory(taskRoot))) continue
566
- const entries = (yield* fs.readDirectory(taskRoot))
567
- .filter((entry) => entry.isDirectory)
568
- .sort((a, b) => a.name.localeCompare(b.name))
569
- const documents = yield* Effect.all(
570
- entries.map((entry) =>
571
- Effect.gen(function* () {
572
- const path = join(taskRoot, entry.name, "TASK.md")
573
- let taskDocument: Document<TaskData> | null = null
574
- if (yield* fs.exists(path)) {
575
- const content = yield* fs.readFile(path)
576
- const parsed = yield* Effect.either(
577
- parseFrontmatter(content, path),
578
- )
579
- if (Either.isRight(parsed)) {
580
- const decoded = decode(TaskFrontmatter, parsed.right.data)
581
- if (decoded.ok) {
582
- taskDocument = {
583
- id: entry.name,
584
- path,
585
- sha256: documentRevision(content),
586
- data: decoded.value,
587
- body: parsed.right.body,
588
- }
589
- }
590
- }
591
- }
592
-
593
- const phasesPath = join(taskRoot, entry.name, "phases")
594
- if (!(yield* fs.isDirectory(phasesPath))) {
595
- return { taskDocument, phaseDocuments: [] }
596
- }
597
- const phaseEntries = (yield* fs.readDirectory(phasesPath))
598
- .filter((item) => item.isDirectory)
599
- .sort((a, b) => a.name.localeCompare(b.name))
600
- const childDocuments = yield* Effect.all(
601
- phaseEntries.map((phaseEntry) =>
602
- Effect.gen(function* () {
603
- const phasePath = join(
604
- phasesPath,
605
- phaseEntry.name,
606
- "PHASE.md",
607
- )
608
- if (!(yield* fs.exists(phasePath))) return null
609
- const content = yield* fs.readFile(phasePath)
610
- const parsed = yield* Effect.either(
611
- parseFrontmatter(content, phasePath),
612
- )
613
- if (Either.isLeft(parsed)) return null
614
- const decoded = decode(
615
- PhaseFrontmatter,
616
- parsed.right.data,
617
- )
618
- if (!decoded.ok) return null
619
- return [
620
- `${entry.name}/${phaseEntry.name}`,
621
- {
622
- id: phaseEntry.name,
623
- path: phasePath,
624
- sha256: documentRevision(content),
625
- data: decoded.value,
626
- body: parsed.right.body,
627
- },
628
- ] as const
629
- }),
630
- ),
631
- { concurrency: "unbounded" },
632
- )
633
- return {
634
- taskDocument,
635
- phaseDocuments: childDocuments.filter(
636
- (document): document is NonNullable<typeof document> =>
637
- document !== null,
638
- ),
639
- }
640
- }),
641
- ),
642
- { concurrency: "unbounded" },
643
- )
555
+ for (const [taskId, documents] of validationDocuments.phasesByTask) {
644
556
  for (const document of documents) {
645
- if (
646
- document.taskDocument &&
647
- !taskDocuments.has(document.taskDocument.id)
648
- ) {
649
- taskDocuments.set(
650
- document.taskDocument.id,
651
- document.taskDocument,
652
- )
653
- }
654
- for (const [key, phaseDocument] of document.phaseDocuments) {
655
- if (!phaseDocuments.has(key))
656
- phaseDocuments.set(key, phaseDocument)
557
+ phaseDocuments.set(`${taskId}/${document.id}`, {
558
+ id: document.id,
559
+ path: document.path,
560
+ sha256: document.revision,
561
+ data: document.data,
562
+ body: "",
563
+ })
564
+ }
565
+ }
566
+ if (task && !taskDocuments.has(task.id))
567
+ taskDocuments.set(task.id, task)
568
+ if (phase && target.taskId) {
569
+ phaseDocuments.set(`${target.taskId}/${phase.id}`, phase)
570
+ }
571
+ const relevantTaskIds = new Set([
572
+ ...(target.taskId ? [target.taskId] : []),
573
+ ...(epic?.data.tasks.map((child: Dependency) => child.id) ?? []),
574
+ ])
575
+ for (const taskId of relevantTaskIds) {
576
+ let document = taskDocuments.get(taskId)
577
+ if (!document) {
578
+ const archived = yield* Effect.either(
579
+ readOptionalDocument(
580
+ taskId,
581
+ join(archivedTaskDirectory(root, taskId), "TASK.md"),
582
+ TaskFrontmatter,
583
+ ),
584
+ )
585
+ document = Either.isRight(archived)
586
+ ? (archived.right ?? undefined)
587
+ : undefined
588
+ if (document) taskDocuments.set(taskId, document)
589
+ }
590
+ if (!document || !("phases" in document.data)) continue
591
+ for (const child of document.data.phases) {
592
+ const key = `${taskId}/${child.id}`
593
+ if (phaseDocuments.has(key)) continue
594
+ const archived = yield* Effect.either(
595
+ readOptionalDocument(
596
+ child.id,
597
+ join(
598
+ archivedPhaseDirectory(root, taskId, child.id),
599
+ "PHASE.md",
600
+ ),
601
+ PhaseFrontmatter,
602
+ ),
603
+ )
604
+ if (Either.isRight(archived) && archived.right) {
605
+ phaseDocuments.set(key, archived.right)
657
606
  }
658
607
  }
659
608
  }
@@ -720,9 +669,6 @@ export class ContextService extends Effect.Service<ContextService>()(
720
669
  })
721
670
  }
722
671
 
723
- const validation = yield* provideContextServices(
724
- workbase.validate(root),
725
- )
726
672
  const relevantPaths = new Set<string>(
727
673
  [epic?.path, task?.path, phase?.path]
728
674
  .filter((path): path is string => Boolean(path))
@@ -1017,9 +963,11 @@ export class ContextService extends Effect.Service<ContextService>()(
1017
963
  const codePath = join(entityDirectory, "code")
1018
964
  const inspectionWarnings: string[] = []
1019
965
  const repositories = new Map(
1020
- (yield* provideContextServices(repositoryService.list(root))).map(
1021
- (repository) => [repository.alias, repository],
1022
- ),
966
+ reviewData
967
+ ? (yield* provideContextServices(
968
+ repositoryService.list(root),
969
+ )).map((repository) => [repository.alias, repository])
970
+ : [],
1023
971
  )
1024
972
 
1025
973
  const inspectCheckout = (
@@ -1027,12 +975,18 @@ export class ContextService extends Effect.Service<ContextService>()(
1027
975
  checkoutPath: string,
1028
976
  ) =>
1029
977
  Effect.gen(function* (): Generator<any, CheckoutInspection, any> {
1030
- const materialized = yield* fs.isDirectory(checkoutPath)
1031
- const listed = yield* runGit(fs, repositoryPath, [
1032
- "worktree",
1033
- "list",
1034
- "--porcelain",
1035
- ])
978
+ const [materialized, listed, canonicalRoot] = yield* Effect.all(
979
+ [
980
+ fs.isDirectory(checkoutPath),
981
+ runGit(fs, repositoryPath, [
982
+ "worktree",
983
+ "list",
984
+ "--porcelain",
985
+ ]),
986
+ fs.realPath(root),
987
+ ],
988
+ { concurrency: "unbounded" },
989
+ )
1036
990
  if (listed === null) {
1037
991
  inspectionWarnings.push(
1038
992
  `Unable to inspect worktree registrations for ${repositoryPath}`,
@@ -1040,7 +994,7 @@ export class ContextService extends Effect.Service<ContextService>()(
1040
994
  }
1041
995
  const listedPaths = worktreePaths(listed)
1042
996
  const canonicalCheckoutPath = join(
1043
- yield* fs.realPath(root),
997
+ canonicalRoot,
1044
998
  relative(root, checkoutPath),
1045
999
  )
1046
1000
  let registered =
@@ -1056,21 +1010,25 @@ export class ContextService extends Effect.Service<ContextService>()(
1056
1010
  dirty: null,
1057
1011
  }
1058
1012
  }
1059
- const checkoutCommit = yield* runGit(fs, checkoutPath, [
1060
- "rev-parse",
1061
- "HEAD",
1062
- ])
1063
- const checkoutBranch = yield* runGit(fs, checkoutPath, [
1064
- "symbolic-ref",
1065
- "--quiet",
1066
- "--short",
1067
- "HEAD",
1068
- ])
1069
- const checkoutStatus = yield* runGitText(fs, checkoutPath, [
1070
- "status",
1071
- "--porcelain",
1072
- ])
1073
- const resolvedCheckoutPath = yield* fs.realPath(checkoutPath)
1013
+ const [
1014
+ checkoutCommit,
1015
+ checkoutBranch,
1016
+ checkoutStatus,
1017
+ resolvedCheckoutPath,
1018
+ ] = yield* Effect.all(
1019
+ [
1020
+ runGit(fs, checkoutPath, ["rev-parse", "HEAD"]),
1021
+ runGit(fs, checkoutPath, [
1022
+ "symbolic-ref",
1023
+ "--quiet",
1024
+ "--short",
1025
+ "HEAD",
1026
+ ]),
1027
+ runGitText(fs, checkoutPath, ["status", "--porcelain"]),
1028
+ fs.realPath(checkoutPath),
1029
+ ],
1030
+ { concurrency: "unbounded" },
1031
+ )
1074
1032
  registered = registered || listedPaths.has(resolvedCheckoutPath)
1075
1033
  if (checkoutCommit === null) {
1076
1034
  inspectionWarnings.push(
@@ -1094,17 +1052,16 @@ export class ContextService extends Effect.Service<ContextService>()(
1094
1052
  const repositoryPath =
1095
1053
  repository?.path ?? join(root, "repos", executionData.repo)
1096
1054
  const checkoutPath = join(codePath, executionData.repo)
1097
- const branchCommit = yield* backend.resolveRevision(
1098
- repositoryPath,
1099
- executionData.branch,
1100
- )
1101
- const baseCommit = yield* backend.resolveRevision(
1102
- repositoryPath,
1103
- executionData.base,
1104
- )
1105
- const checkout = yield* inspectCheckout(
1106
- repositoryPath,
1107
- checkoutPath,
1055
+ const [branchCommit, baseCommit, checkout] = yield* Effect.all(
1056
+ [
1057
+ backend.resolveRevision(
1058
+ repositoryPath,
1059
+ executionData.branch,
1060
+ ),
1061
+ backend.resolveRevision(repositoryPath, executionData.base),
1062
+ inspectCheckout(repositoryPath, checkoutPath),
1063
+ ],
1064
+ { concurrency: "unbounded" },
1108
1065
  )
1109
1066
  if (branchCommit === null) {
1110
1067
  inspectionWarnings.push(