@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.
- package/package.json +7 -2
- package/src/workbase/schemas.ts +12 -0
- package/fixtures/protocol/orchestration-recipes.json +0 -53
- package/pi-extensions/agency.test.ts +0 -117
- package/src/check-commits.test.ts +0 -58
- package/src/cli-parser.test.ts +0 -945
- package/src/cli.test.ts +0 -1889
- package/src/commands/act.test.ts +0 -418
- package/src/commands/archive.test.ts +0 -236
- package/src/commands/context.test.ts +0 -665
- package/src/commands/description.test.ts +0 -103
- package/src/commands/doctor.test.ts +0 -185
- package/src/commands/epic.test.ts +0 -196
- package/src/commands/init.test.ts +0 -117
- package/src/commands/integration.test.ts +0 -165
- package/src/commands/pr.test.ts +0 -248
- package/src/commands/push.test.ts +0 -56
- package/src/commands/read-only.test.ts +0 -161
- package/src/commands/repo.test.ts +0 -199
- package/src/commands/restore.test.ts +0 -106
- package/src/commands/status.test.ts +0 -113
- package/src/commands/sync.test.ts +0 -200
- package/src/commands/task-phase.test.ts +0 -410
- package/src/commands/task.test.ts +0 -281
- package/src/commands/validate.test.ts +0 -186
- package/src/commands/work.test.ts +0 -1375
- package/src/commands/workbase.test.ts +0 -170
- package/src/graph-schema.test.ts +0 -124
- package/src/protocol.test.ts +0 -163
- package/src/readiness.test.ts +0 -105
- package/src/services/ArchiveBulkService.test.ts +0 -384
- package/src/services/ArchiveService.test.ts +0 -1092
- package/src/services/EpicService.test.ts +0 -74
- package/src/services/FileSystemService.test.ts +0 -81
- package/src/services/GraphMutationService.test.ts +0 -486
- package/src/services/GraphService.test.ts +0 -494
- package/src/services/IntegrationService.test.ts +0 -1091
- package/src/services/LifecycleTransaction.test.ts +0 -145
- package/src/services/PullRequestService.test.ts +0 -716
- package/src/services/PushService.test.ts +0 -408
- package/src/services/ReadinessService.test.ts +0 -290
- package/src/services/RepositoryService.test.ts +0 -789
- package/src/services/ReviewService.test.ts +0 -408
- package/src/services/SyncService.test.ts +0 -1377
- package/src/services/TaskPhaseService.test.ts +0 -852
- package/src/services/VersionControlService.test.ts +0 -62
- package/src/services/WorkbaseService.test.ts +0 -910
- package/src/services/WorktreeLock.test.ts +0 -205
- package/src/services/WorktreePerformance.test.ts +0 -71
- package/src/services/WorktreeService.test.ts +0 -2292
- package/src/test-utils.ts +0 -110
- package/src/usage-log.test.ts +0 -188
- package/src/utils/chooser.test.ts +0 -192
- package/src/utils/effect.test.ts +0 -30
- package/src/utils/interactive.pty.test.ts +0 -128
- package/src/utils/interactive.test.tsx +0 -860
- package/src/utils/process.test.ts +0 -132
- package/src/utils/progress.test.ts +0 -37
- package/src/work-view.test.ts +0 -151
- package/src/workbase/agent-command.test.ts +0 -128
- package/src/workbase/checkout-command.test.ts +0 -62
- package/src/workbase/delivery-command.test.ts +0 -180
- package/src/workbase/dependency-graph.test.ts +0 -50
- package/src/workbase/execution-contract.test.ts +0 -161
- package/src/workbase/frontmatter.test.ts +0 -67
- package/src/workbase/repository-reference.test.ts +0 -25
- package/src/workbase/schemas.test.ts +0 -543
- package/src/workbase/work-target.test.ts +0 -108
- package/src/workbase/worktree-command.test.ts +0 -61
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, test } from "bun:test"
|
|
2
|
-
import { Effect } from "effect"
|
|
3
|
-
import { mkdtemp, readdir, rm, utimes } from "node:fs/promises"
|
|
4
|
-
import { tmpdir } from "node:os"
|
|
5
|
-
import { join } from "node:path"
|
|
6
|
-
import { withWorktreeLocks } from "./WorktreeLock"
|
|
7
|
-
|
|
8
|
-
const createTempDir = () => mkdtemp(join(tmpdir(), "agency-lock-test-"))
|
|
9
|
-
const cleanupTempDir = (path: string) =>
|
|
10
|
-
rm(path, { recursive: true, force: true })
|
|
11
|
-
|
|
12
|
-
describe("withWorktreeLocks", () => {
|
|
13
|
-
const tempDirs: string[] = []
|
|
14
|
-
|
|
15
|
-
afterEach(async () => {
|
|
16
|
-
await Promise.all(tempDirs.splice(0).map(cleanupTempDir))
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
test("rejects a concurrent operation for the same target", async () => {
|
|
20
|
-
const root = await createTempDir()
|
|
21
|
-
tempDirs.push(root)
|
|
22
|
-
let entered!: () => void
|
|
23
|
-
let release!: () => void
|
|
24
|
-
const enteredPromise = new Promise<void>((resolve) => {
|
|
25
|
-
entered = resolve
|
|
26
|
-
})
|
|
27
|
-
const releasePromise = new Promise<void>((resolve) => {
|
|
28
|
-
release = resolve
|
|
29
|
-
})
|
|
30
|
-
const held = Effect.runPromise(
|
|
31
|
-
withWorktreeLocks(
|
|
32
|
-
root,
|
|
33
|
-
[{ taskId: "alpha" }],
|
|
34
|
-
Effect.promise(async () => {
|
|
35
|
-
entered()
|
|
36
|
-
await releasePromise
|
|
37
|
-
}),
|
|
38
|
-
),
|
|
39
|
-
)
|
|
40
|
-
await enteredPromise
|
|
41
|
-
|
|
42
|
-
const conflict = await Effect.runPromise(
|
|
43
|
-
Effect.either(
|
|
44
|
-
withWorktreeLocks(root, [{ taskId: "alpha" }], Effect.void),
|
|
45
|
-
),
|
|
46
|
-
)
|
|
47
|
-
release()
|
|
48
|
-
await held
|
|
49
|
-
const lockPath = join(
|
|
50
|
-
root,
|
|
51
|
-
`.agency-worktree-${Buffer.from("alpha:task").toString("hex")}.lock`,
|
|
52
|
-
)
|
|
53
|
-
expect(conflict).toMatchObject({
|
|
54
|
-
_tag: "Left",
|
|
55
|
-
left: {
|
|
56
|
-
_tag: "WorktreeLockError",
|
|
57
|
-
message: `Another worktree operation is in progress for 'alpha'. Retry with --force or remove the stale sentinel with: rm '${lockPath}'`,
|
|
58
|
-
},
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
test("removes stale locks before acquiring them", async () => {
|
|
63
|
-
const root = await createTempDir()
|
|
64
|
-
tempDirs.push(root)
|
|
65
|
-
const lockPath = join(
|
|
66
|
-
root,
|
|
67
|
-
`.agency-worktree-${Buffer.from("alpha:task").toString("hex")}.lock`,
|
|
68
|
-
)
|
|
69
|
-
await Bun.write(lockPath, "")
|
|
70
|
-
const staleAt = new Date(Date.now() - 11 * 60 * 1000)
|
|
71
|
-
await utimes(lockPath, staleAt, staleAt)
|
|
72
|
-
|
|
73
|
-
await expect(
|
|
74
|
-
Effect.runPromise(
|
|
75
|
-
withWorktreeLocks(root, [{ taskId: "alpha" }], Effect.void),
|
|
76
|
-
),
|
|
77
|
-
).resolves.toBeUndefined()
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
test("force overrides an active lock", async () => {
|
|
81
|
-
const root = await createTempDir()
|
|
82
|
-
tempDirs.push(root)
|
|
83
|
-
let firstEntered!: () => void
|
|
84
|
-
let firstRelease!: () => void
|
|
85
|
-
const firstEnteredPromise = new Promise<void>((resolve) => {
|
|
86
|
-
firstEntered = resolve
|
|
87
|
-
})
|
|
88
|
-
const firstReleasePromise = new Promise<void>((resolve) => {
|
|
89
|
-
firstRelease = resolve
|
|
90
|
-
})
|
|
91
|
-
const first = Effect.runPromise(
|
|
92
|
-
withWorktreeLocks(
|
|
93
|
-
root,
|
|
94
|
-
[{ taskId: "alpha" }],
|
|
95
|
-
Effect.promise(async () => {
|
|
96
|
-
firstEntered()
|
|
97
|
-
await firstReleasePromise
|
|
98
|
-
}),
|
|
99
|
-
),
|
|
100
|
-
)
|
|
101
|
-
await firstEnteredPromise
|
|
102
|
-
|
|
103
|
-
let forcedEntered!: () => void
|
|
104
|
-
let forcedRelease!: () => void
|
|
105
|
-
const forcedEnteredPromise = new Promise<void>((resolve) => {
|
|
106
|
-
forcedEntered = resolve
|
|
107
|
-
})
|
|
108
|
-
const forcedReleasePromise = new Promise<void>((resolve) => {
|
|
109
|
-
forcedRelease = resolve
|
|
110
|
-
})
|
|
111
|
-
const forced = Effect.runPromise(
|
|
112
|
-
withWorktreeLocks(
|
|
113
|
-
root,
|
|
114
|
-
[{ taskId: "alpha" }],
|
|
115
|
-
Effect.promise(async () => {
|
|
116
|
-
forcedEntered()
|
|
117
|
-
await forcedReleasePromise
|
|
118
|
-
}),
|
|
119
|
-
{ force: true },
|
|
120
|
-
),
|
|
121
|
-
)
|
|
122
|
-
await forcedEnteredPromise
|
|
123
|
-
|
|
124
|
-
firstRelease()
|
|
125
|
-
await first
|
|
126
|
-
|
|
127
|
-
const conflict = await Effect.runPromise(
|
|
128
|
-
Effect.either(
|
|
129
|
-
withWorktreeLocks(root, [{ taskId: "alpha" }], Effect.void),
|
|
130
|
-
),
|
|
131
|
-
)
|
|
132
|
-
expect(conflict).toMatchObject({
|
|
133
|
-
_tag: "Left",
|
|
134
|
-
left: { _tag: "WorktreeLockError" },
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
forcedRelease()
|
|
138
|
-
await forced
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
test("releases locks when the protected operation fails", async () => {
|
|
142
|
-
const root = await createTempDir()
|
|
143
|
-
tempDirs.push(root)
|
|
144
|
-
const failure = new Error("operation failed")
|
|
145
|
-
|
|
146
|
-
const result = await Effect.runPromise(
|
|
147
|
-
Effect.either(
|
|
148
|
-
withWorktreeLocks(
|
|
149
|
-
root,
|
|
150
|
-
[{ taskId: "alpha", phaseId: "build" }],
|
|
151
|
-
Effect.fail(failure),
|
|
152
|
-
),
|
|
153
|
-
),
|
|
154
|
-
)
|
|
155
|
-
expect(result._tag).toBe("Left")
|
|
156
|
-
if (result._tag === "Left") expect(result.left).toBe(failure)
|
|
157
|
-
await expect(
|
|
158
|
-
Effect.runPromise(
|
|
159
|
-
withWorktreeLocks(
|
|
160
|
-
root,
|
|
161
|
-
[{ taskId: "alpha", phaseId: "build" }],
|
|
162
|
-
Effect.succeed("reacquired"),
|
|
163
|
-
),
|
|
164
|
-
),
|
|
165
|
-
).resolves.toBe("reacquired")
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
test("deduplicates targets while keeping task and phase locks distinct", async () => {
|
|
169
|
-
const root = await createTempDir()
|
|
170
|
-
tempDirs.push(root)
|
|
171
|
-
let entered!: () => void
|
|
172
|
-
let release!: () => void
|
|
173
|
-
const enteredPromise = new Promise<void>((resolve) => {
|
|
174
|
-
entered = resolve
|
|
175
|
-
})
|
|
176
|
-
const releasePromise = new Promise<void>((resolve) => {
|
|
177
|
-
release = resolve
|
|
178
|
-
})
|
|
179
|
-
const held = Effect.runPromise(
|
|
180
|
-
withWorktreeLocks(
|
|
181
|
-
root,
|
|
182
|
-
[
|
|
183
|
-
{ taskId: "alpha" },
|
|
184
|
-
{ taskId: "alpha" },
|
|
185
|
-
{ taskId: "alpha", phaseId: "build" },
|
|
186
|
-
],
|
|
187
|
-
Effect.promise(async () => {
|
|
188
|
-
entered()
|
|
189
|
-
await releasePromise
|
|
190
|
-
}),
|
|
191
|
-
),
|
|
192
|
-
)
|
|
193
|
-
await enteredPromise
|
|
194
|
-
|
|
195
|
-
expect(
|
|
196
|
-
(await readdir(root)).filter((path) => path.endsWith(".lock")),
|
|
197
|
-
).toHaveLength(2)
|
|
198
|
-
|
|
199
|
-
release()
|
|
200
|
-
await held
|
|
201
|
-
expect(
|
|
202
|
-
(await readdir(root)).filter((path) => path.endsWith(".lock")),
|
|
203
|
-
).toEqual([])
|
|
204
|
-
})
|
|
205
|
-
})
|
|
@@ -1,71 +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 { PhaseService } from "./PhaseService"
|
|
7
|
-
import { TaskService } from "./TaskService"
|
|
8
|
-
|
|
9
|
-
const taskDocument = (id: string) => `---
|
|
10
|
-
ticketUrl: null
|
|
11
|
-
repo: agency
|
|
12
|
-
branch: task/${id}
|
|
13
|
-
base: main
|
|
14
|
-
pr: null
|
|
15
|
-
status: open
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
# ${id}
|
|
19
|
-
`
|
|
20
|
-
|
|
21
|
-
describe("worktree document loading", () => {
|
|
22
|
-
let root: string
|
|
23
|
-
|
|
24
|
-
beforeEach(async () => {
|
|
25
|
-
root = await createTempDir()
|
|
26
|
-
await Bun.write(join(root, "agency.json"), '{"version":2}\n')
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
afterEach(async () => cleanupTempDir(root))
|
|
30
|
-
|
|
31
|
-
test("shows a task without parsing sibling task documents", async () => {
|
|
32
|
-
await mkdir(join(root, "tasks/target"), { recursive: true })
|
|
33
|
-
await mkdir(join(root, "tasks/broken"), { recursive: true })
|
|
34
|
-
await Bun.write(join(root, "tasks/target/TASK.md"), taskDocument("target"))
|
|
35
|
-
await Bun.write(join(root, "tasks/broken/TASK.md"), "invalid frontmatter")
|
|
36
|
-
|
|
37
|
-
const task = await runTestEffect(
|
|
38
|
-
TaskService.pipe(
|
|
39
|
-
Effect.flatMap((service) => service.show("target", root)),
|
|
40
|
-
),
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
expect(task.id).toBe("target")
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
test("lists phases from a preloaded task without rereading its parent", async () => {
|
|
47
|
-
const phasePath = join(root, "tasks/multi/phases/build")
|
|
48
|
-
await mkdir(phasePath, { recursive: true })
|
|
49
|
-
await Bun.write(
|
|
50
|
-
join(phasePath, "PHASE.md"),
|
|
51
|
-
`---
|
|
52
|
-
repo: agency
|
|
53
|
-
branch: task/multi-build
|
|
54
|
-
base: main
|
|
55
|
-
pr: null
|
|
56
|
-
status: open
|
|
57
|
-
---
|
|
58
|
-
|
|
59
|
-
# Build
|
|
60
|
-
`,
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
const phases = await runTestEffect(
|
|
64
|
-
PhaseService.pipe(
|
|
65
|
-
Effect.flatMap((service) => service.list("multi", root, true)),
|
|
66
|
-
),
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
expect(phases.map(({ id }) => id)).toEqual(["build"])
|
|
70
|
-
})
|
|
71
|
-
})
|