pumuki 6.3.169 → 6.3.171

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,43 +6,6 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
- ## [6.3.169] - 2026-05-06
10
-
11
- ### Fixed
12
-
13
- - **Tracked evidence working-tree hygiene:** successful `pumuki-pre-commit` now restores tracked `.ai_evidence.json` from `HEAD` when the file was not staged at hook start, preventing pre-commit frameworks from failing with “files were modified by this hook” while still excluding evidence from code commits.
14
-
15
- ## [6.3.168] - 2026-05-06
16
-
17
- ### Fixed
18
-
19
- - **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.
20
-
21
- ## [6.3.167] - 2026-05-06
22
-
23
- ### Fixed
24
-
25
- - **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.
26
-
27
- ## [6.3.166] - 2026-05-06
28
-
29
- ### Fixed
30
-
31
- - **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`.
32
-
33
- ## [6.3.165] - 2026-05-06
34
-
35
- ### Fixed
36
-
37
- - **MCP evidence stdio PRE_PUSH:** los defaults de host, ruta, puerto y timeout quedan nombrados como constantes para cumplir reglas backend de configuración y evitar bloqueos del hook al publicar ramas de release.
38
-
39
- ## [6.3.164] - 2026-05-06
40
-
41
- ### Fixed
42
-
43
- - **PUMUKI-INC-130 XCTest helpers brownfield:** `trackForMemoryLeaks`/factory helpers that import `XCTest` but do not define `XCTestCase` suites or `test...` methods no longer trigger `skills.ios.critical-test-quality`.
44
- - **Skills runtime enforcement:** declarative skill registry rules stay visible in coverage metadata without being counted as missing runtime AST detectors; AUTO rules without detector mapping still fail closed.
45
-
46
9
  ## [6.3.143] - 2026-05-05
47
10
 
48
11
  ### Fixed
package/VERSION CHANGED
@@ -1 +1 @@
1
- v6.3.166
1
+ v6.3.171
@@ -683,46 +683,10 @@ func trackForMemoryLeaks(_ instance: AnyObject, file: StaticString = #filePath,
683
683
  addTeardownBlock { _ = instance }
684
684
  }
685
685
  `;
686
- const ruralGoHelperSource = `
687
- import XCTest
688
-
689
- final class WeakReference<T: AnyObject> {
690
- weak var value: T?
691
-
692
- init(_ value: T) {
693
- self.value = value
694
- }
695
- }
696
-
697
- func trackForMemoryLeaks(
698
- _ instance: AnyObject,
699
- testCase: XCTestCase,
700
- file: StaticString = #filePath,
701
- line: UInt = #line
702
- ) {
703
- nonisolated(unsafe) let reference = makeSUT(instance)
704
- testCase.addTeardownBlock {
705
- XCTAssertNil(
706
- reference.value,
707
- "Instance should have been deallocated. Potential memory leak.",
708
- file: file,
709
- line: line
710
- )
711
- }
712
- }
713
-
714
- private func makeSUT<T: AnyObject>(_ instance: T) -> WeakReference<T> {
715
- WeakReference(instance)
716
- }
717
- `;
718
686
 
719
687
  assert.equal(hasSwiftLegacyXCTestImportUsage(helperSource), false);
720
688
  assert.equal(hasSwiftModernizableXCTestSuiteUsage(helperSource), false);
721
689
  assert.equal(hasSwiftXCTestAssertionUsage(helperSource), false);
722
- assert.equal(hasSwiftLegacyXCTestImportUsage(ruralGoHelperSource), false);
723
- assert.equal(hasSwiftModernizableXCTestSuiteUsage(ruralGoHelperSource), false);
724
- assert.equal(hasSwiftXCTestAssertionUsage(ruralGoHelperSource), false);
725
- assert.equal(hasSwiftXCTUnwrapUsage(ruralGoHelperSource), false);
726
690
  });
727
691
 
