pumuki-ast-hooks 5.4.8 → 5.5.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki-ast-hooks",
3
- "version": "5.4.8",
3
+ "version": "5.5.1",
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": {
@@ -18,8 +18,6 @@
18
18
  const fs = require('fs');
19
19
  const path = require('path');
20
20
  const { execSync } = require('child_process');
21
- const crypto = require('crypto');
22
- const os = require('os');
23
21
  const env = require('../../config/env');
24
22
 
25
23
  // Removed global requires for performance (Lazy Loading)
@@ -56,165 +54,8 @@ function resolveRepoRoot() {
56
54
 
57
55
  const REPO_ROOT = resolveRepoRoot();
58
56
 
59
- // Create unique lock per project using hash of REPO_ROOT
60
- // This allows multiple projects to run MCP simultaneously
61
- const repoHash = crypto.createHash('md5').update(REPO_ROOT).digest('hex').substring(0, 8);
62
- const MCP_LOCK_DIR = path.join(os.tmpdir(), `mcp-ast-intelligence-${repoHash}.lock`);
63
- const MCP_LOCK_PID = path.join(MCP_LOCK_DIR, 'pid');
64
-
65
- let MCP_IS_PRIMARY = true;
66
-
67
- function logMcpError(context, error) {
68
- const msg = error instanceof Error ? error.message : String(error);
69
- process.stderr.write(`[MCP][ERROR] ${context}: ${msg}\n`);
70
- }
71
-
72
- function logMcpDebug(message) {
73
- if (env.getBool('DEBUG', false)) {
74
- process.stderr.write(`[MCP][DEBUG] ${message}\n`);
75
- }
76
- }
77
-
78
- function isPidRunning(pid) {
79
- if (!pid || !Number.isFinite(pid) || pid <= 0) return false;
80
- try {
81
- process.kill(pid, 0);
82
- return true;
83
- } catch (error) {
84
- logMcpDebug(`isPidRunning(${pid}) = false: ${error.code || error.message}`);
85
- return false;
86
- }
87
- }
88
-
89
- function safeReadPid(filePath) {
90
- try {
91
- if (!fs.existsSync(filePath)) return null;
92
- const raw = String(fs.readFileSync(filePath, 'utf8') || '').trim();
93
- const pid = Number(raw);
94
- if (!Number.isFinite(pid) || pid <= 0) return null;
95
- return pid;
96
- } catch (error) {
97
- logMcpError('safeReadPid', error);
98
- return null;
99
- }
100
- }
101
-
102
- function removeLockDir() {
103
- try {
104
- if (fs.existsSync(MCP_LOCK_PID)) {
105
- fs.unlinkSync(MCP_LOCK_PID);
106
- logMcpDebug('Removed lock PID file');
107
- }
108
- } catch (error) {
109
- logMcpError('removeLockDir (pid file)', error);
110
- }
111
- try {
112
- if (fs.existsSync(MCP_LOCK_DIR)) {
113
- fs.rmdirSync(MCP_LOCK_DIR);
114
- logMcpDebug('Removed lock directory');
115
- }
116
- } catch (error) {
117
- logMcpError('removeLockDir (directory)', error);
118
- }
119
- }
120
-
121
- function cleanupAndExit(code = 0) {
122
- const myPid = process.pid;
123
- const lockPid = safeReadPid(MCP_LOCK_PID);
124
-
125
- if (lockPid === myPid) {
126
- logMcpDebug(`Cleaning up lock (my pid=${myPid})`);
127
- removeLockDir();
128
- } else {
129
- logMcpDebug(`Not cleaning lock (lockPid=${lockPid}, myPid=${myPid})`);
130
- }
131
-
132
- process.exit(code);
133
- }
134
-
135
- function installStdioExitHandlers() {
136
- const handleStdioTermination = (source) => (error) => {
137
- if (error) {
138
- const code = String(error.code || '').toUpperCase();
139
- if (code === 'EPIPE' || code === 'ERR_STREAM_DESTROYED' || code === 'ECONNRESET') {
140
- logMcpDebug(`STDIO ${source} closed (${code}), exiting cleanly`);
141
- cleanupAndExit(0);
142
- return;
143
- }
144
- logMcpError(`STDIO ${source} error`, error);
145
- } else {
146
- logMcpDebug(`STDIO ${source} ended, exiting cleanly`);
147
- }
148
- cleanupAndExit(0);
149
- };
150
-
151
- try {
152
- process.stdin.on('end', handleStdioTermination('stdin'));
153
- process.stdin.on('close', handleStdioTermination('stdin'));
154
- process.stdin.on('error', handleStdioTermination('stdin'));
155
- } catch (error) {
156
- logMcpError('installStdioExitHandlers (stdin)', error);
157
- }
158
-
159
- try {
160
- process.stdout.on('error', handleStdioTermination('stdout'));
161
- process.stderr.on('error', handleStdioTermination('stderr'));
162
- } catch (error) {
163
- logMcpError('installStdioExitHandlers (stdout/stderr)', error);
164
- }
165
- }
166
-
167
- function acquireSingletonLock() {
168
- // No need to create .audit_tmp since lock is in /tmp/
169
-
170
- try {
171
- fs.mkdirSync(MCP_LOCK_DIR);
172
- } catch (error) {
173
- const existingPid = safeReadPid(MCP_LOCK_PID);
174
-
175
- if (existingPid && isPidRunning(existingPid)) {
176
- process.stderr.write(`[MCP] Another instance is already running (pid ${existingPid}). Exiting.\n`);
177
- process.exit(0);
178
- }
179
-
180
- logMcpDebug(`Lock exists but PID ${existingPid || 'unknown'} is not running, cleaning up`);
181
- removeLockDir();
182
-
183
- try {
184
- fs.mkdirSync(MCP_LOCK_DIR);
185
- } catch (retryError) {
186
- logMcpError('acquireSingletonLock (retry mkdir)', retryError);
187
- process.stderr.write(`[MCP] Failed to acquire lock after cleanup. Exiting.\n`);
188
- process.exit(1);
189
- }
190
- }
191
-
192
- try {
193
- fs.writeFileSync(MCP_LOCK_PID, String(process.pid), { encoding: 'utf8' });
194
- logMcpDebug(`Lock acquired, PID ${process.pid} written`);
195
- } catch (error) {
196
- logMcpError('acquireSingletonLock (write pid)', error);
197
- }
198
-
199
- process.on('exit', () => {
200
- const lockPid = safeReadPid(MCP_LOCK_PID);
201
- if (lockPid === process.pid) {
202
- removeLockDir();
203
- }
204
- });
205
-
206
- process.on('SIGINT', () => cleanupAndExit(0));
207
- process.on('SIGTERM', () => cleanupAndExit(0));
208
- process.on('SIGHUP', () => cleanupAndExit(0));
209
-
210
- return { acquired: true, pid: process.pid };
211
- }
212
-
213
- const singleton = acquireSingletonLock();
214
- if (!singleton.acquired) {
215
- process.exit(0);
216
- }
217
- installStdioExitHandlers();
57
+ // NO singleton lock - Windsurf manages process lifecycle
58
+ // Each project gets its own independent MCP process
218
59
 
219
60
  // Lazy-loaded CompositionRoot - only initialized when first needed
220
61
  let _compositionRoot = null;
@@ -1126,10 +967,14 @@ async function handleMcpMessage(message) {
1126
967
  // Start protocol handler
1127
968
  protocolHandler.start(handleMcpMessage);
1128
969
 
970
+ // Log MCP ready
971
+ process.stderr.write(`[MCP] Server ready for ${REPO_ROOT}\n`);
972
+
1129
973
  /**
1130
974
  * Polling loop for background notifications and automations
975
+ * IMPORTANT: Delayed start to avoid blocking MCP initialization handshake
1131
976
  */
1132
- if (MCP_IS_PRIMARY) {
977
+ setTimeout(() => {
1133
978
  setInterval(async () => {
1134
979
  try {
1135
980
  const now = Date.now();
@@ -1186,10 +1031,8 @@ if (MCP_IS_PRIMARY) {
1186
1031
  if (process.env.DEBUG) console.error('[MCP] Polling loop error:', error);
1187
1032
  }
1188
1033
  }, 30000);
1189
- }
1190
1034
 
1191
- // AUTO-COMMIT: Only for project code changes (no node_modules, no library)
1192
- if (MCP_IS_PRIMARY) {
1035
+ // AUTO-COMMIT: Only for project code changes (no node_modules, no library)
1193
1036
  setInterval(async () => {
1194
1037
  if (!AUTO_COMMIT_ENABLED) {
1195
1038
  return;
@@ -1292,4 +1135,4 @@ if (MCP_IS_PRIMARY) {
1292
1135
  if (process.env.DEBUG) console.error('[MCP] Auto-commit error:', error);
1293
1136
  }
1294
1137
  }, AUTO_COMMIT_INTERVAL);
1295
- }
1138
+ }, 2000); // Delay 2 seconds to allow MCP handshake to complete first