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.
|
|
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
|
-
//
|
|
60
|
-
//
|
|
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
|
-
|
|
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
|