pumuki-ast-hooks 5.5.49 → 5.5.51
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/hooks/git-status-monitor.ts +5 -0
- package/hooks/notify-macos.ts +1 -0
- package/hooks/pre-tool-use-evidence-validator.ts +1 -0
- package/package.json +1 -1
- package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +56 -0
- package/scripts/hooks-system/application/services/guard/GuardConfig.js +4 -2
- package/scripts/hooks-system/application/services/installation/GitEnvironmentService.js +20 -2
- package/scripts/hooks-system/application/services/installation/HookAssetsInstaller.js +0 -0
- package/scripts/hooks-system/application/services/installation/McpConfigurator.js +84 -18
- package/scripts/hooks-system/application/services/monitoring/EvidenceMonitor.js +5 -146
- package/scripts/hooks-system/application/services/monitoring/EvidenceRefreshRunner.js +161 -0
- package/scripts/hooks-system/infrastructure/ast/ast-core.js +13 -1
- package/scripts/hooks-system/infrastructure/ast/ast-intelligence.js +2 -3
- package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +1 -4
- package/scripts/hooks-system/infrastructure/ast/backend/detectors/god-class-detector.js +1 -2
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/StagedSwiftFilePreparer.js +59 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/SwiftAstRunner.js +51 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/SwiftToolchainResolver.js +57 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSASTIntelligentAnalyzer.js +27 -137
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSAstAnalysisOrchestrator.js +32 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSEnterpriseAnalyzer.js +34 -397
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSEnterpriseChecks.js +350 -0
- package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +407 -5
- package/scripts/hooks-system/infrastructure/orchestration/__tests__/intelligent-audit.spec.js +16 -0
- package/scripts/hooks-system/infrastructure/orchestration/intelligent-audit.js +1 -1
- package/scripts/hooks-system/infrastructure/shell/gitflow/gitflow-enforcer.sh +33 -11
|
@@ -42,6 +42,7 @@ function getGitStatus(projectDir: string): GitStatus | null {
|
|
|
42
42
|
hasUncommittedChanges: lines.length > 0
|
|
43
43
|
};
|
|
44
44
|
} catch (err) {
|
|
45
|
+
console.error(`[git-status-monitor] Failed to read git status: ${(err as Error).message}`);
|
|
45
46
|
return null;
|
|
46
47
|
}
|
|
47
48
|
}
|
|
@@ -79,6 +80,7 @@ function detectPlatformFromFiles(projectDir: string): string[] {
|
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
} catch (err) {
|
|
83
|
+
console.error(`[git-status-monitor] Failed to detect platforms from files: ${(err as Error).message}`);
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
return platforms.length > 0 ? platforms : ['frontend', 'backend', 'ios', 'android'];
|
|
@@ -134,6 +136,7 @@ async function main() {
|
|
|
134
136
|
sound: 'Ping'
|
|
135
137
|
});
|
|
136
138
|
} catch (err) {
|
|
139
|
+
console.error(`[git-status-monitor] Notification failed (staged): ${(err as Error).message}`);
|
|
137
140
|
}
|
|
138
141
|
} else if (totalChanges > 10) {
|
|
139
142
|
try {
|
|
@@ -144,11 +147,13 @@ async function main() {
|
|
|
144
147
|
sound: 'Glass'
|
|
145
148
|
});
|
|
146
149
|
} catch (err) {
|
|
150
|
+
console.error(`[git-status-monitor] Notification failed (unstaged): ${(err as Error).message}`);
|
|
147
151
|
}
|
|
148
152
|
}
|
|
149
153
|
|
|
150
154
|
process.exit(0);
|
|
151
155
|
} catch (err) {
|
|
156
|
+
console.error(`[git-status-monitor] Unexpected error: ${(err as Error).message}`);
|
|
152
157
|
process.exit(0);
|
|
153
158
|
}
|
|
154
159
|
}
|
package/hooks/notify-macos.ts
CHANGED
|
@@ -24,6 +24,7 @@ export function sendMacOSNotification(options: NotificationOptions): void {
|
|
|
24
24
|
try {
|
|
25
25
|
execSync(`osascript -e '${script}'`, { stdio: 'ignore' });
|
|
26
26
|
} catch (err) {
|
|
27
|
+
console.error(`[notify-macos] Failed to send notification: ${(err as Error).message}`);
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -235,6 +235,7 @@ async function main() {
|
|
|
235
235
|
sound: 'Basso'
|
|
236
236
|
});
|
|
237
237
|
} catch (err) {
|
|
238
|
+
process.stderr.write(`Notification failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
238
239
|
}
|
|
239
240
|
process.stderr.write(`${validation.error || ''}\n`);
|
|
240
241
|
process.exit(2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki-ast-hooks",
|
|
3
|
-
"version": "5.5.
|
|
3
|
+
"version": "5.5.51",
|
|
4
4
|
"description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -102,3 +102,59 @@
|
|
|
102
102
|
{"timestamp":1767739777882,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
103
103
|
{"timestamp":1767739777882,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
104
104
|
{"timestamp":1767739777882,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
105
|
+
{"timestamp":1767772854432,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
106
|
+
{"timestamp":1767772854432,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
107
|
+
{"timestamp":1767772854432,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
108
|
+
{"timestamp":1767772854432,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
109
|
+
{"timestamp":1767772971939,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
110
|
+
{"timestamp":1767772971939,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
111
|
+
{"timestamp":1767772971939,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
112
|
+
{"timestamp":1767772971939,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
113
|
+
{"timestamp":1767775802167,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
114
|
+
{"timestamp":1767775802167,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
115
|
+
{"timestamp":1767775802167,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
116
|
+
{"timestamp":1767775802167,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
117
|
+
{"timestamp":1767775833943,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
118
|
+
{"timestamp":1767775833943,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
119
|
+
{"timestamp":1767775833943,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
120
|
+
{"timestamp":1767775833943,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
121
|
+
{"timestamp":1767776135844,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
122
|
+
{"timestamp":1767776135844,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
123
|
+
{"timestamp":1767776135844,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
124
|
+
{"timestamp":1767776135844,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
125
|
+
{"timestamp":1767776384649,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
126
|
+
{"timestamp":1767776384649,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
127
|
+
{"timestamp":1767776384649,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
128
|
+
{"timestamp":1767776384649,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
129
|
+
{"timestamp":1767777653343,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
130
|
+
{"timestamp":1767777653343,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
131
|
+
{"timestamp":1767777653343,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
132
|
+
{"timestamp":1767777653344,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
133
|
+
{"timestamp":1767778142566,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
134
|
+
{"timestamp":1767778142567,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
135
|
+
{"timestamp":1767778142567,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
136
|
+
{"timestamp":1767778142567,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
137
|
+
{"timestamp":1767778800954,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
138
|
+
{"timestamp":1767778800955,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
139
|
+
{"timestamp":1767778800955,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
140
|
+
{"timestamp":1767778800955,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
141
|
+
{"timestamp":1767779528552,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
142
|
+
{"timestamp":1767779528552,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
143
|
+
{"timestamp":1767779528552,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
144
|
+
{"timestamp":1767779528552,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
145
|
+
{"timestamp":1767779903813,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
146
|
+
{"timestamp":1767779903813,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
147
|
+
{"timestamp":1767779903813,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
148
|
+
{"timestamp":1767779903813,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
149
|
+
{"timestamp":1767780497468,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
150
|
+
{"timestamp":1767780497468,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
151
|
+
{"timestamp":1767780497468,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
152
|
+
{"timestamp":1767780497468,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
153
|
+
{"timestamp":1767780655724,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
154
|
+
{"timestamp":1767780655725,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
155
|
+
{"timestamp":1767780655725,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
156
|
+
{"timestamp":1767780655725,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
157
|
+
{"timestamp":1767782291627,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
158
|
+
{"timestamp":1767782291627,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
159
|
+
{"timestamp":1767782291627,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
160
|
+
{"timestamp":1767782291627,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
@@ -3,9 +3,11 @@ const AuditLogger = require('../logging/AuditLogger');
|
|
|
3
3
|
|
|
4
4
|
class GuardConfig {
|
|
5
5
|
constructor(env = envHelper) {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
this.auditLogger = new AuditLogger({ repoRoot: process.cwd() });
|
|
7
|
+
|
|
8
|
+
const getNumber = (name, def) =>
|
|
8
9
|
typeof env.getNumber === 'function' ? env.getNumber(name, def) : Number(env[name] || def);
|
|
10
|
+
|
|
9
11
|
const getBool = (name, def) =>
|
|
10
12
|
typeof env.getBool === 'function' ? env.getBool(name, def) : (env[name] !== 'false');
|
|
11
13
|
|
|
@@ -128,6 +128,20 @@ if [[ "$CURRENT_BRANCH" == "main" ]] || [[ "$CURRENT_BRANCH" == "master" ]] || [
|
|
|
128
128
|
exit 1
|
|
129
129
|
fi
|
|
130
130
|
|
|
131
|
+
# Enforce Git Flow checks (strict) before allowing commit
|
|
132
|
+
ENFORCER_SCRIPT="scripts/hooks-system/infrastructure/shell/gitflow/gitflow-enforcer.sh"
|
|
133
|
+
if [[ -f "$ENFORCER_SCRIPT" ]]; then
|
|
134
|
+
echo ""
|
|
135
|
+
echo "🔍 Running Git Flow checks (strict)..."
|
|
136
|
+
echo ""
|
|
137
|
+
if ! GITFLOW_STRICT_CHECK=true bash "$ENFORCER_SCRIPT" check; then
|
|
138
|
+
echo ""
|
|
139
|
+
echo "🚨 COMMIT BLOCKED: Git Flow checks failed"
|
|
140
|
+
echo ""
|
|
141
|
+
exit 1
|
|
142
|
+
fi
|
|
143
|
+
fi
|
|
144
|
+
|
|
131
145
|
# Check if there are staged files
|
|
132
146
|
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null | grep -E '\\.(ts|tsx|js|jsx|swift|kt)$' || true)
|
|
133
147
|
if [ -z "$STAGED_FILES" ]; then
|
|
@@ -263,10 +277,14 @@ fi
|
|
|
263
277
|
# Run gitflow-enforcer if available (optional validation)
|
|
264
278
|
ENFORCER_SCRIPT="scripts/hooks-system/infrastructure/shell/gitflow/gitflow-enforcer.sh"
|
|
265
279
|
if [[ -f "$ENFORCER_SCRIPT" ]]; then
|
|
266
|
-
|
|
280
|
+
echo ""
|
|
281
|
+
echo "🔍 Running Git Flow checks (strict)..."
|
|
282
|
+
echo ""
|
|
283
|
+
if ! GITFLOW_STRICT_CHECK=true bash "$ENFORCER_SCRIPT" check; then
|
|
267
284
|
echo ""
|
|
268
|
-
echo "
|
|
285
|
+
echo "🚨 PUSH BLOCKED: Git Flow checks failed"
|
|
269
286
|
echo ""
|
|
287
|
+
exit 1
|
|
270
288
|
fi
|
|
271
289
|
fi
|
|
272
290
|
|
|
File without changes
|
|
@@ -130,33 +130,99 @@ class McpConfigurator {
|
|
|
130
130
|
}
|
|
131
131
|
};
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
this.configureProjectScoped(mcpConfig, serverId);
|
|
134
|
+
this.cleanupGlobalConfig(serverId);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
configureProjectScoped(mcpConfig, serverId) {
|
|
138
|
+
const windsurfProjectDir = path.join(this.targetRoot, '.windsurf');
|
|
139
|
+
const windsurfProjectPath = path.join(windsurfProjectDir, 'mcp.json');
|
|
138
140
|
|
|
139
141
|
try {
|
|
140
|
-
if (!fs.existsSync(
|
|
141
|
-
fs.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
142
|
+
if (!fs.existsSync(windsurfProjectDir)) {
|
|
143
|
+
fs.mkdirSync(windsurfProjectDir, { recursive: true });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let finalConfig = mcpConfig;
|
|
147
|
+
if (fs.existsSync(windsurfProjectPath)) {
|
|
148
|
+
const existing = JSON.parse(fs.readFileSync(windsurfProjectPath, 'utf8'));
|
|
146
149
|
if (!existing.mcpServers) existing.mcpServers = {};
|
|
150
|
+
Object.keys(existing.mcpServers).forEach(id => {
|
|
151
|
+
if (id.startsWith('ast-intelligence-automation-') && id !== serverId) {
|
|
152
|
+
delete existing.mcpServers[id];
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
existing.mcpServers[serverId] = mcpConfig.mcpServers[serverId];
|
|
156
|
+
finalConfig = existing;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
fs.writeFileSync(windsurfProjectPath, JSON.stringify(finalConfig, null, 2));
|
|
160
|
+
this.logSuccess(`Configured project-scoped Windsurf MCP at ${windsurfProjectPath}`);
|
|
161
|
+
if (this.logger) this.logger.info('MCP_PROJECT_CONFIGURED', { path: windsurfProjectPath, serverId });
|
|
162
|
+
} catch (error) {
|
|
163
|
+
this.logWarning(`Failed to configure project-scoped MCP: ${error.message}`);
|
|
164
|
+
if (this.logger) this.logger.warn('MCP_PROJECT_CONFIGURE_FAILED', { error: error.message });
|
|
165
|
+
}
|
|
147
166
|
|
|
148
|
-
|
|
149
|
-
|
|
167
|
+
const cursorProjectDir = path.join(this.targetRoot, '.cursor');
|
|
168
|
+
const cursorProjectPath = path.join(cursorProjectDir, 'mcp.json');
|
|
150
169
|
|
|
170
|
+
try {
|
|
171
|
+
if (!fs.existsSync(cursorProjectDir)) {
|
|
172
|
+
fs.mkdirSync(cursorProjectDir, { recursive: true });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let finalConfig = mcpConfig;
|
|
176
|
+
if (fs.existsSync(cursorProjectPath)) {
|
|
177
|
+
const existing = JSON.parse(fs.readFileSync(cursorProjectPath, 'utf8'));
|
|
178
|
+
if (!existing.mcpServers) existing.mcpServers = {};
|
|
179
|
+
Object.keys(existing.mcpServers).forEach(id => {
|
|
180
|
+
if (id.startsWith('ast-intelligence-automation-') && id !== serverId) {
|
|
181
|
+
delete existing.mcpServers[id];
|
|
182
|
+
}
|
|
183
|
+
});
|
|
151
184
|
existing.mcpServers[serverId] = mcpConfig.mcpServers[serverId];
|
|
185
|
+
finalConfig = existing;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
fs.writeFileSync(cursorProjectPath, JSON.stringify(finalConfig, null, 2));
|
|
189
|
+
this.logSuccess(`Configured project-scoped Cursor MCP at ${cursorProjectPath}`);
|
|
190
|
+
if (this.logger) this.logger.info('MCP_CURSOR_CONFIGURED', { path: cursorProjectPath, serverId });
|
|
191
|
+
} catch (error) {
|
|
192
|
+
this.logWarning(`Failed to configure Cursor MCP: ${error.message}`);
|
|
193
|
+
if (this.logger) this.logger.warn('MCP_CURSOR_CONFIGURE_FAILED', { error: error.message });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
cleanupGlobalConfig(currentServerId) {
|
|
198
|
+
const globalConfigPath = this.getGlobalWindsurfConfigPath();
|
|
152
199
|
|
|
200
|
+
try {
|
|
201
|
+
if (!fs.existsSync(globalConfigPath)) return;
|
|
202
|
+
|
|
203
|
+
const existing = JSON.parse(fs.readFileSync(globalConfigPath, 'utf8'));
|
|
204
|
+
if (!existing.mcpServers) return;
|
|
205
|
+
|
|
206
|
+
let modified = false;
|
|
207
|
+
Object.keys(existing.mcpServers).forEach(id => {
|
|
208
|
+
const server = existing.mcpServers[id];
|
|
209
|
+
if (!server || !server.env) return;
|
|
210
|
+
|
|
211
|
+
if (server.env.REPO_ROOT === this.targetRoot) {
|
|
212
|
+
delete existing.mcpServers[id];
|
|
213
|
+
modified = true;
|
|
214
|
+
this.logInfo(`Removed ${id} from global config (now project-scoped)`);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
if (modified) {
|
|
153
219
|
fs.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2));
|
|
154
|
-
this.
|
|
155
|
-
|
|
220
|
+
if (this.logger) this.logger.info('MCP_GLOBAL_CLEANUP', { removedForRepo: this.targetRoot });
|
|
221
|
+
}
|
|
222
|
+
} catch (error) {
|
|
223
|
+
if (process.env.DEBUG) {
|
|
224
|
+
process.stderr.write(`[MCP] cleanupGlobalConfig failed: ${error.message}\n`);
|
|
156
225
|
}
|
|
157
|
-
} catch (mergeError) {
|
|
158
|
-
this.logWarning(`${globalConfigPath} exists but couldn't be merged, skipping`);
|
|
159
|
-
if (this.logger) this.logger.warn('MCP_GLOBAL_MERGE_FAILED', { error: mergeError.message });
|
|
160
226
|
}
|
|
161
227
|
}
|
|
162
228
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const {
|
|
4
|
-
const { ConfigurationError, DomainError } = require('../../../domain/errors');
|
|
3
|
+
const { EvidenceRefreshRunner } = require('./EvidenceRefreshRunner');
|
|
5
4
|
const AuditLogger = require('../logging/AuditLogger');
|
|
6
5
|
|
|
7
6
|
class EvidenceMonitor {
|
|
@@ -14,98 +13,9 @@ class EvidenceMonitor {
|
|
|
14
13
|
this.lastStaleNotification = 0;
|
|
15
14
|
this.pollTimer = null;
|
|
16
15
|
this.evidencePath = path.join(repoRoot, '.AI_EVIDENCE.json');
|
|
17
|
-
this.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
this.refreshTimeoutMs = options.refreshTimeoutMs || 120000;
|
|
21
|
-
this.refreshLockFile = path.join(this.tempDir, 'evidence-refresh.lock');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
isPidRunning(pid) {
|
|
25
|
-
if (!pid || !Number.isFinite(pid) || pid <= 0) return false;
|
|
26
|
-
try {
|
|
27
|
-
process.kill(pid, 0);
|
|
28
|
-
return true;
|
|
29
|
-
} catch {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
acquireRefreshLock() {
|
|
35
|
-
try {
|
|
36
|
-
fs.mkdirSync(this.tempDir, { recursive: true });
|
|
37
|
-
} catch (error) {
|
|
38
|
-
console.warn('[EvidenceMonitor] Failed to ensure temp dir:', error.message);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
const fd = fs.openSync(this.refreshLockFile, 'wx');
|
|
43
|
-
const payload = JSON.stringify({ pid: process.pid, timestamp: new Date().toISOString() });
|
|
44
|
-
fs.writeFileSync(fd, payload, { encoding: 'utf8' });
|
|
45
|
-
fs.closeSync(fd);
|
|
46
|
-
return { acquired: true };
|
|
47
|
-
} catch (error) {
|
|
48
|
-
if (error && error.code !== 'EEXIST') {
|
|
49
|
-
return { acquired: false, reason: 'error', error };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
const raw = String(fs.readFileSync(this.refreshLockFile, 'utf8') || '').trim();
|
|
54
|
-
const data = raw ? JSON.parse(raw) : null;
|
|
55
|
-
const lockPid = data && Number(data.pid);
|
|
56
|
-
if (lockPid && this.isPidRunning(lockPid)) {
|
|
57
|
-
return { acquired: false, reason: 'locked', pid: lockPid };
|
|
58
|
-
}
|
|
59
|
-
} catch (error) {
|
|
60
|
-
console.warn('[EvidenceMonitor] Failed to read refresh lock file:', error.message);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
fs.unlinkSync(this.refreshLockFile);
|
|
65
|
-
} catch (error) {
|
|
66
|
-
console.warn('[EvidenceMonitor] Failed to remove stale refresh lock:', error.message);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
const fd = fs.openSync(this.refreshLockFile, 'wx');
|
|
71
|
-
const payload = JSON.stringify({ pid: process.pid, timestamp: new Date().toISOString() });
|
|
72
|
-
fs.writeFileSync(fd, payload, { encoding: 'utf8' });
|
|
73
|
-
fs.closeSync(fd);
|
|
74
|
-
return { acquired: true };
|
|
75
|
-
} catch (retryError) {
|
|
76
|
-
return { acquired: false, reason: 'locked', error: retryError };
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
releaseRefreshLock() {
|
|
82
|
-
try {
|
|
83
|
-
if (!fs.existsSync(this.refreshLockFile)) return;
|
|
84
|
-
const raw = String(fs.readFileSync(this.refreshLockFile, 'utf8') || '').trim();
|
|
85
|
-
const data = raw ? JSON.parse(raw) : null;
|
|
86
|
-
const lockPid = data && Number(data.pid);
|
|
87
|
-
if (lockPid === process.pid) {
|
|
88
|
-
fs.unlinkSync(this.refreshLockFile);
|
|
89
|
-
}
|
|
90
|
-
} catch (error) {
|
|
91
|
-
console.warn('[EvidenceMonitor] Failed to release refresh lock:', error.message);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
resolveUpdateEvidenceScript() {
|
|
96
|
-
const candidates = [
|
|
97
|
-
path.join(this.repoRoot, 'node_modules/@pumuki/ast-intelligence-hooks/bin/update-evidence.sh'),
|
|
98
|
-
path.join(this.repoRoot, 'scripts/hooks-system/bin/update-evidence.sh'),
|
|
99
|
-
path.join(this.repoRoot, 'bin/update-evidence.sh')
|
|
100
|
-
];
|
|
101
|
-
|
|
102
|
-
for (const candidate of candidates) {
|
|
103
|
-
if (fs.existsSync(candidate)) {
|
|
104
|
-
return candidate;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return null;
|
|
16
|
+
this.refreshRunner = new EvidenceRefreshRunner(repoRoot, {
|
|
17
|
+
refreshTimeoutMs: options.refreshTimeoutMs
|
|
18
|
+
});
|
|
109
19
|
}
|
|
110
20
|
|
|
111
21
|
isStale() {
|
|
@@ -123,58 +33,7 @@ class EvidenceMonitor {
|
|
|
123
33
|
}
|
|
124
34
|
|
|
125
35
|
async refresh() {
|
|
126
|
-
|
|
127
|
-
throw new ConfigurationError('Update evidence script not found', 'updateScript');
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (this.refreshInFlight) {
|
|
131
|
-
return '';
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const lock = this.acquireRefreshLock();
|
|
135
|
-
if (!lock.acquired) {
|
|
136
|
-
return '';
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
this.refreshInFlight = true;
|
|
140
|
-
|
|
141
|
-
return new Promise((resolve, reject) => {
|
|
142
|
-
const child = require('child_process').spawn('bash', [this.updateScript, '--auto', '--refresh-only'], {
|
|
143
|
-
cwd: this.repoRoot,
|
|
144
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
let output = '';
|
|
148
|
-
child.stdout.on('data', (data) => {
|
|
149
|
-
output += data.toString();
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
const timeoutId = setTimeout(() => {
|
|
153
|
-
try {
|
|
154
|
-
child.kill('SIGKILL');
|
|
155
|
-
} catch (error) {
|
|
156
|
-
console.warn('[EvidenceMonitor] Failed to kill timed-out refresh process:', error.message);
|
|
157
|
-
}
|
|
158
|
-
}, this.refreshTimeoutMs);
|
|
159
|
-
|
|
160
|
-
child.on('close', (code) => {
|
|
161
|
-
clearTimeout(timeoutId);
|
|
162
|
-
this.refreshInFlight = false;
|
|
163
|
-
this.releaseRefreshLock();
|
|
164
|
-
if (code === 0) {
|
|
165
|
-
resolve(output);
|
|
166
|
-
} else {
|
|
167
|
-
reject(new DomainError(`Evidence refresh failed with code ${code}`, 'EVIDENCE_REFRESH_FAILED'));
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
child.on('error', (err) => {
|
|
172
|
-
clearTimeout(timeoutId);
|
|
173
|
-
this.refreshInFlight = false;
|
|
174
|
-
this.releaseRefreshLock();
|
|
175
|
-
reject(err);
|
|
176
|
-
});
|
|
177
|
-
});
|
|
36
|
+
return this.refreshRunner.refresh();
|
|
178
37
|
}
|
|
179
38
|
|
|
180
39
|
startPolling(onStale, onRefreshed) {
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const { ConfigurationError, DomainError } = require('../../../domain/errors');
|
|
5
|
+
|
|
6
|
+
class EvidenceRefreshRunner {
|
|
7
|
+
constructor(repoRoot, options = {}) {
|
|
8
|
+
this.repoRoot = repoRoot;
|
|
9
|
+
this.tempDir = path.join(repoRoot, '.audit_tmp');
|
|
10
|
+
this.refreshLockFile = path.join(this.tempDir, 'evidence-refresh.lock');
|
|
11
|
+
this.refreshTimeoutMs = options.refreshTimeoutMs || 120000;
|
|
12
|
+
this.refreshInFlight = false;
|
|
13
|
+
this.updateScript = this.resolveUpdateEvidenceScript();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
resolveUpdateEvidenceScript() {
|
|
17
|
+
const candidates = [
|
|
18
|
+
path.join(this.repoRoot, 'node_modules/@pumuki/ast-intelligence-hooks/bin/update-evidence.sh'),
|
|
19
|
+
path.join(this.repoRoot, 'scripts/hooks-system/bin/update-evidence.sh'),
|
|
20
|
+
path.join(this.repoRoot, 'bin/update-evidence.sh')
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
for (const candidate of candidates) {
|
|
24
|
+
if (fs.existsSync(candidate)) {
|
|
25
|
+
return candidate;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
isPidRunning(pid) {
|
|
33
|
+
if (!pid || !Number.isFinite(pid) || pid <= 0) return false;
|
|
34
|
+
try {
|
|
35
|
+
process.kill(pid, 0);
|
|
36
|
+
return true;
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
acquireRefreshLock() {
|
|
43
|
+
try {
|
|
44
|
+
fs.mkdirSync(this.tempDir, { recursive: true });
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.warn('[EvidenceRefreshRunner] Failed to ensure temp dir:', error.message);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const fd = fs.openSync(this.refreshLockFile, 'wx');
|
|
51
|
+
const payload = JSON.stringify({ pid: process.pid, timestamp: new Date().toISOString() });
|
|
52
|
+
fs.writeFileSync(fd, payload, { encoding: 'utf8' });
|
|
53
|
+
fs.closeSync(fd);
|
|
54
|
+
return { acquired: true };
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error && error.code !== 'EEXIST') {
|
|
57
|
+
return { acquired: false, reason: 'error', error };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const raw = String(fs.readFileSync(this.refreshLockFile, 'utf8') || '').trim();
|
|
62
|
+
const data = raw ? JSON.parse(raw) : null;
|
|
63
|
+
const lockPid = data && Number(data.pid);
|
|
64
|
+
if (lockPid && this.isPidRunning(lockPid)) {
|
|
65
|
+
return { acquired: false, reason: 'locked', pid: lockPid };
|
|
66
|
+
}
|
|
67
|
+
} catch (readError) {
|
|
68
|
+
console.warn('[EvidenceRefreshRunner] Failed to read refresh lock file:', readError.message);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
fs.unlinkSync(this.refreshLockFile);
|
|
73
|
+
} catch (removeError) {
|
|
74
|
+
console.warn('[EvidenceRefreshRunner] Failed to remove stale refresh lock:', removeError.message);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const fd = fs.openSync(this.refreshLockFile, 'wx');
|
|
79
|
+
const payload = JSON.stringify({ pid: process.pid, timestamp: new Date().toISOString() });
|
|
80
|
+
fs.writeFileSync(fd, payload, { encoding: 'utf8' });
|
|
81
|
+
fs.closeSync(fd);
|
|
82
|
+
return { acquired: true };
|
|
83
|
+
} catch (retryError) {
|
|
84
|
+
return { acquired: false, reason: 'locked', error: retryError };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
releaseRefreshLock() {
|
|
90
|
+
try {
|
|
91
|
+
if (!fs.existsSync(this.refreshLockFile)) return;
|
|
92
|
+
const raw = String(fs.readFileSync(this.refreshLockFile, 'utf8') || '').trim();
|
|
93
|
+
const data = raw ? JSON.parse(raw) : null;
|
|
94
|
+
const lockPid = data && Number(data.pid);
|
|
95
|
+
if (lockPid === process.pid) {
|
|
96
|
+
fs.unlinkSync(this.refreshLockFile);
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.warn('[EvidenceRefreshRunner] Failed to release refresh lock:', error.message);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async refresh() {
|
|
104
|
+
if (!this.updateScript) {
|
|
105
|
+
throw new ConfigurationError('Update evidence script not found', 'updateScript');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (this.refreshInFlight) {
|
|
109
|
+
return '';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const lock = this.acquireRefreshLock();
|
|
113
|
+
if (!lock.acquired) {
|
|
114
|
+
return '';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.refreshInFlight = true;
|
|
118
|
+
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
const child = spawn('bash', [this.updateScript, '--auto', '--refresh-only'], {
|
|
121
|
+
cwd: this.repoRoot,
|
|
122
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
let output = '';
|
|
126
|
+
child.stdout.on('data', (data) => {
|
|
127
|
+
output += data.toString();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const timeoutId = setTimeout(() => {
|
|
131
|
+
try {
|
|
132
|
+
child.kill('SIGKILL');
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.warn('[EvidenceRefreshRunner] Failed to kill timed-out refresh process:', error.message);
|
|
135
|
+
}
|
|
136
|
+
}, this.refreshTimeoutMs);
|
|
137
|
+
|
|
138
|
+
const finalize = () => {
|
|
139
|
+
clearTimeout(timeoutId);
|
|
140
|
+
this.refreshInFlight = false;
|
|
141
|
+
this.releaseRefreshLock();
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
child.on('close', (code) => {
|
|
145
|
+
finalize();
|
|
146
|
+
if (code === 0) {
|
|
147
|
+
resolve(output);
|
|
148
|
+
} else {
|
|
149
|
+
reject(new DomainError(`Evidence refresh failed with code ${code}`, 'EVIDENCE_REFRESH_FAILED'));
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
child.on('error', (err) => {
|
|
154
|
+
finalize();
|
|
155
|
+
reject(err);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = { EvidenceRefreshRunner };
|