728
692
  test('hasSwiftXCTUnwrapUsage detecta XCTUnwrap real y evita strings', () => {
@@ -840,14 +840,6 @@ const hasSwiftXCTestOnlyBrownfieldSuiteUsage = (source: string): boolean => {
840
840
  return !hasSwiftTestingImportUsage(source) && !hasSwiftTestingSuiteAttributeUsage(source);
841
841
  };
842
842
 
843
- const hasSwiftXCTestHelperOrFactoryUsage = (source: string): boolean => {
844
- return (
845
- hasSwiftXCTestImportUsage(source) &&
846
- !hasSwiftXCTestCaseSubclassUsage(source) &&
847
- !hasSwiftLegacyXCTestMethodUsage(source)
848
- );
849
- };
850
-
851
843
  export const hasSwiftLegacyXCTestImportUsage = (source: string): boolean => {
852
844
  if (!hasSwiftXCTestImportUsage(source)) {
853
845
  return false;
@@ -893,10 +885,6 @@ export const hasSwiftXCTestAssertionUsage = (source: string): boolean => {
893
885
  return false;
894
886
  }
895
887
 
896
- if (hasSwiftXCTestHelperOrFactoryUsage(source)) {
897
- return false;
898
- }
899
-
900
888
  if (hasSwiftXCTestOnlyBrownfieldSuiteUsage(source)) {
901
889
  return false;
902
890
  }
@@ -912,10 +900,6 @@ export const hasSwiftXCTUnwrapUsage = (source: string): boolean => {
912
900
  return false;
913
901
  }
914
902
 
915
- if (hasSwiftXCTestHelperOrFactoryUsage(source)) {
916
- return false;
917
- }
918
-
919
903
  if (hasSwiftXCTestOnlyBrownfieldSuiteUsage(source)) {
920
904
  return false;
921
905
  }
@@ -932,10 +916,6 @@ const hasSwiftConfirmationUsage = (source: string): boolean => {
932
916
  };
933
917
 
934
918
  export const hasSwiftWaitForExpectationsUsage = (source: string): boolean => {
935
- if (hasSwiftXCTestHelperOrFactoryUsage(source)) {
936
- return false;
937
- }
938
-
939
919
  const legacyWaitPattern = /\bwait\s*\(\s*for\s*:|\bwaitForExpectations\s*\(/;
940
920
  return collectSwiftFunctionDeclarations(source).some((declaration) => {
941
921
  if (!/\basync\b/.test(declaration.signature)) {
@@ -947,10 +927,6 @@ export const hasSwiftWaitForExpectationsUsage = (source: string): boolean => {
947
927
  };
948
928
 
949
929
  export const hasSwiftLegacyExpectationDescriptionUsage = (source: string): boolean => {
950
- if (hasSwiftXCTestHelperOrFactoryUsage(source)) {
951
- return false;
952
- }
953
-
954
930
  return collectSwiftFunctionDeclarations(source).some((declaration) => {
955
931
  if (!/\basync\b/.test(declaration.signature)) {
956
932
  return false;
@@ -4845,12 +4845,7 @@ const isRuntimeApiLiteral = (node: AstNode): boolean => {
4845
4845
  };
4846
4846
 
4847
4847
  const isNeutralHardcodedNumericLiteral = (node: AstNode): boolean => {
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
- );
4848
+ return node.type === 'NumericLiteral' && (node.value === 0 || node.value === 1);
4854
4849
  };
4855
4850
 
4856
4851
  const isBenignHardcodedConfigLiteral = (node: AstNode): boolean => {
@@ -8,15 +8,6 @@ const PACKAGE_ROOT = resolve(__dirname, '..', '..');
8
8
 
9
9
  let cachedCoreSkillsLock: SkillsLockV1 | undefined;
10
10
 
11
- const writeDebugFallback = (error: unknown): void => {
12
- if (process.env.PUMUKI_DEBUG !== '1') {
13
- return;
14
- }
15
-
16
- const message = error instanceof Error ? error.message : String(error);
17
- process.stderr.write(`[pumuki][skills-lock] compile fallback: ${message}\n`);
18
- };
19
-
20
11
  export const resolveCoreSkillsLockForPackageRoot = (
21
12
  packageRoot: string
22
13
  ): SkillsLockV1 | undefined => {
@@ -29,8 +20,7 @@ export const resolveCoreSkillsLockForPackageRoot = (
29
20
  if (compiledLock.bundles.length > 0) {
30
21
  return compiledLock;
31
22
  }
32
- } catch (error) {
33
- writeDebugFallback(error);
23
+ } catch {
34
24
  }
35
25
 
36
26
  return loadSkillsLock(packageRoot);
@@ -603,6 +603,7 @@ export const loadSkillsRuleSetForStage = (
603
603
  continue;
604
604
  }
605
605
  if (evaluationMode !== 'AUTO') {
606
+ unsupportedDetectorRuleIds.add(compiledRule.id);
606
607
  continue;
607
608
  }
608
609
  stageApplicableAutoRuleIds.add(compiledRule.id);
@@ -77,9 +77,6 @@ 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
-
83
80
  const PRE_COMMIT_EVIDENCE_MAX_AGE_SECONDS = 900;
84
81
  const PRE_PUSH_EVIDENCE_MAX_AGE_SECONDS = 1800;
85
82
  const HOOK_GATE_PROGRESS_REMINDER_MS = 2000;
@@ -523,7 +520,6 @@ const syncTrackedEvidenceAfterSuccessfulPreCommit = (params: {
523
520
  dependencies: StageRunnerDependencies;
524
521
  repoRoot: string;
525
522
  gateBlocked: boolean;
526
- evidenceWasStagedAtStart: boolean;
527
523
  }): boolean => {
528
524
  const evidenceAbsolutePath = join(params.repoRoot, EVIDENCE_FILE_PATH);
529
525
  if (!existsSync(evidenceAbsolutePath)) {
@@ -547,20 +543,6 @@ const syncTrackedEvidenceAfterSuccessfulPreCommit = (params: {
547
543
  params.dependencies.restorePathFromHead(params.repoRoot, EVIDENCE_FILE_PATH);
548
544
  return false;
549
545
  }
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} restored because it was not staged before PRE_COMMIT. ` +
558
- `Force previous behavior: PUMUKI_PRE_COMMIT_ALWAYS_RESTAGE_TRACKED_EVIDENCE=1\n`
559
- );
560
- }
561
- params.dependencies.restorePathFromHead(params.repoRoot, EVIDENCE_FILE_PATH);
562
- return false;
563
- }
564
546
  try {
565
547
  params.dependencies.stagePath(params.repoRoot, EVIDENCE_FILE_PATH);
566
548
  return false;
@@ -805,7 +787,6 @@ export async function runPreCommitStage(
805
787
  const activeDependencies = getDependencies(dependencies);
806
788
  const repoRoot = activeDependencies.resolveRepoRoot();
807
789
  const manifestSnapshot = captureManifestGuardSnapshot(repoRoot);
808
- const initiallyStagedPaths = activeDependencies.listStagedIndexPaths(repoRoot);
809
790
  activeDependencies.ensureRuntimeArtifactsIgnored(repoRoot);
810
791
  if (
811
792
  enforceGitAtomicityGate({
@@ -839,7 +820,6 @@ export async function runPreCommitStage(
839
820
  dependencies: activeDependencies,
840
821
  repoRoot,
841
822
  gateBlocked: result.exitCode !== 0,
842
- evidenceWasStagedAtStart: hasStagedEvidencePath(initiallyStagedPaths),
843
823
  })
844
824
  ) {
845
825
  return 1;
@@ -4,7 +4,7 @@ import { GitService, type IGitService } from '../git/GitService';
4
4
  import { hasAllowedExtension } from '../git/gitDiffUtils';
5
5
  import { runPlatformGate } from '../git/runPlatformGate';
6
6
  import { evaluatePlatformGateFindings } from '../git/runPlatformGateEvaluation';
7
- import { DEFAULT_FACT_FILE_EXTENSIONS } from '../git/runPlatformGateFacts';
7
+ import { DEFAULT_FACT_FILE_EXTENSIONS, type GateScope } from '../git/runPlatformGateFacts';
8
8
  import { resolvePolicyForStage, type ResolvedStagePolicy } from '../gate/stagePolicies';
9
9
 
10
10
  export type LifecycleAuditStage = 'PRE_WRITE' | 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
@@ -23,7 +23,7 @@ export type LifecycleAuditResult = {
23
23
  command: 'pumuki audit';
24
24
  repo_root: string;
25
25
  stage: LifecycleAuditStage;
26
- scope: { kind: 'repo' };
26
+ scope: { kind: 'repo' | 'staged'; staged_matching_extensions_count: number };
27
27
  audit_mode: 'gate' | 'engine';
28
28
  gate_exit_code: number;
29
29
  files_scanned: number | null;
@@ -70,6 +70,28 @@ const countUntrackedMatchingExtensions = (
70
70
  .filter((path) => hasAllowedExtension(path, extensions)).length;
71
71
  };
72
72
 
73
+ const collectStagedMatchingExtensions = (
74
+ git: Pick<IGitService, 'resolveRepoRoot' | 'runGit'>,
75
+ extensions: ReadonlyArray<string>
76
+ ): string[] => {
77
+ const repoRoot = git.resolveRepoRoot();
78
+ return git.runGit(['diff', '--cached', '--name-only'], repoRoot)
79
+ .split('\n')
80
+ .map((line) => line.trim())
81
+ .filter((line) => line.length > 0)
82
+ .filter((path) => hasAllowedExtension(path, extensions));
83
+ };
84
+
85
+ const resolveLifecycleAuditScope = (params: {
86
+ stage: LifecycleAuditStage;
87
+ stagedMatchingExtensions: ReadonlyArray<string>;
88
+ }): GateScope => {
89
+ if (params.stage === 'PRE_WRITE' && params.stagedMatchingExtensions.length > 0) {
90
+ return { kind: 'staged' };
91
+ }
92
+ return { kind: 'repo' };
93
+ };
94
+
73
95
  const isFindingBlocking = (finding: SnapshotFinding): boolean => {
74
96
  return finding.severity === 'CRITICAL' ||
75
97
  finding.severity === 'ERROR' ||
@@ -179,13 +201,18 @@ export const runLifecycleAudit = async (params: {
179
201
  );
180
202
  const extensions = DEFAULT_FACT_FILE_EXTENSIONS;
181
203
  const untrackedMatchingExtensionsCount = countUntrackedMatchingExtensions(git, extensions);
204
+ const stagedMatchingExtensions = collectStagedMatchingExtensions(git, extensions);
205
+ const scope = resolveLifecycleAuditScope({
206
+ stage: params.stage,
207
+ stagedMatchingExtensions,
208
+ });
182
209
 
183
210
  const gateParams =
184
211
  params.auditMode === 'engine'
185
212
  ? {
186
213
  policy: resolved.policy,
187
214
  policyTrace: resolved.trace,
188
- scope: { kind: 'repo' as const },
215
+ scope,
189
216
  silent: true,
190
217
  auditMode: 'engine' as const,
191
218
  dependencies: {
@@ -204,7 +231,7 @@ export const runLifecycleAudit = async (params: {
204
231
  : {
205
232
  policy: resolved.policy,
206
233
  policyTrace: resolved.trace,
207
- scope: { kind: 'repo' as const },
234
+ scope,
208
235
  silent: true,
209
236
  auditMode: 'gate' as const,
210
237
  };
@@ -229,7 +256,10 @@ export const runLifecycleAudit = async (params: {
229
256
  command: 'pumuki audit',
230
257
  repo_root: repoRoot,
231
258
  stage: params.stage,
232
- scope: { kind: 'repo' },
259
+ scope: {
260
+ kind: scope.kind === 'staged' ? 'staged' : 'repo',
261
+ staged_matching_extensions_count: stagedMatchingExtensions.length,
262
+ },
233
263
  audit_mode: params.auditMode,
234
264
  gate_exit_code: gateExitCode,
235
265
  files_scanned: filesScanned,
@@ -21,31 +21,6 @@ type JsonRpcResponse = {
21
21
  };
22
22
 
23
23
  const MCP_PROTOCOL_VERSION = '2024-11-05';
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';
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
- };
49
24
 
50
25
  const toJsonRpcId = (value: unknown): JsonRpcId => {
51
26
  if (typeof value === 'string' || typeof value === 'number' || value === null) {
@@ -60,7 +35,7 @@ const sendMessage = (message: JsonRpcResponse): void => {
60
35
 
61
36
  const sendResult = (id: JsonRpcId, result: unknown): void => {
62
37
  sendMessage({
63
- jsonrpc: JSON_RPC_VERSION,
38
+ jsonrpc: '2.0',
64
39
  id,
65
40
  result,
66
41
  });
@@ -68,7 +43,7 @@ const sendResult = (id: JsonRpcId, result: unknown): void => {
68
43
 
69
44
  const sendError = (id: JsonRpcId, code: number, message: string): void => {
70
45
  sendMessage({
71
- jsonrpc: JSON_RPC_VERSION,
46
+ jsonrpc: '2.0',
72
47
  id,
73
48
  error: {
74
49
  code,
@@ -77,10 +52,10 @@ const sendError = (id: JsonRpcId, code: number, message: string): void => {
77
52
  });
78
53
  };
79
54
 
80
- const canConnectToAddress = async (host: string, port: number): Promise<boolean> =>
55
+ const isPortInUse = async (host: string, port: number): Promise<boolean> =>
81
56
  await new Promise((resolve) => {
82
57
  const socket = new Socket();
83
- socket.setTimeout(PORT_PROBE_TIMEOUT_MS);
58
+ socket.setTimeout(600);
84
59
  socket.once('connect', () => {
85
60
  socket.destroy();
86
61
  resolve(true);
@@ -96,14 +71,13 @@ const canConnectToAddress = async (host: string, port: number): Promise<boolean>
96
71
  socket.connect(port, host);
97
72
  });
98
73
 
99
- const findAvailableListenerNumber = async (host: string): Promise<number> =>
74
+ const findEphemeralPort = async (host: string): Promise<number> =>
100
75
  await new Promise((resolve, reject) => {
101
76
  const probe = createServer();
102
77
  probe.once('error', reject);
103
- probe.listen(EPHEMERAL_LISTENER_PORT, host, () => {
78
+ probe.listen(0, host, () => {
104
79
  const address = probe.address();
105
- const port =
106
- address && typeof address === 'object' ? address.port : EPHEMERAL_LISTENER_PORT;
80
+ const port = address && typeof address === 'object' ? address.port : 0;
107
81
  probe.close(() => resolve(port));
108
82
  });
109
83
  });
@@ -111,62 +85,45 @@ const findAvailableListenerNumber = async (host: string): Promise<number> =>
111
85
  const fetchJson = async (url: string): Promise<unknown> => {
112
86
  const response = await fetch(url);
113
87
  const text = await response.text();
114
- if (text.trim().length === EMPTY_TEXT_LENGTH) {
88
+ if (text.trim().length === 0) {
115
89
  return {};
116
90
  }
117
91
  return JSON.parse(text) as unknown;
118
92
  };
119
93
 
120
- const writeDebugHealthProbeFailure = (error: unknown): void => {
121
- if (process.env.PUMUKI_DEBUG !== '1') {
122
- return;
123
- }
124
-
125
- const message = error instanceof Error ? error.message : String(error);
126
- process.stderr.write(`[pumuki-mcp-evidence-stdio] health probe fallback: ${message}\n`);
127
- };
128
-
129
94
  const startOrReuseEvidenceHttp = async (): Promise<{
130
95
  host: string;
131
96
  port: number;
132
97
  route: string;
133
98
  }> => {
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`;
99
+ const host = process.env.PUMUKI_EVIDENCE_HOST ?? '127.0.0.1';
100
+ const route = process.env.PUMUKI_EVIDENCE_ROUTE ?? '/ai-evidence';
101
+ const parsedPort = Number.parseInt(process.env.PUMUKI_EVIDENCE_PORT ?? '', 10);
102
+ const preferredPort = Number.isFinite(parsedPort) ? parsedPort : 7341;
103
+ const requestedPort = preferredPort > 0 ? preferredPort : await findEphemeralPort(host);
104
+ const healthUrl = `http://${host}:${requestedPort}/health`;
146
105
 
147
106
  try {
148
107
  const health = (await fetchJson(healthUrl)) as { status?: string };
149
108
  if (health.status === 'ok') {
150
- return { host, port: requestedListener, route };
109
+ return { host, port: requestedPort, route };
151
110
  }
152
- } catch (error) {
153
- writeDebugHealthProbeFailure(error);
111
+ } catch {
112
+ // ignored
154
113
  }
155
114
 
156
- const listenerInUse = await canConnectToAddress(host, requestedListener);
157
- const resolvedListener = listenerInUse
158
- ? await findAvailableListenerNumber(host)
159
- : requestedListener;
115
+ const portInUse = await isPortInUse(host, requestedPort);
116
+ const resolvedPort = portInUse ? await findEphemeralPort(host) : requestedPort;
160
117
  startEvidenceContextServer({
161
118
  host,
162
- port: resolvedListener,
119
+ port: resolvedPort,
163
120
  route,
164
121
  repoRoot: process.cwd(),
165
122
  });
166
123
 
167
124
  return {
168
125
  host,
169
- port: resolvedListener,
126
+ port: resolvedPort,
170
127
  route,
171
128
  };
172
129
  };
@@ -234,8 +191,8 @@ const run = async (): Promise<void> => {
234
191
  ] as const;
235
192
 
236
193
  const handleRequest = async (request: JsonRpcRequest): Promise<void> => {
237
- if (request.jsonrpc !== JSON_RPC_VERSION) {
238
- sendError(toJsonRpcId(request.id), JSON_RPC_INVALID_REQUEST, 'Invalid JSON-RPC version.');
194
+ if (request.jsonrpc !== '2.0') {
195
+ sendError(toJsonRpcId(request.id), -32600, 'Invalid JSON-RPC version.');
239
196
  return;
240
197
  }
241
198
 
@@ -251,7 +208,7 @@ const run = async (): Promise<void> => {
251
208
  protocolVersion: MCP_PROTOCOL_VERSION,
252
209
  serverInfo: {
253
210
  name: 'pumuki-evidence-stdio',
254
- version: TOOL_IMPLEMENTATION_VERSION,
211
+ version: '1.0.0',
255
212
  },
256
213
  capabilities: {
257
214
  tools: {
@@ -292,7 +249,7 @@ const run = async (): Promise<void> => {
292
249
  const name = typeof params.name === 'string' ? params.name : '';
293
250
  const tool = toolsCatalog.find((entry) => entry.name === name);
294
251
  if (!tool) {
295
- sendError(id, JSON_RPC_INVALID_PARAMS, `Unknown tool: ${name}`);
252
+ sendError(id, -32602, `Unknown tool: ${name}`);
296
253
  return;
297
254
  }
298
255
  const payload = await fetchJson(`${baseUrl}${tool.path}`);
@@ -327,7 +284,7 @@ const run = async (): Promise<void> => {
327
284
  const uri = typeof params.uri === 'string' ? params.uri : '';
328
285
  const resource = resourcesCatalog.find((entry) => entry.uri === uri);
329
286
  if (!resource) {
330
- sendError(id, JSON_RPC_INVALID_PARAMS, `Unknown resource URI: ${uri}`);
287
+ sendError(id, -32602, `Unknown resource URI: ${uri}`);
331
288
  return;
332
289
  }
333
290
  const payload = await fetchJson(`${baseUrl}${resource.path}`);
@@ -343,31 +300,31 @@ const run = async (): Promise<void> => {
343
300
  return;
344
301
  }
345
302
 
346
- sendError(id, JSON_RPC_METHOD_NOT_FOUND, `Method not found: ${method}`);
303
+ sendError(id, -32601, `Method not found: ${method}`);
347
304
  };
348
305
 
349
306
  const processBuffer = (): void => {
350
307
  while (true) {
351
308
  const lineEnd = textBuffer.indexOf('\n');
352
- if (lineEnd === LINE_BREAK_NOT_FOUND) {
309
+ if (lineEnd === -1) {
353
310
  return;
354
311
  }
355
312
  const rawLine = textBuffer.slice(0, lineEnd).trim();
356
- textBuffer = textBuffer.slice(lineEnd + LINE_BREAK_WIDTH);
357
- if (rawLine.length === EMPTY_TEXT_LENGTH) {
313
+ textBuffer = textBuffer.slice(lineEnd + 1);
314
+ if (rawLine.length === 0) {
358
315
  continue;
359
316
  }
360
317
  let payload: JsonRpcRequest;
361
318
  try {
362
319
  payload = JSON.parse(rawLine) as JsonRpcRequest;
363
320
  } catch {
364
- sendError(null, JSON_RPC_PARSE_ERROR, 'Parse error');
321
+ sendError(null, -32700, 'Parse error');
365
322
  continue;
366
323
  }
367
324
  void handleRequest(payload).catch((error) => {
368
325
  const id = toJsonRpcId(payload.id);
369
326
  const message = error instanceof Error ? error.message : 'Internal error';
370
- sendError(id, JSON_RPC_INTERNAL_ERROR, message);
327
+ sendError(id, -32603, message);
371
328
  });
372
329
  }
373
330
  };
@@ -381,5 +338,5 @@ const run = async (): Promise<void> => {
381
338
  void run().catch((error) => {
382
339
  const message = error instanceof Error ? error.message : 'Unknown MCP stdio bridge error';
383
340
  process.stderr.write(`[pumuki-mcp-evidence-stdio] ${message}\n`);
384
- process.exit(PROCESS_FAILURE_EXIT_CODE);
341
+ process.exit(1);
385
342
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.169",
3
+ "version": "6.3.171",
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": {