@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.
Files changed (59) hide show
  1. package/README.md +35 -134
  2. package/cli-main.ts +0 -26
  3. package/cli.ts +1 -17
  4. package/package.json +1 -2
  5. package/schemas/agency-graph-v1.schema.json +1 -1
  6. package/src/cli-parser.test.ts +0 -19
  7. package/src/cli-parser.ts +0 -23
  8. package/src/cli.test.ts +3 -3
  9. package/src/commands/context.test.ts +11 -71
  10. package/src/commands/context.ts +7 -7
  11. package/src/commands/init.test.ts +1 -2
  12. package/src/commands/pr.test.ts +2 -87
  13. package/src/commands/pr.ts +2 -79
  14. package/src/commands/push.test.ts +1 -2
  15. package/src/graph-schema.ts +1 -1
  16. package/src/services/ArchiveBulkService.test.ts +1 -142
  17. package/src/services/ArchiveService.test.ts +1 -172
  18. package/src/services/ArchiveService.ts +0 -26
  19. package/src/services/ContextService.ts +7 -50
  20. package/src/services/DoctorService.ts +8 -24
  21. package/src/services/GraphService.test.ts +1 -0
  22. package/src/services/GraphService.ts +2 -5
  23. package/src/services/IntegrationService.test.ts +4 -0
  24. package/src/services/PhaseService.ts +70 -191
  25. package/src/services/PullRequestService.test.ts +1 -45
  26. package/src/services/PullRequestService.ts +1 -22
  27. package/src/services/PushService.test.ts +29 -295
  28. package/src/services/PushService.ts +10 -239
  29. package/src/services/RepositoryService.test.ts +1 -61
  30. package/src/services/RepositoryService.ts +2 -13
  31. package/src/services/ReviewService.test.ts +1 -84
  32. package/src/services/ReviewService.ts +9 -48
  33. package/src/services/SyncService.test.ts +0 -81
  34. package/src/services/SyncService.ts +12 -39
  35. package/src/services/TaskPhaseService.test.ts +0 -80
  36. package/src/services/TaskService.ts +1 -9
  37. package/src/services/VersionControlService.test.ts +4 -117
  38. package/src/services/VersionControlService.ts +3 -426
  39. package/src/services/WorkbaseService.test.ts +0 -20
  40. package/src/services/WorkbaseService.ts +1 -18
  41. package/src/services/WorktreeService.test.ts +1 -705
  42. package/src/services/WorktreeService.ts +395 -1604
  43. package/src/services/push-validation.ts +0 -7
  44. package/src/test-utils.ts +0 -4
  45. package/src/workbase/AGENTS.md +7 -7
  46. package/src/workbase/checkout-command.test.ts +1 -1
  47. package/src/workbase/checkout-command.ts +1 -1
  48. package/src/workbase/delivery-command.test.ts +4 -35
  49. package/src/workbase/delivery-command.ts +2 -15
  50. package/src/workbase/schemas.test.ts +0 -19
  51. package/src/workbase/schemas.ts +1 -2
  52. package/src/commands/vcs.test.ts +0 -75
  53. package/src/commands/vcs.ts +0 -72
  54. package/src/services/VcsMigrationService.test.ts +0 -348
  55. package/src/services/VcsMigrationService.ts +0 -857
  56. package/src/vcs-status-fast.ts +0 -310
  57. package/src/workbase/version-control.ts +0 -5
  58. package/src/workbase/workspace-command.test.ts +0 -70
  59. package/src/workbase/workspace-command.ts +0 -63
package/README.md CHANGED
@@ -9,7 +9,6 @@ read or write.
9
9
 
