pumuki-ast-hooks 5.5.53 → 5.5.54

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/README.md CHANGED
@@ -866,15 +866,6 @@ Automates the complete Git Flow cycle: commit → push → PR → merge, plus co
866
866
 
867
867
  For more details, see [MCP_SERVERS.md](./docs/MCP_SERVERS.md).
868
868
 
869
- #### Troubleshooting
870
-
871
- If `ai_gate_check` behaves inconsistently (stale branch name, missing rules, or intermittent transport errors), verify you are not running multiple `ast-intelligence-automation` servers across different repositories.
872
-
873
- - Prefer enabling a single MCP server for the repository you are working on.
874
- - Verify the active process points to this repository path:
875
- - `.../ast-intelligence-hooks/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js`
876
- - If you detect multiple processes, stop the duplicates and restart your IDE/MCP servers.
877
-
878
869
  ---
879
870
 
880
871
  ## API Reference
@@ -426,12 +426,26 @@ Checks if there are violations blocking work and returns `BLOCKED` or `ALLOWED`.
426
426
  ```json
427
427
  {
428
428
  "status": "ALLOWED|BLOCKED",
429
- "message": "All checks passed",
429
+ "timestamp": "2026-01-07T22:19:30.363Z",
430
+ "branch": "feature/my-task",
430
431
  "violations": [],
431
- "blockingReason": null
432
+ "warnings": [],
433
+ "autoFixes": [],
434
+ "mandatory_rules": {
435
+ "platforms": ["backend", "frontend", "ios", "android"],
436
+ "criticalRules": [],
437
+ "rulesLoaded": ["gold"],
438
+ "warning": "⚠️ AI MUST read and follow these rules before ANY code generation or modification"
439
+ },
440
+ "summary": "🚦 ALLOWED: Gate passed.",
441
+ "instructions": "You may proceed with user task. CRITICAL: Review mandatory_rules.criticalRules BEFORE generating ANY code."
432
442
  }
433
443
  ```
434
444
 
445
+ **Guarantee:** `mandatory_rules` is always returned (never `null`).
446
+ - If context/platform detection fails, it falls back to `['backend','frontend','ios','android']`.
447
+ - If rules loading fails, it returns an object with `criticalRules: []` and an `error` field instead of `null`.
448
+
435
449
  **When it returns `BLOCKED`:**
436
450
  - There are CRITICAL or HIGH violations
437
451
  - Evidence is stale and must be updated first
@@ -52,6 +52,40 @@ npm run ast:guard:restart
52
52
 
53
53
  ---
54
54
 
55
+ # Release Notes - v5.5.52
56
+
57
+ **Release Date**: January 8, 2026
58
+ **Type**: Patch Release
59
+ **Compatibility**: Fully backward compatible with 5.5.x
60
+
61
+ ---
62
+
63
+ ## 🚦 MCP AI Gate: mandatory_rules always present
64
+
65
+ ### Problem
66
+ `ai_gate_check` could return `mandatory_rules: null` when context/platform detection failed, forcing manual rule loading.
67
+
68
+ ### Solution
69
+ `ai_gate_check` now guarantees `mandatory_rules` is always returned:
70
+ - Fallback to `PlatformDetectionService` when `analyzeContext()` fails or returns no platforms.
71
+ - Deterministic fallback to `backend`, `frontend`, `ios`, `android` when detection is inconclusive.
72
+ - If rule loading fails, returns a non-null object with `criticalRules: []` and an `error` field.
73
+
74
+ ### Impact
75
+ - The AI can reliably read and apply rules on every iteration.
76
+ - Removes a class of regressions caused by missing rule payload in MCP responses.
77
+
78
+ ---
79
+
80
+ ## 📦 Installation / Upgrade
81
+
82
+ ```bash
83
+ npm install --save-dev pumuki-ast-hooks@5.5.52
84
+ npm run install-hooks
85
+ ```
86
+
87
+ ---
88
+
55
89
  # Release Notes - v5.5.24
56
90
 
