@oisincoveney/pipeline 2.2.0 → 2.3.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.
- package/.agents/skills/orchestrate/SKILL.md +80 -0
- package/defaults/profiles.yaml +5 -2
- package/dist/argo-submit.d.ts +0 -1
- package/dist/argo-submit.js +1 -4
- package/dist/argo-workflow.d.ts +1 -2
- package/dist/argo-workflow.js +0 -2
- package/dist/cli/program.js +2 -3
- package/dist/cli/submit-options.js +1 -2
- package/dist/cluster-doctor.js +0 -12
- package/dist/commands/pipeline-command.js +1 -1
- package/dist/config/schemas.d.ts +4 -4
- package/dist/install-commands/opencode.js +17 -7
- package/dist/moka-global-config.d.ts +0 -1
- package/dist/moka-global-config.js +0 -1
- package/dist/moka-submit.d.ts +1 -4
- package/dist/moka-submit.js +1 -4
- package/dist/pipeline-runtime.d.ts +9 -0
- package/dist/planned-node.js +2 -5
- package/dist/{workflow-planner.d.ts → planning/compile.d.ts} +2 -2
- package/dist/{workflow-planner.js → planning/compile.js} +6 -83
- package/dist/{schedule/planner.d.ts → planning/generate.d.ts} +17 -3
- package/dist/{schedule/planner.js → planning/generate.js} +24 -56
- package/dist/planning/graph.js +138 -0
- package/dist/runner-command/lifecycle-context.js +2 -3
- package/dist/runner-command/run.js +2 -3
- package/dist/runner.d.ts +27 -0
- package/dist/runtime/context/context.js +1 -1
- package/dist/runtime/contracts/contracts.d.ts +16 -1
- package/dist/schedule/passes/coverage.js +7 -51
- package/dist/schedule/passes/ids.js +3 -23
- package/dist/schedule/scheduling-roles.js +19 -0
- package/dist/strings.js +30 -1
- package/docs/config-architecture.md +32 -0
- package/docs/operator-guide.md +2 -3
- package/docs/pipeline-console-runner-contract.md +3 -4
- package/package.json +5 -5
- package/dist/schedule-planner.d.ts +0 -2
- package/dist/schedule-planner.js +0 -2
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Local multi-agent orchestrator — decompose a task and fan it out to the MoKa specialist roster on the current machine instead of submitting to the remote Moka pipeline. The local twin of the MoKa Orchestrator. On Claude Code, dispatch each agent with `opencode run`; on OpenCode, spawn native Task subagents. Use when the user wants parallel specialist agents driven locally rather than as Argo/k8s jobs.
|
|
3
|
+
name: orchestrate
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Orchestrate
|
|
7
|
+
|
|
8
|
+
The **local twin of the MoKa Orchestrator**. The MoKa Orchestrator decomposes a task and submits a schedule to Argo/k8s via `moka submit`, where the runtime executes it as DAG jobs on the cluster. Orchestrate runs the **same roster, same loop, on the current machine** — no schedule, no `moka submit`, no Argo. It is the hands-on, here-and-now path for getting work done through specialist agents.
|
|
9
|
+
|
|
10
|
+
Use this skill when the user wants real work driven through **parallel specialist agents locally**: a task large enough to decompose into research / test / implement / verify lanes, where you stay the controller and the agents do the labor.
|
|
11
|
+
|
|
12
|
+
## When NOT to use
|
|
13
|
+
|
|
14
|
+
- **Durable, reproducible, or cluster-scale runs** → use [[quick]] or [[execute]] (these submit through `moka submit`). Orchestrate is ephemeral and local; it leaves no schedule artifact.
|
|
15
|
+
- **Trivial single-threaded work** → just do it inline. Spawning agents for a one-line change is pure overhead.
|
|
16
|
+
- **You need package gates enforced as part of a pipeline run** → that is the remote path's job. Orchestrate still *uses* the gate agents (Verifier, Acceptance Reviewer) but does not replace pipeline-level gating.
|
|
17
|
+
|
|
18
|
+
## The roster
|
|
19
|
+
|
|
20
|
+
The same specialist agents the MoKa pipeline uses, mirrored locally. Each is `mode: all`, so it works both as an `opencode run --agent` subprocess and as a native Task subagent.
|
|
21
|
+
|
|
22
|
+
| Role | Agent name | Writes | Job |
|
|
23
|
+
|-------------|-------------------------|---------------|-----|
|
|
24
|
+
| Research | `MoKa Researcher` | `research.json` only | Read-only; map the codebase, gather context, extract acceptance criteria. |
|
|
25
|
+
| Test | `MoKa Test Writer` | `*.test.ts` only | Write failing tests that describe the desired behaviour. |
|
|
26
|
+
| Implement | `MoKa Code Writer` | `src/**` only | Smallest production change that makes the failing tests pass. |
|
|
27
|
+
| Verify | `MoKa Verifier` | nothing | Run checks, judge diff against AC, emit `PASS`/`FAIL` with evidence. |
|
|
28
|
+
| Review | `MoKa Acceptance Reviewer` | nothing | Acceptance/quality gate before declaring done. |
|
|
29
|
+
| Inspect | `MoKa Inspector` | nothing | Read-only repository inspection / explanation. |
|
|
30
|
+
|
|
31
|
+
Keep each agent inside its lane — never ask the Code Writer to touch tests, or the Verifier to write files. The lane boundaries are what make fan-out safe.
|
|
32
|
+
|
|
33
|
+
## Dispatch by host
|
|
34
|
+
|
|
35
|
+
The orchestration **doctrine below is identical on every host**. Only the spawn mechanism differs — select the branch for the host you are actually running in.
|
|
36
|
+
|
|
37
|
+
### On Claude Code → `opencode run`
|
|
38
|
+
|
|
39
|
+
Spawn each roster member as a headless OpenCode subprocess and read its JSON back:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
opencode run --agent "MoKa Code Writer" --format json \
|
|
43
|
+
"<scoped task + acceptance criteria + paths to read>"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- Select the roster member with `--agent "<exact name>"` (names from the table above).
|
|
47
|
+
- Use `--format json` so the agent's structured result comes back machine-readable; parse it, do not eyeball it.
|
|
48
|
+
- **Parallelize independent lanes**: launch each `opencode run` as a background Bash process (one tool call per lane in the same turn), then collect. Run dependent lanes only after their inputs land.
|
|
49
|
+
- Pass context by path, not by paste — agents read the worktree directly. Hand them the files/AC the Researcher produced.
|
|
50
|
+
- `--model` / `--variant` only when a lane genuinely needs a different tier; otherwise inherit.
|
|
51
|
+
|
|
52
|
+
### On OpenCode → native Task subagents
|
|
53
|
+
|
|
54
|
+
You are already inside OpenCode — do not shell out to `opencode run`. Spawn the roster directly with the native **Task** tool, selecting the agent by the same name:
|
|
55
|
+
|
|
56
|
+
- `task` → `MoKa Researcher`, `MoKa Test Writer`, `MoKa Code Writer`, `MoKa Verifier`, `MoKa Acceptance Reviewer`.
|
|
57
|
+
- Issue independent Task calls together so they run concurrently; sequence dependent ones.
|
|
58
|
+
- Each subagent's structured output returns to you as the controller — gather, do not re-do their work.
|
|
59
|
+
|
|
60
|
+
## The loop
|
|
61
|
+
|
|
62
|
+
Whichever host you are on, run the same five steps:
|
|
63
|
+
|
|
64
|
+
1. **Plan** — Decompose the task into a DAG of agent lanes. Model parallelism *structurally*: independent lanes fan out together, dependents wait on their inputs (research → tests → implementation → verify). Do not invent JSON-pointer fanout; nest the work as real lanes.
|
|
65
|
+
2. **Dispatch** — Fan out per the host branch above. Scope each agent tightly: one job, its lane's write boundary, explicit acceptance criteria.
|
|
66
|
+
3. **Gather** — Collect each agent's structured output (`research.json`, the Verifier's verdict JSON, etc.). Treat the returned artifact as the source of truth.
|
|
67
|
+
4. **Gate** — Run `MoKa Verifier`, then `MoKa Acceptance Reviewer`. Do **not** accept work on a `FAIL`. Loop the relevant lane (re-dispatch Code Writer with the failure evidence) rather than papering over it.
|
|
68
|
+
5. **Synthesize** — Report only the evidence the agents actually returned: what passed, what the diff is, what the verifier proved. Never fabricate or assume an outcome an agent did not report.
|
|
69
|
+
|
|
70
|
+
## Rules
|
|
71
|
+
|
|
72
|
+
- **Doctrine is host-neutral; only the Dispatch section is host-specific.** Do not leak `opencode run` syntax into an OpenCode run or Task-tool talk into a Claude run.
|
|
73
|
+
- **You are the controller, not a worker.** Decompose, dispatch, gate, synthesize. Let the specialists do the labor inside their lanes.
|
|
74
|
+
- **Evidence only.** Report what agents returned. A green claim needs a Verifier `PASS` with evidence behind it — see [[verify]].
|
|
75
|
+
- **Respect lane write boundaries.** Researcher/Verifier/Reviewer write nothing; Test Writer touches only tests; Code Writer touches only `src/`. Mixed lanes corrupt parallel fan-out.
|
|
76
|
+
- **Local, not durable.** If the user needs a reproducible cluster run or a schedule artifact, route to [[quick]] / [[execute]] instead.
|
|
77
|
+
|
|
78
|
+
## The short version
|
|
79
|
+
|
|
80
|
+
Orchestrate is `moka submit` brought home: same roster, same decompose → dispatch → gather → gate → synthesize loop, run on this machine. On Claude Code each agent is an `opencode run --agent` subprocess; on OpenCode each is a native Task subagent. You stay the orchestrator — fan out the lanes, gate on real verifier evidence, and report only what the agents proved.
|
package/defaults/profiles.yaml
CHANGED
|
@@ -30,6 +30,9 @@ mcp_gateway:
|
|
|
30
30
|
workspace_path_source: PIPELINE_TARGET_PATH
|
|
31
31
|
tool_prefixes: [backlog]
|
|
32
32
|
skills:
|
|
33
|
+
orchestrate:
|
|
34
|
+
path: .agents/skills/orchestrate/SKILL.md
|
|
35
|
+
source_root: package
|
|
33
36
|
execute:
|
|
34
37
|
path: .agents/skills/execute/SKILL.md
|
|
35
38
|
source_root: package
|
|
@@ -85,8 +88,8 @@ profiles:
|
|
|
85
88
|
moka-orchestrator:
|
|
86
89
|
runner: opencode
|
|
87
90
|
description: Orchestrate the configured pipeline and enforce gates.
|
|
88
|
-
instructions: { inline: "Orchestrate the configured pipeline
|
|
89
|
-
skills: [execute, quick, inspect]
|
|
91
|
+
instructions: { inline: "Orchestrate the configured pipeline locally. Load the `orchestrate` skill and spawn the roster as native Task subagents on this machine. Do not submit to Argo or run `moka submit`. Enforce only package-configured gates." }
|
|
92
|
+
skills: [orchestrate, execute, quick, inspect]
|
|
90
93
|
mcp_servers: [pipeline-gateway]
|
|
91
94
|
tools: [read, list, grep, glob, bash]
|
|
92
95
|
filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
|
package/dist/argo-submit.d.ts
CHANGED
|
@@ -22,7 +22,6 @@ declare const submitRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
|
|
|
22
22
|
namespace: z.ZodString;
|
|
23
23
|
opencodeAuthSecretName: z.ZodOptional<z.ZodString>;
|
|
24
24
|
payloadJson: z.ZodString;
|
|
25
|
-
queueName: z.ZodOptional<z.ZodString>;
|
|
26
25
|
scheduleYaml: z.ZodString;
|
|
27
26
|
serviceAccountName: z.ZodOptional<z.ZodString>;
|
|
28
27
|
}, z.core.$strict>;
|
package/dist/argo-submit.js
CHANGED
|
@@ -2,9 +2,8 @@ import { ArgoGraphCompilerError, compileArgoExecutionGraph } from "./argo-graph.
|
|
|
2
2
|
import { buildRunnerTaskDescriptor } from "./runner-command/task-descriptor.js";
|
|
3
3
|
import { buildRunnerArgoWorkflowManifest, runnerArgoWorkflowManifestSchema } from "./argo-workflow.js";
|
|
4
4
|
import { normalizeRunnerRepositoryForSubmit } from "./git-remote-url.js";
|
|
5
|
+
import { compileScheduleArtifact, parseScheduleArtifact } from "./planning/generate.js";
|
|
5
6
|
import { parseRunnerCommandPayload, runnerCommandPayloadSchema } from "./runner-command-contract.js";
|
|
6
|
-
import { compileScheduleArtifact, parseScheduleArtifact } from "./schedule/planner.js";
|
|
7
|
-
import "./schedule-planner.js";
|
|
8
7
|
import { workflowSubmitResultSchema } from "./workflow-submit-contract.js";
|
|
9
8
|
import { stringify } from "yaml";
|
|
10
9
|
import { z } from "zod";
|
|
@@ -40,7 +39,6 @@ const submitRunnerArgoWorkflowOptionsSchema = z.object({
|
|
|
40
39
|
namespace: z.string().min(1),
|
|
41
40
|
opencodeAuthSecretName: z.string().min(1).optional(),
|
|
42
41
|
payloadJson: z.string().min(1),
|
|
43
|
-
queueName: z.string().min(1).optional(),
|
|
44
42
|
scheduleYaml: z.string().min(1),
|
|
45
43
|
serviceAccountName: z.string().min(1).optional()
|
|
46
44
|
}).strict().refine((options) => options.name !== void 0 || options.generateName !== void 0, { message: "Argo submit options must declare name or generateName" });
|
|
@@ -89,7 +87,6 @@ async function submitRunnerArgoWorkflow(rawOptions, dependencies = {}) {
|
|
|
89
87
|
opencodeAuthSecretName: options.opencodeAuthSecretName,
|
|
90
88
|
payloadConfigMapName,
|
|
91
89
|
plan: compiled.plan,
|
|
92
|
-
queueName: options.queueName,
|
|
93
90
|
scheduleConfigMapName: scheduleArtifactConfigMapName,
|
|
94
91
|
serviceAccountName: options.serviceAccountName,
|
|
95
92
|
taskDescriptorConfigMapName
|
package/dist/argo-workflow.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WorkflowExecutionPlan } from "./
|
|
1
|
+
import { WorkflowExecutionPlan } from "./planning/compile.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
//#region src/argo-workflow.d.ts
|
|
@@ -143,7 +143,6 @@ declare const buildRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
|
|
|
143
143
|
}, z.core.$strict>>;
|
|
144
144
|
payloadConfigMapKey: z.ZodDefault<z.ZodString>;
|
|
145
145
|
payloadConfigMapName: z.ZodString;
|
|
146
|
-
queueName: z.ZodOptional<z.ZodString>;
|
|
147
146
|
resources: z.ZodOptional<z.ZodObject<{
|
|
148
147
|
limits: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
149
148
|
requests: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
package/dist/argo-workflow.js
CHANGED
|
@@ -153,7 +153,6 @@ const buildRunnerArgoWorkflowOptionsSchema = z.object({
|
|
|
153
153
|
}).strict().optional(),
|
|
154
154
|
payloadConfigMapKey: z.string().min(1).default("payload.json"),
|
|
155
155
|
payloadConfigMapName: kubernetesNameSchema,
|
|
156
|
-
queueName: kubernetesNameSchema.optional(),
|
|
157
156
|
resources: argoWorkflowResourceRequirementsSchema.optional(),
|
|
158
157
|
scheduleConfigMapKey: z.string().min(1).default("schedule.yaml"),
|
|
159
158
|
scheduleConfigMapName: kubernetesNameSchema,
|
|
@@ -191,7 +190,6 @@ function buildRunnerArgoWorkflowManifest(rawOptions) {
|
|
|
191
190
|
...options.activeDeadlineSeconds ? { activeDeadlineSeconds: options.activeDeadlineSeconds } : {},
|
|
192
191
|
entrypoint: RUNNER_WORKFLOW_ENTRYPOINT,
|
|
193
192
|
...options.imagePullSecretName ? { imagePullSecrets: [{ name: options.imagePullSecretName }] } : {},
|
|
194
|
-
...options.queueName ? { podMetadata: { labels: { "kueue.x-k8s.io/queue-name": options.queueName } } } : {},
|
|
195
193
|
onExit: "pipeline-finalizer",
|
|
196
194
|
serviceAccountName: options.serviceAccountName,
|
|
197
195
|
templates: [
|
package/dist/cli/program.js
CHANGED
|
@@ -2,9 +2,8 @@ import { PipelineConfigError } from "../config/schemas.js";
|
|
|
2
2
|
import { loadPipelineConfig } from "../config/load.js";
|
|
3
3
|
import "../config.js";
|
|
4
4
|
import { createOrchestratorLaunchPlan, createRunnerLaunchPlan } from "../runner.js";
|
|
5
|
-
import { compileWorkflowPlan } from "../
|
|
6
|
-
import { compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact } from "../
|
|
7
|
-
import "../schedule-planner.js";
|
|
5
|
+
import { compileWorkflowPlan } from "../planning/compile.js";
|
|
6
|
+
import { compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact } from "../planning/generate.js";
|
|
8
7
|
import { loadMokaGlobalConfig } from "../moka-global-config.js";
|
|
9
8
|
import { defaultClusterDoctorNamespace, runClusterDoctor } from "../cluster-doctor.js";
|
|
10
9
|
import { formatCodexAuthSyncResult, syncLocalCodexAuth } from "../codex-auth-sync.js";
|
|
@@ -48,7 +48,6 @@ function mokaCommonSubmitOptions(input) {
|
|
|
48
48
|
name: input.flags.name,
|
|
49
49
|
namespace: input.flags.namespace ?? momokaya?.kubernetes.namespace,
|
|
50
50
|
opencodeAuthSecretName: momokaya?.submit.opencodeAuthSecretName,
|
|
51
|
-
queueName: input.flags.queueName ?? momokaya?.submit.queueName,
|
|
52
51
|
serviceAccountName: input.flags.serviceAccount ?? momokaya?.submit.serviceAccountName,
|
|
53
52
|
worktreePath: input.cwd
|
|
54
53
|
};
|
|
@@ -77,7 +76,7 @@ function submitMokaGraphInput(input, flags, commonOptions) {
|
|
|
77
76
|
function addRunnerArgoOptions(command, options = {}) {
|
|
78
77
|
command.option("--name <name>", "Workflow metadata.name").option("--generate-name <prefix>", "Workflow metadata.generateName").option("--namespace <namespace>", "Workflow namespace");
|
|
79
78
|
if (options.kubeconfig) command.option("--kubeconfig <path>", "kubeconfig path");
|
|
80
|
-
return command.option("--
|
|
79
|
+
return command.option("--service-account <name>", "Workflow service account").option("--image <image>", "runner image").addOption(new Option("--image-pull-policy <policy>", "runner image pull policy").choices([
|
|
81
80
|
"Always",
|
|
82
81
|
"IfNotPresent",
|
|
83
82
|
"Never"
|
package/dist/cluster-doctor.js
CHANGED
|
@@ -10,7 +10,6 @@ const DEFAULT_RESOURCES = {
|
|
|
10
10
|
githubAuthSecretName: "oisin-bot-github-auth",
|
|
11
11
|
imagePullSecretName: "ghcr-pull-secret",
|
|
12
12
|
opencodeAuthSecretName: "opencode-auth-1",
|
|
13
|
-
queueName: "pipeline-runner",
|
|
14
13
|
serviceAccountName: "pipeline-runner"
|
|
15
14
|
};
|
|
16
15
|
const FORBIDDEN_RE = /forbidden/i;
|
|
@@ -32,7 +31,6 @@ async function runClusterDoctor(options = {}) {
|
|
|
32
31
|
verb: "create",
|
|
33
32
|
...kubectlOptions
|
|
34
33
|
}),
|
|
35
|
-
checkLocalQueue(namespace, resources.queueName, kubectlOptions),
|
|
36
34
|
checkClusterResource("argo-workflow-crd", [
|
|
37
35
|
"get",
|
|
38
36
|
"crd",
|
|
@@ -63,7 +61,6 @@ function clusterResources() {
|
|
|
63
61
|
githubAuthSecretName: configured.githubAuthSecretName,
|
|
64
62
|
imagePullSecretName: configured.imagePullSecretName,
|
|
65
63
|
opencodeAuthSecretName: configured.opencodeAuthSecretName,
|
|
66
|
-
queueName: configured.queueName,
|
|
67
64
|
serviceAccountName: configured.serviceAccountName
|
|
68
65
|
} : DEFAULT_RESOURCES;
|
|
69
66
|
}
|
|
@@ -169,15 +166,6 @@ async function checkWorkflowSubmitPermission(namespace, options) {
|
|
|
169
166
|
passed: false
|
|
170
167
|
};
|
|
171
168
|
}
|
|
172
|
-
function checkLocalQueue(namespace, queueName, kubectlOptions) {
|
|
173
|
-
return checkNamespacedResource(`localqueue/${queueName}`, [
|
|
174
|
-
"get",
|
|
175
|
-
"localqueue",
|
|
176
|
-
queueName,
|
|
177
|
-
"-n",
|
|
178
|
-
namespace
|
|
179
|
-
], `Kueue LocalQueue ${queueName} missing in ${namespace}; runner Workflow pods cannot be admitted to the expected queue.`, kubectlOptions);
|
|
180
|
-
}
|
|
181
169
|
async function checkClusterResource(name, args, kubectlOptions) {
|
|
182
170
|
const result = await kubectl(args, kubectlOptions);
|
|
183
171
|
return result.ok ? {
|
|
@@ -17,7 +17,7 @@ function registerConfiguredEntrypointCommands(program, config, runEntrypoint) {
|
|
|
17
17
|
for (const [id, entrypoint] of Object.entries(config.entrypoints)) {
|
|
18
18
|
if (reservedCommands.has(id)) continue;
|
|
19
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("--
|
|
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
21
|
command.action(async (descriptionParts, flags) => {
|
|
22
22
|
await runEntrypoint(id, descriptionParts.join(" "), flags);
|
|
23
23
|
});
|
package/dist/config/schemas.d.ts
CHANGED
|
@@ -220,8 +220,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
220
220
|
policy: z.ZodOptional<z.ZodObject<{
|
|
221
221
|
commands: z.ZodOptional<z.ZodEnum<{
|
|
222
222
|
allow: "allow";
|
|
223
|
-
"trusted-only": "trusted-only";
|
|
224
223
|
deny: "deny";
|
|
224
|
+
"trusted-only": "trusted-only";
|
|
225
225
|
}>>;
|
|
226
226
|
modules: z.ZodOptional<z.ZodEnum<{
|
|
227
227
|
allow: "allow";
|
|
@@ -245,8 +245,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
245
245
|
}, z.core.$strict>>>;
|
|
246
246
|
default_profile: z.ZodOptional<z.ZodString>;
|
|
247
247
|
mode: z.ZodEnum<{
|
|
248
|
-
hosted: "hosted";
|
|
249
248
|
local: "local";
|
|
249
|
+
hosted: "hosted";
|
|
250
250
|
}>;
|
|
251
251
|
provider: z.ZodLiteral<"toolhive">;
|
|
252
252
|
authorization_env: z.ZodDefault<z.ZodString>;
|
|
@@ -289,10 +289,10 @@ declare const configSchema: z.ZodObject<{
|
|
|
289
289
|
}, z.core.$strict>>;
|
|
290
290
|
output: z.ZodOptional<z.ZodObject<{
|
|
291
291
|
format: z.ZodEnum<{
|
|
292
|
+
json_schema: "json_schema";
|
|
292
293
|
text: "text";
|
|
293
294
|
json: "json";
|
|
294
295
|
jsonl: "jsonl";
|
|
295
|
-
json_schema: "json_schema";
|
|
296
296
|
}>;
|
|
297
297
|
repair: z.ZodOptional<z.ZodObject<{
|
|
298
298
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -361,10 +361,10 @@ declare const configSchema: z.ZodObject<{
|
|
|
361
361
|
disabled: "disabled";
|
|
362
362
|
}>>>;
|
|
363
363
|
output_formats: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
364
|
+
json_schema: "json_schema";
|
|
364
365
|
text: "text";
|
|
365
366
|
json: "json";
|
|
366
367
|
jsonl: "jsonl";
|
|
367
|
-
json_schema: "json_schema";
|
|
368
368
|
}>>>;
|
|
369
369
|
rules: z.ZodOptional<z.ZodBoolean>;
|
|
370
370
|
skills: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -1,7 +1,7 @@
|
|
|
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 "../
|
|
4
|
+
import { compileWorkflowPlan } from "../planning/compile.js";
|
|
5
5
|
import { mergeOpenCodeProjectConfig } from "../opencode-project-config.js";
|
|
6
6
|
import { renderOpenCodeGatewayConfig } from "../mcp/gateway.js";
|
|
7
7
|
import { opencodeAgentName } from "../runtime/opencode-agent-name.js";
|
|
@@ -149,10 +149,19 @@ function scheduledEntrypointK8sNote(entrypoint) {
|
|
|
149
149
|
if ("workflow" in entrypoint) return;
|
|
150
150
|
return "Submit Momokaya work as Argo Workflows through `moka submit` and `moka submit --quick`.";
|
|
151
151
|
}
|
|
152
|
-
function
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
function localRosterAgentIds(config) {
|
|
153
|
+
return nativeProfileEntries("opencode", config).map(([id]) => nativeAgentIdForHost("opencode", id));
|
|
154
|
+
}
|
|
155
|
+
function localOrchestratorDispatchBlock(config) {
|
|
156
|
+
return [
|
|
157
|
+
"Orchestrate locally. Load and follow the `orchestrate` skill.",
|
|
158
|
+
"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.",
|
|
159
|
+
"",
|
|
160
|
+
"Roster (Task tool subagent_type):",
|
|
161
|
+
...localRosterAgentIds(config).map((id) => `- ${id}`),
|
|
162
|
+
"",
|
|
163
|
+
"Gather each subagent's structured output, enforce only package-configured gates, and report only the evidence the subagents returned."
|
|
164
|
+
].join("\n");
|
|
156
165
|
}
|
|
157
166
|
function nativeDispatchBlock(host, routes) {
|
|
158
167
|
if (routes.length === 0) return;
|
|
@@ -325,13 +334,13 @@ function opencodeDefinitions(config, cwd) {
|
|
|
325
334
|
description: "Orchestrate the configured pipeline and enforce gates.",
|
|
326
335
|
mode: "primary",
|
|
327
336
|
name: OPENCODE_ORCHESTRATOR_AGENT_ID,
|
|
328
|
-
permission: opencodePermission(orchestrator, { allowedTaskAgents:
|
|
337
|
+
permission: opencodePermission(orchestrator, { allowedTaskAgents: localRosterAgentIds(config) })
|
|
329
338
|
}, compactLines([
|
|
330
339
|
header("opencode").trimEnd(),
|
|
331
340
|
"",
|
|
332
341
|
orchestratorBlock(config),
|
|
333
342
|
"",
|
|
334
|
-
|
|
343
|
+
localOrchestratorDispatchBlock(config)
|
|
335
344
|
]).join("\n")),
|
|
336
345
|
host: "opencode",
|
|
337
346
|
invocation: invocationForHost("opencode"),
|
|
@@ -382,6 +391,7 @@ function projectAgentsMdDefinition(cwd, host) {
|
|
|
382
391
|
"- Use `/moka-quick`, `/moka-execute`, or `/moka-inspect` for OpenCode slash-command entrypoints when available.",
|
|
383
392
|
"- Load and follow the relevant skill from `.agents/skills` before doing specialized work.",
|
|
384
393
|
"- Prefer the package-defined pipeline profiles and generated command surfaces over ad hoc subagent prompts.",
|
|
394
|
+
"- When the user needs to run a command, copy the command into the clipboard and tell the user what needs to be returned.",
|
|
385
395
|
"",
|
|
386
396
|
"## Pipeline Memory",
|
|
387
397
|
"",
|
|
@@ -16,7 +16,6 @@ declare const mokaGlobalConfigSchema: z.ZodObject<{
|
|
|
16
16
|
githubAuthSecretName: z.ZodString;
|
|
17
17
|
imagePullSecretName: z.ZodString;
|
|
18
18
|
opencodeAuthSecretName: z.ZodString;
|
|
19
|
-
queueName: z.ZodString;
|
|
20
19
|
serviceAccountName: z.ZodString;
|
|
21
20
|
}, z.core.$strict>;
|
|
22
21
|
}, z.core.$strict>;
|
|
@@ -15,7 +15,6 @@ const mokaSubmitGlobalConfigSchema = z.object({
|
|
|
15
15
|
githubAuthSecretName: z.string().min(1),
|
|
16
16
|
imagePullSecretName: z.string().min(1),
|
|
17
17
|
opencodeAuthSecretName: z.string().min(1),
|
|
18
|
-
queueName: z.string().min(1),
|
|
19
18
|
serviceAccountName: z.string().min(1)
|
|
20
19
|
}).strict();
|
|
21
20
|
const mokaKubernetesGlobalConfigSchema = z.object({
|
package/dist/moka-submit.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PipelineConfig } from "./config/schemas.js";
|
|
2
|
-
import { generateScheduleArtifact } from "./
|
|
2
|
+
import { generateScheduleArtifact } from "./planning/generate.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
5
|
//#region src/moka-submit.d.ts
|
|
@@ -148,7 +148,6 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
148
148
|
name: z.ZodOptional<z.ZodString>;
|
|
149
149
|
namespace: z.ZodOptional<z.ZodString>;
|
|
150
150
|
opencodeAuthSecretName: z.ZodOptional<z.ZodString>;
|
|
151
|
-
queueName: z.ZodOptional<z.ZodString>;
|
|
152
151
|
repository: z.ZodOptional<z.ZodObject<{
|
|
153
152
|
baseBranch: z.ZodString;
|
|
154
153
|
sha: z.ZodOptional<z.ZodString>;
|
|
@@ -261,7 +260,6 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
261
260
|
name: z.ZodOptional<z.ZodString>;
|
|
262
261
|
namespace: z.ZodOptional<z.ZodString>;
|
|
263
262
|
opencodeAuthSecretName: z.ZodOptional<z.ZodString>;
|
|
264
|
-
queueName: z.ZodOptional<z.ZodString>;
|
|
265
263
|
repository: z.ZodOptional<z.ZodObject<{
|
|
266
264
|
baseBranch: z.ZodString;
|
|
267
265
|
sha: z.ZodOptional<z.ZodString>;
|
|
@@ -327,7 +325,6 @@ interface MokaWorkflowSubmitOptions {
|
|
|
327
325
|
namespace: string;
|
|
328
326
|
opencodeAuthSecretName?: string;
|
|
329
327
|
payloadJson: string;
|
|
330
|
-
queueName?: string;
|
|
331
328
|
scheduleYaml: string;
|
|
332
329
|
serviceAccountName?: string;
|
|
333
330
|
}
|
package/dist/moka-submit.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { normalizeRunnerRepositoryForSubmit } from "./git-remote-url.js";
|
|
2
|
+
import { compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact } from "./planning/generate.js";
|
|
2
3
|
import { buildRunnerCommandPayload, runnerDeliverySchema, runnerHookPolicySchema, runnerRepositoryContextSchema, runnerRunIdentitySchema, runnerTaskSchema } from "./runner-command-contract.js";
|
|
3
|
-
import { compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact } from "./schedule/planner.js";
|
|
4
|
-
import "./schedule-planner.js";
|
|
5
4
|
import { workflowSubmitResultSchema } from "./workflow-submit-contract.js";
|
|
6
5
|
import { buildCommandScheduleYaml, submitRunnerArgoWorkflow } from "./argo-submit.js";
|
|
7
6
|
import { z } from "zod";
|
|
@@ -79,7 +78,6 @@ const mokaSubmitBaseOptionsSchema = z.object({
|
|
|
79
78
|
name: z.string().min(1).optional(),
|
|
80
79
|
namespace: z.string().min(1).optional(),
|
|
81
80
|
opencodeAuthSecretName: z.string().min(1).optional(),
|
|
82
|
-
queueName: z.string().min(1).optional(),
|
|
83
81
|
repository: runnerRepositoryContextSchema.optional(),
|
|
84
82
|
run: runnerRunIdentitySchema.optional(),
|
|
85
83
|
serviceAccountName: z.string().min(1).optional()
|
|
@@ -293,7 +291,6 @@ function workflowSubmitOptions(options) {
|
|
|
293
291
|
name: options.name,
|
|
294
292
|
namespace: requireSubmitOption(options.namespace, "namespace"),
|
|
295
293
|
opencodeAuthSecretName: options.opencodeAuthSecretName,
|
|
296
|
-
queueName: options.queueName,
|
|
297
294
|
serviceAccountName: options.serviceAccountName
|
|
298
295
|
};
|
|
299
296
|
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { PipelineConfigError } from "./config/schemas.js";
|
|
2
2
|
import { AcceptanceCriterion, HookRuntimePolicy, NodeExecutionState, NodeStatus, PipelineRuntimeEvent, PipelineRuntimeObservabilityLevel, PipelineRuntimeOptions, PipelineRuntimeResult, PipelineTaskContext, RuntimeFailure, RuntimeGateResult, RuntimeNodeResult, RuntimeStructuredOutput } from "./runtime/contracts/contracts.js";
|
|
3
3
|
//#region src/pipeline-runtime.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Top layer of the runtime-options stack (PIPE-74 B3). Extends
|
|
6
|
+
* {@link PipelineRuntimeOptions} for the schedule-driven path that runs a
|
|
7
|
+
* SINGLE workflow node (`nodeId`) in isolation, supplying that node's upstream
|
|
8
|
+
* `dependencyOutputs`. Full stack:
|
|
9
|
+
* RunnerExecutionOptions (src/runner.ts)
|
|
10
|
+
* < PipelineRuntimeOptions (src/runtime/contracts/contracts.ts)
|
|
11
|
+
* < ScheduledWorkflowTaskRuntimeOptions (this type)
|
|
12
|
+
*/
|
|
4
13
|
interface ScheduledWorkflowTaskRuntimeOptions extends PipelineRuntimeOptions {
|
|
5
14
|
dependencyOutputs?: Map<string, string> | Record<string, string>;
|
|
6
15
|
nodeId: string;
|
package/dist/planned-node.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
+
import { findNode } from "./planning/graph.js";
|
|
1
2
|
//#region src/planned-node.ts
|
|
2
3
|
function findPlannedNode(nodes, nodeId) {
|
|
3
|
-
|
|
4
|
-
if (node.id === nodeId) return node;
|
|
5
|
-
const child = findPlannedNode(node.children ?? [], nodeId);
|
|
6
|
-
if (child) return child;
|
|
7
|
-
}
|
|
4
|
+
return findNode(nodes, nodeId, (node) => node.children);
|
|
8
5
|
}
|
|
9
6
|
//#endregion
|
|
10
7
|
export { findPlannedNode };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { PipelineConfig, WorkflowNodeKind } from "
|
|
1
|
+
import { PipelineConfig, WorkflowNodeKind } from "../config/schemas.js";
|
|
2
2
|
import { Graph } from "@dagrejs/graphlib";
|
|
3
3
|
|
|
4
|
-
//#region src/
|
|
4
|
+
//#region src/planning/compile.d.ts
|
|
5
5
|
type WorkflowPlannerErrorCode = "WORKFLOW_CYCLE" | "WORKFLOW_DUPLICATE_NODE" | "WORKFLOW_GROUP_REFERENCE" | "WORKFLOW_MISSING_DEPENDENCY" | "WORKFLOW_MISSING_WORKFLOW";
|
|
6
6
|
interface WorkflowPlannerIssue {
|
|
7
7
|
message: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { uniqueStrings } from "
|
|
1
|
+
import { uniqueStrings } from "../strings.js";
|
|
2
|
+
import { findDependencyCycles } from "./graph.js";
|
|
2
3
|
import { Graph } from "@dagrejs/graphlib";
|
|
3
|
-
//#region src/
|
|
4
|
+
//#region src/planning/compile.ts
|
|
4
5
|
var WorkflowPlannerError = class extends Error {
|
|
5
6
|
code;
|
|
6
7
|
issues;
|
|
@@ -54,7 +55,7 @@ function validateNodeGraph(workflowId, nodes) {
|
|
|
54
55
|
...groupIssues(workflowId, nodes, nodeIds),
|
|
55
56
|
...dependencyIssues(workflowId, nodes, nodeIds)
|
|
56
57
|
];
|
|
57
|
-
if (duplicateIssues.length === 0) return [...issues, ...cycleIssues(workflowId, nodes
|
|
58
|
+
if (duplicateIssues.length === 0) return [...issues, ...cycleIssues(workflowId, nodes)];
|
|
58
59
|
return issues;
|
|
59
60
|
}
|
|
60
61
|
function duplicateNodeIssues(workflowId, nodes) {
|
|
@@ -100,92 +101,14 @@ function groupChildIssues(workflowId, node, nodeIds) {
|
|
|
100
101
|
function isGroupNode(node) {
|
|
101
102
|
return node.kind === "group";
|
|
102
103
|
}
|
|
103
|
-
function cycleIssues(workflowId, nodes
|
|
104
|
-
return
|
|
104
|
+
function cycleIssues(workflowId, nodes) {
|
|
105
|
+
return findDependencyCycles(nodes).map((cycle) => {
|
|
105
106
|
return {
|
|
106
107
|
path: `workflows.${workflowId}.nodes.${cycle[0] ?? "nodes"}.needs`,
|
|
107
108
|
message: `workflow '${workflowId}' contains dependency cycle: ${cycle.join(" -> ")}`
|
|
108
109
|
};
|
|
109
110
|
});
|
|
110
111
|
}
|
|
111
|
-
function dependencyCycles(nodes, nodeIds) {
|
|
112
|
-
const dependentsByNeed = dependentsByNeedMap(nodes, nodeIds);
|
|
113
|
-
const state = /* @__PURE__ */ new Map();
|
|
114
|
-
const path = [];
|
|
115
|
-
const pathIndex = /* @__PURE__ */ new Map();
|
|
116
|
-
const cycles = [];
|
|
117
|
-
const cycleKeys = /* @__PURE__ */ new Set();
|
|
118
|
-
for (const node of nodes) {
|
|
119
|
-
if (state.has(node.id)) continue;
|
|
120
|
-
visitForCycles(node.id, {
|
|
121
|
-
cycleKeys,
|
|
122
|
-
cycles,
|
|
123
|
-
dependentsByNeed,
|
|
124
|
-
path,
|
|
125
|
-
pathIndex,
|
|
126
|
-
state
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
return cycles;
|
|
130
|
-
}
|
|
131
|
-
function visitForCycles(startId, visitState) {
|
|
132
|
-
const frames = [{
|
|
133
|
-
index: 0,
|
|
134
|
-
nodeId: startId
|
|
135
|
-
}];
|
|
136
|
-
markVisiting(startId, visitState);
|
|
137
|
-
while (frames.length > 0) {
|
|
138
|
-
const frame = frames.at(-1);
|
|
139
|
-
if (!frame) return;
|
|
140
|
-
const dependentId = (visitState.dependentsByNeed.get(frame.nodeId) ?? [])[frame.index];
|
|
141
|
-
if (!dependentId) {
|
|
142
|
-
markDone(frame.nodeId, visitState);
|
|
143
|
-
frames.pop();
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
frame.index += 1;
|
|
147
|
-
const dependentState = visitState.state.get(dependentId);
|
|
148
|
-
if (dependentState === "visiting") {
|
|
149
|
-
recordCycle(dependentId, visitState);
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
if (dependentState === "done") continue;
|
|
153
|
-
markVisiting(dependentId, visitState);
|
|
154
|
-
frames.push({
|
|
155
|
-
index: 0,
|
|
156
|
-
nodeId: dependentId
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
function markVisiting(nodeId, visitState) {
|
|
161
|
-
visitState.state.set(nodeId, "visiting");
|
|
162
|
-
visitState.pathIndex.set(nodeId, visitState.path.length);
|
|
163
|
-
visitState.path.push(nodeId);
|
|
164
|
-
}
|
|
165
|
-
function markDone(nodeId, visitState) {
|
|
166
|
-
visitState.state.set(nodeId, "done");
|
|
167
|
-
visitState.pathIndex.delete(nodeId);
|
|
168
|
-
visitState.path.pop();
|
|
169
|
-
}
|
|
170
|
-
function recordCycle(nodeId, visitState) {
|
|
171
|
-
const startIndex = visitState.pathIndex.get(nodeId);
|
|
172
|
-
if (startIndex === void 0) return;
|
|
173
|
-
const cycle = visitState.path.slice(startIndex);
|
|
174
|
-
const key = [...cycle].sort().join("\0");
|
|
175
|
-
if (visitState.cycleKeys.has(key)) return;
|
|
176
|
-
visitState.cycleKeys.add(key);
|
|
177
|
-
visitState.cycles.push(cycle);
|
|
178
|
-
}
|
|
179
|
-
function dependentsByNeedMap(nodes, nodeIds) {
|
|
180
|
-
const dependentsByNeed = /* @__PURE__ */ new Map();
|
|
181
|
-
for (const node of nodes) for (const need of uniqueStrings(node.needs ?? [])) {
|
|
182
|
-
if (!nodeIds.has(need)) continue;
|
|
183
|
-
const dependents = dependentsByNeed.get(need) ?? [];
|
|
184
|
-
dependents.push(node.id);
|
|
185
|
-
dependentsByNeed.set(need, dependents);
|
|
186
|
-
}
|
|
187
|
-
return dependentsByNeed;
|
|
188
|
-
}
|
|
189
112
|
function topologicalOrderForPlan(graph) {
|
|
190
113
|
const visited = /* @__PURE__ */ new Set();
|
|
191
114
|
const inStack = /* @__PURE__ */ new Set();
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { PipelineConfig } from "../config/schemas.js";
|
|
2
|
-
import { WorkflowExecutionPlan } from "
|
|
2
|
+
import { WorkflowExecutionPlan } from "./compile.js";
|
|
3
3
|
import { AgentResult, RunnerExecutionOptions, RunnerLaunchPlan } from "../runner.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
|
-
//#region src/
|
|
6
|
+
//#region src/planning/generate.d.ts
|
|
7
7
|
declare const scheduleArtifactSchema: z.ZodObject<{
|
|
8
8
|
generated_at: z.ZodString;
|
|
9
9
|
kind: z.ZodLiteral<"pipeline-schedule">;
|
|
@@ -1575,6 +1575,20 @@ interface GenerateScheduleResult {
|
|
|
1575
1575
|
artifact: ScheduleArtifact;
|
|
1576
1576
|
path: string;
|
|
1577
1577
|
}
|
|
1578
|
+
interface BacklogWorkUnit {
|
|
1579
|
+
acceptance_criteria: Array<{
|
|
1580
|
+
id: string;
|
|
1581
|
+
text: string;
|
|
1582
|
+
}>;
|
|
1583
|
+
dependencies?: string[];
|
|
1584
|
+
description?: string;
|
|
1585
|
+
id: string;
|
|
1586
|
+
title?: string;
|
|
1587
|
+
}
|
|
1588
|
+
interface SchedulePlanningContext {
|
|
1589
|
+
parentWorkUnits: BacklogWorkUnit[];
|
|
1590
|
+
workUnits: BacklogWorkUnit[];
|
|
1591
|
+
}
|
|
1578
1592
|
declare function parseScheduleArtifact(source: string, sourcePath?: string): ScheduleArtifact;
|
|
1579
1593
|
declare class ScheduleArtifactError extends Error {
|
|
1580
1594
|
constructor(message: string);
|
|
@@ -1583,4 +1597,4 @@ declare function compileScheduleArtifact(config: PipelineConfig, artifact: Sched
|
|
|
1583
1597
|
declare function generateScheduleArtifact(options: GenerateScheduleOptions): Promise<GenerateScheduleResult>;
|
|
1584
1598
|
declare function scheduleArtifactPath(worktreePath: string, scheduleId: string): string;
|
|
1585
1599
|
//#endregion
|
|
1586
|
-
export { CompiledScheduleArtifact, GenerateScheduleOptions, GenerateScheduleResult, ScheduleArtifact, ScheduleArtifactError, compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact, scheduleArtifactPath };
|
|
1600
|
+
export { BacklogWorkUnit, CompiledScheduleArtifact, GenerateScheduleOptions, GenerateScheduleResult, ScheduleArtifact, ScheduleArtifactError, SchedulePlanningContext, compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact, scheduleArtifactPath };
|