Vibecode Jenni AI
track this build5 phases, 15 steps, beginner friendly0%The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Jenni AI, draft academic-style prose from uploaded sources while keeping citation provenance visible. The hard boundary is citation search, document workflow, and polished editor integrations, plus workflow, data, and model tuning.
You are building a lean indie version of Jenni AI. 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 ===== # Jenni AI · indie build Upload your sources, and the assistant drafts sections that quote and cite only those documents, showing which passage supports each sentence, rejecting any output that cites something you did not supply, and exporting to a document with a bibliography built from the sources you actually used. Estimated effort: **multi-day**. 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 | TypeScript with a desktop shell | your sources and your draft stay on your machine unless you choose otherwise | | Sources | PDF and text extraction with page-level positions | a citation needs a page, and a passage without one cannot be checked | | Retrieval | Chunked passages with search over them | the model sees passages you supplied and nothing else | | Validation | Every claim mapped to a passage id, checked before display | this is the feature, not a safeguard bolted on | | Export | Markdown with citations, and a bibliography generated from used sources | one source of truth for what was cited | ## 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 build and the extraction pipeline. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **The policy that applies to your writing** · free, and it is the first task - Why: Whether assistance of this kind is permitted differs by institution, publisher and assignment, and it is changing. The tool cannot know your rules and the consequence of getting it wrong is yours. - Get it: Read your institution's or publisher's current position on writing assistance, and record what it says and when you read it. - Verify: A policy note is recorded for the project with a date - [ ] **Real source documents for a real piece of work** · free - Why: Extraction, page positions and retrieval are all only meaningful against real papers, which have columns, footnotes and figures that break naive extraction. - Get it: Use the sources for something you are actually writing. Include at least one two column paper and one scanned document. - Verify: At least ten sources import, including a two column paper - [ ] **A model API key, or a local model** · pay per token; a section is typically a few cents - Why: Phase 3 onward. Import and retrieval work without one, which is deliberate. - Get it: A provider key with a spend limit, or Ollama for a local model where nothing leaves the machine. - Verify: A draft generates and passes validation - [ ] **The citation style you must use** · free - Why: Styles differ in what fields they require, and discovering a missing field at export is worse than requiring it at import. - Get it: Confirm the required style for your work and which fields it needs. - Verify: The style is configured and its required fields are enforced at source import ## 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: - Citation search across the literature. - Any claim that output is publishable as written. - A judgement about whether your use is permitted. - Plagiarism detection against a corpus. - citation search, document workflow, and polished editor integrations - 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 Jenni AI, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Jenni AI 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 writing assistant for sourced work, where every claim traces to a document you uploaded to replace Jenni AI. 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 failure mode in this category is a fabricated citation: a plausible author, a plausible title, a plausible year, and no such paper. It happens often enough that a tool which does not prevent it is actively dangerous for the work it is meant to help with. So this drafts only from sources you have supplied, every generated sentence carries the passage it came from, and a draft containing an unsupported claim is rejected rather than shown. Nothing is invented and nothing is cited that you did not provide. The other thing to settle first: whether using assistance like this is permitted for what you are writing. Institutional and publisher policies differ and are changing. Check yours and record it, because the tool cannot know your rules and the consequence is yours. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a desktop shell · your sources and your draft stay on your machine unless you choose otherwise - Sources: PDF and text extraction with page-level positions · a citation needs a page, and a passage without one cannot be checked - Retrieval: Chunked passages with search over them · the model sees passages you supplied and nothing else - Validation: Every claim mapped to a passage id, checked before display · this is the feature, not a safeguard bolted on - Export: Markdown with citations, and a bibliography generated from used sources · one source of truth for what was cited ### Data model (create this before Phase 1) - projects(id, title, style, created_at, policy_note) · policy_note records what you checked about permitted assistance - sources(id, project_id, path, title, authors, year, doi, added_at, extraction_state) - passages(id, source_id, page, ordinal, text) · page-level position is what makes a citation checkable - sections(id, project_id, ordinal, heading, brief) - drafts(id, section_id, at, model, text, claims_json, validated) · claims_json maps each sentence to passage ids - revisions(id, draft_id, at, text, author) · your edits, kept separately from generated text - A sentence with no supporting passage is not written. That is the constraint the whole build enforces. ### Phase 1 · Sources with checkable positions Build: Import PDFs and text. Extract with page numbers preserved. Chunk into passages that keep their page and position. Record the bibliographic details, entered by you rather than guessed, since a wrong reference is as bad as a missing one. Trap: Guessing bibliographic details from a PDF's metadata or its first page is wrong often enough to matter, and a reference list with a wrong year is a real problem in academic work. Ask for the details, prefill from metadata as a suggestion, and mark anything unconfirmed. Done when: A PDF imports with passages carrying correct page numbers, verified by checking three against the document. Bibliographic details are entered or confirmed rather than assumed, and unconfirmed fields are marked. A scanned PDF with no text layer is reported as needing recognition rather than importing empty. Passage text matches the source exactly. Do not build yet: No drafting, citations or export. ### Phase 2 · Retrieval over your sources only Build: Search passages for a section's brief and assemble a context within a token budget. Show the assembled passages before generating. Record which passages were supplied on the draft, so what the model saw is part of the record. Trap: A model asked to write on a topic will draw on its training data whether or not you gave it sources, and the result reads identically. Supplying only your passages narrows what it can say, but the validation in the next phase is what actually enforces it, so build both. Done when: Assembled context is visible before generation and lists the passages included. Context stays within the token budget regardless of source count. The passages supplied are recorded on the draft. A section with no relevant passages reports that rather than generating from nothing. Do not build yet: No generation yet. ### Phase 3 · Drafts where every sentence is attributable Build: Generate with numbered passages and require a passage reference per sentence. Validate that every referenced identifier exists in the supplied set. Reject the whole draft on a violation rather than stripping the offending sentence. Trap: Stripping an unsupported sentence and showing the rest teaches you to trust the output, and the next unsupported sentence will be one the validator missed. Reject the draft, show why, and regenerate, so trust attaches to the process rather than to individual sentences. Done when: A generated draft shows a passage reference for every sentence and each resolves to real text. A crafted draft citing a passage identifier that was not supplied is rejected, tested by feeding one to the validator directly. Clicking a reference shows the passage and its page. A rejected draft explains which sentence failed. Do not build yet: No editing history or export yet. ### Phase 4 · Your writing and its writing, kept apart Build: Store generated text and your revisions separately. Show a view distinguishing them. Track how much of the final text is unchanged generated output. Make that number visible rather than hidden. Trap: Once generated text is edited in place, the boundary disappears and you cannot answer what was written by whom, which is exactly the question an institution or a coauthor may ask. Keep the generated text as a revision and your edits as later revisions, so the history answers it. Done when: A view distinguishes generated text from your edits. The proportion of unchanged generated text is computed and shown per section. Reverting to a previous revision is possible. The history survives export and can be produced if asked. Do not build yet: No export or bibliography yet. ### Phase 5 · Bibliography from what was actually cited Build: Generate the bibliography from cited passages rather than from the source list, in the style you configured. Format from the confirmed bibliographic fields. Report any source uploaded but never cited, and any citation whose source lacks confirmed details. Trap: A reference list built from everything you uploaded includes sources you read and did not use, which is wrong in most styles, and one built from the model's output includes whatever it invented. Build it from the passages actually cited, which is the only list that is both complete and true. Done when: The bibliography lists exactly the sources whose passages were cited. An uploaded but uncited source is reported and excluded. A cited source with unconfirmed details blocks the export until confirmed. Changing the citation style reformats without changing which sources appear. Do not build yet: Export and policy checks come last. ### Phase 6 · Export, policy and privacy Build: Export to Markdown and to a document format with citations and bibliography. Record the policy that applies to your work and whether this use is permitted. State clearly which model was used and whether your sources left the machine. Trap: Uploading unpublished sources or a draft to a hosted model may itself breach a confidentiality or publication agreement, separately from any question about assistance. Say which provider receives what, and offer a local model path for work where nothing may leave the machine. Done when: Export produces a document with citations and a bibliography in the configured style. The policy note is recorded per project with the date checked. The interface states which model is active and whether sources leave the machine. A local model configuration works with networking disabled. Do not build yet: Nothing after this. ### Out of scope (and why) - Citation search across the literature, which is the part of the hosted product that finds sources rather than using yours. - Any claim that output is publishable as written. This drafts from your sources and you remain the author responsible for it. - Judging whether your use is permitted. Policies differ by institution and publisher and are changing; the tool records what you checked and nothing more. - Plagiarism detection against a corpus, which requires a corpus and a service. ### README must contain - That drafting uses only sources you supplied and a draft citing anything else is rejected - That every sentence carries the passage supporting it, with a page you can check - That generated text and your edits are kept as separate revisions - That the bibliography is built from cited passages rather than from uploads - Which model is used, whether sources leave the machine, and where the policy note lives ===== AGENTS.md ===== # Agent instructions · Jenni AI indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with a desktop shell, PDF and text extraction with page-level positions, Chunked passages with search over them, Every claim mapped to a passage id, checked before display, Markdown with citations, and a bibliography generated from used sources. 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 - Guessing bibliographic details from a PDF's metadata or its first page is wrong often enough to matter, and a reference list with a wrong year is a real problem in academic work. Ask for the details, prefill from metadata as a suggestion, and mark anything unconfirmed. - A model asked to write on a topic will draw on its training data whether or not you gave it sources, and the result reads identically. Supplying only your passages narrows what it can say, but the validation in the next phase is what actually enforces it, so build both. - Stripping an unsupported sentence and showing the rest teaches you to trust the output, and the next unsupported sentence will be one the validator missed. Reject the draft, show why, and regenerate, so trust attaches to the process rather than to individual sentences. - Once generated text is edited in place, the boundary disappears and you cannot answer what was written by whom, which is exactly the question an institution or a coauthor may ask. Keep the generated text as a revision and your edits as later revisions, so the history answers it. - A reference list built from everything you uploaded includes sources you read and did not use, which is wrong in most styles, and one built from the model's output includes whatever it invented. Build it from the passages actually cited, which is the only list that is both complete and true. ===== BUILD_PLAN.md ===== # Build plan · Jenni AI Upload your sources, and the assistant drafts sections that quote and cite only those documents, showing which passage supports each sentence, rejecting any output that cites something you did not supply, and exporting to a document with a bibliography built from the sources you actually used. 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 with checkable positions An uploaded document becomes passages that each know their page. ### Steps 1. Extract with page positions Page number preserved per passage. A citation without a page cannot be checked. Files: `src/extract.ts` 2. Confirm bibliographic details Prefilled from metadata as a suggestion, confirmed by you, with unconfirmed fields marked. Files: `src/sources.ts` 3. Detect scanned documents No text layer means no passages. Say so rather than importing an empty source. Files: `src/extract.ts` ### Done when - [ ] Page numbers are correct for three checked passages - [ ] Bibliographic details are confirmed rather than assumed - [ ] A scanned PDF is reported rather than imported empty - [ ] Passage text matches the source exactly ### Watch out - Guessing bibliographic details from a PDF's metadata or its first page is wrong often enough to matter, and a reference list with a wrong year is a real problem in academic work. Ask for the details, prefill from metadata as a suggestion, and mark anything unconfirmed. ## Phase 2 · Retrieval over your sources only The model sees passages from your documents and nothing else. ### Steps 1. Retrieve by section brief Search your passages, rank, take until the budget is reached. Files: `src/retrieve.ts` 2. Show what will be sent The actual passages. You are about to be responsible for whatever is written from them. Files: `src/ui/ContextPreview.tsx` 3. Refuse an empty context No relevant passages means no draft. Generating anyway is where fabrication starts. Files: `src/retrieve.ts` ### Done when - [ ] Assembled passages are visible before generation - [ ] Context stays within the budget at any source count - [ ] Supplied passages are recorded on the draft - [ ] An empty context refuses rather than generating ### Watch out - A model asked to write on a topic will draw on its training data whether or not you gave it sources, and the result reads identically. Supplying only your passages narrows what it can say, but the validation in the next phase is what actually enforces it, so build both. ## Phase 3 · Drafts where every sentence is attributable Each generated sentence names the passage that supports it, and one that cannot is not shown. ### Steps 1. Require per-sentence references Structured output with a passage identifier per sentence, so parsing is not a regular expression. Files: `src/generate.ts` 2. Validate against the supplied set A pure function, unit tested with a crafted bad reference rather than waiting for a model to produce one. Files: `src/validate.ts`, `src/validate.test.ts` 3. Reject wholesale One bad sentence rejects the draft. Partial acceptance is how unsupported claims survive. Files: `src/generate.ts` ### Done when - [ ] Every sentence resolves to a real passage - [ ] A crafted bad reference is rejected by the validator - [ ] Clicking a reference shows the passage and page - [ ] A rejection names the failing sentence ### Watch out - Stripping an unsupported sentence and showing the rest teaches you to trust the output, and the next unsupported sentence will be one the validator missed. Reject the draft, show why, and regenerate, so trust attaches to the process rather than to individual sentences. ## Phase 4 · Your writing and its writing, kept apart You can always see what was generated and what you wrote. ### Steps 1. Keep revisions rather than editing in place Generated text is revision one. Your edits are later revisions. The chain answers the question. Files: `src/revisions.ts` 2. Compute and show the proportion Per section. Visible, because hiding it is the same as not tracking it. Files: `src/attribution.ts` 3. Support reverting To any revision. Editing without a way back makes people cautious rather than productive. Files: `src/revisions.ts` ### Done when - [ ] Generated and edited text are distinguishable - [ ] The unchanged proportion is shown per section - [ ] Reverting to a previous revision works - [ ] The history is exportable ### Watch out - Once generated text is edited in place, the boundary disappears and you cannot answer what was written by whom, which is exactly the question an institution or a coauthor may ask. Keep the generated text as a revision and your edits as later revisions, so the history answers it. ## Phase 5 · Bibliography from what was actually cited The reference list contains every source you used and nothing you did not. ### Steps 1. Build from cited passages Walk the citations, collect their sources. Not the upload list and not the model's output. Files: `src/bibliography.ts` 2. Format from confirmed fields Unconfirmed details block the export rather than producing a plausible wrong reference. Files: `src/styles.ts` 3. Report uncited uploads Useful information rather than an error, and it belongs in the report rather than the bibliography. Files: `src/report.ts` ### Done when - [ ] The bibliography matches cited sources exactly - [ ] An uncited upload is reported and excluded - [ ] An unconfirmed source blocks the export - [ ] A style change reformats without changing membership ### Watch out - A reference list built from everything you uploaded includes sources you read and did not use, which is wrong in most styles, and one built from the model's output includes whatever it invented. Build it from the passages actually cited, which is the only list that is both complete and true. ## Not in this build - Citation search across the literature. - Any claim that output is publishable as written. - A judgement about whether your use is permitted. - Plagiarism detection against a corpus. ## After v1, if you want it - Reference manager import, so sources arrive with confirmed details already - A coverage view showing which sources support which sections - Optical character recognition for scanned sources, which currently import empty ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Sources, drafts and revisions. Your work, in your backup. PROJECT_DIR=~/Writing/Thesis # Required. Which adapter. Local means sources never leave the machine. MODEL_PROVIDER=local # Optional · secret. Only for a hosted provider. Consider whether your sources may be sent to one. MODEL_API_KEY=sk-... # Required. Maximum assembled passages per generation. CONTEXT_TOKEN_BUDGET=8000 # Required. Drives required bibliographic fields and export formatting. CITATION_STYLE=apa # Required. Below this, generation refuses rather than drafting from thin context. MIN_PASSAGES_TO_GENERATE=3
You are building a lean indie version of Jenni AI. 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 ===== # Jenni AI · indie build Upload your sources, and the assistant drafts sections that quote and cite only those documents, showing which passage supports each sentence, rejecting any output that cites something you did not supply, and exporting to a document with a bibliography built from the sources you actually used. Estimated effort: **multi-day**. 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 | TypeScript with a desktop shell | your sources and your draft stay on your machine unless you choose otherwise | | Sources | PDF and text extraction with page-level positions | a citation needs a page, and a passage without one cannot be checked | | Retrieval | Chunked passages with search over them | the model sees passages you supplied and nothing else | | Validation | Every claim mapped to a passage id, checked before display | this is the feature, not a safeguard bolted on | | Export | Markdown with citations, and a bibliography generated from used sources | one source of truth for what was cited | ## 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 build and the extraction pipeline. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **The policy that applies to your writing** · free, and it is the first task - Why: Whether assistance of this kind is permitted differs by institution, publisher and assignment, and it is changing. The tool cannot know your rules and the consequence of getting it wrong is yours. - Get it: Read your institution's or publisher's current position on writing assistance, and record what it says and when you read it. - Verify: A policy note is recorded for the project with a date - [ ] **Real source documents for a real piece of work** · free - Why: Extraction, page positions and retrieval are all only meaningful against real papers, which have columns, footnotes and figures that break naive extraction. - Get it: Use the sources for something you are actually writing. Include at least one two column paper and one scanned document. - Verify: At least ten sources import, including a two column paper - [ ] **A model API key, or a local model** · pay per token; a section is typically a few cents - Why: Phase 3 onward. Import and retrieval work without one, which is deliberate. - Get it: A provider key with a spend limit, or Ollama for a local model where nothing leaves the machine. - Verify: A draft generates and passes validation - [ ] **The citation style you must use** · free - Why: Styles differ in what fields they require, and discovering a missing field at export is worse than requiring it at import. - Get it: Confirm the required style for your work and which fields it needs. - Verify: The style is configured and its required fields are enforced at source import ## 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: - Citation search across the literature. - Any claim that output is publishable as written. - A judgement about whether your use is permitted. - Plagiarism detection against a corpus. - citation search, document workflow, and polished editor integrations - 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 Jenni AI, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Jenni AI 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 writing assistant for sourced work, where every claim traces to a document you uploaded to replace Jenni AI. 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 failure mode in this category is a fabricated citation: a plausible author, a plausible title, a plausible year, and no such paper. It happens often enough that a tool which does not prevent it is actively dangerous for the work it is meant to help with. So this drafts only from sources you have supplied, every generated sentence carries the passage it came from, and a draft containing an unsupported claim is rejected rather than shown. Nothing is invented and nothing is cited that you did not provide. The other thing to settle first: whether using assistance like this is permitted for what you are writing. Institutional and publisher policies differ and are changing. Check yours and record it, because the tool cannot know your rules and the consequence is yours. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a desktop shell · your sources and your draft stay on your machine unless you choose otherwise - Sources: PDF and text extraction with page-level positions · a citation needs a page, and a passage without one cannot be checked - Retrieval: Chunked passages with search over them · the model sees passages you supplied and nothing else - Validation: Every claim mapped to a passage id, checked before display · this is the feature, not a safeguard bolted on - Export: Markdown with citations, and a bibliography generated from used sources · one source of truth for what was cited ### Data model (create this before Phase 1) - projects(id, title, style, created_at, policy_note) · policy_note records what you checked about permitted assistance - sources(id, project_id, path, title, authors, year, doi, added_at, extraction_state) - passages(id, source_id, page, ordinal, text) · page-level position is what makes a citation checkable - sections(id, project_id, ordinal, heading, brief) - drafts(id, section_id, at, model, text, claims_json, validated) · claims_json maps each sentence to passage ids - revisions(id, draft_id, at, text, author) · your edits, kept separately from generated text - A sentence with no supporting passage is not written. That is the constraint the whole build enforces. ### Phase 1 · Sources with checkable positions Build: Import PDFs and text. Extract with page numbers preserved. Chunk into passages that keep their page and position. Record the bibliographic details, entered by you rather than guessed, since a wrong reference is as bad as a missing one. Trap: Guessing bibliographic details from a PDF's metadata or its first page is wrong often enough to matter, and a reference list with a wrong year is a real problem in academic work. Ask for the details, prefill from metadata as a suggestion, and mark anything unconfirmed. Done when: A PDF imports with passages carrying correct page numbers, verified by checking three against the document. Bibliographic details are entered or confirmed rather than assumed, and unconfirmed fields are marked. A scanned PDF with no text layer is reported as needing recognition rather than importing empty. Passage text matches the source exactly. Do not build yet: No drafting, citations or export. ### Phase 2 · Retrieval over your sources only Build: Search passages for a section's brief and assemble a context within a token budget. Show the assembled passages before generating. Record which passages were supplied on the draft, so what the model saw is part of the record. Trap: A model asked to write on a topic will draw on its training data whether or not you gave it sources, and the result reads identically. Supplying only your passages narrows what it can say, but the validation in the next phase is what actually enforces it, so build both. Done when: Assembled context is visible before generation and lists the passages included. Context stays within the token budget regardless of source count. The passages supplied are recorded on the draft. A section with no relevant passages reports that rather than generating from nothing. Do not build yet: No generation yet. ### Phase 3 · Drafts where every sentence is attributable Build: Generate with numbered passages and require a passage reference per sentence. Validate that every referenced identifier exists in the supplied set. Reject the whole draft on a violation rather than stripping the offending sentence. Trap: Stripping an unsupported sentence and showing the rest teaches you to trust the output, and the next unsupported sentence will be one the validator missed. Reject the draft, show why, and regenerate, so trust attaches to the process rather than to individual sentences. Done when: A generated draft shows a passage reference for every sentence and each resolves to real text. A crafted draft citing a passage identifier that was not supplied is rejected, tested by feeding one to the validator directly. Clicking a reference shows the passage and its page. A rejected draft explains which sentence failed. Do not build yet: No editing history or export yet. ### Phase 4 · Your writing and its writing, kept apart Build: Store generated text and your revisions separately. Show a view distinguishing them. Track how much of the final text is unchanged generated output. Make that number visible rather than hidden. Trap: Once generated text is edited in place, the boundary disappears and you cannot answer what was written by whom, which is exactly the question an institution or a coauthor may ask. Keep the generated text as a revision and your edits as later revisions, so the history answers it. Done when: A view distinguishes generated text from your edits. The proportion of unchanged generated text is computed and shown per section. Reverting to a previous revision is possible. The history survives export and can be produced if asked. Do not build yet: No export or bibliography yet. ### Phase 5 · Bibliography from what was actually cited Build: Generate the bibliography from cited passages rather than from the source list, in the style you configured. Format from the confirmed bibliographic fields. Report any source uploaded but never cited, and any citation whose source lacks confirmed details. Trap: A reference list built from everything you uploaded includes sources you read and did not use, which is wrong in most styles, and one built from the model's output includes whatever it invented. Build it from the passages actually cited, which is the only list that is both complete and true. Done when: The bibliography lists exactly the sources whose passages were cited. An uploaded but uncited source is reported and excluded. A cited source with unconfirmed details blocks the export until confirmed. Changing the citation style reformats without changing which sources appear. Do not build yet: Export and policy checks come last. ### Phase 6 · Export, policy and privacy Build: Export to Markdown and to a document format with citations and bibliography. Record the policy that applies to your work and whether this use is permitted. State clearly which model was used and whether your sources left the machine. Trap: Uploading unpublished sources or a draft to a hosted model may itself breach a confidentiality or publication agreement, separately from any question about assistance. Say which provider receives what, and offer a local model path for work where nothing may leave the machine. Done when: Export produces a document with citations and a bibliography in the configured style. The policy note is recorded per project with the date checked. The interface states which model is active and whether sources leave the machine. A local model configuration works with networking disabled. Do not build yet: Nothing after this. ### Out of scope (and why) - Citation search across the literature, which is the part of the hosted product that finds sources rather than using yours. - Any claim that output is publishable as written. This drafts from your sources and you remain the author responsible for it. - Judging whether your use is permitted. Policies differ by institution and publisher and are changing; the tool records what you checked and nothing more. - Plagiarism detection against a corpus, which requires a corpus and a service. ### README must contain - That drafting uses only sources you supplied and a draft citing anything else is rejected - That every sentence carries the passage supporting it, with a page you can check - That generated text and your edits are kept as separate revisions - That the bibliography is built from cited passages rather than from uploads - Which model is used, whether sources leave the machine, and where the policy note lives ===== AGENTS.md ===== # Agent instructions · Jenni AI indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with a desktop shell, PDF and text extraction with page-level positions, Chunked passages with search over them, Every claim mapped to a passage id, checked before display, Markdown with citations, and a bibliography generated from used sources. 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 - Guessing bibliographic details from a PDF's metadata or its first page is wrong often enough to matter, and a reference list with a wrong year is a real problem in academic work. Ask for the details, prefill from metadata as a suggestion, and mark anything unconfirmed. - A model asked to write on a topic will draw on its training data whether or not you gave it sources, and the result reads identically. Supplying only your passages narrows what it can say, but the validation in the next phase is what actually enforces it, so build both. - Stripping an unsupported sentence and showing the rest teaches you to trust the output, and the next unsupported sentence will be one the validator missed. Reject the draft, show why, and regenerate, so trust attaches to the process rather than to individual sentences. - Once generated text is edited in place, the boundary disappears and you cannot answer what was written by whom, which is exactly the question an institution or a coauthor may ask. Keep the generated text as a revision and your edits as later revisions, so the history answers it. - A reference list built from everything you uploaded includes sources you read and did not use, which is wrong in most styles, and one built from the model's output includes whatever it invented. Build it from the passages actually cited, which is the only list that is both complete and true. ===== BUILD_PLAN.md ===== # Build plan · Jenni AI Upload your sources, and the assistant drafts sections that quote and cite only those documents, showing which passage supports each sentence, rejecting any output that cites something you did not supply, and exporting to a document with a bibliography built from the sources you actually used. 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 with checkable positions An uploaded document becomes passages that each know their page. ### Steps 1. Extract with page positions Page number preserved per passage. A citation without a page cannot be checked. Files: `src/extract.ts` 2. Confirm bibliographic details Prefilled from metadata as a suggestion, confirmed by you, with unconfirmed fields marked. Files: `src/sources.ts` 3. Detect scanned documents No text layer means no passages. Say so rather than importing an empty source. Files: `src/extract.ts` ### Done when - [ ] Page numbers are correct for three checked passages - [ ] Bibliographic details are confirmed rather than assumed - [ ] A scanned PDF is reported rather than imported empty - [ ] Passage text matches the source exactly ### Watch out - Guessing bibliographic details from a PDF's metadata or its first page is wrong often enough to matter, and a reference list with a wrong year is a real problem in academic work. Ask for the details, prefill from metadata as a suggestion, and mark anything unconfirmed. ## Phase 2 · Retrieval over your sources only The model sees passages from your documents and nothing else. ### Steps 1. Retrieve by section brief Search your passages, rank, take until the budget is reached. Files: `src/retrieve.ts` 2. Show what will be sent The actual passages. You are about to be responsible for whatever is written from them. Files: `src/ui/ContextPreview.tsx` 3. Refuse an empty context No relevant passages means no draft. Generating anyway is where fabrication starts. Files: `src/retrieve.ts` ### Done when - [ ] Assembled passages are visible before generation - [ ] Context stays within the budget at any source count - [ ] Supplied passages are recorded on the draft - [ ] An empty context refuses rather than generating ### Watch out - A model asked to write on a topic will draw on its training data whether or not you gave it sources, and the result reads identically. Supplying only your passages narrows what it can say, but the validation in the next phase is what actually enforces it, so build both. ## Phase 3 · Drafts where every sentence is attributable Each generated sentence names the passage that supports it, and one that cannot is not shown. ### Steps 1. Require per-sentence references Structured output with a passage identifier per sentence, so parsing is not a regular expression. Files: `src/generate.ts` 2. Validate against the supplied set A pure function, unit tested with a crafted bad reference rather than waiting for a model to produce one. Files: `src/validate.ts`, `src/validate.test.ts` 3. Reject wholesale One bad sentence rejects the draft. Partial acceptance is how unsupported claims survive. Files: `src/generate.ts` ### Done when - [ ] Every sentence resolves to a real passage - [ ] A crafted bad reference is rejected by the validator - [ ] Clicking a reference shows the passage and page - [ ] A rejection names the failing sentence ### Watch out - Stripping an unsupported sentence and showing the rest teaches you to trust the output, and the next unsupported sentence will be one the validator missed. Reject the draft, show why, and regenerate, so trust attaches to the process rather than to individual sentences. ## Phase 4 · Your writing and its writing, kept apart You can always see what was generated and what you wrote. ### Steps 1. Keep revisions rather than editing in place Generated text is revision one. Your edits are later revisions. The chain answers the question. Files: `src/revisions.ts` 2. Compute and show the proportion Per section. Visible, because hiding it is the same as not tracking it. Files: `src/attribution.ts` 3. Support reverting To any revision. Editing without a way back makes people cautious rather than productive. Files: `src/revisions.ts` ### Done when - [ ] Generated and edited text are distinguishable - [ ] The unchanged proportion is shown per section - [ ] Reverting to a previous revision works - [ ] The history is exportable ### Watch out - Once generated text is edited in place, the boundary disappears and you cannot answer what was written by whom, which is exactly the question an institution or a coauthor may ask. Keep the generated text as a revision and your edits as later revisions, so the history answers it. ## Phase 5 · Bibliography from what was actually cited The reference list contains every source you used and nothing you did not. ### Steps 1. Build from cited passages Walk the citations, collect their sources. Not the upload list and not the model's output. Files: `src/bibliography.ts` 2. Format from confirmed fields Unconfirmed details block the export rather than producing a plausible wrong reference. Files: `src/styles.ts` 3. Report uncited uploads Useful information rather than an error, and it belongs in the report rather than the bibliography. Files: `src/report.ts` ### Done when - [ ] The bibliography matches cited sources exactly - [ ] An uncited upload is reported and excluded - [ ] An unconfirmed source blocks the export - [ ] A style change reformats without changing membership ### Watch out - A reference list built from everything you uploaded includes sources you read and did not use, which is wrong in most styles, and one built from the model's output includes whatever it invented. Build it from the passages actually cited, which is the only list that is both complete and true. ## Not in this build - Citation search across the literature. - Any claim that output is publishable as written. - A judgement about whether your use is permitted. - Plagiarism detection against a corpus. ## After v1, if you want it - Reference manager import, so sources arrive with confirmed details already - A coverage view showing which sources support which sections - Optical character recognition for scanned sources, which currently import empty ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Sources, drafts and revisions. Your work, in your backup. PROJECT_DIR=~/Writing/Thesis # Required. Which adapter. Local means sources never leave the machine. MODEL_PROVIDER=local # Optional · secret. Only for a hosted provider. Consider whether your sources may be sent to one. MODEL_API_KEY=sk-... # Required. Maximum assembled passages per generation. CONTEXT_TOKEN_BUDGET=8000 # Required. Drives required bibliographic fields and export formatting. CITATION_STYLE=apa # Required. Below this, generation refuses rather than drafting from thin context. MIN_PASSAGES_TO_GENERATE=3
You are building a production product version of Jenni AI. 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 ===== # Jenni AI · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Jenni AI, draft academic-style prose from uploaded sources while keeping citation provenance visible. The hard boundary is citation search, document workflow, and polished editor integrations, plus workflow, data, and model tuning. ## Product outcome A drafting tool that cannot cite what you did not give it, shows the passage behind every sentence, keeps your writing distinguishable from its own, and produces a bibliography containing exactly what was used. ## 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 - Citation search across the literature. - Any claim that output is publishable as written. - A judgement about whether your use is permitted. - Plagiarism detection against a corpus. - citation search, document workflow, and polished editor integrations - proprietary ranking data - brand-trained models - team workflows - large template libraries ## Success criteria - The validator rejects a crafted unsupported reference in a unit test - Page numbers are verified correct on a two column paper - The bibliography is confirmed to match cited sources exactly - Generated and edited text are confirmed distinguishable - A local configuration is confirmed to send nothing ===== BRIEF.md ===== # Build brief · Jenni AI 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 writing assistant for sourced work, where every claim traces to a document you uploaded to replace Jenni AI. 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 failure mode in this category is a fabricated citation: a plausible author, a plausible title, a plausible year, and no such paper. It happens often enough that a tool which does not prevent it is actively dangerous for the work it is meant to help with. So this drafts only from sources you have supplied, every generated sentence carries the passage it came from, and a draft containing an unsupported claim is rejected rather than shown. Nothing is invented and nothing is cited that you did not provide. The other thing to settle first: whether using assistance like this is permitted for what you are writing. Institutional and publisher policies differ and are changing. Check yours and record it, because the tool cannot know your rules and the consequence is yours. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a desktop shell · your sources and your draft stay on your machine unless you choose otherwise - Sources: PDF and text extraction with page-level positions · a citation needs a page, and a passage without one cannot be checked - Retrieval: Chunked passages with search over them · the model sees passages you supplied and nothing else - Validation: Every claim mapped to a passage id, checked before display · this is the feature, not a safeguard bolted on - Export: Markdown with citations, and a bibliography generated from used sources · one source of truth for what was cited ### Data model (create this before Phase 1) - projects(id, title, style, created_at, policy_note) · policy_note records what you checked about permitted assistance - sources(id, project_id, path, title, authors, year, doi, added_at, extraction_state) - passages(id, source_id, page, ordinal, text) · page-level position is what makes a citation checkable - sections(id, project_id, ordinal, heading, brief) - drafts(id, section_id, at, model, text, claims_json, validated) · claims_json maps each sentence to passage ids - revisions(id, draft_id, at, text, author) · your edits, kept separately from generated text - A sentence with no supporting passage is not written. That is the constraint the whole build enforces. ### Phase 1 · Sources with checkable positions Build: Import PDFs and text. Extract with page numbers preserved. Chunk into passages that keep their page and position. Record the bibliographic details, entered by you rather than guessed, since a wrong reference is as bad as a missing one. Trap: Guessing bibliographic details from a PDF's metadata or its first page is wrong often enough to matter, and a reference list with a wrong year is a real problem in academic work. Ask for the details, prefill from metadata as a suggestion, and mark anything unconfirmed. Done when: A PDF imports with passages carrying correct page numbers, verified by checking three against the document. Bibliographic details are entered or confirmed rather than assumed, and unconfirmed fields are marked. A scanned PDF with no text layer is reported as needing recognition rather than importing empty. Passage text matches the source exactly. Do not build yet: No drafting, citations or export. ### Phase 2 · Retrieval over your sources only Build: Search passages for a section's brief and assemble a context within a token budget. Show the assembled passages before generating. Record which passages were supplied on the draft, so what the model saw is part of the record. Trap: A model asked to write on a topic will draw on its training data whether or not you gave it sources, and the result reads identically. Supplying only your passages narrows what it can say, but the validation in the next phase is what actually enforces it, so build both. Done when: Assembled context is visible before generation and lists the passages included. Context stays within the token budget regardless of source count. The passages supplied are recorded on the draft. A section with no relevant passages reports that rather than generating from nothing. Do not build yet: No generation yet. ### Phase 3 · Drafts where every sentence is attributable Build: Generate with numbered passages and require a passage reference per sentence. Validate that every referenced identifier exists in the supplied set. Reject the whole draft on a violation rather than stripping the offending sentence. Trap: Stripping an unsupported sentence and showing the rest teaches you to trust the output, and the next unsupported sentence will be one the validator missed. Reject the draft, show why, and regenerate, so trust attaches to the process rather than to individual sentences. Done when: A generated draft shows a passage reference for every sentence and each resolves to real text. A crafted draft citing a passage identifier that was not supplied is rejected, tested by feeding one to the validator directly. Clicking a reference shows the passage and its page. A rejected draft explains which sentence failed. Do not build yet: No editing history or export yet. ### Phase 4 · Your writing and its writing, kept apart Build: Store generated text and your revisions separately. Show a view distinguishing them. Track how much of the final text is unchanged generated output. Make that number visible rather than hidden. Trap: Once generated text is edited in place, the boundary disappears and you cannot answer what was written by whom, which is exactly the question an institution or a coauthor may ask. Keep the generated text as a revision and your edits as later revisions, so the history answers it. Done when: A view distinguishes generated text from your edits. The proportion of unchanged generated text is computed and shown per section. Reverting to a previous revision is possible. The history survives export and can be produced if asked. Do not build yet: No export or bibliography yet. ### Phase 5 · Bibliography from what was actually cited Build: Generate the bibliography from cited passages rather than from the source list, in the style you configured. Format from the confirmed bibliographic fields. Report any source uploaded but never cited, and any citation whose source lacks confirmed details. Trap: A reference list built from everything you uploaded includes sources you read and did not use, which is wrong in most styles, and one built from the model's output includes whatever it invented. Build it from the passages actually cited, which is the only list that is both complete and true. Done when: The bibliography lists exactly the sources whose passages were cited. An uploaded but uncited source is reported and excluded. A cited source with unconfirmed details blocks the export until confirmed. Changing the citation style reformats without changing which sources appear. Do not build yet: Export and policy checks come last. ### Phase 6 · Export, policy and privacy Build: Export to Markdown and to a document format with citations and bibliography. Record the policy that applies to your work and whether this use is permitted. State clearly which model was used and whether your sources left the machine. Trap: Uploading unpublished sources or a draft to a hosted model may itself breach a confidentiality or publication agreement, separately from any question about assistance. Say which provider receives what, and offer a local model path for work where nothing may leave the machine. Done when: Export produces a document with citations and a bibliography in the configured style. The policy note is recorded per project with the date checked. The interface states which model is active and whether sources leave the machine. A local model configuration works with networking disabled. Do not build yet: Nothing after this. ### Out of scope (and why) - Citation search across the literature, which is the part of the hosted product that finds sources rather than using yours. - Any claim that output is publishable as written. This drafts from your sources and you remain the author responsible for it. - Judging whether your use is permitted. Policies differ by institution and publisher and are changing; the tool records what you checked and nothing more. - Plagiarism detection against a corpus, which requires a corpus and a service. ### README must contain - That drafting uses only sources you supplied and a draft citing anything else is rejected - That every sentence carries the passage supporting it, with a page you can check - That generated text and your edits are kept as separate revisions - That the bibliography is built from cited passages rather than from uploads - Which model is used, whether sources leave the machine, and where the policy note lives ===== ARCHITECTURE.md ===== # Architecture · Jenni AI ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with a desktop shell | your sources and your draft stay on your machine unless you choose otherwise | | Sources | PDF and text extraction with page-level positions | a citation needs a page, and a passage without one cannot be checked | | Retrieval | Chunked passages with search over them | the model sees passages you supplied and nothing else | | Validation | Every claim mapped to a passage id, checked before display | this is the feature, not a safeguard bolted on | | Export | Markdown with citations, and a bibliography generated from used sources | one source of truth for what was cited | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Sources | Extraction, page positions and confirmed details. | Any extractor preserving page positions. Confirmed rather than guessed details is a correctness property. | | Retrieval | Choosing passages and disclosing them. | Any search. Refusing to generate on an empty context must survive. | | Validation | Rejecting unsupported claims. | Never remove this. It is the difference between a research tool and a fabrication engine. | | Revisions | Keeping generated and written text apart. | Any history model, provided the boundary remains answerable. | | Bibliography | Building the reference list from citations. | Styles are formatting; membership from cited passages is the correctness property. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `PROJECT_DIR` · required · Sources, drafts and revisions. Your work, in your backup. - `MODEL_PROVIDER` · required · Which adapter. Local means sources never leave the machine. - `MODEL_API_KEY` · optional, secret · Only for a hosted provider. Consider whether your sources may be sent to one. - `CONTEXT_TOKEN_BUDGET` · required · Maximum assembled passages per generation. - `CITATION_STYLE` · required · Drives required bibliographic fields and export formatting. - `MIN_PASSAGES_TO_GENERATE` · required · Below this, generation refuses rather than drafting from thin context. ## 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 · Jenni AI product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: TypeScript with a desktop shell, PDF and text extraction with page-level positions, Chunked passages with search over them, Every claim mapped to a passage id, checked before display, Markdown with citations, and a bibliography generated from used sources. - 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 - Guessing bibliographic details from a PDF's metadata or its first page is wrong often enough to matter, and a reference list with a wrong year is a real problem in academic work. Ask for the details, prefill from metadata as a suggestion, and mark anything unconfirmed. - A model asked to write on a topic will draw on its training data whether or not you gave it sources, and the result reads identically. Supplying only your passages narrows what it can say, but the validation in the next phase is what actually enforces it, so build both. - Stripping an unsupported sentence and showing the rest teaches you to trust the output, and the next unsupported sentence will be one the validator missed. Reject the draft, show why, and regenerate, so trust attaches to the process rather than to individual sentences. - Once generated text is edited in place, the boundary disappears and you cannot answer what was written by whom, which is exactly the question an institution or a coauthor may ask. Keep the generated text as a revision and your edits as later revisions, so the history answers it. - A reference list built from everything you uploaded includes sources you read and did not use, which is wrong in most styles, and one built from the model's output includes whatever it invented. Build it from the passages actually cited, which is the only list that is both complete and true. - Uploading unpublished sources or a draft to a hosted model may itself breach a confidentiality or publication agreement, separately from any question about assistance. Say which provider receives what, and offer a local model path for work where nothing may leave the machine. ===== MILESTONES.md ===== # Delivery milestones · Jenni AI Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Sources with checkable positions An uploaded document becomes passages that each know their page. ### Steps 1. Extract with page positions Page number preserved per passage. A citation without a page cannot be checked. Files: `src/extract.ts` 2. Confirm bibliographic details Prefilled from metadata as a suggestion, confirmed by you, with unconfirmed fields marked. Files: `src/sources.ts` 3. Detect scanned documents No text layer means no passages. Say so rather than importing an empty source. Files: `src/extract.ts` ### Done when - [ ] Page numbers are correct for three checked passages - [ ] Bibliographic details are confirmed rather than assumed - [ ] A scanned PDF is reported rather than imported empty - [ ] Passage text matches the source exactly ### Watch out - Guessing bibliographic details from a PDF's metadata or its first page is wrong often enough to matter, and a reference list with a wrong year is a real problem in academic work. Ask for the details, prefill from metadata as a suggestion, and mark anything unconfirmed. ## M2 · Retrieval over your sources only The model sees passages from your documents and nothing else. ### Steps 1. Retrieve by section brief Search your passages, rank, take until the budget is reached. Files: `src/retrieve.ts` 2. Show what will be sent The actual passages. You are about to be responsible for whatever is written from them. Files: `src/ui/ContextPreview.tsx` 3. Refuse an empty context No relevant passages means no draft. Generating anyway is where fabrication starts. Files: `src/retrieve.ts` ### Done when - [ ] Assembled passages are visible before generation - [ ] Context stays within the budget at any source count - [ ] Supplied passages are recorded on the draft - [ ] An empty context refuses rather than generating ### Watch out - A model asked to write on a topic will draw on its training data whether or not you gave it sources, and the result reads identically. Supplying only your passages narrows what it can say, but the validation in the next phase is what actually enforces it, so build both. ## M3 · Drafts where every sentence is attributable Each generated sentence names the passage that supports it, and one that cannot is not shown. ### Steps 1. Require per-sentence references Structured output with a passage identifier per sentence, so parsing is not a regular expression. Files: `src/generate.ts` 2. Validate against the supplied set A pure function, unit tested with a crafted bad reference rather than waiting for a model to produce one. Files: `src/validate.ts`, `src/validate.test.ts` 3. Reject wholesale One bad sentence rejects the draft. Partial acceptance is how unsupported claims survive. Files: `src/generate.ts` ### Done when - [ ] Every sentence resolves to a real passage - [ ] A crafted bad reference is rejected by the validator - [ ] Clicking a reference shows the passage and page - [ ] A rejection names the failing sentence ### Watch out - Stripping an unsupported sentence and showing the rest teaches you to trust the output, and the next unsupported sentence will be one the validator missed. Reject the draft, show why, and regenerate, so trust attaches to the process rather than to individual sentences. ## M4 · Your writing and its writing, kept apart You can always see what was generated and what you wrote. ### Steps 1. Keep revisions rather than editing in place Generated text is revision one. Your edits are later revisions. The chain answers the question. Files: `src/revisions.ts` 2. Compute and show the proportion Per section. Visible, because hiding it is the same as not tracking it. Files: `src/attribution.ts` 3. Support reverting To any revision. Editing without a way back makes people cautious rather than productive. Files: `src/revisions.ts` ### Done when - [ ] Generated and edited text are distinguishable - [ ] The unchanged proportion is shown per section - [ ] Reverting to a previous revision works - [ ] The history is exportable ### Watch out - Once generated text is edited in place, the boundary disappears and you cannot answer what was written by whom, which is exactly the question an institution or a coauthor may ask. Keep the generated text as a revision and your edits as later revisions, so the history answers it. ## M5 · Bibliography from what was actually cited The reference list contains every source you used and nothing you did not. ### Steps 1. Build from cited passages Walk the citations, collect their sources. Not the upload list and not the model's output. Files: `src/bibliography.ts` 2. Format from confirmed fields Unconfirmed details block the export rather than producing a plausible wrong reference. Files: `src/styles.ts` 3. Report uncited uploads Useful information rather than an error, and it belongs in the report rather than the bibliography. Files: `src/report.ts` ### Done when - [ ] The bibliography matches cited sources exactly - [ ] An uncited upload is reported and excluded - [ ] An unconfirmed source blocks the export - [ ] A style change reformats without changing membership ### Watch out - A reference list built from everything you uploaded includes sources you read and did not use, which is wrong in most styles, and one built from the model's output includes whatever it invented. Build it from the passages actually cited, which is the only list that is both complete and true. ## M6 · Export, policy and privacy (production only) The document leaves cleanly, and you have settled what you are allowed to do with it. ### Steps 1. Export with citations intact Markdown and a document format. Citations and bibliography from the same source of truth. Files: `src/export.ts` 2. Record the policy you checked Per project, with the date. The tool cannot know your rules; recording that you checked is the honest step. Files: `src/projects.ts` 3. Make the data path visible Which model, and whether sources leave the machine. Shown in the interface, not only documented. Files: `src/ui/ModelBadge.tsx` ### Done when - [ ] Export produces citations and a bibliography in the configured style - [ ] The policy note is recorded with a date - [ ] The active model and data path are visible - [ ] A local configuration works with networking disabled ### Watch out - Uploading unpublished sources or a draft to a hosted model may itself breach a confidentiality or publication agreement, separately from any question about assistance. Say which provider receives what, and offer a local model path for work where nothing may leave the machine. ===== OPERATIONS.md ===== # Operations · Jenni AI ## Backup Sources, drafts and revisions are the work. Back them up, and note that the revision history is what answers questions about authorship later, so it is not disposable. ## Restore Restore the project directory and open a section. The revision chain should be intact, which is the check that matters more than the current text. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Validation rejection rate during drafting. A rising rate means retrieval is returning weak passages and the model is filling gaps, which is the early signal of unusable output. ## Incident checklist The failure that matters is an unsupported claim reaching a document you submit. The validator is the control, and if one gets through, the fix belongs in the validator rather than in reading more carefully. 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 - [ ] The validator rejects a crafted unsupported reference in a unit test - [ ] Page numbers are verified correct on a two column paper - [ ] The bibliography is confirmed to match cited sources exactly - [ ] Generated and edited text are confirmed distinguishable - [ ] A local configuration is confirmed to send nothing ## Launch constraint Do not market omitted Jenni AI 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. Sources, drafts and revisions. Your work, in your backup. PROJECT_DIR=~/Writing/Thesis # Required. Which adapter. Local means sources never leave the machine. MODEL_PROVIDER=local # Optional · secret. Only for a hosted provider. Consider whether your sources may be sent to one. MODEL_API_KEY=sk-... # Required. Maximum assembled passages per generation. CONTEXT_TOKEN_BUDGET=8000 # Required. Drives required bibliographic fields and export formatting. CITATION_STYLE=apa # Required. Below this, generation refuses rather than drafting from thin context. MIN_PASSAGES_TO_GENERATE=3
# Jenni AI · indie build Upload your sources, and the assistant drafts sections that quote and cite only those documents, showing which passage supports each sentence, rejecting any output that cites something you did not supply, and exporting to a document with a bibliography built from the sources you actually used. Estimated effort: **multi-day**. 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 | TypeScript with a desktop shell | your sources and your draft stay on your machine unless you choose otherwise | | Sources | PDF and text extraction with page-level positions | a citation needs a page, and a passage without one cannot be checked | | Retrieval | Chunked passages with search over them | the model sees passages you supplied and nothing else | | Validation | Every claim mapped to a passage id, checked before display | this is the feature, not a safeguard bolted on | | Export | Markdown with citations, and a bibliography generated from used sources | one source of truth for what was cited | ## 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 build and the extraction pipeline. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **The policy that applies to your writing** · free, and it is the first task - Why: Whether assistance of this kind is permitted differs by institution, publisher and assignment, and it is changing. The tool cannot know your rules and the consequence of getting it wrong is yours. - Get it: Read your institution's or publisher's current position on writing assistance, and record what it says and when you read it. - Verify: A policy note is recorded for the project with a date - [ ] **Real source documents for a real piece of work** · free - Why: Extraction, page positions and retrieval are all only meaningful against real papers, which have columns, footnotes and figures that break naive extraction. - Get it: Use the sources for something you are actually writing. Include at least one two column paper and one scanned document. - Verify: At least ten sources import, including a two column paper - [ ] **A model API key, or a local model** · pay per token; a section is typically a few cents - Why: Phase 3 onward. Import and retrieval work without one, which is deliberate. - Get it: A provider key with a spend limit, or Ollama for a local model where nothing leaves the machine. - Verify: A draft generates and passes validation - [ ] **The citation style you must use** · free - Why: Styles differ in what fields they require, and discovering a missing field at export is worse than requiring it at import. - Get it: Confirm the required style for your work and which fields it needs. - Verify: The style is configured and its required fields are enforced at source import ## 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: - Citation search across the literature. - Any claim that output is publishable as written. - A judgement about whether your use is permitted. - Plagiarism detection against a corpus. - citation search, document workflow, and polished editor integrations - 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 Jenni AI, and the README should say so rather than pretend.
# Build brief · Jenni AI 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 writing assistant for sourced work, where every claim traces to a document you uploaded to replace Jenni AI. 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 failure mode in this category is a fabricated citation: a plausible author, a plausible title, a plausible year, and no such paper. It happens often enough that a tool which does not prevent it is actively dangerous for the work it is meant to help with. So this drafts only from sources you have supplied, every generated sentence carries the passage it came from, and a draft containing an unsupported claim is rejected rather than shown. Nothing is invented and nothing is cited that you did not provide. The other thing to settle first: whether using assistance like this is permitted for what you are writing. Institutional and publisher policies differ and are changing. Check yours and record it, because the tool cannot know your rules and the consequence is yours. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a desktop shell · your sources and your draft stay on your machine unless you choose otherwise - Sources: PDF and text extraction with page-level positions · a citation needs a page, and a passage without one cannot be checked - Retrieval: Chunked passages with search over them · the model sees passages you supplied and nothing else - Validation: Every claim mapped to a passage id, checked before display · this is the feature, not a safeguard bolted on - Export: Markdown with citations, and a bibliography generated from used sources · one source of truth for what was cited ### Data model (create this before Phase 1) - projects(id, title, style, created_at, policy_note) · policy_note records what you checked about permitted assistance - sources(id, project_id, path, title, authors, year, doi, added_at, extraction_state) - passages(id, source_id, page, ordinal, text) · page-level position is what makes a citation checkable - sections(id, project_id, ordinal, heading, brief) - drafts(id, section_id, at, model, text, claims_json, validated) · claims_json maps each sentence to passage ids - revisions(id, draft_id, at, text, author) · your edits, kept separately from generated text - A sentence with no supporting passage is not written. That is the constraint the whole build enforces. ### Phase 1 · Sources with checkable positions Build: Import PDFs and text. Extract with page numbers preserved. Chunk into passages that keep their page and position. Record the bibliographic details, entered by you rather than guessed, since a wrong reference is as bad as a missing one. Trap: Guessing bibliographic details from a PDF's metadata or its first page is wrong often enough to matter, and a reference list with a wrong year is a real problem in academic work. Ask for the details, prefill from metadata as a suggestion, and mark anything unconfirmed. Done when: A PDF imports with passages carrying correct page numbers, verified by checking three against the document. Bibliographic details are entered or confirmed rather than assumed, and unconfirmed fields are marked. A scanned PDF with no text layer is reported as needing recognition rather than importing empty. Passage text matches the source exactly. Do not build yet: No drafting, citations or export. ### Phase 2 · Retrieval over your sources only Build: Search passages for a section's brief and assemble a context within a token budget. Show the assembled passages before generating. Record which passages were supplied on the draft, so what the model saw is part of the record. Trap: A model asked to write on a topic will draw on its training data whether or not you gave it sources, and the result reads identically. Supplying only your passages narrows what it can say, but the validation in the next phase is what actually enforces it, so build both. Done when: Assembled context is visible before generation and lists the passages included. Context stays within the token budget regardless of source count. The passages supplied are recorded on the draft. A section with no relevant passages reports that rather than generating from nothing. Do not build yet: No generation yet. ### Phase 3 · Drafts where every sentence is attributable Build: Generate with numbered passages and require a passage reference per sentence. Validate that every referenced identifier exists in the supplied set. Reject the whole draft on a violation rather than stripping the offending sentence. Trap: Stripping an unsupported sentence and showing the rest teaches you to trust the output, and the next unsupported sentence will be one the validator missed. Reject the draft, show why, and regenerate, so trust attaches to the process rather than to individual sentences. Done when: A generated draft shows a passage reference for every sentence and each resolves to real text. A crafted draft citing a passage identifier that was not supplied is rejected, tested by feeding one to the validator directly. Clicking a reference shows the passage and its page. A rejected draft explains which sentence failed. Do not build yet: No editing history or export yet. ### Phase 4 · Your writing and its writing, kept apart Build: Store generated text and your revisions separately. Show a view distinguishing them. Track how much of the final text is unchanged generated output. Make that number visible rather than hidden. Trap: Once generated text is edited in place, the boundary disappears and you cannot answer what was written by whom, which is exactly the question an institution or a coauthor may ask. Keep the generated text as a revision and your edits as later revisions, so the history answers it. Done when: A view distinguishes generated text from your edits. The proportion of unchanged generated text is computed and shown per section. Reverting to a previous revision is possible. The history survives export and can be produced if asked. Do not build yet: No export or bibliography yet. ### Phase 5 · Bibliography from what was actually cited Build: Generate the bibliography from cited passages rather than from the source list, in the style you configured. Format from the confirmed bibliographic fields. Report any source uploaded but never cited, and any citation whose source lacks confirmed details. Trap: A reference list built from everything you uploaded includes sources you read and did not use, which is wrong in most styles, and one built from the model's output includes whatever it invented. Build it from the passages actually cited, which is the only list that is both complete and true. Done when: The bibliography lists exactly the sources whose passages were cited. An uploaded but uncited source is reported and excluded. A cited source with unconfirmed details blocks the export until confirmed. Changing the citation style reformats without changing which sources appear. Do not build yet: Export and policy checks come last. ### Phase 6 · Export, policy and privacy Build: Export to Markdown and to a document format with citations and bibliography. Record the policy that applies to your work and whether this use is permitted. State clearly which model was used and whether your sources left the machine. Trap: Uploading unpublished sources or a draft to a hosted model may itself breach a confidentiality or publication agreement, separately from any question about assistance. Say which provider receives what, and offer a local model path for work where nothing may leave the machine. Done when: Export produces a document with citations and a bibliography in the configured style. The policy note is recorded per project with the date checked. The interface states which model is active and whether sources leave the machine. A local model configuration works with networking disabled. Do not build yet: Nothing after this. ### Out of scope (and why) - Citation search across the literature, which is the part of the hosted product that finds sources rather than using yours. - Any claim that output is publishable as written. This drafts from your sources and you remain the author responsible for it. - Judging whether your use is permitted. Policies differ by institution and publisher and are changing; the tool records what you checked and nothing more. - Plagiarism detection against a corpus, which requires a corpus and a service. ### README must contain - That drafting uses only sources you supplied and a draft citing anything else is rejected - That every sentence carries the passage supporting it, with a page you can check - That generated text and your edits are kept as separate revisions - That the bibliography is built from cited passages rather than from uploads - Which model is used, whether sources leave the machine, and where the policy note lives
# Agent instructions · Jenni AI indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with a desktop shell, PDF and text extraction with page-level positions, Chunked passages with search over them, Every claim mapped to a passage id, checked before display, Markdown with citations, and a bibliography generated from used sources. 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 - Guessing bibliographic details from a PDF's metadata or its first page is wrong often enough to matter, and a reference list with a wrong year is a real problem in academic work. Ask for the details, prefill from metadata as a suggestion, and mark anything unconfirmed. - A model asked to write on a topic will draw on its training data whether or not you gave it sources, and the result reads identically. Supplying only your passages narrows what it can say, but the validation in the next phase is what actually enforces it, so build both. - Stripping an unsupported sentence and showing the rest teaches you to trust the output, and the next unsupported sentence will be one the validator missed. Reject the draft, show why, and regenerate, so trust attaches to the process rather than to individual sentences. - Once generated text is edited in place, the boundary disappears and you cannot answer what was written by whom, which is exactly the question an institution or a coauthor may ask. Keep the generated text as a revision and your edits as later revisions, so the history answers it. - A reference list built from everything you uploaded includes sources you read and did not use, which is wrong in most styles, and one built from the model's output includes whatever it invented. Build it from the passages actually cited, which is the only list that is both complete and true.
# Build plan · Jenni AI Upload your sources, and the assistant drafts sections that quote and cite only those documents, showing which passage supports each sentence, rejecting any output that cites something you did not supply, and exporting to a document with a bibliography built from the sources you actually used. 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 with checkable positions An uploaded document becomes passages that each know their page. ### Steps 1. Extract with page positions Page number preserved per passage. A citation without a page cannot be checked. Files: `src/extract.ts` 2. Confirm bibliographic details Prefilled from metadata as a suggestion, confirmed by you, with unconfirmed fields marked. Files: `src/sources.ts` 3. Detect scanned documents No text layer means no passages. Say so rather than importing an empty source. Files: `src/extract.ts` ### Done when - [ ] Page numbers are correct for three checked passages - [ ] Bibliographic details are confirmed rather than assumed - [ ] A scanned PDF is reported rather than imported empty - [ ] Passage text matches the source exactly ### Watch out - Guessing bibliographic details from a PDF's metadata or its first page is wrong often enough to matter, and a reference list with a wrong year is a real problem in academic work. Ask for the details, prefill from metadata as a suggestion, and mark anything unconfirmed. ## Phase 2 · Retrieval over your sources only The model sees passages from your documents and nothing else. ### Steps 1. Retrieve by section brief Search your passages, rank, take until the budget is reached. Files: `src/retrieve.ts` 2. Show what will be sent The actual passages. You are about to be responsible for whatever is written from them. Files: `src/ui/ContextPreview.tsx` 3. Refuse an empty context No relevant passages means no draft. Generating anyway is where fabrication starts. Files: `src/retrieve.ts` ### Done when - [ ] Assembled passages are visible before generation - [ ] Context stays within the budget at any source count - [ ] Supplied passages are recorded on the draft - [ ] An empty context refuses rather than generating ### Watch out - A model asked to write on a topic will draw on its training data whether or not you gave it sources, and the result reads identically. Supplying only your passages narrows what it can say, but the validation in the next phase is what actually enforces it, so build both. ## Phase 3 · Drafts where every sentence is attributable Each generated sentence names the passage that supports it, and one that cannot is not shown. ### Steps 1. Require per-sentence references Structured output with a passage identifier per sentence, so parsing is not a regular expression. Files: `src/generate.ts` 2. Validate against the supplied set A pure function, unit tested with a crafted bad reference rather than waiting for a model to produce one. Files: `src/validate.ts`, `src/validate.test.ts` 3. Reject wholesale One bad sentence rejects the draft. Partial acceptance is how unsupported claims survive. Files: `src/generate.ts` ### Done when - [ ] Every sentence resolves to a real passage - [ ] A crafted bad reference is rejected by the validator - [ ] Clicking a reference shows the passage and page - [ ] A rejection names the failing sentence ### Watch out - Stripping an unsupported sentence and showing the rest teaches you to trust the output, and the next unsupported sentence will be one the validator missed. Reject the draft, show why, and regenerate, so trust attaches to the process rather than to individual sentences. ## Phase 4 · Your writing and its writing, kept apart You can always see what was generated and what you wrote. ### Steps 1. Keep revisions rather than editing in place Generated text is revision one. Your edits are later revisions. The chain answers the question. Files: `src/revisions.ts` 2. Compute and show the proportion Per section. Visible, because hiding it is the same as not tracking it. Files: `src/attribution.ts` 3. Support reverting To any revision. Editing without a way back makes people cautious rather than productive. Files: `src/revisions.ts` ### Done when - [ ] Generated and edited text are distinguishable - [ ] The unchanged proportion is shown per section - [ ] Reverting to a previous revision works - [ ] The history is exportable ### Watch out - Once generated text is edited in place, the boundary disappears and you cannot answer what was written by whom, which is exactly the question an institution or a coauthor may ask. Keep the generated text as a revision and your edits as later revisions, so the history answers it. ## Phase 5 · Bibliography from what was actually cited The reference list contains every source you used and nothing you did not. ### Steps 1. Build from cited passages Walk the citations, collect their sources. Not the upload list and not the model's output. Files: `src/bibliography.ts` 2. Format from confirmed fields Unconfirmed details block the export rather than producing a plausible wrong reference. Files: `src/styles.ts` 3. Report uncited uploads Useful information rather than an error, and it belongs in the report rather than the bibliography. Files: `src/report.ts` ### Done when - [ ] The bibliography matches cited sources exactly - [ ] An uncited upload is reported and excluded - [ ] An unconfirmed source blocks the export - [ ] A style change reformats without changing membership ### Watch out - A reference list built from everything you uploaded includes sources you read and did not use, which is wrong in most styles, and one built from the model's output includes whatever it invented. Build it from the passages actually cited, which is the only list that is both complete and true. ## Not in this build - Citation search across the literature. - Any claim that output is publishable as written. - A judgement about whether your use is permitted. - Plagiarism detection against a corpus. ## After v1, if you want it - Reference manager import, so sources arrive with confirmed details already - A coverage view showing which sources support which sections - Optical character recognition for scanned sources, which currently import empty
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Sources, drafts and revisions. Your work, in your backup. PROJECT_DIR=~/Writing/Thesis # Required. Which adapter. Local means sources never leave the machine. MODEL_PROVIDER=local # Optional · secret. Only for a hosted provider. Consider whether your sources may be sent to one. MODEL_API_KEY=sk-... # Required. Maximum assembled passages per generation. CONTEXT_TOKEN_BUDGET=8000 # Required. Drives required bibliographic fields and export formatting. CITATION_STYLE=apa # Required. Below this, generation refuses rather than drafting from thin context. MIN_PASSAGES_TO_GENERATE=3
# Jenni AI · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Jenni AI, draft academic-style prose from uploaded sources while keeping citation provenance visible. The hard boundary is citation search, document workflow, and polished editor integrations, plus workflow, data, and model tuning. ## Product outcome A drafting tool that cannot cite what you did not give it, shows the passage behind every sentence, keeps your writing distinguishable from its own, and produces a bibliography containing exactly what was used. ## 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 - Citation search across the literature. - Any claim that output is publishable as written. - A judgement about whether your use is permitted. - Plagiarism detection against a corpus. - citation search, document workflow, and polished editor integrations - proprietary ranking data - brand-trained models - team workflows - large template libraries ## Success criteria - The validator rejects a crafted unsupported reference in a unit test - Page numbers are verified correct on a two column paper - The bibliography is confirmed to match cited sources exactly - Generated and edited text are confirmed distinguishable - A local configuration is confirmed to send nothing
# Build brief · Jenni AI 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 writing assistant for sourced work, where every claim traces to a document you uploaded to replace Jenni AI. 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 failure mode in this category is a fabricated citation: a plausible author, a plausible title, a plausible year, and no such paper. It happens often enough that a tool which does not prevent it is actively dangerous for the work it is meant to help with. So this drafts only from sources you have supplied, every generated sentence carries the passage it came from, and a draft containing an unsupported claim is rejected rather than shown. Nothing is invented and nothing is cited that you did not provide. The other thing to settle first: whether using assistance like this is permitted for what you are writing. Institutional and publisher policies differ and are changing. Check yours and record it, because the tool cannot know your rules and the consequence is yours. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a desktop shell · your sources and your draft stay on your machine unless you choose otherwise - Sources: PDF and text extraction with page-level positions · a citation needs a page, and a passage without one cannot be checked - Retrieval: Chunked passages with search over them · the model sees passages you supplied and nothing else - Validation: Every claim mapped to a passage id, checked before display · this is the feature, not a safeguard bolted on - Export: Markdown with citations, and a bibliography generated from used sources · one source of truth for what was cited ### Data model (create this before Phase 1) - projects(id, title, style, created_at, policy_note) · policy_note records what you checked about permitted assistance - sources(id, project_id, path, title, authors, year, doi, added_at, extraction_state) - passages(id, source_id, page, ordinal, text) · page-level position is what makes a citation checkable - sections(id, project_id, ordinal, heading, brief) - drafts(id, section_id, at, model, text, claims_json, validated) · claims_json maps each sentence to passage ids - revisions(id, draft_id, at, text, author) · your edits, kept separately from generated text - A sentence with no supporting passage is not written. That is the constraint the whole build enforces. ### Phase 1 · Sources with checkable positions Build: Import PDFs and text. Extract with page numbers preserved. Chunk into passages that keep their page and position. Record the bibliographic details, entered by you rather than guessed, since a wrong reference is as bad as a missing one. Trap: Guessing bibliographic details from a PDF's metadata or its first page is wrong often enough to matter, and a reference list with a wrong year is a real problem in academic work. Ask for the details, prefill from metadata as a suggestion, and mark anything unconfirmed. Done when: A PDF imports with passages carrying correct page numbers, verified by checking three against the document. Bibliographic details are entered or confirmed rather than assumed, and unconfirmed fields are marked. A scanned PDF with no text layer is reported as needing recognition rather than importing empty. Passage text matches the source exactly. Do not build yet: No drafting, citations or export. ### Phase 2 · Retrieval over your sources only Build: Search passages for a section's brief and assemble a context within a token budget. Show the assembled passages before generating. Record which passages were supplied on the draft, so what the model saw is part of the record. Trap: A model asked to write on a topic will draw on its training data whether or not you gave it sources, and the result reads identically. Supplying only your passages narrows what it can say, but the validation in the next phase is what actually enforces it, so build both. Done when: Assembled context is visible before generation and lists the passages included. Context stays within the token budget regardless of source count. The passages supplied are recorded on the draft. A section with no relevant passages reports that rather than generating from nothing. Do not build yet: No generation yet. ### Phase 3 · Drafts where every sentence is attributable Build: Generate with numbered passages and require a passage reference per sentence. Validate that every referenced identifier exists in the supplied set. Reject the whole draft on a violation rather than stripping the offending sentence. Trap: Stripping an unsupported sentence and showing the rest teaches you to trust the output, and the next unsupported sentence will be one the validator missed. Reject the draft, show why, and regenerate, so trust attaches to the process rather than to individual sentences. Done when: A generated draft shows a passage reference for every sentence and each resolves to real text. A crafted draft citing a passage identifier that was not supplied is rejected, tested by feeding one to the validator directly. Clicking a reference shows the passage and its page. A rejected draft explains which sentence failed. Do not build yet: No editing history or export yet. ### Phase 4 · Your writing and its writing, kept apart Build: Store generated text and your revisions separately. Show a view distinguishing them. Track how much of the final text is unchanged generated output. Make that number visible rather than hidden. Trap: Once generated text is edited in place, the boundary disappears and you cannot answer what was written by whom, which is exactly the question an institution or a coauthor may ask. Keep the generated text as a revision and your edits as later revisions, so the history answers it. Done when: A view distinguishes generated text from your edits. The proportion of unchanged generated text is computed and shown per section. Reverting to a previous revision is possible. The history survives export and can be produced if asked. Do not build yet: No export or bibliography yet. ### Phase 5 · Bibliography from what was actually cited Build: Generate the bibliography from cited passages rather than from the source list, in the style you configured. Format from the confirmed bibliographic fields. Report any source uploaded but never cited, and any citation whose source lacks confirmed details. Trap: A reference list built from everything you uploaded includes sources you read and did not use, which is wrong in most styles, and one built from the model's output includes whatever it invented. Build it from the passages actually cited, which is the only list that is both complete and true. Done when: The bibliography lists exactly the sources whose passages were cited. An uploaded but uncited source is reported and excluded. A cited source with unconfirmed details blocks the export until confirmed. Changing the citation style reformats without changing which sources appear. Do not build yet: Export and policy checks come last. ### Phase 6 · Export, policy and privacy Build: Export to Markdown and to a document format with citations and bibliography. Record the policy that applies to your work and whether this use is permitted. State clearly which model was used and whether your sources left the machine. Trap: Uploading unpublished sources or a draft to a hosted model may itself breach a confidentiality or publication agreement, separately from any question about assistance. Say which provider receives what, and offer a local model path for work where nothing may leave the machine. Done when: Export produces a document with citations and a bibliography in the configured style. The policy note is recorded per project with the date checked. The interface states which model is active and whether sources leave the machine. A local model configuration works with networking disabled. Do not build yet: Nothing after this. ### Out of scope (and why) - Citation search across the literature, which is the part of the hosted product that finds sources rather than using yours. - Any claim that output is publishable as written. This drafts from your sources and you remain the author responsible for it. - Judging whether your use is permitted. Policies differ by institution and publisher and are changing; the tool records what you checked and nothing more. - Plagiarism detection against a corpus, which requires a corpus and a service. ### README must contain - That drafting uses only sources you supplied and a draft citing anything else is rejected - That every sentence carries the passage supporting it, with a page you can check - That generated text and your edits are kept as separate revisions - That the bibliography is built from cited passages rather than from uploads - Which model is used, whether sources leave the machine, and where the policy note lives
# Architecture · Jenni AI ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with a desktop shell | your sources and your draft stay on your machine unless you choose otherwise | | Sources | PDF and text extraction with page-level positions | a citation needs a page, and a passage without one cannot be checked | | Retrieval | Chunked passages with search over them | the model sees passages you supplied and nothing else | | Validation | Every claim mapped to a passage id, checked before display | this is the feature, not a safeguard bolted on | | Export | Markdown with citations, and a bibliography generated from used sources | one source of truth for what was cited | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Sources | Extraction, page positions and confirmed details. | Any extractor preserving page positions. Confirmed rather than guessed details is a correctness property. | | Retrieval | Choosing passages and disclosing them. | Any search. Refusing to generate on an empty context must survive. | | Validation | Rejecting unsupported claims. | Never remove this. It is the difference between a research tool and a fabrication engine. | | Revisions | Keeping generated and written text apart. | Any history model, provided the boundary remains answerable. | | Bibliography | Building the reference list from citations. | Styles are formatting; membership from cited passages is the correctness property. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `PROJECT_DIR` · required · Sources, drafts and revisions. Your work, in your backup. - `MODEL_PROVIDER` · required · Which adapter. Local means sources never leave the machine. - `MODEL_API_KEY` · optional, secret · Only for a hosted provider. Consider whether your sources may be sent to one. - `CONTEXT_TOKEN_BUDGET` · required · Maximum assembled passages per generation. - `CITATION_STYLE` · required · Drives required bibliographic fields and export formatting. - `MIN_PASSAGES_TO_GENERATE` · required · Below this, generation refuses rather than drafting from thin context. ## 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 · Jenni AI product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: TypeScript with a desktop shell, PDF and text extraction with page-level positions, Chunked passages with search over them, Every claim mapped to a passage id, checked before display, Markdown with citations, and a bibliography generated from used sources. - 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 - Guessing bibliographic details from a PDF's metadata or its first page is wrong often enough to matter, and a reference list with a wrong year is a real problem in academic work. Ask for the details, prefill from metadata as a suggestion, and mark anything unconfirmed. - A model asked to write on a topic will draw on its training data whether or not you gave it sources, and the result reads identically. Supplying only your passages narrows what it can say, but the validation in the next phase is what actually enforces it, so build both. - Stripping an unsupported sentence and showing the rest teaches you to trust the output, and the next unsupported sentence will be one the validator missed. Reject the draft, show why, and regenerate, so trust attaches to the process rather than to individual sentences. - Once generated text is edited in place, the boundary disappears and you cannot answer what was written by whom, which is exactly the question an institution or a coauthor may ask. Keep the generated text as a revision and your edits as later revisions, so the history answers it. - A reference list built from everything you uploaded includes sources you read and did not use, which is wrong in most styles, and one built from the model's output includes whatever it invented. Build it from the passages actually cited, which is the only list that is both complete and true. - Uploading unpublished sources or a draft to a hosted model may itself breach a confidentiality or publication agreement, separately from any question about assistance. Say which provider receives what, and offer a local model path for work where nothing may leave the machine.
# Delivery milestones · Jenni AI Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Sources with checkable positions An uploaded document becomes passages that each know their page. ### Steps 1. Extract with page positions Page number preserved per passage. A citation without a page cannot be checked. Files: `src/extract.ts` 2. Confirm bibliographic details Prefilled from metadata as a suggestion, confirmed by you, with unconfirmed fields marked. Files: `src/sources.ts` 3. Detect scanned documents No text layer means no passages. Say so rather than importing an empty source. Files: `src/extract.ts` ### Done when - [ ] Page numbers are correct for three checked passages - [ ] Bibliographic details are confirmed rather than assumed - [ ] A scanned PDF is reported rather than imported empty - [ ] Passage text matches the source exactly ### Watch out - Guessing bibliographic details from a PDF's metadata or its first page is wrong often enough to matter, and a reference list with a wrong year is a real problem in academic work. Ask for the details, prefill from metadata as a suggestion, and mark anything unconfirmed. ## M2 · Retrieval over your sources only The model sees passages from your documents and nothing else. ### Steps 1. Retrieve by section brief Search your passages, rank, take until the budget is reached. Files: `src/retrieve.ts` 2. Show what will be sent The actual passages. You are about to be responsible for whatever is written from them. Files: `src/ui/ContextPreview.tsx` 3. Refuse an empty context No relevant passages means no draft. Generating anyway is where fabrication starts. Files: `src/retrieve.ts` ### Done when - [ ] Assembled passages are visible before generation - [ ] Context stays within the budget at any source count - [ ] Supplied passages are recorded on the draft - [ ] An empty context refuses rather than generating ### Watch out - A model asked to write on a topic will draw on its training data whether or not you gave it sources, and the result reads identically. Supplying only your passages narrows what it can say, but the validation in the next phase is what actually enforces it, so build both. ## M3 · Drafts where every sentence is attributable Each generated sentence names the passage that supports it, and one that cannot is not shown. ### Steps 1. Require per-sentence references Structured output with a passage identifier per sentence, so parsing is not a regular expression. Files: `src/generate.ts` 2. Validate against the supplied set A pure function, unit tested with a crafted bad reference rather than waiting for a model to produce one. Files: `src/validate.ts`, `src/validate.test.ts` 3. Reject wholesale One bad sentence rejects the draft. Partial acceptance is how unsupported claims survive. Files: `src/generate.ts` ### Done when - [ ] Every sentence resolves to a real passage - [ ] A crafted bad reference is rejected by the validator - [ ] Clicking a reference shows the passage and page - [ ] A rejection names the failing sentence ### Watch out - Stripping an unsupported sentence and showing the rest teaches you to trust the output, and the next unsupported sentence will be one the validator missed. Reject the draft, show why, and regenerate, so trust attaches to the process rather than to individual sentences. ## M4 · Your writing and its writing, kept apart You can always see what was generated and what you wrote. ### Steps 1. Keep revisions rather than editing in place Generated text is revision one. Your edits are later revisions. The chain answers the question. Files: `src/revisions.ts` 2. Compute and show the proportion Per section. Visible, because hiding it is the same as not tracking it. Files: `src/attribution.ts` 3. Support reverting To any revision. Editing without a way back makes people cautious rather than productive. Files: `src/revisions.ts` ### Done when - [ ] Generated and edited text are distinguishable - [ ] The unchanged proportion is shown per section - [ ] Reverting to a previous revision works - [ ] The history is exportable ### Watch out - Once generated text is edited in place, the boundary disappears and you cannot answer what was written by whom, which is exactly the question an institution or a coauthor may ask. Keep the generated text as a revision and your edits as later revisions, so the history answers it. ## M5 · Bibliography from what was actually cited The reference list contains every source you used and nothing you did not. ### Steps 1. Build from cited passages Walk the citations, collect their sources. Not the upload list and not the model's output. Files: `src/bibliography.ts` 2. Format from confirmed fields Unconfirmed details block the export rather than producing a plausible wrong reference. Files: `src/styles.ts` 3. Report uncited uploads Useful information rather than an error, and it belongs in the report rather than the bibliography. Files: `src/report.ts` ### Done when - [ ] The bibliography matches cited sources exactly - [ ] An uncited upload is reported and excluded - [ ] An unconfirmed source blocks the export - [ ] A style change reformats without changing membership ### Watch out - A reference list built from everything you uploaded includes sources you read and did not use, which is wrong in most styles, and one built from the model's output includes whatever it invented. Build it from the passages actually cited, which is the only list that is both complete and true. ## M6 · Export, policy and privacy (production only) The document leaves cleanly, and you have settled what you are allowed to do with it. ### Steps 1. Export with citations intact Markdown and a document format. Citations and bibliography from the same source of truth. Files: `src/export.ts` 2. Record the policy you checked Per project, with the date. The tool cannot know your rules; recording that you checked is the honest step. Files: `src/projects.ts` 3. Make the data path visible Which model, and whether sources leave the machine. Shown in the interface, not only documented. Files: `src/ui/ModelBadge.tsx` ### Done when - [ ] Export produces citations and a bibliography in the configured style - [ ] The policy note is recorded with a date - [ ] The active model and data path are visible - [ ] A local configuration works with networking disabled ### Watch out - Uploading unpublished sources or a draft to a hosted model may itself breach a confidentiality or publication agreement, separately from any question about assistance. Say which provider receives what, and offer a local model path for work where nothing may leave the machine.
# Operations · Jenni AI ## Backup Sources, drafts and revisions are the work. Back them up, and note that the revision history is what answers questions about authorship later, so it is not disposable. ## Restore Restore the project directory and open a section. The revision chain should be intact, which is the check that matters more than the current text. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Validation rejection rate during drafting. A rising rate means retrieval is returning weak passages and the model is filling gaps, which is the early signal of unusable output. ## Incident checklist The failure that matters is an unsupported claim reaching a document you submit. The validator is the control, and if one gets through, the fix belongs in the validator rather than in reading more carefully. 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 - [ ] The validator rejects a crafted unsupported reference in a unit test - [ ] Page numbers are verified correct on a two column paper - [ ] The bibliography is confirmed to match cited sources exactly - [ ] Generated and edited text are confirmed distinguishable - [ ] A local configuration is confirmed to send nothing ## Launch constraint Do not market omitted Jenni AI 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. Sources, drafts and revisions. Your work, in your backup. PROJECT_DIR=~/Writing/Thesis # Required. Which adapter. Local means sources never leave the machine. MODEL_PROVIDER=local # Optional · secret. Only for a hosted provider. Consider whether your sources may be sent to one. MODEL_API_KEY=sk-... # Required. Maximum assembled passages per generation. CONTEXT_TOKEN_BUDGET=8000 # Required. Drives required bibliographic fields and export formatting. CITATION_STYLE=apa # Required. Below this, generation refuses rather than drafting from thin context. MIN_PASSAGES_TO_GENERATE=3
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for Jenni AI 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.
xcitation search, document workflow, and polished editor integrations
xproprietary ranking data
xbrand-trained models
xteam workflows
xlarge template libraries
Don't feel like building it? These folks already made it free.
all 3 free alternatives to Jenni AI →· no votes, no pay-to-list · just what's real
Jenni AI pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| free | $0 | $0 | 10 autocompletes/day; 10 PDF uploads; 5 chat messages; 3 AI edits on the pricing card (comparison table says 5); 3 reviews; 25 MB and 150 pages/PDF. |
| plus | $12 | — | 5,000 autocompletes/month; 500 AI edits; 500 chats; 10 reviews; unlimited PDFs; 100 MB and 500 pages/PDF. |
| pro | $29 | — | Unlimited autocompletes, AI edits, chats, reviews, and PDFs; 100 MB and 1,000 pages/PDF. |
free tier10 autocompletes/day; 10 PDF uploads; 5 chats; 3 AI edits on the card (5 in comparison table); 3 reviews; 25 MB/150 pages per PDF
billingmonthly only, no annual plan
verified 2026-08-12 · source ↗
Vibecode Jenni AI
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal Jenni AI replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does Jenni AI cost?
Jenni AI costs about $12/month (Plus, checked 2026-08-12), which is $144 per year. That's what you save by replacing it with one prompt.
What do I lose by replacing Jenni AI?
Honestly: citation search, document workflow, and polished editor integrations; 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 Jenni AI?
Yes: AnythingLLM (A local AI workspace that keeps sources beside the draft, cites uploaded material, and turns recurring prompts into reusable agents.) Gemini Notebook (Upload the sources, ask for the draft, and click the citations when the machine gets confident.) Open WebUI (A self-hosted AI workbench with saved prompts, knowledge, web search, and multi-model comparison; powerful, not especially house-trained.) All 3 curated free alternatives are at vibecodeit.com/jenni-ai/alternatives. The prompt is for when you want it exactly your way.