@oisincoveney/pipeline 2.1.0 → 2.2.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.
Files changed (39) hide show
  1. package/defaults/pipeline.yaml +111 -0
  2. package/defaults/profiles.yaml +212 -0
  3. package/defaults/runners.yaml +24 -0
  4. package/dist/argo-graph.js +59 -27
  5. package/dist/argo-submit.js +28 -4
  6. package/dist/claude-settings-config.js +44 -0
  7. package/dist/cli/program.js +1 -1
  8. package/dist/config/defaults.js +7 -353
  9. package/dist/git-remote-url.js +30 -0
  10. package/dist/hooks.d.ts +1 -1
  11. package/dist/install-commands/claude-code.js +153 -17
  12. package/dist/install-commands/opencode.js +37 -33
  13. package/dist/install-commands/shared.js +27 -6
  14. package/dist/install-commands.js +26 -22
  15. package/dist/json-config-merge.js +47 -0
  16. package/dist/mcp/gateway.js +9 -1
  17. package/dist/moka-submit.d.ts +6 -6
  18. package/dist/moka-submit.js +4 -3
  19. package/dist/opencode-project-config.js +2 -45
  20. package/dist/pipeline-runtime.js +50 -8
  21. package/dist/runner-event-schema.d.ts +349 -0
  22. package/dist/runner-event-schema.js +185 -0
  23. package/dist/runner-output.js +2 -2
  24. package/dist/runner.d.ts +2 -0
  25. package/dist/runtime/agent-node/agent-node.js +1 -0
  26. package/dist/runtime/contracts/contracts.d.ts +2 -0
  27. package/dist/runtime/node-state-store.js +7 -0
  28. package/dist/runtime/opencode-adapter.js +28 -8
  29. package/dist/runtime/opencode-agent-name.js +18 -0
  30. package/dist/runtime/opencode-runtime.js +62 -0
  31. package/dist/runtime/opencode-server.js +67 -0
  32. package/dist/runtime/opencode-session-executor.js +206 -0
  33. package/docs/adr-opencode-first-goal-loop-runtime.md +1 -1
  34. package/docs/config-architecture.md +11 -6
  35. package/docs/mcp-gateway.md +4 -4
  36. package/docs/operator-guide.md +7 -5
  37. package/docs/slash-command-adapter-contract.md +1 -0
  38. package/package.json +6 -1
  39. package/.agents/skills/claude-code-opencode-execute/SKILL.md +0 -116
