diff --git a/.gitignore b/.gitignore
index fdc57c2..194e5ae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,9 +44,9 @@ Thumbs.db
# Working notes and local tooling, not part of the published source
.claude/
-docs/project-memory.md
-docs/assistant-store-implementation-note.md
-docs/assistant-ui-guide.md
-docs/bias-impact-experiments-plan.md
-docs/vault-credential-gate-frontend.md
-docs/vault-credential-gate-tasks.md
+# In docs/ only the user guide is published; notes, the scripts that build the guide and its
+# screenshots stay local.
+docs/*
+!docs/user-guide.md
+!docs/user-guide.pdf
+!docs/user-guide.docx
diff --git a/docs/render-user-guide.py b/docs/render-user-guide.py
deleted file mode 100644
index 0d2d908..0000000
--- a/docs/render-user-guide.py
+++ /dev/null
@@ -1,191 +0,0 @@
-#!/usr/bin/env python3
-"""Render the user guide to a print-ready HTML page.
-
-From web/, to regenerate docs/user-guide.pdf after editing docs/user-guide.md:
-
- python3 docs/render-user-guide.py docs/user-guide.md /tmp/ug.html \
- "HumAIn Flow" "User Guide" "Version of 16 September 2026"
- npx playwright pdf file:///tmp/ug.html docs/user-guide.pdf
-
-Deliberately not a general Markdown implementation: it handles the constructs the
-guide actually uses, so the output is predictable and the print styling can be
-tuned for it (page breaks per chapter, a real table of contents, no orphan
-headings). Pandoc is not installed here and pulling one in for one document costs
-more than it saves.
-"""
-import html
-import re
-import sys
-
-INLINE = [
- (re.compile(r"`([^`]+)`"), r"\1"),
- (re.compile(r"\*\*([^*]+)\*\*"), r"\1"),
- (re.compile(r"(?\1"),
- (re.compile(r"\[([^\]]+)\]\(([^)]+)\)"), r'\1'),
-]
-
-
-def inline(text):
- out = html.escape(text)
- for pattern, replacement in INLINE:
- out = pattern.sub(replacement, out)
- return out
-
-
-def convert(markdown):
- lines = markdown.split("\n")
- out, toc = [], []
- i = 0
- list_stack = []
-
- def close_lists(to_depth=0):
- while len(list_stack) > to_depth:
- out.append(f"{list_stack.pop()}>")
-
- while i < len(lines):
- line = lines[i]
- stripped = line.strip()
-
- if not stripped:
- close_lists()
- i += 1
- continue
-
- if stripped.startswith("```"):
- close_lists()
- i += 1
- block = []
- while i < len(lines) and not lines[i].strip().startswith("```"):
- block.append(html.escape(lines[i]))
- i += 1
- i += 1
- out.append("
" + "\n".join(block) + "")
- continue
-
- heading = re.match(r"^(#{1,4})\s+(.*)$", stripped)
- if heading:
- close_lists()
- level = len(heading.group(1))
- text = heading.group(2)
- anchor = re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")
- # Chapters are h2 and sections h3, so the contents list is those two levels.
- if 2 <= level <= 3:
- toc.append((level - 1, text, anchor))
- out.append(f'{inline(stripped[2:])}
') - i += 1 - continue - - close_lists() - paragraph = [stripped] - i += 1 - while i < len(lines) and lines[i].strip() and not re.match(r"^\s*([-*]|\d+\.|#|\||>|```)", lines[i]): - paragraph.append(lines[i].strip()) - i += 1 - out.append(f"{inline(' '.join(paragraph))}
") - - close_lists() - return "\n".join(out), toc - - -CSS = """ -@page { size: A4; margin: 18mm 16mm 20mm; } -* { box-sizing: border-box; } -body { font: 10.5pt/1.55 -apple-system, "Segoe UI", Helvetica, Arial, sans-serif; - color: #1e293b; margin: 0; } -h1 { font-size: 21pt; color: #0f172a; margin: 0 0 6pt; line-height: 1.25; } -h2 { font-size: 19pt; color: #0f172a; page-break-before: always; page-break-after: avoid; - border-top: 2px solid #2563eb; padding-top: 6pt; margin: 0 0 10pt; line-height: 1.25; } -h3 { font-size: 12.5pt; color: #1d4ed8; margin: 16pt 0 5pt; page-break-after: avoid; } -h4 { font-size: 10.8pt; color: #0f172a; margin: 12pt 0 3pt; page-break-after: avoid; } -p { margin: 0 0 7pt; orphans: 2; widows: 2; } -ul, ol { margin: 0 0 8pt; padding-left: 18pt; } -li { margin-bottom: 2.5pt; } -code { font-family: "SF Mono", Menlo, Consolas, monospace; font-size: 9pt; - background: #f1f5f9; border: 1px solid #e2e8f0; border-radius: 3px; padding: 0 3px; } -pre { background: #f8fafc; border: 1px solid #e2e8f0; border-left: 3px solid #94a3b8; - border-radius: 4px; padding: 8pt 10pt; overflow-x: auto; page-break-inside: avoid; } -pre code { background: none; border: none; padding: 0; font-size: 8.5pt; line-height: 1.45; } -table { width: 100%; border-collapse: collapse; margin: 0 0 10pt; font-size: 9.5pt; - page-break-inside: avoid; } -th { background: #eff6ff; color: #1e3a8a; text-align: left; font-weight: 600; } -th, td { border: 1px solid #cbd5e1; padding: 4pt 6pt; vertical-align: top; } -.note { border-left: 3px solid #f59e0b; background: #fffbeb; padding: 6pt 9pt; - margin: 0 0 8pt; page-break-inside: avoid; } -hr.page-break { border: 0; page-break-after: always; } -.cover { height: 247mm; display: flex; flex-direction: column; justify-content: center; } -.cover-title { font-size: 34pt; border: 0; padding: 0; } -.cover-sub { font-size: 13pt; color: #475569; margin-top: 2pt; } -.cover-meta { margin-top: 26pt; font-size: 10pt; color: #64748b; } -.toc { page-break-after: always; } -.toc-title { font-size: 19pt; color: #0f172a; border-top: 2px solid #2563eb; - padding-top: 6pt; margin: 0 0 10pt; } -.toc ol { list-style: none; padding-left: 0; counter-reset: chapter; } -.toc li.l1 { counter-increment: chapter; font-weight: 600; margin-top: 6pt; } -.toc li.l1::before { content: counter(chapter) ". "; color: #2563eb; } -.toc li.l2 { padding-left: 16pt; font-weight: 400; font-size: 9.5pt; color: #475569; } -.toc a { color: inherit; text-decoration: none; } -a { color: #1d4ed8; } -""" - - -def main(md_path, html_path, title, subtitle, meta): - # The cover carries the document title, so the file's own leading H1 would only repeat it. - source = re.sub(r"\A\s*#\s+.*\n", "", open(md_path).read()) - body, toc = convert(source) - items = "".join( - f'