pumuki-ast-hooks 5.3.16 → 5.3.18

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.
Files changed (41) hide show
  1. package/docs/VIOLATIONS_RESOLUTION_PLAN.md +95 -0
  2. package/docs/alerting-system.md +51 -0
  3. package/docs/observability.md +36 -0
  4. package/docs/type-safety.md +8 -0
  5. package/package.json +1 -1
  6. package/scripts/hooks-system/.AI_TOKEN_STATUS.txt +1 -1
  7. package/scripts/hooks-system/.audit-reports/auto-recovery.log +2 -0
  8. package/scripts/hooks-system/.audit-reports/install-wizard.log +8 -0
  9. package/scripts/hooks-system/.audit-reports/notifications.log +45 -0
  10. package/scripts/hooks-system/.audit-reports/token-monitor.log +174 -0
  11. package/scripts/hooks-system/application/CompositionRoot.js +73 -24
  12. package/scripts/hooks-system/application/services/DynamicRulesLoader.js +2 -1
  13. package/scripts/hooks-system/application/services/PlaybookRunner.js +1 -1
  14. package/scripts/hooks-system/application/services/RealtimeGuardService.js +85 -15
  15. package/scripts/hooks-system/application/services/guard/GuardAutoManagerService.js +31 -2
  16. package/scripts/hooks-system/application/services/guard/GuardConfig.js +17 -9
  17. package/scripts/hooks-system/application/services/guard/GuardHeartbeatMonitor.js +6 -9
  18. package/scripts/hooks-system/application/services/guard/GuardProcessManager.js +29 -0
  19. package/scripts/hooks-system/application/services/installation/GitEnvironmentService.js +4 -1
  20. package/scripts/hooks-system/application/services/installation/McpConfigurator.js +2 -1
  21. package/scripts/hooks-system/application/services/logging/AuditLogger.js +88 -0
  22. package/scripts/hooks-system/application/services/logging/UnifiedLogger.js +13 -4
  23. package/scripts/hooks-system/application/services/monitoring/EvidenceMonitorService.js +7 -3
  24. package/scripts/hooks-system/application/services/token/TokenMetricsService.js +14 -1
  25. package/scripts/hooks-system/bin/cli.js +15 -1
  26. package/scripts/hooks-system/config/env.js +33 -0
  27. package/scripts/hooks-system/domain/events/__tests__/EventBus.spec.js +33 -0
  28. package/scripts/hooks-system/domain/events/index.js +32 -6
  29. package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidAnalysisOrchestrator.js +3 -2
  30. package/scripts/hooks-system/infrastructure/ast/ast-core.js +12 -20
  31. package/scripts/hooks-system/infrastructure/ast/ast-intelligence.js +8 -18
  32. package/scripts/hooks-system/infrastructure/ast/backend/analyzers/BackendPatternDetector.js +2 -1
  33. package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +10 -8
  34. package/scripts/hooks-system/infrastructure/ast/frontend/ast-frontend.js +196 -196
  35. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSASTIntelligentAnalyzer.js +3 -2
  36. package/scripts/hooks-system/infrastructure/hooks/skill-activation-prompt.js +3 -2
  37. package/scripts/hooks-system/infrastructure/logging/UnifiedLoggerFactory.js +35 -5
  38. package/scripts/hooks-system/infrastructure/orchestration/intelligent-audit.js +86 -16
  39. package/scripts/hooks-system/infrastructure/telemetry/metrics-server.js +51 -2
  40. package/scripts/hooks-system/infrastructure/validators/enforce-english-literals.js +6 -8
  41. package/scripts/hooks-system/infrastructure/watchdog/__tests__/.audit-reports/token-monitor.log +3 -0
@@ -1,6 +1,9 @@
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');
4
7
 
