@oisincoveney/pipeline 2.8.4 → 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 (61) hide show
  1. package/.agents/skills/orchestrate/SKILL.md +45 -32
  2. package/README.md +51 -41
  3. package/defaults/profiles.yaml +1 -1
  4. package/dist/argo-submit.js +1 -1
  5. package/dist/cli/doctor.d.ts +21 -0
  6. package/dist/cli/doctor.js +268 -0
  7. package/dist/cli/format.js +6 -3
  8. package/dist/cli/program.d.ts +14 -16
  9. package/dist/cli/program.js +291 -104
  10. package/dist/cli/run-resolver.js +58 -0
  11. package/dist/commands/bench-command.js +12 -4
  12. package/dist/commands/pipeline-command.js +22 -5
  13. package/dist/commands/runner-command-command.js +32 -9
  14. package/dist/config/lint.js +44 -26
  15. package/dist/context/repo-map.js +72 -56
  16. package/dist/gates.js +1 -1
  17. package/dist/index.d.ts +2 -1
  18. package/dist/index.js +20 -14
  19. package/dist/install-commands/claude-code.js +4 -33
  20. package/dist/install-commands/opencode.js +119 -171
  21. package/dist/mcp/repo-local-backends.js +51 -39
  22. package/dist/moka-submit.d.ts +6 -6
  23. package/dist/moka-submit.js +3 -3
  24. package/dist/pipeline-runtime.js +15 -5
  25. package/dist/planning/generate.js +2 -2
  26. package/dist/run-control/commands.js +340 -0
  27. package/dist/run-control/contracts.d.ts +21 -0
  28. package/dist/run-control/contracts.js +129 -0
  29. package/dist/run-control/detach.js +79 -0
  30. package/dist/run-control/runtime-reporter.js +187 -0
  31. package/dist/run-control/store.js +304 -0
  32. package/dist/run-control/supervisor.js +192 -0
  33. package/dist/runner-command/finalize.js +28 -37
  34. package/dist/runner-command/lifecycle-context.js +130 -63
  35. package/dist/runner-command/lifecycle.js +22 -31
  36. package/dist/runner-command/run.js +120 -72
  37. package/dist/runner-command/task-descriptor.js +11 -4
  38. package/dist/runner-event-schema.d.ts +6 -6
  39. package/dist/runner.js +1 -1
  40. package/dist/runtime/agent-node/agent-node.js +3 -3
  41. package/dist/runtime/builtins/builtins.js +1 -1
  42. package/dist/runtime/changed-files/changed-files.js +1 -1
  43. package/dist/runtime/context/context.js +1 -1
  44. package/dist/runtime/contracts/contracts.d.ts +4 -0
  45. package/dist/runtime/hooks/hooks.js +1 -1
  46. package/dist/runtime/json-validation/json-validation.js +49 -23
  47. package/dist/runtime/local-scheduler.js +49 -26
  48. package/dist/runtime/opencode-adapter.js +14 -10
  49. package/dist/runtime/opencode-runtime.js +22 -20
  50. package/dist/runtime/opencode-session-executor.js +1 -1
  51. package/dist/runtime/parallel-node/parallel-node.js +10 -0
  52. package/dist/runtime/run-journal.js +17 -10
  53. package/dist/runtime/services/file-system-service.js +29 -0
  54. package/dist/runtime/services/opencode-runtime-server-service.js +27 -0
  55. package/dist/runtime/services/repo-io-service.js +48 -0
  56. package/dist/runtime/services/run-journal-file-service.js +20 -0
  57. package/dist/runtime/services/runner-command-io-service.js +88 -0
  58. package/dist/runtime/workflow-lifecycle.js +76 -39
  59. package/dist/schedule/backlog-context.js +55 -29
  60. package/docs/operator-guide.md +73 -32
  61. package/package.json +1 -1
@@ -1,35 +1,33 @@
1
1
  import { runPipelineFromConfig } from "../pipeline-runtime.js";
2
+ import { RunEffort, RunMode, RunTarget } from "../run-control/contracts.js";
3
+ import { Effect } from "effect";
2
4
  import { Command } from "commander";
3
5
 
4
6
  //#region src/cli/program.d.ts
5
7
  interface ExecuteOptions {
6
8
  entrypoint?: string;
7
9
  pipelineRunner?: typeof runPipelineFromConfig;
10
+ runControl?: RunControlOptions;
11
+ runId?: string;
12
+ runStoreMode?: RunStoreMode;
8
13
  schedule?: string;
14
+ supervised?: boolean;
15
+ supervisor?: boolean;
9
16
  workflow?: string;
10
17
  }
18
+ type RunStoreMode = "create" | "reuse";
19
+ interface RunControlOptions {
20
+ effort?: RunEffort;
21
+ mode?: RunMode;
22
+ target?: RunTarget;
23
+ }
11
24
  /**
12
25
  * Config-driven `execute` entrypoint. Package-owned defaults are the source of
13
26
  * truth; repo-local pipeline files are ignored by runtime loading.
14
27
  */
