baro-ai 0.23.2 → 0.23.3
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 +470 -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,427 @@ 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
|
+
levels = [];
|
|
9346
|
+
stories = /* @__PURE__ */ new Map();
|
|
9347
|
+
/**
|
|
9348
|
+
* Resolves once finalize() has completed (or been short-circuited).
|
|
9349
|
+
* Lets orchestrate.ts gate its TUI `done` event so the PR URL lands
|
|
9350
|
+
* in the completion screen instead of after it.
|
|
9351
|
+
*/
|
|
9352
|
+
finalizePromise = null;
|
|
9353
|
+
constructor(opts) {
|
|
9354
|
+
super();
|
|
9355
|
+
this.opts = {
|
|
9356
|
+
cwd: opts.cwd,
|
|
9357
|
+
prdPath: opts.prdPath,
|
|
9358
|
+
createPr: opts.createPr ?? true,
|
|
9359
|
+
onLog: opts.onLog
|
|
9360
|
+
};
|
|
9361
|
+
this.baseSha = opts.baseSha ?? null;
|
|
9362
|
+
}
|
|
9363
|
+
setEnvironment(env) {
|
|
9364
|
+
this.envRef = env;
|
|
9365
|
+
}
|
|
9366
|
+
async onContextItem(_source, item) {
|
|
9367
|
+
if (item instanceof RunStartedItem) {
|
|
9368
|
+
this.startedAtMs = Date.now();
|
|
9369
|
+
if (this.baseSha == null) {
|
|
9370
|
+
this.baseSha = await getHeadSha(this.opts.cwd);
|
|
9371
|
+
}
|
|
9372
|
+
const prd = this.safeLoadPrd();
|
|
9373
|
+
this.branchName = prd?.branchName ?? null;
|
|
9374
|
+
return;
|
|
9375
|
+
}
|
|
9376
|
+
if (item instanceof LevelStartedItem) {
|
|
9377
|
+
this.levels[item.ordinal] = [...item.storyIds];
|
|
9378
|
+
for (const id of item.storyIds) {
|
|
9379
|
+
if (!this.stories.has(id)) {
|
|
9380
|
+
this.stories.set(id, {
|
|
9381
|
+
id,
|
|
9382
|
+
title: "",
|
|
9383
|
+
success: null,
|
|
9384
|
+
durationSecs: null,
|
|
9385
|
+
attempts: 0,
|
|
9386
|
+
levelOrdinal: item.ordinal
|
|
9387
|
+
});
|
|
9388
|
+
} else {
|
|
9389
|
+
const rec = this.stories.get(id);
|
|
9390
|
+
rec.levelOrdinal = item.ordinal;
|
|
9391
|
+
}
|
|
9392
|
+
}
|
|
9393
|
+
return;
|
|
9394
|
+
}
|
|
9395
|
+
if (item instanceof StoryResultItem) {
|
|
9396
|
+
const existing = this.stories.get(item.storyId) ?? {
|
|
9397
|
+
id: item.storyId,
|
|
9398
|
+
title: "",
|
|
9399
|
+
success: null,
|
|
9400
|
+
durationSecs: null,
|
|
9401
|
+
attempts: 0,
|
|
9402
|
+
levelOrdinal: null
|
|
9403
|
+
};
|
|
9404
|
+
existing.success = item.success;
|
|
9405
|
+
existing.durationSecs = item.durationSecs;
|
|
9406
|
+
existing.attempts = item.attempts;
|
|
9407
|
+
this.stories.set(item.storyId, existing);
|
|
9408
|
+
return;
|
|
9409
|
+
}
|
|
9410
|
+
if (item instanceof RunCompletedItem) {
|
|
9411
|
+
this.finalizePromise = this.finalize(item);
|
|
9412
|
+
await this.finalizePromise;
|
|
9413
|
+
return;
|
|
9414
|
+
}
|
|
9415
|
+
}
|
|
9416
|
+
/**
|
|
9417
|
+
* Resolves once Finalizer has finished handling RunCompletedItem
|
|
9418
|
+
* (PR opened, skipped, or failed). Resolves immediately if no run
|
|
9419
|
+
* has completed yet. Safe to call multiple times.
|
|
9420
|
+
*/
|
|
9421
|
+
complete() {
|
|
9422
|
+
return this.finalizePromise ?? Promise.resolve();
|
|
9423
|
+
}
|
|
9424
|
+
async finalize(run) {
|
|
9425
|
+
if (!this.opts.createPr) return;
|
|
9426
|
+
if (!run.success && run.completedStories.length === 0) {
|
|
9427
|
+
this.log("[finalizer] run failed before producing any commits; skipping PR");
|
|
9428
|
+
this.emit(new PrCreatedItem(null, this.branchName ?? "", ""));
|
|
9429
|
+
return;
|
|
9430
|
+
}
|
|
9431
|
+
if (!await this.hasGhBinary()) {
|
|
9432
|
+
this.log("[finalizer] `gh` not found on PATH; skipping PR creation");
|
|
9433
|
+
this.emit(new PrCreatedItem(null, this.branchName ?? "", ""));
|
|
9434
|
+
return;
|
|
9435
|
+
}
|
|
9436
|
+
const branch = this.branchName ?? await this.detectBranch();
|
|
9437
|
+
if (!branch) {
|
|
9438
|
+
this.log("[finalizer] could not determine branch; skipping PR");
|
|
9439
|
+
this.emit(new PrCreatedItem(null, "", ""));
|
|
9440
|
+
return;
|
|
9441
|
+
}
|
|
9442
|
+
const baseBranch = await this.detectDefaultBaseBranch();
|
|
9443
|
+
if (!baseBranch) {
|
|
9444
|
+
this.log("[finalizer] could not determine base branch; skipping PR");
|
|
9445
|
+
this.emit(new PrCreatedItem(null, branch, ""));
|
|
9446
|
+
return;
|
|
9447
|
+
}
|
|
9448
|
+
if (branch === baseBranch) {
|
|
9449
|
+
this.log(
|
|
9450
|
+
`[finalizer] branch '${branch}' matches base; skipping PR (run committed straight to main?)`
|
|
9451
|
+
);
|
|
9452
|
+
this.emit(new PrCreatedItem(null, branch, baseBranch));
|
|
9453
|
+
return;
|
|
9454
|
+
}
|
|
9455
|
+
this.emit(new FinalizeStartedItem(branch));
|
|
9456
|
+
const prd = this.safeLoadPrd();
|
|
9457
|
+
if (prd) {
|
|
9458
|
+
for (const s of prd.userStories) {
|
|
9459
|
+
const rec = this.stories.get(s.id);
|
|
9460
|
+
if (rec && !rec.title) rec.title = s.title;
|
|
9461
|
+
}
|
|
9462
|
+
}
|
|
9463
|
+
const commits = await this.collectCommitsSinceBase();
|
|
9464
|
+
const orderedStories = this.orderStories();
|
|
9465
|
+
const { passed, failed } = this.partition(orderedStories);
|
|
9466
|
+
const filesStats = await this.collectFileStats();
|
|
9467
|
+
const totalSecs = run.totalDurationSecs;
|
|
9468
|
+
const title = this.buildPrTitle(prd, passed.length, orderedStories.length);
|
|
9469
|
+
const body = this.buildPrBody({
|
|
9470
|
+
prd,
|
|
9471
|
+
run,
|
|
9472
|
+
orderedStories,
|
|
9473
|
+
passed,
|
|
9474
|
+
failed,
|
|
9475
|
+
commits,
|
|
9476
|
+
filesStats,
|
|
9477
|
+
totalSecs,
|
|
9478
|
+
sequentialSecs: this.sequentialSeconds()
|
|
9479
|
+
});
|
|
9480
|
+
this.log(`[finalizer] opening PR on ${baseBranch} \u2190 ${branch}`);
|
|
9481
|
+
const url = await this.openPr({ title, body, baseBranch, branch });
|
|
9482
|
+
if (url) {
|
|
9483
|
+
this.log(`[finalizer] PR opened: ${url}`);
|
|
9484
|
+
}
|
|
9485
|
+
this.emit(new PrCreatedItem(url, branch, baseBranch));
|
|
9486
|
+
}
|
|
9487
|
+
// ─── Bus & env helpers ──────────────────────────────────────────
|
|
9488
|
+
emit(item) {
|
|
9489
|
+
this.envRef?.deliverContextItem(this, item);
|
|
9490
|
+
}
|
|
9491
|
+
log(line) {
|
|
9492
|
+
this.opts.onLog?.(line);
|
|
9493
|
+
}
|
|
9494
|
+
// ─── PRD ────────────────────────────────────────────────────────
|
|
9495
|
+
safeLoadPrd() {
|
|
9496
|
+
try {
|
|
9497
|
+
return loadPrd(this.opts.prdPath);
|
|
9498
|
+
} catch {
|
|
9499
|
+
return null;
|
|
9500
|
+
}
|
|
9501
|
+
}
|
|
9502
|
+
// ─── Story ordering & partitioning ──────────────────────────────
|
|
9503
|
+
/**
|
|
9504
|
+
* Stories returned in DAG order so the table reads top-down the same
|
|
9505
|
+
* way the run executed: level 0 first, then level 1, etc. Within a
|
|
9506
|
+
* level we sort by id (stable) to keep the table deterministic.
|
|
9507
|
+
*/
|
|
9508
|
+
orderStories() {
|
|
9509
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9510
|
+
const ordered = [];
|
|
9511
|
+
for (const lvl of this.levels) {
|
|
9512
|
+
const sorted = [...lvl].sort();
|
|
9513
|
+
for (const id of sorted) {
|
|
9514
|
+
const rec = this.stories.get(id);
|
|
9515
|
+
if (rec && !seen.has(id)) {
|
|
9516
|
+
ordered.push(rec);
|
|
9517
|
+
seen.add(id);
|
|
9518
|
+
}
|
|
9519
|
+
}
|
|
9520
|
+
}
|
|
9521
|
+
for (const [id, rec] of this.stories.entries()) {
|
|
9522
|
+
if (!seen.has(id)) ordered.push(rec);
|
|
9523
|
+
}
|
|
9524
|
+
return ordered;
|
|
9525
|
+
}
|
|
9526
|
+
partition(stories) {
|
|
9527
|
+
const passed = [];
|
|
9528
|
+
const failed = [];
|
|
9529
|
+
for (const s of stories) {
|
|
9530
|
+
if (s.success === true) passed.push(s);
|
|
9531
|
+
else if (s.success === false) failed.push(s);
|
|
9532
|
+
}
|
|
9533
|
+
return { passed, failed };
|
|
9534
|
+
}
|
|
9535
|
+
sequentialSeconds() {
|
|
9536
|
+
let sum = 0;
|
|
9537
|
+
for (const s of this.stories.values()) {
|
|
9538
|
+
if (s.durationSecs && s.success !== false) sum += s.durationSecs;
|
|
9539
|
+
}
|
|
9540
|
+
return sum;
|
|
9541
|
+
}
|
|
9542
|
+
// ─── Git / commits / files ──────────────────────────────────────
|
|
9543
|
+
async collectCommitsSinceBase() {
|
|
9544
|
+
if (!this.baseSha) return [];
|
|
9545
|
+
try {
|
|
9546
|
+
const { stdout } = await execFileAsync2(
|
|
9547
|
+
"git",
|
|
9548
|
+
["log", `${this.baseSha}..HEAD`, "--pretty=format:%H%x09%s"],
|
|
9549
|
+
{ cwd: this.opts.cwd }
|
|
9550
|
+
);
|
|
9551
|
+
return stdout.split("\n").filter((l) => l.includes(" ")).map((l) => {
|
|
9552
|
+
const [sha, ...rest] = l.split(" ");
|
|
9553
|
+
return { sha, subject: rest.join(" ").trim() };
|
|
9554
|
+
});
|
|
9555
|
+
} catch {
|
|
9556
|
+
return [];
|
|
9557
|
+
}
|
|
9558
|
+
}
|
|
9559
|
+
async collectFileStats() {
|
|
9560
|
+
if (!this.baseSha) return { created: 0, modified: 0 };
|
|
9561
|
+
try {
|
|
9562
|
+
const { stdout } = await execFileAsync2(
|
|
9563
|
+
"git",
|
|
9564
|
+
["diff", "--name-status", this.baseSha, "HEAD"],
|
|
9565
|
+
{ cwd: this.opts.cwd }
|
|
9566
|
+
);
|
|
9567
|
+
let created = 0;
|
|
9568
|
+
let modified = 0;
|
|
9569
|
+
for (const line of stdout.split("\n")) {
|
|
9570
|
+
const ch = line.charAt(0);
|
|
9571
|
+
if (ch === "A") created++;
|
|
9572
|
+
else if (ch === "M" || ch === "R") modified++;
|
|
9573
|
+
}
|
|
9574
|
+
return { created, modified };
|
|
9575
|
+
} catch {
|
|
9576
|
+
return { created: 0, modified: 0 };
|
|
9577
|
+
}
|
|
9578
|
+
}
|
|
9579
|
+
async detectBranch() {
|
|
9580
|
+
try {
|
|
9581
|
+
const { stdout } = await execFileAsync2(
|
|
9582
|
+
"git",
|
|
9583
|
+
["branch", "--show-current"],
|
|
9584
|
+
{ cwd: this.opts.cwd }
|
|
9585
|
+
);
|
|
9586
|
+
return stdout.trim() || null;
|
|
9587
|
+
} catch {
|
|
9588
|
+
return null;
|
|
9589
|
+
}
|
|
9590
|
+
}
|
|
9591
|
+
async detectDefaultBaseBranch() {
|
|
9592
|
+
try {
|
|
9593
|
+
const { stdout } = await execFileAsync2(
|
|
9594
|
+
"gh",
|
|
9595
|
+
["repo", "view", "--json", "defaultBranchRef", "--jq", ".defaultBranchRef.name"],
|
|
9596
|
+
{ cwd: this.opts.cwd }
|
|
9597
|
+
);
|
|
9598
|
+
const name = stdout.trim();
|
|
9599
|
+
if (name) return name;
|
|
9600
|
+
} catch {
|
|
9601
|
+
}
|
|
9602
|
+
try {
|
|
9603
|
+
const { stdout } = await execFileAsync2(
|
|
9604
|
+
"git",
|
|
9605
|
+
["symbolic-ref", "--short", "refs/remotes/origin/HEAD"],
|
|
9606
|
+
{ cwd: this.opts.cwd }
|
|
9607
|
+
);
|
|
9608
|
+
const ref = stdout.trim();
|
|
9609
|
+
if (ref.startsWith("origin/")) return ref.slice("origin/".length);
|
|
9610
|
+
} catch {
|
|
9611
|
+
}
|
|
9612
|
+
return "main";
|
|
9613
|
+
}
|
|
9614
|
+
// ─── PR body composition ────────────────────────────────────────
|
|
9615
|
+
buildPrTitle(prd, passed, total) {
|
|
9616
|
+
const project = prd?.project ?? "baro run";
|
|
9617
|
+
if (passed === total) {
|
|
9618
|
+
return `${project} (${total} ${total === 1 ? "story" : "stories"})`;
|
|
9619
|
+
}
|
|
9620
|
+
return `${project} (${passed}/${total} stories)`;
|
|
9621
|
+
}
|
|
9622
|
+
buildPrBody(args) {
|
|
9623
|
+
const { prd, run, orderedStories, passed, failed, commits, filesStats } = args;
|
|
9624
|
+
const lines = [];
|
|
9625
|
+
lines.push(
|
|
9626
|
+
`> Opened by [baro](https://baro.rs) \u2014 Mozaik-orchestrated parallel coding agents.`
|
|
9627
|
+
);
|
|
9628
|
+
lines.push("");
|
|
9629
|
+
if (prd?.description) {
|
|
9630
|
+
lines.push("## Goal");
|
|
9631
|
+
lines.push("");
|
|
9632
|
+
lines.push(prd.description.trim());
|
|
9633
|
+
lines.push("");
|
|
9634
|
+
}
|
|
9635
|
+
if (this.levels.length > 0) {
|
|
9636
|
+
lines.push("## Plan");
|
|
9637
|
+
lines.push("");
|
|
9638
|
+
lines.push("```");
|
|
9639
|
+
for (let i = 0; i < this.levels.length; i++) {
|
|
9640
|
+
const ids = this.levels[i];
|
|
9641
|
+
lines.push(`Level ${i} \u2500\u2500\u2500 ${ids.join(", ")}`);
|
|
9642
|
+
}
|
|
9643
|
+
lines.push("```");
|
|
9644
|
+
lines.push("");
|
|
9645
|
+
}
|
|
9646
|
+
lines.push("## Stories");
|
|
9647
|
+
lines.push("");
|
|
9648
|
+
lines.push("| # | Story | Status | Duration | Commit |");
|
|
9649
|
+
lines.push("|---|-------|--------|----------|--------|");
|
|
9650
|
+
for (const s of orderedStories) {
|
|
9651
|
+
const title = (s.title || s.id).replace(/\|/g, "\\|");
|
|
9652
|
+
const status = s.success === true ? "\u2713" : s.success === false ? "\u2717" : "\u2014";
|
|
9653
|
+
const dur = s.durationSecs != null ? formatDuration(s.durationSecs) : "\u2014";
|
|
9654
|
+
const commit = matchCommit(s, commits);
|
|
9655
|
+
lines.push(
|
|
9656
|
+
`| ${s.id} | ${title} | ${status} | ${dur} | ${commit ? "`" + commit.slice(0, 7) + "`" : "\u2014"} |`
|
|
9657
|
+
);
|
|
9658
|
+
}
|
|
9659
|
+
lines.push("");
|
|
9660
|
+
lines.push("## Diff stats");
|
|
9661
|
+
lines.push("");
|
|
9662
|
+
lines.push(`- **Files created**: ${filesStats.created}`);
|
|
9663
|
+
lines.push(`- **Files modified**: ${filesStats.modified}`);
|
|
9664
|
+
lines.push(`- **Total commits**: ${commits.length}`);
|
|
9665
|
+
lines.push("");
|
|
9666
|
+
lines.push("## Run summary");
|
|
9667
|
+
lines.push("");
|
|
9668
|
+
const wall = formatDuration(args.totalSecs);
|
|
9669
|
+
const seq = formatDuration(args.sequentialSecs);
|
|
9670
|
+
const speedup = args.totalSecs > 0 ? (args.sequentialSecs / args.totalSecs).toFixed(2) + "\xD7" : "\u2014";
|
|
9671
|
+
lines.push(`- **Wall time**: ${wall}`);
|
|
9672
|
+
lines.push(`- **Sequential time**: ${seq}`);
|
|
9673
|
+
lines.push(`- **Parallel speedup**: ${speedup}`);
|
|
9674
|
+
lines.push(`- **Stories passed**: ${passed.length}/${orderedStories.length}`);
|
|
9675
|
+
lines.push(`- **Stories failed**: ${failed.length}`);
|
|
9676
|
+
lines.push(`- **Total story attempts**: ${run.totalAttempts}`);
|
|
9677
|
+
if (run.abortReason) {
|
|
9678
|
+
lines.push(`- **Abort reason**: ${run.abortReason}`);
|
|
9679
|
+
}
|
|
9680
|
+
lines.push("");
|
|
9681
|
+
lines.push("---");
|
|
9682
|
+
lines.push("");
|
|
9683
|
+
lines.push("\u{1F916} Plan. Parallelize. Review. Ship. \u2014 opened by baro");
|
|
9684
|
+
return lines.join("\n");
|
|
9685
|
+
}
|
|
9686
|
+
async hasGhBinary() {
|
|
9687
|
+
try {
|
|
9688
|
+
await execFileAsync2("gh", ["--version"], { cwd: this.opts.cwd });
|
|
9689
|
+
return true;
|
|
9690
|
+
} catch {
|
|
9691
|
+
return false;
|
|
9692
|
+
}
|
|
9693
|
+
}
|
|
9694
|
+
async openPr(args) {
|
|
9695
|
+
try {
|
|
9696
|
+
const { stdout } = await execFileAsync2(
|
|
9697
|
+
"gh",
|
|
9698
|
+
[
|
|
9699
|
+
"pr",
|
|
9700
|
+
"create",
|
|
9701
|
+
"--base",
|
|
9702
|
+
args.baseBranch,
|
|
9703
|
+
"--head",
|
|
9704
|
+
args.branch,
|
|
9705
|
+
"--title",
|
|
9706
|
+
args.title,
|
|
9707
|
+
"--body",
|
|
9708
|
+
args.body
|
|
9709
|
+
],
|
|
9710
|
+
{ cwd: this.opts.cwd }
|
|
9711
|
+
);
|
|
9712
|
+
const url = stdout.trim().split("\n").pop() ?? "";
|
|
9713
|
+
return url || null;
|
|
9714
|
+
} catch (e) {
|
|
9715
|
+
const stderr = e?.stderr ?? "";
|
|
9716
|
+
const existing = stderr.match(/https:\/\/github\.com\/\S+\/pull\/\d+/)?.[0];
|
|
9717
|
+
if (existing) {
|
|
9718
|
+
this.log(`[finalizer] PR already exists: ${existing}`);
|
|
9719
|
+
return existing;
|
|
9720
|
+
}
|
|
9721
|
+
this.log(
|
|
9722
|
+
`[finalizer] gh pr create failed: ${stderr.split("\n")[0]?.trim() || e.message}`
|
|
9723
|
+
);
|
|
9724
|
+
return null;
|
|
9725
|
+
}
|
|
9726
|
+
}
|
|
9727
|
+
};
|
|
9728
|
+
function formatDuration(secs) {
|
|
9729
|
+
if (secs < 60) return `${Math.round(secs)}s`;
|
|
9730
|
+
const m = Math.floor(secs / 60);
|
|
9731
|
+
const s = Math.round(secs % 60);
|
|
9732
|
+
return `${m}:${s.toString().padStart(2, "0")}`;
|
|
9733
|
+
}
|
|
9734
|
+
function matchCommit(story, commits) {
|
|
9735
|
+
if (commits.length === 0) return null;
|
|
9736
|
+
const idPattern = new RegExp(`\\b${story.id}\\b`, "i");
|
|
9737
|
+
for (const c of commits) {
|
|
9738
|
+
if (idPattern.test(c.subject)) return c.sha;
|
|
9739
|
+
}
|
|
9740
|
+
if (!story.title) return null;
|
|
9741
|
+
const keywords = story.title.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length >= 4);
|
|
9742
|
+
if (keywords.length === 0) return null;
|
|
9743
|
+
let bestSha = null;
|
|
9744
|
+
let bestScore = 0;
|
|
9745
|
+
for (const c of commits) {
|
|
9746
|
+
const subj = c.subject.toLowerCase();
|
|
9747
|
+
const hits = keywords.reduce((n, k) => subj.includes(k) ? n + 1 : n, 0);
|
|
9748
|
+
if (hits > bestScore) {
|
|
9749
|
+
bestScore = hits;
|
|
9750
|
+
bestSha = c.sha;
|
|
9751
|
+
}
|
|
9752
|
+
}
|
|
9753
|
+
return bestScore >= 2 ? bestSha : null;
|
|
9754
|
+
}
|
|
9755
|
+
|
|
9308
9756
|
// ../baro-orchestrator/src/participants/librarian.ts
|
|
9309
9757
|
var EXPLORATION_TOOLS = /* @__PURE__ */ new Set([
|
|
9310
9758
|
"Read",
|
|
@@ -9651,9 +10099,9 @@ var StoryFactory = class extends Participant {
|
|
|
9651
10099
|
};
|
|
9652
10100
|
|
|
9653
10101
|
// ../baro-orchestrator/src/participants/surgeon.ts
|
|
9654
|
-
import { execFile as
|
|
9655
|
-
import { promisify as
|
|
9656
|
-
var
|
|
10102
|
+
import { execFile as execFile4 } from "child_process";
|
|
10103
|
+
import { promisify as promisify4 } from "util";
|
|
10104
|
+
var execFileAsync3 = promisify4(execFile4);
|
|
9657
10105
|
var SURGEON_SYSTEM_PROMPT = `You are the Surgeon \u2014 an autonomous planner that adapts a software-project
|
|
9658
10106
|
DAG when stories fail. Given:
|
|
9659
10107
|
1. A snapshot of the current PRD (project, story list with dependencies +
|
|
@@ -9765,7 +10213,7 @@ var Surgeon = class extends Participant {
|
|
|
9765
10213
|
const snap = this.opts.snapshot();
|
|
9766
10214
|
const prompt = buildSurgeonPrompt(snap, failure);
|
|
9767
10215
|
try {
|
|
9768
|
-
const { stdout } = await
|
|
10216
|
+
const { stdout } = await execFileAsync3(
|
|
9769
10217
|
this.opts.claudeBin,
|
|
9770
10218
|
[
|
|
9771
10219
|
"--print",
|
|
@@ -9926,6 +10374,15 @@ async function orchestrate(config) {
|
|
|
9926
10374
|
});
|
|
9927
10375
|
critic.join(env);
|
|
9928
10376
|
}
|
|
10377
|
+
const finalizer = useGit ? new Finalizer({
|
|
10378
|
+
cwd: config.cwd,
|
|
10379
|
+
prdPath: config.prdPath,
|
|
10380
|
+
onLog: (line) => emitTui && emit({ type: "story_log", id: "_finalizer", line })
|
|
10381
|
+
}) : null;
|
|
10382
|
+
if (finalizer) {
|
|
10383
|
+
finalizer.setEnvironment(env);
|
|
10384
|
+
finalizer.join(env);
|
|
10385
|
+
}
|
|
9929
10386
|
const conductor = new Conductor({
|
|
9930
10387
|
prdPath: config.prdPath,
|
|
9931
10388
|
cwd: config.cwd,
|
|
@@ -10006,6 +10463,7 @@ async function orchestrate(config) {
|
|
|
10006
10463
|
const summary = await conductor.done;
|
|
10007
10464
|
if (critic) await critic.idle();
|
|
10008
10465
|
if (surgeon) await surgeon.idle();
|
|
10466
|
+
if (finalizer) await finalizer.complete();
|
|
10009
10467
|
let filesCreated = 0;
|
|
10010
10468
|
let filesModified = 0;
|
|
10011
10469
|
if (useGit && baseSha) {
|
|
@@ -10081,6 +10539,14 @@ var BaroEventForwarder = class extends Participant {
|
|
|
10081
10539
|
this.handleCritique(item);
|
|
10082
10540
|
return;
|
|
10083
10541
|
}
|
|
10542
|
+
if (item instanceof FinalizeStartedItem) {
|
|
10543
|
+
emit({ type: "finalize_start" });
|
|
10544
|
+
return;
|
|
10545
|
+
}
|
|
10546
|
+
if (item instanceof PrCreatedItem) {
|
|
10547
|
+
emit({ type: "finalize_complete", pr_url: item.url });
|
|
10548
|
+
return;
|
|
10549
|
+
}
|
|
10084
10550
|
}
|
|
10085
10551
|
handleCoordination(item) {
|
|
10086
10552
|
emit({
|