@oisincoveney/pipeline 2.0.0 → 2.0.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.
@@ -0,0 +1,267 @@
1
+ //#region src/schedule/baseline.ts
2
+ const SCHEDULE_KIND = "pipeline-schedule";
3
+ function baselineScheduleArtifact(input) {
4
+ const scheduleId = input.runId ?? defaultScheduleId(input.generatedAt);
5
+ const baseline = baselineWorkflows(input.baseline, input.config);
6
+ return {
7
+ generated_at: input.generatedAt.toISOString(),
8
+ kind: SCHEDULE_KIND,
9
+ root_workflow: baseline.rootWorkflow,
10
+ schedule_id: scheduleId,
11
+ source_entrypoint: input.entrypointId,
12
+ task: input.task,
13
+ version: 1,
14
+ workflows: baseline.workflows
15
+ };
16
+ }
17
+ function baselineWorkflows(baseline, _config) {
18
+ if (baseline === "quick") return {
19
+ rootWorkflow: "root",
20
+ workflows: quickBaselineWorkflow()
21
+ };
22
+ return {
23
+ rootWorkflow: "root",
24
+ workflows: executeBaselineWorkflow()
25
+ };
26
+ }
27
+ function quickBaselineWorkflow() {
28
+ return { root: {
29
+ description: "Compact generated quick schedule seed.",
30
+ nodes: [
31
+ {
32
+ id: "backlog-intake",
33
+ kind: "agent",
34
+ profile: "moka-researcher"
35
+ },
36
+ {
37
+ id: "red-tests",
38
+ kind: "agent",
39
+ needs: ["backlog-intake"],
40
+ profile: "moka-test-writer"
41
+ },
42
+ {
43
+ id: "implement",
44
+ kind: "agent",
45
+ needs: ["red-tests"],
46
+ profile: "moka-code-writer"
47
+ },
48
+ {
49
+ builtin: "test",
50
+ id: "mechanical-tests",
51
+ kind: "builtin",
52
+ needs: ["implement"]
53
+ },
54
+ {
55
+ builtin: "typecheck",
56
+ id: "mechanical-typecheck",
57
+ kind: "builtin",
58
+ needs: ["implement"]
59
+ },
60
+ {
61
+ gates: [
62
+ {
63
+ builtin: "typecheck",
64
+ id: "verify-typecheck",
65
+ kind: "builtin"
66
+ },
67
+ {
68
+ builtin: "test",
69
+ id: "verify-tests",
70
+ kind: "builtin"
71
+ },
72
+ {
73
+ builtin: "lint",
74
+ id: "verify-lint",
75
+ kind: "builtin"
76
+ },
77
+ {
78
+ builtin: "fallow",
79
+ id: "verify-fallow",
80
+ kind: "builtin"
81
+ },
82
+ {
83
+ kind: "verdict",
84
+ id: "verify-verdict",
85
+ target: "stdout"
86
+ }
87
+ ],
88
+ id: "verify",
89
+ kind: "agent",
90
+ needs: ["mechanical-tests", "mechanical-typecheck"],
91
+ profile: "moka-verifier"
92
+ }
93
+ ]
94
+ } };
95
+ }
96
+ function executeBaselineWorkflow() {
97
+ return { root: {
98
+ description: "Full generated execute schedule seed.",
99
+ nodes: [
100
+ {
101
+ id: "backlog-intake",
102
+ kind: "agent",
103
+ profile: "moka-researcher"
104
+ },
105
+ {
106
+ id: "research",
107
+ kind: "agent",
108
+ needs: ["backlog-intake"],
109
+ profile: "moka-researcher"
110
+ },
111
+ {
112
+ gates: [{
113
+ changed_files: {
114
+ allow: [
115
+ "**/*.test.*",
116
+ "**/*.spec.*",
117
+ "**/*_test.*",
118
+ "**/__tests__/**",
119
+ "test/**",
120
+ "tests/**",
121
+ "**/*.snap"
122
+ ],
123
+ require_any: [
124
+ "**/*.test.*",
125
+ "**/*.spec.*",
126
+ "**/*_test.*",
127
+ "**/__tests__/**",
128
+ "test/**",
129
+ "tests/**"
130
+ ]
131
+ },
132
+ id: "red-test-file-policy",
133
+ kind: "changed_files"
134
+ }],
135
+ id: "red-tests",
136
+ kind: "agent",
137
+ needs: ["research"],
138
+ profile: "moka-test-writer"
139
+ },
140
+ {
141
+ id: "green-implementation",
142
+ kind: "agent",
143
+ needs: ["red-tests"],
144
+ profile: "moka-code-writer"
145
+ },
146
+ {
147
+ builtin: "test",
148
+ id: "mechanical-green-tests",
149
+ kind: "builtin",
150
+ needs: ["green-implementation"]
151
+ },
152
+ {
153
+ builtin: "typecheck",
154
+ id: "mechanical-green-typecheck",
155
+ kind: "builtin",
156
+ needs: ["green-implementation"]
157
+ },
158
+ {
159
+ builtin: "lint",
160
+ id: "mechanical-green-lint",
161
+ kind: "builtin",
162
+ needs: ["green-implementation"]
163
+ },
164
+ {
165
+ builtin: "fallow",
166
+ id: "mechanical-green-fallow",
167
+ kind: "builtin",
168
+ needs: ["green-implementation"]
169
+ },
170
+ {
171
+ gates: [{
172
+ id: "acceptance-coverage",
173
+ kind: "acceptance",
174
+ required: false,
175
+ target: "stdout"
176
+ }, {
177
+ id: "acceptance-verdict",
178
+ kind: "verdict",
179
+ target: "stdout"
180
+ }],
181
+ id: "acceptance-review",
182
+ kind: "agent",
183
+ needs: [
184
+ "mechanical-green-tests",
185
+ "mechanical-green-typecheck",
186
+ "mechanical-green-lint",
187
+ "mechanical-green-fallow"
188
+ ],
189
+ profile: "moka-acceptance-reviewer"
190
+ },
191
+ {
192
+ gates: [
193
+ {
194
+ builtin: "typecheck",
195
+ id: "verify-typecheck",
196
+ kind: "builtin"
197
+ },
198
+ {
199
+ builtin: "test",
200
+ id: "verify-tests",
201
+ kind: "builtin"
202
+ },
203
+ {
204
+ builtin: "lint",
205
+ id: "verify-lint",
206
+ kind: "builtin"
207
+ },
208
+ {
209
+ builtin: "fallow",
210
+ id: "verify-fallow",
211
+ kind: "builtin"
212
+ },
213
+ {
214
+ builtin: "semgrep",
215
+ id: "verify-semgrep",
216
+ kind: "builtin"
217
+ },
218
+ {
219
+ builtin: "duplication",
220
+ id: "verify-duplication",
221
+ kind: "builtin"
222
+ },
223
+ {
224
+ id: "verify-verdict",
225
+ kind: "verdict",
226
+ target: "stdout"
227
+ }
228
+ ],
229
+ id: "verification",
230
+ kind: "agent",
231
+ needs: [
232
+ "mechanical-green-tests",
233
+ "mechanical-green-typecheck",
234
+ "mechanical-green-lint",
235
+ "mechanical-green-fallow"
236
+ ],
237
+ profile: "moka-verifier"
238
+ },
239
+ {
240
+ id: "code-quality-review",
241
+ kind: "agent",
242
+ needs: [
243
+ "mechanical-green-tests",
244
+ "mechanical-green-typecheck",
245
+ "mechanical-green-lint",
246
+ "mechanical-green-fallow"
247
+ ],
248
+ profile: "moka-thermo-nuclear-reviewer"
249
+ },
250
+ {
251
+ id: "learn",
252
+ kind: "agent",
253
+ needs: [
254
+ "acceptance-review",
255
+ "verification",
256
+ "code-quality-review"
257
+ ],
258
+ profile: "moka-learner"
259
+ }
260
+ ]
261
+ } };
262
+ }
263
+ function defaultScheduleId(date) {
264
+ return `run-${date.toISOString().replaceAll(/[-:.TZ]/g, "").slice(0, 14)}`;
265
+ }
266
+ //#endregion
267
+ export { baselineScheduleArtifact };
@@ -3,8 +3,9 @@ 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 { extractTicketIds } from "../task-ref.js";
7
6
  import { compileWorkflowPlan } from "../workflow-planner.js";
