@markjaquith/agency 2.7.0 → 2.7.1

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/cli.ts CHANGED
@@ -240,7 +240,9 @@ const commands: Record<string, Command> = {
240
240
  return
241
241
  }
242
242
  if (args.length > 1) {
243
- throw new Error("Usage: agency work [<directory> | --epic <epic-id>]")
243
+ throw new Error(
244
+ "Usage: agency work [<directory-or-task-id> | --epic <epic-id>]",
245
+ )
244
246
  }
245
247
 
246
248
  await runCommand(
@@ -301,7 +303,7 @@ Commands:
301
303
  phase <subcommand> Manage task phases
302
304
  archive <type> Archive a work item
303
305
  task <subcommand> Manage tasks
304
- work [directory] Work on an epic, task, or phase
306
+ work [directory|task] Work on an epic, task, or phase
305
307
  pr create Create a pull request for an execution unit
306
308
  repo <subcommand> Manage workbase repositories
307
309
  status Show status for the current workbase
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "2.7.0",
3
+ "version": "2.7.1",
4
4
  "description": "Manage agentic work across repositories with durable workbases",
5
5
  "keywords": [
6
6
  "agents",
@@ -46,12 +46,14 @@ interface HarnessOptions {
46
46
  readonly phaseRecords?: readonly any[]
47
47
  readonly outsideWorkbase?: boolean
48
48
  readonly registeredWorkbases?: readonly string[]
49
+ readonly existingDirectories?: readonly string[]
49
50
  }
50
51
 
51
52
  const createHarness = (options: HarnessOptions = {}) => {
52
53
  const events: string[] = []
53
54
  const probes: string[] = []
54
55
  const statusUpdates: string[] = []
56
+ const shownTasks: string[] = []
55
57
  const progressUpdates: string[] = []
56
58
  const launches: Array<{
57
59
  cli: string
@@ -95,14 +97,16 @@ const createHarness = (options: HarnessOptions = {}) => {
95
97
  list: () => Effect.succeed(options.epicRecords ?? []),
96
98
  }
97
99
  const tasks = {
98
- show: (id: string) =>
99
- Effect.succeed({
100
+ show: (id: string) => {
101
+ shownTasks.push(id)
102
+ return Effect.succeed({
100
103
  id,
101
104
  path: `/workbase/tasks/${id}/TASK.md`,
102
105
  data: options.multiPhaseTasks?.includes(id)
103
106
  ? { phases: [] }
104
107
  : { repo: "agency", branch: `task/${id}`, base: "main" },
105
- }),
108
+ })
109
+ },
106
110
  list: () => Effect.succeed(options.taskRecords ?? []),
107
111
  setStatus: (id: string, status: string) => {
108
112
  statusUpdates.push(`task:${id}:${status}`)
@@ -124,7 +128,8 @@ const createHarness = (options: HarnessOptions = {}) => {
124
128
  },
125
129
  }
126
130
  const fs = {
127
- isDirectory: () => Effect.succeed(true),
131
+ isDirectory: (path: string) =>
132
+ Effect.succeed(options.existingDirectories?.includes(path) ?? true),
128
133
  runCommand: (args: readonly string[]) => {
129
134
  const cli = args[1] as "opencode" | "claude"
130
135
  events.push(`probe:${cli}`)
@@ -169,6 +174,7 @@ const createHarness = (options: HarnessOptions = {}) => {
169
174
  launches,
170
175
  materializeOptions,
171
176
  statusUpdates,
177
+ shownTasks,
172
178
  progressUpdates,
173
179
  run,
174
180
  }
@@ -192,6 +198,34 @@ describe("work command", () => {
192
198
  })
193
199
  })
194
200
 
201
+ test("resolves an existing positional path before treating it as a task ID", async () => {
202
+ const harness = createHarness({
203
+ existingDirectories: ["/workbase/tasks/delivery"],
204
+ })
205
+
206
+ await harness.run({
207
+ cwd: "/workbase/tasks/delivery",
208
+ directory: ".",
209
+ opencode: true,
210
+ })
211
+
212
+ expect(harness.shownTasks).toEqual(["delivery"])
213
+ expect(harness.launches[0]?.cwd).toBe("/workbase/tasks/delivery")
214
+ })
215
+
216
+ test("treats a positional value as a task ID when it is not a directory", async () => {
217
+ const harness = createHarness({ existingDirectories: [] })
218
+
219
+ await harness.run({
220
+ cwd: "/workbase",
221
+ directory: "delivery",
222
+ opencode: true,
223
+ })
224
+
225
+ expect(harness.shownTasks).toEqual(["delivery"])
226
+ expect(harness.launches[0]?.cwd).toBe("/workbase/tasks/delivery")
227
+ })
228
+
195
229
  test("launches a multi-phase task agent without materializing", async () => {
196
230
  const harness = createHarness({ multiPhaseTasks: ["delivery"] })
197
231
 
@@ -77,12 +77,13 @@ export const work = (
77
77
  const phases = yield* PhaseService
78
78
  const { log, verboseLog } = createLoggers(options)
79
79
  const cwd = options.cwd ?? process.cwd()
80
- const startPath = options.directory ? resolve(cwd, options.directory) : cwd
81
- if (options.directory && !(yield* fs.isDirectory(startPath))) {
82
- return yield* Effect.fail(
83
- new Error(`Work directory does not exist: ${startPath}`),
84
- )
85
- }
80
+ const directoryPath = options.directory
81
+ ? resolve(cwd, options.directory)
82
+ : undefined
83
+ const isDirectory = directoryPath
84
+ ? yield* fs.isDirectory(directoryPath)
85
+ : false
86
+ const startPath = isDirectory && directoryPath ? directoryPath : cwd
86
87
  const root = yield* resolveWorkbase(startPath, log, pickBase)
87
88
  if (!root) return
88
89
 
@@ -108,7 +109,15 @@ export const work = (
108
109
  multiPhase: "phases" in task.data,
109
110
  }
110
111
  }
111
- } else if (options.directory) {
112
+ } else if (options.directory && !isDirectory) {
113
+ const task = yield* tasks.show(options.directory, root)
114
+ target = {
115
+ kind: "task",
116
+ taskId: task.id,
117
+ path: task.path,
118
+ multiPhase: "phases" in task.data,
119
+ }
120
+ } else if (directoryPath) {
112
121
  const path = relative(root, startPath)
113
122
  const parts =
114
123
  !path || isAbsolute(path) || path.startsWith(`..${sep}`)
@@ -226,11 +235,12 @@ export const work = (
226
235
  })
227
236
 
228
237
  export const help = `
229
- Usage: agency work [<directory> | --epic <epic-id>]
238
+ Usage: agency work [<directory-or-task-id> | --epic <epic-id>]
230
239
 
231
240
  Launch an agent for an epic, task, or phase. With no directory, select one
232
- with fzf. Use '.' for the current directory. Outside a workbase, select a
233
- registered workbase first.
241
+ with fzf. A positional argument resolves as a directory first, then as a task
242
+ ID. Use '.' for the current directory. Outside a workbase, select a registered
243
+ workbase first.
234
244
 
235
245
  Options:
236
246
  --epic <id> Work on an epic