@backburner/cli 0.1.7 → 0.1.8
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/README.md +1 -1
- package/dist/src/cli/commands/run.js +53 -10
- package/dist/src/cli/run.js +1 -1
- package/dist/src/git/types.js +15 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -250,7 +250,7 @@ The normal orchestrator starts its own broker on loopback. The standalone broker
|
|
|
250
250
|
|
|
251
251
|
## What a run does
|
|
252
252
|
|
|
253
|
-
Each cycle loads configuration and state,
|
|
253
|
+
Each cycle loads configuration and state, fetches and synchronizes enabled repositories, scans GitHub through `gh`, derives eligible tasks, starts a local GitHub broker for provider access, dispatches work to provider CLIs, and writes state, journal events, and execution logs. If a managed checkout has local changes, Backburner does not modify it; after a successful fetch, packet worktrees may still be created from the exact remote integration-branch ref.
|
|
254
254
|
|
|
255
255
|
Backburner uses isolated git worktrees as implementation units. Generated control-plane state is readable JSON under `<outputs-dir>/state`; execution logs and artifacts are under `<outputs-dir>/logs`; journal events are under `<outputs-dir>/journal`.
|
|
256
256
|
|
|
@@ -47,6 +47,26 @@ export function buildPacketWaveAdvancementEvaluationForRun(input) {
|
|
|
47
47
|
waveAssessments: input.waveAssessments
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Returns local repositories that can safely provide packet worktrees.
|
|
52
|
+
*
|
|
53
|
+
* Packet worktrees start at an exact `origin/<integration-branch>` ref, so a
|
|
54
|
+
* successfully fetched repository remains usable only when its primary
|
|
55
|
+
* checkout was explicitly blocked for local changes. Other failed sync states
|
|
56
|
+
* remain excluded because their remote refs may be stale or unavailable.
|
|
57
|
+
*/
|
|
58
|
+
export function buildPacketProvisioningRepoPathMap(repoResults) {
|
|
59
|
+
return new Map(repoResults
|
|
60
|
+
.filter(isPacketProvisioningRepo)
|
|
61
|
+
.map((result) => [result.repoId, result.localPath]));
|
|
62
|
+
}
|
|
63
|
+
function isPacketProvisioningRepo(result) {
|
|
64
|
+
if (result.status === "synced") {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return (result.fetched &&
|
|
68
|
+
result.defaultBranchSyncStatus === "blocked_dirty");
|
|
69
|
+
}
|
|
50
70
|
export async function processProviderLimitResumption(input) {
|
|
51
71
|
const providerLimits = [...(input.previousState.providerLimits ?? [])];
|
|
52
72
|
const currentTime = Date.parse(input.now);
|
|
@@ -871,9 +891,7 @@ export async function runCli(argv, options = {}) {
|
|
|
871
891
|
.filter((assessment) => assessment !== undefined)
|
|
872
892
|
});
|
|
873
893
|
// Packet planning artifact creation
|
|
874
|
-
const repoPathMap =
|
|
875
|
-
.filter((result) => result.status === "synced")
|
|
876
|
-
.map((result) => [result.repoId, result.localPath]));
|
|
894
|
+
const repoPathMap = buildPacketProvisioningRepoPathMap(scanResult.repoResults);
|
|
877
895
|
let currentWorktrees = finalWorktrees;
|
|
878
896
|
let currentWorkstreams = enrichedWorkstreams;
|
|
879
897
|
// Preserve new packet plans before provisioning external packet artifacts.
|
|
@@ -887,13 +905,38 @@ export async function runCli(argv, options = {}) {
|
|
|
887
905
|
if (!repo || !repoPath) {
|
|
888
906
|
continue;
|
|
889
907
|
}
|
|
890
|
-
const
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
908
|
+
const parentIntegrationBranch = workstream.integration?.branchName;
|
|
909
|
+
if (!parentIntegrationBranch) {
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
try {
|
|
913
|
+
const comparison = await gitGateway.compareBranchSyncStatus(repoPath, parentIntegrationBranch, repo.defaultBranch ?? "main");
|
|
914
|
+
if (comparison.status === "missing_parent_branch" || comparison.status === "unknown") {
|
|
915
|
+
logger.warn(`Packet worktree start point origin/${parentIntegrationBranch} is unavailable for ${workstream.id}; will retry next cycle.`);
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
catch (error) {
|
|
920
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
921
|
+
logger.warn(`Could not verify packet worktree start point for ${workstream.id}; will retry next cycle: ${message}`);
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
let packetWorktreeResults;
|
|
925
|
+
try {
|
|
926
|
+
packetWorktreeResults =
|
|
927
|
+
await implementationWorkflowService.ensurePacketPlanningWorktreesForCurrentWave({
|
|
928
|
+
repo,
|
|
929
|
+
repoPath,
|
|
930
|
+
codeRoot,
|
|
931
|
+
parentWorkstream: workstream,
|
|
932
|
+
existingWorktrees: currentWorktrees
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
catch (error) {
|
|
936
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
937
|
+
logger.warn(`Failed to provision packet worktrees for ${workstream.id}; will retry next cycle: ${message}`);
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
897
940
|
if (packetWorktreeResults.length > 0) {
|
|
898
941
|
for (const result of packetWorktreeResults) {
|
|
899
942
|
const existingIndex = currentWorktrees.findIndex((wt) => wt.id === result.worktree.id);
|
package/dist/src/cli/run.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { realpathSync } from "node:fs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
export { runCli, buildPacketWaveAdvancementEvaluationForRun } from "./commands/run.js";
|
|
4
|
+
export { runCli, buildPacketProvisioningRepoPathMap, buildPacketWaveAdvancementEvaluationForRun } from "./commands/run.js";
|
|
5
5
|
export { buildEffectiveProviderBrokerToolAllowLists } from "./runtime/provider-tools.js";
|
|
6
6
|
export { runDispatchWithMergePrepLifecycle } from "./runtime/dispatch-lifecycle.js";
|
|
7
7
|
export { runLoopCli, SerializedCycleRunner } from "./commands/run-loop.js";
|
package/dist/src/git/types.js
CHANGED
|
@@ -74,18 +74,18 @@ export class GitCliGateway {
|
|
|
74
74
|
defaultRef
|
|
75
75
|
};
|
|
76
76
|
try {
|
|
77
|
-
if (!(await this.hasRef(localPath,
|
|
77
|
+
if (!(await this.hasRef(localPath, parentRef))) {
|
|
78
78
|
return {
|
|
79
79
|
...baseComparison,
|
|
80
|
-
status: "
|
|
81
|
-
message: "
|
|
80
|
+
status: "missing_parent_branch",
|
|
81
|
+
message: "Parent branch ref was not found locally."
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
|
-
if (!(await this.hasRef(localPath,
|
|
84
|
+
if (!(await this.hasRef(localPath, defaultRef))) {
|
|
85
85
|
return {
|
|
86
86
|
...baseComparison,
|
|
87
|
-
status: "
|
|
88
|
-
message: "
|
|
87
|
+
status: "missing_default_branch",
|
|
88
|
+
message: "Default branch ref was not found locally."
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
91
|
const result = await this.commandRunner.run("git", ["rev-list", "--left-right", "--count", `${defaultRef}...${parentRef}`], { cwd: localPath });
|
|
@@ -382,6 +382,8 @@ export class LocalRepoSyncService {
|
|
|
382
382
|
const existedBeforeSync = await pathExists(localPath);
|
|
383
383
|
let currentBranchBefore = null;
|
|
384
384
|
let currentBranchAfter = null;
|
|
385
|
+
let fetched = false;
|
|
386
|
+
let defaultBranchSyncStatus = "not_attempted";
|
|
385
387
|
let defaultBranch = repo.defaultBranch ?? null;
|
|
386
388
|
let defaultBranchSource = repo.defaultBranch ? "config" : "unknown";
|
|
387
389
|
try {
|
|
@@ -389,6 +391,7 @@ export class LocalRepoSyncService {
|
|
|
389
391
|
await this.gitGateway.cloneRepository(repo, localPath);
|
|
390
392
|
}
|
|
391
393
|
await this.gitGateway.fetchRepository(localPath);
|
|
394
|
+
fetched = true;
|
|
392
395
|
currentBranchBefore = await this.gitGateway.getCurrentBranch(localPath);
|
|
393
396
|
if (!defaultBranch) {
|
|
394
397
|
try {
|
|
@@ -402,10 +405,12 @@ export class LocalRepoSyncService {
|
|
|
402
405
|
}
|
|
403
406
|
const clean = await this.gitGateway.isWorkingTreeClean(localPath);
|
|
404
407
|
if (!clean) {
|
|
408
|
+
defaultBranchSyncStatus = "blocked_dirty";
|
|
405
409
|
throw new Error("working tree is dirty; refusing to switch or fast-forward the default branch");
|
|
406
410
|
}
|
|
407
411
|
await this.gitGateway.ensureDefaultBranchCheckedOut(localPath, defaultBranch);
|
|
408
412
|
await this.gitGateway.fastForwardDefaultBranch(localPath, defaultBranch);
|
|
413
|
+
defaultBranchSyncStatus = "synced";
|
|
409
414
|
currentBranchAfter = await this.gitGateway.getCurrentBranch(localPath);
|
|
410
415
|
return {
|
|
411
416
|
repoId: repo.id,
|
|
@@ -416,7 +421,8 @@ export class LocalRepoSyncService {
|
|
|
416
421
|
status: "synced",
|
|
417
422
|
existedBeforeSync,
|
|
418
423
|
cloned: !existedBeforeSync,
|
|
419
|
-
fetched
|
|
424
|
+
fetched,
|
|
425
|
+
defaultBranchSyncStatus,
|
|
420
426
|
defaultBranch,
|
|
421
427
|
defaultBranchSource,
|
|
422
428
|
currentBranchBefore,
|
|
@@ -438,7 +444,8 @@ export class LocalRepoSyncService {
|
|
|
438
444
|
status: "failed",
|
|
439
445
|
existedBeforeSync,
|
|
440
446
|
cloned: !existedBeforeSync && currentBranchAfter !== null,
|
|
441
|
-
fetched
|
|
447
|
+
fetched,
|
|
448
|
+
defaultBranchSyncStatus: defaultBranchSyncStatus === "blocked_dirty" ? "blocked_dirty" : "failed",
|
|
442
449
|
defaultBranch,
|
|
443
450
|
defaultBranchSource,
|
|
444
451
|
currentBranchBefore,
|