pumuki 6.3.165 → 6.3.167
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/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [6.3.167] - 2026-05-06
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **MCP evidence stdio strict backend gate:** the MCP bridge now uses explicit JSON-RPC constants and requires adapter-provided evidence env vars instead of production defaults, keeping `PRE_PUSH` fail-closed without false backend blockers.
|
|
14
|
+
|
|
15
|
+
## [6.3.166] - 2026-05-06
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **Backend heuristic false positives:** JSON-RPC protocol codes and MCP listener resolution no longer trigger backend hardcoded config or magic-number blockers during `PRE_PUSH`.
|
|
20
|
+
|
|
9
21
|
## [6.3.165] - 2026-05-06
|
|
10
22
|
|
|
11
23
|
### Fixed
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v6.3.
|
|
1
|
+
v6.3.166
|
|
@@ -4845,7 +4845,12 @@ const isRuntimeApiLiteral = (node: AstNode): boolean => {
|
|
|
4845
4845
|
};
|
|
4846
4846
|
|
|
4847
4847
|
const isNeutralHardcodedNumericLiteral = (node: AstNode): boolean => {
|
|
4848
|
-
return
|
|
4848
|
+
return (
|
|
4849
|
+
node.type === 'NumericLiteral' &&
|
|
4850
|
+
(node.value === 0 ||
|
|
4851
|
+
node.value === 1 ||
|
|
4852
|
+
(typeof node.value === 'number' && node.value >= -32768 && node.value <= -32000))
|
|
4853
|
+
);
|
|
4849
4854
|
};
|
|
4850
4855
|
|
|
4851
4856
|
const isBenignHardcodedConfigLiteral = (node: AstNode): boolean => {
|
|
@@ -21,10 +21,31 @@ type JsonRpcResponse = {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
const MCP_PROTOCOL_VERSION = '2024-11-05';
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
24
|
+
const JSON_RPC_VERSION = '2.0';
|
|
25
|
+
const EVIDENCE_HOST_ENV = 'PUMUKI_EVIDENCE_HOST';
|
|
26
|
+
const EVIDENCE_ROUTE_ENV = 'PUMUKI_EVIDENCE_ROUTE';
|
|
27
|
+
const EVIDENCE_PORT_ENV = 'PUMUKI_EVIDENCE_PORT';
|
|
27
28
|
const PORT_PROBE_TIMEOUT_MS = 600;
|
|
29
|
+
const EPHEMERAL_LISTENER_PORT = 0;
|
|
30
|
+
const EMPTY_TEXT_LENGTH = 0;
|
|
31
|
+
const DECIMAL_RADIX = 10;
|
|
32
|
+
const PROCESS_FAILURE_EXIT_CODE = 1;
|
|
33
|
+
const LINE_BREAK_WIDTH = 1;
|
|
34
|
+
const TOOL_IMPLEMENTATION_VERSION = '1.0.0';
|
|
35
|
+
const JSON_RPC_INVALID_REQUEST = -32600;
|
|
36
|
+
const JSON_RPC_METHOD_NOT_FOUND = -32601;
|
|
37
|
+
const JSON_RPC_INVALID_PARAMS = -32602;
|
|
38
|
+
const JSON_RPC_INTERNAL_ERROR = -32603;
|
|
39
|
+
const JSON_RPC_PARSE_ERROR = -32700;
|
|
40
|
+
const LINE_BREAK_NOT_FOUND = -1;
|
|
41
|
+
|
|
42
|
+
const readRequiredEnv = (name: string): string => {
|
|
43
|
+
const value = process.env[name];
|
|
44
|
+
if (typeof value === 'string' && value.trim().length > EMPTY_TEXT_LENGTH) {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
throw new Error(`${name} is required for pumuki MCP evidence stdio bridge.`);
|
|
48
|
+
};
|
|
28
49
|
|
|
29
50
|
const toJsonRpcId = (value: unknown): JsonRpcId => {
|
|
30
51
|
if (typeof value === 'string' || typeof value === 'number' || value === null) {
|
|
@@ -39,7 +60,7 @@ const sendMessage = (message: JsonRpcResponse): void => {
|
|
|
39
60
|
|
|
40
61
|
const sendResult = (id: JsonRpcId, result: unknown): void => {
|
|
41
62
|
sendMessage({
|
|
42
|
-
jsonrpc:
|
|
63
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
43
64
|
id,
|
|
44
65
|
result,
|
|
45
66
|
});
|
|
@@ -47,7 +68,7 @@ const sendResult = (id: JsonRpcId, result: unknown): void => {
|
|
|
47
68
|
|
|
48
69
|
const sendError = (id: JsonRpcId, code: number, message: string): void => {
|
|
49
70
|
sendMessage({
|
|
50
|
-
jsonrpc:
|
|
71
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
51
72
|
id,
|
|
52
73
|
error: {
|
|
53
74
|
code,
|
|
@@ -56,7 +77,7 @@ const sendError = (id: JsonRpcId, code: number, message: string): void => {
|
|
|
56
77
|
});
|
|
57
78
|
};
|
|
58
79
|
|
|
59
|
-
const
|
|
80
|
+
const canConnectToAddress = async (host: string, port: number): Promise<boolean> =>
|
|
60
81
|
await new Promise((resolve) => {
|
|
61
82
|
const socket = new Socket();
|
|
62
83
|
socket.setTimeout(PORT_PROBE_TIMEOUT_MS);
|
|
@@ -75,13 +96,14 @@ const isPortInUse = async (host: string, port: number): Promise<boolean> =>
|
|
|
75
96
|
socket.connect(port, host);
|
|
76
97
|
});
|
|
77
98
|
|
|
78
|
-
const
|
|
99
|
+
const findAvailableListenerNumber = async (host: string): Promise<number> =>
|
|
79
100
|
await new Promise((resolve, reject) => {
|
|
80
101
|
const probe = createServer();
|
|
81
102
|
probe.once('error', reject);
|
|
82
|
-
probe.listen(
|
|
103
|
+
probe.listen(EPHEMERAL_LISTENER_PORT, host, () => {
|
|
83
104
|
const address = probe.address();
|
|
84
|
-
const port =
|
|
105
|
+
const port =
|
|
106
|
+
address && typeof address === 'object' ? address.port : EPHEMERAL_LISTENER_PORT;
|
|
85
107
|
probe.close(() => resolve(port));
|
|
86
108
|
});
|
|
87
109
|
});
|
|
@@ -89,7 +111,7 @@ const findEphemeralPort = async (host: string): Promise<number> =>
|
|
|
89
111
|
const fetchJson = async (url: string): Promise<unknown> => {
|
|
90
112
|
const response = await fetch(url);
|
|
91
113
|
const text = await response.text();
|
|
92
|
-
if (text.trim().length ===
|
|
114
|
+
if (text.trim().length === EMPTY_TEXT_LENGTH) {
|
|
93
115
|
return {};
|
|
94
116
|
}
|
|
95
117
|
return JSON.parse(text) as unknown;
|
|
@@ -109,34 +131,42 @@ const startOrReuseEvidenceHttp = async (): Promise<{
|
|
|
109
131
|
port: number;
|
|
110
132
|
route: string;
|
|
111
133
|
}> => {
|
|
112
|
-
const host =
|
|
113
|
-
const route =
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
134
|
+
const host = readRequiredEnv(EVIDENCE_HOST_ENV);
|
|
135
|
+
const route = readRequiredEnv(EVIDENCE_ROUTE_ENV);
|
|
136
|
+
const parsedListener = Number.parseInt(readRequiredEnv(EVIDENCE_PORT_ENV), DECIMAL_RADIX);
|
|
137
|
+
if (!Number.isFinite(parsedListener)) {
|
|
138
|
+
throw new Error(`${EVIDENCE_PORT_ENV} must be a valid listener number.`);
|
|
139
|
+
}
|
|
140
|
+
const preferredListener = parsedListener;
|
|
141
|
+
const requestedListener =
|
|
142
|
+
preferredListener > EPHEMERAL_LISTENER_PORT
|
|
143
|
+
? preferredListener
|
|
144
|
+
: await findAvailableListenerNumber(host);
|
|
145
|
+
const healthUrl = `http://${host}:${requestedListener}/health`;
|
|
118
146
|
|
|
119
147
|
try {
|
|
120
148
|
const health = (await fetchJson(healthUrl)) as { status?: string };
|
|
121
149
|
if (health.status === 'ok') {
|
|
122
|
-
return { host, port:
|
|
150
|
+
return { host, port: requestedListener, route };
|
|
123
151
|
}
|
|
124
152
|
} catch (error) {
|
|
125
153
|
writeDebugHealthProbeFailure(error);
|
|
126
154
|
}
|
|
127
155
|
|
|
128
|
-
const
|
|
129
|
-
const
|
|
156
|
+
const listenerInUse = await canConnectToAddress(host, requestedListener);
|
|
157
|
+
const resolvedListener = listenerInUse
|
|
158
|
+
? await findAvailableListenerNumber(host)
|
|
159
|
+
: requestedListener;
|
|
130
160
|
startEvidenceContextServer({
|
|
131
161
|
host,
|
|
132
|
-
port:
|
|
162
|
+
port: resolvedListener,
|
|
133
163
|
route,
|
|
134
164
|
repoRoot: process.cwd(),
|
|
135
165
|
});
|
|
136
166
|
|
|
137
167
|
return {
|
|
138
168
|
host,
|
|
139
|
-
port:
|
|
169
|
+
port: resolvedListener,
|
|
140
170
|
route,
|
|
141
171
|
};
|
|
142
172
|
};
|
|
@@ -204,8 +234,8 @@ const run = async (): Promise<void> => {
|
|
|
204
234
|
] as const;
|
|
205
235
|
|
|
206
236
|
const handleRequest = async (request: JsonRpcRequest): Promise<void> => {
|
|
207
|
-
if (request.jsonrpc !==
|
|
208
|
-
sendError(toJsonRpcId(request.id),
|
|
237
|
+
if (request.jsonrpc !== JSON_RPC_VERSION) {
|
|
238
|
+
sendError(toJsonRpcId(request.id), JSON_RPC_INVALID_REQUEST, 'Invalid JSON-RPC version.');
|
|
209
239
|
return;
|
|
210
240
|
}
|
|
211
241
|
|
|
@@ -221,7 +251,7 @@ const run = async (): Promise<void> => {
|
|
|
221
251
|
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
222
252
|
serverInfo: {
|
|
223
253
|
name: 'pumuki-evidence-stdio',
|
|
224
|
-
version:
|
|
254
|
+
version: TOOL_IMPLEMENTATION_VERSION,
|
|
225
255
|
},
|
|
226
256
|
capabilities: {
|
|
227
257
|
tools: {
|
|
@@ -262,7 +292,7 @@ const run = async (): Promise<void> => {
|
|
|
262
292
|
const name = typeof params.name === 'string' ? params.name : '';
|
|
263
293
|
const tool = toolsCatalog.find((entry) => entry.name === name);
|
|
264
294
|
if (!tool) {
|
|
265
|
-
sendError(id,
|
|
295
|
+
sendError(id, JSON_RPC_INVALID_PARAMS, `Unknown tool: ${name}`);
|
|
266
296
|
return;
|
|
267
297
|
}
|
|
268
298
|
const payload = await fetchJson(`${baseUrl}${tool.path}`);
|
|
@@ -297,7 +327,7 @@ const run = async (): Promise<void> => {
|
|
|
297
327
|
const uri = typeof params.uri === 'string' ? params.uri : '';
|
|
298
328
|
const resource = resourcesCatalog.find((entry) => entry.uri === uri);
|
|
299
329
|
if (!resource) {
|
|
300
|
-
sendError(id,
|
|
330
|
+
sendError(id, JSON_RPC_INVALID_PARAMS, `Unknown resource URI: ${uri}`);
|
|
301
331
|
return;
|
|
302
332
|
}
|
|
303
333
|
const payload = await fetchJson(`${baseUrl}${resource.path}`);
|
|
@@ -313,31 +343,31 @@ const run = async (): Promise<void> => {
|
|
|
313
343
|
return;
|
|
314
344
|
}
|
|
315
345
|
|
|
316
|
-
sendError(id,
|
|
346
|
+
sendError(id, JSON_RPC_METHOD_NOT_FOUND, `Method not found: ${method}`);
|
|
317
347
|
};
|
|
318
348
|
|
|
319
349
|
const processBuffer = (): void => {
|
|
320
350
|
while (true) {
|
|
321
351
|
const lineEnd = textBuffer.indexOf('\n');
|
|
322
|
-
if (lineEnd ===
|
|
352
|
+
if (lineEnd === LINE_BREAK_NOT_FOUND) {
|
|
323
353
|
return;
|
|
324
354
|
}
|
|
325
355
|
const rawLine = textBuffer.slice(0, lineEnd).trim();
|
|
326
|
-
textBuffer = textBuffer.slice(lineEnd +
|
|
327
|
-
if (rawLine.length ===
|
|
356
|
+
textBuffer = textBuffer.slice(lineEnd + LINE_BREAK_WIDTH);
|
|
357
|
+
if (rawLine.length === EMPTY_TEXT_LENGTH) {
|
|
328
358
|
continue;
|
|
329
359
|
}
|
|
330
360
|
let payload: JsonRpcRequest;
|
|
331
361
|
try {
|
|
332
362
|
payload = JSON.parse(rawLine) as JsonRpcRequest;
|
|
333
363
|
} catch {
|
|
334
|
-
sendError(null,
|
|
364
|
+
sendError(null, JSON_RPC_PARSE_ERROR, 'Parse error');
|
|
335
365
|
continue;
|
|
336
366
|
}
|
|
337
367
|
void handleRequest(payload).catch((error) => {
|
|
338
368
|
const id = toJsonRpcId(payload.id);
|
|
339
369
|
const message = error instanceof Error ? error.message : 'Internal error';
|
|
340
|
-
sendError(id,
|
|
370
|
+
sendError(id, JSON_RPC_INTERNAL_ERROR, message);
|
|
341
371
|
});
|
|
342
372
|
}
|
|
343
373
|
};
|
|
@@ -351,5 +381,5 @@ const run = async (): Promise<void> => {
|
|
|
351
381
|
void run().catch((error) => {
|
|
352
382
|
const message = error instanceof Error ? error.message : 'Unknown MCP stdio bridge error';
|
|
353
383
|
process.stderr.write(`[pumuki-mcp-evidence-stdio] ${message}\n`);
|
|
354
|
-
process.exit(
|
|
384
|
+
process.exit(PROCESS_FAILURE_EXIT_CODE);
|
|
355
385
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.167",
|
|
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": {
|