@markjaquith/agency 2.54.2 → 2.54.3
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
CHANGED
|
@@ -857,7 +857,8 @@ including readiness checks and durable PR recording. Other `agency pr`
|
|
|
857
857
|
invocations forward every argument to `gh pr`. From an execution task or phase
|
|
858
858
|
directory, including descendants, passthrough runs in that execution unit's
|
|
859
859
|
authoritative writable checkout. Otherwise it runs in the caller's current
|
|
860
|
-
directory.
|
|
860
|
+
directory. In jj workbases, Agency also supplies the repository and the work
|
|
861
|
+
item's declared branch to subcommands that would otherwise infer Git context.
|
|
861
862
|
|
|
862
863
|
### Status and Validation
|
|
863
864
|
|
package/package.json
CHANGED
package/src/commands/pr.test.ts
CHANGED
|
@@ -50,9 +50,19 @@ exit "\${GH_EXIT:-0}"
|
|
|
50
50
|
const captured = async () =>
|
|
51
51
|
(await Bun.file(capturePath).text()).trim().split("\n")
|
|
52
52
|
|
|
53
|
-
const createExecutionWorkbase = async () => {
|
|
53
|
+
const createExecutionWorkbase = async (vcs: "git" | "jj" = "git") => {
|
|
54
54
|
const root = join(tempRoot, "workbase")
|
|
55
|
-
await write(
|
|
55
|
+
await write(
|
|
56
|
+
root,
|
|
57
|
+
"agency.json",
|
|
58
|
+
`${JSON.stringify({
|
|
59
|
+
version: 2,
|
|
60
|
+
vcs,
|
|
61
|
+
repositories: {
|
|
62
|
+
agency: { remote: "https://github.com/example/agency.git" },
|
|
63
|
+
},
|
|
64
|
+
})}\n`,
|
|
65
|
+
)
|
|
56
66
|
await mkdir(join(root, "repos/agency"), { recursive: true })
|
|
57
67
|
await mkdir(join(root, "tasks/single/code/agency"), { recursive: true })
|
|
58
68
|
await write(
|
|
@@ -163,6 +173,51 @@ status: open
|
|
|
163
173
|
])
|
|
164
174
|
})
|
|
165
175
|
|
|
176
|
+
test("injects the execution branch and repository for jj targets", async () => {
|
|
177
|
+
const root = await createExecutionWorkbase("jj")
|
|
178
|
+
const task = join(root, "tasks/single")
|
|
179
|
+
const phase = join(root, "tasks/multi/phases/build")
|
|
180
|
+
|
|
181
|
+
expect(await runTestEffect(pr(["view", "--web"], task))).toBe(0)
|
|
182
|
+
expect(await captured()).toEqual([
|
|
183
|
+
await realpath(join(task, "code/agency")),
|
|
184
|
+
"pr",
|
|
185
|
+
"view",
|
|
186
|
+
"feat/single",
|
|
187
|
+
"--repo",
|
|
188
|
+
"example/agency",
|
|
189
|
+
"--web",
|
|
190
|
+
])
|
|
191
|
+
|
|
192
|
+
expect(
|
|
193
|
+
await runTestEffect(pr(["create", "--title", "Example"], phase)),
|
|
194
|
+
).toBe(0)
|
|
195
|
+
expect(await captured()).toEqual([
|
|
196
|
+
await realpath(join(phase, "code/agency")),
|
|
197
|
+
"pr",
|
|
198
|
+
"create",
|
|
199
|
+
"--head",
|
|
200
|
+
"feat/build",
|
|
201
|
+
"--repo",
|
|
202
|
+
"example/agency",
|
|
203
|
+
"--title",
|
|
204
|
+
"Example",
|
|
205
|
+
])
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
test("preserves explicit jj PR and repository targets", async () => {
|
|
209
|
+
const root = await createExecutionWorkbase("jj")
|
|
210
|
+
const task = join(root, "tasks/single")
|
|
211
|
+
const args = ["view", "123", "--repo", "other/repository"]
|
|
212
|
+
|
|
213
|
+
expect(await runTestEffect(pr(args, task))).toBe(0)
|
|
214
|
+
expect(await captured()).toEqual([
|
|
215
|
+
await realpath(join(task, "code/agency")),
|
|
216
|
+
"pr",
|
|
217
|
+
...args,
|
|
218
|
+
])
|
|
219
|
+
})
|
|
220
|
+
|
|
166
221
|
test("falls back to the invocation directory without execution authority", async () => {
|
|
167
222
|
const root = await createExecutionWorkbase()
|
|
168
223
|
const orchestration = join(root, "tasks/multi")
|
package/src/commands/pr.ts
CHANGED
|
@@ -3,8 +3,10 @@ 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 { WorkbaseService } from "../services/WorkbaseService"
|
|
6
7
|
import type { BaseCommandOptions } from "../utils/command"
|
|
7
8
|
import { createLoggers } from "../utils/effect"
|
|
9
|
+
import { repositoryFromRemote } from "../workbase/delivery-command"
|
|
8
10
|
|
|
9
11
|
interface PrCreateOptions extends BaseCommandOptions {
|
|
10
12
|
readonly taskId: string
|
|
@@ -13,6 +15,56 @@ interface PrCreateOptions extends BaseCommandOptions {
|
|
|
13
15
|
readonly force?: boolean
|
|
14
16
|
}
|
|
15
17
|
|
|
18
|
+
const branchTargetCommands = new Set([
|
|
19
|
+
"checkout",
|
|
20
|
+
"checks",
|
|
21
|
+
"close",
|
|
22
|
+
"comment",
|
|
23
|
+
"diff",
|
|
24
|
+
"edit",
|
|
25
|
+
"lock",
|
|
26
|
+
"merge",
|
|
27
|
+
"ready",
|
|
28
|
+
"reopen",
|
|
29
|
+
"revert",
|
|
30
|
+
"review",
|
|
31
|
+
"unlock",
|
|
32
|
+
"update-branch",
|
|
33
|
+
"view",
|
|
34
|
+
])
|
|
35
|
+
|
|
36
|
+
const hasOption = (args: readonly string[], short: string, long: string) =>
|
|
37
|
+
args.some(
|
|
38
|
+
(argument) =>
|
|
39
|
+
argument === short ||
|
|
40
|
+
argument.startsWith(`${short}=`) ||
|
|
41
|
+
argument === long ||
|
|
42
|
+
argument.startsWith(`${long}=`),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const withJjContext = (
|
|
46
|
+
args: readonly string[],
|
|
47
|
+
branch: string,
|
|
48
|
+
repository: string,
|
|
49
|
+
) => {
|
|
50
|
+
const [command, ...rest] = args
|
|
51
|
+
if (!command) return args
|
|
52
|
+
|
|
53
|
+
const branchArgs =
|
|
54
|
+
(command === "create" || command === "new") &&
|
|
55
|
+
!hasOption(rest, "-H", "--head")
|
|
56
|
+
? ["--head", branch]
|
|
57
|
+
: branchTargetCommands.has(command) &&
|
|
58
|
+
(rest.length === 0 || rest[0]?.startsWith("-"))
|
|
59
|
+
? [branch]
|
|
60
|
+
: []
|
|
61
|
+
const repositoryArgs = hasOption(rest, "-R", "--repo")
|
|
62
|
+
? []
|
|
63
|
+
: ["--repo", repository]
|
|
64
|
+
|
|
65
|
+
return [command, ...branchArgs, ...repositoryArgs, ...rest]
|
|
66
|
+
}
|
|
67
|
+
|
|
16
68
|
export const prCreate = (options: PrCreateOptions) =>
|
|
17
69
|
Effect.gen(function* () {
|
|
18
70
|
const pullRequests = yield* PullRequestService
|
|
@@ -31,6 +83,7 @@ export const pr = (args: readonly string[], cwd: string = process.cwd()) =>
|
|
|
31
83
|
Effect.gen(function* () {
|
|
32
84
|
const contexts = yield* ContextService
|
|
33
85
|
const fs = yield* FileSystemService
|
|
86
|
+
const workbase = yield* WorkbaseService
|
|
34
87
|
const invocationCwd = resolve(cwd)
|
|
35
88
|
const context = yield* contexts
|
|
36
89
|
.get({ cwd: invocationCwd, target: ".", compact: true })
|
|
@@ -40,7 +93,26 @@ export const pr = (args: readonly string[], cwd: string = process.cwd()) =>
|
|
|
40
93
|
? context.authority.writable?.checkoutPath
|
|
41
94
|
: null
|
|
42
95
|
const focusedCwd = writableCheckout ?? invocationCwd
|
|
43
|
-
|
|
96
|
+
let forwardedArgs = args
|
|
97
|
+
if (
|
|
98
|
+
writableCheckout &&
|
|
99
|
+
context?.workbase.vcs === "jj" &&
|
|
100
|
+
context.authority.writable
|
|
101
|
+
) {
|
|
102
|
+
const execution =
|
|
103
|
+
context.documents?.phase?.data ?? context.documents?.task?.data
|
|
104
|
+
const { config } = yield* workbase.loadConfig(context.workbase.root)
|
|
105
|
+
const remote =
|
|
106
|
+
config.repositories?.[context.authority.writable.repo]?.remote
|
|
107
|
+
if (execution && "branch" in execution && remote) {
|
|
108
|
+
forwardedArgs = withJjContext(
|
|
109
|
+
args,
|
|
110
|
+
execution.branch,
|
|
111
|
+
repositoryFromRemote(remote),
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const result = yield* fs.runCommand(["gh", "pr", ...forwardedArgs], {
|
|
44
116
|
cwd: focusedCwd,
|
|
45
117
|
passthrough: true,
|
|
46
118
|
})
|
|
@@ -53,5 +125,6 @@ Usage: agency pr create <task-id> [phase-id] [--draft] [--force] [--json]
|
|
|
53
125
|
|
|
54
126
|
Create records a pull request for an Agency execution unit. Other invocations
|
|
55
127
|
run gh pr unchanged, focusing the writable repository checkout when invoked from
|
|
56
|
-
an Agency execution task or phase.
|
|
128
|
+
an Agency execution task or phase. In jj workbases, Agency supplies the declared
|
|
129
|
+
branch and repository to gh subcommands that would otherwise infer Git context.
|
|
57
130
|
`
|
|
@@ -16,20 +16,15 @@ import {
|
|
|
16
16
|
normalizePullRequestRecord,
|
|
17
17
|
parsePullRequestRecord,
|
|
18
18
|
recordFromGitHubUrl,
|
|
19
|
+
repositoryFromRemote,
|
|
19
20
|
resolveDeliveryCommand,
|
|
21
|
+
resolveGitHubCreateCommand,
|
|
20
22
|
} from "../workbase/delivery-command"
|
|
21
23
|
|
|
22
24
|
class PullRequestError extends Data.TaggedError("PullRequestError")<{
|
|
23
25
|
readonly message: string
|
|
24
26
|
}> {}
|
|
25
27
|
|
|
26
|
-
const repositoryFromRemote = (remote: string) =>
|
|
27
|
-
remote
|
|
28
|
-
.replace(/^[a-z][a-z0-9+.-]*:\/\/(?:[^@/]+@)?[^/]+\//i, "")
|
|
29
|
-
.replace(/^[^:]+:/, "")
|
|
30
|
-
.replace(/\.git\/?$/, "")
|
|
31
|
-
.replace(/\/$/, "")
|
|
32
|
-
|
|
33
28
|
interface PullRequestOptions extends BaseCommandOptions {
|
|
34
29
|
readonly force?: boolean
|
|
35
30
|
}
|
|
@@ -197,18 +192,13 @@ export class PullRequestService extends Effect.Service<PullRequestService>()(
|
|
|
197
192
|
url: "",
|
|
198
193
|
identifier: "",
|
|
199
194
|
})
|
|
200
|
-
: {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
execution.base,
|
|
208
|
-
...(draft ? ["--draft"] : []),
|
|
209
|
-
],
|
|
210
|
-
environment: {},
|
|
211
|
-
}
|
|
195
|
+
: resolveGitHubCreateCommand({
|
|
196
|
+
base: execution.base,
|
|
197
|
+
branch: execution.branch,
|
|
198
|
+
repository,
|
|
199
|
+
draft,
|
|
200
|
+
vcs: config.vcs ?? "git",
|
|
201
|
+
})
|
|
212
202
|
const created = yield* fs.runCommand(resolved.argv, {
|
|
213
203
|
cwd: workspace.writablePath,
|
|
214
204
|
captureOutput: true,
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
parsePullRequestRecord,
|
|
4
4
|
recordFromGitHubJson,
|
|
5
5
|
resolveDeliveryCommand,
|
|
6
|
+
resolveGitHubCreateCommand,
|
|
6
7
|
validateDelivery,
|
|
7
8
|
} from "./delivery-command"
|
|
8
9
|
|
|
@@ -31,6 +32,34 @@ describe("delivery commands", () => {
|
|
|
31
32
|
})
|
|
32
33
|
})
|
|
33
34
|
|
|
35
|
+
test("adds explicit jj context to the default GitHub create command", () => {
|
|
36
|
+
const input = {
|
|
37
|
+
base: "main",
|
|
38
|
+
branch: "feat/example",
|
|
39
|
+
repository: "example/agency",
|
|
40
|
+
draft: false,
|
|
41
|
+
} as const
|
|
42
|
+
expect(resolveGitHubCreateCommand({ ...input, vcs: "jj" })).toEqual({
|
|
43
|
+
argv: [
|
|
44
|
+
"gh",
|
|
45
|
+
"pr",
|
|
46
|
+
"create",
|
|
47
|
+
"--fill",
|
|
48
|
+
"--base",
|
|
49
|
+
"main",
|
|
50
|
+
"--head",
|
|
51
|
+
"feat/example",
|
|
52
|
+
"--repo",
|
|
53
|
+
"example/agency",
|
|
54
|
+
],
|
|
55
|
+
environment: {},
|
|
56
|
+
})
|
|
57
|
+
expect(resolveGitHubCreateCommand({ ...input, vcs: "git" })).toEqual({
|
|
58
|
+
argv: ["gh", "pr", "create", "--fill", "--base", "main"],
|
|
59
|
+
environment: {},
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
34
63
|
test("rejects unknown placeholders", () => {
|
|
35
64
|
expect(() =>
|
|
36
65
|
validateDelivery({ ...delivery, queryCommand: ["forge", "{unknown}"] }),
|
|
@@ -11,6 +11,39 @@ export interface DeliveryCommandVariables {
|
|
|
11
11
|
readonly identifier: string
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export const repositoryFromRemote = (remote: string) =>
|
|
15
|
+
remote
|
|
16
|
+
.replace(/^[a-z][a-z0-9+.-]*:\/\/(?:[^@/]+@)?[^/]+\//i, "")
|
|
17
|
+
.replace(/^[^:]+:/, "")
|
|
18
|
+
.replace(/\.git\/?$/, "")
|
|
19
|
+
.replace(/\/$/, "")
|
|
20
|
+
|
|
21
|
+
export const resolveGitHubCreateCommand = ({
|
|
22
|
+
base,
|
|
23
|
+
branch,
|
|
24
|
+
repository,
|
|
25
|
+
draft,
|
|
26
|
+
vcs,
|
|
27
|
+
}: {
|
|
28
|
+
readonly base: string
|
|
29
|
+
readonly branch: string
|
|
30
|
+
readonly repository: string
|
|
31
|
+
readonly draft: boolean
|
|
32
|
+
readonly vcs: "git" | "jj"
|
|
33
|
+
}) => ({
|
|
34
|
+
argv: [
|
|
35
|
+
"gh",
|
|
36
|
+
"pr",
|
|
37
|
+
"create",
|
|
38
|
+
"--fill",
|
|
39
|
+
"--base",
|
|
40
|
+
base,
|
|
41
|
+
...(vcs === "jj" ? ["--head", branch, "--repo", repository] : []),
|
|
42
|
+
...(draft ? ["--draft"] : []),
|
|
43
|
+
],
|
|
44
|
+
environment: {},
|
|
45
|
+
})
|
|
46
|
+
|
|
14
47
|
const PLACEHOLDERS = new Set<keyof DeliveryCommandVariables>([
|
|
15
48
|
"repository",
|
|
16
49
|
"branch",
|