Skip to content

Weaver 0.3: doctor, repair path, style rules + hooks, approvals, character voice - #9

Merged
MrPickering merged 10 commits into
mainfrom
agent/weaver-s4-voice
Sep 26, 2026
Merged

MrPickering merged 10 commits into
mainfrom
agent/weaver-s4-voice

Conversation

@MrPickering

Copy link
Copy Markdown
Contributor

Please squash-merge. This combines the stacked PRs #4, #5, #6, #7 and #8 into one commit on main. Those PRs will be closed as superseded once this lands.

What's in it

Verification

  • npm run preflight: 51/51, leakscan, consumer install. CI runs on Linux, Windows and macOS.
  • End-to-end simulation of a 12-chapter, 17,500-word, four-POV book with planted flaws:
    • 4/4 style-rule flaws and 2/2 voice swaps caught, with no voice false positives.
    • Headless Claude Code repaired every flagged chapter on the first attempt. Every changed paragraph was a flagged one, and untouched chapters stayed byte-identical.
    • The DOCX export opens correctly in Word.

🤖 Generated with Claude Code

MrPickering and others added 10 commits September 25, 2026 09:41
A book pipeline that travels as one zip holding both the tool and the
book, edited by chat-only AIs, cannot work: every session can change the
tool, and nothing can tell a modified tool from the released one. Weaver
now checks its setup and refuses, in plain words, to write into a book
when that setup is unsafe.

- `weaver doctor [--root] [--json]` reports: Node >= 22, Git available,
  book under Git, tool and book in separate folders (including a copied
  Weaver engine inside the book), install integrity, and a note that a
  command-running AI host is required.
- state:accept, release and `grounding --output` refuse when anything is
  blocking and write nothing. Read-only commands still run and warn on
  stderr. No flag or environment variable bypasses a refusal.
- `weaver init` runs `git init` in new books.
- integrity.json: a committed, lockfile-style sha256 manifest of the
  published package, hashed with CRLF normalised to LF so Windows, Linux
  and npm installs agree. `npm test` fails when it is stale.
- Tests prove each blocking check can fail, including tampering with an
  installed copy from the packed tarball (doctor reports `modified`,
  release refuses).

Verified: npm run preflight (19/19, leakscan, smoke, consumer install
with tamper break-test); manual refusals for a book outside Git and a
copied engine inside a book.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Session-Id: 04db4d93-9884-44ee-b28f-627e92b33731
Agent: claude-code
Weaver could only draft forward from an outline. Most real requests are
repairing a finished manuscript whose original must never change.

- `weaver import <file|folder>` reads DOCX (stdlib ZIP/XML), Markdown,
  text, or a folder of chapter files. It splits chapters on headings or
  "Chapter N" lines and scenes on break lines, and takes POV from the
  chapter title (an untitled chapter carries the previous POV). It
  refuses a book that already has scenes, writes an import record, and
  never touches the source (sha256 checked before and after).
- Word specifics: hyperlink text and field results are kept; footnotes,
  endnotes and comments are left out and counted in the summary.
  Unaccepted tracked changes are refused: the author decides which
  changes stand, not the importer.
- Scene prose stores italics as *x* and escapes literal asterisks and
  backslashes, so "f***" and "5 * 3" survive a round trip. Export writes
  literal scene breaks, never through the italics parser.
- `weaver export --format md|docx` writes deterministic output and
  refuses to overwrite files or target the source's name.
- `weaver init --empty` starts a book for import.
- Imported books keep the stale-state release gate: `check` reports it
  and `release` refuses until narrative state is current.
- The CLI prints plain error messages, not stack traces.
- README: "How PickBits Weaver works" states the setup requirements
  up front.

Verified: npm run preflight (29/29). A manuscript written by Microsoft
Word (hyperlink, field, footnote, comment, italics, curly quotes, em
dash, tracked change) is refused while changes are pending, and imports
correctly once they are accepted. Weaver's DOCX export opens in Word with
literal asterisks, a literal scene break, correct italics and a Title
style.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Session-Id: 04db4d93-9884-44ee-b28f-627e92b33731
Agent: claude-code
…e hooks

Authors set style rules; AI assistants forget them, especially after a
repair. Rules are now deterministic scripts, run after every AI edit and
enforced at the gates.

- quality/rules.json per book (contracts/rules.schema.json). Rule types:
  count-per-words, phrase-list, sentence-start-share,
  repeated-sentence-starts, max-per-paragraph, regex. Severity block|warn;
  scope all|narration|dialogue (quote-based, with multi-paragraph speech).
- Rules run on reader text, so escaped asterisks are never markup and
  italics never hide a word; curly and straight apostrophes match alike.
- `weaver rules` reports findings; `weaver check` adds one issue for
  blocking findings, so release refuses. The release manifest is unchanged.
- Starter pack in new books: warn-level rules (em dashes, common AI-style
  phrases, repeated sentence openings) and quality/editorial-rules.md
  (R1-R9, story-agnostic) with a feedback log.
- `weaver hooks install` merges a Claude Code hook pack into
  .claude/settings.json: PreToolUse injects the editorial rules and flags
  net-add edits; PostToolUse runs the rules on the edited scene and
  returns blocking findings to the AI. Both fail open; check and release
  still enforce.

Verified: npm run preflight (36/36). End to end in real Claude Code
(headless, packed tarball installed in a fresh book, a blocking
phrase-list rule): the pre hook delivered the rules and the net-add note,
the post hook blocked with the finding, and the assistant surfaced the
conflict to the author instead of rewording on its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Session-Id: 04db4d93-9884-44ee-b28f-627e92b33731
Agent: claude-code
AI edits to scene files are pending until the author approves them.
Approval is a Git snapshot; rejection restores the last approved text and
keeps what was rejected; undo reverses an approval without rewriting
history. One chapter at a time.

