From c7e02eeac968feee53745787c289d9ad46e3ae5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Sun, 24 May 2026 15:36:14 +0300 Subject: [PATCH] =?UTF-8?q?feat(router):=20=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB?= =?UTF-8?q?=D1=8E=D1=87=D0=B8=D1=82=D1=8C=20UTF-8=20helper=20=D0=BA=20?= =?UTF-8?q?=D1=82=D1=80=D1=91=D0=BC=20=D1=85=D1=83=D0=BA=D0=B0=D0=BC=20(st?= =?UTF-8?q?age=203=20follow-up=201)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit router-prehook, router-stop-gate, router-tool-gate теперь читают stdin через readStdinAsUtf8 (StringDecoder). Русский в промпте корректно доходит до Anthropic API и в state-файл — никаких mojibake типа 'посмотри'. Co-Authored-By: Claude Sonnet 4.6 --- tools/router-prehook.mjs | 4 ++-- tools/router-prehook.test.mjs | 7 +++++++ tools/router-stop-gate.mjs | 4 ++-- tools/router-stop-gate.test.mjs | 7 +++++++ tools/router-tool-gate.mjs | 4 ++-- tools/router-tool-gate.test.mjs | 7 +++++++ 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/tools/router-prehook.mjs b/tools/router-prehook.mjs index 748ad6ca..5fd623be 100644 --- a/tools/router-prehook.mjs +++ b/tools/router-prehook.mjs @@ -20,6 +20,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'; import { join, dirname } from 'path'; import { homedir } from 'os'; import { fileURLToPath } from 'url'; +import { readStdinAsUtf8 } from './router-stdin-helper.mjs'; const ENFORCEMENT_TYPES = new Set(['feature', 'planning', 'bugfix', 'refactor', 'cleanup', 'marketing', 'security', 'analysis', 'monitoring']); @@ -54,8 +55,7 @@ function stateFilePath(sessionId) { } async function main() { - let input = ''; - for await (const chunk of process.stdin) input += chunk; + const input = await readStdinAsUtf8(process.stdin); const event = JSON.parse(input || '{}'); const sessionId = event.session_id || 'unknown'; const userPrompt = event.prompt || event.user_prompt || ''; diff --git a/tools/router-prehook.test.mjs b/tools/router-prehook.test.mjs index 3dc1ae49..f77df3a5 100644 --- a/tools/router-prehook.test.mjs +++ b/tools/router-prehook.test.mjs @@ -48,3 +48,10 @@ describe('isEnforcementRequired', () => { expect(isEnforcementRequired({ taskType: 'memory-sync', micro: false, recommendedNode: '#33' })).toBe(false); }); }); + +describe('UTF-8 cyrillic stdin (regression — Stage 3 fix 1)', () => { + it('module loads with UTF-8 helper wired (smoke)', async () => { + const mod = await import('./router-prehook.mjs'); + expect(typeof mod.buildStateFromClassification).toBe('function'); + }); +}); diff --git a/tools/router-stop-gate.mjs b/tools/router-stop-gate.mjs index 5f1ac9ac..9bdc0d90 100644 --- a/tools/router-stop-gate.mjs +++ b/tools/router-stop-gate.mjs @@ -11,6 +11,7 @@ import { readFileSync, writeFileSync, existsSync } from 'fs'; import { join } from 'path'; import { homedir } from 'os'; import { fileURLToPath } from 'url'; +import { readStdinAsUtf8 } from './router-stdin-helper.mjs'; export function extractSkillInvocations(events) { return (events || []) @@ -46,8 +47,7 @@ export function updateChainProgress(state, skillsInvoked, chains) { } async function main() { - let input = ''; - for await (const chunk of process.stdin) input += chunk; + const input = await readStdinAsUtf8(process.stdin); const event = JSON.parse(input || '{}'); const sessionId = event.session_id || 'unknown'; const events = event.turn_events || []; diff --git a/tools/router-stop-gate.test.mjs b/tools/router-stop-gate.test.mjs index ba2de30e..49d8bc1a 100644 --- a/tools/router-stop-gate.test.mjs +++ b/tools/router-stop-gate.test.mjs @@ -57,3 +57,10 @@ describe('updateChainProgress', () => { expect(updated.chainProgress).toEqual(['brainstorming', 'writing-plans']); }); }); + +describe('UTF-8 cyrillic stdin (regression — Stage 3 fix 1)', () => { + it('module loads with UTF-8 helper wired (smoke)', async () => { + const mod = await import('./router-stop-gate.mjs'); + expect(typeof mod.updateChainProgress).toBe('function'); + }); +}); diff --git a/tools/router-tool-gate.mjs b/tools/router-tool-gate.mjs index 6efabfe4..0de58591 100644 --- a/tools/router-tool-gate.mjs +++ b/tools/router-tool-gate.mjs @@ -86,8 +86,7 @@ function readState(sessionId) { } async function main() { - let input = ''; - for await (const chunk of process.stdin) input += chunk; + const input = await readStdinAsUtf8(process.stdin); const event = JSON.parse(input || '{}'); const sessionId = event.session_id || 'unknown'; const tool = event.tool_name; @@ -109,4 +108,5 @@ async function main() { // CLI guard — Windows-cyrillic quirk: use fileURLToPath(import.meta.url) import { fileURLToPath } from 'url'; +import { readStdinAsUtf8 } from './router-stdin-helper.mjs'; if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) { main(); } diff --git a/tools/router-tool-gate.test.mjs b/tools/router-tool-gate.test.mjs index 689b94b3..476c691b 100644 --- a/tools/router-tool-gate.test.mjs +++ b/tools/router-tool-gate.test.mjs @@ -97,3 +97,10 @@ describe('decideDecision', () => { expect(r.warning).toMatch(/#19/); }); }); + +describe('UTF-8 cyrillic stdin (regression — Stage 3 fix 1)', () => { + it('module loads with UTF-8 helper wired (smoke)', async () => { + const mod = await import('./router-tool-gate.mjs'); + expect(typeof mod.shouldBlock).toBe('function'); + }); +});