@axiomatic-labs/claudeflow 2.13.76 → 2.13.78
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 +188 -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,9 @@ 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';
|
|
889
|
+
let validatorsScopeFilter = 'all';
|
|
862
890
|
let timer = null;
|
|
863
891
|
|
|
864
892
|
// Preserve which <details> were open across re-renders (auto-refresh would
|
|
@@ -1061,6 +1089,7 @@ function renderContent() {
|
|
|
1061
1089
|
reminders: renderReminders,
|
|
1062
1090
|
logs: renderLogs,
|
|
1063
1091
|
observer: renderObserver,
|
|
1092
|
+
validators: renderValidators,
|
|
1064
1093
|
activePlan: renderActivePlan,
|
|
1065
1094
|
mcp: renderMcp,
|
|
1066
1095
|
setupContext: renderSetup,
|
|
@@ -1632,6 +1661,102 @@ function renderObserver() {
|
|
|
1632
1661
|
<div class="card" style="padding:0;">\${incidentsBlock}</div>\`;
|
|
1633
1662
|
}
|
|
1634
1663
|
|
|
1664
|
+
function renderValidators() {
|
|
1665
|
+
const v = (state && state.validators) || { available: false, runs: [], stats: { total: 0, pass: 0, block: 0, skip: 0, error: 0 } };
|
|
1666
|
+
if (!v.available || v.runs.length === 0) {
|
|
1667
|
+
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>\`;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
const validators = [...new Set(v.runs.map((r) => r.validator))].sort();
|
|
1671
|
+
const scopes = [...new Set(v.runs.map((r) => r.scope))].sort();
|
|
1672
|
+
const validatorPill = (label, value, count) => {
|
|
1673
|
+
const a = (validatorsFilter === value);
|
|
1674
|
+
const cls = a ? 'pill pill-active' : 'pill';
|
|
1675
|
+
return \`<button class="\${cls}" data-validators-filter="\${escapeHtml(value)}">\${escapeHtml(label)}<span class="muted" style="margin-left:6px;font-size:11px;">\${count}</span></button>\`;
|
|
1676
|
+
};
|
|
1677
|
+
const statusPill = (label, value, count) => {
|
|
1678
|
+
const a = (validatorsStatusFilter === value);
|
|
1679
|
+
const cls = a ? 'pill pill-active' : 'pill';
|
|
1680
|
+
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>\`;
|
|
1681
|
+
};
|
|
1682
|
+
const scopePill = (label, value, count) => {
|
|
1683
|
+
const a = (validatorsScopeFilter === value);
|
|
1684
|
+
const cls = a ? 'pill pill-active' : 'pill';
|
|
1685
|
+
return \`<button class="\${cls}" data-validators-scope-filter="\${escapeHtml(value)}">\${escapeHtml(label)}<span class="muted" style="margin-left:6px;font-size:11px;">\${count}</span></button>\`;
|
|
1686
|
+
};
|
|
1687
|
+
|
|
1688
|
+
const filteredByValidator = validatorsFilter === 'all'
|
|
1689
|
+
? v.runs
|
|
1690
|
+
: v.runs.filter((r) => r.validator === validatorsFilter);
|
|
1691
|
+
const filteredByScope = validatorsScopeFilter === 'all'
|
|
1692
|
+
? filteredByValidator
|
|
1693
|
+
: filteredByValidator.filter((r) => r.scope === validatorsScopeFilter);
|
|
1694
|
+
const filtered = validatorsStatusFilter === 'all'
|
|
1695
|
+
? filteredByScope
|
|
1696
|
+
: filteredByScope.filter((r) => r.status === validatorsStatusFilter);
|
|
1697
|
+
|
|
1698
|
+
const STATUS_BADGE = {
|
|
1699
|
+
pass: '<span style="color:#3fb950;font-weight:600;">pass</span>',
|
|
1700
|
+
block: '<span style="color:#f85149;font-weight:600;">block</span>',
|
|
1701
|
+
skip: '<span class="muted">skip</span>',
|
|
1702
|
+
error: '<span style="color:#d29922;font-weight:600;">error</span>',
|
|
1703
|
+
};
|
|
1704
|
+
|
|
1705
|
+
const runRows = filtered.map((r) => {
|
|
1706
|
+
const when = formatRelativeTime(r.finished_at || r.started_at);
|
|
1707
|
+
const dur = (r.duration_ms && r.duration_ms > 0) ? \` · \${r.duration_ms}ms\` : '';
|
|
1708
|
+
const status = STATUS_BADGE[r.status] || escapeHtml(r.status);
|
|
1709
|
+
const findingsLine = (r.findings && r.findings.length > 0)
|
|
1710
|
+
? \`<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>\`
|
|
1711
|
+
: '';
|
|
1712
|
+
return \`<div style="border-bottom:1px solid var(--border);padding:10px 12px;">
|
|
1713
|
+
<div style="display:flex;justify-content:space-between;gap:12px;">
|
|
1714
|
+
<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>
|
|
1715
|
+
<div style="font-size:12px;">\${status} <span class="muted">· \${when}\${dur}</span></div>
|
|
1716
|
+
</div>
|
|
1717
|
+
<div class="muted" style="font-size:12px;margin-top:4px;">\${escapeHtml(r.reason || '')}</div>
|
|
1718
|
+
\${findingsLine}
|
|
1719
|
+
</div>\`;
|
|
1720
|
+
}).join('');
|
|
1721
|
+
|
|
1722
|
+
const validatorPills = [validatorPill('all', 'all', v.runs.length), ...validators.map((vname) =>
|
|
1723
|
+
validatorPill(vname, vname, v.runs.filter((r) => r.validator === vname).length)
|
|
1724
|
+
)].join(' ');
|
|
1725
|
+
|
|
1726
|
+
const scopePills = [scopePill('all', 'all', v.runs.length), ...scopes.map((s) =>
|
|
1727
|
+
scopePill(s, s, v.runs.filter((r) => r.scope === s).length)
|
|
1728
|
+
)].join(' ');
|
|
1729
|
+
|
|
1730
|
+
const statusPills = ['all', 'pass', 'block', 'skip', 'error'].map((s) =>
|
|
1731
|
+
statusPill(s, s, s === 'all' ? v.runs.length : v.runs.filter((r) => r.status === s).length)
|
|
1732
|
+
).join(' ');
|
|
1733
|
+
|
|
1734
|
+
// Clear button label changes when a scope filter is active so the user
|
|
1735
|
+
// sees the action they're about to take. "Clear (Admin-Office)" is
|
|
1736
|
+
// unambiguous; "Clear history" wiped everything pre-v2.13.78.
|
|
1737
|
+
const clearLabel = validatorsScopeFilter === 'all'
|
|
1738
|
+
? 'Clear all history'
|
|
1739
|
+
: \`Clear "\${escapeHtml(validatorsScopeFilter)}" history\`;
|
|
1740
|
+
|
|
1741
|
+
return \`<h2>Validators
|
|
1742
|
+
<button id="clear-validators" class="btn btn-ghost" style="float:right;font-size:12px;" data-scope="\${escapeHtml(validatorsScopeFilter)}">\${clearLabel}</button>
|
|
1743
|
+
</h2>
|
|
1744
|
+
<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>
|
|
1745
|
+
<div style="margin:12px 0;">
|
|
1746
|
+
<div style="margin-bottom:6px;"><span class="muted" style="font-size:11px;text-transform:uppercase;">Validator</span></div>
|
|
1747
|
+
\${validatorPills}
|
|
1748
|
+
</div>
|
|
1749
|
+
<div style="margin:12px 0;">
|
|
1750
|
+
<div style="margin-bottom:6px;"><span class="muted" style="font-size:11px;text-transform:uppercase;">Scope</span></div>
|
|
1751
|
+
\${scopePills}
|
|
1752
|
+
</div>
|
|
1753
|
+
<div style="margin:12px 0;">
|
|
1754
|
+
<div style="margin-bottom:6px;"><span class="muted" style="font-size:11px;text-transform:uppercase;">Status</span></div>
|
|
1755
|
+
\${statusPills}
|
|
1756
|
+
</div>
|
|
1757
|
+
<div class="card" style="padding:0;">\${runRows || '<p class="muted" style="padding:12px;">No runs match the current filter.</p>'}</div>\`;
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1635
1760
|
function formatRelativeTime(iso) {
|
|
1636
1761
|
if (!iso) return 'never';
|
|
1637
1762
|
try {
|
|
@@ -1791,8 +1916,57 @@ document.addEventListener('click', (e) => {
|
|
|
1791
1916
|
renderContent();
|
|
1792
1917
|
return;
|
|
1793
1918
|
}
|
|
1919
|
+
if (t.dataset && t.dataset.validatorsFilter) {
|
|
1920
|
+
validatorsFilter = t.dataset.validatorsFilter;
|
|
1921
|
+
renderContent();
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
if (t.dataset && t.dataset.validatorsStatusFilter) {
|
|
1925
|
+
validatorsStatusFilter = t.dataset.validatorsStatusFilter;
|
|
1926
|
+
renderContent();
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
if (t.dataset && t.dataset.validatorsScopeFilter) {
|
|
1930
|
+
validatorsScopeFilter = t.dataset.validatorsScopeFilter;
|
|
1931
|
+
renderContent();
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
if (t.id === 'clear-validators') {
|
|
1935
|
+
return clearValidatorsAction(t);
|
|
1936
|
+
}
|
|
1794
1937
|
});
|
|
1795
1938
|
|
|
1939
|
+
async function clearValidatorsAction(btn) {
|
|
1940
|
+
const scope = (btn.dataset && btn.dataset.scope) || 'all';
|
|
1941
|
+
const isScoped = scope !== 'all';
|
|
1942
|
+
const message = isScoped
|
|
1943
|
+
? \`Clear validator history for scope "\${scope}"? Runs from other scopes are preserved. Validators continue running.\`
|
|
1944
|
+
: 'Clear ALL validator run history? This wipes the entire log. Validators continue running.';
|
|
1945
|
+
if (!confirm(message)) return;
|
|
1946
|
+
const originalLabel = btn.textContent;
|
|
1947
|
+
btn.disabled = true;
|
|
1948
|
+
btn.textContent = 'Clearing…';
|
|
1949
|
+
try {
|
|
1950
|
+
const url = isScoped
|
|
1951
|
+
? '/api/validators/clear?scope=' + encodeURIComponent(scope)
|
|
1952
|
+
: '/api/validators/clear';
|
|
1953
|
+
const r = await fetch(url, { method: 'POST' });
|
|
1954
|
+
const result = await r.json();
|
|
1955
|
+
if (r.ok && result.ok) {
|
|
1956
|
+
const removed = typeof result.removed === 'number' ? \` (removed \${result.removed})\` : '';
|
|
1957
|
+
showToast(isScoped ? \`Cleared "\${scope}"\${removed}\` : \`Cleared all history\${removed}\`, 'ok');
|
|
1958
|
+
} else {
|
|
1959
|
+
showToast('Clear failed: ' + (result.error || 'unknown'), 'err');
|
|
1960
|
+
}
|
|
1961
|
+
} catch (e) {
|
|
1962
|
+
showToast('Network error: ' + e.message, 'err');
|
|
1963
|
+
} finally {
|
|
1964
|
+
btn.disabled = false;
|
|
1965
|
+
btn.textContent = originalLabel;
|
|
1966
|
+
await refresh();
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1796
1970
|
async function clearObserverAction(btn) {
|
|
1797
1971
|
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
1972
|
const originalLabel = btn.textContent;
|
|
@@ -2220,6 +2394,19 @@ function handler(cwd) {
|
|
|
2220
2394
|
return send(502, JSON.stringify({ ok: false, error: 'observer returned ' + result.status, daemon_response: result.body }), 'application/json');
|
|
2221
2395
|
}
|
|
2222
2396
|
|
|
2397
|
+
if (req.method === 'POST' && route === '/api/validators/clear') {
|
|
2398
|
+
try {
|
|
2399
|
+
const validatorState = require(path.join(cwd, '.claudeflow', 'runtime', 'validator-state.js'));
|
|
2400
|
+
const scope = url.searchParams.get('scope');
|
|
2401
|
+
const removed = scope && scope !== 'all'
|
|
2402
|
+
? validatorState.clearScope(cwd, scope)
|
|
2403
|
+
: (() => { const s = validatorState.readState(cwd); const n = s ? s.runs.length : 0; validatorState.clearState(cwd); return n; })();
|
|
2404
|
+
return send(200, JSON.stringify({ ok: true, removed, scope: scope || 'all' }), 'application/json');
|
|
2405
|
+
} catch (err) {
|
|
2406
|
+
return send(500, JSON.stringify({ ok: false, error: err.message }), 'application/json');
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2223
2410
|
if (req.method === 'POST' && route === '/api/observer/restart') {
|
|
2224
2411
|
// Re-run the SessionStart/browser-error-daemon.js hook script
|
|
2225
2412
|
// synchronously. The script calls ensureObserver() which spawns
|
|
@@ -2389,6 +2576,7 @@ module.exports.start = start;
|
|
|
2389
2576
|
module.exports.createPanelServer = handler;
|
|
2390
2577
|
module.exports.derivePanelPort = derivePanelPort;
|
|
2391
2578
|
module.exports.getObserverInfo = getObserverInfo;
|
|
2579
|
+
module.exports.getValidatorsInfo = getValidatorsInfo;
|
|
2392
2580
|
module.exports.collectStatus = collectStatus;
|
|
2393
2581
|
module.exports.getClaudeMdInfo = getClaudeMdInfo;
|
|
2394
2582
|
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.78",
|
|
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"
|