@markjaquith/agency 2.48.0 → 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,10 +83,11 @@ 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
93
  The managed `.agency/AGENTS.md` is the complete in-workbase operating contract
@@ -122,13 +122,6 @@ The plugin grants whole-workbase access dynamically, while the portable
122
122
  reference advertises that context to agents. Bash and Agency operations must
123
123
  still follow the write authority reported by `agency context`.
124
124
 
125
- OpenCode also discovers a managed `/agency` command. Use `/agency status` for a
126
- read-only current-work summary, `/agency start [target]` to begin or resume work
127
- in the active session, `/agency next` to inspect ready work, `/agency validate`
128
- to check the workbase, and `/agency finish [target]` for verified closeout. The
129
- command uses OpenCode positional arguments internally and defaults to the safe
130
- `status` workflow when no subcommand is supplied.
131
-
132
125
  Repository aliases and canonical fetch remotes are declared in tracked
133
126
  `agency.json`; local bare clones and symlinks remain ignored under
134
127
  `repos/{alias}`. A declaration contains no local path, symlink target, checkout,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "2.48.0",
3
+ "version": "2.48.1",
4
4
  "description": "Manage agentic work across repositories with durable workbases",
5
5
  "keywords": [
6
6
  "agents",
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,
@@ -68,11 +68,9 @@ describe("init command", () => {
68
68
  workbase: expect.objectContaining({ path: ".." }),
69
69
  })
70
70
  expect(config.permission).toBeUndefined()
71
- const command = await Bun.file(
72
- join(root, ".opencode/command/agency.md"),
73
- ).text()
74
- expect(command).toContain("Workflow: `$1`")
75
- expect(command).toContain("Optional target: `$2`")
71
+ expect(
72
+ await Bun.file(join(root, ".opencode/command/agency.md")).exists(),
73
+ ).toBe(false)
76
74
  const plugin = await Bun.file(
77
75
  join(root, ".opencode/plugin/agency-repository-skills.ts"),
78
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,36 +251,6 @@ 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
 
@@ -323,7 +287,7 @@ describe("IntegrationService", () => {
323
287
  expect(body).toContain("agency integration status")
324
288
  expect(body).not.toContain("SKILL.md")
325
289
  expect(body).not.toContain("~/.agents")
326
- expect(body).toContain(".opencode/command/agency.md")
290
+ expect(body).not.toContain(".opencode/command/agency.md")
327
291
  })
328
292
 
329
293
  test("configures Agency agents with complete workbase access", () => {
@@ -454,7 +418,6 @@ describe("IntegrationService", () => {
454
418
  expect(first.files).toMatchObject([
455
419
  { name: "agents", state: "customized", changed: false },
456
420
  { name: "opencode", state: "managed", changed: true },
457
- { name: "opencode-command", state: "managed", changed: true },
458
421
  { name: "opencode-plugin", state: "managed", changed: true },
459
422
  { name: "opencode-tui", state: "managed", changed: true },
460
423
  { name: "opencode-tui-plugin", state: "managed", changed: true },
@@ -469,8 +432,8 @@ describe("IntegrationService", () => {
469
432
  managedWorkbaseOpencode,
470
433
  )
471
434
  expect(
472
- await Bun.file(join(root, ".opencode/command/agency.md")).text(),
473
- ).toBe(managedWorkbaseOpencodeCommand)
435
+ await Bun.file(join(root, ".opencode/command/agency.md")).exists(),
436
+ ).toBe(false)
474
437
  expect(
475
438
  await Bun.file(
476
439
  join(root, ".opencode/plugin/agency-repository-skills.ts"),
@@ -524,7 +487,7 @@ describe("IntegrationService", () => {
524
487
  await write(root, ".opencode/plugins/agency-repository-skills.ts", custom)
525
488
 
526
489
  let result = await sync(root)
527
- expect(result.files[3]).toMatchObject({
490
+ expect(result.files[2]).toMatchObject({
528
491
  name: "opencode-plugin",
529
492
  path: join(root, ".opencode/plugins/agency-repository-skills.ts"),
530
493
  state: "customized",
@@ -539,7 +502,7 @@ describe("IntegrationService", () => {
539
502
  await unlink(join(root, ".opencode/plugins/agency-repository-skills.ts"))
540
503
  await write(root, ".opencode/plugin/agency-repository-skills.ts", custom)
541
504
  result = await sync(root)
542
- expect(result.files[3]).toMatchObject({
505
+ expect(result.files[2]).toMatchObject({
543
506
  path: join(root, ".opencode/plugin/agency-repository-skills.ts"),
544
507
  state: "customized",
545
508
  changed: false,
@@ -554,14 +517,14 @@ describe("IntegrationService", () => {
554
517
  await write(root, ".opencode/tui/agency-debug.ts", customPlugin)
555
518
 
556
519
  const result = await sync(root)
557
- expect(result.files[4]).toMatchObject({
520
+ expect(result.files[3]).toMatchObject({
558
521
  name: "opencode-tui",
559
522
  path: join(root, ".opencode/tui.json"),
560
523
  state: "customized",
561
524
  changed: false,
562
525
  remediation: expect.stringContaining("plugin list"),
563
526
  })
564
- expect(result.files[5]).toMatchObject({
527
+ expect(result.files[4]).toMatchObject({
565
528
  name: "opencode-tui-plugin",
566
529
  state: "customized",
567
530
  changed: false,
@@ -574,32 +537,23 @@ describe("IntegrationService", () => {
574
537
  ).toBe(customPlugin)
575
538
  })
576
539
 
577
- test("preserves user-owned OpenCode commands at either supported path", async () => {
578
- const custom = "---\ndescription: Custom Agency command\n---\n\nCustom.\n"
579
- 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)
580
546
 
581
- let result = await sync(root)
582
- expect(result.files[2]).toMatchObject({
583
- name: "opencode-command",
584
- path: join(root, ".opencode/commands/agency.md"),
585
- state: "customized",
586
- changed: false,
587
- })
588
- expect(
589
- await Bun.file(join(root, ".opencode/command/agency.md")).exists(),
590
- ).toBe(false)
547
+ await status(root)
548
+ expect(await Bun.file(join(root, path)).text()).toBe(legacy)
591
549
 
592
- await unlink(join(root, ".opencode/commands/agency.md"))
593
- await write(root, ".opencode/command/agency.md", custom)
594
- result = await sync(root)
595
- expect(result.files[2]).toMatchObject({
596
- path: join(root, ".opencode/command/agency.md"),
597
- state: "customized",
598
- changed: false,
599
- })
600
- expect(
601
- await Bun.file(join(root, ".opencode/command/agency.md")).text(),
602
- ).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)
603
557
  })
604
558
 
605
559
  test("migrates checksum-valid root instructions", async () => {
@@ -1,4 +1,5 @@
1
1
  import { Effect } from "effect"
2
+ import { createHash } from "node:crypto"
2
3
  import { join } from "node:path"
3
4
  import { FileSystemService } from "./FileSystemService"
4
5
  import { WorkbaseService } from "./WorkbaseService"
@@ -10,10 +11,6 @@ import {
10
11
  canUpdateManagedWorkbaseOpencode,
11
12
  managedWorkbaseOpencode,
12
13
  } from "../workbase/opencode-file"
13
- import {
14
- canUpdateManagedWorkbaseOpencodeCommand,
15
- managedWorkbaseOpencodeCommand,
16
- } from "../workbase/opencode-command-file"
17
14
  import {
18
15
  canUpdateManagedWorkbaseOpencodePlugin,
19
16
  managedWorkbaseOpencodePlugin,
@@ -33,7 +30,6 @@ interface IntegrationFileStatus {
33
30
  readonly name:
34
31
  | "agents"
35
32
  | "opencode"
36
- | "opencode-command"
37
33
  | "opencode-plugin"
38
34
  | "opencode-tui"
39
35
  | "opencode-tui-plugin"
@@ -74,25 +70,6 @@ const describe = (
74
70
  "Run 'agency integration sync' to install Agency instructions and whole-workbase OpenCode access.",
75
71
  }
76
72
  }
77
- if (name === "opencode-command") {
78
- return state === "managed"
79
- ? {
80
- diagnostic: "Agency's managed OpenCode /agency command is current.",
81
- remediation: null,
82
- }
83
- : state === "customized"
84
- ? {
85
- diagnostic:
86
- "A user-owned OpenCode /agency command is present and was preserved.",
87
- remediation: null,
88
- }
89
- : {
90
- diagnostic:
91
- "The managed OpenCode /agency command needs synchronization.",
92
- remediation:
93
- "Run 'agency integration sync' to install the managed /agency command.",
94
- }
95
- }
96
73
  if (name === "opencode-plugin") {
97
74
  return state === "managed"
98
75
  ? {
@@ -202,8 +179,6 @@ const inspect = (root: string) =>
202
179
  const opencodeJsonPath = join(opencodeDirectory, "opencode.json")
203
180
  const tuiPath = join(opencodeDirectory, "tui.jsonc")
204
181
  const tuiJsonPath = join(opencodeDirectory, "tui.json")
205
- const commandPath = join(opencodeDirectory, "command", "agency.md")
206
- const pluralCommandPath = join(opencodeDirectory, "commands", "agency.md")
207
182
  const pluginPath = join(
208
183
  opencodeDirectory,
209
184
  "plugin",
@@ -252,29 +227,6 @@ const inspect = (root: string) =>
252
227
  files.push(fileStatus("opencode", opencodePath, "missing"))
253
228
  }
254
229
 
255
- if ((yield* fs.readSymlinkTarget(commandPath)) !== null) {
256
- files.push(fileStatus("opencode-command", commandPath, "customized"))
257
- } else if (
258
- (yield* fs.readSymlinkTarget(pluralCommandPath)) !== null ||
259
- (yield* fs.exists(pluralCommandPath))
260
- ) {
261
- files.push(
262
- fileStatus("opencode-command", pluralCommandPath, "customized"),
263
- )
264
- } else if (yield* fs.exists(commandPath)) {
265
- files.push(
266
- classify(
267
- "opencode-command",
268
- commandPath,
269
- yield* fs.readFile(commandPath),
270
- managedWorkbaseOpencodeCommand,
271
- canUpdateManagedWorkbaseOpencodeCommand,
272
- ),
273
- )
274
- } else {
275
- files.push(fileStatus("opencode-command", commandPath, "missing"))
276
- }
277
-
278
230
  if ((yield* fs.readSymlinkTarget(pluginPath)) !== null) {
279
231
  files.push(fileStatus("opencode-plugin", pluginPath, "customized"))
280
232
  } else if (
@@ -348,6 +300,25 @@ const canRemoveLegacyAgents = (root: string) =>
348
300
  return canUpdateManagedWorkbaseAgents(yield* fs.readFile(path))
349
301
  })
350
302
 
303
+ const canRemoveLegacyOpencodeCommand = (root: string) =>
304
+ Effect.gen(function* () {
305
+ const fs = yield* FileSystemService
306
+ const path = join(root, ".opencode", "command", "agency.md")
307
+ if (
308
+ (yield* fs.readSymlinkTarget(path)) !== null ||
309
+ !(yield* fs.exists(path))
310
+ )
311
+ return false
312
+
313
+ const content = yield* fs.readFile(path)
314
+ const header = /^---\r?\n# agency-managed: sha256=([a-f0-9]{64})\r?\n/
315
+ const match = content.match(header)
316
+ if (!match?.[1]) return false
317
+
318
+ const canonical = content.replace(header, "---\n")
319
+ return createHash("sha256").update(canonical).digest("hex") === match[1]
320
+ })
321
+
351
322
  export class IntegrationService extends Effect.Service<IntegrationService>()(
352
323
  "IntegrationService",
353
324
  {
@@ -370,6 +341,8 @@ export class IntegrationService extends Effect.Service<IntegrationService>()(
370
341
  (status) =>
371
342
  status.name === "opencode" && status.state !== "customized",
372
343
  ) && (yield* canRemoveLegacyAgents(root))
344
+ const removeLegacyOpencodeCommand =
345
+ yield* canRemoveLegacyOpencodeCommand(root)
373
346
  const files: IntegrationSyncFile[] = []
374
347
 
375
348
  for (const status of statuses) {
@@ -382,9 +355,6 @@ export class IntegrationService extends Effect.Service<IntegrationService>()(
382
355
  } else if (status.name === "opencode") {
383
356
  yield* fs.createDirectory(join(root, ".opencode"))
384
357
  yield* fs.writeFile(status.path, managedWorkbaseOpencode)
385
- } else if (status.name === "opencode-command") {
386
- yield* fs.createDirectory(join(root, ".opencode", "command"))
387
- yield* fs.writeFile(status.path, managedWorkbaseOpencodeCommand)
388
358
  } else if (status.name === "opencode-plugin") {
389
359
  yield* fs.createDirectory(join(root, ".opencode", "plugin"))
390
360
  yield* fs.writeFile(status.path, managedWorkbaseOpencodePlugin)
@@ -411,6 +381,11 @@ export class IntegrationService extends Effect.Service<IntegrationService>()(
411
381
  }
412
382
 
413
383
  if (removeLegacyAgents) yield* fs.deleteFile(join(root, "AGENTS.md"))
384
+ if (removeLegacyOpencodeCommand) {
385
+ yield* fs.deleteFile(
386
+ join(root, ".opencode", "command", "agency.md"),
387
+ )
388
+ }
414
389
 
415
390
  return { root, files }
416
391
  }),
@@ -100,15 +100,15 @@ a refinement loop, or pausing or handing off completed implementation work):
100
100
  `agency integration status` reports `managed`, `drifted`, `customized`, or
101
101
  `missing` generated files. Agency keeps these instructions in
102
102
  `.agency/AGENTS.md`, and its managed OpenCode config loads them automatically.
103
- It also installs `.opencode/command/agency.md`, which provides safe `/agency`
104
- workflows for active OpenCode sessions, a managed server plugin that exposes
105
- skills from the authoritative writable checkout, and an explicitly registered
106
- TUI companion providing `/agency-debug` without submitting an LLM prompt.
103
+ It also installs a managed server plugin that exposes skills from the
104
+ authoritative writable checkout and an explicitly registered TUI companion
105
+ providing `/agency-debug` without submitting an LLM prompt.
107
106
  The workbase-root `AGENTS.md`, when present, belongs entirely to the workbase
108
107
  owner and composes with these instructions through OpenCode's normal discovery.
109
108
  `agency integration sync` updates only missing or checksum-safe drifted managed
110
- files and preserves user-customized files. `agency init` creates the managed
111
- files, and `agency work` reconciles them before launching an agent.
109
+ files, removes checksum-valid retired artifacts, and preserves user-customized
110
+ files. `agency init` creates the managed files, and `agency work` reconciles them
111
+ before launching an agent.
112
112
 
113
113
  OpenCode can access the complete workbase tree, but this filesystem permission
114
114
  does not expand Agency write authority beyond the checkout reported by
@@ -1,48 +0,0 @@
1
- ---
2
- description: Operate Agency work with safe start, status, next, validate, and finish workflows
3
- ---
4
-
5
- Operate the current Agency workbase using the managed Agency instructions.
6
-
7
- Invocation inputs:
8
-
9
- - Workflow: `$1`
10
- - Optional target: `$2`
11
- - Complete request: `$ARGUMENTS`
12
-
13
- Use `status` when the workflow is empty. Treat words after the optional target as
14
- additional user instructions. If the workflow is unknown, make no changes and
15
- list the supported workflows.
16
-
17
- Always follow these rules:
18
-
19
- - Run `agency context . --json` first when no target is provided. With a target,
20
- pass that target to `agency context` instead.
21
- - Use the returned document paths, readiness, authority, checkout state, claim,
22
- and validation result. Do not infer them from directory names.
23
- - Stop on validation errors, dependency blockers, an unexpected writable
24
- repository, or a conflicting active claim.
25
- - Write code only in `authority.writable.checkoutPath`; references are read-only.
26
- - Never run `agency work` to start the current agent again.
27
- - Create a pull request only when the complete request explicitly asks for one.
28
-
29
- Dispatch the workflow as follows:
30
-
31
- - `start`: Read the returned task and phase documents, inspect the writable
32
- checkout, then begin or resume the requested work in this session. Keep durable
33
- decisions current and carry the work through focused verification.
34
- - `status`: Make no changes. Summarize the target, readiness, authority, durable
35
- status, claim and PR state, checkout state, current Git changes, blockers, and
36
- the most useful next action.
37
- - `next`: Run `agency next --json`, summarize ready and blocked execution units,
38
- and recommend the highest-priority ready unit. Do not launch another agent.
39
- - `validate`: Run Agency validation for the discovered workbase. Explain every
40
- issue and, when safe and within authority, repair requested non-structural
41
- problems before validating again.
42
- - `finish`: Complete any remaining requested implementation first. Run focused
43
- repository checks, refresh the task or phase delivery context, and run
44
- `agency validate`. Finish an active claim with its current revision; otherwise
45
- update unclaimed status only when the requested outcome and delivery work are
46
- actually complete. Do not create a PR unless explicitly requested.
47
- - `help`: Make no changes. Briefly explain these workflows and the positional
48
- form `/agency <workflow> [target] [additional instructions]`.
@@ -1,30 +0,0 @@
1
- import { createHash } from "node:crypto"
2
- import commandTemplate from "./AGENCY_COMMAND.md" with { type: "text" }
3
-
4
- const managedHeaderPattern =
5
- /^---\r?\n# agency-managed: sha256=([a-f0-9]{64})\r?\n/
6
-
7
- const checksum = (content: string) =>
8
- createHash("sha256").update(content).digest("hex")
9
-
10
- const canonicalBody = commandTemplate.endsWith("\n")
11
- ? commandTemplate
12
- : `${commandTemplate}\n`
13
-
14
- const renderManagedWorkbaseOpencodeCommand = (
15
- content: string = canonicalBody,
16
- ) =>
17
- content.replace(
18
- /^---\n/,
19
- `---\n# agency-managed: sha256=${checksum(content)}\n`,
20
- )
21
-
22
- export const managedWorkbaseOpencodeCommand =
23
- renderManagedWorkbaseOpencodeCommand()
24
-
25
- export const canUpdateManagedWorkbaseOpencodeCommand = (content: string) => {
26
- const match = content.match(managedHeaderPattern)
27
- if (!match?.[1]) return false
28
-
29
- return checksum(content.replace(managedHeaderPattern, "---\n")) === match[1]
30
- }