91ded23462
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
80 lines
6.5 KiB
JavaScript
80 lines
6.5 KiB
JavaScript
// tools/secretary-apply.mjs
|
||
// Детерминированное применение результатов спана к реестру: садовник + новые ветки + кандидаты.
|
||
import { one } from './secretary-armor.mjs';
|
||
import { applyTend, makeHeir } from './secretary-gardener.mjs';
|
||
import { collapseProtocol } from './secretary-reconcile.mjs';
|
||
|
||
const norm = (s) => String(s || '').trim().toLowerCase().replace(/\s+/g, ' ');
|
||
|
||
/** Зажим тяжести: догадка не может быть блокером → важное (правило тяжести, находка №13). */
|
||
function clampSeverity(опора, тяжесть) {
|
||
return (one(опора) === 'догадка' && тяжесть === 'блокер') ? 'важное' : (тяжесть || 'важное');
|
||
}
|
||
|
||
/** Разложить результаты спана по реестру. Вход НЕ мутируется (работаем на копии). */
|
||
export function applyResults(proto, turn, d11, d12, d13, dG) {
|
||
const p = JSON.parse(JSON.stringify(proto));
|
||
p.hidden = p.hidden || []; p.nextSvId = p.nextSvId || 1; p.candidates = p.candidates || [];
|
||
|
||
// садовник (тенд по id; close без proof → carry — внутри applyTend)
|
||
applyTend(p, (dG && dG.tend) || [], turn);
|
||
|
||
// новые ветки из диагностики
|
||
for (const n of (d11 && d11.new) || []) {
|
||
p.hidden.push({ id: `СВ-${p.nextSvId++}`, lens: n.lens || 'Л?', status: 'открыт', text: String(n.text || ''), опора: one(n.опора), ref: n.ref || '', тяжесть: clampSeverity(n.опора, n.тяжесть), born: turn, lastTouch: turn, lineage: [] });
|
||
}
|
||
|
||
// ловец → долговечная ветка lens=брош
|
||
for (const dr of (d12 && d12.dropped) || []) {
|
||
p.hidden.push({ id: `СВ-${p.nextSvId++}`, lens: 'брош', status: 'открыт', text: String(dr.text || ''), опора: one(dr.опора), ref: dr.цитата || '', тяжесть: 'важное', born: turn, lastTouch: turn, lineage: [] });
|
||
}
|
||
|
||
// кандидаты — НАКАПЛИВАЮТСЯ (ход рождения, без дублей по branch), помечены слабыми (доживают до разбора)
|
||
const seen = new Set(p.candidates.map((c) => norm(c.branch)));
|
||
for (const f of (d13 && d13.forks) || []) {
|
||
if (seen.has(norm(f.branch))) continue;
|
||
seen.add(norm(f.branch));
|
||
p.candidates.push({ branch: f.branch, trigger: f.trigger, why: f.why, опора: one(f.опора), релевантность: f.релевантность || 'low', born: turn });
|
||
}
|
||
|
||
return p;
|
||
}
|
||
|
||
/** ЭТАП 1 (fluffy v2): урожай — новые ветки (диагност+ловец) + рождение/заточка/полнота кандидатов (брейншторм).
|
||
* Садовника НЕ применяет (это applyGardener отдельно, на свежей грядке). Вход НЕ мутируется. */
|
||
export function applyHarvest(proto, turn, d11, d12, d13) {
|
||
const p = JSON.parse(JSON.stringify(proto));
|
||
p.hidden = p.hidden || []; p.candidates = p.candidates || []; p.nextSvId = p.nextSvId || 1; p.nextKdId = p.nextKdId || 1;
|
||
// новые ветки (диагностика) — с title/тема/важность/history
|
||
for (const n of (d11 && d11.new) || []) p.hidden.push({ id: `СВ-${p.nextSvId++}`, lens: n.lens || 'Л?', status: 'открыт',
|
||
title: n.title || '', тема: n.тема || '', важность: n.важность || 'medium', text: String(n.text || ''),
|
||
опора: one(n.опора), ref: n.ref || '', тяжесть: clampSeverity(n.опора, n.тяжесть), born: turn, lastTouch: turn, lineage: [],
|
||
history: [{ turn, ev: 'рождён', why: `диагност ${n.lens || ''}`, detail: n.ref || '' }] });
|
||
// ловец → ветка брош
|
||
for (const dr of (d12 && d12.dropped) || []) p.hidden.push({ id: `СВ-${p.nextSvId++}`, lens: 'брош', status: 'открыт',
|
||
title: dr.title || '', тема: dr.тема || '', важность: dr.важность || 'medium', text: String(dr.text || ''),
|
||
опора: one(dr.опора), ref: dr.цитата || '', тяжесть: 'важное', born: turn, lastTouch: turn, lineage: [],
|
||
history: [{ turn, ev: 'рождён', why: 'ловец (брошенная ветка)', detail: dr.цитата || '' }] });
|
||
// брейншторм точит СВОИХ кандидатов (мутация → наследник)
|
||
for (const m of (d13 && d13.mutate) || []) {
|
||
const it = p.candidates.find((x) => x.id === m.id);
|
||
if (!it || (it.status && it.status !== 'жив')) continue;
|
||
it.history = it.history || []; it.history.push({ turn, ev: 'мутировал', why: m.why || '', detail: m.newText || '' });
|
||
it.status = 'мутировал'; it.endedTurn = turn; it.why = m.why;
|
||
makeHeir(it, turn, m.newText, m.newTitle, 'cand', p, m.why, 'рождён заточкой');
|
||
}
|
||
// брейншторм рождает новых (дедуп по branch) — с id/title/тема/важность/history
|
||
const seen = new Set(p.candidates.map((c) => norm(c.branch)));
|
||
for (const f of (d13 && d13.forks) || []) { if (seen.has(norm(f.branch))) continue; seen.add(norm(f.branch));
|
||
p.candidates.push({ id: `КД-${p.nextKdId++}`, branch: f.branch, title: f.title || '', тема: f.тема || '', важность: f.важность || 'low',
|
||
trigger: f.trigger, why: f.why, опора: one(f.опора), релевантность: f.релевантность || 'low', born: turn, status: 'жив',
|
||
history: [{ turn, ev: 'рождён', why: 'брейншторм', detail: f.trigger || '' }] }); }
|
||
// критик полноты (зияющий пробел ЦЕЛОГО пласта) — дедуп по теме
|
||
const themesHave = new Set([...p.hidden, ...p.candidates].map((x) => norm(x.тема)).filter(Boolean));
|
||
for (const g of (d13 && d13.gaps) || []) { const th = norm(g.тема); if (th && themesHave.has(th)) continue; themesHave.add(th);
|
||
p.candidates.push({ id: `КД-${p.nextKdId++}`, branch: g.why || g.title || '', title: g.title || '', тема: g.тема || 'полнота',
|
||
важность: 'high', gap: true, why: g.why, опора: 'догадка', релевантность: 'high', born: turn, status: 'жив',
|
||
history: [{ turn, ev: 'рождён', why: 'критик полноты (зияющий пробел пласта)', detail: g.title || '' }] }); }
|
||
return collapseProtocol(p);
|
||
}
|