@oisincoveney/pipeline 2.2.0 → 2.3.1
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/critique/SKILL.md +8 -6
- package/.agents/skills/diagnose/SKILL.md +2 -0
- package/.agents/skills/doubt/SKILL.md +3 -3
- package/.agents/skills/execute/SKILL.md +21 -0
- package/.agents/skills/fix/SKILL.md +5 -1
- package/.agents/skills/grill/SKILL.md +3 -1
- package/.agents/skills/improve/SKILL.md +2 -0
- package/.agents/skills/inspect/SKILL.md +2 -0
- package/.agents/skills/library-first-development/SKILL.md +17 -2
- package/.agents/skills/migrate/SKILL.md +2 -0
- package/.agents/skills/optimize/SKILL.md +2 -0
- package/.agents/skills/orchestrate/SKILL.md +83 -0
- package/.agents/skills/quality-gate/SKILL.md +2 -0
- package/.agents/skills/quick/SKILL.md +2 -0
- package/.agents/skills/research/SKILL.md +18 -6
- package/.agents/skills/schedule-graph-shaping/SKILL.md +8 -0
- package/.agents/skills/scope/SKILL.md +13 -2
- package/.agents/skills/secure/SKILL.md +1 -1
- package/.agents/skills/spec/SKILL.md +2 -0
- package/.agents/skills/test/SKILL.md +2 -0
- package/.agents/skills/trace/SKILL.md +4 -0
- package/.agents/skills/verify/SKILL.md +18 -2
- 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 +22 -11
- package/dist/moka-global-config.d.ts +0 -1
- package/dist/moka-global-config.js +0 -1
- package/dist/moka-submit.d.ts +7 -10
- 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-event-schema.d.ts +6 -6
- 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
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";
|
|
@@ -93,12 +93,13 @@ function nativeAgentIdForHost(host, profileId) {
|
|
|
93
93
|
return host === "opencode" ? opencodeAgentName(profileId) : profileId;
|
|
94
94
|
}
|
|
95
95
|
function grants(actor) {
|
|
96
|
+
const listGrant = (values) => (values ?? []).join(", ") || "none";
|
|
96
97
|
return [
|
|
97
98
|
`model: ${actor.model ?? "default"}`,
|
|
98
|
-
`tools: ${(actor.tools
|
|
99
|
-
`rules: ${(actor.rules
|
|
100
|
-
`skills: ${(actor.skills
|
|
101
|
-
`mcp_servers: ${(actor.mcp_servers
|
|
99
|
+
`tools: ${listGrant(actor.tools)}`,
|
|
100
|
+
`rules: ${listGrant(actor.rules)}`,
|
|
101
|
+
`skills: ${listGrant(actor.skills)}`,
|
|
102
|
+
`mcp_servers: ${listGrant(actor.mcp_servers)}`,
|
|
102
103
|
`filesystem: ${actor.filesystem?.mode ?? "default"}`,
|
|
103
104
|
`network: ${actor.network?.mode ?? "default"}`,
|
|
104
105
|
..."output" in actor ? [`output: ${actor.output?.format ?? "text"}`] : []
|
|
@@ -149,10 +150,19 @@ function scheduledEntrypointK8sNote(entrypoint) {
|
|
|
149
150
|
if ("workflow" in entrypoint) return;
|
|
150
151
|
return "Submit Momokaya work as Argo Workflows through `moka submit` and `moka submit --quick`.";
|
|
151
152
|
}
|
|
152
|
-
function
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
function localRosterAgentIds(config) {
|
|
154
|
+
return nativeProfileEntries("opencode", config).map(([id]) => nativeAgentIdForHost("opencode", id));
|
|
155
|
+
}
|
|
156
|
+
function localOrchestratorDispatchBlock(config) {
|
|
157
|
+
return [
|
|
158
|
+
"Orchestrate locally. Load and follow the `orchestrate` skill.",
|
|
159
|
+
"Do not submit to Argo or run `moka submit`. Spawn the roster as native Task subagents on this machine and run nodes with satisfied dependencies in parallel.",
|
|
160
|
+
"",
|
|
161
|
+
"Roster (Task tool subagent_type):",
|
|
162
|
+
...localRosterAgentIds(config).map((id) => `- ${id}`),
|
|
163
|
+
"",
|
|
164
|
+
"Gather each subagent's structured output, enforce only package-configured gates, and report only the evidence the subagents returned."
|
|
165
|
+
].join("\n");
|
|
156
166
|
}
|
|
157
167
|
function nativeDispatchBlock(host, routes) {
|
|
158
168
|
if (routes.length === 0) return;
|
|
@@ -325,13 +335,13 @@ function opencodeDefinitions(config, cwd) {
|
|
|
325
335
|
description: "Orchestrate the configured pipeline and enforce gates.",
|
|
326
336
|
mode: "primary",
|
|
327
337
|
name: OPENCODE_ORCHESTRATOR_AGENT_ID,
|
|
328
|
-
permission: opencodePermission(orchestrator, { allowedTaskAgents:
|
|
338
|
+
permission: opencodePermission(orchestrator, { allowedTaskAgents: localRosterAgentIds(config) })
|
|
329
339
|
}, compactLines([
|
|
330
340
|
header("opencode").trimEnd(),
|
|
331
341
|
"",
|
|
332
342
|
orchestratorBlock(config),
|
|
333
343
|
"",
|
|
334
|
-
|
|
344
|
+
localOrchestratorDispatchBlock(config)
|
|
335
345
|
]).join("\n")),
|
|
336
346
|
host: "opencode",
|
|
337
347
|
invocation: invocationForHost("opencode"),
|
|
@@ -382,6 +392,7 @@ function projectAgentsMdDefinition(cwd, host) {
|
|
|
382
392
|
"- Use `/moka-quick`, `/moka-execute`, or `/moka-inspect` for OpenCode slash-command entrypoints when available.",
|
|
383
393
|
"- Load and follow the relevant skill from `.agents/skills` before doing specialized work.",
|
|
384
394
|
"- Prefer the package-defined pipeline profiles and generated command surfaces over ad hoc subagent prompts.",
|
|
395
|
+
"- When the user needs to run a command, copy the command into the clipboard and tell the user what needs to be returned.",
|
|
385
396
|
"",
|
|
386
397
|
"## Pipeline Memory",
|
|
387
398
|
"",
|
|
@@ -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,17 +1,17 @@
|
|
|
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
|
|
6
6
|
declare const mokaSubmitDirectHooksSchema: z.ZodRecord<z.ZodEnum<{
|
|
7
7
|
"workflow.start": "workflow.start";
|
|
8
|
-
"node.finish": "node.finish";
|
|
9
|
-
"node.start": "node.start";
|
|
10
8
|
"workflow.success": "workflow.success";
|
|
11
9
|
"workflow.failure": "workflow.failure";
|
|
12
10
|
"workflow.complete": "workflow.complete";
|
|
11
|
+
"node.start": "node.start";
|
|
13
12
|
"node.success": "node.success";
|
|
14
13
|
"node.error": "node.error";
|
|
14
|
+
"node.finish": "node.finish";
|
|
15
15
|
"gate.failure": "gate.failure";
|
|
16
16
|
}> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
17
17
|
failure: z.ZodDefault<z.ZodEnum<{
|
|
@@ -94,13 +94,13 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
94
94
|
}, z.core.$strict>>;
|
|
95
95
|
hooks: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
|
|
96
96
|
"workflow.start": "workflow.start";
|
|
97
|
-
"node.finish": "node.finish";
|
|
98
|
-
"node.start": "node.start";
|
|
99
97
|
"workflow.success": "workflow.success";
|
|
100
98
|
"workflow.failure": "workflow.failure";
|
|
101
99
|
"workflow.complete": "workflow.complete";
|
|
100
|
+
"node.start": "node.start";
|
|
102
101
|
"node.success": "node.success";
|
|
103
102
|
"node.error": "node.error";
|
|
103
|
+
"node.finish": "node.finish";
|
|
104
104
|
"gate.failure": "gate.failure";
|
|
105
105
|
}> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
106
106
|
failure: z.ZodDefault<z.ZodEnum<{
|
|
@@ -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>;
|
|
@@ -207,13 +206,13 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
207
206
|
}, z.core.$strict>>;
|
|
208
207
|
hooks: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
|
|
209
208
|
"workflow.start": "workflow.start";
|
|
210
|
-
"node.finish": "node.finish";
|
|
211
|
-
"node.start": "node.start";
|
|
212
209
|
"workflow.success": "workflow.success";
|
|
213
210
|
"workflow.failure": "workflow.failure";
|
|
214
211
|
"workflow.complete": "workflow.complete";
|
|
212
|
+
"node.start": "node.start";
|
|
215
213
|
"node.success": "node.success";
|
|
216
214
|
"node.error": "node.error";
|
|
215
|
+
"node.finish": "node.finish";
|
|
217
216
|
"gate.failure": "gate.failure";
|
|
218
217
|
}> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
219
218
|
failure: z.ZodDefault<z.ZodEnum<{
|
|
@@ -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 };
|
|
@@ -3,20 +3,22 @@ import { validatePipelineConfig } from "../config/validate.js";
|
|
|
3
3
|
import "../config.js";
|
|
4
4
|
import { createRunnerLaunchPlan, runLaunchPlan } from "../runner.js";
|
|
5
5
|
import { normalizeRunnerOutput } from "../runner-output.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
6
|
+
import { loadBacklogPlanningContext } from "../schedule/backlog-context.js";
|
|
7
|
+
import { baselineScheduleArtifact } from "../schedule/baseline.js";
|
|
8
|
+
import { dependentsByNeed, flattenNodes, hasReachableDependent } from "./graph.js";
|
|
9
|
+
import { isCoverageNode, isImplementationNode } from "../schedule/scheduling-roles.js";
|
|
10
|
+
import { addGeneratedImplementationCoverage } from "../schedule/passes/coverage.js";
|
|
11
|
+
import { canonicalizeGeneratedScheduleIds } from "../schedule/passes/ids.js";
|
|
12
|
+
import { SCHEDULE_PASS_ORDER } from "../schedule/passes/index.js";
|
|
13
|
+
import { applyNodeCatalogModelFallbacks } from "../schedule/passes/models.js";
|
|
14
|
+
import { namespaceScheduleWorkflows } from "../schedule/passes/references.js";
|
|
15
|
+
import { plannerPrompt, plannerRepairPrompt } from "../schedule/prompts.js";
|
|
16
|
+
import { compileWorkflowPlan } from "./compile.js";
|
|
15
17
|
import { parseDocument, stringify } from "yaml";
|
|
16
18
|
import { z } from "zod";
|
|
17
19
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
18
20
|
import { join } from "node:path";
|
|
19
|
-
//#region src/
|
|
21
|
+
//#region src/planning/generate.ts
|
|
20
22
|
const SCHEDULE_KIND = "pipeline-schedule";
|
|
21
23
|
const ID_RE = /^[a-z][a-z0-9-]*$/;
|
|
22
24
|
const SCHEDULE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
|
|
@@ -253,9 +255,9 @@ function unsafeParallelWorktreeIssues(config, artifact) {
|
|
|
253
255
|
}
|
|
254
256
|
function workflowNodeIssues(artifact, collectIssues) {
|
|
255
257
|
return Object.entries(artifact.workflows).flatMap(([workflowId, workflow]) => {
|
|
256
|
-
const nodes = workflow.nodes
|
|
258
|
+
const nodes = flattenWorkflowNodes(workflow.nodes);
|
|
257
259
|
return collectIssues({
|
|
258
|
-
dependentsByNeed:
|
|
260
|
+
dependentsByNeed: dependentsByNeed(nodes),
|
|
259
261
|
nodes,
|
|
260
262
|
workflowId
|
|
261
263
|
});
|
|
@@ -267,20 +269,8 @@ function isWriteCapableParallelChild(config, node) {
|
|
|
267
269
|
if (node.kind === "parallel") return node.nodes.some((child) => isWriteCapableParallelChild(config, child));
|
|
268
270
|
return false;
|
|
269
271
|
}
|
|
270
|
-
function hasDownstreamDrainMerge(nodeId,
|
|
271
|
-
return hasReachableDependent(nodeId,
|
|
272
|
-
}
|
|
273
|
-
function hasReachableDependent(nodeId, dependentsByNeed, matches) {
|
|
274
|
-
const queue = [...dependentsByNeed.get(nodeId) ?? []];
|
|
275
|
-
const seen = /* @__PURE__ */ new Set();
|
|
276
|
-
while (queue.length > 0) {
|
|
277
|
-
const node = queue.shift();
|
|
278
|
-
if (!node || seen.has(node.id)) continue;
|
|
279
|
-
seen.add(node.id);
|
|
280
|
-
if (matches(node)) return true;
|
|
281
|
-
queue.push(...dependentsByNeed.get(node.id) ?? []);
|
|
282
|
-
}
|
|
283
|
-
return false;
|
|
272
|
+
function hasDownstreamDrainMerge(nodeId, index) {
|
|
273
|
+
return hasReachableDependent(nodeId, index, (node) => node.kind === "builtin" && node.builtin === "drain-merge");
|
|
284
274
|
}
|
|
285
275
|
function generatedRootWorkflowIssues(artifact) {
|
|
286
276
|
const workflowIds = Object.keys(artifact.workflows);
|
|
@@ -331,15 +321,15 @@ function workUnitDependencyIssues(config, artifact, workUnits) {
|
|
|
331
321
|
const workUnitIds = new Set(workUnits.map((unit) => unit.id));
|
|
332
322
|
const dependenciesByUnit = new Map(workUnits.map((unit) => [unit.id, (unit.dependencies ?? []).filter((id) => workUnitIds.has(id))]));
|
|
333
323
|
return Object.entries(artifact.workflows).flatMap(([workflowId, workflow]) => {
|
|
334
|
-
const nodes = workflow.nodes
|
|
335
|
-
const
|
|
324
|
+
const nodes = flattenWorkflowNodes(workflow.nodes);
|
|
325
|
+
const index = dependentsByNeed(nodes);
|
|
336
326
|
const nodesByWorkUnit = nodesByAssignedWorkUnit(nodes);
|
|
337
327
|
return nodes.filter((node) => isImplementationNode(config, node)).flatMap((node) => {
|
|
338
328
|
const dependentId = node.task_context?.id;
|
|
339
329
|
if (!dependentId) return [];
|
|
340
330
|
return (dependenciesByUnit.get(dependentId) ?? []).flatMap((prerequisiteId) => {
|
|
341
331
|
const prerequisiteNodes = nodesByWorkUnit.get(prerequisiteId) ?? [];
|
|
342
|
-
return prerequisiteNodes.some((source) =>
|
|
332
|
+
return prerequisiteNodes.some((source) => hasReachableDependent(source.id, index, (candidate) => candidate.id === node.id)) ? [] : [`work unit dependency edge missing in '${workflowId}': '${dependentId}' node '${node.id}' must depend on prerequisite '${prerequisiteId}' nodes ${prerequisiteNodes.map((prerequisite) => `'${prerequisite.id}'`).join(", ")}`];
|
|
343
333
|
});
|
|
344
334
|
});
|
|
345
335
|
});
|
|
@@ -355,9 +345,6 @@ function nodesByAssignedWorkUnit(nodes) {
|
|
|
355
345
|
}
|
|
356
346
|
return grouped;
|
|
357
347
|
}
|
|
358
|
-
function hasPathToNode(sourceId, targetId, dependentsByNeed) {
|
|
359
|
-
return hasReachableDependent(sourceId, dependentsByNeed, (node) => node.id === targetId);
|
|
360
|
-
}
|
|
361
348
|
function unsupportedGeneratedBuiltinIssues(artifact) {
|
|
362
349
|
const allowed = new Set(SCHEDULE_BUILTINS);
|
|
363
350
|
return allWorkflowNodes(artifact.workflows).flatMap((node) => {
|
|
@@ -369,33 +356,14 @@ function unsupportedGeneratedBuiltinIssues(artifact) {
|
|
|
369
356
|
function implementationCoverageIssues(config, artifact) {
|
|
370
357
|
return workflowNodeIssues(artifact, ({ dependentsByNeed, nodes, workflowId }) => nodes.filter((node) => isImplementationNode(config, node)).filter((node) => !hasDownstreamCoverage(config, node.id, dependentsByNeed)).map((node) => `implementation node '${workflowId}.${node.id}' is without downstream verification or review`));
|
|
371
358
|
}
|
|
372
|
-
function
|
|
373
|
-
return
|
|
374
|
-
}
|
|
375
|
-
function workflowDependentsByNeed(nodes) {
|
|
376
|
-
const dependentsByNeed = /* @__PURE__ */ new Map();
|
|
377
|
-
for (const node of nodes) for (const need of node.needs ?? []) {
|
|
378
|
-
const dependents = dependentsByNeed.get(need) ?? [];
|
|
379
|
-
dependents.push(node);
|
|
380
|
-
dependentsByNeed.set(need, dependents);
|
|
381
|
-
}
|
|
382
|
-
return dependentsByNeed;
|
|
383
|
-
}
|
|
384
|
-
function hasDownstreamCoverage(config, nodeId, dependentsByNeed) {
|
|
385
|
-
return hasReachableDependent(nodeId, dependentsByNeed, (node) => isCoverageNode(config, node));
|
|
386
|
-
}
|
|
387
|
-
function isCoverageNode(config, node) {
|
|
388
|
-
return hasSchedulingRole(config, node, "coverage");
|
|
389
|
-
}
|
|
390
|
-
function hasSchedulingRole(config, node, role) {
|
|
391
|
-
if (node.kind !== "agent") return false;
|
|
392
|
-
return config.profiles[node.profile]?.scheduling_roles?.includes(role) ?? false;
|
|
359
|
+
function hasDownstreamCoverage(config, nodeId, index) {
|
|
360
|
+
return hasReachableDependent(nodeId, index, (node) => isCoverageNode(config, node));
|
|
393
361
|
}
|
|
394
362
|
function allWorkflowNodes(workflows) {
|
|
395
|
-
return Object.values(workflows).flatMap((workflow) => workflow.nodes
|
|
363
|
+
return Object.values(workflows).flatMap((workflow) => flattenWorkflowNodes(workflow.nodes));
|
|
396
364
|
}
|
|
397
|
-
function
|
|
398
|
-
return node.kind === "parallel" ?
|
|
365
|
+
function flattenWorkflowNodes(nodes) {
|
|
366
|
+
return flattenNodes(nodes, (node) => node.kind === "parallel" ? node.nodes : void 0);
|
|
399
367
|
}
|
|
400
368
|
//#endregion
|
|
401
369
|
export { ScheduleArtifactError, compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact, scheduleArtifactPath };
|