@markjaquith/agency 3.7.0 → 3.9.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 +32 -23
- package/package.json +1 -1
- package/src/act-schema.ts +3 -0
- package/src/cli-parser.ts +12 -4
- package/src/commands/act-actions.ts +799 -29
- package/src/commands/act.ts +9 -20
- package/src/commands/archive.ts +17 -9
- package/src/commands/sync.ts +2 -1
- package/src/commands/work.ts +11 -14
- package/src/services/ArchiveService.ts +25 -7
- package/src/services/SyncService.ts +6 -9
package/src/commands/act.ts
CHANGED
|
@@ -169,29 +169,18 @@ const entitySummary = (node: ActEntity) => ({
|
|
|
169
169
|
readiness: node.readiness,
|
|
170
170
|
revision: node.data.sha256,
|
|
171
171
|
})
|
|
172
|
-
const actionIcons: Record<string, string> = {
|
|
173
|
-
reopen: "",
|
|
174
|
-
drop: "",
|
|
175
|
-
complete: "",
|
|
176
|
-
sync: "",
|
|
177
|
-
"pr-ready": "",
|
|
178
|
-
"pr-close": "",
|
|
179
|
-
}
|
|
180
172
|
const actionChoices = (actions: readonly ActAction[]): Choice<string>[] =>
|
|
181
|
-
actions.map(({ id, label }) => {
|
|
182
|
-
const icon =
|
|
183
|
-
actionIcons[id] ??
|
|
184
|
-
actionGroups.find((group) =>
|
|
185
|
-
group.actions.some((action) => action === id),
|
|
186
|
-
)!.icon
|
|
187
|
-
const color =
|
|
188
|
-
id === "drop" || id === "pr-close" ? macchiato.red : macchiato.overlay1
|
|
173
|
+
actions.map(({ id, label, description, icon, color }) => {
|
|
189
174
|
return {
|
|
190
175
|
key: id,
|
|
191
176
|
value: id,
|
|
192
|
-
label: `${icon} ${label}`,
|
|
193
|
-
plainLabel: label
|
|
194
|
-
segments: [
|
|
177
|
+
label: `${icon} ${label} — ${description}`,
|
|
178
|
+
plainLabel: `${label} — ${description}`,
|
|
179
|
+
segments: [
|
|
180
|
+
{ text: `${icon} `, color },
|
|
181
|
+
{ text: label },
|
|
182
|
+
{ text: ` — ${description}`, color: macchiato.overlay0 },
|
|
183
|
+
],
|
|
195
184
|
}
|
|
196
185
|
})
|
|
197
186
|
|
|
@@ -568,7 +557,7 @@ const actStep = (
|
|
|
568
557
|
graph.nodes
|
|
569
558
|
.filter((node) => node.kind === "repository")
|
|
570
559
|
.map((node) => node.key),
|
|
571
|
-
nodes.
|
|
560
|
+
nodes.map((node) => node.key),
|
|
572
561
|
config.chooserCommand,
|
|
573
562
|
),
|
|
574
563
|
)
|
package/src/commands/archive.ts
CHANGED
|
@@ -26,11 +26,13 @@ export const archive = (options: ArchiveOptions) =>
|
|
|
26
26
|
const [id, phaseId] = options.args
|
|
27
27
|
let archiveType = options.type
|
|
28
28
|
let archiveId = id
|
|
29
|
+
let archiveTaskId: string | undefined
|
|
29
30
|
|
|
30
|
-
if (!archiveType
|
|
31
|
-
const target = yield* archives.resolvePathTarget(id, cwd)
|
|
31
|
+
if (!archiveType) {
|
|
32
|
+
const target = yield* archives.resolvePathTarget(id ?? ".", cwd)
|
|
32
33
|
archiveType = target.kind
|
|
33
34
|
archiveId = target.id
|
|
35
|
+
archiveTaskId = target.taskId
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
if (options.type === "list") {
|
|
@@ -106,13 +108,18 @@ export const archive = (options: ArchiveOptions) =>
|
|
|
106
108
|
})
|
|
107
109
|
break
|
|
108
110
|
case "phase":
|
|
109
|
-
if (!id || !phaseId)
|
|
111
|
+
if (!(archiveTaskId && archiveId) && (!id || !phaseId))
|
|
110
112
|
return yield* Effect.fail(
|
|
111
113
|
new Error("Usage: agency archive phase <task-id> <phase-id>"),
|
|
112
114
|
)
|
|
113
|
-
result = yield* archives.archivePhase(
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
result = yield* archives.archivePhase(
|
|
116
|
+
archiveTaskId ?? id!,
|
|
117
|
+
archiveTaskId ? archiveId! : phaseId!,
|
|
118
|
+
cwd,
|
|
119
|
+
{
|
|
120
|
+
dryRun: options.dryRun,
|
|
121
|
+
},
|
|
122
|
+
)
|
|
116
123
|
break
|
|
117
124
|
default:
|
|
118
125
|
return yield* Effect.fail(
|
|
@@ -149,14 +156,15 @@ export const archive = (options: ArchiveOptions) =>
|
|
|
149
156
|
})
|
|
150
157
|
|
|
151
158
|
export const help = `
|
|
152
|
-
Usage: agency archive
|
|
159
|
+
Usage: agency archive [path|list|show|epic|task|tasks|phase]
|
|
153
160
|
|
|
154
161
|
Browse or archive work items after preflighting worktrees and graph references.
|
|
155
162
|
|
|
156
|
-
|
|
163
|
+
With no target, the current epic, task, or phase is inferred. An existing path
|
|
164
|
+
within one of those items infers it too.
|
|
157
165
|
|
|
158
166
|
Commands:
|
|
159
|
-
|
|
167
|
+
[path] Archive the containing epic, task, or phase
|
|
160
168
|
list [filters] List archived work
|
|
161
169
|
show <type> <id> Show an archived epic or task
|
|
162
170
|
show phase <task-id> <phase-id> Show an archived phase
|
package/src/commands/sync.ts
CHANGED
|
@@ -129,7 +129,8 @@ repositories, worktrees, branches, references, and pull requests.
|
|
|
129
129
|
Safe reconciliation transitions are applied by default.
|
|
130
130
|
When a task, phase, or item path is provided, only that target and its
|
|
131
131
|
repositories are queried or reconciled. Task and epic scopes include their
|
|
132
|
-
child execution units.
|
|
132
|
+
child execution units. With no target, the current item is inferred when the
|
|
133
|
+
command runs inside one; the workbase root retains whole-workbase scope.
|
|
133
134
|
|
|
134
135
|
Options:
|
|
135
136
|
--dry-run Report planned safe transitions without changing state
|
package/src/commands/work.ts
CHANGED
|
@@ -139,12 +139,8 @@ export const work = (
|
|
|
139
139
|
const integrations = yield* IntegrationService
|
|
140
140
|
const { log, verboseLog } = createLoggers(options)
|
|
141
141
|
const cwd = options.cwd ?? process.cwd()
|
|
142
|
-
const directoryPath = options.directory
|
|
143
|
-
|
|
144
|
-
: undefined
|
|
145
|
-
const isDirectory = directoryPath
|
|
146
|
-
? yield* fs.isDirectory(directoryPath)
|
|
147
|
-
: false
|
|
142
|
+
const directoryPath = resolve(cwd, options.directory ?? ".")
|
|
143
|
+
const isDirectory = yield* fs.isDirectory(directoryPath)
|
|
148
144
|
const startPath = isDirectory && directoryPath ? directoryPath : cwd
|
|
149
145
|
const inputAllowed = options.inputAllowed ?? true
|
|
150
146
|
const root = yield* resolveWorkbase(startPath, pickBase, inputAllowed)
|
|
@@ -660,16 +656,17 @@ export const help = `
|
|
|
660
656
|
Usage: agency work [<directory-or-task-id> | --epic <epic-id>] [--agent <name>] [--auto]
|
|
661
657
|
agency work prepare [target] [--evidence <json-or-path>] [--force] [--dry-run] [--json]
|
|
662
658
|
|
|
663
|
-
Launch an agent for an epic, task, or phase. With no directory,
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
workbase first. Managed OpenCode launches receive whole-workbase access
|
|
667
|
-
Agency's project plugin; Agency context remains authoritative for writes.
|
|
668
|
-
agent discovery checks opencode2, opencode, pi, then claude.
|
|
659
|
+
Launch an agent for an epic, task, or phase. With no directory, infer the current
|
|
660
|
+
item or select one interactively at the workbase root. A positional argument
|
|
661
|
+
resolves as a directory first, then as a task ID. Outside a workbase, select a
|
|
662
|
+
registered workbase first. Managed OpenCode launches receive whole-workbase access
|
|
663
|
+
through Agency's project plugin; Agency context remains authoritative for writes.
|
|
664
|
+
Automatic agent discovery checks opencode2, opencode, pi, then claude.
|
|
669
665
|
|
|
670
666
|
The prepare subcommand resolves and materializes an execution workspace without
|
|
671
|
-
launching an agent or changing lifecycle status.
|
|
672
|
-
|
|
667
|
+
launching an agent or changing lifecycle status. Its omitted target defaults to
|
|
668
|
+
the current execution unit. --dry-run reports planned Git changes without
|
|
669
|
+
fetching, creating branches, or creating worktrees.
|
|
673
670
|
It emits revision-bound validation evidence and an idempotent external-orchestrator
|
|
674
671
|
contract. Evidence is reused only while the target, workbase, configuration, and
|
|
675
672
|
repository mapping remain unchanged. Dynamic readiness and workspace safety checks
|
|
@@ -54,8 +54,9 @@ class ArchiveError extends Data.TaggedError("ArchiveError")<{
|
|
|
54
54
|
export type ArchiveKind = "epic" | "task" | "phase"
|
|
55
55
|
|
|
56
56
|
interface ArchivePathTarget {
|
|
57
|
-
readonly kind:
|
|
57
|
+
readonly kind: ArchiveKind
|
|
58
58
|
readonly id: string
|
|
59
|
+
readonly taskId?: string
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
const LifecycleEventSchema = Schema.Struct({
|
|
@@ -489,14 +490,11 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
489
490
|
: undefined
|
|
490
491
|
if (!kind || !id) {
|
|
491
492
|
return yield* new ArchiveError({
|
|
492
|
-
message: `Archive path must be within an active epic or
|
|
493
|
-
})
|
|
494
|
-
}
|
|
495
|
-
if (kind === "task" && parts[2] === "phases") {
|
|
496
|
-
return yield* new ArchiveError({
|
|
497
|
-
message: `Archive path identifies a phase; use 'agency archive phase <task-id> <phase-id>': ${canonicalPath}`,
|
|
493
|
+
message: `Archive path must be within an active epic, task, or phase: ${canonicalPath}`,
|
|
498
494
|
})
|
|
499
495
|
}
|
|
496
|
+
const phaseId =
|
|
497
|
+
kind === "task" && parts[2] === "phases" ? parts[3] : undefined
|
|
500
498
|
|
|
501
499
|
const document = join(
|
|
502
500
|
root,
|
|
@@ -509,6 +507,26 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
509
507
|
message: `Archive path does not identify an active ${kind}: ${canonicalPath}`,
|
|
510
508
|
})
|
|
511
509
|
}
|
|
510
|
+
if (phaseId) {
|
|
511
|
+
const phaseDocument = join(
|
|
512
|
+
root,
|
|
513
|
+
"tasks",
|
|
514
|
+
id,
|
|
515
|
+
"phases",
|
|
516
|
+
phaseId,
|
|
517
|
+
"PHASE.md",
|
|
518
|
+
)
|
|
519
|
+
if (!(yield* fs.exists(phaseDocument))) {
|
|
520
|
+
return yield* new ArchiveError({
|
|
521
|
+
message: `Archive path does not identify an active phase: ${canonicalPath}`,
|
|
522
|
+
})
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
kind: "phase",
|
|
526
|
+
id: phaseId,
|
|
527
|
+
taskId: id,
|
|
528
|
+
} satisfies ArchivePathTarget
|
|
529
|
+
}
|
|
512
530
|
return { kind, id } satisfies ArchivePathTarget
|
|
513
531
|
}),
|
|
514
532
|
|
|
@@ -229,13 +229,10 @@ export class SyncService extends Effect.Service<SyncService>()("SyncService", {
|
|
|
229
229
|
const repositories = yield* RepositoryService
|
|
230
230
|
const versionControl = yield* VersionControlService
|
|
231
231
|
const cwd = resolve(options.cwd ?? process.cwd())
|
|
232
|
-
const candidate = options.taskId
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
candidate !== undefined && !options.phaseId
|
|
237
|
-
? yield* fs.exists(candidate)
|
|
238
|
-
: false
|
|
232
|
+
const candidate = resolve(cwd, options.taskId ?? ".")
|
|
233
|
+
const candidateExists = !options.phaseId
|
|
234
|
+
? yield* fs.exists(candidate)
|
|
235
|
+
: false
|
|
239
236
|
const { root, config } = yield* workbase.loadConfig(
|
|
240
237
|
candidateExists ? candidate : cwd,
|
|
241
238
|
)
|
|
@@ -262,7 +259,7 @@ export class SyncService extends Effect.Service<SyncService>()("SyncService", {
|
|
|
262
259
|
const existingTaskSelector =
|
|
263
260
|
options.taskId !== undefined &&
|
|
264
261
|
documents.tasks.some((task) => task.id === options.taskId)
|
|
265
|
-
if (candidateExists &&
|
|
262
|
+
if (candidateExists && !existingTaskSelector) {
|
|
266
263
|
const canonicalPath = yield* fs.realPath(candidate)
|
|
267
264
|
const canonicalRoot = yield* fs.realPath(root)
|
|
268
265
|
const parts = relative(canonicalRoot, canonicalPath).split(sep)
|
|
@@ -272,7 +269,7 @@ export class SyncService extends Effect.Service<SyncService>()("SyncService", {
|
|
|
272
269
|
} else if (parts[0] === "tasks" && parts[1]) {
|
|
273
270
|
taskId = parts[1]
|
|
274
271
|
phaseId = parts[2] === "phases" && parts[3] ? parts[3] : undefined
|
|
275
|
-
} else {
|
|
272
|
+
} else if (options.taskId !== undefined) {
|
|
276
273
|
return yield* new SyncError({
|
|
277
274
|
message: `Sync path does not identify an active task, phase, or epic: ${canonicalPath}`,
|
|
278
275
|
})
|