2772b197b3
secretary-audit.mjs (новый): 9 линз, buildAuditPrompt -> {system,user}, parseAuditResponse,
applyAudit (новые СВ с номером от хука, мутация+родословная, close/тихо/partial, горящие
блоки Л8/Л9), preserveRegistry (реестр СВ изолирован от reconcile).
protocol: поля hidden/acceptance/tails/nextSvId + рендер горящих блоков и раздела
«Скрытые вопросы (фон)». stop-hook: второй проход после reconcile + снимок реестра ДО
reconcile (reconcile не владеет СВ). + дизайн-спека и план.
97 юнит-тестов зелёные. Живьём подтверждены: наполнение, мутация под тем же номером,
routing Л9 в горящий блок. Известно: старый реестр в деле «линза» уже искажён до фикса.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
99 lines
5.3 KiB
JavaScript
99 lines
5.3 KiB
JavaScript
// Структура и сверка короткого протокола (§D5/§D7). Отменённое зачёркивается, не удаляется.
|
|
export function EMPTY_PROTOCOL() {
|
|
return {
|
|
subject: '', status: 'открыто',
|
|
decisions: [], alternatives: [], consequences: [],
|
|
will: [], open: [], doneNext: [], history: [], steps: [],
|
|
hidden: [], acceptance: [], tails: [], nextSvId: 1,
|
|
};
|
|
}
|
|
|
|
// Провенанс: первый ход — где внесено [→N]; последующие — где снова касались [M].
|
|
function prov(turns) {
|
|
if (!Array.isArray(turns) || !turns.length) return '';
|
|
const [first, ...rest] = turns;
|
|
return ` [→${first}]${rest.map((t) => `, [${t}]`).join('')}`;
|
|
}
|
|
|
|
// Провенанс в обычных строках — только [→N]; имя файла/сессии живёт в разделе «Шаги».
|
|
const line = (e) => `${e.struck ? `~~${e.text}~~` : e.text}${prov(e.turns)}`;
|
|
|
|
// Шаги (Слой 1): человекочитаемая строка на КАЖДЫЙ ход («спросил → ответил»), в конце —
|
|
// ссылка(и) на сырьё для подробностей. Шаги ведёт хук (по ходу), не модель.
|
|
function stepsSection(p) {
|
|
const steps = (p.steps || []).slice().sort((a, b) => (a.turn || 0) - (b.turn || 0));
|
|
// Ссылка на отдельный файл хода (file=«ходы/turn-N.log»), проставленный при остановке;
|
|
// до нарезки — запасной вариант на общий лог сессии. Между ходами разных сессий —
|
|
// строка-разделитель «—— сессия X ——» (граница рабочего захода в сквозной тетради).
|
|
const out = [];
|
|
let prevSession = null;
|
|
for (const s of steps) {
|
|
if (s.session && prevSession !== null && s.session !== prevSession) {
|
|
out.push(`—— сессия ${s.session} ——`);
|
|
}
|
|
const ref = s.file || (s.session ? `${s.session}.log` : '');
|
|
out.push(`- ${s.text}${ref ? ` · ${ref}` : ''}`);
|
|
if (s.session) prevSession = s.session;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// Полная форма протокола (§D7): шапка «Дело» + 8 корзин (2–9) + навигация Шаги→Слой 1.
|
|
export function renderProtocol(protocol, opts = {}) {
|
|
const L = [];
|
|
if (opts.work) {
|
|
L.push(`**Дело:** ${opts.work} · **Статус:** ${protocol.status || 'открыто'} · `
|
|
+ `**Дата:** ${opts.date || ''} · **Хозяин:** владелец · **Цель:** ${protocol.subject || ''}`, '');
|
|
}
|
|
const burn = (title, arr) => {
|
|
const live = (arr || []).filter((e) => !e.done);
|
|
if (!live.length) return;
|
|
L.push(title);
|
|
for (const e of live) {
|
|
const stale = e.lastTouch != null && opts.turn && opts.turn > e.lastTouch ? ` · висит ${opts.turn - e.born} ходов` : '';
|
|
L.push(`- ${e.text}${e.born ? ` [→${e.born}]` : ''}${stale}`);
|
|
}
|
|
L.push('');
|
|
};
|
|
burn('## ⚠️ ЗАЯВЛЕНО ГОТОВО — НЕ ПРОВЕРЕНО (Л8)', protocol.acceptance);
|
|
burn('## 🧹 ХВОСТЫ — НЕ ПРИБРАНО (Л9)', protocol.tails);
|
|
L.push('## Решения');
|
|
for (const d of protocol.decisions || []) {
|
|
const body = d.struck ? `~~${d.text}~~` : d.text;
|
|
const why = d.why ? ` — ${d.why}` : '';
|
|
L.push(`- ${body}${why}${prov(d.turns)}`);
|
|
}
|
|
L.push('', '## Альтернативы');
|
|
for (const a of protocol.alternatives || []) L.push(`- ${line(a)}`);
|
|
L.push('', '## Последствия / цена');
|
|
for (const c of protocol.consequences || []) L.push(`- ${line(c)}`);
|
|
L.push('', '## Твоя воля / запреты');
|
|
for (const w of protocol.will || []) L.push(`- ${line(w)}`);
|
|
L.push('', '## Открытые вопросы');
|
|
for (const o of protocol.open || []) L.push(`- ${line(o)}`);
|
|
L.push('', '## Сделано / дальше');
|
|
for (const s of protocol.doneNext || []) L.push(`- [${s.done ? 'x' : ' '}] ${s.struck ? `~~${s.text}~~` : s.text}${prov(s.turns)}`);
|
|
L.push('', '## История (заменено, не стёрто)');
|
|
for (const hh of protocol.history || []) {
|
|
if (Array.isArray(hh.events) && hh.events.length) {
|
|
// Тайм-линия: внёс [→N], вынес [←N]; может повторяться (вернул/снова вынес).
|
|
const seq = hh.events.map((ev) => (ev.dir === 'out' ? `[←${ev.turn}]` : `[→${ev.turn}]`)).join(' ');
|
|
const removed = hh.events[hh.events.length - 1].dir === 'out';
|
|
L.push(`- ${removed ? `~~${hh.text}~~` : hh.text} ${seq}`);
|
|
} else {
|
|
L.push(`- ~~${hh.oldText}~~ → ${hh.newText}${prov(hh.turns)}`); // legacy-формат
|
|
}
|
|
}
|
|
L.push('', '## Шаги (Слой 1)');
|
|
for (const s of stepsSection(protocol)) L.push(s);
|
|
L.push('', '## Скрытые вопросы (фон)');
|
|
for (const h of (protocol.hidden || [])) {
|
|
const head = h.lineage && h.lineage.length
|
|
? h.lineage.map((x) => `~~${x.text}~~`).join(' → ') + ' → ' + h.text
|
|
: h.text;
|
|
const prov2 = ` [→${h.born}]` + (h.lastTouch && h.lastTouch !== h.born ? ` [${h.lastTouch}]` : '');
|
|
L.push(`- ${h.id} [${h.lens} · ${h.status}]: ${head}${prov2}`);
|
|
}
|
|
return L.join('\n');
|
|
}
|