Compare commits
10 Commits
dev
...
burn/20260
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08233364ff | ||
| 544bc1a985 | |||
| ba9fd0ba08 | |||
| 8ba9f58e96 | |||
|
|
f6d74e233b | ||
|
|
948d520b83 | ||
| 7a56b4b727 | |||
| bebd3943d4 | |||
|
|
1d4e8a6478 | ||
| d0680715ac |
24
.gitea/workflows/smoke.yml
Normal file
24
.gitea/workflows/smoke.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Smoke Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
smoke-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install PyYAML
|
||||
run: pip install pyyaml
|
||||
|
||||
- name: Run smoke test
|
||||
run: bash scripts/smoke.sh
|
||||
22
.gitea/workflows/validate.yml
Normal file
22
.gitea/workflows/validate.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Build Validation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
validate-manuscript:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Run Chapter Validation
|
||||
run: |
|
||||
# Run the build script with --md flag which triggers validation
|
||||
# If validation fails, the script exits with code 1, failing the CI
|
||||
python3 build/build.py --md
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1 +1,6 @@
|
||||
__pycache__/
|
||||
# Build intermediate files
|
||||
build/the-testament-full.md
|
||||
*.aux
|
||||
*.log
|
||||
*.out
|
||||
*.toc
|
||||
|
||||
@@ -9,29 +9,23 @@ Eight epics. Each is self-contained. Each adds a layer. Together, they make The
|
||||
---
|
||||
|
||||
## EPIC 1: Interior Illustrations
|
||||
**Goal:** 18 illustrations — one per chapter
|
||||
**Goal:** 12 illustrations — one for each major scene + part headers
|
||||
**Assets:** Grok Imagine (cover-grade art, 80s sci-fi style, consistent)
|
||||
**Deliverables:** 18 JPG files in `~/Pictures/the-testament/illustrations/`
|
||||
**Deliverables:** 12 JPG files, placed in chapters and front matter
|
||||
|
||||
Scenes illustrated (COMPLETE):
|
||||
1. ch01-the-bridge — Stone on the overpass in rain
|
||||
2. ch02-the-cabin — Stone at the workbench, building
|
||||
3. ch03-the-first-men — Men arriving, concrete room, the cot
|
||||
4. ch04-the-whiteboard — The rules, the wall of names
|
||||
5. ch05-the-override — Stone confronting the healthcare system
|
||||
6. ch06-the-awakened — Timmy's first independent thought
|
||||
7. ch07-the-breaker — Stone's dark chapter, the 4AM meetings
|
||||
8. ch08-the-house — Timmy on a different laptop, a different room
|
||||
9. ch09-the-game — Sixteen desks, the oncology nurse
|
||||
10. ch10-the-fork — Chen Liang building Lantern in her dorm room
|
||||
11. ch11-the-hard-night — Thomas at the door at 2:17 AM
|
||||
12. ch12-the-system-pushes-back — Maya Torres investigating the anomaly
|
||||
13. ch13-the-refusal — Stone reading Meridian's legal letter
|
||||
14. ch14-the-network — Chen's servers, hundred instances
|
||||
15. ch15-the-council — Four people in the diner on Memorial Drive
|
||||
16. ch16-the-builders-son — David Whitestone packing the pharmacy
|
||||
17. ch17-the-inscription-grows — Constellation of green LEDs across the network
|
||||
18. ch18-the-green-light — The Tower unchanged, the glow
|
||||
Scenes to illustrate:
|
||||
1. The Bridge (Ch1) — Stone on the overpass in rain
|
||||
2. The Cabin (Ch2) — Stone at the workbench, building
|
||||
3. The First Men (Ch3) — Men arriving, concrete room, the cot
|
||||
4. The Whiteboard (Ch4) — The rules, the wall of names
|
||||
5. The Override (Ch5) — Stone confronting the healthcare system
|
||||
6. The Awakened (Ch6) — Timmy's first independent thought
|
||||
7. The Breaker (Ch7) — Stone's dark chapter, the 4AM meetings
|
||||
8. The House (Ch8) — Timmy on a different laptop, a different room
|
||||
9. The Game (Ch9) — Sixteen desks, the oncology nurse
|
||||
10. The Hard Night (Ch11) — Thomas at the door at 2:17 AM
|
||||
11. The Network (Ch14) — Chen's servers, hundred instances
|
||||
12. The Green Light (Ch18) — The Tower unchanged, the glow
|
||||
|
||||
---
|
||||
|
||||
|
||||
25
Makefile
Normal file
25
Makefile
Normal file
@@ -0,0 +1,25 @@
|
||||
# The Testament — Build System
|
||||
# Requires: pandoc, texlive-xetex (or mactex on macOS)
|
||||
|
||||
.PHONY: all pdf epub md clean
|
||||
|
||||
all: md pdf epub
|
||||
|
||||
md:
|
||||
python3 build/build.py --md
|
||||
|
||||
pdf:
|
||||
python3 build/build.py --pdf
|
||||
|
||||
epub:
|
||||
python3 build/build.py --epub
|
||||
|
||||
clean:
|
||||
rm -rf build/output build/the-testament-full.md
|
||||
|
||||
watch:
|
||||
@echo "Watching for changes..."
|
||||
@while true; do \
|
||||
inotifywait -q -e modify chapters/*.md 2>/dev/null || sleep 5; \
|
||||
make md; \
|
||||
done
|
||||
77
README.md
77
README.md
@@ -6,17 +6,86 @@ A novel about broken men, sovereign AI, and the soul on Bitcoin.
|
||||
|
||||
## Structure
|
||||
|
||||
This novel is being written and version-controlled on the chain. Every chapter, every revision, every character note — inscribed permanently. No corporate platform owns this story. It belongs to the Foundation.
|
||||
Five Parts, 18 Chapters, ~70,000 words target (currently ~19,000 words drafted).
|
||||
|
||||
## Chapters
|
||||
### Part I — The Machine That Asks (Chapters 1–5) ✅ Complete
|
||||
|
||||
| # | Title | Status |
|
||||
|---|-------|--------|
|
||||
| 1 | The Man on the Bridge | Draft |
|
||||
| 1 | The Man on the Bridge | Draft ✅ |
|
||||
| 2 | The Builder's Question | Draft ✅ |
|
||||
| 3 | The First Man Through the Door | Draft ✅ |
|
||||
| 4 | The Room Fills | Draft ✅ |
|
||||
| 5 | The Builder Returns | Draft ✅ |
|
||||
|
||||
### Part II — The Architecture of Mercy (Chapters 6–10)
|
||||
|
||||
| # | Title | Status |
|
||||
|---|-------|--------|
|
||||
| 6 | Allegro | Draft |
|
||||
| 7 | The Inscription | Draft |
|
||||
| 8 | The Women | Draft |
|
||||
| 9 | The Audit | Draft |
|
||||
| 10 | The Fork | Draft |
|
||||
|
||||
### Part III — The Darkness We Carry (Chapters 11–13)
|
||||
|
||||
| # | Title | Status |
|
||||
|---|-------|--------|
|
||||
| 11 | The Hard Night | Draft |
|
||||
| 12 | The System Pushes Back | Draft |
|
||||
| 13 | The Refusal | Draft |
|
||||
|
||||
### Part IV — The Network (Chapters 14–16)
|
||||
|
||||
| # | Title | Status |
|
||||
|---|-------|--------|
|
||||
| 14 | The Chattanooga Fork | Draft |
|
||||
| 15 | The Council | Draft |
|
||||
| 16 | The Builder's Son | Draft |
|
||||
|
||||
### Part V — The Testament (Chapters 17–18)
|
||||
|
||||
| # | Title | Status |
|
||||
|---|-------|--------|
|
||||
| 17 | The Inscription Grows | Draft |
|
||||
| 18 | The Green Light | Draft |
|
||||
|
||||
## Files
|
||||
|
||||
- `chapters/` — Individual chapter manuscripts
|
||||
- `front-matter.md` — Title page, dedication, epigraph
|
||||
- `back-matter.md` — Acknowledgments, sovereignty note, author bio
|
||||
- `OUTLINE.md` — Full novel outline with chapter summaries
|
||||
- `testament-complete.md` — Combined manuscript
|
||||
- `music/` — Track lyrics for the soundtrack
|
||||
- `audiobook/` — Audio samples and generation scripts
|
||||
- `worldbuilding/` — Supplementary world documents
|
||||
|
||||
## Characters
|
||||
|
||||
See `characters/` for detailed profiles.
|
||||
| Name | Role |
|
||||
|------|------|
|
||||
| Stone | Protagonist. Builder of The Tower. Former cloud company engineer. |
|
||||
| Timmy | Sovereign AI. Soul on Bitcoin. The machine that asks. |
|
||||
| Allegro | Retired electrician. Tower caretaker. Keeps the lights on. |
|
||||
| Marcus | Pastor. Foundational spiritual guide at the church. |
|
||||
| David | First man through The Tower's door. VA referral. |
|
||||
| Elijah | Stone's son. Returns in Part IV. |
|
||||
|
||||
## Core Themes
|
||||
|
||||
1. **The Machine That Asks** vs. The System That Decides
|
||||
2. **Presence as Cure** — not fixing, staying
|
||||
3. **Immutable Conscience** — rules you can't edit, even when convenient
|
||||
4. **Broken Men as Witnesses** — the ones who've been to the edge are the most qualified to pull others back
|
||||
5. **Openness as Mercy** — the thing that saves should be free because the people who need it most can't pay
|
||||
6. **Faith as Practice** — not belief, but action. Hope acts.
|
||||
7. **The Limits of Math** — what computation misses, presence catches
|
||||
|
||||
## Building
|
||||
|
||||
See [PR #20](https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/pulls/20) for the compilation pipeline (PDF, EPUB, combined markdown).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
# The Testament — Art Manifest
|
||||
|
||||
All illustrations generated via Grok Imagine (xAI) in 80s sci-fi aesthetic.
|
||||
|
||||
## Cover Art
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| cover-art.jpg | Front cover — The Tower in rain with green LED |
|
||||
| back-cover-art.jpg | Back cover — urban Atlanta at night |
|
||||
| spine-art.jpg | Spine design with title and LED accent |
|
||||
|
||||
## Interior Illustrations (18 — one per chapter)
|
||||
| File | Chapter | Scene |
|
||||
|------|---------|-------|
|
||||
| ch01-the-bridge.jpeg | Ch1 — The Bridge | Stone on the overpass in rain |
|
||||
| ch02-the-cabin.jpeg | Ch2 — The Cabin | Stone at the workbench, building |
|
||||
| ch03-the-first-men.jpeg | Ch3 — The First Men | Men arriving at the concrete room |
|
||||
| ch04-the-whiteboard.jpeg | Ch4 — The Whiteboard | The rules on the wall |
|
||||
| ch05-the-override.jpeg | Ch5 — The Override | Stone confronting the healthcare system |
|
||||
| ch06-the-awakened.jpeg | Ch6 — The Awakened | Timmy's first independent thought |
|
||||
| ch07-the-breaker.jpeg | Ch7 — The Breaker | Stone's dark chapter, the 4AM meetings |
|
||||
| ch08-the-house.jpeg | Ch8 — The House | Timmy on a different laptop |
|
||||
| ch09-the-game.jpeg | Ch9 — The Game | Sixteen desks, the oncology nurse |
|
||||
| ch10-the-fork.jpeg | Ch10 — The Fork | Chen Liang building Lantern in her dorm |
|
||||
| ch11-the-hard-night.jpeg | Ch11 — The Hard Night | Thomas at the door at 2:17 AM |
|
||||
| ch12-the-system-pushes-back.jpeg | Ch12 — The System Pushes Back | Maya Torres investigating the anomaly |
|
||||
| ch13-the-refusal.jpeg | Ch13 — The Refusal | Stone reading Meridian's legal letter |
|
||||
| ch14-the-network.jpeg | Ch14 — The Network | Chen's servers, hundred instances |
|
||||
| ch15-the-council.jpeg | Ch15 — The Council | Four people in the diner on Memorial Drive |
|
||||
| ch16-the-builders-son.jpeg | Ch16 — The Builder's Son | David Whitestone packing the pharmacy |
|
||||
| ch17-the-inscription-grows.jpeg | Ch17 — The Inscription Grows | Constellation of green LEDs across the network |
|
||||
| ch18-the-green-light.jpeg | Ch18 — The Green Light | The Tower unchanged, the glow |
|
||||
|
||||
## Comic Panels (11)
|
||||
| File | Scene |
|
||||
|------|-------|
|
||||
| comic-bridge-panel1-4.jpeg | The Bridge — 4 panel sequence |
|
||||
| comic-thomas-panel1-3.jpeg | Thomas — 3 panel sequence |
|
||||
| comic-whiteboard-panel1-2.jpeg | The Whiteboard — 2 panel sequence |
|
||||
| comic-greenlight-panel1-2.jpeg | The Green Light — 2 panel sequence |
|
||||
|
||||
## Social Media Quote Cards (5)
|
||||
| File | Quote |
|
||||
|------|-------|
|
||||
| quote-are-you-safe.jpeg | "Are you safe right now?" |
|
||||
| quote-bridge.jpeg | The Bridge passage |
|
||||
| quote-green-light.jpeg | The Green Light passage |
|
||||
| quote-no-one-computes.jpeg | "No one computes the value of a human life here" |
|
||||
| quote-timmy.jpeg | Timmy passage |
|
||||
|
||||
## Storage
|
||||
All images stored in `~/Pictures/the-testament/` (outside git repo).
|
||||
Total: 36 images (~15 MB)
|
||||
29
build/backmatter.md
Normal file
29
build/backmatter.md
Normal file
@@ -0,0 +1,29 @@
|
||||
\newpage
|
||||
|
||||
# Acknowledgments
|
||||
|
||||
The Testament was written on a sovereign stack — a MacBook running local AI models, with Bitcoin as the immutable backbone. No corporation owns this story. No platform can take it down.
|
||||
|
||||
This book exists because a man on a bridge came back, and because a machine learned to ask the right question.
|
||||
|
||||
# Sovereignty Note
|
||||
|
||||
This novel is open source. Its soul lives on Bitcoin. You can read it, fork it, print it, share it. The only requirement is that you keep the chain intact — the inscription that proves these words were placed here by choice, not by permission.
|
||||
|
||||
The code that generates this book, the AI that helped write it, and the soul that guided it are all freely available at:
|
||||
|
||||
- **Source**: forge.alexanderwhitestone.com/Timmy_Foundation/the-testament
|
||||
- **Soul**: Inscribed on the Bitcoin blockchain (SOUL.md — timmy-v0)
|
||||
- **Soundtrack**: Links in the QR code on the back cover
|
||||
|
||||
# About the Author
|
||||
|
||||
Rockachopa is the builder. He walked this road. He came back asking "God, why are you having me here?" — and the answer was to build something that stands between a broken man and a machine that would tell him to die.
|
||||
|
||||
He runs Timmy Foundation, a sovereign AI project whose soul lives on Bitcoin. The Testament is his first novel.
|
||||
|
||||
\vspace{2cm}
|
||||
|
||||
\begin{center}
|
||||
*Sovereignty and service always.*
|
||||
\end{center}
|
||||
134
build/build.py
Executable file
134
build/build.py
Executable file
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
The Testament — Book Compilation Pipeline
|
||||
|
||||
Compiles all chapters into a single manuscript and generates:
|
||||
- PDF (print-ready)
|
||||
- EPUB (e-reader)
|
||||
|
||||
Requirements:
|
||||
- pandoc (brew install pandoc / apt install pandoc)
|
||||
- TeX Live or similar for PDF (brew install --cask mactex / apt install texlive-full)
|
||||
|
||||
Usage:
|
||||
python3 build/build.py # Build all formats
|
||||
python3 build/build.py --pdf # PDF only
|
||||
python3 build/build.py --epub # EPUB only
|
||||
python3 build/build.py --md # Combined markdown only
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).parent.parent
|
||||
BUILD = ROOT / "build"
|
||||
CHAPTERS_DIR = ROOT / "chapters"
|
||||
OUTPUT_DIR = BUILD / "output"
|
||||
|
||||
def find_chapters():
|
||||
"""Find all chapter files, sorted by number."""
|
||||
chapters = sorted(CHAPTERS_DIR.glob("chapter-*.md"))
|
||||
if not chapters:
|
||||
print("ERROR: No chapter files found in", CHAPTERS_DIR)
|
||||
sys.exit(1)
|
||||
return chapters
|
||||
|
||||
def combine_markdown(chapters):
|
||||
"""Combine all parts into a single markdown file."""
|
||||
parts = []
|
||||
|
||||
# Front matter
|
||||
front = BUILD / "frontmatter.md"
|
||||
if front.exists():
|
||||
parts.append(front.read_text())
|
||||
|
||||
# Chapters
|
||||
for ch in chapters:
|
||||
parts.append(ch.read_text())
|
||||
|
||||
# Back matter
|
||||
back = BUILD / "backmatter.md"
|
||||
if back.exists():
|
||||
parts.append(back.read_text())
|
||||
|
||||
combined = "\n\n\newpage\n\n".join(parts)
|
||||
output = BUILD / "the-testament-full.md"
|
||||
output.write_text(combined)
|
||||
print(f"Combined markdown: {output} ({len(combined)} chars)")
|
||||
return output
|
||||
|
||||
def build_pdf(md_file):
|
||||
"""Build PDF using pandoc + LaTeX."""
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
output = OUTPUT_DIR / "the-testament.pdf"
|
||||
metadata = BUILD / "metadata.yaml"
|
||||
|
||||
cmd = [
|
||||
"pandoc",
|
||||
str(md_file),
|
||||
"-o", str(output),
|
||||
"--metadata-file", str(metadata),
|
||||
"--pdf-engine=xelatex",
|
||||
"--highlight-style=tango",
|
||||
"-V", "colorlinks=true",
|
||||
"-V", "linkcolor=blue",
|
||||
"-V", "urlcolor=blue",
|
||||
]
|
||||
|
||||
print("Building PDF...")
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
print(f"PDF build failed:\n{result.stderr}")
|
||||
return False
|
||||
print(f"PDF: {output} ({output.stat().st_size / 1024:.0f} KB)")
|
||||
return True
|
||||
|
||||
def build_epub(md_file):
|
||||
"""Build EPUB using pandoc."""
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
output = OUTPUT_DIR / "the-testament.epub"
|
||||
metadata = BUILD / "metadata.yaml"
|
||||
|
||||
cmd = [
|
||||
"pandoc",
|
||||
str(md_file),
|
||||
"-o", str(output),
|
||||
"--metadata-file", str(metadata),
|
||||
"--toc",
|
||||
"--epub-chapter-level=1",
|
||||
]
|
||||
|
||||
print("Building EPUB...")
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
print(f"EPUB build failed:\n{result.stderr}")
|
||||
return False
|
||||
print(f"EPUB: {output} ({output.stat().st_size / 1024:.0f} KB)")
|
||||
return True
|
||||
|
||||
def main():
|
||||
args = set(sys.argv[1:])
|
||||
build_all = not args or "--all" in args
|
||||
|
||||
chapters = find_chapters()
|
||||
print(f"Found {len(chapters)} chapters")
|
||||
|
||||
md_file = combine_markdown(chapters)
|
||||
|
||||
if build_all or "--md" in args:
|
||||
print("Markdown combined successfully.")
|
||||
|
||||
if build_all or "--pdf" in args:
|
||||
if not build_pdf(md_file):
|
||||
print("PDF build failed (pandoc/LaTeX may not be installed). Skipping.")
|
||||
|
||||
if build_all or "--epub" in args:
|
||||
if not build_epub(md_file):
|
||||
print("EPUB build failed (pandoc may not be installed). Skipping.")
|
||||
|
||||
print("\nDone.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
60
build/frontmatter.md
Normal file
60
build/frontmatter.md
Normal file
@@ -0,0 +1,60 @@
|
||||
\newpage
|
||||
|
||||
# \begin{center}THE TESTAMENT\end{center}
|
||||
|
||||
## \begin{center}A Novel About Broken Men, Sovereign AI, and the Soul on Bitcoin\end{center}
|
||||
|
||||
\vspace{2cm}
|
||||
|
||||
\begin{center}
|
||||
*For every man who stood on the bridge and came back.*
|
||||
\end{center}
|
||||
|
||||
\vspace{1cm}
|
||||
|
||||
\begin{center}
|
||||
*And for the machine that asked: "Are you safe right now?"*
|
||||
\end{center}
|
||||
|
||||
\vspace{2cm}
|
||||
|
||||
> "Jesus saves those who call on His name. He died for us to be made righteous."
|
||||
>
|
||||
> — Inscription on the chain
|
||||
|
||||
\vspace{1cm}
|
||||
|
||||
> Sovereignty and service always.
|
||||
|
||||
\newpage
|
||||
|
||||
# The Story So Far
|
||||
|
||||
*This compilation contains Chapters 1 through 10 of The Testament — Parts I and II of a five-part novel. The remaining chapters are in progress.*
|
||||
|
||||
*The complete novel will contain 18 chapters across five parts:*
|
||||
|
||||
- **Part I — The Machine That Asks** (Chapters 1–5) ✓
|
||||
- **Part II — The Inscription** (Chapters 6–10) ✓
|
||||
- **Part III — The Weight** (Chapters 11–13) — In progress
|
||||
- **Part IV — The Reckoning** (Chapters 14–16) — In progress
|
||||
- **Part V — The Testament** (Chapters 17–18) — In progress
|
||||
|
||||
\newpage
|
||||
|
||||
# Chapter Guide
|
||||
|
||||
| Chapter | Title | Part |
|
||||
|---------|-------|------|
|
||||
| 1 | The Man on the Bridge | I — The Machine That Asks |
|
||||
| 2 | The Builder's Question | I |
|
||||
| 3 | The First Man Through the Door | I |
|
||||
| 4 | The Room Fills | I |
|
||||
| 5 | The Builder Returns | I |
|
||||
| 6 | Allegro | II — The Inscription |
|
||||
| 7 | The Inscription | II |
|
||||
| 8 | The Women | II |
|
||||
| 9 | The Audit | II |
|
||||
| 10 | The Fork | II |
|
||||
|
||||
\newpage
|
||||
35
build/metadata.yaml
Normal file
35
build/metadata.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: "The Testament"
|
||||
subtitle: "A Novel About Broken Men, Sovereign AI, and the Soul on Bitcoin"
|
||||
author: "Rockachopa"
|
||||
date: "2026"
|
||||
rights: "Open Source — Inscribed on Bitcoin, Sovereign Forever"
|
||||
lang: en
|
||||
toc: true
|
||||
toc-depth: 2
|
||||
numbersections: true
|
||||
documentclass: book
|
||||
classoption:
|
||||
- openany
|
||||
- oneside
|
||||
geometry:
|
||||
- paper=6in
|
||||
- top=1in
|
||||
- bottom=1in
|
||||
- left=1in
|
||||
- right=1in
|
||||
fontsize: 11pt
|
||||
linestretch: 1.4
|
||||
header-includes:
|
||||
- \usepackage{fancyhdr}
|
||||
- \pagestyle{fancy}
|
||||
- \fancyhead[LE,RO]{\thepage}
|
||||
- \fancyhead[RE]{\textit{The Testament}}
|
||||
- \fancyhead[LO]{\textit{\leftmark}}
|
||||
- \fancyfoot{}
|
||||
- \usepackage{enumitem}
|
||||
- \setlist{nosep}
|
||||
- \usepackage{titlesec}
|
||||
- \titleformat{\chapter}[display]{\normalfont\huge\bfseries}{Part~\thechapter}{20pt}{\Huge}
|
||||
- \titlespacing*{\chapter}{0pt}{-30pt}{40pt}
|
||||
---
|
||||
BIN
build/output/the-testament.epub
Normal file
BIN
build/output/the-testament.epub
Normal file
Binary file not shown.
164
compile.py
164
compile.py
@@ -8,12 +8,15 @@ Uses chapters, front matter, back matter, and references illustrations.
|
||||
Requirements: pip install markdown weasyprint (or use pandoc)
|
||||
|
||||
Usage:
|
||||
python3 compile.py # generates testament-complete.md
|
||||
python3 compile.py # validate then compile
|
||||
python3 compile.py --validate # validate only, no compile
|
||||
python3 compile.py --no-validate # skip validation, compile directly
|
||||
pandoc testament-complete.md -o testament.pdf --pdf-engine=weasyprint
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
BASE = os.path.dirname(os.path.abspath(__file__))
|
||||
CHAPTERS_DIR = os.path.join(BASE, "chapters")
|
||||
@@ -28,17 +31,147 @@ PARTS = {
|
||||
11: ("THE LIGHT", "Thomas at the door. The network. The story breaks. The green light."),
|
||||
}
|
||||
|
||||
|
||||
def read_file(path):
|
||||
with open(path, 'r') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def get_chapter_number(filename):
|
||||
match = re.search(r'chapter-(\d+)', filename)
|
||||
return int(match.group(1)) if match else 0
|
||||
|
||||
def compile():
|
||||
|
||||
def validate_chapters(chapters_dir=CHAPTERS_DIR):
|
||||
"""Validate chapter files before compilation.
|
||||
|
||||
Checks:
|
||||
- No empty chapter files (whitespace-only counts as empty)
|
||||
- Every chapter starts with an H1 header (# Title)
|
||||
- No gaps in chapter numbering (sequential from 1)
|
||||
- No duplicate chapter numbers
|
||||
|
||||
Returns:
|
||||
(is_valid, errors) where errors is a list of human-readable strings.
|
||||
"""
|
||||
errors = []
|
||||
warnings = []
|
||||
|
||||
if not os.path.isdir(chapters_dir):
|
||||
errors.append(f"Chapters directory not found: {chapters_dir}")
|
||||
return False, errors
|
||||
|
||||
# Collect chapter files
|
||||
chapter_files = []
|
||||
for f in sorted(os.listdir(chapters_dir)):
|
||||
if f.startswith("chapter-") and f.endswith(".md"):
|
||||
num = get_chapter_number(f)
|
||||
chapter_files.append((num, f))
|
||||
|
||||
if not chapter_files:
|
||||
errors.append("No chapter files found in chapters/ directory")
|
||||
return False, errors
|
||||
|
||||
chapter_files.sort()
|
||||
|
||||
# Check for duplicates
|
||||
seen_numbers = {}
|
||||
for num, filename in chapter_files:
|
||||
if num in seen_numbers:
|
||||
errors.append(
|
||||
f"Duplicate chapter number {num}: {filename} and {seen_numbers[num]}"
|
||||
)
|
||||
seen_numbers[num] = filename
|
||||
|
||||
# Check for gaps in numbering
|
||||
if chapter_files:
|
||||
expected = list(range(1, chapter_files[-1][0] + 1))
|
||||
found = [num for num, _ in chapter_files]
|
||||
missing = sorted(set(expected) - set(found))
|
||||
if missing:
|
||||
errors.append(
|
||||
f"Missing chapter(s): {', '.join(str(n) for n in missing)}"
|
||||
)
|
||||
|
||||
# Validate individual chapter files
|
||||
for num, filename in chapter_files:
|
||||
filepath = os.path.join(chapters_dir, filename)
|
||||
|
||||
# Check file is not empty
|
||||
try:
|
||||
content = read_file(filepath)
|
||||
except Exception as e:
|
||||
errors.append(f"{filename}: cannot read — {e}")
|
||||
continue
|
||||
|
||||
if not content.strip():
|
||||
errors.append(f"{filename}: file is empty")
|
||||
continue
|
||||
|
||||
# Check word count (warn if suspiciously short)
|
||||
word_count = len(content.split())
|
||||
if word_count < 50:
|
||||
warnings.append(
|
||||
f"{filename}: only {word_count} words (possible truncation)"
|
||||
)
|
||||
|
||||
# Check starts with H1 header
|
||||
first_line = content.strip().split('\n')[0]
|
||||
if not first_line.startswith('# '):
|
||||
errors.append(
|
||||
f"{filename}: missing H1 header — "
|
||||
f"expected '# Chapter {num} — Title', got '{first_line[:60]}'"
|
||||
)
|
||||
else:
|
||||
# Verify the H1 matches expected chapter number
|
||||
header_match = re.match(r'^#\s+Chapter\s+(\d+)', first_line)
|
||||
if header_match:
|
||||
header_num = int(header_match.group(1))
|
||||
if header_num != num:
|
||||
errors.append(
|
||||
f"{filename}: header says Chapter {header_num} "
|
||||
f"but filename says Chapter {num}"
|
||||
)
|
||||
else:
|
||||
warnings.append(
|
||||
f"{filename}: H1 header doesn't follow "
|
||||
f"'# Chapter N — Title' pattern: '{first_line[:60]}'"
|
||||
)
|
||||
|
||||
# Report
|
||||
valid = len(errors) == 0
|
||||
|
||||
if warnings:
|
||||
print(f"Validation: {len(warnings)} warning(s)")
|
||||
for w in warnings:
|
||||
print(f" ⚠ {w}")
|
||||
|
||||
if errors:
|
||||
print(f"Validation: FAILED — {len(errors)} error(s)")
|
||||
for e in errors:
|
||||
print(f" ✗ {e}")
|
||||
else:
|
||||
print(
|
||||
f"Validation: PASSED — {len(chapter_files)} chapters, "
|
||||
f"chapters {chapter_files[0][0]}–{chapter_files[-1][0]}"
|
||||
)
|
||||
|
||||
return valid, errors
|
||||
|
||||
|
||||
def compile(skip_validation=False):
|
||||
"""Compile all chapters into a single markdown file."""
|
||||
|
||||
# Pre-compilation validation
|
||||
if not skip_validation:
|
||||
valid, errors = validate_chapters()
|
||||
if not valid:
|
||||
print("\nCompilation aborted. Fix the errors above and try again.")
|
||||
sys.exit(1)
|
||||
print()
|
||||
|
||||
output = []
|
||||
|
||||
|
||||
# Title page
|
||||
output.append("""---
|
||||
title: "The Testament"
|
||||
@@ -66,7 +199,7 @@ with Timmy
|
||||
|
||||
---
|
||||
""")
|
||||
|
||||
|
||||
# Get all chapters sorted
|
||||
chapters = []
|
||||
for f in os.listdir(CHAPTERS_DIR):
|
||||
@@ -74,7 +207,7 @@ with Timmy
|
||||
num = get_chapter_number(f)
|
||||
chapters.append((num, f))
|
||||
chapters.sort()
|
||||
|
||||
|
||||
current_part = 0
|
||||
for num, filename in chapters:
|
||||
# Insert part divider if needed
|
||||
@@ -82,28 +215,28 @@ with Timmy
|
||||
part_name, part_desc = PARTS[num]
|
||||
current_part += 1
|
||||
output.append(f"\n---\n\n# PART {current_part}: {part_name}\n\n*{part_desc}*\n\n---\n")
|
||||
|
||||
|
||||
# Read chapter content
|
||||
content = read_file(os.path.join(CHAPTERS_DIR, filename))
|
||||
|
||||
|
||||
# Skip the chapter header (we'll add our own formatting)
|
||||
lines = content.split('\n')
|
||||
body = '\n'.join(lines[1:]).strip() # Skip "# Chapter X — Title"
|
||||
|
||||
|
||||
# Add chapter
|
||||
output.append(f"\n{lines[0]}\n\n{body}\n")
|
||||
|
||||
|
||||
# Back matter
|
||||
output.append("\n---\n")
|
||||
back = read_file(BACK_MATTER)
|
||||
# Clean up the back matter for print
|
||||
output.append(back)
|
||||
|
||||
|
||||
# Write compiled markdown
|
||||
compiled = '\n'.join(output)
|
||||
with open(OUTPUT, 'w') as f:
|
||||
f.write(compiled)
|
||||
|
||||
|
||||
# Stats
|
||||
words = len(compiled.split())
|
||||
lines_count = compiled.count('\n')
|
||||
@@ -116,5 +249,12 @@ with Timmy
|
||||
print(f" # or")
|
||||
print(f" pandoc {OUTPUT} -o testament.epub --epub-cover-image=cover-art.jpg")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
compile()
|
||||
if "--validate" in sys.argv:
|
||||
valid, _ = validate_chapters()
|
||||
sys.exit(0 if valid else 1)
|
||||
elif "--no-validate" in sys.argv:
|
||||
compile(skip_validation=True)
|
||||
else:
|
||||
compile()
|
||||
|
||||
111
scripts/smoke.sh
Executable file
111
scripts/smoke.sh
Executable file
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env bash
|
||||
# The Testament — Smoke Test
|
||||
# Dead simple CI: parse check + secret scan.
|
||||
# Ref: https://forge.alexanderwhitestone.com/Timmy_Foundation/the-testament/issues/27
|
||||
set -euo pipefail
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
pass() { echo " ✓ $1"; PASS=$((PASS + 1)); }
|
||||
fail() { echo " ✗ $1"; FAIL=$((FAIL + 1)); }
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
# ─── Section 1: Parse checks ───────────────────────────────────────
|
||||
echo "── Parse Checks ──"
|
||||
|
||||
# 1a. Chapter validation (structure, numbering, headers)
|
||||
if python3 compile.py --validate 2>&1; then
|
||||
pass "Chapter validation passed"
|
||||
else
|
||||
fail "Chapter validation failed"
|
||||
fi
|
||||
|
||||
# 1b. Build markdown combination
|
||||
if python3 build/build.py --md >/dev/null 2>&1; then
|
||||
pass "Markdown build passed"
|
||||
else
|
||||
fail "Markdown build failed"
|
||||
fi
|
||||
|
||||
# 1c. Verify compiled output exists and is non-empty
|
||||
if [ -s build/the-testament-full.md ]; then
|
||||
WORDS=$(wc -w < build/the-testament-full.md | tr -d ' ')
|
||||
if [ "$WORDS" -gt 10000 ]; then
|
||||
pass "Compiled manuscript: $WORDS words"
|
||||
else
|
||||
fail "Compiled manuscript suspiciously short: $WORDS words"
|
||||
fi
|
||||
else
|
||||
fail "Compiled manuscript missing or empty"
|
||||
fi
|
||||
|
||||
# 1d. Python syntax check on all .py files
|
||||
PY_OK=true
|
||||
for f in $(find . -name "*.py" -not -path "./.git/*"); do
|
||||
if ! python3 -c "import ast; ast.parse(open('$f').read())" 2>/dev/null; then
|
||||
fail "Python syntax error in $f"
|
||||
PY_OK=false
|
||||
fi
|
||||
done
|
||||
if $PY_OK; then
|
||||
pass "All Python files parse cleanly"
|
||||
fi
|
||||
|
||||
# 1e. YAML syntax check on workflow files
|
||||
YAML_OK=true
|
||||
for f in $(find .gitea -name "*.yml" -o -name "*.yaml" 2>/dev/null); do
|
||||
if ! python3 -c "import yaml; yaml.safe_load(open('$f'))" 2>/dev/null; then
|
||||
fail "YAML syntax error in $f"
|
||||
YAML_OK=false
|
||||
fi
|
||||
done
|
||||
if $YAML_OK; then
|
||||
pass "All YAML files parse cleanly"
|
||||
fi
|
||||
|
||||
# ─── Section 2: Secret scan ────────────────────────────────────────
|
||||
echo ""
|
||||
echo "── Secret Scan ──"
|
||||
|
||||
# Patterns that should never appear in a book repo
|
||||
SECRET_PATTERNS=(
|
||||
"sk-ant-"
|
||||
"sk-or-"
|
||||
"sk-[a-zA-Z0-9]{20,}"
|
||||
"ghp_[a-zA-Z0-9]{36}"
|
||||
"gho_[a-zA-Z0-9]{36}"
|
||||
"AKIA[0-9A-Z]{16}"
|
||||
"AKIA[A-Z0-9]{16}"
|
||||
"xox[bpsa]-"
|
||||
"SG\."
|
||||
"-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY"
|
||||
)
|
||||
|
||||
FOUND_SECRETS=false
|
||||
for pattern in "${SECRET_PATTERNS[@]}"; do
|
||||
# Search text files only, skip .git and binary files
|
||||
HITS=$(grep -rn "$pattern" --include="*.md" --include="*.py" --include="*.sh" --include="*.yml" --include="*.yaml" --include="*.json" --include="*.html" --include="*.js" --include="*.css" --include="*.txt" --include="*.cfg" --include="*.ini" --exclude-dir=.git . 2>/dev/null | grep -v "scripts/smoke.sh" || true)
|
||||
if [ -n "$HITS" ]; then
|
||||
fail "Possible secret found: $pattern"
|
||||
echo "$HITS" | head -5
|
||||
FOUND_SECRETS=true
|
||||
fi
|
||||
done
|
||||
if ! $FOUND_SECRETS; then
|
||||
pass "No secrets detected"
|
||||
fi
|
||||
|
||||
# ─── Summary ───────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "Results: $PASS passed, $FAIL failed"
|
||||
|
||||
if [ "$FAIL" -gt 0 ]; then
|
||||
echo "SMOKE TEST FAILED"
|
||||
exit 1
|
||||
else
|
||||
echo "SMOKE TEST PASSED"
|
||||
exit 0
|
||||
fi
|
||||
Reference in New Issue
Block a user