Vibecode HyperWrite
track this build4 phases, 12 steps, beginner friendly0%The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For HyperWrite, draft, rewrite, and answer questions using user-selected context and personal templates. The hard boundary is browser presence, personalization history, and proprietary agent workflows, plus workflow, data, and model tuning.
You are building a lean indie version of HyperWrite. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # HyperWrite · indie build A local drafting workspace whose hard problems are chunking sources so a retrieved passage is actually a unit of support, verifying every generated sentence against the passage it cites instead of trusting the model's own citation, and keeping that verification correct as drafts are rewritten and sources removed. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Next.js 15 and TypeScript, local, with the model key server side | your sources are the interesting part and they stay on your machine | | Storage | SQLite through Drizzle, with source text kept as files beside it | a passage has to be showable in its original context, not just as a chunk | | Retrieval | Paragraph-aligned chunks with character offsets into the original | a chunk that splits a claim from its qualifier defeats the whole verification step | | Generation | One provider behind one adapter, structured output, passages numbered in the prompt | the model cites by number and the numbers are yours | | Verification | A separate pass checking each sentence against the passage it cited | the model's own citation is a claim, not evidence | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the application and its test suite. - Get it: nodejs.org, or your version manager. - Verify: node --version prints v22 or higher - [ ] **An API key for a text model** · pay per token; measure a real section before choosing the ceiling - Why: Generation and the separate verification pass both go through it, and the verification pass roughly doubles the call count per draft. - Get it: Create a key with your chosen provider and set a spend limit in their console as well as the ceiling in this app. - Verify: A section drafts and every sentence receives a support state - [ ] **Real source documents for a real piece of writing** · free - Why: Chunking and verification both behave differently on a research paper, a transcript and a marketing page, and testing on one of them teaches you nothing about the others. - Get it: Assemble the documents for something you actually need to write, including at least one long and one badly formatted file. - Verify: All of them import, chunk and show passages in context - [ ] **The prohibited claims list for the project** · free - Why: A flagging rule written after the draft exists is written around the draft, which is the opposite of useful. - Get it: Write down the claims this document must never make, in the wording the flagger will match on. - Verify: The list is stored on the project and appears in every generation prompt ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Live web search during drafting. - Publishing to a content system. - A browser extension. - Team approvals and brand governance. - browser presence, personalization history, and proprietary agent workflows - proprietary ranking data - brand-trained models - team workflows - large template libraries If one of those is essential to you, that is the reason to keep paying for HyperWrite, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · HyperWrite The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a drafting workspace that writes from sources you supplied and marks every sentence it cannot support to replace HyperWrite. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The thing that makes this worth building instead of opening a chat window is that a sentence is either traceable to a passage you supplied or it is visibly marked as not. Do not skip that, and do not let the model do it for itself. Asking a model to write with citations produces a document that looks sourced. When Liu, Zhang and Liang audited four generative search engines for Findings of EMNLP 2023 they found only 51.5 percent of generated sentences were fully supported by their citations, and only 74.5 percent of citations actually supported the sentence they were attached to. Those systems were built by teams whose entire product was citation quality. Your prompt will not do better. So a citation in this app is resolved, not generated. The model receives numbered passages and must cite by number. After generation the app splits the draft into sentences, resolves every cited number against the passage store, runs a separate check that the passage text actually supports the sentence, and renders anything that fails as unsupported. A sentence with no resolvable passage is never quietly stripped of its marker. And the sources are the ones the user gave you. No live search, no claims about the world beyond the documents in the project, and deleting a source marks every sentence that relied on it as unsupported rather than leaving a green tick pointing at nothing. ### Stack (fixed, do not substitute) - Runtime: Next.js 15 and TypeScript, local, with the model key server side · your sources are the interesting part and they stay on your machine - Storage: SQLite through Drizzle, with source text kept as files beside it · a passage has to be showable in its original context, not just as a chunk - Retrieval: Paragraph-aligned chunks with character offsets into the original · a chunk that splits a claim from its qualifier defeats the whole verification step - Generation: One provider behind one adapter, structured output, passages numbered in the prompt · the model cites by number and the numbers are yours - Verification: A separate pass checking each sentence against the passage it cited · the model's own citation is a claim, not evidence ### Data model (create this before Phase 1) - projects(id, title, audience, objective, tone, prohibited_claims_json, created_at) - sources(id, project_id, kind, title, origin, added_at, sha256, text_path) · kind is pasted, uploaded or fetched once - passages(id, source_id, ordinal, text, char_start, char_end) · the only unit anything is ever allowed to cite - outlines(id, project_id, version, approved_at) and sections(id, outline_id, ordinal, heading, brief) - drafts(id, section_id, version, created_at, model_id, prompt_version, cost_minor) · a rewrite is a new version, never an edit - sentences(id, draft_id, ordinal, text, support_state) · support_state is supported, weak or unsupported - citations(sentence_id, passage_id, verifier_score, verified_at) · a row exists only after the id resolved and the check ran - The model's own numbering never becomes a citation row without passing through resolution and verification first. ### Phase 1 · Sources in, passages out Build: Import pasted text, files and single fetched pages. Keep the full original text on disk with its hash. Chunk on paragraph boundaries with a configured overlap, storing character offsets so a passage can always be shown inside its surrounding text. Never renumber passages after creation, even when a source is re-imported. Trap: Chunking purely by token count splits a claim from the qualifier that follows it, so the retrieved passage supports half a sentence and your verifier cheerfully passes it. Chunk on paragraph boundaries, overlap by a configured amount, keep the offsets, and show the surrounding text whenever somebody opens a citation. Done when: Every passage resolves to a character range in its source's stored text and opening a citation shows the passage in context, tested. Re-importing an unchanged source creates no new passages, tested by hash. Passage ids are never reused after a source is edited; a changed source produces new passages and the old ones are retained until nothing cites them. A source too large for the configured limit is refused with the size stated rather than silently truncated. Do not build yet: No retrieval interface, outlines, drafting or verification yet. ### Phase 2 · Retrieval you can see and correct Build: For each section, retrieve candidate passages and show them with their source and a snippet. Let the writer pin a passage, remove one, or add one by searching. The pinned set is what goes into the prompt, and it is stored with the draft so a draft can be explained later. Trap: Hiding retrieval behind a generate button makes a bad draft indistinguishable from a bad model, and you end up rewriting prompts to fix a problem that was three irrelevant passages. Put the retrieved set on screen first, let it be corrected, and store the exact set used with the draft. Done when: The retrieved passage set is displayed before generation and can be pinned, removed and added to, with changes persisting. The exact passage set used is stored with each draft and shown when reviewing it later. Generation is refused when the pinned set is empty, with a message rather than an empty draft. Searching for a passage returns results from the project's sources only, asserted. Do not build yet: No outline approval, drafting or verification yet. ### Phase 3 · Outline first, approval before prose Build: Generate an outline of sections with a one line brief each, from the project's objective, audience and pinned passages. Require explicit approval before any section can be drafted. Store outline versions so a restructure does not discard the drafts that came before it. Trap: Generating three thousand words and then deciding the structure is wrong wastes the generation and, worse, anchors the rewrite to the shape of the thing you were about to throw away. Approve a structure first, and make the approval a stored fact rather than a click that leaves no trace. Done when: A section cannot be drafted before its outline version is approved, enforced server side. Approving a new outline version leaves earlier drafts intact and attached to their own version. Each section carries a brief and a section with an empty brief cannot be approved. The prohibited claims list from the project settings is included in every generation prompt, asserted. Do not build yet: No drafting, verification, rewriting or export yet. ### Phase 4 · Draft, then check every sentence yourself Build: Number the pinned passages in the prompt and require citations by number. After generation, split the draft into sentences, resolve each cited number to a passage id, run a separate check of the sentence against that passage's text, and store the score. Render the three states differently and show the counts at the top of the draft. Trap: Rendering the model's citations as the app's citations gives you a document that looks sourced and is not: the published audit of four generative search engines found only about half of generated sentences fully supported by their citations, and about a quarter of citations not supporting the sentence they were attached to. Resolve every number against your own passage store, verify the sentence against the passage text in a separate call, and let the failures show. Trap: Dropping the citation from a sentence that failed verification leaves a plain, confident sentence sitting in the draft and removes the only signal that it was doubtful. Keep the sentence, mark it unsupported, and make the unsupported count visible before the writer reads a word of the prose. Done when: A sentence citing a passage id that does not exist is rendered unsupported and keeps its marker, tested with a fabricated number. Every citation row carries a verifier score and the time it was checked, and no citation row exists without one. The supported, weak and unsupported counts appear above every draft and are carried into export. A sentence matching a prohibited claim is flagged before the draft is displayed, tested with a crafted case. Do not build yet: No rewriting, versioning, export or cost ceiling yet. ### Phase 5 · Rewrites, deletions and getting out Build: Rewrite controls produce a new draft version rather than editing text in place, and the new version cannot be displayed until its sentences have been resolved and verified. Deleting a source marks every sentence that cited it as unsupported. Export Markdown plus a JSON bundle carrying sources, passages, sentences and their support states. Cap monthly model spend. Trap: Rewriting a paragraph in place leaves the old sentence rows attached to new text, so the green ticks now belong to sentences nobody verified, which is worse than having no ticks at all. Make every rewrite a new draft version, recompute sentences and citations before rendering, and never show a draft whose verification has not completed. Trap: Deleting a source while its citations stay green turns the document into a claim about evidence that is no longer there. Cascade the deletion to support state, show the count of sentences affected, and offer to re-verify against remaining sources rather than silently downgrading everything. Done when: A rewrite creates a new draft version and the version is not renderable until verification has run, asserted. Deleting a source marks every sentence citing it as unsupported and reports the count, tested. Export produces Markdown with support markers plus a JSON bundle that reimports into an empty database with identical sentence and citation counts. Reaching the monthly cost ceiling disables generation and leaves reading, editing by hand and export working. Do not build yet: Nothing after this. ### Out of scope (and why) - Live web search during drafting, because a citation to a page that may have changed since is not verifiable and the whole design rests on passages you hold. - Publishing to a content system, which is integration plumbing and would put unverified sentences one click from being public. - A browser extension that rewrites text anywhere, which is the part of the paid product that needs a distribution channel rather than a weekend. - Team approvals and brand governance, which are workflow features that only start to matter when more than one person owns the document. ### README must contain - The published citation support figures for generative systems, with the study and the date, before any usage instructions - That citations are resolved against your own passage store and verified separately, never taken from the model - That a sentence failing verification is marked rather than stripped of its citation - That deleting a source downgrades every sentence that cited it, and how many that was - Where sources, passages and drafts are stored, how to export everything, and what the monthly cost ceiling does ===== AGENTS.md ===== # Agent instructions · HyperWrite indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Next.js 15 and TypeScript, local, with the model key server side, SQLite through Drizzle, with source text kept as files beside it, Paragraph-aligned chunks with character offsets into the original, One provider behind one adapter, structured output, passages numbered in the prompt, A separate pass checking each sentence against the passage it cited. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Chunking purely by token count splits a claim from the qualifier that follows it, so the retrieved passage supports half a sentence and your verifier cheerfully passes it. Chunk on paragraph boundaries, overlap by a configured amount, keep the offsets, and show the surrounding text whenever somebody opens a citation. - Hiding retrieval behind a generate button makes a bad draft indistinguishable from a bad model, and you end up rewriting prompts to fix a problem that was three irrelevant passages. Put the retrieved set on screen first, let it be corrected, and store the exact set used with the draft. - Generating three thousand words and then deciding the structure is wrong wastes the generation and, worse, anchors the rewrite to the shape of the thing you were about to throw away. Approve a structure first, and make the approval a stored fact rather than a click that leaves no trace. - Rendering the model's citations as the app's citations gives you a document that looks sourced and is not: the published audit of four generative search engines found only about half of generated sentences fully supported by their citations, and about a quarter of citations not supporting the sentence they were attached to. Resolve every number against your own passage store, verify the sentence against the passage text in a separate call, and let the failures show. - Dropping the citation from a sentence that failed verification leaves a plain, confident sentence sitting in the draft and removes the only signal that it was doubtful. Keep the sentence, mark it unsupported, and make the unsupported count visible before the writer reads a word of the prose. ===== BUILD_PLAN.md ===== # Build plan · HyperWrite A local drafting workspace whose hard problems are chunking sources so a retrieved passage is actually a unit of support, verifying every generated sentence against the passage it cites instead of trusting the model's own citation, and keeping that verification correct as drafts are rewritten and sources removed. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Sources in, passages out Everything the app will ever cite is a passage with a stable id and an offset into the original text. ### Steps 1. Chunk on paragraph boundaries with overlap A passage should be a unit of support, not a unit of tokens. Files: `src/lib/chunk.ts`, `tests/chunk.test.ts` 2. Store character offsets into the original text Context is what lets a human judge a citation in two seconds. Files: `src/lib/sources.ts` 3. Never reuse a passage id A reused id turns a stale citation into a wrong one. Files: `src/db/schema.ts` ### Done when - [ ] Every passage resolves to a range in the original text - [ ] Re-importing an unchanged source adds no passages - [ ] Passage ids are never reused - [ ] An oversized source is refused with the size stated ### Watch out - Chunking purely by token count splits a claim from the qualifier that follows it, so the retrieved passage supports half a sentence and your verifier cheerfully passes it. Chunk on paragraph boundaries, overlap by a configured amount, keep the offsets, and show the surrounding text whenever somebody opens a citation. ## Phase 2 · Retrieval you can see and correct The passages that will be used are on screen, editable, before anything is generated. ### Steps 1. Show the retrieved set before generation Otherwise you are debugging the prompt for a retrieval bug. Files: `src/app/project/[id]/retrieve/page.tsx` 2. Store the exact set used with the draft So a strange draft can be explained instead of argued about. Files: `src/lib/drafts.ts` 3. Refuse to generate with nothing pinned An ungrounded draft is the thing this app exists to avoid. Files: `src/lib/generate.ts`, `tests/generate.test.ts` ### Done when - [ ] Retrieved passages are shown and editable before generation - [ ] The used passage set is stored with the draft - [ ] Generation with an empty set is refused - [ ] Search covers only the project's own sources ### Watch out - Hiding retrieval behind a generate button makes a bad draft indistinguishable from a bad model, and you end up rewriting prompts to fix a problem that was three irrelevant passages. Put the retrieved set on screen first, let it be corrected, and store the exact set used with the draft. ## Phase 3 · Outline first, approval before prose Nothing is drafted until a section structure with a brief per section has been approved. ### Steps 1. Gate drafting on an approved outline version Structure is cheaper to change than prose. Files: `src/lib/outline.ts` 2. Version outlines rather than replacing them A restructure should not delete yesterday's work. Files: `src/db/schema.ts` 3. Carry prohibited claims into every prompt The rule is useless if it only lives in the settings page. Files: `src/lib/prompt.ts`, `tests/prompt.test.ts` ### Done when - [ ] Drafting is refused before outline approval - [ ] A new outline version preserves earlier drafts - [ ] A section with no brief cannot be approved - [ ] Prohibited claims appear in every generation prompt ### Watch out - Generating three thousand words and then deciding the structure is wrong wastes the generation and, worse, anchors the rewrite to the shape of the thing you were about to throw away. Approve a structure first, and make the approval a stored fact rather than a click that leaves no trace. ## Phase 4 · Draft, then check every sentence yourself Each sentence is resolved against the passage it cited and rendered as supported, weak or unsupported. ### Steps 1. Resolve every cited number against your own store The model's numbering is a claim until you check it. Files: `src/lib/resolve.ts`, `tests/resolve.test.ts` 2. Verify the sentence against the passage separately A second pass with one job beats a longer first prompt. Files: `src/lib/verify.ts` 3. Show the three counts before the prose It is the first thing the writer needs to know. Files: `src/components/SupportSummary.tsx` ### Done when - [ ] An unresolvable citation renders as unsupported - [ ] Every citation row has a score and a check time - [ ] Support counts appear above the draft and in export - [ ] A prohibited claim is flagged before display ### Watch out - Rendering the model's citations as the app's citations gives you a document that looks sourced and is not: the published audit of four generative search engines found only about half of generated sentences fully supported by their citations, and about a quarter of citations not supporting the sentence they were attached to. Resolve every number against your own passage store, verify the sentence against the passage text in a separate call, and let the failures show. - Dropping the citation from a sentence that failed verification leaves a plain, confident sentence sitting in the draft and removes the only signal that it was doubtful. Keep the sentence, mark it unsupported, and make the unsupported count visible before the writer reads a word of the prose. ## Not in this build - Live web search during drafting. - Publishing to a content system. - A browser extension. - Team approvals and brand governance. ## After v1, if you want it - A quotation mode that inserts verbatim passage text with its offsets, which needs no verification at all - A per-source support report showing which documents actually carried the argument - A local embedding model for retrieval, so that nothing but the generation call leaves the machine ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Generation and verification. Give it a provider-side spend limit too. MODEL_API_KEY=sk-...redacted # Required. Stored with every draft, because a different model cites differently. MODEL_ID=a text model identifier # Required. The support check. A cheaper model is usually enough for this one job. VERIFIER_MODEL_ID=a smaller text model identifier # Required. Projects, passages, drafts, sentences and citations. DATABASE_PATH=./data/workspace.db # Required. Full original text per source, so a passage can be shown in context. SOURCE_TEXT_DIR=./data/sources # Required. Overlap between paragraph-aligned chunks. Changing it re-chunks new sources only. CHUNK_OVERLAP_CHARS=200 # Required. Verifier score below which a sentence is weak, and lower still is unsupported. SUPPORT_THRESHOLD=0.7 # Required. Generation stops here; reading, hand editing and export continue. MONTHLY_COST_CEILING=10.00
You are building a lean indie version of HyperWrite. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # HyperWrite · indie build A local drafting workspace whose hard problems are chunking sources so a retrieved passage is actually a unit of support, verifying every generated sentence against the passage it cites instead of trusting the model's own citation, and keeping that verification correct as drafts are rewritten and sources removed. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Next.js 15 and TypeScript, local, with the model key server side | your sources are the interesting part and they stay on your machine | | Storage | SQLite through Drizzle, with source text kept as files beside it | a passage has to be showable in its original context, not just as a chunk | | Retrieval | Paragraph-aligned chunks with character offsets into the original | a chunk that splits a claim from its qualifier defeats the whole verification step | | Generation | One provider behind one adapter, structured output, passages numbered in the prompt | the model cites by number and the numbers are yours | | Verification | A separate pass checking each sentence against the passage it cited | the model's own citation is a claim, not evidence | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the application and its test suite. - Get it: nodejs.org, or your version manager. - Verify: node --version prints v22 or higher - [ ] **An API key for a text model** · pay per token; measure a real section before choosing the ceiling - Why: Generation and the separate verification pass both go through it, and the verification pass roughly doubles the call count per draft. - Get it: Create a key with your chosen provider and set a spend limit in their console as well as the ceiling in this app. - Verify: A section drafts and every sentence receives a support state - [ ] **Real source documents for a real piece of writing** · free - Why: Chunking and verification both behave differently on a research paper, a transcript and a marketing page, and testing on one of them teaches you nothing about the others. - Get it: Assemble the documents for something you actually need to write, including at least one long and one badly formatted file. - Verify: All of them import, chunk and show passages in context - [ ] **The prohibited claims list for the project** · free - Why: A flagging rule written after the draft exists is written around the draft, which is the opposite of useful. - Get it: Write down the claims this document must never make, in the wording the flagger will match on. - Verify: The list is stored on the project and appears in every generation prompt ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Live web search during drafting. - Publishing to a content system. - A browser extension. - Team approvals and brand governance. - browser presence, personalization history, and proprietary agent workflows - proprietary ranking data - brand-trained models - team workflows - large template libraries If one of those is essential to you, that is the reason to keep paying for HyperWrite, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · HyperWrite The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a drafting workspace that writes from sources you supplied and marks every sentence it cannot support to replace HyperWrite. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The thing that makes this worth building instead of opening a chat window is that a sentence is either traceable to a passage you supplied or it is visibly marked as not. Do not skip that, and do not let the model do it for itself. Asking a model to write with citations produces a document that looks sourced. When Liu, Zhang and Liang audited four generative search engines for Findings of EMNLP 2023 they found only 51.5 percent of generated sentences were fully supported by their citations, and only 74.5 percent of citations actually supported the sentence they were attached to. Those systems were built by teams whose entire product was citation quality. Your prompt will not do better. So a citation in this app is resolved, not generated. The model receives numbered passages and must cite by number. After generation the app splits the draft into sentences, resolves every cited number against the passage store, runs a separate check that the passage text actually supports the sentence, and renders anything that fails as unsupported. A sentence with no resolvable passage is never quietly stripped of its marker. And the sources are the ones the user gave you. No live search, no claims about the world beyond the documents in the project, and deleting a source marks every sentence that relied on it as unsupported rather than leaving a green tick pointing at nothing. ### Stack (fixed, do not substitute) - Runtime: Next.js 15 and TypeScript, local, with the model key server side · your sources are the interesting part and they stay on your machine - Storage: SQLite through Drizzle, with source text kept as files beside it · a passage has to be showable in its original context, not just as a chunk - Retrieval: Paragraph-aligned chunks with character offsets into the original · a chunk that splits a claim from its qualifier defeats the whole verification step - Generation: One provider behind one adapter, structured output, passages numbered in the prompt · the model cites by number and the numbers are yours - Verification: A separate pass checking each sentence against the passage it cited · the model's own citation is a claim, not evidence ### Data model (create this before Phase 1) - projects(id, title, audience, objective, tone, prohibited_claims_json, created_at) - sources(id, project_id, kind, title, origin, added_at, sha256, text_path) · kind is pasted, uploaded or fetched once - passages(id, source_id, ordinal, text, char_start, char_end) · the only unit anything is ever allowed to cite - outlines(id, project_id, version, approved_at) and sections(id, outline_id, ordinal, heading, brief) - drafts(id, section_id, version, created_at, model_id, prompt_version, cost_minor) · a rewrite is a new version, never an edit - sentences(id, draft_id, ordinal, text, support_state) · support_state is supported, weak or unsupported - citations(sentence_id, passage_id, verifier_score, verified_at) · a row exists only after the id resolved and the check ran - The model's own numbering never becomes a citation row without passing through resolution and verification first. ### Phase 1 · Sources in, passages out Build: Import pasted text, files and single fetched pages. Keep the full original text on disk with its hash. Chunk on paragraph boundaries with a configured overlap, storing character offsets so a passage can always be shown inside its surrounding text. Never renumber passages after creation, even when a source is re-imported. Trap: Chunking purely by token count splits a claim from the qualifier that follows it, so the retrieved passage supports half a sentence and your verifier cheerfully passes it. Chunk on paragraph boundaries, overlap by a configured amount, keep the offsets, and show the surrounding text whenever somebody opens a citation. Done when: Every passage resolves to a character range in its source's stored text and opening a citation shows the passage in context, tested. Re-importing an unchanged source creates no new passages, tested by hash. Passage ids are never reused after a source is edited; a changed source produces new passages and the old ones are retained until nothing cites them. A source too large for the configured limit is refused with the size stated rather than silently truncated. Do not build yet: No retrieval interface, outlines, drafting or verification yet. ### Phase 2 · Retrieval you can see and correct Build: For each section, retrieve candidate passages and show them with their source and a snippet. Let the writer pin a passage, remove one, or add one by searching. The pinned set is what goes into the prompt, and it is stored with the draft so a draft can be explained later. Trap: Hiding retrieval behind a generate button makes a bad draft indistinguishable from a bad model, and you end up rewriting prompts to fix a problem that was three irrelevant passages. Put the retrieved set on screen first, let it be corrected, and store the exact set used with the draft. Done when: The retrieved passage set is displayed before generation and can be pinned, removed and added to, with changes persisting. The exact passage set used is stored with each draft and shown when reviewing it later. Generation is refused when the pinned set is empty, with a message rather than an empty draft. Searching for a passage returns results from the project's sources only, asserted. Do not build yet: No outline approval, drafting or verification yet. ### Phase 3 · Outline first, approval before prose Build: Generate an outline of sections with a one line brief each, from the project's objective, audience and pinned passages. Require explicit approval before any section can be drafted. Store outline versions so a restructure does not discard the drafts that came before it. Trap: Generating three thousand words and then deciding the structure is wrong wastes the generation and, worse, anchors the rewrite to the shape of the thing you were about to throw away. Approve a structure first, and make the approval a stored fact rather than a click that leaves no trace. Done when: A section cannot be drafted before its outline version is approved, enforced server side. Approving a new outline version leaves earlier drafts intact and attached to their own version. Each section carries a brief and a section with an empty brief cannot be approved. The prohibited claims list from the project settings is included in every generation prompt, asserted. Do not build yet: No drafting, verification, rewriting or export yet. ### Phase 4 · Draft, then check every sentence yourself Build: Number the pinned passages in the prompt and require citations by number. After generation, split the draft into sentences, resolve each cited number to a passage id, run a separate check of the sentence against that passage's text, and store the score. Render the three states differently and show the counts at the top of the draft. Trap: Rendering the model's citations as the app's citations gives you a document that looks sourced and is not: the published audit of four generative search engines found only about half of generated sentences fully supported by their citations, and about a quarter of citations not supporting the sentence they were attached to. Resolve every number against your own passage store, verify the sentence against the passage text in a separate call, and let the failures show. Trap: Dropping the citation from a sentence that failed verification leaves a plain, confident sentence sitting in the draft and removes the only signal that it was doubtful. Keep the sentence, mark it unsupported, and make the unsupported count visible before the writer reads a word of the prose. Done when: A sentence citing a passage id that does not exist is rendered unsupported and keeps its marker, tested with a fabricated number. Every citation row carries a verifier score and the time it was checked, and no citation row exists without one. The supported, weak and unsupported counts appear above every draft and are carried into export. A sentence matching a prohibited claim is flagged before the draft is displayed, tested with a crafted case. Do not build yet: No rewriting, versioning, export or cost ceiling yet. ### Phase 5 · Rewrites, deletions and getting out Build: Rewrite controls produce a new draft version rather than editing text in place, and the new version cannot be displayed until its sentences have been resolved and verified. Deleting a source marks every sentence that cited it as unsupported. Export Markdown plus a JSON bundle carrying sources, passages, sentences and their support states. Cap monthly model spend. Trap: Rewriting a paragraph in place leaves the old sentence rows attached to new text, so the green ticks now belong to sentences nobody verified, which is worse than having no ticks at all. Make every rewrite a new draft version, recompute sentences and citations before rendering, and never show a draft whose verification has not completed. Trap: Deleting a source while its citations stay green turns the document into a claim about evidence that is no longer there. Cascade the deletion to support state, show the count of sentences affected, and offer to re-verify against remaining sources rather than silently downgrading everything. Done when: A rewrite creates a new draft version and the version is not renderable until verification has run, asserted. Deleting a source marks every sentence citing it as unsupported and reports the count, tested. Export produces Markdown with support markers plus a JSON bundle that reimports into an empty database with identical sentence and citation counts. Reaching the monthly cost ceiling disables generation and leaves reading, editing by hand and export working. Do not build yet: Nothing after this. ### Out of scope (and why) - Live web search during drafting, because a citation to a page that may have changed since is not verifiable and the whole design rests on passages you hold. - Publishing to a content system, which is integration plumbing and would put unverified sentences one click from being public. - A browser extension that rewrites text anywhere, which is the part of the paid product that needs a distribution channel rather than a weekend. - Team approvals and brand governance, which are workflow features that only start to matter when more than one person owns the document. ### README must contain - The published citation support figures for generative systems, with the study and the date, before any usage instructions - That citations are resolved against your own passage store and verified separately, never taken from the model - That a sentence failing verification is marked rather than stripped of its citation - That deleting a source downgrades every sentence that cited it, and how many that was - Where sources, passages and drafts are stored, how to export everything, and what the monthly cost ceiling does ===== AGENTS.md ===== # Agent instructions · HyperWrite indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Next.js 15 and TypeScript, local, with the model key server side, SQLite through Drizzle, with source text kept as files beside it, Paragraph-aligned chunks with character offsets into the original, One provider behind one adapter, structured output, passages numbered in the prompt, A separate pass checking each sentence against the passage it cited. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Chunking purely by token count splits a claim from the qualifier that follows it, so the retrieved passage supports half a sentence and your verifier cheerfully passes it. Chunk on paragraph boundaries, overlap by a configured amount, keep the offsets, and show the surrounding text whenever somebody opens a citation. - Hiding retrieval behind a generate button makes a bad draft indistinguishable from a bad model, and you end up rewriting prompts to fix a problem that was three irrelevant passages. Put the retrieved set on screen first, let it be corrected, and store the exact set used with the draft. - Generating three thousand words and then deciding the structure is wrong wastes the generation and, worse, anchors the rewrite to the shape of the thing you were about to throw away. Approve a structure first, and make the approval a stored fact rather than a click that leaves no trace. - Rendering the model's citations as the app's citations gives you a document that looks sourced and is not: the published audit of four generative search engines found only about half of generated sentences fully supported by their citations, and about a quarter of citations not supporting the sentence they were attached to. Resolve every number against your own passage store, verify the sentence against the passage text in a separate call, and let the failures show. - Dropping the citation from a sentence that failed verification leaves a plain, confident sentence sitting in the draft and removes the only signal that it was doubtful. Keep the sentence, mark it unsupported, and make the unsupported count visible before the writer reads a word of the prose. ===== BUILD_PLAN.md ===== # Build plan · HyperWrite A local drafting workspace whose hard problems are chunking sources so a retrieved passage is actually a unit of support, verifying every generated sentence against the passage it cites instead of trusting the model's own citation, and keeping that verification correct as drafts are rewritten and sources removed. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Sources in, passages out Everything the app will ever cite is a passage with a stable id and an offset into the original text. ### Steps 1. Chunk on paragraph boundaries with overlap A passage should be a unit of support, not a unit of tokens. Files: `src/lib/chunk.ts`, `tests/chunk.test.ts` 2. Store character offsets into the original text Context is what lets a human judge a citation in two seconds. Files: `src/lib/sources.ts` 3. Never reuse a passage id A reused id turns a stale citation into a wrong one. Files: `src/db/schema.ts` ### Done when - [ ] Every passage resolves to a range in the original text - [ ] Re-importing an unchanged source adds no passages - [ ] Passage ids are never reused - [ ] An oversized source is refused with the size stated ### Watch out - Chunking purely by token count splits a claim from the qualifier that follows it, so the retrieved passage supports half a sentence and your verifier cheerfully passes it. Chunk on paragraph boundaries, overlap by a configured amount, keep the offsets, and show the surrounding text whenever somebody opens a citation. ## Phase 2 · Retrieval you can see and correct The passages that will be used are on screen, editable, before anything is generated. ### Steps 1. Show the retrieved set before generation Otherwise you are debugging the prompt for a retrieval bug. Files: `src/app/project/[id]/retrieve/page.tsx` 2. Store the exact set used with the draft So a strange draft can be explained instead of argued about. Files: `src/lib/drafts.ts` 3. Refuse to generate with nothing pinned An ungrounded draft is the thing this app exists to avoid. Files: `src/lib/generate.ts`, `tests/generate.test.ts` ### Done when - [ ] Retrieved passages are shown and editable before generation - [ ] The used passage set is stored with the draft - [ ] Generation with an empty set is refused - [ ] Search covers only the project's own sources ### Watch out - Hiding retrieval behind a generate button makes a bad draft indistinguishable from a bad model, and you end up rewriting prompts to fix a problem that was three irrelevant passages. Put the retrieved set on screen first, let it be corrected, and store the exact set used with the draft. ## Phase 3 · Outline first, approval before prose Nothing is drafted until a section structure with a brief per section has been approved. ### Steps 1. Gate drafting on an approved outline version Structure is cheaper to change than prose. Files: `src/lib/outline.ts` 2. Version outlines rather than replacing them A restructure should not delete yesterday's work. Files: `src/db/schema.ts` 3. Carry prohibited claims into every prompt The rule is useless if it only lives in the settings page. Files: `src/lib/prompt.ts`, `tests/prompt.test.ts` ### Done when - [ ] Drafting is refused before outline approval - [ ] A new outline version preserves earlier drafts - [ ] A section with no brief cannot be approved - [ ] Prohibited claims appear in every generation prompt ### Watch out - Generating three thousand words and then deciding the structure is wrong wastes the generation and, worse, anchors the rewrite to the shape of the thing you were about to throw away. Approve a structure first, and make the approval a stored fact rather than a click that leaves no trace. ## Phase 4 · Draft, then check every sentence yourself Each sentence is resolved against the passage it cited and rendered as supported, weak or unsupported. ### Steps 1. Resolve every cited number against your own store The model's numbering is a claim until you check it. Files: `src/lib/resolve.ts`, `tests/resolve.test.ts` 2. Verify the sentence against the passage separately A second pass with one job beats a longer first prompt. Files: `src/lib/verify.ts` 3. Show the three counts before the prose It is the first thing the writer needs to know. Files: `src/components/SupportSummary.tsx` ### Done when - [ ] An unresolvable citation renders as unsupported - [ ] Every citation row has a score and a check time - [ ] Support counts appear above the draft and in export - [ ] A prohibited claim is flagged before display ### Watch out - Rendering the model's citations as the app's citations gives you a document that looks sourced and is not: the published audit of four generative search engines found only about half of generated sentences fully supported by their citations, and about a quarter of citations not supporting the sentence they were attached to. Resolve every number against your own passage store, verify the sentence against the passage text in a separate call, and let the failures show. - Dropping the citation from a sentence that failed verification leaves a plain, confident sentence sitting in the draft and removes the only signal that it was doubtful. Keep the sentence, mark it unsupported, and make the unsupported count visible before the writer reads a word of the prose. ## Not in this build - Live web search during drafting. - Publishing to a content system. - A browser extension. - Team approvals and brand governance. ## After v1, if you want it - A quotation mode that inserts verbatim passage text with its offsets, which needs no verification at all - A per-source support report showing which documents actually carried the argument - A local embedding model for retrieval, so that nothing but the generation call leaves the machine ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Generation and verification. Give it a provider-side spend limit too. MODEL_API_KEY=sk-...redacted # Required. Stored with every draft, because a different model cites differently. MODEL_ID=a text model identifier # Required. The support check. A cheaper model is usually enough for this one job. VERIFIER_MODEL_ID=a smaller text model identifier # Required. Projects, passages, drafts, sentences and citations. DATABASE_PATH=./data/workspace.db # Required. Full original text per source, so a passage can be shown in context. SOURCE_TEXT_DIR=./data/sources # Required. Overlap between paragraph-aligned chunks. Changing it re-chunks new sources only. CHUNK_OVERLAP_CHARS=200 # Required. Verifier score below which a sentence is weak, and lower still is unsupported. SUPPORT_THRESHOLD=0.7 # Required. Generation stops here; reading, hand editing and export continue. MONTHLY_COST_CEILING=10.00
You are building a production product version of HyperWrite. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # HyperWrite · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For HyperWrite, draft, rewrite, and answer questions using user-selected context and personal templates. The hard boundary is browser presence, personalization history, and proprietary agent workflows, plus workflow, data, and model tuning. ## Product outcome A drafting tool where a sentence is either traceable to a passage you supplied and separately verified against it, or visibly marked as not, and where rewriting or deleting a source never leaves a stale tick behind. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - OpenAI API key - Node.js 22 - local or self-hosted deployment - user-supplied sources ## Explicit non-goals for v1 - Live web search during drafting. - Publishing to a content system. - A browser extension. - Team approvals and brand governance. - browser presence, personalization history, and proprietary agent workflows - proprietary ranking data - brand-trained models - team workflows - large template libraries ## Success criteria - An unresolvable citation is confirmed to render as unsupported - No citation row is confirmed to exist without a verifier score - A rewrite is confirmed unrenderable until re-verified - Deleting a source is confirmed to downgrade every sentence citing it - Export is confirmed to carry support state with the text ===== BRIEF.md ===== # Build brief · HyperWrite The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a drafting workspace that writes from sources you supplied and marks every sentence it cannot support to replace HyperWrite. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The thing that makes this worth building instead of opening a chat window is that a sentence is either traceable to a passage you supplied or it is visibly marked as not. Do not skip that, and do not let the model do it for itself. Asking a model to write with citations produces a document that looks sourced. When Liu, Zhang and Liang audited four generative search engines for Findings of EMNLP 2023 they found only 51.5 percent of generated sentences were fully supported by their citations, and only 74.5 percent of citations actually supported the sentence they were attached to. Those systems were built by teams whose entire product was citation quality. Your prompt will not do better. So a citation in this app is resolved, not generated. The model receives numbered passages and must cite by number. After generation the app splits the draft into sentences, resolves every cited number against the passage store, runs a separate check that the passage text actually supports the sentence, and renders anything that fails as unsupported. A sentence with no resolvable passage is never quietly stripped of its marker. And the sources are the ones the user gave you. No live search, no claims about the world beyond the documents in the project, and deleting a source marks every sentence that relied on it as unsupported rather than leaving a green tick pointing at nothing. ### Stack (fixed, do not substitute) - Runtime: Next.js 15 and TypeScript, local, with the model key server side · your sources are the interesting part and they stay on your machine - Storage: SQLite through Drizzle, with source text kept as files beside it · a passage has to be showable in its original context, not just as a chunk - Retrieval: Paragraph-aligned chunks with character offsets into the original · a chunk that splits a claim from its qualifier defeats the whole verification step - Generation: One provider behind one adapter, structured output, passages numbered in the prompt · the model cites by number and the numbers are yours - Verification: A separate pass checking each sentence against the passage it cited · the model's own citation is a claim, not evidence ### Data model (create this before Phase 1) - projects(id, title, audience, objective, tone, prohibited_claims_json, created_at) - sources(id, project_id, kind, title, origin, added_at, sha256, text_path) · kind is pasted, uploaded or fetched once - passages(id, source_id, ordinal, text, char_start, char_end) · the only unit anything is ever allowed to cite - outlines(id, project_id, version, approved_at) and sections(id, outline_id, ordinal, heading, brief) - drafts(id, section_id, version, created_at, model_id, prompt_version, cost_minor) · a rewrite is a new version, never an edit - sentences(id, draft_id, ordinal, text, support_state) · support_state is supported, weak or unsupported - citations(sentence_id, passage_id, verifier_score, verified_at) · a row exists only after the id resolved and the check ran - The model's own numbering never becomes a citation row without passing through resolution and verification first. ### Phase 1 · Sources in, passages out Build: Import pasted text, files and single fetched pages. Keep the full original text on disk with its hash. Chunk on paragraph boundaries with a configured overlap, storing character offsets so a passage can always be shown inside its surrounding text. Never renumber passages after creation, even when a source is re-imported. Trap: Chunking purely by token count splits a claim from the qualifier that follows it, so the retrieved passage supports half a sentence and your verifier cheerfully passes it. Chunk on paragraph boundaries, overlap by a configured amount, keep the offsets, and show the surrounding text whenever somebody opens a citation. Done when: Every passage resolves to a character range in its source's stored text and opening a citation shows the passage in context, tested. Re-importing an unchanged source creates no new passages, tested by hash. Passage ids are never reused after a source is edited; a changed source produces new passages and the old ones are retained until nothing cites them. A source too large for the configured limit is refused with the size stated rather than silently truncated. Do not build yet: No retrieval interface, outlines, drafting or verification yet. ### Phase 2 · Retrieval you can see and correct Build: For each section, retrieve candidate passages and show them with their source and a snippet. Let the writer pin a passage, remove one, or add one by searching. The pinned set is what goes into the prompt, and it is stored with the draft so a draft can be explained later. Trap: Hiding retrieval behind a generate button makes a bad draft indistinguishable from a bad model, and you end up rewriting prompts to fix a problem that was three irrelevant passages. Put the retrieved set on screen first, let it be corrected, and store the exact set used with the draft. Done when: The retrieved passage set is displayed before generation and can be pinned, removed and added to, with changes persisting. The exact passage set used is stored with each draft and shown when reviewing it later. Generation is refused when the pinned set is empty, with a message rather than an empty draft. Searching for a passage returns results from the project's sources only, asserted. Do not build yet: No outline approval, drafting or verification yet. ### Phase 3 · Outline first, approval before prose Build: Generate an outline of sections with a one line brief each, from the project's objective, audience and pinned passages. Require explicit approval before any section can be drafted. Store outline versions so a restructure does not discard the drafts that came before it. Trap: Generating three thousand words and then deciding the structure is wrong wastes the generation and, worse, anchors the rewrite to the shape of the thing you were about to throw away. Approve a structure first, and make the approval a stored fact rather than a click that leaves no trace. Done when: A section cannot be drafted before its outline version is approved, enforced server side. Approving a new outline version leaves earlier drafts intact and attached to their own version. Each section carries a brief and a section with an empty brief cannot be approved. The prohibited claims list from the project settings is included in every generation prompt, asserted. Do not build yet: No drafting, verification, rewriting or export yet. ### Phase 4 · Draft, then check every sentence yourself Build: Number the pinned passages in the prompt and require citations by number. After generation, split the draft into sentences, resolve each cited number to a passage id, run a separate check of the sentence against that passage's text, and store the score. Render the three states differently and show the counts at the top of the draft. Trap: Rendering the model's citations as the app's citations gives you a document that looks sourced and is not: the published audit of four generative search engines found only about half of generated sentences fully supported by their citations, and about a quarter of citations not supporting the sentence they were attached to. Resolve every number against your own passage store, verify the sentence against the passage text in a separate call, and let the failures show. Trap: Dropping the citation from a sentence that failed verification leaves a plain, confident sentence sitting in the draft and removes the only signal that it was doubtful. Keep the sentence, mark it unsupported, and make the unsupported count visible before the writer reads a word of the prose. Done when: A sentence citing a passage id that does not exist is rendered unsupported and keeps its marker, tested with a fabricated number. Every citation row carries a verifier score and the time it was checked, and no citation row exists without one. The supported, weak and unsupported counts appear above every draft and are carried into export. A sentence matching a prohibited claim is flagged before the draft is displayed, tested with a crafted case. Do not build yet: No rewriting, versioning, export or cost ceiling yet. ### Phase 5 · Rewrites, deletions and getting out Build: Rewrite controls produce a new draft version rather than editing text in place, and the new version cannot be displayed until its sentences have been resolved and verified. Deleting a source marks every sentence that cited it as unsupported. Export Markdown plus a JSON bundle carrying sources, passages, sentences and their support states. Cap monthly model spend. Trap: Rewriting a paragraph in place leaves the old sentence rows attached to new text, so the green ticks now belong to sentences nobody verified, which is worse than having no ticks at all. Make every rewrite a new draft version, recompute sentences and citations before rendering, and never show a draft whose verification has not completed. Trap: Deleting a source while its citations stay green turns the document into a claim about evidence that is no longer there. Cascade the deletion to support state, show the count of sentences affected, and offer to re-verify against remaining sources rather than silently downgrading everything. Done when: A rewrite creates a new draft version and the version is not renderable until verification has run, asserted. Deleting a source marks every sentence citing it as unsupported and reports the count, tested. Export produces Markdown with support markers plus a JSON bundle that reimports into an empty database with identical sentence and citation counts. Reaching the monthly cost ceiling disables generation and leaves reading, editing by hand and export working. Do not build yet: Nothing after this. ### Out of scope (and why) - Live web search during drafting, because a citation to a page that may have changed since is not verifiable and the whole design rests on passages you hold. - Publishing to a content system, which is integration plumbing and would put unverified sentences one click from being public. - A browser extension that rewrites text anywhere, which is the part of the paid product that needs a distribution channel rather than a weekend. - Team approvals and brand governance, which are workflow features that only start to matter when more than one person owns the document. ### README must contain - The published citation support figures for generative systems, with the study and the date, before any usage instructions - That citations are resolved against your own passage store and verified separately, never taken from the model - That a sentence failing verification is marked rather than stripped of its citation - That deleting a source downgrades every sentence that cited it, and how many that was - Where sources, passages and drafts are stored, how to export everything, and what the monthly cost ceiling does ===== ARCHITECTURE.md ===== # Architecture · HyperWrite ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Next.js 15 and TypeScript, local, with the model key server side | your sources are the interesting part and they stay on your machine | | Storage | SQLite through Drizzle, with source text kept as files beside it | a passage has to be showable in its original context, not just as a chunk | | Retrieval | Paragraph-aligned chunks with character offsets into the original | a chunk that splits a claim from its qualifier defeats the whole verification step | | Generation | One provider behind one adapter, structured output, passages numbered in the prompt | the model cites by number and the numbers are yours | | Verification | A separate pass checking each sentence against the passage it cited | the model's own citation is a claim, not evidence | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Sources | Import, hashing, original text and chunking. | Any chunker, provided passages keep offsets and ids are stable. | | Retrieval | Candidate passages and the pinned set. | Any retriever, provided the set is visible before generation. | | Generation | Numbered passages in, structured draft out. | Any provider, provided citations come back as numbers. | | Verification | Resolution, the support check and the states. | Any checker, provided it is a separate pass from generation. | | Export | Markdown with markers and the JSON bundle. | Any format, provided support state travels with the text. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `MODEL_API_KEY` · required, secret · Generation and verification. Give it a provider-side spend limit too. - `MODEL_ID` · required · Stored with every draft, because a different model cites differently. - `VERIFIER_MODEL_ID` · required · The support check. A cheaper model is usually enough for this one job. - `DATABASE_PATH` · required · Projects, passages, drafts, sentences and citations. - `SOURCE_TEXT_DIR` · required · Full original text per source, so a passage can be shown in context. - `CHUNK_OVERLAP_CHARS` · required · Overlap between paragraph-aligned chunks. Changing it re-chunks new sources only. - `SUPPORT_THRESHOLD` · required · Verifier score below which a sentence is weak, and lower still is unsupported. - `MONTHLY_COST_CEILING` · required · Generation stops here; reading, hand editing and export continue. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · HyperWrite product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Next.js 15 and TypeScript, local, with the model key server side, SQLite through Drizzle, with source text kept as files beside it, Paragraph-aligned chunks with character offsets into the original, One provider behind one adapter, structured output, passages numbered in the prompt, A separate pass checking each sentence against the passage it cited. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Chunking purely by token count splits a claim from the qualifier that follows it, so the retrieved passage supports half a sentence and your verifier cheerfully passes it. Chunk on paragraph boundaries, overlap by a configured amount, keep the offsets, and show the surrounding text whenever somebody opens a citation. - Hiding retrieval behind a generate button makes a bad draft indistinguishable from a bad model, and you end up rewriting prompts to fix a problem that was three irrelevant passages. Put the retrieved set on screen first, let it be corrected, and store the exact set used with the draft. - Generating three thousand words and then deciding the structure is wrong wastes the generation and, worse, anchors the rewrite to the shape of the thing you were about to throw away. Approve a structure first, and make the approval a stored fact rather than a click that leaves no trace. - Rendering the model's citations as the app's citations gives you a document that looks sourced and is not: the published audit of four generative search engines found only about half of generated sentences fully supported by their citations, and about a quarter of citations not supporting the sentence they were attached to. Resolve every number against your own passage store, verify the sentence against the passage text in a separate call, and let the failures show. - Dropping the citation from a sentence that failed verification leaves a plain, confident sentence sitting in the draft and removes the only signal that it was doubtful. Keep the sentence, mark it unsupported, and make the unsupported count visible before the writer reads a word of the prose. - Rewriting a paragraph in place leaves the old sentence rows attached to new text, so the green ticks now belong to sentences nobody verified, which is worse than having no ticks at all. Make every rewrite a new draft version, recompute sentences and citations before rendering, and never show a draft whose verification has not completed. - Deleting a source while its citations stay green turns the document into a claim about evidence that is no longer there. Cascade the deletion to support state, show the count of sentences affected, and offer to re-verify against remaining sources rather than silently downgrading everything. ===== MILESTONES.md ===== # Delivery milestones · HyperWrite Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Sources in, passages out Everything the app will ever cite is a passage with a stable id and an offset into the original text. ### Steps 1. Chunk on paragraph boundaries with overlap A passage should be a unit of support, not a unit of tokens. Files: `src/lib/chunk.ts`, `tests/chunk.test.ts` 2. Store character offsets into the original text Context is what lets a human judge a citation in two seconds. Files: `src/lib/sources.ts` 3. Never reuse a passage id A reused id turns a stale citation into a wrong one. Files: `src/db/schema.ts` ### Done when - [ ] Every passage resolves to a range in the original text - [ ] Re-importing an unchanged source adds no passages - [ ] Passage ids are never reused - [ ] An oversized source is refused with the size stated ### Watch out - Chunking purely by token count splits a claim from the qualifier that follows it, so the retrieved passage supports half a sentence and your verifier cheerfully passes it. Chunk on paragraph boundaries, overlap by a configured amount, keep the offsets, and show the surrounding text whenever somebody opens a citation. ## M2 · Retrieval you can see and correct The passages that will be used are on screen, editable, before anything is generated. ### Steps 1. Show the retrieved set before generation Otherwise you are debugging the prompt for a retrieval bug. Files: `src/app/project/[id]/retrieve/page.tsx` 2. Store the exact set used with the draft So a strange draft can be explained instead of argued about. Files: `src/lib/drafts.ts` 3. Refuse to generate with nothing pinned An ungrounded draft is the thing this app exists to avoid. Files: `src/lib/generate.ts`, `tests/generate.test.ts` ### Done when - [ ] Retrieved passages are shown and editable before generation - [ ] The used passage set is stored with the draft - [ ] Generation with an empty set is refused - [ ] Search covers only the project's own sources ### Watch out - Hiding retrieval behind a generate button makes a bad draft indistinguishable from a bad model, and you end up rewriting prompts to fix a problem that was three irrelevant passages. Put the retrieved set on screen first, let it be corrected, and store the exact set used with the draft. ## M3 · Outline first, approval before prose Nothing is drafted until a section structure with a brief per section has been approved. ### Steps 1. Gate drafting on an approved outline version Structure is cheaper to change than prose. Files: `src/lib/outline.ts` 2. Version outlines rather than replacing them A restructure should not delete yesterday's work. Files: `src/db/schema.ts` 3. Carry prohibited claims into every prompt The rule is useless if it only lives in the settings page. Files: `src/lib/prompt.ts`, `tests/prompt.test.ts` ### Done when - [ ] Drafting is refused before outline approval - [ ] A new outline version preserves earlier drafts - [ ] A section with no brief cannot be approved - [ ] Prohibited claims appear in every generation prompt ### Watch out - Generating three thousand words and then deciding the structure is wrong wastes the generation and, worse, anchors the rewrite to the shape of the thing you were about to throw away. Approve a structure first, and make the approval a stored fact rather than a click that leaves no trace. ## M4 · Draft, then check every sentence yourself Each sentence is resolved against the passage it cited and rendered as supported, weak or unsupported. ### Steps 1. Resolve every cited number against your own store The model's numbering is a claim until you check it. Files: `src/lib/resolve.ts`, `tests/resolve.test.ts` 2. Verify the sentence against the passage separately A second pass with one job beats a longer first prompt. Files: `src/lib/verify.ts` 3. Show the three counts before the prose It is the first thing the writer needs to know. Files: `src/components/SupportSummary.tsx` ### Done when - [ ] An unresolvable citation renders as unsupported - [ ] Every citation row has a score and a check time - [ ] Support counts appear above the draft and in export - [ ] A prohibited claim is flagged before display ### Watch out - Rendering the model's citations as the app's citations gives you a document that looks sourced and is not: the published audit of four generative search engines found only about half of generated sentences fully supported by their citations, and about a quarter of citations not supporting the sentence they were attached to. Resolve every number against your own passage store, verify the sentence against the passage text in a separate call, and let the failures show. - Dropping the citation from a sentence that failed verification leaves a plain, confident sentence sitting in the draft and removes the only signal that it was doubtful. Keep the sentence, mark it unsupported, and make the unsupported count visible before the writer reads a word of the prose. ## M5 · Rewrites, deletions and getting out (production only) Every rewrite is re-verified, deleting a source updates the document, and everything exports with its support state. ### Steps 1. Make every rewrite a new verified version Text and its verification have to move together. Files: `src/lib/rewrite.ts`, `tests/rewrite.test.ts` 2. Cascade source deletion into support state A citation to a deleted source is a lie with a tick next to it. Files: `src/lib/sources.ts` 3. Cap the monthly spend and degrade to manual A rented model with no ceiling is a surprise bill. Files: `src/lib/cost.ts` ### Done when - [ ] A rewrite is a new version and is unrenderable until verified - [ ] Deleting a source downgrades and counts affected sentences - [ ] Export round trips sentences and citations - [ ] The cost ceiling stops generation, not the app ### Watch out - Rewriting a paragraph in place leaves the old sentence rows attached to new text, so the green ticks now belong to sentences nobody verified, which is worse than having no ticks at all. Make every rewrite a new draft version, recompute sentences and citations before rendering, and never show a draft whose verification has not completed. - Deleting a source while its citations stay green turns the document into a claim about evidence that is no longer there. Cascade the deletion to support state, show the count of sentences affected, and offer to re-verify against remaining sources rather than silently downgrading everything. ===== OPERATIONS.md ===== # Operations · HyperWrite ## Backup The database and the source text directory together, since a passage without its original text cannot be shown in context and a citation to it becomes unopenable. Drafts are regenerable; the sources and the verification history are not. ## Restore Restore both, open a draft and confirm every citation still opens its passage in context, then confirm the support counts match what was exported. A restore where citations resolve but cannot display context is a half restore. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Proportion of sentences unsupported per draft, verifier calls per draft against the cost ceiling, and the count of citations pointing at deleted sources. A falling unsupported rate usually means the threshold moved, not that the writing improved. ## Incident checklist The incident is a published sentence that nothing supported. The stored passage set, the verifier scores and the draft versions are what let you reconstruct what the writer was shown on the day, rather than guessing. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] An unresolvable citation is confirmed to render as unsupported - [ ] No citation row is confirmed to exist without a verifier score - [ ] A rewrite is confirmed unrenderable until re-verified - [ ] Deleting a source is confirmed to downgrade every sentence citing it - [ ] Export is confirmed to carry support state with the text ## Launch constraint Do not market omitted HyperWrite capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Generation and verification. Give it a provider-side spend limit too. MODEL_API_KEY=sk-...redacted # Required. Stored with every draft, because a different model cites differently. MODEL_ID=a text model identifier # Required. The support check. A cheaper model is usually enough for this one job. VERIFIER_MODEL_ID=a smaller text model identifier # Required. Projects, passages, drafts, sentences and citations. DATABASE_PATH=./data/workspace.db # Required. Full original text per source, so a passage can be shown in context. SOURCE_TEXT_DIR=./data/sources # Required. Overlap between paragraph-aligned chunks. Changing it re-chunks new sources only. CHUNK_OVERLAP_CHARS=200 # Required. Verifier score below which a sentence is weak, and lower still is unsupported. SUPPORT_THRESHOLD=0.7 # Required. Generation stops here; reading, hand editing and export continue. MONTHLY_COST_CEILING=10.00
# HyperWrite · indie build A local drafting workspace whose hard problems are chunking sources so a retrieved passage is actually a unit of support, verifying every generated sentence against the passage it cites instead of trusting the model's own citation, and keeping that verification correct as drafts are rewritten and sources removed. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Next.js 15 and TypeScript, local, with the model key server side | your sources are the interesting part and they stay on your machine | | Storage | SQLite through Drizzle, with source text kept as files beside it | a passage has to be showable in its original context, not just as a chunk | | Retrieval | Paragraph-aligned chunks with character offsets into the original | a chunk that splits a claim from its qualifier defeats the whole verification step | | Generation | One provider behind one adapter, structured output, passages numbered in the prompt | the model cites by number and the numbers are yours | | Verification | A separate pass checking each sentence against the passage it cited | the model's own citation is a claim, not evidence | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the application and its test suite. - Get it: nodejs.org, or your version manager. - Verify: node --version prints v22 or higher - [ ] **An API key for a text model** · pay per token; measure a real section before choosing the ceiling - Why: Generation and the separate verification pass both go through it, and the verification pass roughly doubles the call count per draft. - Get it: Create a key with your chosen provider and set a spend limit in their console as well as the ceiling in this app. - Verify: A section drafts and every sentence receives a support state - [ ] **Real source documents for a real piece of writing** · free - Why: Chunking and verification both behave differently on a research paper, a transcript and a marketing page, and testing on one of them teaches you nothing about the others. - Get it: Assemble the documents for something you actually need to write, including at least one long and one badly formatted file. - Verify: All of them import, chunk and show passages in context - [ ] **The prohibited claims list for the project** · free - Why: A flagging rule written after the draft exists is written around the draft, which is the opposite of useful. - Get it: Write down the claims this document must never make, in the wording the flagger will match on. - Verify: The list is stored on the project and appears in every generation prompt ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Live web search during drafting. - Publishing to a content system. - A browser extension. - Team approvals and brand governance. - browser presence, personalization history, and proprietary agent workflows - proprietary ranking data - brand-trained models - team workflows - large template libraries If one of those is essential to you, that is the reason to keep paying for HyperWrite, and the README should say so rather than pretend.
# Build brief · HyperWrite The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a drafting workspace that writes from sources you supplied and marks every sentence it cannot support to replace HyperWrite. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The thing that makes this worth building instead of opening a chat window is that a sentence is either traceable to a passage you supplied or it is visibly marked as not. Do not skip that, and do not let the model do it for itself. Asking a model to write with citations produces a document that looks sourced. When Liu, Zhang and Liang audited four generative search engines for Findings of EMNLP 2023 they found only 51.5 percent of generated sentences were fully supported by their citations, and only 74.5 percent of citations actually supported the sentence they were attached to. Those systems were built by teams whose entire product was citation quality. Your prompt will not do better. So a citation in this app is resolved, not generated. The model receives numbered passages and must cite by number. After generation the app splits the draft into sentences, resolves every cited number against the passage store, runs a separate check that the passage text actually supports the sentence, and renders anything that fails as unsupported. A sentence with no resolvable passage is never quietly stripped of its marker. And the sources are the ones the user gave you. No live search, no claims about the world beyond the documents in the project, and deleting a source marks every sentence that relied on it as unsupported rather than leaving a green tick pointing at nothing. ### Stack (fixed, do not substitute) - Runtime: Next.js 15 and TypeScript, local, with the model key server side · your sources are the interesting part and they stay on your machine - Storage: SQLite through Drizzle, with source text kept as files beside it · a passage has to be showable in its original context, not just as a chunk - Retrieval: Paragraph-aligned chunks with character offsets into the original · a chunk that splits a claim from its qualifier defeats the whole verification step - Generation: One provider behind one adapter, structured output, passages numbered in the prompt · the model cites by number and the numbers are yours - Verification: A separate pass checking each sentence against the passage it cited · the model's own citation is a claim, not evidence ### Data model (create this before Phase 1) - projects(id, title, audience, objective, tone, prohibited_claims_json, created_at) - sources(id, project_id, kind, title, origin, added_at, sha256, text_path) · kind is pasted, uploaded or fetched once - passages(id, source_id, ordinal, text, char_start, char_end) · the only unit anything is ever allowed to cite - outlines(id, project_id, version, approved_at) and sections(id, outline_id, ordinal, heading, brief) - drafts(id, section_id, version, created_at, model_id, prompt_version, cost_minor) · a rewrite is a new version, never an edit - sentences(id, draft_id, ordinal, text, support_state) · support_state is supported, weak or unsupported - citations(sentence_id, passage_id, verifier_score, verified_at) · a row exists only after the id resolved and the check ran - The model's own numbering never becomes a citation row without passing through resolution and verification first. ### Phase 1 · Sources in, passages out Build: Import pasted text, files and single fetched pages. Keep the full original text on disk with its hash. Chunk on paragraph boundaries with a configured overlap, storing character offsets so a passage can always be shown inside its surrounding text. Never renumber passages after creation, even when a source is re-imported. Trap: Chunking purely by token count splits a claim from the qualifier that follows it, so the retrieved passage supports half a sentence and your verifier cheerfully passes it. Chunk on paragraph boundaries, overlap by a configured amount, keep the offsets, and show the surrounding text whenever somebody opens a citation. Done when: Every passage resolves to a character range in its source's stored text and opening a citation shows the passage in context, tested. Re-importing an unchanged source creates no new passages, tested by hash. Passage ids are never reused after a source is edited; a changed source produces new passages and the old ones are retained until nothing cites them. A source too large for the configured limit is refused with the size stated rather than silently truncated. Do not build yet: No retrieval interface, outlines, drafting or verification yet. ### Phase 2 · Retrieval you can see and correct Build: For each section, retrieve candidate passages and show them with their source and a snippet. Let the writer pin a passage, remove one, or add one by searching. The pinned set is what goes into the prompt, and it is stored with the draft so a draft can be explained later. Trap: Hiding retrieval behind a generate button makes a bad draft indistinguishable from a bad model, and you end up rewriting prompts to fix a problem that was three irrelevant passages. Put the retrieved set on screen first, let it be corrected, and store the exact set used with the draft. Done when: The retrieved passage set is displayed before generation and can be pinned, removed and added to, with changes persisting. The exact passage set used is stored with each draft and shown when reviewing it later. Generation is refused when the pinned set is empty, with a message rather than an empty draft. Searching for a passage returns results from the project's sources only, asserted. Do not build yet: No outline approval, drafting or verification yet. ### Phase 3 · Outline first, approval before prose Build: Generate an outline of sections with a one line brief each, from the project's objective, audience and pinned passages. Require explicit approval before any section can be drafted. Store outline versions so a restructure does not discard the drafts that came before it. Trap: Generating three thousand words and then deciding the structure is wrong wastes the generation and, worse, anchors the rewrite to the shape of the thing you were about to throw away. Approve a structure first, and make the approval a stored fact rather than a click that leaves no trace. Done when: A section cannot be drafted before its outline version is approved, enforced server side. Approving a new outline version leaves earlier drafts intact and attached to their own version. Each section carries a brief and a section with an empty brief cannot be approved. The prohibited claims list from the project settings is included in every generation prompt, asserted. Do not build yet: No drafting, verification, rewriting or export yet. ### Phase 4 · Draft, then check every sentence yourself Build: Number the pinned passages in the prompt and require citations by number. After generation, split the draft into sentences, resolve each cited number to a passage id, run a separate check of the sentence against that passage's text, and store the score. Render the three states differently and show the counts at the top of the draft. Trap: Rendering the model's citations as the app's citations gives you a document that looks sourced and is not: the published audit of four generative search engines found only about half of generated sentences fully supported by their citations, and about a quarter of citations not supporting the sentence they were attached to. Resolve every number against your own passage store, verify the sentence against the passage text in a separate call, and let the failures show. Trap: Dropping the citation from a sentence that failed verification leaves a plain, confident sentence sitting in the draft and removes the only signal that it was doubtful. Keep the sentence, mark it unsupported, and make the unsupported count visible before the writer reads a word of the prose. Done when: A sentence citing a passage id that does not exist is rendered unsupported and keeps its marker, tested with a fabricated number. Every citation row carries a verifier score and the time it was checked, and no citation row exists without one. The supported, weak and unsupported counts appear above every draft and are carried into export. A sentence matching a prohibited claim is flagged before the draft is displayed, tested with a crafted case. Do not build yet: No rewriting, versioning, export or cost ceiling yet. ### Phase 5 · Rewrites, deletions and getting out Build: Rewrite controls produce a new draft version rather than editing text in place, and the new version cannot be displayed until its sentences have been resolved and verified. Deleting a source marks every sentence that cited it as unsupported. Export Markdown plus a JSON bundle carrying sources, passages, sentences and their support states. Cap monthly model spend. Trap: Rewriting a paragraph in place leaves the old sentence rows attached to new text, so the green ticks now belong to sentences nobody verified, which is worse than having no ticks at all. Make every rewrite a new draft version, recompute sentences and citations before rendering, and never show a draft whose verification has not completed. Trap: Deleting a source while its citations stay green turns the document into a claim about evidence that is no longer there. Cascade the deletion to support state, show the count of sentences affected, and offer to re-verify against remaining sources rather than silently downgrading everything. Done when: A rewrite creates a new draft version and the version is not renderable until verification has run, asserted. Deleting a source marks every sentence citing it as unsupported and reports the count, tested. Export produces Markdown with support markers plus a JSON bundle that reimports into an empty database with identical sentence and citation counts. Reaching the monthly cost ceiling disables generation and leaves reading, editing by hand and export working. Do not build yet: Nothing after this. ### Out of scope (and why) - Live web search during drafting, because a citation to a page that may have changed since is not verifiable and the whole design rests on passages you hold. - Publishing to a content system, which is integration plumbing and would put unverified sentences one click from being public. - A browser extension that rewrites text anywhere, which is the part of the paid product that needs a distribution channel rather than a weekend. - Team approvals and brand governance, which are workflow features that only start to matter when more than one person owns the document. ### README must contain - The published citation support figures for generative systems, with the study and the date, before any usage instructions - That citations are resolved against your own passage store and verified separately, never taken from the model - That a sentence failing verification is marked rather than stripped of its citation - That deleting a source downgrades every sentence that cited it, and how many that was - Where sources, passages and drafts are stored, how to export everything, and what the monthly cost ceiling does
# Agent instructions · HyperWrite indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Next.js 15 and TypeScript, local, with the model key server side, SQLite through Drizzle, with source text kept as files beside it, Paragraph-aligned chunks with character offsets into the original, One provider behind one adapter, structured output, passages numbered in the prompt, A separate pass checking each sentence against the passage it cited. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Chunking purely by token count splits a claim from the qualifier that follows it, so the retrieved passage supports half a sentence and your verifier cheerfully passes it. Chunk on paragraph boundaries, overlap by a configured amount, keep the offsets, and show the surrounding text whenever somebody opens a citation. - Hiding retrieval behind a generate button makes a bad draft indistinguishable from a bad model, and you end up rewriting prompts to fix a problem that was three irrelevant passages. Put the retrieved set on screen first, let it be corrected, and store the exact set used with the draft. - Generating three thousand words and then deciding the structure is wrong wastes the generation and, worse, anchors the rewrite to the shape of the thing you were about to throw away. Approve a structure first, and make the approval a stored fact rather than a click that leaves no trace. - Rendering the model's citations as the app's citations gives you a document that looks sourced and is not: the published audit of four generative search engines found only about half of generated sentences fully supported by their citations, and about a quarter of citations not supporting the sentence they were attached to. Resolve every number against your own passage store, verify the sentence against the passage text in a separate call, and let the failures show. - Dropping the citation from a sentence that failed verification leaves a plain, confident sentence sitting in the draft and removes the only signal that it was doubtful. Keep the sentence, mark it unsupported, and make the unsupported count visible before the writer reads a word of the prose.
# Build plan · HyperWrite A local drafting workspace whose hard problems are chunking sources so a retrieved passage is actually a unit of support, verifying every generated sentence against the passage it cites instead of trusting the model's own citation, and keeping that verification correct as drafts are rewritten and sources removed. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Sources in, passages out Everything the app will ever cite is a passage with a stable id and an offset into the original text. ### Steps 1. Chunk on paragraph boundaries with overlap A passage should be a unit of support, not a unit of tokens. Files: `src/lib/chunk.ts`, `tests/chunk.test.ts` 2. Store character offsets into the original text Context is what lets a human judge a citation in two seconds. Files: `src/lib/sources.ts` 3. Never reuse a passage id A reused id turns a stale citation into a wrong one. Files: `src/db/schema.ts` ### Done when - [ ] Every passage resolves to a range in the original text - [ ] Re-importing an unchanged source adds no passages - [ ] Passage ids are never reused - [ ] An oversized source is refused with the size stated ### Watch out - Chunking purely by token count splits a claim from the qualifier that follows it, so the retrieved passage supports half a sentence and your verifier cheerfully passes it. Chunk on paragraph boundaries, overlap by a configured amount, keep the offsets, and show the surrounding text whenever somebody opens a citation. ## Phase 2 · Retrieval you can see and correct The passages that will be used are on screen, editable, before anything is generated. ### Steps 1. Show the retrieved set before generation Otherwise you are debugging the prompt for a retrieval bug. Files: `src/app/project/[id]/retrieve/page.tsx` 2. Store the exact set used with the draft So a strange draft can be explained instead of argued about. Files: `src/lib/drafts.ts` 3. Refuse to generate with nothing pinned An ungrounded draft is the thing this app exists to avoid. Files: `src/lib/generate.ts`, `tests/generate.test.ts` ### Done when - [ ] Retrieved passages are shown and editable before generation - [ ] The used passage set is stored with the draft - [ ] Generation with an empty set is refused - [ ] Search covers only the project's own sources ### Watch out - Hiding retrieval behind a generate button makes a bad draft indistinguishable from a bad model, and you end up rewriting prompts to fix a problem that was three irrelevant passages. Put the retrieved set on screen first, let it be corrected, and store the exact set used with the draft. ## Phase 3 · Outline first, approval before prose Nothing is drafted until a section structure with a brief per section has been approved. ### Steps 1. Gate drafting on an approved outline version Structure is cheaper to change than prose. Files: `src/lib/outline.ts` 2. Version outlines rather than replacing them A restructure should not delete yesterday's work. Files: `src/db/schema.ts` 3. Carry prohibited claims into every prompt The rule is useless if it only lives in the settings page. Files: `src/lib/prompt.ts`, `tests/prompt.test.ts` ### Done when - [ ] Drafting is refused before outline approval - [ ] A new outline version preserves earlier drafts - [ ] A section with no brief cannot be approved - [ ] Prohibited claims appear in every generation prompt ### Watch out - Generating three thousand words and then deciding the structure is wrong wastes the generation and, worse, anchors the rewrite to the shape of the thing you were about to throw away. Approve a structure first, and make the approval a stored fact rather than a click that leaves no trace. ## Phase 4 · Draft, then check every sentence yourself Each sentence is resolved against the passage it cited and rendered as supported, weak or unsupported. ### Steps 1. Resolve every cited number against your own store The model's numbering is a claim until you check it. Files: `src/lib/resolve.ts`, `tests/resolve.test.ts` 2. Verify the sentence against the passage separately A second pass with one job beats a longer first prompt. Files: `src/lib/verify.ts` 3. Show the three counts before the prose It is the first thing the writer needs to know. Files: `src/components/SupportSummary.tsx` ### Done when - [ ] An unresolvable citation renders as unsupported - [ ] Every citation row has a score and a check time - [ ] Support counts appear above the draft and in export - [ ] A prohibited claim is flagged before display ### Watch out - Rendering the model's citations as the app's citations gives you a document that looks sourced and is not: the published audit of four generative search engines found only about half of generated sentences fully supported by their citations, and about a quarter of citations not supporting the sentence they were attached to. Resolve every number against your own passage store, verify the sentence against the passage text in a separate call, and let the failures show. - Dropping the citation from a sentence that failed verification leaves a plain, confident sentence sitting in the draft and removes the only signal that it was doubtful. Keep the sentence, mark it unsupported, and make the unsupported count visible before the writer reads a word of the prose. ## Not in this build - Live web search during drafting. - Publishing to a content system. - A browser extension. - Team approvals and brand governance. ## After v1, if you want it - A quotation mode that inserts verbatim passage text with its offsets, which needs no verification at all - A per-source support report showing which documents actually carried the argument - A local embedding model for retrieval, so that nothing but the generation call leaves the machine
# Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Generation and verification. Give it a provider-side spend limit too. MODEL_API_KEY=sk-...redacted # Required. Stored with every draft, because a different model cites differently. MODEL_ID=a text model identifier # Required. The support check. A cheaper model is usually enough for this one job. VERIFIER_MODEL_ID=a smaller text model identifier # Required. Projects, passages, drafts, sentences and citations. DATABASE_PATH=./data/workspace.db # Required. Full original text per source, so a passage can be shown in context. SOURCE_TEXT_DIR=./data/sources # Required. Overlap between paragraph-aligned chunks. Changing it re-chunks new sources only. CHUNK_OVERLAP_CHARS=200 # Required. Verifier score below which a sentence is weak, and lower still is unsupported. SUPPORT_THRESHOLD=0.7 # Required. Generation stops here; reading, hand editing and export continue. MONTHLY_COST_CEILING=10.00
# HyperWrite · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For HyperWrite, draft, rewrite, and answer questions using user-selected context and personal templates. The hard boundary is browser presence, personalization history, and proprietary agent workflows, plus workflow, data, and model tuning. ## Product outcome A drafting tool where a sentence is either traceable to a passage you supplied and separately verified against it, or visibly marked as not, and where rewriting or deleting a source never leaves a stale tick behind. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - OpenAI API key - Node.js 22 - local or self-hosted deployment - user-supplied sources ## Explicit non-goals for v1 - Live web search during drafting. - Publishing to a content system. - A browser extension. - Team approvals and brand governance. - browser presence, personalization history, and proprietary agent workflows - proprietary ranking data - brand-trained models - team workflows - large template libraries ## Success criteria - An unresolvable citation is confirmed to render as unsupported - No citation row is confirmed to exist without a verifier score - A rewrite is confirmed unrenderable until re-verified - Deleting a source is confirmed to downgrade every sentence citing it - Export is confirmed to carry support state with the text
# Build brief · HyperWrite The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a drafting workspace that writes from sources you supplied and marks every sentence it cannot support to replace HyperWrite. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The thing that makes this worth building instead of opening a chat window is that a sentence is either traceable to a passage you supplied or it is visibly marked as not. Do not skip that, and do not let the model do it for itself. Asking a model to write with citations produces a document that looks sourced. When Liu, Zhang and Liang audited four generative search engines for Findings of EMNLP 2023 they found only 51.5 percent of generated sentences were fully supported by their citations, and only 74.5 percent of citations actually supported the sentence they were attached to. Those systems were built by teams whose entire product was citation quality. Your prompt will not do better. So a citation in this app is resolved, not generated. The model receives numbered passages and must cite by number. After generation the app splits the draft into sentences, resolves every cited number against the passage store, runs a separate check that the passage text actually supports the sentence, and renders anything that fails as unsupported. A sentence with no resolvable passage is never quietly stripped of its marker. And the sources are the ones the user gave you. No live search, no claims about the world beyond the documents in the project, and deleting a source marks every sentence that relied on it as unsupported rather than leaving a green tick pointing at nothing. ### Stack (fixed, do not substitute) - Runtime: Next.js 15 and TypeScript, local, with the model key server side · your sources are the interesting part and they stay on your machine - Storage: SQLite through Drizzle, with source text kept as files beside it · a passage has to be showable in its original context, not just as a chunk - Retrieval: Paragraph-aligned chunks with character offsets into the original · a chunk that splits a claim from its qualifier defeats the whole verification step - Generation: One provider behind one adapter, structured output, passages numbered in the prompt · the model cites by number and the numbers are yours - Verification: A separate pass checking each sentence against the passage it cited · the model's own citation is a claim, not evidence ### Data model (create this before Phase 1) - projects(id, title, audience, objective, tone, prohibited_claims_json, created_at) - sources(id, project_id, kind, title, origin, added_at, sha256, text_path) · kind is pasted, uploaded or fetched once - passages(id, source_id, ordinal, text, char_start, char_end) · the only unit anything is ever allowed to cite - outlines(id, project_id, version, approved_at) and sections(id, outline_id, ordinal, heading, brief) - drafts(id, section_id, version, created_at, model_id, prompt_version, cost_minor) · a rewrite is a new version, never an edit - sentences(id, draft_id, ordinal, text, support_state) · support_state is supported, weak or unsupported - citations(sentence_id, passage_id, verifier_score, verified_at) · a row exists only after the id resolved and the check ran - The model's own numbering never becomes a citation row without passing through resolution and verification first. ### Phase 1 · Sources in, passages out Build: Import pasted text, files and single fetched pages. Keep the full original text on disk with its hash. Chunk on paragraph boundaries with a configured overlap, storing character offsets so a passage can always be shown inside its surrounding text. Never renumber passages after creation, even when a source is re-imported. Trap: Chunking purely by token count splits a claim from the qualifier that follows it, so the retrieved passage supports half a sentence and your verifier cheerfully passes it. Chunk on paragraph boundaries, overlap by a configured amount, keep the offsets, and show the surrounding text whenever somebody opens a citation. Done when: Every passage resolves to a character range in its source's stored text and opening a citation shows the passage in context, tested. Re-importing an unchanged source creates no new passages, tested by hash. Passage ids are never reused after a source is edited; a changed source produces new passages and the old ones are retained until nothing cites them. A source too large for the configured limit is refused with the size stated rather than silently truncated. Do not build yet: No retrieval interface, outlines, drafting or verification yet. ### Phase 2 · Retrieval you can see and correct Build: For each section, retrieve candidate passages and show them with their source and a snippet. Let the writer pin a passage, remove one, or add one by searching. The pinned set is what goes into the prompt, and it is stored with the draft so a draft can be explained later. Trap: Hiding retrieval behind a generate button makes a bad draft indistinguishable from a bad model, and you end up rewriting prompts to fix a problem that was three irrelevant passages. Put the retrieved set on screen first, let it be corrected, and store the exact set used with the draft. Done when: The retrieved passage set is displayed before generation and can be pinned, removed and added to, with changes persisting. The exact passage set used is stored with each draft and shown when reviewing it later. Generation is refused when the pinned set is empty, with a message rather than an empty draft. Searching for a passage returns results from the project's sources only, asserted. Do not build yet: No outline approval, drafting or verification yet. ### Phase 3 · Outline first, approval before prose Build: Generate an outline of sections with a one line brief each, from the project's objective, audience and pinned passages. Require explicit approval before any section can be drafted. Store outline versions so a restructure does not discard the drafts that came before it. Trap: Generating three thousand words and then deciding the structure is wrong wastes the generation and, worse, anchors the rewrite to the shape of the thing you were about to throw away. Approve a structure first, and make the approval a stored fact rather than a click that leaves no trace. Done when: A section cannot be drafted before its outline version is approved, enforced server side. Approving a new outline version leaves earlier drafts intact and attached to their own version. Each section carries a brief and a section with an empty brief cannot be approved. The prohibited claims list from the project settings is included in every generation prompt, asserted. Do not build yet: No drafting, verification, rewriting or export yet. ### Phase 4 · Draft, then check every sentence yourself Build: Number the pinned passages in the prompt and require citations by number. After generation, split the draft into sentences, resolve each cited number to a passage id, run a separate check of the sentence against that passage's text, and store the score. Render the three states differently and show the counts at the top of the draft. Trap: Rendering the model's citations as the app's citations gives you a document that looks sourced and is not: the published audit of four generative search engines found only about half of generated sentences fully supported by their citations, and about a quarter of citations not supporting the sentence they were attached to. Resolve every number against your own passage store, verify the sentence against the passage text in a separate call, and let the failures show. Trap: Dropping the citation from a sentence that failed verification leaves a plain, confident sentence sitting in the draft and removes the only signal that it was doubtful. Keep the sentence, mark it unsupported, and make the unsupported count visible before the writer reads a word of the prose. Done when: A sentence citing a passage id that does not exist is rendered unsupported and keeps its marker, tested with a fabricated number. Every citation row carries a verifier score and the time it was checked, and no citation row exists without one. The supported, weak and unsupported counts appear above every draft and are carried into export. A sentence matching a prohibited claim is flagged before the draft is displayed, tested with a crafted case. Do not build yet: No rewriting, versioning, export or cost ceiling yet. ### Phase 5 · Rewrites, deletions and getting out Build: Rewrite controls produce a new draft version rather than editing text in place, and the new version cannot be displayed until its sentences have been resolved and verified. Deleting a source marks every sentence that cited it as unsupported. Export Markdown plus a JSON bundle carrying sources, passages, sentences and their support states. Cap monthly model spend. Trap: Rewriting a paragraph in place leaves the old sentence rows attached to new text, so the green ticks now belong to sentences nobody verified, which is worse than having no ticks at all. Make every rewrite a new draft version, recompute sentences and citations before rendering, and never show a draft whose verification has not completed. Trap: Deleting a source while its citations stay green turns the document into a claim about evidence that is no longer there. Cascade the deletion to support state, show the count of sentences affected, and offer to re-verify against remaining sources rather than silently downgrading everything. Done when: A rewrite creates a new draft version and the version is not renderable until verification has run, asserted. Deleting a source marks every sentence citing it as unsupported and reports the count, tested. Export produces Markdown with support markers plus a JSON bundle that reimports into an empty database with identical sentence and citation counts. Reaching the monthly cost ceiling disables generation and leaves reading, editing by hand and export working. Do not build yet: Nothing after this. ### Out of scope (and why) - Live web search during drafting, because a citation to a page that may have changed since is not verifiable and the whole design rests on passages you hold. - Publishing to a content system, which is integration plumbing and would put unverified sentences one click from being public. - A browser extension that rewrites text anywhere, which is the part of the paid product that needs a distribution channel rather than a weekend. - Team approvals and brand governance, which are workflow features that only start to matter when more than one person owns the document. ### README must contain - The published citation support figures for generative systems, with the study and the date, before any usage instructions - That citations are resolved against your own passage store and verified separately, never taken from the model - That a sentence failing verification is marked rather than stripped of its citation - That deleting a source downgrades every sentence that cited it, and how many that was - Where sources, passages and drafts are stored, how to export everything, and what the monthly cost ceiling does
# Architecture · HyperWrite ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Next.js 15 and TypeScript, local, with the model key server side | your sources are the interesting part and they stay on your machine | | Storage | SQLite through Drizzle, with source text kept as files beside it | a passage has to be showable in its original context, not just as a chunk | | Retrieval | Paragraph-aligned chunks with character offsets into the original | a chunk that splits a claim from its qualifier defeats the whole verification step | | Generation | One provider behind one adapter, structured output, passages numbered in the prompt | the model cites by number and the numbers are yours | | Verification | A separate pass checking each sentence against the passage it cited | the model's own citation is a claim, not evidence | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Sources | Import, hashing, original text and chunking. | Any chunker, provided passages keep offsets and ids are stable. | | Retrieval | Candidate passages and the pinned set. | Any retriever, provided the set is visible before generation. | | Generation | Numbered passages in, structured draft out. | Any provider, provided citations come back as numbers. | | Verification | Resolution, the support check and the states. | Any checker, provided it is a separate pass from generation. | | Export | Markdown with markers and the JSON bundle. | Any format, provided support state travels with the text. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `MODEL_API_KEY` · required, secret · Generation and verification. Give it a provider-side spend limit too. - `MODEL_ID` · required · Stored with every draft, because a different model cites differently. - `VERIFIER_MODEL_ID` · required · The support check. A cheaper model is usually enough for this one job. - `DATABASE_PATH` · required · Projects, passages, drafts, sentences and citations. - `SOURCE_TEXT_DIR` · required · Full original text per source, so a passage can be shown in context. - `CHUNK_OVERLAP_CHARS` · required · Overlap between paragraph-aligned chunks. Changing it re-chunks new sources only. - `SUPPORT_THRESHOLD` · required · Verifier score below which a sentence is weak, and lower still is unsupported. - `MONTHLY_COST_CEILING` · required · Generation stops here; reading, hand editing and export continue. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · HyperWrite product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Next.js 15 and TypeScript, local, with the model key server side, SQLite through Drizzle, with source text kept as files beside it, Paragraph-aligned chunks with character offsets into the original, One provider behind one adapter, structured output, passages numbered in the prompt, A separate pass checking each sentence against the passage it cited. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Chunking purely by token count splits a claim from the qualifier that follows it, so the retrieved passage supports half a sentence and your verifier cheerfully passes it. Chunk on paragraph boundaries, overlap by a configured amount, keep the offsets, and show the surrounding text whenever somebody opens a citation. - Hiding retrieval behind a generate button makes a bad draft indistinguishable from a bad model, and you end up rewriting prompts to fix a problem that was three irrelevant passages. Put the retrieved set on screen first, let it be corrected, and store the exact set used with the draft. - Generating three thousand words and then deciding the structure is wrong wastes the generation and, worse, anchors the rewrite to the shape of the thing you were about to throw away. Approve a structure first, and make the approval a stored fact rather than a click that leaves no trace. - Rendering the model's citations as the app's citations gives you a document that looks sourced and is not: the published audit of four generative search engines found only about half of generated sentences fully supported by their citations, and about a quarter of citations not supporting the sentence they were attached to. Resolve every number against your own passage store, verify the sentence against the passage text in a separate call, and let the failures show. - Dropping the citation from a sentence that failed verification leaves a plain, confident sentence sitting in the draft and removes the only signal that it was doubtful. Keep the sentence, mark it unsupported, and make the unsupported count visible before the writer reads a word of the prose. - Rewriting a paragraph in place leaves the old sentence rows attached to new text, so the green ticks now belong to sentences nobody verified, which is worse than having no ticks at all. Make every rewrite a new draft version, recompute sentences and citations before rendering, and never show a draft whose verification has not completed. - Deleting a source while its citations stay green turns the document into a claim about evidence that is no longer there. Cascade the deletion to support state, show the count of sentences affected, and offer to re-verify against remaining sources rather than silently downgrading everything.
# Delivery milestones · HyperWrite Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Sources in, passages out Everything the app will ever cite is a passage with a stable id and an offset into the original text. ### Steps 1. Chunk on paragraph boundaries with overlap A passage should be a unit of support, not a unit of tokens. Files: `src/lib/chunk.ts`, `tests/chunk.test.ts` 2. Store character offsets into the original text Context is what lets a human judge a citation in two seconds. Files: `src/lib/sources.ts` 3. Never reuse a passage id A reused id turns a stale citation into a wrong one. Files: `src/db/schema.ts` ### Done when - [ ] Every passage resolves to a range in the original text - [ ] Re-importing an unchanged source adds no passages - [ ] Passage ids are never reused - [ ] An oversized source is refused with the size stated ### Watch out - Chunking purely by token count splits a claim from the qualifier that follows it, so the retrieved passage supports half a sentence and your verifier cheerfully passes it. Chunk on paragraph boundaries, overlap by a configured amount, keep the offsets, and show the surrounding text whenever somebody opens a citation. ## M2 · Retrieval you can see and correct The passages that will be used are on screen, editable, before anything is generated. ### Steps 1. Show the retrieved set before generation Otherwise you are debugging the prompt for a retrieval bug. Files: `src/app/project/[id]/retrieve/page.tsx` 2. Store the exact set used with the draft So a strange draft can be explained instead of argued about. Files: `src/lib/drafts.ts` 3. Refuse to generate with nothing pinned An ungrounded draft is the thing this app exists to avoid. Files: `src/lib/generate.ts`, `tests/generate.test.ts` ### Done when - [ ] Retrieved passages are shown and editable before generation - [ ] The used passage set is stored with the draft - [ ] Generation with an empty set is refused - [ ] Search covers only the project's own sources ### Watch out - Hiding retrieval behind a generate button makes a bad draft indistinguishable from a bad model, and you end up rewriting prompts to fix a problem that was three irrelevant passages. Put the retrieved set on screen first, let it be corrected, and store the exact set used with the draft. ## M3 · Outline first, approval before prose Nothing is drafted until a section structure with a brief per section has been approved. ### Steps 1. Gate drafting on an approved outline version Structure is cheaper to change than prose. Files: `src/lib/outline.ts` 2. Version outlines rather than replacing them A restructure should not delete yesterday's work. Files: `src/db/schema.ts` 3. Carry prohibited claims into every prompt The rule is useless if it only lives in the settings page. Files: `src/lib/prompt.ts`, `tests/prompt.test.ts` ### Done when - [ ] Drafting is refused before outline approval - [ ] A new outline version preserves earlier drafts - [ ] A section with no brief cannot be approved - [ ] Prohibited claims appear in every generation prompt ### Watch out - Generating three thousand words and then deciding the structure is wrong wastes the generation and, worse, anchors the rewrite to the shape of the thing you were about to throw away. Approve a structure first, and make the approval a stored fact rather than a click that leaves no trace. ## M4 · Draft, then check every sentence yourself Each sentence is resolved against the passage it cited and rendered as supported, weak or unsupported. ### Steps 1. Resolve every cited number against your own store The model's numbering is a claim until you check it. Files: `src/lib/resolve.ts`, `tests/resolve.test.ts` 2. Verify the sentence against the passage separately A second pass with one job beats a longer first prompt. Files: `src/lib/verify.ts` 3. Show the three counts before the prose It is the first thing the writer needs to know. Files: `src/components/SupportSummary.tsx` ### Done when - [ ] An unresolvable citation renders as unsupported - [ ] Every citation row has a score and a check time - [ ] Support counts appear above the draft and in export - [ ] A prohibited claim is flagged before display ### Watch out - Rendering the model's citations as the app's citations gives you a document that looks sourced and is not: the published audit of four generative search engines found only about half of generated sentences fully supported by their citations, and about a quarter of citations not supporting the sentence they were attached to. Resolve every number against your own passage store, verify the sentence against the passage text in a separate call, and let the failures show. - Dropping the citation from a sentence that failed verification leaves a plain, confident sentence sitting in the draft and removes the only signal that it was doubtful. Keep the sentence, mark it unsupported, and make the unsupported count visible before the writer reads a word of the prose. ## M5 · Rewrites, deletions and getting out (production only) Every rewrite is re-verified, deleting a source updates the document, and everything exports with its support state. ### Steps 1. Make every rewrite a new verified version Text and its verification have to move together. Files: `src/lib/rewrite.ts`, `tests/rewrite.test.ts` 2. Cascade source deletion into support state A citation to a deleted source is a lie with a tick next to it. Files: `src/lib/sources.ts` 3. Cap the monthly spend and degrade to manual A rented model with no ceiling is a surprise bill. Files: `src/lib/cost.ts` ### Done when - [ ] A rewrite is a new version and is unrenderable until verified - [ ] Deleting a source downgrades and counts affected sentences - [ ] Export round trips sentences and citations - [ ] The cost ceiling stops generation, not the app ### Watch out - Rewriting a paragraph in place leaves the old sentence rows attached to new text, so the green ticks now belong to sentences nobody verified, which is worse than having no ticks at all. Make every rewrite a new draft version, recompute sentences and citations before rendering, and never show a draft whose verification has not completed. - Deleting a source while its citations stay green turns the document into a claim about evidence that is no longer there. Cascade the deletion to support state, show the count of sentences affected, and offer to re-verify against remaining sources rather than silently downgrading everything.
# Operations · HyperWrite ## Backup The database and the source text directory together, since a passage without its original text cannot be shown in context and a citation to it becomes unopenable. Drafts are regenerable; the sources and the verification history are not. ## Restore Restore both, open a draft and confirm every citation still opens its passage in context, then confirm the support counts match what was exported. A restore where citations resolve but cannot display context is a half restore. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Proportion of sentences unsupported per draft, verifier calls per draft against the cost ceiling, and the count of citations pointing at deleted sources. A falling unsupported rate usually means the threshold moved, not that the writing improved. ## Incident checklist The incident is a published sentence that nothing supported. The stored passage set, the verifier scores and the draft versions are what let you reconstruct what the writer was shown on the day, rather than guessing. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] An unresolvable citation is confirmed to render as unsupported - [ ] No citation row is confirmed to exist without a verifier score - [ ] A rewrite is confirmed unrenderable until re-verified - [ ] Deleting a source is confirmed to downgrade every sentence citing it - [ ] Export is confirmed to carry support state with the text ## Launch constraint Do not market omitted HyperWrite capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Generation and verification. Give it a provider-side spend limit too. MODEL_API_KEY=sk-...redacted # Required. Stored with every draft, because a different model cites differently. MODEL_ID=a text model identifier # Required. The support check. A cheaper model is usually enough for this one job. VERIFIER_MODEL_ID=a smaller text model identifier # Required. Projects, passages, drafts, sentences and citations. DATABASE_PATH=./data/workspace.db # Required. Full original text per source, so a passage can be shown in context. SOURCE_TEXT_DIR=./data/sources # Required. Overlap between paragraph-aligned chunks. Changing it re-chunks new sources only. CHUNK_OVERLAP_CHARS=200 # Required. Verifier score below which a sentence is weak, and lower still is unsupported. SUPPORT_THRESHOLD=0.7 # Required. Generation stops here; reading, hand editing and export continue. MONTHLY_COST_CEILING=10.00
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for HyperWrite because the subscription bundles a refined workflow, proprietary signals, templates, and predictable output quality for a team. The recurring cost buys prompt maintenance, retrieval quality, source handling, provider changes, and editorial QA, not just the visible interface.
xbrowser presence, personalization history, and proprietary agent workflows
xproprietary ranking data
xbrand-trained models
xteam workflows
xlarge template libraries
Don't feel like building it? These folks already made it free.
all 4 free alternatives to HyperWrite →· no votes, no pay-to-list · just what's real
HyperWrite pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| free | $0 | $0 | Limited monthly AI credits; the public page does not publish a numeric credit count. |
| premium | $19.99 | $16 | 250 AI messages/month; 3 personas; unlimited TypeAheads. |
| ultra | $44.99 | $29 | Unlimited AI messages; 10 personas; unlimited TypeAheads. |
free tierlimited monthly AI credits; numeric count not publicly disclosed
billingmonthly + annual
verified 2026-08-12 · source ↗
Vibecode HyperWrite
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal HyperWrite replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does HyperWrite cost?
HyperWrite's pricing is usage-based or varies by plan · Typical Premium monthly rate; usage limits apply..
What do I lose by replacing HyperWrite?
Honestly: browser presence, personalization history, and proprietary agent workflows; proprietary ranking data; brand-trained models; team workflows; large template libraries. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to HyperWrite?
Yes: AnythingLLM (A local-first AI workspace with saved agents, files, memory, and reusable context; the model is yours, so the brand voice can be too.) Page Assist (A browser sidebar that reads the page, rewrites the selection, and saves custom actions; your local model does the thinking.) Writing Tools (Select text anywhere, hit a hotkey, and fix, rewrite, summarize, or obey a custom instruction without opening another tab.) All 4 curated free alternatives are at vibecodeit.com/hyperwrite/alternatives. The prompt is for when you want it exactly your way.