@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,165 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
|
-
import { mkdir } from "node:fs/promises"
|
|
3
|
-
import { join } from "node:path"
|
|
4
|
-
import {
|
|
5
|
-
captureLogs,
|
|
6
|
-
cleanupTempDir,
|
|
7
|
-
createTempDir,
|
|
8
|
-
runTestEffect,
|
|
9
|
-
} from "../test-utils"
|
|
10
|
-
import { integration } from "./integration"
|
|
11
|
-
|
|
12
|
-
describe("integration command", () => {
|
|
13
|
-
let root: string
|
|
14
|
-
|
|
15
|
-
beforeEach(async () => {
|
|
16
|
-
root = await createTempDir()
|
|
17
|
-
await Bun.write(join(root, "agency.json"), '{"version":2}\n')
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
afterEach(async () => cleanupTempDir(root))
|
|
21
|
-
|
|
22
|
-
test("reports integration status as JSON", async () => {
|
|
23
|
-
const logs = await captureLogs(() =>
|
|
24
|
-
runTestEffect(
|
|
25
|
-
integration({ subcommand: "status", cwd: root, json: true }),
|
|
26
|
-
),
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
expect(JSON.parse(logs[0]!)).toMatchObject({
|
|
30
|
-
root,
|
|
31
|
-
files: [
|
|
32
|
-
{ name: "agents", state: "missing" },
|
|
33
|
-
{
|
|
34
|
-
name: "opencode",
|
|
35
|
-
state: "missing",
|
|
36
|
-
diagnostic: expect.stringContaining("cannot load"),
|
|
37
|
-
remediation: expect.stringContaining("integration sync"),
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
name: "opencode-plugin",
|
|
41
|
-
state: "missing",
|
|
42
|
-
remediation: expect.stringContaining("integration sync"),
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
name: "opencode-tui",
|
|
46
|
-
state: "missing",
|
|
47
|
-
remediation: expect.stringContaining("integration sync"),
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
name: "opencode-tui-plugin",
|
|
51
|
-
state: "missing",
|
|
52
|
-
remediation: expect.stringContaining("integration sync"),
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
})
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
test("explains remediation for customized OpenCode config", async () => {
|
|
59
|
-
await mkdir(join(root, ".opencode"))
|
|
60
|
-
await Bun.write(
|
|
61
|
-
join(root, ".opencode", "opencode.json"),
|
|
62
|
-
'{"model":"test/model"}\n',
|
|
63
|
-
)
|
|
64
|
-
const logs = await captureLogs(() =>
|
|
65
|
-
runTestEffect(integration({ subcommand: "status", cwd: root })),
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
expect(logs.join("\n")).toContain("cannot guarantee its instructions")
|
|
69
|
-
expect(logs.join("\n")).toContain("global config")
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
test("formats integration status for people", async () => {
|
|
73
|
-
const logs = await captureLogs(() =>
|
|
74
|
-
runTestEffect(integration({ subcommand: "status", cwd: root })),
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
expect(logs.join("\n")).toBe(`Integration status: ${root}
|
|
78
|
-
|
|
79
|
-
Agent instructions: missing
|
|
80
|
-
Path: .agency/AGENTS.md
|
|
81
|
-
Managed workbase instructions need synchronization.
|
|
82
|
-
Action: Run 'agency integration sync' to restore managed instructions.
|
|
83
|
-
|
|
84
|
-
OpenCode config: missing
|
|
85
|
-
Path: .opencode/opencode.jsonc
|
|
86
|
-
Agency OpenCode launches cannot load current Agency instructions or whole-workbase access.
|
|
87
|
-
Action: Run 'agency integration sync' to install Agency instructions and whole-workbase OpenCode access.
|
|
88
|
-
|
|
89
|
-
OpenCode workbase plugin: missing
|
|
90
|
-
Path: .opencode/plugins/agency-repository-skills.ts
|
|
91
|
-
The managed OpenCode workbase plugin needs synchronization.
|
|
92
|
-
Action: Run 'agency integration sync' to provide workbase access and expose writable-checkout skills in OpenCode.
|
|
93
|
-
|
|
94
|
-
OpenCode TUI config: missing
|
|
95
|
-
Path: .opencode/tui.jsonc
|
|
96
|
-
The managed OpenCode TUI config needs synchronization.
|
|
97
|
-
Action: Run 'agency integration sync' to register /agency-debug.
|
|
98
|
-
|
|
99
|
-
OpenCode /agency-debug: missing
|
|
100
|
-
Path: .opencode/tui/agency-debug.ts
|
|
101
|
-
The managed OpenCode TUI diagnostic companion needs synchronization.
|
|
102
|
-
Action: Run 'agency integration sync' to install /agency-debug.`)
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
test("explicitly synchronizes integration files", async () => {
|
|
106
|
-
const logs = await captureLogs(() =>
|
|
107
|
-
runTestEffect(integration({ subcommand: "sync", cwd: root, json: true })),
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
expect(JSON.parse(logs[0]!).files).toMatchObject([
|
|
111
|
-
{ name: "agents", state: "managed", changed: true },
|
|
112
|
-
{ name: "opencode", state: "managed", changed: true },
|
|
113
|
-
{ name: "opencode-plugin", state: "managed", changed: true },
|
|
114
|
-
{ name: "opencode-tui", state: "managed", changed: true },
|
|
115
|
-
{ name: "opencode-tui-plugin", state: "managed", changed: true },
|
|
116
|
-
])
|
|
117
|
-
expect(await Bun.file(join(root, "AGENTS.md")).exists()).toBe(false)
|
|
118
|
-
expect(await Bun.file(join(root, ".agency/AGENTS.md")).exists()).toBe(true)
|
|
119
|
-
expect(
|
|
120
|
-
await Bun.file(join(root, ".opencode/opencode.jsonc")).exists(),
|
|
121
|
-
).toBe(true)
|
|
122
|
-
expect(
|
|
123
|
-
await Bun.file(join(root, ".opencode/command/agency.md")).exists(),
|
|
124
|
-
).toBe(false)
|
|
125
|
-
expect(
|
|
126
|
-
await Bun.file(
|
|
127
|
-
join(root, ".opencode/plugins/agency-repository-skills.ts"),
|
|
128
|
-
).exists(),
|
|
129
|
-
).toBe(true)
|
|
130
|
-
expect(await Bun.file(join(root, ".opencode/tui.jsonc")).exists()).toBe(
|
|
131
|
-
true,
|
|
132
|
-
)
|
|
133
|
-
expect(
|
|
134
|
-
await Bun.file(join(root, ".opencode/tui/agency-debug.ts")).exists(),
|
|
135
|
-
).toBe(true)
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
test("formats integration sync results for people", async () => {
|
|
139
|
-
const logs = await captureLogs(() =>
|
|
140
|
-
runTestEffect(integration({ subcommand: "sync", cwd: root })),
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
expect(logs.join("\n")).toBe(`Integration sync: ${root}
|
|
144
|
-
|
|
145
|
-
Agent instructions: synced
|
|
146
|
-
Path: .agency/AGENTS.md
|
|
147
|
-
Managed workbase instructions are current.
|
|
148
|
-
|
|
149
|
-
OpenCode config: synced
|
|
150
|
-
Path: .opencode/opencode.jsonc
|
|
151
|
-
Agency's managed OpenCode launch config is ready to load Agency instructions and provide whole-workbase read access.
|
|
152
|
-
|
|
153
|
-
OpenCode workbase plugin: synced
|
|
154
|
-
Path: .opencode/plugins/agency-repository-skills.ts
|
|
155
|
-
Agency's managed OpenCode plugin provides whole-workbase access and exposes writable-checkout skills.
|
|
156
|
-
|
|
157
|
-
OpenCode TUI config: synced
|
|
158
|
-
Path: .opencode/tui.jsonc
|
|
159
|
-
Agency's managed OpenCode TUI config explicitly loads /agency-debug.
|
|
160
|
-
|
|
161
|
-
OpenCode /agency-debug: synced
|
|
162
|
-
Path: .opencode/tui/agency-debug.ts
|
|
163
|
-
Agency's managed OpenCode TUI diagnostic companion is current.`)
|
|
164
|
-
})
|
|
165
|
-
})
|
package/src/commands/pr.test.ts
DELETED
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
|
-
import { Effect } from "effect"
|
|
3
|
-
import { chmod, mkdir, realpath } from "node:fs/promises"
|
|
4
|
-
import { dirname, join } from "node:path"
|
|
5
|
-
import { PullRequestService } from "../services/PullRequestService"
|
|
6
|
-
import {
|
|
7
|
-
captureLogs,
|
|
8
|
-
cleanupTempDir,
|
|
9
|
-
createTempDir,
|
|
10
|
-
runTestEffect,
|
|
11
|
-
} from "../test-utils"
|
|
12
|
-
import { pr, prCreate } from "./pr"
|
|
13
|
-
|
|
14
|
-
const write = async (root: string, path: string, content: string) => {
|
|
15
|
-
const fullPath = join(root, path)
|
|
16
|
-
await mkdir(dirname(fullPath), { recursive: true })
|
|
17
|
-
await Bun.write(fullPath, content)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe("pr command", () => {
|
|
21
|
-
let tempRoot: string
|
|
22
|
-
let capturePath: string
|
|
23
|
-
let originalPath: string | undefined
|
|
24
|
-
|
|
25
|
-
beforeEach(async () => {
|
|
26
|
-
tempRoot = await createTempDir()
|
|
27
|
-
capturePath = join(tempRoot, "capture")
|
|
28
|
-
const bin = join(tempRoot, "bin")
|
|
29
|
-
await mkdir(bin)
|
|
30
|
-
await Bun.write(
|
|
31
|
-
join(bin, "gh"),
|
|
32
|
-
`#!/bin/sh
|
|
33
|
-
printf '%s\n' "$PWD" "$@" > "$GH_CAPTURE"
|
|
34
|
-
exit "\${GH_EXIT:-0}"
|
|
35
|
-
`,
|
|
36
|
-
)
|
|
37
|
-
await chmod(join(bin, "gh"), 0o755)
|
|
38
|
-
originalPath = process.env.PATH
|
|
39
|
-
process.env.PATH = `${bin}:${originalPath ?? ""}`
|
|
40
|
-
process.env.GH_CAPTURE = capturePath
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
afterEach(async () => {
|
|
44
|
-
process.env.PATH = originalPath
|
|
45
|
-
delete process.env.GH_CAPTURE
|
|
46
|
-
delete process.env.GH_EXIT
|
|
47
|
-
await cleanupTempDir(tempRoot)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
const captured = async () =>
|
|
51
|
-
(await Bun.file(capturePath).text()).trim().split("\n")
|
|
52
|
-
|
|
53
|
-
const createExecutionWorkbase = async () => {
|
|
54
|
-
const root = join(tempRoot, "workbase")
|
|
55
|
-
await write(
|
|
56
|
-
root,
|
|
57
|
-
"agency.json",
|
|
58
|
-
`${JSON.stringify({
|
|
59
|
-
version: 2,
|
|
60
|
-
repositories: {
|
|
61
|
-
agency: { remote: "https://github.com/example/agency.git" },
|
|
62
|
-
},
|
|
63
|
-
})}\n`,
|
|
64
|
-
)
|
|
65
|
-
await mkdir(join(root, "repos/agency"), { recursive: true })
|
|
66
|
-
await mkdir(join(root, "tasks/single/code/agency"), { recursive: true })
|
|
67
|
-
await write(
|
|
68
|
-
root,
|
|
69
|
-
"tasks/single/TASK.md",
|
|
70
|
-
`---
|
|
71
|
-
ticketUrl: null
|
|
72
|
-
repo: agency
|
|
73
|
-
branch: feat/single
|
|
74
|
-
base: main
|
|
75
|
-
pr: null
|
|
76
|
-
status: open
|
|
77
|
-
---
|
|
78
|
-
|
|
79
|
-
# Single
|
|
80
|
-
`,
|
|
81
|
-
)
|
|
82
|
-
await mkdir(join(root, "tasks/multi/phases/build/code/agency"), {
|
|
83
|
-
recursive: true,
|
|
84
|
-
})
|
|
85
|
-
await write(
|
|
86
|
-
root,
|
|
87
|
-
"tasks/multi/TASK.md",
|
|
88
|
-
`---
|
|
89
|
-
ticketUrl: null
|
|
90
|
-
phases:
|
|
91
|
-
- id: build
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
# Multi
|
|
95
|
-
`,
|
|
96
|
-
)
|
|
97
|
-
await write(
|
|
98
|
-
root,
|
|
99
|
-
"tasks/multi/phases/build/PHASE.md",
|
|
100
|
-
`---
|
|
101
|
-
repo: agency
|
|
102
|
-
branch: feat/build
|
|
103
|
-
base: main
|
|
104
|
-
pr: null
|
|
105
|
-
status: open
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
# Build
|
|
109
|
-
`,
|
|
110
|
-
)
|
|
111
|
-
return root
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const createReviewWorkbase = async () => {
|
|
115
|
-
const root = join(tempRoot, "review-workbase")
|
|
116
|
-
await write(root, "agency.json", '{"version":2}\n')
|
|
117
|
-
await mkdir(join(root, "repos/agency"), { recursive: true })
|
|
118
|
-
await mkdir(join(root, "tasks/review/code/agency"), { recursive: true })
|
|
119
|
-
await write(
|
|
120
|
-
root,
|
|
121
|
-
"tasks/review/TASK.md",
|
|
122
|
-
`---
|
|
123
|
-
ticketUrl: null
|
|
124
|
-
description: Review a branch
|
|
125
|
-
review:
|
|
126
|
-
repo: agency
|
|
127
|
-
source:
|
|
128
|
-
kind: branch
|
|
129
|
-
ref: refs/heads/review
|
|
130
|
-
commit: 0123456789abcdef0123456789abcdef01234567
|
|
131
|
-
refreshedAt: 2026-08-19T00:00:00.000Z
|
|
132
|
-
status: working
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
# Review
|
|
136
|
-
`,
|
|
137
|
-
)
|
|
138
|
-
return root
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
test("creates and records an Agency pull request", async () => {
|
|
142
|
-
const url = "https://github.com/markjaquith/agency/pull/123"
|
|
143
|
-
let received: unknown[] = []
|
|
144
|
-
const logs = await captureLogs(() =>
|
|
145
|
-
Effect.runPromise(
|
|
146
|
-
prCreate({
|
|
147
|
-
taskId: "example",
|
|
148
|
-
phaseId: "implementation",
|
|
149
|
-
draft: true,
|
|
150
|
-
force: true,
|
|
151
|
-
title: "Ship it",
|
|
152
|
-
head: "task/example",
|
|
153
|
-
base: "main",
|
|
154
|
-
labels: ["ai-assisted"],
|
|
155
|
-
cwd: "/workbase",
|
|
156
|
-
json: true,
|
|
157
|
-
}).pipe(
|
|
158
|
-
Effect.provideService(PullRequestService, {
|
|
159
|
-
create: (...args: unknown[]) => {
|
|
160
|
-
received = args
|
|
161
|
-
return Effect.succeed(url)
|
|
162
|
-
},
|
|
163
|
-
} as never),
|
|
164
|
-
) as Effect.Effect<void, unknown, never>,
|
|
165
|
-
),
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
expect(JSON.parse(logs[0]!)).toEqual({ url })
|
|
169
|
-
expect(received).toEqual([
|
|
170
|
-
"example",
|
|
171
|
-
"implementation",
|
|
172
|
-
true,
|
|
173
|
-
"/workbase",
|
|
174
|
-
expect.objectContaining({
|
|
175
|
-
force: true,
|
|
176
|
-
draft: true,
|
|
177
|
-
title: "Ship it",
|
|
178
|
-
head: "task/example",
|
|
179
|
-
base: "main",
|
|
180
|
-
labels: ["ai-assisted"],
|
|
181
|
-
json: true,
|
|
182
|
-
}),
|
|
183
|
-
])
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
test("focuses task and descendant invocations on the writable checkout", async () => {
|
|
187
|
-
const root = await createExecutionWorkbase()
|
|
188
|
-
const task = join(root, "tasks/single")
|
|
189
|
-
const descendant = join(task, "notes/deep")
|
|
190
|
-
await mkdir(descendant, { recursive: true })
|
|
191
|
-
|
|
192
|
-
for (const cwd of [task, descendant]) {
|
|
193
|
-
expect(await runTestEffect(pr(["view"], cwd))).toBe(0)
|
|
194
|
-
expect(await captured()).toEqual([
|
|
195
|
-
await realpath(join(task, "code/agency")),
|
|
196
|
-
"pr",
|
|
197
|
-
"view",
|
|
198
|
-
])
|
|
199
|
-
}
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
test("focuses a phase invocation on its writable checkout", async () => {
|
|
203
|
-
const root = await createExecutionWorkbase()
|
|
204
|
-
const phase = join(root, "tasks/multi/phases/build")
|
|
205
|
-
|
|
206
|
-
expect(await runTestEffect(pr(["status"], phase))).toBe(0)
|
|
207
|
-
expect(await captured()).toEqual([
|
|
208
|
-
await realpath(join(phase, "code/agency")),
|
|
209
|
-
"pr",
|
|
210
|
-
"status",
|
|
211
|
-
])
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
test("focuses a review task invocation on its review checkout", async () => {
|
|
215
|
-
const root = await createReviewWorkbase()
|
|
216
|
-
const task = join(root, "tasks/review")
|
|
217
|
-
|
|
218
|
-
expect(await runTestEffect(pr(["view", "--web"], task))).toBe(0)
|
|
219
|
-
expect(await captured()).toEqual([
|
|
220
|
-
await realpath(join(task, "code/agency")),
|
|
221
|
-
"pr",
|
|
222
|
-
"view",
|
|
223
|
-
"--web",
|
|
224
|
-
])
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
test("falls back to the invocation directory without execution authority", async () => {
|
|
228
|
-
const root = await createExecutionWorkbase()
|
|
229
|
-
const orchestration = join(root, "tasks/multi")
|
|
230
|
-
const outside = join(tempRoot, "outside")
|
|
231
|
-
await mkdir(outside)
|
|
232
|
-
|
|
233
|
-
for (const cwd of [orchestration, outside]) {
|
|
234
|
-
expect(await runTestEffect(pr(["list"], cwd))).toBe(0)
|
|
235
|
-
expect(await captured()).toEqual([await realpath(cwd), "pr", "list"])
|
|
236
|
-
}
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
test("forwards arguments unchanged and returns the gh exit code", async () => {
|
|
240
|
-
const outside = join(tempRoot, "outside")
|
|
241
|
-
await mkdir(outside)
|
|
242
|
-
process.env.GH_EXIT = "23"
|
|
243
|
-
const args = ["create", "--title", "two words", "--", "literal"]
|
|
244
|
-
|
|
245
|
-
expect(await runTestEffect(pr(args, outside))).toBe(23)
|
|
246
|
-
expect(await captured()).toEqual([await realpath(outside), "pr", ...args])
|
|
247
|
-
})
|
|
248
|
-
})
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test"
|
|
2
|
-
import { Effect } from "effect"
|
|
3
|
-
import { PushService } from "../services/PushService"
|
|
4
|
-
import { captureLogs } from "../test-utils"
|
|
5
|
-
import type { Progress } from "../utils/progress"
|
|
6
|
-
import { push } from "./push"
|
|
7
|
-
|
|
8
|
-
describe("push command", () => {
|
|
9
|
-
test("reports deterministic progress while preserving one JSON result", async () => {
|
|
10
|
-
const updates: string[] = []
|
|
11
|
-
const progress: Progress = {
|
|
12
|
-
start: (message) => updates.push(`start:${message}`),
|
|
13
|
-
succeed: (message) => updates.push(`succeed:${message}`),
|
|
14
|
-
fail: (message) => updates.push(`fail:${message}`),
|
|
15
|
-
}
|
|
16
|
-
const result = {
|
|
17
|
-
vcs: "git" as const,
|
|
18
|
-
taskId: "example",
|
|
19
|
-
branch: "task/example",
|
|
20
|
-
base: "main",
|
|
21
|
-
remote: "origin",
|
|
22
|
-
tip: "abc123",
|
|
23
|
-
}
|
|
24
|
-
const logs = await captureLogs(() =>
|
|
25
|
-
Effect.runPromise(
|
|
26
|
-
push({ cwd: "/workbase", json: true }, progress).pipe(
|
|
27
|
-
Effect.provideService(PushService, {
|
|
28
|
-
publish: (_cwd: string, options: any) => {
|
|
29
|
-
for (const stage of [
|
|
30
|
-
"context",
|
|
31
|
-
"inspect",
|
|
32
|
-
"validate",
|
|
33
|
-
"fetch",
|
|
34
|
-
"validate",
|
|
35
|
-
"publish",
|
|
36
|
-
] as const)
|
|
37
|
-
options.onProgress(stage)
|
|
38
|
-
return Effect.succeed(result)
|
|
39
|
-
},
|
|
40
|
-
} as never),
|
|
41
|
-
) as Effect.Effect<void, unknown, never>,
|
|
42
|
-
),
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
expect(logs).toEqual([JSON.stringify(result, null, 2)])
|
|
46
|
-
expect(updates).toEqual([
|
|
47
|
-
"start:Inspecting Agency execution context",
|
|
48
|
-
"start:Selecting the publication tip",
|
|
49
|
-
"start:Validating outgoing changes",
|
|
50
|
-
"start:Fetching remote state",
|
|
51
|
-
"start:Validating outgoing changes",
|
|
52
|
-
"start:Publishing the declared branch",
|
|
53
|
-
"succeed:Published task/example to origin",
|
|
54
|
-
])
|
|
55
|
-
})
|
|
56
|
-
})
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
|
-
import { Effect } from "effect"
|
|
3
|
-
import { chmod, lstat, mkdir, readdir } from "node:fs/promises"
|
|
4
|
-
import { dirname, join } from "node:path"
|
|
5
|
-
import { cleanupTempDir, createTempDir, runTestEffect } from "../test-utils"
|
|
6
|
-
import { WorkbaseService } from "../services/WorkbaseService"
|
|
7
|
-
import { epic } from "./epic"
|
|
8
|
-
import { integration } from "./integration"
|
|
9
|
-
import { phase } from "./phase"
|
|
10
|
-
import { repo } from "./repo"
|
|
11
|
-
import { status } from "./status"
|
|
12
|
-
import { task } from "./task"
|
|
13
|
-
import { validate } from "./validate"
|
|
14
|
-
import { context } from "./context"
|
|
15
|
-
import { graph } from "./graph"
|
|
16
|
-
import { next } from "./next"
|
|
17
|
-
import { doctor } from "./doctor"
|
|
18
|
-
|
|
19
|
-
const write = async (root: string, path: string, content: string) => {
|
|
20
|
-
const fullPath = join(root, path)
|
|
21
|
-
await mkdir(dirname(fullPath), { recursive: true })
|
|
22
|
-
await Bun.write(fullPath, content)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const setReadOnly = async (path: string, readOnly: boolean): Promise<void> => {
|
|
26
|
-
const metadata = await lstat(path)
|
|
27
|
-
if (!metadata.isDirectory()) {
|
|
28
|
-
await chmod(path, readOnly ? 0o444 : 0o644)
|
|
29
|
-
return
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (!readOnly) await chmod(path, 0o755)
|
|
33
|
-
for (const entry of await readdir(path)) {
|
|
34
|
-
await setReadOnly(join(path, entry), readOnly)
|
|
35
|
-
}
|
|
36
|
-
if (readOnly) await chmod(path, 0o555)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
describe("observational commands", () => {
|
|
40
|
-
let root: string
|
|
41
|
-
|
|
42
|
-
beforeEach(async () => {
|
|
43
|
-
root = await createTempDir()
|
|
44
|
-
await write(root, "agency.json", '{"version":2}\n')
|
|
45
|
-
await mkdir(join(root, "repos/agency"), { recursive: true })
|
|
46
|
-
await write(
|
|
47
|
-
root,
|
|
48
|
-
"epics/example/EPIC.md",
|
|
49
|
-
`---
|
|
50
|
-
ticketUrl: https://example.com/epics/example
|
|
51
|
-
repos:
|
|
52
|
-
- repo: agency
|
|
53
|
-
ref: main
|
|
54
|
-
tasks:
|
|
55
|
-
- id: example-task
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
# Example
|
|
59
|
-
`,
|
|
60
|
-
)
|
|
61
|
-
await write(
|
|
62
|
-
root,
|
|
63
|
-
"tasks/example-task/TASK.md",
|
|
64
|
-
`---
|
|
65
|
-
ticketUrl: null
|
|
66
|
-
epic: example
|
|
67
|
-
phases:
|
|
68
|
-
- id: implementation
|
|
69
|
-
---
|
|
70
|
-
|
|
71
|
-
# Example task
|
|
72
|
-
`,
|
|
73
|
-
)
|
|
74
|
-
await write(
|
|
75
|
-
root,
|
|
76
|
-
"tasks/example-task/phases/implementation/PHASE.md",
|
|
77
|
-
`---
|
|
78
|
-
repo: agency
|
|
79
|
-
branch: feat/example
|
|
80
|
-
base: main
|
|
81
|
-
pr: null
|
|
82
|
-
status: open
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
# Implementation
|
|
86
|
-
`,
|
|
87
|
-
)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
afterEach(async () => {
|
|
91
|
-
await setReadOnly(root, false)
|
|
92
|
-
await cleanupTempDir(root)
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
test("remain usable when the workbase is read-only", async () => {
|
|
96
|
-
await setReadOnly(root, true)
|
|
97
|
-
|
|
98
|
-
await runTestEffect(
|
|
99
|
-
WorkbaseService.pipe(Effect.flatMap((service) => service.discover(root))),
|
|
100
|
-
)
|
|
101
|
-
await runTestEffect(
|
|
102
|
-
integration({ subcommand: "status", cwd: root, silent: true }),
|
|
103
|
-
)
|
|
104
|
-
await runTestEffect(
|
|
105
|
-
epic({ subcommand: "list", args: [], cwd: root, silent: true }),
|
|
106
|
-
)
|
|
107
|
-
await runTestEffect(
|
|
108
|
-
epic({ subcommand: "show", args: ["example"], cwd: root, silent: true }),
|
|
109
|
-
)
|
|
110
|
-
await runTestEffect(
|
|
111
|
-
task({ subcommand: "list", args: [], cwd: root, silent: true }),
|
|
112
|
-
)
|
|
113
|
-
await runTestEffect(
|
|
114
|
-
task({
|
|
115
|
-
subcommand: "show",
|
|
116
|
-
args: ["example-task"],
|
|
117
|
-
cwd: root,
|
|
118
|
-
silent: true,
|
|
119
|
-
}),
|
|
120
|
-
)
|
|
121
|
-
await runTestEffect(
|
|
122
|
-
phase({
|
|
123
|
-
subcommand: "list",
|
|
124
|
-
args: ["example-task"],
|
|
125
|
-
cwd: root,
|
|
126
|
-
silent: true,
|
|
127
|
-
}),
|
|
128
|
-
)
|
|
129
|
-
await runTestEffect(
|
|
130
|
-
phase({
|
|
131
|
-
subcommand: "show",
|
|
132
|
-
args: ["example-task", "implementation"],
|
|
133
|
-
cwd: root,
|
|
134
|
-
silent: true,
|
|
135
|
-
}),
|
|
136
|
-
)
|
|
137
|
-
await runTestEffect(
|
|
138
|
-
repo({ subcommand: "list", args: [], cwd: root, silent: true }),
|
|
139
|
-
)
|
|
140
|
-
await runTestEffect(status({ cwd: root, silent: true }))
|
|
141
|
-
await runTestEffect(validate({ path: root, silent: true }))
|
|
142
|
-
await runTestEffect(
|
|
143
|
-
context({
|
|
144
|
-
target: "tasks/example-task/phases/implementation",
|
|
145
|
-
cwd: root,
|
|
146
|
-
silent: true,
|
|
147
|
-
}),
|
|
148
|
-
)
|
|
149
|
-
await runTestEffect(graph({ cwd: root, silent: true }))
|
|
150
|
-
await runTestEffect(next({ cwd: root, silent: true }))
|
|
151
|
-
await runTestEffect(doctor({ cwd: root, silent: true }))
|
|
152
|
-
|
|
153
|
-
expect(await Bun.file(join(root, "AGENTS.md")).exists()).toBe(false)
|
|
154
|
-
expect(
|
|
155
|
-
await Bun.file(join(root, ".opencode/opencode.jsonc")).exists(),
|
|
156
|
-
).toBe(false)
|
|
157
|
-
expect(
|
|
158
|
-
await Bun.file(join(root, ".opencode/command/agency.md")).exists(),
|
|
159
|
-
).toBe(false)
|
|
160
|
-
})
|
|
161
|
-
})
|