@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.
- package/LICENSE +21 -0
- package/README.md +329 -0
- package/dist/src/agents/antigravity-launch.js +162 -0
- package/dist/src/agents/antigravity.js +251 -0
- package/dist/src/agents/claude-launch.js +117 -0
- package/dist/src/agents/claude.js +271 -0
- package/dist/src/agents/codex-launch.js +82 -0
- package/dist/src/agents/codex.js +261 -0
- package/dist/src/agents/composite-runner.js +13 -0
- package/dist/src/agents/extraction.js +442 -0
- package/dist/src/agents/failure-feedback.js +132 -0
- package/dist/src/agents/gemini-launch.js +96 -0
- package/dist/src/agents/gemini.js +392 -0
- package/dist/src/agents/job-agent-assignments.js +180 -0
- package/dist/src/agents/lifecycle-feedback.js +147 -0
- package/dist/src/agents/limit-detector.js +83 -0
- package/dist/src/agents/opencode-launch.js +138 -0
- package/dist/src/agents/opencode.js +156 -0
- package/dist/src/agents/plan-breakdown-feedback.js +97 -0
- package/dist/src/agents/pool.js +168 -0
- package/dist/src/agents/prompt.js +462 -0
- package/dist/src/agents/provider-sessions.js +105 -0
- package/dist/src/agents/review-feedback.js +255 -0
- package/dist/src/agents/selection.js +113 -0
- package/dist/src/agents/service.js +1087 -0
- package/dist/src/agents/types.js +1 -0
- package/dist/src/agents/wave-assessment-feedback.js +69 -0
- package/dist/src/attention/derive.js +337 -0
- package/dist/src/capabilities/index.js +2 -0
- package/dist/src/capabilities/projection.js +183 -0
- package/dist/src/capabilities/types.js +1 -0
- package/dist/src/capabilities/validator.js +87 -0
- package/dist/src/cli/commands/broker-smoke.js +96 -0
- package/dist/src/cli/commands/broker.js +56 -0
- package/dist/src/cli/commands/init.js +4 -0
- package/dist/src/cli/commands/journal.js +43 -0
- package/dist/src/cli/commands/run-loop.js +56 -0
- package/dist/src/cli/commands/run.js +1241 -0
- package/dist/src/cli/commands/tui.js +187 -0
- package/dist/src/cli/dispatcher.js +51 -0
- package/dist/src/cli/errors.js +2 -0
- package/dist/src/cli/init.js +92 -0
- package/dist/src/cli/options.js +86 -0
- package/dist/src/cli/output.js +231 -0
- package/dist/src/cli/run-journal.js +342 -0
- package/dist/src/cli/run-types.js +1 -0
- package/dist/src/cli/run.js +32 -0
- package/dist/src/cli/runtime/dispatch-lifecycle.js +772 -0
- package/dist/src/cli/runtime/provider-runtime-types.js +1 -0
- package/dist/src/cli/runtime/provider-runtime.js +152 -0
- package/dist/src/cli/runtime/provider-tools.js +73 -0
- package/dist/src/cli/runtime/providers/antigravity.js +26 -0
- package/dist/src/cli/runtime/providers/claude.js +20 -0
- package/dist/src/cli/runtime/providers/codex.js +20 -0
- package/dist/src/cli/runtime/providers/gemini.js +20 -0
- package/dist/src/cli/runtime/providers/opencode.js +20 -0
- package/dist/src/cli/runtime/run-context.js +131 -0
- package/dist/src/cli/tui/index.js +3 -0
- package/dist/src/cli/tui/screen.js +388 -0
- package/dist/src/cli/tui/tracker.js +558 -0
- package/dist/src/cli/tui/types.js +1 -0
- package/dist/src/cli/tui/views.js +1006 -0
- package/dist/src/config/loader.js +136 -0
- package/dist/src/config/schemas.js +403 -0
- package/dist/src/config/types.js +1 -0
- package/dist/src/context/types.js +331 -0
- package/dist/src/domain/entities.js +1 -0
- package/dist/src/git/backburner-git-tool-service.js +680 -0
- package/dist/src/git/gitops-lifecycle.js +56 -0
- package/dist/src/git/merge-prep-workspace.js +357 -0
- package/dist/src/git/types.js +484 -0
- package/dist/src/github/broker-model-content.js +215 -0
- package/dist/src/github/broker-server.js +237 -0
- package/dist/src/github/broker.js +638 -0
- package/dist/src/github/composed.js +53 -0
- package/dist/src/github/gateway.js +478 -0
- package/dist/src/github/normalize.js +222 -0
- package/dist/src/github/pr-classification.js +73 -0
- package/dist/src/github/scoped-broker.js +35 -0
- package/dist/src/github/security.js +126 -0
- package/dist/src/github/service.js +332 -0
- package/dist/src/github/types.js +1 -0
- package/dist/src/github/utils.js +16 -0
- package/dist/src/journal/index.js +4 -0
- package/dist/src/journal/reader.js +62 -0
- package/dist/src/journal/renderer.js +50 -0
- package/dist/src/journal/service.js +72 -0
- package/dist/src/journal/types.js +1 -0
- package/dist/src/journal/writer.js +19 -0
- package/dist/src/mcps/bridge.js +166 -0
- package/dist/src/mcps/index.js +5 -0
- package/dist/src/mcps/reconcile.js +113 -0
- package/dist/src/mcps/smithery-client.js +186 -0
- package/dist/src/mcps/smithery.js +164 -0
- package/dist/src/mcps/types.js +1 -0
- package/dist/src/memory/json-store.js +71 -0
- package/dist/src/memory/prompt.js +16 -0
- package/dist/src/memory/scope.js +31 -0
- package/dist/src/memory/store.js +1 -0
- package/dist/src/memory/tool-service.js +142 -0
- package/dist/src/memory/types.js +12 -0
- package/dist/src/onboarding/config-writer.js +96 -0
- package/dist/src/onboarding/engine.js +86 -0
- package/dist/src/onboarding/prompt.js +102 -0
- package/dist/src/onboarding/services/provider-discovery.js +58 -0
- package/dist/src/onboarding/services/repo-discovery.js +246 -0
- package/dist/src/onboarding/steps/check-github-auth.js +32 -0
- package/dist/src/onboarding/steps/check-requirements.js +34 -0
- package/dist/src/onboarding/steps/configure-models.js +105 -0
- package/dist/src/onboarding/steps/configure-paths.js +33 -0
- package/dist/src/onboarding/steps/discover-providers.js +38 -0
- package/dist/src/onboarding/steps/discover-repos.js +39 -0
- package/dist/src/onboarding/steps/generate-config.js +106 -0
- package/dist/src/onboarding/steps/readiness-validation.js +158 -0
- package/dist/src/onboarding/steps/select-repos.js +76 -0
- package/dist/src/onboarding/steps/show-summary.js +97 -0
- package/dist/src/onboarding/steps/welcome.js +27 -0
- package/dist/src/onboarding/types.js +1 -0
- package/dist/src/onboarding/validators/git-access.js +63 -0
- package/dist/src/onboarding/validators/github-auth.js +85 -0
- package/dist/src/onboarding/validators/tool-checker.js +153 -0
- package/dist/src/onboarding/validators/workspace.js +83 -0
- package/dist/src/prompts/resolver.js +106 -0
- package/dist/src/retrospectives/candidates.js +224 -0
- package/dist/src/retrospectives/derive.js +169 -0
- package/dist/src/retrospectives/ingest.js +321 -0
- package/dist/src/retrospectives/management-prs.js +380 -0
- package/dist/src/retrospectives/proposals.js +199 -0
- package/dist/src/retrospectives/scopes.js +272 -0
- package/dist/src/retrospectives/scoring.js +171 -0
- package/dist/src/retrospectives/types.js +1 -0
- package/dist/src/state/loader.js +473 -0
- package/dist/src/state/types.js +15 -0
- package/dist/src/tasks/derivation/builders.js +70 -0
- package/dist/src/tasks/derivation/handlers/comment-response.js +169 -0
- package/dist/src/tasks/derivation/handlers/implement-plan.js +103 -0
- package/dist/src/tasks/derivation/handlers/index.js +54 -0
- package/dist/src/tasks/derivation/handlers/packet-pr-control.js +92 -0
- package/dist/src/tasks/derivation/handlers/packet-worktree.js +29 -0
- package/dist/src/tasks/derivation/handlers/plan-breakdown.js +45 -0
- package/dist/src/tasks/derivation/handlers/prepare-for-merge.js +91 -0
- package/dist/src/tasks/derivation/handlers/product-discovery.js +36 -0
- package/dist/src/tasks/derivation/handlers/retry-failed-task.js +125 -0
- package/dist/src/tasks/derivation/handlers/review-pr.js +28 -0
- package/dist/src/tasks/derivation/handlers/shared.js +24 -0
- package/dist/src/tasks/derivation/handlers/sync-parent-branch.js +100 -0
- package/dist/src/tasks/derivation/handlers/wave-assessment.js +223 -0
- package/dist/src/tasks/derivation/handlers/write-plan.js +101 -0
- package/dist/src/tasks/derivation/handlers/write-product-spec.js +49 -0
- package/dist/src/tasks/derivation/runner.js +18 -0
- package/dist/src/tasks/derivation/types.js +1 -0
- package/dist/src/tasks/derive.js +810 -0
- package/dist/src/tasks/mcp-skip.js +10 -0
- package/dist/src/tasks/retry.js +10 -0
- package/dist/src/tasks/task.js +184 -0
- package/dist/src/tasks/types.js +1 -0
- package/dist/src/utils/command-runner.js +192 -0
- package/dist/src/utils/json.js +38 -0
- package/dist/src/utils/logger.js +43 -0
- package/dist/src/utils/paths.js +38 -0
- package/dist/src/utils/slug.js +10 -0
- package/dist/src/workflows/registry.js +174 -0
- package/dist/src/workflows/triage.js +90 -0
- package/dist/src/workflows/workstream-machine/derive-events.js +121 -0
- package/dist/src/workflows/workstream-machine/evaluate.js +24 -0
- package/dist/src/workflows/workstream-machine/events.js +1 -0
- package/dist/src/workflows/workstream-machine/machine.js +355 -0
- package/dist/src/workflows/workstream-machine/selectors.js +19 -0
- package/dist/src/workflows/workstream-machine/types.js +1 -0
- package/dist/src/workstreams/branch-sync.js +87 -0
- package/dist/src/workstreams/derive.js +629 -0
- package/dist/src/workstreams/mcp-blockers.js +420 -0
- package/dist/src/workstreams/packet-candidates.js +42 -0
- package/dist/src/workstreams/packet-lifecycle-feedback.js +60 -0
- package/dist/src/workstreams/packet-lifecycle-report.js +199 -0
- package/dist/src/workstreams/packet-plan.js +597 -0
- package/dist/src/workstreams/packet-projections.js +190 -0
- package/dist/src/workstreams/parent-branch-sync-action.js +312 -0
- package/dist/src/workstreams/parent-branch-sync.js +148 -0
- package/dist/src/workstreams/phases.js +4 -0
- package/dist/src/workstreams/plan-breakdown-generator/export.js +19 -0
- package/dist/src/workstreams/plan-breakdown-generator/index.js +6 -0
- package/dist/src/workstreams/plan-breakdown-generator/inspect.js +50 -0
- package/dist/src/workstreams/plan-breakdown-generator/patch.js +139 -0
- package/dist/src/workstreams/plan-breakdown-generator/session.js +19 -0
- package/dist/src/workstreams/plan-breakdown-generator/store.js +27 -0
- package/dist/src/workstreams/plan-breakdown-generator/tools.js +407 -0
- package/dist/src/workstreams/plan-breakdown-generator/types.js +6 -0
- package/dist/src/workstreams/plan-breakdown-generator/validate.js +212 -0
- package/dist/src/workstreams/plan-breakdown-schema.js +175 -0
- package/dist/src/workstreams/utils.js +15 -0
- package/dist/src/workstreams/wave-assessment/checkpoint.js +172 -0
- package/dist/src/workstreams/wave-assessment/index.js +3 -0
- package/dist/src/workstreams/wave-assessment/session.js +84 -0
- package/dist/src/workstreams/wave-assessment/store.js +51 -0
- package/dist/src/workstreams/wave-assessment/tools.js +419 -0
- package/dist/src/workstreams/wave-assessment/types.js +1 -0
- package/dist/src/workstreams/wave-assessment/validate.js +78 -0
- package/dist/src/worktrees/service.js +839 -0
- package/dist/src/worktrees/workspace-ownership.js +34 -0
- package/dist/src/worktrees/workspace-recovery.js +177 -0
- package/package.json +61 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { loadConfig } from "../../config/loader.js";
|
|
3
|
+
import { StateStore } from "../../state/loader.js";
|
|
4
|
+
import { defaultBackburnerManagementRoot, defaultBackburnerOutputsRoot, resolveOutputsLogsRoot, resolveOutputsStateRoot } from "../../utils/paths.js";
|
|
5
|
+
import { RuntimeStatusTracker, TuiScreen } from "../tui/index.js";
|
|
6
|
+
import { readIntervalMs, readOptionalStringFlag, readStringFlag } from "../options.js";
|
|
7
|
+
import { runCli } from "./run.js";
|
|
8
|
+
export function shouldUseRuntimeStatusScreen(rest) {
|
|
9
|
+
return Boolean(process.stdout.isTTY &&
|
|
10
|
+
process.stdin.isTTY &&
|
|
11
|
+
!rest.includes("--headless") &&
|
|
12
|
+
!rest.includes("--once"));
|
|
13
|
+
}
|
|
14
|
+
export class SerializedCycleRunner {
|
|
15
|
+
runCycleFn;
|
|
16
|
+
scheduleNextCycleFn;
|
|
17
|
+
onRunningChange;
|
|
18
|
+
running = false;
|
|
19
|
+
refreshRequested = false;
|
|
20
|
+
stopped = false;
|
|
21
|
+
timer;
|
|
22
|
+
constructor(runCycleFn, scheduleNextCycleFn, onRunningChange) {
|
|
23
|
+
this.runCycleFn = runCycleFn;
|
|
24
|
+
this.scheduleNextCycleFn = scheduleNextCycleFn;
|
|
25
|
+
this.onRunningChange = onRunningChange;
|
|
26
|
+
}
|
|
27
|
+
clearTimer() {
|
|
28
|
+
if (this.timer) {
|
|
29
|
+
clearTimeout(this.timer);
|
|
30
|
+
this.timer = undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async run(reason) {
|
|
34
|
+
if (this.running) {
|
|
35
|
+
if (reason === "manual") {
|
|
36
|
+
this.refreshRequested = true;
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// A real cycle is starting, so any pending scheduled wakeup is stale.
|
|
41
|
+
this.clearTimer();
|
|
42
|
+
this.running = true;
|
|
43
|
+
if (this.onRunningChange)
|
|
44
|
+
this.onRunningChange(true, reason);
|
|
45
|
+
try {
|
|
46
|
+
await this.runCycleFn();
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
this.running = false;
|
|
50
|
+
if (this.onRunningChange)
|
|
51
|
+
this.onRunningChange(false, reason);
|
|
52
|
+
if (this.stopped)
|
|
53
|
+
return;
|
|
54
|
+
if (this.refreshRequested) {
|
|
55
|
+
this.refreshRequested = false;
|
|
56
|
+
void this.run("manual");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
this.scheduleNextCycleFn();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
stop() {
|
|
63
|
+
this.stopped = true;
|
|
64
|
+
this.clearTimer();
|
|
65
|
+
}
|
|
66
|
+
setTimer(timer) {
|
|
67
|
+
this.clearTimer();
|
|
68
|
+
this.timer = timer;
|
|
69
|
+
}
|
|
70
|
+
isStopped() {
|
|
71
|
+
return this.stopped;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export async function runInteractiveTui(argv) {
|
|
75
|
+
const rest = argv.filter((arg) => arg !== "run");
|
|
76
|
+
const managementDir = path.resolve(readStringFlag(rest, "--management-dir", defaultBackburnerManagementRoot()));
|
|
77
|
+
let repoCount = 0;
|
|
78
|
+
let initialConfig = null;
|
|
79
|
+
try {
|
|
80
|
+
initialConfig = await loadConfig(managementDir);
|
|
81
|
+
repoCount = initialConfig.repos.filter((r) => r.enabled).length;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Config load error will be captured in runCycle
|
|
85
|
+
}
|
|
86
|
+
const tracker = new RuntimeStatusTracker(repoCount);
|
|
87
|
+
if (initialConfig) {
|
|
88
|
+
tracker.setConfigs(initialConfig.mcps, initialConfig.capabilities, initialConfig.maxConcurrentAgents);
|
|
89
|
+
}
|
|
90
|
+
const intervalMs = readIntervalMs(rest);
|
|
91
|
+
tracker.setIntervalMs(intervalMs);
|
|
92
|
+
// Load initial state for the explorer immediately on start
|
|
93
|
+
try {
|
|
94
|
+
const outputsDir = path.resolve(readOptionalStringFlag(rest, "--outputs-dir") ?? defaultBackburnerOutputsRoot());
|
|
95
|
+
const stateDir = path.resolve(resolveOutputsStateRoot(outputsDir));
|
|
96
|
+
const logDir = path.resolve(resolveOutputsLogsRoot(outputsDir));
|
|
97
|
+
const stateStore = new StateStore({ stateDir, executionDir: logDir });
|
|
98
|
+
const state = await stateStore.load();
|
|
99
|
+
tracker.setState(state);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Ignore initial state load errors
|
|
103
|
+
}
|
|
104
|
+
const runCycle = async () => {
|
|
105
|
+
const silentLogger = {
|
|
106
|
+
info: () => { },
|
|
107
|
+
warn: () => { },
|
|
108
|
+
error: () => { }
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
const currentCycleNo = tracker.getModel().cycleNo === "—" ? 1 : Number(tracker.getModel().cycleNo) + 1;
|
|
112
|
+
const summary = await runCli(["run", ...rest], {
|
|
113
|
+
statusTracker: tracker,
|
|
114
|
+
logger: silentLogger,
|
|
115
|
+
cycleNo: currentCycleNo
|
|
116
|
+
});
|
|
117
|
+
const outputsDir = path.resolve(readOptionalStringFlag(rest, "--outputs-dir") ?? defaultBackburnerOutputsRoot());
|
|
118
|
+
const stateDir = path.resolve(resolveOutputsStateRoot(outputsDir));
|
|
119
|
+
const logDir = path.resolve(resolveOutputsLogsRoot(outputsDir));
|
|
120
|
+
const stateStore = new StateStore({ stateDir, executionDir: logDir });
|
|
121
|
+
const state = await stateStore.load();
|
|
122
|
+
try {
|
|
123
|
+
const config = await loadConfig(managementDir);
|
|
124
|
+
tracker.setConfigs(config.mcps, config.capabilities, config.maxConcurrentAgents);
|
|
125
|
+
tracker.setConfigReloadError(null);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
tracker.setConfigReloadError(err);
|
|
129
|
+
}
|
|
130
|
+
tracker.onCycleSuccess(summary, state);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
tracker.onCycleFailure(error);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
let screen;
|
|
137
|
+
let runner;
|
|
138
|
+
const scheduleNextCycle = () => {
|
|
139
|
+
if (runner.isStopped())
|
|
140
|
+
return;
|
|
141
|
+
tracker.setNextCycleAt(Date.now() + intervalMs);
|
|
142
|
+
const timer = setTimeout(() => {
|
|
143
|
+
void runner.run("scheduled");
|
|
144
|
+
}, intervalMs);
|
|
145
|
+
runner.setTimer(timer);
|
|
146
|
+
};
|
|
147
|
+
const shutdown = () => {
|
|
148
|
+
runner.stop();
|
|
149
|
+
screen.exit();
|
|
150
|
+
};
|
|
151
|
+
const onQuit = () => {
|
|
152
|
+
shutdown();
|
|
153
|
+
process.exit(0);
|
|
154
|
+
};
|
|
155
|
+
screen = new TuiScreen(tracker, async () => {
|
|
156
|
+
await runner.run("manual");
|
|
157
|
+
}, onQuit);
|
|
158
|
+
runner = new SerializedCycleRunner(runCycle, scheduleNextCycle, (running, reason) => {
|
|
159
|
+
if (running) {
|
|
160
|
+
tracker.setNextCycleAt(0);
|
|
161
|
+
}
|
|
162
|
+
if (reason === "manual") {
|
|
163
|
+
screen.setRefreshing(running);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
const sigintHandler = () => {
|
|
167
|
+
shutdown();
|
|
168
|
+
process.exit(130);
|
|
169
|
+
};
|
|
170
|
+
const sigtermHandler = () => {
|
|
171
|
+
shutdown();
|
|
172
|
+
process.exit(143);
|
|
173
|
+
};
|
|
174
|
+
process.once("SIGINT", sigintHandler);
|
|
175
|
+
process.once("SIGTERM", sigtermHandler);
|
|
176
|
+
try {
|
|
177
|
+
screen.start();
|
|
178
|
+
void runner.run("initial");
|
|
179
|
+
// Keep process alive
|
|
180
|
+
await new Promise(() => { });
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
process.off("SIGINT", sigintHandler);
|
|
184
|
+
process.off("SIGTERM", sigtermHandler);
|
|
185
|
+
shutdown();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { formatRunSummary } from "./output.js";
|
|
2
|
+
import { CliUsageError } from "./errors.js";
|
|
3
|
+
import { runBrokerCli } from "./commands/broker.js";
|
|
4
|
+
import { runBrokerSmokeCli } from "./commands/broker-smoke.js";
|
|
5
|
+
import { runInitCommand } from "./commands/init.js";
|
|
6
|
+
import { runJournalCli } from "./commands/journal.js";
|
|
7
|
+
import { runCli } from "./commands/run.js";
|
|
8
|
+
import { runLoopCli } from "./commands/run-loop.js";
|
|
9
|
+
import { runInteractiveTui, shouldUseRuntimeStatusScreen } from "./commands/tui.js";
|
|
10
|
+
const usage = 'Expected command: backburner run [--code-root <path>] [--management-dir <path>] [--outputs-dir <path>] [--log-dir <path>] [--broker-port <port>] [--headless] [--once] | run-loop ... | broker ... | broker-smoke ... | init [--code-root <path>] [--management-dir <path>] [--outputs-dir <path>] [--log-dir <path>] [--workspaces-dir <path>] [--yes] | journal [--outputs-dir <path>] [--repo <id>] [--workstream <id>] [--issue <n>] [--pr <n>] [--since <ISO>] [--last <n>] [--json] [--verbose]';
|
|
11
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
12
|
+
const [command, ...rest] = argv;
|
|
13
|
+
if (command === "--help" || command === "-h" || command === "help") {
|
|
14
|
+
process.stdout.write(`${usage}\n`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (command === "run") {
|
|
18
|
+
if (shouldUseRuntimeStatusScreen(rest)) {
|
|
19
|
+
await runInteractiveTui([command, ...rest]);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const summary = await runCli([command, ...rest]);
|
|
23
|
+
process.stdout.write(formatRunSummary(summary));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (command === "run-loop") {
|
|
27
|
+
if (shouldUseRuntimeStatusScreen(rest)) {
|
|
28
|
+
await runInteractiveTui(["run", ...rest]);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
await runLoopCli([command, ...rest]);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (command === "broker") {
|
|
35
|
+
await runBrokerCli(rest);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (command === "broker-smoke") {
|
|
39
|
+
await runBrokerSmokeCli(rest);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (command === "init") {
|
|
43
|
+
await runInitCommand(rest);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (command === "journal") {
|
|
47
|
+
await runJournalCli(rest);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
throw new CliUsageError(usage);
|
|
51
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { ExecFileCommandRunner } from "../utils/command-runner.js";
|
|
3
|
+
import { defaultBackburnerCodeRoot, defaultBackburnerManagementRoot, defaultBackburnerOutputsRoot, defaultWorkspacesRootForCodeRoot } from "../utils/paths.js";
|
|
4
|
+
import { OnboardingEngine } from "../onboarding/engine.js";
|
|
5
|
+
import { NodeReadlinePrompter, NonInteractivePrompter } from "../onboarding/prompt.js";
|
|
6
|
+
import { WelcomeStep } from "../onboarding/steps/welcome.js";
|
|
7
|
+
import { CheckRequirementsStep } from "../onboarding/steps/check-requirements.js";
|
|
8
|
+
import { CheckGitHubAuthStep } from "../onboarding/steps/check-github-auth.js";
|
|
9
|
+
import { DiscoverReposStep } from "../onboarding/steps/discover-repos.js";
|
|
10
|
+
import { SelectReposStep } from "../onboarding/steps/select-repos.js";
|
|
11
|
+
import { ConfigurePathsStep } from "../onboarding/steps/configure-paths.js";
|
|
12
|
+
import { DiscoverProvidersStep } from "../onboarding/steps/discover-providers.js";
|
|
13
|
+
import { ConfigureModelsStep } from "../onboarding/steps/configure-models.js";
|
|
14
|
+
import { GenerateConfigStep } from "../onboarding/steps/generate-config.js";
|
|
15
|
+
import { ReadinessValidationStep } from "../onboarding/steps/readiness-validation.js";
|
|
16
|
+
import { ShowSummaryStep } from "../onboarding/steps/show-summary.js";
|
|
17
|
+
export async function runInitCli(argv) {
|
|
18
|
+
const codeRoot = readStringFlag(argv, "--code-root") ?? defaultBackburnerCodeRoot();
|
|
19
|
+
const managementDir = readStringFlag(argv, "--management-dir");
|
|
20
|
+
const outputsDir = readStringFlag(argv, "--outputs-dir");
|
|
21
|
+
const logDir = readStringFlag(argv, "--log-dir");
|
|
22
|
+
const workspacesDir = readStringFlag(argv, "--workspaces-dir");
|
|
23
|
+
const nonInteractive = argv.includes("--yes") || argv.includes("-y");
|
|
24
|
+
const result = await runInit({
|
|
25
|
+
codeRoot,
|
|
26
|
+
...(managementDir !== undefined ? { managementDir } : {}),
|
|
27
|
+
...(outputsDir !== undefined ? { outputsDir } : {}),
|
|
28
|
+
...(logDir !== undefined ? { logDir } : {}),
|
|
29
|
+
...(workspacesDir !== undefined ? { workspacesDir } : {}),
|
|
30
|
+
nonInteractive
|
|
31
|
+
});
|
|
32
|
+
// Let the summary print first, then signal failure via exit code.
|
|
33
|
+
if (result.overallStatus === "failed" || result.overallStatus === "requiresUserAction") {
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export async function runInit(options) {
|
|
38
|
+
const commandRunner = new ExecFileCommandRunner();
|
|
39
|
+
const { codeRoot, nonInteractive = false } = options;
|
|
40
|
+
const resolvedCodeRoot = path.resolve(codeRoot);
|
|
41
|
+
const resolvedOutputsDir = path.resolve(options.outputsDir ?? defaultBackburnerOutputsRoot());
|
|
42
|
+
const ctx = {
|
|
43
|
+
codeRoot: resolvedCodeRoot,
|
|
44
|
+
managementDir: path.resolve(options.managementDir ?? defaultBackburnerManagementRoot()),
|
|
45
|
+
outputsDir: resolvedOutputsDir,
|
|
46
|
+
logDir: path.resolve(options.logDir ?? path.join(resolvedOutputsDir, "logs")),
|
|
47
|
+
workspacesDir: path.resolve(options.workspacesDir ?? defaultWorkspacesRootForCodeRoot(resolvedCodeRoot)),
|
|
48
|
+
discoveredRepos: [],
|
|
49
|
+
selectedRepos: [],
|
|
50
|
+
discoveredProviders: [],
|
|
51
|
+
warnings: [],
|
|
52
|
+
stepResults: new Map()
|
|
53
|
+
};
|
|
54
|
+
const steps = [
|
|
55
|
+
new WelcomeStep(),
|
|
56
|
+
new CheckRequirementsStep(commandRunner),
|
|
57
|
+
new CheckGitHubAuthStep(commandRunner),
|
|
58
|
+
new DiscoverReposStep(commandRunner, ctx.codeRoot),
|
|
59
|
+
new SelectReposStep(),
|
|
60
|
+
new ConfigurePathsStep(),
|
|
61
|
+
new DiscoverProvidersStep(commandRunner),
|
|
62
|
+
new ConfigureModelsStep(),
|
|
63
|
+
new GenerateConfigStep(),
|
|
64
|
+
new ReadinessValidationStep(commandRunner),
|
|
65
|
+
new ShowSummaryStep()
|
|
66
|
+
];
|
|
67
|
+
let prompter;
|
|
68
|
+
let interactivePrompter;
|
|
69
|
+
if (nonInteractive) {
|
|
70
|
+
prompter = new NonInteractivePrompter();
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
interactivePrompter = new NodeReadlinePrompter();
|
|
74
|
+
prompter = interactivePrompter;
|
|
75
|
+
}
|
|
76
|
+
const engine = new OnboardingEngine(steps, prompter);
|
|
77
|
+
try {
|
|
78
|
+
return await engine.run(ctx);
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
interactivePrompter?.close();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function readStringFlag(argv, flag) {
|
|
85
|
+
const index = argv.indexOf(flag);
|
|
86
|
+
if (index < 0)
|
|
87
|
+
return undefined;
|
|
88
|
+
const value = argv[index + 1];
|
|
89
|
+
if (value === undefined || value.startsWith("--"))
|
|
90
|
+
return undefined;
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { CliUsageError } from "./errors.js";
|
|
2
|
+
export const DEFAULT_GITHUB_BROKER_PORT = 4444;
|
|
3
|
+
export const DEFAULT_RUN_LOOP_INTERVAL_MS = 5 * 60 * 1000;
|
|
4
|
+
export function readStringFlag(argv, flag, fallback) {
|
|
5
|
+
const index = argv.indexOf(flag);
|
|
6
|
+
if (index === -1) {
|
|
7
|
+
return fallback;
|
|
8
|
+
}
|
|
9
|
+
const value = argv[index + 1];
|
|
10
|
+
if (!value) {
|
|
11
|
+
throw new CliUsageError(`Missing value for ${flag}`);
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
export function readOptionalStringFlag(argv, flag) {
|
|
16
|
+
const index = argv.indexOf(flag);
|
|
17
|
+
if (index === -1) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const value = argv[index + 1];
|
|
21
|
+
if (!value) {
|
|
22
|
+
throw new CliUsageError(`Missing value for ${flag}`);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
export function readNumberFlag(argv, flag, fallback) {
|
|
27
|
+
const value = readOptionalStringFlag(argv, flag);
|
|
28
|
+
if (value === undefined) {
|
|
29
|
+
return fallback;
|
|
30
|
+
}
|
|
31
|
+
const parsed = Number(value);
|
|
32
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
33
|
+
throw new CliUsageError(`${flag} must be a non-negative integer`);
|
|
34
|
+
}
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
37
|
+
export function readIntervalMs(argv) {
|
|
38
|
+
const seconds = readOptionalNumberFlag(argv, "--interval-seconds");
|
|
39
|
+
const minutes = readOptionalNumberFlag(argv, "--interval-minutes");
|
|
40
|
+
if (seconds !== undefined && minutes !== undefined) {
|
|
41
|
+
throw new CliUsageError("Use only one of --interval-seconds or --interval-minutes");
|
|
42
|
+
}
|
|
43
|
+
if (seconds !== undefined) {
|
|
44
|
+
return seconds * 1000;
|
|
45
|
+
}
|
|
46
|
+
if (minutes !== undefined) {
|
|
47
|
+
return minutes * 60 * 1000;
|
|
48
|
+
}
|
|
49
|
+
return DEFAULT_RUN_LOOP_INTERVAL_MS;
|
|
50
|
+
}
|
|
51
|
+
export function readOptionalNumberFlag(argv, flag) {
|
|
52
|
+
const value = readOptionalStringFlag(argv, flag);
|
|
53
|
+
if (value === undefined) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
const parsed = Number(value);
|
|
57
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
58
|
+
throw new CliUsageError(`${flag} must be a positive integer`);
|
|
59
|
+
}
|
|
60
|
+
return parsed;
|
|
61
|
+
}
|
|
62
|
+
export async function delayWithAbort(ms, signal) {
|
|
63
|
+
if (signal.aborted) {
|
|
64
|
+
throw createAbortError();
|
|
65
|
+
}
|
|
66
|
+
await new Promise((resolve, reject) => {
|
|
67
|
+
const timer = setTimeout(() => {
|
|
68
|
+
signal.removeEventListener("abort", onAbort);
|
|
69
|
+
resolve();
|
|
70
|
+
}, ms);
|
|
71
|
+
const onAbort = () => {
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
signal.removeEventListener("abort", onAbort);
|
|
74
|
+
reject(createAbortError());
|
|
75
|
+
};
|
|
76
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
export function createAbortError() {
|
|
80
|
+
const error = new Error("Aborted");
|
|
81
|
+
error.name = "AbortError";
|
|
82
|
+
return error;
|
|
83
|
+
}
|
|
84
|
+
export function isAbortError(error) {
|
|
85
|
+
return error instanceof Error && error.name === "AbortError";
|
|
86
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { recommendBranchSyncAction } from "../workstreams/branch-sync.js";
|
|
2
|
+
import { buildPacketLifecycleWorkstreamReport, formatPacketLifecycleReport } from "../workstreams/packet-lifecycle-report.js";
|
|
3
|
+
export function formatRunSummary(summary) {
|
|
4
|
+
const lines = [
|
|
5
|
+
`Orchestrator run ${summary.failedRepoCount > 0 || summary.failedTaskCount > 0 ? "finished with failures" : "completed successfully"}.`,
|
|
6
|
+
`Code root: ${summary.codeRoot}`,
|
|
7
|
+
`Management dir: ${summary.managementDir}`,
|
|
8
|
+
`Outputs dir: ${summary.outputsDir}`,
|
|
9
|
+
`State dir: ${summary.stateDir}`,
|
|
10
|
+
`Log dir: ${summary.logDir}`,
|
|
11
|
+
`Time: ${summary.startedAt} -> ${summary.completedAt}`,
|
|
12
|
+
`Repos: ${summary.repoCount} enabled, ${summary.syncedRepoCount} synced, ${summary.failedRepoCount} failed sync, ${summary.scannedRepoCount} scanned, ${summary.partiallyScannedRepoCount} partial scan, ${summary.failedScanRepoCount} failed scan`,
|
|
13
|
+
`Entities: ${summary.issueCount} issues, ${summary.pullRequestCount} PRs, ${summary.commentCount} comments`,
|
|
14
|
+
`Tasks: ${summary.taskCount} tracked, ${summary.pendingTaskCount} pending, ${summary.completedTaskCount} completed, ${summary.failedTaskCount} failed`,
|
|
15
|
+
`Product discovery: ${summary.productDiscoveryCount ?? 0}`,
|
|
16
|
+
`Product specification: ${summary.productSpecificationCount ?? 0}`,
|
|
17
|
+
`Waiting for product approval: ${summary.waitingForProductApprovalCount ?? 0}`,
|
|
18
|
+
`Executions: ${summary.executionCount}`,
|
|
19
|
+
"",
|
|
20
|
+
"GitOps:",
|
|
21
|
+
`- Management: ${formatManagementGitOps(summary.managementGitOps)}`,
|
|
22
|
+
`- Outputs: ${formatOutputsGitOps(summary.outputsGitOps)}`,
|
|
23
|
+
"",
|
|
24
|
+
"Repo results:"
|
|
25
|
+
];
|
|
26
|
+
for (const result of summary.results) {
|
|
27
|
+
lines.push(formatRepoResult(result));
|
|
28
|
+
}
|
|
29
|
+
if (summary.pullRequestBaseClassifications.length > 0) {
|
|
30
|
+
lines.push("");
|
|
31
|
+
lines.push("Pull request base classifications:");
|
|
32
|
+
for (const classification of summary.pullRequestBaseClassifications) {
|
|
33
|
+
if (classification.kind === "top_level") {
|
|
34
|
+
lines.push(`- ${classification.repoSlug} PR #${classification.pullRequestNumber} targets default branch \`${classification.defaultBranch}\`.`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
lines.push(`- ${classification.repoSlug} PR #${classification.pullRequestNumber} targets non-default parent branch \`${classification.baseBranch}\`; repo default branch is \`${classification.defaultBranch}\`.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (summary.packetPullRequestCandidates.length > 0) {
|
|
42
|
+
lines.push("");
|
|
43
|
+
lines.push("Packet PR candidates:");
|
|
44
|
+
for (const candidate of summary.packetPullRequestCandidates) {
|
|
45
|
+
if (candidate.status === "matched_parent_workstream") {
|
|
46
|
+
lines.push(`- ${candidate.repoSlug} PR #${candidate.pullRequestNumber} targets parent branch \`${candidate.baseBranch}\` and is attached to workstream \`${candidate.parentWorkstreamId}\`.`);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
lines.push(`- ${candidate.repoSlug} PR #${candidate.pullRequestNumber} targets non-default parent branch \`${candidate.baseBranch}\` but no active parent workstream was found.`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (summary.parentBranchSyncPreflights.length > 0) {
|
|
54
|
+
lines.push("");
|
|
55
|
+
lines.push("Parent branch sync preflights:");
|
|
56
|
+
for (const item of summary.parentBranchSyncPreflights) {
|
|
57
|
+
const preflight = item.preflight;
|
|
58
|
+
const parentPr = preflight.parentPrNumber !== undefined ? `#${preflight.parentPrNumber}` : "unknown";
|
|
59
|
+
lines.push(`- ${preflight.workstreamId}: sync \`${preflight.parentIntegrationBranch}\` from \`${preflight.defaultBranch}\` after wave ${preflight.currentWaveIndex} before wave ${preflight.nextWaveIndex}`);
|
|
60
|
+
lines.push(` status: ${formatBranchSyncComparison(item)}`);
|
|
61
|
+
lines.push(` recommendation: ${formatBranchSyncRecommendation(item)}`);
|
|
62
|
+
lines.push(` parent PR: ${parentPr}`);
|
|
63
|
+
lines.push(` completed packets: ${formatPacketIds(preflight.completedPacketIds)}`);
|
|
64
|
+
lines.push(` pending future packets: ${formatPacketIds(preflight.pendingFuturePacketIds)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const nextWaveReadyPreflights = collectNextWaveReadyPreflights(summary);
|
|
68
|
+
if (nextWaveReadyPreflights.length > 0) {
|
|
69
|
+
lines.push("");
|
|
70
|
+
lines.push("Next wave readiness:");
|
|
71
|
+
for (const item of nextWaveReadyPreflights) {
|
|
72
|
+
lines.push(...formatNextWaveReadyPreflight(item));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (summary.packetPlans.length > 0) {
|
|
76
|
+
lines.push("");
|
|
77
|
+
lines.push("Multi-wave packet lifecycle:");
|
|
78
|
+
for (const plan of summary.packetPlans) {
|
|
79
|
+
const report = buildPacketLifecycleWorkstreamReport({
|
|
80
|
+
workstream: {
|
|
81
|
+
id: plan.workstreamId,
|
|
82
|
+
repoKey: plan.repoKey,
|
|
83
|
+
integration: plan.parentPrNumber ? { branchName: "unknown", prNumber: plan.parentPrNumber } : undefined,
|
|
84
|
+
packetPlan: {
|
|
85
|
+
status: plan.status,
|
|
86
|
+
packets: Object.fromEntries(plan.waves.flatMap(w => w.packets).map(p => [p.id, p])),
|
|
87
|
+
waves: plan.waves.map(w => w.packetIds),
|
|
88
|
+
currentWaveIndex: plan.currentWaveIndex
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
packetPrsCreatedThisRun: summary.packetPrsCreatedThisRun,
|
|
92
|
+
waveAdvancementsThisRun: summary.packetWaveAdvancementsThisRun,
|
|
93
|
+
blockedThisRun: summary.packetWaveBlockedThisRun,
|
|
94
|
+
parentBranchSyncPreflights: summary.parentBranchSyncPreflights.map(p => ({
|
|
95
|
+
preflight: p.preflight,
|
|
96
|
+
branchSyncComparison: p.branchSyncComparison,
|
|
97
|
+
branchSyncComparisonSkippedReason: p.branchSyncComparisonSkippedReason
|
|
98
|
+
}))
|
|
99
|
+
});
|
|
100
|
+
if (report) {
|
|
101
|
+
lines.push(formatPacketLifecycleReport(report));
|
|
102
|
+
lines.push("");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (summary.packetPlans.length > 0) {
|
|
107
|
+
lines.push("");
|
|
108
|
+
lines.push("Packet plans:");
|
|
109
|
+
for (const plan of summary.packetPlans) {
|
|
110
|
+
const waveInfo = plan.currentWaveIndex !== undefined ? `, current wave ${plan.currentWaveIndex}` : "";
|
|
111
|
+
lines.push(`- ${plan.workstreamId} status ${plan.status}, ${plan.packetCount} packets, ${plan.waveCount} waves${waveInfo}`);
|
|
112
|
+
plan.waves.forEach((wave, index) => {
|
|
113
|
+
lines.push(` wave ${index}: ${wave.packetIds.join(", ")}`);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const failedTasks = summary.tasks.filter((task) => task.status === "failed");
|
|
118
|
+
if (failedTasks.length > 0) {
|
|
119
|
+
lines.push("");
|
|
120
|
+
lines.push("Failed tasks:");
|
|
121
|
+
for (const task of failedTasks) {
|
|
122
|
+
lines.push(`- ${task.repoSlug} ${task.kind}: ${task.summary}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const scanFailures = collectScanFailures(summary.results);
|
|
126
|
+
if (scanFailures.length > 0) {
|
|
127
|
+
lines.push("");
|
|
128
|
+
lines.push("Scan failures:");
|
|
129
|
+
for (const failure of scanFailures) {
|
|
130
|
+
lines.push(`- ${failure.repoSlug} ${failure.step} (${failure.scope}): ${failure.message}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
lines.push("");
|
|
134
|
+
lines.push(`Generated state: ${summary.stateDir}/{repos,issues,prs,comments,tasks,worktrees,workstreams,attention}.json`);
|
|
135
|
+
lines.push(`Execution log: ${summary.logDir}/{executions.json,artifacts/<execution-id>/...}`);
|
|
136
|
+
return `${lines.join("\n")}\n`;
|
|
137
|
+
}
|
|
138
|
+
function formatRepoResult(result) {
|
|
139
|
+
const parts = [`- ${result.repoSlug}`];
|
|
140
|
+
parts.push(result.status === "synced" ? "sync ok" : "sync failed");
|
|
141
|
+
if (result.githubScan) {
|
|
142
|
+
parts.push(`scan ${result.githubScan.status}`);
|
|
143
|
+
}
|
|
144
|
+
if (result.status === "failed" && result.error?.message) {
|
|
145
|
+
parts.push(result.error.message);
|
|
146
|
+
}
|
|
147
|
+
else if (result.githubScan?.failures[0]?.message) {
|
|
148
|
+
parts.push(result.githubScan.failures[0].message);
|
|
149
|
+
}
|
|
150
|
+
return parts.join(" | ");
|
|
151
|
+
}
|
|
152
|
+
function collectScanFailures(results) {
|
|
153
|
+
return results.flatMap((result) => result.githubScan?.failures ?? []);
|
|
154
|
+
}
|
|
155
|
+
function formatPacketIds(ids) {
|
|
156
|
+
return ids.length > 0 ? ids.join(", ") : "(none)";
|
|
157
|
+
}
|
|
158
|
+
function formatBranchSyncRecommendation(item) {
|
|
159
|
+
const recommendation = recommendBranchSyncAction(item.branchSyncComparison, item.branchSyncComparisonSkippedReason);
|
|
160
|
+
const nextWaveIndex = item.preflight.nextWaveIndex;
|
|
161
|
+
switch (recommendation) {
|
|
162
|
+
case "ready_to_advance":
|
|
163
|
+
return "parent branch is up to date with default; next wave may proceed";
|
|
164
|
+
case "sync_parent_from_default":
|
|
165
|
+
return `sync parent from default before starting wave ${nextWaveIndex}`;
|
|
166
|
+
case "manual_conflict_aware_sync_required":
|
|
167
|
+
return `parent and default diverged; perform conflict-aware sync before starting wave ${nextWaveIndex}`;
|
|
168
|
+
case "blocked_missing_ref":
|
|
169
|
+
return "blocked; inspect missing local branch refs before continuing";
|
|
170
|
+
case "blocked_unknown":
|
|
171
|
+
return "blocked; branch sync status is unavailable or unknown";
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function formatBranchSyncComparison(item) {
|
|
175
|
+
if (item.branchSyncComparisonSkippedReason === "missing_local_path") {
|
|
176
|
+
return "unavailable (missing local repo path)";
|
|
177
|
+
}
|
|
178
|
+
const comparison = item.branchSyncComparison;
|
|
179
|
+
if (!comparison) {
|
|
180
|
+
return "unavailable";
|
|
181
|
+
}
|
|
182
|
+
const hasCounts = comparison.parentBehindCount !== undefined && comparison.parentAheadCount !== undefined;
|
|
183
|
+
return hasCounts
|
|
184
|
+
? `${comparison.status} (behind ${comparison.parentBehindCount}, ahead ${comparison.parentAheadCount})`
|
|
185
|
+
: comparison.status;
|
|
186
|
+
}
|
|
187
|
+
function collectNextWaveReadyPreflights(summary) {
|
|
188
|
+
return summary.parentBranchSyncPreflights.filter((item) => recommendBranchSyncAction(item.branchSyncComparison, item.branchSyncComparisonSkippedReason) === "ready_to_advance");
|
|
189
|
+
}
|
|
190
|
+
function formatNextWaveReadyPreflight(item) {
|
|
191
|
+
const preflight = item.preflight;
|
|
192
|
+
const parentPr = preflight.parentPrNumber !== undefined ? `#${preflight.parentPrNumber}` : "unknown";
|
|
193
|
+
return [
|
|
194
|
+
`- ${preflight.workstreamId}: wave ${preflight.nextWaveIndex} is ready to prepare`,
|
|
195
|
+
` parent branch: ${preflight.parentIntegrationBranch}`,
|
|
196
|
+
` default branch: ${preflight.defaultBranch}`,
|
|
197
|
+
` parent PR: ${parentPr}`,
|
|
198
|
+
` completed packets: ${formatPacketIds(preflight.completedPacketIds)}`,
|
|
199
|
+
` pending future packets: ${formatPacketIds(preflight.pendingFuturePacketIds)}`
|
|
200
|
+
];
|
|
201
|
+
}
|
|
202
|
+
function formatManagementGitOps(result) {
|
|
203
|
+
if (!result) {
|
|
204
|
+
return "unknown";
|
|
205
|
+
}
|
|
206
|
+
switch (result.status) {
|
|
207
|
+
case "skipped_not_git":
|
|
208
|
+
return "skipped (not a Git worktree)";
|
|
209
|
+
case "pulled":
|
|
210
|
+
return "pulled with fast-forward-only update";
|
|
211
|
+
default:
|
|
212
|
+
return "unknown";
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function formatOutputsGitOps(result) {
|
|
216
|
+
if (!result) {
|
|
217
|
+
return "unknown";
|
|
218
|
+
}
|
|
219
|
+
switch (result.status) {
|
|
220
|
+
case "skipped_not_git":
|
|
221
|
+
return "skipped (not a Git worktree)";
|
|
222
|
+
case "clean_before_run":
|
|
223
|
+
return "clean before run; failed to finalize (check logs)";
|
|
224
|
+
case "no_changes":
|
|
225
|
+
return "clean before run; no generated changes";
|
|
226
|
+
case "pushed":
|
|
227
|
+
return `clean before run; committed and pushed ${result.commitSha}`;
|
|
228
|
+
default:
|
|
229
|
+
return "unknown";
|
|
230
|
+
}
|
|
231
|
+
}
|