@oisincoveney/pipeline 2.0.0 → 2.1.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.
@@ -0,0 +1,114 @@
1
+ import { extractTicketIds } from "../task-ref.js";
2
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { Graph, alg } from "@dagrejs/graphlib";
5
+ import matter from "gray-matter";
6
+ //#region src/schedule/backlog-context.ts
7
+ const DESCRIPTION_SECTION_RE = /## Description\s+([\s\S]*?)(?=\n## |\s*$)/;
8
+ const ACCEPTANCE_SECTION_RE = /## Acceptance Criteria\s+([\s\S]*?)(?=\n## |\s*$)/;
9
+ const ACCEPTANCE_ITEM_RE = /^\s*-\s*\[[ xX]\]\s*#?([\w.-]+)\s+(.+)$/;
10
+ const LINE_RE = /\r?\n/;
11
+ function loadBacklogPlanningContext(task, worktreePath) {
12
+ const ticketIds = extractTicketIds(task);
13
+ if (ticketIds.length === 0) return {
14
+ parentWorkUnits: [],
15
+ workUnits: []
16
+ };
17
+ const tasks = readBacklogTasks(worktreePath);
18
+ const tasksById = new Map(tasks.map((taskFile) => [taskFile.id, taskFile]));
19
+ const taskGraph = backlogTaskGraph(tasks);
20
+ const parentWorkUnits = [];
21
+ const workUnits = [];
22
+ const parentIds = /* @__PURE__ */ new Set();
23
+ const workUnitIds = /* @__PURE__ */ new Set();
24
+ for (const ticketId of ticketIds) {
25
+ const taskFile = tasksById.get(ticketId);
26
+ if (!taskFile) continue;
27
+ const descendants = descendantBacklogTasks(ticketId, taskGraph);
28
+ if (descendants.length === 0) {
29
+ addUniqueWorkUnit(taskFile.workUnit, workUnits, workUnitIds);
30
+ continue;
31
+ }
32
+ addUniqueWorkUnit(taskFile.workUnit, parentWorkUnits, parentIds);
33
+ for (const descendant of descendants) addUniqueWorkUnit(descendant.workUnit, workUnits, workUnitIds);
34
+ }
35
+ return {
36
+ parentWorkUnits,
37
+ workUnits
38
+ };
39
+ }
40
+ function backlogTaskGraph(tasks) {
41
+ const graph = new Graph();
42
+ const sortedTasks = [...tasks].sort(compareBacklogTaskIds);
43
+ for (const task of sortedTasks) graph.setNode(task.id, task);
44
+ for (const task of sortedTasks) if (task.parentTaskId && graph.hasNode(task.parentTaskId)) graph.setEdge(task.parentTaskId, task.id);
45
+ return graph;
46
+ }
47
+ function descendantBacklogTasks(taskId, taskGraph) {
48
+ if (!taskGraph.hasNode(taskId)) return [];
49
+ return alg.preorder(taskGraph, taskId).slice(1).map((id) => taskGraph.node(id)).filter((task) => Boolean(task));
50
+ }
51
+ function compareBacklogTaskIds(a, b) {
52
+ return a.id.localeCompare(b.id, void 0, { numeric: true });
53
+ }
54
+ function addUniqueWorkUnit(workUnit, target, seen) {
55
+ if (seen.has(workUnit.id)) return;
56
+ seen.add(workUnit.id);
57
+ target.push(workUnit);
58
+ }
59
+ function readBacklogTasks(worktreePath) {
60
+ const tasksDir = join(worktreePath, "backlog", "tasks");
61
+ if (!existsSync(tasksDir)) return [];
62
+ return readdirSync(tasksDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).flatMap((entry) => readBacklogTaskFile(join(tasksDir, entry.name)));
63
+ }
64
+ function readBacklogTaskFile(path) {
65
+ const parsed = matter(readFileSync(path, "utf8"));
66
+ const id = stringFrontmatter(parsed.data.id);
67
+ if (!id) return [];
68
+ return [{
69
+ id,
70
+ parentTaskId: stringFrontmatter(parsed.data.parent_task_id),
71
+ workUnit: {
72
+ acceptance_criteria: acceptanceCriteriaFromMarkdown(parsed.content),
73
+ ...optionalStringArrayField("dependencies", stringArrayFrontmatter(parsed.data.dependencies)),
74
+ ...optionalStringField("description", descriptionFromMarkdown(parsed.content)),
75
+ id,
76
+ ...optionalStringField("title", stringFrontmatter(parsed.data.title))
77
+ }
78
+ }];
79
+ }
80
+ function stringFrontmatter(value) {
81
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
82
+ }
83
+ function stringArrayFrontmatter(value) {
84
+ if (!Array.isArray(value)) return [];
85
+ return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
86
+ }
87
+ function optionalStringField(key, value) {
88
+ return value ? { [key]: value } : {};
89
+ }
90
+ function optionalStringArrayField(key, value) {
91
+ return value.length > 0 ? { [key]: value } : {};
92
+ }
93
+ function descriptionFromMarkdown(content) {
94
+ const marked = betweenMarkers(content, "<!-- SECTION:DESCRIPTION:BEGIN -->", "<!-- SECTION:DESCRIPTION:END -->");
95
+ if (marked) return marked;
96
+ return cleanupMarkdownSection(content.match(DESCRIPTION_SECTION_RE)?.[1]);
97
+ }
98
+ function acceptanceCriteriaFromMarkdown(content) {
99
+ 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) => ({
100
+ id: match[1] ?? "",
101
+ text: (match[2] ?? "").trim()
102
+ })).filter((criterion) => criterion.id && criterion.text);
103
+ }
104
+ function betweenMarkers(content, start, end) {
105
+ const startIndex = content.indexOf(start);
106
+ const endIndex = content.indexOf(end);
107
+ if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) return;
108
+ return cleanupMarkdownSection(content.slice(startIndex + start.length, endIndex));
109
+ }
110
+ function cleanupMarkdownSection(value) {
111
+ return value?.split(LINE_RE).map((line) => line.trim()).filter(Boolean).join("\n") || void 0;
112
+ }
113
+ //#endregion
114
+ export { loadBacklogPlanningContext };
@@ -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 };