restore: run-test-json конфиг от корня репо через resolveVitestConfig plus тест buildVitestJsonArgs plus GUIDE Уроки 9 формат плана

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-06-17 20:20:23 +03:00
parent 07f24382e8
commit c50ac7c915
3 changed files with 53 additions and 5 deletions
@@ -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 круга наставника/судьи.
+11 -5
View File
@@ -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) : '';
+18
View File
@@ -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');
});
});