@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.14",
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 ?? (yield* workbase.validate(root))
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
- graph: AgencyGraph,
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 = graph.nodes.find(
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 = graph.nodes.filter(
82
- (candidate): candidate is ExecutionNode =>
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
- graph.nodes
118
- .filter((node): node is ExecutionNode => node.kind === "execution-unit")
119
- .map((node) => itemFor(node, graph, 0))
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",