7
+ import { loadBacklogPlanningContext } from "./backlog-context.js";
8
+ import { baselineScheduleArtifact } from "./baseline.js";
8
9
  import { addGeneratedImplementationCoverage } from "./passes/coverage.js";
9
10
  import { canonicalizeGeneratedScheduleIds } from "./passes/ids.js";
10
11
  import { SCHEDULE_PASS_ORDER } from "./passes/index.js";
@@ -13,18 +14,12 @@ import { namespaceScheduleWorkflows } from "./passes/references.js";
13
14
  import { plannerPrompt, plannerRepairPrompt } from "./prompts.js";
14
15
  import { parseDocument, stringify } from "yaml";
15
16
  import { z } from "zod";
16
- import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
17
+ import { mkdirSync, writeFileSync } from "node:fs";
17
18
  import { join } from "node:path";
18
- import { Graph, alg } from "@dagrejs/graphlib";
19
- import matter from "gray-matter";
20
19
  //#region src/schedule/planner.ts
21
20
  const SCHEDULE_KIND = "pipeline-schedule";
22
21
  const ID_RE = /^[a-z][a-z0-9-]*$/;
23
22
  const SCHEDULE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
24
- const DESCRIPTION_SECTION_RE = /## Description\s+([\s\S]*?)(?=\n## |\s*$)/;
25
- const ACCEPTANCE_SECTION_RE = /## Acceptance Criteria\s+([\s\S]*?)(?=\n## |\s*$)/;
26
- const ACCEPTANCE_ITEM_RE = /^\s*-\s*\[[ xX]\]\s*#?([\w.-]+)\s+(.+)$/;
27
- const LINE_RE = /\r?\n/;
28
23
  const MARKDOWN_YAML_FENCE_RE = /```(?:ya?ml)?\s*\r?\n([\s\S]*?)\r?\n```/i;
