@markjaquith/agency 2.24.0 → 2.25.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.
package/cli.ts CHANGED
@@ -683,6 +683,7 @@ try {
683
683
  }
684
684
  if (error instanceof Error) {
685
685
  let message = error.message
686
+ let details: any = error
686
687
 
687
688
  // Handle Effect FiberFailure errors that wrap tagged errors
688
689
  // When the message is generic "An error has occurred", try to extract the actual error
@@ -695,6 +696,7 @@ try {
695
696
  const cause = (error as any)[causeSymbol]
696
697
  if (cause && cause._tag === "Fail" && cause.failure) {
697
698
  const failure = cause.failure
699
+ details = failure
698
700
  // Try common error message patterns
699
701
  message =
700
702
  failure.message ||
@@ -705,6 +707,14 @@ try {
705
707
  }
706
708
  }
707
709
  }
710
+ for (const [field, label] of [
711
+ ["completed", "Completed"],
712
+ ["rolledBack", "Rolled back"],
713
+ ["manualRecovery", "Manual recovery"],
714
+ ] as const) {
715
+ if (Array.isArray(details[field]) && details[field].length > 0)
716
+ message += `\n${label}: ${details[field].join("; ")}`
717
+ }
708
718
 
709
719
  console.error(`ⓘ ${message}`)
710
720
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "2.24.0",
3
+ "version": "2.25.0",
4
4
  "description": "Manage agentic work across repositories with durable workbases",
5
5
  "keywords": [
6
6
  "agents",
@@ -292,6 +292,15 @@ describe("strict CLI parsing", () => {
292
292
  )
293
293
  })
294
294
 
295
+ test("accepts archive dry-run", () => {
296
+ expect(parseCli(["archive", "task", "example", "--dry-run"])).toMatchObject(
297
+ {
298
+ commandName: "archive",
299
+ values: { "dry-run": true },
300
+ },
301
+ )
302
+ })
303
+
295
304
  test("validates revision-guarded claim operations", () => {
296
305
  const revision = "0".repeat(64)
297
306
  expect(
@@ -63,6 +63,28 @@ describe("archive command", () => {
63
63
  })
64
64
  })
65
65
 
66
+ test("preflights an archive without moving the task", async () => {
67
+ const logs = await captureLogs(() =>
68
+ runTestEffect(
69
+ archive({
70
+ type: "task",
71
+ args: ["example"],
72
+ cwd: root,
73
+ dryRun: true,
74
+ json: true,
75
+ }),
76
+ ),
77
+ )
78
+
79
+ expect(JSON.parse(logs[0]!).dryRun).toBe(true)
80
+ expect(await Bun.file(join(root, "tasks/example/TASK.md")).exists()).toBe(
81
+ true,
82
+ )
83
+ expect(await Bun.file(join(root, "archive/tasks/example")).exists()).toBe(
84
+ false,
85
+ )
86
+ })
87
+
66
88
  test("requires a supported work item type", async () => {
67
89
  await expect(
68
90
  runTestEffect(archive({ args: [], cwd: root, silent: true })),
@@ -112,7 +112,9 @@ export const archive = (options: ArchiveOptions) =>
112
112
  log(
113
113
  options.json
114
114
  ? JSON.stringify(result, null, 2)
115
- : `${result.dryRun ? "Would archive" : "Archived"} ${result.kind} '${result.id}' to ${result.path}`,
115
+ : result.dryRun
116
+ ? `Would archive ${result.kind} '${result.id}' to ${result.path}`
117
+ : `Archived ${result.kind} '${result.id}' to ${result.path}`,
116
118
  )
117
119
  })
118
120
 
@@ -850,4 +850,63 @@ describe("ArchiveService", () => {
850
850
  ),
851
851
  ).rejects.toThrow("is archived; restore it")
852
852
  })
853
+
854
+ test("preflights every phase worktree before removing any of them", async () => {
855
+ await runTestEffect(
856
+ TaskService.pipe(
857
+ Effect.flatMap((service) =>
858
+ service.create(
859
+ {
860
+ id: "multi-dirty",
861
+ ticketUrl: "https://example.com/task",
862
+ multiPhase: true,
863
+ },
864
+ root,
865
+ ),
866
+ ),
867
+ ),
868
+ )
869
+ for (const id of ["clean", "dirty"]) {
870
+ await runTestEffect(
871
+ PhaseService.pipe(
872
+ Effect.flatMap((service) =>
873
+ service.create(
874
+ {
875
+ taskId: "multi-dirty",
876
+ id,
877
+ repo: "agency",
878
+ branch: `task/${id}`,
879
+ base: "main",
880
+ },
881
+ root,
882
+ ),
883
+ ),
884
+ ),
885
+ )
886
+ await runTestEffect(
887
+ WorktreeService.pipe(
888
+ Effect.flatMap((service) =>
889
+ service.materialize("multi-dirty", id, root),
890
+ ),
891
+ ),
892
+ )
893
+ }
894
+ await Bun.write(
895
+ join(root, "tasks/multi-dirty/phases/dirty/code/agency/dirty.txt"),
896
+ "keep me\n",
897
+ )
898
+
899
+ await expect(
900
+ runTestEffect(
901
+ ArchiveService.pipe(
902
+ Effect.flatMap((service) => service.archiveTask("multi-dirty", root)),
903
+ ),
904
+ ),
905
+ ).rejects.toThrow("uncommitted changes")
906
+ expect(
907
+ await Bun.file(
908
+ join(root, "tasks/multi-dirty/phases/clean/code/agency/README.md"),
909
+ ).exists(),
910
+ ).toBe(true)
911
+ })
853
912
  })