@@ -0,0 +1,62 @@
1
+ import { OpencodeServerStartupError, openOpencodeServer } from "./opencode-server.js";
2
+ import { createOpencodeExecutor, createOpencodeSessionRegistry } from "./opencode-session-executor.js";
3
+ //#region src/runtime/opencode-runtime.ts
4
+ /**
5
+ * True when the config declares any opencode runner, i.e. the SDK transport is
6
+ * relevant for this run. Command-only configs never start a server.
7
+ */
8
+ function configUsesOpencode(config) {
9
+ return Object.values(config.runners).some((runner) => runner.type === "opencode");
10
+ }
11
+ /**
12
+ * Return an SDK-backed executor plus a release() that tears the server down.
13
+ * The opencode server is started LAZILY on the first executor call, not at
14
+ * lease time: a run whose ready nodes are all command/builtin (or that fails
15
+ * before any agent node executes) never spawns opencode, so the binary is only
16
+ * required when an agent node actually runs. release() is a no-op if the server
17
+ * was never started. Caller owns the lifecycle:
18
+ *
19
+ * const lease = await leaseOpencodeRuntime({ config, worktreePath });
20
+ * try { ...run with lease.executor... } finally { await lease.release(); }
21
+ */
22
+ function leaseOpencodeRuntime(input) {
23
+ const registry = createOpencodeSessionRegistry();
24
+ const openHandle = input.openServer ?? startServer;
25
+ let handle;
26
+ let pending;
27
+ const ensureExecutor = () => {
28
+ pending ??= openHandle(input).then((started) => {
29
+ handle = started;
30
+ return createOpencodeExecutor({
31
+ client: started.client,
32
+ directory: input.worktreePath,
33
+ registry
34
+ });
35
+ });
36
+ return pending;
37
+ };
38
+ return Promise.resolve({
39
+ executor: async (plan, options) => {
40
+ return await (await ensureExecutor())(plan, options);
41
+ },
42
+ release: async () => {
43
+ if (handle) {
44
+ await handle.close();
45
+ handle = void 0;
46
+ }
47
+ }
48
+ });
49
+ }
50
+ async function startServer(input) {
51
+ try {
52
+ return await openOpencodeServer({
53
+ directory: input.worktreePath,
54
+ ...input.signal ? { signal: input.signal } : {}
55
+ });
56
+ } catch (error) {
57
+ if (error instanceof OpencodeServerStartupError) throw new OpencodeServerStartupError(`${error.message}. Confirm the opencode binary is installed and recent enough to expose 'opencode serve', or set OPENCODE_SERVER_URL to an already-running server.`, { cause: error });
58
+ throw error;
59
+ }
60
+ }
61
+ //#endregion
62
+ export { configUsesOpencode, leaseOpencodeRuntime };
@@ -0,0 +1,67 @@
1
+ import { createOpencode, createOpencodeClient } from "@opencode-ai/sdk";
2
+ //#region src/runtime/opencode-server.ts
3
+ const DEFAULT_STARTUP_TIMEOUT_MS = 3e4;
4
+ var OpencodeServerStartupError = class extends Error {
5
+ constructor(message, options) {
6
+ super(message, options);
7
+ this.name = "OpencodeServerStartupError";
8
+ }
9
+ };
10
+ async function openOpencodeServer(options) {
11
+ const externalUrl = options.serverUrl ?? process.env.OPENCODE_SERVER_URL ?? "";
12
+ if (externalUrl.length > 0) return connectExternalServer(externalUrl, options.directory);
13
+ return await spawnOwnedServer(options);
14
+ }
15
+ function connectExternalServer(url, directory) {
16
+ return {
17
+ close: () => Promise.resolve(),
18
+ client: createOpencodeClient({
19
+ baseUrl: url,
20
+ directory
21
+ }),
22
+ owned: false,
23
+ url
24
+ };
25
+ }
26
+ async function spawnOwnedServer(options) {
27
+ const spawn = options.spawn ?? createOpencode;
28
+ try {
29
+ const { client, server } = await spawn(spawnArgs(options));
30
+ return ownedHandle(client, server, options.directory);
31
+ } catch (error) {
32
+ throw new OpencodeServerStartupError(`Failed to start opencode server: ${errorText(error)}`, { cause: error });
33
+ }
34
+ }
35
+ function spawnArgs(options) {
36
+ return {
37
+ port: 0,
38
+ ...options.signal ? { signal: options.signal } : {},
39
+ timeout: options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS
40
+ };
41
+ }
42
+ function ownedHandle(client, server, directory) {
43
+ return {
44
+ close: () => {
45
+ server.close();
46
+ return Promise.resolve();
47
+ },
48
+ client: withDirectory(client, server.url, directory),
49
+ owned: true,
50
+ url: server.url
51
+ };
52
+ }
53
+ function errorText(error) {
54
+ return error instanceof Error ? error.message : String(error);
55
+ }
56
+ function withDirectory(fallback, url, directory) {
57
+ try {
58
+ return createOpencodeClient({
59
+ baseUrl: url,
60
+ directory
61
+ });
62
+ } catch {
63
+ return fallback;
64
+ }
65
+ }
66
+ //#endregion
67
+ export { OpencodeServerStartupError, openOpencodeServer };
@@ -0,0 +1,206 @@
1
+ import { opencodeAgentName } from "./opencode-agent-name.js";
2
+ //#region src/runtime/opencode-session-executor.ts
3
+ function createOpencodeSessionRegistry() {
4
+ return { sessions: /* @__PURE__ */ new Map() };
5
+ }
6
+ /**
7
+ * Distinguish infra failure (server/session error -> retry-eligible exit 70)
8
+ * from a normal agent completion (the agent may still have produced a wrong
9
+ * answer; gates decide that, exit 0). This mirrors the EXIT_STARTUP convention
10
+ * in runner-command/run.ts and feeds retry.ts via the node's retry policy.
11
+ */
12
+ const EXIT_OK = 0;
13
+ const EXIT_AGENT_ERROR = 1;
14
+ const EXIT_INFRA = 70;
15
+ /**
16
+ * SDK-backed replacement for the subprocess `runLaunchPlan`. Conforms to the
17
+ * RuntimeContext.executor seam so agent-node never learns the transport.
18
+ */
19
+ function createOpencodeExecutor(deps) {
20
+ return async function execute(plan, options = {}) {
21
+ if (plan.type !== "opencode") throw new Error(`opencode executor cannot drive runner type '${plan.type}'`);
22
+ try {
23
+ return successResult(plan, await driveSession(deps, plan, options));
24
+ } catch (error) {
25
+ return failureResult(plan, error);
26
+ }
27
+ };
28
+ }
29
+ async function driveSession(deps, plan, options) {
30
+ const sessionId = await resolveSessionId(deps, plan);
31
+ deps.onSession?.(plan.nodeId, sessionId);
32
+ const stream = await streamEventsToOutput(deps, sessionId, plan, options);
33
+ try {
34
+ const data = unwrap(await deps.client.session.prompt({
35
+ body: promptBody(plan),
36
+ path: { id: sessionId },
37
+ query: { directory: deps.directory }
38
+ }));
39
+ return {
40
+ ...data.info ? { assistant: data.info } : {},
41
+ parts: data.parts ?? [],
42
+ sessionId
43
+ };
44
+ } finally {
45
+ await stream.stop();
46
+ }
47
+ }
48
+ async function resolveSessionId(deps, plan) {
49
+ const existing = deps.registry.sessions.get(plan.nodeId);
50
+ if (existing) return existing;
51
+ const session = unwrap(await deps.client.session.create({
52
+ body: { title: `moka:${plan.nodeId}` },
53
+ query: { directory: deps.directory }
54
+ }));
55
+ deps.registry.sessions.set(plan.nodeId, session.id);
56
+ return session.id;
57
+ }
58
+ function promptBody(plan) {
59
+ const prompt = promptText(plan);
60
+ const model = parseModel(plan.model);
61
+ const agent = plan.profileId ? opencodeAgentName(plan.profileId) : void 0;
62
+ return {
63
+ parts: [{
64
+ text: prompt,
65
+ type: "text"
66
+ }],
67
+ ...agent ? { agent } : {},
68
+ ...model ? { model } : {}
69
+ };
70
+ }
71
+ const FLAGS_TAKING_VALUE = new Set([
72
+ "--model",
73
+ "--dir",
74
+ "--file",
75
+ "--format"
76
+ ]);
77
+ /**
78
+ * The launch plan carries the prompt inside the CLI argv (`run <prompt>` or
79
+ * `run <prompt> --file <ctx>`). Recover it as the trailing positional arg
80
+ * (skipping flags and their values) so the adapter boundary is identical
81
+ * regardless of transport.
82
+ */
83
+ function promptText(plan) {
84
+ return plan.args.filter((arg, index) => index > 0 && !arg.startsWith("-") && !FLAGS_TAKING_VALUE.has(plan.args[index - 1] ?? "")).at(-1) ?? "";
85
+ }
86
+ function parseModel(model) {
87
+ if (!model) return;
88
+ const slash = model.indexOf("/");
89
+ if (slash === -1) return;
90
+ return {
91
+ modelID: model.slice(slash + 1),
92
+ providerID: model.slice(0, slash)
93
+ };
94
+ }
95
+ async function streamEventsToOutput(deps, sessionId, plan, options) {
96
+ if (!options.onOutput) return { stop: () => Promise.resolve() };
97
+ const iterator = (await deps.client.event.subscribe()).stream;
98
+ const pump = (async () => {
99
+ try {
100
+ for await (const event of iterator) forwardEvent(event, sessionId, plan, options);
101
+ } catch (error) {
102
+ options.onOutput?.({
103
+ chunk: `opencode event stream dropped: ${errorMessage(error)}\n`,
104
+ nodeId: plan.nodeId,
105
+ stream: "stderr"
106
+ });
107
+ }
108
+ })();
109
+ return { stop: async () => {
110
+ await iterator.return?.(void 0);
111
+ await pump;
112
+ } };
113
+ }
114
+ function forwardEvent(event, sessionId, plan, options) {
115
+ const forwarded = eventChunk(event, sessionId);
116
+ if (forwarded) options.onOutput?.({
117
+ chunk: forwarded.chunk,
118
+ nodeId: plan.nodeId,
119
+ stream: forwarded.stream
120
+ });
121
+ }
122
+ function eventChunk(event, sessionId) {
123
+ if (event.type === "message.part.updated") return partChunk(event.properties.part, sessionId);
124
+ if (event.type === "session.error" && belongsToSession(event, sessionId)) return {
125
+ chunk: `opencode session error: ${describeMessageError(event.properties.error)}\n`,
126
+ stream: "stderr"
127
+ };
128
+ }
129
+ function belongsToSession(event, sessionId) {
130
+ return event.properties.sessionID === void 0 || event.properties.sessionID === sessionId;
131
+ }
132
+ function partChunk(part, sessionId) {
133
+ if (part.sessionID !== sessionId) return;
134
+ if (part.type === "text") return {
135
+ chunk: `${JSON.stringify({ part: {
136
+ text: part.text,
137
+ type: "text"
138
+ } })}\n`,
139
+ stream: "stdout"
140
+ };
141
+ if (part.type === "tool") return {
142
+ chunk: `opencode tool ${part.tool} ${part.state.status}\n`,
143
+ stream: "stderr"
144
+ };
145
+ }
146
+ /**
147
+ * Reconstruct the JSONL stdout the existing normalizeOutput/outputCandidates
148
+ * parser already understands, so the structured-output and repair passes work
149
+ * unchanged on top of SDK responses.
150
+ */
151
+ function successResult(plan, drive) {
152
+ const stdout = drive.parts.filter((part) => part.type === "text").map((part) => JSON.stringify({ part: {
153
+ text: part.text,
154
+ type: "text"
155
+ } })).join("\n");
156
+ const assistantError = drive.assistant?.error;
157
+ if (assistantError) return {
158
+ argv: plan.args,
159
+ exitCode: infraErrorExitCode(assistantError),
160
+ sessionId: drive.sessionId,
161
+ stderr: describeMessageError(assistantError),
162
+ stdout
163
+ };
164
+ return {
165
+ argv: plan.args,
166
+ exitCode: EXIT_OK,
167
+ sessionId: drive.sessionId,
168
+ stdout
169
+ };
170
+ }
171
+ function failureResult(plan, error) {
172
+ return {
173
+ argv: plan.args,
174
+ exitCode: EXIT_INFRA,
175
+ stderr: `opencode session failed: ${errorMessage(error)}`,
176
+ stdout: ""
177
+ };
178
+ }
179
+ /**
180
+ * Map opencode message errors to the runner's infra-vs-agent exit convention.
181
+ * Output-length / aborted are agent-task outcomes (exit 1, gate territory);
182
+ * provider-auth, API, and unknown are infra (exit 70, retry-eligible).
183
+ */
184
+ function infraErrorExitCode(error) {
185
+ switch (error.name) {
186
+ case "MessageOutputLengthError":
187
+ case "MessageAbortedError": return EXIT_AGENT_ERROR;
188
+ default: return EXIT_INFRA;
189
+ }
190
+ }
191
+ function describeMessageError(error) {
192
+ if (!error) return "unknown opencode error";
193
+ const data = error.data;
194
+ const detail = data && typeof data.message === "string" ? `: ${data.message}` : "";
195
+ return `${error.name}${detail}`;
196
+ }
197
+ function unwrap(response) {
198
+ if (response.error) throw new Error(typeof response.error === "string" ? response.error : JSON.stringify(response.error));
199
+ if (response.data === void 0) throw new Error("opencode response contained no data");
200
+ return response.data;
201
+ }
202
+ function errorMessage(error) {
203
+ return error instanceof Error ? error.message : String(error);
204
+ }
205
+ //#endregion
206
+ export { createOpencodeExecutor, createOpencodeSessionRegistry };
@@ -1,6 +1,6 @@
1
1
  # ADR: OpenCode-First Goal Loop Runtime
