b739d5adad
Болезни B (роутер в пустоту) + A (наставник не заворачивал) — лечение Р7/Р8 (Подход 1): наставник — единый мозг-рецензент, зовёт classify() как функцию (3 слоя + граф nodes.yaml + карточки — код не тронут, новый вызыватель), судит спеку+план+выбор скилов, заворачивает NO-GO. - validateMentorVerdict + промпты (план/спека): явное decision GO|NO-GO (поглощённый Р7) - plan-skills.mjs: parsePlanSkills (skills-json) + extractPlanGoal (зеркало extractGoal судьи) - mentor-seam: renderSkillContext; onPlanWrite зовёт classifyImpl (fail-safe: сбой → без скил-сверки) - decideMentorObjection: заворот на decision=NO-GO ИЛИ сломанный вердикт; mentor-GO только на чистом GO - formatMentorObjection доносит суть (recommendation + reasoning + plan_points), GO -> пусто - enforce-mentor main: loadRegistry + classify; счётчик L1 decision-aware (Р7/§3.4) - скил-сверка — только план (gate2); спека (gate1) — по сути + decision - включает redesign согласования L1->L2 (Фазы 0-6, способ B: наставник->судья->печать) - регрессия tools-only 3901 passed + 2 skip (база 3877, +24 теста, 0 регрессий) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
22 lines
1.2 KiB
JavaScript
22 lines
1.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/** Парсер объявленных в плане скилов (мерж роутер↔наставник). Зеркало parseVerifiedContext:
|
|
* ищет fenced-блок ```skills-json со списком строк. Нет/битый → []. */
|
|
export function parsePlanSkills(content) {
|
|
const m = String(content ?? '').match(/```skills-json\s*\n([\s\S]*?)\n```/i);
|
|
if (!m) return [];
|
|
let arr;
|
|
try { arr = JSON.parse(m[1]); } catch { return []; }
|
|
if (!Array.isArray(arr)) return [];
|
|
return arr.filter((s) => typeof s === 'string' && s.trim());
|
|
}
|
|
|
|
/** Цель плана для classify(): секция ## Цель / ## Goal (до след. заголовка) или первый
|
|
* непустой не-заголовок абзац. Зеркало extractGoal судьи (enforce-judge-gate.mjs:174). */
|
|
export function extractPlanGoal(content) {
|
|
const text = String(content ?? '');
|
|
const m = text.match(/^##\s*(?:Цель|Goal)[^\n]*\n([\s\S]*?)(?:\n##\s|$)/im);
|
|
if (m && m[1].trim()) return m[1].trim();
|
|
const para = text.split(/\n\s*\n/).map((s) => s.trim()).find((s) => s && !s.startsWith('#'));
|
|
return para || '';
|
|
}
|