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.
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
if (
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
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
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
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
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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
|
-
|
|
1191
|
-
if (
|
|
1192
|
-
|
|
1193
|
-
|
|
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
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
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
|
-
|
|
1206
|
-
|
|
1036
|
+
const now = Date.now();
|
|
1037
|
+
if (now - lastAutoCommitTime < AUTO_COMMIT_INTERVAL) return;
|
|
1207
1038
|
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1039
|
+
try {
|
|
1040
|
+
const gitFlowService = getCompositionRoot().getGitFlowService();
|
|
1041
|
+
const gitQuery = getCompositionRoot().getGitQueryAdapter();
|
|
1042
|
+
const gitCommand = getCompositionRoot().getGitCommandAdapter();
|
|
1211
1043
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
}
|
|
1044
|
+
const currentBranch = gitFlowService.getCurrentBranch();
|
|
1045
|
+
const isFeatureBranch = currentBranch.match(/^(feature|fix|hotfix)\//);
|
|
1215
1046
|
|
|
1216
|
-
|
|
1217
|
-
|
|
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
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
}
|
|
1051
|
+
if (gitFlowService.isClean()) {
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1239
1054
|
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
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
|
-
|
|
1246
|
-
|
|
1074
|
+
// Exclude library itself
|
|
1075
|
+
if (libraryPath && file.startsWith(libraryPath + '/')) {
|
|
1076
|
+
return false;
|
|
1247
1077
|
}
|
|
1248
1078
|
|
|
1249
|
-
//
|
|
1250
|
-
|
|
1251
|
-
|
|
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
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1084
|
+
if (filesToCommit.length === 0) {
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1257
1087
|
|
|
1258
|
-
|
|
1259
|
-
|
|
1088
|
+
// Stage files
|
|
1089
|
+
filesToCommit.forEach(file => {
|
|
1090
|
+
gitCommand.add(file);
|
|
1091
|
+
});
|
|
1260
1092
|
|
|
1261
|
-
|
|
1262
|
-
|
|
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
|
-
|
|
1265
|
-
|
|
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
|
-
|
|
1271
|
-
|
|
1272
|
-
const branchState = gitQuery.getBranchState(currentBranch);
|
|
1100
|
+
sendNotification('✅ Auto-Commit', `${filesToCommit.length} files in ${currentBranch}`, 'Purr');
|
|
1101
|
+
lastAutoCommitTime = now;
|
|
1273
1102
|
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
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
|
-
}
|
|
1283
|
-
|
|
1284
|
-
|
|
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
|
-
|
|
1294
|
-
}
|
|
1128
|
+
|
|
1129
|
+
} catch (error) {
|
|
1130
|
+
if (process.env.DEBUG) console.error('[MCP] Auto-commit error:', error);
|
|
1131
|
+
}
|
|
1132
|
+
}, AUTO_COMMIT_INTERVAL);
|