jonah-fleet 1.32.0 → 1.34.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 +17 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/index.js +645 -82
- package/dist/lib/runner.d.ts +3 -1
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/lib/telemetry.d.ts +89 -0
- package/dist/lib/telemetry.d.ts.map +1 -1
- package/dist/lib/terminal-card.d.ts +4 -2
- package/dist/lib/terminal-card.d.ts.map +1 -1
- package/package.json +1 -1
- package/templates/prompts/ORCHESTRATION.md +5 -1
- package/templates/prompts/optimizer.md +23 -13
package/dist/index.js
CHANGED
|
@@ -1448,16 +1448,36 @@ import pc3 from "picocolors";
|
|
|
1448
1448
|
// src/lib/terminal-card.ts
|
|
1449
1449
|
import fs7 from "fs";
|
|
1450
1450
|
import path7 from "path";
|
|
1451
|
-
import { execFileSync, execFile as
|
|
1452
|
-
import { promisify as
|
|
1451
|
+
import { execFileSync, execFile as execFile3 } from "child_process";
|
|
1452
|
+
import { promisify as promisify3 } from "util";
|
|
1453
1453
|
import pc2 from "picocolors";
|
|
1454
1454
|
|
|
1455
1455
|
// src/lib/telemetry.ts
|
|
1456
1456
|
import fs6 from "fs";
|
|
1457
1457
|
import path6 from "path";
|
|
1458
|
+
import { execSync, execFile as execFile2 } from "child_process";
|
|
1459
|
+
import { promisify as promisify2 } from "util";
|
|
1458
1460
|
import pc from "picocolors";
|
|
1461
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
1459
1462
|
var GLOBAL_WEEKLY_TOKEN_BUDGET = 875e4;
|
|
1460
1463
|
var GLOBAL_5H_TOKEN_BUDGET = 2e6;
|
|
1464
|
+
var TOKEN_ANOMALY_THRESHOLDS = {
|
|
1465
|
+
CONTEXT_ASYMMETRY_RATIO: 35,
|
|
1466
|
+
CONTEXT_ASYMMETRY_MIN_INPUT_TOKENS: 5e4,
|
|
1467
|
+
BUDGET_HOG_PERCENT: 75,
|
|
1468
|
+
MULTI_ROUTINE_CANNIBALIZATION_PERCENT: 80,
|
|
1469
|
+
MULTI_ROUTINE_MIN_ROUTINES: 3,
|
|
1470
|
+
ITERATION_EXHAUSTION_PERCENT: 20,
|
|
1471
|
+
PLAN_QUOTA_WEEKLY_WARNING_PERCENT: 30,
|
|
1472
|
+
PLAN_QUOTA_WEEKLY_CRITICAL_PERCENT: 20,
|
|
1473
|
+
PLAN_QUOTA_5H_CRITICAL_PERCENT: 20,
|
|
1474
|
+
RE_REVIEW_THRASH_THRESHOLD: 2
|
|
1475
|
+
};
|
|
1476
|
+
function formatIoRatio(inputTokens, outputTokens, precision = 0) {
|
|
1477
|
+
const safeOut = Math.max(1, outputTokens);
|
|
1478
|
+
const ratio = inputTokens / safeOut;
|
|
1479
|
+
return `${ratio.toFixed(precision)}:1`;
|
|
1480
|
+
}
|
|
1461
1481
|
function formatQuotaStatusBadge(status) {
|
|
1462
1482
|
if (status === "HEALTHY") return pc.green("[HEALTHY]");
|
|
1463
1483
|
if (status === "WARNING") return pc.yellow("[WARNING]");
|
|
@@ -1553,6 +1573,269 @@ function getRollingWindowTokenUsage(repoRoot, options = {}) {
|
|
|
1553
1573
|
status
|
|
1554
1574
|
};
|
|
1555
1575
|
}
|
|
1576
|
+
function parseAgyQuotaOutput(raw) {
|
|
1577
|
+
if (!raw || typeof raw !== "string") {
|
|
1578
|
+
return {
|
|
1579
|
+
available: false,
|
|
1580
|
+
groups: {},
|
|
1581
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
if (raw.includes("not logged into Antigravity") || raw.includes("error getting token source") || raw.includes("not authenticated")) {
|
|
1585
|
+
return {
|
|
1586
|
+
available: false,
|
|
1587
|
+
groups: {},
|
|
1588
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1589
|
+
error: "Not logged into Antigravity"
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
let jsonData = null;
|
|
1593
|
+
try {
|
|
1594
|
+
const trimmed = raw.trim();
|
|
1595
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
1596
|
+
jsonData = JSON.parse(trimmed);
|
|
1597
|
+
}
|
|
1598
|
+
} catch {
|
|
1599
|
+
}
|
|
1600
|
+
const groups = {};
|
|
1601
|
+
let description;
|
|
1602
|
+
if (jsonData) {
|
|
1603
|
+
const commandData = jsonData.command?.data || jsonData.data || jsonData;
|
|
1604
|
+
description = commandData.description;
|
|
1605
|
+
const rawGroups = commandData.groups || [];
|
|
1606
|
+
for (const g of rawGroups) {
|
|
1607
|
+
if (!g || !g.name) continue;
|
|
1608
|
+
const groupName = g.name;
|
|
1609
|
+
const buckets = [];
|
|
1610
|
+
let weeklyRemainingPct;
|
|
1611
|
+
let weeklyResetTime;
|
|
1612
|
+
let window5hRemainingPct;
|
|
1613
|
+
let window5hResetTime;
|
|
1614
|
+
for (const b of g.buckets || []) {
|
|
1615
|
+
const remainingFraction = typeof b.remaining_fraction === "number" ? b.remaining_fraction : 0;
|
|
1616
|
+
const remainingPercentage = remainingFraction * 100;
|
|
1617
|
+
const resetTime = b.reset_time || "";
|
|
1618
|
+
const window = b.window || (b.id?.includes("5h") ? "5h" : "weekly");
|
|
1619
|
+
const bucket = {
|
|
1620
|
+
id: b.id || "",
|
|
1621
|
+
name: b.name || "",
|
|
1622
|
+
window,
|
|
1623
|
+
remainingFraction,
|
|
1624
|
+
remainingPercentage,
|
|
1625
|
+
resetTime,
|
|
1626
|
+
description: b.description
|
|
1627
|
+
};
|
|
1628
|
+
buckets.push(bucket);
|
|
1629
|
+
if (window === "weekly" || b.id?.includes("weekly")) {
|
|
1630
|
+
weeklyRemainingPct = remainingPercentage;
|
|
1631
|
+
weeklyResetTime = resetTime;
|
|
1632
|
+
} else if (window === "5h" || b.id?.includes("5h")) {
|
|
1633
|
+
window5hRemainingPct = remainingPercentage;
|
|
1634
|
+
window5hResetTime = resetTime;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
groups[groupName] = {
|
|
1638
|
+
name: groupName,
|
|
1639
|
+
description: g.description,
|
|
1640
|
+
buckets,
|
|
1641
|
+
weeklyRemainingPct,
|
|
1642
|
+
weeklyResetTime,
|
|
1643
|
+
window5hRemainingPct,
|
|
1644
|
+
window5hResetTime
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
} else {
|
|
1648
|
+
const lines = raw.split("\n");
|
|
1649
|
+
for (const line of lines) {
|
|
1650
|
+
const match = line.match(/^\s*([A-Za-z0-9 ]+?)\t+([^\t]+)\t+(\d+)%\t+(.*)$/) || line.match(/^\s*(.+?)\s{2,}(Weekly Limit Remaining|Five Hour Limit Remaining)\s+(\d+)%\s*(.*)$/);
|
|
1651
|
+
if (match) {
|
|
1652
|
+
const groupName = match[1].trim();
|
|
1653
|
+
const bucketName = match[2].trim();
|
|
1654
|
+
const pct = parseInt(match[3], 10);
|
|
1655
|
+
const resetTime = match[4].trim();
|
|
1656
|
+
if (!groups[groupName]) {
|
|
1657
|
+
groups[groupName] = {
|
|
1658
|
+
name: groupName,
|
|
1659
|
+
buckets: []
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
const isWeekly = bucketName.toLowerCase().includes("weekly");
|
|
1663
|
+
const window = isWeekly ? "weekly" : "5h";
|
|
1664
|
+
groups[groupName].buckets.push({
|
|
1665
|
+
id: isWeekly ? `${groupName.toLowerCase().replace(/\s+/g, "-")}-weekly` : `${groupName.toLowerCase().replace(/\s+/g, "-")}-5h`,
|
|
1666
|
+
name: bucketName,
|
|
1667
|
+
window,
|
|
1668
|
+
remainingFraction: pct / 100,
|
|
1669
|
+
remainingPercentage: pct,
|
|
1670
|
+
resetTime
|
|
1671
|
+
});
|
|
1672
|
+
if (isWeekly) {
|
|
1673
|
+
groups[groupName].weeklyRemainingPct = pct;
|
|
1674
|
+
groups[groupName].weeklyResetTime = resetTime;
|
|
1675
|
+
} else {
|
|
1676
|
+
groups[groupName].window5hRemainingPct = pct;
|
|
1677
|
+
groups[groupName].window5hResetTime = resetTime;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
const hasGroups = Object.keys(groups).length > 0;
|
|
1683
|
+
if (!hasGroups) {
|
|
1684
|
+
return {
|
|
1685
|
+
available: false,
|
|
1686
|
+
groups: {},
|
|
1687
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1688
|
+
error: raw.length > 0 ? raw.length > 200 ? raw.slice(0, 200) + "..." : raw : "No quota information found"
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
const geminiGroup = groups["Gemini Models"];
|
|
1692
|
+
const claudeGroup = groups["Claude and GPT models"];
|
|
1693
|
+
return {
|
|
1694
|
+
available: true,
|
|
1695
|
+
description,
|
|
1696
|
+
groups,
|
|
1697
|
+
geminiWeeklyRemainingPct: geminiGroup?.weeklyRemainingPct,
|
|
1698
|
+
gemini5hRemainingPct: geminiGroup?.window5hRemainingPct,
|
|
1699
|
+
geminiWeeklyResetTime: geminiGroup?.weeklyResetTime,
|
|
1700
|
+
gemini5hResetTime: geminiGroup?.window5hResetTime,
|
|
1701
|
+
claudeWeeklyRemainingPct: claudeGroup?.weeklyRemainingPct,
|
|
1702
|
+
claude5hRemainingPct: claudeGroup?.window5hRemainingPct,
|
|
1703
|
+
claudeWeeklyResetTime: claudeGroup?.weeklyResetTime,
|
|
1704
|
+
claude5hResetTime: claudeGroup?.window5hResetTime,
|
|
1705
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
function readCachedPlanQuota(cacheFile, cacheTtlMs) {
|
|
1709
|
+
if (!cacheFile || cacheTtlMs <= 0 || !fs6.existsSync(cacheFile)) return null;
|
|
1710
|
+
try {
|
|
1711
|
+
const cached = JSON.parse(fs6.readFileSync(cacheFile, "utf8"));
|
|
1712
|
+
if (cached && typeof cached.cachedAt === "number" && Date.now() - cached.cachedAt < cacheTtlMs && cached.quota) {
|
|
1713
|
+
return cached.quota;
|
|
1714
|
+
}
|
|
1715
|
+
} catch {
|
|
1716
|
+
}
|
|
1717
|
+
return null;
|
|
1718
|
+
}
|
|
1719
|
+
function writeCachedPlanQuota(cacheFile, quota) {
|
|
1720
|
+
if (!cacheFile || !quota.available) return;
|
|
1721
|
+
try {
|
|
1722
|
+
fs6.mkdirSync(path6.dirname(cacheFile), { recursive: true });
|
|
1723
|
+
fs6.writeFileSync(cacheFile, JSON.stringify({ cachedAt: Date.now(), quota }, null, 2));
|
|
1724
|
+
} catch {
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
function getActualPlanQuotaSync(options = {}) {
|
|
1728
|
+
const cacheTtlMs = options.cacheTtlMs ?? 6e4;
|
|
1729
|
+
const cacheFile = options.repoRoot ? path6.join(options.repoRoot, ".jonah-fleet", "plan-quota-cache.json") : void 0;
|
|
1730
|
+
const cached = readCachedPlanQuota(cacheFile, cacheTtlMs);
|
|
1731
|
+
if (cached) return cached;
|
|
1732
|
+
try {
|
|
1733
|
+
let rawOutput;
|
|
1734
|
+
if (options.executor) {
|
|
1735
|
+
rawOutput = options.executor();
|
|
1736
|
+
} else if (process.env.VITEST) {
|
|
1737
|
+
return {
|
|
1738
|
+
available: false,
|
|
1739
|
+
groups: {},
|
|
1740
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1741
|
+
error: "Skipped in test environment"
|
|
1742
|
+
};
|
|
1743
|
+
} else {
|
|
1744
|
+
rawOutput = execSync('agy --output-format json --print "/quota"', {
|
|
1745
|
+
encoding: "utf8",
|
|
1746
|
+
timeout: 1e4,
|
|
1747
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
const quota = parseAgyQuotaOutput(rawOutput);
|
|
1751
|
+
writeCachedPlanQuota(cacheFile, quota);
|
|
1752
|
+
return quota;
|
|
1753
|
+
} catch (err) {
|
|
1754
|
+
return {
|
|
1755
|
+
available: false,
|
|
1756
|
+
groups: {},
|
|
1757
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1758
|
+
error: err?.message || String(err)
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
async function fetchActualPlanQuota(options = {}) {
|
|
1763
|
+
const cacheTtlMs = options.cacheTtlMs ?? 6e4;
|
|
1764
|
+
const cacheFile = options.repoRoot ? path6.join(options.repoRoot, ".jonah-fleet", "plan-quota-cache.json") : void 0;
|
|
1765
|
+
const cached = readCachedPlanQuota(cacheFile, cacheTtlMs);
|
|
1766
|
+
if (cached) return cached;
|
|
1767
|
+
try {
|
|
1768
|
+
let rawOutput;
|
|
1769
|
+
if (options.executor) {
|
|
1770
|
+
rawOutput = await options.executor();
|
|
1771
|
+
} else if (process.env.VITEST) {
|
|
1772
|
+
return {
|
|
1773
|
+
available: false,
|
|
1774
|
+
groups: {},
|
|
1775
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1776
|
+
error: "Skipped in test environment"
|
|
1777
|
+
};
|
|
1778
|
+
} else {
|
|
1779
|
+
const { stdout } = await execFileAsync2("agy", ["--output-format", "json", "--print", "/quota"], {
|
|
1780
|
+
timeout: 1e4
|
|
1781
|
+
});
|
|
1782
|
+
rawOutput = stdout;
|
|
1783
|
+
}
|
|
1784
|
+
const quota = parseAgyQuotaOutput(rawOutput);
|
|
1785
|
+
writeCachedPlanQuota(cacheFile, quota);
|
|
1786
|
+
return quota;
|
|
1787
|
+
} catch (err) {
|
|
1788
|
+
return {
|
|
1789
|
+
available: false,
|
|
1790
|
+
groups: {},
|
|
1791
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1792
|
+
error: err?.message || String(err)
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
function formatPlanQuotaSummary(quota, options = {}) {
|
|
1797
|
+
if (!quota || !quota.available) {
|
|
1798
|
+
return "Plan Quota: Unavailable";
|
|
1799
|
+
}
|
|
1800
|
+
const parts = [];
|
|
1801
|
+
const fmt = (pct) => options.markdown ? `\`${pct.toFixed(1)}% remaining\`` : `${pct.toFixed(1)}% remaining`;
|
|
1802
|
+
const geminiUnused = (quota.gemini5hRemainingPct === void 0 || quota.gemini5hRemainingPct === 100) && (quota.geminiWeeklyRemainingPct === void 0 || quota.geminiWeeklyRemainingPct === 100);
|
|
1803
|
+
const claudeUnused = (quota.claude5hRemainingPct === void 0 || quota.claude5hRemainingPct === 100) && (quota.claudeWeeklyRemainingPct === void 0 || quota.claudeWeeklyRemainingPct === 100);
|
|
1804
|
+
const allProvidersUnused = geminiUnused && claudeUnused;
|
|
1805
|
+
const shouldDisplayGemini = !options.hideUnused || !geminiUnused || allProvidersUnused;
|
|
1806
|
+
const shouldDisplayClaude = !options.hideUnused || !claudeUnused;
|
|
1807
|
+
if (quota.gemini5hRemainingPct !== void 0 || quota.geminiWeeklyRemainingPct !== void 0) {
|
|
1808
|
+
if (shouldDisplayGemini) {
|
|
1809
|
+
const subParts = [];
|
|
1810
|
+
if (quota.gemini5hRemainingPct !== void 0) {
|
|
1811
|
+
subParts.push(`Gemini 5h: ${fmt(quota.gemini5hRemainingPct)}`);
|
|
1812
|
+
}
|
|
1813
|
+
if (quota.geminiWeeklyRemainingPct !== void 0) {
|
|
1814
|
+
const label = quota.gemini5hRemainingPct !== void 0 ? "Weekly" : "Gemini Weekly";
|
|
1815
|
+
subParts.push(`${label}: ${fmt(quota.geminiWeeklyRemainingPct)}`);
|
|
1816
|
+
}
|
|
1817
|
+
if (subParts.length > 0) {
|
|
1818
|
+
parts.push(subParts.join(" \xB7 "));
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
if (quota.claude5hRemainingPct !== void 0 || quota.claudeWeeklyRemainingPct !== void 0) {
|
|
1823
|
+
if (!options.hideUnused || !claudeUnused) {
|
|
1824
|
+
const subParts = [];
|
|
1825
|
+
if (quota.claude5hRemainingPct !== void 0) {
|
|
1826
|
+
subParts.push(`Claude/GPT 5h: ${fmt(quota.claude5hRemainingPct)}`);
|
|
1827
|
+
}
|
|
1828
|
+
if (quota.claudeWeeklyRemainingPct !== void 0) {
|
|
1829
|
+
const label = quota.claude5hRemainingPct !== void 0 ? "Weekly" : "Claude/GPT Weekly";
|
|
1830
|
+
subParts.push(`${label}: ${fmt(quota.claudeWeeklyRemainingPct)}`);
|
|
1831
|
+
}
|
|
1832
|
+
if (subParts.length > 0) {
|
|
1833
|
+
parts.push(subParts.join(" \xB7 "));
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
return parts.join(options.markdown ? "<br>" : "\n");
|
|
1838
|
+
}
|
|
1556
1839
|
function parseLogToTelemetry(content, options = {}) {
|
|
1557
1840
|
if (!content || typeof content !== "string") return null;
|
|
1558
1841
|
const lines = content.split("\n");
|
|
@@ -1619,6 +1902,17 @@ function parseLogToTelemetry(content, options = {}) {
|
|
|
1619
1902
|
);
|
|
1620
1903
|
questionsAskedCount = questionLines.length > 0 ? questionLines.length : 2;
|
|
1621
1904
|
}
|
|
1905
|
+
let targetPr;
|
|
1906
|
+
const rawTarget = metadata["target"] || metadata["target / context"] || metadata["pr"];
|
|
1907
|
+
if (rawTarget) {
|
|
1908
|
+
const prMatch = rawTarget.match(/PR\s*#?(\d+)/i);
|
|
1909
|
+
if (prMatch) targetPr = parseInt(prMatch[1], 10);
|
|
1910
|
+
}
|
|
1911
|
+
let commitSha = metadata["commit"] || metadata["commit sha"] || metadata["head sha"] || metadata["sha"];
|
|
1912
|
+
if (!commitSha) {
|
|
1913
|
+
const shaMatch = content.match(/(?:commit|head\s+sha|head\s+commit)[:\s]+`?([0-9a-f]{7,40})`?/i);
|
|
1914
|
+
if (shaMatch) commitSha = shaMatch[1];
|
|
1915
|
+
}
|
|
1622
1916
|
return {
|
|
1623
1917
|
schemaVersion: "1.0.0",
|
|
1624
1918
|
routine,
|
|
@@ -1639,7 +1933,9 @@ function parseLogToTelemetry(content, options = {}) {
|
|
|
1639
1933
|
promptSha,
|
|
1640
1934
|
ambiguityGateTriggered: ambiguityGateTriggered || void 0,
|
|
1641
1935
|
questionsAskedCount,
|
|
1642
|
-
needsInfoApplied: needsInfoApplied || void 0
|
|
1936
|
+
needsInfoApplied: needsInfoApplied || void 0,
|
|
1937
|
+
targetPr,
|
|
1938
|
+
commitSha
|
|
1643
1939
|
};
|
|
1644
1940
|
}
|
|
1645
1941
|
function checkWeeklyBudgetLimit(usedTokens, ceilingTokens = GLOBAL_WEEKLY_TOKEN_BUDGET) {
|
|
@@ -1802,15 +2098,72 @@ function collectLocalTelemetryLogs(dir, repositoryName = "local") {
|
|
|
1802
2098
|
const traverse = (currentDir) => {
|
|
1803
2099
|
if (!fs6.existsSync(currentDir)) return;
|
|
1804
2100
|
const entries = fs6.readdirSync(currentDir, { withFileTypes: true });
|
|
2101
|
+
const filesByBase = /* @__PURE__ */ new Map();
|
|
1805
2102
|
for (const entry of entries) {
|
|
1806
|
-
const fullPath = path6.join(currentDir, entry.name);
|
|
1807
2103
|
if (entry.isDirectory()) {
|
|
1808
|
-
traverse(
|
|
1809
|
-
} else if (entry.isFile()
|
|
2104
|
+
traverse(path6.join(currentDir, entry.name));
|
|
2105
|
+
} else if (entry.isFile()) {
|
|
2106
|
+
if (entry.name.endsWith(".md")) {
|
|
2107
|
+
const base = entry.name.slice(0, -3);
|
|
2108
|
+
const current = filesByBase.get(base) || {};
|
|
2109
|
+
current.md = path6.join(currentDir, entry.name);
|
|
2110
|
+
filesByBase.set(base, current);
|
|
2111
|
+
} else if (entry.name.endsWith(".json")) {
|
|
2112
|
+
const base = entry.name.slice(0, -5);
|
|
2113
|
+
const current = filesByBase.get(base) || {};
|
|
2114
|
+
current.json = path6.join(currentDir, entry.name);
|
|
2115
|
+
filesByBase.set(base, current);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
for (const [, paths] of filesByBase) {
|
|
2120
|
+
let parsed = false;
|
|
2121
|
+
if (paths.json) {
|
|
1810
2122
|
try {
|
|
1811
|
-
const content = fs6.readFileSync(
|
|
2123
|
+
const content = fs6.readFileSync(paths.json, "utf8");
|
|
2124
|
+
const data = JSON.parse(content);
|
|
2125
|
+
if (data.routine && data.timestamp) {
|
|
2126
|
+
const inTokens = data.usage?.inputTokens ?? data.usage?.input_tokens ?? data.inputTokens ?? 0;
|
|
2127
|
+
const outTokens = data.usage?.outputTokens ?? data.usage?.output_tokens ?? data.outputTokens ?? 0;
|
|
2128
|
+
const totTokens = data.usage?.totalTokens ?? data.usage?.total_tokens ?? data.totalTokens ?? inTokens + outTokens;
|
|
2129
|
+
const durationSec = data.durationMs ? Math.round(data.durationMs / 1e3) : data.durationSeconds ?? void 0;
|
|
2130
|
+
const result = data.result ?? (data.success === true ? "SUCCESS" : data.success === false ? "FAILURE" : "UNKNOWN");
|
|
2131
|
+
const targetPr = data.pr ?? data.targetPr ?? (typeof data.target === "string" ? data.target.match(/PR\s*#?(\d+)/i)?.[1] ? parseInt(data.target.match(/PR\s*#?(\d+)/i)[1], 10) : void 0 : void 0);
|
|
2132
|
+
const commitSha = data.commitSha ?? data.headRefOid ?? data.headSha ?? (typeof data.commit === "string" ? data.commit : data.commit?.oid);
|
|
2133
|
+
summaries.push({
|
|
2134
|
+
schemaVersion: "1.0.0",
|
|
2135
|
+
routine: data.routine,
|
|
2136
|
+
repository: repositoryName,
|
|
2137
|
+
timestamp: data.timestamp,
|
|
2138
|
+
result,
|
|
2139
|
+
errorReason: data.errorReason || data.error,
|
|
2140
|
+
failureCategory: data.failureCategory,
|
|
2141
|
+
inputTokens: inTokens,
|
|
2142
|
+
outputTokens: outTokens,
|
|
2143
|
+
totalTokens: totTokens,
|
|
2144
|
+
estimatedCost: data.estimatedCost ?? totTokens / 1e6 * 0.35,
|
|
2145
|
+
durationSeconds: durationSec,
|
|
2146
|
+
iterationsUsed: data.iterationsUsed,
|
|
2147
|
+
maxIterations: data.maxIterations,
|
|
2148
|
+
ambiguityGateTriggered: Boolean(data.ambiguityGateTriggered),
|
|
2149
|
+
questionsAskedCount: data.questionsAskedCount || 0,
|
|
2150
|
+
needsInfoApplied: Boolean(data.needsInfoApplied),
|
|
2151
|
+
targetPr,
|
|
2152
|
+
commitSha
|
|
2153
|
+
});
|
|
2154
|
+
parsed = true;
|
|
2155
|
+
}
|
|
2156
|
+
} catch {
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
if (!parsed && paths.md) {
|
|
2160
|
+
try {
|
|
2161
|
+
const content = fs6.readFileSync(paths.md, "utf8");
|
|
1812
2162
|
const summary = parseLogToTelemetry(content, { repository: repositoryName });
|
|
1813
|
-
if (summary)
|
|
2163
|
+
if (summary) {
|
|
2164
|
+
summaries.push(summary);
|
|
2165
|
+
parsed = true;
|
|
2166
|
+
}
|
|
1814
2167
|
} catch {
|
|
1815
2168
|
}
|
|
1816
2169
|
}
|
|
@@ -1902,6 +2255,129 @@ function renderProgressBar(percentage, width = 25) {
|
|
|
1902
2255
|
if (clamped >= 70) return pc.yellow(filledBar) + pc.gray(emptyBar);
|
|
1903
2256
|
return pc.green(filledBar) + pc.gray(emptyBar);
|
|
1904
2257
|
}
|
|
2258
|
+
function detectTokenAnomalies(telemetry, options = {}) {
|
|
2259
|
+
const anomalies = [];
|
|
2260
|
+
for (const [routineName, r] of Object.entries(telemetry.byRoutine)) {
|
|
2261
|
+
const ratio = r.totalInputTokens / Math.max(1, r.totalOutputTokens);
|
|
2262
|
+
if (ratio > TOKEN_ANOMALY_THRESHOLDS.CONTEXT_ASYMMETRY_RATIO && r.totalInputTokens > TOKEN_ANOMALY_THRESHOLDS.CONTEXT_ASYMMETRY_MIN_INPUT_TOKENS) {
|
|
2263
|
+
anomalies.push({
|
|
2264
|
+
type: "context_asymmetry",
|
|
2265
|
+
severity: "WARNING",
|
|
2266
|
+
routine: routineName,
|
|
2267
|
+
message: `Context Asymmetry on '${routineName}': Input/Output ratio is ${formatIoRatio(r.totalInputTokens, r.totalOutputTokens, 1)} (${r.totalInputTokens.toLocaleString()} input vs ${r.totalOutputTokens.toLocaleString()} output tokens).`,
|
|
2268
|
+
remediation: `Apply progressive disclosure and skill pruning. Scope skills strictly per routine rather than injecting all skills globally.`
|
|
2269
|
+
});
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
if (telemetry.totalTokens > 0) {
|
|
2273
|
+
for (const [routineName, r] of Object.entries(telemetry.byRoutine)) {
|
|
2274
|
+
const share = r.totalTokens / telemetry.totalTokens * 100;
|
|
2275
|
+
if (share > TOKEN_ANOMALY_THRESHOLDS.BUDGET_HOG_PERCENT && Object.keys(telemetry.byRoutine).length > 1) {
|
|
2276
|
+
anomalies.push({
|
|
2277
|
+
type: "budget_hog",
|
|
2278
|
+
severity: "CRITICAL",
|
|
2279
|
+
routine: routineName,
|
|
2280
|
+
message: `Budget Hog on '${routineName}': Consumes ${share.toFixed(1)}% of total fleet token allowance.`,
|
|
2281
|
+
remediation: `Throttle dispatch cadence, introduce stricter pre-qualification filters, or add early exit guards.`
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
if (telemetry.totalTokens > 0 && Object.keys(telemetry.byRoutine).length >= TOKEN_ANOMALY_THRESHOLDS.MULTI_ROUTINE_MIN_ROUTINES) {
|
|
2287
|
+
const sortedRoutines = Object.values(telemetry.byRoutine).sort(
|
|
2288
|
+
(a, b) => b.totalTokens - a.totalTokens
|
|
2289
|
+
);
|
|
2290
|
+
const top2Spend = (sortedRoutines[0]?.totalTokens || 0) + (sortedRoutines[1]?.totalTokens || 0);
|
|
2291
|
+
const top2Share = top2Spend / telemetry.totalTokens * 100;
|
|
2292
|
+
if (top2Share > TOKEN_ANOMALY_THRESHOLDS.MULTI_ROUTINE_CANNIBALIZATION_PERCENT) {
|
|
2293
|
+
anomalies.push({
|
|
2294
|
+
type: "multi_routine_cannibalization",
|
|
2295
|
+
severity: "WARNING",
|
|
2296
|
+
message: `Multi-Routine Budget Cannibalization: Top 2 routines ('${sortedRoutines[0]?.routine}' and '${sortedRoutines[1]?.routine}') consume ${top2Share.toFixed(1)}% of fleet tokens.`,
|
|
2297
|
+
remediation: `Rebalance dispatch pacing and coordinate candidate skips across authoring and review routines.`
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
const prShaEvaluations = /* @__PURE__ */ new Map();
|
|
2302
|
+
for (const e of telemetry.events || []) {
|
|
2303
|
+
if (e.targetPr && e.commitSha) {
|
|
2304
|
+
const key = `${e.targetPr}:${e.commitSha}`;
|
|
2305
|
+
const existing = prShaEvaluations.get(key);
|
|
2306
|
+
if (existing) {
|
|
2307
|
+
existing.count++;
|
|
2308
|
+
} else {
|
|
2309
|
+
prShaEvaluations.set(key, { pr: e.targetPr, sha: e.commitSha, count: 1, routine: e.routine });
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
for (const { pr, sha, count, routine } of prShaEvaluations.values()) {
|
|
2314
|
+
if (count >= TOKEN_ANOMALY_THRESHOLDS.RE_REVIEW_THRASH_THRESHOLD) {
|
|
2315
|
+
anomalies.push({
|
|
2316
|
+
type: "unilateral_rereview_thrash",
|
|
2317
|
+
severity: "WARNING",
|
|
2318
|
+
routine,
|
|
2319
|
+
message: `Unilateral Re-Review Thrash on PR #${pr}: Evaluated ${count} times on identical commit SHA ${sha.slice(0, 7)} without intervening commits.`,
|
|
2320
|
+
remediation: `Enforce persistent review commit state and failure cooldowns (e.g. daemon.json review state cache).`
|
|
2321
|
+
});
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
for (const [routineName, r] of Object.entries(telemetry.byRoutine)) {
|
|
2325
|
+
if (r.runCount >= 3) {
|
|
2326
|
+
const tokenLimitFailures = (telemetry.events || []).filter(
|
|
2327
|
+
(e) => e.routine === routineName && e.failureCategory === "token_limit"
|
|
2328
|
+
).length;
|
|
2329
|
+
const failPct = tokenLimitFailures / r.runCount * 100;
|
|
2330
|
+
if (failPct > TOKEN_ANOMALY_THRESHOLDS.ITERATION_EXHAUSTION_PERCENT) {
|
|
2331
|
+
anomalies.push({
|
|
2332
|
+
type: "iteration_exhaustion",
|
|
2333
|
+
severity: "WARNING",
|
|
2334
|
+
routine: routineName,
|
|
2335
|
+
message: `Iteration Ceiling Exhaustion on '${routineName}': ${failPct.toFixed(1)}% of runs terminate at max iteration/token limit.`,
|
|
2336
|
+
remediation: `Decompose tasks vertically into smaller slices and tighten pre-ready self-audits.`
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
if (options.actualQuota?.available) {
|
|
2342
|
+
const geminiWeekly = options.actualQuota.geminiWeeklyRemainingPct;
|
|
2343
|
+
const gemini5h = options.actualQuota.gemini5hRemainingPct;
|
|
2344
|
+
if (geminiWeekly !== void 0 && geminiWeekly < TOKEN_ANOMALY_THRESHOLDS.PLAN_QUOTA_WEEKLY_WARNING_PERCENT) {
|
|
2345
|
+
anomalies.push({
|
|
2346
|
+
type: "plan_quota_velocity",
|
|
2347
|
+
severity: geminiWeekly < TOKEN_ANOMALY_THRESHOLDS.PLAN_QUOTA_WEEKLY_CRITICAL_PERCENT ? "CRITICAL" : "WARNING",
|
|
2348
|
+
message: `Plan Quota Burn Velocity: Gemini weekly plan quota is at ${geminiWeekly.toFixed(1)}% remaining (<${TOKEN_ANOMALY_THRESHOLDS.PLAN_QUOTA_WEEKLY_WARNING_PERCENT}% threshold).`,
|
|
2349
|
+
remediation: `Downgrade auxiliary routines ('peer-review', 'optimizer') to medium reasoning effort and standard Flash models (gemini-3.8-flash-medium).`
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
if (gemini5h !== void 0 && gemini5h < TOKEN_ANOMALY_THRESHOLDS.PLAN_QUOTA_5H_CRITICAL_PERCENT) {
|
|
2353
|
+
anomalies.push({
|
|
2354
|
+
type: "plan_quota_velocity",
|
|
2355
|
+
severity: "CRITICAL",
|
|
2356
|
+
message: `Plan Quota Burn Velocity: Gemini 5h rolling plan quota is at ${gemini5h.toFixed(1)}% remaining (<${TOKEN_ANOMALY_THRESHOLDS.PLAN_QUOTA_5H_CRITICAL_PERCENT}% threshold).`,
|
|
2357
|
+
remediation: `Throttle active agent execution to prevent rate limit lockout during the current 5-hour window.`
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
const claudeWeekly = options.actualQuota.claudeWeeklyRemainingPct;
|
|
2361
|
+
const claude5h = options.actualQuota.claude5hRemainingPct;
|
|
2362
|
+
if (claudeWeekly !== void 0 && claudeWeekly < TOKEN_ANOMALY_THRESHOLDS.PLAN_QUOTA_WEEKLY_WARNING_PERCENT) {
|
|
2363
|
+
anomalies.push({
|
|
2364
|
+
type: "plan_quota_velocity",
|
|
2365
|
+
severity: claudeWeekly < TOKEN_ANOMALY_THRESHOLDS.PLAN_QUOTA_WEEKLY_CRITICAL_PERCENT ? "CRITICAL" : "WARNING",
|
|
2366
|
+
message: `Plan Quota Burn Velocity: Claude/GPT weekly plan quota is at ${claudeWeekly.toFixed(1)}% remaining (<${TOKEN_ANOMALY_THRESHOLDS.PLAN_QUOTA_WEEKLY_WARNING_PERCENT}% threshold).`,
|
|
2367
|
+
remediation: `Conserve Claude/GPT quota for complex architectural reasoning; route standard tasks to Gemini Flash.`
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
if (claude5h !== void 0 && claude5h < TOKEN_ANOMALY_THRESHOLDS.PLAN_QUOTA_5H_CRITICAL_PERCENT) {
|
|
2371
|
+
anomalies.push({
|
|
2372
|
+
type: "plan_quota_velocity",
|
|
2373
|
+
severity: "CRITICAL",
|
|
2374
|
+
message: `Plan Quota Burn Velocity: Claude/GPT 5h rolling plan quota is at ${claude5h.toFixed(1)}% remaining (<${TOKEN_ANOMALY_THRESHOLDS.PLAN_QUOTA_5H_CRITICAL_PERCENT}% threshold).`,
|
|
2375
|
+
remediation: `Throttle active agent execution to prevent Claude/GPT rate limit lockout during the current 5-hour window.`
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
return anomalies;
|
|
2380
|
+
}
|
|
1905
2381
|
function renderTelemetryDashboard(telemetry, options = {}) {
|
|
1906
2382
|
if (options.json) {
|
|
1907
2383
|
return JSON.stringify(telemetry, null, 2);
|
|
@@ -1930,8 +2406,9 @@ function renderTelemetryDashboard(telemetry, options = {}) {
|
|
|
1930
2406
|
lines.push("\n" + pc.bold("\u{1F916} Spend by Agent Routine:"));
|
|
1931
2407
|
for (const [routineName, r] of Object.entries(telemetry.byRoutine)) {
|
|
1932
2408
|
const costStr = pc.green(`$${r.totalEstimatedCost.toFixed(2)}`);
|
|
2409
|
+
const ioRatioStr = formatIoRatio(r.totalInputTokens, r.totalOutputTokens);
|
|
1933
2410
|
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)}`
|
|
2411
|
+
` \u2022 ${pc.cyan(routineName.padEnd(30))} Runs: ${pc.bold(r.runCount.toString().padStart(2))} | Tokens: ${pc.bold(formatTokens(r.totalTokens).padStart(7))} | I/O: ${ioRatioStr.padStart(5)} | Cost: ${costStr.padStart(6)} | Avg Iter: ${r.avgIterationsUsed.toFixed(1)}`
|
|
1935
2412
|
);
|
|
1936
2413
|
}
|
|
1937
2414
|
if (Object.keys(telemetry.byRepository).length > 0) {
|
|
@@ -1956,6 +2433,15 @@ function renderTelemetryDashboard(telemetry, options = {}) {
|
|
|
1956
2433
|
lines.push(
|
|
1957
2434
|
` \u2022 Est. Wasted Tokens Averted: ~${pc.bold(pc.green(formatTokens(amb.estimatedTokensSaved)))} tokens (avoided speculative builds)`
|
|
1958
2435
|
);
|
|
2436
|
+
const anomalies = detectTokenAnomalies(telemetry, { actualQuota: options.actualQuota });
|
|
2437
|
+
if (anomalies.length > 0) {
|
|
2438
|
+
lines.push("\n" + pc.bold(pc.yellow("\u{1F6A8} Detected Token & Pacing Anomalies:")));
|
|
2439
|
+
for (const anom of anomalies) {
|
|
2440
|
+
const badge = anom.severity === "CRITICAL" ? pc.red("[CRITICAL]") : pc.yellow("[WARNING]");
|
|
2441
|
+
lines.push(` ${badge} ${anom.message}`);
|
|
2442
|
+
lines.push(pc.gray(` \u21B3 Remediation: ${anom.remediation}`));
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
1959
2445
|
const failKeys = Object.keys(telemetry.failureCategories);
|
|
1960
2446
|
if (failKeys.length > 0) {
|
|
1961
2447
|
lines.push("\n" + pc.bold(pc.red("\u26A0\uFE0F Failure Categories Breakdown:")));
|
|
@@ -1968,7 +2454,7 @@ function renderTelemetryDashboard(telemetry, options = {}) {
|
|
|
1968
2454
|
}
|
|
1969
2455
|
|
|
1970
2456
|
// src/lib/terminal-card.ts
|
|
1971
|
-
var
|
|
2457
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
1972
2458
|
function stripAnsi(text) {
|
|
1973
2459
|
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1974
2460
|
}
|
|
@@ -2058,7 +2544,7 @@ async function fetchTargetTitleAsync(repoRoot, target) {
|
|
|
2058
2544
|
try {
|
|
2059
2545
|
const prMatch = target.match(/PR\s*#?(\d+)/i);
|
|
2060
2546
|
if (prMatch) {
|
|
2061
|
-
const { stdout } = await
|
|
2547
|
+
const { stdout } = await execFileAsync3("gh", ["pr", "view", prMatch[1], "--json", "title", "-q", ".title"], {
|
|
2062
2548
|
cwd: repoRoot,
|
|
2063
2549
|
encoding: "utf8",
|
|
2064
2550
|
timeout: 3e3
|
|
@@ -2067,7 +2553,7 @@ async function fetchTargetTitleAsync(repoRoot, target) {
|
|
|
2067
2553
|
}
|
|
2068
2554
|
const issueMatch = target.match(/Issue\s*#?(\d+)/i);
|
|
2069
2555
|
if (issueMatch) {
|
|
2070
|
-
const { stdout } = await
|
|
2556
|
+
const { stdout } = await execFileAsync3("gh", ["issue", "view", issueMatch[1], "--json", "title", "-q", ".title"], {
|
|
2071
2557
|
cwd: repoRoot,
|
|
2072
2558
|
encoding: "utf8",
|
|
2073
2559
|
timeout: 3e3
|
|
@@ -2887,11 +3373,12 @@ function renderSummaryCard(options) {
|
|
|
2887
3373
|
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
2888
3374
|
lines.push(...bodyLines);
|
|
2889
3375
|
}
|
|
2890
|
-
if (typeof options.usage?.totalTokens === "number") {
|
|
3376
|
+
if (typeof options.usage?.totalTokens === "number" || options.planQuota?.available) {
|
|
2891
3377
|
lines.push(
|
|
2892
3378
|
...renderTokenQuotaSection({
|
|
2893
3379
|
usage: options.usage,
|
|
2894
3380
|
repoRoot: options.repoRoot,
|
|
3381
|
+
planQuota: options.planQuota,
|
|
2895
3382
|
width,
|
|
2896
3383
|
borderColor: border
|
|
2897
3384
|
})
|
|
@@ -2908,30 +3395,54 @@ function renderSummaryCard(options) {
|
|
|
2908
3395
|
}
|
|
2909
3396
|
function renderTokenQuotaSection(options) {
|
|
2910
3397
|
const total = options.usage?.totalTokens;
|
|
2911
|
-
|
|
3398
|
+
const hasUsage = typeof total === "number";
|
|
3399
|
+
const plan = options.planQuota;
|
|
3400
|
+
const hasPlan = Boolean(plan && plan.available);
|
|
3401
|
+
if (!hasUsage && !hasPlan) return [];
|
|
2912
3402
|
const border = options.borderColor ?? ((s) => pc2.dim(pc2.gray(s)));
|
|
2913
3403
|
const width = options.width;
|
|
2914
3404
|
const horizontal = "\u2500".repeat(width - 2);
|
|
2915
3405
|
const lines = [];
|
|
2916
|
-
const quota = calculateTokenQuotaPercentages(total);
|
|
2917
|
-
const breakdown = formatTokenBreakdown(options.usage, "prefix");
|
|
2918
3406
|
lines.push(border(`\u251C${horizontal}\u2524`));
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
3407
|
+
if (hasUsage) {
|
|
3408
|
+
const breakdown = formatTokenBreakdown(options.usage, "prefix");
|
|
3409
|
+
const inTok = options.usage?.inputTokens;
|
|
3410
|
+
const outTok = options.usage?.outputTokens;
|
|
3411
|
+
let ioStr = "";
|
|
3412
|
+
if (typeof inTok === "number" && typeof outTok === "number" && outTok > 0) {
|
|
3413
|
+
ioStr = ` \xB7 I/O: ${formatIoRatio(inTok, outTok)}`;
|
|
3414
|
+
}
|
|
3415
|
+
const tokenLine1 = ` Tokens: ${pc2.bold(total.toLocaleString())}${breakdown ? pc2.dim(` (${breakdown})`) : ""}${ioStr ? pc2.cyan(ioStr) : ""}`;
|
|
3416
|
+
lines.push(border("\u2502") + tokenLine1 + " ".repeat(Math.max(1, width - 2 - stripAnsi(tokenLine1).length)) + border("\u2502"));
|
|
3417
|
+
if (!hasPlan) {
|
|
3418
|
+
const quota = calculateTokenQuotaPercentages(total);
|
|
3419
|
+
const tokenLine2 = ` \u21B3 ${pc2.cyan(quota.pctOf5hLimit.toFixed(2) + "% of 5h limit")} \xB7 ${pc2.cyan(quota.pctOfWeeklyLimit.toFixed(2) + "% of weekly limit")}`;
|
|
3420
|
+
lines.push(border("\u2502") + tokenLine2 + " ".repeat(Math.max(1, width - 2 - stripAnsi(tokenLine2).length)) + border("\u2502"));
|
|
3421
|
+
if (options.repoRoot) {
|
|
3422
|
+
try {
|
|
3423
|
+
const rolling = getRollingWindowTokenUsage(options.repoRoot);
|
|
3424
|
+
if (rolling.windowTokens > 0 || rolling.weeklyTokens > 0) {
|
|
3425
|
+
const badge5h = formatQuotaStatusBadge(rolling.windowStatus);
|
|
3426
|
+
const badgeWeekly = formatQuotaStatusBadge(rolling.weeklyStatus);
|
|
3427
|
+
const rollingLine1 = ` Rolling 5h: ${rolling.windowTokens.toLocaleString()} / ${rolling.windowLimit.toLocaleString()} tokens (${rolling.windowPercentage.toFixed(1)}% \xB7 ${badge5h})`;
|
|
3428
|
+
lines.push(border("\u2502") + rollingLine1 + " ".repeat(Math.max(1, width - 2 - stripAnsi(rollingLine1).length)) + border("\u2502"));
|
|
3429
|
+
const rollingLine2 = ` Rolling 7d: ${rolling.weeklyTokens.toLocaleString()} / ${rolling.weeklyLimit.toLocaleString()} tokens (${rolling.weeklyPercentage.toFixed(1)}% \xB7 ${badgeWeekly})`;
|
|
3430
|
+
lines.push(border("\u2502") + rollingLine2 + " ".repeat(Math.max(1, width - 2 - stripAnsi(rollingLine2).length)) + border("\u2502"));
|
|
3431
|
+
}
|
|
3432
|
+
} catch {
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
if (hasPlan && plan) {
|
|
3438
|
+
const summary = formatPlanQuotaSummary(plan, { hideUnused: true });
|
|
3439
|
+
if (summary && summary !== "Plan Quota: Unavailable") {
|
|
3440
|
+
const summaryLines = summary.split("\n");
|
|
3441
|
+
for (let i = 0; i < summaryLines.length; i++) {
|
|
3442
|
+
const prefix = i === 0 ? " Plan Quota: " : " ";
|
|
3443
|
+
const planLine = `${prefix}${summaryLines[i]}`;
|
|
3444
|
+
lines.push(border("\u2502") + planLine + " ".repeat(Math.max(1, width - 2 - stripAnsi(planLine).length)) + border("\u2502"));
|
|
2933
3445
|
}
|
|
2934
|
-
} catch {
|
|
2935
3446
|
}
|
|
2936
3447
|
}
|
|
2937
3448
|
return lines;
|
|
@@ -2970,11 +3481,12 @@ function renderErrorCard(options) {
|
|
|
2970
3481
|
} else {
|
|
2971
3482
|
lines.push(pc2.red("\u2502") + pc2.dim(" (No daemon.log found)") + " ".repeat(Math.max(1, width - 26)) + pc2.red("\u2502"));
|
|
2972
3483
|
}
|
|
2973
|
-
if (typeof options.usage?.totalTokens === "number") {
|
|
3484
|
+
if (typeof options.usage?.totalTokens === "number" || options.planQuota?.available) {
|
|
2974
3485
|
lines.push(
|
|
2975
3486
|
...renderTokenQuotaSection({
|
|
2976
3487
|
usage: options.usage,
|
|
2977
3488
|
repoRoot: options.repoRoot,
|
|
3489
|
+
planQuota: options.planQuota,
|
|
2978
3490
|
width,
|
|
2979
3491
|
borderColor: (s) => pc2.red(s)
|
|
2980
3492
|
})
|
|
@@ -3834,6 +4346,7 @@ async function runStatus(options = {}) {
|
|
|
3834
4346
|
tokenUsage = computeTokenSpendFromLogs(logContents);
|
|
3835
4347
|
}
|
|
3836
4348
|
const rollingQuota = getRollingWindowTokenUsage(cwd);
|
|
4349
|
+
const planQuota = getActualPlanQuotaSync({ repoRoot: cwd });
|
|
3837
4350
|
if (options.json) {
|
|
3838
4351
|
console.log(
|
|
3839
4352
|
JSON.stringify(
|
|
@@ -3850,6 +4363,7 @@ async function runStatus(options = {}) {
|
|
|
3850
4363
|
repositories: manifest.repositories || [],
|
|
3851
4364
|
tokenUsage,
|
|
3852
4365
|
quotaPacing: rollingQuota,
|
|
4366
|
+
planQuota: planQuota.available ? planQuota : void 0,
|
|
3853
4367
|
drift: {
|
|
3854
4368
|
hasDrift,
|
|
3855
4369
|
...drift
|
|
@@ -3949,6 +4463,37 @@ async function runStatus(options = {}) {
|
|
|
3949
4463
|
` \u2022 7-Day Spend: ${pc8.bold(formatTokens2(rollingQuota.weeklyTokens))} / ${formatTokens2(rollingQuota.weeklyLimit)} tokens (${pc8.cyan(rollingQuota.weeklyPercentage.toFixed(1) + "%")}) ${badgeWeekly}`
|
|
3950
4464
|
);
|
|
3951
4465
|
}
|
|
4466
|
+
if (planQuota.available) {
|
|
4467
|
+
console.log(pc8.bold("\n \u26A1 Real-Time Plan Quota (Google Antigravity):"));
|
|
4468
|
+
if (planQuota.gemini5hRemainingPct !== void 0 || planQuota.geminiWeeklyRemainingPct !== void 0) {
|
|
4469
|
+
const parts = [];
|
|
4470
|
+
if (planQuota.gemini5hRemainingPct !== void 0) {
|
|
4471
|
+
const reset5h = planQuota.gemini5hResetTime ? pc8.dim(` (resets ${planQuota.gemini5hResetTime})`) : "";
|
|
4472
|
+
parts.push(`${pc8.bold(planQuota.gemini5hRemainingPct.toFixed(1) + "% 5h remaining")}${reset5h}`);
|
|
4473
|
+
}
|
|
4474
|
+
if (planQuota.geminiWeeklyRemainingPct !== void 0) {
|
|
4475
|
+
const resetWk = planQuota.geminiWeeklyResetTime ? pc8.dim(` (resets ${planQuota.geminiWeeklyResetTime})`) : "";
|
|
4476
|
+
parts.push(`${pc8.bold(planQuota.geminiWeeklyRemainingPct.toFixed(1) + "% weekly remaining")}${resetWk}`);
|
|
4477
|
+
}
|
|
4478
|
+
if (parts.length > 0) {
|
|
4479
|
+
console.log(` \u2022 Gemini Models: ${parts.join(" \xB7 ")}`);
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4482
|
+
if (planQuota.claude5hRemainingPct !== void 0 || planQuota.claudeWeeklyRemainingPct !== void 0) {
|
|
4483
|
+
const parts = [];
|
|
4484
|
+
if (planQuota.claude5hRemainingPct !== void 0) {
|
|
4485
|
+
const reset5h = planQuota.claude5hResetTime ? pc8.dim(` (resets ${planQuota.claude5hResetTime})`) : "";
|
|
4486
|
+
parts.push(`${pc8.bold(planQuota.claude5hRemainingPct.toFixed(1) + "% 5h remaining")}${reset5h}`);
|
|
4487
|
+
}
|
|
4488
|
+
if (planQuota.claudeWeeklyRemainingPct !== void 0) {
|
|
4489
|
+
const resetWk = planQuota.claudeWeeklyResetTime ? pc8.dim(` (resets ${planQuota.claudeWeeklyResetTime})`) : "";
|
|
4490
|
+
parts.push(`${pc8.bold(planQuota.claudeWeeklyRemainingPct.toFixed(1) + "% weekly remaining")}${resetWk}`);
|
|
4491
|
+
}
|
|
4492
|
+
if (parts.length > 0) {
|
|
4493
|
+
console.log(` \u2022 Claude/GPT Models: ${parts.join(" \xB7 ")}`);
|
|
4494
|
+
}
|
|
4495
|
+
}
|
|
4496
|
+
}
|
|
3952
4497
|
console.log(pc8.bold("\n Drift / Health:"));
|
|
3953
4498
|
if (!hasDrift) {
|
|
3954
4499
|
console.log(pc8.green(" \u2713 All prompts, workflows, and skills are healthy and match fleet templates.\n"));
|
|
@@ -3963,7 +4508,7 @@ async function runStatus(options = {}) {
|
|
|
3963
4508
|
}
|
|
3964
4509
|
|
|
3965
4510
|
// src/commands/contribute.ts
|
|
3966
|
-
import { execSync } from "child_process";
|
|
4511
|
+
import { execSync as execSync2 } from "child_process";
|
|
3967
4512
|
import pc9 from "picocolors";
|
|
3968
4513
|
function prepareContributionPayload(options = {}) {
|
|
3969
4514
|
const repo = options.repo || "Jonah-Projects/jonah-fleet";
|
|
@@ -4023,7 +4568,7 @@ async function runContribute(options = {}) {
|
|
|
4023
4568
|
};
|
|
4024
4569
|
} else {
|
|
4025
4570
|
try {
|
|
4026
|
-
|
|
4571
|
+
execSync2("gh auth status", { stdio: "pipe" });
|
|
4027
4572
|
} catch {
|
|
4028
4573
|
const errorMsg = "GitHub CLI (`gh`) is not authenticated. Run `gh auth login` first.";
|
|
4029
4574
|
console.error(pc9.red(`\u274C ${errorMsg}`));
|
|
@@ -4168,15 +4713,15 @@ import pc12 from "picocolors";
|
|
|
4168
4713
|
import fs13 from "fs";
|
|
4169
4714
|
import path13 from "path";
|
|
4170
4715
|
import os2 from "os";
|
|
4171
|
-
import { spawn, execSync as
|
|
4172
|
-
import { promisify as
|
|
4716
|
+
import { spawn, execSync as execSync3, execFile as execFile5 } from "child_process";
|
|
4717
|
+
import { promisify as promisify5 } from "util";
|
|
4173
4718
|
|
|
4174
4719
|
// src/lib/worktree.ts
|
|
4175
4720
|
import fs12 from "fs";
|
|
4176
4721
|
import path12 from "path";
|
|
4177
|
-
import { execFile as
|
|
4178
|
-
import { promisify as
|
|
4179
|
-
var
|
|
4722
|
+
import { execFile as execFile4 } from "child_process";
|
|
4723
|
+
import { promisify as promisify4 } from "util";
|
|
4724
|
+
var execFileAsync4 = promisify4(execFile4);
|
|
4180
4725
|
function getWorktreesBaseDir(repoRoot) {
|
|
4181
4726
|
return path12.join(repoRoot, ".jonah-fleet", "worktrees");
|
|
4182
4727
|
}
|
|
@@ -4187,10 +4732,10 @@ async function createWorktree(repoRoot, options) {
|
|
|
4187
4732
|
const worktreePath = path12.join(baseDir, sanitizedDirName);
|
|
4188
4733
|
if (fs12.existsSync(worktreePath)) {
|
|
4189
4734
|
try {
|
|
4190
|
-
await
|
|
4735
|
+
await execFileAsync4("git", ["worktree", "remove", "--force", worktreePath], { cwd: repoRoot });
|
|
4191
4736
|
} catch {
|
|
4192
4737
|
fs12.rmSync(worktreePath, { recursive: true, force: true });
|
|
4193
|
-
await
|
|
4738
|
+
await execFileAsync4("git", ["worktree", "prune"], { cwd: repoRoot }).catch(() => {
|
|
4194
4739
|
});
|
|
4195
4740
|
}
|
|
4196
4741
|
}
|
|
@@ -4200,7 +4745,7 @@ async function createWorktree(repoRoot, options) {
|
|
|
4200
4745
|
baseRef = "HEAD";
|
|
4201
4746
|
for (const candidate of candidates) {
|
|
4202
4747
|
try {
|
|
4203
|
-
await
|
|
4748
|
+
await execFileAsync4("git", ["rev-parse", "--verify", candidate], { cwd: repoRoot });
|
|
4204
4749
|
baseRef = candidate;
|
|
4205
4750
|
break;
|
|
4206
4751
|
} catch {
|
|
@@ -4208,10 +4753,10 @@ async function createWorktree(repoRoot, options) {
|
|
|
4208
4753
|
}
|
|
4209
4754
|
}
|
|
4210
4755
|
try {
|
|
4211
|
-
await
|
|
4756
|
+
await execFileAsync4("git", ["branch", "-D", options.branchName], { cwd: repoRoot });
|
|
4212
4757
|
} catch {
|
|
4213
4758
|
}
|
|
4214
|
-
await
|
|
4759
|
+
await execFileAsync4("git", ["worktree", "add", worktreePath, "-b", options.branchName, baseRef], {
|
|
4215
4760
|
cwd: repoRoot
|
|
4216
4761
|
});
|
|
4217
4762
|
return { worktreePath, branchName: options.branchName };
|
|
@@ -4219,24 +4764,24 @@ async function createWorktree(repoRoot, options) {
|
|
|
4219
4764
|
async function removeWorktree(repoRoot, worktreePath, options = {}) {
|
|
4220
4765
|
try {
|
|
4221
4766
|
if (fs12.existsSync(worktreePath)) {
|
|
4222
|
-
await
|
|
4767
|
+
await execFileAsync4("git", ["worktree", "remove", "--force", worktreePath], { cwd: repoRoot });
|
|
4223
4768
|
}
|
|
4224
4769
|
} catch {
|
|
4225
4770
|
if (fs12.existsSync(worktreePath)) {
|
|
4226
4771
|
fs12.rmSync(worktreePath, { recursive: true, force: true });
|
|
4227
4772
|
}
|
|
4228
4773
|
} finally {
|
|
4229
|
-
await
|
|
4774
|
+
await execFileAsync4("git", ["worktree", "prune"], { cwd: repoRoot }).catch(() => {
|
|
4230
4775
|
});
|
|
4231
4776
|
}
|
|
4232
4777
|
if (options.deleteBranch && options.branchName) {
|
|
4233
|
-
await
|
|
4778
|
+
await execFileAsync4("git", ["branch", "-D", options.branchName], { cwd: repoRoot }).catch(() => {
|
|
4234
4779
|
});
|
|
4235
4780
|
}
|
|
4236
4781
|
}
|
|
4237
4782
|
async function listActiveWorktrees(repoRoot) {
|
|
4238
4783
|
try {
|
|
4239
|
-
const { stdout } = await
|
|
4784
|
+
const { stdout } = await execFileAsync4("git", ["worktree", "list", "--porcelain"], { cwd: repoRoot });
|
|
4240
4785
|
const entries = stdout.trim().split("\n\n");
|
|
4241
4786
|
const worktrees = [];
|
|
4242
4787
|
const baseDir = path12.resolve(getWorktreesBaseDir(repoRoot));
|
|
@@ -4281,7 +4826,7 @@ async function cleanupStaleWorktrees(repoRoot, options = {}) {
|
|
|
4281
4826
|
const isInsideBaseDir = wtResolved === baseDir || wtResolved.startsWith(baseDir + path12.sep);
|
|
4282
4827
|
if (isInsideBaseDir && wtResolved !== keepNormalized) {
|
|
4283
4828
|
try {
|
|
4284
|
-
await
|
|
4829
|
+
await execFileAsync4("git", ["worktree", "remove", "--force", wt.path], { cwd: repoRoot });
|
|
4285
4830
|
} catch {
|
|
4286
4831
|
if (fs12.existsSync(wt.path)) {
|
|
4287
4832
|
fs12.rmSync(wt.path, { recursive: true, force: true });
|
|
@@ -4291,7 +4836,7 @@ async function cleanupStaleWorktrees(repoRoot, options = {}) {
|
|
|
4291
4836
|
}
|
|
4292
4837
|
}
|
|
4293
4838
|
}
|
|
4294
|
-
await
|
|
4839
|
+
await execFileAsync4("git", ["worktree", "prune"], { cwd: repoRoot }).catch(() => {
|
|
4295
4840
|
});
|
|
4296
4841
|
if (fs12.existsSync(baseDir)) {
|
|
4297
4842
|
const remainingActive = await listActiveWorktrees(repoRoot);
|
|
@@ -4305,7 +4850,7 @@ async function cleanupStaleWorktrees(repoRoot, options = {}) {
|
|
|
4305
4850
|
}
|
|
4306
4851
|
}
|
|
4307
4852
|
}
|
|
4308
|
-
await
|
|
4853
|
+
await execFileAsync4("git", ["worktree", "prune"], { cwd: repoRoot }).catch(() => {
|
|
4309
4854
|
});
|
|
4310
4855
|
} catch {
|
|
4311
4856
|
}
|
|
@@ -4581,7 +5126,7 @@ function formatLoopGuardReport(options) {
|
|
|
4581
5126
|
|
|
4582
5127
|
// src/lib/runner.ts
|
|
4583
5128
|
import pc11 from "picocolors";
|
|
4584
|
-
var
|
|
5129
|
+
var execFileAsync5 = promisify5(execFile5);
|
|
4585
5130
|
var LineBufferedStreamParser = class {
|
|
4586
5131
|
buffer = "";
|
|
4587
5132
|
onLine;
|
|
@@ -4741,7 +5286,7 @@ _Running via Jonah Fleet CLI runner._`;
|
|
|
4741
5286
|
const tmpFile = path13.join(os2.tmpdir(), `jonah-fleet-run-issue-${Date.now()}.md`);
|
|
4742
5287
|
fs13.writeFileSync(tmpFile, body, "utf8");
|
|
4743
5288
|
try {
|
|
4744
|
-
const out =
|
|
5289
|
+
const out = execSync3(`gh issue create --title ${JSON.stringify(title)} --body-file ${JSON.stringify(tmpFile)} --label ${JSON.stringify(labels)}`, {
|
|
4745
5290
|
cwd,
|
|
4746
5291
|
encoding: "utf8",
|
|
4747
5292
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -4776,7 +5321,7 @@ function tryPostLocalRunMilestone(cwd, issueNumber, cardContent) {
|
|
|
4776
5321
|
const tmpFile = path13.join(os2.tmpdir(), `jonah-fleet-milestone-${issueNumber}-${Date.now()}.md`);
|
|
4777
5322
|
fs13.writeFileSync(tmpFile, cardContent, "utf8");
|
|
4778
5323
|
try {
|
|
4779
|
-
|
|
5324
|
+
execSync3(`gh issue comment ${issueNumber} --body-file ${JSON.stringify(tmpFile)}`, {
|
|
4780
5325
|
cwd,
|
|
4781
5326
|
stdio: ["ignore", "ignore", "ignore"]
|
|
4782
5327
|
});
|
|
@@ -4792,7 +5337,7 @@ async function tryPostLocalRunMilestoneAsync(cwd, issueNumber, cardContent) {
|
|
|
4792
5337
|
const tmpFile = path13.join(os2.tmpdir(), `jonah-fleet-milestone-${issueNumber}-${Date.now()}.md`);
|
|
4793
5338
|
try {
|
|
4794
5339
|
await fs13.promises.writeFile(tmpFile, cardContent, "utf8");
|
|
4795
|
-
await
|
|
5340
|
+
await execFileAsync5("gh", ["issue", "comment", String(issueNumber), "--body-file", tmpFile], {
|
|
4796
5341
|
cwd
|
|
4797
5342
|
});
|
|
4798
5343
|
return true;
|
|
@@ -4845,11 +5390,11 @@ _Generated by Jonah Fleet local runner on ${hostname}._`;
|
|
|
4845
5390
|
const tmpFile2 = path13.join(os2.tmpdir(), `jonah-fleet-run-${issueNumber}-${Date.now()}.md`);
|
|
4846
5391
|
fs13.writeFileSync(tmpFile2, finalBody2, "utf8");
|
|
4847
5392
|
try {
|
|
4848
|
-
|
|
5393
|
+
execSync3(`gh issue edit ${issueNumber} --body-file ${JSON.stringify(tmpFile2)}`, {
|
|
4849
5394
|
cwd,
|
|
4850
5395
|
stdio: ["ignore", "ignore", "ignore"]
|
|
4851
5396
|
});
|
|
4852
|
-
|
|
5397
|
+
execSync3(`gh issue edit ${issueNumber} --add-label "status:quota-paused" --remove-label "status:running"`, {
|
|
4853
5398
|
cwd,
|
|
4854
5399
|
stdio: ["ignore", "ignore", "ignore"]
|
|
4855
5400
|
});
|
|
@@ -4875,21 +5420,21 @@ _Generated by Jonah Fleet local runner on ${hostname}._`;
|
|
|
4875
5420
|
const tmpFile = path13.join(os2.tmpdir(), `jonah-fleet-run-${issueNumber}-${Date.now()}.md`);
|
|
4876
5421
|
fs13.writeFileSync(tmpFile, finalBody, "utf8");
|
|
4877
5422
|
try {
|
|
4878
|
-
|
|
5423
|
+
execSync3(`gh issue edit ${issueNumber} --body-file ${JSON.stringify(tmpFile)}`, {
|
|
4879
5424
|
cwd,
|
|
4880
5425
|
stdio: ["ignore", "ignore", "ignore"]
|
|
4881
5426
|
});
|
|
4882
5427
|
if (exitCode === 0) {
|
|
4883
|
-
|
|
5428
|
+
execSync3(`gh issue edit ${issueNumber} --add-label "status:success" --remove-label "status:running"`, {
|
|
4884
5429
|
cwd,
|
|
4885
5430
|
stdio: ["ignore", "ignore", "ignore"]
|
|
4886
5431
|
});
|
|
4887
|
-
|
|
5432
|
+
execSync3(`gh issue close ${issueNumber} --reason completed`, {
|
|
4888
5433
|
cwd,
|
|
4889
5434
|
stdio: ["ignore", "ignore", "ignore"]
|
|
4890
5435
|
});
|
|
4891
5436
|
} else {
|
|
4892
|
-
|
|
5437
|
+
execSync3(`gh issue edit ${issueNumber} --add-label "status:failure,needs-attention" --remove-label "status:running"`, {
|
|
4893
5438
|
cwd,
|
|
4894
5439
|
stdio: ["ignore", "ignore", "ignore"]
|
|
4895
5440
|
});
|
|
@@ -4911,7 +5456,7 @@ async function tryMarkLocalRunInterruptedAsync(cwd, issueNumber, hostname, reaso
|
|
|
4911
5456
|
step: "Local execution interrupted before completion"
|
|
4912
5457
|
});
|
|
4913
5458
|
await tryPostLocalRunMilestoneAsync(cwd, issueNumber, interruptionCard);
|
|
4914
|
-
await
|
|
5459
|
+
await execFileAsync5(
|
|
4915
5460
|
"gh",
|
|
4916
5461
|
["issue", "edit", String(issueNumber), "--add-label", "status:failure,needs-attention", "--remove-label", "status:running"],
|
|
4917
5462
|
{ cwd }
|
|
@@ -4940,8 +5485,8 @@ _Generated by Jonah Fleet local runner on ${hostname}._`;
|
|
|
4940
5485
|
const tmpFile2 = path13.join(os2.tmpdir(), `jonah-fleet-run-${issueNumber}-${Date.now()}.md`);
|
|
4941
5486
|
await fs13.promises.writeFile(tmpFile2, finalBody2, "utf8");
|
|
4942
5487
|
try {
|
|
4943
|
-
await
|
|
4944
|
-
await
|
|
5488
|
+
await execFileAsync5("gh", ["issue", "edit", String(issueNumber), "--body-file", tmpFile2], { cwd });
|
|
5489
|
+
await execFileAsync5("gh", ["issue", "edit", String(issueNumber), "--add-label", "status:quota-paused", "--remove-label", "status:running"], { cwd });
|
|
4945
5490
|
return true;
|
|
4946
5491
|
} finally {
|
|
4947
5492
|
try {
|
|
@@ -4967,22 +5512,22 @@ _Generated by Jonah Fleet local runner on ${hostname}._`;
|
|
|
4967
5512
|
const tmpFile = path13.join(os2.tmpdir(), `jonah-fleet-run-${issueNumber}-${Date.now()}.md`);
|
|
4968
5513
|
await fs13.promises.writeFile(tmpFile, finalBody, "utf8");
|
|
4969
5514
|
try {
|
|
4970
|
-
await
|
|
5515
|
+
await execFileAsync5("gh", ["issue", "edit", String(issueNumber), "--body-file", tmpFile], {
|
|
4971
5516
|
cwd
|
|
4972
5517
|
});
|
|
4973
5518
|
if (exitCode === 0) {
|
|
4974
|
-
await
|
|
5519
|
+
await execFileAsync5(
|
|
4975
5520
|
"gh",
|
|
4976
5521
|
["issue", "edit", String(issueNumber), "--add-label", "status:success", "--remove-label", "status:running"],
|
|
4977
5522
|
{ cwd }
|
|
4978
5523
|
);
|
|
4979
|
-
await
|
|
5524
|
+
await execFileAsync5(
|
|
4980
5525
|
"gh",
|
|
4981
5526
|
["issue", "close", String(issueNumber), "--reason", "completed"],
|
|
4982
5527
|
{ cwd }
|
|
4983
5528
|
);
|
|
4984
5529
|
} else {
|
|
4985
|
-
await
|
|
5530
|
+
await execFileAsync5(
|
|
4986
5531
|
"gh",
|
|
4987
5532
|
["issue", "edit", String(issueNumber), "--add-label", "status:failure,needs-attention", "--remove-label", "status:running"],
|
|
4988
5533
|
{ cwd }
|
|
@@ -5118,6 +5663,13 @@ function formatFallbackRunReport(options) {
|
|
|
5118
5663
|
}
|
|
5119
5664
|
}
|
|
5120
5665
|
}
|
|
5666
|
+
if (options.planQuota && options.planQuota.available) {
|
|
5667
|
+
const quotaSummary = formatPlanQuotaSummary(options.planQuota, { markdown: true });
|
|
5668
|
+
if (quotaSummary && quotaSummary !== "Plan Quota: Unavailable") {
|
|
5669
|
+
reportContent += `| Real Plan Quota | ${quotaSummary} |
|
|
5670
|
+
`;
|
|
5671
|
+
}
|
|
5672
|
+
}
|
|
5121
5673
|
if (options.exitCode !== 0) {
|
|
5122
5674
|
const errorChunks = [options.output?.trim(), options.stderr?.trim()].filter(Boolean);
|
|
5123
5675
|
const combinedError = errorChunks.join("\n");
|
|
@@ -5137,17 +5689,17 @@ ${sanitizedOutput}
|
|
|
5137
5689
|
async function preserveUncommittedWork(worktreePath, routine) {
|
|
5138
5690
|
try {
|
|
5139
5691
|
if (!fs13.existsSync(worktreePath)) return { preserved: false };
|
|
5140
|
-
const { stdout: statusOut } = await
|
|
5692
|
+
const { stdout: statusOut } = await execFileAsync5("git", ["status", "--porcelain"], { cwd: worktreePath });
|
|
5141
5693
|
const trimmed = statusOut.trim();
|
|
5142
5694
|
if (!trimmed) return { preserved: false };
|
|
5143
5695
|
const fileCount = trimmed.split("\n").filter(Boolean).length;
|
|
5144
|
-
await
|
|
5145
|
-
await
|
|
5696
|
+
await execFileAsync5("git", ["add", "-A"], { cwd: worktreePath });
|
|
5697
|
+
await execFileAsync5(
|
|
5146
5698
|
"git",
|
|
5147
5699
|
["commit", "-m", `wip(${routine}): preserve uncommitted work (${fileCount} files) before session termination`],
|
|
5148
5700
|
{ cwd: worktreePath }
|
|
5149
5701
|
);
|
|
5150
|
-
const { stdout: shaOut } = await
|
|
5702
|
+
const { stdout: shaOut } = await execFileAsync5("git", ["rev-parse", "--short", "HEAD"], { cwd: worktreePath });
|
|
5151
5703
|
return { preserved: true, commitSha: shaOut.trim(), fileCount };
|
|
5152
5704
|
} catch {
|
|
5153
5705
|
return { preserved: false };
|
|
@@ -5553,6 +6105,7 @@ Timeout: ${printTimeout}`,
|
|
|
5553
6105
|
exitCode = 1;
|
|
5554
6106
|
}
|
|
5555
6107
|
}
|
|
6108
|
+
const planQuota = await fetchActualPlanQuota({ repoRoot: targetDir });
|
|
5556
6109
|
if (loopGuardTrip) {
|
|
5557
6110
|
exitCode = 1;
|
|
5558
6111
|
reportContent = formatLoopGuardReport({
|
|
@@ -5583,6 +6136,7 @@ Timeout: ${printTimeout}`,
|
|
|
5583
6136
|
output,
|
|
5584
6137
|
stderr: [accumulatedStderr, prematureError].filter(Boolean).join("\n"),
|
|
5585
6138
|
usage: sessionUsage,
|
|
6139
|
+
planQuota,
|
|
5586
6140
|
repoRoot: targetDir
|
|
5587
6141
|
});
|
|
5588
6142
|
}
|
|
@@ -5605,7 +6159,13 @@ Timeout: ${printTimeout}`,
|
|
|
5605
6159
|
target: targetLabel,
|
|
5606
6160
|
issueNumber: routineIssueNumber,
|
|
5607
6161
|
durationMs,
|
|
5608
|
-
usage: sessionUsage
|
|
6162
|
+
usage: sessionUsage,
|
|
6163
|
+
planQuota: planQuota.available ? {
|
|
6164
|
+
gemini5hRemainingPct: planQuota.gemini5hRemainingPct,
|
|
6165
|
+
geminiWeeklyRemainingPct: planQuota.geminiWeeklyRemainingPct,
|
|
6166
|
+
claude5hRemainingPct: planQuota.claude5hRemainingPct,
|
|
6167
|
+
claudeWeeklyRemainingPct: planQuota.claudeWeeklyRemainingPct
|
|
6168
|
+
} : void 0
|
|
5609
6169
|
},
|
|
5610
6170
|
null,
|
|
5611
6171
|
2
|
|
@@ -5633,7 +6193,8 @@ Timeout: ${printTimeout}`,
|
|
|
5633
6193
|
pr: effectivePR,
|
|
5634
6194
|
title: targetTitle,
|
|
5635
6195
|
durationMs,
|
|
5636
|
-
usage: sessionUsage
|
|
6196
|
+
usage: sessionUsage,
|
|
6197
|
+
planQuota
|
|
5637
6198
|
}) + "\n"
|
|
5638
6199
|
);
|
|
5639
6200
|
} else {
|
|
@@ -5645,7 +6206,8 @@ Timeout: ${printTimeout}`,
|
|
|
5645
6206
|
issue: effectiveIssue,
|
|
5646
6207
|
pr: effectivePR,
|
|
5647
6208
|
durationMs,
|
|
5648
|
-
usage: sessionUsage
|
|
6209
|
+
usage: sessionUsage,
|
|
6210
|
+
planQuota
|
|
5649
6211
|
}) + "\n"
|
|
5650
6212
|
);
|
|
5651
6213
|
}
|
|
@@ -5664,6 +6226,7 @@ Timeout: ${printTimeout}`,
|
|
|
5664
6226
|
transientServiceError,
|
|
5665
6227
|
durationMs,
|
|
5666
6228
|
usage: sessionUsage,
|
|
6229
|
+
planQuota,
|
|
5667
6230
|
outcome: routine === "peer-review" && exitCode === 0 ? detectPeerReviewOutcome({
|
|
5668
6231
|
output,
|
|
5669
6232
|
prNumber: effectivePR,
|
|
@@ -5754,8 +6317,8 @@ import pc15 from "picocolors";
|
|
|
5754
6317
|
import fs15 from "fs";
|
|
5755
6318
|
import path15 from "path";
|
|
5756
6319
|
import os3 from "os";
|
|
5757
|
-
import { spawn as spawn2, execFile as
|
|
5758
|
-
import { promisify as
|
|
6320
|
+
import { spawn as spawn2, execFile as execFile6 } from "child_process";
|
|
6321
|
+
import { promisify as promisify6 } from "util";
|
|
5759
6322
|
|
|
5760
6323
|
// src/lib/daemon-keys.ts
|
|
5761
6324
|
import readline2 from "readline";
|
|
@@ -6144,7 +6707,7 @@ function formatDaemonStatusLine(options = {}) {
|
|
|
6144
6707
|
|
|
6145
6708
|
// src/lib/daemon.ts
|
|
6146
6709
|
import pc14 from "picocolors";
|
|
6147
|
-
var
|
|
6710
|
+
var execFileAsync6 = promisify6(execFile6);
|
|
6148
6711
|
function getDaemonStatePath(repoRoot) {
|
|
6149
6712
|
return path15.join(repoRoot, ".jonah-fleet", "daemon.json");
|
|
6150
6713
|
}
|
|
@@ -6322,7 +6885,7 @@ function filterReviewablePRs(prs, options = {}) {
|
|
|
6322
6885
|
}
|
|
6323
6886
|
async function getOpenReviewablePRs(repoRoot, options = {}) {
|
|
6324
6887
|
try {
|
|
6325
|
-
const { stdout } = await
|
|
6888
|
+
const { stdout } = await execFileAsync6(
|
|
6326
6889
|
"gh",
|
|
6327
6890
|
[
|
|
6328
6891
|
"pr",
|
|
@@ -6475,7 +7038,7 @@ function classifyBacklogIssues(issues, openPRs = []) {
|
|
|
6475
7038
|
}
|
|
6476
7039
|
async function getBacklogIssues(repoRoot) {
|
|
6477
7040
|
try {
|
|
6478
|
-
const { stdout } = await
|
|
7041
|
+
const { stdout } = await execFileAsync6(
|
|
6479
7042
|
"gh",
|
|
6480
7043
|
["issue", "list", "--state", "open", "--json", "number,title,labels,assignees,url", "--limit", "50"],
|
|
6481
7044
|
{ cwd: repoRoot, timeout: 5e3 }
|
|
@@ -6487,7 +7050,7 @@ async function getBacklogIssues(repoRoot) {
|
|
|
6487
7050
|
}
|
|
6488
7051
|
async function getOpenPRsForBacklog(repoRoot) {
|
|
6489
7052
|
try {
|
|
6490
|
-
const { stdout } = await
|
|
7053
|
+
const { stdout } = await execFileAsync6(
|
|
6491
7054
|
"gh",
|
|
6492
7055
|
["pr", "list", "--state", "open", "--json", "number,title,body,headRefName,isDraft,labels,assignees,url", "--limit", "50"],
|
|
6493
7056
|
{ cwd: repoRoot, timeout: 5e3 }
|
|
@@ -6565,7 +7128,7 @@ async function stopDaemon(repoRoot) {
|
|
|
6565
7128
|
}
|
|
6566
7129
|
async function queryOrphanedLocalRunIssues(repoRoot) {
|
|
6567
7130
|
try {
|
|
6568
|
-
const { stdout } = await
|
|
7131
|
+
const { stdout } = await execFileAsync6(
|
|
6569
7132
|
"gh",
|
|
6570
7133
|
["issue", "list", "--state", "open", "--label", "runner:local,status:running", "--json", "number,title,createdAt", "--limit", "20"],
|
|
6571
7134
|
{ cwd: repoRoot }
|