2
2
 
3
- Status: Accepted
3
+ Status: Accepted (Codex compatibility subsequently removed; OpenCode and Claude Code are the active command hosts)
4
4
 
5
5
  Date: 2026-06-08
6
6
 
@@ -306,14 +306,19 @@ timeouts, output limits, sanitized env, and explicit trust flags.
306
306
 
307
307
  ## Host Support Matrix
308
308
 
309
- | Runner | Native subagents | Rules | Skills | MCP | Outputs | Generated resources |
310
- | -------- | ---------------- | ----- | ------ | --- | ------------------------- | ------------------------------- |
311
- | OpenCode | yes | yes | yes | yes | text, JSON, JSONL, schema | commands, agents, skills, plugins, LSP |
312
- | command | no | no | no | no | declared by runner | subprocess argv |
309
+ | Runner | Native subagents | Rules | Skills | MCP | Outputs | Generated resources |
310
+ | ----------- | ---------------- | ----- | ------ | --- | ------------------------- | ------------------------------- |
311
+ | OpenCode | yes | yes | yes | yes | text, JSON, JSONL, schema | commands, agents, skills, plugins, LSP |
312
+ | Claude Code | via `opencode run` | yes (skill) | yes | yes | declared by runner | commands, wrapper agents, settings |
313
+ | command | no | no | no | no | declared by runner | subprocess argv |
313
314
 
