Markus Michalski FAQ

StoryForge Banlist Architecture

Where StoryForge stores banned phrases and patterns, which scanner reads which source, which format each source expects, and how to decide where a new rule belongs.

StoryForge Banlist Architecture

Deutsche Version

Back to StoryForge Main Page


What This Is About

StoryForge has several places where banned phrases and patterns live, and two scanners that read them: the prose hook and the manuscript checker. This page is the map: what lives where, who scans what, in which format and at which level.

If you wonder where a new rule belongs, jump to the decision tree.


The Two Scanners

Scanner When Mechanics Consequence
Prose hook (hooks/validate_chapter.py) PostToolUse: after every Write, Edit and MultiEdit on draft.md The file is already written. Severity block gives exit code 2 in strict mode and feeds the message back to the model through stderr the model has to fix the hit before it continues
Manuscript checker (/storyforge:manuscript-checker) called by you, scans the whole book report at research/manuscript-report.md and a gate result in the tool response does not block any write, see below

The hook checks chapter by chapter right after writing, the checker the finished book. Both read the same sources and use mostly the same category names, but report with their own levels. The one difference for catalog words: the hook calls the category ai_tell, the checker ai_tell_violation.

The manuscript checker's gate is FAIL when there is a book_rule_violation finding whose rule documents no exception of its own. The gate is not in the Markdown report, only in the gate key of the scan_manuscript tool response. The manuscript checker is not part of run_pre_export_gates, so a FAIL does not stop an export by itself. Both scanners also read storage G, the tone rules from the book's plot/tone.md.


The Storage Locations

# Place Where Content
A Book database of the series or book (book rules, type rule) rules of the book, also visible in CLAUDE.md under ## Rules (from DB)
B Author ~/.storyforge/db/authors.db, type donts banned phrases, patterns and don'ts of the author
C Author authors.db, type recurring_tics recurring tics of the author
D Author authors.db, type style_principles positive style patterns, not scanned
E Catalog reference/craft/anti-ai-patterns.md section 1: ### Heavily Flagged Words and Phrases
F Catalog reference/craft/anti-ai-patterns.md section 11: lines with **Banned shape:**
G Book plot/tone.md, "Banned Prose Patterns" section banned patterns of the book's tone brief

An author's vocabulary.md file is reference only and is not scanned. Banned words that sit in such a file are carried into storage B by scripts/migrate_vocabulary_to_db.py. create_author additionally seeds 19 default AI-tell words (delve, tapestry, nuanced, ...) straight into B as flat phrases — they take effect immediately as author_vocab_violation and no longer appear in vocabulary.md. If that database write fails, the author is still created; create_author's response then carries a warning field pointing to add_vocabulary_entry to add them afterward.

For storage B and C to apply to a book, the hook and the manuscript checker first have to resolve its author: they read the - **Author:** ... line from the book's CLAUDE.md and slugify the name. When that line is missing — the standard workflow never writes it — both fall back to the author: field in the README.md YAML frontmatter instead, which create_book_structure sets on every book. Without either source, B and C stay empty for that book.


Who Scans What

Storage Hook Hook level Hook category Manuscript checker MC weight MC category
A book rules yes, backticks only block book_rule_violation yes, backticks and quotes with a ban cue high book_rule_violation
B author don'ts, flat phrases yes block author_vocab_violation yes high author_vocab_violation
B author don'ts, bold title with no pattern (fallback) yes block author_vocab_violation yes high author_vocab_violation
B author don'ts, backtick and italic yes block author_rule_violation yes high author_rule_violation
C author tics yes block or warn writing_discovery_violation yes medium or high writing_discovery_violation
D style principles no no
E catalog section 1 yes warn ai_tell yes medium ai_tell_violation
F catalog section 11 yes warn global_shape_violation yes medium global_shape_violation
G tone rules (book) yes, backticks only block book_rule_violation yes, like a book rule high book_rule_violation

The hook reports the catalog layers (E, F) as warn, the manuscript checker as medium. The catalog layers E and F apply only to books written in English, see the E and F subsections below.

The manuscript checker's report sorts every finding into a fixed order (_CATEGORY_RANK): first the two meta-findings book_rules_unreadable and callbacks_unreadable — they signal that the book-rules or callback database itself could not be read (e.g. a series book not yet linked via add_book_to_series), with no text findings of their own. Then come book_rule_violation, author_rule_violation, author_vocab_violation, writing_discovery_violation, global_shape_violation, ai_tell_violation, anonymization_leak, plot_hole and cliche. Everything else is appended by severity.


Formats per Storage

Every storage has its own parser rules. A pattern in the wrong format is silently ignored.

A: Book rules

A rule is a text. The hook pulls out only backtick patterns, the checker more:

