@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.
Files changed (130) hide show
  1. package/README.md +302 -113
  2. package/cli.ts +123 -415
  3. package/index.ts +1 -1
  4. package/package.json +12 -17
  5. package/skills/agency/SKILL.md +258 -0
  6. package/src/cli.test.ts +290 -0
  7. package/src/commands/description.test.ts +103 -0
  8. package/src/commands/epic.test.ts +103 -0
  9. package/src/commands/epic.ts +101 -0
  10. package/src/commands/init.test.ts +42 -290
  11. package/src/commands/init.ts +15 -123
  12. package/src/commands/phase.ts +120 -0
  13. package/src/commands/pr.test.ts +19 -170
  14. package/src/commands/pr.ts +23 -179
  15. package/src/commands/repo.test.ts +80 -0
  16. package/src/commands/repo.ts +83 -0
  17. package/src/commands/status.test.ts +35 -477
  18. package/src/commands/status.ts +24 -213
  19. package/src/commands/task-phase.test.ts +206 -0
  20. package/src/commands/task.ts +83 -1285
  21. package/src/commands/validate.test.ts +91 -0
  22. package/src/commands/validate.ts +50 -0
  23. package/src/commands/work.test.ts +172 -419
  24. package/src/commands/work.ts +47 -166
  25. package/src/services/EpicService.test.ts +71 -0
  26. package/src/services/EpicService.ts +135 -0
  27. package/src/services/FileSystemService.ts +61 -92
  28. package/src/services/PhaseService.ts +323 -0
  29. package/src/services/PullRequestService.test.ts +386 -0
  30. package/src/services/PullRequestService.ts +143 -0
  31. package/src/services/RepositoryService.test.ts +106 -0
  32. package/src/services/RepositoryService.ts +159 -0
  33. package/src/services/TaskPhaseService.test.ts +217 -0
  34. package/src/services/TaskService.ts +192 -0
  35. package/src/services/WorkbaseService.test.ts +446 -0
  36. package/src/services/WorkbaseService.ts +493 -0
  37. package/src/services/WorktreeService.test.ts +599 -0
  38. package/src/services/WorktreeService.ts +357 -0
  39. package/src/test-utils.ts +41 -438
  40. package/src/types.ts +1 -215
  41. package/src/utils/command.ts +1 -5
  42. package/src/utils/effect.test.ts +24 -0
  43. package/src/utils/effect.ts +3 -273
  44. package/src/utils/process.test.ts +0 -15
  45. package/src/utils/process.ts +1 -60
  46. package/src/workbase/frontmatter.test.ts +50 -0
  47. package/src/workbase/frontmatter.ts +77 -0
  48. package/src/workbase/repository-reference.test.ts +25 -0
  49. package/src/workbase/repository-reference.ts +18 -0
  50. package/src/workbase/schemas.test.ts +146 -0
  51. package/src/workbase/schemas.ts +78 -0
  52. package/src/workbase/worktree-command.test.ts +61 -0
  53. package/src/workbase/worktree-command.ts +52 -0
  54. package/src/commands/base.test.ts +0 -198
  55. package/src/commands/base.ts +0 -198
  56. package/src/commands/clean.test.ts +0 -299
  57. package/src/commands/clean.ts +0 -274
  58. package/src/commands/completions.test.ts +0 -51
  59. package/src/commands/completions.ts +0 -308
  60. package/src/commands/emit.integration.test.ts +0 -549
  61. package/src/commands/emit.test.ts +0 -576
  62. package/src/commands/emit.ts +0 -700
  63. package/src/commands/emitted.test.ts +0 -226
  64. package/src/commands/emitted.ts +0 -57
  65. package/src/commands/loop.test.ts +0 -621
  66. package/src/commands/loop.ts +0 -406
  67. package/src/commands/merge.integration.test.ts +0 -195
  68. package/src/commands/merge.test.ts +0 -246
  69. package/src/commands/merge.ts +0 -244
  70. package/src/commands/pull.test.ts +0 -395
  71. package/src/commands/pull.ts +0 -205
  72. package/src/commands/push.test.ts +0 -518
  73. package/src/commands/push.ts +0 -434
  74. package/src/commands/rebase.test.ts +0 -521
  75. package/src/commands/rebase.ts +0 -232
  76. package/src/commands/save.test.ts +0 -247
  77. package/src/commands/save.ts +0 -162
  78. package/src/commands/source.test.ts +0 -195
  79. package/src/commands/source.ts +0 -72
  80. package/src/commands/switch.test.ts +0 -194
  81. package/src/commands/switch.ts +0 -84
  82. package/src/commands/task-branching.test.ts +0 -633
  83. package/src/commands/task-continue.test.ts +0 -311
  84. package/src/commands/task-edit.test.ts +0 -235
  85. package/src/commands/task-main.test.ts +0 -1066
  86. package/src/commands/task-squash.test.ts +0 -266
  87. package/src/commands/tasks.test.ts +0 -391
  88. package/src/commands/tasks.ts +0 -161
  89. package/src/commands/template-delete.test.ts +0 -178
  90. package/src/commands/template-delete.ts +0 -98
  91. package/src/commands/template-list.test.ts +0 -135
  92. package/src/commands/template-list.ts +0 -87
  93. package/src/commands/template-view.test.ts +0 -158
  94. package/src/commands/template-view.ts +0 -86
  95. package/src/commands/template.test.ts +0 -32
  96. package/src/commands/template.ts +0 -96
  97. package/src/commands/use.test.ts +0 -87
  98. package/src/commands/use.ts +0 -97
  99. package/src/constants.ts +0 -10
  100. package/src/errors.ts +0 -17
  101. package/src/schemas.ts +0 -33
  102. package/src/services/AgencyMetadataService.ts +0 -311
  103. package/src/services/ClaudeService.test.ts +0 -184
  104. package/src/services/ClaudeService.ts +0 -91
  105. package/src/services/ConfigService.ts +0 -115
  106. package/src/services/FilterRepoService.ts +0 -164
  107. package/src/services/FormatterService.test.ts +0 -432
  108. package/src/services/FormatterService.ts +0 -219
  109. package/src/services/GitService.ts +0 -1183
  110. package/src/services/MockFilterRepoService.ts +0 -140
  111. package/src/services/OpencodeService.ts +0 -263
  112. package/src/services/PromptService.ts +0 -183
  113. package/src/services/TemplateService.ts +0 -81
  114. package/src/types/native-exec.d.ts +0 -8
  115. package/src/utils/colors.ts +0 -178
  116. package/src/utils/git-path.ts +0 -6
  117. package/src/utils/glob.test.ts +0 -161
  118. package/src/utils/glob.ts +0 -78
  119. package/src/utils/paths.ts +0 -51
  120. package/src/utils/pr-branch.test.ts +0 -376
  121. package/src/utils/pr-branch.ts +0 -508
  122. package/src/utils/spinner.test.ts +0 -45
  123. package/src/utils/spinner.ts +0 -96
  124. package/src/utils/task-parser.test.ts +0 -172
  125. package/src/utils/task-parser.ts +0 -93
  126. package/templates/AGENCY.md +0 -22
  127. package/templates/AGENTS.md +0 -11
  128. package/templates/CLAUDE.md +0 -3
  129. package/templates/TASK.md +0 -5
  130. package/templates/opencode.json +0 -4
