diff --git a/docs/superpowers/router-mentor-wall-GUIDE.md b/docs/superpowers/router-mentor-wall-GUIDE.md index e40176b..3f7b76f 100644 --- a/docs/superpowers/router-mentor-wall-GUIDE.md +++ b/docs/superpowers/router-mentor-wall-GUIDE.md @@ -351,3 +351,27 @@ Verify-шаги под стеной сдвигают указатель, но GR **Мета-урок.** «SyntaxError без позиции» + «статика чистая, vitest красный» = почти всегда CRLF/кодировка, а не логика. Проверь байты и переносы ПЕРВЫМ делом, а не последним. + +## Уроки сессии №9 (2026-06-17) — формат ПЛАНА: verified-context-json + ref=якорь (не проза) + +**Симптом (частая ошибка новой сессии).** Спека опечатана, план содержательно отличный, но печать +не встаёт — наставник/судья вроде не против, а seal не приходит. + +**Причина — нарушен ФОРМАТ плана, не суть:** + +1. в плане НЕТ блока ` ```verified-context-json``` ` — а он обязателен и для ПЛАНА (≥1 `EXTRACTED` с + реальным якорём-подстрокой), иначе печать не встаёт; +2. в `steps-json` поле `ref` несёт ПРОЗУ-описание («Task1 RED: …») вместо якоря спеки. `ref` обязан + быть НЕПУСТЫМ якорем секции спеки (`{#u3}` → `ref:"u3"`; `D1..Dn`/`u1..un`). +3. навык в `skills-json` — БЕЗ плагин-префикса (`test-driven-development`, не + `superpowers:test-driven-development`). + +**Лечение:** добавить в план блок `verified-context-json` (готовый рабочий якорь: ref +`tools/cost-pricing.mjs`, anchor `export const PRICING = Object.freeze(`) + заменить все `ref` на +якоря секций спеки. + +**Прецедент (параллель-сессия трек 2c, 2026-06-17).** План r1 не имел `verified-context-json` и нёс +прозу в `ref` → печать не вставала; контроллер переписал план трижды (r1→r2→r3), на r3 добавил +`verified-context-json` EXTRACTED + якорные `ref` → наставник GO, судья GO, церемония пройдена. Суть +фичи была верной с r1 — итерации терялись ТОЛЬКО на ритуальном формате. **Вывод:** сверь план с +«Рецептом церемонии» (выше, п.2) ДО первой записи — экономит 2-3 круга наставника/судьи. diff --git a/tools/run-test-json.mjs b/tools/run-test-json.mjs index 4a0dd93..5fcc0b0 100644 --- a/tools/run-test-json.mjs +++ b/tools/run-test-json.mjs @@ -6,7 +6,7 @@ * mutate-runner трактует как inconclusive (не «убит»). Чистый parseVitestJson + I/O runVitestJson. */ import { execFileSync } from 'node:child_process'; -import { join } from 'node:path'; +import { resolveVitestConfig } from './produce-verify-receipt.mjs'; /** Разобрать stdout vitest --reporter json. Битый/пустой → loadError. */ export function parseVitestJson(jsonText) { @@ -23,14 +23,20 @@ export function parseVitestJson(jsonText) { return { allGreen, numTests, numPassed, numFailed, loadError: false }; } +/** Аргументы `npx` для прогона одного тест-файла. Корень и конфиг сюиты — ОТ КОРНЯ репозитория + * через общий resolveVitestConfig (есть app/ → app-режим, иначе корень); без хардкода app/. + * `exists` инъектируется (для теста); по умолчанию resolveVitestConfig берёт existsSync. */ +export function buildVitestJsonArgs(testFile, gitCwd, exists) { + const { root, config } = resolveVitestConfig(gitCwd, exists); + return ['vitest', 'run', testFile, '--root', root, '--config', config, '--reporter', 'json']; +} + /** I/O: прогнать testFile через vitest json. exit≠0 (упавшие тесты) — это нормально, парсим stdout. */ export function runVitestJson(testFile, gitCwd) { let stdout = ''; try { - stdout = execFileSync('npx', ['vitest', 'run', testFile, - '--root', join(gitCwd, 'app'), - '--config', join(gitCwd, 'app', 'vitest.config.tools.mjs'), - '--reporter', 'json'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }); + stdout = execFileSync('npx', buildVitestJsonArgs(testFile, gitCwd), + { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }); } catch (e) { // Упавшие тесты → execFileSync бросает с ненулевым кодом; stdout всё равно несёт json. stdout = (e && e.stdout) ? String(e.stdout) : ''; diff --git a/tools/run-test-json.test.mjs b/tools/run-test-json.test.mjs index d011375..ee42524 100644 --- a/tools/run-test-json.test.mjs +++ b/tools/run-test-json.test.mjs @@ -22,3 +22,21 @@ describe('parseVitestJson (анти-вакуум SE-LB-1/11)', () => { expect(parseVitestJson('')).toEqual({ allGreen: false, numTests: 0, numPassed: 0, numFailed: 0, loadError: true }); }); }); + +import { buildVitestJsonArgs } from './run-test-json.mjs'; + +describe('buildVitestJsonArgs (конфиг от корня репо, без хардкода app/)', () => { + const norm = (s) => String(s).replace(/\\/g, '/'); + it('есть app/vitest.config.tools.mjs → root=app', () => { + const a = buildVitestJsonArgs('t.mjs', '/repo', (p) => norm(p).endsWith('app/vitest.config.tools.mjs')); + expect(norm(a[a.indexOf('--root') + 1])).toBe('/repo/app'); + expect(norm(a[a.indexOf('--config') + 1])).toBe('/repo/app/vitest.config.tools.mjs'); + }); + it('нет app/-конфига → root=корень репо + базовая форма аргументов', () => { + const a = buildVitestJsonArgs('t.mjs', '/repo', () => false); + expect(a[a.indexOf('--root') + 1]).toBe('/repo'); + expect(a[0]).toBe('vitest'); + expect(a).toContain('t.mjs'); + expect(a).toContain('json'); + }); +});