5
8
  class RealtimeGuardService {
6
9
  /**
@@ -12,6 +15,12 @@ class RealtimeGuardService {
12
15
  * @param {Object} dependencies.config
13
16
  */
14
17
  constructor(dependencies = {}) {
18
+ this._initializeDependencies(dependencies);
19
+ this._initializeConfiguration();
20
+ this._initializeMonitors();
21
+ }
22
+
23
+ _initializeDependencies(dependencies = {}) {
15
24
  const {
16
25
  logger,
17
26
  notificationService,
@@ -30,39 +39,49 @@ class RealtimeGuardService {
30
39
  this.monitors = monitors || {};
31
40
  this.orchestration = orchestration;
32
41
  this.config = config || {};
42
+ this.auditLogger = dependencies.auditLogger || new AuditLogger({ repoRoot: process.cwd(), logger: this.logger });
43
+ }
33
44
 
45
+ _initializeConfiguration() {
34
46
  if (!this.config.debugLogPath) {
35
47
  this.config.debugLogPath = path.join(process.cwd(), '.audit-reports', 'guard-debug.log');
36
48
  }
37
49
 
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);
50
+ this.evidencePath = this.config.evidencePath || path.join(env.get('REPO_ROOT', process.cwd()), '.AI_EVIDENCE.json');
51
+ this.staleThresholdMs = env.getNumber('HOOK_GUARD_EVIDENCE_STALE_THRESHOLD', 60000);
52
+ this.reminderIntervalMs = env.getNumber('HOOK_GUARD_EVIDENCE_REMINDER_INTERVAL', 60000);
53
+ this.inactivityGraceMs = env.getNumber('HOOK_GUARD_INACTIVITY_GRACE_MS', 120000);
54
+ this.pollIntervalMs = env.getNumber('HOOK_GUARD_EVIDENCE_POLL_INTERVAL', 30000);
43
55
  this.pollTimer = null;
44
56
  this.lastStaleNotification = 0;
45
57
  this.lastUserActivityAt = 0;
46
58
 
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);
59
+ this.gitTreeStagedThreshold = env.getNumber('HOOK_GUARD_DIRTY_TREE_STAGED_LIMIT', 10);
60
+ this.gitTreeUnstagedThreshold = env.getNumber('HOOK_GUARD_DIRTY_TREE_UNSTAGED_LIMIT', 15);
61
+ this.gitTreeTotalThreshold = env.getNumber('HOOK_GUARD_DIRTY_TREE_TOTAL_LIMIT', 20);
62
+ this.gitTreeCheckIntervalMs = env.getNumber('HOOK_GUARD_DIRTY_TREE_INTERVAL', 60000);
63
+ this.gitTreeReminderMs = env.getNumber('HOOK_GUARD_DIRTY_TREE_REMINDER', 300000);
52
64
  this.gitTreeTimer = null;
53
65
  this.lastDirtyTreeNotification = 0;
54
66
  this.dirtyTreeActive = false;
55
67
 
56
- this.autoRefreshCooldownMs = Number(process.env.HOOK_GUARD_EVIDENCE_AUTO_REFRESH_COOLDOWN || 180000);
68
+ this.autoRefreshCooldownMs = env.getNumber('HOOK_GUARD_EVIDENCE_AUTO_REFRESH_COOLDOWN', 180000);
57
69
  this.lastAutoRefresh = 0;
58
70
  this.autoRefreshInFlight = false;
59
71
 
60
72
  this.watchers = [];
61
- this.embedTokenMonitor = process.env.HOOK_GUARD_EMBEDDED_TOKEN_MONITOR === 'true';
73
+ this.embedTokenMonitor = env.getBool('HOOK_GUARD_EMBEDDED_TOKEN_MONITOR', false);
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
62
79
  }
63
80
 
64
81
  start() {
65
82
  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' });
66
85
 
67
86
  // Start all monitors
68
87
  this._startEvidenceMonitoring();
@@ -82,6 +101,8 @@ class RealtimeGuardService {
82
101
 
83
102
  stop() {
84
103
  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' });
85
106
 
86
107
  this.watchers.forEach(w => w.close());
87
108
  this.watchers = [];
@@ -103,14 +124,27 @@ class RealtimeGuardService {
103
124
  }
104
125
 
105
126
  _startGitTreeMonitoring() {
106
- if (process.env.HOOK_GUARD_DIRTY_TREE_DISABLED === 'true') return;
127
+ if (env.getBool('HOOK_GUARD_DIRTY_TREE_DISABLED', false)) return;
107
128
 
108
129
  this.monitors.gitTree.startMonitoring((state) => {
109
130
  if (state.isBeyondLimit) {
110
131
  const message = `Git tree has too many files: ${state.total} total (${state.staged} staged, ${state.unstaged} unstaged)`;
111
132
  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 });
112
140
  } else {
113
141
  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' });
114
148
  }
115
149
  });
116
150
  }
@@ -124,22 +158,44 @@ class RealtimeGuardService {
124
158
  try {
125
159
  this.monitors.token.start();
126
160
  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' });
127
167
  } catch (error) {
128
168
  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' });
129
176
  }
130
177
  }
131
178
 
132
179
  _startGitFlowSync() {
133
180
  if (!this.monitors.gitFlow.autoSyncEnabled) return;
134
181
 
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
+
135
190
  const syncInterval = setInterval(() => {
136
191
  if (this.monitors.gitFlow.isClean()) {
137
192
  const result = this.monitors.gitFlow.syncBranches();
138
193
  if (result.success) {
139
194
  this.notify('🔄 Branches synchronized', 'info');
195
+ recordMetric({ hook: 'gitflow_autosync', status: 'sync_success' });
140
196
  }
141
197
  }
142
- }, Number(process.env.HOOK_GUARD_GITFLOW_AUTOSYNC_INTERVAL || 300000));
198
+ }, env.getNumber('HOOK_GUARD_GITFLOW_AUTOSYNC_INTERVAL', 300000));
143
199
 
144
200
  syncInterval.unref();
145
201
  }
