@markjaquith/agency 3.2.1 → 3.2.3
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 +30 -60
- package/cli-main.ts +38 -72
- package/fixtures/protocol/orchestration-recipes.json +9 -34
- package/package.json +1 -4
- package/schemas/agency-graph-v1.schema.json +2 -31
- package/src/cli-parser.test.ts +13 -132
- package/src/cli-parser.ts +10 -101
- package/src/cli.test.ts +12 -111
- package/src/commands/act.ts +2 -6
- package/src/commands/push.test.ts +4 -2
- package/src/commands/push.ts +2 -0
- package/src/commands/sync.ts +3 -3
- package/src/commands/validate.ts +3 -1
- package/src/commands/work.test.ts +70 -8
- package/src/commands/work.ts +15 -8
- package/src/graph-schema.ts +0 -2
- package/src/protocol.test.ts +24 -25
- package/src/protocol.ts +56 -15
- package/src/readiness.test.ts +2 -2
- package/src/services/ArchiveBulkService.test.ts +0 -88
- package/src/services/ArchiveService.ts +0 -32
- package/src/services/FileSystemService.ts +2 -0
- package/src/services/GraphMutationService.ts +0 -26
- package/src/services/GraphService.test.ts +1 -1
- package/src/services/IntegrationService.test.ts +4 -4
- package/src/services/LifecycleTransaction.ts +5 -1
- package/src/services/PhaseService.ts +1 -8
- package/src/services/PushService.test.ts +110 -4
- package/src/services/PushService.ts +435 -85
- package/src/services/ReadinessService.test.ts +0 -34
- package/src/services/ReadinessService.ts +1 -20
- package/src/services/ReviewService.test.ts +0 -64
- package/src/services/ReviewService.ts +0 -5
- package/src/services/SyncService.test.ts +75 -88
- package/src/services/SyncService.ts +52 -123
- package/src/services/TaskPhaseService.test.ts +13 -1
- package/src/services/TaskService.ts +1 -7
- package/src/services/WorkbaseService.ts +0 -3
- package/src/services/WorktreeService.test.ts +192 -1
- package/src/services/WorktreeService.ts +169 -34
- package/src/test-utils.ts +0 -2
- package/src/usage-log.test.ts +11 -1
- package/src/usage-log.ts +22 -4
- package/src/utils/process.test.ts +10 -0
- package/src/utils/process.ts +59 -6
- package/src/workbase/AGENTS.md +9 -15
- package/src/workbase/agent-command.test.ts +0 -3
- package/src/workbase/agent-command.ts +0 -6
- package/src/workbase/document-revision.ts +0 -4
- package/src/workbase/schemas.test.ts +12 -25
- package/src/workbase/schemas.ts +0 -16
- package/src/commands/claim.ts +0 -122
- package/src/services/ClaimService.test.ts +0 -415
- package/src/services/ClaimService.ts +0 -608
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from "bun:test"
|
|
2
2
|
import { Effect } from "effect"
|
|
3
|
-
import { mkdir } from "node:fs/promises"
|
|
4
|
-
import { join } from "node:path"
|
|
3
|
+
import { chmod, mkdir } from "node:fs/promises"
|
|
4
|
+
import { join, resolve } from "node:path"
|
|
5
5
|
import { cleanupTempDir, createTempDir, runTestEffect } from "../test-utils"
|
|
6
|
+
import { errorEnvelope } from "../protocol"
|
|
6
7
|
import { PushService } from "./PushService"
|
|
7
8
|
import { TaskService } from "./TaskService"
|
|
8
9
|
import { WorktreeService } from "./WorktreeService"
|
|
@@ -130,9 +131,18 @@ describe("PushService", () => {
|
|
|
130
131
|
}
|
|
131
132
|
}
|
|
132
133
|
|
|
133
|
-
const publish = (
|
|
134
|
+
const publish = (
|
|
135
|
+
taskPath: string,
|
|
136
|
+
options: {
|
|
137
|
+
fetchTimeoutMs?: number
|
|
138
|
+
pushTimeoutMs?: number
|
|
139
|
+
retryDelayMs?: number
|
|
140
|
+
} = {},
|
|
141
|
+
) =>
|
|
134
142
|
runTestEffect(
|
|
135
|
-
PushService.pipe(
|
|
143
|
+
PushService.pipe(
|
|
144
|
+
Effect.flatMap((service) => service.publish(taskPath, options)),
|
|
145
|
+
),
|
|
136
146
|
)
|
|
137
147
|
|
|
138
148
|
const remoteBranch = async (remote: string) =>
|
|
@@ -157,6 +167,17 @@ describe("PushService", () => {
|
|
|
157
167
|
)
|
|
158
168
|
}
|
|
159
169
|
|
|
170
|
+
const prePushHook = async (checkout: string) =>
|
|
171
|
+
resolve(
|
|
172
|
+
checkout,
|
|
173
|
+
(
|
|
174
|
+
await requireCommand(
|
|
175
|
+
["git", "rev-parse", "--git-path", "hooks/pre-push"],
|
|
176
|
+
checkout,
|
|
177
|
+
)
|
|
178
|
+
).stdout,
|
|
179
|
+
)
|
|
180
|
+
|
|
160
181
|
test("publishes a clean Git HEAD and establishes upstream tracking", async () => {
|
|
161
182
|
const fixture = await setup()
|
|
162
183
|
await configureAuthor(fixture.checkout)
|
|
@@ -299,4 +320,89 @@ describe("PushService", () => {
|
|
|
299
320
|
"has an invalid author",
|
|
300
321
|
)
|
|
301
322
|
})
|
|
323
|
+
|
|
324
|
+
test("bounds a stalled pre-push hook and confirms non-publication", async () => {
|
|
325
|
+
const fixture = await setup()
|
|
326
|
+
await configureAuthor(fixture.checkout)
|
|
327
|
+
await Bun.write(join(fixture.checkout, "feature.txt"), "timeout\n")
|
|
328
|
+
await requireCommand(["git", "add", "feature.txt"], fixture.checkout)
|
|
329
|
+
await requireCommand(
|
|
330
|
+
["git", "commit", "-m", "Add timed publication"],
|
|
331
|
+
fixture.checkout,
|
|
332
|
+
)
|
|
333
|
+
const hook = await prePushHook(fixture.checkout)
|
|
334
|
+
await Bun.write(hook, "#!/bin/sh\nsleep 30\n")
|
|
335
|
+
await chmod(hook, 0o755)
|
|
336
|
+
|
|
337
|
+
const startedAt = performance.now()
|
|
338
|
+
const failure = await publish(fixture.taskPath, {
|
|
339
|
+
pushTimeoutMs: 25,
|
|
340
|
+
}).catch((error) => error)
|
|
341
|
+
expect(performance.now() - startedAt).toBeLessThan(1_000)
|
|
342
|
+
expect(errorEnvelope(failure).error).toMatchObject({
|
|
343
|
+
code: "PUSH_TIMEOUT",
|
|
344
|
+
fields: { category: "timeout", stage: "publish" },
|
|
345
|
+
retryable: true,
|
|
346
|
+
})
|
|
347
|
+
await expect(remoteBranch(fixture.remote)).rejects.toThrow()
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
test("classifies hook rejection separately from transport failure", async () => {
|
|
351
|
+
const fixture = await setup()
|
|
352
|
+
await configureAuthor(fixture.checkout)
|
|
353
|
+
await Bun.write(join(fixture.checkout, "feature.txt"), "rejected\n")
|
|
354
|
+
await requireCommand(["git", "add", "feature.txt"], fixture.checkout)
|
|
355
|
+
await requireCommand(
|
|
356
|
+
["git", "commit", "-m", "Add rejected publication"],
|
|
357
|
+
fixture.checkout,
|
|
358
|
+
)
|
|
359
|
+
const hook = await prePushHook(fixture.checkout)
|
|
360
|
+
await Bun.write(
|
|
361
|
+
hook,
|
|
362
|
+
"#!/bin/sh\necho 'pre-push hook declined' >&2\nexit 1\n",
|
|
363
|
+
)
|
|
364
|
+
await chmod(hook, 0o755)
|
|
365
|
+
|
|
366
|
+
const failure = await publish(fixture.taskPath).catch((error) => error)
|
|
367
|
+
expect(errorEnvelope(failure).error).toMatchObject({
|
|
368
|
+
code: "PUSH_HOOK_REJECTED",
|
|
369
|
+
fields: { category: "hook_rejection", stage: "publish" },
|
|
370
|
+
retryable: false,
|
|
371
|
+
})
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
test("retries one transient fetch with non-interactive authentication", async () => {
|
|
375
|
+
const fixture = await setup()
|
|
376
|
+
await configureAuthor(fixture.checkout)
|
|
377
|
+
await Bun.write(join(fixture.checkout, "feature.txt"), "retried\n")
|
|
378
|
+
await requireCommand(["git", "add", "feature.txt"], fixture.checkout)
|
|
379
|
+
await requireCommand(
|
|
380
|
+
["git", "commit", "-m", "Add retried publication"],
|
|
381
|
+
fixture.checkout,
|
|
382
|
+
)
|
|
383
|
+
const attempts = join(fixture.root, "upload-pack-attempts")
|
|
384
|
+
const uploadPack = join(fixture.root, "upload-pack")
|
|
385
|
+
await Bun.write(
|
|
386
|
+
uploadPack,
|
|
387
|
+
`#!/bin/sh
|
|
388
|
+
echo x >> ${JSON.stringify(attempts)}
|
|
389
|
+
test "$GIT_TERMINAL_PROMPT" = 0 || exit 2
|
|
390
|
+
test "$GCM_INTERACTIVE" = Never || exit 2
|
|
391
|
+
if test "$(wc -l < ${JSON.stringify(attempts)})" -eq 1; then
|
|
392
|
+
echo 'Connection reset by peer' >&2
|
|
393
|
+
exit 1
|
|
394
|
+
fi
|
|
395
|
+
exec git-upload-pack "$@"
|
|
396
|
+
`,
|
|
397
|
+
)
|
|
398
|
+
await chmod(uploadPack, 0o755)
|
|
399
|
+
await requireCommand(
|
|
400
|
+
["git", "config", "remote.origin.uploadpack", uploadPack],
|
|
401
|
+
fixture.checkout,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
const result = await publish(fixture.taskPath, { retryDelayMs: 0 })
|
|
405
|
+
expect(result.tip).toBe(await remoteBranch(fixture.remote))
|
|
406
|
+
expect((await Bun.file(attempts).text()).trim().split("\n")).toHaveLength(2)
|
|
407
|
+
})
|
|
302
408
|
})
|