@markjaquith/agency 1.11.0 → 2.0.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 +302 -113
- package/cli.ts +123 -415
- package/index.ts +1 -1
- package/package.json +12 -17
- package/skills/agency/SKILL.md +258 -0
- package/src/cli.test.ts +290 -0
- package/src/commands/description.test.ts +103 -0
- package/src/commands/epic.test.ts +103 -0
- package/src/commands/epic.ts +101 -0
- package/src/commands/init.test.ts +42 -290
- package/src/commands/init.ts +15 -123
- package/src/commands/phase.ts +120 -0
- package/src/commands/pr.test.ts +19 -170
- package/src/commands/pr.ts +23 -179
- package/src/commands/repo.test.ts +80 -0
- package/src/commands/repo.ts +83 -0
- package/src/commands/status.test.ts +35 -477
- package/src/commands/status.ts +24 -213
- package/src/commands/task-phase.test.ts +206 -0
- package/src/commands/task.ts +83 -1285
- package/src/commands/validate.test.ts +91 -0
- package/src/commands/validate.ts +50 -0
- package/src/commands/work.test.ts +172 -419
- package/src/commands/work.ts +47 -166
- package/src/services/EpicService.test.ts +71 -0
- package/src/services/EpicService.ts +135 -0
- package/src/services/FileSystemService.ts +61 -92
- package/src/services/PhaseService.ts +323 -0
- package/src/services/PullRequestService.test.ts +386 -0
- package/src/services/PullRequestService.ts +143 -0
- package/src/services/RepositoryService.test.ts +106 -0
- package/src/services/RepositoryService.ts +159 -0
- package/src/services/TaskPhaseService.test.ts +217 -0
- package/src/services/TaskService.ts +192 -0
- package/src/services/WorkbaseService.test.ts +446 -0
- package/src/services/WorkbaseService.ts +493 -0
- package/src/services/WorktreeService.test.ts +599 -0
- package/src/services/WorktreeService.ts +357 -0
- package/src/test-utils.ts +41 -438
- package/src/types.ts +1 -215
- package/src/utils/command.ts +1 -5
- package/src/utils/effect.test.ts +24 -0
- package/src/utils/effect.ts +3 -273
- package/src/utils/process.test.ts +0 -15
- package/src/utils/process.ts +1 -60
- package/src/workbase/frontmatter.test.ts +50 -0
- package/src/workbase/frontmatter.ts +77 -0
- package/src/workbase/repository-reference.test.ts +25 -0
- package/src/workbase/repository-reference.ts +18 -0
- package/src/workbase/schemas.test.ts +146 -0
- package/src/workbase/schemas.ts +78 -0
- package/src/workbase/worktree-command.test.ts +61 -0
- package/src/workbase/worktree-command.ts +52 -0
- package/src/commands/base.test.ts +0 -198
- package/src/commands/base.ts +0 -198
- package/src/commands/clean.test.ts +0 -299
- package/src/commands/clean.ts +0 -274
- package/src/commands/completions.test.ts +0 -51
- package/src/commands/completions.ts +0 -308
- package/src/commands/emit.integration.test.ts +0 -549
- package/src/commands/emit.test.ts +0 -576
- package/src/commands/emit.ts +0 -700
- package/src/commands/emitted.test.ts +0 -226
- package/src/commands/emitted.ts +0 -57
- package/src/commands/loop.test.ts +0 -621
- package/src/commands/loop.ts +0 -406
- package/src/commands/merge.integration.test.ts +0 -195
- package/src/commands/merge.test.ts +0 -246
- package/src/commands/merge.ts +0 -244
- package/src/commands/pull.test.ts +0 -395
- package/src/commands/pull.ts +0 -205
- package/src/commands/push.test.ts +0 -518
- package/src/commands/push.ts +0 -434
- package/src/commands/rebase.test.ts +0 -521
- package/src/commands/rebase.ts +0 -232
- package/src/commands/save.test.ts +0 -247
- package/src/commands/save.ts +0 -162
- package/src/commands/source.test.ts +0 -195
- package/src/commands/source.ts +0 -72
- package/src/commands/switch.test.ts +0 -194
- package/src/commands/switch.ts +0 -84
- package/src/commands/task-branching.test.ts +0 -633
- package/src/commands/task-continue.test.ts +0 -311
- package/src/commands/task-edit.test.ts +0 -235
- package/src/commands/task-main.test.ts +0 -1066
- package/src/commands/task-squash.test.ts +0 -266
- package/src/commands/tasks.test.ts +0 -391
- package/src/commands/tasks.ts +0 -161
- package/src/commands/template-delete.test.ts +0 -178
- package/src/commands/template-delete.ts +0 -98
- package/src/commands/template-list.test.ts +0 -135
- package/src/commands/template-list.ts +0 -87
- package/src/commands/template-view.test.ts +0 -158
- package/src/commands/template-view.ts +0 -86
- package/src/commands/template.test.ts +0 -32
- package/src/commands/template.ts +0 -96
- package/src/commands/use.test.ts +0 -87
- package/src/commands/use.ts +0 -97
- package/src/constants.ts +0 -10
- package/src/errors.ts +0 -17
- package/src/schemas.ts +0 -33
- package/src/services/AgencyMetadataService.ts +0 -311
- package/src/services/ClaudeService.test.ts +0 -184
- package/src/services/ClaudeService.ts +0 -91
- package/src/services/ConfigService.ts +0 -115
- package/src/services/FilterRepoService.ts +0 -164
- package/src/services/FormatterService.test.ts +0 -432
- package/src/services/FormatterService.ts +0 -219
- package/src/services/GitService.ts +0 -1183
- package/src/services/MockFilterRepoService.ts +0 -140
- package/src/services/OpencodeService.ts +0 -263
- package/src/services/PromptService.ts +0 -183
- package/src/services/TemplateService.ts +0 -81
- package/src/types/native-exec.d.ts +0 -8
- package/src/utils/colors.ts +0 -178
- package/src/utils/git-path.ts +0 -6
- package/src/utils/glob.test.ts +0 -161
- package/src/utils/glob.ts +0 -78
- package/src/utils/paths.ts +0 -51
- package/src/utils/pr-branch.test.ts +0 -376
- package/src/utils/pr-branch.ts +0 -508
- package/src/utils/spinner.test.ts +0 -45
- package/src/utils/spinner.ts +0 -96
- package/src/utils/task-parser.test.ts +0 -172
- package/src/utils/task-parser.ts +0 -93
- package/templates/AGENCY.md +0 -22
- package/templates/AGENTS.md +0 -11
- package/templates/CLAUDE.md +0 -3
- package/templates/TASK.md +0 -5
- package/templates/opencode.json +0 -4
|
@@ -0,0 +1,80 @@
|
|
|
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 { repo } from "./repo"
|
|
11
|
+
|
|
12
|
+
describe("repo 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
|
+
await mkdir(join(root, "repos/agency"), { recursive: true })
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
afterEach(async () => cleanupTempDir(root))
|
|
22
|
+
|
|
23
|
+
test("requires a subcommand", async () => {
|
|
24
|
+
await expect(
|
|
25
|
+
runTestEffect(repo({ args: [], silent: true })),
|
|
26
|
+
).rejects.toThrow("Available subcommands: add, link, list")
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test("requires add arguments", async () => {
|
|
30
|
+
await expect(
|
|
31
|
+
runTestEffect(
|
|
32
|
+
repo({ subcommand: "add", args: ["agency"], silent: true }),
|
|
33
|
+
),
|
|
34
|
+
).rejects.toThrow("Usage: agency repo add")
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test("lists repository metadata as JSON", async () => {
|
|
38
|
+
const logs = await captureLogs(() =>
|
|
39
|
+
runTestEffect(
|
|
40
|
+
repo({ subcommand: "list", args: [], cwd: root, json: true }),
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
expect(JSON.parse(logs[0]!)).toEqual([
|
|
45
|
+
{
|
|
46
|
+
alias: "agency",
|
|
47
|
+
path: join(root, "repos/agency"),
|
|
48
|
+
kind: "repository",
|
|
49
|
+
remote: null,
|
|
50
|
+
target: null,
|
|
51
|
+
},
|
|
52
|
+
])
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test("outputs a linked repository as JSON", async () => {
|
|
56
|
+
const target = join(root, "source")
|
|
57
|
+
await mkdir(target)
|
|
58
|
+
const git = Bun.spawn(["git", "init", target], {
|
|
59
|
+
stdout: "ignore",
|
|
60
|
+
stderr: "ignore",
|
|
61
|
+
})
|
|
62
|
+
expect(await git.exited).toBe(0)
|
|
63
|
+
|
|
64
|
+
const logs = await captureLogs(() =>
|
|
65
|
+
runTestEffect(
|
|
66
|
+
repo({
|
|
67
|
+
subcommand: "link",
|
|
68
|
+
args: ["linked", target],
|
|
69
|
+
cwd: root,
|
|
70
|
+
json: true,
|
|
71
|
+
}),
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
expect(JSON.parse(logs[0]!)).toEqual({
|
|
76
|
+
alias: "linked",
|
|
77
|
+
path: join(root, "repos/linked"),
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
})
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Effect } from "effect"
|
|
2
|
+
import type { BaseCommandOptions } from "../utils/command"
|
|
3
|
+
import { RepositoryService } from "../services/RepositoryService"
|
|
4
|
+
import { createLoggers } from "../utils/effect"
|
|
5
|
+
|
|
6
|
+
interface RepoOptions extends BaseCommandOptions {
|
|
7
|
+
readonly subcommand?: string
|
|
8
|
+
readonly args: readonly string[]
|
|
9
|
+
readonly json?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const repo = (options: RepoOptions) =>
|
|
13
|
+
Effect.gen(function* () {
|
|
14
|
+
const repositories = yield* RepositoryService
|
|
15
|
+
const { log } = createLoggers(options)
|
|
16
|
+
const cwd = options.cwd ?? process.cwd()
|
|
17
|
+
|
|
18
|
+
switch (options.subcommand) {
|
|
19
|
+
case "add": {
|
|
20
|
+
const [alias, remote] = options.args
|
|
21
|
+
if (!alias || !remote) {
|
|
22
|
+
return yield* Effect.fail(
|
|
23
|
+
new Error("Usage: agency repo add <alias> <remote>"),
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
const path = yield* repositories.add(alias, remote, cwd)
|
|
27
|
+
log(
|
|
28
|
+
options.json
|
|
29
|
+
? JSON.stringify({ alias, path }, null, 2)
|
|
30
|
+
: `Added repository '${alias}'`,
|
|
31
|
+
)
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
case "link": {
|
|
36
|
+
const [alias, path] = options.args
|
|
37
|
+
if (!alias || !path) {
|
|
38
|
+
return yield* Effect.fail(
|
|
39
|
+
new Error("Usage: agency repo link <alias> <path>"),
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
const destination = yield* repositories.link(alias, path, cwd)
|
|
43
|
+
log(
|
|
44
|
+
options.json
|
|
45
|
+
? JSON.stringify({ alias, path: destination }, null, 2)
|
|
46
|
+
: `Linked repository '${alias}'`,
|
|
47
|
+
)
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
case "list": {
|
|
52
|
+
const items = yield* repositories.list(cwd)
|
|
53
|
+
if (options.json) {
|
|
54
|
+
log(JSON.stringify(items, null, 2))
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
for (const item of items) {
|
|
58
|
+
const detail = item.target ?? item.remote ?? item.path
|
|
59
|
+
log(`${item.alias}\t${item.kind}\t${detail}`)
|
|
60
|
+
}
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
default:
|
|
65
|
+
return yield* Effect.fail(
|
|
66
|
+
new Error(
|
|
67
|
+
"Subcommand is required. Available subcommands: add, link, list",
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
export const help = `
|
|
74
|
+
Usage: agency repo <subcommand>
|
|
75
|
+
|
|
76
|
+
Subcommands:
|
|
77
|
+
add <alias> <remote> Create a bare clone
|
|
78
|
+
link <alias> <path> Link an existing Git repository
|
|
79
|
+
list List repository aliases
|
|
80
|
+
|
|
81
|
+
Options:
|
|
82
|
+
--json Output repository aliases as JSON
|
|
83
|
+
`
|
|
@@ -1,489 +1,47 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
|
+
import { mkdir } from "node:fs/promises"
|
|
3
|
+
import { join } from "node:path"
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
captureLogs,
|
|
6
6
|
cleanupTempDir,
|
|
7
|
-
|
|
8
|
-
getCurrentBranch,
|
|
9
|
-
createCommit,
|
|
10
|
-
initAgency,
|
|
7
|
+
createTempDir,
|
|
11
8
|
runTestEffect,
|
|
12
9
|
} from "../test-utils"
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
async function createBranch(cwd: string, branchName: string): Promise<void> {
|
|
16
|
-
await Bun.spawn(["git", "checkout", "-b", branchName], {
|
|
17
|
-
cwd,
|
|
18
|
-
stdout: "pipe",
|
|
19
|
-
stderr: "pipe",
|
|
20
|
-
}).exited
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function checkoutBranch(cwd: string, branchName: string): Promise<void> {
|
|
24
|
-
await Bun.spawn(["git", "checkout", branchName], {
|
|
25
|
-
cwd,
|
|
26
|
-
stdout: "pipe",
|
|
27
|
-
stderr: "pipe",
|
|
28
|
-
}).exited
|
|
29
|
-
}
|
|
10
|
+
import { status } from "./status"
|
|
30
11
|
|
|
31
12
|
describe("status command", () => {
|
|
32
|
-
let
|
|
33
|
-
let originalCwd: string
|
|
13
|
+
let root: string
|
|
34
14
|
|
|
35
15
|
beforeEach(async () => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// Set config path to non-existent file to use defaults
|
|
41
|
-
process.env.AGENCY_CONFIG_PATH = join(tempDir, "non-existent-config.json")
|
|
42
|
-
|
|
43
|
-
// Initialize git repo
|
|
44
|
-
await initGitRepo(tempDir)
|
|
45
|
-
await createCommit(tempDir, "Initial commit")
|
|
46
|
-
|
|
47
|
-
// Rename to main if needed
|
|
48
|
-
const currentBranch = await getCurrentBranch(tempDir)
|
|
49
|
-
if (currentBranch === "master") {
|
|
50
|
-
await Bun.spawn(["git", "branch", "-m", "main"], {
|
|
51
|
-
cwd: tempDir,
|
|
52
|
-
stdout: "pipe",
|
|
53
|
-
stderr: "pipe",
|
|
54
|
-
}).exited
|
|
55
|
-
}
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
afterEach(async () => {
|
|
59
|
-
process.chdir(originalCwd)
|
|
60
|
-
delete process.env.AGENCY_CONFIG_PATH
|
|
61
|
-
await cleanupTempDir(tempDir)
|
|
16
|
+
root = await createTempDir()
|
|
17
|
+
await Bun.write(join(root, "agency.json"), '{"version":2}\n')
|
|
18
|
+
await mkdir(join(root, "repos/agency"), { recursive: true })
|
|
62
19
|
})
|
|
63
20
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
createdAt: new Date().toISOString(),
|
|
90
|
-
} as any)
|
|
91
|
-
|
|
92
|
-
let output = ""
|
|
93
|
-
const originalLog = console.log
|
|
94
|
-
console.log = (msg: string) => {
|
|
95
|
-
output += msg + "\n"
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
await runTestEffect(status({}))
|
|
99
|
-
|
|
100
|
-
console.log = originalLog
|
|
101
|
-
expect(output).toContain("Current branch:")
|
|
102
|
-
expect(output).toContain("Branch type: Source branch")
|
|
103
|
-
expect(output).toContain("Template:")
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
test("shows source branch type when on source branch", async () => {
|
|
107
|
-
await initAgency(tempDir, "test-template")
|
|
108
|
-
await createBranch(tempDir, "feature")
|
|
109
|
-
await writeAgencyMetadata(tempDir, {
|
|
110
|
-
version: 1,
|
|
111
|
-
injectedFiles: [],
|
|
112
|
-
template: "test-template",
|
|
113
|
-
createdAt: new Date().toISOString(),
|
|
114
|
-
} as any)
|
|
115
|
-
|
|
116
|
-
let output = ""
|
|
117
|
-
const originalLog = console.log
|
|
118
|
-
console.log = (msg: string) => {
|
|
119
|
-
output += msg + "\n"
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
await runTestEffect(status({}))
|
|
123
|
-
|
|
124
|
-
console.log = originalLog
|
|
125
|
-
expect(output).toContain("Branch type:")
|
|
126
|
-
expect(output).toContain("Source branch")
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
test("shows emit branch type when on emit branch", async () => {
|
|
130
|
-
await initAgency(tempDir, "test-template")
|
|
131
|
-
|
|
132
|
-
// Create source branch with agency.json
|
|
133
|
-
await createBranch(tempDir, "agency--feature")
|
|
134
|
-
await writeAgencyMetadata(tempDir, {
|
|
135
|
-
version: 1,
|
|
136
|
-
injectedFiles: [],
|
|
137
|
-
template: "test-template",
|
|
138
|
-
emitBranch: "feature",
|
|
139
|
-
createdAt: new Date().toISOString(),
|
|
140
|
-
} as any)
|
|
141
|
-
// Stage and commit agency.json
|
|
142
|
-
await Bun.spawn(["git", "add", "agency.json"], {
|
|
143
|
-
cwd: tempDir,
|
|
144
|
-
stdout: "pipe",
|
|
145
|
-
stderr: "pipe",
|
|
146
|
-
}).exited
|
|
147
|
-
await createCommit(tempDir, "Feature work")
|
|
148
|
-
|
|
149
|
-
// Create and switch to emit branch
|
|
150
|
-
await createBranch(tempDir, "feature")
|
|
151
|
-
|
|
152
|
-
let output = ""
|
|
153
|
-
const originalLog = console.log
|
|
154
|
-
console.log = (msg: string) => {
|
|
155
|
-
output += msg + "\n"
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
await runTestEffect(status({}))
|
|
159
|
-
|
|
160
|
-
console.log = originalLog
|
|
161
|
-
expect(output).toContain("Branch type:")
|
|
162
|
-
expect(output).toContain("Emit branch")
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
test("shows corresponding branch when it exists", async () => {
|
|
166
|
-
await initAgency(tempDir, "test-template")
|
|
167
|
-
await createBranch(tempDir, "agency--feature")
|
|
168
|
-
await writeAgencyMetadata(tempDir, {
|
|
169
|
-
version: 1,
|
|
170
|
-
injectedFiles: [],
|
|
171
|
-
template: "test-template",
|
|
172
|
-
emitBranch: "feature",
|
|
173
|
-
createdAt: new Date().toISOString(),
|
|
174
|
-
} as any)
|
|
175
|
-
|
|
176
|
-
// Stage and commit agency.json
|
|
177
|
-
await Bun.spawn(["git", "add", "agency.json"], {
|
|
178
|
-
cwd: tempDir,
|
|
179
|
-
stdout: "pipe",
|
|
180
|
-
stderr: "pipe",
|
|
181
|
-
}).exited
|
|
182
|
-
await createCommit(tempDir, "Set up branch")
|
|
183
|
-
|
|
184
|
-
// Create emit branch
|
|
185
|
-
await createBranch(tempDir, "feature")
|
|
186
|
-
await checkoutBranch(tempDir, "agency--feature")
|
|
187
|
-
|
|
188
|
-
let output = ""
|
|
189
|
-
const originalLog = console.log
|
|
190
|
-
console.log = (msg: string) => {
|
|
191
|
-
output += msg + "\n"
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
await runTestEffect(status({}))
|
|
195
|
-
|
|
196
|
-
console.log = originalLog
|
|
197
|
-
expect(output).toContain("Emit branch: ")
|
|
198
|
-
expect(output).toContain("feature")
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
test("shows backpack files", async () => {
|
|
202
|
-
await initAgency(tempDir, "test-template")
|
|
203
|
-
await createBranch(tempDir, "feature")
|
|
204
|
-
await writeAgencyMetadata(tempDir, {
|
|
205
|
-
version: 1,
|
|
206
|
-
injectedFiles: ["AGENTS.md", "opencode.json"],
|
|
207
|
-
template: "test-template",
|
|
208
|
-
createdAt: new Date().toISOString(),
|
|
209
|
-
} as any)
|
|
210
|
-
|
|
211
|
-
let output = ""
|
|
212
|
-
const originalLog = console.log
|
|
213
|
-
console.log = (msg: string) => {
|
|
214
|
-
output += msg + "\n"
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
await runTestEffect(status({}))
|
|
218
|
-
|
|
219
|
-
console.log = originalLog
|
|
220
|
-
expect(output).toContain("Backpack:")
|
|
221
|
-
// Base backpack files (TASK.md, AGENCY.md, agency.json) + injected files (AGENTS.md, opencode.json)
|
|
222
|
-
expect(output).toContain("TASK.md")
|
|
223
|
-
expect(output).toContain("AGENCY.md")
|
|
224
|
-
expect(output).toContain("agency.json")
|
|
225
|
-
expect(output).toContain("AGENTS.md")
|
|
226
|
-
expect(output).toContain("opencode.json")
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
test("shows base branch when set", async () => {
|
|
230
|
-
await initAgency(tempDir, "test-template")
|
|
231
|
-
await createBranch(tempDir, "feature")
|
|
232
|
-
await writeAgencyMetadata(tempDir, {
|
|
233
|
-
version: 1,
|
|
234
|
-
injectedFiles: [],
|
|
235
|
-
template: "test-template",
|
|
236
|
-
baseBranch: "main",
|
|
237
|
-
createdAt: new Date().toISOString(),
|
|
238
|
-
} as any)
|
|
239
|
-
|
|
240
|
-
let output = ""
|
|
241
|
-
const originalLog = console.log
|
|
242
|
-
console.log = (msg: string) => {
|
|
243
|
-
output += msg + "\n"
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
await runTestEffect(status({}))
|
|
247
|
-
|
|
248
|
-
console.log = originalLog
|
|
249
|
-
expect(output).toContain("Base branch:")
|
|
250
|
-
expect(output).toContain("main")
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
test("shows template name", async () => {
|
|
254
|
-
await initAgency(tempDir, "my-custom-template")
|
|
255
|
-
await createBranch(tempDir, "feature")
|
|
256
|
-
await writeAgencyMetadata(tempDir, {
|
|
257
|
-
version: 1,
|
|
258
|
-
injectedFiles: [],
|
|
259
|
-
template: "my-custom-template",
|
|
260
|
-
createdAt: new Date().toISOString(),
|
|
261
|
-
} as any)
|
|
262
|
-
|
|
263
|
-
let output = ""
|
|
264
|
-
const originalLog = console.log
|
|
265
|
-
console.log = (msg: string) => {
|
|
266
|
-
output += msg + "\n"
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
await runTestEffect(status({}))
|
|
270
|
-
|
|
271
|
-
console.log = originalLog
|
|
272
|
-
expect(output).toContain("Template:")
|
|
273
|
-
expect(output).toContain("my-custom-template")
|
|
274
|
-
})
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
describe("JSON output", () => {
|
|
278
|
-
test("outputs valid JSON with --json flag", async () => {
|
|
279
|
-
await initAgency(tempDir, "test-template")
|
|
280
|
-
await createBranch(tempDir, "feature")
|
|
281
|
-
await writeAgencyMetadata(tempDir, {
|
|
282
|
-
version: 1,
|
|
283
|
-
injectedFiles: ["AGENTS.md"],
|
|
284
|
-
baseBranch: "main",
|
|
285
|
-
template: "test-template",
|
|
286
|
-
createdAt: new Date().toISOString(),
|
|
287
|
-
} as any)
|
|
288
|
-
|
|
289
|
-
let output = ""
|
|
290
|
-
const originalLog = console.log
|
|
291
|
-
console.log = (msg: string) => {
|
|
292
|
-
output += msg + "\n"
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
await runTestEffect(status({ json: true }))
|
|
296
|
-
|
|
297
|
-
console.log = originalLog
|
|
298
|
-
|
|
299
|
-
const data = JSON.parse(output.trim())
|
|
300
|
-
expect(data.initialized).toBe(true)
|
|
301
|
-
expect(data.branchType).toBe("source")
|
|
302
|
-
expect(data.currentBranch).toBe("feature")
|
|
303
|
-
expect(data.template).toBe("test-template")
|
|
304
|
-
// managedFiles includes base backpack files + injected files
|
|
305
|
-
expect(data.managedFiles).toContain("TASK.md")
|
|
306
|
-
expect(data.managedFiles).toContain("AGENCY.md")
|
|
307
|
-
expect(data.managedFiles).toContain("agency.json")
|
|
308
|
-
expect(data.managedFiles).toContain("AGENTS.md")
|
|
309
|
-
expect(data.baseBranch).toBe("main")
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
test("JSON output contains all expected fields", async () => {
|
|
313
|
-
await initAgency(tempDir, "test-template")
|
|
314
|
-
await createBranch(tempDir, "feature")
|
|
315
|
-
await writeAgencyMetadata(tempDir, {
|
|
316
|
-
version: 1,
|
|
317
|
-
injectedFiles: [],
|
|
318
|
-
template: "test-template",
|
|
319
|
-
createdAt: new Date().toISOString(),
|
|
320
|
-
} as any)
|
|
321
|
-
|
|
322
|
-
let output = ""
|
|
323
|
-
const originalLog = console.log
|
|
324
|
-
console.log = (msg: string) => {
|
|
325
|
-
output += msg + "\n"
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
await runTestEffect(status({ json: true }))
|
|
329
|
-
|
|
330
|
-
console.log = originalLog
|
|
331
|
-
|
|
332
|
-
const data = JSON.parse(output.trim())
|
|
333
|
-
expect(data).toHaveProperty("initialized")
|
|
334
|
-
expect(data).toHaveProperty("branchType")
|
|
335
|
-
expect(data).toHaveProperty("currentBranch")
|
|
336
|
-
expect(data).toHaveProperty("sourceBranch")
|
|
337
|
-
expect(data).toHaveProperty("emitBranch")
|
|
338
|
-
expect(data).toHaveProperty("correspondingBranchExists")
|
|
339
|
-
expect(data).toHaveProperty("template")
|
|
340
|
-
expect(data).toHaveProperty("managedFiles")
|
|
341
|
-
expect(data).toHaveProperty("baseBranch")
|
|
342
|
-
expect(data).toHaveProperty("createdAt")
|
|
343
|
-
})
|
|
344
|
-
|
|
345
|
-
test("JSON output shows not initialized state", async () => {
|
|
346
|
-
let output = ""
|
|
347
|
-
const originalLog = console.log
|
|
348
|
-
console.log = (msg: string) => {
|
|
349
|
-
output += msg + "\n"
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
await runTestEffect(status({ json: true }))
|
|
353
|
-
|
|
354
|
-
console.log = originalLog
|
|
355
|
-
|
|
356
|
-
const data = JSON.parse(output.trim())
|
|
357
|
-
expect(data.initialized).toBe(false)
|
|
358
|
-
expect(data.branchType).toBe("neither")
|
|
359
|
-
})
|
|
360
|
-
})
|
|
361
|
-
|
|
362
|
-
describe("error handling", () => {
|
|
363
|
-
test("throws error when not in a git repository", async () => {
|
|
364
|
-
const nonGitDir = await createTempDir()
|
|
365
|
-
process.chdir(nonGitDir)
|
|
366
|
-
|
|
367
|
-
await expect(runTestEffect(status({}))).rejects.toThrow(
|
|
368
|
-
"Not in a git repository",
|
|
369
|
-
)
|
|
370
|
-
|
|
371
|
-
await cleanupTempDir(nonGitDir)
|
|
372
|
-
})
|
|
373
|
-
})
|
|
374
|
-
|
|
375
|
-
describe("silent mode", () => {
|
|
376
|
-
test("silent flag suppresses output", async () => {
|
|
377
|
-
const originalLog = console.log
|
|
378
|
-
let logCalled = false
|
|
379
|
-
console.log = () => {
|
|
380
|
-
logCalled = true
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
await runTestEffect(status({ silent: true }))
|
|
384
|
-
|
|
385
|
-
console.log = originalLog
|
|
386
|
-
expect(logCalled).toBe(false)
|
|
387
|
-
})
|
|
388
|
-
})
|
|
389
|
-
|
|
390
|
-
describe("emit branch behavior", () => {
|
|
391
|
-
test("reads metadata from source branch when on emit branch without agency.json on disk", async () => {
|
|
392
|
-
await initAgency(tempDir, "test-template")
|
|
393
|
-
|
|
394
|
-
// Create source branch (agency--feature) with agency.json committed
|
|
395
|
-
await createBranch(tempDir, "agency--feature")
|
|
396
|
-
await writeAgencyMetadata(tempDir, {
|
|
397
|
-
version: 1,
|
|
398
|
-
injectedFiles: ["AGENTS.md", "opencode.json"],
|
|
399
|
-
template: "test-template",
|
|
400
|
-
baseBranch: "main",
|
|
401
|
-
emitBranch: "feature",
|
|
402
|
-
createdAt: new Date().toISOString(),
|
|
403
|
-
} as any)
|
|
404
|
-
// Stage and commit agency.json
|
|
405
|
-
await Bun.spawn(["git", "add", "agency.json"], {
|
|
406
|
-
cwd: tempDir,
|
|
407
|
-
stdout: "pipe",
|
|
408
|
-
stderr: "pipe",
|
|
409
|
-
}).exited
|
|
410
|
-
await createCommit(tempDir, "Feature work")
|
|
411
|
-
|
|
412
|
-
// Create emit branch and remove agency.json from working tree
|
|
413
|
-
await createBranch(tempDir, "feature")
|
|
414
|
-
await Bun.spawn(["rm", "agency.json"], {
|
|
415
|
-
cwd: tempDir,
|
|
416
|
-
stdout: "pipe",
|
|
417
|
-
stderr: "pipe",
|
|
418
|
-
}).exited
|
|
419
|
-
|
|
420
|
-
let output = ""
|
|
421
|
-
const originalLog = console.log
|
|
422
|
-
console.log = (msg: string) => {
|
|
423
|
-
output += msg + "\n"
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
await runTestEffect(status({ json: true }))
|
|
427
|
-
|
|
428
|
-
console.log = originalLog
|
|
429
|
-
|
|
430
|
-
const data = JSON.parse(output.trim())
|
|
431
|
-
// Verify that we're recognized as on an emit branch
|
|
432
|
-
expect(data.branchType).toBe("emit")
|
|
433
|
-
expect(data.initialized).toBe(true)
|
|
434
|
-
// Verify metadata is correctly read from source branch
|
|
435
|
-
expect(data.baseBranch).toBe("main")
|
|
436
|
-
expect(data.managedFiles).toContain("AGENTS.md")
|
|
437
|
-
expect(data.managedFiles).toContain("opencode.json")
|
|
438
|
-
expect(data.sourceBranch).toBe("agency--feature")
|
|
439
|
-
})
|
|
440
|
-
|
|
441
|
-
test("shows correct backpack when on emit branch", async () => {
|
|
442
|
-
await initAgency(tempDir, "test-template")
|
|
443
|
-
|
|
444
|
-
// Create source branch (agency--feature) with agency.json committed
|
|
445
|
-
await createBranch(tempDir, "agency--feature")
|
|
446
|
-
await writeAgencyMetadata(tempDir, {
|
|
447
|
-
version: 1,
|
|
448
|
-
injectedFiles: ["AGENTS.md"],
|
|
449
|
-
template: "test-template",
|
|
450
|
-
emitBranch: "feature",
|
|
451
|
-
createdAt: new Date().toISOString(),
|
|
452
|
-
} as any)
|
|
453
|
-
// Stage and commit agency.json
|
|
454
|
-
await Bun.spawn(["git", "add", "agency.json"], {
|
|
455
|
-
cwd: tempDir,
|
|
456
|
-
stdout: "pipe",
|
|
457
|
-
stderr: "pipe",
|
|
458
|
-
}).exited
|
|
459
|
-
await createCommit(tempDir, "Feature work")
|
|
460
|
-
|
|
461
|
-
// Create emit branch and remove agency.json from disk to simulate clean emit branch
|
|
462
|
-
await createBranch(tempDir, "feature")
|
|
463
|
-
await Bun.spawn(["rm", "agency.json"], {
|
|
464
|
-
cwd: tempDir,
|
|
465
|
-
stdout: "pipe",
|
|
466
|
-
stderr: "pipe",
|
|
467
|
-
}).exited
|
|
468
|
-
|
|
469
|
-
let output = ""
|
|
470
|
-
const originalLog = console.log
|
|
471
|
-
console.log = (msg: string) => {
|
|
472
|
-
output += msg + "\n"
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
await runTestEffect(status({ json: true }))
|
|
476
|
-
|
|
477
|
-
console.log = originalLog
|
|
478
|
-
|
|
479
|
-
const data = JSON.parse(output.trim())
|
|
480
|
-
expect(data.branchType).toBe("emit")
|
|
481
|
-
expect(data.initialized).toBe(true)
|
|
482
|
-
// Backpack should include injected files from source branch
|
|
483
|
-
expect(data.managedFiles).toContain("AGENTS.md")
|
|
484
|
-
expect(data.managedFiles).toContain("TASK.md")
|
|
485
|
-
expect(data.managedFiles).toContain("AGENCY.md")
|
|
486
|
-
expect(data.managedFiles).toContain("agency.json")
|
|
487
|
-
})
|
|
21
|
+
afterEach(async () => cleanupTempDir(root))
|
|
22
|
+
|
|
23
|
+
test("outputs status and repository metadata as JSON", async () => {
|
|
24
|
+
const logs = await captureLogs(() =>
|
|
25
|
+
runTestEffect(status({ cwd: root, json: true })),
|
|
26
|
+
)
|
|
27
|
+
const output = JSON.parse(logs[0]!)
|
|
28
|
+
|
|
29
|
+
expect(output).toMatchObject({
|
|
30
|
+
root,
|
|
31
|
+
issues: [],
|
|
32
|
+
epicCount: 0,
|
|
33
|
+
taskCount: 0,
|
|
34
|
+
phaseCount: 0,
|
|
35
|
+
valid: true,
|
|
36
|
+
})
|
|
37
|
+
expect(output.repositories).toEqual([
|
|
38
|
+
{
|
|
39
|
+
alias: "agency",
|
|
40
|
+
path: join(root, "repos/agency"),
|
|
41
|
+
kind: "repository",
|
|
42
|
+
remote: null,
|
|
43
|
+
target: null,
|
|
44
|
+
},
|
|
45
|
+
])
|
|
488
46
|
})
|
|
489
47
|
})
|