@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
package/src/commands/status.ts
CHANGED
|
@@ -1,232 +1,43 @@
|
|
|
1
|
-
import { Effect
|
|
2
|
-
import { join } from "node:path"
|
|
1
|
+
import { Effect } from "effect"
|
|
3
2
|
import type { BaseCommandOptions } from "../utils/command"
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
AgencyMetadataService,
|
|
9
|
-
parseAgencyMetadata,
|
|
10
|
-
} from "../services/AgencyMetadataService"
|
|
11
|
-
import { resolveBranchPairWithAgencyJson } from "../utils/pr-branch"
|
|
12
|
-
import highlight, { plural } from "../utils/colors"
|
|
13
|
-
import { createLoggers, ensureGitRepo, getTemplateName } from "../utils/effect"
|
|
3
|
+
import { WorkbaseService } from "../services/WorkbaseService"
|
|
4
|
+
import { RepositoryService } from "../services/RepositoryService"
|
|
5
|
+
import { createLoggers } from "../utils/effect"
|
|
14
6
|
|
|
15
7
|
interface StatusOptions extends BaseCommandOptions {
|
|
16
|
-
json?: boolean
|
|
8
|
+
readonly json?: boolean
|
|
17
9
|
}
|
|
18
10
|
|
|
19
|
-
/**
|
|
20
|
-
* Branch type for status output
|
|
21
|
-
*/
|
|
22
|
-
type BranchType = "source" | "emit" | "neither"
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Base files that are always in the backpack
|
|
26
|
-
*/
|
|
27
|
-
const BASE_BACKPACK_FILES = ["TASK.md", "AGENCY.md", "agency.json"]
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Status data structure returned by the status command
|
|
31
|
-
*/
|
|
32
|
-
interface StatusData {
|
|
33
|
-
initialized: boolean
|
|
34
|
-
branchType: BranchType
|
|
35
|
-
currentBranch: string
|
|
36
|
-
sourceBranch: string | null
|
|
37
|
-
emitBranch: string | null
|
|
38
|
-
correspondingBranchExists: boolean
|
|
39
|
-
template: string | null
|
|
40
|
-
managedFiles: string[]
|
|
41
|
-
baseBranch: string | null
|
|
42
|
-
createdAt: string | null
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Read agency.json metadata from the current working directory.
|
|
47
|
-
*/
|
|
48
|
-
const readAgencyMetadataFromDisk = (gitRoot: string) =>
|
|
49
|
-
Effect.gen(function* () {
|
|
50
|
-
const fs = yield* FileSystemService
|
|
51
|
-
const metadataPath = join(gitRoot, "agency.json")
|
|
52
|
-
|
|
53
|
-
const exists = yield* fs.exists(metadataPath)
|
|
54
|
-
if (!exists) {
|
|
55
|
-
return null
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const content = yield* fs.readFile(metadataPath)
|
|
59
|
-
return yield* parseAgencyMetadata(content)
|
|
60
|
-
}).pipe(Effect.catchAll(() => Effect.succeed(null)))
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Read agency.json metadata from a specific branch using git show.
|
|
64
|
-
*/
|
|
65
|
-
const readAgencyMetadataFromBranch = (gitRoot: string, branch: string) =>
|
|
66
|
-
Effect.gen(function* () {
|
|
67
|
-
const git = yield* GitService
|
|
68
|
-
|
|
69
|
-
// Try to read agency.json from the branch using git show
|
|
70
|
-
const content = yield* git.getFileAtRef(gitRoot, branch, "agency.json")
|
|
71
|
-
|
|
72
|
-
if (!content) {
|
|
73
|
-
return null
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return yield* parseAgencyMetadata(content)
|
|
77
|
-
}).pipe(Effect.catchAll(() => Effect.succeed(null)))
|
|
78
|
-
|
|
79
11
|
export const status = (options: StatusOptions = {}) =>
|
|
80
12
|
Effect.gen(function* () {
|
|
81
|
-
const
|
|
13
|
+
const workbase = yield* WorkbaseService
|
|
14
|
+
const repositories = yield* RepositoryService
|
|
82
15
|
const { log } = createLoggers(options)
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// Get current branch
|
|
93
|
-
const currentBranch = yield* git.getCurrentBranch(gitRoot)
|
|
94
|
-
|
|
95
|
-
// Resolve branch pair to determine if we're on source or emit branch
|
|
96
|
-
const branches = yield* resolveBranchPairWithAgencyJson(
|
|
97
|
-
gitRoot,
|
|
98
|
-
currentBranch,
|
|
99
|
-
config.sourceBranchPattern,
|
|
100
|
-
config.emitBranch,
|
|
101
|
-
)
|
|
102
|
-
const { sourceBranch, emitBranch, isOnEmitBranch } = branches
|
|
103
|
-
|
|
104
|
-
// Check if agency is initialized
|
|
105
|
-
// If on emit branch, read agency.json from source branch; otherwise read from disk
|
|
106
|
-
const metadata = isOnEmitBranch
|
|
107
|
-
? yield* readAgencyMetadataFromBranch(gitRoot, sourceBranch)
|
|
108
|
-
: yield* readAgencyMetadataFromDisk(gitRoot)
|
|
109
|
-
const initialized = metadata !== null
|
|
110
|
-
|
|
111
|
-
// Determine branch type
|
|
112
|
-
let branchType: BranchType = "neither"
|
|
113
|
-
if (initialized) {
|
|
114
|
-
branchType = isOnEmitBranch ? "emit" : "source"
|
|
16
|
+
const cwd = options.cwd ?? process.cwd()
|
|
17
|
+
const report = yield* workbase.validate(cwd)
|
|
18
|
+
const repos = yield* repositories.list(report.root)
|
|
19
|
+
const data = { ...report, repositories: repos }
|
|
20
|
+
|
|
21
|
+
if (options.json) {
|
|
22
|
+
log(JSON.stringify(data, null, 2))
|
|
23
|
+
return
|
|
115
24
|
}
|
|
116
25
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
26
|
+
log(`Workbase: ${report.root}`)
|
|
27
|
+
log(`Repositories: ${repos.length}`)
|
|
28
|
+
log(`Epics: ${report.epicCount}`)
|
|
29
|
+
log(`Tasks: ${report.taskCount}`)
|
|
30
|
+
log(`Phases: ${report.phaseCount}`)
|
|
31
|
+
log(
|
|
32
|
+
`Validation: ${report.valid ? "valid" : `${report.issues.length} issues`}`,
|
|
122
33
|
)
|
|
123
|
-
|
|
124
|
-
// Get template name from git config
|
|
125
|
-
const template = yield* getTemplateName(gitRoot)
|
|
126
|
-
|
|
127
|
-
// Build the complete backpack (all files carried in/out)
|
|
128
|
-
const backpackFiles = metadata
|
|
129
|
-
? [...BASE_BACKPACK_FILES, ...metadata.injectedFiles]
|
|
130
|
-
: []
|
|
131
|
-
|
|
132
|
-
// Build status data
|
|
133
|
-
const statusData: StatusData = {
|
|
134
|
-
initialized,
|
|
135
|
-
branchType,
|
|
136
|
-
currentBranch,
|
|
137
|
-
sourceBranch: isOnEmitBranch ? sourceBranch : currentBranch,
|
|
138
|
-
emitBranch: isOnEmitBranch ? currentBranch : emitBranch,
|
|
139
|
-
correspondingBranchExists,
|
|
140
|
-
template,
|
|
141
|
-
managedFiles: backpackFiles,
|
|
142
|
-
baseBranch: metadata?.baseBranch ?? null,
|
|
143
|
-
createdAt: metadata?.createdAt
|
|
144
|
-
? DateTime.toDateUtc(metadata.createdAt).toISOString()
|
|
145
|
-
: null,
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (json) {
|
|
149
|
-
// Output JSON format
|
|
150
|
-
log(JSON.stringify(statusData, null, 2))
|
|
151
|
-
} else {
|
|
152
|
-
// Output human-readable format with highlighting
|
|
153
|
-
log("")
|
|
154
|
-
|
|
155
|
-
if (!initialized) {
|
|
156
|
-
// Not initialized - show minimal info
|
|
157
|
-
log(
|
|
158
|
-
`Not initialized (run ${highlight.value("agency task")} to initialize)`,
|
|
159
|
-
)
|
|
160
|
-
log(`Current branch: ${highlight.branch(currentBranch)}`)
|
|
161
|
-
if (template) {
|
|
162
|
-
log(`Template: ${highlight.template(template)}`)
|
|
163
|
-
}
|
|
164
|
-
} else {
|
|
165
|
-
// Initialized - show full status
|
|
166
|
-
log(`Current branch: ${highlight.branch(currentBranch)}`)
|
|
167
|
-
|
|
168
|
-
// Branch type
|
|
169
|
-
const branchTypeDisplay = isOnEmitBranch
|
|
170
|
-
? "Emit branch"
|
|
171
|
-
: "Source branch"
|
|
172
|
-
log(`Branch type: ${branchTypeDisplay}`)
|
|
173
|
-
|
|
174
|
-
// Show corresponding branch only if it exists
|
|
175
|
-
if (correspondingBranchExists) {
|
|
176
|
-
if (isOnEmitBranch) {
|
|
177
|
-
log(`Source branch: ${highlight.branch(statusData.sourceBranch!)}`)
|
|
178
|
-
} else {
|
|
179
|
-
log(`Emit branch: ${highlight.branch(statusData.emitBranch!)}`)
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Template
|
|
184
|
-
if (template) {
|
|
185
|
-
log(`Template: ${highlight.template(template)}`)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Base branch
|
|
189
|
-
if (metadata?.baseBranch) {
|
|
190
|
-
log(`Base branch: ${highlight.branch(metadata.baseBranch)}`)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Backpack (files carried into the job and will leave with)
|
|
194
|
-
if (backpackFiles.length > 0) {
|
|
195
|
-
log(`Backpack:`)
|
|
196
|
-
for (const file of backpackFiles) {
|
|
197
|
-
log(` ${highlight.file(file)}`)
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Created at
|
|
202
|
-
if (metadata?.createdAt) {
|
|
203
|
-
const date = DateTime.toDateUtc(metadata.createdAt)
|
|
204
|
-
log(`Created: ${date.toLocaleString()}`)
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
log("")
|
|
209
|
-
}
|
|
210
34
|
})
|
|
211
35
|
|
|
212
36
|
export const help = `
|
|
213
37
|
Usage: agency status [options]
|
|
214
38
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
Information shown:
|
|
218
|
-
- Whether agency is initialized (agency.json exists)
|
|
219
|
-
- Current branch and branch type (source, emit, or neither)
|
|
220
|
-
- Source and emit branch names
|
|
221
|
-
- Whether the corresponding branch exists
|
|
222
|
-
- Configured template name
|
|
223
|
-
- Backpack (files carried in and filtered during emit)
|
|
224
|
-
- Base branch and creation timestamp
|
|
39
|
+
Show workbase repository, entity, and validation status.
|
|
225
40
|
|
|
226
41
|
Options:
|
|
227
|
-
--json Output status as JSON
|
|
228
|
-
|
|
229
|
-
Example:
|
|
230
|
-
agency status # Show human-readable status
|
|
231
|
-
agency status --json # Output as JSON
|
|
42
|
+
--json Output status as JSON
|
|
232
43
|
`
|
|
@@ -0,0 +1,206 @@
|
|
|
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 { phase } from "./phase"
|
|
11
|
+
import { task } from "./task"
|
|
12
|
+
|
|
13
|
+
describe("task and phase command JSON output", () => {
|
|
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
|
+
await runTestEffect(
|
|
21
|
+
task({
|
|
22
|
+
subcommand: "create",
|
|
23
|
+
args: ["multi"],
|
|
24
|
+
ticketUrl: "https://example.com/task",
|
|
25
|
+
multiPhase: true,
|
|
26
|
+
cwd: root,
|
|
27
|
+
silent: true,
|
|
28
|
+
}),
|
|
29
|
+
)
|
|
30
|
+
await runTestEffect(
|
|
31
|
+
phase({
|
|
32
|
+
subcommand: "create",
|
|
33
|
+
args: ["multi", "first"],
|
|
34
|
+
repo: "agency",
|
|
35
|
+
branch: "task/first",
|
|
36
|
+
base: "main",
|
|
37
|
+
cwd: root,
|
|
38
|
+
silent: true,
|
|
39
|
+
}),
|
|
40
|
+
)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
afterEach(async () => cleanupTempDir(root))
|
|
44
|
+
|
|
45
|
+
test("lists and shows task metadata without Markdown content", async () => {
|
|
46
|
+
const listLogs = await captureLogs(() =>
|
|
47
|
+
runTestEffect(
|
|
48
|
+
task({ subcommand: "list", args: [], cwd: root, json: true }),
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
expect(JSON.parse(listLogs[0]!)).toEqual([
|
|
52
|
+
{
|
|
53
|
+
id: "multi",
|
|
54
|
+
path: join(root, "tasks/multi/TASK.md"),
|
|
55
|
+
data: {
|
|
56
|
+
ticketUrl: "https://example.com/task",
|
|
57
|
+
phases: [{ id: "first" }],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
])
|
|
61
|
+
|
|
62
|
+
const showLogs = await captureLogs(() =>
|
|
63
|
+
runTestEffect(
|
|
64
|
+
task({
|
|
65
|
+
subcommand: "show",
|
|
66
|
+
args: ["multi"],
|
|
67
|
+
cwd: root,
|
|
68
|
+
json: true,
|
|
69
|
+
}),
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
expect(JSON.parse(showLogs[0]!)).toEqual({
|
|
73
|
+
id: "multi",
|
|
74
|
+
path: join(root, "tasks/multi/TASK.md"),
|
|
75
|
+
data: {
|
|
76
|
+
ticketUrl: "https://example.com/task",
|
|
77
|
+
phases: [{ id: "first" }],
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test("lists and shows phase metadata without Markdown content", async () => {
|
|
83
|
+
const listLogs = await captureLogs(() =>
|
|
84
|
+
runTestEffect(
|
|
85
|
+
phase({ subcommand: "list", args: ["multi"], cwd: root, json: true }),
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
expect(JSON.parse(listLogs[0]!)).toEqual([
|
|
89
|
+
{
|
|
90
|
+
taskId: "multi",
|
|
91
|
+
id: "first",
|
|
92
|
+
path: join(root, "tasks/multi/phases/first/PHASE.md"),
|
|
93
|
+
data: {
|
|
94
|
+
repo: "agency",
|
|
95
|
+
branch: "task/first",
|
|
96
|
+
base: "main",
|
|
97
|
+
pr: null,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
])
|
|
101
|
+
|
|
102
|
+
const showLogs = await captureLogs(() =>
|
|
103
|
+
runTestEffect(
|
|
104
|
+
phase({
|
|
105
|
+
subcommand: "show",
|
|
106
|
+
args: ["multi", "first"],
|
|
107
|
+
cwd: root,
|
|
108
|
+
json: true,
|
|
109
|
+
}),
|
|
110
|
+
),
|
|
111
|
+
)
|
|
112
|
+
expect(JSON.parse(showLogs[0]!)).toEqual({
|
|
113
|
+
taskId: "multi",
|
|
114
|
+
id: "first",
|
|
115
|
+
path: join(root, "tasks/multi/phases/first/PHASE.md"),
|
|
116
|
+
data: {
|
|
117
|
+
repo: "agency",
|
|
118
|
+
branch: "task/first",
|
|
119
|
+
base: "main",
|
|
120
|
+
pr: null,
|
|
121
|
+
},
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test("outputs created task and phase records as JSON", async () => {
|
|
126
|
+
const taskLogs = await captureLogs(() =>
|
|
127
|
+
runTestEffect(
|
|
128
|
+
task({
|
|
129
|
+
subcommand: "create",
|
|
130
|
+
args: ["another"],
|
|
131
|
+
ticketUrl: "https://example.com/another",
|
|
132
|
+
multiPhase: true,
|
|
133
|
+
cwd: root,
|
|
134
|
+
json: true,
|
|
135
|
+
}),
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
expect(JSON.parse(taskLogs[0]!)).toMatchObject({
|
|
139
|
+
id: "another",
|
|
140
|
+
data: { ticketUrl: "https://example.com/another", phases: [] },
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
const phaseLogs = await captureLogs(() =>
|
|
144
|
+
runTestEffect(
|
|
145
|
+
phase({
|
|
146
|
+
subcommand: "create",
|
|
147
|
+
args: ["another", "first"],
|
|
148
|
+
repo: "agency",
|
|
149
|
+
branch: "task/another-first",
|
|
150
|
+
base: "main",
|
|
151
|
+
cwd: root,
|
|
152
|
+
json: true,
|
|
153
|
+
}),
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
expect(JSON.parse(phaseLogs[0]!)).toMatchObject({
|
|
157
|
+
taskId: "another",
|
|
158
|
+
id: "first",
|
|
159
|
+
data: { repo: "agency", branch: "task/another-first", base: "main" },
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
test("converts a single-phase task with an explicit first phase ID", async () => {
|
|
164
|
+
await runTestEffect(
|
|
165
|
+
task({
|
|
166
|
+
subcommand: "create",
|
|
167
|
+
args: ["single"],
|
|
168
|
+
ticketUrl: "https://example.com/single",
|
|
169
|
+
repo: "agency",
|
|
170
|
+
branch: "task/single",
|
|
171
|
+
base: "main",
|
|
172
|
+
cwd: root,
|
|
173
|
+
silent: true,
|
|
174
|
+
}),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
await runTestEffect(
|
|
178
|
+
phase({
|
|
179
|
+
subcommand: "create",
|
|
180
|
+
args: ["single", "follow-up"],
|
|
181
|
+
firstPhase: "implementation",
|
|
182
|
+
repo: "agency",
|
|
183
|
+
branch: "task/follow-up",
|
|
184
|
+
base: "main",
|
|
185
|
+
dependsOn: ["implementation"],
|
|
186
|
+
cwd: root,
|
|
187
|
+
silent: true,
|
|
188
|
+
}),
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
const logs = await captureLogs(() =>
|
|
192
|
+
runTestEffect(
|
|
193
|
+
task({
|
|
194
|
+
subcommand: "show",
|
|
195
|
+
args: ["single"],
|
|
196
|
+
cwd: root,
|
|
197
|
+
json: true,
|
|
198
|
+
}),
|
|
199
|
+
),
|
|
200
|
+
)
|
|
201
|
+
expect(JSON.parse(logs[0]!).data.phases).toEqual([
|
|
202
|
+
{ id: "implementation" },
|
|
203
|
+
{ id: "follow-up", dependsOn: ["implementation"] },
|
|
204
|
+
])
|
|
205
|
+
})
|
|
206
|
+
})
|