@oisincoveney/pipeline 2.0.0 → 2.1.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.
@@ -73,6 +73,9 @@ skills:
73
73
  quick:
74
74
  path: .agents/skills/quick/SKILL.md
75
75
  source_root: package
76
+ claude-code-opencode-execute:
77
+ path: .agents/skills/claude-code-opencode-execute/SKILL.md
78
+ source_root: package
76
79
  critique:
77
80
  path: .agents/skills/critique/SKILL.md
78
81
  source_root: package
@@ -141,6 +144,7 @@ profiles:
141
144
  repair: { enabled: true, max_attempts: 1 }
142
145
  moka-inspector:
143
146
  runner: opencode
147
+ model: openai/gpt-5.5-low
144
148
  description: Inspect the repository without modifying files.
145
149
  instructions: { inline: "Inspect the repository without modifying files." }
146
150
  skills: [research]
@@ -150,6 +154,7 @@ profiles:
150
154
  network: { mode: inherit }
151
155
  moka-schedule-planner:
152
156
  runner: opencode
157
+ model: openai/gpt-5.5-xhigh
153
158
  description: Refine a baseline schedule into a specialized approved-plan artifact.
154
159
  instructions: { inline: "Generate exactly one workflow named root as an explicit schedule graph. Return YAML only." }
155
160
  timeout_ms: 300000
@@ -230,6 +235,7 @@ profiles:
230
235
  repair: { enabled: true, max_attempts: 1 }
231
236
  moka-learner:
232
237
  runner: opencode
238
+ model: openai/gpt-5.5-low
233
239
  description: Store durable lessons from the completed run.
234
240
  instructions: { inline: "Store durable lessons from the completed run when useful." }
235
241
  skills: [migrate]
@@ -289,54 +295,54 @@ scheduler:
289
295
  backlog-intake:
290
296
  category: intake
291
297
  profile: moka-researcher
292
- models: [openai/gpt-5.5-fast]
298
+ models: [openai/gpt-5.5-medium]
293
299
  red-tests:
294
300
  category: red
295
301
  profile: moka-test-writer
296
- models: [openai/gpt-5.5, kimi-for-coding/kimi-k2-thinking]
302
+ models: [openai/gpt-5.5-high, kimi-for-coding/kimi-k2-thinking]
297
303
  green-implementation:
298
304
  category: green
299
305
  profile: moka-code-writer
300
- models: [openai/gpt-5.5, kimi-for-coding/k2p6, opencode-go/qwen3.7-max]
306
+ models: [openai/gpt-5.5-high, kimi-for-coding/k2p6, opencode-go/qwen3.7-max]
301
307
  verification:
302
308
  category: verification
303
309
  profile: moka-verifier
304
- models: [openai/gpt-5.5]
310
+ models: [openai/gpt-5.5-medium]
305
311
  execute:
306
312
  required_categories: [intake, research, red, green, mechanical, acceptance, verification, learn]
307
313
  nodes:
308
314
  backlog-intake:
309
315
  category: intake
310
316
  profile: moka-researcher
311
- models: [openai/gpt-5.5-fast]
317
+ models: [openai/gpt-5.5-medium]
312
318
  research:
313
319
  category: research
314
320
  profile: moka-researcher
315
- models: [openai/gpt-5.5-fast, kimi-for-coding/k2p6]
321
+ models: [openai/gpt-5.5-medium, kimi-for-coding/k2p6]
316
322
  red-tests:
317
323
  category: red
318
324
  profile: moka-test-writer
319
- models: [openai/gpt-5.5, kimi-for-coding/kimi-k2-thinking]
325
+ models: [openai/gpt-5.5-high, kimi-for-coding/kimi-k2-thinking]
320
326
  green-backend:
321
327
  category: green
322
328
  profile: moka-code-writer
323
- models: [openai/gpt-5.5, kimi-for-coding/k2p6, opencode-go/qwen3.7-max]
329
+ models: [openai/gpt-5.5-high, kimi-for-coding/k2p6, opencode-go/qwen3.7-max]
324
330
  green-frontend:
325
331
  category: green
326
332
  profile: moka-code-writer
327
- models: [openai/gpt-5.5, kimi-for-coding/k2p6, opencode-go/qwen3.7-max]
333
+ models: [openai/gpt-5.5-high, kimi-for-coding/k2p6, opencode-go/qwen3.7-max]
328
334
  acceptance-review:
