@markjaquith/agency 3.1.0 → 3.1.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 +9 -12
- package/cli-main.ts +1 -0
- package/package.json +1 -1
- package/src/cli-parser.test.ts +7 -1
- package/src/cli-parser.ts +9 -8
- package/src/cli.test.ts +1 -18
- package/src/commands/init.test.ts +1 -5
- package/src/commands/worktree.ts +2 -0
- package/src/services/IntegrationService.test.ts +10 -18
- package/src/services/WorktreeLock.test.ts +81 -2
- package/src/services/WorktreeLock.ts +42 -5
- package/src/services/WorktreeService.ts +6 -0
- package/src/workbase/AGENTS.md +2 -2
- package/src/workbase/opencode-file.ts +1 -13
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ workbase/
|
|
|
66
66
|
.agency/
|
|
67
67
|
AGENTS.md # managed Agency instructions
|
|
68
68
|
.opencode/
|
|
69
|
-
opencode.jsonc # managed
|
|
69
|
+
opencode.jsonc # managed planning agent, instructions, and reference
|
|
70
70
|
tui.jsonc # managed TUI plugin registration
|
|
71
71
|
plugins/agency-repository-skills.ts # managed workbase access and checkout skills
|
|
72
72
|
tui/agency-debug.ts # managed /agency-debug TUI diagnostic
|
|
@@ -116,17 +116,14 @@ Agency-managed root `AGENTS.md` to `.agency/AGENTS.md` once the OpenCode config
|
|
|
116
116
|
can load the hidden file. A customized root file, including a symlink, is
|
|
117
117
|
preserved as user-owned content.
|
|
118
118
|
|
|
119
|
-
The OpenCode config
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
operation.
|
|
128
|
-
When the subagent launches work in another agent, it verifies that the agent
|
|
129
|
-
started and returns without waiting for the task to finish.
|
|
119
|
+
The OpenCode config loads Agency's hidden instructions in addition to any
|
|
120
|
+
user-owned root `AGENTS.md`, advertises the complete workbase as one portable
|
|
121
|
+
reference, and replaces the built-in Plan agent with `agency-plan`. That
|
|
122
|
+
planning agent can update `TASK.md`, `PHASE.md`, and `EPIC.md`, inspect the
|
|
123
|
+
workbase through read-only Agency commands, and use explicit Agency CLI
|
|
124
|
+
permissions to create or update planning structure. Its normal research tools
|
|
125
|
+
and the complete Agency CLI remain available; managed Agency instructions and
|
|
126
|
+
reported authority govern each operation.
|
|
130
127
|
The TUI-only `/agency-debug` command reports TUI companion initialization and
|
|
131
128
|
whether the server plugin registered writable-checkout skills. It uses a native
|
|
132
129
|
toast and does not submit a prompt to an LLM. When no writable checkout skill
|
package/cli-main.ts
CHANGED
package/package.json
CHANGED
package/src/cli-parser.test.ts
CHANGED
|
@@ -664,11 +664,17 @@ describe("strict CLI parsing", () => {
|
|
|
664
664
|
"--phase",
|
|
665
665
|
"verify",
|
|
666
666
|
"--dry-run",
|
|
667
|
+
"--force",
|
|
667
668
|
]),
|
|
668
669
|
).toMatchObject({
|
|
669
670
|
commandName: "worktree",
|
|
670
671
|
args: ["rebuild", "example", "verify"],
|
|
671
|
-
values: {
|
|
672
|
+
values: {
|
|
673
|
+
task: "example",
|
|
674
|
+
phase: "verify",
|
|
675
|
+
"dry-run": true,
|
|
676
|
+
force: true,
|
|
677
|
+
},
|
|
672
678
|
})
|
|
673
679
|
expectUsageError(
|
|
674
680
|
["worktree", "inspect", "example", "--dry-run"],
|
package/src/cli-parser.ts
CHANGED
|
@@ -870,6 +870,7 @@ const commands = {
|
|
|
870
870
|
...outputOptions,
|
|
871
871
|
...entitySelectorOptions,
|
|
872
872
|
"dry-run": { type: "boolean" },
|
|
873
|
+
force: { type: "boolean" },
|
|
873
874
|
},
|
|
874
875
|
subcommands: {
|
|
875
876
|
list: {
|
|
@@ -886,31 +887,31 @@ const commands = {
|
|
|
886
887
|
},
|
|
887
888
|
prepare: {
|
|
888
889
|
usage:
|
|
889
|
-
"agency worktree prepare <task-id> [phase-id] [--dry-run] [--json]",
|
|
890
|
+
"agency worktree prepare <task-id> [phase-id] [--dry-run] [--force] [--json]",
|
|
890
891
|
minArgs: 1,
|
|
891
892
|
maxArgs: 2,
|
|
892
|
-
options: ["dry-run", "json", "task", "phase"],
|
|
893
|
+
options: ["dry-run", "force", "json", "task", "phase"],
|
|
893
894
|
},
|
|
894
895
|
remove: {
|
|
895
896
|
usage:
|
|
896
|
-
"agency worktree remove <task-id> [phase-id] [--dry-run] [--json]",
|
|
897
|
+
"agency worktree remove <task-id> [phase-id] [--dry-run] [--force] [--json]",
|
|
897
898
|
minArgs: 1,
|
|
898
899
|
maxArgs: 2,
|
|
899
|
-
options: ["dry-run", "json", "task", "phase"],
|
|
900
|
+
options: ["dry-run", "force", "json", "task", "phase"],
|
|
900
901
|
},
|
|
901
902
|
rebuild: {
|
|
902
903
|
usage:
|
|
903
|
-
"agency worktree rebuild <task-id> [phase-id] [--dry-run] [--json]",
|
|
904
|
+
"agency worktree rebuild <task-id> [phase-id] [--dry-run] [--force] [--json]",
|
|
904
905
|
minArgs: 1,
|
|
905
906
|
maxArgs: 2,
|
|
906
|
-
options: ["dry-run", "json", "task", "phase"],
|
|
907
|
+
options: ["dry-run", "force", "json", "task", "phase"],
|
|
907
908
|
},
|
|
908
909
|
repair: {
|
|
909
910
|
usage:
|
|
910
|
-
"agency worktree repair <task-id> [phase-id] [--dry-run] [--json]",
|
|
911
|
+
"agency worktree repair <task-id> [phase-id] [--dry-run] [--force] [--json]",
|
|
911
912
|
minArgs: 1,
|
|
912
913
|
maxArgs: 2,
|
|
913
|
-
options: ["dry-run", "json", "task", "phase"],
|
|
914
|
+
options: ["dry-run", "force", "json", "task", "phase"],
|
|
914
915
|
},
|
|
915
916
|
},
|
|
916
917
|
},
|
package/src/cli.test.ts
CHANGED
|
@@ -1373,12 +1373,7 @@ status: open
|
|
|
1373
1373
|
}
|
|
1374
1374
|
if (launch === launches[0]) {
|
|
1375
1375
|
expect(effectiveConfig.instructions).toContain(".agency/AGENTS.md")
|
|
1376
|
-
expect(effectiveConfig.agent.agency).
|
|
1377
|
-
description: expect.stringContaining(
|
|
1378
|
-
"Agency workbase orchestration",
|
|
1379
|
-
),
|
|
1380
|
-
mode: "subagent",
|
|
1381
|
-
})
|
|
1376
|
+
expect(effectiveConfig.agent.agency).toBeUndefined()
|
|
1382
1377
|
expect(effectiveConfig.references).toEqual({
|
|
1383
1378
|
workbase: {
|
|
1384
1379
|
path: "..",
|
|
@@ -1386,18 +1381,6 @@ status: open
|
|
|
1386
1381
|
"Complete Agency workbase context; write authority still comes only from agency context",
|
|
1387
1382
|
},
|
|
1388
1383
|
})
|
|
1389
|
-
const agencyProbe = Bun.spawnSync(
|
|
1390
|
-
["opencode", "debug", "agent", "agency"],
|
|
1391
|
-
{ cwd: contract.cwd, env: environment },
|
|
1392
|
-
)
|
|
1393
|
-
expect(agencyProbe.exitCode).toBe(0)
|
|
1394
|
-
expect(JSON.parse(agencyProbe.stdout.toString())).toMatchObject({
|
|
1395
|
-
name: "agency",
|
|
1396
|
-
description: expect.stringContaining(
|
|
1397
|
-
"Agency workbase orchestration",
|
|
1398
|
-
),
|
|
1399
|
-
mode: "subagent",
|
|
1400
|
-
})
|
|
1401
1384
|
for (const document of documents) {
|
|
1402
1385
|
const read = Bun.spawnSync(
|
|
1403
1386
|
[
|
|
@@ -45,11 +45,7 @@ describe("init command", () => {
|
|
|
45
45
|
).text()
|
|
46
46
|
const config = JSON.parse(opencode.slice(opencode.indexOf("\n\n") + 2))
|
|
47
47
|
expect(config.instructions).toEqual([".agency/AGENTS.md"])
|
|
48
|
-
expect(config.agent.agency).
|
|
49
|
-
description: expect.stringContaining("Agency workbase orchestration"),
|
|
50
|
-
mode: "subagent",
|
|
51
|
-
prompt: expect.stringContaining("agency context . --json"),
|
|
52
|
-
})
|
|
48
|
+
expect(config.agent.agency).toBeUndefined()
|
|
53
49
|
expect(config.agent.plan).toEqual({ disable: true })
|
|
54
50
|
expect(config.agent["agency-plan"]).toMatchObject({
|
|
55
51
|
mode: "primary",
|
package/src/commands/worktree.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { WorktreeService } from "../services/WorktreeService"
|
|
|
6
6
|
interface WorktreeOptions extends BaseCommandOptions {
|
|
7
7
|
readonly subcommand?: string
|
|
8
8
|
readonly args?: readonly string[]
|
|
9
|
+
readonly force?: boolean
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
const targetLabel = (owner: {
|
|
@@ -134,5 +135,6 @@ Options:
|
|
|
134
135
|
--task <id> Select a task without positional IDs
|
|
135
136
|
--phase <id> Select a phase with --task
|
|
136
137
|
--dry-run Preflight and report changes without applying them
|
|
138
|
+
--force Override an existing worktree operation lock
|
|
137
139
|
--json Print structured output
|
|
138
140
|
`
|
|
@@ -186,9 +186,12 @@ describe("IntegrationService", () => {
|
|
|
186
186
|
}
|
|
187
187
|
expect(managedWorkbaseAgents).toContain("agency push --json")
|
|
188
188
|
expect(managedWorkbaseAgents).toContain("agency-execution-v1")
|
|
189
|
-
expect(managedWorkbaseAgents).toContain("agency context . --
|
|
189
|
+
expect(managedWorkbaseAgents).toContain("agency context . --json")
|
|
190
190
|
expect(managedWorkbaseAgents).toContain(
|
|
191
|
-
"
|
|
191
|
+
"Pass `--full` only when document prose or low-level VCS details are needed",
|
|
192
|
+
)
|
|
193
|
+
expect(managedWorkbaseAgents).not.toContain(
|
|
194
|
+
"agency context . --full --json",
|
|
192
195
|
)
|
|
193
196
|
expect(managedWorkbaseAgents).toContain(
|
|
194
197
|
"Never pass `--work` or `--auto` to `agency task create`",
|
|
@@ -681,19 +684,11 @@ describe("IntegrationService", () => {
|
|
|
681
684
|
)
|
|
682
685
|
})
|
|
683
686
|
|
|
684
|
-
test("configures Agency
|
|
687
|
+
test("configures Agency planning with complete workbase access", () => {
|
|
685
688
|
const config = JSON.parse(managedBody(managedWorkbaseOpencode))
|
|
686
689
|
|
|
687
690
|
expect(config.instructions).toEqual([".agency/AGENTS.md"])
|
|
688
691
|
expect(config.agent).toEqual({
|
|
689
|
-
agency: {
|
|
690
|
-
description:
|
|
691
|
-
"Handles Agency workbase orchestration and workflow operations with the Agency CLI",
|
|
692
|
-
mode: "subagent",
|
|
693
|
-
prompt: expect.stringMatching(
|
|
694
|
-
/agency context \. --json[\s\S]+agency work prepare[\s\S]+never pass `--work` or `--auto`/,
|
|
695
|
-
),
|
|
696
|
-
},
|
|
697
692
|
plan: {
|
|
698
693
|
disable: true,
|
|
699
694
|
},
|
|
@@ -716,19 +711,16 @@ describe("IntegrationService", () => {
|
|
|
716
711
|
},
|
|
717
712
|
},
|
|
718
713
|
})
|
|
719
|
-
expect(config.agent.agency.model).toBeUndefined()
|
|
720
|
-
expect(config.agent.agency.permission).toBeUndefined()
|
|
721
714
|
expect(config.agent["agency-plan"].prompt).toContain(
|
|
722
715
|
"Explicit-new intent overrides reuse",
|
|
723
716
|
)
|
|
724
|
-
expect(config.agent.agency
|
|
725
|
-
expect(config.agent.agency.steps).toBeUndefined()
|
|
726
|
-
expect(config.agent.agency.prompt).toContain(
|
|
727
|
-
"Return the prepared execution contract to the caller",
|
|
728
|
-
)
|
|
717
|
+
expect(config.agent.agency).toBeUndefined()
|
|
729
718
|
expect(config.agent["agency-plan"].prompt).toContain(
|
|
730
719
|
"Start with `agency context . --json`",
|
|
731
720
|
)
|
|
721
|
+
expect(config.agent["agency-plan"].prompt).toContain(
|
|
722
|
+
"Pass `--full` only when document prose or low-level VCS details are needed",
|
|
723
|
+
)
|
|
732
724
|
expect(config.agent["agency-plan"].prompt).toContain(
|
|
733
725
|
"decompose it into independently deliverable tasks",
|
|
734
726
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from "bun:test"
|
|
2
2
|
import { Effect } from "effect"
|
|
3
|
-
import { mkdtemp, readdir, rm } from "node:fs/promises"
|
|
3
|
+
import { mkdtemp, readdir, rm, utimes } from "node:fs/promises"
|
|
4
4
|
import { tmpdir } from "node:os"
|
|
5
5
|
import { join } from "node:path"
|
|
6
6
|
import { withWorktreeLocks } from "./WorktreeLock"
|
|
@@ -54,11 +54,90 @@ describe("withWorktreeLocks", () => {
|
|
|
54
54
|
_tag: "Left",
|
|
55
55
|
left: {
|
|
56
56
|
_tag: "WorktreeLockError",
|
|
57
|
-
message: `Another worktree operation is in progress for 'alpha'.
|
|
57
|
+
message: `Another worktree operation is in progress for 'alpha'. Retry with --force or remove the stale sentinel with: rm '${lockPath}'`,
|
|
58
58
|
},
|
|
59
59
|
})
|
|
60
60
|
})
|
|
61
61
|
|
|
62
|
+
test("removes stale locks before acquiring them", async () => {
|
|
63
|
+
const root = await createTempDir()
|
|
64
|
+
tempDirs.push(root)
|
|
65
|
+
const lockPath = join(
|
|
66
|
+
root,
|
|
67
|
+
`.agency-worktree-${Buffer.from("alpha:task").toString("hex")}.lock`,
|
|
68
|
+
)
|
|
69
|
+
await Bun.write(lockPath, "")
|
|
70
|
+
const staleAt = new Date(Date.now() - 11 * 60 * 1000)
|
|
71
|
+
await utimes(lockPath, staleAt, staleAt)
|
|
72
|
+
|
|
73
|
+
await expect(
|
|
74
|
+
Effect.runPromise(
|
|
75
|
+
withWorktreeLocks(root, [{ taskId: "alpha" }], Effect.void),
|
|
76
|
+
),
|
|
77
|
+
).resolves.toBeUndefined()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test("force overrides an active lock", async () => {
|
|
81
|
+
const root = await createTempDir()
|
|
82
|
+
tempDirs.push(root)
|
|
83
|
+
let firstEntered!: () => void
|
|
84
|
+
let firstRelease!: () => void
|
|
85
|
+
const firstEnteredPromise = new Promise<void>((resolve) => {
|
|
86
|
+
firstEntered = resolve
|
|
87
|
+
})
|
|
88
|
+
const firstReleasePromise = new Promise<void>((resolve) => {
|
|
89
|
+
firstRelease = resolve
|
|
90
|
+
})
|
|
91
|
+
const first = Effect.runPromise(
|
|
92
|
+
withWorktreeLocks(
|
|
93
|
+
root,
|
|
94
|
+
[{ taskId: "alpha" }],
|
|
95
|
+
Effect.promise(async () => {
|
|
96
|
+
firstEntered()
|
|
97
|
+
await firstReleasePromise
|
|
98
|
+
}),
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
await firstEnteredPromise
|
|
102
|
+
|
|
103
|
+
let forcedEntered!: () => void
|
|
104
|
+
let forcedRelease!: () => void
|
|
105
|
+
const forcedEnteredPromise = new Promise<void>((resolve) => {
|
|
106
|
+
forcedEntered = resolve
|
|
107
|
+
})
|
|
108
|
+
const forcedReleasePromise = new Promise<void>((resolve) => {
|
|
109
|
+
forcedRelease = resolve
|
|
110
|
+
})
|
|
111
|
+
const forced = Effect.runPromise(
|
|
112
|
+
withWorktreeLocks(
|
|
113
|
+
root,
|
|
114
|
+
[{ taskId: "alpha" }],
|
|
115
|
+
Effect.promise(async () => {
|
|
116
|
+
forcedEntered()
|
|
117
|
+
await forcedReleasePromise
|
|
118
|
+
}),
|
|
119
|
+
{ force: true },
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
await forcedEnteredPromise
|
|
123
|
+
|
|
124
|
+
firstRelease()
|
|
125
|
+
await first
|
|
126
|
+
|
|
127
|
+
const conflict = await Effect.runPromise(
|
|
128
|
+
Effect.either(
|
|
129
|
+
withWorktreeLocks(root, [{ taskId: "alpha" }], Effect.void),
|
|
130
|
+
),
|
|
131
|
+
)
|
|
132
|
+
expect(conflict).toMatchObject({
|
|
133
|
+
_tag: "Left",
|
|
134
|
+
left: { _tag: "WorktreeLockError" },
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
forcedRelease()
|
|
138
|
+
await forced
|
|
139
|
+
})
|
|
140
|
+
|
|
62
141
|
test("releases locks when the protected operation fails", async () => {
|
|
63
142
|
const root = await createTempDir()
|
|
64
143
|
tempDirs.push(root)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Data, Effect } from "effect"
|
|
2
|
-
import { open, rm } from "node:fs/promises"
|
|
2
|
+
import { open, rm, stat } from "node:fs/promises"
|
|
3
3
|
import { join } from "node:path"
|
|
4
4
|
|
|
5
5
|
class WorktreeLockError extends Data.TaggedError("WorktreeLockError")<{
|
|
@@ -12,10 +12,23 @@ export interface WorktreeLockTarget {
|
|
|
12
12
|
readonly phaseId?: string
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
export interface WorktreeLockOptions {
|
|
16
|
+
readonly force?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const lockTimeoutMs = 10 * 60 * 1000
|
|
20
|
+
|
|
21
|
+
const isErrorCode = (cause: unknown, code: string) =>
|
|
22
|
+
typeof cause === "object" &&
|
|
23
|
+
cause !== null &&
|
|
24
|
+
"code" in cause &&
|
|
25
|
+
cause.code === code
|
|
26
|
+
|
|
15
27
|
const withWorktreeLock = <A, E, R>(
|
|
16
28
|
root: string,
|
|
17
29
|
target: WorktreeLockTarget,
|
|
18
30
|
effect: Effect.Effect<A, E, R>,
|
|
31
|
+
options: WorktreeLockOptions,
|
|
19
32
|
): Effect.Effect<A, E | WorktreeLockError, R> => {
|
|
20
33
|
const key = Buffer.from(
|
|
21
34
|
`${target.taskId}:${target.phaseId ?? "task"}`,
|
|
@@ -24,18 +37,41 @@ const withWorktreeLock = <A, E, R>(
|
|
|
24
37
|
const removalCommand = `rm '${lockPath.replaceAll("'", `'\\''`)}'`
|
|
25
38
|
return Effect.acquireUseRelease(
|
|
26
39
|
Effect.tryPromise({
|
|
27
|
-
try: () =>
|
|
40
|
+
try: async () => {
|
|
41
|
+
try {
|
|
42
|
+
return await open(lockPath, "wx")
|
|
43
|
+
} catch (cause) {
|
|
44
|
+
if (!isErrorCode(cause, "EEXIST")) throw cause
|
|
45
|
+
let stale = false
|
|
46
|
+
try {
|
|
47
|
+
stale = Date.now() - (await stat(lockPath)).mtimeMs >= lockTimeoutMs
|
|
48
|
+
} catch (statCause) {
|
|
49
|
+
if (!isErrorCode(statCause, "ENOENT")) throw statCause
|
|
50
|
+
}
|
|
51
|
+
if (!options.force && !stale) throw cause
|
|
52
|
+
await rm(lockPath, { force: true })
|
|
53
|
+
return open(lockPath, "wx")
|
|
54
|
+
}
|
|
55
|
+
},
|
|
28
56
|
catch: (cause) =>
|
|
29
57
|
new WorktreeLockError({
|
|
30
|
-
message: `Another worktree operation is in progress for '${target.taskId}${target.phaseId ? `/${target.phaseId}` : ""}'.
|
|
58
|
+
message: `Another worktree operation is in progress for '${target.taskId}${target.phaseId ? `/${target.phaseId}` : ""}'. Retry with --force or remove the stale sentinel with: ${removalCommand}`,
|
|
31
59
|
cause,
|
|
32
60
|
}),
|
|
33
61
|
}),
|
|
34
62
|
() => effect,
|
|
35
63
|
(lock) =>
|
|
36
64
|
Effect.promise(async () => {
|
|
65
|
+
let ownsLock = false
|
|
66
|
+
try {
|
|
67
|
+
const [held, current] = await Promise.all([
|
|
68
|
+
lock.stat(),
|
|
69
|
+
stat(lockPath),
|
|
70
|
+
])
|
|
71
|
+
ownsLock = held.dev === current.dev && held.ino === current.ino
|
|
72
|
+
} catch {}
|
|
37
73
|
await lock.close().catch(() => undefined)
|
|
38
|
-
await rm(lockPath, { force: true }).catch(() => undefined)
|
|
74
|
+
if (ownsLock) await rm(lockPath, { force: true }).catch(() => undefined)
|
|
39
75
|
}),
|
|
40
76
|
)
|
|
41
77
|
}
|
|
@@ -44,6 +80,7 @@ export const withWorktreeLocks = <A, E, R>(
|
|
|
44
80
|
root: string,
|
|
45
81
|
targets: readonly WorktreeLockTarget[],
|
|
46
82
|
effect: Effect.Effect<A, E, R>,
|
|
83
|
+
options: WorktreeLockOptions = {},
|
|
47
84
|
): Effect.Effect<A, E | WorktreeLockError, R> => {
|
|
48
85
|
const unique = new Map(
|
|
49
86
|
targets.map((target) => [
|
|
@@ -55,7 +92,7 @@ export const withWorktreeLocks = <A, E, R>(
|
|
|
55
92
|
for (const [, target] of [...unique.entries()]
|
|
56
93
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
57
94
|
.reverse()) {
|
|
58
|
-
current = withWorktreeLock(root, target, current)
|
|
95
|
+
current = withWorktreeLock(root, target, current, options)
|
|
59
96
|
}
|
|
60
97
|
return current
|
|
61
98
|
}
|
|
@@ -259,6 +259,7 @@ interface MaterializeOptions extends BaseCommandOptions {
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
interface RemoveOptions extends BaseCommandOptions {
|
|
262
|
+
readonly force?: boolean
|
|
262
263
|
readonly snapshots?: WorktreeRemovalSnapshot[]
|
|
263
264
|
readonly lockHeld?: boolean
|
|
264
265
|
readonly allowReferenceDrift?: boolean
|
|
@@ -267,6 +268,7 @@ interface RemoveOptions extends BaseCommandOptions {
|
|
|
267
268
|
}
|
|
268
269
|
|
|
269
270
|
interface LifecycleOptions extends BaseCommandOptions {
|
|
271
|
+
readonly force?: boolean
|
|
270
272
|
readonly lockHeld?: boolean
|
|
271
273
|
}
|
|
272
274
|
|
|
@@ -1825,6 +1827,7 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
|
|
|
1825
1827
|
root,
|
|
1826
1828
|
[{ taskId, ...(phaseId ? { phaseId } : {}) }],
|
|
1827
1829
|
materialization,
|
|
1830
|
+
{ force: options.force },
|
|
1828
1831
|
)
|
|
1829
1832
|
}),
|
|
1830
1833
|
|
|
@@ -2148,6 +2151,7 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
|
|
|
2148
2151
|
root,
|
|
2149
2152
|
[{ taskId, ...(phaseId ? { phaseId } : {}) }],
|
|
2150
2153
|
removal,
|
|
2154
|
+
{ force: options.force },
|
|
2151
2155
|
)
|
|
2152
2156
|
}),
|
|
2153
2157
|
|
|
@@ -2170,6 +2174,7 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
|
|
|
2170
2174
|
...options,
|
|
2171
2175
|
lockHeld: true,
|
|
2172
2176
|
}),
|
|
2177
|
+
{ force: options.force },
|
|
2173
2178
|
)
|
|
2174
2179
|
}
|
|
2175
2180
|
const inspection = yield* inspectExecution(taskId, phaseId, root)
|
|
@@ -2316,6 +2321,7 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
|
|
|
2316
2321
|
...options,
|
|
2317
2322
|
lockHeld: true,
|
|
2318
2323
|
}),
|
|
2324
|
+
{ force: options.force },
|
|
2319
2325
|
)
|
|
2320
2326
|
}
|
|
2321
2327
|
const inspection = yield* inspectExecution(taskId, phaseId, root)
|
package/src/workbase/AGENTS.md
CHANGED
|
@@ -79,10 +79,10 @@ commands without prescribing an execution environment.
|
|
|
79
79
|
Start every session with one read-only command:
|
|
80
80
|
|
|
81
81
|
```bash
|
|
82
|
-
agency context . --
|
|
82
|
+
agency context . --json
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
Pass `--full` only when document prose or low-level VCS details are needed. Use
|
|
86
86
|
the returned target, document paths and revisions, dependency readiness,
|
|
87
87
|
authority, checkout state, PR state, and validation result. Do not infer these
|
|
88
88
|
from directory names or stale prose.
|
|
@@ -8,7 +8,7 @@ const checksum = (content: string) =>
|
|
|
8
8
|
|
|
9
9
|
const agencyPlanPrompt = `You are in Agency Plan mode. Think, read, search, and delegate exploration to construct a well-formed plan for the user's goal. Keep the plan comprehensive but concise, and ask clarifying questions when important tradeoffs or intent are unclear.
|
|
10
10
|
|
|
11
|
-
Start with \`agency context . --json\`. Use its document paths and revisions, then inspect the graph, related epics, tasks, phases, linked tickets, and repository declarations needed to understand the work. Use machine-readable Agency output when available instead of inferring structure from directory names.
|
|
11
|
+
Start with \`agency context . --json\`. Pass \`--full\` only when document prose or low-level VCS details are needed. Use its document paths and revisions, then inspect the graph, related epics, tasks, phases, linked tickets, and repository declarations needed to understand the work. Use machine-readable Agency output when available instead of inferring structure from directory names.
|
|
12
12
|
|
|
13
13
|
When planning an epic, decompose it into independently deliverable tasks with explicit dependencies. Add phases only when one task genuinely requires multiple ordered delivery units. Reuse or update existing work instead of creating duplicate tasks or phases, except when the user explicitly requests a new, separate, or follow-up item. Explicit-new intent overrides reuse of active and archived work even when the subject or suggested ID matches.
|
|
14
14
|
|
|
@@ -20,24 +20,12 @@ const agencyPlanBashPermissions = {
|
|
|
20
20
|
"agency *": "allow",
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const agencyAgentPrompt = `You are the Agency workflow specialist. Use the Agency CLI to handle delegated workbase orchestration and workflow operations. Always start with \`agency context . --json\` and follow the managed Agency instructions and reported authority.
|
|
24
|
-
|
|
25
|
-
When the intent and parameters are known, use the matching managed Command Fast Path exactly. The recipes cover creation, preparation, synchronization, phase conversion, archiving, review work, inspection, dropping, continuation, publication, pull requests, non-PR completion, multi-phase setup, investigation handoff, and review refresh. Preparation recipes call \`agency work prepare <task-or-document> --json\`. Do not probe help or list unrelated state, never pass \`--work\` or \`--auto\` to \`agency task create\`, and use \`--dry-run\` only where the recipe or caller requests it. Use CLI discovery only when no fast-path recipe matches or a prescribed command rejects known-current syntax.
|
|
26
|
-
|
|
27
|
-
Return the prepared execution contract to the caller. Agency owns the target, validation, materialized workspace facts, and native commands; the caller owns presentation and process orchestration.`
|
|
28
|
-
|
|
29
23
|
const body = () =>
|
|
30
24
|
`${JSON.stringify(
|
|
31
25
|
{
|
|
32
26
|
$schema: "https://opencode.ai/config.json",
|
|
33
27
|
instructions: [".agency/AGENTS.md"],
|
|
34
28
|
agent: {
|
|
35
|
-
agency: {
|
|
36
|
-
description:
|
|
37
|
-
"Handles Agency workbase orchestration and workflow operations with the Agency CLI",
|
|
38
|
-
mode: "subagent",
|
|
39
|
-
prompt: agencyAgentPrompt,
|
|
40
|
-
},
|
|
41
29
|
plan: {
|
|
42
30
|
disable: true,
|
|
43
31
|
},
|