@axiomatic-labs/claudeflow 2.13.30 → 2.13.31
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/lib/panel.js +118 -14
- package/package.json +1 -1
package/lib/panel.js
CHANGED
|
@@ -309,6 +309,39 @@ function getRemindersInfo(cwd) {
|
|
|
309
309
|
};
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
// ─── token-budget thresholds ─────────────────────────────────────
|
|
313
|
+
//
|
|
314
|
+
// Anthropic does NOT publish official limits for per-turn additionalContext.
|
|
315
|
+
// These defaults are heuristics derived from:
|
|
316
|
+
// - GitHub issue anthropics/claude-code#45188 (system prompt 100K+ tokens
|
|
317
|
+
// made sessions unusable without /compact)
|
|
318
|
+
// - Community guidance: keep injected per-turn content well under 10–15%
|
|
319
|
+
// of the 200K context window so user content + tool results have room
|
|
320
|
+
// - Practical observation that 5–10K tokens for project rules is common
|
|
321
|
+
//
|
|
322
|
+
// Override per project via env vars before launching `claudeflow panel`:
|
|
323
|
+
// CLAUDEFLOW_TOKEN_HEAVY=8000 (yellow threshold)
|
|
324
|
+
// CLAUDEFLOW_TOKEN_HIGH=20000 (red threshold)
|
|
325
|
+
const TOKEN_HEAVY_DEFAULT = 8000;
|
|
326
|
+
const TOKEN_HIGH_DEFAULT = 20000;
|
|
327
|
+
|
|
328
|
+
function tokenThresholds() {
|
|
329
|
+
const heavy = parseInt(process.env.CLAUDEFLOW_TOKEN_HEAVY, 10);
|
|
330
|
+
const high = parseInt(process.env.CLAUDEFLOW_TOKEN_HIGH, 10);
|
|
331
|
+
return {
|
|
332
|
+
heavy: Number.isFinite(heavy) && heavy > 0 ? heavy : TOKEN_HEAVY_DEFAULT,
|
|
333
|
+
high: Number.isFinite(high) && high > 0 ? high : TOKEN_HIGH_DEFAULT,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function classifyTokens(tokens, thresholds = tokenThresholds()) {
|
|
338
|
+
if (!tokens || tokens <= 0) return 'none';
|
|
339
|
+
if (tokens >= thresholds.high) return 'high';
|
|
340
|
+
if (tokens >= thresholds.heavy) return 'heavy';
|
|
341
|
+
if (tokens < 2000) return 'light';
|
|
342
|
+
return 'normal';
|
|
343
|
+
}
|
|
344
|
+
|
|
312
345
|
// ─── log events (Logs tab) ───────────────────────────────────────
|
|
313
346
|
//
|
|
314
347
|
// Sidecar files live in `.claude/tmp/log-events/<id>.json`. Each is one
|
|
@@ -331,10 +364,11 @@ function listLogEvents(cwd, { limit = 200, before = null, type = 'all' } = {}) {
|
|
|
331
364
|
try {
|
|
332
365
|
files = fs.readdirSync(dir).filter((f) => f.endsWith('.json'));
|
|
333
366
|
} catch {
|
|
334
|
-
return { entries: [], total: 0 };
|
|
367
|
+
return { entries: [], total: 0, thresholds: tokenThresholds() };
|
|
335
368
|
}
|
|
336
369
|
files.sort().reverse(); // filenames begin with sortable timestamp; newest first
|
|
337
370
|
const total = files.length;
|
|
371
|
+
const thresholds = tokenThresholds();
|
|
338
372
|
|
|
339
373
|
let beforeIdx = 0;
|
|
340
374
|
if (before) {
|
|
@@ -369,10 +403,11 @@ function listLogEvents(cwd, { limit = 200, before = null, type = 'all' } = {}) {
|
|
|
369
403
|
summary: r.summary,
|
|
370
404
|
contentBytes: r.contentBytes || 0,
|
|
371
405
|
contentTokens: tokens,
|
|
406
|
+
tokenStatus: classifyTokens(tokens, thresholds),
|
|
372
407
|
activeReminderIds: r.activeReminderIds || null,
|
|
373
408
|
});
|
|
374
409
|
}
|
|
375
|
-
return { entries, total };
|
|
410
|
+
return { entries, total, thresholds };
|
|
376
411
|
}
|
|
377
412
|
|
|
378
413
|
function readLogEvent(cwd, id) {
|
|
@@ -386,19 +421,37 @@ function readLogEvent(cwd, id) {
|
|
|
386
421
|
|
|
387
422
|
function getLogsInfo(cwd) {
|
|
388
423
|
const dir = logEventsDir(cwd);
|
|
424
|
+
const thresholds = tokenThresholds();
|
|
389
425
|
let total = 0;
|
|
390
426
|
let mostRecent = null;
|
|
427
|
+
let latestReminderTokens = null;
|
|
428
|
+
let latestReminderStatus = null;
|
|
391
429
|
try {
|
|
392
430
|
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.json')).sort();
|
|
393
431
|
total = files.length;
|
|
394
|
-
|
|
432
|
+
// Walk from newest to find both latest event timestamp and latest reminder.
|
|
433
|
+
for (let i = files.length - 1; i >= 0 && (mostRecent === null || latestReminderTokens === null); i--) {
|
|
395
434
|
try {
|
|
396
|
-
const r = JSON.parse(fs.readFileSync(path.join(dir, files[
|
|
397
|
-
mostRecent = r.timestamp;
|
|
435
|
+
const r = JSON.parse(fs.readFileSync(path.join(dir, files[i]), 'utf8'));
|
|
436
|
+
if (mostRecent === null) mostRecent = r.timestamp;
|
|
437
|
+
if (latestReminderTokens === null && r.type && r.type.startsWith('reminder')) {
|
|
438
|
+
const tokens = typeof r.contentTokens === 'number'
|
|
439
|
+
? r.contentTokens
|
|
440
|
+
: (r.contentBytes ? Math.round(r.contentBytes / 4) : 0);
|
|
441
|
+
latestReminderTokens = tokens;
|
|
442
|
+
latestReminderStatus = classifyTokens(tokens, thresholds);
|
|
443
|
+
}
|
|
398
444
|
} catch {}
|
|
399
445
|
}
|
|
400
446
|
} catch {}
|
|
401
|
-
return {
|
|
447
|
+
return {
|
|
448
|
+
available: true,
|
|
449
|
+
total,
|
|
450
|
+
mostRecent,
|
|
451
|
+
thresholds,
|
|
452
|
+
latestReminderTokens,
|
|
453
|
+
latestReminderStatus,
|
|
454
|
+
};
|
|
402
455
|
}
|
|
403
456
|
|
|
404
457
|
function getDoctorInfo(cwd) {
|
|
@@ -546,6 +599,14 @@ details[open] summary { padding-bottom: 8px; border-bottom: 1px solid var(--bord
|
|
|
546
599
|
.logs-size { font-family: var(--mono); font-size: 12px; color: var(--muted); white-space: nowrap; }
|
|
547
600
|
.logs-view { background: var(--panel-2); border: 1px solid var(--border); color: var(--fg); padding: 4px 12px; border-radius: 5px; font: inherit; font-size: 12px; cursor: pointer; }
|
|
548
601
|
.logs-view:hover { border-color: var(--accent); }
|
|
602
|
+
.logs-table tr.token-light td:first-child { border-left: 3px solid #3fb950; }
|
|
603
|
+
.logs-table tr.token-normal td:first-child { border-left: 3px solid var(--border); }
|
|
604
|
+
.logs-table tr.token-heavy td:first-child { border-left: 3px solid var(--warn); }
|
|
605
|
+
.logs-table tr.token-high td:first-child { border-left: 3px solid var(--err); }
|
|
606
|
+
.token-badge { display: inline-block; padding: 1px 7px; border-radius: 999px; font-size: 10px; font-family: var(--mono); margin-left: 6px; vertical-align: middle; }
|
|
607
|
+
.token-badge.heavy { background: rgba(210,153,34,0.18); color: var(--warn); }
|
|
608
|
+
.token-badge.high { background: rgba(248,81,73,0.20); color: var(--err); }
|
|
609
|
+
.token-badge.light { background: rgba(63,185,80,0.18); color: var(--ok); }
|
|
549
610
|
#log-drawer { display: none; position: fixed; top: 0; right: 0; bottom: 0; width: 60%; max-width: 920px; background: var(--panel); border-left: 1px solid var(--border); box-shadow: -8px 0 24px rgba(0,0,0,0.4); z-index: 100; overflow-y: auto; }
|
|
550
611
|
#log-drawer .drawer-head { display: flex; justify-content: space-between; align-items: center; padding: 14px 22px; border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--panel); }
|
|
551
612
|
#log-drawer .drawer-head .title { font-weight: 600; }
|
|
@@ -694,7 +755,12 @@ function severityFor(id) {
|
|
|
694
755
|
case 'claudeMd': return !s.claudeMd.exists ? 'err' : (s.claudeMd.optOut ? 'info' : 'ok');
|
|
695
756
|
case 'hooks': return s.hooks.warnings > 0 ? 'warn' : 'ok';
|
|
696
757
|
case 'reminders': return s.reminders && s.reminders.disabledCount > 0 ? 'info' : 'ok';
|
|
697
|
-
case 'logs':
|
|
758
|
+
case 'logs': {
|
|
759
|
+
if (!s.logs) return 'info';
|
|
760
|
+
if (s.logs.latestReminderStatus === 'high') return 'err';
|
|
761
|
+
if (s.logs.latestReminderStatus === 'heavy') return 'warn';
|
|
762
|
+
return s.logs.total > 0 ? 'ok' : 'info';
|
|
763
|
+
}
|
|
698
764
|
case 'mcp': return !s.mcp.configFound ? 'info' : (s.mcp.playwright.match && s.mcp.staleLockfiles.length === 0 ? 'ok' : 'warn');
|
|
699
765
|
case 'setupContext': return !s.setupContext.exists ? 'err' : (s.setupContext.toolingComplete ? 'ok' : 'warn');
|
|
700
766
|
case 'activeRun': return s.activeRun.active ? 'info' : 'info';
|
|
@@ -767,9 +833,17 @@ function renderOverview() {
|
|
|
767
833
|
const ef = s.enforcement && s.enforcement.available
|
|
768
834
|
? (s.enforcement.on ? \`ON (\${s.enforcement.count} blocking hooks)\` : \`OFF — \${s.enforcement.count} blocking hooks silenced\`)
|
|
769
835
|
: 'unavailable';
|
|
836
|
+
const reminderTokens = s.logs && s.logs.latestReminderTokens;
|
|
837
|
+
const reminderStatus = s.logs && s.logs.latestReminderStatus;
|
|
770
838
|
const lg = s.logs && s.logs.available
|
|
771
|
-
? \`\${s.logs.total} captured\${s.logs.mostRecent ? ' · last ' + fmtRelativeTime(s.logs.mostRecent) : ''}\`
|
|
839
|
+
? \`\${s.logs.total} captured\${s.logs.mostRecent ? ' · last ' + fmtRelativeTime(s.logs.mostRecent) : ''}\${reminderTokens ? ' · latest reminder ≈' + reminderTokens.toLocaleString() + ' tokens' + (reminderStatus === 'heavy' ? ' ⚠ heavy' : reminderStatus === 'high' ? ' ✗ HIGH' : '') : ''}\`
|
|
772
840
|
: 'no events';
|
|
841
|
+
const logsKind = (() => {
|
|
842
|
+
if (!s.logs || !s.logs.available) return 'info';
|
|
843
|
+
if (reminderStatus === 'high') return 'err';
|
|
844
|
+
if (reminderStatus === 'heavy') return 'warn';
|
|
845
|
+
return s.logs.total > 0 ? 'ok' : 'info';
|
|
846
|
+
})();
|
|
773
847
|
return \`<h2>Overview</h2>
|
|
774
848
|
<p class="sub">Snapshot at \${new Date(s.timestamp).toLocaleTimeString()}</p>
|
|
775
849
|
<div class="card">
|
|
@@ -781,7 +855,7 @@ function renderOverview() {
|
|
|
781
855
|
\${row('Active run', ar, { kind: 'info', text: s.activeRun.active ? '·' : '·' })}
|
|
782
856
|
\${row('Reminders', rm, { kind: s.reminders && s.reminders.available ? (s.reminders.disabledCount ? 'info' : 'ok') : 'err', text: s.reminders && s.reminders.disabledCount ? '·' : '✓' })}
|
|
783
857
|
\${row('Enforcement', ef, { kind: s.enforcement && s.enforcement.available ? (s.enforcement.on ? 'ok' : 'err') : 'err', text: s.enforcement && s.enforcement.available ? (s.enforcement.on ? '✓' : '✗') : '·' })}
|
|
784
|
-
\${row('Logs', lg, { kind:
|
|
858
|
+
\${row('Logs', lg, { kind: logsKind, text: logsKind === 'err' ? '✗' : logsKind === 'warn' ? '!' : '·' })}
|
|
785
859
|
\${row('Doctor', dc, { kind: s.doctor.issueCount === 0 ? 'ok' : 'warn', text: s.doctor.issueCount === 0 ? '✓' : '!' })}
|
|
786
860
|
</div>\`;
|
|
787
861
|
}
|
|
@@ -910,7 +984,25 @@ async function viewLogDetail(id) {
|
|
|
910
984
|
}
|
|
911
985
|
if (typeof ev.contentBytes === 'number' && ev.contentBytes > 0) {
|
|
912
986
|
const tokens = typeof ev.contentTokens === 'number' ? ev.contentTokens : Math.round(ev.contentBytes / 4);
|
|
913
|
-
|
|
987
|
+
const th = (state.logs && state.logs.thresholds) || { heavy: 8000, high: 20000 };
|
|
988
|
+
const cls = tokens >= th.high ? 'high' : tokens >= th.heavy ? 'heavy' : tokens < 2000 ? 'light' : 'normal';
|
|
989
|
+
const statusBadge = cls === 'high'
|
|
990
|
+
? '<span class="token-badge high">⚠ HIGH</span>'
|
|
991
|
+
: cls === 'heavy'
|
|
992
|
+
? '<span class="token-badge heavy">⚠ HEAVY</span>'
|
|
993
|
+
: '';
|
|
994
|
+
html += '<h4 style="margin-top:14px">Injected content — ≈' + tokens.toLocaleString() + ' tokens ' + statusBadge + ' <span class="muted" style="font-size:11px">(approx · ' + fmtBytes(ev.contentBytes) + ' raw)</span></h4>';
|
|
995
|
+
if (cls === 'heavy' || cls === 'high') {
|
|
996
|
+
const advice = cls === 'high'
|
|
997
|
+
? 'This reminder exceeds <strong>' + th.high.toLocaleString() + ' tokens</strong> — eats meaningful context budget. Strongly consider:'
|
|
998
|
+
: 'This reminder exceeds <strong>' + th.heavy.toLocaleString() + ' tokens</strong> — heavy. Consider:';
|
|
999
|
+
html += '<div class="card" style="border-color:' + (cls === 'high' ? 'rgba(248,81,73,0.4)' : 'rgba(210,153,34,0.4)') + '"><p>' + advice + '</p>';
|
|
1000
|
+
html += '<ul style="margin:6px 0 0 18px; padding:0">';
|
|
1001
|
+
html += '<li>Trim CLAUDE.md (currently the largest contributor) — see the CLAUDE.md tab</li>';
|
|
1002
|
+
html += '<li>Disable individual reminder IDs you don\\\'t need in the Reminders tab</li>';
|
|
1003
|
+
html += '<li>Tune the threshold via <code>CLAUDEFLOW_TOKEN_HEAVY</code> / <code>CLAUDEFLOW_TOKEN_HIGH</code> env vars if your project genuinely needs more</li>';
|
|
1004
|
+
html += '</ul></div>';
|
|
1005
|
+
}
|
|
914
1006
|
}
|
|
915
1007
|
if (typeof ev.content === 'string' && ev.content.length > 0) {
|
|
916
1008
|
html += '<pre>' + escapeHtml(ev.content) + '</pre>';
|
|
@@ -950,23 +1042,30 @@ function renderLogs() {
|
|
|
950
1042
|
loadLogsPage();
|
|
951
1043
|
}
|
|
952
1044
|
const totalLabel = state.logs && state.logs.total != null ? \`\${state.logs.total} total events\` : '';
|
|
1045
|
+
const thresholds = (state.logs && state.logs.thresholds) || { heavy: 8000, high: 20000 };
|
|
953
1046
|
const rows = logsState.entries.map((e) => {
|
|
954
1047
|
const isReminder = e.type.startsWith('reminder');
|
|
955
1048
|
const badge = isReminder
|
|
956
1049
|
? '<span class="badge info" style="background:rgba(88,166,255,0.18); color:#58a6ff">REMINDER</span>'
|
|
957
1050
|
: '<span class="badge err">ENFORCEMENT</span>';
|
|
1051
|
+
const tokenStatus = e.tokenStatus || 'none';
|
|
1052
|
+
const tokenBadge = tokenStatus === 'high'
|
|
1053
|
+
? '<span class="token-badge high" title="High — exceeds the high-water threshold (' + thresholds.high.toLocaleString() + ' tokens). Consider trimming.">⚠ HIGH</span>'
|
|
1054
|
+
: tokenStatus === 'heavy'
|
|
1055
|
+
? '<span class="token-badge heavy" title="Heavy — exceeds the heavy threshold (' + thresholds.heavy.toLocaleString() + ' tokens). Consider trimming.">⚠ HEAVY</span>'
|
|
1056
|
+
: '';
|
|
958
1057
|
const tokensStr = e.contentTokens
|
|
959
1058
|
? \`≈\${e.contentTokens.toLocaleString()}\`
|
|
960
1059
|
: '—';
|
|
961
1060
|
const bytesStr = e.contentBytes ? fmtBytes(e.contentBytes) : '';
|
|
962
1061
|
const tokensTitle = e.contentBytes
|
|
963
|
-
? \`≈\${e.contentTokens} tokens (
|
|
1062
|
+
? \`≈\${e.contentTokens} tokens (chars/4 heuristic) · \${fmtBytes(e.contentBytes)} raw · status: \${tokenStatus}\`
|
|
964
1063
|
: 'No content (enforcement-silenced)';
|
|
965
|
-
return \`<tr>
|
|
1064
|
+
return \`<tr class="token-\${tokenStatus}">
|
|
966
1065
|
<td class="logs-time"><span title="\${escapeHtml(e.timestamp)}">\${escapeHtml(fmtLocalTime(e.timestamp))}</span><br><span class="muted" style="font-size:11px">\${fmtRelativeTime(e.timestamp)}</span></td>
|
|
967
1066
|
<td>\${badge}</td>
|
|
968
1067
|
<td class="logs-handler"><span class="muted">\${escapeHtml(e.event)}</span><br>\${escapeHtml(e.handler)}\${e.matcher ? \` <span class="muted">(\${escapeHtml(e.matcher)})</span>\` : ''}</td>
|
|
969
|
-
<td class="logs-size" title="\${escapeHtml(tokensTitle)}">\${tokensStr}\${bytesStr ? \`<br><span class="muted" style="font-size:11px">\${bytesStr}</span>\` : ''}</td>
|
|
1068
|
+
<td class="logs-size" title="\${escapeHtml(tokensTitle)}">\${tokensStr}\${tokenBadge}\${bytesStr ? \`<br><span class="muted" style="font-size:11px">\${bytesStr}</span>\` : ''}</td>
|
|
970
1069
|
<td><button class="logs-view" data-log-id="\${escapeHtml(e.id)}">View</button></td>
|
|
971
1070
|
</tr>\`;
|
|
972
1071
|
}).join('');
|
|
@@ -975,6 +1074,11 @@ function renderLogs() {
|
|
|
975
1074
|
: '';
|
|
976
1075
|
return \`<h2>Logs</h2>
|
|
977
1076
|
<p class="sub">Real-time trace of every reminder injection and enforcement-silenced hook. \${totalLabel} · sidecars in <code>.claude/tmp/log-events/</code> (capped at 2000, oldest evicted).</p>
|
|
1077
|
+
<p class="sub muted" style="font-size:11px">
|
|
1078
|
+
Token-budget thresholds: <strong style="color:var(--warn)">heavy ≥\${thresholds.heavy.toLocaleString()}</strong> · <strong style="color:var(--err)">high ≥\${thresholds.high.toLocaleString()}</strong>.
|
|
1079
|
+
Heuristic — Anthropic doesn't publish official limits for per-turn additionalContext.
|
|
1080
|
+
Override via <code>CLAUDEFLOW_TOKEN_HEAVY</code> / <code>CLAUDEFLOW_TOKEN_HIGH</code> env vars.
|
|
1081
|
+
</p>
|
|
978
1082
|
<div class="logs-controls">
|
|
979
1083
|
<label>Filter:
|
|
980
1084
|
<select id="logs-filter">
|
|
@@ -986,7 +1090,7 @@ function renderLogs() {
|
|
|
986
1090
|
<button id="logs-reload">Reload</button>
|
|
987
1091
|
</div>
|
|
988
1092
|
\${empty || \`<table class="logs-table">
|
|
989
|
-
<thead><tr><th>Time</th><th>Type</th><th>Handler</th><th title="Approximate token count (chars/4 heuristic
|
|
1093
|
+
<thead><tr><th>Time</th><th>Type</th><th>Handler</th><th title="Approximate token count (chars/4 heuristic, ±10–15% vs Claude's real tokenizer). Heavy ≥\${thresholds.heavy.toLocaleString()} → yellow border. High ≥\${thresholds.high.toLocaleString()} → red border. Anthropic does not publish official thresholds for per-turn additionalContext.">≈ Tokens</th><th></th></tr></thead>
|
|
990
1094
|
<tbody>\${rows}</tbody>
|
|
991
1095
|
</table>\`}
|
|
992
1096
|
\${logsState.allLoaded || logsState.entries.length === 0 ? '' : '<div style="margin-top:12px"><button id="logs-load-more">Load older</button></div>'}\`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiomatic-labs/claudeflow",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.31",
|
|
4
4
|
"description": "Claudeflow — AI-powered development toolkit for Claude Code. Skills, agents, hooks, and quality gates that ship production apps.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"claudeflow": "./bin/cli.js"
|