fix: наставник-хук silent-swallow срыв в видимый degraded plus GUIDE Уроки 7 диагностика наставник не вернулся
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
# Наставник-хук: фикс silent-swallow → видимый degraded (re-trigger) Implementation Plan
|
||||
|
||||
**Goal:** Обернуть регион производства вердикта наставника (память кругов + `onPlanWrite`/`onSpecWrite`)
|
||||
в `try/catch`, чтобы throw в нём возвращал видимый `{ran:true, wired:false}` (degraded) вместо
|
||||
молчаливого пробрасывания в `catch` main() — план/спека больше не зависают без сигнала.
|
||||
|
||||
**Architecture:** Правка только в `tools/enforce-mentor-on-plan-write.mjs` (обе ветки — план и спека).
|
||||
`planHash`/`specHash` поднимаются выше региона (нужны в `catch` для binding снимка). Успешный путь
|
||||
байт-в-байт прежний; degraded-контракт (`wired:false`) уже обрабатывается main() штатно (снимок
|
||||
degraded + decideMentorObjection degraded-блок). Тесты — инъекцией `roundMemoryImpl`, который бросает.
|
||||
|
||||
**Tech Stack:** Node ESM, vitest (config `vitest.config.tools.mjs`). TDD.
|
||||
|
||||
## Цель
|
||||
|
||||
Превратить молчаливый срыв наставника в видимый degraded-вердикт (Уроки №7), чтобы зависший
|
||||
наставник был ВИДЕН в снимке и перезапускался, а не выглядел вечным «считает». Обе ветки (план и
|
||||
спека) симметрично.
|
||||
|
||||
## Структура файлов
|
||||
|
||||
- Изменить (тест): `tools/enforce-mentor-on-plan-write.test.mjs` — 2 теста (план/спека: throw в
|
||||
регионе → ran:true, wired:false).
|
||||
- Изменить: `tools/enforce-mentor-on-plan-write.mjs` — guard план-ветки и спека-ветки.
|
||||
|
||||
```skills-json
|
||||
["test-driven-development"]
|
||||
```
|
||||
|
||||
```steps-json
|
||||
[
|
||||
{"op":"Edit","object":"tools/enforce-mentor-on-plan-write.test.mjs","ref":"m4"},
|
||||
{"op":"Bash","object":"npx vitest run tools/enforce-mentor-on-plan-write.test.mjs --config vitest.config.tools.mjs --no-file-parallelism","ref":"m4"},
|
||||
{"op":"Edit","object":"tools/enforce-mentor-on-plan-write.mjs","ref":"m1"},
|
||||
{"op":"Bash","object":"npx vitest run tools/enforce-mentor-on-plan-write.test.mjs --config vitest.config.tools.mjs --no-file-parallelism","ref":"m4"},
|
||||
{"op":"Edit","object":"tools/enforce-mentor-on-plan-write.mjs","ref":"m1"},
|
||||
{"op":"Bash","object":"npx vitest run tools/enforce-mentor-on-plan-write.test.mjs --config vitest.config.tools.mjs --no-file-parallelism","ref":"m4"},
|
||||
{"op":"Bash","object":"npx vitest run --config vitest.config.tools.mjs --no-file-parallelism","ref":"m4"}
|
||||
]
|
||||
```
|
||||
|
||||
```verified-context-json
|
||||
[{"id":"mpc2","kind":"EXTRACTED","ref":"tools/enforce-mentor-on-plan-write.mjs","anchor":"export async function runMentorOnPlanWrite("}]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 1: тесты «throw в регионе → видимый degraded» (§m4)
|
||||
|
||||
- [ ] **Step 1 (Edit `tools/enforce-mentor-on-plan-write.test.mjs`): 2 падающих теста**
|
||||
|
||||
В конец describe `runMentorOnPlanWrite` (перед его закрывающим `});`) дописать: план с
|
||||
`roundMemoryImpl`, который бросает → `{ran:true, wired:false}` + непустой `planHash` + `reason`/срыв;
|
||||
то же для `specEvent`.
|
||||
|
||||
- [ ] **Step 2 (Bash): прогон — тесты падают (RED)**
|
||||
|
||||
Run: `npx vitest run tools/enforce-mentor-on-plan-write.test.mjs --config vitest.config.tools.mjs --no-file-parallelism`
|
||||
Expected: FAIL — сейчас throw в регионе реджектит промис. (Под Claude harness-collapse; авторитет — терминал владельца.)
|
||||
|
||||
### Task 2: guard обеих веток (§m1)
|
||||
|
||||
- [ ] **Step 3 (Edit `tools/enforce-mentor-on-plan-write.mjs`): guard план-ветки**
|
||||
|
||||
`planHash` поднять выше региона; обернуть `roundMemoryP` + `onPlanWrite` в `try/catch`; на throw —
|
||||
вернуть `{ran:true, ok:false, wired:false, reason:'наставник-путь сорвался: <msg>', taskId:taskIdForPrompt, planHash, verdict:null, routerClassification:null}`.
|
||||
|
||||
- [ ] **Step 4 (Bash): прогон — план-тест зелёный, спека-тест ещё красный**
|
||||
|
||||
Run: `npx vitest run tools/enforce-mentor-on-plan-write.test.mjs --config vitest.config.tools.mjs --no-file-parallelism`
|
||||
Expected: план-ветка проходит, спека-ветка ещё падает. (Авторитет — терминал владельца.)
|
||||
|
||||
- [ ] **Step 5 (Edit `tools/enforce-mentor-on-plan-write.mjs`): guard спека-ветки**
|
||||
|
||||
Обернуть `roundMemoryS` + `onSpecWrite` в `try/catch`; на throw — вернуть `{ran:true, ok:false, wired:false, reason:'наставник-путь (спека) сорвался: <msg>', taskId:taskIdForPromptS, planHash:specHash, verdict:null}`.
|
||||
|
||||
- [ ] **Step 6 (Bash): прогон — оба теста зелёные (GREEN)**
|
||||
|
||||
Run: `npx vitest run tools/enforce-mentor-on-plan-write.test.mjs --config vitest.config.tools.mjs --no-file-parallelism`
|
||||
Expected: PASS — оба новых теста + прежние тесты файла без регрессий. (Авторитет — терминал владельца.)
|
||||
|
||||
- [ ] **Step 7 (Bash): полная регрессия tools**
|
||||
|
||||
Run: `npx vitest run --config vitest.config.tools.mjs --no-file-parallelism`
|
||||
Expected: PASS — база + новые тесты, 0 регрессий. (Под Claude harness-collapse; авторитетный свод — терминал владельца.)
|
||||
|
||||
## Self-Review (план против спеки)
|
||||
|
||||
- **§m1 guard** — Task 2: обе ветки обёрнуты, `planHash`/`specHash` выше региона для binding в catch,
|
||||
degraded-возврат `wired:false`. ✓
|
||||
- **§m2 крайние случаи** — throw в `roundMemoryImpl` и внутри `onPlanWrite`/`onSpecWrite` ловится;
|
||||
транспортный сбой по-прежнему через `runMentorVerdict` (не трогаем). ✓
|
||||
- **§m4 критерий** — RED (throw реджектит) → GREEN (degraded) для плана и спеки; прежние тесты целы. ✓
|
||||
- **DR-1** — мутирующие шаги 1,3,5 сопровождены Bash (2,4,6); шаги 3,5 один файл, между ними Bash (4);
|
||||
шаг 7 — отдельная полная регрессия. ✓
|
||||
- **Конвенция §m3** — аддитивный точечный diff, успешный путь не меняется, без новых файлов. ✓
|
||||
@@ -267,3 +267,58 @@ Verify-шаги под стеной сдвигают указатель, но GR
|
||||
(`claude-sonnet-4-6`→`deepseek-v4-pro`, `HEAVY_LLM_TIMEOUT_MS` 90000→300000) ломает 3 теста
|
||||
(`router-config`, `enforce-mentor-on-plan-write` timeout, `enforce-judge-gate` parse `plan_soundness`) —
|
||||
это пре-существующий дрейф, НЕ регресс твоей задачи. Не путать со своими провалами.
|
||||
|
||||
## Уроки сессии №6 (2026-06-17) — escape-рецепт + verify-gate
|
||||
|
||||
- **Метка escape — в LABEL опции, НЕ в описании.** `enforce-askuser-answer-parser` подписывает
|
||||
ВЫБРАННЫЙ ответ = текст label. Маркер `FLOOR-ESCAPE: <action>` в `description` НЕ подписывается
|
||||
(проверено вживую: Edit блокнут «разговорный режим», грант не записан). Клади ТОЧНУЮ каноническую
|
||||
строку прямо в `label`.
|
||||
- **Один маркер на вопрос; батч = несколько ВОПРОСОВ.** Две метки в одном label (через перенос
|
||||
строки) НЕ подписываются. «Разные файлы в одном AskUser» = массив `questions[]` (до 4), каждый
|
||||
вопрос = один грант. Один файл несколько раз = РАЗНЫЕ раунды/вопросы.
|
||||
- **Грант одноразовый — ВСЕ правки файла ОДНИМ Edit.** `enforce-floor-escape-consume` гасит грант на
|
||||
первом УСПЕШНОМ Edit; вторая правка того же файла под тем же грантом блокнётся. Делай широкий
|
||||
`old_string`, охватывающий все изменения, либо запрашивай отдельный грант. Упавший Edit грант НЕ
|
||||
гасит (можно повторить). Read-перед-Edit обязателен и тут — Grep чтение не засчитывается.
|
||||
- **`enforce-verify-gate` ≠ escape/override.** На `git commit`/`push` через Claude требует ПОДПИСАННУЮ
|
||||
`~/.claude/runtime/verify-receipt.json` (fingerprint == staged-diff) от `node tools/produce-verify-receipt.mjs`.
|
||||
Ни `floor_escape`, ни `ремонт инфраструктуры` его не снимают. А продюсер в claude-brain СЛОМАН (смотрит
|
||||
на `app/vitest.config.tools.mjs` после сплита → always suite-not-passed, см. `bags/2026-06-17-produce-verify-receipt-app-path-bug.md`)
|
||||
→ Claude-коммиты тут тупиковы, коммить в терминале владельца (verify-gate — Claude-PreToolUse-хук).
|
||||
- **`node tools/verdict-wait.mjs` разрешён в стене** как read-only сторож видимости вердиктов
|
||||
(`isObserveOnly` regex без цепочек/редиректов) — для по-этапного ожидания вердиктов под стеной.
|
||||
|
||||
## Уроки сессии №7 (2026-06-17) — диагностика «наставник не вернулся» (молчаливый срыв)
|
||||
|
||||
**Симптом.** Написал план, печать не встаёт, режим разговорный. В снимке вердиктов нет записи по
|
||||
плану (нет 4-го `hash`), наставник/судья по плану не появляются — будто «ещё считает» бесконечно.
|
||||
|
||||
**Причина (корневой дефект машинерии).** `main()` в `tools/enforce-mentor-on-plan-write.mjs`
|
||||
обёрнут в `catch { /* производитель никогда не блокирует */ }`. Любой throw МЕЖДУ вызовом роутера
|
||||
(`classifyImpl`/`classify`) и LLM-вердиктом наставника проглатывается МОЛЧА — нет вердикта, нет
|
||||
записи в снимок, нет печати, нет ошибки. Незащищённое место: `renderSkillContext`
|
||||
([tools/on-plan-write.mjs](../../tools/on-plan-write.mjs) ~стр.71) НЕ в `try`; ранний throw там или
|
||||
в `runMentorVerdict` до транспорта → всплывает в молчаливый catch. В снимке
|
||||
**«упало-и-проглотилось» НЕОТЛИЧИМО от «ещё считает»** — в обоих случаев записи просто нет.
|
||||
|
||||
**Чем срыв отличается от медленной модели.** В логах вызовов владельца (внешняя панель провайдера):
|
||||
роутер по плану отработал (есть строка `Agent Router` / `recommend`), а **второго вызова
|
||||
`Agent mentor` после него НЕТ**. Это срыв, а не латентность. Если доступа к логам нет — считать
|
||||
«вердикта нет дольше 1-2 циклов ПОСЛЕ того как роутер точно отработал» = вероятный молчаливый срыв.
|
||||
|
||||
**Рецепт (что делать — НЕ повторять прокол сессии №7):**
|
||||
|
||||
1. **НЕ опрашивать снимок по кругу и НЕ гадать «медленная модель».** «Нет 4-го hash» — это НЕ
|
||||
диагноз (срыв и счёт выглядят одинаково).
|
||||
2. **Сразу читать оркестратор** (`enforce-mentor-on-plan-write.mjs` + `on-plan-write.mjs`) — чтение
|
||||
в разговорном режиме свободно. Помнить про молчаливый catch: он прячет срыв.
|
||||
3. **Лечение — перезапуск плана НОВЫМ именем файла** (свежий `plan_id` фитилит наставника заново;
|
||||
`planId` от steps, поэтому при идентичных шагах слегка варьируй один шаг, напр. `--reporter dot`).
|
||||
Транзиент обычно не повторяется — r2 печатается чисто. Это быстрее, чем ждать/спрашивать владельца.
|
||||
4. **Опрос снимка полезен ТОЛЬКО чтобы поймать момент печати** (Grep `verdict-snapshot` блокируется
|
||||
стеной = план опечатан = режим исполнения), но НЕ как диагноз зависания.
|
||||
|
||||
**Мета-урок.** При «застряло» — сначала прочитать код пути (средства под рукой в разговорном
|
||||
режиме), потом действовать. НЕ скатываться в «ждать или спросить владельца», когда можешь сам
|
||||
диагностировать чтением. Owner видит вердикты в логах — но это не повод не открыть код самому.
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# Наставник-хук: молчаливый срыв в видимый degraded (фикс silent-swallow)
|
||||
|
||||
**Дата:** 2026-06-17 · **Статус:** дизайн под реализацию (TDD). Багфикс машинерии переговоров.
|
||||
|
||||
## Цель
|
||||
|
||||
`main()` в `tools/enforce-mentor-on-plan-write.mjs` обёрнут в `catch {}` («производитель никогда не
|
||||
блокирует»). Побочка: любой throw в регионе производства вердикта наставника — МЕЖДУ вызовом роутера
|
||||
и LLM-вердиктом (например `renderSkillContext`, загрузчик памяти кругов, сборка промпта) —
|
||||
проглатывается молча. Итог: ни вердикта, ни записи в снимок, ни печати, ни сигнала; план тихо не
|
||||
печатается, а в снимке «упало-и-проглотилось» НЕОТЛИЧИМО от «ещё считает». Цель — превратить такой
|
||||
срыв в **видимый degraded-вердикт** (`wired:false`), который проходит штатным каналом видимости
|
||||
(снимок `mentor:plan`/`mentor:spec` = degraded) и блокировки (degraded-сообщение контроллеру), чтобы
|
||||
зависший наставник был ВИДЕН и перезапускался, а не выглядел вечным «считает».
|
||||
|
||||
## Контракт guard'а {#m1}
|
||||
|
||||
В `runMentorOnPlanWrite` регион производства вердикта (загрузка памяти кругов + вызов
|
||||
`onPlanWrite`/`onSpecWrite`) оборачивается в `try/catch`:
|
||||
|
||||
- успех — поведение прежнее (возврат `{ran:true, ok, wired, ...}` без изменений);
|
||||
- throw в регионе — НЕ пробрасывается наружу (раньше всплывал в молчаливый `catch` main()), а
|
||||
возвращается как `{ran:true, ok:false, wired:false, reason:'наставник-путь сорвался: <msg>',
|
||||
taskId, planHash|specHash, verdict:null}`.
|
||||
|
||||
`wired:false` — это ровно тот degraded-контракт, что уже умеет `main()`: снимок пишет стадию
|
||||
`degraded`, `decideMentorObjection` отдаёт degraded-блок (recordMentorGo:false, escalation не растёт).
|
||||
`planHash`/`specHash` доступны в `catch` (считаются ДО региона), поэтому снимок несёт binding.
|
||||
|
||||
## Крайние случаи {#m2}
|
||||
|
||||
- throw в загрузчике памяти кругов (`roundMemoryImpl`) — degraded, не молчаливый throw.
|
||||
- throw внутри `onPlanWrite`/`onSpecWrite` до транспорта (`renderSkillContext` и пр.) — degraded.
|
||||
- сбой ТРАНСПОРТА LLM (сеть/таймаут) — уже ловится внутри `runMentorVerdict` → `wired:false` штатно;
|
||||
этот путь не меняется (остаётся degraded, как был).
|
||||
- оба пути (план и спека) симметричны — guard ставится в обеих ветках.
|
||||
|
||||
## Конвенция {#m3}
|
||||
|
||||
Минимальный аддитивный diff: обернуть существующий регион в `try/catch`, `planHash`/`specHash`
|
||||
поднять выше региона (нужны в `catch`). Успешный путь байт-в-байт прежний. Без новых файлов и
|
||||
зависимостей. Прочая логика `main()` (снимок, счётчик, decideMentorObjection) переиспользуется как
|
||||
есть — она уже корректно обрабатывает `wired:false`.
|
||||
|
||||
## Критерий приёмки {#m4}
|
||||
|
||||
TDD-тесты в `tools/enforce-mentor-on-plan-write.test.mjs` (инъекция `roundMemoryImpl`, который
|
||||
бросает — доступная точка throw в защищаемом регионе):
|
||||
|
||||
- план: `roundMemoryImpl` бросает → `runMentorOnPlanWrite` НЕ реджектит, а возвращает
|
||||
`{ran:true, wired:false}` с непустым `planHash` и `reason` про срыв;
|
||||
- спека: то же для spec-пути (`specEvent`) → `{ran:true, wired:false}`;
|
||||
- существующие тесты (сбой транспорта, GO, NO-GO, видимость) — без регрессий.
|
||||
|
||||
Полная регрессия tools зелёная: `npx vitest run --config vitest.config.tools.mjs --no-file-parallelism`.
|
||||
|
||||
```verified-context-json
|
||||
[{"id":"mc1","kind":"EXTRACTED","ref":"tools/enforce-mentor-on-plan-write.mjs","anchor":"производитель никогда не блокирует"}]
|
||||
```
|
||||
@@ -135,22 +135,29 @@ export async function runMentorOnPlanWrite(event, {
|
||||
let graphSectionS = null;
|
||||
try { graphSectionS = graphSectionImpl(); } catch { graphSectionS = null; }
|
||||
const verifiedContextS = parseVerifiedContext(content);
|
||||
// SP2c-2: память кругов M-side спеки (свои замечания + M-доводы + diff + замечание судьи).
|
||||
const roundMemoryS = roundMemoryImpl ? roundMemoryImpl({ stage: 'spec', content, taskId: taskIdForPromptS }) : {};
|
||||
const rs = await onSpecWrite({
|
||||
specContent: content,
|
||||
specHash,
|
||||
existingTaskId: loadTaskIdImpl(),
|
||||
persistTaskIdImpl,
|
||||
llmCall,
|
||||
journalEntries: journalS.entries,
|
||||
journalKey,
|
||||
nowMs,
|
||||
verifiedContext: verifiedContextS,
|
||||
negotiationLog: negotiationLogS,
|
||||
graphSection: graphSectionS,
|
||||
roundMemory: roundMemoryS,
|
||||
});
|
||||
// Фикс silent-swallow (Уроки №7): симметрично план-ветке — throw в регионе спеки → видимый
|
||||
// degraded (wired:false), не молчаливый реджект в catch main().
|
||||
let rs;
|
||||
try {
|
||||
// SP2c-2: память кругов M-side спеки (свои замечания + M-доводы + diff + замечание судьи).
|
||||
const roundMemoryS = roundMemoryImpl ? roundMemoryImpl({ stage: 'spec', content, taskId: taskIdForPromptS }) : {};
|
||||
rs = await onSpecWrite({
|
||||
specContent: content,
|
||||
specHash,
|
||||
existingTaskId: loadTaskIdImpl(),
|
||||
persistTaskIdImpl,
|
||||
llmCall,
|
||||
journalEntries: journalS.entries,
|
||||
journalKey,
|
||||
nowMs,
|
||||
verifiedContext: verifiedContextS,
|
||||
negotiationLog: negotiationLogS,
|
||||
graphSection: graphSectionS,
|
||||
roundMemory: roundMemoryS,
|
||||
});
|
||||
} catch (e) {
|
||||
return { ran: true, ok: false, wired: false, reason: `наставник-путь (спека) сорвался: ${e && e.message ? e.message : e}`, taskId: taskIdForPromptS, planHash: specHash, verdict: null };
|
||||
}
|
||||
try { persistVerdictImpl({ ok: rs.ok, wired: rs.wired, reason: rs.reason ?? null, planHash: specHash, verdict: rs.verdict }); } catch { /* best-effort */ }
|
||||
if (rs.journalOk && rs.journal) { try { persistJournalImpl(rs.journal); } catch { /* best-effort (SE10) */ } }
|
||||
return { ran: true, ok: rs.ok, wired: rs.wired, reason: rs.reason, taskId: rs.taskId, planHash: specHash, verdict: rs.verdict };
|
||||
@@ -177,26 +184,35 @@ export async function runMentorOnPlanWrite(event, {
|
||||
const planGoal = extractPlanGoal(content);
|
||||
let registry = null;
|
||||
try { registry = typeof registryImpl === 'function' ? registryImpl() : null; } catch { registry = null; }
|
||||
// SP2c-2: память кругов M-side плана (свои замечания + M-доводы + diff + замечание судьи).
|
||||
const roundMemoryP = roundMemoryImpl ? roundMemoryImpl({ stage: 'plan', content, taskId: taskIdForPrompt }) : {};
|
||||
const r = await onPlanWrite({
|
||||
planSteps: steps,
|
||||
existingTaskId: loadTaskIdImpl(),
|
||||
persistTaskIdImpl,
|
||||
llmCall,
|
||||
journalEntries: journal0.entries,
|
||||
journalKey,
|
||||
nowMs,
|
||||
verifiedContext,
|
||||
negotiationLog,
|
||||
graphSection,
|
||||
classifyImpl,
|
||||
registry,
|
||||
declaredSkills,
|
||||
planGoal,
|
||||
roundMemory: roundMemoryP,
|
||||
});
|
||||
const planHash = planId(steps);
|
||||
// Фикс silent-swallow (Уроки №7): срыв В РЕГИОНЕ наставника (renderSkillContext в on-plan-write.mjs
|
||||
// и пр.) раньше всплывал в молчаливый catch main() → ни вердикта, ни снимка, ни печати, ни сигнала
|
||||
// (в снимке «упало» неотличимо от «считает»). Теперь любой throw → ВИДИМЫЙ degraded (wired:false):
|
||||
// снимок mentor:plan=degraded + блок «наставник сорвался» → контроллер видит и перезапускает.
|
||||
let r;
|
||||
try {
|
||||
// SP2c-2: память кругов M-side плана (свои замечания + M-доводы + diff + замечание судьи).
|
||||
const roundMemoryP = roundMemoryImpl ? roundMemoryImpl({ stage: 'plan', content, taskId: taskIdForPrompt }) : {};
|
||||
r = await onPlanWrite({
|
||||
planSteps: steps,
|
||||
existingTaskId: loadTaskIdImpl(),
|
||||
persistTaskIdImpl,
|
||||
llmCall,
|
||||
journalEntries: journal0.entries,
|
||||
journalKey,
|
||||
nowMs,
|
||||
verifiedContext,
|
||||
negotiationLog,
|
||||
graphSection,
|
||||
classifyImpl,
|
||||
registry,
|
||||
declaredSkills,
|
||||
planGoal,
|
||||
roundMemory: roundMemoryP,
|
||||
});
|
||||
} catch (e) {
|
||||
return { ran: true, ok: false, wired: false, reason: `наставник-путь сорвался: ${e && e.message ? e.message : e}`, taskId: taskIdForPrompt, planHash, verdict: null, routerClassification: null };
|
||||
}
|
||||
try { persistVerdictImpl({ ok: r.ok, wired: r.wired, reason: r.reason ?? null, planHash, verdict: r.verdict }); } catch { /* best-effort */ }
|
||||
if (r.journalOk && r.journal) { try { persistJournalImpl(r.journal); } catch { /* best-effort (SE10) */ } }
|
||||
return { ran: true, ok: r.ok, wired: r.wired, reason: r.reason, taskId: r.taskId, planHash, verdict: r.verdict, routerClassification: r.routerClassification ?? null };
|
||||
|
||||
@@ -145,6 +145,26 @@ describe('runMentorOnPlanWrite (обёртка-производитель W7)',
|
||||
const r = await runMentorOnPlanWrite(planEvent, d);
|
||||
expect(r.routerClassification).toEqual({ recommended_chain: ['systematic-debugging'] });
|
||||
});
|
||||
|
||||
// Фикс silent-swallow (Уроки №7): срыв В РЕГИОНЕ наставника (между роутером и LLM-вердиктом —
|
||||
// renderSkillContext, загрузчик памяти кругов и пр.) раньше реджектил промис → молчаливый catch
|
||||
// main() → ни вердикта, ни снимка, ни печати. Теперь → видимый degraded (ran:true, wired:false).
|
||||
it('срыв в регионе наставника (план, roundMemoryImpl бросил) → ran:true, wired:false (видимый degraded, не реджект)', async () => {
|
||||
const d = deps({ roundMemoryImpl: () => { throw new Error('boom-в-регионе'); } });
|
||||
const r = await runMentorOnPlanWrite(planEvent, d);
|
||||
expect(r.ran).toBe(true);
|
||||
expect(r.wired).toBe(false);
|
||||
expect(r.reason).toMatch(/сорвал/i);
|
||||
expect(typeof r.planHash).toBe('string');
|
||||
expect(r.planHash.length).toBeGreaterThan(0);
|
||||
});
|
||||
it('срыв в регионе наставника (спека, roundMemoryImpl бросил) → ran:true, wired:false', async () => {
|
||||
const d = deps({ roundMemoryImpl: () => { throw new Error('boom-спека'); } });
|
||||
const r = await runMentorOnPlanWrite(specEvent, d);
|
||||
expect(r.ran).toBe(true);
|
||||
expect(r.wired).toBe(false);
|
||||
expect(r.reason).toMatch(/сорвал/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('decideMentorObjection (Фаза 1 — канал замечаний наставника контроллеру)', () => {
|
||||
|
||||
Reference in New Issue
Block a user