Form Hook Manuscript checker
backtick regex (`\b...\b`) yes, hard yes
backtick literal (`delve`) yes, hard yes
quoted phrase with a ban cue (Never, Avoid, nie, kein ...) in the same sentence no yes, if at least six characters
italic (*phrase*) no no (treated as a narrative example)
plain text no no

A backtick text with regex characters (| ( ) [ ] \ ^ $ ? + * { }) is compiled as a regular expression — through compile_backtick_pattern, a single function shared by the hook and the manuscript checker, applying the same way to book rules (A), author don'ts (B) and author tics (C). It has a guard against costly expressions: the text becomes a literal only when a quantified group (+, * or an open {n,}) itself carries an unbounded quantifier inside its own body, for example (a+)+. Flagging every quantified group, not only nested ones, would also catch harmless expressions: (foo)+ stays a regex because the group's body itself repeats nothing, and (?:being){2} stays a regex because {2} is a fixed upper bound, not an open {n,}.

Per-chapter cap: an addition such as max N per chapter activates a per-chapter cap. This applies to book rules (A) and author tics (C), not to don'ts (B). In the hook the limit is scaled by the length of the current draft relative to the chapter's target word count. The manuscript checker does not know this limit for book rules and reports all hits there; for tics (C) it does evaluate it, see section C.

B: Author don'ts

An entry can be flat (one phrase), carry only a bold title with no pattern, carry backticks, or carry italic examples with a ban cue. The hook and the checker extract to different extents:

Form Hook Manuscript checker
flat phrase without backticks and bold (aliases with /, parenthetical notes are removed) yes, with inflections and a word boundary at the start yes
line that starts with a bold title, with no backtick and no italic example phrase after a ban cue (falls back to the title text) yes, like a flat phrase: with inflections and a word boundary at the start yes, like a flat phrase
backtick regex or literal yes, always; a literal is a plain substring without inflection and word boundary yes, always
italic with a ban cue yes, only before a recommendation marker yes, only before a recommendation marker
quote with a ban cue no yes, only before a recommendation marker

Entries written by write_author_banned_phrase are backtick literals: `delve` matches redelve, but not delving. Inflection and word boundary exist only for flat entries (add_vocabulary_entry, migration from vocabulary.md) and for the bold-title fallback.

A donts row with a bold title but no backtick and no italic example phrase after a ban cue falls back to the title text itself, provided the bold sits directly at the start of the line: load_author_vocab reads it like a flat phrase. That way every such row is enforced by exactly one of the two loaders — either here as a fallback literal, or, when the entry does carry a backtick pattern or an italic example phrase with a ban cue, by load_author_dont_rules (next row below).

For italic patterns, trailing punctuation is stripped so that *The room received it.* also hits The room received it without complaint.

Recommendation markers: Render, Replace, Use instead, Instead:, Allowed:, Better:, Rewrite as, Rather: and →. The marker window (extract_ban_window) is the same function in the hook and the manuscript checker: both extract italic phrases only from the slice before the first marker, after which they count as positive examples. The checker additionally applies the window to quotes; the hook does not extract quotes for don'ts at all (see the table above). Backticks are extracted by both scanners regardless of position.

- **Never personify rooms** — *The room received it.* / *the silence held it.*
  Render the impact through a named body: *Caelan's eyes did not move*

In the example, both scanners treat only The room received it. and the silence held it. as bans; Caelan's eyes did not move follows Render and counts as a recommendation, in the hook too. For recommendations inside don'ts that means: put them after one of the markers — whether they sit in italics there or not no longer matters for extraction.

C: Author tics

A bullet with a bold title. The phrase is searched in this order:

Priority Form When
1 quote in the bold title (**Vague-noun "thing" as fallback**) main path
2 backticks or quotes in the bullet text when the title has no quote
3 text of the bold title when neither title nor text carry anything scannable

That way German rule titles with English example phrases in the text work. Additions in the bullet control the level in the hook: [warn] or [advisory] turns block into warn, Max N per chapter (also Max N–M pro Kapitel, Max einmal pro Kapitel) allows up to N hits per chapter, scaled to the length of the current draft. The manuscript checker now evaluates both additions too: [warn]/[advisory] gives weight medium there instead of high, and a chapter at or under the cap produces no finding at all — evaluated per chapter of the finished draft, not against the manuscript-wide total.

D: Style principles

Not machine-scanned, deliberately. They are narrative instructions to chapter-writer and chapter-reviewer, not phrase banlists. They are not part of the chapter writing brief: both skills load them through get_author.

E: Catalog section 1

A numbered list under ### Heavily Flagged Words and Phrases. Each entry sets its terms in bold, followed by the reason after a dash:

1. **Delve** / **Delve into** — One of the most notorious AI tells.
2. **Tapestry** (metaphorical) — "A rich tapestry of..." Almost never used by humans.

