issue-flow 0.11.1 → 0.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.
Files changed (180) hide show
  1. package/README.md +79 -274
  2. package/dist/agent-BXRIEKVM.js +238 -0
  3. package/dist/agent-BXRIEKVM.js.map +1 -0
  4. package/dist/{analyze-XV3K25K3.js → analyze-WS47UYGN.js} +32 -15
  5. package/dist/analyze-WS47UYGN.js.map +1 -0
  6. package/dist/{apply-WUJHDD7A.js → apply-OS62UWRN.js} +15 -5
  7. package/dist/{apply-WUJHDD7A.js.map → apply-OS62UWRN.js.map} +1 -1
  8. package/dist/bench-SA3BOME5.js +906 -0
  9. package/dist/bench-SA3BOME5.js.map +1 -0
  10. package/dist/chunk-37N2RJW2.js +120 -0
  11. package/dist/chunk-37N2RJW2.js.map +1 -0
  12. package/dist/chunk-3XLCNVMD.js +180 -0
  13. package/dist/chunk-3XLCNVMD.js.map +1 -0
  14. package/dist/chunk-5TB6KLC2.js +72 -0
  15. package/dist/chunk-5TB6KLC2.js.map +1 -0
  16. package/dist/{chunk-OBLVLIUS.js → chunk-664Y3NDX.js} +25 -13
  17. package/dist/chunk-664Y3NDX.js.map +1 -0
  18. package/dist/chunk-6FDFRNXZ.js +357 -0
  19. package/dist/chunk-6FDFRNXZ.js.map +1 -0
  20. package/dist/chunk-7KD66LIM.js +658 -0
  21. package/dist/chunk-7KD66LIM.js.map +1 -0
  22. package/dist/chunk-7Z4Y3VKM.js +26 -0
  23. package/dist/chunk-7Z4Y3VKM.js.map +1 -0
  24. package/dist/chunk-ARFVHC2F.js +1111 -0
  25. package/dist/chunk-ARFVHC2F.js.map +1 -0
  26. package/dist/{chunk-RKTEV6EG.js → chunk-BHJCGWQX.js} +141 -12
  27. package/dist/chunk-BHJCGWQX.js.map +1 -0
  28. package/dist/chunk-BQDQTIM7.js +74 -0
  29. package/dist/chunk-BQDQTIM7.js.map +1 -0
  30. package/dist/chunk-CNBYCMSU.js +2709 -0
  31. package/dist/chunk-CNBYCMSU.js.map +1 -0
  32. package/dist/chunk-CRGIQFUQ.js +83 -0
  33. package/dist/chunk-CRGIQFUQ.js.map +1 -0
  34. package/dist/chunk-DIG5XKQQ.js +298 -0
  35. package/dist/chunk-DIG5XKQQ.js.map +1 -0
  36. package/dist/chunk-FZDR7CGH.js +71 -0
  37. package/dist/chunk-FZDR7CGH.js.map +1 -0
  38. package/dist/chunk-HEF2RMKN.js +413 -0
  39. package/dist/chunk-HEF2RMKN.js.map +1 -0
  40. package/dist/{chunk-IVHZEYAX.js → chunk-HJGGNLWI.js} +25 -19
  41. package/dist/chunk-HJGGNLWI.js.map +1 -0
  42. package/dist/chunk-HQAHNIAR.js +283 -0
  43. package/dist/chunk-HQAHNIAR.js.map +1 -0
  44. package/dist/{chunk-GANSDJV7.js → chunk-IHIEXUTC.js} +46 -24
  45. package/dist/chunk-IHIEXUTC.js.map +1 -0
  46. package/dist/chunk-IV5SS4JN.js +84 -0
  47. package/dist/chunk-IV5SS4JN.js.map +1 -0
  48. package/dist/{chunk-HC2MT7BX.js → chunk-IYV6KNVF.js} +158 -592
  49. package/dist/chunk-IYV6KNVF.js.map +1 -0
  50. package/dist/{chunk-LVKQ2AKT.js → chunk-J62QSVP6.js} +10 -8
  51. package/dist/chunk-J62QSVP6.js.map +1 -0
  52. package/dist/chunk-KMMASOA6.js +134 -0
  53. package/dist/chunk-KMMASOA6.js.map +1 -0
  54. package/dist/chunk-KQHB7ZT3.js +559 -0
  55. package/dist/chunk-KQHB7ZT3.js.map +1 -0
  56. package/dist/chunk-L6HSKTQE.js +77 -0
  57. package/dist/chunk-L6HSKTQE.js.map +1 -0
  58. package/dist/{chunk-6N7LZZ5U.js → chunk-LZ6GFQAG.js} +474 -160
  59. package/dist/chunk-LZ6GFQAG.js.map +1 -0
  60. package/dist/{chunk-HQYXVIH6.js → chunk-MIVHK5YI.js} +5 -5
  61. package/dist/chunk-NEZZ6KDR.js +1217 -0
  62. package/dist/chunk-NEZZ6KDR.js.map +1 -0
  63. package/dist/{run-DBF2VWUN.js → chunk-OPNGPUKJ.js} +747 -594
  64. package/dist/chunk-OPNGPUKJ.js.map +1 -0
  65. package/dist/chunk-P3LRUNC6.js +16 -0
  66. package/dist/chunk-P3LRUNC6.js.map +1 -0
  67. package/dist/{chunk-BXCPXRBR.js → chunk-PIVGJM6K.js} +129 -32
  68. package/dist/chunk-PIVGJM6K.js.map +1 -0
  69. package/dist/{chunk-AAY6KM7N.js → chunk-PV4OX5TC.js} +167 -196
  70. package/dist/chunk-PV4OX5TC.js.map +1 -0
  71. package/dist/chunk-QCXPNSEL.js +141 -0
  72. package/dist/chunk-QCXPNSEL.js.map +1 -0
  73. package/dist/{chunk-34WNTK3K.js → chunk-QXDNBYQF.js} +53 -13
  74. package/dist/chunk-QXDNBYQF.js.map +1 -0
  75. package/dist/chunk-SNTTOQAD.js +30 -0
  76. package/dist/chunk-SNTTOQAD.js.map +1 -0
  77. package/dist/{chunk-EY6WMHV2.js → chunk-TM67EHSV.js} +67 -23
  78. package/dist/chunk-TM67EHSV.js.map +1 -0
  79. package/dist/chunk-TVN2JVOF.js +77 -0
  80. package/dist/chunk-TVN2JVOF.js.map +1 -0
  81. package/dist/chunk-TX5WIVYP.js +129 -0
  82. package/dist/chunk-TX5WIVYP.js.map +1 -0
  83. package/dist/{chunk-KQ4JQN7T.js → chunk-UJ7XWK7J.js} +83 -21
  84. package/dist/chunk-UJ7XWK7J.js.map +1 -0
  85. package/dist/{chunk-47VIN33V.js → chunk-UOCPC2Y2.js} +596 -17
  86. package/dist/chunk-UOCPC2Y2.js.map +1 -0
  87. package/dist/chunk-XEYH6ZZI.js +289 -0
  88. package/dist/chunk-XEYH6ZZI.js.map +1 -0
  89. package/dist/cli.js +390 -40
  90. package/dist/cli.js.map +1 -1
  91. package/dist/config-MEM2IWWN.js +76 -0
  92. package/dist/contract-JH2WBIO6.js +8 -0
  93. package/dist/conventions-WV4NDXGH.js +143 -0
  94. package/dist/conventions-WV4NDXGH.js.map +1 -0
  95. package/dist/execute-NITIHHCK.js +31 -0
  96. package/dist/{generate-TBVTLMOP.js → generate-MVQI7A45.js} +39 -19
  97. package/dist/{generate-TBVTLMOP.js.map → generate-MVQI7A45.js.map} +1 -1
  98. package/dist/git-YRVRUN3C.js +31 -0
  99. package/dist/init-OKWEC3EV.js +21 -0
  100. package/dist/operations-LU6JA3BX.js +374 -0
  101. package/dist/operations-LU6JA3BX.js.map +1 -0
  102. package/dist/permissions-7NMDFU27.js +18 -0
  103. package/dist/plan-LEJJF3DE.js +31 -0
  104. package/dist/{policy-XQW5NUIE.js → policy-JSYEK5B2.js} +17 -4
  105. package/dist/policy-JSYEK5B2.js.map +1 -0
  106. package/dist/pr-HPVUZ7S3.js +36 -0
  107. package/dist/pr-review-R2QZBT3F.js +29 -0
  108. package/dist/pr-review-R2QZBT3F.js.map +1 -0
  109. package/dist/prd-5O6GYCC6.js +30 -0
  110. package/dist/prd-5O6GYCC6.js.map +1 -0
  111. package/dist/ps-TNE44R5V.js +90 -0
  112. package/dist/ps-TNE44R5V.js.map +1 -0
  113. package/dist/recorder-XGF55WXI.js +31 -0
  114. package/dist/recorder-XGF55WXI.js.map +1 -0
  115. package/dist/registry-LGPE53QD.js +15 -0
  116. package/dist/registry-LGPE53QD.js.map +1 -0
  117. package/dist/resume-QMEPMSZW.js +238 -0
  118. package/dist/resume-QMEPMSZW.js.map +1 -0
  119. package/dist/review-YKWD6SNN.js +32 -0
  120. package/dist/review-YKWD6SNN.js.map +1 -0
  121. package/dist/routing-2ZUMZPJB.js +91 -0
  122. package/dist/routing-2ZUMZPJB.js.map +1 -0
  123. package/dist/run-YU4TR62R.js +50 -0
  124. package/dist/run-YU4TR62R.js.map +1 -0
  125. package/dist/state-manager-X3PT3FGT.js +36 -0
  126. package/dist/state-manager-X3PT3FGT.js.map +1 -0
  127. package/dist/usage-VCFLDA5Y.js +174 -0
  128. package/dist/usage-VCFLDA5Y.js.map +1 -0
  129. package/dist/{web-ANQQTTRD.js → web-XVHLX62B.js} +43 -27
  130. package/dist/web-XVHLX62B.js.map +1 -0
  131. package/package.json +6 -2
  132. package/prompts/execute.md +7 -8
  133. package/prompts/plan.md +4 -5
  134. package/prompts/pr.md +13 -6
  135. package/web/AGENTS.md +3 -3
  136. package/web/public/app.css +38 -0
  137. package/web/public/app.js +132 -10
  138. package/web/public/index.html +24 -0
  139. package/dist/analyze-XV3K25K3.js.map +0 -1
  140. package/dist/chunk-34WNTK3K.js.map +0 -1
  141. package/dist/chunk-3BOHTH4V.js +0 -58
  142. package/dist/chunk-3BOHTH4V.js.map +0 -1
  143. package/dist/chunk-47VIN33V.js.map +0 -1
  144. package/dist/chunk-6N7LZZ5U.js.map +0 -1
  145. package/dist/chunk-AAY6KM7N.js.map +0 -1
  146. package/dist/chunk-BXCPXRBR.js.map +0 -1
  147. package/dist/chunk-EWDJENFW.js +0 -303
  148. package/dist/chunk-EWDJENFW.js.map +0 -1
  149. package/dist/chunk-EY6WMHV2.js.map +0 -1
  150. package/dist/chunk-GANSDJV7.js.map +0 -1
  151. package/dist/chunk-HC2MT7BX.js.map +0 -1
  152. package/dist/chunk-HDV3YDTG.js +0 -52
  153. package/dist/chunk-HDV3YDTG.js.map +0 -1
  154. package/dist/chunk-IVHZEYAX.js.map +0 -1
  155. package/dist/chunk-KQ4JQN7T.js.map +0 -1
  156. package/dist/chunk-LVKQ2AKT.js.map +0 -1
  157. package/dist/chunk-OBLVLIUS.js.map +0 -1
  158. package/dist/chunk-RKTEV6EG.js.map +0 -1
  159. package/dist/chunk-VQ5VN7DF.js +0 -1018
  160. package/dist/chunk-VQ5VN7DF.js.map +0 -1
  161. package/dist/config-4THHOXNN.js +0 -41
  162. package/dist/execute-5OEKLM5X.js +0 -15
  163. package/dist/init-PRMOTQ7N.js +0 -9
  164. package/dist/plan-Y2BYZD24.js +0 -17
  165. package/dist/policy-XQW5NUIE.js.map +0 -1
  166. package/dist/pr-H6GLOSXF.js +0 -21
  167. package/dist/pr-review-CK5FR6WX.js +0 -17
  168. package/dist/prd-4PWLCGH5.js +0 -17
  169. package/dist/review-QGVAHEZF.js +0 -15
  170. package/dist/run-DBF2VWUN.js.map +0 -1
  171. package/dist/web-ANQQTTRD.js.map +0 -1
  172. /package/dist/{chunk-HQYXVIH6.js.map → chunk-MIVHK5YI.js.map} +0 -0
  173. /package/dist/{config-4THHOXNN.js.map → config-MEM2IWWN.js.map} +0 -0
  174. /package/dist/{execute-5OEKLM5X.js.map → contract-JH2WBIO6.js.map} +0 -0
  175. /package/dist/{init-PRMOTQ7N.js.map → execute-NITIHHCK.js.map} +0 -0
  176. /package/dist/{plan-Y2BYZD24.js.map → git-YRVRUN3C.js.map} +0 -0
  177. /package/dist/{pr-H6GLOSXF.js.map → init-OKWEC3EV.js.map} +0 -0
  178. /package/dist/{pr-review-CK5FR6WX.js.map → permissions-7NMDFU27.js.map} +0 -0
  179. /package/dist/{prd-4PWLCGH5.js.map → plan-LEJJF3DE.js.map} +0 -0
  180. /package/dist/{review-QGVAHEZF.js.map → pr-HPVUZ7S3.js.map} +0 -0
