pumuki-ast-hooks 5.4.7 → 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.7",
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": {
@@ -54,166 +54,8 @@ function resolveRepoRoot() {
54
54
 
55
55
  const REPO_ROOT = resolveRepoRoot();
56
56
 
57
- const MCP_LOCK_DIR = path.join(REPO_ROOT, '.audit_tmp', 'mcp-singleton.lock');
58
- const MCP_LOCK_PID = path.join(MCP_LOCK_DIR, 'pid');
59
-
60
- let MCP_IS_PRIMARY = true;
61
-
62
- function logMcpError(context, error) {
63
- const msg = error instanceof Error ? error.message : String(error);
64
- process.stderr.write(`[MCP][ERROR] ${context}: ${msg}\n`);
65
- }
66
-
67
- function logMcpDebug(message) {
68
- if (env.getBool('DEBUG', false)) {
69
- process.stderr.write(`[MCP][DEBUG] ${message}\n`);
70
- }
71
- }
72
-
73
- function isPidRunning(pid) {
74
- if (!pid || !Number.isFinite(pid) || pid <= 0) return false;
75
- try {
76
- process.kill(pid, 0);
77
- return true;
78
- } catch (error) {
79
- logMcpDebug(`isPidRunning(${pid}) = false: ${error.code || error.message}`);
80
- return false;
81
- }
82
- }
83
-
84
- function safeReadPid(filePath) {
85
- try {
86
- if (!fs.existsSync(filePath)) return null;
87
- const raw = String(fs.readFileSync(filePath, 'utf8') || '').trim();
88
- const pid = Number(raw);
89
- if (!Number.isFinite(pid) || pid <= 0) return null;
90
- return pid;
91
- } catch (error) {
92
- logMcpError('safeReadPid', error);
93
- return null;
94
- }
95
- }
96
-
97
- function removeLockDir() {
98
- try {
99
- if (fs.existsSync(MCP_LOCK_PID)) {
100
- fs.unlinkSync(MCP_LOCK_PID);
101
- logMcpDebug('Removed lock PID file');
102
- }
103
- } catch (error) {
104
- logMcpError('removeLockDir (pid file)', error);
105
- }
106
- try {
107
- if (fs.existsSync(MCP_LOCK_DIR)) {
108
- fs.rmdirSync(MCP_LOCK_DIR);
109
- logMcpDebug('Removed lock directory');
110
- }
111
- } catch (error) {
112
- logMcpError('removeLockDir (directory)', error);
113
- }
114
- }
115
-
116
- function cleanupAndExit(code = 0) {
117
- const myPid = process.pid;
118
- const lockPid = safeReadPid(MCP_LOCK_PID);
119
-
120
- if (lockPid === myPid) {
121
- logMcpDebug(`Cleaning up lock (my pid=${myPid})`);
122
- removeLockDir();
123
- } else {
124
- logMcpDebug(`Not cleaning lock (lockPid=${lockPid}, myPid=${myPid})`);
125
- }
126
-
127
- process.exit(code);
128
- }
129
-
130
- function installStdioExitHandlers() {
131
- const handleStdioTermination = (source) => (error) => {
132
- if (error) {
133
- const code = String(error.code || '').toUpperCase();
134
- if (code === 'EPIPE' || code === 'ERR_STREAM_DESTROYED' || code === 'ECONNRESET') {
135
- logMcpDebug(`STDIO ${source} closed (${code}), exiting cleanly`);
136
- cleanupAndExit(0);
137
- return;
138
- }
139
- logMcpError(`STDIO ${source} error`, error);
140
- } else {
141
- logMcpDebug(`STDIO ${source} ended, exiting cleanly`);
142
- }
143
- cleanupAndExit(0);
144
- };
145
-
146
- try {
147
- process.stdin.on('end', handleStdioTermination('stdin'));
148
- process.stdin.on('close', handleStdioTermination('stdin'));
149
- process.stdin.on('error', handleStdioTermination('stdin'));
150
- } catch (error) {
151
- logMcpError('installStdioExitHandlers (stdin)', error);
152
- }
153
-
154
- try {
155
- process.stdout.on('error', handleStdioTermination('stdout'));
156
- process.stderr.on('error', handleStdioTermination('stderr'));
157
- } catch (error) {
158
- logMcpError('installStdioExitHandlers (stdout/stderr)', error);
159
- }
160
- }
161
-
162
- function acquireSingletonLock() {
163
- try {
164
- fs.mkdirSync(path.join(REPO_ROOT, '.audit_tmp'), { recursive: true });
165
- } catch (error) {
166
- logMcpError('acquireSingletonLock (create .audit_tmp)', error);
167
- }
168
-
169
- try {
170
- fs.mkdirSync(MCP_LOCK_DIR);
171
- } catch (error) {
172
- const existingPid = safeReadPid(MCP_LOCK_PID);
173
-
174
- if (existingPid && isPidRunning(existingPid)) {
175
- process.stderr.write(`[MCP] Another instance is already running (pid ${existingPid}). Exiting.\n`);
176
- process.exit(0);
177
- }
178
-
179
- logMcpDebug(`Lock exists but PID ${existingPid || 'unknown'} is not running, cleaning up`);
180
- removeLockDir();
181
-
182
- try {
183
- fs.mkdirSync(MCP_LOCK_DIR);
184
- } catch (retryError) {
185
- logMcpError('acquireSingletonLock (retry mkdir)', retryError);
186
- process.stderr.write(`[MCP] Failed to acquire lock after cleanup. Exiting.\n`);
187
- process.exit(1);
188
- }
189
- }
190
-
191
- try {
192
- fs.writeFileSync(MCP_LOCK_PID, String(process.pid), { encoding: 'utf8' });
193
- logMcpDebug(`Lock acquired, PID ${process.pid} written`);
194
- } catch (error) {
195
- logMcpError('acquireSingletonLock (write pid)', error);
196
- }
197
-
198
- process.on('exit', () => {
199
- const lockPid = safeReadPid(MCP_LOCK_PID);
200
- if (lockPid === process.pid) {
201
- removeLockDir();
202
- }
203
- });
204
-
205
- process.on('SIGINT', () => cleanupAndExit(0));
206
- process.on('SIGTERM', () => cleanupAndExit(0));
207
- process.on('SIGHUP', () => cleanupAndExit(0));
208
-
209
- return { acquired: true, pid: process.pid };
210
- }
211
-
212
- const singleton = acquireSingletonLock();
213
- if (!singleton.acquired) {
214
- process.exit(0);
215
- }
216
- installStdioExitHandlers();
57
+ // NO singleton lock - Windsurf manages process lifecycle
58
+ // Each project gets its own independent MCP process
217
59
 
218
60
  // Lazy-loaded CompositionRoot - only initialized when first needed
219
61
  let _compositionRoot = null;
@@ -1128,167 +970,163 @@ protocolHandler.start(handleMcpMessage);
1128
970
  /**
1129
971
  * Polling loop for background notifications and automations
1130
972
  */
1131
- if (MCP_IS_PRIMARY) {
1132
- setInterval(async () => {
1133
- try {
1134
- const now = Date.now();
1135
- const gitFlowService = getCompositionRoot().getGitFlowService();
1136
- const gitQuery = getCompositionRoot().getGitQueryAdapter();
1137
- const evidenceMonitor = getCompositionRoot().getEvidenceMonitor();
1138
- const orchestrator = getCompositionRoot().getOrchestrator();
1139
-
1140
- const currentBranch = gitFlowService.getCurrentBranch();
1141
- const baseBranch = process.env.AST_BASE_BRANCH || 'develop';
1142
- const isProtectedBranch = ['main', 'master', baseBranch].includes(currentBranch);
1143
-
1144
- const uncommittedChanges = gitQuery.getUncommittedChanges();
1145
- const hasUncommittedChanges = uncommittedChanges && uncommittedChanges.length > 0;
1146
-
1147
- // 1. Protected Branch Guard
1148
- if (isProtectedBranch && hasUncommittedChanges) {
1149
- if (now - lastGitFlowNotification > NOTIFICATION_COOLDOWN) {
1150
- const state = gitQuery.getBranchState(currentBranch);
1151
- sendNotification(
1152
- '⚠️ Git Flow Violation',
1153
- `branch=${currentBranch} changes detected on protected branch. Create a feature branch.`,
1154
- 'Basso'
1155
- );
1156
- lastGitFlowNotification = now;
1157
- }
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;
1158
998
  }
999
+ }
1159
1000
 
1160
- // 2. Evidence Freshness Guard
1161
- if (evidenceMonitor.isStale() && (now - lastEvidenceNotification > NOTIFICATION_COOLDOWN)) {
1162
- try {
1163
- await evidenceMonitor.refresh();
1164
- sendNotification('🔄 Evidence Auto-Updated', 'AI Evidence has been refreshed automatically', 'Purr');
1165
- } catch (err) {
1166
- sendNotification('⚠️ Evidence Stale', `Failed to auto-refresh evidence: ${err.message}`, 'Basso');
1167
- }
1168
- 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');
1169
1008
  }
1009
+ lastEvidenceNotification = now;
1010
+ }
1170
1011
 
1171
- // 3. Autonomous Orchestration
1172
- if (orchestrator.shouldReanalyze()) {
1173
- const decision = await orchestrator.analyzeContext();
1174
- if (decision.action === 'auto-execute' && decision.platforms.length > 0) {
1175
- try {
1176
- await evidenceMonitor.refresh();
1177
- sendNotification('✅ AI Start Executed', `Platforms: ${decision.platforms.map(p => p.platform.toUpperCase()).join(', ')}`, 'Glass');
1178
- } catch (e) {
1179
- sendNotification('❌ AI Start Error', `Failed to execute: ${e.message}`, 'Basso');
1180
- }
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');
1181
1021
  }
1182
1022
  }
1183
-
1184
- } catch (error) {
1185
- if (process.env.DEBUG) console.error('[MCP] Polling loop error:', error);
1186
1023
  }
1187
- }, 30000);
1188
- }
1189
1024
 
