@markjaquith/agency 3.2.13 → 3.2.15

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 (69) hide show
  1. package/package.json +7 -2
  2. package/src/workbase/schemas.ts +12 -0
  3. package/fixtures/protocol/orchestration-recipes.json +0 -53
  4. package/pi-extensions/agency.test.ts +0 -117
  5. package/src/check-commits.test.ts +0 -58
  6. package/src/cli-parser.test.ts +0 -945
  7. package/src/cli.test.ts +0 -1889
  8. package/src/commands/act.test.ts +0 -418
  9. package/src/commands/archive.test.ts +0 -236
  10. package/src/commands/context.test.ts +0 -665
  11. package/src/commands/description.test.ts +0 -103
  12. package/src/commands/doctor.test.ts +0 -185
  13. package/src/commands/epic.test.ts +0 -196
  14. package/src/commands/init.test.ts +0 -117
  15. package/src/commands/integration.test.ts +0 -165
  16. package/src/commands/pr.test.ts +0 -248
  17. package/src/commands/push.test.ts +0 -56
  18. package/src/commands/read-only.test.ts +0 -161
  19. package/src/commands/repo.test.ts +0 -199
  20. package/src/commands/restore.test.ts +0 -106
  21. package/src/commands/status.test.ts +0 -113
  22. package/src/commands/sync.test.ts +0 -200
  23. package/src/commands/task-phase.test.ts +0 -410
  24. package/src/commands/task.test.ts +0 -281
  25. package/src/commands/validate.test.ts +0 -186
  26. package/src/commands/work.test.ts +0 -1375
  27. package/src/commands/workbase.test.ts +0 -170
  28. package/src/graph-schema.test.ts +0 -124
  29. package/src/protocol.test.ts +0 -163
  30. package/src/readiness.test.ts +0 -105
  31. package/src/services/ArchiveBulkService.test.ts +0 -384
  32. package/src/services/ArchiveService.test.ts +0 -1092
  33. package/src/services/EpicService.test.ts +0 -74
  34. package/src/services/FileSystemService.test.ts +0 -81
  35. package/src/services/GraphMutationService.test.ts +0 -486
  36. package/src/services/GraphService.test.ts +0 -494
  37. package/src/services/IntegrationService.test.ts +0 -1091
  38. package/src/services/LifecycleTransaction.test.ts +0 -145
  39. package/src/services/PullRequestService.test.ts +0 -716
  40. package/src/services/PushService.test.ts +0 -408
  41. package/src/services/ReadinessService.test.ts +0 -290
  42. package/src/services/RepositoryService.test.ts +0 -789
  43. package/src/services/ReviewService.test.ts +0 -408
  44. package/src/services/SyncService.test.ts +0 -1377
  45. package/src/services/TaskPhaseService.test.ts +0 -852
  46. package/src/services/VersionControlService.test.ts +0 -62
  47. package/src/services/WorkbaseService.test.ts +0 -910
  48. package/src/services/WorktreeLock.test.ts +0 -205
  49. package/src/services/WorktreePerformance.test.ts +0 -71
  50. package/src/services/WorktreeService.test.ts +0 -2292
  51. package/src/test-utils.ts +0 -110
  52. package/src/usage-log.test.ts +0 -188
  53. package/src/utils/chooser.test.ts +0 -192
  54. package/src/utils/effect.test.ts +0 -30
  55. package/src/utils/interactive.pty.test.ts +0 -128
  56. package/src/utils/interactive.test.tsx +0 -860
  57. package/src/utils/process.test.ts +0 -132
  58. package/src/utils/progress.test.ts +0 -37
  59. package/src/work-view.test.ts +0 -151
  60. package/src/workbase/agent-command.test.ts +0 -128
  61. package/src/workbase/checkout-command.test.ts +0 -62
  62. package/src/workbase/delivery-command.test.ts +0 -180
  63. package/src/workbase/dependency-graph.test.ts +0 -50
  64. package/src/workbase/execution-contract.test.ts +0 -161
  65. package/src/workbase/frontmatter.test.ts +0 -67
  66. package/src/workbase/repository-reference.test.ts +0 -25
  67. package/src/workbase/schemas.test.ts +0 -543
  68. package/src/workbase/work-target.test.ts +0 -108
  69. package/src/workbase/worktree-command.test.ts +0 -61
