7c282242c2
Узел «журнал» скила surfacing-open-questions (спека v6, раздел zhurnal): formatEventLine/parseEventLine, replay с отбросом битой хвостовой строки, activeSessionOf (порог протухания 30 мин — одна активная сессия на тему). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
40 lines
2.1 KiB
JavaScript
40 lines
2.1 KiB
JavaScript
/**
|
|
* oq-ledger — журнал «охотника за открытыми вопросами» (surfacing-open-questions, раздел спеки zhurnal).
|
|
* Append-only человекочитаемый .md-журнал: одна строка на событие, с провенансом источника.
|
|
* Чистое ядро (формат / разбор / replay / активная-сессия); запись на диск делает вызывающий
|
|
* скил через окно карантина docs/observer/questions/open-questions-<slug>.md.
|
|
*/
|
|
const SEP = ' · ';
|
|
|
|
/** Событие → одна .md-строка с провенансом и сессией. */
|
|
export function formatEventLine({ at, kind, qid, text, provenance, session }) {
|
|
return `- [${at}] ${kind} ${qid}: ${text}${SEP}источник: ${provenance}${SEP}сессия:${session}`;
|
|
}
|
|
|
|
/** Обратная операция formatEventLine; не-наша строка → null. */
|
|
export function parseEventLine(line) {
|
|
const m = /^- \[([^\]]+)\] (\S+) (\S+): (.*?) · источник: (.*?) · сессия:(.*)$/.exec(String(line));
|
|
if (!m) return null;
|
|
return { at: m[1], kind: m[2], qid: m[3], text: m[4], provenance: m[5], session: m[6] };
|
|
}
|
|
|
|
/** Сырьё журнала → события; непарсящаяся (битый хвост/обрыв) строка отбрасывается (терпимый replay). */
|
|
export function replay(raw) {
|
|
const lines = String(raw).split('\n').filter((l) => l.trim() !== '');
|
|
const out = [];
|
|
for (const line of lines) {
|
|
const ev = parseEventLine(line);
|
|
if (ev) out.push(ev);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** Активная сессия темы или null: последняя запись свежее порога → её session; иначе свободно. */
|
|
export function activeSessionOf(raw, { nowMs, staleMinutes = 30 } = {}) {
|
|
const events = replay(raw);
|
|
if (events.length === 0) return null;
|
|
const last = events[events.length - 1];
|
|
const ageMin = (nowMs - Date.parse(last.at)) / 60000;
|
|
return ageMin <= staleMinutes ? last.session : null;
|
|
}
|