@markjaquith/agency 2.27.0 → 2.28.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/README.md +42 -24
- package/cli.ts +20 -0
- package/fixtures/protocol/orchestration-recipes.json +59 -0
- package/package.json +1 -1
- package/skills/agency/SKILL.md +112 -331
- package/skills/agency/references/commands.md +161 -0
- package/skills/agency/references/contracts.md +288 -0
- package/skills/agency/references/recipes.md +219 -0
- package/src/cli-parser.test.ts +8 -0
- package/src/cli-parser.ts +10 -0
- package/src/cli.test.ts +37 -4
- package/src/commands/doctor.test.ts +156 -0
- package/src/commands/doctor.ts +47 -0
- package/src/commands/init.test.ts +8 -4
- package/src/commands/init.ts +3 -0
- package/src/commands/read-only.test.ts +2 -0
- package/src/commands/work.test.ts +20 -0
- package/src/commands/work.ts +3 -0
- package/src/services/DoctorService.ts +419 -0
- package/src/services/IntegrationService.test.ts +80 -3
- package/src/services/IntegrationService.ts +2 -2
- package/src/test-utils.ts +2 -0
- package/src/workbase/AGENTS.md +53 -19
- package/src/workbase/opencode-file.ts +7 -8
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# Agency Data Contracts
|
|
2
|
+
|
|
3
|
+
Use Agency JSON output instead of scraping human tables. Entity IDs are directory
|
|
4
|
+
names; they are not duplicated in frontmatter.
|
|
5
|
+
|
|
6
|
+
## Context Contract
|
|
7
|
+
|
|
8
|
+
`agency context . --json` returns a versioned success envelope whose result
|
|
9
|
+
contains:
|
|
10
|
+
|
|
11
|
+
- `projection`: `complete` or explicitly requested `compact`;
|
|
12
|
+
- `workbase`: root, config path, and config version;
|
|
13
|
+
- `target`: resolved epic, task, or phase identity;
|
|
14
|
+
- `documents`: ancestor frontmatter, paths, SHA-256 revisions, and prose;
|
|
15
|
+
- `graph`: parent, dependencies, dependents, readiness blockers, and progress;
|
|
16
|
+
- `authority`: `orchestration` or `execution`, one writable checkout or none,
|
|
17
|
+
and read-only references;
|
|
18
|
+
- `workspace`: code path, materialization and registration state, commits, and
|
|
19
|
+
inspection warnings;
|
|
20
|
+
- `pr`: recorded URL and state; and
|
|
21
|
+
- `validation`: validity and issues.
|
|
22
|
+
|
|
23
|
+
Compact context retains identity, revisions, authority, paths, graph state,
|
|
24
|
+
materialization, and warnings while omitting prose and low-level Git details.
|
|
25
|
+
|
|
26
|
+
## Frontmatter Shapes
|
|
27
|
+
|
|
28
|
+
### Epic
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
---
|
|
32
|
+
ticketUrl: https://example.com/tickets/checkout
|
|
33
|
+
description: Coordinate checkout delivery.
|
|
34
|
+
repos:
|
|
35
|
+
- repo: frontend
|
|
36
|
+
ref: main
|
|
37
|
+
tasks:
|
|
38
|
+
- id: api
|
|
39
|
+
- id: ui
|
|
40
|
+
dependsOn:
|
|
41
|
+
- api
|
|
42
|
+
---
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Epics have read-only `repos`, never a writable `repo`. Their `tasks` list owns
|
|
46
|
+
task ordering and task dependencies.
|
|
47
|
+
|
|
48
|
+
### Single-Phase Task
|
|
49
|
+
|
|
50
|
+
```yaml
|
|
51
|
+
---
|
|
52
|
+
ticketUrl: null
|
|
53
|
+
description: Refresh checkout copy.
|
|
54
|
+
epic: checkout
|
|
55
|
+
repo: frontend
|
|
56
|
+
repos:
|
|
57
|
+
- repo: backend
|
|
58
|
+
ref: main
|
|
59
|
+
branch: task/refresh-copy
|
|
60
|
+
base: main
|
|
61
|
+
pr: null
|
|
62
|
+
status: open
|
|
63
|
+
---
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Multi-Phase Task
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
---
|
|
70
|
+
ticketUrl: null
|
|
71
|
+
description: Deliver checkout.
|
|
72
|
+
epic: checkout
|
|
73
|
+
phases:
|
|
74
|
+
- id: api
|
|
75
|
+
- id: ui
|
|
76
|
+
dependsOn:
|
|
77
|
+
- api
|
|
78
|
+
---
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The task's `phases` list owns phase ordering and dependencies. Execution fields
|
|
82
|
+
belong in each phase document.
|
|
83
|
+
|
|
84
|
+
### Phase
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
---
|
|
88
|
+
description: Build the checkout UI.
|
|
89
|
+
repo: frontend
|
|
90
|
+
repos:
|
|
91
|
+
- repo: backend
|
|
92
|
+
ref: main
|
|
93
|
+
branch: task/checkout-ui
|
|
94
|
+
base: task/checkout-api
|
|
95
|
+
pr: null
|
|
96
|
+
status: open
|
|
97
|
+
---
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`ticketUrl` belongs to tasks and epics, not phases. `description` is optional but
|
|
101
|
+
must be non-empty when present. `pr` is a GitHub PR URL or `null`. Status is
|
|
102
|
+
`open`, `working`, `delegated`, `done`, or `dropped`; `delegated` is readable
|
|
103
|
+
legacy state but cannot be newly assigned.
|
|
104
|
+
|
|
105
|
+
## Structural Invariants
|
|
106
|
+
|
|
107
|
+
- An execution unit has one writable `repo`; plural `repos` are read-only
|
|
108
|
+
`{ repo, ref }` entries and cannot repeat the writable alias.
|
|
109
|
+
- A writable `(repo, branch)` pair belongs to exactly one active execution unit.
|
|
110
|
+
- Only `done` satisfies dependencies. `dropped` is terminal but blocks dependents.
|
|
111
|
+
- Epic task dependencies live in `EPIC.md`; phase dependencies live in `TASK.md`.
|
|
112
|
+
- IDs remain stable; use `dependsOn`, not numeric directory prefixes, for order.
|
|
113
|
+
- YAML duplicate keys, anchors, aliases, and custom tags are invalid.
|
|
114
|
+
- Use a commit SHA as a reference `ref` when reproducibility is required.
|
|
115
|
+
|
|
116
|
+
## Graph Contract
|
|
117
|
+
|
|
118
|
+
`agency graph --json` emits graph contract version 1. Stable node IDs are
|
|
119
|
+
`epic:<id>`, `task:<id>`, `phase:<task>/<phase>`,
|
|
120
|
+
`repository:<alias>`, and `execution-unit:<kind>/<id>`. Edge types are `owns`,
|
|
121
|
+
`depends_on`, `writes`, and `references`.
|
|
122
|
+
|
|
123
|
+
Every work node includes status, readiness, `blockedBy`, detailed blockers,
|
|
124
|
+
terminal state, reverse dependents, and aggregate progress. Filters run after
|
|
125
|
+
state computation. `--jsonl` emits a versioned `meta` record, node and edge
|
|
126
|
+
records, then an `end` record; together they reconstruct the JSON result.
|
|
127
|
+
|
|
128
|
+
## Machine Envelope
|
|
129
|
+
|
|
130
|
+
The published success fixture is normalized from
|
|
131
|
+
`agency init /work/agency --json`:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"version": 1,
|
|
136
|
+
"ok": true,
|
|
137
|
+
"result": {
|
|
138
|
+
"root": "/work/agency"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The published error fixture is the output of `agency unknown --json`:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"version": 1,
|
|
148
|
+
"ok": false,
|
|
149
|
+
"error": {
|
|
150
|
+
"code": "CLI_USAGE",
|
|
151
|
+
"message": "Unknown command 'unknown'.\n\nUsage: agency <command> [options]",
|
|
152
|
+
"fields": {
|
|
153
|
+
"detail": "Unknown command 'unknown'.",
|
|
154
|
+
"usage": "agency <command> [options]"
|
|
155
|
+
},
|
|
156
|
+
"retryable": false,
|
|
157
|
+
"remediation": "Correct the arguments using the usage value in error.fields."
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
These examples are exported from `fixtures/protocol/` and tested against real
|
|
163
|
+
CLI subprocess output. The Effect schemas are exported by the package and by
|
|
164
|
+
`@markjaquith/agency/protocol`. The distributable JSON Schemas are exported as
|
|
165
|
+
`@markjaquith/agency/schemas/agency-envelope-v1.json` and
|
|
166
|
+
`@markjaquith/agency/schemas/agency-graph-v1.json`.
|
|
167
|
+
|
|
168
|
+
Only the envelope and graph result have published JSON Schemas. The envelope's
|
|
169
|
+
`result` is intentionally unconstrained. Context, next, claim, prepare, sync,
|
|
170
|
+
and PR result shapes are exercised by CLI tests but do not have independent
|
|
171
|
+
published schemas.
|
|
172
|
+
|
|
173
|
+
### Output And Exit Guarantees
|
|
174
|
+
|
|
175
|
+
- `--json` writes exactly one newline-terminated envelope to stdout on success
|
|
176
|
+
or failure. It disables interactive selection and takes precedence over
|
|
177
|
+
`--silent`; explicit entity selectors remain valid.
|
|
178
|
+
- Human output is not a machine contract. Do not parse tables, progress text, or
|
|
179
|
+
diagnostic wording.
|
|
180
|
+
- Progress and verbose diagnostics use stderr. Structured command warnings stay
|
|
181
|
+
inside the stdout result. Successful JSON mode does not promise empty stderr
|
|
182
|
+
when `--verbose` is requested.
|
|
183
|
+
- Success, help, and version output exit `0`. Usage errors and every command
|
|
184
|
+
failure exit `1`. Version 1 has no error-specific exit statuses; branch on
|
|
185
|
+
`error.code`.
|
|
186
|
+
- `graph --jsonl` is the one streaming exception. On success it writes a `meta`
|
|
187
|
+
record, node and edge records, and an `end` record rather than an envelope. A
|
|
188
|
+
JSONL failure still writes one error envelope and exits `1`.
|
|
189
|
+
- A command that emits zero machine results succeeds with `result: null`.
|
|
190
|
+
Multiple results are a `PROTOCOL_OUTPUT_ERROR`.
|
|
191
|
+
|
|
192
|
+
### Error Codes
|
|
193
|
+
|
|
194
|
+
| Code | Meaning |
|
|
195
|
+
| ------------------------- | -------------------------------------------------------------- |
|
|
196
|
+
| `CLI_USAGE` | Invalid command, option, argument, or combination |
|
|
197
|
+
| `WORKBASE_NOT_FOUND` | No workbase could be resolved |
|
|
198
|
+
| `WORKBASE_CONFIG_INVALID` | Invalid workbase configuration |
|
|
199
|
+
| `WORKBASE_REGISTRY_ERROR` | Invalid or inaccessible workbase registry |
|
|
200
|
+
| `FILE_NOT_FOUND` | A required path does not exist |
|
|
201
|
+
| `FILESYSTEM_ERROR` | A filesystem operation failed |
|
|
202
|
+
| `FRONTMATTER_INVALID` | Durable document frontmatter is invalid |
|
|
203
|
+
| `VALIDATION_FAILED` | Workbase validation reported issues |
|
|
204
|
+
| `REPOSITORY_ERROR` | Repository operation failed |
|
|
205
|
+
| `EPIC_ERROR` | Epic operation failed |
|
|
206
|
+
| `TASK_ERROR` | Task operation failed |
|
|
207
|
+
| `PHASE_ERROR` | Phase operation failed |
|
|
208
|
+
| `CLAIM_ERROR` | Claim input or lifecycle state is invalid |
|
|
209
|
+
| `CLAIM_CONFLICT` | Active or legacy ownership conflicts with an operation |
|
|
210
|
+
| `REVISION_CONFLICT` | A durable document changed since inspection |
|
|
211
|
+
| `CLAIM_OWNERSHIP` | The session does not own the active claim |
|
|
212
|
+
| `ARCHIVE_ERROR` | Archive or restore operation failed |
|
|
213
|
+
| `WORKTREE_ERROR` | Worktree operation failed |
|
|
214
|
+
| `PULL_REQUEST_ERROR` | Pull request operation failed |
|
|
215
|
+
| `CONTEXT_ERROR` | A context target or required document is invalid |
|
|
216
|
+
| `GRAPH_ERROR` | Graph construction or filtering failed |
|
|
217
|
+
| `EXECUTION_BLOCKED` | Readiness or lifecycle blockers prevent execution |
|
|
218
|
+
| `SYNC_ERROR` | Reconciliation validation, inspection, or provider data failed |
|
|
219
|
+
| `PROCESS_ERROR` | A child process failed |
|
|
220
|
+
| `PROTOCOL_OUTPUT_ERROR` | A command violated the one-result machine contract |
|
|
221
|
+
| `COMMAND_FAILED` | An otherwise unclassified failure |
|
|
222
|
+
|
|
223
|
+
`CLAIM_CONFLICT`, `REVISION_CONFLICT`, and `PROCESS_ERROR` are retryable in the
|
|
224
|
+
v1 metadata. Retryable means new evidence may change the result, not that blind
|
|
225
|
+
or non-idempotent retries are safe. Inspect `fields` and apply `remediation`
|
|
226
|
+
before retrying.
|
|
227
|
+
|
|
228
|
+
## Revisions And Concurrency
|
|
229
|
+
|
|
230
|
+
A document revision is the lowercase SHA-256 hash of the complete Markdown file,
|
|
231
|
+
including frontmatter and prose. It is per document, not a workbase-wide graph
|
|
232
|
+
revision. Context, graph, and entity reads expose revisions.
|
|
233
|
+
|
|
234
|
+
`claim`, `release`, and `finish` require `--revision <sha256>`. They lock and
|
|
235
|
+
recheck the execution document before an atomic replacement, then return
|
|
236
|
+
`previousRevision` and the new `revision`. Use the returned revision or inspect
|
|
237
|
+
again before the next mutation. Claim conflicts include the current revision and
|
|
238
|
+
ownership evidence in `error.fields`; revision conflicts include ownership only
|
|
239
|
+
when claim evidence applies.
|
|
240
|
+
|
|
241
|
+
Structural update, rename, move, and dependency commands accept optional
|
|
242
|
+
`--if-revision <sha256>`. Machine orchestrators should provide it. Multi-document
|
|
243
|
+
mutations recheck every affected file while holding the graph mutation lock.
|
|
244
|
+
|
|
245
|
+
## Selectors And Projections
|
|
246
|
+
|
|
247
|
+
`--workbase <id|name|path>` selects a registered workbase. `--cwd <path>` asks
|
|
248
|
+
Agency to perform target inference as if invoked there. They are mutually
|
|
249
|
+
exclusive. Targeted commands accept `--epic`, `--task`, and `--phase` where
|
|
250
|
+
applicable; phase requires task, and entity selectors cannot be combined with a
|
|
251
|
+
positional target ID. Non-target positional values, such as a status outcome,
|
|
252
|
+
remain valid where the command syntax requires them.
|
|
253
|
+
|
|
254
|
+
`--json`, `--no-input`, and non-TTY execution disable interactive prompts and
|
|
255
|
+
selection. Supply every required value or explicit entity selector. The current
|
|
256
|
+
`next` implementation uses the process cwd even when global `--cwd` or
|
|
257
|
+
`--workbase` parses successfully; run `next --json` from the intended workbase
|
|
258
|
+
until that routing gap is fixed.
|
|
259
|
+
|
|
260
|
+
Context defaults to the `complete` projection. `--compact` omits prose and
|
|
261
|
+
low-level Git details but retains identity, document hashes, authority, paths,
|
|
262
|
+
graph state, materialization, and validation warnings.
|
|
263
|
+
|
|
264
|
+
Graph projections are opt-in with repeatable
|
|
265
|
+
`--include <bodies|workspace|git|pr>`. Filters such as `--ready`, `--blocked`,
|
|
266
|
+
`--status`, `--repository`, and `--kind` are applied after readiness and graph
|
|
267
|
+
state are computed; returned edges always have both endpoints in the filtered
|
|
268
|
+
node set.
|
|
269
|
+
|
|
270
|
+
## Capability Boundaries
|
|
271
|
+
|
|
272
|
+
- There is no atomic find-ready-and-claim operation. `next` is observational,
|
|
273
|
+
and `claim` does not enforce dependency readiness. Inspect readiness, then
|
|
274
|
+
claim with the observed revision and handle conflicts.
|
|
275
|
+
- There is no `assign` command, remote queue, scheduler, heartbeat, claim renewal,
|
|
276
|
+
runner monitor, or cancellation API. `work` can claim and launch one local
|
|
277
|
+
configured runner, but it is a process-launching, non-JSON flow rather than a
|
|
278
|
+
machine assignment API. External orchestrators claim with claimant and runner
|
|
279
|
+
IDs, then manage their runner themselves.
|
|
280
|
+
- Agency does not edit code, create commits, run repository checks, wait for PR
|
|
281
|
+
checks, merge PRs, or verify that a requested completion condition is true.
|
|
282
|
+
`finish` records the caller's asserted outcome after ownership checks.
|
|
283
|
+
- Reconciliation never discards changes, switches branches, resets reference
|
|
284
|
+
commits, moves conflicting worktrees, chooses among multiple PRs, or bypasses
|
|
285
|
+
active claims. Such conditions remain unresolved for a human or orchestrator.
|
|
286
|
+
- `delegated` is readable legacy state but cannot be newly assigned. `--force`
|
|
287
|
+
only overrides readiness for `work` and `pr create`; it is not general
|
|
288
|
+
reconciliation authority.
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Agency Recipes
|
|
2
|
+
|
|
3
|
+
For an entity target, run `agency context . --json` before selecting a recipe.
|
|
4
|
+
At the workbase root, use `agency next --json` or `agency graph --json` to choose
|
|
5
|
+
a target, then inspect that explicit target with `agency context <target> --json`.
|
|
6
|
+
|
|
7
|
+
The machine-orchestration forms in the inspect-through-recover recipes are
|
|
8
|
+
captured in `fixtures/protocol/orchestration-recipes.json` and tested against the
|
|
9
|
+
real CLI parser. Their lifecycle behavior is covered by CLI and service fixtures.
|
|
10
|
+
Replace angle-bracket placeholders with values from context or a prior machine
|
|
11
|
+
result; never scrape them from human output.
|
|
12
|
+
|
|
13
|
+
## Inspect A Target
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
agency context --task <task-id> --phase <phase-id> --json
|
|
17
|
+
agency worktree inspect <task-id> <phase-id> --json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Confirm the target, current document revision, readiness, authority, checkout,
|
|
21
|
+
claim, PR, and validation state. Use `--compact` only when prose and detailed Git
|
|
22
|
+
evidence are not needed.
|
|
23
|
+
|
|
24
|
+
## Find Ready Work
|
|
25
|
+
|
|
26
|
+
Run this from the intended workbase:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
agency next --json
|
|
30
|
+
agency graph --ready --json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`next` ranks ready execution units and explains every excluded unit. It does not
|
|
34
|
+
reserve work. Another orchestrator may claim the same candidate before you do,
|
|
35
|
+
so always handle `CLAIM_CONFLICT` or `REVISION_CONFLICT`.
|
|
36
|
+
|
|
37
|
+
## Human: Create And Launch Work
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
agency task create refresh-copy --repo frontend --branch task/refresh-copy --base main
|
|
41
|
+
agency validate
|
|
42
|
+
agency work tasks/refresh-copy
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
For a multi-PR outcome:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
agency task create checkout --multi-phase
|
|
49
|
+
agency phase create checkout api --repo backend --branch task/checkout-api --base main
|
|
50
|
+
agency phase create checkout ui --repo frontend --branch task/checkout-ui \
|
|
51
|
+
--base main --reference backend:main --depends-on api
|
|
52
|
+
agency validate
|
|
53
|
+
agency work tasks/checkout/phases/api
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`agency work` is intentionally last: it checks readiness, materializes
|
|
57
|
+
worktrees, creates a claim, marks the execution unit working, and launches the
|
|
58
|
+
runner.
|
|
59
|
+
|
|
60
|
+
## Active Agent: Execute Assigned Work
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
agency context . --json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
1. Verify context reports the expected execution target, no blockers, valid
|
|
67
|
+
structure, and the current checkout as `authority.writable.checkoutPath`.
|
|
68
|
+
2. Read `TASK.md`, and `PHASE.md` for phase work.
|
|
69
|
+
3. Implement only in the writable checkout; treat all reference checkouts as
|
|
70
|
+
read-only.
|
|
71
|
+
4. Run repository formatting, checks, build, and focused tests.
|
|
72
|
+
5. Review and commit the diff.
|
|
73
|
+
6. If requested, run `agency validate`, then
|
|
74
|
+
`agency pr create <task-id> [phase-id]`.
|
|
75
|
+
7. Finish the current claim only after its completion condition is true.
|
|
76
|
+
|
|
77
|
+
Never invoke `agency work` merely because an execution checkout already exists;
|
|
78
|
+
that would start another agent rather than continue the current assignment.
|
|
79
|
+
|
|
80
|
+
## Claim Ready Work
|
|
81
|
+
|
|
82
|
+
Get the current document revision from context. Use stable claimant, runner, and
|
|
83
|
+
session IDs:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
agency claim checkout ui \
|
|
87
|
+
--claimant orchestrator-1 --runner opencode --session-id session-123 \
|
|
88
|
+
--revision <sha256> --json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Claim records ownership and sets status to `working`; it does not launch a runner
|
|
92
|
+
or recheck dependency readiness. A machine orchestrator must launch and monitor
|
|
93
|
+
its runner separately. There is no atomic find-and-claim or `assign` command.
|
|
94
|
+
|
|
95
|
+
## Prepare Checkouts
|
|
96
|
+
|
|
97
|
+
Preview and then materialize without claiming, launching, or changing status:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
agency work prepare --task checkout --phase ui --dry-run --json
|
|
101
|
+
agency work prepare --task checkout --phase ui --json
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Use the returned checkout paths and resolved commits. Do not create or move
|
|
105
|
+
worktrees manually.
|
|
106
|
+
|
|
107
|
+
## Assign A Runner
|
|
108
|
+
|
|
109
|
+
For a human-operated local launch, Agency combines readiness checks, prepare,
|
|
110
|
+
claim, status mutation, and runner launch:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
agency work tasks/checkout/phases/ui --runner opencode
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
This is not a JSON assignment API. An external orchestrator instead claims with
|
|
117
|
+
its own `claimant`, `runner`, and `session-id`, starts the runner outside Agency,
|
|
118
|
+
and passes it the target and claim result. Agency has no queue, heartbeat,
|
|
119
|
+
monitoring, cancellation, or automatic claim-renewal service.
|
|
120
|
+
|
|
121
|
+
## Reconcile Durable And Local State
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
agency sync --dry-run --json
|
|
125
|
+
agency sync --apply --json
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The first command is observational. Apply may materialize an unconflicted missing
|
|
129
|
+
checkout, release an expired claim, record one unambiguous matching PR, or mark
|
|
130
|
+
unclaimed work done after its authoritative PR merges. Review `warnings` and
|
|
131
|
+
`unresolved`; apply never discards or resets work and never chooses among
|
|
132
|
+
ambiguous PRs.
|
|
133
|
+
|
|
134
|
+
## Release Interrupted Work
|
|
135
|
+
|
|
136
|
+
After every claim mutation, use the returned revision or inspect again. Release
|
|
137
|
+
interrupted work back to open:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
agency release checkout ui --session-id session-123 \
|
|
141
|
+
--revision <current-sha256> --json
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Finish Verified Work
|
|
145
|
+
|
|
146
|
+
Only after the assigned completion condition is true:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
agency finish checkout ui --session-id session-123 \
|
|
150
|
+
--revision <current-sha256> --outcome done --json
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Do not substitute `phase status done` for `finish` when an active claim exists;
|
|
154
|
+
`finish` preserves ownership history and revision safety.
|
|
155
|
+
|
|
156
|
+
## Create A Pull Request
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
agency validate
|
|
160
|
+
agency pr create checkout ui --json
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The writable worktree must be clean. Agency pushes the declared branch, invokes
|
|
164
|
+
the configured delivery provider's create command or falls back to
|
|
165
|
+
`gh pr create --fill`, and records the returned URL. Do not manually write a URL
|
|
166
|
+
if creation fails. A PR being open is not equivalent to completion when the
|
|
167
|
+
assigned outcome requires merge.
|
|
168
|
+
|
|
169
|
+
Agency does not create commits, run tests, wait for checks, merge the PR, or
|
|
170
|
+
verify completion. Those remain orchestrator responsibilities.
|
|
171
|
+
|
|
172
|
+
## Convert A Task To Phases
|
|
173
|
+
|
|
174
|
+
Name the phase that inherits the existing task's execution metadata:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
agency phase create refresh-copy verification \
|
|
178
|
+
--first-phase implementation \
|
|
179
|
+
--repo frontend --branch task/refresh-copy-verification --base main \
|
|
180
|
+
--depends-on implementation
|
|
181
|
+
agency validate
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Agency converts the task shape, creates both phase documents, and relocates
|
|
185
|
+
materialized worktrees. Do not perform those moves manually.
|
|
186
|
+
|
|
187
|
+
## Recover From Interrupted State
|
|
188
|
+
|
|
189
|
+
Inspect before applying changes:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
agency doctor --json
|
|
193
|
+
agency sync --dry-run --json
|
|
194
|
+
agency worktree inspect <task-id> [phase-id] --json
|
|
195
|
+
agency worktree repair <task-id> [phase-id] --dry-run --json
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Use `agency sync --apply` only with explicit user intent. It may safely
|
|
199
|
+
materialize an unconflicted missing checkout, release an expired claim, record a
|
|
200
|
+
single matching PR, or mark work done after its authoritative PR merged and no
|
|
201
|
+
claim remains. It never modifies dirty checkouts, switches branches, resets
|
|
202
|
+
references, chooses among PRs, or bypasses active claims.
|
|
203
|
+
|
|
204
|
+
For a worktree-specific issue, use `worktree repair --dry-run` before repair.
|
|
205
|
+
Repair is conservative and never discards work. Use remove or rebuild only after
|
|
206
|
+
reviewing the dry run and confirming every checkout is clean.
|
|
207
|
+
|
|
208
|
+
## Archive Or Restore
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
agency archive phase checkout ui --dry-run --json
|
|
212
|
+
agency archive phase checkout ui
|
|
213
|
+
agency restore phase checkout ui --dry-run --json
|
|
214
|
+
agency restore phase checkout ui
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Archive only terminal work and only with explicit intent. Agency preserves the
|
|
218
|
+
branch and lifecycle provenance while enforcing graph, worktree, and destination
|
|
219
|
+
safety. Never move archived directories by hand.
|
package/src/cli-parser.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import orchestrationRecipes from "../fixtures/protocol/orchestration-recipes.json"
|
|
2
3
|
import { parseCli } from "./cli-parser"
|
|
3
4
|
|
|
4
5
|
const expectUsageError = (args: string[], usage: string) => {
|
|
@@ -6,6 +7,12 @@ const expectUsageError = (args: string[], usage: string) => {
|
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
describe("strict CLI parsing", () => {
|
|
10
|
+
test("accepts every documented orchestration recipe command", () => {
|
|
11
|
+
for (const args of orchestrationRecipes) {
|
|
12
|
+
expect(() => parseCli(args)).not.toThrow()
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
|
|
9
16
|
test("rejects misspelled and command-inapplicable options", () => {
|
|
10
17
|
expectUsageError(["task", "list", "--josn"], "agency task")
|
|
11
18
|
expectUsageError(
|
|
@@ -123,6 +130,7 @@ describe("strict CLI parsing", () => {
|
|
|
123
130
|
|
|
124
131
|
test("parses addressable resource maintenance commands", () => {
|
|
125
132
|
for (const args of [
|
|
133
|
+
["doctor", "--json"],
|
|
126
134
|
["repo", "show", "agency", "--json"],
|
|
127
135
|
["repo", "fetch", "agency"],
|
|
128
136
|
["repo", "remove", "agency"],
|
package/src/cli-parser.ts
CHANGED
|
@@ -804,6 +804,16 @@ const commands = {
|
|
|
804
804
|
conflicts: viewConflicts,
|
|
805
805
|
},
|
|
806
806
|
},
|
|
807
|
+
doctor: {
|
|
808
|
+
usage: "agency doctor [--json]",
|
|
809
|
+
options: outputOptions,
|
|
810
|
+
command: {
|
|
811
|
+
usage: "agency doctor [--json]",
|
|
812
|
+
minArgs: 0,
|
|
813
|
+
maxArgs: 0,
|
|
814
|
+
options: ["json"],
|
|
815
|
+
},
|
|
816
|
+
},
|
|
807
817
|
validate: {
|
|
808
818
|
usage: "agency validate [path] [--json] [--no-input]",
|
|
809
819
|
options: {
|
package/src/cli.test.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from "bun:test"
|
|
2
2
|
import { access, mkdir, realpath } from "node:fs/promises"
|
|
3
3
|
import { join } from "node:path"
|
|
4
|
+
import errorFixture from "../fixtures/protocol/error.json"
|
|
5
|
+
import successFixture from "../fixtures/protocol/success.json"
|
|
4
6
|
import { cleanupTempDir, createTempDir } from "./test-utils"
|
|
5
7
|
|
|
6
8
|
const projectRoot = join(import.meta.dir, "..")
|
|
@@ -65,6 +67,28 @@ describe("CLI", () => {
|
|
|
65
67
|
expect(help.stderr).toBe("")
|
|
66
68
|
})
|
|
67
69
|
|
|
70
|
+
test("keeps the published protocol fixtures synchronized with CLI output", async () => {
|
|
71
|
+
const parent = await createTempDir()
|
|
72
|
+
tempDirs.push(parent)
|
|
73
|
+
const root = join(parent, "workbase")
|
|
74
|
+
const success = await runCli(["init", root, "--json"], parent)
|
|
75
|
+
expect(success.exitCode).toBe(0)
|
|
76
|
+
expect(success.stderr).toBe("")
|
|
77
|
+
expect(success.stdout.endsWith("\n")).toBe(true)
|
|
78
|
+
const successEnvelope = JSON.parse(success.stdout)
|
|
79
|
+
expect(successEnvelope.result.root).toBe(root)
|
|
80
|
+
expect({
|
|
81
|
+
...successEnvelope,
|
|
82
|
+
result: { ...successEnvelope.result, root: "/work/agency" },
|
|
83
|
+
}).toEqual(successFixture)
|
|
84
|
+
|
|
85
|
+
const failure = await runCli(["unknown", "--json"])
|
|
86
|
+
expect(failure.exitCode).toBe(1)
|
|
87
|
+
expect(failure.stderr).toBe("")
|
|
88
|
+
expect(failure.stdout.endsWith("\n")).toBe(true)
|
|
89
|
+
expect(JSON.parse(failure.stdout)).toEqual(errorFixture)
|
|
90
|
+
})
|
|
91
|
+
|
|
68
92
|
test("reports unknown commands and preserves tagged error messages", async () => {
|
|
69
93
|
const unknown = await runCli(["unknown"])
|
|
70
94
|
expect(unknown.exitCode).toBe(1)
|
|
@@ -432,16 +456,16 @@ describe("CLI", () => {
|
|
|
432
456
|
await runCli(["integration", "status", "--json"], root),
|
|
433
457
|
)
|
|
434
458
|
expect(before.files).toMatchObject([
|
|
435
|
-
{ name: "agents", state: "
|
|
436
|
-
{ name: "opencode", state: "
|
|
459
|
+
{ name: "agents", state: "managed" },
|
|
460
|
+
{ name: "opencode", state: "managed" },
|
|
437
461
|
])
|
|
438
462
|
|
|
439
463
|
const synced = parseJson(
|
|
440
464
|
await runCli(["integration", "sync", "--json"], root),
|
|
441
465
|
)
|
|
442
466
|
expect(synced.files).toMatchObject([
|
|
443
|
-
{ name: "agents", state: "managed", changed:
|
|
444
|
-
{ name: "opencode", state: "managed", changed:
|
|
467
|
+
{ name: "agents", state: "managed", changed: false },
|
|
468
|
+
{ name: "opencode", state: "managed", changed: false },
|
|
445
469
|
])
|
|
446
470
|
})
|
|
447
471
|
|
|
@@ -935,6 +959,15 @@ status: open
|
|
|
935
959
|
valid: true,
|
|
936
960
|
issues: [],
|
|
937
961
|
})
|
|
962
|
+
const doctor = parseJson(await runCli(["doctor", "--json"], root))
|
|
963
|
+
expect(doctor).toMatchObject({
|
|
964
|
+
version: 1,
|
|
965
|
+
root: workbaseRoot,
|
|
966
|
+
checks: expect.arrayContaining([
|
|
967
|
+
expect.objectContaining({ id: "tool.git", status: "pass" }),
|
|
968
|
+
expect.objectContaining({ id: "workbase.validation", status: "pass" }),
|
|
969
|
+
]),
|
|
970
|
+
})
|
|
938
971
|
|
|
939
972
|
const validation = parseJson(
|
|
940
973
|
await runCli(["validate", root, "--json"], parent),
|