pumuki-ast-hooks 5.4.8 → 5.5.0

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.0",
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;
@@ -1129,167 +970,163 @@ protocolHandler.start(handleMcpMessage);
1129
970
  /**
1130
971
  * Polling loop for background notifications and automations
1131
972
  */
1132
- if (MCP_IS_PRIMARY) {
1133
- setInterval(async () => {
1134
- try {
1135
- const now = Date.now();
1136
- const gitFlowService = getCompositionRoot().getGitFlowService();
1137
- const gitQuery = getCompositionRoot().getGitQueryAdapter();
1138
- const evidenceMonitor = getCompositionRoot().getEvidenceMonitor();
1139
- const orchestrator = getCompositionRoot().getOrchestrator();
1140
-
1141
- const currentBranch = gitFlowService.getCurrentBranch();
1142
- const baseBranch = process.env.AST_BASE_BRANCH || 'develop';
1143
- const isProtectedBranch = ['main', 'master', baseBranch].includes(currentBranch);
1144
-
1145
- const uncommittedChanges = gitQuery.getUncommittedChanges();
1146
- const hasUncommittedChanges = uncommittedChanges && uncommittedChanges.length > 0;
1147
-
1148
- // 1. Protected Branch Guard
1149
- if (isProtectedBranch && hasUncommittedChanges) {
1150
- if (now - lastGitFlowNotification > NOTIFICATION_COOLDOWN) {
1151
- const state = gitQuery.getBranchState(currentBranch);
1152
- sendNotification(
1153
- '⚠️ Git Flow Violation',
1154
- `branch=${currentBranch} changes detected on protected branch. Create a feature branch.`,
1155
- 'Basso'
1156
- );
1157
- lastGitFlowNotification = now;
1158
- }
973
+ setInterval(async () => {
974
+ try {
975
+ const now = Date.now();
976
+ const gitFlowService = getCompositionRoot().getGitFlowService();
977
+ const gitQuery = getCompositionRoot().getGitQueryAdapter();
978
+ const evidenceMonitor = getCompositionRoot().getEvidenceMonitor();
979
+ const orchestrator = getCompositionRoot().getOrchestrator();
980
+
981
+ const currentBranch = gitFlowService.getCurrentBranch();
982
+ const baseBranch = process.env.AST_BASE_BRANCH || 'develop';
983
+ const isProtectedBranch = ['main', 'master', baseBranch].includes(currentBranch);
984
+
985
+ const uncommittedChanges = gitQuery.getUncommittedChanges();
986
+ const hasUncommittedChanges = uncommittedChanges && uncommittedChanges.length > 0;
987
+
988
+ // 1. Protected Branch Guard
989
+ if (isProtectedBranch && hasUncommittedChanges) {
990
+ if (now - lastGitFlowNotification > NOTIFICATION_COOLDOWN) {
991
+ const state = gitQuery.getBranchState(currentBranch);
992
+ sendNotification(
993
+ '⚠️ Git Flow Violation',
994
+ `branch=${currentBranch} changes detected on protected branch. Create a feature branch.`,
995
+ 'Basso'
996
+ );
997
+ lastGitFlowNotification = now;
1159
998
  }
999
+ }
1160
1000
 
1161
- // 2. Evidence Freshness Guard
1162
- if (evidenceMonitor.isStale() && (now - lastEvidenceNotification > NOTIFICATION_COOLDOWN)) {
1163
- try {
1164
- await evidenceMonitor.refresh();
1165
- sendNotification('🔄 Evidence Auto-Updated', 'AI Evidence has been refreshed automatically', 'Purr');
1166
- } catch (err) {
1167
- sendNotification('⚠️ Evidence Stale', `Failed to auto-refresh evidence: ${err.message}`, 'Basso');
1168
- }
1169
- lastEvidenceNotification = now;
1001
+ // 2. Evidence Freshness Guard
1002
+ if (evidenceMonitor.isStale() && (now - lastEvidenceNotification > NOTIFICATION_COOLDOWN)) {
1003
+ try {
1004
+ await evidenceMonitor.refresh();
1005
+ sendNotification('🔄 Evidence Auto-Updated', 'AI Evidence has been refreshed automatically', 'Purr');
1006
+ } catch (err) {
1007
+ sendNotification('⚠️ Evidence Stale', `Failed to auto-refresh evidence: ${err.message}`, 'Basso');
1170
1008
  }
1009
+ lastEvidenceNotification = now;
1010
+ }
1171
1011
 
1172
- // 3. Autonomous Orchestration
1173
- if (orchestrator.shouldReanalyze()) {
1174
- const decision = await orchestrator.analyzeContext();
1175
- if (decision.action === 'auto-execute' && decision.platforms.length > 0) {
1176
- try {
1177
- await evidenceMonitor.refresh();
1178
- sendNotification('✅ AI Start Executed', `Platforms: ${decision.platforms.map(p => p.platform.toUpperCase()).join(', ')}`, 'Glass');
1179
- } catch (e) {
1180
- sendNotification('❌ AI Start Error', `Failed to execute: ${e.message}`, 'Basso');
1181
- }
1012
+ // 3. Autonomous Orchestration
1013
+ if (orchestrator.shouldReanalyze()) {
1014
+ const decision = await orchestrator.analyzeContext();
1015
+ if (decision.action === 'auto-execute' && decision.platforms.length > 0) {
1016
+ try {
1017
+ await evidenceMonitor.refresh();
1018
+ sendNotification('✅ AI Start Executed', `Platforms: ${decision.platforms.map(p => p.platform.toUpperCase()).join(', ')}`, 'Glass');
1019
+ } catch (e) {
1020
+ sendNotification('❌ AI Start Error', `Failed to execute: ${e.message}`, 'Basso');
1182
1021
  }
1183
1022
  }
1184
-
1185
- } catch (error) {
1186
- if (process.env.DEBUG) console.error('[MCP] Polling loop error:', error);
1187
1023
  }
1188
- }, 30000);
1189
- }
1190
1024
 
1191
- // AUTO-COMMIT: Only for project code changes (no node_modules, no library)
1192
- if (MCP_IS_PRIMARY) {
1193
- setInterval(async () => {
1194
- if (!AUTO_COMMIT_ENABLED) {
1195
- return;
1196
- }
1197
-
1198
- const now = Date.now();
1199
- if (now - lastAutoCommitTime < AUTO_COMMIT_INTERVAL) return;
1025
+ } catch (error) {
1026
+ if (process.env.DEBUG) console.error('[MCP] Polling loop error:', error);
1027
+ }
1028
+ }, 30000);
1200
1029
 
1201
- try {
1202
- const gitFlowService = getCompositionRoot().getGitFlowService();
1203
- const gitQuery = getCompositionRoot().getGitQueryAdapter();
1204
- const gitCommand = getCompositionRoot().getGitCommandAdapter();
1030
+ // AUTO-COMMIT: Only for project code changes (no node_modules, no library)
1031
+ setInterval(async () => {
1032
+ if (!AUTO_COMMIT_ENABLED) {
1033
+ return;
1034
+ }
1205
1035
 
1206
- const currentBranch = gitFlowService.getCurrentBranch();
1207
- const isFeatureBranch = currentBranch.match(/^(feature|fix|hotfix)\//);
1036
+ const now = Date.now();
1037
+ if (now - lastAutoCommitTime < AUTO_COMMIT_INTERVAL) return;
1208
1038
 
1209
- if (!isFeatureBranch) {
1210
- return;
1211
- }
1039
+ try {
1040
+ const gitFlowService = getCompositionRoot().getGitFlowService();
1041
+ const gitQuery = getCompositionRoot().getGitQueryAdapter();
1042
+ const gitCommand = getCompositionRoot().getGitCommandAdapter();
1212
1043
 
1213
- if (gitFlowService.isClean()) {
1214
- return;
1215
- }
1044
+ const currentBranch = gitFlowService.getCurrentBranch();
1045
+ const isFeatureBranch = currentBranch.match(/^(feature|fix|hotfix)\//);
1216
1046
 
1217
- // Get uncommitted changes
1218
- const uncommittedChanges = gitQuery.getUncommittedChanges();
1219
-
1220
- // Detect library installation path
1221
- const libraryPath = getLibraryInstallPath();
1222
-
1223
- // Filter changes: project code only
1224
- const filesToCommit = uncommittedChanges.filter(file => {
1225
- // Exclude noise
1226
- if (file.startsWith('node_modules/') ||
1227
- file.includes('package-lock.json') ||
1228
- file.startsWith('.git/') ||
1229
- file.startsWith('.cursor/') ||
1230
- file.startsWith('.ast-intelligence/') ||
1231
- file.startsWith('.vscode/') ||
1232
- file.startsWith('.idea/')) {
1233
- return false;
1234
- }
1047
+ if (!isFeatureBranch) {
1048
+ return;
1049
+ }
1235
1050
 
1236
- // Exclude library itself
1237
- if (libraryPath && file.startsWith(libraryPath + '/')) {
1238
- return false;
1239
- }
1051
+ if (gitFlowService.isClean()) {
1052
+ return;
1053
+ }
1240
1054
 
1241
- // Code/Doc files only
1242
- const codeExtensions = ['.ts', '.tsx', '.js', '.jsx', '.swift', '.kt', '.py', '.java', '.go', '.rs', '.md', '.json', '.yaml', '.yml'];
1243
- return codeExtensions.some(ext => file.endsWith(ext));
1244
- });
1055
+ // Get uncommitted changes
1056
+ const uncommittedChanges = gitQuery.getUncommittedChanges();
1057
+
1058
+ // Detect library installation path
1059
+ const libraryPath = getLibraryInstallPath();
1060
+
1061
+ // Filter changes: project code only
1062
+ const filesToCommit = uncommittedChanges.filter(file => {
1063
+ // Exclude noise
1064
+ if (file.startsWith('node_modules/') ||
1065
+ file.includes('package-lock.json') ||
1066
+ file.startsWith('.git/') ||
1067
+ file.startsWith('.cursor/') ||
1068
+ file.startsWith('.ast-intelligence/') ||
1069
+ file.startsWith('.vscode/') ||
1070
+ file.startsWith('.idea/')) {
1071
+ return false;
1072
+ }
1245
1073
 
1246
- if (filesToCommit.length === 0) {
1247
- return;
1074
+ // Exclude library itself
1075
+ if (libraryPath && file.startsWith(libraryPath + '/')) {
1076
+ return false;
1248
1077
  }
1249
1078
 
1250
- // Stage files
1251
- filesToCommit.forEach(file => {
1252
- gitCommand.add(file);
1253
- });
1079
+ // Code/Doc files only
1080
+ const codeExtensions = ['.ts', '.tsx', '.js', '.jsx', '.swift', '.kt', '.py', '.java', '.go', '.rs', '.md', '.json', '.yaml', '.yml'];
1081
+ return codeExtensions.some(ext => file.endsWith(ext));
1082
+ });
1254
1083
 
1255
- const branchType = currentBranch.split('/')[0];
1256
- const branchName = currentBranch.split('/').slice(1).join('/');
1257
- const commitMessage = `${branchType}(auto): ${branchName} - ${filesToCommit.length} files`;
1084
+ if (filesToCommit.length === 0) {
1085
+ return;
1086
+ }
1258
1087
 
1259
- // Commit
1260
- gitCommand.commit(commitMessage);
1088
+ // Stage files
1089
+ filesToCommit.forEach(file => {
1090
+ gitCommand.add(file);
1091
+ });
1261
1092
 
1262
- sendNotification('✅ Auto-Commit', `${filesToCommit.length} files in ${currentBranch}`, 'Purr');
1263
- lastAutoCommitTime = now;
1093
+ const branchType = currentBranch.split('/')[0];
1094
+ const branchName = currentBranch.split('/').slice(1).join('/');
1095
+ const commitMessage = `${branchType}(auto): ${branchName} - ${filesToCommit.length} files`;
1264
1096
 
1265
- if (AUTO_PUSH_ENABLED) {
1266
- if (gitFlowService.isGitHubAvailable()) {
1267
- try {
1268
- gitCommand.push('origin', currentBranch);
1269
- sendNotification('✅ Auto-Push', `Pushed to origin/${currentBranch}`, 'Glass');
1097
+ // Commit
1098
+ gitCommand.commit(commitMessage);
1270
1099
 
1271
- if (AUTO_PR_ENABLED) {
1272
- const baseBranch = process.env.AST_BASE_BRANCH || 'develop';
1273
- const branchState = gitQuery.getBranchState(currentBranch);
1100
+ sendNotification('✅ Auto-Commit', `${filesToCommit.length} files in ${currentBranch}`, 'Purr');
1101
+ lastAutoCommitTime = now;
1274
1102
 
1275
- if (branchState.ahead >= 3) {
1276
- const prTitle = `Auto-PR: ${branchName}`;
1277
- const prUrl = gitFlowService.createPullRequest(currentBranch, baseBranch, prTitle, 'Automated PR by Pumuki Git Flow');
1278
- if (prUrl) {
1279
- sendNotification('✅ Auto-PR Created', prTitle, 'Hero');
1280
- }
1103
+ if (AUTO_PUSH_ENABLED) {
1104
+ if (gitFlowService.isGitHubAvailable()) {
1105
+ try {
1106
+ gitCommand.push('origin', currentBranch);
1107
+ sendNotification('✅ Auto-Push', `Pushed to origin/${currentBranch}`, 'Glass');
1108
+
1109
+ if (AUTO_PR_ENABLED) {
1110
+ const baseBranch = process.env.AST_BASE_BRANCH || 'develop';
1111
+ const branchState = gitQuery.getBranchState(currentBranch);
1112
+
1113
+ if (branchState.ahead >= 3) {
1114
+ const prTitle = `Auto-PR: ${branchName}`;
1115
+ const prUrl = gitFlowService.createPullRequest(currentBranch, baseBranch, prTitle, 'Automated PR by Pumuki Git Flow');
1116
+ if (prUrl) {
1117
+ sendNotification('✅ Auto-PR Created', prTitle, 'Hero');
1281
1118
  }
1282
1119
  }
1283
- } catch (e) {
1284
- if (!e.message.includes('No remote')) {
1285
- sendNotification('⚠️ Auto-Push Failed', 'Push manual required', 'Basso');
1286
- }
1120
+ }
1121
+ } catch (e) {
1122
+ if (!e.message.includes('No remote')) {
1123
+ sendNotification('⚠️ Auto-Push Failed', 'Push manual required', 'Basso');
1287
1124
  }
1288
1125
  }
1289
1126
  }
1290
-
1291
- } catch (error) {
1292
- if (process.env.DEBUG) console.error('[MCP] Auto-commit error:', error);
1293
1127
  }
1294
- }, AUTO_COMMIT_INTERVAL);
1295
- }
1128
+
1129
+ } catch (error) {
1130
+ if (process.env.DEBUG) console.error('[MCP] Auto-commit error:', error);
1131
+ }
1132
+ }, AUTO_COMMIT_INTERVAL);