- `weaver changes`: pending scene changes per chapter, with word deltas
  and style findings.
- `weaver approve [--chapter N] [--note]`: commits only that chapter's
  scene files, recomputes each scene's Words footer, and adds an
  Approved-with trailer. Refuses when nothing is pending, when changes
  span chapters without --chapter, or on blocking style findings. Sets a
  repo-local Git identity only when none exists at all, so an author's
  own identity is never overridden and a new machine never hits Git's
  "tell me who you are" error.
- `weaver reject`: saves pending text under .weaver/rejected/ (kept on
  disk, ignored by Git) before restoring; new untracked scenes are moved
  there, never deleted.
- `weaver undo`: `git revert` of the latest approval (a new commit),
  refused with pending changes; `weaver history` lists approvals.
- The PostToolUse hook blocks edits to a second chapter while another
  chapter awaits the author.
- `weaver init` writes .gitignore, installs the Claude Code hooks, and
  makes a baseline commit. Doctor adds hooks-installed (blocking) and
  hooks-runnable (warning, since hooks fail open if Weaver isn't installed
  in the book).
- No reset, clean, force or history rewriting anywhere.

Verified: npm run preflight (43/43). Tests read Git's own record (log,
show --stat). The identity test fails against a local-only check and
passes with the fix. A manual author walkthrough (changes, approve with
note, history, undo, reject) produces plain messages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Session-Id: 04db4d93-9884-44ee-b28f-627e92b33731
Agent: claude-code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Session-Id: 04db4d93-9884-44ee-b28f-627e92b33731
Agent: claude-code
…eline

In multi-POV fiction, AI assistants blur characters. Every dialogue
change is now checked against how that character actually speaks, with
their own lines as evidence. It is a judgement aid, never a verdict.

- world-bible/characters.json registry (contracts/characters.schema.json);
  `weaver characters seed` adds POV characters from an import.
- Deterministic dialogue attribution: speech tags, the single-named-
  character action beat, and multi-paragraph continuation. Anything else
  stays unattributed; speakers are never guessed.
- `weaver voices build`: per-character profiles from APPROVED text (Git
  HEAD): sentence length, contractions, profanity, formal markers,
  exclamation/question share, distinctive words, and typical evidence
  lines. Characters under 8 lines / 80 words get "not enough of their
  lines yet" instead of a verdict. Approve rebuilds profiles; pending
  edits never make them stale.
- `weaver voices check`: out-of-voice findings with concrete numbers
  ("one 17-word sentence; Grell averages 4.2") and "maybe sounds like"
  another character, in both directions. Always warnings, surfaced in
  `changes`, `check` and the PostToolUse hook; never blocking.
- `weaver import` now records the author's original as the approved
  starting point ("Import original manuscript"), so repairs are reviewed
  against it one chapter at a time instead of the whole book sitting
  pending (which deadlocked approval on the very flaws being repaired).

Verified: npm run preflight (48/48). A hold-out test with characters
unlike the tuning fixture (a terse dockhand and a formal magister)
catches swaps in both directions with evidence, leaves in-character and
pronoun-only lines alone, and is now a permanent test. The import-
baseline test fails without the feature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Session-Id: 04db4d93-9884-44ee-b28f-627e92b33731
Agent: claude-code
On a simulated 17,500-word, 4-POV book, 13 of 15 voice warnings were
false positives on short or situational lines ("Hell," from a dockhand,
"How many?" from a captain). A warning that fires on in-character lines
makes an AI "fix" good prose.

- Rate features are compared only on lines of 12+ words; shorter lines
  can only trip presence-based hard contrasts.
- Absence is never a finding (no profanity, no formal words, no
  questions). Missing contractions count only on long lines from a
  contraction-heavy speaker, and only alongside another signal.
- Questions and exclamations are situational, never reasons on their own.
- Sentence length is directional and relative: LONG needs 3x the
  speaker's average and 15+ words; SHORT only applies to verbose
  speakers across 2+ sentences.
- A finding needs a hard contrast (profanity or formal words from a
  character who never uses them, over 150+ words of evidence) or two
  independent signals.

Test fixture: Lady Veyra sat at 149 evidence words, one under the
hard-contrast minimum, so the precision pass had flipped an assertion to
expect no profanity finding. One more in-character line puts her above
the minimum and the original assertion is restored unchanged.

Verified: npm run preflight (49/49), including new precision tests (7
in-character lines produce nothing; 3 real swaps are caught).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Session-Id: 04db4d93-9884-44ee-b28f-627e92b33731
Agent: claude-code
Profiles are built from the approved manuscript, which contains the very
out-of-voice lines to catch. On a simulated book, a formal scholar's one
planted profane line put his profanity rate above zero, so the "never
swears" hard contrast no longer fired and the flaw was missed.

- Profiles (version 2) carry totals and sorted per-line records (scene,
  paragraph, sha256 of normalised text, feature counts).
- A line that is part of the profile is judged against the profile minus
  itself (and its paragraph's other lines by the same speaker): rates,
  means, z-scores, the 150-word minimum and enough_evidence are all
  recomputed. Evidence lines never include the line being judged.
- Legacy profiles without per-line data still work, with a rebuild note.

Verified: npm run preflight (51/51, new test/voice-loo.test.mjs). On the
simulated 17,500-word book the planted line is now caught ("no profanity
markers across 1162 words of evidence"), sounds like the dockhand, with
no false positives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Session-Id: 04db4d93-9884-44ee-b28f-627e92b33731
Agent: claude-code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant