Skip to main content

cadence

Parse prose, measure the shape of its sentences, then rewrite a draft to match.

Ask a model to write like you and it copies your topics and your favourite words, which are the easiest things to copy and the least of what makes writing recognisable. What actually distinguishes one writer from another is closer to the skeleton: how long a sentence runs before its main verb arrives, how deep clauses embed, whether ideas get joined or subordinated, how often a claim becomes a noun rather than staying a verb.

Those are measurable, which means a style can be a specification rather than a mood, and a specification can be checked after the fact.

The rule that shapes everything

A rewrite may fix structure. It may not invent content.

Where the analysis finds missing information, a superlative with no comparison set, a passive with a suppressed agent, a pronoun with no recoverable referent, that stays missing and is reported as a gap. Filling it would mean fabricating facts, which is the one failure mode this tool is built to avoid.

That promise used to be a line in a prompt plus the model's own report of whether it had obeyed. A model that fabricates is not a reliable witness to its own fabrication, so fidelity.py now checks it mechanically.

Install

pip install "cadence-writer[llm]"   # the import name is `cadence`
cadence download-model              # English
cadence download-model --lang all   # English, Spanish and Portuguese

The spaCy models are a separate step because PyPI rejects packages that declare a dependency by URL, and the models are only distributed that way.

Languages

cadence measures English, Spanish and Portuguese. A profile is measured in one language and carries it, so a restyle against a Spanish profile is told to write Spanish and its output is parsed as Spanish. Every command takes --lang en|es|pt, every function takes lang=, and the MCP tools take language. The default is English.

