Write a complete book from scratch — outline, parallel Sonnet drafting, Thai word break, typst PDF rendering, review, title brainstorm, publish to GitHub. Full pipeline proven on 'The Oracle Pattern' (15 chapters, 200+ pages). TRIGGER when: user says 'เขียนหนังสือ', 'write book', 'complete book', 'เขียนเล่มยาว', or wants a full book with PDF output. DO NOT TRIGGER for: short guides (use /oracle-write-book), cheatsheets (use /oracle-cheatsheet), single docs.
SKILL.md
/oracle-write-complete-book — Complete Book Pipeline
จาก idea สู่หนังสือ PDF — standard <=100 หน้า, short <=50-60 หน้า
proven pipeline จาก "The Oracle Pattern"
When to Use
standard — 8-12 บท, <=100 หน้า (default)
short — 5-8 บท, <=50-60 หน้า (--short)
ถ้าร่างเกิน cap ให้ตัด/รวมบท ไม่ใช่ปล่อยยาว — page cap เป็นข้อจำกัดจริง ไม่ใช่คำแนะนำ
ต้องการ PDF สวย (typst + Thai font + code blocks)
ต้องการ parallel agents เขียนขนาน
ต้องการ Thai word segmentation ที่ถูกต้อง
Pipeline Overview
Step 0: Mine session ← dig JSONL + traces + issues
Step 1: Outline ← per-chapter metadata (YAML)
Step 2: Prism review ← 5-lens structure check
Step 3: Title brainstorm ← 10 DNA + 3 judges workflow
Step 4: Parallel draft ← N Sonnet agents write files
Step 5: Thai word break ← PyThaiNLP ZWSP insertion
Step 6: Compile ← pandoc MD → typst markup
Step 7: Render PDF ← typst compile with styling
Step 8: Review ← 3 Sonnet agents check
Step 9: Iterate ← fix findings, re-render
Step 9.5: Save to working repo ← commit PDF+sources to ψ/writing/books/, THEN copy to ~/Downloads/
Step 10: Publish ← separate public GitHub repo + release — confirm with human first
Step 0: Mine Session Material
ห้ามเขียนจาก memory — ขุด source material จริงเสมอ
ภาษาไทยไม่มี space ระหว่างคำ → typst ตัดบรรทัดผิดจุด
แก้ด้วย zero-width space (U+200B) ที่จุดตัดคำ
thai-wordbreak.py
#!/usr/bin/env python3
"""Insert ZWSP at Thai word boundaries for proper line breaking."""
import sys, re
from pythainlp.tokenize import word_tokenize
ZWSP = ""
def has_thai(text):
return bool(re.search(r'[-]', text))
def insert_zwsp(text):
if not has_thai(text):
return text
parts = re.split(r'(`[^`]+`)', text) # preserve inline code
result = []
for part in parts:
if part.startswith('`'):
result.append(part)
elif has_thai(part):
segments = re.split(r'([-]+)', part)
for seg in segments:
if has_thai(seg):
result.append(ZWSP.join(word_tokenize(seg, engine="newmm")))
else:
result.append(seg)
else:
result.append(part)
return ''.join(result)
Run
for f in 00-*.md 01-*.md ... 15-*.md; do
uvx --from pythainlp python3 thai-wordbreak.py "$f" >> /tmp/book-zwsp.md
done
⚠️ Thai rendering — two bugs that WILL bite (learned the hard way writing "วรรณยุกต์ที่หายไป", 2026-07-23):
typst must be ≥ 0.15.1. typst 0.14.x mis-stacks/detaches Thai tone marks (ไม้เอก / ไม้หันอากาศ / สระบน) — a real shaper defect. 0.15.1 (rustybuzz 0.20.1) shapes Thai correctly, verified glyph-for-glyph vs hb-shape. brew upgrade typst first.
Font-substitution is the real gotcha, not the shaper.pandoc -t typst emits NO #set text(font:…), so typst falls back to its default (no Thai) → tofu or a GPOS-poor face that drops/mis-stacks marks. Fix = pin an explicit Thai font AND make the build deterministic: vendor the .ttf into ./fonts in the repo, set #set text(font: "<ThaiFont>", lang: "th"), and compile with typst compile --font-path fonts. Gate: typst compile … 2>&1 | rg -i "unknown font" must be empty.
Font pick: Laksaman (TLWG's TH Sarabun New) — readable + reliable: separate .ttf per weight, GSUB mark stacking. Prefer it over the macOS Sarabun.ttc (a collection that occasionally mis-selects a face). Full set: github.com/tlwg/fonts-tlwg/releases (Laksaman / Norasi / Garuda / Kinnari). Norasi = Thai serif for a book feel; Kanit reads heavy in bold.
Keep justification off (justify: false): typst justifies by stretching whitespace, but Thai words join with ZWSP (zero-width, no stretch) → justified Thai gets uneven rivers. Genuine typst-vs-LaTeX gap, no fix — use ragged-right (or xelatex if you truly need LaTeX-grade justified Thai). See oracle memory typst-thai-rendering.
book.typ — Golden Page Layout Config
// Page setup
#set page(paper: "a4", margin: (top: 2.5cm, bottom: 2.5cm, left: 3cm, right: 3cm))
#set text(font: "Laksaman", size: 12pt, lang: "th") // pin an explicit Thai font; compile with --font-path fonts (see ⚠️ above). typst ≥ 0.15.1.
#set heading(numbering: none)
#set par(leading: 1.6em, justify: false, first-line-indent: 0em)
#set block(spacing: 2.5em)
// Chapter headings — page break before
#show heading.where(level: 1): it => {
pagebreak(weak: true)
set text(size: 20pt, weight: "bold")
v(2em); it; v(1em)
}
// Section headings
#show heading.where(level: 2): it => {
set text(size: 14pt, weight: "bold")
v(1em); it; v(0.5em)
}
// Code blocks — Fira Code mono + background
#show raw.where(block: true): it => {
set text(font: "Fira Code", size: 9pt)
block(fill: rgb("#f6f8fa"), stroke: 0.5pt + luma(200),
inset: 14pt, radius: 4pt, width: 100%, it)
}
// Inline code — subtle grey background + charcoal
#show raw.where(block: false): it => {
box(fill: rgb("#f0f0f0"), inset: (x: 3pt, y: 1.5pt), radius: 2pt,
text(font: "Fira Code", size: 9pt, fill: rgb("#36454f"), it))
}
// Bold — dark navy
#show strong: it => {
text(weight: "bold", fill: rgb("#1a1a2e"), it)
}
// Blockquotes — blue left border + light blue background
#show quote.where(block: true): it => {
block(fill: rgb("#f0f4f8"), stroke: (left: 3pt + rgb("#3498db")),
inset: (left: 16pt, right: 12pt, top: 10pt, bottom: 10pt),
radius: (right: 4pt), it)
}
// Tables — dark header + zebra stripes
#set table(
stroke: 0.5pt + luma(180),
fill: (_, row) => if row == 0 { rgb("#2c3e50") }
else if calc.odd(row) { rgb("#f8f9fa") } else { white },
)
#show table.cell: it => {
set text(size: 10pt); set align(left)
if it.y == 0 { set text(fill: white, weight: "bold"); it } else { it }
}
// TOC — depth 1 only
#outline(title: "สารบัญ", depth: 1)
Font Requirements
Font
Purpose
Install
Laksaman (TLWG)
Body text (Thai + EN) — reliable mark stacking
github.com/tlwg/fonts-tlwg/releases → vendor the .ttf into ./fonts
Norasi (TLWG)
Optional Thai serif (book feel)
same TLWG release, vendor too
Fira Code
Code blocks + inline code
brew install --cask font-fira-code (vendor for reproducible builds)
Avoid the macOS Sarabun.ttc — a .ttc collection occasionally mis-selects a face. Prefer separate-.ttf fonts and always compile with --font-path fonts (typst won't emit a font from pandoc → falls back to a no-Thai default otherwise).
Typography Rules (from 10-DNA Prism)
Rule
Value
Why
Body font
Laksaman 12pt (vendored, --font-path)
reliable Thai mark stacking; not a .ttc
Code font
Fira Code 9pt mono
Code blocks only
Line height
1.6em
Thai ascender/descender
Block spacing
2.5em
Paragraph gap > line gap (1.67× ratio)
Justify
false (ragged-right)
Thai has no word spaces to stretch
Margin
3cm L/R, 2.5cm T/B
~65 chars/line
TOC depth
1
Chapter titles only
Heading numbering
none
Clean, no 3.1.2 nesting
Compile
# typst ≥ 0.15.1 required for correct Thai marks; --font-path picks up vendored ./fonts
typst compile --font-path fonts book-full.typ book-typst.pdf
# GATE: nothing fell back to a default font (a fallback silently mis-stacks Thai marks)
typst compile --font-path fonts book-full.typ book-typst.pdf 2>&1 | rg -i "unknown font" \
&& echo "❌ FIX FONTS (vendor the .ttf, check the font name)" || echo "✓ fonts OK"
Step 9.5: Save to the working repo FIRST (repo is home — Nat, 2026-07-13)
Before any public-repo publish decision, the book lives in git. Don't let a finished PDF sit
only on disk or only in ~/Downloads/ while a separate "should we publish externally?" question is
still pending — those are two different questions:
# commit the PDF + chapter sources into the CURRENT oracle repo (ψ/writing/books/<slug>/)
git add ψ/writing/books/<slug>/*.pdf ψ/writing/books/<slug>/*.md ψ/writing/books/<slug>/*.typ
git commit -m "book: <title> — draft complete, N pages"
git push
# then copy the PDF out so it's easy to double-click open
cp ψ/writing/books/<slug>/<book-name>.pdf ~/Downloads/
Creating a new dedicated public GitHub repo for the book (below) is a separate, bigger decision —
that's a fresh public artifact, so confirm with the human before running gh repo create /
publishing there, even though the book is already safely committed in the working repo by this point.
Step 10: Publish (separate public repo — confirm with the human first)