397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
37 lines
1.5 KiB
JavaScript
37 lines
1.5 KiB
JavaScript
#!/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: { <anchor>: <содержимое раздела> } }. */
|
|
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 };
|
|
}
|