@axiomatic-labs/claudeflow 2.13.75 → 2.13.77
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 +143 -0
- package/package.json +1 -1
package/lib/panel.js
CHANGED
|
@@ -286,6 +286,29 @@ function getEnforcementInfo(cwd) {
|
|
|
286
286
|
};
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
+
function getValidatorsInfo(cwd) {
|
|
290
|
+
const file = path.join(cwd, '.claudeflow', 'tmp', 'validators-state.json');
|
|
291
|
+
let state = null;
|
|
292
|
+
try {
|
|
293
|
+
const raw = fs.readFileSync(file, 'utf8');
|
|
294
|
+
if (raw.trim()) state = JSON.parse(raw);
|
|
295
|
+
} catch {}
|
|
296
|
+
if (!state || !Array.isArray(state.runs)) {
|
|
297
|
+
return { ok: true, available: false, updated_at: null, runs: [], stats: { total: 0, pass: 0, block: 0, skip: 0, error: 0 } };
|
|
298
|
+
}
|
|
299
|
+
const stats = { total: state.runs.length, pass: 0, block: 0, skip: 0, error: 0 };
|
|
300
|
+
for (const r of state.runs) {
|
|
301
|
+
if (stats[r.status] !== undefined) stats[r.status] += 1;
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
ok: true,
|
|
305
|
+
available: true,
|
|
306
|
+
updated_at: state.updated_at || null,
|
|
307
|
+
runs: state.runs,
|
|
308
|
+
stats,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
289
312
|
function getObserverInfo(cwd) {
|
|
290
313
|
// Read both the canonical state file (full snapshot) AND the summary file
|
|
291
314
|
// (which is what hooks consume — has the incident filter applied).
|
|
@@ -639,6 +662,7 @@ function collectStatus(cwd) {
|
|
|
639
662
|
runToCompletion: getRunToCompletionInfo(cwd),
|
|
640
663
|
cwdLock: getCwdLockInfo(cwd),
|
|
641
664
|
observer: getObserverInfo(cwd),
|
|
665
|
+
validators: getValidatorsInfo(cwd),
|
|
642
666
|
activePlan: getActivePlanInfo(cwd),
|
|
643
667
|
logs: getLogsInfo(cwd),
|
|
644
668
|
doctor: getDoctorInfo(cwd),
|
|
@@ -848,6 +872,7 @@ const SECTIONS = [
|
|
|
848
872
|
{ id: 'reminders', label: 'Reminders' },
|
|
849
873
|
{ id: 'logs', label: 'Logs' },
|
|
850
874
|
{ id: 'observer', label: 'Observer' },
|
|
875
|
+
{ id: 'validators', label: 'Validators' },
|
|
851
876
|
{ id: 'activePlan', label: 'Active plan' },
|
|
852
877
|
{ id: 'mcp', label: 'MCP & observer' },
|
|
853
878
|
{ id: 'setupContext', label: 'Setup context' },
|
|
@@ -859,6 +884,8 @@ let state = null;
|
|
|
859
884
|
let active = 'overview';
|
|
860
885
|
let observerFilter = 'all';
|
|
861
886
|
let observerShowResolved = false;
|
|
887
|
+
let validatorsFilter = 'all';
|
|
888
|
+
let validatorsStatusFilter = 'all';
|
|
862
889
|
let timer = null;
|
|
863
890
|
|
|
864
891
|
// Preserve which <details> were open across re-renders (auto-refresh would
|
|
@@ -1061,6 +1088,7 @@ function renderContent() {
|
|
|
1061
1088
|
reminders: renderReminders,
|
|
1062
1089
|
logs: renderLogs,
|
|
1063
1090
|
observer: renderObserver,
|
|
1091
|
+
validators: renderValidators,
|
|
1064
1092
|
activePlan: renderActivePlan,
|
|
1065
1093
|
mcp: renderMcp,
|
|
1066
1094
|
setupContext: renderSetup,
|
|
@@ -1632,6 +1660,78 @@ function renderObserver() {
|
|
|
1632
1660
|
<div class="card" style="padding:0;">\${incidentsBlock}</div>\`;
|
|
1633
1661
|
}
|
|
1634
1662
|
|
|
1663
|
+
function renderValidators() {
|
|
1664
|
+
const v = (state && state.validators) || { available: false, runs: [], stats: { total: 0, pass: 0, block: 0, skip: 0, error: 0 } };
|
|
1665
|
+
if (!v.available || v.runs.length === 0) {
|
|
1666
|
+
return \`<h2>Validators</h2><p class="muted">No validator runs recorded yet. The security-scan and validate-file hooks log here on every Stop / SubagentStop / TaskCompleted event.</p>\`;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
const validators = [...new Set(v.runs.map((r) => r.validator))].sort();
|
|
1670
|
+
const validatorPill = (label, value, count) => {
|
|
1671
|
+
const a = (validatorsFilter === value);
|
|
1672
|
+
const cls = a ? 'pill pill-active' : 'pill';
|
|
1673
|
+
return \`<button class="\${cls}" data-validators-filter="\${escapeHtml(value)}">\${escapeHtml(label)}<span class="muted" style="margin-left:6px;font-size:11px;">\${count}</span></button>\`;
|
|
1674
|
+
};
|
|
1675
|
+
const statusPill = (label, value, count) => {
|
|
1676
|
+
const a = (validatorsStatusFilter === value);
|
|
1677
|
+
const cls = a ? 'pill pill-active' : 'pill';
|
|
1678
|
+
return \`<button class="\${cls}" data-validators-status-filter="\${escapeHtml(value)}">\${escapeHtml(label)}<span class="muted" style="margin-left:6px;font-size:11px;">\${count}</span></button>\`;
|
|
1679
|
+
};
|
|
1680
|
+
|
|
1681
|
+
const filteredByValidator = validatorsFilter === 'all'
|
|
1682
|
+
? v.runs
|
|
1683
|
+
: v.runs.filter((r) => r.validator === validatorsFilter);
|
|
1684
|
+
const filtered = validatorsStatusFilter === 'all'
|
|
1685
|
+
? filteredByValidator
|
|
1686
|
+
: filteredByValidator.filter((r) => r.status === validatorsStatusFilter);
|
|
1687
|
+
|
|
1688
|
+
const STATUS_BADGE = {
|
|
1689
|
+
pass: '<span style="color:#3fb950;font-weight:600;">pass</span>',
|
|
1690
|
+
block: '<span style="color:#f85149;font-weight:600;">block</span>',
|
|
1691
|
+
skip: '<span class="muted">skip</span>',
|
|
1692
|
+
error: '<span style="color:#d29922;font-weight:600;">error</span>',
|
|
1693
|
+
};
|
|
1694
|
+
|
|
1695
|
+
const runRows = filtered.map((r) => {
|
|
1696
|
+
const when = formatRelativeTime(r.finished_at || r.started_at);
|
|
1697
|
+
const dur = (r.duration_ms && r.duration_ms > 0) ? \` · \${r.duration_ms}ms\` : '';
|
|
1698
|
+
const status = STATUS_BADGE[r.status] || escapeHtml(r.status);
|
|
1699
|
+
const findingsLine = (r.findings && r.findings.length > 0)
|
|
1700
|
+
? \`<details style="margin-top:6px;"><summary class="muted" style="cursor:pointer;font-size:11px;">\${r.findings_count} finding(s) — show</summary><pre style="margin:6px 0 0 0;font-size:11px;white-space:pre-wrap;">\${escapeHtml(JSON.stringify(r.findings, null, 2))}</pre></details>\`
|
|
1701
|
+
: '';
|
|
1702
|
+
return \`<div style="border-bottom:1px solid var(--border);padding:10px 12px;">
|
|
1703
|
+
<div style="display:flex;justify-content:space-between;gap:12px;">
|
|
1704
|
+
<div><strong>\${escapeHtml(r.validator)}</strong> <span class="muted">→</span> \${escapeHtml(r.scope)} <span class="muted" style="margin-left:8px;font-size:11px;">\${escapeHtml(r.hook_event || '')}</span></div>
|
|
1705
|
+
<div style="font-size:12px;">\${status} <span class="muted">· \${when}\${dur}</span></div>
|
|
1706
|
+
</div>
|
|
1707
|
+
<div class="muted" style="font-size:12px;margin-top:4px;">\${escapeHtml(r.reason || '')}</div>
|
|
1708
|
+
\${findingsLine}
|
|
1709
|
+
</div>\`;
|
|
1710
|
+
}).join('');
|
|
1711
|
+
|
|
1712
|
+
const validatorPills = [validatorPill('all', 'all', v.runs.length), ...validators.map((vname) =>
|
|
1713
|
+
validatorPill(vname, vname, v.runs.filter((r) => r.validator === vname).length)
|
|
1714
|
+
)].join(' ');
|
|
1715
|
+
|
|
1716
|
+
const statusPills = ['all', 'pass', 'block', 'skip', 'error'].map((s) =>
|
|
1717
|
+
statusPill(s, s, s === 'all' ? v.runs.length : v.runs.filter((r) => r.status === s).length)
|
|
1718
|
+
).join(' ');
|
|
1719
|
+
|
|
1720
|
+
return \`<h2>Validators
|
|
1721
|
+
<button id="clear-validators" class="btn btn-ghost" style="float:right;font-size:12px;">Clear history</button>
|
|
1722
|
+
</h2>
|
|
1723
|
+
<p class="muted" style="margin-top:0;">Last updated \${formatRelativeTime(v.updated_at)} · \${v.stats.total} total run(s): \${v.stats.pass} pass, \${v.stats.block} block, \${v.stats.skip} skip, \${v.stats.error} error.</p>
|
|
1724
|
+
<div style="margin:12px 0;">
|
|
1725
|
+
<div style="margin-bottom:6px;"><span class="muted" style="font-size:11px;text-transform:uppercase;">Validator</span></div>
|
|
1726
|
+
\${validatorPills}
|
|
1727
|
+
</div>
|
|
1728
|
+
<div style="margin:12px 0;">
|
|
1729
|
+
<div style="margin-bottom:6px;"><span class="muted" style="font-size:11px;text-transform:uppercase;">Status</span></div>
|
|
1730
|
+
\${statusPills}
|
|
1731
|
+
</div>
|
|
1732
|
+
<div class="card" style="padding:0;">\${runRows || '<p class="muted" style="padding:12px;">No runs match the current filter.</p>'}</div>\`;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1635
1735
|
function formatRelativeTime(iso) {
|
|
1636
1736
|
if (!iso) return 'never';
|
|
1637
1737
|
try {
|
|
@@ -1791,8 +1891,40 @@ document.addEventListener('click', (e) => {
|
|
|
1791
1891
|
renderContent();
|
|
1792
1892
|
return;
|
|
1793
1893
|
}
|
|
1894
|
+
if (t.dataset && t.dataset.validatorsFilter) {
|
|
1895
|
+
validatorsFilter = t.dataset.validatorsFilter;
|
|
1896
|
+
renderContent();
|
|
1897
|
+
return;
|
|
1898
|
+
}
|
|
1899
|
+
if (t.dataset && t.dataset.validatorsStatusFilter) {
|
|
1900
|
+
validatorsStatusFilter = t.dataset.validatorsStatusFilter;
|
|
1901
|
+
renderContent();
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1904
|
+
if (t.id === 'clear-validators') {
|
|
1905
|
+
return clearValidatorsAction(t);
|
|
1906
|
+
}
|
|
1794
1907
|
});
|
|
1795
1908
|
|
|
1909
|
+
async function clearValidatorsAction(btn) {
|
|
1910
|
+
if (!confirm('Clear validator run history? This wipes the log shown in this tab. Validators continue running.')) return;
|
|
1911
|
+
const originalLabel = btn.textContent;
|
|
1912
|
+
btn.disabled = true;
|
|
1913
|
+
btn.textContent = 'Clearing…';
|
|
1914
|
+
try {
|
|
1915
|
+
const r = await fetch('/api/validators/clear', { method: 'POST' });
|
|
1916
|
+
const result = await r.json();
|
|
1917
|
+
if (r.ok && result.ok) showToast('Validator history cleared', 'ok');
|
|
1918
|
+
else showToast('Clear failed: ' + (result.error || 'unknown'), 'err');
|
|
1919
|
+
} catch (e) {
|
|
1920
|
+
showToast('Network error: ' + e.message, 'err');
|
|
1921
|
+
} finally {
|
|
1922
|
+
btn.disabled = false;
|
|
1923
|
+
btn.textContent = originalLabel;
|
|
1924
|
+
await refresh();
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1796
1928
|
async function clearObserverAction(btn) {
|
|
1797
1929
|
if (!confirm('Resolve all active observer incidents and clear server logs?\\n\\nIncident history is preserved in state.incidents (marked resolved). Future events repopulate normally. This cannot be undone.')) return;
|
|
1798
1930
|
const originalLabel = btn.textContent;
|
|
@@ -2220,6 +2352,16 @@ function handler(cwd) {
|
|
|
2220
2352
|
return send(502, JSON.stringify({ ok: false, error: 'observer returned ' + result.status, daemon_response: result.body }), 'application/json');
|
|
2221
2353
|
}
|
|
2222
2354
|
|
|
2355
|
+
if (req.method === 'POST' && route === '/api/validators/clear') {
|
|
2356
|
+
try {
|
|
2357
|
+
const validatorState = require(path.join(cwd, '.claudeflow', 'runtime', 'validator-state.js'));
|
|
2358
|
+
validatorState.clearState(cwd);
|
|
2359
|
+
return send(200, JSON.stringify({ ok: true }), 'application/json');
|
|
2360
|
+
} catch (err) {
|
|
2361
|
+
return send(500, JSON.stringify({ ok: false, error: err.message }), 'application/json');
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2223
2365
|
if (req.method === 'POST' && route === '/api/observer/restart') {
|
|
2224
2366
|
// Re-run the SessionStart/browser-error-daemon.js hook script
|
|
2225
2367
|
// synchronously. The script calls ensureObserver() which spawns
|
|
@@ -2389,6 +2531,7 @@ module.exports.start = start;
|
|
|
2389
2531
|
module.exports.createPanelServer = handler;
|
|
2390
2532
|
module.exports.derivePanelPort = derivePanelPort;
|
|
2391
2533
|
module.exports.getObserverInfo = getObserverInfo;
|
|
2534
|
+
module.exports.getValidatorsInfo = getValidatorsInfo;
|
|
2392
2535
|
module.exports.collectStatus = collectStatus;
|
|
2393
2536
|
module.exports.getClaudeMdInfo = getClaudeMdInfo;
|
|
2394
2537
|
module.exports.getHooksInfo = getHooksInfo;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiomatic-labs/claudeflow",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.77",
|
|
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"
|