pumuki-ast-hooks 5.3.18 → 5.3.19
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/docs/VIOLATIONS_RESOLUTION_PLAN.md +23 -25
- package/package.json +2 -2
- package/scripts/hooks-system/application/CompositionRoot.js +24 -73
- package/scripts/hooks-system/application/services/DynamicRulesLoader.js +1 -2
- package/scripts/hooks-system/application/services/GitTreeState.js +139 -13
- package/scripts/hooks-system/application/services/HookSystemScheduler.js +43 -0
- package/scripts/hooks-system/application/services/PlaybookRunner.js +1 -1
- package/scripts/hooks-system/application/services/RealtimeGuardService.js +15 -85
- package/scripts/hooks-system/application/services/guard/GuardAutoManagerService.js +2 -31
- package/scripts/hooks-system/application/services/guard/GuardConfig.js +9 -17
- package/scripts/hooks-system/application/services/guard/GuardHeartbeatMonitor.js +9 -6
- package/scripts/hooks-system/application/services/guard/GuardProcessManager.js +0 -29
- package/scripts/hooks-system/application/services/installation/GitEnvironmentService.js +1 -4
- package/scripts/hooks-system/application/services/installation/McpConfigurator.js +1 -2
- package/scripts/hooks-system/application/services/logging/AuditLogger.js +86 -1
- package/scripts/hooks-system/application/services/logging/UnifiedLogger.js +4 -13
- package/scripts/hooks-system/application/services/monitoring/EvidenceMonitor.js +1 -0
- package/scripts/hooks-system/application/services/monitoring/EvidenceMonitorService.js +3 -7
- package/scripts/hooks-system/application/services/token/TokenMetricsService.js +1 -14
- package/scripts/hooks-system/bin/__tests__/evidence-update.spec.js +49 -0
- package/scripts/hooks-system/bin/cli.js +1 -15
- package/scripts/hooks-system/domain/events/index.js +6 -32
- package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidAnalysisOrchestrator.js +2 -3
- package/scripts/hooks-system/infrastructure/ast/ast-core.js +20 -12
- package/scripts/hooks-system/infrastructure/ast/ast-intelligence.js +18 -8
- package/scripts/hooks-system/infrastructure/ast/backend/analyzers/BackendPatternDetector.js +1 -2
- package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +8 -10
- package/scripts/hooks-system/infrastructure/ast/frontend/ast-frontend.js +196 -196
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSASTIntelligentAnalyzer.spec.js +66 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSASTIntelligentAnalyzer.js +2 -3
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSArchitectureRules.js +24 -86
- package/scripts/hooks-system/infrastructure/hooks/skill-activation-prompt.js +2 -3
- package/scripts/hooks-system/infrastructure/logging/UnifiedLoggerFactory.js +5 -35
- package/scripts/hooks-system/infrastructure/orchestration/intelligent-audit.js +16 -86
- package/scripts/hooks-system/infrastructure/telemetry/metrics-server.js +2 -51
- package/scripts/hooks-system/infrastructure/validators/enforce-english-literals.js +8 -6
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { getGitTreeState, isTreeBeyondLimit } = require('./GitTreeState');
|
|
4
|
-
const AuditLogger = require('./logging/AuditLogger');
|
|
5
|
-
const { recordMetric } = require('../../infrastructure/telemetry/metrics-logger');
|
|
6
|
-
const env = require('../../config/env');
|
|
7
4
|
|
|
8
5
|
class RealtimeGuardService {
|
|
9
6
|
/**
|
|
@@ -15,12 +12,6 @@ class RealtimeGuardService {
|
|
|
15
12
|
* @param {Object} dependencies.config
|
|
16
13
|
*/
|
|
17
14
|
constructor(dependencies = {}) {
|
|
18
|
-
this._initializeDependencies(dependencies);
|
|
19
|
-
this._initializeConfiguration();
|
|
20
|
-
this._initializeMonitors();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
_initializeDependencies(dependencies = {}) {
|
|
24
15
|
const {
|
|
25
16
|
logger,
|
|
26
17
|
notificationService,
|
|
@@ -39,49 +30,39 @@ class RealtimeGuardService {
|
|
|
39
30
|
this.monitors = monitors || {};
|
|
40
31
|
this.orchestration = orchestration;
|
|
41
32
|
this.config = config || {};
|
|
42
|
-
this.auditLogger = dependencies.auditLogger || new AuditLogger({ repoRoot: process.cwd(), logger: this.logger });
|
|
43
|
-
}
|
|
44
33
|
|
|
45
|
-
_initializeConfiguration() {
|
|
46
34
|
if (!this.config.debugLogPath) {
|
|
47
35
|
this.config.debugLogPath = path.join(process.cwd(), '.audit-reports', 'guard-debug.log');
|
|
48
36
|
}
|
|
49
37
|
|
|
50
|
-
this.evidencePath = this.config.evidencePath || path.join(
|
|
51
|
-
this.staleThresholdMs = env.
|
|
52
|
-
this.reminderIntervalMs = env.
|
|
53
|
-
this.inactivityGraceMs = env.
|
|
54
|
-
this.pollIntervalMs = env.
|
|
38
|
+
this.evidencePath = this.config.evidencePath || path.join(process.cwd(), '.AI_EVIDENCE.json');
|
|
39
|
+
this.staleThresholdMs = Number(process.env.HOOK_GUARD_EVIDENCE_STALE_THRESHOLD || 60000);
|
|
40
|
+
this.reminderIntervalMs = Number(process.env.HOOK_GUARD_EVIDENCE_REMINDER_INTERVAL || 60000);
|
|
41
|
+
this.inactivityGraceMs = Number(process.env.HOOK_GUARD_INACTIVITY_GRACE_MS || 120000);
|
|
42
|
+
this.pollIntervalMs = Number(process.env.HOOK_GUARD_EVIDENCE_POLL_INTERVAL || 30000);
|
|
55
43
|
this.pollTimer = null;
|
|
56
44
|
this.lastStaleNotification = 0;
|
|
57
45
|
this.lastUserActivityAt = 0;
|
|
58
46
|
|
|
59
|
-
this.gitTreeStagedThreshold = env.
|
|
60
|
-
this.gitTreeUnstagedThreshold = env.
|
|
61
|
-
this.gitTreeTotalThreshold = env.
|
|
62
|
-
this.gitTreeCheckIntervalMs = env.
|
|
63
|
-
this.gitTreeReminderMs = env.
|
|
47
|
+
this.gitTreeStagedThreshold = Number(process.env.HOOK_GUARD_DIRTY_TREE_STAGED_LIMIT || 10);
|
|
48
|
+
this.gitTreeUnstagedThreshold = Number(process.env.HOOK_GUARD_DIRTY_TREE_UNSTAGED_LIMIT || 15);
|
|
49
|
+
this.gitTreeTotalThreshold = Number(process.env.HOOK_GUARD_DIRTY_TREE_TOTAL_LIMIT || 20);
|
|
50
|
+
this.gitTreeCheckIntervalMs = Number(process.env.HOOK_GUARD_DIRTY_TREE_INTERVAL || 60000);
|
|
51
|
+
this.gitTreeReminderMs = Number(process.env.HOOK_GUARD_DIRTY_TREE_REMINDER || 300000);
|
|
64
52
|
this.gitTreeTimer = null;
|
|
65
53
|
this.lastDirtyTreeNotification = 0;
|
|
66
54
|
this.dirtyTreeActive = false;
|
|
67
55
|
|
|
68
|
-
this.autoRefreshCooldownMs = env.
|
|
56
|
+
this.autoRefreshCooldownMs = Number(process.env.HOOK_GUARD_EVIDENCE_AUTO_REFRESH_COOLDOWN || 180000);
|
|
69
57
|
this.lastAutoRefresh = 0;
|
|
70
58
|
this.autoRefreshInFlight = false;
|
|
71
59
|
|
|
72
60
|
this.watchers = [];
|
|
73
|
-
this.embedTokenMonitor = env.
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
_initializeMonitors() {
|
|
77
|
-
// Monitors are configured but not started here
|
|
78
|
-
// They are started in the start() method to allow for proper initialization order
|
|
61
|
+
this.embedTokenMonitor = process.env.HOOK_GUARD_EMBEDDED_TOKEN_MONITOR === 'true';
|
|
79
62
|
}
|
|
80
63
|
|
|
81
64
|
start() {
|
|
82
65
|
this.logger.info('Starting RealtimeGuardService...');
|
|
83
|
-
this.auditLogger.record({ action: 'guard.realtime.start', resource: 'realtime_guard', status: 'success' });
|
|
84
|
-
recordMetric({ hook: 'realtime_guard', status: 'start' });
|
|
85
66
|
|
|
86
67
|
// Start all monitors
|
|
87
68
|
this._startEvidenceMonitoring();
|
|
@@ -101,8 +82,6 @@ class RealtimeGuardService {
|
|
|
101
82
|
|
|
102
83
|
stop() {
|
|
103
84
|
this.logger.info('Stopping RealtimeGuardService...');
|
|
104
|
-
this.auditLogger.record({ action: 'guard.realtime.stop', resource: 'realtime_guard', status: 'success' });
|
|
105
|
-
recordMetric({ hook: 'realtime_guard', status: 'stop' });
|
|
106
85
|
|
|
107
86
|
this.watchers.forEach(w => w.close());
|
|
108
87
|
this.watchers = [];
|
|
@@ -124,27 +103,14 @@ class RealtimeGuardService {
|
|
|
124
103
|
}
|
|
125
104
|
|
|
126
105
|
_startGitTreeMonitoring() {
|
|
127
|
-
if (env.
|
|
106
|
+
if (process.env.HOOK_GUARD_DIRTY_TREE_DISABLED === 'true') return;
|
|
128
107
|
|
|
129
108
|
this.monitors.gitTree.startMonitoring((state) => {
|
|
130
109
|
if (state.isBeyondLimit) {
|
|
131
110
|
const message = `Git tree has too many files: ${state.total} total (${state.staged} staged, ${state.unstaged} unstaged)`;
|
|
132
111
|
this.notify(message, 'error', { forceDialog: true });
|
|
133
|
-
this.auditLogger.record({
|
|
134
|
-
action: 'guard.git_tree.dirty',
|
|
135
|
-
resource: 'git_tree',
|
|
136
|
-
status: 'warning',
|
|
137
|
-
meta: { total: state.total, staged: state.staged, unstaged: state.unstaged }
|
|
138
|
-
});
|
|
139
|
-
recordMetric({ hook: 'git_tree', status: 'dirty', total: state.total, staged: state.staged, unstaged: state.unstaged });
|
|
140
112
|
} else {
|
|
141
113
|
this.notify('✅ Git tree is clean', 'success');
|
|
142
|
-
this.auditLogger.record({
|
|
143
|
-
action: 'guard.git_tree.clean',
|
|
144
|
-
resource: 'git_tree',
|
|
145
|
-
status: 'success'
|
|
146
|
-
});
|
|
147
|
-
recordMetric({ hook: 'git_tree', status: 'clean' });
|
|
148
114
|
}
|
|
149
115
|
});
|
|
150
116
|
}
|
|
@@ -158,44 +124,22 @@ class RealtimeGuardService {
|
|
|
158
124
|
try {
|
|
159
125
|
this.monitors.token.start();
|
|
160
126
|
this.notify('🔋 Token monitor started', 'info');
|
|
161
|
-
this.auditLogger.record({
|
|
162
|
-
action: 'guard.token_monitor.start',
|
|
163
|
-
resource: 'token_monitor',
|
|
164
|
-
status: 'success'
|
|
165
|
-
});
|
|
166
|
-
recordMetric({ hook: 'token_monitor', status: 'start' });
|
|
167
127
|
} catch (error) {
|
|
168
128
|
this.notify(`Failed to start token monitor: ${error.message}`, 'error');
|
|
169
|
-
this.auditLogger.record({
|
|
170
|
-
action: 'guard.token_monitor.start',
|
|
171
|
-
resource: 'token_monitor',
|
|
172
|
-
status: 'fail',
|
|
173
|
-
meta: { message: error.message }
|
|
174
|
-
});
|
|
175
|
-
recordMetric({ hook: 'token_monitor', status: 'fail' });
|
|
176
129
|
}
|
|
177
130
|
}
|
|
178
131
|
|
|
179
132
|
_startGitFlowSync() {
|
|
180
133
|
if (!this.monitors.gitFlow.autoSyncEnabled) return;
|
|
181
134
|
|
|
182
|
-
this.auditLogger.record({
|
|
183
|
-
action: 'guard.gitflow.autosync.enabled',
|
|
184
|
-
resource: 'gitflow',
|
|
185
|
-
status: 'success',
|
|
186
|
-
meta: { intervalMs: env.getNumber('HOOK_GUARD_GITFLOW_AUTOSYNC_INTERVAL', 300000) }
|
|
187
|
-
});
|
|
188
|
-
recordMetric({ hook: 'gitflow_autosync', status: 'enabled' });
|
|
189
|
-
|
|
190
135
|
const syncInterval = setInterval(() => {
|
|
191
136
|
if (this.monitors.gitFlow.isClean()) {
|
|
192
137
|
const result = this.monitors.gitFlow.syncBranches();
|
|
193
138
|
if (result.success) {
|
|
194
139
|
this.notify('🔄 Branches synchronized', 'info');
|
|
195
|
-
recordMetric({ hook: 'gitflow_autosync', status: 'sync_success' });
|
|
196
140
|
}
|
|
197
141
|
}
|
|
198
|
-
}, env.
|
|
142
|
+
}, Number(process.env.HOOK_GUARD_GITFLOW_AUTOSYNC_INTERVAL || 300000));
|
|
199
143
|
|
|
200
144
|
syncInterval.unref();
|
|
201
145
|
}
|
|
@@ -303,18 +247,11 @@ class RealtimeGuardService {
|
|
|
303
247
|
this.lastStaleNotification = now;
|
|
304
248
|
const ageSec = Math.floor(ageMs / 1000);
|
|
305
249
|
this.notify(`Evidence has been stale for ${ageSec}s (source: ${source}).`, 'warn', { forceDialog: true });
|
|
306
|
-
this.auditLogger.record({
|
|
307
|
-
action: 'guard.evidence.stale',
|
|
308
|
-
resource: 'evidence',
|
|
309
|
-
status: 'warning',
|
|
310
|
-
meta: { ageSec, source }
|
|
311
|
-
});
|
|
312
|
-
recordMetric({ hook: 'evidence', status: 'stale', ageSec, source });
|
|
313
250
|
void this.attemptAutoRefresh('stale');
|
|
314
251
|
}
|
|
315
252
|
|
|
316
253
|
async attemptAutoRefresh(reason = 'manual') {
|
|
317
|
-
if (
|
|
254
|
+
if (process.env.HOOK_GUARD_AUTO_REFRESH !== 'true') {
|
|
318
255
|
return;
|
|
319
256
|
}
|
|
320
257
|
|
|
@@ -347,13 +284,6 @@ class RealtimeGuardService {
|
|
|
347
284
|
try {
|
|
348
285
|
await this.runDirectEvidenceRefresh(reason);
|
|
349
286
|
this.lastAutoRefresh = now;
|
|
350
|
-
this.auditLogger.record({
|
|
351
|
-
action: 'guard.evidence.auto_refresh',
|
|
352
|
-
resource: 'evidence',
|
|
353
|
-
status: 'success',
|
|
354
|
-
meta: { reason }
|
|
355
|
-
});
|
|
356
|
-
recordMetric({ hook: 'evidence', status: 'auto_refresh_success', reason });
|
|
357
287
|
} finally {
|
|
358
288
|
this.autoRefreshInFlight = false;
|
|
359
289
|
}
|
|
@@ -10,9 +10,6 @@ const GuardConfig = require('./GuardConfig');
|
|
|
10
10
|
const GuardNotificationHandler = require('./GuardNotificationHandler');
|
|
11
11
|
const GuardMonitorLoop = require('./GuardMonitorLoop');
|
|
12
12
|
const GuardHealthReminder = require('./GuardHealthReminder');
|
|
13
|
-
const AuditLogger = require('../logging/AuditLogger');
|
|
14
|
-
const envHelper = require('../../../config/env');
|
|
15
|
-
const { recordMetric } = require('../../../infrastructure/telemetry/metrics-logger');
|
|
16
13
|
|
|
17
14
|
class GuardAutoManagerService {
|
|
18
15
|
constructor({
|
|
@@ -22,10 +19,9 @@ class GuardAutoManagerService {
|
|
|
22
19
|
fsModule = fs,
|
|
23
20
|
childProcess = { spawnSync },
|
|
24
21
|
timers = { setInterval, clearInterval },
|
|
25
|
-
env =
|
|
22
|
+
env = process.env,
|
|
26
23
|
processRef = process,
|
|
27
|
-
heartbeatMonitor = null
|
|
28
|
-
auditLogger = null
|
|
24
|
+
heartbeatMonitor = null
|
|
29
25
|
} = {}) {
|
|
30
26
|
this.process = processRef;
|
|
31
27
|
|
|
@@ -34,7 +30,6 @@ class GuardAutoManagerService {
|
|
|
34
30
|
this.eventLogger = new GuardEventLogger({ repoRoot, logger, fsModule });
|
|
35
31
|
this.lockManager = new GuardLockManager({ repoRoot, logger, fsModule });
|
|
36
32
|
this.processManager = new GuardProcessManager({ repoRoot, logger, fsModule, childProcess });
|
|
37
|
-
this.auditLogger = auditLogger || new AuditLogger({ repoRoot, logger });
|
|
38
33
|
|
|
39
34
|
// Monitors & Handlers
|
|
40
35
|
this.heartbeatMonitor = heartbeatMonitor || new GuardHeartbeatMonitor({
|
|
@@ -64,14 +59,10 @@ class GuardAutoManagerService {
|
|
|
64
59
|
start() {
|
|
65
60
|
if (!this.lockManager.acquireLock()) {
|
|
66
61
|
this.eventLogger.log('Another guard auto manager instance detected. Exiting.');
|
|
67
|
-
this.auditLogger.record({ action: 'guard.lock.acquire', resource: 'guard_auto_manager', status: 'fail', meta: { reason: 'lock_exists' } });
|
|
68
|
-
recordMetric({ hook: 'guard_auto_manager', status: 'lock_fail' });
|
|
69
62
|
return false;
|
|
70
63
|
}
|
|
71
64
|
this.lockManager.writePidFile();
|
|
72
65
|
this.eventLogger.log('Guard auto manager started');
|
|
73
|
-
this.auditLogger.record({ action: 'guard.manager.start', resource: 'guard_auto_manager', status: 'success' });
|
|
74
|
-
recordMetric({ hook: 'guard_auto_manager', status: 'start' });
|
|
75
66
|
|
|
76
67
|
this.ensureSupervisor('initial-start');
|
|
77
68
|
this._startReminder();
|
|
@@ -103,12 +94,6 @@ class GuardAutoManagerService {
|
|
|
103
94
|
handleMissingSupervisor() {
|
|
104
95
|
this.lastHeartbeatState = { healthy: false, reason: 'missing-supervisor' };
|
|
105
96
|
this.eventLogger.recordEvent('Guard supervisor no se encuentra en ejecución; reinicio automático.');
|
|
106
|
-
this.auditLogger.record({
|
|
107
|
-
action: 'guard.supervisor.missing',
|
|
108
|
-
resource: 'guard_supervisor',
|
|
109
|
-
status: 'fail',
|
|
110
|
-
meta: { reason: 'missing-supervisor' }
|
|
111
|
-
});
|
|
112
97
|
this.ensureSupervisor('missing-supervisor');
|
|
113
98
|
}
|
|
114
99
|
|
|
@@ -121,21 +106,9 @@ class GuardAutoManagerService {
|
|
|
121
106
|
this.eventLogger.log(`Heartbeat degraded (${heartbeat.reason}); attempting supervisor ensure.`);
|
|
122
107
|
this.lastHeartbeatRestart = now;
|
|
123
108
|
this.ensureSupervisor(`heartbeat-${heartbeat.reason}`);
|
|
124
|
-
this.auditLogger.record({
|
|
125
|
-
action: 'guard.supervisor.ensure',
|
|
126
|
-
resource: 'guard_supervisor',
|
|
127
|
-
status: 'success',
|
|
128
|
-
meta: { reason: heartbeat.reason }
|
|
129
|
-
});
|
|
130
109
|
} else {
|
|
131
110
|
this.eventLogger.log(`Heartbeat degraded (${heartbeat.reason}); restart suppressed (cooldown).`);
|
|
132
111
|
this.eventLogger.recordEvent(`Heartbeat degradado (${heartbeat.reason}); reinicio omitido por cooldown.`);
|
|
133
|
-
this.auditLogger.record({
|
|
134
|
-
action: 'guard.supervisor.ensure',
|
|
135
|
-
resource: 'guard_supervisor',
|
|
136
|
-
status: 'fail',
|
|
137
|
-
meta: { reason: heartbeat.reason, suppressed: true }
|
|
138
|
-
});
|
|
139
112
|
}
|
|
140
113
|
} else {
|
|
141
114
|
this.eventLogger.recordEvent(`Heartbeat en estado ${heartbeat.reason}; reinicio no requerido.`);
|
|
@@ -175,8 +148,6 @@ class GuardAutoManagerService {
|
|
|
175
148
|
this.lockManager.removePidFile();
|
|
176
149
|
this.lockManager.releaseLock();
|
|
177
150
|
this.healthReminder.stop();
|
|
178
|
-
this.auditLogger.record({ action: 'guard.manager.stop', resource: 'guard_auto_manager', status: 'success' });
|
|
179
|
-
recordMetric({ hook: 'guard_auto_manager', status: 'stop' });
|
|
180
151
|
}
|
|
181
152
|
|
|
182
153
|
ensureSupervisor(reason) {
|
|
@@ -1,22 +1,14 @@
|
|
|
1
|
-
const envHelper = require('../../../config/env');
|
|
2
|
-
|
|
3
1
|
class GuardConfig {
|
|
4
|
-
constructor(env =
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
this.healthyReminderIntervalMs = getNumber('GUARD_AUTOSTART_HEALTHY_INTERVAL', 0);
|
|
11
|
-
this.heartbeatNotifyCooldownMs = getNumber('GUARD_AUTOSTART_NOTIFY_COOLDOWN', 60000);
|
|
12
|
-
this.healthyReminderCooldownMs = getNumber(
|
|
13
|
-
'GUARD_AUTOSTART_HEALTHY_COOLDOWN',
|
|
14
|
-
this.healthyReminderIntervalMs > 0 ? this.healthyReminderIntervalMs : 0
|
|
2
|
+
constructor(env = process.env) {
|
|
3
|
+
this.healthyReminderIntervalMs = Number(env.GUARD_AUTOSTART_HEALTHY_INTERVAL || 0);
|
|
4
|
+
this.heartbeatNotifyCooldownMs = Number(env.GUARD_AUTOSTART_NOTIFY_COOLDOWN || 60000);
|
|
5
|
+
this.healthyReminderCooldownMs = Number(
|
|
6
|
+
env.GUARD_AUTOSTART_HEALTHY_COOLDOWN || (this.healthyReminderIntervalMs > 0 ? this.healthyReminderIntervalMs : 0)
|
|
15
7
|
);
|
|
16
|
-
this.heartbeatRestartCooldownMs =
|
|
17
|
-
this.monitorIntervalMs =
|
|
18
|
-
this.restartCooldownMs =
|
|
19
|
-
this.stopSupervisorOnExit =
|
|
8
|
+
this.heartbeatRestartCooldownMs = Number(env.GUARD_AUTOSTART_HEARTBEAT_COOLDOWN || 60000);
|
|
9
|
+
this.monitorIntervalMs = Number(env.GUARD_AUTOSTART_MONITOR_INTERVAL || 5000);
|
|
10
|
+
this.restartCooldownMs = Number(env.GUARD_AUTOSTART_RESTART_COOLDOWN || 2000);
|
|
11
|
+
this.stopSupervisorOnExit = env.GUARD_AUTOSTART_STOP_SUPERVISOR_ON_EXIT !== 'false';
|
|
20
12
|
}
|
|
21
13
|
}
|
|
22
14
|
|
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const env = require('../../../config/env');
|
|
4
3
|
|
|
5
4
|
class GuardHeartbeatMonitor {
|
|
6
5
|
constructor({
|
|
7
6
|
repoRoot = process.cwd(),
|
|
8
7
|
logger = console,
|
|
9
|
-
fsModule = fs
|
|
8
|
+
fsModule = fs,
|
|
9
|
+
env = process.env
|
|
10
10
|
} = {}) {
|
|
11
11
|
this.repoRoot = repoRoot;
|
|
12
12
|
this.logger = logger;
|
|
13
13
|
this.fs = fsModule;
|
|
14
|
-
|
|
14
|
+
this.env = env;
|
|
15
|
+
|
|
16
|
+
const heartbeatRelative = env.HOOK_GUARD_HEARTBEAT_PATH || path.join('.audit_tmp', 'guard-heartbeat.json');
|
|
15
17
|
this.heartbeatPath = path.isAbsolute(heartbeatRelative)
|
|
16
18
|
? heartbeatRelative
|
|
17
19
|
: path.join(this.repoRoot, heartbeatRelative);
|
|
18
20
|
|
|
19
|
-
this.heartbeatMaxAgeMs =
|
|
20
|
-
env.
|
|
21
|
+
this.heartbeatMaxAgeMs = Number(
|
|
22
|
+
env.GUARD_AUTOSTART_HEARTBEAT_MAX_AGE || env.HOOK_GUARD_HEARTBEAT_MAX_AGE || 60000
|
|
23
|
+
);
|
|
21
24
|
|
|
22
25
|
this.heartbeatRestartReasons = new Set(
|
|
23
|
-
(env.
|
|
26
|
+
(env.GUARD_AUTOSTART_HEARTBEAT_RESTART || 'missing,stale,invalid,degraded')
|
|
24
27
|
.split(',')
|
|
25
28
|
.map(entry => entry.trim().toLowerCase())
|
|
26
29
|
.filter(Boolean)
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const { spawnSync } = require('child_process');
|
|
3
3
|
|
|
4
|
-
// Import AuditLogger for logging critical operations
|
|
5
|
-
const AuditLogger = require('../logging/AuditLogger');
|
|
6
|
-
|
|
7
4
|
class GuardProcessManager {
|
|
8
5
|
constructor({
|
|
9
6
|
repoRoot = process.cwd(),
|
|
@@ -18,10 +15,6 @@ class GuardProcessManager {
|
|
|
18
15
|
|
|
19
16
|
this.supervisorPidFile = path.join(this.repoRoot, '.guard-supervisor.pid');
|
|
20
17
|
this.startScript = path.join(this.repoRoot, 'bin', 'start-guards.sh');
|
|
21
|
-
this.busy = false;
|
|
22
|
-
|
|
23
|
-
// Initialize audit logger
|
|
24
|
-
this.auditLogger = new AuditLogger({ repoRoot, logger: this.logger });
|
|
25
18
|
}
|
|
26
19
|
|
|
27
20
|
isSupervisorRunning() {
|
|
@@ -65,15 +58,6 @@ class GuardProcessManager {
|
|
|
65
58
|
}
|
|
66
59
|
|
|
67
60
|
startSupervisor() {
|
|
68
|
-
if (this.busy) {
|
|
69
|
-
return {
|
|
70
|
-
success: false,
|
|
71
|
-
error: new Error('bulkhead_busy'),
|
|
72
|
-
stdout: '',
|
|
73
|
-
stderr: 'bulkhead_busy'
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
this.busy = true;
|
|
77
61
|
try {
|
|
78
62
|
const result = this.childProcess.spawnSync(this.startScript, ['start'], {
|
|
79
63
|
cwd: this.repoRoot,
|
|
@@ -96,21 +80,10 @@ class GuardProcessManager {
|
|
|
96
80
|
stdout: '',
|
|
97
81
|
stderr: error.message
|
|
98
82
|
};
|
|
99
|
-
} finally {
|
|
100
|
-
this.busy = false;
|
|
101
83
|
}
|
|
102
84
|
}
|
|
103
85
|
|
|
104
86
|
stopSupervisor() {
|
|
105
|
-
if (this.busy) {
|
|
106
|
-
return {
|
|
107
|
-
success: false,
|
|
108
|
-
error: new Error('bulkhead_busy'),
|
|
109
|
-
stdout: '',
|
|
110
|
-
stderr: 'bulkhead_busy'
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
this.busy = true;
|
|
114
87
|
try {
|
|
115
88
|
const result = this.childProcess.spawnSync(this.startScript, ['stop'], {
|
|
116
89
|
cwd: this.repoRoot,
|
|
@@ -133,8 +106,6 @@ class GuardProcessManager {
|
|
|
133
106
|
stdout: '',
|
|
134
107
|
stderr: error.message
|
|
135
108
|
};
|
|
136
|
-
} finally {
|
|
137
|
-
this.busy = false;
|
|
138
109
|
}
|
|
139
110
|
}
|
|
140
111
|
}
|
|
@@ -2,9 +2,6 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { execSync, spawnSync } = require('child_process');
|
|
4
4
|
|
|
5
|
-
// Import recordMetric for prometheus metrics
|
|
6
|
-
const { recordMetric } = require('../../../infrastructure/telemetry/metrics-logger');
|
|
7
|
-
|
|
8
5
|
const COLORS = {
|
|
9
6
|
reset: '\x1b[0m',
|
|
10
7
|
blue: '\x1b[34m',
|
|
@@ -113,7 +110,7 @@ fi
|
|
|
113
110
|
|
|
114
111
|
# Try node_modules/.bin first (works with npm install)
|
|
115
112
|
if [ -f "node_modules/.bin/ast-hooks" ]; then
|
|
116
|
-
OUTPUT=$(node_modules/.bin/ast-hooks ast
|
|
113
|
+
OUTPUT=$(node_modules/.bin/ast-hooks ast 2>&1)
|
|
117
114
|
EXIT_CODE=$?
|
|
118
115
|
echo "$OUTPUT"
|
|
119
116
|
if [ $EXIT_CODE -ne 0 ]; then
|
|
@@ -3,7 +3,6 @@ const path = require('path');
|
|
|
3
3
|
const { execSync } = require('child_process');
|
|
4
4
|
const crypto = require('crypto');
|
|
5
5
|
const os = require('os');
|
|
6
|
-
const env = require('../../config/env');
|
|
7
6
|
|
|
8
7
|
const COLORS = {
|
|
9
8
|
reset: '\x1b[0m',
|
|
@@ -32,7 +31,7 @@ function computeRepoFingerprint(repoRoot) {
|
|
|
32
31
|
|
|
33
32
|
function computeServerIdForRepo(repoRoot) {
|
|
34
33
|
const legacyServerId = 'ast-intelligence-automation';
|
|
35
|
-
const forced = (env.
|
|
34
|
+
const forced = (process.env.MCP_SERVER_ID || '').trim();
|
|
36
35
|
if (forced.length > 0) return forced;
|
|
37
36
|
|
|
38
37
|
const repoName = path.basename(repoRoot || process.cwd());
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
|
|
4
|
+
// Import recordMetric for prometheus metrics
|
|
5
|
+
const { recordMetric } = require('../../../infrastructure/telemetry/metrics-logger');
|
|
6
|
+
|
|
4
7
|
class AuditLogger {
|
|
5
8
|
/**
|
|
6
9
|
* @param {Object} options
|
|
@@ -9,6 +12,13 @@ class AuditLogger {
|
|
|
9
12
|
* @param {Object} [options.logger=console] - fallback logger for warnings
|
|
10
13
|
*/
|
|
11
14
|
constructor({ repoRoot = process.cwd(), filename, logger = console } = {}) {
|
|
15
|
+
recordMetric({
|
|
16
|
+
hook: 'audit_logger',
|
|
17
|
+
operation: 'constructor',
|
|
18
|
+
status: 'started',
|
|
19
|
+
repoRoot: repoRoot.substring(0, 100)
|
|
20
|
+
});
|
|
21
|
+
|
|
12
22
|
this.repoRoot = repoRoot;
|
|
13
23
|
this.logger = logger;
|
|
14
24
|
this.logPath = filename
|
|
@@ -16,9 +26,22 @@ class AuditLogger {
|
|
|
16
26
|
: path.join(repoRoot, '.audit_tmp', 'audit.log');
|
|
17
27
|
|
|
18
28
|
this.ensureDir();
|
|
29
|
+
|
|
30
|
+
recordMetric({
|
|
31
|
+
hook: 'audit_logger',
|
|
32
|
+
operation: 'constructor',
|
|
33
|
+
status: 'success',
|
|
34
|
+
repoRoot: repoRoot.substring(0, 100)
|
|
35
|
+
});
|
|
19
36
|
}
|
|
20
37
|
|
|
21
38
|
ensureDir() {
|
|
39
|
+
recordMetric({
|
|
40
|
+
hook: 'audit_logger',
|
|
41
|
+
operation: 'ensure_dir',
|
|
42
|
+
status: 'started'
|
|
43
|
+
});
|
|
44
|
+
|
|
22
45
|
try {
|
|
23
46
|
const dir = path.dirname(this.logPath);
|
|
24
47
|
if (!fs.existsSync(dir)) {
|
|
@@ -30,14 +53,33 @@ class AuditLogger {
|
|
|
30
53
|
} catch (error) {
|
|
31
54
|
this.warn('AUDIT_LOGGER_INIT_ERROR', error);
|
|
32
55
|
}
|
|
56
|
+
|
|
57
|
+
recordMetric({
|
|
58
|
+
hook: 'audit_logger',
|
|
59
|
+
operation: 'ensure_dir',
|
|
60
|
+
status: 'success'
|
|
61
|
+
});
|
|
33
62
|
}
|
|
34
63
|
|
|
35
64
|
warn(message, error) {
|
|
65
|
+
recordMetric({
|
|
66
|
+
hook: 'audit_logger',
|
|
67
|
+
operation: 'warn',
|
|
68
|
+
status: 'started',
|
|
69
|
+
message: message
|
|
70
|
+
});
|
|
71
|
+
|
|
36
72
|
if (this.logger?.warn) {
|
|
37
73
|
this.logger.warn(message, { error: error?.message });
|
|
38
74
|
} else {
|
|
39
75
|
console.warn(message, error?.message);
|
|
40
76
|
}
|
|
77
|
+
|
|
78
|
+
recordMetric({
|
|
79
|
+
hook: 'audit_logger',
|
|
80
|
+
operation: 'warn',
|
|
81
|
+
status: 'success'
|
|
82
|
+
});
|
|
41
83
|
}
|
|
42
84
|
|
|
43
85
|
/**
|
|
@@ -50,7 +92,22 @@ class AuditLogger {
|
|
|
50
92
|
* @param {string|null} [entry.correlationId=null]
|
|
51
93
|
*/
|
|
52
94
|
record(entry = {}) {
|
|
53
|
-
|
|
95
|
+
recordMetric({
|
|
96
|
+
hook: 'audit_logger',
|
|
97
|
+
operation: 'record',
|
|
98
|
+
status: 'started',
|
|
99
|
+
action: entry.action
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (!entry.action) {
|
|
103
|
+
recordMetric({
|
|
104
|
+
hook: 'audit_logger',
|
|
105
|
+
operation: 'record',
|
|
106
|
+
status: 'success',
|
|
107
|
+
reason: 'no_action'
|
|
108
|
+
});
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
54
111
|
const safeMeta = this.sanitizeMeta(entry.meta || {});
|
|
55
112
|
|
|
56
113
|
const payload = {
|
|
@@ -65,12 +122,32 @@ class AuditLogger {
|
|
|
65
122
|
|
|
66
123
|
try {
|
|
67
124
|
fs.appendFileSync(this.logPath, `${JSON.stringify(payload)}\n`, { encoding: 'utf8' });
|
|
125
|
+
recordMetric({
|
|
126
|
+
hook: 'audit_logger',
|
|
127
|
+
operation: 'record',
|
|
128
|
+
status: 'success',
|
|
129
|
+
action: entry.action
|
|
130
|
+
});
|
|
68
131
|
} catch (error) {
|
|
69
132
|
this.warn('AUDIT_LOGGER_WRITE_ERROR', error);
|
|
133
|
+
recordMetric({
|
|
134
|
+
hook: 'audit_logger',
|
|
135
|
+
operation: 'record',
|
|
136
|
+
status: 'failed',
|
|
137
|
+
action: entry.action,
|
|
138
|
+
error: error.message
|
|
139
|
+
});
|
|
70
140
|
}
|
|
71
141
|
}
|
|
72
142
|
|
|
73
143
|
sanitizeMeta(meta) {
|
|
144
|
+
recordMetric({
|
|
145
|
+
hook: 'audit_logger',
|
|
146
|
+
operation: 'sanitize_meta',
|
|
147
|
+
status: 'started',
|
|
148
|
+
metaKeys: Object.keys(meta || {}).length
|
|
149
|
+
});
|
|
150
|
+
|
|
74
151
|
const forbidden = ['token', 'password', 'secret', 'authorization', 'auth', 'apiKey'];
|
|
75
152
|
const clone = {};
|
|
76
153
|
Object.entries(meta).forEach(([k, v]) => {
|
|
@@ -81,6 +158,14 @@ class AuditLogger {
|
|
|
81
158
|
clone[k] = v;
|
|
82
159
|
}
|
|
83
160
|
});
|
|
161
|
+
|
|
162
|
+
recordMetric({
|
|
163
|
+
hook: 'audit_logger',
|
|
164
|
+
operation: 'sanitize_meta',
|
|
165
|
+
status: 'success',
|
|
166
|
+
metaKeys: Object.keys(clone).length
|
|
167
|
+
});
|
|
168
|
+
|
|
84
169
|
return clone;
|
|
85
170
|
}
|
|
86
171
|
}
|
|
@@ -97,19 +97,10 @@ class UnifiedLogger {
|
|
|
97
97
|
this.rotateFileIfNeeded();
|
|
98
98
|
fs.appendFileSync(this.fileConfig.path, `${JSON.stringify(entry)}\n`, 'utf8');
|
|
99
99
|
} catch (error) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
path: this.fileConfig.path,
|
|
105
|
-
error: error.message
|
|
106
|
-
});
|
|
107
|
-
} else {
|
|
108
|
-
console.warn('[UnifiedLogger] File logging skipped due to error');
|
|
109
|
-
}
|
|
110
|
-
} catch (secondaryError) {
|
|
111
|
-
console.error('[UnifiedLogger] Secondary logging failure', {
|
|
112
|
-
error: secondaryError.message
|
|
100
|
+
if (process.env.DEBUG) {
|
|
101
|
+
console.error('[UnifiedLogger] Failed to write log file', {
|
|
102
|
+
path: this.fileConfig.path,
|
|
103
|
+
error: error.message
|
|
113
104
|
});
|
|
114
105
|
}
|
|
115
106
|
}
|
|
@@ -19,6 +19,7 @@ class EvidenceMonitor {
|
|
|
19
19
|
resolveUpdateEvidenceScript() {
|
|
20
20
|
const candidates = [
|
|
21
21
|
path.join(this.repoRoot, 'node_modules/@pumuki/ast-intelligence-hooks/bin/update-evidence.sh'),
|
|
22
|
+
path.join(this.repoRoot, 'scripts/hooks-system/bin/update-evidence.sh'),
|
|
22
23
|
path.join(this.repoRoot, 'bin/update-evidence.sh')
|
|
23
24
|
];
|
|
24
25
|
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { execSync } = require('child_process');
|
|
4
|
-
const env = require('../../config/env');
|
|
5
|
-
|
|
6
|
-
// Import recordMetric for prometheus metrics
|
|
7
|
-
const { recordMetric } = require('../../../infrastructure/telemetry/metrics-logger');
|
|
8
4
|
|
|
9
5
|
function resolveUpdateEvidenceScript(repoRoot) {
|
|
10
6
|
const candidates = [
|
|
@@ -29,9 +25,9 @@ class EvidenceMonitorService {
|
|
|
29
25
|
updateScriptPath = resolveUpdateEvidenceScript(repoRoot) || path.join(process.cwd(), 'scripts', 'hooks-system', 'bin', 'update-evidence.sh'),
|
|
30
26
|
notifier = () => { },
|
|
31
27
|
logger = console,
|
|
32
|
-
autoRefreshEnabled = env.
|
|
33
|
-
autoRefreshCooldownMs = env.
|
|
34
|
-
staleThresholdMs = env.
|
|
28
|
+
autoRefreshEnabled = process.env.HOOK_GUARD_AUTO_REFRESH !== 'false',
|
|
29
|
+
autoRefreshCooldownMs = Number(process.env.HOOK_GUARD_AUTO_REFRESH_COOLDOWN || 180000),
|
|
30
|
+
staleThresholdMs = Number(process.env.HOOK_GUARD_EVIDENCE_STALE_THRESHOLD || 10 * 60 * 1000),
|
|
35
31
|
fsModule = fs,
|
|
36
32
|
execFn = execSync
|
|
37
33
|
} = {}) {
|