baro-ai 0.23.2 → 0.23.4
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/dist/cli.mjs +492 -4
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -7918,6 +7918,33 @@ var StorySpawnedItem = class extends ContextItem {
|
|
|
7918
7918
|
return { type: this.type, storyId: this.storyId };
|
|
7919
7919
|
}
|
|
7920
7920
|
};
|
|
7921
|
+
var FinalizeStartedItem = class extends ContextItem {
|
|
7922
|
+
constructor(branch) {
|
|
7923
|
+
super();
|
|
7924
|
+
this.branch = branch;
|
|
7925
|
+
}
|
|
7926
|
+
type = "finalize_started";
|
|
7927
|
+
toJSON() {
|
|
7928
|
+
return { type: this.type, branch: this.branch };
|
|
7929
|
+
}
|
|
7930
|
+
};
|
|
7931
|
+
var PrCreatedItem = class extends ContextItem {
|
|
7932
|
+
constructor(url, branch, baseBranch) {
|
|
7933
|
+
super();
|
|
7934
|
+
this.url = url;
|
|
7935
|
+
this.branch = branch;
|
|
7936
|
+
this.baseBranch = baseBranch;
|
|
7937
|
+
}
|
|
7938
|
+
type = "pr_created";
|
|
7939
|
+
toJSON() {
|
|
7940
|
+
return {
|
|
7941
|
+
type: this.type,
|
|
7942
|
+
url: this.url,
|
|
7943
|
+
branch: this.branch,
|
|
7944
|
+
baseBranch: this.baseBranch
|
|
7945
|
+
};
|
|
7946
|
+
}
|
|
7947
|
+
};
|
|
7921
7948
|
var RunCompletedItem = class extends ContextItem {
|
|
7922
7949
|
constructor(success, completedStories, failedStories, totalDurationSecs, totalAttempts, abortReason = null) {
|
|
7923
7950
|
super();
|
|
@@ -9305,6 +9332,449 @@ function extractVerdictJson(text) {
|
|
|
9305
9332
|
throw new Error(`unbalanced JSON object in critic response: ${trimmed.slice(0, 200)}`);
|
|
9306
9333
|
}
|
|
9307
9334
|
|
|
9335
|
+
// ../baro-orchestrator/src/participants/finalizer.ts
|
|
9336
|
+
import { execFile as execFile3 } from "child_process";
|
|
9337
|
+
import { promisify as promisify3 } from "util";
|
|
9338
|
+
var execFileAsync2 = promisify3(execFile3);
|
|
9339
|
+
var Finalizer = class extends Participant {
|
|
9340
|
+
opts;
|
|
9341
|
+
envRef = null;
|
|
9342
|
+
startedAtMs = null;
|
|
9343
|
+
baseSha;
|
|
9344
|
+
branchName = null;
|
|
9345
|
+
/**
|
|
9346
|
+
* DAG levels keyed by their ordinal as emitted in
|
|
9347
|
+
* LevelStartedItem. We use a Map rather than an array because
|
|
9348
|
+
* Conductor's ordinals are 1-based and would otherwise leave a
|
|
9349
|
+
* `levels[0] = undefined` hole that crashes any `for...of`
|
|
9350
|
+
* iteration (it walks holes too).
|
|
9351
|
+
*/
|
|
9352
|
+
levels = /* @__PURE__ */ new Map();
|
|
9353
|
+
stories = /* @__PURE__ */ new Map();
|
|
9354
|
+
/**
|
|
9355
|
+
* Resolves once finalize() has completed (or been short-circuited).
|
|
9356
|
+
* Lets orchestrate.ts gate its TUI `done` event so the PR URL lands
|
|
9357
|
+
* in the completion screen instead of after it.
|
|
9358
|
+
*/
|
|
9359
|
+
finalizePromise = null;
|
|
9360
|
+
constructor(opts) {
|
|
9361
|
+
super();
|
|
9362
|
+
this.opts = {
|
|
9363
|
+
cwd: opts.cwd,
|
|
9364
|
+
prdPath: opts.prdPath,
|
|
9365
|
+
createPr: opts.createPr ?? true,
|
|
9366
|
+
onLog: opts.onLog
|
|
9367
|
+
};
|
|
9368
|
+
this.baseSha = opts.baseSha ?? null;
|
|
9369
|
+
}
|
|
9370
|
+
setEnvironment(env) {
|
|
9371
|
+
this.envRef = env;
|
|
9372
|
+
}
|
|
9373
|
+
async onContextItem(_source, item) {
|
|
9374
|
+
if (item instanceof RunStartedItem) {
|
|
9375
|
+
this.startedAtMs = Date.now();
|
|
9376
|
+
if (this.baseSha == null) {
|
|
9377
|
+
this.baseSha = await getHeadSha(this.opts.cwd);
|
|
9378
|
+
}
|
|
9379
|
+
const prd = this.safeLoadPrd();
|
|
9380
|
+
this.branchName = prd?.branchName ?? null;
|
|
9381
|
+
return;
|
|
9382
|
+
}
|
|
9383
|
+
if (item instanceof LevelStartedItem) {
|
|
9384
|
+
this.levels.set(item.ordinal, [...item.storyIds]);
|
|
9385
|
+
for (const id of item.storyIds) {
|
|
9386
|
+
if (!this.stories.has(id)) {
|
|
9387
|
+
this.stories.set(id, {
|
|
9388
|
+
id,
|
|
9389
|
+
title: "",
|
|
9390
|
+
success: null,
|
|
9391
|
+
durationSecs: null,
|
|
9392
|
+
attempts: 0,
|
|
9393
|
+
levelOrdinal: item.ordinal
|
|
9394
|
+
});
|
|
9395
|
+
} else {
|
|
9396
|
+
const rec = this.stories.get(id);
|
|
9397
|
+
rec.levelOrdinal = item.ordinal;
|
|
9398
|
+
}
|
|
9399
|
+
}
|
|
9400
|
+
return;
|
|
9401
|
+
}
|
|
9402
|
+
if (item instanceof StoryResultItem) {
|
|
9403
|
+
const existing = this.stories.get(item.storyId) ?? {
|
|
9404
|
+
id: item.storyId,
|
|
9405
|
+
title: "",
|
|
9406
|
+
success: null,
|
|
9407
|
+
durationSecs: null,
|
|
9408
|
+
attempts: 0,
|
|
9409
|
+
levelOrdinal: null
|
|
9410
|
+
};
|
|
9411
|
+
existing.success = item.success;
|
|
9412
|
+
existing.durationSecs = item.durationSecs;
|
|
9413
|
+
existing.attempts = item.attempts;
|
|
9414
|
+
this.stories.set(item.storyId, existing);
|
|
9415
|
+
return;
|
|
9416
|
+
}
|
|
9417
|
+
if (item instanceof RunCompletedItem) {
|
|
9418
|
+
this.finalizePromise = this.safeFinalize(item);
|
|
9419
|
+
await this.finalizePromise;
|
|
9420
|
+
return;
|
|
9421
|
+
}
|
|
9422
|
+
}
|
|
9423
|
+
async safeFinalize(item) {
|
|
9424
|
+
try {
|
|
9425
|
+
await this.finalize(item);
|
|
9426
|
+
} catch (e) {
|
|
9427
|
+
const msg = e?.stack ?? String(e);
|
|
9428
|
+
this.log(`[finalizer] internal error, skipping PR: ${msg.split("\n")[0]}`);
|
|
9429
|
+
process.stderr.write(`[finalizer] crash: ${msg}
|
|
9430
|
+
`);
|
|
9431
|
+
this.emit(new PrCreatedItem(null, this.branchName ?? "", ""));
|
|
9432
|
+
}
|
|
9433
|
+
}
|
|
9434
|
+
/**
|
|
9435
|
+
* Resolves once Finalizer has finished handling RunCompletedItem
|
|
9436
|
+
* (PR opened, skipped, or failed). Resolves immediately if no run
|
|
9437
|
+
* has completed yet. Safe to call multiple times.
|
|
9438
|
+
*/
|
|
9439
|
+
complete() {
|
|
9440
|
+
return this.finalizePromise ?? Promise.resolve();
|
|
9441
|
+
}
|
|
9442
|
+
async finalize(run) {
|
|
9443
|
+
if (!this.opts.createPr) return;
|
|
9444
|
+
if (!run.success && run.completedStories.length === 0) {
|
|
9445
|
+
this.log("[finalizer] run failed before producing any commits; skipping PR");
|
|
9446
|
+
this.emit(new PrCreatedItem(null, this.branchName ?? "", ""));
|
|
9447
|
+
return;
|
|
9448
|
+
}
|
|
9449
|
+
if (!await this.hasGhBinary()) {
|
|
9450
|
+
this.log("[finalizer] `gh` not found on PATH; skipping PR creation");
|
|
9451
|
+
this.emit(new PrCreatedItem(null, this.branchName ?? "", ""));
|
|
9452
|
+
return;
|
|
9453
|
+
}
|
|
9454
|
+
const branch = this.branchName ?? await this.detectBranch();
|
|
9455
|
+
if (!branch) {
|
|
9456
|
+
this.log("[finalizer] could not determine branch; skipping PR");
|
|
9457
|
+
this.emit(new PrCreatedItem(null, "", ""));
|
|
9458
|
+
return;
|
|
9459
|
+
}
|
|
9460
|
+
const baseBranch = await this.detectDefaultBaseBranch();
|
|
9461
|
+
if (!baseBranch) {
|
|
9462
|
+
this.log("[finalizer] could not determine base branch; skipping PR");
|
|
9463
|
+
this.emit(new PrCreatedItem(null, branch, ""));
|
|
9464
|
+
return;
|
|
9465
|
+
}
|
|
9466
|
+
if (branch === baseBranch) {
|
|
9467
|
+
this.log(
|
|
9468
|
+
`[finalizer] branch '${branch}' matches base; skipping PR (run committed straight to main?)`
|
|
9469
|
+
);
|
|
9470
|
+
this.emit(new PrCreatedItem(null, branch, baseBranch));
|
|
9471
|
+
return;
|
|
9472
|
+
}
|
|
9473
|
+
this.emit(new FinalizeStartedItem(branch));
|
|
9474
|
+
const prd = this.safeLoadPrd();
|
|
9475
|
+
if (prd) {
|
|
9476
|
+
for (const s of prd.userStories) {
|
|
9477
|
+
const rec = this.stories.get(s.id);
|
|
9478
|
+
if (rec && !rec.title) rec.title = s.title;
|
|
9479
|
+
}
|
|
9480
|
+
}
|
|
9481
|
+
const commits = await this.collectCommitsSinceBase();
|
|
9482
|
+
const orderedStories = this.orderStories();
|
|
9483
|
+
const { passed, failed } = this.partition(orderedStories);
|
|
9484
|
+
const filesStats = await this.collectFileStats();
|
|
9485
|
+
const totalSecs = run.totalDurationSecs;
|
|
9486
|
+
const title = this.buildPrTitle(prd, passed.length, orderedStories.length);
|
|
9487
|
+
const body = this.buildPrBody({
|
|
9488
|
+
prd,
|
|
9489
|
+
run,
|
|
9490
|
+
orderedStories,
|
|
9491
|
+
passed,
|
|
9492
|
+
failed,
|
|
9493
|
+
commits,
|
|
9494
|
+
filesStats,
|
|
9495
|
+
totalSecs,
|
|
9496
|
+
sequentialSecs: this.sequentialSeconds()
|
|
9497
|
+
});
|
|
9498
|
+
this.log(`[finalizer] opening PR on ${baseBranch} \u2190 ${branch}`);
|
|
9499
|
+
const url = await this.openPr({ title, body, baseBranch, branch });
|
|
9500
|
+
if (url) {
|
|
9501
|
+
this.log(`[finalizer] PR opened: ${url}`);
|
|
9502
|
+
}
|
|
9503
|
+
this.emit(new PrCreatedItem(url, branch, baseBranch));
|
|
9504
|
+
}
|
|
9505
|
+
// ─── Bus & env helpers ──────────────────────────────────────────
|
|
9506
|
+
emit(item) {
|
|
9507
|
+
this.envRef?.deliverContextItem(this, item);
|
|
9508
|
+
}
|
|
9509
|
+
log(line) {
|
|
9510
|
+
this.opts.onLog?.(line);
|
|
9511
|
+
}
|
|
9512
|
+
// ─── PRD ────────────────────────────────────────────────────────
|
|
9513
|
+
safeLoadPrd() {
|
|
9514
|
+
try {
|
|
9515
|
+
return loadPrd(this.opts.prdPath);
|
|
9516
|
+
} catch {
|
|
9517
|
+
return null;
|
|
9518
|
+
}
|
|
9519
|
+
}
|
|
9520
|
+
// ─── Story ordering & partitioning ──────────────────────────────
|
|
9521
|
+
/**
|
|
9522
|
+
* Stories returned in DAG order so the table reads top-down the same
|
|
9523
|
+
* way the run executed: lowest-ordinal level first. Within a level
|
|
9524
|
+
* we sort by id (stable) to keep the table deterministic.
|
|
9525
|
+
*/
|
|
9526
|
+
orderStories() {
|
|
9527
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9528
|
+
const ordered = [];
|
|
9529
|
+
const ordinals = [...this.levels.keys()].sort((a, b) => a - b);
|
|
9530
|
+
for (const ord of ordinals) {
|
|
9531
|
+
const ids = this.levels.get(ord);
|
|
9532
|
+
if (!ids) continue;
|
|
9533
|
+
const sorted = [...ids].sort();
|
|
9534
|
+
for (const id of sorted) {
|
|
9535
|
+
const rec = this.stories.get(id);
|
|
9536
|
+
if (rec && !seen.has(id)) {
|
|
9537
|
+
ordered.push(rec);
|
|
9538
|
+
seen.add(id);
|
|
9539
|
+
}
|
|
9540
|
+
}
|
|
9541
|
+
}
|
|
9542
|
+
for (const [id, rec] of this.stories.entries()) {
|
|
9543
|
+
if (!seen.has(id)) ordered.push(rec);
|
|
9544
|
+
}
|
|
9545
|
+
return ordered;
|
|
9546
|
+
}
|
|
9547
|
+
partition(stories) {
|
|
9548
|
+
const passed = [];
|
|
9549
|
+
const failed = [];
|
|
9550
|
+
for (const s of stories) {
|
|
9551
|
+
if (s.success === true) passed.push(s);
|
|
9552
|
+
else if (s.success === false) failed.push(s);
|
|
9553
|
+
}
|
|
9554
|
+
return { passed, failed };
|
|
9555
|
+
}
|
|
9556
|
+
sequentialSeconds() {
|
|
9557
|
+
let sum = 0;
|
|
9558
|
+
for (const s of this.stories.values()) {
|
|
9559
|
+
if (s.durationSecs && s.success !== false) sum += s.durationSecs;
|
|
9560
|
+
}
|
|
9561
|
+
return sum;
|
|
9562
|
+
}
|
|
9563
|
+
// ─── Git / commits / files ──────────────────────────────────────
|
|
9564
|
+
async collectCommitsSinceBase() {
|
|
9565
|
+
if (!this.baseSha) return [];
|
|
9566
|
+
try {
|
|
9567
|
+
const { stdout } = await execFileAsync2(
|
|
9568
|
+
"git",
|
|
9569
|
+
["log", `${this.baseSha}..HEAD`, "--pretty=format:%H%x09%s"],
|
|
9570
|
+
{ cwd: this.opts.cwd }
|
|
9571
|
+
);
|
|
9572
|
+
return stdout.split("\n").filter((l) => l.includes(" ")).map((l) => {
|
|
9573
|
+
const [sha, ...rest] = l.split(" ");
|
|
9574
|
+
return { sha, subject: rest.join(" ").trim() };
|
|
9575
|
+
});
|
|
9576
|
+
} catch {
|
|
9577
|
+
return [];
|
|
9578
|
+
}
|
|
9579
|
+
}
|
|
9580
|
+
async collectFileStats() {
|
|
9581
|
+
if (!this.baseSha) return { created: 0, modified: 0 };
|
|
9582
|
+
try {
|
|
9583
|
+
const { stdout } = await execFileAsync2(
|
|
9584
|
+
"git",
|
|
9585
|
+
["diff", "--name-status", this.baseSha, "HEAD"],
|
|
9586
|
+
{ cwd: this.opts.cwd }
|
|
9587
|
+
);
|
|
9588
|
+
let created = 0;
|
|
9589
|
+
let modified = 0;
|
|
9590
|
+
for (const line of stdout.split("\n")) {
|
|
9591
|
+
const ch = line.charAt(0);
|
|
9592
|
+
if (ch === "A") created++;
|
|
9593
|
+
else if (ch === "M" || ch === "R") modified++;
|
|
9594
|
+
}
|
|
9595
|
+
return { created, modified };
|
|
9596
|
+
} catch {
|
|
9597
|
+
return { created: 0, modified: 0 };
|
|
9598
|
+
}
|
|
9599
|
+
}
|
|
9600
|
+
async detectBranch() {
|
|
9601
|
+
try {
|
|
9602
|
+
const { stdout } = await execFileAsync2(
|
|
9603
|
+
"git",
|
|
9604
|
+
["branch", "--show-current"],
|
|
9605
|
+
{ cwd: this.opts.cwd }
|
|
9606
|
+
);
|
|
9607
|
+
return stdout.trim() || null;
|
|
9608
|
+
} catch {
|
|
9609
|
+
return null;
|
|
9610
|
+
}
|
|
9611
|
+
}
|
|
9612
|
+
async detectDefaultBaseBranch() {
|
|
9613
|
+
try {
|
|
9614
|
+
const { stdout } = await execFileAsync2(
|
|
9615
|
+
"gh",
|
|
9616
|
+
["repo", "view", "--json", "defaultBranchRef", "--jq", ".defaultBranchRef.name"],
|
|
9617
|
+
{ cwd: this.opts.cwd }
|
|
9618
|
+
);
|
|
9619
|
+
const name = stdout.trim();
|
|
9620
|
+
if (name) return name;
|
|
9621
|
+
} catch {
|
|
9622
|
+
}
|
|
9623
|
+
try {
|
|
9624
|
+
const { stdout } = await execFileAsync2(
|
|
9625
|
+
"git",
|
|
9626
|
+
["symbolic-ref", "--short", "refs/remotes/origin/HEAD"],
|
|
9627
|
+
{ cwd: this.opts.cwd }
|
|
9628
|
+
);
|
|
9629
|
+
const ref = stdout.trim();
|
|
9630
|
+
if (ref.startsWith("origin/")) return ref.slice("origin/".length);
|
|
9631
|
+
} catch {
|
|
9632
|
+
}
|
|
9633
|
+
return "main";
|
|
9634
|
+
}
|
|
9635
|
+
// ─── PR body composition ────────────────────────────────────────
|
|
9636
|
+
buildPrTitle(prd, passed, total) {
|
|
9637
|
+
const project = prd?.project ?? "baro run";
|
|
9638
|
+
if (passed === total) {
|
|
9639
|
+
return `${project} (${total} ${total === 1 ? "story" : "stories"})`;
|
|
9640
|
+
}
|
|
9641
|
+
return `${project} (${passed}/${total} stories)`;
|
|
9642
|
+
}
|
|
9643
|
+
buildPrBody(args) {
|
|
9644
|
+
const { prd, run, orderedStories, passed, failed, commits, filesStats } = args;
|
|
9645
|
+
const lines = [];
|
|
9646
|
+
lines.push(
|
|
9647
|
+
`> Opened by [baro](https://baro.rs) \u2014 Mozaik-orchestrated parallel coding agents.`
|
|
9648
|
+
);
|
|
9649
|
+
lines.push("");
|
|
9650
|
+
if (prd?.description) {
|
|
9651
|
+
lines.push("## Goal");
|
|
9652
|
+
lines.push("");
|
|
9653
|
+
lines.push(prd.description.trim());
|
|
9654
|
+
lines.push("");
|
|
9655
|
+
}
|
|
9656
|
+
if (this.levels.size > 0) {
|
|
9657
|
+
lines.push("## Plan");
|
|
9658
|
+
lines.push("");
|
|
9659
|
+
lines.push("```");
|
|
9660
|
+
const ordinals = [...this.levels.keys()].sort((a, b) => a - b);
|
|
9661
|
+
for (const ord of ordinals) {
|
|
9662
|
+
const ids = this.levels.get(ord) ?? [];
|
|
9663
|
+
lines.push(`Level ${ord} \u2500\u2500\u2500 ${ids.join(", ")}`);
|
|
9664
|
+
}
|
|
9665
|
+
lines.push("```");
|
|
9666
|
+
lines.push("");
|
|
9667
|
+
}
|
|
9668
|
+
lines.push("## Stories");
|
|
9669
|
+
lines.push("");
|
|
9670
|
+
lines.push("| # | Story | Status | Duration | Commit |");
|
|
9671
|
+
lines.push("|---|-------|--------|----------|--------|");
|
|
9672
|
+
for (const s of orderedStories) {
|
|
9673
|
+
const title = (s.title || s.id).replace(/\|/g, "\\|");
|
|
9674
|
+
const status = s.success === true ? "\u2713" : s.success === false ? "\u2717" : "\u2014";
|
|
9675
|
+
const dur = s.durationSecs != null ? formatDuration(s.durationSecs) : "\u2014";
|
|
9676
|
+
const commit = matchCommit(s, commits);
|
|
9677
|
+
lines.push(
|
|
9678
|
+
`| ${s.id} | ${title} | ${status} | ${dur} | ${commit ? "`" + commit.slice(0, 7) + "`" : "\u2014"} |`
|
|
9679
|
+
);
|
|
9680
|
+
}
|
|
9681
|
+
lines.push("");
|
|
9682
|
+
lines.push("## Diff stats");
|
|
9683
|
+
lines.push("");
|
|
9684
|
+
lines.push(`- **Files created**: ${filesStats.created}`);
|
|
9685
|
+
lines.push(`- **Files modified**: ${filesStats.modified}`);
|
|
9686
|
+
lines.push(`- **Total commits**: ${commits.length}`);
|
|
9687
|
+
lines.push("");
|
|
9688
|
+
lines.push("## Run summary");
|
|
9689
|
+
lines.push("");
|
|
9690
|
+
const wall = formatDuration(args.totalSecs);
|
|
9691
|
+
const seq = formatDuration(args.sequentialSecs);
|
|
9692
|
+
const speedup = args.totalSecs > 0 ? (args.sequentialSecs / args.totalSecs).toFixed(2) + "\xD7" : "\u2014";
|
|
9693
|
+
lines.push(`- **Wall time**: ${wall}`);
|
|
9694
|
+
lines.push(`- **Sequential time**: ${seq}`);
|
|
9695
|
+
lines.push(`- **Parallel speedup**: ${speedup}`);
|
|
9696
|
+
lines.push(`- **Stories passed**: ${passed.length}/${orderedStories.length}`);
|
|
9697
|
+
lines.push(`- **Stories failed**: ${failed.length}`);
|
|
9698
|
+
lines.push(`- **Total story attempts**: ${run.totalAttempts}`);
|
|
9699
|
+
if (run.abortReason) {
|
|
9700
|
+
lines.push(`- **Abort reason**: ${run.abortReason}`);
|
|
9701
|
+
}
|
|
9702
|
+
lines.push("");
|
|
9703
|
+
lines.push("---");
|
|
9704
|
+
lines.push("");
|
|
9705
|
+
lines.push("\u{1F916} Plan. Parallelize. Review. Ship. \u2014 opened by baro");
|
|
9706
|
+
return lines.join("\n");
|
|
9707
|
+
}
|
|
9708
|
+
async hasGhBinary() {
|
|
9709
|
+
try {
|
|
9710
|
+
await execFileAsync2("gh", ["--version"], { cwd: this.opts.cwd });
|
|
9711
|
+
return true;
|
|
9712
|
+
} catch {
|
|
9713
|
+
return false;
|
|
9714
|
+
}
|
|
9715
|
+
}
|
|
9716
|
+
async openPr(args) {
|
|
9717
|
+
try {
|
|
9718
|
+
const { stdout } = await execFileAsync2(
|
|
9719
|
+
"gh",
|
|
9720
|
+
[
|
|
9721
|
+
"pr",
|
|
9722
|
+
"create",
|
|
9723
|
+
"--base",
|
|
9724
|
+
args.baseBranch,
|
|
9725
|
+
"--head",
|
|
9726
|
+
args.branch,
|
|
9727
|
+
"--title",
|
|
9728
|
+
args.title,
|
|
9729
|
+
"--body",
|
|
9730
|
+
args.body
|
|
9731
|
+
],
|
|
9732
|
+
{ cwd: this.opts.cwd }
|
|
9733
|
+
);
|
|
9734
|
+
const url = stdout.trim().split("\n").pop() ?? "";
|
|
9735
|
+
return url || null;
|
|
9736
|
+
} catch (e) {
|
|
9737
|
+
const stderr = e?.stderr ?? "";
|
|
9738
|
+
const existing = stderr.match(/https:\/\/github\.com\/\S+\/pull\/\d+/)?.[0];
|
|
9739
|
+
if (existing) {
|
|
9740
|
+
this.log(`[finalizer] PR already exists: ${existing}`);
|
|
9741
|
+
return existing;
|
|
9742
|
+
}
|
|
9743
|
+
this.log(
|
|
9744
|
+
`[finalizer] gh pr create failed: ${stderr.split("\n")[0]?.trim() || e.message}`
|
|
9745
|
+
);
|
|
9746
|
+
return null;
|
|
9747
|
+
}
|
|
9748
|
+
}
|
|
9749
|
+
};
|
|
9750
|
+
function formatDuration(secs) {
|
|
9751
|
+
if (secs < 60) return `${Math.round(secs)}s`;
|
|
9752
|
+
const m = Math.floor(secs / 60);
|
|
9753
|
+
const s = Math.round(secs % 60);
|
|
9754
|
+
return `${m}:${s.toString().padStart(2, "0")}`;
|
|
9755
|
+
}
|
|
9756
|
+
function matchCommit(story, commits) {
|
|
9757
|
+
if (commits.length === 0) return null;
|
|
9758
|
+
const idPattern = new RegExp(`\\b${story.id}\\b`, "i");
|
|
9759
|
+
for (const c of commits) {
|
|
9760
|
+
if (idPattern.test(c.subject)) return c.sha;
|
|
9761
|
+
}
|
|
9762
|
+
if (!story.title) return null;
|
|
9763
|
+
const keywords = story.title.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length >= 4);
|
|
9764
|
+
if (keywords.length === 0) return null;
|
|
9765
|
+
let bestSha = null;
|
|
9766
|
+
let bestScore = 0;
|
|
9767
|
+
for (const c of commits) {
|
|
9768
|
+
const subj = c.subject.toLowerCase();
|
|
9769
|
+
const hits = keywords.reduce((n, k) => subj.includes(k) ? n + 1 : n, 0);
|
|
9770
|
+
if (hits > bestScore) {
|
|
9771
|
+
bestScore = hits;
|
|
9772
|
+
bestSha = c.sha;
|
|
9773
|
+
}
|
|
9774
|
+
}
|
|
9775
|
+
return bestScore >= 2 ? bestSha : null;
|
|
9776
|
+
}
|
|
9777
|
+
|
|
9308
9778
|
// ../baro-orchestrator/src/participants/librarian.ts
|
|
9309
9779
|
var EXPLORATION_TOOLS = /* @__PURE__ */ new Set([
|
|
9310
9780
|
"Read",
|
|
@@ -9651,9 +10121,9 @@ var StoryFactory = class extends Participant {
|
|
|
9651
10121
|
};
|
|
9652
10122
|
|
|
9653
10123
|
// ../baro-orchestrator/src/participants/surgeon.ts
|
|
9654
|
-
import { execFile as
|
|
9655
|
-
import { promisify as
|
|
9656
|
-
var
|
|
10124
|
+
import { execFile as execFile4 } from "child_process";
|
|
10125
|
+
import { promisify as promisify4 } from "util";
|
|
10126
|
+
var execFileAsync3 = promisify4(execFile4);
|
|
9657
10127
|
var SURGEON_SYSTEM_PROMPT = `You are the Surgeon \u2014 an autonomous planner that adapts a software-project
|
|
9658
10128
|
DAG when stories fail. Given:
|
|
9659
10129
|
1. A snapshot of the current PRD (project, story list with dependencies +
|
|
@@ -9765,7 +10235,7 @@ var Surgeon = class extends Participant {
|
|
|
9765
10235
|
const snap = this.opts.snapshot();
|
|
9766
10236
|
const prompt = buildSurgeonPrompt(snap, failure);
|
|
9767
10237
|
try {
|
|
9768
|
-
const { stdout } = await
|
|
10238
|
+
const { stdout } = await execFileAsync3(
|
|
9769
10239
|
this.opts.claudeBin,
|
|
9770
10240
|
[
|
|
9771
10241
|
"--print",
|
|
@@ -9926,6 +10396,15 @@ async function orchestrate(config) {
|
|
|
9926
10396
|
});
|
|
9927
10397
|
critic.join(env);
|
|
9928
10398
|
}
|
|
10399
|
+
const finalizer = useGit ? new Finalizer({
|
|
10400
|
+
cwd: config.cwd,
|
|
10401
|
+
prdPath: config.prdPath,
|
|
10402
|
+
onLog: (line) => emitTui && emit({ type: "story_log", id: "_finalizer", line })
|
|
10403
|
+
}) : null;
|
|
10404
|
+
if (finalizer) {
|
|
10405
|
+
finalizer.setEnvironment(env);
|
|
10406
|
+
finalizer.join(env);
|
|
10407
|
+
}
|
|
9929
10408
|
const conductor = new Conductor({
|
|
9930
10409
|
prdPath: config.prdPath,
|
|
9931
10410
|
cwd: config.cwd,
|
|
@@ -10006,6 +10485,7 @@ async function orchestrate(config) {
|
|
|
10006
10485
|
const summary = await conductor.done;
|
|
10007
10486
|
if (critic) await critic.idle();
|
|
10008
10487
|
if (surgeon) await surgeon.idle();
|
|
10488
|
+
if (finalizer) await finalizer.complete();
|
|
10009
10489
|
let filesCreated = 0;
|
|
10010
10490
|
let filesModified = 0;
|
|
10011
10491
|
if (useGit && baseSha) {
|
|
@@ -10081,6 +10561,14 @@ var BaroEventForwarder = class extends Participant {
|
|
|
10081
10561
|
this.handleCritique(item);
|
|
10082
10562
|
return;
|
|
10083
10563
|
}
|
|
10564
|
+
if (item instanceof FinalizeStartedItem) {
|
|
10565
|
+
emit({ type: "finalize_start" });
|
|
10566
|
+
return;
|
|
10567
|
+
}
|
|
10568
|
+
if (item instanceof PrCreatedItem) {
|
|
10569
|
+
emit({ type: "finalize_complete", pr_url: item.url });
|
|
10570
|
+
return;
|
|
10571
|
+
}
|
|
10084
10572
|
}
|
|
10085
10573
|
handleCoordination(item) {
|
|
10086
10574
|
emit({
|