@markjaquith/agency 2.51.0 → 2.52.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/package.json
CHANGED
|
@@ -158,6 +158,77 @@ describe("GraphMutationService", () => {
|
|
|
158
158
|
)
|
|
159
159
|
})
|
|
160
160
|
|
|
161
|
+
test("sets and clears pull request metadata with materialized code", async () => {
|
|
162
|
+
await mkdir(join(root, "tasks/alpha/code/agency"), { recursive: true })
|
|
163
|
+
await mkdir(join(root, "tasks/multi/phases/build/code/agency"), {
|
|
164
|
+
recursive: true,
|
|
165
|
+
})
|
|
166
|
+
const taskPr = "https://github.com/example/agency/pull/12"
|
|
167
|
+
const phasePr = "https://github.com/example/agency/pull/13"
|
|
168
|
+
|
|
169
|
+
await runTestEffect(
|
|
170
|
+
Effect.gen(function* () {
|
|
171
|
+
const mutations = yield* GraphMutationService
|
|
172
|
+
yield* mutations.updateTask("alpha", { pr: taskPr }, root)
|
|
173
|
+
yield* mutations.updatePhase("multi", "build", { pr: phasePr }, root)
|
|
174
|
+
const tasks = yield* TaskService
|
|
175
|
+
const phases = yield* PhaseService
|
|
176
|
+
expect((yield* tasks.show("alpha", root)).data).toMatchObject({
|
|
177
|
+
pr: taskPr,
|
|
178
|
+
})
|
|
179
|
+
expect((yield* phases.show("multi", "build", root)).data.pr).toBe(
|
|
180
|
+
phasePr,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
yield* mutations.updateTask("alpha", { pr: null }, root)
|
|
184
|
+
yield* mutations.updatePhase("multi", "build", { pr: null }, root)
|
|
185
|
+
expect((yield* tasks.show("alpha", root)).data).toMatchObject({
|
|
186
|
+
pr: null,
|
|
187
|
+
})
|
|
188
|
+
expect((yield* phases.show("multi", "build", root)).data.pr).toBeNull()
|
|
189
|
+
}),
|
|
190
|
+
)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
test("rejects checkout topology updates with materialized code", async () => {
|
|
194
|
+
await mkdir(join(root, "tasks/alpha/code/agency"), { recursive: true })
|
|
195
|
+
await mkdir(join(root, "tasks/multi/phases/build/code/agency"), {
|
|
196
|
+
recursive: true,
|
|
197
|
+
})
|
|
198
|
+
const topologyUpdates = [
|
|
199
|
+
{ repo: "docs" },
|
|
200
|
+
{ repos: [{ repo: "docs", ref: "main" }] },
|
|
201
|
+
{ branch: "feature/materialized" },
|
|
202
|
+
{ base: "develop" },
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
for (const updates of topologyUpdates) {
|
|
206
|
+
await expect(
|
|
207
|
+
runTestEffect(
|
|
208
|
+
Effect.gen(function* () {
|
|
209
|
+
return yield* (yield* GraphMutationService).updateTask(
|
|
210
|
+
"alpha",
|
|
211
|
+
updates,
|
|
212
|
+
root,
|
|
213
|
+
)
|
|
214
|
+
}),
|
|
215
|
+
),
|
|
216
|
+
).rejects.toThrow("materialized code")
|
|
217
|
+
await expect(
|
|
218
|
+
runTestEffect(
|
|
219
|
+
Effect.gen(function* () {
|
|
220
|
+
return yield* (yield* GraphMutationService).updatePhase(
|
|
221
|
+
"multi",
|
|
222
|
+
"build",
|
|
223
|
+
updates,
|
|
224
|
+
root,
|
|
225
|
+
)
|
|
226
|
+
}),
|
|
227
|
+
),
|
|
228
|
+
).rejects.toThrow("materialized code")
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
|
|
161
232
|
test("preserves dependency order and rejects cycles", async () => {
|
|
162
233
|
await runTestEffect(
|
|
163
234
|
Effect.gen(function* () {
|
|
@@ -360,6 +360,12 @@ export class GraphMutationService extends Effect.Service<GraphMutationService>()
|
|
|
360
360
|
"base",
|
|
361
361
|
"pr",
|
|
362
362
|
].some((key) => updates[key as keyof TaskUpdates] !== undefined)
|
|
363
|
+
const checkoutTopologyChange = [
|
|
364
|
+
"repo",
|
|
365
|
+
"repos",
|
|
366
|
+
"branch",
|
|
367
|
+
"base",
|
|
368
|
+
].some((key) => updates[key as keyof TaskUpdates] !== undefined)
|
|
363
369
|
if ("review" in record.data && executionChange) {
|
|
364
370
|
return yield* new GraphMutationError({
|
|
365
371
|
message: `Review task '${id}' does not have writable execution metadata`,
|
|
@@ -390,7 +396,7 @@ export class GraphMutationService extends Effect.Service<GraphMutationService>()
|
|
|
390
396
|
})
|
|
391
397
|
}
|
|
392
398
|
if (
|
|
393
|
-
|
|
399
|
+
checkoutTopologyChange &&
|
|
394
400
|
(yield* fs.isDirectory(join(dirname(record.path), "code")))
|
|
395
401
|
) {
|
|
396
402
|
return yield* new GraphMutationError({
|
|
@@ -506,8 +512,14 @@ export class GraphMutationService extends Effect.Service<GraphMutationService>()
|
|
|
506
512
|
"base",
|
|
507
513
|
"pr",
|
|
508
514
|
].some((key) => updates[key as keyof PhaseUpdates] !== undefined)
|
|
515
|
+
const checkoutTopologyChange = [
|
|
516
|
+
"repo",
|
|
517
|
+
"repos",
|
|
518
|
+
"branch",
|
|
519
|
+
"base",
|
|
520
|
+
].some((key) => updates[key as keyof PhaseUpdates] !== undefined)
|
|
509
521
|
if (
|
|
510
|
-
|
|
522
|
+
checkoutTopologyChange &&
|
|
511
523
|
(yield* fs.isDirectory(join(dirname(record.path), "code")))
|
|
512
524
|
) {
|
|
513
525
|
return yield* new GraphMutationError({
|
|
@@ -3,6 +3,7 @@ import { Effect } from "effect"
|
|
|
3
3
|
import { lstat, mkdir } from "node:fs/promises"
|
|
4
4
|
import { join } from "node:path"
|
|
5
5
|
import { cleanupTempDir, createTempDir, runTestEffect } from "../test-utils"
|
|
6
|
+
import { ClaimService } from "./ClaimService"
|
|
6
7
|
import { TaskService } from "./TaskService"
|
|
7
8
|
import { VcsMigrationService } from "./VcsMigrationService"
|
|
8
9
|
import { WorktreeService } from "./WorktreeService"
|
|
@@ -149,7 +150,7 @@ describe("VcsMigrationService", () => {
|
|
|
149
150
|
).toEqual([])
|
|
150
151
|
})
|
|
151
152
|
|
|
152
|
-
test("
|
|
153
|
+
test("allows clean unclaimed working workspaces", async () => {
|
|
153
154
|
if (!Bun.which("jj")) return
|
|
154
155
|
await runTestEffect(
|
|
155
156
|
TaskService.pipe(
|
|
@@ -158,6 +159,41 @@ describe("VcsMigrationService", () => {
|
|
|
158
159
|
),
|
|
159
160
|
),
|
|
160
161
|
)
|
|
162
|
+
const migrated = await runTestEffect(
|
|
163
|
+
VcsMigrationService.pipe(
|
|
164
|
+
Effect.flatMap((service) =>
|
|
165
|
+
service.migrate("jj", root, { apply: true }),
|
|
166
|
+
),
|
|
167
|
+
),
|
|
168
|
+
)
|
|
169
|
+
expect(migrated.mode).toBe("apply")
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
test("blocks active claims", async () => {
|
|
173
|
+
if (!Bun.which("jj")) return
|
|
174
|
+
const inspected = await runTestEffect(
|
|
175
|
+
ClaimService.pipe(
|
|
176
|
+
Effect.flatMap((service) =>
|
|
177
|
+
service.inspect("example", undefined, root),
|
|
178
|
+
),
|
|
179
|
+
),
|
|
180
|
+
)
|
|
181
|
+
await runTestEffect(
|
|
182
|
+
ClaimService.pipe(
|
|
183
|
+
Effect.flatMap((service) =>
|
|
184
|
+
service.claim(
|
|
185
|
+
{
|
|
186
|
+
taskId: "example",
|
|
187
|
+
claimant: "orchestrator",
|
|
188
|
+
runner: "agent",
|
|
189
|
+
sessionId: "session-1",
|
|
190
|
+
revision: inspected.revision,
|
|
191
|
+
},
|
|
192
|
+
root,
|
|
193
|
+
),
|
|
194
|
+
),
|
|
195
|
+
),
|
|
196
|
+
)
|
|
161
197
|
await expect(
|
|
162
198
|
runTestEffect(
|
|
163
199
|
VcsMigrationService.pipe(
|
|
@@ -167,12 +203,10 @@ describe("VcsMigrationService", () => {
|
|
|
167
203
|
),
|
|
168
204
|
),
|
|
169
205
|
).rejects.toThrow("is active")
|
|
206
|
+
})
|
|
170
207
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
Effect.flatMap((service) => service.setStatus("example", "open", root)),
|
|
174
|
-
),
|
|
175
|
-
)
|
|
208
|
+
test("blocks dirty workspaces", async () => {
|
|
209
|
+
if (!Bun.which("jj")) return
|
|
176
210
|
await Bun.write(join(root, "tasks/example/code/agency/DIRTY.md"), "dirty\n")
|
|
177
211
|
await expect(
|
|
178
212
|
runTestEffect(
|
|
@@ -243,11 +243,7 @@ const inspectMigration = (startPath: string, requestedTarget?: VcsKind) =>
|
|
|
243
243
|
|
|
244
244
|
const records = yield* executionRecords(root)
|
|
245
245
|
for (const record of records) {
|
|
246
|
-
if (
|
|
247
|
-
record.status === "working" ||
|
|
248
|
-
record.status === "delegated" ||
|
|
249
|
-
record.claimActive
|
|
250
|
-
) {
|
|
246
|
+
if (record.claimActive) {
|
|
251
247
|
const label = record.phaseId
|
|
252
248
|
? `phase:${record.taskId}/${record.phaseId}`
|
|
253
249
|
: `task:${record.taskId}`
|