@axiomatic-labs/claudeflow 2.13.29 → 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 +133 -15
- 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) {
|
|
@@ -353,6 +387,12 @@ function listLogEvents(cwd, { limit = 200, before = null, type = 'all' } = {}) {
|
|
|
353
387
|
if (type === 'reminder' && !r.type.startsWith('reminder')) continue;
|
|
354
388
|
}
|
|
355
389
|
// Strip large `content` from list view; clients fetch it via /api/logs/:id
|
|
390
|
+
// Old sidecars (pre-token-estimate) lack `contentTokens`. Backfill on
|
|
391
|
+
// the fly using the same chars/4 heuristic — keeps the UI consistent
|
|
392
|
+
// for events captured before the upgrade.
|
|
393
|
+
const tokens = typeof r.contentTokens === 'number'
|
|
394
|
+
? r.contentTokens
|
|
395
|
+
: (r.contentBytes ? Math.round(r.contentBytes / 4) : 0);
|
|
356
396
|
entries.push({
|
|
357
397
|
id: r.id,
|
|
358
398
|
timestamp: r.timestamp,
|
|
@@ -362,10 +402,12 @@ function listLogEvents(cwd, { limit = 200, before = null, type = 'all' } = {}) {
|
|
|
362
402
|
handler: r.handler,
|
|
363
403
|
summary: r.summary,
|
|
364
404
|
contentBytes: r.contentBytes || 0,
|
|
405
|
+
contentTokens: tokens,
|
|
406
|
+
tokenStatus: classifyTokens(tokens, thresholds),
|
|
365
407
|
activeReminderIds: r.activeReminderIds || null,
|
|
366
408
|
});
|
|
367
409
|
}
|
|
368
|
-
return { entries, total };
|
|
410
|
+
return { entries, total, thresholds };
|
|
369
411
|
}
|
|
370
412
|
|
|
371
413
|
function readLogEvent(cwd, id) {
|
|
@@ -379,19 +421,37 @@ function readLogEvent(cwd, id) {
|
|
|
379
421
|
|
|
380
422
|
function getLogsInfo(cwd) {
|
|
381
423
|
const dir = logEventsDir(cwd);
|
|
424
|
+
const thresholds = tokenThresholds();
|
|
382
425
|
let total = 0;
|
|
383
426
|
let mostRecent = null;
|
|
427
|
+
let latestReminderTokens = null;
|
|
428
|
+
let latestReminderStatus = null;
|
|
384
429
|
try {
|
|
385
430
|
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.json')).sort();
|
|
386
431
|
total = files.length;
|
|
387
|
-
|
|
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--) {
|
|
388
434
|
try {
|
|
389
|
-
const r = JSON.parse(fs.readFileSync(path.join(dir, files[
|
|
390
|
-
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
|
+
}
|
|
391
444
|
} catch {}
|
|
392
445
|
}
|
|
393
446
|
} catch {}
|
|
394
|
-
return {
|
|
447
|
+
return {
|
|
448
|
+
available: true,
|
|
449
|
+
total,
|
|
450
|
+
mostRecent,
|
|
451
|
+
thresholds,
|
|
452
|
+
latestReminderTokens,
|
|
453
|
+
latestReminderStatus,
|
|
454
|
+
};
|
|
395
455
|
}
|
|
396
456
|
|
|
397
457
|
function getDoctorInfo(cwd) {
|
|
@@ -539,6 +599,14 @@ details[open] summary { padding-bottom: 8px; border-bottom: 1px solid var(--bord
|
|
|
539
599
|
.logs-size { font-family: var(--mono); font-size: 12px; color: var(--muted); white-space: nowrap; }
|
|
540
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; }
|
|
541
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); }
|
|
542
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; }
|
|
543
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); }
|
|
544
612
|
#log-drawer .drawer-head .title { font-weight: 600; }
|
|
@@ -687,7 +755,12 @@ function severityFor(id) {
|
|
|
687
755
|
case 'claudeMd': return !s.claudeMd.exists ? 'err' : (s.claudeMd.optOut ? 'info' : 'ok');
|
|
688
756
|
case 'hooks': return s.hooks.warnings > 0 ? 'warn' : 'ok';
|
|
689
757
|
case 'reminders': return s.reminders && s.reminders.disabledCount > 0 ? 'info' : 'ok';
|
|
690
|
-
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
|
+
}
|
|
691
764
|
case 'mcp': return !s.mcp.configFound ? 'info' : (s.mcp.playwright.match && s.mcp.staleLockfiles.length === 0 ? 'ok' : 'warn');
|
|
692
765
|
case 'setupContext': return !s.setupContext.exists ? 'err' : (s.setupContext.toolingComplete ? 'ok' : 'warn');
|
|
693
766
|
case 'activeRun': return s.activeRun.active ? 'info' : 'info';
|
|
@@ -760,9 +833,17 @@ function renderOverview() {
|
|
|
760
833
|
const ef = s.enforcement && s.enforcement.available
|
|
761
834
|
? (s.enforcement.on ? \`ON (\${s.enforcement.count} blocking hooks)\` : \`OFF — \${s.enforcement.count} blocking hooks silenced\`)
|
|
762
835
|
: 'unavailable';
|
|
836
|
+
const reminderTokens = s.logs && s.logs.latestReminderTokens;
|
|
837
|
+
const reminderStatus = s.logs && s.logs.latestReminderStatus;
|
|
763
838
|
const lg = s.logs && s.logs.available
|
|
764
|
-
? \`\${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' : '') : ''}\`
|
|
765
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
|
+
})();
|
|
766
847
|
return \`<h2>Overview</h2>
|
|
767
848
|
<p class="sub">Snapshot at \${new Date(s.timestamp).toLocaleTimeString()}</p>
|
|
768
849
|
<div class="card">
|
|
@@ -774,7 +855,7 @@ function renderOverview() {
|
|
|
774
855
|
\${row('Active run', ar, { kind: 'info', text: s.activeRun.active ? '·' : '·' })}
|
|
775
856
|
\${row('Reminders', rm, { kind: s.reminders && s.reminders.available ? (s.reminders.disabledCount ? 'info' : 'ok') : 'err', text: s.reminders && s.reminders.disabledCount ? '·' : '✓' })}
|
|
776
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 ? '✓' : '✗') : '·' })}
|
|
777
|
-
\${row('Logs', lg, { kind:
|
|
858
|
+
\${row('Logs', lg, { kind: logsKind, text: logsKind === 'err' ? '✗' : logsKind === 'warn' ? '!' : '·' })}
|
|
778
859
|
\${row('Doctor', dc, { kind: s.doctor.issueCount === 0 ? 'ok' : 'warn', text: s.doctor.issueCount === 0 ? '✓' : '!' })}
|
|
779
860
|
</div>\`;
|
|
780
861
|
}
|
|
@@ -901,8 +982,27 @@ async function viewLogDetail(id) {
|
|
|
901
982
|
html += '<h4 style="margin-top:14px">Active reminders at fire time</h4>';
|
|
902
983
|
html += '<p class="sub">' + ev.activeReminderIds.map(escapeHtml).join(', ') + '</p>';
|
|
903
984
|
}
|
|
904
|
-
if (typeof ev.contentBytes === 'number') {
|
|
905
|
-
|
|
985
|
+
if (typeof ev.contentBytes === 'number' && ev.contentBytes > 0) {
|
|
986
|
+
const tokens = typeof ev.contentTokens === 'number' ? ev.contentTokens : Math.round(ev.contentBytes / 4);
|
|
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
|
+
}
|
|
906
1006
|
}
|
|
907
1007
|
if (typeof ev.content === 'string' && ev.content.length > 0) {
|
|
908
1008
|
html += '<pre>' + escapeHtml(ev.content) + '</pre>';
|
|
@@ -942,17 +1042,30 @@ function renderLogs() {
|
|
|
942
1042
|
loadLogsPage();
|
|
943
1043
|
}
|
|
944
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 };
|
|
945
1046
|
const rows = logsState.entries.map((e) => {
|
|
946
1047
|
const isReminder = e.type.startsWith('reminder');
|
|
947
1048
|
const badge = isReminder
|
|
948
1049
|
? '<span class="badge info" style="background:rgba(88,166,255,0.18); color:#58a6ff">REMINDER</span>'
|
|
949
1050
|
: '<span class="badge err">ENFORCEMENT</span>';
|
|
950
|
-
const
|
|
951
|
-
|
|
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
|
+
: '';
|
|
1057
|
+
const tokensStr = e.contentTokens
|
|
1058
|
+
? \`≈\${e.contentTokens.toLocaleString()}\`
|
|
1059
|
+
: '—';
|
|
1060
|
+
const bytesStr = e.contentBytes ? fmtBytes(e.contentBytes) : '';
|
|
1061
|
+
const tokensTitle = e.contentBytes
|
|
1062
|
+
? \`≈\${e.contentTokens} tokens (chars/4 heuristic) · \${fmtBytes(e.contentBytes)} raw · status: \${tokenStatus}\`
|
|
1063
|
+
: 'No content (enforcement-silenced)';
|
|
1064
|
+
return \`<tr class="token-\${tokenStatus}">
|
|
952
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>
|
|
953
1066
|
<td>\${badge}</td>
|
|
954
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>
|
|
955
|
-
<td class="logs-size">\${
|
|
1068
|
+
<td class="logs-size" title="\${escapeHtml(tokensTitle)}">\${tokensStr}\${tokenBadge}\${bytesStr ? \`<br><span class="muted" style="font-size:11px">\${bytesStr}</span>\` : ''}</td>
|
|
956
1069
|
<td><button class="logs-view" data-log-id="\${escapeHtml(e.id)}">View</button></td>
|
|
957
1070
|
</tr>\`;
|
|
958
1071
|
}).join('');
|
|
@@ -961,6 +1074,11 @@ function renderLogs() {
|
|
|
961
1074
|
: '';
|
|
962
1075
|
return \`<h2>Logs</h2>
|
|
963
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>
|
|
964
1082
|
<div class="logs-controls">
|
|
965
1083
|
<label>Filter:
|
|
966
1084
|
<select id="logs-filter">
|
|
@@ -972,7 +1090,7 @@ function renderLogs() {
|
|
|
972
1090
|
<button id="logs-reload">Reload</button>
|
|
973
1091
|
</div>
|
|
974
1092
|
\${empty || \`<table class="logs-table">
|
|
975
|
-
<thead><tr><th>Time</th><th>Type</th><th>Handler</th><th
|
|
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>
|
|
976
1094
|
<tbody>\${rows}</tbody>
|
|
977
1095
|
</table>\`}
|
|
978
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"
|