@axiomatic-labs/claudeflow 2.13.35 → 2.13.37
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 +103 -3
- package/package.json +1 -1
package/lib/panel.js
CHANGED
|
@@ -286,6 +286,30 @@ function getEnforcementInfo(cwd) {
|
|
|
286
286
|
};
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
+
function getActivePlanInfo(cwd) {
|
|
290
|
+
const p = path.join(cwd, '.claudeflow', 'state', 'active-plan.json');
|
|
291
|
+
let raw;
|
|
292
|
+
try { raw = fs.readFileSync(p, 'utf8'); }
|
|
293
|
+
catch { return { available: false }; }
|
|
294
|
+
let record;
|
|
295
|
+
try { record = JSON.parse(raw); }
|
|
296
|
+
catch (err) { return { available: false, error: err.message }; }
|
|
297
|
+
const captured = record.captured_at ? Date.parse(record.captured_at) : null;
|
|
298
|
+
const ageSeconds = captured ? Math.floor((Date.now() - captured) / 1000) : null;
|
|
299
|
+
const content = typeof record.content === 'string' ? record.content : '';
|
|
300
|
+
return {
|
|
301
|
+
available: true,
|
|
302
|
+
path: record.path || null,
|
|
303
|
+
source: record.source || null,
|
|
304
|
+
sessionId: record.session_id || null,
|
|
305
|
+
capturedAt: record.captured_at || null,
|
|
306
|
+
ageSeconds,
|
|
307
|
+
sha256: record.sha256 || null,
|
|
308
|
+
contentBytes: record.contentBytes || content.length,
|
|
309
|
+
summaryPreview: content.slice(0, 240).replace(/\s+/g, ' ').trim(),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
289
313
|
function getRemindersInfo(cwd) {
|
|
290
314
|
const helpers = loadReminderHelpers(cwd);
|
|
291
315
|
if (!helpers || typeof helpers.listReminderCatalog !== 'function') {
|
|
@@ -512,6 +536,7 @@ function collectStatus(cwd) {
|
|
|
512
536
|
activeRun: getActiveRunInfo(cwd),
|
|
513
537
|
reminders: getRemindersInfo(cwd),
|
|
514
538
|
enforcement: getEnforcementInfo(cwd),
|
|
539
|
+
activePlan: getActivePlanInfo(cwd),
|
|
515
540
|
logs: getLogsInfo(cwd),
|
|
516
541
|
doctor: getDoctorInfo(cwd),
|
|
517
542
|
};
|
|
@@ -703,6 +728,7 @@ const SECTIONS = [
|
|
|
703
728
|
{ id: 'hooks', label: 'Hooks' },
|
|
704
729
|
{ id: 'reminders', label: 'Reminders' },
|
|
705
730
|
{ id: 'logs', label: 'Logs' },
|
|
731
|
+
{ id: 'activePlan', label: 'Active plan' },
|
|
706
732
|
{ id: 'mcp', label: 'MCP & observer' },
|
|
707
733
|
{ id: 'setupContext', label: 'Setup context' },
|
|
708
734
|
{ id: 'activeRun', label: 'Active run' },
|
|
@@ -760,6 +786,7 @@ async function loadClaudeMdPreview() {
|
|
|
760
786
|
|
|
761
787
|
async function refresh() {
|
|
762
788
|
const open = captureOpenDetails();
|
|
789
|
+
const prevPlanSha = state && state.activePlan && state.activePlan.sha256;
|
|
763
790
|
try {
|
|
764
791
|
const r = await fetch('/api/status');
|
|
765
792
|
state = await r.json();
|
|
@@ -767,12 +794,16 @@ async function refresh() {
|
|
|
767
794
|
showToast && showToast('Refresh failed: ' + e.message, 'err');
|
|
768
795
|
return;
|
|
769
796
|
}
|
|
797
|
+
// Invalidate cached active-plan body if the underlying sha256 changed.
|
|
798
|
+
const newPlanSha = state && state.activePlan && state.activePlan.sha256;
|
|
799
|
+
if (newPlanSha !== prevPlanSha) activePlanCache = null;
|
|
770
800
|
renderHeader();
|
|
771
801
|
renderNav();
|
|
772
802
|
renderContent();
|
|
773
803
|
restoreOpenDetails(open);
|
|
774
804
|
// If preview was visible/open, ensure it stays loaded after re-render.
|
|
775
805
|
if (open.has('claude-md-preview')) loadClaudeMdPreview();
|
|
806
|
+
if (open.has('active-plan-full')) loadActivePlanPreview();
|
|
776
807
|
// Refresh logs entries silently on the Logs tab so new events appear.
|
|
777
808
|
if (active === 'logs') loadLogsPage();
|
|
778
809
|
}
|
|
@@ -804,6 +835,7 @@ function severityFor(id) {
|
|
|
804
835
|
case 'claudeMd': return !s.claudeMd.exists ? 'err' : (s.claudeMd.optOut ? 'info' : 'ok');
|
|
805
836
|
case 'hooks': return s.hooks.warnings > 0 ? 'warn' : 'ok';
|
|
806
837
|
case 'reminders': return s.reminders && s.reminders.disabledCount > 0 ? 'info' : 'ok';
|
|
838
|
+
case 'activePlan': return s.activePlan && s.activePlan.available ? 'info' : 'info';
|
|
807
839
|
case 'logs': {
|
|
808
840
|
if (!s.logs) return 'info';
|
|
809
841
|
if (s.logs.recentBlockedCount > 0) return 'err';
|
|
@@ -856,6 +888,7 @@ function renderContent() {
|
|
|
856
888
|
hooks: renderHooks,
|
|
857
889
|
reminders: renderReminders,
|
|
858
890
|
logs: renderLogs,
|
|
891
|
+
activePlan: renderActivePlan,
|
|
859
892
|
mcp: renderMcp,
|
|
860
893
|
setupContext: renderSetup,
|
|
861
894
|
activeRun: renderRun,
|
|
@@ -883,6 +916,9 @@ function renderOverview() {
|
|
|
883
916
|
const ef = s.enforcement && s.enforcement.available
|
|
884
917
|
? (s.enforcement.on ? \`ON (\${s.enforcement.count} blocking hooks)\` : \`OFF — \${s.enforcement.count} blocking hooks silenced\`)
|
|
885
918
|
: 'unavailable';
|
|
919
|
+
const activePlanRow = s.activePlan && s.activePlan.available
|
|
920
|
+
? \`\${s.activePlan.path || '(inline)'} · approved \${s.activePlan.ageSeconds == null ? 'unknown' : (s.activePlan.ageSeconds < 60 ? s.activePlan.ageSeconds + 's' : Math.floor(s.activePlan.ageSeconds / 60) + 'm')} ago · \${fmtBytes(s.activePlan.contentBytes || 0)}\`
|
|
921
|
+
: 'none';
|
|
886
922
|
const reminderTokens = s.logs && s.logs.latestReminderTokens;
|
|
887
923
|
const reminderStatus = s.logs && s.logs.latestReminderStatus;
|
|
888
924
|
const lg = s.logs && s.logs.available
|
|
@@ -905,6 +941,7 @@ function renderOverview() {
|
|
|
905
941
|
\${row('Active run', ar, { kind: 'info', text: s.activeRun.active ? '·' : '·' })}
|
|
906
942
|
\${row('Reminders', rm, { kind: s.reminders && s.reminders.available ? (s.reminders.disabledCount ? 'info' : 'ok') : 'err', text: s.reminders && s.reminders.disabledCount ? '·' : '✓' })}
|
|
907
943
|
\${row('Enforcement', ef, { kind: s.enforcement && s.enforcement.available ? (s.enforcement.on ? 'ok' : 'err') : 'err', text: s.enforcement && s.enforcement.available ? (s.enforcement.on ? '✓' : '✗') : '·' })}
|
|
944
|
+
\${row('Active plan', activePlanRow, { kind: s.activePlan && s.activePlan.available ? 'info' : 'info', text: '·' })}
|
|
908
945
|
\${row('Logs', lg, { kind: logsKind, text: logsKind === 'err' ? '✗' : logsKind === 'warn' ? '!' : '·' })}
|
|
909
946
|
\${row('Doctor', dc, { kind: s.doctor.issueCount === 0 ? 'ok' : 'warn', text: s.doctor.issueCount === 0 ? '✓' : '!' })}
|
|
910
947
|
</div>\`;
|
|
@@ -1184,6 +1221,38 @@ function renderLogs() {
|
|
|
1184
1221
|
\${logsState.allLoaded || logsState.entries.length === 0 ? '' : '<div style="margin-top:12px"><button id="logs-load-more">Load older</button></div>'}\`;
|
|
1185
1222
|
}
|
|
1186
1223
|
|
|
1224
|
+
function renderActivePlan() {
|
|
1225
|
+
const p = state.activePlan;
|
|
1226
|
+
if (!p || !p.available) {
|
|
1227
|
+
return \`<h2>Active plan</h2>
|
|
1228
|
+
<p class="sub">No approved plan on disk. <code>.claudeflow/state/active-plan.json</code> doesn't exist.</p>
|
|
1229
|
+
<p class="muted" style="font-size:12px">When you approve a plan via ExitPlanMode in Claude Code, the
|
|
1230
|
+
<code>capture-approved-plan</code> hook will persist it here. The next time you run
|
|
1231
|
+
<code>claudeflow-build</code>, phase-0 will prompt to use it as the source of truth.</p>\`;
|
|
1232
|
+
}
|
|
1233
|
+
const age = p.ageSeconds == null
|
|
1234
|
+
? 'unknown age'
|
|
1235
|
+
: p.ageSeconds < 60 ? p.ageSeconds + 's ago'
|
|
1236
|
+
: p.ageSeconds < 3600 ? Math.floor(p.ageSeconds / 60) + 'm ago'
|
|
1237
|
+
: p.ageSeconds < 86400 ? Math.floor(p.ageSeconds / 3600) + 'h ago'
|
|
1238
|
+
: Math.floor(p.ageSeconds / 86400) + 'd ago';
|
|
1239
|
+
const sha = p.sha256 ? p.sha256.slice(0, 12) + '…' : '(none)';
|
|
1240
|
+
const preview = escapeHtml(p.summaryPreview || '(empty)');
|
|
1241
|
+
return \`<h2>Active plan</h2>
|
|
1242
|
+
<p class="sub">Read-only view of the latest approved plan persisted by the <code>ExitPlanMode</code> hook. The terminal prompt in <code>claudeflow-build</code> phase-0 is the only place you can ACT on this — the panel is informational.</p>
|
|
1243
|
+
<div class="card">
|
|
1244
|
+
\${row('Path', p.path || '(inline plan, no file path)')}
|
|
1245
|
+
\${row('Source', p.source || 'unknown')}
|
|
1246
|
+
\${row('Approved', age + ' · ' + (p.capturedAt || 'unknown timestamp'))}
|
|
1247
|
+
\${row('Session id', p.sessionId || '(unknown)')}
|
|
1248
|
+
\${row('Sha256', sha)}
|
|
1249
|
+
\${row('Size', fmtBytes(p.contentBytes || 0))}
|
|
1250
|
+
</div>
|
|
1251
|
+
<p class="sub" style="margin-top:14px">Preview</p>
|
|
1252
|
+
<pre style="max-height:120px">\${preview}</pre>
|
|
1253
|
+
<details data-detail-id="active-plan-full"><summary>Full plan content</summary><div id="active-plan-body" class="muted">Click to load…</div></details>\`;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1187
1256
|
function renderMcp() {
|
|
1188
1257
|
const m = state.mcp;
|
|
1189
1258
|
if (!m.configFound) return '<h2>MCP & observer</h2><p class="muted">.mcp.json not found</p>';
|
|
@@ -1348,13 +1417,35 @@ document.addEventListener('change', (e) => {
|
|
|
1348
1417
|
}
|
|
1349
1418
|
});
|
|
1350
1419
|
|
|
1420
|
+
let activePlanCache = null;
|
|
1421
|
+
async function loadActivePlanPreview() {
|
|
1422
|
+
const target = document.getElementById('active-plan-body');
|
|
1423
|
+
if (!target) return;
|
|
1424
|
+
if (target.dataset.loaded === '1') return;
|
|
1425
|
+
target.dataset.loaded = '1';
|
|
1426
|
+
target.textContent = 'Loading…';
|
|
1427
|
+
try {
|
|
1428
|
+
if (!activePlanCache) {
|
|
1429
|
+
const r = await fetch('/api/active-plan');
|
|
1430
|
+
activePlanCache = await r.text();
|
|
1431
|
+
}
|
|
1432
|
+
const pre = document.createElement('pre');
|
|
1433
|
+
pre.textContent = activePlanCache;
|
|
1434
|
+
target.innerHTML = '';
|
|
1435
|
+
target.appendChild(pre);
|
|
1436
|
+
} catch (e) {
|
|
1437
|
+
target.textContent = 'Failed to load: ' + e.message;
|
|
1438
|
+
target.dataset.loaded = '';
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1351
1442
|
// Lazy-load CLAUDE.md preview when the user opens the details element.
|
|
1352
1443
|
// The "toggle" event does not bubble by default — capture phase keeps delegation working.
|
|
1353
1444
|
document.addEventListener('toggle', (e) => {
|
|
1354
1445
|
const t = e.target;
|
|
1355
|
-
if (t
|
|
1356
|
-
|
|
1357
|
-
|
|
1446
|
+
if (!t || t.tagName !== 'DETAILS' || !t.open) return;
|
|
1447
|
+
if (t.dataset.detailId === 'claude-md-preview') loadClaudeMdPreview();
|
|
1448
|
+
else if (t.dataset.detailId === 'active-plan-full') loadActivePlanPreview();
|
|
1358
1449
|
}, true);
|
|
1359
1450
|
|
|
1360
1451
|
async function toggleEnforcement(disable) {
|
|
@@ -1437,6 +1528,15 @@ function handler(cwd) {
|
|
|
1437
1528
|
if (route === '/' || route === '/index.html') return send(200, renderHtml(), 'text/html; charset=utf-8');
|
|
1438
1529
|
if (route === '/api/status') return send(200, JSON.stringify(collectStatus(cwd)), 'application/json');
|
|
1439
1530
|
if (route === '/api/claude-md') return send(200, readClaudeMdSafe(cwd), 'text/plain; charset=utf-8');
|
|
1531
|
+
if (route === '/api/active-plan') {
|
|
1532
|
+
const p = path.join(cwd, '.claudeflow', 'state', 'active-plan.json');
|
|
1533
|
+
try {
|
|
1534
|
+
const r = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
1535
|
+
return send(200, r.content || '', 'text/plain; charset=utf-8');
|
|
1536
|
+
} catch {
|
|
1537
|
+
return send(404, '', 'text/plain');
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1440
1540
|
if (route === '/api/logs') {
|
|
1441
1541
|
const limit = Math.min(parseInt(url.searchParams.get('limit'), 10) || 100, 500);
|
|
1442
1542
|
const before = url.searchParams.get('before') || null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiomatic-labs/claudeflow",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.37",
|
|
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"
|