@markjaquith/agency 2.6.0 → 2.7.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 +8 -8
- package/cli.ts +6 -4
- package/package.json +1 -1
- package/skills/agency/SKILL.md +4 -4
- package/src/commands/init.test.ts +3 -0
- package/src/commands/work.test.ts +16 -5
- package/src/commands/work.ts +32 -11
- package/src/services/WorkbaseService.test.ts +2 -2
- package/src/services/WorkbaseService.ts +4 -6
- package/src/services/WorktreeService.test.ts +45 -0
- package/src/services/WorktreeService.ts +10 -6
- package/src/workbase/opencode-file.ts +25 -16
package/README.md
CHANGED
|
@@ -230,7 +230,7 @@ agency repo link backend ~/Dev/backend
|
|
|
230
230
|
agency task new
|
|
231
231
|
|
|
232
232
|
agency validate
|
|
233
|
-
agency work refresh-copy
|
|
233
|
+
agency work tasks/refresh-copy
|
|
234
234
|
agency pr create refresh-copy
|
|
235
235
|
```
|
|
236
236
|
|
|
@@ -350,21 +350,21 @@ before moving files, refuses dirty worktrees, and preserves branches.
|
|
|
350
350
|
### Work and Pull Requests
|
|
351
351
|
|
|
352
352
|
```text
|
|
353
|
-
agency work [<
|
|
353
|
+
agency work [<directory> | --epic <epic-id>] [--opencode | --claude]
|
|
354
354
|
agency pr create <task-id> [phase-id] [--draft] [--json]
|
|
355
355
|
```
|
|
356
356
|
|
|
357
|
-
`agency work`
|
|
358
|
-
|
|
359
|
-
workbase,
|
|
357
|
+
`agency work` presents the full hierarchy in `fzf`. Pass a directory, including
|
|
358
|
+
`.` for the current directory, to infer its epic, task, or phase. Outside a
|
|
359
|
+
workbase, Agency first presents the registered workbases, then the selected
|
|
360
360
|
workbase's hierarchy. If `fzf` is not installed, Agency prints the available
|
|
361
|
-
choices and asks for an explicit
|
|
361
|
+
choices and asks for an explicit directory.
|
|
362
362
|
|
|
363
363
|
Epic and multi-phase task targets launch orchestration agents beside their
|
|
364
364
|
documents. Single-phase tasks and phases fetch repositories, create or reuse
|
|
365
365
|
worktrees under `code/`, and launch an execution agent in the writable checkout
|
|
366
|
-
with absolute context paths.
|
|
367
|
-
|
|
366
|
+
with absolute context paths. An explicit directory or `--epic` target bypasses
|
|
367
|
+
the hierarchy chooser.
|
|
368
368
|
|
|
369
369
|
Each writable `(repo, branch)` pair may belong to only one task or phase. Agency
|
|
370
370
|
validation reports duplicate ownership, and `agency work` checks Git's worktree
|
package/cli.ts
CHANGED
|
@@ -239,11 +239,13 @@ const commands: Record<string, Command> = {
|
|
|
239
239
|
console.log(workHelp)
|
|
240
240
|
return
|
|
241
241
|
}
|
|
242
|
+
if (args.length > 1) {
|
|
243
|
+
throw new Error("Usage: agency work [<directory> | --epic <epic-id>]")
|
|
244
|
+
}
|
|
242
245
|
|
|
243
246
|
await runCommand(
|
|
244
247
|
work({
|
|
245
|
-
|
|
246
|
-
phaseId: args[1],
|
|
248
|
+
directory: args[0],
|
|
247
249
|
epicId: options.epic,
|
|
248
250
|
silent: options.silent,
|
|
249
251
|
verbose: options.verbose,
|
|
@@ -299,7 +301,7 @@ Commands:
|
|
|
299
301
|
phase <subcommand> Manage task phases
|
|
300
302
|
archive <type> Archive a work item
|
|
301
303
|
task <subcommand> Manage tasks
|
|
302
|
-
work [
|
|
304
|
+
work [directory] Work on an epic, task, or phase
|
|
303
305
|
pr create Create a pull request for an execution unit
|
|
304
306
|
repo <subcommand> Manage workbase repositories
|
|
305
307
|
status Show status for the current workbase
|
|
@@ -314,7 +316,7 @@ Global Options:
|
|
|
314
316
|
Examples:
|
|
315
317
|
agency init # Initialize the current directory
|
|
316
318
|
agency task list # List tasks
|
|
317
|
-
agency work refresh-cli-copy
|
|
319
|
+
agency work tasks/refresh-cli-copy # Start working on a task
|
|
318
320
|
|
|
319
321
|
For more information about a command, run:
|
|
320
322
|
agency <command> --help
|
package/package.json
CHANGED
package/skills/agency/SKILL.md
CHANGED
|
@@ -246,15 +246,15 @@ Outside a workbase, omitting path opens the registered-workbase picker.
|
|
|
246
246
|
|
|
247
247
|
```bash
|
|
248
248
|
agency work
|
|
249
|
-
agency work <
|
|
249
|
+
agency work <directory>
|
|
250
250
|
agency work --epic <epic-id>
|
|
251
251
|
```
|
|
252
252
|
|
|
253
253
|
Use `--opencode` or `--claude` to require a specific agent. This command fetches
|
|
254
254
|
repositories for execution targets, creates or reuses their worktrees, and
|
|
255
|
-
replaces the current process with the selected agent.
|
|
256
|
-
|
|
257
|
-
|
|
255
|
+
replaces the current process with the selected agent. With no directory it opens
|
|
256
|
+
an `fzf` picker containing the workbase hierarchy. Pass `.`, or another
|
|
257
|
+
directory, to infer the nearest epic, task, or phase.
|
|
258
258
|
Outside a workbase, it first opens a picker containing registered workbases.
|
|
259
259
|
|
|
260
260
|
Epic and multi-phase task targets are orchestration sessions launched beside
|
|
@@ -39,6 +39,9 @@ describe("init command", () => {
|
|
|
39
39
|
expect(
|
|
40
40
|
await Bun.file(join(root, ".opencode/opencode.jsonc")).text(),
|
|
41
41
|
).toContain('"path": "../tasks"')
|
|
42
|
+
expect(
|
|
43
|
+
await Bun.file(join(root, ".opencode/opencode.jsonc")).text(),
|
|
44
|
+
).toContain(`"${join(root, "tasks")}/*": "allow"`)
|
|
42
45
|
})
|
|
43
46
|
|
|
44
47
|
test("preserves existing gitignore entries", async () => {
|
|
@@ -124,6 +124,7 @@ const createHarness = (options: HarnessOptions = {}) => {
|
|
|
124
124
|
},
|
|
125
125
|
}
|
|
126
126
|
const fs = {
|
|
127
|
+
isDirectory: () => Effect.succeed(true),
|
|
127
128
|
runCommand: (args: readonly string[]) => {
|
|
128
129
|
const cli = args[1] as "opencode" | "claude"
|
|
129
130
|
events.push(`probe:${cli}`)
|
|
@@ -177,7 +178,11 @@ describe("work command", () => {
|
|
|
177
178
|
test("launches an epic agent from an epic directory", async () => {
|
|
178
179
|
const harness = createHarness()
|
|
179
180
|
|
|
180
|
-
await harness.run({
|
|
181
|
+
await harness.run({
|
|
182
|
+
cwd: "/workbase/epics/delivery",
|
|
183
|
+
directory: ".",
|
|
184
|
+
opencode: true,
|
|
185
|
+
})
|
|
181
186
|
|
|
182
187
|
expect(harness.events).toEqual(["probe:opencode", "launch:opencode"])
|
|
183
188
|
expect(harness.launches[0]).toEqual({
|
|
@@ -190,7 +195,11 @@ describe("work command", () => {
|
|
|
190
195
|
test("launches a multi-phase task agent without materializing", async () => {
|
|
191
196
|
const harness = createHarness({ multiPhaseTasks: ["delivery"] })
|
|
192
197
|
|
|
193
|
-
await harness.run({
|
|
198
|
+
await harness.run({
|
|
199
|
+
cwd: "/workbase/tasks/delivery",
|
|
200
|
+
directory: ".",
|
|
201
|
+
opencode: true,
|
|
202
|
+
})
|
|
194
203
|
|
|
195
204
|
expect(harness.events).toEqual(["probe:opencode", "launch:opencode"])
|
|
196
205
|
expect(harness.launches[0]).toEqual({
|
|
@@ -205,6 +214,7 @@ describe("work command", () => {
|
|
|
205
214
|
|
|
206
215
|
await harness.run({
|
|
207
216
|
cwd: "/workbase/tasks/example/phases/implementation/code/agency/src",
|
|
217
|
+
directory: ".",
|
|
208
218
|
opencode: true,
|
|
209
219
|
})
|
|
210
220
|
|
|
@@ -224,6 +234,7 @@ describe("work command", () => {
|
|
|
224
234
|
|
|
225
235
|
await harness.run({
|
|
226
236
|
cwd: "/workbase/tasks/example/code/agency/src",
|
|
237
|
+
directory: ".",
|
|
227
238
|
opencode: true,
|
|
228
239
|
})
|
|
229
240
|
|
|
@@ -231,7 +242,7 @@ describe("work command", () => {
|
|
|
231
242
|
expect(harness.launches[0]?.cwd).toBe("/workbase/tasks/example")
|
|
232
243
|
})
|
|
233
244
|
|
|
234
|
-
test("selects a target with fzf
|
|
245
|
+
test("selects a target with fzf when no directory is provided", async () => {
|
|
235
246
|
const phase = {
|
|
236
247
|
taskId: "delivery",
|
|
237
248
|
id: "build",
|
|
@@ -254,7 +265,7 @@ describe("work command", () => {
|
|
|
254
265
|
choices.find((choice) => choice.label.includes("build"))!.target,
|
|
255
266
|
)
|
|
256
267
|
|
|
257
|
-
await harness.run({ cwd: "/workbase", opencode: true }, pick)
|
|
268
|
+
await harness.run({ cwd: "/workbase/tasks/example", opencode: true }, pick)
|
|
258
269
|
|
|
259
270
|
expect(harness.events).toEqual([
|
|
260
271
|
"probe:fzf",
|
|
@@ -448,7 +459,7 @@ describe("work command", () => {
|
|
|
448
459
|
verboseHarness.run({ taskId: "example", verbose: true }),
|
|
449
460
|
)
|
|
450
461
|
expect(verboseLogs).toEqual([
|
|
451
|
-
"Launching opencode
|
|
462
|
+
"Launching command: opencode --continue (cwd: /workbase/tasks/example)",
|
|
452
463
|
])
|
|
453
464
|
expect(verboseHarness.materializeOptions[0]?.verbose).toBe(true)
|
|
454
465
|
|
package/src/commands/work.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
} from "../workbase/workbase-choice"
|
|
24
24
|
|
|
25
25
|
interface WorkOptions extends BaseCommandOptions {
|
|
26
|
+
readonly directory?: string
|
|
26
27
|
readonly taskId?: string
|
|
27
28
|
readonly phaseId?: string
|
|
28
29
|
readonly epicId?: string
|
|
@@ -32,6 +33,15 @@ interface WorkOptions extends BaseCommandOptions {
|
|
|
32
33
|
|
|
33
34
|
type LaunchAgent = (cli: string, args: readonly string[], cwd: string) => void
|
|
34
35
|
|
|
36
|
+
const formatCommand = (args: readonly string[]) =>
|
|
37
|
+
args
|
|
38
|
+
.map((argument) =>
|
|
39
|
+
/^[A-Za-z0-9_./:=+@%-]+$/.test(argument)
|
|
40
|
+
? argument
|
|
41
|
+
: `'${argument.replaceAll("'", `'\\''`)}'`,
|
|
42
|
+
)
|
|
43
|
+
.join(" ")
|
|
44
|
+
|
|
35
45
|
const launchAgent: LaunchAgent = (cli, args, cwd) => {
|
|
36
46
|
process.chdir(cwd)
|
|
37
47
|
execvp(cli, [...args])
|
|
@@ -50,9 +60,12 @@ export const work = (
|
|
|
50
60
|
new Error("Cannot use both --opencode and --claude"),
|
|
51
61
|
)
|
|
52
62
|
}
|
|
53
|
-
if (
|
|
63
|
+
if (
|
|
64
|
+
options.epicId &&
|
|
65
|
+
(options.directory || options.taskId || options.phaseId)
|
|
66
|
+
) {
|
|
54
67
|
return yield* Effect.fail(
|
|
55
|
-
new Error("Cannot combine --epic with a task or phase ID"),
|
|
68
|
+
new Error("Cannot combine --epic with a directory, task, or phase ID"),
|
|
56
69
|
)
|
|
57
70
|
}
|
|
58
71
|
|
|
@@ -64,7 +77,13 @@ export const work = (
|
|
|
64
77
|
const phases = yield* PhaseService
|
|
65
78
|
const { log, verboseLog } = createLoggers(options)
|
|
66
79
|
const cwd = options.cwd ?? process.cwd()
|
|
67
|
-
const
|
|
80
|
+
const startPath = options.directory ? resolve(cwd, options.directory) : cwd
|
|
81
|
+
if (options.directory && !(yield* fs.isDirectory(startPath))) {
|
|
82
|
+
return yield* Effect.fail(
|
|
83
|
+
new Error(`Work directory does not exist: ${startPath}`),
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
const root = yield* resolveWorkbase(startPath, log, pickBase)
|
|
68
87
|
if (!root) return
|
|
69
88
|
|
|
70
89
|
let target: WorkTarget | null = null
|
|
@@ -89,8 +108,8 @@ export const work = (
|
|
|
89
108
|
multiPhase: "phases" in task.data,
|
|
90
109
|
}
|
|
91
110
|
}
|
|
92
|
-
} else {
|
|
93
|
-
const path = relative(root,
|
|
111
|
+
} else if (options.directory) {
|
|
112
|
+
const path = relative(root, startPath)
|
|
94
113
|
const parts =
|
|
95
114
|
!path || isAbsolute(path) || path.startsWith(`..${sep}`)
|
|
96
115
|
? []
|
|
@@ -145,7 +164,7 @@ export const work = (
|
|
|
145
164
|
for (const choice of choices) log(choice.label)
|
|
146
165
|
return yield* Effect.fail(
|
|
147
166
|
new Error(
|
|
148
|
-
"fzf is required to select a work target; install fzf or provide a
|
|
167
|
+
"fzf is required to select a work target; install fzf or provide a directory explicitly",
|
|
149
168
|
),
|
|
150
169
|
)
|
|
151
170
|
}
|
|
@@ -199,17 +218,19 @@ export const work = (
|
|
|
199
218
|
yield* tasks.setStatus(target.taskId, "working", root)
|
|
200
219
|
}
|
|
201
220
|
|
|
202
|
-
verboseLog(`Launching ${cli} in ${launchPath}`)
|
|
203
221
|
const args = cli === "opencode" ? ["--continue"] : [prompt]
|
|
222
|
+
verboseLog(
|
|
223
|
+
`Launching command: ${formatCommand([cli, ...args])} (cwd: ${launchPath})`,
|
|
224
|
+
)
|
|
204
225
|
launch(cli, [cli, ...args], launchPath)
|
|
205
226
|
})
|
|
206
227
|
|
|
207
228
|
export const help = `
|
|
208
|
-
Usage: agency work [<
|
|
229
|
+
Usage: agency work [<directory> | --epic <epic-id>]
|
|
209
230
|
|
|
210
|
-
Launch an agent for
|
|
211
|
-
|
|
212
|
-
workbase first.
|
|
231
|
+
Launch an agent for an epic, task, or phase. With no directory, select one
|
|
232
|
+
with fzf. Use '.' for the current directory. Outside a workbase, select a
|
|
233
|
+
registered workbase first.
|
|
213
234
|
|
|
214
235
|
Options:
|
|
215
236
|
--epic <id> Work on an epic
|
|
@@ -52,7 +52,7 @@ describe("WorkbaseService", () => {
|
|
|
52
52
|
managedWorkbaseAgents,
|
|
53
53
|
)
|
|
54
54
|
expect(await Bun.file(join(root, ".opencode/opencode.jsonc")).text()).toBe(
|
|
55
|
-
managedWorkbaseOpencode,
|
|
55
|
+
managedWorkbaseOpencode(root),
|
|
56
56
|
)
|
|
57
57
|
})
|
|
58
58
|
|
|
@@ -127,7 +127,7 @@ describe("WorkbaseService", () => {
|
|
|
127
127
|
)
|
|
128
128
|
|
|
129
129
|
expect(await Bun.file(join(root, ".opencode/opencode.jsonc")).text()).toBe(
|
|
130
|
-
managedWorkbaseOpencode,
|
|
130
|
+
managedWorkbaseOpencode(root),
|
|
131
131
|
)
|
|
132
132
|
})
|
|
133
133
|
|
|
@@ -142,19 +142,17 @@ const ensureWorkbaseOpencode = (root: string) =>
|
|
|
142
142
|
const fs = yield* FileSystemService
|
|
143
143
|
const directory = join(root, ".opencode")
|
|
144
144
|
const path = join(directory, "opencode.jsonc")
|
|
145
|
+
const managed = managedWorkbaseOpencode(root)
|
|
145
146
|
if (!(yield* fs.exists(path))) {
|
|
146
147
|
if (yield* fs.exists(join(directory, "opencode.json"))) return
|
|
147
148
|
yield* fs.createDirectory(directory)
|
|
148
|
-
yield* fs.writeFile(path,
|
|
149
|
+
yield* fs.writeFile(path, managed)
|
|
149
150
|
return
|
|
150
151
|
}
|
|
151
152
|
|
|
152
153
|
const content = yield* fs.readFile(path)
|
|
153
|
-
if (
|
|
154
|
-
|
|
155
|
-
canUpdateManagedWorkbaseOpencode(content)
|
|
156
|
-
) {
|
|
157
|
-
yield* fs.writeFile(path, managedWorkbaseOpencode)
|
|
154
|
+
if (content !== managed && canUpdateManagedWorkbaseOpencode(content)) {
|
|
155
|
+
yield* fs.writeFile(path, managed)
|
|
158
156
|
}
|
|
159
157
|
})
|
|
160
158
|
|
|
@@ -85,6 +85,51 @@ describe("WorktreeService", () => {
|
|
|
85
85
|
expect(new TextDecoder().decode(branch.stdout).trim()).toBe("task/example")
|
|
86
86
|
})
|
|
87
87
|
|
|
88
|
+
test("does not fetch origins for existing worktrees", async () => {
|
|
89
|
+
await runTestEffect(
|
|
90
|
+
TaskService.pipe(
|
|
91
|
+
Effect.flatMap((service) =>
|
|
92
|
+
service.create(
|
|
93
|
+
{
|
|
94
|
+
id: "existing",
|
|
95
|
+
ticketUrl: "https://example.com/task",
|
|
96
|
+
repo: "agency",
|
|
97
|
+
repos: [{ repo: "effect", ref: "main" }],
|
|
98
|
+
branch: "task/existing",
|
|
99
|
+
base: "main",
|
|
100
|
+
},
|
|
101
|
+
root,
|
|
102
|
+
),
|
|
103
|
+
),
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
await runTestEffect(
|
|
107
|
+
WorktreeService.pipe(
|
|
108
|
+
Effect.flatMap((service) =>
|
|
109
|
+
service.materialize("existing", undefined, root),
|
|
110
|
+
),
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
await git(
|
|
114
|
+
["remote", "set-url", "origin", join(root, "missing")],
|
|
115
|
+
join(root, "repos/agency"),
|
|
116
|
+
)
|
|
117
|
+
await git(
|
|
118
|
+
["remote", "set-url", "origin", join(root, "missing")],
|
|
119
|
+
join(root, "repos/effect"),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
await expect(
|
|
123
|
+
runTestEffect(
|
|
124
|
+
WorktreeService.pipe(
|
|
125
|
+
Effect.flatMap((service) =>
|
|
126
|
+
service.materialize("existing", undefined, root),
|
|
127
|
+
),
|
|
128
|
+
),
|
|
129
|
+
),
|
|
130
|
+
).resolves.toMatchObject({ repo: "agency" })
|
|
131
|
+
})
|
|
132
|
+
|
|
88
133
|
test("uses a configured worktree creation command", async () => {
|
|
89
134
|
await Bun.write(
|
|
90
135
|
join(root, "agency.json"),
|
|
@@ -136,11 +136,13 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
|
|
|
136
136
|
})
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
const fetchOrigin = Effect.gen(function* () {
|
|
140
|
+
const remote = yield* fs.runCommand(
|
|
141
|
+
["git", "-C", repositoryPath, "remote", "get-url", "origin"],
|
|
142
|
+
{ captureOutput: true },
|
|
143
|
+
)
|
|
144
|
+
if (remote.exitCode !== 0) return
|
|
145
|
+
|
|
144
146
|
const fetch = yield* fs.runCommand(
|
|
145
147
|
["git", "-C", repositoryPath, "fetch", "origin"],
|
|
146
148
|
{ captureOutput: true },
|
|
@@ -150,7 +152,7 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
|
|
|
150
152
|
message: `Failed to fetch '${alias}': ${fetch.stderr}`,
|
|
151
153
|
})
|
|
152
154
|
}
|
|
153
|
-
}
|
|
155
|
+
})
|
|
154
156
|
|
|
155
157
|
const listed = yield* fs.runCommand(
|
|
156
158
|
[
|
|
@@ -208,6 +210,7 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
|
|
|
208
210
|
message: `Worktree registry contains a missing checkout at ${checkoutPath}`,
|
|
209
211
|
})
|
|
210
212
|
}
|
|
213
|
+
yield* fetchOrigin
|
|
211
214
|
|
|
212
215
|
let args: string[]
|
|
213
216
|
let env: Record<string, string> | undefined
|
|
@@ -341,6 +344,7 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
|
|
|
341
344
|
message: `Worktree registry contains a missing checkout at ${checkoutPath}`,
|
|
342
345
|
})
|
|
343
346
|
}
|
|
347
|
+
yield* fetchOrigin
|
|
344
348
|
const result = yield* fs.runCommand(
|
|
345
349
|
[
|
|
346
350
|
"git",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto"
|
|
2
|
+
import { join } from "node:path"
|
|
2
3
|
|
|
3
4
|
const managedHeaderPattern =
|
|
4
5
|
/^\/\/ agency-managed: sha256=([a-f0-9]{64})\r?\n\r?\n/
|
|
@@ -6,28 +7,36 @@ const managedHeaderPattern =
|
|
|
6
7
|
const checksum = (content: string) =>
|
|
7
8
|
createHash("sha256").update(content).digest("hex")
|
|
8
9
|
|
|
9
|
-
const body =
|
|
10
|
-
{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
const body = (root: string) =>
|
|
11
|
+
`${JSON.stringify(
|
|
12
|
+
{
|
|
13
|
+
$schema: "https://opencode.ai/config.json",
|
|
14
|
+
references: {
|
|
15
|
+
tasks: {
|
|
16
|
+
path: "../tasks",
|
|
17
|
+
description: "Agency task definitions and execution context",
|
|
18
|
+
},
|
|
19
|
+
epics: {
|
|
20
|
+
path: "../epics",
|
|
21
|
+
description: "Agency epic definitions and orchestration context",
|
|
22
|
+
},
|
|
16
23
|
},
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
permission: {
|
|
25
|
+
external_directory: {
|
|
26
|
+
[`${join(root, "tasks")}/*`]: "allow",
|
|
27
|
+
[`${join(root, "epics")}/*`]: "allow",
|
|
28
|
+
},
|
|
20
29
|
},
|
|
21
30
|
},
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
)}\n`
|
|
31
|
+
null,
|
|
32
|
+
2,
|
|
33
|
+
)}\n`
|
|
26
34
|
|
|
27
|
-
const renderManagedWorkbaseOpencode = (content: string
|
|
35
|
+
const renderManagedWorkbaseOpencode = (content: string) =>
|
|
28
36
|
`// agency-managed: sha256=${checksum(content)}\n\n${content}`
|
|
29
37
|
|
|
30
|
-
export const managedWorkbaseOpencode =
|
|
38
|
+
export const managedWorkbaseOpencode = (root: string) =>
|
|
39
|
+
renderManagedWorkbaseOpencode(body(root))
|
|
31
40
|
|
|
32
41
|
export const canUpdateManagedWorkbaseOpencode = (content: string) => {
|
|
33
42
|
const match = content.match(managedHeaderPattern)
|