New:Microsoft Teams Notifications Are Now Available in Socket.Learn more →
Get Started

@livewiki/core

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@livewiki/core

Deterministic structural index and batch documentation engine for livewiki.

Source
npmnpm
Version
0.1.0
Version published
Weekly downloads
41
51.85%
Maintainers
1
Weekly downloads
 
Created
Source

/**

  • readme-export — deterministic README.md synthesis from the generated wiki.
  • Roadmap item 11 (export readme). ZERO LLM: every sentence in the output
  • traces to a wiki page (quickstart purpose/digests, flows/topics hubs) or is
  • a fixed template line. The README is positioned as an OUTPUT of the wiki,
  • for repos that have none (or that opted into a managed block).
  • Rule #6 contract (human content is never rewritten by automation):
    • README.md absent → create it; the whole file is the generated
  •                             block wrapped in README_START/README_END
    
  •                             markers plus a one-line provenance comment.
    
    • README.md with the block → replace ONLY the content between the
  •                             markers; outside bytes preserved exactly.
    
  •                             Idempotent (unchanged wiki ⇒ no diff).
    
    • README.md without markers → REFUSE, with the exact opt-in instructions.
  • All I/O goes through safe-io with allowReadme: true (rule #1), mirroring
  • the pointer module's allowPointer exception. Writing additionally
  • requires an explicit yes (rule-#2-style opt-in); without it the export
  • is a dry-run preview and nothing is written. / import * as nodePath from "node:path"; import * as safeIo from "./safe-io.js"; import { parseFrontmatter } from "./frontmatter.js"; import { loadConfig } from "./config.js"; import { loadUnderstandingSynthesis } from "./understanding.js"; /* Marker block — stable; external parsers may depend on it. / export const README_START = ""; export const README_END = ""; /* Target file, relative to repoRoot. / export const README_REL_PATH = "README.md"; /* Cap for the module digest list (mirrors navigation's MODULE_DIGEST_CAP). / const README_DIGEST_CAP = 6; /* Lines of the would-be file shown in a dry-run preview. / const PREVIEW_LINES = 12; export class ReadmeExportError extends Error { code; constructor(code, message) { super(message); this.name = "ReadmeExportError"; this.code = code; } } /*
  • Locates the livewiki readme block, tolerating whitespace inside the
  • markers (same discipline as the pointer block parser). Returns the span
  • INCLUDING both marker lines, or null when absent/truncated. / export function findReadmeBlock(content) { const startRegex = //; const endRegex = //; const startMatch = startRegex.exec(content); if (!startMatch) return null; const endMatch = endRegex.exec(content); if (!endMatch) return null; const startIdx = startMatch.index; const endIdx = endMatch.index + endMatch[0].length; const inner = content.slice(startIdx + startMatch[0].length, endMatch.index); return { startIdx, endIdx, inner }; } /* The canonical block (markers + generated body) written into the file. / function canonicalBlock(inner) { return ${README_START}\n\n${inner.trim()}\n\n${README_END}; } /* The full file content for the create case (provenance comment + block). / function fullFileContent(inner) { return ("\n" + canonicalBlock(inner) + "\n"); } /* Opt-in instructions for the refusal path (rule #6). / function refusalMessage() { return [ "README.md exists without a livewiki marker block; a human-authored README is never overwritten (rule #6).", "To opt in, insert these two lines where the generated section should go, then re-run:", ${README_START}, ${README_END}, "Alternatively, remove or rename README.md and re-run to create a fully generated file.", ].join("\n"); } /*
  • Pure application of the rule-#6 contract. generated is the block BODY
  • (no markers). Never throws; a human README without markers is a refusal,
  • not an error. / export function applyReadme(existing, generated) { if (existing === null) { return { action: "create", content: fullFileContent(generated) }; } const found = findReadmeBlock(existing); if (!found) return { refusal: refusalMessage() }; const replaced = existing.slice(0, found.startIdx) + canonicalBlock(generated) + existing.slice(found.endIdx); if (replaced === existing) return { action: "unchanged", content: existing }; return { action: "replace-block", content: replaced }; } // ── Wiki evidence extraction (all deterministic parsers) ─────────────────── function stripFrontmatter(content) { try { return parseFrontmatter(content).body; } catch { return content; } } /* Lines of the section under a ## <heading> match, up to the next heading. / function sectionLines(lines, headingRe) { const start = lines.findIndex((line) => headingRe.test(line.trim())); if (start < 0) return null; const out = []; for (let i = start + 1; i < lines.length; i++) { if (/^#{1,6}\s/.test(lines[i].trim())) break; out.push(lines[i]); } return out; } /*
  • First plain paragraph in the given lines: consecutive non-blank lines that
  • are not headings, list items, provenance italics (*(...)*), bold labels
  • (entry-point/fast-path lines), or blockquotes. Headings/non-paragraph lines
  • are skipped while the buffer is empty and terminate it once filled. / function firstPlainParagraph(lines) { const buffer = []; for (const line of lines) { const t = line.trim(); const isParagraphLine = t !== "" && !/^#{1,6}\s/.test(t) && !t.startsWith("-") && !t.startsWith("") && !t.startsWith(">") && !/^\d+.\s/.test(t); if (!isParagraphLine) { if (buffer.length > 0) break; continue; } buffer.push(t); } return buffer.length > 0 ? buffer.join(" ") : null; } /**
  • The purpose paragraph: the ## What this repository is opening when the
  • quickstart carries the orientation block, else the first non-heading
  • paragraph of the page. Null when the quickstart has neither. / function extractPurpose(quickstartBody) { const lines = quickstartBody.split("\n"); const orientation = sectionLines(lines, /^##\s+What this repository is\s$/i); if (orientation !== null) { const paragraph = firstPlainParagraph(orientation); if (paragraph !== null) return paragraph; } return firstPlainParagraph(lines); } /**
  • The ## What you'll find in this wiki digest bullets of the quickstart —
  • the same data the README re-emits (title — responsibility), never
  • re-derived from module pages. / function extractDigests(quickstartBody) { const lines = quickstartBody.split("\n"); const section = sectionLines(lines, /^##\s+What you'll find in this wiki\s$/i); if (section === null) return []; const digests = []; const bulletRe = /^-\s+**[([^]]+)](([^)]+))**(?:\s+—\s+(.+))?$/; for (const line of section) { if (digests.length >= README_DIGEST_CAP) break; const match = bulletRe.exec(line.trim()); if (!match) continue; digests.push({ title: match[1], link: match[2], responsibility: match[3] ?? null, }); } return digests; } /** Title+link entries of a hub page (### [t](x) flows, - [t](x) topics). / function extractHubLinks(hubBody, hubDir) { const links = []; const entryRe = /^(?:###|-)\s+[([^]]+)](([^)]+))\s$/; for (const line of hubBody.split("\n")) { const match = entryRe.exec(line.trim()); if (!match) continue; links.push({ title: match[1], link: livewiki/${hubDir}/${match[2]} }); } return links; } /** Deterministic no-purpose fallback, mirroring navigation's synthesis. / function synthesizePurposeFromDigests(digests) { const usable = digests.filter((d) => d.responsibility !== null).slice(0, 3); if (usable.length === 0) return null; const items = usable.map((d) => ${d.title} (${d.responsibility})); const joined = items.length === 1 ? items[0] : items.length === 2 ? ${items[0]} and ${items[1]} : ${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}; return This repository is organized around ${joined}.; } async function readWikiPage(repoRoot, relPath) { try { return await safeIo.readText(repoRoot, relPath); } catch { return null; } } /*
  • Builds the README block body from the wiki. Throws ReadmeExportError
  • (code "missing_wiki") when livewiki/quickstart.md does not exist. / async function buildReadme(repoRoot) { const absRoot = nodePath.resolve(repoRoot); const notes = []; const quickstart = await readWikiPage(absRoot, "livewiki/quickstart.md"); if (quickstart === null) { throw new ReadmeExportError("missing_wiki", "livewiki/quickstart.md not found — run livewiki init (and livewiki init --batch) first to generate the wiki."); } const quickstartBody = stripFrontmatter(quickstart); const config = await loadConfig(absRoot).catch(() => ({})); const language = config.language ?? "en"; if (!language.toLowerCase().startsWith("en")) { notes.push(wiki language is "${language}"; section headings are kept in English + "(prose is preserved from the wiki in its own language)"); } const repoName = nodePath.basename(absRoot); const digests = extractDigests(quickstartBody); // Item 23: the stage-5c understanding synthesis (when present) is the // purpose paragraph; the README is one evidence input, never the // authority. The fallback chain is unchanged. const understanding = await loadUnderstandingSynthesis(absRoot); const purpose = understanding?.purpose ?? extractPurpose(quickstartBody) ?? synthesizePurposeFromDigests(digests) ?? This is the \${repoName}` repository.; const flowsHub = await readWikiPage(absRoot, "livewiki/flows/index.md"); const topicsHub = await readWikiPage(absRoot, "livewiki/topics/index.md"); const flows = flowsHub === null ? [] : extractHubLinks(stripFrontmatter(flowsHub), "flows"); const topics = topicsHub === null ? [] : extractHubLinks(stripFrontmatter(topicsHub), "topics"); const lines = [ # ${repoName}, "", purpose, "", "## Documentation", "", "This repository is documented by a livewiki wiki — start at the [quickstart](livewiki/quickstart.md).", "", ]; if (digests.length > 0) { lines.push("## What you'll find in the wiki", ""); for (const digest of digests) { const link = livewiki/${digest.link}; lines.push(digest.responsibility !== null ? - ${digest.title} — ${digest.responsibility} :- ${digest.title}); } lines.push(""); } if (flows.length > 0) { lines.push("## How it works", ""); for (const flow of flows) lines.push(- ${flow.title}); lines.push(""); } if (topics.length > 0) { lines.push("## Concept topics", ""); for (const topic of topics) lines.push(- ${topic.title}`); lines.push(""); } return { content: lines.join("\n").trimEnd(), notes }; } /*
  • The generated README block body (no markers) for the repo's wiki.
  • Throws ReadmeExportError when livewiki/quickstart.md does not exist. / export async function generateReadmeContent(repoRoot) { return (await buildReadme(repoRoot)).content; } /*
  • Orchestrates generate → apply → safe-io write (allowReadme: true).
  • Never overwrites a marker-less README; never writes without yes: true. */ export async function exportReadme(repoRoot, opts = {}) { const absRoot = nodePath.resolve(repoRoot); const dryRun = opts.yes !== true; const { content, notes } = await buildReadme(absRoot); const ioOpts = { allowReadme: true }; let existing = null; if (await safeIo.exists(absRoot, README_REL_PATH, ioOpts)) { existing = await safeIo.readText(absRoot, README_REL_PATH, ioOpts); } const applied = applyReadme(existing, content); const path = nodePath.join(absRoot, README_REL_PATH); if ("refusal" in applied) { return { ok: false, action: "refused", dryRun, path, bytesChanged: 0, refusal: applied.refusal, notes, }; } if (dryRun) { const preview = applied.action === "unchanged" ? undefined : applied.content.split("\n").slice(0, PREVIEW_LINES); return { ok: true, action: applied.action, dryRun: true, path, bytesChanged: 0, notes, ...(preview !== undefined ? { preview } : {}), }; } if (applied.action === "unchanged") { return { ok: true, action: "unchanged", dryRun: false, path, bytesChanged: 0, notes }; } await safeIo.writeText(absRoot, README_REL_PATH, applied.content, ioOpts); return { ok: true, action: applied.action, dryRun: false, path, bytesChanged: applied.content.length - (existing?.length ?? 0), notes, }; } //# sourceMappingURL=readme-export.js.map

FAQs

Package last updated on 12 Aug 2026

Related posts