Compare commits
4 Commits
dev
...
burn/20260
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
948d520b83 | ||
| bebd3943d4 | |||
|
|
1d4e8a6478 | ||
| d0680715ac |
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
|
||||
|
||||
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user