@@ -0,0 +1,146 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { Schema } from "@effect/schema"
3
+ import {
4
+ EntityId,
5
+ EpicFrontmatter,
6
+ PhaseFrontmatter,
7
+ TaskFrontmatter,
8
+ WorkbaseConfig,
9
+ } from "./schemas"
10
+
11
+ describe("body-of-work descriptions", () => {
12
+ test("accepts descriptions on epics, tasks, and phases", () => {
13
+ const epic = Schema.decodeUnknownSync(EpicFrontmatter)({
14
+ ticketUrl: "https://example.com/epic",
15
+ description: "Coordinate the rollout.",
16
+ repos: [{ repo: "agency", ref: "main" }],
17
+ tasks: [],
18
+ })
19
+ const singlePhaseTask = Schema.decodeUnknownSync(TaskFrontmatter)({
20
+ ticketUrl: "https://example.com/task",
21
+ description: "Deliver the rollout.",
22
+ repo: "agency",
23
+ branch: "task/rollout",
24
+ base: "main",
25
+ pr: null,
26
+ })
27
+ const multiPhaseTask = Schema.decodeUnknownSync(TaskFrontmatter)({
28
+ ticketUrl: "https://example.com/task",
29
+ description: "Deliver the rollout in phases.",
30
+ phases: [],
31
+ })
32
+ const phase = Schema.decodeUnknownSync(PhaseFrontmatter)({
33
+ description: "Introduce the service contract.",
34
+ repo: "agency",
35
+ branch: "task/contract",
36
+ base: "main",
37
+ pr: null,
38
+ })
39
+
40
+ expect(epic.description).toBe("Coordinate the rollout.")
41
+ expect(singlePhaseTask.description).toBe("Deliver the rollout.")
42
+ expect(multiPhaseTask.description).toBe("Deliver the rollout in phases.")
43
+ expect(phase.description).toBe("Introduce the service contract.")
44
+ })
45
+
46
+ test("allows omitted descriptions for existing documents", () => {
47
+ const epic = Schema.decodeUnknownSync(EpicFrontmatter)({
48
+ ticketUrl: "https://example.com/epic",
49
+ repos: [{ repo: "agency", ref: "main" }],
50
+ tasks: [],
51
+ })
52
+
53
+ expect(epic.description).toBeUndefined()
54
+ })
55
+
56
+ test("rejects an empty description when present", () => {
57
+ expect(() =>
58
+ Schema.decodeUnknownSync(PhaseFrontmatter)({
59
+ description: "",
60
+ repo: "agency",
61
+ branch: "task/contract",
62
+ base: "main",
63
+ pr: null,
64
+ }),
65
+ ).toThrow()
66
+ })
67
+ })
68
+
69
+ describe("schema boundaries", () => {
70
+ const rejects = <S extends Schema.Schema.AnyNoContext>(
71
+ schema: S,
72
+ input: unknown,
73
+ ) => {
74
+ expect(() =>
75
+ Schema.decodeUnknownSync(schema, { onExcessProperty: "error" })(input),
76
+ ).toThrow()
77
+ }
78
+
79
+ test("rejects invalid boundary values", () => {
80
+ const cases = [
81
+ {
82
+ name: "unsupported config version",
83
+ schema: WorkbaseConfig,
84
+ input: { version: 1 },
85
+ },
86
+ {
87
+ name: "excess config fields",
88
+ schema: WorkbaseConfig,
89
+ input: { version: 2, legacy: true },
90
+ },
91
+ {
92
+ name: "invalid entity ID",
93
+ schema: EntityId,
94
+ input: "invalid/id",
95
+ },
96
+ {
97
+ name: "empty epic repositories",
98
+ schema: EpicFrontmatter,
99
+ input: {
100
+ ticketUrl: "https://example.com/epic",
101
+ repos: [],
102
+ tasks: [],
103
+ },
104
+ },
105
+ {
106
+ name: "mixed task union",
107
+ schema: TaskFrontmatter,
108
+ input: {
109
+ ticketUrl: "https://example.com/task",
110
+ phases: [],
111
+ repo: "agency",
112
+ branch: "task/mixed",
113
+ base: "main",
114
+ pr: null,
115
+ },
116
+ },
117
+ {
118
+ name: "single-phase task missing pr",
119
+ schema: TaskFrontmatter,
120
+ input: {
121
+ ticketUrl: "https://example.com/task",
122
+ repo: "agency",
123
+ branch: "task/missing-pr",
124
+ base: "main",
125
+ },
126
+ },
127
+ {
128
+ name: "invalid phase PR URL",
129
+ schema: PhaseFrontmatter,
130
+ input: {
131
+ repo: "agency",
132
+ branch: "task/invalid-pr",
133
+ base: "main",
134
+ pr: "https://example.com/pull/1",
135
+ },
136
+ },
137
+ ] as const
138
+
139
+ for (const fixture of cases) {
140
+ expect(
141
+ () => rejects(fixture.schema, fixture.input),
142
+ fixture.name,
143
+ ).not.toThrow()
144
+ }
145
+ })
146
+ })
@@ -0,0 +1,78 @@
1
+ import { Schema } from "@effect/schema"
2
+
3
+ const NonEmptyString = Schema.String.pipe(Schema.minLength(1))
4
+
5
+ const Description = Schema.optional(NonEmptyString)
6
+
7
+ const IdPattern = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/
8
+
9
+ export const EntityId = NonEmptyString.pipe(Schema.pattern(IdPattern))
10
+
11
+ export const RepositoryAlias = NonEmptyString.pipe(Schema.pattern(IdPattern))
12
+
13
+ export const RepositoryReference = Schema.Struct({
14
+ repo: RepositoryAlias,
15
+ ref: NonEmptyString,
16
+ })
17
+
18
+ const Url = NonEmptyString.pipe(Schema.pattern(/^[a-zA-Z][a-zA-Z0-9+.-]*:/))
19
+
20
+ const GitHubPullRequestUrl = NonEmptyString.pipe(
21
+ Schema.pattern(/^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+\/?$/),
22
+ )
23
+
24
+ export const WorkbaseConfig = Schema.Struct({
25
+ version: Schema.Literal(2),
26
+ worktreeCreateCommand: Schema.optional(Schema.NonEmptyArray(NonEmptyString)),
27
+ })
28
+
29
+ export const Dependency = Schema.Struct({
30
+ id: EntityId,
31
+ dependsOn: Schema.optional(Schema.Array(EntityId)),
32
+ })
33
+
34
+ const ExecutionUnit = {
35
+ repo: RepositoryAlias,
36
+ repos: Schema.optional(Schema.Array(RepositoryReference)),
37
+ branch: NonEmptyString,
38
+ base: NonEmptyString,
39
+ pr: Schema.NullOr(GitHubPullRequestUrl),
40
+ }
41
+
42
+ export const EpicFrontmatter = Schema.Struct({
43
+ ticketUrl: Url,
44
+ description: Description,
45
+ repos: Schema.NonEmptyArray(RepositoryReference),
46
+ tasks: Schema.Array(Dependency),
47
+ })
48
+
49
+ const SinglePhaseTaskFrontmatter = Schema.Struct({
50
+ ticketUrl: Url,
51
+ description: Description,
52
+ epic: Schema.optional(EntityId),
53
+ ...ExecutionUnit,
54
+ })
55
+
56
+ const MultiPhaseTaskFrontmatter = Schema.Struct({
57
+ ticketUrl: Url,
58
+ description: Description,
59
+ epic: Schema.optional(EntityId),
60
+ phases: Schema.Array(Dependency),
61
+ })
62
+
63
+ export const TaskFrontmatter = Schema.Union(
64
+ SinglePhaseTaskFrontmatter,
65
+ MultiPhaseTaskFrontmatter,
66
+ )
67
+
68
+ export const PhaseFrontmatter = Schema.Struct({
69
+ description: Description,
70
+ ...ExecutionUnit,
71
+ })
72
+
73
+ export type WorkbaseConfig = Schema.Schema.Type<typeof WorkbaseConfig>
74
+ export type Dependency = Schema.Schema.Type<typeof Dependency>
75
+ export type RepositoryReference = Schema.Schema.Type<typeof RepositoryReference>
76
+ export type EpicFrontmatter = Schema.Schema.Type<typeof EpicFrontmatter>
77
+ export type TaskFrontmatter = Schema.Schema.Type<typeof TaskFrontmatter>
78
+ export type PhaseFrontmatter = Schema.Schema.Type<typeof PhaseFrontmatter>
@@ -0,0 +1,61 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import {
3
+ expandWorktreeCreateCommand,
4
+ worktreeCommandEnvironment,
5
+ } from "./worktree-command"
6
+
7
+ const variables = {
8
+ repo: "/work/repos/app",
9
+ worktree: "/work/tasks/example/code/app",
10
+ branch: "task/example",
11
+ base: "main",
12
+ }
13
+
14
+ describe("worktree command templates", () => {
15
+ test("expands argv placeholders without shell interpolation", () => {
16
+ expect(
17
+ expandWorktreeCreateCommand(
18
+ [
19
+ "tool",
20
+ "--repo={repo}",
21
+ "--worktree",
22
+ "{worktree}",
23
+ "{branch}",
24
+ "{base}",
25
+ ],
26
+ variables,
27
+ ),
28
+ ).toEqual([
29
+ "tool",
30
+ "--repo=/work/repos/app",
31
+ "--worktree",
32
+ "/work/tasks/example/code/app",
33
+ "task/example",
34
+ "main",
35
+ ])
36
+ })
37
+
38
+ test("requires repo and worktree placeholders", () => {
39
+ expect(() =>
40
+ expandWorktreeCreateCommand(["tool", "{repo}"], variables),
41
+ ).toThrow("{worktree}")
42
+ })
43
+
44
+ test("rejects unknown placeholders", () => {
45
+ expect(() =>
46
+ expandWorktreeCreateCommand(
47
+ ["tool", "{repo}", "{worktree}", "{unknown}"],
48
+ variables,
49
+ ),
50
+ ).toThrow("{unknown}")
51
+ })
52
+
53
+ test("provides equivalent environment variables", () => {
54
+ expect(worktreeCommandEnvironment(variables)).toEqual({
55
+ AGENCY_REPO: variables.repo,
56
+ AGENCY_WORKTREE: variables.worktree,
57
+ AGENCY_BRANCH: variables.branch,
58
+ AGENCY_BASE: variables.base,
59
+ })
60
+ })
61
+ })
@@ -0,0 +1,52 @@
1
+ interface WorktreeCommandVariables {
2
+ readonly repo: string
3
+ readonly worktree: string
4
+ readonly branch: string
5
+ readonly base: string
6
+ }
7
+
8
+ const REQUIRED_PLACEHOLDERS = ["repo", "worktree"] as const
9
+ const PLACEHOLDERS = new Set(["repo", "worktree", "branch", "base"])
10
+
11
+ export const validateWorktreeCreateCommand = (command: readonly string[]) => {
12
+ const template = command.join("\u0000")
13
+ for (const placeholder of REQUIRED_PLACEHOLDERS) {
14
+ if (!template.includes(`{${placeholder}}`)) {
15
+ throw new Error(
16
+ `worktreeCreateCommand must include the {${placeholder}} placeholder`,
17
+ )
18
+ }
19
+ }
20
+ for (const argument of command) {
21
+ for (const match of argument.matchAll(/\{([^{}]+)\}/g)) {
22
+ const placeholder = match[1]!
23
+ if (!PLACEHOLDERS.has(placeholder)) {
24
+ throw new Error(
25
+ `Unknown worktreeCreateCommand placeholder: {${placeholder}}`,
26
+ )
27
+ }
28
+ }
29
+ }
30
+ }
31
+
32
+ export const expandWorktreeCreateCommand = (
33
+ command: readonly string[],
34
+ variables: WorktreeCommandVariables,
35
+ ): string[] => {
36
+ validateWorktreeCreateCommand(command)
37
+
38
+ return command.map((argument) =>
39
+ argument.replaceAll(/\{([^{}]+)\}/g, (match, placeholder: string) => {
40
+ return variables[placeholder as keyof WorktreeCommandVariables] ?? match
41
+ }),
42
+ )
43
+ }
44
+
45
+ export const worktreeCommandEnvironment = (
46
+ variables: WorktreeCommandVariables,
47
+ ): Record<string, string> => ({
48
+ AGENCY_REPO: variables.repo,
49
+ AGENCY_WORKTREE: variables.worktree,
50
+ AGENCY_BRANCH: variables.branch,
51
+ AGENCY_BASE: variables.base,
52
+ })
@@ -1,198 +0,0 @@
1
- import { describe, test, expect, beforeEach, afterEach } from "bun:test"
2
- import { base } from "./base"
3
- import {
4
- createTempDir,
5
- cleanupTempDir,
6
- initGitRepo,
7
- getGitConfig,
8
- runTestEffect,
9
- } from "../test-utils"
10
- import { getBaseBranchFromMetadata, writeAgencyMetadata } from "../types"
11
-
12
- describe("base", () => {
13
- let testDir: string
14
- let originalCwd: string
15
-
16
- beforeEach(async () => {
17
- originalCwd = process.cwd()
18
- testDir = await createTempDir()
19
- await initGitRepo(testDir)
20
- process.chdir(testDir)
21
- })
22
-
23
- afterEach(async () => {
24
- process.chdir(originalCwd)
25
- await cleanupTempDir(testDir)
26
- })
27
-
28
- describe("base set", () => {
29
- test("sets base branch for current branch", async () => {
30
- // Create a feature branch
31
- await Bun.spawn(["git", "checkout", "-b", "feature"], {
32
- cwd: testDir,
33
- stdout: "pipe",
34
- stderr: "pipe",
35
- }).exited
36
-
37
- // Create agency.json first
38
- await writeAgencyMetadata(testDir, {
39
- version: 1 as const,
40
- template: "test",
41
- injectedFiles: [],
42
- createdAt: new Date().toISOString(),
43
- } as any)
44
-
45
- // Set base branch
46
- await runTestEffect(
47
- base({
48
- subcommand: "set",
49
- args: ["main"],
50
- silent: true,
51
- }),
52
- )
53
-
54
- // Verify it was saved
55
- const savedBase = await getBaseBranchFromMetadata(testDir)
56
- expect(savedBase).toBe("main")
57
- })
58
-
59
- test("sets repository-level default base branch", async () => {
60
- // Set repo-level base branch
61
- await runTestEffect(
62
- base({
63
- subcommand: "set",
64
- args: ["main"],
65
- repo: true,
66
- silent: true,
67
- }),
68
- )
69
-
70
- // Verify it was saved to git config
71
- const savedBase = await getGitConfig("agency.baseBranch", testDir)
72
- expect(savedBase).toBe("main")
73
- })
74
-
75
- test("throws error if base branch does not exist", async () => {
76
- await expect(
77
- runTestEffect(
78
- base({
79
- subcommand: "set",
80
- args: ["nonexistent"],
81
- silent: true,
82
- }),
83
- ),
84
- ).rejects.toThrow("does not exist")
85
- })
86
- })
87
-
88
- describe("base get", () => {
89
- test("gets base branch for current branch", async () => {
90
- // Create a feature branch
91
- await Bun.spawn(["git", "checkout", "-b", "feature"], {
92
- cwd: testDir,
93
- stdout: "pipe",
94
- stderr: "pipe",
95
- }).exited
96
-
97
- // Create agency.json with base branch
98
- await writeAgencyMetadata(testDir, {
99
- version: 1,
100
- template: "test",
101
- injectedFiles: [],
102
- baseBranch: "main",
103
- createdAt: new Date().toISOString(),
104
- } as any)
105
-
106
- // Mock console.log to capture output
107
- const logs: string[] = []
108
- const originalLog = console.log
109
- console.log = (...args: any[]) => logs.push(args.join(" "))
110
-
111
- try {
112
- await runTestEffect(
113
- base({
114
- subcommand: "get",
115
- args: [],
116
- silent: false,
117
- }),
118
- )
119
- expect(logs[0]).toBe("main")
120
- } finally {
121
- console.log = originalLog
122
- }
123
- })
124
-
125
- test("throws error if no base branch configured", async () => {
126
- // Create a feature branch
127
- await Bun.spawn(["git", "checkout", "-b", "feature"], {
128
- cwd: testDir,
129
- stdout: "pipe",
130
- stderr: "pipe",
131
- }).exited
132
-
133
- // Create agency.json without base branch
134
- await writeAgencyMetadata(testDir, {
135
- version: 1,
136
- template: "test",
137
- injectedFiles: [],
138
- createdAt: new Date().toISOString(),
139
- } as any)
140
-
141
- await expect(
142
- runTestEffect(
143
- base({
144
- subcommand: "get",
145
- args: [],
146
- silent: true,
147
- }),
148
- ),
149
- ).rejects.toThrow("No base branch configured")
150
- })
151
- })
152
-
153
- describe("base command", () => {
154
- test("requires subcommand", async () => {
155
- await expect(
156
- runTestEffect(base({ args: [], silent: true })),
157
- ).rejects.toThrow("Subcommand is required")
158
- })
159
-
160
- test("handles 'set' subcommand", async () => {
161
- await Bun.spawn(["git", "checkout", "-b", "feature"], {
162
- cwd: testDir,
163
- stdout: "pipe",
164
- stderr: "pipe",
165
- }).exited
166
-
167
- await writeAgencyMetadata(testDir, {
168
- version: 1,
169
- template: "test",
170
- injectedFiles: [],
171
- createdAt: new Date().toISOString(),
172
- } as any)
173
-
174
- await runTestEffect(
175
- base({
176
- subcommand: "set",
177
- args: ["main"],
178
- silent: true,
179
- }),
180
- )
181
-
182
- const savedBase = await getBaseBranchFromMetadata(testDir)
183
- expect(savedBase).toBe("main")
184
- })
185
-
186
- test("throws error for unknown subcommand", async () => {
187
- await expect(
188
- runTestEffect(
189
- base({
190
- subcommand: "unknown",
191
- args: [],
192
- silent: true,
193
- }),
194
- ),
195
- ).rejects.toThrow("Unknown subcommand")
196
- })
197
- })
198
- })