pumuki-ast-hooks 5.3.14 → 5.3.16

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 = computeServerIdForRepo(this.targetRoot);
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,30 @@ 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
- }
72
-
73
- existing.mcpServers['ast-intelligence-automation'] = mcpConfig.mcpServers['ast-intelligence-automation'];
74
-
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 });
82
- }
83
- }
84
- });
96
+ const existing = JSON.parse(fs.readFileSync(globalConfigPath, 'utf8'));
97
+ if (!existing.mcpServers) existing.mcpServers = {};
85
98
 
86
- if (configuredCount === 0 && ideConfigs.length === 0) {
87
- this.configureFallback(mcpConfig);
88
- }
89
- }
99
+ existing.mcpServers[serverId] = mcpConfig.mcpServers[serverId];
90
100
 
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 });
101
+ fs.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2));
102
+ this.logSuccess(`Updated global Windsurf MCP at ${globalConfigPath}`);
103
+ if (this.logger) this.logger.info('MCP_GLOBAL_UPDATED', { path: globalConfigPath });
115
104
  }
105
+ } catch (mergeError) {
106
+ this.logWarning(`${globalConfigPath} exists but couldn't be merged, skipping`);
107
+ if (this.logger) this.logger.warn('MCP_GLOBAL_MERGE_FAILED', { error: mergeError.message });
116
108
  }
117
109
  }
118
110
 
@@ -10,57 +10,7 @@
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,
@@ -28,7 +28,30 @@ const { execSync } = require('child_process');
28
28
  const MCP_VERSION = '2024-11-05';
29
29
 
30
30
  // Configuration - LAZY LOADING to avoid blocking MCP initialization
31
- const REPO_ROOT = process.env.REPO_ROOT || process.cwd();
31
+ function safeGitRoot(startDir) {
32
+ try {
33
+ const out = execSync('git rev-parse --show-toplevel', {
34
+ cwd: startDir,
35
+ encoding: 'utf8',
36
+ stdio: ['ignore', 'pipe', 'ignore']
37
+ });
38
+ const root = String(out || '').trim();
39
+ return root || null;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ function resolveRepoRoot() {
46
+ const envRoot = (process.env.REPO_ROOT || '').trim() || null;
47
+ const cwdRoot = safeGitRoot(process.cwd());
48
+ // Prefer explicit REPO_ROOT to avoid cross-repo bleed when MCP server is launched from another workspace
49
+ if (envRoot) return envRoot;
50
+ if (cwdRoot) return cwdRoot;
51
+ return process.cwd();
52
+ }
53
+
54
+ const REPO_ROOT = resolveRepoRoot();
32
55
 
33
56
  // Lazy-loaded CompositionRoot - only initialized when first needed
34
57
  let _compositionRoot = null;
@@ -1026,11 +1026,11 @@ run_ast_intelligence() {
1026
1026
  done
1027
1027
 
1028
1028
  # Execute AST with proper error handling and NODE_PATH
1029
- # IMPORTANT: run from ROOT_DIR so git rev-parse resolves project root correctly
1029
+ # Change to HOOKS_SYSTEM_DIR so Node.js resolves modules correctly
1030
1030
  if [[ -n "$node_path_value" ]]; then
1031
- ast_output=$(cd "$ROOT_DIR" && export NODE_PATH="$node_path_value" && export AUDIT_TMP="$TMP_DIR" && export AUDIT_LIBRARY="${AUDIT_LIBRARY:-false}" && "$node_bin" "${AST_DIR}/ast-intelligence.js" 2>&1) || ast_exit_code=$?
1031
+ ast_output=$(cd "$HOOKS_SYSTEM_DIR" && export NODE_PATH="$node_path_value" && export AUDIT_TMP="$TMP_DIR" && export AUDIT_LIBRARY="${AUDIT_LIBRARY:-false}" && "$node_bin" "${AST_DIR}/ast-intelligence.js" 2>&1) || ast_exit_code=$?
1032
1032
  else
1033
- ast_output=$(cd "$ROOT_DIR" && export AUDIT_TMP="$TMP_DIR" && export AUDIT_LIBRARY="${AUDIT_LIBRARY:-false}" && "$node_bin" "${AST_DIR}/ast-intelligence.js" 2>&1) || ast_exit_code=$?
1033
+ ast_output=$(cd "$HOOKS_SYSTEM_DIR" && export AUDIT_TMP="$TMP_DIR" && export AUDIT_LIBRARY="${AUDIT_LIBRARY:-false}" && "$node_bin" "${AST_DIR}/ast-intelligence.js" 2>&1) || ast_exit_code=$?
1034
1034
  fi
1035
1035
 
1036
1036
  # Check if AST script failed