@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.
@@ -0,0 +1,29 @@
1
+ //#region src/install-commands/shared.ts
2
+ const GENERATED_MARKER = "<!-- Generated by @oisincoveney/pipeline. -->";
3
+ const GENERATED_TS_MARKER = "// Generated by @oisincoveney/pipeline.";
4
+ const GENERATED_YAML_MARKER = "# Generated by @oisincoveney/pipeline.";
5
+ const OWNER_MARKER_PREFIX = "<!-- @oisincoveney/pipeline:";
6
+ const OWNER_TS_MARKER_PREFIX = "// @oisincoveney/pipeline:";
7
+ const OWNER_YAML_MARKER_PREFIX = "# @oisincoveney/pipeline:";
8
+ const AGENTS_MD_START = "<!-- @oisincoveney/pipeline:agents:start -->";
9
+ const AGENTS_MD_END = "<!-- @oisincoveney/pipeline:agents:end -->";
10
+ const SINGLE_OPENCODE_PLUGIN_ARRAY_RE = /\n {2}"plugin": \[\n {4}("[^"]+")\n {2}\]/;
11
+ const OPENCODE_PROJECT_CONFIG_PATH = ".opencode/opencode.json";
12
+ const OPENCODE_COMMAND_PREFIX = "moka-";
13
+ const ENTRYPOINT_PATH_PATTERNS = {
14
+ "claude-code": [],
15
+ opencode: [/^\.opencode\/commands\/(?:moka-)?([^/]+)\.md$/]
16
+ };
17
+ const COMMAND_HOSTS = ["opencode", "claude-code"];
18
+ function invocationForHost(host, entrypointId = "execute") {
19
+ return `${{
20
+ "claude-code": "/",
21
+ opencode: "/"
22
+ }[host]}${commandIdForHost(host, entrypointId)} <task description>`;
23
+ }
24
+ function commandIdForHost(host, entrypointId) {
25
+ if (host === "opencode") return `${OPENCODE_COMMAND_PREFIX}${entrypointId}`;
26
+ return entrypointId;
27
+ }
28
+ //#endregion
29
+ export { AGENTS_MD_END, AGENTS_MD_START, COMMAND_HOSTS, ENTRYPOINT_PATH_PATTERNS, GENERATED_MARKER, GENERATED_TS_MARKER, GENERATED_YAML_MARKER, OPENCODE_PROJECT_CONFIG_PATH, OWNER_MARKER_PREFIX, OWNER_TS_MARKER_PREFIX, OWNER_YAML_MARKER_PREFIX, SINGLE_OPENCODE_PLUGIN_ARRAY_RE, commandIdForHost, invocationForHost };
@@ -1,440 +1,18 @@
1
- import { DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST } from "./config/defaults.js";
2
- import { resolvePackageAssetPath } from "./package-assets.js";
3
1
  import { loadPipelineConfig } from "./config/load.js";
4
2
  import "./config.js";
5
- import { compileWorkflowPlan } from "./workflow-planner.js";
6
3
  import { mergeOpenCodeProjectConfig } from "./opencode-project-config.js";
7
- import { renderOpenCodeGatewayConfig } from "./mcp/gateway.js";
4
+ import { COMMAND_HOSTS, ENTRYPOINT_PATH_PATTERNS, invocationForHost } from "./install-commands/shared.js";
5
+ import { claudeCodeDefinitions } from "./install-commands/claude-code.js";
6
+ import { opencodeDefinitions } from "./install-commands/opencode.js";
8
7
  import { existsSync, readFileSync, statSync } from "node:fs";
9
- import { basename, dirname, join, relative } from "node:path";
10
- import matter from "gray-matter";
8
+ import { dirname, join, relative } from "node:path";
11
9
  import { mkdir, readdir, rm, writeFile } from "node:fs/promises";
12
10
  //#region src/install-commands.ts
13
- const GENERATED_MARKER = "<!-- Generated by @oisincoveney/pipeline. -->";
14
- const GENERATED_TS_MARKER = "// Generated by @oisincoveney/pipeline.";
15
- const GENERATED_YAML_MARKER = "# Generated by @oisincoveney/pipeline.";
16
- const OWNER_MARKER_PREFIX = "<!-- @oisincoveney/pipeline:";
17
- const OWNER_TS_MARKER_PREFIX = "// @oisincoveney/pipeline:";
18
- const OWNER_YAML_MARKER_PREFIX = "# @oisincoveney/pipeline:";
19
- const AGENTS_MD_START = "<!-- @oisincoveney/pipeline:agents:start -->";
20
- const AGENTS_MD_END = "<!-- @oisincoveney/pipeline:agents:end -->";
21
- const SINGLE_OPENCODE_PLUGIN_ARRAY_RE = /\n {2}"plugin": \[\n {4}("[^"]+")\n {2}\]/;
22
- const OPENCODE_PROJECT_CONFIG_PATH = ".opencode/opencode.json";
23
- const OPENCODE_COMMAND_PREFIX = "moka-";
24
- const ENTRYPOINT_PATH_PATTERNS = { opencode: [/^\.opencode\/commands\/(?:moka-)?([^/]+)\.md$/] };
25
- const COMMAND_HOSTS = ["opencode"];
26
- const OPENCODE_ORCHESTRATOR_AGENT_ID = "MoKa Orchestrator";
27
- const MOKA_PROFILE_PREFIX = "moka-";
28
- function header(host) {
29
- return [
30
- GENERATED_MARKER,
31
- `${OWNER_MARKER_PREFIX}host=${host} -->`,
32
- ""
33
- ].join("\n");
34
- }
35
- function markdown(data, body) {
36
- return `${matter.stringify(body.trimEnd(), data).trimEnd()}\n`;
37
- }
38
- function profileEntries(config) {
39
- return Object.entries(config.profiles).sort(([a], [b]) => a.localeCompare(b));
40
- }
41
- function entrypointEntries(config) {
42
- const entries = Object.entries(config.entrypoints);
43
- return entries.length > 0 ? entries : [["execute", {
44
- description: "Run the configured pipeline workflow",
45
- workflow: config.default_workflow
46
- }]];
47
- }
48
- function entrypointDescription(id, entrypoint) {
49
- return entrypoint.description ?? `Run the ${id} workflow`;
50
- }
51
- function entrypointCommandDefinitions(_host, config, makeDefinition) {
52
- return entrypointEntries(config).map(([id, entrypoint]) => makeDefinition(id, entrypoint));
53
- }
54
- function nativeProfileEntries(host, config) {
55
- return profileEntries(config).filter(([id, profile]) => id !== config.orchestrator?.profile && canRunNatively(host, profile));
56
- }
57
- function orchestratorProfile(config) {
58
- if (!config.orchestrator) return;
59
- const profile = config.profiles[config.orchestrator.profile];
60
- if (!profile) throw new Error(`Orchestrator profile '${config.orchestrator.profile}' is not declared.`);
61
- return { ...profile };
62
- }
63
- function resolvedHostModel(config, host, profile) {
64
- const runner = config.runners[profile.runner];
65
- const hostRunner = config.runners[host];
66
- if (profile.host_models?.[host]) return profile.host_models[host];
67
- if (runner?.host_models?.[host]) return runner.host_models[host];
68
- if (profile.runner === host) return profile.model ?? runner?.model;
69
- return hostRunner?.model;
70
- }
71
- function canRunNatively(host, profile) {
72
- if (profile.runner === host) return true;
73
- return host === "opencode" && isModelRunner(profile.runner);
74
- }
75
- function isModelRunner(runnerId) {
76
- return COMMAND_HOSTS.some((host) => host === runnerId);
77
- }
78
- function agentDispatchRoutes(host, config, workflowId = config.default_workflow) {
79
- return compileWorkflowPlan(config, workflowId).topologicalOrder.flatMap((node) => {
80
- if (!(node.kind === "agent" && node.profile)) return [];
81
- const profile = config.profiles[node.profile];
82
- if (!profile) return [];
83
- return [dispatchRouteForAgent(host, config, {
84
- needs: node.needs,
85
- nodeId: node.id,
86
- profile,
87
- profileId: node.profile
88
- })];
89
- });
90
- }
91
- function dispatchRouteForAgent(host, config, route) {
92
- const runnerId = route.profile.runner;
93
- if (runnerId === host) {
94
- const model = resolvedHostModel(config, host, route.profile);
95
- return {
96
- ...route,
97
- kind: "native-named-agent",
98
- ...model ? { model } : {},
99
- nativeAgentId: nativeAgentIdForHost(host, route.profileId),
100
- runnerId
101
- };
102
- }
103
- if (host === "opencode" && isModelRunner(runnerId)) {
104
- const model = resolvedHostModel(config, host, route.profile);
105
- return {
106
- ...route,
107
- kind: "native-model-agent",
108
- ...model ? { model } : {},
109
- nativeAgentId: nativeAgentIdForHost(host, route.profileId),
110
- runnerId
111
- };
112
- }
113
- return {
114
- ...route,
115
- kind: "cli",
116
- runnerId
117
- };
118
- }
119
- function nativeAgentIdForHost(host, profileId) {
120
- return host === "opencode" ? opencodeAgentName(profileId) : profileId;
121
- }
122
- function opencodeAgentName(profileId) {
123
- if (!profileId.startsWith(MOKA_PROFILE_PREFIX)) return profileId;
124
- return `MoKa ${profileId.slice(5).split("-").map(opencodeAgentNamePart).join(" ")}`;
125
- }
126
- function opencodeAgentNamePart(part) {
127
- if (part === "opencode") return "OpenCode";
128
- return `${part.charAt(0).toUpperCase()}${part.slice(1)}`;
129
- }
130
- function grants(actor) {
131
- return [
132
- `model: ${actor.model ?? "default"}`,
133
- `tools: ${(actor.tools ?? []).join(", ") || "none"}`,
134
- `rules: ${(actor.rules ?? []).join(", ") || "none"}`,
135
- `skills: ${(actor.skills ?? []).join(", ") || "none"}`,
136
- `mcp_servers: ${(actor.mcp_servers ?? []).join(", ") || "none"}`,
137
- `filesystem: ${actor.filesystem?.mode ?? "default"}`,
138
- `network: ${actor.network?.mode ?? "default"}`,
139
- ..."output" in actor ? [`output: ${actor.output?.format ?? "text"}`] : []
140
- ].join("\n");
141
- }
142
- function orchestratorBlock(config) {
143
- const profile = orchestratorProfile(config);
144
- if (!profile) return "Configured orchestrator: none";
145
- return [
146
- "Configured orchestrator:",
147
- grants(profile),
148
- `hooks: ${Object.keys(config.hooks.functions).join(", ") || "none"}`,
149
- "",
150
- instructionsPointer(profile)
151
- ].join("\n");
152
- }
153
- function dispatchBlock(host, config, workflowId = config.default_workflow) {
154
- const routes = agentDispatchRoutes(host, config, workflowId);
155
- if (routes.length === 0) return;
156
- const plan = compileWorkflowPlan(config, workflowId);
157
- const nativeRoutes = routes.filter((route) => route.kind !== "cli");
158
- const cliRoutes = routes.filter((route) => route.kind === "cli");
159
- return [
160
- `Run workflow \`${plan.workflowId}\` for the user task.`,
161
- "",
162
- nativeDispatchBlock(host, nativeRoutes),
163
- cliDispatchBlock(host, cliRoutes),
164
- nodePromptContract(plan.workflowId, routes),
165
- "Only package-configured gates are blocking. Do not invent RED, GREEN, full-suite, typecheck, or unrelated-drift gates.",
166
- "If a node returns targeted evidence and has no configured blocking gate, advance to the next node.",
167
- "Do not bypass configured runner subprocesses or package-configured gates when executing nodes.",
168
- "Use the listed Task tool routes for native nodes, and run nodes with satisfied dependencies in parallel whenever the host supports concurrent subagent work.",
169
- hostSpecificDispatchGuard(host, nativeRoutes, cliRoutes)
170
- ].filter((line) => Boolean(line)).join("\n");
171
- }
172
- function entrypointDispatchBlock(host, config, id, entrypoint) {
173
- if ("workflow" in entrypoint) return dispatchBlock(host, config, entrypoint.workflow);
174
- return [
175
- `Generate a schedule for entrypoint \`${id}\` and the user task.`,
176
- `The schedule policy is \`${entrypoint.schedule}\`.`,
177
- 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.`,
178
- "The pipeline runtime executes as Argo DAG tasks using the package-owned runner image.",
179
- "Configure the target in `~/.config/moka/config.yaml`; use `--kubeconfig <path>` and `--namespace <namespace>` only for explicit command overrides.",
180
- "Use `moka submit --schedule <schedule.yaml> <task description>` only when rerunning an existing schedule artifact."
181
- ].join("\n");
182
- }
183
- function scheduledEntrypointK8sNote(entrypoint) {
184
- if ("workflow" in entrypoint) return;
185
- return "Submit Momokaya work as Argo Workflows through `moka submit` and `moka submit --quick`.";
186
- }
187
- function orchestratorEntrypointDispatchBlock(host, config) {
188
- const scheduledEntrypoints = entrypointEntries(config).filter(([, entrypoint]) => !("workflow" in entrypoint));
189
- if (scheduledEntrypoints.length === 0) return dispatchBlock(host, config);
190
- return scheduledEntrypoints.map(([id, entrypoint]) => entrypointDispatchBlock(host, config, id, entrypoint)).filter((block) => Boolean(block)).join("\n\n");
191
- }
192
- function nativeDispatchBlock(host, routes) {
193
- if (routes.length === 0) return;
194
- return [
195
- `${hostDisplayName(host)} native routes:`,
196
- ...routes.map(nativeDispatchLine),
197
- ""
198
- ].join("\n");
199
- }
200
- function nativeDispatchLine(route) {
201
- const needs = needsSummary(route.needs);
202
- const model = route.model ? ` model=${route.model}` : "";
203
- return `- ${route.nodeId}: Task tool subagent_type=${route.nativeAgentId}${model} runner=${route.runnerId} needs=${needs}`;
204
- }
205
- function cliDispatchBlock(host, routes) {
206
- if (routes.length === 0) return;
207
- return [
208
- `These nodes are not ${hostDisplayName(host)} native routes.`,
209
- "CLI routes:",
210
- ...routes.map(cliDispatchLine),
211
- ""
212
- ].join("\n");
213
- }
214
- function cliDispatchLine(route) {
215
- return `- ${route.nodeId}: ${route.runnerId} CLI profile=${route.profileId} command=\`${runnerCliCommand(route)}\` needs=${needsSummary(route.needs)}`;
216
- }
217
- function runnerCliCommand(route) {
218
- if (route.runnerId === "opencode") return `opencode run --agent "${opencodeAgentName(route.profileId)}" --format json --dir <repo-root> <node prompt>`;
219
- throw new Error(`runner '${route.runnerId}' cannot be represented as a supported native or CLI route`);
220
- }
221
- function nodePromptContract(workflowId, routes) {
222
- return [
223
- routes.some((route) => route.kind === "cli") ? "For each CLI node prompt include:" : "For each native node prompt include:",
224
- "- user task",
225
- `- workflow id: ${workflowId}`,
226
- "- node id",
227
- "- profile id",
228
- "- runner id",
229
- "- profile instructions reference",
230
- "- profile grants",
231
- "- dependency outputs",
232
- ""
233
- ].join("\n");
234
- }
235
- function hostSpecificDispatchGuard(host, nativeRoutes, cliRoutes) {
236
- if (cliRoutes.length > 0 && nativeRoutes.length > 0) return `Do not claim CLI routes are ${hostDisplayName(host)} native routes.`;
237
- if (cliRoutes.length > 0 && nativeRoutes.length === 0) return `Do not claim these nodes are ${hostDisplayName(host)} subagents.`;
238
- }
239
- function hostDisplayName(host) {
240
- return { opencode: "OpenCode" }[host];
241
- }
242
- function needsSummary(needs) {
243
- return needs.length > 0 ? needs.join(",") : "none";
244
- }
245
- function compactLines(lines) {
246
- return lines.filter((line) => line !== void 0);
247
- }
248
- const OPENCODE_PERMISSION_TOOLS = [
249
- "bash",
250
- "edit",
251
- "glob",
252
- "grep",
253
- "list",
254
- "read",
255
- "write"
256
- ];
257
- function opencodePermission(actor, options = {}) {
258
- const allowed = new Set(actor.tools ?? []);
259
- return {
260
- ...opencodeToolPermissions(allowed),
261
- external_directory: "deny",
262
- lsp: "allow",
263
- skill: opencodeSkillPermission(actor.skills ?? []),
264
- task: opencodeTaskPermission(allowed, options.allowedTaskAgents ?? [])
265
- };
266
- }
267
- function opencodeToolPermissions(allowed) {
268
- return Object.fromEntries(OPENCODE_PERMISSION_TOOLS.map((tool) => [tool, allowed.has(tool) ? "allow" : "deny"]));
269
- }
270
- function opencodeSkillPermission(skills) {
271
- return namedOpencodePermissionMap(skills);
272
- }
273
- function opencodeTaskPermission(allowed, allowedTaskAgents) {
274
- return allowedTaskAgents.length > 0 ? namedOpencodePermissionMap(allowedTaskAgents) : toolPermission(allowed, "task");
275
- }
276
- function namedOpencodePermissionMap(names) {
277
- return names.length > 0 ? {
278
- "*": "deny",
279
- ...Object.fromEntries(names.map((name) => [name, "allow"]))
280
- } : "deny";
281
- }
282
- function toolPermission(allowed, tool) {
283
- return allowed.has(tool) ? "allow" : "deny";
284
- }
285
- function renderOpenCodeProjectConfig(config) {
286
- return formatOpenCodeProjectJson({
287
- ...config.mcp_gateway ? JSON.parse(renderOpenCodeGatewayConfig(config)) : { $schema: "https://opencode.ai/config.json" },
288
- lsp: true,
289
- ...opencodePluginConfig()
290
- });
291
- }
292
- function opencodePluginConfig() {
293
- const plugins = DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST.ecosystem_code.flatMap((item) => npmPluginPackage(item)).sort((a, b) => a.localeCompare(b));
294
- return plugins.length > 0 ? { plugin: plugins } : {};
295
- }
296
- function npmPluginPackage(item) {
297
- if (item.plugin?.kind === "npm") return [item.plugin.package];
298
- return [];
299
- }
300
- function formatOpenCodeProjectJson(value) {
301
- return `${JSON.stringify(value, null, 2).replace(SINGLE_OPENCODE_PLUGIN_ARRAY_RE, "\n \"plugin\": [$1]")}\n`;
302
- }
303
- function localPluginDefinitions() {
304
- return DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST.ecosystem_code.flatMap((item) => {
305
- if (item.plugin?.kind !== "local") return [];
306
- const plugin = readFileSync(resolvePackageAssetPath(item.plugin.source_path), "utf8").trimEnd();
307
- return [{
308
- content: [
309
- GENERATED_TS_MARKER,
310
- `${OWNER_TS_MARKER_PREFIX}host=opencode`,
311
- "",
312
- plugin,
313
- ""
314
- ].join("\n"),
315
- host: "opencode",
316
- invocation: invocationForHost("opencode"),
317
- path: item.plugin.target_path
318
- }];
319
- });
320
- }
321
- function opencodeDefinitions(config, cwd) {
322
- const orchestrator = orchestratorProfile(config);
323
- return [
324
- ...entrypointCommandDefinitions("opencode", config, (id, entrypoint) => ({
325
- content: markdown({
326
- ...orchestrator ? { agent: OPENCODE_ORCHESTRATOR_AGENT_ID } : {},
327
- description: entrypointDescription(id, entrypoint)
328
- }, compactLines([
329
- header("opencode").trimEnd(),
330
- "",
331
- `Invoke this command with \`${invocationForHost("opencode", id)}\`.`,
332
- "",
333
- orchestratorBlock(config),
334
- "",
335
- scheduledEntrypointK8sNote(entrypoint),
336
- scheduledEntrypointK8sNote(entrypoint) ? "" : void 0,
337
- entrypointDispatchBlock("opencode", config, id, entrypoint)
338
- ]).join("\n")),
339
- host: "opencode",
340
- invocation: invocationForHost("opencode", id),
341
- path: `.opencode/commands/${commandIdForHost("opencode", id)}.md`
342
- })),
343
- {
344
- content: renderOpenCodeProjectConfig(config),
345
- host: "opencode",
346
- invocation: invocationForHost("opencode"),
347
- path: ".opencode/opencode.json"
348
- },
349
- ...orchestrator ? [{
350
- content: markdown({
351
- description: "Orchestrate the configured pipeline and enforce gates.",
352
- mode: "primary",
353
- name: OPENCODE_ORCHESTRATOR_AGENT_ID,
354
- permission: opencodePermission(orchestrator, { allowedTaskAgents: agentDispatchRoutes("opencode", config).filter((route) => route.kind !== "cli").map((route) => route.nativeAgentId).filter((id) => Boolean(id)) })
355
- }, compactLines([
356
- header("opencode").trimEnd(),
357
- "",
358
- orchestratorBlock(config),
359
- "",
360
- orchestratorEntrypointDispatchBlock("opencode", config)
361
- ]).join("\n")),
362
- host: "opencode",
363
- invocation: invocationForHost("opencode"),
364
- path: `.opencode/agents/${OPENCODE_ORCHESTRATOR_AGENT_ID}.md`
365
- }] : [],
366
- ...nativeProfileEntries("opencode", config).map(([id, profile]) => ({
367
- content: markdown({
368
- name: nativeAgentIdForHost("opencode", id),
369
- description: profile.description ?? id,
370
- hidden: false,
371
- mode: "all",
372
- ...opencodeModelProjection(config, profile),
373
- permission: opencodePermission(profile)
374
- }, [
375
- header("opencode").trimEnd(),
376
- "",
377
- profile.description ?? id,
378
- "",
379
- "Configured grants:",
380
- grants(profile),
381
- "",
382
- instructionsPointer(profile)
383
- ].join("\n")),
384
- host: "opencode",
385
- invocation: invocationForHost("opencode"),
386
- path: `.opencode/agents/${nativeAgentIdForHost("opencode", id)}.md`
387
- })),
388
- ...localPluginDefinitions(),
389
- projectAgentsMdDefinition(cwd, "opencode")
390
- ];
391
- }
392
- function projectAgentsMdDefinition(cwd, host) {
393
- const repoName = basename(cwd);
394
- return {
395
- block: {
396
- end: AGENTS_MD_END,
397
- start: AGENTS_MD_START
398
- },
399
- content: [
400
- AGENTS_MD_START,
401
- GENERATED_MARKER,
402
- `${OWNER_MARKER_PREFIX}host=opencode -->`,
403
- "",
404
- "## Pipeline Guidance",
405
- "",
406
- "This repository uses package-owned `@oisincoveney/pipeline` config.",
407
- "",
408
- "- Use `/moka-quick`, `/moka-execute`, or `/moka-inspect` for OpenCode slash-command entrypoints when available.",
409
- "- Load and follow the relevant skill from `.agents/skills` before doing specialized work.",
410
- "- Prefer the package-defined pipeline profiles and generated command surfaces over ad hoc subagent prompts.",
411
- "",
412
- "## Pipeline Memory",
413
- "",
414
- `Use Qdrant collection \`${repoName}\` for this repository.`,
415
- "",
416
- `- Call \`qdrant-find\` before research with \`collection_name: ${repoName}\` unless the user explicitly disables memory.`,
417
- `- Call \`qdrant-store\` during LEARN with \`collection_name: ${repoName}\` for durable lessons worth reusing.`,
418
- "- Include metadata with at least `repo`, `phase`, `workflow` or `entrypoint`, `task`, and `outcome` when storing lessons.",
419
- "",
420
- AGENTS_MD_END,
421
- ""
422
- ].join("\n"),
423
- host,
424
- invocation: invocationForHost(host),
425
- path: "AGENTS.md"
426
- };
427
- }
428
- function opencodeModelProjection(config, profile) {
429
- const model = resolvedHostModel(config, "opencode", profile);
430
- return model ? { model } : {};
431
- }
432
- function instructionsPointer(actor) {
433
- if (actor.instructions.path) return `Instructions: ${actor.instructions.path}`;
434
- return `Instructions:\n${actor.instructions.inline ?? ""}`;
435
- }
436
11
  function definitionsFor(host, config, cwd) {
437
- const definitions = { opencode: () => opencodeDefinitions(config, cwd) };
12
+ const definitions = {
13
+ "claude-code": () => claudeCodeDefinitions(),
14
+ opencode: () => opencodeDefinitions(config, cwd)
15
+ };
438
16
  return dedupeDefinitionsByPath((host === "all" ? COMMAND_HOSTS : [host]).flatMap((name) => definitions[name]()));
439
17
  }
440
18
  function dedupeDefinitionsByPath(definitions) {
@@ -447,12 +25,15 @@ function dedupeDefinitionsByPath(definitions) {
447
25
  function selectedHosts(host) {
448
26
  return host === "all" ? [...COMMAND_HOSTS] : [host];
449
27
  }
450
- const GENERATED_RESOURCE_ROOTS = { opencode: [
451
- ".opencode/commands",
452
- ".opencode/agents",
453
- ".opencode/plugins",
454
- ".opencode/skills"
455
- ] };
28
+ const GENERATED_RESOURCE_ROOTS = {
29
+ "claude-code": [".claude/skills"],
30
+ opencode: [
31
+ ".opencode/commands",
32
+ ".opencode/agents",
33
+ ".opencode/plugins",
34
+ ".opencode/skills"
35
+ ]
36
+ };
456
37
  async function listFiles(root) {
457
38
  if (!existsSync(root)) return [];
458
39
  if (statSync(root).isFile()) return [root];
@@ -464,7 +45,7 @@ async function listFiles(root) {
464
45
  }))).flat();
465
46
  }
466
47
  function generatedHostFor(content) {
467
- return COMMAND_HOSTS.find((host) => content.includes(`${OWNER_MARKER_PREFIX}host=${host} -->`) || content.includes(`${OWNER_TS_MARKER_PREFIX}host=${host}`) || content.includes(`${OWNER_YAML_MARKER_PREFIX}host=${host}`));
48
+ return COMMAND_HOSTS.find((host) => content.includes(`<!-- @oisincoveney/pipeline:host=${host} -->`) || content.includes(`// @oisincoveney/pipeline:host=${host}`) || content.includes(`# @oisincoveney/pipeline:host=${host}`));
468
49
  }
469
50
  async function obsoleteGeneratedItems(cwd, host, wantedPaths) {
470
51
  const hosts = new Set(selectedHosts(host));
@@ -488,15 +69,8 @@ function entrypointIdFromGeneratedPath(host, path) {
488
69
  if (match) return match[1];
489
70
  }
490
71
  }
491
- function invocationForHost(host, entrypointId = "execute") {
492
- return `${{ opencode: "/" }[host]}${commandIdForHost(host, entrypointId)} <task description>`;
493
- }
494
- function commandIdForHost(host, entrypointId) {
495
- if (host === "opencode") return `${OPENCODE_COMMAND_PREFIX}${entrypointId}`;
496
- return entrypointId;
497
- }
498
72
  function resolveDefinitionContent(definition, target) {
499
- if (definition.path !== OPENCODE_PROJECT_CONFIG_PATH || !existsSync(target)) return {
73
+ if (definition.path !== ".opencode/opencode.json" || !existsSync(target)) return {
500
74
  conflict: false,
501
75
  content: definition.content
502
76
  };
@@ -519,7 +93,7 @@ function actionFor(path, content, force, block) {
519
93
  return "update";
520
94
  }
521
95
  if (current === content) return "unchanged";
522
- if (!(current.includes(GENERATED_MARKER) || current.includes(GENERATED_TS_MARKER) || current.includes(GENERATED_YAML_MARKER) || force)) return "conflict";
96
+ if (!(current.includes("<!-- Generated by @oisincoveney/pipeline. -->") || current.includes("// Generated by @oisincoveney/pipeline.") || current.includes("# Generated by @oisincoveney/pipeline.") || force)) return "conflict";
523
97
  return "update";
524
98
  }
525
99
  function upsertGeneratedBlock(current, content, block) {
@@ -536,7 +110,7 @@ function upsertGeneratedBlock(current, content, block) {
536
110
  }
537
111
  function installActionForDefinition(definition, target, resolved, force) {
538
112
  if (resolved.conflict) return "conflict";
539
- return actionFor(target, resolved.content, force || definition.path === OPENCODE_PROJECT_CONFIG_PATH, definition.block);
113
+ return actionFor(target, resolved.content, force || definition.path === ".opencode/opencode.json", definition.block);
540
114
  }
541
115
  async function writeDefinition(definition, target, content) {
542
116
  await mkdir(dirname(target), { recursive: true });
@@ -161,8 +161,8 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
161
161
  }, z.core.$strict>>;
162
162
  serviceAccountName: z.ZodOptional<z.ZodString>;
163
163
  mode: z.ZodEnum<{
164
- quick: "quick";
165
164
  full: "full";
165
+ quick: "quick";
166
166
  }>;
167
167
  schedulePath: z.ZodOptional<z.ZodString>;
168
168
  scheduleYaml: z.ZodOptional<z.ZodString>;
@@ -32,7 +32,15 @@ function parseOpenCodeProjectConfig(currentText) {
32
32
  };
33
33
  }
34
34
  function applyOpenCodeProjection(currentText, parsed, projection) {
35
- return applyPluginProjection(applyMcpProjection(setIfMissing(setIfMissing(currentText, parsed, ["$schema"], projection.$schema), parsed, ["lsp"], projection.lsp), parsed, projection), parsed, projection);
35
+ return applyProviderProjection(applyPluginProjection(applyMcpProjection(setIfMissing(setIfMissing(currentText, parsed, ["$schema"], projection.$schema), parsed, ["lsp"], projection.lsp), parsed, projection), parsed, projection), parsed, projection);
36
+ }
37
+ function applyProviderProjection(content, parsed, projection) {
38
+ return Object.entries(projection.provider ?? {}).reduce((providerContent, [providerId, provider]) => Object.entries(provider.models ?? {}).reduce((modelContent, [modelId, model]) => setIfMissing(modelContent, parsed, [
39
+ "provider",
40
+ providerId,
41
+ "models",
42
+ modelId
43
+ ], model), providerContent), content);
36
44
  }
37
45
  function applyMcpProjection(content, parsed, projection) {
38
46
  return Object.entries(projection.mcp ?? {}).reduce((nextContent, [name, server]) => setIfMissing(nextContent, parsed, ["mcp", name], server), content);
@@ -54,8 +62,14 @@ function hasPath(value, path) {
54
62
  return true;
55
63
  }
56
64
  function mergePluginEntries(existing, projected) {
57
- const merged = Array.isArray(existing) ? [...existing] : [];
58
- const seen = new Set(merged.map(pluginKey));
65
+ const projectedByKey = new Map(projected.map((plugin) => [pluginKey(plugin), plugin]));
66
+ const merged = [];
67
+ const seen = /* @__PURE__ */ new Set();
68
+ for (const plugin of Array.isArray(existing) ? existing : []) {
69
+ const key = pluginKey(plugin);
70
+ merged.push(projectedByKey.get(key) ?? plugin);
71
+ seen.add(key);
72
+ }
59
73
  for (const plugin of projected) {
60
74
  const key = pluginKey(plugin);
61
75
  if (seen.has(key)) continue;
@@ -65,8 +79,12 @@ function mergePluginEntries(existing, projected) {
65
79
  return merged;
66
80
  }
67
81
  function pluginKey(value) {
68
- if (Array.isArray(value)) return typeof value[0] === "string" ? value[0] : JSON.stringify(value);
69
- return typeof value === "string" ? value : JSON.stringify(value);
82
+ if (Array.isArray(value)) return typeof value[0] === "string" ? pluginName(value[0]) : JSON.stringify(value);
83
+ return typeof value === "string" ? pluginName(value) : JSON.stringify(value);
84
+ }
85
+ function pluginName(specifier) {
86
+ const versionSeparator = specifier.indexOf("@", 1);
87
+ return versionSeparator === -1 ? specifier : specifier.slice(0, versionSeparator);
70
88
  }
71
89
  function applyJsonEdit(content, path, value) {
72
90
  return applyEdits(content, modify(content, path, value, { formattingOptions: JSON_FORMAT_OPTIONS }));
@@ -43,8 +43,8 @@ declare const runnerDeliverySchema: z.ZodObject<{
43
43
  declare const mokaSubmissionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
44
44
  kind: z.ZodLiteral<"graph">;
45
45
  mode: z.ZodEnum<{
46
- quick: "quick";
47
46
  full: "full";
47
+ quick: "quick";
48
48
  }>;
49
49
  }, z.core.$strict>, z.ZodObject<{
50
50
  argv: z.ZodArray<z.ZodString>;
@@ -104,8 +104,8 @@ declare const runnerCommandPayloadSchema: z.ZodObject<{
104
104
  submission: z.ZodDefault<z.ZodDiscriminatedUnion<[z.ZodObject<{
105
105
  kind: z.ZodLiteral<"graph">;
106
106
  mode: z.ZodEnum<{
107
- quick: "quick";
108
107
  full: "full";
108
+ quick: "quick";
109
109
  }>;
110
110
  }, z.core.$strict>, z.ZodObject<{
111
111
  argv: z.ZodArray<z.ZodString>;