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 +0 -9
- package/docs/MCP_SERVERS.md +16 -2
- package/docs/RELEASE_NOTES.md +34 -0
- package/package.json +1 -1
- package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +40 -0
- package/scripts/hooks-system/application/services/installation/McpConfigurator.js +9 -205
- package/scripts/hooks-system/application/services/installation/mcp/McpGlobalConfigCleaner.js +49 -0
- package/scripts/hooks-system/application/services/installation/mcp/McpProjectConfigWriter.js +59 -0
- package/scripts/hooks-system/application/services/installation/mcp/McpServerConfigBuilder.js +103 -0
- package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +13 -8
- package/scripts/hooks-system/infrastructure/ast/common/ast-common.js +15 -8
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSCICDChecks.js +385 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSCICDRules.js +38 -408
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSSPMChecks.js +408 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSSPMRules.js +36 -442
- package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenExtractor.js +146 -0
- package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenParser.js +22 -190
- package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenRunner.js +62 -0
- package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +27 -9
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
|
package/docs/MCP_SERVERS.md
CHANGED
|
@@ -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
|
-
"
|
|
429
|
+
"timestamp": "2026-01-07T22:19:30.363Z",
|
|
430
|
+
"branch": "feature/my-task",
|
|
430
431
|
"violations": [],
|
|
431
|
-
"
|
|
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
|
package/docs/RELEASE_NOTES.md
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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;
|