@backburner/cli 0.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.
Files changed (202) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +329 -0
  3. package/dist/src/agents/antigravity-launch.js +162 -0
  4. package/dist/src/agents/antigravity.js +251 -0
  5. package/dist/src/agents/claude-launch.js +117 -0
  6. package/dist/src/agents/claude.js +271 -0
  7. package/dist/src/agents/codex-launch.js +82 -0
  8. package/dist/src/agents/codex.js +261 -0
  9. package/dist/src/agents/composite-runner.js +13 -0
  10. package/dist/src/agents/extraction.js +442 -0
  11. package/dist/src/agents/failure-feedback.js +132 -0
  12. package/dist/src/agents/gemini-launch.js +96 -0
  13. package/dist/src/agents/gemini.js +392 -0
  14. package/dist/src/agents/job-agent-assignments.js +180 -0
  15. package/dist/src/agents/lifecycle-feedback.js +147 -0
  16. package/dist/src/agents/limit-detector.js +83 -0
  17. package/dist/src/agents/opencode-launch.js +138 -0
  18. package/dist/src/agents/opencode.js +156 -0
  19. package/dist/src/agents/plan-breakdown-feedback.js +97 -0
  20. package/dist/src/agents/pool.js +168 -0
  21. package/dist/src/agents/prompt.js +462 -0
  22. package/dist/src/agents/provider-sessions.js +105 -0
  23. package/dist/src/agents/review-feedback.js +255 -0
  24. package/dist/src/agents/selection.js +113 -0
  25. package/dist/src/agents/service.js +1087 -0
  26. package/dist/src/agents/types.js +1 -0
  27. package/dist/src/agents/wave-assessment-feedback.js +69 -0
  28. package/dist/src/attention/derive.js +337 -0
  29. package/dist/src/capabilities/index.js +2 -0
  30. package/dist/src/capabilities/projection.js +183 -0
  31. package/dist/src/capabilities/types.js +1 -0
  32. package/dist/src/capabilities/validator.js +87 -0
  33. package/dist/src/cli/commands/broker-smoke.js +96 -0
  34. package/dist/src/cli/commands/broker.js +56 -0
  35. package/dist/src/cli/commands/init.js +4 -0
  36. package/dist/src/cli/commands/journal.js +43 -0
  37. package/dist/src/cli/commands/run-loop.js +56 -0
  38. package/dist/src/cli/commands/run.js +1241 -0
  39. package/dist/src/cli/commands/tui.js +187 -0
  40. package/dist/src/cli/dispatcher.js +51 -0
  41. package/dist/src/cli/errors.js +2 -0
  42. package/dist/src/cli/init.js +92 -0
  43. package/dist/src/cli/options.js +86 -0
  44. package/dist/src/cli/output.js +231 -0
  45. package/dist/src/cli/run-journal.js +342 -0
  46. package/dist/src/cli/run-types.js +1 -0
  47. package/dist/src/cli/run.js +32 -0
  48. package/dist/src/cli/runtime/dispatch-lifecycle.js +772 -0
  49. package/dist/src/cli/runtime/provider-runtime-types.js +1 -0
  50. package/dist/src/cli/runtime/provider-runtime.js +152 -0
  51. package/dist/src/cli/runtime/provider-tools.js +73 -0
  52. package/dist/src/cli/runtime/providers/antigravity.js +26 -0
  53. package/dist/src/cli/runtime/providers/claude.js +20 -0
  54. package/dist/src/cli/runtime/providers/codex.js +20 -0
  55. package/dist/src/cli/runtime/providers/gemini.js +20 -0
  56. package/dist/src/cli/runtime/providers/opencode.js +20 -0
  57. package/dist/src/cli/runtime/run-context.js +131 -0
  58. package/dist/src/cli/tui/index.js +3 -0
  59. package/dist/src/cli/tui/screen.js +388 -0
  60. package/dist/src/cli/tui/tracker.js +558 -0
  61. package/dist/src/cli/tui/types.js +1 -0
  62. package/dist/src/cli/tui/views.js +1006 -0
  63. package/dist/src/config/loader.js +136 -0
  64. package/dist/src/config/schemas.js +403 -0
  65. package/dist/src/config/types.js +1 -0
  66. package/dist/src/context/types.js +331 -0
  67. package/dist/src/domain/entities.js +1 -0
  68. package/dist/src/git/backburner-git-tool-service.js +680 -0
  69. package/dist/src/git/gitops-lifecycle.js +56 -0
  70. package/dist/src/git/merge-prep-workspace.js +357 -0
  71. package/dist/src/git/types.js +484 -0
  72. package/dist/src/github/broker-model-content.js +215 -0
  73. package/dist/src/github/broker-server.js +237 -0
  74. package/dist/src/github/broker.js +638 -0
  75. package/dist/src/github/composed.js +53 -0
  76. package/dist/src/github/gateway.js +478 -0
  77. package/dist/src/github/normalize.js +222 -0
  78. package/dist/src/github/pr-classification.js +73 -0
  79. package/dist/src/github/scoped-broker.js +35 -0
  80. package/dist/src/github/security.js +126 -0
  81. package/dist/src/github/service.js +332 -0
  82. package/dist/src/github/types.js +1 -0
  83. package/dist/src/github/utils.js +16 -0
  84. package/dist/src/journal/index.js +4 -0
  85. package/dist/src/journal/reader.js +62 -0
  86. package/dist/src/journal/renderer.js +50 -0
  87. package/dist/src/journal/service.js +72 -0
  88. package/dist/src/journal/types.js +1 -0
  89. package/dist/src/journal/writer.js +19 -0
  90. package/dist/src/mcps/bridge.js +166 -0
  91. package/dist/src/mcps/index.js +5 -0
  92. package/dist/src/mcps/reconcile.js +113 -0
  93. package/dist/src/mcps/smithery-client.js +186 -0
  94. package/dist/src/mcps/smithery.js +164 -0
  95. package/dist/src/mcps/types.js +1 -0
  96. package/dist/src/memory/json-store.js +71 -0
  97. package/dist/src/memory/prompt.js +16 -0
  98. package/dist/src/memory/scope.js +31 -0
  99. package/dist/src/memory/store.js +1 -0
  100. package/dist/src/memory/tool-service.js +142 -0
  101. package/dist/src/memory/types.js +12 -0
  102. package/dist/src/onboarding/config-writer.js +96 -0
  103. package/dist/src/onboarding/engine.js +86 -0
  104. package/dist/src/onboarding/prompt.js +102 -0
  105. package/dist/src/onboarding/services/provider-discovery.js +58 -0
  106. package/dist/src/onboarding/services/repo-discovery.js +246 -0
  107. package/dist/src/onboarding/steps/check-github-auth.js +32 -0
  108. package/dist/src/onboarding/steps/check-requirements.js +34 -0
  109. package/dist/src/onboarding/steps/configure-models.js +105 -0
  110. package/dist/src/onboarding/steps/configure-paths.js +33 -0
  111. package/dist/src/onboarding/steps/discover-providers.js +38 -0
  112. package/dist/src/onboarding/steps/discover-repos.js +39 -0
  113. package/dist/src/onboarding/steps/generate-config.js +106 -0
  114. package/dist/src/onboarding/steps/readiness-validation.js +158 -0
  115. package/dist/src/onboarding/steps/select-repos.js +76 -0
  116. package/dist/src/onboarding/steps/show-summary.js +97 -0
  117. package/dist/src/onboarding/steps/welcome.js +27 -0
  118. package/dist/src/onboarding/types.js +1 -0
  119. package/dist/src/onboarding/validators/git-access.js +63 -0
  120. package/dist/src/onboarding/validators/github-auth.js +85 -0
  121. package/dist/src/onboarding/validators/tool-checker.js +153 -0
  122. package/dist/src/onboarding/validators/workspace.js +83 -0
  123. package/dist/src/prompts/resolver.js +106 -0
  124. package/dist/src/retrospectives/candidates.js +224 -0
  125. package/dist/src/retrospectives/derive.js +169 -0
  126. package/dist/src/retrospectives/ingest.js +321 -0
  127. package/dist/src/retrospectives/management-prs.js +380 -0
  128. package/dist/src/retrospectives/proposals.js +199 -0
  129. package/dist/src/retrospectives/scopes.js +272 -0
  130. package/dist/src/retrospectives/scoring.js +171 -0
  131. package/dist/src/retrospectives/types.js +1 -0
  132. package/dist/src/state/loader.js +473 -0
  133. package/dist/src/state/types.js +15 -0
  134. package/dist/src/tasks/derivation/builders.js +70 -0
  135. package/dist/src/tasks/derivation/handlers/comment-response.js +169 -0
  136. package/dist/src/tasks/derivation/handlers/implement-plan.js +103 -0
  137. package/dist/src/tasks/derivation/handlers/index.js +54 -0
  138. package/dist/src/tasks/derivation/handlers/packet-pr-control.js +92 -0
  139. package/dist/src/tasks/derivation/handlers/packet-worktree.js +29 -0
  140. package/dist/src/tasks/derivation/handlers/plan-breakdown.js +45 -0
  141. package/dist/src/tasks/derivation/handlers/prepare-for-merge.js +91 -0
  142. package/dist/src/tasks/derivation/handlers/product-discovery.js +36 -0
  143. package/dist/src/tasks/derivation/handlers/retry-failed-task.js +125 -0
  144. package/dist/src/tasks/derivation/handlers/review-pr.js +28 -0
  145. package/dist/src/tasks/derivation/handlers/shared.js +24 -0
  146. package/dist/src/tasks/derivation/handlers/sync-parent-branch.js +100 -0
  147. package/dist/src/tasks/derivation/handlers/wave-assessment.js +223 -0
  148. package/dist/src/tasks/derivation/handlers/write-plan.js +101 -0
  149. package/dist/src/tasks/derivation/handlers/write-product-spec.js +49 -0
  150. package/dist/src/tasks/derivation/runner.js +18 -0
  151. package/dist/src/tasks/derivation/types.js +1 -0
  152. package/dist/src/tasks/derive.js +810 -0
  153. package/dist/src/tasks/mcp-skip.js +10 -0
  154. package/dist/src/tasks/retry.js +10 -0
  155. package/dist/src/tasks/task.js +184 -0
  156. package/dist/src/tasks/types.js +1 -0
  157. package/dist/src/utils/command-runner.js +192 -0
  158. package/dist/src/utils/json.js +38 -0
  159. package/dist/src/utils/logger.js +43 -0
  160. package/dist/src/utils/paths.js +38 -0
  161. package/dist/src/utils/slug.js +10 -0
  162. package/dist/src/workflows/registry.js +174 -0
  163. package/dist/src/workflows/triage.js +90 -0
  164. package/dist/src/workflows/workstream-machine/derive-events.js +121 -0
  165. package/dist/src/workflows/workstream-machine/evaluate.js +24 -0
  166. package/dist/src/workflows/workstream-machine/events.js +1 -0
  167. package/dist/src/workflows/workstream-machine/machine.js +355 -0
  168. package/dist/src/workflows/workstream-machine/selectors.js +19 -0
  169. package/dist/src/workflows/workstream-machine/types.js +1 -0
  170. package/dist/src/workstreams/branch-sync.js +87 -0
  171. package/dist/src/workstreams/derive.js +629 -0
  172. package/dist/src/workstreams/mcp-blockers.js +420 -0
  173. package/dist/src/workstreams/packet-candidates.js +42 -0
  174. package/dist/src/workstreams/packet-lifecycle-feedback.js +60 -0
  175. package/dist/src/workstreams/packet-lifecycle-report.js +199 -0
  176. package/dist/src/workstreams/packet-plan.js +597 -0
  177. package/dist/src/workstreams/packet-projections.js +190 -0
  178. package/dist/src/workstreams/parent-branch-sync-action.js +312 -0
  179. package/dist/src/workstreams/parent-branch-sync.js +148 -0
  180. package/dist/src/workstreams/phases.js +4 -0
  181. package/dist/src/workstreams/plan-breakdown-generator/export.js +19 -0
  182. package/dist/src/workstreams/plan-breakdown-generator/index.js +6 -0
  183. package/dist/src/workstreams/plan-breakdown-generator/inspect.js +50 -0
  184. package/dist/src/workstreams/plan-breakdown-generator/patch.js +139 -0
  185. package/dist/src/workstreams/plan-breakdown-generator/session.js +19 -0
  186. package/dist/src/workstreams/plan-breakdown-generator/store.js +27 -0
  187. package/dist/src/workstreams/plan-breakdown-generator/tools.js +407 -0
  188. package/dist/src/workstreams/plan-breakdown-generator/types.js +6 -0
  189. package/dist/src/workstreams/plan-breakdown-generator/validate.js +212 -0
  190. package/dist/src/workstreams/plan-breakdown-schema.js +175 -0
  191. package/dist/src/workstreams/utils.js +15 -0
  192. package/dist/src/workstreams/wave-assessment/checkpoint.js +172 -0
  193. package/dist/src/workstreams/wave-assessment/index.js +3 -0
  194. package/dist/src/workstreams/wave-assessment/session.js +84 -0
  195. package/dist/src/workstreams/wave-assessment/store.js +51 -0
  196. package/dist/src/workstreams/wave-assessment/tools.js +419 -0
  197. package/dist/src/workstreams/wave-assessment/types.js +1 -0
  198. package/dist/src/workstreams/wave-assessment/validate.js +78 -0
  199. package/dist/src/worktrees/service.js +839 -0
  200. package/dist/src/worktrees/workspace-ownership.js +34 -0
  201. package/dist/src/worktrees/workspace-recovery.js +177 -0
  202. package/package.json +61 -0