15
28
  declare function execute(description: string, options?: ExecuteOptions): Promise<void>;
16
29
  declare function quick(description: string, options?: Omit<ExecuteOptions, "entrypoint">): Promise<void>;
17
- interface DoctorCheck {
18
- detail: string;
19
- name: string;
20
- passed: boolean;
21
- }
22
- interface DoctorResult {
23
- checks: DoctorCheck[];
24
- passed: boolean;
25
- }
26
- interface DoctorFlags {
27
- cluster?: boolean | string;
28
- kubeContext?: string;
29
- kubeconfig?: string;
30
- }
31
30
  declare function createCliProgram(): Command;
32
31
  declare function runCli(argv: string[]): Promise<void>;
33
- declare function runDoctor(cwd: string, options?: DoctorFlags): Promise<DoctorResult>;
34
32
  //#endregion
35
- export { createCliProgram, execute, quick, runCli, runDoctor };
33
+ export { createCliProgram, execute, quick, runCli };
@@ -1,28 +1,32 @@
1
- import { PipelineConfigError } from "../config/schemas.js";
2
1
  import { loadPipelineConfig } from "../config/load.js";
3
2
  import "../config.js";
3
+ import { configureGatewayHosts, localGatewayStatus, reconcileGateway, renderGatewayConfig, runGatewayDoctor, startLocalGateway } from "../mcp/gateway.js";
4
4
  import { createOrchestratorLaunchPlan, createRunnerLaunchPlan } from "../runner.js";
5
5
  import { compileWorkflowPlan } from "../planning/compile.js";
6
+ import { generateRuntimeRunId } from "../runtime/context/context.js";
7
+ import "../runtime/context/index.js";
8
+ import { runPipelineFromConfig } from "../pipeline-runtime.js";
6
9
  import { compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact } from "../planning/generate.js";
7
- import { loadMokaGlobalConfig } from "../moka-global-config.js";
8
- import { defaultClusterDoctorNamespace, runClusterDoctor } from "../cluster-doctor.js";
9
10
  import { formatCodexAuthSyncResult, syncLocalCodexAuth } from "../codex-auth-sync.js";
10
11
  import { registerBenchCommand } from "../commands/bench-command.js";
11
12
  import { registerConfiguredEntrypointCommands } from "../commands/pipeline-command.js";
12
- import { configureGatewayHosts, localGatewayStatus, reconcileGateway, renderGatewayConfig, runGatewayDoctor, startLocalGateway } from "../mcp/gateway.js";
13
- import { generateRuntimeRunId } from "../runtime/context/context.js";
14
- import "../runtime/context/index.js";
15
- import { runPipelineFromConfig } from "../pipeline-runtime.js";
16
13
  import { registerRunnerCommandCommand } from "../commands/runner-command-command.js";
17
14
  import { formatConfigLintWarning, lintPipelineConfig } from "../config/lint.js";
18
15
  import { formatInstallCommandsResult, installCommands, parseCommandHost } from "../install-commands.js";
19
16
  import { formatPipelineInitResult, initPipelineProject } from "../pipeline-init.js";
17
+ import { createRun, runControlStatusPaths, updateRunController } from "../run-control/store.js";
18
+ import { registerRunControlCommands } from "../run-control/commands.js";
19
+ import { startDetachedRunController } from "../run-control/detach.js";
20
+ import { createRunStoreRuntimeReporter } from "../run-control/runtime-reporter.js";
21
+ import { createRunControlSupervisor } from "../run-control/supervisor.js";
22
+ import { runDoctor } from "./doctor.js";
20
23
  import { createTerminalRuntimeReporter, formatDoctorResult, formatRuntimeFailure, formatRuntimeResult } from "./format.js";
24
+ import { MOKA_RUN_EFFORTS, MOKA_RUN_TARGETS, resolveMokaRun } from "./run-resolver.js";
21
25
  import { addMokaSubmitOptions, runMokaSubmitFromCli } from "./submit-options.js";
26
+ import { Effect } from "effect";
22
27
  import { readFileSync } from "node:fs";
23
28
  import { resolve } from "node:path";
24
- import { execa } from "execa";
25
- import { Command, Help, Option } from "commander";
29
+ import { Command, Help, Option as Option$1 } from "commander";
26
30
  //#region src/cli/program.ts