329
335
  category: acceptance
330
336
  profile: moka-acceptance-reviewer
331
- models: [openai/gpt-5.5]
337
+ models: [openai/gpt-5.5-medium]
332
338
  verification:
333
339
  category: verification
334
340
  profile: moka-verifier
335
- models: [openai/gpt-5.5]
341
+ models: [openai/gpt-5.5-medium]
336
342
  learn:
337
343
  category: learn
338
344
  profile: moka-learner
339
- models: [openai/gpt-5.5-fast]
345
+ models: [openai/gpt-5.5-low]
340
346
  schedules:
341
347
  quick-schedule:
342
348
  baseline: quick
@@ -385,6 +391,29 @@ const ecosystemCodeSchema = z.object({
385
391
  role: z.string().min(1),
386
392
  source: z.string().url()
387
393
  }).strict();
394
+ const ecosystemProviderModelOptionsSchema = z.object({
395
+ include: ecosystemStringArraySchema,
396
+ reasoningEffort: z.enum([
397
+ "none",
398
+ "low",
399
+ "medium",
400
+ "high",
401
+ "xhigh"
402
+ ]),
403
+ reasoningSummary: z.enum(["auto", "detailed"]),
404
+ store: z.literal(false),
405
+ textVerbosity: z.enum([
406
+ "low",
407
+ "medium",
408
+ "high"
409
+ ])
410
+ }).strict();
411
+ const ecosystemProviderModelSchema = z.object({
412
+ id: z.string().min(1),
413
+ options: ecosystemProviderModelOptionsSchema,
414
+ provider: z.string().min(1),
415
+ role: z.string().min(1)
416
+ }).strict();
388
417
  const ecosystemMcpBackendSchema = z.object({
389
418
  credentials: ecosystemStringArraySchema,
390
419
  id: z.string().min(1),
@@ -421,6 +450,7 @@ const openCodeEcosystemManifestSchema = z.object({
421
450
  mcp_backends: z.array(ecosystemMcpBackendSchema).min(1),
422
451
  official_dependencies: z.array(ecosystemDependencySchema).min(1),
423
452
  prompts: z.array(ecosystemProfileResourceSchema).min(1),
453
+ provider_models: z.array(ecosystemProviderModelSchema).min(1),
424
454
  runtime: ecosystemRuntimeSchema,
425
455
  skills: z.array(ecosystemProfileResourceSchema).min(1),
426
456
  sources: z.array(ecosystemSourceSchema).min(1),
@@ -310,6 +310,7 @@ declare const configSchema: z.ZodObject<{
310
310
  skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
311
311
  timeout_ms: z.ZodOptional<z.ZodNumber>;
312
312
  tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
313
+ task: "task";
313
314
  read: "read";
314
315
  list: "list";
315
316
  grep: "grep";
@@ -317,7 +318,6 @@ declare const configSchema: z.ZodObject<{
317
318
  bash: "bash";
318
319
  edit: "edit";
319
320
  write: "write";
320
- task: "task";
321
321
  }>>>;
322
322
  }, z.core.$strict>>>;
323
323
  runner_command: z.ZodDefault<z.ZodObject<{
@@ -343,8 +343,8 @@ declare const configSchema: z.ZodObject<{
343
343
  rules: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
344
344
  path: z.ZodString;
345
345
  source_root: z.ZodDefault<z.ZodEnum<{
346
- package: "package";
347
346
  project: "project";
347
+ package: "package";
348
348
  }>>;
349
349
  }, z.core.$strict>>>;
350
350
  runners: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -369,6 +369,7 @@ declare const configSchema: z.ZodObject<{
369
369
  rules: z.ZodOptional<z.ZodBoolean>;
370
370
  skills: z.ZodOptional<z.ZodBoolean>;
371
371
  tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
372
+ task: "task";
372
373
  read: "read";
373
374
  list: "list";
374
375
  grep: "grep";
@@ -376,7 +377,6 @@ declare const configSchema: z.ZodObject<{
376
377
  bash: "bash";
377
378
  edit: "edit";
378
379
  write: "write";
379
- task: "task";
380
380
  }>>>;
381
381
  }, z.core.$strict>;
382
382
  command: z.ZodOptional<z.ZodString>;
@@ -471,8 +471,8 @@ declare const configSchema: z.ZodObject<{
471
471
  schedules: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
472
472
  description: z.ZodOptional<z.ZodString>;
473
473
  baseline: z.ZodEnum<{
474
- execute: "execute";
475
474
  quick: "quick";
475
+ execute: "execute";
476
476
  }>;
477
477
  max_parallel_nodes: z.ZodOptional<z.ZodNumber>;
478
478
  node_catalog: z.ZodOptional<z.ZodString>;
@@ -484,8 +484,8 @@ declare const configSchema: z.ZodObject<{
484
484
  skills: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
485
485
  path: z.ZodString;
486
486
  source_root: z.ZodDefault<z.ZodEnum<{
487
- package: "package";
488
487
  project: "project";
488
+ package: "package";
489
489
  }>>;
490
490
  }, z.core.$strict>>>;
491
491
  task_context: z.ZodOptional<z.ZodObject<{
package/dist/hooks.d.ts CHANGED
@@ -13,8 +13,8 @@ declare const hookResultSchema: z.ZodObject<{
13
13
  taskContext: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
14
14
  }, z.core.$strict>>;
15
15
  status: z.ZodEnum<{
16
- fail: "fail";
17
16
  pass: "pass";
17
+ fail: "fail";
18
18
  skip: "skip";
19
19
  }>;
20
20
  summary: z.ZodOptional<z.ZodString>;
@@ -0,0 +1,24 @@
1
+ import { resolvePackageAssetPath } from "../package-assets.js";
2
+ import { GENERATED_MARKER, OWNER_MARKER_PREFIX, invocationForHost } from "./shared.js";
3
+ import { readFileSync } from "node:fs";
4
+ //#region src/install-commands/claude-code.ts
5
+ const CLAUDE_CODE_OPENCODE_EXECUTE_SKILL = "claude-code-opencode-execute";
6
+ const PACKAGE_SKILL_PATH = `.agents/skills/${CLAUDE_CODE_OPENCODE_EXECUTE_SKILL}/SKILL.md`;
7
+ function generatedClaudeSkillContent() {
8
+ return readFileSync(resolvePackageAssetPath(PACKAGE_SKILL_PATH), "utf8").replace("# Claude Code OpenCode Execute", [
9
+ GENERATED_MARKER,
10
+ `${OWNER_MARKER_PREFIX}host=claude-code -->`,
11
+ "",
12
+ "# Claude Code OpenCode Execute"
13
+ ].join("\n"));
14
+ }
15
+ function claudeCodeDefinitions() {
16
+ return [{
17
+ content: generatedClaudeSkillContent(),
18
+ host: "claude-code",
19
+ invocation: invocationForHost("claude-code", CLAUDE_CODE_OPENCODE_EXECUTE_SKILL),
20
+ path: `.claude/skills/${CLAUDE_CODE_OPENCODE_EXECUTE_SKILL}/SKILL.md`
21
+ }];
22
+ }
23
+ //#endregion
24
+ export { claudeCodeDefinitions };
@@ -0,0 +1,434 @@
1
+ import { DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST } from "../config/defaults.js";
2
+ import { resolvePackageAssetPath } from "../package-assets.js";
3
+ import "../config.js";
4
+ import { compileWorkflowPlan } from "../workflow-planner.js";
5
+ import { renderOpenCodeGatewayConfig } from "../mcp/gateway.js";
6
+ import { AGENTS_MD_END, AGENTS_MD_START, COMMAND_HOSTS, GENERATED_MARKER, GENERATED_TS_MARKER, OWNER_MARKER_PREFIX, OWNER_TS_MARKER_PREFIX, SINGLE_OPENCODE_PLUGIN_ARRAY_RE, commandIdForHost, invocationForHost } from "./shared.js";
7
+ import { readFileSync } from "node:fs";
8
+ import { basename } from "node:path";
9
+ import matter from "gray-matter";
10
+ //#region src/install-commands/opencode.ts
11
+ const OPENCODE_ORCHESTRATOR_AGENT_ID = "MoKa Orchestrator";
12
+ const MOKA_PROFILE_PREFIX = "moka-";
13
+ function header(host) {
14
+ return [
15
+ GENERATED_MARKER,
16
+ `${OWNER_MARKER_PREFIX}host=${host} -->`,
17
+ ""
18
+ ].join("\n");
19
+ }
20
+ function markdown(data, body) {
21
+ return `${matter.stringify(body.trimEnd(), data).trimEnd()}\n`;
22
+ }
23
+ function profileEntries(config) {
24
+ return Object.entries(config.profiles).sort(([a], [b]) => a.localeCompare(b));
25
+ }
26
+ function entrypointEntries(config) {
27
+ const entries = Object.entries(config.entrypoints);
28
+ return entries.length > 0 ? entries : [["execute", {
29
+ description: "Run the configured pipeline workflow",
30
+ workflow: config.default_workflow
31
+ }]];
32
+ }
33
+ function entrypointDescription(id, entrypoint) {
34
+ return entrypoint.description ?? `Run the ${id} workflow`;
35
+ }
36
+ function entrypointCommandDefinitions(_host, config, makeDefinition) {
37
+ return entrypointEntries(config).map(([id, entrypoint]) => makeDefinition(id, entrypoint));
38
+ }
39
+ function nativeProfileEntries(host, config) {
40
+ return profileEntries(config).filter(([id, profile]) => id !== config.orchestrator?.profile && canRunNatively(host, profile));
41
+ }
42
+ function orchestratorProfile(config) {
43
+ if (!config.orchestrator) return;
44
+ const profile = config.profiles[config.orchestrator.profile];
45
+ if (!profile) throw new Error(`Orchestrator profile '${config.orchestrator.profile}' is not declared.`);
46
+ return { ...profile };
47
+ }
48
+ function resolvedHostModel(config, host, profile) {
49
+ const runner = config.runners[profile.runner];
50
+ const hostRunner = config.runners[host];
51
+ if (profile.host_models?.[host]) return profile.host_models[host];
52
+ if (runner?.host_models?.[host]) return runner.host_models[host];
53
+ if (profile.runner === host) return profile.model ?? runner?.model;
54
+ return hostRunner?.model;
55
+ }
56
+ function canRunNatively(host, profile) {
57
+ if (profile.runner === host) return true;
58
+ return host === "opencode" && isModelRunner(profile.runner);
59
+ }
60
+ function isModelRunner(runnerId) {
61
+ return COMMAND_HOSTS.some((host) => host === runnerId);
62
+ }
63
+ function agentDispatchRoutes(host, config, workflowId = config.default_workflow) {
64
+ return compileWorkflowPlan(config, workflowId).topologicalOrder.flatMap((node) => {
65
+ if (!(node.kind === "agent" && node.profile)) return [];
66
+ const profile = config.profiles[node.profile];
67
+ if (!profile) return [];
68
+ return [dispatchRouteForAgent(host, config, {
69
+ needs: node.needs,
70
+ nodeId: node.id,
71
+ profile,
72
+ profileId: node.profile
73
+ })];
74
+ });
75
+ }
76
+ function dispatchRouteForAgent(host, config, route) {
77
+ const runnerId = route.profile.runner;
78
+ if (runnerId === host) {
79
+ const model = resolvedHostModel(config, host, route.profile);
80
+ return {
81
+ ...route,
82
+ kind: "native-named-agent",
83
+ ...model ? { model } : {},
84
+ nativeAgentId: nativeAgentIdForHost(host, route.profileId),
85
+ runnerId
86
+ };
87
+ }
88
+ if (host === "opencode" && isModelRunner(runnerId)) {
89
+ const model = resolvedHostModel(config, host, route.profile);
90
+ return {
91
+ ...route,
92
+ kind: "native-model-agent",
93
+ ...model ? { model } : {},
94
+ nativeAgentId: nativeAgentIdForHost(host, route.profileId),
95
+ runnerId
96
+ };
97
+ }
98
+ return {
99
+ ...route,
100
+ kind: "cli",
101
+ runnerId
102
+ };
103
+ }
104
+ function nativeAgentIdForHost(host, profileId) {
105
+ return host === "opencode" ? opencodeAgentName(profileId) : profileId;
106
+ }
107
+ function opencodeAgentName(profileId) {
108
+ if (!profileId.startsWith(MOKA_PROFILE_PREFIX)) return profileId;
109
+ return `MoKa ${profileId.slice(5).split("-").map(opencodeAgentNamePart).join(" ")}`;
110
+ }
111
+ function opencodeAgentNamePart(part) {
112
+ if (part === "opencode") return "OpenCode";
113
+ return `${part.charAt(0).toUpperCase()}${part.slice(1)}`;
114
+ }
115
+ function grants(actor) {
116
+ return [
117
+ `model: ${actor.model ?? "default"}`,
118
+ `tools: ${(actor.tools ?? []).join(", ") || "none"}`,
119
+ `rules: ${(actor.rules ?? []).join(", ") || "none"}`,
120
+ `skills: ${(actor.skills ?? []).join(", ") || "none"}`,
121
+ `mcp_servers: ${(actor.mcp_servers ?? []).join(", ") || "none"}`,
122
+ `filesystem: ${actor.filesystem?.mode ?? "default"}`,
123
+ `network: ${actor.network?.mode ?? "default"}`,
124
+ ..."output" in actor ? [`output: ${actor.output?.format ?? "text"}`] : []
125
+ ].join("\n");
126
+ }
127
+ function orchestratorBlock(config) {
128
+ const profile = orchestratorProfile(config);
129
+ if (!profile) return "Configured orchestrator: none";
130
+ return [
131
+ "Configured orchestrator:",
132
+ grants(profile),
133
+ `hooks: ${Object.keys(config.hooks.functions).join(", ") || "none"}`,
134
+ "",
135
+ instructionsPointer(profile)
136
+ ].join("\n");
137
+ }
138
+ function dispatchBlock(host, config, workflowId = config.default_workflow) {
139
+ const routes = agentDispatchRoutes(host, config, workflowId);
140
+ if (routes.length === 0) return;
141
+ const plan = compileWorkflowPlan(config, workflowId);
142
+ const nativeRoutes = routes.filter((route) => route.kind !== "cli");
143
+ const cliRoutes = routes.filter((route) => route.kind === "cli");
144
+ return [
145
+ `Run workflow \`${plan.workflowId}\` for the user task.`,
146
+ "",
147
+ nativeDispatchBlock(host, nativeRoutes),
148
+ cliDispatchBlock(host, cliRoutes),
149
+ nodePromptContract(plan.workflowId, routes),
150
+ "Only package-configured gates are blocking. Do not invent RED, GREEN, full-suite, typecheck, or unrelated-drift gates.",
151
+ "If a node returns targeted evidence and has no configured blocking gate, advance to the next node.",
152
+ "Do not bypass configured runner subprocesses or package-configured gates when executing nodes.",
153
+ "Use the listed Task tool routes for native nodes, and run nodes with satisfied dependencies in parallel whenever the host supports concurrent subagent work.",
154
+ hostSpecificDispatchGuard(host, nativeRoutes, cliRoutes)
155
+ ].filter((line) => Boolean(line)).join("\n");
156
+ }
157
+ function entrypointDispatchBlock(host, config, id, entrypoint) {
158
+ if ("workflow" in entrypoint) return dispatchBlock(host, config, entrypoint.workflow);
159
+ return [
160
+ `Generate a schedule for entrypoint \`${id}\` and the user task.`,
161
+ `The schedule policy is \`${entrypoint.schedule}\`.`,
162
+ id === "quick" ? "Run `moka submit --quick <task description>` to submit the graph as an Argo Workflow." : `Run \`moka submit <task description>\` to submit the \`${id}\` graph as an Argo Workflow.`,
163
+ "The pipeline runtime executes as Argo DAG tasks using the package-owned runner image.",
164
+ "Configure the target in `~/.config/moka/config.yaml`; use `--kubeconfig <path>` and `--namespace <namespace>` only for explicit command overrides.",
165
+ "Use `moka submit --schedule <schedule.yaml> <task description>` only when rerunning an existing schedule artifact."
166
+ ].join("\n");
167
+ }
168
+ function scheduledEntrypointK8sNote(entrypoint) {
169
+ if ("workflow" in entrypoint) return;
170
+ return "Submit Momokaya work as Argo Workflows through `moka submit` and `moka submit --quick`.";
171
+ }
172
+ function orchestratorEntrypointDispatchBlock(host, config) {
173
+ const scheduledEntrypoints = entrypointEntries(config).filter(([, entrypoint]) => !("workflow" in entrypoint));
174
+ if (scheduledEntrypoints.length === 0) return dispatchBlock(host, config);
175
+ return scheduledEntrypoints.map(([id, entrypoint]) => entrypointDispatchBlock(host, config, id, entrypoint)).filter((block) => Boolean(block)).join("\n\n");
176
+ }
177
+ function nativeDispatchBlock(host, routes) {
178
+ if (routes.length === 0) return;
179
+ return [
180
+ `${hostDisplayName(host)} native routes:`,
181
+ ...routes.map(nativeDispatchLine),
182
+ ""
183
+ ].join("\n");
184
+ }
185
+ function nativeDispatchLine(route) {
186
+ const needs = needsSummary(route.needs);
187
+ const model = route.model ? ` model=${route.model}` : "";
188
+ return `- ${route.nodeId}: Task tool subagent_type=${route.nativeAgentId}${model} runner=${route.runnerId} needs=${needs}`;
189
+ }
190
+ function cliDispatchBlock(host, routes) {
191
+ if (routes.length === 0) return;
192
+ return [
193
+ `These nodes are not ${hostDisplayName(host)} native routes.`,
194
+ "CLI routes:",
195
+ ...routes.map(cliDispatchLine),
196
+ ""
197
+ ].join("\n");
198
+ }
199
+ function cliDispatchLine(route) {
200
+ return `- ${route.nodeId}: ${route.runnerId} CLI profile=${route.profileId} command=\`${runnerCliCommand(route)}\` needs=${needsSummary(route.needs)}`;
201
+ }
202
+ function runnerCliCommand(route) {
203
+ if (route.runnerId === "opencode") return `opencode run --agent "${opencodeAgentName(route.profileId)}" --format json --dir <repo-root> <node prompt>`;
204
+ throw new Error(`runner '${route.runnerId}' cannot be represented as a supported native or CLI route`);
205
+ }
206
+ function nodePromptContract(workflowId, routes) {
207
+ return [
208
+ routes.some((route) => route.kind === "cli") ? "For each CLI node prompt include:" : "For each native node prompt include:",
209
+ "- user task",
210
+ `- workflow id: ${workflowId}`,
211
+ "- node id",
212
+ "- profile id",
213
+ "- runner id",
214
+ "- profile instructions reference",
215
+ "- profile grants",
216
+ "- dependency outputs",
217
+ ""
218
+ ].join("\n");
219
+ }
220
+ function hostSpecificDispatchGuard(host, nativeRoutes, cliRoutes) {
221
+ if (cliRoutes.length > 0 && nativeRoutes.length > 0) return `Do not claim CLI routes are ${hostDisplayName(host)} native routes.`;
222
+ if (cliRoutes.length > 0 && nativeRoutes.length === 0) return `Do not claim these nodes are ${hostDisplayName(host)} subagents.`;
223
+ }
224
+ function hostDisplayName(host) {
225
+ return {
226
+ "claude-code": "Claude Code",
227
+ opencode: "OpenCode"
228
+ }[host];
229
+ }
230
+ function needsSummary(needs) {
231
+ return needs.length > 0 ? needs.join(",") : "none";
232
+ }
233
+ function compactLines(lines) {
234
+ return lines.filter((line) => line !== void 0);
235
+ }
236
+ const OPENCODE_PERMISSION_TOOLS = [
237
+ "bash",
238
+ "edit",
239
+ "glob",
240
+ "grep",
241
+ "list",
242
+ "read",
243
+ "write"
244
+ ];
245
+ function opencodePermission(actor, options = {}) {
246
+ const allowed = new Set(actor.tools ?? []);
247
+ return {
248
+ ...opencodeToolPermissions(allowed),
249
+ external_directory: "deny",
250
+ lsp: "allow",
251
+ skill: opencodeSkillPermission(actor.skills ?? []),
252
+ task: opencodeTaskPermission(allowed, options.allowedTaskAgents ?? [])
253
+ };
254
+ }
255
+ function opencodeToolPermissions(allowed) {
256
+ return Object.fromEntries(OPENCODE_PERMISSION_TOOLS.map((tool) => [tool, allowed.has(tool) ? "allow" : "deny"]));
257
+ }
258
+ function opencodeSkillPermission(skills) {
259
+ return namedOpencodePermissionMap(skills);
260
+ }
261
+ function opencodeTaskPermission(allowed, allowedTaskAgents) {
262
+ return allowedTaskAgents.length > 0 ? namedOpencodePermissionMap(allowedTaskAgents) : toolPermission(allowed, "task");
263
+ }
264
+ function namedOpencodePermissionMap(names) {
265
+ return names.length > 0 ? {
266
+ "*": "deny",
267
+ ...Object.fromEntries(names.map((name) => [name, "allow"]))
268
+ } : "deny";
269
+ }
270
+ function toolPermission(allowed, tool) {
271
+ return allowed.has(tool) ? "allow" : "deny";
272
+ }
273
+ function renderOpenCodeProjectConfig(config) {
274
+ return formatOpenCodeProjectJson({
275
+ ...config.mcp_gateway ? JSON.parse(renderOpenCodeGatewayConfig(config)) : { $schema: "https://opencode.ai/config.json" },
276
+ lsp: true,
277
+ ...opencodePluginConfig(),
278
+ ...opencodeProviderConfig()
279
+ });
280
+ }
281
+ function opencodeProviderConfig() {
282
+ const provider = {};
283
+ for (const model of DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST.provider_models) {
284
+ provider[model.provider] ??= { models: {} };
285
+ provider[model.provider].models[model.id] = { options: model.options };
286
+ }
287
+ return Object.keys(provider).length > 0 ? { provider } : {};
288
+ }
289
+ function opencodePluginConfig() {
290
+ const plugins = DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST.ecosystem_code.flatMap((item) => npmPluginPackage(item)).sort((a, b) => a.localeCompare(b));
291
+ return plugins.length > 0 ? { plugin: plugins } : {};
292
+ }
293
+ function npmPluginPackage(item) {
294
+ if (item.plugin?.kind === "npm") return [item.plugin.package];
295
+ return [];
296
+ }
297
+ function formatOpenCodeProjectJson(value) {
298
+ return `${JSON.stringify(value, null, 2).replace(SINGLE_OPENCODE_PLUGIN_ARRAY_RE, "\n \"plugin\": [$1]")}\n`;
299
+ }
300
+ function localPluginDefinitions() {
301
+ return DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST.ecosystem_code.flatMap((item) => {
302
+ if (item.plugin?.kind !== "local") return [];
303
+ const plugin = readFileSync(resolvePackageAssetPath(item.plugin.source_path), "utf8").trimEnd();
304
+ return [{
305
+ content: [
306
+ GENERATED_TS_MARKER,
307
+ `${OWNER_TS_MARKER_PREFIX}host=opencode`,
308
+ "",
309
+ plugin,
310
+ ""
311
+ ].join("\n"),
312
+ host: "opencode",
313
+ invocation: invocationForHost("opencode"),
314
+ path: item.plugin.target_path
315
+ }];
316
+ });
317
+ }
318
+ function opencodeDefinitions(config, cwd) {
319
+ const orchestrator = orchestratorProfile(config);
320
+ return [
321
+ ...entrypointCommandDefinitions("opencode", config, (id, entrypoint) => ({
322
+ content: markdown({
323
+ ...orchestrator ? { agent: OPENCODE_ORCHESTRATOR_AGENT_ID } : {},
324
+ description: entrypointDescription(id, entrypoint)
325
+ }, compactLines([
326
+ header("opencode").trimEnd(),
327
+ "",
328
+ `Invoke this command with \`${invocationForHost("opencode", id)}\`.`,
329
+ "",
330
+ orchestratorBlock(config),
331
+ "",
332
+ scheduledEntrypointK8sNote(entrypoint),
333
+ scheduledEntrypointK8sNote(entrypoint) ? "" : void 0,
334
+ entrypointDispatchBlock("opencode", config, id, entrypoint)
335
+ ]).join("\n")),
336
+ host: "opencode",
337
+ invocation: invocationForHost("opencode", id),
338
+ path: `.opencode/commands/${commandIdForHost("opencode", id)}.md`
339
+ })),
340
+ {
341
+ content: renderOpenCodeProjectConfig(config),
342
+ host: "opencode",
343
+ invocation: invocationForHost("opencode"),
344
+ path: ".opencode/opencode.json"
345
+ },
346
+ ...orchestrator ? [{
347
+ content: markdown({
348
+ description: "Orchestrate the configured pipeline and enforce gates.",
349
+ mode: "primary",
350
+ name: OPENCODE_ORCHESTRATOR_AGENT_ID,
351
+ permission: opencodePermission(orchestrator, { allowedTaskAgents: agentDispatchRoutes("opencode", config).filter((route) => route.kind !== "cli").map((route) => route.nativeAgentId).filter((id) => Boolean(id)) })
352
+ }, compactLines([
353
+ header("opencode").trimEnd(),
354
+ "",
355
+ orchestratorBlock(config),
356
+ "",
357
+ orchestratorEntrypointDispatchBlock("opencode", config)
358
+ ]).join("\n")),
359
+ host: "opencode",
360
+ invocation: invocationForHost("opencode"),
361
+ path: `.opencode/agents/${OPENCODE_ORCHESTRATOR_AGENT_ID}.md`
362
+ }] : [],
363
+ ...nativeProfileEntries("opencode", config).map(([id, profile]) => ({
364
+ content: markdown({
365
+ name: nativeAgentIdForHost("opencode", id),
366
+ description: profile.description ?? id,
367
+ hidden: false,
368
+ mode: "all",
369
+ ...opencodeModelProjection(config, profile),
370
+ permission: opencodePermission(profile)
371
+ }, [
372
+ header("opencode").trimEnd(),
373
+ "",
374
+ profile.description ?? id,
375
+ "",
376
+ "Configured grants:",
377
+ grants(profile),
378
+ "",
379
+ instructionsPointer(profile)
380
+ ].join("\n")),
381
+ host: "opencode",
382
+ invocation: invocationForHost("opencode"),
383
+ path: `.opencode/agents/${nativeAgentIdForHost("opencode", id)}.md`
384
+ })),
385
+ ...localPluginDefinitions(),
386
+ projectAgentsMdDefinition(cwd, "opencode")
387
+ ];
388
+ }
389
+ function projectAgentsMdDefinition(cwd, host) {
390
+ const repoName = basename(cwd);
391
+ return {
392
+ block: {
393
+ end: AGENTS_MD_END,
394
+ start: AGENTS_MD_START
395
+ },
396
+ content: [
397
+ AGENTS_MD_START,
398
+ GENERATED_MARKER,
399
+ `${OWNER_MARKER_PREFIX}host=opencode -->`,
400
+ "",
401
+ "## Pipeline Guidance",
402
+ "",
403
+ "This repository uses package-owned `@oisincoveney/pipeline` config.",
404
+ "",
405
+ "- Use `/moka-quick`, `/moka-execute`, or `/moka-inspect` for OpenCode slash-command entrypoints when available.",
406
+ "- Load and follow the relevant skill from `.agents/skills` before doing specialized work.",
407
+ "- Prefer the package-defined pipeline profiles and generated command surfaces over ad hoc subagent prompts.",
408
+ "",
409
+ "## Pipeline Memory",
410
+ "",
411
+ `Use Qdrant collection \`${repoName}\` for this repository.`,
412
+ "",
413
+ `- Call \`qdrant-find\` before research with \`collection_name: ${repoName}\` unless the user explicitly disables memory.`,
414
+ `- Call \`qdrant-store\` during LEARN with \`collection_name: ${repoName}\` for durable lessons worth reusing.`,
415
+ "- Include metadata with at least `repo`, `phase`, `workflow` or `entrypoint`, `task`, and `outcome` when storing lessons.",
416
+ "",
417
+ AGENTS_MD_END,
418
+ ""
419
+ ].join("\n"),
420
+ host,
421
+ invocation: invocationForHost(host),
422
+ path: "AGENTS.md"
423
+ };
424
+ }
425
+ function opencodeModelProjection(config, profile) {
426
+ const model = resolvedHostModel(config, "opencode", profile);
427
+ return model ? { model } : {};
428
+ }
429
+ function instructionsPointer(actor) {
430
+ if (actor.instructions.path) return `Instructions: ${actor.instructions.path}`;
431
+ return `Instructions:\n${actor.instructions.inline ?? ""}`;
432
+ }
433
+ //#endregion
434
+ export { opencodeDefinitions };