Vibecode Huntr
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 Huntr, track job applications, contacts, tasks, documents, and tailored materials in one workspace. The hard boundary is browser capture, ai writing, resume builder, contact enrichment, and sync, plus data, distribution, and coaching.
You are building a lean indie version of Huntr. 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 ===== # Huntr · indie build A local workspace tracking applications through stages, storing the job description alongside each one, holding a master record of your real experience, and generating tailored documents where every bullet is linked to an entry in that record so nothing is invented. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite, running locally | a job search is private and there is no reason for it to live on someone else's server | | Storage | SQLite, with documents as files on disk | applications are structured; your resume is a document you also open elsewhere | | Documents | Markdown as the master format, rendered to PDF | one source of truth that diffs well, with the format employers want generated from it | | Tailoring | Retrieval over your evidence record, with a model constrained to it | the model rearranges and rephrases what you did; it must never add | | Rendering | A typesetting step producing consistent PDFs | a resume that reflows differently on each generation is a resume you cannot trust to look right | ## 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 rendering pipeline. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **An honest account of your own experience** · free, and it is the most valuable hour in this build - Why: Phase 3 is the foundation of everything after it, and it cannot be generated. Roles, dates, projects and any metrics you could actually defend. - Get it: Set aside an hour and write it from memory and from records, not from an existing resume. Where you want to claim a number, find the number. - Verify: Every role and project you would mention has an entry with real dates - [ ] **A model API key, or a local model** (optional) · pay per token; tailoring one document is a fraction of a cent - Why: Phase 4 only. Tracking, capture and the evidence record all work with no model at all. - Get it: A provider key with a spend limit set, or Ollama with a local model if you would rather your job search stayed on the machine. - Verify: A tailoring run completes and its provenance is recorded - [ ] **A PDF rendering toolchain** · free - Why: Employers want PDFs and applicant tracking systems parse them, so the output has to be real text rather than an image. - Get it: A headless browser print path or a typesetting tool. Whichever you choose, confirm the output text is selectable. - Verify: Generated PDF text can be selected and copied - [ ] **Five real job descriptions** · free - Why: Tailoring and retrieval cannot be judged against invented postings, and real ones contain the vague requirements that break naive keyword matching. - Get it: Save five postings for roles you would actually apply for. - Verify: Five descriptions are stored as fixtures ## 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: - Browser capture from job boards. - Recruiter contact enrichment. - Job board distribution and one-click applying. - Any claim to improve your chances. - browser capture, AI writing, resume builder, contact enrichment, and sync - proprietary recruiter data - job-board distribution - human coaching - automated application networks If one of those is essential to you, that is the reason to keep paying for Huntr, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Huntr 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 job application tracker where every claim on a tailored document traces back to something you actually did to replace Huntr. 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. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite, running locally · a job search is private and there is no reason for it to live on someone else's server - Storage: SQLite, with documents as files on disk · applications are structured; your resume is a document you also open elsewhere - Documents: Markdown as the master format, rendered to PDF · one source of truth that diffs well, with the format employers want generated from it - Tailoring: Retrieval over your evidence record, with a model constrained to it · the model rearranges and rephrases what you did; it must never add - Rendering: A typesetting step producing consistent PDFs · a resume that reflows differently on each generation is a resume you cannot trust to look right ### Data model (create this before Phase 1) - applications(id, company, role, url, source, applied_at, stage, salary_range, location, notes) - stages(application_id, stage, entered_at) · the history, so you can see how long each stage actually takes - descriptions(application_id, text, captured_at) · the job description as it was when you applied, because postings disappear - evidence(id, kind, title, organisation, started_at, ended_at, text, metrics_json) · your master record of what you actually did - documents(id, application_id, kind, path, generated_at, evidence_ids_json) · which evidence entries produced this document - contacts(id, application_id, name, role, notes), tasks(id, application_id, due_at, text, done_at) - The evidence identifiers on a document are the audit trail: every claim traces to something you recorded about yourself. ### Phase 1 · Applications and their stages Build: Create applications with company, role, link and source. Move them through stages and record when each transition happened. A board view by stage and a list view sorted by how long since the last movement. Trap: Storing only the current stage loses the timing, which is the information that actually helps: knowing that applications sit in one stage for three weeks changes when you follow up. Store transitions as rows, not a single field. Done when: An application moves through stages and its history shows each transition with a date. The list view surfaces applications with no movement for a chosen number of days. Deleting an application removes its stages, tasks and documents together. Stage timing statistics are computed from real transitions. Do not build yet: No documents, no tailoring, no capture. ### Phase 2 · Capturing the posting before it disappears Build: Store the job description text with the application. Capture it by paste or from a URL where the site permits fetching. Keep the captured date, and detect when a posting has changed or been removed if you re-check it. Trap: Job postings are taken down constantly, often before an interview, and preparing without the description you applied against is a real disadvantage. Capture the text at application time rather than storing a link, because the link will break exactly when you need it. Done when: A pasted description is stored with its capture date. Re-checking a removed posting marks it as gone rather than losing the stored text. The stored description is searchable across all applications. A description captured from a URL preserves paragraph structure rather than collapsing into one block. Do not build yet: No evidence record or tailoring yet. ### Phase 3 · An evidence record of what you actually did Build: A master record of roles, projects, education and achievements, each with dates, the organisation, a description in your words, and any real metrics. This is the only thing the tailoring step is allowed to draw from. Trap: Skipping the evidence record and tailoring directly from a previous resume means the model is rephrasing a rephrasing, and small exaggerations compound across applications until you cannot remember what you actually claimed. Write the record once from what you did, and treat every document as derived from it. Done when: Entries cover every role and project you would mention, with dates and metrics where they exist. A metric field is either a real number you can support or is empty, never an estimate. Searching the record returns relevant entries for a given job description's keywords. The record exports as Markdown you can read outside the application. Do not build yet: No tailoring yet. ### Phase 4 · Tailoring that cannot invent Build: Given a job description, retrieve relevant evidence entries and generate a tailored document constrained to them. Record which entries produced each bullet. Validate that every generated claim maps to an entry, and reject the output if one does not. Trap: A model asked to tailor a resume will add a plausible achievement you never had, and it will be the most impressive line on the page. Validate every claim against the evidence record and reject the generation rather than trusting an instruction, because the failure mode here is discovered in an interview. Done when: A generated document lists the evidence entries behind each section. A deliberately crafted output containing an unsupported claim fails validation, tested by feeding one to the validator directly. Every bullet in a real generated document traces to an entry you can point at. Regenerating for the same description produces a document drawing on the same entries. Do not build yet: No rendering or export yet. ### Phase 5 · Documents that look right every time Build: Render Markdown to PDF with a fixed template. Handle the one-page constraint explicitly: report when content overflows rather than silently shrinking type to illegibility. Keep the Markdown source alongside every PDF. Trap: Auto-shrinking type to fit a page produces a resume nobody can read at seven point type, and the tool that did it never says so. Report the overflow and let the writer cut something, because the decision about what to remove is theirs. Done when: A generated PDF is consistent across regenerations of the same content. Content exceeding one page produces a warning naming the overflow rather than shrinking the type below a floor. The Markdown source is stored next to the PDF. The PDF's text is selectable and machine readable rather than an image. Do not build yet: No follow-up tracking yet. ### Phase 6 · Follow-ups, privacy and export Build: Tasks with due dates per application, and reminders for stale ones. Export everything as JSON and Markdown. Be explicit about whether any text reaches a model provider, and give a way to delete an application and everything about it. Trap: A job search is unusually sensitive: it reveals that you are leaving, and where you applied. If tailoring uses a hosted model, your evidence record and the job descriptions leave your machine. Say so plainly and offer a local model path, because this is a case where the privacy preference is often strong. Done when: A task with a due date produces a reminder. Export produces JSON and Markdown covering applications, evidence and documents. Deleting an application removes its description, documents, tasks and contacts, verified by searching for its company name afterwards. The interface states which model is used and whether text leaves the machine. Do not build yet: Nothing after this. ### Out of scope (and why) - Browser capture from job boards, which is an extension per browser and is fragile against every site redesign. - Contact enrichment from a recruiter database, which is licensed data rather than a feature. - Job board distribution and one-click applying, which are integrations with each board. - Any claim to improve your chances. This organises what you did and keeps you honest about it, which is a smaller and more defensible promise. ### README must contain - That the evidence record is the only source tailoring may draw from - That every generated claim is validated against it and an unsupported one is rejected - That job descriptions are captured as text because postings disappear - That overflow is reported rather than fixed by shrinking type - Whether tailoring uses a hosted model, and therefore whether your job search leaves the machine ===== AGENTS.md ===== # Agent instructions · Huntr indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with Vite, running locally, SQLite, with documents as files on disk, Markdown as the master format, rendered to PDF, Retrieval over your evidence record, with a model constrained to it, A typesetting step producing consistent PDFs. 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 - Storing only the current stage loses the timing, which is the information that actually helps: knowing that applications sit in one stage for three weeks changes when you follow up. Store transitions as rows, not a single field. - Job postings are taken down constantly, often before an interview, and preparing without the description you applied against is a real disadvantage. Capture the text at application time rather than storing a link, because the link will break exactly when you need it. - Skipping the evidence record and tailoring directly from a previous resume means the model is rephrasing a rephrasing, and small exaggerations compound across applications until you cannot remember what you actually claimed. Write the record once from what you did, and treat every document as derived from it. - A model asked to tailor a resume will add a plausible achievement you never had, and it will be the most impressive line on the page. Validate every claim against the evidence record and reject the generation rather than trusting an instruction, because the failure mode here is discovered in an interview. - Auto-shrinking type to fit a page produces a resume nobody can read at seven point type, and the tool that did it never says so. Report the overflow and let the writer cut something, because the decision about what to remove is theirs. ===== BUILD_PLAN.md ===== # Build plan · Huntr A local workspace tracking applications through stages, storing the job description alongside each one, holding a master record of your real experience, and generating tailored documents where every bullet is linked to an entry in that record so nothing is invented. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Applications and their stages You can see every application and how long it has been sitting. ### Steps 1. Model stages as transitions One row per transition. The current stage is derived from the latest row. Files: `src/applications.ts` 2. Build board and list views Board for a sense of the pipeline, list sorted by staleness for deciding what to do today. Files: `src/views/` 3. Compute timing from history Average days per stage, and which applications have gone quiet. This is the number that changes behaviour. Files: `src/stats.ts` ### Done when - [ ] Stage history shows each transition with a date - [ ] The list surfaces applications with no recent movement - [ ] Deleting an application cascades to its related rows - [ ] Stage timing is computed from real transitions ### Watch out - Storing only the current stage loses the timing, which is the information that actually helps: knowing that applications sit in one stage for three weeks changes when you follow up. Store transitions as rows, not a single field. ## Phase 2 · Capturing the posting before it disappears You still have the job description when they call you three weeks later. ### Steps 1. Store the text, not the link Paste is the reliable path. A link is stored too, but as a convenience rather than the record. Files: `src/descriptions.ts` 2. Fetch where permitted Check the site's terms before automating a fetch. Many job boards forbid it, and paste always works. Files: `src/capture.ts` 3. Detect removal without losing anything Re-checking marks a posting gone and keeps the captured text. The text is the point. Files: `src/capture.ts` ### Done when - [ ] A pasted description is stored with its date - [ ] A removed posting is marked gone and its text survives - [ ] Descriptions are searchable across applications - [ ] Captured text preserves paragraph structure ### Watch out - Job postings are taken down constantly, often before an interview, and preparing without the description you applied against is a real disadvantage. Capture the text at application time rather than storing a link, because the link will break exactly when you need it. ## Phase 3 · An evidence record of what you actually did Every claim you might make exists in one place, in your own words, before any tailoring. ### Steps 1. Write the record from what you did Not from an existing resume. Your own words, with dates and real numbers only where you can support them. Files: `src/evidence.ts` 2. Keep metrics honest A metric is a number you could defend in an interview. If you cannot, leave it empty; an empty field is better than one you have to walk back. Files: `src/evidence.ts` 3. Make it searchable Full text over entries, since tailoring starts with finding relevant ones. Files: `src/search.ts` ### Done when - [ ] Every role and project you would mention has an entry - [ ] Metrics are real numbers or empty, never estimates - [ ] Search returns relevant entries for a description's keywords - [ ] The record exports as readable Markdown ### Watch out - Skipping the evidence record and tailoring directly from a previous resume means the model is rephrasing a rephrasing, and small exaggerations compound across applications until you cannot remember what you actually claimed. Write the record once from what you did, and treat every document as derived from it. ## Phase 4 · Tailoring that cannot invent A tailored document is a rearrangement of things you did, and you can prove it. ### Steps 1. Retrieve before generating Search the evidence record against the description, and pass only the retrieved entries. The model never sees your whole history at once. Files: `src/tailor.ts` 2. Validate every claim A checking step mapping each generated bullet to an entry. Unit test it with a crafted unsupported claim rather than waiting for a model to produce one. Files: `src/validate.ts`, `src/validate.test.ts` 3. Record the provenance Evidence identifiers on the document row, shown in the interface next to each section. Files: `src/documents.ts` ### Done when - [ ] A generated document shows the evidence behind each section - [ ] A crafted unsupported claim is rejected by the validator - [ ] Every bullet traces to an evidence entry - [ ] The validator is unit tested independently of any model ### Watch out - A model asked to tailor a resume will add a plausible achievement you never had, and it will be the most impressive line on the page. Validate every claim against the evidence record and reject the generation rather than trusting an instruction, because the failure mode here is discovered in an interview. ## Phase 5 · Documents that look right every time A generated PDF is consistent, readable and fits the page. ### Steps 1. Render with a fixed template One template, deterministic output. Compare two renders of the same content byte for byte. Files: `src/render.ts` 2. Warn on overflow rather than shrinking A minimum type size below which the render reports a problem instead of continuing. Files: `src/render.ts` 3. Keep the source with the output The Markdown next to the PDF. Regeneration and editing both need it. Files: `src/documents.ts` ### Done when - [ ] Two renders of the same content are identical - [ ] Overflow produces a warning rather than illegible type - [ ] The Markdown source is stored with every PDF - [ ] PDF text is selectable rather than an image ### Watch out - Auto-shrinking type to fit a page produces a resume nobody can read at seven point type, and the tool that did it never says so. Report the overflow and let the writer cut something, because the decision about what to remove is theirs. ## Not in this build - Browser capture from job boards. - Recruiter contact enrichment. - Job board distribution and one-click applying. - Any claim to improve your chances. ## After v1, if you want it - Interview notes attached to an application, which are worth more than the tracking - A comparison of stage timing across sources, so you learn which channels actually progress - Cover letter generation from the same evidence record and the same validation ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Applications, evidence and documents. Private, and worth backing up. DB_PATH=./data/search.db # Required. Generated Markdown and PDFs. DOCUMENTS_DIR=./data/documents # Required. Which adapter tailoring uses. Local means your job search stays on the machine. MODEL_PROVIDER=local # Optional · secret. Only for a hosted provider. Set a spend limit at the provider too. MODEL_API_KEY=sk-... # Required. Below this the renderer reports overflow rather than shrinking further. MIN_FONT_PT=9.5 # Required. How long without movement before an application is surfaced for follow-up. STALE_DAYS=14
You are building a lean indie version of Huntr. 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 ===== # Huntr · indie build A local workspace tracking applications through stages, storing the job description alongside each one, holding a master record of your real experience, and generating tailored documents where every bullet is linked to an entry in that record so nothing is invented. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite, running locally | a job search is private and there is no reason for it to live on someone else's server | | Storage | SQLite, with documents as files on disk | applications are structured; your resume is a document you also open elsewhere | | Documents | Markdown as the master format, rendered to PDF | one source of truth that diffs well, with the format employers want generated from it | | Tailoring | Retrieval over your evidence record, with a model constrained to it | the model rearranges and rephrases what you did; it must never add | | Rendering | A typesetting step producing consistent PDFs | a resume that reflows differently on each generation is a resume you cannot trust to look right | ## 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 rendering pipeline. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **An honest account of your own experience** · free, and it is the most valuable hour in this build - Why: Phase 3 is the foundation of everything after it, and it cannot be generated. Roles, dates, projects and any metrics you could actually defend. - Get it: Set aside an hour and write it from memory and from records, not from an existing resume. Where you want to claim a number, find the number. - Verify: Every role and project you would mention has an entry with real dates - [ ] **A model API key, or a local model** (optional) · pay per token; tailoring one document is a fraction of a cent - Why: Phase 4 only. Tracking, capture and the evidence record all work with no model at all. - Get it: A provider key with a spend limit set, or Ollama with a local model if you would rather your job search stayed on the machine. - Verify: A tailoring run completes and its provenance is recorded - [ ] **A PDF rendering toolchain** · free - Why: Employers want PDFs and applicant tracking systems parse them, so the output has to be real text rather than an image. - Get it: A headless browser print path or a typesetting tool. Whichever you choose, confirm the output text is selectable. - Verify: Generated PDF text can be selected and copied - [ ] **Five real job descriptions** · free - Why: Tailoring and retrieval cannot be judged against invented postings, and real ones contain the vague requirements that break naive keyword matching. - Get it: Save five postings for roles you would actually apply for. - Verify: Five descriptions are stored as fixtures ## 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: - Browser capture from job boards. - Recruiter contact enrichment. - Job board distribution and one-click applying. - Any claim to improve your chances. - browser capture, AI writing, resume builder, contact enrichment, and sync - proprietary recruiter data - job-board distribution - human coaching - automated application networks If one of those is essential to you, that is the reason to keep paying for Huntr, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Huntr 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 job application tracker where every claim on a tailored document traces back to something you actually did to replace Huntr. 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. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite, running locally · a job search is private and there is no reason for it to live on someone else's server - Storage: SQLite, with documents as files on disk · applications are structured; your resume is a document you also open elsewhere - Documents: Markdown as the master format, rendered to PDF · one source of truth that diffs well, with the format employers want generated from it - Tailoring: Retrieval over your evidence record, with a model constrained to it · the model rearranges and rephrases what you did; it must never add - Rendering: A typesetting step producing consistent PDFs · a resume that reflows differently on each generation is a resume you cannot trust to look right ### Data model (create this before Phase 1) - applications(id, company, role, url, source, applied_at, stage, salary_range, location, notes) - stages(application_id, stage, entered_at) · the history, so you can see how long each stage actually takes - descriptions(application_id, text, captured_at) · the job description as it was when you applied, because postings disappear - evidence(id, kind, title, organisation, started_at, ended_at, text, metrics_json) · your master record of what you actually did - documents(id, application_id, kind, path, generated_at, evidence_ids_json) · which evidence entries produced this document - contacts(id, application_id, name, role, notes), tasks(id, application_id, due_at, text, done_at) - The evidence identifiers on a document are the audit trail: every claim traces to something you recorded about yourself. ### Phase 1 · Applications and their stages Build: Create applications with company, role, link and source. Move them through stages and record when each transition happened. A board view by stage and a list view sorted by how long since the last movement. Trap: Storing only the current stage loses the timing, which is the information that actually helps: knowing that applications sit in one stage for three weeks changes when you follow up. Store transitions as rows, not a single field. Done when: An application moves through stages and its history shows each transition with a date. The list view surfaces applications with no movement for a chosen number of days. Deleting an application removes its stages, tasks and documents together. Stage timing statistics are computed from real transitions. Do not build yet: No documents, no tailoring, no capture. ### Phase 2 · Capturing the posting before it disappears Build: Store the job description text with the application. Capture it by paste or from a URL where the site permits fetching. Keep the captured date, and detect when a posting has changed or been removed if you re-check it. Trap: Job postings are taken down constantly, often before an interview, and preparing without the description you applied against is a real disadvantage. Capture the text at application time rather than storing a link, because the link will break exactly when you need it. Done when: A pasted description is stored with its capture date. Re-checking a removed posting marks it as gone rather than losing the stored text. The stored description is searchable across all applications. A description captured from a URL preserves paragraph structure rather than collapsing into one block. Do not build yet: No evidence record or tailoring yet. ### Phase 3 · An evidence record of what you actually did Build: A master record of roles, projects, education and achievements, each with dates, the organisation, a description in your words, and any real metrics. This is the only thing the tailoring step is allowed to draw from. Trap: Skipping the evidence record and tailoring directly from a previous resume means the model is rephrasing a rephrasing, and small exaggerations compound across applications until you cannot remember what you actually claimed. Write the record once from what you did, and treat every document as derived from it. Done when: Entries cover every role and project you would mention, with dates and metrics where they exist. A metric field is either a real number you can support or is empty, never an estimate. Searching the record returns relevant entries for a given job description's keywords. The record exports as Markdown you can read outside the application. Do not build yet: No tailoring yet. ### Phase 4 · Tailoring that cannot invent Build: Given a job description, retrieve relevant evidence entries and generate a tailored document constrained to them. Record which entries produced each bullet. Validate that every generated claim maps to an entry, and reject the output if one does not. Trap: A model asked to tailor a resume will add a plausible achievement you never had, and it will be the most impressive line on the page. Validate every claim against the evidence record and reject the generation rather than trusting an instruction, because the failure mode here is discovered in an interview. Done when: A generated document lists the evidence entries behind each section. A deliberately crafted output containing an unsupported claim fails validation, tested by feeding one to the validator directly. Every bullet in a real generated document traces to an entry you can point at. Regenerating for the same description produces a document drawing on the same entries. Do not build yet: No rendering or export yet. ### Phase 5 · Documents that look right every time Build: Render Markdown to PDF with a fixed template. Handle the one-page constraint explicitly: report when content overflows rather than silently shrinking type to illegibility. Keep the Markdown source alongside every PDF. Trap: Auto-shrinking type to fit a page produces a resume nobody can read at seven point type, and the tool that did it never says so. Report the overflow and let the writer cut something, because the decision about what to remove is theirs. Done when: A generated PDF is consistent across regenerations of the same content. Content exceeding one page produces a warning naming the overflow rather than shrinking the type below a floor. The Markdown source is stored next to the PDF. The PDF's text is selectable and machine readable rather than an image. Do not build yet: No follow-up tracking yet. ### Phase 6 · Follow-ups, privacy and export Build: Tasks with due dates per application, and reminders for stale ones. Export everything as JSON and Markdown. Be explicit about whether any text reaches a model provider, and give a way to delete an application and everything about it. Trap: A job search is unusually sensitive: it reveals that you are leaving, and where you applied. If tailoring uses a hosted model, your evidence record and the job descriptions leave your machine. Say so plainly and offer a local model path, because this is a case where the privacy preference is often strong. Done when: A task with a due date produces a reminder. Export produces JSON and Markdown covering applications, evidence and documents. Deleting an application removes its description, documents, tasks and contacts, verified by searching for its company name afterwards. The interface states which model is used and whether text leaves the machine. Do not build yet: Nothing after this. ### Out of scope (and why) - Browser capture from job boards, which is an extension per browser and is fragile against every site redesign. - Contact enrichment from a recruiter database, which is licensed data rather than a feature. - Job board distribution and one-click applying, which are integrations with each board. - Any claim to improve your chances. This organises what you did and keeps you honest about it, which is a smaller and more defensible promise. ### README must contain - That the evidence record is the only source tailoring may draw from - That every generated claim is validated against it and an unsupported one is rejected - That job descriptions are captured as text because postings disappear - That overflow is reported rather than fixed by shrinking type - Whether tailoring uses a hosted model, and therefore whether your job search leaves the machine ===== AGENTS.md ===== # Agent instructions · Huntr indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with Vite, running locally, SQLite, with documents as files on disk, Markdown as the master format, rendered to PDF, Retrieval over your evidence record, with a model constrained to it, A typesetting step producing consistent PDFs. 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 - Storing only the current stage loses the timing, which is the information that actually helps: knowing that applications sit in one stage for three weeks changes when you follow up. Store transitions as rows, not a single field. - Job postings are taken down constantly, often before an interview, and preparing without the description you applied against is a real disadvantage. Capture the text at application time rather than storing a link, because the link will break exactly when you need it. - Skipping the evidence record and tailoring directly from a previous resume means the model is rephrasing a rephrasing, and small exaggerations compound across applications until you cannot remember what you actually claimed. Write the record once from what you did, and treat every document as derived from it. - A model asked to tailor a resume will add a plausible achievement you never had, and it will be the most impressive line on the page. Validate every claim against the evidence record and reject the generation rather than trusting an instruction, because the failure mode here is discovered in an interview. - Auto-shrinking type to fit a page produces a resume nobody can read at seven point type, and the tool that did it never says so. Report the overflow and let the writer cut something, because the decision about what to remove is theirs. ===== BUILD_PLAN.md ===== # Build plan · Huntr A local workspace tracking applications through stages, storing the job description alongside each one, holding a master record of your real experience, and generating tailored documents where every bullet is linked to an entry in that record so nothing is invented. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Applications and their stages You can see every application and how long it has been sitting. ### Steps 1. Model stages as transitions One row per transition. The current stage is derived from the latest row. Files: `src/applications.ts` 2. Build board and list views Board for a sense of the pipeline, list sorted by staleness for deciding what to do today. Files: `src/views/` 3. Compute timing from history Average days per stage, and which applications have gone quiet. This is the number that changes behaviour. Files: `src/stats.ts` ### Done when - [ ] Stage history shows each transition with a date - [ ] The list surfaces applications with no recent movement - [ ] Deleting an application cascades to its related rows - [ ] Stage timing is computed from real transitions ### Watch out - Storing only the current stage loses the timing, which is the information that actually helps: knowing that applications sit in one stage for three weeks changes when you follow up. Store transitions as rows, not a single field. ## Phase 2 · Capturing the posting before it disappears You still have the job description when they call you three weeks later. ### Steps 1. Store the text, not the link Paste is the reliable path. A link is stored too, but as a convenience rather than the record. Files: `src/descriptions.ts` 2. Fetch where permitted Check the site's terms before automating a fetch. Many job boards forbid it, and paste always works. Files: `src/capture.ts` 3. Detect removal without losing anything Re-checking marks a posting gone and keeps the captured text. The text is the point. Files: `src/capture.ts` ### Done when - [ ] A pasted description is stored with its date - [ ] A removed posting is marked gone and its text survives - [ ] Descriptions are searchable across applications - [ ] Captured text preserves paragraph structure ### Watch out - Job postings are taken down constantly, often before an interview, and preparing without the description you applied against is a real disadvantage. Capture the text at application time rather than storing a link, because the link will break exactly when you need it. ## Phase 3 · An evidence record of what you actually did Every claim you might make exists in one place, in your own words, before any tailoring. ### Steps 1. Write the record from what you did Not from an existing resume. Your own words, with dates and real numbers only where you can support them. Files: `src/evidence.ts` 2. Keep metrics honest A metric is a number you could defend in an interview. If you cannot, leave it empty; an empty field is better than one you have to walk back. Files: `src/evidence.ts` 3. Make it searchable Full text over entries, since tailoring starts with finding relevant ones. Files: `src/search.ts` ### Done when - [ ] Every role and project you would mention has an entry - [ ] Metrics are real numbers or empty, never estimates - [ ] Search returns relevant entries for a description's keywords - [ ] The record exports as readable Markdown ### Watch out - Skipping the evidence record and tailoring directly from a previous resume means the model is rephrasing a rephrasing, and small exaggerations compound across applications until you cannot remember what you actually claimed. Write the record once from what you did, and treat every document as derived from it. ## Phase 4 · Tailoring that cannot invent A tailored document is a rearrangement of things you did, and you can prove it. ### Steps 1. Retrieve before generating Search the evidence record against the description, and pass only the retrieved entries. The model never sees your whole history at once. Files: `src/tailor.ts` 2. Validate every claim A checking step mapping each generated bullet to an entry. Unit test it with a crafted unsupported claim rather than waiting for a model to produce one. Files: `src/validate.ts`, `src/validate.test.ts` 3. Record the provenance Evidence identifiers on the document row, shown in the interface next to each section. Files: `src/documents.ts` ### Done when - [ ] A generated document shows the evidence behind each section - [ ] A crafted unsupported claim is rejected by the validator - [ ] Every bullet traces to an evidence entry - [ ] The validator is unit tested independently of any model ### Watch out - A model asked to tailor a resume will add a plausible achievement you never had, and it will be the most impressive line on the page. Validate every claim against the evidence record and reject the generation rather than trusting an instruction, because the failure mode here is discovered in an interview. ## Phase 5 · Documents that look right every time A generated PDF is consistent, readable and fits the page. ### Steps 1. Render with a fixed template One template, deterministic output. Compare two renders of the same content byte for byte. Files: `src/render.ts` 2. Warn on overflow rather than shrinking A minimum type size below which the render reports a problem instead of continuing. Files: `src/render.ts` 3. Keep the source with the output The Markdown next to the PDF. Regeneration and editing both need it. Files: `src/documents.ts` ### Done when - [ ] Two renders of the same content are identical - [ ] Overflow produces a warning rather than illegible type - [ ] The Markdown source is stored with every PDF - [ ] PDF text is selectable rather than an image ### Watch out - Auto-shrinking type to fit a page produces a resume nobody can read at seven point type, and the tool that did it never says so. Report the overflow and let the writer cut something, because the decision about what to remove is theirs. ## Not in this build - Browser capture from job boards. - Recruiter contact enrichment. - Job board distribution and one-click applying. - Any claim to improve your chances. ## After v1, if you want it - Interview notes attached to an application, which are worth more than the tracking - A comparison of stage timing across sources, so you learn which channels actually progress - Cover letter generation from the same evidence record and the same validation ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Applications, evidence and documents. Private, and worth backing up. DB_PATH=./data/search.db # Required. Generated Markdown and PDFs. DOCUMENTS_DIR=./data/documents # Required. Which adapter tailoring uses. Local means your job search stays on the machine. MODEL_PROVIDER=local # Optional · secret. Only for a hosted provider. Set a spend limit at the provider too. MODEL_API_KEY=sk-... # Required. Below this the renderer reports overflow rather than shrinking further. MIN_FONT_PT=9.5 # Required. How long without movement before an application is surfaced for follow-up. STALE_DAYS=14
You are building a production product version of Huntr. 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 ===== # Huntr · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Huntr, track job applications, contacts, tasks, documents, and tailored materials in one workspace. The hard boundary is browser capture, ai writing, resume builder, contact enrichment, and sync, plus data, distribution, and coaching. ## Product outcome A private job search workspace where you can see what is stalling, still have every job description, and generate tailored documents whose every claim you can point to in your own record. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - local browser - resume and job-description files - optional OpenAI API key ## Explicit non-goals for v1 - Browser capture from job boards. - Recruiter contact enrichment. - Job board distribution and one-click applying. - Any claim to improve your chances. - browser capture, AI writing, resume builder, contact enrichment, and sync - proprietary recruiter data - job-board distribution - human coaching - automated application networks ## Success criteria - The validator rejects a crafted unsupported claim in a unit test - Every bullet in a generated document is confirmed to trace to an evidence entry - Two renders of identical content produce identical PDFs - Deleting an application is confirmed to leave nothing findable - The interface states whether text leaves the machine ===== BRIEF.md ===== # Build brief · Huntr 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 job application tracker where every claim on a tailored document traces back to something you actually did to replace Huntr. 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. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite, running locally · a job search is private and there is no reason for it to live on someone else's server - Storage: SQLite, with documents as files on disk · applications are structured; your resume is a document you also open elsewhere - Documents: Markdown as the master format, rendered to PDF · one source of truth that diffs well, with the format employers want generated from it - Tailoring: Retrieval over your evidence record, with a model constrained to it · the model rearranges and rephrases what you did; it must never add - Rendering: A typesetting step producing consistent PDFs · a resume that reflows differently on each generation is a resume you cannot trust to look right ### Data model (create this before Phase 1) - applications(id, company, role, url, source, applied_at, stage, salary_range, location, notes) - stages(application_id, stage, entered_at) · the history, so you can see how long each stage actually takes - descriptions(application_id, text, captured_at) · the job description as it was when you applied, because postings disappear - evidence(id, kind, title, organisation, started_at, ended_at, text, metrics_json) · your master record of what you actually did - documents(id, application_id, kind, path, generated_at, evidence_ids_json) · which evidence entries produced this document - contacts(id, application_id, name, role, notes), tasks(id, application_id, due_at, text, done_at) - The evidence identifiers on a document are the audit trail: every claim traces to something you recorded about yourself. ### Phase 1 · Applications and their stages Build: Create applications with company, role, link and source. Move them through stages and record when each transition happened. A board view by stage and a list view sorted by how long since the last movement. Trap: Storing only the current stage loses the timing, which is the information that actually helps: knowing that applications sit in one stage for three weeks changes when you follow up. Store transitions as rows, not a single field. Done when: An application moves through stages and its history shows each transition with a date. The list view surfaces applications with no movement for a chosen number of days. Deleting an application removes its stages, tasks and documents together. Stage timing statistics are computed from real transitions. Do not build yet: No documents, no tailoring, no capture. ### Phase 2 · Capturing the posting before it disappears Build: Store the job description text with the application. Capture it by paste or from a URL where the site permits fetching. Keep the captured date, and detect when a posting has changed or been removed if you re-check it. Trap: Job postings are taken down constantly, often before an interview, and preparing without the description you applied against is a real disadvantage. Capture the text at application time rather than storing a link, because the link will break exactly when you need it. Done when: A pasted description is stored with its capture date. Re-checking a removed posting marks it as gone rather than losing the stored text. The stored description is searchable across all applications. A description captured from a URL preserves paragraph structure rather than collapsing into one block. Do not build yet: No evidence record or tailoring yet. ### Phase 3 · An evidence record of what you actually did Build: A master record of roles, projects, education and achievements, each with dates, the organisation, a description in your words, and any real metrics. This is the only thing the tailoring step is allowed to draw from. Trap: Skipping the evidence record and tailoring directly from a previous resume means the model is rephrasing a rephrasing, and small exaggerations compound across applications until you cannot remember what you actually claimed. Write the record once from what you did, and treat every document as derived from it. Done when: Entries cover every role and project you would mention, with dates and metrics where they exist. A metric field is either a real number you can support or is empty, never an estimate. Searching the record returns relevant entries for a given job description's keywords. The record exports as Markdown you can read outside the application. Do not build yet: No tailoring yet. ### Phase 4 · Tailoring that cannot invent Build: Given a job description, retrieve relevant evidence entries and generate a tailored document constrained to them. Record which entries produced each bullet. Validate that every generated claim maps to an entry, and reject the output if one does not. Trap: A model asked to tailor a resume will add a plausible achievement you never had, and it will be the most impressive line on the page. Validate every claim against the evidence record and reject the generation rather than trusting an instruction, because the failure mode here is discovered in an interview. Done when: A generated document lists the evidence entries behind each section. A deliberately crafted output containing an unsupported claim fails validation, tested by feeding one to the validator directly. Every bullet in a real generated document traces to an entry you can point at. Regenerating for the same description produces a document drawing on the same entries. Do not build yet: No rendering or export yet. ### Phase 5 · Documents that look right every time Build: Render Markdown to PDF with a fixed template. Handle the one-page constraint explicitly: report when content overflows rather than silently shrinking type to illegibility. Keep the Markdown source alongside every PDF. Trap: Auto-shrinking type to fit a page produces a resume nobody can read at seven point type, and the tool that did it never says so. Report the overflow and let the writer cut something, because the decision about what to remove is theirs. Done when: A generated PDF is consistent across regenerations of the same content. Content exceeding one page produces a warning naming the overflow rather than shrinking the type below a floor. The Markdown source is stored next to the PDF. The PDF's text is selectable and machine readable rather than an image. Do not build yet: No follow-up tracking yet. ### Phase 6 · Follow-ups, privacy and export Build: Tasks with due dates per application, and reminders for stale ones. Export everything as JSON and Markdown. Be explicit about whether any text reaches a model provider, and give a way to delete an application and everything about it. Trap: A job search is unusually sensitive: it reveals that you are leaving, and where you applied. If tailoring uses a hosted model, your evidence record and the job descriptions leave your machine. Say so plainly and offer a local model path, because this is a case where the privacy preference is often strong. Done when: A task with a due date produces a reminder. Export produces JSON and Markdown covering applications, evidence and documents. Deleting an application removes its description, documents, tasks and contacts, verified by searching for its company name afterwards. The interface states which model is used and whether text leaves the machine. Do not build yet: Nothing after this. ### Out of scope (and why) - Browser capture from job boards, which is an extension per browser and is fragile against every site redesign. - Contact enrichment from a recruiter database, which is licensed data rather than a feature. - Job board distribution and one-click applying, which are integrations with each board. - Any claim to improve your chances. This organises what you did and keeps you honest about it, which is a smaller and more defensible promise. ### README must contain - That the evidence record is the only source tailoring may draw from - That every generated claim is validated against it and an unsupported one is rejected - That job descriptions are captured as text because postings disappear - That overflow is reported rather than fixed by shrinking type - Whether tailoring uses a hosted model, and therefore whether your job search leaves the machine ===== ARCHITECTURE.md ===== # Architecture · Huntr ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite, running locally | a job search is private and there is no reason for it to live on someone else's server | | Storage | SQLite, with documents as files on disk | applications are structured; your resume is a document you also open elsewhere | | Documents | Markdown as the master format, rendered to PDF | one source of truth that diffs well, with the format employers want generated from it | | Tailoring | Retrieval over your evidence record, with a model constrained to it | the model rearranges and rephrases what you did; it must never add | | Rendering | A typesetting step producing consistent PDFs | a resume that reflows differently on each generation is a resume you cannot trust to look right | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Applications | Stages, transitions and timing. | Straightforward, and the transitions-as-rows decision is what makes the timing view possible. | | Capture | Preserving job descriptions. | Paste always works. Fetching is a convenience constrained by each site's terms. | | Evidence | The master record of what you did. | This is the asset. Everything else is derived from it and it should outlive the application. | | Tailor | Retrieval, generation and validation. | Any model. The validator is what makes swapping safe, because a weaker model's invention is caught rather than sent. | | Render | Deterministic PDF output. | Any toolchain producing selectable text. Reporting overflow rather than shrinking is the rule to keep. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DB_PATH` · required · Applications, evidence and documents. Private, and worth backing up. - `DOCUMENTS_DIR` · required · Generated Markdown and PDFs. - `MODEL_PROVIDER` · required · Which adapter tailoring uses. Local means your job search stays on the machine. - `MODEL_API_KEY` · optional, secret · Only for a hosted provider. Set a spend limit at the provider too. - `MIN_FONT_PT` · required · Below this the renderer reports overflow rather than shrinking further. - `STALE_DAYS` · required · How long without movement before an application is surfaced for follow-up. ## 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 · Huntr product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: TypeScript with Vite, running locally, SQLite, with documents as files on disk, Markdown as the master format, rendered to PDF, Retrieval over your evidence record, with a model constrained to it, A typesetting step producing consistent PDFs. - 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 - Storing only the current stage loses the timing, which is the information that actually helps: knowing that applications sit in one stage for three weeks changes when you follow up. Store transitions as rows, not a single field. - Job postings are taken down constantly, often before an interview, and preparing without the description you applied against is a real disadvantage. Capture the text at application time rather than storing a link, because the link will break exactly when you need it. - Skipping the evidence record and tailoring directly from a previous resume means the model is rephrasing a rephrasing, and small exaggerations compound across applications until you cannot remember what you actually claimed. Write the record once from what you did, and treat every document as derived from it. - A model asked to tailor a resume will add a plausible achievement you never had, and it will be the most impressive line on the page. Validate every claim against the evidence record and reject the generation rather than trusting an instruction, because the failure mode here is discovered in an interview. - Auto-shrinking type to fit a page produces a resume nobody can read at seven point type, and the tool that did it never says so. Report the overflow and let the writer cut something, because the decision about what to remove is theirs. - A job search is unusually sensitive: it reveals that you are leaving, and where you applied. If tailoring uses a hosted model, your evidence record and the job descriptions leave your machine. Say so plainly and offer a local model path, because this is a case where the privacy preference is often strong. ===== MILESTONES.md ===== # Delivery milestones · Huntr Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Applications and their stages You can see every application and how long it has been sitting. ### Steps 1. Model stages as transitions One row per transition. The current stage is derived from the latest row. Files: `src/applications.ts` 2. Build board and list views Board for a sense of the pipeline, list sorted by staleness for deciding what to do today. Files: `src/views/` 3. Compute timing from history Average days per stage, and which applications have gone quiet. This is the number that changes behaviour. Files: `src/stats.ts` ### Done when - [ ] Stage history shows each transition with a date - [ ] The list surfaces applications with no recent movement - [ ] Deleting an application cascades to its related rows - [ ] Stage timing is computed from real transitions ### Watch out - Storing only the current stage loses the timing, which is the information that actually helps: knowing that applications sit in one stage for three weeks changes when you follow up. Store transitions as rows, not a single field. ## M2 · Capturing the posting before it disappears You still have the job description when they call you three weeks later. ### Steps 1. Store the text, not the link Paste is the reliable path. A link is stored too, but as a convenience rather than the record. Files: `src/descriptions.ts` 2. Fetch where permitted Check the site's terms before automating a fetch. Many job boards forbid it, and paste always works. Files: `src/capture.ts` 3. Detect removal without losing anything Re-checking marks a posting gone and keeps the captured text. The text is the point. Files: `src/capture.ts` ### Done when - [ ] A pasted description is stored with its date - [ ] A removed posting is marked gone and its text survives - [ ] Descriptions are searchable across applications - [ ] Captured text preserves paragraph structure ### Watch out - Job postings are taken down constantly, often before an interview, and preparing without the description you applied against is a real disadvantage. Capture the text at application time rather than storing a link, because the link will break exactly when you need it. ## M3 · An evidence record of what you actually did Every claim you might make exists in one place, in your own words, before any tailoring. ### Steps 1. Write the record from what you did Not from an existing resume. Your own words, with dates and real numbers only where you can support them. Files: `src/evidence.ts` 2. Keep metrics honest A metric is a number you could defend in an interview. If you cannot, leave it empty; an empty field is better than one you have to walk back. Files: `src/evidence.ts` 3. Make it searchable Full text over entries, since tailoring starts with finding relevant ones. Files: `src/search.ts` ### Done when - [ ] Every role and project you would mention has an entry - [ ] Metrics are real numbers or empty, never estimates - [ ] Search returns relevant entries for a description's keywords - [ ] The record exports as readable Markdown ### Watch out - Skipping the evidence record and tailoring directly from a previous resume means the model is rephrasing a rephrasing, and small exaggerations compound across applications until you cannot remember what you actually claimed. Write the record once from what you did, and treat every document as derived from it. ## M4 · Tailoring that cannot invent A tailored document is a rearrangement of things you did, and you can prove it. ### Steps 1. Retrieve before generating Search the evidence record against the description, and pass only the retrieved entries. The model never sees your whole history at once. Files: `src/tailor.ts` 2. Validate every claim A checking step mapping each generated bullet to an entry. Unit test it with a crafted unsupported claim rather than waiting for a model to produce one. Files: `src/validate.ts`, `src/validate.test.ts` 3. Record the provenance Evidence identifiers on the document row, shown in the interface next to each section. Files: `src/documents.ts` ### Done when - [ ] A generated document shows the evidence behind each section - [ ] A crafted unsupported claim is rejected by the validator - [ ] Every bullet traces to an evidence entry - [ ] The validator is unit tested independently of any model ### Watch out - A model asked to tailor a resume will add a plausible achievement you never had, and it will be the most impressive line on the page. Validate every claim against the evidence record and reject the generation rather than trusting an instruction, because the failure mode here is discovered in an interview. ## M5 · Documents that look right every time A generated PDF is consistent, readable and fits the page. ### Steps 1. Render with a fixed template One template, deterministic output. Compare two renders of the same content byte for byte. Files: `src/render.ts` 2. Warn on overflow rather than shrinking A minimum type size below which the render reports a problem instead of continuing. Files: `src/render.ts` 3. Keep the source with the output The Markdown next to the PDF. Regeneration and editing both need it. Files: `src/documents.ts` ### Done when - [ ] Two renders of the same content are identical - [ ] Overflow produces a warning rather than illegible type - [ ] The Markdown source is stored with every PDF - [ ] PDF text is selectable rather than an image ### Watch out - Auto-shrinking type to fit a page produces a resume nobody can read at seven point type, and the tool that did it never says so. Report the overflow and let the writer cut something, because the decision about what to remove is theirs. ## M6 · Follow-ups, privacy and export (production only) Nothing falls through, and your job search is not sitting somewhere you cannot delete it. ### Steps 1. Add tasks and staleness reminders Due dates per application, plus a prompt for applications that have not moved. Following up is most of the value of tracking. Files: `src/tasks.ts` 2. Export everything JSON for completeness, Markdown for reading. A job search you cannot take with you is a lock-in you did not need. Files: `src/export.ts` 3. Make deletion complete and state the privacy position One deletion covering every table, verified by search. And a plain statement of what reaches a provider. Files: `src/delete.ts`, `README.md` ### Done when - [ ] A due task produces a reminder - [ ] Export covers applications, evidence and documents - [ ] Deleting an application leaves nothing findable by company name - [ ] The interface states whether text leaves the machine ### Watch out - A job search is unusually sensitive: it reveals that you are leaving, and where you applied. If tailoring uses a hosted model, your evidence record and the job descriptions leave your machine. Say so plainly and offer a local model path, because this is a case where the privacy preference is often strong. ===== OPERATIONS.md ===== # Operations · Huntr ## Backup The database and the documents directory. A job search is months of work and is unusually annoying to reconstruct, and it is small enough that there is no excuse. ## Restore Restore both and open the application. Documents reference evidence entries by identifier, so restoring one without the other leaves documents whose provenance cannot be shown. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Nothing runs as a service. What is worth watching is the validation rejection rate during tailoring: a rising rate means retrieval is returning irrelevant entries and the model is filling gaps. ## Incident checklist The incident that matters is discovering a document contains a claim you cannot support. The validator exists to prevent it, and if one gets through, the fix is in the validator rather than in remembering to check. 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 claim in a unit test - [ ] Every bullet in a generated document is confirmed to trace to an evidence entry - [ ] Two renders of identical content produce identical PDFs - [ ] Deleting an application is confirmed to leave nothing findable - [ ] The interface states whether text leaves the machine ## Launch constraint Do not market omitted Huntr 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. Applications, evidence and documents. Private, and worth backing up. DB_PATH=./data/search.db # Required. Generated Markdown and PDFs. DOCUMENTS_DIR=./data/documents # Required. Which adapter tailoring uses. Local means your job search stays on the machine. MODEL_PROVIDER=local # Optional · secret. Only for a hosted provider. Set a spend limit at the provider too. MODEL_API_KEY=sk-... # Required. Below this the renderer reports overflow rather than shrinking further. MIN_FONT_PT=9.5 # Required. How long without movement before an application is surfaced for follow-up. STALE_DAYS=14
# Huntr · indie build A local workspace tracking applications through stages, storing the job description alongside each one, holding a master record of your real experience, and generating tailored documents where every bullet is linked to an entry in that record so nothing is invented. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite, running locally | a job search is private and there is no reason for it to live on someone else's server | | Storage | SQLite, with documents as files on disk | applications are structured; your resume is a document you also open elsewhere | | Documents | Markdown as the master format, rendered to PDF | one source of truth that diffs well, with the format employers want generated from it | | Tailoring | Retrieval over your evidence record, with a model constrained to it | the model rearranges and rephrases what you did; it must never add | | Rendering | A typesetting step producing consistent PDFs | a resume that reflows differently on each generation is a resume you cannot trust to look right | ## 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 rendering pipeline. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **An honest account of your own experience** · free, and it is the most valuable hour in this build - Why: Phase 3 is the foundation of everything after it, and it cannot be generated. Roles, dates, projects and any metrics you could actually defend. - Get it: Set aside an hour and write it from memory and from records, not from an existing resume. Where you want to claim a number, find the number. - Verify: Every role and project you would mention has an entry with real dates - [ ] **A model API key, or a local model** (optional) · pay per token; tailoring one document is a fraction of a cent - Why: Phase 4 only. Tracking, capture and the evidence record all work with no model at all. - Get it: A provider key with a spend limit set, or Ollama with a local model if you would rather your job search stayed on the machine. - Verify: A tailoring run completes and its provenance is recorded - [ ] **A PDF rendering toolchain** · free - Why: Employers want PDFs and applicant tracking systems parse them, so the output has to be real text rather than an image. - Get it: A headless browser print path or a typesetting tool. Whichever you choose, confirm the output text is selectable. - Verify: Generated PDF text can be selected and copied - [ ] **Five real job descriptions** · free - Why: Tailoring and retrieval cannot be judged against invented postings, and real ones contain the vague requirements that break naive keyword matching. - Get it: Save five postings for roles you would actually apply for. - Verify: Five descriptions are stored as fixtures ## 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: - Browser capture from job boards. - Recruiter contact enrichment. - Job board distribution and one-click applying. - Any claim to improve your chances. - browser capture, AI writing, resume builder, contact enrichment, and sync - proprietary recruiter data - job-board distribution - human coaching - automated application networks If one of those is essential to you, that is the reason to keep paying for Huntr, and the README should say so rather than pretend.
# Build brief · Huntr 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 job application tracker where every claim on a tailored document traces back to something you actually did to replace Huntr. 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. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite, running locally · a job search is private and there is no reason for it to live on someone else's server - Storage: SQLite, with documents as files on disk · applications are structured; your resume is a document you also open elsewhere - Documents: Markdown as the master format, rendered to PDF · one source of truth that diffs well, with the format employers want generated from it - Tailoring: Retrieval over your evidence record, with a model constrained to it · the model rearranges and rephrases what you did; it must never add - Rendering: A typesetting step producing consistent PDFs · a resume that reflows differently on each generation is a resume you cannot trust to look right ### Data model (create this before Phase 1) - applications(id, company, role, url, source, applied_at, stage, salary_range, location, notes) - stages(application_id, stage, entered_at) · the history, so you can see how long each stage actually takes - descriptions(application_id, text, captured_at) · the job description as it was when you applied, because postings disappear - evidence(id, kind, title, organisation, started_at, ended_at, text, metrics_json) · your master record of what you actually did - documents(id, application_id, kind, path, generated_at, evidence_ids_json) · which evidence entries produced this document - contacts(id, application_id, name, role, notes), tasks(id, application_id, due_at, text, done_at) - The evidence identifiers on a document are the audit trail: every claim traces to something you recorded about yourself. ### Phase 1 · Applications and their stages Build: Create applications with company, role, link and source. Move them through stages and record when each transition happened. A board view by stage and a list view sorted by how long since the last movement. Trap: Storing only the current stage loses the timing, which is the information that actually helps: knowing that applications sit in one stage for three weeks changes when you follow up. Store transitions as rows, not a single field. Done when: An application moves through stages and its history shows each transition with a date. The list view surfaces applications with no movement for a chosen number of days. Deleting an application removes its stages, tasks and documents together. Stage timing statistics are computed from real transitions. Do not build yet: No documents, no tailoring, no capture. ### Phase 2 · Capturing the posting before it disappears Build: Store the job description text with the application. Capture it by paste or from a URL where the site permits fetching. Keep the captured date, and detect when a posting has changed or been removed if you re-check it. Trap: Job postings are taken down constantly, often before an interview, and preparing without the description you applied against is a real disadvantage. Capture the text at application time rather than storing a link, because the link will break exactly when you need it. Done when: A pasted description is stored with its capture date. Re-checking a removed posting marks it as gone rather than losing the stored text. The stored description is searchable across all applications. A description captured from a URL preserves paragraph structure rather than collapsing into one block. Do not build yet: No evidence record or tailoring yet. ### Phase 3 · An evidence record of what you actually did Build: A master record of roles, projects, education and achievements, each with dates, the organisation, a description in your words, and any real metrics. This is the only thing the tailoring step is allowed to draw from. Trap: Skipping the evidence record and tailoring directly from a previous resume means the model is rephrasing a rephrasing, and small exaggerations compound across applications until you cannot remember what you actually claimed. Write the record once from what you did, and treat every document as derived from it. Done when: Entries cover every role and project you would mention, with dates and metrics where they exist. A metric field is either a real number you can support or is empty, never an estimate. Searching the record returns relevant entries for a given job description's keywords. The record exports as Markdown you can read outside the application. Do not build yet: No tailoring yet. ### Phase 4 · Tailoring that cannot invent Build: Given a job description, retrieve relevant evidence entries and generate a tailored document constrained to them. Record which entries produced each bullet. Validate that every generated claim maps to an entry, and reject the output if one does not. Trap: A model asked to tailor a resume will add a plausible achievement you never had, and it will be the most impressive line on the page. Validate every claim against the evidence record and reject the generation rather than trusting an instruction, because the failure mode here is discovered in an interview. Done when: A generated document lists the evidence entries behind each section. A deliberately crafted output containing an unsupported claim fails validation, tested by feeding one to the validator directly. Every bullet in a real generated document traces to an entry you can point at. Regenerating for the same description produces a document drawing on the same entries. Do not build yet: No rendering or export yet. ### Phase 5 · Documents that look right every time Build: Render Markdown to PDF with a fixed template. Handle the one-page constraint explicitly: report when content overflows rather than silently shrinking type to illegibility. Keep the Markdown source alongside every PDF. Trap: Auto-shrinking type to fit a page produces a resume nobody can read at seven point type, and the tool that did it never says so. Report the overflow and let the writer cut something, because the decision about what to remove is theirs. Done when: A generated PDF is consistent across regenerations of the same content. Content exceeding one page produces a warning naming the overflow rather than shrinking the type below a floor. The Markdown source is stored next to the PDF. The PDF's text is selectable and machine readable rather than an image. Do not build yet: No follow-up tracking yet. ### Phase 6 · Follow-ups, privacy and export Build: Tasks with due dates per application, and reminders for stale ones. Export everything as JSON and Markdown. Be explicit about whether any text reaches a model provider, and give a way to delete an application and everything about it. Trap: A job search is unusually sensitive: it reveals that you are leaving, and where you applied. If tailoring uses a hosted model, your evidence record and the job descriptions leave your machine. Say so plainly and offer a local model path, because this is a case where the privacy preference is often strong. Done when: A task with a due date produces a reminder. Export produces JSON and Markdown covering applications, evidence and documents. Deleting an application removes its description, documents, tasks and contacts, verified by searching for its company name afterwards. The interface states which model is used and whether text leaves the machine. Do not build yet: Nothing after this. ### Out of scope (and why) - Browser capture from job boards, which is an extension per browser and is fragile against every site redesign. - Contact enrichment from a recruiter database, which is licensed data rather than a feature. - Job board distribution and one-click applying, which are integrations with each board. - Any claim to improve your chances. This organises what you did and keeps you honest about it, which is a smaller and more defensible promise. ### README must contain - That the evidence record is the only source tailoring may draw from - That every generated claim is validated against it and an unsupported one is rejected - That job descriptions are captured as text because postings disappear - That overflow is reported rather than fixed by shrinking type - Whether tailoring uses a hosted model, and therefore whether your job search leaves the machine
# Agent instructions · Huntr indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with Vite, running locally, SQLite, with documents as files on disk, Markdown as the master format, rendered to PDF, Retrieval over your evidence record, with a model constrained to it, A typesetting step producing consistent PDFs. 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 - Storing only the current stage loses the timing, which is the information that actually helps: knowing that applications sit in one stage for three weeks changes when you follow up. Store transitions as rows, not a single field. - Job postings are taken down constantly, often before an interview, and preparing without the description you applied against is a real disadvantage. Capture the text at application time rather than storing a link, because the link will break exactly when you need it. - Skipping the evidence record and tailoring directly from a previous resume means the model is rephrasing a rephrasing, and small exaggerations compound across applications until you cannot remember what you actually claimed. Write the record once from what you did, and treat every document as derived from it. - A model asked to tailor a resume will add a plausible achievement you never had, and it will be the most impressive line on the page. Validate every claim against the evidence record and reject the generation rather than trusting an instruction, because the failure mode here is discovered in an interview. - Auto-shrinking type to fit a page produces a resume nobody can read at seven point type, and the tool that did it never says so. Report the overflow and let the writer cut something, because the decision about what to remove is theirs.
# Build plan · Huntr A local workspace tracking applications through stages, storing the job description alongside each one, holding a master record of your real experience, and generating tailored documents where every bullet is linked to an entry in that record so nothing is invented. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Applications and their stages You can see every application and how long it has been sitting. ### Steps 1. Model stages as transitions One row per transition. The current stage is derived from the latest row. Files: `src/applications.ts` 2. Build board and list views Board for a sense of the pipeline, list sorted by staleness for deciding what to do today. Files: `src/views/` 3. Compute timing from history Average days per stage, and which applications have gone quiet. This is the number that changes behaviour. Files: `src/stats.ts` ### Done when - [ ] Stage history shows each transition with a date - [ ] The list surfaces applications with no recent movement - [ ] Deleting an application cascades to its related rows - [ ] Stage timing is computed from real transitions ### Watch out - Storing only the current stage loses the timing, which is the information that actually helps: knowing that applications sit in one stage for three weeks changes when you follow up. Store transitions as rows, not a single field. ## Phase 2 · Capturing the posting before it disappears You still have the job description when they call you three weeks later. ### Steps 1. Store the text, not the link Paste is the reliable path. A link is stored too, but as a convenience rather than the record. Files: `src/descriptions.ts` 2. Fetch where permitted Check the site's terms before automating a fetch. Many job boards forbid it, and paste always works. Files: `src/capture.ts` 3. Detect removal without losing anything Re-checking marks a posting gone and keeps the captured text. The text is the point. Files: `src/capture.ts` ### Done when - [ ] A pasted description is stored with its date - [ ] A removed posting is marked gone and its text survives - [ ] Descriptions are searchable across applications - [ ] Captured text preserves paragraph structure ### Watch out - Job postings are taken down constantly, often before an interview, and preparing without the description you applied against is a real disadvantage. Capture the text at application time rather than storing a link, because the link will break exactly when you need it. ## Phase 3 · An evidence record of what you actually did Every claim you might make exists in one place, in your own words, before any tailoring. ### Steps 1. Write the record from what you did Not from an existing resume. Your own words, with dates and real numbers only where you can support them. Files: `src/evidence.ts` 2. Keep metrics honest A metric is a number you could defend in an interview. If you cannot, leave it empty; an empty field is better than one you have to walk back. Files: `src/evidence.ts` 3. Make it searchable Full text over entries, since tailoring starts with finding relevant ones. Files: `src/search.ts` ### Done when - [ ] Every role and project you would mention has an entry - [ ] Metrics are real numbers or empty, never estimates - [ ] Search returns relevant entries for a description's keywords - [ ] The record exports as readable Markdown ### Watch out - Skipping the evidence record and tailoring directly from a previous resume means the model is rephrasing a rephrasing, and small exaggerations compound across applications until you cannot remember what you actually claimed. Write the record once from what you did, and treat every document as derived from it. ## Phase 4 · Tailoring that cannot invent A tailored document is a rearrangement of things you did, and you can prove it. ### Steps 1. Retrieve before generating Search the evidence record against the description, and pass only the retrieved entries. The model never sees your whole history at once. Files: `src/tailor.ts` 2. Validate every claim A checking step mapping each generated bullet to an entry. Unit test it with a crafted unsupported claim rather than waiting for a model to produce one. Files: `src/validate.ts`, `src/validate.test.ts` 3. Record the provenance Evidence identifiers on the document row, shown in the interface next to each section. Files: `src/documents.ts` ### Done when - [ ] A generated document shows the evidence behind each section - [ ] A crafted unsupported claim is rejected by the validator - [ ] Every bullet traces to an evidence entry - [ ] The validator is unit tested independently of any model ### Watch out - A model asked to tailor a resume will add a plausible achievement you never had, and it will be the most impressive line on the page. Validate every claim against the evidence record and reject the generation rather than trusting an instruction, because the failure mode here is discovered in an interview. ## Phase 5 · Documents that look right every time A generated PDF is consistent, readable and fits the page. ### Steps 1. Render with a fixed template One template, deterministic output. Compare two renders of the same content byte for byte. Files: `src/render.ts` 2. Warn on overflow rather than shrinking A minimum type size below which the render reports a problem instead of continuing. Files: `src/render.ts` 3. Keep the source with the output The Markdown next to the PDF. Regeneration and editing both need it. Files: `src/documents.ts` ### Done when - [ ] Two renders of the same content are identical - [ ] Overflow produces a warning rather than illegible type - [ ] The Markdown source is stored with every PDF - [ ] PDF text is selectable rather than an image ### Watch out - Auto-shrinking type to fit a page produces a resume nobody can read at seven point type, and the tool that did it never says so. Report the overflow and let the writer cut something, because the decision about what to remove is theirs. ## Not in this build - Browser capture from job boards. - Recruiter contact enrichment. - Job board distribution and one-click applying. - Any claim to improve your chances. ## After v1, if you want it - Interview notes attached to an application, which are worth more than the tracking - A comparison of stage timing across sources, so you learn which channels actually progress - Cover letter generation from the same evidence record and the same validation
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Applications, evidence and documents. Private, and worth backing up. DB_PATH=./data/search.db # Required. Generated Markdown and PDFs. DOCUMENTS_DIR=./data/documents # Required. Which adapter tailoring uses. Local means your job search stays on the machine. MODEL_PROVIDER=local # Optional · secret. Only for a hosted provider. Set a spend limit at the provider too. MODEL_API_KEY=sk-... # Required. Below this the renderer reports overflow rather than shrinking further. MIN_FONT_PT=9.5 # Required. How long without movement before an application is surfaced for follow-up. STALE_DAYS=14
# Huntr · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Huntr, track job applications, contacts, tasks, documents, and tailored materials in one workspace. The hard boundary is browser capture, ai writing, resume builder, contact enrichment, and sync, plus data, distribution, and coaching. ## Product outcome A private job search workspace where you can see what is stalling, still have every job description, and generate tailored documents whose every claim you can point to in your own record. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - local browser - resume and job-description files - optional OpenAI API key ## Explicit non-goals for v1 - Browser capture from job boards. - Recruiter contact enrichment. - Job board distribution and one-click applying. - Any claim to improve your chances. - browser capture, AI writing, resume builder, contact enrichment, and sync - proprietary recruiter data - job-board distribution - human coaching - automated application networks ## Success criteria - The validator rejects a crafted unsupported claim in a unit test - Every bullet in a generated document is confirmed to trace to an evidence entry - Two renders of identical content produce identical PDFs - Deleting an application is confirmed to leave nothing findable - The interface states whether text leaves the machine
# Build brief · Huntr 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 job application tracker where every claim on a tailored document traces back to something you actually did to replace Huntr. 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. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite, running locally · a job search is private and there is no reason for it to live on someone else's server - Storage: SQLite, with documents as files on disk · applications are structured; your resume is a document you also open elsewhere - Documents: Markdown as the master format, rendered to PDF · one source of truth that diffs well, with the format employers want generated from it - Tailoring: Retrieval over your evidence record, with a model constrained to it · the model rearranges and rephrases what you did; it must never add - Rendering: A typesetting step producing consistent PDFs · a resume that reflows differently on each generation is a resume you cannot trust to look right ### Data model (create this before Phase 1) - applications(id, company, role, url, source, applied_at, stage, salary_range, location, notes) - stages(application_id, stage, entered_at) · the history, so you can see how long each stage actually takes - descriptions(application_id, text, captured_at) · the job description as it was when you applied, because postings disappear - evidence(id, kind, title, organisation, started_at, ended_at, text, metrics_json) · your master record of what you actually did - documents(id, application_id, kind, path, generated_at, evidence_ids_json) · which evidence entries produced this document - contacts(id, application_id, name, role, notes), tasks(id, application_id, due_at, text, done_at) - The evidence identifiers on a document are the audit trail: every claim traces to something you recorded about yourself. ### Phase 1 · Applications and their stages Build: Create applications with company, role, link and source. Move them through stages and record when each transition happened. A board view by stage and a list view sorted by how long since the last movement. Trap: Storing only the current stage loses the timing, which is the information that actually helps: knowing that applications sit in one stage for three weeks changes when you follow up. Store transitions as rows, not a single field. Done when: An application moves through stages and its history shows each transition with a date. The list view surfaces applications with no movement for a chosen number of days. Deleting an application removes its stages, tasks and documents together. Stage timing statistics are computed from real transitions. Do not build yet: No documents, no tailoring, no capture. ### Phase 2 · Capturing the posting before it disappears Build: Store the job description text with the application. Capture it by paste or from a URL where the site permits fetching. Keep the captured date, and detect when a posting has changed or been removed if you re-check it. Trap: Job postings are taken down constantly, often before an interview, and preparing without the description you applied against is a real disadvantage. Capture the text at application time rather than storing a link, because the link will break exactly when you need it. Done when: A pasted description is stored with its capture date. Re-checking a removed posting marks it as gone rather than losing the stored text. The stored description is searchable across all applications. A description captured from a URL preserves paragraph structure rather than collapsing into one block. Do not build yet: No evidence record or tailoring yet. ### Phase 3 · An evidence record of what you actually did Build: A master record of roles, projects, education and achievements, each with dates, the organisation, a description in your words, and any real metrics. This is the only thing the tailoring step is allowed to draw from. Trap: Skipping the evidence record and tailoring directly from a previous resume means the model is rephrasing a rephrasing, and small exaggerations compound across applications until you cannot remember what you actually claimed. Write the record once from what you did, and treat every document as derived from it. Done when: Entries cover every role and project you would mention, with dates and metrics where they exist. A metric field is either a real number you can support or is empty, never an estimate. Searching the record returns relevant entries for a given job description's keywords. The record exports as Markdown you can read outside the application. Do not build yet: No tailoring yet. ### Phase 4 · Tailoring that cannot invent Build: Given a job description, retrieve relevant evidence entries and generate a tailored document constrained to them. Record which entries produced each bullet. Validate that every generated claim maps to an entry, and reject the output if one does not. Trap: A model asked to tailor a resume will add a plausible achievement you never had, and it will be the most impressive line on the page. Validate every claim against the evidence record and reject the generation rather than trusting an instruction, because the failure mode here is discovered in an interview. Done when: A generated document lists the evidence entries behind each section. A deliberately crafted output containing an unsupported claim fails validation, tested by feeding one to the validator directly. Every bullet in a real generated document traces to an entry you can point at. Regenerating for the same description produces a document drawing on the same entries. Do not build yet: No rendering or export yet. ### Phase 5 · Documents that look right every time Build: Render Markdown to PDF with a fixed template. Handle the one-page constraint explicitly: report when content overflows rather than silently shrinking type to illegibility. Keep the Markdown source alongside every PDF. Trap: Auto-shrinking type to fit a page produces a resume nobody can read at seven point type, and the tool that did it never says so. Report the overflow and let the writer cut something, because the decision about what to remove is theirs. Done when: A generated PDF is consistent across regenerations of the same content. Content exceeding one page produces a warning naming the overflow rather than shrinking the type below a floor. The Markdown source is stored next to the PDF. The PDF's text is selectable and machine readable rather than an image. Do not build yet: No follow-up tracking yet. ### Phase 6 · Follow-ups, privacy and export Build: Tasks with due dates per application, and reminders for stale ones. Export everything as JSON and Markdown. Be explicit about whether any text reaches a model provider, and give a way to delete an application and everything about it. Trap: A job search is unusually sensitive: it reveals that you are leaving, and where you applied. If tailoring uses a hosted model, your evidence record and the job descriptions leave your machine. Say so plainly and offer a local model path, because this is a case where the privacy preference is often strong. Done when: A task with a due date produces a reminder. Export produces JSON and Markdown covering applications, evidence and documents. Deleting an application removes its description, documents, tasks and contacts, verified by searching for its company name afterwards. The interface states which model is used and whether text leaves the machine. Do not build yet: Nothing after this. ### Out of scope (and why) - Browser capture from job boards, which is an extension per browser and is fragile against every site redesign. - Contact enrichment from a recruiter database, which is licensed data rather than a feature. - Job board distribution and one-click applying, which are integrations with each board. - Any claim to improve your chances. This organises what you did and keeps you honest about it, which is a smaller and more defensible promise. ### README must contain - That the evidence record is the only source tailoring may draw from - That every generated claim is validated against it and an unsupported one is rejected - That job descriptions are captured as text because postings disappear - That overflow is reported rather than fixed by shrinking type - Whether tailoring uses a hosted model, and therefore whether your job search leaves the machine
# Architecture · Huntr ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite, running locally | a job search is private and there is no reason for it to live on someone else's server | | Storage | SQLite, with documents as files on disk | applications are structured; your resume is a document you also open elsewhere | | Documents | Markdown as the master format, rendered to PDF | one source of truth that diffs well, with the format employers want generated from it | | Tailoring | Retrieval over your evidence record, with a model constrained to it | the model rearranges and rephrases what you did; it must never add | | Rendering | A typesetting step producing consistent PDFs | a resume that reflows differently on each generation is a resume you cannot trust to look right | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Applications | Stages, transitions and timing. | Straightforward, and the transitions-as-rows decision is what makes the timing view possible. | | Capture | Preserving job descriptions. | Paste always works. Fetching is a convenience constrained by each site's terms. | | Evidence | The master record of what you did. | This is the asset. Everything else is derived from it and it should outlive the application. | | Tailor | Retrieval, generation and validation. | Any model. The validator is what makes swapping safe, because a weaker model's invention is caught rather than sent. | | Render | Deterministic PDF output. | Any toolchain producing selectable text. Reporting overflow rather than shrinking is the rule to keep. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DB_PATH` · required · Applications, evidence and documents. Private, and worth backing up. - `DOCUMENTS_DIR` · required · Generated Markdown and PDFs. - `MODEL_PROVIDER` · required · Which adapter tailoring uses. Local means your job search stays on the machine. - `MODEL_API_KEY` · optional, secret · Only for a hosted provider. Set a spend limit at the provider too. - `MIN_FONT_PT` · required · Below this the renderer reports overflow rather than shrinking further. - `STALE_DAYS` · required · How long without movement before an application is surfaced for follow-up. ## 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 · Huntr product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: TypeScript with Vite, running locally, SQLite, with documents as files on disk, Markdown as the master format, rendered to PDF, Retrieval over your evidence record, with a model constrained to it, A typesetting step producing consistent PDFs. - 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 - Storing only the current stage loses the timing, which is the information that actually helps: knowing that applications sit in one stage for three weeks changes when you follow up. Store transitions as rows, not a single field. - Job postings are taken down constantly, often before an interview, and preparing without the description you applied against is a real disadvantage. Capture the text at application time rather than storing a link, because the link will break exactly when you need it. - Skipping the evidence record and tailoring directly from a previous resume means the model is rephrasing a rephrasing, and small exaggerations compound across applications until you cannot remember what you actually claimed. Write the record once from what you did, and treat every document as derived from it. - A model asked to tailor a resume will add a plausible achievement you never had, and it will be the most impressive line on the page. Validate every claim against the evidence record and reject the generation rather than trusting an instruction, because the failure mode here is discovered in an interview. - Auto-shrinking type to fit a page produces a resume nobody can read at seven point type, and the tool that did it never says so. Report the overflow and let the writer cut something, because the decision about what to remove is theirs. - A job search is unusually sensitive: it reveals that you are leaving, and where you applied. If tailoring uses a hosted model, your evidence record and the job descriptions leave your machine. Say so plainly and offer a local model path, because this is a case where the privacy preference is often strong.
# Delivery milestones · Huntr Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Applications and their stages You can see every application and how long it has been sitting. ### Steps 1. Model stages as transitions One row per transition. The current stage is derived from the latest row. Files: `src/applications.ts` 2. Build board and list views Board for a sense of the pipeline, list sorted by staleness for deciding what to do today. Files: `src/views/` 3. Compute timing from history Average days per stage, and which applications have gone quiet. This is the number that changes behaviour. Files: `src/stats.ts` ### Done when - [ ] Stage history shows each transition with a date - [ ] The list surfaces applications with no recent movement - [ ] Deleting an application cascades to its related rows - [ ] Stage timing is computed from real transitions ### Watch out - Storing only the current stage loses the timing, which is the information that actually helps: knowing that applications sit in one stage for three weeks changes when you follow up. Store transitions as rows, not a single field. ## M2 · Capturing the posting before it disappears You still have the job description when they call you three weeks later. ### Steps 1. Store the text, not the link Paste is the reliable path. A link is stored too, but as a convenience rather than the record. Files: `src/descriptions.ts` 2. Fetch where permitted Check the site's terms before automating a fetch. Many job boards forbid it, and paste always works. Files: `src/capture.ts` 3. Detect removal without losing anything Re-checking marks a posting gone and keeps the captured text. The text is the point. Files: `src/capture.ts` ### Done when - [ ] A pasted description is stored with its date - [ ] A removed posting is marked gone and its text survives - [ ] Descriptions are searchable across applications - [ ] Captured text preserves paragraph structure ### Watch out - Job postings are taken down constantly, often before an interview, and preparing without the description you applied against is a real disadvantage. Capture the text at application time rather than storing a link, because the link will break exactly when you need it. ## M3 · An evidence record of what you actually did Every claim you might make exists in one place, in your own words, before any tailoring. ### Steps 1. Write the record from what you did Not from an existing resume. Your own words, with dates and real numbers only where you can support them. Files: `src/evidence.ts` 2. Keep metrics honest A metric is a number you could defend in an interview. If you cannot, leave it empty; an empty field is better than one you have to walk back. Files: `src/evidence.ts` 3. Make it searchable Full text over entries, since tailoring starts with finding relevant ones. Files: `src/search.ts` ### Done when - [ ] Every role and project you would mention has an entry - [ ] Metrics are real numbers or empty, never estimates - [ ] Search returns relevant entries for a description's keywords - [ ] The record exports as readable Markdown ### Watch out - Skipping the evidence record and tailoring directly from a previous resume means the model is rephrasing a rephrasing, and small exaggerations compound across applications until you cannot remember what you actually claimed. Write the record once from what you did, and treat every document as derived from it. ## M4 · Tailoring that cannot invent A tailored document is a rearrangement of things you did, and you can prove it. ### Steps 1. Retrieve before generating Search the evidence record against the description, and pass only the retrieved entries. The model never sees your whole history at once. Files: `src/tailor.ts` 2. Validate every claim A checking step mapping each generated bullet to an entry. Unit test it with a crafted unsupported claim rather than waiting for a model to produce one. Files: `src/validate.ts`, `src/validate.test.ts` 3. Record the provenance Evidence identifiers on the document row, shown in the interface next to each section. Files: `src/documents.ts` ### Done when - [ ] A generated document shows the evidence behind each section - [ ] A crafted unsupported claim is rejected by the validator - [ ] Every bullet traces to an evidence entry - [ ] The validator is unit tested independently of any model ### Watch out - A model asked to tailor a resume will add a plausible achievement you never had, and it will be the most impressive line on the page. Validate every claim against the evidence record and reject the generation rather than trusting an instruction, because the failure mode here is discovered in an interview. ## M5 · Documents that look right every time A generated PDF is consistent, readable and fits the page. ### Steps 1. Render with a fixed template One template, deterministic output. Compare two renders of the same content byte for byte. Files: `src/render.ts` 2. Warn on overflow rather than shrinking A minimum type size below which the render reports a problem instead of continuing. Files: `src/render.ts` 3. Keep the source with the output The Markdown next to the PDF. Regeneration and editing both need it. Files: `src/documents.ts` ### Done when - [ ] Two renders of the same content are identical - [ ] Overflow produces a warning rather than illegible type - [ ] The Markdown source is stored with every PDF - [ ] PDF text is selectable rather than an image ### Watch out - Auto-shrinking type to fit a page produces a resume nobody can read at seven point type, and the tool that did it never says so. Report the overflow and let the writer cut something, because the decision about what to remove is theirs. ## M6 · Follow-ups, privacy and export (production only) Nothing falls through, and your job search is not sitting somewhere you cannot delete it. ### Steps 1. Add tasks and staleness reminders Due dates per application, plus a prompt for applications that have not moved. Following up is most of the value of tracking. Files: `src/tasks.ts` 2. Export everything JSON for completeness, Markdown for reading. A job search you cannot take with you is a lock-in you did not need. Files: `src/export.ts` 3. Make deletion complete and state the privacy position One deletion covering every table, verified by search. And a plain statement of what reaches a provider. Files: `src/delete.ts`, `README.md` ### Done when - [ ] A due task produces a reminder - [ ] Export covers applications, evidence and documents - [ ] Deleting an application leaves nothing findable by company name - [ ] The interface states whether text leaves the machine ### Watch out - A job search is unusually sensitive: it reveals that you are leaving, and where you applied. If tailoring uses a hosted model, your evidence record and the job descriptions leave your machine. Say so plainly and offer a local model path, because this is a case where the privacy preference is often strong.
# Operations · Huntr ## Backup The database and the documents directory. A job search is months of work and is unusually annoying to reconstruct, and it is small enough that there is no excuse. ## Restore Restore both and open the application. Documents reference evidence entries by identifier, so restoring one without the other leaves documents whose provenance cannot be shown. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Nothing runs as a service. What is worth watching is the validation rejection rate during tailoring: a rising rate means retrieval is returning irrelevant entries and the model is filling gaps. ## Incident checklist The incident that matters is discovering a document contains a claim you cannot support. The validator exists to prevent it, and if one gets through, the fix is in the validator rather than in remembering to check. 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 claim in a unit test - [ ] Every bullet in a generated document is confirmed to trace to an evidence entry - [ ] Two renders of identical content produce identical PDFs - [ ] Deleting an application is confirmed to leave nothing findable - [ ] The interface states whether text leaves the machine ## Launch constraint Do not market omitted Huntr 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. Applications, evidence and documents. Private, and worth backing up. DB_PATH=./data/search.db # Required. Generated Markdown and PDFs. DOCUMENTS_DIR=./data/documents # Required. Which adapter tailoring uses. Local means your job search stays on the machine. MODEL_PROVIDER=local # Optional · secret. Only for a hosted provider. Set a spend limit at the provider too. MODEL_API_KEY=sk-... # Required. Below this the renderer reports overflow rather than shrinking further. MIN_FONT_PT=9.5 # Required. How long without movement before an application is surfaced for follow-up. STALE_DAYS=14
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for Huntr because people pay for convenience, curated guidance, and distribution; the personal tracking and drafting loop is highly buildable. The recurring cost buys document parsing, truthful claim handling, job-source changes, browser automation rules, privacy, model drift, and user review, not just the visible interface.
xbrowser capture, AI writing, resume builder, contact enrichment, and sync
xproprietary recruiter data
xjob-board distribution
xhuman coaching
xautomated application networks
Don't feel like building it? These folks already made it free.
no votes, no pay-to-list · just what's real
Huntr pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| free | $0/user | $0/user | Unlimited base resumes; 2 tailored resumes; 2 application packets; track 100 jobs; 100 document uploads. |
| pro | $40/user | — | Unlimited tailored resumes, application packets, AI usage, tracked jobs and document uploads. |
free tierUnlimited base resumes; 2 tailored resumes, 2 application packets, 100 tracked jobs and 100 document uploads
billingmonthly + quarterly + 6-month; no annual plan
hidden costsThe lower advertised effective rates require $90 quarterly or $160 six-month prepayment; there is no annual billing option.
verified 2026-08-14 · source ↗
Vibecode Huntr
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal Huntr replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does Huntr cost?
Huntr costs about $40/month (Pro, checked 2026-07-31), which is $480 per year. That's what you save by replacing it with one prompt.
What do I lose by replacing Huntr?
Honestly: browser capture, AI writing, resume builder, contact enrichment, and sync; proprietary recruiter data; job-board distribution; human coaching; automated application networks. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Huntr?
Yes: Reactive Resume (A credible job-search workspace: applications, contacts, tasks, files, resumes and cover letters all live together.) JobSync (A self-hosted application tracker with tasks, documents and activity history, minus Huntr's polished contact CRM.) The prompt is for when you want it exactly your way.