@oisincoveney/pipeline 1.12.0 → 1.13.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 +2 -2
- package/dist/pipeline-init.js +1 -0
- package/dist/schedule-planner.js +51 -0
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -231,10 +231,10 @@ declare const configSchema: z.ZodObject<{
|
|
|
231
231
|
}, z.core.$strict>>;
|
|
232
232
|
output: z.ZodOptional<z.ZodObject<{
|
|
233
233
|
format: z.ZodEnum<{
|
|
234
|
-
json_schema: "json_schema";
|
|
235
234
|
text: "text";
|
|
236
235
|
json: "json";
|
|
237
236
|
jsonl: "jsonl";
|
|
237
|
+
json_schema: "json_schema";
|
|
238
238
|
}>;
|
|
239
239
|
repair: z.ZodOptional<z.ZodObject<{
|
|
240
240
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -274,10 +274,10 @@ declare const configSchema: z.ZodObject<{
|
|
|
274
274
|
disabled: "disabled";
|
|
275
275
|
}>>>;
|
|
276
276
|
output_formats: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
277
|
-
json_schema: "json_schema";
|
|
278
277
|
text: "text";
|
|
279
278
|
json: "json";
|
|
280
279
|
jsonl: "jsonl";
|
|
280
|
+
json_schema: "json_schema";
|
|
281
281
|
}>>>;
|
|
282
282
|
rules: z.ZodOptional<z.ZodBoolean>;
|
|
283
283
|
skills: z.ZodOptional<z.ZodBoolean>;
|
package/dist/pipeline-init.js
CHANGED
|
@@ -734,6 +734,7 @@ const SCAFFOLD_FILES = {
|
|
|
734
734
|
"",
|
|
735
735
|
"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.",
|
|
736
736
|
"Do not copy backlog descriptions or acceptance criteria into output; the scheduler hydrates them from the assigned `task_context.id` after parsing.",
|
|
737
|
+
"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.",
|
|
737
738
|
"",
|
|
738
739
|
"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. Use one RED node for a group of tickets when they share a test strategy, fan out to parallel GREEN implementation nodes where the work can be implemented independently, and fan back in to shared acceptance or verifier nodes when the same acceptance checklist or real repository commands prove the group. Only serialize ticket nodes when the backlog, a shared migration/schema/API dependency, or implementation risk requires it.",
|
|
739
740
|
"",
|
package/dist/schedule-planner.js
CHANGED
|
@@ -372,6 +372,7 @@ function plannerPrompt(entrypointId, task, baseline, config, planningContext) {
|
|
|
372
372
|
"Assign each backlog work unit to explicit generated agent nodes with task_context.id. The scheduler hydrates title, description, and acceptance_criteria after parsing.",
|
|
373
373
|
"Do not copy backlog descriptions or acceptance criteria into task_context output.",
|
|
374
374
|
"Implementation work must have downstream acceptance, verification, or review coverage in the generated DAG.",
|
|
375
|
+
"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.",
|
|
375
376
|
"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.",
|
|
376
377
|
"Use one RED node for a group of tickets when they share a test strategy, then fan out to parallel GREEN implementation nodes where the work can be implemented independently.",
|
|
377
378
|
"Use one acceptance or verifier node for multiple GREEN nodes when the same acceptance checklist or real repository commands prove the group.",
|
|
@@ -402,6 +403,7 @@ function validateScheduleArtifact(config, artifact, planningContext) {
|
|
|
402
403
|
...workflowReferenceNodeIssues(artifact),
|
|
403
404
|
...workflowAssignedWorkUnitIssues(artifact, planningContext.workUnits),
|
|
404
405
|
...missingAssignedWorkUnitIssues(artifact, planningContext.workUnits),
|
|
406
|
+
...workUnitDependencyIssues(artifact, planningContext.workUnits),
|
|
405
407
|
...invalidWorkflowPrimitiveIssues(config, artifact),
|
|
406
408
|
...implementationCoverageIssues(artifact)
|
|
407
409
|
];
|
|
@@ -464,6 +466,47 @@ function missingAssignedWorkUnitIssues(artifact, workUnits) {
|
|
|
464
466
|
const missing = workUnits.map((unit) => unit.id).filter((id) => !assigned.has(id));
|
|
465
467
|
return missing.length > 0 ? [`missing assigned backlog work units: ${missing.join(", ")}`] : [];
|
|
466
468
|
}
|
|
469
|
+
function workUnitDependencyIssues(artifact, workUnits) {
|
|
470
|
+
if (workUnits.length === 0) return [];
|
|
471
|
+
const workUnitIds = new Set(workUnits.map((unit) => unit.id));
|
|
472
|
+
const dependenciesByUnit = new Map(workUnits.map((unit) => [unit.id, (unit.dependencies ?? []).filter((id) => workUnitIds.has(id))]));
|
|
473
|
+
return Object.entries(artifact.workflows).flatMap(([workflowId, workflow]) => {
|
|
474
|
+
const nodes = workflow.nodes.flatMap(flattenWorkflowNode);
|
|
475
|
+
const dependentsByNeed = workflowDependentsByNeed(nodes);
|
|
476
|
+
const nodesByWorkUnit = nodesByAssignedWorkUnit(nodes);
|
|
477
|
+
return nodes.filter(isImplementationNode).flatMap((node) => {
|
|
478
|
+
const dependentId = node.task_context?.id;
|
|
479
|
+
if (!dependentId) return [];
|
|
480
|
+
return (dependenciesByUnit.get(dependentId) ?? []).flatMap((prerequisiteId) => {
|
|
481
|
+
const prerequisiteNodes = nodesByWorkUnit.get(prerequisiteId) ?? [];
|
|
482
|
+
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(", ")}`];
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
function nodesByAssignedWorkUnit(nodes) {
|
|
488
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
489
|
+
for (const node of nodes) {
|
|
490
|
+
const id = node.task_context?.id;
|
|
491
|
+
if (!id) continue;
|
|
492
|
+
const current = grouped.get(id) ?? [];
|
|
493
|
+
current.push(node);
|
|
494
|
+
grouped.set(id, current);
|
|
495
|
+
}
|
|
496
|
+
return grouped;
|
|
497
|
+
}
|
|
498
|
+
function hasPathToNode(sourceId, targetId, dependentsByNeed) {
|
|
499
|
+
const queue = [...dependentsByNeed.get(sourceId) ?? []];
|
|
500
|
+
const seen = /* @__PURE__ */ new Set();
|
|
501
|
+
while (queue.length > 0) {
|
|
502
|
+
const node = queue.shift();
|
|
503
|
+
if (!node || seen.has(node.id)) continue;
|
|
504
|
+
if (node.id === targetId) return true;
|
|
505
|
+
seen.add(node.id);
|
|
506
|
+
queue.push(...dependentsByNeed.get(node.id) ?? []);
|
|
507
|
+
}
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
467
510
|
function invalidWorkflowPrimitiveIssues(config, artifact) {
|
|
468
511
|
const allowed = new Set(Object.keys(config.workflows));
|
|
469
512
|
return allWorkflowNodes(artifact.workflows).flatMap((node) => {
|
|
@@ -543,6 +586,7 @@ function readBacklogTaskFile(path) {
|
|
|
543
586
|
parentTaskId: stringFrontmatter(parsed.data.parent_task_id),
|
|
544
587
|
workUnit: {
|
|
545
588
|
acceptance_criteria: acceptanceCriteriaFromMarkdown(parsed.content),
|
|
589
|
+
...optionalStringArrayField("dependencies", stringArrayFrontmatter(parsed.data.dependencies)),
|
|
546
590
|
...optionalStringField("description", descriptionFromMarkdown(parsed.content)),
|
|
547
591
|
id,
|
|
548
592
|
...optionalStringField("title", stringFrontmatter(parsed.data.title))
|
|
@@ -552,9 +596,16 @@ function readBacklogTaskFile(path) {
|
|
|
552
596
|
function stringFrontmatter(value) {
|
|
553
597
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
554
598
|
}
|
|
599
|
+
function stringArrayFrontmatter(value) {
|
|
600
|
+
if (!Array.isArray(value)) return [];
|
|
601
|
+
return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
|
|
602
|
+
}
|
|
555
603
|
function optionalStringField(key, value) {
|
|
556
604
|
return value ? { [key]: value } : {};
|
|
557
605
|
}
|
|
606
|
+
function optionalStringArrayField(key, value) {
|
|
607
|
+
return value.length > 0 ? { [key]: value } : {};
|
|
608
|
+
}
|
|
558
609
|
function descriptionFromMarkdown(content) {
|
|
559
610
|
const marked = betweenMarkers(content, "<!-- SECTION:DESCRIPTION:BEGIN -->", "<!-- SECTION:DESCRIPTION:END -->");
|
|
560
611
|
if (marked) return marked;
|
package/package.json
CHANGED