@markjaquith/agency 3.14.0 → 3.16.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 +38 -3
- package/cli-main.ts +20 -1
- package/package.json +1 -1
- package/src/act-schema.ts +1 -0
- package/src/cli-parser.ts +20 -1
- package/src/commands/act-actions.ts +82 -9
- package/src/commands/act.ts +2 -0
- package/src/commands/config.ts +43 -0
- package/src/commands/phase.ts +3 -0
- package/src/commands/review.ts +31 -7
- package/src/commands/status.ts +7 -1
- package/src/commands/sync.ts +5 -0
- package/src/commands/task.ts +3 -0
- package/src/services/ContextService.ts +2 -0
- package/src/services/PhaseService.ts +7 -1
- package/src/services/ReviewService.ts +47 -0
- package/src/services/SyncService.ts +10 -0
- package/src/services/TaskService.ts +16 -1
- package/src/services/WorkbaseService.ts +13 -0
- package/src/services/auto-archive.ts +82 -0
- package/src/workbase/AGENTS.md +17 -3
- package/src/workbase/schemas.ts +1 -0
package/README.md
CHANGED
|
@@ -710,7 +710,7 @@ external chooser continues to offer goals and **Browse items** through that choo
|
|
|
710
710
|
| Split work | Add a phase / split this task; name the existing work's first phase | `split` |
|
|
711
711
|
| Work on a task or phase | Work on this item | `work` |
|
|
712
712
|
| Move from investigation to execution | Create implementation follow-up | `handoff` |
|
|
713
|
-
| Review someone else's work | Review a PR, or a remote branch/commit
|
|
713
|
+
| Review someone else's work | Review a PR, or a remote branch/commit; finish a review | `review`, `review-ref`, `finish-review` |
|
|
714
714
|
| Publish or update PR status | Push without a PR, refresh provider state, create a PR, mark a GitHub PR ready, or close it | `push`, `sync`, `pr`, `pr-ready`, `pr-close` |
|
|
715
715
|
| Maintain review work | Fetch and repin a review task to its current source | `review-refresh` |
|
|
716
716
|
| Organize work | Rename an item, move a task into/out of an epic, or add/remove a sibling dependency | `rename`, `move-to-epic`, `remove-from-epic`, `dependency-add`, `dependency-remove` |
|
|
@@ -723,7 +723,9 @@ on the new item now** or **Finish**. Only choosing Work prepares checkouts and
|
|
|
723
723
|
launches the configured runner; creation alone leaves the item for later.
|
|
724
724
|
Investigation handoff creates a distinct implementation item with source and
|
|
725
725
|
revision provenance. Completing a non-PR outcome requires a durable summary.
|
|
726
|
-
|
|
726
|
+
**Finish review** marks a review task done without a summary; review tasks offer
|
|
727
|
+
it instead of non-PR completion. PR-backed completion comes from provider
|
|
728
|
+
reconciliation after merge.
|
|
727
729
|
|
|
728
730
|
**Refresh Agency state** reads the provider and reconciles local records. The
|
|
729
731
|
explicit **GitHub PR ready/close** actions mutate the recorded GitHub URL, then
|
|
@@ -1060,6 +1062,7 @@ Create a pinned, read-only review task from the selected alias's origin:
|
|
|
1060
1062
|
agency task create <id> --review <alias> --pull-request <url-or-number>
|
|
1061
1063
|
agency task create <id> --review <alias> --ref <remote-ref>
|
|
1062
1064
|
agency review refresh <id> [--if-revision <hash>] [--json]
|
|
1065
|
+
agency review finish <id> [--if-revision <hash>] [--json]
|
|
1063
1066
|
```
|
|
1064
1067
|
|
|
1065
1068
|
Review creation fetches the source and records its exact 40-character commit.
|
|
@@ -1069,7 +1072,10 @@ detached checkout and no writable branch. Source movement is observed separately
|
|
|
1069
1072
|
from the pin and applied only by `review refresh`; sync, doctor, work, cleanup,
|
|
1070
1073
|
and archive never move the pin implicitly. Dirty or structurally unexpected
|
|
1071
1074
|
review checkouts block refresh and cleanup. Review tasks support the normal status
|
|
1072
|
-
lifecycle, but reject phase conversion and delivery PR operations.
|
|
1075
|
+
lifecycle, but reject phase conversion and delivery PR operations. Because a
|
|
1076
|
+
review has no delivery pull request, `review finish` marks an open, working, or
|
|
1077
|
+
delegated review done without the `--no-pull-request --summary` requirement
|
|
1078
|
+
that applies to other work.
|
|
1073
1079
|
Each active or archived review task owns one internal task-scoped pin ref. A
|
|
1074
1080
|
refresh advances that ref transactionally; failed creation removes it. Archiving
|
|
1075
1081
|
retains the pin so a deleted source can still be restored and inspected.
|
|
@@ -1223,6 +1229,35 @@ parent epic implicitly. Versioned lifecycle provenance preserves parent
|
|
|
1223
1229
|
declarations and dependency edges for restoration. Archived IDs are reserved
|
|
1224
1230
|
until restored.
|
|
1225
1231
|
|
|
1232
|
+
#### Automatic archiving
|
|
1233
|
+
|
|
1234
|
+
```text
|
|
1235
|
+
agency config auto-archive # show the effective setting
|
|
1236
|
+
agency config auto-archive on # enable for this workbase
|
|
1237
|
+
agency config auto-archive off # disable
|
|
1238
|
+
```
|
|
1239
|
+
|
|
1240
|
+
The boolean `autoArchive` setting in the workbase's `agency.json` defaults to
|
|
1241
|
+
`false`. Enabling it authorizes automatic task archiving on future terminal
|
|
1242
|
+
transitions; it does not sweep existing terminal work. `agency act --json`
|
|
1243
|
+
lists these configuration commands, and context/status JSON includes the
|
|
1244
|
+
effective setting.
|
|
1245
|
+
|
|
1246
|
+
Explicit `task status` / `phase status` changes to `done` or `dropped`, non-PR
|
|
1247
|
+
completion, `review finish`, and merged-PR reconciliation automatically
|
|
1248
|
+
attempt to archive the owning task. A multi-phase task waits until every phase
|
|
1249
|
+
is terminal; individual phases and epics are never auto-archived. Sync defers
|
|
1250
|
+
cleanup until its reconciliation pass finishes, and `sync --dry-run` never
|
|
1251
|
+
archives anything.
|
|
1252
|
+
|
|
1253
|
+
Automatic cleanup uses the same preflight as `archive task --dry-run`, without
|
|
1254
|
+
forcing past dirty checkouts, retained dependents, or other safety
|
|
1255
|
+
failures. A blocked attempt leaves the terminal status intact and the task
|
|
1256
|
+
unarchived, with the reason reported in human output and the triggering command's
|
|
1257
|
+
JSON `autoArchive` result (`sync` returns an array). After resolving the blocker,
|
|
1258
|
+
use explicit `archive` to retry safely. Successful automatic archiving removes
|
|
1259
|
+
clean managed checkouts, so paths inside the task may cease to exist.
|
|
1260
|
+
|
|
1226
1261
|
### Work, Publication, and Pull Requests
|
|
1227
1262
|
|
|
1228
1263
|
```text
|
package/cli-main.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { push, help as pushHelp } from "./src/commands/push"
|
|
|
10
10
|
import { work, workPrepare, help as workHelp } from "./src/commands/work"
|
|
11
11
|
import { worktree, help as worktreeHelp } from "./src/commands/worktree"
|
|
12
12
|
import { status, help as statusHelp } from "./src/commands/status"
|
|
13
|
+
import { config, help as configHelp } from "./src/commands/config"
|
|
13
14
|
import { doctor, help as doctorHelp } from "./src/commands/doctor"
|
|
14
15
|
import { validate, help as validateHelp } from "./src/commands/validate"
|
|
15
16
|
import { context, help as contextHelp } from "./src/commands/context"
|
|
@@ -579,6 +580,23 @@ const commands: Record<string, Command> = {
|
|
|
579
580
|
)
|
|
580
581
|
},
|
|
581
582
|
},
|
|
583
|
+
config: {
|
|
584
|
+
async run(args: string[], options: Record<string, any>) {
|
|
585
|
+
if (options.help) {
|
|
586
|
+
console.log(configHelp)
|
|
587
|
+
return
|
|
588
|
+
}
|
|
589
|
+
await runCommand(
|
|
590
|
+
config({
|
|
591
|
+
silent: options.silent,
|
|
592
|
+
verbose: options.verbose,
|
|
593
|
+
json: options.json,
|
|
594
|
+
cwd: options.cwd,
|
|
595
|
+
args,
|
|
596
|
+
}),
|
|
597
|
+
)
|
|
598
|
+
},
|
|
599
|
+
},
|
|
582
600
|
status: {
|
|
583
601
|
run: async (_args: string[], options: Record<string, any>) => {
|
|
584
602
|
if (options.help) {
|
|
@@ -726,9 +744,10 @@ Commands:
|
|
|
726
744
|
next List or select ready execution units
|
|
727
745
|
pr create / pr [...] Create an Agency PR or run gh pr with repository focus
|
|
728
746
|
push Validate and publish the current execution unit
|
|
729
|
-
review refresh
|
|
747
|
+
review refresh|finish Refresh or finish a pinned review task
|
|
730
748
|
repo <subcommand> Manage workbase repositories
|
|
731
749
|
status Show status for the current workbase
|
|
750
|
+
config Show or change workbase settings
|
|
732
751
|
doctor Diagnose workbase health and integrations
|
|
733
752
|
validate [path] Validate a workbase
|
|
734
753
|
context [target] Return complete target context
|
package/package.json
CHANGED
package/src/act-schema.ts
CHANGED
package/src/cli-parser.ts
CHANGED
|
@@ -783,7 +783,8 @@ const commands = {
|
|
|
783
783
|
},
|
|
784
784
|
},
|
|
785
785
|
review: {
|
|
786
|
-
usage:
|
|
786
|
+
usage:
|
|
787
|
+
"agency review <refresh|finish> <task-id> [--if-revision <hash>] [--json]",
|
|
787
788
|
options: {
|
|
788
789
|
...outputOptions,
|
|
789
790
|
"if-revision": { type: "string" },
|
|
@@ -796,6 +797,12 @@ const commands = {
|
|
|
796
797
|
maxArgs: 1,
|
|
797
798
|
options: ["if-revision", "json"],
|
|
798
799
|
},
|
|
800
|
+
finish: {
|
|
801
|
+
usage: "agency review finish <task-id> [--if-revision <hash>] [--json]",
|
|
802
|
+
minArgs: 1,
|
|
803
|
+
maxArgs: 1,
|
|
804
|
+
options: ["if-revision", "json"],
|
|
805
|
+
},
|
|
799
806
|
},
|
|
800
807
|
},
|
|
801
808
|
worktree: {
|
|
@@ -951,6 +958,18 @@ const commands = {
|
|
|
951
958
|
options: ["select", "json"],
|
|
952
959
|
},
|
|
953
960
|
},
|
|
961
|
+
config: {
|
|
962
|
+
usage: "agency config auto-archive [on|off] [--json]",
|
|
963
|
+
options: outputOptions,
|
|
964
|
+
subcommands: {
|
|
965
|
+
"auto-archive": {
|
|
966
|
+
usage: "agency config auto-archive [on|off] [--json]",
|
|
967
|
+
minArgs: 0,
|
|
968
|
+
maxArgs: 1,
|
|
969
|
+
options: ["json"],
|
|
970
|
+
},
|
|
971
|
+
},
|
|
972
|
+
},
|
|
954
973
|
status: {
|
|
955
974
|
usage: "agency status [filters] [--json]",
|
|
956
975
|
options: { ...outputOptions, ...viewOptions },
|
|
@@ -14,6 +14,7 @@ import { phase } from "./phase"
|
|
|
14
14
|
import { push } from "./push"
|
|
15
15
|
import { sync } from "./sync"
|
|
16
16
|
import { status } from "./status"
|
|
17
|
+
import { config } from "./config"
|
|
17
18
|
import { pr, prCreate } from "./pr"
|
|
18
19
|
import { validate } from "./validate"
|
|
19
20
|
import { work as startWork, type StartWork } from "./work"
|
|
@@ -33,6 +34,7 @@ type NativeOperation =
|
|
|
33
34
|
| ReturnType<typeof push>
|
|
34
35
|
| ReturnType<typeof sync>
|
|
35
36
|
| ReturnType<typeof status>
|
|
37
|
+
| ReturnType<typeof config>
|
|
36
38
|
| ReturnType<typeof doctor>
|
|
37
39
|
| ReturnType<typeof integration>
|
|
38
40
|
| ReturnType<typeof validate>
|
|
@@ -188,6 +190,22 @@ const actionPresentation: Record<
|
|
|
188
190
|
icon: "",
|
|
189
191
|
color: macchiato.green,
|
|
190
192
|
},
|
|
193
|
+
"auto-archive-show": {
|
|
194
|
+
description: "Show the effective automatic archive setting.",
|
|
195
|
+
icon: "",
|
|
196
|
+
color: macchiato.blue,
|
|
197
|
+
},
|
|
198
|
+
"auto-archive-enable": {
|
|
199
|
+
description:
|
|
200
|
+
"Automatically archive tasks on future terminal transitions, subject to safety checks.",
|
|
201
|
+
icon: "",
|
|
202
|
+
color: macchiato.green,
|
|
203
|
+
},
|
|
204
|
+
"auto-archive-disable": {
|
|
205
|
+
description: "Leave terminal tasks active until explicitly archived.",
|
|
206
|
+
icon: "",
|
|
207
|
+
color: macchiato.yellow,
|
|
208
|
+
},
|
|
191
209
|
doctor: {
|
|
192
210
|
description: "Diagnose workbase, repository, and integration health.",
|
|
193
211
|
icon: "",
|
|
@@ -234,6 +252,11 @@ const actionPresentation: Record<
|
|
|
234
252
|
icon: "",
|
|
235
253
|
color: macchiato.sapphire,
|
|
236
254
|
},
|
|
255
|
+
"finish-review": {
|
|
256
|
+
description: "Mark this review done; no completion summary is required.",
|
|
257
|
+
icon: "",
|
|
258
|
+
color: macchiato.green,
|
|
259
|
+
},
|
|
237
260
|
reopen: {
|
|
238
261
|
description: "Return terminal work to open status.",
|
|
239
262
|
icon: "",
|
|
@@ -371,7 +394,7 @@ export const actionGroups = [
|
|
|
371
394
|
id: "review",
|
|
372
395
|
label: "Review someone else's work",
|
|
373
396
|
icon: "",
|
|
374
|
-
actions: ["review", "review-ref"],
|
|
397
|
+
actions: ["review", "review-ref", "finish-review"],
|
|
375
398
|
},
|
|
376
399
|
{
|
|
377
400
|
id: "split",
|
|
@@ -436,6 +459,9 @@ export const actionGroups = [
|
|
|
436
459
|
"sync-all",
|
|
437
460
|
"integration-status",
|
|
438
461
|
"integration-sync",
|
|
462
|
+
"auto-archive-show",
|
|
463
|
+
"auto-archive-enable",
|
|
464
|
+
"auto-archive-disable",
|
|
439
465
|
],
|
|
440
466
|
},
|
|
441
467
|
{
|
|
@@ -825,6 +851,25 @@ export const actActions = (
|
|
|
825
851
|
}),
|
|
826
852
|
),
|
|
827
853
|
),
|
|
854
|
+
...(["show", "enable", "disable"] as const).map((operation) => {
|
|
855
|
+
const args = [
|
|
856
|
+
"auto-archive",
|
|
857
|
+
...(operation === "show"
|
|
858
|
+
? []
|
|
859
|
+
: [operation === "enable" ? "on" : "off"]),
|
|
860
|
+
]
|
|
861
|
+
return immediate(
|
|
862
|
+
{
|
|
863
|
+
id: `auto-archive-${operation}`,
|
|
864
|
+
label: `${operation === "show" ? "Show" : operation === "enable" ? "Enable" : "Disable"} auto archive`,
|
|
865
|
+
blockedReason: null,
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
command: ["agency", "config", ...args],
|
|
869
|
+
run: config({ ...options, args }),
|
|
870
|
+
},
|
|
871
|
+
)
|
|
872
|
+
}),
|
|
828
873
|
immediate(
|
|
829
874
|
{ id: "validate", label: "Validate workbase", blockedReason: null },
|
|
830
875
|
{
|
|
@@ -875,6 +920,7 @@ export const actActions = (
|
|
|
875
920
|
const purpose =
|
|
876
921
|
parent && "purpose" in parent.data ? parent.data.purpose : undefined
|
|
877
922
|
const terminal = node.status === "done" || node.status === "dropped"
|
|
923
|
+
const isReview = node.kind === "task" && "review" in node.data
|
|
878
924
|
const blockers = (execution ?? node).readiness.blockers
|
|
879
925
|
const validation = blockers.find(
|
|
880
926
|
(blocker) => blocker.kind === "validation",
|
|
@@ -1044,9 +1090,7 @@ export const actActions = (
|
|
|
1044
1090
|
details(
|
|
1045
1091
|
"review-refresh",
|
|
1046
1092
|
"Refresh review source",
|
|
1047
|
-
|
|
1048
|
-
? null
|
|
1049
|
-
: "Select a review task",
|
|
1093
|
+
isReview ? null : "Select a review task",
|
|
1050
1094
|
),
|
|
1051
1095
|
{
|
|
1052
1096
|
command: [
|
|
@@ -1065,6 +1109,33 @@ export const actActions = (
|
|
|
1065
1109
|
}),
|
|
1066
1110
|
},
|
|
1067
1111
|
),
|
|
1112
|
+
immediate(
|
|
1113
|
+
details(
|
|
1114
|
+
"finish-review",
|
|
1115
|
+
"Finish review",
|
|
1116
|
+
!isReview
|
|
1117
|
+
? "Select a review task"
|
|
1118
|
+
: terminal
|
|
1119
|
+
? "Review is already terminal"
|
|
1120
|
+
: null,
|
|
1121
|
+
),
|
|
1122
|
+
{
|
|
1123
|
+
command: [
|
|
1124
|
+
"agency",
|
|
1125
|
+
"review",
|
|
1126
|
+
"finish",
|
|
1127
|
+
target.taskId,
|
|
1128
|
+
"--if-revision",
|
|
1129
|
+
node.data.sha256,
|
|
1130
|
+
],
|
|
1131
|
+
run: review({
|
|
1132
|
+
...options,
|
|
1133
|
+
subcommand: "finish",
|
|
1134
|
+
taskId: target.taskId,
|
|
1135
|
+
ifRevision: node.data.sha256,
|
|
1136
|
+
}),
|
|
1137
|
+
},
|
|
1138
|
+
),
|
|
1068
1139
|
...(["reopen", "drop"] as const).map((id) => {
|
|
1069
1140
|
const status = id === "reopen" ? "open" : "dropped"
|
|
1070
1141
|
return immediate(
|
|
@@ -1300,11 +1371,13 @@ export const actActions = (
|
|
|
1300
1371
|
"complete",
|
|
1301
1372
|
"Complete without a pull request",
|
|
1302
1373
|
noExecution ??
|
|
1303
|
-
(
|
|
1304
|
-
? "
|
|
1305
|
-
:
|
|
1306
|
-
? "
|
|
1307
|
-
:
|
|
1374
|
+
(isReview
|
|
1375
|
+
? "Use Finish review for review tasks"
|
|
1376
|
+
: terminal
|
|
1377
|
+
? "Item is already terminal"
|
|
1378
|
+
: prUrl
|
|
1379
|
+
? "Reconcile the recorded pull request instead"
|
|
1380
|
+
: null),
|
|
1308
1381
|
),
|
|
1309
1382
|
inputs: [
|
|
1310
1383
|
input("summary", "Completed outcome summary", { multiline: true }),
|
package/src/commands/act.ts
CHANGED
|
@@ -481,6 +481,7 @@ const actStep = (
|
|
|
481
481
|
creationDefaults: creationDefaults(config),
|
|
482
482
|
workbase: {
|
|
483
483
|
root,
|
|
484
|
+
autoArchive: config.autoArchive ?? false,
|
|
484
485
|
repositories: graph.nodes
|
|
485
486
|
.filter((node) => node.kind === "repository")
|
|
486
487
|
.map((node) => node.key),
|
|
@@ -790,6 +791,7 @@ A workbase path opens Workbase actions. Otherwise the positional value is a
|
|
|
790
791
|
task ID. Selectors skip item selection.
|
|
791
792
|
JSON discovery includes creation defaults identifying when callers should omit
|
|
792
793
|
--branch so the workbase branchNameCommand can choose it.
|
|
794
|
+
JSON discovery also includes the effective auto-archive setting and its commands.
|
|
793
795
|
|
|
794
796
|
Options:
|
|
795
797
|
--action <id> Start an action or filter discovery (IDs from --json)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Effect } from "effect"
|
|
2
|
+
import { WorkbaseService } from "../services/WorkbaseService"
|
|
3
|
+
import type { BaseCommandOptions } from "../utils/command"
|
|
4
|
+
import { createLoggers } from "../utils/effect"
|
|
5
|
+
|
|
6
|
+
export const config = (
|
|
7
|
+
options: BaseCommandOptions & { readonly args: readonly string[] },
|
|
8
|
+
) =>
|
|
9
|
+
Effect.gen(function* () {
|
|
10
|
+
const [setting, value] = options.args
|
|
11
|
+
if (
|
|
12
|
+
setting !== "auto-archive" ||
|
|
13
|
+
(value !== undefined && value !== "on" && value !== "off")
|
|
14
|
+
) {
|
|
15
|
+
return yield* Effect.fail(
|
|
16
|
+
new Error("Usage: agency config auto-archive [on|off]"),
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
const workbase = yield* WorkbaseService
|
|
20
|
+
const { log } = createLoggers(options)
|
|
21
|
+
const result =
|
|
22
|
+
value === undefined
|
|
23
|
+
? yield* workbase.loadConfig(options.cwd).pipe(
|
|
24
|
+
Effect.map(({ root, config }) => ({
|
|
25
|
+
root,
|
|
26
|
+
autoArchive: config.autoArchive ?? false,
|
|
27
|
+
})),
|
|
28
|
+
)
|
|
29
|
+
: yield* workbase.setAutoArchive(value === "on", options.cwd)
|
|
30
|
+
log(
|
|
31
|
+
options.json
|
|
32
|
+
? JSON.stringify(result, null, 2)
|
|
33
|
+
: `Auto archive: ${result.autoArchive ? "on" : "off"}`,
|
|
34
|
+
)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export const help = `
|
|
38
|
+
Usage: agency config auto-archive [on|off] [--json]
|
|
39
|
+
|
|
40
|
+
Show, enable, or disable automatic archiving in this workbase.
|
|
41
|
+
Defaults to off. Enabling applies to future terminal transitions, not existing
|
|
42
|
+
terminal work. Archive safety checks are never bypassed.
|
|
43
|
+
`
|
package/src/commands/phase.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Effect } from "effect"
|
|
2
|
+
import { autoArchiveMessage } from "../services/auto-archive"
|
|
2
3
|
import type { BaseCommandOptions } from "../utils/command"
|
|
3
4
|
import { PhaseService } from "../services/PhaseService"
|
|
4
5
|
import { createLoggers } from "../utils/effect"
|
|
@@ -199,6 +200,8 @@ export const phase = (options: PhaseOptions, work: StartWork = startWork) =>
|
|
|
199
200
|
? JSON.stringify(output, null, 2)
|
|
200
201
|
: `Marked phase '${phaseId}' as ${record.data.status}`,
|
|
201
202
|
)
|
|
203
|
+
const notice = autoArchiveMessage(record.autoArchive)
|
|
204
|
+
if (!options.json && notice) log(notice)
|
|
202
205
|
return
|
|
203
206
|
}
|
|
204
207
|
case "update": {
|
package/src/commands/review.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Effect } from "effect"
|
|
2
2
|
import { ReviewService } from "../services/ReviewService"
|
|
3
|
+
import { autoArchiveMessage } from "../services/auto-archive"
|
|
3
4
|
import type { BaseCommandOptions } from "../utils/command"
|
|
4
5
|
import { createLoggers } from "../utils/effect"
|
|
5
6
|
|
|
@@ -12,17 +13,36 @@ interface ReviewOptions extends BaseCommandOptions {
|
|
|
12
13
|
|
|
13
14
|
export const review = (options: ReviewOptions) =>
|
|
14
15
|
Effect.gen(function* () {
|
|
15
|
-
if (
|
|
16
|
+
if (
|
|
17
|
+
(options.subcommand !== "refresh" && options.subcommand !== "finish") ||
|
|
18
|
+
!options.taskId
|
|
19
|
+
) {
|
|
16
20
|
return yield* Effect.fail(
|
|
17
|
-
new Error("Usage: agency review refresh <task>"),
|
|
21
|
+
new Error("Usage: agency review <refresh|finish> <task>"),
|
|
18
22
|
)
|
|
19
23
|
}
|
|
20
|
-
const
|
|
24
|
+
const service = yield* ReviewService
|
|
25
|
+
const { log } = createLoggers(options)
|
|
26
|
+
if (options.subcommand === "finish") {
|
|
27
|
+
const result = yield* service.finish(
|
|
28
|
+
options.taskId,
|
|
29
|
+
options.cwd,
|
|
30
|
+
options.ifRevision,
|
|
31
|
+
)
|
|
32
|
+
log(
|
|
33
|
+
options.json
|
|
34
|
+
? JSON.stringify(result, null, 2)
|
|
35
|
+
: `Finished review '${options.taskId}'`,
|
|
36
|
+
)
|
|
37
|
+
const notice = autoArchiveMessage(result.autoArchive)
|
|
38
|
+
if (!options.json && notice) log(notice)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
const result = yield* service.refresh(
|
|
21
42
|
options.taskId,
|
|
22
43
|
options.cwd,
|
|
23
44
|
options.ifRevision,
|
|
24
45
|
)
|
|
25
|
-
const { log } = createLoggers(options)
|
|
26
46
|
log(
|
|
27
47
|
options.json
|
|
28
48
|
? JSON.stringify(result, null, 2)
|
|
@@ -31,8 +51,12 @@ export const review = (options: ReviewOptions) =>
|
|
|
31
51
|
})
|
|
32
52
|
|
|
33
53
|
export const help = `
|
|
34
|
-
Usage: agency review refresh <task-id> [--if-revision <hash>] [--json]
|
|
54
|
+
Usage: agency review <refresh|finish> <task-id> [--if-revision <hash>] [--json]
|
|
35
55
|
|
|
36
|
-
|
|
37
|
-
|
|
56
|
+
Subcommands:
|
|
57
|
+
refresh Fetch the review source explicitly and replace the pinned commit
|
|
58
|
+
and any clean, detached review checkout. Review sources never
|
|
59
|
+
move implicitly.
|
|
60
|
+
finish Mark an open, working, or delegated review task done. Reviews
|
|
61
|
+
have no delivery pull request and need no completion summary.
|
|
38
62
|
`
|
package/src/commands/status.ts
CHANGED
|
@@ -34,7 +34,13 @@ export const status = (options: StatusOptions = {}) =>
|
|
|
34
34
|
pr: options.pr,
|
|
35
35
|
})).executionRows
|
|
36
36
|
: []
|
|
37
|
-
const
|
|
37
|
+
const { config } = yield* workbase.loadConfig(cwd)
|
|
38
|
+
const data = {
|
|
39
|
+
...report,
|
|
40
|
+
autoArchive: config.autoArchive ?? false,
|
|
41
|
+
repositories: repos,
|
|
42
|
+
work: executionRows,
|
|
43
|
+
}
|
|
38
44
|
|
|
39
45
|
if (options.json) {
|
|
40
46
|
log(JSON.stringify(data, null, 2))
|
package/src/commands/sync.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Effect } from "effect"
|
|
2
2
|
import { SyncService } from "../services/SyncService"
|
|
3
|
+
import { autoArchiveMessage } from "../services/auto-archive"
|
|
3
4
|
import type { BaseCommandOptions } from "../utils/command"
|
|
4
5
|
import { createLoggers } from "../utils/effect"
|
|
5
6
|
import { createProgress, type Progress } from "../utils/progress"
|
|
@@ -87,6 +88,10 @@ export const sync = (
|
|
|
87
88
|
`${change.status === "applied" ? "Applied" : "Planned"} ${change.kind} '${change.target}': ${change.message}`,
|
|
88
89
|
)
|
|
89
90
|
}
|
|
91
|
+
for (const archive of result.autoArchive) {
|
|
92
|
+
const notice = autoArchiveMessage(archive)
|
|
93
|
+
if (notice) log(notice)
|
|
94
|
+
}
|
|
90
95
|
for (const { notice: warning, targets } of groupedNotices(
|
|
91
96
|
result.warnings,
|
|
92
97
|
)) {
|
package/src/commands/task.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Effect } from "effect"
|
|
2
|
+
import { autoArchiveMessage } from "../services/auto-archive"
|
|
2
3
|
import type { BaseCommandOptions } from "../utils/command"
|
|
3
4
|
import { TaskService } from "../services/TaskService"
|
|
4
5
|
import { EpicService } from "../services/EpicService"
|
|
@@ -491,6 +492,8 @@ export const task = (
|
|
|
491
492
|
? JSON.stringify(output, null, 2)
|
|
492
493
|
: `Marked task '${id}' as ${record.data.status}`,
|
|
493
494
|
)
|
|
495
|
+
const notice = autoArchiveMessage(record.autoArchive)
|
|
496
|
+
if (!options.json && notice) log(notice)
|
|
494
497
|
return
|
|
495
498
|
}
|
|
496
499
|
case "update": {
|
|
@@ -329,6 +329,7 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
329
329
|
root,
|
|
330
330
|
configPath: join(root, "agency.json"),
|
|
331
331
|
version: config.version,
|
|
332
|
+
autoArchive: config.autoArchive ?? false,
|
|
332
333
|
vcs: "git",
|
|
333
334
|
},
|
|
334
335
|
target: { kind: "workbase", path: root },
|
|
@@ -1174,6 +1175,7 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
1174
1175
|
root,
|
|
1175
1176
|
configPath: join(root, "agency.json"),
|
|
1176
1177
|
version: config.version,
|
|
1178
|
+
autoArchive: config.autoArchive ?? false,
|
|
1177
1179
|
vcs: "git",
|
|
1178
1180
|
},
|
|
1179
1181
|
target,
|
|
@@ -17,7 +17,8 @@ import {
|
|
|
17
17
|
formatWorkDocumentBody,
|
|
18
18
|
parseFrontmatter,
|
|
19
19
|
} from "../workbase/frontmatter"
|
|
20
|
-
import { canTransitionStatus } from "../readiness"
|
|
20
|
+
import { canTransitionStatus, isTerminalStatus } from "../readiness"
|
|
21
|
+
import { autoArchiveTask, type AutoArchiveResult } from "./auto-archive"
|
|
21
22
|
import { documentRevision } from "../workbase/document-revision"
|
|
22
23
|
import { archivedPhaseDirectory } from "../workbase/archive"
|
|
23
24
|
import {
|
|
@@ -43,6 +44,7 @@ export interface PhaseRecord {
|
|
|
43
44
|
readonly content: string
|
|
44
45
|
readonly revision: string
|
|
45
46
|
readonly data: PhaseData
|
|
47
|
+
readonly autoArchive?: AutoArchiveResult
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
export interface CreatePhaseInput {
|
|
@@ -573,11 +575,15 @@ export class PhaseService extends Effect.Service<PhaseService>()(
|
|
|
573
575
|
: { ...record.data, status: validStatus }
|
|
574
576
|
const content = formatMarkdownDocument(data, parsed.body)
|
|
575
577
|
yield* fs.writeFile(record.path, content)
|
|
578
|
+
const autoArchive = isTerminalStatus(data.status)
|
|
579
|
+
? yield* autoArchiveTask(taskId, startPath)
|
|
580
|
+
: undefined
|
|
576
581
|
return {
|
|
577
582
|
...record,
|
|
578
583
|
content,
|
|
579
584
|
revision: documentRevision(content),
|
|
580
585
|
data,
|
|
586
|
+
...(autoArchive ? { autoArchive } : {}),
|
|
581
587
|
} satisfies PhaseRecord
|
|
582
588
|
}),
|
|
583
589
|
}),
|
|
@@ -7,6 +7,7 @@ import { PhaseService } from "./PhaseService"
|
|
|
7
7
|
import { RepositoryService } from "./RepositoryService"
|
|
8
8
|
import { TaskService } from "./TaskService"
|
|
9
9
|
import { WorkbaseService } from "./WorkbaseService"
|
|
10
|
+
import { autoArchiveTask } from "./auto-archive"
|
|
10
11
|
import {
|
|
11
12
|
WorktreeService,
|
|
12
13
|
type WorktreeRemovalSnapshot,
|
|
@@ -424,6 +425,52 @@ export class ReviewService extends Effect.Service<ReviewService>()(
|
|
|
424
425
|
}),
|
|
425
426
|
)
|
|
426
427
|
}),
|
|
428
|
+
|
|
429
|
+
finish: (
|
|
430
|
+
taskId: string,
|
|
431
|
+
startPath: string = process.cwd(),
|
|
432
|
+
ifRevision?: string,
|
|
433
|
+
) =>
|
|
434
|
+
Effect.gen(function* () {
|
|
435
|
+
const workbase = yield* WorkbaseService
|
|
436
|
+
const tasks = yield* TaskService
|
|
437
|
+
const root = yield* workbase.discover(startPath)
|
|
438
|
+
const task = yield* tasks.show(taskId, root)
|
|
439
|
+
if (!("review" in task.data)) {
|
|
440
|
+
return yield* new ReviewError({
|
|
441
|
+
message: `Task '${taskId}' is not a review task`,
|
|
442
|
+
})
|
|
443
|
+
}
|
|
444
|
+
if (ifRevision && task.revision !== ifRevision) {
|
|
445
|
+
return yield* new RevisionConflictError({
|
|
446
|
+
path: task.path,
|
|
447
|
+
target: `task '${taskId}'`,
|
|
448
|
+
expectedRevision: ifRevision,
|
|
449
|
+
currentRevision: task.revision,
|
|
450
|
+
message: `Revision conflict for task '${taskId}'`,
|
|
451
|
+
})
|
|
452
|
+
}
|
|
453
|
+
if (task.data.status === "done" || task.data.status === "dropped") {
|
|
454
|
+
return yield* new ReviewError({
|
|
455
|
+
message: `Cannot finish review task '${taskId}' from ${task.data.status}; reopen it first`,
|
|
456
|
+
})
|
|
457
|
+
}
|
|
458
|
+
const parsed = yield* parseFrontmatter(task.content, task.path)
|
|
459
|
+
const data = { ...task.data, status: "done" as const }
|
|
460
|
+
const content = formatMarkdownDocument(data, parsed.body)
|
|
461
|
+
yield* runLifecycleTransaction({
|
|
462
|
+
root,
|
|
463
|
+
preconditions: [{ path: task.path, revision: task.revision }],
|
|
464
|
+
steps: [documentWriteStep(root, [{ path: task.path, content }])],
|
|
465
|
+
})
|
|
466
|
+
return {
|
|
467
|
+
id: taskId,
|
|
468
|
+
path: task.path,
|
|
469
|
+
revision: documentRevision(content),
|
|
470
|
+
data,
|
|
471
|
+
autoArchive: yield* autoArchiveTask(taskId, root),
|
|
472
|
+
}
|
|
473
|
+
}),
|
|
427
474
|
}),
|
|
428
475
|
},
|
|
429
476
|
) {}
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
type RepositorySetupResult,
|
|
32
32
|
} from "./RepositoryService"
|
|
33
33
|
import { VersionControlService } from "./VersionControlService"
|
|
34
|
+
import { autoArchiveTask, type AutoArchiveResult } from "./auto-archive"
|
|
34
35
|
|
|
35
36
|
class SyncError extends Data.TaggedError("SyncError")<{
|
|
36
37
|
readonly message: string
|
|
@@ -111,6 +112,7 @@ interface SyncResult {
|
|
|
111
112
|
readonly unresolved: readonly SyncNotice[]
|
|
112
113
|
readonly executions: readonly ExecutionSyncState[]
|
|
113
114
|
readonly repositories: RepositorySetupResult
|
|
115
|
+
readonly autoArchive: readonly AutoArchiveResult[]
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
export interface SyncProgress {
|
|
@@ -370,6 +372,7 @@ export class SyncService extends Effect.Service<SyncService>()("SyncService", {
|
|
|
370
372
|
|
|
371
373
|
const apply = options.apply === true
|
|
372
374
|
const changes: SyncChange[] = []
|
|
375
|
+
const completedTasks = new Set<string>()
|
|
373
376
|
const warnings: SyncNotice[] = []
|
|
374
377
|
const unresolved: SyncNotice[] = []
|
|
375
378
|
for (const issue of repositorySetup.unresolved) {
|
|
@@ -1285,6 +1288,7 @@ export class SyncService extends Effect.Service<SyncService>()("SyncService", {
|
|
|
1285
1288
|
})
|
|
1286
1289
|
data = completed.data
|
|
1287
1290
|
revision = completed.revision
|
|
1291
|
+
completedTasks.add(record.taskId)
|
|
1288
1292
|
}
|
|
1289
1293
|
changes.push({
|
|
1290
1294
|
kind: "mark-done",
|
|
@@ -1372,6 +1376,11 @@ export class SyncService extends Effect.Service<SyncService>()("SyncService", {
|
|
|
1372
1376
|
reportExecution(`task:${task.id}`)
|
|
1373
1377
|
}
|
|
1374
1378
|
|
|
1379
|
+
const autoArchive: AutoArchiveResult[] = []
|
|
1380
|
+
if (apply && config.autoArchive) {
|
|
1381
|
+
for (const taskId of completedTasks)
|
|
1382
|
+
autoArchive.push(yield* autoArchiveTask(taskId, root))
|
|
1383
|
+
}
|
|
1375
1384
|
return {
|
|
1376
1385
|
root,
|
|
1377
1386
|
mode: apply ? "apply" : "dry-run",
|
|
@@ -1380,6 +1389,7 @@ export class SyncService extends Effect.Service<SyncService>()("SyncService", {
|
|
|
1380
1389
|
unresolved,
|
|
1381
1390
|
executions,
|
|
1382
1391
|
repositories: repositorySetup,
|
|
1392
|
+
autoArchive,
|
|
1383
1393
|
} satisfies SyncResult
|
|
1384
1394
|
}),
|
|
1385
1395
|
}),
|
|
@@ -22,7 +22,8 @@ import {
|
|
|
22
22
|
parseFrontmatter,
|
|
23
23
|
parseFrontmatterSync,
|
|
24
24
|
} from "../workbase/frontmatter"
|
|
25
|
-
import { canTransitionStatus } from "../readiness"
|
|
25
|
+
import { canTransitionStatus, isTerminalStatus } from "../readiness"
|
|
26
|
+
import { autoArchiveTask, type AutoArchiveResult } from "./auto-archive"
|
|
26
27
|
import { documentRevision } from "../workbase/document-revision"
|
|
27
28
|
import { archivedTaskDirectory } from "../workbase/archive"
|
|
28
29
|
import {
|
|
@@ -47,6 +48,7 @@ export interface TaskRecord {
|
|
|
47
48
|
readonly content: string
|
|
48
49
|
readonly revision: string
|
|
49
50
|
readonly data: TaskData
|
|
51
|
+
readonly autoArchive?: AutoArchiveResult
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
export interface CreateTaskInput {
|
|
@@ -652,6 +654,15 @@ export class TaskService extends Effect.Service<TaskService>()("TaskService", {
|
|
|
652
654
|
!canTransitionStatus(record.data.status, validStatus) &&
|
|
653
655
|
!completionResult
|
|
654
656
|
) {
|
|
657
|
+
if (
|
|
658
|
+
validStatus === "done" &&
|
|
659
|
+
"review" in record.data &&
|
|
660
|
+
record.data.status !== "dropped"
|
|
661
|
+
) {
|
|
662
|
+
return yield* new TaskError({
|
|
663
|
+
message: `Finish review task '${id}' with 'agency review finish ${id}'; reviews do not require a completion summary`,
|
|
664
|
+
})
|
|
665
|
+
}
|
|
655
666
|
if (validStatus === "done") {
|
|
656
667
|
return yield* new TaskError({
|
|
657
668
|
message:
|
|
@@ -675,11 +686,15 @@ export class TaskService extends Effect.Service<TaskService>()("TaskService", {
|
|
|
675
686
|
: { ...record.data, status: validStatus }
|
|
676
687
|
const content = formatMarkdownDocument(data, parsed.body)
|
|
677
688
|
yield* fs.writeFile(record.path, content)
|
|
689
|
+
const autoArchive = isTerminalStatus(data.status)
|
|
690
|
+
? yield* autoArchiveTask(id, startPath)
|
|
691
|
+
: undefined
|
|
678
692
|
return {
|
|
679
693
|
...record,
|
|
680
694
|
content,
|
|
681
695
|
revision: documentRevision(content),
|
|
682
696
|
data,
|
|
697
|
+
...(autoArchive ? { autoArchive } : {}),
|
|
683
698
|
} satisfies TaskRecord
|
|
684
699
|
}),
|
|
685
700
|
}),
|
|
@@ -416,6 +416,19 @@ export class WorkbaseService extends Effect.Service<WorkbaseService>()(
|
|
|
416
416
|
return { root, config: decoded.value }
|
|
417
417
|
}),
|
|
418
418
|
|
|
419
|
+
setAutoArchive: (enabled: boolean, startPath: string = process.cwd()) =>
|
|
420
|
+
Effect.gen(function* () {
|
|
421
|
+
const service = yield* WorkbaseService
|
|
422
|
+
const fs = yield* FileSystemService
|
|
423
|
+
const { root, config } = yield* service.loadConfig(startPath)
|
|
424
|
+
const updated = { ...config, autoArchive: enabled }
|
|
425
|
+
yield* fs.writeFile(
|
|
426
|
+
join(root, "agency.json"),
|
|
427
|
+
`${JSON.stringify(updated, null, 2)}\n`,
|
|
428
|
+
)
|
|
429
|
+
return { root, autoArchive: enabled }
|
|
430
|
+
}),
|
|
431
|
+
|
|
419
432
|
repositoryAliases: (startPath: string = process.cwd()) =>
|
|
420
433
|
Effect.gen(function* () {
|
|
421
434
|
const service = yield* WorkbaseService
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Effect } from "effect"
|
|
2
|
+
import type {
|
|
3
|
+
GitVersionControlService,
|
|
4
|
+
VersionControlService,
|
|
5
|
+
} from "./VersionControlService"
|
|
6
|
+
import type { ArchiveService } from "./ArchiveService"
|
|
7
|
+
import { WorkbaseService } from "./WorkbaseService"
|
|
8
|
+
import type { FileSystemService } from "./FileSystemService"
|
|
9
|
+
import { TaskService } from "./TaskService"
|
|
10
|
+
import { PhaseService } from "./PhaseService"
|
|
11
|
+
import type { EpicService } from "./EpicService"
|
|
12
|
+
import type { WorktreeService } from "./WorktreeService"
|
|
13
|
+
import { aggregateProgress, isTerminalStatus } from "../readiness"
|
|
14
|
+
|
|
15
|
+
export interface AutoArchiveResult {
|
|
16
|
+
readonly taskId: string
|
|
17
|
+
readonly status: "disabled" | "non-terminal" | "archived" | "skipped"
|
|
18
|
+
readonly path?: string
|
|
19
|
+
readonly reason?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// A terminal transition is durable even when archive safety checks reject cleanup.
|
|
23
|
+
// Explicit typing also keeps the lazy service references free of inference cycles.
|
|
24
|
+
export const autoArchiveTask = (
|
|
25
|
+
taskId: string,
|
|
26
|
+
startPath: string,
|
|
27
|
+
): Effect.Effect<
|
|
28
|
+
AutoArchiveResult,
|
|
29
|
+
never,
|
|
30
|
+
| WorkbaseService
|
|
31
|
+
| ArchiveService
|
|
32
|
+
| FileSystemService
|
|
33
|
+
| TaskService
|
|
34
|
+
| PhaseService
|
|
35
|
+
| EpicService
|
|
36
|
+
| WorktreeService
|
|
37
|
+
| GitVersionControlService
|
|
38
|
+
| VersionControlService
|
|
39
|
+
> =>
|
|
40
|
+
Effect.gen(function* () {
|
|
41
|
+
const workbase = yield* WorkbaseService
|
|
42
|
+
const { root, config } = yield* workbase.loadConfig(startPath)
|
|
43
|
+
if (!config.autoArchive) return { taskId, status: "disabled" as const }
|
|
44
|
+
const tasks = yield* TaskService
|
|
45
|
+
const task = yield* tasks.show(taskId, root)
|
|
46
|
+
let status
|
|
47
|
+
if ("phases" in task.data) {
|
|
48
|
+
const phases = yield* PhaseService
|
|
49
|
+
const records = yield* Effect.forEach(task.data.phases, (phase) =>
|
|
50
|
+
phases.show(taskId, phase.id, root),
|
|
51
|
+
)
|
|
52
|
+
status = aggregateProgress(
|
|
53
|
+
records.map((phase) => phase.data.status),
|
|
54
|
+
).status
|
|
55
|
+
} else status = task.data.status
|
|
56
|
+
if (!isTerminalStatus(status))
|
|
57
|
+
return { taskId, status: "non-terminal" as const }
|
|
58
|
+
// ArchiveService uses TaskService for preflight; resolve it only after the
|
|
59
|
+
// terminal write to avoid an eager module initialization cycle.
|
|
60
|
+
const { ArchiveService } = yield* Effect.promise(
|
|
61
|
+
() => import("./ArchiveService"),
|
|
62
|
+
)
|
|
63
|
+
const archives = yield* ArchiveService
|
|
64
|
+
// archiveTask always performs the same preflight as --dry-run before applying.
|
|
65
|
+
const result = yield* archives.archiveTask(taskId, root)
|
|
66
|
+
return { taskId, status: "archived" as const, path: result.path }
|
|
67
|
+
}).pipe(
|
|
68
|
+
Effect.catchAll((error) =>
|
|
69
|
+
Effect.succeed({
|
|
70
|
+
taskId,
|
|
71
|
+
status: "skipped" as const,
|
|
72
|
+
reason: error.message,
|
|
73
|
+
}),
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
export const autoArchiveMessage = (result: AutoArchiveResult | undefined) =>
|
|
78
|
+
result?.status === "archived"
|
|
79
|
+
? `Auto-archived task '${result.taskId}' to ${result.path}`
|
|
80
|
+
: result?.status === "skipped"
|
|
81
|
+
? `Auto-archive skipped for task '${result.taskId}': ${result.reason}`
|
|
82
|
+
: undefined
|
package/src/workbase/AGENTS.md
CHANGED
|
@@ -108,6 +108,8 @@ retain `--if-revision` guards when shown, and do not add flags that are not show
|
|
|
108
108
|
`agency context <new-task> --json`. Do not prepare or start it unless requested.
|
|
109
109
|
15. Refresh a pinned review task with the current revision:
|
|
110
110
|
`agency review refresh <task> --if-revision <revision> --json`.
|
|
111
|
+
16. Finish a review task with the current revision; no summary is required:
|
|
112
|
+
`agency review finish <task> --if-revision <revision> --json`.
|
|
111
113
|
|
|
112
114
|
Never pass `--work` or `--auto` to `agency task create`. Do not run separate
|
|
113
115
|
`agency validate`, `agency worktree prepare`, `agency graph`, `agency task list`,
|
|
@@ -182,8 +184,18 @@ agency validate --json
|
|
|
182
184
|
Require explicit user intent before initializing a workbase; changing repository
|
|
183
185
|
aliases or applying repository setup or workbase sync changes; launching another
|
|
184
186
|
agent from an active agent session; creating a pull request; archiving, restoring,
|
|
185
|
-
dropping, reopening, or completing work without a pull
|
|
186
|
-
to override readiness.
|
|
187
|
+
dropping, reopening, finishing a review, or completing work without a pull
|
|
188
|
+
request; or using `--force` to override readiness.
|
|
189
|
+
|
|
190
|
+
The workbase's `autoArchive` setting defaults to off. Enabling it with
|
|
191
|
+
`agency config auto-archive on` is standing consent for automatic task archiving
|
|
192
|
+
on terminal transitions, including merged-PR sync and explicit non-PR completion.
|
|
193
|
+
Show or disable it with `agency config auto-archive [off]`; context/status JSON
|
|
194
|
+
reports its effective value. Multi-phase tasks wait until every phase is terminal.
|
|
195
|
+
Archive preflight safeguards still apply: inspect the triggering command's
|
|
196
|
+
`autoArchive` result for skips, resolve their reasons, and never force cleanup.
|
|
197
|
+
Successful auto-archive can remove the current task directory and clean checkouts;
|
|
198
|
+
continue lifecycle commands from the workbase root rather than a removed path.
|
|
187
199
|
|
|
188
200
|
## Investigation Handoffs
|
|
189
201
|
|
|
@@ -272,7 +284,9 @@ its pull request is open. It becomes `done` only after its authoritative pull
|
|
|
272
284
|
request is merged and Agency reconciles that state. Do not mark committed or
|
|
273
285
|
review-ready work `done` manually. A genuine investigation, operational action,
|
|
274
286
|
or no-change result may complete without a pull request only with explicit user
|
|
275
|
-
intent, `--no-pull-request`, and a durable outcome summary.
|
|
287
|
+
intent, `--no-pull-request`, and a durable outcome summary. A review task has no
|
|
288
|
+
delivery pull request; with explicit user intent, finish it with
|
|
289
|
+
`agency review finish` instead, which requires no summary.
|
|
276
290
|
|
|
277
291
|
At each closeout trigger (creating or updating a PR, marking it ready, completing
|
|
278
292
|
a refinement loop, or pausing or handing off completed implementation work):
|
package/src/workbase/schemas.ts
CHANGED
|
@@ -103,6 +103,7 @@ const DeliveryProvider = Schema.Struct({
|
|
|
103
103
|
export const WorkbaseConfig = Schema.Struct({
|
|
104
104
|
version: Schema.Literal(2),
|
|
105
105
|
vcs: Schema.optional(Schema.Literal("git")),
|
|
106
|
+
autoArchive: Schema.optional(Schema.Boolean),
|
|
106
107
|
repositories: Schema.optional(
|
|
107
108
|
Schema.Record({ key: RepositoryAlias, value: RepositoryDeclaration }),
|
|
108
109
|
),
|