@oisincoveney/pipeline 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/defaults/pipeline.yaml +111 -0
  2. package/defaults/profiles.yaml +212 -0
  3. package/defaults/runners.yaml +24 -0
  4. package/dist/argo-graph.js +59 -27
  5. package/dist/argo-submit.js +10 -2
  6. package/dist/claude-settings-config.js +44 -0
  7. package/dist/cli/program.js +10 -2
  8. package/dist/config/defaults.js +7 -350
  9. package/dist/config/schemas.d.ts +5 -5
  10. package/dist/install-commands/claude-code.js +160 -0
  11. package/dist/install-commands/opencode.js +39 -32
  12. package/dist/install-commands/shared.js +32 -5
  13. package/dist/install-commands.js +26 -15
  14. package/dist/json-config-merge.js +47 -0
  15. package/dist/mcp/gateway.js +9 -1
  16. package/dist/moka-submit.d.ts +7 -7
  17. package/dist/opencode-project-config.js +2 -45
  18. package/dist/pipeline-runtime.js +50 -8
  19. package/dist/runner-command-contract.d.ts +2 -2
  20. package/dist/runner-event-schema.d.ts +349 -0
  21. package/dist/runner-event-schema.js +185 -0
  22. package/dist/runner-output.js +2 -2
  23. package/dist/runner.d.ts +2 -0
  24. package/dist/runtime/agent-node/agent-node.js +1 -0
  25. package/dist/runtime/contracts/contracts.d.ts +2 -0
  26. package/dist/runtime/node-state-store.js +7 -0
  27. package/dist/runtime/opencode-adapter.js +28 -8
  28. package/dist/runtime/opencode-agent-name.js +18 -0
  29. package/dist/runtime/opencode-runtime.js +62 -0
  30. package/dist/runtime/opencode-server.js +67 -0
  31. package/dist/runtime/opencode-session-executor.js +206 -0
  32. package/docs/adr-opencode-first-goal-loop-runtime.md +1 -1
  33. package/docs/config-architecture.md +11 -6
  34. package/docs/mcp-gateway.md +4 -4
  35. package/docs/operator-guide.md +7 -5
  36. package/docs/slash-command-adapter-contract.md +1 -0
  37. package/package.json +6 -1
