@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.
Files changed (60) hide show
  1. package/.agents/skills/critique/SKILL.md +8 -6
  2. package/.agents/skills/diagnose/SKILL.md +2 -0
  3. package/.agents/skills/doubt/SKILL.md +3 -3
  4. package/.agents/skills/execute/SKILL.md +21 -0
  5. package/.agents/skills/fix/SKILL.md +5 -1
  6. package/.agents/skills/grill/SKILL.md +3 -1
  7. package/.agents/skills/improve/SKILL.md +2 -0
  8. package/.agents/skills/inspect/SKILL.md +2 -0
  9. package/.agents/skills/library-first-development/SKILL.md +17 -2
  10. package/.agents/skills/migrate/SKILL.md +2 -0
  11. package/.agents/skills/optimize/SKILL.md +2 -0
  12. package/.agents/skills/orchestrate/SKILL.md +83 -0
  13. package/.agents/skills/quality-gate/SKILL.md +2 -0
  14. package/.agents/skills/quick/SKILL.md +2 -0
  15. package/.agents/skills/research/SKILL.md +18 -6
  16. package/.agents/skills/schedule-graph-shaping/SKILL.md +8 -0
  17. package/.agents/skills/scope/SKILL.md +13 -2
  18. package/.agents/skills/secure/SKILL.md +1 -1
  19. package/.agents/skills/spec/SKILL.md +2 -0
  20. package/.agents/skills/test/SKILL.md +2 -0
  21. package/.agents/skills/trace/SKILL.md +4 -0
  22. package/.agents/skills/verify/SKILL.md +18 -2
  23. package/defaults/profiles.yaml +5 -2
  24. package/dist/argo-submit.d.ts +0 -1
  25. package/dist/argo-submit.js +1 -4
  26. package/dist/argo-workflow.d.ts +1 -2
  27. package/dist/argo-workflow.js +0 -2
  28. package/dist/cli/program.js +2 -3
  29. package/dist/cli/submit-options.js +1 -2
  30. package/dist/cluster-doctor.js +0 -12
  31. package/dist/commands/pipeline-command.js +1 -1
  32. package/dist/config/schemas.d.ts +4 -4
  33. package/dist/install-commands/opencode.js +22 -11
  34. package/dist/moka-global-config.d.ts +0 -1
  35. package/dist/moka-global-config.js +0 -1
  36. package/dist/moka-submit.d.ts +7 -10
  37. package/dist/moka-submit.js +1 -4
  38. package/dist/pipeline-runtime.d.ts +9 -0
  39. package/dist/planned-node.js +2 -5
  40. package/dist/{workflow-planner.d.ts → planning/compile.d.ts} +2 -2
  41. package/dist/{workflow-planner.js → planning/compile.js} +6 -83
  42. package/dist/{schedule/planner.d.ts → planning/generate.d.ts} +17 -3
  43. package/dist/{schedule/planner.js → planning/generate.js} +24 -56
  44. package/dist/planning/graph.js +138 -0
  45. package/dist/runner-command/lifecycle-context.js +2 -3
  46. package/dist/runner-command/run.js +2 -3
  47. package/dist/runner-event-schema.d.ts +6 -6
  48. package/dist/runner.d.ts +27 -0
  49. package/dist/runtime/context/context.js +1 -1
  50. package/dist/runtime/contracts/contracts.d.ts +16 -1
  51. package/dist/schedule/passes/coverage.js +7 -51
  52. package/dist/schedule/passes/ids.js +3 -23
  53. package/dist/schedule/scheduling-roles.js +19 -0
  54. package/dist/strings.js +30 -1
  55. package/docs/config-architecture.md +32 -0
  56. package/docs/operator-guide.md +2 -3
  57. package/docs/pipeline-console-runner-contract.md +3 -4
  58. package/package.json +5 -5
  59. package/dist/schedule-planner.d.ts +0 -2
  60. package/dist/schedule-planner.js +0 -2
