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/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 execFile2 } from "child_process";
1452
- import { promisify as promisify2 } from "util";
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(fullPath);
1809
- } else if (entry.isFile() && entry.name.endsWith(".md")) {
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(fullPath, "utf8");
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) summaries.push(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 execFileAsync2 = promisify2(execFile2);
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 execFileAsync2("gh", ["pr", "view", prMatch[1], "--json", "title", "-q", ".title"], {
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 execFileAsync2("gh", ["issue", "view", issueMatch[1], "--json", "title", "-q", ".title"], {
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
- if (typeof total !== "number") return [];
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
- 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"));
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
- execSync("gh auth status", { stdio: "pipe" });
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 execSync2, execFile as execFile4 } from "child_process";
4172
- import { promisify as promisify4 } from "util";
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 execFile3 } from "child_process";
4178
- import { promisify as promisify3 } from "util";
4179
- var execFileAsync3 = promisify3(execFile3);
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 execFileAsync3("git", ["worktree", "remove", "--force", worktreePath], { cwd: repoRoot });
4735
+ await execFileAsync4("git", ["worktree", "remove", "--force", worktreePath], { cwd: repoRoot });
4191
4736
  } catch {
4192
4737
  fs12.rmSync(worktreePath, { recursive: true, force: true });
4193
- await execFileAsync3("git", ["worktree", "prune"], { cwd: repoRoot }).catch(() => {
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 execFileAsync3("git", ["rev-parse", "--verify", candidate], { cwd: repoRoot });
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 execFileAsync3("git", ["branch", "-D", options.branchName], { cwd: repoRoot });
4756
+ await execFileAsync4("git", ["branch", "-D", options.branchName], { cwd: repoRoot });
4212
4757
  } catch {
4213
4758
  }
4214
- await execFileAsync3("git", ["worktree", "add", worktreePath, "-b", options.branchName, baseRef], {
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 execFileAsync3("git", ["worktree", "remove", "--force", worktreePath], { cwd: repoRoot });
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 execFileAsync3("git", ["worktree", "prune"], { cwd: repoRoot }).catch(() => {
4774
+ await execFileAsync4("git", ["worktree", "prune"], { cwd: repoRoot }).catch(() => {
4230
4775
  });
4231
4776
  }
4232
4777
  if (options.deleteBranch && options.branchName) {
4233
- await execFileAsync3("git", ["branch", "-D", options.branchName], { cwd: repoRoot }).catch(() => {
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 execFileAsync3("git", ["worktree", "list", "--porcelain"], { cwd: repoRoot });
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 execFileAsync3("git", ["worktree", "remove", "--force", wt.path], { cwd: repoRoot });
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 execFileAsync3("git", ["worktree", "prune"], { cwd: repoRoot }).catch(() => {
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 execFileAsync3("git", ["worktree", "prune"], { cwd: repoRoot }).catch(() => {
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 execFileAsync4 = promisify4(execFile4);
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 = execSync2(`gh issue create --title ${JSON.stringify(title)} --body-file ${JSON.stringify(tmpFile)} --label ${JSON.stringify(labels)}`, {
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
- execSync2(`gh issue comment ${issueNumber} --body-file ${JSON.stringify(tmpFile)}`, {
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 execFileAsync4("gh", ["issue", "comment", String(issueNumber), "--body-file", tmpFile], {
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
- execSync2(`gh issue edit ${issueNumber} --body-file ${JSON.stringify(tmpFile2)}`, {
5393
+ execSync3(`gh issue edit ${issueNumber} --body-file ${JSON.stringify(tmpFile2)}`, {
4849
5394
  cwd,
4850
5395
  stdio: ["ignore", "ignore", "ignore"]
4851
5396
  });
4852
- execSync2(`gh issue edit ${issueNumber} --add-label "status:quota-paused" --remove-label "status:running"`, {
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
- execSync2(`gh issue edit ${issueNumber} --body-file ${JSON.stringify(tmpFile)}`, {
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
- execSync2(`gh issue edit ${issueNumber} --add-label "status:success" --remove-label "status:running"`, {
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
- execSync2(`gh issue close ${issueNumber} --reason completed`, {
5432
+ execSync3(`gh issue close ${issueNumber} --reason completed`, {
4888
5433
  cwd,
4889
5434
  stdio: ["ignore", "ignore", "ignore"]
4890
5435
  });
4891
5436
  } else {
4892
- execSync2(`gh issue edit ${issueNumber} --add-label "status:failure,needs-attention" --remove-label "status:running"`, {
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 execFileAsync4(
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 execFileAsync4("gh", ["issue", "edit", String(issueNumber), "--body-file", tmpFile2], { cwd });
4944
- await execFileAsync4("gh", ["issue", "edit", String(issueNumber), "--add-label", "status:quota-paused", "--remove-label", "status:running"], { cwd });
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 execFileAsync4("gh", ["issue", "edit", String(issueNumber), "--body-file", tmpFile], {
5515
+ await execFileAsync5("gh", ["issue", "edit", String(issueNumber), "--body-file", tmpFile], {
4971
5516
  cwd
4972
5517
  });
4973
5518
  if (exitCode === 0) {
4974
- await execFileAsync4(
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 execFileAsync4(
5524
+ await execFileAsync5(
4980
5525
  "gh",
4981
5526
  ["issue", "close", String(issueNumber), "--reason", "completed"],
4982
5527
  { cwd }
4983
5528
  );
4984
5529
  } else {
4985
- await execFileAsync4(
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 execFileAsync4("git", ["status", "--porcelain"], { cwd: worktreePath });
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 execFileAsync4("git", ["add", "-A"], { cwd: worktreePath });
5145
- await execFileAsync4(
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 execFileAsync4("git", ["rev-parse", "--short", "HEAD"], { cwd: worktreePath });
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 execFile5 } from "child_process";
5758
- import { promisify as promisify5 } from "util";
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 execFileAsync5 = promisify5(execFile5);
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 execFileAsync5(
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 execFileAsync5(
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 execFileAsync5(
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 execFileAsync5(
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 }