@markjaquith/agency 2.69.1 → 2.71.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 CHANGED
@@ -21,6 +21,21 @@ bun install -g @markjaquith/agency
21
21
 
22
22
  For development, run `bun link` from this repository.
23
23
 
24
+ ## Local Usage Logging
25
+
26
+ Agency records privacy-safe CLI usage events locally so command journeys,
27
+ failures, and flag adoption can be analyzed. Events are stored in SQLite at
28
+ `$XDG_STATE_HOME/agency/usage.sqlite3` (or
29
+ `~/.local/state/agency/usage.sqlite3`) and retained for 90 days by default.
30
+ Each event contains the normalized command path, flag names, timing, outcome,
31
+ Agency version, and ordered `AGENCY_SESSION_ID` correlation. Raw arguments,
32
+ flag values, free-form input, and the current directory are never recorded.
33
+
34
+ Export events as JSON Lines with `agency usage export`. Set
35
+ `AGENCY_NO_USAGE_LOG=1` to opt out, `AGENCY_USAGE_RETENTION_DAYS` to change
36
+ retention, or `AGENCY_USAGE_DB` to select a different database path. Logging is
37
+ best effort and never changes command output or exit behavior.
38
+
24
39
  ## Core Model
25
40
 
26
41
  - A **workbase** is the root containing durable documents and local repository
@@ -56,8 +71,6 @@ workbase/
56
71
  tui.jsonc # managed TUI plugin registration
57
72
  plugins/agency-repository-skills.ts # managed workbase access and checkout skills
58
73
  tui/agency-debug.ts # managed /agency-debug TUI diagnostic
59
- .pi/
60
- extensions/agency-workbase.ts # managed workbase context and checkout skills
61
74
  agency.json # tracked config and portable repository declarations
62
75
  repos/ # ignored local materializations
63
76
  frontend/ # bare Git repository or symlink
@@ -86,8 +99,8 @@ workbase/
86
99
 
87
100
  Agency keeps discovery and other observational commands read-only. Run
88
101
  `agency integration status` to inspect `.agency/AGENTS.md`, the managed
89
- OpenCode configuration and plugins, and `.pi/extensions/agency-workbase.ts`,
90
- then `agency integration sync` to create missing files or refresh checksum-safe
102
+ OpenCode configuration and plugins, then `agency integration sync` to create
103
+ missing files or refresh checksum-safe
91
104
  managed files. Customized files are reported but never overwritten. Sync also
92
105
  removes checksum-valid retired managed artifacts while
93
106
  preserving customized files at their former paths. The root
@@ -128,15 +141,15 @@ The plugin grants whole-workbase access dynamically, while the portable
128
141
  reference advertises that context to agents. Bash and Agency operations must
129
142
  still follow the write authority reported by `agency context`.
130
143
 
131
- Pi discovers the managed project extension automatically when launched from
132
- the workbase root after the project is trusted. When launching Pi from a task,
133
- phase, or epic directory, pass the managed file with `--extension`, for example
134
- `pi -e /path/to/workbase/.pi/extensions/agency-workbase.ts`. The extension loads
135
- the managed Agency instructions into Pi's system prompt, advertises the complete
136
- workbase, and exposes skills from the writable checkout's `.claude/skills`,
137
- `.agents/skills`, `.opencode/{skill,skills}`, and `.pi/skills` directories.
138
- Agency context still determines write authority; reference checkouts remain
139
- read-only.
144
+ Agency's package lifecycle installs one Pi extension at
145
+ `~/.pi/agent/extensions/agency.ts`. Pi loads it globally, so it works from
146
+ workbase roots and nested epic, task, phase, or checkout directories without
147
+ project trust or an explicit `--extension` flag. Outside an Agency workbase it
148
+ registers no resources and changes no prompt. Inside one, it loads the managed
149
+ Agency instructions, advertises the complete workbase, and exposes skills from
150
+ the writable checkout's `.claude/skills`, `.agents/skills`,
151
+ `.opencode/{skill,skills}`, and `.pi/skills` directories. `agency context`
152
+ remains the authority for writes; reference checkouts remain read-only.
140
153
 
141
154
  Repository aliases, the version-control backend, and canonical fetch remotes are
142
155
  declared in tracked `agency.json`; local clones and symlinks remain ignored under
