@markjaquith/agency 2.59.0 → 2.61.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 +96 -4
- package/cli-main.ts +7 -0
- package/fixtures/protocol/orchestration-recipes.json +19 -0
- package/index.ts +1 -0
- package/package.json +4 -1
- package/schemas/agency-graph-v1.schema.json +53 -0
- package/schemas/agency-kickoff-v1.schema.json +124 -0
- package/src/cli-parser.test.ts +50 -2
- package/src/cli-parser.ts +52 -6
- package/src/commands/task-phase.test.ts +49 -0
- package/src/commands/task.test.ts +77 -1
- package/src/commands/task.ts +123 -7
- package/src/commands/work.test.ts +62 -3
- package/src/commands/work.ts +106 -3
- package/src/graph-schema.test.ts +8 -0
- package/src/graph-schema.ts +7 -0
- package/src/services/ArchiveService.test.ts +18 -1
- package/src/services/GraphMutationService.test.ts +48 -0
- package/src/services/IntegrationService.test.ts +47 -0
- package/src/services/LifecycleTransaction.ts +12 -0
- package/src/services/PhaseService.ts +2 -0
- package/src/services/TaskPhaseService.test.ts +238 -0
- package/src/services/TaskService.ts +265 -6
- package/src/services/WorkbaseService.ts +30 -0
- package/src/services/WorktreeService.ts +9 -6
- package/src/workbase/AGENTS.md +80 -4
- package/src/workbase/frontmatter.test.ts +18 -1
- package/src/workbase/frontmatter.ts +29 -1
- package/src/workbase/kickoff-contract.test.ts +145 -0
- package/src/workbase/kickoff-contract.ts +358 -0
- package/src/workbase/opencode-file.ts +1 -1
- package/src/workbase/schemas.test.ts +36 -0
- package/src/workbase/schemas.ts +33 -1
package/README.md
CHANGED
|
@@ -427,6 +427,24 @@ status: open
|
|
|
427
427
|
---
|
|
428
428
|
```
|
|
429
429
|
|
|
430
|
+
Tasks may also record a purpose and one-way investigation handoff provenance:
|
|
431
|
+
|
|
432
|
+
```yaml
|
|
433
|
+
purpose: implementation
|
|
434
|
+
handoff:
|
|
435
|
+
source:
|
|
436
|
+
kind: phase
|
|
437
|
+
taskId: investigate-checkout
|
|
438
|
+
phaseId: reproduce
|
|
439
|
+
sourceRevision: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
The source revision is historical evidence, not a readiness dependency or a
|
|
443
|
+
claim that the source still has that content. Source rename, archive, restore,
|
|
444
|
+
or later edits do not rewrite it. Destination rename, archive, and restore
|
|
445
|
+
preserve it unchanged. Task show, context, and graph JSON expose the record as
|
|
446
|
+
task metadata; graph output does not add an edge for it.
|
|
447
|
+
|
|
430
448
|
### Multi-Phase Task
|
|
431
449
|
|
|
432
450
|
```yaml
|
|
@@ -684,6 +702,64 @@ The branch defaults to `task/<id>` and the base defaults to `main`.
|
|
|
684
702
|
`task create` is always noninteractive and requires `--repo` for a single-phase
|
|
685
703
|
task. Use it instead of `task new` in scripts and agent workflows.
|
|
686
704
|
|
|
705
|
+
For deterministic callers, creation also accepts recalled context:
|
|
706
|
+
|
|
707
|
+
```text
|
|
708
|
+
agency task create <id>
|
|
709
|
+
--context-repo <alias> --context-base <base> --context-slug <id>
|
|
710
|
+
[--authoritative-source <absolute-path-or-http-url>...] --json
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
Recalled values are used instead of rediscovery but must agree with equivalent
|
|
714
|
+
explicit flags. The preferred slug must equal the created ID, repository aliases
|
|
715
|
+
are validated by normal task creation, and authoritative sources must be absolute
|
|
716
|
+
paths or HTTP(S) URLs.
|
|
717
|
+
|
|
718
|
+
Machine output adds fields without changing the version 1 protocol envelope. It
|
|
719
|
+
includes `selector`, absolute `documentPath`, the document `revision`, full
|
|
720
|
+
`validation`, normalized `recalledContext`, and `evidence`. Evidence version 1 is
|
|
721
|
+
an auditable local payload containing the canonical workbase root, target and
|
|
722
|
+
document identities, aggregate workbase revision, configuration revision,
|
|
723
|
+
repository-mapping revision, kickoff-contract version, validity result, recalled
|
|
724
|
+
context, and a digest over those fields. It is not a signature or an authority
|
|
725
|
+
grant. A different workbase, target, document revision, document set,
|
|
726
|
+
configuration, repository mapping, contract version, or payload digest
|
|
727
|
+
invalidates reuse. Older creation output without evidence remains compatible;
|
|
728
|
+
preflight simply validates again. The published machine schema is
|
|
729
|
+
`schemas/agency-kickoff-v1.schema.json`.
|
|
730
|
+
|
|
731
|
+
Create investigation-only work with the existing task architecture:
|
|
732
|
+
|
|
733
|
+
```text
|
|
734
|
+
agency task create <id> --purpose investigation --repo <alias> [options]
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
This records `purpose: investigation` and generates explicit Investigation
|
|
738
|
+
Boundary, Evidence, Findings, Recommendation, Implementation Handoff, and
|
|
739
|
+
Important Decisions sections. A no-change recommendation is a valid result;
|
|
740
|
+
implementation does not belong in the investigation task merely because that
|
|
741
|
+
task has execution authority.
|
|
742
|
+
|
|
743
|
+
Create a distinct implementation task from an investigation task or phase:
|
|
744
|
+
|
|
745
|
+
```text
|
|
746
|
+
agency task handoff <investigation-task> <new-task> --repo <alias> [options] --json
|
|
747
|
+
agency task handoff <investigation-task> <new-task> --source-phase <phase> --repo <alias> [options] --json
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
The destination ID must be new across active and archived work; Agency never
|
|
751
|
+
restores, reopens, renames, or reuses a matching item. Explicit requests for a
|
|
752
|
+
new, separate, or follow-up item always override reuse, even when the subject or
|
|
753
|
+
suggested ID matches existing work. The transaction writes only the destination
|
|
754
|
+
task and any requested epic backlink, while revision-checking the source.
|
|
755
|
+
|
|
756
|
+
JSON returns the exact destination selector, directory and document path,
|
|
757
|
+
branch/base and revision; source selector, document path and captured revision;
|
|
758
|
+
the validation report; and the later `worktree prepare` target and command.
|
|
759
|
+
Handoff creation does not prepare a worktree, change status, launch an agent, or
|
|
760
|
+
perform UI actions. Run `agency context <new-task> --json` to verify authority,
|
|
761
|
+
and prepare or launch only when separately requested.
|
|
762
|
+
|
|
687
763
|
Create a multi-phase task container:
|
|
688
764
|
|
|
689
765
|
```text
|
|
@@ -892,7 +968,7 @@ until restored.
|
|
|
892
968
|
|
|
893
969
|
```text
|
|
894
970
|
agency work [<directory> | --epic <epic-id>] [--runner <name>] [--auto] [--print-command]
|
|
895
|
-
agency work prepare [target] [--dry-run] [--json]
|
|
971
|
+
agency work prepare [target] [--evidence <json-or-path>] [--dry-run] [--json]
|
|
896
972
|
agency worktree <list|inspect|prepare|remove|rebuild|repair>
|
|
897
973
|
agency push [--json]
|
|
898
974
|
agency pr create <task-id> [phase-id] [--draft] [--force] [--json]
|
|
@@ -912,9 +988,25 @@ their built-in presets. Launches are interactive and promptless by default; use
|
|
|
912
988
|
`agency work prepare` resolves an execution unit and creates or reuses its
|
|
913
989
|
writable and reference worktrees, or its single pinned review checkout, without
|
|
914
990
|
launching an agent or changing status.
|
|
915
|
-
Its JSON result includes
|
|
916
|
-
|
|
917
|
-
|
|
991
|
+
Its JSON result includes the workspace, validation result, whether supplied
|
|
992
|
+
evidence was `reused` or `refreshed` with stable reason strings, refreshed
|
|
993
|
+
evidence, and a versioned `agency-kickoff-v1` orchestration plan. The plan has a
|
|
994
|
+
deterministic idempotency key and ordered, retry-safe actions for worktree
|
|
995
|
+
preflight/preparation, a background tab, side-by-side task document,
|
|
996
|
+
`agency work . --auto`, and exactly one final `agency context <document> --json`.
|
|
997
|
+
The evidence argument may be an evidence object, task-creation JSON, or a path to
|
|
998
|
+
either. Use `--dry-run` to report planned fetch, branch, and worktree changes
|
|
999
|
+
without applying them. Validation reuse never skips readiness, active-claim,
|
|
1000
|
+
repository, ownership, reference-drift, dirty-workspace, or worktree safety
|
|
1001
|
+
checks.
|
|
1002
|
+
|
|
1003
|
+
The authoritative implementation locations for this contract are
|
|
1004
|
+
`src/commands/task.ts` (creation output),
|
|
1005
|
+
`src/workbase/kickoff-contract.ts` (evidence and orchestration schemas),
|
|
1006
|
+
`src/commands/work.ts` (launch preflight),
|
|
1007
|
+
`src/services/WorktreeService.ts` (workspace safety), and
|
|
1008
|
+
`src/workbase/AGENTS.md` (generated OpenCode guidance). These paths are the
|
|
1009
|
+
deterministic source-location fixture for compatible orchestrators.
|
|
918
1010
|
|
|
919
1011
|
`agency worktree list` and `inspect` report each declared checkout's expected and
|
|
920
1012
|
registered path, branch, commit, Agency owner, dirtiness, and conflicts. `prepare`
|
package/cli-main.ts
CHANGED
|
@@ -494,6 +494,12 @@ const commands: Record<string, Command> = {
|
|
|
494
494
|
references: options.reference,
|
|
495
495
|
branch: options.branch,
|
|
496
496
|
base: options.base,
|
|
497
|
+
contextRepo: options["context-repo"],
|
|
498
|
+
contextBase: options["context-base"],
|
|
499
|
+
contextSlug: options["context-slug"],
|
|
500
|
+
authoritativeSources: options["authoritative-source"],
|
|
501
|
+
purpose: options.purpose,
|
|
502
|
+
sourcePhase: options["source-phase"],
|
|
497
503
|
clearReferences: options["clear-references"],
|
|
498
504
|
prUrl: options["pr-url"],
|
|
499
505
|
clearPr: options["clear-pr"],
|
|
@@ -560,6 +566,7 @@ const commands: Record<string, Command> = {
|
|
|
560
566
|
cwd: options.cwd,
|
|
561
567
|
taskId: options.task,
|
|
562
568
|
phaseId: options.phase,
|
|
569
|
+
evidence: options.evidence,
|
|
563
570
|
}),
|
|
564
571
|
)
|
|
565
572
|
},
|
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
["worktree", "inspect", "checkout", "ui", "--json"],
|
|
4
4
|
["next", "--json"],
|
|
5
5
|
["graph", "--ready", "--json"],
|
|
6
|
+
[
|
|
7
|
+
"task",
|
|
8
|
+
"create",
|
|
9
|
+
"investigate-checkout",
|
|
10
|
+
"--purpose",
|
|
11
|
+
"investigation",
|
|
12
|
+
"--repo",
|
|
13
|
+
"agency",
|
|
14
|
+
"--json"
|
|
15
|
+
],
|
|
16
|
+
[
|
|
17
|
+
"task",
|
|
18
|
+
"handoff",
|
|
19
|
+
"investigate-checkout",
|
|
20
|
+
"implement-checkout",
|
|
21
|
+
"--repo",
|
|
22
|
+
"agency",
|
|
23
|
+
"--json"
|
|
24
|
+
],
|
|
6
25
|
[
|
|
7
26
|
"claim",
|
|
8
27
|
"checkout",
|
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.61.0",
|
|
4
4
|
"description": "Manage agentic work across repositories with durable workbases",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -44,6 +44,9 @@
|
|
|
44
44
|
"./schemas/agency-graph-v1.json": {
|
|
45
45
|
"default": "./schemas/agency-graph-v1.schema.json"
|
|
46
46
|
},
|
|
47
|
+
"./schemas/agency-kickoff-v1.json": {
|
|
48
|
+
"default": "./schemas/agency-kickoff-v1.schema.json"
|
|
49
|
+
},
|
|
47
50
|
"./fixtures/protocol/success.json": {
|
|
48
51
|
"default": "./fixtures/protocol/success.json"
|
|
49
52
|
},
|
|
@@ -333,6 +333,8 @@
|
|
|
333
333
|
"ticketUrl": { "type": ["string", "null"] },
|
|
334
334
|
"description": { "type": "string" },
|
|
335
335
|
"epic": { "type": "string" },
|
|
336
|
+
"purpose": { "$ref": "#/$defs/taskPurpose" },
|
|
337
|
+
"handoff": { "$ref": "#/$defs/taskHandoff" },
|
|
336
338
|
"repo": { "type": "string" },
|
|
337
339
|
"repos": {
|
|
338
340
|
"type": "array",
|
|
@@ -354,6 +356,8 @@
|
|
|
354
356
|
"ticketUrl": { "type": ["string", "null"] },
|
|
355
357
|
"description": { "type": "string" },
|
|
356
358
|
"epic": { "type": "string" },
|
|
359
|
+
"purpose": { "$ref": "#/$defs/taskPurpose" },
|
|
360
|
+
"handoff": { "$ref": "#/$defs/taskHandoff" },
|
|
357
361
|
"phases": {
|
|
358
362
|
"type": "array",
|
|
359
363
|
"items": { "$ref": "#/$defs/dependency" }
|
|
@@ -416,6 +420,8 @@
|
|
|
416
420
|
"ticketUrl": { "type": ["string", "null"] },
|
|
417
421
|
"description": { "type": "string" },
|
|
418
422
|
"epic": { "type": "string" },
|
|
423
|
+
"purpose": { "$ref": "#/$defs/taskPurpose" },
|
|
424
|
+
"handoff": { "$ref": "#/$defs/taskHandoff" },
|
|
419
425
|
"review": { "$ref": "#/$defs/reviewRecord" },
|
|
420
426
|
"status": { "$ref": "#/$defs/status" },
|
|
421
427
|
"claim": { "$ref": "#/$defs/claim" },
|
|
@@ -452,6 +458,8 @@
|
|
|
452
458
|
"phaseId": { "type": "string" },
|
|
453
459
|
"ticketUrl": { "type": ["string", "null"] },
|
|
454
460
|
"epic": { "type": "string" },
|
|
461
|
+
"purpose": { "$ref": "#/$defs/taskPurpose" },
|
|
462
|
+
"handoff": { "$ref": "#/$defs/taskHandoff" },
|
|
455
463
|
"description": { "type": "string" },
|
|
456
464
|
"repo": { "type": "string" },
|
|
457
465
|
"repos": {
|
|
@@ -474,6 +482,8 @@
|
|
|
474
482
|
"ticketUrl": { "type": ["string", "null"] },
|
|
475
483
|
"description": { "type": "string" },
|
|
476
484
|
"epic": { "type": "string" },
|
|
485
|
+
"purpose": { "$ref": "#/$defs/taskPurpose" },
|
|
486
|
+
"handoff": { "$ref": "#/$defs/taskHandoff" },
|
|
477
487
|
"review": { "$ref": "#/$defs/reviewRecord" },
|
|
478
488
|
"status": { "$ref": "#/$defs/status" },
|
|
479
489
|
"claim": { "$ref": "#/$defs/claim" }
|
|
@@ -481,6 +491,49 @@
|
|
|
481
491
|
}
|
|
482
492
|
]
|
|
483
493
|
},
|
|
494
|
+
"taskPurpose": {
|
|
495
|
+
"enum": ["investigation", "implementation"]
|
|
496
|
+
},
|
|
497
|
+
"taskHandoff": {
|
|
498
|
+
"type": "object",
|
|
499
|
+
"additionalProperties": false,
|
|
500
|
+
"required": ["source", "sourceRevision"],
|
|
501
|
+
"properties": {
|
|
502
|
+
"source": {
|
|
503
|
+
"oneOf": [
|
|
504
|
+
{
|
|
505
|
+
"type": "object",
|
|
506
|
+
"additionalProperties": false,
|
|
507
|
+
"required": ["kind", "taskId"],
|
|
508
|
+
"properties": {
|
|
509
|
+
"kind": { "const": "task" },
|
|
510
|
+
"taskId": {
|
|
511
|
+
"type": "string",
|
|
512
|
+
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]*$"
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
"type": "object",
|
|
518
|
+
"additionalProperties": false,
|
|
519
|
+
"required": ["kind", "taskId", "phaseId"],
|
|
520
|
+
"properties": {
|
|
521
|
+
"kind": { "const": "phase" },
|
|
522
|
+
"taskId": {
|
|
523
|
+
"type": "string",
|
|
524
|
+
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]*$"
|
|
525
|
+
},
|
|
526
|
+
"phaseId": {
|
|
527
|
+
"type": "string",
|
|
528
|
+
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]*$"
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
]
|
|
533
|
+
},
|
|
534
|
+
"sourceRevision": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
|
|
535
|
+
}
|
|
536
|
+
},
|
|
484
537
|
"claim": {
|
|
485
538
|
"type": "object",
|
|
486
539
|
"additionalProperties": false,
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/markjaquith/agency/schemas/agency-kickoff-v1.schema.json",
|
|
4
|
+
"title": "Agency kickoff contract v1",
|
|
5
|
+
"oneOf": [
|
|
6
|
+
{ "$ref": "#/$defs/validationEvidence" },
|
|
7
|
+
{ "$ref": "#/$defs/kickoffPlan" }
|
|
8
|
+
],
|
|
9
|
+
"$defs": {
|
|
10
|
+
"recalledContext": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"required": ["preferredSlug", "authoritativeSources"],
|
|
13
|
+
"properties": {
|
|
14
|
+
"repo": { "type": "string", "minLength": 1 },
|
|
15
|
+
"base": { "type": "string", "minLength": 1 },
|
|
16
|
+
"preferredSlug": { "type": "string", "minLength": 1 },
|
|
17
|
+
"authoritativeSources": {
|
|
18
|
+
"type": "array",
|
|
19
|
+
"items": { "type": "string", "minLength": 1 }
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"additionalProperties": false
|
|
23
|
+
},
|
|
24
|
+
"validationEvidence": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"required": [
|
|
27
|
+
"version",
|
|
28
|
+
"workbaseRoot",
|
|
29
|
+
"target",
|
|
30
|
+
"documentPath",
|
|
31
|
+
"documentRevision",
|
|
32
|
+
"workbaseRevision",
|
|
33
|
+
"configRevision",
|
|
34
|
+
"repositoryMappingRevision",
|
|
35
|
+
"valid",
|
|
36
|
+
"recalledContext",
|
|
37
|
+
"digest"
|
|
38
|
+
],
|
|
39
|
+
"properties": {
|
|
40
|
+
"version": { "const": 1 },
|
|
41
|
+
"workbaseRoot": { "type": "string" },
|
|
42
|
+
"target": { "type": "string" },
|
|
43
|
+
"documentPath": { "type": "string" },
|
|
44
|
+
"documentRevision": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
45
|
+
"workbaseRevision": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
46
|
+
"configRevision": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
47
|
+
"repositoryMappingRevision": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
50
|
+
},
|
|
51
|
+
"valid": { "const": true },
|
|
52
|
+
"recalledContext": { "$ref": "#/$defs/recalledContext" },
|
|
53
|
+
"digest": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
|
|
54
|
+
},
|
|
55
|
+
"additionalProperties": false
|
|
56
|
+
},
|
|
57
|
+
"kickoffPlan": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"required": [
|
|
60
|
+
"version",
|
|
61
|
+
"idempotencyKey",
|
|
62
|
+
"workbaseRoot",
|
|
63
|
+
"target",
|
|
64
|
+
"documentRevision",
|
|
65
|
+
"sourceLocations",
|
|
66
|
+
"taskDirectory",
|
|
67
|
+
"taskDocument",
|
|
68
|
+
"phaseDocument",
|
|
69
|
+
"preparedCheckout",
|
|
70
|
+
"orchestrator",
|
|
71
|
+
"steps",
|
|
72
|
+
"successFields"
|
|
73
|
+
],
|
|
74
|
+
"properties": {
|
|
75
|
+
"version": { "const": 1 },
|
|
76
|
+
"idempotencyKey": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
77
|
+
"workbaseRoot": { "type": "string" },
|
|
78
|
+
"target": { "type": "string" },
|
|
79
|
+
"documentRevision": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
80
|
+
"sourceLocations": { "type": "array", "items": { "type": "string" } },
|
|
81
|
+
"taskDirectory": { "type": "string" },
|
|
82
|
+
"taskDocument": { "type": "string" },
|
|
83
|
+
"phaseDocument": { "type": ["string", "null"] },
|
|
84
|
+
"preparedCheckout": { "type": ["string", "null"] },
|
|
85
|
+
"orchestrator": {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"required": [
|
|
88
|
+
"capability",
|
|
89
|
+
"knownCurrentCommandsBypassDiscovery",
|
|
90
|
+
"fallback"
|
|
91
|
+
],
|
|
92
|
+
"properties": {
|
|
93
|
+
"capability": { "const": "agency-kickoff-v1" },
|
|
94
|
+
"knownCurrentCommandsBypassDiscovery": { "const": true },
|
|
95
|
+
"fallback": { "type": "string" }
|
|
96
|
+
},
|
|
97
|
+
"additionalProperties": false
|
|
98
|
+
},
|
|
99
|
+
"steps": {
|
|
100
|
+
"type": "array",
|
|
101
|
+
"minItems": 6,
|
|
102
|
+
"items": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"required": ["id"],
|
|
105
|
+
"properties": {
|
|
106
|
+
"id": { "type": "string" },
|
|
107
|
+
"argv": { "type": "array", "items": { "type": "string" } },
|
|
108
|
+
"cwd": { "type": "string" },
|
|
109
|
+
"action": { "type": "string" },
|
|
110
|
+
"path": { "type": "string" },
|
|
111
|
+
"retry": { "type": "string" },
|
|
112
|
+
"recovery": { "type": "string" },
|
|
113
|
+
"idempotencyKey": { "type": "string" },
|
|
114
|
+
"exactlyOnce": { "type": "boolean" }
|
|
115
|
+
},
|
|
116
|
+
"additionalProperties": false
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"successFields": { "type": "array", "items": { "type": "string" } }
|
|
120
|
+
},
|
|
121
|
+
"additionalProperties": false
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
package/src/cli-parser.test.ts
CHANGED
|
@@ -759,10 +759,58 @@ describe("strict CLI parsing", () => {
|
|
|
759
759
|
)
|
|
760
760
|
expectUsageError(
|
|
761
761
|
["task", "crate"],
|
|
762
|
-
"agency task <new|create|list|show|status|update|rename|move|dependency>",
|
|
762
|
+
"agency task <new|create|handoff|list|show|status|update|rename|move|dependency>",
|
|
763
763
|
)
|
|
764
764
|
})
|
|
765
765
|
|
|
766
|
+
test("parses investigation creation and explicit handoff commands", () => {
|
|
767
|
+
expect(
|
|
768
|
+
parseCli([
|
|
769
|
+
"task",
|
|
770
|
+
"create",
|
|
771
|
+
"investigate",
|
|
772
|
+
"--purpose",
|
|
773
|
+
"investigation",
|
|
774
|
+
"--repo",
|
|
775
|
+
"agency",
|
|
776
|
+
]),
|
|
777
|
+
).toMatchObject({
|
|
778
|
+
args: ["create", "investigate"],
|
|
779
|
+
values: { purpose: "investigation", repo: ["agency"] },
|
|
780
|
+
})
|
|
781
|
+
expect(
|
|
782
|
+
parseCli([
|
|
783
|
+
"task",
|
|
784
|
+
"handoff",
|
|
785
|
+
"investigate",
|
|
786
|
+
"implement",
|
|
787
|
+
"--source-phase",
|
|
788
|
+
"evidence",
|
|
789
|
+
"--repo",
|
|
790
|
+
"agency",
|
|
791
|
+
"--json",
|
|
792
|
+
]),
|
|
793
|
+
).toMatchObject({
|
|
794
|
+
args: ["handoff", "investigate", "implement"],
|
|
795
|
+
values: {
|
|
796
|
+
"source-phase": "evidence",
|
|
797
|
+
repo: ["agency"],
|
|
798
|
+
json: true,
|
|
799
|
+
},
|
|
800
|
+
})
|
|
801
|
+
expect(() =>
|
|
802
|
+
parseCli([
|
|
803
|
+
"task",
|
|
804
|
+
"create",
|
|
805
|
+
"investigate",
|
|
806
|
+
"--purpose",
|
|
807
|
+
"unknown",
|
|
808
|
+
"--repo",
|
|
809
|
+
"agency",
|
|
810
|
+
]),
|
|
811
|
+
).toThrow("Invalid '--purpose' value")
|
|
812
|
+
})
|
|
813
|
+
|
|
766
814
|
test("parses graph mutation commands and rejects unsafe ambiguity", () => {
|
|
767
815
|
expect(
|
|
768
816
|
parseCli([
|
|
@@ -809,7 +857,7 @@ describe("strict CLI parsing", () => {
|
|
|
809
857
|
|
|
810
858
|
test("rejects required-option omissions and explicit conflicts", () => {
|
|
811
859
|
expect(() => parseCli(["task", "create", "example"])).toThrow(
|
|
812
|
-
"--repo' is required",
|
|
860
|
+
"--repo' or '--context-repo' is required",
|
|
813
861
|
)
|
|
814
862
|
for (const option of ["repo", "reference", "branch", "base"] as const) {
|
|
815
863
|
const value = option === "reference" ? "other:main" : "value"
|
package/src/cli-parser.ts
CHANGED
|
@@ -85,6 +85,12 @@ const taskCreateOptions = {
|
|
|
85
85
|
review: { type: "string" },
|
|
86
86
|
"pull-request": { type: "string" },
|
|
87
87
|
ref: { type: "string" },
|
|
88
|
+
"context-repo": { type: "string" },
|
|
89
|
+
"context-base": { type: "string" },
|
|
90
|
+
"context-slug": { type: "string" },
|
|
91
|
+
"authoritative-source": { type: "string", multiple: true },
|
|
92
|
+
purpose: { type: "string" },
|
|
93
|
+
"source-phase": { type: "string" },
|
|
88
94
|
} satisfies OptionConfig
|
|
89
95
|
|
|
90
96
|
const phaseCreateOptions = {
|
|
@@ -363,7 +369,7 @@ const commands = {
|
|
|
363
369
|
},
|
|
364
370
|
task: {
|
|
365
371
|
usage:
|
|
366
|
-
"agency task <new|create|list|show|status|update|rename|move|dependency>",
|
|
372
|
+
"agency task <new|create|handoff|list|show|status|update|rename|move|dependency>",
|
|
367
373
|
options: {
|
|
368
374
|
...taskCreateOptions,
|
|
369
375
|
...newWorkOptions,
|
|
@@ -386,6 +392,7 @@ const commands = {
|
|
|
386
392
|
"reference",
|
|
387
393
|
"branch",
|
|
388
394
|
"base",
|
|
395
|
+
"purpose",
|
|
389
396
|
"multi-phase",
|
|
390
397
|
"work",
|
|
391
398
|
"auto",
|
|
@@ -396,7 +403,7 @@ const commands = {
|
|
|
396
403
|
},
|
|
397
404
|
create: {
|
|
398
405
|
usage:
|
|
399
|
-
"agency task create <id> (--repo <alias> | --multi-phase | --review <alias> (--pull-request <value> | --ref <remote-ref>)) [options]",
|
|
406
|
+
"agency task create <id> (--repo <alias> | --context-repo <alias> | --multi-phase | --review <alias> (--pull-request <value> | --ref <remote-ref>)) [options]",
|
|
400
407
|
minArgs: 1,
|
|
401
408
|
maxArgs: 1,
|
|
402
409
|
options: [
|
|
@@ -407,12 +414,36 @@ const commands = {
|
|
|
407
414
|
"reference",
|
|
408
415
|
"branch",
|
|
409
416
|
"base",
|
|
417
|
+
"purpose",
|
|
410
418
|
"multi-phase",
|
|
411
419
|
"review",
|
|
412
420
|
"pull-request",
|
|
413
421
|
"ref",
|
|
422
|
+
"context-repo",
|
|
423
|
+
"context-base",
|
|
424
|
+
"context-slug",
|
|
425
|
+
"authoritative-source",
|
|
414
426
|
"json",
|
|
415
427
|
],
|
|
428
|
+
repeatable: ["reference", "authoritative-source"],
|
|
429
|
+
},
|
|
430
|
+
handoff: {
|
|
431
|
+
usage:
|
|
432
|
+
"agency task handoff <source-task-id> <new-task-id> --repo <alias> [--source-phase <phase-id>] [options]",
|
|
433
|
+
minArgs: 2,
|
|
434
|
+
maxArgs: 2,
|
|
435
|
+
options: [
|
|
436
|
+
"ticket-url",
|
|
437
|
+
"description",
|
|
438
|
+
"epic",
|
|
439
|
+
"repo",
|
|
440
|
+
"reference",
|
|
441
|
+
"branch",
|
|
442
|
+
"base",
|
|
443
|
+
"source-phase",
|
|
444
|
+
"json",
|
|
445
|
+
],
|
|
446
|
+
required: ["repo"],
|
|
416
447
|
repeatable: ["reference"],
|
|
417
448
|
},
|
|
418
449
|
list: {
|
|
@@ -873,6 +904,7 @@ const commands = {
|
|
|
873
904
|
opencode: { type: "boolean" },
|
|
874
905
|
claude: { type: "boolean" },
|
|
875
906
|
force: { type: "boolean" },
|
|
907
|
+
evidence: { type: "string" },
|
|
876
908
|
},
|
|
877
909
|
command: {
|
|
878
910
|
usage:
|
|
@@ -891,6 +923,7 @@ const commands = {
|
|
|
891
923
|
"opencode",
|
|
892
924
|
"claude",
|
|
893
925
|
"force",
|
|
926
|
+
"evidence",
|
|
894
927
|
],
|
|
895
928
|
conflicts: [
|
|
896
929
|
["opencode", "claude"],
|
|
@@ -1221,6 +1254,12 @@ function validateTaskCreate(
|
|
|
1221
1254
|
spec: LeafCommand,
|
|
1222
1255
|
requireRepo: boolean,
|
|
1223
1256
|
) {
|
|
1257
|
+
if (values.purpose !== undefined && values.purpose !== "investigation") {
|
|
1258
|
+
throw usageError(
|
|
1259
|
+
`Invalid '--purpose' value '${String(values.purpose)}'. Expected: investigation.`,
|
|
1260
|
+
spec.usage,
|
|
1261
|
+
)
|
|
1262
|
+
}
|
|
1224
1263
|
const review = values.review !== undefined
|
|
1225
1264
|
const pullRequest = values["pull-request"] !== undefined
|
|
1226
1265
|
const ref = values.ref !== undefined
|
|
@@ -1262,9 +1301,13 @@ function validateTaskCreate(
|
|
|
1262
1301
|
)
|
|
1263
1302
|
}
|
|
1264
1303
|
}
|
|
1265
|
-
} else if (
|
|
1304
|
+
} else if (
|
|
1305
|
+
requireRepo &&
|
|
1306
|
+
values.repo === undefined &&
|
|
1307
|
+
values["context-repo"] === undefined
|
|
1308
|
+
) {
|
|
1266
1309
|
throw usageError(
|
|
1267
|
-
"Option '--repo' is required unless '--multi-phase' is used.",
|
|
1310
|
+
"Option '--repo' or '--context-repo' is required unless '--multi-phase' is used.",
|
|
1268
1311
|
spec.usage,
|
|
1269
1312
|
)
|
|
1270
1313
|
}
|
|
@@ -1620,9 +1663,12 @@ export function parseCli(args: readonly string[]): ParsedCli {
|
|
|
1620
1663
|
spec.usage,
|
|
1621
1664
|
)
|
|
1622
1665
|
}
|
|
1623
|
-
if (
|
|
1666
|
+
if (
|
|
1667
|
+
!preparing &&
|
|
1668
|
+
(parsed.values.json || parsed.values["dry-run"] || parsed.values.evidence)
|
|
1669
|
+
) {
|
|
1624
1670
|
throw usageError(
|
|
1625
|
-
"Options '--json'
|
|
1671
|
+
"Options '--json', '--dry-run', and '--evidence' are only valid with 'agency work prepare'.",
|
|
1626
1672
|
spec.usage,
|
|
1627
1673
|
)
|
|
1628
1674
|
}
|
|
@@ -86,6 +86,55 @@ describe("task and phase command JSON output", () => {
|
|
|
86
86
|
})
|
|
87
87
|
})
|
|
88
88
|
|
|
89
|
+
test("returns complete handoff JSON without preparing work", async () => {
|
|
90
|
+
await runTestEffect(
|
|
91
|
+
task({
|
|
92
|
+
subcommand: "create",
|
|
93
|
+
args: ["investigate"],
|
|
94
|
+
repo: "agency",
|
|
95
|
+
purpose: "investigation",
|
|
96
|
+
cwd: root,
|
|
97
|
+
silent: true,
|
|
98
|
+
}),
|
|
99
|
+
)
|
|
100
|
+
const logs = await captureLogs(() =>
|
|
101
|
+
runTestEffect(
|
|
102
|
+
task({
|
|
103
|
+
subcommand: "handoff",
|
|
104
|
+
args: ["investigate", "implement"],
|
|
105
|
+
repo: "agency",
|
|
106
|
+
cwd: root,
|
|
107
|
+
json: true,
|
|
108
|
+
}),
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
const output = JSON.parse(logs[0]!)
|
|
112
|
+
expect(output).toMatchObject({
|
|
113
|
+
task: {
|
|
114
|
+
id: "implement",
|
|
115
|
+
selector: "task/implement",
|
|
116
|
+
directory: join(root, "tasks/implement"),
|
|
117
|
+
documentPath: join(root, "tasks/implement/TASK.md"),
|
|
118
|
+
branch: "task/implement",
|
|
119
|
+
base: "main",
|
|
120
|
+
},
|
|
121
|
+
source: {
|
|
122
|
+
selector: "task/investigate",
|
|
123
|
+
documentPath: join(root, "tasks/investigate/TASK.md"),
|
|
124
|
+
},
|
|
125
|
+
validation: { valid: true },
|
|
126
|
+
worktreePrepare: {
|
|
127
|
+
target: "task/implement",
|
|
128
|
+
command: ["agency", "worktree", "prepare", "implement"],
|
|
129
|
+
},
|
|
130
|
+
})
|
|
131
|
+
expect(output.task.revision).toMatch(/^[a-f0-9]{64}$/)
|
|
132
|
+
expect(output.source.revision).toMatch(/^[a-f0-9]{64}$/)
|
|
133
|
+
expect(await Bun.file(join(root, "tasks/implement/code")).exists()).toBe(
|
|
134
|
+
false,
|
|
135
|
+
)
|
|
136
|
+
})
|
|
137
|
+
|
|
89
138
|
test("lists and shows phase metadata without Markdown content", async () => {
|
|
90
139
|
const listLogs = await captureLogs(() =>
|
|
91
140
|
runTestEffect(
|