@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/tasks.ts
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import { Effect, DateTime } from "effect"
|
|
2
|
-
import { Schema } from "@effect/schema"
|
|
3
|
-
import type { BaseCommandOptions } from "../utils/command"
|
|
4
|
-
import { GitService } from "../services/GitService"
|
|
5
|
-
import { ConfigService } from "../services/ConfigService"
|
|
6
|
-
import { AgencyMetadata } from "../schemas"
|
|
7
|
-
import highlight from "../utils/colors"
|
|
8
|
-
import { createLoggers, ensureGitRepo } from "../utils/effect"
|
|
9
|
-
|
|
10
|
-
interface TasksOptions extends BaseCommandOptions {
|
|
11
|
-
json?: boolean
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Task branch info for output
|
|
16
|
-
*/
|
|
17
|
-
interface TaskBranchInfo {
|
|
18
|
-
branch: string
|
|
19
|
-
template: string | null
|
|
20
|
-
baseBranch: string | null
|
|
21
|
-
createdAt: string | null
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Read agency.json metadata from a specific branch using git show.
|
|
26
|
-
*/
|
|
27
|
-
const readAgencyMetadataFromBranch = (gitRoot: string, branch: string) =>
|
|
28
|
-
Effect.gen(function* () {
|
|
29
|
-
const git = yield* GitService
|
|
30
|
-
|
|
31
|
-
// Try to read agency.json from the branch using git show
|
|
32
|
-
const content = yield* git.getFileAtRef(gitRoot, branch, "agency.json")
|
|
33
|
-
|
|
34
|
-
if (!content) {
|
|
35
|
-
return null
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return yield* parseAgencyMetadata(content)
|
|
39
|
-
}).pipe(Effect.catchAll(() => Effect.succeed(null)))
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Parse and validate agency.json content.
|
|
43
|
-
*/
|
|
44
|
-
const parseAgencyMetadata = (content: string) =>
|
|
45
|
-
Effect.gen(function* () {
|
|
46
|
-
const data = yield* Effect.try({
|
|
47
|
-
try: () => JSON.parse(content),
|
|
48
|
-
catch: () => new Error("Failed to parse agency.json"),
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
// Validate version
|
|
52
|
-
if (typeof data.version !== "number" || data.version !== 1) {
|
|
53
|
-
return null
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Parse and validate using Effect schema
|
|
57
|
-
const metadata = yield* Effect.try({
|
|
58
|
-
try: () => Schema.decodeUnknownSync(AgencyMetadata)(data),
|
|
59
|
-
catch: () => new Error("Invalid agency.json format"),
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
return metadata
|
|
63
|
-
}).pipe(Effect.catchAll(() => Effect.succeed(null)))
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Extract the prefix from a source branch pattern (everything before %branch%).
|
|
67
|
-
*/
|
|
68
|
-
const getSourceBranchPrefix = (pattern: string): string => {
|
|
69
|
-
if (pattern.includes("%branch%")) {
|
|
70
|
-
return pattern.split("%branch%")[0]!
|
|
71
|
-
}
|
|
72
|
-
// If no %branch% placeholder, the whole pattern is the prefix
|
|
73
|
-
return pattern
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Find all branches that match the source branch pattern prefix
|
|
78
|
-
*/
|
|
79
|
-
const findAllTaskBranches = (gitRoot: string) =>
|
|
80
|
-
Effect.gen(function* () {
|
|
81
|
-
const git = yield* GitService
|
|
82
|
-
const configService = yield* ConfigService
|
|
83
|
-
|
|
84
|
-
// Get source branch pattern from config
|
|
85
|
-
const config = yield* configService.loadConfig()
|
|
86
|
-
const prefix = getSourceBranchPrefix(config.sourceBranchPattern)
|
|
87
|
-
|
|
88
|
-
// Get only branches matching the prefix (fast - single git command)
|
|
89
|
-
const branches = yield* git.getBranchesByPrefix(gitRoot, prefix)
|
|
90
|
-
|
|
91
|
-
// For each matching branch, try to read agency.json for metadata
|
|
92
|
-
const taskBranches: TaskBranchInfo[] = []
|
|
93
|
-
for (const branch of branches) {
|
|
94
|
-
const metadata = yield* readAgencyMetadataFromBranch(gitRoot, branch)
|
|
95
|
-
|
|
96
|
-
if (metadata) {
|
|
97
|
-
taskBranches.push({
|
|
98
|
-
branch,
|
|
99
|
-
template: metadata.template ?? null,
|
|
100
|
-
baseBranch: metadata.baseBranch ?? null,
|
|
101
|
-
createdAt: metadata.createdAt
|
|
102
|
-
? DateTime.toDateUtc(metadata.createdAt).toISOString()
|
|
103
|
-
: null,
|
|
104
|
-
})
|
|
105
|
-
} else {
|
|
106
|
-
// Branch matches prefix but has no valid agency.json - still list it
|
|
107
|
-
taskBranches.push({
|
|
108
|
-
branch,
|
|
109
|
-
template: null,
|
|
110
|
-
baseBranch: null,
|
|
111
|
-
createdAt: null,
|
|
112
|
-
})
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return taskBranches
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
export const tasks = (options: TasksOptions = {}) =>
|
|
120
|
-
Effect.gen(function* () {
|
|
121
|
-
const { json = false } = options
|
|
122
|
-
const { log } = createLoggers(options)
|
|
123
|
-
|
|
124
|
-
const gitRoot = yield* ensureGitRepo()
|
|
125
|
-
|
|
126
|
-
// Find all branches with agency.json
|
|
127
|
-
const taskBranches = yield* findAllTaskBranches(gitRoot)
|
|
128
|
-
|
|
129
|
-
if (json) {
|
|
130
|
-
// Output JSON format
|
|
131
|
-
log(JSON.stringify(taskBranches, null, 2))
|
|
132
|
-
} else {
|
|
133
|
-
// Output human-readable format - just branch names
|
|
134
|
-
if (taskBranches.length === 0) {
|
|
135
|
-
log("")
|
|
136
|
-
log("No task branches found.")
|
|
137
|
-
log(
|
|
138
|
-
`Run ${highlight.value("agency task")} to create a task on a feature branch.`,
|
|
139
|
-
)
|
|
140
|
-
log("")
|
|
141
|
-
} else {
|
|
142
|
-
for (const task of taskBranches) {
|
|
143
|
-
log(task.branch)
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
export const help = `
|
|
150
|
-
Usage: agency tasks [options]
|
|
151
|
-
|
|
152
|
-
List all source branches that have agency tasks (branches matching the source
|
|
153
|
-
branch pattern, e.g. "agency--*").
|
|
154
|
-
|
|
155
|
-
Options:
|
|
156
|
-
--json Output as JSON (includes metadata: template, base branch, created date)
|
|
157
|
-
|
|
158
|
-
Example:
|
|
159
|
-
agency tasks # List all task branches (names only)
|
|
160
|
-
agency tasks --json # Output as JSON with full metadata
|
|
161
|
-
`
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test, beforeEach, afterEach } from "bun:test"
|
|
2
|
-
import { templateDelete } from "./template-delete"
|
|
3
|
-
import { mkdir, writeFile, rm } from "node:fs/promises"
|
|
4
|
-
import { join } from "node:path"
|
|
5
|
-
import { tmpdir } from "node:os"
|
|
6
|
-
import { runTestEffect } from "../test-utils"
|
|
7
|
-
|
|
8
|
-
describe("template delete command", () => {
|
|
9
|
-
let testDir: string
|
|
10
|
-
let gitRoot: string
|
|
11
|
-
let templateDir: string
|
|
12
|
-
|
|
13
|
-
beforeEach(async () => {
|
|
14
|
-
// Create temporary test directory
|
|
15
|
-
testDir = join(tmpdir(), `agency-test-delete-${Date.now()}`)
|
|
16
|
-
await mkdir(testDir, { recursive: true })
|
|
17
|
-
gitRoot = join(testDir, "repo")
|
|
18
|
-
await mkdir(gitRoot, { recursive: true })
|
|
19
|
-
|
|
20
|
-
// Initialize git repo
|
|
21
|
-
await Bun.spawn(["git", "init"], {
|
|
22
|
-
cwd: gitRoot,
|
|
23
|
-
stdout: "ignore",
|
|
24
|
-
stderr: "ignore",
|
|
25
|
-
}).exited
|
|
26
|
-
|
|
27
|
-
// Set up config dir
|
|
28
|
-
const configDir = join(testDir, "config")
|
|
29
|
-
templateDir = join(configDir, "templates", "test-template")
|
|
30
|
-
await mkdir(templateDir, { recursive: true })
|
|
31
|
-
|
|
32
|
-
// Set environment variable for config
|
|
33
|
-
process.env.AGENCY_CONFIG_DIR = configDir
|
|
34
|
-
|
|
35
|
-
// Set git config
|
|
36
|
-
await Bun.spawn(["git", "config", "agency.template", "test-template"], {
|
|
37
|
-
cwd: gitRoot,
|
|
38
|
-
stdout: "ignore",
|
|
39
|
-
stderr: "ignore",
|
|
40
|
-
}).exited
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
afterEach(async () => {
|
|
44
|
-
// Clean up
|
|
45
|
-
await rm(testDir, { recursive: true, force: true })
|
|
46
|
-
delete process.env.AGENCY_CONFIG_DIR
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
test("deletes file from template directory", async () => {
|
|
50
|
-
// Create test file in template
|
|
51
|
-
const testFile = join(templateDir, "test.md")
|
|
52
|
-
await writeFile(testFile, "# Test")
|
|
53
|
-
|
|
54
|
-
const originalCwd = process.cwd()
|
|
55
|
-
process.chdir(gitRoot)
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
await runTestEffect(templateDelete({ files: ["test.md"], silent: true }))
|
|
59
|
-
|
|
60
|
-
// Verify file was deleted
|
|
61
|
-
const file = Bun.file(testFile)
|
|
62
|
-
expect(await file.exists()).toBe(false)
|
|
63
|
-
} finally {
|
|
64
|
-
process.chdir(originalCwd)
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
test("deletes multiple files", async () => {
|
|
69
|
-
// Create test files in template
|
|
70
|
-
await writeFile(join(templateDir, "file1.md"), "# File 1")
|
|
71
|
-
await writeFile(join(templateDir, "file2.md"), "# File 2")
|
|
72
|
-
await writeFile(join(templateDir, "file3.md"), "# File 3")
|
|
73
|
-
|
|
74
|
-
const originalCwd = process.cwd()
|
|
75
|
-
process.chdir(gitRoot)
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
await runTestEffect(
|
|
79
|
-
templateDelete({
|
|
80
|
-
files: ["file1.md", "file2.md"],
|
|
81
|
-
silent: true,
|
|
82
|
-
}),
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
// Verify files were deleted
|
|
86
|
-
expect(await Bun.file(join(templateDir, "file1.md")).exists()).toBe(false)
|
|
87
|
-
expect(await Bun.file(join(templateDir, "file2.md")).exists()).toBe(false)
|
|
88
|
-
// file3.md should still exist
|
|
89
|
-
expect(await Bun.file(join(templateDir, "file3.md")).exists()).toBe(true)
|
|
90
|
-
} finally {
|
|
91
|
-
process.chdir(originalCwd)
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
test("deletes directory recursively", async () => {
|
|
96
|
-
// Create test directory with files
|
|
97
|
-
const docsDir = join(templateDir, "docs")
|
|
98
|
-
await mkdir(docsDir, { recursive: true })
|
|
99
|
-
await writeFile(join(docsDir, "README.md"), "# Docs")
|
|
100
|
-
await writeFile(join(docsDir, "guide.md"), "# Guide")
|
|
101
|
-
|
|
102
|
-
const originalCwd = process.cwd()
|
|
103
|
-
process.chdir(gitRoot)
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
await runTestEffect(templateDelete({ files: ["docs"], silent: true }))
|
|
107
|
-
|
|
108
|
-
// Verify directory was deleted
|
|
109
|
-
const dir = Bun.file(docsDir)
|
|
110
|
-
expect(await dir.exists()).toBe(false)
|
|
111
|
-
} finally {
|
|
112
|
-
process.chdir(originalCwd)
|
|
113
|
-
}
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
test("handles non-existent files gracefully", async () => {
|
|
117
|
-
const originalCwd = process.cwd()
|
|
118
|
-
process.chdir(gitRoot)
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
// Should not throw error for non-existent file
|
|
122
|
-
await runTestEffect(
|
|
123
|
-
templateDelete({ files: ["nonexistent.md"], silent: true }),
|
|
124
|
-
)
|
|
125
|
-
} finally {
|
|
126
|
-
process.chdir(originalCwd)
|
|
127
|
-
}
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
test("throws error when no files specified", async () => {
|
|
131
|
-
const originalCwd = process.cwd()
|
|
132
|
-
process.chdir(gitRoot)
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
await expect(
|
|
136
|
-
runTestEffect(templateDelete({ files: [], silent: true })),
|
|
137
|
-
).rejects.toThrow("No files specified")
|
|
138
|
-
} finally {
|
|
139
|
-
process.chdir(originalCwd)
|
|
140
|
-
}
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
test("throws error when not in git repository", async () => {
|
|
144
|
-
const nonGitDir = join(testDir, "non-git")
|
|
145
|
-
await mkdir(nonGitDir, { recursive: true })
|
|
146
|
-
|
|
147
|
-
const originalCwd = process.cwd()
|
|
148
|
-
process.chdir(nonGitDir)
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
await expect(
|
|
152
|
-
runTestEffect(templateDelete({ files: ["test.md"], silent: true })),
|
|
153
|
-
).rejects.toThrow("Not in a git repository")
|
|
154
|
-
} finally {
|
|
155
|
-
process.chdir(originalCwd)
|
|
156
|
-
}
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
test("throws error when template not configured", async () => {
|
|
160
|
-
// Remove git config
|
|
161
|
-
await Bun.spawn(["git", "config", "--unset", "agency.template"], {
|
|
162
|
-
cwd: gitRoot,
|
|
163
|
-
stdout: "ignore",
|
|
164
|
-
stderr: "ignore",
|
|
165
|
-
}).exited
|
|
166
|
-
|
|
167
|
-
const originalCwd = process.cwd()
|
|
168
|
-
process.chdir(gitRoot)
|
|
169
|
-
|
|
170
|
-
try {
|
|
171
|
-
await expect(
|
|
172
|
-
runTestEffect(templateDelete({ files: ["test.md"], silent: true })),
|
|
173
|
-
).rejects.toThrow("Repository not initialized")
|
|
174
|
-
} finally {
|
|
175
|
-
process.chdir(originalCwd)
|
|
176
|
-
}
|
|
177
|
-
})
|
|
178
|
-
})
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { resolve } from "path"
|
|
2
|
-
import { rm } from "node:fs/promises"
|
|
3
|
-
import { Effect } from "effect"
|
|
4
|
-
import type { BaseCommandOptions } from "../utils/command"
|
|
5
|
-
import { TemplateService } from "../services/TemplateService"
|
|
6
|
-
import { FileSystemService } from "../services/FileSystemService"
|
|
7
|
-
import { RepositoryNotInitializedError } from "../errors"
|
|
8
|
-
import highlight, { done } from "../utils/colors"
|
|
9
|
-
import { createLoggers, ensureGitRepo, getTemplateName } from "../utils/effect"
|
|
10
|
-
|
|
11
|
-
interface DeleteOptions extends BaseCommandOptions {
|
|
12
|
-
files?: string[]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const templateDelete = (options: DeleteOptions = {}) =>
|
|
16
|
-
Effect.gen(function* () {
|
|
17
|
-
const { files: filesToDelete = [] } = options
|
|
18
|
-
const { log, verboseLog } = createLoggers(options)
|
|
19
|
-
|
|
20
|
-
const templateService = yield* TemplateService
|
|
21
|
-
const fs = yield* FileSystemService
|
|
22
|
-
|
|
23
|
-
const gitRoot = yield* ensureGitRepo()
|
|
24
|
-
|
|
25
|
-
// Get template name from git config
|
|
26
|
-
const templateName = yield* getTemplateName(gitRoot)
|
|
27
|
-
if (!templateName) {
|
|
28
|
-
return yield* Effect.fail(new RepositoryNotInitializedError())
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (filesToDelete.length === 0) {
|
|
32
|
-
return yield* Effect.fail(
|
|
33
|
-
new Error(
|
|
34
|
-
"No files specified. Usage: agency template delete <file> [file ...]",
|
|
35
|
-
),
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
verboseLog(`Deleting from template: ${highlight.template(templateName)}`)
|
|
40
|
-
|
|
41
|
-
// Get template directory
|
|
42
|
-
const templateDir = yield* templateService.getTemplateDir(templateName)
|
|
43
|
-
|
|
44
|
-
// Delete each file
|
|
45
|
-
for (const filePath of filesToDelete) {
|
|
46
|
-
const templateFilePath = resolve(templateDir, filePath)
|
|
47
|
-
|
|
48
|
-
// Check if file exists
|
|
49
|
-
const exists = yield* fs.exists(templateFilePath)
|
|
50
|
-
if (!exists) {
|
|
51
|
-
verboseLog(`Skipping ${filePath} (does not exist in template)`)
|
|
52
|
-
continue
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Delete the file/directory
|
|
56
|
-
yield* Effect.tryPromise({
|
|
57
|
-
try: () => rm(templateFilePath, { recursive: true, force: true }),
|
|
58
|
-
catch: (error) => new Error(`Failed to delete ${filePath}: ${error}`),
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
log(
|
|
62
|
-
done(
|
|
63
|
-
`Deleted ${highlight.file(filePath)} from ${highlight.template(templateName)} template`,
|
|
64
|
-
),
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
// Help text for reference (not exported as it's handled by template command)
|
|
70
|
-
const help = `
|
|
71
|
-
Usage: agency template delete <file> [file ...] [options]
|
|
72
|
-
|
|
73
|
-
Delete specified files from the configured template directory.
|
|
74
|
-
|
|
75
|
-
This command removes files from the template directory configured in
|
|
76
|
-
.git/config (agency.template).
|
|
77
|
-
|
|
78
|
-
Arguments:
|
|
79
|
-
<file> File path to delete (relative to template root)
|
|
80
|
-
[file ...] Additional files to delete
|
|
81
|
-
|
|
82
|
-
Options:
|
|
83
|
-
-h, --help Show this help message
|
|
84
|
-
-s, --silent Suppress output messages
|
|
85
|
-
-v, --verbose Show verbose output
|
|
86
|
-
|
|
87
|
-
Examples:
|
|
88
|
-
agency template delete AGENTS.md # Delete specific file
|
|
89
|
-
agency template delete docs/ src/ # Delete directories
|
|
90
|
-
agency template delete file1 file2 file3 # Delete multiple files
|
|
91
|
-
|
|
92
|
-
Notes:
|
|
93
|
-
- Requires agency.template to be set (run 'agency init' first)
|
|
94
|
-
- At least one file must be specified
|
|
95
|
-
- Files are deleted from ~/.config/agency/templates/{template-name}/
|
|
96
|
-
- Non-existent files are skipped with a warning in verbose mode
|
|
97
|
-
- Directories are deleted recursively
|
|
98
|
-
`
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test, beforeEach, afterEach } from "bun:test"
|
|
2
|
-
import { templateList } from "./template-list"
|
|
3
|
-
import { mkdir, writeFile, rm } from "node:fs/promises"
|
|
4
|
-
import { join } from "node:path"
|
|
5
|
-
import { tmpdir } from "node:os"
|
|
6
|
-
import { runTestEffect } from "../test-utils"
|
|
7
|
-
|
|
8
|
-
describe("template list command", () => {
|
|
9
|
-
let testDir: string
|
|
10
|
-
let gitRoot: string
|
|
11
|
-
let templateDir: string
|
|
12
|
-
|
|
13
|
-
beforeEach(async () => {
|
|
14
|
-
// Create temporary test directory
|
|
15
|
-
testDir = join(tmpdir(), `agency-test-list-${Date.now()}`)
|
|
16
|
-
await mkdir(testDir, { recursive: true })
|
|
17
|
-
gitRoot = join(testDir, "repo")
|
|
18
|
-
await mkdir(gitRoot, { recursive: true })
|
|
19
|
-
|
|
20
|
-
// Initialize git repo
|
|
21
|
-
await Bun.spawn(["git", "init"], {
|
|
22
|
-
cwd: gitRoot,
|
|
23
|
-
stdout: "ignore",
|
|
24
|
-
stderr: "ignore",
|
|
25
|
-
}).exited
|
|
26
|
-
|
|
27
|
-
// Set up config dir
|
|
28
|
-
const configDir = join(testDir, "config")
|
|
29
|
-
templateDir = join(configDir, "templates", "test-template")
|
|
30
|
-
await mkdir(templateDir, { recursive: true })
|
|
31
|
-
|
|
32
|
-
// Set environment variable for config
|
|
33
|
-
process.env.AGENCY_CONFIG_DIR = configDir
|
|
34
|
-
|
|
35
|
-
// Set git config
|
|
36
|
-
await Bun.spawn(["git", "config", "agency.template", "test-template"], {
|
|
37
|
-
cwd: gitRoot,
|
|
38
|
-
stdout: "ignore",
|
|
39
|
-
stderr: "ignore",
|
|
40
|
-
}).exited
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
afterEach(async () => {
|
|
44
|
-
// Clean up
|
|
45
|
-
await rm(testDir, { recursive: true, force: true })
|
|
46
|
-
delete process.env.AGENCY_CONFIG_DIR
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
test("lists files in template directory", async () => {
|
|
50
|
-
// Create test files in template
|
|
51
|
-
await writeFile(join(templateDir, "AGENTS.md"), "# Agents")
|
|
52
|
-
await writeFile(join(templateDir, "opencode.json"), "{}")
|
|
53
|
-
await mkdir(join(templateDir, "docs"), { recursive: true })
|
|
54
|
-
await writeFile(join(templateDir, "docs", "README.md"), "# Docs")
|
|
55
|
-
|
|
56
|
-
// Capture output
|
|
57
|
-
const output: string[] = []
|
|
58
|
-
const originalLog = console.log
|
|
59
|
-
console.log = (...args: any[]) => {
|
|
60
|
-
output.push(args.join(" "))
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const originalCwd = process.cwd()
|
|
64
|
-
process.chdir(gitRoot)
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
await runTestEffect(templateList({ silent: false }))
|
|
68
|
-
} finally {
|
|
69
|
-
process.chdir(originalCwd)
|
|
70
|
-
console.log = originalLog
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
expect(output.length).toBeGreaterThan(0)
|
|
74
|
-
expect(output.some((line) => line.includes("AGENTS.md"))).toBe(true)
|
|
75
|
-
expect(output.some((line) => line.includes("opencode.json"))).toBe(true)
|
|
76
|
-
expect(output.some((line) => line.includes("docs/README.md"))).toBe(true)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
test("handles empty template directory", async () => {
|
|
80
|
-
// Capture output
|
|
81
|
-
const output: string[] = []
|
|
82
|
-
const originalLog = console.log
|
|
83
|
-
console.log = (...args: any[]) => {
|
|
84
|
-
output.push(args.join(" "))
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const originalCwd = process.cwd()
|
|
88
|
-
process.chdir(gitRoot)
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
await runTestEffect(templateList({ silent: false }))
|
|
92
|
-
} finally {
|
|
93
|
-
process.chdir(originalCwd)
|
|
94
|
-
console.log = originalLog
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
expect(output.some((line) => line.includes("has no files"))).toBe(true)
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
test("throws error when not in git repository", async () => {
|
|
101
|
-
const nonGitDir = join(testDir, "non-git")
|
|
102
|
-
await mkdir(nonGitDir, { recursive: true })
|
|
103
|
-
|
|
104
|
-
const originalCwd = process.cwd()
|
|
105
|
-
process.chdir(nonGitDir)
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
await expect(
|
|
109
|
-
runTestEffect(templateList({ silent: true })),
|
|
110
|
-
).rejects.toThrow("Not in a git repository")
|
|
111
|
-
} finally {
|
|
112
|
-
process.chdir(originalCwd)
|
|
113
|
-
}
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
test("throws error when template not configured", async () => {
|
|
117
|
-
// Remove git config
|
|
118
|
-
await Bun.spawn(["git", "config", "--unset", "agency.template"], {
|
|
119
|
-
cwd: gitRoot,
|
|
120
|
-
stdout: "ignore",
|
|
121
|
-
stderr: "ignore",
|
|
122
|
-
}).exited
|
|
123
|
-
|
|
124
|
-
const originalCwd = process.cwd()
|
|
125
|
-
process.chdir(gitRoot)
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
await expect(
|
|
129
|
-
runTestEffect(templateList({ silent: true })),
|
|
130
|
-
).rejects.toThrow("Repository not initialized")
|
|
131
|
-
} finally {
|
|
132
|
-
process.chdir(originalCwd)
|
|
133
|
-
}
|
|
134
|
-
})
|
|
135
|
-
})
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect"
|
|
2
|
-
import type { BaseCommandOptions } from "../utils/command"
|
|
3
|
-
import { TemplateService } from "../services/TemplateService"
|
|
4
|
-
import { FileSystemService } from "../services/FileSystemService"
|
|
5
|
-
import { RepositoryNotInitializedError } from "../errors"
|
|
6
|
-
import highlight from "../utils/colors"
|
|
7
|
-
import { createLoggers, ensureGitRepo, getTemplateName } from "../utils/effect"
|
|
8
|
-
|
|
9
|
-
interface ListOptions extends BaseCommandOptions {}
|
|
10
|
-
|
|
11
|
-
export const templateList = (options: ListOptions = {}) =>
|
|
12
|
-
Effect.gen(function* () {
|
|
13
|
-
const { log, verboseLog } = createLoggers(options)
|
|
14
|
-
|
|
15
|
-
const templateService = yield* TemplateService
|
|
16
|
-
const fs = yield* FileSystemService
|
|
17
|
-
|
|
18
|
-
const gitRoot = yield* ensureGitRepo()
|
|
19
|
-
|
|
20
|
-
// Get template name from git config
|
|
21
|
-
const templateName = yield* getTemplateName(gitRoot)
|
|
22
|
-
if (!templateName) {
|
|
23
|
-
return yield* Effect.fail(new RepositoryNotInitializedError())
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
verboseLog(`Listing files in template: ${highlight.template(templateName)}`)
|
|
27
|
-
|
|
28
|
-
// Get template directory
|
|
29
|
-
const templateDir = yield* templateService.getTemplateDir(templateName)
|
|
30
|
-
|
|
31
|
-
// Check if template directory exists and is a directory
|
|
32
|
-
const isDirectory = yield* Effect.tryPromise({
|
|
33
|
-
try: async () => {
|
|
34
|
-
const file = Bun.file(templateDir)
|
|
35
|
-
const stat = await file.stat()
|
|
36
|
-
return stat?.isDirectory?.() ?? false
|
|
37
|
-
},
|
|
38
|
-
catch: (error) =>
|
|
39
|
-
new Error(`Failed to check template directory: ${error}`),
|
|
40
|
-
})
|
|
41
|
-
if (!isDirectory) {
|
|
42
|
-
return yield* Effect.fail(
|
|
43
|
-
new Error(
|
|
44
|
-
`Template directory does not exist: ${highlight.template(templateName)}`,
|
|
45
|
-
),
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Collect all files recursively
|
|
50
|
-
const files = yield* fs.collectFiles(templateDir, {
|
|
51
|
-
exclude: [".gitkeep"],
|
|
52
|
-
sort: true,
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
if (files.length === 0) {
|
|
56
|
-
log(`Template ${highlight.template(templateName)} has no files`)
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
for (const file of files) {
|
|
61
|
-
log(highlight.file(file))
|
|
62
|
-
}
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
// Help text for reference (not exported as it's handled by template command)
|
|
66
|
-
const help = `
|
|
67
|
-
Usage: agency template list [options]
|
|
68
|
-
|
|
69
|
-
List all files in the configured template directory.
|
|
70
|
-
|
|
71
|
-
This command displays all files stored in the template configured for the
|
|
72
|
-
current git repository.
|
|
73
|
-
|
|
74
|
-
Options:
|
|
75
|
-
-h, --help Show this help message
|
|
76
|
-
-s, --silent Suppress output messages
|
|
77
|
-
-v, --verbose Show verbose output
|
|
78
|
-
|
|
79
|
-
Example:
|
|
80
|
-
agency template list # List files in current template
|
|
81
|
-
|
|
82
|
-
Notes:
|
|
83
|
-
- Requires agency.template to be set (run 'agency init' first)
|
|
84
|
-
- Shows files relative to template root directory
|
|
85
|
-
- Files are listed in alphabetical order
|
|
86
|
-
- Template directory must exist (created when you save files)
|
|
87
|
-
`
|