@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,1241 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { GitHubTaskFailureNotifier } from "../../agents/failure-feedback.js";
|
|
5
|
+
import { GitHubPullRequestReviewPublisher } from "../../agents/review-feedback.js";
|
|
6
|
+
import { GitHubPlanBreakdownPublisher } from "../../agents/plan-breakdown-feedback.js";
|
|
7
|
+
import { GitHubWaveAssessmentPublisher } from "../../agents/wave-assessment-feedback.js";
|
|
8
|
+
import { GitHubTaskLifecycleNotifier } from "../../agents/lifecycle-feedback.js";
|
|
9
|
+
import { buildSinglePreDispatchFailureExecution, materializePreDispatchFailure, safelyNotifyFailure } from "../../agents/service.js";
|
|
10
|
+
import { validateCapabilities } from "../../capabilities/validator.js";
|
|
11
|
+
import { DefaultContextBuilder } from "../../context/types.js";
|
|
12
|
+
import { runDispatchWithMergePrepLifecycle } from "../runtime/dispatch-lifecycle.js";
|
|
13
|
+
import { createRunContext } from "../runtime/run-context.js";
|
|
14
|
+
import { setupProviderRuntime } from "../runtime/provider-runtime.js";
|
|
15
|
+
import { classifyPullRequestBase } from "../../github/pr-classification.js";
|
|
16
|
+
import { startGitHubBrokerServer } from "../../github/broker-server.js";
|
|
17
|
+
import { deriveTasks } from "../../tasks/derive.js";
|
|
18
|
+
import { buildEntityMaps, resolveTaskJournalIds, resolveRepoActionState, journalWorkstreamTransitions, journalTaskTransitions, journalWorktreeTransitions, journalNewExecutions, journalPacketWaveEvents } from "../run-journal.js";
|
|
19
|
+
import { deriveWorkstreams } from "../../workstreams/derive.js";
|
|
20
|
+
import { collectPacketPullRequestProjections } from "../../workstreams/packet-projections.js";
|
|
21
|
+
import { GitHubPacketLifecyclePublisher } from "../../workstreams/packet-lifecycle-feedback.js";
|
|
22
|
+
import { evaluatePacketWaveAdvancement } from "../../workstreams/branch-sync.js";
|
|
23
|
+
import { applyPlanBreakdownResultsToWorkstreams, buildParentBranchSyncPreflight, markPacketPlanningPrOpen } from "../../workstreams/packet-plan.js";
|
|
24
|
+
import { applyWaveAssessmentResultsToWorkstreams } from "../../workstreams/wave-assessment/checkpoint.js";
|
|
25
|
+
import { deriveAttentionRecords } from "../../attention/derive.js";
|
|
26
|
+
import { collectPacketPullRequestCandidates } from "../../workstreams/packet-candidates.js";
|
|
27
|
+
import { LocalParentBranchSyncService, } from "../../workstreams/parent-branch-sync.js";
|
|
28
|
+
import { executeParentBranchSyncActions } from "../../workstreams/parent-branch-sync-action.js";
|
|
29
|
+
import { registerRetrospectiveCandidates } from "../../retrospectives/candidates.js";
|
|
30
|
+
import { deriveRetrospectiveTasks } from "../../retrospectives/derive.js";
|
|
31
|
+
import { ingestRetrospectiveExecutions } from "../../retrospectives/ingest.js";
|
|
32
|
+
import { scoreRetrospectiveProblems, buildScopeValidationConfigFromRuntime } from "../../retrospectives/scoring.js";
|
|
33
|
+
import { validateLearningProposals } from "../../retrospectives/proposals.js";
|
|
34
|
+
import { materializeManagementLearningProposals } from "../../retrospectives/management-prs.js";
|
|
35
|
+
import { applyMcpSkipExclusion, rehydrateTaskMcpSkipContexts } from "../../workstreams/mcp-blockers.js";
|
|
36
|
+
import { CliUsageError } from "../errors.js";
|
|
37
|
+
/**
|
|
38
|
+
* Builds a packet-wave advancement evaluation for the current run based on summary workstreams
|
|
39
|
+
* and collected parent branch-sync preflights.
|
|
40
|
+
*
|
|
41
|
+
* This helper is pure and synchronous.
|
|
42
|
+
*/
|
|
43
|
+
export function buildPacketWaveAdvancementEvaluationForRun(input) {
|
|
44
|
+
return evaluatePacketWaveAdvancement({
|
|
45
|
+
workstreams: input.workstreams,
|
|
46
|
+
parentBranchSyncPreflights: input.parentBranchSyncPreflights,
|
|
47
|
+
waveAssessments: input.waveAssessments
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export async function processProviderLimitResumption(input) {
|
|
51
|
+
const providerLimits = [...(input.previousState.providerLimits ?? [])];
|
|
52
|
+
const currentTime = Date.parse(input.now);
|
|
53
|
+
const expiredProviders = new Set();
|
|
54
|
+
let providerLimitsChanged = false;
|
|
55
|
+
for (let index = 0; index < providerLimits.length; index += 1) {
|
|
56
|
+
const record = providerLimits[index];
|
|
57
|
+
if (record.status !== "limited" || !record.resetTime) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const resetTime = Date.parse(record.resetTime);
|
|
61
|
+
if (!Number.isFinite(resetTime) || resetTime > currentTime) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
expiredProviders.add(record.provider);
|
|
65
|
+
providerLimits[index] = {
|
|
66
|
+
...record,
|
|
67
|
+
status: "available",
|
|
68
|
+
updatedAt: input.now
|
|
69
|
+
};
|
|
70
|
+
providerLimitsChanged = true;
|
|
71
|
+
}
|
|
72
|
+
if (expiredProviders.size === 0) {
|
|
73
|
+
return input.previousState;
|
|
74
|
+
}
|
|
75
|
+
const currentIssuesById = new Map(input.currentIssues.map((issue) => [issue.id, issue]));
|
|
76
|
+
const currentPullRequestsById = new Map(input.currentPullRequests.map((pr) => [pr.id, pr]));
|
|
77
|
+
const tasks = [...input.previousState.tasks];
|
|
78
|
+
const executions = [...input.previousState.executions];
|
|
79
|
+
let tasksChanged = false;
|
|
80
|
+
let executionsChanged = false;
|
|
81
|
+
const queuedIndexes = tasks
|
|
82
|
+
.map((task, index) => ({ task, index }))
|
|
83
|
+
.filter(({ task }) => task.status === "queued" && task.waitingOnProvider !== undefined && expiredProviders.has(task.waitingOnProvider))
|
|
84
|
+
.sort((left, right) => {
|
|
85
|
+
const providerComparison = left.task.waitingOnProvider.localeCompare(right.task.waitingOnProvider);
|
|
86
|
+
if (providerComparison !== 0)
|
|
87
|
+
return providerComparison;
|
|
88
|
+
const leftQueuedAt = left.task.context.providerLimitQueuedAt ?? left.task.updatedAt;
|
|
89
|
+
const rightQueuedAt = right.task.context.providerLimitQueuedAt ?? right.task.updatedAt;
|
|
90
|
+
const queueComparison = leftQueuedAt.localeCompare(rightQueuedAt);
|
|
91
|
+
if (queueComparison !== 0)
|
|
92
|
+
return queueComparison;
|
|
93
|
+
return left.task.id.localeCompare(right.task.id);
|
|
94
|
+
});
|
|
95
|
+
for (const { task, index } of queuedIndexes) {
|
|
96
|
+
if (isQueuedTaskStillValid(task, currentIssuesById, currentPullRequestsById)) {
|
|
97
|
+
tasks[index] = {
|
|
98
|
+
...task,
|
|
99
|
+
status: "pending",
|
|
100
|
+
waitingOnProvider: undefined,
|
|
101
|
+
updatedAt: input.now,
|
|
102
|
+
context: {
|
|
103
|
+
...task.context,
|
|
104
|
+
notifiedQueued: false,
|
|
105
|
+
providerLimitQueuedAt: task.context.providerLimitQueuedAt ?? task.updatedAt,
|
|
106
|
+
providerLimitResumedProvider: task.waitingOnProvider
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
tasksChanged = true;
|
|
110
|
+
await input.lifecycleNotifier.notifyResumed(tasks[index]);
|
|
111
|
+
input.logger.info?.(`[provider-limits] Resumed queued task ${task.id} after ${task.waitingOnProvider} became available.`);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const failure = await buildSinglePreDispatchFailureExecution({
|
|
115
|
+
task,
|
|
116
|
+
reason: "Task was canceled or the owning PR/issue was closed while queued.",
|
|
117
|
+
logDir: input.logDir,
|
|
118
|
+
now: () => input.now
|
|
119
|
+
});
|
|
120
|
+
tasks[index] = {
|
|
121
|
+
...failure.updatedTask,
|
|
122
|
+
waitingOnProvider: undefined,
|
|
123
|
+
context: {
|
|
124
|
+
...failure.updatedTask.context,
|
|
125
|
+
notifiedQueued: false,
|
|
126
|
+
providerLimitQueuedAt: undefined,
|
|
127
|
+
providerLimitResumedProvider: undefined
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
executions.push(failure.execution);
|
|
131
|
+
tasksChanged = true;
|
|
132
|
+
executionsChanged = true;
|
|
133
|
+
await input.lifecycleNotifier.notifyCanceled(task);
|
|
134
|
+
input.logger.info?.(`[provider-limits] Canceled queued task ${task.id}; owning PR/issue is no longer open.`);
|
|
135
|
+
}
|
|
136
|
+
if (providerLimitsChanged) {
|
|
137
|
+
await input.stateStore.writeProviderLimitState(providerLimits);
|
|
138
|
+
}
|
|
139
|
+
if (executionsChanged) {
|
|
140
|
+
await input.stateStore.writeExecutionState(executions);
|
|
141
|
+
}
|
|
142
|
+
if (tasksChanged) {
|
|
143
|
+
await input.stateStore.writeTaskState(tasks);
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
...input.previousState,
|
|
147
|
+
providerLimits,
|
|
148
|
+
tasks,
|
|
149
|
+
executions
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function isQueuedTaskStillValid(task, currentIssuesById, currentPullRequestsById) {
|
|
153
|
+
const sourceId = task.parentSourceId ?? task.sourceId;
|
|
154
|
+
const sourceKind = task.parentSourceKind ?? task.sourceKind;
|
|
155
|
+
if (sourceKind === "issue") {
|
|
156
|
+
const issue = currentIssuesById.get(sourceId);
|
|
157
|
+
return issue !== undefined && issue.state === "open" && !hasCanceledLabel(issue.labels);
|
|
158
|
+
}
|
|
159
|
+
if (sourceKind === "pull_request") {
|
|
160
|
+
const pullRequest = currentPullRequestsById.get(sourceId);
|
|
161
|
+
return pullRequest !== undefined && pullRequest.state === "open" && !hasCanceledLabel(pullRequest.labels);
|
|
162
|
+
}
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
function hasCanceledLabel(labels) {
|
|
166
|
+
return labels.some((label) => {
|
|
167
|
+
const normalized = label.toLowerCase();
|
|
168
|
+
return normalized === "canceled" || normalized === "cancelled" || normalized === "agent-canceled" || normalized === "agent-cancelled";
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function applyMcpSkipRequests(input) {
|
|
172
|
+
let tasks = input.tasks;
|
|
173
|
+
let workstreams = input.workstreams;
|
|
174
|
+
let changed = false;
|
|
175
|
+
const appliedExclusions = [];
|
|
176
|
+
for (const attention of input.attention) {
|
|
177
|
+
if (!attention.reasons.includes("mcp_skip_requested") ||
|
|
178
|
+
attention.mcpSkipTargetTaskId === undefined ||
|
|
179
|
+
attention.mcpSkipMcpHandle === undefined) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const workstreamIndex = workstreams.findIndex((workstream) => workstream.id === attention.workstreamId);
|
|
183
|
+
if (workstreamIndex === -1) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const workstream = workstreams[workstreamIndex];
|
|
187
|
+
const blocker = (workstream.mcpBlockers ?? []).find((candidate) => candidate.blockedTaskId === attention.mcpSkipTargetTaskId &&
|
|
188
|
+
candidate.mcpHandle === attention.mcpSkipMcpHandle &&
|
|
189
|
+
candidate.resolutionState === "waiting_for_user");
|
|
190
|
+
if (!blocker) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const updatedWorkstream = applyMcpSkipExclusion(workstream, blocker.blockedTaskId, blocker.blockedTaskKind, blocker.packetId, blocker.mcpHandle, blocker.affectedCapabilityNames, input.now);
|
|
194
|
+
if (updatedWorkstream !== workstream) {
|
|
195
|
+
workstreams = [
|
|
196
|
+
...workstreams.slice(0, workstreamIndex),
|
|
197
|
+
updatedWorkstream,
|
|
198
|
+
...workstreams.slice(workstreamIndex + 1)
|
|
199
|
+
];
|
|
200
|
+
const exclusion = updatedWorkstream.mcpSkipExclusions?.find((candidate) => candidate.taskId === blocker.blockedTaskId &&
|
|
201
|
+
candidate.mcpHandle === blocker.mcpHandle);
|
|
202
|
+
if (exclusion) {
|
|
203
|
+
appliedExclusions.push(exclusion);
|
|
204
|
+
}
|
|
205
|
+
changed = true;
|
|
206
|
+
}
|
|
207
|
+
const taskIndex = tasks.findIndex((task) => task.id === blocker.blockedTaskId);
|
|
208
|
+
if (taskIndex === -1) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const task = tasks[taskIndex];
|
|
212
|
+
const mcpSkipContexts = [...(task.context.mcpSkipContexts ?? [])];
|
|
213
|
+
const alreadyRecorded = mcpSkipContexts.some((context) => context.mcpHandle === blocker.mcpHandle);
|
|
214
|
+
if (!alreadyRecorded) {
|
|
215
|
+
mcpSkipContexts.push({
|
|
216
|
+
mcpHandle: blocker.mcpHandle,
|
|
217
|
+
affectedCapabilityNames: blocker.affectedCapabilityNames,
|
|
218
|
+
skipReason: "User explicitly skipped MCP authorization for this task."
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
tasks = [
|
|
222
|
+
...tasks.slice(0, taskIndex),
|
|
223
|
+
{
|
|
224
|
+
...task,
|
|
225
|
+
status: task.status === "failed" ? "pending" : task.status,
|
|
226
|
+
context: {
|
|
227
|
+
...task.context,
|
|
228
|
+
recoveryNeeded: false,
|
|
229
|
+
mcpSkipContexts
|
|
230
|
+
},
|
|
231
|
+
updatedAt: input.now
|
|
232
|
+
},
|
|
233
|
+
...tasks.slice(taskIndex + 1)
|
|
234
|
+
];
|
|
235
|
+
changed = true;
|
|
236
|
+
}
|
|
237
|
+
return { tasks, workstreams, changed, appliedExclusions };
|
|
238
|
+
}
|
|
239
|
+
export async function runCli(argv, options = {}) {
|
|
240
|
+
const [command, ...rest] = argv;
|
|
241
|
+
if (command !== "run") {
|
|
242
|
+
throw new CliUsageError('Expected command: backburner run [--code-root <path>] [--management-dir <path>] [--outputs-dir <path>] [--log-dir <path>] [--broker-port <port>] [--headless] [--once]');
|
|
243
|
+
}
|
|
244
|
+
const startedAt = new Date().toISOString();
|
|
245
|
+
const cycleId = randomUUID();
|
|
246
|
+
options.statusTracker?.onCycleStart(options.cycleNo ?? 1);
|
|
247
|
+
const context = await createRunContext({ rest, options });
|
|
248
|
+
const { managementDir, outputsDir, stateDir, logDir, logger, codeRoot, brokerPort, commandRunner, gitGateway, gitOpsLifecycle, workspaceRecoveryService, managementGitOps, config, agentPool, outputsGitOpsPrepared, managementRepo, stateStore, journal, memoryStore, memoryToolService, backburnerGitToolService, previousState: loadedPreviousState, currentMcpStatuses, gitHubGateway, syncService, scanService, implementationWorkflowService, triageFollowUpService, mergePrepService, enabledRepos, results } = context;
|
|
249
|
+
let previousState = loadedPreviousState;
|
|
250
|
+
await journal.write({
|
|
251
|
+
cycleId,
|
|
252
|
+
scope: "system",
|
|
253
|
+
scopeId: cycleId,
|
|
254
|
+
category: "observation",
|
|
255
|
+
severity: "info",
|
|
256
|
+
summary: `Orchestration cycle started with ${enabledRepos.length} enabled repo(s)`,
|
|
257
|
+
observedState: { codeRoot, managementDir, outputsDir, enabledRepoCount: enabledRepos.length }
|
|
258
|
+
});
|
|
259
|
+
if (enabledRepos.length === 0) {
|
|
260
|
+
await journal.recordNoAction({
|
|
261
|
+
cycleId,
|
|
262
|
+
scope: "system",
|
|
263
|
+
scopeId: cycleId,
|
|
264
|
+
reason: "No enabled repositories configured",
|
|
265
|
+
derivedState: { totalRepos: config.repos.length, enabledRepos: 0 }
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
for (const repo of enabledRepos) {
|
|
269
|
+
options.statusTracker?.onRepoSyncStart(`${repo.owner}/${repo.name}`);
|
|
270
|
+
logger.info(`Syncing ${repo.owner}/${repo.name} into ${path.join(codeRoot, repo.localPath)}`);
|
|
271
|
+
const result = await syncService.syncRepository(repo, codeRoot);
|
|
272
|
+
if (result.status === "failed") {
|
|
273
|
+
logger.warn(`Failed to sync ${result.repoSlug}: ${result.error?.message ?? result.message}`);
|
|
274
|
+
}
|
|
275
|
+
results.push(result);
|
|
276
|
+
await journal.write({
|
|
277
|
+
cycleId,
|
|
278
|
+
scope: "repo",
|
|
279
|
+
scopeId: result.repoId,
|
|
280
|
+
repoId: result.repoId,
|
|
281
|
+
category: "sync",
|
|
282
|
+
severity: result.status === "synced" ? "info" : "error",
|
|
283
|
+
summary: result.status === "synced"
|
|
284
|
+
? `Synced ${result.repoSlug}`
|
|
285
|
+
: `Failed to sync ${result.repoSlug}: ${result.error?.message ?? result.message}`,
|
|
286
|
+
observedState: {
|
|
287
|
+
repoSlug: result.repoSlug,
|
|
288
|
+
status: result.status,
|
|
289
|
+
cloned: result.cloned,
|
|
290
|
+
fetched: result.fetched,
|
|
291
|
+
defaultBranch: result.defaultBranch,
|
|
292
|
+
syncedAt: result.syncedAt
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
options.statusTracker?.onGitHubScanStart();
|
|
297
|
+
logger.info(`Scanning ${enabledRepos.length} configured repos via GitHub`);
|
|
298
|
+
const scanResult = await scanService.scanRepositories(enabledRepos, results, previousState);
|
|
299
|
+
const generatedAt = new Date().toISOString();
|
|
300
|
+
const lifecycleNotifier = new GitHubTaskLifecycleNotifier(gitHubGateway, enabledRepos, logger);
|
|
301
|
+
previousState = await processProviderLimitResumption({
|
|
302
|
+
previousState,
|
|
303
|
+
stateStore,
|
|
304
|
+
currentIssues: scanResult.issues,
|
|
305
|
+
currentPullRequests: scanResult.prs,
|
|
306
|
+
lifecycleNotifier,
|
|
307
|
+
logDir,
|
|
308
|
+
now: generatedAt,
|
|
309
|
+
logger
|
|
310
|
+
});
|
|
311
|
+
for (const repoResult of scanResult.repoResults) {
|
|
312
|
+
const scan = repoResult.githubScan;
|
|
313
|
+
if (scan) {
|
|
314
|
+
await journal.write({
|
|
315
|
+
cycleId,
|
|
316
|
+
scope: "repo",
|
|
317
|
+
scopeId: repoResult.repoId,
|
|
318
|
+
repoId: repoResult.repoId,
|
|
319
|
+
category: "observation",
|
|
320
|
+
severity: scan.status === "failed" ? "error" : scan.status === "partial" ? "warning" : "info",
|
|
321
|
+
summary: `GitHub scan ${scan.status} for ${repoResult.repoSlug}: ${scan.issueCount} issues, ${scan.pullRequestCount} PRs`,
|
|
322
|
+
observedState: {
|
|
323
|
+
repoSlug: repoResult.repoSlug,
|
|
324
|
+
scanStatus: scan.status,
|
|
325
|
+
issueCount: scan.issueCount,
|
|
326
|
+
pullRequestCount: scan.pullRequestCount,
|
|
327
|
+
commentCount: scan.commentCount,
|
|
328
|
+
failureCount: scan.failures.length
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const baseLinkedWorktrees = implementationWorkflowService.linkPullRequests(previousState.worktrees, scanResult.prs, enabledRepos);
|
|
334
|
+
const initialWorkstreams = deriveWorkstreams({
|
|
335
|
+
repos: enabledRepos,
|
|
336
|
+
currentIssues: scanResult.issues,
|
|
337
|
+
currentPullRequests: scanResult.prs,
|
|
338
|
+
previousPullRequests: previousState.prs,
|
|
339
|
+
worktrees: baseLinkedWorktrees,
|
|
340
|
+
previousWorkstreams: previousState.workstreams
|
|
341
|
+
});
|
|
342
|
+
const packetPullRequestProjections = collectPacketPullRequestProjections({
|
|
343
|
+
pullRequests: scanResult.prs,
|
|
344
|
+
workstreams: initialWorkstreams,
|
|
345
|
+
worktrees: baseLinkedWorktrees
|
|
346
|
+
});
|
|
347
|
+
const linkedWorktrees = implementationWorkflowService.reconcilePacketWorktreeReadinessFromProjections(baseLinkedWorktrees, packetPullRequestProjections, enabledRepos, previousState.tasks, initialWorkstreams, scanResult.prs);
|
|
348
|
+
const initialAttention = deriveAttentionRecords({
|
|
349
|
+
repos: enabledRepos,
|
|
350
|
+
workstreams: initialWorkstreams,
|
|
351
|
+
previousState,
|
|
352
|
+
currentIssues: scanResult.issues,
|
|
353
|
+
currentPullRequests: scanResult.prs,
|
|
354
|
+
currentComments: scanResult.comments,
|
|
355
|
+
tasks: previousState.tasks,
|
|
356
|
+
now: generatedAt
|
|
357
|
+
});
|
|
358
|
+
let phaseAwareWorkstreams = deriveWorkstreams({
|
|
359
|
+
repos: enabledRepos,
|
|
360
|
+
currentIssues: scanResult.issues,
|
|
361
|
+
currentPullRequests: scanResult.prs,
|
|
362
|
+
previousPullRequests: previousState.prs,
|
|
363
|
+
worktrees: linkedWorktrees,
|
|
364
|
+
previousWorkstreams: previousState.workstreams,
|
|
365
|
+
attention: initialAttention
|
|
366
|
+
});
|
|
367
|
+
let summaryWorkstreams = phaseAwareWorkstreams;
|
|
368
|
+
const candidateResult = registerRetrospectiveCandidates({
|
|
369
|
+
previousCandidates: previousState.retrospectiveCandidates,
|
|
370
|
+
workstreams: phaseAwareWorkstreams,
|
|
371
|
+
issues: scanResult.issues,
|
|
372
|
+
pullRequests: scanResult.prs,
|
|
373
|
+
tasks: previousState.tasks,
|
|
374
|
+
executions: previousState.executions,
|
|
375
|
+
worktrees: linkedWorktrees,
|
|
376
|
+
managementPrs: previousState.managementPrs,
|
|
377
|
+
...(managementRepo ? { managementRepo } : {}),
|
|
378
|
+
now: generatedAt
|
|
379
|
+
});
|
|
380
|
+
let tasks = deriveTasks({
|
|
381
|
+
repos: enabledRepos,
|
|
382
|
+
repoSyncRecords: scanResult.repoResults,
|
|
383
|
+
previousState,
|
|
384
|
+
currentIssues: scanResult.issues,
|
|
385
|
+
currentPullRequests: scanResult.prs,
|
|
386
|
+
currentComments: scanResult.comments,
|
|
387
|
+
worktrees: linkedWorktrees,
|
|
388
|
+
workstreams: phaseAwareWorkstreams,
|
|
389
|
+
attention: initialAttention,
|
|
390
|
+
contextBuilder: new DefaultContextBuilder(),
|
|
391
|
+
now: generatedAt,
|
|
392
|
+
...(managementRepo ? { managementRepo } : {})
|
|
393
|
+
});
|
|
394
|
+
const mcpSkipApplication = applyMcpSkipRequests({
|
|
395
|
+
tasks,
|
|
396
|
+
workstreams: phaseAwareWorkstreams,
|
|
397
|
+
attention: initialAttention,
|
|
398
|
+
now: generatedAt
|
|
399
|
+
});
|
|
400
|
+
if (mcpSkipApplication.changed) {
|
|
401
|
+
tasks = mcpSkipApplication.tasks;
|
|
402
|
+
phaseAwareWorkstreams = mcpSkipApplication.workstreams;
|
|
403
|
+
summaryWorkstreams = phaseAwareWorkstreams;
|
|
404
|
+
}
|
|
405
|
+
const skipContextHydration = rehydrateTaskMcpSkipContexts({
|
|
406
|
+
tasks,
|
|
407
|
+
workstreams: phaseAwareWorkstreams
|
|
408
|
+
});
|
|
409
|
+
if (skipContextHydration.changed) {
|
|
410
|
+
tasks = skipContextHydration.tasks;
|
|
411
|
+
}
|
|
412
|
+
// Build entity maps for issue/PR number resolution in journal entries
|
|
413
|
+
const entityMaps = buildEntityMaps(scanResult.issues, scanResult.prs, enabledRepos);
|
|
414
|
+
const previousTaskIds = new Set(previousState.tasks.map((t) => t.id));
|
|
415
|
+
const newTasks = tasks.filter((t) => !previousTaskIds.has(t.id));
|
|
416
|
+
for (const task of newTasks) {
|
|
417
|
+
const taskIds = resolveTaskJournalIds(task, entityMaps, phaseAwareWorkstreams);
|
|
418
|
+
if (task.relevant) {
|
|
419
|
+
await journal.write({
|
|
420
|
+
cycleId,
|
|
421
|
+
scope: task.workstreamId ? "workstream" : "repo",
|
|
422
|
+
scopeId: task.workstreamId ?? task.repoId,
|
|
423
|
+
repoId: task.repoId,
|
|
424
|
+
...(task.workstreamId !== undefined ? { workstreamId: task.workstreamId } : {}),
|
|
425
|
+
...(taskIds.issueNumber !== undefined ? { issueNumber: taskIds.issueNumber } : {}),
|
|
426
|
+
...(taskIds.prNumber !== undefined ? { prNumber: taskIds.prNumber } : {}),
|
|
427
|
+
category: "task_created",
|
|
428
|
+
severity: "info",
|
|
429
|
+
summary: `Task created: ${task.kind} for ${task.repoSlug}`,
|
|
430
|
+
observedState: { sourceKind: task.sourceKind, sourceId: task.sourceId },
|
|
431
|
+
derivedState: { taskId: task.id, kind: task.kind, relevant: true, reason: task.reason },
|
|
432
|
+
expectedNextAction: `Dispatch ${task.kind} to agent`
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
await journal.write({
|
|
437
|
+
cycleId,
|
|
438
|
+
scope: task.workstreamId ? "workstream" : "repo",
|
|
439
|
+
scopeId: task.workstreamId ?? task.repoId,
|
|
440
|
+
repoId: task.repoId,
|
|
441
|
+
...(task.workstreamId !== undefined ? { workstreamId: task.workstreamId } : {}),
|
|
442
|
+
...(taskIds.issueNumber !== undefined ? { issueNumber: taskIds.issueNumber } : {}),
|
|
443
|
+
...(taskIds.prNumber !== undefined ? { prNumber: taskIds.prNumber } : {}),
|
|
444
|
+
category: "task_skipped",
|
|
445
|
+
severity: "info",
|
|
446
|
+
summary: `Task skipped: ${task.kind} for ${task.repoSlug} — ${task.reason}`,
|
|
447
|
+
noActionReason: task.reason,
|
|
448
|
+
observedState: { sourceKind: task.sourceKind, sourceId: task.sourceId },
|
|
449
|
+
derivedState: { taskId: task.id, kind: task.kind, relevant: false, reason: task.reason }
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// Comprehensive per-repo no-action journaling (checks sync failure, scan failure, workstream phases, etc.)
|
|
454
|
+
const syncRecordById = new Map(scanResult.repoResults.map((r) => [r.repoId, r]));
|
|
455
|
+
for (const repo of enabledRepos) {
|
|
456
|
+
const actionState = resolveRepoActionState({
|
|
457
|
+
repoId: repo.id,
|
|
458
|
+
repoSlug: `${repo.owner}/${repo.name}`,
|
|
459
|
+
syncRecord: syncRecordById.get(repo.id),
|
|
460
|
+
tasks,
|
|
461
|
+
workstreams: phaseAwareWorkstreams,
|
|
462
|
+
issues: scanResult.issues,
|
|
463
|
+
prs: scanResult.prs
|
|
464
|
+
});
|
|
465
|
+
if (!actionState.hasAction && actionState.noActionReason !== undefined) {
|
|
466
|
+
await journal.recordNoAction({
|
|
467
|
+
cycleId,
|
|
468
|
+
scope: "repo",
|
|
469
|
+
scopeId: repo.id,
|
|
470
|
+
repoId: repo.id,
|
|
471
|
+
reason: actionState.noActionReason,
|
|
472
|
+
observedState: {
|
|
473
|
+
repoSlug: `${repo.owner}/${repo.name}`,
|
|
474
|
+
taskCount: tasks.filter((t) => t.repoId === repo.id).length
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
// Journal workstream transitions (phase/attention/status changes)
|
|
480
|
+
await journalWorkstreamTransitions({
|
|
481
|
+
cycleId,
|
|
482
|
+
previousWorkstreams: previousState.workstreams,
|
|
483
|
+
currentWorkstreams: phaseAwareWorkstreams,
|
|
484
|
+
journal,
|
|
485
|
+
entityMaps
|
|
486
|
+
});
|
|
487
|
+
// Track execution IDs before dispatch for dedup
|
|
488
|
+
const executionIdsBeforeCycle = new Set(previousState.executions.map((e) => e.id));
|
|
489
|
+
await stateStore.writeRepoSyncState(scanResult.repoResults);
|
|
490
|
+
await stateStore.writeEntityState({
|
|
491
|
+
issueMetadata: buildMetadata(generatedAt, scanResult.issues.length, enabledRepos.length, scanResult.failures),
|
|
492
|
+
prMetadata: buildMetadata(generatedAt, scanResult.prs.length, enabledRepos.length, scanResult.failures),
|
|
493
|
+
commentMetadata: buildMetadata(generatedAt, scanResult.comments.length, enabledRepos.length, scanResult.failures),
|
|
494
|
+
issues: scanResult.issues,
|
|
495
|
+
prs: scanResult.prs,
|
|
496
|
+
comments: scanResult.comments
|
|
497
|
+
});
|
|
498
|
+
await stateStore.writeWorktreeState(linkedWorktrees);
|
|
499
|
+
await stateStore.writeWorkstreamState(phaseAwareWorkstreams);
|
|
500
|
+
await stateStore.writeAttentionState(initialAttention);
|
|
501
|
+
await stateStore.writeTaskState(tasks);
|
|
502
|
+
await stateStore.writeRetrospectiveCandidateState(candidateResult.candidates);
|
|
503
|
+
const parentBranchSyncService = options.parentBranchSyncService ?? new LocalParentBranchSyncService(gitGateway);
|
|
504
|
+
const syncActionResult = await executeParentBranchSyncActions({
|
|
505
|
+
tasks,
|
|
506
|
+
executions: previousState.executions,
|
|
507
|
+
parentBranchSyncService,
|
|
508
|
+
gitGateway,
|
|
509
|
+
gitHubGateway,
|
|
510
|
+
stateStore,
|
|
511
|
+
logDir,
|
|
512
|
+
logger
|
|
513
|
+
});
|
|
514
|
+
let tasksAfterSync = syncActionResult.tasks;
|
|
515
|
+
const executionsAfterSync = syncActionResult.executions;
|
|
516
|
+
const postSyncSkipContextHydration = rehydrateTaskMcpSkipContexts({
|
|
517
|
+
tasks: tasksAfterSync,
|
|
518
|
+
workstreams: phaseAwareWorkstreams
|
|
519
|
+
});
|
|
520
|
+
if (postSyncSkipContextHydration.changed) {
|
|
521
|
+
tasksAfterSync = postSyncSkipContextHydration.tasks;
|
|
522
|
+
await stateStore.writeTaskState(tasksAfterSync);
|
|
523
|
+
}
|
|
524
|
+
let brokerServer;
|
|
525
|
+
let brokerLogger;
|
|
526
|
+
let agentRunner = options.agentRunner;
|
|
527
|
+
let capabilitiesByProvider;
|
|
528
|
+
let providerBrokerToolAllowLists;
|
|
529
|
+
let brokerToolScope;
|
|
530
|
+
let packetPrsCreatedThisRun = [];
|
|
531
|
+
let packetWaveAdvancementsThisRun = [];
|
|
532
|
+
let packetWaveBlockedThisRun = [];
|
|
533
|
+
const failureNotifier = new GitHubTaskFailureNotifier(gitHubGateway, enabledRepos, logger);
|
|
534
|
+
const reviewPublisher = new GitHubPullRequestReviewPublisher(gitHubGateway, enabledRepos, logger);
|
|
535
|
+
const planBreakdownPublisher = new GitHubPlanBreakdownPublisher(gitHubGateway, enabledRepos, logger);
|
|
536
|
+
const waveAssessmentPublisher = new GitHubWaveAssessmentPublisher(gitHubGateway, enabledRepos, logger);
|
|
537
|
+
const capabilityValidation = validateCapabilities(config.capabilities, config.mcps, currentMcpStatuses);
|
|
538
|
+
let dispatchResult = {
|
|
539
|
+
tasks: tasksAfterSync,
|
|
540
|
+
executions: executionsAfterSync,
|
|
541
|
+
workstreams: phaseAwareWorkstreams
|
|
542
|
+
};
|
|
543
|
+
let dispatchSetupFailed = false;
|
|
544
|
+
try {
|
|
545
|
+
const providerRuntime = await setupProviderRuntime({
|
|
546
|
+
agentRunner,
|
|
547
|
+
commandRunner,
|
|
548
|
+
config,
|
|
549
|
+
currentMcpStatuses,
|
|
550
|
+
gitHubGateway,
|
|
551
|
+
logger,
|
|
552
|
+
logDir,
|
|
553
|
+
brokerPort,
|
|
554
|
+
startBrokerServer: options.startBrokerServer ?? startGitHubBrokerServer,
|
|
555
|
+
memoryToolService,
|
|
556
|
+
backburnerGitToolService
|
|
557
|
+
});
|
|
558
|
+
agentRunner = providerRuntime.agentRunner;
|
|
559
|
+
capabilitiesByProvider = providerRuntime.capabilitiesByProvider;
|
|
560
|
+
providerBrokerToolAllowLists = providerRuntime.providerBrokerToolAllowLists;
|
|
561
|
+
brokerToolScope = providerRuntime.brokerToolScope;
|
|
562
|
+
brokerServer = providerRuntime.brokerServer;
|
|
563
|
+
brokerLogger = providerRuntime.brokerLogger;
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
dispatchSetupFailed = true;
|
|
567
|
+
const failedAt = new Date().toISOString();
|
|
568
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
569
|
+
logger.warn(`Dispatch setup failed before agent execution began: ${message}`);
|
|
570
|
+
const failureResult = await materializePreDispatchFailure({
|
|
571
|
+
tasks: tasksAfterSync,
|
|
572
|
+
existingExecutions: executionsAfterSync,
|
|
573
|
+
logDir,
|
|
574
|
+
error,
|
|
575
|
+
now: () => failedAt
|
|
576
|
+
});
|
|
577
|
+
await stateStore.writeExecutionState(failureResult.executions);
|
|
578
|
+
await stateStore.writeTaskState(failureResult.tasks);
|
|
579
|
+
let preDispatchTasks = [...failureResult.tasks];
|
|
580
|
+
let preDispatchTasksDirty = false;
|
|
581
|
+
for (let pi = 0; pi < preDispatchTasks.length; pi++) {
|
|
582
|
+
const task = preDispatchTasks[pi];
|
|
583
|
+
if (task.status !== "failed" || !task.lastExecutionId) {
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
const execution = failureResult.executions.find((entry) => entry.id === task.lastExecutionId);
|
|
587
|
+
if (!execution) {
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
const failureCommentId = await safelyNotifyFailure(failureNotifier, task, execution);
|
|
591
|
+
if (failureCommentId !== undefined) {
|
|
592
|
+
preDispatchTasks[pi] = { ...task, lastFailureCommentId: failureCommentId };
|
|
593
|
+
preDispatchTasksDirty = true;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
if (preDispatchTasksDirty) {
|
|
597
|
+
await stateStore.writeTaskState(preDispatchTasks);
|
|
598
|
+
}
|
|
599
|
+
dispatchResult = { ...failureResult, tasks: preDispatchTasks, workstreams: phaseAwareWorkstreams };
|
|
600
|
+
// Journal failed executions from pre-dispatch setup failure
|
|
601
|
+
await journalNewExecutions({
|
|
602
|
+
cycleId,
|
|
603
|
+
executionIdsBeforeCycle,
|
|
604
|
+
dispatchResult: failureResult,
|
|
605
|
+
journal,
|
|
606
|
+
entityMaps,
|
|
607
|
+
workstreams: phaseAwareWorkstreams
|
|
608
|
+
});
|
|
609
|
+
await journalTaskTransitions({
|
|
610
|
+
cycleId,
|
|
611
|
+
tasksBefore: tasks,
|
|
612
|
+
tasksAfter: failureResult.tasks,
|
|
613
|
+
journal,
|
|
614
|
+
entityMaps,
|
|
615
|
+
workstreams: phaseAwareWorkstreams
|
|
616
|
+
});
|
|
617
|
+
if (brokerServer) {
|
|
618
|
+
await brokerServer.close();
|
|
619
|
+
brokerServer = undefined;
|
|
620
|
+
}
|
|
621
|
+
if (brokerLogger) {
|
|
622
|
+
await brokerLogger.close();
|
|
623
|
+
brokerLogger = undefined;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (!dispatchSetupFailed) {
|
|
627
|
+
try {
|
|
628
|
+
let dispatchAgentRunner = agentRunner;
|
|
629
|
+
if (!dispatchAgentRunner) {
|
|
630
|
+
throw new Error("Agent runner was not initialized.");
|
|
631
|
+
}
|
|
632
|
+
if (options.statusTracker) {
|
|
633
|
+
const tracker = options.statusTracker;
|
|
634
|
+
const realRunner = dispatchAgentRunner;
|
|
635
|
+
dispatchAgentRunner = {
|
|
636
|
+
dispatch: async (task, context, agentConfig, sessionDecision, promptContent) => {
|
|
637
|
+
tracker.onTaskStart(task, agentConfig);
|
|
638
|
+
try {
|
|
639
|
+
const res = await realRunner.dispatch(task, context, agentConfig, sessionDecision, promptContent);
|
|
640
|
+
tracker.onTaskEnd(task.id);
|
|
641
|
+
return res;
|
|
642
|
+
}
|
|
643
|
+
catch (err) {
|
|
644
|
+
tracker.onTaskEnd(task.id);
|
|
645
|
+
throw err;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
logger.info(`Dispatching pending tasks (${tasksAfterSync.filter((task) => task.status === "pending").length})`);
|
|
651
|
+
const initialDispatchResult = await runDispatchWithMergePrepLifecycle({
|
|
652
|
+
tasks: tasksAfterSync,
|
|
653
|
+
worktrees: linkedWorktrees,
|
|
654
|
+
workstreams: phaseAwareWorkstreams,
|
|
655
|
+
capabilityValidation,
|
|
656
|
+
mcpStatuses: currentMcpStatuses,
|
|
657
|
+
declaredMcps: config.mcps,
|
|
658
|
+
capabilitiesConfig: config.capabilities,
|
|
659
|
+
agents: config.agents,
|
|
660
|
+
enabledRepos,
|
|
661
|
+
codeRoot,
|
|
662
|
+
executions: executionsAfterSync,
|
|
663
|
+
stateStore,
|
|
664
|
+
logDir,
|
|
665
|
+
agentRunner: dispatchAgentRunner,
|
|
666
|
+
failureNotifier,
|
|
667
|
+
reviewPublisher,
|
|
668
|
+
planBreakdownPublisher,
|
|
669
|
+
waveAssessmentPublisher,
|
|
670
|
+
lifecycleNotifier,
|
|
671
|
+
jobMemoryRuntime: { store: memoryStore, toolService: memoryToolService },
|
|
672
|
+
logger,
|
|
673
|
+
promptsConfig: config.prompts,
|
|
674
|
+
managementRoot: managementDir,
|
|
675
|
+
...(capabilitiesByProvider ? { capabilitiesByProvider } : {}),
|
|
676
|
+
...(providerBrokerToolAllowLists ? { providerBrokerToolAllowLists } : {}),
|
|
677
|
+
...(brokerToolScope ? { brokerToolScope } : {}),
|
|
678
|
+
mergePrepService,
|
|
679
|
+
githubGateway: gitHubGateway,
|
|
680
|
+
backburnerGitToolService,
|
|
681
|
+
commandRunner,
|
|
682
|
+
gitGateway,
|
|
683
|
+
workspaceRecoveryService,
|
|
684
|
+
agentPool
|
|
685
|
+
});
|
|
686
|
+
const triageFollowUp = await triageFollowUpService.materializeStartImplementation({
|
|
687
|
+
repos: enabledRepos,
|
|
688
|
+
issues: scanResult.issues,
|
|
689
|
+
repoPaths: new Map(scanResult.repoResults
|
|
690
|
+
.filter((result) => result.status === "synced")
|
|
691
|
+
.map((result) => [result.repoId, result.localPath])),
|
|
692
|
+
codeRoot,
|
|
693
|
+
state: {
|
|
694
|
+
...previousState,
|
|
695
|
+
prs: scanResult.prs,
|
|
696
|
+
executions: initialDispatchResult.executions,
|
|
697
|
+
worktrees: linkedWorktrees
|
|
698
|
+
},
|
|
699
|
+
tasks: initialDispatchResult.tasks
|
|
700
|
+
});
|
|
701
|
+
const triageAwareWorktrees = implementationWorkflowService.reconcileTaskState(triageFollowUp.worktrees, triageFollowUp.tasks);
|
|
702
|
+
const projectionAwareTriageWorktrees = implementationWorkflowService.reconcilePacketWorktreeReadinessFromProjections(triageAwareWorktrees, packetPullRequestProjections, enabledRepos, triageFollowUp.tasks, initialDispatchResult.workstreams, scanResult.prs);
|
|
703
|
+
await stateStore.writeWorktreeState(projectionAwareTriageWorktrees);
|
|
704
|
+
await stateStore.writeTaskState(triageFollowUp.tasks);
|
|
705
|
+
const retrospectiveDerivation = deriveRetrospectiveTasks({
|
|
706
|
+
existingTasks: triageFollowUp.tasks,
|
|
707
|
+
candidates: candidateResult.candidates,
|
|
708
|
+
problems: previousState.retrospectiveProblems,
|
|
709
|
+
opportunities: previousState.retrospectiveOpportunities,
|
|
710
|
+
proposals: previousState.learningProposals,
|
|
711
|
+
agents: config.agents,
|
|
712
|
+
contextBuilder: new DefaultContextBuilder(),
|
|
713
|
+
now: generatedAt
|
|
714
|
+
});
|
|
715
|
+
let finalTasksToDispatch = triageFollowUp.tasks;
|
|
716
|
+
if (retrospectiveDerivation.tasks.length > 0) {
|
|
717
|
+
finalTasksToDispatch = [...triageFollowUp.tasks, ...retrospectiveDerivation.tasks];
|
|
718
|
+
}
|
|
719
|
+
const finalSkipContextHydration = rehydrateTaskMcpSkipContexts({
|
|
720
|
+
tasks: finalTasksToDispatch,
|
|
721
|
+
workstreams: initialDispatchResult.workstreams
|
|
722
|
+
});
|
|
723
|
+
if (finalSkipContextHydration.changed) {
|
|
724
|
+
finalTasksToDispatch = finalSkipContextHydration.tasks;
|
|
725
|
+
}
|
|
726
|
+
if (retrospectiveDerivation.tasks.length > 0 || finalSkipContextHydration.changed) {
|
|
727
|
+
await stateStore.writeTaskState(finalTasksToDispatch);
|
|
728
|
+
}
|
|
729
|
+
dispatchResult = await runDispatchWithMergePrepLifecycle({
|
|
730
|
+
tasks: finalTasksToDispatch,
|
|
731
|
+
worktrees: projectionAwareTriageWorktrees,
|
|
732
|
+
workstreams: initialDispatchResult.workstreams,
|
|
733
|
+
capabilityValidation,
|
|
734
|
+
mcpStatuses: currentMcpStatuses,
|
|
735
|
+
declaredMcps: config.mcps,
|
|
736
|
+
capabilitiesConfig: config.capabilities,
|
|
737
|
+
agents: config.agents,
|
|
738
|
+
enabledRepos,
|
|
739
|
+
codeRoot,
|
|
740
|
+
executions: initialDispatchResult.executions,
|
|
741
|
+
stateStore,
|
|
742
|
+
logDir,
|
|
743
|
+
agentRunner: dispatchAgentRunner,
|
|
744
|
+
failureNotifier,
|
|
745
|
+
reviewPublisher,
|
|
746
|
+
planBreakdownPublisher,
|
|
747
|
+
waveAssessmentPublisher,
|
|
748
|
+
lifecycleNotifier,
|
|
749
|
+
jobMemoryRuntime: { store: memoryStore, toolService: memoryToolService },
|
|
750
|
+
logger,
|
|
751
|
+
promptsConfig: config.prompts,
|
|
752
|
+
managementRoot: managementDir,
|
|
753
|
+
...(capabilitiesByProvider ? { capabilitiesByProvider } : {}),
|
|
754
|
+
...(providerBrokerToolAllowLists ? { providerBrokerToolAllowLists } : {}),
|
|
755
|
+
...(brokerToolScope ? { brokerToolScope } : {}),
|
|
756
|
+
mergePrepService,
|
|
757
|
+
githubGateway: gitHubGateway,
|
|
758
|
+
backburnerGitToolService,
|
|
759
|
+
commandRunner,
|
|
760
|
+
gitGateway,
|
|
761
|
+
workspaceRecoveryService,
|
|
762
|
+
agentPool
|
|
763
|
+
});
|
|
764
|
+
// Journal new executions (dedup via executionIdsBeforeCycle)
|
|
765
|
+
await journalNewExecutions({
|
|
766
|
+
cycleId,
|
|
767
|
+
executionIdsBeforeCycle,
|
|
768
|
+
dispatchResult,
|
|
769
|
+
journal,
|
|
770
|
+
entityMaps,
|
|
771
|
+
workstreams: dispatchResult.workstreams
|
|
772
|
+
});
|
|
773
|
+
// Journal task status transitions after dispatch
|
|
774
|
+
await journalTaskTransitions({
|
|
775
|
+
cycleId,
|
|
776
|
+
tasksBefore: tasks,
|
|
777
|
+
tasksAfter: dispatchResult.tasks,
|
|
778
|
+
journal,
|
|
779
|
+
entityMaps,
|
|
780
|
+
workstreams: dispatchResult.workstreams
|
|
781
|
+
});
|
|
782
|
+
const ingestionResult = await ingestRetrospectiveExecutions({
|
|
783
|
+
tasks: [...dispatchResult.tasks, ...previousState.tasks],
|
|
784
|
+
executions: dispatchResult.executions,
|
|
785
|
+
candidates: retrospectiveDerivation.candidates,
|
|
786
|
+
problems: retrospectiveDerivation.problems,
|
|
787
|
+
opportunities: retrospectiveDerivation.opportunities,
|
|
788
|
+
proposals: previousState.learningProposals,
|
|
789
|
+
readArtifact: (p) => readFile(p, "utf8"),
|
|
790
|
+
now: generatedAt
|
|
791
|
+
});
|
|
792
|
+
const scopeValidationConfig = buildScopeValidationConfigFromRuntime({
|
|
793
|
+
repos: config.repos,
|
|
794
|
+
agents: config.agents
|
|
795
|
+
});
|
|
796
|
+
const scoredProblems = scoreRetrospectiveProblems(ingestionResult.problems, scopeValidationConfig, generatedAt);
|
|
797
|
+
const validationResult = await validateLearningProposals({
|
|
798
|
+
proposals: ingestionResult.proposals,
|
|
799
|
+
opportunities: ingestionResult.opportunities,
|
|
800
|
+
problems: scoredProblems,
|
|
801
|
+
scopeValidationConfig,
|
|
802
|
+
managementRoot: managementDir,
|
|
803
|
+
readArtifact: async (p) => {
|
|
804
|
+
try {
|
|
805
|
+
return await readFile(p, "utf8");
|
|
806
|
+
}
|
|
807
|
+
catch {
|
|
808
|
+
return undefined;
|
|
809
|
+
}
|
|
810
|
+
},
|
|
811
|
+
now: generatedAt
|
|
812
|
+
});
|
|
813
|
+
const materializationResult = await materializeManagementLearningProposals({
|
|
814
|
+
proposals: validationResult.proposals,
|
|
815
|
+
managementPrs: previousState.managementPrs,
|
|
816
|
+
candidates: ingestionResult.candidates,
|
|
817
|
+
problems: scoredProblems,
|
|
818
|
+
opportunities: ingestionResult.opportunities,
|
|
819
|
+
managementRoot: managementDir,
|
|
820
|
+
gitGateway,
|
|
821
|
+
githubGateway: gitHubGateway,
|
|
822
|
+
commandRunner,
|
|
823
|
+
now: generatedAt
|
|
824
|
+
});
|
|
825
|
+
await stateStore.writeRetrospectiveCandidateState(ingestionResult.candidates);
|
|
826
|
+
await stateStore.writeRetrospectiveProblemState(scoredProblems);
|
|
827
|
+
await stateStore.writeRetrospectiveOpportunityState(ingestionResult.opportunities);
|
|
828
|
+
await stateStore.writeLearningProposalState(materializationResult.proposals);
|
|
829
|
+
await stateStore.writeManagementPrState(materializationResult.managementPrs);
|
|
830
|
+
const reconciledWorktrees = implementationWorkflowService.reconcileTaskState(projectionAwareTriageWorktrees, dispatchResult.tasks);
|
|
831
|
+
const projectionAwareReconciledWorktrees = implementationWorkflowService.reconcilePacketWorktreeReadinessFromProjections(reconciledWorktrees, packetPullRequestProjections, enabledRepos, dispatchResult.tasks, dispatchResult.workstreams, scanResult.prs);
|
|
832
|
+
const finalWorktrees = await implementationWorkflowService.finalizePushableHandoffs(projectionAwareReconciledWorktrees, dispatchResult.tasks, logger);
|
|
833
|
+
await stateStore.writeWorktreeState(finalWorktrees);
|
|
834
|
+
const finalWorkstreams = deriveWorkstreams({
|
|
835
|
+
repos: enabledRepos,
|
|
836
|
+
currentIssues: scanResult.issues,
|
|
837
|
+
currentPullRequests: scanResult.prs,
|
|
838
|
+
previousPullRequests: previousState.prs,
|
|
839
|
+
worktrees: finalWorktrees,
|
|
840
|
+
previousWorkstreams: dispatchResult.workstreams
|
|
841
|
+
});
|
|
842
|
+
const finalAttentionObservedAt = new Date().toISOString();
|
|
843
|
+
const finalAttention = deriveAttentionRecords({
|
|
844
|
+
repos: enabledRepos,
|
|
845
|
+
workstreams: finalWorkstreams,
|
|
846
|
+
previousState,
|
|
847
|
+
currentIssues: scanResult.issues,
|
|
848
|
+
currentPullRequests: scanResult.prs,
|
|
849
|
+
currentComments: scanResult.comments,
|
|
850
|
+
tasks: dispatchResult.tasks,
|
|
851
|
+
now: finalAttentionObservedAt
|
|
852
|
+
});
|
|
853
|
+
const finalPhaseAwareWorkstreams = deriveWorkstreams({
|
|
854
|
+
repos: enabledRepos,
|
|
855
|
+
currentIssues: scanResult.issues,
|
|
856
|
+
currentPullRequests: scanResult.prs,
|
|
857
|
+
previousPullRequests: previousState.prs,
|
|
858
|
+
worktrees: finalWorktrees,
|
|
859
|
+
previousWorkstreams: finalWorkstreams,
|
|
860
|
+
attention: finalAttention
|
|
861
|
+
});
|
|
862
|
+
const breakdownEnrichedWorkstreams = applyPlanBreakdownResultsToWorkstreams({
|
|
863
|
+
workstreams: finalPhaseAwareWorkstreams,
|
|
864
|
+
tasks: dispatchResult.tasks,
|
|
865
|
+
executions: dispatchResult.executions
|
|
866
|
+
});
|
|
867
|
+
const enrichedWorkstreams = applyWaveAssessmentResultsToWorkstreams({
|
|
868
|
+
workstreams: breakdownEnrichedWorkstreams,
|
|
869
|
+
assessments: dispatchResult.executions
|
|
870
|
+
.map((execution) => execution.result?.waveAssessment)
|
|
871
|
+
.filter((assessment) => assessment !== undefined)
|
|
872
|
+
});
|
|
873
|
+
// Packet planning artifact creation
|
|
874
|
+
const repoPathMap = new Map(scanResult.repoResults
|
|
875
|
+
.filter((result) => result.status === "synced")
|
|
876
|
+
.map((result) => [result.repoId, result.localPath]));
|
|
877
|
+
let currentWorktrees = finalWorktrees;
|
|
878
|
+
let currentWorkstreams = enrichedWorkstreams;
|
|
879
|
+
// Preserve new packet plans before provisioning external packet artifacts.
|
|
880
|
+
await stateStore.writeWorkstreamState(currentWorkstreams);
|
|
881
|
+
for (const workstream of currentWorkstreams) {
|
|
882
|
+
if (!workstream.packetPlan) {
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
const repo = enabledRepos.find((r) => r.id === workstream.repoKey || `${r.owner}/${r.name}` === workstream.repoKey);
|
|
886
|
+
const repoPath = repo ? repoPathMap.get(repo.id) : undefined;
|
|
887
|
+
if (!repo || !repoPath) {
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
const packetWorktreeResults = await implementationWorkflowService.ensurePacketPlanningWorktreesForCurrentWave({
|
|
891
|
+
repo,
|
|
892
|
+
repoPath,
|
|
893
|
+
codeRoot,
|
|
894
|
+
parentWorkstream: workstream,
|
|
895
|
+
existingWorktrees: currentWorktrees
|
|
896
|
+
});
|
|
897
|
+
if (packetWorktreeResults.length > 0) {
|
|
898
|
+
for (const result of packetWorktreeResults) {
|
|
899
|
+
const existingIndex = currentWorktrees.findIndex((wt) => wt.id === result.worktree.id);
|
|
900
|
+
if (existingIndex !== -1) {
|
|
901
|
+
currentWorktrees = [
|
|
902
|
+
...currentWorktrees.slice(0, existingIndex),
|
|
903
|
+
result.worktree,
|
|
904
|
+
...currentWorktrees.slice(existingIndex + 1)
|
|
905
|
+
];
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
currentWorktrees = [...currentWorktrees, result.worktree];
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
// A PR create failure must leave a persisted worktree that can be retried.
|
|
912
|
+
await stateStore.writeWorktreeState(currentWorktrees);
|
|
913
|
+
let runningWorkstream = workstream;
|
|
914
|
+
for (const result of packetWorktreeResults) {
|
|
915
|
+
const { worktree: updatedWorktree, pullRequestCreated } = await implementationWorkflowService.ensurePullRequestForPacketWorktree(repo, runningWorkstream, result.worktree);
|
|
916
|
+
if (pullRequestCreated && updatedWorktree.packet && updatedWorktree.prNumber) {
|
|
917
|
+
packetPrsCreatedThisRun.push({
|
|
918
|
+
workstreamId: runningWorkstream.id,
|
|
919
|
+
repoKey: runningWorkstream.repoKey,
|
|
920
|
+
parentPrNumber: runningWorkstream.integration?.prNumber,
|
|
921
|
+
packetId: updatedWorktree.packet.packetId,
|
|
922
|
+
waveIndex: updatedWorktree.packet.waveIndex,
|
|
923
|
+
branchName: updatedWorktree.branchName,
|
|
924
|
+
prNumber: updatedWorktree.prNumber
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
// Update WorktreeRecord list
|
|
928
|
+
const existingIndex = currentWorktrees.findIndex((wt) => wt.id === updatedWorktree.id);
|
|
929
|
+
if (existingIndex !== -1) {
|
|
930
|
+
currentWorktrees = [
|
|
931
|
+
...currentWorktrees.slice(0, existingIndex),
|
|
932
|
+
updatedWorktree,
|
|
933
|
+
...currentWorktrees.slice(existingIndex + 1)
|
|
934
|
+
];
|
|
935
|
+
}
|
|
936
|
+
else {
|
|
937
|
+
currentWorktrees = [...currentWorktrees, updatedWorktree];
|
|
938
|
+
}
|
|
939
|
+
// Update WorkstreamRecord state
|
|
940
|
+
const packetId = updatedWorktree.packet?.packetId;
|
|
941
|
+
if (packetId && updatedWorktree.prNumber) {
|
|
942
|
+
const updatedPacketPlan = markPacketPlanningPrOpen({
|
|
943
|
+
packetPlan: runningWorkstream.packetPlan,
|
|
944
|
+
packetId,
|
|
945
|
+
branchName: updatedWorktree.branchName,
|
|
946
|
+
prNumber: updatedWorktree.prNumber,
|
|
947
|
+
updatedAt: new Date().toISOString()
|
|
948
|
+
});
|
|
949
|
+
runningWorkstream = { ...runningWorkstream, packetPlan: updatedPacketPlan };
|
|
950
|
+
currentWorkstreams = currentWorkstreams.map((ws) => ws.id === runningWorkstream.id ? runningWorkstream : ws);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
await stateStore.writeWorktreeState(currentWorktrees);
|
|
956
|
+
const parentBranchSyncPreflights = await collectParentBranchSyncPreflights({
|
|
957
|
+
workstreams: currentWorkstreams,
|
|
958
|
+
repos: enabledRepos,
|
|
959
|
+
pullRequests: scanResult.prs,
|
|
960
|
+
repoResults: scanResult.repoResults,
|
|
961
|
+
tasks: [...dispatchResult.tasks, ...previousState.tasks],
|
|
962
|
+
executions: dispatchResult.executions,
|
|
963
|
+
gitGateway
|
|
964
|
+
});
|
|
965
|
+
const parentBranchSyncedThisRun = syncActionResult.attempts.some((attempt) => attempt.attempted && attempt.status === "synced");
|
|
966
|
+
const packetWaveAdvancementEvaluation = buildPacketWaveAdvancementEvaluationForRun({
|
|
967
|
+
workstreams: currentWorkstreams,
|
|
968
|
+
parentBranchSyncPreflights: parentBranchSyncedThisRun ? [] : parentBranchSyncPreflights,
|
|
969
|
+
waveAssessments: currentWorkstreams.flatMap((workstream) => workstream.packetPlan?.waveAssessments ?? [])
|
|
970
|
+
});
|
|
971
|
+
packetWaveAdvancementsThisRun = packetWaveAdvancementEvaluation.result.advanced.map((adv) => {
|
|
972
|
+
const workstream = currentWorkstreams.find((ws) => ws.id === adv.workstreamId);
|
|
973
|
+
return {
|
|
974
|
+
workstreamId: adv.workstreamId,
|
|
975
|
+
repoKey: workstream?.repoKey ?? "unknown",
|
|
976
|
+
parentPrNumber: workstream?.integration?.prNumber,
|
|
977
|
+
fromWaveIndex: adv.fromWaveIndex,
|
|
978
|
+
toWaveIndex: adv.toWaveIndex,
|
|
979
|
+
completedPacketIds: adv.result.packetPlanResult.preview.completedCurrentWavePacketIds,
|
|
980
|
+
newlyCurrentPacketIds: adv.nextWavePacketIds
|
|
981
|
+
};
|
|
982
|
+
});
|
|
983
|
+
packetWaveBlockedThisRun = packetWaveAdvancementEvaluation.result.blocked.map((blocked) => {
|
|
984
|
+
const workstream = currentWorkstreams.find((ws) => ws.id === blocked.workstreamId);
|
|
985
|
+
return {
|
|
986
|
+
workstreamId: blocked.workstreamId,
|
|
987
|
+
repoKey: workstream?.repoKey ?? "unknown",
|
|
988
|
+
parentPrNumber: workstream?.integration?.prNumber,
|
|
989
|
+
reason: blocked.reason,
|
|
990
|
+
currentWaveIndex: blocked.result.packetPlanResult.preview.currentWaveIndex,
|
|
991
|
+
incompletePacketIds: blocked.result.packetPlanResult.preview.incompleteCurrentWavePacketIds
|
|
992
|
+
};
|
|
993
|
+
});
|
|
994
|
+
if (packetWaveAdvancementEvaluation.result.advanced.length > 0) {
|
|
995
|
+
currentWorkstreams = currentWorkstreams.map((workstream) => packetWaveAdvancementEvaluation.result.workstreams[workstream.id] ?? workstream);
|
|
996
|
+
}
|
|
997
|
+
const persistedAttentionObservedAt = new Date().toISOString();
|
|
998
|
+
const persistedAttention = deriveAttentionRecords({
|
|
999
|
+
repos: enabledRepos,
|
|
1000
|
+
workstreams: currentWorkstreams,
|
|
1001
|
+
previousState,
|
|
1002
|
+
currentIssues: scanResult.issues,
|
|
1003
|
+
currentPullRequests: scanResult.prs,
|
|
1004
|
+
currentComments: scanResult.comments,
|
|
1005
|
+
tasks: dispatchResult.tasks,
|
|
1006
|
+
now: persistedAttentionObservedAt
|
|
1007
|
+
});
|
|
1008
|
+
await stateStore.writeWorkstreamState(currentWorkstreams);
|
|
1009
|
+
await stateStore.writeAttentionState(persistedAttention);
|
|
1010
|
+
const lifecyclePublisher = new GitHubPacketLifecyclePublisher(gitHubGateway, logger);
|
|
1011
|
+
await lifecyclePublisher.publishLifecycleFeedback({
|
|
1012
|
+
workstreams: currentWorkstreams,
|
|
1013
|
+
repos: enabledRepos,
|
|
1014
|
+
packetPrsCreatedThisRun,
|
|
1015
|
+
waveAdvancementsThisRun: packetWaveAdvancementsThisRun,
|
|
1016
|
+
blockedThisRun: packetWaveBlockedThisRun,
|
|
1017
|
+
parentBranchSyncPreflights
|
|
1018
|
+
});
|
|
1019
|
+
summaryWorkstreams = currentWorkstreams;
|
|
1020
|
+
// Journal worktree and packet wave transitions
|
|
1021
|
+
await journalWorktreeTransitions({
|
|
1022
|
+
cycleId,
|
|
1023
|
+
previousWorktrees: previousState.worktrees,
|
|
1024
|
+
currentWorktrees: currentWorktrees,
|
|
1025
|
+
journal
|
|
1026
|
+
});
|
|
1027
|
+
await journalPacketWaveEvents({
|
|
1028
|
+
cycleId,
|
|
1029
|
+
packetWaveAdvancementsThisRun,
|
|
1030
|
+
packetWaveBlockedThisRun,
|
|
1031
|
+
journal
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
finally {
|
|
1035
|
+
if (brokerServer) {
|
|
1036
|
+
await brokerServer.close();
|
|
1037
|
+
}
|
|
1038
|
+
if (brokerLogger) {
|
|
1039
|
+
await brokerLogger.close();
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
const outputsGitOps = await gitOpsLifecycle.finalizeOutputsRoot(outputsDir, startedAt, outputsGitOpsPrepared);
|
|
1044
|
+
const completedAt = new Date().toISOString();
|
|
1045
|
+
logger.info(`Run complete: ${dispatchResult.tasks.length} tasks tracked, ${dispatchResult.tasks.filter((task) => task.status === "failed").length} failed`);
|
|
1046
|
+
const summary = {
|
|
1047
|
+
command: "run",
|
|
1048
|
+
codeRoot,
|
|
1049
|
+
managementDir,
|
|
1050
|
+
outputsDir,
|
|
1051
|
+
stateDir,
|
|
1052
|
+
logDir,
|
|
1053
|
+
startedAt,
|
|
1054
|
+
completedAt,
|
|
1055
|
+
managementGitOps,
|
|
1056
|
+
outputsGitOps,
|
|
1057
|
+
repoCount: enabledRepos.length,
|
|
1058
|
+
syncedRepoCount: scanResult.repoResults.filter((result) => result.status === "synced").length,
|
|
1059
|
+
failedRepoCount: scanResult.repoResults.filter((result) => result.status === "failed").length,
|
|
1060
|
+
scannedRepoCount: scanResult.repoResults.filter((result) => result.githubScan?.status === "scanned").length,
|
|
1061
|
+
partiallyScannedRepoCount: scanResult.repoResults.filter((result) => result.githubScan?.status === "partial").length,
|
|
1062
|
+
failedScanRepoCount: scanResult.repoResults.filter((result) => result.githubScan?.status === "failed").length,
|
|
1063
|
+
issueCount: scanResult.issues.length,
|
|
1064
|
+
pullRequestCount: scanResult.prs.length,
|
|
1065
|
+
commentCount: scanResult.comments.length,
|
|
1066
|
+
taskCount: dispatchResult.tasks.length,
|
|
1067
|
+
pendingTaskCount: dispatchResult.tasks.filter((task) => task.status === "pending").length,
|
|
1068
|
+
dispatchedTaskCount: dispatchResult.tasks.filter((task) => task.status === "dispatched").length,
|
|
1069
|
+
completedTaskCount: dispatchResult.tasks.filter((task) => task.status === "completed").length,
|
|
1070
|
+
failedTaskCount: dispatchResult.tasks.filter((task) => task.status === "failed").length,
|
|
1071
|
+
executionCount: dispatchResult.executions.length,
|
|
1072
|
+
productDiscoveryCount: summaryWorkstreams.filter((workstream) => workstream.phase === "product_discovery").length,
|
|
1073
|
+
productSpecificationCount: summaryWorkstreams.filter((workstream) => workstream.phase === "product_specification").length,
|
|
1074
|
+
waitingForProductApprovalCount: summaryWorkstreams.filter((workstream) => workstream.product?.status === "in_discovery").length,
|
|
1075
|
+
pullRequestBaseClassifications: scanResult.prs
|
|
1076
|
+
.map((pr) => classifyPullRequestBase({
|
|
1077
|
+
pullRequestNumber: pr.number,
|
|
1078
|
+
repoSlug: pr.repoSlug,
|
|
1079
|
+
baseBranch: pr.baseBranch,
|
|
1080
|
+
defaultBranch: pr.defaultBranch
|
|
1081
|
+
}))
|
|
1082
|
+
.sort((left, right) => {
|
|
1083
|
+
const slugCompare = left.repoSlug.localeCompare(right.repoSlug);
|
|
1084
|
+
if (slugCompare !== 0) {
|
|
1085
|
+
return slugCompare;
|
|
1086
|
+
}
|
|
1087
|
+
return left.pullRequestNumber - right.pullRequestNumber;
|
|
1088
|
+
}),
|
|
1089
|
+
packetPullRequestCandidates: collectPacketPullRequestCandidates({
|
|
1090
|
+
pullRequests: scanResult.prs,
|
|
1091
|
+
workstreams: summaryWorkstreams
|
|
1092
|
+
}),
|
|
1093
|
+
parentBranchSyncPreflights: await collectParentBranchSyncPreflights({
|
|
1094
|
+
workstreams: summaryWorkstreams,
|
|
1095
|
+
repos: enabledRepos,
|
|
1096
|
+
pullRequests: scanResult.prs,
|
|
1097
|
+
repoResults: scanResult.repoResults,
|
|
1098
|
+
tasks: [...dispatchResult.tasks, ...previousState.tasks],
|
|
1099
|
+
executions: dispatchResult.executions,
|
|
1100
|
+
gitGateway
|
|
1101
|
+
}),
|
|
1102
|
+
packetPrsCreatedThisRun,
|
|
1103
|
+
packetWaveAdvancementsThisRun,
|
|
1104
|
+
packetWaveBlockedThisRun,
|
|
1105
|
+
packetPlans: summaryWorkstreams
|
|
1106
|
+
.filter((ws) => ws.packetPlan)
|
|
1107
|
+
.map((ws) => ({
|
|
1108
|
+
workstreamId: ws.id,
|
|
1109
|
+
repoKey: ws.repoKey,
|
|
1110
|
+
parentPrNumber: ws.integration?.prNumber,
|
|
1111
|
+
status: ws.packetPlan.status,
|
|
1112
|
+
packetCount: Object.keys(ws.packetPlan.packets).length,
|
|
1113
|
+
waveCount: ws.packetPlan.waves.length,
|
|
1114
|
+
currentWaveIndex: ws.packetPlan.currentWaveIndex,
|
|
1115
|
+
waves: ws.packetPlan.waves.map((packetIds, index) => ({
|
|
1116
|
+
index,
|
|
1117
|
+
packetIds,
|
|
1118
|
+
packets: packetIds.map((id) => {
|
|
1119
|
+
const p = ws.packetPlan.packets[id];
|
|
1120
|
+
return {
|
|
1121
|
+
id,
|
|
1122
|
+
status: p?.status ?? "planned",
|
|
1123
|
+
branchName: p?.branchName,
|
|
1124
|
+
prNumber: p?.prNumber
|
|
1125
|
+
};
|
|
1126
|
+
})
|
|
1127
|
+
}))
|
|
1128
|
+
}))
|
|
1129
|
+
.sort((a, b) => a.workstreamId.localeCompare(b.workstreamId)),
|
|
1130
|
+
results: scanResult.repoResults
|
|
1131
|
+
.slice()
|
|
1132
|
+
.sort((left, right) => left.repoSlug.localeCompare(right.repoSlug)),
|
|
1133
|
+
tasks: dispatchResult.tasks
|
|
1134
|
+
};
|
|
1135
|
+
return summary;
|
|
1136
|
+
}
|
|
1137
|
+
async function collectParentBranchSyncPreflights(input) {
|
|
1138
|
+
const repoByKey = new Map();
|
|
1139
|
+
for (const repo of input.repos) {
|
|
1140
|
+
repoByKey.set(repo.id, repo);
|
|
1141
|
+
repoByKey.set(`${repo.owner}/${repo.name}`, repo);
|
|
1142
|
+
}
|
|
1143
|
+
const repoResultById = new Map(input.repoResults.map((result) => [result.repoId, result]));
|
|
1144
|
+
const repoResultBySlug = new Map(input.repoResults.map((result) => [result.repoSlug, result]));
|
|
1145
|
+
const pullRequestByRepoAndNumber = new Map();
|
|
1146
|
+
for (const pullRequest of input.pullRequests) {
|
|
1147
|
+
pullRequestByRepoAndNumber.set(`${pullRequest.repoId}:${pullRequest.number}`, pullRequest);
|
|
1148
|
+
pullRequestByRepoAndNumber.set(`${pullRequest.repoKey}:${pullRequest.number}`, pullRequest);
|
|
1149
|
+
pullRequestByRepoAndNumber.set(`${pullRequest.repoSlug}:${pullRequest.number}`, pullRequest);
|
|
1150
|
+
}
|
|
1151
|
+
const basePreflights = input.workstreams
|
|
1152
|
+
.map((workstream) => {
|
|
1153
|
+
const repo = repoByKey.get(workstream.repoKey);
|
|
1154
|
+
const repoResult = repo
|
|
1155
|
+
? repoResultById.get(repo.id) ?? repoResultBySlug.get(`${repo.owner}/${repo.name}`)
|
|
1156
|
+
: repoResultBySlug.get(workstream.repoKey);
|
|
1157
|
+
const defaultBranch = repo?.defaultBranch ?? repoResult?.defaultBranch ?? undefined;
|
|
1158
|
+
const preflight = buildParentBranchSyncPreflight({
|
|
1159
|
+
workstream,
|
|
1160
|
+
defaultBranch: defaultBranch ?? undefined
|
|
1161
|
+
});
|
|
1162
|
+
if (preflight?.parentPrNumber !== undefined) {
|
|
1163
|
+
const parentPr = pullRequestByRepoAndNumber.get(`${workstream.repoKey}:${preflight.parentPrNumber}`) ??
|
|
1164
|
+
(repo
|
|
1165
|
+
? pullRequestByRepoAndNumber.get(`${repo.id}:${preflight.parentPrNumber}`) ??
|
|
1166
|
+
pullRequestByRepoAndNumber.get(`${repo.owner}/${repo.name}:${preflight.parentPrNumber}`)
|
|
1167
|
+
: undefined);
|
|
1168
|
+
if (parentPr?.headSha) {
|
|
1169
|
+
preflight.parentHeadSha = parentPr.headSha;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
if (preflight) {
|
|
1173
|
+
const originalPlanningSessionId = resolveWaveAssessmentOriginalPlanningSessionId(workstream, preflight, input.tasks, input.executions);
|
|
1174
|
+
if (originalPlanningSessionId) {
|
|
1175
|
+
preflight.originalPlanningSessionId = originalPlanningSessionId;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return { preflight, repoResult };
|
|
1179
|
+
})
|
|
1180
|
+
.filter((item) => item.preflight !== undefined);
|
|
1181
|
+
const enriched = [];
|
|
1182
|
+
for (const { preflight, repoResult } of basePreflights) {
|
|
1183
|
+
if (!repoResult?.localPath) {
|
|
1184
|
+
enriched.push({ preflight, branchSyncComparisonSkippedReason: "missing_local_path" });
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
try {
|
|
1188
|
+
const branchSyncComparison = await input.gitGateway.compareBranchSyncStatus(repoResult.localPath, preflight.parentIntegrationBranch, preflight.defaultBranch);
|
|
1189
|
+
enriched.push({ preflight, branchSyncComparison });
|
|
1190
|
+
}
|
|
1191
|
+
catch {
|
|
1192
|
+
enriched.push({
|
|
1193
|
+
preflight,
|
|
1194
|
+
branchSyncComparison: {
|
|
1195
|
+
parentBranch: preflight.parentIntegrationBranch,
|
|
1196
|
+
defaultBranch: preflight.defaultBranch,
|
|
1197
|
+
parentRef: `refs/remotes/origin/${preflight.parentIntegrationBranch}`,
|
|
1198
|
+
defaultRef: `refs/remotes/origin/${preflight.defaultBranch}`,
|
|
1199
|
+
status: "unknown",
|
|
1200
|
+
message: "Could not compare branch sync status."
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
return enriched.sort((left, right) => {
|
|
1206
|
+
const repoCompare = left.preflight.repoKey.localeCompare(right.preflight.repoKey);
|
|
1207
|
+
if (repoCompare !== 0)
|
|
1208
|
+
return repoCompare;
|
|
1209
|
+
return left.preflight.workstreamId.localeCompare(right.preflight.workstreamId);
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
function resolveWaveAssessmentOriginalPlanningSessionId(workstream, preflight, tasks, executions) {
|
|
1213
|
+
if (!preflight) {
|
|
1214
|
+
return undefined;
|
|
1215
|
+
}
|
|
1216
|
+
const taskById = new Map(tasks.map((task) => [task.id, task]));
|
|
1217
|
+
const latestPlanBreakdownExecution = executions
|
|
1218
|
+
.filter((execution) => execution.taskKind === "plan_breakdown" && execution.taskStatus === "completed")
|
|
1219
|
+
.filter((execution) => taskById.get(execution.taskId)?.workstreamId === workstream.id)
|
|
1220
|
+
.sort((left, right) => right.endedAt.localeCompare(left.endedAt))[0];
|
|
1221
|
+
if (latestPlanBreakdownExecution?.result?.planBreakdownSessionId) {
|
|
1222
|
+
return latestPlanBreakdownExecution.result.planBreakdownSessionId;
|
|
1223
|
+
}
|
|
1224
|
+
const matchingAssessment = (workstream.packetPlan?.waveAssessments ?? []).find((assessment) => assessment.checkpoint.identity.workstreamId === preflight.workstreamId &&
|
|
1225
|
+
assessment.checkpoint.identity.currentWaveIndex === preflight.currentWaveIndex &&
|
|
1226
|
+
assessment.checkpoint.identity.nextWaveIndex === preflight.nextWaveIndex &&
|
|
1227
|
+
assessment.checkpoint.identity.parentPrNumber === (preflight.parentPrNumber ?? 0) &&
|
|
1228
|
+
assessment.checkpoint.identity.parentIntegrationBranch === preflight.parentIntegrationBranch &&
|
|
1229
|
+
assessment.checkpoint.identity.defaultBranch === preflight.defaultBranch &&
|
|
1230
|
+
assessment.checkpoint.identity.parentHeadSha === (preflight.parentHeadSha ?? "unknown") &&
|
|
1231
|
+
assessment.checkpoint.identity.planRevision === (preflight.planRevision ?? "unknown"));
|
|
1232
|
+
return matchingAssessment?.checkpoint.identity.originalPlanningSessionId;
|
|
1233
|
+
}
|
|
1234
|
+
function buildMetadata(generatedAt, entityCount, repoCount, failures) {
|
|
1235
|
+
return {
|
|
1236
|
+
generatedAt,
|
|
1237
|
+
repoCount,
|
|
1238
|
+
entityCount,
|
|
1239
|
+
partialFailures: failures
|
|
1240
|
+
};
|
|
1241
|
+
}
|