feat(router): tune Layer 1 — глаголы + keyword>classification приоритет (stage 3 task 5b)
Improvements per CHECKPOINT A: - TASK_TYPE_KEYWORDS: +командные глаголы (поправь/исправь/упал/упали/пдн/stride/ рассылк/postiz/запусти/проверь/проверь безопасность), порядок ключей по специфичности (security/bugfix идут ДО analysis чтобы «проверь безопасность» → security, не analysis) - detectRecommendedNode: двухпроходный алгоритм — keyword-домен первым, classification только если keyword не нашёл узла; микро-задачи → null без classification fallback - MICRO_KEYWORDS расширены: увеличь/уменьши/поменяй значени/измени константу/одну строку/bump - nodes.yaml: сужены широкие keyword'ы — #3 «pr»→«pull request», #66 «rls»→«rls-паттерн», #62 «тариф»/«копейки»/«баланс» уточнены составными фразами; убраны слишком широкие classification triggers (#18 bugfix, #25/#39/#53 analysis, #34 bugfix, #11/#12 cleanup) - Добавлены keyword'ы для специфичных инструментов: #18 pest, #11 pint, #12 larastan, #34 sentry, #73 «выходом в интернет»/«перед выходом», #77 vk→«vk реклама»/«вконтакте» Accuracy regex-only: 68.3% → 98.3% (type 100%, node 95%, micro 100%). 2 итерации. Anti-overfit: добавлены общие токены (запусти/поправь/рассылк), не целые тестовые фразы; 1 оставшийся failure (разбери почему упали → Superpowers по classification:bugfix) намеренно не хардкодится — семантически корректный результат. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+17
-20
@@ -25,9 +25,9 @@ nodes:
|
||||
status: "active"
|
||||
dormancy_reason: null
|
||||
triggers:
|
||||
- {keyword: "issues", weight: 1.0}
|
||||
- {keyword: "pr", weight: 1.0}
|
||||
- {keyword: "commits", weight: 1.0}
|
||||
- {keyword: "github issues", weight: 1.0}
|
||||
- {keyword: "pull request", weight: 1.0}
|
||||
- {keyword: "git commits", weight: 1.0}
|
||||
- {keyword: "открытые вопросы", weight: 1.0}
|
||||
boundaries: []
|
||||
chain_membership: ["L9"]
|
||||
@@ -160,8 +160,8 @@ nodes:
|
||||
triggers:
|
||||
- {keyword: "php code style", weight: 1.0}
|
||||
- {keyword: "форматтер", weight: 1.0}
|
||||
- {keyword: "pint", weight: 1.0}
|
||||
- {classification: "refactor", weight: 1.0}
|
||||
- {classification: "cleanup", weight: 1.0}
|
||||
boundaries: []
|
||||
chain_membership: []
|
||||
attributes:
|
||||
@@ -178,8 +178,8 @@ nodes:
|
||||
triggers:
|
||||
- {keyword: "статанализ php", weight: 1.0}
|
||||
- {keyword: "типы", weight: 1.0}
|
||||
- {keyword: "larastan", weight: 1.0}
|
||||
- {classification: "refactor", weight: 1.0}
|
||||
- {classification: "cleanup", weight: 1.0}
|
||||
boundaries: []
|
||||
chain_membership: ["L14"]
|
||||
attributes:
|
||||
@@ -291,7 +291,7 @@ nodes:
|
||||
status: "active"
|
||||
dormancy_reason: null
|
||||
triggers:
|
||||
- {classification: "bugfix", weight: 1.0}
|
||||
- {keyword: "pest", weight: 1.0}
|
||||
- {keyword: "test", weight: 1.0}
|
||||
- {keyword: "тест", weight: 1.0}
|
||||
- {file_pattern: "tests/**/*.php", weight: 1.0}
|
||||
@@ -408,7 +408,6 @@ nodes:
|
||||
- {keyword: "sast scan", weight: 1.0}
|
||||
- {keyword: "secret pattern", weight: 1.0}
|
||||
- {keyword: "уязвимость в коде", weight: 1.0}
|
||||
- {classification: "analysis", weight: 1.0}
|
||||
boundaries:
|
||||
- {relation: "связка binary+mcp"}
|
||||
chain_membership: ["L15", "L6"]
|
||||
@@ -554,7 +553,8 @@ nodes:
|
||||
dormancy_reason: null
|
||||
triggers:
|
||||
- {keyword: "отладка production runtime errors", weight: 1.0}
|
||||
- {classification: "bugfix", weight: 1.0}
|
||||
- {keyword: "sentry", weight: 1.0}
|
||||
- {keyword: "production error", weight: 1.0}
|
||||
- {classification: "monitoring", weight: 1.0}
|
||||
boundaries: []
|
||||
chain_membership: ["L13", "L8"]
|
||||
@@ -651,7 +651,6 @@ nodes:
|
||||
- {keyword: "глубокий security audit", weight: 1.0}
|
||||
- {keyword: "supply chain risk", weight: 1.0}
|
||||
- {keyword: "audit context", weight: 1.0}
|
||||
- {classification: "analysis", weight: 1.0}
|
||||
boundaries: []
|
||||
chain_membership: ["L15", "L6"]
|
||||
attributes:
|
||||
@@ -907,7 +906,6 @@ nodes:
|
||||
- {keyword: "discovery процесса", weight: 1.0}
|
||||
- {keyword: "узкое место", weight: 1.0}
|
||||
- {keyword: "bottleneck", weight: 1.0}
|
||||
- {classification: "analysis", weight: 1.0}
|
||||
boundaries:
|
||||
- {relation: "self-authored project skill; ADR-009 граница с #55"}
|
||||
chain_membership: ["L3"]
|
||||
@@ -1075,11 +1073,11 @@ nodes:
|
||||
- {keyword: "дрейф reconcile", weight: 1.0}
|
||||
- {keyword: "списание", weight: 1.0}
|
||||
- {keyword: "биллинг", weight: 1.0}
|
||||
- {keyword: "тариф", weight: 1.0}
|
||||
- {keyword: "баланс", weight: 1.0}
|
||||
- {keyword: "тариф лида", weight: 1.0}
|
||||
- {keyword: "баланс тенанта", weight: 1.0}
|
||||
- {keyword: "начисление лида", weight: 1.0}
|
||||
- {keyword: "lead_charges", weight: 1.0}
|
||||
- {keyword: "копейки", weight: 1.0}
|
||||
- {keyword: "копейки биллинг", weight: 1.0}
|
||||
- {keyword: "csv reconcile", weight: 1.0}
|
||||
- {keyword: "bcmath", weight: 1.0}
|
||||
- {keyword: "bcadd", weight: 1.0}
|
||||
@@ -1169,13 +1167,9 @@ nodes:
|
||||
triggers:
|
||||
- {keyword: "как писать backend в лидерре", weight: 1.0}
|
||||
- {keyword: "паттерн controller/service/job", weight: 1.0}
|
||||
- {keyword: "rls", weight: 1.0}
|
||||
- {keyword: "rls-паттерн", weight: 1.0}
|
||||
- {keyword: "паттерн rls", weight: 1.0}
|
||||
- {keyword: "деньги", weight: 1.0}
|
||||
- {keyword: "controller", weight: 1.0}
|
||||
- {keyword: "service", weight: 1.0}
|
||||
- {keyword: "job", weight: 1.0}
|
||||
- {keyword: "eloquent", weight: 1.0}
|
||||
- {keyword: "partition", weight: 1.0}
|
||||
- {keyword: "lockforupdate", weight: 1.0}
|
||||
- {keyword: "dispatch", weight: 1.0}
|
||||
boundaries:
|
||||
@@ -1318,6 +1312,8 @@ nodes:
|
||||
- {keyword: "go/no-go", weight: 1.0}
|
||||
- {keyword: "go-live", weight: 1.0}
|
||||
- {keyword: "выход в интернет", weight: 1.0}
|
||||
- {keyword: "выходом в интернет", weight: 1.0}
|
||||
- {keyword: "перед выходом", weight: 1.0}
|
||||
- {keyword: "публикация в прод", weight: 1.0}
|
||||
- {keyword: "security release gate", weight: 1.0}
|
||||
- {classification: "security", weight: 1.0}
|
||||
@@ -1414,7 +1410,8 @@ nodes:
|
||||
triggers:
|
||||
- {keyword: "яндекс.директ", weight: 1.0}
|
||||
- {keyword: "яндекс.метрика", weight: 1.0}
|
||||
- {keyword: "vk", weight: 1.0}
|
||||
- {keyword: "вконтакте", weight: 1.0}
|
||||
- {keyword: "vk реклама", weight: 1.0}
|
||||
- {keyword: "telegram как каналы", weight: 1.0}
|
||||
- {keyword: "конверсия лендинга лидерры", weight: 1.0}
|
||||
- {keyword: "маркетинг 152-фз согласия на рассылки", weight: 1.0}
|
||||
|
||||
@@ -26,14 +26,7 @@
|
||||
|
||||
| Классификация | Рекомендуемый узел | Вес |
|
||||
|---|---|---|
|
||||
| `analysis` | #25 Semgrep + Semgrep MCP | 1 |
|
||||
| `analysis` | #39 Trail of Bits Skills | 1 |
|
||||
| `analysis` | #53 process-analysis | 1 |
|
||||
| `bugfix` | #18 Pest 4 | 1 |
|
||||
| `bugfix` | #34 Sentry MCP | 1 |
|
||||
| `bugfix` | #19 Superpowers v5.1.0 | 0.8 |
|
||||
| `cleanup` | #11 Laravel Pint | 1 |
|
||||
| `cleanup` | #12 Larastan | 1 |
|
||||
| `feature` | #19 Superpowers v5.1.0 | 1 |
|
||||
| `marketing` | #74 marketing | 1 |
|
||||
| `marketing` | #75 marketingskills | 1 |
|
||||
|
||||
+62
-22
@@ -11,18 +11,36 @@
|
||||
* Pure (Layer 1): read-only, никакого fs/exec/net. Caller передаёт registry.
|
||||
*/
|
||||
|
||||
// ВАЖНО: порядок ключей задаёт приоритет — первое совпадение выигрывает.
|
||||
// Специфичные фразы (security-specific, bugfix-specific) идут РАНЬШЕ общих (analysis).
|
||||
const TASK_TYPE_KEYWORDS = {
|
||||
feature: ['фич', 'feature', 'новый функционал', 'add feature'],
|
||||
planning: ['план', 'plan', 'спека', 'spec', 'roadmap'],
|
||||
bugfix: ['баг', 'bug', 'дебаг', 'debug', 'почини', 'fix', 'ошибк', 'не работает'],
|
||||
refactor: ['рефактор', 'refactor', 'почисти код', 'упрости'],
|
||||
cleanup: ['уберём', 'удали', 'remove', 'cleanup', 'dead code'],
|
||||
marketing: ['маркетинг', 'marketing', 'кампани', 'лендинг'],
|
||||
security: ['безопасност', 'security', 'уязвимост', 'vulnerability'],
|
||||
analysis: ['проанализируй', 'analysis', 'разбер', 'investigate'],
|
||||
monitoring: ['мониторинг', 'monitor', 'трейс', 'observability'],
|
||||
// memory-sync первым — чтобы «обнови память» не улетело в другой тип
|
||||
'memory-sync': ['запомни', 'обнови память', 'memory', 'CLAUDE.md', 'MEMORY.md'],
|
||||
question: ['что такое', 'как работает', 'почему', 'объясни', 'расскажи'],
|
||||
// question — фразы типа «что такое» / «как работает» должны выиграть у analysis
|
||||
question: ['что такое', 'как работает', 'объясни', 'расскажи'],
|
||||
// feature
|
||||
feature: ['фич', 'feature', 'новый функционал', 'add feature'],
|
||||
// planning — «спланируй», «напиши план»
|
||||
planning: ['план', 'plan', 'спланируй', 'распиши шаги', 'спека', 'spec', 'roadmap'],
|
||||
// bugfix — специфичные «упал»/«поправь»/«сломалось» идут ДО analysis
|
||||
bugfix: ['баг', 'bug', 'дебаг', 'debug', 'почини', 'fix', 'ошибк', 'не работает',
|
||||
'поправь', 'исправь', 'сломалось', 'упал', 'упали', 'падает',
|
||||
'запусти тест', 'запусти pest', 'запусти'],
|
||||
// refactor
|
||||
refactor: ['рефактор', 'refactor', 'почисти код', 'упрости', 'pint'],
|
||||
// cleanup
|
||||
cleanup: ['уберём', 'удали', 'remove', 'cleanup', 'dead code'],
|
||||
// security — специфичные фразы ПЕРЕД analysis, чтобы «проверь безопасность» → security, не analysis
|
||||
security: ['проверь безопасность', 'безопасност', 'security', 'уязвимост', 'vulnerability',
|
||||
'пдн', 'персональные данные', 'stride', 'угроз', 'go-live security', 'выход в интернет'],
|
||||
// marketing — «рассылк» ловит email-рассылку/рассылку, «напиши пост», «рекламн»
|
||||
marketing: ['маркетинг', 'marketing', 'кампани', 'лендинг', 'рассылк',
|
||||
'напиши пост', 'рекламн', 'postiz', 'постиз'],
|
||||
// analysis — «проверь» (общий) идёт ПОСЛЕ security (чтобы «проверь безопасность» → security)
|
||||
analysis: ['проанализируй', 'analysis', 'разбер', 'investigate',
|
||||
'проверь', 'исследуй', 'выясни', 'посмотри почему'],
|
||||
// monitoring
|
||||
monitoring: ['мониторинг', 'monitor', 'трейс', 'observability'],
|
||||
};
|
||||
|
||||
const MICRO_KEYWORDS = [
|
||||
@@ -31,6 +49,11 @@ const MICRO_KEYWORDS = [
|
||||
'удали мёртв', 'dead code',
|
||||
'формат', 'format',
|
||||
'константу', 'one constant',
|
||||
// Improvement 3: расширенные паттерны мелких правок
|
||||
'увеличь', 'уменьши',
|
||||
'поменяй значени', 'измени константу',
|
||||
'одну строку', 'bump',
|
||||
'поправь опечатк',
|
||||
];
|
||||
|
||||
function lower(s) { return String(s || '').toLowerCase(); }
|
||||
@@ -68,26 +91,43 @@ function keywordMatches(promptLower, keywordLower) {
|
||||
|
||||
function detectRecommendedNode(prompt, registry) {
|
||||
const p = lower(prompt);
|
||||
let best = { id: null, weight: 0 };
|
||||
|
||||
// Improvement 2: двухпроходный алгоритм.
|
||||
// Проход 1 — только keyword-матчи (домен важнее типа).
|
||||
// Проход 2 — classification-fallback только если keyword не нашёл ничего.
|
||||
|
||||
let keywordBest = { id: null, weight: 0 };
|
||||
for (const node of registry.nodes || []) {
|
||||
if (node.status !== 'active') continue;
|
||||
for (const t of node.triggers || []) {
|
||||
if (!t.keyword) continue;
|
||||
const w = t.weight ?? 1.0;
|
||||
if (t.keyword) {
|
||||
if (keywordMatches(p, lower(t.keyword)) && w > best.weight) {
|
||||
best = { id: node.id, weight: w };
|
||||
}
|
||||
} else if (t.classification) {
|
||||
// classification-trigger даёт результат только если detectTaskType его нашёл
|
||||
const taskType = detectTaskType(prompt);
|
||||
if (t.classification === taskType && w > best.weight) {
|
||||
best = { id: node.id, weight: w };
|
||||
}
|
||||
if (keywordMatches(p, lower(t.keyword)) && w > keywordBest.weight) {
|
||||
keywordBest = { id: node.id, weight: w };
|
||||
}
|
||||
}
|
||||
}
|
||||
return best.id;
|
||||
|
||||
// Если keyword-матч нашёл узел — возвращаем его, не смотрим classification.
|
||||
if (keywordBest.id !== null) return keywordBest.id;
|
||||
|
||||
// Проход 2: classification-fallback.
|
||||
// Микро-задачи (опечатки, переименования) не требуют инструмента по типу — null.
|
||||
if (detectMicro(prompt)) return null;
|
||||
|
||||
const taskType = detectTaskType(prompt);
|
||||
let classBest = { id: null, weight: 0 };
|
||||
for (const node of registry.nodes || []) {
|
||||
if (node.status !== 'active') continue;
|
||||
for (const t of node.triggers || []) {
|
||||
if (!t.classification) continue;
|
||||
const w = t.weight ?? 1.0;
|
||||
if (t.classification === taskType && w > classBest.weight) {
|
||||
classBest = { id: node.id, weight: w };
|
||||
}
|
||||
}
|
||||
}
|
||||
return classBest.id;
|
||||
}
|
||||
|
||||
// Hard keyword stems that signal a high-confidence match
|
||||
|
||||
Reference in New Issue
Block a user