@markjaquith/agency 2.74.0 → 3.1.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 +29 -122
- package/cli-main.ts +10 -28
- package/cli.ts +1 -17
- package/package.json +1 -2
- package/schemas/agency-graph-v1.schema.json +1 -1
- package/src/cli-parser.test.ts +5 -19
- package/src/cli-parser.ts +12 -30
- package/src/cli.test.ts +6 -6
- package/src/commands/archive.test.ts +73 -1
- package/src/commands/archive.ts +18 -7
- package/src/commands/context.test.ts +0 -67
- package/src/commands/init.test.ts +1 -2
- package/src/commands/pr.test.ts +2 -87
- package/src/commands/pr.ts +2 -79
- package/src/commands/push.test.ts +1 -2
- package/src/graph-schema.ts +1 -1
- package/src/services/ArchiveBulkService.test.ts +1 -142
- package/src/services/ArchiveService.test.ts +1 -172
- package/src/services/ArchiveService.ts +59 -27
- package/src/services/ContextService.ts +3 -47
- package/src/services/DoctorService.ts +8 -24
- package/src/services/GraphService.test.ts +1 -0
- package/src/services/GraphService.ts +2 -5
- package/src/services/PhaseService.ts +70 -191
- package/src/services/PullRequestService.test.ts +1 -45
- package/src/services/PullRequestService.ts +1 -22
- package/src/services/PushService.test.ts +29 -295
- package/src/services/PushService.ts +10 -239
- package/src/services/RepositoryService.test.ts +1 -61
- package/src/services/RepositoryService.ts +2 -13
- package/src/services/ReviewService.test.ts +1 -84
- package/src/services/ReviewService.ts +9 -48
- package/src/services/SyncService.test.ts +0 -81
- package/src/services/SyncService.ts +12 -39
- package/src/services/TaskPhaseService.test.ts +0 -80
- package/src/services/TaskService.ts +1 -9
- package/src/services/VersionControlService.test.ts +4 -117
- package/src/services/VersionControlService.ts +3 -426
- package/src/services/WorkbaseService.test.ts +0 -20
- package/src/services/WorkbaseService.ts +1 -18
- package/src/services/WorktreeService.test.ts +1 -705
- package/src/services/WorktreeService.ts +395 -1604
- package/src/services/push-validation.ts +0 -7
- package/src/test-utils.ts +0 -4
- package/src/workbase/AGENTS.md +4 -5
- package/src/workbase/checkout-command.test.ts +1 -1
- package/src/workbase/checkout-command.ts +1 -1
- package/src/workbase/delivery-command.test.ts +4 -35
- package/src/workbase/delivery-command.ts +2 -15
- package/src/workbase/schemas.test.ts +0 -19
- package/src/workbase/schemas.ts +1 -2
- package/src/commands/vcs.test.ts +0 -75
- package/src/commands/vcs.ts +0 -72
- package/src/services/VcsMigrationService.test.ts +0 -348
- package/src/services/VcsMigrationService.ts +0 -857
- package/src/vcs-status-fast.ts +0 -310
- package/src/workbase/version-control.ts +0 -5
- package/src/workbase/workspace-command.test.ts +0 -70
- package/src/workbase/workspace-command.ts +0 -63
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
2
|
import { Effect } from "effect"
|
|
3
|
-
import { mkdir
|
|
3
|
+
import { mkdir } from "node:fs/promises"
|
|
4
4
|
import { join } from "node:path"
|
|
5
5
|
import { cleanupTempDir, createTempDir, runTestEffect } from "../test-utils"
|
|
6
6
|
import { ArchiveService } from "./ArchiveService"
|
|
@@ -22,17 +22,6 @@ const git = async (args: string[], cwd?: string) => {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const jj = async (args: string[]) => {
|
|
26
|
-
const process = Bun.spawn(["jj", ...args], {
|
|
27
|
-
stdout: "pipe",
|
|
28
|
-
stderr: "pipe",
|
|
29
|
-
})
|
|
30
|
-
await process.exited
|
|
31
|
-
if (process.exitCode !== 0) {
|
|
32
|
-
throw new Error(await new Response(process.stderr).text())
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
25
|
describe("ArchiveService", () => {
|
|
37
26
|
let root: string
|
|
38
27
|
const dropTask = (id: string) =>
|
|
@@ -276,166 +265,6 @@ describe("ArchiveService", () => {
|
|
|
276
265
|
).toBe(0)
|
|
277
266
|
})
|
|
278
267
|
|
|
279
|
-
test("retries archive after a jj workspace was forgotten but not deleted", async () => {
|
|
280
|
-
if (!Bun.which("jj")) return
|
|
281
|
-
const repository = join(root, "repos/agency")
|
|
282
|
-
await rm(repository, { recursive: true, force: true })
|
|
283
|
-
await git(["clone", join(root, "source"), repository])
|
|
284
|
-
await jj(["git", "init", "--colocate", repository])
|
|
285
|
-
await Bun.write(
|
|
286
|
-
join(root, "agency.json"),
|
|
287
|
-
JSON.stringify({ version: 2, vcs: "jj" }),
|
|
288
|
-
)
|
|
289
|
-
await runTestEffect(
|
|
290
|
-
TaskService.pipe(
|
|
291
|
-
Effect.flatMap((service) =>
|
|
292
|
-
service.create(
|
|
293
|
-
{
|
|
294
|
-
id: "interrupted",
|
|
295
|
-
ticketUrl: null,
|
|
296
|
-
repo: "agency",
|
|
297
|
-
branch: "task/interrupted",
|
|
298
|
-
base: "main",
|
|
299
|
-
},
|
|
300
|
-
root,
|
|
301
|
-
),
|
|
302
|
-
),
|
|
303
|
-
),
|
|
304
|
-
)
|
|
305
|
-
await runTestEffect(
|
|
306
|
-
TaskService.pipe(
|
|
307
|
-
Effect.flatMap((service) =>
|
|
308
|
-
service.setStatus("interrupted", "dropped", root),
|
|
309
|
-
),
|
|
310
|
-
),
|
|
311
|
-
)
|
|
312
|
-
const workspace = await runTestEffect(
|
|
313
|
-
WorktreeService.pipe(
|
|
314
|
-
Effect.flatMap((service) =>
|
|
315
|
-
service.materialize("interrupted", undefined, root),
|
|
316
|
-
),
|
|
317
|
-
),
|
|
318
|
-
)
|
|
319
|
-
await jj([
|
|
320
|
-
"-R",
|
|
321
|
-
repository,
|
|
322
|
-
"workspace",
|
|
323
|
-
"forget",
|
|
324
|
-
"agency-interrupted-task-agency",
|
|
325
|
-
])
|
|
326
|
-
|
|
327
|
-
const preview = await runTestEffect(
|
|
328
|
-
ArchiveService.pipe(
|
|
329
|
-
Effect.flatMap((service) =>
|
|
330
|
-
service.archiveTask("interrupted", root, { dryRun: true }),
|
|
331
|
-
),
|
|
332
|
-
),
|
|
333
|
-
)
|
|
334
|
-
expect(preview.removedWorktrees).toEqual([workspace.writablePath!])
|
|
335
|
-
|
|
336
|
-
await runTestEffect(
|
|
337
|
-
ArchiveService.pipe(
|
|
338
|
-
Effect.flatMap((service) => service.archiveTask("interrupted", root)),
|
|
339
|
-
),
|
|
340
|
-
)
|
|
341
|
-
expect(await Bun.file(workspace.writablePath!).exists()).toBe(false)
|
|
342
|
-
expect(
|
|
343
|
-
await Bun.file(join(root, "archive/tasks/interrupted/TASK.md")).exists(),
|
|
344
|
-
).toBe(true)
|
|
345
|
-
})
|
|
346
|
-
|
|
347
|
-
test("refuses a modified residual jj checkout after workspace removal", async () => {
|
|
348
|
-
if (!Bun.which("jj")) return
|
|
349
|
-
const repository = join(root, "repos/agency")
|
|
350
|
-
await rm(repository, { recursive: true, force: true })
|
|
351
|
-
await git(["clone", join(root, "source"), repository])
|
|
352
|
-
await jj(["git", "init", "--colocate", repository])
|
|
353
|
-
await Bun.write(
|
|
354
|
-
join(root, "agency.json"),
|
|
355
|
-
JSON.stringify({ version: 2, vcs: "jj" }),
|
|
356
|
-
)
|
|
357
|
-
await runTestEffect(
|
|
358
|
-
TaskService.pipe(
|
|
359
|
-
Effect.flatMap((service) =>
|
|
360
|
-
service.create(
|
|
361
|
-
{
|
|
362
|
-
id: "modified-residual",
|
|
363
|
-
ticketUrl: null,
|
|
364
|
-
repo: "agency",
|
|
365
|
-
branch: "task/modified-residual",
|
|
366
|
-
base: "main",
|
|
367
|
-
},
|
|
368
|
-
root,
|
|
369
|
-
),
|
|
370
|
-
),
|
|
371
|
-
),
|
|
372
|
-
)
|
|
373
|
-
await runTestEffect(
|
|
374
|
-
TaskService.pipe(
|
|
375
|
-
Effect.flatMap((service) =>
|
|
376
|
-
service.setStatus("modified-residual", "dropped", root),
|
|
377
|
-
),
|
|
378
|
-
),
|
|
379
|
-
)
|
|
380
|
-
const workspace = await runTestEffect(
|
|
381
|
-
WorktreeService.pipe(
|
|
382
|
-
Effect.flatMap((service) =>
|
|
383
|
-
service.materialize("modified-residual", undefined, root),
|
|
384
|
-
),
|
|
385
|
-
),
|
|
386
|
-
)
|
|
387
|
-
await jj([
|
|
388
|
-
"-R",
|
|
389
|
-
repository,
|
|
390
|
-
"workspace",
|
|
391
|
-
"forget",
|
|
392
|
-
"agency-modified-residual-task-agency",
|
|
393
|
-
])
|
|
394
|
-
await Bun.write(join(workspace.writablePath!, "keep.txt"), "keep me\n")
|
|
395
|
-
|
|
396
|
-
await expect(
|
|
397
|
-
runTestEffect(
|
|
398
|
-
ArchiveService.pipe(
|
|
399
|
-
Effect.flatMap((service) =>
|
|
400
|
-
service.archiveTask("modified-residual", root),
|
|
401
|
-
),
|
|
402
|
-
),
|
|
403
|
-
),
|
|
404
|
-
).rejects.toThrow("contents do not match expected revision")
|
|
405
|
-
expect(
|
|
406
|
-
await Bun.file(join(workspace.writablePath!, "keep.txt")).text(),
|
|
407
|
-
).toBe("keep me\n")
|
|
408
|
-
expect(
|
|
409
|
-
await Bun.file(join(root, "tasks/modified-residual/TASK.md")).exists(),
|
|
410
|
-
).toBe(true)
|
|
411
|
-
expect(
|
|
412
|
-
await Bun.file(
|
|
413
|
-
join(
|
|
414
|
-
root,
|
|
415
|
-
`.agency-worktree-${Buffer.from("modified-residual:task").toString("hex")}.lock`,
|
|
416
|
-
),
|
|
417
|
-
).exists(),
|
|
418
|
-
).toBe(false)
|
|
419
|
-
|
|
420
|
-
await rm(join(workspace.writablePath!, "keep.txt"))
|
|
421
|
-
await rm(join(workspace.writablePath!, ".jj"), {
|
|
422
|
-
recursive: true,
|
|
423
|
-
force: true,
|
|
424
|
-
})
|
|
425
|
-
await expect(
|
|
426
|
-
runTestEffect(
|
|
427
|
-
ArchiveService.pipe(
|
|
428
|
-
Effect.flatMap((service) =>
|
|
429
|
-
service.archiveTask("modified-residual", root),
|
|
430
|
-
),
|
|
431
|
-
),
|
|
432
|
-
),
|
|
433
|
-
).rejects.toThrow("is not registered as a jj workspace")
|
|
434
|
-
expect(
|
|
435
|
-
await Bun.file(join(workspace.writablePath!, "README.md")).exists(),
|
|
436
|
-
).toBe(true)
|
|
437
|
-
})
|
|
438
|
-
|
|
439
268
|
test("archives a phase in the mirrored task hierarchy", async () => {
|
|
440
269
|
await runTestEffect(
|
|
441
270
|
TaskService.pipe(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Schema, TreeFormatter } from "@effect/schema"
|
|
2
2
|
import { Data, Effect, Either, Layer } from "effect"
|
|
3
3
|
import { lstat, mkdir, open, rename, rm } from "node:fs/promises"
|
|
4
|
-
import { dirname, join, relative } from "node:path"
|
|
4
|
+
import { dirname, join, relative, resolve, sep } from "node:path"
|
|
5
5
|
import { EpicService, type EpicRecord } from "./EpicService"
|
|
6
6
|
import { FileSystemService } from "./FileSystemService"
|
|
7
7
|
import { PhaseService, type PhaseRecord } from "./PhaseService"
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
} from "./WorktreeService"
|
|
14
14
|
import {
|
|
15
15
|
GitVersionControlService,
|
|
16
|
-
JjVersionControlService,
|
|
17
16
|
VersionControlService,
|
|
18
17
|
} from "./VersionControlService"
|
|
19
18
|
import {
|
|
@@ -53,6 +52,11 @@ class ArchiveError extends Data.TaggedError("ArchiveError")<{
|
|
|
53
52
|
|
|
54
53
|
export type ArchiveKind = "epic" | "task" | "phase"
|
|
55
54
|
|
|
55
|
+
interface ArchivePathTarget {
|
|
56
|
+
readonly kind: "epic" | "task"
|
|
57
|
+
readonly id: string
|
|
58
|
+
}
|
|
59
|
+
|
|
56
60
|
const LifecycleEventSchema = Schema.Struct({
|
|
57
61
|
operation: Schema.Literal("archive", "restore"),
|
|
58
62
|
at: Schema.String,
|
|
@@ -368,7 +372,6 @@ const WorktreeLayer = Layer.mergeAll(
|
|
|
368
372
|
FileSystemService.Default,
|
|
369
373
|
WorkbaseService.Default,
|
|
370
374
|
GitVersionControlService.Default,
|
|
371
|
-
JjVersionControlService.Default,
|
|
372
375
|
VersionControlService.Default,
|
|
373
376
|
TaskService.Default,
|
|
374
377
|
PhaseService.Default,
|
|
@@ -400,22 +403,6 @@ const restoreWorktreeSnapshots = async (
|
|
|
400
403
|
continue
|
|
401
404
|
} catch {}
|
|
402
405
|
await mkdir(dirname(snapshot.path), { recursive: true })
|
|
403
|
-
if (snapshot.vcs === "jj") {
|
|
404
|
-
await runGit([
|
|
405
|
-
"jj",
|
|
406
|
-
"-R",
|
|
407
|
-
snapshot.repositoryPath,
|
|
408
|
-
"workspace",
|
|
409
|
-
"add",
|
|
410
|
-
"--name",
|
|
411
|
-
snapshot.workspaceName!,
|
|
412
|
-
"-r",
|
|
413
|
-
snapshot.head,
|
|
414
|
-
snapshot.path,
|
|
415
|
-
])
|
|
416
|
-
await runGit(["jj", "-R", snapshot.path, "edit", snapshot.head])
|
|
417
|
-
continue
|
|
418
|
-
}
|
|
419
406
|
await runGit(
|
|
420
407
|
snapshot.branch
|
|
421
408
|
? [
|
|
@@ -496,6 +483,59 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
496
483
|
"ArchiveService",
|
|
497
484
|
{
|
|
498
485
|
sync: () => ({
|
|
486
|
+
resolvePathTarget: (path: string, cwd: string = process.cwd()) =>
|
|
487
|
+
Effect.gen(function* () {
|
|
488
|
+
const fs = yield* FileSystemService
|
|
489
|
+
const workbase = yield* WorkbaseService
|
|
490
|
+
const candidate = resolve(cwd, path)
|
|
491
|
+
if (!(yield* fs.exists(candidate))) {
|
|
492
|
+
return yield* new ArchiveError({
|
|
493
|
+
message: `Archive path does not exist: ${candidate}`,
|
|
494
|
+
})
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const canonicalPath = yield* fs.realPath(candidate)
|
|
498
|
+
const root = yield* workbase.discover(canonicalPath)
|
|
499
|
+
const child = relative(root, canonicalPath)
|
|
500
|
+
const parts = child.split(sep)
|
|
501
|
+
if (child === "" || parts.length === 1) {
|
|
502
|
+
return yield* new ArchiveError({
|
|
503
|
+
message: `Archive path is ambiguous; it does not identify a single epic or task: ${canonicalPath}`,
|
|
504
|
+
})
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const [collection, id] = parts
|
|
508
|
+
const kind =
|
|
509
|
+
collection === "epics"
|
|
510
|
+
? "epic"
|
|
511
|
+
: collection === "tasks"
|
|
512
|
+
? "task"
|
|
513
|
+
: undefined
|
|
514
|
+
if (!kind || !id) {
|
|
515
|
+
return yield* new ArchiveError({
|
|
516
|
+
message: `Archive path must be within an active epic or task: ${canonicalPath}`,
|
|
517
|
+
})
|
|
518
|
+
}
|
|
519
|
+
if (kind === "task" && parts[2] === "phases") {
|
|
520
|
+
return yield* new ArchiveError({
|
|
521
|
+
message: `Archive path identifies a phase; use 'agency archive phase <task-id> <phase-id>': ${canonicalPath}`,
|
|
522
|
+
})
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const document = join(
|
|
526
|
+
root,
|
|
527
|
+
kind === "epic" ? "epics" : "tasks",
|
|
528
|
+
id,
|
|
529
|
+
kind === "epic" ? "EPIC.md" : "TASK.md",
|
|
530
|
+
)
|
|
531
|
+
if (!(yield* fs.exists(document))) {
|
|
532
|
+
return yield* new ArchiveError({
|
|
533
|
+
message: `Archive path does not identify an active ${kind}: ${canonicalPath}`,
|
|
534
|
+
})
|
|
535
|
+
}
|
|
536
|
+
return { kind, id } satisfies ArchivePathTarget
|
|
537
|
+
}),
|
|
538
|
+
|
|
499
539
|
list: (filters: ArchiveFilters = {}, startPath: string = process.cwd()) =>
|
|
500
540
|
Effect.gen(function* () {
|
|
501
541
|
const fs = yield* FileSystemService
|
|
@@ -724,7 +764,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
724
764
|
removedWorktrees.push(
|
|
725
765
|
...(yield* worktrees.remove(unit.taskId, unit.phaseId, root, {
|
|
726
766
|
dryRun: true,
|
|
727
|
-
persistResume: false,
|
|
728
767
|
})),
|
|
729
768
|
)
|
|
730
769
|
}
|
|
@@ -783,7 +822,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
783
822
|
worktrees.remove(unit.taskId, unit.phaseId, root, {
|
|
784
823
|
snapshots,
|
|
785
824
|
lockHeld: true,
|
|
786
|
-
persistResume: false,
|
|
787
825
|
}),
|
|
788
826
|
)
|
|
789
827
|
} catch (cause) {
|
|
@@ -897,7 +935,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
897
935
|
const result = yield* worktrees
|
|
898
936
|
.remove(unit.taskId, executionPhaseId(unit), root, {
|
|
899
937
|
dryRun: true,
|
|
900
|
-
persistResume: false,
|
|
901
938
|
task: context.task,
|
|
902
939
|
phase: unit.phaseId
|
|
903
940
|
? context.phases.find((phase) => phase.id === unit.phaseId)
|
|
@@ -1075,7 +1112,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
1075
1112
|
worktrees.remove(unit.taskId, unit.phaseId, root, {
|
|
1076
1113
|
snapshots,
|
|
1077
1114
|
lockHeld: true,
|
|
1078
|
-
persistResume: false,
|
|
1079
1115
|
task: contexts.get(unit.taskId)!.task,
|
|
1080
1116
|
phase: unit.phaseId
|
|
1081
1117
|
? contexts
|
|
@@ -1232,7 +1268,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
1232
1268
|
root,
|
|
1233
1269
|
{
|
|
1234
1270
|
dryRun: true,
|
|
1235
|
-
persistResume: false,
|
|
1236
1271
|
},
|
|
1237
1272
|
)),
|
|
1238
1273
|
)
|
|
@@ -1288,7 +1323,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
1288
1323
|
{
|
|
1289
1324
|
snapshots,
|
|
1290
1325
|
lockHeld: true,
|
|
1291
|
-
persistResume: false,
|
|
1292
1326
|
},
|
|
1293
1327
|
),
|
|
1294
1328
|
)
|
|
@@ -1381,7 +1415,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
1381
1415
|
yield* rejectExistingDestination(destination, "Archive")
|
|
1382
1416
|
const removedWorktrees = yield* worktrees.remove(taskId, id, root, {
|
|
1383
1417
|
dryRun: true,
|
|
1384
|
-
persistResume: false,
|
|
1385
1418
|
})
|
|
1386
1419
|
const at = new Date().toISOString()
|
|
1387
1420
|
const content = yield* declarationContent(task, {
|
|
@@ -1430,7 +1463,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
1430
1463
|
worktrees.remove(taskId, id, root, {
|
|
1431
1464
|
snapshots,
|
|
1432
1465
|
lockHeld: true,
|
|
1433
|
-
persistResume: false,
|
|
1434
1466
|
}),
|
|
1435
1467
|
)
|
|
1436
1468
|
},
|
|
@@ -350,7 +350,7 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
350
350
|
root,
|
|
351
351
|
configPath: join(root, "agency.json"),
|
|
352
352
|
version: config.version,
|
|
353
|
-
vcs:
|
|
353
|
+
vcs: "git",
|
|
354
354
|
},
|
|
355
355
|
target: { kind: "workbase", path: root },
|
|
356
356
|
hint: compact
|
|
@@ -1025,51 +1025,9 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
1025
1025
|
const inspectCheckout = (
|
|
1026
1026
|
repositoryPath: string,
|
|
1027
1027
|
checkoutPath: string,
|
|
1028
|
-
expectedBranch: string | null = null,
|
|
1029
|
-
branchCommit: string | null = null,
|
|
1030
1028
|
) =>
|
|
1031
1029
|
Effect.gen(function* (): Generator<any, CheckoutInspection, any> {
|
|
1032
1030
|
const materialized = yield* fs.isDirectory(checkoutPath)
|
|
1033
|
-
if (backend.kind === "jj") {
|
|
1034
|
-
const listed = yield* Effect.either(
|
|
1035
|
-
backend.listWorkspaces(repositoryPath),
|
|
1036
|
-
)
|
|
1037
|
-
const workspaces = Either.isRight(listed) ? listed.right : []
|
|
1038
|
-
if (Either.isLeft(listed)) {
|
|
1039
|
-
inspectionWarnings.push(
|
|
1040
|
-
`Unable to inspect jj workspace registrations for ${repositoryPath}`,
|
|
1041
|
-
)
|
|
1042
|
-
}
|
|
1043
|
-
const canonicalCheckoutPath = materialized
|
|
1044
|
-
? yield* fs.realPath(checkoutPath)
|
|
1045
|
-
: resolve(checkoutPath)
|
|
1046
|
-
const workspace = workspaces.find(
|
|
1047
|
-
(workspace) => workspace.path === canonicalCheckoutPath,
|
|
1048
|
-
)
|
|
1049
|
-
const registered = workspace !== undefined
|
|
1050
|
-
if (!materialized) {
|
|
1051
|
-
return {
|
|
1052
|
-
materialized: false,
|
|
1053
|
-
registered,
|
|
1054
|
-
checkoutCommit: null,
|
|
1055
|
-
checkoutBranch: null,
|
|
1056
|
-
detached: null,
|
|
1057
|
-
dirty: null,
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
return {
|
|
1061
|
-
materialized: true,
|
|
1062
|
-
registered,
|
|
1063
|
-
checkoutCommit: workspace?.commit ?? null,
|
|
1064
|
-
checkoutBranch:
|
|
1065
|
-
expectedBranch && branchCommit === workspace?.commit
|
|
1066
|
-
? expectedBranch
|
|
1067
|
-
: null,
|
|
1068
|
-
detached:
|
|
1069
|
-
!expectedBranch || branchCommit !== workspace?.commit,
|
|
1070
|
-
dirty: workspace ? false : null,
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
1031
|
const listed = yield* runGit(fs, repositoryPath, [
|
|
1074
1032
|
"worktree",
|
|
1075
1033
|
"list",
|
|
@@ -1136,7 +1094,7 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
1136
1094
|
const repositoryPath =
|
|
1137
1095
|
repository?.path ?? join(root, "repos", executionData.repo)
|
|
1138
1096
|
const checkoutPath = join(codePath, executionData.repo)
|
|
1139
|
-
|
|
1097
|
+
const branchCommit = yield* backend.resolveRevision(
|
|
1140
1098
|
repositoryPath,
|
|
1141
1099
|
executionData.branch,
|
|
1142
1100
|
)
|
|
@@ -1147,8 +1105,6 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
1147
1105
|
const checkout = yield* inspectCheckout(
|
|
1148
1106
|
repositoryPath,
|
|
1149
1107
|
checkoutPath,
|
|
1150
|
-
executionData.branch,
|
|
1151
|
-
branchCommit,
|
|
1152
1108
|
)
|
|
1153
1109
|
if (branchCommit === null) {
|
|
1154
1110
|
inspectionWarnings.push(
|
|
@@ -1261,7 +1217,7 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
1261
1217
|
root,
|
|
1262
1218
|
configPath: join(root, "agency.json"),
|
|
1263
1219
|
version: config.version,
|
|
1264
|
-
vcs:
|
|
1220
|
+
vcs: "git",
|
|
1265
1221
|
},
|
|
1266
1222
|
target,
|
|
1267
1223
|
documents: {
|
|
@@ -125,20 +125,12 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
125
125
|
return available
|
|
126
126
|
})
|
|
127
127
|
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
"jj",
|
|
134
|
-
config.vcs === "jj" ? "error" : "optional",
|
|
135
|
-
"Jujutsu",
|
|
136
|
-
),
|
|
137
|
-
],
|
|
138
|
-
{ concurrency: "unbounded", batching: true },
|
|
128
|
+
const versionControlAvailable = yield* tool(
|
|
129
|
+
"tool.git",
|
|
130
|
+
"git",
|
|
131
|
+
"error",
|
|
132
|
+
"Git",
|
|
139
133
|
)
|
|
140
|
-
const versionControlAvailable =
|
|
141
|
-
gitAvailable && (config.vcs !== "jj" || jjAvailable)
|
|
142
134
|
yield* Effect.all(
|
|
143
135
|
[
|
|
144
136
|
tool(
|
|
@@ -180,15 +172,6 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
180
172
|
] as const,
|
|
181
173
|
]
|
|
182
174
|
: []),
|
|
183
|
-
...(config.workspaceCreateCommand
|
|
184
|
-
? [
|
|
185
|
-
[
|
|
186
|
-
"integration.workspace-create",
|
|
187
|
-
config.workspaceCreateCommand,
|
|
188
|
-
"Workspace creator",
|
|
189
|
-
] as const,
|
|
190
|
-
]
|
|
191
|
-
: []),
|
|
192
175
|
...Object.entries(config.repositories ?? {}).flatMap(
|
|
193
176
|
([alias, repository]) =>
|
|
194
177
|
repository.postCheckoutCommand
|
|
@@ -384,8 +367,9 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
384
367
|
category: "repository",
|
|
385
368
|
level: "warning",
|
|
386
369
|
status: "fail",
|
|
387
|
-
message:
|
|
388
|
-
|
|
370
|
+
message:
|
|
371
|
+
"Repository, ref, remote, and workspace checks were skipped because Git is unavailable",
|
|
372
|
+
remediation: "Install 'git' and rerun 'agency doctor'.",
|
|
389
373
|
})
|
|
390
374
|
}
|
|
391
375
|
for (const repository of repositoryList) {
|
|
@@ -121,6 +121,7 @@ describe("GraphService", () => {
|
|
|
121
121
|
const graph = await getGraph(root)
|
|
122
122
|
|
|
123
123
|
expect(Schema.decodeUnknownSync(AgencyGraph)(graph)).toEqual(graph)
|
|
124
|
+
expect(graph.workbase.vcs).toBe("git")
|
|
124
125
|
expect(graph.nodes.map((node) => node.id)).toEqual([
|
|
125
126
|
"epic:delivery",
|
|
126
127
|
"execution-unit:phase/ship/implement",
|
|
@@ -821,10 +821,7 @@ export class GraphService extends Effect.Service<GraphService>()(
|
|
|
821
821
|
checkoutCommit: materialized
|
|
822
822
|
? yield* backend.workspaceHead(checkoutPath)
|
|
823
823
|
: null,
|
|
824
|
-
checkoutBranch:
|
|
825
|
-
backend.kind === "jj" && materialized
|
|
826
|
-
? data.branch
|
|
827
|
-
: actualBranch,
|
|
824
|
+
checkoutBranch: actualBranch,
|
|
828
825
|
dirty: materialized
|
|
829
826
|
? yield* backend.workspaceDirty(checkoutPath)
|
|
830
827
|
: null,
|
|
@@ -1022,7 +1019,7 @@ export class GraphService extends Effect.Service<GraphService>()(
|
|
|
1022
1019
|
version: GRAPH_VERSION,
|
|
1023
1020
|
workbase: {
|
|
1024
1021
|
version: config.version,
|
|
1025
|
-
vcs:
|
|
1022
|
+
vcs: "git",
|
|
1026
1023
|
...(include.has("workspace") ? { root } : {}),
|
|
1027
1024
|
},
|
|
1028
1025
|
filters,
|