27
31
  /**
28
32
  * Config-driven `execute` entrypoint. Package-owned defaults are the source of
@@ -35,7 +39,12 @@ function execute(description, options = {}) {
35
39
  return runConfiguredPipeline({
36
40
  pipelineRunner: options.pipelineRunner,
37
41
  entrypoint: options.entrypoint,
42
+ runId: options.runId,
43
+ runStoreMode: options.runStoreMode,
44
+ runControl: options.runControl,
38
45
  schedule: options.schedule,
46
+ supervised: options.supervised,
47
+ supervisor: options.supervisor,
39
48
  task: description,
40
49
  workflow: options.workflow,
41
50
  worktreePath
@@ -47,7 +56,11 @@ function execute(description, options = {}) {
47
56
  function quick(description, options = {}) {
48
57
  return execute(description, {
49
58
  ...options,
50
- entrypoint: "quick"
59
+ entrypoint: "quick",
60
+ runControl: {
61
+ ...options.runControl,
62
+ effort: "quick"
63
+ }
51
64
  });
52
65
  }
53
66
  function withRunId(inputs) {
@@ -100,18 +113,117 @@ async function runConfiguredPipeline(rawInputs) {
100
113
  }
101
114
  async function runAndPrintPipeline(inputs) {
102
115
  const runner = inputs.pipelineRunner ?? runPipelineFromConfig;
103
- const reporter = createTerminalRuntimeReporter();
104
- const result = await runner({
105
- config: inputs.config,
116
+ const runStoreReporter = await createRunStoreReporter(inputs, createTerminalRuntimeReporter());
117
+ if (inputs.supervised) console.log(formatSupervisedRunFollowUp(requireRunId(inputs.runId)));
118
+ let result;
119
+ try {
120
+ result = await runWithFlushedReporter(runStoreReporter.flush, () => runner({
121
+ config: inputs.config,
122
+ reporter: runStoreReporter.reporter,
123
+ entrypoint: inputs.entrypoint,
124
+ runId: inputs.runId,
125
+ task: inputs.task,
126
+ workflowId: inputs.workflow,
127
+ worktreePath: inputs.worktreePath
128
+ }));
129
+ } catch (error) {
130
+ throw runtimeErrorWithFollowUp(error, inputs);
131
+ }
132
+ console.log(formatRuntimeResult(result));
133
+ if (result.outcome !== "PASS") throw new Error(formatRuntimeFailureWithFollowUp(result, inputs));
134
+ }
135
+ async function runWithFlushedReporter(flush, run) {
136
+ try {
137
+ return await run();
138
+ } finally {
139
+ await flush();
140
+ }
141
+ }
142
+ async function createLocalRunStoreRuntimeReporter(inputs, reporter) {
143
+ const runId = requireRunId(inputs.runId);
144
+ await createRun({
145
+ ...resolvedRunControlOptions(inputs.runControl),
146
+ nodeIds: plannedRunStoreNodeIds(inputs),
147
+ runId,
148
+ workspaceRoot: inputs.worktreePath
149
+ });
150
+ return createRunStoreRuntimeReporter({
106
151
  reporter,
107
- entrypoint: inputs.entrypoint,
108
- runId: inputs.runId,
109
- task: inputs.task,
110
- workflowId: inputs.workflow,
111
- worktreePath: inputs.worktreePath
152
+ runId,
153
+ workspaceRoot: inputs.worktreePath
112
154
  });
113
- console.log(formatRuntimeResult(result));
114
- if (result.outcome !== "PASS") throw new Error(formatRuntimeFailure(result));
155
+ }
156
+ function createRunStoreReporter(inputs, reporter) {
157
+ if (inputs.runStoreMode === "reuse") {
158
+ const runId = requireRunId(inputs.runId);
159
+ if (inputs.supervisor) {
160
+ const supervisor = createRunControlSupervisor({
161
+ reporter,
162
+ runId,
163
+ workspaceRoot: inputs.worktreePath
164
+ });
165
+ supervisor.start();
166
+ return {
167
+ flush: supervisor.stop,
168
+ reporter: supervisor.reporter
169
+ };
170
+ }
171
+ return createRunStoreRuntimeReporter({
172
+ reporter,
173
+ runId,
174
+ workspaceRoot: inputs.worktreePath
175
+ });
176
+ }
177
+ return createLocalRunStoreRuntimeReporter(inputs, reporter);
178
+ }
179
+ function requireRunId(runId) {
180
+ if (!runId) throw new Error("Run id is required for local run-control persistence.");
181
+ return runId;
182
+ }
183
+ function resolvedRunControlOptions(input) {
184
+ return {
185
+ effort: input?.effort ?? "normal",
186
+ mode: input?.mode ?? "write",
187
+ target: input?.target ?? "local"
188
+ };
189
+ }
190
+ function plannedRunStoreNodeIds(inputs) {
191
+ if (inputs.pipelineRunner) return [];
192
+ const workflowId = resolveWorkflowSelection(inputs.config, inputs.workflow, inputs.entrypoint);
193
+ return compileWorkflowPlan(inputs.config, workflowId).topologicalOrder.map((node) => node.id);
194
+ }
195
+ function formatSupervisedRunFollowUp(runId) {
196
+ return [
197
+ `Run id: ${runId}`,
198
+ `Status: moka status ${runId}`,
199
+ `Logs: moka logs ${runId}`
200
+ ].join("\n");
201
+ }
202
+ function formatDetachedRunFollowUp(runId) {
203
+ return [
204
+ `Run id: ${runId}`,
205
+ `Status: moka status ${runId}`,
206
+ `Logs: moka logs ${runId}`,
207
+ `Stop: moka stop ${runId}`
208
+ ].join("\n");
209
+ }
210
+ function formatRuntimeFailureWithFollowUp(result, inputs) {
211
+ const message = formatRuntimeFailure(result);
212
+ if (!(inputs.supervised && inputs.runId)) return message;
213
+ return [
214
+ message,
215
+ "",
216
+ formatSupervisedRunFollowUp(inputs.runId)
217
+ ].join("\n");
218
+ }
219
+ function runtimeErrorWithFollowUp(error, inputs) {
220
+ if (!(inputs.supervised && inputs.runId)) return error;
221
+ const message = error instanceof Error ? error.message : String(error);
222
+ return new Error([
223
+ message,
224
+ "",
225
+ formatSupervisedRunFollowUp(inputs.runId)
226
+ ].join("\n"));
115
227
  }
116
228
  function scheduledEntrypointId(config, workflowId, entrypointId) {
117
229
  if (workflowId) return null;
@@ -124,13 +236,39 @@ function createCliProgram() {
124
236
  const program = new Command();
125
237
  program.name("moka").description("Submit work to Momokaya").version(readPackageVersion()).exitOverride();
126
238
  const runAction = async (descriptionParts, flags) => {
239
+ const task = descriptionParts.join(" ");
240
+ const resolution = resolveMokaRun({
241
+ flags,
242
+ task
243
+ });
244
+ if (resolution.execution.kind === "remote-submit") {
245
+ printMokaSubmitResult(await runMokaSubmitFromCli(descriptionParts, remoteSubmitFlags(resolution.execution)));
246
+ return;
247
+ }
248
+ const runControl = {
249
+ effort: resolution.effort,
250
+ mode: resolution.mode === "read" ? "read-only" : "write",
251
+ target: resolution.target
252
+ };
253
+ if (flags.detach) {
254
+ await runDetachedResolvedTask(task, resolution.execution, runControl);
255
+ return;
256
+ }
257
+ await runLocalResolvedTask(task, resolution.execution, runControl);
258
+ };
259
+ program.command("run").description("Primary command: run a workflow from package-owned @oisincoveney/pipeline config").argument("<description...>", "task description").option("--command", "treat input after -- as explicit argv for remote submission").option("--entrypoint <entrypoint>", "entrypoint alias from package config").option("--detach", "start a supervised controller process in the background").addOption(new Option$1("--effort <effort>", "run effort").choices([...MOKA_RUN_EFFORTS]).default("normal")).option("--read-only", "run the read-only inspect workflow").option("--schedule <schedule>", "approved schedule YAML to execute").addOption(new Option$1("--target <target>", "execution target").choices([...MOKA_RUN_TARGETS]).default("local")).option("--workflow <workflow>", "workflow id from package config").action(runAction);
260
+ program.command("run-controller", { hidden: true }).description("Internal detached run controller").argument("<description...>", "task description").requiredOption("--run-id <run-id>", "existing run id to supervise").option("--entrypoint <entrypoint>", "entrypoint alias from package config").option("--schedule <schedule>", "approved schedule YAML to execute").option("--workflow <workflow>", "workflow id from package config").action(async (descriptionParts, flags) => {
127
261
  await execute(descriptionParts.join(" "), {
128
262
  entrypoint: flags.entrypoint,
263
+ runId: flags.runId,
264
+ runStoreMode: "reuse",
129
265
  schedule: flags.schedule,
266
+ supervised: true,
267
+ supervisor: true,
130
268
  workflow: flags.workflow
131
269
  });
132
- };
133
- program.command("run").description("Run a workflow from package-owned @oisincoveney/pipeline config").argument("<description...>", "task description").option("--entrypoint <entrypoint>", "entrypoint alias from package config").option("--schedule <schedule>", "approved schedule YAML to execute").option("--workflow <workflow>", "workflow id from package config").action(runAction);
270
+ });
271
+ registerRunControlCommands(program);
134
272
  program.command("validate").description("Validate package-owned @oisincoveney/pipeline config and compile the workflow plan").option("--entrypoint <entrypoint>", "entrypoint alias from package config").option("--schedule <schedule>", "approved schedule YAML to validate").option("--strict", "fail when validation lint warnings are emitted").option("--no-lint", "skip validation lint warnings").option("--workflow <workflow>", "workflow id from package config").action((flags) => {
135
273
  const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
136
274
  const config = loadPipelineConfig(cwd, { allowMissingLintFileReferences: true });
@@ -145,9 +283,9 @@ function createCliProgram() {
145
283
  const config = loadPipelineConfig(cwd, { allowMissingLintFileReferences: true });
146
284
  console.log(formatSelectedWorkflowPlan(config, cwd, flags));
147
285
  });
148
- program.command("doctor").description("Check local prerequisites for pipeline init and execution").option("--cluster [namespace]", "also check runner-job Kubernetes prerequisites").option("--kube-context <context>", "kubectl context for cluster checks").option("--kubeconfig <path>", "kubeconfig path for cluster checks").action(async (flags) => {
286
+ program.command("doctor").description("Check local prerequisites for pipeline init and execution").option("--cluster [namespace]", "also check runner-job Kubernetes prerequisites").option("--json", "print machine-readable readiness results").option("--kube-context <context>", "kubectl context for cluster checks").option("--kubeconfig <path>", "kubeconfig path for cluster checks").action(async (flags) => {
149
287
  const result = await runDoctor(process.env.PIPELINE_TARGET_PATH ?? process.cwd(), flags);
150
- console.log(formatDoctorResult(result));
288
+ console.log(flags.json ? JSON.stringify(result) : formatDoctorResult(result));
151
289
  if (!result.passed) throw new Error("Doctor checks failed.");
152
290
  });
153
291
  const gatewayCommand = program.command("mcp").description("Manage the hosted-first MCP gateway").command("gateway").description("Inspect and configure the pipeline MCP gateway");
@@ -161,7 +299,7 @@ function createCliProgram() {
161
299
  const config = loadPipelineConfig(process.env.PIPELINE_TARGET_PATH ?? process.cwd(), { allowMissingLintFileReferences: true });
162
300
  console.log(renderGatewayConfig(config));
163
301
  });
164
- 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) => {
302
+ gatewayCommand.command("configure-host").description("Rewrite host MCP config to the singleton pipeline gateway").addOption(new Option$1("--host <host>", "host config to update").choices(["all", "opencode"]).default("all").argParser(parseGatewayHost)).addOption(new Option$1("--scope <scope>", "config scope to update").choices(["project", "global"]).default("project").argParser(parseGatewayHostScope)).action((flags) => {
165
303
  const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
166
304
  const result = configureGatewayHosts(loadPipelineConfig(cwd, { allowMissingLintFileReferences: true }), {
167
305
  cwd,
@@ -189,14 +327,14 @@ function createCliProgram() {
189
327
  const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
190
328
  console.log(await localGatewayStatus(cwd));
191
329
  });
192
- program.command("init").description("Initialize package-owned pipeline support without repo-local config").addOption(new Option("--skill-scope <scope>", "where to install default skills: project (repo-local copy) or personal (one inherited user/global install)").choices(["project", "personal"]).default("project")).action(async (flags) => {
330
+ program.command("init").description("Initialize package-owned pipeline support without repo-local config").addOption(new Option$1("--skill-scope <scope>", "where to install default skills: project (repo-local copy) or personal (one inherited user/global install)").choices(["project", "personal"]).default("project")).action(async (flags) => {
193
331
  const result = await initPipelineProject({
194
332
  cwd: process.env.PIPELINE_TARGET_PATH ?? process.cwd(),
195
333
  scope: flags.skillScope
196
334
  });
197
335
  console.log(formatPipelineInitResult(result));
198
336
  });
199
- program.command("install-commands").description("Install generated slash-command adapters into this repository").addOption(new Option("--host <host>", "host command set to install").choices([
337
+ program.command("install-commands").description("Install generated slash-command adapters into this repository").addOption(new Option$1("--host <host>", "host command set to install").choices([
200
338
  "all",
201
339
  "opencode",
202
340
  "claude-code"
@@ -216,14 +354,17 @@ function createCliProgram() {
216
354
  console.log(formatCodexAuthSyncResult(result));
217
355
  if (!result.ok) process.exitCode = 1;
218
356
  });
219
- addMokaSubmitOptions(program.command("submit").description("Submit work to Momokaya as an Argo Workflow").argument("[input...]", "task description, or command argv with --command")).action(async (input, flags) => {
220
- const result = await runMokaSubmitFromCli(input, flags);
221
- console.log(`Workflow submitted: ${result.workflowName} in ${result.namespace}`);
222
- if (result.workflowUid) console.log(`Workflow UID: ${result.workflowUid}`);
357
+ addMokaSubmitOptions(program.command("submit").description([
358
+ "Submit work to Momokaya as an Argo Workflow.",
359
+ "Compatibility alias for `moka run \"<task>\" --target remote --effort thorough`.",
360
+ "Quick equivalent: `moka run \"<task>\" --target remote --effort quick`.",
361
+ "Command equivalent: `moka run --target remote --command -- <argv...>`."
362
+ ].join("\n")).argument("[input...]", "task description, or command argv with --command")).action(async (input, flags) => {
363
+ printMokaSubmitResult(await runMokaSubmitFromCli(input, flags));
223
364
  });
224
365
  registerRunnerCommandCommand(program);
225
366
  registerBenchCommand(program);
226
- const configuredEntrypointCommands = registerConfiguredEntrypointCommands(program, omitConfiguredEntrypoints(configuredPipeline, ["execute", "quick"]), async (entrypoint, task, _opts) => {
367
+ const configuredEntrypointCommands = registerConfiguredEntrypointCommands(program, compatibilityPresetDescriptions(configuredPipeline), async (entrypoint, task, _opts) => {
227
368
  await execute(task, { entrypoint });
228
369
  });
229
370
  if (configuredEntrypointCommands.size > 0) program.configureHelp({ subcommandTerm(command) {
@@ -232,6 +373,107 @@ function createCliProgram() {
232
373
  } });
233
374
  return program;
234
375
  }
376
+ function runLocalResolvedTask(task, execution, runControl) {
377
+ return execute(task, {
378
+ entrypoint: execution.entrypoint,
379
+ runControl,
380
+ schedule: execution.schedule,
381
+ supervised: true,
382
+ workflow: execution.workflow
383
+ });
384
+ }
385
+ async function runDetachedResolvedTask(task, execution, runControl) {
386
+ const worktreePath = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
387
+ const runId = generateRuntimeRunId();
388
+ const prepared = await prepareDetachedRun({
389
+ config: loadPipelineConfig(worktreePath, { allowMissingLintFileReferences: true }),
390
+ execution,
391
+ runId,
392
+ task,
393
+ worktreePath
394
+ });
395
+ await createRun({
396
+ ...resolvedRunControlOptions(runControl),
397
+ nodeIds: plannedRunStoreNodeIds({
398
+ config: prepared.config,
399
+ entrypoint: prepared.entrypoint,
400
+ runId,
401
+ runControl,
402
+ schedule: prepared.schedule,
403
+ task,
404
+ workflow: prepared.workflow,
405
+ worktreePath
406
+ }),
407
+ runId,
408
+ workspaceRoot: worktreePath
409
+ });
410
+ const launch = await startDetachedRunController({
411
+ entrypoint: prepared.entrypoint,
412
+ runId,
413
+ schedule: prepared.schedule,
414
+ task,
415
+ workflow: prepared.workflow,
416
+ workspaceRoot: worktreePath
417
+ });
418
+ await updateRunController({
419
+ controller: {
420
+ argv: launch.argv,
421
+ cwd: worktreePath,
422
+ paths: runControlStatusPaths({
423
+ runId,
424
+ workspaceRoot: worktreePath
425
+ }),
426
+ pid: launch.pid,
427
+ startedAt: launch.startedAt
428
+ },
429
+ runId,
430
+ workspaceRoot: worktreePath
431
+ });
432
+ console.log(formatDetachedRunFollowUp(runId));
433
+ }
434
+ async function prepareDetachedRun(input) {
435
+ if (input.execution.schedule) {
436
+ const schedule = resolve(input.execution.schedule);
437
+ const compiled = compileScheduleArtifact(input.config, parseScheduleArtifact(readFileSync(schedule, "utf8"), schedule), input.worktreePath);
438
+ return {
439
+ config: compiled.config,
440
+ schedule,
441
+ workflow: compiled.workflowId
442
+ };
443
+ }
444
+ const scheduledEntrypoint = scheduledEntrypointId(input.config, input.execution.workflow, input.execution.entrypoint);
445
+ if (!scheduledEntrypoint) return {
446
+ config: input.config,
447
+ entrypoint: input.execution.entrypoint,
448
+ workflow: input.execution.workflow
449
+ };
450
+ const result = await generateScheduleArtifact({
451
+ config: input.config,
452
+ entrypointId: scheduledEntrypoint,
453
+ runId: input.runId,
454
+ task: input.task,
455
+ worktreePath: input.worktreePath
456
+ });
457
+ console.log(`Schedule generated: ${result.path}`);
458
+ const schedule = resolve(input.worktreePath, result.path);
459
+ const compiled = compileScheduleArtifact(input.config, parseScheduleArtifact(readFileSync(schedule, "utf8"), result.path), input.worktreePath);
460
+ return {
461
+ config: compiled.config,
462
+ schedule,
463
+ workflow: compiled.workflowId
464
+ };
465
+ }
466
+ function remoteSubmitFlags(execution) {
467
+ return {
468
+ command: execution.command,
469
+ quick: execution.mode === "quick",
470
+ schedule: execution.schedule
471
+ };
472
+ }
473
+ function printMokaSubmitResult(result) {
474
+ console.log(`Workflow submitted: ${result.workflowName} in ${result.namespace}`);
475
+ if (result.workflowUid) console.log(`Workflow UID: ${result.workflowUid}`);
476
+ }
235
477
  function readPackageVersion() {
236
478
  const packageJson = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
237
479
  if (typeof packageJson.version !== "string") throw new Error("Unable to read @oisincoveney/pipeline package version.");
@@ -240,11 +482,18 @@ function readPackageVersion() {
240
482
  function loadConfiguredEntrypoints(cwd) {
241
483
  return loadPipelineConfig(cwd, { allowMissingLintFileReferences: true });
242
484
  }
243
- function omitConfiguredEntrypoints(config, ids) {
244
- const omitted = new Set(ids);
485
+ function compatibilityPresetDescriptions(config) {
486
+ const descriptions = {
487
+ execute: "Compatibility preset for `moka run --effort thorough`: full planner-generated pipeline for repository work",
488
+ inspect: "Compatibility preset for `moka run --read-only`: read-only repository inspection",
489
+ quick: "Compatibility preset for `moka run --effort quick`: compact planner-generated pipeline for small work"
490
+ };
245
491
  return {
246
492
  ...config,
247
- entrypoints: Object.fromEntries(Object.entries(config.entrypoints).filter(([id]) => !omitted.has(id)))
493
+ entrypoints: Object.fromEntries(Object.entries(config.entrypoints).map(([id, entrypoint]) => [id, descriptions[id] ? {
494
+ ...entrypoint,
495
+ description: descriptions[id]
496
+ } : entrypoint]))
248
497
  };
249
498
  }
250
499
  function parseGatewayHostScope(value) {
@@ -255,77 +504,15 @@ function parseGatewayHost(value) {
255
504
  if (value === "all" || value === "opencode") return value;
256
505
  throw new Error("host must be all or opencode");
257
506
  }
507
+ function runCliEffect(argv) {
508
+ return Effect.tryPromise({
509
+ catch: (error) => error,
510
+ try: () => createCliProgram().parseAsync(argv, { from: "node" })
511
+ }).pipe(Effect.asVoid);
512
+ }
258
513
  async function runCli(argv) {
259
514
  await createCliProgram().parseAsync(argv, { from: "node" });
260
515
  }
261
- async function runDoctor(cwd, options = {}) {
262
- const commandChecks = await Promise.all([
263
- checkCommand("npx", ["--version"], cwd),
264
- checkCommand("opencode", ["--version"], cwd),
265
- checkCommand("fallow", ["--version"], cwd)
266
- ]);
267
- const configCheck = checkPipelineConfig(cwd);
268
- const globalConfig = loadMokaGlobalConfig();
269
- const clusterResult = options.cluster ? await runClusterDoctor({
270
- kubeContext: options.kubeContext,
271
- kubeconfigPath: options.kubeconfig ?? globalConfig?.momokaya.kubernetes.kubeconfig,
272
- namespace: clusterNamespace(options.cluster, globalConfig?.momokaya.kubernetes.namespace)
273
- }) : { checks: [] };
274
- const checks = [
275
- ...commandChecks,
276
- configCheck,
277
- ...clusterResult.checks
278
- ];
279
- return {
280
- checks,
281
- passed: checks.every((check) => check.passed)
282
- };
283
- }
284
- function clusterNamespace(value, configuredNamespace) {
285
- return typeof value === "string" && value.length > 0 ? value : configuredNamespace ?? defaultClusterDoctorNamespace();
286
- }
287
- function checkCommand(name, args, cwd) {
288
- return checkCommandWithRunner(name, name, args, cwd);
289
- }
290
- async function checkCommandWithRunner(name, command, args, cwd) {
291
- try {
292
- await execa(command, args, {
293
- cwd,
294
- stdin: "ignore"
295
- });
296
- return {
297
- detail: "available",
298
- name,
299
- passed: true
300
- };
301
- } catch (err) {
302
- const error = err;
303
- return {
304
- detail: (error.shortMessage || error.stderr || "not available").trim(),
305
- name,
306
- passed: false
307
- };
308
- }
309
- }
310
- function checkPipelineConfig(cwd) {
311
- try {
312
- loadPipelineConfig(cwd);
313
- return {
314
- detail: "valid",
315
- name: "pipeline-config",
316
- passed: true
317
- };
318
- } catch (err) {
319
- let message = "invalid";
320
- if (err instanceof PipelineConfigError) message = err.issues.map((issue) => issue.message).join("; ");
321
- else if (err instanceof Error) message = err.message;
322
- return {
323
- detail: message || "missing or invalid",
324
- name: "pipeline-config",
325
- passed: false
326
- };
327
- }
328
- }
329
516
  function formatWorkflowPlan(config, worktreePath, workflowId) {
330
517
  return formatCompiledWorkflowPlan(config, worktreePath, compileWorkflowPlan(config, workflowId));
331
518
  }
@@ -392,4 +579,4 @@ function formatList(label, items) {
392
579
  return items?.length ? `${label}=${items.join(",")}` : "";
393
580
  }
394
581
  //#endregion
395
- export { createCliProgram, execute, quick, runCli, runDoctor };
582
+ export { createCliProgram, execute, quick, runCli, runCliEffect };
@@ -0,0 +1,58 @@
1
+ //#region src/cli/run-resolver.ts
2
+ const MOKA_RUN_EFFORTS = [
3
+ "normal",
4
+ "quick",
5
+ "thorough"
6
+ ];
7
+ const MOKA_RUN_TARGETS = ["local", "remote"];
8
+ function resolveMokaRun(input) {
9
+ const flags = input.flags ?? {};
10
+ const effort = flags.effort ?? "normal";
11
+ const target = flags.target ?? "local";
12
+ const mode = flags.readOnly ? "read" : "write";
13
+ if (flags.command && target !== "remote") throw new Error("--command requires --target remote");
14
+ if (flags.detach && target !== "local") throw new Error("--detach requires --target local");
15
+ return {
16
+ effort,
17
+ execution: target === "remote" ? resolveRemoteSubmit(flags, effort) : resolveLocalRuntime(flags, effort),
18
+ mode,
19
+ target
20
+ };
21
+ }
22
+ function resolveRemoteSubmit(flags, effort) {
23
+ return {
24
+ command: flags.command,
25
+ kind: "remote-submit",
26
+ mode: effort === "quick" ? "quick" : "full",
27
+ schedule: flags.schedule
28
+ };
29
+ }
30
+ function resolveLocalRuntime(flags, effort) {
31
+ if (flags.schedule) return {
32
+ kind: "local-runtime",
33
+ schedule: flags.schedule
34
+ };
35
+ if (flags.workflow) return {
36
+ kind: "local-runtime",
37
+ workflow: flags.workflow
38
+ };
39
+ if (flags.readOnly) return {
40
+ kind: "local-runtime",
41
+ workflow: "inspect"
42
+ };
43
+ if (flags.entrypoint) return {
44
+ entrypoint: flags.entrypoint,
45
+ kind: "local-runtime"
46
+ };
47
+ if (effort === "quick") return {
48
+ entrypoint: "quick",
49
+ kind: "local-runtime"
50
+ };
51
+ if (effort === "thorough") return {
52
+ entrypoint: "execute",
53
+ kind: "local-runtime"
54
+ };
55
+ return { kind: "local-runtime" };
56
+ }
57
+ //#endregion
58
+ export { MOKA_RUN_EFFORTS, MOKA_RUN_TARGETS, resolveMokaRun };
@@ -1,6 +1,17 @@
1
1
  import { buildEvalReport, renderEvalReport } from "../bench/eval-report.js";
2
+ import { Context, Effect, Layer } from "effect";
2
3
  import { readFileSync } from "node:fs";
3
4
  //#region src/commands/bench-command.ts
5
+ var BenchCommandService = class extends Context.Tag("BenchCommandService")() {};
6
+ const BenchCommandServiceLive = Layer.succeed(BenchCommandService, {
7
+ readResults: (path) => Effect.try(() => JSON.parse(readFileSync(path, "utf8"))),
8
+ writeReport: (report) => Effect.try(() => process.stdout.write(`${report}\n`))
9
+ });
10
+ const runBenchCommand = (options) => Effect.gen(function* () {
11
+ const service = yield* BenchCommandService;
12
+ const report = renderEvalReport(buildEvalReport(yield* service.readResults(options.results)));
13
+ yield* service.writeReport(report);
14
+ });
4
15
  /**
5
16
  * PIPE-83.6: `moka bench` — score a flat single-agent baseline vs the pipeline
6
17
  * (and ablations) over a recorded run set. Runs are produced by executing the
@@ -9,10 +20,7 @@ import { readFileSync } from "node:fs";
9
20
  * comparison report.
10
21
  */
11
22
  function registerBenchCommand(program) {
12
- program.command("bench").description("Score a flat single-agent baseline vs the pipeline over recorded bench runs").requiredOption("--results <path>", "JSON file: array of { task, variant, resolved, costTokens, wallMs }").action((options) => {
13
- const records = JSON.parse(readFileSync(options.results, "utf8"));
14
- process.stdout.write(`${renderEvalReport(buildEvalReport(records))}\n`);
15
- });
23
+ program.command("bench").description("Score a flat single-agent baseline vs the pipeline over recorded bench runs").requiredOption("--results <path>", "JSON file: array of { task, variant, resolved, costTokens, wallMs }").action((options) => Effect.runPromise(Effect.provide(runBenchCommand(options), BenchCommandServiceLive)));
16
24
  }
17
25
  //#endregion
18
26
  export { registerBenchCommand };