@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,11 +1,12 @@
1
1
  import { compileArgoExecutionGraph } from "../argo-graph.js";
2
- import { RunnerCommandPayloadValidationError } from "../runner-command-contract.js";
3
- import { promoteFinalRef } from "../run-state/git-refs.js";
4
2
  import { dispatchHooks } from "../runtime/hooks/hooks.js";
5
3
  import "../runtime/hooks/index.js";
6
4
  import { finalizeWorkflowLifecycle } from "../runtime/workflow-lifecycle.js";
7
- import { createRunnerLifecycleContext } from "./lifecycle-context.js";
5
+ import { RunnerCommandPayloadValidationError } from "../runner-command-contract.js";
6
+ import { RunnerCommandIoService, flushAndReport, isOutputStream, runValidatedRunnerCommand } from "../runtime/services/runner-command-io-service.js";
7
+ import { createRunnerLifecycleContextEffect } from "./lifecycle-context.js";
8
8
  import { z } from "zod";
9
+ import { Effect } from "effect";
9
10
  //#region src/runner-command/finalize.ts
10
11
  const runnerFinalizeOptionsSchema = z.object({
11
12
  argoStatus: z.string().min(1),
@@ -20,26 +21,26 @@ const EXIT_PASS = 0;
20
21
  const EXIT_FAIL = 1;
21
22
  const EXIT_VALIDATION = 64;
22
23
  const EXIT_STARTUP = 70;
23
- async function runRunnerFinalize(rawOptions = {}) {
24
- const parsedOptions = runnerFinalizeOptionsSchema.safeParse(rawOptions);
25
- const stderr = rawOptions.stderr ?? process.stderr;
26
- if (!parsedOptions.success) {
27
- stderr.write(`${parsedOptions.error.message}\n`);
28
- return EXIT_VALIDATION;
29
- }
30
- const options = parsedOptions.data;
31
- try {
32
- const { compiled, context, payload, sink, worktreePath } = await createRunnerLifecycleContext(options);
33
- const lifecycle = await finalizeWorkflowLifecycle({
34
- buildResult: (outcome, nodes, failure) => runnerFinalizeRuntimeResult(context, outcome, nodes, failure),
35
- runWorkflowHook: (event, failure) => dispatchHooks(context, event, failure)
36
- }, {
37
- completed: [],
38
- outcome: options.argoStatus === "Succeeded" ? "PASS" : "FAIL"
24
+ function runRunnerFinalize(rawOptions = {}) {
25
+ return runValidatedRunnerCommand(runnerFinalizeOptionsSchema, rawOptions, runRunnerFinalizeEffect);
26
+ }
27
+ function runRunnerFinalizeEffect(options, stderr) {
28
+ return Effect.gen(function* () {
29
+ const io = yield* RunnerCommandIoService;
30
+ const { compiled, context, payload, sink, worktreePath } = yield* createRunnerLifecycleContextEffect(options);
31
+ const lifecycle = yield* Effect.tryPromise({
32
+ try: () => finalizeWorkflowLifecycle({
33
+ buildResult: (outcome, nodes, failure) => runnerFinalizeRuntimeResult(context, outcome, nodes, failure),
34
+ runWorkflowHook: (event, failure) => dispatchHooks(context, event, failure)
35
+ }, {
36
+ completed: [],
37
+ outcome: options.argoStatus === "Succeeded" ? "PASS" : "FAIL"
38
+ }),
39
+ catch: (error) => error
39
40
  });
40
41
  if (lifecycle.result.outcome === "PASS") {
41
42
  const graph = compileArgoExecutionGraph(compiled.plan);
42
- await promoteFinalRef({
43
+ yield* io.promoteFinalRef({
43
44
  committer: compiled.config.runner_command.git.committer,
44
45
  payload,
45
46
  sourceNodeIds: graph.terminalNodeIds,
@@ -47,13 +48,14 @@ async function runRunnerFinalize(rawOptions = {}) {
47
48
  });
48
49
  }
49
50
  sink.recordFinalResult(lifecycle.result.outcome, payload.workflow.id);
50
- await flushAndReport(sink, stderr);
51
+ yield* flushAndReport(sink, stderr);
51
52
  return lifecycle.result.outcome === "PASS" ? EXIT_PASS : EXIT_FAIL;
52
- } catch (error) {
53
- const message = error instanceof Error ? error.message : String(error);
54
- stderr.write(`${message}\n`);
55
- return error instanceof RunnerCommandPayloadValidationError || error instanceof z.ZodError ? EXIT_VALIDATION : EXIT_STARTUP;
56
- }
53
+ }).pipe(Effect.catchAll((error) => Effect.sync(() => finalizeErrorExitCode(error, stderr))));
54
+ }
55
+ function finalizeErrorExitCode(error, stderr) {
56
+ const message = error instanceof Error ? error.message : String(error);
57
+ stderr.write(`${message}\n`);
58
+ return error instanceof RunnerCommandPayloadValidationError || error instanceof z.ZodError ? EXIT_VALIDATION : EXIT_STARTUP;
57
59
  }
58
60
  function runnerFinalizeRuntimeResult(context, outcome, nodes, failure) {
59
61
  return {
@@ -68,16 +70,5 @@ function runnerFinalizeRuntimeResult(context, outcome, nodes, failure) {
68
70
  structuredOutputs: context.nodeStateStore.structuredOutputList()
69
71
  };
70
72
  }
71
- function isOutputStream(value) {
72
- return typeof value === "object" && value !== null && "write" in value && typeof value.write === "function";
73
- }
74
- async function flushAndReport(sink, stderr) {
75
- try {
76
- await sink.flush();
77
- } catch (error) {
78
- const message = error instanceof Error ? error.message : String(error);
79
- stderr.write(`runner event flush failed: ${message}\n`);
80
- }
81
- }
82
73
  //#endregion
83
74
  export { runRunnerFinalize };
@@ -1,75 +1,142 @@
1
1
  import { PIPELINE_CONFIG_PATH, PROFILES_CONFIG_PATH, RUNNERS_CONFIG_PATH } from "../config/defaults.js";
2
2
  import { loadPipelineConfig, parsePipelineConfigParts } from "../config/load.js";
3
3
  import "../config.js";
4
- import { compileScheduleArtifact, parseScheduleArtifact } from "../planning/generate.js";
5
- import { parseRunnerCommandPayload, resolveRunnerEventSinkAuthToken } from "../runner-command-contract.js";
6
- import { prepareRunnerGitWorkspace } from "../run-state/git-refs.js";
7
- import { createRunnerEventSink } from "../runner-event-sink.js";
8
4
  import { initialNodeStateStore } from "../runtime/node-state-store.js";
9
- import { runnerTaskText } from "./run.js";
10
- import { existsSync, readFileSync } from "node:fs";
5
+ import { parseRunnerCommandPayload } from "../runner-command-contract.js";
6
+ import { RunnerCommandIoService, createRunnerCommandEventSink } from "../runtime/services/runner-command-io-service.js";
7
+ import { compileScheduleArtifact, parseScheduleArtifact } from "../planning/generate.js";
8
+ import { runnerTaskTextEffect } from "./run.js";
9
+ import { Effect } from "effect";
11
10
  import { join } from "node:path";
12
11
  //#region src/runner-command/lifecycle-context.ts
13
- async function createRunnerLifecycleContext(options) {
14
- const payload = parseRunnerCommandPayload(readFileSync(options.payloadFile, "utf8"));
15
- const authToken = resolveRunnerEventSinkAuthToken({ authTokenFile: payload.events.authTokenFile });
16
- const sink = createRunnerEventSink({
17
- authHeader: payload.events.authHeader,
18
- authToken,
19
- fetch: options.fetch,
20
- runId: payload.run.id,
21
- url: payload.events.url
22
- });
23
- const worktreePath = await prepareRunnerGitWorkspace(payload, { cwd: options.cwd });
24
- const compiled = compileScheduleArtifact(loadRunnerLifecycleConfig(worktreePath), parseScheduleArtifact(readFileSync(options.scheduleFile, "utf8"), options.scheduleFile), worktreePath);
25
- if (payload.workflow.id !== compiled.workflowId) throw new Error(`Runner payload workflow '${payload.workflow.id}' does not match schedule workflow '${compiled.workflowId}'`);
26
- return {
27
- compiled,
28
- context: {
29
- agentInvocations: [],
30
- config: compiled.config,
31
- executor: () => ({
32
- exitCode: 0,
33
- stdout: ""
12
+ function createRunnerLifecycleContextEffect(options) {
13
+ return Effect.gen(function* () {
14
+ const payload = yield* readRunnerPayloadEffect(options.payloadFile);
15
+ const sink = createRunnerCommandEventSink({
16
+ fetch: options.fetch,
17
+ payload
18
+ });
19
+ const worktreePath = yield* prepareRunnerWorktreeEffect(payload, options.cwd);
20
+ const compiled = yield* compileRunnerScheduleEffect(yield* loadRunnerLifecycleConfigEffect(worktreePath), options.scheduleFile, worktreePath);
21
+ yield* assertWorkflowIdsMatch(payload.workflow.id, compiled.workflowId);
22
+ return {
23
+ compiled,
24
+ context: buildRunnerRuntimeContext({
25
+ compiled,
26
+ payload,
27
+ sink,
28
+ task: yield* runnerTaskTextEffect(payload.task, worktreePath),
29
+ worktreePath
34
30
  }),
35
- gates: [],
36
- hookFailures: [],
37
- hookPolicy: {
38
- allowCommandHooks: payload.hookPolicy?.allowCommandHooks ?? true,
39
- allowUntrustedCommandHooks: payload.hookPolicy?.allowUntrustedCommandHooks ?? true,
40
- env: payload.hookPolicy?.env ?? {},
41
- envPassthrough: payload.hookPolicy?.envPassthrough ?? ["PATH"],
42
- outputLimitBytes: payload.hookPolicy?.outputLimitBytes ?? 64 * 1024,
43
- timeoutMs: payload.hookPolicy?.timeoutMs ?? 3e4
44
- },
45
- hookResults: /* @__PURE__ */ new Map(),
46
- nodeStateStore: initialNodeStateStore(compiled.plan),
47
- plan: compiled.plan,
48
- reporter: (event) => sink.recordRuntimeEvent(event),
49
- runId: payload.run.id,
50
- task: runnerTaskText(payload.task, worktreePath),
51
- workflowId: compiled.workflowId,
31
+ payload,
32
+ sink,
52
33
  worktreePath
53
- },
54
- payload,
55
- sink,
56
- worktreePath
34
+ };
35
+ });
36
+ }
37
+ function readRunnerPayloadEffect(payloadFile) {
38
+ return Effect.gen(function* () {
39
+ const payloadRaw = yield* (yield* RunnerCommandIoService).readText(payloadFile);
40
+ return yield* attemptSync(() => parseRunnerCommandPayload(payloadRaw));
41
+ });
42
+ }
43
+ function prepareRunnerWorktreeEffect(payload, cwd) {
44
+ return Effect.gen(function* () {
45
+ return yield* (yield* RunnerCommandIoService).prepareRunnerGitWorkspace(payload, { cwd });
46
+ });
47
+ }
48
+ function compileRunnerScheduleEffect(config, scheduleFile, worktreePath) {
49
+ return Effect.gen(function* () {
50
+ const scheduleRaw = yield* (yield* RunnerCommandIoService).readText(scheduleFile);
51
+ return yield* attemptSync(() => compileScheduleArtifact(config, parseScheduleArtifact(scheduleRaw, scheduleFile), worktreePath));
52
+ });
53
+ }
54
+ function assertWorkflowIdsMatch(payloadWorkflowId, scheduleWorkflowId) {
55
+ if (payloadWorkflowId === scheduleWorkflowId) return Effect.void;
56
+ return Effect.fail(/* @__PURE__ */ new Error(`Runner payload workflow '${payloadWorkflowId}' does not match schedule workflow '${scheduleWorkflowId}'`));
57
+ }
58
+ function buildRunnerRuntimeContext(options) {
59
+ return {
60
+ agentInvocations: [],
61
+ config: options.compiled.config,
62
+ executor: () => ({
63
+ exitCode: 0,
64
+ stdout: ""
65
+ }),
66
+ gates: [],
67
+ hookFailures: [],
68
+ hookPolicy: runnerHookPolicy(options.payload),
69
+ hookResults: /* @__PURE__ */ new Map(),
70
+ nodeStateStore: initialNodeStateStore(options.compiled.plan),
71
+ plan: options.compiled.plan,
72
+ reporter: (event) => options.sink.recordRuntimeEvent(event),
73
+ runId: options.payload.run.id,
74
+ task: options.task,
75
+ workflowId: options.compiled.workflowId,
76
+ worktreePath: options.worktreePath
77
+ };
78
+ }
79
+ function runnerHookPolicy(payload) {
80
+ const { allowCommandHooks = true, allowUntrustedCommandHooks = true, env = {}, envPassthrough = ["PATH"], outputLimitBytes = 64 * 1024, timeoutMs = 3e4 } = payload.hookPolicy ?? {};
81
+ return {
82
+ allowCommandHooks,
83
+ allowUntrustedCommandHooks,
84
+ env,
85
+ envPassthrough,
86
+ outputLimitBytes,
87
+ timeoutMs
57
88
  };
58
89
  }
59
- function loadRunnerLifecycleConfig(worktreePath) {
60
- const pipelinePath = join(worktreePath, PIPELINE_CONFIG_PATH);
61
- const profilesPath = join(worktreePath, PROFILES_CONFIG_PATH);
62
- const runnersPath = join(worktreePath, RUNNERS_CONFIG_PATH);
63
- if (existsSync(pipelinePath) && existsSync(profilesPath) && existsSync(runnersPath)) return parsePipelineConfigParts({
64
- pipeline: readFileSync(pipelinePath, "utf8"),
65
- profiles: readFileSync(profilesPath, "utf8"),
66
- runners: readFileSync(runnersPath, "utf8")
67
- }, worktreePath, {
68
- pipeline: PIPELINE_CONFIG_PATH,
69
- profiles: PROFILES_CONFIG_PATH,
70
- runners: RUNNERS_CONFIG_PATH
71
- }, { allowMissingLintFileReferences: true });
72
- return loadPipelineConfig(worktreePath, { allowMissingLintFileReferences: true });
90
+ function loadRunnerLifecycleConfigEffect(worktreePath) {
91
+ return Effect.gen(function* () {
92
+ const pipelinePath = join(worktreePath, PIPELINE_CONFIG_PATH);
93
+ const profilesPath = join(worktreePath, PROFILES_CONFIG_PATH);
94
+ const runnersPath = join(worktreePath, RUNNERS_CONFIG_PATH);
95
+ if (yield* hasRunnerLifecycleConfigParts([
96
+ pipelinePath,
97
+ profilesPath,
98
+ runnersPath
99
+ ])) {
100
+ const parts = yield* readRunnerLifecycleConfigParts({
101
+ pipelinePath,
102
+ profilesPath,
103
+ runnersPath
104
+ });
105
+ return yield* attemptSync(() => parsePipelineConfigParts({
106
+ pipeline: parts.pipeline,
107
+ profiles: parts.profiles,
108
+ runners: parts.runners
109
+ }, worktreePath, {
110
+ pipeline: PIPELINE_CONFIG_PATH,
111
+ profiles: PROFILES_CONFIG_PATH,
112
+ runners: RUNNERS_CONFIG_PATH
113
+ }, { allowMissingLintFileReferences: true }));
114
+ }
115
+ return yield* attemptSync(() => loadPipelineConfig(worktreePath, { allowMissingLintFileReferences: true }));
116
+ });
117
+ }
118
+ function hasRunnerLifecycleConfigParts(paths) {
119
+ return Effect.gen(function* () {
120
+ const io = yield* RunnerCommandIoService;
121
+ for (const path of paths) if (!(yield* io.exists(path))) return false;
122
+ return true;
123
+ });
124
+ }
125
+ function readRunnerLifecycleConfigParts(paths) {
126
+ return Effect.gen(function* () {
127
+ const io = yield* RunnerCommandIoService;
128
+ return {
129
+ pipeline: yield* io.readText(paths.pipelinePath),
130
+ profiles: yield* io.readText(paths.profilesPath),
131
+ runners: yield* io.readText(paths.runnersPath)
132
+ };
133
+ });
134
+ }
135
+ function attemptSync(try_) {
136
+ return Effect.try({
137
+ try: try_,
138
+ catch: (error) => error
139
+ });
73
140
  }
74
141
  //#endregion
75
- export { createRunnerLifecycleContext };
142
+ export { createRunnerLifecycleContextEffect };
@@ -2,8 +2,10 @@ import { emitWorkflowPlanned, emitWorkflowStarted } from "../runtime/events/even
2
2
  import { dispatchHooks } from "../runtime/hooks/hooks.js";
3
3
  import "../runtime/hooks/index.js";
4
4
  import { runWorkflowStartLifecycle } from "../runtime/workflow-lifecycle.js";
5
- import { createRunnerLifecycleContext } from "./lifecycle-context.js";
5
+ import { flushAndReport, isOutputStream, runValidatedRunnerCommand } from "../runtime/services/runner-command-io-service.js";
6
+ import { createRunnerLifecycleContextEffect } from "./lifecycle-context.js";
6
7
  import { z } from "zod";
8
+ import { Effect } from "effect";
7
9
  //#region src/runner-command/lifecycle.ts
8
10
  const runnerLifecycleOptionsSchema = z.object({
9
11
  cwd: z.string().min(1).optional(),
@@ -17,39 +19,28 @@ const EXIT_PASS = 0;
17
19
  const EXIT_FAIL = 1;
18
20
  const EXIT_VALIDATION = 64;
19
21
  const EXIT_STARTUP = 70;
20
- async function runRunnerLifecycle(rawOptions = {}) {
21
- const parsedOptions = runnerLifecycleOptionsSchema.safeParse(rawOptions);
22
- const stderr = rawOptions.stderr ?? process.stderr;
23
- if (!parsedOptions.success) {
24
- stderr.write(`${parsedOptions.error.message}\n`);
25
- return EXIT_VALIDATION;
26
- }
27
- const options = parsedOptions.data;
28
- try {
29
- const { context, sink } = await createRunnerLifecycleContext(options);
30
- const failure = await runWorkflowStartLifecycle({
31
- emitWorkflowPlanned: () => emitWorkflowPlanned(context),
32
- emitWorkflowStarted: () => emitWorkflowStarted(context),
33
- runWorkflowHook: (event) => dispatchHooks(context, event)
22
+ function runRunnerLifecycle(rawOptions = {}) {
23
+ return runValidatedRunnerCommand(runnerLifecycleOptionsSchema, rawOptions, runRunnerLifecycleEffect);
24
+ }
25
+ function runRunnerLifecycleEffect(options, stderr) {
26
+ return Effect.gen(function* () {
27
+ const { context, sink } = yield* createRunnerLifecycleContextEffect(options);
28
+ const failure = yield* Effect.tryPromise({
29
+ try: () => runWorkflowStartLifecycle({
30
+ emitWorkflowPlanned: () => emitWorkflowPlanned(context),
31
+ emitWorkflowStarted: () => emitWorkflowStarted(context),
32
+ runWorkflowHook: (event) => dispatchHooks(context, event)
33
+ }),
34
+ catch: (error) => error
34
35
  });
35
- await flushAndReport(sink, stderr);
36
+ yield* flushAndReport(sink, stderr);
36
37
  return failure ? EXIT_FAIL : EXIT_PASS;
37
- } catch (error) {
38
- const message = error instanceof Error ? error.message : String(error);
39
- stderr.write(`${message}\n`);
40
- return error instanceof z.ZodError ? EXIT_VALIDATION : EXIT_STARTUP;
41
- }
42
- }
43
- function isOutputStream(value) {
44
- return typeof value === "object" && value !== null && "write" in value && typeof value.write === "function";
38
+ }).pipe(Effect.catchAll((error) => Effect.sync(() => lifecycleErrorExitCode(error, stderr))));
45
39
  }
46
- async function flushAndReport(sink, stderr) {
47
- try {
48
- await sink.flush();
49
- } catch (error) {
50
- const message = error instanceof Error ? error.message : String(error);
51
- stderr.write(`runner event flush failed: ${message}\n`);
52
- }
40
+ function lifecycleErrorExitCode(error, stderr) {
41
+ const message = error instanceof Error ? error.message : String(error);
42
+ stderr.write(`${message}\n`);
43
+ return error instanceof z.ZodError ? EXIT_VALIDATION : EXIT_STARTUP;
53
44
  }
54
45
  //#endregion
55
46
  export { runRunnerLifecycle };