1190
- // AUTO-COMMIT: Only for project code changes (no node_modules, no library)
1191
- if (MCP_IS_PRIMARY) {
1192
- setInterval(async () => {
1193
- if (!AUTO_COMMIT_ENABLED) {
1194
- return;
1195
- }
1196
-
1197
- const now = Date.now();
1198
- 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);
1199
1029
 
1200
- try {
1201
- const gitFlowService = getCompositionRoot().getGitFlowService();
1202
- const gitQuery = getCompositionRoot().getGitQueryAdapter();
1203
- 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
+ }
1204
1035
 
1205
- const currentBranch = gitFlowService.getCurrentBranch();
1206
- const isFeatureBranch = currentBranch.match(/^(feature|fix|hotfix)\//);
1036
+ const now = Date.now();
1037
+ if (now - lastAutoCommitTime < AUTO_COMMIT_INTERVAL) return;
1207
1038
 
1208
- if (!isFeatureBranch) {
1209
- return;
1210
- }
1039
+ try {
1040
+ const gitFlowService = getCompositionRoot().getGitFlowService();
1041
+ const gitQuery = getCompositionRoot().getGitQueryAdapter();
1042
+ const gitCommand = getCompositionRoot().getGitCommandAdapter();
1211
1043
 
1212
- if (gitFlowService.isClean()) {
1213
- return;
1214
- }
1044
+ const currentBranch = gitFlowService.getCurrentBranch();
1045
+ const isFeatureBranch = currentBranch.match(/^(feature|fix|hotfix)\//);
1215
1046
 
1216
- // Get uncommitted changes
1217
- const uncommittedChanges = gitQuery.getUncommittedChanges();
1218
-
1219
- // Detect library installation path
1220
- const libraryPath = getLibraryInstallPath();
1221
-
1222
- // Filter changes: project code only
1223
- const filesToCommit = uncommittedChanges.filter(file => {
1224
- // Exclude noise
1225
- if (file.startsWith('node_modules/') ||
1226
- file.includes('package-lock.json') ||
1227
- file.startsWith('.git/') ||
1228
- file.startsWith('.cursor/') ||
1229
- file.startsWith('.ast-intelligence/') ||
1230
- file.startsWith('.vscode/') ||
1231
- file.startsWith('.idea/')) {
1232
- return false;
1233
- }
1047
+ if (!isFeatureBranch) {
1048
+ return;
1049
+ }
1234
1050
 
1235
- // Exclude library itself
1236
- if (libraryPath && file.startsWith(libraryPath + '/')) {
1237
- return false;
1238
- }
1051
+ if (gitFlowService.isClean()) {
1052
+ return;
1053
+ }
1239
1054
 
1240
- // Code/Doc files only
1241
- const codeExtensions = ['.ts', '.tsx', '.js', '.jsx', '.swift', '.kt', '.py', '.java', '.go', '.rs', '.md', '.json', '.yaml', '.yml'];
1242
- return codeExtensions.some(ext => file.endsWith(ext));
1243
- });
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
+ }
1244
1073
 
1245
- if (filesToCommit.length === 0) {
1246
- return;
1074
+ // Exclude library itself
1075
+ if (libraryPath && file.startsWith(libraryPath + '/')) {
1076
+ return false;
1247
1077
  }
1248
1078
 
1249
- // Stage files
1250
- filesToCommit.forEach(file => {
1251
- gitCommand.add(file);
1252
- });
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
+ });
1253
1083
 
1254
- const branchType = currentBranch.split('/')[0];
1255
- const branchName = currentBranch.split('/').slice(1).join('/');
1256
- const commitMessage = `${branchType}(auto): ${branchName} - ${filesToCommit.length} files`;
1084
+ if (filesToCommit.length === 0) {
1085
+ return;
1086
+ }
1257
1087
 
1258
- // Commit
1259
- gitCommand.commit(commitMessage);
1088
+ // Stage files
1089
+ filesToCommit.forEach(file => {
1090
+ gitCommand.add(file);
1091
+ });
1260
1092
 
1261
- sendNotification('✅ Auto-Commit', `${filesToCommit.length} files in ${currentBranch}`, 'Purr');
1262
- 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`;
1263
1096
 
1264
- if (AUTO_PUSH_ENABLED) {
1265
- if (gitFlowService.isGitHubAvailable()) {
1266
- try {
1267
- gitCommand.push('origin', currentBranch);
1268
- sendNotification('✅ Auto-Push', `Pushed to origin/${currentBranch}`, 'Glass');
1097
+ // Commit
1098
+ gitCommand.commit(commitMessage);
1269
1099
 
1270
- if (AUTO_PR_ENABLED) {
1271
- const baseBranch = process.env.AST_BASE_BRANCH || 'develop';
1272
- const branchState = gitQuery.getBranchState(currentBranch);
1100
+ sendNotification('✅ Auto-Commit', `${filesToCommit.length} files in ${currentBranch}`, 'Purr');
1101
+ lastAutoCommitTime = now;
1273
1102
 
1274
- if (branchState.ahead >= 3) {
1275
- const prTitle = `Auto-PR: ${branchName}`;
1276
- const prUrl = gitFlowService.createPullRequest(currentBranch, baseBranch, prTitle, 'Automated PR by Pumuki Git Flow');
1277
- if (prUrl) {
1278
- sendNotification('✅ Auto-PR Created', prTitle, 'Hero');
1279
- }
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');
1280
1118
  }
1281
1119
  }
1282
- } catch (e) {
1283
- if (!e.message.includes('No remote')) {
1284
- sendNotification('⚠️ Auto-Push Failed', 'Push manual required', 'Basso');
1285
- }
1120
+ }
1121
+ } catch (e) {
1122
+ if (!e.message.includes('No remote')) {
1123
+ sendNotification('⚠️ Auto-Push Failed', 'Push manual required', 'Basso');
1286
1124
  }
1287
1125
  }
1288
1126
  }
1289
-
1290
- } catch (error) {
1291
- if (process.env.DEBUG) console.error('[MCP] Auto-commit error:', error);
1292
1127
  }
1293
- }, AUTO_COMMIT_INTERVAL);
1294
- }
1128
+
1129
+ } catch (error) {
1130
+ if (process.env.DEBUG) console.error('[MCP] Auto-commit error:', error);
1131
+ }
1132
+ }, AUTO_COMMIT_INTERVAL);