10 Commits

Author SHA1 Message Date
Alexander Whitestone
08233364ff burn: add smoke test workflow — parse check + secret scan
All checks were successful
Smoke Test / smoke-test (pull_request) Successful in 10s
Build Validation / validate-manuscript (pull_request) Successful in 9s
Smoke Test / smoke-test (push) Successful in 8s
Closes #27

Adds a dead-simple CI smoke test that runs on every PR and push to main:

Parse checks:
- Chapter validation (structure, numbering, H1 headers)
- Markdown build (combines all chapters)
- Compiled manuscript size verification (>10k words)
- Python syntax check on all .py files
- YAML syntax check on workflow files

Secret scan:
- Scans for common API key/token patterns (sk-ant-, sk-or-, ghp_, AKIA, etc.)
- Searches all text files, excludes .git and the smoke test itself
- Hard fail if any secrets found

Two files:
- scripts/smoke.sh — the smoke test script
- .gitea/workflows/smoke.yml — Gitea Actions workflow
2026-04-10 20:58:16 -04:00
544bc1a985 Merge pull request 'feat: add CI workflow for manuscript build validation' (#25) from feat/ci-build-validation into main
Merged PR #25: feat: add CI workflow for manuscript build validation
2026-04-11 00:44:01 +00:00
ba9fd0ba08 Merge pull request 'burn: add chapter validation to build pipeline (closes #24)' (#26) from burn/20260410-chapter-validation into main
Merged PR #26: burn: add chapter validation to build pipeline
2026-04-11 00:43:38 +00:00
8ba9f58e96 Merge pull request 'feat: add book compilation pipeline (rescued from #20)' (#28) from rescue/book-compilation into main
Merged PR #28: feat: add book compilation pipeline
2026-04-11 00:43:36 +00:00
Alexander Whitestone
f6d74e233b feat: add book compilation pipeline (rescued from #20)
Build system for The Testament:
- build/build.py: compiles chapters to PDF, EPUB, MD
- build/metadata.yaml: book metadata
- build/frontmatter.md: title page, dedication
- build/backmatter.md: acknowledgments, sovereignty note
- Makefile: make pdf, make epub, make md
- .gitignore: build artifacts
2026-04-10 20:32:38 -04:00
Alexander Whitestone
948d520b83 burn: add chapter validation to build pipeline (closes #24)
Add validate_chapters() function that checks:
- No empty chapter files (whitespace-only counts as empty)
- Every chapter starts with an H1 header (# Chapter N — Title)
- No gaps in chapter numbering (sequential from 1)
- No duplicate chapter numbers
- Header chapter number matches filename number
- Warns on suspiciously short chapters (<50 words)

Validation runs automatically before compilation. If errors are found,
compilation is aborted with clear error messages showing exactly what
to fix.

CLI flags:
  python3 compile.py --validate     # validate only
  python3 compile.py --no-validate  # skip validation
  python3 compile.py                # validate then compile
2026-04-10 19:57:27 -04:00
7a56b4b727 feat: add CI workflow for manuscript build validation
Some checks failed
Build Validation / validate-manuscript (pull_request) Failing after 5s
2026-04-10 23:55:17 +00:00
bebd3943d4 [auto-merge] README update
Auto-merged by PR review bot: README update
2026-04-10 11:48:32 +00:00
Alexander Whitestone
1d4e8a6478 burn: update README with full 18-chapter structure, characters, themes
Closes #21

The README previously listed only Chapter 1 with 'Draft' status.
Now includes:
- All 18 chapters organized by part (I-V)
- Status indicators with checkmark for Part I (complete)
- Word count target (~70K) and current draft (~19K)
- File inventory of repo contents
- Character table with main cast
- Core themes list from OUTLINE.md
- Link to compilation pipeline PR #20
2026-04-10 06:42:59 -04:00
d0680715ac Merge pull request #19
Merged PR #19
2026-04-10 03:43:49 +00:00
14 changed files with 686 additions and 91 deletions

View 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

View 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
View File

@@ -1 +1,6 @@
__pycache__/
# Build intermediate files
build/the-testament-full.md
*.aux
*.log
*.out
*.toc

View File

@@ -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
View 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

View File

@@ -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 15) ✅ 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 610)
| # | 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 1113)
| # | Title | Status |
|---|-------|--------|
| 11 | The Hard Night | Draft |
| 12 | The System Pushes Back | Draft |
| 13 | The Refusal | Draft |
### Part IV — The Network (Chapters 1416)
| # | Title | Status |
|---|-------|--------|
| 14 | The Chattanooga Fork | Draft |
| 15 | The Council | Draft |
| 16 | The Builder's Son | Draft |
### Part V — The Testament (Chapters 1718)
| # | 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

View File

@@ -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
View 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
View 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
View 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 15) ✓
- **Part II — The Inscription** (Chapters 610) ✓
- **Part III — The Weight** (Chapters 1113) — In progress
- **Part IV — The Reckoning** (Chapters 1416) — In progress
- **Part V — The Testament** (Chapters 1718) — 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
View 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}
---

Binary file not shown.

View File

@@ -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
View 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