@markjaquith/agency 2.73.2 → 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 +35 -134
- 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 +11 -71
- package/src/commands/context.ts +7 -7
- 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 +7 -50
- 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/IntegrationService.test.ts +4 -0
- 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 +7 -7
- 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.test.ts
CHANGED
|
@@ -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, realpath
|
|
3
|
+
import { chmod, mkdir, realpath } from "node:fs/promises"
|
|
4
4
|
import { dirname, join } from "node:path"
|
|
5
5
|
import { PullRequestService } from "../services/PullRequestService"
|
|
6
6
|
import {
|
|
@@ -31,7 +31,6 @@ describe("pr command", () => {
|
|
|
31
31
|
join(bin, "gh"),
|
|
32
32
|
`#!/bin/sh
|
|
33
33
|
printf '%s\n' "$PWD" "$@" > "$GH_CAPTURE"
|
|
34
|
-
if [ -n "$GIT_DIR" ]; then printf 'GIT_DIR=%s\n' "$GIT_DIR" >> "$GH_CAPTURE"; fi
|
|
35
34
|
exit "\${GH_EXIT:-0}"
|
|
36
35
|
`,
|
|
37
36
|
)
|
|
@@ -51,14 +50,13 @@ exit "\${GH_EXIT:-0}"
|
|
|
51
50
|
const captured = async () =>
|
|
52
51
|
(await Bun.file(capturePath).text()).trim().split("\n")
|
|
53
52
|
|
|
54
|
-
const createExecutionWorkbase = async (
|
|
53
|
+
const createExecutionWorkbase = async () => {
|
|
55
54
|
const root = join(tempRoot, "workbase")
|
|
56
55
|
await write(
|
|
57
56
|
root,
|
|
58
57
|
"agency.json",
|
|
59
58
|
`${JSON.stringify({
|
|
60
59
|
version: 2,
|
|
61
|
-
vcs,
|
|
62
60
|
repositories: {
|
|
63
61
|
agency: { remote: "https://github.com/example/agency.git" },
|
|
64
62
|
},
|
|
@@ -110,41 +108,6 @@ status: open
|
|
|
110
108
|
# Build
|
|
111
109
|
`,
|
|
112
110
|
)
|
|
113
|
-
if (vcs === "jj") {
|
|
114
|
-
const repository = join(root, "repos/agency")
|
|
115
|
-
await rm(join(root, "tasks/single/code/agency"), { recursive: true })
|
|
116
|
-
await rm(join(root, "tasks/multi/phases/build/code/agency"), {
|
|
117
|
-
recursive: true,
|
|
118
|
-
})
|
|
119
|
-
const initialized = Bun.spawn(
|
|
120
|
-
["jj", "git", "init", "--no-colocate", repository],
|
|
121
|
-
{ stdout: "pipe", stderr: "pipe" },
|
|
122
|
-
)
|
|
123
|
-
if ((await initialized.exited) !== 0)
|
|
124
|
-
throw new Error(await new Response(initialized.stderr).text())
|
|
125
|
-
for (const [name, path] of [
|
|
126
|
-
["single", join(root, "tasks/single/code/agency")],
|
|
127
|
-
["build", join(root, "tasks/multi/phases/build/code/agency")],
|
|
128
|
-
] as const) {
|
|
129
|
-
const workspace = Bun.spawn(
|
|
130
|
-
[
|
|
131
|
-
"jj",
|
|
132
|
-
"-R",
|
|
133
|
-
repository,
|
|
134
|
-
"workspace",
|
|
135
|
-
"add",
|
|
136
|
-
"--name",
|
|
137
|
-
name,
|
|
138
|
-
"-r",
|
|
139
|
-
"root()",
|
|
140
|
-
path,
|
|
141
|
-
],
|
|
142
|
-
{ stdout: "pipe", stderr: "pipe" },
|
|
143
|
-
)
|
|
144
|
-
if ((await workspace.exited) !== 0)
|
|
145
|
-
throw new Error(await new Response(workspace.stderr).text())
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
111
|
return root
|
|
149
112
|
}
|
|
150
113
|
|
|
@@ -261,54 +224,6 @@ status: working
|
|
|
261
224
|
])
|
|
262
225
|
})
|
|
263
226
|
|
|
264
|
-
test("injects the execution branch and repository for jj targets", async () => {
|
|
265
|
-
const root = await createExecutionWorkbase("jj")
|
|
266
|
-
const task = join(root, "tasks/single")
|
|
267
|
-
const phase = join(root, "tasks/multi/phases/build")
|
|
268
|
-
|
|
269
|
-
expect(await runTestEffect(pr(["view", "--web"], task))).toBe(0)
|
|
270
|
-
expect(await captured()).toEqual([
|
|
271
|
-
await realpath(join(task, "code/agency")),
|
|
272
|
-
"pr",
|
|
273
|
-
"view",
|
|
274
|
-
"feat/single",
|
|
275
|
-
"--repo",
|
|
276
|
-
"example/agency",
|
|
277
|
-
"--web",
|
|
278
|
-
expect.stringMatching(/^GIT_DIR=.*\.jj/),
|
|
279
|
-
])
|
|
280
|
-
|
|
281
|
-
expect(
|
|
282
|
-
await runTestEffect(pr(["create", "--title", "Example"], phase)),
|
|
283
|
-
).toBe(0)
|
|
284
|
-
expect(await captured()).toEqual([
|
|
285
|
-
await realpath(join(phase, "code/agency")),
|
|
286
|
-
"pr",
|
|
287
|
-
"create",
|
|
288
|
-
"--head",
|
|
289
|
-
"feat/build",
|
|
290
|
-
"--repo",
|
|
291
|
-
"example/agency",
|
|
292
|
-
"--title",
|
|
293
|
-
"Example",
|
|
294
|
-
expect.stringMatching(/^GIT_DIR=.*\.jj/),
|
|
295
|
-
])
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
test("preserves explicit jj PR and repository targets", async () => {
|
|
299
|
-
const root = await createExecutionWorkbase("jj")
|
|
300
|
-
const task = join(root, "tasks/single")
|
|
301
|
-
const args = ["view", "123", "--repo", "other/repository"]
|
|
302
|
-
|
|
303
|
-
expect(await runTestEffect(pr(args, task))).toBe(0)
|
|
304
|
-
expect(await captured()).toEqual([
|
|
305
|
-
await realpath(join(task, "code/agency")),
|
|
306
|
-
"pr",
|
|
307
|
-
...args,
|
|
308
|
-
expect.stringMatching(/^GIT_DIR=.*\.jj/),
|
|
309
|
-
])
|
|
310
|
-
})
|
|
311
|
-
|
|
312
227
|
test("falls back to the invocation directory without execution authority", async () => {
|
|
313
228
|
const root = await createExecutionWorkbase()
|
|
314
229
|
const orchestration = join(root, "tasks/multi")
|
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(
|