@markjaquith/agency 2.31.0 → 2.32.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 +50 -38
- package/cli.ts +2 -1
- package/package.json +5 -2
- package/skills/agency/SKILL.md +7 -6
- package/skills/agency/references/commands.md +17 -14
- package/skills/agency/references/contracts.md +4 -5
- package/skills/agency/references/recipes.md +13 -10
- package/src/cli-parser.test.ts +12 -2
- package/src/cli-parser.ts +5 -2
- package/src/cli.test.ts +34 -10
- package/src/commands/doctor.test.ts +11 -1
- package/src/commands/phase.ts +1 -1
- package/src/commands/task.test.ts +34 -1
- package/src/commands/task.ts +16 -20
- package/src/commands/work.test.ts +65 -65
- package/src/commands/work.ts +43 -35
- package/src/opentui.d.ts +1 -0
- package/src/services/DoctorService.ts +22 -0
- package/src/services/IntegrationService.test.ts +2 -1
- package/src/services/PhaseService.ts +2 -2
- package/src/services/ReadinessService.test.ts +54 -0
- package/src/services/ReadinessService.ts +39 -2
- package/src/services/TaskPhaseService.test.ts +23 -9
- package/src/services/TaskService.ts +2 -2
- package/src/utils/chooser.test.ts +49 -12
- package/src/utils/chooser.ts +20 -37
- package/src/utils/interactive-loader.ts +6 -0
- package/src/utils/interactive.test.tsx +258 -0
- package/src/utils/interactive.tsx +290 -0
- package/src/workbase/AGENTS.md +5 -5
- package/src/workbase/runner-command.test.ts +30 -4
- package/src/workbase/runner-command.ts +21 -6
- package/src/workbase/schemas.test.ts +4 -2
- package/src/workbase/schemas.ts +4 -0
package/README.md
CHANGED
|
@@ -183,7 +183,8 @@ OpenCode and Claude Code are built-in runner presets. Select either preset or a
|
|
|
183
183
|
configured runner with `agency work --runner <name>`. A launch is fresh unless
|
|
184
184
|
`AGENCY_SESSION_ID` is already set; resumed launches use the runner's
|
|
185
185
|
`resumeCommand` when configured. The built-in presets use `--continue` only for
|
|
186
|
-
resumed launches.
|
|
186
|
+
resumed launches. By default Agency opens the runner without a prompt. `--auto`
|
|
187
|
+
uses its autonomous command and sends the generated task, phase, or epic prompt.
|
|
187
188
|
|
|
188
189
|
Custom runners are direct argv commands, never shell snippets:
|
|
189
190
|
|
|
@@ -192,8 +193,10 @@ Custom runners are direct argv commands, never shell snippets:
|
|
|
192
193
|
"version": 2,
|
|
193
194
|
"runners": {
|
|
194
195
|
"custom": {
|
|
195
|
-
"command": ["my-agent"
|
|
196
|
-
"
|
|
196
|
+
"command": ["my-agent"],
|
|
197
|
+
"autoCommand": ["my-agent", "--prompt", "{prompt}"],
|
|
198
|
+
"resumeCommand": ["my-agent", "resume", "{sessionId}"],
|
|
199
|
+
"autoResumeCommand": ["my-agent", "resume", "{sessionId}", "{prompt}"],
|
|
197
200
|
"environment": { "MY_AGENT_TARGET": "{target}" }
|
|
198
201
|
}
|
|
199
202
|
}
|
|
@@ -202,24 +205,32 @@ Custom runners are direct argv commands, never shell snippets:
|
|
|
202
205
|
|
|
203
206
|
Available placeholders are `{prompt}`, `{workbase}`, `{target}`, `{task}`,
|
|
204
207
|
`{phase}`, `{claimant}`, `{sessionId}`, and `{claimRevision}`. Task and phase
|
|
205
|
-
placeholders are empty when they do not apply.
|
|
206
|
-
the fresh command is also used
|
|
208
|
+
placeholders are empty when they do not apply. `{prompt}` is empty unless
|
|
209
|
+
`--auto` is set. If `resumeCommand` is omitted, the fresh command is also used
|
|
210
|
+
for resumed sessions. If `autoResumeCommand` is omitted, `autoCommand` is used;
|
|
211
|
+
configured runners without `autoCommand` reject `--auto`.
|
|
207
212
|
|
|
208
213
|
Every runner receives the same `AGENCY_RUNNER`, `AGENCY_CLAIMANT`,
|
|
209
214
|
`AGENCY_SESSION_ID`, `AGENCY_CLAIM_REVISION`, `AGENCY_WORKBASE`, `AGENCY_TARGET`,
|
|
210
215
|
`AGENCY_TASK_ID`, `AGENCY_PHASE_ID`, and `AGENCY_PROMPT` environment. Configured
|
|
211
216
|
environment is added without overriding these normalized values.
|
|
217
|
+
`AGENCY_CLAIM_REVISION` is empty for local `agency work` launches.
|
|
218
|
+
`AGENCY_PROMPT` is empty unless `--auto` is set.
|
|
212
219
|
The `opencode` runner additionally receives `OPENCODE_CONFIG` for the workbase's
|
|
213
220
|
managed integration and `OPENCODE_CONFIG_CONTENT` with runtime-only,
|
|
214
221
|
workbase-scoped access and edit rules. These values keep the Git-synced config
|
|
215
222
|
portable while providing whole-workbase read access at every launch location.
|
|
223
|
+
`AGENCY_CLAIM_REVISION` is empty for local `agency work` launches.
|
|
224
|
+
`AGENCY_PROMPT` is empty unless `--auto` is set.
|
|
216
225
|
`--print-command` prints the exact cwd and argv plus non-secret environment keys
|
|
217
226
|
without launching the runner.
|
|
218
227
|
|
|
219
228
|
### Custom Chooser Command
|
|
220
229
|
|
|
221
|
-
Interactive selection uses
|
|
222
|
-
|
|
230
|
+
Interactive selection uses an OpenTUI Solid split footer by default. Type to
|
|
231
|
+
fuzzy-filter choices, use arrow keys or Ctrl-N/Ctrl-P to move, press Enter to
|
|
232
|
+
select, and press Escape or Ctrl-C to cancel. To use an external chooser
|
|
233
|
+
instead, configure an argv command in `agency.json`:
|
|
223
234
|
|
|
224
235
|
```json
|
|
225
236
|
{
|
|
@@ -231,13 +242,14 @@ external chooser, configure an argv command in `agency.json`:
|
|
|
231
242
|
Agency writes one `key<TAB>label` record per choice to the command's stdin. The
|
|
232
243
|
command must write the selected opaque key or selected record to stdout; commands
|
|
233
244
|
such as `["gum", "filter"]` therefore work without wrappers. Exit codes 1 and
|
|
234
|
-
130
|
|
235
|
-
|
|
245
|
+
130 or empty stdout cancel external selection. Other nonzero exits and unknown
|
|
246
|
+
keys are errors.
|
|
236
247
|
|
|
237
|
-
Selectors are opened only when stdin and
|
|
238
|
-
`--no-input` nor JSON output is active.
|
|
239
|
-
terminal, `TERM` is not `dumb`, and `NO_COLOR` is unset
|
|
240
|
-
plain labels without ANSI styling or icon-font
|
|
248
|
+
Selectors are opened only when stdin and stdout are terminals and neither
|
|
249
|
+
`--no-input` nor JSON output is active. External chooser labels use color only
|
|
250
|
+
when stdout is a terminal, `TERM` is not `dumb`, and `NO_COLOR` is unset. The
|
|
251
|
+
native OpenTUI selector uses plain labels without ANSI styling or icon-font
|
|
252
|
+
dependencies.
|
|
241
253
|
|
|
242
254
|
## Frontmatter
|
|
243
255
|
|
|
@@ -500,8 +512,9 @@ back-reference.
|
|
|
500
512
|
|
|
501
513
|
### Tasks
|
|
502
514
|
|
|
503
|
-
Create a task interactively
|
|
504
|
-
|
|
515
|
+
Create a task interactively with the OpenTUI Solid footer. When exactly one
|
|
516
|
+
repository is available, Agency selects it without presenting a redundant
|
|
517
|
+
choice. This command requires a TTY and fails with `--no-input`:
|
|
505
518
|
|
|
506
519
|
```text
|
|
507
520
|
agency task new [id]
|
|
@@ -606,12 +619,13 @@ writing anything.
|
|
|
606
619
|
|
|
607
620
|
Single-phase tasks and phases store status in YAML. New execution units start
|
|
608
621
|
`open`, and `agency work` marks the selected execution unit `working` immediately
|
|
609
|
-
before launch.
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
terminal
|
|
614
|
-
work before changing its
|
|
622
|
+
before launch. Running `agency work` again can relaunch unclaimed `working` work.
|
|
623
|
+
Use explicit claims only when an external orchestrator needs coordinated
|
|
624
|
+
ownership. The interactive work selector displays status markers before
|
|
625
|
+
execution units. Existing working and delegated work may be released to `open`
|
|
626
|
+
or assigned a terminal outcome. Done and dropped work are terminal and may only
|
|
627
|
+
remain unchanged or transition to open; reopen terminal work before changing its
|
|
628
|
+
outcome.
|
|
615
629
|
|
|
616
630
|
`delegated` remains readable for existing workbases but cannot be newly assigned.
|
|
617
631
|
Delegation is now explicit: the claimant identifies the orchestrator and the
|
|
@@ -645,11 +659,10 @@ frontmatter. Conflicts return the current revision and complete ownership record
|
|
|
645
659
|
in the machine error envelope rather than overwriting it. Expired claims may be
|
|
646
660
|
replaced with a revision-guarded claim.
|
|
647
661
|
|
|
648
|
-
`agency work`
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
runner environment documented above for a later release or finish operation.
|
|
662
|
+
`agency work` does not claim execution units. It refuses active explicit claims,
|
|
663
|
+
marks open execution work `working`, and launches the runner. External
|
|
664
|
+
orchestrators use `agency claim`, launch and monitor their runner separately, and
|
|
665
|
+
later call `agency release` or `agency finish`.
|
|
653
666
|
|
|
654
667
|
### Archive
|
|
655
668
|
|
|
@@ -676,21 +689,21 @@ for restoration. Archived IDs are reserved until restored.
|
|
|
676
689
|
### Work and Pull Requests
|
|
677
690
|
|
|
678
691
|
```text
|
|
679
|
-
agency work [<directory> | --epic <epic-id>] [--runner <name>] [--print-command]
|
|
692
|
+
agency work [<directory> | --epic <epic-id>] [--runner <name>] [--auto] [--print-command]
|
|
680
693
|
agency work prepare [target] [--dry-run] [--json]
|
|
681
694
|
agency worktree <list|inspect|prepare|remove|rebuild|repair>
|
|
682
695
|
agency pr create <task-id> [phase-id] [--draft] [--json]
|
|
683
696
|
```
|
|
684
697
|
|
|
685
|
-
`agency work` presents the full hierarchy in
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
choices and asks for an explicit directory.
|
|
698
|
+
`agency work` presents the full hierarchy in the native OpenTUI selector or the
|
|
699
|
+
configured external chooser. Pass a directory, including `.` for the current
|
|
700
|
+
directory, to infer its epic, task, or phase. Outside a workbase, Agency first
|
|
701
|
+
presents the registered workbases, then the selected workbase's hierarchy.
|
|
690
702
|
|
|
691
703
|
OpenCode is the default runner, with automatic Claude fallback when neither is
|
|
692
704
|
explicitly selected. `--opencode` and `--claude` remain aliases for requiring
|
|
693
|
-
their built-in presets.
|
|
705
|
+
their built-in presets. Launches are interactive and promptless by default; use
|
|
706
|
+
`--auto` to send Agency's generated context prompt.
|
|
694
707
|
|
|
695
708
|
`agency work prepare` resolves an execution unit and creates or reuses its
|
|
696
709
|
writable and reference worktrees without launching an agent or changing status.
|
|
@@ -707,11 +720,10 @@ conflicting worktrees. Repair is deliberately conservative: it repairs safe Git
|
|
|
707
720
|
registration issues and materializes missing checkouts, but never switches a
|
|
708
721
|
branch, resets a commit, or discards uncommitted work.
|
|
709
722
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
the hierarchy chooser.
|
|
723
|
+
Agency launches every agent beside its epic or task document. Single-phase tasks
|
|
724
|
+
and phases first fetch repositories and create or reuse worktrees under `code/`,
|
|
725
|
+
then launch the execution agent from the task directory with absolute context
|
|
726
|
+
paths. An explicit directory or `--epic` target bypasses the hierarchy chooser.
|
|
715
727
|
|
|
716
728
|
Each writable `(repo, branch)` pair may belong to only one task or phase. Agency
|
|
717
729
|
validation reports duplicate ownership, and `agency work` checks Git's worktree
|
package/cli.ts
CHANGED
|
@@ -478,6 +478,7 @@ const commands: Record<string, Command> = {
|
|
|
478
478
|
opencode: options.opencode,
|
|
479
479
|
claude: options.claude,
|
|
480
480
|
runner: options.runner,
|
|
481
|
+
auto: options.auto,
|
|
481
482
|
printCommand: options["print-command"],
|
|
482
483
|
force: options.force,
|
|
483
484
|
inputAllowed: options.inputAllowed,
|
|
@@ -720,7 +721,7 @@ try {
|
|
|
720
721
|
const inputAllowed =
|
|
721
722
|
!values.json &&
|
|
722
723
|
!values["no-input"] &&
|
|
723
|
-
Boolean(process.stdin.isTTY && process.
|
|
724
|
+
Boolean(process.stdin.isTTY && process.stdout.isTTY)
|
|
724
725
|
const cwd = await resolveInvocationCwd(commandName, values)
|
|
725
726
|
if (values.json || (values.jsonl && values.help)) {
|
|
726
727
|
const result = await collectCommandResult(() =>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markjaquith/agency",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.32.0",
|
|
4
4
|
"description": "Manage agentic work across repositories with durable workbases",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"tag": "latest"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
|
-
"test": "find src -name '*.test.ts' -print0 | xargs -0 -n 1 -P 4 bun test",
|
|
61
|
+
"test": "find src \\( -name '*.test.ts' -o -name '*.test.tsx' \\) -print0 | xargs -0 -n 1 -P 4 bun test",
|
|
62
62
|
"format": "oxfmt",
|
|
63
63
|
"format:check": "oxfmt --check",
|
|
64
64
|
"knip": "knip --production",
|
|
@@ -68,7 +68,10 @@
|
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@effect/schema": "^0.75.5",
|
|
71
|
+
"@opentui/core": "0.4.5",
|
|
72
|
+
"@opentui/solid": "0.4.5",
|
|
71
73
|
"effect": "^3.19.15",
|
|
74
|
+
"solid-js": "1.9.12",
|
|
72
75
|
"yaml": "^2.9.0"
|
|
73
76
|
},
|
|
74
77
|
"devDependencies": {
|
package/skills/agency/SKILL.md
CHANGED
|
@@ -102,8 +102,8 @@ independently meaningful tasks need coordination.
|
|
|
102
102
|
2. Confirm `target`, `graph.readiness`, `authority`, `workspace`, and `validation`.
|
|
103
103
|
3. Read the returned task and phase document paths for prose requirements.
|
|
104
104
|
4. Stop on validation errors, dependency blockers, an unexpected writable
|
|
105
|
-
repository, or a conflicting active owner.
|
|
106
|
-
|
|
105
|
+
repository, or a conflicting active owner. A local `agency work` launch has
|
|
106
|
+
no claim; an externally assigned agent must own the active claim.
|
|
107
107
|
|
|
108
108
|
### Work
|
|
109
109
|
|
|
@@ -129,9 +129,10 @@ independently meaningful tasks need coordination.
|
|
|
129
129
|
|
|
130
130
|
`agency work` is a human/orchestrator launch flow. It first reconciles managed
|
|
131
131
|
integration files, then selects work and checks readiness. For an execution unit,
|
|
132
|
-
it materializes managed checkouts,
|
|
132
|
+
it materializes managed checkouts, marks it working without a claim, and starts
|
|
133
133
|
the selected built-in or configured runner. Epic and multi-phase task launches
|
|
134
|
-
start in orchestration context without materializing
|
|
134
|
+
start in orchestration context without materializing execution work. The runner
|
|
135
|
+
opens without a prompt unless the human explicitly selects `--auto`.
|
|
135
136
|
OpenCode launches expose the managed config file and inject runtime-only access
|
|
136
137
|
rules scoped to the workbase. These grant visibility from orchestration
|
|
137
138
|
directories and nested execution checkouts while denying direct edits outside
|
|
@@ -140,8 +141,8 @@ or replace write authority from `agency context`.
|
|
|
140
141
|
|
|
141
142
|
An agent already running in an Agency checkout must not call `agency work` to
|
|
142
143
|
start itself again. It should inspect context, perform the assigned work, and
|
|
143
|
-
finish or
|
|
144
|
-
when the user explicitly asks.
|
|
144
|
+
finish its claim or update unclaimed status directly. Launch a nested or
|
|
145
|
+
replacement agent only when the user explicitly asks.
|
|
145
146
|
|
|
146
147
|
Use [`references/recipes.md`](references/recipes.md) for human setup, agent
|
|
147
148
|
execution, claim, PR, conversion, and recovery workflows. Use
|
|
@@ -109,8 +109,9 @@ agency phase dependency <add|remove> <task-id> <phase-id> <dependency-id>
|
|
|
109
109
|
[--if-revision <hash>] [--json]
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
`task new`
|
|
113
|
-
|
|
112
|
+
`task new` uses the OpenTUI Solid footer and requires a TTY. A sole repository is
|
|
113
|
+
selected automatically. Agents and scripts use noninteractive `task create`.
|
|
114
|
+
Mutation commands that accept `--if-revision`
|
|
114
115
|
return a revision conflict instead of overwriting changed documents.
|
|
115
116
|
|
|
116
117
|
## Ownership And Lifecycle
|
|
@@ -143,7 +144,7 @@ worktrees when needed, preserve branches, and retain lifecycle provenance.
|
|
|
143
144
|
|
|
144
145
|
```text
|
|
145
146
|
agency work [<directory-or-task-id> | --epic <id> | --task <id> [--phase <id>]]
|
|
146
|
-
[--runner <name> | --opencode | --claude] [--print-command] [--force]
|
|
147
|
+
[--runner <name> | --opencode | --claude] [--auto] [--print-command] [--force]
|
|
147
148
|
agency work prepare [target] [--dry-run] [--json]
|
|
148
149
|
agency worktree list [--json]
|
|
149
150
|
agency worktree inspect <task-id> [phase-id] [--json]
|
|
@@ -154,17 +155,19 @@ agency worktree repair <task-id> [phase-id] [--dry-run] [--json]
|
|
|
154
155
|
agency pr create <task-id> [phase-id] [--draft] [--force] [--json]
|
|
155
156
|
```
|
|
156
157
|
|
|
157
|
-
`work` is a launch flow, not an active-agent step. It synchronizes managed
|
|
158
|
-
integration files before launch. Execution targets are materialized and
|
|
159
|
-
epics and multi-phase tasks launch in orchestration
|
|
158
|
+
`work` is a local launch flow, not an active-agent step. It synchronizes managed
|
|
159
|
+
integration files before launch. Execution targets are materialized and marked
|
|
160
|
+
working without a claim; epics and multi-phase tasks launch in orchestration
|
|
161
|
+
context without those steps. Unclaimed working targets can be launched again.
|
|
162
|
+
The runner opens without a generated prompt unless `--auto` is set.
|
|
160
163
|
The OpenCode runner receives a runtime managed-config path plus absolute access
|
|
161
164
|
and edit rules scoped to the workbase; none are persisted, and `context` still
|
|
162
165
|
defines write authority.
|
|
163
|
-
`--print-command`
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
`--print-command` materializes execution targets but does not launch or change
|
|
167
|
+
status. `work prepare` materializes without launching or changing status.
|
|
168
|
+
Destructive remove and rebuild operations refuse dirty or conflicting state.
|
|
169
|
+
Conservative repair may correct registration while preserving dirty files, but
|
|
170
|
+
never discards changes.
|
|
168
171
|
`pr create` materializes a missing workspace, requires the resulting writable
|
|
169
172
|
checkout to be clean, pushes the declared branch, invokes the configured delivery
|
|
170
173
|
provider or falls back to `gh pr create --fill`, and records the returned pull
|
|
@@ -178,6 +181,6 @@ perform cwd inference elsewhere. Targeted commands accept `--epic`, `--task`,
|
|
|
178
181
|
and, with a task, `--phase`. `--json`, `--no-input`, or non-TTY execution disables
|
|
179
182
|
prompts; provide all selectors and required inputs explicitly.
|
|
180
183
|
|
|
181
|
-
Selectors are only a resolution mechanism
|
|
182
|
-
|
|
183
|
-
|
|
184
|
+
Selectors are only a resolution mechanism. There is no `assign` command. Use
|
|
185
|
+
`work` for a local human launch, or use `claim` and manage an external runner
|
|
186
|
+
separately.
|
|
@@ -299,11 +299,10 @@ node set.
|
|
|
299
299
|
and `claim` does not enforce dependency readiness. Inspect readiness, then
|
|
300
300
|
claim with the observed revision and handle conflicts.
|
|
301
301
|
- There is no `assign` command, remote queue, scheduler, heartbeat, claim renewal,
|
|
302
|
-
runner monitor, or cancellation API.
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
themselves.
|
|
302
|
+
runner monitor, or cancellation API. `work` launches one local built-in or
|
|
303
|
+
configured runner without a claim; it is a process-launching, non-JSON flow
|
|
304
|
+
rather than a machine assignment API. External orchestrators claim with
|
|
305
|
+
claimant and runner IDs, then manage their runner themselves.
|
|
307
306
|
- Agency does not edit code, create commits, run repository checks, wait for PR
|
|
308
307
|
checks, merge PRs, or verify that a requested completion condition is true.
|
|
309
308
|
`finish` records the caller's asserted outcome after ownership checks.
|
|
@@ -88,10 +88,13 @@ agency work tasks/checkout/phases/api
|
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
`agency work` is intentionally last: it synchronizes managed integration files,
|
|
91
|
-
checks readiness, materializes worktrees,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
checks readiness, materializes worktrees, marks this execution unit working
|
|
92
|
+
without a claim, and launches the runner. Run it again to relaunch unclaimed
|
|
93
|
+
working work. The runner opens without a prompt by default; add `--auto` only
|
|
94
|
+
when the generated context prompt should start autonomous execution.
|
|
95
|
+
OpenCode receives whole-workbase read visibility through runtime-only config and
|
|
96
|
+
permission values; this does not expand the writable checkout reported by
|
|
97
|
+
`agency context`.
|
|
95
98
|
|
|
96
99
|
## Active Agent: Execute Assigned Work
|
|
97
100
|
|
|
@@ -99,10 +102,9 @@ expand the writable checkout reported by `agency context`.
|
|
|
99
102
|
agency context . --json
|
|
100
103
|
```
|
|
101
104
|
|
|
102
|
-
1. Verify context reports the expected execution target, valid structure,
|
|
103
|
-
current checkout as `authority.writable.checkoutPath
|
|
104
|
-
|
|
105
|
-
claims; the owned unit's `working` status blocker is expected.
|
|
105
|
+
1. Verify context reports the expected execution target, valid structure, and
|
|
106
|
+
the current checkout as `authority.writable.checkoutPath`. Reject dependency
|
|
107
|
+
or validation blockers and conflicting active claims.
|
|
106
108
|
2. Read `TASK.md`, and `PHASE.md` for phase work.
|
|
107
109
|
3. Implement only in the writable checkout; treat all reference checkouts as
|
|
108
110
|
read-only.
|
|
@@ -110,7 +112,8 @@ agency context . --json
|
|
|
110
112
|
5. Review and commit the diff.
|
|
111
113
|
6. If requested, run `agency validate`, then
|
|
112
114
|
`agency pr create <task-id> [phase-id]`.
|
|
113
|
-
7. Finish
|
|
115
|
+
7. Finish an active claim after its completion condition is true; otherwise set
|
|
116
|
+
the task or phase status directly.
|
|
114
117
|
|
|
115
118
|
Never invoke `agency work` merely because an execution checkout already exists;
|
|
116
119
|
that would start another agent rather than continue the current assignment.
|
|
@@ -145,7 +148,7 @@ worktrees manually.
|
|
|
145
148
|
## Assign A Runner
|
|
146
149
|
|
|
147
150
|
For a human-operated local launch, Agency combines readiness checks, prepare,
|
|
148
|
-
|
|
151
|
+
status mutation, and runner launch without creating a claim:
|
|
149
152
|
|
|
150
153
|
```bash
|
|
151
154
|
agency work tasks/checkout/phases/ui --runner opencode
|
package/src/cli-parser.test.ts
CHANGED
|
@@ -453,11 +453,18 @@ describe("strict CLI parsing", () => {
|
|
|
453
453
|
|
|
454
454
|
test("accepts runner selection and command inspection for work", () => {
|
|
455
455
|
expect(
|
|
456
|
-
parseCli([
|
|
456
|
+
parseCli([
|
|
457
|
+
"work",
|
|
458
|
+
"example",
|
|
459
|
+
"--runner",
|
|
460
|
+
"custom",
|
|
461
|
+
"--auto",
|
|
462
|
+
"--print-command",
|
|
463
|
+
]),
|
|
457
464
|
).toMatchObject({
|
|
458
465
|
commandName: "work",
|
|
459
466
|
args: ["example"],
|
|
460
|
-
values: { runner: "custom", "print-command": true },
|
|
467
|
+
values: { runner: "custom", auto: true, "print-command": true },
|
|
461
468
|
})
|
|
462
469
|
expect(() =>
|
|
463
470
|
parseCli(["work", "example", "--runner", "custom", "--claude"]),
|
|
@@ -465,6 +472,9 @@ describe("strict CLI parsing", () => {
|
|
|
465
472
|
expect(() => parseCli(["work", "prepare", "--print-command"])).toThrow(
|
|
466
473
|
"cannot be combined",
|
|
467
474
|
)
|
|
475
|
+
expect(() => parseCli(["work", "prepare", "--auto"])).toThrow(
|
|
476
|
+
"cannot be combined",
|
|
477
|
+
)
|
|
468
478
|
})
|
|
469
479
|
|
|
470
480
|
test("accepts repeatable graph filters and rejects output conflicts", () => {
|
package/src/cli-parser.ts
CHANGED
|
@@ -740,13 +740,14 @@ const commands = {
|
|
|
740
740
|
},
|
|
741
741
|
work: {
|
|
742
742
|
usage:
|
|
743
|
-
"agency work [<directory-or-task-id> | --epic <epic-id>] [--runner <name>] | agency work prepare [target] [--dry-run] [--json]",
|
|
743
|
+
"agency work [<directory-or-task-id> | --epic <epic-id>] [--runner <name>] [--auto] | agency work prepare [target] [--dry-run] [--json]",
|
|
744
744
|
options: {
|
|
745
745
|
...commonOptions,
|
|
746
746
|
...entitySelectorOptions,
|
|
747
747
|
json: { type: "boolean" },
|
|
748
748
|
"dry-run": { type: "boolean" },
|
|
749
749
|
runner: { type: "string" },
|
|
750
|
+
auto: { type: "boolean" },
|
|
750
751
|
"print-command": { type: "boolean" },
|
|
751
752
|
opencode: { type: "boolean" },
|
|
752
753
|
claude: { type: "boolean" },
|
|
@@ -754,7 +755,7 @@ const commands = {
|
|
|
754
755
|
},
|
|
755
756
|
command: {
|
|
756
757
|
usage:
|
|
757
|
-
"agency work [<directory-or-task-id> | --epic <epic-id>] [--runner <name>] | agency work prepare [target] [--dry-run] [--json]",
|
|
758
|
+
"agency work [<directory-or-task-id> | --epic <epic-id>] [--runner <name>] [--auto] | agency work prepare [target] [--dry-run] [--json]",
|
|
758
759
|
minArgs: 0,
|
|
759
760
|
maxArgs: 2,
|
|
760
761
|
options: [
|
|
@@ -764,6 +765,7 @@ const commands = {
|
|
|
764
765
|
"task",
|
|
765
766
|
"phase",
|
|
766
767
|
"runner",
|
|
768
|
+
"auto",
|
|
767
769
|
"print-command",
|
|
768
770
|
"opencode",
|
|
769
771
|
"claude",
|
|
@@ -1361,6 +1363,7 @@ export function parseCli(args: readonly string[]): ParsedCli {
|
|
|
1361
1363
|
parsed.values.opencode ||
|
|
1362
1364
|
parsed.values.claude ||
|
|
1363
1365
|
parsed.values.runner ||
|
|
1366
|
+
parsed.values.auto ||
|
|
1364
1367
|
parsed.values["print-command"] ||
|
|
1365
1368
|
parsed.values.force))
|
|
1366
1369
|
) {
|
package/src/cli.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { afterAll, afterEach, describe, expect, test } from "bun:test"
|
|
2
2
|
import { access, mkdir, realpath } from "node:fs/promises"
|
|
3
|
-
import { join } from "node:path"
|
|
3
|
+
import { join, relative } from "node:path"
|
|
4
4
|
import errorFixture from "../fixtures/protocol/error.json"
|
|
5
5
|
import successFixture from "../fixtures/protocol/success.json"
|
|
6
6
|
import { cleanupTempDir, createTempDir } from "./test-utils"
|
|
@@ -92,8 +92,15 @@ async function startGitDaemon(basePath: string) {
|
|
|
92
92
|
|
|
93
93
|
describe("CLI", () => {
|
|
94
94
|
const tempDirs: string[] = []
|
|
95
|
+
const daemons: Bun.Subprocess[] = []
|
|
95
96
|
|
|
96
97
|
afterEach(async () => {
|
|
98
|
+
await Promise.all(
|
|
99
|
+
daemons.splice(0).map(async (daemon) => {
|
|
100
|
+
daemon.kill()
|
|
101
|
+
await daemon.exited
|
|
102
|
+
}),
|
|
103
|
+
)
|
|
97
104
|
await Promise.all(tempDirs.splice(0).map(cleanupTempDir))
|
|
98
105
|
})
|
|
99
106
|
|
|
@@ -445,7 +452,7 @@ describe("CLI", () => {
|
|
|
445
452
|
|
|
446
453
|
const after = await runCli(["status", "--silent"], root)
|
|
447
454
|
expect(after).toEqual({ exitCode: 0, stdout: "", stderr: "" })
|
|
448
|
-
},
|
|
455
|
+
}, 30_000)
|
|
449
456
|
|
|
450
457
|
test("lists ready work and exposes excluded blockers through one result", async () => {
|
|
451
458
|
const root = await createTempDir()
|
|
@@ -632,7 +639,7 @@ describe("CLI", () => {
|
|
|
632
639
|
expect(JSON.parse(explicitOutside.stdout).error.message).toContain(
|
|
633
640
|
"No Agency workbase found from",
|
|
634
641
|
)
|
|
635
|
-
})
|
|
642
|
+
}, 20_000)
|
|
636
643
|
|
|
637
644
|
test("exports equivalent JSON and JSONL graph contracts", async () => {
|
|
638
645
|
const root = await createTempDir()
|
|
@@ -849,6 +856,10 @@ status: open
|
|
|
849
856
|
]) {
|
|
850
857
|
expect(Bun.spawnSync(["git", "-C", source, ...args]).exitCode).toBe(0)
|
|
851
858
|
}
|
|
859
|
+
await runGit(["clone", "--bare", source, join(parent, "source.git")])
|
|
860
|
+
const daemon = await startGitDaemon(parent)
|
|
861
|
+
daemons.push(daemon.process)
|
|
862
|
+
await runGit(["-C", source, "remote", "add", "origin", daemon.remote])
|
|
852
863
|
|
|
853
864
|
parseJson(await runCli(["init", root, "--json"], parent))
|
|
854
865
|
parseJson(
|
|
@@ -959,23 +970,23 @@ status: open
|
|
|
959
970
|
const launches = [
|
|
960
971
|
{
|
|
961
972
|
args: ["--task", "example"],
|
|
962
|
-
cwd:
|
|
963
|
-
writable:
|
|
973
|
+
cwd: join(workbaseRoot, "tasks/example"),
|
|
974
|
+
writable: taskWorkspace.writablePath,
|
|
964
975
|
},
|
|
965
976
|
{
|
|
966
977
|
args: ["--task", "pipeline", "--phase", "build"],
|
|
967
|
-
cwd:
|
|
968
|
-
writable:
|
|
978
|
+
cwd: join(workbaseRoot, "tasks/pipeline"),
|
|
979
|
+
writable: phaseWorkspace.writablePath,
|
|
969
980
|
},
|
|
970
981
|
{
|
|
971
982
|
args: ["--epic", "delivery"],
|
|
972
983
|
cwd: join(workbaseRoot, "epics/delivery"),
|
|
973
|
-
writable:
|
|
984
|
+
writable: null,
|
|
974
985
|
},
|
|
975
986
|
{
|
|
976
987
|
args: ["--task", "pipeline"],
|
|
977
988
|
cwd: join(workbaseRoot, "tasks/pipeline"),
|
|
978
|
-
writable:
|
|
989
|
+
writable: null,
|
|
979
990
|
},
|
|
980
991
|
]
|
|
981
992
|
for (const launch of launches) {
|
|
@@ -1015,11 +1026,24 @@ status: open
|
|
|
1015
1026
|
expect.arrayContaining([
|
|
1016
1027
|
expect.objectContaining({
|
|
1017
1028
|
permission: "edit",
|
|
1018
|
-
pattern:
|
|
1029
|
+
pattern: "*",
|
|
1019
1030
|
action: "deny",
|
|
1020
1031
|
}),
|
|
1021
1032
|
]),
|
|
1022
1033
|
)
|
|
1034
|
+
if (launch.writable) {
|
|
1035
|
+
expect(agent.permission).toEqual(
|
|
1036
|
+
expect.arrayContaining(
|
|
1037
|
+
[workbaseRoot, launch.cwd].map((base) =>
|
|
1038
|
+
expect.objectContaining({
|
|
1039
|
+
permission: "edit",
|
|
1040
|
+
pattern: join(relative(base, launch.writable!), "**"),
|
|
1041
|
+
action: "allow",
|
|
1042
|
+
}),
|
|
1043
|
+
),
|
|
1044
|
+
),
|
|
1045
|
+
)
|
|
1046
|
+
}
|
|
1023
1047
|
|
|
1024
1048
|
if (launch === launches[0]) {
|
|
1025
1049
|
for (const document of documents) {
|
|
@@ -39,7 +39,12 @@ describe("doctor command", () => {
|
|
|
39
39
|
"agency.json",
|
|
40
40
|
JSON.stringify({
|
|
41
41
|
version: 2,
|
|
42
|
-
runners: {
|
|
42
|
+
runners: {
|
|
43
|
+
missing: {
|
|
44
|
+
command: ["definitely-not-installed"],
|
|
45
|
+
autoCommand: ["also-not-installed", "{prompt}"],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
43
48
|
}),
|
|
44
49
|
)
|
|
45
50
|
await mkdir(join(root, "repos"), { recursive: true })
|
|
@@ -86,6 +91,11 @@ status: open
|
|
|
86
91
|
level: "error",
|
|
87
92
|
status: "fail",
|
|
88
93
|
}),
|
|
94
|
+
expect.objectContaining({
|
|
95
|
+
id: "integration.runner.missing.auto",
|
|
96
|
+
level: "error",
|
|
97
|
+
status: "fail",
|
|
98
|
+
}),
|
|
89
99
|
expect.objectContaining({
|
|
90
100
|
id: "ref.agency.main",
|
|
91
101
|
status: "pass",
|
package/src/commands/phase.ts
CHANGED
|
@@ -259,7 +259,7 @@ Subcommands:
|
|
|
259
259
|
list <task> List task phases
|
|
260
260
|
show <task> <phase> Show a phase
|
|
261
261
|
status <task> <phase> <status>
|
|
262
|
-
Set open, done, or dropped
|
|
262
|
+
Set open, working, done, or dropped
|
|
263
263
|
update <task> <phase> Update phase metadata
|
|
264
264
|
rename <task> <phase> <new-id>
|
|
265
265
|
Rename a phase and update dependencies
|
|
@@ -61,10 +61,43 @@ describe("task creation input", () => {
|
|
|
61
61
|
"Description (optional): ",
|
|
62
62
|
"Parent epic: (none),delivery",
|
|
63
63
|
"Task type: single-phase,multi-phase",
|
|
64
|
-
"Writable repository: agency",
|
|
65
64
|
])
|
|
66
65
|
})
|
|
67
66
|
|
|
67
|
+
test("asks for a repository only when more than one is available", async () => {
|
|
68
|
+
await mkdir(join(root, "repos/web"), { recursive: true })
|
|
69
|
+
const prompts: string[] = []
|
|
70
|
+
const interaction: TaskInteraction = {
|
|
71
|
+
text: () => Effect.fail(new Error("unexpected text prompt")),
|
|
72
|
+
select: (prompt, choices) => {
|
|
73
|
+
prompts.push(`${prompt}: ${choices.join(",")}`)
|
|
74
|
+
return Effect.succeed(prompt === "Task type" ? "single-phase" : "web")
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await runTestEffect(
|
|
79
|
+
task(
|
|
80
|
+
{
|
|
81
|
+
subcommand: "new",
|
|
82
|
+
args: ["multi-repo"],
|
|
83
|
+
ticketUrl: "",
|
|
84
|
+
description: "",
|
|
85
|
+
cwd: root,
|
|
86
|
+
silent: true,
|
|
87
|
+
},
|
|
88
|
+
interaction,
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
expect(prompts).toEqual([
|
|
93
|
+
"Task type: single-phase,multi-phase",
|
|
94
|
+
"Writable repository: agency,web",
|
|
95
|
+
])
|
|
96
|
+
expect(
|
|
97
|
+
await Bun.file(join(root, "tasks/multi-repo/TASK.md")).text(),
|
|
98
|
+
).toContain("repo: web")
|
|
99
|
+
})
|
|
100
|
+
|
|
68
101
|
test("keeps scripted creation non-interactive and permits no ticket URL", async () => {
|
|
69
102
|
const interaction: TaskInteraction = {
|
|
70
103
|
text: () => Effect.fail(new Error("unexpected text prompt")),
|