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>
32 lines
1.8 KiB
JavaScript
32 lines
1.8 KiB
JavaScript
// tools/plan-skills.test.mjs
|
|
import { describe, it, expect } from 'vitest';
|
|
import { parsePlanSkills, extractPlanGoal } from './plan-skills.mjs';
|
|
|
|
describe('parsePlanSkills', () => {
|
|
const md = ['# План', '```skills-json', '["executing-plans","test-driven-development"]', '```', '## Цель', 'x'].join('\n');
|
|
it('достаёт список скилов из ```skills-json блока', () => {
|
|
expect(parsePlanSkills(md)).toEqual(['executing-plans', 'test-driven-development']);
|
|
});
|
|
it('нет блока → пустой массив', () => { expect(parsePlanSkills('# План\nтекст')).toEqual([]); });
|
|
it('битый JSON → пустой массив (fail-safe)', () => {
|
|
expect(parsePlanSkills('```skills-json\n[не json\n```')).toEqual([]);
|
|
});
|
|
it('не-массив / не-строки отфильтрованы', () => {
|
|
expect(parsePlanSkills('```skills-json\n["ok", 5, null, "two"]\n```')).toEqual(['ok', 'two']);
|
|
});
|
|
});
|
|
|
|
describe('extractPlanGoal (зеркало extractGoal судьи)', () => {
|
|
it('секция ## Цель → её текст до след. заголовка', () => {
|
|
const md = ['# План', '## Цель', 'починить парсер X', '', '## Шаги', 'шаг 1'].join('\n');
|
|
expect(extractPlanGoal(md)).toBe('починить парсер X');
|
|
});
|
|
it('секция ## Goal → её текст', () => {
|
|
expect(extractPlanGoal('## Goal\nfix the bug\n## Steps')).toBe('fix the bug');
|
|
});
|
|
it('нет секции цели → первый непустой не-заголовок абзац', () => {
|
|
expect(extractPlanGoal('# План\n\nделаем фичу\n')).toBe('делаем фичу');
|
|
});
|
|
it('пусто → пустая строка', () => { expect(extractPlanGoal('')).toBe(''); });
|
|
});
|