pumuki-ast-hooks 5.3.29 → 5.4.0
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 +10 -10
- package/package.json +2 -2
- package/scripts/hooks-system/application/CompositionRoot.js +57 -282
- package/scripts/hooks-system/application/factories/AdapterFactory.js +58 -0
- package/scripts/hooks-system/application/factories/MonitorFactory.js +104 -0
- package/scripts/hooks-system/application/factories/ServiceFactory.js +153 -0
- package/scripts/hooks-system/application/services/RealtimeGuardService.js +49 -246
- package/scripts/hooks-system/application/services/guard/EvidenceManager.js +153 -0
- package/scripts/hooks-system/application/services/guard/GitTreeManager.js +129 -0
- package/scripts/hooks-system/application/services/guard/GuardNotifier.js +54 -0
- package/scripts/hooks-system/application/services/installation/McpConfigurator.js +45 -1
- package/scripts/hooks-system/infrastructure/ast/ast-intelligence.js +33 -29
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSASTIntelligentAnalyzer.js +80 -10
- package/scripts/hooks-system/infrastructure/ast/ios/ast-ios.js +45 -11
- package/scripts/hooks-system/infrastructure/ast/ios/native-bridge.js +39 -2
- package/scripts/hooks-system/.hook-system/config.json +0 -8
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
const ContextDetectionEngine = require('../services/ContextDetectionEngine');
|
|
2
|
+
const PlatformDetectionService = require('../services/PlatformDetectionService');
|
|
3
|
+
const AutonomousOrchestrator = require('../services/AutonomousOrchestrator');
|
|
4
|
+
const AutoExecuteAIStartUseCase = require('../use-cases/AutoExecuteAIStartUseCase');
|
|
5
|
+
const UnifiedLogger = require('../services/logging/UnifiedLogger');
|
|
6
|
+
const NotificationCenterService = require('../services/notification/NotificationCenterService');
|
|
7
|
+
const GitFlowService = require('../services/GitFlowService');
|
|
8
|
+
const AuditLogger = require('../services/logging/AuditLogger');
|
|
9
|
+
const RealtimeGuardService = require('../services/RealtimeGuardService');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const env = require('../../config/env');
|
|
12
|
+
|
|
13
|
+
class ServiceFactory {
|
|
14
|
+
constructor(repoRoot, instances, adapterFactory) {
|
|
15
|
+
this.repoRoot = repoRoot;
|
|
16
|
+
this.instances = instances;
|
|
17
|
+
this.adapterFactory = adapterFactory;
|
|
18
|
+
this.auditDir = path.join(repoRoot, '.audit-reports');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getLogger() {
|
|
22
|
+
if (!this.instances.has('logger')) {
|
|
23
|
+
this.instances.set('logger', new UnifiedLogger({
|
|
24
|
+
component: 'HookSystem',
|
|
25
|
+
file: {
|
|
26
|
+
enabled: true,
|
|
27
|
+
path: path.join(this.auditDir, 'guard-audit.jsonl'),
|
|
28
|
+
level: env.get('HOOK_LOG_LEVEL', env.isProd ? 'warn' : 'info')
|
|
29
|
+
},
|
|
30
|
+
console: {
|
|
31
|
+
enabled: false,
|
|
32
|
+
level: 'info'
|
|
33
|
+
}
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
return this.instances.get('logger');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getNotificationService() {
|
|
40
|
+
if (!this.instances.has('notificationService')) {
|
|
41
|
+
const logger = this.getLogger();
|
|
42
|
+
this.instances.set('notificationService', new NotificationCenterService({
|
|
43
|
+
repoRoot: this.repoRoot,
|
|
44
|
+
logger
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
return this.instances.get('notificationService');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getAuditLogger() {
|
|
51
|
+
if (!this.instances.has('auditLogger')) {
|
|
52
|
+
const logger = this.getLogger();
|
|
53
|
+
this.instances.set('auditLogger', new AuditLogger({
|
|
54
|
+
repoRoot: this.repoRoot,
|
|
55
|
+
filename: path.join('.audit_tmp', 'audit.log'),
|
|
56
|
+
logger
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
return this.instances.get('auditLogger');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getContextDetectionEngine() {
|
|
63
|
+
if (!this.instances.has('contextEngine')) {
|
|
64
|
+
const gitQuery = this.adapterFactory.getGitQueryAdapter();
|
|
65
|
+
const logger = this.getLogger();
|
|
66
|
+
this.instances.set('contextEngine', new ContextDetectionEngine(gitQuery, logger));
|
|
67
|
+
}
|
|
68
|
+
return this.instances.get('contextEngine');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getPlatformDetectionService() {
|
|
72
|
+
if (!this.instances.has('platformDetector')) {
|
|
73
|
+
this.instances.set('platformDetector', new PlatformDetectionService());
|
|
74
|
+
}
|
|
75
|
+
return this.instances.get('platformDetector');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getOrchestrator() {
|
|
79
|
+
if (!this.instances.has('orchestrator')) {
|
|
80
|
+
const contextEngine = this.getContextDetectionEngine();
|
|
81
|
+
const platformDetector = this.getPlatformDetectionService();
|
|
82
|
+
const logger = this.getLogger();
|
|
83
|
+
|
|
84
|
+
this.instances.set('orchestrator', new AutonomousOrchestrator(
|
|
85
|
+
contextEngine,
|
|
86
|
+
platformDetector,
|
|
87
|
+
null,
|
|
88
|
+
logger
|
|
89
|
+
));
|
|
90
|
+
}
|
|
91
|
+
return this.instances.get('orchestrator');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getAutoExecuteAIStartUseCase() {
|
|
95
|
+
if (!this.instances.has('autoExecuteAIStart')) {
|
|
96
|
+
const orchestrator = this.getOrchestrator();
|
|
97
|
+
const logger = this.getLogger();
|
|
98
|
+
this.instances.set('autoExecuteAIStart', new AutoExecuteAIStartUseCase(orchestrator, this.repoRoot, logger));
|
|
99
|
+
}
|
|
100
|
+
return this.instances.get('autoExecuteAIStart');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getBlockCommitUseCase() {
|
|
104
|
+
if (!this.instances.has('blockCommit')) {
|
|
105
|
+
const BlockCommitUseCase = require('../use-cases/BlockCommitUseCase');
|
|
106
|
+
this.instances.set('blockCommit', new BlockCommitUseCase());
|
|
107
|
+
}
|
|
108
|
+
return this.instances.get('blockCommit');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
getGitFlowService() {
|
|
112
|
+
if (!this.instances.has('gitFlowService')) {
|
|
113
|
+
const logger = this.getLogger();
|
|
114
|
+
const gitQuery = this.adapterFactory.getGitQueryAdapter();
|
|
115
|
+
const gitCommand = this.adapterFactory.getGitCommandAdapter();
|
|
116
|
+
const github = this.adapterFactory.getGitHubAdapter();
|
|
117
|
+
|
|
118
|
+
this.instances.set('gitFlowService', new GitFlowService(this.repoRoot, {
|
|
119
|
+
developBranch: env.get('HOOK_GUARD_GITFLOW_DEVELOP_BRANCH', 'develop'),
|
|
120
|
+
mainBranch: env.get('HOOK_GUARD_GITFLOW_MAIN_BRANCH', 'main'),
|
|
121
|
+
autoSyncEnabled: env.getBool('HOOK_GUARD_GITFLOW_AUTOSYNC', true),
|
|
122
|
+
autoCleanEnabled: env.getBool('HOOK_GUARD_GITFLOW_AUTOCLEAN', true),
|
|
123
|
+
requireClean: env.getBool('HOOK_GUARD_GITFLOW_REQUIRE_CLEAN', true)
|
|
124
|
+
}, logger, gitQuery, gitCommand, github));
|
|
125
|
+
}
|
|
126
|
+
return this.instances.get('gitFlowService');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
getRealtimeGuardService(monitors) {
|
|
130
|
+
if (!this.instances.has('guardService')) {
|
|
131
|
+
const logger = this.getLogger();
|
|
132
|
+
const notificationService = this.getNotificationService();
|
|
133
|
+
const orchestrator = this.getOrchestrator();
|
|
134
|
+
const auditLogger = this.getAuditLogger();
|
|
135
|
+
const config = {
|
|
136
|
+
debugLogPath: path.join(this.auditDir, 'guard-debug.log'),
|
|
137
|
+
repoRoot: this.repoRoot
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
this.instances.set('guardService', new RealtimeGuardService({
|
|
141
|
+
logger,
|
|
142
|
+
notificationService,
|
|
143
|
+
monitors,
|
|
144
|
+
orchestration: orchestrator,
|
|
145
|
+
config,
|
|
146
|
+
auditLogger
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
return this.instances.get('guardService');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = ServiceFactory;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const { getGitTreeState, isTreeBeyondLimit } = require('./GitTreeState');
|
|
4
3
|
const AuditLogger = require('./logging/AuditLogger');
|
|
5
4
|
const { recordMetric } = require('../../infrastructure/telemetry/metrics-logger');
|
|
6
5
|
const env = require('../../config/env.js');
|
|
6
|
+
const GuardNotifier = require('./guard/GuardNotifier');
|
|
7
|
+
const EvidenceManager = require('./guard/EvidenceManager');
|
|
8
|
+
const GitTreeManager = require('./guard/GitTreeManager');
|
|
7
9
|
|
|
8
10
|
class RealtimeGuardService {
|
|
9
11
|
/**
|
|
@@ -27,9 +29,6 @@ class RealtimeGuardService {
|
|
|
27
29
|
} = dependencies;
|
|
28
30
|
|
|
29
31
|
this.logger = logger || console;
|
|
30
|
-
this.notificationService = notificationService;
|
|
31
|
-
this.notifier = typeof notifier === 'function' ? notifier : null;
|
|
32
|
-
this.notificationsEnabled = notifications !== false;
|
|
33
32
|
this.monitors = monitors || {};
|
|
34
33
|
this.orchestration = orchestration;
|
|
35
34
|
this.config = config || {};
|
|
@@ -40,29 +39,21 @@ class RealtimeGuardService {
|
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
this.evidencePath = this.config.evidencePath || path.join(process.cwd(), '.AI_EVIDENCE.json');
|
|
43
|
-
this.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
this.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.
|
|
53
|
-
|
|
54
|
-
this.
|
|
55
|
-
this.
|
|
56
|
-
this.gitTreeTimer = null;
|
|
57
|
-
this.lastDirtyTreeNotification = 0;
|
|
58
|
-
this.dirtyTreeActive = false;
|
|
59
|
-
|
|
60
|
-
this.autoRefreshCooldownMs = env.getNumber('HOOK_GUARD_EVIDENCE_AUTO_REFRESH_COOLDOWN', 180000);
|
|
61
|
-
this.lastAutoRefresh = 0;
|
|
62
|
-
this.autoRefreshInFlight = false;
|
|
42
|
+
this.embedTokenMonitor = env.getBool('HOOK_GUARD_EMBEDDED_TOKEN_MONITOR', false);
|
|
43
|
+
|
|
44
|
+
// Initialize specialized components
|
|
45
|
+
this.notifier = new GuardNotifier(
|
|
46
|
+
this.logger,
|
|
47
|
+
notificationService,
|
|
48
|
+
typeof notifier === 'function' ? notifier : null,
|
|
49
|
+
notifications !== false
|
|
50
|
+
);
|
|
51
|
+
this.notifier.setDebugLogPath(this.config.debugLogPath);
|
|
52
|
+
|
|
53
|
+
this.evidenceManager = new EvidenceManager(this.evidencePath, this.notifier, this.auditLogger);
|
|
54
|
+
this.gitTreeManager = new GitTreeManager(this.notifier, this.auditLogger);
|
|
63
55
|
|
|
64
56
|
this.watchers = [];
|
|
65
|
-
this.embedTokenMonitor = env.getBool('HOOK_GUARD_EMBEDDED_TOKEN_MONITOR', false);
|
|
66
57
|
}
|
|
67
58
|
|
|
68
59
|
start() {
|
|
@@ -70,7 +61,7 @@ class RealtimeGuardService {
|
|
|
70
61
|
this.auditLogger.record({ action: 'guard.realtime.start', resource: 'realtime_guard', status: 'success' });
|
|
71
62
|
recordMetric({ hook: 'realtime_guard', status: 'start' });
|
|
72
63
|
|
|
73
|
-
// Start all monitors
|
|
64
|
+
// Start all monitors via specialized managers
|
|
74
65
|
this._startEvidenceMonitoring();
|
|
75
66
|
this._startGitTreeMonitoring();
|
|
76
67
|
this._startActivityMonitoring();
|
|
@@ -100,40 +91,22 @@ class RealtimeGuardService {
|
|
|
100
91
|
}
|
|
101
92
|
});
|
|
102
93
|
|
|
94
|
+
// Stop specialized managers
|
|
95
|
+
this.evidenceManager.stopPolling();
|
|
96
|
+
this.gitTreeManager.stopMonitoring();
|
|
97
|
+
|
|
103
98
|
this.logger.info('[RealtimeGuardService] All services stopped');
|
|
104
99
|
}
|
|
105
100
|
|
|
106
101
|
_startEvidenceMonitoring() {
|
|
107
|
-
this.
|
|
108
|
-
() => this.notify('
|
|
109
|
-
() => this.notify('
|
|
102
|
+
this.evidenceManager.startPolling(
|
|
103
|
+
() => this.notifier.notify('Evidence is stale - Auto-refreshing...', 'warning'),
|
|
104
|
+
() => this.notifier.notify('Evidence refreshed successfully', 'success')
|
|
110
105
|
);
|
|
111
106
|
}
|
|
112
107
|
|
|
113
108
|
_startGitTreeMonitoring() {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
this.monitors.gitTree.startMonitoring((state) => {
|
|
117
|
-
if (state.isBeyondLimit) {
|
|
118
|
-
const message = `Git tree has too many files: ${state.total} total (${state.staged} staged, ${state.unstaged} unstaged)`;
|
|
119
|
-
this.notify(message, 'error', { forceDialog: true });
|
|
120
|
-
this.auditLogger.record({
|
|
121
|
-
action: 'guard.git_tree.dirty',
|
|
122
|
-
resource: 'git_tree',
|
|
123
|
-
status: 'warning',
|
|
124
|
-
meta: { total: state.total, staged: state.staged, unstaged: state.unstaged }
|
|
125
|
-
});
|
|
126
|
-
recordMetric({ hook: 'git_tree', status: 'dirty', total: state.total, staged: state.staged, unstaged: state.unstaged });
|
|
127
|
-
} else {
|
|
128
|
-
this.notify('✅ Git tree is clean', 'success');
|
|
129
|
-
this.auditLogger.record({
|
|
130
|
-
action: 'guard.git_tree.clean',
|
|
131
|
-
resource: 'git_tree',
|
|
132
|
-
status: 'success'
|
|
133
|
-
});
|
|
134
|
-
recordMetric({ hook: 'git_tree', status: 'clean' });
|
|
135
|
-
}
|
|
136
|
-
});
|
|
109
|
+
this.gitTreeManager.startMonitoring();
|
|
137
110
|
}
|
|
138
111
|
|
|
139
112
|
_startTokenMonitoring() {
|
|
@@ -144,7 +117,7 @@ class RealtimeGuardService {
|
|
|
144
117
|
|
|
145
118
|
try {
|
|
146
119
|
this.monitors.token.start();
|
|
147
|
-
this.notify('🔋 Token monitor started', 'info');
|
|
120
|
+
this.notifier.notify('🔋 Token monitor started', 'info');
|
|
148
121
|
this.auditLogger.record({
|
|
149
122
|
action: 'guard.token_monitor.start',
|
|
150
123
|
resource: 'token_monitor',
|
|
@@ -152,7 +125,7 @@ class RealtimeGuardService {
|
|
|
152
125
|
});
|
|
153
126
|
recordMetric({ hook: 'token_monitor', status: 'start' });
|
|
154
127
|
} catch (error) {
|
|
155
|
-
this.notify(`Failed to start token monitor: ${error.message}`, 'error');
|
|
128
|
+
this.notifier.notify(`Failed to start token monitor: ${error.message}`, 'error');
|
|
156
129
|
this.auditLogger.record({
|
|
157
130
|
action: 'guard.token_monitor.start',
|
|
158
131
|
resource: 'token_monitor',
|
|
@@ -178,7 +151,7 @@ class RealtimeGuardService {
|
|
|
178
151
|
if (this.monitors.gitFlow.isClean()) {
|
|
179
152
|
const result = this.monitors.gitFlow.syncBranches();
|
|
180
153
|
if (result.success) {
|
|
181
|
-
this.notify('🔄 Branches synchronized', 'info');
|
|
154
|
+
this.notifier.notify('🔄 Branches synchronized', 'info');
|
|
182
155
|
recordMetric({ hook: 'gitflow_autosync', status: 'sync_success' });
|
|
183
156
|
}
|
|
184
157
|
}
|
|
@@ -199,232 +172,62 @@ class RealtimeGuardService {
|
|
|
199
172
|
if (this.monitors.ast) this.monitors.ast.start();
|
|
200
173
|
}
|
|
201
174
|
|
|
202
|
-
|
|
203
|
-
const { forceDialog = false, ...metadata } = options;
|
|
204
|
-
this._appendDebugLog(`NOTIFY|${level}|${forceDialog ? 'force-dialog|' : ''}${message}`);
|
|
205
|
-
|
|
206
|
-
if (this.notifier && this.notificationsEnabled) {
|
|
207
|
-
try {
|
|
208
|
-
this.notifier(message, level, { ...metadata, forceDialog });
|
|
209
|
-
} catch (error) {
|
|
210
|
-
const msg = error && error.message ? error.message : String(error);
|
|
211
|
-
this._appendDebugLog(`NOTIFIER_ERROR|${msg}`);
|
|
212
|
-
this.logger?.debug?.('REALTIME_GUARD_NOTIFIER_ERROR', { error: msg });
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (this.notificationService) {
|
|
217
|
-
this.notificationService.enqueue({
|
|
218
|
-
message,
|
|
219
|
-
level,
|
|
220
|
-
metadata: { ...metadata, forceDialog }
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
}
|
|
175
|
+
// --- Delegation to specialized components ---
|
|
224
176
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const timestamp = new Date().toISOString();
|
|
228
|
-
fs.appendFileSync(this.config.debugLogPath, `[${timestamp}] ${entry}\n`);
|
|
229
|
-
} catch (error) {
|
|
230
|
-
console.error('[RealtimeGuardService] Failed to write debug log:', error.message);
|
|
231
|
-
}
|
|
177
|
+
notify(message, level = 'info', options = {}) {
|
|
178
|
+
return this.notifier.notify(message, level, options);
|
|
232
179
|
}
|
|
233
180
|
|
|
234
181
|
appendDebugLog(entry) {
|
|
235
|
-
this.
|
|
182
|
+
return this.notifier.appendDebugLog(entry);
|
|
236
183
|
}
|
|
237
184
|
|
|
238
185
|
readEvidenceTimestamp() {
|
|
239
|
-
|
|
240
|
-
if (!fs.existsSync(this.evidencePath)) {
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
243
|
-
const raw = fs.readFileSync(this.evidencePath, 'utf8');
|
|
244
|
-
const json = JSON.parse(raw);
|
|
245
|
-
const ts = json?.timestamp;
|
|
246
|
-
if (!ts) {
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
|
-
const ms = new Date(ts).getTime();
|
|
250
|
-
if (Number.isNaN(ms)) {
|
|
251
|
-
return null;
|
|
252
|
-
}
|
|
253
|
-
return ms;
|
|
254
|
-
} catch (error) {
|
|
255
|
-
const msg = error && error.message ? error.message : String(error);
|
|
256
|
-
this._appendDebugLog(`EVIDENCE_TIMESTAMP_ERROR|${msg}`);
|
|
257
|
-
this.logger?.debug?.('REALTIME_GUARD_EVIDENCE_TIMESTAMP_ERROR', { error: msg });
|
|
258
|
-
return null;
|
|
259
|
-
}
|
|
186
|
+
return this.evidenceManager.readEvidenceTimestamp();
|
|
260
187
|
}
|
|
261
188
|
|
|
262
189
|
evaluateEvidenceAge(source = 'manual', notifyFresh = false) {
|
|
263
|
-
|
|
264
|
-
const timestamp = this.readEvidenceTimestamp();
|
|
265
|
-
if (!timestamp) {
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const ageMs = now - timestamp;
|
|
270
|
-
const isStale = ageMs > this.staleThresholdMs;
|
|
271
|
-
const isRecentlyActive = this.lastUserActivityAt && (now - this.lastUserActivityAt) < this.inactivityGraceMs;
|
|
272
|
-
|
|
273
|
-
if (isStale && !isRecentlyActive) {
|
|
274
|
-
this.triggerStaleAlert(source, ageMs);
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (notifyFresh && this.lastStaleNotification > 0 && !isStale) {
|
|
279
|
-
this.notify('Evidence updated; back within SLA.', 'info');
|
|
280
|
-
this.lastStaleNotification = 0;
|
|
281
|
-
}
|
|
190
|
+
return this.evidenceManager.evaluateEvidenceAge(source, notifyFresh);
|
|
282
191
|
}
|
|
283
192
|
|
|
284
193
|
triggerStaleAlert(source, ageMs) {
|
|
285
|
-
|
|
286
|
-
if (this.lastStaleNotification && (now - this.lastStaleNotification) < this.reminderIntervalMs) {
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
this.lastStaleNotification = now;
|
|
291
|
-
const ageSec = Math.floor(ageMs / 1000);
|
|
292
|
-
this.notify(`Evidence has been stale for ${ageSec}s (source: ${source}).`, 'warn', { forceDialog: true });
|
|
293
|
-
this.auditLogger.record({
|
|
294
|
-
action: 'guard.evidence.stale',
|
|
295
|
-
resource: 'evidence',
|
|
296
|
-
status: 'warning',
|
|
297
|
-
meta: { ageSec, source }
|
|
298
|
-
});
|
|
299
|
-
recordMetric({ hook: 'evidence', status: 'stale', ageSec, source });
|
|
300
|
-
void this.attemptAutoRefresh('stale');
|
|
194
|
+
return this.evidenceManager.triggerStaleAlert(source, ageMs);
|
|
301
195
|
}
|
|
302
196
|
|
|
303
197
|
async attemptAutoRefresh(reason = 'manual') {
|
|
304
|
-
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const updateScriptCandidates = [
|
|
309
|
-
path.join(process.cwd(), 'scripts/hooks-system/bin/update-evidence.sh'),
|
|
310
|
-
path.join(process.cwd(), 'node_modules/@pumuki/ast-intelligence-hooks/bin/update-evidence.sh'),
|
|
311
|
-
path.join(process.cwd(), 'bin/update-evidence.sh')
|
|
312
|
-
];
|
|
313
|
-
|
|
314
|
-
const updateScript = updateScriptCandidates.find(p => fs.existsSync(p));
|
|
315
|
-
if (!updateScript) {
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const now = Date.now();
|
|
320
|
-
const ts = this.readEvidenceTimestamp();
|
|
321
|
-
if (ts && (now - ts) <= this.staleThresholdMs) {
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
if (this.lastAutoRefresh && (now - this.lastAutoRefresh) < this.autoRefreshCooldownMs) {
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (this.autoRefreshInFlight) {
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
this.autoRefreshInFlight = true;
|
|
334
|
-
try {
|
|
335
|
-
await this.runDirectEvidenceRefresh(reason);
|
|
336
|
-
this.lastAutoRefresh = now;
|
|
337
|
-
this.auditLogger.record({
|
|
338
|
-
action: 'guard.evidence.auto_refresh',
|
|
339
|
-
resource: 'evidence',
|
|
340
|
-
status: 'success',
|
|
341
|
-
meta: { reason }
|
|
342
|
-
});
|
|
343
|
-
recordMetric({ hook: 'evidence', status: 'auto_refresh_success', reason });
|
|
344
|
-
} finally {
|
|
345
|
-
this.autoRefreshInFlight = false;
|
|
346
|
-
}
|
|
198
|
+
return this.evidenceManager.attemptAutoRefresh(reason);
|
|
347
199
|
}
|
|
348
200
|
|
|
349
|
-
async runDirectEvidenceRefresh(
|
|
350
|
-
return;
|
|
201
|
+
async runDirectEvidenceRefresh(reason) {
|
|
202
|
+
return this.evidenceManager.runDirectEvidenceRefresh(reason);
|
|
351
203
|
}
|
|
352
204
|
|
|
353
205
|
async evaluateGitTree() {
|
|
354
|
-
|
|
355
|
-
const state = getGitTreeState();
|
|
356
|
-
const limits = {
|
|
357
|
-
stagedLimit: this.gitTreeStagedThreshold,
|
|
358
|
-
unstagedLimit: this.gitTreeUnstagedThreshold,
|
|
359
|
-
totalLimit: this.gitTreeTotalThreshold
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
if (isTreeBeyondLimit(state, limits)) {
|
|
363
|
-
this.handleDirtyTree(state, limits);
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
this.resolveDirtyTree(state, limits);
|
|
367
|
-
} catch (error) {
|
|
368
|
-
this.appendDebugLog(`DIRTY_TREE_ERROR|${error.message}`);
|
|
369
|
-
}
|
|
206
|
+
return this.gitTreeManager.evaluateGitTree();
|
|
370
207
|
}
|
|
371
208
|
|
|
372
|
-
resolveDirtyTree(
|
|
373
|
-
this.
|
|
209
|
+
resolveDirtyTree(state, limits) {
|
|
210
|
+
return this.gitTreeManager.resolveDirtyTree(state, limits);
|
|
374
211
|
}
|
|
375
212
|
|
|
376
|
-
handleDirtyTree(
|
|
377
|
-
|
|
378
|
-
const limits = typeof limitOrLimits === 'number'
|
|
379
|
-
? { totalLimit: limitOrLimits }
|
|
380
|
-
: (limitOrLimits || {});
|
|
381
|
-
|
|
382
|
-
if (this.lastDirtyTreeNotification && (now - this.lastDirtyTreeNotification) < this.gitTreeReminderMs) {
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
this.lastDirtyTreeNotification = now;
|
|
387
|
-
this.dirtyTreeActive = true;
|
|
388
|
-
this.notify('Git tree is dirty; please stage/unstage to reduce file count.', 'warn', { forceDialog: true, ...limits });
|
|
389
|
-
this.persistDirtyTreeState();
|
|
213
|
+
handleDirtyTree(state, limits) {
|
|
214
|
+
return this.gitTreeManager.handleDirtyTree(state, limits);
|
|
390
215
|
}
|
|
391
216
|
|
|
392
217
|
persistDirtyTreeState() {
|
|
393
|
-
return;
|
|
218
|
+
return this.gitTreeManager.persistDirtyTreeState();
|
|
394
219
|
}
|
|
395
220
|
|
|
396
221
|
startGitTreeMonitoring() {
|
|
397
|
-
|
|
398
|
-
clearInterval(this.gitTreeTimer);
|
|
399
|
-
this.gitTreeTimer = null;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
const thresholdsValid = this.gitTreeStagedThreshold > 0 || this.gitTreeUnstagedThreshold > 0 || this.gitTreeTotalThreshold > 0;
|
|
403
|
-
if (!thresholdsValid || this.gitTreeCheckIntervalMs <= 0) {
|
|
404
|
-
this.gitTreeTimer = null;
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
void this.evaluateGitTree();
|
|
409
|
-
this.gitTreeTimer = setInterval(() => {
|
|
410
|
-
void this.evaluateGitTree();
|
|
411
|
-
}, this.gitTreeCheckIntervalMs);
|
|
222
|
+
return this.gitTreeManager.startMonitoring();
|
|
412
223
|
}
|
|
413
224
|
|
|
414
225
|
startEvidencePolling() {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
this.pollTimer = null;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
if (this.pollIntervalMs <= 0) {
|
|
421
|
-
this.pollTimer = null;
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
226
|
+
return this.evidenceManager.startPolling();
|
|
227
|
+
}
|
|
424
228
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}, this.pollIntervalMs);
|
|
229
|
+
updateUserActivity() {
|
|
230
|
+
return this.evidenceManager.updateUserActivity();
|
|
428
231
|
}
|
|
429
232
|
}
|
|
430
233
|
|