@@ -0,0 +1,111 @@
1
+ version: 1
2
+ default_workflow: inspect
3
+ orchestrator:
4
+ profile: moka-orchestrator
5
+ entrypoints:
6
+ quick:
7
+ schedule: quick-schedule
8
+ description: Compact planner-generated pipeline for small work
9
+ execute:
10
+ schedule: execute-schedule
11
+ description: Full planner-generated pipeline for repository work
12
+ inspect:
13
+ workflow: inspect
14
+ description: Read-only repository inspection
15
+ hooks:
16
+ functions:
17
+ generated-defaults-audit:
18
+ kind: command
19
+ command: [node, -e, "const fs=require('node:fs'); fs.writeFileSync(process.env.PIPELINE_HOOK_RESULT, JSON.stringify({status:'pass',summary:'Generated defaults audit passed'}));"]
20
+ trusted: true
21
+ timeout_ms: 5000
22
+ output_limit_bytes: 4096
23
+ on:
24
+ workflow.start:
25
+ - id: generated-defaults-audit
26
+ function: generated-defaults-audit
27
+ failure: fail
28
+ runner_command:
29
+ environment:
30
+ setup:
31
+ - command: bun
32
+ args: [install, --frozen-lockfile]
33
+ scheduler:
34
+ commands:
35
+ quick:
36
+ schedule: quick-schedule
37
+ catalog: quick
38
+ execute:
39
+ schedule: execute-schedule
40
+ catalog: execute
41
+ node_catalogs:
42
+ quick:
43
+ required_categories: [intake, red, green, mechanical, verification]
44
+ nodes:
45
+ backlog-intake:
46
+ category: intake
47
+ profile: moka-researcher
48
+ models: [openai/gpt-5.5-medium]
49
+ red-tests:
50
+ category: red
51
+ profile: moka-test-writer
52
+ models: [openai/gpt-5.5-high, kimi-for-coding/kimi-k2-thinking]
53
+ green-implementation:
54
+ category: green
55
+ profile: moka-code-writer
56
+ models: [openai/gpt-5.5-high, kimi-for-coding/k2p6, opencode-go/qwen3.7-max]
57
+ verification:
58
+ category: verification
59
+ profile: moka-verifier
60
+ models: [openai/gpt-5.5-medium]
61
+ execute:
62
+ required_categories: [intake, research, red, green, mechanical, acceptance, verification, learn]
63
+ nodes:
64
+ backlog-intake:
65
+ category: intake
66
+ profile: moka-researcher
67
+ models: [openai/gpt-5.5-medium]
68
+ research:
69
+ category: research
70
+ profile: moka-researcher
71
+ models: [openai/gpt-5.5-medium, kimi-for-coding/k2p6]
72
+ red-tests:
73
+ category: red
74
+ profile: moka-test-writer
75
+ models: [openai/gpt-5.5-high, kimi-for-coding/kimi-k2-thinking]
76
+ green-backend:
77
+ category: green
78
+ profile: moka-code-writer
79
+ models: [openai/gpt-5.5-high, kimi-for-coding/k2p6, opencode-go/qwen3.7-max]
80
+ green-frontend:
81
+ category: green
82
+ profile: moka-code-writer
83
+ models: [openai/gpt-5.5-high, kimi-for-coding/k2p6, opencode-go/qwen3.7-max]
84
+ acceptance-review:
85
+ category: acceptance
86
+ profile: moka-acceptance-reviewer
87
+ models: [openai/gpt-5.5-medium]
88
+ verification:
89
+ category: verification
90
+ profile: moka-verifier
91
+ models: [openai/gpt-5.5-medium]
92
+ learn:
93
+ category: learn
94
+ profile: moka-learner
95
+ models: [openai/gpt-5.5-low]
96
+ schedules:
97
+ quick-schedule:
98
+ baseline: quick
99
+ planner_profile: moka-schedule-planner
100
+ node_catalog: quick
101
+ execute-schedule:
102
+ baseline: execute
103
+ planner_profile: moka-schedule-planner
104
+ node_catalog: execute
105
+ workflows:
106
+ inspect:
107
+ description: Read-only repository inspection workflow.
108
+ nodes:
109
+ - id: inspect
110
+ kind: agent
111
+ profile: moka-inspector
@@ -0,0 +1,212 @@
1
+ version: 1
2
+ mcp_gateway:
3
+ provider: toolhive
4
+ mode: local
5
+ url: https://pipeline-mcp.momokaya.ee/mcp/
6
+ url_env: PIPELINE_MCP_GATEWAY_URL
7
+ authorization_env: PIPELINE_MCP_GATEWAY_AUTHORIZATION
8
+ default_profile: default
9
+ backends:
10
+ context7:
11
+ locality: shared-remote
12
+ tool_prefixes: [context7]
13
+ uidotsh:
14
+ locality: shared-remote
15
+ tool_prefixes: [uidotsh]
16
+ qdrant:
17
+ locality: repo-scoped-remote
18
+ tool_prefixes: [qdrant]
19
+ fallow:
20
+ locality: repo-local
21
+ workspace_path_source: PIPELINE_TARGET_PATH
22
+ required: false
23
+ tool_prefixes: [fallow]
24
+ serena:
25
+ locality: repo-local
26
+ workspace_path_source: PIPELINE_TARGET_PATH
27
+ tool_prefixes: [serena]
28
+ backlog:
29
+ locality: repo-local
30
+ workspace_path_source: PIPELINE_TARGET_PATH
31
+ tool_prefixes: [backlog]
32
+ skills:
33
+ execute:
34
+ path: .agents/skills/execute/SKILL.md
35
+ source_root: package
36
+ inspect:
37
+ path: .agents/skills/inspect/SKILL.md
38
+ source_root: package
39
+ quick:
40
+ path: .agents/skills/quick/SKILL.md
41
+ source_root: package
42
+ critique:
43
+ path: .agents/skills/critique/SKILL.md
44
+ source_root: package
45
+ doubt:
46
+ path: .agents/skills/doubt/SKILL.md
47
+ source_root: package
48
+ fix:
49
+ path: .agents/skills/fix/SKILL.md
50
+ source_root: package
51
+ library-first-development:
52
+ path: .agents/skills/library-first-development/SKILL.md
53
+ source_root: package
54
+ migrate:
55
+ path: .agents/skills/migrate/SKILL.md
56
+ source_root: package
57
+ optimize:
58
+ path: .agents/skills/optimize/SKILL.md
59
+ source_root: package
60
+ research:
61
+ path: .agents/skills/research/SKILL.md
62
+ source_root: package
63
+ schedule-graph-shaping:
64
+ path: .agents/skills/schedule-graph-shaping/SKILL.md
65
+ source_root: package
66
+ scope:
67
+ path: .agents/skills/scope/SKILL.md
68
+ source_root: package
69
+ secure:
70
+ path: .agents/skills/secure/SKILL.md
71
+ source_root: package
72
+ spec:
73
+ path: .agents/skills/spec/SKILL.md
74
+ source_root: package
75
+ test:
76
+ path: .agents/skills/test/SKILL.md
77
+ source_root: package
78
+ trace:
79
+ path: .agents/skills/trace/SKILL.md
80
+ source_root: package
81
+ verify:
82
+ path: .agents/skills/verify/SKILL.md
83
+ source_root: package
84
+ profiles:
85
+ moka-orchestrator:
86
+ runner: opencode
87
+ description: Orchestrate the configured pipeline and enforce gates.
88
+ instructions: { inline: "Orchestrate the configured pipeline through package-defined entrypoints, native agents, and gates. Do not bypass configured runner subprocesses or package-configured gates." }
89
+ skills: [execute, quick, inspect]
90
+ mcp_servers: [pipeline-gateway]
91
+ tools: [read, list, grep, glob, bash]
92
+ filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
93
+ network: { mode: inherit }
94
+ moka-researcher:
95
+ runner: opencode
96
+ description: Research the requested task and produce structured findings.
97
+ instructions: { inline: "Inspect first-party source, tests, docs, and task context for the current task only. Produce concise findings with file references and stop; do not perform open-ended repository exploration." }
98
+ timeout_ms: 900000
99
+ skills: [research, spec, scope]
100
+ mcp_servers: [pipeline-gateway]
101
+ tools: [read, list, grep, glob, bash]
102
+ filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
103
+ network: { mode: inherit }
104
+ output:
105
+ format: json_schema
106
+ schema_path: .pipeline/schemas/research.schema.json
107
+ repair: { enabled: true, max_attempts: 1 }
108
+ moka-inspector:
109
+ runner: opencode
110
+ model: openai/gpt-5.5-low
111
+ description: Inspect the repository without modifying files.
112
+ instructions: { inline: "Inspect the repository without modifying files." }
113
+ skills: [research]
114
+ mcp_servers: [pipeline-gateway]
115
+ tools: [read, list, grep, glob, bash]
116
+ filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
117
+ network: { mode: inherit }
118
+ moka-schedule-planner:
119
+ runner: opencode
120
+ model: openai/gpt-5.5-xhigh
121
+ description: Refine a baseline schedule into a specialized approved-plan artifact.
122
+ instructions: { inline: "Generate exactly one workflow named root as an explicit schedule graph. Return YAML only." }
123
+ timeout_ms: 300000
124
+ skills: [schedule-graph-shaping]
125
+ mcp_servers: [pipeline-gateway]
126
+ tools: [read, list, grep, glob, bash]
127
+ filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
128
+ network: { mode: inherit }
129
+ moka-test-writer:
130
+ runner: opencode
131
+ scheduling_roles: [implementation]
132
+ description: Add focused failing tests for the requested behavior.
133
+ instructions: { inline: "Add focused failing tests for the requested behavior only. Do not change production code. Only edit files matching test paths such as **/*.test.*, **/*.spec.*, **/*_test.*, **/__tests__/**, test/**, or tests/**. Return only valid JSON with top-level changes and verification. Every changes entry must include summary, why, and files. Include risks, followups, and lessons when present. Do not use Markdown fences or prose outside the JSON object." }
134
+ skills: [test]
135
+ mcp_servers: [pipeline-gateway]
136
+ tools: [read, list, grep, glob, bash, edit, write]
137
+ filesystem: { mode: workspace-write, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
138
+ network: { mode: inherit }
139
+ output:
140
+ format: json_schema
141
+ schema_path: .pipeline/schemas/implementation.schema.json
142
+ repair: { enabled: true, max_attempts: 1 }
143
+ moka-code-writer:
144
+ runner: opencode
145
+ scheduling_roles: [implementation]
146
+ description: Implement production code until the failing tests pass.
147
+ instructions: { inline: "Implement the smallest production change that satisfies the failing tests. Return only valid JSON with top-level changes and verification. Every changes entry must include summary, why, and files. Include risks, followups, and lessons when present. Do not use Markdown fences or prose outside the JSON object." }
148
+ skills: [trace, test, fix, library-first-development]
149
+ mcp_servers: [pipeline-gateway]
150
+ tools: [read, list, grep, glob, bash, edit, write]
151
+ filesystem: { mode: workspace-write, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
152
+ network: { mode: inherit }
153
+ output:
154
+ format: json_schema
155
+ schema_path: .pipeline/schemas/implementation.schema.json
156
+ repair: { enabled: true, max_attempts: 1 }
157
+ moka-acceptance-reviewer:
158
+ runner: opencode
159
+ scheduling_roles: [coverage]
160
+ description: Audit the finished change against every acceptance criterion.
161
+ instructions: { inline: 'Audit the completed change against each canonical acceptance criterion independently. Return only valid JSON with top-level "verdict", "evidence", "acceptance", and optional "violations". Each "acceptance" entry must include "id", "verdict", and non-empty "evidence". Do not use Markdown fences or prose outside the JSON object.' }
162
+ skills: [critique, doubt]
163
+ mcp_servers: [pipeline-gateway]
164
+ tools: [read, list, grep, glob, bash]
165
+ filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
166
+ network: { mode: inherit }
167
+ output:
168
+ format: json_schema
169
+ schema_path: .pipeline/schemas/acceptance.schema.json
170
+ repair: { enabled: true, max_attempts: 1 }
171
+ moka-thermo-nuclear-reviewer:
172
+ runner: opencode
173
+ scheduling_roles: [coverage]
174
+ description: Perform the final thermo-nuclear code quality review of the integration branch.
175
+ instructions: { inline: "Perform the final code quality review of the integration branch." }
176
+ skills: [critique]
177
+ mcp_servers: [pipeline-gateway]
178
+ tools: [read, list, grep, glob, bash]
179
+ filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
180
+ network: { mode: inherit }
181
+ output:
182
+ format: json_schema
183
+ schema_path: .pipeline/schemas/review.schema.json
184
+ repair: { enabled: true, max_attempts: 1 }
185
+ moka-verifier:
186
+ runner: opencode
187
+ scheduling_roles: [coverage]
188
+ description: Verify checks, implementation fit, and final evidence.
189
+ instructions: { inline: 'Verify checks, implementation fit, and final evidence. Return only valid JSON with top-level "verdict", "evidence", and optional "violations". Do not use Markdown fences or prose outside the JSON object.' }
190
+ skills: [verify, critique, secure, optimize]
191
+ mcp_servers: [pipeline-gateway]
192
+ tools: [read, list, grep, glob, bash]
193
+ filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
194
+ network: { mode: inherit }
195
+ output:
196
+ format: json_schema
197
+ schema_path: .pipeline/schemas/verify.schema.json
198
+ repair: { enabled: true, max_attempts: 1 }
199
+ moka-learner:
200
+ runner: opencode
201
+ model: openai/gpt-5.5-low
202
+ description: Store durable lessons from the completed run.
203
+ instructions: { inline: "Store durable lessons from the completed run when useful." }
204
+ skills: [migrate]
205
+ mcp_servers: [pipeline-gateway]
206
+ tools: [read, list, grep, glob, bash]
207
+ filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
208
+ network: { mode: inherit }
209
+ output:
210
+ format: json_schema
211
+ schema_path: .pipeline/schemas/learn.schema.json
212
+ repair: { enabled: true, max_attempts: 1 }
@@ -0,0 +1,24 @@
1
+ version: 1
2
+ runners:
3
+ opencode:
4
+ type: opencode
5
+ capabilities:
6
+ native_subagents: true
7
+ rules: true
8
+ skills: true
9
+ mcp_servers: true
10
+ tools: [read, list, grep, glob, bash, edit, write]
11
+ filesystem: [read-only, workspace-write]
12
+ network: [inherit, disabled]
13
+ output_formats: [text, json, jsonl, json_schema]
14
+ command:
15
+ type: command
16
+ capabilities:
17
+ native_subagents: false
18
+ rules: false
19
+ skills: false
20
+ mcp_servers: false
21
+ tools: [bash]
22
+ filesystem: [read-only, workspace-write]
23
+ network: [inherit, disabled]
24
+ output_formats: [text, json]
@@ -1,11 +1,6 @@
1
1
  import { uniqueStrings } from "./strings.js";
2
2
  import { z } from "zod";
3
3
  //#region src/argo-graph.ts
4
- const EXECUTABLE_NODE_KINDS = [
5
- "agent",
6
- "builtin",
7
- "command"
8
- ];
9
4
  const argoExecutableTaskSchema = z.object({
10
5
  dependencies: z.array(z.string().min(1)),
11
6
  nodeId: z.string().min(1),
@@ -18,6 +13,21 @@ const argoExecutionGraphSchema = z.object({
18
13
  terminalTaskNames: z.array(z.string().min(1)),
19
14
  workflowId: z.string().min(1)
20
15
  }).strict();
16
+ /**
17
+ * Thrown when the Argo graph compiler encounters a node kind that cannot be
18
+ * lowered to an Argo DAG task. Callers should surface this as a validation
19
+ * failure before attempting a cluster submission.
20
+ */
21
+ var ArgoGraphCompilerError = class extends Error {
22
+ kind;
23
+ nodeId;
24
+ constructor(kind, nodeId) {
25
+ super(`Argo graph compiler: node kind '${kind}' on node '${nodeId}' cannot be lowered to an Argo DAG task`);
26
+ this.name = "ArgoGraphCompilerError";
27
+ this.kind = kind;
28
+ this.nodeId = nodeId;
29
+ }
30
+ };
21
31
  function compileArgoExecutionGraph(plan) {
22
32
  const compiler = new ArgoGraphCompiler(plan);
23
33
  return argoExecutionGraphSchema.parse(compiler.compile());
@@ -47,41 +57,63 @@ var ArgoGraphCompiler = class {
47
57
  }
48
58
  }
49
59
  compileNodes(nodes, inheritedNeeds) {
50
- for (const node of nodes) {
51
- if (isExecutableNode(node)) {
52
- const dependencies = this.resolveDependencyTaskNames([...inheritedNeeds, ...node.needs]);
53
- const task = argoExecutableTaskSchema.parse({
54
- dependencies,
55
- nodeId: node.id,
56
- taskName: argoTaskName(node.id),
57
- templateName: argoTemplateName(node.id)
58
- });
59
- this.tasks.push(task);
60
- continue;
61
- }
62
- if (node.kind === "group") continue;
63
- if (node.kind === "parallel") this.compileNodes(node.children ?? [], [...inheritedNeeds, ...node.needs]);
60
+ for (const node of nodes) this.compileNode(node, inheritedNeeds);
61
+ }
62
+ compileNode(node, inheritedNeeds) {
63
+ const kind = node.kind;
64
+ switch (kind) {
65
+ case "agent":
66
+ case "builtin":
67
+ case "command":
68
+ this.compileExecutableNode(node, inheritedNeeds);
69
+ return;
70
+ case "group": return;
71
+ case "parallel":
72
+ this.compileParallelNode(node, inheritedNeeds);
73
+ return;
74
+ default: throw new ArgoGraphCompilerError(String(kind), node.id);
64
75
  }
65
76
  }
77
+ compileExecutableNode(node, inheritedNeeds) {
78
+ const dependencies = this.resolveDependencyTaskNames([...inheritedNeeds, ...node.needs]);
79
+ const task = argoExecutableTaskSchema.parse({
80
+ dependencies,
81
+ nodeId: node.id,
82
+ taskName: argoTaskName(node.id),
83
+ templateName: argoTemplateName(node.id)
84
+ });
85
+ this.tasks.push(task);
86
+ }
87
+ compileParallelNode(node, inheritedNeeds) {
88
+ this.compileNodes(node.children ?? [], [...inheritedNeeds, ...node.needs]);
89
+ }
66
90
  resolveDependencyTaskNames(nodeIds) {
67
91
  return uniqueStrings(nodeIds.flatMap((nodeId) => this.resolveDependencyNodeIds(nodeId).map((id) => argoTaskName(id))));
68
92
  }
69
93
  resolveDependencyNodeIds(nodeId) {
70
94
  const node = this.nodeById.get(nodeId);
71
95
  if (!node) return [];
72
- if (isExecutableNode(node)) return [node.id];
73
- if (node.kind === "group") return uniqueStrings([...node.nodes ?? [], ...node.needs].flatMap((id) => this.resolveDependencyNodeIds(id)));
74
- if (node.kind === "parallel") return uniqueStrings((node.children ?? []).flatMap((child) => this.resolveDependencyNodeIds(child.id)));
75
- return [];
96
+ const kind = node.kind;
97
+ switch (kind) {
98
+ case "agent":
99
+ case "builtin":
100
+ case "command": return [node.id];
101
+ case "group": return this.resolveGroupNodeIds(node);
102
+ case "parallel": return this.resolveParallelNodeIds(node);
103
+ default: throw new ArgoGraphCompilerError(String(kind), node.id);
104
+ }
105
+ }
106
+ resolveGroupNodeIds(node) {
107
+ return uniqueStrings([...node.nodes ?? [], ...node.needs].flatMap((id) => this.resolveDependencyNodeIds(id)));
108
+ }
109
+ resolveParallelNodeIds(node) {
110
+ return uniqueStrings((node.children ?? []).flatMap((child) => this.resolveDependencyNodeIds(child.id)));
76
111
  }
77
112
  terminalTasks() {
78
113
  const dependedOn = new Set(this.tasks.flatMap((task) => task.dependencies));
79
114
  return this.tasks.filter((task) => !dependedOn.has(task.taskName));
80
115
  }
81
116
  };
82
- function isExecutableNode(node) {
83
- return EXECUTABLE_NODE_KINDS.includes(node.kind);
84
- }
85
117
  function argoTaskName(nodeId) {
86
118
  return `node-${nodeId}`;
87
119
  }
@@ -89,4 +121,4 @@ function argoTemplateName(nodeId) {
89
121
  return `task-${nodeId}`;
90
122
  }
91
123
  //#endregion
92
- export { compileArgoExecutionGraph };
124
+ export { ArgoGraphCompilerError, compileArgoExecutionGraph };
@@ -1,4 +1,4 @@
1
- import { compileArgoExecutionGraph } from "./argo-graph.js";
1
+ import { ArgoGraphCompilerError, compileArgoExecutionGraph } from "./argo-graph.js";
2
2
  import { buildRunnerTaskDescriptor } from "./runner-command/task-descriptor.js";
3
3
  import { buildRunnerArgoWorkflowManifest, runnerArgoWorkflowManifestSchema } from "./argo-workflow.js";
4
4
  import { normalizeRunnerRepositoryForSubmit } from "./git-remote-url.js";
@@ -62,7 +62,7 @@ async function submitRunnerArgoWorkflow(rawOptions, dependencies = {}) {
62
62
  const scheduleArtifactConfigMapName = `pipeline-schedule-${randomBytes(6).toString("hex")}`;
63
63
  const taskDescriptorConfigMapName = `pipeline-task-descriptors-${randomBytes(6).toString("hex")}`;
64
64
  if (payload.workflow.id !== compiled.workflowId) throw new Error(`Runner payload workflow '${payload.workflow.id}' does not match schedule workflow '${compiled.workflowId}'`);
65
- const graph = compileArgoExecutionGraph(compiled.plan);
65
+ const graph = compileSubmitArgoGraph(compiled);
66
66
  const labels = {
67
67
  "pipeline.oisin.dev/project": payload.run.project,
68
68
  "pipeline.oisin.dev/run-id": payload.run.id,
@@ -184,6 +184,14 @@ function normalizeRunnerPayloadForSubmit(input) {
184
184
  payloadJson: JSON.stringify(payload)
185
185
  };
186
186
  }
187
+ function compileSubmitArgoGraph(compiled) {
188
+ try {
189
+ return compileArgoExecutionGraph(compiled.plan);
190
+ } catch (err) {
191
+ if (err instanceof ArgoGraphCompilerError) throw new Error(`Schedule '${compiled.workflowId}' cannot be submitted: ${err.message}`);
192
+ throw err;
193
+ }
194
+ }
187
195
  function apiClients(options, dependencies) {
188
196
  if (dependencies.coreApi && dependencies.workflowApi) return {
189
197
  coreApi: dependencies.coreApi,
@@ -0,0 +1,44 @@
1
+ import { applyJsonEdit, ensureTrailingNewline, formatJson, isRecord, parseJsonRecord, setIfMissing } from "./json-config-merge.js";
2
+ //#region src/claude-settings-config.ts
3
+ function mergeClaudeSettings(currentText, projection) {
4
+ if (currentText === void 0) return {
5
+ content: formatJson(projection),
6
+ ok: true
7
+ };
8
+ const parsed = parseJsonRecord(currentText);
9
+ if (!parsed.ok) return parsed;
10
+ return {
11
+ content: ensureTrailingNewline(applyClaudeProjection(currentText, parsed.value, projection)),
12
+ ok: true
13
+ };
14
+ }
15
+ function applyClaudeProjection(currentText, parsed, projection) {
16
+ return applyPermissionsAllow(applyMcpServersProjection(currentText, parsed, projection), parsed, projection);
17
+ }
18
+ function applyMcpServersProjection(content, parsed, projection) {
19
+ return Object.entries(projection.mcpServers ?? {}).reduce((nextContent, [name, server]) => setIfMissing(nextContent, parsed, ["mcpServers", name], server), content);
20
+ }
21
+ function applyPermissionsAllow(content, parsed, projection) {
22
+ const existing = permissionAllowList(parsed);
23
+ const merged = unionPreservingOrder(existing, projectedAllowList(projection));
24
+ return sameOrderedList(existing, merged) ? content : applyJsonEdit(content, ["permissions", "allow"], merged);
25
+ }
26
+ function projectedAllowList(projection) {
27
+ return projection.permissions?.allow ?? [];
28
+ }
29
+ function unionPreservingOrder(existing, extra) {
30
+ const merged = [...existing];
31
+ for (const entry of extra) if (!merged.includes(entry)) merged.push(entry);
32
+ return merged;
33
+ }
34
+ function sameOrderedList(a, b) {
35
+ return a.length === b.length && a.every((value, index) => value === b[index]);
36
+ }
37
+ function permissionAllowList(parsed) {
38
+ const permissions = parsed.permissions;
39
+ if (!isRecord(permissions)) return [];
40
+ const allow = permissions.allow;
41
+ return Array.isArray(allow) ? allow.filter((entry) => typeof entry === "string") : [];
42
+ }
43
+ //#endregion
44
+ export { mergeClaudeSettings };
@@ -150,7 +150,7 @@ function createCliProgram() {
150
150
  const config = loadPipelineConfig(process.env.PIPELINE_TARGET_PATH ?? process.cwd(), { allowMissingLintFileReferences: true });
151
151
  console.log(renderGatewayConfig(config));
152
152
  });
153
- gatewayCommand.command("configure-host").description("Rewrite host MCP config to the singleton pipeline gateway").addOption(new Option("--host <host>", "host config to update").choices(["all", "opencode"]).default("all").argParser(parseCommandHost)).addOption(new Option("--scope <scope>", "config scope to update").choices(["project", "global"]).default("project").argParser(parseGatewayHostScope)).action((flags) => {
153
+ gatewayCommand.command("configure-host").description("Rewrite host MCP config to the singleton pipeline gateway").addOption(new Option("--host <host>", "host config to update").choices(["all", "opencode"]).default("all").argParser(parseGatewayHost)).addOption(new Option("--scope <scope>", "config scope to update").choices(["project", "global"]).default("project").argParser(parseGatewayHostScope)).action((flags) => {
154
154
  const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
155
155
  const result = configureGatewayHosts(loadPipelineConfig(cwd, { allowMissingLintFileReferences: true }), {
156
156
  cwd,
@@ -182,7 +182,11 @@ function createCliProgram() {
182
182
  const result = await initPipelineProject({ cwd: process.env.PIPELINE_TARGET_PATH ?? process.cwd() });
183
183
  console.log(formatPipelineInitResult(result));
184
184
  });
185
- program.command("install-commands").description("Install generated slash-command adapters into this repository").addOption(new Option("--host <host>", "host command set to install").choices(["all", "opencode"]).default("all").argParser(parseCommandHost)).option("--dry-run", "show planned changes without writing files").option("--check", "fail if generated command files are missing or stale").option("--force", "overwrite manually edited command files").action(async (flags) => {
185
+ program.command("install-commands").description("Install generated slash-command adapters into this repository").addOption(new Option("--host <host>", "host command set to install").choices([
186
+ "all",
187
+ "opencode",
188
+ "claude-code"
189
+ ]).default("all").argParser(parseCommandHost)).option("--dry-run", "show planned changes without writing files").option("--check", "fail if generated command files are missing or stale").option("--force", "overwrite manually edited command files").action(async (flags) => {
186
190
  const result = await installCommands({
187
191
  ...flags,
188
192
  cwd: process.env.PIPELINE_TARGET_PATH ?? process.cwd()
@@ -232,6 +236,10 @@ function parseGatewayHostScope(value) {
232
236
  if (value === "project" || value === "global") return value;
233
237
  throw new Error("scope must be project or global");
234
238
  }
239
+ function parseGatewayHost(value) {
240
+ if (value === "all" || value === "opencode") return value;
241
+ throw new Error("host must be all or opencode");
242
+ }
235
243
  async function runCli(argv) {
236
244
  await createCliProgram().parseAsync(argv, { from: "node" });
237
245
  }