jonah-fleet 1.33.0 → 1.35.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
@@ -1461,6 +1461,23 @@ import pc from "picocolors";
1461
1461
  var execFileAsync2 = promisify2(execFile2);
1462
1462
  var GLOBAL_WEEKLY_TOKEN_BUDGET = 875e4;
1463
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
+ }
1464
1481
  function formatQuotaStatusBadge(status) {
1465
1482
  if (status === "HEALTHY") return pc.green("[HEALTHY]");
1466
1483
  if (status === "WARNING") return pc.yellow("[WARNING]");
@@ -1776,36 +1793,133 @@ async function fetchActualPlanQuota(options = {}) {
1776
1793
  };
1777
1794
  }
1778
1795
  }
1796
+ function checkPlanQuotaDepletion(quota, options = {}) {
1797
+ const threshold = options.thresholdPct ?? 20;
1798
+ if (!quota || !quota.available) {
1799
+ return { depleted: false, thresholdPct: threshold };
1800
+ }
1801
+ const model = options.model?.toLowerCase() || "";
1802
+ const isClaude = model.includes("claude") || model.includes("gpt") || model.includes("openai") || model.includes("anthropic");
1803
+ let groupName = isClaude ? "Claude and GPT models" : "Gemini Models";
1804
+ let weeklyPct = isClaude ? quota.claudeWeeklyRemainingPct : quota.geminiWeeklyRemainingPct;
1805
+ let weeklyReset = isClaude ? quota.claudeWeeklyResetTime : quota.geminiWeeklyResetTime;
1806
+ let window5hPct = isClaude ? quota.claude5hRemainingPct : quota.gemini5hRemainingPct;
1807
+ let window5hReset = isClaude ? quota.claude5hResetTime : quota.gemini5hResetTime;
1808
+ if (quota.groups) {
1809
+ const targetGroup = quota.groups[groupName];
1810
+ if (targetGroup) {
1811
+ if (weeklyPct === void 0) {
1812
+ weeklyPct = targetGroup.weeklyRemainingPct;
1813
+ weeklyReset = targetGroup.weeklyResetTime;
1814
+ }
1815
+ if (window5hPct === void 0) {
1816
+ window5hPct = targetGroup.window5hRemainingPct;
1817
+ window5hReset = targetGroup.window5hResetTime;
1818
+ }
1819
+ if (weeklyPct === void 0 || window5hPct === void 0) {
1820
+ for (const b of targetGroup.buckets || []) {
1821
+ if ((b.window === "5h" || b.id.includes("5h")) && window5hPct === void 0) {
1822
+ window5hPct = b.remainingPercentage;
1823
+ window5hReset = b.resetTime;
1824
+ } else if ((b.window === "weekly" || b.id.includes("weekly")) && weeklyPct === void 0) {
1825
+ weeklyPct = b.remainingPercentage;
1826
+ weeklyReset = b.resetTime;
1827
+ }
1828
+ }
1829
+ }
1830
+ } else if (weeklyPct === void 0 && window5hPct === void 0) {
1831
+ const groupKeys = Object.keys(quota.groups);
1832
+ if (groupKeys.length > 0) {
1833
+ const fallbackGroup = quota.groups[groupKeys[0]];
1834
+ if (fallbackGroup) {
1835
+ groupName = fallbackGroup.name;
1836
+ weeklyPct = fallbackGroup.weeklyRemainingPct;
1837
+ weeklyReset = fallbackGroup.weeklyResetTime;
1838
+ window5hPct = fallbackGroup.window5hRemainingPct;
1839
+ window5hReset = fallbackGroup.window5hResetTime;
1840
+ }
1841
+ }
1842
+ }
1843
+ }
1844
+ const is5hDepleted = window5hPct !== void 0 && window5hPct < threshold;
1845
+ const isWeeklyDepleted = weeklyPct !== void 0 && weeklyPct < threshold;
1846
+ if (is5hDepleted || isWeeklyDepleted) {
1847
+ if (is5hDepleted && isWeeklyDepleted) {
1848
+ const lowerWindow = window5hPct <= weeklyPct ? "5h" : "7days";
1849
+ const remainingPct = Math.min(window5hPct, weeklyPct);
1850
+ const resetTime = lowerWindow === "5h" ? window5hReset : weeklyReset;
1851
+ return {
1852
+ depleted: true,
1853
+ thresholdPct: threshold,
1854
+ window: lowerWindow,
1855
+ remainingPct,
1856
+ resetTime,
1857
+ groupName,
1858
+ message: `${groupName} quota is below ${threshold}% in both 5h (${window5hPct.toFixed(1)}%) and 7days (${weeklyPct.toFixed(1)}%) windows`
1859
+ };
1860
+ }
1861
+ if (is5hDepleted) {
1862
+ return {
1863
+ depleted: true,
1864
+ thresholdPct: threshold,
1865
+ window: "5h",
1866
+ remainingPct: window5hPct,
1867
+ resetTime: window5hReset,
1868
+ groupName,
1869
+ message: `${groupName} 5h quota is at ${window5hPct.toFixed(1)}% remaining (<${threshold}% floor)`
1870
+ };
1871
+ }
1872
+ return {
1873
+ depleted: true,
1874
+ thresholdPct: threshold,
1875
+ window: "7days",
1876
+ remainingPct: weeklyPct,
1877
+ resetTime: weeklyReset,
1878
+ groupName,
1879
+ message: `${groupName} 7days weekly quota is at ${weeklyPct.toFixed(1)}% remaining (<${threshold}% floor)`
1880
+ };
1881
+ }
1882
+ return { depleted: false, thresholdPct: threshold };
1883
+ }
1779
1884
  function formatPlanQuotaSummary(quota, options = {}) {
1780
1885
  if (!quota || !quota.available) {
1781
1886
  return "Plan Quota: Unavailable";
1782
1887
  }
1783
1888
  const parts = [];
1784
1889
  const fmt = (pct) => options.markdown ? `\`${pct.toFixed(1)}% remaining\`` : `${pct.toFixed(1)}% remaining`;
1890
+ const geminiUnused = (quota.gemini5hRemainingPct === void 0 || quota.gemini5hRemainingPct === 100) && (quota.geminiWeeklyRemainingPct === void 0 || quota.geminiWeeklyRemainingPct === 100);
1891
+ const claudeUnused = (quota.claude5hRemainingPct === void 0 || quota.claude5hRemainingPct === 100) && (quota.claudeWeeklyRemainingPct === void 0 || quota.claudeWeeklyRemainingPct === 100);
1892
+ const allProvidersUnused = geminiUnused && claudeUnused;
1893
+ const shouldDisplayGemini = !options.hideUnused || !geminiUnused || allProvidersUnused;
1894
+ const shouldDisplayClaude = !options.hideUnused || !claudeUnused;
1785
1895
  if (quota.gemini5hRemainingPct !== void 0 || quota.geminiWeeklyRemainingPct !== void 0) {
1786
- const subParts = [];
1787
- if (quota.gemini5hRemainingPct !== void 0) {
1788
- subParts.push(`Gemini 5h: ${fmt(quota.gemini5hRemainingPct)}`);
1789
- }
1790
- if (quota.geminiWeeklyRemainingPct !== void 0) {
1791
- const label = quota.gemini5hRemainingPct !== void 0 ? "Weekly" : "Gemini Weekly";
1792
- subParts.push(`${label}: ${fmt(quota.geminiWeeklyRemainingPct)}`);
1793
- }
1794
- if (subParts.length > 0) {
1795
- parts.push(subParts.join(" \xB7 "));
1896
+ if (shouldDisplayGemini) {
1897
+ const subParts = [];
1898
+ if (quota.gemini5hRemainingPct !== void 0) {
1899
+ subParts.push(`Gemini 5h: ${fmt(quota.gemini5hRemainingPct)}`);
1900
+ }
1901
+ if (quota.geminiWeeklyRemainingPct !== void 0) {
1902
+ const label = quota.gemini5hRemainingPct !== void 0 ? "Weekly" : "Gemini Weekly";
1903
+ subParts.push(`${label}: ${fmt(quota.geminiWeeklyRemainingPct)}`);
1904
+ }
1905
+ if (subParts.length > 0) {
1906
+ parts.push(subParts.join(" \xB7 "));
1907
+ }
1796
1908
  }
1797
1909
  }
1798
1910
  if (quota.claude5hRemainingPct !== void 0 || quota.claudeWeeklyRemainingPct !== void 0) {
1799
- const subParts = [];
1800
- if (quota.claude5hRemainingPct !== void 0) {
1801
- subParts.push(`Claude/GPT 5h: ${fmt(quota.claude5hRemainingPct)}`);
1802
- }
1803
- if (quota.claudeWeeklyRemainingPct !== void 0) {
1804
- const label = quota.claude5hRemainingPct !== void 0 ? "Weekly" : "Claude/GPT Weekly";
1805
- subParts.push(`${label}: ${fmt(quota.claudeWeeklyRemainingPct)}`);
1806
- }
1807
- if (subParts.length > 0) {
1808
- parts.push(subParts.join(" \xB7 "));
1911
+ if (!options.hideUnused || !claudeUnused) {
1912
+ const subParts = [];
1913
+ if (quota.claude5hRemainingPct !== void 0) {
1914
+ subParts.push(`Claude/GPT 5h: ${fmt(quota.claude5hRemainingPct)}`);
1915
+ }
1916
+ if (quota.claudeWeeklyRemainingPct !== void 0) {
1917
+ const label = quota.claude5hRemainingPct !== void 0 ? "Weekly" : "Claude/GPT Weekly";
1918
+ subParts.push(`${label}: ${fmt(quota.claudeWeeklyRemainingPct)}`);
1919
+ }
1920
+ if (subParts.length > 0) {
1921
+ parts.push(subParts.join(" \xB7 "));
1922
+ }
1809
1923
  }
1810
1924
  }
1811
1925
  return parts.join(options.markdown ? "<br>" : "\n");
@@ -1876,6 +1990,17 @@ function parseLogToTelemetry(content, options = {}) {
1876
1990
  );
1877
1991
  questionsAskedCount = questionLines.length > 0 ? questionLines.length : 2;
1878
1992
  }
1993
+ let targetPr;
1994
+ const rawTarget = metadata["target"] || metadata["target / context"] || metadata["pr"];
1995
+ if (rawTarget) {
1996
+ const prMatch = rawTarget.match(/PR\s*#?(\d+)/i);
1997
+ if (prMatch) targetPr = parseInt(prMatch[1], 10);
1998
+ }
1999
+ let commitSha = metadata["commit"] || metadata["commit sha"] || metadata["head sha"] || metadata["sha"];
2000
+ if (!commitSha) {
2001
+ const shaMatch = content.match(/(?:commit|head\s+sha|head\s+commit)[:\s]+`?([0-9a-f]{7,40})`?/i);
2002
+ if (shaMatch) commitSha = shaMatch[1];
2003
+ }
1879
2004
  return {
1880
2005
  schemaVersion: "1.0.0",
1881
2006
  routine,
@@ -1896,7 +2021,9 @@ function parseLogToTelemetry(content, options = {}) {
1896
2021
  promptSha,
1897
2022
  ambiguityGateTriggered: ambiguityGateTriggered || void 0,
1898
2023
  questionsAskedCount,
1899
- needsInfoApplied: needsInfoApplied || void 0
2024
+ needsInfoApplied: needsInfoApplied || void 0,
2025
+ targetPr,
2026
+ commitSha
1900
2027
  };
1901
2028
  }
1902
2029
  function checkWeeklyBudgetLimit(usedTokens, ceilingTokens = GLOBAL_WEEKLY_TOKEN_BUDGET) {
@@ -2059,15 +2186,72 @@ function collectLocalTelemetryLogs(dir, repositoryName = "local") {
2059
2186
  const traverse = (currentDir) => {
2060
2187
  if (!fs6.existsSync(currentDir)) return;
2061
2188
  const entries = fs6.readdirSync(currentDir, { withFileTypes: true });
2189
+ const filesByBase = /* @__PURE__ */ new Map();
2062
2190
  for (const entry of entries) {
2063
- const fullPath = path6.join(currentDir, entry.name);
2064
2191
  if (entry.isDirectory()) {
2065
- traverse(fullPath);
2066
- } else if (entry.isFile() && entry.name.endsWith(".md")) {
2192
+ traverse(path6.join(currentDir, entry.name));
2193
+ } else if (entry.isFile()) {
2194
+ if (entry.name.endsWith(".md")) {
2195
+ const base = entry.name.slice(0, -3);
2196
+ const current = filesByBase.get(base) || {};
2197
+ current.md = path6.join(currentDir, entry.name);
2198
+ filesByBase.set(base, current);
2199
+ } else if (entry.name.endsWith(".json")) {
2200
+ const base = entry.name.slice(0, -5);
2201
+ const current = filesByBase.get(base) || {};
2202
+ current.json = path6.join(currentDir, entry.name);
2203
+ filesByBase.set(base, current);
2204
+ }
2205
+ }
2206
+ }
2207
+ for (const [, paths] of filesByBase) {
2208
+ let parsed = false;
2209
+ if (paths.json) {
2067
2210
  try {
2068
- const content = fs6.readFileSync(fullPath, "utf8");
2211
+ const content = fs6.readFileSync(paths.json, "utf8");
2212
+ const data = JSON.parse(content);
2213
+ if (data.routine && data.timestamp) {
2214
+ const inTokens = data.usage?.inputTokens ?? data.usage?.input_tokens ?? data.inputTokens ?? 0;
2215
+ const outTokens = data.usage?.outputTokens ?? data.usage?.output_tokens ?? data.outputTokens ?? 0;
2216
+ const totTokens = data.usage?.totalTokens ?? data.usage?.total_tokens ?? data.totalTokens ?? inTokens + outTokens;
2217
+ const durationSec = data.durationMs ? Math.round(data.durationMs / 1e3) : data.durationSeconds ?? void 0;
2218
+ const result = data.result ?? (data.success === true ? "SUCCESS" : data.success === false ? "FAILURE" : "UNKNOWN");
2219
+ 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);
2220
+ const commitSha = data.commitSha ?? data.headRefOid ?? data.headSha ?? (typeof data.commit === "string" ? data.commit : data.commit?.oid);
2221
+ summaries.push({
2222
+ schemaVersion: "1.0.0",
2223
+ routine: data.routine,
2224
+ repository: repositoryName,
2225
+ timestamp: data.timestamp,
2226
+ result,
2227
+ errorReason: data.errorReason || data.error,
2228
+ failureCategory: data.failureCategory,
2229
+ inputTokens: inTokens,
2230
+ outputTokens: outTokens,
2231
+ totalTokens: totTokens,
2232
+ estimatedCost: data.estimatedCost ?? totTokens / 1e6 * 0.35,
2233
+ durationSeconds: durationSec,
2234
+ iterationsUsed: data.iterationsUsed,
2235
+ maxIterations: data.maxIterations,
2236
+ ambiguityGateTriggered: Boolean(data.ambiguityGateTriggered),
2237
+ questionsAskedCount: data.questionsAskedCount || 0,
2238
+ needsInfoApplied: Boolean(data.needsInfoApplied),
2239
+ targetPr,
2240
+ commitSha
2241
+ });
2242
+ parsed = true;
2243
+ }
2244
+ } catch {
2245
+ }
2246
+ }
2247
+ if (!parsed && paths.md) {
2248
+ try {
2249
+ const content = fs6.readFileSync(paths.md, "utf8");
2069
2250
  const summary = parseLogToTelemetry(content, { repository: repositoryName });
2070
- if (summary) summaries.push(summary);
2251
+ if (summary) {
2252
+ summaries.push(summary);
2253
+ parsed = true;
2254
+ }
2071
2255
  } catch {
2072
2256
  }
2073
2257
  }
@@ -2159,6 +2343,129 @@ function renderProgressBar(percentage, width = 25) {
2159
2343
  if (clamped >= 70) return pc.yellow(filledBar) + pc.gray(emptyBar);
2160
2344
  return pc.green(filledBar) + pc.gray(emptyBar);
2161
2345
  }
2346
+ function detectTokenAnomalies(telemetry, options = {}) {
2347
+ const anomalies = [];
2348
+ for (const [routineName, r] of Object.entries(telemetry.byRoutine)) {
2349
+ const ratio = r.totalInputTokens / Math.max(1, r.totalOutputTokens);
2350
+ if (ratio > TOKEN_ANOMALY_THRESHOLDS.CONTEXT_ASYMMETRY_RATIO && r.totalInputTokens > TOKEN_ANOMALY_THRESHOLDS.CONTEXT_ASYMMETRY_MIN_INPUT_TOKENS) {
2351
+ anomalies.push({
2352
+ type: "context_asymmetry",
2353
+ severity: "WARNING",
2354
+ routine: routineName,
2355
+ 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).`,
2356
+ remediation: `Apply progressive disclosure and skill pruning. Scope skills strictly per routine rather than injecting all skills globally.`
2357
+ });
2358
+ }
2359
+ }
2360
+ if (telemetry.totalTokens > 0) {
2361
+ for (const [routineName, r] of Object.entries(telemetry.byRoutine)) {
2362
+ const share = r.totalTokens / telemetry.totalTokens * 100;
2363
+ if (share > TOKEN_ANOMALY_THRESHOLDS.BUDGET_HOG_PERCENT && Object.keys(telemetry.byRoutine).length > 1) {
2364
+ anomalies.push({
2365
+ type: "budget_hog",
2366
+ severity: "CRITICAL",
2367
+ routine: routineName,
2368
+ message: `Budget Hog on '${routineName}': Consumes ${share.toFixed(1)}% of total fleet token allowance.`,
2369
+ remediation: `Throttle dispatch cadence, introduce stricter pre-qualification filters, or add early exit guards.`
2370
+ });
2371
+ }
2372
+ }
2373
+ }
2374
+ if (telemetry.totalTokens > 0 && Object.keys(telemetry.byRoutine).length >= TOKEN_ANOMALY_THRESHOLDS.MULTI_ROUTINE_MIN_ROUTINES) {
2375
+ const sortedRoutines = Object.values(telemetry.byRoutine).sort(
2376
+ (a, b) => b.totalTokens - a.totalTokens
2377
+ );
2378
+ const top2Spend = (sortedRoutines[0]?.totalTokens || 0) + (sortedRoutines[1]?.totalTokens || 0);
2379
+ const top2Share = top2Spend / telemetry.totalTokens * 100;
2380
+ if (top2Share > TOKEN_ANOMALY_THRESHOLDS.MULTI_ROUTINE_CANNIBALIZATION_PERCENT) {
2381
+ anomalies.push({
2382
+ type: "multi_routine_cannibalization",
2383
+ severity: "WARNING",
2384
+ message: `Multi-Routine Budget Cannibalization: Top 2 routines ('${sortedRoutines[0]?.routine}' and '${sortedRoutines[1]?.routine}') consume ${top2Share.toFixed(1)}% of fleet tokens.`,
2385
+ remediation: `Rebalance dispatch pacing and coordinate candidate skips across authoring and review routines.`
2386
+ });
2387
+ }
2388
+ }
2389
+ const prShaEvaluations = /* @__PURE__ */ new Map();
2390
+ for (const e of telemetry.events || []) {
2391
+ if (e.targetPr && e.commitSha) {
2392
+ const key = `${e.targetPr}:${e.commitSha}`;
2393
+ const existing = prShaEvaluations.get(key);
2394
+ if (existing) {
2395
+ existing.count++;
2396
+ } else {
2397
+ prShaEvaluations.set(key, { pr: e.targetPr, sha: e.commitSha, count: 1, routine: e.routine });
2398
+ }
2399
+ }
2400
+ }
2401
+ for (const { pr, sha, count, routine } of prShaEvaluations.values()) {
2402
+ if (count >= TOKEN_ANOMALY_THRESHOLDS.RE_REVIEW_THRASH_THRESHOLD) {
2403
+ anomalies.push({
2404
+ type: "unilateral_rereview_thrash",
2405
+ severity: "WARNING",
2406
+ routine,
2407
+ message: `Unilateral Re-Review Thrash on PR #${pr}: Evaluated ${count} times on identical commit SHA ${sha.slice(0, 7)} without intervening commits.`,
2408
+ remediation: `Enforce persistent review commit state and failure cooldowns (e.g. daemon.json review state cache).`
2409
+ });
2410
+ }
2411
+ }
2412
+ for (const [routineName, r] of Object.entries(telemetry.byRoutine)) {
2413
+ if (r.runCount >= 3) {
2414
+ const tokenLimitFailures = (telemetry.events || []).filter(
2415
+ (e) => e.routine === routineName && e.failureCategory === "token_limit"
2416
+ ).length;
2417
+ const failPct = tokenLimitFailures / r.runCount * 100;
2418
+ if (failPct > TOKEN_ANOMALY_THRESHOLDS.ITERATION_EXHAUSTION_PERCENT) {
2419
+ anomalies.push({
2420
+ type: "iteration_exhaustion",
2421
+ severity: "WARNING",
2422
+ routine: routineName,
2423
+ message: `Iteration Ceiling Exhaustion on '${routineName}': ${failPct.toFixed(1)}% of runs terminate at max iteration/token limit.`,
2424
+ remediation: `Decompose tasks vertically into smaller slices and tighten pre-ready self-audits.`
2425
+ });
2426
+ }
2427
+ }
2428
+ }
2429
+ if (options.actualQuota?.available) {
2430
+ const geminiWeekly = options.actualQuota.geminiWeeklyRemainingPct;
2431
+ const gemini5h = options.actualQuota.gemini5hRemainingPct;
2432
+ if (geminiWeekly !== void 0 && geminiWeekly < TOKEN_ANOMALY_THRESHOLDS.PLAN_QUOTA_WEEKLY_WARNING_PERCENT) {
2433
+ anomalies.push({
2434
+ type: "plan_quota_velocity",
2435
+ severity: geminiWeekly < TOKEN_ANOMALY_THRESHOLDS.PLAN_QUOTA_WEEKLY_CRITICAL_PERCENT ? "CRITICAL" : "WARNING",
2436
+ message: `Plan Quota Burn Velocity: Gemini weekly plan quota is at ${geminiWeekly.toFixed(1)}% remaining (<${TOKEN_ANOMALY_THRESHOLDS.PLAN_QUOTA_WEEKLY_WARNING_PERCENT}% threshold).`,
2437
+ remediation: `Downgrade auxiliary routines ('peer-review', 'optimizer') to medium reasoning effort and standard Flash models (gemini-3.8-flash-medium).`
2438
+ });
2439
+ }
2440
+ if (gemini5h !== void 0 && gemini5h < TOKEN_ANOMALY_THRESHOLDS.PLAN_QUOTA_5H_CRITICAL_PERCENT) {
2441
+ anomalies.push({
2442
+ type: "plan_quota_velocity",
2443
+ severity: "CRITICAL",
2444
+ 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).`,
2445
+ remediation: `Throttle active agent execution to prevent rate limit lockout during the current 5-hour window.`
2446
+ });
2447
+ }
2448
+ const claudeWeekly = options.actualQuota.claudeWeeklyRemainingPct;
2449
+ const claude5h = options.actualQuota.claude5hRemainingPct;
2450
+ if (claudeWeekly !== void 0 && claudeWeekly < TOKEN_ANOMALY_THRESHOLDS.PLAN_QUOTA_WEEKLY_WARNING_PERCENT) {
2451
+ anomalies.push({
2452
+ type: "plan_quota_velocity",
2453
+ severity: claudeWeekly < TOKEN_ANOMALY_THRESHOLDS.PLAN_QUOTA_WEEKLY_CRITICAL_PERCENT ? "CRITICAL" : "WARNING",
2454
+ 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).`,
2455
+ remediation: `Conserve Claude/GPT quota for complex architectural reasoning; route standard tasks to Gemini Flash.`
2456
+ });
2457
+ }
2458
+ if (claude5h !== void 0 && claude5h < TOKEN_ANOMALY_THRESHOLDS.PLAN_QUOTA_5H_CRITICAL_PERCENT) {
2459
+ anomalies.push({
2460
+ type: "plan_quota_velocity",
2461
+ severity: "CRITICAL",
2462
+ 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).`,
2463
+ remediation: `Throttle active agent execution to prevent Claude/GPT rate limit lockout during the current 5-hour window.`
2464
+ });
2465
+ }
2466
+ }
2467
+ return anomalies;
2468
+ }
2162
2469
  function renderTelemetryDashboard(telemetry, options = {}) {
2163
2470
  if (options.json) {
2164
2471
  return JSON.stringify(telemetry, null, 2);
@@ -2187,8 +2494,9 @@ function renderTelemetryDashboard(telemetry, options = {}) {
2187
2494
  lines.push("\n" + pc.bold("\u{1F916} Spend by Agent Routine:"));
2188
2495
  for (const [routineName, r] of Object.entries(telemetry.byRoutine)) {
2189
2496
  const costStr = pc.green(`$${r.totalEstimatedCost.toFixed(2)}`);
2497
+ const ioRatioStr = formatIoRatio(r.totalInputTokens, r.totalOutputTokens);
2190
2498
  lines.push(
2191
- ` \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)}`
2499
+ ` \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)}`
2192
2500
  );
2193
2501
  }
2194
2502
  if (Object.keys(telemetry.byRepository).length > 0) {
@@ -2213,6 +2521,15 @@ function renderTelemetryDashboard(telemetry, options = {}) {
2213
2521
  lines.push(
2214
2522
  ` \u2022 Est. Wasted Tokens Averted: ~${pc.bold(pc.green(formatTokens(amb.estimatedTokensSaved)))} tokens (avoided speculative builds)`
2215
2523
  );
2524
+ const anomalies = detectTokenAnomalies(telemetry, { actualQuota: options.actualQuota });
2525
+ if (anomalies.length > 0) {
2526
+ lines.push("\n" + pc.bold(pc.yellow("\u{1F6A8} Detected Token & Pacing Anomalies:")));
2527
+ for (const anom of anomalies) {
2528
+ const badge = anom.severity === "CRITICAL" ? pc.red("[CRITICAL]") : pc.yellow("[WARNING]");
2529
+ lines.push(` ${badge} ${anom.message}`);
2530
+ lines.push(pc.gray(` \u21B3 Remediation: ${anom.remediation}`));
2531
+ }
2532
+ }
2216
2533
  const failKeys = Object.keys(telemetry.failureCategories);
2217
2534
  if (failKeys.length > 0) {
2218
2535
  lines.push("\n" + pc.bold(pc.red("\u26A0\uFE0F Failure Categories Breakdown:")));
@@ -3176,29 +3493,37 @@ function renderTokenQuotaSection(options) {
3176
3493
  const lines = [];
3177
3494
  lines.push(border(`\u251C${horizontal}\u2524`));
3178
3495
  if (hasUsage) {
3179
- const quota = calculateTokenQuotaPercentages(total);
3180
3496
  const breakdown = formatTokenBreakdown(options.usage, "prefix");
3181
- const tokenLine1 = ` Tokens: ${pc2.bold(total.toLocaleString())}${breakdown ? pc2.dim(` (${breakdown})`) : ""}`;
3497
+ const inTok = options.usage?.inputTokens;
3498
+ const outTok = options.usage?.outputTokens;
3499
+ let ioStr = "";
3500
+ if (typeof inTok === "number" && typeof outTok === "number" && outTok > 0) {
3501
+ ioStr = ` \xB7 I/O: ${formatIoRatio(inTok, outTok)}`;
3502
+ }
3503
+ const tokenLine1 = ` Tokens: ${pc2.bold(total.toLocaleString())}${breakdown ? pc2.dim(` (${breakdown})`) : ""}${ioStr ? pc2.cyan(ioStr) : ""}`;
3182
3504
  lines.push(border("\u2502") + tokenLine1 + " ".repeat(Math.max(1, width - 2 - stripAnsi(tokenLine1).length)) + border("\u2502"));
3183
- const tokenLine2 = ` \u21B3 ${pc2.cyan(quota.pctOf5hLimit.toFixed(2) + "% of 5h limit")} \xB7 ${pc2.cyan(quota.pctOfWeeklyLimit.toFixed(2) + "% of weekly limit")}`;
3184
- lines.push(border("\u2502") + tokenLine2 + " ".repeat(Math.max(1, width - 2 - stripAnsi(tokenLine2).length)) + border("\u2502"));
3185
- if (options.repoRoot) {
3186
- try {
3187
- const rolling = getRollingWindowTokenUsage(options.repoRoot);
3188
- if (rolling.windowTokens > 0 || rolling.weeklyTokens > 0) {
3189
- const badge5h = formatQuotaStatusBadge(rolling.windowStatus);
3190
- const badgeWeekly = formatQuotaStatusBadge(rolling.weeklyStatus);
3191
- const rollingLine1 = ` Rolling 5h: ${rolling.windowTokens.toLocaleString()} / ${rolling.windowLimit.toLocaleString()} tokens (${rolling.windowPercentage.toFixed(1)}% \xB7 ${badge5h})`;
3192
- lines.push(border("\u2502") + rollingLine1 + " ".repeat(Math.max(1, width - 2 - stripAnsi(rollingLine1).length)) + border("\u2502"));
3193
- const rollingLine2 = ` Rolling 7d: ${rolling.weeklyTokens.toLocaleString()} / ${rolling.weeklyLimit.toLocaleString()} tokens (${rolling.weeklyPercentage.toFixed(1)}% \xB7 ${badgeWeekly})`;
3194
- lines.push(border("\u2502") + rollingLine2 + " ".repeat(Math.max(1, width - 2 - stripAnsi(rollingLine2).length)) + border("\u2502"));
3505
+ if (!hasPlan) {
3506
+ const quota = calculateTokenQuotaPercentages(total);
3507
+ const tokenLine2 = ` \u21B3 ${pc2.cyan(quota.pctOf5hLimit.toFixed(2) + "% of 5h limit")} \xB7 ${pc2.cyan(quota.pctOfWeeklyLimit.toFixed(2) + "% of weekly limit")}`;
3508
+ lines.push(border("\u2502") + tokenLine2 + " ".repeat(Math.max(1, width - 2 - stripAnsi(tokenLine2).length)) + border("\u2502"));
3509
+ if (options.repoRoot) {
3510
+ try {
3511
+ const rolling = getRollingWindowTokenUsage(options.repoRoot);
3512
+ if (rolling.windowTokens > 0 || rolling.weeklyTokens > 0) {
3513
+ const badge5h = formatQuotaStatusBadge(rolling.windowStatus);
3514
+ const badgeWeekly = formatQuotaStatusBadge(rolling.weeklyStatus);
3515
+ const rollingLine1 = ` Rolling 5h: ${rolling.windowTokens.toLocaleString()} / ${rolling.windowLimit.toLocaleString()} tokens (${rolling.windowPercentage.toFixed(1)}% \xB7 ${badge5h})`;
3516
+ lines.push(border("\u2502") + rollingLine1 + " ".repeat(Math.max(1, width - 2 - stripAnsi(rollingLine1).length)) + border("\u2502"));
3517
+ const rollingLine2 = ` Rolling 7d: ${rolling.weeklyTokens.toLocaleString()} / ${rolling.weeklyLimit.toLocaleString()} tokens (${rolling.weeklyPercentage.toFixed(1)}% \xB7 ${badgeWeekly})`;
3518
+ lines.push(border("\u2502") + rollingLine2 + " ".repeat(Math.max(1, width - 2 - stripAnsi(rollingLine2).length)) + border("\u2502"));
3519
+ }
3520
+ } catch {
3195
3521
  }
3196
- } catch {
3197
3522
  }
3198
3523
  }
3199
3524
  }
3200
3525
  if (hasPlan && plan) {
3201
- const summary = formatPlanQuotaSummary(plan);
3526
+ const summary = formatPlanQuotaSummary(plan, { hideUnused: true });
3202
3527
  if (summary && summary !== "Plan Quota: Unavailable") {
3203
3528
  const summaryLines = summary.split("\n");
3204
3529
  for (let i = 0; i < summaryLines.length; i++) {
@@ -6161,6 +6486,10 @@ var KeyboardController = class {
6161
6486
  this.options.onToggleVerbose?.();
6162
6487
  return;
6163
6488
  }
6489
+ if (str === "b" || str === "B" || keyName === "b") {
6490
+ this.options.onToggleFullBurn?.();
6491
+ return;
6492
+ }
6164
6493
  if (str === "l" || str === "L" || keyName === "l") {
6165
6494
  this.options.onTailLog?.();
6166
6495
  return;
@@ -6352,6 +6681,7 @@ function printKeybindingCheatSheet() {
6352
6681
  console.log(` ${pc13.bold(pc13.green("p"))} Pause / resume automated polling intervals`);
6353
6682
  console.log(` ${pc13.bold(pc13.green("s"))} Print current daemon status summary card`);
6354
6683
  console.log(` ${pc13.bold(pc13.green("v"))} Toggle verbose streaming logging live`);
6684
+ console.log(` ${pc13.bold(pc13.green("b"))} Toggle full burn mode (override <20% plan quota floor)`);
6355
6685
  console.log(` ${pc13.bold(pc13.green("l"))} Tail recent lines from .jonah-fleet/daemon.log`);
6356
6686
  console.log(` ${pc13.bold(pc13.green("w"))} Inspect active worktrees and clean stale ones`);
6357
6687
  console.log(` ${pc13.bold(pc13.green("q"))} Graceful shutdown (waits for active routine to finish)`);
@@ -6385,6 +6715,11 @@ function printDaemonStatusSummary(options) {
6385
6715
  ` Verbose Mode: ${verbose ? pc13.green("ENABLED (streaming tokens)") : pc13.gray("DISABLED (compact spinner)")}`
6386
6716
  );
6387
6717
  }
6718
+ if (state.fullBurn !== void 0) {
6719
+ console.log(
6720
+ ` Full Burn: ${state.fullBurn ? pc13.red(pc13.bold("ENABLED (quota floor bypassed)")) : pc13.green("DISABLED (stops at <20% quota)")}`
6721
+ );
6722
+ }
6388
6723
  if (pendingRoutine) {
6389
6724
  console.log(` Queued Routine: ${pc13.cyan(pc13.bold(pendingRoutine))}`);
6390
6725
  }
@@ -6443,10 +6778,11 @@ function formatDaemonStatusLine(options = {}) {
6443
6778
  const columns = options.columns !== void 0 ? options.columns : process.stderr.columns || 80;
6444
6779
  const maxCols = Math.max(20, (columns || 80) - 2);
6445
6780
  const includeTip = columns >= 55;
6781
+ const burnBadge = options.fullBurn ? pc13.red(" [FULL BURN]") : "";
6446
6782
  let core;
6447
6783
  if (options.isPaused) {
6448
6784
  const queueStr = options.pendingRoutine ? pc13.cyan(` [Queued: ${options.pendingRoutine}]`) : "";
6449
- core = `${pc13.dim("[" + timeString + "]")} \u23F8 ${pc13.yellow("PAUSED")}${queueStr}`;
6785
+ core = `${pc13.dim("[" + timeString + "]")} \u23F8 ${pc13.yellow("PAUSED")}${burnBadge}${queueStr}`;
6450
6786
  } else {
6451
6787
  const nextCheck = options.nextCheckTime !== void 0 ? options.nextCheckTime : nowMs;
6452
6788
  const diffMs = Math.max(0, nextCheck - nowMs);
@@ -6456,7 +6792,7 @@ function formatDaemonStatusLine(options = {}) {
6456
6792
  const timeStr = `${mins}m ${secs < 10 ? "0" : ""}${secs}s`;
6457
6793
  const prStr = options.lastOpenPRCount !== void 0 ? ` (${options.lastOpenPRCount} ready PRs)` : "";
6458
6794
  const queueStr = options.pendingRoutine ? pc13.cyan(` [Queued: ${options.pendingRoutine}]`) : "";
6459
- core = `${pc13.dim("[" + timeString + "]")} \u26A1 ${pc13.dim("Watchdog Idle \xB7 Next check in " + timeStr + prStr)}${queueStr}`;
6795
+ core = `${pc13.dim("[" + timeString + "]")} \u26A1 ${pc13.dim("Watchdog Idle \xB7 Next check in " + timeStr + prStr)}${burnBadge}${queueStr}`;
6460
6796
  }
6461
6797
  if (!includeTip) {
6462
6798
  return truncateAnsi(core, maxCols);
@@ -6514,6 +6850,46 @@ function isDaemonRunning(repoRoot) {
6514
6850
  return false;
6515
6851
  }
6516
6852
  }
6853
+ function isFullBurnEnabled(options = {}, state, repoRoot) {
6854
+ if (options.fullBurn === true || state?.fullBurn === true) return true;
6855
+ if (process.env.JONAH_FLEET_FULL_BURN === "true" || process.env.JONAH_FLEET_FULL_BURN === "1" || process.env.FULL_BURN === "true" || process.env.FULL_BURN === "1") {
6856
+ return true;
6857
+ }
6858
+ if (repoRoot) {
6859
+ try {
6860
+ const manifest = loadManifest(repoRoot);
6861
+ if (manifest?.budgets?.fullBurn === true || manifest?.daemon?.fullBurn === true) {
6862
+ return true;
6863
+ }
6864
+ } catch {
6865
+ }
6866
+ }
6867
+ return false;
6868
+ }
6869
+ async function checkDaemonQuotaGuard(repoRoot, options = {}, state, cachedQuota) {
6870
+ if (isFullBurnEnabled(options, state, repoRoot)) {
6871
+ return { shouldStop: false };
6872
+ }
6873
+ let quota = cachedQuota;
6874
+ if (!quota) {
6875
+ if (options.getPlanQuota) {
6876
+ quota = await options.getPlanQuota(repoRoot);
6877
+ } else {
6878
+ quota = await fetchActualPlanQuota({ repoRoot });
6879
+ }
6880
+ }
6881
+ if (!quota || !quota.available) {
6882
+ return { shouldStop: false };
6883
+ }
6884
+ const depletion = checkPlanQuotaDepletion(quota, {
6885
+ thresholdPct: options.quotaThresholdPct ?? 20,
6886
+ model: options.model
6887
+ });
6888
+ if (depletion.depleted) {
6889
+ return { shouldStop: true, depletion };
6890
+ }
6891
+ return { shouldStop: false };
6892
+ }
6517
6893
  function isBotLogin(login) {
6518
6894
  if (!login) return false;
6519
6895
  const l = login.toLowerCase().trim();
@@ -6835,9 +7211,16 @@ async function startBackgroundDaemon(repoRoot, options = {}) {
6835
7211
  const existing = readDaemonState(repoRoot);
6836
7212
  throw new Error(`Daemon is already running with PID ${existing?.pid}`);
6837
7213
  }
7214
+ const guard = await checkDaemonQuotaGuard(repoRoot, options);
7215
+ if (guard.shouldStop) {
7216
+ throw new Error(
7217
+ `Plan quota is below 20% (${guard.depletion?.message}). Daemon stopped to protect compute budget. Run with --full-burn to bypass this guard.`
7218
+ );
7219
+ }
6838
7220
  const reviewInterval = options.reviewInterval || 3;
6839
7221
  const autoworkInterval = options.autoworkInterval || options.interval || 30;
6840
7222
  const routines = options.routines || ["peer-review", "autowork"];
7223
+ const fullBurn = isFullBurnEnabled(options, void 0, repoRoot);
6841
7224
  const logFilePath = path15.join(repoRoot, ".jonah-fleet", "daemon.log");
6842
7225
  fs15.mkdirSync(path15.dirname(logFilePath), { recursive: true });
6843
7226
  const logFd = fs15.openSync(logFilePath, "a");
@@ -6858,11 +7241,14 @@ async function startBackgroundDaemon(repoRoot, options = {}) {
6858
7241
  if (options.verbose) {
6859
7242
  args.push("--verbose");
6860
7243
  }
7244
+ if (fullBurn) {
7245
+ args.push("--full-burn");
7246
+ }
6861
7247
  const child = spawn2(process.execPath, [cliPath, ...args], {
6862
7248
  cwd: repoRoot,
6863
7249
  detached: true,
6864
7250
  stdio: ["ignore", logFd, logFd],
6865
- env: { ...process.env, JONAH_FLEET_DAEMON: "true" }
7251
+ env: { ...process.env, JONAH_FLEET_DAEMON: "true", ...fullBurn ? { JONAH_FLEET_FULL_BURN: "true" } : {} }
6866
7252
  });
6867
7253
  child.unref();
6868
7254
  const state = {
@@ -6871,7 +7257,8 @@ async function startBackgroundDaemon(repoRoot, options = {}) {
6871
7257
  reviewIntervalMinutes: reviewInterval,
6872
7258
  autoworkIntervalMinutes: autoworkInterval,
6873
7259
  routines,
6874
- status: "idle"
7260
+ status: "idle",
7261
+ fullBurn
6875
7262
  };
6876
7263
  writeDaemonState(repoRoot, state);
6877
7264
  return state;
@@ -7009,9 +7396,22 @@ async function drainReviewQueue(drainOptions) {
7009
7396
  runRoutine = runLocalRoutine,
7010
7397
  onAttempted,
7011
7398
  failureCooldowns,
7012
- onQuotaExhausted
7399
+ onQuotaExhausted,
7400
+ onQuotaDepleted
7013
7401
  } = drainOptions;
7014
7402
  if (isStopping()) return;
7403
+ const guard = await checkDaemonQuotaGuard(repoRoot, options, state);
7404
+ if (guard.shouldStop) {
7405
+ if (clearTicker) clearTicker();
7406
+ console.warn(
7407
+ pc14.red(
7408
+ `
7409
+ \u{1F6D1} Peer Review Watchdog stopped: Plan quota is below 20% (${guard.depletion?.message}).`
7410
+ )
7411
+ );
7412
+ onQuotaDepleted?.(guard.depletion);
7413
+ return;
7414
+ }
7015
7415
  if (state) {
7016
7416
  state.lastReviewCheckAt = (/* @__PURE__ */ new Date()).toISOString();
7017
7417
  writeDaemonState(repoRoot, state);
@@ -7055,6 +7455,18 @@ async function drainReviewQueue(drainOptions) {
7055
7455
  const currentPR = candidatePRs[0];
7056
7456
  const targetLabel = formatTargetLabel(`PR #${currentPR.number}`, currentPR.title);
7057
7457
  let targetPRStr = targetLabel;
7458
+ const loopGuard = await checkDaemonQuotaGuard(repoRoot, options, state);
7459
+ if (loopGuard.shouldStop) {
7460
+ if (clearTicker) clearTicker();
7461
+ console.warn(
7462
+ pc14.red(
7463
+ `
7464
+ \u{1F6D1} Peer Review Watchdog stopped: Plan quota is below 20% (${loopGuard.depletion?.message}).`
7465
+ )
7466
+ );
7467
+ onQuotaDepleted?.(loopGuard.depletion);
7468
+ break;
7469
+ }
7058
7470
  try {
7059
7471
  if (clearTicker) clearTicker();
7060
7472
  if (state) {
@@ -7088,6 +7500,20 @@ async function drainReviewQueue(drainOptions) {
7088
7500
  }
7089
7501
  }
7090
7502
  });
7503
+ if (result.planQuota) {
7504
+ const postGuard = await checkDaemonQuotaGuard(repoRoot, options, state, result.planQuota);
7505
+ if (postGuard.shouldStop) {
7506
+ if (clearTicker) clearTicker();
7507
+ console.warn(
7508
+ pc14.red(
7509
+ `
7510
+ \u{1F6D1} Peer Review Watchdog stopped: Plan quota dropped below 20% after routine (${postGuard.depletion?.message}).`
7511
+ )
7512
+ );
7513
+ onQuotaDepleted?.(postGuard.depletion);
7514
+ break;
7515
+ }
7516
+ }
7091
7517
  const activeTargetStr = targetPRStr;
7092
7518
  const match = activeTargetStr?.match(/PR\s*#?([0-9]+)/i);
7093
7519
  const prNum = match ? parseInt(match[1], 10) : currentPR.number;
@@ -7200,6 +7626,18 @@ async function performAutoworkScan(scanOptions) {
7200
7626
  onDiagnosticCard
7201
7627
  } = scanOptions;
7202
7628
  if (isStopping()) return { executed: false, reason: "stopping" };
7629
+ const initialGuard = await checkDaemonQuotaGuard(repoRoot, options, state);
7630
+ if (initialGuard.shouldStop) {
7631
+ if (clearTicker) clearTicker();
7632
+ console.warn(
7633
+ pc14.red(
7634
+ `
7635
+ \u{1F6D1} Autowork Backlog Scan stopped: Plan quota depleted (<20% remaining). ${initialGuard.depletion?.message}`
7636
+ )
7637
+ );
7638
+ scanOptions.onQuotaDepleted?.(initialGuard.depletion);
7639
+ return { executed: false, reason: "quota_depleted" };
7640
+ }
7203
7641
  const routines = options.routines || ["peer-review", "autowork"];
7204
7642
  if (routines.includes("peer-review")) {
7205
7643
  const pendingPRs = (await getPRs(repoRoot)).length;
@@ -7220,7 +7658,8 @@ async function performAutoworkScan(scanOptions) {
7220
7658
  runRoutine,
7221
7659
  onAttempted: scanOptions.onAttempted,
7222
7660
  failureCooldowns: scanOptions.failureCooldowns,
7223
- onQuotaExhausted: scanOptions.onQuotaExhausted
7661
+ onQuotaExhausted: scanOptions.onQuotaExhausted,
7662
+ onQuotaDepleted: scanOptions.onQuotaDepleted
7224
7663
  });
7225
7664
  const remainingPRs = (await getPRs(repoRoot)).length;
7226
7665
  if (remainingPRs > 0) {
@@ -7270,6 +7709,20 @@ async function performAutoworkScan(scanOptions) {
7270
7709
  }
7271
7710
  });
7272
7711
  recordDaemonSessionUsage(repoRoot, state, result.usage);
7712
+ if (result.planQuota) {
7713
+ const postRoutineGuard = await checkDaemonQuotaGuard(repoRoot, options, state, result.planQuota);
7714
+ if (postRoutineGuard.shouldStop) {
7715
+ if (clearTicker) clearTicker();
7716
+ console.warn(
7717
+ pc14.red(
7718
+ `
7719
+ \u{1F6D1} Autowork Scan stopped: Plan quota dropped below 20% after routine (${postRoutineGuard.depletion?.message}).`
7720
+ )
7721
+ );
7722
+ scanOptions.onQuotaDepleted?.(postRoutineGuard.depletion);
7723
+ return { executed: true, reason: "quota_depleted" };
7724
+ }
7725
+ }
7273
7726
  const isQuota = result.quotaPaused || !result.success && detectQuotaExceeded(result.output || "", result.stderr || "").isQuota;
7274
7727
  if (isQuota) {
7275
7728
  const resetInfo = result.quotaResetInfo || detectQuotaExceeded(result.output || "", result.stderr || "").resetInfo;
@@ -7315,7 +7768,8 @@ function initializeDaemonState(repoRoot, options = {}) {
7315
7768
  routines,
7316
7769
  status: "idle",
7317
7770
  evaluatedPRs: existingState?.evaluatedPRs,
7318
- failureCooldowns: existingState?.failureCooldowns
7771
+ failureCooldowns: existingState?.failureCooldowns,
7772
+ fullBurn: isFullBurnEnabled(options, existingState, repoRoot)
7319
7773
  };
7320
7774
  writeDaemonState(repoRoot, state);
7321
7775
  const failureCooldowns = /* @__PURE__ */ new Map();
@@ -7331,6 +7785,21 @@ async function runDaemonLoop(repoRoot, options = {}) {
7331
7785
  const autoworkInterval = options.autoworkInterval || options.interval || 30;
7332
7786
  const routines = options.routines || ["peer-review", "autowork"];
7333
7787
  const { state, failureCooldowns } = initializeDaemonState(repoRoot, options);
7788
+ const initialGuard = await checkDaemonQuotaGuard(repoRoot, options, state);
7789
+ if (initialGuard.shouldStop) {
7790
+ console.warn(
7791
+ pc14.red(
7792
+ `
7793
+ \u{1F6D1} Daemon stopped: Plan quota is below 20% (${initialGuard.depletion?.message}).
7794
+ Remaining quota must stay >= 20% for 5h and 7days windows.
7795
+ To bypass this safety limit, start with --full-burn or set FULL_BURN=true.
7796
+ `
7797
+ )
7798
+ );
7799
+ clearDaemonState(repoRoot);
7800
+ return;
7801
+ }
7802
+ const isFullBurn = isFullBurnEnabled(options, state, repoRoot);
7334
7803
  console.log(
7335
7804
  renderFleetBanner({
7336
7805
  command: "DAEMON",
@@ -7340,8 +7809,9 @@ async function runDaemonLoop(repoRoot, options = {}) {
7340
7809
  { label: "Review Watchdog", value: `Every ${reviewInterval}m (zero-token preflight)` },
7341
7810
  { label: "Autowork Scan", value: `Every ${autoworkInterval}m` },
7342
7811
  { label: "Routines", value: routines.join(", ") },
7812
+ { label: "Full Burn", value: isFullBurn ? "ENABLED" : "DISABLED (stops at <20% quota)" },
7343
7813
  { label: "Target", value: repoRoot },
7344
- { label: "Hotkeys", value: "'r' review \xB7 'a' autowork \xB7 'p' pause \xB7 's' status \xB7 '?' help" }
7814
+ { label: "Hotkeys", value: "'r' review \xB7 'a' autowork \xB7 'p' pause \xB7 's' status \xB7 'b' burn \xB7 '?' help" }
7345
7815
  ]
7346
7816
  })
7347
7817
  );
@@ -7430,7 +7900,10 @@ Stopping local agent daemon...`));
7430
7900
  getPRs: getPRsFn,
7431
7901
  runRoutine: runRoutineFn,
7432
7902
  failureCooldowns,
7433
- onQuotaExhausted: handleQuotaPause
7903
+ onQuotaExhausted: handleQuotaPause,
7904
+ onQuotaDepleted: async () => {
7905
+ await handleStop();
7906
+ }
7434
7907
  });
7435
7908
  try {
7436
7909
  const prs = await getPRsFn(repoRoot);
@@ -7488,7 +7961,10 @@ Stopping local agent daemon...`));
7488
7961
  getBacklog: options.getBacklog || getBacklogTriageReport,
7489
7962
  runRoutine: runRoutineFn,
7490
7963
  failureCooldowns,
7491
- onQuotaExhausted: handleQuotaPause
7964
+ onQuotaExhausted: handleQuotaPause,
7965
+ onQuotaDepleted: async () => {
7966
+ await handleStop();
7967
+ }
7492
7968
  });
7493
7969
  try {
7494
7970
  const prs = await getPRsFn(repoRoot);
@@ -7549,6 +8025,19 @@ Stopping local agent daemon...`));
7549
8025
  };
7550
8026
  const runTargetedReview = async (prNumber) => {
7551
8027
  if (isStopping || isWorking) return;
8028
+ const guard = await checkDaemonQuotaGuard(repoRoot, options, state);
8029
+ if (guard.shouldStop) {
8030
+ clearTicker();
8031
+ console.warn(
8032
+ pc14.red(
8033
+ `
8034
+ \u{1F6D1} Cannot start targeted review: Plan quota is below 20% (${guard.depletion?.message}).
8035
+ Press 'b' to toggle full-burn mode if you wish to proceed.`
8036
+ )
8037
+ );
8038
+ updateTicker();
8039
+ return;
8040
+ }
7552
8041
  try {
7553
8042
  isWorking = true;
7554
8043
  clearTicker();
@@ -7573,6 +8062,20 @@ Stopping local agent daemon...`));
7573
8062
  writeDaemonState(repoRoot, state);
