@markjaquith/agency 2.54.4 → 2.56.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 +68 -1
- package/cli-main.ts +20 -0
- package/package.json +1 -1
- package/src/cli-parser.test.ts +9 -0
- package/src/cli-parser.ts +10 -0
- package/src/cli.test.ts +1 -0
- package/src/commands/push.ts +25 -0
- package/src/protocol.ts +1 -0
- package/src/services/DoctorService.ts +12 -0
- package/src/services/IntegrationService.test.ts +2 -0
- package/src/services/PushService.test.ts +516 -0
- package/src/services/PushService.ts +576 -0
- package/src/services/WorkbaseService.test.ts +24 -0
- package/src/services/WorkbaseService.ts +18 -0
- package/src/services/WorktreeService.test.ts +383 -1
- package/src/services/WorktreeService.ts +232 -4
- package/src/test-utils.ts +2 -0
- package/src/workbase/AGENTS.md +5 -0
- package/src/workbase/checkout-command.test.ts +62 -0
- package/src/workbase/checkout-command.ts +66 -0
- package/src/workbase/schemas.test.ts +34 -0
- package/src/workbase/schemas.ts +1 -0
package/README.md
CHANGED
|
@@ -235,6 +235,56 @@ Supplemental read-only repositories remain detached Git worktrees at their
|
|
|
235
235
|
declared refs so they do not acquire writable branches. Jj workbases always use
|
|
236
236
|
jj workspaces and ignore this Git-specific customization.
|
|
237
237
|
|
|
238
|
+
### Post-checkout Commands
|
|
239
|
+
|
|
240
|
+
Each repository declaration may provide a VCS-neutral `postCheckoutCommand` argv
|
|
241
|
+
template for repository-specific setup. Agency invokes it directly, without a
|
|
242
|
+
shell, with the new checkout as its working directory:
|
|
243
|
+
|
|
244
|
+
```json
|
|
245
|
+
{
|
|
246
|
+
"version": 2,
|
|
247
|
+
"repositories": {
|
|
248
|
+
"frontend": {
|
|
249
|
+
"remote": "git@example.com:team/frontend.git",
|
|
250
|
+
"postCheckoutCommand": ["bun", "install", "--frozen-lockfile"]
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The hook runs for each newly created managed checkout, including writable and
|
|
257
|
+
reference checkouts, after Git worktree or jj workspace creation has completed
|
|
258
|
+
and Agency has validated the checkout. It does not run for a reused checkout or
|
|
259
|
+
for inspection-only commands. A custom `worktreeCreateCommand` completes and is
|
|
260
|
+
validated before this hook runs.
|
|
261
|
+
|
|
262
|
+
Available placeholders and matching environment variables are:
|
|
263
|
+
|
|
264
|
+
| Placeholder | Environment | Value |
|
|
265
|
+
| ------------------ | ------------------------ | ---------------------------------------------- |
|
|
266
|
+
| `{repoAlias}` | `AGENCY_REPO_ALIAS` | Repository alias |
|
|
267
|
+
| `{repositoryPath}` | `AGENCY_REPOSITORY_PATH` | Absolute source repository path under `repos/` |
|
|
268
|
+
| `{checkoutPath}` | `AGENCY_CHECKOUT_PATH` | Absolute managed checkout path |
|
|
269
|
+
| `{checkoutKind}` | `AGENCY_CHECKOUT_KIND` | `writable` or `reference` |
|
|
270
|
+
| `{requestedRef}` | `AGENCY_REQUESTED_REF` | Requested branch, reference, or review commit |
|
|
271
|
+
| `{base}` | `AGENCY_BASE` | Configured execution base |
|
|
272
|
+
| `{vcs}` | `AGENCY_VCS` | `git` or `jj` |
|
|
273
|
+
| `{workbaseRoot}` | `AGENCY_WORKBASE_ROOT` | Absolute workbase root |
|
|
274
|
+
| `{taskId}` | `AGENCY_TASK_ID` | Task ID |
|
|
275
|
+
| `{phaseId}` | `AGENCY_PHASE_ID` | Phase ID |
|
|
276
|
+
|
|
277
|
+
`{base}` and `{phaseId}` and their environment variables are empty strings when
|
|
278
|
+
they do not apply. Dry runs report a planned `post-checkout` operation but never
|
|
279
|
+
execute it. Verbose output identifies the repository and expanded command.
|
|
280
|
+
|
|
281
|
+
Hook success is part of checkout creation. A non-zero exit or failure to start
|
|
282
|
+
rolls back the checkout and any branch created by the same operation; if cleanup
|
|
283
|
+
also fails, Agency reports the exact manual recovery action. A later command
|
|
284
|
+
retries checkout creation and the hook rather than reusing an uninitialized
|
|
285
|
+
checkout. Hook commands should be idempotent so a retry is safe after any
|
|
286
|
+
external effects the failed invocation may have completed.
|
|
287
|
+
|
|
238
288
|
### Agent Runners
|
|
239
289
|
|
|
240
290
|
OpenCode and Claude Code are built-in runner presets. Select either preset or a
|
|
@@ -812,12 +862,13 @@ restore preflight all destinations and graph references before changing files.
|
|
|
812
862
|
Versioned lifecycle provenance preserves parent declarations and dependency edges
|
|
813
863
|
for restoration. Archived IDs are reserved until restored.
|
|
814
864
|
|
|
815
|
-
### Work and Pull Requests
|
|
865
|
+
### Work, Publication, and Pull Requests
|
|
816
866
|
|
|
817
867
|
```text
|
|
818
868
|
agency work [<directory> | --epic <epic-id>] [--runner <name>] [--auto] [--print-command]
|
|
819
869
|
agency work prepare [target] [--dry-run] [--json]
|
|
820
870
|
agency worktree <list|inspect|prepare|remove|rebuild|repair>
|
|
871
|
+
agency push [--json]
|
|
821
872
|
agency pr create <task-id> [phase-id] [--draft] [--force] [--json]
|
|
822
873
|
agency pr [args...]
|
|
823
874
|
```
|
|
@@ -865,6 +916,22 @@ Agency resolves the ref to a commit and creates a detached worktree. Existing
|
|
|
865
916
|
reference worktrees are reused only while their commit still matches the declared
|
|
866
917
|
ref; use a commit SHA as `ref` when reproducibility matters.
|
|
867
918
|
|
|
919
|
+
`agency push` publishes the execution unit identified by current Agency context
|
|
920
|
+
without creating a pull request. It requires a valid, registered writable
|
|
921
|
+
checkout in `working` state, fetches the configured delivery remote, verifies
|
|
922
|
+
that the declared base is in the publication history, validates every outgoing
|
|
923
|
+
commit's description, author, and conflict state, and refuses non-fast-forward
|
|
924
|
+
updates.
|
|
925
|
+
|
|
926
|
+
For Git, YAML `branch` must exactly match the checked-out local branch, the
|
|
927
|
+
worktree must be clean, and `HEAD` is pushed with upstream tracking. For jj, YAML
|
|
928
|
+
`branch` is the authoritative delivery bookmark and need not exist before
|
|
929
|
+
publication. Agency publishes `@` unless it is the canonical empty, undescribed
|
|
930
|
+
post-commit working copy, in which case it publishes `@-`; described empty
|
|
931
|
+
changes remain intentional publication tips. Missing descriptions or authors
|
|
932
|
+
stop with exact change IDs and remediation commands. Agency creates or safely
|
|
933
|
+
advances only the declared bookmark and never invents a `push-*` bookmark.
|
|
934
|
+
|
|
868
935
|
Task-aware `agency pr create <task-id> [phase-id]` uses Agency's delivery flow,
|
|
869
936
|
including readiness checks and durable PR recording. Other `agency pr`
|
|
870
937
|
invocations forward every argument to `gh pr`. From an execution task or phase
|
package/cli-main.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { parseCli } from "./src/cli-parser"
|
|
|
6
6
|
import { init, help as initHelp } from "./src/commands/init"
|
|
7
7
|
import { task, help as taskHelp } from "./src/commands/task"
|
|
8
8
|
import { pr, prCreate, help as prHelp } from "./src/commands/pr"
|
|
9
|
+
import { push, help as pushHelp } from "./src/commands/push"
|
|
9
10
|
import { work, workPrepare, help as workHelp } from "./src/commands/work"
|
|
10
11
|
import { worktree, help as worktreeHelp } from "./src/commands/worktree"
|
|
11
12
|
import { status, help as statusHelp } from "./src/commands/status"
|
|
@@ -34,6 +35,7 @@ import { TaskService } from "./src/services/TaskService"
|
|
|
34
35
|
import { PhaseService } from "./src/services/PhaseService"
|
|
35
36
|
import { WorktreeService } from "./src/services/WorktreeService"
|
|
36
37
|
import { PullRequestService } from "./src/services/PullRequestService"
|
|
38
|
+
import { PushService } from "./src/services/PushService"
|
|
37
39
|
import { ArchiveService } from "./src/services/ArchiveService"
|
|
38
40
|
import { IntegrationService } from "./src/services/IntegrationService"
|
|
39
41
|
import { ContextService } from "./src/services/ContextService"
|
|
@@ -79,6 +81,7 @@ const CliLayer = Layer.mergeAll(
|
|
|
79
81
|
PhaseService.Default,
|
|
80
82
|
WorktreeService.Default,
|
|
81
83
|
PullRequestService.Default,
|
|
84
|
+
PushService.Default,
|
|
82
85
|
ArchiveService.Default,
|
|
83
86
|
IntegrationService.Default,
|
|
84
87
|
ContextService.Default,
|
|
@@ -305,6 +308,22 @@ const commands: Record<string, Command> = {
|
|
|
305
308
|
)
|
|
306
309
|
},
|
|
307
310
|
},
|
|
311
|
+
push: {
|
|
312
|
+
run: async (_args: string[], options: Record<string, any>) => {
|
|
313
|
+
if (options.help) {
|
|
314
|
+
console.log(pushHelp)
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
await runCommand(
|
|
318
|
+
push({
|
|
319
|
+
json: options.json,
|
|
320
|
+
silent: options.silent,
|
|
321
|
+
verbose: options.verbose,
|
|
322
|
+
cwd: options.cwd,
|
|
323
|
+
}),
|
|
324
|
+
)
|
|
325
|
+
},
|
|
326
|
+
},
|
|
308
327
|
phase: {
|
|
309
328
|
run: async (args: string[], options: Record<string, any>) => {
|
|
310
329
|
if (options.help) return console.log(phaseHelp)
|
|
@@ -742,6 +761,7 @@ Commands:
|
|
|
742
761
|
vcs <subcommand> Inspect or migrate the version-control backend
|
|
743
762
|
next List or select ready execution units
|
|
744
763
|
pr create / pr [...] Create an Agency PR or run gh pr with repository focus
|
|
764
|
+
push Validate and publish the current execution unit
|
|
745
765
|
review refresh Explicitly refresh a pinned review task
|
|
746
766
|
repo <subcommand> Manage workbase repositories
|
|
747
767
|
status Show status for the current workbase
|
package/package.json
CHANGED
package/src/cli-parser.test.ts
CHANGED
|
@@ -616,6 +616,15 @@ describe("strict CLI parsing", () => {
|
|
|
616
616
|
).toThrow("cannot be combined")
|
|
617
617
|
})
|
|
618
618
|
|
|
619
|
+
test("parses push without accepting an alternate target", () => {
|
|
620
|
+
expect(parseCli(["push", "--json"])).toMatchObject({
|
|
621
|
+
commandName: "push",
|
|
622
|
+
args: [],
|
|
623
|
+
values: { json: true },
|
|
624
|
+
})
|
|
625
|
+
expectUsageError(["push", "example"], "agency push")
|
|
626
|
+
})
|
|
627
|
+
|
|
619
628
|
test("accepts runner selection and command inspection for work", () => {
|
|
620
629
|
expect(
|
|
621
630
|
parseCli([
|
package/src/cli-parser.ts
CHANGED
|
@@ -913,6 +913,16 @@ const commands = {
|
|
|
913
913
|
},
|
|
914
914
|
},
|
|
915
915
|
},
|
|
916
|
+
push: {
|
|
917
|
+
usage: "agency push [--json]",
|
|
918
|
+
options: outputOptions,
|
|
919
|
+
command: {
|
|
920
|
+
usage: "agency push [--json]",
|
|
921
|
+
minArgs: 0,
|
|
922
|
+
maxArgs: 0,
|
|
923
|
+
options: ["json"],
|
|
924
|
+
},
|
|
925
|
+
},
|
|
916
926
|
next: {
|
|
917
927
|
usage: "agency next [--select] [--json]",
|
|
918
928
|
options: {
|
package/src/cli.test.ts
CHANGED
|
@@ -502,6 +502,7 @@ describe("CLI", () => {
|
|
|
502
502
|
["archive", "Usage: agency archive"],
|
|
503
503
|
["restore", "Usage: agency restore"],
|
|
504
504
|
["work", "Usage: agency work"],
|
|
505
|
+
["push", "Usage: agency push"],
|
|
505
506
|
["pr", "Work with GitHub pull requests."],
|
|
506
507
|
["status", "Usage: agency status"],
|
|
507
508
|
["validate", "Usage: agency validate"],
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Effect } from "effect"
|
|
2
|
+
import { PushService } from "../services/PushService"
|
|
3
|
+
import type { BaseCommandOptions } from "../utils/command"
|
|
4
|
+
import { createLoggers } from "../utils/effect"
|
|
5
|
+
|
|
6
|
+
export const push = (options: BaseCommandOptions = {}) =>
|
|
7
|
+
Effect.gen(function* () {
|
|
8
|
+
const publications = yield* PushService
|
|
9
|
+
const { log } = createLoggers(options)
|
|
10
|
+
const result = yield* publications.publish(options.cwd ?? process.cwd())
|
|
11
|
+
log(
|
|
12
|
+
options.json
|
|
13
|
+
? JSON.stringify(result, null, 2)
|
|
14
|
+
: `Published ${result.vcs} ${result.branch} at ${result.tip} to ${result.remote}`,
|
|
15
|
+
)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const help = `
|
|
19
|
+
Usage: agency push [--json]
|
|
20
|
+
|
|
21
|
+
Validate and publish the current Agency execution unit without creating a pull
|
|
22
|
+
request. The command uses the durable base and branch declarations, requires the
|
|
23
|
+
managed writable checkout and working status, refreshes remote state, validates
|
|
24
|
+
every outgoing commit, and rejects non-fast-forward publication.
|
|
25
|
+
`
|
package/src/protocol.ts
CHANGED
|
@@ -99,6 +99,7 @@ const errorMetadata: Readonly<Record<string, ErrorMetadata>> = {
|
|
|
99
99
|
},
|
|
100
100
|
ArchiveError: { code: "ARCHIVE_ERROR", retryable: false },
|
|
101
101
|
WorktreeError: { code: "WORKTREE_ERROR", retryable: false },
|
|
102
|
+
PushError: { code: "PUSH_ERROR", retryable: false },
|
|
102
103
|
PullRequestError: { code: "PULL_REQUEST_ERROR", retryable: false },
|
|
103
104
|
ReviewError: { code: "REVIEW_ERROR", retryable: false },
|
|
104
105
|
ContextError: {
|
|
@@ -173,6 +173,18 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
173
173
|
] as const,
|
|
174
174
|
]
|
|
175
175
|
: []),
|
|
176
|
+
...Object.entries(config.repositories ?? {}).flatMap(
|
|
177
|
+
([alias, repository]) =>
|
|
178
|
+
repository.postCheckoutCommand
|
|
179
|
+
? [
|
|
180
|
+
[
|
|
181
|
+
`integration.repository.${alias}.post-checkout`,
|
|
182
|
+
repository.postCheckoutCommand,
|
|
183
|
+
`Repository '${alias}' post-checkout hook`,
|
|
184
|
+
] as const,
|
|
185
|
+
]
|
|
186
|
+
: [],
|
|
187
|
+
),
|
|
176
188
|
...Object.entries(config.runners ?? {}).map(
|
|
177
189
|
([name, runner]) =>
|
|
178
190
|
[
|
|
@@ -422,6 +422,8 @@ describe("IntegrationService", () => {
|
|
|
422
422
|
expect(body).toContain("without creating a claim")
|
|
423
423
|
expect(body).toContain("formatting, type checks, build, dead-code checks")
|
|
424
424
|
expect(body).toContain("Review and commit the diff")
|
|
425
|
+
expect(body).toContain("Use `agency push`")
|
|
426
|
+
expect(body).toContain("never authors semantic commit descriptions")
|
|
425
427
|
expect(body).toContain("Run `agency validate`")
|
|
426
428
|
expect(body).toContain("only with explicit user intent")
|
|
427
429
|
expect(body).toContain("An execution unit remains `working`")
|