@@ -0,0 +1,558 @@
1
+ export class RuntimeStatusTracker {
2
+ model;
3
+ listeners = new Set();
4
+ startTime = Date.now();
5
+ lastOkTime = null;
6
+ currentCycleNo = 0;
7
+ intervalMs = 5 * 60 * 1000;
8
+ nextCycleAt = 0;
9
+ lastCycleDurationMs = 0;
10
+ mcps = [];
11
+ capabilities = { sensors: [], controllers: [] };
12
+ maxConcurrentAgents = 1;
13
+ mcpStatuses = [];
14
+ runningTasks = new Map();
15
+ configReloadError = null;
16
+ state = null;
17
+ constructor(repoCount) {
18
+ this.model = {
19
+ status: "idle",
20
+ uptime: 0,
21
+ cycleNo: "—",
22
+ repos: repoCount,
23
+ readiness: "ok",
24
+ lastOk: "—",
25
+ nextText: "—",
26
+ opText: "no active operation",
27
+ opTarget: "",
28
+ opSub: "initializing...",
29
+ cycleGlyph: "○",
30
+ cycleText: "waiting for first cycle",
31
+ cycleMeta: "—",
32
+ hints: [
33
+ { glyph: "·", text: "nothing needs you right now", color: "dim" }
34
+ ]
35
+ };
36
+ this.updateUptime();
37
+ }
38
+ register(listener) {
39
+ this.listeners.add(listener);
40
+ listener(this.model);
41
+ return () => {
42
+ this.listeners.delete(listener);
43
+ };
44
+ }
45
+ setConfigs(mcps, capabilities, maxConcurrent) {
46
+ this.mcps = mcps;
47
+ this.capabilities = capabilities;
48
+ if (maxConcurrent !== undefined) {
49
+ this.maxConcurrentAgents = maxConcurrent;
50
+ }
51
+ this.notify();
52
+ }
53
+ setMcpStatuses(statuses) {
54
+ this.mcpStatuses = statuses ?? [];
55
+ this.notify();
56
+ }
57
+ getMcps() {
58
+ return this.mcps;
59
+ }
60
+ getCapabilities() {
61
+ return this.capabilities;
62
+ }
63
+ getMaxConcurrent() {
64
+ return this.maxConcurrentAgents;
65
+ }
66
+ getMcpStatuses() {
67
+ return this.mcpStatuses;
68
+ }
69
+ getRunningTasks() {
70
+ return this.runningTasks;
71
+ }
72
+ setConfigReloadError(error) {
73
+ this.configReloadError = error;
74
+ this.notify();
75
+ }
76
+ getConfigReloadError() {
77
+ return this.configReloadError;
78
+ }
79
+ setState(state) {
80
+ this.state = state;
81
+ this.mcpStatuses = state.mcpStatuses ?? [];
82
+ this.notify();
83
+ }
84
+ getUniqueRepoKeys() {
85
+ if (!this.state)
86
+ return [];
87
+ const keys = new Set();
88
+ const workstreams = this.state.workstreams ?? [];
89
+ for (const w of workstreams) {
90
+ if (w.repoKey) {
91
+ keys.add(w.repoKey);
92
+ }
93
+ }
94
+ return Array.from(keys).sort();
95
+ }
96
+ getWorkstreamsForRepo(repoKey) {
97
+ if (!this.state)
98
+ return [];
99
+ const workstreams = this.state.workstreams ?? [];
100
+ // Index most recent task activity per workstream for sort key
101
+ const latestActivity = new Map();
102
+ for (const task of this.state.tasks ?? []) {
103
+ if (!task.workstreamId)
104
+ continue;
105
+ const t = new Date(task.updatedAt).getTime();
106
+ const prev = latestActivity.get(task.workstreamId) ?? 0;
107
+ if (t > prev)
108
+ latestActivity.set(task.workstreamId, t);
109
+ }
110
+ return workstreams
111
+ .filter(w => w.repoKey === repoKey)
112
+ .sort((a, b) => (latestActivity.get(b.id) ?? 0) - (latestActivity.get(a.id) ?? 0));
113
+ }
114
+ getWorkstreamById(id) {
115
+ if (!this.state)
116
+ return undefined;
117
+ const workstreams = this.state.workstreams ?? [];
118
+ return workstreams.find(w => w.id === id);
119
+ }
120
+ getWorkstreamTitle(workstream) {
121
+ if (!this.state)
122
+ return "";
123
+ const num = workstream.origin.number;
124
+ if (workstream.origin.type === "issue") {
125
+ const issues = this.state.issues ?? [];
126
+ const issue = issues.find(i => i.repoSlug === workstream.repoKey && i.number === num);
127
+ return issue ? issue.title : "";
128
+ }
129
+ else {
130
+ const prs = this.state.prs ?? [];
131
+ const pr = prs.find(p => p.repoSlug === workstream.repoKey && p.number === num);
132
+ return pr ? pr.title : "";
133
+ }
134
+ }
135
+ getExecutionsForWorkstream(workstreamId) {
136
+ if (!this.state)
137
+ return [];
138
+ // Scoped matching logic:
139
+ // 1. Find all tasks with task.workstreamId === workstreamId
140
+ const tasks = this.state.tasks ?? [];
141
+ const taskIds = new Set(tasks
142
+ .filter(t => t.workstreamId === workstreamId)
143
+ .map(t => t.id));
144
+ // 2. Parse number from workstreamId for fallback matching
145
+ const match = workstreamId.match(/:issue:(\d+)$|:pr:(\d+)$/);
146
+ const numStr = match ? (match[1] || match[2]) : null;
147
+ const executions = this.state.executions ?? [];
148
+ return executions
149
+ .filter(e => {
150
+ if (taskIds.has(e.taskId))
151
+ return true;
152
+ if (numStr) {
153
+ // Fallback: split taskId by delimiters to ensure exact match of the issue/PR number component
154
+ const parts = e.taskId.split(/[:_]/);
155
+ if (parts.includes(numStr))
156
+ return true;
157
+ }
158
+ return false;
159
+ })
160
+ .sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
161
+ }
162
+ getTasks() {
163
+ if (!this.state)
164
+ return [];
165
+ return this.state.tasks ?? [];
166
+ }
167
+ getExecutions() {
168
+ if (!this.state)
169
+ return [];
170
+ return this.state.executions ?? [];
171
+ }
172
+ notify() {
173
+ for (const listener of this.listeners) {
174
+ listener({ ...this.model });
175
+ }
176
+ }
177
+ getModel() {
178
+ return this.model;
179
+ }
180
+ setIntervalMs(intervalMs) {
181
+ this.intervalMs = intervalMs;
182
+ }
183
+ setNextCycleAt(timestamp) {
184
+ this.nextCycleAt = timestamp;
185
+ this.updateCountdown();
186
+ }
187
+ updateUptime() {
188
+ this.model.uptime = Math.floor((Date.now() - this.startTime) / 1000);
189
+ this.updateLastOk();
190
+ this.updateCountdown();
191
+ this.notify();
192
+ }
193
+ updateLastOk() {
194
+ if (this.lastOkTime === null) {
195
+ this.model.lastOk = "—";
196
+ }
197
+ else {
198
+ const diffSec = Math.floor((Date.now() - this.lastOkTime) / 1000);
199
+ if (diffSec < 60) {
200
+ this.model.lastOk = `${diffSec}s ago`;
201
+ }
202
+ else {
203
+ const diffMin = Math.floor(diffSec / 60);
204
+ const remSec = diffSec % 60;
205
+ this.model.lastOk = `${diffMin}m ${String(remSec).padStart(2, "0")}s ago`;
206
+ }
207
+ }
208
+ }
209
+ updateCountdown() {
210
+ if (["executing", "syncing"].includes(this.model.status)) {
211
+ this.model.nextText = "— · running";
212
+ return;
213
+ }
214
+ if (this.model.status === "blocked") {
215
+ this.model.nextText = "paused";
216
+ return;
217
+ }
218
+ if (this.model.status === "waiting") {
219
+ this.model.nextText = "paused · awaiting approval";
220
+ return;
221
+ }
222
+ if (this.nextCycleAt === 0) {
223
+ this.model.nextText = "—";
224
+ return;
225
+ }
226
+ const diffMs = this.nextCycleAt - Date.now();
227
+ if (diffMs <= 0) {
228
+ this.model.nextText = "in 0:00";
229
+ return;
230
+ }
231
+ const totalSec = Math.ceil(diffMs / 1000);
232
+ const min = Math.floor(totalSec / 60);
233
+ const sec = totalSec % 60;
234
+ const timeStr = `${min}:${String(sec).padStart(2, "0")}`;
235
+ if (this.model.status === "failed") {
236
+ this.model.nextText = `retry in ${timeStr}`;
237
+ }
238
+ else {
239
+ this.model.nextText = `in ${timeStr}`;
240
+ }
241
+ }
242
+ onStatusChange(status) {
243
+ this.model = {
244
+ ...this.model,
245
+ ...status
246
+ };
247
+ this.updateCountdown();
248
+ this.notify();
249
+ }
250
+ onLog(level, message) {
251
+ // Currently, we silence direct logs to screen.
252
+ // If needed, we can log them or print to debug area.
253
+ }
254
+ onCycleStart(cycleNo) {
255
+ this.runningTasks.clear();
256
+ this.runningTasks.set("orchestrator:sync", {
257
+ task: {
258
+ id: "orchestrator:sync",
259
+ kind: "sync_parent_branch",
260
+ repoId: "orchestrator",
261
+ repoSlug: "orchestrator",
262
+ status: "pending",
263
+ updatedAt: new Date().toISOString(),
264
+ context: { localRepoPath: "" }
265
+ },
266
+ agent: undefined,
267
+ startTime: Date.now()
268
+ });
269
+ this.currentCycleNo = cycleNo;
270
+ this.model.status = "syncing";
271
+ this.model.cycleNo = cycleNo;
272
+ this.model.opText = "reading GitHub state";
273
+ this.model.opTarget = "";
274
+ this.model.opSub = "starting cycle...";
275
+ this.updateCountdown();
276
+ this.notify();
277
+ }
278
+ onRepoSyncStart(repoSlug) {
279
+ this.model.status = "syncing";
280
+ this.model.opText = "syncing repository";
281
+ this.model.opTarget = repoSlug;
282
+ this.model.opSub = "fetching git updates...";
283
+ for (const key of [...this.runningTasks.keys()]) {
284
+ if (key.startsWith("orchestrator:sync:")) {
285
+ this.runningTasks.delete(key);
286
+ }
287
+ }
288
+ this.runningTasks.delete("orchestrator:sync");
289
+ this.runningTasks.set(`orchestrator:sync:${repoSlug}`, {
290
+ task: {
291
+ id: `orchestrator:sync:${repoSlug}`,
292
+ kind: "sync_repository",
293
+ repoId: "orchestrator",
294
+ repoSlug: repoSlug,
295
+ status: "pending",
296
+ updatedAt: new Date().toISOString(),
297
+ context: { localRepoPath: "" }
298
+ },
299
+ agent: undefined,
300
+ startTime: Date.now()
301
+ });
302
+ this.notify();
303
+ }
304
+ onGitHubScanStart() {
305
+ this.model.status = "syncing";
306
+ this.model.opText = "reading GitHub state";
307
+ this.model.opTarget = "";
308
+ this.model.opSub = "deriving attention...";
309
+ for (const key of this.runningTasks.keys()) {
310
+ if (key.startsWith("orchestrator:sync")) {
311
+ this.runningTasks.delete(key);
312
+ }
313
+ }
314
+ this.runningTasks.set("orchestrator:scan", {
315
+ task: {
316
+ id: "orchestrator:scan",
317
+ kind: "github_scan",
318
+ repoId: "orchestrator",
319
+ repoSlug: "GitHub",
320
+ status: "pending",
321
+ updatedAt: new Date().toISOString(),
322
+ context: { localRepoPath: "" }
323
+ },
324
+ agent: undefined,
325
+ startTime: Date.now()
326
+ });
327
+ this.notify();
328
+ }
329
+ onTaskStart(task, agent) {
330
+ this.runningTasks.delete("orchestrator:scan");
331
+ this.runningTasks.delete("orchestrator:sync");
332
+ for (const key of this.runningTasks.keys()) {
333
+ if (key.startsWith("orchestrator:sync")) {
334
+ this.runningTasks.delete(key);
335
+ }
336
+ }
337
+ this.model.status = "executing";
338
+ this.model.opText = this.getTaskKindText(task.kind);
339
+ this.model.opTarget = this.getTaskTargetText(task);
340
+ this.model.opSub = `agent running · worktree ${task.context.localRepoPath ? task.context.localRepoPath.split("/").pop() : "unknown"}`;
341
+ this.runningTasks.set(task.id, {
342
+ task,
343
+ agent: agent ?? undefined,
344
+ startTime: Date.now()
345
+ });
346
+ this.notify();
347
+ }
348
+ onTaskEnd(taskId) {
349
+ this.runningTasks.delete(taskId);
350
+ this.notify();
351
+ }
352
+ onCycleSuccess(summary, stateOrWorkstreams) {
353
+ let state;
354
+ if (Array.isArray(stateOrWorkstreams)) {
355
+ state = {
356
+ workstreams: stateOrWorkstreams,
357
+ tasks: [],
358
+ executions: [],
359
+ issues: [],
360
+ prs: [],
361
+ mcpStatuses: []
362
+ };
363
+ }
364
+ else {
365
+ state = stateOrWorkstreams;
366
+ }
367
+ this.state = state;
368
+ this.mcpStatuses = state.mcpStatuses ?? [];
369
+ const workstreams = state.workstreams ?? [];
370
+ this.runningTasks.clear();
371
+ this.lastOkTime = Date.now();
372
+ this.lastCycleDurationMs = new Date(summary.completedAt).getTime() - new Date(summary.startedAt).getTime();
373
+ // Determine readiness and status
374
+ const failedRepos = summary.failedRepoCount + summary.failedScanRepoCount;
375
+ const failedTasks = summary.failedTaskCount;
376
+ let readiness = "ok";
377
+ let status = "idle";
378
+ let opText = "no active operation";
379
+ let opTarget = "";
380
+ let opSub = "sleeping until next cycle";
381
+ if (failedRepos > 0 || failedTasks > 0) {
382
+ readiness = "warnings";
383
+ }
384
+ // Check if waiting for plan feedback/approval
385
+ const waitingWorkstream = workstreams.find((w) => w.phase === "plan_feedback" || w.phase === "planning" && w.packetPlan?.status === "proposed");
386
+ if (waitingWorkstream) {
387
+ status = "waiting";
388
+ opText = "awaiting plan approval/review";
389
+ const issueNum = waitingWorkstream.linkedIssueNumber ?? this.parseNumberFromUrl(waitingWorkstream.id);
390
+ opTarget = issueNum ? `issue #${issueNum} · ${waitingWorkstream.repoKey}` : waitingWorkstream.repoKey;
391
+ opSub = "paused — check GitHub to approve plan or review PR";
392
+ }
393
+ // Set cycle outcome details
394
+ let cycleGlyph = "○";
395
+ let cycleText = "no actionable work";
396
+ if (failedRepos > 0) {
397
+ cycleGlyph = "✗";
398
+ cycleText = `failed sync on ${failedRepos} repository/ies`;
399
+ }
400
+ else if (failedTasks > 0) {
401
+ cycleGlyph = "✗";
402
+ cycleText = `failed tasks: ${failedTasks}`;
403
+ }
404
+ else {
405
+ const disp = summary.dispatchedTaskCount;
406
+ const comp = summary.completedTaskCount;
407
+ if (disp > 0 && comp > 0) {
408
+ cycleGlyph = "✓";
409
+ const dispText = disp === 1 ? "1 task" : `${disp} tasks`;
410
+ const compText = comp === 1 ? "1 task" : `${comp} tasks`;
411
+ cycleText = `dispatched ${dispText} · completed ${compText}`;
412
+ }
413
+ else if (comp > 0) {
414
+ cycleGlyph = "✓";
415
+ cycleText = comp === 1 ? "completed 1 task" : `completed ${comp} tasks`;
416
+ }
417
+ else if (disp > 0) {
418
+ cycleGlyph = "✓";
419
+ cycleText = disp === 1 ? "dispatched 1 task" : `dispatched ${disp} tasks`;
420
+ }
421
+ else if (waitingWorkstream) {
422
+ cycleGlyph = "⚠";
423
+ cycleText = "one known waiting workstream";
424
+ }
425
+ }
426
+ const durationSec = Math.ceil(this.lastCycleDurationMs / 1000);
427
+ const cycleMeta = `cycle #${this.currentCycleNo} · ${durationSec}s`;
428
+ // Hints
429
+ const hints = [];
430
+ if (status === "waiting" && waitingWorkstream) {
431
+ const issueNum = waitingWorkstream.linkedIssueNumber ?? this.parseNumberFromUrl(waitingWorkstream.id);
432
+ hints.push({
433
+ glyph: "→",
434
+ text: `approve plan on issue #${issueNum} (GitHub)`,
435
+ color: "yellow"
436
+ });
437
+ hints.push({
438
+ glyph: "→",
439
+ text: "check the latest cycle details below",
440
+ color: "cyan"
441
+ });
442
+ }
443
+ else if (readiness === "warnings") {
444
+ hints.push({
445
+ glyph: "→",
446
+ text: "check credentials and setup configuration",
447
+ color: "red"
448
+ });
449
+ hints.push({
450
+ glyph: "→",
451
+ text: "inspect the logs/state repo for the trace",
452
+ color: "cyan"
453
+ });
454
+ }
455
+ else {
456
+ hints.push({
457
+ glyph: "·",
458
+ text: "nothing needs you right now",
459
+ color: "dim"
460
+ });
461
+ hints.push({
462
+ glyph: "→",
463
+ text: "open the journal or logs when you need history",
464
+ color: "cyan"
465
+ });
466
+ }
467
+ this.model = {
468
+ ...this.model,
469
+ status,
470
+ readiness,
471
+ opText,
472
+ opTarget,
473
+ opSub,
474
+ cycleGlyph,
475
+ cycleText,
476
+ cycleMeta,
477
+ hints
478
+ };
479
+ this.updateCountdown();
480
+ this.notify();
481
+ }
482
+ onCycleFailure(error) {
483
+ this.runningTasks.clear();
484
+ const isConfigError = error?.message?.includes("config") || error?.message?.includes("backburner.json");
485
+ const status = isConfigError ? "blocked" : "failed";
486
+ const readiness = isConfigError ? "blocking problems" : "warnings";
487
+ this.model.status = status;
488
+ this.model.readiness = readiness;
489
+ this.model.opText = "no active operation";
490
+ this.model.opTarget = "";
491
+ this.model.opSub = isConfigError ? "Backburner paused — configuration invalid" : "cycle aborted — will retry next cycle";
492
+ this.model.cycleGlyph = "✗";
493
+ this.model.cycleText = error?.message || String(error);
494
+ this.model.cycleMeta = `cycle #${this.currentCycleNo || 1} · failed`;
495
+ const hints = [];
496
+ if (isConfigError) {
497
+ hints.push({
498
+ glyph: "→",
499
+ text: "review setup configuration",
500
+ color: "cyan"
501
+ });
502
+ hints.push({
503
+ glyph: "·",
504
+ text: "configuration failed to load · check backburner.json",
505
+ color: "dim"
506
+ });
507
+ }
508
+ else {
509
+ hints.push({
510
+ glyph: "→",
511
+ text: "check credentials and setup configuration",
512
+ color: "red"
513
+ });
514
+ hints.push({
515
+ glyph: "→",
516
+ text: "inspect the logs/state repo for the trace",
517
+ color: "cyan"
518
+ });
519
+ }
520
+ this.model.hints = hints;
521
+ this.updateCountdown();
522
+ this.notify();
523
+ }
524
+ getTaskKindText(kind) {
525
+ switch (kind) {
526
+ case "product_discovery":
527
+ return "Product discovery analysis";
528
+ case "write_product_spec":
529
+ return "writing Product Spec";
530
+ case "implement_plan":
531
+ return "running implementation agent";
532
+ case "write_plan":
533
+ return "writing plan";
534
+ case "review_pr":
535
+ return "reviewing PR";
536
+ case "prepare_pr_for_merge":
537
+ return "preparing PR for merge";
538
+ case "sync_parent_branch":
539
+ return "syncing parent branch";
540
+ default:
541
+ return kind.replace(/_/g, " ");
542
+ }
543
+ }
544
+ getTaskTargetText(task) {
545
+ const issueNum = task.context.prepareForMerge?.prNumber ??
546
+ task.context.preflight?.parentPrNumber ??
547
+ this.parseNumberFromUrl(task.context.issueUrl || task.sourceUrl || "");
548
+ const prefix = task.kind === "review_pr" || task.kind === "prepare_pr_for_merge" ? "PR" : "issue";
549
+ if (issueNum) {
550
+ return `${prefix} #${issueNum} · ${task.repoSlug}`;
551
+ }
552
+ return task.repoSlug;
553
+ }
554
+ parseNumberFromUrl(url) {
555
+ const match = url.match(/\/(?:issues|pull)\/(\d+)/);
556
+ return match ? Number(match[1]) : undefined;
557
+ }
558
+ }
@@ -0,0 +1 @@
1
+ export {};