feat(router): подключить UTF-8 helper к трём хукам (stage 3 follow-up 1)

router-prehook, router-stop-gate, router-tool-gate теперь читают stdin
через readStdinAsUtf8 (StringDecoder). Русский в промпте корректно
доходит до Anthropic API и в state-файл — никаких mojibake типа
'посмотри'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-05-24 15:36:14 +03:00
parent d7d8c5edac
commit c7e02eeac9
6 changed files with 27 additions and 6 deletions
+2 -2
View File
@@ -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 || '';
+7
View File
@@ -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');
});
});
+2 -2
View File
@@ -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 || [];
+7
View File
@@ -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');
});
});
+2 -2
View File
@@ -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(); }
+7
View File
@@ -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');
});
});