#!/usr/bin/env node /** * artifact-from-spec (C1, §5) — спека markdown → {sections, source_sha}. * ref = ЯВНЫЙ якорь `## ... {#id}` (P2-2, НЕ слаг заголовка). Пустой раздел отброшен * (fail-CLOSE). source_sha — sha256 исходного текста (входит в sealable-объект, SD-1). */ import { createHash } from 'node:crypto'; const ANCHOR_RE = /^#{1,6}\s+.*\{#([a-zA-Z0-9._-]+)\}\s*$/; /** markdown → { sections: { : <содержимое раздела> } }. */ export function parseAnchoredSections(md) { const lines = String(md == null ? '' : md).split(/\r?\n/); const sections = {}; let curKey = null; let buf = []; const flush = () => { if (curKey) { const body = buf.join('\n').trim(); if (body) sections[curKey] = body; } buf = []; }; for (const line of lines) { const m = line.match(ANCHOR_RE); const isHeading = /^#{1,6}\s+/.test(line); if (m) { flush(); curKey = m[1]; } else if (isHeading) { flush(); curKey = null; } // заголовок без якоря закрывает раздел else if (curKey) buf.push(line); } flush(); return { sections }; } /** sealable-объект артефакта: { sections, source_sha }. */ export function buildArtifact(md) { const { sections } = parseAnchoredSections(md); const source_sha = createHash('sha256').update(String(md == null ? '' : md)).digest('hex'); return { sections, source_sha }; }