@@ -0,0 +1,658 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ emptyRelations,
4
+ uniqueIds
5
+ } from "./chunk-KMMASOA6.js";
6
+ import {
7
+ isoNow,
8
+ loadTaskPlan,
9
+ saveTaskPlan
10
+ } from "./chunk-DIG5XKQQ.js";
11
+ import {
12
+ executionPlanSchema
13
+ } from "./chunk-NEZZ6KDR.js";
14
+ import {
15
+ writeFileAtomic
16
+ } from "./chunk-7Z4Y3VKM.js";
17
+
18
+ // src/core/pipeline.ts
19
+ var PIPELINE_PHASES = ["init", "prd", "plan", "execute", "review", "pr"];
20
+ var PIPELINE_PHASES_WITH_PR_REVIEW = [...PIPELINE_PHASES, "pr-review"];
21
+ var PIPELINE_PHASES_NO_BRANCH = PIPELINE_PHASES.filter(
22
+ (p) => p !== "pr"
23
+ );
24
+ var PHASE_TO_FIELD = {
25
+ init: null,
26
+ prd: "prdCompleted",
27
+ plan: "jsonCompleted",
28
+ execute: "executionCompleted",
29
+ review: "reviewCompleted",
30
+ pr: "prCreated",
31
+ "pr-review": "prReviewCompleted"
32
+ };
33
+ var PipelineManager = class {
34
+ tasksJsonPath;
35
+ plan;
36
+ activePhases;
37
+ constructor(plan, tasksJsonPath, activePhases) {
38
+ this.plan = plan;
39
+ this.tasksJsonPath = tasksJsonPath;
40
+ this.activePhases = activePhases ?? PIPELINE_PHASES;
41
+ }
42
+ /**
43
+ * Reload state from disk.
44
+ */
45
+ async reload() {
46
+ this.plan = await loadTaskPlan(this.tasksJsonPath);
47
+ }
48
+ /**
49
+ * Check if a phase is complete.
50
+ */
51
+ isPhaseComplete(phase) {
52
+ const field = PHASE_TO_FIELD[phase];
53
+ if (field === null) return true;
54
+ return this.plan.pipeline?.[field] ?? false;
55
+ }
56
+ /**
57
+ * Get the first incomplete phase in the pipeline.
58
+ * Returns null if all phases are complete.
59
+ */
60
+ getNextPhase() {
61
+ for (const phase of this.activePhases) {
62
+ if (!this.isPhaseComplete(phase)) {
63
+ return phase;
64
+ }
65
+ }
66
+ return null;
67
+ }
68
+ /**
69
+ * Get the current phase (alias for getNextPhase).
70
+ */
71
+ getCurrentPhase() {
72
+ return this.getNextPhase();
73
+ }
74
+ /**
75
+ * Check whether we can resume from a specific phase.
76
+ * All prerequisite phases must be complete.
77
+ */
78
+ canResume(fromPhase) {
79
+ const idx = this.activePhases.indexOf(fromPhase);
80
+ if (idx < 0) return false;
81
+ for (let i = 0; i < idx; i++) {
82
+ if (!this.isPhaseComplete(this.activePhases[i])) {
83
+ return false;
84
+ }
85
+ }
86
+ return true;
87
+ }
88
+ /**
89
+ * Mark a phase as complete and persist to disk.
90
+ */
91
+ async markPhaseComplete(phase) {
92
+ const field = PHASE_TO_FIELD[phase];
93
+ if (field === null) return;
94
+ this.plan = {
95
+ ...this.plan,
96
+ pipeline: {
97
+ ...this.plan.pipeline ?? {
98
+ prdCompleted: false,
99
+ jsonCompleted: false,
100
+ executionCompleted: false,
101
+ reviewCompleted: false,
102
+ prCreated: false
103
+ },
104
+ [field]: true
105
+ }
106
+ };
107
+ await saveTaskPlan(this.tasksJsonPath, this.plan);
108
+ }
109
+ };
110
+
111
+ // src/execution/plan.ts
112
+ import { mkdir, readFile } from "fs/promises";
113
+ import { dirname } from "path";
114
+ import { ZodError } from "zod";
115
+
116
+ // src/execution/containers.ts
117
+ var DEFAULT_CONTAINER_CONFIG = {
118
+ detect: ["children"],
119
+ issueTypes: ["Epic"],
120
+ labels: ["epic"],
121
+ titlePrefixes: ["[Epic]"]
122
+ };
123
+ function enabled(config, signal) {
124
+ return config.detect.includes(signal);
125
+ }
126
+ function hasTitlePrefix(title, prefixes) {
127
+ const trimmed = title.trim();
128
+ return prefixes.some((prefix) => trimmed.toLowerCase().startsWith(prefix.toLowerCase()));
129
+ }
130
+ function isContainer(probe, config = DEFAULT_CONTAINER_CONFIG) {
131
+ if (enabled(config, "children") && probe.children.length > 0) return true;
132
+ const issue = probe.issue;
133
+ if (issue === null) return false;
134
+ if (enabled(config, "issue-type")) {
135
+ const type = issue.type?.trim();
136
+ if (type !== void 0 && type !== "" && config.issueTypes.some((name) => name.toLowerCase() === type.toLowerCase())) {
137
+ return true;
138
+ }
139
+ }
140
+ if (enabled(config, "label")) {
141
+ const labels = new Set(issue.labels.map((label) => label.toLowerCase()));
142
+ if (config.labels.some((label) => labels.has(label.toLowerCase()))) return true;
143
+ }
144
+ if (enabled(config, "title-prefix") && hasTitlePrefix(issue.title, config.titlePrefixes)) {
145
+ return true;
146
+ }
147
+ return false;
148
+ }
149
+ function collectCascadeIds(graph, roots) {
150
+ const ids = /* @__PURE__ */ new Set();
151
+ const walk = (id) => {
152
+ if (ids.has(id)) return;
153
+ ids.add(id);
154
+ for (const child of graph.nodes.get(id)?.relations.children ?? []) {
155
+ walk(child);
156
+ }
157
+ };
158
+ for (const root of roots) walk(root);
159
+ return [...ids];
160
+ }
161
+
162
+ // src/issues/graph.ts
163
+ var DEFAULT_MAX_NODES = 25;
164
+ var DEFAULT_MAX_DEPTH = 3;
165
+ function dependencyEdges(graph) {
166
+ const edges = [];
167
+ const seen = /* @__PURE__ */ new Set();
168
+ const add = (from, to) => {
169
+ if (from === to || !graph.nodes.has(from) || !graph.nodes.has(to)) return;
170
+ const key = `${from}->${to}`;
171
+ if (seen.has(key)) return;
172
+ seen.add(key);
173
+ edges.push({ from, to });
174
+ };
175
+ for (const node of graph.nodes.values()) {
176
+ for (const blocker of node.relations.blockedBy) {
177
+ add(blocker, node.id);
178
+ }
179
+ for (const blocked of node.relations.blocking) {
180
+ add(node.id, blocked);
181
+ }
182
+ }
183
+ return edges;
184
+ }
185
+ function findCycles(graph) {
186
+ const adjacency = /* @__PURE__ */ new Map();
187
+ for (const { from, to } of dependencyEdges(graph)) {
188
+ const list = adjacency.get(from);
189
+ if (list === void 0) {
190
+ adjacency.set(from, [to]);
191
+ } else {
192
+ list.push(to);
193
+ }
194
+ }
195
+ const cycles = [];
196
+ const reported = /* @__PURE__ */ new Set();
197
+ const state = /* @__PURE__ */ new Map();
198
+ const stack = [];
199
+ const record = (cycle) => {
200
+ let pivot = 0;
201
+ for (let i = 1; i < cycle.length; i++) {
202
+ if (compareIds(cycle[i], cycle[pivot]) < 0) pivot = i;
203
+ }
204
+ const normalized = [...cycle.slice(pivot), ...cycle.slice(0, pivot)];
205
+ const key = normalized.join("->");
206
+ if (reported.has(key)) return;
207
+ reported.add(key);
208
+ cycles.push(normalized);
209
+ };
210
+ const visit = (id) => {
211
+ state.set(id, "visiting");
212
+ stack.push(id);
213
+ for (const next of adjacency.get(id) ?? []) {
214
+ const status = state.get(next);
215
+ if (status === "visiting") {
216
+ record(stack.slice(stack.indexOf(next)));
217
+ continue;
218
+ }
219
+ if (status === void 0) {
220
+ visit(next);
221
+ }
222
+ }
223
+ stack.pop();
224
+ state.set(id, "done");
225
+ };
226
+ for (const id of graph.nodes.keys()) {
227
+ if (!state.has(id)) visit(id);
228
+ }
229
+ return cycles;
230
+ }
231
+ function compareIds(a, b) {
232
+ const na = Number(a);
233
+ const nb = Number(b);
234
+ if (Number.isInteger(na) && Number.isInteger(nb)) {
235
+ return na - nb;
236
+ }
237
+ return a < b ? -1 : a > b ? 1 : 0;
238
+ }
239
+ function neighbours(relations) {
240
+ return uniqueIds(
241
+ [
242
+ ...relations.parent === null ? [] : [relations.parent],
243
+ ...relations.children,
244
+ ...relations.blockedBy,
245
+ ...relations.blocking
246
+ ],
247
+ relations.id
248
+ );
249
+ }
250
+ async function buildDependencyGraph(roots, fetchRelations, options = {}) {
251
+ const maxNodes = options.maxNodes ?? DEFAULT_MAX_NODES;
252
+ const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
253
+ const warn = options.warn ?? (() => {
254
+ });
255
+ const known = /* @__PURE__ */ new Map();
256
+ for (const issue of options.known ?? []) {
257
+ known.set(issue.id, issue);
258
+ }
259
+ const rootIds = uniqueIds(roots);
260
+ const nodes = /* @__PURE__ */ new Map();
261
+ const queued = new Set(rootIds);
262
+ let truncated = false;
263
+ let frontier = rootIds.map((id) => ({ id, depth: 0 }));
264
+ while (frontier.length > 0) {
265
+ const next = [];
266
+ for (const { id, depth } of frontier) {
267
+ if (nodes.size >= maxNodes) {
268
+ truncated = true;
269
+ break;
270
+ }
271
+ let relations = null;
272
+ try {
273
+ relations = await fetchRelations(id);
274
+ } catch (err) {
275
+ warn(
276
+ `Could not read the relations of issue '${id}': ${err instanceof Error ? err.message : String(err)}`
277
+ );
278
+ }
279
+ let issue = known.get(id) ?? null;
280
+ if (issue === null && options.fetchIssue !== void 0) {
281
+ try {
282
+ issue = await options.fetchIssue(id);
283
+ } catch (err) {
284
+ warn(`Could not read issue '${id}': ${err instanceof Error ? err.message : String(err)}`);
285
+ }
286
+ }
287
+ const resolved = relations ?? emptyRelations(id);
288
+ nodes.set(id, { id, relations: resolved, depth, issue, root: rootIds.includes(id) });
289
+ if (depth >= maxDepth) {
290
+ if (neighbours(resolved).some((neighbour) => !queued.has(neighbour))) {
291
+ truncated = true;
292
+ }
293
+ continue;
294
+ }
295
+ for (const neighbour of neighbours(resolved)) {
296
+ if (queued.has(neighbour)) continue;
297
+ queued.add(neighbour);
298
+ next.push({ id: neighbour, depth: depth + 1 });
299
+ }
300
+ }
301
+ if (nodes.size >= maxNodes && next.length > 0) {
302
+ truncated = true;
303
+ break;
304
+ }
305
+ frontier = next;
306
+ }
307
+ const graph = {
308
+ roots: rootIds,
309
+ nodes,
310
+ cycles: [],
311
+ truncated,
312
+ limits: { maxNodes, maxDepth }
313
+ };
314
+ linkReferencedBy(graph);
315
+ graph.cycles = findCycles(graph);
316
+ return graph;
317
+ }
318
+ function linkReferencedBy(graph) {
319
+ for (const node of graph.nodes.values()) {
320
+ for (const cited of node.relations.references) {
321
+ const target = graph.nodes.get(cited);
322
+ if (target === void 0 || target.id === node.id) continue;
323
+ target.relations.referencedBy = uniqueIds(
324
+ [...target.relations.referencedBy, node.id],
325
+ target.id
326
+ );
327
+ }
328
+ }
329
+ }
330
+
331
+ // src/execution/order.ts
332
+ var PRIORITY_LABELS = {
333
+ high: "high",
334
+ medium: "medium",
335
+ low: "low"
336
+ };
337
+ var PRIORITY_RANK = { high: 0, medium: 1, low: 2 };
338
+ var NO_PRIORITY_RANK = 3;
339
+ function priorityOf(labels) {
340
+ for (const label of labels) {
341
+ const normalized = label.trim().toLowerCase();
342
+ const direct = PRIORITY_LABELS[normalized];
343
+ if (direct !== void 0) return direct;
344
+ const qualified = /^(?:priority|prio|p)\s*[:/-]\s*(high|medium|low)$/.exec(normalized);
345
+ const matched = qualified?.[1];
346
+ if (matched !== void 0) return PRIORITY_LABELS[matched];
347
+ }
348
+ return null;
349
+ }
350
+ function hierarchyDepth(id, parentOf2, selected) {
351
+ let depth = 0;
352
+ let current = parentOf2.get(id) ?? null;
353
+ const seen = /* @__PURE__ */ new Set([id]);
354
+ while (current !== null && selected.has(current) && !seen.has(current)) {
355
+ depth++;
356
+ seen.add(current);
357
+ current = parentOf2.get(current) ?? null;
358
+ }
359
+ return depth;
360
+ }
361
+ function computeExecutionOrder(graph, options = {}) {
362
+ const selected = new Set(
363
+ (options.include ?? [...graph.nodes.keys()]).filter((id) => graph.nodes.has(id))
364
+ );
365
+ const parentOf2 = /* @__PURE__ */ new Map();
366
+ const priority = /* @__PURE__ */ new Map();
367
+ for (const id of selected) {
368
+ const node = graph.nodes.get(id);
369
+ parentOf2.set(id, node?.relations.parent ?? null);
370
+ priority.set(id, priorityOf(node?.issue?.labels ?? []));
371
+ }
372
+ const incoming = /* @__PURE__ */ new Map();
373
+ const outgoing = /* @__PURE__ */ new Map();
374
+ for (const id of selected) {
375
+ incoming.set(id, /* @__PURE__ */ new Set());
376
+ outgoing.set(id, []);
377
+ }
378
+ for (const { from, to } of dependencyEdges(graph)) {
379
+ if (!selected.has(from) || !selected.has(to) || from === to) continue;
380
+ incoming.get(to)?.add(from);
381
+ outgoing.get(from)?.push(to);
382
+ }
383
+ const depth = /* @__PURE__ */ new Map();
384
+ for (const id of selected) {
385
+ depth.set(id, hierarchyDepth(id, parentOf2, selected));
386
+ }
387
+ const rank = (id) => {
388
+ const value = priority.get(id) ?? null;
389
+ return value === null ? NO_PRIORITY_RANK : PRIORITY_RANK[value];
390
+ };
391
+ const better = (a, b) => {
392
+ const byHierarchy = (depth.get(a) ?? 0) - (depth.get(b) ?? 0);
393
+ if (byHierarchy !== 0) return byHierarchy;
394
+ const byPriority = rank(a) - rank(b);
395
+ if (byPriority !== 0) return byPriority;
396
+ return compareIds(a, b);
397
+ };
398
+ const ready = [...selected].filter((id) => (incoming.get(id)?.size ?? 0) === 0);
399
+ const order = [];
400
+ while (ready.length > 0) {
401
+ ready.sort(better);
402
+ const next = ready.shift();
403
+ order.push(next);
404
+ for (const dependent of outgoing.get(next) ?? []) {
405
+ const pending = incoming.get(dependent);
406
+ if (pending === void 0) continue;
407
+ pending.delete(next);
408
+ if (pending.size === 0) {
409
+ ready.push(dependent);
410
+ }
411
+ }
412
+ }
413
+ if (order.length !== selected.size) {
414
+ const cycles = graph.cycles.length > 0 ? graph.cycles : findCycles(graph);
415
+ const relevant = cycles.filter((cycle) => cycle.every((id) => selected.has(id)));
416
+ return { ok: false, cycles: relevant.length > 0 ? relevant : cycles };
417
+ }
418
+ return { ok: true, order };
419
+ }
420
+ function describeCycles(cycles) {
421
+ return cycles.map((cycle) => [...cycle, cycle[0]].map((id) => `#${id}`).join(" \u2192 ")).join("; ");
422
+ }
423
+
424
+ // src/execution/plan.ts
425
+ function parentOf(graph, id) {
426
+ for (const node of graph.nodes.values()) {
427
+ if (node.relations.children.includes(id)) return node.id;
428
+ }
429
+ return null;
430
+ }
431
+ function describeIssue(issue, id) {
432
+ const parsed = Number.parseInt(id, 10);
433
+ return {
434
+ number: issue?.number ?? (Number.isNaN(parsed) ? null : parsed),
435
+ title: issue?.title ?? "",
436
+ url: issue?.remoteRef ?? null,
437
+ source: issue?.source ?? "github"
438
+ };
439
+ }
440
+ function buildExecutionPlan(input) {
441
+ const now = input.now ?? isoNow;
442
+ const at = now();
443
+ const scheduled = new Set(input.order);
444
+ const requested = new Set(input.requested);
445
+ const issues = input.order.map((id, index) => {
446
+ const node = input.graph.nodes.get(id);
447
+ const relations = node?.relations;
448
+ const described = describeIssue(node?.issue ?? null, id);
449
+ const children = relations?.children ?? [];
450
+ const role = isContainer({
451
+ issue: node?.issue ?? null,
452
+ children
453
+ }) ? "container" : "executable";
454
+ return {
455
+ id,
456
+ number: described.number,
457
+ title: described.title,
458
+ url: described.url,
459
+ source: described.source,
460
+ position: index + 1,
461
+ status: "pending",
462
+ origin: requested.has(id) ? "requested" : "discovered",
463
+ role,
464
+ externalDependencies: (relations?.blockedBy ?? []).filter(
465
+ (blocker) => !scheduled.has(blocker)
466
+ ),
467
+ // Only the constraints that survive inside the queue: a blocker left out
468
+ // of it is reported in `excluded`, not enforced as a dependency.
469
+ dependsOn: (relations?.blockedBy ?? []).filter((blocker) => scheduled.has(blocker)),
470
+ parent: relations?.parent ?? parentOf(input.graph, id),
471
+ priority: priorityOf(node?.issue?.labels ?? []),
472
+ heuristic: (relations?.heuristic.length ?? 0) > 0,
473
+ failedPhase: null,
474
+ lastError: null,
475
+ attempts: 0,
476
+ blockedReason: null,
477
+ startedAt: null,
478
+ completedAt: null
479
+ };
480
+ });
481
+ const excluded = [];
482
+ for (const node of input.graph.nodes.values()) {
483
+ if (scheduled.has(node.id)) continue;
484
+ const described = describeIssue(node.issue, node.id);
485
+ excluded.push({
486
+ id: node.id,
487
+ number: described.number,
488
+ title: described.title,
489
+ url: described.url,
490
+ reason: "Discovered in the hierarchy but not selected for this run"
491
+ });
492
+ }
493
+ return {
494
+ schemaVersion: 1,
495
+ id: input.requested[0] ?? input.order[0],
496
+ project: input.projectId,
497
+ requested: [...input.requested],
498
+ branchName: null,
499
+ noBranch: input.noBranch ?? false,
500
+ prReview: input.prReview ?? false,
501
+ status: "pending",
502
+ createdAt: at,
503
+ updatedAt: at,
504
+ truncated: input.graph.truncated,
505
+ issues,
506
+ excluded
507
+ };
508
+ }
509
+ async function loadExecutionPlan(path) {
510
+ const raw = JSON.parse(await readFile(path, "utf-8"));
511
+ try {
512
+ return executionPlanSchema.parse(raw);
513
+ } catch (err) {
514
+ if (err instanceof ZodError) {
515
+ const issues = err.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
516
+ throw new Error(`Invalid execution-plan.json at ${path}:
517
+ ${issues}`);
518
+ }
519
+ throw err;
520
+ }
521
+ }
522
+ async function saveExecutionPlan(path, plan, now = isoNow) {
523
+ await mkdir(dirname(path), { recursive: true });
524
+ const stamped = { ...plan, updatedAt: now() };
525
+ await writeFileAtomic(path, `${JSON.stringify(stamped, null, 2)}
526
+ `);
527
+ }
528
+ function dependenciesSatisfied(plan, entry) {
529
+ return entry.dependsOn.every((id) => {
530
+ const dependency = plan.issues.find((candidate) => candidate.id === id);
531
+ if (dependency === void 0) return true;
532
+ if (dependency.role === "container") return true;
533
+ return dependency.status === "completed";
534
+ });
535
+ }
536
+ function nextQueueIssue(plan, options = {}) {
537
+ const exclude = options.exclude ?? /* @__PURE__ */ new Set();
538
+ const eligible = (status) => (entry) => entry.role !== "container" && entry.status === status && !exclude.has(entry.id) && dependenciesSatisfied(plan, entry);
539
+ return plan.issues.find(eligible("in_progress")) ?? plan.issues.find(eligible("failed")) ?? plan.issues.find(eligible("pending")) ?? plan.issues.find(eligible("skipped")) ?? null;
540
+ }
541
+ function queueStatus(plan) {
542
+ if (plan.issues.some((entry) => entry.status === "failed" || entry.status === "blocked")) {
543
+ return "failed";
544
+ }
545
+ if (plan.issues.every((entry) => entry.status === "completed")) return "completed";
546
+ if (plan.issues.some((entry) => entry.status !== "pending")) return "in_progress";
547
+ return "pending";
548
+ }
549
+ function isQueueComplete(plan) {
550
+ return plan.issues.every((entry) => entry.status === "completed");
551
+ }
552
+ function updateIssue(plan, id, patch) {
553
+ const issues = plan.issues.map((entry) => entry.id === id ? { ...entry, ...patch } : entry);
554
+ const next = { ...plan, issues };
555
+ return { ...next, status: queueStatus(next) };
556
+ }
557
+ function markQueueIssueInProgress(plan, id, now = isoNow) {
558
+ const current = plan.issues.find((entry) => entry.id === id);
559
+ return updateIssue(plan, id, {
560
+ status: "in_progress",
561
+ // Preserved across a resume: the queue reports when the work on this Issue
562
+ // really began, not when it was last picked up.
563
+ startedAt: current?.startedAt ?? now(),
564
+ failedPhase: null,
565
+ lastError: null
566
+ });
567
+ }
568
+ function completeResolvedContainers(plan, now) {
569
+ let next = plan;
570
+ let changed = true;
571
+ while (changed) {
572
+ changed = false;
573
+ for (const entry of next.issues) {
574
+ if (entry.role !== "container" || entry.status === "completed") continue;
575
+ const children = next.issues.filter((candidate) => candidate.parent === entry.id);
576
+ if (children.length === 0) continue;
577
+ if (children.every((child) => child.status === "completed")) {
578
+ next = updateIssue(next, entry.id, {
579
+ status: "completed",
580
+ completedAt: now(),
581
+ failedPhase: null,
582
+ lastError: null
583
+ });
584
+ changed = true;
585
+ }
586
+ }
587
+ }
588
+ return next;
589
+ }
590
+ function markQueueIssueCompleted(plan, id, now = isoNow) {
591
+ return completeResolvedContainers(
592
+ updateIssue(plan, id, {
593
+ status: "completed",
594
+ completedAt: now(),
595
+ failedPhase: null,
596
+ lastError: null
597
+ }),
598
+ now
599
+ );
600
+ }
601
+ function markQueueIssueFailed(plan, id, failure) {
602
+ const current = plan.issues.find((entry) => entry.id === id);
603
+ return updateIssue(plan, id, {
604
+ status: "failed",
605
+ failedPhase: failure.phase,
606
+ lastError: failure.error,
607
+ attempts: (current?.attempts ?? 0) + 1
608
+ });
609
+ }
610
+ function markQueueIssueSkipped(plan, id, failure) {
611
+ const current = plan.issues.find((entry) => entry.id === id);
612
+ return updateIssue(plan, id, {
613
+ status: "skipped",
614
+ failedPhase: failure.phase,
615
+ lastError: failure.error,
616
+ attempts: (current?.attempts ?? 0) + 1
617
+ });
618
+ }
619
+ function markQueueIssueBlocked(plan, id, reason, failure) {
620
+ const current = plan.issues.find((entry) => entry.id === id);
621
+ return updateIssue(plan, id, {
622
+ status: "blocked",
623
+ blockedReason: reason,
624
+ failedPhase: failure.phase,
625
+ lastError: failure.error,
626
+ attempts: (current?.attempts ?? 0) + 1
627
+ });
628
+ }
629
+ function setQueueBranch(plan, branchName) {
630
+ return { ...plan, branchName };
631
+ }
632
+ function setQueuePullRequest(plan, pullRequest) {
633
+ return { ...plan, pullRequest };
634
+ }
635
+
636
+ export {
637
+ PIPELINE_PHASES,
638
+ PIPELINE_PHASES_WITH_PR_REVIEW,
639
+ PIPELINE_PHASES_NO_BRANCH,
640
+ PipelineManager,
641
+ collectCascadeIds,
642
+ buildDependencyGraph,
643
+ computeExecutionOrder,
644
+ describeCycles,
645
+ buildExecutionPlan,
646
+ loadExecutionPlan,
647
+ saveExecutionPlan,
648
+ nextQueueIssue,
649
+ isQueueComplete,
650
+ markQueueIssueInProgress,
651
+ markQueueIssueCompleted,
652
+ markQueueIssueFailed,
653
+ markQueueIssueSkipped,
654
+ markQueueIssueBlocked,
655
+ setQueueBranch,
656
+ setQueuePullRequest
657
+ };
658
+ //# sourceMappingURL=chunk-7KD66LIM.js.map