@markjaquith/agency 2.40.1 → 2.41.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 +11 -4
- package/package.json +1 -1
- package/src/cli.test.ts +2 -0
- package/src/commands/init.test.ts +5 -0
- package/src/commands/integration.test.ts +9 -0
- package/src/commands/integration.ts +2 -2
- package/src/commands/read-only.test.ts +3 -0
- package/src/services/IntegrationService.test.ts +75 -0
- package/src/services/IntegrationService.ts +53 -2
- package/src/workbase/AGENCY_COMMAND.md +48 -0
- package/src/workbase/AGENTS.md +2 -0
- package/src/workbase/opencode-command-file.ts +30 -0
package/README.md
CHANGED
|
@@ -80,10 +80,10 @@ workbase/
|
|
|
80
80
|
|
|
81
81
|
Agency keeps discovery and other observational commands read-only. Run
|
|
82
82
|
`agency integration status` to inspect `.agency/AGENTS.md` and
|
|
83
|
-
`.opencode/opencode.jsonc`,
|
|
84
|
-
files or refresh checksum-safe managed
|
|
85
|
-
|
|
86
|
-
modified by Agency.
|
|
83
|
+
`.opencode/opencode.jsonc`, and `.opencode/command/agency.md`, then `agency
|
|
84
|
+
integration sync` to create missing files or refresh checksum-safe managed
|
|
85
|
+
files. Customized files are reported but never overwritten. The root
|
|
86
|
+
`AGENTS.md` is user-owned and is not inspected or modified by Agency.
|
|
87
87
|
|
|
88
88
|
When upgrading an existing workbase, synchronization moves a checksum-valid
|
|
89
89
|
Agency-managed root `AGENTS.md` to `.agency/AGENTS.md` once the OpenCode config
|
|
@@ -96,6 +96,13 @@ reference. OpenCode discovers that config from task and epic launch directories.
|
|
|
96
96
|
Agents receive whole-workbase visibility from that reference. Bash and Agency
|
|
97
97
|
operations must still follow the write authority reported by `agency context`.
|
|
98
98
|
|
|
99
|
+
OpenCode also discovers a managed `/agency` command. Use `/agency status` for a
|
|
100
|
+
read-only current-work summary, `/agency start [target]` to begin or resume work
|
|
101
|
+
in the active session, `/agency next` to inspect ready work, `/agency validate`
|
|
102
|
+
to check the workbase, and `/agency finish [target]` for verified closeout. The
|
|
103
|
+
command uses OpenCode positional arguments internally and defaults to the safe
|
|
104
|
+
`status` workflow when no subcommand is supplied.
|
|
105
|
+
|
|
99
106
|
Repository aliases and canonical fetch remotes are declared in tracked
|
|
100
107
|
`agency.json`; local bare clones and symlinks remain ignored under
|
|
101
108
|
`repos/{alias}`. A declaration contains no local path, symlink target, checkout,
|
package/package.json
CHANGED
package/src/cli.test.ts
CHANGED
|
@@ -526,6 +526,7 @@ 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" },
|
|
529
530
|
])
|
|
530
531
|
|
|
531
532
|
const synced = parseJson(
|
|
@@ -534,6 +535,7 @@ describe("CLI", () => {
|
|
|
534
535
|
expect(synced.files).toMatchObject([
|
|
535
536
|
{ name: "agents", state: "managed", changed: false },
|
|
536
537
|
{ name: "opencode", state: "managed", changed: false },
|
|
538
|
+
{ name: "opencode-command", state: "managed", changed: false },
|
|
537
539
|
])
|
|
538
540
|
})
|
|
539
541
|
|
|
@@ -44,6 +44,11 @@ describe("init command", () => {
|
|
|
44
44
|
workbase: expect.objectContaining({ path: ".." }),
|
|
45
45
|
})
|
|
46
46
|
expect(config.permission).toBeUndefined()
|
|
47
|
+
const command = await Bun.file(
|
|
48
|
+
join(root, ".opencode/command/agency.md"),
|
|
49
|
+
).text()
|
|
50
|
+
expect(command).toContain("Workflow: `$1`")
|
|
51
|
+
expect(command).toContain("Optional target: `$2`")
|
|
47
52
|
})
|
|
48
53
|
|
|
49
54
|
test("preserves existing gitignore entries", async () => {
|
|
@@ -36,6 +36,11 @@ 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
|
+
},
|
|
39
44
|
],
|
|
40
45
|
})
|
|
41
46
|
})
|
|
@@ -62,11 +67,15 @@ describe("integration command", () => {
|
|
|
62
67
|
expect(JSON.parse(logs[0]!).files).toMatchObject([
|
|
63
68
|
{ name: "agents", state: "managed", changed: true },
|
|
64
69
|
{ name: "opencode", state: "managed", changed: true },
|
|
70
|
+
{ name: "opencode-command", state: "managed", changed: true },
|
|
65
71
|
])
|
|
66
72
|
expect(await Bun.file(join(root, "AGENTS.md")).exists()).toBe(false)
|
|
67
73
|
expect(await Bun.file(join(root, ".agency/AGENTS.md")).exists()).toBe(true)
|
|
68
74
|
expect(
|
|
69
75
|
await Bun.file(join(root, ".opencode/opencode.jsonc")).exists(),
|
|
70
76
|
).toBe(true)
|
|
77
|
+
expect(
|
|
78
|
+
await Bun.file(join(root, ".opencode/command/agency.md")).exists(),
|
|
79
|
+
).toBe(true)
|
|
71
80
|
})
|
|
72
81
|
})
|
|
@@ -54,8 +54,8 @@ export const help = `
|
|
|
54
54
|
Usage: agency integration <subcommand>
|
|
55
55
|
|
|
56
56
|
Inspect or explicitly synchronize managed agent integration files. OpenCode
|
|
57
|
-
launches load the managed
|
|
58
|
-
|
|
57
|
+
launches load the managed instructions and project config at runtime, and expose
|
|
58
|
+
the managed /agency command, without changing Agency write authority.
|
|
59
59
|
|
|
60
60
|
Subcommands:
|
|
61
61
|
status Report file state, access diagnostics, and safe remediation
|
|
@@ -5,6 +5,10 @@ import { mkdir, stat, symlink, unlink, utimes } from "node:fs/promises"
|
|
|
5
5
|
import { dirname, join } from "node:path"
|
|
6
6
|
import { cleanupTempDir, createTempDir, runTestEffect } from "../test-utils"
|
|
7
7
|
import { managedWorkbaseAgents } from "../workbase/agents-file"
|
|
8
|
+
import {
|
|
9
|
+
canUpdateManagedWorkbaseOpencodeCommand,
|
|
10
|
+
managedWorkbaseOpencodeCommand,
|
|
11
|
+
} from "../workbase/opencode-command-file"
|
|
8
12
|
import { managedWorkbaseOpencode } from "../workbase/opencode-file"
|
|
9
13
|
import { IntegrationService } from "./IntegrationService"
|
|
10
14
|
|
|
@@ -46,14 +50,21 @@ describe("IntegrationService", () => {
|
|
|
46
50
|
expect((await status(root)).files.map(({ state }) => state)).toEqual([
|
|
47
51
|
"missing",
|
|
48
52
|
"missing",
|
|
53
|
+
"missing",
|
|
49
54
|
])
|
|
50
55
|
expect(await Bun.file(join(root, ".agency/AGENTS.md")).exists()).toBe(false)
|
|
51
56
|
|
|
52
57
|
await write(root, ".agency/AGENTS.md", managedWorkbaseAgents)
|
|
53
58
|
await write(root, ".opencode/opencode.jsonc", managedWorkbaseOpencode)
|
|
59
|
+
await write(
|
|
60
|
+
root,
|
|
61
|
+
".opencode/command/agency.md",
|
|
62
|
+
managedWorkbaseOpencodeCommand,
|
|
63
|
+
)
|
|
54
64
|
expect((await status(root)).files.map(({ state }) => state)).toEqual([
|
|
55
65
|
"managed",
|
|
56
66
|
"managed",
|
|
67
|
+
"managed",
|
|
57
68
|
])
|
|
58
69
|
})
|
|
59
70
|
|
|
@@ -68,9 +79,40 @@ describe("IntegrationService", () => {
|
|
|
68
79
|
expect((await status(root)).files.map(({ state }) => state)).toEqual([
|
|
69
80
|
"customized",
|
|
70
81
|
"drifted",
|
|
82
|
+
"missing",
|
|
71
83
|
])
|
|
72
84
|
})
|
|
73
85
|
|
|
86
|
+
test("generates a positional OpenCode command for Agency workflows", () => {
|
|
87
|
+
expect(managedWorkbaseOpencodeCommand).toContain(
|
|
88
|
+
"description: Operate Agency work",
|
|
89
|
+
)
|
|
90
|
+
expect(managedWorkbaseOpencodeCommand).toContain("Workflow: `$1`")
|
|
91
|
+
expect(managedWorkbaseOpencodeCommand).toContain("Optional target: `$2`")
|
|
92
|
+
expect(managedWorkbaseOpencodeCommand).toContain(
|
|
93
|
+
"Complete request: `$ARGUMENTS`",
|
|
94
|
+
)
|
|
95
|
+
for (const workflow of [
|
|
96
|
+
"start",
|
|
97
|
+
"status",
|
|
98
|
+
"next",
|
|
99
|
+
"validate",
|
|
100
|
+
"finish",
|
|
101
|
+
"help",
|
|
102
|
+
]) {
|
|
103
|
+
expect(managedWorkbaseOpencodeCommand).toContain(`- \`${workflow}\`:`)
|
|
104
|
+
}
|
|
105
|
+
expect(managedWorkbaseOpencodeCommand).toContain("Never run `agency work`")
|
|
106
|
+
expect(
|
|
107
|
+
canUpdateManagedWorkbaseOpencodeCommand(managedWorkbaseOpencodeCommand),
|
|
108
|
+
).toBe(true)
|
|
109
|
+
expect(
|
|
110
|
+
canUpdateManagedWorkbaseOpencodeCommand(
|
|
111
|
+
managedWorkbaseOpencodeCommand.replace("Workflow: `$1`", "Workflow"),
|
|
112
|
+
),
|
|
113
|
+
).toBe(false)
|
|
114
|
+
})
|
|
115
|
+
|
|
74
116
|
test("generates context-first safety and execution closeout guidance", () => {
|
|
75
117
|
const body = managedBody(managedWorkbaseAgents)
|
|
76
118
|
|
|
@@ -93,6 +135,7 @@ describe("IntegrationService", () => {
|
|
|
93
135
|
expect(body).toContain("PR state, current head, diff summary")
|
|
94
136
|
expect(body).toContain("Run `agency validate` before reporting completion")
|
|
95
137
|
expect(body).toContain("agency integration status")
|
|
138
|
+
expect(body).toContain(".opencode/command/agency.md")
|
|
96
139
|
})
|
|
97
140
|
|
|
98
141
|
test("grants OpenCode access to the complete workbase", () => {
|
|
@@ -167,6 +210,7 @@ describe("IntegrationService", () => {
|
|
|
167
210
|
expect(first.files).toMatchObject([
|
|
168
211
|
{ name: "agents", state: "customized", changed: false },
|
|
169
212
|
{ name: "opencode", state: "managed", changed: true },
|
|
213
|
+
{ name: "opencode-command", state: "managed", changed: true },
|
|
170
214
|
])
|
|
171
215
|
expect(await Bun.file(join(root, "AGENTS.md")).text()).toBe(
|
|
172
216
|
customRootAgents,
|
|
@@ -177,6 +221,9 @@ describe("IntegrationService", () => {
|
|
|
177
221
|
expect(await Bun.file(join(root, ".opencode/opencode.jsonc")).text()).toBe(
|
|
178
222
|
managedWorkbaseOpencode,
|
|
179
223
|
)
|
|
224
|
+
expect(
|
|
225
|
+
await Bun.file(join(root, ".opencode/command/agency.md")).text(),
|
|
226
|
+
).toBe(managedWorkbaseOpencodeCommand)
|
|
180
227
|
|
|
181
228
|
await unlink(join(root, ".agency/AGENTS.md"))
|
|
182
229
|
const second = await sync(root)
|
|
@@ -189,6 +236,34 @@ describe("IntegrationService", () => {
|
|
|
189
236
|
)
|
|
190
237
|
})
|
|
191
238
|
|
|
239
|
+
test("preserves user-owned OpenCode commands at either supported path", async () => {
|
|
240
|
+
const custom = "---\ndescription: Custom Agency command\n---\n\nCustom.\n"
|
|
241
|
+
await write(root, ".opencode/commands/agency.md", custom)
|
|
242
|
+
|
|
243
|
+
let result = await sync(root)
|
|
244
|
+
expect(result.files[2]).toMatchObject({
|
|
245
|
+
name: "opencode-command",
|
|
246
|
+
path: join(root, ".opencode/commands/agency.md"),
|
|
247
|
+
state: "customized",
|
|
248
|
+
changed: false,
|
|
249
|
+
})
|
|
250
|
+
expect(
|
|
251
|
+
await Bun.file(join(root, ".opencode/command/agency.md")).exists(),
|
|
252
|
+
).toBe(false)
|
|
253
|
+
|
|
254
|
+
await unlink(join(root, ".opencode/commands/agency.md"))
|
|
255
|
+
await write(root, ".opencode/command/agency.md", custom)
|
|
256
|
+
result = await sync(root)
|
|
257
|
+
expect(result.files[2]).toMatchObject({
|
|
258
|
+
path: join(root, ".opencode/command/agency.md"),
|
|
259
|
+
state: "customized",
|
|
260
|
+
changed: false,
|
|
261
|
+
})
|
|
262
|
+
expect(
|
|
263
|
+
await Bun.file(join(root, ".opencode/command/agency.md")).text(),
|
|
264
|
+
).toBe(custom)
|
|
265
|
+
})
|
|
266
|
+
|
|
192
267
|
test("migrates checksum-valid root instructions", async () => {
|
|
193
268
|
await write(root, "AGENTS.md", managedWorkbaseAgents)
|
|
194
269
|
|
|
@@ -10,11 +10,15 @@ import {
|
|
|
10
10
|
canUpdateManagedWorkbaseOpencode,
|
|
11
11
|
managedWorkbaseOpencode,
|
|
12
12
|
} from "../workbase/opencode-file"
|
|
13
|
+
import {
|
|
14
|
+
canUpdateManagedWorkbaseOpencodeCommand,
|
|
15
|
+
managedWorkbaseOpencodeCommand,
|
|
16
|
+
} from "../workbase/opencode-command-file"
|
|
13
17
|
|
|
14
18
|
type IntegrationFileState = "managed" | "customized" | "missing" | "drifted"
|
|
15
19
|
|
|
16
20
|
interface IntegrationFileStatus {
|
|
17
|
-
readonly name: "agents" | "opencode"
|
|
21
|
+
readonly name: "agents" | "opencode" | "opencode-command"
|
|
18
22
|
readonly path: string
|
|
19
23
|
readonly state: IntegrationFileState
|
|
20
24
|
readonly diagnostic: string
|
|
@@ -52,6 +56,25 @@ const describe = (
|
|
|
52
56
|
"Run 'agency integration sync' to install Agency instructions and whole-workbase OpenCode access.",
|
|
53
57
|
}
|
|
54
58
|
}
|
|
59
|
+
if (name === "opencode-command") {
|
|
60
|
+
return state === "managed"
|
|
61
|
+
? {
|
|
62
|
+
diagnostic: "Agency's managed OpenCode /agency command is current.",
|
|
63
|
+
remediation: null,
|
|
64
|
+
}
|
|
65
|
+
: state === "customized"
|
|
66
|
+
? {
|
|
67
|
+
diagnostic:
|
|
68
|
+
"A user-owned OpenCode /agency command is present and was preserved.",
|
|
69
|
+
remediation: null,
|
|
70
|
+
}
|
|
71
|
+
: {
|
|
72
|
+
diagnostic:
|
|
73
|
+
"The managed OpenCode /agency command needs synchronization.",
|
|
74
|
+
remediation:
|
|
75
|
+
"Run 'agency integration sync' to install the managed /agency command.",
|
|
76
|
+
}
|
|
77
|
+
}
|
|
55
78
|
|
|
56
79
|
return state === "missing" || state === "drifted"
|
|
57
80
|
? {
|
|
@@ -98,6 +121,8 @@ const inspect = (root: string) =>
|
|
|
98
121
|
const opencodeDirectory = join(root, ".opencode")
|
|
99
122
|
const opencodePath = join(opencodeDirectory, "opencode.jsonc")
|
|
100
123
|
const opencodeJsonPath = join(opencodeDirectory, "opencode.json")
|
|
124
|
+
const commandPath = join(opencodeDirectory, "command", "agency.md")
|
|
125
|
+
const pluralCommandPath = join(opencodeDirectory, "commands", "agency.md")
|
|
101
126
|
const files: IntegrationFileStatus[] = []
|
|
102
127
|
|
|
103
128
|
files.push(
|
|
@@ -135,6 +160,29 @@ const inspect = (root: string) =>
|
|
|
135
160
|
files.push(fileStatus("opencode", opencodePath, "missing"))
|
|
136
161
|
}
|
|
137
162
|
|
|
163
|
+
if ((yield* fs.readSymlinkTarget(commandPath)) !== null) {
|
|
164
|
+
files.push(fileStatus("opencode-command", commandPath, "customized"))
|
|
165
|
+
} else if (
|
|
166
|
+
(yield* fs.readSymlinkTarget(pluralCommandPath)) !== null ||
|
|
167
|
+
(yield* fs.exists(pluralCommandPath))
|
|
168
|
+
) {
|
|
169
|
+
files.push(
|
|
170
|
+
fileStatus("opencode-command", pluralCommandPath, "customized"),
|
|
171
|
+
)
|
|
172
|
+
} else if (yield* fs.exists(commandPath)) {
|
|
173
|
+
files.push(
|
|
174
|
+
classify(
|
|
175
|
+
"opencode-command",
|
|
176
|
+
commandPath,
|
|
177
|
+
yield* fs.readFile(commandPath),
|
|
178
|
+
managedWorkbaseOpencodeCommand,
|
|
179
|
+
canUpdateManagedWorkbaseOpencodeCommand,
|
|
180
|
+
),
|
|
181
|
+
)
|
|
182
|
+
} else {
|
|
183
|
+
files.push(fileStatus("opencode-command", commandPath, "missing"))
|
|
184
|
+
}
|
|
185
|
+
|
|
138
186
|
return files
|
|
139
187
|
})
|
|
140
188
|
|
|
@@ -181,9 +229,12 @@ export class IntegrationService extends Effect.Service<IntegrationService>()(
|
|
|
181
229
|
if (status.name === "agents") {
|
|
182
230
|
yield* fs.createDirectory(join(root, ".agency"))
|
|
183
231
|
yield* fs.writeFile(status.path, managedWorkbaseAgents)
|
|
184
|
-
} else {
|
|
232
|
+
} else if (status.name === "opencode") {
|
|
185
233
|
yield* fs.createDirectory(join(root, ".opencode"))
|
|
186
234
|
yield* fs.writeFile(status.path, managedWorkbaseOpencode)
|
|
235
|
+
} else {
|
|
236
|
+
yield* fs.createDirectory(join(root, ".opencode", "command"))
|
|
237
|
+
yield* fs.writeFile(status.path, managedWorkbaseOpencodeCommand)
|
|
187
238
|
}
|
|
188
239
|
}
|
|
189
240
|
files.push({
|
|
@@ -0,0 +1,48 @@
|
|
|
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]`.
|
package/src/workbase/AGENTS.md
CHANGED
|
@@ -71,6 +71,8 @@ a refinement loop, or pausing or handing off completed implementation work):
|
|
|
71
71
|
`agency integration status` reports `managed`, `drifted`, `customized`, or
|
|
72
72
|
`missing` generated files. Agency keeps these instructions in
|
|
73
73
|
`.agency/AGENTS.md`, and its managed OpenCode config loads them automatically.
|
|
74
|
+
It also installs `.opencode/command/agency.md`, which provides safe `/agency`
|
|
75
|
+
workflows for active OpenCode sessions.
|
|
74
76
|
The workbase-root `AGENTS.md`, when present, belongs entirely to the workbase
|
|
75
77
|
owner and composes with these instructions through OpenCode's normal discovery.
|
|
76
78
|
`agency integration sync` updates only missing or checksum-safe drifted managed
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
}
|