@markjaquith/agency 2.47.3 → 2.48.1

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
@@ -53,7 +53,6 @@ workbase/
53
53
  .opencode/
54
54
  opencode.jsonc # managed @agency subagent, instructions, and reference
55
55
  tui.jsonc # managed TUI plugin registration
56
- command/agency.md # managed /agency workflow command
57
56
  plugin/agency-repository-skills.ts # managed workbase access and checkout skills
58
57
  tui/agency-debug.ts # managed /agency-debug TUI diagnostic
59
58
  agency.json # tracked config and portable repository declarations
@@ -84,12 +83,19 @@ workbase/
84
83
 
85
84
  Agency keeps discovery and other observational commands read-only. Run
86
85
  `agency integration status` to inspect `.agency/AGENTS.md` and
87
- `.opencode/opencode.jsonc`, `.opencode/tui.jsonc`, and their managed command and
88
- plugin files, then `agency integration sync` to create missing files or refresh
86
+ `.opencode/opencode.jsonc`, `.opencode/tui.jsonc`, and their managed plugin
87
+ files, then `agency integration sync` to create missing files or refresh
89
88
  checksum-safe managed files. Customized files are reported but never
90
- overwritten. The root
89
+ overwritten. Sync also removes checksum-valid retired managed artifacts while
90
+ preserving customized files at their former paths. The root
91
91
  `AGENTS.md` is user-owned and is not inspected or modified by Agency.
92
92
 
93
+ The managed `.agency/AGENTS.md` is the complete in-workbase operating contract
94
+ for agents. It is created by `agency init`, updated by checksum-safe integration
95
+ sync, and requires no separately installed Agency skill. CLI help remains the
96
+ source of truth for exact command syntax; this README provides the detailed
97
+ product and protocol reference.
98
+
93
99
  When upgrading an existing workbase, synchronization moves a checksum-valid
94
100
  Agency-managed root `AGENTS.md` to `.agency/AGENTS.md` once the OpenCode config
95
101
  can load the hidden file. A customized root file, including a symlink, is
@@ -116,13 +122,6 @@ The plugin grants whole-workbase access dynamically, while the portable
116
122
  reference advertises that context to agents. Bash and Agency operations must
117
123
  still follow the write authority reported by `agency context`.
118
124
 
119
- OpenCode also discovers a managed `/agency` command. Use `/agency status` for a
120
- read-only current-work summary, `/agency start [target]` to begin or resume work
121
- in the active session, `/agency next` to inspect ready work, `/agency validate`
122
- to check the workbase, and `/agency finish [target]` for verified closeout. The
123
- command uses OpenCode positional arguments internally and defaults to the safe
124
- `status` workflow when no subcommand is supplied.
125
-
126
125
  Repository aliases and canonical fetch remotes are declared in tracked
127
126
  `agency.json`; local bare clones and symlinks remain ignored under
128
127
  `repos/{alias}`. A declaration contains no local path, symlink target, checkout,
@@ -882,14 +881,10 @@ payloads are exported as `@markjaquith/agency/fixtures/protocol/success.json` an
882
881
  Success, help, and version output exit `0`; usage and command failures exit `1`.
883
882
  There are no error-specific exit statuses. `graph --jsonl` streams versioned
884
883
  records on success instead of wrapping them in an envelope; JSONL failures still
885
- use one error envelope. See `skills/agency/references/contracts.md` for revision,
886
- selector, projection, retry, and capability details.
887
-
888
- ## Agent Skill
889
-
890
- `skills/agency/SKILL.md` contains an agent-oriented operating guide for Agency.
891
- Install or link that directory into your agent's skill location when you want
892
- Agency workflows to be discovered automatically.
884
+ use one error envelope. Revision and concurrency behavior is documented under
885
+ Tasks, Phases, and Claims; selector behavior under Noninteractive Use; projection
886
+ behavior under Target Context and Workbase Graph; and retry behavior in the
887
+ machine error contract above.
893
888
 
894
889
  ## Development
895
890
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "2.47.3",
3
+ "version": "2.48.1",
4
4
  "description": "Manage agentic work across repositories with durable workbases",