7574
8063
  }
7575
8064
  });
8065
+ if (result.planQuota) {
8066
+ const postGuard = await checkDaemonQuotaGuard(repoRoot, options, state, result.planQuota);
8067
+ if (postGuard.shouldStop) {
8068
+ clearTicker();
8069
+ console.warn(
8070
+ pc14.red(
8071
+ `
8072
+ \u{1F6D1} Daemon stopped after targeted review: Plan quota dropped below 20% (${postGuard.depletion?.message}).`
8073
+ )
8074
+ );
8075
+ await handleStop();
8076
+ return;
8077
+ }
8078
+ }
7576
8079
  if (result.success) {
7577
8080
  const outcome = result.outcome || detectPeerReviewOutcome({
7578
8081
  output: result.output,
@@ -7616,6 +8119,19 @@ Stopping local agent daemon...`));
7616
8119
  };
7617
8120
  const runTargetedAutowork = async (issueNumber) => {
7618
8121
  if (isStopping || isWorking) return;
8122
+ const guard = await checkDaemonQuotaGuard(repoRoot, options, state);
8123
+ if (guard.shouldStop) {
8124
+ clearTicker();
8125
+ console.warn(
8126
+ pc14.red(
8127
+ `
8128
+ \u{1F6D1} Cannot start targeted autowork: Plan quota is below 20% (${guard.depletion?.message}).
8129
+ Press 'b' to toggle full-burn mode if you wish to proceed.`
8130
+ )
8131
+ );
8132
+ updateTicker();
8133
+ return;
8134
+ }
7619
8135
  try {
7620
8136
  isWorking = true;
7621
8137
  clearTicker();
@@ -7640,6 +8156,20 @@ Stopping local agent daemon...`));
7640
8156
  writeDaemonState(repoRoot, state);
7641
8157
  }
