pumuki-ast-hooks 5.3.14 → 5.3.15

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.
@@ -1,6 +1,8 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { execSync } = require('child_process');
4
+ const crypto = require('crypto');
5
+ const os = require('os');
4
6
 
5
7
  const COLORS = {
6
8
  reset: '\x1b[0m',
@@ -9,6 +11,35 @@ const COLORS = {
9
11
  cyan: '\x1b[36m'
10
12
  };
11
13
 
14
+ function slugifyId(input) {
15
+ return String(input || '')
16
+ .trim()
17
+ .toLowerCase()
18
+ .replace(/[^a-z0-9]+/g, '-')
19
+ .replace(/^-+|-+$/g, '')
20
+ .slice(0, 48);
21
+ }
22
+
23
+ function computeRepoFingerprint(repoRoot) {
24
+ try {
25
+ const real = fs.realpathSync(repoRoot);
26
+ return crypto.createHash('sha1').update(real).digest('hex').slice(0, 8);
27
+ } catch {
28
+ return crypto.createHash('sha1').update(String(repoRoot || '')).digest('hex').slice(0, 8);
29
+ }
30
+ }
31
+
32
+ function computeServerIdForRepo(repoRoot) {
33
+ const legacyServerId = 'ast-intelligence-automation';
34
+ const forced = (process.env.MCP_SERVER_ID || '').trim();
35
+ if (forced.length > 0) return forced;
36
+
37
+ const repoName = path.basename(repoRoot || process.cwd());
38
+ const slug = slugifyId(repoName) || 'repo';
39
+ const fp = computeRepoFingerprint(repoRoot);
40
+ return `${legacyServerId}-${slug}-${fp}`;
41
+ }
42
+
12
43
  class McpConfigurator {
13
44
  constructor(targetRoot, hookSystemRoot, logger = null) {
14
45
  this.targetRoot = targetRoot;
@@ -16,26 +47,32 @@ class McpConfigurator {
16
47
  this.logger = logger;
17
48
  }
18
49
 
50
+ getGlobalWindsurfConfigPath() {
51
+ return path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json');
52
+ }
53
+
19
54
  configure() {
20
55
  if (this.logger) this.logger.info('MCP_CONFIGURATION_STARTED');
56
+
21
57
  let nodePath = process.execPath;
22
58
  if (!nodePath || !fs.existsSync(nodePath)) {
23
59
  try {
24
60
  nodePath = execSync('which node', { encoding: 'utf-8' }).trim();
25
- } catch (err) {
61
+ } catch {
26
62
  nodePath = 'node';
27
63
  }
28
64
  }
29
65
 
66
+ const serverId = 'ast-intelligence-automation';
30
67
  const mcpConfig = {
31
68
  mcpServers: {
32
- 'ast-intelligence-automation': {
69
+ [serverId]: {
33
70
  command: nodePath,
34
71
  args: [
35
- '${workspaceFolder}/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js'
72
+ path.join(this.targetRoot, 'scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js')
36
73
  ],
37
74
  env: {
38
- REPO_ROOT: '${workspaceFolder}',
75
+ REPO_ROOT: this.targetRoot,
39
76
  AUTO_COMMIT_ENABLED: 'false',
40
77
  AUTO_PUSH_ENABLED: 'false',
41
78
  AUTO_PR_ENABLED: 'false'
@@ -44,75 +81,34 @@ class McpConfigurator {
44
81
  }
45
82
  };
46
83
 
47
- const ideConfigs = this.detectIDEs();
48
- let configuredCount = 0;
49
-
50
- ideConfigs.forEach(ide => {
51
- if (!fs.existsSync(ide.configDir)) {
52
- fs.mkdirSync(ide.configDir, { recursive: true });
53
- }
54
-
55
- const mcpConfigPath = path.join(ide.configDir, 'mcp.json');
56
-
57
- if (!fs.existsSync(mcpConfigPath)) {
58
- const configToWrite = ide.name === 'Claude Desktop'
59
- ? this.adaptConfigForClaudeDesktop(mcpConfig)
60
- : mcpConfig;
84
+ const globalConfigPath = this.getGlobalWindsurfConfigPath();
85
+ const globalConfigDir = path.dirname(globalConfigPath);
86
+ if (!fs.existsSync(globalConfigDir)) {
87
+ fs.mkdirSync(globalConfigDir, { recursive: true });
88
+ }
61
89
 
62
- fs.writeFileSync(mcpConfigPath, JSON.stringify(configToWrite, null, 2));
63
- this.logSuccess(`Configured ${ide.configPath}`);
64
- if (this.logger) this.logger.info('MCP_CONFIGURED', { ide: ide.name, path: ide.configPath });
65
- configuredCount++;
90
+ try {
91
+ if (!fs.existsSync(globalConfigPath)) {
92
+ fs.writeFileSync(globalConfigPath, JSON.stringify(mcpConfig, null, 2));
93
+ this.logSuccess(`Configured global Windsurf MCP at ${globalConfigPath}`);
94
+ if (this.logger) this.logger.info('MCP_GLOBAL_CONFIGURED', { path: globalConfigPath });
66
95
  } else {
67
- try {
68
- const existing = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8'));
69
- if (!existing.mcpServers) {
70
- existing.mcpServers = {};
71
- }
96
+ const existing = JSON.parse(fs.readFileSync(globalConfigPath, 'utf8'));
97
+ if (!existing.mcpServers) existing.mcpServers = {};
72
98
 
73
- existing.mcpServers['ast-intelligence-automation'] = mcpConfig.mcpServers['ast-intelligence-automation'];
99
+ existing.mcpServers[serverId] = mcpConfig.mcpServers[serverId];
74
100
 
75
- fs.writeFileSync(mcpConfigPath, JSON.stringify(existing, null, 2));
76
- this.logSuccess(`Updated ${ide.configPath} (merged configuration)`);
77
- if (this.logger) this.logger.info('MCP_CONFIG_UPDATED', { ide: ide.name, path: ide.configPath });
78
- configuredCount++;
79
- } catch (mergeError) {
80
- this.logWarning(`${ide.configPath} already exists and couldn't be merged, skipping`);
81
- if (this.logger) this.logger.warn('MCP_CONFIG_MERGE_FAILED', { ide: ide.name, error: mergeError.message });
101
+ if (existing.mcpServers['ast-intelligence-automation']) {
102
+ existing.mcpServers['ast-intelligence-automation'] = mcpConfig.mcpServers[serverId];
82
103
  }
83
- }
84
- });
85
104
 
86
- if (configuredCount === 0 && ideConfigs.length === 0) {
87
- this.configureFallback(mcpConfig);
88
- }
89
- }
90
-
91
- configureFallback(mcpConfig) {
92
- const cursorDir = path.join(this.targetRoot, '.cursor');
93
- if (!fs.existsSync(cursorDir)) {
94
- fs.mkdirSync(cursorDir, { recursive: true });
95
- }
96
- const fallbackPath = path.join(cursorDir, 'mcp.json');
97
- if (!fs.existsSync(fallbackPath)) {
98
- fs.writeFileSync(fallbackPath, JSON.stringify(mcpConfig, null, 2));
99
- this.logSuccess('Configured .cursor/mcp.json (generic fallback)');
100
- this.logInfo('Note: MCP servers work with any MCP-compatible IDE');
101
- if (this.logger) this.logger.info('MCP_FALLBACK_CONFIGURED', { path: fallbackPath });
102
- } else {
103
- try {
104
- const existing = JSON.parse(fs.readFileSync(fallbackPath, 'utf8'));
105
- if (!existing.mcpServers) {
106
- existing.mcpServers = {};
107
- }
108
- existing.mcpServers['ast-intelligence-automation'] = mcpConfig.mcpServers['ast-intelligence-automation'];
109
- fs.writeFileSync(fallbackPath, JSON.stringify(existing, null, 2));
110
- this.logSuccess('Updated .cursor/mcp.json (merged configuration)');
111
- if (this.logger) this.logger.info('MCP_FALLBACK_UPDATED', { path: fallbackPath });
112
- } catch (mergeError) {
113
- this.logWarning('.cursor/mcp.json exists and couldn\'t be merged, skipping');
114
- if (this.logger) this.logger.warn('MCP_FALLBACK_MERGE_FAILED', { error: mergeError.message });
105
+ fs.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2));
106
+ this.logSuccess(`Updated global Windsurf MCP at ${globalConfigPath}`);
107
+ if (this.logger) this.logger.info('MCP_GLOBAL_UPDATED', { path: globalConfigPath });
115
108
  }
109
+ } catch (mergeError) {
110
+ this.logWarning(`${globalConfigPath} exists but couldn't be merged, skipping`);
111
+ if (this.logger) this.logger.warn('MCP_GLOBAL_MERGE_FAILED', { error: mergeError.message });
116
112
  }
117
113
  }
118
114
 
@@ -2,10 +2,40 @@
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
4
  const os = require('os');
5
+ const crypto = require('crypto');
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
+ function computeRepoFingerprint(repoRoot) {
17
+ try {
18
+ const real = fs.realpathSync(repoRoot);
19
+ return crypto.createHash('sha1').update(real).digest('hex').slice(0, 8);
20
+ } catch {
21
+ return crypto.createHash('sha1').update(String(repoRoot || '')).digest('hex').slice(0, 8);
22
+ }
23
+ }
24
+
25
+ function computeServerIdForRepo(repoRoot) {
26
+ const legacyServerId = 'ast-intelligence-automation';
27
+ const forced = (process.env.MCP_SERVER_ID || '').trim();
28
+ if (forced.length > 0) return forced;
29
+
30
+ const repoName = path.basename(repoRoot || process.cwd());
31
+ const slug = slugifyId(repoName) || 'repo';
32
+ const fp = computeRepoFingerprint(repoRoot);
33
+ return `${legacyServerId}-${slug}-${fp}`;
34
+ }
5
35
 
6
36
  const MCP_CONFIG = {
7
37
  mcpServers: {
8
- 'ast-intelligence-automation': {
38
+ '__SERVER_ID__': {
9
39
  command: 'node',
10
40
  type: 'stdio',
11
41
  args: ['./scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js'],
@@ -30,14 +60,16 @@ function detectIDE() {
30
60
 
31
61
  function configureMCP() {
32
62
  const cwd = process.cwd();
33
- console.log('🔌 Pumuki Hooks - MCP Configuration\n');
63
+ console.log(' Pumuki Hooks - MCP Configuration\n');
64
+
65
+ const serverId = computeServerIdForRepo(cwd);
34
66
 
35
67
  const ide = detectIDE();
36
68
  if (!ide) {
37
- console.log('⚠️ No supported IDE detected (Cursor, Windsurf, VSCode)');
69
+ console.log(' No supported IDE detected (Cursor, Windsurf, VSCode)');
38
70
  console.log(' Creating local .cursor/mcp.json instead.\n');
39
71
  } else {
40
- console.log(`✅ Detected IDE: ${ide.name}`);
72
+ console.log(` Detected IDE: ${ide.name}`);
41
73
  }
42
74
 
43
75
  const mcpDir = path.join(cwd, '.cursor');
@@ -48,23 +80,31 @@ function configureMCP() {
48
80
  }
49
81
 
50
82
  const config = JSON.parse(JSON.stringify(MCP_CONFIG));
51
- config.mcpServers['ast-intelligence-automation'].args[0] =
83
+ config.mcpServers[serverId] = config.mcpServers['__SERVER_ID__'];
84
+ delete config.mcpServers['__SERVER_ID__'];
85
+
86
+ config.mcpServers[serverId].args[0] =
52
87
  path.join(cwd, 'scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js');
53
- config.mcpServers['ast-intelligence-automation'].env.REPO_ROOT = cwd;
88
+ config.mcpServers[serverId].env.REPO_ROOT = cwd;
54
89
 
55
90
  if (fs.existsSync(mcpFile)) {
56
91
  const existing = JSON.parse(fs.readFileSync(mcpFile, 'utf8'));
57
- existing.mcpServers = { ...existing.mcpServers, ...config.mcpServers };
92
+ existing.mcpServers = { ...(existing.mcpServers || {}), ...config.mcpServers };
93
+
94
+ // Remove legacy id to avoid collisions across multiple open workspaces
95
+ if (existing.mcpServers['ast-intelligence-automation']) {
96
+ delete existing.mcpServers['ast-intelligence-automation'];
97
+ }
58
98
  fs.writeFileSync(mcpFile, JSON.stringify(existing, null, 2));
59
- console.log(' Updated existing .cursor/mcp.json');
99
+ console.log(' Updated existing .cursor/mcp.json');
60
100
  } else {
61
101
  fs.writeFileSync(mcpFile, JSON.stringify(config, null, 2));
62
- console.log(' Created .cursor/mcp.json');
102
+ console.log(' Created .cursor/mcp.json');
63
103
  }
64
104
 
65
- console.log('\n📋 MCP Server configured:');
66
- console.log(' - ast-intelligence-automation');
67
- console.log('\n🔄 Restart your IDE to activate the MCP server.');
105
+ console.log('\n MCP Server configured:');
106
+ console.log(` - ${serverId}`);
107
+ console.log('\n Restart your IDE to activate the MCP server.');
68
108
  }
69
109
 
70
110
  if (require.main === module) {
@@ -5,62 +5,12 @@
5
5
  "platforms": [
6
6
  "backend"
7
7
  ],
8
- "created": "2025-12-25T22:02:53.249Z"
8
+ "created": "2025-12-30T05:45:54.907Z"
9
9
  },
10
10
  "architecture": {
11
11
  "pattern": "FEATURE_FIRST_CLEAN_DDD",
12
12
  "strictMode": true,
13
- "enforcement": "strict",
14
- "ios": {
15
- "architecturePattern": "FEATURE_FIRST_CLEAN_DDD",
16
- "allowedPatterns": [
17
- "MVP",
18
- "MVVM",
19
- "VIPER",
20
- "HEXAGONAL",
21
- "TCA"
22
- ],
23
- "prohibitedPatterns": [
24
- "MVVM-C",
25
- "MVC"
26
- ],
27
- "navigationPattern": "EVENT_DRIVEN",
28
- "documentation": "docs/ARCHITECTURE.md"
29
- },
30
- "android": {
31
- "architecturePattern": "FEATURE_FIRST_CLEAN_DDD",
32
- "allowedPatterns": [
33
- "MVVM",
34
- "MVI",
35
- "MVP"
36
- ],
37
- "prohibitedPatterns": [
38
- "MVC"
39
- ],
40
- "composeOnly": true,
41
- "hiltRequired": true
42
- },
43
- "frontend": {
44
- "architecturePattern": "FEATURE_FIRST_CLEAN_DDD",
45
- "allowedPatterns": [
46
- "COMPONENT_BASED",
47
- "ATOMIC_DESIGN"
48
- ],
49
- "prohibitedPatterns": [
50
- "MVC"
51
- ]
52
- },
53
- "backend": {
54
- "architecturePattern": "FEATURE_FIRST_CLEAN_DDD",
55
- "allowedPatterns": [
56
- "HEXAGONAL",
57
- "CLEAN",
58
- "ONION"
59
- ],
60
- "prohibitedPatterns": [
61
- "MVC"
62
- ]
63
- }
13
+ "enforcement": "strict"
64
14
  },
65
15
  "rules": {
66
16
  "enabled": true,