@markjaquith/agency 2.71.14 → 2.71.16
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markjaquith/agency",
|
|
3
|
-
"version": "2.71.
|
|
3
|
+
"version": "2.71.16",
|
|
4
4
|
"description": "Manage agentic work across repositories with durable workbases",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -68,12 +68,14 @@
|
|
|
68
68
|
"benchmark:pr": "bun scripts/benchmark-pr.ts",
|
|
69
69
|
"benchmark:release": "bun scripts/benchmark-release.ts",
|
|
70
70
|
"benchmark:status": "bun scripts/benchmark-status.ts",
|
|
71
|
+
"benchmark:next": "bun scripts/benchmark-next.ts",
|
|
71
72
|
"benchmark:init": "bun scripts/benchmark-init.ts",
|
|
72
73
|
"benchmark:workbase": "bun scripts/benchmark-workbase.ts",
|
|
73
74
|
"benchmark:doctor": "bun scripts/benchmark-doctor.ts",
|
|
74
75
|
"benchmark:context": "bun scripts/benchmark-context.ts",
|
|
75
76
|
"benchmark:claim": "bun scripts/benchmark-claim.ts",
|
|
76
77
|
"benchmark:finish": "bun scripts/benchmark-finish.ts",
|
|
78
|
+
"benchmark:phase": "bun scripts/benchmark-phase.ts",
|
|
77
79
|
"benchmark:push": "bun scripts/benchmark-push.ts",
|
|
78
80
|
"benchmark:sync": "bun scripts/benchmark-sync.ts",
|
|
79
81
|
"benchmark:task": "bun scripts/benchmark-task.ts",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
2
|
import { mkdir } from "node:fs/promises"
|
|
3
3
|
import { join } from "node:path"
|
|
4
|
-
import { Effect } from "effect"
|
|
4
|
+
import { Effect, Layer } from "effect"
|
|
5
5
|
import {
|
|
6
6
|
captureLogs,
|
|
7
7
|
cleanupTempDir,
|
|
@@ -10,6 +10,10 @@ import {
|
|
|
10
10
|
} from "../test-utils"
|
|
11
11
|
import { phase } from "./phase"
|
|
12
12
|
import { task } from "./task"
|
|
13
|
+
import { FileSystemService } from "../services/FileSystemService"
|
|
14
|
+
import { PhaseService } from "../services/PhaseService"
|
|
15
|
+
import { TaskService } from "../services/TaskService"
|
|
16
|
+
import { WorkbaseService } from "../services/WorkbaseService"
|
|
13
17
|
|
|
14
18
|
describe("task and phase command JSON output", () => {
|
|
15
19
|
let root: string
|
|
@@ -186,6 +190,45 @@ describe("task and phase command JSON output", () => {
|
|
|
186
190
|
})
|
|
187
191
|
})
|
|
188
192
|
|
|
193
|
+
test("shows one phase without reading sibling phase documents", async () => {
|
|
194
|
+
await runTestEffect(
|
|
195
|
+
phase({
|
|
196
|
+
subcommand: "create",
|
|
197
|
+
args: ["multi", "second"],
|
|
198
|
+
repo: "agency",
|
|
199
|
+
branch: "task/second",
|
|
200
|
+
base: "main",
|
|
201
|
+
cwd: root,
|
|
202
|
+
silent: true,
|
|
203
|
+
}),
|
|
204
|
+
)
|
|
205
|
+
const fs = await Effect.runPromise(
|
|
206
|
+
FileSystemService.pipe(Effect.provide(FileSystemService.Default)),
|
|
207
|
+
)
|
|
208
|
+
const readPaths: string[] = []
|
|
209
|
+
const countingFs = {
|
|
210
|
+
...fs,
|
|
211
|
+
readFile: (path: string) => {
|
|
212
|
+
readPaths.push(path)
|
|
213
|
+
return fs.readFile(path)
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
const program = PhaseService.pipe(
|
|
217
|
+
Effect.flatMap((service) => service.show("multi", "second", root)),
|
|
218
|
+
Effect.provide(PhaseService.Default),
|
|
219
|
+
Effect.provide(TaskService.Default),
|
|
220
|
+
Effect.provide(WorkbaseService.Default),
|
|
221
|
+
Effect.provide(Layer.succeed(FileSystemService, countingFs)),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
const record = await Effect.runPromise(program)
|
|
225
|
+
|
|
226
|
+
expect(record.id).toBe("second")
|
|
227
|
+
expect(readPaths.filter((path) => path.endsWith("PHASE.md"))).toEqual([
|
|
228
|
+
join(root, "tasks/multi/phases/second/PHASE.md"),
|
|
229
|
+
])
|
|
230
|
+
})
|
|
231
|
+
|
|
189
232
|
test("renders task and phase operational tables", async () => {
|
|
190
233
|
const taskLogs = await captureLogs(() =>
|
|
191
234
|
runTestEffect(task({ subcommand: "list", args: [], cwd: root })),
|
|
@@ -339,7 +339,8 @@ export class GraphService extends Effect.Service<GraphService>()(
|
|
|
339
339
|
})
|
|
340
340
|
|
|
341
341
|
const validation =
|
|
342
|
-
options.validation ??
|
|
342
|
+
options.validation ??
|
|
343
|
+
(yield* workbase.validate(root, { includeDocuments: true }))
|
|
343
344
|
const validationIssuesByPath = Map.groupBy(
|
|
344
345
|
validation.issues,
|
|
345
346
|
(issue) => issue.path,
|
|
@@ -65,24 +65,19 @@ const isWorkTarget = (node: GraphNode) =>
|
|
|
65
65
|
|
|
66
66
|
const itemFor = (
|
|
67
67
|
node: ExecutionNode,
|
|
68
|
-
|
|
68
|
+
tasks: ReadonlyMap<string, Extract<GraphNode, { readonly kind: "task" }>>,
|
|
69
|
+
executionsByTask: ReadonlyMap<string, readonly ExecutionNode[]>,
|
|
69
70
|
rank: number,
|
|
70
71
|
): NextItem => {
|
|
71
|
-
const task =
|
|
72
|
-
(candidate) =>
|
|
73
|
-
candidate.kind === "task" && candidate.key === node.data.taskId,
|
|
74
|
-
)
|
|
72
|
+
const task = tasks.get(node.data.taskId)
|
|
75
73
|
const epicId =
|
|
76
74
|
task?.kind === "task" && typeof task.data.epic === "string"
|
|
77
75
|
? task.data.epic
|
|
78
76
|
: undefined
|
|
79
77
|
const dependentIds = new Set(node.dependents)
|
|
80
78
|
if ("phaseId" in node.data && node.data.phaseId) {
|
|
81
|
-
const siblings =
|
|
82
|
-
(candidate)
|
|
83
|
-
candidate.kind === "execution-unit" &&
|
|
84
|
-
candidate.data.taskId === node.data.taskId &&
|
|
85
|
-
candidate.id !== node.id,
|
|
79
|
+
const siblings = (executionsByTask.get(node.data.taskId) ?? []).filter(
|
|
80
|
+
(candidate) => candidate.id !== node.id,
|
|
86
81
|
)
|
|
87
82
|
if (siblings.every((sibling) => sibling.status === "done")) {
|
|
88
83
|
for (const dependent of task?.dependents ?? [])
|
|
@@ -113,16 +108,27 @@ const itemFor = (
|
|
|
113
108
|
}
|
|
114
109
|
}
|
|
115
110
|
|
|
116
|
-
const rankedItems = (graph: AgencyGraph) =>
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
111
|
+
const rankedItems = (graph: AgencyGraph) => {
|
|
112
|
+
const tasks = new Map<string, Extract<GraphNode, { readonly kind: "task" }>>()
|
|
113
|
+
const executionsByTask = new Map<string, ExecutionNode[]>()
|
|
114
|
+
const executions: ExecutionNode[] = []
|
|
115
|
+
for (const node of graph.nodes) {
|
|
116
|
+
if (node.kind === "task") tasks.set(node.key, node)
|
|
117
|
+
if (node.kind !== "execution-unit") continue
|
|
118
|
+
executions.push(node)
|
|
119
|
+
const siblings = executionsByTask.get(node.data.taskId)
|
|
120
|
+
if (siblings) siblings.push(node)
|
|
121
|
+
else executionsByTask.set(node.data.taskId, [node])
|
|
122
|
+
}
|
|
123
|
+
return executions
|
|
124
|
+
.map((node) => itemFor(node, tasks, executionsByTask, 0))
|
|
120
125
|
.sort(
|
|
121
126
|
(left, right) =>
|
|
122
127
|
right.priority.dependentCount - left.priority.dependentCount ||
|
|
123
128
|
left.key.localeCompare(right.key),
|
|
124
129
|
)
|
|
125
130
|
.map((item, index) => ({ ...item, rank: index + 1 }))
|
|
131
|
+
}
|
|
126
132
|
|
|
127
133
|
const guardMessage = (
|
|
128
134
|
action: "work" | "pr",
|