MCPcopy Create free account
hub / github.com/SethGammon/Citadel / classifyHookProblem

Function classifyHookProblem

scripts/dashboard.js:737–803  ·  view source on GitHub ↗
(entry, now = new Date(), context = {})

Source from the content-addressed store, hash-verified

735 const detail = entry.detail || entry.reason || entry.message || entry.error || '';
736 const command = normalizeCommandForMatch(extractBlockedCommand(detail));
737 if (!command) return false;
738
739 const blockedAt = new Date(eventTimestamp(entry) || 0).getTime();
740 return auditEntries.some((auditEntry) => {
741 if ((auditEntry.event || '') !== 'tool-call') return false;
742 const target = normalizeCommandForMatch(auditEntry.target || auditEntry.command || '');
743 if (!target || target !== command) return false;
744 const calledAt = new Date(eventTimestamp(auditEntry) || 0).getTime();
745 return calledAt + 5000 >= blockedAt;
746 });
747}
748
749function classifyHookProblem(entry, now = new Date(), context = {}) {
750 const hook = entry.hook || 'hook';
751 const actionName = entry.action || entry.outcome || entry.status || 'recorded';
752 const detail = entry.detail || entry.reason || entry.message || entry.error || entry.action || 'hook issue recorded';
753 const timestamp = eventTimestamp(entry);
754 const ageMs = timestamp ? now.getTime() - new Date(timestamp).getTime() : 0;
755 const stale = timestamp ? ageMs > 24 * 60 * 60 * 1000 : false;
756 const description = truncate(detail, 90);
757 const text = `${hook} ${actionName} ${detail}`.toLowerCase();
758
759 let category = 'attention';
760 let severity = 'medium';
761 let actionable = true;
762
763 if (stale) {
764 category = 'stale';
765 severity = 'low';
766 actionable = false;
767 } else if (actionName === 'error' || actionName === 'parse-fail' || text.includes('unknown error')) {
768 category = 'hook-failure';
769 severity = 'high';
770 actionable = true;
771 } else if (actionName === 'blocked-restricted') {
772 category = 'restricted-scope-block';
773 severity = 'high';
774 actionable = true;
775 } else if (
776 hook === 'external-action-gate' &&
777 (actionName === 'first-encounter' || actionName === 'consent-block') &&
778 hasLaterMatchingToolCall(entry, context.auditEntries || [])
779 ) {
780 category = 'resolved-approval';
781 severity = 'info';
782 actionable = false;
783 } else if (
784 hook === 'external-action-gate' &&
785 (actionName === 'first-encounter' || actionName === 'consent-block') &&
786 timestamp &&
787 ageMs > 15 * 60 * 1000
788 ) {
789 category = 'stale-approval';
790 severity = 'low';
791 actionable = false;
792 } else if (hook === 'external-action-gate' && (actionName === 'first-encounter' || actionName === 'consent-block')) {
793 category = 'approval-needed';
794 severity = 'medium';

Callers 2

readProblemsFunction · 0.85
test-dashboard.jsFile · 0.85

Calls 4

eventTimestampFunction · 0.85
truncateFunction · 0.85
hasLaterMatchingToolCallFunction · 0.85
relativeTimeFunction · 0.85

Tested by

no test coverage detected