5
5
  "keywords": [
6
6
  "agents",
@@ -23,7 +23,6 @@
23
23
  "src",
24
24
  "schemas",
25
25
  "fixtures/protocol",
26
- "skills",
27
26
  "README.md",
28
27
  "LICENSE"
29
28
  ],
@@ -1,6 +1,5 @@
1
1
  import { describe, expect, test } from "bun:test"
2
2
  import orchestrationRecipes from "../fixtures/protocol/orchestration-recipes.json"
3
- import skillSetupCommands from "../fixtures/protocol/skill-setup-commands.json"
4
3
  import { parseCli } from "./cli-parser"
5
4
 
6
5
  const expectUsageError = (args: string[], usage: string) => {
@@ -14,12 +13,6 @@ describe("strict CLI parsing", () => {
14
13
  }
15
14
  })
16
15
 
17
- test("accepts every documented skill setup command", () => {
18
- for (const args of skillSetupCommands) {
19
- expect(() => parseCli(args)).not.toThrow()
20
- }
21
- })
22
-
23
16
  test("rejects misspelled and command-inapplicable options", () => {
24
17
  expectUsageError(["task", "list", "--josn"], "agency task")
25
18
  expectUsageError(
package/src/cli.test.ts CHANGED
@@ -526,7 +526,6 @@ describe("CLI", () => {
526
526
  expect(before.files).toMatchObject([
527
527
  { name: "agents", state: "managed" },
528
528
  { name: "opencode", state: "managed" },
529
- { name: "opencode-command", state: "managed" },
530
529
  { name: "opencode-plugin", state: "managed" },
531
530
  { name: "opencode-tui", state: "managed" },
532
531
  { name: "opencode-tui-plugin", state: "managed" },
@@ -538,7 +537,6 @@ describe("CLI", () => {
538
537
  expect(synced.files).toMatchObject([
539
538
  { name: "agents", state: "managed", changed: false },
540
539
  { name: "opencode", state: "managed", changed: false },
541
- { name: "opencode-command", state: "managed", changed: false },
542
540
  { name: "opencode-plugin", state: "managed", changed: false },
543
541
  { name: "opencode-tui", state: "managed", changed: false },
544
542
  { name: "opencode-tui-plugin", state: "managed", changed: false },
@@ -1022,7 +1020,7 @@ status: open
1022
1020
  },
1023
1021
  {
1024
1022
  args: ["--task", "pipeline", "--phase", "build"],
1025
- cwd: join(workbaseRoot, "tasks/pipeline"),
1023
+ cwd: join(workbaseRoot, "tasks/pipeline/phases/build"),
1026
1024
  checkoutPath: join(
1027
1025
  workbaseRoot,
1028
1026
  "tasks/pipeline/phases/build/code/agency",
@@ -1060,6 +1058,16 @@ status: open
1060
1058
  launch.checkoutPath,
1061
1059
  )
1062
1060
  expect(contract.environment.OPENCODE_CONFIG_CONTENT).toBeUndefined()
1061
+ if (launch === launches[1]) {
1062
+ const context = parseJson(
1063
+ await runCli(["context", ".", "--json"], contract.cwd),
1064
+ )
1065
+ expect(context.target).toMatchObject({
1066
+ kind: "phase",
1067
+ taskId: "pipeline",
1068
+ phaseId: "build",
1069
+ })
1070
+ }
1063
1071
  const environment = {
1064
1072
  ...process.env,
1065
1073
  ...contract.environment,
@@ -34,7 +34,11 @@ describe("init command", () => {
34
34
  "/repos/\n/tasks/*/code/\n/tasks/*/phases/*/code/\n",
35
35
  )
36
36
  expect(await Bun.file(join(root, "AGENTS.md")).exists()).toBe(false)
37
- expect(await Bun.file(join(root, ".agency/AGENTS.md")).exists()).toBe(true)
37
+ const agents = await Bun.file(join(root, ".agency/AGENTS.md")).text()
38
+ expect(agents).toContain("agency context . --json")
39
+ expect(agents).toContain("## Consent Boundaries")
40
+ expect(agents).toContain("## Execution")
41
+ expect(agents).not.toContain("SKILL.md")
38
42
  const opencode = await Bun.file(
39
43
  join(root, ".opencode/opencode.jsonc"),
40
44
  ).text()
@@ -64,11 +68,9 @@ describe("init command", () => {
64
68
  workbase: expect.objectContaining({ path: ".." }),
65
69
  })
66
70
  expect(config.permission).toBeUndefined()
67
- const command = await Bun.file(
68
- join(root, ".opencode/command/agency.md"),
69
- ).text()
70
- expect(command).toContain("Workflow: `$1`")
71
- expect(command).toContain("Optional target: `$2`")
71
+ expect(
72
+ await Bun.file(join(root, ".opencode/command/agency.md")).exists(),
73
+ ).toBe(false)
72
74
  const plugin = await Bun.file(
73
75
  join(root, ".opencode/plugin/agency-repository-skills.ts"),
74
76
  ).text()
@@ -36,11 +36,6 @@ describe("integration command", () => {
36
36
  diagnostic: expect.stringContaining("cannot load"),
37
37
  remediation: expect.stringContaining("integration sync"),
38
38
  },
39
- {
40
- name: "opencode-command",
41
- state: "missing",
42
- remediation: expect.stringContaining("integration sync"),
43
- },
44
39
  {
45
40
  name: "opencode-plugin",
46
41
  state: "missing",
@@ -91,11 +86,6 @@ OpenCode config: missing
91
86
  Agency OpenCode launches cannot load current Agency instructions or whole-workbase access.
92
87
  Action: Run 'agency integration sync' to install Agency instructions and whole-workbase OpenCode access.
93
88
 
94
- OpenCode /agency command: missing
95
- Path: .opencode/command/agency.md
96
- The managed OpenCode /agency command needs synchronization.
97
- Action: Run 'agency integration sync' to install the managed /agency command.
98
-
99
89
  OpenCode workbase plugin: missing
100
90
  Path: .opencode/plugin/agency-repository-skills.ts
101
91
  The managed OpenCode workbase plugin needs synchronization.
@@ -120,7 +110,6 @@ OpenCode /agency-debug: missing
120
110
  expect(JSON.parse(logs[0]!).files).toMatchObject([
121
111
  { name: "agents", state: "managed", changed: true },
122
112
  { name: "opencode", state: "managed", changed: true },
123
- { name: "opencode-command", state: "managed", changed: true },
124
113
  { name: "opencode-plugin", state: "managed", changed: true },
125
114
  { name: "opencode-tui", state: "managed", changed: true },
126
115
  { name: "opencode-tui-plugin", state: "managed", changed: true },
@@ -132,7 +121,7 @@ OpenCode /agency-debug: missing
132
121
  ).toBe(true)
133
122
  expect(
134
123
  await Bun.file(join(root, ".opencode/command/agency.md")).exists(),
135
- ).toBe(true)
124
+ ).toBe(false)
136
125
  expect(
137
126
  await Bun.file(
138
127
  join(root, ".opencode/plugin/agency-repository-skills.ts"),
@@ -161,10 +150,6 @@ OpenCode config: synced
161
150
  Path: .opencode/opencode.jsonc
162
151
  Agency's managed OpenCode launch config is ready to load Agency instructions and provide whole-workbase read access.
163
152
 
164
- OpenCode /agency command: synced
165
- Path: .opencode/command/agency.md
166
- Agency's managed OpenCode /agency command is current.
167
-
168
153
  OpenCode workbase plugin: synced
169
154
  Path: .opencode/plugin/agency-repository-skills.ts
170
155
  Agency's managed OpenCode plugin provides whole-workbase access and exposes writable-checkout skills.
@@ -15,7 +15,6 @@ interface IntegrationResult {
15
15
  readonly name:
16
16
  | "agents"
17
17
  | "opencode"
18
- | "opencode-command"
19
18
  | "opencode-plugin"
20
19
  | "opencode-tui"
21
20
  | "opencode-tui-plugin"
@@ -30,7 +29,6 @@ interface IntegrationResult {
30
29
  const integrationNames = {
31
30
  agents: "Agent instructions",
32
31
  opencode: "OpenCode config",
33
- "opencode-command": "OpenCode /agency command",
34
32
  "opencode-plugin": "OpenCode workbase plugin",
35
33
  "opencode-tui": "OpenCode TUI config",
36
34
  "opencode-tui-plugin": "OpenCode /agency-debug",
@@ -45,6 +45,8 @@ const multiPhaseWorkspace: ExecutionWorkspace = {
45
45
  }
46
46
 
47
47
  const taskDirectory = "/workbase/tasks/example"
48
+ const phaseDirectory = `${taskDirectory}/phases/implementation`
49
+
48
50
  interface HarnessOptions {
49
51
  readonly workspace?: ExecutionWorkspace
50
52
  readonly materializeError?: Error
@@ -563,7 +565,7 @@ describe("work command", () => {
563
565
  "--prompt",
564
566
  "Start the task. Read /workbase/tasks/example/TASK.md and /workbase/tasks/example/phases/implementation/PHASE.md.",
565
567
  ],
566
- cwd: taskDirectory,
568
+ cwd: phaseDirectory,
567
569
  })
568
570
  expect(harness.statusUpdates).toEqual([
569
571
  "phase:example:implementation:working",
@@ -593,7 +595,11 @@ describe("work command", () => {
593
595
  data: {},
594
596
  }
595
597
  const harness = createHarness({
596
- workspace: multiPhaseWorkspace,
598
+ workspace: {
599
+ ...multiPhaseWorkspace,
600
+ taskPath: "/workbase/tasks/delivery/TASK.md",
601
+ phasePath: "/workbase/tasks/delivery/phases/build/PHASE.md",
602
+ },
597
603
  taskRecords: [
598
604
  {
599
605
  id: "delivery",
@@ -615,7 +621,9 @@ describe("work command", () => {
615
621
  "probe:opencode",
616
622
  "launch:opencode",
617
623
  ])
618
- expect(harness.launches[0]?.cwd).toBe(taskDirectory)
624
+ expect(harness.launches[0]?.cwd).toBe(
625
+ "/workbase/tasks/delivery/phases/build",
626
+ )
619
627
  })
620
628
 
621
629
  test("requires an explicit target when input is disabled", async () => {
@@ -767,6 +775,18 @@ describe("work command", () => {
767
775
  ])
768
776
  })
769
777
 
778
+ test("launches OpenCode in the phase directory with explicit context", async () => {
779
+ const harness = createHarness({ workspace: multiPhaseWorkspace })
780
+
781
+ await harness.run({
782
+ taskId: "example",
783
+ phaseId: "implementation",
784
+ opencode: true,
785
+ })
786
+
787
+ expect(harness.launches[0]?.cwd).toBe(phaseDirectory)
788
+ })
789
+
770
790
  test("sends the generated prompt only with --auto", async () => {
771
791
  const harness = createHarness()
772
792
 
@@ -820,7 +840,7 @@ describe("work command", () => {
820
840
  "--prompt",
821
841
  "Continue the task. Read /workbase/tasks/example/TASK.md and /workbase/tasks/example/phases/implementation/PHASE.md.",
822
842
  ],
823
- cwd: taskDirectory,
843
+ cwd: phaseDirectory,
824
844
  })
825
845
  })
826
846
 
@@ -897,6 +917,20 @@ describe("work command", () => {
897
917
  expect(printed.environment.API_TOKEN).toBeUndefined()
898
918
  })
899
919
 
920
+ test("prints the phase directory in the command contract", async () => {
921
+ const harness = createHarness({ workspace: multiPhaseWorkspace })
922
+ const output = await captureLogs(() =>
923
+ harness.run({
924
+ taskId: "example",
925
+ phaseId: "implementation",
926
+ opencode: true,
927
+ printCommand: true,
928
+ }),
929
+ )
930
+
931
+ expect(JSON.parse(output.join("\n")).cwd).toBe(phaseDirectory)
932
+ })
933
+
900
934
  test("does not reopen a forced terminal target in print-only mode", async () => {
901
935
  const harness = createHarness({ taskStatuses: { example: "done" } })
902
936
 
@@ -261,7 +261,7 @@ export const work = (
261
261
  prompt = workspace.phasePath
262
262
  ? `${action} the task. Read ${workspace.taskPath} and ${workspace.phasePath}.`
263
263
  : `${action} the task. Read ${workspace.taskPath}.`
264
- launchPath = dirname(workspace.taskPath)
264
+ launchPath = dirname(workspace.phasePath ?? workspace.taskPath)
265
265
  writablePath = workspace.writablePath
266
266
  }
267
267
 
@@ -6,10 +6,6 @@ import { dirname, join } from "node:path"
6
6
  import { pathToFileURL } from "node:url"
7
7
  import { cleanupTempDir, createTempDir, runTestEffect } from "../test-utils"
8
8
  import { managedWorkbaseAgents } from "../workbase/agents-file"
9
- import {
10
- canUpdateManagedWorkbaseOpencodeCommand,
11
- managedWorkbaseOpencodeCommand,
12
- } from "../workbase/opencode-command-file"
13
9
  import { managedWorkbaseOpencode } from "../workbase/opencode-file"
14
10
  import {
15
11
  canUpdateManagedWorkbaseOpencodePlugin,
@@ -36,6 +32,12 @@ const managed = (prefix: string, body: string, suffix = "") => {
36
32
  const managedBody = (content: string) =>
37
33
  content.slice(content.indexOf("\n\n") + 2)
38
34
 
35
+ const legacyCommand = (content: string) =>
36
+ content.replace(
37
+ /^---\n/,
38
+ `---\n# agency-managed: sha256=${createHash("sha256").update(content).digest("hex")}\n`,
39
+ )
40
+
39
41
  const status = (root: string) =>
40
42
  runTestEffect(
41
43
  IntegrationService.pipe(Effect.flatMap((service) => service.status(root))),
@@ -63,17 +65,11 @@ describe("IntegrationService", () => {
63
65
  "missing",
64
66
  "missing",
65
67
  "missing",
66
- "missing",
67
68
  ])
68
69
  expect(await Bun.file(join(root, ".agency/AGENTS.md")).exists()).toBe(false)
69
70
 
70
71
  await write(root, ".agency/AGENTS.md", managedWorkbaseAgents)
71
72
  await write(root, ".opencode/opencode.jsonc", managedWorkbaseOpencode)
72
- await write(
73
- root,
74
- ".opencode/command/agency.md",
75
- managedWorkbaseOpencodeCommand,
76
- )
77
73
  await write(
78
74
  root,
79
75
  ".opencode/plugin/agency-repository-skills.ts",
@@ -91,7 +87,6 @@ describe("IntegrationService", () => {
91
87
  "managed",
92
88
  "managed",
93
89
  "managed",
94
- "managed",
95
90
  ])
96
91
  })
97
92
 
@@ -109,7 +104,6 @@ describe("IntegrationService", () => {
109
104
  "missing",
110
105
  "missing",
111
106
  "missing",
112
- "missing",
113
107
  ])
114
108
  })
115
109
 
@@ -257,43 +251,25 @@ describe("IntegrationService", () => {
257
251
  ).toBe(true)
258
252
  })
259
253
 
260
- test("generates a positional OpenCode command for Agency workflows", () => {
261
- expect(managedWorkbaseOpencodeCommand).toContain(
262
- "description: Operate Agency work",
263
- )
264
- expect(managedWorkbaseOpencodeCommand).toContain("Workflow: `$1`")
265
- expect(managedWorkbaseOpencodeCommand).toContain("Optional target: `$2`")
266
- expect(managedWorkbaseOpencodeCommand).toContain(
267
- "Complete request: `$ARGUMENTS`",
268
- )
269
- for (const workflow of [
270
- "start",
271
- "status",
272
- "next",
273
- "validate",
274
- "finish",
275
- "help",
276
- ]) {
277
- expect(managedWorkbaseOpencodeCommand).toContain(`- \`${workflow}\`:`)
278
- }
279
- expect(managedWorkbaseOpencodeCommand).toContain("Never run `agency work`")
280
- expect(
281
- canUpdateManagedWorkbaseOpencodeCommand(managedWorkbaseOpencodeCommand),
282
- ).toBe(true)
283
- expect(
284
- canUpdateManagedWorkbaseOpencodeCommand(
285
- managedWorkbaseOpencodeCommand.replace("Workflow: `$1`", "Workflow"),
286
- ),
287
- ).toBe(false)
288
- })
289
-
290
254
  test("generates context-first safety and execution closeout guidance", () => {
291
255
  const body = managedBody(managedWorkbaseAgents)
292
256
 
293
257
  expect(body).toContain("agency context . --json")
258
+ expect(body).toContain("agency next --json")
259
+ expect(body).toContain("agency <command> --help")
294
260
  expect(body).toContain("authority.writable.checkoutPath")
295
- expect(body).toContain("`agency work` is the local launch flow")
296
- expect(body).toContain("External orchestrators claim before launching")
261
+ expect(body).toContain("Only `done` satisfies a dependency")
262
+ expect(body).toContain("Require explicit user intent")
263
+ expect(body).toContain("changing repository")
264
+ expect(body).toMatch(/archiving, restoring,\s+dropping, or/)
265
+ expect(body).toContain("Never invent entity IDs")
266
+ expect(body).toContain("Preserve parent backlinks")
267
+ expect(body).toContain("dirty-worktree, active-claim, revision")
268
+ expect(body).toContain("`agency work` is the human launch flow")
269
+ expect(body).toContain("marks execution work")
270
+ expect(body).toContain("without creating a claim")
271
+ expect(body).toContain("formatting, type checks, build, dead-code checks")
272
+ expect(body).toContain("Review and commit the diff")
297
273
  expect(body).toContain("Run `agency validate`")
298
274
  expect(body).toContain("only with explicit user intent")
299
275
  expect(body).toContain("An execution unit remains `working`")
@@ -309,7 +285,9 @@ describe("IntegrationService", () => {
309
285
  expect(body).toContain("PR state, current head, diff summary")
310
286
  expect(body).toContain("Run `agency validate` before reporting completion")
311
287
  expect(body).toContain("agency integration status")
312
- expect(body).toContain(".opencode/command/agency.md")
288
+ expect(body).not.toContain("SKILL.md")
289
+ expect(body).not.toContain("~/.agents")
290
+ expect(body).not.toContain(".opencode/command/agency.md")
313
291
  })
314
292
 
315
293
  test("configures Agency agents with complete workbase access", () => {
@@ -440,7 +418,6 @@ describe("IntegrationService", () => {
440
418
  expect(first.files).toMatchObject([
441
419
  { name: "agents", state: "customized", changed: false },
442
420
  { name: "opencode", state: "managed", changed: true },
443
- { name: "opencode-command", state: "managed", changed: true },
444
421
  { name: "opencode-plugin", state: "managed", changed: true },
445
422
  { name: "opencode-tui", state: "managed", changed: true },
446
423
  { name: "opencode-tui-plugin", state: "managed", changed: true },
@@ -455,8 +432,8 @@ describe("IntegrationService", () => {
455
432
  managedWorkbaseOpencode,
456
433
  )
457
434
  expect(
458
- await Bun.file(join(root, ".opencode/command/agency.md")).text(),
459
- ).toBe(managedWorkbaseOpencodeCommand)
435
+ await Bun.file(join(root, ".opencode/command/agency.md")).exists(),
436
+ ).toBe(false)
460
437
  expect(
461
438
  await Bun.file(
462
439
  join(root, ".opencode/plugin/agency-repository-skills.ts"),
@@ -480,12 +457,37 @@ describe("IntegrationService", () => {
480
457
  )
481
458
  })
482
459
 
460
+ test("upgrades prior managed instructions without changing owner instructions", async () => {
461
+ const ownerInstructions = "# User-owned workbase instructions\n"
462
+ const priorInstructions = managed(
463
+ "<!-- agency-managed: sha256=",
464
+ "# Previous Agency instructions\n",
465
+ " -->",
466
+ )
467
+ await write(root, "AGENTS.md", ownerInstructions)
468
+ await write(root, ".agency/AGENTS.md", priorInstructions)
469
+
470
+ const result = await sync(root)
471
+
472
+ expect(result.files[0]).toMatchObject({
473
+ name: "agents",
474
+ state: "managed",
475
+ changed: true,
476
+ })
477
+ expect(await Bun.file(join(root, ".agency/AGENTS.md")).text()).toBe(
478
+ managedWorkbaseAgents,
479
+ )
480
+ expect(await Bun.file(join(root, "AGENTS.md")).text()).toBe(
481
+ ownerInstructions,
482
+ )
483
+ })
484
+
483
485
  test("preserves user-owned checkout-skill plugins at either supported path", async () => {
484
486
  const custom = "export default async () => ({})\n"
485
487
  await write(root, ".opencode/plugins/agency-repository-skills.ts", custom)
486
488
 
487
489
  let result = await sync(root)
488
- expect(result.files[3]).toMatchObject({
490
+ expect(result.files[2]).toMatchObject({
489
491
  name: "opencode-plugin",
490
492
  path: join(root, ".opencode/plugins/agency-repository-skills.ts"),
491
493
  state: "customized",
@@ -500,7 +502,7 @@ describe("IntegrationService", () => {
500
502
  await unlink(join(root, ".opencode/plugins/agency-repository-skills.ts"))
501
503
  await write(root, ".opencode/plugin/agency-repository-skills.ts", custom)
502
504
  result = await sync(root)
503
- expect(result.files[3]).toMatchObject({
505
+ expect(result.files[2]).toMatchObject({
504
506
  path: join(root, ".opencode/plugin/agency-repository-skills.ts"),
505
507
  state: "customized",
506
508
  changed: false,
@@ -515,14 +517,14 @@ describe("IntegrationService", () => {
515
517
  await write(root, ".opencode/tui/agency-debug.ts", customPlugin)
516
518
 
517
519
  const result = await sync(root)
518
- expect(result.files[4]).toMatchObject({
520
+ expect(result.files[3]).toMatchObject({
519
521
  name: "opencode-tui",
520
522
  path: join(root, ".opencode/tui.json"),
521
523
  state: "customized",
522
524
  changed: false,
523
525
  remediation: expect.stringContaining("plugin list"),
524
526
  })
525
- expect(result.files[5]).toMatchObject({
527
+ expect(result.files[4]).toMatchObject({
526
528
  name: "opencode-tui-plugin",
527
529
  state: "customized",
528
530
  changed: false,
@@ -535,32 +537,23 @@ describe("IntegrationService", () => {
535
537
  ).toBe(customPlugin)
536
538
  })
537
539
 
538
- test("preserves user-owned OpenCode commands at either supported path", async () => {
539
- const custom = "---\ndescription: Custom Agency command\n---\n\nCustom.\n"
540
- await write(root, ".opencode/commands/agency.md", custom)
540
+ test("removes a checksum-valid legacy command and preserves a customized file", async () => {
541
+ const path = ".opencode/command/agency.md"
542
+ const legacy = legacyCommand(
543
+ "---\ndescription: Operate Agency work\n---\n\nLegacy command.\n",
544
+ )
545
+ await write(root, path, legacy)
541
546
 
542
- let result = await sync(root)
543
- expect(result.files[2]).toMatchObject({
544
- name: "opencode-command",
545
- path: join(root, ".opencode/commands/agency.md"),
546
- state: "customized",
547
- changed: false,
548
- })
549
- expect(
550
- await Bun.file(join(root, ".opencode/command/agency.md")).exists(),
551
- ).toBe(false)
547
+ await status(root)
548
+ expect(await Bun.file(join(root, path)).text()).toBe(legacy)
552
549
 
553
- await unlink(join(root, ".opencode/commands/agency.md"))
554
- await write(root, ".opencode/command/agency.md", custom)
555
- result = await sync(root)
556
- expect(result.files[2]).toMatchObject({
557
- path: join(root, ".opencode/command/agency.md"),
558
- state: "customized",
559
- changed: false,
560
- })
561
- expect(
562
- await Bun.file(join(root, ".opencode/command/agency.md")).text(),
563
- ).toBe(custom)
550
+ await sync(root)
551
+ expect(await Bun.file(join(root, path)).exists()).toBe(false)
552
+
553
+ const custom = `${legacy}User edit.\n`
554
+ await write(root, path, custom)
555
+ await sync(root)
556
+ expect(await Bun.file(join(root, path)).text()).toBe(custom)
564
557
  })
565
558
 
566
559
  test("migrates checksum-valid root instructions", async () => {