Each bold term is read separately, so Delve and Delve into. The / between the bold terms is only notation: the loader does not split inside a bold term at /, so every term must carry its own **. A parenthetical note such as (metaphorical) outside the bold is not read. If a parenthetical note sits inside the bold term, the loader removes it. Single words match inflections. Level: warn in the hook, medium in the checker. They are hints, not a user-asserted rule. Applies only to books written in English (language from language in the README frontmatter); for other languages there is currently no catalog, so these hits are dropped.

Only the split at / exists exclusively for flat don'ts (B). The loader also removes parenthetical notes for flat don'ts, for bold catalog terms and for tic phrases.

F: Catalog section 11

Lines in section 11 with this exact form:

**Banned shape:** `\bregex_here\b`

Only the text in backticks after **Banned shape:** is compiled as a regular expression. One regex per shape. Level: warn in the hook, medium in the checker. An invalid expression is skipped. Lines with an addition such as **Banned shape (density):** do not match this form and are not loaded; at present six lines are loaded. Applies only to books written in English (language from language in the README frontmatter); for other languages there is currently no catalog, so these hits are dropped.

G: Tone rules

The "Banned Prose Patterns" section in a book's plot/tone.md, heading at any level from ## to ######. Bullets with a phrase in backticks are enforceable, using the same compile_backtick_pattern as A, B and C; an addition such as max N per chapter caps hits per chapter exactly like book rules. Bullets without backticks stay a plain hint that no scanner enforces — visible to reviewers and the humanizer, not to the hook or the manuscript checker.


Check When Writing Discoveries

When you create a discovery with write_author_discovery(validate=True), a section-aware check runs and attaches warnings and extracted_patterns to the response. The write always goes through. The check shows what the scanner will actually enforce, and it follows the extractor of the manuscript checker:

Section Code Finding
donts mixed_positive_negative_italics italics on both sides of a recommendation marker. Italic phrases after the marker are not extracted as a ban by either scanner — the check surfaces this asymmetry so you can verify they really are meant as a recommendation.
donts mixed_positive_negative_quotes several quotes under one ban cue
donts scanner_extracts_nothing ban cue without backtick, quote or italic, the rule does not take effect
donts, recurring_tics, style_principles bracket_placeholder [word] in backticks, which is a character class and not a placeholder
recurring_tics bold_title_unscannable non-ASCII title without a pattern in the text

Style principles deliberately produce no gap warnings. For book rules the same idea exists with lint_book_rules and /storyforge:rules-audit, see Quality System.


Decision Tree

Where do I put a pattern that should be blocked or reported?

  1. Is it a universal AI tell that affects every author?

    • single word (delve, tapestry): E, catalog section 1
    • phrase shape (the room received it): F, catalog section 11
    • effect: warn in the hook, medium in the checker, for all authors automatically. This is a change to the plugin itself.
  2. Does it apply to all books of one author, so it is a trait of their voice?

    • banned phrase or pattern: B (write_author_banned_phrase)
    • recurring tic from finished books, with a per-chapter limit or markable as warn/advisory — both scanners evaluate it: C (harvest-author-rules or write_author_discovery with recurring_tics)
    • narrative instruction without a scannable pattern: D
    • effect: block for every book of the author (for C, depending on marking, warn in the hook resp. medium in the checker)
  3. Does it apply to this one book only?

    • pattern, a setting, plot or voice detail, in the hook also with a per-chapter limit (max N per chapter): A (append_book_rule)
    • a banned pattern that belongs to the book's tone brief: G (the "Banned Prose Patterns" section in plot/tone.md)
    • effect: block only in this book

Often misassigned:

Temptation Correct
"AI tell, so a book rule" catalog (E or F)
"Voice trait of the author, so vocabulary.md" The file is not read. A phrase or shape belongs in B, a recurring tic in C.
"A style principle as a scannable pattern" D is not scanned. If it should block, it is a don't in B.

Overlaps: Who Wins

When the same pattern exists in several places:

Case Result
book rule (A) and author entry (B or C) both report, the categories are independent
author entry (B, C) and catalog (E, F) in the hook both report: the author entry as block, the catalog as warn
author entry and catalog in the manuscript checker the catalog finding (ai_tell_violation, global_shape_violation) is dropped on the line that an author entry already hits
flat phrase (B), tic (C) and don't (B) in the hook the same phrase only once: flat phrase first, then tic, then don't
book rule (A) and tone rule (G) with the same pattern in the hook reports only once

Dedup keeps no mute lists. If a catalog pattern reports falsely, you can only narrow it in the catalog (a change to the plugin) or deliberately not copy it into an author don't, so it stays at warn.



← Back to StoryForge Main Page