@oisincoveney/pipeline 2.8.3 → 2.9.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 (69) hide show
  1. package/.agents/skills/orchestrate/SKILL.md +45 -32
  2. package/README.md +51 -41
  3. package/defaults/pipeline.yaml +13 -12
  4. package/defaults/profiles.yaml +1 -1
  5. package/dist/argo-submit.js +1 -1
  6. package/dist/cli/doctor.d.ts +21 -0
  7. package/dist/cli/doctor.js +268 -0
  8. package/dist/cli/format.js +6 -3
  9. package/dist/cli/program.d.ts +14 -16
  10. package/dist/cli/program.js +291 -104
  11. package/dist/cli/run-resolver.js +58 -0
  12. package/dist/commands/bench-command.js +12 -4
  13. package/dist/commands/pipeline-command.js +22 -5
  14. package/dist/commands/runner-command-command.js +32 -9
  15. package/dist/config/lint.js +44 -26
  16. package/dist/config/load.js +0 -1
  17. package/dist/config/schemas.d.ts +0 -6
  18. package/dist/config/schemas.js +0 -8
  19. package/dist/context/repo-map.js +72 -56
  20. package/dist/gates.js +1 -1
  21. package/dist/index.d.ts +2 -1
  22. package/dist/index.js +20 -14
  23. package/dist/install-commands/claude-code.js +4 -33
  24. package/dist/install-commands/opencode.js +119 -171
  25. package/dist/mcp/repo-local-backends.js +51 -39
  26. package/dist/moka-submit.js +3 -3
  27. package/dist/pipeline-runtime.js +15 -5
  28. package/dist/planning/generate.js +5 -11
  29. package/dist/run-control/commands.js +340 -0
  30. package/dist/run-control/contracts.d.ts +21 -0
  31. package/dist/run-control/contracts.js +129 -0
  32. package/dist/run-control/detach.js +79 -0
  33. package/dist/run-control/runtime-reporter.js +187 -0
  34. package/dist/run-control/store.js +304 -0
  35. package/dist/run-control/supervisor.js +192 -0
  36. package/dist/runner-command/finalize.js +28 -37
  37. package/dist/runner-command/lifecycle-context.js +130 -63
  38. package/dist/runner-command/lifecycle.js +22 -31
  39. package/dist/runner-command/run.js +120 -72
  40. package/dist/runner-command/task-descriptor.js +11 -4
  41. package/dist/runner.js +1 -1
  42. package/dist/runtime/agent-node/agent-node.js +3 -3
  43. package/dist/runtime/builtins/builtins.js +1 -3
  44. package/dist/runtime/changed-files/changed-files.js +1 -1
  45. package/dist/runtime/context/context.js +1 -1
  46. package/dist/runtime/contracts/contracts.d.ts +4 -0
  47. package/dist/runtime/hooks/hooks.js +1 -1
  48. package/dist/runtime/json-validation/json-validation.js +49 -23
  49. package/dist/runtime/local-scheduler.js +49 -26
  50. package/dist/runtime/opencode-adapter.js +14 -10
  51. package/dist/runtime/opencode-runtime.js +22 -20
  52. package/dist/runtime/opencode-session-executor.js +1 -1
  53. package/dist/runtime/parallel-node/parallel-node.js +10 -0
  54. package/dist/runtime/parallel-worktrees/parallel-worktrees.js +2 -35
  55. package/dist/runtime/run-journal.js +17 -10
  56. package/dist/runtime/services/file-system-service.js +29 -0
  57. package/dist/runtime/services/opencode-runtime-server-service.js +27 -0
  58. package/dist/runtime/services/repo-io-service.js +48 -0
  59. package/dist/runtime/services/run-journal-file-service.js +20 -0
  60. package/dist/runtime/services/runner-command-io-service.js +88 -0
  61. package/dist/runtime/workflow-lifecycle.js +76 -39
  62. package/dist/schedule/backlog-context.js +55 -29
  63. package/dist/schedule/passes/index.js +0 -1
  64. package/docs/config-architecture.md +4 -26
  65. package/docs/operator-guide.md +73 -32
  66. package/package.json +2 -1
  67. package/dist/runtime/select-candidate/select-candidate.js +0 -144
  68. package/dist/runtime/services/select-candidate-service.js +0 -13
  69. package/dist/schedule/passes/candidates.js +0 -51
@@ -1,12 +1,13 @@
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 "../planning/compile.js";
5
- import { mergeOpenCodeProjectConfig } from "../opencode-project-config.js";
4
+ import { RepoIoService, runRepoIoSync } from "../runtime/services/repo-io-service.js";
6
5
  import { renderOpenCodeGatewayConfig } from "../mcp/gateway.js";