@@ -434,11 +447,11 @@ working directory so the workbase `AGENTS.md` and managed OpenCode config are
434
447
  discovered normally.
435
448
  Agency's managed OpenCode plugin grants the active workbase external-directory
436
449
  access and adds existing checkout-local `.claude/skills`, `.agents/skills`, and
437
- `.opencode/{skill,skills}` directories to `skills.paths`. The managed Pi
450
+ `.opencode/{skill,skills}` directories to `skills.paths`. The global Pi
438
451
  extension provides equivalent whole-workbase context and additionally discovers
439
452
  checkout-local `.pi/skills` through Pi's `resources_discover` lifecycle.
440
- `agency work` supplies the checkout directly; plain OpenCode launches and Pi
441
- launches with the managed extension loaded resolve a materialized execution-unit
453
+ `agency work` supplies the checkout directly; plain OpenCode and Pi launches
454
+ resolve a materialized execution-unit
442
455
  checkout through `agency context`. A multi-phase
443
456
  task root has no single checkout, so launch from its phase directory when using
444
457
  plain OpenCode or Pi. Other checkout-local configuration is not composed.
package/cli-main.ts CHANGED
@@ -67,6 +67,7 @@ import {
67
67
  successEnvelope,
68
68
  writeEnvelope,
69
69
  } from "./src/protocol"
70
+ import { exportUsageEvents, recordUsageEvent } from "./src/usage-log"
70
71
 
71
72
  // Create CLI layer with all services
