jonah-fleet 1.5.0 → 1.6.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/CHANGELOG.md +51 -0
- package/dist/index.js +431 -108
- package/dist/lib/daemon.d.ts +33 -0
- package/dist/lib/daemon.d.ts.map +1 -1
- package/dist/lib/evals.d.ts +56 -1
- package/dist/lib/evals.d.ts.map +1 -1
- package/dist/lib/presets.d.ts +1 -1
- package/dist/lib/telemetry.d.ts +13 -0
- package/dist/lib/telemetry.d.ts.map +1 -1
- package/dist/lib/terminal-card.d.ts +14 -0
- package/dist/lib/terminal-card.d.ts.map +1 -1
- package/package.json +4 -4
- package/templates/evals/ambiguity-benchmark.json +92 -0
- package/templates/prompts/ORCHESTRATION.md +25 -9
- package/templates/prompts/_prompt-template.md +13 -1
- package/templates/prompts/autowork.md +33 -7
- package/templates/prompts/optimizer.md +3 -0
- package/templates/workflows/autowork-cron.yml +33 -0
- package/templates/workflows/trigger-autowork-manual.yml +2 -0
- package/templates/workflows/trigger-autowork-on-bug.yml +2 -0
- package/templates/workflows/trigger-autowork-on-merge.yml +2 -0
package/dist/index.js
CHANGED
|
@@ -92,7 +92,7 @@ var ROUTINE_TO_WORKFLOW_MAP = {
|
|
|
92
92
|
"product-planning": [],
|
|
93
93
|
"analytics-review": []
|
|
94
94
|
};
|
|
95
|
-
var FLEET_VERSION = "1.
|
|
95
|
+
var FLEET_VERSION = "1.6.0";
|
|
96
96
|
var SCHEMA_URL = "https://raw.githubusercontent.com/juliendurandeu/jonah-fleet/main/schema.json";
|
|
97
97
|
|
|
98
98
|
// src/lib/manifest.ts
|
|
@@ -1759,6 +1759,8 @@ function parseLogToTelemetry(content, options = {}) {
|
|
|
1759
1759
|
failureCategory = "infeasible";
|
|
1760
1760
|
} else if (lower.includes("conflict")) {
|
|
1761
1761
|
failureCategory = "merge_conflict";
|
|
1762
|
+
} else if (lower.includes("ambiguous") || lower.includes("needs-info") || lower.includes("clarification") || lower.includes("acceptance criteria")) {
|
|
1763
|
+
failureCategory = "ambiguous_spec";
|
|
1762
1764
|
}
|
|
1763
1765
|
}
|
|
1764
1766
|
const inputTokens = parseInt((metadata["input tokens"] || "0").replace(/[^\d]/g, ""), 10) || 0;
|
|
@@ -1783,6 +1785,16 @@ function parseLogToTelemetry(content, options = {}) {
|
|
|
1783
1785
|
}
|
|
1784
1786
|
}
|
|
1785
1787
|
const promptSha = metadata["prompt sha"];
|
|
1788
|
+
const lowerContent = content.toLowerCase();
|
|
1789
|
+
const ambiguityGateTriggered = lowerContent.includes("ambiguity & missing acceptance criteria gate") || lowerContent.includes("ambiguity gate") || lowerContent.includes("clarifications needed before implementation") || lowerContent.includes("needs-info") && (lowerContent.includes("clarification") || lowerContent.includes("questions"));
|
|
1790
|
+
const needsInfoApplied = lowerContent.includes("needs-info");
|
|
1791
|
+
let questionsAskedCount;
|
|
1792
|
+
if (ambiguityGateTriggered) {
|
|
1793
|
+
const questionLines = content.split("\n").filter(
|
|
1794
|
+
(line) => (line.trim().startsWith("1.") || line.trim().startsWith("2.") || line.trim().startsWith("3.") || line.trim().startsWith("-")) && line.includes("?")
|
|
1795
|
+
);
|
|
1796
|
+
questionsAskedCount = questionLines.length > 0 ? questionLines.length : 2;
|
|
1797
|
+
}
|
|
1786
1798
|
return {
|
|
1787
1799
|
schemaVersion: "1.0.0",
|
|
1788
1800
|
routine,
|
|
@@ -1800,7 +1812,10 @@ function parseLogToTelemetry(content, options = {}) {
|
|
|
1800
1812
|
durationSeconds,
|
|
1801
1813
|
iterationsUsed,
|
|
1802
1814
|
maxIterations,
|
|
1803
|
-
promptSha
|
|
1815
|
+
promptSha,
|
|
1816
|
+
ambiguityGateTriggered: ambiguityGateTriggered || void 0,
|
|
1817
|
+
questionsAskedCount,
|
|
1818
|
+
needsInfoApplied: needsInfoApplied || void 0
|
|
1804
1819
|
};
|
|
1805
1820
|
}
|
|
1806
1821
|
function checkWeeklyBudgetLimit(usedTokens, ceilingTokens = GLOBAL_WEEKLY_TOKEN_BUDGET) {
|
|
@@ -1859,7 +1874,10 @@ function aggregateFleetTelemetry(summaries, options = {}) {
|
|
|
1859
1874
|
failureCount: 0,
|
|
1860
1875
|
bouncedCount: 0,
|
|
1861
1876
|
avgDurationSeconds: 0,
|
|
1862
|
-
avgIterationsUsed: 0
|
|
1877
|
+
avgIterationsUsed: 0,
|
|
1878
|
+
ambiguityGatesTriggered: 0,
|
|
1879
|
+
questionsAskedCount: 0,
|
|
1880
|
+
needsInfoAppliedCount: 0
|
|
1863
1881
|
};
|
|
1864
1882
|
}
|
|
1865
1883
|
const r = byRoutine[s.routine];
|
|
@@ -1877,6 +1895,9 @@ function aggregateFleetTelemetry(summaries, options = {}) {
|
|
|
1877
1895
|
if (s.iterationsUsed) {
|
|
1878
1896
|
r.avgIterationsUsed = (r.avgIterationsUsed * (r.runCount - 1) + s.iterationsUsed) / r.runCount;
|
|
1879
1897
|
}
|
|
1898
|
+
if (s.ambiguityGateTriggered) r.ambiguityGatesTriggered++;
|
|
1899
|
+
if (s.questionsAskedCount) r.questionsAskedCount += s.questionsAskedCount;
|
|
1900
|
+
if (s.needsInfoApplied) r.needsInfoAppliedCount++;
|
|
1880
1901
|
if (!byRepository[s.repository]) {
|
|
1881
1902
|
byRepository[s.repository] = {
|
|
1882
1903
|
repository: s.repository,
|
|
@@ -1894,6 +1915,21 @@ function aggregateFleetTelemetry(summaries, options = {}) {
|
|
|
1894
1915
|
if (s.result === "SUCCESS") repoObj.successCount++;
|
|
1895
1916
|
else if (s.result === "FAILURE") repoObj.failureCount++;
|
|
1896
1917
|
}
|
|
1918
|
+
let totalAmbiguityGatesTriggered = 0;
|
|
1919
|
+
let totalQuestionsAsked = 0;
|
|
1920
|
+
let totalNeedsInfoApplied = 0;
|
|
1921
|
+
for (const s of summaries) {
|
|
1922
|
+
if (s.ambiguityGateTriggered) totalAmbiguityGatesTriggered++;
|
|
1923
|
+
if (s.questionsAskedCount) totalQuestionsAsked += s.questionsAskedCount;
|
|
1924
|
+
if (s.needsInfoApplied) totalNeedsInfoApplied++;
|
|
1925
|
+
}
|
|
1926
|
+
const estimatedTokensSaved = totalAmbiguityGatesTriggered * 5e4;
|
|
1927
|
+
const ambiguity = {
|
|
1928
|
+
totalAmbiguityGatesTriggered,
|
|
1929
|
+
totalQuestionsAsked,
|
|
1930
|
+
needsInfoAppliedCount: totalNeedsInfoApplied,
|
|
1931
|
+
estimatedTokensSaved
|
|
1932
|
+
};
|
|
1897
1933
|
const totalTokens = totalInputTokens + totalOutputTokens;
|
|
1898
1934
|
const budget = checkWeeklyBudgetLimit(totalTokens, budgetCeiling);
|
|
1899
1935
|
return {
|
|
@@ -1910,6 +1946,7 @@ function aggregateFleetTelemetry(summaries, options = {}) {
|
|
|
1910
1946
|
byRoutine,
|
|
1911
1947
|
byRepository,
|
|
1912
1948
|
failureCategories,
|
|
1949
|
+
ambiguity,
|
|
1913
1950
|
events: summaries
|
|
1914
1951
|
};
|
|
1915
1952
|
}
|
|
@@ -2046,6 +2083,20 @@ function renderTelemetryDashboard(telemetry, options = {}) {
|
|
|
2046
2083
|
);
|
|
2047
2084
|
}
|
|
2048
2085
|
}
|
|
2086
|
+
const amb = telemetry.ambiguity;
|
|
2087
|
+
lines.push("\n" + pc7.bold("\u2753 Inquisitive Stance & Ambiguity Gate Signals:"));
|
|
2088
|
+
lines.push(
|
|
2089
|
+
` \u2022 Ambiguity Gate Triggers: ${pc7.bold(amb.totalAmbiguityGatesTriggered.toString())} runs stopped to request clarification`
|
|
2090
|
+
);
|
|
2091
|
+
lines.push(
|
|
2092
|
+
` \u2022 Clarifying Questions Posed: ${pc7.bold(amb.totalQuestionsAsked.toString())} targeted questions`
|
|
2093
|
+
);
|
|
2094
|
+
lines.push(
|
|
2095
|
+
` \u2022 Needs-Info Labels Applied: ${pc7.bold(amb.needsInfoAppliedCount.toString())}`
|
|
2096
|
+
);
|
|
2097
|
+
lines.push(
|
|
2098
|
+
` \u2022 Est. Wasted Tokens Averted: ~${pc7.bold(pc7.green(formatTokens2(amb.estimatedTokensSaved)))} tokens (avoided speculative builds)`
|
|
2099
|
+
);
|
|
2049
2100
|
const failKeys = Object.keys(telemetry.failureCategories);
|
|
2050
2101
|
if (failKeys.length > 0) {
|
|
2051
2102
|
lines.push("\n" + pc7.bold(pc7.red("\u26A0\uFE0F Failure Categories Breakdown:")));
|
|
@@ -2289,7 +2340,60 @@ async function cleanupStaleWorktrees(repoRoot) {
|
|
|
2289
2340
|
// src/lib/terminal-card.ts
|
|
2290
2341
|
import fs11 from "fs";
|
|
2291
2342
|
import path11 from "path";
|
|
2343
|
+
import { execFileSync } from "child_process";
|
|
2292
2344
|
import pc9 from "picocolors";
|
|
2345
|
+
function stripAnsi(text) {
|
|
2346
|
+
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2347
|
+
}
|
|
2348
|
+
function wrapText(text, maxWidth) {
|
|
2349
|
+
if (maxWidth <= 0) return [text];
|
|
2350
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
2351
|
+
if (words.length === 0) return [];
|
|
2352
|
+
const lines = [];
|
|
2353
|
+
let current = "";
|
|
2354
|
+
for (const word of words) {
|
|
2355
|
+
if (!current) {
|
|
2356
|
+
current = word;
|
|
2357
|
+
} else {
|
|
2358
|
+
const proposed = current + " " + word;
|
|
2359
|
+
if (stripAnsi(proposed).length <= maxWidth) {
|
|
2360
|
+
current = proposed;
|
|
2361
|
+
} else {
|
|
2362
|
+
lines.push(current);
|
|
2363
|
+
current = word;
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
if (current) lines.push(current);
|
|
2368
|
+
return lines;
|
|
2369
|
+
}
|
|
2370
|
+
function fetchTargetTitle(repoRoot, target) {
|
|
2371
|
+
try {
|
|
2372
|
+
const prMatch = target.match(/PR\s*#?(\d+)/i);
|
|
2373
|
+
if (prMatch) {
|
|
2374
|
+
const stdout = execFileSync("gh", ["pr", "view", prMatch[1], "--json", "title", "-q", ".title"], {
|
|
2375
|
+
cwd: repoRoot,
|
|
2376
|
+
encoding: "utf8",
|
|
2377
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2378
|
+
timeout: 4e3
|
|
2379
|
+
});
|
|
2380
|
+
return stdout.trim() || null;
|
|
2381
|
+
}
|
|
2382
|
+
const issueMatch = target.match(/Issue\s*#?(\d+)/i);
|
|
2383
|
+
if (issueMatch) {
|
|
2384
|
+
const stdout = execFileSync("gh", ["issue", "view", issueMatch[1], "--json", "title", "-q", ".title"], {
|
|
2385
|
+
cwd: repoRoot,
|
|
2386
|
+
encoding: "utf8",
|
|
2387
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2388
|
+
timeout: 4e3
|
|
2389
|
+
});
|
|
2390
|
+
return stdout.trim() || null;
|
|
2391
|
+
}
|
|
2392
|
+
} catch {
|
|
2393
|
+
return null;
|
|
2394
|
+
}
|
|
2395
|
+
return null;
|
|
2396
|
+
}
|
|
2293
2397
|
function sanitizeWorktreePaths(text) {
|
|
2294
2398
|
let cleaned = text.replace(/file:\/\/\/[^\s"'()]+?\/\.jonah-fleet\/worktrees\/[^/\s"'()]+\//g, "");
|
|
2295
2399
|
cleaned = cleaned.replace(/(?:^|[\s"'(`[])(?:\/[^\s"'()]+?)?\.jonah-fleet\/worktrees\/[^/\s"'()]+\//g, (match) => {
|
|
@@ -2300,7 +2404,7 @@ function sanitizeWorktreePaths(text) {
|
|
|
2300
2404
|
return cleaned;
|
|
2301
2405
|
}
|
|
2302
2406
|
function extractExecutionSummary(output) {
|
|
2303
|
-
const summaryHeaderRegex =
|
|
2407
|
+
const summaryHeaderRegex = /#{1,3}\s+([A-Za-z0-9\s_-]*?(?:Execution|Review|Autowork)\s+Summary[\s\S]*)/i;
|
|
2304
2408
|
const match = output.match(summaryHeaderRegex);
|
|
2305
2409
|
if (!match) return null;
|
|
2306
2410
|
let summary = match[1].trim();
|
|
@@ -2308,8 +2412,8 @@ function extractExecutionSummary(output) {
|
|
|
2308
2412
|
"\u2713 Local peer-review completed",
|
|
2309
2413
|
"\u2713 Local autowork completed",
|
|
2310
2414
|
"\u2713 Local agent session",
|
|
2311
|
-
"
|
|
2312
|
-
"
|
|
2415
|
+
"Peer Review Watchdog:",
|
|
2416
|
+
"Autowork Backlog Scan:"
|
|
2313
2417
|
];
|
|
2314
2418
|
for (const sep of trailingSeparators) {
|
|
2315
2419
|
const idx = summary.indexOf(sep);
|
|
@@ -2317,6 +2421,10 @@ function extractExecutionSummary(output) {
|
|
|
2317
2421
|
summary = summary.slice(0, idx).trim();
|
|
2318
2422
|
}
|
|
2319
2423
|
}
|
|
2424
|
+
const timestampMatch = summary.match(/\n\s*\[\d{1,2}:\d{2}:\d{2}\s*(?:AM|PM)?\][\s\S]*/);
|
|
2425
|
+
if (timestampMatch && timestampMatch.index !== void 0) {
|
|
2426
|
+
summary = summary.slice(0, timestampMatch.index).trim();
|
|
2427
|
+
}
|
|
2320
2428
|
return sanitizeWorktreePaths(summary);
|
|
2321
2429
|
}
|
|
2322
2430
|
function findLatestRunLog(repoRoot, routine) {
|
|
@@ -2337,57 +2445,73 @@ function parseRunLog(logContent) {
|
|
|
2337
2445
|
actions: []
|
|
2338
2446
|
};
|
|
2339
2447
|
const lines = logContent.split("\n");
|
|
2340
|
-
|
|
2341
|
-
const trimmed = line.trim();
|
|
2342
|
-
if (trimmed.startsWith("|") && trimmed.includes("|")) {
|
|
2343
|
-
const parts = trimmed.split("|").map((p) => p.trim()).filter(Boolean);
|
|
2344
|
-
if (parts.length >= 2) {
|
|
2345
|
-
const key = parts[0].toLowerCase();
|
|
2346
|
-
const value = parts[1].replace(/`/g, "");
|
|
2347
|
-
if (key.includes("routine")) summary.routine = value;
|
|
2348
|
-
if (key.includes("target pr") || key.includes("target issue")) summary.target = value;
|
|
2349
|
-
if (key.includes("decision")) summary.decision = value;
|
|
2350
|
-
if (key.includes("result")) summary.result = value;
|
|
2351
|
-
if (key.includes("duration")) summary.duration = value;
|
|
2352
|
-
}
|
|
2353
|
-
}
|
|
2354
|
-
}
|
|
2448
|
+
let inMetadata = false;
|
|
2355
2449
|
let inDoD = false;
|
|
2356
2450
|
let inFindings = false;
|
|
2357
2451
|
let inActions = false;
|
|
2358
2452
|
for (const line of lines) {
|
|
2359
2453
|
const trimmed = line.trim();
|
|
2360
|
-
if (trimmed.startsWith("##
|
|
2454
|
+
if (trimmed.startsWith("## Metadata")) {
|
|
2455
|
+
inMetadata = true;
|
|
2456
|
+
inDoD = false;
|
|
2457
|
+
inFindings = false;
|
|
2458
|
+
inActions = false;
|
|
2459
|
+
continue;
|
|
2460
|
+
} else if (trimmed.startsWith("## Definition of Done")) {
|
|
2461
|
+
inMetadata = false;
|
|
2361
2462
|
inDoD = true;
|
|
2362
2463
|
inFindings = false;
|
|
2363
2464
|
inActions = false;
|
|
2364
2465
|
continue;
|
|
2365
2466
|
} else if (trimmed.startsWith("## Code Review Findings") || trimmed.startsWith("## Findings")) {
|
|
2467
|
+
inMetadata = false;
|
|
2366
2468
|
inDoD = false;
|
|
2367
2469
|
inFindings = true;
|
|
2368
2470
|
inActions = false;
|
|
2369
2471
|
continue;
|
|
2370
|
-
} else if (trimmed.startsWith("## Execution Trace") || trimmed.startsWith("### Actions Taken")) {
|
|
2472
|
+
} else if (trimmed.startsWith("## Execution Trace") || trimmed.startsWith("### Actions Taken") || trimmed.startsWith("## Actions Taken") || trimmed.startsWith("## Artifacts")) {
|
|
2473
|
+
inMetadata = false;
|
|
2371
2474
|
inDoD = false;
|
|
2372
2475
|
inFindings = false;
|
|
2373
|
-
inActions =
|
|
2476
|
+
inActions = trimmed.startsWith("### Actions Taken") || trimmed.startsWith("## Actions Taken");
|
|
2374
2477
|
continue;
|
|
2375
2478
|
} else if (trimmed.startsWith("## ")) {
|
|
2479
|
+
inMetadata = false;
|
|
2376
2480
|
inDoD = false;
|
|
2377
2481
|
inFindings = false;
|
|
2378
2482
|
inActions = false;
|
|
2379
2483
|
}
|
|
2484
|
+
if (inMetadata && trimmed.startsWith("|") && trimmed.includes("|")) {
|
|
2485
|
+
const parts = trimmed.split("|").map((p) => p.trim()).filter(Boolean);
|
|
2486
|
+
if (parts.length >= 2) {
|
|
2487
|
+
const key = parts[0].toLowerCase();
|
|
2488
|
+
const value = parts[1].replace(/[`*]/g, "").trim();
|
|
2489
|
+
if (key === "routine") summary.routine = value;
|
|
2490
|
+
if (key === "target pr" || key === "target issue" || key === "target") {
|
|
2491
|
+
summary.target = value;
|
|
2492
|
+
}
|
|
2493
|
+
if (key === "decision") summary.decision = value;
|
|
2494
|
+
if (key === "result") summary.result = value;
|
|
2495
|
+
if (key === "duration") summary.duration = value;
|
|
2496
|
+
if (key === "title" || key === "pr title" || key === "issue title") summary.title = value;
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2380
2499
|
if (inDoD && trimmed.startsWith("|") && !trimmed.includes("Criterion") && !trimmed.includes("---")) {
|
|
2381
2500
|
const parts = trimmed.split("|").map((p) => p.trim()).filter(Boolean);
|
|
2382
2501
|
if (parts.length >= 2) {
|
|
2383
2502
|
const criterion = parts[0];
|
|
2384
|
-
const
|
|
2385
|
-
const
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2503
|
+
const metRaw = parts[1].toUpperCase();
|
|
2504
|
+
const met = metRaw === "YES" || metRaw === "PASS";
|
|
2505
|
+
const evidence = parts[2] ? parts[2].trim() : "";
|
|
2506
|
+
const isConditionalScan = criterion.toLowerCase().startsWith("if in scan mode and no eligible");
|
|
2507
|
+
const isNA = evidence.toLowerCase().includes("n/a") || metRaw === "N/A";
|
|
2508
|
+
if (!isConditionalScan && !isNA) {
|
|
2509
|
+
summary.passes?.push({
|
|
2510
|
+
name: criterion,
|
|
2511
|
+
status: met ? "pass" : "fail",
|
|
2512
|
+
detail: evidence ? ` (${evidence})` : ""
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2391
2515
|
}
|
|
2392
2516
|
}
|
|
2393
2517
|
if (inActions && (trimmed.startsWith("- ") || trimmed.startsWith("* "))) {
|
|
@@ -2398,9 +2522,15 @@ function parseRunLog(logContent) {
|
|
|
2398
2522
|
}
|
|
2399
2523
|
function detectActivePhase(chunk, currentPhase = "Executing routine") {
|
|
2400
2524
|
const lower = chunk.toLowerCase();
|
|
2525
|
+
if (lower.includes("\u{1F512} addressing review findings") || lower.includes("addressing review findings by")) {
|
|
2526
|
+
return "Claimed bounced PR, addressing review findings";
|
|
2527
|
+
}
|
|
2401
2528
|
if (lower.includes("\u{1F512} claimed") || lower.includes("claimed by local autowork") || lower.includes("claimed by autowork")) {
|
|
2402
2529
|
return "Claimed target issue, starting implementation";
|
|
2403
2530
|
}
|
|
2531
|
+
if (lower.includes("addressing review findings") || lower.includes("fixing review findings")) {
|
|
2532
|
+
return "Claimed bounced PR, addressing review findings";
|
|
2533
|
+
}
|
|
2404
2534
|
if (lower.includes("starting review (round")) {
|
|
2405
2535
|
return "Claimed review window, starting review passes";
|
|
2406
2536
|
}
|
|
@@ -2430,16 +2560,18 @@ function detectClaimedIssue(chunk) {
|
|
|
2430
2560
|
return null;
|
|
2431
2561
|
}
|
|
2432
2562
|
function detectClaimedPR(chunk) {
|
|
2563
|
+
const findingMatch = chunk.match(/(?:addressing\s+review\s+findings|fixing\s+review\s+findings)[^\n#]*?#(\d+)/i);
|
|
2564
|
+
if (findingMatch) return `PR #${findingMatch[1]}`;
|
|
2433
2565
|
const reviewMatch = chunk.match(/Starting\s+review[^\n#]*?#(\d+)/i);
|
|
2434
2566
|
if (reviewMatch) return `PR #${reviewMatch[1]}`;
|
|
2435
2567
|
const prMatch = chunk.match(/(?:selected|target|reviewing)\s+(?:target\s+)?PR:?\s*\[?PR\s*#?(\d+)/i);
|
|
2436
2568
|
if (prMatch) return `PR #${prMatch[1]}`;
|
|
2437
|
-
const ghPrMatch = chunk.match(/gh\s+pr\s+(?:view|diff|checkout|review)\s+(\d+)/i);
|
|
2569
|
+
const ghPrMatch = chunk.match(/gh\s+pr\s+(?:view|diff|checkout|review|edit|ready)\s+(\d+)/i);
|
|
2438
2570
|
if (ghPrMatch) return `PR #${ghPrMatch[1]}`;
|
|
2439
2571
|
return null;
|
|
2440
2572
|
}
|
|
2441
2573
|
function renderSummaryCard(options) {
|
|
2442
|
-
const width = Math.min(Math.max((process.stdout.columns || 80) - 4,
|
|
2574
|
+
const width = Math.min(Math.max((process.stdout.columns || 80) - 4, 64), 90);
|
|
2443
2575
|
const horizontal = "\u2500".repeat(width - 2);
|
|
2444
2576
|
const rawSummary = options.output ? extractExecutionSummary(options.output) : null;
|
|
2445
2577
|
let parsedFromLog = null;
|
|
@@ -2455,7 +2587,28 @@ function renderSummaryCard(options) {
|
|
|
2455
2587
|
}
|
|
2456
2588
|
}
|
|
2457
2589
|
let target = options.pr ? `PR #${options.pr}` : options.issue ? `Issue #${options.issue}` : "";
|
|
2458
|
-
if (!target && parsedFromLog?.target
|
|
2590
|
+
if (!target && parsedFromLog?.target && parsedFromLog.target.toUpperCase() !== "YES") {
|
|
2591
|
+
const rawTarget = parsedFromLog.target;
|
|
2592
|
+
target = rawTarget.startsWith("#") ? options.routine === "peer-review" ? `PR ${rawTarget}` : `Issue ${rawTarget}` : rawTarget;
|
|
2593
|
+
}
|
|
2594
|
+
if (!target && options.output) {
|
|
2595
|
+
const targetMatch = options.output.match(/Selected\s+Target\s+PR:?\s*\[?PR\s*#?(\d+)\]?/i) || options.output.match(/Starting\s+review[^\n#]*?#(\d+)/i) || options.output.match(/Target(?:ing)?\s+(?:issue|PR)\s*#?(\d+)/i) || options.output.match(/Candidate\s+issue\s*#?(\d+)/i);
|
|
2596
|
+
if (targetMatch) {
|
|
2597
|
+
target = options.routine === "peer-review" ? `PR #${targetMatch[1]}` : `Issue #${targetMatch[1]}`;
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
let title = options.title || parsedFromLog?.title || "";
|
|
2601
|
+
if (!title && rawSummary) {
|
|
2602
|
+
const titleMatch = rawSummary.match(/\[PR\s*#?\d+\s*\((`?[^`)]+`?)\)\]/i) || rawSummary.match(/Selected\s+Target\s+PR:?\s*\[.*?\]\([^)]+\)\s*\(([^)]+)\)/i) || rawSummary.match(/PR\s*#?\d+[:\s]+`?([^`\n]+)`?/i);
|
|
2603
|
+
if (titleMatch) title = titleMatch[1].replace(/[`*]/g, "").trim();
|
|
2604
|
+
}
|
|
2605
|
+
if (!title && options.output) {
|
|
2606
|
+
const titleMatch = options.output.match(/Selected\s+Target\s+PR:?\s*\[PR\s*#?\d+\s*\((`?[^`)]+`?)\)\]/i) || options.output.match(/Selected\s+candidate\s+issue\s*#?\d+[:\s]+`?([^`\n]+)`?/i);
|
|
2607
|
+
if (titleMatch) title = titleMatch[1].replace(/[`*]/g, "").trim();
|
|
2608
|
+
}
|
|
2609
|
+
if (!title && options.repoRoot && target) {
|
|
2610
|
+
title = fetchTargetTitle(options.repoRoot, target) || "";
|
|
2611
|
+
}
|
|
2459
2612
|
let decision = parsedFromLog?.decision || "";
|
|
2460
2613
|
if (!decision && rawSummary) {
|
|
2461
2614
|
const decisionMatch = rawSummary.match(/\*\*Final Action\*\*:\s*([^\n]+)/i);
|
|
@@ -2464,15 +2617,26 @@ function renderSummaryCard(options) {
|
|
|
2464
2617
|
const durationStr = options.durationMs ? `${Math.round(options.durationMs / 1e3)}s` : parsedFromLog?.duration || "";
|
|
2465
2618
|
const lines = [];
|
|
2466
2619
|
lines.push(pc9.cyan(`\u250C${horizontal}\u2510`));
|
|
2467
|
-
const
|
|
2620
|
+
const headerParts = [pc9.bold(pc9.white(options.routine.toUpperCase()))];
|
|
2621
|
+
if (target) headerParts.push(pc9.yellow(target));
|
|
2622
|
+
if (durationStr) headerParts.push(pc9.dim(`(${durationStr})`));
|
|
2623
|
+
const headerContent = headerParts.join(" \xB7 ");
|
|
2624
|
+
const headerPlain = stripAnsi(headerContent);
|
|
2468
2625
|
lines.push(
|
|
2469
|
-
pc9.cyan("\u2502") + ` ${
|
|
2470
|
-
Math.max(
|
|
2471
|
-
1,
|
|
2472
|
-
width - 4 - options.routine.length - target.length - (durationStr ? durationStr.length + 3 : 0)
|
|
2473
|
-
)
|
|
2474
|
-
) + pc9.cyan("\u2502")
|
|
2626
|
+
pc9.cyan("\u2502") + ` ${headerContent}` + " ".repeat(Math.max(1, width - 3 - headerPlain.length)) + pc9.cyan("\u2502")
|
|
2475
2627
|
);
|
|
2628
|
+
if (title) {
|
|
2629
|
+
const titlePrefix = " Title: ";
|
|
2630
|
+
const wrappedTitle = wrapText(title, width - 4 - titlePrefix.length);
|
|
2631
|
+
for (let i = 0; i < wrappedTitle.length; i++) {
|
|
2632
|
+
const prefix = i === 0 ? pc9.dim(titlePrefix) : " ".repeat(titlePrefix.length);
|
|
2633
|
+
const text = wrappedTitle[i];
|
|
2634
|
+
const plainLen = titlePrefix.length + stripAnsi(text).length;
|
|
2635
|
+
lines.push(
|
|
2636
|
+
pc9.cyan("\u2502") + ` ${prefix}${pc9.white(pc9.bold(text))}` + " ".repeat(Math.max(1, width - 3 - plainLen)) + pc9.cyan("\u2502")
|
|
2637
|
+
);
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2476
2640
|
if (decision) {
|
|
2477
2641
|
let decisionBadge = pc9.green(`\u2714 ${decision}`);
|
|
2478
2642
|
if (/bounce|draft|reject|fail/i.test(decision)) {
|
|
@@ -2480,8 +2644,9 @@ function renderSummaryCard(options) {
|
|
|
2480
2644
|
} else if (/escalat/i.test(decision)) {
|
|
2481
2645
|
decisionBadge = pc9.red(`\u{1F6A8} ${decision}`);
|
|
2482
2646
|
}
|
|
2647
|
+
const decisionPlain = ` Action: ${decision}`;
|
|
2483
2648
|
lines.push(
|
|
2484
|
-
pc9.cyan("\u2502") + ` Action: ${decisionBadge}` + " ".repeat(Math.max(1, width -
|
|
2649
|
+
pc9.cyan("\u2502") + ` Action: ${decisionBadge}` + " ".repeat(Math.max(1, width - 3 - decisionPlain.length)) + pc9.cyan("\u2502")
|
|
2485
2650
|
);
|
|
2486
2651
|
}
|
|
2487
2652
|
lines.push(pc9.cyan(`\u251C${horizontal}\u2524`));
|
|
@@ -2503,22 +2668,36 @@ function renderSummaryCard(options) {
|
|
|
2503
2668
|
} else if (line.startsWith("- ") || line.startsWith("* ")) {
|
|
2504
2669
|
const item = sanitizeWorktreePaths(line.slice(2)).trim();
|
|
2505
2670
|
const formatted = item.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*([^*]+)\*\*/g, (_, text) => pc9.bold(text)).replace(/`([^`]+)`/g, (_, code) => pc9.yellow(code));
|
|
2506
|
-
const
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2671
|
+
const wrapped = wrapText(formatted, width - 8);
|
|
2672
|
+
for (let i = 0; i < wrapped.length; i++) {
|
|
2673
|
+
const wLine = wrapped[i];
|
|
2674
|
+
const wPlain = stripAnsi(wLine);
|
|
2675
|
+
if (i === 0) {
|
|
2676
|
+
lines.push(
|
|
2677
|
+
pc9.cyan("\u2502") + ` \u2022 ${wLine}` + " ".repeat(Math.max(1, width - 5 - wPlain.length)) + pc9.cyan("\u2502")
|
|
2678
|
+
);
|
|
2679
|
+
} else {
|
|
2680
|
+
lines.push(
|
|
2681
|
+
pc9.cyan("\u2502") + ` ${wLine}` + " ".repeat(Math.max(1, width - 5 - wPlain.length)) + pc9.cyan("\u2502")
|
|
2682
|
+
);
|
|
2683
|
+
}
|
|
2512
2684
|
}
|
|
2513
2685
|
} else if (/^[0-9]+\.\s+/.test(line)) {
|
|
2514
2686
|
const item = sanitizeWorktreePaths(line.replace(/^[0-9]+\.\s+/, "")).trim();
|
|
2515
2687
|
const formatted = item.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*([^*]+)\*\*/g, (_, text) => pc9.bold(text)).replace(/`([^`]+)`/g, (_, code) => pc9.yellow(code));
|
|
2516
|
-
const
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2688
|
+
const wrapped = wrapText(formatted, width - 8);
|
|
2689
|
+
for (let i = 0; i < wrapped.length; i++) {
|
|
2690
|
+
const wLine = wrapped[i];
|
|
2691
|
+
const wPlain = stripAnsi(wLine);
|
|
2692
|
+
if (i === 0) {
|
|
2693
|
+
lines.push(
|
|
2694
|
+
pc9.cyan("\u2502") + ` \u2714 ${wLine}` + " ".repeat(Math.max(1, width - 5 - wPlain.length)) + pc9.cyan("\u2502")
|
|
2695
|
+
);
|
|
2696
|
+
} else {
|
|
2697
|
+
lines.push(
|
|
2698
|
+
pc9.cyan("\u2502") + ` ${wLine}` + " ".repeat(Math.max(1, width - 5 - wPlain.length)) + pc9.cyan("\u2502")
|
|
2699
|
+
);
|
|
2700
|
+
}
|
|
2522
2701
|
}
|
|
2523
2702
|
}
|
|
2524
2703
|
}
|
|
@@ -2526,10 +2705,45 @@ function renderSummaryCard(options) {
|
|
|
2526
2705
|
lines.push(pc9.cyan("\u2502") + ` ${pc9.bold("Verification Passes:")}` + " ".repeat(Math.max(1, width - 23)) + pc9.cyan("\u2502"));
|
|
2527
2706
|
for (const pass of parsedFromLog.passes.slice(0, 6)) {
|
|
2528
2707
|
const icon = pass.status === "pass" ? pc9.green("\u2714") : pc9.red("\u2716");
|
|
2529
|
-
|
|
2530
|
-
const
|
|
2531
|
-
|
|
2532
|
-
|
|
2708
|
+
let criterionName = pass.name;
|
|
2709
|
+
const colonIdx = criterionName.indexOf(":");
|
|
2710
|
+
if (colonIdx > 10 && colonIdx < 40) {
|
|
2711
|
+
criterionName = criterionName.slice(0, colonIdx);
|
|
2712
|
+
}
|
|
2713
|
+
const passText = `${criterionName}${pass.detail || ""}`;
|
|
2714
|
+
const wrapped = wrapText(passText, width - 8);
|
|
2715
|
+
for (let i = 0; i < wrapped.length; i++) {
|
|
2716
|
+
const wLine = wrapped[i];
|
|
2717
|
+
const wPlain = stripAnsi(wLine);
|
|
2718
|
+
if (i === 0) {
|
|
2719
|
+
lines.push(
|
|
2720
|
+
pc9.cyan("\u2502") + ` ${icon} ${wLine}` + " ".repeat(Math.max(1, width - 5 - wPlain.length)) + pc9.cyan("\u2502")
|
|
2721
|
+
);
|
|
2722
|
+
} else {
|
|
2723
|
+
lines.push(
|
|
2724
|
+
pc9.cyan("\u2502") + ` ${pc9.dim(wLine)}` + " ".repeat(Math.max(1, width - 5 - wPlain.length)) + pc9.cyan("\u2502")
|
|
2725
|
+
);
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
if (parsedFromLog.actions && parsedFromLog.actions.length > 0) {
|
|
2730
|
+
lines.push(pc9.cyan("\u2502") + ` ${pc9.bold("Actions Taken:")}` + " ".repeat(Math.max(1, width - 16)) + pc9.cyan("\u2502"));
|
|
2731
|
+
for (const action of parsedFromLog.actions.slice(0, 4)) {
|
|
2732
|
+
const wrapped = wrapText(action, width - 8);
|
|
2733
|
+
for (let i = 0; i < wrapped.length; i++) {
|
|
2734
|
+
const wLine = wrapped[i];
|
|
2735
|
+
const wPlain = stripAnsi(wLine);
|
|
2736
|
+
if (i === 0) {
|
|
2737
|
+
lines.push(
|
|
2738
|
+
pc9.cyan("\u2502") + ` \u2022 ${wLine}` + " ".repeat(Math.max(1, width - 5 - wPlain.length)) + pc9.cyan("\u2502")
|
|
2739
|
+
);
|
|
2740
|
+
} else {
|
|
2741
|
+
lines.push(
|
|
2742
|
+
pc9.cyan("\u2502") + ` ${wLine}` + " ".repeat(Math.max(1, width - 5 - wPlain.length)) + pc9.cyan("\u2502")
|
|
2743
|
+
);
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2533
2747
|
}
|
|
2534
2748
|
}
|
|
2535
2749
|
if (parsedFromLog?.logPath) {
|
|
@@ -2972,18 +3186,28 @@ function isDaemonRunning(repoRoot) {
|
|
|
2972
3186
|
return false;
|
|
2973
3187
|
}
|
|
2974
3188
|
}
|
|
2975
|
-
|
|
3189
|
+
function filterReviewablePRs(prs) {
|
|
3190
|
+
return (prs || []).filter(
|
|
3191
|
+
(pr) => pr && typeof pr.number === "number" && !pr.headRefName?.startsWith("release-please--") && !pr.title?.startsWith("chore(main): release")
|
|
3192
|
+
);
|
|
3193
|
+
}
|
|
3194
|
+
async function getOpenReviewablePRs(repoRoot) {
|
|
2976
3195
|
try {
|
|
2977
3196
|
const { stdout } = await execFileAsync3(
|
|
2978
3197
|
"gh",
|
|
2979
|
-
["pr", "list", "--state", "open", "--draft=false", "--json", "number
|
|
3198
|
+
["pr", "list", "--state", "open", "--draft=false", "--json", "number,headRefName,title"],
|
|
2980
3199
|
{ cwd: repoRoot }
|
|
2981
3200
|
);
|
|
2982
|
-
|
|
3201
|
+
const prs = JSON.parse(stdout);
|
|
3202
|
+
return filterReviewablePRs(prs);
|
|
2983
3203
|
} catch {
|
|
2984
|
-
return
|
|
3204
|
+
return [];
|
|
2985
3205
|
}
|
|
2986
3206
|
}
|
|
3207
|
+
async function countOpenReadyPRs(repoRoot) {
|
|
3208
|
+
const prs = await getOpenReviewablePRs(repoRoot);
|
|
3209
|
+
return prs.length;
|
|
3210
|
+
}
|
|
2987
3211
|
async function startBackgroundDaemon(repoRoot, options = {}) {
|
|
2988
3212
|
if (isDaemonRunning(repoRoot)) {
|
|
2989
3213
|
const existing = readDaemonState(repoRoot);
|
|
@@ -3043,6 +3267,103 @@ async function stopDaemon(repoRoot) {
|
|
|
3043
3267
|
return false;
|
|
3044
3268
|
}
|
|
3045
3269
|
}
|
|
3270
|
+
async function drainReviewQueue(drainOptions) {
|
|
3271
|
+
const {
|
|
3272
|
+
repoRoot,
|
|
3273
|
+
state,
|
|
3274
|
+
options = {},
|
|
3275
|
+
isStopping = () => false,
|
|
3276
|
+
clearTicker,
|
|
3277
|
+
getPRs = getOpenReviewablePRs,
|
|
3278
|
+
runRoutine = runLocalRoutine,
|
|
3279
|
+
onAttempted
|
|
3280
|
+
} = drainOptions;
|
|
3281
|
+
if (isStopping()) return;
|
|
3282
|
+
if (state) {
|
|
3283
|
+
state.lastReviewCheckAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3284
|
+
writeDaemonState(repoRoot, state);
|
|
3285
|
+
}
|
|
3286
|
+
let reviewablePRs = await getPRs(repoRoot);
|
|
3287
|
+
if (reviewablePRs.length === 0) {
|
|
3288
|
+
if (options.verbose) {
|
|
3289
|
+
console.log(pc11.dim(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Peer Review Watchdog: 0 ready PRs found (0 tokens used).`));
|
|
3290
|
+
}
|
|
3291
|
+
return;
|
|
3292
|
+
}
|
|
3293
|
+
const attemptedPRNumbers = /* @__PURE__ */ new Set();
|
|
3294
|
+
while (!isStopping() && reviewablePRs.length > 0) {
|
|
3295
|
+
const candidatePRs = reviewablePRs.filter((pr) => !attemptedPRNumbers.has(pr.number));
|
|
3296
|
+
if (candidatePRs.length === 0) {
|
|
3297
|
+
if (options.verbose) {
|
|
3298
|
+
console.log(
|
|
3299
|
+
pc11.dim(
|
|
3300
|
+
`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] All ${reviewablePRs.length} remaining ready PR(s) were already evaluated in this drain pass.`
|
|
3301
|
+
)
|
|
3302
|
+
);
|
|
3303
|
+
}
|
|
3304
|
+
break;
|
|
3305
|
+
}
|
|
3306
|
+
const totalRemaining = candidatePRs.length;
|
|
3307
|
+
let targetPRStr = void 0;
|
|
3308
|
+
try {
|
|
3309
|
+
if (clearTicker) clearTicker();
|
|
3310
|
+
if (state) {
|
|
3311
|
+
state.status = "working";
|
|
3312
|
+
state.activeRoutine = "peer-review";
|
|
3313
|
+
writeDaemonState(repoRoot, state);
|
|
3314
|
+
}
|
|
3315
|
+
console.log(
|
|
3316
|
+
pc11.cyan(
|
|
3317
|
+
`
|
|
3318
|
+
[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] \u{1F50D} Peer Review Watchdog: Draining PR backlog (${totalRemaining} PR(s) remaining). Starting review session...`
|
|
3319
|
+
)
|
|
3320
|
+
);
|
|
3321
|
+
await cleanupStaleWorktrees(repoRoot);
|
|
3322
|
+
const result = await runRoutine({
|
|
3323
|
+
targetDir: repoRoot,
|
|
3324
|
+
routine: "peer-review",
|
|
3325
|
+
model: options.model,
|
|
3326
|
+
verbose: options.verbose,
|
|
3327
|
+
noWorktree: false,
|
|
3328
|
+
onTargetDetected: (target) => {
|
|
3329
|
+
targetPRStr = target;
|
|
3330
|
+
if (state) {
|
|
3331
|
+
state.activeTarget = target;
|
|
3332
|
+
writeDaemonState(repoRoot, state);
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
});
|
|
3336
|
+
const activeTargetStr = targetPRStr;
|
|
3337
|
+
const match = activeTargetStr?.match(/PR\s*#?([0-9]+)/i);
|
|
3338
|
+
const prNum = match ? parseInt(match[1], 10) : candidatePRs[0]?.number;
|
|
3339
|
+
if (typeof prNum === "number") {
|
|
3340
|
+
attemptedPRNumbers.add(prNum);
|
|
3341
|
+
onAttempted?.(prNum);
|
|
3342
|
+
}
|
|
3343
|
+
if (result.success) {
|
|
3344
|
+
console.log(pc11.green(`\u2713 Local peer-review completed successfully.
|
|
3345
|
+
`));
|
|
3346
|
+
} else {
|
|
3347
|
+
console.warn(pc11.yellow(`\u26A0\uFE0F Local peer-review completed with code ${result.exitCode}.
|
|
3348
|
+
`));
|
|
3349
|
+
}
|
|
3350
|
+
} catch (err) {
|
|
3351
|
+
console.error(pc11.red(`\u2717 Error in peer-review: ${err.message}`));
|
|
3352
|
+
if (candidatePRs[0]) {
|
|
3353
|
+
attemptedPRNumbers.add(candidatePRs[0].number);
|
|
3354
|
+
onAttempted?.(candidatePRs[0].number);
|
|
3355
|
+
}
|
|
3356
|
+
} finally {
|
|
3357
|
+
if (state) {
|
|
3358
|
+
state.status = "idle";
|
|
3359
|
+
state.activeRoutine = void 0;
|
|
3360
|
+
state.activeTarget = void 0;
|
|
3361
|
+
writeDaemonState(repoRoot, state);
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
reviewablePRs = await getPRs(repoRoot);
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3046
3367
|
async function runDaemonLoop(repoRoot, options = {}) {
|
|
3047
3368
|
const reviewInterval = options.reviewInterval || 3;
|
|
3048
3369
|
const autoworkInterval = options.autoworkInterval || options.interval || 30;
|
|
@@ -3105,61 +3426,51 @@ Stopping local agent daemon...`));
|
|
|
3105
3426
|
};
|
|
3106
3427
|
process.once("SIGINT", handleStop);
|
|
3107
3428
|
process.once("SIGTERM", handleStop);
|
|
3108
|
-
const
|
|
3429
|
+
const performReviewDrain = async () => {
|
|
3109
3430
|
if (isStopping || isWorking) return;
|
|
3110
|
-
state.lastReviewCheckAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3111
|
-
writeDaemonState(repoRoot, state);
|
|
3112
|
-
const openPRCount = await countOpenReadyPRs(repoRoot);
|
|
3113
|
-
lastOpenPRCount = openPRCount;
|
|
3114
|
-
if (openPRCount === 0) {
|
|
3115
|
-
if (options.verbose) {
|
|
3116
|
-
console.log(pc11.dim(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Peer Review Watchdog: 0 ready PRs found (0 tokens used).`));
|
|
3117
|
-
}
|
|
3118
|
-
nextReviewCheckTime = Date.now() + reviewIntervalMs;
|
|
3119
|
-
updateTicker();
|
|
3120
|
-
return;
|
|
3121
|
-
}
|
|
3122
3431
|
try {
|
|
3123
3432
|
isWorking = true;
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
await cleanupStaleWorktrees(repoRoot);
|
|
3131
|
-
const result = await runLocalRoutine({
|
|
3132
|
-
targetDir: repoRoot,
|
|
3133
|
-
routine: "peer-review",
|
|
3134
|
-
model: options.model,
|
|
3135
|
-
verbose: options.verbose,
|
|
3136
|
-
noWorktree: false,
|
|
3137
|
-
onTargetDetected: (target) => {
|
|
3138
|
-
state.activeTarget = target;
|
|
3139
|
-
writeDaemonState(repoRoot, state);
|
|
3140
|
-
}
|
|
3433
|
+
await drainReviewQueue({
|
|
3434
|
+
repoRoot,
|
|
3435
|
+
state,
|
|
3436
|
+
options,
|
|
3437
|
+
isStopping: () => isStopping,
|
|
3438
|
+
clearTicker
|
|
3141
3439
|
});
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
`));
|
|
3145
|
-
} else {
|
|
3146
|
-
console.warn(pc11.yellow(`\u26A0\uFE0F Local peer-review completed with code ${result.exitCode}.
|
|
3147
|
-
`));
|
|
3148
|
-
}
|
|
3149
|
-
} catch (err) {
|
|
3150
|
-
console.error(pc11.red(`\u2717 Error in peer-review: ${err.message}`));
|
|
3440
|
+
const prs = await getOpenReviewablePRs(repoRoot);
|
|
3441
|
+
lastOpenPRCount = prs.length;
|
|
3151
3442
|
} finally {
|
|
3152
3443
|
isWorking = false;
|
|
3153
|
-
state.status = "idle";
|
|
3154
|
-
state.activeRoutine = void 0;
|
|
3155
|
-
state.activeTarget = void 0;
|
|
3156
|
-
writeDaemonState(repoRoot, state);
|
|
3157
3444
|
nextReviewCheckTime = Date.now() + reviewIntervalMs;
|
|
3158
3445
|
updateTicker();
|
|
3159
3446
|
}
|
|
3160
3447
|
};
|
|
3161
3448
|
const runAutoworkCheck = async () => {
|
|
3162
3449
|
if (isStopping || isWorking || !routines.includes("autowork")) return;
|
|
3450
|
+
if (routines.includes("peer-review")) {
|
|
3451
|
+
const pendingPRs = await countOpenReadyPRs(repoRoot);
|
|
3452
|
+
if (pendingPRs > 0) {
|
|
3453
|
+
console.log(
|
|
3454
|
+
pc11.cyan(
|
|
3455
|
+
`
|
|
3456
|
+
[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] \u23F3 Autowork paused: draining ${pendingPRs} reviewable PR(s) first...`
|
|
3457
|
+
)
|
|
3458
|
+
);
|
|
3459
|
+
await performReviewDrain();
|
|
3460
|
+
const remainingPRs = await countOpenReadyPRs(repoRoot);
|
|
3461
|
+
if (remainingPRs > 0) {
|
|
3462
|
+
console.log(
|
|
3463
|
+
pc11.yellow(
|
|
3464
|
+
`
|
|
3465
|
+
[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] \u26A0\uFE0F Review backlog still has ${remainingPRs} pending PR(s). Postponing autowork session.`
|
|
3466
|
+
)
|
|
3467
|
+
);
|
|
3468
|
+
nextAutoworkCheckTime = Date.now() + autoworkIntervalMs;
|
|
3469
|
+
updateTicker();
|
|
3470
|
+
return;
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3163
3474
|
state.lastAutoworkCheckAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3164
3475
|
writeDaemonState(repoRoot, state);
|
|
3165
3476
|
try {
|
|
@@ -3199,15 +3510,27 @@ Stopping local agent daemon...`));
|
|
|
3199
3510
|
writeDaemonState(repoRoot, state);
|
|
3200
3511
|
nextAutoworkCheckTime = Date.now() + autoworkIntervalMs;
|
|
3201
3512
|
updateTicker();
|
|
3513
|
+
if (!isStopping && routines.includes("peer-review")) {
|
|
3514
|
+
const newPRCount = await countOpenReadyPRs(repoRoot);
|
|
3515
|
+
if (newPRCount > 0) {
|
|
3516
|
+
console.log(
|
|
3517
|
+
pc11.cyan(
|
|
3518
|
+
`
|
|
3519
|
+
[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] \u{1F504} Post-autowork convergence: Found ${newPRCount} ready PR(s). Initiating review sweep...`
|
|
3520
|
+
)
|
|
3521
|
+
);
|
|
3522
|
+
await performReviewDrain();
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3202
3525
|
}
|
|
3203
3526
|
};
|
|
3204
3527
|
if (routines.includes("peer-review")) {
|
|
3205
|
-
await
|
|
3528
|
+
await performReviewDrain();
|
|
3206
3529
|
}
|
|
3207
|
-
if (routines.includes("autowork")) {
|
|
3530
|
+
if (!isStopping && routines.includes("autowork")) {
|
|
3208
3531
|
await runAutoworkCheck();
|
|
3209
3532
|
}
|
|
3210
|
-
const reviewTimer = setInterval(
|
|
3533
|
+
const reviewTimer = setInterval(performReviewDrain, reviewIntervalMs);
|
|
3211
3534
|
const autoworkTimer = setInterval(runAutoworkCheck, autoworkIntervalMs);
|
|
3212
3535
|
await new Promise(() => {
|
|
3213
3536
|
});
|