57
91
  **Release Date**: January 4, 2026
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki-ast-hooks",
3
- "version": "5.5.53",
3
+ "version": "5.5.54",
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": {
@@ -166,3 +166,43 @@
166
166
  {"timestamp":1767784524038,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
167
167
  {"timestamp":1767784524038,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
168
168
  {"timestamp":1767784524038,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
169
+ {"timestamp":1767795854402,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
170
+ {"timestamp":1767795854402,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
171
+ {"timestamp":1767795854402,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
172
+ {"timestamp":1767795854402,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
173
+ {"timestamp":1767796412339,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
174
+ {"timestamp":1767796412339,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
175
+ {"timestamp":1767796412339,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
176
+ {"timestamp":1767796412339,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
177
+ {"timestamp":1767814882292,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
178
+ {"timestamp":1767814882292,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
179
+ {"timestamp":1767814882292,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
180
+ {"timestamp":1767814882292,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
181
+ {"timestamp":1767815229484,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
182
+ {"timestamp":1767815229484,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
183
+ {"timestamp":1767815229484,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
184
+ {"timestamp":1767815229484,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
185
+ {"timestamp":1767815730534,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
186
+ {"timestamp":1767815730534,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
187
+ {"timestamp":1767815730534,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
188
+ {"timestamp":1767815730534,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
189
+ {"timestamp":1767815803186,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
190
+ {"timestamp":1767815803186,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
191
+ {"timestamp":1767815803186,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
192
+ {"timestamp":1767815803186,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
193
+ {"timestamp":1767815859028,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
194
+ {"timestamp":1767815859028,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
195
+ {"timestamp":1767815859028,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
196
+ {"timestamp":1767815859028,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
197
+ {"timestamp":1767818741021,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
198
+ {"timestamp":1767818741022,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
199
+ {"timestamp":1767818741022,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
200
+ {"timestamp":1767818741022,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
201
+ {"timestamp":1767818773283,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
202
+ {"timestamp":1767818773283,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
203
+ {"timestamp":1767818773283,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
204
+ {"timestamp":1767818773283,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
205
+ {"timestamp":1767819338043,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
206
+ {"timestamp":1767819338043,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
207
+ {"timestamp":1767819338043,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
208
+ {"timestamp":1767819338043,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
@@ -1,10 +1,9 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const { execSync } = require('child_process');
4
- const crypto = require('crypto');
5
- const os = require('os');
6
- const env = require('../../../config/env.js');
7
3
  const AuditLogger = require('../logging/AuditLogger');
4
+ const McpServerConfigBuilder = require('./mcp/McpServerConfigBuilder');
5
+ const McpProjectConfigWriter = require('./mcp/McpProjectConfigWriter');
6
+ const McpGlobalConfigCleaner = require('./mcp/McpGlobalConfigCleaner');
8
7
 
9
8
  const COLORS = {
10
9
  reset: '\x1b[0m',
@@ -13,217 +12,22 @@ const COLORS = {
13
12
  cyan: '\x1b[36m'
14
13
  };
15
14
 
16
- function slugifyId(input) {
17
- return String(input || '')
18
- .trim()
19
- .toLowerCase()
20
- .replace(/[^a-z0-9]+/g, '-')
21
- .replace(/^-+|-+$/g, '')
22
- .slice(0, 48);
23
- }
24
-
25
- function computeRepoFingerprint(repoRoot) {
26
- try {
27
- const real = fs.realpathSync(repoRoot);
28
- return crypto.createHash('sha1').update(real).digest('hex').slice(0, 8);
29
- } catch (error) {
30
- if (process.env.DEBUG) {
31
- process.stderr.write(`[MCP] computeRepoFingerprint fallback: ${error && error.message ? error.message : String(error)}\n`);
32
- }
33
- return crypto.createHash('sha1').update(String(repoRoot || '')).digest('hex').slice(0, 8);
34
- }
35
- }
36
-
37
- function computeServerIdForRepo(repoRoot) {
38
- const legacyServerId = 'ast-intelligence-automation';
39
- const forced = (env.get('MCP_SERVER_ID', '') || '').trim();
40
- if (forced.length > 0) return forced;
41
-
42
- const repoName = path.basename(repoRoot || process.cwd());
43
- const slug = slugifyId(repoName) || 'repo';
44
- const fp = computeRepoFingerprint(repoRoot);
45
- return `${legacyServerId}-${slug}-${fp}`;
46
- }
47
-
48
15
  class McpConfigurator {
49
16
  constructor(targetRoot, hookSystemRoot, logger = null) {
50
17
  this.targetRoot = targetRoot;
51
18
  this.hookSystemRoot = hookSystemRoot;
52
19
  this.logger = logger;
53
- }
54
-
55
- resolveAutomationEntrypoint() {
56
- // Prefer dependency-installed hook system (node_modules) to avoid requiring scripts/hooks-system in consumer repos
57
- // Fallback to repo-local hook system if it exists.
58
- const candidates = [
59
- this.hookSystemRoot
60
- ? path.join(this.hookSystemRoot, 'infrastructure', 'mcp', 'ast-intelligence-automation.js')
61
- : null,
62
- path.join(this.targetRoot, 'scripts', 'hooks-system', 'infrastructure', 'mcp', 'ast-intelligence-automation.js')
63
- ].filter(Boolean);
64
-
65
- for (const c of candidates) {
66
- try {
67
- if (fs.existsSync(c)) return c;
68
- } catch (error) {
69
- // Log error but continue checking other candidates
70
- console.warn(`Failed to check path ${c}:`, error.message);
71
- }
72
- }
73
-
74
- // Last resort: keep previous behaviour
75
- return path.join(this.targetRoot, 'scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js');
76
- }
77
-
78
- disableDuplicateServersForRepo(existing, currentServerId) {
79
- if (!existing || !existing.mcpServers) return;
80
-
81
- Object.entries(existing.mcpServers).forEach(([id, server]) => {
82
- if (!server || id === currentServerId) return;
83
- const envObj = server.env || {};
84
- const repoRoot = envObj.REPO_ROOT;
85
- if (repoRoot !== this.targetRoot) return;
86
-
87
- const args0 = Array.isArray(server.args) ? String(server.args[0] || '') : '';
88
- // Disable legacy server pointing to repo-local scripts/hooks-system when we already configure the canonical one
89
- if (args0.includes('/scripts/hooks-system/') || args0.includes('\\scripts\\hooks-system\\')) {
90
- server.disabled = true;
91
- }
92
- });
93
- }
94
-
95
- getGlobalWindsurfConfigPath() {
96
- return path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json');
20
+ this.serverConfigBuilder = new McpServerConfigBuilder(targetRoot, hookSystemRoot, logger);
21
+ this.projectWriter = new McpProjectConfigWriter(targetRoot, logger, this.logSuccess.bind(this), this.logWarning.bind(this), this.logInfo.bind(this));
22
+ this.globalCleaner = new McpGlobalConfigCleaner(targetRoot, logger, this.logInfo.bind(this));
97
23
  }
98
24
 
99
25
  configure() {
100
26
  if (this.logger) this.logger.info('MCP_CONFIGURATION_STARTED');
101
27
 
102
- let nodePath = process.execPath;
103
- if (!nodePath || !fs.existsSync(nodePath)) {
104
- try {
105
- nodePath = execSync('which node', { encoding: 'utf-8' }).trim();
106
- } catch (error) {
107
- if (process.env.DEBUG) {
108
- process.stderr.write(`[MCP] which node failed: ${error && error.message ? error.message : String(error)}\n`);
109
- }
110
- nodePath = 'node';
111
- }
112
- }
113
-
114
- const serverId = computeServerIdForRepo(this.targetRoot);
115
- const entrypoint = this.resolveAutomationEntrypoint();
116
- const mcpConfig = {
117
- mcpServers: {
118
- [serverId]: {
119
- command: nodePath,
120
- args: [
121
- entrypoint
122
- ],
123
- env: {
124
- REPO_ROOT: this.targetRoot,
125
- AUTO_COMMIT_ENABLED: 'false',
126
- AUTO_PUSH_ENABLED: 'false',
127
- AUTO_PR_ENABLED: 'false'
128
- }
129
- }
130
- }
131
- };
132
-
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');
140
-
141
- try {
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'));
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
- }
166
-
167
- const cursorProjectDir = path.join(this.targetRoot, '.cursor');
168
- const cursorProjectPath = path.join(cursorProjectDir, 'mcp.json');
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
- });
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();
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) {
219
- fs.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2));
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`);
225
- }
226
- }
28
+ const { serverId, mcpConfig } = this.serverConfigBuilder.build();
29
+ this.projectWriter.configureProjectScoped(mcpConfig, serverId);
30
+ this.globalCleaner.cleanupGlobalConfig(serverId);
227
31
  }
228
32
 
229
33
  detectIDEs() {
@@ -0,0 +1,49 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ class McpGlobalConfigCleaner {
5
+ constructor(targetRoot, logger, logInfo) {
6
+ this.targetRoot = targetRoot;
7
+ this.logger = logger;
8
+ this.logInfo = logInfo;
9
+ }
10
+
11
+ cleanupGlobalConfig(currentServerId) {
12
+ const globalConfigPath = this.getGlobalWindsurfConfigPath();
13
+
14
+ try {
15
+ if (!fs.existsSync(globalConfigPath)) return;
16
+
17
+ const existing = JSON.parse(fs.readFileSync(globalConfigPath, 'utf8'));
18
+ if (!existing.mcpServers) return;
19
+
20
+ let modified = false;
21
+ Object.keys(existing.mcpServers).forEach(id => {
22
+ const server = existing.mcpServers[id];
23
+ if (!server || !server.env) return;
24
+
25
+ if (server.env.REPO_ROOT === this.targetRoot) {
26
+ delete existing.mcpServers[id];
27
+ modified = true;
28
+ this.logInfo?.(`Removed ${id} from global config (now project-scoped)`);
29
+ }
30
+ });
31
+
32
+ if (modified) {
33
+ fs.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2));
34
+ this.logger?.info?.('MCP_GLOBAL_CLEANUP', { removedForRepo: this.targetRoot });
35
+ }
36
+ } catch (error) {
37
+ if (process.env.DEBUG) {
38
+ process.stderr.write(`[MCP] cleanupGlobalConfig failed: ${error.message}\n`);
39
+ }
40
+ }
41
+ }
42
+
43
+ getGlobalWindsurfConfigPath() {
44
+ const os = require('os');
45
+ return path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json');
46
+ }
47
+ }
48
+
49
+ module.exports = McpGlobalConfigCleaner;
@@ -0,0 +1,59 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ class McpProjectConfigWriter {
5
+ constructor(targetRoot, logger, logSuccess, logWarning, logInfo) {
6
+ this.targetRoot = targetRoot;
7
+ this.logger = logger;
8
+ this.logSuccess = logSuccess;
9
+ this.logWarning = logWarning;
10
+ this.logInfo = logInfo;
11
+ }
12
+
13
+ configureProjectScoped(mcpConfig, serverId) {
14
+ this.writeWindsurfConfig(mcpConfig, serverId);
15
+ this.writeCursorConfig(mcpConfig, serverId);
16
+ }
17
+
18
+ writeWindsurfConfig(mcpConfig, serverId) {
19
+ const windsurfProjectDir = path.join(this.targetRoot, '.windsurf');
20
+ const windsurfProjectPath = path.join(windsurfProjectDir, 'mcp.json');
21
+ this.writeProjectConfig(windsurfProjectDir, windsurfProjectPath, mcpConfig, serverId, 'Windsurf', 'MCP_PROJECT_CONFIGURED', 'MCP_PROJECT_CONFIGURE_FAILED');
22
+ }
23
+
24
+ writeCursorConfig(mcpConfig, serverId) {
25
+ const cursorProjectDir = path.join(this.targetRoot, '.cursor');
26
+ const cursorProjectPath = path.join(cursorProjectDir, 'mcp.json');
27
+ this.writeProjectConfig(cursorProjectDir, cursorProjectPath, mcpConfig, serverId, 'Cursor', 'MCP_CURSOR_CONFIGURED', 'MCP_CURSOR_CONFIGURE_FAILED');
28
+ }
29
+
30
+ writeProjectConfig(dir, filePath, mcpConfig, serverId, label, successEvent, errorEvent) {
31
+ try {
32
+ if (!fs.existsSync(dir)) {
33
+ fs.mkdirSync(dir, { recursive: true });
34
+ }
35
+
36
+ let finalConfig = mcpConfig;
37
+ if (fs.existsSync(filePath)) {
38
+ const existing = JSON.parse(fs.readFileSync(filePath, 'utf8'));
39
+ if (!existing.mcpServers) existing.mcpServers = {};
40
+ Object.keys(existing.mcpServers).forEach(id => {
41
+ if (id.startsWith('ast-intelligence-automation-') && id !== serverId) {
42
+ delete existing.mcpServers[id];
43
+ }
44
+ });
45
+ existing.mcpServers[serverId] = mcpConfig.mcpServers[serverId];
46
+ finalConfig = existing;
47
+ }
48
+
49
+ fs.writeFileSync(filePath, JSON.stringify(finalConfig, null, 2));
50
+ this.logSuccess?.(`Configured project-scoped ${label} MCP at ${filePath}`);
51
+ this.logger?.info?.(successEvent, { path: filePath, serverId });
52
+ } catch (error) {
53
+ this.logWarning?.(`Failed to configure ${label} MCP: ${error.message}`);
54
+ this.logger?.warn?.(errorEvent, { error: error.message });
55
+ }
56
+ }
57
+ }
58
+
59
+ module.exports = McpProjectConfigWriter;
@@ -0,0 +1,103 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { execSync } = require('child_process');
4
+ const crypto = require('crypto');
5
+ const env = require('../../../../config/env.js');
6
+
7
+ function slugifyId(input) {
8
+ return String(input || '')
9
+ .trim()
10
+ .toLowerCase()
11
+ .replace(/[^a-z0-9]+/g, '-')
12
+ .replace(/^-+|-+$/g, '')
13
+ .slice(0, 48);
14
+ }
15
+
16
+ class McpServerConfigBuilder {
17
+ constructor(targetRoot, hookSystemRoot, logger = null) {
18
+ this.targetRoot = targetRoot;
19
+ this.hookSystemRoot = hookSystemRoot;
20
+ this.logger = logger;
21
+ }
22
+
23
+ build() {
24
+ const serverId = this.computeServerIdForRepo(this.targetRoot);
25
+ const entrypoint = this.resolveAutomationEntrypoint();
26
+ const nodePath = this.resolveNodeBinary();
27
+
28
+ const mcpConfig = {
29
+ mcpServers: {
30
+ [serverId]: {
31
+ command: nodePath,
32
+ args: [entrypoint],
33
+ env: {
34
+ REPO_ROOT: this.targetRoot,
35
+ AUTO_COMMIT_ENABLED: 'false',
36
+ AUTO_PUSH_ENABLED: 'false',
37
+ AUTO_PR_ENABLED: 'false'
38
+ }
39
+ }
40
+ }
41
+ };
42
+
43
+ return { serverId, mcpConfig };
44
+ }
45
+
46
+ resolveAutomationEntrypoint() {
47
+ const candidates = [
48
+ this.hookSystemRoot
49
+ ? path.join(this.hookSystemRoot, 'infrastructure', 'mcp', 'ast-intelligence-automation.js')
50
+ : null,
51
+ path.join(this.targetRoot, 'scripts', 'hooks-system', 'infrastructure', 'mcp', 'ast-intelligence-automation.js')
52
+ ].filter(Boolean);
53
+
54
+ for (const candidate of candidates) {
55
+ try {
56
+ if (fs.existsSync(candidate)) return candidate;
57
+ } catch (error) {
58
+ this.logger?.warn?.('MCP_ENTRYPOINT_CHECK_FAILED', { candidate, error: error.message });
59
+ }
60
+ }
61
+
62
+ return path.join(this.targetRoot, 'scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js');
63
+ }
64
+
65
+ resolveNodeBinary() {
66
+ let nodePath = process.execPath;
67
+ if (nodePath && fs.existsSync(nodePath)) return nodePath;
68
+
69
+ try {
70
+ return execSync('which node', { encoding: 'utf-8' }).trim();
71
+ } catch (error) {
72
+ if (process.env.DEBUG) {
73
+ process.stderr.write(`[MCP] which node failed: ${error && error.message ? error.message : String(error)}\n`);
74
+ }
75
+ return 'node';
76
+ }
77
+ }
78
+
79
+ computeServerIdForRepo(repoRoot) {
80
+ const legacyServerId = 'ast-intelligence-automation';
81
+ const forced = (env.get('MCP_SERVER_ID', '') || '').trim();
82
+ if (forced.length > 0) return forced;
83
+
84
+ const repoName = path.basename(repoRoot || process.cwd());
85
+ const slug = slugifyId(repoName) || 'repo';
86
+ const fp = this.computeRepoFingerprint(repoRoot);
87
+ return `${legacyServerId}-${slug}-${fp}`;
88
+ }
89
+
90
+ computeRepoFingerprint(repoRoot) {
91
+ try {
92
+ const real = fs.realpathSync(repoRoot);
93
+ return crypto.createHash('sha1').update(real).digest('hex').slice(0, 8);
94
+ } catch (error) {
95
+ if (process.env.DEBUG) {
96
+ process.stderr.write(`[MCP] computeRepoFingerprint fallback: ${error && error.message ? error.message : String(error)}\n`);
97
+ }
98
+ return crypto.createHash('sha1').update(String(repoRoot || '')).digest('hex').slice(0, 8);
99
+ }
100
+ }
101
+ }
102
+
103
+ module.exports = McpServerConfigBuilder;