@markjaquith/agency 2.74.0 → 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.
- package/README.md +23 -121
- package/cli-main.ts +0 -26
- package/cli.ts +1 -17
- package/package.json +1 -2
- package/schemas/agency-graph-v1.schema.json +1 -1
- package/src/cli-parser.test.ts +0 -19
- package/src/cli-parser.ts +0 -23
- package/src/cli.test.ts +3 -3
- package/src/commands/context.test.ts +0 -67
- package/src/commands/init.test.ts +1 -2
- package/src/commands/pr.test.ts +2 -87
- package/src/commands/pr.ts +2 -79
- package/src/commands/push.test.ts +1 -2
- package/src/graph-schema.ts +1 -1
- package/src/services/ArchiveBulkService.test.ts +1 -142
- package/src/services/ArchiveService.test.ts +1 -172
- package/src/services/ArchiveService.ts +0 -26
- package/src/services/ContextService.ts +3 -47
- package/src/services/DoctorService.ts +8 -24
- package/src/services/GraphService.test.ts +1 -0
- package/src/services/GraphService.ts +2 -5
- package/src/services/PhaseService.ts +70 -191
- package/src/services/PullRequestService.test.ts +1 -45
- package/src/services/PullRequestService.ts +1 -22
- package/src/services/PushService.test.ts +29 -295
- package/src/services/PushService.ts +10 -239
- package/src/services/RepositoryService.test.ts +1 -61
- package/src/services/RepositoryService.ts +2 -13
- package/src/services/ReviewService.test.ts +1 -84
- package/src/services/ReviewService.ts +9 -48
- package/src/services/SyncService.test.ts +0 -81
- package/src/services/SyncService.ts +12 -39
- package/src/services/TaskPhaseService.test.ts +0 -80
- package/src/services/TaskService.ts +1 -9
- package/src/services/VersionControlService.test.ts +4 -117
- package/src/services/VersionControlService.ts +3 -426
- package/src/services/WorkbaseService.test.ts +0 -20
- package/src/services/WorkbaseService.ts +1 -18
- package/src/services/WorktreeService.test.ts +1 -705
- package/src/services/WorktreeService.ts +395 -1604
- package/src/services/push-validation.ts +0 -7
- package/src/test-utils.ts +0 -4
- package/src/workbase/AGENTS.md +4 -5
- package/src/workbase/checkout-command.test.ts +1 -1
- package/src/workbase/checkout-command.ts +1 -1
- package/src/workbase/delivery-command.test.ts +4 -35
- package/src/workbase/delivery-command.ts +2 -15
- package/src/workbase/schemas.test.ts +0 -19
- package/src/workbase/schemas.ts +1 -2
- package/src/commands/vcs.test.ts +0 -75
- package/src/commands/vcs.ts +0 -72
- package/src/services/VcsMigrationService.test.ts +0 -348
- package/src/services/VcsMigrationService.ts +0 -857
- package/src/vcs-status-fast.ts +0 -310
- package/src/workbase/version-control.ts +0 -5
- package/src/workbase/workspace-command.test.ts +0 -70
- package/src/workbase/workspace-command.ts +0 -63
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
|
-
import { Effect } from "effect"
|
|
3
|
-
import { lstat, mkdir, rm } from "node:fs/promises"
|
|
4
|
-
import { join } from "node:path"
|
|
5
|
-
import { cleanupTempDir, createTempDir, runTestEffect } from "../test-utils"
|
|
6
|
-
import { ClaimService } from "./ClaimService"
|
|
7
|
-
import { TaskService } from "./TaskService"
|
|
8
|
-
import { VcsMigrationService } from "./VcsMigrationService"
|
|
9
|
-
import { WorktreeService } from "./WorktreeService"
|
|
10
|
-
import { runVcsStatusFast } from "../vcs-status-fast"
|
|
11
|
-
|
|
12
|
-
const run = async (args: string[], cwd?: string) => {
|
|
13
|
-
const child = Bun.spawn(args, { cwd, stdout: "pipe", stderr: "pipe" })
|
|
14
|
-
const exitCode = await child.exited
|
|
15
|
-
if (exitCode !== 0) throw new Error(await new Response(child.stderr).text())
|
|
16
|
-
return new Response(child.stdout).text()
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const exists = async (path: string) => {
|
|
20
|
-
try {
|
|
21
|
-
await lstat(path)
|
|
22
|
-
return true
|
|
23
|
-
} catch {
|
|
24
|
-
return false
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
describe("VcsMigrationService", () => {
|
|
29
|
-
let root: string
|
|
30
|
-
|
|
31
|
-
beforeEach(async () => {
|
|
32
|
-
root = await createTempDir()
|
|
33
|
-
await Bun.write(
|
|
34
|
-
join(root, "agency.json"),
|
|
35
|
-
JSON.stringify({ version: 2, vcs: "git" }),
|
|
36
|
-
)
|
|
37
|
-
const source = join(root, "source")
|
|
38
|
-
await mkdir(source)
|
|
39
|
-
await run(["git", "init", "--initial-branch=main"], source)
|
|
40
|
-
await run(["git", "config", "user.email", "test@example.com"], source)
|
|
41
|
-
await run(["git", "config", "user.name", "Test"], source)
|
|
42
|
-
await Bun.write(join(source, "README.md"), "example\n")
|
|
43
|
-
await run(["git", "add", "README.md"], source)
|
|
44
|
-
await run(["git", "commit", "-m", "initial"], source)
|
|
45
|
-
await mkdir(join(root, "repos"))
|
|
46
|
-
await run(["git", "clone", "--bare", source, join(root, "repos/agency")])
|
|
47
|
-
await run(["git", "clone", "--bare", source, join(root, "repos/effect")])
|
|
48
|
-
await runTestEffect(
|
|
49
|
-
TaskService.pipe(
|
|
50
|
-
Effect.flatMap((service) =>
|
|
51
|
-
service.create(
|
|
52
|
-
{
|
|
53
|
-
id: "example",
|
|
54
|
-
ticketUrl: null,
|
|
55
|
-
repo: "agency",
|
|
56
|
-
repos: [{ repo: "effect", ref: "main" }],
|
|
57
|
-
branch: "task/example",
|
|
58
|
-
base: "main",
|
|
59
|
-
},
|
|
60
|
-
root,
|
|
61
|
-
),
|
|
62
|
-
),
|
|
63
|
-
),
|
|
64
|
-
)
|
|
65
|
-
await runTestEffect(
|
|
66
|
-
WorktreeService.pipe(
|
|
67
|
-
Effect.flatMap((service) =>
|
|
68
|
-
service.materialize("example", undefined, root),
|
|
69
|
-
),
|
|
70
|
-
),
|
|
71
|
-
)
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
afterEach(async () => cleanupTempDir(root))
|
|
75
|
-
|
|
76
|
-
test("reports same-backend status for materialized workspaces", async () => {
|
|
77
|
-
const status = await runTestEffect(
|
|
78
|
-
VcsMigrationService.pipe(
|
|
79
|
-
Effect.flatMap((service) => service.status(root)),
|
|
80
|
-
),
|
|
81
|
-
)
|
|
82
|
-
expect(status).toMatchObject({
|
|
83
|
-
configured: "git",
|
|
84
|
-
source: "git",
|
|
85
|
-
target: "git",
|
|
86
|
-
workspaceCount: 2,
|
|
87
|
-
blockers: [],
|
|
88
|
-
})
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
test("fast jj status matches the validated service result", async () => {
|
|
92
|
-
if (!Bun.which("jj")) return
|
|
93
|
-
await runTestEffect(
|
|
94
|
-
VcsMigrationService.pipe(
|
|
95
|
-
Effect.flatMap((service) =>
|
|
96
|
-
service.migrate("jj", root, { apply: true }),
|
|
97
|
-
),
|
|
98
|
-
),
|
|
99
|
-
)
|
|
100
|
-
const taskPath = join(root, "tasks/example/TASK.md")
|
|
101
|
-
const content = await Bun.file(taskPath).text()
|
|
102
|
-
const withoutReference = content.replace(
|
|
103
|
-
/\nrepos:\n(?: .+\n)+(?=branch:)/,
|
|
104
|
-
"\n",
|
|
105
|
-
)
|
|
106
|
-
expect(withoutReference).not.toBe(content)
|
|
107
|
-
await Bun.write(taskPath, withoutReference)
|
|
108
|
-
|
|
109
|
-
const validated = await runTestEffect(
|
|
110
|
-
VcsMigrationService.pipe(
|
|
111
|
-
Effect.flatMap((service) => service.status(root)),
|
|
112
|
-
),
|
|
113
|
-
)
|
|
114
|
-
const output: string[] = []
|
|
115
|
-
expect(
|
|
116
|
-
await runVcsStatusFast(true, root, (line) => output.push(line)),
|
|
117
|
-
).toBe(true)
|
|
118
|
-
expect(JSON.parse(output.join("\n")).result).toEqual(validated)
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
test("migrates Git worktrees to jj workspaces and back", async () => {
|
|
122
|
-
if (!Bun.which("jj")) return
|
|
123
|
-
const checkout = join(root, "tasks/example/code/agency")
|
|
124
|
-
const reference = join(root, "tasks/example/code/effect")
|
|
125
|
-
const dryRun = await runTestEffect(
|
|
126
|
-
VcsMigrationService.pipe(
|
|
127
|
-
Effect.flatMap((service) => service.migrate("jj", root)),
|
|
128
|
-
),
|
|
129
|
-
)
|
|
130
|
-
expect(dryRun).toMatchObject({
|
|
131
|
-
source: "git",
|
|
132
|
-
target: "jj",
|
|
133
|
-
mode: "dry-run",
|
|
134
|
-
workspaceCount: 2,
|
|
135
|
-
})
|
|
136
|
-
expect((await Bun.file(join(root, "agency.json")).json()).vcs).toBe("git")
|
|
137
|
-
|
|
138
|
-
const migrated = await runTestEffect(
|
|
139
|
-
VcsMigrationService.pipe(
|
|
140
|
-
Effect.flatMap((service) =>
|
|
141
|
-
service.migrate("jj", root, { apply: true }),
|
|
142
|
-
),
|
|
143
|
-
),
|
|
144
|
-
)
|
|
145
|
-
expect(migrated.mode).toBe("apply")
|
|
146
|
-
expect((await Bun.file(join(root, "agency.json")).json()).vcs).toBe("jj")
|
|
147
|
-
expect(await exists(join(root, "repos/agency/.jj"))).toBe(true)
|
|
148
|
-
expect(await exists(join(root, "repos/effect/.jj"))).toBe(true)
|
|
149
|
-
expect(await exists(join(checkout, ".jj"))).toBe(true)
|
|
150
|
-
expect(await exists(join(reference, ".jj"))).toBe(true)
|
|
151
|
-
expect(
|
|
152
|
-
await run(["jj", "-R", checkout, "log", "--no-graph", "-r", "@-"]),
|
|
153
|
-
).toContain("initial")
|
|
154
|
-
expect(
|
|
155
|
-
(
|
|
156
|
-
await runTestEffect(
|
|
157
|
-
WorktreeService.pipe(
|
|
158
|
-
Effect.flatMap((service) =>
|
|
159
|
-
service.inspect("example", undefined, root),
|
|
160
|
-
),
|
|
161
|
-
),
|
|
162
|
-
)
|
|
163
|
-
).conflicts,
|
|
164
|
-
).toEqual([])
|
|
165
|
-
|
|
166
|
-
await runTestEffect(
|
|
167
|
-
VcsMigrationService.pipe(
|
|
168
|
-
Effect.flatMap((service) =>
|
|
169
|
-
service.migrate("git", root, { apply: true }),
|
|
170
|
-
),
|
|
171
|
-
),
|
|
172
|
-
)
|
|
173
|
-
expect((await Bun.file(join(root, "agency.json")).json()).vcs).toBe("git")
|
|
174
|
-
expect(await exists(join(root, "repos/agency/.jj"))).toBe(false)
|
|
175
|
-
expect(await exists(join(root, "repos/effect/.jj"))).toBe(false)
|
|
176
|
-
expect(await exists(join(checkout, ".git"))).toBe(true)
|
|
177
|
-
expect(await exists(join(reference, ".git"))).toBe(true)
|
|
178
|
-
expect(
|
|
179
|
-
(await run(["git", "-C", checkout, "branch", "--show-current"])).trim(),
|
|
180
|
-
).toBe("task/example")
|
|
181
|
-
expect(
|
|
182
|
-
(
|
|
183
|
-
await run(["git", "-C", reference, "rev-parse", "--abbrev-ref", "HEAD"])
|
|
184
|
-
).trim(),
|
|
185
|
-
).toBe("HEAD")
|
|
186
|
-
expect(
|
|
187
|
-
(
|
|
188
|
-
await runTestEffect(
|
|
189
|
-
WorktreeService.pipe(
|
|
190
|
-
Effect.flatMap((service) =>
|
|
191
|
-
service.inspect("example", undefined, root),
|
|
192
|
-
),
|
|
193
|
-
),
|
|
194
|
-
)
|
|
195
|
-
).conflicts,
|
|
196
|
-
).toEqual([])
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
test("blocks non-colocated jj to Git migration before mutation", async () => {
|
|
200
|
-
if (!Bun.which("jj")) return
|
|
201
|
-
await runTestEffect(
|
|
202
|
-
WorktreeService.pipe(
|
|
203
|
-
Effect.flatMap((service) => service.remove("example", undefined, root)),
|
|
204
|
-
),
|
|
205
|
-
)
|
|
206
|
-
for (const alias of ["agency", "effect"]) {
|
|
207
|
-
const repository = join(root, "repos", alias)
|
|
208
|
-
await rm(repository, { recursive: true, force: true })
|
|
209
|
-
await run([
|
|
210
|
-
"jj",
|
|
211
|
-
"git",
|
|
212
|
-
"clone",
|
|
213
|
-
"--no-colocate",
|
|
214
|
-
join(root, "source"),
|
|
215
|
-
repository,
|
|
216
|
-
])
|
|
217
|
-
expect(await exists(join(repository, ".git"))).toBe(false)
|
|
218
|
-
}
|
|
219
|
-
await Bun.write(
|
|
220
|
-
join(root, "agency.json"),
|
|
221
|
-
JSON.stringify({ version: 2, vcs: "jj" }),
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
const planned = await runTestEffect(
|
|
225
|
-
VcsMigrationService.pipe(
|
|
226
|
-
Effect.flatMap((service) => service.migrate("git", root)),
|
|
227
|
-
),
|
|
228
|
-
)
|
|
229
|
-
expect(planned.blockers).toEqual(
|
|
230
|
-
expect.arrayContaining([
|
|
231
|
-
expect.objectContaining({
|
|
232
|
-
target: "repository:agency",
|
|
233
|
-
message: expect.stringContaining("non-colocated"),
|
|
234
|
-
}),
|
|
235
|
-
expect.objectContaining({
|
|
236
|
-
target: "repository:effect",
|
|
237
|
-
message: expect.stringContaining("non-colocated"),
|
|
238
|
-
}),
|
|
239
|
-
]),
|
|
240
|
-
)
|
|
241
|
-
expect((await Bun.file(join(root, "agency.json")).json()).vcs).toBe("jj")
|
|
242
|
-
expect(await exists(join(root, "repos/agency/.jj"))).toBe(true)
|
|
243
|
-
await expect(
|
|
244
|
-
runTestEffect(
|
|
245
|
-
VcsMigrationService.pipe(
|
|
246
|
-
Effect.flatMap((service) =>
|
|
247
|
-
service.migrate("git", root, { apply: true }),
|
|
248
|
-
),
|
|
249
|
-
),
|
|
250
|
-
),
|
|
251
|
-
).rejects.toThrow("non-colocated")
|
|
252
|
-
expect((await Bun.file(join(root, "agency.json")).json()).vcs).toBe("jj")
|
|
253
|
-
expect(await exists(join(root, "repos/agency/.jj"))).toBe(true)
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
test("allows clean unclaimed working workspaces", async () => {
|
|
257
|
-
if (!Bun.which("jj")) return
|
|
258
|
-
await runTestEffect(
|
|
259
|
-
TaskService.pipe(
|
|
260
|
-
Effect.flatMap((service) =>
|
|
261
|
-
service.setStatus("example", "working", root),
|
|
262
|
-
),
|
|
263
|
-
),
|
|
264
|
-
)
|
|
265
|
-
const migrated = await runTestEffect(
|
|
266
|
-
VcsMigrationService.pipe(
|
|
267
|
-
Effect.flatMap((service) =>
|
|
268
|
-
service.migrate("jj", root, { apply: true }),
|
|
269
|
-
),
|
|
270
|
-
),
|
|
271
|
-
)
|
|
272
|
-
expect(migrated.mode).toBe("apply")
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
test("blocks active claims", async () => {
|
|
276
|
-
if (!Bun.which("jj")) return
|
|
277
|
-
const inspected = await runTestEffect(
|
|
278
|
-
ClaimService.pipe(
|
|
279
|
-
Effect.flatMap((service) =>
|
|
280
|
-
service.inspect("example", undefined, root),
|
|
281
|
-
),
|
|
282
|
-
),
|
|
283
|
-
)
|
|
284
|
-
await runTestEffect(
|
|
285
|
-
ClaimService.pipe(
|
|
286
|
-
Effect.flatMap((service) =>
|
|
287
|
-
service.claim(
|
|
288
|
-
{
|
|
289
|
-
taskId: "example",
|
|
290
|
-
claimant: "orchestrator",
|
|
291
|
-
agent: "agent",
|
|
292
|
-
sessionId: "session-1",
|
|
293
|
-
revision: inspected.revision,
|
|
294
|
-
},
|
|
295
|
-
root,
|
|
296
|
-
),
|
|
297
|
-
),
|
|
298
|
-
),
|
|
299
|
-
)
|
|
300
|
-
await expect(
|
|
301
|
-
runTestEffect(
|
|
302
|
-
VcsMigrationService.pipe(
|
|
303
|
-
Effect.flatMap((service) =>
|
|
304
|
-
service.migrate("jj", root, { apply: true }),
|
|
305
|
-
),
|
|
306
|
-
),
|
|
307
|
-
),
|
|
308
|
-
).rejects.toThrow("is active")
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
test("blocks dirty workspaces", async () => {
|
|
312
|
-
if (!Bun.which("jj")) return
|
|
313
|
-
await Bun.write(join(root, "tasks/example/code/agency/DIRTY.md"), "dirty\n")
|
|
314
|
-
await expect(
|
|
315
|
-
runTestEffect(
|
|
316
|
-
VcsMigrationService.pipe(
|
|
317
|
-
Effect.flatMap((service) =>
|
|
318
|
-
service.migrate("jj", root, { apply: true }),
|
|
319
|
-
),
|
|
320
|
-
),
|
|
321
|
-
),
|
|
322
|
-
).rejects.toThrow("must be clean")
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
test("blocks jj-only heads when migrating to Git", async () => {
|
|
326
|
-
if (!Bun.which("jj")) return
|
|
327
|
-
await runTestEffect(
|
|
328
|
-
VcsMigrationService.pipe(
|
|
329
|
-
Effect.flatMap((service) =>
|
|
330
|
-
service.migrate("jj", root, { apply: true }),
|
|
331
|
-
),
|
|
332
|
-
),
|
|
333
|
-
)
|
|
334
|
-
const repository = join(root, "repos/agency")
|
|
335
|
-
await run(["jj", "-R", repository, "new", "main", "-m", "hidden head"])
|
|
336
|
-
await run(["jj", "-R", repository, "new", "main"])
|
|
337
|
-
|
|
338
|
-
await expect(
|
|
339
|
-
runTestEffect(
|
|
340
|
-
VcsMigrationService.pipe(
|
|
341
|
-
Effect.flatMap((service) =>
|
|
342
|
-
service.migrate("git", root, { apply: true }),
|
|
343
|
-
),
|
|
344
|
-
),
|
|
345
|
-
),
|
|
346
|
-
).rejects.toThrow("jj-only heads")
|
|
347
|
-
})
|
|
348
|
-
})
|