@markjaquith/agency 2.47.3 → 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 +14 -19
- package/package.json +1 -2
- package/src/cli-parser.test.ts +0 -7
- package/src/cli.test.ts +11 -3
- package/src/commands/init.test.ts +8 -6
- package/src/commands/integration.test.ts +1 -16
- package/src/commands/integration.ts +0 -2
- package/src/commands/work.test.ts +38 -4
- package/src/commands/work.ts +1 -1
- package/src/services/IntegrationService.test.ts +69 -76
- package/src/services/IntegrationService.ts +27 -52
- package/src/workbase/AGENTS.md +37 -8
- package/fixtures/protocol/skill-setup-commands.json +0 -18
- package/skills/agency/SKILL.md +0 -151
- package/skills/agency/references/commands.md +0 -188
- package/skills/agency/references/contracts.md +0 -322
- package/skills/agency/references/recipes.md +0 -264
- package/src/workbase/AGENCY_COMMAND.md +0 -48
- package/src/workbase/opencode-command-file.ts +0 -30
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,12 +83,19 @@ 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
|
|
88
|
-
|
|
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.
|
|
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
|
+
The managed `.agency/AGENTS.md` is the complete in-workbase operating contract
|
|
94
|
+
for agents. It is created by `agency init`, updated by checksum-safe integration
|
|
95
|
+
sync, and requires no separately installed Agency skill. CLI help remains the
|
|
96
|
+
source of truth for exact command syntax; this README provides the detailed
|
|
97
|
+
product and protocol reference.
|
|
98
|
+
|
|
93
99
|
When upgrading an existing workbase, synchronization moves a checksum-valid
|
|
94
100
|
Agency-managed root `AGENTS.md` to `.agency/AGENTS.md` once the OpenCode config
|
|
95
101
|
can load the hidden file. A customized root file, including a symlink, is
|
|
@@ -116,13 +122,6 @@ The plugin grants whole-workbase access dynamically, while the portable
|
|
|
116
122
|
reference advertises that context to agents. Bash and Agency operations must
|
|
117
123
|
still follow the write authority reported by `agency context`.
|
|
118
124
|
|
|
119
|
-
OpenCode also discovers a managed `/agency` command. Use `/agency status` for a
|
|
120
|
-
read-only current-work summary, `/agency start [target]` to begin or resume work
|
|
121
|
-
in the active session, `/agency next` to inspect ready work, `/agency validate`
|
|
122
|
-
to check the workbase, and `/agency finish [target]` for verified closeout. The
|
|
123
|
-
command uses OpenCode positional arguments internally and defaults to the safe
|
|
124
|
-
`status` workflow when no subcommand is supplied.
|
|
125
|
-
|
|
126
125
|
Repository aliases and canonical fetch remotes are declared in tracked
|
|
127
126
|
`agency.json`; local bare clones and symlinks remain ignored under
|
|
128
127
|
`repos/{alias}`. A declaration contains no local path, symlink target, checkout,
|
|
@@ -882,14 +881,10 @@ payloads are exported as `@markjaquith/agency/fixtures/protocol/success.json` an
|
|
|
882
881
|
Success, help, and version output exit `0`; usage and command failures exit `1`.
|
|
883
882
|
There are no error-specific exit statuses. `graph --jsonl` streams versioned
|
|
884
883
|
records on success instead of wrapping them in an envelope; JSONL failures still
|
|
885
|
-
use one error envelope.
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
`skills/agency/SKILL.md` contains an agent-oriented operating guide for Agency.
|
|
891
|
-
Install or link that directory into your agent's skill location when you want
|
|
892
|
-
Agency workflows to be discovered automatically.
|
|
884
|
+
use one error envelope. Revision and concurrency behavior is documented under
|
|
885
|
+
Tasks, Phases, and Claims; selector behavior under Noninteractive Use; projection
|
|
886
|
+
behavior under Target Context and Workbase Graph; and retry behavior in the
|
|
887
|
+
machine error contract above.
|
|
893
888
|
|
|
894
889
|
## Development
|
|
895
890
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markjaquith/agency",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.48.1",
|
|
4
4
|
"description": "Manage agentic work across repositories with durable workbases",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
"src",
|
|
24
24
|
"schemas",
|
|
25
25
|
"fixtures/protocol",
|
|
26
|
-
"skills",
|
|
27
26
|
"README.md",
|
|
28
27
|
"LICENSE"
|
|
29
28
|
],
|
package/src/cli-parser.test.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test"
|
|
2
2
|
import orchestrationRecipes from "../fixtures/protocol/orchestration-recipes.json"
|
|
3
|
-
import skillSetupCommands from "../fixtures/protocol/skill-setup-commands.json"
|
|
4
3
|
import { parseCli } from "./cli-parser"
|
|
5
4
|
|
|
6
5
|
const expectUsageError = (args: string[], usage: string) => {
|
|
@@ -14,12 +13,6 @@ describe("strict CLI parsing", () => {
|
|
|
14
13
|
}
|
|
15
14
|
})
|
|
16
15
|
|
|
17
|
-
test("accepts every documented skill setup command", () => {
|
|
18
|
-
for (const args of skillSetupCommands) {
|
|
19
|
-
expect(() => parseCli(args)).not.toThrow()
|
|
20
|
-
}
|
|
21
|
-
})
|
|
22
|
-
|
|
23
16
|
test("rejects misspelled and command-inapplicable options", () => {
|
|
24
17
|
expectUsageError(["task", "list", "--josn"], "agency task")
|
|
25
18
|
expectUsageError(
|
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,
|
|
@@ -34,7 +34,11 @@ describe("init command", () => {
|
|
|
34
34
|
"/repos/\n/tasks/*/code/\n/tasks/*/phases/*/code/\n",
|
|
35
35
|
)
|
|
36
36
|
expect(await Bun.file(join(root, "AGENTS.md")).exists()).toBe(false)
|
|
37
|
-
|
|
37
|
+
const agents = await Bun.file(join(root, ".agency/AGENTS.md")).text()
|
|
38
|
+
expect(agents).toContain("agency context . --json")
|
|
39
|
+
expect(agents).toContain("## Consent Boundaries")
|
|
40
|
+
expect(agents).toContain("## Execution")
|
|
41
|
+
expect(agents).not.toContain("SKILL.md")
|
|
38
42
|
const opencode = await Bun.file(
|
|
39
43
|
join(root, ".opencode/opencode.jsonc"),
|
|
40
44
|
).text()
|
|
@@ -64,11 +68,9 @@ describe("init command", () => {
|
|
|
64
68
|
workbase: expect.objectContaining({ path: ".." }),
|
|
65
69
|
})
|
|
66
70
|
expect(config.permission).toBeUndefined()
|
|
67
|
-
|
|
68
|
-
join(root, ".opencode/command/agency.md"),
|
|
69
|
-
).
|
|
70
|
-
expect(command).toContain("Workflow: `$1`")
|
|
71
|
-
expect(command).toContain("Optional target: `$2`")
|
|
71
|
+
expect(
|
|
72
|
+
await Bun.file(join(root, ".opencode/command/agency.md")).exists(),
|
|
73
|
+
).toBe(false)
|
|
72
74
|
const plugin = await Bun.file(
|
|
73
75
|
join(root, ".opencode/plugin/agency-repository-skills.ts"),
|
|
74
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(
|
|
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:
|
|
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:
|
|
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(
|
|
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:
|
|
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
|
|
package/src/commands/work.ts
CHANGED
|
@@ -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,43 +251,25 @@ 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
|
|
|
293
257
|
expect(body).toContain("agency context . --json")
|
|
258
|
+
expect(body).toContain("agency next --json")
|
|
259
|
+
expect(body).toContain("agency <command> --help")
|
|
294
260
|
expect(body).toContain("authority.writable.checkoutPath")
|
|
295
|
-
expect(body).toContain("
|
|
296
|
-
expect(body).toContain("
|
|
261
|
+
expect(body).toContain("Only `done` satisfies a dependency")
|
|
262
|
+
expect(body).toContain("Require explicit user intent")
|
|
263
|
+
expect(body).toContain("changing repository")
|
|
264
|
+
expect(body).toMatch(/archiving, restoring,\s+dropping, or/)
|
|
265
|
+
expect(body).toContain("Never invent entity IDs")
|
|
266
|
+
expect(body).toContain("Preserve parent backlinks")
|
|
267
|
+
expect(body).toContain("dirty-worktree, active-claim, revision")
|
|
268
|
+
expect(body).toContain("`agency work` is the human launch flow")
|
|
269
|
+
expect(body).toContain("marks execution work")
|
|
270
|
+
expect(body).toContain("without creating a claim")
|
|
271
|
+
expect(body).toContain("formatting, type checks, build, dead-code checks")
|
|
272
|
+
expect(body).toContain("Review and commit the diff")
|
|
297
273
|
expect(body).toContain("Run `agency validate`")
|
|
298
274
|
expect(body).toContain("only with explicit user intent")
|
|
299
275
|
expect(body).toContain("An execution unit remains `working`")
|
|
@@ -309,7 +285,9 @@ describe("IntegrationService", () => {
|
|
|
309
285
|
expect(body).toContain("PR state, current head, diff summary")
|
|
310
286
|
expect(body).toContain("Run `agency validate` before reporting completion")
|
|
311
287
|
expect(body).toContain("agency integration status")
|
|
312
|
-
expect(body).toContain(".
|
|
288
|
+
expect(body).not.toContain("SKILL.md")
|
|
289
|
+
expect(body).not.toContain("~/.agents")
|
|
290
|
+
expect(body).not.toContain(".opencode/command/agency.md")
|
|
313
291
|
})
|
|
314
292
|
|
|
315
293
|
test("configures Agency agents with complete workbase access", () => {
|
|
@@ -440,7 +418,6 @@ describe("IntegrationService", () => {
|
|
|
440
418
|
expect(first.files).toMatchObject([
|
|
441
419
|
{ name: "agents", state: "customized", changed: false },
|
|
442
420
|
{ name: "opencode", state: "managed", changed: true },
|
|
443
|
-
{ name: "opencode-command", state: "managed", changed: true },
|
|
444
421
|
{ name: "opencode-plugin", state: "managed", changed: true },
|
|
445
422
|
{ name: "opencode-tui", state: "managed", changed: true },
|
|
446
423
|
{ name: "opencode-tui-plugin", state: "managed", changed: true },
|
|
@@ -455,8 +432,8 @@ describe("IntegrationService", () => {
|
|
|
455
432
|
managedWorkbaseOpencode,
|
|
456
433
|
)
|
|
457
434
|
expect(
|
|
458
|
-
await Bun.file(join(root, ".opencode/command/agency.md")).
|
|
459
|
-
).toBe(
|
|
435
|
+
await Bun.file(join(root, ".opencode/command/agency.md")).exists(),
|
|
436
|
+
).toBe(false)
|
|
460
437
|
expect(
|
|
461
438
|
await Bun.file(
|
|
462
439
|
join(root, ".opencode/plugin/agency-repository-skills.ts"),
|
|
@@ -480,12 +457,37 @@ describe("IntegrationService", () => {
|
|
|
480
457
|
)
|
|
481
458
|
})
|
|
482
459
|
|
|
460
|
+
test("upgrades prior managed instructions without changing owner instructions", async () => {
|
|
461
|
+
const ownerInstructions = "# User-owned workbase instructions\n"
|
|
462
|
+
const priorInstructions = managed(
|
|
463
|
+
"<!-- agency-managed: sha256=",
|
|
464
|
+
"# Previous Agency instructions\n",
|
|
465
|
+
" -->",
|
|
466
|
+
)
|
|
467
|
+
await write(root, "AGENTS.md", ownerInstructions)
|
|
468
|
+
await write(root, ".agency/AGENTS.md", priorInstructions)
|
|
469
|
+
|
|
470
|
+
const result = await sync(root)
|
|
471
|
+
|
|
472
|
+
expect(result.files[0]).toMatchObject({
|
|
473
|
+
name: "agents",
|
|
474
|
+
state: "managed",
|
|
475
|
+
changed: true,
|
|
476
|
+
})
|
|
477
|
+
expect(await Bun.file(join(root, ".agency/AGENTS.md")).text()).toBe(
|
|
478
|
+
managedWorkbaseAgents,
|
|
479
|
+
)
|
|
480
|
+
expect(await Bun.file(join(root, "AGENTS.md")).text()).toBe(
|
|
481
|
+
ownerInstructions,
|
|
482
|
+
)
|
|
483
|
+
})
|
|
484
|
+
|
|
483
485
|
test("preserves user-owned checkout-skill plugins at either supported path", async () => {
|
|
484
486
|
const custom = "export default async () => ({})\n"
|
|
485
487
|
await write(root, ".opencode/plugins/agency-repository-skills.ts", custom)
|
|
486
488
|
|
|
487
489
|
let result = await sync(root)
|
|
488
|
-
expect(result.files[
|
|
490
|
+
expect(result.files[2]).toMatchObject({
|
|
489
491
|
name: "opencode-plugin",
|
|
490
492
|
path: join(root, ".opencode/plugins/agency-repository-skills.ts"),
|
|
491
493
|
state: "customized",
|
|
@@ -500,7 +502,7 @@ describe("IntegrationService", () => {
|
|
|
500
502
|
await unlink(join(root, ".opencode/plugins/agency-repository-skills.ts"))
|
|
501
503
|
await write(root, ".opencode/plugin/agency-repository-skills.ts", custom)
|
|
502
504
|
result = await sync(root)
|
|
503
|
-
expect(result.files[
|
|
505
|
+
expect(result.files[2]).toMatchObject({
|
|
504
506
|
path: join(root, ".opencode/plugin/agency-repository-skills.ts"),
|
|
505
507
|
state: "customized",
|
|
506
508
|
changed: false,
|
|
@@ -515,14 +517,14 @@ describe("IntegrationService", () => {
|
|
|
515
517
|
await write(root, ".opencode/tui/agency-debug.ts", customPlugin)
|
|
516
518
|
|
|
517
519
|
const result = await sync(root)
|
|
518
|
-
expect(result.files[
|
|
520
|
+
expect(result.files[3]).toMatchObject({
|
|
519
521
|
name: "opencode-tui",
|
|
520
522
|
path: join(root, ".opencode/tui.json"),
|
|
521
523
|
state: "customized",
|
|
522
524
|
changed: false,
|
|
523
525
|
remediation: expect.stringContaining("plugin list"),
|
|
524
526
|
})
|
|
525
|
-
expect(result.files[
|
|
527
|
+
expect(result.files[4]).toMatchObject({
|
|
526
528
|
name: "opencode-tui-plugin",
|
|
527
529
|
state: "customized",
|
|
528
530
|
changed: false,
|
|
@@ -535,32 +537,23 @@ describe("IntegrationService", () => {
|
|
|
535
537
|
).toBe(customPlugin)
|
|
536
538
|
})
|
|
537
539
|
|
|
538
|
-
test("
|
|
539
|
-
const
|
|
540
|
-
|
|
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)
|
|
541
546
|
|
|
542
|
-
|
|
543
|
-
expect(
|
|
544
|
-
name: "opencode-command",
|
|
545
|
-
path: join(root, ".opencode/commands/agency.md"),
|
|
546
|
-
state: "customized",
|
|
547
|
-
changed: false,
|
|
548
|
-
})
|
|
549
|
-
expect(
|
|
550
|
-
await Bun.file(join(root, ".opencode/command/agency.md")).exists(),
|
|
551
|
-
).toBe(false)
|
|
547
|
+
await status(root)
|
|
548
|
+
expect(await Bun.file(join(root, path)).text()).toBe(legacy)
|
|
552
549
|
|
|
553
|
-
await
|
|
554
|
-
await
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
})
|
|
561
|
-
expect(
|
|
562
|
-
await Bun.file(join(root, ".opencode/command/agency.md")).text(),
|
|
563
|
-
).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)
|
|
564
557
|
})
|
|
565
558
|
|
|
566
559
|
test("migrates checksum-valid root instructions", async () => {
|