@@ -247,11 +303,18 @@ class RealtimeGuardService {
247
303
  this.lastStaleNotification = now;
248
304
  const ageSec = Math.floor(ageMs / 1000);
249
305
  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 });
250
313
  void this.attemptAutoRefresh('stale');
251
314
  }
252
315
 
253
316
  async attemptAutoRefresh(reason = 'manual') {
254
- if (process.env.HOOK_GUARD_AUTO_REFRESH !== 'true') {
317
+ if (!env.getBool('HOOK_GUARD_AUTO_REFRESH', false)) {
255
318
  return;
256
319
  }
257
320
 
@@ -284,6 +347,13 @@ class RealtimeGuardService {
284
347
  try {
285
348
  await this.runDirectEvidenceRefresh(reason);
286
349
  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 });
287
357
  } finally {
288
358
  this.autoRefreshInFlight = false;
289
359
  }
@@ -10,6 +10,9 @@ 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');
13
16
 
14
17
  class GuardAutoManagerService {
15
18
  constructor({
@@ -19,9 +22,10 @@ class GuardAutoManagerService {
19
22
  fsModule = fs,
20
23
  childProcess = { spawnSync },
21
24
  timers = { setInterval, clearInterval },
22
- env = process.env,
25
+ env = envHelper,
23
26
  processRef = process,
24
- heartbeatMonitor = null
27
+ heartbeatMonitor = null,
28
+ auditLogger = null
25
29
  } = {}) {
26
30
  this.process = processRef;
27
31
 
@@ -30,6 +34,7 @@ class GuardAutoManagerService {
30
34
  this.eventLogger = new GuardEventLogger({ repoRoot, logger, fsModule });
31
35
  this.lockManager = new GuardLockManager({ repoRoot, logger, fsModule });
32
36
  this.processManager = new GuardProcessManager({ repoRoot, logger, fsModule, childProcess });
37
+ this.auditLogger = auditLogger || new AuditLogger({ repoRoot, logger });
33
38
 
34
39
  // Monitors & Handlers
35
40
  this.heartbeatMonitor = heartbeatMonitor || new GuardHeartbeatMonitor({
@@ -59,10 +64,14 @@ class GuardAutoManagerService {
59
64
  start() {
60
65
  if (!this.lockManager.acquireLock()) {
61
66
  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' });
62
69
  return false;
63
70
  }
64
71
  this.lockManager.writePidFile();
65
72
  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' });
66
75
 
67
76
  this.ensureSupervisor('initial-start');
68
77
  this._startReminder();
@@ -94,6 +103,12 @@ class GuardAutoManagerService {
94
103
  handleMissingSupervisor() {
95
104
  this.lastHeartbeatState = { healthy: false, reason: 'missing-supervisor' };
96
105
  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
+ });
97
112
  this.ensureSupervisor('missing-supervisor');
98
113
  }
99
114
 
@@ -106,9 +121,21 @@ class GuardAutoManagerService {
106
121
  this.eventLogger.log(`Heartbeat degraded (${heartbeat.reason}); attempting supervisor ensure.`);
107
122
  this.lastHeartbeatRestart = now;
108
123
  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
+ });
109
130
  } else {
110
131
  this.eventLogger.log(`Heartbeat degraded (${heartbeat.reason}); restart suppressed (cooldown).`);
111
132
  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
+ });
112
139
  }
113
140
  } else {
114
141
  this.eventLogger.recordEvent(`Heartbeat en estado ${heartbeat.reason}; reinicio no requerido.`);
@@ -148,6 +175,8 @@ class GuardAutoManagerService {
148
175
  this.lockManager.removePidFile();
149
176
  this.lockManager.releaseLock();
150
177
  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' });
151
180
  }
152
181
 
153
182
  ensureSupervisor(reason) {
@@ -1,14 +1,22 @@
1
+ const envHelper = require('../../../config/env');
2
+
1
3
  class GuardConfig {
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)
4
+ constructor(env = envHelper) {
5
+ const getNumber = (name, def) =>
6
+ typeof env.getNumber === 'function' ? env.getNumber(name, def) : Number(env[name] || def);
7
+ const getBool = (name, def) =>
8
+ typeof env.getBool === 'function' ? env.getBool(name, def) : (env[name] !== 'false');
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
7
15
  );
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';
16
+ this.heartbeatRestartCooldownMs = getNumber('GUARD_AUTOSTART_HEARTBEAT_COOLDOWN', 60000);
17
+ this.monitorIntervalMs = getNumber('GUARD_AUTOSTART_MONITOR_INTERVAL', 5000);
18
+ this.restartCooldownMs = getNumber('GUARD_AUTOSTART_RESTART_COOLDOWN', 2000);
19
+ this.stopSupervisorOnExit = getBool('GUARD_AUTOSTART_STOP_SUPERVISOR_ON_EXIT', true);
12
20
  }
13
21
  }
14
22
 
@@ -1,29 +1,26 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const env = require('../../../config/env');
3
4
 
4
5
  class GuardHeartbeatMonitor {
5
6
  constructor({
6
7
  repoRoot = process.cwd(),
7
8
  logger = console,
8
- fsModule = fs,
9
- env = process.env
9
+ fsModule = fs
10
10
  } = {}) {
11
11
  this.repoRoot = repoRoot;
12
12
  this.logger = logger;
13
13
  this.fs = fsModule;
14
- this.env = env;
15
-
16
- const heartbeatRelative = env.HOOK_GUARD_HEARTBEAT_PATH || path.join('.audit_tmp', 'guard-heartbeat.json');
14
+ const heartbeatRelative = env.get('HOOK_GUARD_HEARTBEAT_PATH', path.join('.audit_tmp', 'guard-heartbeat.json'));
17
15
  this.heartbeatPath = path.isAbsolute(heartbeatRelative)
18
16
  ? heartbeatRelative
19
17
  : path.join(this.repoRoot, heartbeatRelative);
20
18
 
21
- this.heartbeatMaxAgeMs = Number(
22
- env.GUARD_AUTOSTART_HEARTBEAT_MAX_AGE || env.HOOK_GUARD_HEARTBEAT_MAX_AGE || 60000
23
- );
19
+ this.heartbeatMaxAgeMs = env.getNumber('GUARD_AUTOSTART_HEARTBEAT_MAX_AGE',
20
+ env.getNumber('HOOK_GUARD_HEARTBEAT_MAX_AGE', 60000));
24
21
 
25
22
  this.heartbeatRestartReasons = new Set(
26
- (env.GUARD_AUTOSTART_HEARTBEAT_RESTART || 'missing,stale,invalid,degraded')
23
+ (env.get('GUARD_AUTOSTART_HEARTBEAT_RESTART', 'missing,stale,invalid,degraded'))
27
24
  .split(',')
28
25
  .map(entry => entry.trim().toLowerCase())
29
26
  .filter(Boolean)
@@ -1,6 +1,9 @@
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
+
4
7
  class GuardProcessManager {
5
8
  constructor({
6
9
  repoRoot = process.cwd(),
@@ -15,6 +18,10 @@ class GuardProcessManager {
15
18
 
16
19
  this.supervisorPidFile = path.join(this.repoRoot, '.guard-supervisor.pid');
17
20
  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 });
18
25
  }
19
26
 
20
27
  isSupervisorRunning() {
@@ -58,6 +65,15 @@ class GuardProcessManager {
58
65
  }
59
66
 
60
67
  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;
61
77
  try {
62
78
  const result = this.childProcess.spawnSync(this.startScript, ['start'], {
63
79
  cwd: this.repoRoot,
@@ -80,10 +96,21 @@ class GuardProcessManager {
80
96
  stdout: '',
81
97
  stderr: error.message
82
98
  };
99
+ } finally {
100
+ this.busy = false;
83
101
  }
84
102
  }
85
103
 
86
104
  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;
87
114
  try {
88
115
  const result = this.childProcess.spawnSync(this.startScript, ['stop'], {
89
116
  cwd: this.repoRoot,
@@ -106,6 +133,8 @@ class GuardProcessManager {
106
133
  stdout: '',
107
134
  stderr: error.message
108
135
  };
136
+ } finally {
137
+ this.busy = false;
109
138
  }
110
139
  }
111
140
  }
@@ -2,6 +2,9 @@ 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
+
5
8
  const COLORS = {
6
9
  reset: '\x1b[0m',
7
10
  blue: '\x1b[34m',
@@ -110,7 +113,7 @@ fi
110
113
 
111
114
  # Try node_modules/.bin first (works with npm install)
112
115
  if [ -f "node_modules/.bin/ast-hooks" ]; then
113
- OUTPUT=$(node_modules/.bin/ast-hooks ast 2>&1)
116
+ OUTPUT=$(node_modules/.bin/ast-hooks ast --staged 2>&1)
114
117
  EXIT_CODE=$?
115
118
  echo "$OUTPUT"
116
119
  if [ $EXIT_CODE -ne 0 ]; then
@@ -3,6 +3,7 @@ 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');
6
7
 
7
8
  const COLORS = {
8
9
  reset: '\x1b[0m',
@@ -31,7 +32,7 @@ function computeRepoFingerprint(repoRoot) {
31
32
 
32
33
  function computeServerIdForRepo(repoRoot) {
33
34
  const legacyServerId = 'ast-intelligence-automation';
34
- const forced = (process.env.MCP_SERVER_ID || '').trim();
35
+ const forced = (env.get('MCP_SERVER_ID', '') || '').trim();
35
36
  if (forced.length > 0) return forced;
36
37
 
37
38
  const repoName = path.basename(repoRoot || process.cwd());
@@ -0,0 +1,88 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ class AuditLogger {
5
+ /**
6
+ * @param {Object} options
7
+ * @param {string} [options.repoRoot=process.cwd()]
8
+ * @param {string} [options.filename='.audit_tmp/audit.log']
9
+ * @param {Object} [options.logger=console] - fallback logger for warnings
10
+ */
11
+ constructor({ repoRoot = process.cwd(), filename, logger = console } = {}) {
12
+ this.repoRoot = repoRoot;
13
+ this.logger = logger;
14
+ this.logPath = filename
15
+ ? (path.isAbsolute(filename) ? filename : path.join(repoRoot, filename))
16
+ : path.join(repoRoot, '.audit_tmp', 'audit.log');
17
+
18
+ this.ensureDir();
19
+ }
20
+
21
+ ensureDir() {
22
+ try {
23
+ const dir = path.dirname(this.logPath);
24
+ if (!fs.existsSync(dir)) {
25
+ fs.mkdirSync(dir, { recursive: true });
26
+ }
27
+ if (!fs.existsSync(this.logPath)) {
28
+ fs.writeFileSync(this.logPath, '', { encoding: 'utf8' });
29
+ }
30
+ } catch (error) {
31
+ this.warn('AUDIT_LOGGER_INIT_ERROR', error);
32
+ }
33
+ }
34
+
35
+ warn(message, error) {
36
+ if (this.logger?.warn) {
37
+ this.logger.warn(message, { error: error?.message });
38
+ } else {
39
+ console.warn(message, error?.message);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * @param {Object} entry
45
+ * @param {string} entry.action
46
+ * @param {string} [entry.resource]
47
+ * @param {string} [entry.status='success']
48
+ * @param {string|null} [entry.actor=null]
49
+ * @param {Object} [entry.meta={}]
50
+ * @param {string|null} [entry.correlationId=null]
51
+ */
52
+ record(entry = {}) {
53
+ if (!entry.action) return;
54
+ const safeMeta = this.sanitizeMeta(entry.meta || {});
55
+
56
+ const payload = {
57
+ ts: new Date().toISOString(),
58
+ action: entry.action,
59
+ resource: entry.resource || null,
60
+ status: entry.status || 'success',
61
+ actor: entry.actor || null,
62
+ correlationId: entry.correlationId || null,
63
+ meta: safeMeta
64
+ };
65
+
66
+ try {
67
+ fs.appendFileSync(this.logPath, `${JSON.stringify(payload)}\n`, { encoding: 'utf8' });
68
+ } catch (error) {
69
+ this.warn('AUDIT_LOGGER_WRITE_ERROR', error);
70
+ }
71
+ }
72
+
73
+ sanitizeMeta(meta) {
74
+ const forbidden = ['token', 'password', 'secret', 'authorization', 'auth', 'apiKey'];
75
+ const clone = {};
76
+ Object.entries(meta).forEach(([k, v]) => {
77
+ const lowered = k.toLowerCase();
78
+ if (forbidden.some(f => lowered.includes(f))) {
79
+ clone[k] = '[REDACTED]';
80
+ } else {
81
+ clone[k] = v;
82
+ }
83
+ });
84
+ return clone;
85
+ }
86
+ }
87
+
88
+ module.exports = AuditLogger;
@@ -97,10 +97,19 @@ class UnifiedLogger {
97
97
  this.rotateFileIfNeeded();
98
98
  fs.appendFileSync(this.fileConfig.path, `${JSON.stringify(entry)}\n`, 'utf8');
99
99
  } catch (error) {
100
- if (process.env.DEBUG) {
101
- console.error('[UnifiedLogger] Failed to write log file', {
102
- path: this.fileConfig.path,
103
- error: error.message
100
+ try {
101
+ const env = require('../../../config/env');
102
+ if (env.getBool('DEBUG', false)) {
103
+ console.error('[UnifiedLogger] Failed to write log file', {
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
104
113
  });
105
114
  }
106
115
  }
@@ -1,6 +1,10 @@
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');
4
8
 
5
9
  function resolveUpdateEvidenceScript(repoRoot) {
6
10
  const candidates = [
@@ -25,9 +29,9 @@ class EvidenceMonitorService {
25
29
  updateScriptPath = resolveUpdateEvidenceScript(repoRoot) || path.join(process.cwd(), 'scripts', 'hooks-system', 'bin', 'update-evidence.sh'),
26
30
  notifier = () => { },
27
31
  logger = console,
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),
32
+ autoRefreshEnabled = env.getBool('HOOK_GUARD_AUTO_REFRESH', true),
33
+ autoRefreshCooldownMs = env.getNumber('HOOK_GUARD_AUTO_REFRESH_COOLDOWN', 180000),
34
+ staleThresholdMs = env.getNumber('HOOK_GUARD_EVIDENCE_STALE_THRESHOLD', 10 * 60 * 1000),
31
35
  fsModule = fs,
32
36
  execFn = execSync
33
37
  } = {}) {
@@ -1,5 +1,8 @@
1
1
  const { execSync } = require('child_process');
2
2
 
3
+ // Import recordMetric for prometheus metrics
4
+ const { recordMetric } = require('../../../infrastructure/telemetry/metrics-logger');
5
+
3
6
  class TokenMetricsService {
4
7
  constructor(cursorTokenService, thresholds, logger) {
5
8
  this.cursorTokenService = cursorTokenService;
@@ -54,7 +57,8 @@ class TokenMetricsService {
54
57
  if (untrusted) {
55
58
  level = 'ok';
56
59
  }
57
- const forceLevel = (process.env.TOKEN_MONITOR_FORCE_LEVEL || '').toLowerCase();
60
+ const env = require('../../config/env');
61
+ const forceLevel = (env.get('TOKEN_MONITOR_FORCE_LEVEL', '') || '').toLowerCase();
58
62
  if (forceLevel === 'warning' || forceLevel === 'critical' || forceLevel === 'ok') {
59
63
  level = forceLevel;
60
64
  }
@@ -75,6 +79,15 @@ class TokenMetricsService {
75
79
  this.logger.debug('TOKEN_MONITOR_METRICS', metrics);
76
80
  }
77
81
 
82
+ // Record prometheus metrics
83
+ recordMetric({
84
+ hook: 'token_monitor',
85
+ status: 'collect',
86
+ tokensUsed,
87
+ percentUsed: Number(percentUsed.toFixed(0)),
88
+ level
89
+ });
90
+
78
91
  return metrics;
79
92
  }
80
93
 
@@ -121,7 +121,21 @@ const commands = {
121
121
  },
122
122
 
123
123
  ast: () => {
124
- execSync(`node ${path.join(HOOKS_ROOT, 'infrastructure/ast/ast-intelligence.js')}`, { stdio: 'inherit' });
124
+ const env = { ...process.env };
125
+ const filteredArgs = [];
126
+
127
+ for (const arg of args) {
128
+ if (arg === '--staged') {
129
+ env.STAGING_ONLY_MODE = '1';
130
+ } else {
131
+ filteredArgs.push(arg);
132
+ }
133
+ }
134
+
135
+ execSync(
136
+ `node ${path.join(HOOKS_ROOT, 'infrastructure/ast/ast-intelligence.js')} ${filteredArgs.join(' ')}`,
137
+ { stdio: 'inherit', env }
138
+ );
125
139
  },
126
140
 
127
141
  install: () => {