cadence profile cartas/*.txt --lang es
cadence restyle borrador.txt --corpus cartas/*.txt --lang es
profile = cadence.build_profile(cartas, name="yo", lang="es")
result = cadence.generate(borrador, mode="profile_verify", profile=profile,
                          task="restyle")          # writes Spanish: the profile says so
cadence.guess_language(text)                       # "en", "es" or "pt", by function words

The measurements are the same in all three: finite clauses, embedding depth, subordination, coordination, what sits before the main verb, passives and their agents, function-word frequency. What differs is the parser. spaCy's Spanish and Portuguese models label dependencies by the Universal Dependencies scheme and mark finiteness in morphology rather than in a Penn tag, and languages.py rewrites those into the labels the counters read, so no counter knows which language it is counting. The fidelity check has each language's negators, relative dates and spelled-out numbers. The remove-slop phrase lists and the note-register parse repairs are English and stay out of the way in the other two.

Use

cadence analyze notes.txt                    # trees and findings, no rewrite
cadence report notes.txt                     # everything, including fidelity
cadence rewrite notes.txt --backend rules    # deterministic, no API call
cadence profile samples/*.txt                # the specification for a corpus
cadence restyle draft.txt --corpus samples/*.txt
cadence score out.txt --corpus samples/*.txt --source-text draft.txt
import cadence

profile = cadence.build_profile(my_documents, name="me")
result = cadence.generate(draft, mode="profile", profile=profile,
                          task="restyle", api_key=key)

result.similarity          # voice match, 0 to 100
result.fidelity.score      # content kept, 0 to 100
result.fidelity.introduced # anything invented, which should be empty

As an MCP server

pip install "cadence-writer[mcp]"
cadence mcp

That serves the library over stdio for Claude Desktop, Claude Code, or any other MCP client, so a model can measure a voice, restyle a draft, and check fidelity as tools. Nothing leaves the machine except the model call the restyle tool makes. In Claude Code:

claude mcp add cadence -- cadence mcp

Tools: measure_voice, analyze_text, restyle, check_fidelity, score, repair_structure. Each is one library function with its arguments flattened; where a tool takes a corpus, it takes the documents themselves, so the server holds no state a client has to manage.

Two numbers, and what neither can tell you

Voice match compares sentence shape against the corpus: length, embedding depth, clause mix, subordination, function words. It cannot tell you whether the result sounds right.

Content kept compares names, numbers, dates, negations and coverage between the draft and the rewrite. It cannot tell you the meaning survived. Two passages can share every anchor and still say different things.

Both are floors. They catch the failures worth catching and they are honest about the ones they miss.

What the diagnostics find

Code What it finds
FRAG / PUNCT_COPULA Punctuation carrying predication; no finite verb on one side
AGENTLESS_PASSIVE Passive with the agent suppressed, not merely omitted
NULL_SUBJECT Finite verb with no subject (diary-drop)
UCP Unlike coordination (a teacher, patient, and writing daily)
NOMINALIZATION A claim packaged as a noun so it need never be asserted
SUPERLATIVE_NO_SET Superlative ranking against a set the text never gives
SUPERLATIVE_VAGUE_SET Set given, but as a free relative you cannot enumerate
PROP_ANAPHORA this / that / it pointing at a proposition
RELCL_ATTACH / PP_ATTACH Attachment ambiguities that change what is claimed
AMBIG_SUBORD since / as / while — two readings, one POS tag
DEEP_NESTING, SHARED_GAP, NP_NEGATION, ANAPHORIC_ONE see diagnostics.py
SLOP_DASH / SLOP_HYPHEN dashes as connectors, hyphenated compounds
SLOP_RHETORICAL / SLOP_VAPID_OPENER staged reveals, camera metaphors, scene setting
SLOP_SUMMARY_INTRO "the upshot", "in short", "bottom line" leading a sentence
SLOP_APPLAUSE short declarative landing after a long one, as punctuation
SLOP_PATRONIZING / SLOP_EVALUATIVE / SLOP_INTENSIFIER telling the reader what to think
SLOP_UNIFORM_RHYTHM / SLOP_UNIFORM_PARAGRAPHS measured variance below the floor

Findings are defects. Observations are register facts (dates used as attributive modifiers, container-noun inventory) — true of the text, not wrong with it. They are reported separately on purpose.

Layers

syntax.py       PTB tags, dependency trees, constituency projection
repair.py       parse repairs for note-register English (R1–R5)
diagnostics.py  the detectors
profile.py      corpus measurement, divergence, the generation spec
generate.py     constrained generation, and the verify loop
rewrite.py      rules backend (deterministic) + Claude backend
fidelity.py     did the rewrite keep the content, measured not asserted
scores.py       the two numbers, in one call
llm.py          credentials, one call, token usage

On the constituency layer

The dependency parse is real. The constituency tree is a head projection of it, not a native PTB parse. It reproduces PTB conventions that follow from dependencies (flat base NPs, -SBJ/-PRD function tags, PP/ADJP/SBAR labels) and cannot reproduce what only a constituency treebank knows (traces, empty categories, licensed UCP nodes). It is labelled as projected everywhere it appears.

On remove-slop

The remove-slop rule set (slop.py) is not a mode or a checkbox. It runs in the same pass as the structural detectors, and its rules are appended to every rewrite and generation prompt. A dash used as a copula is a structural finding (PUNCT_COPULA) and a slop finding (SLOP_DASH) at the same time; splitting them would have forced the reader to reconcile two reports of one sentence.

Most of the rules are mechanically checkable, which is why they belong here rather than in prompt text alone. "Uniform rhythm is a stronger tell than any single word" is a claim about variance, and variance is what profile.py already computes, so it is measured rather than eyeballed.

Where the two layers meet. A style profile is descriptive and remove-slop is normative, so they can disagree. The resolution is fixed, not a preference:

  1. The profile owns structure (length, depth, clause mix, subordination).
  2. remove-slop owns surface machine tells (banned phrases, dashes, applause).
  3. Where they overlap, the ban wins, and the override is printed rather than left for the reader to notice. Banned features are still measured — the profile stays a complete description — but they are marked NOT A TARGET in the spec and never handed to a generator to reproduce.
  4. On rhythm the two agree, with a floor: if a corpus is itself uniform, the spec says do not reproduce that uniformity, because copying it would copy the strongest machine tell there is.

On the parse repairs

en_core_web_sm is trained on newswire and fails on this register in specific, reproducible ways. Five repairs run before any diagnostic, and every one that fires is reported:

  • R1 ISO dates and hyphenated compounds kept as single tokens
  • R2 the category read is… — head noun misread as a verb
  • R2b subject-like arc landing on a nominal head → compound
  • R3 oneNN when pronominal, CD when a partitive numeral
  • R4 accusative pronoun parsed as a subject (liked him)
  • R5 hyphenated premodifier tagging: noun-noun → NN, prefix+noun → JJ

Sentences also carry a coarse parse-confidence signal — a list of structural reasons to distrust the parse, not a probability.

Tests

pytest -q

Nothing in the suite touches the network. Every expectation was verified by hand before it was automated, so a failure means the tool has drifted from an analysis a human checked.

License

MIT.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cadence_writer-0.3.0.tar.gz (78.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cadence_writer-0.3.0-py3-none-any.whl (70.8 kB view details)

Uploaded Python 3

File details

Details for the file cadence_writer-0.3.0.tar.gz.

File metadata

  • Download URL: cadence_writer-0.3.0.tar.gz
  • Upload date:
  • Size: 78.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for cadence_writer-0.3.0.tar.gz
Algorithm Hash digest
SHA256 60332a5fab28882a30276b58edd40edd97523b87940fd7926a4205bba232991c
MD5 212474166b66338edbd73777520fc32d
BLAKE2b-256 e60f115ef4d79f33f3d04ac25b33d6b7099ab5b12594aa53bb01ae927855f80d

See more details on using hashes here.

Provenance

The following attestation bundles were made for cadence_writer-0.3.0.tar.gz:

Publisher: publish.yml on josemiguel/cadence

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cadence_writer-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: cadence_writer-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 70.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for cadence_writer-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 17e40aeb6c3dbea746935ac17e7cdbd4e1d60b659c495364f07ba3f61fbb246f
MD5 e98a01ff68c3652653cb6fb138d8f363
BLAKE2b-256 a272971666397c1b00e3116ffe0d66ac010e0b34a43f92f28597f68196e0cb3d

See more details on using hashes here.

Provenance

The following attestation bundles were made for cadence_writer-0.3.0-py3-none-any.whl:

Publisher: publish.yml on josemiguel/cadence

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page