@@ -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 "../workflow-planner.js";
6
- import { compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact } from "../schedule/planner.js";
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("--queue-name <name>", "Kueue LocalQueue label for Workflow pods").option("--service-account <name>", "Workflow service account").option("--image <image>", "runner image").addOption(new Option("--image-pull-policy <policy>", "runner image pull policy").choices([
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"
@@ -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("--queue-name <name>", "Kueue LocalQueue label for Workflow pods").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");
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
  });
@@ -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 "../workflow-planner.js";
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 ?? []).join(", ") || "none"}`,
99
- `rules: ${(actor.rules ?? []).join(", ") || "none"}`,
100
- `skills: ${(actor.skills ?? []).join(", ") || "none"}`,
101
- `mcp_servers: ${(actor.mcp_servers ?? []).join(", ") || "none"}`,
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 orchestratorEntrypointDispatchBlock(host, config) {
153
- const scheduledEntrypoints = entrypointEntries(config).filter(([, entrypoint]) => !("workflow" in entrypoint));
154
- if (scheduledEntrypoints.length === 0) return dispatchBlock(host, config);
155
- return scheduledEntrypoints.map(([id, entrypoint]) => entrypointDispatchBlock(host, config, id, entrypoint)).filter((block) => Boolean(block)).join("\n\n");
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: agentDispatchRoutes("opencode", config).filter((route) => route.kind !== "cli").map((route) => route.nativeAgentId).filter((id) => Boolean(id)) })
338
+ permission: opencodePermission(orchestrator, { allowedTaskAgents: localRosterAgentIds(config) })
329
339
  }, compactLines([
330
340
  header("opencode").trimEnd(),
331
341
  "",
332
342
  orchestratorBlock(config),
333
343
  "",
334
- orchestratorEntrypointDispatchBlock("opencode", config)
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({
@@ -1,17 +1,17 @@
1
1
  import { PipelineConfig } from "./config/schemas.js";
2
- import { generateScheduleArtifact } from "./schedule/planner.js";
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
  }
@@ -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;
@@ -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
- for (const node of nodes) {
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 "./config/schemas.js";
1
+ import { PipelineConfig, WorkflowNodeKind } from "../config/schemas.js";
2
2
  import { Graph } from "@dagrejs/graphlib";
3
3
 
4
- //#region src/workflow-planner.d.ts
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 "./strings.js";
1
+ import { uniqueStrings } from "../strings.js";
2
+ import { findDependencyCycles } from "./graph.js";
2
3
  import { Graph } from "@dagrejs/graphlib";
3
- //#region src/workflow-planner.ts
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, nodeIds)];
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, nodeIds) {
104
- return dependencyCycles(nodes, nodeIds).map((cycle) => {
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 "../workflow-planner.js";
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/schedule/planner.d.ts
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 { compileWorkflowPlan } from "../workflow-planner.js";
7
- import { loadBacklogPlanningContext } from "./backlog-context.js";
8
- import { baselineScheduleArtifact } from "./baseline.js";
9
- import { addGeneratedImplementationCoverage } from "./passes/coverage.js";
10
- import { canonicalizeGeneratedScheduleIds } from "./passes/ids.js";
11
- import { SCHEDULE_PASS_ORDER } from "./passes/index.js";
12
- import { applyNodeCatalogModelFallbacks } from "./passes/models.js";
13
- import { namespaceScheduleWorkflows } from "./passes/references.js";
14
- import { plannerPrompt, plannerRepairPrompt } from "./prompts.js";
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/schedule/planner.ts
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.flatMap(flattenWorkflowNode);
258
+ const nodes = flattenWorkflowNodes(workflow.nodes);
257
259
  return collectIssues({
258
- dependentsByNeed: workflowDependentsByNeed(nodes),
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, dependentsByNeed) {
271
- return hasReachableDependent(nodeId, dependentsByNeed, (node) => node.kind === "builtin" && node.builtin === "drain-merge");
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.flatMap(flattenWorkflowNode);
335
- const dependentsByNeed = workflowDependentsByNeed(nodes);
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) => hasPathToNode(source.id, node.id, dependentsByNeed)) ? [] : [`work unit dependency edge missing in '${workflowId}': '${dependentId}' node '${node.id}' must depend on prerequisite '${prerequisiteId}' nodes ${prerequisiteNodes.map((prerequisite) => `'${prerequisite.id}'`).join(", ")}`];
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 isImplementationNode(config, node) {
373
- return hasSchedulingRole(config, node, "implementation");
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.flatMap(flattenWorkflowNode));
363
+ return Object.values(workflows).flatMap((workflow) => flattenWorkflowNodes(workflow.nodes));
396
364
  }
397
- function flattenWorkflowNode(node) {
398
- return node.kind === "parallel" ? [node, ...node.nodes.flatMap(flattenWorkflowNode)] : [node];
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 };