| name | kb-publish |
| description | Publish KB notes or synthesized reports as interactive HTML |
| user_invocable | true |
| arguments | Note name, research hub name, or --explain topic. Optional: --kb <name> to specify which KB the note is in |
/kb-publish
Publish KB content as a self-contained interactive HTML report.
The conversion is already implemented in .kb/build-report.py — use it. Do not hand-assemble HTML, hand-number footnotes, or hand-convert markdown unless the script can't do what's needed (see Fallback). The script is the single source of truth for markdown→HTML, wikilink resolution, citation→footnote conversion, hub-following, and template injection.
KB resolution: Parse $ARGUMENTS for --kb <name>. The builder has no --kb flag — instead pass the slug in kb:slug form (e.g. general:video-prompt-anatomy) when a bare slug is ambiguous across KBs or when you want to force a specific KB. A bare slug works when it's unique across all KBs. If a bare slug is ambiguous and no KB was given, ask the user which KB.
Modes
/kb-publish <note-name> — publish a single note
/kb-publish <research-hub-name> — publish a hub + all linked notes as a multi-section report
/kb-publish <collection-note> — publish a curated, grouped multi-part report (see Collection mode)
/kb-publish --explain <topic> — synthesize a narrative first, then publish it
Single vs. hub is automatic. The builder counts the note's hub children — its related: and depends_on: wikilinks combined (synthesis notes list their atomic components in depends_on, so those expand too): if the count exceeds graph.hub_threshold in .kb/config.yaml (default 3), it renders hub mode (the note's body first, then every child note as a collapsible section, footnotes pooled and de-duplicated). Otherwise it renders single-note mode (just that note). You don't choose hub vs. single — picking the right starting slug determines it.
Collection mode
To publish several hubs / research sets together as one cohesive, grouped report, add a collection: key to a master note's frontmatter. Each entry is a titled "Part" whose member notes render as sections under a divider, with a nested TOC. Notes shared across parts are de-duplicated (first occurrence wins); frontmatter-only notes are skipped.
collection:
- title: "Part I — Landscape"
notes: [some-hub, comp-a, comp-b, comp-c]
- title: "Part II — Deep-Dive"
notes: [deep-hub, sub-a, sub-b]
- title: "Part III — Video"
hub: video-patterns-hub
notes: is the explicit, predictable form (full control over membership and order — preferred when a hub's related: would pull in tangential cross-links). hub: auto-expands a sub-hub's children. The master note's own body renders as the un-collapsed intro. Then run python3 .kb/build-report.py <master-slug>. Output naming follows the rules below.
Primary path — run the builder
For single-note and hub modes:
python3 .kb/build-report.py <slug> [--title "Custom Title"]
Examples:
python3 .kb/build-report.py research-hub-prompting-ai-video-quality
python3 .kb/build-report.py general:soc2-compliance-overview --title "SOC 2 Guide"
What it does automatically:
- Finds the note across all KBs (or in the named KB for
kb:slug).
- Follows every
[[wikilink]] in related: (hub mode), reading each linked note. Cross-KB links ([[kb:slug]]) resolve too; an explicit cross-KB link that isn't found fails closed (rendered as plain text, not a broken anchor).
- Strips frontmatter; converts markdown (headings, tables, lists, blockquotes, code) via python-markdown.
- Resolves citations:
[text](../../references/file.md) and external [text](https://…) → numbered footnotes, de-duplicated by URL, linking to the Source: URL inside each reference file.
- Section IDs are KB-qualified:
id="{kb}--{slug}" (e.g. general--video-prompt-anatomy); in-report wikilinks resolve to href="#{kb}--{slug}". (Use these IDs if you ever post-process the output.)
- Injects everything into
.kb/templates/report.html and writes the file.
Output naming — read this
The output filename is derived from the title:
- No
--title → publish/<slug>.html (clean).
- With
--title → publish/<title-lowercased-with-spaces-as-hyphens>.html, keeping punctuation. A title with em-dashes, parentheses, or colons produces an ugly/awkward filename.
So: prefer omitting --title (the note's own title: is used for the heading regardless), or pass a slug-friendly title, or mv the file to a clean name afterward. Always report the final path and size.
Explain mode (--explain <topic>)
The builder only takes an existing slug, so synthesize first:
- Run the
/kb-explain logic: search the KB, read all relevant notes, synthesize a grounded narrative with [[wikilinks]] to the notes used and inline [text](../../references/…) citations.
- Write the narrative as a temporary
reference-type note in the target KB (e.g. kbs/<kb>/published-<topic-slug>.md) with a related: list of every note it references — so the builder renders it as a hub (narrative first, source notes as collapsible sections).
- Run
python3 .kb/build-report.py <topic-slug>.
- Optionally delete the temporary note afterward (the HTML is self-contained), or keep it if the synthesis is worth retaining.
Fallback — manual assembly
Only if the builder fails (template/script error) or the user needs a format the script can't produce (custom section ordering, a bespoke layout). Then assemble by hand following the script's contract so output stays consistent:
Rules
- Prefer the builder; reach for the fallback only with a reason, and say why in your summary to the user.
- Output is ONE self-contained HTML file — inline CSS/JS, no external dependencies. Never modify the template.
- Preserve all source attribution — every claim traces to a footnote.
- The report should print cleanly to PDF via the browser Print button.
- Skip notes that have only frontmatter and no body.
- Reports are point-in-time; if notes carry
valid_until/dated pricing or standings, mention that the snapshot will age.
Locations
- Template:
.kb/templates/report.html (read-only)
- Output:
publish/
$ARGUMENTS