Local-first writing analysis with human-in-the-loop revision.
Paste text, get document statistics, sentence rhythm, vocabulary, repetition, transitions, punctuation and readability metrics. Build a personal style profile from your own writing. Detect measurable problem passages. Revise them in an editing workspace with undo/redo and diffs.
All of that runs locally — no LLM involved.
Most "writing improvement" tools send your document to a model and hand back a rewrite. That has two problems: your text leaves your machine, and you have no way to tell whether a number, a name or a quote survived the rewrite.
HumanWrite inverts the default. Measurement happens locally and is always available. If you choose to configure an AI provider, it is asked for alternatives on one passage at a time, and every replacement — including ones you edit yourself — is checked locally against the original before it can be applied. The human stays in control of every edit.
Not an AI detector. Not a human-probability estimator. Not an authorship verifier. Not a detector-bypass tool. Not an automatic whole-document humanizer. It never rewrites a document by itself.
Local analysis — document, sentence-rhythm and paragraph statistics; lexical diversity (TTR and Root TTR); repeated bigrams and trigrams; transition usage; repeated sentence openings; punctuation counts and per-100-word rates; contractions; Flesch readability. See METRICS.md.
Personal Style Profile — pool 2–12 of your own samples into a profile with a reliability level, per-feature stability across samples, and pooled style measurements. Compare any document against it.
Problem Map — an explainable list of measurable issues with UTF-16 offsets and verbatim excerpts. Eight issue types across sentence, paragraph and local-pattern scopes. Repetition detection works without a profile.
Human editing workspace — Working / Original / Changes / Analysis views, undo/redo, word-level diffs, and stale-map protection after edits. Direct manual editing is never restricted.
Optional targeted AI suggestions — 1–3 alternatives for one selected passage, reachable either from a Problem Map issue or from a manual selection in the editor. Analysis is optional for the manual path.
Protected content validation — a hard local filter. Numbers, percentages, dates, currency, URLs, emails, citations, quotes, technical identifiers, high-confidence names and your custom protected terms must survive exactly.
Semantic validation — advisory local meaning check (PASS / REVIEW /
HIGH_RISK / UNAVAILABLE). Never calls a second model, never claims facts,
never falsely reports PASS.
Style fit — an advisory normalized style-distance measurement that ranks and explains candidates. It never filters, and it is never presented as an authorship probability.
Browser (React 19 + TS + Vite)
└─ typed API client ──▶ FastAPI (Python 3.12)
├─ services/ analysis · metrics · profiling · problem map
├─ protection/ hard filter (Sprint 6)
├─ semantics/ advisory (Sprint 7)
├─ advanced_style/ advisory (Sprint 8)
└─ providers/ the ONLY egress point
app/providers/llm.py is the single module in the codebase that makes an
outbound network request. Everything else is local and deterministic.
The full module map, request flows and enforced design invariants are in ARCHITECTURE.md.
Requirements: Python 3.12 (e.g. via uv), Node.js 20+ and npm. No global Python packages are needed — the spaCy model is a pinned pip dependency.
cd backend
python3.12 -m venv .venv # or: uv venv --python 3.12 .venv
source .venv/bin/activate
pip install -e ".[dev]" # or: uv pip install -e ".[dev]"
uvicorn app.main:app --reload --port 8000GET http://localhost:8000/api/health→{"status":"ok"}POST http://localhost:8000/api/analysiswith{"text": "..."}
No API credentials are required. Local analysis, profiles, problem maps and the editor all start with no provider configured.
cd frontend
npm install # or: npm ci
npm run devOpen http://localhost:5173. The Vite dev server proxies /api to
http://localhost:8000.
Meaning checks can use a local sentence-transformers embedding model:
cd backend && pip install -e ".[semantic]"The model loads lazily on first use and is never downloaded at startup. If the
library or model is unavailable, suggestions still work and meaning checks are
marked UNAVAILABLE — never falsely PASS. Tests use a deterministic offline
backend and never load a real model.
Everything above works with nothing configured. To enable the optional
AI-suggestions button, create a .env (git-ignored) from the template:
cp .env.example .env # repository root or backend/ — both workHUMANWRITE_LLM_PROVIDER=openai_compatible # or "fake" for a deterministic offline demo
HUMANWRITE_LLM_API_KEY=your-key-here
HUMANWRITE_LLM_BASE_URL=https://api.openai.com/v1
HUMANWRITE_LLM_MODEL=gpt-4o-miniThe provider speaks an OpenAI-compatible chat/completions API, so any
compatible provider works by changing the base URL and model. Official
DeepSeek is a supported configuration:
HUMANWRITE_LLM_BASE_URL=https://api.deepseek.com
HUMANWRITE_LLM_MODEL=deepseek-v4-flash
HUMANWRITE_LLM_THINKING_MODE=disabledWith the DeepSeek base URL, HUMANWRITE_LLM_REASONING_EFFORT=none is
automatically translated to thinking.type=disabled, because DeepSeek rejects
reasoning.effort=none. Other effort values pass through unchanged.
Optional tuning: HUMANWRITE_LLM_TIMEOUT_SECONDS (default 120 — timeouts are
never retried). No response_format is ever sent, since some gateways reject
it; structured output is requested in the prompt and the response is parsed and
validated locally, tolerating Markdown fences, content-part arrays and separate
reasoning fields. Requests are bounded to 3 suggestions and 1600 output tokens,
with one retry on transient 5xx only — never on auth failures, rate limits or
malformed responses, and never a second "repair" call.
The design claim is simple: the machine never certifies meaning on a provider's word, and your text does not leave the machine unless you ask.
- The local validator — not a provider's
facts_preservedfield — is the authority on whether a replacement is safe. - Every final replacement is validated against the original passage immediately before Apply, whether it was edited or not. Normal Apply never implicitly overrides protection; the override is a separate, clearly-labelled action after a visible rejection.
- Guards read live state, never render closures: an apply is dropped unless the selected issue and snapshot version still match what the text was validated for.
- Requesting a suggestion sends only the selected passage, at most its adjacent sentence, an optional short goal, a compact style context and compact protected-content constraints — never your corpus, embeddings, editor history or the full document.
- Your API key never reaches the frontend, and logs never contain keys, headers, prompts or document text.
The full model is in SAFETY.md.
cd backend && .venv/bin/pytest -q # 675 passed
cd frontend && npm run typecheck # clean
cd frontend && npm test # 195 passed across 13 files
cd frontend && npm run build # typecheck + production buildEvery test is deterministic and offline. Provider behaviour is mocked with
httpx.MockTransport or replaced by a deterministic fake; semantic tests use a
fake embedding backend; frontend tests run in jsdom and never touch the
network. Tests never depend on a developer's local .env. New metrics require
golden tests with manually verifiable expected values.
| Document | Contents |
|---|---|
| ARCHITECTURE.md | Module map, request flows, design invariants |
| API.md | All 9 endpoints, error codes, limits |
| METRICS.md | Every metric, formula and caveat |
| SAFETY.md | Protection, semantic validation, style fit, privacy boundary |
| DEVELOPMENT.md | Setup, conventions, and hard-won gotchas |
- Profiles live in frontend memory only — no persistence, no accounts.
- The editor is a plain textarea: no rich text, no dark mode.
- Stale problem-map offsets are never navigated against changed text; re-analyze after editing. This is by design.
- Provider configuration guidance shows environment-variable names.
- Semantic thresholds (0.80 / 0.55) and the protection detectors are heuristics,
documented in
backend/app/config.pyandbackend/app/protection/.
Sprints 1–10 are complete (local analysis → style profiles → problem map → editing workspace → optional AI suggestions → protected content → semantic validation → advanced style intelligence → UI polish → hardening and v1 readiness). Deliberately not started: whole-document rewrite, AI-detector integration, auth, database, Docker, payments, browser extension, rich-text editing.
MIT © 2026 Hrishikesh Das