@oisincoveney/pipeline 1.15.3 → 1.16.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/dist/config.d.ts CHANGED
@@ -9,6 +9,7 @@ declare const NODE_KINDS: readonly ["agent", "command", "builtin", "group", "par
9
9
  declare const HOOK_EVENTS: readonly ["workflow.start", "workflow.success", "workflow.failure", "workflow.complete", "node.start", "node.success", "node.error", "node.finish", "gate.failure"];
10
10
  declare const GATE_KINDS: readonly ["acceptance", "artifact", "builtin", "changed_files", "command", "json_schema", "verdict"];
11
11
  declare const SCHEDULE_BASELINES: readonly ["epic", "pipe"];
12
+ declare const SCHEDULING_ROLES: readonly ["coverage", "implementation"];
12
13
  type PipelineConfigErrorCode = "PIPELINE_CONFIG_LEGACY_UNSUPPORTED" | "PIPELINE_CONFIG_MISSING" | "PIPELINE_CONFIG_PARSE_ERROR" | "PIPELINE_CONFIG_VALIDATION_ERROR";
13
14
  interface PipelineConfigIssue {
14
15
  message: string;
@@ -291,6 +292,10 @@ declare const configSchema: z.ZodObject<{
291
292
  }, z.core.$strict>>;
292
293
  rules: z.ZodOptional<z.ZodArray<z.ZodString>>;
293
294
  runner: z.ZodString;
295
+ scheduling_roles: z.ZodOptional<z.ZodArray<z.ZodEnum<{
296
+ coverage: "coverage";
297
+ implementation: "implementation";
298
+ }>>>;
294
299
  skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
295
300
  tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
296
301
  read: "read";
@@ -379,6 +384,7 @@ type WorkflowNodeKind = (typeof NODE_KINDS)[number];
379
384
  type HookEvent = (typeof HOOK_EVENTS)[number];
380
385
  type GateKind = (typeof GATE_KINDS)[number];
381
386
  type ScheduleBaseline = (typeof SCHEDULE_BASELINES)[number];
387
+ type SchedulingRole = (typeof SCHEDULING_ROLES)[number];
382
388
  interface PipelineConfigParts {
383
389
  pipeline: string;
384
390
  profiles: string;
@@ -393,4 +399,4 @@ declare function parsePipelineConfigYaml(source: string, sourcePath?: string, pr
393
399
  declare function parsePipelineConfigParts(sources: PipelineConfigParts, projectRoot?: string, sourcePaths?: PipelineConfigParts, options?: PipelineConfigValidationOptions): PipelineConfig;
394
400
  declare function validatePipelineConfig(rawConfig: PipelineConfig, projectRoot?: string, options?: PipelineConfigValidationOptions): PipelineConfig;
395
401
  //#endregion
396
- export { GateKind, HookEvent, PIPELINE_CONFIG_PATH, PROFILES_CONFIG_PATH, PipelineConfig, PipelineConfigError, PipelineConfigErrorCode, PipelineConfigIssue, PipelineConfigParts, PipelineConfigValidationOptions, RUNNERS_CONFIG_PATH, RunnerType, ScheduleBaseline, WorkflowNodeKind, loadPipelineConfig, parsePipelineConfigParts, parsePipelineConfigYaml, tryLoadPipelineConfig, validatePipelineConfig, workflowSchema };
402
+ export { GateKind, HookEvent, PIPELINE_CONFIG_PATH, PROFILES_CONFIG_PATH, PipelineConfig, PipelineConfigError, PipelineConfigErrorCode, PipelineConfigIssue, PipelineConfigParts, PipelineConfigValidationOptions, RUNNERS_CONFIG_PATH, RunnerType, ScheduleBaseline, SchedulingRole, WorkflowNodeKind, loadPipelineConfig, parsePipelineConfigParts, parsePipelineConfigYaml, tryLoadPipelineConfig, validatePipelineConfig, workflowSchema };
package/dist/config.js CHANGED
@@ -55,6 +55,7 @@ const RETRY_REASONS = [
55
55
  "timeout"
56
56
  ];
57
57
  const SCHEDULE_BASELINES = ["epic", "pipe"];
58
+ const SCHEDULING_ROLES = ["coverage", "implementation"];
58
59
  const PIPELINE_GATEWAY_SERVER_ID = "pipeline-gateway";
59
60
  var PipelineConfigError = class extends Error {
60
61
  code;
@@ -227,6 +228,7 @@ const profileSchema = z.object({
227
228
  output: outputSchema.optional(),
228
229
  rules: z.array(z.string()).optional(),
229
230
  runner: z.string(),
231
+ scheduling_roles: z.array(z.enum(SCHEDULING_ROLES)).optional(),
230
232
  skills: z.array(z.string()).optional(),
231
233
  tools: z.array(z.enum(TOOL_NAMES)).optional()
232
234
  }).strict();
@@ -462,6 +462,7 @@ profiles:
462
462
  max_attempts: 1
463
463
  pipeline-code-writer:
464
464
  runner: codex
465
+ scheduling_roles: [implementation]
465
466
  description: Implement production code until the failing tests pass.
466
467
  instructions:
467
468
  path: .pipeline/prompts/code-writer.md
@@ -479,6 +480,7 @@ profiles:
479
480
  format: text
480
481
  pipeline-acceptance-reviewer:
481
482
  runner: codex
483
+ scheduling_roles: [coverage]
482
484
  description: Audit the finished change against every acceptance criterion.
483
485
  instructions:
484
486
  path: .pipeline/prompts/acceptance-reviewer.md
@@ -500,6 +502,7 @@ profiles:
500
502
  max_attempts: 1
501
503
  pipeline-thermo-nuclear-reviewer:
502
504
  runner: codex
505
+ scheduling_roles: [coverage]
503
506
  description: Perform the final thermo-nuclear code quality review of the integration branch.
504
507
  instructions:
505
508
  path: .agents/skills/critique/SKILL.md
@@ -520,6 +523,7 @@ profiles:
520
523
  max_attempts: 1
521
524
  pipeline-verifier:
522
525
  runner: codex
526
+ scheduling_roles: [coverage]
523
527
  description: Verify checks, implementation fit, and final evidence.
524
528
  instructions:
525
529
  path: .pipeline/prompts/verifier.md
@@ -720,7 +724,7 @@ const SCAFFOLD_FILES = {
720
724
  "",
721
725
  "Generate exactly one workflow named `root`. Do not embed `default`, `epic-drain`, `infra`, `track`, or other configured workflow copies. Use explicit generated agent, builtin, command, parallel, or group nodes. Do not use `kind: workflow`.",
722
726
  "",
723
- "Use the provided backlog work units as the source of truth when present. Assign each work unit to explicit generated agent nodes with only its `task_context.id`, use only allowed configured profiles, and ensure implementation work has downstream acceptance, verification, or review coverage. Do not invent profiles or node-level skill overrides.",
727
+ "Use the provided backlog work units as the source of truth when present. Assign each work unit to explicit generated agent nodes with only its `task_context.id`, use only allowed configured profiles, and ensure profiles with the `implementation` scheduling role have downstream profiles with the `coverage` scheduling role. Do not invent profiles or node-level skill overrides.",
724
728
  "Do not copy backlog descriptions or acceptance criteria into output; the scheduler hydrates them from the assigned `task_context.id` after parsing.",
725
729
  "Preserve Backlog dependency ids as schedule needs edges. A node assigned a dependent work unit must depend on the nodes assigned its prerequisite work units, directly or through an explicit path.",
726
730
  "",
@@ -376,7 +376,7 @@ function plannerPrompt(entrypointId, task, baseline, config, planningContext) {
376
376
  "Every agent node must declare one configured profile id. Do not invent profile ids or node-level skill overrides.",
377
377
  "Assign each backlog work unit to explicit generated agent nodes with task_context.id. The scheduler hydrates title, description, and acceptance_criteria after parsing.",
378
378
  "Do not copy backlog descriptions or acceptance criteria into task_context output.",
379
- "Implementation work must have downstream acceptance, verification, or review coverage in the generated DAG.",
379
+ "Profiles with the implementation scheduling role must have downstream profiles with the coverage scheduling role in the generated DAG.",
380
380
  "Preserve Backlog dependency ids as schedule needs edges. A node assigned a dependent work unit must depend on the nodes assigned its prerequisite work units, directly or through an explicit path.",
381
381
  "Shape the graph by intent, not by ticket count. Do not create a full RED/GREEN/ACCEPTANCE/VERIFY chain for each backlog ticket unless each step needs ticket-specific evidence.",
382
382
  "Only add needs edges for real dependencies, shared constraints, or verification/review fan-in.",
@@ -385,7 +385,7 @@ function plannerPrompt(entrypointId, task, baseline, config, planningContext) {
385
385
  "Only serialize ticket nodes when the backlog, a shared migration/schema/API dependency, or implementation risk requires it.",
386
386
  "",
387
387
  "Allowed profiles:",
388
- ...Object.keys(config.profiles).sort().map((id) => `- ${id}`),
388
+ ...Object.keys(config.profiles).sort().map((id) => allowedProfilePromptLine(config, id)),
389
389
  "",
390
390
  "Gate recipes:",
391
391
  "- Prefer preserving valid gates from the baseline workflows instead of recreating them.",
@@ -403,6 +403,13 @@ function plannerPrompt(entrypointId, task, baseline, config, planningContext) {
403
403
  stringify(baseline)
404
404
  ].join("\n");
405
405
  }
406
+ function allowedProfilePromptLine(config, id) {
407
+ const roles = effectiveSchedulingRoles(config, id);
408
+ return roles.length > 0 ? `- ${id} (scheduling_roles: ${roles.join(", ")})` : `- ${id}`;
409
+ }
410
+ function effectiveSchedulingRoles(config, profileId) {
411
+ return [...new Set(config.profiles[profileId]?.scheduling_roles ?? [])].sort();
412
+ }
406
413
  function canonicalizeGeneratedScheduleIds(artifact) {
407
414
  return {
408
415
  ...artifact,
@@ -451,9 +458,9 @@ function validateScheduleArtifact(config, artifact, planningContext) {
451
458
  ...workflowReferenceNodeIssues(artifact),
452
459
  ...workflowAssignedWorkUnitIssues(artifact, planningContext.workUnits),
453
460
  ...missingAssignedWorkUnitIssues(artifact, planningContext.workUnits),
454
- ...workUnitDependencyIssues(artifact, planningContext.workUnits),
461
+ ...workUnitDependencyIssues(config, artifact, planningContext.workUnits),
455
462
  ...invalidWorkflowPrimitiveIssues(config, artifact),
456
- ...implementationCoverageIssues(artifact)
463
+ ...implementationCoverageIssues(config, artifact)
457
464
  ];
458
465
  if (issues.length > 0) throw new ScheduleArtifactError(["Invalid generated schedule:", ...issues.map((issue) => `- ${issue}`)].join("\n"));
459
466
  }
@@ -514,7 +521,7 @@ function missingAssignedWorkUnitIssues(artifact, workUnits) {
514
521
  const missing = workUnits.map((unit) => unit.id).filter((id) => !assigned.has(id));
515
522
  return missing.length > 0 ? [`missing assigned backlog work units: ${missing.join(", ")}`] : [];
516
523
  }
517
- function workUnitDependencyIssues(artifact, workUnits) {
524
+ function workUnitDependencyIssues(config, artifact, workUnits) {
518
525
  if (workUnits.length === 0) return [];
519
526
  const workUnitIds = new Set(workUnits.map((unit) => unit.id));
520
527
  const dependenciesByUnit = new Map(workUnits.map((unit) => [unit.id, (unit.dependencies ?? []).filter((id) => workUnitIds.has(id))]));
@@ -522,7 +529,7 @@ function workUnitDependencyIssues(artifact, workUnits) {
522
529
  const nodes = workflow.nodes.flatMap(flattenWorkflowNode);
523
530
  const dependentsByNeed = workflowDependentsByNeed(nodes);
524
531
  const nodesByWorkUnit = nodesByAssignedWorkUnit(nodes);
525
- return nodes.filter(isImplementationNode).flatMap((node) => {
532
+ return nodes.filter((node) => isImplementationNode(config, node)).flatMap((node) => {
526
533
  const dependentId = node.task_context?.id;
527
534
  if (!dependentId) return [];
528
535
  return (dependenciesByUnit.get(dependentId) ?? []).flatMap((prerequisiteId) => {
@@ -564,14 +571,15 @@ function invalidWorkflowPrimitiveIssues(config, artifact) {
564
571
  return [];
565
572
  });
566
573
  }
567
- function implementationCoverageIssues(artifact) {
574
+ function implementationCoverageIssues(config, artifact) {
568
575
  return Object.entries(artifact.workflows).flatMap(([workflowId, workflow]) => {
569
- const dependentsByNeed = workflowDependentsByNeed(workflow.nodes);
570
- return workflow.nodes.filter(isImplementationNode).filter((node) => !hasDownstreamCoverage(node.id, dependentsByNeed)).map((node) => `implementation node '${workflowId}.${node.id}' is without downstream verification or review`);
576
+ const nodes = workflow.nodes.flatMap(flattenWorkflowNode);
577
+ const dependentsByNeed = workflowDependentsByNeed(nodes);
578
+ return 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`);
571
579
  });
572
580
  }
573
- function isImplementationNode(node) {
574
- return node.kind === "agent" && node.profile === "pipeline-code-writer";
581
+ function isImplementationNode(config, node) {
582
+ return hasSchedulingRole(config, node, "implementation");
575
583
  }
576
584
  function workflowDependentsByNeed(nodes) {
577
585
  const dependentsByNeed = /* @__PURE__ */ new Map();
@@ -582,25 +590,24 @@ function workflowDependentsByNeed(nodes) {
582
590
  }
583
591
  return dependentsByNeed;
584
592
  }
585
- function hasDownstreamCoverage(nodeId, dependentsByNeed) {
593
+ function hasDownstreamCoverage(config, nodeId, dependentsByNeed) {
586
594
  const queue = [...dependentsByNeed.get(nodeId) ?? []];
587
595
  const seen = /* @__PURE__ */ new Set();
588
596
  while (queue.length > 0) {
589
597
  const node = queue.shift();
590
598
  if (!node || seen.has(node.id)) continue;
591
599
  seen.add(node.id);
592
- if (isCoverageNode(node)) return true;
600
+ if (isCoverageNode(config, node)) return true;
593
601
  queue.push(...dependentsByNeed.get(node.id) ?? []);
594
602
  }
595
603
  return false;
596
604
  }
597
- function isCoverageNode(node) {
605
+ function isCoverageNode(config, node) {
606
+ return hasSchedulingRole(config, node, "coverage");
607
+ }
608
+ function hasSchedulingRole(config, node, role) {
598
609
  if (node.kind !== "agent") return false;
599
- return [
600
- "pipeline-acceptance-reviewer",
601
- "pipeline-thermo-nuclear-reviewer",
602
- "pipeline-verifier"
603
- ].includes(node.profile);
610
+ return config.profiles[node.profile]?.scheduling_roles?.includes(role) ?? false;
604
611
  }
605
612
  function allWorkflowNodes(workflows) {
606
613
  return Object.values(workflows).flatMap((workflow) => workflow.nodes.flatMap(flattenWorkflowNode));
package/package.json CHANGED
@@ -99,7 +99,7 @@
99
99
  "prepack": "bun run build:cli"
100
100
  },
101
101
  "type": "module",
102
- "version": "1.15.3",
102
+ "version": "1.16.0",
103
103
  "description": "Config-driven multi-agent pipeline runner for repository work",
104
104
  "main": "./dist/index.js",
105
105
  "types": "./dist/index.d.ts",