From 86457dc8f5b2b6bb1f79fe148ce3e85bce7e534a 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: Fri, 29 May 2026 20:46:54 +0300 Subject: [PATCH] =?UTF-8?q?fix(router-gate):=20stream=20A=20todowrite-veri?= =?UTF-8?q?fier=20=E2=80=94=20unicode=20boundary=20for=20cyrillic=20mentio?= =?UTF-8?q?n=20patterns=20+=20DRY=20+=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- tools/todowrite-skill-verifier.mjs | 19 ++++++++++--------- tools/todowrite-skill-verifier.test.mjs | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/tools/todowrite-skill-verifier.mjs b/tools/todowrite-skill-verifier.mjs index 7b0c12db..38765d70 100644 --- a/tools/todowrite-skill-verifier.mjs +++ b/tools/todowrite-skill-verifier.mjs @@ -8,8 +8,8 @@ export const SKILL_MENTION_PATTERNS = [ /superpowers:[a-z-]+/g, /Skill\(([^)]+)\)/g, /\binvoke\s+([a-z][a-z0-9-]*)/gi, - /\bвызови\s+([a-z][a-z0-9-]*)/giu, - /\bделай\s+([a-z][a-z0-9-]*)/giu, + /(? - for (const re of [/\binvoke\s+([a-z][a-z0-9-]*)/gi, /\bвызови\s+([a-z][a-z0-9-]*)/giu, /\bделай\s+([a-z][a-z0-9-]*)/giu]) { - for (const m of text.matchAll(re)) found.add(normSkill(m[1])); + for (const pat of SKILL_MENTION_PATTERNS) { + const re = new RegExp(pat.source, pat.flags); + for (const m of String(text).matchAll(re)) { + // superpowers: and Skill() use m[0] / m[1] respectively + // invoke/вызови/делай capture group 1 for skill name + const val = m[1] !== undefined ? m[1] : m[0]; + found.add(normSkill(val)); + } } for (const skill_name of found) out.push({ skill_name, text, status }); } diff --git a/tools/todowrite-skill-verifier.test.mjs b/tools/todowrite-skill-verifier.test.mjs index ec2c78f9..12c14966 100644 --- a/tools/todowrite-skill-verifier.test.mjs +++ b/tools/todowrite-skill-verifier.test.mjs @@ -103,3 +103,18 @@ describe('extractSkillMentions — dedup', () => { expect(names.filter((n) => n === 'superpowers:writing-plans').length).toBe(1); }); }); + +describe('extractSkillMentions — cyrillic patterns', () => { + it('вызови pattern detects skill mention', () => { + const m = extractSkillMentions([{ content: 'вызови brain-retro', status: 'pending' }]); + expect(m.some((x) => x.skill_name === 'brain-retro')).toBe(true); + }); + it('делай pattern detects skill mention', () => { + const m = extractSkillMentions([{ content: 'делай subagent-driven-development', status: 'pending' }]); + expect(m.some((x) => x.skill_name === 'subagent-driven-development')).toBe(true); + }); + it('no match inside longer word (перевызови)', () => { + const m = extractSkillMentions([{ content: 'перевызови foo', status: 'pending' }]); + expect(m.some((x) => x.skill_name === 'foo')).toBe(false); + }); +});