314
315
  Generated host resources follow a native runner rule. OpenCode runner nodes use
315
- OpenCode native agents. Unsupported runner or host mappings fail closed instead
316
- of doing instruction-only translation or generic worker substitution.
316
+ OpenCode native agents. The Claude Code host does not run MoKa profiles natively:
317
+ its generated `.claude/agents/moka-<role>.md` subagents each wrap a single
318
+ `opencode run --agent "MoKa <role>"` subprocess, so the OpenCode runner remains
319
+ the execution surface while Claude Code orchestrates. Unsupported runner or host
320
+ mappings fail closed instead of doing instruction-only translation or generic
321
+ worker substitution.
317
322
 
318
323
  ## Troubleshooting
319
324
 
@@ -9,7 +9,7 @@ Playwright, Qdrant, or Neon.
9
9
  ## Target Shape
10
10
 
11
11
  ```text
12
- Codex/OpenCode
12
+ OpenCode / Claude Code
13
13
  |
14
14
  | project-level MCP config
15
15
  v
@@ -47,7 +47,7 @@ host-specific MCP config.
47
47
  3. Configure `mcp_gateway` in package-owned profile config.
48
48
  4. Run `moka mcp gateway reconcile` to render and apply the full ToolHive vMCP
49
49
  backend inventory for the current workspace.
50
- 5. Run `moka init` to write project Codex/OpenCode command surfaces and host
50
+ 5. Run `moka init` to write project OpenCode and Claude Code command surfaces and host
51
51
  MCP config.
52
52
  6. Run `moka mcp gateway doctor` to verify gateway health and required tools.
53
53
  7. Keep high-risk upstream capabilities controlled by gateway-side policy, not
@@ -88,7 +88,7 @@ orchestrator MCP set * subagent count * host config layers
88
88
  ```
