@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,3 +1,4 @@
1
+ import { Context, Effect, Layer } from "effect";
1
2
  //#region src/commands/pipeline-command.ts
2
3
  const BUILTIN_PIPE_COMMANDS = new Set([
3
4
  "run",
@@ -11,16 +12,32 @@ const BUILTIN_PIPE_COMMANDS = new Set([
11
12
  "argo",
12
13
  "runner-command"
13
14
  ]);
15
+ var EntrypointCommandService = class extends Context.Tag("EntrypointCommandService")() {};
16
+ const createEntrypointCommandServiceLive = (runEntrypoint) => Layer.succeed(EntrypointCommandService, { runEntrypoint: (entrypoint, task, opts) => Effect.tryPromise({
17
+ catch: (error) => error,
18
+ try: () => runEntrypoint(entrypoint, task, opts)
19
+ }) });
20
+ const runConfiguredEntrypointCommand = (entrypoint, descriptionParts, flags) => Effect.gen(function* () {
21
+ yield* (yield* EntrypointCommandService).runEntrypoint(entrypoint, descriptionParts.join(" "), flags);
22
+ });
23
+ const addScheduledEntrypointOptions = (command) => command.option("--namespace <namespace>", "Workflow namespace").option("--schedule <path>", "approved schedule YAML to submit").option("--kubeconfig <path>", "kubeconfig path").option("--service-account <name>", "Workflow service account").option("--image <image>", "runner image").option("--image-pull-policy <policy>", "runner image pull policy").option("--image-pull-secret <name>", "imagePullSecret name").option("--event-url <url>", "runner event sink URL");
24
+ const configureEntrypointOptions = (command, entrypoint) => {
25
+ if ("schedule" in entrypoint) return addScheduledEntrypointOptions(command);
26
+ return command;
27
+ };
28
+ const createEntrypointCommand = (program, id, entrypoint) => {
29
+ return configureEntrypointOptions(program.command(id).description(entrypoint.description ?? `Run the ${id} workflow`).argument("<description...>", "task description"), entrypoint);
30
+ };
31
+ const registerEntrypointAction = (command, id, serviceLive) => {
32
+ command.action((descriptionParts, flags) => Effect.runPromise(Effect.provide(runConfiguredEntrypointCommand(id, descriptionParts, flags), serviceLive)));
33
+ };
14
34
  function registerConfiguredEntrypointCommands(program, config, runEntrypoint) {
15
35
  const registered = /* @__PURE__ */ new Set();
36
+ const serviceLive = createEntrypointCommandServiceLive(runEntrypoint);
16
37
  const reservedCommands = new Set(program.commands.map((command) => command.name()));
17
38
  for (const [id, entrypoint] of Object.entries(config.entrypoints)) {
18
39
  if (reservedCommands.has(id)) continue;
19
- const command = program.command(id).description(entrypoint.description ?? `Run the ${id} workflow`).argument("<description...>", "task description");
20
- if ("schedule" in entrypoint) command.option("--namespace <namespace>", "Workflow namespace").option("--schedule <path>", "approved schedule YAML to submit").option("--kubeconfig <path>", "kubeconfig path").option("--service-account <name>", "Workflow service account").option("--image <image>", "runner image").option("--image-pull-policy <policy>", "runner image pull policy").option("--image-pull-secret <name>", "imagePullSecret name").option("--event-url <url>", "runner event sink URL");
21
- command.action(async (descriptionParts, flags) => {
22
- await runEntrypoint(id, descriptionParts.join(" "), flags);
23
- });
40
+ registerEntrypointAction(createEntrypointCommand(program, id, entrypoint), id, serviceLive);
24
41
  registered.add(id);
25
42
  reservedCommands.add(id);
26
43
  }
@@ -1,17 +1,40 @@
1
1
  import { runRunnerCommand } from "../runner-command/run.js";
2
2
  import { runRunnerFinalize } from "../runner-command/finalize.js";
3
3
  import { runRunnerLifecycle } from "../runner-command/lifecycle.js";
4
+ import { Context, Effect, Layer } from "effect";
4
5
  //#region src/commands/runner-command-command.ts
6
+ var RunnerCommandService = class extends Context.Tag("RunnerCommandService")() {};
7
+ const RunnerCommandServiceLive = Layer.succeed(RunnerCommandService, {
8
+ finalize: (options) => Effect.tryPromise({
9
+ catch: (error) => error,
10
+ try: () => runRunnerFinalize(options)
11
+ }),
12
+ lifecycle: (options) => Effect.tryPromise({
13
+ catch: (error) => error,
14
+ try: () => runRunnerLifecycle(options)
15
+ }),
16
+ run: (options) => Effect.tryPromise({
17
+ catch: (error) => error,
18
+ try: () => runRunnerCommand(options)
19
+ })
20
+ });
21
+ const setProcessExitCode = (exitCode) => Effect.sync(() => {
22
+ process.exitCode = exitCode;
23
+ });
24
+ const runRunnerCommandEffect = (options) => Effect.gen(function* () {
25
+ yield* setProcessExitCode(yield* (yield* RunnerCommandService).run(options));
26
+ });
27
+ const runRunnerLifecycleEffect = (options) => Effect.gen(function* () {
28
+ yield* setProcessExitCode(yield* (yield* RunnerCommandService).lifecycle(options));
29
+ });
30
+ const runRunnerFinalizeEffect = (options) => Effect.gen(function* () {
31
+ yield* setProcessExitCode(yield* (yield* RunnerCommandService).finalize(options));
32
+ });
33
+ const runRunnerProgram = (program) => Effect.runPromise(Effect.provide(program, RunnerCommandServiceLive));
5
34
  function registerRunnerCommandCommand(program) {
6
- program.command("runner-command").description("Run one scheduled Argo Workflow task").requiredOption("--payload-file <path>", "Path to the runner payload JSON").requiredOption("--schedule-file <path>", "Path to the schedule artifact YAML").action(async (options) => {
7
- process.exitCode = await runRunnerCommand(options);
8
- });
9
- program.command("runner-lifecycle").description("Run one Argo Workflow lifecycle phase").requiredOption("--phase <phase>", "Lifecycle phase to run").requiredOption("--payload-file <path>", "Path to the runner payload JSON").requiredOption("--schedule-file <path>", "Path to the schedule artifact YAML").action(async (options) => {
10
- process.exitCode = await runRunnerLifecycle(options);
11
- });
12
- program.command("runner-finalize").description("Finalize one Argo Workflow run").requiredOption("--payload-file <path>", "Path to the runner payload JSON").requiredOption("--schedule-file <path>", "Path to the schedule artifact YAML").requiredOption("--argo-status <status>", "Argo Workflow status").action(async (options) => {
13
- process.exitCode = await runRunnerFinalize(options);
14
- });
35
+ program.command("runner-command").description("Run one scheduled Argo Workflow task").requiredOption("--payload-file <path>", "Path to the runner payload JSON").requiredOption("--schedule-file <path>", "Path to the schedule artifact YAML").action((options) => runRunnerProgram(runRunnerCommandEffect(options)));
36
+ program.command("runner-lifecycle").description("Run one Argo Workflow lifecycle phase").requiredOption("--phase <phase>", "Lifecycle phase to run").requiredOption("--payload-file <path>", "Path to the runner payload JSON").requiredOption("--schedule-file <path>", "Path to the schedule artifact YAML").action((options) => runRunnerProgram(runRunnerLifecycleEffect(options)));
37
+ program.command("runner-finalize").description("Finalize one Argo Workflow run").requiredOption("--payload-file <path>", "Path to the runner payload JSON").requiredOption("--schedule-file <path>", "Path to the schedule artifact YAML").requiredOption("--argo-status <status>", "Argo Workflow status").action((options) => runRunnerProgram(runRunnerFinalizeEffect(options)));
15
38
  }
16
39
  //#endregion
17
40
  export { registerRunnerCommandCommand };
@@ -1,15 +1,22 @@
1
1
  import { resolvePackageAssetPath } from "../package-assets.js";
2
2
  import { standardOutputSchemaNameFromPath } from "../standard-output-schemas.js";
3
+ import { FileSystemService, FileSystemServiceLive, runFileSystemSync } from "../runtime/services/file-system-service.js";
3
4
  import { BUILTIN_PIPE_COMMANDS } from "../commands/pipeline-command.js";
4
- import { existsSync } from "node:fs";
5
+ import { Effect } from "effect";
5
6
  import { resolve } from "node:path";
6
7
  //#region src/config/lint.ts
7
8
  function lintPipelineConfig(config, projectRoot) {
8
- return [
9
- ...lintShadowedEntrypoints(config),
10
- ...lintMissingFileReferences(config, projectRoot),
11
- ...lintWorkflowNodes(config)
12
- ];
9
+ return runFileSystemSync(lintPipelineConfigEffect(config, projectRoot), FileSystemServiceLive);
10
+ }
11
+ function lintPipelineConfigEffect(config, projectRoot) {
12
+ return Effect.gen(function* () {
13
+ const missingFiles = yield* lintMissingFileReferencesEffect(config, projectRoot);
14
+ return [
15
+ ...lintShadowedEntrypoints(config),
16
+ ...missingFiles,
17
+ ...lintWorkflowNodes(config)
18
+ ];
19
+ });
13
20
  }
14
21
  function lintShadowedEntrypoints(config) {
15
22
  return Object.keys(config.entrypoints).filter((id) => BUILTIN_PIPE_COMMANDS.has(id)).map((id) => ({
@@ -17,31 +24,42 @@ function lintShadowedEntrypoints(config) {
17
24
  message: `entrypoint '${id}' is shadowed by the builtin subcommand; invoke via 'moka run --entrypoint ${id} ...'`
18
25
  }));
19
26
  }
20
- function lintMissingFileReferences(config, projectRoot) {
21
- const refs = [];
22
- for (const [skillId, skill] of Object.entries(config.skills)) refs.push({
23
- path: `skills.${skillId}.path`,
24
- ref: skill
27
+ function lintMissingFileReferencesEffect(config, projectRoot) {
28
+ return Effect.gen(function* () {
29
+ const fileSystem = yield* FileSystemService;
30
+ const warnings = [];
31
+ for (const ref of lintFileReferences(config)) if (!(yield* lintFileReferenceExists(projectRoot, ref, fileSystem.exists))) warnings.push(missingFileReferenceWarning(ref.path, ref.ref.path));
32
+ return warnings;
25
33
  });
34
+ }
35
+ function lintFileReferences(config) {
36
+ const refs = [];
37
+ for (const [skillId, skill] of Object.entries(config.skills)) pushLintPathRef(refs, `skills.${skillId}.path`, skill);
26
38
  for (const [profileId, profile] of Object.entries(config.profiles)) {
27
- refs.push({
28
- path: `profiles.${profileId}.instructions.path`,
29
- ref: { path: profile.instructions.path }
30
- });
31
- refs.push({
32
- path: `profiles.${profileId}.output.schema_path`,
33
- ref: { path: profile.output?.schema_path }
34
- });
39
+ pushLintPathRef(refs, `profiles.${profileId}.instructions.path`, { path: profile.instructions.path });
40
+ pushLintPathRef(refs, `profiles.${profileId}.output.schema_path`, { path: profile.output?.schema_path });
35
41
  }
36
- return refs.flatMap((ref) => {
37
- const value = ref.ref?.path;
38
- if (!value || standardOutputSchemaNameFromPath(value) || existsSync(resolveLintPathReference(projectRoot, ref.ref))) return [];
39
- return [{
40
- ruleId: "missing-file-reference",
41
- message: missingFileReferenceMessage(ref.path, value)
42
- }];
42
+ return refs;
43
+ }
44
+ function pushLintPathRef(refs, path, ref) {
45
+ if (ref.path) refs.push({
46
+ path,
47
+ ref: {
48
+ ...ref,
49
+ path: ref.path
50
+ }
43
51
  });
44
52
  }
53
+ function lintFileReferenceExists(projectRoot, ref, exists) {
54
+ if (standardOutputSchemaNameFromPath(ref.ref.path)) return Effect.succeed(true);
55
+ return exists(resolveLintPathReference(projectRoot, ref.ref));
56
+ }
57
+ function missingFileReferenceWarning(path, value) {
58
+ return {
59
+ ruleId: "missing-file-reference",
60
+ message: missingFileReferenceMessage(path, value)
61
+ };
62
+ }
45
63
  function missingFileReferenceMessage(path, value) {
46
64
  const base = `${path} references missing file '${value}'`;
47
65
  if (path.startsWith("skills.") && value.startsWith(".agents/skills/")) return `${base}. Run \`moka init\` to install project-local skills with \`npx --yes skills add oisin-ee/skills\`.`;
@@ -1,10 +1,11 @@
1
+ import { RepoIoService, RepoIoServiceLive } from "../runtime/services/repo-io-service.js";
1
2
  import { estimateTokens } from "../token-estimator.js";
2
3
  import { createRequire } from "node:module";
3
- import { readFileSync, readdirSync, statSync } from "node:fs";
4
+ import { Effect } from "effect";
4
5
  import { extname, join, relative } from "node:path";
6
+ import { Query } from "web-tree-sitter";
5
7
  import Graph from "graphology";
6
8
  import pagerank from "graphology-metrics/centrality/pagerank.js";
7
- import { Language, Parser, Query } from "web-tree-sitter";
8
9
  //#region src/context/repo-map.ts
9
10
  const SOURCE_EXTENSIONS = new Set([
10
11
  ".cjs",
@@ -23,62 +24,71 @@ const SKIP_DIRS = new Set([
23
24
  const SEED_BONUS = 1;
24
25
  const WORD_RE = /[a-z_][a-z0-9_]+/gi;
25
26
  const require = createRequire(import.meta.url);
26
- let parserPromise = null;
27
- const languageCache = /* @__PURE__ */ new Map();
28
27
  const queryCache = /* @__PURE__ */ new Map();
29
- function getParser() {
30
- parserPromise ??= Parser.init().then(() => new Parser());
31
- return parserPromise;
32
- }
33
- function loadLanguage(grammar) {
34
- const cached = languageCache.get(grammar);
35
- if (cached) return cached;
36
- const promise = Language.load(require.resolve(`tree-sitter-${grammar}/tree-sitter-${grammar}.wasm`));
37
- languageCache.set(grammar, promise);
38
- return promise;
39
- }
40
- function tagsQuery(language, grammar) {
28
+ function tagsQueryEffect(language, grammar) {
41
29
  const cached = queryCache.get(grammar);
42
- if (cached) return cached;
43
- const query = new Query(language, `${readFileSync(require.resolve("tree-sitter-javascript/queries/tags.scm"), "utf8")}\n${readFileSync(require.resolve("tree-sitter-typescript/queries/tags.scm"), "utf8")}`);
44
- queryCache.set(grammar, query);
45
- return query;
30
+ if (cached) return Effect.succeed(cached);
31
+ return Effect.gen(function* () {
32
+ const service = yield* RepoIoService;
33
+ const js = yield* service.readText(require.resolve("tree-sitter-javascript/queries/tags.scm"));
34
+ const ts = yield* service.readText(require.resolve("tree-sitter-typescript/queries/tags.scm"));
35
+ const query = yield* Effect.try({
36
+ catch: (error) => error,
37
+ try: () => new Query(language, `${js}\n${ts}`)
38
+ });
39
+ queryCache.set(grammar, query);
40
+ return query;
41
+ });
46
42
  }
47
43
  function grammarFor(file) {
48
44
  const ext = extname(file);
49
45
  return ext === ".ts" || ext === ".tsx" ? "typescript" : "javascript";
50
46
  }
51
- function discoverFiles(root) {
52
- const found = [];
53
- walkDir(root, found);
54
- return found.sort();
47
+ function discoverFilesEffect(root) {
48
+ return Effect.gen(function* () {
49
+ const found = [];
50
+ yield* walkDirEffect(root, found);
51
+ return found.sort();
52
+ });
55
53
  }
56
- function walkDir(dir, found) {
57
- for (const entry of readdirSync(dir).sort()) handleEntry(join(dir, entry), entry, found);
54
+ function walkDirEffect(dir, found) {
55
+ return Effect.gen(function* () {
56
+ const service = yield* RepoIoService;
57
+ for (const entry of yield* service.readDir(dir)) yield* handleEntryEffect(join(dir, entry.name), entry.name, found);
58
+ });
58
59
  }
59
- function handleEntry(full, name, found) {
60
- if (SKIP_DIRS.has(name)) return;
61
- if (statSync(full).isDirectory()) {
62
- walkDir(full, found);
63
- return;
64
- }
60
+ function handleEntryEffect(full, name, found) {
61
+ if (SKIP_DIRS.has(name)) return Effect.void;
62
+ return Effect.gen(function* () {
63
+ if (yield* (yield* RepoIoService).isDirectory(full)) return yield* walkDirEffect(full, found);
64
+ addSourceFile(full, name, found);
65
+ });
66
+ }
67
+ function addSourceFile(full, name, found) {
65
68
  if (SOURCE_EXTENSIONS.has(extname(name))) found.push(full);
66
69
  }
67
- async function tagFile(root, file) {
68
- const parser = await getParser();
70
+ function tagFileEffect(root, file) {
71
+ return Effect.gen(function* () {
72
+ const service = yield* RepoIoService;
73
+ return yield* parseFileTags(root, file, yield* service.createParser(), yield* service.readText(file));
74
+ });
75
+ }
76
+ function parseFileTags(root, file, parser, content) {
69
77
  const grammar = grammarFor(file);
70
- const language = await loadLanguage(grammar);
71
- parser.setLanguage(language);
72
78
  const path = relative(root, file);
73
- const tree = parser.parse(readFileSync(file, "utf8"));
74
- const tags = {
75
- definitions: [],
76
- path,
77
- references: []
78
- };
79
- if (!tree) return tags;
80
- for (const match of tagsQuery(language, grammar).matches(tree.rootNode)) addMatch(tags, path, match);
81
- return tags;
79
+ return Effect.gen(function* () {
80
+ const language = yield* (yield* RepoIoService).loadLanguage(require.resolve(`tree-sitter-${grammar}/tree-sitter-${grammar}.wasm`));
81
+ parser.setLanguage(language);
82
+ const tree = parser.parse(content);
83
+ const tags = {
84
+ definitions: [],
85
+ path,
86
+ references: []
87
+ };
88
+ const query = yield* tagsQueryEffect(language, grammar);
89
+ if (tree) for (const match of query.matches(tree.rootNode)) addMatch(tags, path, match);
90
+ return tags;
91
+ });
82
92
  }
83
93
  function addMatch(tags, path, match) {
84
94
  const nameCapture = match.captures.find((c) => c.name === "name");
@@ -187,17 +197,23 @@ function selectWithinBudget(ranked, budget, estimateTokens) {
187
197
  selected
188
198
  };
189
199
  }
190
- async function buildRepoMapContext(input) {
191
- const estimateTokens$1 = input.estimateTokens ?? estimateTokens;
192
- const ranked = rankDefinitions(buildGraph(await Promise.all(discoverFiles(input.worktreePath).map((file) => tagFile(input.worktreePath, file))), input));
193
- const { context, estimatedTokens, selected } = selectWithinBudget(ranked, input.tokenBudget, estimateTokens$1);
194
- return {
195
- budget: input.tokenBudget,
196
- context,
197
- estimatedTokens,
198
- selected,
199
- totalRanked: ranked.length
200
- };
200
+ function buildRepoMapContext(input) {
201
+ return Effect.runPromise(Effect.provide(buildRepoMapContextEffect(input), RepoIoServiceLive));
202
+ }
203
+ function buildRepoMapContextEffect(input) {
204
+ return Effect.gen(function* () {
205
+ const estimateTokens$1 = input.estimateTokens ?? estimateTokens;
206
+ const files = yield* discoverFilesEffect(input.worktreePath);
207
+ const ranked = rankDefinitions(buildGraph(yield* Effect.all(files.map((file) => tagFileEffect(input.worktreePath, file)), { concurrency: "unbounded" }), input));
208
+ const { context, estimatedTokens, selected } = selectWithinBudget(ranked, input.tokenBudget, estimateTokens$1);
209
+ return {
210
+ budget: input.tokenBudget,
211
+ context,
212
+ estimatedTokens,
213
+ selected,
214
+ totalRanked: ranked.length
215
+ };
216
+ });
201
217
  }
202
218
  //#endregion
203
219
  export { buildRepoMapContext };
package/dist/gates.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import "./safe-json.js";
2
2
  import { existsSync } from "node:fs";
3
- import { join } from "node:path";
4
3
  import "execa";
4
+ import { join } from "node:path";
5
5
  import "package-manager-detector/commands";
6
6
  import "package-manager-detector/detect";
7
7
  //#region src/gates.ts
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { createCliProgram, execute, quick, runCli, runDoctor } from "./cli/program.js";
1
+ import { runDoctor } from "./cli/doctor.js";
2
+ import { createCliProgram, execute, quick, runCli } from "./cli/program.js";
2
3
 
3
4
  //#region src/index.d.ts
4
5
  declare function isCliEntrypoint(argv: string[]): boolean;
package/dist/index.js CHANGED
@@ -2,7 +2,9 @@
2
2
  import { PipelineConfigError } from "./config/schemas.js";
3
3
  import "./config.js";
4
4
  import { formatConfigError } from "./pipeline-runtime.js";
5
- import { createCliProgram, execute, quick, runCli, runDoctor } from "./cli/program.js";
5
+ import { runDoctor } from "./cli/doctor.js";
6
+ import { createCliProgram, execute, quick, runCli, runCliEffect } from "./cli/program.js";
7
+ import { Cause, Effect, Exit } from "effect";
6
8
  import { existsSync, realpathSync } from "node:fs";
7
9
  import { resolve } from "node:path";
8
10
  import { fileURLToPath } from "node:url";
@@ -21,20 +23,24 @@ function normalizeEntrypointPath(path) {
21
23
  const resolved = resolve(path);
22
24
  return existsSync(resolved) ? realpathSync(resolved) : resolved;
23
25
  }
24
- if (isCliEntrypoint(process.argv)) runCli(process.argv).catch((err) => {
25
- if (err instanceof CommanderError) process.exit(err.exitCode);
26
- if (hasExitCode(err)) {
27
- if (err.message) console.error(err.message);
28
- process.exit(err.exitCode);
29
- }
30
- if (err instanceof Error) {
31
- if (err instanceof PipelineConfigError) console.error(formatConfigError(err));
32
- else console.error(err.message);
33
- process.exit(1);
34
- }
35
- console.error(String(err));
36
- process.exit(1);
26
+ if (isCliEntrypoint(process.argv)) Effect.runPromiseExit(runCliEffect(process.argv)).then((exit) => {
27
+ if (Exit.isFailure(exit)) handleCliFailure(Cause.squash(exit.cause));
37
28
  });
29
+ function handleCliFailure(err) {
30
+ const message = cliErrorMessage(err);
31
+ if (message) console.error(message);
32
+ process.exit(cliErrorCode(err));
33
+ }
34
+ function cliErrorCode(err) {
35
+ if (err instanceof CommanderError || hasExitCode(err)) return err.exitCode;
36
+ return 1;
37
+ }
38
+ function cliErrorMessage(err) {
39
+ if (err instanceof CommanderError) return;
40
+ if (err instanceof PipelineConfigError) return formatConfigError(err);
41
+ if (err instanceof Error) return err.message;
42
+ return String(err);
43
+ }
38
44
  function hasExitCode(err) {
39
45
  return err instanceof Error && "exitCode" in err && typeof err.exitCode === "number";
40
46
  }
@@ -2,10 +2,10 @@ import { renderClaudeGatewayMcpServers } from "../mcp/gateway.js";
2
2
  import { opencodeAgentName } from "../runtime/opencode-agent-name.js";
3
3
  import { mergeClaudeSettings } from "../claude-settings-config.js";
4
4
  import { CLAUDE_PROJECT_CONFIG_PATH, commandIdForHost, compactLines, entrypointDescription, entrypointEntries, instructionsPointer, invocationForHost } from "./shared.js";
5
- import { agentDispatchRoutes, entrypointDispatchBlock, grants, header, markdown, projectAgentsMdDefinition, resolvedHostModel, scheduledEntrypointK8sNote } from "./opencode.js";
5
+ import { agentDispatchRoutes, entrypointDispatchBlock, grants, header, markdown, projectAgentsMdDefinition, resolvedHostModel } from "./opencode.js";
6
6
  //#region src/install-commands/claude-code.ts
7
7
  const CLAUDE_CODE_HOST = "claude-code";
8
- const CLAUDE_ALLOWED_TOOLS = "Task, Bash(opencode run *)";
8
+ const CLAUDE_ALLOWED_TOOLS = "Bash(moka run *)";
9
9
  const CLAUDE_AGENT_TOOLS = "Bash, Read";
10
10
  const MOKA_PROFILE_PREFIX = "moka-";
11
11
  function claudeAgentNameForProfile(profileId) {
@@ -25,34 +25,7 @@ function distinctCliProfiles(config) {
25
25
  return profiles.sort((a, b) => a.profileId.localeCompare(b.profileId));
26
26
  }
27
27
  function commandDispatchBody(config, id, entrypoint) {
28
- if (!("workflow" in entrypoint)) return entrypointDispatchBlock(CLAUDE_CODE_HOST, config, id, entrypoint) ?? "";
29
- const routes = agentDispatchRoutes(CLAUDE_CODE_HOST, config, entrypoint.workflow);
30
- return [
31
- `Run workflow \`${entrypoint.workflow}\` for the user task.`,
32
- "",
33
- "Delegate each agent node to a Claude Code `Task` subagent that wraps a local `opencode run` subprocess.",
34
- "Spawn one node at a time respecting `needs`; run nodes whose dependencies are satisfied in parallel.",
35
- "",
36
- "Node routes:",
37
- ...routes.map(claudeNodeRouteLine),
38
- "",
39
- "For each node prompt include:",
40
- "- user task",
41
- `- workflow id: ${entrypoint.workflow}`,
42
- "- node id",
43
- "- profile id",
44
- "- profile instructions reference",
45
- "- profile grants",
46
- "- dependency outputs",
47
- "",
48
- "Only package-configured gates are blocking. Do not invent RED, GREEN, full-suite, typecheck, or unrelated-drift gates.",
49
- "If a node returns targeted evidence and has no configured blocking gate, advance to the next node.",
50
- "The Task subagents wrap `opencode run` subprocesses; do not claim these worker nodes are Claude Code native agents."
51
- ].join("\n");
52
- }
53
- function claudeNodeRouteLine(route) {
54
- const needs = route.needs.length > 0 ? route.needs.join(",") : "none";
55
- return `- ${route.nodeId}: Task subagent_type=${claudeAgentNameForProfile(route.profileId)} runner=${route.runnerId} agent="${opencodeAgentName(route.profileId)}" needs=${needs}`;
28
+ return entrypointDispatchBlock(CLAUDE_CODE_HOST, config, id, entrypoint) ?? "";
56
29
  }
57
30
  function commandDefinitions(config) {
58
31
  return entrypointEntries(config).map(([id, entrypoint]) => ({
@@ -67,8 +40,6 @@ function commandDefinitions(config) {
67
40
  "",
68
41
  "Load and follow the `execute` skill for the execution doctrine before dispatching work.",
69
42
  "",
70
- scheduledEntrypointK8sNote(entrypoint),
71
- scheduledEntrypointK8sNote(entrypoint) ? "" : void 0,
72
43
  commandDispatchBody(config, id, entrypoint)
73
44
  ]).join("\n")),
74
45
  host: CLAUDE_CODE_HOST,
@@ -113,7 +84,7 @@ function agentDefinitions(config) {
113
84
  });
114
85
  }
115
86
  function settingsDefinition(config) {
116
- const settings = { permissions: { allow: ["Task", "Bash(opencode run *)"] } };
87
+ const settings = { permissions: { allow: ["Bash(moka run *)"] } };
117
88
  if (config.mcp_gateway) settings.mcpServers = renderClaudeGatewayMcpServers(config);
118
89
  return [{
119
90
  content: `${JSON.stringify(settings, null, 2)}\n`,