6
+ import { compileWorkflowPlan } from "../planning/compile.js";
7
7
  import { opencodeAgentName } from "../runtime/opencode-agent-name.js";
8
+ import { mergeOpenCodeProjectConfig } from "../opencode-project-config.js";
8
9
  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";
9
- import { readFileSync } from "node:fs";
10
+ import { Effect } from "effect";
10
11
  import { basename } from "node:path";
11
12
  import matter from "gray-matter";
12
13
  //#region src/install-commands/opencode.ts
@@ -116,110 +117,48 @@ function orchestratorBlock(config) {
116
117
  instructionsPointer(profile)
117
118
  ].join("\n");
118
119
  }
119
- function dispatchBlock(host, config, workflowId = config.default_workflow) {
120
- const routes = agentDispatchRoutes(host, config, workflowId);
121
- if (routes.length === 0) return;
122
- const plan = compileWorkflowPlan(config, workflowId);
123
- const nativeRoutes = routes.filter((route) => route.kind !== "cli");
124
- const cliRoutes = routes.filter((route) => route.kind === "cli");
125
- return [
126
- `Run workflow \`${plan.workflowId}\` for the user task.`,
127
- "",
128
- nativeDispatchBlock(host, nativeRoutes),
129
- cliDispatchBlock(host, cliRoutes),
130
- nodePromptContract(plan.workflowId, routes),
131
- "Only package-configured gates are blocking. Do not invent RED, GREEN, full-suite, typecheck, or unrelated-drift gates.",
132
- "If a node returns targeted evidence and has no configured blocking gate, advance to the next node.",
133
- "Do not bypass configured runner subprocesses or package-configured gates when executing nodes.",
134
- "Use the listed Task tool routes for native nodes, and run nodes with satisfied dependencies in parallel whenever the host supports concurrent subagent work.",
135
- hostSpecificDispatchGuard(host, nativeRoutes, cliRoutes)
136
- ].filter((line) => Boolean(line)).join("\n");
137
- }
138
- function entrypointDispatchBlock(host, config, id, entrypoint) {
139
- if ("workflow" in entrypoint) return dispatchBlock(host, config, entrypoint.workflow);
120
+ function entrypointDispatchBlock(_host, _config, id, entrypoint) {
140
121
  return [
141
- `Generate a schedule for entrypoint \`${id}\` and the user task.`,
142
- `The schedule policy is \`${entrypoint.schedule}\`.`,
143
- 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.`,
144
- "The pipeline runtime executes as Argo DAG tasks using the package-owned runner image.",
145
- "Configure the target in `~/.config/moka/config.yaml`; use `--kubeconfig <path>` and `--namespace <namespace>` only for explicit command overrides.",
146
- "Use `moka submit --schedule <schedule.yaml> <task description>` only when rerunning an existing schedule artifact."
122
+ `Run \`${canonicalLocalRunCommand(id, entrypoint)}\` for local supervised execution.`,
123
+ `Configured entrypoint target: ${entrypointTargetId(entrypoint)}.`,
124
+ "This compatibility slash command delegates to the canonical `moka run` supervisor instead of reimplementing orchestration in the host.",
125
+ "The supervisor owns schedule generation, node execution, run state, and configured gates.",
126
+ "Keep reporting clear that this path is CLI/supervised runtime, not host-native Task execution."
147
127
  ].join("\n");
148
128
  }
149
- function scheduledEntrypointK8sNote(entrypoint) {
150
- if ("workflow" in entrypoint) return;
151
- return "Submit Momokaya work as Argo Workflows through `moka submit` and `moka submit --quick`.";
129
+ function entrypointTargetId(entrypoint) {
130
+ return "workflow" in entrypoint ? entrypoint.workflow : entrypoint.schedule;
131
+ }
132
+ function canonicalLocalRunCommand(id, entrypoint) {
133
+ return [
134
+ "moka",
135
+ "run",
136
+ ...canonicalLocalRunFlags(id, entrypoint),
137
+ "<task description>"
138
+ ].join(" ");
139
+ }
140
+ function canonicalLocalRunFlags(id, entrypoint) {
141
+ if (id === "quick") return ["--effort", "quick"];
142
+ if (id === "execute") return ["--effort", "thorough"];
143
+ if (id === "inspect") return ["--read-only"];
144
+ if ("workflow" in entrypoint) return ["--workflow", entrypoint.workflow];
145
+ return ["--entrypoint", id];
152
146
  }
153
147
  function localRosterAgentIds(config) {
154
148
  return nativeProfileEntries("opencode", config).map(([id]) => nativeAgentIdForHost("opencode", id));
155
149
  }
156
150
  function localOrchestratorDispatchBlock(config) {
157
151
  return [
158
- "Orchestrate locally. Load and follow the `orchestrate` skill.",
159
- "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.",
152
+ "Orchestrate through the canonical local `moka run` supervisor. Load and follow the `orchestrate` skill.",
153
+ "For compatibility slash commands, run the `moka run` command and flags shown in the command body.",
154
+ "Treat execution as CLI/supervised runtime, not OpenCode-native Task execution.",
160
155
  "",
161
- "Roster (Task tool subagent_type):",
156
+ "Configured roster:",
162
157
  ...localRosterAgentIds(config).map((id) => `- ${id}`),
163
158
  "",
164
- "Gather each subagent's structured output, enforce only package-configured gates, and report only the evidence the subagents returned."
165
- ].join("\n");
166
- }
167
- function nativeDispatchBlock(host, routes) {
168
- if (routes.length === 0) return;
169
- return [
170
- `${hostDisplayName(host)} native routes:`,
171
- ...routes.map(nativeDispatchLine),
172
- ""
159
+ "Report the supervisor's evidence and configured-gate results; do not invent extra gates."
173
160
  ].join("\n");
174
161
  }
175
- function nativeDispatchLine(route) {
176
- const needs = needsSummary(route.needs);
177
- const model = route.model ? ` model=${route.model}` : "";
178
- return `- ${route.nodeId}: Task tool subagent_type=${route.nativeAgentId}${model} runner=${route.runnerId} needs=${needs}`;
179
- }
180
- function cliDispatchBlock(host, routes) {
181
- if (routes.length === 0) return;
182
- return [
183
- `These nodes are not ${hostDisplayName(host)} native routes.`,
184
- "CLI routes:",
185
- ...routes.map(cliDispatchLine),
186
- ""
187
- ].join("\n");
188
- }
189
- function cliDispatchLine(route) {
190
- return `- ${route.nodeId}: ${route.runnerId} CLI profile=${route.profileId} command=\`${runnerCliCommand(route)}\` needs=${needsSummary(route.needs)}`;
191
- }
192
- function runnerCliCommand(route) {
193
- if (route.runnerId === "opencode") return `opencode run --agent "${opencodeAgentName(route.profileId)}" --format json --dir <repo-root> <node prompt>`;
194
- throw new Error(`runner '${route.runnerId}' cannot be represented as a supported native or CLI route`);
195
- }
196
- function nodePromptContract(workflowId, routes) {
197
- return [
198
- routes.some((route) => route.kind === "cli") ? "For each CLI node prompt include:" : "For each native node prompt include:",
199
- "- user task",
200
- `- workflow id: ${workflowId}`,
201
- "- node id",
202
- "- profile id",
203
- "- runner id",
204
- "- profile instructions reference",
205
- "- profile grants",
206
- "- dependency outputs",
207
- ""
208
- ].join("\n");
209
- }
210
- function hostSpecificDispatchGuard(host, nativeRoutes, cliRoutes) {
211
- if (cliRoutes.length > 0 && nativeRoutes.length > 0) return `Do not claim CLI routes are ${hostDisplayName(host)} native routes.`;
212
- if (cliRoutes.length > 0 && nativeRoutes.length === 0) return `Do not claim these nodes are ${hostDisplayName(host)} subagents.`;
213
- }
214
- function hostDisplayName(host) {
215
- return {
216
- opencode: "OpenCode",
217
- "claude-code": "Claude Code"
218
- }[host];
219
- }
220
- function needsSummary(needs) {
221
- return needs.length > 0 ? needs.join(",") : "none";
222
- }
223
162
  const OPENCODE_PERMISSION_TOOLS = [
224
163
  "bash",
225
164
  "edit",
@@ -293,10 +232,17 @@ function npmPluginPackage(item) {
293
232
  function formatOpenCodeProjectJson(value) {
294
233
  return `${JSON.stringify(value, null, 2).replace(SINGLE_OPENCODE_PLUGIN_ARRAY_RE, "\n \"plugin\": [$1]")}\n`;
295
234
  }
296
- function localPluginDefinitions() {
297
- return DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST.ecosystem_code.flatMap((item) => {
298
- if (item.plugin?.kind !== "local") return [];
299
- const plugin = readFileSync(resolvePackageAssetPath(item.plugin.source_path), "utf8").trimEnd();
235
+ function localPluginDefinitionsEffect() {
236
+ return Effect.gen(function* () {
237
+ return (yield* Effect.all(DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST.ecosystem_code.map(localPluginDefinitionEffect))).flat();
238
+ });
239
+ }
240
+ function localPluginDefinitionEffect(item) {
241
+ const pluginConfig = item.plugin;
242
+ if (pluginConfig?.kind !== "local") return Effect.succeed([]);
243
+ const source = resolvePackageAssetPath(pluginConfig.source_path);
244
+ return Effect.gen(function* () {
245
+ const plugin = (yield* (yield* RepoIoService).readText(source)).trimEnd();
300
246
  return [{
301
247
  content: [
302
248
  GENERATED_TS_MARKER,
@@ -307,80 +253,81 @@ function localPluginDefinitions() {
307
253
  ].join("\n"),
308
254
  host: "opencode",
309
255
  invocation: invocationForHost("opencode"),
310
- path: item.plugin.target_path
256
+ path: pluginConfig.target_path
311
257
  }];
312
258
  });
313
259
  }
314
- function opencodeDefinitions(config, cwd) {
315
- const orchestrator = orchestratorProfile(config);
316
- return [
317
- ...entrypointCommandDefinitions("opencode", config, (id, entrypoint) => ({
318
- content: markdown({
319
- ...orchestrator ? { agent: OPENCODE_ORCHESTRATOR_AGENT_ID } : {},
320
- description: entrypointDescription(id, entrypoint)
321
- }, compactLines([
322
- header("opencode").trimEnd(),
323
- "",
324
- `Invoke this command with \`${invocationForHost("opencode", id)}\`.`,
325
- "",
326
- orchestratorBlock(config),
327
- "",
328
- scheduledEntrypointK8sNote(entrypoint),
329
- scheduledEntrypointK8sNote(entrypoint) ? "" : void 0,
330
- entrypointDispatchBlock("opencode", config, id, entrypoint)
331
- ]).join("\n")),
332
- host: "opencode",
333
- invocation: invocationForHost("opencode", id),
334
- path: `.opencode/commands/${commandIdForHost("opencode", id)}.md`
335
- })),
336
- {
337
- content: renderOpenCodeProjectConfig(config),
338
- host: "opencode",
339
- invocation: invocationForHost("opencode"),
340
- path: ".opencode/opencode.json"
341
- },
342
- ...orchestrator ? [{
343
- content: markdown({
344
- description: "Orchestrate the configured pipeline and enforce gates.",
345
- mode: "primary",
346
- name: OPENCODE_ORCHESTRATOR_AGENT_ID,
347
- permission: opencodePermission(orchestrator, { allowedTaskAgents: localRosterAgentIds(config) })
348
- }, compactLines([
349
- header("opencode").trimEnd(),
350
- "",
351
- orchestratorBlock(config),
352
- "",
353
- localOrchestratorDispatchBlock(config)
354
- ]).join("\n")),
355
- host: "opencode",
356
- invocation: invocationForHost("opencode"),
357
- path: `.opencode/agents/${OPENCODE_ORCHESTRATOR_AGENT_ID}.md`
358
- }] : [],
359
- ...nativeProfileEntries("opencode", config).map(([id, profile]) => ({
360
- content: markdown({
361
- name: nativeAgentIdForHost("opencode", id),
362
- description: profile.description ?? id,
363
- hidden: false,
364
- mode: "all",
365
- ...opencodeModelProjection(config, profile),
366
- permission: opencodePermission(profile)
367
- }, [
368
- header("opencode").trimEnd(),
369
- "",
370
- profile.description ?? id,
371
- "",
372
- "Configured grants:",
373
- grants(profile),
374
- "",
375
- instructionsPointer(profile)
376
- ].join("\n")),
377
- host: "opencode",
378
- invocation: invocationForHost("opencode"),
379
- path: `.opencode/agents/${nativeAgentIdForHost("opencode", id)}.md`
380
- })),
381
- ...localPluginDefinitions(),
382
- projectAgentsMdDefinition(cwd, "opencode")
383
- ];
260
+ function opencodeDefinitionsEffect(config, cwd) {
261
+ return Effect.gen(function* () {
262
+ const orchestrator = orchestratorProfile(config);
263
+ const pluginDefinitions = yield* localPluginDefinitionsEffect();
264
+ return [
265
+ ...entrypointCommandDefinitions("opencode", config, (id, entrypoint) => ({
266
+ content: markdown({
267
+ ...orchestrator ? { agent: OPENCODE_ORCHESTRATOR_AGENT_ID } : {},
268
+ description: entrypointDescription(id, entrypoint)
269
+ }, compactLines([
270
+ header("opencode").trimEnd(),
271
+ "",
272
+ `Invoke this command with \`${invocationForHost("opencode", id)}\`.`,
273
+ "",
274
+ orchestratorBlock(config),
275
+ "",
276
+ entrypointDispatchBlock("opencode", config, id, entrypoint)
277
+ ]).join("\n")),
278
+ host: "opencode",
279
+ invocation: invocationForHost("opencode", id),
280
+ path: `.opencode/commands/${commandIdForHost("opencode", id)}.md`
281
+ })),
282
+ {
283
+ content: renderOpenCodeProjectConfig(config),
284
+ host: "opencode",
285
+ invocation: invocationForHost("opencode"),
286
+ path: ".opencode/opencode.json"
287
+ },
288
+ ...orchestrator ? [{
289
+ content: markdown({
290
+ description: "Orchestrate the configured pipeline and enforce gates.",
291
+ mode: "primary",
292
+ name: OPENCODE_ORCHESTRATOR_AGENT_ID,
293
+ permission: opencodePermission(orchestrator, { allowedTaskAgents: localRosterAgentIds(config) })
294
+ }, compactLines([
295
+ header("opencode").trimEnd(),
296
+ "",
297
+ orchestratorBlock(config),
298
+ "",
299
+ localOrchestratorDispatchBlock(config)
300
+ ]).join("\n")),
301
+ host: "opencode",
302
+ invocation: invocationForHost("opencode"),
303
+ path: `.opencode/agents/${OPENCODE_ORCHESTRATOR_AGENT_ID}.md`
304
+ }] : [],
305
+ ...nativeProfileEntries("opencode", config).map(([id, profile]) => ({
306
+ content: markdown({
307
+ name: nativeAgentIdForHost("opencode", id),
308
+ description: profile.description ?? id,
309
+ hidden: false,
310
+ mode: "all",
311
+ ...opencodeModelProjection(config, profile),
312
+ permission: opencodePermission(profile)
313
+ }, [
314
+ header("opencode").trimEnd(),
315
+ "",
316
+ profile.description ?? id,
317
+ "",
318
+ "Configured grants:",
319
+ grants(profile),
320
+ "",
321
+ instructionsPointer(profile)
322
+ ].join("\n")),
323
+ host: "opencode",
324
+ invocation: invocationForHost("opencode"),
325
+ path: `.opencode/agents/${nativeAgentIdForHost("opencode", id)}.md`
326
+ })),
327
+ ...pluginDefinitions,
328
+ projectAgentsMdDefinition(cwd, "opencode")
329
+ ];
330
+ });
384
331
  }
385
332
  function projectAgentsMdDefinition(cwd, host) {
386
333
  const repoName = basename(cwd);
@@ -398,7 +345,8 @@ function projectAgentsMdDefinition(cwd, host) {
398
345
  "",
399
346
  "This repository uses package-owned `@oisincoveney/pipeline` config.",
400
347
  "",
401
- "- Use `/moka-quick`, `/moka-execute`, or `/moka-inspect` for OpenCode slash-command entrypoints when available.",
348
+ "- Use `moka run \"<task>\"` first for local supervised execution from the package-owned pipeline config.",
349
+ "- Use `/moka-quick`, `/moka-execute`, or `/moka-inspect` as compatibility slash-command entrypoints when available.",
402
350
  "- Load and follow the relevant skill from `.agents/skills` before doing specialized work.",
403
351
  "- Prefer the package-defined pipeline profiles and generated command surfaces over ad hoc subagent prompts.",
404
352
  "- When the user needs to run a command, copy the command into the clipboard and tell the user what needs to be returned.",
@@ -436,7 +384,7 @@ const opencodeAdapter = {
436
384
  ".opencode/skills"
437
385
  ],
438
386
  definitions(config, cwd) {
439
- return opencodeDefinitions(config, cwd);
387
+ return runRepoIoSync(opencodeDefinitionsEffect(config, cwd));
440
388
  },
441
389
  mergeDefinition(definition, existingContent) {
442
390
  if (definition.path !== ".opencode/opencode.json") return;
@@ -455,4 +403,4 @@ const opencodeAdapter = {
455
403
  }
456
404
  };
457
405
  //#endregion
458
- export { agentDispatchRoutes, entrypointDispatchBlock, grants, header, markdown, opencodeAdapter, projectAgentsMdDefinition, resolvedHostModel, scheduledEntrypointK8sNote };
406
+ export { agentDispatchRoutes, entrypointDispatchBlock, grants, header, markdown, opencodeAdapter, projectAgentsMdDefinition, resolvedHostModel };
@@ -1,4 +1,5 @@
1
- import { existsSync } from "node:fs";
1
+ import { RepoIoService, runRepoIoSync } from "../runtime/services/repo-io-service.js";
2
+ import { Effect } from "effect";
2
3
  import { join } from "node:path";
3
4
  //#region src/mcp/repo-local-backends.ts
4
5
  const BACKEND_TEMPLATES = {
@@ -23,45 +24,56 @@ const BACKEND_TEMPLATES = {
23
24
  }
24
25
  };
25
26
  function resolveRepoLocalBackendSpecs(config, options) {
26
- const gateway = config.mcp_gateway;
27
- if (!gateway) return [];
28
- const exists = options.exists ?? existsSync;
29
- return Object.entries(gateway.backends).filter(([, backend]) => backend.locality === "repo-local").map(([id, backend]) => repoLocalBackendSpec(id, backend, {
30
- cwd: options.cwd,
31
- env: options.env ?? process.env,
32
- exists
33
- }));
27
+ return runRepoIoSync(resolveRepoLocalBackendSpecsEffect(config, options));
34
28
  }
35
- function repoLocalBackendSpec(id, backend, options) {
36
- const workspacePath = workspacePathForBackend(backend, options);
37
- const template = BACKEND_TEMPLATES[id] ?? {
38
- args: () => [],
39
- command: id,
40
- requiredPath: "."
41
- };
42
- const requiredPath = join(workspacePath, template.requiredPath);
43
- const ready = options.exists(requiredPath);
44
- const readiness = ready ? { ok: true } : {
45
- ok: false,
46
- reason: `Missing ${template.requiredPath} in ${workspacePath}`
47
- };
48
- const enabled = ready || backend.required;
49
- return {
50
- args: template.args(workspacePath),
51
- command: template.command,
52
- cwd: workspacePath,
53
- enabled,
54
- env: { PIPELINE_TARGET_PATH: workspacePath },
55
- id,
56
- mount: {
57
- containerPath: "/workspace",
58
- hostPath: workspacePath
59
- },
60
- readiness,
61
- required: backend.required,
62
- toolPrefixes: backend.tool_prefixes,
63
- workspacePath
64
- };
29
+ function resolveRepoLocalBackendSpecsEffect(config, options) {
30
+ return Effect.gen(function* () {
31
+ const gateway = config.mcp_gateway;
32
+ if (!gateway) return [];
33
+ return yield* Effect.all(Object.entries(gateway.backends).filter(([, backend]) => backend.locality === "repo-local").map(([id, backend]) => repoLocalBackendSpecEffect(id, backend, {
34
+ cwd: options.cwd,
35
+ env: options.env ?? process.env,
36
+ exists: options.exists
37
+ })));
38
+ });
39
+ }
40
+ function repoLocalBackendSpecEffect(id, backend, options) {
41
+ return Effect.gen(function* () {
42
+ const workspacePath = workspacePathForBackend(backend, options);
43
+ const template = BACKEND_TEMPLATES[id] ?? {
44
+ args: () => [],
45
+ command: id,
46
+ requiredPath: "."
47
+ };
48
+ const ready = yield* backendPathExists(join(workspacePath, template.requiredPath), options.exists);
49
+ const readiness = ready ? { ok: true } : {
50
+ ok: false,
51
+ reason: `Missing ${template.requiredPath} in ${workspacePath}`
52
+ };
53
+ const enabled = ready || backend.required;
54
+ return {
55
+ args: template.args(workspacePath),
56
+ command: template.command,
57
+ cwd: workspacePath,
58
+ enabled,
59
+ env: { PIPELINE_TARGET_PATH: workspacePath },
60
+ id,
61
+ mount: {
62
+ containerPath: "/workspace",
63
+ hostPath: workspacePath
64
+ },
65
+ readiness,
66
+ required: backend.required,
67
+ toolPrefixes: backend.tool_prefixes,
68
+ workspacePath
69
+ };
70
+ });
71
+ }
72
+ function backendPathExists(path, exists) {
73
+ if (exists) return Effect.sync(() => exists(path));
74
+ return Effect.gen(function* () {
75
+ return yield* (yield* RepoIoService).exists(path);
76
+ });
65
77
  }
66
78
  function workspacePathForBackend(backend, options) {
67
79
  return backend.workspace_path_source === "PIPELINE_TARGET_PATH" ? options.env.PIPELINE_TARGET_PATH || options.cwd : options.cwd;
@@ -1,14 +1,14 @@
1
+ import { buildRunnerCommandPayload, runnerDeliverySchema, runnerHookPolicySchema, runnerRepositoryContextSchema, runnerRunIdentitySchema, runnerTaskSchema } from "./runner-command-contract.js";
1
2
  import { normalizeRunnerRepositoryForSubmit } from "./git-remote-url.js";
2
3
  import { compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact } from "./planning/generate.js";
3
- import { buildRunnerCommandPayload, runnerDeliverySchema, runnerHookPolicySchema, runnerRepositoryContextSchema, runnerRunIdentitySchema, runnerTaskSchema } from "./runner-command-contract.js";
4
4
  import { workflowSubmitResultSchema } from "./workflow-submit-contract.js";
5
5
  import { buildCommandScheduleYaml, submitRunnerArgoWorkflow } from "./argo-submit.js";
6
6
  import { z } from "zod";
7
7
  import { readFileSync } from "node:fs";
8
- import { randomBytes } from "node:crypto";
9
- import parseGitUrl from "git-url-parse";
10
8
  import { resolve } from "node:path";
9
+ import { randomBytes } from "node:crypto";
11
10
  import { simpleGit } from "simple-git";
11
+ import parseGitUrl from "git-url-parse";
12
12
  //#region src/moka-submit.ts
13
13
  const imagePullPolicySchema = z.enum([
14
14
  "Always",
@@ -1,24 +1,24 @@
1
1
  import { loadPipelineConfig } from "./config/load.js";
2
2
  import "./config.js";
3
+ import { findPlannedNode } from "./planned-node.js";
3
4
  import { parseJsonObject } from "./runtime/json-validation/json-validation.js";
4
5
  import "./runtime/json-validation/index.js";
5
6
  import { emitNodeFinish, emitNodeOutputRecorded, emitNodeStart, emitWorkflowFinish, emitWorkflowPlanned, emitWorkflowStarted, runtimeNodeActorDescriptor } from "./runtime/events/events.js";
6
7
  import "./runtime/events/index.js";
7
- import { executeCommand } from "./runtime/command-executor/command-executor.js";
8
- import "./runtime/command-executor/index.js";
9
- import { dispatchHooks } from "./runtime/hooks/hooks.js";
10
- import "./runtime/hooks/index.js";
11
- import { findPlannedNode } from "./planned-node.js";
12
8
  import { executeAgentNode } from "./runtime/agent-node/agent-node.js";
13
9
  import "./runtime/agent-node/index.js";
14
10
  import { createRuntimeContext } from "./runtime/context/context.js";
15
11
  import "./runtime/context/index.js";
12
+ import { executeCommand } from "./runtime/command-executor/command-executor.js";
13
+ import "./runtime/command-executor/index.js";
16
14
  import { executeBuiltin } from "./runtime/builtins/builtins.js";
17
15
  import "./runtime/builtins/index.js";
18
16
  import { diffChangedFiles, snapshotChangedFiles } from "./runtime/changed-files/changed-files.js";
19
17
  import "./runtime/changed-files/index.js";
20
18
  import { evaluateNodeGates } from "./runtime/gates/gates.js";
21
19
  import "./runtime/gates/index.js";
20
+ import { dispatchHooks } from "./runtime/hooks/hooks.js";
21
+ import "./runtime/hooks/index.js";
22
22
  import { LocalScheduler } from "./runtime/local-scheduler.js";
23
23
  import { NodeStateTracker } from "./runtime/node-state-tracker.js";
24
24
  import { configUsesOpencode, leaseOpencodeRuntime } from "./runtime/opencode-runtime.js";
@@ -73,6 +73,7 @@ function runWithLeasedOpencode(options, config, worktreePath, run) {
73
73
  return Effect.scoped(Effect.gen(function* () {
74
74
  const lease = yield* Effect.acquireRelease(Effect.tryPromise(() => leaseOpencodeRuntime({
75
75
  config,
76
+ ...options.reporter ? { onSession: opencodeSessionReporter(options.reporter) } : {},
76
77
  ...options.signal ? { signal: options.signal } : {},
77
78
  worktreePath
78
79
  })), (lease) => Effect.promise(() => lease.release()));
@@ -83,6 +84,15 @@ function runWithLeasedOpencode(options, config, worktreePath, run) {
83
84
  });
84
85
  }));
85
86
  }
87
+ function opencodeSessionReporter(reporter) {
88
+ return (nodeId, sessionId) => {
89
+ reporter({
90
+ nodeId,
91
+ sessionId,
92
+ type: "node.session"
93
+ });
94
+ };
95
+ }
86
96
  function runJournalPath(context, dir) {
87
97
  return join(context.worktreePath ?? process.cwd(), dir, `${context.runId}.jsonl`);
88
98
  }
@@ -1,12 +1,12 @@
1
1
  import { workflowSchema } from "../config/schemas.js";
2
2
  import { validatePipelineConfig } from "../config/validate.js";
3
3
  import "../config.js";
4
+ import { dependentsByNeed, flattenNodes, hasReachableDependent } from "./graph.js";
4
5
  import { createRunnerLaunchPlan, runLaunchPlan } from "../runner.js";
5
6
  import { normalizeRunnerOutput } from "../runner-output.js";
7
+ import { compileWorkflowPlan } from "./compile.js";
6
8
  import { loadBacklogPlanningContext } from "../schedule/backlog-context.js";
7
9
  import { baselineScheduleArtifact } from "../schedule/baseline.js";
8
- import { expandBestOfNCandidates } from "../schedule/passes/candidates.js";
9
- import { dependentsByNeed, flattenNodes, hasReachableDependent } from "./graph.js";
10
10
  import { isCoverageNode, isImplementationNode } from "../schedule/scheduling-roles.js";
11
11
  import { addGeneratedImplementationCoverage } from "../schedule/passes/coverage.js";
12
12
  import { canonicalizeGeneratedScheduleIds } from "../schedule/passes/ids.js";
@@ -14,7 +14,6 @@ import { SCHEDULE_PASS_ORDER } from "../schedule/passes/index.js";
14
14
  import { applyNodeCatalogModelFallbacks } from "../schedule/passes/models.js";
15
15
  import { namespaceScheduleWorkflows } from "../schedule/passes/references.js";
16
16
  import { plannerPrompt, plannerRepairPrompt } from "../schedule/prompts.js";
17
- import { compileWorkflowPlan } from "./compile.js";
18
17
  import { parseDocument, stringify } from "yaml";
19
18
  import { z } from "zod";
20
19
  import { Data } from "effect";
@@ -31,12 +30,11 @@ const SCHEDULE_BUILTINS = [
31
30
  "duplication",
32
31
  "fallow",
33
32
  "lint",
34
- "select-candidate",
35
33
  "semgrep",
36
34
  "test",
37
35
  "typecheck"
38
36
  ];
39
- const PARALLEL_MERGE_BUILTINS = new Set(["drain-merge", "select-candidate"]);
37
+ const PARALLEL_MERGE_BUILTINS = new Set(["drain-merge"]);
40
38
  const scheduleArtifactSchema = z.object({
41
39
  generated_at: z.string().datetime(),
42
40
  kind: z.literal(SCHEDULE_KIND),
@@ -94,7 +92,7 @@ async function generateScheduleArtifact(options) {
94
92
  const planningContext = { ...loadBacklogPlanningContext(options.task, options.worktreePath) };
95
93
  const generatedArtifact = await planScheduleArtifact(baseline, policy.planner_profile, options, planningContext);
96
94
  assertSchedulePassOrder();
97
- const artifact = hydrateScheduleTaskContexts(canonicalizeGeneratedScheduleIds(applyNodeCatalogModelFallbacks(options.config, policy.node_catalog, expandBestOfNCandidates(options.config, addGeneratedImplementationCoverage(options.config, generatedArtifact)))), planningContext);
95
+ const artifact = hydrateScheduleTaskContexts(canonicalizeGeneratedScheduleIds(applyNodeCatalogModelFallbacks(options.config, policy.node_catalog, addGeneratedImplementationCoverage(options.config, generatedArtifact))), planningContext);
98
96
  validateScheduleArtifact(options.config, artifact, planningContext);
99
97
  compileScheduleArtifact(options.config, artifact, options.worktreePath);
100
98
  return {
@@ -105,7 +103,6 @@ async function generateScheduleArtifact(options) {
105
103
  function assertSchedulePassOrder() {
106
104
  if (SCHEDULE_PASS_ORDER.join("\0") !== [
107
105
  "coverage",
108
- "candidates",
109
106
  "models",
110
107
  "ids",
111
108
  "references"
@@ -345,7 +342,7 @@ function workUnitDependencyIssues(config, artifact, workUnits) {
345
342
  const nodes = flattenWorkflowNodes(workflow.nodes);
346
343
  const index = dependentsByNeedWithContainment(workflow.nodes, nodes);
347
344
  const nodesByWorkUnit = nodesByAssignedWorkUnit(nodes);
348
- return nodes.filter((node) => isImplementationNode(config, node) || isSelectCandidateNode(node)).flatMap((node) => {
345
+ return nodes.filter((node) => isImplementationNode(config, node)).flatMap((node) => {
349
346
  const dependentId = node.task_context?.id;
350
347
  if (!dependentId) return [];
351
348
  return (dependenciesByUnit.get(dependentId) ?? []).flatMap((prerequisiteId) => {
@@ -355,9 +352,6 @@ function workUnitDependencyIssues(config, artifact, workUnits) {
355
352
  });
356
353
  });
357
354
  }
358
- function isSelectCandidateNode(node) {
359
- return node.kind === "builtin" && node.builtin === "select-candidate";
360
- }
361
355
  function nodesByAssignedWorkUnit(nodes) {
362
356
  const grouped = /* @__PURE__ */ new Map();
363
357
  for (const node of nodes) {