89
89
 
90
90
  With a gateway, the runtime launches zero local upstream MCP processes for
91
- agents. Codex and OpenCode read the same project-level host config, which
91
+ agents. OpenCode and Claude Code read the same project-level host config, which
92
92
  contains only `pipeline-gateway`.
93
93
 
94
94
  OpenCode receives that gateway through `.opencode/opencode.json` alongside the
@@ -114,7 +114,7 @@ adding one backend must not replace the existing Context7, uidotsh, Qdrant,
114
114
  Fallow, Serena, or Backlog declarations. Use `moka mcp gateway doctor` to check
115
115
  required environment variables, gateway health, required `tools/list` prefixes,
116
116
  local ToolHive availability for local mode, and legacy direct MCP entries. Use
117
- `moka init` to install generated Codex and OpenCode host surfaces with the
117
+ `moka init` to install generated OpenCode and Claude Code host surfaces with the
118
118
  singleton `pipeline-gateway` remote entry. Use `moka install-commands --host all`
119
119
  to refresh generated host files after package upgrades, and use
120
120
  `moka mcp gateway configure-host` as an explicit migration or repair command
@@ -302,8 +302,8 @@ Troubleshooting:
302
302
  Generated invocations include:
303
303
 
304
304
  ```text
305
- OpenCode: /moka-quick, /moka-execute, /moka-inspect
306
- Codex: $quick, $execute, $inspect
305
+ OpenCode: /moka-quick, /moka-execute, /moka-inspect
306
+ Claude Code: /moka-quick, /moka-execute, /moka-inspect
307
307
  ```
308
308
 
309
309
  `moka init` and `moka install-commands --host opencode` generate:
@@ -316,9 +316,11 @@ Codex: $quick, $execute, $inspect
316
316
  - `.opencode/opencode.json` with LSP, the singleton `pipeline-gateway` MCP
317
317
  server, and pinned package-selected plugins
318
318
 
319
+ `moka install-commands --host claude-code` generates `.claude/commands/moka-<entrypoint>.md`
320
+ slash commands for Claude Code.
321
+
319
322
  Package defaults select OpenCode for built-in profiles and runner-command
