@markjaquith/agency 3.2.14 → 3.3.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 +20 -3
- package/cli-main.ts +1 -0
- package/package.json +7 -2
- package/src/cli-parser.ts +13 -0
- package/src/commands/work.ts +70 -2
- package/src/services/ReadinessService.ts +20 -0
- package/src/workbase/execution-contract.ts +10 -1
- 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 -711
- 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,103 +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 { cleanupTempDir, createTempDir, runTestEffect } from "../test-utils"
|
|
5
|
-
import { epic } from "./epic"
|
|
6
|
-
import { phase } from "./phase"
|
|
7
|
-
import { task } from "./task"
|
|
8
|
-
|
|
9
|
-
describe("create command descriptions", () => {
|
|
10
|
-
let root: string
|
|
11
|
-
|
|
12
|
-
beforeEach(async () => {
|
|
13
|
-
root = await createTempDir()
|
|
14
|
-
await Bun.write(join(root, "agency.json"), '{"version":2}\n')
|
|
15
|
-
await mkdir(join(root, "repos/agency"), { recursive: true })
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
afterEach(async () => cleanupTempDir(root))
|
|
19
|
-
|
|
20
|
-
test("persists and exposes descriptions for every body-of-work shape", async () => {
|
|
21
|
-
await runTestEffect(
|
|
22
|
-
epic({
|
|
23
|
-
subcommand: "create",
|
|
24
|
-
args: ["rollout"],
|
|
25
|
-
ticketUrl: "https://example.com/epic",
|
|
26
|
-
description: "Coordinate the rollout.",
|
|
27
|
-
repos: ["agency:main"],
|
|
28
|
-
cwd: root,
|
|
29
|
-
silent: true,
|
|
30
|
-
}),
|
|
31
|
-
)
|
|
32
|
-
await runTestEffect(
|
|
33
|
-
task({
|
|
34
|
-
subcommand: "create",
|
|
35
|
-
args: ["delivery"],
|
|
36
|
-
ticketUrl: "https://example.com/task",
|
|
37
|
-
description: "Deliver the rollout.",
|
|
38
|
-
multiPhase: true,
|
|
39
|
-
cwd: root,
|
|
40
|
-
silent: true,
|
|
41
|
-
}),
|
|
42
|
-
)
|
|
43
|
-
await runTestEffect(
|
|
44
|
-
phase({
|
|
45
|
-
subcommand: "create",
|
|
46
|
-
args: ["delivery", "contract"],
|
|
47
|
-
description: "Introduce the service contract.",
|
|
48
|
-
repo: "agency",
|
|
49
|
-
branch: "task/contract",
|
|
50
|
-
base: "main",
|
|
51
|
-
cwd: root,
|
|
52
|
-
silent: true,
|
|
53
|
-
}),
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
expect(
|
|
57
|
-
await Bun.file(join(root, "epics/rollout/EPIC.md")).text(),
|
|
58
|
-
).toContain("description: Coordinate the rollout.")
|
|
59
|
-
expect(
|
|
60
|
-
await Bun.file(join(root, "tasks/delivery/TASK.md")).text(),
|
|
61
|
-
).toContain("description: Deliver the rollout.")
|
|
62
|
-
expect(
|
|
63
|
-
await Bun.file(
|
|
64
|
-
join(root, "tasks/delivery/phases/contract/PHASE.md"),
|
|
65
|
-
).text(),
|
|
66
|
-
).toContain("description: Introduce the service contract.")
|
|
67
|
-
|
|
68
|
-
const output: string[] = []
|
|
69
|
-
const originalLog = console.log
|
|
70
|
-
console.log = (message?: unknown) => output.push(String(message))
|
|
71
|
-
try {
|
|
72
|
-
await runTestEffect(
|
|
73
|
-
epic({ subcommand: "list", args: [], cwd: root, json: true }),
|
|
74
|
-
)
|
|
75
|
-
await runTestEffect(
|
|
76
|
-
task({
|
|
77
|
-
subcommand: "show",
|
|
78
|
-
args: ["delivery"],
|
|
79
|
-
cwd: root,
|
|
80
|
-
json: true,
|
|
81
|
-
}),
|
|
82
|
-
)
|
|
83
|
-
await runTestEffect(
|
|
84
|
-
phase({
|
|
85
|
-
subcommand: "show",
|
|
86
|
-
args: ["delivery", "contract"],
|
|
87
|
-
cwd: root,
|
|
88
|
-
json: true,
|
|
89
|
-
}),
|
|
90
|
-
)
|
|
91
|
-
} finally {
|
|
92
|
-
console.log = originalLog
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
expect(JSON.parse(output[0]!)[0].data.description).toBe(
|
|
96
|
-
"Coordinate the rollout.",
|
|
97
|
-
)
|
|
98
|
-
expect(JSON.parse(output[1]!).data.description).toBe("Deliver the rollout.")
|
|
99
|
-
expect(JSON.parse(output[2]!).data.description).toBe(
|
|
100
|
-
"Introduce the service contract.",
|
|
101
|
-
)
|
|
102
|
-
})
|
|
103
|
-
})
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
|
-
import { createHash } from "node:crypto"
|
|
3
|
-
import { chmod, mkdir } from "node:fs/promises"
|
|
4
|
-
import { dirname, join } from "node:path"
|
|
5
|
-
import {
|
|
6
|
-
captureLogs,
|
|
7
|
-
cleanupTempDir,
|
|
8
|
-
createTempDir,
|
|
9
|
-
runTestEffect,
|
|
10
|
-
} from "../test-utils"
|
|
11
|
-
import { doctor } from "./doctor"
|
|
12
|
-
|
|
13
|
-
const write = async (root: string, path: string, content: string) => {
|
|
14
|
-
const fullPath = join(root, path)
|
|
15
|
-
await mkdir(dirname(fullPath), { recursive: true })
|
|
16
|
-
await Bun.write(fullPath, content)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const managedAgents = (body: string) =>
|
|
20
|
-
`<!-- agency-managed: sha256=${createHash("sha256").update(body).digest("hex")} -->\n\n${body}`
|
|
21
|
-
|
|
22
|
-
describe("doctor command", () => {
|
|
23
|
-
let root: string
|
|
24
|
-
let repository: string
|
|
25
|
-
|
|
26
|
-
beforeEach(async () => {
|
|
27
|
-
root = await createTempDir()
|
|
28
|
-
repository = join(root, "source")
|
|
29
|
-
await mkdir(repository)
|
|
30
|
-
await Bun.$`git init -q -b main ${repository}`
|
|
31
|
-
await Bun.$`git -C ${repository} config user.email test@example.com`
|
|
32
|
-
await Bun.$`git -C ${repository} config user.name Test`
|
|
33
|
-
await write(repository, "README.md", "test\n")
|
|
34
|
-
await Bun.$`git -C ${repository} add README.md`
|
|
35
|
-
await Bun.$`git -C ${repository} commit -q -m initial`
|
|
36
|
-
await Bun.$`git -C ${repository} remote add origin https://example.com/agency.git`
|
|
37
|
-
await write(
|
|
38
|
-
root,
|
|
39
|
-
"agency.json",
|
|
40
|
-
JSON.stringify({
|
|
41
|
-
version: 2,
|
|
42
|
-
agents: {
|
|
43
|
-
missing: {
|
|
44
|
-
command: ["definitely-not-installed"],
|
|
45
|
-
autoCommand: ["also-not-installed", "{prompt}"],
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
}),
|
|
49
|
-
)
|
|
50
|
-
await mkdir(join(root, "repos"), { recursive: true })
|
|
51
|
-
await Bun.$`ln -s ${repository} ${join(root, "repos/agency")}`
|
|
52
|
-
await write(
|
|
53
|
-
root,
|
|
54
|
-
"tasks/example/TASK.md",
|
|
55
|
-
`---
|
|
56
|
-
ticketUrl: null
|
|
57
|
-
repo: agency
|
|
58
|
-
branch: feat/example
|
|
59
|
-
base: main
|
|
60
|
-
pr: null
|
|
61
|
-
status: open
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
# Example
|
|
65
|
-
`,
|
|
66
|
-
)
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
afterEach(async () => cleanupTempDir(root))
|
|
70
|
-
|
|
71
|
-
test("returns stable checks, severities, and remediation in JSON", async () => {
|
|
72
|
-
const logs = await captureLogs(() =>
|
|
73
|
-
runTestEffect(doctor({ cwd: root, json: true })),
|
|
74
|
-
)
|
|
75
|
-
const report = JSON.parse(logs[0]!)
|
|
76
|
-
|
|
77
|
-
expect(report).toMatchObject({
|
|
78
|
-
version: 1,
|
|
79
|
-
root,
|
|
80
|
-
healthy: false,
|
|
81
|
-
})
|
|
82
|
-
expect(report.checks).toEqual(
|
|
83
|
-
expect.arrayContaining([
|
|
84
|
-
expect.objectContaining({
|
|
85
|
-
id: "tool.git",
|
|
86
|
-
level: "error",
|
|
87
|
-
status: "pass",
|
|
88
|
-
}),
|
|
89
|
-
expect.objectContaining({
|
|
90
|
-
id: "integration.agent.missing",
|
|
91
|
-
level: "error",
|
|
92
|
-
status: "fail",
|
|
93
|
-
}),
|
|
94
|
-
expect.objectContaining({
|
|
95
|
-
id: "integration.agent.missing.auto",
|
|
96
|
-
level: "error",
|
|
97
|
-
status: "fail",
|
|
98
|
-
}),
|
|
99
|
-
expect.objectContaining({
|
|
100
|
-
id: "ref.agency.main",
|
|
101
|
-
status: "pass",
|
|
102
|
-
}),
|
|
103
|
-
]),
|
|
104
|
-
)
|
|
105
|
-
for (const check of report.checks) {
|
|
106
|
-
if (check.status === "fail") expect(check.remediation).toBeTruthy()
|
|
107
|
-
}
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
test("reports repository, ref, remote, and managed-file failures", async () => {
|
|
111
|
-
await Bun.$`git -C ${repository} remote remove origin`
|
|
112
|
-
await Bun.$`git -C ${repository} branch -m other`
|
|
113
|
-
await write(root, ".agency/AGENTS.md", managedAgents("old\n"))
|
|
114
|
-
|
|
115
|
-
const logs = await captureLogs(() =>
|
|
116
|
-
runTestEffect(doctor({ cwd: root, json: true })),
|
|
117
|
-
)
|
|
118
|
-
const checks = JSON.parse(logs[0]!).checks
|
|
119
|
-
|
|
120
|
-
expect(checks).toEqual(
|
|
121
|
-
expect.arrayContaining([
|
|
122
|
-
expect.objectContaining({
|
|
123
|
-
id: "repository.agency.remote",
|
|
124
|
-
status: "fail",
|
|
125
|
-
}),
|
|
126
|
-
expect.objectContaining({
|
|
127
|
-
id: "ref.agency.main",
|
|
128
|
-
status: "fail",
|
|
129
|
-
}),
|
|
130
|
-
expect.objectContaining({
|
|
131
|
-
id: "integration.file.agents",
|
|
132
|
-
status: "fail",
|
|
133
|
-
}),
|
|
134
|
-
]),
|
|
135
|
-
)
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
test("warns when customized OpenCode config cannot guarantee access", async () => {
|
|
139
|
-
const custom = '{"references":{}}\n'
|
|
140
|
-
await write(root, ".opencode/opencode.jsonc", custom)
|
|
141
|
-
|
|
142
|
-
const logs = await captureLogs(() =>
|
|
143
|
-
runTestEffect(doctor({ cwd: root, json: true })),
|
|
144
|
-
)
|
|
145
|
-
const check = JSON.parse(logs[0]!).checks.find(
|
|
146
|
-
(value: { id: string }) => value.id === "integration.file.opencode",
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
expect(check).toMatchObject({
|
|
150
|
-
level: "warning",
|
|
151
|
-
status: "fail",
|
|
152
|
-
message: expect.stringContaining("cannot guarantee its instructions"),
|
|
153
|
-
remediation: expect.stringContaining("global config"),
|
|
154
|
-
})
|
|
155
|
-
expect(await Bun.file(join(root, ".opencode/opencode.jsonc")).text()).toBe(
|
|
156
|
-
custom,
|
|
157
|
-
)
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
test("is safe when the workbase is read-only", async () => {
|
|
161
|
-
for (const path of [
|
|
162
|
-
join(root, "agency.json"),
|
|
163
|
-
join(root, "tasks/example/TASK.md"),
|
|
164
|
-
]) {
|
|
165
|
-
await chmod(path, 0o444)
|
|
166
|
-
}
|
|
167
|
-
await chmod(join(root, "tasks/example"), 0o555)
|
|
168
|
-
await chmod(join(root, "tasks"), 0o555)
|
|
169
|
-
await chmod(root, 0o555)
|
|
170
|
-
|
|
171
|
-
try {
|
|
172
|
-
await runTestEffect(doctor({ cwd: root, silent: true }))
|
|
173
|
-
} finally {
|
|
174
|
-
await chmod(root, 0o755)
|
|
175
|
-
await chmod(join(root, "tasks"), 0o755)
|
|
176
|
-
await chmod(join(root, "tasks/example"), 0o755)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
expect(await Bun.file(join(root, "AGENTS.md")).exists()).toBe(false)
|
|
180
|
-
expect(await Bun.file(join(root, ".agency/AGENTS.md")).exists()).toBe(false)
|
|
181
|
-
expect(
|
|
182
|
-
await Bun.file(join(root, ".opencode/opencode.jsonc")).exists(),
|
|
183
|
-
).toBe(false)
|
|
184
|
-
})
|
|
185
|
-
})
|
|
@@ -1,196 +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 { Effect } from "effect"
|
|
5
|
-
import {
|
|
6
|
-
captureLogs,
|
|
7
|
-
cleanupTempDir,
|
|
8
|
-
createTempDir,
|
|
9
|
-
runTestEffect,
|
|
10
|
-
} from "../test-utils"
|
|
11
|
-
import { epic } from "./epic"
|
|
12
|
-
|
|
13
|
-
describe("epic command", () => {
|
|
14
|
-
let root: string
|
|
15
|
-
|
|
16
|
-
beforeEach(async () => {
|
|
17
|
-
root = await createTempDir()
|
|
18
|
-
await Bun.write(join(root, "agency.json"), '{"version":2}\n')
|
|
19
|
-
await mkdir(join(root, "repos/agency"), { recursive: true })
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
afterEach(async () => cleanupTempDir(root))
|
|
23
|
-
|
|
24
|
-
test("requires create arguments", async () => {
|
|
25
|
-
await expect(
|
|
26
|
-
runTestEffect(epic({ subcommand: "create", args: [], silent: true })),
|
|
27
|
-
).rejects.toThrow("Usage: agency epic create")
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
test("outputs the created epic as JSON", async () => {
|
|
31
|
-
const logs = await captureLogs(() =>
|
|
32
|
-
runTestEffect(
|
|
33
|
-
epic({
|
|
34
|
-
subcommand: "create",
|
|
35
|
-
args: ["example"],
|
|
36
|
-
ticketUrl: "https://example.com/epic",
|
|
37
|
-
repos: ["agency:main"],
|
|
38
|
-
cwd: root,
|
|
39
|
-
json: true,
|
|
40
|
-
}),
|
|
41
|
-
),
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
const created = JSON.parse(logs[0]!)
|
|
45
|
-
expect(created.revision).toMatch(/^[a-f0-9]{64}$/)
|
|
46
|
-
expect(created).toEqual({
|
|
47
|
-
id: "example",
|
|
48
|
-
path: join(root, "epics/example/EPIC.md"),
|
|
49
|
-
revision: created.revision,
|
|
50
|
-
data: {
|
|
51
|
-
ticketUrl: "https://example.com/epic",
|
|
52
|
-
repos: [{ repo: "agency", ref: "main" }],
|
|
53
|
-
tasks: [],
|
|
54
|
-
},
|
|
55
|
-
})
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
test("lists and shows stable epic JSON records", async () => {
|
|
59
|
-
await runTestEffect(
|
|
60
|
-
epic({
|
|
61
|
-
subcommand: "create",
|
|
62
|
-
args: ["example"],
|
|
63
|
-
ticketUrl: "https://example.com/epic",
|
|
64
|
-
repos: ["agency:main"],
|
|
65
|
-
cwd: root,
|
|
66
|
-
silent: true,
|
|
67
|
-
}),
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
const listLogs = await captureLogs(() =>
|
|
71
|
-
runTestEffect(
|
|
72
|
-
epic({ subcommand: "list", args: [], cwd: root, json: true }),
|
|
73
|
-
),
|
|
74
|
-
)
|
|
75
|
-
const listed = JSON.parse(listLogs[0]!)
|
|
76
|
-
expect(listed[0].revision).toMatch(/^[a-f0-9]{64}$/)
|
|
77
|
-
expect(listed).toEqual([
|
|
78
|
-
{
|
|
79
|
-
id: "example",
|
|
80
|
-
path: join(root, "epics/example/EPIC.md"),
|
|
81
|
-
revision: listed[0].revision,
|
|
82
|
-
data: {
|
|
83
|
-
ticketUrl: "https://example.com/epic",
|
|
84
|
-
repos: [{ repo: "agency", ref: "main" }],
|
|
85
|
-
tasks: [],
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
])
|
|
89
|
-
|
|
90
|
-
const showLogs = await captureLogs(() =>
|
|
91
|
-
runTestEffect(
|
|
92
|
-
epic({
|
|
93
|
-
subcommand: "show",
|
|
94
|
-
args: ["example"],
|
|
95
|
-
cwd: root,
|
|
96
|
-
json: true,
|
|
97
|
-
}),
|
|
98
|
-
),
|
|
99
|
-
)
|
|
100
|
-
const shown = JSON.parse(showLogs[0]!)
|
|
101
|
-
expect(shown.revision).toBe(listed[0].revision)
|
|
102
|
-
expect(shown).toEqual({
|
|
103
|
-
id: "example",
|
|
104
|
-
path: join(root, "epics/example/EPIC.md"),
|
|
105
|
-
revision: shown.revision,
|
|
106
|
-
data: {
|
|
107
|
-
ticketUrl: "https://example.com/epic",
|
|
108
|
-
repos: [{ repo: "agency", ref: "main" }],
|
|
109
|
-
tasks: [],
|
|
110
|
-
},
|
|
111
|
-
})
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
test("reads each epic document once when listing JSON", async () => {
|
|
115
|
-
await runTestEffect(
|
|
116
|
-
epic({
|
|
117
|
-
subcommand: "create",
|
|
118
|
-
args: ["example"],
|
|
119
|
-
ticketUrl: "https://example.com/epic",
|
|
120
|
-
repos: ["agency:main"],
|
|
121
|
-
cwd: root,
|
|
122
|
-
silent: true,
|
|
123
|
-
}),
|
|
124
|
-
)
|
|
125
|
-
const original = Bun.file
|
|
126
|
-
let reads = 0
|
|
127
|
-
Bun.file = ((path: Parameters<typeof Bun.file>[0], ...args: never[]) => {
|
|
128
|
-
if (String(path).endsWith("/epics/example/EPIC.md")) reads += 1
|
|
129
|
-
return original(path as unknown as string, ...args)
|
|
130
|
-
}) as typeof Bun.file
|
|
131
|
-
try {
|
|
132
|
-
await captureLogs(() =>
|
|
133
|
-
runTestEffect(
|
|
134
|
-
epic({ subcommand: "list", args: [], cwd: root, json: true }),
|
|
135
|
-
),
|
|
136
|
-
)
|
|
137
|
-
} finally {
|
|
138
|
-
Bun.file = original
|
|
139
|
-
}
|
|
140
|
-
expect(reads).toBe(2)
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
test("renders a readable operational table", async () => {
|
|
144
|
-
await runTestEffect(
|
|
145
|
-
epic({
|
|
146
|
-
subcommand: "create",
|
|
147
|
-
args: ["example"],
|
|
148
|
-
ticketUrl: "https://example.com/epic",
|
|
149
|
-
repos: ["agency:main"],
|
|
150
|
-
cwd: root,
|
|
151
|
-
silent: true,
|
|
152
|
-
}),
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
const logs = await captureLogs(() =>
|
|
156
|
-
runTestEffect(epic({ subcommand: "list", args: [], cwd: root })),
|
|
157
|
-
)
|
|
158
|
-
expect(logs[0]).toContain(
|
|
159
|
-
"EPIC STATUS READINESS REPOSITORIES PR WORKTREE",
|
|
160
|
-
)
|
|
161
|
-
expect(logs[0]).toContain("example open waiting agency")
|
|
162
|
-
expect(logs[0]).not.toMatch(/[\u{e000}-\u{f8ff}]/u)
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
test("starts work on a newly created epic", async () => {
|
|
166
|
-
const launches: unknown[] = []
|
|
167
|
-
|
|
168
|
-
await runTestEffect(
|
|
169
|
-
epic(
|
|
170
|
-
{
|
|
171
|
-
subcommand: "new",
|
|
172
|
-
args: ["immediate"],
|
|
173
|
-
ticketUrl: "https://example.com/immediate",
|
|
174
|
-
repos: ["agency:main"],
|
|
175
|
-
work: true,
|
|
176
|
-
auto: true,
|
|
177
|
-
cwd: root,
|
|
178
|
-
silent: true,
|
|
179
|
-
},
|
|
180
|
-
(options) =>
|
|
181
|
-
Effect.sync(() => {
|
|
182
|
-
launches.push(options)
|
|
183
|
-
return undefined
|
|
184
|
-
}),
|
|
185
|
-
),
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
expect(launches).toEqual([
|
|
189
|
-
expect.objectContaining({
|
|
190
|
-
epicId: "immediate",
|
|
191
|
-
auto: true,
|
|
192
|
-
cwd: root,
|
|
193
|
-
}),
|
|
194
|
-
])
|
|
195
|
-
})
|
|
196
|
-
})
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
|
-
import { stat } 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 { init } from "./init"
|
|
11
|
-
|
|
12
|
-
describe("init command", () => {
|
|
13
|
-
let parent: string
|
|
14
|
-
|
|
15
|
-
beforeEach(async () => {
|
|
16
|
-
parent = await createTempDir()
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
afterEach(async () => {
|
|
20
|
-
await cleanupTempDir(parent)
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
test("creates a workbase and required directories", async () => {
|
|
24
|
-
const root = join(parent, "workbase")
|
|
25
|
-
await runTestEffect(init({ path: root, silent: true }))
|
|
26
|
-
|
|
27
|
-
expect(await Bun.file(join(root, "agency.json")).json()).toEqual({
|
|
28
|
-
version: 2,
|
|
29
|
-
vcs: "git",
|
|
30
|
-
})
|
|
31
|
-
for (const directory of ["repos", "epics", "tasks"]) {
|
|
32
|
-
expect((await stat(join(root, directory))).isDirectory()).toBe(true)
|
|
33
|
-
}
|
|
34
|
-
expect(await Bun.file(join(root, ".gitignore")).text()).toBe(
|
|
35
|
-
"/repos/\n/tasks/*/code/\n/tasks/*/phases/*/code/\n",
|
|
36
|
-
)
|
|
37
|
-
expect(await Bun.file(join(root, "AGENTS.md")).exists()).toBe(false)
|
|
38
|
-
const agents = await Bun.file(join(root, ".agency/AGENTS.md")).text()
|
|
39
|
-
expect(agents).toContain("agency context . --json")
|
|
40
|
-
expect(agents).toContain("## Consent Boundaries")
|
|
41
|
-
expect(agents).toContain("## Execution")
|
|
42
|
-
expect(agents).not.toContain("SKILL.md")
|
|
43
|
-
const opencode = await Bun.file(
|
|
44
|
-
join(root, ".opencode/opencode.jsonc"),
|
|
45
|
-
).text()
|
|
46
|
-
const config = JSON.parse(opencode.slice(opencode.indexOf("\n\n") + 2))
|
|
47
|
-
expect(config.instructions).toEqual([".agency/AGENTS.md"])
|
|
48
|
-
expect(config.agent.agency).toBeUndefined()
|
|
49
|
-
expect(config.agent.plan).toEqual({ disable: true })
|
|
50
|
-
expect(config.agent["agency-plan"]).toMatchObject({
|
|
51
|
-
mode: "primary",
|
|
52
|
-
permission: {
|
|
53
|
-
bash: {
|
|
54
|
-
"agency *": "allow",
|
|
55
|
-
},
|
|
56
|
-
edit: {
|
|
57
|
-
"*": "deny",
|
|
58
|
-
"tasks/*/TASK.md": "allow",
|
|
59
|
-
"tasks/*/phases/*/PHASE.md": "allow",
|
|
60
|
-
"epics/*/EPIC.md": "allow",
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
})
|
|
64
|
-
expect(config.references).toEqual({
|
|
65
|
-
workbase: expect.objectContaining({ path: ".." }),
|
|
66
|
-
})
|
|
67
|
-
expect(config.permission).toBeUndefined()
|
|
68
|
-
expect(
|
|
69
|
-
await Bun.file(join(root, ".opencode/command/agency.md")).exists(),
|
|
70
|
-
).toBe(false)
|
|
71
|
-
const plugin = await Bun.file(
|
|
72
|
-
join(root, ".opencode/plugins/agency-repository-skills.ts"),
|
|
73
|
-
).text()
|
|
74
|
-
expect(plugin).toContain("AGENCY_WRITABLE_CHECKOUT")
|
|
75
|
-
expect(plugin).toContain("config.skills.paths")
|
|
76
|
-
const tui = await Bun.file(join(root, ".opencode/tui.jsonc")).text()
|
|
77
|
-
expect(JSON.parse(tui.slice(tui.indexOf("\n\n") + 2))).toEqual({
|
|
78
|
-
$schema: "https://opencode.ai/tui.json",
|
|
79
|
-
plugin: ["./tui/agency-debug.ts"],
|
|
80
|
-
})
|
|
81
|
-
const tuiPlugin = await Bun.file(
|
|
82
|
-
join(root, ".opencode/tui/agency-debug.ts"),
|
|
83
|
-
).text()
|
|
84
|
-
expect(tuiPlugin).toContain('slashName: "agency-debug"')
|
|
85
|
-
expect(tuiPlugin).toContain("api.ui.toast")
|
|
86
|
-
expect(tuiPlugin).not.toContain("chat.message")
|
|
87
|
-
expect(
|
|
88
|
-
await Bun.file(join(root, ".pi/extensions/agency-workbase.ts")).exists(),
|
|
89
|
-
).toBe(false)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
test("preserves existing gitignore entries", async () => {
|
|
93
|
-
await Bun.write(join(parent, ".gitignore"), "custom\n")
|
|
94
|
-
await runTestEffect(init({ path: parent, silent: true }))
|
|
95
|
-
|
|
96
|
-
expect(await Bun.file(join(parent, ".gitignore")).text()).toStartWith(
|
|
97
|
-
"custom\n",
|
|
98
|
-
)
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
test("outputs the initialized workbase as JSON", async () => {
|
|
102
|
-
const root = join(parent, "json-workbase")
|
|
103
|
-
const logs = await captureLogs(() =>
|
|
104
|
-
runTestEffect(init({ path: root, json: true })),
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
expect(JSON.parse(logs[0]!)).toEqual({ root })
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
test("rejects an existing Agency configuration", async () => {
|
|
111
|
-
await Bun.write(join(parent, "agency.json"), '{"version":2}\n')
|
|
112
|
-
|
|
113
|
-
await expect(
|
|
114
|
-
runTestEffect(init({ path: parent, silent: true })),
|
|
115
|
-
).rejects.toThrow("already exists")
|
|
116
|
-
})
|
|
117
|
-
})
|