@markjaquith/agency 2.74.0 → 3.0.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 +23 -121
- package/cli-main.ts +0 -26
- 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 +0 -19
- package/src/cli-parser.ts +0 -23
- package/src/cli.test.ts +3 -3
- 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 +0 -26
- 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
package/src/commands/pr.ts
CHANGED
|
@@ -3,11 +3,8 @@ import { resolve } from "node:path"
|
|
|
3
3
|
import { ContextService } from "../services/ContextService"
|
|
4
4
|
import { FileSystemService } from "../services/FileSystemService"
|
|
5
5
|
import { PullRequestService } from "../services/PullRequestService"
|
|
6
|
-
import { VersionControlService } from "../services/VersionControlService"
|
|
7
|
-
import { WorkbaseService } from "../services/WorkbaseService"
|
|
8
6
|
import type { BaseCommandOptions } from "../utils/command"
|
|
9
7
|
import { createLoggers } from "../utils/effect"
|
|
10
|
-
import { repositoryFromRemote } from "../workbase/delivery-command"
|
|
11
8
|
|
|
12
9
|
interface PrCreateOptions extends BaseCommandOptions {
|
|
13
10
|
readonly taskId: string
|
|
@@ -20,56 +17,6 @@ interface PrCreateOptions extends BaseCommandOptions {
|
|
|
20
17
|
readonly labels?: readonly string[]
|
|
21
18
|
}
|
|
22
19
|
|
|
23
|
-
const branchTargetCommands = new Set([
|
|
24
|
-
"checkout",
|
|
25
|
-
"checks",
|
|
26
|
-
"close",
|
|
27
|
-
"comment",
|
|
28
|
-
"diff",
|
|
29
|
-
"edit",
|
|
30
|
-
"lock",
|
|
31
|
-
"merge",
|
|
32
|
-
"ready",
|
|
33
|
-
"reopen",
|
|
34
|
-
"revert",
|
|
35
|
-
"review",
|
|
36
|
-
"unlock",
|
|
37
|
-
"update-branch",
|
|
38
|
-
"view",
|
|
39
|
-
])
|
|
40
|
-
|
|
41
|
-
const hasOption = (args: readonly string[], short: string, long: string) =>
|
|
42
|
-
args.some(
|
|
43
|
-
(argument) =>
|
|
44
|
-
argument === short ||
|
|
45
|
-
argument.startsWith(`${short}=`) ||
|
|
46
|
-
argument === long ||
|
|
47
|
-
argument.startsWith(`${long}=`),
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
const withJjContext = (
|
|
51
|
-
args: readonly string[],
|
|
52
|
-
branch: string,
|
|
53
|
-
repository: string,
|
|
54
|
-
) => {
|
|
55
|
-
const [command, ...rest] = args
|
|
56
|
-
if (!command) return args
|
|
57
|
-
|
|
58
|
-
const branchArgs =
|
|
59
|
-
(command === "create" || command === "new") &&
|
|
60
|
-
!hasOption(rest, "-H", "--head")
|
|
61
|
-
? ["--head", branch]
|
|
62
|
-
: branchTargetCommands.has(command) &&
|
|
63
|
-
(rest.length === 0 || rest[0]?.startsWith("-"))
|
|
64
|
-
? [branch]
|
|
65
|
-
: []
|
|
66
|
-
const repositoryArgs = hasOption(rest, "-R", "--repo")
|
|
67
|
-
? []
|
|
68
|
-
: ["--repo", repository]
|
|
69
|
-
|
|
70
|
-
return [command, ...branchArgs, ...repositoryArgs, ...rest]
|
|
71
|
-
}
|
|
72
|
-
|
|
73
20
|
export const prCreate = (options: PrCreateOptions) =>
|
|
74
21
|
Effect.gen(function* () {
|
|
75
22
|
const pullRequests = yield* PullRequestService
|
|
@@ -88,8 +35,6 @@ export const pr = (args: readonly string[], cwd: string = process.cwd()) =>
|
|
|
88
35
|
Effect.gen(function* () {
|
|
89
36
|
const contexts = yield* ContextService
|
|
90
37
|
const fs = yield* FileSystemService
|
|
91
|
-
const workbase = yield* WorkbaseService
|
|
92
|
-
const versionControl = yield* VersionControlService
|
|
93
38
|
const invocationCwd = resolve(cwd)
|
|
94
39
|
const context = yield* contexts
|
|
95
40
|
.get({ cwd: invocationCwd, target: ".", compact: true })
|
|
@@ -104,30 +49,9 @@ export const pr = (args: readonly string[], cwd: string = process.cwd()) =>
|
|
|
104
49
|
: null
|
|
105
50
|
const focusedCheckout = writableCheckout ?? reviewCheckout
|
|
106
51
|
const focusedCwd = focusedCheckout ?? invocationCwd
|
|
107
|
-
|
|
108
|
-
let environment: Record<string, string> = {}
|
|
109
|
-
if (focusedCheckout && context?.workbase.vcs === "jj") {
|
|
110
|
-
if (writableCheckout && context.authority.writable) {
|
|
111
|
-
const execution =
|
|
112
|
-
context.documents?.phase?.data ?? context.documents?.task?.data
|
|
113
|
-
const { config } = yield* workbase.loadConfig(context.workbase.root)
|
|
114
|
-
const remote =
|
|
115
|
-
config.repositories?.[context.authority.writable.repo]?.remote
|
|
116
|
-
if (execution && "branch" in execution && remote) {
|
|
117
|
-
forwardedArgs = withJjContext(
|
|
118
|
-
args,
|
|
119
|
-
execution.branch,
|
|
120
|
-
repositoryFromRemote(remote),
|
|
121
|
-
)
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
const backend = yield* versionControl.forWorkbase(context.workbase.root)
|
|
125
|
-
environment = yield* backend.gitEnvironment(focusedCheckout)
|
|
126
|
-
}
|
|
127
|
-
const result = yield* fs.runCommand(["gh", "pr", ...forwardedArgs], {
|
|
52
|
+
const result = yield* fs.runCommand(["gh", "pr", ...args], {
|
|
128
53
|
cwd: focusedCwd,
|
|
129
54
|
passthrough: true,
|
|
130
|
-
env: environment,
|
|
131
55
|
})
|
|
132
56
|
return result.exitCode
|
|
133
57
|
})
|
|
@@ -139,8 +63,7 @@ Usage: agency pr create <task-id> [phase-id] [options]
|
|
|
139
63
|
Create records a pull request for an Agency execution unit and accepts the
|
|
140
64
|
options listed below. Invocations without an Agency task target run gh pr
|
|
141
65
|
unchanged, focusing the writable repository checkout when invoked from
|
|
142
|
-
an Agency execution task or phase.
|
|
143
|
-
branch and repository to gh subcommands that would otherwise infer Git context.
|
|
66
|
+
an Agency execution task or phase.
|
|
144
67
|
|
|
145
68
|
Options:
|
|
146
69
|
--draft Create the pull request as a draft
|
|
@@ -14,13 +14,12 @@ describe("push command", () => {
|
|
|
14
14
|
fail: (message) => updates.push(`fail:${message}`),
|
|
15
15
|
}
|
|
16
16
|
const result = {
|
|
17
|
-
vcs: "
|
|
17
|
+
vcs: "git" as const,
|
|
18
18
|
taskId: "example",
|
|
19
19
|
branch: "task/example",
|
|
20
20
|
base: "main",
|
|
21
21
|
remote: "origin",
|
|
22
22
|
tip: "abc123",
|
|
23
|
-
changeId: "change",
|
|
24
23
|
}
|
|
25
24
|
const logs = await captureLogs(() =>
|
|
26
25
|
Effect.runPromise(
|
package/src/graph-schema.ts
CHANGED
|
@@ -57,7 +57,7 @@ export const GraphReadiness = Schema.Struct({
|
|
|
57
57
|
|
|
58
58
|
export const GraphWorkbase = Schema.Struct({
|
|
59
59
|
version: Schema.Literal(2),
|
|
60
|
-
vcs: Schema.optional(Schema.Literal("git"
|
|
60
|
+
vcs: Schema.optional(Schema.Literal("git")),
|
|
61
61
|
root: Schema.optional(Schema.String),
|
|
62
62
|
})
|
|
63
63
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
2
|
import { Effect } from "effect"
|
|
3
|
-
import { chmod, mkdir
|
|
3
|
+
import { chmod, 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"
|
|
@@ -17,13 +17,6 @@ const git = (args: readonly string[], cwd?: string) => {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const jj = (args: readonly string[], cwd?: string) => {
|
|
21
|
-
const result = Bun.spawnSync(["jj", ...args], { cwd })
|
|
22
|
-
if (result.exitCode !== 0) {
|
|
23
|
-
throw new Error(new TextDecoder().decode(result.stderr))
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
20
|
describe("ArchiveService bulk task archive", () => {
|
|
28
21
|
let root: string
|
|
29
22
|
let source: string
|
|
@@ -430,140 +423,6 @@ claim:
|
|
|
430
423
|
])
|
|
431
424
|
})
|
|
432
425
|
|
|
433
|
-
test("skips a dirty jj workspace", async () => {
|
|
434
|
-
if (!Bun.which("jj")) return
|
|
435
|
-
const repository = join(root, "repos/agency")
|
|
436
|
-
await rm(repository, { recursive: true, force: true })
|
|
437
|
-
git(["clone", source, repository])
|
|
438
|
-
const initialized = Bun.spawnSync([
|
|
439
|
-
"jj",
|
|
440
|
-
"git",
|
|
441
|
-
"init",
|
|
442
|
-
"--colocate",
|
|
443
|
-
repository,
|
|
444
|
-
])
|
|
445
|
-
if (initialized.exitCode !== 0) {
|
|
446
|
-
throw new Error(new TextDecoder().decode(initialized.stderr))
|
|
447
|
-
}
|
|
448
|
-
await Bun.write(
|
|
449
|
-
join(root, "agency.json"),
|
|
450
|
-
JSON.stringify({ version: 2, vcs: "jj" }),
|
|
451
|
-
)
|
|
452
|
-
await createTask("jj-dirty")
|
|
453
|
-
await dropTask("jj-dirty")
|
|
454
|
-
const workspace = await runTestEffect(
|
|
455
|
-
WorktreeService.pipe(
|
|
456
|
-
Effect.flatMap((service) =>
|
|
457
|
-
service.materialize("jj-dirty", undefined, root),
|
|
458
|
-
),
|
|
459
|
-
),
|
|
460
|
-
)
|
|
461
|
-
await Bun.write(join(workspace.writablePath!, "dirty.txt"), "keep\n")
|
|
462
|
-
|
|
463
|
-
const result = await archiveTasks()
|
|
464
|
-
|
|
465
|
-
expect(result.tasks[0]).toMatchObject({
|
|
466
|
-
id: "jj-dirty",
|
|
467
|
-
disposition: "skipped",
|
|
468
|
-
reason: { code: "dirty-worktree" },
|
|
469
|
-
})
|
|
470
|
-
expect(
|
|
471
|
-
await Bun.file(join(workspace.writablePath!, "dirty.txt")).exists(),
|
|
472
|
-
).toBe(true)
|
|
473
|
-
})
|
|
474
|
-
|
|
475
|
-
test("archives a jj working-copy commit preserved by its task bookmark", async () => {
|
|
476
|
-
if (!Bun.which("jj")) return
|
|
477
|
-
const repository = join(root, "repos/agency")
|
|
478
|
-
await rm(repository, { recursive: true, force: true })
|
|
479
|
-
git(["clone", source, repository])
|
|
480
|
-
jj(["git", "init", "--colocate", repository])
|
|
481
|
-
await Bun.write(
|
|
482
|
-
join(root, "agency.json"),
|
|
483
|
-
JSON.stringify({ version: 2, vcs: "jj" }),
|
|
484
|
-
)
|
|
485
|
-
await createTask("jj-bookmarked")
|
|
486
|
-
await dropTask("jj-bookmarked")
|
|
487
|
-
const workspace = await runTestEffect(
|
|
488
|
-
WorktreeService.pipe(
|
|
489
|
-
Effect.flatMap((service) =>
|
|
490
|
-
service.materialize("jj-bookmarked", undefined, root),
|
|
491
|
-
),
|
|
492
|
-
),
|
|
493
|
-
)
|
|
494
|
-
await Bun.write(join(workspace.writablePath!, "preserved.txt"), "keep\n")
|
|
495
|
-
jj(
|
|
496
|
-
["bookmark", "set", "task/jj-bookmarked", "-r", "@"],
|
|
497
|
-
workspace.writablePath!,
|
|
498
|
-
)
|
|
499
|
-
|
|
500
|
-
const preview = await archiveTasks(true)
|
|
501
|
-
expect(preview.tasks[0]).toMatchObject({
|
|
502
|
-
id: "jj-bookmarked",
|
|
503
|
-
disposition: "planned",
|
|
504
|
-
removedWorktrees: [workspace.writablePath!],
|
|
505
|
-
})
|
|
506
|
-
expect(
|
|
507
|
-
await Bun.file(join(workspace.writablePath!, "preserved.txt")).text(),
|
|
508
|
-
).toBe("keep\n")
|
|
509
|
-
|
|
510
|
-
const result = await archiveTasks()
|
|
511
|
-
expect(result.tasks[0]).toMatchObject({
|
|
512
|
-
id: "jj-bookmarked",
|
|
513
|
-
disposition: "archived",
|
|
514
|
-
})
|
|
515
|
-
expect(await Bun.file(workspace.writablePath!).exists()).toBe(false)
|
|
516
|
-
const preserved = Bun.spawnSync([
|
|
517
|
-
"jj",
|
|
518
|
-
"-R",
|
|
519
|
-
repository,
|
|
520
|
-
"file",
|
|
521
|
-
"show",
|
|
522
|
-
"-r",
|
|
523
|
-
"task/jj-bookmarked",
|
|
524
|
-
'root:"preserved.txt"',
|
|
525
|
-
])
|
|
526
|
-
if (preserved.exitCode !== 0) {
|
|
527
|
-
throw new Error(preserved.stderr.toString())
|
|
528
|
-
}
|
|
529
|
-
expect(preserved.stdout.toString()).toBe("keep\n")
|
|
530
|
-
})
|
|
531
|
-
|
|
532
|
-
test("archives a forgotten jj workspace preserved by its task bookmark", async () => {
|
|
533
|
-
if (!Bun.which("jj")) return
|
|
534
|
-
const repository = join(root, "repos/agency")
|
|
535
|
-
await rm(repository, { recursive: true, force: true })
|
|
536
|
-
git(["clone", source, repository])
|
|
537
|
-
jj(["git", "init", "--colocate", repository])
|
|
538
|
-
await Bun.write(
|
|
539
|
-
join(root, "agency.json"),
|
|
540
|
-
JSON.stringify({ version: 2, vcs: "jj" }),
|
|
541
|
-
)
|
|
542
|
-
await createTask("jj-stale")
|
|
543
|
-
await dropTask("jj-stale")
|
|
544
|
-
const workspace = await runTestEffect(
|
|
545
|
-
WorktreeService.pipe(
|
|
546
|
-
Effect.flatMap((service) =>
|
|
547
|
-
service.materialize("jj-stale", undefined, root),
|
|
548
|
-
),
|
|
549
|
-
),
|
|
550
|
-
)
|
|
551
|
-
await Bun.write(join(workspace.writablePath!, "preserved.txt"), "keep\n")
|
|
552
|
-
jj(["bookmark", "set", "task/jj-stale", "-r", "@"], workspace.writablePath!)
|
|
553
|
-
jj(["-R", repository, "workspace", "forget", "agency-jj-stale-task-agency"])
|
|
554
|
-
|
|
555
|
-
const result = await archiveTasks(true)
|
|
556
|
-
|
|
557
|
-
expect(result.tasks[0]).toMatchObject({
|
|
558
|
-
id: "jj-stale",
|
|
559
|
-
disposition: "planned",
|
|
560
|
-
removedWorktrees: [workspace.writablePath!],
|
|
561
|
-
})
|
|
562
|
-
expect(
|
|
563
|
-
await Bun.file(join(workspace.writablePath!, "preserved.txt")).text(),
|
|
564
|
-
).toBe("keep\n")
|
|
565
|
-
})
|
|
566
|
-
|
|
567
426
|
test("rolls back the entire cohort when application fails", async () => {
|
|
568
427
|
await runTestEffect(
|
|
569
428
|
EpicService.pipe(
|
|
@@ -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(
|
|
@@ -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 {
|
|
@@ -368,7 +367,6 @@ const WorktreeLayer = Layer.mergeAll(
|
|
|
368
367
|
FileSystemService.Default,
|
|
369
368
|
WorkbaseService.Default,
|
|
370
369
|
GitVersionControlService.Default,
|
|
371
|
-
JjVersionControlService.Default,
|
|
372
370
|
VersionControlService.Default,
|
|
373
371
|
TaskService.Default,
|
|
374
372
|
PhaseService.Default,
|
|
@@ -400,22 +398,6 @@ const restoreWorktreeSnapshots = async (
|
|
|
400
398
|
continue
|
|
401
399
|
} catch {}
|
|
402
400
|
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
401
|
await runGit(
|
|
420
402
|
snapshot.branch
|
|
421
403
|
? [
|
|
@@ -724,7 +706,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
724
706
|
removedWorktrees.push(
|
|
725
707
|
...(yield* worktrees.remove(unit.taskId, unit.phaseId, root, {
|
|
726
708
|
dryRun: true,
|
|
727
|
-
persistResume: false,
|
|
728
709
|
})),
|
|
729
710
|
)
|
|
730
711
|
}
|
|
@@ -783,7 +764,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
783
764
|
worktrees.remove(unit.taskId, unit.phaseId, root, {
|
|
784
765
|
snapshots,
|
|
785
766
|
lockHeld: true,
|
|
786
|
-
persistResume: false,
|
|
787
767
|
}),
|
|
788
768
|
)
|
|
789
769
|
} catch (cause) {
|
|
@@ -897,7 +877,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
897
877
|
const result = yield* worktrees
|
|
898
878
|
.remove(unit.taskId, executionPhaseId(unit), root, {
|
|
899
879
|
dryRun: true,
|
|
900
|
-
persistResume: false,
|
|
901
880
|
task: context.task,
|
|
902
881
|
phase: unit.phaseId
|
|
903
882
|
? context.phases.find((phase) => phase.id === unit.phaseId)
|
|
@@ -1075,7 +1054,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
1075
1054
|
worktrees.remove(unit.taskId, unit.phaseId, root, {
|
|
1076
1055
|
snapshots,
|
|
1077
1056
|
lockHeld: true,
|
|
1078
|
-
persistResume: false,
|
|
1079
1057
|
task: contexts.get(unit.taskId)!.task,
|
|
1080
1058
|
phase: unit.phaseId
|
|
1081
1059
|
? contexts
|
|
@@ -1232,7 +1210,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
1232
1210
|
root,
|
|
1233
1211
|
{
|
|
1234
1212
|
dryRun: true,
|
|
1235
|
-
persistResume: false,
|
|
1236
1213
|
},
|
|
1237
1214
|
)),
|
|
1238
1215
|
)
|
|
@@ -1288,7 +1265,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
1288
1265
|
{
|
|
1289
1266
|
snapshots,
|
|
1290
1267
|
lockHeld: true,
|
|
1291
|
-
persistResume: false,
|
|
1292
1268
|
},
|
|
1293
1269
|
),
|
|
1294
1270
|
)
|
|
@@ -1381,7 +1357,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
1381
1357
|
yield* rejectExistingDestination(destination, "Archive")
|
|
1382
1358
|
const removedWorktrees = yield* worktrees.remove(taskId, id, root, {
|
|
1383
1359
|
dryRun: true,
|
|
1384
|
-
persistResume: false,
|
|
1385
1360
|
})
|
|
1386
1361
|
const at = new Date().toISOString()
|
|
1387
1362
|
const content = yield* declarationContent(task, {
|
|
@@ -1430,7 +1405,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
1430
1405
|
worktrees.remove(taskId, id, root, {
|
|
1431
1406
|
snapshots,
|
|
1432
1407
|
lockHeld: true,
|
|
1433
|
-
persistResume: false,
|
|
1434
1408
|
}),
|
|
1435
1409
|
)
|
|
1436
1410
|
},
|
|
@@ -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: {
|