320
- orchestration. Codex compatibility stays generated through `$quick`, `$execute`,
321
- `$inspect`, and Codex agent config, but it is not the default package runtime.
323
+ orchestration. Codex is not a supported runtime host.
322
324
 
323
325
  ## How The Package Works
324
326
 
@@ -373,7 +375,7 @@ profiles, workflows, or node-level skill overrides.
373
375
 
374
376
  ## Configuring MCP Gateway
375
377
 
376
- MCP access is host-level and gateway-only. Codex, OpenCode, pipeline agents,
378
+ MCP access is host-level and gateway-only. OpenCode, Claude Code, pipeline agents,
377
379
  manual sessions, and CI all connect to one ToolHive/vMCP gateway URL. Do not
378
380
  start upstream MCP servers from individual sessions.
379
381
 
@@ -16,6 +16,7 @@ moka install-commands --host all --check
16
16
  | Host | Generated resources | Invocation | Mechanical path |
17
17
  | -------- | -------------------------------------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------- |
18
18
  | OpenCode | `.opencode/commands/moka-<entrypoint>.md`, `.opencode/agents/*.md`, `.opencode/opencode.json` | `/moka-quick <task>`, `/moka-execute <task>`, `/moka-inspect <task>` | Project commands run a primary orchestrator and OpenCode native subagents with package-owned skill, MCP, permission, and LSP projection. |
19
+ | Claude Code | `.claude/commands/moka-<entrypoint>.md`, `.claude/agents/moka-<role>.md`, `.claude/settings.json` | `/moka-quick <task>`, `/moka-execute <task>`, `/moka-inspect <task>` | Slash commands load the `execute` skill, then dispatch each agent node to a Claude Code `Task` subagent that wraps a single `opencode run --agent "MoKa <role>"` subprocess. `.claude/settings.json` is merged (gateway MCP + `Bash(opencode run *)` permission), never clobbered. |
19
20
 
20
21
  ## Projection Rules
21
22
 
package/package.json CHANGED
@@ -2,6 +2,7 @@
2
2
  "dependencies": {
3
3
  "@dagrejs/graphlib": "^4.0.1",
4
4
  "@kubernetes/client-node": "^1.4.0",
5
+ "@opencode-ai/sdk": "1.17.4",
5
6
  "ajv": "^8.20.0",
6
7
  "ajv-formats": "^3.0.1",
7
8
  "commander": "^14.0.3",
@@ -62,6 +63,10 @@
62
63
  "types": "./dist/config.d.ts",
63
64
  "import": "./dist/config.js"
64
65
  },
66
+ "./events": {
67
+ "types": "./dist/runner-event-schema.d.ts",
68
+ "import": "./dist/runner-event-schema.js"
69
+ },
65
70
  "./hooks": {
66
71
  "types": "./dist/hooks.d.ts",
67
72
  "import": "./dist/hooks.js"
@@ -115,7 +120,7 @@
115
120
  "prepack": "bun run build:cli"
116
121
  },
117
122
  "type": "module",
118
- "version": "2.1.0",
123
+ "version": "2.2.0",
119
124
  "description": "Config-driven multi-agent pipeline runner for repository work",
120
125
  "main": "./dist/index.js",
121
126
  "types": "./dist/index.d.ts",