29
24
  const SCHEDULE_PLANNER_REPAIR_ATTEMPTS = 1;
30
25
  const SCHEDULE_BUILTINS = [
@@ -120,266 +115,6 @@ function persistScheduleArtifact(worktreePath, artifact) {
120
115
  function scheduleArtifactPath(worktreePath, scheduleId) {
121
116
  return join(worktreePath, ".pipeline", "runs", scheduleId, "schedule.yaml");
122
117
  }
123
- function baselineScheduleArtifact(input) {
124
- const scheduleId = input.runId ?? defaultScheduleId(input.generatedAt);
125
- const baseline = baselineWorkflows(input.baseline, input.config);
126
- return {
127
- generated_at: input.generatedAt.toISOString(),
128
- kind: SCHEDULE_KIND,
129
- root_workflow: baseline.rootWorkflow,
130
- schedule_id: scheduleId,
131
- source_entrypoint: input.entrypointId,
132
- task: input.task,
133
- version: 1,
134
- workflows: baseline.workflows
135
- };
136
- }
137
- function baselineWorkflows(baseline, _config) {
138
- if (baseline === "quick") return {
139
- rootWorkflow: "root",
140
- workflows: quickBaselineWorkflow()
141
- };
142
- return {
143
- rootWorkflow: "root",
144
- workflows: executeBaselineWorkflow()
145
- };
146
- }
147
- function quickBaselineWorkflow() {
148
- return { root: {
149
- description: "Compact generated quick schedule seed.",
150
- nodes: [
151
- {
152
- id: "backlog-intake",
153
- kind: "agent",
154
- profile: "moka-researcher"
155
- },
156
- {
157
- id: "red-tests",
158
- kind: "agent",
159
- needs: ["backlog-intake"],
160
- profile: "moka-test-writer"
161
- },
162
- {
163
- id: "implement",
164
- kind: "agent",
165
- needs: ["red-tests"],
166
- profile: "moka-code-writer"
167
- },
168
- {
169
- builtin: "test",
170
- id: "mechanical-tests",
171
- kind: "builtin",
172
- needs: ["implement"]
173
- },
174
- {
175
- builtin: "typecheck",
176
- id: "mechanical-typecheck",
177
- kind: "builtin",
178
- needs: ["implement"]
179
- },
180
- {
181
- gates: [
182
- {
183
- builtin: "typecheck",
184
- id: "verify-typecheck",
185
- kind: "builtin"
186
- },
187
- {
188
- builtin: "test",
189
- id: "verify-tests",
190
- kind: "builtin"
191
- },
192
- {
193
- builtin: "lint",
194
- id: "verify-lint",
195
- kind: "builtin"
196
- },
197
- {
198
- builtin: "fallow",
199
- id: "verify-fallow",
200
- kind: "builtin"
201
- },
202
- {
203
- kind: "verdict",
204
- id: "verify-verdict",
205
- target: "stdout"
206
- }
207
- ],
208
- id: "verify",
209
- kind: "agent",
210
- needs: ["mechanical-tests", "mechanical-typecheck"],
211
- profile: "moka-verifier"
212
- }
213
- ]
214
- } };
215
- }
216
- function executeBaselineWorkflow() {
217
- return { root: {
218
- description: "Full generated execute schedule seed.",
219
- nodes: [
220
- {
221
- id: "backlog-intake",
222
- kind: "agent",
223
- profile: "moka-researcher"
224
- },
225
- {
226
- id: "research",
227
- kind: "agent",
228
- needs: ["backlog-intake"],
229
- profile: "moka-researcher"
230
- },
231
- {
232
- gates: [{
233
- changed_files: {
234
- allow: [
235
- "**/*.test.*",
236
- "**/*.spec.*",
237
- "**/*_test.*",
238
- "**/__tests__/**",
239
- "test/**",
240
- "tests/**",
241
- "**/*.snap"
242
- ],
243
- require_any: [
244
- "**/*.test.*",
245
- "**/*.spec.*",
246
- "**/*_test.*",
247
- "**/__tests__/**",
248
- "test/**",
249
- "tests/**"
250
- ]
251
- },
252
- id: "red-test-file-policy",
253
- kind: "changed_files"
254
- }],
255
- id: "red-tests",
256
- kind: "agent",
257
- needs: ["research"],
258
- profile: "moka-test-writer"
259
- },
260
- {
261
- id: "green-implementation",
262
- kind: "agent",
263
- needs: ["red-tests"],
264
- profile: "moka-code-writer"
265
- },
266
- {
267
- builtin: "test",
268
- id: "mechanical-green-tests",
269
- kind: "builtin",
270
- needs: ["green-implementation"]
271
- },
272
- {
273
- builtin: "typecheck",
274
- id: "mechanical-green-typecheck",
275
- kind: "builtin",
276
- needs: ["green-implementation"]
277
- },
278
- {
279
- builtin: "lint",
280
- id: "mechanical-green-lint",
281
- kind: "builtin",
282
- needs: ["green-implementation"]
283
- },
284
- {
285
- builtin: "fallow",
286
- id: "mechanical-green-fallow",
287
- kind: "builtin",
288
- needs: ["green-implementation"]
289
- },
290
- {
291
- gates: [{
292
- id: "acceptance-coverage",
293
- kind: "acceptance",
294
- required: false,
295
- target: "stdout"
296
- }, {
297
- id: "acceptance-verdict",
298
- kind: "verdict",
299
- target: "stdout"
300
- }],
301
- id: "acceptance-review",
302
- kind: "agent",
303
- needs: [
304
- "mechanical-green-tests",
305
- "mechanical-green-typecheck",
306
- "mechanical-green-lint",
307
- "mechanical-green-fallow"
308
- ],
309
- profile: "moka-acceptance-reviewer"
310
- },
311
- {
312
- gates: [
313
- {
314
- builtin: "typecheck",
315
- id: "verify-typecheck",
316
- kind: "builtin"
317
- },
318
- {
319
- builtin: "test",
320
- id: "verify-tests",
321
- kind: "builtin"
322
- },
323
- {
324
- builtin: "lint",
325
- id: "verify-lint",
326
- kind: "builtin"
327
- },
328
- {
329
- builtin: "fallow",
330
- id: "verify-fallow",
331
- kind: "builtin"
332
- },
333
- {
334
- builtin: "semgrep",
335
- id: "verify-semgrep",
336
- kind: "builtin"
337
- },
338
- {
339
- builtin: "duplication",
340
- id: "verify-duplication",
341
- kind: "builtin"
342
- },
343
- {
344
- id: "verify-verdict",
345
- kind: "verdict",
346
- target: "stdout"
347
- }
348
- ],
349
- id: "verification",
350
- kind: "agent",
351
- needs: [
352
- "mechanical-green-tests",
353
- "mechanical-green-typecheck",
354
- "mechanical-green-lint",
355
- "mechanical-green-fallow"
356
- ],
357
- profile: "moka-verifier"
358
- },
359
- {
360
- id: "code-quality-review",
361
- kind: "agent",
362
- needs: [
363
- "mechanical-green-tests",
364
- "mechanical-green-typecheck",
365
- "mechanical-green-lint",
366
- "mechanical-green-fallow"
367
- ],
368
- profile: "moka-thermo-nuclear-reviewer"
369
- },
370
- {
371
- id: "learn",
372
- kind: "agent",
373
- needs: [
374
- "acceptance-review",
375
- "verification",
376
- "code-quality-review"
377
- ],
378
- profile: "moka-learner"
379
- }
380
- ]
381
- } };
382
- }
383
118
  async function planScheduleArtifact(baseline, plannerProfile, options, planningContext) {
384
119
  const requiredPlannerProfile = requireSchedulePlannerProfile(plannerProfile, options.entrypointId);
385
120
  const source = requireSchedulePlannerSource(await runSchedulePlanner(requiredPlannerProfile, plannerPrompt(options.entrypointId, options.task, baseline, options.config, planningContext), options));
@@ -662,110 +397,5 @@ function allWorkflowNodes(workflows) {
662
397
  function flattenWorkflowNode(node) {
663
398
  return node.kind === "parallel" ? [node, ...node.nodes.flatMap(flattenWorkflowNode)] : [node];
664
399
  }
665
- function loadBacklogPlanningContext(task, worktreePath) {
666
- const ticketIds = extractTicketIds(task);
667
- if (ticketIds.length === 0) return {
668
- parentWorkUnits: [],
669
- workUnits: []
670
- };
671
- const tasks = readBacklogTasks(worktreePath);
672
- const tasksById = new Map(tasks.map((taskFile) => [taskFile.id, taskFile]));
673
- const taskGraph = backlogTaskGraph(tasks);
674
- const parentWorkUnits = [];
675
- const workUnits = [];
676
- const parentIds = /* @__PURE__ */ new Set();
677
- const workUnitIds = /* @__PURE__ */ new Set();
678
- for (const ticketId of ticketIds) {
679
- const taskFile = tasksById.get(ticketId);
680
- if (!taskFile) continue;
681
- const descendants = descendantBacklogTasks(ticketId, taskGraph);
682
- if (descendants.length === 0) {
683
- addUniqueWorkUnit(taskFile.workUnit, workUnits, workUnitIds);
684
- continue;
685
- }
686
- addUniqueWorkUnit(taskFile.workUnit, parentWorkUnits, parentIds);
687
- for (const descendant of descendants) addUniqueWorkUnit(descendant.workUnit, workUnits, workUnitIds);
688
- }
689
- return {
690
- parentWorkUnits,
691
- workUnits
692
- };
693
- }
694
- function backlogTaskGraph(tasks) {
695
- const graph = new Graph();
696
- const sortedTasks = [...tasks].sort(compareBacklogTaskIds);
697
- for (const task of sortedTasks) graph.setNode(task.id, task);
698
- for (const task of sortedTasks) if (task.parentTaskId && graph.hasNode(task.parentTaskId)) graph.setEdge(task.parentTaskId, task.id);
699
- return graph;
700
- }
701
- function descendantBacklogTasks(taskId, taskGraph) {
702
- if (!taskGraph.hasNode(taskId)) return [];
703
- return alg.preorder(taskGraph, taskId).slice(1).map((id) => taskGraph.node(id)).filter((task) => Boolean(task));
704
- }
705
- function compareBacklogTaskIds(a, b) {
706
- return a.id.localeCompare(b.id, void 0, { numeric: true });
707
- }
708
- function addUniqueWorkUnit(workUnit, target, seen) {
709
- if (seen.has(workUnit.id)) return;
710
- seen.add(workUnit.id);
711
- target.push(workUnit);
712
- }
713
- function readBacklogTasks(worktreePath) {
714
- const tasksDir = join(worktreePath, "backlog", "tasks");
715
- if (!existsSync(tasksDir)) return [];
716
- return readdirSync(tasksDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).flatMap((entry) => readBacklogTaskFile(join(tasksDir, entry.name)));
717
- }
718
- function readBacklogTaskFile(path) {
719
- const parsed = matter(readFileSync(path, "utf8"));
720
- const id = stringFrontmatter(parsed.data.id);
721
- if (!id) return [];
722
- return [{
723
- id,
724
- parentTaskId: stringFrontmatter(parsed.data.parent_task_id),
725
- workUnit: {
726
- acceptance_criteria: acceptanceCriteriaFromMarkdown(parsed.content),
727
- ...optionalStringArrayField("dependencies", stringArrayFrontmatter(parsed.data.dependencies)),
728
- ...optionalStringField("description", descriptionFromMarkdown(parsed.content)),
729
- id,
730
- ...optionalStringField("title", stringFrontmatter(parsed.data.title))
731
- }
732
- }];
733
- }
734
- function stringFrontmatter(value) {
735
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
736
- }
737
- function stringArrayFrontmatter(value) {
738
- if (!Array.isArray(value)) return [];
739
- return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
740
- }
741
- function optionalStringField(key, value) {
742
- return value ? { [key]: value } : {};
743
- }
744
- function optionalStringArrayField(key, value) {
745
- return value.length > 0 ? { [key]: value } : {};
746
- }
747
- function descriptionFromMarkdown(content) {
748
- const marked = betweenMarkers(content, "<!-- SECTION:DESCRIPTION:BEGIN -->", "<!-- SECTION:DESCRIPTION:END -->");
749
- if (marked) return marked;
750
- return cleanupMarkdownSection(content.match(DESCRIPTION_SECTION_RE)?.[1]);
751
- }
752
- function acceptanceCriteriaFromMarkdown(content) {
753
- return (betweenMarkers(content, "<!-- AC:BEGIN -->", "<!-- AC:END -->") ?? content.match(ACCEPTANCE_SECTION_RE)?.[1] ?? "").split(LINE_RE).map((line) => line.match(ACCEPTANCE_ITEM_RE)).filter((match) => Boolean(match)).map((match) => ({
754
- id: match[1] ?? "",
755
- text: (match[2] ?? "").trim()
756
- })).filter((criterion) => criterion.id && criterion.text);
757
- }
758
- function betweenMarkers(content, start, end) {
759
- const startIndex = content.indexOf(start);
760
- const endIndex = content.indexOf(end);
761
- if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) return;
762
- return cleanupMarkdownSection(content.slice(startIndex + start.length, endIndex));
763
- }
764
- function cleanupMarkdownSection(value) {
765
- return value?.split(LINE_RE).map((line) => line.trim()).filter(Boolean).join("\n") || void 0;
766
- }
767
- function defaultScheduleId(date) {
768
- return `run-${date.toISOString().replaceAll(/[-:.TZ]/g, "").slice(0, 14)}`;
769
- }
770
400
  //#endregion
771
401
  export { ScheduleArtifactError, compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact, scheduleArtifactPath };
package/package.json CHANGED
@@ -115,7 +115,7 @@
115
115
  "prepack": "bun run build:cli"
116
116
  },
117
117
  "type": "module",
118
- "version": "2.0.0",
118
+ "version": "2.0.1",
119
119
  "description": "Config-driven multi-agent pipeline runner for repository work",
120
120
  "main": "./dist/index.js",
121
121
  "types": "./dist/index.d.ts",