-
Notifications
You must be signed in to change notification settings - Fork 126
Expand file tree
/
Copy pathdraft_chapter.py
More file actions
168 lines (145 loc) · 6.89 KB
/
draft_chapter.py
File metadata and controls
168 lines (145 loc) · 6.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#!/usr/bin/env python3
"""
Draft a single chapter using the writer model.
Usage: python draft_chapter.py 1
"""
import os
import re
import sys
from pathlib import Path
from dotenv import load_dotenv
BASE_DIR = Path(__file__).parent
load_dotenv(BASE_DIR / ".env")
WRITER_MODEL = os.environ.get("AUTONOVEL_WRITER_MODEL", "claude-sonnet-4-6")
API_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
API_BASE = os.environ.get("AUTONOVEL_API_BASE_URL", "https://api.anthropic.com")
CHAPTERS_DIR = BASE_DIR / "chapters"
def call_writer(prompt, max_tokens=16000):
import httpx
headers = {
"x-api-key": API_KEY,
"anthropic-version": "2023-06-01",
"anthropic-beta": "context-1m-2025-08-07",
"content-type": "application/json",
}
payload = {
"model": WRITER_MODEL,
"max_tokens": max_tokens,
"temperature": 0.8,
"system": (
"You are a literary fiction writer drafting a fantasy novel chapter. "
"You write in third-person limited past tense, locked to one POV character. "
"You follow the voice definition exactly. You hit every beat in the outline. "
"You never use words from the banned list. You show, never tell emotions. "
"Your prose is specific, sensory, grounded. Metaphors come from the character's "
"experience. You vary sentence length. You trust the reader. "
"You write the FULL chapter -- do not truncate, summarize, or skip ahead."
),
"messages": [{"role": "user", "content": prompt}],
}
resp = httpx.post(f"{API_BASE}/v1/messages", headers=headers, json=payload, timeout=600)
resp.raise_for_status()
return resp.json()["content"][0]["text"]
def load_file(path):
try:
return Path(path).read_text()
except FileNotFoundError:
return ""
def extract_chapter_outline(outline_text, chapter_num):
"""Extract a specific chapter's outline entry."""
pattern = rf'### Ch {chapter_num}:.*?(?=### Ch {chapter_num + 1}:|## Foreshadowing|$)'
match = re.search(pattern, outline_text, re.DOTALL)
return match.group(0).strip() if match else "(not found)"
def extract_next_chapter_outline(outline_text, chapter_num):
"""Extract the next chapter's outline (just first few lines for continuity)."""
next_entry = extract_chapter_outline(outline_text, chapter_num + 1)
if next_entry == "(not found)":
return "(final chapter)"
lines = next_entry.split('\n')[:10]
return '\n'.join(lines)
def main():
chapter_num = int(sys.argv[1])
# Load all context
voice = load_file(BASE_DIR / "voice.md")
world = load_file(BASE_DIR / "world.md")
characters = load_file(BASE_DIR / "characters.md")
outline = load_file(BASE_DIR / "outline.md")
canon = load_file(BASE_DIR / "canon.md")
# Chapter-specific context
chapter_outline = extract_chapter_outline(outline, chapter_num)
next_chapter = extract_next_chapter_outline(outline, chapter_num)
# Previous chapter (if exists)
prev_path = CHAPTERS_DIR / f"ch_{chapter_num - 1:02d}.md"
if prev_path.exists():
prev_text = prev_path.read_text()
prev_tail = prev_text[-2000:] if len(prev_text) > 2000 else prev_text
else:
prev_tail = "(first chapter -- no previous)"
prompt = f"""Write Chapter {chapter_num} of "The Second Son of the House of Bells."
VOICE DEFINITION (follow this exactly):
{voice}
THIS CHAPTER'S OUTLINE (hit every beat):
{chapter_outline}
NEXT CHAPTER'S OUTLINE (for continuity -- end this chapter so it flows into the next):
{next_chapter}
PREVIOUS CHAPTER'S ENDING (continue from here):
{prev_tail}
WORLD BIBLE (reference for worldbuilding details):
{world}
CHARACTER REGISTRY (reference for speech patterns and behavior):
{characters}
WRITING INSTRUCTIONS:
1. Write the COMPLETE chapter. Target ~3,200 words. Do not truncate or summarize.
2. Third-person limited, past tense, locked to Cass's POV.
3. Hit ALL numbered beats from the outline in order.
4. Plant ALL foreshadowing elements listed under "Plants."
5. Show sensory detail: what Cass hears, smells, feels physically.
6. The under-note causes specific physical pain (needle behind left eye, not vague discomfort).
7. Dialogue follows the speech patterns defined in characters.md.
8. No banned words from voice.md Part 1 guardrails.
9. No AI fiction tells: no "a sense of," no "couldn't help but feel," no "eyes widened."
10. Vary sentence length. Short sentences for impact. Longer ones to build.
11. Metaphors from Cass's experience: sound, bronze, craft, the body's response to pitch.
12. Trust the reader. Don't explain what scenes mean. Let them land.
13. Start the chapter in scene, not with exposition. End on a moment, not a summary.
PATTERNS TO AVOID (these have been flagged in previous chapters):
14. NO triadic sensory lists. Never "X. Y. Z." or "X and Y and Z" as three
separate items in a row. Combine two, cut one, or restructure.
15. NO "He did not [verb]" more than once per chapter. Convert negatives
to active alternatives or just cut them.
16. NO "He thought about [X]" constructions. Replace with: the thought
itself as a fragment, a physical action, or dialogue.
17. NO "the way [X] did [Y]" as a simile connector more than twice per
chapter. Use different simile structures or cut the comparison.
18. NO over-explaining after showing. If a scene demonstrates something,
do not have the narrator restate it. Trust the scene.
19. NO section breaks (---) as rhythm crutches. Only use for genuine
time/location jumps. Max 2 per chapter.
20. VARY paragraph length deliberately. Never more than 3 consecutive
paragraphs of similar length. Include at least one 1-2 sentence
paragraph and one 6+ sentence paragraph.
21. END the chapter differently from previous chapters. Do NOT end with
Cass outside listening to his father work. Find the ending that
belongs to THIS chapter specifically.
22. INCLUDE at least one moment that surprises -- a character saying
the wrong thing, an emotional beat arriving early or late, a detail
that doesn't fit the expected pattern. Predictable excellence is
still predictable.
23. FAVOR scene over summary. At least 70% of the chapter should be
in-scene (moment by moment, with dialogue and action) rather than
summary (narrator compressing time).
24. DIALOGUE should sound like speech, not prose. Characters should
occasionally stumble, interrupt, trail off, or say something
slightly wrong. A 14-year-old does not speak in polished epigrams.
Write the chapter now. Full text, beginning to end.
"""
print(f"Drafting Chapter {chapter_num}...", file=sys.stderr)
result = call_writer(prompt)
# Save
out_path = CHAPTERS_DIR / f"ch_{chapter_num:02d}.md"
out_path.write_text(result)
print(f"Saved to {out_path}", file=sys.stderr)
print(f"Word count: {len(result.split())}", file=sys.stderr)
print(result)
if __name__ == "__main__":
main()