@@ -1,116 +0,0 @@
1
- ---
2
- name: claude-code-opencode-execute
3
- description: Use in Claude Code when executing Moka work locally through OpenCode; loads execute first, then spawns Claude Task agents that run opencode run with the correct MoKa agent prompts.
4
- allowed-tools: Bash(opencode run *) Bash(pwd) Bash(git status *) Task
5
- ---
6
-
7
- # Claude Code OpenCode Execute
8
-
9
- Use this skill when Claude Code is the interactive host, but the work should be executed by local OpenCode MoKa agents through `opencode run` subprocesses.
10
-
11
- This skill is a host adapter. It does not replace [[execute]]. Load and follow [[execute]] first; use this skill only for the Claude Code dispatch mechanics.
12
-
13
- ## Contract
14
-
15
- 1. Load [[execute]] and let it classify the request, required companion skills, acceptance criteria, and verification path.
16
- 2. Keep the execution doctrine from [[execute]]: vertical slices, test-first for behavior, root-cause fixes, fidelity checks, critique, and verification.
17
- 3. Use Claude Code `Task` subagents as wrappers around local OpenCode subprocesses when work can be delegated.
18
- 4. Each delegated Task agent should run exactly one MoKa-flavored OpenCode command and return the command, exit status, parsed evidence, touched files, and blockers.
19
- 5. Batch independent nodes in parallel, wait at barriers, synthesize outputs, then decide the next batch.
20
-
21
- ## OpenCode command shape
22
-
23
- Task agents should run OpenCode with this shape from the repository root:
24
-
25
- ```sh
26
- opencode run --agent "<MoKa Agent Name>" --format json --dir "$PWD" '<node prompt>'
27
- ```
28
-
29
- Use `--format json` so the parent can inspect structured event output. If the output contains multiple assistant text candidates, prefer the latest candidate that satisfies the requested JSON or evidence contract.
30
-
31
- Do not use `moka submit` for this local Claude Code adapter unless the user explicitly asks to submit an Argo Workflow. `moka submit` is the durable pipeline path; this skill is for interactive local orchestration through Claude Task agents and `opencode run`.
32
-
33
- ## Agent Selection
34
-
35
- Select the MoKa agent by the slice's role:
36
-
37
- | Role | OpenCode agent |
38
- | --- | --- |
39
- | Intake, repository research, requirements clarification | `MoKa Researcher` |
40
- | Read-only code inspection | `MoKa Inspector` |
41
- | Schedule graph generation or schedule review | `MoKa Schedule Planner` |
42
- | Focused failing tests | `MoKa Test Writer` |
43
- | Production implementation | `MoKa Code Writer` |
44
- | Acceptance criteria audit | `MoKa Acceptance Reviewer` |
45
- | Final quality review | `MoKa Thermo Nuclear Reviewer` |
46
- | Verification evidence and command checks | `MoKa Verifier` |
47
- | Durable lessons after a completed run | `MoKa Learner` |
48
-
49
- Do not delegate normal child work to `MoKa Orchestrator`; the Claude Code parent is the local orchestrator for this adapter.
50
-
51
- ## Prompt Contract
52
-
53
- Every `opencode run` prompt must include:
54
-
55
- - The original user task.
56
- - The current execution contract from [[execute]].
57
- - The node id and role.
58
- - The selected MoKa agent name and why it was selected.
59
- - The exact files or modules in scope, or a read-only discovery scope.
60
- - Dependency outputs from earlier nodes.
61
- - The acceptance criteria this node owns.
62
- - The output shape expected by the parent.
63
-
64
- Use this template for delegated prompts:
65
-
66
- ```text
67
- You are running as <MoKa Agent Name> for a Claude Code local Moka execution.
68
-
69
- Original task:
70
- <user task>
71
-
72
- Execution contract:
73
- <contract produced by execute>
74
-
75
- Node:
76
- - id: <node id>
77
- - role: <role>
78
- - selected agent: <MoKa Agent Name>
79
- - scope: <files/modules/commands>
80
-
81
- Dependency outputs:
82
- <summaries or "none">
83
-
84
- Acceptance criteria owned by this node:
85
- <criteria>
86
-
87
- Instructions:
88
- - Follow the skills and grants configured for this MoKa agent.
89
- - Stay inside this node's scope.
90
- - Do not claim completion without fresh evidence.
91
- - Return only the requested output shape.
92
-
93
- Output shape:
94
- <JSON or concise evidence contract>
95
- ```
96
-
97
- ## Batching Rules
98
-
99
- - Run read-only discovery agents in parallel when their scopes do not require a shared conclusion first.
100
- - Run test-writing before production implementation for behavior changes.
101
- - Run multiple `MoKa Code Writer` agents in the same batch only when [[execute]] has produced independent vertical slices with disjoint files or clearly isolated worktrees.
102
- - Never let two write-capable agents edit the same file in the same batch.
103
- - Run acceptance review, final quality review, and verifier after implementation outputs have been integrated.
104
- - If any delegated command fails, stop the batch, read the output, classify the blocker, and return to [[execute]] routing instead of retrying blindly.
105
-
106
- ## Parent Responsibilities
107
-
108
- The Claude Code parent must:
109
-
110
- - Inspect outputs before launching the next batch.
111
- - Integrate or reconcile changes itself when multiple agents wrote code.
112
- - Run the verification required by [[execute]] in the real repository context.
113
- - Inspect the final diff for accidental edits, secrets, generated churn, and unrelated changes.
114
- - Report partial verification honestly if the real command path cannot be exercised.
115
-
116
- This skill is complete only when [[execute]] would allow the parent to claim completion.