10
10
  - [Bun](https://bun.sh) 1.0 or newer
11
11
  - Git
12
- - [Jujutsu](https://jj-vcs.github.io/jj/) is preferred when available
13
12
  - [GitHub CLI](https://cli.github.com/) for `agency pr`
14
13
  - OpenCode, Claude Code, or a configured agent for `agency work`
15
14
 
@@ -151,15 +150,16 @@ the writable checkout's `.claude/skills`, `.agents/skills`,
151
150
  `.opencode/{skill,skills}`, and `.pi/skills` directories. `agency context`
152
151
  remains the authority for writes; reference checkouts remain read-only.
153
152
 
154
- Repository aliases, the version-control backend, and canonical fetch remotes are
155
- declared in tracked `agency.json`; local clones and symlinks remain ignored under
156
- `repos/{alias}`. A declaration contains no local path, symlink target, checkout,
157
- or credential:
153
+ Repository aliases and canonical fetch remotes are declared in tracked
154
+ `agency.json`; local Git clones and symlinks remain ignored under
155
+ `repos/{alias}`. A declaration contains no local path, symlink target, worktree,
156
+ or credential. The optional `vcs` field accepts only `"git"`; omitting it also
157
+ selects Git:
158
158
 
159
159
  ```json
160
160
  {
161
161
  "version": 2,
162
- "vcs": "jj",
162
+ "vcs": "git",
163
163
  "repositories": {
164
164
  "frontend": {
165
165
  "remote": "git@example.com:team/frontend.git"
@@ -168,39 +168,9 @@ or credential:
168
168
  }
169
169
  ```
170
170
 
171
- New workbases select `"jj"` when the `jj` executable is available and otherwise
172
- select `"git"`. Existing version 2 workbases without `vcs` remain Git workbases.
173
- The selected backend is a workbase-level invariant: jj workbases create
174
- non-colocated managed clones and jj workspaces, while Git workbases continue to
175
- use bare repositories and Git worktrees. Existing colocated jj repositories are
176
- also supported.
177
-
178
- Agency uses jj-native commands for repository validity, workspace registration,
179
- working-copy state, revisions, bookmarks, ancestry, fetch, and push. Raw Git is
180
- limited to backing-store plumbing such as durable review refs and integrations
181
- that require Git, including GitHub CLI commit discovery. For those operations,
182
- Agency obtains the backing repository with `jj git root` and supplies it as
183
- `GIT_DIR`; it does not use raw Git to infer jj workspace state.
184
-
185
- Inspect the current backend and migration readiness with:
186
-
187
- ```bash
188
- agency vcs status
189
- agency vcs migrate jj # dry-run
190
- agency vcs migrate jj --apply
191
- agency vcs migrate git --apply
192
- ```
193
-
194
- Migration is workbase-wide and transactional. Agency locks every execution
195
- unit, requires clean registered workspaces, blocks active claims and working or
196
- delegated units, converts repository metadata, recreates checkout paths with the
197
- target backend, and updates `agency.json` last. Migration from jj to Git also
198
- blocks jj-only heads that are not preserved by a bookmark or workspace. Failed
199
- migrations restore the source repositories and workspaces when rollback is
200
- possible and report explicit manual recovery paths otherwise.
201
- Converting a non-colocated jj workbase to Git is currently blocked before any
202
- mutation because its backing Git object store is inside `.jj`; first convert the
203
- repositories to colocated jj so the Git store survives metadata removal.
171
+ Agency materializes declared remotes as bare Git repositories and creates
172
+ managed Git worktrees for execution units. Linked aliases remain symlinks to
173
+ existing Git repositories.
204
174
 
205
175
  Existing version 2 workbases without `repositories` remain valid. Run
206
176
  `agency repo setup` to preview deterministic adoption of legacy local aliases;
@@ -274,63 +244,13 @@ Agency also reconciles known tool-owned artifacts such as Worktrunk's
274
244
  new and existing managed worktrees clean without changing the repository's
275
245
  tracked ignore configuration or overwriting user-maintained local excludes.
276
246
 
277
- The configured command applies only to the writable checkout of a Git workbase.
247
+ The configured command applies only to the writable checkout.
278
248
  Supplemental read-only repositories remain detached Git worktrees at their
279
- declared refs so they do not acquire writable branches. Jj workbases always use
280
- jj workspaces and ignore this Git-specific customization.
281
-
282
- ### Custom Jj Workspace Command
283
-
284
- Jj workbases normally create managed workspaces with `jj workspace add`. Set
285
- `workspaceCreateCommand` to an argv template when another tool should create or
286
- adopt a prepared workspace directly at Agency's destination:
287
-
288
- ```json
289
- {
290
- "version": 2,
291
- "vcs": "jj",
292
- "workspaceCreateCommand": [
293
- "my-prewarm-tool",
294
- "adopt",
295
- "--repo",
296
- "{repo}",
297
- "--destination",
298
- "{workspace}",
299
- "--name",
300
- "{name}",
301
- "--revision",
302
- "{revision}"
303
- ]
304
- }
305
- ```
306
-
307
- Available placeholders are:
308
-
309
- - `{repo}`: absolute repository alias path under `repos/`
310
- - `{workspace}`: absolute managed workspace path Agency requires
311
- - `{name}`: unique jj workspace name Agency requires
312
- - `{revision}`: exact commit the new working copy must be based on
313
- - `{kind}`: `writable` or `reference`
314
- - `{requestedRef}`: configured branch, reference, or review commit
315
-
316
- `{repo}`, `{workspace}`, `{name}`, and `{revision}` are required. Agency invokes
317
- the command directly without a shell and sets matching `AGENCY_REPO`,
318
- `AGENCY_WORKSPACE`, `AGENCY_WORKSPACE_NAME`, `AGENCY_REVISION`,
319
- `AGENCY_CHECKOUT_KIND`, and `AGENCY_REQUESTED_REF` environment variables. The
320
- command applies to each new jj checkout and must leave `{workspace}` registered
321
- under `{name}` with its working-copy parent at `{revision}`. This lets a prewarm
322
- tool move or adopt a prepared workspace without first paying for Agency's normal
323
- full checkout.
324
-
325
- Agency validates the registration, name, path, and revision before running any
326
- `postCheckoutCommand`. A failed command or validation removes a partially
327
- created workspace when possible and otherwise reports manual recovery. Resume
328
- restoration continues to use Agency's built-in exact-target recovery path.
329
- Git workbases ignore this jj-specific customization.
249
+ declared refs so they do not acquire writable branches.
330
250
 
331
251
  ### Post-checkout Commands
332
252
 
333
- Each repository declaration may provide a VCS-neutral `postCheckoutCommand` argv
253
+ Each repository declaration may provide a `postCheckoutCommand` argv
334
254
  template for repository-specific setup. Agency invokes it directly, without a
335
255
  shell, with the new checkout as its working directory:
336
256
 
@@ -346,11 +266,10 @@ shell, with the new checkout as its working directory:
346
266
  }
347
267
  ```
348
268
 
349
- The hook runs for each newly created managed checkout, including writable and
350
- reference checkouts, after Git worktree or jj workspace creation has completed
351
- and Agency has validated the checkout. It does not run for a reused checkout or
352
- for inspection-only commands. A custom `worktreeCreateCommand` or
353
- `workspaceCreateCommand` completes and is validated before this hook runs.
269
+ The hook runs for each newly created managed Git worktree, including writable
270
+ and reference worktrees, after Agency has validated it. It does not run for a
271
+ reused worktree or for inspection-only commands. A custom
272
+ `worktreeCreateCommand` completes and is validated before this hook runs.
354
273
 
355
274
  Available placeholders and matching environment variables are:
356
275
 
@@ -362,7 +281,7 @@ Available placeholders and matching environment variables are:
362
281
  | `{checkoutKind}` | `AGENCY_CHECKOUT_KIND` | `writable` or `reference` |
363
282
  | `{requestedRef}` | `AGENCY_REQUESTED_REF` | Requested branch, reference, or review commit |
364
283
  | `{base}` | `AGENCY_BASE` | Configured execution base |
365
- | `{vcs}` | `AGENCY_VCS` | `git` or `jj` |
284
+ | `{vcs}` | `AGENCY_VCS` | `git` |
366
285
  | `{workbaseRoot}` | `AGENCY_WORKBASE_ROOT` | Absolute workbase root |
367
286
  | `{taskId}` | `AGENCY_TASK_ID` | Task ID |
368
287
  | `{phaseId}` | `AGENCY_PHASE_ID` | Phase ID |
@@ -631,7 +550,7 @@ included in generated or executed pull request commands.
631
550
 
632
551
  ### Target Context
633
552
 
634
- `agency context [target] --json` returns complete bootstrap context without
553
+ `agency context [target] --json` returns compact bootstrap context without
635
554
  modifying the workbase or fetching repositories. At the workbase root it returns
636
555
  a discovery catalog of all epics, tasks, and phases, including frontmatter,
637
556
  paths, and document revisions. Elsewhere it returns context for an epic, task,
@@ -642,15 +561,15 @@ explicitly marked with `target.archived: true` and never grants writable
642
561
  document, repository checkout, or reference authority; restore the item before
643
562
  attempting mutation or execution.
644
563
 
645
- Root discovery is compact by default and includes a hint to run `agency context
646
- . --full --json` when document prose is needed. Entity context remains complete
647
- by default. `--compact` explicitly requests compact entity context.
564
+ Root discovery and entity context are compact by default. Root discovery includes
565
+ a hint to run `agency context . --full --json` when document prose is needed.
566
+ `--compact` remains available as an explicit request for the compact projection.
648
567
 
649
- The result includes workbase and target identity, ancestor frontmatter and prose
650
- with SHA-256 hashes, dependency and readiness state, aggregate status, writable
651
- and reference authority, local checkout and resolved-commit state, recorded PR
652
- state, and validation warnings. Only `done` satisfies a dependency; `dropped` is
653
- terminal but remains a blocker.
568
+ The result includes workbase and target identity, ancestor frontmatter with
569
+ SHA-256 hashes, dependency and readiness state, aggregate status, writable and
570
+ reference authority, local materialization state, recorded PR state, and
571
+ validation warnings. Only `done` satisfies a dependency; `dropped` is terminal
572
+ but remains a blocker.
654
573
 
655
574
  `authority.writable` identifies the writable repository checkout, while
656
575
  `authority.documents.writable` lists the absolute paths of Agency documents the
@@ -658,10 +577,9 @@ target may maintain. A single-phase task lists its `TASK.md`; a phase lists its
658
577
  owning `TASK.md` and active `PHASE.md`; orchestration targets list none. Use
659
578
  Agency commands rather than direct edits for structural frontmatter mutations.
660
579
 
661
- Complete output is the default for entity targets. Pass `--compact` explicitly
662
- to omit document prose and low-level Git details while retaining identity,
663
- hashes, authority, paths, graph state, materialization state, and validation
664
- warnings.
580
+ Pass `--full` to include document prose and low-level VCS details. Compact output
581
+ retains identity, hashes, authority, paths, graph state, materialization state,
582
+ recorded PR state, and validation warnings.
665
583
 
666
584
  ### Workbase Graph
667
585
 
@@ -1163,15 +1081,6 @@ conflicting worktrees. Repair is deliberately conservative: it repairs safe Git
1163
1081
  registration issues and materializes missing checkouts, but never switches a
1164
1082
  branch, resets a commit, or discards uncommitted work.
1165
1083
 
1166
- For jj, explicit removal is a suspend operation. Agency records each workspace's
1167
- exact `@` commit and stable change ID in `.agency-jj-resume.json`, roots that
1168
- commit with an internal local bookmark, and only then forgets the workspace.
1169
- The next `work prepare` validates all three identities, restores the workspace by
1170
- editing the recorded commit, and consumes the resume metadata and bookmarks.
1171
- Missing, ambiguous, or conflicting resume state stops instead of falling back to
1172
- the declared delivery bookmark, `@-`, or the base. Unknown checkout cleanliness
1173
- also stops removal. Git worktree removal and preparation are unchanged.
1174
-
1175
1084
  Agency launches every agent beside its epic or task document. Single-phase tasks
1176
1085
  and phases first fetch repositories and create or reuse worktrees under `code/`,
1177
1086
  then launch the execution agent from the task directory with absolute context
@@ -1196,18 +1105,11 @@ that the declared base is in the publication history, validates every outgoing
1196
1105
  commit's description, author, and conflict state, and refuses non-fast-forward
1197
1106
  updates.
1198
1107
 
1199
- For Git, YAML `branch` must exactly match the checked-out local branch, the
1200
- worktree must be clean, and `HEAD` is pushed with upstream tracking. For jj, YAML
1201
- `branch` is the authoritative delivery bookmark and need not exist before
1202
- publication. Agency publishes `@` unless it is the canonical empty, undescribed
1203
- post-commit working copy, in which case it publishes `@-`; described empty
1204
- changes remain intentional publication tips. Missing descriptions or authors
1205
- stop with exact change IDs and remediation commands. Agency creates or safely
1206
- advances only the declared bookmark and never invents a `push-*` bookmark.
1207
- If the fetched remote base advanced outside the local stack, Agency prints the
1208
- exact `jj rebase` command needed to move the stack onto `<base>@<remote>`. Push
1209
- reports deterministic fetch, inspection, validation, and publication progress
1210
- on stderr, including while `--json` reserves stdout for one machine result.
1108
+ YAML `branch` must exactly match the checked-out local branch, the worktree must
1109
+ be clean, and `HEAD` is pushed with upstream tracking. Missing commit descriptions
1110
+ or authors stop publication with exact commits and remediation commands. Push
1111
+ reports deterministic fetch, inspection, validation, and publication progress on
1112
+ stderr, including while `--json` reserves stdout for one machine result.
1211
1113
 
1212
1114
  Task-aware `agency pr create <task-id> [phase-id]` uses Agency's delivery flow,
1213
1115
  including readiness checks and durable PR recording. It accepts draft, title,
@@ -1217,8 +1119,7 @@ Other `agency pr`
1217
1119
  invocations forward every argument to `gh pr`. From an execution task or phase
1218
1120
  directory, including descendants, passthrough runs in that execution unit's
1219
1121
  authoritative writable checkout. Otherwise it runs in the caller's current
1220
- directory. In jj workbases, Agency also supplies the repository and the work
1221
- item's declared branch to subcommands that would otherwise infer Git context.
1122
+ directory.
1222
1123
 
1223
1124
  ### Status and Validation
1224
1125
 
package/cli-main.ts CHANGED
@@ -48,12 +48,9 @@ import { DoctorService } from "./src/services/DoctorService"
48
48
  import { ReviewService } from "./src/services/ReviewService"
49
49
  import {
50
50
  GitVersionControlService,
51
- JjVersionControlService,
52
51
  VersionControlService,
53
52
  } from "./src/services/VersionControlService"
54
53
  import { review, help as reviewHelp } from "./src/commands/review"
55
- import { vcs, help as vcsHelp } from "./src/commands/vcs"
56
- import { VcsMigrationService } from "./src/services/VcsMigrationService"
57
54
  import { act, help as actHelp } from "./src/commands/act"
58
55
  import {
59
56
  claimCommand,
@@ -74,9 +71,7 @@ const CliLayer = Layer.mergeAll(
74
71
  FileSystemService.Default,
75
72
  WorkbaseService.Default,
76
73
  GitVersionControlService.Default,
77
- JjVersionControlService.Default,
78
74
  VersionControlService.Default,
79
- VcsMigrationService.Default,
80
75
  RepositoryService.Default,
81
76
  EpicService.Default,
82
77
  TaskService.Default,
@@ -623,26 +618,6 @@ const commands: Record<string, Command> = {
623
618
  )
624
619
  },
625
620
  },
626
- vcs: {
627
- run: async (args: string[], options: Record<string, any>) => {
628
- if (options.help) {
629
- console.log(vcsHelp)
630
- return
631
- }
632
- await runCommand(
633
- vcs({
634
- subcommand: args[0],
635
- target: args[1],
636
- apply: options.apply,
637
- dryRun: options["dry-run"],
638
- json: options.json,
639
- silent: options.silent,
640
- verbose: options.verbose,
641
- cwd: options.cwd,
642
- }),
643
- )
644
- },
645
- },
646
621
  next: {
647
622
  run: async (_args: string[], options: Record<string, any>) => {
648
623
  if (options.help) return console.log(nextHelp)
@@ -802,7 +777,6 @@ Commands:
802
777
  task <subcommand> Manage tasks
803
778
  work [directory|task] Work on an epic, task, or phase
804
779
  worktree <subcommand> Inspect and maintain managed workspaces
805
- vcs <subcommand> Inspect or migrate the version-control backend
806
780
  next List or select ready execution units
807
781
  pr create / pr [...] Create an Agency PR or run gh pr with repository focus
808
782
  push Validate and publish the current execution unit
package/cli.ts CHANGED
@@ -1,19 +1,3 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- const args = Bun.argv.slice(2)
4
- const fastVcsStatus =
5
- args[0] === "vcs" &&
6
- args[1] === "status" &&
7
- (args.length === 2 || (args.length === 3 && args[2] === "--json"))
8
-
9
- if (fastVcsStatus) {
10
- try {
11
- const { runVcsStatusFast } = await import("./src/vcs-status-fast")
12
- if (!(await runVcsStatusFast(args[2] === "--json")))
13
- await import("./cli-main")
14
- } catch {
15
- await import("./cli-main")
16
- }
17
- } else {
18
- await import("./cli-main")
19
- }
3
+ await import("./cli-main")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "2.73.2",
3
+ "version": "3.0.0",
4
4
  "description": "Manage agentic work across repositories with durable workbases",
5
5
  "keywords": [
6
6
  "agents",
@@ -83,7 +83,6 @@
83
83
  "benchmark:graph": "bun scripts/benchmark-graph.ts",
84
84
  "benchmark:push": "bun scripts/benchmark-push.ts",
85
85
  "benchmark:archive": "bun scripts/benchmark-archive.ts",
86
- "benchmark:vcs": "bun scripts/benchmark-vcs.ts",
87
86
  "benchmark:sync": "bun scripts/benchmark-sync.ts",
88
87
  "benchmark:task": "bun scripts/benchmark-task.ts",
89
88
  "benchmark:validate": "bun scripts/benchmark-validate.ts",
@@ -22,7 +22,7 @@
22
22
  "required": ["version"],
23
23
  "properties": {
24
24
  "version": { "const": 2 },
25
- "vcs": { "enum": ["git", "jj"] },
25
+ "vcs": { "enum": ["git"] },
26
26
  "root": { "type": "string" }
27
27
  }
28
28
  },
@@ -674,25 +674,6 @@ describe("strict CLI parsing", () => {
674
674
  )
675
675
  })
676
676
 
677
- test("parses VCS status and migration commands", () => {
678
- expect(parseCli(["vcs", "status", "--json"])).toMatchObject({
679
- commandName: "vcs",
680
- args: ["status"],
681
- values: { json: true },
682
- })
683
- expect(
684
- parseCli(["vcs", "migrate", "jj", "--apply", "--json"]),
685
- ).toMatchObject({
686
- commandName: "vcs",
687
- args: ["migrate", "jj"],
688
- values: { apply: true, json: true },
689
- })
690
- expectUsageError(["vcs", "migrate"], "agency vcs migrate")
691
- expect(() =>
692
- parseCli(["vcs", "migrate", "jj", "--dry-run", "--apply"]),
693
- ).toThrow("cannot be combined")
694
- })
695
-
696
677
  test("parses push without accepting an alternate target", () => {
697
678
  expect(parseCli(["push", "--json"])).toMatchObject({
698
679
  commandName: "push",
package/src/cli-parser.ts CHANGED
@@ -908,29 +908,6 @@ const commands = {
908
908
  },
909
909
  },
910
910
  },
911
- vcs: {
912
- usage: "agency vcs <status|migrate>",
913
- options: {
914
- ...outputOptions,
915
- apply: { type: "boolean" },
916
- "dry-run": { type: "boolean" },
917
- },
918
- subcommands: {
919
- status: {
920
- usage: "agency vcs status [--json]",
921
- minArgs: 0,
922
- maxArgs: 0,
923
- options: ["json"],
924
- },
925
- migrate: {
926
- usage: "agency vcs migrate <git|jj> [--dry-run | --apply] [--json]",
927
- minArgs: 1,
928
- maxArgs: 1,
929
- options: ["apply", "dry-run", "json"],
930
- conflicts: [["apply", "dry-run"]],
931
- },
932
- },
933
- },
934
911
  work: {
935
912
  usage:
936
913
  "agency work [<directory-or-task-id> | --epic <epic-id>] [--agent <name>] [--auto] | agency work prepare [target] [--dry-run] [--json]",
package/src/cli.test.ts CHANGED
@@ -1929,9 +1929,9 @@ status: open
1929
1929
  alias: "agency",
1930
1930
  status: "applied",
1931
1931
  })
1932
- expect(
1933
- (await stat(join(restored, "repos/agency/.jj"))).isDirectory(),
1934
- ).toBe(true)
1932
+ expect((await stat(join(restored, "repos/agency/HEAD"))).isFile()).toBe(
1933
+ true,
1934
+ )
1935
1935
 
1936
1936
  const prepared = parseJson(
1937
1937
  await runCli(["work", "prepare", "portable", "--json"], restored),
@@ -161,9 +161,9 @@ Phase prose.
161
161
  await cleanupTempDir(root)
162
162
  })
163
163
 
164
- test("returns complete phase context with graph, authority, Git, and validation state", async () => {
164
+ test("returns full phase context with graph, authority, Git, and validation state", async () => {
165
165
  const target = "tasks/agent-contract/phases/context-command"
166
- const result = await readContext(root, target)
166
+ const result = await readContext(root, target, false, true)
167
167
 
168
168
  expect(result).toMatchObject({
169
169
  projection: "complete",
@@ -219,81 +219,19 @@ Phase prose.
219
219
  )
220
220
  })
221
221
 
222
- test("reports the current jj change without inventing a missing bookmark", async () => {
223
- if (!Bun.which("jj")) return
224
- await cleanupTempDir(root)
225
- root = await createTempDir()
226
- const remote = join(root, "remote.git")
227
- const seed = join(root, "seed")
228
- const repository = join(root, "repos/agency")
229
- const checkout = join(root, "tasks/example/code/agency")
230
- await mkdir(join(root, "repos"), { recursive: true })
231
- await write(root, "agency.json", '{"version":2,"vcs":"jj"}\n')
232
- await run(root, ["git", "init", "--bare", "--initial-branch=main", remote])
233
- await run(root, ["git", "init", "--initial-branch=main", seed])
234
- await run(seed, ["git", "config", "user.email", "test@example.com"])
235
- await run(seed, ["git", "config", "user.name", "Test"])
236
- await Bun.write(join(seed, "README.md"), "example\n")
237
- await run(seed, ["git", "add", "README.md"])
238
- await run(seed, ["git", "commit", "-m", "initial"])
239
- await run(seed, ["git", "remote", "add", "origin", remote])
240
- await run(seed, ["git", "push", "origin", "main"])
241
- await run(root, ["jj", "git", "clone", "--no-colocate", remote, repository])
242
- await write(
222
+ test("defaults to compact while preserving explicit compact compatibility", async () => {
223
+ const result = await readContext(
243
224
  root,
244
- "tasks/example/TASK.md",
245
- `---
246
- ticketUrl: null
247
- repo: agency
248
- branch: task/example
249
- base: main
250
- pr: null
251
- status: working
252
- ---
253
-
254
- # Example
255
- `,
256
- )
257
- await mkdir(dirname(checkout), { recursive: true })
258
- await run(repository, [
259
- "jj",
260
- "workspace",
261
- "add",
262
- "--name",
263
- "task-example",
264
- "-r",
265
- "main",
266
- checkout,
267
- ])
268
- await Bun.write(join(checkout, "feature.txt"), "current change\n")
269
- const current = Bun.spawn(
270
- ["jj", "log", "--no-graph", "-r", "@", "-T", "commit_id"],
271
- { cwd: checkout, stdout: "pipe", stderr: "pipe" },
272
- )
273
- const currentCommit = (await new Response(current.stdout).text()).trim()
274
- expect(await current.exited).toBe(0)
275
-
276
- const result = await readContext(root, "tasks/example")
277
- expect(result.workspace.writable).toMatchObject({
278
- branchCommit: null,
279
- checkoutCommit: currentCommit,
280
- checkoutBranch: null,
281
- detached: true,
282
- dirty: false,
283
- })
284
- expect(result.workspace.warnings).toContain(
285
- `Unable to resolve branch 'task/example' in ${repository}`,
225
+ "tasks/agent-contract/phases/context-command/code/agency",
286
226
  )
287
- })
288
-
289
- test("makes compact projection explicit without omitting essential identity", async () => {
290
- const result = await readContext(
227
+ const explicit = await readContext(
291
228
  root,
292
229
  "tasks/agent-contract/phases/context-command/code/agency",
293
230
  true,
294
231
  )
295
232
 
296
233
  expect(result.projection).toBe("compact")
234
+ expect(explicit).toEqual(result)
297
235
  expect(result.target.kind).toBe("phase")
298
236
  expect(result.documents.phase.body).toBeUndefined()
299
237
  expect(result.documents.phase.data.branch).toBe("feat/context")
@@ -441,7 +379,7 @@ status: dropped
441
379
  join(root, "archive/tasks/foundations"),
442
380
  )
443
381
 
444
- const result = await readContext(root, "foundations")
382
+ const result = await readContext(root, "foundations", false, true)
445
383
 
446
384
  expect(result.target).toMatchObject({
447
385
  kind: "task",
@@ -488,6 +426,8 @@ status: dropped
488
426
  const result = await readContext(
489
427
  root,
490
428
  "archive/tasks/agent-contract/phases/context-command",
429
+ false,
430
+ true,
491
431
  )
492
432
 
493
433
  expect(result.target).toMatchObject({
@@ -529,7 +469,7 @@ status: dropped
529
469
  )
530
470
  }
531
471
 
532
- const result = await readContext(root, "epics/contract")
472
+ const result = await readContext(root, "epics/contract", false, true)
533
473
 
534
474
  expect(result.target).toMatchObject({
535
475
  kind: "epic",
@@ -21,16 +21,16 @@ export const context = (options: ContextOptions = {}) =>
21
21
  export const help = `
22
22
  Usage: agency context [target] [options]
23
23
 
24
- Return complete, read-only context for a workbase, epic, task, or phase. The
25
- target defaults to the current directory and may be the workbase root, an entity
26
- path, or a task ID. Archived entities are inspectable but never receive mutation
27
- or execution authority. Workbase context catalogs all active epics, tasks, and
28
- phases.
24
+ Return read-only context for a workbase, epic, task, or phase. Compact output is
25
+ the default. The target defaults to the current directory and may be the workbase
26
+ root, an entity path, or a task ID. Archived entities are inspectable but never
27
+ receive mutation or execution authority. Workbase context catalogs all active
28
+ epics, tasks, and phases.
29
29
 
30
30
  Options:
31
31
  --json Output a versioned machine result
32
- --compact Omit prose bodies and low-level Git details
33
- --full Include prose bodies in root workbase context
32
+ --compact Use compact output (default)
33
+ --full Include prose bodies and low-level VCS details
34
34
  --epic <id> Select an epic
35
35
  --task <id> Select a task
36
36
  --phase <id> Select a phase together with --task
@@ -8,7 +8,6 @@ import {
8
8
  runTestEffect,
9
9
  } from "../test-utils"
10
10
  import { init } from "./init"
11
- import { preferredVersionControl } from "../workbase/version-control"
12
11
 
13
12
  describe("init command", () => {
14
13
  let parent: string
@@ -27,7 +26,7 @@ describe("init command", () => {
27
26
 
28
27
  expect(await Bun.file(join(root, "agency.json")).json()).toEqual({
29
28
  version: 2,
30
- vcs: preferredVersionControl(),
29
+ vcs: "git",
31
30
  })
32
31
  for (const directory of ["repos", "epics", "tasks"]) {
33
32
  expect((await stat(join(root, directory))).isDirectory()).toBe(true)