fix(router-gate): stream A todowrite-verifier — unicode boundary for cyrillic mention patterns + DRY + tests

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-05-29 20:46:54 +03:00
parent 6ca4950eee
commit 86457dc8f5
2 changed files with 25 additions and 9 deletions
+10 -9
View File
@@ -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,
/(?<![\p{L}\p{N}_])вызови\s+([a-z][a-z0-9-]*)/giu,
/(?<![\p{L}\p{N}_])делай\s+([a-z][a-z0-9-]*)/giu,
];
function normSkill(name) {
@@ -26,13 +26,14 @@ export function extractSkillMentions(todoItems) {
const text = itemText(item);
const status = item.status || 'pending';
const found = new Set();
// superpowers: full names
for (const m of text.matchAll(/superpowers:[a-z-]+/g)) found.add(normSkill(m[0]));
// Skill(...)
for (const m of text.matchAll(/Skill\(([^)]+)\)/g)) found.add(normSkill(m[1]));
// invoke / вызови / делай <name>
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 });
}
+15
View File
@@ -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);
});
});