7642
8158
  });
8159
+ if (result.planQuota) {
8160
+ const postGuard = await checkDaemonQuotaGuard(repoRoot, options, state, result.planQuota);
8161
+ if (postGuard.shouldStop) {
8162
+ clearTicker();
8163
+ console.warn(
8164
+ pc14.red(
8165
+ `
8166
+ \u{1F6D1} Daemon stopped after targeted autowork: Plan quota dropped below 20% (${postGuard.depletion?.message}).`
8167
+ )
8168
+ );
8169
+ await handleStop();
8170
+ return;
8171
+ }
8172
+ }
7643
8173
  if (result.success) {
7644
8174
  console.log(pc14.green(`\u2713 Targeted autowork on Issue #${issueNumber} completed successfully.
7645
8175
  `));
@@ -7834,6 +8364,27 @@ ${pc14.cyan("Enter Issue # to work (Esc/Enter to cancel):")} `, {
7834
8364
  }
7835
8365
  updateTicker();
7836
8366
  },
8367
+ onToggleFullBurn: () => {
8368
+ if (isStopping || isGracefulStopping) return;
8369
+ options.fullBurn = !isFullBurnEnabled(options, state, repoRoot);
8370
+ if (state) {
8371
+ state.fullBurn = options.fullBurn;
8372
+ writeDaemonState(repoRoot, state);
8373
+ }
8374
+ clearTicker();
8375
+ if (options.fullBurn) {
8376
+ console.log(
8377
+ pc14.red(`
8378
+ [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] \u{1F525} Full burn mode ENABLED (ignoring <20% quota floor).`)
8379
+ );
8380
+ } else {
8381
+ console.log(
8382
+ pc14.green(`
8383
+ [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] \u{1F6E1}\uFE0F Full burn mode DISABLED (daemon will stop if quota < 20%).`)
8384
+ );
8385
+ }
8386
+ updateTicker();
8387
+ },
7837
8388
  onTailLog: () => {
7838
8389
  if (isStopping || isGracefulStopping) return;
7839
8390
  clearTicker();
@@ -7928,6 +8479,18 @@ ${pc14.cyan("Enter Issue # to work (Esc/Enter to cancel):")} `, {
7928
8479
  isTicking = true;
7929
8480
  try {
7930
8481
  if (!isPaused) {
8482
+ const tickGuard = await checkDaemonQuotaGuard(repoRoot, options, state);
8483
+ if (tickGuard.shouldStop) {
8484
+ clearTicker();
8485
+ console.warn(
8486
+ pc14.red(
8487
+ `
8488
+ \u{1F6D1} Daemon stopped by quota guard: Plan quota is below 20% (${tickGuard.depletion?.message}). Stopping daemon...`
8489
+ )
8490
+ );
8491
+ await handleStop();
8492
+ return;
8493
+ }
7931
8494
  const now = Date.now();
7932
8495
  if (routines.includes("peer-review") && now >= nextReviewCheckTime) {
7933
8496
  await performReviewDrain();
@@ -7963,7 +8526,8 @@ async function runDaemonCommand(action, options = {}) {
7963
8526
  routines: options.routines ? options.routines.split(",").map((r) => r.trim()) : void 0,
7964
8527
  model: options.model,
7965
8528
  foreground: options.foreground,
7966
- verbose: options.verbose
8529
+ verbose: options.verbose,
8530
+ fullBurn: options.fullBurn
7967
8531
  };
7968
8532
  if (act === "start") {
7969
8533
  if (options.foreground) {
@@ -7987,6 +8551,7 @@ async function runDaemonCommand(action, options = {}) {
7987
8551
  { label: "Review Watchdog", value: `Every ${state2.reviewIntervalMinutes}m (zero-token preflight)` },
7988
8552
  { label: "Autowork Scan", value: `Every ${state2.autoworkIntervalMinutes}m` },
7989
8553
  { label: "Routines", value: state2.routines.join(", ") },
8554
+ { label: "Full Burn", value: state2.fullBurn ? "ENABLED" : "DISABLED (stops at <20% quota)" },
7990
8555
  { label: "Log", value: ".jonah-fleet/daemon.log" }
7991
8556
  ]
7992
8557
  })
@@ -8041,6 +8606,7 @@ Stopping background agent daemon (PID ${state2?.pid})...`));
8041
8606
  details: [
8042
8607
  { label: "Status", value: running && state ? state.status.toUpperCase() : "STOPPED" },
8043
8608
  ...running && state ? [{ label: "PID", value: String(state.pid) }] : [],
8609
+ ...running && state && state.fullBurn !== void 0 ? [{ label: "Full Burn", value: state.fullBurn ? "ENABLED" : "DISABLED" }] : [],
8044
8610
  { label: "Target", value: cwd }
8045
8611
  ]
8046
8612
  })
@@ -8055,6 +8621,11 @@ Stopping background agent daemon (PID ${state2?.pid})...`));
8055
8621
  console.log(` Peer Review Cadence: Every ${state.reviewIntervalMinutes} minutes (0-token fast preflight)`);
8056
8622
  console.log(` Autowork Cadence: Every ${state.autoworkIntervalMinutes} minutes`);
8057
8623
  console.log(` Routines: ${state.routines.join(", ")}`);
8624
+ if (state.fullBurn !== void 0) {
8625
+ console.log(
8626
+ ` Full Burn: ${state.fullBurn ? pc15.red(pc15.bold("ENABLED (quota floor bypassed)")) : pc15.green("DISABLED (stops at <20% quota)")}`
8627
+ );
8628
+ }
8058
8629
  let currentStateText = pc15.green("IDLE");
8059
8630
  if (state.status === "working") {
8060
8631
  const workingDesc = state.activeRoutine + (state.activeTarget ? ` (${pc15.bold(state.activeTarget)})` : "");
@@ -8225,7 +8796,7 @@ program.name("jonah-fleet").description("Manage autonomous agent fleet, prompt r
8225
8796
  program.command("run <routine>").description("Run a specific prompt routine locally in an isolated git worktree").option("-i, --issue <number>", "Targeted issue number for autowork").option("-p, --pr <number>", "Targeted pull request number for peer-review").option("-m, --model <model>", "LLM model override (defaults to gemini-3.8-flash-high)").option("--timeout <duration>", "CLI execution print timeout (default: 30m)").option("--no-worktree", "Execute directly in current directory without creating a git worktree").option("--keep-worktree", "Preserve the git worktree after routine execution completes").option("-d, --dry-run", "Preview prompt and execution parameters without launching agent").option("-v, --verbose", "Stream raw agent tokens and logs directly to stdout").action(async (routine, options) => {
8226
8797
  await runRoutineCommand(routine, options);
8227
8798
  });
8228
- program.command("daemon [action]").description("Manage background local worker daemon polling for unclaimed issues and pull requests").option("-i, --interval <minutes>", "Legacy global polling interval in minutes (default: 30)").option("--review-interval <minutes>", "Peer Review watchdog cadence in minutes (default: 3)").option("--autowork-interval <minutes>", "Autowork backlog cadence in minutes (default: 30)").option("-r, --routines <list>", "Comma-separated routines to run (default: peer-review,autowork)").option("-m, --model <model>", "LLM model override").option("--foreground", "Run daemon in foreground with live console logs").option("-v, --verbose", "Stream raw agent tokens and logs directly to stdout").action(async (action, options) => {
8799
+ program.command("daemon [action]").description("Manage background local worker daemon polling for unclaimed issues and pull requests").option("-i, --interval <minutes>", "Legacy global polling interval in minutes (default: 30)").option("--review-interval <minutes>", "Peer Review watchdog cadence in minutes (default: 3)").option("--autowork-interval <minutes>", "Autowork backlog cadence in minutes (default: 30)").option("-r, --routines <list>", "Comma-separated routines to run (default: peer-review,autowork)").option("-m, --model <model>", "LLM model override").option("--foreground", "Run daemon in foreground with live console logs").option("-v, --verbose", "Stream raw agent tokens and logs directly to stdout").option("--full-burn", "Run in full burn mode without stopping on <20% plan quota limit").action(async (action, options) => {
8229
8800
  await runDaemonCommand(action, options);
8230
8801
  });
8231
8802
  program.command("labels [action]").description("Audit, prune, or provision repository labels for autonomous fleet taxonomy (audit | prune | provision)").option("-d, --dry-run", "Preview prunable labels without deleting them", false).option("-y, --yes", "Confirm automatic deletion of prunable labels", false).option("-r, --repo <repo>", "Target GitHub repository (defaults to current)").option("-j, --json", "Output results as JSON", false).action(async (action, options) => {