72
73
  const CliLayer = Layer.mergeAll(
@@ -176,6 +177,12 @@ const VERSION = packageJson.version
176
177
 
177
178
  // Define commands
178
179
  const commands: Record<string, Command> = {
180
+ usage: {
181
+ run: async () => {
182
+ for (const event of await exportUsageEvents())
183
+ console.log(JSON.stringify(event))
184
+ },
185
+ },
179
186
  act: {
180
187
  run: async (args: string[], options: Record<string, any>) => {
181
188
  if (options.help) return console.log(actHelp)
@@ -785,6 +792,7 @@ Commands:
785
792
  init [path] Initialize an Agency workbase
786
793
  workbase <subcommand> Manage registered workbases
787
794
  integration <command> Inspect or sync managed integration files
795
+ usage export Export local usage events as JSON Lines
788
796
  epic <subcommand> Manage epics
789
797
  phase <subcommand> Manage task phases
790
798
  claim <task> [phase] Claim an execution unit
@@ -830,9 +838,30 @@ const machineMode = process.argv
830
838
  .slice(2)
831
839
  .some((argument) => argument === "--json" || argument === "--jsonl")
832
840
 
841
+ const invocationStartedAt = performance.now()
842
+ const rawArguments = process.argv.slice(2)
843
+ let usageCommandPath = "invalid"
844
+ let usageFlagNames = rawArguments
845
+ .filter((argument) => argument.startsWith("--"))
846
+ .map((argument) => argument.slice(2).split("=", 1)[0]!)
847
+
833
848
  try {
834
- const args = process.argv.slice(2)
835
- const { commandName, args: commandArgs, passthrough, values } = parseCli(args)
849
+ const {
850
+ commandName,
851
+ args: commandArgs,
852
+ passthrough,
853
+ values,
854
+ } = parseCli(rawArguments)
855
+ usageCommandPath =
856
+ [commandName, commandArgs[0]]
857
+ .filter(
858
+ (part): part is string =>
859
+ typeof part === "string" && part.length > 0 && !part.startsWith("-"),
860
+ )
861
+ .join("/") || "root"
862
+ usageFlagNames = Object.entries(values)
863
+ .filter(([, value]) => value !== undefined && value !== false)
864
+ .map(([name]) => name)
836
865
 
837
866
  // Handle global flags
838
867
  if (values.version) {
@@ -841,6 +870,16 @@ try {
841
870
  } else {
842
871
  console.log(`v${VERSION}`)
843
872
  }
873
+ await recordUsageEvent(
874
+ {
875
+ commandPath: "version",
876
+ flagNames: usageFlagNames,
877
+ durationMs: performance.now() - invocationStartedAt,
878
+ outcome: "success",
879
+ exitStatus: 0,
880
+ },
881
+ VERSION,
882
+ )
844
883
  process.exit(0)
845
884
  }
846
885
 
@@ -848,7 +887,18 @@ try {
848
887
  // Show help if no command
849
888
  if (!commandName) {
850
889
  showMainHelp()
851
- process.exit(values.help ? 0 : 1)
890
+ const exitStatus = values.help ? 0 : 1
891
+ await recordUsageEvent(
892
+ {
893
+ commandPath: "help",
894
+ flagNames: usageFlagNames,
895
+ durationMs: performance.now() - invocationStartedAt,
896
+ outcome: exitStatus === 0 ? "success" : "failure",
897
+ exitStatus,
898
+ },
899
+ VERSION,
900
+ )
901
+ process.exit(exitStatus)
852
902
  }
853
903
 
854
904
  const command = commands[commandName]!
@@ -880,7 +930,28 @@ try {
880
930
  passthrough,
881
931
  })
882
932
  }
933
+ const exitStatus = Number(process.exitCode ?? 0)
934
+ await recordUsageEvent(
935
+ {
936
+ commandPath: usageCommandPath,
937
+ flagNames: usageFlagNames,
938
+ durationMs: performance.now() - invocationStartedAt,
939
+ outcome: exitStatus === 0 ? "success" : "failure",
940
+ exitStatus,
941
+ },
942
+ VERSION,
943
+ )
883
944
  } catch (error) {
945
+ await recordUsageEvent(
946
+ {
947
+ commandPath: usageCommandPath,
948
+ flagNames: usageFlagNames,
949
+ durationMs: performance.now() - invocationStartedAt,
950
+ outcome: "failure",
951
+ exitStatus: 1,
952
+ },
953
+ VERSION,
954
+ )
884
955
  if (machineMode) {
885
956
  writeEnvelope(errorEnvelope(error))
886
957
  process.exit(1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "2.69.1",
3
+ "version": "2.71.0",
4
4
  "description": "Manage agentic work across repositories with durable workbases",
5
5
  "keywords": [
6
6
  "agents",
@@ -22,6 +22,8 @@
22
22
  "cli.ts",
23
23
  "cli-main.ts",
24
24
  "src",
25
+ "pi-extensions",
26
+ "scripts/install-pi-extension.ts",
25
27
  "schemas",
26
28
  "fixtures/protocol",
27
29
  "README.md",
@@ -61,6 +63,8 @@
61
63
  "tag": "latest"
62
64
  },
63
65
  "scripts": {
66
+ "postinstall": "bun scripts/install-pi-extension.ts install",
67
+ "preuninstall": "bun scripts/install-pi-extension.ts uninstall",
64
68
  "benchmark:sync": "bun scripts/benchmark-sync.ts",
65
69
  "test": "find src \\( -name '*.test.ts' -o -name '*.test.tsx' \\) -print0 | xargs -0 -n 1 -P 4 bun test",
66
70
  "test:opencode": "AGENCY_TEST_OPENCODE=1 bun test src/cli.test.ts --test-name-pattern 'provides effective whole-workbase OpenCode access'",
@@ -0,0 +1,117 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test"
2
+ import { mkdir } from "node:fs/promises"
3
+ import { join } from "node:path"
4
+ import { cleanupTempDir, createTempDir } from "../src/test-utils"
5
+ import agencyExtension from "./agency"
6
+
7
+ describe("global Pi extension", () => {
8
+ let root: string
9
+
10
+ beforeEach(async () => {
11
+ root = await createTempDir()
12
+ })
13
+
14
+ afterEach(async () => cleanupTempDir(root))
15
+
16
+ const load = (response?: object) => {
17
+ const handlers = new Map<string, (...args: any[]) => any>()
18
+ const pi = {
19
+ exec: async () => ({
20
+ code: response ? 0 : 1,
21
+ stdout: response ? JSON.stringify(response) : "",
22
+ stderr: "",
23
+ }),
24
+ on: (name: string, handler: (...args: any[]) => any) => {
25
+ handlers.set(name, handler)
26
+ },
27
+ }
28
+ agencyExtension(pi as never)
29
+ return handlers
30
+ }
31
+
32
+ test("no-ops outside Agency workbases without invoking the CLI", async () => {
33
+ let executions = 0
34
+ const handlers = new Map<string, (...args: any[]) => any>()
35
+ agencyExtension({
36
+ exec: async () => {
37
+ executions += 1
38
+ return { code: 1, stdout: "", stderr: "" }
39
+ },
40
+ on: (name: string, handler: (...args: any[]) => any) => {
41
+ handlers.set(name, handler)
42
+ },
43
+ } as never)
44
+
45
+ expect(
46
+ await handlers.get("resources_discover")?.({ cwd: root }),
47
+ ).toBeUndefined()
48
+ expect(
49
+ await handlers.get("before_agent_start")?.(
50
+ { systemPrompt: "Base" },
51
+ { cwd: root },
52
+ ),
53
+ ).toBeUndefined()
54
+ expect(executions).toBe(0)
55
+ })
56
+
57
+ test("uses CLI context from nested directories for skills and authority", async () => {
58
+ const task = join(root, "tasks", "example")
59
+ const nested = join(task, "code", "agency", "src")
60
+ const checkout = join(task, "code", "agency")
61
+ await Bun.write(join(root, "agency.json"), '{"version":2}\n')
62
+ await mkdir(join(root, ".agency"), { recursive: true })
63
+ await Bun.write(
64
+ join(root, ".agency", "AGENTS.md"),
65
+ "# Managed instructions\n",
66
+ )
67
+ await mkdir(join(checkout, ".agents", "skills"), { recursive: true })
68
+ await mkdir(nested, { recursive: true })
69
+
70
+ const handlers = load({
71
+ ok: true,
72
+ result: {
73
+ workbase: { root },
74
+ target: {
75
+ kind: "task",
76
+ taskId: "example",
77
+ path: join(task, "TASK.md"),
78
+ },
79
+ authority: { mode: "execution", writable: { checkoutPath: checkout } },
80
+ documents: { task: { data: { status: "working" } } },
81
+ validation: { valid: true },
82
+ },
83
+ })
84
+ const resources = await handlers.get("resources_discover")?.({
85
+ cwd: nested,
86
+ })
87
+ expect(resources.skillPaths).toEqual([join(checkout, ".agents", "skills")])
88
+
89
+ const prompt = await handlers.get("before_agent_start")?.(
90
+ { systemPrompt: "Base" },
91
+ { cwd: nested },
92
+ )
93
+ expect(prompt.systemPrompt).toContain("# Managed instructions")
94
+ expect(prompt.systemPrompt).toContain(
95
+ `${checkout} as the default implementation directory`,
96
+ )
97
+ })
98
+
99
+ test("rejects CLI context for a different workbase", async () => {
100
+ await Bun.write(join(root, "agency.json"), '{"version":2}\n')
101
+ const handlers = load({
102
+ ok: true,
103
+ result: {
104
+ workbase: { root: join(root, "other") },
105
+ target: { kind: "epic", epicId: "example" },
106
+ validation: { valid: true },
107
+ },
108
+ })
109
+
110
+ expect(
111
+ await handlers.get("before_agent_start")?.(
112
+ { systemPrompt: "Base" },
113
+ { cwd: root },
114
+ ),
115
+ ).toBeUndefined()
116
+ })
117
+ })
@@ -0,0 +1,141 @@
1
+ import { existsSync, readFileSync } from "node:fs"
2
+ import { dirname, join, resolve } from "node:path"
3
+
4
+ type ExtensionAPI = {
5
+ exec: (
6
+ command: string,
7
+ args: string[],
8
+ options: { timeout: number },
9
+ ) => Promise<{ code: number; stdout: string }>
10
+ on: (
11
+ name: "resources_discover" | "before_agent_start",
12
+ handler: (...args: any[]) => unknown,
13
+ ) => void
14
+ }
15
+
16
+ type AgencyContext = {
17
+ root?: string
18
+ checkout?: string
19
+ target?: string
20
+ }
21
+
22
+ const contextTarget = (result: Record<string, any>): string | undefined => {
23
+ const target = result.target
24
+ if (target?.kind === "epic") return `epic:${target.epicId}`
25
+ if (target?.kind === "phase") {
26
+ return `execution-unit:phase/${target.taskId}/${target.phaseId}`
27
+ }
28
+ if (target?.kind === "task") {
29
+ return result.authority?.mode === "execution"
30
+ ? `execution-unit:task/${target.taskId}`
31
+ : `task:${target.taskId}`
32
+ }
33
+ }
34
+
35
+ const discoverWorkbase = (directory: string) => {
36
+ let current = resolve(directory)
37
+ while (true) {
38
+ if (existsSync(join(current, "agency.json"))) return current
39
+ const parent = dirname(current)
40
+ if (parent === current) return
41
+ current = parent
42
+ }
43
+ }
44
+
45
+ const agencyContext = async (
46
+ pi: ExtensionAPI,
47
+ directory: string,
48
+ ): Promise<AgencyContext | undefined> => {
49
+ const root = discoverWorkbase(directory)
50
+ if (!root) return
51
+
52
+ const command = await pi.exec(
53
+ "agency",
54
+ ["context", directory, "--compact", "--json"],
55
+ { timeout: 5000 },
56
+ )
57
+ if (command.code !== 0) return
58
+
59
+ const envelope = JSON.parse(command.stdout)
60
+ if (envelope.ok !== true) return
61
+ const result = envelope.result ?? {}
62
+ const target = contextTarget(result)
63
+ const status =
64
+ result.documents?.phase?.data?.status ??
65
+ result.documents?.task?.data?.status
66
+ if (
67
+ result.validation?.valid !== true ||
68
+ result.workbase?.root !== root ||
69
+ !target ||
70
+ (target.startsWith("execution-unit:") &&
71
+ (!result.authority?.writable?.checkoutPath || status !== "working"))
72
+ )
73
+ return
74
+
75
+ return {
76
+ root,
77
+ checkout: result.authority?.writable?.checkoutPath,
78
+ target,
79
+ }
80
+ }
81
+
82
+ export default function agencyExtension(pi: ExtensionAPI) {
83
+ const contexts = new Map<string, Promise<AgencyContext | undefined>>()
84
+ const runtimeContext = (directory: string) => {
85
+ const key = resolve(directory)
86
+ let context = contexts.get(key)
87
+ if (!context) {
88
+ context = agencyContext(pi, key).catch(() => undefined)
89
+ contexts.set(key, context)
90
+ }
91
+ return context
92
+ }
93
+
94
+ pi.on("resources_discover", async (event: { cwd: string }) => {
95
+ const context = await runtimeContext(event.cwd)
96
+ if (!context?.checkout) return
97
+
98
+ const skillPaths = [
99
+ join(context.checkout, ".claude", "skills"),
100
+ join(context.checkout, ".agents", "skills"),
101
+ join(context.checkout, ".opencode", "skill"),
102
+ join(context.checkout, ".opencode", "skills"),
103
+ join(context.checkout, ".pi", "skills"),
104
+ ].filter(existsSync)
105
+ if (skillPaths.length > 0) return { skillPaths: [...new Set(skillPaths)] }
106
+ })
107
+
108
+ pi.on(
109
+ "before_agent_start",
110
+ async (event: { systemPrompt: string }, ctx: { cwd: string }) => {
111
+ const context = await runtimeContext(ctx.cwd)
112
+ if (!context?.root) return
113
+
114
+ const instructionsPath = join(context.root, ".agency", "AGENTS.md")
115
+ const instructions = existsSync(instructionsPath)
116
+ ? readFileSync(instructionsPath, "utf8").trim()
117
+ : undefined
118
+ const activeTarget = process.env.AGENCY_TARGET
119
+ const worker =
120
+ activeTarget && context.target === activeTarget
121
+ ? `Agency verified this Pi session as the active worker for ${activeTarget}. Perform the assigned work directly. Do not invoke agency work for this target or launch a replacement worker.`
122
+ : undefined
123
+ const access = `The complete Agency workbase is available at ${context.root}. Use absolute paths under that root when workbase context is needed. Agency context remains the authority for writes.`
124
+ const implementation = context.checkout
125
+ ? `Pi remains rooted in the task or phase directory for Agency instructions and context. Treat ${context.checkout} as the default implementation directory for source reads, edits, repository status, builds, tests, formatting, and other repository-local commands. Run Agency lifecycle and context commands from the task or phase directory. Any reference checkouts reported by Agency context are read-only.`
126
+ : undefined
127
+
128
+ return {
129
+ systemPrompt: [
130
+ event.systemPrompt,
131
+ instructions,
132
+ access,
133
+ worker,
134
+ implementation,
135
+ ]
136
+ .filter(Boolean)
137
+ .join("\n\n"),
138
+ }
139
+ },
140
+ )
141
+ }
@@ -0,0 +1,25 @@
1
+ import { cp, mkdir, rm } from "node:fs/promises"
2
+ import { homedir } from "node:os"
3
+ import { dirname, join } from "node:path"
4
+
5
+ export const piExtensionPath = (home = homedir()) =>
6
+ join(home, ".pi", "agent", "extensions", "agency.ts")
7
+
8
+ export const installPiExtension = async (
9
+ source = join(import.meta.dir, "..", "pi-extensions", "agency.ts"),
10
+ destination = piExtensionPath(),
11
+ ) => {
12
+ await mkdir(dirname(destination), { recursive: true })
13
+ await cp(source, destination)
14
+ }
15
+
16
+ export const uninstallPiExtension = async (destination = piExtensionPath()) => {
17
+ await rm(destination, { force: true })
18
+ }
19
+
20
+ if (import.meta.main) {
21
+ const command = process.argv[2] ?? "install"
22
+ if (command === "install") await installPiExtension()
23
+ else if (command === "uninstall") await uninstallPiExtension()
24
+ else throw new Error(`Unknown Pi extension lifecycle command: ${command}`)
25
+ }
package/src/cli-parser.ts CHANGED
@@ -136,6 +136,17 @@ const nonPrCompletionOptions = {
136
136
  } satisfies OptionConfig
137
137
 
138
138
  const commands = {
139
+ usage: {
140
+ usage: "agency usage export",
141
+ options: commonOptions,
142
+ subcommands: {
143
+ export: {
144
+ usage: "agency usage export",
145
+ minArgs: 0,
146
+ maxArgs: 0,
147
+ },
148
+ },
149
+ },
139
150
  init: {
140
151
  usage: "agency init [path] [--json]",
141
152
  options: outputOptions,
package/src/cli.test.ts CHANGED
@@ -36,6 +36,8 @@ async function runCli(
36
36
  env: {
37
37
  ...process.env,
38
38
  XDG_CONFIG_HOME: isolatedConfigHome,
39
+ XDG_STATE_HOME: isolatedConfigHome,
40
+ AGENCY_NO_USAGE_LOG: "1",
39
41
  ...env,
40
42
  },
41
43
  stdout: "pipe",
@@ -170,6 +172,44 @@ describe("CLI", () => {
170
172
  expect(taggedError.stderr).not.toContain("An error has occurred")
171
173
  })
172
174
 
175
+ test("records successful and failed invocations without argument values", async () => {
176
+ const state = await createTempDir()
177
+ tempDirs.push(state)
178
+ const env = {
179
+ XDG_STATE_HOME: state,
180
+ AGENCY_SESSION_ID: "cli-session",
181
+ AGENCY_NO_USAGE_LOG: "0",
182
+ }
183
+ expect((await runCli(["--version"], projectRoot, env)).exitCode).toBe(0)
184
+ expect(
185
+ (await runCli(["unknown", "--cwd", "/private/value"], projectRoot, env))
186
+ .exitCode,
187
+ ).toBe(1)
188
+
189
+ const exported = await runCli(["usage", "export"], projectRoot, env)
190
+ expect(exported).toMatchObject({ exitCode: 0, stderr: "" })
191
+ const events = exported.stdout
192
+ .trim()
193
+ .split("\n")
194
+ .map((line) => JSON.parse(line))
195
+ expect(events).toEqual([
196
+ expect.objectContaining({
197
+ sessionId: "cli-session",
198
+ sessionSequence: 1,
199
+ commandPath: "version",
200
+ flagNames: ["version"],
201
+ outcome: "success",
202
+ }),
203
+ expect.objectContaining({
204
+ sessionSequence: 2,
205
+ commandPath: "invalid",
206
+ flagNames: ["cwd"],
207
+ outcome: "failure",
208
+ }),
209
+ ])
210
+ expect(exported.stdout).not.toContain("/private/value")
211
+ })
212
+
173
213
  test("coordinates claims through revision-guarded machine commands", async () => {
174
214
  const root = await createTempDir()
175
215
  tempDirs.push(root)
@@ -642,7 +682,6 @@ exit 23
642
682
  { name: "opencode-plugin", state: "managed" },
643
683
  { name: "opencode-tui", state: "managed" },
644
684
  { name: "opencode-tui-plugin", state: "managed" },
645
- { name: "pi-extension", state: "managed" },
646
685
  ])
647
686
 
648
687
  const synced = parseJson(
@@ -654,7 +693,6 @@ exit 23
654
693
  { name: "opencode-plugin", state: "managed", changed: false },
655
694
  { name: "opencode-tui", state: "managed", changed: false },
656
695
  { name: "opencode-tui-plugin", state: "managed", changed: false },
657
- { name: "pi-extension", state: "managed", changed: false },
658
696
  ])
659
697
  })
660
698
 
@@ -89,12 +89,9 @@ describe("init command", () => {
89
89
  expect(tuiPlugin).toContain('slashName: "agency-debug"')
90
90
  expect(tuiPlugin).toContain("api.ui.toast")
91
91
  expect(tuiPlugin).not.toContain("chat.message")
92
- const piExtension = await Bun.file(
93
- join(root, ".pi/extensions/agency-workbase.ts"),
94
- ).text()
95
- expect(piExtension).toContain('pi.on("resources_discover"')
96
- expect(piExtension).toContain('pi.on("before_agent_start"')
97
- expect(piExtension).toContain("AGENCY_WRITABLE_CHECKOUT")
92
+ expect(
93
+ await Bun.file(join(root, ".pi/extensions/agency-workbase.ts")).exists(),
94
+ ).toBe(false)
98
95
  })
99
96
 
100
97
  test("preserves existing gitignore entries", async () => {
@@ -51,11 +51,6 @@ describe("integration command", () => {
51
51
  state: "missing",
52
52
  remediation: expect.stringContaining("integration sync"),
53
53
  },
54
- {
55
- name: "pi-extension",
56
- state: "missing",
57
- remediation: expect.stringContaining("integration sync"),
58
- },
59
54
  ],
60
55
  })
61
56
  })
@@ -104,12 +99,7 @@ OpenCode TUI config: missing
104
99
  OpenCode /agency-debug: missing
105
100
  Path: .opencode/tui/agency-debug.ts
106
101
  The managed OpenCode TUI diagnostic companion needs synchronization.
107
- Action: Run 'agency integration sync' to install /agency-debug.
108
-
109
- Pi workbase extension: missing
110
- Path: .pi/extensions/agency-workbase.ts
111
- The managed Pi workbase extension needs synchronization.
112
- Action: Run 'agency integration sync' to provide workbase context and expose writable-checkout skills in Pi.`)
102
+ Action: Run 'agency integration sync' to install /agency-debug.`)
113
103
  })
114
104
 
115
105
  test("explicitly synchronizes integration files", async () => {
@@ -123,7 +113,6 @@ Pi workbase extension: missing
123
113
  { name: "opencode-plugin", state: "managed", changed: true },
124
114
  { name: "opencode-tui", state: "managed", changed: true },
125
115
  { name: "opencode-tui-plugin", state: "managed", changed: true },
126
- { name: "pi-extension", state: "managed", changed: true },
127
116
  ])
128
117
  expect(await Bun.file(join(root, "AGENTS.md")).exists()).toBe(false)
129
118
  expect(await Bun.file(join(root, ".agency/AGENTS.md")).exists()).toBe(true)
@@ -144,9 +133,6 @@ Pi workbase extension: missing
144
133
  expect(
145
134
  await Bun.file(join(root, ".opencode/tui/agency-debug.ts")).exists(),
146
135
  ).toBe(true)
147
- expect(
148
- await Bun.file(join(root, ".pi/extensions/agency-workbase.ts")).exists(),
149
- ).toBe(true)
150
136
  })
151
137
 
152
138
  test("formats integration sync results for people", async () => {
@@ -174,10 +160,6 @@ OpenCode TUI config: synced
174
160
 
175
161
  OpenCode /agency-debug: synced
176
162
  Path: .opencode/tui/agency-debug.ts
177
- Agency's managed OpenCode TUI diagnostic companion is current.
178
-
179
- Pi workbase extension: synced
180
- Path: .pi/extensions/agency-workbase.ts
181
- Agency's managed Pi extension provides whole-workbase context and exposes writable-checkout skills.`)
163
+ Agency's managed OpenCode TUI diagnostic companion is current.`)
182
164
  })
183
165
  })