@oisincoveney/pipeline 2.1.1 → 2.3.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.
- package/.agents/skills/orchestrate/SKILL.md +80 -0
- package/defaults/pipeline.yaml +111 -0
- package/defaults/profiles.yaml +215 -0
- package/defaults/runners.yaml +24 -0
- package/dist/argo-graph.js +59 -27
- package/dist/argo-submit.d.ts +0 -1
- package/dist/argo-submit.js +11 -6
- package/dist/argo-workflow.d.ts +1 -2
- package/dist/argo-workflow.js +0 -2
- package/dist/claude-settings-config.js +44 -0
- package/dist/cli/program.js +12 -5
- package/dist/cli/submit-options.js +1 -2
- package/dist/cluster-doctor.js +0 -12
- package/dist/commands/pipeline-command.js +1 -1
- package/dist/config/defaults.js +7 -350
- package/dist/config/schemas.d.ts +9 -9
- package/dist/install-commands/claude-code.js +160 -0
- package/dist/install-commands/opencode.js +56 -39
- package/dist/install-commands/shared.js +32 -5
- package/dist/install-commands.js +26 -15
- package/dist/json-config-merge.js +47 -0
- package/dist/mcp/gateway.js +9 -1
- package/dist/moka-global-config.d.ts +0 -1
- package/dist/moka-global-config.js +0 -1
- package/dist/moka-submit.d.ts +8 -11
- package/dist/moka-submit.js +1 -4
- package/dist/opencode-project-config.js +2 -45
- package/dist/pipeline-runtime.d.ts +9 -0
- package/dist/pipeline-runtime.js +50 -8
- package/dist/planned-node.js +2 -5
- package/dist/{workflow-planner.d.ts → planning/compile.d.ts} +2 -2
- package/dist/{workflow-planner.js → planning/compile.js} +6 -83
- package/dist/{schedule/planner.d.ts → planning/generate.d.ts} +17 -3
- package/dist/{schedule/planner.js → planning/generate.js} +24 -56
- package/dist/planning/graph.js +138 -0
- package/dist/runner-command/lifecycle-context.js +2 -3
- package/dist/runner-command/run.js +2 -3
- package/dist/runner-command-contract.d.ts +2 -2
- package/dist/runner-event-schema.d.ts +349 -0
- package/dist/runner-event-schema.js +185 -0
- package/dist/runner-output.js +2 -2
- package/dist/runner.d.ts +29 -0
- package/dist/runtime/agent-node/agent-node.js +1 -0
- package/dist/runtime/context/context.js +1 -1
- package/dist/runtime/contracts/contracts.d.ts +18 -1
- package/dist/runtime/node-state-store.js +7 -0
- package/dist/runtime/opencode-adapter.js +28 -8
- package/dist/runtime/opencode-agent-name.js +18 -0
- package/dist/runtime/opencode-runtime.js +62 -0
- package/dist/runtime/opencode-server.js +67 -0
- package/dist/runtime/opencode-session-executor.js +206 -0
- package/dist/schedule/passes/coverage.js +7 -51
- package/dist/schedule/passes/ids.js +3 -23
- package/dist/schedule/scheduling-roles.js +19 -0
- package/dist/strings.js +30 -1
- package/docs/adr-opencode-first-goal-loop-runtime.md +1 -1
- package/docs/config-architecture.md +43 -6
- package/docs/mcp-gateway.md +4 -4
- package/docs/operator-guide.md +9 -8
- package/docs/pipeline-console-runner-contract.md +3 -4
- package/docs/slash-command-adapter-contract.md +1 -0
- package/package.json +10 -5
- package/dist/schedule-planner.d.ts +0 -2
- package/dist/schedule-planner.js +0 -2
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { renderClaudeGatewayMcpServers } from "../mcp/gateway.js";
|
|
2
|
+
import { opencodeAgentName } from "../runtime/opencode-agent-name.js";
|
|
3
|
+
import { mergeClaudeSettings } from "../claude-settings-config.js";
|
|
4
|
+
import { CLAUDE_PROJECT_CONFIG_PATH, commandIdForHost, compactLines, entrypointDescription, entrypointEntries, instructionsPointer, invocationForHost } from "./shared.js";
|
|
5
|
+
import { agentDispatchRoutes, entrypointDispatchBlock, grants, header, markdown, projectAgentsMdDefinition, resolvedHostModel, scheduledEntrypointK8sNote } from "./opencode.js";
|
|
6
|
+
//#region src/install-commands/claude-code.ts
|
|
7
|
+
const CLAUDE_CODE_HOST = "claude-code";
|
|
8
|
+
const CLAUDE_ALLOWED_TOOLS = "Task, Bash(opencode run *)";
|
|
9
|
+
const CLAUDE_AGENT_TOOLS = "Bash, Read";
|
|
10
|
+
const MOKA_PROFILE_PREFIX = "moka-";
|
|
11
|
+
function claudeAgentNameForProfile(profileId) {
|
|
12
|
+
return profileId.startsWith(MOKA_PROFILE_PREFIX) ? profileId : `${MOKA_PROFILE_PREFIX}${profileId}`;
|
|
13
|
+
}
|
|
14
|
+
function cliRoutesForConfig(config) {
|
|
15
|
+
return entrypointEntries(config).flatMap(([, entrypoint]) => "workflow" in entrypoint ? agentDispatchRoutes(CLAUDE_CODE_HOST, config, entrypoint.workflow) : []);
|
|
16
|
+
}
|
|
17
|
+
function distinctCliProfiles(config) {
|
|
18
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19
|
+
const profiles = [];
|
|
20
|
+
for (const route of cliRoutesForConfig(config)) {
|
|
21
|
+
if (route.kind !== "cli" || seen.has(route.profileId)) continue;
|
|
22
|
+
seen.add(route.profileId);
|
|
23
|
+
profiles.push(route);
|
|
24
|
+
}
|
|
25
|
+
return profiles.sort((a, b) => a.profileId.localeCompare(b.profileId));
|
|
26
|
+
}
|
|
27
|
+
function commandDispatchBody(config, id, entrypoint) {
|
|
28
|
+
if (!("workflow" in entrypoint)) return entrypointDispatchBlock(CLAUDE_CODE_HOST, config, id, entrypoint) ?? "";
|
|
29
|
+
const routes = agentDispatchRoutes(CLAUDE_CODE_HOST, config, entrypoint.workflow);
|
|
30
|
+
return [
|
|
31
|
+
`Run workflow \`${entrypoint.workflow}\` for the user task.`,
|
|
32
|
+
"",
|
|
33
|
+
"Delegate each agent node to a Claude Code `Task` subagent that wraps a local `opencode run` subprocess.",
|
|
34
|
+
"Spawn one node at a time respecting `needs`; run nodes whose dependencies are satisfied in parallel.",
|
|
35
|
+
"",
|
|
36
|
+
"Node routes:",
|
|
37
|
+
...routes.map(claudeNodeRouteLine),
|
|
38
|
+
"",
|
|
39
|
+
"For each node prompt include:",
|
|
40
|
+
"- user task",
|
|
41
|
+
`- workflow id: ${entrypoint.workflow}`,
|
|
42
|
+
"- node id",
|
|
43
|
+
"- profile id",
|
|
44
|
+
"- profile instructions reference",
|
|
45
|
+
"- profile grants",
|
|
46
|
+
"- dependency outputs",
|
|
47
|
+
"",
|
|
48
|
+
"Only package-configured gates are blocking. Do not invent RED, GREEN, full-suite, typecheck, or unrelated-drift gates.",
|
|
49
|
+
"If a node returns targeted evidence and has no configured blocking gate, advance to the next node.",
|
|
50
|
+
"The Task subagents wrap `opencode run` subprocesses; do not claim these worker nodes are Claude Code native agents."
|
|
51
|
+
].join("\n");
|
|
52
|
+
}
|
|
53
|
+
function claudeNodeRouteLine(route) {
|
|
54
|
+
const needs = route.needs.length > 0 ? route.needs.join(",") : "none";
|
|
55
|
+
return `- ${route.nodeId}: Task subagent_type=${claudeAgentNameForProfile(route.profileId)} runner=${route.runnerId} agent="${opencodeAgentName(route.profileId)}" needs=${needs}`;
|
|
56
|
+
}
|
|
57
|
+
function commandDefinitions(config) {
|
|
58
|
+
return entrypointEntries(config).map(([id, entrypoint]) => ({
|
|
59
|
+
content: markdown({
|
|
60
|
+
"argument-hint": "<task description>",
|
|
61
|
+
"allowed-tools": CLAUDE_ALLOWED_TOOLS,
|
|
62
|
+
description: entrypointDescription(id, entrypoint)
|
|
63
|
+
}, compactLines([
|
|
64
|
+
header(CLAUDE_CODE_HOST).trimEnd(),
|
|
65
|
+
"",
|
|
66
|
+
`Invoke this command with \`${invocationForHost(CLAUDE_CODE_HOST, id)}\`.`,
|
|
67
|
+
"",
|
|
68
|
+
"Load and follow the `execute` skill for the execution doctrine before dispatching work.",
|
|
69
|
+
"",
|
|
70
|
+
scheduledEntrypointK8sNote(entrypoint),
|
|
71
|
+
scheduledEntrypointK8sNote(entrypoint) ? "" : void 0,
|
|
72
|
+
commandDispatchBody(config, id, entrypoint)
|
|
73
|
+
]).join("\n")),
|
|
74
|
+
host: CLAUDE_CODE_HOST,
|
|
75
|
+
invocation: invocationForHost(CLAUDE_CODE_HOST, id),
|
|
76
|
+
path: `.claude/commands/${commandIdForHost(CLAUDE_CODE_HOST, id)}.md`
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
function agentModelProjection(config, profile) {
|
|
80
|
+
const model = resolvedHostModel(config, CLAUDE_CODE_HOST, profile);
|
|
81
|
+
return model ? { model } : {};
|
|
82
|
+
}
|
|
83
|
+
function agentDefinitions(config) {
|
|
84
|
+
return distinctCliProfiles(config).map((route) => {
|
|
85
|
+
const profile = route.profile;
|
|
86
|
+
const agentName = claudeAgentNameForProfile(route.profileId);
|
|
87
|
+
const displayName = opencodeAgentName(route.profileId);
|
|
88
|
+
return {
|
|
89
|
+
content: markdown({
|
|
90
|
+
name: agentName,
|
|
91
|
+
description: profile.description ?? route.profileId,
|
|
92
|
+
tools: CLAUDE_AGENT_TOOLS,
|
|
93
|
+
...agentModelProjection(config, profile)
|
|
94
|
+
}, [
|
|
95
|
+
header(CLAUDE_CODE_HOST).trimEnd(),
|
|
96
|
+
"",
|
|
97
|
+
profile.description ?? route.profileId,
|
|
98
|
+
"",
|
|
99
|
+
`Run EXACTLY ONE \`opencode run --agent "${displayName}" --format json --dir "$PWD" '<node prompt>'\` subprocess for this node.`,
|
|
100
|
+
"Stay inside this node's scope and do not branch into adjacent nodes.",
|
|
101
|
+
"Do not claim completion without fresh evidence from the subprocess output.",
|
|
102
|
+
"Return only: { command, exit status, parsed evidence, touched files, blockers }.",
|
|
103
|
+
"",
|
|
104
|
+
"Configured grants:",
|
|
105
|
+
grants(profile),
|
|
106
|
+
"",
|
|
107
|
+
instructionsPointer(profile)
|
|
108
|
+
].join("\n")),
|
|
109
|
+
host: CLAUDE_CODE_HOST,
|
|
110
|
+
invocation: invocationForHost(CLAUDE_CODE_HOST),
|
|
111
|
+
path: `.claude/agents/${agentName}.md`
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function settingsDefinition(config) {
|
|
116
|
+
const settings = { permissions: { allow: ["Task", "Bash(opencode run *)"] } };
|
|
117
|
+
if (config.mcp_gateway) settings.mcpServers = renderClaudeGatewayMcpServers(config);
|
|
118
|
+
return [{
|
|
119
|
+
content: `${JSON.stringify(settings, null, 2)}\n`,
|
|
120
|
+
host: CLAUDE_CODE_HOST,
|
|
121
|
+
invocation: invocationForHost(CLAUDE_CODE_HOST),
|
|
122
|
+
path: CLAUDE_PROJECT_CONFIG_PATH
|
|
123
|
+
}];
|
|
124
|
+
}
|
|
125
|
+
function claudeCodeDefinitions(config, cwd) {
|
|
126
|
+
return [
|
|
127
|
+
...commandDefinitions(config),
|
|
128
|
+
...agentDefinitions(config),
|
|
129
|
+
...settingsDefinition(config),
|
|
130
|
+
projectAgentsMdDefinition(cwd, CLAUDE_CODE_HOST)
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The claude-code HostAdapter. Encapsulates all Claude Code-specific command
|
|
135
|
+
* generation, resource roots, and settings-merge behaviour.
|
|
136
|
+
*/
|
|
137
|
+
const claudeCodeAdapter = {
|
|
138
|
+
host: "claude-code",
|
|
139
|
+
resourceRoots: [".claude/commands", ".claude/agents"],
|
|
140
|
+
definitions(config, cwd) {
|
|
141
|
+
return claudeCodeDefinitions(config, cwd);
|
|
142
|
+
},
|
|
143
|
+
mergeDefinition(definition, existingContent) {
|
|
144
|
+
if (definition.path !== ".claude/settings.json") return;
|
|
145
|
+
const merged = mergeClaudeSettings(existingContent, JSON.parse(definition.content));
|
|
146
|
+
if (!merged.ok) return {
|
|
147
|
+
ok: false,
|
|
148
|
+
content: definition.content
|
|
149
|
+
};
|
|
150
|
+
return {
|
|
151
|
+
ok: true,
|
|
152
|
+
content: merged.content
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
isAlwaysForced(definition) {
|
|
156
|
+
return definition.path === CLAUDE_PROJECT_CONFIG_PATH;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
//#endregion
|
|
160
|
+
export { claudeCodeAdapter };
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST } from "../config/defaults.js";
|
|
2
2
|
import { resolvePackageAssetPath } from "../package-assets.js";
|
|
3
3
|
import "../config.js";
|
|
4
|
-
import { compileWorkflowPlan } from "../
|
|
4
|
+
import { compileWorkflowPlan } from "../planning/compile.js";
|
|
5
|
+
import { mergeOpenCodeProjectConfig } from "../opencode-project-config.js";
|
|
5
6
|
import { renderOpenCodeGatewayConfig } from "../mcp/gateway.js";
|
|
6
|
-
import {
|
|
7
|
+
import { opencodeAgentName } from "../runtime/opencode-agent-name.js";
|
|
8
|
+
import { AGENTS_MD_END, AGENTS_MD_START, COMMAND_HOSTS, GENERATED_MARKER, GENERATED_TS_MARKER, OPENCODE_PROJECT_CONFIG_PATH, OWNER_MARKER_PREFIX, OWNER_TS_MARKER_PREFIX, SINGLE_OPENCODE_PLUGIN_ARRAY_RE, commandIdForHost, compactLines, entrypointDescription, entrypointEntries, instructionsPointer, invocationForHost, profileEntries } from "./shared.js";
|
|
7
9
|
import { readFileSync } from "node:fs";
|
|
8
10
|
import { basename } from "node:path";
|
|
9
11
|
import matter from "gray-matter";
|
|
10
12
|
//#region src/install-commands/opencode.ts
|
|
11
13
|
const OPENCODE_ORCHESTRATOR_AGENT_ID = "MoKa Orchestrator";
|
|
12
|
-
const MOKA_PROFILE_PREFIX = "moka-";
|
|
13
14
|
function header(host) {
|
|
14
15
|
return [
|
|
15
16
|
GENERATED_MARKER,
|
|
@@ -20,19 +21,6 @@ function header(host) {
|
|
|
20
21
|
function markdown(data, body) {
|
|
21
22
|
return `${matter.stringify(body.trimEnd(), data).trimEnd()}\n`;
|
|
22
23
|
}
|
|
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
24
|
function entrypointCommandDefinitions(_host, config, makeDefinition) {
|
|
37
25
|
return entrypointEntries(config).map(([id, entrypoint]) => makeDefinition(id, entrypoint));
|
|
38
26
|
}
|
|
@@ -104,14 +92,6 @@ function dispatchRouteForAgent(host, config, route) {
|
|
|
104
92
|
function nativeAgentIdForHost(host, profileId) {
|
|
105
93
|
return host === "opencode" ? opencodeAgentName(profileId) : profileId;
|
|
106
94
|
}
|
|
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
95
|
function grants(actor) {
|
|
116
96
|
return [
|
|
117
97
|
`model: ${actor.model ?? "default"}`,
|
|
@@ -169,10 +149,19 @@ function scheduledEntrypointK8sNote(entrypoint) {
|
|
|
169
149
|
if ("workflow" in entrypoint) return;
|
|
170
150
|
return "Submit Momokaya work as Argo Workflows through `moka submit` and `moka submit --quick`.";
|
|
171
151
|
}
|
|
172
|
-
function
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
152
|
+
function localRosterAgentIds(config) {
|
|
153
|
+
return nativeProfileEntries("opencode", config).map(([id]) => nativeAgentIdForHost("opencode", id));
|
|
154
|
+
}
|
|
155
|
+
function localOrchestratorDispatchBlock(config) {
|
|
156
|
+
return [
|
|
157
|
+
"Orchestrate locally. Load and follow the `orchestrate` skill.",
|
|
158
|
+
"Do not submit to Argo or run `moka submit`. Spawn the roster as native Task subagents on this machine and run nodes with satisfied dependencies in parallel.",
|
|
159
|
+
"",
|
|
160
|
+
"Roster (Task tool subagent_type):",
|
|
161
|
+
...localRosterAgentIds(config).map((id) => `- ${id}`),
|
|
162
|
+
"",
|
|
163
|
+
"Gather each subagent's structured output, enforce only package-configured gates, and report only the evidence the subagents returned."
|
|
164
|
+
].join("\n");
|
|
176
165
|
}
|
|
177
166
|
function nativeDispatchBlock(host, routes) {
|
|
178
167
|
if (routes.length === 0) return;
|
|
@@ -222,14 +211,14 @@ function hostSpecificDispatchGuard(host, nativeRoutes, cliRoutes) {
|
|
|
222
211
|
if (cliRoutes.length > 0 && nativeRoutes.length === 0) return `Do not claim these nodes are ${hostDisplayName(host)} subagents.`;
|
|
223
212
|
}
|
|
224
213
|
function hostDisplayName(host) {
|
|
225
|
-
return {
|
|
214
|
+
return {
|
|
215
|
+
opencode: "OpenCode",
|
|
216
|
+
"claude-code": "Claude Code"
|
|
217
|
+
}[host];
|
|
226
218
|
}
|
|
227
219
|
function needsSummary(needs) {
|
|
228
220
|
return needs.length > 0 ? needs.join(",") : "none";
|
|
229
221
|
}
|
|
230
|
-
function compactLines(lines) {
|
|
231
|
-
return lines.filter((line) => line !== void 0);
|
|
232
|
-
}
|
|
233
222
|
const OPENCODE_PERMISSION_TOOLS = [
|
|
234
223
|
"bash",
|
|
235
224
|
"edit",
|
|
@@ -345,13 +334,13 @@ function opencodeDefinitions(config, cwd) {
|
|
|
345
334
|
description: "Orchestrate the configured pipeline and enforce gates.",
|
|
346
335
|
mode: "primary",
|
|
347
336
|
name: OPENCODE_ORCHESTRATOR_AGENT_ID,
|
|
348
|
-
permission: opencodePermission(orchestrator, { allowedTaskAgents:
|
|
337
|
+
permission: opencodePermission(orchestrator, { allowedTaskAgents: localRosterAgentIds(config) })
|
|
349
338
|
}, compactLines([
|
|
350
339
|
header("opencode").trimEnd(),
|
|
351
340
|
"",
|
|
352
341
|
orchestratorBlock(config),
|
|
353
342
|
"",
|
|
354
|
-
|
|
343
|
+
localOrchestratorDispatchBlock(config)
|
|
355
344
|
]).join("\n")),
|
|
356
345
|
host: "opencode",
|
|
357
346
|
invocation: invocationForHost("opencode"),
|
|
@@ -402,6 +391,7 @@ function projectAgentsMdDefinition(cwd, host) {
|
|
|
402
391
|
"- Use `/moka-quick`, `/moka-execute`, or `/moka-inspect` for OpenCode slash-command entrypoints when available.",
|
|
403
392
|
"- Load and follow the relevant skill from `.agents/skills` before doing specialized work.",
|
|
404
393
|
"- Prefer the package-defined pipeline profiles and generated command surfaces over ad hoc subagent prompts.",
|
|
394
|
+
"- When the user needs to run a command, copy the command into the clipboard and tell the user what needs to be returned.",
|
|
405
395
|
"",
|
|
406
396
|
"## Pipeline Memory",
|
|
407
397
|
"",
|
|
@@ -423,9 +413,36 @@ function opencodeModelProjection(config, profile) {
|
|
|
423
413
|
const model = resolvedHostModel(config, "opencode", profile);
|
|
424
414
|
return model ? { model } : {};
|
|
425
415
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
416
|
+
/**
|
|
417
|
+
* The opencode HostAdapter. Encapsulates all opencode-specific command
|
|
418
|
+
* generation, resource roots, and config-merge behaviour.
|
|
419
|
+
*/
|
|
420
|
+
const opencodeAdapter = {
|
|
421
|
+
host: "opencode",
|
|
422
|
+
resourceRoots: [
|
|
423
|
+
".opencode/commands",
|
|
424
|
+
".opencode/agents",
|
|
425
|
+
".opencode/plugins",
|
|
426
|
+
".opencode/skills"
|
|
427
|
+
],
|
|
428
|
+
definitions(config, cwd) {
|
|
429
|
+
return opencodeDefinitions(config, cwd);
|
|
430
|
+
},
|
|
431
|
+
mergeDefinition(definition, existingContent) {
|
|
432
|
+
if (definition.path !== ".opencode/opencode.json") return;
|
|
433
|
+
const merged = mergeOpenCodeProjectConfig(existingContent, JSON.parse(definition.content));
|
|
434
|
+
if (!merged.ok) return {
|
|
435
|
+
ok: false,
|
|
436
|
+
content: definition.content
|
|
437
|
+
};
|
|
438
|
+
return {
|
|
439
|
+
ok: true,
|
|
440
|
+
content: merged.content
|
|
441
|
+
};
|
|
442
|
+
},
|
|
443
|
+
isAlwaysForced(definition) {
|
|
444
|
+
return definition.path === OPENCODE_PROJECT_CONFIG_PATH;
|
|
445
|
+
}
|
|
446
|
+
};
|
|
430
447
|
//#endregion
|
|
431
|
-
export {
|
|
448
|
+
export { agentDispatchRoutes, entrypointDispatchBlock, grants, header, markdown, opencodeAdapter, projectAgentsMdDefinition, resolvedHostModel, scheduledEntrypointK8sNote };
|
|
@@ -9,15 +9,42 @@ const AGENTS_MD_START = "<!-- @oisincoveney/pipeline:agents:start -->";
|
|
|
9
9
|
const AGENTS_MD_END = "<!-- @oisincoveney/pipeline:agents:end -->";
|
|
10
10
|
const SINGLE_OPENCODE_PLUGIN_ARRAY_RE = /\n {2}"plugin": \[\n {4}("[^"]+")\n {2}\]/;
|
|
11
11
|
const OPENCODE_PROJECT_CONFIG_PATH = ".opencode/opencode.json";
|
|
12
|
+
const CLAUDE_PROJECT_CONFIG_PATH = ".claude/settings.json";
|
|
12
13
|
const OPENCODE_COMMAND_PREFIX = "moka-";
|
|
13
|
-
const ENTRYPOINT_PATH_PATTERNS = {
|
|
14
|
-
|
|
14
|
+
const ENTRYPOINT_PATH_PATTERNS = {
|
|
15
|
+
opencode: [/^\.opencode\/commands\/(?:moka-)?([^/]+)\.md$/],
|
|
16
|
+
"claude-code": [/^\.claude\/commands\/(?:moka-)?([^/]+)\.md$/]
|
|
17
|
+
};
|
|
18
|
+
const COMMAND_HOSTS = ["opencode", "claude-code"];
|
|
19
|
+
function profileEntries(config) {
|
|
20
|
+
return Object.entries(config.profiles).sort(([a], [b]) => a.localeCompare(b));
|
|
21
|
+
}
|
|
22
|
+
function entrypointEntries(config) {
|
|
23
|
+
const entries = Object.entries(config.entrypoints);
|
|
24
|
+
return entries.length > 0 ? entries : [["execute", {
|
|
25
|
+
description: "Run the configured pipeline workflow",
|
|
26
|
+
workflow: config.default_workflow
|
|
27
|
+
}]];
|
|
28
|
+
}
|
|
29
|
+
function entrypointDescription(id, entrypoint) {
|
|
30
|
+
return entrypoint.description ?? `Run the ${id} workflow`;
|
|
31
|
+
}
|
|
32
|
+
function instructionsPointer(actor) {
|
|
33
|
+
if (actor.instructions.path) return `Instructions: ${actor.instructions.path}`;
|
|
34
|
+
return `Instructions:\n${actor.instructions.inline ?? ""}`;
|
|
35
|
+
}
|
|
36
|
+
function compactLines(lines) {
|
|
37
|
+
return lines.filter((line) => line !== void 0);
|
|
38
|
+
}
|
|
15
39
|
function invocationForHost(host, entrypointId = "execute") {
|
|
16
|
-
return `${{
|
|
40
|
+
return `${{
|
|
41
|
+
opencode: "/",
|
|
42
|
+
"claude-code": "/"
|
|
43
|
+
}[host]}${commandIdForHost(host, entrypointId)} <task description>`;
|
|
17
44
|
}
|
|
18
45
|
function commandIdForHost(host, entrypointId) {
|
|
19
|
-
if (host === "opencode") return `${OPENCODE_COMMAND_PREFIX}${entrypointId}`;
|
|
46
|
+
if (host === "opencode" || host === "claude-code") return `${OPENCODE_COMMAND_PREFIX}${entrypointId}`;
|
|
20
47
|
return entrypointId;
|
|
21
48
|
}
|
|
22
49
|
//#endregion
|
|
23
|
-
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 };
|
|
50
|
+
export { AGENTS_MD_END, AGENTS_MD_START, CLAUDE_PROJECT_CONFIG_PATH, 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, compactLines, entrypointDescription, entrypointEntries, instructionsPointer, invocationForHost, profileEntries };
|
package/dist/install-commands.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { loadPipelineConfig } from "./config/load.js";
|
|
2
2
|
import "./config.js";
|
|
3
|
-
import { mergeOpenCodeProjectConfig } from "./opencode-project-config.js";
|
|
4
3
|
import { COMMAND_HOSTS, ENTRYPOINT_PATH_PATTERNS, invocationForHost } from "./install-commands/shared.js";
|
|
5
|
-
import {
|
|
4
|
+
import { opencodeAdapter } from "./install-commands/opencode.js";
|
|
5
|
+
import { claudeCodeAdapter } from "./install-commands/claude-code.js";
|
|
6
6
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
7
7
|
import { dirname, join, relative } from "node:path";
|
|
8
8
|
import { mkdir, readdir, rm, writeFile } from "node:fs/promises";
|
|
9
9
|
//#region src/install-commands.ts
|
|
10
|
+
const ADAPTERS = {
|
|
11
|
+
opencode: opencodeAdapter,
|
|
12
|
+
"claude-code": claudeCodeAdapter
|
|
13
|
+
};
|
|
10
14
|
function definitionsFor(host, config, cwd) {
|
|
11
|
-
|
|
12
|
-
return dedupeDefinitionsByPath((host === "all" ? COMMAND_HOSTS : [host]).flatMap((name) => definitions[name]()));
|
|
15
|
+
return dedupeDefinitionsByPath((host === "all" ? COMMAND_HOSTS : [host]).flatMap((name) => ADAPTERS[name].definitions(config, cwd)));
|
|
13
16
|
}
|
|
14
17
|
function dedupeDefinitionsByPath(definitions) {
|
|
15
18
|
const lastIndexes = /* @__PURE__ */ new Map();
|
|
@@ -21,12 +24,9 @@ function dedupeDefinitionsByPath(definitions) {
|
|
|
21
24
|
function selectedHosts(host) {
|
|
22
25
|
return host === "all" ? [...COMMAND_HOSTS] : [host];
|
|
23
26
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
".opencode/plugins",
|
|
28
|
-
".opencode/skills"
|
|
29
|
-
] };
|
|
27
|
+
function resourceRootsFor(host) {
|
|
28
|
+
return ADAPTERS[host].resourceRoots;
|
|
29
|
+
}
|
|
30
30
|
async function listFiles(root) {
|
|
31
31
|
if (!existsSync(root)) return [];
|
|
32
32
|
if (statSync(root).isFile()) return [root];
|
|
@@ -42,7 +42,7 @@ function generatedHostFor(content) {
|
|
|
42
42
|
}
|
|
43
43
|
async function obsoleteGeneratedItems(cwd, host, wantedPaths) {
|
|
44
44
|
const hosts = new Set(selectedHosts(host));
|
|
45
|
-
const roots = selectedHosts(host).flatMap((selectedHost) =>
|
|
45
|
+
const roots = selectedHosts(host).flatMap((selectedHost) => resourceRootsFor(selectedHost));
|
|
46
46
|
return (await Promise.all(roots.map((root) => listFiles(join(cwd, root))))).flat().flatMap((absolutePath) => {
|
|
47
47
|
const generatedHost = generatedHostFor(readFileSync(absolutePath, "utf8"));
|
|
48
48
|
if (!(generatedHost && hosts.has(generatedHost))) return [];
|
|
@@ -63,12 +63,19 @@ function entrypointIdFromGeneratedPath(host, path) {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
function resolveDefinitionContent(definition, target) {
|
|
66
|
-
|
|
66
|
+
const adapter = ADAPTERS[definition.host];
|
|
67
|
+
if (!(adapter.mergeDefinition && existsSync(target))) return {
|
|
68
|
+
conflict: false,
|
|
69
|
+
content: definition.content
|
|
70
|
+
};
|
|
71
|
+
return applyMergeDefinition(adapter.mergeDefinition.bind(adapter), definition, target);
|
|
72
|
+
}
|
|
73
|
+
function applyMergeDefinition(merge, definition, target) {
|
|
74
|
+
const merged = merge(definition, readFileSync(target, "utf8"));
|
|
75
|
+
if (!merged) return {
|
|
67
76
|
conflict: false,
|
|
68
77
|
content: definition.content
|
|
69
78
|
};
|
|
70
|
-
const projection = JSON.parse(definition.content);
|
|
71
|
-
const merged = mergeOpenCodeProjectConfig(readFileSync(target, "utf8"), projection);
|
|
72
79
|
if (!merged.ok) return {
|
|
73
80
|
conflict: true,
|
|
74
81
|
content: definition.content
|
|
@@ -101,9 +108,13 @@ function upsertGeneratedBlock(current, content, block) {
|
|
|
101
108
|
const separator = current.trimEnd().length > 0 ? "\n\n" : "";
|
|
102
109
|
return `${current.trimEnd()}${separator}${content}`;
|
|
103
110
|
}
|
|
111
|
+
function adapterForcesDefinition(definition) {
|
|
112
|
+
const fn = ADAPTERS[definition.host].isAlwaysForced;
|
|
113
|
+
return fn ? fn(definition) : false;
|
|
114
|
+
}
|
|
104
115
|
function installActionForDefinition(definition, target, resolved, force) {
|
|
105
116
|
if (resolved.conflict) return "conflict";
|
|
106
|
-
return actionFor(target, resolved.content, force || definition
|
|
117
|
+
return actionFor(target, resolved.content, force || adapterForcesDefinition(definition), definition.block);
|
|
107
118
|
}
|
|
108
119
|
async function writeDefinition(definition, target, content) {
|
|
109
120
|
await mkdir(dirname(target), { recursive: true });
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { applyEdits, modify, parse } from "jsonc-parser";
|
|
2
|
+
//#region src/json-config-merge.ts
|
|
3
|
+
const JSON_FORMAT_OPTIONS = {
|
|
4
|
+
insertSpaces: true,
|
|
5
|
+
tabSize: 2
|
|
6
|
+
};
|
|
7
|
+
function parseJsonRecord(currentText) {
|
|
8
|
+
const errors = [];
|
|
9
|
+
const value = parse(currentText, errors, {
|
|
10
|
+
allowTrailingComma: true,
|
|
11
|
+
disallowComments: false
|
|
12
|
+
});
|
|
13
|
+
if (errors.length > 0 || !isRecord(value)) return {
|
|
14
|
+
errors,
|
|
15
|
+
ok: false
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
ok: true,
|
|
19
|
+
value
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function setIfMissing(content, parsed, path, value) {
|
|
23
|
+
if (value === void 0 || hasPath(parsed, path)) return content;
|
|
24
|
+
return applyJsonEdit(content, path, value);
|
|
25
|
+
}
|
|
26
|
+
function hasPath(value, path) {
|
|
27
|
+
let cursor = value;
|
|
28
|
+
for (const segment of path) {
|
|
29
|
+
if (!(isRecord(cursor) && segment in cursor)) return false;
|
|
30
|
+
cursor = cursor[segment];
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
function applyJsonEdit(content, path, value) {
|
|
35
|
+
return applyEdits(content, modify(content, path, value, { formattingOptions: JSON_FORMAT_OPTIONS }));
|
|
36
|
+
}
|
|
37
|
+
function formatJson(value) {
|
|
38
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
39
|
+
}
|
|
40
|
+
function ensureTrailingNewline(value) {
|
|
41
|
+
return value.endsWith("\n") ? value : `${value}\n`;
|
|
42
|
+
}
|
|
43
|
+
function isRecord(value) {
|
|
44
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { applyJsonEdit, ensureTrailingNewline, formatJson, isRecord, parseJsonRecord, setIfMissing };
|
package/dist/mcp/gateway.js
CHANGED
|
@@ -66,6 +66,14 @@ function renderOpenCodeGatewayConfig(config) {
|
|
|
66
66
|
} }
|
|
67
67
|
}, null, 2)}\n`;
|
|
68
68
|
}
|
|
69
|
+
function renderClaudeGatewayMcpServers(config) {
|
|
70
|
+
const gateway = configuredGateway(config);
|
|
71
|
+
return { [PIPELINE_GATEWAY_SERVER_ID]: {
|
|
72
|
+
headers: gatewayOpenCodeHeaders(gateway),
|
|
73
|
+
type: "http",
|
|
74
|
+
url: gatewayUrl(gateway)
|
|
75
|
+
} };
|
|
76
|
+
}
|
|
69
77
|
function configureGatewayHosts(config, options) {
|
|
70
78
|
return selectedGatewayHosts(options.host).map((host) => {
|
|
71
79
|
const path = gatewayHostConfigPath(options.scope, options.cwd);
|
|
@@ -422,4 +430,4 @@ function legacyContentHit(cwd, path, pattern) {
|
|
|
422
430
|
return pattern.test(readFileSync(fullPath, "utf8")) ? path : void 0;
|
|
423
431
|
}
|
|
424
432
|
//#endregion
|
|
425
|
-
export { configureGatewayHosts, gatewayServerForProfile, localGatewayStatus, reconcileGateway, renderGatewayConfig, renderOpenCodeGatewayConfig, runGatewayDoctor, startLocalGateway };
|
|
433
|
+
export { configureGatewayHosts, gatewayServerForProfile, localGatewayStatus, reconcileGateway, renderClaudeGatewayMcpServers, renderGatewayConfig, renderOpenCodeGatewayConfig, runGatewayDoctor, startLocalGateway };
|
|
@@ -16,7 +16,6 @@ declare const mokaGlobalConfigSchema: z.ZodObject<{
|
|
|
16
16
|
githubAuthSecretName: z.ZodString;
|
|
17
17
|
imagePullSecretName: z.ZodString;
|
|
18
18
|
opencodeAuthSecretName: z.ZodString;
|
|
19
|
-
queueName: z.ZodString;
|
|
20
19
|
serviceAccountName: z.ZodString;
|
|
21
20
|
}, z.core.$strict>;
|
|
22
21
|
}, z.core.$strict>;
|
|
@@ -15,7 +15,6 @@ const mokaSubmitGlobalConfigSchema = z.object({
|
|
|
15
15
|
githubAuthSecretName: z.string().min(1),
|
|
16
16
|
imagePullSecretName: z.string().min(1),
|
|
17
17
|
opencodeAuthSecretName: z.string().min(1),
|
|
18
|
-
queueName: z.string().min(1),
|
|
19
18
|
serviceAccountName: z.string().min(1)
|
|
20
19
|
}).strict();
|
|
21
20
|
const mokaKubernetesGlobalConfigSchema = z.object({
|
package/dist/moka-submit.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { PipelineConfig } from "./config/schemas.js";
|
|
2
|
-
import { generateScheduleArtifact } from "./
|
|
2
|
+
import { generateScheduleArtifact } from "./planning/generate.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
5
|
//#region src/moka-submit.d.ts
|
|
6
6
|
declare const mokaSubmitDirectHooksSchema: z.ZodRecord<z.ZodEnum<{
|
|
7
7
|
"workflow.start": "workflow.start";
|
|
8
|
+
"node.finish": "node.finish";
|
|
9
|
+
"node.start": "node.start";
|
|
8
10
|
"workflow.success": "workflow.success";
|
|
9
11
|
"workflow.failure": "workflow.failure";
|
|
10
12
|
"workflow.complete": "workflow.complete";
|
|
11
|
-
"node.start": "node.start";
|
|
12
13
|
"node.success": "node.success";
|
|
13
14
|
"node.error": "node.error";
|
|
14
|
-
"node.finish": "node.finish";
|
|
15
15
|
"gate.failure": "gate.failure";
|
|
16
16
|
}> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
17
17
|
failure: z.ZodDefault<z.ZodEnum<{
|
|
@@ -94,13 +94,13 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
94
94
|
}, z.core.$strict>>;
|
|
95
95
|
hooks: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
|
|
96
96
|
"workflow.start": "workflow.start";
|
|
97
|
+
"node.finish": "node.finish";
|
|
98
|
+
"node.start": "node.start";
|
|
97
99
|
"workflow.success": "workflow.success";
|
|
98
100
|
"workflow.failure": "workflow.failure";
|
|
99
101
|
"workflow.complete": "workflow.complete";
|
|
100
|
-
"node.start": "node.start";
|
|
101
102
|
"node.success": "node.success";
|
|
102
103
|
"node.error": "node.error";
|
|
103
|
-
"node.finish": "node.finish";
|
|
104
104
|
"gate.failure": "gate.failure";
|
|
105
105
|
}> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
106
106
|
failure: z.ZodDefault<z.ZodEnum<{
|
|
@@ -148,7 +148,6 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
148
148
|
name: z.ZodOptional<z.ZodString>;
|
|
149
149
|
namespace: z.ZodOptional<z.ZodString>;
|
|
150
150
|
opencodeAuthSecretName: z.ZodOptional<z.ZodString>;
|
|
151
|
-
queueName: z.ZodOptional<z.ZodString>;
|
|
152
151
|
repository: z.ZodOptional<z.ZodObject<{
|
|
153
152
|
baseBranch: z.ZodString;
|
|
154
153
|
sha: z.ZodOptional<z.ZodString>;
|
|
@@ -161,8 +160,8 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
161
160
|
}, z.core.$strict>>;
|
|
162
161
|
serviceAccountName: z.ZodOptional<z.ZodString>;
|
|
163
162
|
mode: z.ZodEnum<{
|
|
164
|
-
quick: "quick";
|
|
165
163
|
full: "full";
|
|
164
|
+
quick: "quick";
|
|
166
165
|
}>;
|
|
167
166
|
schedulePath: z.ZodOptional<z.ZodString>;
|
|
168
167
|
scheduleYaml: z.ZodOptional<z.ZodString>;
|
|
@@ -207,13 +206,13 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
207
206
|
}, z.core.$strict>>;
|
|
208
207
|
hooks: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
|
|
209
208
|
"workflow.start": "workflow.start";
|
|
209
|
+
"node.finish": "node.finish";
|
|
210
|
+
"node.start": "node.start";
|
|
210
211
|
"workflow.success": "workflow.success";
|
|
211
212
|
"workflow.failure": "workflow.failure";
|
|
212
213
|
"workflow.complete": "workflow.complete";
|
|
213
|
-
"node.start": "node.start";
|
|
214
214
|
"node.success": "node.success";
|
|
215
215
|
"node.error": "node.error";
|
|
216
|
-
"node.finish": "node.finish";
|
|
217
216
|
"gate.failure": "gate.failure";
|
|
218
217
|
}> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
219
218
|
failure: z.ZodDefault<z.ZodEnum<{
|
|
@@ -261,7 +260,6 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
261
260
|
name: z.ZodOptional<z.ZodString>;
|
|
262
261
|
namespace: z.ZodOptional<z.ZodString>;
|
|
263
262
|
opencodeAuthSecretName: z.ZodOptional<z.ZodString>;
|
|
264
|
-
queueName: z.ZodOptional<z.ZodString>;
|
|
265
263
|
repository: z.ZodOptional<z.ZodObject<{
|
|
266
264
|
baseBranch: z.ZodString;
|
|
267
265
|
sha: z.ZodOptional<z.ZodString>;
|
|
@@ -327,7 +325,6 @@ interface MokaWorkflowSubmitOptions {
|
|
|
327
325
|
namespace: string;
|
|
328
326
|
opencodeAuthSecretName?: string;
|
|
329
327
|
payloadJson: string;
|
|
330
|
-
queueName?: string;
|
|
331
328
|
scheduleYaml: string;
|
|
332
329
|
serviceAccountName?: string;
|
|
333
330
|
}
|
package/dist/moka-submit.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { normalizeRunnerRepositoryForSubmit } from "./git-remote-url.js";
|
|
2
|
+
import { compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact } from "./planning/generate.js";
|
|
2
3
|
import { buildRunnerCommandPayload, runnerDeliverySchema, runnerHookPolicySchema, runnerRepositoryContextSchema, runnerRunIdentitySchema, runnerTaskSchema } from "./runner-command-contract.js";
|
|
3
|
-
import { compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact } from "./schedule/planner.js";
|
|
4
|
-
import "./schedule-planner.js";
|
|
5
4
|
import { workflowSubmitResultSchema } from "./workflow-submit-contract.js";
|
|
6
5
|
import { buildCommandScheduleYaml, submitRunnerArgoWorkflow } from "./argo-submit.js";
|
|
7
6
|
import { z } from "zod";
|
|
@@ -79,7 +78,6 @@ const mokaSubmitBaseOptionsSchema = z.object({
|
|
|
79
78
|
name: z.string().min(1).optional(),
|
|
80
79
|
namespace: z.string().min(1).optional(),
|
|
81
80
|
opencodeAuthSecretName: z.string().min(1).optional(),
|
|
82
|
-
queueName: z.string().min(1).optional(),
|
|
83
81
|
repository: runnerRepositoryContextSchema.optional(),
|
|
84
82
|
run: runnerRunIdentitySchema.optional(),
|
|
85
83
|
serviceAccountName: z.string().min(1).optional()
|
|
@@ -293,7 +291,6 @@ function workflowSubmitOptions(options) {
|
|
|
293
291
|
name: options.name,
|
|
294
292
|
namespace: requireSubmitOption(options.namespace, "namespace"),
|
|
295
293
|
opencodeAuthSecretName: options.opencodeAuthSecretName,
|
|
296
|
-
queueName: options.queueName,
|
|
297
294
|
serviceAccountName: options.serviceAccountName
|
|
298
295
|
};
|
|
299
296
|
}
|