jonah-fleet 1.31.6 → 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 +19 -2
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/index.js +1570 -1352
- 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/templates/prompts/ORCHESTRATION.md +1 -1
- package/templates/prompts/autowork.md +4 -4
- package/templates/prompts/peer-review.md +2 -2
- package/templates/workflows/trigger-review-routine.yml +11 -2
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,7 +2869,7 @@ 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(
|
|
@@ -2366,55 +2887,105 @@ function renderSummaryCard(options) {
|
|
|
2366
2887
|
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
2367
2888
|
lines.push(...bodyLines);
|
|
2368
2889
|
}
|
|
2890
|
+
if (typeof options.usage?.totalTokens === "number") {
|
|
2891
|
+
lines.push(
|
|
2892
|
+
...renderTokenQuotaSection({
|
|
2893
|
+
usage: options.usage,
|
|
2894
|
+
repoRoot: options.repoRoot,
|
|
2895
|
+
width,
|
|
2896
|
+
borderColor: border
|
|
2897
|
+
})
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2369
2900
|
if (parsedFromLog?.logPath) {
|
|
2370
2901
|
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
2371
|
-
const logInfo = ` Run log: ${
|
|
2902
|
+
const logInfo = ` Run log: ${pc2.dim(parsedFromLog.logPath)}`;
|
|
2372
2903
|
const logPlain = ` Run log: ${parsedFromLog.logPath}`;
|
|
2373
2904
|
lines.push(border("\u2502") + logInfo + " ".repeat(Math.max(1, width - 2 - logPlain.length)) + border("\u2502"));
|
|
2374
2905
|
}
|
|
2375
2906
|
lines.push(border(`\u2514${horizontal}\u2518`));
|
|
2376
2907
|
return lines.join("\n");
|
|
2377
2908
|
}
|
|
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);
|
|
2915
|
+
const lines = [];
|
|
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 {
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
return lines;
|
|
2938
|
+
}
|
|
2378
2939
|
function renderErrorCard(options) {
|
|
2379
2940
|
const width = Math.min(Math.max((process.stdout.columns || 80) - 4, 60), 86);
|
|
2380
2941
|
const horizontal = "\u2500".repeat(width - 2);
|
|
2381
2942
|
const lines = [];
|
|
2382
|
-
lines.push(
|
|
2943
|
+
lines.push(pc2.red(`\u250C${horizontal}\u2510`));
|
|
2383
2944
|
const target = options.pr ? ` \xB7 PR #${options.pr}` : options.issue ? ` \xB7 Issue #${options.issue}` : "";
|
|
2384
2945
|
const durationStr = options.durationMs ? ` (${Math.round(options.durationMs / 1e3)}s)` : "";
|
|
2385
2946
|
const routineUpper = options.routine.toUpperCase();
|
|
2386
2947
|
const header = ` \u2717 Routine '${routineUpper}' Failed (Exit Code ${options.exitCode})${target}${durationStr}`;
|
|
2387
2948
|
const headerPlain = ` \u2717 Routine '${routineUpper}' Failed (Exit Code ${options.exitCode})${target}${durationStr}`;
|
|
2388
2949
|
lines.push(
|
|
2389
|
-
|
|
2950
|
+
pc2.red("\u2502") + pc2.bold(pc2.red(header.slice(0, width - 3))) + " ".repeat(Math.max(1, width - 2 - headerPlain.length)) + pc2.red("\u2502")
|
|
2390
2951
|
);
|
|
2391
|
-
lines.push(
|
|
2392
|
-
const logPath =
|
|
2393
|
-
lines.push(
|
|
2394
|
-
if (
|
|
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)) {
|
|
2395
2956
|
try {
|
|
2396
|
-
const logContent =
|
|
2957
|
+
const logContent = fs7.readFileSync(logPath, "utf8");
|
|
2397
2958
|
const allLines = logContent.split("\n").filter((l) => l.trim().length > 0);
|
|
2398
2959
|
const tailLines = allLines.slice(-10);
|
|
2399
2960
|
for (const line of tailLines) {
|
|
2400
2961
|
const cleaned = sanitizeWorktreePaths(line).trim();
|
|
2401
2962
|
const truncated = cleaned.length > width - 6 ? cleaned.slice(0, width - 9) + "..." : cleaned;
|
|
2402
|
-
lines.push(
|
|
2963
|
+
lines.push(pc2.red("\u2502") + pc2.dim(` ${truncated}`) + " ".repeat(Math.max(1, width - 4 - truncated.length)) + pc2.red("\u2502"));
|
|
2403
2964
|
}
|
|
2404
2965
|
} catch {
|
|
2405
2966
|
lines.push(
|
|
2406
|
-
|
|
2967
|
+
pc2.red("\u2502") + pc2.dim(" (Could not read .jonah-fleet/daemon.log)") + " ".repeat(Math.max(1, width - 45)) + pc2.red("\u2502")
|
|
2407
2968
|
);
|
|
2408
2969
|
}
|
|
2409
2970
|
} else {
|
|
2410
|
-
lines.push(
|
|
2971
|
+
lines.push(pc2.red("\u2502") + pc2.dim(" (No daemon.log found)") + " ".repeat(Math.max(1, width - 26)) + pc2.red("\u2502"));
|
|
2972
|
+
}
|
|
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
|
+
);
|
|
2411
2982
|
}
|
|
2412
|
-
lines.push(
|
|
2413
|
-
const relLogPath =
|
|
2983
|
+
lines.push(pc2.red(`\u251C${horizontal}\u2524`));
|
|
2984
|
+
const relLogPath = path7.relative(options.repoRoot, logPath) || ".jonah-fleet/daemon.log";
|
|
2414
2985
|
const footer = ` Full trace: ${relLogPath}`;
|
|
2415
2986
|
const truncatedFooter = footer.slice(0, width - 4);
|
|
2416
|
-
lines.push(
|
|
2417
|
-
lines.push(
|
|
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`));
|
|
2418
2989
|
return lines.join("\n");
|
|
2419
2990
|
}
|
|
2420
2991
|
var TerminalSpinner = class {
|
|
@@ -2455,12 +3026,12 @@ var TerminalSpinner = class {
|
|
|
2455
3026
|
}
|
|
2456
3027
|
formatLine(message, maxWidth) {
|
|
2457
3028
|
const cols = maxWidth ?? (process.stderr.columns || process.stdout.columns || 80);
|
|
2458
|
-
const frame =
|
|
3029
|
+
const frame = pc2.cyan(this.frames[this.currentFrame]);
|
|
2459
3030
|
const elapsedSeconds = Math.floor((Date.now() - this.startTime) / 1e3);
|
|
2460
3031
|
const mins = Math.floor(elapsedSeconds / 60);
|
|
2461
3032
|
const secs = elapsedSeconds % 60;
|
|
2462
3033
|
const timePlain = `[${mins}m ${secs < 10 ? "0" : ""}${secs}s]`;
|
|
2463
|
-
const timeStr =
|
|
3034
|
+
const timeStr = pc2.dim(timePlain);
|
|
2464
3035
|
const fixedWidth = 6 + timePlain.length;
|
|
2465
3036
|
const availableMsgWidth = Math.max(0, cols - fixedWidth - 1);
|
|
2466
3037
|
let truncatedMsg = message;
|
|
@@ -2490,15 +3061,15 @@ var TerminalSpinner = class {
|
|
|
2490
3061
|
function renderBacklogDiagnosticCard(report, options) {
|
|
2491
3062
|
const width = options?.width || Math.min(Math.max((process.stdout.columns || 80) - 4, 64), 90);
|
|
2492
3063
|
const horizontal = "\u2500".repeat(width - 2);
|
|
2493
|
-
const border = (s) =>
|
|
3064
|
+
const border = (s) => pc2.dim(pc2.gray(s));
|
|
2494
3065
|
const lines = [];
|
|
2495
3066
|
lines.push(border(`\u250C${horizontal}\u2510`));
|
|
2496
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);
|
|
2497
3068
|
const actionableCount = report.actionable?.length || 0;
|
|
2498
3069
|
const headerParts = [
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
3070
|
+
pc2.bold(pc2.white("AUTOWORK BACKLOG TRIAGE")),
|
|
3071
|
+
pc2.yellow(`${actionableCount} Actionable`),
|
|
3072
|
+
pc2.dim("(0 tokens used)")
|
|
2502
3073
|
];
|
|
2503
3074
|
const headerContent = headerParts.join(" \xB7 ");
|
|
2504
3075
|
const headerPlain = stripAnsi(headerContent);
|
|
@@ -2510,23 +3081,23 @@ function renderBacklogDiagnosticCard(report, options) {
|
|
|
2510
3081
|
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
2511
3082
|
const emptyMsg = "Backlog is completely empty. 0 open issues found.";
|
|
2512
3083
|
lines.push(
|
|
2513
|
-
border("\u2502") + ` ${
|
|
3084
|
+
border("\u2502") + ` ${pc2.bold(emptyMsg)}` + " ".repeat(Math.max(1, width - 4 - stripAnsi(emptyMsg).length)) + border("\u2502")
|
|
2514
3085
|
);
|
|
2515
3086
|
const tipMsg = "Tip: Create an issue with priority/P1 or priority/P2 to trigger Autowork.";
|
|
2516
3087
|
lines.push(
|
|
2517
|
-
border("\u2502") + ` ${
|
|
3088
|
+
border("\u2502") + ` ${pc2.dim(tipMsg)}` + " ".repeat(Math.max(1, width - 4 - stripAnsi(tipMsg).length)) + border("\u2502")
|
|
2518
3089
|
);
|
|
2519
3090
|
lines.push(border(`\u2514${horizontal}\u2518`));
|
|
2520
3091
|
return lines.join("\n");
|
|
2521
3092
|
}
|
|
2522
3093
|
const statusMsg = `Preflight: Zero-token bypass active (no worktrees spawned)`;
|
|
2523
3094
|
lines.push(
|
|
2524
|
-
border("\u2502") + ` ${
|
|
3095
|
+
border("\u2502") + ` ${pc2.dim(statusMsg)}` + " ".repeat(Math.max(1, width - 4 - stripAnsi(statusMsg).length)) + border("\u2502")
|
|
2525
3096
|
);
|
|
2526
3097
|
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
2527
3098
|
const renderSection = (title, colorFn, items, reasonLabel) => {
|
|
2528
3099
|
if (!items || items.length === 0) return;
|
|
2529
|
-
const sectionHeader = ` ${colorFn(
|
|
3100
|
+
const sectionHeader = ` ${colorFn(pc2.bold(title))}`;
|
|
2530
3101
|
lines.push(
|
|
2531
3102
|
border("\u2502") + sectionHeader + " ".repeat(Math.max(1, width - 2 - stripAnsi(sectionHeader).length)) + border("\u2502")
|
|
2532
3103
|
);
|
|
@@ -2547,7 +3118,7 @@ function renderBacklogDiagnosticCard(report, options) {
|
|
|
2547
3118
|
if (report.gatedHuman && report.gatedHuman.length > 0) {
|
|
2548
3119
|
renderSection(
|
|
2549
3120
|
"Gated by Human Decision (needs-human / needs-attention / radar):",
|
|
2550
|
-
|
|
3121
|
+
pc2.red,
|
|
2551
3122
|
report.gatedHuman,
|
|
2552
3123
|
(item) => {
|
|
2553
3124
|
const labels = (item.labels || []).map(
|
|
@@ -2562,25 +3133,25 @@ function renderBacklogDiagnosticCard(report, options) {
|
|
|
2562
3133
|
if (report.awaitingInfo && report.awaitingInfo.length > 0) {
|
|
2563
3134
|
renderSection(
|
|
2564
3135
|
"Awaiting Specifications (needs-info / needs-design):",
|
|
2565
|
-
|
|
3136
|
+
pc2.yellow,
|
|
2566
3137
|
report.awaitingInfo,
|
|
2567
3138
|
"Awaiting info: needs-info"
|
|
2568
3139
|
);
|
|
2569
3140
|
}
|
|
2570
3141
|
if (report.inProgress && report.inProgress.length > 0) {
|
|
2571
|
-
renderSection("Already In Progress:",
|
|
3142
|
+
renderSection("Already In Progress:", pc2.blue, report.inProgress, "In Progress");
|
|
2572
3143
|
}
|
|
2573
3144
|
if (report.guardrails && report.guardrails.length > 0) {
|
|
2574
|
-
renderSection("Metrics & Guardrails (measurement / wontfix):",
|
|
3145
|
+
renderSection("Metrics & Guardrails (measurement / wontfix):", pc2.magenta, report.guardrails, "Guardrail: measurement");
|
|
2575
3146
|
}
|
|
2576
3147
|
if (report.routineLogs && report.routineLogs.length > 0) {
|
|
2577
3148
|
const routineText = ` Operational Logs: ${report.routineLogs.length} routine run log(s) filtered`;
|
|
2578
3149
|
lines.push(
|
|
2579
|
-
border("\u2502") +
|
|
3150
|
+
border("\u2502") + pc2.dim(routineText) + " ".repeat(Math.max(1, width - 2 - stripAnsi(routineText).length)) + border("\u2502")
|
|
2580
3151
|
);
|
|
2581
3152
|
}
|
|
2582
3153
|
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
2583
|
-
const hintsHeader = ` ${
|
|
3154
|
+
const hintsHeader = ` ${pc2.bold("Hints to unblock:")}`;
|
|
2584
3155
|
lines.push(
|
|
2585
3156
|
border("\u2502") + hintsHeader + " ".repeat(Math.max(1, width - 2 - stripAnsi(hintsHeader).length)) + border("\u2502")
|
|
2586
3157
|
);
|
|
@@ -2603,7 +3174,7 @@ function renderBacklogDiagnosticCard(report, options) {
|
|
|
2603
3174
|
for (const wLine of wrapped) {
|
|
2604
3175
|
const wPlain = stripAnsi(wLine);
|
|
2605
3176
|
lines.push(
|
|
2606
|
-
border("\u2502") + ` ${
|
|
3177
|
+
border("\u2502") + ` ${pc2.dim(wLine)}` + " ".repeat(Math.max(1, width - 5 - wPlain.length)) + border("\u2502")
|
|
2607
3178
|
);
|
|
2608
3179
|
}
|
|
2609
3180
|
}
|
|
@@ -2640,18 +3211,18 @@ function renderFleetBanner(options) {
|
|
|
2640
3211
|
const width = options.width || Math.min(Math.max((process.stdout.columns || 80) - 4, 60), 80);
|
|
2641
3212
|
const borderChar = "\u2500";
|
|
2642
3213
|
const horizontal = borderChar.repeat(Math.max(10, width - 2));
|
|
2643
|
-
const border = (s) =>
|
|
2644
|
-
const emblemLine = (s) =>
|
|
3214
|
+
const border = (s) => pc3.dim(pc3.gray(s));
|
|
3215
|
+
const emblemLine = (s) => pc3.bold(pc3.green(s));
|
|
2645
3216
|
const lines = [];
|
|
2646
3217
|
lines.push(border(`\u250C${horizontal}\u2510`));
|
|
2647
3218
|
if (width >= 72) {
|
|
2648
3219
|
const telemetry = [
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
options.subtitle ?
|
|
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")
|
|
2655
3226
|
];
|
|
2656
3227
|
for (let i = 0; i < FLEET_ASCII_EMBLEM.length; i++) {
|
|
2657
3228
|
const art = FLEET_ASCII_EMBLEM[i];
|
|
@@ -2665,22 +3236,22 @@ function renderFleetBanner(options) {
|
|
|
2665
3236
|
const header = ` \u26A1 FLEET // COMMAND \xB7 ${options.command.toUpperCase()}`;
|
|
2666
3237
|
const headerPlain = stripAnsi(header);
|
|
2667
3238
|
lines.push(
|
|
2668
|
-
`${border("\u2502")}${
|
|
3239
|
+
`${border("\u2502")}${pc3.bold(pc3.cyan(header))}${" ".repeat(Math.max(1, width - 2 - headerPlain.length))}${border("\u2502")}`
|
|
2669
3240
|
);
|
|
2670
3241
|
const sub = ` SYS: READY \xB7 v${FLEET_VERSION}${options.subtitle ? ` \xB7 ${options.subtitle}` : ""}`;
|
|
2671
3242
|
const subPlain = stripAnsi(sub);
|
|
2672
3243
|
const truncatedSub = subPlain.length > width - 4 ? sub.slice(0, width - 7) + "..." : sub;
|
|
2673
3244
|
const finalSubPlain = stripAnsi(truncatedSub);
|
|
2674
3245
|
lines.push(
|
|
2675
|
-
`${border("\u2502")}${
|
|
3246
|
+
`${border("\u2502")}${pc3.dim(pc3.gray(truncatedSub))}${" ".repeat(Math.max(1, width - 2 - finalSubPlain.length))}${border("\u2502")}`
|
|
2676
3247
|
);
|
|
2677
3248
|
}
|
|
2678
3249
|
if (options.details && options.details.length > 0) {
|
|
2679
3250
|
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
2680
3251
|
for (const detail of options.details) {
|
|
2681
|
-
const row = ` \u2022 ${
|
|
3252
|
+
const row = ` \u2022 ${pc3.dim(pc3.gray(detail.label.toUpperCase() + ":"))} ${pc3.white(detail.value)}`;
|
|
2682
3253
|
const rowPlain = stripAnsi(row);
|
|
2683
|
-
const truncatedRow = rowPlain.length > width - 4 ? ` \u2022 ${
|
|
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;
|
|
2684
3255
|
const finalPlain = stripAnsi(truncatedRow);
|
|
2685
3256
|
lines.push(
|
|
2686
3257
|
`${border("\u2502")}${truncatedRow}${" ".repeat(Math.max(1, width - 2 - finalPlain.length))}${border("\u2502")}`
|
|
@@ -2703,1197 +3274,789 @@ async function promptQuestion(query, defaultValue) {
|
|
|
2703
3274
|
resolve(answer.trim() || defaultValue);
|
|
2704
3275
|
});
|
|
2705
3276
|
});
|
|
2706
|
-
}
|
|
2707
|
-
async function runInit(options = {}) {
|
|
2708
|
-
const cwd = options.cwd || process.cwd();
|
|
2709
|
-
const preset = options.preset || "standard";
|
|
2710
|
-
console.log(
|
|
2711
|
-
renderFleetBanner({
|
|
2712
|
-
command: "INIT",
|
|
2713
|
-
subtitle: `INITIALIZING FLEET (preset: ${preset})`,
|
|
2714
|
-
details: [
|
|
2715
|
-
{ label: "Preset", value: preset },
|
|
2716
|
-
{ label: "Target", value: cwd }
|
|
2717
|
-
]
|
|
2718
|
-
})
|
|
2719
|
-
);
|
|
2720
|
-
console.log(pc3.cyan(`
|
|
2721
|
-
\u2693 Initializing Jonah Fleet (preset: ${pc3.bold(preset)}) in ${cwd}
|
|
2722
|
-
`));
|
|
2723
|
-
let detected = detectTechStack(cwd);
|
|
2724
|
-
console.log(pc3.bold("\u{1F50D} Tech Stack Auto-Detection:"));
|
|
2725
|
-
console.log(pc3.cyan(` - Detected Stack: ${pc3.bold(detected.name)}`));
|
|
2726
|
-
console.log(pc3.cyan(` - Language: ${detected.language}`));
|
|
2727
|
-
if (detected.framework) {
|
|
2728
|
-
console.log(pc3.cyan(` - Framework: ${detected.framework}`));
|
|
2729
|
-
}
|
|
2730
|
-
console.log(pc3.cyan(` - Package Manager: ${detected.packageManager}`));
|
|
2731
|
-
if (detected.testFramework) {
|
|
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
|
|
2754
|
-
}
|
|
2755
|
-
};
|
|
2756
|
-
}
|
|
2757
|
-
if (options.stack) {
|
|
2758
|
-
detected.name = options.stack;
|
|
2759
|
-
detected.language = options.stack;
|
|
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}'...`));
|
|
2775
|
-
} else {
|
|
2776
|
-
manifest = createDefaultManifest(preset);
|
|
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}`));
|
|
2785
|
-
}
|
|
2786
|
-
if (result.workflowsInstalled.length > 0) {
|
|
2787
|
-
console.log(pc3.green(` \u2699\uFE0F Workflows (.github/workflows/):`));
|
|
2788
|
-
result.workflowsInstalled.forEach((w) => console.log(` - ${w}`));
|
|
2789
|
-
}
|
|
2790
|
-
if (result.skillsInstalled.length > 0) {
|
|
2791
|
-
console.log(pc3.green(` \u{1F9E0} Skills (.agents/skills/):`));
|
|
2792
|
-
result.skillsInstalled.forEach((s) => console.log(` - ${s}`));
|
|
2793
|
-
}
|
|
2794
|
-
if (result.docsInstalled.length > 0) {
|
|
2795
|
-
console.log(pc3.green(` \u{1F4C4} Documentation:`));
|
|
2796
|
-
result.docsInstalled.forEach((d) => console.log(` - ${d}`));
|
|
2797
|
-
}
|
|
2798
|
-
if (options.pruneLabels) {
|
|
2799
|
-
try {
|
|
2800
|
-
console.log(pc3.bold("\n\u{1F3F7}\uFE0F Pruning unused boilerplate labels..."));
|
|
2801
|
-
const pruneRes = await pruneLabels({ cwd, yes: true, dryRun: false, executor: options.executor });
|
|
2802
|
-
if (pruneRes.pruned.length > 0) {
|
|
2803
|
-
console.log(pc3.green(` \u2713 Pruned ${pruneRes.pruned.length} unused boilerplate label(s): ${pruneRes.pruned.join(", ")}`));
|
|
2804
|
-
} else {
|
|
2805
|
-
console.log(pc3.green(" \u2713 No unused boilerplate labels found."));
|
|
2806
|
-
}
|
|
2807
|
-
} catch (err) {
|
|
2808
|
-
console.log(pc3.yellow(` \u26A0\uFE0F Could not prune labels: ${err.message}`));
|
|
2809
|
-
}
|
|
2810
|
-
}
|
|
2811
|
-
console.log(pc3.bold(pc3.green("\n\u{1F389} Jonah Fleet initialization complete!\n")));
|
|
2812
|
-
console.log(pc3.cyan("Next steps for GitHub repository configuration:"));
|
|
2813
|
-
console.log(" 1. In Settings \u2192 Actions \u2192 General \u2192 Workflow permissions:");
|
|
2814
|
-
console.log(' Select "Read and write permissions" and check "Allow GitHub Actions to create and approve pull requests".');
|
|
2815
|
-
console.log(" 2. In Settings \u2192 Actions \u2192 General \u2192 Fork pull request workflows:");
|
|
2816
|
-
console.log(" Configure workflow approval settings to prevent automated runs from stalling awaiting approval.");
|
|
2817
|
-
console.log(" 3. Customize project context, build, and test commands in AGENTS.md.\n");
|
|
2818
|
-
}
|
|
2819
|
-
|
|
2820
|
-
// src/commands/sync.ts
|
|
2821
|
-
import pc4 from "picocolors";
|
|
2822
|
-
|
|
2823
|
-
// src/lib/diff.ts
|
|
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);
|
|
2846
|
-
}
|
|
2847
|
-
}
|
|
2848
|
-
for (const [routineName, isEnabled] of Object.entries(manifest.routines)) {
|
|
2849
|
-
if (!isEnabled) continue;
|
|
2850
|
-
const promptFile = `${routineName}.md`;
|
|
2851
|
-
const promptSrc = path7.join(templatesDir, "prompts", promptFile);
|
|
2852
|
-
const promptDest = path7.join(targetPromptsDir, promptFile);
|
|
2853
|
-
if (!fs7.existsSync(promptDest)) {
|
|
2854
|
-
report.missingPrompts.push(promptFile);
|
|
2855
|
-
} else if (fs7.existsSync(promptSrc) && fs7.readFileSync(promptSrc, "utf8") !== fs7.readFileSync(promptDest, "utf8")) {
|
|
2856
|
-
report.modifiedPrompts.push(promptFile);
|
|
2857
|
-
}
|
|
2858
|
-
const workflows = ROUTINE_TO_WORKFLOW_MAP[routineName] || [];
|
|
2859
|
-
for (const workflowFile of workflows) {
|
|
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
|
-
}
|
|
2873
|
-
}
|
|
2874
|
-
}
|
|
2875
|
-
if (manifest.autoUpdate?.enabled) {
|
|
2876
|
-
const syncWfSrc = path7.join(templatesDir, "workflows/sync-fleet.yml");
|
|
2877
|
-
const syncWfDest = path7.join(targetWorkflowsDir, "sync-fleet.yml");
|
|
2878
|
-
if (!fs7.existsSync(syncWfDest)) {
|
|
2879
|
-
report.missingWorkflows.push("sync-fleet.yml");
|
|
2880
|
-
} else if (fs7.existsSync(syncWfSrc)) {
|
|
2881
|
-
const rawSrc = fs7.readFileSync(syncWfSrc, "utf8");
|
|
2882
|
-
const destContent = fs7.readFileSync(syncWfDest, "utf8");
|
|
2883
|
-
const schedule = resolveWorkflowSchedule("sync-fleet.yml", "sync-fleet", manifest, destContent);
|
|
2884
|
-
const expectedSrc = applyWorkflowSchedule(rawSrc, schedule);
|
|
2885
|
-
if (expectedSrc !== destContent) {
|
|
2886
|
-
report.modifiedWorkflows.push("sync-fleet.yml");
|
|
2887
|
-
}
|
|
2888
|
-
}
|
|
2889
|
-
}
|
|
2890
|
-
for (const skill of manifest.skills) {
|
|
2891
|
-
const skillDestDir = path7.join(targetSkillsDir, skill);
|
|
2892
|
-
if (!fs7.existsSync(skillDestDir)) {
|
|
2893
|
-
report.missingSkills.push(skill);
|
|
2894
|
-
}
|
|
2895
|
-
}
|
|
2896
|
-
return report;
|
|
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);
|
|
2906
|
-
}
|
|
3277
|
+
}
|
|
3278
|
+
async function runInit(options = {}) {
|
|
3279
|
+
const cwd = options.cwd || process.cwd();
|
|
3280
|
+
const preset = options.preset || "standard";
|
|
2907
3281
|
console.log(
|
|
2908
3282
|
renderFleetBanner({
|
|
2909
|
-
command: "
|
|
2910
|
-
subtitle: `
|
|
3283
|
+
command: "INIT",
|
|
3284
|
+
subtitle: `INITIALIZING FLEET (preset: ${preset})`,
|
|
2911
3285
|
details: [
|
|
2912
|
-
{ label: "
|
|
2913
|
-
{ label: "
|
|
2914
|
-
{
|
|
2915
|
-
label: "Mode",
|
|
2916
|
-
value: options.check ? "Check Drift" : options.force ? "Force Overwrite" : "Standard Sync"
|
|
2917
|
-
}
|
|
3286
|
+
{ label: "Preset", value: preset },
|
|
3287
|
+
{ label: "Target", value: cwd }
|
|
2918
3288
|
]
|
|
2919
3289
|
})
|
|
2920
3290
|
);
|
|
2921
3291
|
console.log(pc4.cyan(`
|
|
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.
|
|
3292
|
+
\u2693 Initializing Jonah Fleet (preset: ${pc4.bold(preset)}) in ${cwd}
|
|
2940
3293
|
`));
|
|
2941
|
-
|
|
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}`));
|
|
2942
3300
|
}
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
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 {
|
|
3301
|
+
console.log(pc4.cyan(` - Package Manager: ${detected.packageManager}`));
|
|
3302
|
+
if (detected.testFramework) {
|
|
3303
|
+
console.log(pc4.cyan(` - Testing: ${detected.testFramework}`));
|
|
2954
3304
|
}
|
|
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`;
|
|
3305
|
+
if (detected.commands.test) {
|
|
3306
|
+
console.log(pc4.cyan(` - Test Command: ${detected.commands.test}`));
|
|
2968
3307
|
}
|
|
2969
|
-
|
|
2970
|
-
|
|
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
|
+
};
|
|
2971
3327
|
}
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
}
|
|
2977
|
-
function renderFleetDashboard(statuses, options = {}) {
|
|
2978
|
-
const summary = summarizeFleet(statuses);
|
|
2979
|
-
if (options.json) {
|
|
2980
|
-
return JSON.stringify(
|
|
2981
|
-
{
|
|
2982
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2983
|
-
summary,
|
|
2984
|
-
repositories: statuses
|
|
2985
|
-
},
|
|
2986
|
-
null,
|
|
2987
|
-
2
|
|
2988
|
-
);
|
|
3328
|
+
if (options.stack) {
|
|
3329
|
+
detected.name = options.stack;
|
|
3330
|
+
detected.language = options.stack;
|
|
3331
|
+
detected.framework = void 0;
|
|
2989
3332
|
}
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
if (statuses.length === 0) {
|
|
2993
|
-
lines.push(pc5.yellow(" No repositories configured in fleet registry."));
|
|
2994
|
-
lines.push(pc5.gray(" Use `jonah-fleet monitor --add <owner/repo>` to register repositories.\n"));
|
|
2995
|
-
return lines.join("\n");
|
|
3333
|
+
if (options.packageManager) {
|
|
3334
|
+
detected.packageManager = options.packageManager;
|
|
2996
3335
|
}
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
const presetStr = s.preset ? `preset: ${s.preset}` : "";
|
|
3000
|
-
const headerInfo = [versionStr, presetStr].filter(Boolean).join(", ");
|
|
3001
|
-
lines.push(pc5.bold(`\u{1F4E6} ${pc5.cyan(s.repo)} ${pc5.gray(`(${headerInfo})`)}`));
|
|
3002
|
-
if (s.error) {
|
|
3003
|
-
lines.push(pc5.red(` \u274C Error: ${s.error}`));
|
|
3004
|
-
}
|
|
3005
|
-
lines.push(pc5.bold(" \u{1F512} Active Claims:"));
|
|
3006
|
-
if (s.activeClaims.length === 0) {
|
|
3007
|
-
lines.push(pc5.gray(" None (idle)"));
|
|
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
|
-
}
|
|
3026
|
-
}
|
|
3027
|
-
lines.push(pc5.bold(" \u{1F4C8} 7-Day Token Spend:"));
|
|
3028
|
-
const t = s.tokenUsage;
|
|
3029
|
-
lines.push(
|
|
3030
|
-
` Runs: ${pc5.bold(t.recentRunCount.toString())} | Tokens: ${pc5.bold(formatTokens(t.sevenDayTotalTokens))} ` + pc5.gray(`(in: ${formatTokens(t.sevenDayInputTokens)}, out: ${formatTokens(t.sevenDayOutputTokens)})`) + ` | Cost: ${pc5.bold(pc5.green(formatCurrency(t.sevenDayEstimatedCost)))}`
|
|
3031
|
-
);
|
|
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("");
|
|
3336
|
+
if (options.testCmd) {
|
|
3337
|
+
detected.commands.test = options.testCmd;
|
|
3049
3338
|
}
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
lines.push(
|
|
3053
|
-
` Repositories: ${pc5.bold(summary.totalRepos.toString())} | Active Claims: ${pc5.bold(summary.activeClaimsCount.toString())} ` + (summary.staleClaimsCount > 0 ? pc5.red(`(${summary.staleClaimsCount} stale)`) : pc5.green("(0 stale)")) + ` | Open PRs: ${pc5.bold(summary.openPRsCount.toString())} ` + pc5.gray(`(${summary.draftPRsCount} draft, ${summary.readyPRsCount} ready)`)
|
|
3054
|
-
);
|
|
3055
|
-
const CEILING_70_PERCENT = 875e4;
|
|
3056
|
-
const ceilingPct = summary.totalTokens7d / CEILING_70_PERCENT * 100;
|
|
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` : "";
|
|
3072
|
-
lines.push(
|
|
3073
|
-
` \u2022 ${pc5.bold(r.routine)}: ${pc5.cyan(formatTokens(r.totalTokens))} tokens ` + pc5.gray(`(${r.fleetSharePercent.toFixed(1)}%)`) + ` | Cost: ${pc5.green(formatCurrency(r.estimatedCost))} | ${r.runCount} run${r.runCount === 1 ? "" : "s"}${pc5.gray(iterStr)}`
|
|
3074
|
-
);
|
|
3075
|
-
}
|
|
3339
|
+
if (options.buildCmd) {
|
|
3340
|
+
detected.commands.build = options.buildCmd;
|
|
3076
3341
|
}
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
// src/lib/global-config.ts
|
|
3085
|
-
import fs8 from "fs";
|
|
3086
|
-
import path8 from "path";
|
|
3087
|
-
import os from "os";
|
|
3088
|
-
function getDefaultGlobalConfigPath() {
|
|
3089
|
-
const baseDir = process.env.JONAH_FLEET_CONFIG_DIR || path8.join(os.homedir(), ".jonah-fleet");
|
|
3090
|
-
return path8.join(baseDir, "config.json");
|
|
3091
|
-
}
|
|
3092
|
-
function loadGlobalConfig(customPath) {
|
|
3093
|
-
const filePath = customPath || getDefaultGlobalConfigPath();
|
|
3094
|
-
if (!fs8.existsSync(filePath)) {
|
|
3095
|
-
return { repositories: [] };
|
|
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}'...`));
|
|
3346
|
+
} else {
|
|
3347
|
+
manifest = createDefaultManifest(preset);
|
|
3096
3348
|
}
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
};
|
|
3103
|
-
|
|
3104
|
-
return { repositories: [] };
|
|
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}`));
|
|
3105
3356
|
}
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
const dir = path8.dirname(filePath);
|
|
3110
|
-
if (!fs8.existsSync(dir)) {
|
|
3111
|
-
fs8.mkdirSync(dir, { recursive: true });
|
|
3357
|
+
if (result.workflowsInstalled.length > 0) {
|
|
3358
|
+
console.log(pc4.green(` \u2699\uFE0F Workflows (.github/workflows/):`));
|
|
3359
|
+
result.workflowsInstalled.forEach((w) => console.log(` - ${w}`));
|
|
3112
3360
|
}
|
|
3113
|
-
|
|
3114
|
-
}
|
|
3115
|
-
|
|
3116
|
-
const config = loadGlobalConfig(customPath);
|
|
3117
|
-
const normalized = repo.trim();
|
|
3118
|
-
if (!normalized) return config;
|
|
3119
|
-
if (!config.repositories.includes(normalized)) {
|
|
3120
|
-
config.repositories.push(normalized);
|
|
3121
|
-
saveGlobalConfig(config, customPath);
|
|
3361
|
+
if (result.skillsInstalled.length > 0) {
|
|
3362
|
+
console.log(pc4.green(` \u{1F9E0} Skills (.agents/skills/):`));
|
|
3363
|
+
result.skillsInstalled.forEach((s) => console.log(` - ${s}`));
|
|
3122
3364
|
}
|
|
3123
|
-
|
|
3124
|
-
}
|
|
3125
|
-
|
|
3126
|
-
const config = loadGlobalConfig(customPath);
|
|
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());
|
|
3365
|
+
if (result.docsInstalled.length > 0) {
|
|
3366
|
+
console.log(pc4.green(` \u{1F4C4} Documentation:`));
|
|
3367
|
+
result.docsInstalled.forEach((d) => console.log(` - ${d}`));
|
|
3141
3368
|
}
|
|
3142
|
-
|
|
3143
|
-
|
|
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
|
+
}
|
|
3144
3381
|
}
|
|
3145
|
-
|
|
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");
|
|
3146
3389
|
}
|
|
3147
3390
|
|
|
3148
|
-
// src/commands/
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
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
|
-
|
|
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}`));
|
|
3328
3575
|
}
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
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
|
+
);
|
|
3335
3586
|
}
|
|
3336
3587
|
}
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
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}`));
|
|
3343
3596
|
}
|
|
3344
3597
|
}
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
console.log(` - ${pc7.cyan(repo)}`);
|
|
3350
|
-
}
|
|
3351
|
-
}
|
|
3352
|
-
if (tokenUsage && tokenUsage.recentRunCount > 0) {
|
|
3353
|
-
console.log(pc7.bold("\n \u{1F4C8} 7-Day Token Spend:"));
|
|
3354
|
-
console.log(
|
|
3355
|
-
` Runs: ${pc7.bold(tokenUsage.recentRunCount.toString())} | Tokens: ${pc7.bold(formatTokens(tokenUsage.sevenDayTotalTokens))} ` + pc7.gray(`(in: ${formatTokens(tokenUsage.sevenDayInputTokens)}, out: ${formatTokens(tokenUsage.sevenDayOutputTokens)})`) + ` | Cost: ${pc7.bold(pc7.green(formatCurrency(tokenUsage.sevenDayEstimatedCost)))}`
|
|
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());
|
|
3583
3712
|
}
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
utilizationPercentage,
|
|
3589
|
-
status,
|
|
3590
|
-
dailyBurnRate,
|
|
3591
|
-
projectedExhaustionDays
|
|
3592
|
-
};
|
|
3713
|
+
for (const r of globalRepos) {
|
|
3714
|
+
if (r && typeof r === "string" && r.trim()) set.add(r.trim());
|
|
3715
|
+
}
|
|
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
|
-
}
|
|
3751
|
-
return summaries;
|
|
3752
|
-
}
|
|
3753
|
-
async function collectRepoTelemetry(repoIdentifier, executor = defaultGhExecutor, options = {}) {
|
|
3754
|
-
const summaries = [];
|
|
3755
|
-
if (fs10.existsSync(repoIdentifier) && fs10.statSync(repoIdentifier).isDirectory()) {
|
|
3756
|
-
return collectLocalTelemetryLogs(repoIdentifier, repoIdentifier);
|
|
3757
3896
|
}
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
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;
|
|
@@ -4565,22 +4728,31 @@ function buildRoutinePrompt(targetDir, routine, options = {}) {
|
|
|
4565
4728
|
}
|
|
4566
4729
|
function tryCreateLocalRunIssue(cwd, routine, timestamp, targetLabel, hostname) {
|
|
4567
4730
|
try {
|
|
4568
|
-
const title = `[${routine}] run ${timestamp} (local)`;
|
|
4731
|
+
const title = targetLabel && targetLabel !== routine ? `[${routine}] run ${timestamp} (${targetLabel})` : `[${routine}] run ${timestamp} (local)`;
|
|
4732
|
+
const formattedTarget = targetLabel.includes("#") ? targetLabel : `\`${targetLabel}\``;
|
|
4569
4733
|
const body = `### Autonomous Routine Execution in Progress (runner:local)
|
|
4570
4734
|
- **Routine**: \`${routine}\`
|
|
4571
4735
|
- **Timestamp**: \`${timestamp}\`
|
|
4572
|
-
- **Target**:
|
|
4736
|
+
- **Target**: ${formattedTarget}
|
|
4573
4737
|
- **Host**: \`${hostname}\`
|
|
4574
4738
|
|
|
4575
4739
|
_Running via Jonah Fleet CLI runner._`;
|
|
4576
4740
|
const labels = `routine-log,routine:${routine},status:running,runner:local`;
|
|
4577
|
-
const
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4741
|
+
const tmpFile = path13.join(os2.tmpdir(), `jonah-fleet-run-issue-${Date.now()}.md`);
|
|
4742
|
+
fs13.writeFileSync(tmpFile, body, "utf8");
|
|
4743
|
+
try {
|
|
4744
|
+
const out = execSync2(`gh issue create --title ${JSON.stringify(title)} --body-file ${JSON.stringify(tmpFile)} --label ${JSON.stringify(labels)}`, {
|
|
4745
|
+
cwd,
|
|
4746
|
+
encoding: "utf8",
|
|
4747
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
4748
|
+
}).trim();
|
|
4749
|
+
const match = out.match(/(\d+)$/);
|
|
4750
|
+
return match ? parseInt(match[1], 10) : void 0;
|
|
4751
|
+
} finally {
|
|
4752
|
+
if (fs13.existsSync(tmpFile)) {
|
|
4753
|
+
fs13.unlinkSync(tmpFile);
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4584
4756
|
} catch {
|
|
4585
4757
|
return void 0;
|
|
4586
4758
|
}
|
|
@@ -4923,6 +5095,29 @@ function formatFallbackRunReport(options) {
|
|
|
4923
5095
|
);
|
|
4924
5096
|
}
|
|
4925
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
|
+
}
|
|
4926
5121
|
if (options.exitCode !== 0) {
|
|
4927
5122
|
const errorChunks = [options.output?.trim(), options.stderr?.trim()].filter(Boolean);
|
|
4928
5123
|
const combinedError = errorChunks.join("\n");
|
|
@@ -5147,6 +5342,7 @@ Timeout: ${printTimeout}`,
|
|
|
5147
5342
|
}
|
|
5148
5343
|
});
|
|
5149
5344
|
let executedAnyTool = false;
|
|
5345
|
+
let sessionUsage;
|
|
5150
5346
|
const stdoutParser = new LineBufferedStreamParser((line) => {
|
|
5151
5347
|
const event = parseStreamJsonEvent(line);
|
|
5152
5348
|
if (event) {
|
|
@@ -5221,6 +5417,14 @@ Timeout: ${printTimeout}`,
|
|
|
5221
5417
|
finalResponseText = event.result.response;
|
|
5222
5418
|
checkTargetDetection(event.result.response);
|
|
5223
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
|
+
}
|
|
5224
5428
|
if (options.verbose) {
|
|
5225
5429
|
const formatted = formatVerboseEvent(event);
|
|
5226
5430
|
if (formatted) console.log(formatted);
|
|
@@ -5377,7 +5581,9 @@ Timeout: ${printTimeout}`,
|
|
|
5377
5581
|
targetLabel,
|
|
5378
5582
|
durationSec,
|
|
5379
5583
|
output,
|
|
5380
|
-
stderr: [accumulatedStderr, prematureError].filter(Boolean).join("\n")
|
|
5584
|
+
stderr: [accumulatedStderr, prematureError].filter(Boolean).join("\n"),
|
|
5585
|
+
usage: sessionUsage,
|
|
5586
|
+
repoRoot: targetDir
|
|
5381
5587
|
});
|
|
5382
5588
|
}
|
|
5383
5589
|
try {
|
|
@@ -5398,7 +5604,8 @@ Timeout: ${printTimeout}`,
|
|
|
5398
5604
|
success: exitCode === 0,
|
|
5399
5605
|
target: targetLabel,
|
|
5400
5606
|
issueNumber: routineIssueNumber,
|
|
5401
|
-
durationMs
|
|
5607
|
+
durationMs,
|
|
5608
|
+
usage: sessionUsage
|
|
5402
5609
|
},
|
|
5403
5610
|
null,
|
|
5404
5611
|
2
|
|
@@ -5425,7 +5632,8 @@ Timeout: ${printTimeout}`,
|
|
|
5425
5632
|
issue: effectiveIssue,
|
|
5426
5633
|
pr: effectivePR,
|
|
5427
5634
|
title: targetTitle,
|
|
5428
|
-
durationMs
|
|
5635
|
+
durationMs,
|
|
5636
|
+
usage: sessionUsage
|
|
5429
5637
|
}) + "\n"
|
|
5430
5638
|
);
|
|
5431
5639
|
} else {
|
|
@@ -5436,7 +5644,8 @@ Timeout: ${printTimeout}`,
|
|
|
5436
5644
|
repoRoot: targetDir,
|
|
5437
5645
|
issue: effectiveIssue,
|
|
5438
5646
|
pr: effectivePR,
|
|
5439
|
-
durationMs
|
|
5647
|
+
durationMs,
|
|
5648
|
+
usage: sessionUsage
|
|
5440
5649
|
}) + "\n"
|
|
5441
5650
|
);
|
|
5442
5651
|
}
|
|
@@ -5454,6 +5663,7 @@ Timeout: ${printTimeout}`,
|
|
|
5454
5663
|
quotaResetInfo: quotaInfo.resetInfo,
|
|
5455
5664
|
transientServiceError,
|
|
5456
5665
|
durationMs,
|
|
5666
|
+
usage: sessionUsage,
|
|
5457
5667
|
outcome: routine === "peer-review" && exitCode === 0 ? detectPeerReviewOutcome({
|
|
5458
5668
|
output,
|
|
5459
5669
|
prNumber: effectivePR,
|
|
@@ -5952,6 +6162,12 @@ function writeDaemonState(repoRoot, state) {
|
|
|
5952
6162
|
fs15.mkdirSync(path15.dirname(statePath), { recursive: true });
|
|
5953
6163
|
fs15.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
5954
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
|
+
}
|
|
5955
6171
|
function clearDaemonState(repoRoot) {
|
|
5956
6172
|
const statePath = getDaemonStatePath(repoRoot);
|
|
5957
6173
|
if (fs15.existsSync(statePath)) {
|
|
@@ -6553,6 +6769,7 @@ async function drainReviewQueue(drainOptions) {
|
|
|
6553
6769
|
attemptedPRNumbers.add(prNum);
|
|
6554
6770
|
onAttempted?.(prNum);
|
|
6555
6771
|
}
|
|
6772
|
+
recordDaemonSessionUsage(repoRoot, state, result.usage);
|
|
6556
6773
|
const isQuota = result.quotaPaused || !result.success && detectQuotaExceeded(result.output || "", result.stderr || "").isQuota;
|
|
6557
6774
|
if (isQuota) {
|
|
6558
6775
|
const resetInfo = result.quotaResetInfo || detectQuotaExceeded(result.output || "", result.stderr || "").resetInfo;
|
|
@@ -6726,6 +6943,7 @@ async function performAutoworkScan(scanOptions) {
|
|
|
6726
6943
|
}
|
|
6727
6944
|
}
|
|
6728
6945
|
});
|
|
6946
|
+
recordDaemonSessionUsage(repoRoot, state, result.usage);
|
|
6729
6947
|
const isQuota = result.quotaPaused || !result.success && detectQuotaExceeded(result.output || "", result.stderr || "").isQuota;
|
|
6730
6948
|
if (isQuota) {
|
|
6731
6949
|
const resetInfo = result.quotaResetInfo || detectQuotaExceeded(result.output || "", result.stderr || "").resetInfo;
|