@@ -1,408 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "bun:test"
2
- import { Effect } from "effect"
3
- import { mkdir } from "node:fs/promises"
4
- import { join } from "node:path"
5
- import { cleanupTempDir, createTempDir, runTestEffect } from "../test-utils"
6
- import { TaskService } from "./TaskService"
7
- import { ReviewService } from "./ReviewService"
8
- import { WorktreeService } from "./WorktreeService"
9
- import { ContextService } from "./ContextService"
10
- import { GraphService } from "./GraphService"
11
- import { PullRequestService } from "./PullRequestService"
12
- import { PhaseService } from "./PhaseService"
13
- import { ArchiveService } from "./ArchiveService"
14
- import { SyncService } from "./SyncService"
15
- import { task as taskCommand } from "../commands/task"
16
-
17
- const git = async (args: string[], cwd?: string) => {
18
- const child = Bun.spawn(["git", ...args], {
19
- cwd,
20
- stdout: "pipe",
21
- stderr: "pipe",
22
- })
23
- await child.exited
24
- if (child.exitCode !== 0)
25
- throw new Error(await new Response(child.stderr).text())
26
- }
27
-
28
- describe("ReviewService", () => {
29
- let root: string
30
- let source: string
31
-
32
- beforeEach(async () => {
33
- root = await createTempDir()
34
- source = join(root, "source")
35
- await Bun.write(join(root, "agency.json"), '{"version":2}\n')
36
- await mkdir(source, { recursive: true })
37
- await git(["init", "--initial-branch=main"], source)
38
- await git(["config", "user.email", "test@example.com"], source)
39
- await git(["config", "user.name", "Test"], source)
40
- await Bun.write(join(source, "README.md"), "initial\n")
41
- await git(["add", "README.md"], source)
42
- await git(["-c", "commit.gpgsign=false", "commit", "-m", "initial"], source)
43
- await git(["switch", "-c", "review-me"], source)
44
- await Bun.write(join(source, "README.md"), "review one\n")
45
- await git(["commit", "-am", "review one"], source)
46
- await mkdir(join(root, "repos"), { recursive: true })
47
- await git(["clone", "--bare", source, join(root, "repos/agency")])
48
- })
49
-
50
- afterEach(async () => cleanupTempDir(root))
51
-
52
- const createReview = async () => {
53
- const review = await runTestEffect(
54
- ReviewService.pipe(
55
- Effect.flatMap((service) =>
56
- service.resolve("agency", { ref: "review-me" }, root),
57
- ),
58
- ),
59
- )
60
- return runTestEffect(
61
- TaskService.pipe(
62
- Effect.flatMap((service) =>
63
- service.create({ id: "review", ticketUrl: null, review }, root),
64
- ),
65
- ),
66
- )
67
- }
68
-
69
- test("pins, materializes, reports, and explicitly refreshes a branch review", async () => {
70
- const created = await createReview()
71
- const original = "review" in created.data ? created.data.review.commit : ""
72
- expect("review" in created.data && created.data.review.commit).toMatch(
73
- /^[a-f0-9]{40}$/,
74
- )
75
- const workspace = await runTestEffect(
76
- WorktreeService.pipe(
77
- Effect.flatMap((service) =>
78
- service.materialize("review", undefined, root),
79
- ),
80
- ),
81
- )
82
- expect(workspace.writablePath).toBeNull()
83
- expect(workspace.reviewPath).toBe(join(workspace.codePath, "agency"))
84
- expect(
85
- await Bun.file(join(workspace.codePath, "agency/README.md")).text(),
86
- ).toBe("review one\n")
87
- const branch = Bun.spawnSync([
88
- "git",
89
- "-C",
90
- join(workspace.codePath, "agency"),
91
- "branch",
92
- "--show-current",
93
- ])
94
- expect(new TextDecoder().decode(branch.stdout).trim()).toBe("")
95
-
96
- const context = await runTestEffect(
97
- ContextService.pipe(
98
- Effect.flatMap((service) =>
99
- service.get({ target: "review", cwd: root }),
100
- ),
101
- ),
102
- )
103
- expect(context.authority.mode).toBe("review")
104
- expect(context.authority.writable).toBeNull()
105
- expect(context.authority.references).toHaveLength(1)
106
- expect(context.authority.documents?.writable).toEqual([created.path])
107
- expect(context.pr).toEqual({ url: null, state: "none" })
108
-
109
- const graph = await runTestEffect(
110
- GraphService.pipe(
111
- Effect.flatMap((service) =>
112
- service.get({ cwd: root, include: ["workspace", "git", "pr"] }),
113
- ),
114
- ),
115
- )
116
- expect(
117
- graph.edges.some(
118
- (edge) => edge.kind === "writes" && edge.from.includes("review"),
119
- ),
120
- ).toBe(false)
121
- expect(
122
- graph.edges.some(
123
- (edge) => edge.kind === "references" && edge.from.includes("review"),
124
- ),
125
- ).toBe(true)
126
-
127
- await Bun.write(join(source, "README.md"), "review two\n")
128
- await git(["commit", "-am", "review two"], source)
129
- const refreshed = await runTestEffect(
130
- ReviewService.pipe(
131
- Effect.flatMap((service) =>
132
- service.refresh("review", root, created.revision),
133
- ),
134
- ),
135
- )
136
- expect(refreshed.changed).toBe(true)
137
- expect(
138
- await Bun.file(join(workspace.codePath, "agency/README.md")).text(),
139
- ).toBe("review two\n")
140
- const oldPins = Bun.spawnSync([
141
- "git",
142
- "-C",
143
- join(root, "repos/agency"),
144
- "for-each-ref",
145
- "--format=%(refname)",
146
- "--points-at",
147
- original,
148
- "refs/agency/reviews",
149
- ])
150
- expect(new TextDecoder().decode(oldPins.stdout).trim()).toBe("")
151
- })
152
-
153
- test("rejects delivery and phase operations even when forced", async () => {
154
- await createReview()
155
- await expect(
156
- runTestEffect(
157
- PullRequestService.pipe(
158
- Effect.flatMap((service) =>
159
- service.create("review", undefined, false, root, { force: true }),
160
- ),
161
- ),
162
- ),
163
- ).rejects.toThrow("cannot create a delivery pull request")
164
- await expect(
165
- runTestEffect(
166
- PhaseService.pipe(
167
- Effect.flatMap((service) =>
168
- service.create(
169
- {
170
- taskId: "review",
171
- id: "phase",
172
- firstPhase: "original",
173
- repo: "agency",
174
- branch: "phase",
175
- base: "main",
176
- },
177
- root,
178
- ),
179
- ),
180
- ),
181
- ),
182
- ).rejects.toThrow("cannot be converted to phases")
183
- })
184
-
185
- test("rolls back document and checkout when pin advancement fails", async () => {
186
- const created = await createReview()
187
- const workspace = await runTestEffect(
188
- WorktreeService.pipe(
189
- Effect.flatMap((service) =>
190
- service.materialize("review", undefined, root),
191
- ),
192
- ),
193
- )
194
- const original = "review" in created.data ? created.data.review.commit : ""
195
- await Bun.write(join(source, "README.md"), "new source\n")
196
- await git(["commit", "-am", "new source"], source)
197
- await git(
198
- ["update-ref", "refs/agency/reviews/726576696577", "main"],
199
- join(root, "repos/agency"),
200
- )
201
- await expect(
202
- runTestEffect(
203
- ReviewService.pipe(
204
- Effect.flatMap((service) => service.refresh("review", root)),
205
- ),
206
- ),
207
- ).rejects.toThrow("rolled back")
208
- const current = await runTestEffect(
209
- TaskService.pipe(
210
- Effect.flatMap((service) => service.show("review", root)),
211
- ),
212
- )
213
- expect("review" in current.data && current.data.review.commit).toBe(
214
- original,
215
- )
216
- expect(
217
- await Bun.file(join(workspace.reviewPath!, "README.md")).text(),
218
- ).toBe("review one\n")
219
- })
220
-
221
- test("blocks refresh of a dirty detached checkout", async () => {
222
- await createReview()
223
- const workspace = await runTestEffect(
224
- WorktreeService.pipe(
225
- Effect.flatMap((service) =>
226
- service.materialize("review", undefined, root),
227
- ),
228
- ),
229
- )
230
- await Bun.write(join(workspace.codePath, "agency/dirty.txt"), "dirty\n")
231
- await expect(
232
- runTestEffect(
233
- ReviewService.pipe(
234
- Effect.flatMap((service) => service.refresh("review", root)),
235
- ),
236
- ),
237
- ).rejects.toThrow("dirty or structurally unexpected")
238
- })
239
-
240
- test("rejects unsafe branch sources and mixed create inputs", async () => {
241
- for (const ref of [
242
- "HEAD",
243
- "refs/tags/v1",
244
- "--upload-pack=evil",
245
- "feature:*",
246
- "feature/*",
247
- "feature..bad",
248
- ]) {
249
- await expect(
250
- runTestEffect(
251
- ReviewService.pipe(
252
- Effect.flatMap((service) =>
253
- service.resolve("agency", { ref }, root),
254
- ),
255
- ),
256
- ),
257
- ).rejects.toThrow("Invalid review branch")
258
- }
259
- const resolved = await runTestEffect(
260
- ReviewService.pipe(
261
- Effect.flatMap((service) =>
262
- service.resolve("agency", { ref: "origin/review-me" }, root),
263
- ),
264
- ),
265
- )
266
- expect(resolved.source).toEqual({
267
- kind: "branch",
268
- ref: "refs/heads/review-me",
269
- })
270
- await expect(
271
- runTestEffect(
272
- TaskService.pipe(
273
- Effect.flatMap((service) =>
274
- service.create(
275
- {
276
- id: "mixed",
277
- ticketUrl: null,
278
- review: resolved,
279
- repo: "agency",
280
- branch: "mixed",
281
- base: "main",
282
- },
283
- root,
284
- ),
285
- ),
286
- ),
287
- ),
288
- ).rejects.toThrow("cannot include writable")
289
- })
290
-
291
- test("removes a task-scoped pin when task creation fails", async () => {
292
- await runTestEffect(
293
- TaskService.pipe(
294
- Effect.flatMap((service) =>
295
- service.create(
296
- {
297
- id: "review",
298
- ticketUrl: null,
299
- repo: "agency",
300
- branch: "task/review",
301
- base: "main",
302
- },
303
- root,
304
- ),
305
- ),
306
- ),
307
- )
308
- await expect(
309
- runTestEffect(
310
- taskCommand({
311
- subcommand: "create",
312
- args: ["review"],
313
- review: "agency",
314
- ref: "review-me",
315
- cwd: root,
316
- silent: true,
317
- }),
318
- ),
319
- ).rejects.toThrow("already exists")
320
- const pin = Bun.spawnSync([
321
- "git",
322
- "-C",
323
- join(root, "repos/agency"),
324
- "show-ref",
325
- "--verify",
326
- "refs/agency/reviews/726576696577",
327
- ])
328
- expect(pin.exitCode).not.toBe(0)
329
- })
330
-
331
- test("CLI creation stores no writable execution defaults", async () => {
332
- await runTestEffect(
333
- taskCommand({
334
- subcommand: "create",
335
- args: ["command-review"],
336
- review: "agency",
337
- ref: "origin/review-me",
338
- cwd: root,
339
- silent: true,
340
- }),
341
- )
342
- const created = await runTestEffect(
343
- TaskService.pipe(
344
- Effect.flatMap((service) => service.show("command-review", root)),
345
- ),
346
- )
347
- expect("review" in created.data).toBe(true)
348
- expect("repo" in created.data).toBe(false)
349
- expect("branch" in created.data).toBe(false)
350
- expect("base" in created.data).toBe(false)
351
- })
352
-
353
- test("sync observes source movement without changing the pin", async () => {
354
- const created = await createReview()
355
- const original = "review" in created.data ? created.data.review.commit : ""
356
- await Bun.write(join(source, "README.md"), "moved\n")
357
- await git(["commit", "-am", "move source"], source)
358
- const synced = await runTestEffect(
359
- SyncService.pipe(
360
- Effect.flatMap((service) => service.reconcile({ cwd: root })),
361
- ),
362
- )
363
- const state = synced.executions.find(
364
- (execution) => execution.target === "task:review",
365
- )
366
- expect(state?.review?.pinnedCommit).toBe(original)
367
- expect(state?.review?.sourceCommit).not.toBe(original)
368
- const current = await runTestEffect(
369
- TaskService.pipe(
370
- Effect.flatMap((service) => service.show("review", root)),
371
- ),
372
- )
373
- expect("review" in current.data && current.data.review.commit).toBe(
374
- original,
375
- )
376
- })
377
-
378
- test("archives the pinned checkout after its source branch is deleted", async () => {
379
- await createReview()
380
- const workspace = await runTestEffect(
381
- WorktreeService.pipe(
382
- Effect.flatMap((service) =>
383
- service.materialize("review", undefined, root),
384
- ),
385
- ),
386
- )
387
- await git(["switch", "main"], source)
388
- await git(["branch", "-D", "review-me"], source)
389
- await runTestEffect(
390
- TaskService.pipe(
391
- Effect.flatMap((service) =>
392
- service.setStatus("review", "done", root, {
393
- summary: "Review completed.",
394
- }),
395
- ),
396
- ),
397
- )
398
- const archived = await runTestEffect(
399
- ArchiveService.pipe(
400
- Effect.flatMap((service) => service.archiveTask("review", root)),
401
- ),
402
- )
403
- expect(archived.removedWorktrees).toContain(
404
- join(workspace.codePath, "agency"),
405
- )
406
- expect(await Bun.file(join(archived.path, "TASK.md")).exists()).toBe(true)
407
- })
408
- })