@markjaquith/agency 2.8.0 → 2.10.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 +79 -0
- package/cli.ts +50 -4
- package/fixtures/protocol/error.json +14 -0
- package/fixtures/protocol/success.json +7 -0
- package/index.ts +1 -0
- package/package.json +16 -1
- package/schemas/agency-envelope-v1.schema.json +38 -0
- package/src/cli-parser.test.ts +10 -0
- package/src/cli-parser.ts +26 -1
- package/src/cli.test.ts +68 -1
- package/src/commands/context.test.ts +379 -0
- package/src/commands/context.ts +29 -0
- package/src/commands/read-only.test.ts +8 -0
- package/src/commands/validate.ts +7 -0
- package/src/commands/work.test.ts +3 -3
- package/src/protocol.test.ts +87 -0
- package/src/protocol.ts +217 -0
- package/src/services/ContextService.ts +898 -0
- package/src/services/WorktreeService.test.ts +2 -2
- package/src/test-utils.ts +12 -3
- package/src/utils/effect.test.ts +9 -3
- package/src/utils/effect.ts +4 -2
package/README.md
CHANGED
|
@@ -231,12 +231,30 @@ agency repo link backend ~/Dev/backend
|
|
|
231
231
|
agency task new
|
|
232
232
|
|
|
233
233
|
agency validate
|
|
234
|
+
agency context tasks/refresh-copy --json
|
|
234
235
|
agency work tasks/refresh-copy
|
|
235
236
|
agency pr create refresh-copy
|
|
236
237
|
```
|
|
237
238
|
|
|
238
239
|
## Commands
|
|
239
240
|
|
|
241
|
+
### Target Context
|
|
242
|
+
|
|
243
|
+
`agency context [target] --json` returns the complete bootstrap context for an
|
|
244
|
+
epic, task, or phase without modifying the workbase or fetching repositories.
|
|
245
|
+
The target defaults to the current directory; entity directories, document
|
|
246
|
+
paths, checkout descendants, and bare task IDs are accepted.
|
|
247
|
+
|
|
248
|
+
The result includes workbase and target identity, ancestor frontmatter and prose
|
|
249
|
+
with SHA-256 hashes, dependency and readiness state, aggregate status, writable
|
|
250
|
+
and reference authority, local checkout and resolved-commit state, recorded PR
|
|
251
|
+
state, and validation warnings. Only `done` satisfies a dependency; `dropped` is
|
|
252
|
+
terminal but remains a blocker.
|
|
253
|
+
|
|
254
|
+
Complete output is the default. Pass `--compact` explicitly to omit document
|
|
255
|
+
prose and low-level Git details while retaining identity, hashes, authority,
|
|
256
|
+
paths, graph state, materialization state, and validation warnings.
|
|
257
|
+
|
|
240
258
|
### Workbase and Repositories
|
|
241
259
|
|
|
242
260
|
```text
|
|
@@ -302,6 +320,7 @@ agency task create <id> --multi-phase
|
|
|
302
320
|
### Noninteractive Use
|
|
303
321
|
|
|
304
322
|
Agency never prompts when `--no-input` is set or stdin/stderr are not TTYs.
|
|
323
|
+
`--json` also disables prompts and selectors, even when a TTY is available.
|
|
305
324
|
Commands with explicit inputs continue normally. `task new` fails immediately;
|
|
306
325
|
`work` requires an explicit directory, task ID, or `--epic` and must run from a
|
|
307
326
|
workbase; `validate` requires an explicit path or must run from a workbase.
|
|
@@ -408,6 +427,66 @@ unknown dependencies, and dependency cycles. YAML duplicate keys, anchors,
|
|
|
408
427
|
aliases, and custom tags are rejected. When path is omitted outside a workbase,
|
|
409
428
|
Agency prompts for a registered workbase.
|
|
410
429
|
|
|
430
|
+
## Machine Protocol
|
|
431
|
+
|
|
432
|
+
`--json` emits exactly one JSON value on stdout for success or failure. It takes
|
|
433
|
+
precedence over `--silent`; progress, warnings, and verbose diagnostics remain on
|
|
434
|
+
stderr. Version 1 success responses have this shape:
|
|
435
|
+
|
|
436
|
+
```json
|
|
437
|
+
{ "version": 1, "ok": true, "result": { "root": "/work/agency" } }
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Failures exit nonzero and use the same versioned envelope:
|
|
441
|
+
|
|
442
|
+
```json
|
|
443
|
+
{
|
|
444
|
+
"version": 1,
|
|
445
|
+
"ok": false,
|
|
446
|
+
"error": {
|
|
447
|
+
"code": "CLI_USAGE",
|
|
448
|
+
"message": "Unknown command 'unknown'.\n\nUsage: agency <command> [options]",
|
|
449
|
+
"fields": {
|
|
450
|
+
"detail": "Unknown command 'unknown'.",
|
|
451
|
+
"usage": "agency <command> [options]"
|
|
452
|
+
},
|
|
453
|
+
"retryable": false,
|
|
454
|
+
"remediation": "Correct the arguments using the usage value in error.fields."
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Every error contains a stable `code`, human-readable `message`, structured
|
|
460
|
+
`fields`, and `retryable`. `remediation` is included when Agency knows a specific
|
|
461
|
+
recovery action. Version 1 defines these codes:
|
|
462
|
+
|
|
463
|
+
| Code | Meaning |
|
|
464
|
+
| ------------------------- | -------------------------------------------------------- |
|
|
465
|
+
| `CLI_USAGE` | Invalid command, option, argument, or option combination |
|
|
466
|
+
| `WORKBASE_NOT_FOUND` | No workbase could be resolved |
|
|
467
|
+
| `WORKBASE_CONFIG_INVALID` | Invalid workbase configuration |
|
|
468
|
+
| `WORKBASE_REGISTRY_ERROR` | Invalid or inaccessible workbase registry |
|
|
469
|
+
| `FILE_NOT_FOUND` | A required path does not exist |
|
|
470
|
+
| `FILESYSTEM_ERROR` | A filesystem operation failed |
|
|
471
|
+
| `FRONTMATTER_INVALID` | A durable document has invalid frontmatter |
|
|
472
|
+
| `VALIDATION_FAILED` | Workbase validation reported issues |
|
|
473
|
+
| `REPOSITORY_ERROR` | Repository operation failed |
|
|
474
|
+
| `EPIC_ERROR` | Epic operation failed |
|
|
475
|
+
| `TASK_ERROR` | Task operation failed |
|
|
476
|
+
| `PHASE_ERROR` | Phase operation failed |
|
|
477
|
+
| `ARCHIVE_ERROR` | Archive operation failed |
|
|
478
|
+
| `WORKTREE_ERROR` | Worktree operation failed |
|
|
479
|
+
| `PULL_REQUEST_ERROR` | Pull request operation failed |
|
|
480
|
+
| `PROCESS_ERROR` | A child process failed and may be retried |
|
|
481
|
+
| `PROTOCOL_OUTPUT_ERROR` | A command violated the machine output contract |
|
|
482
|
+
| `COMMAND_FAILED` | An otherwise unclassified command failure |
|
|
483
|
+
|
|
484
|
+
The Effect schemas are exported from `@markjaquith/agency` and
|
|
485
|
+
`@markjaquith/agency/protocol`. The distributable JSON Schema is exported as
|
|
486
|
+
`@markjaquith/agency/schemas/agency-envelope-v1.json`. Representative payloads
|
|
487
|
+
are exported as `@markjaquith/agency/fixtures/protocol/success.json` and
|
|
488
|
+
`@markjaquith/agency/fixtures/protocol/error.json`.
|
|
489
|
+
|
|
411
490
|
## Agent Skill
|
|
412
491
|
|
|
413
492
|
`skills/agency/SKILL.md` contains an agent-oriented operating guide for Agency.
|
package/cli.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { pr, help as prHelp } from "./src/commands/pr"
|
|
|
8
8
|
import { work, help as workHelp } from "./src/commands/work"
|
|
9
9
|
import { status, help as statusHelp } from "./src/commands/status"
|
|
10
10
|
import { validate, help as validateHelp } from "./src/commands/validate"
|
|
11
|
+
import { context, help as contextHelp } from "./src/commands/context"
|
|
11
12
|
import { repo, help as repoHelp } from "./src/commands/repo"
|
|
12
13
|
import { epic, help as epicHelp } from "./src/commands/epic"
|
|
13
14
|
import { phase, help as phaseHelp } from "./src/commands/phase"
|
|
@@ -28,6 +29,13 @@ import { WorktreeService } from "./src/services/WorktreeService"
|
|
|
28
29
|
import { PullRequestService } from "./src/services/PullRequestService"
|
|
29
30
|
import { ArchiveService } from "./src/services/ArchiveService"
|
|
30
31
|
import { IntegrationService } from "./src/services/IntegrationService"
|
|
32
|
+
import { ContextService } from "./src/services/ContextService"
|
|
33
|
+
import {
|
|
34
|
+
collectCommandResult,
|
|
35
|
+
errorEnvelope,
|
|
36
|
+
successEnvelope,
|
|
37
|
+
writeEnvelope,
|
|
38
|
+
} from "./src/protocol"
|
|
31
39
|
|
|
32
40
|
// Create CLI layer with all services
|
|
33
41
|
const CliLayer = Layer.mergeAll(
|
|
@@ -41,6 +49,7 @@ const CliLayer = Layer.mergeAll(
|
|
|
41
49
|
PullRequestService.Default,
|
|
42
50
|
ArchiveService.Default,
|
|
43
51
|
IntegrationService.Default,
|
|
52
|
+
ContextService.Default,
|
|
44
53
|
)
|
|
45
54
|
|
|
46
55
|
/**
|
|
@@ -76,7 +85,7 @@ async function runCommand<E>(
|
|
|
76
85
|
Effect.either,
|
|
77
86
|
),
|
|
78
87
|
)
|
|
79
|
-
if (Either.isLeft(result)) throw
|
|
88
|
+
if (Either.isLeft(result)) throw result.left
|
|
80
89
|
}
|
|
81
90
|
|
|
82
91
|
// Read version from package.json
|
|
@@ -307,6 +316,23 @@ const commands: Record<string, Command> = {
|
|
|
307
316
|
)
|
|
308
317
|
},
|
|
309
318
|
},
|
|
319
|
+
context: {
|
|
320
|
+
run: async (args: string[], options: Record<string, any>) => {
|
|
321
|
+
if (options.help) {
|
|
322
|
+
console.log(contextHelp)
|
|
323
|
+
return
|
|
324
|
+
}
|
|
325
|
+
await runCommand(
|
|
326
|
+
context({
|
|
327
|
+
target: args[0],
|
|
328
|
+
compact: options.compact,
|
|
329
|
+
json: options.json,
|
|
330
|
+
silent: options.silent,
|
|
331
|
+
verbose: options.verbose,
|
|
332
|
+
}),
|
|
333
|
+
)
|
|
334
|
+
},
|
|
335
|
+
},
|
|
310
336
|
}
|
|
311
337
|
|
|
312
338
|
function showMainHelp() {
|
|
@@ -328,6 +354,7 @@ Commands:
|
|
|
328
354
|
repo <subcommand> Manage workbase repositories
|
|
329
355
|
status Show status for the current workbase
|
|
330
356
|
validate [path] Validate a workbase
|
|
357
|
+
context [target] Return complete target context
|
|
331
358
|
|
|
332
359
|
Global Options:
|
|
333
360
|
-h, --help Show help for a command
|
|
@@ -346,13 +373,19 @@ For more information about a command, run:
|
|
|
346
373
|
`)
|
|
347
374
|
}
|
|
348
375
|
|
|
376
|
+
const machineMode = process.argv.slice(2).includes("--json")
|
|
377
|
+
|
|
349
378
|
try {
|
|
350
379
|
const args = process.argv.slice(2)
|
|
351
380
|
const { commandName, args: commandArgs, values } = parseCli(args)
|
|
352
381
|
|
|
353
382
|
// Handle global flags
|
|
354
383
|
if (values.version) {
|
|
355
|
-
|
|
384
|
+
if (machineMode) {
|
|
385
|
+
writeEnvelope(successEnvelope({ version: VERSION }))
|
|
386
|
+
} else {
|
|
387
|
+
console.log(`v${VERSION}`)
|
|
388
|
+
}
|
|
356
389
|
process.exit(0)
|
|
357
390
|
}
|
|
358
391
|
|
|
@@ -365,9 +398,22 @@ try {
|
|
|
365
398
|
|
|
366
399
|
const command = commands[commandName]!
|
|
367
400
|
const inputAllowed =
|
|
368
|
-
!values
|
|
369
|
-
|
|
401
|
+
!values.json &&
|
|
402
|
+
!values["no-input"] &&
|
|
403
|
+
Boolean(process.stdin.isTTY && process.stderr.isTTY)
|
|
404
|
+
if (values.json) {
|
|
405
|
+
const result = await collectCommandResult(() =>
|
|
406
|
+
command.run(commandArgs, { ...values, inputAllowed }),
|
|
407
|
+
)
|
|
408
|
+
writeEnvelope(successEnvelope(result))
|
|
409
|
+
} else {
|
|
410
|
+
await command.run(commandArgs, { ...values, inputAllowed })
|
|
411
|
+
}
|
|
370
412
|
} catch (error) {
|
|
413
|
+
if (machineMode) {
|
|
414
|
+
writeEnvelope(errorEnvelope(error))
|
|
415
|
+
process.exit(1)
|
|
416
|
+
}
|
|
371
417
|
if (error instanceof Error) {
|
|
372
418
|
let message = error.message
|
|
373
419
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"ok": false,
|
|
4
|
+
"error": {
|
|
5
|
+
"code": "CLI_USAGE",
|
|
6
|
+
"message": "Unknown command 'unknown'.\n\nUsage: agency <command> [options]",
|
|
7
|
+
"fields": {
|
|
8
|
+
"detail": "Unknown command 'unknown'.",
|
|
9
|
+
"usage": "agency <command> [options]"
|
|
10
|
+
},
|
|
11
|
+
"retryable": false,
|
|
12
|
+
"remediation": "Correct the arguments using the usage value in error.fields."
|
|
13
|
+
}
|
|
14
|
+
}
|
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markjaquith/agency",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "Manage agentic work across repositories with durable workbases",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
"index.ts",
|
|
22
22
|
"cli.ts",
|
|
23
23
|
"src",
|
|
24
|
+
"schemas",
|
|
25
|
+
"fixtures/protocol",
|
|
24
26
|
"skills",
|
|
25
27
|
"README.md",
|
|
26
28
|
"LICENSE"
|
|
@@ -31,6 +33,19 @@
|
|
|
31
33
|
".": {
|
|
32
34
|
"types": "./index.ts",
|
|
33
35
|
"import": "./index.ts"
|
|
36
|
+
},
|
|
37
|
+
"./protocol": {
|
|
38
|
+
"types": "./src/protocol.ts",
|
|
39
|
+
"import": "./src/protocol.ts"
|
|
40
|
+
},
|
|
41
|
+
"./schemas/agency-envelope-v1.json": {
|
|
42
|
+
"default": "./schemas/agency-envelope-v1.schema.json"
|
|
43
|
+
},
|
|
44
|
+
"./fixtures/protocol/success.json": {
|
|
45
|
+
"default": "./fixtures/protocol/success.json"
|
|
46
|
+
},
|
|
47
|
+
"./fixtures/protocol/error.json": {
|
|
48
|
+
"default": "./fixtures/protocol/error.json"
|
|
34
49
|
}
|
|
35
50
|
},
|
|
36
51
|
"publishConfig": {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/markjaquith/agency/schemas/agency-envelope-v1.schema.json",
|
|
4
|
+
"title": "Agency machine result envelope v1",
|
|
5
|
+
"oneOf": [
|
|
6
|
+
{
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"required": ["version", "ok", "result"],
|
|
10
|
+
"properties": {
|
|
11
|
+
"version": { "const": 1 },
|
|
12
|
+
"ok": { "const": true },
|
|
13
|
+
"result": true
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"type": "object",
|
|
18
|
+
"additionalProperties": false,
|
|
19
|
+
"required": ["version", "ok", "error"],
|
|
20
|
+
"properties": {
|
|
21
|
+
"version": { "const": 1 },
|
|
22
|
+
"ok": { "const": false },
|
|
23
|
+
"error": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"additionalProperties": false,
|
|
26
|
+
"required": ["code", "message", "fields", "retryable"],
|
|
27
|
+
"properties": {
|
|
28
|
+
"code": { "type": "string" },
|
|
29
|
+
"message": { "type": "string" },
|
|
30
|
+
"fields": { "type": "object" },
|
|
31
|
+
"retryable": { "type": "boolean" },
|
|
32
|
+
"remediation": { "type": "string" }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
package/src/cli-parser.test.ts
CHANGED
|
@@ -139,11 +139,21 @@ describe("strict CLI parsing", () => {
|
|
|
139
139
|
[["pr", "create", "one", "two", "three"], "agency pr create"],
|
|
140
140
|
[["status", "extra"], "agency status"],
|
|
141
141
|
[["validate", "one", "two"], "agency validate"],
|
|
142
|
+
[["context", "one", "two"], "agency context"],
|
|
142
143
|
] as const) {
|
|
143
144
|
expectUsageError([...args], usage)
|
|
144
145
|
}
|
|
145
146
|
})
|
|
146
147
|
|
|
148
|
+
test("accepts context projections and keeps compact command-local", () => {
|
|
149
|
+
expect(parseCli(["context", ".", "--json", "--compact"])).toMatchObject({
|
|
150
|
+
commandName: "context",
|
|
151
|
+
args: ["."],
|
|
152
|
+
values: { json: true, compact: true },
|
|
153
|
+
})
|
|
154
|
+
expectUsageError(["status", "--compact"], "agency status")
|
|
155
|
+
})
|
|
156
|
+
|
|
147
157
|
test("reports unknown subcommands with parent usage", () => {
|
|
148
158
|
expect(() => parseCli(["task", "crate"])).toThrow(
|
|
149
159
|
"Unknown subcommand 'crate'",
|
package/src/cli-parser.ts
CHANGED
|
@@ -338,6 +338,19 @@ const commands = {
|
|
|
338
338
|
options: ["json"],
|
|
339
339
|
},
|
|
340
340
|
},
|
|
341
|
+
context: {
|
|
342
|
+
usage: "agency context [target] [--json] [--compact]",
|
|
343
|
+
options: {
|
|
344
|
+
...outputOptions,
|
|
345
|
+
compact: { type: "boolean" },
|
|
346
|
+
},
|
|
347
|
+
command: {
|
|
348
|
+
usage: "agency context [target] [--json] [--compact]",
|
|
349
|
+
minArgs: 0,
|
|
350
|
+
maxArgs: 1,
|
|
351
|
+
options: ["json", "compact"],
|
|
352
|
+
},
|
|
353
|
+
},
|
|
341
354
|
} satisfies Readonly<Record<string, CommandDefinition>>
|
|
342
355
|
|
|
343
356
|
const rootOptions = commonOptions
|
|
@@ -363,8 +376,20 @@ export interface ParsedCli {
|
|
|
363
376
|
>
|
|
364
377
|
}
|
|
365
378
|
|
|
379
|
+
class CliUsageError extends Error {
|
|
380
|
+
readonly _tag = "CliUsageError"
|
|
381
|
+
|
|
382
|
+
constructor(
|
|
383
|
+
readonly detail: string,
|
|
384
|
+
readonly usage: string,
|
|
385
|
+
) {
|
|
386
|
+
super(`${detail}\n\nUsage: ${usage}`)
|
|
387
|
+
this.name = "CliUsageError"
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
366
391
|
const usageError = (message: string, usage: string) =>
|
|
367
|
-
new
|
|
392
|
+
new CliUsageError(message, usage)
|
|
368
393
|
|
|
369
394
|
const optionLabel = (name: string) => `--${name}`
|
|
370
395
|
|
package/src/cli.test.ts
CHANGED
|
@@ -34,7 +34,9 @@ async function runCli(
|
|
|
34
34
|
function parseJson(result: CliResult) {
|
|
35
35
|
expect(result.exitCode).toBe(0)
|
|
36
36
|
expect(result.stderr).toBe("")
|
|
37
|
-
|
|
37
|
+
const envelope = JSON.parse(result.stdout)
|
|
38
|
+
expect(envelope).toMatchObject({ version: 1, ok: true })
|
|
39
|
+
return envelope.result
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
describe("CLI", () => {
|
|
@@ -79,6 +81,46 @@ describe("CLI", () => {
|
|
|
79
81
|
expect(taggedError.stderr).not.toContain("An error has occurred")
|
|
80
82
|
})
|
|
81
83
|
|
|
84
|
+
test("emits one versioned error envelope for usage and command failures", async () => {
|
|
85
|
+
const usage = await runCli(["unknown", "--json"])
|
|
86
|
+
expect(usage.exitCode).toBe(1)
|
|
87
|
+
expect(usage.stderr).toBe("")
|
|
88
|
+
expect(usage.stdout.trim().split("\n")).toHaveLength(1)
|
|
89
|
+
expect(JSON.parse(usage.stdout)).toEqual({
|
|
90
|
+
version: 1,
|
|
91
|
+
ok: false,
|
|
92
|
+
error: {
|
|
93
|
+
code: "CLI_USAGE",
|
|
94
|
+
message:
|
|
95
|
+
"Unknown command 'unknown'.\n\nUsage: agency <command> [options]",
|
|
96
|
+
fields: {
|
|
97
|
+
detail: "Unknown command 'unknown'.",
|
|
98
|
+
usage: "agency <command> [options]",
|
|
99
|
+
},
|
|
100
|
+
retryable: false,
|
|
101
|
+
remediation:
|
|
102
|
+
"Correct the arguments using the usage value in error.fields.",
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const cwd = await createTempDir()
|
|
107
|
+
tempDirs.push(cwd)
|
|
108
|
+
const commandFailure = await runCli(
|
|
109
|
+
["repo", "list", "--json", "--silent"],
|
|
110
|
+
cwd,
|
|
111
|
+
)
|
|
112
|
+
expect(commandFailure.exitCode).toBe(1)
|
|
113
|
+
expect(commandFailure.stderr).toBe("")
|
|
114
|
+
expect(JSON.parse(commandFailure.stdout)).toMatchObject({
|
|
115
|
+
version: 1,
|
|
116
|
+
ok: false,
|
|
117
|
+
error: {
|
|
118
|
+
code: "WORKBASE_NOT_FOUND",
|
|
119
|
+
retryable: false,
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
82
124
|
test("rejects malformed input before running a command", async () => {
|
|
83
125
|
const parent = await createTempDir()
|
|
84
126
|
tempDirs.push(parent)
|
|
@@ -116,6 +158,7 @@ describe("CLI", () => {
|
|
|
116
158
|
["pr", "Usage: agency pr"],
|
|
117
159
|
["status", "Usage: agency status"],
|
|
118
160
|
["validate", "Usage: agency validate"],
|
|
161
|
+
["context", "Usage: agency context"],
|
|
119
162
|
] as const) {
|
|
120
163
|
const result = await runCli([command, "--help"])
|
|
121
164
|
expect(result.exitCode).toBe(0)
|
|
@@ -178,6 +221,30 @@ describe("CLI", () => {
|
|
|
178
221
|
).toEqual([await realpath(root)])
|
|
179
222
|
})
|
|
180
223
|
|
|
224
|
+
test("lets JSON override silent and disables interactive task input", async () => {
|
|
225
|
+
const root = await createTempDir()
|
|
226
|
+
tempDirs.push(root)
|
|
227
|
+
const result = await runCli(["init", root, "--json", "--silent"])
|
|
228
|
+
expect(parseJson(result)).toEqual({ root })
|
|
229
|
+
|
|
230
|
+
const interactive = await runCli(["task", "new", "--json"])
|
|
231
|
+
expect(interactive.exitCode).toBe(1)
|
|
232
|
+
expect(interactive.stderr).toBe("")
|
|
233
|
+
expect(JSON.parse(interactive.stdout)).toMatchObject({
|
|
234
|
+
version: 1,
|
|
235
|
+
ok: false,
|
|
236
|
+
error: { code: "COMMAND_FAILED", retryable: false },
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
test("envelopes help and version output in machine mode", async () => {
|
|
241
|
+
const help = await runCli(["status", "--help", "--json"])
|
|
242
|
+
expect(parseJson(help)).toContain("Usage: agency status")
|
|
243
|
+
|
|
244
|
+
const version = await runCli(["status", "--version", "--json"])
|
|
245
|
+
expect(parseJson(version)).toEqual({ version: "0.0.0-development" })
|
|
246
|
+
})
|
|
247
|
+
|
|
181
248
|
test("runs a multi-phase domain workflow through subprocesses", async () => {
|
|
182
249
|
const parent = await createTempDir()
|
|
183
250
|
tempDirs.push(parent)
|