@markjaquith/agency 2.71.5 → 2.71.6
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.6",
|
|
4
4
|
"description": "Manage agentic work across repositories with durable workbases",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"preuninstall": "bun scripts/install-pi-extension.ts uninstall",
|
|
67
67
|
"benchmark:status": "bun scripts/benchmark-status.ts",
|
|
68
68
|
"benchmark:workbase": "bun scripts/benchmark-workbase.ts",
|
|
69
|
+
"benchmark:doctor": "bun scripts/benchmark-doctor.ts",
|
|
69
70
|
"benchmark:context": "bun scripts/benchmark-context.ts",
|
|
70
71
|
"benchmark:sync": "bun scripts/benchmark-sync.ts",
|
|
71
72
|
"benchmark:task": "bun scripts/benchmark-task.ts",
|
|
@@ -2,12 +2,11 @@ import { Effect, Either } from "effect"
|
|
|
2
2
|
import { constants } from "node:fs"
|
|
3
3
|
import { access } from "node:fs/promises"
|
|
4
4
|
import { isAbsolute, join, resolve } from "node:path"
|
|
5
|
-
import { EpicService } from "./EpicService"
|
|
6
5
|
import { FileSystemService } from "./FileSystemService"
|
|
7
6
|
import { IntegrationService } from "./IntegrationService"
|
|
8
|
-
import {
|
|
7
|
+
import type { PhaseRecord } from "./PhaseService"
|
|
9
8
|
import { RepositoryService } from "./RepositoryService"
|
|
10
|
-
import {
|
|
9
|
+
import type { TaskRecord } from "./TaskService"
|
|
11
10
|
import { WorkbaseService } from "./WorkbaseService"
|
|
12
11
|
import { WorktreeService } from "./WorktreeService"
|
|
13
12
|
import { VersionControlService } from "./VersionControlService"
|
|
@@ -80,12 +79,9 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
80
79
|
sync: () => ({
|
|
81
80
|
inspect: (startPath: string = process.cwd()) =>
|
|
82
81
|
Effect.gen(function* () {
|
|
83
|
-
const epics = yield* EpicService
|
|
84
82
|
const fs = yield* FileSystemService
|
|
85
83
|
const integrations = yield* IntegrationService
|
|
86
|
-
const phases = yield* PhaseService
|
|
87
84
|
const repositories = yield* RepositoryService
|
|
88
|
-
const tasks = yield* TaskService
|
|
89
85
|
const workbases = yield* WorkbaseService
|
|
90
86
|
const worktrees = yield* WorktreeService
|
|
91
87
|
const versionControl = yield* VersionControlService
|
|
@@ -105,49 +101,60 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
105
101
|
: null,
|
|
106
102
|
})
|
|
107
103
|
|
|
108
|
-
const tool =
|
|
104
|
+
const tool = (
|
|
109
105
|
id: string,
|
|
110
106
|
executable: string,
|
|
111
107
|
level: DoctorCheckLevel,
|
|
112
108
|
label: string,
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
109
|
+
) =>
|
|
110
|
+
Effect.gen(function* () {
|
|
111
|
+
const available = yield* executableAvailable(executable, root)
|
|
112
|
+
add({
|
|
113
|
+
id,
|
|
114
|
+
category: id.startsWith("tool.") ? "tool" : "integration",
|
|
115
|
+
level,
|
|
116
|
+
status: available ? "pass" : "fail",
|
|
117
|
+
message: available
|
|
118
|
+
? `${label} executable '${executable}' is available`
|
|
119
|
+
: `${label} executable '${executable}' is unavailable`,
|
|
120
|
+
remediation:
|
|
121
|
+
level === "optional"
|
|
122
|
+
? `Install '${executable}' to enable ${label.toLowerCase()}, or leave it unavailable if unused.`
|
|
123
|
+
: `Install '${executable}' and ensure it is executable on PATH.`,
|
|
124
|
+
})
|
|
125
|
+
return available
|
|
127
126
|
})
|
|
128
|
-
return available
|
|
129
|
-
}
|
|
130
127
|
|
|
131
|
-
const gitAvailable = yield*
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
128
|
+
const [gitAvailable, jjAvailable] = yield* Effect.all(
|
|
129
|
+
[
|
|
130
|
+
tool("tool.git", "git", "error", "Git"),
|
|
131
|
+
tool(
|
|
132
|
+
"tool.jj",
|
|
133
|
+
"jj",
|
|
134
|
+
config.vcs === "jj" ? "error" : "optional",
|
|
135
|
+
"Jujutsu",
|
|
136
|
+
),
|
|
137
|
+
],
|
|
138
|
+
{ concurrency: "unbounded", batching: true },
|
|
137
139
|
)
|
|
138
140
|
const versionControlAvailable =
|
|
139
141
|
gitAvailable && (config.vcs !== "jj" || jjAvailable)
|
|
140
|
-
yield*
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
142
|
+
yield* Effect.all(
|
|
143
|
+
[
|
|
144
|
+
tool(
|
|
145
|
+
"capability.agent.opencode",
|
|
146
|
+
"opencode",
|
|
147
|
+
"optional",
|
|
148
|
+
"OpenCode agent",
|
|
149
|
+
),
|
|
150
|
+
tool(
|
|
151
|
+
"capability.agent.claude",
|
|
152
|
+
"claude",
|
|
153
|
+
"optional",
|
|
154
|
+
"Claude agent",
|
|
155
|
+
),
|
|
156
|
+
],
|
|
157
|
+
{ concurrency: "unbounded", batching: true },
|
|
151
158
|
)
|
|
152
159
|
|
|
153
160
|
const configuredCommands: readonly (readonly [
|
|
@@ -250,11 +257,16 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
250
257
|
]
|
|
251
258
|
: []),
|
|
252
259
|
]
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
260
|
+
yield* Effect.all(
|
|
261
|
+
configuredCommands.map(([id, command, label]) =>
|
|
262
|
+
tool(id, command[0]!, "error", label),
|
|
263
|
+
),
|
|
264
|
+
{ concurrency: 16, batching: true },
|
|
265
|
+
)
|
|
256
266
|
|
|
257
|
-
const validation = yield* workbases.validate(root
|
|
267
|
+
const validation = yield* workbases.validate(root, {
|
|
268
|
+
includeDocuments: true,
|
|
269
|
+
})
|
|
258
270
|
add({
|
|
259
271
|
id: "workbase.validation",
|
|
260
272
|
category: "workbase",
|
|
@@ -267,7 +279,7 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
267
279
|
"Run 'agency validate' and correct every reported issue.",
|
|
268
280
|
})
|
|
269
281
|
|
|
270
|
-
|
|
282
|
+
const permissionChecks = [
|
|
271
283
|
[
|
|
272
284
|
"permission.workbase.read",
|
|
273
285
|
constants.R_OK,
|
|
@@ -282,8 +294,16 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
282
294
|
"writable",
|
|
283
295
|
`Grant the current user write access to ${root} before running mutation commands.`,
|
|
284
296
|
],
|
|
285
|
-
] as const
|
|
286
|
-
|
|
297
|
+
] as const
|
|
298
|
+
const permissionResults = yield* Effect.all(
|
|
299
|
+
permissionChecks.map(([, mode]) => permissionAvailable(root, mode)),
|
|
300
|
+
{ concurrency: "unbounded", batching: true },
|
|
301
|
+
)
|
|
302
|
+
for (const [
|
|
303
|
+
index,
|
|
304
|
+
[id, , level, label, remediation],
|
|
305
|
+
] of permissionChecks.entries()) {
|
|
306
|
+
const available = permissionResults[index]!
|
|
287
307
|
add({
|
|
288
308
|
id,
|
|
289
309
|
category: "permission",
|
|
@@ -321,12 +341,12 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
321
341
|
values.add(ref)
|
|
322
342
|
refs.set(repo, values)
|
|
323
343
|
}
|
|
324
|
-
if (validation.valid) {
|
|
325
|
-
for (const epic of
|
|
344
|
+
if (validation.valid && validation.documents) {
|
|
345
|
+
for (const epic of validation.documents.epics) {
|
|
326
346
|
for (const reference of epic.data.repos)
|
|
327
347
|
declareRef(reference.repo, reference.ref)
|
|
328
348
|
}
|
|
329
|
-
for (const task of
|
|
349
|
+
for (const task of validation.documents.tasks) {
|
|
330
350
|
if ("review" in task.data) {
|
|
331
351
|
declareRef(task.data.review.repo, task.data.review.commit)
|
|
332
352
|
reviewSources.push({
|
|
@@ -344,7 +364,9 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
344
364
|
for (const reference of task.data.repos ?? [])
|
|
345
365
|
declareRef(reference.repo, reference.ref)
|
|
346
366
|
} else {
|
|
347
|
-
for (const phase of
|
|
367
|
+
for (const phase of validation.documents.phasesByTask.get(
|
|
368
|
+
task.id,
|
|
369
|
+
) ?? []) {
|
|
348
370
|
declareRef(phase.data.repo, phase.data.base)
|
|
349
371
|
for (const reference of phase.data.repos ?? [])
|
|
350
372
|
declareRef(reference.repo, reference.ref)
|
|
@@ -442,7 +464,36 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
442
464
|
}
|
|
443
465
|
|
|
444
466
|
if (validation.valid && versionControlAvailable) {
|
|
445
|
-
const
|
|
467
|
+
const tasks = validation.documents?.tasks.map(
|
|
468
|
+
(record) =>
|
|
469
|
+
({ ...record, content: "", revision: "" }) satisfies TaskRecord,
|
|
470
|
+
)
|
|
471
|
+
const phasesByTask = validation.documents
|
|
472
|
+
? new Map(
|
|
473
|
+
[...validation.documents.phasesByTask].map(
|
|
474
|
+
([taskId, records]) =>
|
|
475
|
+
[
|
|
476
|
+
taskId,
|
|
477
|
+
records.map(
|
|
478
|
+
(record) =>
|
|
479
|
+
({
|
|
480
|
+
...record,
|
|
481
|
+
taskId,
|
|
482
|
+
content: "",
|
|
483
|
+
revision: "",
|
|
484
|
+
}) satisfies PhaseRecord,
|
|
485
|
+
),
|
|
486
|
+
] as const,
|
|
487
|
+
),
|
|
488
|
+
)
|
|
489
|
+
: undefined
|
|
490
|
+
const inspected = yield* Effect.either(
|
|
491
|
+
worktrees.list(root, {
|
|
492
|
+
materializedOnly: true,
|
|
493
|
+
tasks,
|
|
494
|
+
phasesByTask,
|
|
495
|
+
}),
|
|
496
|
+
)
|
|
446
497
|
if (Either.isLeft(inspected)) {
|
|
447
498
|
add({
|
|
448
499
|
id: "worktree.inspection",
|
|
@@ -56,6 +56,7 @@ export interface ValidationReport {
|
|
|
56
56
|
readonly taskCount: number
|
|
57
57
|
readonly phaseCount: number
|
|
58
58
|
readonly valid: boolean
|
|
59
|
+
readonly documents?: ValidationDocuments
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
interface DocumentRecord<T> {
|
|
@@ -64,6 +65,15 @@ interface DocumentRecord<T> {
|
|
|
64
65
|
readonly data: T
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
interface ValidationDocuments {
|
|
69
|
+
readonly epics: readonly DocumentRecord<EpicData>[]
|
|
70
|
+
readonly tasks: readonly DocumentRecord<TaskData>[]
|
|
71
|
+
readonly phasesByTask: ReadonlyMap<
|
|
72
|
+
string,
|
|
73
|
+
readonly DocumentRecord<PhaseData>[]
|
|
74
|
+
>
|
|
75
|
+
}
|
|
76
|
+
|
|
67
77
|
type DecodeResult<T> =
|
|
68
78
|
| { readonly success: true; readonly value: T }
|
|
69
79
|
| { readonly success: false; readonly error: string }
|
|
@@ -653,7 +663,10 @@ export class WorkbaseService extends Effect.Service<WorkbaseService>()(
|
|
|
653
663
|
),
|
|
654
664
|
),
|
|
655
665
|
|
|
656
|
-
validate: (
|
|
666
|
+
validate: (
|
|
667
|
+
startPath: string = process.cwd(),
|
|
668
|
+
options: { readonly includeDocuments?: boolean } = {},
|
|
669
|
+
) =>
|
|
657
670
|
Effect.gen(function* () {
|
|
658
671
|
const service = yield* WorkbaseService
|
|
659
672
|
const fs = yield* FileSystemService
|
|
@@ -959,6 +972,14 @@ export class WorkbaseService extends Effect.Service<WorkbaseService>()(
|
|
|
959
972
|
: a.path.localeCompare(b.path),
|
|
960
973
|
)
|
|
961
974
|
|
|
975
|
+
const phasesByTask = new Map<string, DocumentRecord<PhaseData>[]>()
|
|
976
|
+
for (const [key, phase] of phases) {
|
|
977
|
+
const taskId = key.slice(0, key.indexOf("/"))
|
|
978
|
+
const records = phasesByTask.get(taskId) ?? []
|
|
979
|
+
records.push(phase)
|
|
980
|
+
phasesByTask.set(taskId, records)
|
|
981
|
+
}
|
|
982
|
+
|
|
962
983
|
return {
|
|
963
984
|
root,
|
|
964
985
|
issues,
|
|
@@ -966,6 +987,15 @@ export class WorkbaseService extends Effect.Service<WorkbaseService>()(
|
|
|
966
987
|
taskCount: tasks.size,
|
|
967
988
|
phaseCount: phases.size,
|
|
968
989
|
valid: issues.length === 0,
|
|
990
|
+
...(options.includeDocuments
|
|
991
|
+
? {
|
|
992
|
+
documents: {
|
|
993
|
+
epics: [...epics.values()],
|
|
994
|
+
tasks: [...tasks.values()],
|
|
995
|
+
phasesByTask,
|
|
996
|
+
},
|
|
997
|
+
}
|
|
998
|
+
: {}),
|
|
969
999
|
} satisfies ValidationReport
|
|
970
1000
|
}),
|
|
971
1001
|
}),
|