jonah-fleet 1.31.7 → 1.32.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 +9 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/index.js +1820 -1611
- package/dist/lib/daemon.d.ts +9 -0
- package/dist/lib/daemon.d.ts.map +1 -1
- package/dist/lib/runner.d.ts +4 -0
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/lib/telemetry.d.ts +48 -0
- package/dist/lib/telemetry.d.ts.map +1 -1
- package/dist/lib/terminal-card.d.ts +4 -0
- package/dist/lib/terminal-card.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Command } from "commander";
|
|
|
5
5
|
|
|
6
6
|
// src/commands/init.ts
|
|
7
7
|
import readline from "readline";
|
|
8
|
-
import
|
|
8
|
+
import pc4 from "picocolors";
|
|
9
9
|
|
|
10
10
|
// src/lib/manifest.ts
|
|
11
11
|
import fs2 from "fs";
|
|
@@ -1443,200 +1443,717 @@ async function provisionLabels(options = {}) {
|
|
|
1443
1443
|
}
|
|
1444
1444
|
|
|
1445
1445
|
// src/lib/brand.ts
|
|
1446
|
-
import
|
|
1446
|
+
import pc3 from "picocolors";
|
|
1447
1447
|
|
|
1448
1448
|
// src/lib/terminal-card.ts
|
|
1449
|
-
import
|
|
1450
|
-
import
|
|
1449
|
+
import fs7 from "fs";
|
|
1450
|
+
import path7 from "path";
|
|
1451
1451
|
import { execFileSync, execFile as execFile2 } from "child_process";
|
|
1452
1452
|
import { promisify as promisify2 } from "util";
|
|
1453
|
+
import pc2 from "picocolors";
|
|
1454
|
+
|
|
1455
|
+
// src/lib/telemetry.ts
|
|
1456
|
+
import fs6 from "fs";
|
|
1457
|
+
import path6 from "path";
|
|
1453
1458
|
import pc from "picocolors";
|
|
1454
|
-
var
|
|
1455
|
-
|
|
1456
|
-
|
|
1459
|
+
var GLOBAL_WEEKLY_TOKEN_BUDGET = 875e4;
|
|
1460
|
+
var GLOBAL_5H_TOKEN_BUDGET = 2e6;
|
|
1461
|
+
function formatQuotaStatusBadge(status) {
|
|
1462
|
+
if (status === "HEALTHY") return pc.green("[HEALTHY]");
|
|
1463
|
+
if (status === "WARNING") return pc.yellow("[WARNING]");
|
|
1464
|
+
if (status === "EXCEEDED") return pc.red("[EXCEEDED]");
|
|
1465
|
+
return pc.red(`[${status}]`);
|
|
1466
|
+
}
|
|
1467
|
+
function formatTokenBreakdown(usage, style = "prefix") {
|
|
1468
|
+
if (!usage) return "";
|
|
1469
|
+
const inTok = usage.inputTokens !== void 0 ? style === "suffix" ? `${usage.inputTokens.toLocaleString()} in` : `in: ${usage.inputTokens.toLocaleString()}` : "";
|
|
1470
|
+
const outTok = usage.outputTokens !== void 0 ? style === "suffix" ? `${usage.outputTokens.toLocaleString()} out` : `out: ${usage.outputTokens.toLocaleString()}` : "";
|
|
1471
|
+
const thinkTok = usage.thinkingTokens !== void 0 ? style === "suffix" ? `${usage.thinkingTokens.toLocaleString()} think` : `think: ${usage.thinkingTokens.toLocaleString()}` : "";
|
|
1472
|
+
return [inTok, outTok, thinkTok].filter(Boolean).join(" \xB7 ");
|
|
1473
|
+
}
|
|
1474
|
+
function calculateTokenQuotaPercentages(totalTokens, options = {}) {
|
|
1475
|
+
const window5hLimit = options.window5hLimit ?? GLOBAL_5H_TOKEN_BUDGET;
|
|
1476
|
+
const weeklyLimit = options.weeklyLimit ?? GLOBAL_WEEKLY_TOKEN_BUDGET;
|
|
1477
|
+
const pctOf5hLimit = window5hLimit > 0 ? totalTokens / window5hLimit * 100 : 0;
|
|
1478
|
+
const pctOfWeeklyLimit = weeklyLimit > 0 ? totalTokens / weeklyLimit * 100 : 0;
|
|
1479
|
+
return {
|
|
1480
|
+
pctOf5hLimit,
|
|
1481
|
+
pctOfWeeklyLimit,
|
|
1482
|
+
window5hLimit,
|
|
1483
|
+
weeklyLimit
|
|
1484
|
+
};
|
|
1457
1485
|
}
|
|
1458
|
-
function
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
let
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1486
|
+
function getRollingWindowTokenUsage(repoRoot, options = {}) {
|
|
1487
|
+
const window5hLimit = options.window5hLimit ?? GLOBAL_5H_TOKEN_BUDGET;
|
|
1488
|
+
const weeklyLimit = options.weeklyLimit ?? GLOBAL_WEEKLY_TOKEN_BUDGET;
|
|
1489
|
+
const now = options.now ?? Date.now();
|
|
1490
|
+
const window5hCutoff = now - 5 * 3600 * 1e3;
|
|
1491
|
+
const weeklyCutoff = now - 7 * 86400 * 1e3;
|
|
1492
|
+
let windowTokens = 0;
|
|
1493
|
+
let weeklyTokens = 0;
|
|
1494
|
+
const runsDir = path6.join(repoRoot, ".jonah-fleet", "runs");
|
|
1495
|
+
if (fs6.existsSync(runsDir)) {
|
|
1496
|
+
try {
|
|
1497
|
+
const files = fs6.readdirSync(runsDir);
|
|
1498
|
+
for (const file of files) {
|
|
1499
|
+
if (!file.endsWith(".json")) continue;
|
|
1500
|
+
const fullPath = path6.join(runsDir, file);
|
|
1501
|
+
try {
|
|
1502
|
+
const content = fs6.readFileSync(fullPath, "utf8");
|
|
1503
|
+
const data = JSON.parse(content);
|
|
1504
|
+
let runTime = null;
|
|
1505
|
+
if (data.timestamp) {
|
|
1506
|
+
const parsed = new Date(data.timestamp).getTime();
|
|
1507
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
1508
|
+
runTime = parsed;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
if (runTime === null) {
|
|
1512
|
+
const stat = fs6.statSync(fullPath);
|
|
1513
|
+
runTime = stat.mtimeMs;
|
|
1514
|
+
}
|
|
1515
|
+
if (runTime < weeklyCutoff) continue;
|
|
1516
|
+
const tokens = data.usage?.totalTokens ?? data.usage?.total_tokens ?? data.totalTokens ?? 0;
|
|
1517
|
+
if (typeof tokens === "number" && tokens > 0) {
|
|
1518
|
+
weeklyTokens += tokens;
|
|
1519
|
+
if (runTime >= window5hCutoff) {
|
|
1520
|
+
windowTokens += tokens;
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
} catch {
|
|
1524
|
+
}
|
|
1478
1525
|
}
|
|
1479
|
-
|
|
1480
|
-
}
|
|
1481
|
-
if (visibleCount < maxWidth) {
|
|
1482
|
-
result += char;
|
|
1483
|
-
visibleCount++;
|
|
1484
|
-
} else {
|
|
1485
|
-
break;
|
|
1526
|
+
} catch {
|
|
1486
1527
|
}
|
|
1487
1528
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
if (stripAnsi(proposed).length <= maxWidth) {
|
|
1503
|
-
current = proposed;
|
|
1504
|
-
} else {
|
|
1505
|
-
lines.push(current);
|
|
1506
|
-
current = word;
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1529
|
+
const windowPercentage = window5hLimit > 0 ? windowTokens / window5hLimit * 100 : 0;
|
|
1530
|
+
const weeklyPercentage = weeklyLimit > 0 ? weeklyTokens / weeklyLimit * 100 : 0;
|
|
1531
|
+
const resolveStatus = (pct) => {
|
|
1532
|
+
if (pct > 100) return "EXCEEDED";
|
|
1533
|
+
if (pct >= 70) return "WARNING";
|
|
1534
|
+
return "HEALTHY";
|
|
1535
|
+
};
|
|
1536
|
+
const windowStatus = resolveStatus(windowPercentage);
|
|
1537
|
+
const weeklyStatus = resolveStatus(weeklyPercentage);
|
|
1538
|
+
let status = "HEALTHY";
|
|
1539
|
+
if (windowStatus === "EXCEEDED" || weeklyStatus === "EXCEEDED") {
|
|
1540
|
+
status = "EXCEEDED";
|
|
1541
|
+
} else if (windowStatus === "WARNING" || weeklyStatus === "WARNING") {
|
|
1542
|
+
status = "WARNING";
|
|
1509
1543
|
}
|
|
1510
|
-
|
|
1511
|
-
|
|
1544
|
+
return {
|
|
1545
|
+
windowTokens,
|
|
1546
|
+
windowLimit: window5hLimit,
|
|
1547
|
+
windowPercentage,
|
|
1548
|
+
windowStatus,
|
|
1549
|
+
weeklyTokens,
|
|
1550
|
+
weeklyLimit,
|
|
1551
|
+
weeklyPercentage,
|
|
1552
|
+
weeklyStatus,
|
|
1553
|
+
status
|
|
1554
|
+
};
|
|
1512
1555
|
}
|
|
1513
|
-
function
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
return stdout.trim() || null;
|
|
1524
|
-
}
|
|
1525
|
-
const issueMatch = target.match(/Issue\s*#?(\d+)/i);
|
|
1526
|
-
if (issueMatch) {
|
|
1527
|
-
const stdout = execFileSync("gh", ["issue", "view", issueMatch[1], "--json", "title", "-q", ".title"], {
|
|
1528
|
-
cwd: repoRoot,
|
|
1529
|
-
encoding: "utf8",
|
|
1530
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
1531
|
-
timeout: 4e3
|
|
1532
|
-
});
|
|
1533
|
-
return stdout.trim() || null;
|
|
1534
|
-
}
|
|
1535
|
-
} catch {
|
|
1536
|
-
return null;
|
|
1556
|
+
function parseLogToTelemetry(content, options = {}) {
|
|
1557
|
+
if (!content || typeof content !== "string") return null;
|
|
1558
|
+
const lines = content.split("\n");
|
|
1559
|
+
const metadata = {};
|
|
1560
|
+
for (const line of lines) {
|
|
1561
|
+
const match = line.match(/^\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|/);
|
|
1562
|
+
if (!match) continue;
|
|
1563
|
+
const key = match[1].trim().toLowerCase();
|
|
1564
|
+
const val = match[2].trim().replace(/`/g, "");
|
|
1565
|
+
metadata[key] = val;
|
|
1537
1566
|
}
|
|
1538
|
-
|
|
1539
|
-
}
|
|
1540
|
-
async function fetchTargetTitleAsync(repoRoot, target) {
|
|
1541
|
-
try {
|
|
1542
|
-
const prMatch = target.match(/PR\s*#?(\d+)/i);
|
|
1543
|
-
if (prMatch) {
|
|
1544
|
-
const { stdout } = await execFileAsync2("gh", ["pr", "view", prMatch[1], "--json", "title", "-q", ".title"], {
|
|
1545
|
-
cwd: repoRoot,
|
|
1546
|
-
encoding: "utf8",
|
|
1547
|
-
timeout: 3e3
|
|
1548
|
-
});
|
|
1549
|
-
return stdout.trim() || null;
|
|
1550
|
-
}
|
|
1551
|
-
const issueMatch = target.match(/Issue\s*#?(\d+)/i);
|
|
1552
|
-
if (issueMatch) {
|
|
1553
|
-
const { stdout } = await execFileAsync2("gh", ["issue", "view", issueMatch[1], "--json", "title", "-q", ".title"], {
|
|
1554
|
-
cwd: repoRoot,
|
|
1555
|
-
encoding: "utf8",
|
|
1556
|
-
timeout: 3e3
|
|
1557
|
-
});
|
|
1558
|
-
return stdout.trim() || null;
|
|
1559
|
-
}
|
|
1560
|
-
} catch {
|
|
1567
|
+
if (!metadata["timestamp"] && !metadata["routine"]) {
|
|
1561
1568
|
return null;
|
|
1562
1569
|
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
|
-
const summaryHeaderRegex = /#{1,3}\s+([A-Za-z0-9\s_-]*?(?:Execution|Review|Autowork)\s+Summary[\s\S]*)/i;
|
|
1582
|
-
const match = output.match(summaryHeaderRegex);
|
|
1583
|
-
if (!match) return null;
|
|
1584
|
-
let summary = match[1].trim();
|
|
1585
|
-
const trailingSeparators = [
|
|
1586
|
-
"\u2713 Local peer-review completed",
|
|
1587
|
-
"\u2713 Local peer-review on",
|
|
1588
|
-
"\u26A0\uFE0F Local peer-review on",
|
|
1589
|
-
"\u2713 Targeted peer-review on",
|
|
1590
|
-
"\u26A0\uFE0F Targeted peer-review on",
|
|
1591
|
-
"\u2713 Local autowork completed",
|
|
1592
|
-
"\u2713 Local agent session",
|
|
1593
|
-
"Peer Review Watchdog:",
|
|
1594
|
-
"Autowork Backlog Scan:"
|
|
1595
|
-
];
|
|
1596
|
-
for (const sep of trailingSeparators) {
|
|
1597
|
-
const idx = summary.indexOf(sep);
|
|
1598
|
-
if (idx !== -1) {
|
|
1599
|
-
summary = summary.slice(0, idx).trim();
|
|
1570
|
+
const routine = metadata["routine"] || "unknown";
|
|
1571
|
+
const timestamp = metadata["timestamp"] || (/* @__PURE__ */ new Date()).toISOString();
|
|
1572
|
+
const result = metadata["result"] || "UNKNOWN";
|
|
1573
|
+
const errorReasonRaw = metadata["error reason"];
|
|
1574
|
+
const errorReason = errorReasonRaw && errorReasonRaw.toUpperCase() !== "N/A" ? errorReasonRaw : void 0;
|
|
1575
|
+
let failureCategory = metadata["failure category"];
|
|
1576
|
+
if (!failureCategory && result === "FAILURE" && errorReason) {
|
|
1577
|
+
const lower = errorReason.toLowerCase();
|
|
1578
|
+
if (lower.includes("token") || lower.includes("ceiling")) {
|
|
1579
|
+
failureCategory = "token_limit";
|
|
1580
|
+
} else if (lower.includes("build") || lower.includes("type-check")) {
|
|
1581
|
+
failureCategory = "build_error";
|
|
1582
|
+
} else if (lower.includes("infeasible") || lower.includes("blocker")) {
|
|
1583
|
+
failureCategory = "infeasible";
|
|
1584
|
+
} else if (lower.includes("conflict")) {
|
|
1585
|
+
failureCategory = "merge_conflict";
|
|
1586
|
+
} else if (lower.includes("ambiguous") || lower.includes("needs-info") || lower.includes("clarification") || lower.includes("acceptance criteria")) {
|
|
1587
|
+
failureCategory = "ambiguous_spec";
|
|
1600
1588
|
}
|
|
1601
1589
|
}
|
|
1602
|
-
const
|
|
1603
|
-
|
|
1604
|
-
|
|
1590
|
+
const inputTokens = parseInt((metadata["input tokens"] || "0").replace(/[^\d]/g, ""), 10) || 0;
|
|
1591
|
+
const outputTokens = parseInt((metadata["output tokens"] || "0").replace(/[^\d]/g, ""), 10) || 0;
|
|
1592
|
+
const totalTokens = inputTokens + outputTokens;
|
|
1593
|
+
const estimatedCost = parseFloat((metadata["estimated cost"] || "0").replace(/[^0-9.]/g, "")) || 0;
|
|
1594
|
+
let durationSeconds;
|
|
1595
|
+
if (metadata["duration"]) {
|
|
1596
|
+
const durNum = parseInt(metadata["duration"].replace(/[^\d]/g, ""), 10);
|
|
1597
|
+
if (!isNaN(durNum)) durationSeconds = durNum;
|
|
1605
1598
|
}
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
}
|
|
1617
|
-
} catch {
|
|
1599
|
+
let iterationsUsed;
|
|
1600
|
+
let maxIterations;
|
|
1601
|
+
if (metadata["iterations used"]) {
|
|
1602
|
+
const iterMatch = metadata["iterations used"].match(/(\d+)\s*\/\s*(\d+)/);
|
|
1603
|
+
if (iterMatch) {
|
|
1604
|
+
iterationsUsed = parseInt(iterMatch[1], 10);
|
|
1605
|
+
maxIterations = parseInt(iterMatch[2], 10);
|
|
1606
|
+
} else {
|
|
1607
|
+
const singleNum = parseInt(metadata["iterations used"].replace(/[^\d]/g, ""), 10);
|
|
1608
|
+
if (!isNaN(singleNum)) iterationsUsed = singleNum;
|
|
1618
1609
|
}
|
|
1619
1610
|
}
|
|
1620
|
-
const
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1611
|
+
const promptSha = metadata["prompt sha"];
|
|
1612
|
+
const lowerContent = content.toLowerCase();
|
|
1613
|
+
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"));
|
|
1614
|
+
const needsInfoApplied = lowerContent.includes("needs-info");
|
|
1615
|
+
let questionsAskedCount;
|
|
1616
|
+
if (ambiguityGateTriggered) {
|
|
1617
|
+
const questionLines = content.split("\n").filter(
|
|
1618
|
+
(line) => (line.trim().startsWith("1.") || line.trim().startsWith("2.") || line.trim().startsWith("3.") || line.trim().startsWith("-")) && line.includes("?")
|
|
1619
|
+
);
|
|
1620
|
+
questionsAskedCount = questionLines.length > 0 ? questionLines.length : 2;
|
|
1630
1621
|
}
|
|
1631
|
-
return
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1622
|
+
return {
|
|
1623
|
+
schemaVersion: "1.0.0",
|
|
1624
|
+
routine,
|
|
1625
|
+
timestamp,
|
|
1626
|
+
repository: options.repository || "local",
|
|
1627
|
+
runId: options.runId,
|
|
1628
|
+
runNumber: options.runNumber,
|
|
1629
|
+
result,
|
|
1630
|
+
errorReason,
|
|
1631
|
+
failureCategory,
|
|
1632
|
+
inputTokens,
|
|
1633
|
+
outputTokens,
|
|
1634
|
+
totalTokens,
|
|
1635
|
+
estimatedCost,
|
|
1636
|
+
durationSeconds,
|
|
1637
|
+
iterationsUsed,
|
|
1638
|
+
maxIterations,
|
|
1639
|
+
promptSha,
|
|
1640
|
+
ambiguityGateTriggered: ambiguityGateTriggered || void 0,
|
|
1641
|
+
questionsAskedCount,
|
|
1642
|
+
needsInfoApplied: needsInfoApplied || void 0
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
function checkWeeklyBudgetLimit(usedTokens, ceilingTokens = GLOBAL_WEEKLY_TOKEN_BUDGET) {
|
|
1646
|
+
const remainingTokens = Math.max(0, ceilingTokens - usedTokens);
|
|
1647
|
+
const utilizationPercentage = ceilingTokens > 0 ? usedTokens / ceilingTokens * 100 : 0;
|
|
1648
|
+
const dailyBurnRate = usedTokens / 7;
|
|
1649
|
+
const projectedExhaustionDays = dailyBurnRate > 0 ? remainingTokens / dailyBurnRate : 999;
|
|
1650
|
+
let status = "HEALTHY";
|
|
1651
|
+
if (usedTokens > ceilingTokens) {
|
|
1652
|
+
status = "EXCEEDED";
|
|
1653
|
+
} else if (utilizationPercentage >= 90) {
|
|
1654
|
+
status = "CRITICAL";
|
|
1655
|
+
} else if (utilizationPercentage >= 70) {
|
|
1656
|
+
status = "WARNING";
|
|
1657
|
+
}
|
|
1658
|
+
return {
|
|
1659
|
+
weeklyCeilingTokens: ceilingTokens,
|
|
1660
|
+
usedTokens,
|
|
1661
|
+
remainingTokens,
|
|
1662
|
+
utilizationPercentage,
|
|
1663
|
+
status,
|
|
1664
|
+
dailyBurnRate,
|
|
1665
|
+
projectedExhaustionDays
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
function aggregateFleetTelemetry(summaries, options = {}) {
|
|
1669
|
+
const budgetCeiling = options.weeklyTokenBudget || GLOBAL_WEEKLY_TOKEN_BUDGET;
|
|
1670
|
+
let totalInputTokens = 0;
|
|
1671
|
+
let totalOutputTokens = 0;
|
|
1672
|
+
let totalCost = 0;
|
|
1673
|
+
let successCount = 0;
|
|
1674
|
+
let failureCount = 0;
|
|
1675
|
+
let bouncedCount = 0;
|
|
1676
|
+
const byRoutine = {};
|
|
1677
|
+
const byRepository = {};
|
|
1678
|
+
const failureCategories = {};
|
|
1679
|
+
for (const s of summaries) {
|
|
1680
|
+
totalInputTokens += s.inputTokens;
|
|
1681
|
+
totalOutputTokens += s.outputTokens;
|
|
1682
|
+
totalCost += s.estimatedCost;
|
|
1683
|
+
if (s.result === "SUCCESS") successCount++;
|
|
1684
|
+
else if (s.result === "FAILURE") failureCount++;
|
|
1685
|
+
else if (s.result === "BOUNCED_TO_DRAFT") bouncedCount++;
|
|
1686
|
+
if (s.failureCategory) {
|
|
1687
|
+
failureCategories[s.failureCategory] = (failureCategories[s.failureCategory] || 0) + 1;
|
|
1688
|
+
}
|
|
1689
|
+
if (!byRoutine[s.routine]) {
|
|
1690
|
+
byRoutine[s.routine] = {
|
|
1691
|
+
routine: s.routine,
|
|
1692
|
+
runCount: 0,
|
|
1693
|
+
totalInputTokens: 0,
|
|
1694
|
+
totalOutputTokens: 0,
|
|
1695
|
+
totalTokens: 0,
|
|
1696
|
+
totalEstimatedCost: 0,
|
|
1697
|
+
successCount: 0,
|
|
1698
|
+
failureCount: 0,
|
|
1699
|
+
bouncedCount: 0,
|
|
1700
|
+
avgDurationSeconds: 0,
|
|
1701
|
+
avgIterationsUsed: 0,
|
|
1702
|
+
ambiguityGatesTriggered: 0,
|
|
1703
|
+
questionsAskedCount: 0,
|
|
1704
|
+
needsInfoAppliedCount: 0
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
const r = byRoutine[s.routine];
|
|
1708
|
+
r.runCount++;
|
|
1709
|
+
r.totalInputTokens += s.inputTokens;
|
|
1710
|
+
r.totalOutputTokens += s.outputTokens;
|
|
1711
|
+
r.totalTokens += s.totalTokens;
|
|
1712
|
+
r.totalEstimatedCost += s.estimatedCost;
|
|
1713
|
+
if (s.result === "SUCCESS") r.successCount++;
|
|
1714
|
+
else if (s.result === "FAILURE") r.failureCount++;
|
|
1715
|
+
else if (s.result === "BOUNCED_TO_DRAFT") r.bouncedCount++;
|
|
1716
|
+
if (s.durationSeconds) {
|
|
1717
|
+
r.avgDurationSeconds = (r.avgDurationSeconds * (r.runCount - 1) + s.durationSeconds) / r.runCount;
|
|
1718
|
+
}
|
|
1719
|
+
if (s.iterationsUsed) {
|
|
1720
|
+
r.avgIterationsUsed = (r.avgIterationsUsed * (r.runCount - 1) + s.iterationsUsed) / r.runCount;
|
|
1721
|
+
}
|
|
1722
|
+
if (s.ambiguityGateTriggered) r.ambiguityGatesTriggered++;
|
|
1723
|
+
if (s.questionsAskedCount) r.questionsAskedCount += s.questionsAskedCount;
|
|
1724
|
+
if (s.needsInfoApplied) r.needsInfoAppliedCount++;
|
|
1725
|
+
if (!byRepository[s.repository]) {
|
|
1726
|
+
byRepository[s.repository] = {
|
|
1727
|
+
repository: s.repository,
|
|
1728
|
+
runCount: 0,
|
|
1729
|
+
totalTokens: 0,
|
|
1730
|
+
totalEstimatedCost: 0,
|
|
1731
|
+
successCount: 0,
|
|
1732
|
+
failureCount: 0
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
const repoObj = byRepository[s.repository];
|
|
1736
|
+
repoObj.runCount++;
|
|
1737
|
+
repoObj.totalTokens += s.totalTokens;
|
|
1738
|
+
repoObj.totalEstimatedCost += s.estimatedCost;
|
|
1739
|
+
if (s.result === "SUCCESS") repoObj.successCount++;
|
|
1740
|
+
else if (s.result === "FAILURE") repoObj.failureCount++;
|
|
1741
|
+
}
|
|
1742
|
+
let totalAmbiguityGatesTriggered = 0;
|
|
1743
|
+
let totalQuestionsAsked = 0;
|
|
1744
|
+
let totalNeedsInfoApplied = 0;
|
|
1745
|
+
for (const s of summaries) {
|
|
1746
|
+
if (s.ambiguityGateTriggered) totalAmbiguityGatesTriggered++;
|
|
1747
|
+
if (s.questionsAskedCount) totalQuestionsAsked += s.questionsAskedCount;
|
|
1748
|
+
if (s.needsInfoApplied) totalNeedsInfoApplied++;
|
|
1749
|
+
}
|
|
1750
|
+
const estimatedTokensSaved = totalAmbiguityGatesTriggered * 5e4;
|
|
1751
|
+
const ambiguity = {
|
|
1752
|
+
totalAmbiguityGatesTriggered,
|
|
1753
|
+
totalQuestionsAsked,
|
|
1754
|
+
needsInfoAppliedCount: totalNeedsInfoApplied,
|
|
1755
|
+
estimatedTokensSaved
|
|
1756
|
+
};
|
|
1757
|
+
const totalTokens = totalInputTokens + totalOutputTokens;
|
|
1758
|
+
const budget = checkWeeklyBudgetLimit(totalTokens, budgetCeiling);
|
|
1759
|
+
return {
|
|
1760
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1761
|
+
totalRuns: summaries.length,
|
|
1762
|
+
successCount,
|
|
1763
|
+
failureCount,
|
|
1764
|
+
bouncedCount,
|
|
1765
|
+
totalInputTokens,
|
|
1766
|
+
totalOutputTokens,
|
|
1767
|
+
totalTokens,
|
|
1768
|
+
totalEstimatedCost: totalCost,
|
|
1769
|
+
budget,
|
|
1770
|
+
byRoutine,
|
|
1771
|
+
byRepository,
|
|
1772
|
+
failureCategories,
|
|
1773
|
+
ambiguity,
|
|
1774
|
+
events: summaries
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
async function emitTelemetry(summary, endpoint, customFetch = globalThis.fetch) {
|
|
1778
|
+
if (!endpoint || !endpoint.trim()) {
|
|
1779
|
+
return { success: true };
|
|
1780
|
+
}
|
|
1781
|
+
try {
|
|
1782
|
+
const res = await customFetch(endpoint.trim(), {
|
|
1783
|
+
method: "POST",
|
|
1784
|
+
headers: {
|
|
1785
|
+
"Content-Type": "application/json",
|
|
1786
|
+
"User-Agent": "jonah-fleet-telemetry/1.0"
|
|
1787
|
+
},
|
|
1788
|
+
body: JSON.stringify(summary)
|
|
1789
|
+
});
|
|
1790
|
+
if (!res.ok) {
|
|
1791
|
+
return { success: false, error: `HTTP ${res.status}: ${res.statusText}` };
|
|
1792
|
+
}
|
|
1793
|
+
return { success: true };
|
|
1794
|
+
} catch (err) {
|
|
1795
|
+
return { success: false, error: err.message || "Unknown network error" };
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
function collectLocalTelemetryLogs(dir, repositoryName = "local") {
|
|
1799
|
+
const summaries = [];
|
|
1800
|
+
const runsDir = path6.join(dir, ".jonah-fleet/runs");
|
|
1801
|
+
const legacyLogsDir = path6.join(dir, ".github/prompts/logs");
|
|
1802
|
+
const traverse = (currentDir) => {
|
|
1803
|
+
if (!fs6.existsSync(currentDir)) return;
|
|
1804
|
+
const entries = fs6.readdirSync(currentDir, { withFileTypes: true });
|
|
1805
|
+
for (const entry of entries) {
|
|
1806
|
+
const fullPath = path6.join(currentDir, entry.name);
|
|
1807
|
+
if (entry.isDirectory()) {
|
|
1808
|
+
traverse(fullPath);
|
|
1809
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
1810
|
+
try {
|
|
1811
|
+
const content = fs6.readFileSync(fullPath, "utf8");
|
|
1812
|
+
const summary = parseLogToTelemetry(content, { repository: repositoryName });
|
|
1813
|
+
if (summary) summaries.push(summary);
|
|
1814
|
+
} catch {
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
};
|
|
1819
|
+
if (fs6.existsSync(runsDir)) {
|
|
1820
|
+
traverse(runsDir);
|
|
1821
|
+
}
|
|
1822
|
+
if (fs6.existsSync(legacyLogsDir)) {
|
|
1823
|
+
traverse(legacyLogsDir);
|
|
1824
|
+
}
|
|
1825
|
+
return summaries;
|
|
1826
|
+
}
|
|
1827
|
+
async function collectRepoTelemetry(repoIdentifier, executor = defaultGhExecutor, options = {}) {
|
|
1828
|
+
const summaries = [];
|
|
1829
|
+
if (fs6.existsSync(repoIdentifier) && fs6.statSync(repoIdentifier).isDirectory()) {
|
|
1830
|
+
return collectLocalTelemetryLogs(repoIdentifier, repoIdentifier);
|
|
1831
|
+
}
|
|
1832
|
+
try {
|
|
1833
|
+
const issuesRaw = await executor([
|
|
1834
|
+
"issue",
|
|
1835
|
+
"list",
|
|
1836
|
+
"--repo",
|
|
1837
|
+
repoIdentifier,
|
|
1838
|
+
"--label",
|
|
1839
|
+
"routine-log",
|
|
1840
|
+
"--state",
|
|
1841
|
+
"all",
|
|
1842
|
+
"--limit",
|
|
1843
|
+
String(options.maxLogs || 25),
|
|
1844
|
+
"--json",
|
|
1845
|
+
"body,number,title,createdAt"
|
|
1846
|
+
]);
|
|
1847
|
+
const issues = JSON.parse(issuesRaw);
|
|
1848
|
+
if (Array.isArray(issues) && issues.length > 0) {
|
|
1849
|
+
for (const issue of issues) {
|
|
1850
|
+
if (issue.body) {
|
|
1851
|
+
const summary = parseLogToTelemetry(issue.body, { repository: repoIdentifier });
|
|
1852
|
+
if (summary) summaries.push(summary);
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
if (summaries.length > 0) {
|
|
1856
|
+
return summaries;
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
} catch {
|
|
1860
|
+
}
|
|
1861
|
+
try {
|
|
1862
|
+
const treeRaw = await executor([
|
|
1863
|
+
"api",
|
|
1864
|
+
`repos/${repoIdentifier}/git/trees/HEAD?recursive=1`
|
|
1865
|
+
]);
|
|
1866
|
+
const tree = JSON.parse(treeRaw);
|
|
1867
|
+
if (Array.isArray(tree.tree)) {
|
|
1868
|
+
const logFiles = tree.tree.filter((node) => node.path && node.path.startsWith(".github/prompts/logs/") && node.path.endsWith(".md")).slice(-(options.maxLogs || 25));
|
|
1869
|
+
for (const file of logFiles) {
|
|
1870
|
+
try {
|
|
1871
|
+
const fileRaw = await executor(["api", `repos/${repoIdentifier}/contents/${file.path}`]);
|
|
1872
|
+
const parsed = JSON.parse(fileRaw);
|
|
1873
|
+
if (parsed.content) {
|
|
1874
|
+
const content = Buffer.from(parsed.content, "base64").toString("utf8");
|
|
1875
|
+
const summary = parseLogToTelemetry(content, { repository: repoIdentifier });
|
|
1876
|
+
if (summary) summaries.push(summary);
|
|
1877
|
+
}
|
|
1878
|
+
} catch {
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
} catch {
|
|
1883
|
+
}
|
|
1884
|
+
return summaries;
|
|
1885
|
+
}
|
|
1886
|
+
function formatTokens(num) {
|
|
1887
|
+
if (num >= 1e6) {
|
|
1888
|
+
return `${(num / 1e6).toFixed(2)}M`;
|
|
1889
|
+
}
|
|
1890
|
+
if (num >= 1e3) {
|
|
1891
|
+
return `${(num / 1e3).toFixed(1)}k`;
|
|
1892
|
+
}
|
|
1893
|
+
return num.toString();
|
|
1894
|
+
}
|
|
1895
|
+
function renderProgressBar(percentage, width = 25) {
|
|
1896
|
+
const clamped = Math.max(0, Math.min(100, percentage));
|
|
1897
|
+
const filledCount = Math.round(clamped / 100 * width);
|
|
1898
|
+
const emptyCount = width - filledCount;
|
|
1899
|
+
const filledBar = "\u2588".repeat(filledCount);
|
|
1900
|
+
const emptyBar = "\u2591".repeat(emptyCount);
|
|
1901
|
+
if (clamped >= 90) return pc.red(filledBar) + pc.gray(emptyBar);
|
|
1902
|
+
if (clamped >= 70) return pc.yellow(filledBar) + pc.gray(emptyBar);
|
|
1903
|
+
return pc.green(filledBar) + pc.gray(emptyBar);
|
|
1904
|
+
}
|
|
1905
|
+
function renderTelemetryDashboard(telemetry, options = {}) {
|
|
1906
|
+
if (options.json) {
|
|
1907
|
+
return JSON.stringify(telemetry, null, 2);
|
|
1908
|
+
}
|
|
1909
|
+
const lines = [];
|
|
1910
|
+
lines.push(pc.bold(pc.cyan("\n\u{1F6F0}\uFE0F Jonah Fleet Telemetry Hub & Token Economics\n")));
|
|
1911
|
+
const b = telemetry.budget;
|
|
1912
|
+
let statusBadge = pc.green("[HEALTHY]");
|
|
1913
|
+
if (b.status === "WARNING") statusBadge = pc.yellow("[WARNING]");
|
|
1914
|
+
else if (b.status === "CRITICAL") statusBadge = pc.red(pc.bold("[CRITICAL]"));
|
|
1915
|
+
else if (b.status === "EXCEEDED") statusBadge = pc.red(pc.bold("[BUDGET EXCEEDED]"));
|
|
1916
|
+
lines.push(pc.bold("\u{1F4C8} Global Weekly Token Budget Ceiling (~70% Fleet Limit):"));
|
|
1917
|
+
lines.push(
|
|
1918
|
+
` ${renderProgressBar(b.utilizationPercentage, 30)} ${pc.bold(`${b.utilizationPercentage.toFixed(1)}%`)} ${statusBadge}`
|
|
1919
|
+
);
|
|
1920
|
+
lines.push(
|
|
1921
|
+
` Used: ${pc.bold(formatTokens(b.usedTokens))} / ${formatTokens(b.weeklyCeilingTokens)} tokens | Remaining: ${pc.green(formatTokens(b.remainingTokens))} | Daily Burn: ${formatTokens(b.dailyBurnRate)}/day`
|
|
1922
|
+
);
|
|
1923
|
+
lines.push("\n" + pc.bold("\u{1F310} Fleet Aggregate Spend:"));
|
|
1924
|
+
lines.push(
|
|
1925
|
+
` Total Runs: ${pc.bold(telemetry.totalRuns.toString())} (${pc.green(telemetry.successCount + " success")}, ${pc.red(telemetry.failureCount + " failed")}, ${pc.yellow(telemetry.bouncedCount + " bounced")})`
|
|
1926
|
+
);
|
|
1927
|
+
lines.push(
|
|
1928
|
+
` Total Tokens: ${pc.bold(formatTokens(telemetry.totalTokens))} ` + pc.gray(`(in: ${formatTokens(telemetry.totalInputTokens)}, out: ${formatTokens(telemetry.totalOutputTokens)})`) + ` | Est. Cost: ${pc.bold(pc.green(`$${telemetry.totalEstimatedCost.toFixed(2)}`))}`
|
|
1929
|
+
);
|
|
1930
|
+
lines.push("\n" + pc.bold("\u{1F916} Spend by Agent Routine:"));
|
|
1931
|
+
for (const [routineName, r] of Object.entries(telemetry.byRoutine)) {
|
|
1932
|
+
const costStr = pc.green(`$${r.totalEstimatedCost.toFixed(2)}`);
|
|
1933
|
+
lines.push(
|
|
1934
|
+
` \u2022 ${pc.cyan(routineName.padEnd(30))} Runs: ${pc.bold(r.runCount.toString().padStart(2))} | Tokens: ${pc.bold(formatTokens(r.totalTokens).padStart(7))} | Cost: ${costStr.padStart(6)} | Avg Iter: ${r.avgIterationsUsed.toFixed(1)}`
|
|
1935
|
+
);
|
|
1936
|
+
}
|
|
1937
|
+
if (Object.keys(telemetry.byRepository).length > 0) {
|
|
1938
|
+
lines.push("\n" + pc.bold("\u{1F4E6} Spend by Repository:"));
|
|
1939
|
+
for (const [repoName, repoObj] of Object.entries(telemetry.byRepository)) {
|
|
1940
|
+
lines.push(
|
|
1941
|
+
` \u2022 ${pc.bold(repoName)}: ${formatTokens(repoObj.totalTokens)} tokens across ${repoObj.runCount} runs ($${repoObj.totalEstimatedCost.toFixed(2)})`
|
|
1942
|
+
);
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
const amb = telemetry.ambiguity;
|
|
1946
|
+
lines.push("\n" + pc.bold("\u2753 Inquisitive Stance & Ambiguity Gate Signals:"));
|
|
1947
|
+
lines.push(
|
|
1948
|
+
` \u2022 Ambiguity Gate Triggers: ${pc.bold(amb.totalAmbiguityGatesTriggered.toString())} runs stopped to request clarification`
|
|
1949
|
+
);
|
|
1950
|
+
lines.push(
|
|
1951
|
+
` \u2022 Clarifying Questions Posed: ${pc.bold(amb.totalQuestionsAsked.toString())} targeted questions`
|
|
1952
|
+
);
|
|
1953
|
+
lines.push(
|
|
1954
|
+
` \u2022 Needs-Info Labels Applied: ${pc.bold(amb.needsInfoAppliedCount.toString())}`
|
|
1955
|
+
);
|
|
1956
|
+
lines.push(
|
|
1957
|
+
` \u2022 Est. Wasted Tokens Averted: ~${pc.bold(pc.green(formatTokens(amb.estimatedTokensSaved)))} tokens (avoided speculative builds)`
|
|
1958
|
+
);
|
|
1959
|
+
const failKeys = Object.keys(telemetry.failureCategories);
|
|
1960
|
+
if (failKeys.length > 0) {
|
|
1961
|
+
lines.push("\n" + pc.bold(pc.red("\u26A0\uFE0F Failure Categories Breakdown:")));
|
|
1962
|
+
for (const cat of failKeys) {
|
|
1963
|
+
lines.push(` - ${cat}: ${telemetry.failureCategories[cat]} occurrences`);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
lines.push("\n" + pc.gray("\u2500".repeat(65)) + "\n");
|
|
1967
|
+
return lines.join("\n");
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
// src/lib/terminal-card.ts
|
|
1971
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
1972
|
+
function stripAnsi(text) {
|
|
1973
|
+
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1974
|
+
}
|
|
1975
|
+
function truncateAnsi(text, maxWidth) {
|
|
1976
|
+
if (maxWidth <= 0) return "";
|
|
1977
|
+
if (stripAnsi(text).length <= maxWidth) return text;
|
|
1978
|
+
let visibleCount = 0;
|
|
1979
|
+
let result = "";
|
|
1980
|
+
let inAnsi = false;
|
|
1981
|
+
let ansiBuffer = "";
|
|
1982
|
+
for (let i = 0; i < text.length; i++) {
|
|
1983
|
+
const char = text[i];
|
|
1984
|
+
if (char === "\x1B") {
|
|
1985
|
+
inAnsi = true;
|
|
1986
|
+
ansiBuffer = char;
|
|
1987
|
+
continue;
|
|
1988
|
+
}
|
|
1989
|
+
if (inAnsi) {
|
|
1990
|
+
ansiBuffer += char;
|
|
1991
|
+
if (char === "m") {
|
|
1992
|
+
inAnsi = false;
|
|
1993
|
+
result += ansiBuffer;
|
|
1994
|
+
ansiBuffer = "";
|
|
1995
|
+
}
|
|
1996
|
+
continue;
|
|
1997
|
+
}
|
|
1998
|
+
if (visibleCount < maxWidth) {
|
|
1999
|
+
result += char;
|
|
2000
|
+
visibleCount++;
|
|
2001
|
+
} else {
|
|
2002
|
+
break;
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
result += "\x1B[0m";
|
|
2006
|
+
return result;
|
|
2007
|
+
}
|
|
2008
|
+
function wrapText(text, maxWidth) {
|
|
2009
|
+
if (maxWidth <= 0) return [text];
|
|
2010
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
2011
|
+
if (words.length === 0) return [];
|
|
2012
|
+
const lines = [];
|
|
2013
|
+
let current = "";
|
|
2014
|
+
for (const word of words) {
|
|
2015
|
+
if (!current) {
|
|
2016
|
+
current = word;
|
|
2017
|
+
} else {
|
|
2018
|
+
const proposed = current + " " + word;
|
|
2019
|
+
if (stripAnsi(proposed).length <= maxWidth) {
|
|
2020
|
+
current = proposed;
|
|
2021
|
+
} else {
|
|
2022
|
+
lines.push(current);
|
|
2023
|
+
current = word;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
if (current) lines.push(current);
|
|
2028
|
+
return lines;
|
|
2029
|
+
}
|
|
2030
|
+
function fetchTargetTitle(repoRoot, target) {
|
|
2031
|
+
try {
|
|
2032
|
+
const prMatch = target.match(/PR\s*#?(\d+)/i);
|
|
2033
|
+
if (prMatch) {
|
|
2034
|
+
const stdout = execFileSync("gh", ["pr", "view", prMatch[1], "--json", "title", "-q", ".title"], {
|
|
2035
|
+
cwd: repoRoot,
|
|
2036
|
+
encoding: "utf8",
|
|
2037
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2038
|
+
timeout: 4e3
|
|
2039
|
+
});
|
|
2040
|
+
return stdout.trim() || null;
|
|
2041
|
+
}
|
|
2042
|
+
const issueMatch = target.match(/Issue\s*#?(\d+)/i);
|
|
2043
|
+
if (issueMatch) {
|
|
2044
|
+
const stdout = execFileSync("gh", ["issue", "view", issueMatch[1], "--json", "title", "-q", ".title"], {
|
|
2045
|
+
cwd: repoRoot,
|
|
2046
|
+
encoding: "utf8",
|
|
2047
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2048
|
+
timeout: 4e3
|
|
2049
|
+
});
|
|
2050
|
+
return stdout.trim() || null;
|
|
2051
|
+
}
|
|
2052
|
+
} catch {
|
|
2053
|
+
return null;
|
|
2054
|
+
}
|
|
2055
|
+
return null;
|
|
2056
|
+
}
|
|
2057
|
+
async function fetchTargetTitleAsync(repoRoot, target) {
|
|
2058
|
+
try {
|
|
2059
|
+
const prMatch = target.match(/PR\s*#?(\d+)/i);
|
|
2060
|
+
if (prMatch) {
|
|
2061
|
+
const { stdout } = await execFileAsync2("gh", ["pr", "view", prMatch[1], "--json", "title", "-q", ".title"], {
|
|
2062
|
+
cwd: repoRoot,
|
|
2063
|
+
encoding: "utf8",
|
|
2064
|
+
timeout: 3e3
|
|
2065
|
+
});
|
|
2066
|
+
return stdout.trim() || null;
|
|
2067
|
+
}
|
|
2068
|
+
const issueMatch = target.match(/Issue\s*#?(\d+)/i);
|
|
2069
|
+
if (issueMatch) {
|
|
2070
|
+
const { stdout } = await execFileAsync2("gh", ["issue", "view", issueMatch[1], "--json", "title", "-q", ".title"], {
|
|
2071
|
+
cwd: repoRoot,
|
|
2072
|
+
encoding: "utf8",
|
|
2073
|
+
timeout: 3e3
|
|
2074
|
+
});
|
|
2075
|
+
return stdout.trim() || null;
|
|
2076
|
+
}
|
|
2077
|
+
} catch {
|
|
2078
|
+
return null;
|
|
2079
|
+
}
|
|
2080
|
+
return null;
|
|
2081
|
+
}
|
|
2082
|
+
function isRoutineRunTitle(title) {
|
|
2083
|
+
return /^\[(autowork|peer-review|optimizer|issues-housekeeping|design-review)\]\s+run\b/i.test(title.trim());
|
|
2084
|
+
}
|
|
2085
|
+
function isRadarDigestTitle(title) {
|
|
2086
|
+
return /^(?:📡\s*)?(?:Upstream\s+)?Ecosystem\s+Radar:\s+Intel\s+Digest\b/i.test(title.trim()) || /\b(upstream ecosystem radar|intel digest)\b/i.test(title.trim());
|
|
2087
|
+
}
|
|
2088
|
+
function sanitizeWorktreePaths(text) {
|
|
2089
|
+
let cleaned = text.replace(/file:\/\/\/[^\s"'()]+?\/\.jonah-fleet\/worktrees\/[^/\s"'()]+\//g, "");
|
|
2090
|
+
cleaned = cleaned.replace(/(?:^|[\s"'(`[])(?:\/[^\s"'()]+?)?\.jonah-fleet\/worktrees\/[^/\s"'()]+\//g, (match) => {
|
|
2091
|
+
const prefix = match.charAt(0);
|
|
2092
|
+
return prefix === "/" ? "" : prefix;
|
|
2093
|
+
});
|
|
2094
|
+
cleaned = cleaned.replace(/\[`?([^`\]]+?)`?\]\(file:\/\/\/[^\s)]+\)/g, "`$1`");
|
|
2095
|
+
return cleaned;
|
|
2096
|
+
}
|
|
2097
|
+
function extractExecutionSummary(output) {
|
|
2098
|
+
const summaryHeaderRegex = /#{1,3}\s+([A-Za-z0-9\s_-]*?(?:Execution|Review|Autowork)\s+Summary[\s\S]*)/i;
|
|
2099
|
+
const match = output.match(summaryHeaderRegex);
|
|
2100
|
+
if (!match) return null;
|
|
2101
|
+
let summary = match[1].trim();
|
|
2102
|
+
const trailingSeparators = [
|
|
2103
|
+
"\u2713 Local peer-review completed",
|
|
2104
|
+
"\u2713 Local peer-review on",
|
|
2105
|
+
"\u26A0\uFE0F Local peer-review on",
|
|
2106
|
+
"\u2713 Targeted peer-review on",
|
|
2107
|
+
"\u26A0\uFE0F Targeted peer-review on",
|
|
2108
|
+
"\u2713 Local autowork completed",
|
|
2109
|
+
"\u2713 Local agent session",
|
|
2110
|
+
"Peer Review Watchdog:",
|
|
2111
|
+
"Autowork Backlog Scan:"
|
|
2112
|
+
];
|
|
2113
|
+
for (const sep of trailingSeparators) {
|
|
2114
|
+
const idx = summary.indexOf(sep);
|
|
2115
|
+
if (idx !== -1) {
|
|
2116
|
+
summary = summary.slice(0, idx).trim();
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
const timestampMatch = summary.match(/\n\s*\[\d{1,2}:\d{2}:\d{2}\s*(?:AM|PM)?\][\s\S]*/);
|
|
2120
|
+
if (timestampMatch && timestampMatch.index !== void 0) {
|
|
2121
|
+
summary = summary.slice(0, timestampMatch.index).trim();
|
|
2122
|
+
}
|
|
2123
|
+
return sanitizeWorktreePaths(summary);
|
|
2124
|
+
}
|
|
2125
|
+
function findLatestRunLog(repoRoot, routine) {
|
|
2126
|
+
const runsDir = path7.join(repoRoot, ".jonah-fleet", "runs");
|
|
2127
|
+
if (fs7.existsSync(runsDir)) {
|
|
2128
|
+
try {
|
|
2129
|
+
const files = fs7.readdirSync(runsDir).filter((f) => f.startsWith(`${routine}-`) && f.endsWith(".md"));
|
|
2130
|
+
if (files.length > 0) {
|
|
2131
|
+
files.sort().reverse();
|
|
2132
|
+
return path7.join(runsDir, files[0]);
|
|
2133
|
+
}
|
|
2134
|
+
} catch {
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
const logsDir = path7.join(repoRoot, ".github", "prompts", "logs", routine);
|
|
2138
|
+
if (fs7.existsSync(logsDir)) {
|
|
2139
|
+
try {
|
|
2140
|
+
const files = fs7.readdirSync(logsDir).filter((f) => f.endsWith(".md") && !f.startsWith("_"));
|
|
2141
|
+
if (files.length > 0) {
|
|
2142
|
+
files.sort().reverse();
|
|
2143
|
+
return path7.join(logsDir, files[0]);
|
|
2144
|
+
}
|
|
2145
|
+
} catch {
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
return null;
|
|
2149
|
+
}
|
|
2150
|
+
function parseRunLog(logContent) {
|
|
2151
|
+
const summary = {
|
|
2152
|
+
passes: [],
|
|
2153
|
+
actions: []
|
|
2154
|
+
};
|
|
2155
|
+
const lines = logContent.split("\n");
|
|
2156
|
+
let inMetadata = false;
|
|
1640
2157
|
let inDoD = false;
|
|
1641
2158
|
let inFindings = false;
|
|
1642
2159
|
let inActions = false;
|
|
@@ -1849,12 +2366,12 @@ function formatActionDescription(toolName, params) {
|
|
|
1849
2366
|
if (name === "view_file") {
|
|
1850
2367
|
const rawPath = params?.AbsolutePath || params?.TargetFile || params?.path || params?.file || "";
|
|
1851
2368
|
if (!rawPath) return "Reading file";
|
|
1852
|
-
return `Reading ${
|
|
2369
|
+
return `Reading ${path7.basename(rawPath)}`;
|
|
1853
2370
|
}
|
|
1854
2371
|
if (name === "replace_file_content" || name === "write_to_file" || name === "multi_replace_file_content") {
|
|
1855
2372
|
const rawPath = params?.TargetFile || params?.AbsolutePath || params?.path || params?.file || "";
|
|
1856
2373
|
if (!rawPath) return "Editing file";
|
|
1857
|
-
return `Editing ${
|
|
2374
|
+
return `Editing ${path7.basename(rawPath)}`;
|
|
1858
2375
|
}
|
|
1859
2376
|
if (name === "grep_search") {
|
|
1860
2377
|
const query = params?.Query || params?.query || params?.pattern || "";
|
|
@@ -1869,7 +2386,7 @@ function formatActionDescription(toolName, params) {
|
|
|
1869
2386
|
if (name === "list_dir") {
|
|
1870
2387
|
const dirPath = params?.DirectoryPath || params?.path || "";
|
|
1871
2388
|
if (!dirPath) return "Listing directory";
|
|
1872
|
-
const base =
|
|
2389
|
+
const base = path7.basename(dirPath.replace(/[/\\]+$/, "")) || dirPath;
|
|
1873
2390
|
return `Listing directory ${base}`;
|
|
1874
2391
|
}
|
|
1875
2392
|
if (name === "manage_subagents") {
|
|
@@ -2015,12 +2532,12 @@ function formatEvaluatingDescription(toolName, params, output) {
|
|
|
2015
2532
|
}
|
|
2016
2533
|
if (name === "view_file") {
|
|
2017
2534
|
const rawPath = params?.AbsolutePath || params?.TargetFile || params?.path || params?.file || "";
|
|
2018
|
-
if (rawPath) return `Evaluating ${
|
|
2535
|
+
if (rawPath) return `Evaluating ${path7.basename(rawPath)}...`;
|
|
2019
2536
|
return "Evaluating file content...";
|
|
2020
2537
|
}
|
|
2021
2538
|
if (name === "replace_file_content" || name === "write_to_file" || name === "multi_replace_file_content") {
|
|
2022
2539
|
const rawPath = params?.TargetFile || params?.AbsolutePath || params?.path || params?.file || "";
|
|
2023
|
-
if (rawPath) return `Verifying edits to ${
|
|
2540
|
+
if (rawPath) return `Verifying edits to ${path7.basename(rawPath)}...`;
|
|
2024
2541
|
return "Verifying file changes...";
|
|
2025
2542
|
}
|
|
2026
2543
|
if (name === "grep_search") {
|
|
@@ -2035,7 +2552,7 @@ function formatEvaluatingDescription(toolName, params, output) {
|
|
|
2035
2552
|
}
|
|
2036
2553
|
if (name === "list_dir") {
|
|
2037
2554
|
const dirPath = params?.DirectoryPath || params?.path || "";
|
|
2038
|
-
const base =
|
|
2555
|
+
const base = path7.basename(dirPath.replace(/[/\\]+$/, "")) || dirPath;
|
|
2039
2556
|
if (base) return `Evaluating directory contents (${base})...`;
|
|
2040
2557
|
return "Evaluating directory contents...";
|
|
2041
2558
|
}
|
|
@@ -2109,23 +2626,23 @@ function detectPeerReviewOutcome(options) {
|
|
|
2109
2626
|
function formatPeerReviewOutcomeMessage(targetLabel, outcome, prefix = "Local") {
|
|
2110
2627
|
switch (outcome) {
|
|
2111
2628
|
case "merged":
|
|
2112
|
-
return
|
|
2629
|
+
return pc2.green(`\u2713 ${prefix} peer-review on ${targetLabel}: merged into main.
|
|
2113
2630
|
`);
|
|
2114
2631
|
case "bounced":
|
|
2115
|
-
return
|
|
2632
|
+
return pc2.yellow(`\u26A0\uFE0F ${prefix} peer-review on ${targetLabel}: bounced to draft.
|
|
2116
2633
|
`);
|
|
2117
2634
|
case "deferred_ci":
|
|
2118
|
-
return
|
|
2635
|
+
return pc2.cyan(`\u23F3 ${prefix} peer-review on ${targetLabel}: approved (deferred merge pending CI).
|
|
2119
2636
|
`);
|
|
2120
2637
|
case "escalated":
|
|
2121
|
-
return
|
|
2638
|
+
return pc2.red(`\u{1F6A8} ${prefix} peer-review on ${targetLabel}: escalated to human.
|
|
2122
2639
|
`);
|
|
2123
2640
|
case "no_prs":
|
|
2124
|
-
return
|
|
2641
|
+
return pc2.dim(`\u2713 ${prefix} peer-review: no PRs to review.
|
|
2125
2642
|
`);
|
|
2126
2643
|
case "unknown":
|
|
2127
2644
|
default:
|
|
2128
|
-
return
|
|
2645
|
+
return pc2.green(`\u2713 ${prefix} peer-review on ${targetLabel} completed successfully.
|
|
2129
2646
|
`);
|
|
2130
2647
|
}
|
|
2131
2648
|
}
|
|
@@ -2138,9 +2655,9 @@ function renderSummaryCard(options) {
|
|
|
2138
2655
|
const latestLog = findLatestRunLog(options.repoRoot, options.routine);
|
|
2139
2656
|
if (latestLog) {
|
|
2140
2657
|
try {
|
|
2141
|
-
const content =
|
|
2658
|
+
const content = fs7.readFileSync(latestLog, "utf8");
|
|
2142
2659
|
parsedFromLog = parseRunLog(content);
|
|
2143
|
-
parsedFromLog.logPath =
|
|
2660
|
+
parsedFromLog.logPath = path7.relative(options.repoRoot, latestLog);
|
|
2144
2661
|
} catch {
|
|
2145
2662
|
}
|
|
2146
2663
|
}
|
|
@@ -2197,12 +2714,16 @@ function renderSummaryCard(options) {
|
|
|
2197
2714
|
}
|
|
2198
2715
|
}
|
|
2199
2716
|
const durationStr = options.durationMs ? `${Math.round(options.durationMs / 1e3)}s` : parsedFromLog?.duration || "";
|
|
2200
|
-
const border = (s) =>
|
|
2717
|
+
const border = (s) => pc2.dim(pc2.gray(s));
|
|
2201
2718
|
const lines = [];
|
|
2202
2719
|
lines.push(border(`\u250C${horizontal}\u2510`));
|
|
2203
|
-
const headerParts = [
|
|
2204
|
-
if (target) headerParts.push(
|
|
2205
|
-
if (durationStr) headerParts.push(
|
|
2720
|
+
const headerParts = [pc2.bold(pc2.white(options.routine.toUpperCase()))];
|
|
2721
|
+
if (target) headerParts.push(pc2.yellow(target));
|
|
2722
|
+
if (durationStr) headerParts.push(pc2.dim(`(${durationStr})`));
|
|
2723
|
+
if (options.usage?.totalTokens) {
|
|
2724
|
+
const kTokens = (options.usage.totalTokens / 1e3).toFixed(1) + "k tokens";
|
|
2725
|
+
headerParts.push(pc2.cyan(kTokens));
|
|
2726
|
+
}
|
|
2206
2727
|
const headerContent = headerParts.join(" \xB7 ");
|
|
2207
2728
|
const headerPlain = stripAnsi(headerContent);
|
|
2208
2729
|
lines.push(
|
|
@@ -2212,24 +2733,24 @@ function renderSummaryCard(options) {
|
|
|
2212
2733
|
const titlePrefix = " Title: ";
|
|
2213
2734
|
const wrappedTitle = wrapText(title, width - 4 - titlePrefix.length);
|
|
2214
2735
|
for (let i = 0; i < wrappedTitle.length; i++) {
|
|
2215
|
-
const prefix = i === 0 ?
|
|
2736
|
+
const prefix = i === 0 ? pc2.dim(titlePrefix) : " ".repeat(titlePrefix.length);
|
|
2216
2737
|
const text = wrappedTitle[i];
|
|
2217
2738
|
const plainLen = titlePrefix.length + stripAnsi(text).length;
|
|
2218
2739
|
lines.push(
|
|
2219
|
-
border("\u2502") + ` ${prefix}${
|
|
2740
|
+
border("\u2502") + ` ${prefix}${pc2.white(pc2.bold(text))}` + " ".repeat(Math.max(1, width - 3 - plainLen)) + border("\u2502")
|
|
2220
2741
|
);
|
|
2221
2742
|
}
|
|
2222
2743
|
}
|
|
2223
2744
|
if (decision) {
|
|
2224
|
-
let decisionBadge =
|
|
2745
|
+
let decisionBadge = pc2.green(`\u2714 ${decision}`);
|
|
2225
2746
|
if (/bounce|draft|reject|fail/i.test(decision)) {
|
|
2226
|
-
decisionBadge =
|
|
2747
|
+
decisionBadge = pc2.yellow(`\u26A0\uFE0F ${decision}`);
|
|
2227
2748
|
} else if (/defer|pending|wait/i.test(decision)) {
|
|
2228
|
-
decisionBadge =
|
|
2749
|
+
decisionBadge = pc2.cyan(`\u23F3 ${decision}`);
|
|
2229
2750
|
} else if (/escalat/i.test(decision)) {
|
|
2230
|
-
decisionBadge =
|
|
2751
|
+
decisionBadge = pc2.red(`\u{1F6A8} ${decision}`);
|
|
2231
2752
|
} else if (/no prs/i.test(decision)) {
|
|
2232
|
-
decisionBadge =
|
|
2753
|
+
decisionBadge = pc2.dim(decision);
|
|
2233
2754
|
}
|
|
2234
2755
|
const decisionPlain = ` Action: ${decision}`;
|
|
2235
2756
|
lines.push(
|
|
@@ -2250,11 +2771,11 @@ function renderSummaryCard(options) {
|
|
|
2250
2771
|
if (line.startsWith("### ")) {
|
|
2251
2772
|
const heading = line.replace("### ", "").trim();
|
|
2252
2773
|
bodyLines.push(
|
|
2253
|
-
border("\u2502") + ` ${
|
|
2774
|
+
border("\u2502") + ` ${pc2.bold(pc2.cyan(heading))}` + " ".repeat(Math.max(1, width - 3 - heading.length)) + border("\u2502")
|
|
2254
2775
|
);
|
|
2255
2776
|
} else if (line.startsWith("- ") || line.startsWith("* ")) {
|
|
2256
2777
|
const item = sanitizeWorktreePaths(line.slice(2)).trim();
|
|
2257
|
-
const formatted = item.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*([^*]+)\*\*/g, (_, text) =>
|
|
2778
|
+
const formatted = item.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*([^*]+)\*\*/g, (_, text) => pc2.bold(text)).replace(/`([^`]+)`/g, (_, code) => pc2.yellow(code));
|
|
2258
2779
|
const wrapped = wrapText(formatted, width - 8);
|
|
2259
2780
|
for (let i = 0; i < wrapped.length; i++) {
|
|
2260
2781
|
const wLine = wrapped[i];
|
|
@@ -2271,7 +2792,7 @@ function renderSummaryCard(options) {
|
|
|
2271
2792
|
}
|
|
2272
2793
|
} else if (/^[0-9]+\.\s+/.test(line)) {
|
|
2273
2794
|
const item = sanitizeWorktreePaths(line.replace(/^[0-9]+\.\s+/, "")).trim();
|
|
2274
|
-
const formatted = item.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*([^*]+)\*\*/g, (_, text) =>
|
|
2795
|
+
const formatted = item.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*([^*]+)\*\*/g, (_, text) => pc2.bold(text)).replace(/`([^`]+)`/g, (_, code) => pc2.yellow(code));
|
|
2275
2796
|
const wrapped = wrapText(formatted, width - 8);
|
|
2276
2797
|
for (let i = 0; i < wrapped.length; i++) {
|
|
2277
2798
|
const wLine = wrapped[i];
|
|
@@ -2289,9 +2810,9 @@ function renderSummaryCard(options) {
|
|
|
2289
2810
|
}
|
|
2290
2811
|
}
|
|
2291
2812
|
} else if (parsedFromLog && parsedFromLog.passes && parsedFromLog.passes.length > 0) {
|
|
2292
|
-
bodyLines.push(border("\u2502") + ` ${
|
|
2813
|
+
bodyLines.push(border("\u2502") + ` ${pc2.bold("Verification Passes:")}` + " ".repeat(Math.max(1, width - 23)) + border("\u2502"));
|
|
2293
2814
|
for (const pass of parsedFromLog.passes.slice(0, 6)) {
|
|
2294
|
-
const icon = pass.status === "pass" ?
|
|
2815
|
+
const icon = pass.status === "pass" ? pc2.green("\u2714") : pc2.red("\u2716");
|
|
2295
2816
|
let criterionName = pass.name;
|
|
2296
2817
|
const colonIdx = criterionName.indexOf(":");
|
|
2297
2818
|
if (colonIdx > 10 && colonIdx < 40) {
|
|
@@ -2308,13 +2829,13 @@ function renderSummaryCard(options) {
|
|
|
2308
2829
|
);
|
|
2309
2830
|
} else {
|
|
2310
2831
|
bodyLines.push(
|
|
2311
|
-
border("\u2502") + ` ${
|
|
2832
|
+
border("\u2502") + ` ${pc2.dim(wLine)}` + " ".repeat(Math.max(1, width - 5 - wPlain.length)) + border("\u2502")
|
|
2312
2833
|
);
|
|
2313
2834
|
}
|
|
2314
2835
|
}
|
|
2315
2836
|
}
|
|
2316
2837
|
if (parsedFromLog.actions && parsedFromLog.actions.length > 0) {
|
|
2317
|
-
bodyLines.push(border("\u2502") + ` ${
|
|
2838
|
+
bodyLines.push(border("\u2502") + ` ${pc2.bold("Actions Taken:")}` + " ".repeat(Math.max(1, width - 16)) + border("\u2502"));
|
|
2318
2839
|
for (const action of parsedFromLog.actions.slice(0, 4)) {
|
|
2319
2840
|
const wrapped = wrapText(action, width - 8);
|
|
2320
2841
|
for (let i = 0; i < wrapped.length; i++) {
|
|
@@ -2348,1552 +2869,1194 @@ function renderSummaryCard(options) {
|
|
|
2348
2869
|
for (let i = 0; i < wrapped.length; i++) {
|
|
2349
2870
|
const wLine = wrapped[i];
|
|
2350
2871
|
const wPlain = stripAnsi(wLine);
|
|
2351
|
-
const icon = /merged/i.test(decision) ?
|
|
2872
|
+
const icon = /merged/i.test(decision) ? pc2.green(" \u2714 ") : pc2.yellow(" \u26A0\uFE0F ");
|
|
2352
2873
|
const iconPlain = " \u2714 ";
|
|
2353
2874
|
if (i === 0) {
|
|
2354
2875
|
bodyLines.push(
|
|
2355
2876
|
border("\u2502") + `${icon}${wLine}` + " ".repeat(Math.max(1, width - 1 - iconPlain.length - wPlain.length)) + border("\u2502")
|
|
2356
2877
|
);
|
|
2357
2878
|
} else {
|
|
2358
|
-
bodyLines.push(
|
|
2359
|
-
border("\u2502") + ` ${wLine}` + " ".repeat(Math.max(1, width - 7 - wPlain.length)) + border("\u2502")
|
|
2360
|
-
);
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2363
|
-
}
|
|
2364
|
-
}
|
|
2365
|
-
if (bodyLines.length > 0) {
|
|
2366
|
-
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
2367
|
-
lines.push(...bodyLines);
|
|
2368
|
-
}
|
|
2369
|
-
if (parsedFromLog?.logPath) {
|
|
2370
|
-
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
2371
|
-
const logInfo = ` Run log: ${pc.dim(parsedFromLog.logPath)}`;
|
|
2372
|
-
const logPlain = ` Run log: ${parsedFromLog.logPath}`;
|
|
2373
|
-
lines.push(border("\u2502") + logInfo + " ".repeat(Math.max(1, width - 2 - logPlain.length)) + border("\u2502"));
|
|
2374
|
-
}
|
|
2375
|
-
lines.push(border(`\u2514${horizontal}\u2518`));
|
|
2376
|
-
return lines.join("\n");
|
|
2377
|
-
}
|
|
2378
|
-
function renderErrorCard(options) {
|
|
2379
|
-
const width = Math.min(Math.max((process.stdout.columns || 80) - 4, 60), 86);
|
|
2380
|
-
const horizontal = "\u2500".repeat(width - 2);
|
|
2381
|
-
const lines = [];
|
|
2382
|
-
lines.push(pc.red(`\u250C${horizontal}\u2510`));
|
|
2383
|
-
const target = options.pr ? ` \xB7 PR #${options.pr}` : options.issue ? ` \xB7 Issue #${options.issue}` : "";
|
|
2384
|
-
const durationStr = options.durationMs ? ` (${Math.round(options.durationMs / 1e3)}s)` : "";
|
|
2385
|
-
const routineUpper = options.routine.toUpperCase();
|
|
2386
|
-
const header = ` \u2717 Routine '${routineUpper}' Failed (Exit Code ${options.exitCode})${target}${durationStr}`;
|
|
2387
|
-
const headerPlain = ` \u2717 Routine '${routineUpper}' Failed (Exit Code ${options.exitCode})${target}${durationStr}`;
|
|
2388
|
-
lines.push(
|
|
2389
|
-
pc.red("\u2502") + pc.bold(pc.red(header.slice(0, width - 3))) + " ".repeat(Math.max(1, width - 2 - headerPlain.length)) + pc.red("\u2502")
|
|
2390
|
-
);
|
|
2391
|
-
lines.push(pc.red(`\u251C${horizontal}\u2524`));
|
|
2392
|
-
const logPath = path6.join(options.repoRoot, ".jonah-fleet", "daemon.log");
|
|
2393
|
-
lines.push(pc.red("\u2502") + pc.yellow(" Recent Log Output:") + " ".repeat(Math.max(1, width - 21)) + pc.red("\u2502"));
|
|
2394
|
-
if (fs6.existsSync(logPath)) {
|
|
2395
|
-
try {
|
|
2396
|
-
const logContent = fs6.readFileSync(logPath, "utf8");
|
|
2397
|
-
const allLines = logContent.split("\n").filter((l) => l.trim().length > 0);
|
|
2398
|
-
const tailLines = allLines.slice(-10);
|
|
2399
|
-
for (const line of tailLines) {
|
|
2400
|
-
const cleaned = sanitizeWorktreePaths(line).trim();
|
|
2401
|
-
const truncated = cleaned.length > width - 6 ? cleaned.slice(0, width - 9) + "..." : cleaned;
|
|
2402
|
-
lines.push(pc.red("\u2502") + pc.dim(` ${truncated}`) + " ".repeat(Math.max(1, width - 4 - truncated.length)) + pc.red("\u2502"));
|
|
2403
|
-
}
|
|
2404
|
-
} catch {
|
|
2405
|
-
lines.push(
|
|
2406
|
-
pc.red("\u2502") + pc.dim(" (Could not read .jonah-fleet/daemon.log)") + " ".repeat(Math.max(1, width - 45)) + pc.red("\u2502")
|
|
2407
|
-
);
|
|
2408
|
-
}
|
|
2409
|
-
} else {
|
|
2410
|
-
lines.push(pc.red("\u2502") + pc.dim(" (No daemon.log found)") + " ".repeat(Math.max(1, width - 26)) + pc.red("\u2502"));
|
|
2411
|
-
}
|
|
2412
|
-
lines.push(pc.red(`\u251C${horizontal}\u2524`));
|
|
2413
|
-
const relLogPath = path6.relative(options.repoRoot, logPath) || ".jonah-fleet/daemon.log";
|
|
2414
|
-
const footer = ` Full trace: ${relLogPath}`;
|
|
2415
|
-
const truncatedFooter = footer.slice(0, width - 4);
|
|
2416
|
-
lines.push(pc.red("\u2502") + pc.dim(truncatedFooter) + " ".repeat(Math.max(1, width - 2 - truncatedFooter.length)) + pc.red("\u2502"));
|
|
2417
|
-
lines.push(pc.red(`\u2514${horizontal}\u2518`));
|
|
2418
|
-
return lines.join("\n");
|
|
2419
|
-
}
|
|
2420
|
-
var TerminalSpinner = class {
|
|
2421
|
-
frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
2422
|
-
currentFrame = 0;
|
|
2423
|
-
intervalId = null;
|
|
2424
|
-
startTime = 0;
|
|
2425
|
-
message = "";
|
|
2426
|
-
isRunning = false;
|
|
2427
|
-
isTTY;
|
|
2428
|
-
lastPrintedMessage = "";
|
|
2429
|
-
constructor() {
|
|
2430
|
-
this.isTTY = Boolean(process.stderr.isTTY);
|
|
2431
|
-
}
|
|
2432
|
-
start(initialMessage) {
|
|
2433
|
-
this.message = initialMessage;
|
|
2434
|
-
this.startTime = Date.now();
|
|
2435
|
-
this.isRunning = true;
|
|
2436
|
-
if (!this.isTTY) {
|
|
2437
|
-
this.lastPrintedMessage = initialMessage;
|
|
2438
|
-
process.stderr.write(`[jonah-fleet] ${initialMessage}
|
|
2439
|
-
`);
|
|
2440
|
-
return;
|
|
2441
|
-
}
|
|
2442
|
-
this.intervalId = setInterval(() => {
|
|
2443
|
-
this.render();
|
|
2444
|
-
}, 80);
|
|
2445
|
-
}
|
|
2446
|
-
update(newMessage) {
|
|
2447
|
-
this.message = newMessage;
|
|
2448
|
-
if (!this.isTTY) {
|
|
2449
|
-
if (newMessage !== this.lastPrintedMessage) {
|
|
2450
|
-
this.lastPrintedMessage = newMessage;
|
|
2451
|
-
process.stderr.write(`[jonah-fleet] ${newMessage}
|
|
2452
|
-
`);
|
|
2453
|
-
}
|
|
2454
|
-
}
|
|
2455
|
-
}
|
|
2456
|
-
formatLine(message, maxWidth) {
|
|
2457
|
-
const cols = maxWidth ?? (process.stderr.columns || process.stdout.columns || 80);
|
|
2458
|
-
const frame = pc.cyan(this.frames[this.currentFrame]);
|
|
2459
|
-
const elapsedSeconds = Math.floor((Date.now() - this.startTime) / 1e3);
|
|
2460
|
-
const mins = Math.floor(elapsedSeconds / 60);
|
|
2461
|
-
const secs = elapsedSeconds % 60;
|
|
2462
|
-
const timePlain = `[${mins}m ${secs < 10 ? "0" : ""}${secs}s]`;
|
|
2463
|
-
const timeStr = pc.dim(timePlain);
|
|
2464
|
-
const fixedWidth = 6 + timePlain.length;
|
|
2465
|
-
const availableMsgWidth = Math.max(0, cols - fixedWidth - 1);
|
|
2466
|
-
let truncatedMsg = message;
|
|
2467
|
-
if (stripAnsi(message).length > availableMsgWidth) {
|
|
2468
|
-
truncatedMsg = availableMsgWidth > 3 ? truncateAnsi(message, availableMsgWidth - 3) + "..." : truncateAnsi(message, availableMsgWidth);
|
|
2469
|
-
}
|
|
2470
|
-
return ` ${frame} ${truncatedMsg} ${timeStr}`;
|
|
2471
|
-
}
|
|
2472
|
-
render() {
|
|
2473
|
-
if (!this.isRunning || !this.isTTY) return;
|
|
2474
|
-
this.currentFrame = (this.currentFrame + 1) % this.frames.length;
|
|
2475
|
-
const line = this.formatLine(this.message);
|
|
2476
|
-
process.stderr.write(`\r\x1B[K${line}`);
|
|
2477
|
-
}
|
|
2478
|
-
stop() {
|
|
2479
|
-
if (!this.isRunning) return;
|
|
2480
|
-
this.isRunning = false;
|
|
2481
|
-
if (this.intervalId) {
|
|
2482
|
-
clearInterval(this.intervalId);
|
|
2483
|
-
this.intervalId = null;
|
|
2484
|
-
}
|
|
2485
|
-
if (this.isTTY) {
|
|
2486
|
-
process.stderr.write("\r\x1B[K");
|
|
2487
|
-
}
|
|
2488
|
-
}
|
|
2489
|
-
};
|
|
2490
|
-
function renderBacklogDiagnosticCard(report, options) {
|
|
2491
|
-
const width = options?.width || Math.min(Math.max((process.stdout.columns || 80) - 4, 64), 90);
|
|
2492
|
-
const horizontal = "\u2500".repeat(width - 2);
|
|
2493
|
-
const border = (s) => pc.dim(pc.gray(s));
|
|
2494
|
-
const lines = [];
|
|
2495
|
-
lines.push(border(`\u250C${horizontal}\u2510`));
|
|
2496
|
-
const total = report.total ?? (report.actionable?.length || 0) + (report.inProgress?.length || 0) + (report.gatedHuman?.length || 0) + (report.awaitingInfo?.length || 0) + (report.guardrails?.length || 0) + (report.routineLogs?.length || 0);
|
|
2497
|
-
const actionableCount = report.actionable?.length || 0;
|
|
2498
|
-
const headerParts = [
|
|
2499
|
-
pc.bold(pc.white("AUTOWORK BACKLOG TRIAGE")),
|
|
2500
|
-
pc.yellow(`${actionableCount} Actionable`),
|
|
2501
|
-
pc.dim("(0 tokens used)")
|
|
2502
|
-
];
|
|
2503
|
-
const headerContent = headerParts.join(" \xB7 ");
|
|
2504
|
-
const headerPlain = stripAnsi(headerContent);
|
|
2505
|
-
lines.push(
|
|
2506
|
-
border("\u2502") + ` ${headerContent}` + " ".repeat(Math.max(1, width - 3 - headerPlain.length)) + border("\u2502")
|
|
2507
|
-
);
|
|
2508
|
-
const hasNoGated = (report.inProgress?.length || 0) === 0 && (report.gatedHuman?.length || 0) === 0 && (report.awaitingInfo?.length || 0) === 0 && (report.guardrails?.length || 0) === 0;
|
|
2509
|
-
if (total === 0 || hasNoGated && actionableCount === 0) {
|
|
2510
|
-
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
2511
|
-
const emptyMsg = "Backlog is completely empty. 0 open issues found.";
|
|
2512
|
-
lines.push(
|
|
2513
|
-
border("\u2502") + ` ${pc.bold(emptyMsg)}` + " ".repeat(Math.max(1, width - 4 - stripAnsi(emptyMsg).length)) + border("\u2502")
|
|
2514
|
-
);
|
|
2515
|
-
const tipMsg = "Tip: Create an issue with priority/P1 or priority/P2 to trigger Autowork.";
|
|
2516
|
-
lines.push(
|
|
2517
|
-
border("\u2502") + ` ${pc.dim(tipMsg)}` + " ".repeat(Math.max(1, width - 4 - stripAnsi(tipMsg).length)) + border("\u2502")
|
|
2518
|
-
);
|
|
2519
|
-
lines.push(border(`\u2514${horizontal}\u2518`));
|
|
2520
|
-
return lines.join("\n");
|
|
2521
|
-
}
|
|
2522
|
-
const statusMsg = `Preflight: Zero-token bypass active (no worktrees spawned)`;
|
|
2523
|
-
lines.push(
|
|
2524
|
-
border("\u2502") + ` ${pc.dim(statusMsg)}` + " ".repeat(Math.max(1, width - 4 - stripAnsi(statusMsg).length)) + border("\u2502")
|
|
2525
|
-
);
|
|
2526
|
-
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
2527
|
-
const renderSection = (title, colorFn, items, reasonLabel) => {
|
|
2528
|
-
if (!items || items.length === 0) return;
|
|
2529
|
-
const sectionHeader = ` ${colorFn(pc.bold(title))}`;
|
|
2530
|
-
lines.push(
|
|
2531
|
-
border("\u2502") + sectionHeader + " ".repeat(Math.max(1, width - 2 - stripAnsi(sectionHeader).length)) + border("\u2502")
|
|
2532
|
-
);
|
|
2533
|
-
for (const item of items) {
|
|
2534
|
-
const itemTitle = cleanTargetTitle(item.title, width - 36);
|
|
2535
|
-
const labelStr = typeof reasonLabel === "function" ? reasonLabel(item) : reasonLabel;
|
|
2536
|
-
const text = `\u2022 #${item.number}: ${itemTitle} (${labelStr})`;
|
|
2537
|
-
const wrapped = wrapText(text, width - 8);
|
|
2538
|
-
for (let i = 0; i < wrapped.length; i++) {
|
|
2539
|
-
const wLine = wrapped[i];
|
|
2540
|
-
const wPlain = stripAnsi(wLine);
|
|
2541
|
-
lines.push(
|
|
2542
|
-
border("\u2502") + ` ${wLine}` + " ".repeat(Math.max(1, width - 5 - wPlain.length)) + border("\u2502")
|
|
2543
|
-
);
|
|
2544
|
-
}
|
|
2545
|
-
}
|
|
2546
|
-
};
|
|
2547
|
-
if (report.gatedHuman && report.gatedHuman.length > 0) {
|
|
2548
|
-
renderSection(
|
|
2549
|
-
"Gated by Human Decision (needs-human / needs-attention / radar):",
|
|
2550
|
-
pc.red,
|
|
2551
|
-
report.gatedHuman,
|
|
2552
|
-
(item) => {
|
|
2553
|
-
const labels = (item.labels || []).map(
|
|
2554
|
-
(l) => (typeof l === "string" ? l : l.name).toLowerCase()
|
|
2555
|
-
);
|
|
2556
|
-
if (isRadarDigestTitle(item.title) || labels.includes("radar")) return "Radar: intel digest";
|
|
2557
|
-
if (labels.includes("needs-attention")) return "Gated: needs-attention";
|
|
2558
|
-
return "Gated: needs-human";
|
|
2559
|
-
}
|
|
2560
|
-
);
|
|
2561
|
-
}
|
|
2562
|
-
if (report.awaitingInfo && report.awaitingInfo.length > 0) {
|
|
2563
|
-
renderSection(
|
|
2564
|
-
"Awaiting Specifications (needs-info / needs-design):",
|
|
2565
|
-
pc.yellow,
|
|
2566
|
-
report.awaitingInfo,
|
|
2567
|
-
"Awaiting info: needs-info"
|
|
2568
|
-
);
|
|
2569
|
-
}
|
|
2570
|
-
if (report.inProgress && report.inProgress.length > 0) {
|
|
2571
|
-
renderSection("Already In Progress:", pc.blue, report.inProgress, "In Progress");
|
|
2879
|
+
bodyLines.push(
|
|
2880
|
+
border("\u2502") + ` ${wLine}` + " ".repeat(Math.max(1, width - 7 - wPlain.length)) + border("\u2502")
|
|
2881
|
+
);
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2572
2885
|
}
|
|
2573
|
-
if (
|
|
2574
|
-
|
|
2886
|
+
if (bodyLines.length > 0) {
|
|
2887
|
+
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
2888
|
+
lines.push(...bodyLines);
|
|
2575
2889
|
}
|
|
2576
|
-
if (
|
|
2577
|
-
const routineText = ` Operational Logs: ${report.routineLogs.length} routine run log(s) filtered`;
|
|
2890
|
+
if (typeof options.usage?.totalTokens === "number") {
|
|
2578
2891
|
lines.push(
|
|
2579
|
-
|
|
2892
|
+
...renderTokenQuotaSection({
|
|
2893
|
+
usage: options.usage,
|
|
2894
|
+
repoRoot: options.repoRoot,
|
|
2895
|
+
width,
|
|
2896
|
+
borderColor: border
|
|
2897
|
+
})
|
|
2580
2898
|
);
|
|
2581
2899
|
}
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
const hints = [];
|
|
2588
|
-
if (report.awaitingInfo && report.awaitingInfo.length > 0) {
|
|
2589
|
-
hints.push("Remove 'needs-info' or 'needs-design' once specifications are clarified.");
|
|
2590
|
-
}
|
|
2591
|
-
if (report.gatedHuman && report.gatedHuman.length > 0) {
|
|
2592
|
-
hints.push("Remove 'needs-human' if autonomous implementation is appropriate.");
|
|
2593
|
-
}
|
|
2594
|
-
if (report.inProgress && report.inProgress.length > 0) {
|
|
2595
|
-
hints.push("Merge or close open PRs, or unassign human assignees to allow agent pickup.");
|
|
2596
|
-
}
|
|
2597
|
-
if (hints.length === 0) {
|
|
2598
|
-
hints.push("Add priority/P1 or priority/P2 label to an issue to schedule autonomous work.");
|
|
2599
|
-
}
|
|
2600
|
-
for (const hint of hints) {
|
|
2601
|
-
const text = `\u2022 ${hint}`;
|
|
2602
|
-
const wrapped = wrapText(text, width - 8);
|
|
2603
|
-
for (const wLine of wrapped) {
|
|
2604
|
-
const wPlain = stripAnsi(wLine);
|
|
2605
|
-
lines.push(
|
|
2606
|
-
border("\u2502") + ` ${pc.dim(wLine)}` + " ".repeat(Math.max(1, width - 5 - wPlain.length)) + border("\u2502")
|
|
2607
|
-
);
|
|
2608
|
-
}
|
|
2900
|
+
if (parsedFromLog?.logPath) {
|
|
2901
|
+
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
2902
|
+
const logInfo = ` Run log: ${pc2.dim(parsedFromLog.logPath)}`;
|
|
2903
|
+
const logPlain = ` Run log: ${parsedFromLog.logPath}`;
|
|
2904
|
+
lines.push(border("\u2502") + logInfo + " ".repeat(Math.max(1, width - 2 - logPlain.length)) + border("\u2502"));
|
|
2609
2905
|
}
|
|
2610
2906
|
lines.push(border(`\u2514${horizontal}\u2518`));
|
|
2611
2907
|
return lines.join("\n");
|
|
2612
2908
|
}
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
battleshipSlate: "#829287",
|
|
2620
|
-
ordnanceFlare: "#CF8B14"
|
|
2621
|
-
};
|
|
2622
|
-
var FLEET_ASCII_EMBLEM = [
|
|
2623
|
-
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
2624
|
-
" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D",
|
|
2625
|
-
" \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 ",
|
|
2626
|
-
" \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 ",
|
|
2627
|
-
" \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 ",
|
|
2628
|
-
" \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D "
|
|
2629
|
-
];
|
|
2630
|
-
function getFleetRunBadge(runId = "${GITHUB_RUN_ID:-}", serverUrl = "${GITHUB_SERVER_URL:-https://github.com}", repository = "${GITHUB_REPOSITORY:-Jonah-Projects/jonah-fleet}") {
|
|
2631
|
-
const runUrl = `${serverUrl}/${repository}/actions/runs/${runId}`;
|
|
2632
|
-
return `[](${runUrl})`;
|
|
2633
|
-
}
|
|
2634
|
-
function getFleetFooter(runId = "${GITHUB_RUN_ID:-}", serverUrl = "${GITHUB_SERVER_URL:-https://github.com}", repository = "${GITHUB_REPOSITORY:-Jonah-Projects/jonah-fleet}") {
|
|
2635
|
-
return `${getFleetRunBadge(runId, serverUrl, repository)}
|
|
2636
|
-
|
|
2637
|
-
_Generated by [Antigravity](${serverUrl}/${repository}/actions/runs/${runId})_`;
|
|
2638
|
-
}
|
|
2639
|
-
function renderFleetBanner(options) {
|
|
2640
|
-
const width = options.width || Math.min(Math.max((process.stdout.columns || 80) - 4, 60), 80);
|
|
2641
|
-
const borderChar = "\u2500";
|
|
2642
|
-
const horizontal = borderChar.repeat(Math.max(10, width - 2));
|
|
2643
|
-
const border = (s) => pc2.dim(pc2.gray(s));
|
|
2644
|
-
const emblemLine = (s) => pc2.bold(pc2.green(s));
|
|
2909
|
+
function renderTokenQuotaSection(options) {
|
|
2910
|
+
const total = options.usage?.totalTokens;
|
|
2911
|
+
if (typeof total !== "number") return [];
|
|
2912
|
+
const border = options.borderColor ?? ((s) => pc2.dim(pc2.gray(s)));
|
|
2913
|
+
const width = options.width;
|
|
2914
|
+
const horizontal = "\u2500".repeat(width - 2);
|
|
2645
2915
|
const lines = [];
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
const header = ` \u26A1 FLEET // COMMAND \xB7 ${options.command.toUpperCase()}`;
|
|
2666
|
-
const headerPlain = stripAnsi(header);
|
|
2667
|
-
lines.push(
|
|
2668
|
-
`${border("\u2502")}${pc2.bold(pc2.cyan(header))}${" ".repeat(Math.max(1, width - 2 - headerPlain.length))}${border("\u2502")}`
|
|
2669
|
-
);
|
|
2670
|
-
const sub = ` SYS: READY \xB7 v${FLEET_VERSION}${options.subtitle ? ` \xB7 ${options.subtitle}` : ""}`;
|
|
2671
|
-
const subPlain = stripAnsi(sub);
|
|
2672
|
-
const truncatedSub = subPlain.length > width - 4 ? sub.slice(0, width - 7) + "..." : sub;
|
|
2673
|
-
const finalSubPlain = stripAnsi(truncatedSub);
|
|
2674
|
-
lines.push(
|
|
2675
|
-
`${border("\u2502")}${pc2.dim(pc2.gray(truncatedSub))}${" ".repeat(Math.max(1, width - 2 - finalSubPlain.length))}${border("\u2502")}`
|
|
2676
|
-
);
|
|
2677
|
-
}
|
|
2678
|
-
if (options.details && options.details.length > 0) {
|
|
2679
|
-
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
2680
|
-
for (const detail of options.details) {
|
|
2681
|
-
const row = ` \u2022 ${pc2.dim(pc2.gray(detail.label.toUpperCase() + ":"))} ${pc2.white(detail.value)}`;
|
|
2682
|
-
const rowPlain = stripAnsi(row);
|
|
2683
|
-
const truncatedRow = rowPlain.length > width - 4 ? ` \u2022 ${pc2.dim(pc2.gray(detail.label.toUpperCase() + ":"))} ${pc2.white(detail.value.slice(0, width - detail.label.length - 12) + "...")}` : row;
|
|
2684
|
-
const finalPlain = stripAnsi(truncatedRow);
|
|
2685
|
-
lines.push(
|
|
2686
|
-
`${border("\u2502")}${truncatedRow}${" ".repeat(Math.max(1, width - 2 - finalPlain.length))}${border("\u2502")}`
|
|
2687
|
-
);
|
|
2916
|
+
const quota = calculateTokenQuotaPercentages(total);
|
|
2917
|
+
const breakdown = formatTokenBreakdown(options.usage, "prefix");
|
|
2918
|
+
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
2919
|
+
const tokenLine1 = ` Tokens: ${pc2.bold(total.toLocaleString())}${breakdown ? pc2.dim(` (${breakdown})`) : ""}`;
|
|
2920
|
+
lines.push(border("\u2502") + tokenLine1 + " ".repeat(Math.max(1, width - 2 - stripAnsi(tokenLine1).length)) + border("\u2502"));
|
|
2921
|
+
const tokenLine2 = ` \u21B3 ${pc2.cyan(quota.pctOf5hLimit.toFixed(2) + "% of 5h limit")} \xB7 ${pc2.cyan(quota.pctOfWeeklyLimit.toFixed(2) + "% of weekly limit")}`;
|
|
2922
|
+
lines.push(border("\u2502") + tokenLine2 + " ".repeat(Math.max(1, width - 2 - stripAnsi(tokenLine2).length)) + border("\u2502"));
|
|
2923
|
+
if (options.repoRoot) {
|
|
2924
|
+
try {
|
|
2925
|
+
const rolling = getRollingWindowTokenUsage(options.repoRoot);
|
|
2926
|
+
if (rolling.windowTokens > 0 || rolling.weeklyTokens > 0) {
|
|
2927
|
+
const badge5h = formatQuotaStatusBadge(rolling.windowStatus);
|
|
2928
|
+
const badgeWeekly = formatQuotaStatusBadge(rolling.weeklyStatus);
|
|
2929
|
+
const rollingLine1 = ` Rolling 5h: ${rolling.windowTokens.toLocaleString()} / ${rolling.windowLimit.toLocaleString()} tokens (${rolling.windowPercentage.toFixed(1)}% \xB7 ${badge5h})`;
|
|
2930
|
+
lines.push(border("\u2502") + rollingLine1 + " ".repeat(Math.max(1, width - 2 - stripAnsi(rollingLine1).length)) + border("\u2502"));
|
|
2931
|
+
const rollingLine2 = ` Rolling 7d: ${rolling.weeklyTokens.toLocaleString()} / ${rolling.weeklyLimit.toLocaleString()} tokens (${rolling.weeklyPercentage.toFixed(1)}% \xB7 ${badgeWeekly})`;
|
|
2932
|
+
lines.push(border("\u2502") + rollingLine2 + " ".repeat(Math.max(1, width - 2 - stripAnsi(rollingLine2).length)) + border("\u2502"));
|
|
2933
|
+
}
|
|
2934
|
+
} catch {
|
|
2688
2935
|
}
|
|
2689
2936
|
}
|
|
2690
|
-
lines
|
|
2691
|
-
return lines.join("\n");
|
|
2692
|
-
}
|
|
2693
|
-
|
|
2694
|
-
// src/commands/init.ts
|
|
2695
|
-
async function promptQuestion(query, defaultValue) {
|
|
2696
|
-
const rl = readline.createInterface({
|
|
2697
|
-
input: process.stdin,
|
|
2698
|
-
output: process.stdout
|
|
2699
|
-
});
|
|
2700
|
-
return new Promise((resolve) => {
|
|
2701
|
-
rl.question(`${query} [${defaultValue}]: `, (answer) => {
|
|
2702
|
-
rl.close();
|
|
2703
|
-
resolve(answer.trim() || defaultValue);
|
|
2704
|
-
});
|
|
2705
|
-
});
|
|
2937
|
+
return lines;
|
|
2706
2938
|
}
|
|
2707
|
-
|
|
2708
|
-
const
|
|
2709
|
-
const
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2939
|
+
function renderErrorCard(options) {
|
|
2940
|
+
const width = Math.min(Math.max((process.stdout.columns || 80) - 4, 60), 86);
|
|
2941
|
+
const horizontal = "\u2500".repeat(width - 2);
|
|
2942
|
+
const lines = [];
|
|
2943
|
+
lines.push(pc2.red(`\u250C${horizontal}\u2510`));
|
|
2944
|
+
const target = options.pr ? ` \xB7 PR #${options.pr}` : options.issue ? ` \xB7 Issue #${options.issue}` : "";
|
|
2945
|
+
const durationStr = options.durationMs ? ` (${Math.round(options.durationMs / 1e3)}s)` : "";
|
|
2946
|
+
const routineUpper = options.routine.toUpperCase();
|
|
2947
|
+
const header = ` \u2717 Routine '${routineUpper}' Failed (Exit Code ${options.exitCode})${target}${durationStr}`;
|
|
2948
|
+
const headerPlain = ` \u2717 Routine '${routineUpper}' Failed (Exit Code ${options.exitCode})${target}${durationStr}`;
|
|
2949
|
+
lines.push(
|
|
2950
|
+
pc2.red("\u2502") + pc2.bold(pc2.red(header.slice(0, width - 3))) + " ".repeat(Math.max(1, width - 2 - headerPlain.length)) + pc2.red("\u2502")
|
|
2719
2951
|
);
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
console.log(pc3.cyan(` - Testing: ${detected.testFramework}`));
|
|
2733
|
-
}
|
|
2734
|
-
if (detected.commands.test) {
|
|
2735
|
-
console.log(pc3.cyan(` - Test Command: ${detected.commands.test}`));
|
|
2736
|
-
}
|
|
2737
|
-
const isInteractive = options.interactive ?? (process.stdin.isTTY && !options.stack && !options.testCmd);
|
|
2738
|
-
if (isInteractive && process.stdin.isTTY) {
|
|
2739
|
-
console.log(pc3.yellow("\n\u2699\uFE0F Configure project settings (press enter to accept defaults):"));
|
|
2740
|
-
const stackName = await promptQuestion("Tech Stack Name", detected.name);
|
|
2741
|
-
const pkgManager = await promptQuestion("Package Manager", detected.packageManager);
|
|
2742
|
-
const testCmd = await promptQuestion("Test Command", detected.commands.test || "npm test");
|
|
2743
|
-
const buildCmd = await promptQuestion("Build Command", detected.commands.build || "npm run build");
|
|
2744
|
-
detected = {
|
|
2745
|
-
...detected,
|
|
2746
|
-
name: stackName,
|
|
2747
|
-
language: stackName,
|
|
2748
|
-
framework: void 0,
|
|
2749
|
-
packageManager: pkgManager,
|
|
2750
|
-
commands: {
|
|
2751
|
-
...detected.commands,
|
|
2752
|
-
test: testCmd,
|
|
2753
|
-
build: buildCmd
|
|
2952
|
+
lines.push(pc2.red(`\u251C${horizontal}\u2524`));
|
|
2953
|
+
const logPath = path7.join(options.repoRoot, ".jonah-fleet", "daemon.log");
|
|
2954
|
+
lines.push(pc2.red("\u2502") + pc2.yellow(" Recent Log Output:") + " ".repeat(Math.max(1, width - 21)) + pc2.red("\u2502"));
|
|
2955
|
+
if (fs7.existsSync(logPath)) {
|
|
2956
|
+
try {
|
|
2957
|
+
const logContent = fs7.readFileSync(logPath, "utf8");
|
|
2958
|
+
const allLines = logContent.split("\n").filter((l) => l.trim().length > 0);
|
|
2959
|
+
const tailLines = allLines.slice(-10);
|
|
2960
|
+
for (const line of tailLines) {
|
|
2961
|
+
const cleaned = sanitizeWorktreePaths(line).trim();
|
|
2962
|
+
const truncated = cleaned.length > width - 6 ? cleaned.slice(0, width - 9) + "..." : cleaned;
|
|
2963
|
+
lines.push(pc2.red("\u2502") + pc2.dim(` ${truncated}`) + " ".repeat(Math.max(1, width - 4 - truncated.length)) + pc2.red("\u2502"));
|
|
2754
2964
|
}
|
|
2755
|
-
}
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
detected.framework = void 0;
|
|
2761
|
-
}
|
|
2762
|
-
if (options.packageManager) {
|
|
2763
|
-
detected.packageManager = options.packageManager;
|
|
2764
|
-
}
|
|
2765
|
-
if (options.testCmd) {
|
|
2766
|
-
detected.commands.test = options.testCmd;
|
|
2767
|
-
}
|
|
2768
|
-
if (options.buildCmd) {
|
|
2769
|
-
detected.commands.build = options.buildCmd;
|
|
2770
|
-
}
|
|
2771
|
-
let manifest = loadManifest(cwd);
|
|
2772
|
-
if (manifest && !options.force) {
|
|
2773
|
-
console.log(pc3.yellow(`
|
|
2774
|
-
\u26A0\uFE0F Found existing agents-manifest.json. Updating with preset '${preset}'...`));
|
|
2965
|
+
} catch {
|
|
2966
|
+
lines.push(
|
|
2967
|
+
pc2.red("\u2502") + pc2.dim(" (Could not read .jonah-fleet/daemon.log)") + " ".repeat(Math.max(1, width - 45)) + pc2.red("\u2502")
|
|
2968
|
+
);
|
|
2969
|
+
}
|
|
2775
2970
|
} else {
|
|
2776
|
-
|
|
2777
|
-
}
|
|
2778
|
-
saveManifest(cwd, manifest);
|
|
2779
|
-
console.log(pc3.green(`\u2713 Created/Updated agents-manifest.json`));
|
|
2780
|
-
const result = installFleet(cwd, manifest, { force: options.force, detectedStack: detected });
|
|
2781
|
-
console.log(pc3.bold("\nInstalled components:"));
|
|
2782
|
-
if (result.promptsInstalled.length > 0) {
|
|
2783
|
-
console.log(pc3.green(` \u{1F4C1} Prompts (.github/prompts/):`));
|
|
2784
|
-
result.promptsInstalled.forEach((p) => console.log(` - ${p}`));
|
|
2971
|
+
lines.push(pc2.red("\u2502") + pc2.dim(" (No daemon.log found)") + " ".repeat(Math.max(1, width - 26)) + pc2.red("\u2502"));
|
|
2785
2972
|
}
|
|
2786
|
-
if (
|
|
2787
|
-
|
|
2788
|
-
|
|
2973
|
+
if (typeof options.usage?.totalTokens === "number") {
|
|
2974
|
+
lines.push(
|
|
2975
|
+
...renderTokenQuotaSection({
|
|
2976
|
+
usage: options.usage,
|
|
2977
|
+
repoRoot: options.repoRoot,
|
|
2978
|
+
width,
|
|
2979
|
+
borderColor: (s) => pc2.red(s)
|
|
2980
|
+
})
|
|
2981
|
+
);
|
|
2789
2982
|
}
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2983
|
+
lines.push(pc2.red(`\u251C${horizontal}\u2524`));
|
|
2984
|
+
const relLogPath = path7.relative(options.repoRoot, logPath) || ".jonah-fleet/daemon.log";
|
|
2985
|
+
const footer = ` Full trace: ${relLogPath}`;
|
|
2986
|
+
const truncatedFooter = footer.slice(0, width - 4);
|
|
2987
|
+
lines.push(pc2.red("\u2502") + pc2.dim(truncatedFooter) + " ".repeat(Math.max(1, width - 2 - truncatedFooter.length)) + pc2.red("\u2502"));
|
|
2988
|
+
lines.push(pc2.red(`\u2514${horizontal}\u2518`));
|
|
2989
|
+
return lines.join("\n");
|
|
2990
|
+
}
|
|
2991
|
+
var TerminalSpinner = class {
|
|
2992
|
+
frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
2993
|
+
currentFrame = 0;
|
|
2994
|
+
intervalId = null;
|
|
2995
|
+
startTime = 0;
|
|
2996
|
+
message = "";
|
|
2997
|
+
isRunning = false;
|
|
2998
|
+
isTTY;
|
|
2999
|
+
lastPrintedMessage = "";
|
|
3000
|
+
constructor() {
|
|
3001
|
+
this.isTTY = Boolean(process.stderr.isTTY);
|
|
2793
3002
|
}
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
3003
|
+
start(initialMessage) {
|
|
3004
|
+
this.message = initialMessage;
|
|
3005
|
+
this.startTime = Date.now();
|
|
3006
|
+
this.isRunning = true;
|
|
3007
|
+
if (!this.isTTY) {
|
|
3008
|
+
this.lastPrintedMessage = initialMessage;
|
|
3009
|
+
process.stderr.write(`[jonah-fleet] ${initialMessage}
|
|
3010
|
+
`);
|
|
3011
|
+
return;
|
|
3012
|
+
}
|
|
3013
|
+
this.intervalId = setInterval(() => {
|
|
3014
|
+
this.render();
|
|
3015
|
+
}, 80);
|
|
2797
3016
|
}
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
console.log(pc3.green(" \u2713 No unused boilerplate labels found."));
|
|
3017
|
+
update(newMessage) {
|
|
3018
|
+
this.message = newMessage;
|
|
3019
|
+
if (!this.isTTY) {
|
|
3020
|
+
if (newMessage !== this.lastPrintedMessage) {
|
|
3021
|
+
this.lastPrintedMessage = newMessage;
|
|
3022
|
+
process.stderr.write(`[jonah-fleet] ${newMessage}
|
|
3023
|
+
`);
|
|
2806
3024
|
}
|
|
2807
|
-
} catch (err) {
|
|
2808
|
-
console.log(pc3.yellow(` \u26A0\uFE0F Could not prune labels: ${err.message}`));
|
|
2809
3025
|
}
|
|
2810
3026
|
}
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
import fs7 from "fs";
|
|
2825
|
-
import path7 from "path";
|
|
2826
|
-
function checkDrift(targetDir, manifest) {
|
|
2827
|
-
const templatesDir = getTemplatesDir();
|
|
2828
|
-
const report = {
|
|
2829
|
-
missingPrompts: [],
|
|
2830
|
-
modifiedPrompts: [],
|
|
2831
|
-
missingWorkflows: [],
|
|
2832
|
-
modifiedWorkflows: [],
|
|
2833
|
-
missingSkills: []
|
|
2834
|
-
};
|
|
2835
|
-
const targetPromptsDir = path7.join(targetDir, ".github/prompts");
|
|
2836
|
-
const targetWorkflowsDir = path7.join(targetDir, ".github/workflows");
|
|
2837
|
-
const targetSkillsDir = path7.join(targetDir, ".agents/skills");
|
|
2838
|
-
const basePrompts = ["ORCHESTRATION.md", "_prompt-template.md"];
|
|
2839
|
-
for (const file of basePrompts) {
|
|
2840
|
-
const src = path7.join(templatesDir, "prompts", file);
|
|
2841
|
-
const dest = path7.join(targetPromptsDir, file);
|
|
2842
|
-
if (!fs7.existsSync(dest)) {
|
|
2843
|
-
report.missingPrompts.push(file);
|
|
2844
|
-
} else if (fs7.readFileSync(src, "utf8") !== fs7.readFileSync(dest, "utf8")) {
|
|
2845
|
-
report.modifiedPrompts.push(file);
|
|
3027
|
+
formatLine(message, maxWidth) {
|
|
3028
|
+
const cols = maxWidth ?? (process.stderr.columns || process.stdout.columns || 80);
|
|
3029
|
+
const frame = pc2.cyan(this.frames[this.currentFrame]);
|
|
3030
|
+
const elapsedSeconds = Math.floor((Date.now() - this.startTime) / 1e3);
|
|
3031
|
+
const mins = Math.floor(elapsedSeconds / 60);
|
|
3032
|
+
const secs = elapsedSeconds % 60;
|
|
3033
|
+
const timePlain = `[${mins}m ${secs < 10 ? "0" : ""}${secs}s]`;
|
|
3034
|
+
const timeStr = pc2.dim(timePlain);
|
|
3035
|
+
const fixedWidth = 6 + timePlain.length;
|
|
3036
|
+
const availableMsgWidth = Math.max(0, cols - fixedWidth - 1);
|
|
3037
|
+
let truncatedMsg = message;
|
|
3038
|
+
if (stripAnsi(message).length > availableMsgWidth) {
|
|
3039
|
+
truncatedMsg = availableMsgWidth > 3 ? truncateAnsi(message, availableMsgWidth - 3) + "..." : truncateAnsi(message, availableMsgWidth);
|
|
2846
3040
|
}
|
|
3041
|
+
return ` ${frame} ${truncatedMsg} ${timeStr}`;
|
|
2847
3042
|
}
|
|
2848
|
-
|
|
2849
|
-
if (!
|
|
2850
|
-
|
|
2851
|
-
const
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
3043
|
+
render() {
|
|
3044
|
+
if (!this.isRunning || !this.isTTY) return;
|
|
3045
|
+
this.currentFrame = (this.currentFrame + 1) % this.frames.length;
|
|
3046
|
+
const line = this.formatLine(this.message);
|
|
3047
|
+
process.stderr.write(`\r\x1B[K${line}`);
|
|
3048
|
+
}
|
|
3049
|
+
stop() {
|
|
3050
|
+
if (!this.isRunning) return;
|
|
3051
|
+
this.isRunning = false;
|
|
3052
|
+
if (this.intervalId) {
|
|
3053
|
+
clearInterval(this.intervalId);
|
|
3054
|
+
this.intervalId = null;
|
|
2857
3055
|
}
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
const wfSrc = path7.join(templatesDir, "workflows", workflowFile);
|
|
2861
|
-
const wfDest = path7.join(targetWorkflowsDir, workflowFile);
|
|
2862
|
-
if (!fs7.existsSync(wfDest)) {
|
|
2863
|
-
report.missingWorkflows.push(workflowFile);
|
|
2864
|
-
} else if (fs7.existsSync(wfSrc)) {
|
|
2865
|
-
const rawSrc = fs7.readFileSync(wfSrc, "utf8");
|
|
2866
|
-
const destContent = fs7.readFileSync(wfDest, "utf8");
|
|
2867
|
-
const schedule = resolveWorkflowSchedule(workflowFile, routineName, manifest, destContent);
|
|
2868
|
-
const expectedSrc = applyWorkflowSchedule(rawSrc, schedule);
|
|
2869
|
-
if (expectedSrc !== destContent) {
|
|
2870
|
-
report.modifiedWorkflows.push(workflowFile);
|
|
2871
|
-
}
|
|
2872
|
-
}
|
|
3056
|
+
if (this.isTTY) {
|
|
3057
|
+
process.stderr.write("\r\x1B[K");
|
|
2873
3058
|
}
|
|
2874
3059
|
}
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
3060
|
+
};
|
|
3061
|
+
function renderBacklogDiagnosticCard(report, options) {
|
|
3062
|
+
const width = options?.width || Math.min(Math.max((process.stdout.columns || 80) - 4, 64), 90);
|
|
3063
|
+
const horizontal = "\u2500".repeat(width - 2);
|
|
3064
|
+
const border = (s) => pc2.dim(pc2.gray(s));
|
|
3065
|
+
const lines = [];
|
|
3066
|
+
lines.push(border(`\u250C${horizontal}\u2510`));
|
|
3067
|
+
const total = report.total ?? (report.actionable?.length || 0) + (report.inProgress?.length || 0) + (report.gatedHuman?.length || 0) + (report.awaitingInfo?.length || 0) + (report.guardrails?.length || 0) + (report.routineLogs?.length || 0);
|
|
3068
|
+
const actionableCount = report.actionable?.length || 0;
|
|
3069
|
+
const headerParts = [
|
|
3070
|
+
pc2.bold(pc2.white("AUTOWORK BACKLOG TRIAGE")),
|
|
3071
|
+
pc2.yellow(`${actionableCount} Actionable`),
|
|
3072
|
+
pc2.dim("(0 tokens used)")
|
|
3073
|
+
];
|
|
3074
|
+
const headerContent = headerParts.join(" \xB7 ");
|
|
3075
|
+
const headerPlain = stripAnsi(headerContent);
|
|
3076
|
+
lines.push(
|
|
3077
|
+
border("\u2502") + ` ${headerContent}` + " ".repeat(Math.max(1, width - 3 - headerPlain.length)) + border("\u2502")
|
|
3078
|
+
);
|
|
3079
|
+
const hasNoGated = (report.inProgress?.length || 0) === 0 && (report.gatedHuman?.length || 0) === 0 && (report.awaitingInfo?.length || 0) === 0 && (report.guardrails?.length || 0) === 0;
|
|
3080
|
+
if (total === 0 || hasNoGated && actionableCount === 0) {
|
|
3081
|
+
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
3082
|
+
const emptyMsg = "Backlog is completely empty. 0 open issues found.";
|
|
3083
|
+
lines.push(
|
|
3084
|
+
border("\u2502") + ` ${pc2.bold(emptyMsg)}` + " ".repeat(Math.max(1, width - 4 - stripAnsi(emptyMsg).length)) + border("\u2502")
|
|
3085
|
+
);
|
|
3086
|
+
const tipMsg = "Tip: Create an issue with priority/P1 or priority/P2 to trigger Autowork.";
|
|
3087
|
+
lines.push(
|
|
3088
|
+
border("\u2502") + ` ${pc2.dim(tipMsg)}` + " ".repeat(Math.max(1, width - 4 - stripAnsi(tipMsg).length)) + border("\u2502")
|
|
3089
|
+
);
|
|
3090
|
+
lines.push(border(`\u2514${horizontal}\u2518`));
|
|
3091
|
+
return lines.join("\n");
|
|
3092
|
+
}
|
|
3093
|
+
const statusMsg = `Preflight: Zero-token bypass active (no worktrees spawned)`;
|
|
3094
|
+
lines.push(
|
|
3095
|
+
border("\u2502") + ` ${pc2.dim(statusMsg)}` + " ".repeat(Math.max(1, width - 4 - stripAnsi(statusMsg).length)) + border("\u2502")
|
|
3096
|
+
);
|
|
3097
|
+
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
3098
|
+
const renderSection = (title, colorFn, items, reasonLabel) => {
|
|
3099
|
+
if (!items || items.length === 0) return;
|
|
3100
|
+
const sectionHeader = ` ${colorFn(pc2.bold(title))}`;
|
|
3101
|
+
lines.push(
|
|
3102
|
+
border("\u2502") + sectionHeader + " ".repeat(Math.max(1, width - 2 - stripAnsi(sectionHeader).length)) + border("\u2502")
|
|
3103
|
+
);
|
|
3104
|
+
for (const item of items) {
|
|
3105
|
+
const itemTitle = cleanTargetTitle(item.title, width - 36);
|
|
3106
|
+
const labelStr = typeof reasonLabel === "function" ? reasonLabel(item) : reasonLabel;
|
|
3107
|
+
const text = `\u2022 #${item.number}: ${itemTitle} (${labelStr})`;
|
|
3108
|
+
const wrapped = wrapText(text, width - 8);
|
|
3109
|
+
for (let i = 0; i < wrapped.length; i++) {
|
|
3110
|
+
const wLine = wrapped[i];
|
|
3111
|
+
const wPlain = stripAnsi(wLine);
|
|
3112
|
+
lines.push(
|
|
3113
|
+
border("\u2502") + ` ${wLine}` + " ".repeat(Math.max(1, width - 5 - wPlain.length)) + border("\u2502")
|
|
3114
|
+
);
|
|
2887
3115
|
}
|
|
2888
3116
|
}
|
|
3117
|
+
};
|
|
3118
|
+
if (report.gatedHuman && report.gatedHuman.length > 0) {
|
|
3119
|
+
renderSection(
|
|
3120
|
+
"Gated by Human Decision (needs-human / needs-attention / radar):",
|
|
3121
|
+
pc2.red,
|
|
3122
|
+
report.gatedHuman,
|
|
3123
|
+
(item) => {
|
|
3124
|
+
const labels = (item.labels || []).map(
|
|
3125
|
+
(l) => (typeof l === "string" ? l : l.name).toLowerCase()
|
|
3126
|
+
);
|
|
3127
|
+
if (isRadarDigestTitle(item.title) || labels.includes("radar")) return "Radar: intel digest";
|
|
3128
|
+
if (labels.includes("needs-attention")) return "Gated: needs-attention";
|
|
3129
|
+
return "Gated: needs-human";
|
|
3130
|
+
}
|
|
3131
|
+
);
|
|
2889
3132
|
}
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
3133
|
+
if (report.awaitingInfo && report.awaitingInfo.length > 0) {
|
|
3134
|
+
renderSection(
|
|
3135
|
+
"Awaiting Specifications (needs-info / needs-design):",
|
|
3136
|
+
pc2.yellow,
|
|
3137
|
+
report.awaitingInfo,
|
|
3138
|
+
"Awaiting info: needs-info"
|
|
3139
|
+
);
|
|
2895
3140
|
}
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
// src/commands/sync.ts
|
|
2900
|
-
async function runSync(options = {}) {
|
|
2901
|
-
const cwd = options.cwd || process.cwd();
|
|
2902
|
-
const manifest = loadManifest(cwd);
|
|
2903
|
-
if (!manifest) {
|
|
2904
|
-
console.error(pc4.red(`\u274C No agents-manifest.json found in ${cwd}. Run 'jonah-fleet init' first.`));
|
|
2905
|
-
process.exit(1);
|
|
3141
|
+
if (report.inProgress && report.inProgress.length > 0) {
|
|
3142
|
+
renderSection("Already In Progress:", pc2.blue, report.inProgress, "In Progress");
|
|
2906
3143
|
}
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
3144
|
+
if (report.guardrails && report.guardrails.length > 0) {
|
|
3145
|
+
renderSection("Metrics & Guardrails (measurement / wontfix):", pc2.magenta, report.guardrails, "Guardrail: measurement");
|
|
3146
|
+
}
|
|
3147
|
+
if (report.routineLogs && report.routineLogs.length > 0) {
|
|
3148
|
+
const routineText = ` Operational Logs: ${report.routineLogs.length} routine run log(s) filtered`;
|
|
3149
|
+
lines.push(
|
|
3150
|
+
border("\u2502") + pc2.dim(routineText) + " ".repeat(Math.max(1, width - 2 - stripAnsi(routineText).length)) + border("\u2502")
|
|
3151
|
+
);
|
|
3152
|
+
}
|
|
3153
|
+
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
3154
|
+
const hintsHeader = ` ${pc2.bold("Hints to unblock:")}`;
|
|
3155
|
+
lines.push(
|
|
3156
|
+
border("\u2502") + hintsHeader + " ".repeat(Math.max(1, width - 2 - stripAnsi(hintsHeader).length)) + border("\u2502")
|
|
2920
3157
|
);
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
const drift = checkDrift(cwd, manifest);
|
|
2925
|
-
const hasDrift = drift.missingPrompts.length > 0 || drift.modifiedPrompts.length > 0 || drift.missingWorkflows.length > 0 || drift.modifiedWorkflows.length > 0 || drift.missingSkills.length > 0;
|
|
2926
|
-
if (options.check) {
|
|
2927
|
-
if (!hasDrift && manifest.version === FLEET_VERSION) {
|
|
2928
|
-
console.log(pc4.green(`\u2713 All routines, workflows, and skills are perfectly in sync with v${FLEET_VERSION}.
|
|
2929
|
-
`));
|
|
2930
|
-
return;
|
|
2931
|
-
}
|
|
2932
|
-
console.log(pc4.yellow(`\u26A0\uFE0F Drift or updates detected:`));
|
|
2933
|
-
if (drift.missingPrompts.length > 0) console.log(pc4.red(` Missing prompts: ${drift.missingPrompts.join(", ")}`));
|
|
2934
|
-
if (drift.modifiedPrompts.length > 0) console.log(pc4.yellow(` Modified prompts: ${drift.modifiedPrompts.join(", ")}`));
|
|
2935
|
-
if (drift.missingWorkflows.length > 0) console.log(pc4.red(` Missing workflows: ${drift.missingWorkflows.join(", ")}`));
|
|
2936
|
-
if (drift.modifiedWorkflows.length > 0) console.log(pc4.yellow(` Modified workflows: ${drift.modifiedWorkflows.join(", ")}`));
|
|
2937
|
-
if (drift.missingSkills.length > 0) console.log(pc4.red(` Missing skills: ${drift.missingSkills.join(", ")}`));
|
|
2938
|
-
console.log(pc4.cyan(`
|
|
2939
|
-
Run 'jonah-fleet sync --force' to apply updates.
|
|
2940
|
-
`));
|
|
2941
|
-
return;
|
|
3158
|
+
const hints = [];
|
|
3159
|
+
if (report.awaitingInfo && report.awaitingInfo.length > 0) {
|
|
3160
|
+
hints.push("Remove 'needs-info' or 'needs-design' once specifications are clarified.");
|
|
2942
3161
|
}
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
const result = installFleet(cwd, manifest, { force: true });
|
|
2946
|
-
console.log(pc4.green(`\u2713 Synchronized with Jonah Fleet v${FLEET_VERSION}`));
|
|
2947
|
-
console.log(pc4.green(`\u2713 Updated ${result.promptsInstalled.length} prompts, ${result.workflowsInstalled.length} workflows, and ${result.skillsInstalled.length} skills.`));
|
|
2948
|
-
try {
|
|
2949
|
-
const labelRes = await provisionLabels({ cwd });
|
|
2950
|
-
if (labelRes.created.length > 0) {
|
|
2951
|
-
console.log(pc4.green(`\u2713 Provisioned ${labelRes.created.length} missing fleet label(s)`));
|
|
2952
|
-
}
|
|
2953
|
-
} catch {
|
|
3162
|
+
if (report.gatedHuman && report.gatedHuman.length > 0) {
|
|
3163
|
+
hints.push("Remove 'needs-human' if autonomous implementation is appropriate.");
|
|
2954
3164
|
}
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
// src/commands/status.ts
|
|
2959
|
-
import fs9 from "fs";
|
|
2960
|
-
import path9 from "path";
|
|
2961
|
-
import pc7 from "picocolors";
|
|
2962
|
-
|
|
2963
|
-
// src/lib/dashboard.ts
|
|
2964
|
-
import pc5 from "picocolors";
|
|
2965
|
-
function formatTokens(num) {
|
|
2966
|
-
if (num >= 1e6) {
|
|
2967
|
-
return `${(num / 1e6).toFixed(2)}M`;
|
|
3165
|
+
if (report.inProgress && report.inProgress.length > 0) {
|
|
3166
|
+
hints.push("Merge or close open PRs, or unassign human assignees to allow agent pickup.");
|
|
2968
3167
|
}
|
|
2969
|
-
if (
|
|
2970
|
-
|
|
3168
|
+
if (hints.length === 0) {
|
|
3169
|
+
hints.push("Add priority/P1 or priority/P2 label to an issue to schedule autonomous work.");
|
|
2971
3170
|
}
|
|
2972
|
-
|
|
3171
|
+
for (const hint of hints) {
|
|
3172
|
+
const text = `\u2022 ${hint}`;
|
|
3173
|
+
const wrapped = wrapText(text, width - 8);
|
|
3174
|
+
for (const wLine of wrapped) {
|
|
3175
|
+
const wPlain = stripAnsi(wLine);
|
|
3176
|
+
lines.push(
|
|
3177
|
+
border("\u2502") + ` ${pc2.dim(wLine)}` + " ".repeat(Math.max(1, width - 5 - wPlain.length)) + border("\u2502")
|
|
3178
|
+
);
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
lines.push(border(`\u2514${horizontal}\u2518`));
|
|
3182
|
+
return lines.join("\n");
|
|
2973
3183
|
}
|
|
2974
|
-
|
|
2975
|
-
|
|
3184
|
+
|
|
3185
|
+
// src/lib/brand.ts
|
|
3186
|
+
var FLEET_PALETTE = {
|
|
3187
|
+
commandNight: "#050811",
|
|
3188
|
+
radarChartreuse: "#BBF65D",
|
|
3189
|
+
thrusterCobalt: "#4619FC",
|
|
3190
|
+
battleshipSlate: "#829287",
|
|
3191
|
+
ordnanceFlare: "#CF8B14"
|
|
3192
|
+
};
|
|
3193
|
+
var FLEET_ASCII_EMBLEM = [
|
|
3194
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
3195
|
+
" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D",
|
|
3196
|
+
" \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 ",
|
|
3197
|
+
" \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 ",
|
|
3198
|
+
" \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 ",
|
|
3199
|
+
" \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D "
|
|
3200
|
+
];
|
|
3201
|
+
function getFleetRunBadge(runId = "${GITHUB_RUN_ID:-}", serverUrl = "${GITHUB_SERVER_URL:-https://github.com}", repository = "${GITHUB_REPOSITORY:-Jonah-Projects/jonah-fleet}") {
|
|
3202
|
+
const runUrl = `${serverUrl}/${repository}/actions/runs/${runId}`;
|
|
3203
|
+
return `[](${runUrl})`;
|
|
2976
3204
|
}
|
|
2977
|
-
function
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
);
|
|
2989
|
-
}
|
|
3205
|
+
function getFleetFooter(runId = "${GITHUB_RUN_ID:-}", serverUrl = "${GITHUB_SERVER_URL:-https://github.com}", repository = "${GITHUB_REPOSITORY:-Jonah-Projects/jonah-fleet}") {
|
|
3206
|
+
return `${getFleetRunBadge(runId, serverUrl, repository)}
|
|
3207
|
+
|
|
3208
|
+
_Generated by [Antigravity](${serverUrl}/${repository}/actions/runs/${runId})_`;
|
|
3209
|
+
}
|
|
3210
|
+
function renderFleetBanner(options) {
|
|
3211
|
+
const width = options.width || Math.min(Math.max((process.stdout.columns || 80) - 4, 60), 80);
|
|
3212
|
+
const borderChar = "\u2500";
|
|
3213
|
+
const horizontal = borderChar.repeat(Math.max(10, width - 2));
|
|
3214
|
+
const border = (s) => pc3.dim(pc3.gray(s));
|
|
3215
|
+
const emblemLine = (s) => pc3.bold(pc3.green(s));
|
|
2990
3216
|
const lines = [];
|
|
2991
|
-
lines.push(
|
|
2992
|
-
if (
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
lines.push(
|
|
3008
|
-
} else {
|
|
3009
|
-
for (const claim of s.activeClaims) {
|
|
3010
|
-
const staleTag = claim.isStale ? pc5.red(pc5.bold(" [\u26A0\uFE0F STALE CLAIM > 6h]")) : pc5.green(" [ACTIVE]");
|
|
3011
|
-
const ageStr = `${claim.ageHours.toFixed(1)}h ago`;
|
|
3012
|
-
lines.push(
|
|
3013
|
-
` #${claim.issueNumber} ${claim.title}${staleTag}` + pc5.gray(` (claimed by @${claim.assignee}, ${ageStr})`)
|
|
3014
|
-
);
|
|
3015
|
-
}
|
|
3016
|
-
}
|
|
3017
|
-
lines.push(pc5.bold(" \u{1F500} Open PRs:"));
|
|
3018
|
-
if (s.openPRs.length === 0) {
|
|
3019
|
-
lines.push(pc5.gray(" None"));
|
|
3020
|
-
} else {
|
|
3021
|
-
for (const pr of s.openPRs) {
|
|
3022
|
-
const stateTag = pr.isDraft ? pc5.yellow("[DRAFT]") : pc5.green("[READY]");
|
|
3023
|
-
const reviewStr = pr.reviewDecision ? pc5.magenta(` (${pr.reviewDecision})`) : "";
|
|
3024
|
-
lines.push(` #${pr.number} ${stateTag} ${pr.title}${reviewStr}` + pc5.gray(` by @${pr.author}`));
|
|
3025
|
-
}
|
|
3217
|
+
lines.push(border(`\u250C${horizontal}\u2510`));
|
|
3218
|
+
if (width >= 72) {
|
|
3219
|
+
const telemetry = [
|
|
3220
|
+
pc3.bold(pc3.cyan("FLEET // COMMAND")),
|
|
3221
|
+
pc3.dim(pc3.gray("SYS: ")) + pc3.green("READY") + pc3.dim(pc3.gray(" [bbf65d90]")),
|
|
3222
|
+
pc3.dim(pc3.gray("CMD: ")) + pc3.bold(pc3.white(options.command.toUpperCase())),
|
|
3223
|
+
pc3.dim(pc3.gray("VEC: ")) + pc3.green("#BBF65D") + pc3.dim(pc3.gray(" \xB7 ")) + pc3.blue("#4619FC"),
|
|
3224
|
+
pc3.dim(pc3.gray("VER: ")) + pc3.cyan(`v${FLEET_VERSION}`),
|
|
3225
|
+
options.subtitle ? pc3.dim(pc3.gray("OP: ")) + pc3.yellow(options.subtitle.length > 22 ? options.subtitle.slice(0, 19) + "..." : options.subtitle) : pc3.dim(pc3.gray("OP: ")) + pc3.green("ONLINE")
|
|
3226
|
+
];
|
|
3227
|
+
for (let i = 0; i < FLEET_ASCII_EMBLEM.length; i++) {
|
|
3228
|
+
const art = FLEET_ASCII_EMBLEM[i];
|
|
3229
|
+
const info = telemetry[i] || "";
|
|
3230
|
+
const combined = `${art} ${info}`;
|
|
3231
|
+
const plainLen = stripAnsi(combined).length;
|
|
3232
|
+
const pad = " ".repeat(Math.max(1, width - 2 - plainLen));
|
|
3233
|
+
lines.push(`${border("\u2502")}${emblemLine(art)} ${info}${pad}${border("\u2502")}`);
|
|
3026
3234
|
}
|
|
3027
|
-
|
|
3028
|
-
const
|
|
3235
|
+
} else {
|
|
3236
|
+
const header = ` \u26A1 FLEET // COMMAND \xB7 ${options.command.toUpperCase()}`;
|
|
3237
|
+
const headerPlain = stripAnsi(header);
|
|
3029
3238
|
lines.push(
|
|
3030
|
-
|
|
3239
|
+
`${border("\u2502")}${pc3.bold(pc3.cyan(header))}${" ".repeat(Math.max(1, width - 2 - headerPlain.length))}${border("\u2502")}`
|
|
3240
|
+
);
|
|
3241
|
+
const sub = ` SYS: READY \xB7 v${FLEET_VERSION}${options.subtitle ? ` \xB7 ${options.subtitle}` : ""}`;
|
|
3242
|
+
const subPlain = stripAnsi(sub);
|
|
3243
|
+
const truncatedSub = subPlain.length > width - 4 ? sub.slice(0, width - 7) + "..." : sub;
|
|
3244
|
+
const finalSubPlain = stripAnsi(truncatedSub);
|
|
3245
|
+
lines.push(
|
|
3246
|
+
`${border("\u2502")}${pc3.dim(pc3.gray(truncatedSub))}${" ".repeat(Math.max(1, width - 2 - finalSubPlain.length))}${border("\u2502")}`
|
|
3031
3247
|
);
|
|
3032
|
-
if (t.byRoutine && Object.keys(t.byRoutine).length > 0) {
|
|
3033
|
-
const routines = Object.values(t.byRoutine).sort((a, b) => b.totalTokens - a.totalTokens);
|
|
3034
|
-
for (const r of routines) {
|
|
3035
|
-
const iterStr = r.avgIterationsUsed !== void 0 ? `, avg ${r.avgIterationsUsed} iters` : "";
|
|
3036
|
-
const tokenDetails = options.tokens || options.detailed ? ` (in: ${formatTokens(r.inputTokens)}, out: ${formatTokens(r.outputTokens)})` : "";
|
|
3037
|
-
lines.push(
|
|
3038
|
-
` \u2022 ${pc5.bold(r.routine)}: ${pc5.cyan(formatTokens(r.totalTokens))} tokens${pc5.gray(tokenDetails)} ` + pc5.gray(`(${r.fleetSharePercent.toFixed(1)}%)`) + ` | Cost: ${pc5.green(formatCurrency(r.estimatedCost))} | ${r.runCount} run${r.runCount === 1 ? "" : "s"}${pc5.gray(iterStr)}`
|
|
3039
|
-
);
|
|
3040
|
-
}
|
|
3041
|
-
}
|
|
3042
|
-
if (s.staleWarnings.length > 0) {
|
|
3043
|
-
lines.push(pc5.bold(pc5.red(" \u26A0\uFE0F Warnings:")));
|
|
3044
|
-
for (const w of s.staleWarnings) {
|
|
3045
|
-
lines.push(pc5.red(` - ${w}`));
|
|
3046
|
-
}
|
|
3047
|
-
}
|
|
3048
|
-
lines.push("");
|
|
3049
3248
|
}
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
let budgetTag = pc5.green("[HEALTHY]");
|
|
3058
|
-
if (ceilingPct > 100) budgetTag = pc5.red(pc5.bold("[EXCEEDED]"));
|
|
3059
|
-
else if (ceilingPct >= 90) budgetTag = pc5.red(pc5.bold("[CRITICAL]"));
|
|
3060
|
-
else if (ceilingPct >= 70) budgetTag = pc5.yellow("[WARNING]");
|
|
3061
|
-
lines.push(
|
|
3062
|
-
` 7-Day Spend: ${pc5.bold(formatTokens(summary.totalTokens7d))} tokens ` + pc5.gray(`(in: ${formatTokens(summary.totalInputTokens7d)}, out: ${formatTokens(summary.totalOutputTokens7d)})`) + ` | Est. Cost: ${pc5.bold(pc5.green(formatCurrency(summary.totalEstimatedCost7d)))} across ${pc5.bold(summary.totalRuns7d.toString())} runs`
|
|
3063
|
-
);
|
|
3064
|
-
lines.push(
|
|
3065
|
-
` Weekly Budget: ${pc5.bold(formatTokens(summary.totalTokens7d))} / ${formatTokens(CEILING_70_PERCENT)} tokens (${ceilingPct.toFixed(1)}% of 70% ceiling) ${budgetTag}`
|
|
3066
|
-
);
|
|
3067
|
-
if (summary.byRoutine && Object.keys(summary.byRoutine).length > 0 && (options.tokens || options.detailed || statuses.length > 1)) {
|
|
3068
|
-
lines.push(pc5.bold("\n Fleet Spend by Routine:"));
|
|
3069
|
-
const fleetRoutines = Object.values(summary.byRoutine).sort((a, b) => b.totalTokens - a.totalTokens);
|
|
3070
|
-
for (const r of fleetRoutines) {
|
|
3071
|
-
const iterStr = r.avgIterationsUsed !== void 0 ? `, avg ${r.avgIterationsUsed} iters` : "";
|
|
3249
|
+
if (options.details && options.details.length > 0) {
|
|
3250
|
+
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
3251
|
+
for (const detail of options.details) {
|
|
3252
|
+
const row = ` \u2022 ${pc3.dim(pc3.gray(detail.label.toUpperCase() + ":"))} ${pc3.white(detail.value)}`;
|
|
3253
|
+
const rowPlain = stripAnsi(row);
|
|
3254
|
+
const truncatedRow = rowPlain.length > width - 4 ? ` \u2022 ${pc3.dim(pc3.gray(detail.label.toUpperCase() + ":"))} ${pc3.white(detail.value.slice(0, width - detail.label.length - 12) + "...")}` : row;
|
|
3255
|
+
const finalPlain = stripAnsi(truncatedRow);
|
|
3072
3256
|
lines.push(
|
|
3073
|
-
|
|
3257
|
+
`${border("\u2502")}${truncatedRow}${" ".repeat(Math.max(1, width - 2 - finalPlain.length))}${border("\u2502")}`
|
|
3074
3258
|
);
|
|
3075
3259
|
}
|
|
3076
3260
|
}
|
|
3077
|
-
lines.push(
|
|
3261
|
+
lines.push(border(`\u2514${horizontal}\u2518`));
|
|
3078
3262
|
return lines.join("\n");
|
|
3079
3263
|
}
|
|
3080
3264
|
|
|
3081
|
-
// src/commands/
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3265
|
+
// src/commands/init.ts
|
|
3266
|
+
async function promptQuestion(query, defaultValue) {
|
|
3267
|
+
const rl = readline.createInterface({
|
|
3268
|
+
input: process.stdin,
|
|
3269
|
+
output: process.stdout
|
|
3270
|
+
});
|
|
3271
|
+
return new Promise((resolve) => {
|
|
3272
|
+
rl.question(`${query} [${defaultValue}]: `, (answer) => {
|
|
3273
|
+
rl.close();
|
|
3274
|
+
resolve(answer.trim() || defaultValue);
|
|
3275
|
+
});
|
|
3276
|
+
});
|
|
3091
3277
|
}
|
|
3092
|
-
function
|
|
3093
|
-
const
|
|
3094
|
-
|
|
3095
|
-
|
|
3278
|
+
async function runInit(options = {}) {
|
|
3279
|
+
const cwd = options.cwd || process.cwd();
|
|
3280
|
+
const preset = options.preset || "standard";
|
|
3281
|
+
console.log(
|
|
3282
|
+
renderFleetBanner({
|
|
3283
|
+
command: "INIT",
|
|
3284
|
+
subtitle: `INITIALIZING FLEET (preset: ${preset})`,
|
|
3285
|
+
details: [
|
|
3286
|
+
{ label: "Preset", value: preset },
|
|
3287
|
+
{ label: "Target", value: cwd }
|
|
3288
|
+
]
|
|
3289
|
+
})
|
|
3290
|
+
);
|
|
3291
|
+
console.log(pc4.cyan(`
|
|
3292
|
+
\u2693 Initializing Jonah Fleet (preset: ${pc4.bold(preset)}) in ${cwd}
|
|
3293
|
+
`));
|
|
3294
|
+
let detected = detectTechStack(cwd);
|
|
3295
|
+
console.log(pc4.bold("\u{1F50D} Tech Stack Auto-Detection:"));
|
|
3296
|
+
console.log(pc4.cyan(` - Detected Stack: ${pc4.bold(detected.name)}`));
|
|
3297
|
+
console.log(pc4.cyan(` - Language: ${detected.language}`));
|
|
3298
|
+
if (detected.framework) {
|
|
3299
|
+
console.log(pc4.cyan(` - Framework: ${detected.framework}`));
|
|
3096
3300
|
}
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
return {
|
|
3101
|
-
repositories: Array.isArray(parsed.repositories) ? parsed.repositories : []
|
|
3102
|
-
};
|
|
3103
|
-
} catch {
|
|
3104
|
-
return { repositories: [] };
|
|
3301
|
+
console.log(pc4.cyan(` - Package Manager: ${detected.packageManager}`));
|
|
3302
|
+
if (detected.testFramework) {
|
|
3303
|
+
console.log(pc4.cyan(` - Testing: ${detected.testFramework}`));
|
|
3105
3304
|
}
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
const filePath = customPath || getDefaultGlobalConfigPath();
|
|
3109
|
-
const dir = path8.dirname(filePath);
|
|
3110
|
-
if (!fs8.existsSync(dir)) {
|
|
3111
|
-
fs8.mkdirSync(dir, { recursive: true });
|
|
3305
|
+
if (detected.commands.test) {
|
|
3306
|
+
console.log(pc4.cyan(` - Test Command: ${detected.commands.test}`));
|
|
3112
3307
|
}
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3308
|
+
const isInteractive = options.interactive ?? (process.stdin.isTTY && !options.stack && !options.testCmd);
|
|
3309
|
+
if (isInteractive && process.stdin.isTTY) {
|
|
3310
|
+
console.log(pc4.yellow("\n\u2699\uFE0F Configure project settings (press enter to accept defaults):"));
|
|
3311
|
+
const stackName = await promptQuestion("Tech Stack Name", detected.name);
|
|
3312
|
+
const pkgManager = await promptQuestion("Package Manager", detected.packageManager);
|
|
3313
|
+
const testCmd = await promptQuestion("Test Command", detected.commands.test || "npm test");
|
|
3314
|
+
const buildCmd = await promptQuestion("Build Command", detected.commands.build || "npm run build");
|
|
3315
|
+
detected = {
|
|
3316
|
+
...detected,
|
|
3317
|
+
name: stackName,
|
|
3318
|
+
language: stackName,
|
|
3319
|
+
framework: void 0,
|
|
3320
|
+
packageManager: pkgManager,
|
|
3321
|
+
commands: {
|
|
3322
|
+
...detected.commands,
|
|
3323
|
+
test: testCmd,
|
|
3324
|
+
build: buildCmd
|
|
3325
|
+
}
|
|
3326
|
+
};
|
|
3122
3327
|
}
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
const normalized = repo.trim();
|
|
3128
|
-
config.repositories = config.repositories.filter((r) => r !== normalized);
|
|
3129
|
-
saveGlobalConfig(config, customPath);
|
|
3130
|
-
return config;
|
|
3131
|
-
}
|
|
3132
|
-
function getFleetRepositories(cwd, customGlobalConfigPath) {
|
|
3133
|
-
const targetDir = cwd || process.cwd();
|
|
3134
|
-
const manifest = loadManifest(targetDir);
|
|
3135
|
-
const manifestRepos = Array.isArray(manifest?.repositories) ? manifest.repositories : [];
|
|
3136
|
-
const globalConfig = loadGlobalConfig(customGlobalConfigPath);
|
|
3137
|
-
const globalRepos = globalConfig.repositories;
|
|
3138
|
-
const set = /* @__PURE__ */ new Set();
|
|
3139
|
-
for (const r of manifestRepos) {
|
|
3140
|
-
if (r && typeof r === "string" && r.trim()) set.add(r.trim());
|
|
3328
|
+
if (options.stack) {
|
|
3329
|
+
detected.name = options.stack;
|
|
3330
|
+
detected.language = options.stack;
|
|
3331
|
+
detected.framework = void 0;
|
|
3141
3332
|
}
|
|
3142
|
-
|
|
3143
|
-
|
|
3333
|
+
if (options.packageManager) {
|
|
3334
|
+
detected.packageManager = options.packageManager;
|
|
3144
3335
|
}
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
// src/commands/monitor.ts
|
|
3149
|
-
async function runMonitor(options = {}) {
|
|
3150
|
-
const cwd = options.cwd || process.cwd();
|
|
3151
|
-
const executor = options.executor || defaultGhExecutor;
|
|
3152
|
-
if (options.add) {
|
|
3153
|
-
const updated = addGlobalRepository(options.add);
|
|
3154
|
-
console.log(pc6.green(`\u2713 Added ${pc6.bold(options.add)} to Jonah Fleet registry.`));
|
|
3155
|
-
console.log(pc6.gray(` Current registered repositories: ${updated.repositories.join(", ") || "none"}
|
|
3156
|
-
`));
|
|
3157
|
-
return;
|
|
3336
|
+
if (options.testCmd) {
|
|
3337
|
+
detected.commands.test = options.testCmd;
|
|
3158
3338
|
}
|
|
3159
|
-
if (options.
|
|
3160
|
-
|
|
3161
|
-
console.log(pc6.yellow(`\u2713 Removed ${pc6.bold(options.remove)} from Jonah Fleet registry.`));
|
|
3162
|
-
console.log(pc6.gray(` Current registered repositories: ${updated.repositories.join(", ") || "none"}
|
|
3163
|
-
`));
|
|
3164
|
-
return;
|
|
3339
|
+
if (options.buildCmd) {
|
|
3340
|
+
detected.commands.build = options.buildCmd;
|
|
3165
3341
|
}
|
|
3166
|
-
let
|
|
3167
|
-
if (
|
|
3168
|
-
|
|
3342
|
+
let manifest = loadManifest(cwd);
|
|
3343
|
+
if (manifest && !options.force) {
|
|
3344
|
+
console.log(pc4.yellow(`
|
|
3345
|
+
\u26A0\uFE0F Found existing agents-manifest.json. Updating with preset '${preset}'...`));
|
|
3169
3346
|
} else {
|
|
3170
|
-
|
|
3347
|
+
manifest = createDefaultManifest(preset);
|
|
3348
|
+
}
|
|
3349
|
+
saveManifest(cwd, manifest);
|
|
3350
|
+
console.log(pc4.green(`\u2713 Created/Updated agents-manifest.json`));
|
|
3351
|
+
const result = installFleet(cwd, manifest, { force: options.force, detectedStack: detected });
|
|
3352
|
+
console.log(pc4.bold("\nInstalled components:"));
|
|
3353
|
+
if (result.promptsInstalled.length > 0) {
|
|
3354
|
+
console.log(pc4.green(` \u{1F4C1} Prompts (.github/prompts/):`));
|
|
3355
|
+
result.promptsInstalled.forEach((p) => console.log(` - ${p}`));
|
|
3356
|
+
}
|
|
3357
|
+
if (result.workflowsInstalled.length > 0) {
|
|
3358
|
+
console.log(pc4.green(` \u2699\uFE0F Workflows (.github/workflows/):`));
|
|
3359
|
+
result.workflowsInstalled.forEach((w) => console.log(` - ${w}`));
|
|
3360
|
+
}
|
|
3361
|
+
if (result.skillsInstalled.length > 0) {
|
|
3362
|
+
console.log(pc4.green(` \u{1F9E0} Skills (.agents/skills/):`));
|
|
3363
|
+
result.skillsInstalled.forEach((s) => console.log(` - ${s}`));
|
|
3364
|
+
}
|
|
3365
|
+
if (result.docsInstalled.length > 0) {
|
|
3366
|
+
console.log(pc4.green(` \u{1F4C4} Documentation:`));
|
|
3367
|
+
result.docsInstalled.forEach((d) => console.log(` - ${d}`));
|
|
3368
|
+
}
|
|
3369
|
+
if (options.pruneLabels) {
|
|
3370
|
+
try {
|
|
3371
|
+
console.log(pc4.bold("\n\u{1F3F7}\uFE0F Pruning unused boilerplate labels..."));
|
|
3372
|
+
const pruneRes = await pruneLabels({ cwd, yes: true, dryRun: false, executor: options.executor });
|
|
3373
|
+
if (pruneRes.pruned.length > 0) {
|
|
3374
|
+
console.log(pc4.green(` \u2713 Pruned ${pruneRes.pruned.length} unused boilerplate label(s): ${pruneRes.pruned.join(", ")}`));
|
|
3375
|
+
} else {
|
|
3376
|
+
console.log(pc4.green(" \u2713 No unused boilerplate labels found."));
|
|
3377
|
+
}
|
|
3378
|
+
} catch (err) {
|
|
3379
|
+
console.log(pc4.yellow(` \u26A0\uFE0F Could not prune labels: ${err.message}`));
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
console.log(pc4.bold(pc4.green("\n\u{1F389} Jonah Fleet initialization complete!\n")));
|
|
3383
|
+
console.log(pc4.cyan("Next steps for GitHub repository configuration:"));
|
|
3384
|
+
console.log(" 1. In Settings \u2192 Actions \u2192 General \u2192 Workflow permissions:");
|
|
3385
|
+
console.log(' Select "Read and write permissions" and check "Allow GitHub Actions to create and approve pull requests".');
|
|
3386
|
+
console.log(" 2. In Settings \u2192 Actions \u2192 General \u2192 Fork pull request workflows:");
|
|
3387
|
+
console.log(" Configure workflow approval settings to prevent automated runs from stalling awaiting approval.");
|
|
3388
|
+
console.log(" 3. Customize project context, build, and test commands in AGENTS.md.\n");
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
// src/commands/sync.ts
|
|
3392
|
+
import pc5 from "picocolors";
|
|
3393
|
+
|
|
3394
|
+
// src/lib/diff.ts
|
|
3395
|
+
import fs8 from "fs";
|
|
3396
|
+
import path8 from "path";
|
|
3397
|
+
function checkDrift(targetDir, manifest) {
|
|
3398
|
+
const templatesDir = getTemplatesDir();
|
|
3399
|
+
const report = {
|
|
3400
|
+
missingPrompts: [],
|
|
3401
|
+
modifiedPrompts: [],
|
|
3402
|
+
missingWorkflows: [],
|
|
3403
|
+
modifiedWorkflows: [],
|
|
3404
|
+
missingSkills: []
|
|
3405
|
+
};
|
|
3406
|
+
const targetPromptsDir = path8.join(targetDir, ".github/prompts");
|
|
3407
|
+
const targetWorkflowsDir = path8.join(targetDir, ".github/workflows");
|
|
3408
|
+
const targetSkillsDir = path8.join(targetDir, ".agents/skills");
|
|
3409
|
+
const basePrompts = ["ORCHESTRATION.md", "_prompt-template.md"];
|
|
3410
|
+
for (const file of basePrompts) {
|
|
3411
|
+
const src = path8.join(templatesDir, "prompts", file);
|
|
3412
|
+
const dest = path8.join(targetPromptsDir, file);
|
|
3413
|
+
if (!fs8.existsSync(dest)) {
|
|
3414
|
+
report.missingPrompts.push(file);
|
|
3415
|
+
} else if (fs8.readFileSync(src, "utf8") !== fs8.readFileSync(dest, "utf8")) {
|
|
3416
|
+
report.modifiedPrompts.push(file);
|
|
3417
|
+
}
|
|
3171
3418
|
}
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3419
|
+
for (const [routineName, isEnabled] of Object.entries(manifest.routines)) {
|
|
3420
|
+
if (!isEnabled) continue;
|
|
3421
|
+
const promptFile = `${routineName}.md`;
|
|
3422
|
+
const promptSrc = path8.join(templatesDir, "prompts", promptFile);
|
|
3423
|
+
const promptDest = path8.join(targetPromptsDir, promptFile);
|
|
3424
|
+
if (!fs8.existsSync(promptDest)) {
|
|
3425
|
+
report.missingPrompts.push(promptFile);
|
|
3426
|
+
} else if (fs8.existsSync(promptSrc) && fs8.readFileSync(promptSrc, "utf8") !== fs8.readFileSync(promptDest, "utf8")) {
|
|
3427
|
+
report.modifiedPrompts.push(promptFile);
|
|
3428
|
+
}
|
|
3429
|
+
const workflows = ROUTINE_TO_WORKFLOW_MAP[routineName] || [];
|
|
3430
|
+
for (const workflowFile of workflows) {
|
|
3431
|
+
const wfSrc = path8.join(templatesDir, "workflows", workflowFile);
|
|
3432
|
+
const wfDest = path8.join(targetWorkflowsDir, workflowFile);
|
|
3433
|
+
if (!fs8.existsSync(wfDest)) {
|
|
3434
|
+
report.missingWorkflows.push(workflowFile);
|
|
3435
|
+
} else if (fs8.existsSync(wfSrc)) {
|
|
3436
|
+
const rawSrc = fs8.readFileSync(wfSrc, "utf8");
|
|
3437
|
+
const destContent = fs8.readFileSync(wfDest, "utf8");
|
|
3438
|
+
const schedule = resolveWorkflowSchedule(workflowFile, routineName, manifest, destContent);
|
|
3439
|
+
const expectedSrc = applyWorkflowSchedule(rawSrc, schedule);
|
|
3440
|
+
if (expectedSrc !== destContent) {
|
|
3441
|
+
report.modifiedWorkflows.push(workflowFile);
|
|
3442
|
+
}
|
|
3178
3443
|
}
|
|
3179
|
-
} catch {
|
|
3180
3444
|
}
|
|
3181
3445
|
}
|
|
3182
|
-
if (
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3446
|
+
if (manifest.autoUpdate?.enabled) {
|
|
3447
|
+
const syncWfSrc = path8.join(templatesDir, "workflows/sync-fleet.yml");
|
|
3448
|
+
const syncWfDest = path8.join(targetWorkflowsDir, "sync-fleet.yml");
|
|
3449
|
+
if (!fs8.existsSync(syncWfDest)) {
|
|
3450
|
+
report.missingWorkflows.push("sync-fleet.yml");
|
|
3451
|
+
} else if (fs8.existsSync(syncWfSrc)) {
|
|
3452
|
+
const rawSrc = fs8.readFileSync(syncWfSrc, "utf8");
|
|
3453
|
+
const destContent = fs8.readFileSync(syncWfDest, "utf8");
|
|
3454
|
+
const schedule = resolveWorkflowSchedule("sync-fleet.yml", "sync-fleet", manifest, destContent);
|
|
3455
|
+
const expectedSrc = applyWorkflowSchedule(rawSrc, schedule);
|
|
3456
|
+
if (expectedSrc !== destContent) {
|
|
3457
|
+
report.modifiedWorkflows.push("sync-fleet.yml");
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3189
3460
|
}
|
|
3190
|
-
const
|
|
3191
|
-
const
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
if (options.watch && !options.json) {
|
|
3195
|
-
console.clear();
|
|
3461
|
+
for (const skill of manifest.skills) {
|
|
3462
|
+
const skillDestDir = path8.join(targetSkillsDir, skill);
|
|
3463
|
+
if (!fs8.existsSync(skillDestDir)) {
|
|
3464
|
+
report.missingSkills.push(skill);
|
|
3196
3465
|
}
|
|
3197
|
-
const output = renderFleetDashboard(statuses, {
|
|
3198
|
-
json: options.json,
|
|
3199
|
-
tokens: options.tokens,
|
|
3200
|
-
detailed: options.detailed
|
|
3201
|
-
});
|
|
3202
|
-
console.log(output);
|
|
3203
|
-
};
|
|
3204
|
-
await pollAndRender();
|
|
3205
|
-
if (options.watch) {
|
|
3206
|
-
const intervalSec = typeof options.interval === "number" ? options.interval : parseInt(options.interval || "10", 10) || 10;
|
|
3207
|
-
const timer = setInterval(async () => {
|
|
3208
|
-
await pollAndRender();
|
|
3209
|
-
}, intervalSec * 1e3);
|
|
3210
|
-
const handleSigint = () => {
|
|
3211
|
-
clearInterval(timer);
|
|
3212
|
-
process.removeListener("SIGINT", handleSigint);
|
|
3213
|
-
console.log(pc6.gray("\nStopped live fleet monitoring.\n"));
|
|
3214
|
-
process.exit(0);
|
|
3215
|
-
};
|
|
3216
|
-
process.on("SIGINT", handleSigint);
|
|
3217
3466
|
}
|
|
3467
|
+
return report;
|
|
3218
3468
|
}
|
|
3219
3469
|
|
|
3220
|
-
// src/commands/
|
|
3221
|
-
async function
|
|
3470
|
+
// src/commands/sync.ts
|
|
3471
|
+
async function runSync(options = {}) {
|
|
3222
3472
|
const cwd = options.cwd || process.cwd();
|
|
3223
|
-
if (options.fleet) {
|
|
3224
|
-
await runMonitor({ cwd, json: options.json, tokens: options.tokens, detailed: options.detailed });
|
|
3225
|
-
return;
|
|
3226
|
-
}
|
|
3227
3473
|
const manifest = loadManifest(cwd);
|
|
3228
3474
|
if (!manifest) {
|
|
3229
|
-
|
|
3230
|
-
|
|
3475
|
+
console.error(pc5.red(`\u274C No agents-manifest.json found in ${cwd}. Run 'jonah-fleet init' first.`));
|
|
3476
|
+
process.exit(1);
|
|
3477
|
+
}
|
|
3478
|
+
console.log(
|
|
3479
|
+
renderFleetBanner({
|
|
3480
|
+
command: "SYNC",
|
|
3481
|
+
subtitle: `SYNCHRONIZING FLEET (v${manifest.version} -> v${FLEET_VERSION})`,
|
|
3482
|
+
details: [
|
|
3483
|
+
{ label: "Installed", value: `v${manifest.version}` },
|
|
3484
|
+
{ label: "Upstream", value: `v${FLEET_VERSION}` },
|
|
3485
|
+
{
|
|
3486
|
+
label: "Mode",
|
|
3487
|
+
value: options.check ? "Check Drift" : options.force ? "Force Overwrite" : "Standard Sync"
|
|
3488
|
+
}
|
|
3489
|
+
]
|
|
3490
|
+
})
|
|
3491
|
+
);
|
|
3492
|
+
console.log(pc5.cyan(`
|
|
3493
|
+
\u{1F504} Syncing Jonah Fleet (current: v${manifest.version}, fleet: v${FLEET_VERSION})...
|
|
3494
|
+
`));
|
|
3495
|
+
const drift = checkDrift(cwd, manifest);
|
|
3496
|
+
const hasDrift = drift.missingPrompts.length > 0 || drift.modifiedPrompts.length > 0 || drift.missingWorkflows.length > 0 || drift.modifiedWorkflows.length > 0 || drift.missingSkills.length > 0;
|
|
3497
|
+
if (options.check) {
|
|
3498
|
+
if (!hasDrift && manifest.version === FLEET_VERSION) {
|
|
3499
|
+
console.log(pc5.green(`\u2713 All routines, workflows, and skills are perfectly in sync with v${FLEET_VERSION}.
|
|
3500
|
+
`));
|
|
3231
3501
|
return;
|
|
3232
3502
|
}
|
|
3233
|
-
console.log(
|
|
3234
|
-
|
|
3235
|
-
console.log(
|
|
3503
|
+
console.log(pc5.yellow(`\u26A0\uFE0F Drift or updates detected:`));
|
|
3504
|
+
if (drift.missingPrompts.length > 0) console.log(pc5.red(` Missing prompts: ${drift.missingPrompts.join(", ")}`));
|
|
3505
|
+
if (drift.modifiedPrompts.length > 0) console.log(pc5.yellow(` Modified prompts: ${drift.modifiedPrompts.join(", ")}`));
|
|
3506
|
+
if (drift.missingWorkflows.length > 0) console.log(pc5.red(` Missing workflows: ${drift.missingWorkflows.join(", ")}`));
|
|
3507
|
+
if (drift.modifiedWorkflows.length > 0) console.log(pc5.yellow(` Modified workflows: ${drift.modifiedWorkflows.join(", ")}`));
|
|
3508
|
+
if (drift.missingSkills.length > 0) console.log(pc5.red(` Missing skills: ${drift.missingSkills.join(", ")}`));
|
|
3509
|
+
console.log(pc5.cyan(`
|
|
3510
|
+
Run 'jonah-fleet sync --force' to apply updates.
|
|
3236
3511
|
`));
|
|
3237
3512
|
return;
|
|
3238
3513
|
}
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
const
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
if (
|
|
3247
|
-
|
|
3248
|
-
for (const entry of entries) {
|
|
3249
|
-
const fullPath = path9.join(dir, entry.name);
|
|
3250
|
-
if (entry.isDirectory()) {
|
|
3251
|
-
collectLogs(fullPath);
|
|
3252
|
-
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
3253
|
-
try {
|
|
3254
|
-
logContents.push(fs9.readFileSync(fullPath, "utf8"));
|
|
3255
|
-
} catch {
|
|
3256
|
-
}
|
|
3257
|
-
}
|
|
3514
|
+
manifest.version = FLEET_VERSION;
|
|
3515
|
+
saveManifest(cwd, manifest);
|
|
3516
|
+
const result = installFleet(cwd, manifest, { force: true });
|
|
3517
|
+
console.log(pc5.green(`\u2713 Synchronized with Jonah Fleet v${FLEET_VERSION}`));
|
|
3518
|
+
console.log(pc5.green(`\u2713 Updated ${result.promptsInstalled.length} prompts, ${result.workflowsInstalled.length} workflows, and ${result.skillsInstalled.length} skills.`));
|
|
3519
|
+
try {
|
|
3520
|
+
const labelRes = await provisionLabels({ cwd });
|
|
3521
|
+
if (labelRes.created.length > 0) {
|
|
3522
|
+
console.log(pc5.green(`\u2713 Provisioned ${labelRes.created.length} missing fleet label(s)`));
|
|
3258
3523
|
}
|
|
3259
|
-
}
|
|
3260
|
-
collectLogs(runsDir);
|
|
3261
|
-
collectLogs(legacyLogsDir);
|
|
3262
|
-
if (logContents.length > 0) {
|
|
3263
|
-
tokenUsage = computeTokenSpendFromLogs(logContents);
|
|
3524
|
+
} catch {
|
|
3264
3525
|
}
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
repositories: manifest.repositories || [],
|
|
3279
|
-
tokenUsage,
|
|
3280
|
-
drift: {
|
|
3281
|
-
hasDrift,
|
|
3282
|
-
...drift
|
|
3283
|
-
}
|
|
3284
|
-
},
|
|
3285
|
-
null,
|
|
3286
|
-
2
|
|
3287
|
-
)
|
|
3288
|
-
);
|
|
3289
|
-
return;
|
|
3526
|
+
console.log();
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
// src/commands/status.ts
|
|
3530
|
+
import fs10 from "fs";
|
|
3531
|
+
import path10 from "path";
|
|
3532
|
+
import pc8 from "picocolors";
|
|
3533
|
+
|
|
3534
|
+
// src/lib/dashboard.ts
|
|
3535
|
+
import pc6 from "picocolors";
|
|
3536
|
+
function formatTokens2(num) {
|
|
3537
|
+
if (num >= 1e6) {
|
|
3538
|
+
return `${(num / 1e6).toFixed(2)}M`;
|
|
3290
3539
|
}
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
command: "STATUS",
|
|
3294
|
-
subtitle: "TELEMETRY & DRIFT AUDIT",
|
|
3295
|
-
details: [
|
|
3296
|
-
{ label: "Version", value: manifest.version },
|
|
3297
|
-
{ label: "Preset", value: manifest.preset },
|
|
3298
|
-
{ label: "Target", value: cwd }
|
|
3299
|
-
]
|
|
3300
|
-
})
|
|
3301
|
-
);
|
|
3302
|
-
console.log(pc7.bold(pc7.cyan(`
|
|
3303
|
-
\u{1F4CA} Jonah Fleet Status for ${cwd}
|
|
3304
|
-
`)));
|
|
3305
|
-
console.log(` Version: ${manifest.version === FLEET_VERSION ? pc7.green(manifest.version) : pc7.yellow(`${manifest.version} (fleet latest: ${FLEET_VERSION})`)}`);
|
|
3306
|
-
console.log(` Preset: ${pc7.bold(manifest.preset)}`);
|
|
3307
|
-
console.log(` Auto-Update: ${manifest.autoUpdate?.enabled ? pc7.green("Enabled (" + manifest.autoUpdate.channel + ")") : pc7.gray("Disabled")}`);
|
|
3308
|
-
console.log(pc7.bold("\n Enabled Routines:"));
|
|
3309
|
-
for (const [routine, enabled] of Object.entries(manifest.routines)) {
|
|
3310
|
-
console.log(` - ${routine.padEnd(35)}: ${enabled ? pc7.green("ENABLED") : pc7.gray("DISABLED")}`);
|
|
3540
|
+
if (num >= 1e3) {
|
|
3541
|
+
return `${(num / 1e3).toFixed(1)}k`;
|
|
3311
3542
|
}
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3543
|
+
return num.toString();
|
|
3544
|
+
}
|
|
3545
|
+
function formatCurrency(amount) {
|
|
3546
|
+
return `$${amount.toFixed(2)}`;
|
|
3547
|
+
}
|
|
3548
|
+
function renderFleetDashboard(statuses, options = {}) {
|
|
3549
|
+
const summary = summarizeFleet(statuses);
|
|
3550
|
+
if (options.json) {
|
|
3551
|
+
return JSON.stringify(
|
|
3552
|
+
{
|
|
3553
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3554
|
+
summary,
|
|
3555
|
+
repositories: statuses
|
|
3556
|
+
},
|
|
3557
|
+
null,
|
|
3558
|
+
2
|
|
3559
|
+
);
|
|
3315
3560
|
}
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
}
|
|
3561
|
+
const lines = [];
|
|
3562
|
+
lines.push(pc6.bold(pc6.cyan("\n\u{1F4CA} Jonah Fleet Multi-Repo Monitor\n")));
|
|
3563
|
+
if (statuses.length === 0) {
|
|
3564
|
+
lines.push(pc6.yellow(" No repositories configured in fleet registry."));
|
|
3565
|
+
lines.push(pc6.gray(" Use `jonah-fleet monitor --add <owner/repo>` to register repositories.\n"));
|
|
3566
|
+
return lines.join("\n");
|
|
3323
3567
|
}
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
}
|
|
3329
|
-
if (
|
|
3330
|
-
|
|
3331
|
-
for (const [routine, timeout] of Object.entries(manifest.budgets.timeoutMinutes)) {
|
|
3332
|
-
if (timeout !== void 0) {
|
|
3333
|
-
console.log(` \u2022 ${routine}: ${pc7.cyan(timeout + "m")}`);
|
|
3334
|
-
}
|
|
3335
|
-
}
|
|
3568
|
+
for (const s of statuses) {
|
|
3569
|
+
const versionStr = s.fleetVersion ? `v${s.fleetVersion}` : "unmanaged";
|
|
3570
|
+
const presetStr = s.preset ? `preset: ${s.preset}` : "";
|
|
3571
|
+
const headerInfo = [versionStr, presetStr].filter(Boolean).join(", ");
|
|
3572
|
+
lines.push(pc6.bold(`\u{1F4E6} ${pc6.cyan(s.repo)} ${pc6.gray(`(${headerInfo})`)}`));
|
|
3573
|
+
if (s.error) {
|
|
3574
|
+
lines.push(pc6.red(` \u274C Error: ${s.error}`));
|
|
3336
3575
|
}
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3576
|
+
lines.push(pc6.bold(" \u{1F512} Active Claims:"));
|
|
3577
|
+
if (s.activeClaims.length === 0) {
|
|
3578
|
+
lines.push(pc6.gray(" None (idle)"));
|
|
3579
|
+
} else {
|
|
3580
|
+
for (const claim of s.activeClaims) {
|
|
3581
|
+
const staleTag = claim.isStale ? pc6.red(pc6.bold(" [\u26A0\uFE0F STALE CLAIM > 6h]")) : pc6.green(" [ACTIVE]");
|
|
3582
|
+
const ageStr = `${claim.ageHours.toFixed(1)}h ago`;
|
|
3583
|
+
lines.push(
|
|
3584
|
+
` #${claim.issueNumber} ${claim.title}${staleTag}` + pc6.gray(` (claimed by @${claim.assignee}, ${ageStr})`)
|
|
3585
|
+
);
|
|
3343
3586
|
}
|
|
3344
3587
|
}
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3588
|
+
lines.push(pc6.bold(" \u{1F500} Open PRs:"));
|
|
3589
|
+
if (s.openPRs.length === 0) {
|
|
3590
|
+
lines.push(pc6.gray(" None"));
|
|
3591
|
+
} else {
|
|
3592
|
+
for (const pr of s.openPRs) {
|
|
3593
|
+
const stateTag = pr.isDraft ? pc6.yellow("[DRAFT]") : pc6.green("[READY]");
|
|
3594
|
+
const reviewStr = pr.reviewDecision ? pc6.magenta(` (${pr.reviewDecision})`) : "";
|
|
3595
|
+
lines.push(` #${pr.number} ${stateTag} ${pr.title}${reviewStr}` + pc6.gray(` by @${pr.author}`));
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
lines.push(pc6.bold(" \u{1F4C8} 7-Day Token Spend:"));
|
|
3599
|
+
const t = s.tokenUsage;
|
|
3600
|
+
lines.push(
|
|
3601
|
+
` Runs: ${pc6.bold(t.recentRunCount.toString())} | Tokens: ${pc6.bold(formatTokens2(t.sevenDayTotalTokens))} ` + pc6.gray(`(in: ${formatTokens2(t.sevenDayInputTokens)}, out: ${formatTokens2(t.sevenDayOutputTokens)})`) + ` | Cost: ${pc6.bold(pc6.green(formatCurrency(t.sevenDayEstimatedCost)))}`
|
|
3356
3602
|
);
|
|
3357
|
-
if (
|
|
3358
|
-
const routines = Object.values(
|
|
3603
|
+
if (t.byRoutine && Object.keys(t.byRoutine).length > 0) {
|
|
3604
|
+
const routines = Object.values(t.byRoutine).sort((a, b) => b.totalTokens - a.totalTokens);
|
|
3359
3605
|
for (const r of routines) {
|
|
3360
3606
|
const iterStr = r.avgIterationsUsed !== void 0 ? `, avg ${r.avgIterationsUsed} iters` : "";
|
|
3361
|
-
const tokenDetails = options.tokens || options.detailed ? ` (in: ${
|
|
3362
|
-
|
|
3363
|
-
` \u2022 ${
|
|
3607
|
+
const tokenDetails = options.tokens || options.detailed ? ` (in: ${formatTokens2(r.inputTokens)}, out: ${formatTokens2(r.outputTokens)})` : "";
|
|
3608
|
+
lines.push(
|
|
3609
|
+
` \u2022 ${pc6.bold(r.routine)}: ${pc6.cyan(formatTokens2(r.totalTokens))} tokens${pc6.gray(tokenDetails)} ` + pc6.gray(`(${r.fleetSharePercent.toFixed(1)}%)`) + ` | Cost: ${pc6.green(formatCurrency(r.estimatedCost))} | ${r.runCount} run${r.runCount === 1 ? "" : "s"}${pc6.gray(iterStr)}`
|
|
3364
3610
|
);
|
|
3365
3611
|
}
|
|
3366
3612
|
}
|
|
3613
|
+
if (s.staleWarnings.length > 0) {
|
|
3614
|
+
lines.push(pc6.bold(pc6.red(" \u26A0\uFE0F Warnings:")));
|
|
3615
|
+
for (const w of s.staleWarnings) {
|
|
3616
|
+
lines.push(pc6.red(` - ${w}`));
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
lines.push("");
|
|
3367
3620
|
}
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
}
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3621
|
+
lines.push(pc6.bold("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
|
|
3622
|
+
lines.push(pc6.bold("\u{1F310} Fleet Summary:"));
|
|
3623
|
+
lines.push(
|
|
3624
|
+
` Repositories: ${pc6.bold(summary.totalRepos.toString())} | Active Claims: ${pc6.bold(summary.activeClaimsCount.toString())} ` + (summary.staleClaimsCount > 0 ? pc6.red(`(${summary.staleClaimsCount} stale)`) : pc6.green("(0 stale)")) + ` | Open PRs: ${pc6.bold(summary.openPRsCount.toString())} ` + pc6.gray(`(${summary.draftPRsCount} draft, ${summary.readyPRsCount} ready)`)
|
|
3625
|
+
);
|
|
3626
|
+
const CEILING_70_PERCENT = 875e4;
|
|
3627
|
+
const ceilingPct = summary.totalTokens7d / CEILING_70_PERCENT * 100;
|
|
3628
|
+
let budgetTag = pc6.green("[HEALTHY]");
|
|
3629
|
+
if (ceilingPct > 100) budgetTag = pc6.red(pc6.bold("[EXCEEDED]"));
|
|
3630
|
+
else if (ceilingPct >= 90) budgetTag = pc6.red(pc6.bold("[CRITICAL]"));
|
|
3631
|
+
else if (ceilingPct >= 70) budgetTag = pc6.yellow("[WARNING]");
|
|
3632
|
+
lines.push(
|
|
3633
|
+
` 7-Day Spend: ${pc6.bold(formatTokens2(summary.totalTokens7d))} tokens ` + pc6.gray(`(in: ${formatTokens2(summary.totalInputTokens7d)}, out: ${formatTokens2(summary.totalOutputTokens7d)})`) + ` | Est. Cost: ${pc6.bold(pc6.green(formatCurrency(summary.totalEstimatedCost7d)))} across ${pc6.bold(summary.totalRuns7d.toString())} runs`
|
|
3634
|
+
);
|
|
3635
|
+
lines.push(
|
|
3636
|
+
` Weekly Budget: ${pc6.bold(formatTokens2(summary.totalTokens7d))} / ${formatTokens2(CEILING_70_PERCENT)} tokens (${ceilingPct.toFixed(1)}% of 70% ceiling) ${budgetTag}`
|
|
3637
|
+
);
|
|
3638
|
+
if (summary.byRoutine && Object.keys(summary.byRoutine).length > 0 && (options.tokens || options.detailed || statuses.length > 1)) {
|
|
3639
|
+
lines.push(pc6.bold("\n Fleet Spend by Routine:"));
|
|
3640
|
+
const fleetRoutines = Object.values(summary.byRoutine).sort((a, b) => b.totalTokens - a.totalTokens);
|
|
3641
|
+
for (const r of fleetRoutines) {
|
|
3642
|
+
const iterStr = r.avgIterationsUsed !== void 0 ? `, avg ${r.avgIterationsUsed} iters` : "";
|
|
3643
|
+
lines.push(
|
|
3644
|
+
` \u2022 ${pc6.bold(r.routine)}: ${pc6.cyan(formatTokens2(r.totalTokens))} tokens ` + pc6.gray(`(${r.fleetSharePercent.toFixed(1)}%)`) + ` | Cost: ${pc6.green(formatCurrency(r.estimatedCost))} | ${r.runCount} run${r.runCount === 1 ? "" : "s"}${pc6.gray(iterStr)}`
|
|
3645
|
+
);
|
|
3646
|
+
}
|
|
3378
3647
|
}
|
|
3648
|
+
lines.push(pc6.bold("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n"));
|
|
3649
|
+
return lines.join("\n");
|
|
3379
3650
|
}
|
|
3380
3651
|
|
|
3381
|
-
// src/commands/
|
|
3382
|
-
import
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
const
|
|
3390
|
-
return
|
|
3391
|
-
repo,
|
|
3392
|
-
branchName,
|
|
3393
|
-
title,
|
|
3394
|
-
body,
|
|
3395
|
-
prompt: options.prompt,
|
|
3396
|
-
prCommand
|
|
3397
|
-
};
|
|
3652
|
+
// src/commands/monitor.ts
|
|
3653
|
+
import pc7 from "picocolors";
|
|
3654
|
+
|
|
3655
|
+
// src/lib/global-config.ts
|
|
3656
|
+
import fs9 from "fs";
|
|
3657
|
+
import path9 from "path";
|
|
3658
|
+
import os from "os";
|
|
3659
|
+
function getDefaultGlobalConfigPath() {
|
|
3660
|
+
const baseDir = process.env.JONAH_FLEET_CONFIG_DIR || path9.join(os.homedir(), ".jonah-fleet");
|
|
3661
|
+
return path9.join(baseDir, "config.json");
|
|
3398
3662
|
}
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
const payload = prepareContributionPayload(options);
|
|
3404
|
-
console.log(`Preparing upstream contribution PR against ${pc8.bold(payload.repo)}...`);
|
|
3405
|
-
console.log(`Title: ${pc8.green(payload.title)}`);
|
|
3406
|
-
console.log(`Body: ${pc8.gray(payload.body)}
|
|
3407
|
-
`);
|
|
3408
|
-
if (options.dryRun) {
|
|
3409
|
-
console.log(pc8.yellow(`[DRY-RUN] Would create branch: ${payload.branchName}`));
|
|
3410
|
-
console.log(pc8.yellow(`[DRY-RUN] Would execute: ${payload.prCommand}
|
|
3411
|
-
`));
|
|
3412
|
-
return {
|
|
3413
|
-
success: true,
|
|
3414
|
-
dryRun: true,
|
|
3415
|
-
branchName: payload.branchName,
|
|
3416
|
-
title: payload.title
|
|
3417
|
-
};
|
|
3663
|
+
function loadGlobalConfig(customPath) {
|
|
3664
|
+
const filePath = customPath || getDefaultGlobalConfigPath();
|
|
3665
|
+
if (!fs9.existsSync(filePath)) {
|
|
3666
|
+
return { repositories: [] };
|
|
3418
3667
|
}
|
|
3419
3668
|
try {
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
await options.executor(["auth", "status"]);
|
|
3423
|
-
const prUrl = await options.executor([
|
|
3424
|
-
"pr",
|
|
3425
|
-
"create",
|
|
3426
|
-
"--repo",
|
|
3427
|
-
payload.repo,
|
|
3428
|
-
"--title",
|
|
3429
|
-
payload.title,
|
|
3430
|
-
"--body",
|
|
3431
|
-
payload.body
|
|
3432
|
-
]);
|
|
3433
|
-
console.log(pc8.green(`\u2713 Submitted upstream contribution: ${prUrl}`));
|
|
3434
|
-
return {
|
|
3435
|
-
success: true,
|
|
3436
|
-
branchName: payload.branchName,
|
|
3437
|
-
title: payload.title,
|
|
3438
|
-
prUrl: typeof prUrl === "string" && prUrl.trim() ? prUrl.trim() : void 0
|
|
3439
|
-
};
|
|
3440
|
-
} else {
|
|
3441
|
-
try {
|
|
3442
|
-
execSync("gh auth status", { stdio: "pipe" });
|
|
3443
|
-
} catch {
|
|
3444
|
-
const errorMsg = "GitHub CLI (`gh`) is not authenticated. Run `gh auth login` first.";
|
|
3445
|
-
console.error(pc8.red(`\u274C ${errorMsg}`));
|
|
3446
|
-
if (options.throwOnError) throw new Error(errorMsg);
|
|
3447
|
-
return { success: false, error: errorMsg };
|
|
3448
|
-
}
|
|
3449
|
-
console.log(pc8.green(`\u2713 Ready to package and submit upstream contribution to ${payload.repo}.`));
|
|
3450
|
-
console.log(pc8.cyan(`Command executed by optimizer routine or operator:
|
|
3451
|
-
`));
|
|
3452
|
-
console.log(` ${payload.prCommand}
|
|
3453
|
-
`);
|
|
3454
|
-
return {
|
|
3455
|
-
success: true,
|
|
3456
|
-
branchName: payload.branchName,
|
|
3457
|
-
title: payload.title
|
|
3458
|
-
};
|
|
3459
|
-
}
|
|
3460
|
-
} catch (err) {
|
|
3461
|
-
console.error(pc8.red(`\u274C Error during contribution preparation: ${err.message}`));
|
|
3462
|
-
if (options.throwOnError) {
|
|
3463
|
-
throw err;
|
|
3464
|
-
}
|
|
3669
|
+
const raw = fs9.readFileSync(filePath, "utf8");
|
|
3670
|
+
const parsed = JSON.parse(raw);
|
|
3465
3671
|
return {
|
|
3466
|
-
|
|
3467
|
-
error: err.message
|
|
3672
|
+
repositories: Array.isArray(parsed.repositories) ? parsed.repositories : []
|
|
3468
3673
|
};
|
|
3674
|
+
} catch {
|
|
3675
|
+
return { repositories: [] };
|
|
3469
3676
|
}
|
|
3470
3677
|
}
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
// src/lib/telemetry.ts
|
|
3478
|
-
import fs10 from "fs";
|
|
3479
|
-
import path10 from "path";
|
|
3480
|
-
import pc9 from "picocolors";
|
|
3481
|
-
var GLOBAL_WEEKLY_TOKEN_BUDGET = 875e4;
|
|
3482
|
-
function parseLogToTelemetry(content, options = {}) {
|
|
3483
|
-
if (!content || typeof content !== "string") return null;
|
|
3484
|
-
const lines = content.split("\n");
|
|
3485
|
-
const metadata = {};
|
|
3486
|
-
for (const line of lines) {
|
|
3487
|
-
const match = line.match(/^\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|/);
|
|
3488
|
-
if (!match) continue;
|
|
3489
|
-
const key = match[1].trim().toLowerCase();
|
|
3490
|
-
const val = match[2].trim().replace(/`/g, "");
|
|
3491
|
-
metadata[key] = val;
|
|
3492
|
-
}
|
|
3493
|
-
if (!metadata["timestamp"] && !metadata["routine"]) {
|
|
3494
|
-
return null;
|
|
3495
|
-
}
|
|
3496
|
-
const routine = metadata["routine"] || "unknown";
|
|
3497
|
-
const timestamp = metadata["timestamp"] || (/* @__PURE__ */ new Date()).toISOString();
|
|
3498
|
-
const result = metadata["result"] || "UNKNOWN";
|
|
3499
|
-
const errorReasonRaw = metadata["error reason"];
|
|
3500
|
-
const errorReason = errorReasonRaw && errorReasonRaw.toUpperCase() !== "N/A" ? errorReasonRaw : void 0;
|
|
3501
|
-
let failureCategory = metadata["failure category"];
|
|
3502
|
-
if (!failureCategory && result === "FAILURE" && errorReason) {
|
|
3503
|
-
const lower = errorReason.toLowerCase();
|
|
3504
|
-
if (lower.includes("token") || lower.includes("ceiling")) {
|
|
3505
|
-
failureCategory = "token_limit";
|
|
3506
|
-
} else if (lower.includes("build") || lower.includes("type-check")) {
|
|
3507
|
-
failureCategory = "build_error";
|
|
3508
|
-
} else if (lower.includes("infeasible") || lower.includes("blocker")) {
|
|
3509
|
-
failureCategory = "infeasible";
|
|
3510
|
-
} else if (lower.includes("conflict")) {
|
|
3511
|
-
failureCategory = "merge_conflict";
|
|
3512
|
-
} else if (lower.includes("ambiguous") || lower.includes("needs-info") || lower.includes("clarification") || lower.includes("acceptance criteria")) {
|
|
3513
|
-
failureCategory = "ambiguous_spec";
|
|
3514
|
-
}
|
|
3515
|
-
}
|
|
3516
|
-
const inputTokens = parseInt((metadata["input tokens"] || "0").replace(/[^\d]/g, ""), 10) || 0;
|
|
3517
|
-
const outputTokens = parseInt((metadata["output tokens"] || "0").replace(/[^\d]/g, ""), 10) || 0;
|
|
3518
|
-
const totalTokens = inputTokens + outputTokens;
|
|
3519
|
-
const estimatedCost = parseFloat((metadata["estimated cost"] || "0").replace(/[^0-9.]/g, "")) || 0;
|
|
3520
|
-
let durationSeconds;
|
|
3521
|
-
if (metadata["duration"]) {
|
|
3522
|
-
const durNum = parseInt(metadata["duration"].replace(/[^\d]/g, ""), 10);
|
|
3523
|
-
if (!isNaN(durNum)) durationSeconds = durNum;
|
|
3524
|
-
}
|
|
3525
|
-
let iterationsUsed;
|
|
3526
|
-
let maxIterations;
|
|
3527
|
-
if (metadata["iterations used"]) {
|
|
3528
|
-
const iterMatch = metadata["iterations used"].match(/(\d+)\s*\/\s*(\d+)/);
|
|
3529
|
-
if (iterMatch) {
|
|
3530
|
-
iterationsUsed = parseInt(iterMatch[1], 10);
|
|
3531
|
-
maxIterations = parseInt(iterMatch[2], 10);
|
|
3532
|
-
} else {
|
|
3533
|
-
const singleNum = parseInt(metadata["iterations used"].replace(/[^\d]/g, ""), 10);
|
|
3534
|
-
if (!isNaN(singleNum)) iterationsUsed = singleNum;
|
|
3535
|
-
}
|
|
3678
|
+
function saveGlobalConfig(config, customPath) {
|
|
3679
|
+
const filePath = customPath || getDefaultGlobalConfigPath();
|
|
3680
|
+
const dir = path9.dirname(filePath);
|
|
3681
|
+
if (!fs9.existsSync(dir)) {
|
|
3682
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
3536
3683
|
}
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
const
|
|
3541
|
-
|
|
3542
|
-
if (
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
);
|
|
3546
|
-
questionsAskedCount = questionLines.length > 0 ? questionLines.length : 2;
|
|
3684
|
+
fs9.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
3685
|
+
}
|
|
3686
|
+
function addGlobalRepository(repo, customPath) {
|
|
3687
|
+
const config = loadGlobalConfig(customPath);
|
|
3688
|
+
const normalized = repo.trim();
|
|
3689
|
+
if (!normalized) return config;
|
|
3690
|
+
if (!config.repositories.includes(normalized)) {
|
|
3691
|
+
config.repositories.push(normalized);
|
|
3692
|
+
saveGlobalConfig(config, customPath);
|
|
3547
3693
|
}
|
|
3548
|
-
return
|
|
3549
|
-
schemaVersion: "1.0.0",
|
|
3550
|
-
routine,
|
|
3551
|
-
timestamp,
|
|
3552
|
-
repository: options.repository || "local",
|
|
3553
|
-
runId: options.runId,
|
|
3554
|
-
runNumber: options.runNumber,
|
|
3555
|
-
result,
|
|
3556
|
-
errorReason,
|
|
3557
|
-
failureCategory,
|
|
3558
|
-
inputTokens,
|
|
3559
|
-
outputTokens,
|
|
3560
|
-
totalTokens,
|
|
3561
|
-
estimatedCost,
|
|
3562
|
-
durationSeconds,
|
|
3563
|
-
iterationsUsed,
|
|
3564
|
-
maxIterations,
|
|
3565
|
-
promptSha,
|
|
3566
|
-
ambiguityGateTriggered: ambiguityGateTriggered || void 0,
|
|
3567
|
-
questionsAskedCount,
|
|
3568
|
-
needsInfoApplied: needsInfoApplied || void 0
|
|
3569
|
-
};
|
|
3694
|
+
return config;
|
|
3570
3695
|
}
|
|
3571
|
-
function
|
|
3572
|
-
const
|
|
3573
|
-
const
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3696
|
+
function removeGlobalRepository(repo, customPath) {
|
|
3697
|
+
const config = loadGlobalConfig(customPath);
|
|
3698
|
+
const normalized = repo.trim();
|
|
3699
|
+
config.repositories = config.repositories.filter((r) => r !== normalized);
|
|
3700
|
+
saveGlobalConfig(config, customPath);
|
|
3701
|
+
return config;
|
|
3702
|
+
}
|
|
3703
|
+
function getFleetRepositories(cwd, customGlobalConfigPath) {
|
|
3704
|
+
const targetDir = cwd || process.cwd();
|
|
3705
|
+
const manifest = loadManifest(targetDir);
|
|
3706
|
+
const manifestRepos = Array.isArray(manifest?.repositories) ? manifest.repositories : [];
|
|
3707
|
+
const globalConfig = loadGlobalConfig(customGlobalConfigPath);
|
|
3708
|
+
const globalRepos = globalConfig.repositories;
|
|
3709
|
+
const set = /* @__PURE__ */ new Set();
|
|
3710
|
+
for (const r of manifestRepos) {
|
|
3711
|
+
if (r && typeof r === "string" && r.trim()) set.add(r.trim());
|
|
3712
|
+
}
|
|
3713
|
+
for (const r of globalRepos) {
|
|
3714
|
+
if (r && typeof r === "string" && r.trim()) set.add(r.trim());
|
|
3583
3715
|
}
|
|
3584
|
-
return
|
|
3585
|
-
weeklyCeilingTokens: ceilingTokens,
|
|
3586
|
-
usedTokens,
|
|
3587
|
-
remainingTokens,
|
|
3588
|
-
utilizationPercentage,
|
|
3589
|
-
status,
|
|
3590
|
-
dailyBurnRate,
|
|
3591
|
-
projectedExhaustionDays
|
|
3592
|
-
};
|
|
3716
|
+
return Array.from(set);
|
|
3593
3717
|
}
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
avgIterationsUsed: 0,
|
|
3628
|
-
ambiguityGatesTriggered: 0,
|
|
3629
|
-
questionsAskedCount: 0,
|
|
3630
|
-
needsInfoAppliedCount: 0
|
|
3631
|
-
};
|
|
3632
|
-
}
|
|
3633
|
-
const r = byRoutine[s.routine];
|
|
3634
|
-
r.runCount++;
|
|
3635
|
-
r.totalInputTokens += s.inputTokens;
|
|
3636
|
-
r.totalOutputTokens += s.outputTokens;
|
|
3637
|
-
r.totalTokens += s.totalTokens;
|
|
3638
|
-
r.totalEstimatedCost += s.estimatedCost;
|
|
3639
|
-
if (s.result === "SUCCESS") r.successCount++;
|
|
3640
|
-
else if (s.result === "FAILURE") r.failureCount++;
|
|
3641
|
-
else if (s.result === "BOUNCED_TO_DRAFT") r.bouncedCount++;
|
|
3642
|
-
if (s.durationSeconds) {
|
|
3643
|
-
r.avgDurationSeconds = (r.avgDurationSeconds * (r.runCount - 1) + s.durationSeconds) / r.runCount;
|
|
3644
|
-
}
|
|
3645
|
-
if (s.iterationsUsed) {
|
|
3646
|
-
r.avgIterationsUsed = (r.avgIterationsUsed * (r.runCount - 1) + s.iterationsUsed) / r.runCount;
|
|
3647
|
-
}
|
|
3648
|
-
if (s.ambiguityGateTriggered) r.ambiguityGatesTriggered++;
|
|
3649
|
-
if (s.questionsAskedCount) r.questionsAskedCount += s.questionsAskedCount;
|
|
3650
|
-
if (s.needsInfoApplied) r.needsInfoAppliedCount++;
|
|
3651
|
-
if (!byRepository[s.repository]) {
|
|
3652
|
-
byRepository[s.repository] = {
|
|
3653
|
-
repository: s.repository,
|
|
3654
|
-
runCount: 0,
|
|
3655
|
-
totalTokens: 0,
|
|
3656
|
-
totalEstimatedCost: 0,
|
|
3657
|
-
successCount: 0,
|
|
3658
|
-
failureCount: 0
|
|
3659
|
-
};
|
|
3718
|
+
|
|
3719
|
+
// src/commands/monitor.ts
|
|
3720
|
+
async function runMonitor(options = {}) {
|
|
3721
|
+
const cwd = options.cwd || process.cwd();
|
|
3722
|
+
const executor = options.executor || defaultGhExecutor;
|
|
3723
|
+
if (options.add) {
|
|
3724
|
+
const updated = addGlobalRepository(options.add);
|
|
3725
|
+
console.log(pc7.green(`\u2713 Added ${pc7.bold(options.add)} to Jonah Fleet registry.`));
|
|
3726
|
+
console.log(pc7.gray(` Current registered repositories: ${updated.repositories.join(", ") || "none"}
|
|
3727
|
+
`));
|
|
3728
|
+
return;
|
|
3729
|
+
}
|
|
3730
|
+
if (options.remove) {
|
|
3731
|
+
const updated = removeGlobalRepository(options.remove);
|
|
3732
|
+
console.log(pc7.yellow(`\u2713 Removed ${pc7.bold(options.remove)} from Jonah Fleet registry.`));
|
|
3733
|
+
console.log(pc7.gray(` Current registered repositories: ${updated.repositories.join(", ") || "none"}
|
|
3734
|
+
`));
|
|
3735
|
+
return;
|
|
3736
|
+
}
|
|
3737
|
+
let targetRepos = [];
|
|
3738
|
+
if (options.repos && options.repos.length > 0) {
|
|
3739
|
+
targetRepos = options.repos;
|
|
3740
|
+
} else {
|
|
3741
|
+
targetRepos = getFleetRepositories(cwd);
|
|
3742
|
+
}
|
|
3743
|
+
if (targetRepos.length === 0) {
|
|
3744
|
+
try {
|
|
3745
|
+
const remoteRaw = await executor(["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"]);
|
|
3746
|
+
const currentRepo = remoteRaw.trim();
|
|
3747
|
+
if (currentRepo) {
|
|
3748
|
+
targetRepos = [currentRepo];
|
|
3749
|
+
}
|
|
3750
|
+
} catch {
|
|
3660
3751
|
}
|
|
3661
|
-
const repoObj = byRepository[s.repository];
|
|
3662
|
-
repoObj.runCount++;
|
|
3663
|
-
repoObj.totalTokens += s.totalTokens;
|
|
3664
|
-
repoObj.totalEstimatedCost += s.estimatedCost;
|
|
3665
|
-
if (s.result === "SUCCESS") repoObj.successCount++;
|
|
3666
|
-
else if (s.result === "FAILURE") repoObj.failureCount++;
|
|
3667
3752
|
}
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3753
|
+
if (targetRepos.length === 0) {
|
|
3754
|
+
console.log(pc7.yellow("\n\u26A0\uFE0F No fleet repositories registered."));
|
|
3755
|
+
console.log(pc7.cyan("Add repositories to monitor with:"));
|
|
3756
|
+
console.log(pc7.gray(" jonah-fleet monitor --add owner/repo"));
|
|
3757
|
+
console.log(pc7.gray("Or specify repositories directly:"));
|
|
3758
|
+
console.log(pc7.gray(" jonah-fleet monitor owner/repo-1 owner/repo-2\n"));
|
|
3759
|
+
return;
|
|
3675
3760
|
}
|
|
3676
|
-
const
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
failureCount,
|
|
3690
|
-
bouncedCount,
|
|
3691
|
-
totalInputTokens,
|
|
3692
|
-
totalOutputTokens,
|
|
3693
|
-
totalTokens,
|
|
3694
|
-
totalEstimatedCost: totalCost,
|
|
3695
|
-
budget,
|
|
3696
|
-
byRoutine,
|
|
3697
|
-
byRepository,
|
|
3698
|
-
failureCategories,
|
|
3699
|
-
ambiguity,
|
|
3700
|
-
events: summaries
|
|
3761
|
+
const pollAndRender = async () => {
|
|
3762
|
+
const statuses = await Promise.all(
|
|
3763
|
+
targetRepos.map((repo) => queryRepoFleetStatus(repo, executor))
|
|
3764
|
+
);
|
|
3765
|
+
if (options.watch && !options.json) {
|
|
3766
|
+
console.clear();
|
|
3767
|
+
}
|
|
3768
|
+
const output = renderFleetDashboard(statuses, {
|
|
3769
|
+
json: options.json,
|
|
3770
|
+
tokens: options.tokens,
|
|
3771
|
+
detailed: options.detailed
|
|
3772
|
+
});
|
|
3773
|
+
console.log(output);
|
|
3701
3774
|
};
|
|
3775
|
+
await pollAndRender();
|
|
3776
|
+
if (options.watch) {
|
|
3777
|
+
const intervalSec = typeof options.interval === "number" ? options.interval : parseInt(options.interval || "10", 10) || 10;
|
|
3778
|
+
const timer = setInterval(async () => {
|
|
3779
|
+
await pollAndRender();
|
|
3780
|
+
}, intervalSec * 1e3);
|
|
3781
|
+
const handleSigint = () => {
|
|
3782
|
+
clearInterval(timer);
|
|
3783
|
+
process.removeListener("SIGINT", handleSigint);
|
|
3784
|
+
console.log(pc7.gray("\nStopped live fleet monitoring.\n"));
|
|
3785
|
+
process.exit(0);
|
|
3786
|
+
};
|
|
3787
|
+
process.on("SIGINT", handleSigint);
|
|
3788
|
+
}
|
|
3702
3789
|
}
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3790
|
+
|
|
3791
|
+
// src/commands/status.ts
|
|
3792
|
+
async function runStatus(options = {}) {
|
|
3793
|
+
const cwd = options.cwd || process.cwd();
|
|
3794
|
+
if (options.fleet) {
|
|
3795
|
+
await runMonitor({ cwd, json: options.json, tokens: options.tokens, detailed: options.detailed });
|
|
3796
|
+
return;
|
|
3706
3797
|
}
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
"User-Agent": "jonah-fleet-telemetry/1.0"
|
|
3713
|
-
},
|
|
3714
|
-
body: JSON.stringify(summary)
|
|
3715
|
-
});
|
|
3716
|
-
if (!res.ok) {
|
|
3717
|
-
return { success: false, error: `HTTP ${res.status}: ${res.statusText}` };
|
|
3798
|
+
const manifest = loadManifest(cwd);
|
|
3799
|
+
if (!manifest) {
|
|
3800
|
+
if (options.json) {
|
|
3801
|
+
console.log(JSON.stringify({ error: "No agents-manifest.json found", cwd }, null, 2));
|
|
3802
|
+
return;
|
|
3718
3803
|
}
|
|
3719
|
-
|
|
3720
|
-
}
|
|
3721
|
-
|
|
3804
|
+
console.log(pc8.yellow(`
|
|
3805
|
+
\u26A0\uFE0F No agents-manifest.json found in ${cwd}. This project is not configured with Jonah Fleet.`));
|
|
3806
|
+
console.log(pc8.cyan(`Run 'npx jonah-fleet init' to set up autonomous agent routines.
|
|
3807
|
+
`));
|
|
3808
|
+
return;
|
|
3809
|
+
}
|
|
3810
|
+
const drift = checkDrift(cwd, manifest);
|
|
3811
|
+
const hasDrift = drift.missingPrompts.length > 0 || drift.modifiedPrompts.length > 0 || drift.missingWorkflows.length > 0 || drift.modifiedWorkflows.length > 0 || drift.missingSkills.length > 0;
|
|
3812
|
+
const runsDir = path10.join(cwd, ".jonah-fleet/runs");
|
|
3813
|
+
const legacyLogsDir = path10.join(cwd, ".github/prompts/logs");
|
|
3814
|
+
let tokenUsage = void 0;
|
|
3815
|
+
const logContents = [];
|
|
3816
|
+
const collectLogs = (dir) => {
|
|
3817
|
+
if (!fs10.existsSync(dir)) return;
|
|
3818
|
+
const entries = fs10.readdirSync(dir, { withFileTypes: true });
|
|
3819
|
+
for (const entry of entries) {
|
|
3820
|
+
const fullPath = path10.join(dir, entry.name);
|
|
3821
|
+
if (entry.isDirectory()) {
|
|
3822
|
+
collectLogs(fullPath);
|
|
3823
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
3824
|
+
try {
|
|
3825
|
+
logContents.push(fs10.readFileSync(fullPath, "utf8"));
|
|
3826
|
+
} catch {
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
};
|
|
3831
|
+
collectLogs(runsDir);
|
|
3832
|
+
collectLogs(legacyLogsDir);
|
|
3833
|
+
if (logContents.length > 0) {
|
|
3834
|
+
tokenUsage = computeTokenSpendFromLogs(logContents);
|
|
3835
|
+
}
|
|
3836
|
+
const rollingQuota = getRollingWindowTokenUsage(cwd);
|
|
3837
|
+
if (options.json) {
|
|
3838
|
+
console.log(
|
|
3839
|
+
JSON.stringify(
|
|
3840
|
+
{
|
|
3841
|
+
cwd,
|
|
3842
|
+
version: manifest.version,
|
|
3843
|
+
fleetLatestVersion: FLEET_VERSION,
|
|
3844
|
+
preset: manifest.preset,
|
|
3845
|
+
autoUpdate: manifest.autoUpdate,
|
|
3846
|
+
routines: manifest.routines,
|
|
3847
|
+
skills: manifest.skills,
|
|
3848
|
+
models: manifest.models,
|
|
3849
|
+
budgets: manifest.budgets,
|
|
3850
|
+
repositories: manifest.repositories || [],
|
|
3851
|
+
tokenUsage,
|
|
3852
|
+
quotaPacing: rollingQuota,
|
|
3853
|
+
drift: {
|
|
3854
|
+
hasDrift,
|
|
3855
|
+
...drift
|
|
3856
|
+
}
|
|
3857
|
+
},
|
|
3858
|
+
null,
|
|
3859
|
+
2
|
|
3860
|
+
)
|
|
3861
|
+
);
|
|
3862
|
+
return;
|
|
3863
|
+
}
|
|
3864
|
+
console.log(
|
|
3865
|
+
renderFleetBanner({
|
|
3866
|
+
command: "STATUS",
|
|
3867
|
+
subtitle: "TELEMETRY & DRIFT AUDIT",
|
|
3868
|
+
details: [
|
|
3869
|
+
{ label: "Version", value: manifest.version },
|
|
3870
|
+
{ label: "Preset", value: manifest.preset },
|
|
3871
|
+
{ label: "Target", value: cwd }
|
|
3872
|
+
]
|
|
3873
|
+
})
|
|
3874
|
+
);
|
|
3875
|
+
console.log(pc8.bold(pc8.cyan(`
|
|
3876
|
+
\u{1F4CA} Jonah Fleet Status for ${cwd}
|
|
3877
|
+
`)));
|
|
3878
|
+
console.log(` Version: ${manifest.version === FLEET_VERSION ? pc8.green(manifest.version) : pc8.yellow(`${manifest.version} (fleet latest: ${FLEET_VERSION})`)}`);
|
|
3879
|
+
console.log(` Preset: ${pc8.bold(manifest.preset)}`);
|
|
3880
|
+
console.log(` Auto-Update: ${manifest.autoUpdate?.enabled ? pc8.green("Enabled (" + manifest.autoUpdate.channel + ")") : pc8.gray("Disabled")}`);
|
|
3881
|
+
console.log(pc8.bold("\n Enabled Routines:"));
|
|
3882
|
+
for (const [routine, enabled] of Object.entries(manifest.routines)) {
|
|
3883
|
+
console.log(` - ${routine.padEnd(35)}: ${enabled ? pc8.green("ENABLED") : pc8.gray("DISABLED")}`);
|
|
3722
3884
|
}
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
const fullPath = path10.join(currentDir, entry.name);
|
|
3733
|
-
if (entry.isDirectory()) {
|
|
3734
|
-
traverse(fullPath);
|
|
3735
|
-
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
3736
|
-
try {
|
|
3737
|
-
const content = fs10.readFileSync(fullPath, "utf8");
|
|
3738
|
-
const summary = parseLogToTelemetry(content, { repository: repositoryName });
|
|
3739
|
-
if (summary) summaries.push(summary);
|
|
3740
|
-
} catch {
|
|
3741
|
-
}
|
|
3885
|
+
console.log(pc8.bold("\n Configured Skills:"));
|
|
3886
|
+
for (const skill of manifest.skills) {
|
|
3887
|
+
console.log(` - ${pc8.cyan(skill)}`);
|
|
3888
|
+
}
|
|
3889
|
+
if (manifest.models && Object.keys(manifest.models).length > 0) {
|
|
3890
|
+
console.log(pc8.bold("\n Model Profiles:"));
|
|
3891
|
+
for (const [routine, model] of Object.entries(manifest.models)) {
|
|
3892
|
+
if (model) {
|
|
3893
|
+
console.log(` - ${routine.padEnd(35)}: ${pc8.cyan(model)}`);
|
|
3742
3894
|
}
|
|
3743
3895
|
}
|
|
3744
|
-
};
|
|
3745
|
-
if (fs10.existsSync(runsDir)) {
|
|
3746
|
-
traverse(runsDir);
|
|
3747
|
-
}
|
|
3748
|
-
if (fs10.existsSync(legacyLogsDir)) {
|
|
3749
|
-
traverse(legacyLogsDir);
|
|
3750
3896
|
}
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
"list",
|
|
3762
|
-
"--repo",
|
|
3763
|
-
repoIdentifier,
|
|
3764
|
-
"--label",
|
|
3765
|
-
"routine-log",
|
|
3766
|
-
"--state",
|
|
3767
|
-
"all",
|
|
3768
|
-
"--limit",
|
|
3769
|
-
String(options.maxLogs || 25),
|
|
3770
|
-
"--json",
|
|
3771
|
-
"body,number,title,createdAt"
|
|
3772
|
-
]);
|
|
3773
|
-
const issues = JSON.parse(issuesRaw);
|
|
3774
|
-
if (Array.isArray(issues) && issues.length > 0) {
|
|
3775
|
-
for (const issue of issues) {
|
|
3776
|
-
if (issue.body) {
|
|
3777
|
-
const summary = parseLogToTelemetry(issue.body, { repository: repoIdentifier });
|
|
3778
|
-
if (summary) summaries.push(summary);
|
|
3897
|
+
if (manifest.budgets) {
|
|
3898
|
+
console.log(pc8.bold("\n Configured Budgets:"));
|
|
3899
|
+
if (manifest.budgets.weeklyTokens) {
|
|
3900
|
+
console.log(` - Weekly Token Budget: ${pc8.cyan(formatTokens2(manifest.budgets.weeklyTokens))}`);
|
|
3901
|
+
}
|
|
3902
|
+
if (manifest.budgets.timeoutMinutes && Object.keys(manifest.budgets.timeoutMinutes).length > 0) {
|
|
3903
|
+
console.log(` - Timeouts:`);
|
|
3904
|
+
for (const [routine, timeout] of Object.entries(manifest.budgets.timeoutMinutes)) {
|
|
3905
|
+
if (timeout !== void 0) {
|
|
3906
|
+
console.log(` \u2022 ${routine}: ${pc8.cyan(timeout + "m")}`);
|
|
3779
3907
|
}
|
|
3780
3908
|
}
|
|
3781
|
-
|
|
3782
|
-
|
|
3909
|
+
}
|
|
3910
|
+
if (manifest.budgets.maxIterations && Object.keys(manifest.budgets.maxIterations).length > 0) {
|
|
3911
|
+
console.log(` - Max Iterations:`);
|
|
3912
|
+
for (const [routine, iter] of Object.entries(manifest.budgets.maxIterations)) {
|
|
3913
|
+
if (iter !== void 0) {
|
|
3914
|
+
console.log(` \u2022 ${routine}: ${pc8.cyan(String(iter))}`);
|
|
3915
|
+
}
|
|
3783
3916
|
}
|
|
3784
3917
|
}
|
|
3785
|
-
} catch {
|
|
3786
3918
|
}
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
`
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3919
|
+
if (manifest.repositories && manifest.repositories.length > 0) {
|
|
3920
|
+
console.log(pc8.bold("\n Fleet Repositories:"));
|
|
3921
|
+
for (const repo of manifest.repositories) {
|
|
3922
|
+
console.log(` - ${pc8.cyan(repo)}`);
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
if (tokenUsage && tokenUsage.recentRunCount > 0) {
|
|
3926
|
+
console.log(pc8.bold("\n \u{1F4C8} 7-Day Token Spend:"));
|
|
3927
|
+
console.log(
|
|
3928
|
+
` Runs: ${pc8.bold(tokenUsage.recentRunCount.toString())} | Tokens: ${pc8.bold(formatTokens2(tokenUsage.sevenDayTotalTokens))} ` + pc8.gray(`(in: ${formatTokens2(tokenUsage.sevenDayInputTokens)}, out: ${formatTokens2(tokenUsage.sevenDayOutputTokens)})`) + ` | Cost: ${pc8.bold(pc8.green(formatCurrency(tokenUsage.sevenDayEstimatedCost)))}`
|
|
3929
|
+
);
|
|
3930
|
+
if (tokenUsage.byRoutine && Object.keys(tokenUsage.byRoutine).length > 0) {
|
|
3931
|
+
const routines = Object.values(tokenUsage.byRoutine).sort((a, b) => b.totalTokens - a.totalTokens);
|
|
3932
|
+
for (const r of routines) {
|
|
3933
|
+
const iterStr = r.avgIterationsUsed !== void 0 ? `, avg ${r.avgIterationsUsed} iters` : "";
|
|
3934
|
+
const tokenDetails = options.tokens || options.detailed ? ` (in: ${formatTokens2(r.inputTokens)}, out: ${formatTokens2(r.outputTokens)})` : "";
|
|
3935
|
+
console.log(
|
|
3936
|
+
` \u2022 ${pc8.bold(r.routine)}: ${pc8.cyan(formatTokens2(r.totalTokens))} tokens${pc8.gray(tokenDetails)} ` + pc8.gray(`(${r.fleetSharePercent.toFixed(1)}%)`) + ` | Cost: ${pc8.green(formatCurrency(r.estimatedCost))} | ${r.runCount} run${r.runCount === 1 ? "" : "s"}${pc8.gray(iterStr)}`
|
|
3937
|
+
);
|
|
3806
3938
|
}
|
|
3807
3939
|
}
|
|
3808
|
-
} catch {
|
|
3809
3940
|
}
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3941
|
+
if (rollingQuota.windowTokens > 0 || rollingQuota.weeklyTokens > 0) {
|
|
3942
|
+
const badge5h = formatQuotaStatusBadge(rollingQuota.windowStatus);
|
|
3943
|
+
const badgeWeekly = formatQuotaStatusBadge(rollingQuota.weeklyStatus);
|
|
3944
|
+
console.log(pc8.bold("\n \u23F1\uFE0F Token Quota & Rolling Window Pacing:"));
|
|
3945
|
+
console.log(
|
|
3946
|
+
` \u2022 5-Hour Window: ${pc8.bold(formatTokens2(rollingQuota.windowTokens))} / ${formatTokens2(rollingQuota.windowLimit)} tokens (${pc8.cyan(rollingQuota.windowPercentage.toFixed(1) + "%")}) ${badge5h}`
|
|
3947
|
+
);
|
|
3948
|
+
console.log(
|
|
3949
|
+
` \u2022 7-Day Spend: ${pc8.bold(formatTokens2(rollingQuota.weeklyTokens))} / ${formatTokens2(rollingQuota.weeklyLimit)} tokens (${pc8.cyan(rollingQuota.weeklyPercentage.toFixed(1) + "%")}) ${badgeWeekly}`
|
|
3950
|
+
);
|
|
3815
3951
|
}
|
|
3816
|
-
|
|
3817
|
-
|
|
3952
|
+
console.log(pc8.bold("\n Drift / Health:"));
|
|
3953
|
+
if (!hasDrift) {
|
|
3954
|
+
console.log(pc8.green(" \u2713 All prompts, workflows, and skills are healthy and match fleet templates.\n"));
|
|
3955
|
+
} else {
|
|
3956
|
+
if (drift.missingPrompts.length > 0) console.log(pc8.red(` \u274C Missing prompts: ${drift.missingPrompts.join(", ")}`));
|
|
3957
|
+
if (drift.modifiedPrompts.length > 0) console.log(pc8.yellow(` \u26A0\uFE0F Modified prompts: ${drift.modifiedPrompts.join(", ")}`));
|
|
3958
|
+
if (drift.missingWorkflows.length > 0) console.log(pc8.red(` \u274C Missing workflows: ${drift.missingWorkflows.join(", ")}`));
|
|
3959
|
+
if (drift.modifiedWorkflows.length > 0) console.log(pc8.yellow(` \u26A0\uFE0F Modified workflows: ${drift.modifiedWorkflows.join(", ")}`));
|
|
3960
|
+
if (drift.missingSkills.length > 0) console.log(pc8.red(` \u274C Missing skills: ${drift.missingSkills.join(", ")}`));
|
|
3961
|
+
console.log(pc8.cyan("\n Run 'jonah-fleet sync' to synchronize files.\n"));
|
|
3818
3962
|
}
|
|
3819
|
-
return num.toString();
|
|
3820
3963
|
}
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
const
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3964
|
+
|
|
3965
|
+
// src/commands/contribute.ts
|
|
3966
|
+
import { execSync } from "child_process";
|
|
3967
|
+
import pc9 from "picocolors";
|
|
3968
|
+
function prepareContributionPayload(options = {}) {
|
|
3969
|
+
const repo = options.repo || "Jonah-Projects/jonah-fleet";
|
|
3970
|
+
const title = options.title || "fix(prompts): improve orchestrator routine handling";
|
|
3971
|
+
const body = options.body || "Proposed prompt optimization discovered during autonomous execution runs.";
|
|
3972
|
+
const branchName = `contrib/optimize-${Date.now()}`;
|
|
3973
|
+
const prCommand = `gh pr create --repo ${repo} --title "${title}" --body "${body}"`;
|
|
3974
|
+
return {
|
|
3975
|
+
repo,
|
|
3976
|
+
branchName,
|
|
3977
|
+
title,
|
|
3978
|
+
body,
|
|
3979
|
+
prompt: options.prompt,
|
|
3980
|
+
prCommand
|
|
3981
|
+
};
|
|
3830
3982
|
}
|
|
3831
|
-
function
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
const
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
lines.push(
|
|
3851
|
-
` Total Runs: ${pc9.bold(telemetry.totalRuns.toString())} (${pc9.green(telemetry.successCount + " success")}, ${pc9.red(telemetry.failureCount + " failed")}, ${pc9.yellow(telemetry.bouncedCount + " bounced")})`
|
|
3852
|
-
);
|
|
3853
|
-
lines.push(
|
|
3854
|
-
` Total Tokens: ${pc9.bold(formatTokens2(telemetry.totalTokens))} ` + pc9.gray(`(in: ${formatTokens2(telemetry.totalInputTokens)}, out: ${formatTokens2(telemetry.totalOutputTokens)})`) + ` | Est. Cost: ${pc9.bold(pc9.green(`$${telemetry.totalEstimatedCost.toFixed(2)}`))}`
|
|
3855
|
-
);
|
|
3856
|
-
lines.push("\n" + pc9.bold("\u{1F916} Spend by Agent Routine:"));
|
|
3857
|
-
for (const [routineName, r] of Object.entries(telemetry.byRoutine)) {
|
|
3858
|
-
const costStr = pc9.green(`$${r.totalEstimatedCost.toFixed(2)}`);
|
|
3859
|
-
lines.push(
|
|
3860
|
-
` \u2022 ${pc9.cyan(routineName.padEnd(30))} Runs: ${pc9.bold(r.runCount.toString().padStart(2))} | Tokens: ${pc9.bold(formatTokens2(r.totalTokens).padStart(7))} | Cost: ${costStr.padStart(6)} | Avg Iter: ${r.avgIterationsUsed.toFixed(1)}`
|
|
3861
|
-
);
|
|
3983
|
+
async function runContribute(options = {}) {
|
|
3984
|
+
console.log(pc9.cyan(`
|
|
3985
|
+
\u{1F680} Jonah Fleet Upstream Contribution Bridge
|
|
3986
|
+
`));
|
|
3987
|
+
const payload = prepareContributionPayload(options);
|
|
3988
|
+
console.log(`Preparing upstream contribution PR against ${pc9.bold(payload.repo)}...`);
|
|
3989
|
+
console.log(`Title: ${pc9.green(payload.title)}`);
|
|
3990
|
+
console.log(`Body: ${pc9.gray(payload.body)}
|
|
3991
|
+
`);
|
|
3992
|
+
if (options.dryRun) {
|
|
3993
|
+
console.log(pc9.yellow(`[DRY-RUN] Would create branch: ${payload.branchName}`));
|
|
3994
|
+
console.log(pc9.yellow(`[DRY-RUN] Would execute: ${payload.prCommand}
|
|
3995
|
+
`));
|
|
3996
|
+
return {
|
|
3997
|
+
success: true,
|
|
3998
|
+
dryRun: true,
|
|
3999
|
+
branchName: payload.branchName,
|
|
4000
|
+
title: payload.title
|
|
4001
|
+
};
|
|
3862
4002
|
}
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
4003
|
+
try {
|
|
4004
|
+
console.log(`Creating branch ${pc9.cyan(payload.branchName)}...`);
|
|
4005
|
+
if (options.executor) {
|
|
4006
|
+
await options.executor(["auth", "status"]);
|
|
4007
|
+
const prUrl = await options.executor([
|
|
4008
|
+
"pr",
|
|
4009
|
+
"create",
|
|
4010
|
+
"--repo",
|
|
4011
|
+
payload.repo,
|
|
4012
|
+
"--title",
|
|
4013
|
+
payload.title,
|
|
4014
|
+
"--body",
|
|
4015
|
+
payload.body
|
|
4016
|
+
]);
|
|
4017
|
+
console.log(pc9.green(`\u2713 Submitted upstream contribution: ${prUrl}`));
|
|
4018
|
+
return {
|
|
4019
|
+
success: true,
|
|
4020
|
+
branchName: payload.branchName,
|
|
4021
|
+
title: payload.title,
|
|
4022
|
+
prUrl: typeof prUrl === "string" && prUrl.trim() ? prUrl.trim() : void 0
|
|
4023
|
+
};
|
|
4024
|
+
} else {
|
|
4025
|
+
try {
|
|
4026
|
+
execSync("gh auth status", { stdio: "pipe" });
|
|
4027
|
+
} catch {
|
|
4028
|
+
const errorMsg = "GitHub CLI (`gh`) is not authenticated. Run `gh auth login` first.";
|
|
4029
|
+
console.error(pc9.red(`\u274C ${errorMsg}`));
|
|
4030
|
+
if (options.throwOnError) throw new Error(errorMsg);
|
|
4031
|
+
return { success: false, error: errorMsg };
|
|
4032
|
+
}
|
|
4033
|
+
console.log(pc9.green(`\u2713 Ready to package and submit upstream contribution to ${payload.repo}.`));
|
|
4034
|
+
console.log(pc9.cyan(`Command executed by optimizer routine or operator:
|
|
4035
|
+
`));
|
|
4036
|
+
console.log(` ${payload.prCommand}
|
|
4037
|
+
`);
|
|
4038
|
+
return {
|
|
4039
|
+
success: true,
|
|
4040
|
+
branchName: payload.branchName,
|
|
4041
|
+
title: payload.title
|
|
4042
|
+
};
|
|
3869
4043
|
}
|
|
3870
|
-
}
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
` \u2022 Ambiguity Gate Triggers: ${pc9.bold(amb.totalAmbiguityGatesTriggered.toString())} runs stopped to request clarification`
|
|
3875
|
-
);
|
|
3876
|
-
lines.push(
|
|
3877
|
-
` \u2022 Clarifying Questions Posed: ${pc9.bold(amb.totalQuestionsAsked.toString())} targeted questions`
|
|
3878
|
-
);
|
|
3879
|
-
lines.push(
|
|
3880
|
-
` \u2022 Needs-Info Labels Applied: ${pc9.bold(amb.needsInfoAppliedCount.toString())}`
|
|
3881
|
-
);
|
|
3882
|
-
lines.push(
|
|
3883
|
-
` \u2022 Est. Wasted Tokens Averted: ~${pc9.bold(pc9.green(formatTokens2(amb.estimatedTokensSaved)))} tokens (avoided speculative builds)`
|
|
3884
|
-
);
|
|
3885
|
-
const failKeys = Object.keys(telemetry.failureCategories);
|
|
3886
|
-
if (failKeys.length > 0) {
|
|
3887
|
-
lines.push("\n" + pc9.bold(pc9.red("\u26A0\uFE0F Failure Categories Breakdown:")));
|
|
3888
|
-
for (const cat of failKeys) {
|
|
3889
|
-
lines.push(` - ${cat}: ${telemetry.failureCategories[cat]} occurrences`);
|
|
4044
|
+
} catch (err) {
|
|
4045
|
+
console.error(pc9.red(`\u274C Error during contribution preparation: ${err.message}`));
|
|
4046
|
+
if (options.throwOnError) {
|
|
4047
|
+
throw err;
|
|
3890
4048
|
}
|
|
4049
|
+
return {
|
|
4050
|
+
success: false,
|
|
4051
|
+
error: err.message
|
|
4052
|
+
};
|
|
3891
4053
|
}
|
|
3892
|
-
lines.push("\n" + pc9.gray("\u2500".repeat(65)) + "\n");
|
|
3893
|
-
return lines.join("\n");
|
|
3894
4054
|
}
|
|
3895
4055
|
|
|
3896
4056
|
// src/commands/telemetry.ts
|
|
4057
|
+
import fs11 from "fs";
|
|
4058
|
+
import path11 from "path";
|
|
4059
|
+
import pc10 from "picocolors";
|
|
3897
4060
|
async function runTelemetry(options = {}) {
|
|
3898
4061
|
const cwd = options.cwd || process.cwd();
|
|
3899
4062
|
const executor = options.executor || defaultGhExecutor;
|
|
@@ -4932,6 +5095,29 @@ function formatFallbackRunReport(options) {
|
|
|
4932
5095
|
);
|
|
4933
5096
|
}
|
|
4934
5097
|
}
|
|
5098
|
+
const total = options.usage?.totalTokens;
|
|
5099
|
+
if (typeof total === "number") {
|
|
5100
|
+
const quota = calculateTokenQuotaPercentages(total);
|
|
5101
|
+
const breakdown = formatTokenBreakdown(options.usage, "suffix");
|
|
5102
|
+
const k5h = (quota.window5hLimit / 1e6).toFixed(1) + "M";
|
|
5103
|
+
const kWeekly = (quota.weeklyLimit / 1e6).toFixed(2) + "M";
|
|
5104
|
+
reportContent += `| Run Tokens | \`${total.toLocaleString()}\`${breakdown ? ` (\`${breakdown}\`)` : ""} |
|
|
5105
|
+
`;
|
|
5106
|
+
reportContent += `| Run % of Limits | \`${quota.pctOf5hLimit.toFixed(2)}%\` of 5h (\`${k5h}\`) \xB7 \`${quota.pctOfWeeklyLimit.toFixed(2)}%\` of weekly (\`${kWeekly}\`) |
|
|
5107
|
+
`;
|
|
5108
|
+
if (options.repoRoot) {
|
|
5109
|
+
try {
|
|
5110
|
+
const rolling = getRollingWindowTokenUsage(options.repoRoot);
|
|
5111
|
+
if (rolling.windowTokens > 0 || rolling.weeklyTokens > 0) {
|
|
5112
|
+
reportContent += `| 5h Window Usage | \`${rolling.windowTokens.toLocaleString()} / ${rolling.windowLimit.toLocaleString()}\` tokens (\`${rolling.windowPercentage.toFixed(1)}%\` \xB7 \`[${rolling.windowStatus}]\`) |
|
|
5113
|
+
`;
|
|
5114
|
+
reportContent += `| Weekly Spend | \`${rolling.weeklyTokens.toLocaleString()} / ${rolling.weeklyLimit.toLocaleString()}\` tokens (\`${rolling.weeklyPercentage.toFixed(1)}%\` \xB7 \`[${rolling.weeklyStatus}]\`) |
|
|
5115
|
+
`;
|
|
5116
|
+
}
|
|
5117
|
+
} catch {
|
|
5118
|
+
}
|
|
5119
|
+
}
|
|
5120
|
+
}
|
|
4935
5121
|
if (options.exitCode !== 0) {
|
|
4936
5122
|
const errorChunks = [options.output?.trim(), options.stderr?.trim()].filter(Boolean);
|
|
4937
5123
|
const combinedError = errorChunks.join("\n");
|
|
@@ -5156,6 +5342,7 @@ Timeout: ${printTimeout}`,
|
|
|
5156
5342
|
}
|
|
5157
5343
|
});
|
|
5158
5344
|
let executedAnyTool = false;
|
|
5345
|
+
let sessionUsage;
|
|
5159
5346
|
const stdoutParser = new LineBufferedStreamParser((line) => {
|
|
5160
5347
|
const event = parseStreamJsonEvent(line);
|
|
5161
5348
|
if (event) {
|
|
@@ -5230,6 +5417,14 @@ Timeout: ${printTimeout}`,
|
|
|
5230
5417
|
finalResponseText = event.result.response;
|
|
5231
5418
|
checkTargetDetection(event.result.response);
|
|
5232
5419
|
}
|
|
5420
|
+
if (event.result.usage) {
|
|
5421
|
+
sessionUsage = {
|
|
5422
|
+
inputTokens: event.result.usage.input_tokens,
|
|
5423
|
+
outputTokens: event.result.usage.output_tokens,
|
|
5424
|
+
thinkingTokens: event.result.usage.thinking_tokens,
|
|
5425
|
+
totalTokens: event.result.usage.total_tokens
|
|
5426
|
+
};
|
|
5427
|
+
}
|
|
5233
5428
|
if (options.verbose) {
|
|
5234
5429
|
const formatted = formatVerboseEvent(event);
|
|
5235
5430
|
if (formatted) console.log(formatted);
|
|
@@ -5386,7 +5581,9 @@ Timeout: ${printTimeout}`,
|
|
|
5386
5581
|
targetLabel,
|
|
5387
5582
|
durationSec,
|
|
5388
5583
|
output,
|
|
5389
|
-
stderr: [accumulatedStderr, prematureError].filter(Boolean).join("\n")
|
|
5584
|
+
stderr: [accumulatedStderr, prematureError].filter(Boolean).join("\n"),
|
|
5585
|
+
usage: sessionUsage,
|
|
5586
|
+
repoRoot: targetDir
|
|
5390
5587
|
});
|
|
5391
5588
|
}
|
|
5392
5589
|
try {
|
|
@@ -5407,7 +5604,8 @@ Timeout: ${printTimeout}`,
|
|
|
5407
5604
|
success: exitCode === 0,
|
|
5408
5605
|
target: targetLabel,
|
|
5409
5606
|
issueNumber: routineIssueNumber,
|
|
5410
|
-
durationMs
|
|
5607
|
+
durationMs,
|
|
5608
|
+
usage: sessionUsage
|
|
5411
5609
|
},
|
|
5412
5610
|
null,
|
|
5413
5611
|
2
|
|
@@ -5434,7 +5632,8 @@ Timeout: ${printTimeout}`,
|
|
|
5434
5632
|
issue: effectiveIssue,
|
|
5435
5633
|
pr: effectivePR,
|
|
5436
5634
|
title: targetTitle,
|
|
5437
|
-
durationMs
|
|
5635
|
+
durationMs,
|
|
5636
|
+
usage: sessionUsage
|
|
5438
5637
|
}) + "\n"
|
|
5439
5638
|
);
|
|
5440
5639
|
} else {
|
|
@@ -5445,7 +5644,8 @@ Timeout: ${printTimeout}`,
|
|
|
5445
5644
|
repoRoot: targetDir,
|
|
5446
5645
|
issue: effectiveIssue,
|
|
5447
5646
|
pr: effectivePR,
|
|
5448
|
-
durationMs
|
|
5647
|
+
durationMs,
|
|
5648
|
+
usage: sessionUsage
|
|
5449
5649
|
}) + "\n"
|
|
5450
5650
|
);
|
|
5451
5651
|
}
|
|
@@ -5463,6 +5663,7 @@ Timeout: ${printTimeout}`,
|
|
|
5463
5663
|
quotaResetInfo: quotaInfo.resetInfo,
|
|
5464
5664
|
transientServiceError,
|
|
5465
5665
|
durationMs,
|
|
5666
|
+
usage: sessionUsage,
|
|
5466
5667
|
outcome: routine === "peer-review" && exitCode === 0 ? detectPeerReviewOutcome({
|
|
5467
5668
|
output,
|
|
5468
5669
|
prNumber: effectivePR,
|
|
@@ -5961,6 +6162,12 @@ function writeDaemonState(repoRoot, state) {
|
|
|
5961
6162
|
fs15.mkdirSync(path15.dirname(statePath), { recursive: true });
|
|
5962
6163
|
fs15.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
5963
6164
|
}
|
|
6165
|
+
function recordDaemonSessionUsage(repoRoot, state, usage) {
|
|
6166
|
+
if (usage?.totalTokens && state) {
|
|
6167
|
+
state.sessionTokens = (state.sessionTokens || 0) + usage.totalTokens;
|
|
6168
|
+
writeDaemonState(repoRoot, state);
|
|
6169
|
+
}
|
|
6170
|
+
}
|
|
5964
6171
|
function clearDaemonState(repoRoot) {
|
|
5965
6172
|
const statePath = getDaemonStatePath(repoRoot);
|
|
5966
6173
|
if (fs15.existsSync(statePath)) {
|
|
@@ -6562,6 +6769,7 @@ async function drainReviewQueue(drainOptions) {
|
|
|
6562
6769
|
attemptedPRNumbers.add(prNum);
|
|
6563
6770
|
onAttempted?.(prNum);
|
|
6564
6771
|
}
|
|
6772
|
+
recordDaemonSessionUsage(repoRoot, state, result.usage);
|
|
6565
6773
|
const isQuota = result.quotaPaused || !result.success && detectQuotaExceeded(result.output || "", result.stderr || "").isQuota;
|
|
6566
6774
|
if (isQuota) {
|
|
6567
6775
|
const resetInfo = result.quotaResetInfo || detectQuotaExceeded(result.output || "", result.stderr || "").resetInfo;
|
|
@@ -6735,6 +6943,7 @@ async function performAutoworkScan(scanOptions) {
|
|
|
6735
6943
|
}
|
|
6736
6944
|
}
|
|
6737
6945
|
});
|
|
6946
|
+
recordDaemonSessionUsage(repoRoot, state, result.usage);
|
|
6738
6947
|
const isQuota = result.quotaPaused || !result.success && detectQuotaExceeded(result.output || "", result.stderr || "").isQuota;
|
|
6739
6948
|
if (isQuota) {
|
|
6740
6949
|
const resetInfo = result.quotaResetInfo || detectQuotaExceeded(result.output || "", result.stderr || "").resetInfo;
|