pumuki 6.3.166 → 6.3.168
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.168] - 2026-05-06
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Tracked evidence commit hygiene:** `pumuki-pre-commit` now respects tracked `.ai_evidence.json` when it was not staged by the developer before a successful code commit. Documentation-only commits still restore evidence drift, and `PUMUKI_PRE_COMMIT_ALWAYS_RESTAGE_TRACKED_EVIDENCE=1` preserves the previous forced restage behavior.
|
|
14
|
+
|
|
15
|
+
## [6.3.167] - 2026-05-06
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **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.
|
|
20
|
+
|
|
9
21
|
## [6.3.166] - 2026-05-06
|
|
10
22
|
|
|
11
23
|
### Fixed
|
|
@@ -77,6 +77,9 @@ const shouldSkipRestagingTrackedEvidenceForDocumentationOnlyScope = (params: {
|
|
|
77
77
|
return paths.every(isDocumentationOnlyStagedPath);
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
+
const hasStagedEvidencePath = (paths: ReadonlyArray<string>): boolean =>
|
|
81
|
+
paths.some((path) => path === '.ai_evidence.json' || path === '.AI_EVIDENCE.json');
|
|
82
|
+
|
|
80
83
|
const PRE_COMMIT_EVIDENCE_MAX_AGE_SECONDS = 900;
|
|
81
84
|
const PRE_PUSH_EVIDENCE_MAX_AGE_SECONDS = 1800;
|
|
82
85
|
const HOOK_GATE_PROGRESS_REMINDER_MS = 2000;
|
|
@@ -520,6 +523,7 @@ const syncTrackedEvidenceAfterSuccessfulPreCommit = (params: {
|
|
|
520
523
|
dependencies: StageRunnerDependencies;
|
|
521
524
|
repoRoot: string;
|
|
522
525
|
gateBlocked: boolean;
|
|
526
|
+
evidenceWasStagedAtStart: boolean;
|
|
523
527
|
}): boolean => {
|
|
524
528
|
const evidenceAbsolutePath = join(params.repoRoot, EVIDENCE_FILE_PATH);
|
|
525
529
|
if (!existsSync(evidenceAbsolutePath)) {
|
|
@@ -543,6 +547,19 @@ const syncTrackedEvidenceAfterSuccessfulPreCommit = (params: {
|
|
|
543
547
|
params.dependencies.restorePathFromHead(params.repoRoot, EVIDENCE_FILE_PATH);
|
|
544
548
|
return false;
|
|
545
549
|
}
|
|
550
|
+
if (
|
|
551
|
+
!params.gateBlocked &&
|
|
552
|
+
!params.evidenceWasStagedAtStart &&
|
|
553
|
+
!isTruthyEnvFlag(process.env.PUMUKI_PRE_COMMIT_ALWAYS_RESTAGE_TRACKED_EVIDENCE)
|
|
554
|
+
) {
|
|
555
|
+
if (!params.dependencies.isQuietMode()) {
|
|
556
|
+
process.stderr.write(
|
|
557
|
+
`[pumuki][evidence-sync] tracked ${EVIDENCE_FILE_PATH} left unstaged because it was not staged before PRE_COMMIT. ` +
|
|
558
|
+
`Force previous behavior: PUMUKI_PRE_COMMIT_ALWAYS_RESTAGE_TRACKED_EVIDENCE=1\n`
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
546
563
|
try {
|
|
547
564
|
params.dependencies.stagePath(params.repoRoot, EVIDENCE_FILE_PATH);
|
|
548
565
|
return false;
|
|
@@ -787,6 +804,7 @@ export async function runPreCommitStage(
|
|
|
787
804
|
const activeDependencies = getDependencies(dependencies);
|
|
788
805
|
const repoRoot = activeDependencies.resolveRepoRoot();
|
|
789
806
|
const manifestSnapshot = captureManifestGuardSnapshot(repoRoot);
|
|
807
|
+
const initiallyStagedPaths = activeDependencies.listStagedIndexPaths(repoRoot);
|
|
790
808
|
activeDependencies.ensureRuntimeArtifactsIgnored(repoRoot);
|
|
791
809
|
if (
|
|
792
810
|
enforceGitAtomicityGate({
|
|
@@ -820,6 +838,7 @@ export async function runPreCommitStage(
|
|
|
820
838
|
dependencies: activeDependencies,
|
|
821
839
|
repoRoot,
|
|
822
840
|
gateBlocked: result.exitCode !== 0,
|
|
841
|
+
evidenceWasStagedAtStart: hasStagedEvidencePath(initiallyStagedPaths),
|
|
823
842
|
})
|
|
824
843
|
) {
|
|
825
844
|
return 1;
|
|
@@ -22,11 +22,30 @@ type JsonRpcResponse = {
|
|
|
22
22
|
|
|
23
23
|
const MCP_PROTOCOL_VERSION = '2024-11-05';
|
|
24
24
|
const JSON_RPC_VERSION = '2.0';
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
25
|
+
const EVIDENCE_HOST_ENV = 'PUMUKI_EVIDENCE_HOST';
|
|
26
|
+
const EVIDENCE_ROUTE_ENV = 'PUMUKI_EVIDENCE_ROUTE';
|
|
27
|
+
const EVIDENCE_PORT_ENV = 'PUMUKI_EVIDENCE_PORT';
|
|
28
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';
|
|
29
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
|
+
};
|
|
30
49
|
|
|
31
50
|
const toJsonRpcId = (value: unknown): JsonRpcId => {
|
|
32
51
|
if (typeof value === 'string' || typeof value === 'number' || value === null) {
|
|
@@ -49,7 +68,7 @@ const sendResult = (id: JsonRpcId, result: unknown): void => {
|
|
|
49
68
|
|
|
50
69
|
const sendError = (id: JsonRpcId, code: number, message: string): void => {
|
|
51
70
|
sendMessage({
|
|
52
|
-
jsonrpc:
|
|
71
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
53
72
|
id,
|
|
54
73
|
error: {
|
|
55
74
|
code,
|
|
@@ -81,9 +100,10 @@ const findAvailableListenerNumber = async (host: string): Promise<number> =>
|
|
|
81
100
|
await new Promise((resolve, reject) => {
|
|
82
101
|
const probe = createServer();
|
|
83
102
|
probe.once('error', reject);
|
|
84
|
-
probe.listen(
|
|
103
|
+
probe.listen(EPHEMERAL_LISTENER_PORT, host, () => {
|
|
85
104
|
const address = probe.address();
|
|
86
|
-
const port =
|
|
105
|
+
const port =
|
|
106
|
+
address && typeof address === 'object' ? address.port : EPHEMERAL_LISTENER_PORT;
|
|
87
107
|
probe.close(() => resolve(port));
|
|
88
108
|
});
|
|
89
109
|
});
|
|
@@ -91,7 +111,7 @@ const findAvailableListenerNumber = async (host: string): Promise<number> =>
|
|
|
91
111
|
const fetchJson = async (url: string): Promise<unknown> => {
|
|
92
112
|
const response = await fetch(url);
|
|
93
113
|
const text = await response.text();
|
|
94
|
-
if (text.trim().length ===
|
|
114
|
+
if (text.trim().length === EMPTY_TEXT_LENGTH) {
|
|
95
115
|
return {};
|
|
96
116
|
}
|
|
97
117
|
return JSON.parse(text) as unknown;
|
|
@@ -111,14 +131,17 @@ const startOrReuseEvidenceHttp = async (): Promise<{
|
|
|
111
131
|
port: number;
|
|
112
132
|
route: string;
|
|
113
133
|
}> => {
|
|
114
|
-
const host =
|
|
115
|
-
const route =
|
|
116
|
-
const parsedListener = Number.parseInt(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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;
|
|
120
141
|
const requestedListener =
|
|
121
|
-
preferredListener >
|
|
142
|
+
preferredListener > EPHEMERAL_LISTENER_PORT
|
|
143
|
+
? preferredListener
|
|
144
|
+
: await findAvailableListenerNumber(host);
|
|
122
145
|
const healthUrl = `http://${host}:${requestedListener}/health`;
|
|
123
146
|
|
|
124
147
|
try {
|
|
@@ -228,7 +251,7 @@ const run = async (): Promise<void> => {
|
|
|
228
251
|
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
229
252
|
serverInfo: {
|
|
230
253
|
name: 'pumuki-evidence-stdio',
|
|
231
|
-
version:
|
|
254
|
+
version: TOOL_IMPLEMENTATION_VERSION,
|
|
232
255
|
},
|
|
233
256
|
capabilities: {
|
|
234
257
|
tools: {
|
|
@@ -269,7 +292,7 @@ const run = async (): Promise<void> => {
|
|
|
269
292
|
const name = typeof params.name === 'string' ? params.name : '';
|
|
270
293
|
const tool = toolsCatalog.find((entry) => entry.name === name);
|
|
271
294
|
if (!tool) {
|
|
272
|
-
sendError(id,
|
|
295
|
+
sendError(id, JSON_RPC_INVALID_PARAMS, `Unknown tool: ${name}`);
|
|
273
296
|
return;
|
|
274
297
|
}
|
|
275
298
|
const payload = await fetchJson(`${baseUrl}${tool.path}`);
|
|
@@ -304,7 +327,7 @@ const run = async (): Promise<void> => {
|
|
|
304
327
|
const uri = typeof params.uri === 'string' ? params.uri : '';
|
|
305
328
|
const resource = resourcesCatalog.find((entry) => entry.uri === uri);
|
|
306
329
|
if (!resource) {
|
|
307
|
-
sendError(id,
|
|
330
|
+
sendError(id, JSON_RPC_INVALID_PARAMS, `Unknown resource URI: ${uri}`);
|
|
308
331
|
return;
|
|
309
332
|
}
|
|
310
333
|
const payload = await fetchJson(`${baseUrl}${resource.path}`);
|
|
@@ -320,31 +343,31 @@ const run = async (): Promise<void> => {
|
|
|
320
343
|
return;
|
|
321
344
|
}
|
|
322
345
|
|
|
323
|
-
sendError(id,
|
|
346
|
+
sendError(id, JSON_RPC_METHOD_NOT_FOUND, `Method not found: ${method}`);
|
|
324
347
|
};
|
|
325
348
|
|
|
326
349
|
const processBuffer = (): void => {
|
|
327
350
|
while (true) {
|
|
328
351
|
const lineEnd = textBuffer.indexOf('\n');
|
|
329
|
-
if (lineEnd ===
|
|
352
|
+
if (lineEnd === LINE_BREAK_NOT_FOUND) {
|
|
330
353
|
return;
|
|
331
354
|
}
|
|
332
355
|
const rawLine = textBuffer.slice(0, lineEnd).trim();
|
|
333
|
-
textBuffer = textBuffer.slice(lineEnd +
|
|
334
|
-
if (rawLine.length ===
|
|
356
|
+
textBuffer = textBuffer.slice(lineEnd + LINE_BREAK_WIDTH);
|
|
357
|
+
if (rawLine.length === EMPTY_TEXT_LENGTH) {
|
|
335
358
|
continue;
|
|
336
359
|
}
|
|
337
360
|
let payload: JsonRpcRequest;
|
|
338
361
|
try {
|
|
339
362
|
payload = JSON.parse(rawLine) as JsonRpcRequest;
|
|
340
363
|
} catch {
|
|
341
|
-
sendError(null,
|
|
364
|
+
sendError(null, JSON_RPC_PARSE_ERROR, 'Parse error');
|
|
342
365
|
continue;
|
|
343
366
|
}
|
|
344
367
|
void handleRequest(payload).catch((error) => {
|
|
345
368
|
const id = toJsonRpcId(payload.id);
|
|
346
369
|
const message = error instanceof Error ? error.message : 'Internal error';
|
|
347
|
-
sendError(id,
|
|
370
|
+
sendError(id, JSON_RPC_INTERNAL_ERROR, message);
|
|
348
371
|
});
|
|
349
372
|
}
|
|
350
373
|
};
|
|
@@ -358,5 +381,5 @@ const run = async (): Promise<void> => {
|
|
|
358
381
|
void run().catch((error) => {
|
|
359
382
|
const message = error instanceof Error ? error.message : 'Unknown MCP stdio bridge error';
|
|
360
383
|
process.stderr.write(`[pumuki-mcp-evidence-stdio] ${message}\n`);
|
|
361
|
-
process.exit(
|
|
384
|
+
process.exit(PROCESS_FAILURE_EXIT_CODE);
|
|
362
385
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.168",
|
|
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": {
|