Vibecode Careerflow
track this build4 phases, 12 steps, beginner friendly0%The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Careerflow, organize applications and create evidence-grounded resume and LinkedIn improvement checklists. The hard boundary is browser tooling, templates, coaching content, ai workflows, and hosted sync, plus data, distribution, and coaching.
You are building a lean indie version of Careerflow. 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 ===== # Careerflow · indie build A local job search workspace whose hard problems are keeping a tailored document traceable to approved evidence, treating imported document text as an approximate suggestion rather than fact, and reporting requirement coverage instead of inventing a score. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Next.js with TypeScript and SQLite through Drizzle, bound to loopback | this ends up being the most sensitive folder on the machine after your mail | | Evidence | Structured rows with an approval flag, the only source of document text | a line with no evidence row behind it cannot be rendered at all | | Imports | PDF and text imports held as drafts with extraction warnings shown | extracted text is a suggestion, and multi column layouts extract badly | | Model | Optional, restricted to rephrasing approved text, with the difference shown before saving | the useful part is wording and the dangerous part is addition | | Output | Documents rendered from structured data, never from edited free text | so the file you send contains exactly what you approved | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the application and the document rendering. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Your own CV and two real job descriptions** · free - Why: Coverage and extraction are only meaningful against a description you might actually apply to, and a seeded example will pass every test that matters. - Get it: Your current CV as a PDF, and two descriptions saved as text or PDF. - Verify: Both import, and the requirements list needs only small edits - [ ] **A two column PDF fixture** · free - Why: The column interleaving warning is the one extraction behaviour most likely to be missed, and it needs a document that actually has the problem. - Get it: Any CV or leaflet laid out in two columns, saved as a PDF and kept in the fixtures. - Verify: Extraction of it raises the interleaving warning with a page number - [ ] **A PDF rendering library for your runtime** · free - Why: Documents are rendered from structured data, so the renderer is a dependency rather than a browser print dialogue. - Get it: Any maintained library that renders from structured content and can be run headless. - Verify: It renders a fixture document whose text extracts back in the right order - [ ] **A model API key** (optional) · pennies per document - Why: Only for rephrasing approved text, and the tool is designed to stay useful when it is absent. - Get it: Create a key in your provider's console and put it in .env, or leave it unset. - Verify: With it unset, everything except rephrasing still works ## 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: - Mass applying. - Live interview assistance. - Any screening score or rejection probability. - Proprietary recruiter and salary databases. - browser tooling, templates, coaching content, AI workflows, and hosted 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 Careerflow, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Careerflow 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 workspace where every line of a tailored CV traces to evidence you wrote yourself to replace Careerflow. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Three refusals, and they are what makes the rest of this worth using. Nothing here invents experience. A tailored document is assembled only from evidence rows the person wrote and approved, and every rendered line shows which row it came from. A model may rephrase approved text; it may not add a skill, a date, a metric or an employer. No screening score. The widely repeated claim that most applications are thrown out by software before a human sees them traces back to a vendor that shut down in 2013 and is contradicted by recruiter surveys, so a percentage predicting it would be a made up number with a decimal point on it. Report coverage instead: which stated requirements have evidence behind them, which have partial evidence, and which have none. And no mass applying and no live interview assistance. Both make a person worse at this while making them feel faster, and both are the features that turn a workspace into a liability. ### Stack (fixed, do not substitute) - Runtime: Next.js with TypeScript and SQLite through Drizzle, bound to loopback · this ends up being the most sensitive folder on the machine after your mail - Evidence: Structured rows with an approval flag, the only source of document text · a line with no evidence row behind it cannot be rendered at all - Imports: PDF and text imports held as drafts with extraction warnings shown · extracted text is a suggestion, and multi column layouts extract badly - Model: Optional, restricted to rephrasing approved text, with the difference shown before saving · the useful part is wording and the dangerous part is addition - Output: Documents rendered from structured data, never from edited free text · so the file you send contains exactly what you approved ### Data model (create this before Phase 1) - evidence(id, kind, title, organisation, start_date, end_date, body, metrics_json, source_note, approved_at) · nothing is usable until it is approved - imports(id, kind, filename, extracted_text, warnings_json, approved_at) · an import is a draft until a person confirms it line by line - roles(id, company, title, description_text, source_url, imported_at) · the job, kept as text you can quote from - requirements(role_id, ordinal, text, kind) · extracted from the description and editable, because the extraction is a suggestion - coverage(role_id, requirement_ordinal, evidence_id, strength, note) · no rows means not covered, which is a perfectly good answer - variants(id, role_id, name, created_at) and variant_items(variant_id, ordinal, evidence_id, rendered_text) · every rendered line points at one evidence row - applications(id, role_id, variant_id, stage, applied_at, next_action_at, outcome, notes) · contacts and activity hang off this row ### Phase 1 · An evidence bank you actually wrote Build: Structured evidence rows for roles, projects, skills and metrics, each with dates, a body and a source note saying how you know it. Nothing becomes usable until it is approved. Duplicate detection on titles and date ranges, because the same job gets entered twice. Trap: Letting a resume import become evidence directly means every later document is tailored from an approximate parse of your own history, complete with a metric that was mangled and a date that slipped, and you will not notice because it looks like something you wrote. Imports land as drafts and become evidence only when a person approves each row. Done when: An unapproved evidence row cannot be selected into a document, enforced in the schema and tested. Every row has a date range and a source note, both required. Entering the same role twice is detected and offered as a merge rather than silently duplicated. Editing a row keeps the previous version visible, because a metric restated is worth checking. Do not build yet: No imports, coverage, documents or tracker. ### Phase 2 · Extracted text is a suggestion Build: Import a job description or an old CV from PDF or text. Show the extracted text next to the page for confirmation. Detect likely column interleaving and warn. Require approval before any extracted line becomes a requirement or an evidence draft. Trap: Text pulled out of a PDF arrives in the order the document's structure gives, not the order the eye follows: the W3C technique on PDF reading order notes that if a two column document is not properly tagged a reader goes straight across both columns and treats them as one, so an untagged two column CV extracts as interleaved half sentences that look like a typing error rather than a parsing failure. Show the extraction for approval and mark the pages where the order looks wrong. Done when: The extracted text is shown beside the source for every import and nothing is stored as data before approval. A two column fixture PDF raises a column interleaving warning naming the page. An import that extracts no text at all says so plainly rather than creating an empty draft. Approving an import records which lines were kept and which were discarded. Do not build yet: No coverage, documents, tracker or export yet. ### Phase 3 · Coverage, not a score Build: Turn the description into an editable list of requirements. For each, link zero or more evidence rows with a strength and a note. Render covered, partly covered and not covered with the evidence shown. Compute no overall percentage anywhere. Trap: A single match percentage is the number everybody wants and the one thing here that cannot be honest, because it implies knowledge of a process you cannot observe and it hides which specific requirement has nothing behind it. Show the three states per requirement with the evidence attached, and leave the summarising to the person applying. Done when: No code path produces an overall match percentage or score, verified by review. Each requirement shows its state and the evidence rows behind it, and an uncovered requirement is shown as plainly as a covered one. Requirements are editable, because the extraction is a suggestion. A requirement with evidence attached links through to that evidence row. Do not build yet: No document rendering, tracker or export yet. ### Phase 4 · A document assembled from approved rows Build: A variant selects and orders evidence rows for a role. A model may rephrase a row's text within limits, with the difference shown before it is saved. Reject a rephrasing that introduces a number, a date, an organisation or a skill absent from the source row. Render from the structured variant rather than from free text. Trap: A model asked to make a bullet sound stronger will quietly add 'by 40 percent' because that is what strong bullets look like, and the invented figure then gets defended in an interview by somebody who believes they wrote it. Compare the rephrasing against the source row, block any new number, date or proper noun, and show the difference before saving. Done when: A rendered line with no evidence link cannot exist, enforced by the renderer and tested. A rephrasing that introduces a percentage absent from the source is blocked, tested with a fixture. The difference between original and rephrased text is shown before saving and can be rejected. Exported documents list, on request, the evidence id behind every line. Do not build yet: No tracker, export or deletion yet. ### Phase 5 · The tracker, the export and the day you stop Build: Applications with stages, contacts, a next action date and an activity log. A view of what is due. Export evidence and documents as JSON and the tracker as CSV. With no API key configured everything except rephrasing still works. Deleting an application removes its documents too. Trap: A tracker where the follow up lives in a free text note means the follow up does not happen, and the whole reason to keep a tracker was the fortnight between an interview and the silence. Make the next action date a column with a due view, and make an application without one visible as an omission. Done when: The due view lists every application with a next action date on or before today, tested across a zone boundary. With no API key set, evidence, coverage, rendering from unmodified text, and the tracker all still work and the interface says which feature is unavailable. The export round trips: re-importing reproduces evidence, variants and applications. Deleting an application removes its rendered documents from disk, asserted by a test. Do not build yet: Nothing after this. ### Out of scope (and why) - Mass applying to many roles at once, which produces worse outcomes than fifteen considered applications and is a good way to be blocked by a job board. - Live interview assistance, which is a deception with a technical wrapper and is not what a workspace should ever be. - Any screening score or rejection probability, because there is no observable ground truth and the widely quoted figure behind the idea does not hold up. - Proprietary recruiter databases and salary data, which you cannot obtain honestly and cannot verify if you could. ### README must contain - That every rendered line traces to an approved evidence row, and that a model may rephrase but never add - That no screening score is produced, and why coverage per requirement replaces it - That extracted PDF text is shown for approval, and that untagged multi column documents extract out of reading order - That everything except rephrasing works with no API key configured - Where documents are written on disk and that deleting an application deletes them ===== AGENTS.md ===== # Agent instructions · Careerflow indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Next.js with TypeScript and SQLite through Drizzle, bound to loopback, Structured rows with an approval flag, the only source of document text, PDF and text imports held as drafts with extraction warnings shown, Optional, restricted to rephrasing approved text, with the difference shown before saving, Documents rendered from structured data, never from edited free text. 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 - Letting a resume import become evidence directly means every later document is tailored from an approximate parse of your own history, complete with a metric that was mangled and a date that slipped, and you will not notice because it looks like something you wrote. Imports land as drafts and become evidence only when a person approves each row. - Text pulled out of a PDF arrives in the order the document's structure gives, not the order the eye follows: the W3C technique on PDF reading order notes that if a two column document is not properly tagged a reader goes straight across both columns and treats them as one, so an untagged two column CV extracts as interleaved half sentences that look like a typing error rather than a parsing failure. Show the extraction for approval and mark the pages where the order looks wrong. - A single match percentage is the number everybody wants and the one thing here that cannot be honest, because it implies knowledge of a process you cannot observe and it hides which specific requirement has nothing behind it. Show the three states per requirement with the evidence attached, and leave the summarising to the person applying. - A model asked to make a bullet sound stronger will quietly add 'by 40 percent' because that is what strong bullets look like, and the invented figure then gets defended in an interview by somebody who believes they wrote it. Compare the rephrasing against the source row, block any new number, date or proper noun, and show the difference before saving. ===== BUILD_PLAN.md ===== # Build plan · Careerflow A local job search workspace whose hard problems are keeping a tailored document traceable to approved evidence, treating imported document text as an approximate suggestion rather than fact, and reporting requirement coverage instead of inventing a score. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · An evidence bank you actually wrote Every fact about your career exists once, dated, with a note on where it came from. ### Steps 1. Make approval a gate, not a label Unapproved rows are invisible to every document query. Files: `src/evidence.ts`, `tests/approval-gate.test.ts` 2. Require a source note on every row One sentence about how you know it, which is also what you will say in the interview. Files: `src/db/schema.ts` 3. Detect duplicate roles on title and dates The same job entered twice produces two truths. Files: `src/evidence/dedupe.ts` ### Done when - [ ] Unapproved rows cannot enter a document - [ ] Every row has dates and a source note - [ ] Duplicate roles are offered as a merge - [ ] Previous versions of an edited row stay visible ### Watch out - Letting a resume import become evidence directly means every later document is tailored from an approximate parse of your own history, complete with a metric that was mangled and a date that slipped, and you will not notice because it looks like something you wrote. Imports land as drafts and become evidence only when a person approves each row. ## Phase 2 · Extracted text is a suggestion Nothing imported becomes data until a person has seen what came out. ### Steps 1. Show extraction beside the source Two panes, and the failure becomes obvious in a second. Files: `src/imports/review.tsx` 2. Warn on likely column interleaving Detect short alternating fragments and say which page. Files: `src/imports/extract.ts`, `tests/two-column.test.ts` 3. Record what was kept and what was dropped So a bad import can be understood later rather than repeated. Files: `src/imports/approve.ts` ### Done when - [ ] Extraction is always reviewed before storage - [ ] A two column fixture raises a warning with the page - [ ] An empty extraction is reported, not stored - [ ] Approval records kept and discarded lines ### Watch out - Text pulled out of a PDF arrives in the order the document's structure gives, not the order the eye follows: the W3C technique on PDF reading order notes that if a two column document is not properly tagged a reader goes straight across both columns and treats them as one, so an untagged two column CV extracts as interleaved half sentences that look like a typing error rather than a parsing failure. Show the extraction for approval and mark the pages where the order looks wrong. ## Phase 3 · Coverage, not a score A role shows which requirements you can evidence and which you cannot. ### Steps 1. Make requirements editable rows Extraction proposes; the person applying decides. Files: `src/requirements.ts` 2. Link evidence per requirement with a strength Strength is a judgement, recorded as one. Files: `src/coverage.ts` 3. Refuse to compute an overall number There is no function that does it, not a setting that hides it. Files: `src/coverage.ts`, `tests/no-score.test.ts` ### Done when - [ ] No overall percentage exists anywhere - [ ] Every requirement shows its state and evidence - [ ] Requirements can be edited after extraction - [ ] Evidence links resolve from a requirement ### Watch out - A single match percentage is the number everybody wants and the one thing here that cannot be honest, because it implies knowledge of a process you cannot observe and it hides which specific requirement has nothing behind it. Show the three states per requirement with the evidence attached, and leave the summarising to the person applying. ## Phase 4 · A document assembled from approved rows Every rendered line traces to an evidence row and adds nothing to it. ### Steps 1. Block new numbers and proper nouns in rephrasing A token level comparison against the source row catches the common cases. Files: `src/rephrase/guard.ts`, `tests/no-invention.test.ts` 2. Show the difference before saving The person approves the change, not the idea of the change. Files: `src/rephrase/review.tsx` 3. Render from structured data only So what you send is what the data says, in an order you chose. Files: `src/render/document.ts` ### Done when - [ ] Lines without evidence links cannot render - [ ] Invented numbers are blocked before saving - [ ] Differences are shown and can be rejected - [ ] Evidence ids can be listed per rendered line ### Watch out - A model asked to make a bullet sound stronger will quietly add 'by 40 percent' because that is what strong bullets look like, and the invented figure then gets defended in an interview by somebody who believes they wrote it. Compare the rephrasing against the source row, block any new number, date or proper noun, and show the difference before saving. ## Not in this build - Mass applying. - Live interview assistance. - Any screening score or rejection probability. - Proprietary recruiter and salary databases. ## After v1, if you want it - Interview question practice with your own answers stored against evidence rows, reviewed by you rather than scored - A second document type, such as a covering letter, assembled by the same evidence rules - A record of which variants led to interviews, which is the only signal here worth collecting ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Evidence, roles, variants and applications. DATABASE_PATH=./data/career.db # Required. Rendered CVs and letters. Deleted with their application. DOCUMENTS_DIR=./data/documents # Required. Loopback. This folder holds your salary history and your rejections. BIND_ADDRESS=127.0.0.1 # Required. The due view depends on it, and follow ups are the point of the tracker. USER_TIMEZONE=Europe/London # Required. JSON and CSV exports, which are how you leave. EXPORT_DIR=./exports # Required. The only permitted mode. There is no generate mode to switch to. MODEL_MODE=rephrase-only # Optional · secret. Optional. Unset disables rephrasing and nothing else. OPENAI_API_KEY=sk-...redacted
You are building a lean indie version of Careerflow. 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 ===== # Careerflow · indie build A local job search workspace whose hard problems are keeping a tailored document traceable to approved evidence, treating imported document text as an approximate suggestion rather than fact, and reporting requirement coverage instead of inventing a score. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Next.js with TypeScript and SQLite through Drizzle, bound to loopback | this ends up being the most sensitive folder on the machine after your mail | | Evidence | Structured rows with an approval flag, the only source of document text | a line with no evidence row behind it cannot be rendered at all | | Imports | PDF and text imports held as drafts with extraction warnings shown | extracted text is a suggestion, and multi column layouts extract badly | | Model | Optional, restricted to rephrasing approved text, with the difference shown before saving | the useful part is wording and the dangerous part is addition | | Output | Documents rendered from structured data, never from edited free text | so the file you send contains exactly what you approved | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the application and the document rendering. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Your own CV and two real job descriptions** · free - Why: Coverage and extraction are only meaningful against a description you might actually apply to, and a seeded example will pass every test that matters. - Get it: Your current CV as a PDF, and two descriptions saved as text or PDF. - Verify: Both import, and the requirements list needs only small edits - [ ] **A two column PDF fixture** · free - Why: The column interleaving warning is the one extraction behaviour most likely to be missed, and it needs a document that actually has the problem. - Get it: Any CV or leaflet laid out in two columns, saved as a PDF and kept in the fixtures. - Verify: Extraction of it raises the interleaving warning with a page number - [ ] **A PDF rendering library for your runtime** · free - Why: Documents are rendered from structured data, so the renderer is a dependency rather than a browser print dialogue. - Get it: Any maintained library that renders from structured content and can be run headless. - Verify: It renders a fixture document whose text extracts back in the right order - [ ] **A model API key** (optional) · pennies per document - Why: Only for rephrasing approved text, and the tool is designed to stay useful when it is absent. - Get it: Create a key in your provider's console and put it in .env, or leave it unset. - Verify: With it unset, everything except rephrasing still works ## 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: - Mass applying. - Live interview assistance. - Any screening score or rejection probability. - Proprietary recruiter and salary databases. - browser tooling, templates, coaching content, AI workflows, and hosted 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 Careerflow, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Careerflow 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 workspace where every line of a tailored CV traces to evidence you wrote yourself to replace Careerflow. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Three refusals, and they are what makes the rest of this worth using. Nothing here invents experience. A tailored document is assembled only from evidence rows the person wrote and approved, and every rendered line shows which row it came from. A model may rephrase approved text; it may not add a skill, a date, a metric or an employer. No screening score. The widely repeated claim that most applications are thrown out by software before a human sees them traces back to a vendor that shut down in 2013 and is contradicted by recruiter surveys, so a percentage predicting it would be a made up number with a decimal point on it. Report coverage instead: which stated requirements have evidence behind them, which have partial evidence, and which have none. And no mass applying and no live interview assistance. Both make a person worse at this while making them feel faster, and both are the features that turn a workspace into a liability. ### Stack (fixed, do not substitute) - Runtime: Next.js with TypeScript and SQLite through Drizzle, bound to loopback · this ends up being the most sensitive folder on the machine after your mail - Evidence: Structured rows with an approval flag, the only source of document text · a line with no evidence row behind it cannot be rendered at all - Imports: PDF and text imports held as drafts with extraction warnings shown · extracted text is a suggestion, and multi column layouts extract badly - Model: Optional, restricted to rephrasing approved text, with the difference shown before saving · the useful part is wording and the dangerous part is addition - Output: Documents rendered from structured data, never from edited free text · so the file you send contains exactly what you approved ### Data model (create this before Phase 1) - evidence(id, kind, title, organisation, start_date, end_date, body, metrics_json, source_note, approved_at) · nothing is usable until it is approved - imports(id, kind, filename, extracted_text, warnings_json, approved_at) · an import is a draft until a person confirms it line by line - roles(id, company, title, description_text, source_url, imported_at) · the job, kept as text you can quote from - requirements(role_id, ordinal, text, kind) · extracted from the description and editable, because the extraction is a suggestion - coverage(role_id, requirement_ordinal, evidence_id, strength, note) · no rows means not covered, which is a perfectly good answer - variants(id, role_id, name, created_at) and variant_items(variant_id, ordinal, evidence_id, rendered_text) · every rendered line points at one evidence row - applications(id, role_id, variant_id, stage, applied_at, next_action_at, outcome, notes) · contacts and activity hang off this row ### Phase 1 · An evidence bank you actually wrote Build: Structured evidence rows for roles, projects, skills and metrics, each with dates, a body and a source note saying how you know it. Nothing becomes usable until it is approved. Duplicate detection on titles and date ranges, because the same job gets entered twice. Trap: Letting a resume import become evidence directly means every later document is tailored from an approximate parse of your own history, complete with a metric that was mangled and a date that slipped, and you will not notice because it looks like something you wrote. Imports land as drafts and become evidence only when a person approves each row. Done when: An unapproved evidence row cannot be selected into a document, enforced in the schema and tested. Every row has a date range and a source note, both required. Entering the same role twice is detected and offered as a merge rather than silently duplicated. Editing a row keeps the previous version visible, because a metric restated is worth checking. Do not build yet: No imports, coverage, documents or tracker. ### Phase 2 · Extracted text is a suggestion Build: Import a job description or an old CV from PDF or text. Show the extracted text next to the page for confirmation. Detect likely column interleaving and warn. Require approval before any extracted line becomes a requirement or an evidence draft. Trap: Text pulled out of a PDF arrives in the order the document's structure gives, not the order the eye follows: the W3C technique on PDF reading order notes that if a two column document is not properly tagged a reader goes straight across both columns and treats them as one, so an untagged two column CV extracts as interleaved half sentences that look like a typing error rather than a parsing failure. Show the extraction for approval and mark the pages where the order looks wrong. Done when: The extracted text is shown beside the source for every import and nothing is stored as data before approval. A two column fixture PDF raises a column interleaving warning naming the page. An import that extracts no text at all says so plainly rather than creating an empty draft. Approving an import records which lines were kept and which were discarded. Do not build yet: No coverage, documents, tracker or export yet. ### Phase 3 · Coverage, not a score Build: Turn the description into an editable list of requirements. For each, link zero or more evidence rows with a strength and a note. Render covered, partly covered and not covered with the evidence shown. Compute no overall percentage anywhere. Trap: A single match percentage is the number everybody wants and the one thing here that cannot be honest, because it implies knowledge of a process you cannot observe and it hides which specific requirement has nothing behind it. Show the three states per requirement with the evidence attached, and leave the summarising to the person applying. Done when: No code path produces an overall match percentage or score, verified by review. Each requirement shows its state and the evidence rows behind it, and an uncovered requirement is shown as plainly as a covered one. Requirements are editable, because the extraction is a suggestion. A requirement with evidence attached links through to that evidence row. Do not build yet: No document rendering, tracker or export yet. ### Phase 4 · A document assembled from approved rows Build: A variant selects and orders evidence rows for a role. A model may rephrase a row's text within limits, with the difference shown before it is saved. Reject a rephrasing that introduces a number, a date, an organisation or a skill absent from the source row. Render from the structured variant rather than from free text. Trap: A model asked to make a bullet sound stronger will quietly add 'by 40 percent' because that is what strong bullets look like, and the invented figure then gets defended in an interview by somebody who believes they wrote it. Compare the rephrasing against the source row, block any new number, date or proper noun, and show the difference before saving. Done when: A rendered line with no evidence link cannot exist, enforced by the renderer and tested. A rephrasing that introduces a percentage absent from the source is blocked, tested with a fixture. The difference between original and rephrased text is shown before saving and can be rejected. Exported documents list, on request, the evidence id behind every line. Do not build yet: No tracker, export or deletion yet. ### Phase 5 · The tracker, the export and the day you stop Build: Applications with stages, contacts, a next action date and an activity log. A view of what is due. Export evidence and documents as JSON and the tracker as CSV. With no API key configured everything except rephrasing still works. Deleting an application removes its documents too. Trap: A tracker where the follow up lives in a free text note means the follow up does not happen, and the whole reason to keep a tracker was the fortnight between an interview and the silence. Make the next action date a column with a due view, and make an application without one visible as an omission. Done when: The due view lists every application with a next action date on or before today, tested across a zone boundary. With no API key set, evidence, coverage, rendering from unmodified text, and the tracker all still work and the interface says which feature is unavailable. The export round trips: re-importing reproduces evidence, variants and applications. Deleting an application removes its rendered documents from disk, asserted by a test. Do not build yet: Nothing after this. ### Out of scope (and why) - Mass applying to many roles at once, which produces worse outcomes than fifteen considered applications and is a good way to be blocked by a job board. - Live interview assistance, which is a deception with a technical wrapper and is not what a workspace should ever be. - Any screening score or rejection probability, because there is no observable ground truth and the widely quoted figure behind the idea does not hold up. - Proprietary recruiter databases and salary data, which you cannot obtain honestly and cannot verify if you could. ### README must contain - That every rendered line traces to an approved evidence row, and that a model may rephrase but never add - That no screening score is produced, and why coverage per requirement replaces it - That extracted PDF text is shown for approval, and that untagged multi column documents extract out of reading order - That everything except rephrasing works with no API key configured - Where documents are written on disk and that deleting an application deletes them ===== AGENTS.md ===== # Agent instructions · Careerflow indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Next.js with TypeScript and SQLite through Drizzle, bound to loopback, Structured rows with an approval flag, the only source of document text, PDF and text imports held as drafts with extraction warnings shown, Optional, restricted to rephrasing approved text, with the difference shown before saving, Documents rendered from structured data, never from edited free text. 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 - Letting a resume import become evidence directly means every later document is tailored from an approximate parse of your own history, complete with a metric that was mangled and a date that slipped, and you will not notice because it looks like something you wrote. Imports land as drafts and become evidence only when a person approves each row. - Text pulled out of a PDF arrives in the order the document's structure gives, not the order the eye follows: the W3C technique on PDF reading order notes that if a two column document is not properly tagged a reader goes straight across both columns and treats them as one, so an untagged two column CV extracts as interleaved half sentences that look like a typing error rather than a parsing failure. Show the extraction for approval and mark the pages where the order looks wrong. - A single match percentage is the number everybody wants and the one thing here that cannot be honest, because it implies knowledge of a process you cannot observe and it hides which specific requirement has nothing behind it. Show the three states per requirement with the evidence attached, and leave the summarising to the person applying. - A model asked to make a bullet sound stronger will quietly add 'by 40 percent' because that is what strong bullets look like, and the invented figure then gets defended in an interview by somebody who believes they wrote it. Compare the rephrasing against the source row, block any new number, date or proper noun, and show the difference before saving. ===== BUILD_PLAN.md ===== # Build plan · Careerflow A local job search workspace whose hard problems are keeping a tailored document traceable to approved evidence, treating imported document text as an approximate suggestion rather than fact, and reporting requirement coverage instead of inventing a score. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · An evidence bank you actually wrote Every fact about your career exists once, dated, with a note on where it came from. ### Steps 1. Make approval a gate, not a label Unapproved rows are invisible to every document query. Files: `src/evidence.ts`, `tests/approval-gate.test.ts` 2. Require a source note on every row One sentence about how you know it, which is also what you will say in the interview. Files: `src/db/schema.ts` 3. Detect duplicate roles on title and dates The same job entered twice produces two truths. Files: `src/evidence/dedupe.ts` ### Done when - [ ] Unapproved rows cannot enter a document - [ ] Every row has dates and a source note - [ ] Duplicate roles are offered as a merge - [ ] Previous versions of an edited row stay visible ### Watch out - Letting a resume import become evidence directly means every later document is tailored from an approximate parse of your own history, complete with a metric that was mangled and a date that slipped, and you will not notice because it looks like something you wrote. Imports land as drafts and become evidence only when a person approves each row. ## Phase 2 · Extracted text is a suggestion Nothing imported becomes data until a person has seen what came out. ### Steps 1. Show extraction beside the source Two panes, and the failure becomes obvious in a second. Files: `src/imports/review.tsx` 2. Warn on likely column interleaving Detect short alternating fragments and say which page. Files: `src/imports/extract.ts`, `tests/two-column.test.ts` 3. Record what was kept and what was dropped So a bad import can be understood later rather than repeated. Files: `src/imports/approve.ts` ### Done when - [ ] Extraction is always reviewed before storage - [ ] A two column fixture raises a warning with the page - [ ] An empty extraction is reported, not stored - [ ] Approval records kept and discarded lines ### Watch out - Text pulled out of a PDF arrives in the order the document's structure gives, not the order the eye follows: the W3C technique on PDF reading order notes that if a two column document is not properly tagged a reader goes straight across both columns and treats them as one, so an untagged two column CV extracts as interleaved half sentences that look like a typing error rather than a parsing failure. Show the extraction for approval and mark the pages where the order looks wrong. ## Phase 3 · Coverage, not a score A role shows which requirements you can evidence and which you cannot. ### Steps 1. Make requirements editable rows Extraction proposes; the person applying decides. Files: `src/requirements.ts` 2. Link evidence per requirement with a strength Strength is a judgement, recorded as one. Files: `src/coverage.ts` 3. Refuse to compute an overall number There is no function that does it, not a setting that hides it. Files: `src/coverage.ts`, `tests/no-score.test.ts` ### Done when - [ ] No overall percentage exists anywhere - [ ] Every requirement shows its state and evidence - [ ] Requirements can be edited after extraction - [ ] Evidence links resolve from a requirement ### Watch out - A single match percentage is the number everybody wants and the one thing here that cannot be honest, because it implies knowledge of a process you cannot observe and it hides which specific requirement has nothing behind it. Show the three states per requirement with the evidence attached, and leave the summarising to the person applying. ## Phase 4 · A document assembled from approved rows Every rendered line traces to an evidence row and adds nothing to it. ### Steps 1. Block new numbers and proper nouns in rephrasing A token level comparison against the source row catches the common cases. Files: `src/rephrase/guard.ts`, `tests/no-invention.test.ts` 2. Show the difference before saving The person approves the change, not the idea of the change. Files: `src/rephrase/review.tsx` 3. Render from structured data only So what you send is what the data says, in an order you chose. Files: `src/render/document.ts` ### Done when - [ ] Lines without evidence links cannot render - [ ] Invented numbers are blocked before saving - [ ] Differences are shown and can be rejected - [ ] Evidence ids can be listed per rendered line ### Watch out - A model asked to make a bullet sound stronger will quietly add 'by 40 percent' because that is what strong bullets look like, and the invented figure then gets defended in an interview by somebody who believes they wrote it. Compare the rephrasing against the source row, block any new number, date or proper noun, and show the difference before saving. ## Not in this build - Mass applying. - Live interview assistance. - Any screening score or rejection probability. - Proprietary recruiter and salary databases. ## After v1, if you want it - Interview question practice with your own answers stored against evidence rows, reviewed by you rather than scored - A second document type, such as a covering letter, assembled by the same evidence rules - A record of which variants led to interviews, which is the only signal here worth collecting ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Evidence, roles, variants and applications. DATABASE_PATH=./data/career.db # Required. Rendered CVs and letters. Deleted with their application. DOCUMENTS_DIR=./data/documents # Required. Loopback. This folder holds your salary history and your rejections. BIND_ADDRESS=127.0.0.1 # Required. The due view depends on it, and follow ups are the point of the tracker. USER_TIMEZONE=Europe/London # Required. JSON and CSV exports, which are how you leave. EXPORT_DIR=./exports # Required. The only permitted mode. There is no generate mode to switch to. MODEL_MODE=rephrase-only # Optional · secret. Optional. Unset disables rephrasing and nothing else. OPENAI_API_KEY=sk-...redacted
You are building a production product version of Careerflow. 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 ===== # Careerflow · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Careerflow, organize applications and create evidence-grounded resume and LinkedIn improvement checklists. The hard boundary is browser tooling, templates, coaching content, ai workflows, and hosted sync, plus data, distribution, and coaching. ## Product outcome A workspace that will not put a word on your CV you did not write, shows exactly which requirements you can evidence rather than inventing a score, treats an imported PDF as a suggestion, and keeps the follow up dates that actually decide how a search goes. ## 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 - Mass applying. - Live interview assistance. - Any screening score or rejection probability. - Proprietary recruiter and salary databases. - browser tooling, templates, coaching content, AI workflows, and hosted sync - proprietary recruiter data - job-board distribution - human coaching - automated application networks ## Success criteria - Unapproved evidence is confirmed unusable in documents - A two column import is confirmed to warn - No overall score is confirmed to exist - Invented numbers are confirmed blocked in rephrasing - Everything except rephrasing is confirmed to work without a key ===== BRIEF.md ===== # Build brief · Careerflow 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 workspace where every line of a tailored CV traces to evidence you wrote yourself to replace Careerflow. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Three refusals, and they are what makes the rest of this worth using. Nothing here invents experience. A tailored document is assembled only from evidence rows the person wrote and approved, and every rendered line shows which row it came from. A model may rephrase approved text; it may not add a skill, a date, a metric or an employer. No screening score. The widely repeated claim that most applications are thrown out by software before a human sees them traces back to a vendor that shut down in 2013 and is contradicted by recruiter surveys, so a percentage predicting it would be a made up number with a decimal point on it. Report coverage instead: which stated requirements have evidence behind them, which have partial evidence, and which have none. And no mass applying and no live interview assistance. Both make a person worse at this while making them feel faster, and both are the features that turn a workspace into a liability. ### Stack (fixed, do not substitute) - Runtime: Next.js with TypeScript and SQLite through Drizzle, bound to loopback · this ends up being the most sensitive folder on the machine after your mail - Evidence: Structured rows with an approval flag, the only source of document text · a line with no evidence row behind it cannot be rendered at all - Imports: PDF and text imports held as drafts with extraction warnings shown · extracted text is a suggestion, and multi column layouts extract badly - Model: Optional, restricted to rephrasing approved text, with the difference shown before saving · the useful part is wording and the dangerous part is addition - Output: Documents rendered from structured data, never from edited free text · so the file you send contains exactly what you approved ### Data model (create this before Phase 1) - evidence(id, kind, title, organisation, start_date, end_date, body, metrics_json, source_note, approved_at) · nothing is usable until it is approved - imports(id, kind, filename, extracted_text, warnings_json, approved_at) · an import is a draft until a person confirms it line by line - roles(id, company, title, description_text, source_url, imported_at) · the job, kept as text you can quote from - requirements(role_id, ordinal, text, kind) · extracted from the description and editable, because the extraction is a suggestion - coverage(role_id, requirement_ordinal, evidence_id, strength, note) · no rows means not covered, which is a perfectly good answer - variants(id, role_id, name, created_at) and variant_items(variant_id, ordinal, evidence_id, rendered_text) · every rendered line points at one evidence row - applications(id, role_id, variant_id, stage, applied_at, next_action_at, outcome, notes) · contacts and activity hang off this row ### Phase 1 · An evidence bank you actually wrote Build: Structured evidence rows for roles, projects, skills and metrics, each with dates, a body and a source note saying how you know it. Nothing becomes usable until it is approved. Duplicate detection on titles and date ranges, because the same job gets entered twice. Trap: Letting a resume import become evidence directly means every later document is tailored from an approximate parse of your own history, complete with a metric that was mangled and a date that slipped, and you will not notice because it looks like something you wrote. Imports land as drafts and become evidence only when a person approves each row. Done when: An unapproved evidence row cannot be selected into a document, enforced in the schema and tested. Every row has a date range and a source note, both required. Entering the same role twice is detected and offered as a merge rather than silently duplicated. Editing a row keeps the previous version visible, because a metric restated is worth checking. Do not build yet: No imports, coverage, documents or tracker. ### Phase 2 · Extracted text is a suggestion Build: Import a job description or an old CV from PDF or text. Show the extracted text next to the page for confirmation. Detect likely column interleaving and warn. Require approval before any extracted line becomes a requirement or an evidence draft. Trap: Text pulled out of a PDF arrives in the order the document's structure gives, not the order the eye follows: the W3C technique on PDF reading order notes that if a two column document is not properly tagged a reader goes straight across both columns and treats them as one, so an untagged two column CV extracts as interleaved half sentences that look like a typing error rather than a parsing failure. Show the extraction for approval and mark the pages where the order looks wrong. Done when: The extracted text is shown beside the source for every import and nothing is stored as data before approval. A two column fixture PDF raises a column interleaving warning naming the page. An import that extracts no text at all says so plainly rather than creating an empty draft. Approving an import records which lines were kept and which were discarded. Do not build yet: No coverage, documents, tracker or export yet. ### Phase 3 · Coverage, not a score Build: Turn the description into an editable list of requirements. For each, link zero or more evidence rows with a strength and a note. Render covered, partly covered and not covered with the evidence shown. Compute no overall percentage anywhere. Trap: A single match percentage is the number everybody wants and the one thing here that cannot be honest, because it implies knowledge of a process you cannot observe and it hides which specific requirement has nothing behind it. Show the three states per requirement with the evidence attached, and leave the summarising to the person applying. Done when: No code path produces an overall match percentage or score, verified by review. Each requirement shows its state and the evidence rows behind it, and an uncovered requirement is shown as plainly as a covered one. Requirements are editable, because the extraction is a suggestion. A requirement with evidence attached links through to that evidence row. Do not build yet: No document rendering, tracker or export yet. ### Phase 4 · A document assembled from approved rows Build: A variant selects and orders evidence rows for a role. A model may rephrase a row's text within limits, with the difference shown before it is saved. Reject a rephrasing that introduces a number, a date, an organisation or a skill absent from the source row. Render from the structured variant rather than from free text. Trap: A model asked to make a bullet sound stronger will quietly add 'by 40 percent' because that is what strong bullets look like, and the invented figure then gets defended in an interview by somebody who believes they wrote it. Compare the rephrasing against the source row, block any new number, date or proper noun, and show the difference before saving. Done when: A rendered line with no evidence link cannot exist, enforced by the renderer and tested. A rephrasing that introduces a percentage absent from the source is blocked, tested with a fixture. The difference between original and rephrased text is shown before saving and can be rejected. Exported documents list, on request, the evidence id behind every line. Do not build yet: No tracker, export or deletion yet. ### Phase 5 · The tracker, the export and the day you stop Build: Applications with stages, contacts, a next action date and an activity log. A view of what is due. Export evidence and documents as JSON and the tracker as CSV. With no API key configured everything except rephrasing still works. Deleting an application removes its documents too. Trap: A tracker where the follow up lives in a free text note means the follow up does not happen, and the whole reason to keep a tracker was the fortnight between an interview and the silence. Make the next action date a column with a due view, and make an application without one visible as an omission. Done when: The due view lists every application with a next action date on or before today, tested across a zone boundary. With no API key set, evidence, coverage, rendering from unmodified text, and the tracker all still work and the interface says which feature is unavailable. The export round trips: re-importing reproduces evidence, variants and applications. Deleting an application removes its rendered documents from disk, asserted by a test. Do not build yet: Nothing after this. ### Out of scope (and why) - Mass applying to many roles at once, which produces worse outcomes than fifteen considered applications and is a good way to be blocked by a job board. - Live interview assistance, which is a deception with a technical wrapper and is not what a workspace should ever be. - Any screening score or rejection probability, because there is no observable ground truth and the widely quoted figure behind the idea does not hold up. - Proprietary recruiter databases and salary data, which you cannot obtain honestly and cannot verify if you could. ### README must contain - That every rendered line traces to an approved evidence row, and that a model may rephrase but never add - That no screening score is produced, and why coverage per requirement replaces it - That extracted PDF text is shown for approval, and that untagged multi column documents extract out of reading order - That everything except rephrasing works with no API key configured - Where documents are written on disk and that deleting an application deletes them ===== ARCHITECTURE.md ===== # Architecture · Careerflow ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Next.js with TypeScript and SQLite through Drizzle, bound to loopback | this ends up being the most sensitive folder on the machine after your mail | | Evidence | Structured rows with an approval flag, the only source of document text | a line with no evidence row behind it cannot be rendered at all | | Imports | PDF and text imports held as drafts with extraction warnings shown | extracted text is a suggestion, and multi column layouts extract badly | | Model | Optional, restricted to rephrasing approved text, with the difference shown before saving | the useful part is wording and the dangerous part is addition | | Output | Documents rendered from structured data, never from edited free text | so the file you send contains exactly what you approved | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Evidence | Approved rows with dates and source notes. | Any store, provided approval stays a gate on every document query. | | Imports | Drafts, extraction warnings and approval. | Any extractor, provided its output is reviewed before storage. | | Coverage | Per requirement states and evidence links. | Not swappable for a score, in any presentation. | | Documents | Variants, guarded rephrasing and rendering. | Any renderer, provided lines keep their evidence links. | | Tracker | Stages, next actions and activity. | Any interface, provided the next action stays a date column. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_PATH` · required · Evidence, roles, variants and applications. - `DOCUMENTS_DIR` · required · Rendered CVs and letters. Deleted with their application. - `BIND_ADDRESS` · required · Loopback. This folder holds your salary history and your rejections. - `USER_TIMEZONE` · required · The due view depends on it, and follow ups are the point of the tracker. - `EXPORT_DIR` · required · JSON and CSV exports, which are how you leave. - `MODEL_MODE` · required · The only permitted mode. There is no generate mode to switch to. - `OPENAI_API_KEY` · optional, secret · Optional. Unset disables rephrasing and nothing else. ## 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 · Careerflow product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Next.js with TypeScript and SQLite through Drizzle, bound to loopback, Structured rows with an approval flag, the only source of document text, PDF and text imports held as drafts with extraction warnings shown, Optional, restricted to rephrasing approved text, with the difference shown before saving, Documents rendered from structured data, never from edited free text. - 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 - Letting a resume import become evidence directly means every later document is tailored from an approximate parse of your own history, complete with a metric that was mangled and a date that slipped, and you will not notice because it looks like something you wrote. Imports land as drafts and become evidence only when a person approves each row. - Text pulled out of a PDF arrives in the order the document's structure gives, not the order the eye follows: the W3C technique on PDF reading order notes that if a two column document is not properly tagged a reader goes straight across both columns and treats them as one, so an untagged two column CV extracts as interleaved half sentences that look like a typing error rather than a parsing failure. Show the extraction for approval and mark the pages where the order looks wrong. - A single match percentage is the number everybody wants and the one thing here that cannot be honest, because it implies knowledge of a process you cannot observe and it hides which specific requirement has nothing behind it. Show the three states per requirement with the evidence attached, and leave the summarising to the person applying. - A model asked to make a bullet sound stronger will quietly add 'by 40 percent' because that is what strong bullets look like, and the invented figure then gets defended in an interview by somebody who believes they wrote it. Compare the rephrasing against the source row, block any new number, date or proper noun, and show the difference before saving. - A tracker where the follow up lives in a free text note means the follow up does not happen, and the whole reason to keep a tracker was the fortnight between an interview and the silence. Make the next action date a column with a due view, and make an application without one visible as an omission. ===== MILESTONES.md ===== # Delivery milestones · Careerflow Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · An evidence bank you actually wrote Every fact about your career exists once, dated, with a note on where it came from. ### Steps 1. Make approval a gate, not a label Unapproved rows are invisible to every document query. Files: `src/evidence.ts`, `tests/approval-gate.test.ts` 2. Require a source note on every row One sentence about how you know it, which is also what you will say in the interview. Files: `src/db/schema.ts` 3. Detect duplicate roles on title and dates The same job entered twice produces two truths. Files: `src/evidence/dedupe.ts` ### Done when - [ ] Unapproved rows cannot enter a document - [ ] Every row has dates and a source note - [ ] Duplicate roles are offered as a merge - [ ] Previous versions of an edited row stay visible ### Watch out - Letting a resume import become evidence directly means every later document is tailored from an approximate parse of your own history, complete with a metric that was mangled and a date that slipped, and you will not notice because it looks like something you wrote. Imports land as drafts and become evidence only when a person approves each row. ## M2 · Extracted text is a suggestion Nothing imported becomes data until a person has seen what came out. ### Steps 1. Show extraction beside the source Two panes, and the failure becomes obvious in a second. Files: `src/imports/review.tsx` 2. Warn on likely column interleaving Detect short alternating fragments and say which page. Files: `src/imports/extract.ts`, `tests/two-column.test.ts` 3. Record what was kept and what was dropped So a bad import can be understood later rather than repeated. Files: `src/imports/approve.ts` ### Done when - [ ] Extraction is always reviewed before storage - [ ] A two column fixture raises a warning with the page - [ ] An empty extraction is reported, not stored - [ ] Approval records kept and discarded lines ### Watch out - Text pulled out of a PDF arrives in the order the document's structure gives, not the order the eye follows: the W3C technique on PDF reading order notes that if a two column document is not properly tagged a reader goes straight across both columns and treats them as one, so an untagged two column CV extracts as interleaved half sentences that look like a typing error rather than a parsing failure. Show the extraction for approval and mark the pages where the order looks wrong. ## M3 · Coverage, not a score A role shows which requirements you can evidence and which you cannot. ### Steps 1. Make requirements editable rows Extraction proposes; the person applying decides. Files: `src/requirements.ts` 2. Link evidence per requirement with a strength Strength is a judgement, recorded as one. Files: `src/coverage.ts` 3. Refuse to compute an overall number There is no function that does it, not a setting that hides it. Files: `src/coverage.ts`, `tests/no-score.test.ts` ### Done when - [ ] No overall percentage exists anywhere - [ ] Every requirement shows its state and evidence - [ ] Requirements can be edited after extraction - [ ] Evidence links resolve from a requirement ### Watch out - A single match percentage is the number everybody wants and the one thing here that cannot be honest, because it implies knowledge of a process you cannot observe and it hides which specific requirement has nothing behind it. Show the three states per requirement with the evidence attached, and leave the summarising to the person applying. ## M4 · A document assembled from approved rows Every rendered line traces to an evidence row and adds nothing to it. ### Steps 1. Block new numbers and proper nouns in rephrasing A token level comparison against the source row catches the common cases. Files: `src/rephrase/guard.ts`, `tests/no-invention.test.ts` 2. Show the difference before saving The person approves the change, not the idea of the change. Files: `src/rephrase/review.tsx` 3. Render from structured data only So what you send is what the data says, in an order you chose. Files: `src/render/document.ts` ### Done when - [ ] Lines without evidence links cannot render - [ ] Invented numbers are blocked before saving - [ ] Differences are shown and can be rejected - [ ] Evidence ids can be listed per rendered line ### Watch out - A model asked to make a bullet sound stronger will quietly add 'by 40 percent' because that is what strong bullets look like, and the invented figure then gets defended in an interview by somebody who believes they wrote it. Compare the rephrasing against the source row, block any new number, date or proper noun, and show the difference before saving. ## M5 · The tracker, the export and the day you stop (production only) Follow ups happen, everything leaves in an open format, and it all runs without a model. ### Steps 1. Make the next action a date column Not a note, because notes are not queryable and do not nag. Files: `src/applications.ts` 2. Keep every feature working without a key The model is an improvement, never a dependency. Files: `src/model/optional.ts` 3. Delete documents with the application Rendered CVs on disk outlive the row that explains them. Files: `src/delete.ts`, `tests/delete-documents.test.ts` ### Done when - [ ] The due view is correct across a zone boundary - [ ] Everything except rephrasing works with no key - [ ] Export and re-import round trip - [ ] Deleting an application removes its documents ### Watch out - A tracker where the follow up lives in a free text note means the follow up does not happen, and the whole reason to keep a tracker was the fortnight between an interview and the silence. Make the next action date a column with a due view, and make an application without one visible as an omission. ===== OPERATIONS.md ===== # Operations · Careerflow ## Backup The database, the documents directory and the latest export together. This is personal data about you and about the people you spoke to, so encrypt the backup and keep it somewhere you would keep your tax records. ## Restore Restore all three, then confirm that a rendered document still resolves every evidence id and that the due view shows the follow ups you expected. Missing evidence links mean the documents directory came from a different backup than the database. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Applications with no next action date, evidence rows edited since a document was rendered from them, and rephrasings blocked by the guard. The middle one is how you find a CV in the wild that no longer matches what you would say. ## Incident checklist The incident is a claim on a sent document that you cannot support. The evidence links exist so that you can find out in twenty seconds which row it came from and whether you ever approved it. 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 - [ ] Unapproved evidence is confirmed unusable in documents - [ ] A two column import is confirmed to warn - [ ] No overall score is confirmed to exist - [ ] Invented numbers are confirmed blocked in rephrasing - [ ] Everything except rephrasing is confirmed to work without a key ## Launch constraint Do not market omitted Careerflow 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. Evidence, roles, variants and applications. DATABASE_PATH=./data/career.db # Required. Rendered CVs and letters. Deleted with their application. DOCUMENTS_DIR=./data/documents # Required. Loopback. This folder holds your salary history and your rejections. BIND_ADDRESS=127.0.0.1 # Required. The due view depends on it, and follow ups are the point of the tracker. USER_TIMEZONE=Europe/London # Required. JSON and CSV exports, which are how you leave. EXPORT_DIR=./exports # Required. The only permitted mode. There is no generate mode to switch to. MODEL_MODE=rephrase-only # Optional · secret. Optional. Unset disables rephrasing and nothing else. OPENAI_API_KEY=sk-...redacted
# Careerflow · indie build A local job search workspace whose hard problems are keeping a tailored document traceable to approved evidence, treating imported document text as an approximate suggestion rather than fact, and reporting requirement coverage instead of inventing a score. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Next.js with TypeScript and SQLite through Drizzle, bound to loopback | this ends up being the most sensitive folder on the machine after your mail | | Evidence | Structured rows with an approval flag, the only source of document text | a line with no evidence row behind it cannot be rendered at all | | Imports | PDF and text imports held as drafts with extraction warnings shown | extracted text is a suggestion, and multi column layouts extract badly | | Model | Optional, restricted to rephrasing approved text, with the difference shown before saving | the useful part is wording and the dangerous part is addition | | Output | Documents rendered from structured data, never from edited free text | so the file you send contains exactly what you approved | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the application and the document rendering. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Your own CV and two real job descriptions** · free - Why: Coverage and extraction are only meaningful against a description you might actually apply to, and a seeded example will pass every test that matters. - Get it: Your current CV as a PDF, and two descriptions saved as text or PDF. - Verify: Both import, and the requirements list needs only small edits - [ ] **A two column PDF fixture** · free - Why: The column interleaving warning is the one extraction behaviour most likely to be missed, and it needs a document that actually has the problem. - Get it: Any CV or leaflet laid out in two columns, saved as a PDF and kept in the fixtures. - Verify: Extraction of it raises the interleaving warning with a page number - [ ] **A PDF rendering library for your runtime** · free - Why: Documents are rendered from structured data, so the renderer is a dependency rather than a browser print dialogue. - Get it: Any maintained library that renders from structured content and can be run headless. - Verify: It renders a fixture document whose text extracts back in the right order - [ ] **A model API key** (optional) · pennies per document - Why: Only for rephrasing approved text, and the tool is designed to stay useful when it is absent. - Get it: Create a key in your provider's console and put it in .env, or leave it unset. - Verify: With it unset, everything except rephrasing still works ## 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: - Mass applying. - Live interview assistance. - Any screening score or rejection probability. - Proprietary recruiter and salary databases. - browser tooling, templates, coaching content, AI workflows, and hosted 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 Careerflow, and the README should say so rather than pretend.
# Build brief · Careerflow 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 workspace where every line of a tailored CV traces to evidence you wrote yourself to replace Careerflow. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Three refusals, and they are what makes the rest of this worth using. Nothing here invents experience. A tailored document is assembled only from evidence rows the person wrote and approved, and every rendered line shows which row it came from. A model may rephrase approved text; it may not add a skill, a date, a metric or an employer. No screening score. The widely repeated claim that most applications are thrown out by software before a human sees them traces back to a vendor that shut down in 2013 and is contradicted by recruiter surveys, so a percentage predicting it would be a made up number with a decimal point on it. Report coverage instead: which stated requirements have evidence behind them, which have partial evidence, and which have none. And no mass applying and no live interview assistance. Both make a person worse at this while making them feel faster, and both are the features that turn a workspace into a liability. ### Stack (fixed, do not substitute) - Runtime: Next.js with TypeScript and SQLite through Drizzle, bound to loopback · this ends up being the most sensitive folder on the machine after your mail - Evidence: Structured rows with an approval flag, the only source of document text · a line with no evidence row behind it cannot be rendered at all - Imports: PDF and text imports held as drafts with extraction warnings shown · extracted text is a suggestion, and multi column layouts extract badly - Model: Optional, restricted to rephrasing approved text, with the difference shown before saving · the useful part is wording and the dangerous part is addition - Output: Documents rendered from structured data, never from edited free text · so the file you send contains exactly what you approved ### Data model (create this before Phase 1) - evidence(id, kind, title, organisation, start_date, end_date, body, metrics_json, source_note, approved_at) · nothing is usable until it is approved - imports(id, kind, filename, extracted_text, warnings_json, approved_at) · an import is a draft until a person confirms it line by line - roles(id, company, title, description_text, source_url, imported_at) · the job, kept as text you can quote from - requirements(role_id, ordinal, text, kind) · extracted from the description and editable, because the extraction is a suggestion - coverage(role_id, requirement_ordinal, evidence_id, strength, note) · no rows means not covered, which is a perfectly good answer - variants(id, role_id, name, created_at) and variant_items(variant_id, ordinal, evidence_id, rendered_text) · every rendered line points at one evidence row - applications(id, role_id, variant_id, stage, applied_at, next_action_at, outcome, notes) · contacts and activity hang off this row ### Phase 1 · An evidence bank you actually wrote Build: Structured evidence rows for roles, projects, skills and metrics, each with dates, a body and a source note saying how you know it. Nothing becomes usable until it is approved. Duplicate detection on titles and date ranges, because the same job gets entered twice. Trap: Letting a resume import become evidence directly means every later document is tailored from an approximate parse of your own history, complete with a metric that was mangled and a date that slipped, and you will not notice because it looks like something you wrote. Imports land as drafts and become evidence only when a person approves each row. Done when: An unapproved evidence row cannot be selected into a document, enforced in the schema and tested. Every row has a date range and a source note, both required. Entering the same role twice is detected and offered as a merge rather than silently duplicated. Editing a row keeps the previous version visible, because a metric restated is worth checking. Do not build yet: No imports, coverage, documents or tracker. ### Phase 2 · Extracted text is a suggestion Build: Import a job description or an old CV from PDF or text. Show the extracted text next to the page for confirmation. Detect likely column interleaving and warn. Require approval before any extracted line becomes a requirement or an evidence draft. Trap: Text pulled out of a PDF arrives in the order the document's structure gives, not the order the eye follows: the W3C technique on PDF reading order notes that if a two column document is not properly tagged a reader goes straight across both columns and treats them as one, so an untagged two column CV extracts as interleaved half sentences that look like a typing error rather than a parsing failure. Show the extraction for approval and mark the pages where the order looks wrong. Done when: The extracted text is shown beside the source for every import and nothing is stored as data before approval. A two column fixture PDF raises a column interleaving warning naming the page. An import that extracts no text at all says so plainly rather than creating an empty draft. Approving an import records which lines were kept and which were discarded. Do not build yet: No coverage, documents, tracker or export yet. ### Phase 3 · Coverage, not a score Build: Turn the description into an editable list of requirements. For each, link zero or more evidence rows with a strength and a note. Render covered, partly covered and not covered with the evidence shown. Compute no overall percentage anywhere. Trap: A single match percentage is the number everybody wants and the one thing here that cannot be honest, because it implies knowledge of a process you cannot observe and it hides which specific requirement has nothing behind it. Show the three states per requirement with the evidence attached, and leave the summarising to the person applying. Done when: No code path produces an overall match percentage or score, verified by review. Each requirement shows its state and the evidence rows behind it, and an uncovered requirement is shown as plainly as a covered one. Requirements are editable, because the extraction is a suggestion. A requirement with evidence attached links through to that evidence row. Do not build yet: No document rendering, tracker or export yet. ### Phase 4 · A document assembled from approved rows Build: A variant selects and orders evidence rows for a role. A model may rephrase a row's text within limits, with the difference shown before it is saved. Reject a rephrasing that introduces a number, a date, an organisation or a skill absent from the source row. Render from the structured variant rather than from free text. Trap: A model asked to make a bullet sound stronger will quietly add 'by 40 percent' because that is what strong bullets look like, and the invented figure then gets defended in an interview by somebody who believes they wrote it. Compare the rephrasing against the source row, block any new number, date or proper noun, and show the difference before saving. Done when: A rendered line with no evidence link cannot exist, enforced by the renderer and tested. A rephrasing that introduces a percentage absent from the source is blocked, tested with a fixture. The difference between original and rephrased text is shown before saving and can be rejected. Exported documents list, on request, the evidence id behind every line. Do not build yet: No tracker, export or deletion yet. ### Phase 5 · The tracker, the export and the day you stop Build: Applications with stages, contacts, a next action date and an activity log. A view of what is due. Export evidence and documents as JSON and the tracker as CSV. With no API key configured everything except rephrasing still works. Deleting an application removes its documents too. Trap: A tracker where the follow up lives in a free text note means the follow up does not happen, and the whole reason to keep a tracker was the fortnight between an interview and the silence. Make the next action date a column with a due view, and make an application without one visible as an omission. Done when: The due view lists every application with a next action date on or before today, tested across a zone boundary. With no API key set, evidence, coverage, rendering from unmodified text, and the tracker all still work and the interface says which feature is unavailable. The export round trips: re-importing reproduces evidence, variants and applications. Deleting an application removes its rendered documents from disk, asserted by a test. Do not build yet: Nothing after this. ### Out of scope (and why) - Mass applying to many roles at once, which produces worse outcomes than fifteen considered applications and is a good way to be blocked by a job board. - Live interview assistance, which is a deception with a technical wrapper and is not what a workspace should ever be. - Any screening score or rejection probability, because there is no observable ground truth and the widely quoted figure behind the idea does not hold up. - Proprietary recruiter databases and salary data, which you cannot obtain honestly and cannot verify if you could. ### README must contain - That every rendered line traces to an approved evidence row, and that a model may rephrase but never add - That no screening score is produced, and why coverage per requirement replaces it - That extracted PDF text is shown for approval, and that untagged multi column documents extract out of reading order - That everything except rephrasing works with no API key configured - Where documents are written on disk and that deleting an application deletes them
# Agent instructions · Careerflow indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Next.js with TypeScript and SQLite through Drizzle, bound to loopback, Structured rows with an approval flag, the only source of document text, PDF and text imports held as drafts with extraction warnings shown, Optional, restricted to rephrasing approved text, with the difference shown before saving, Documents rendered from structured data, never from edited free text. 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 - Letting a resume import become evidence directly means every later document is tailored from an approximate parse of your own history, complete with a metric that was mangled and a date that slipped, and you will not notice because it looks like something you wrote. Imports land as drafts and become evidence only when a person approves each row. - Text pulled out of a PDF arrives in the order the document's structure gives, not the order the eye follows: the W3C technique on PDF reading order notes that if a two column document is not properly tagged a reader goes straight across both columns and treats them as one, so an untagged two column CV extracts as interleaved half sentences that look like a typing error rather than a parsing failure. Show the extraction for approval and mark the pages where the order looks wrong. - A single match percentage is the number everybody wants and the one thing here that cannot be honest, because it implies knowledge of a process you cannot observe and it hides which specific requirement has nothing behind it. Show the three states per requirement with the evidence attached, and leave the summarising to the person applying. - A model asked to make a bullet sound stronger will quietly add 'by 40 percent' because that is what strong bullets look like, and the invented figure then gets defended in an interview by somebody who believes they wrote it. Compare the rephrasing against the source row, block any new number, date or proper noun, and show the difference before saving.
# Build plan · Careerflow A local job search workspace whose hard problems are keeping a tailored document traceable to approved evidence, treating imported document text as an approximate suggestion rather than fact, and reporting requirement coverage instead of inventing a score. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · An evidence bank you actually wrote Every fact about your career exists once, dated, with a note on where it came from. ### Steps 1. Make approval a gate, not a label Unapproved rows are invisible to every document query. Files: `src/evidence.ts`, `tests/approval-gate.test.ts` 2. Require a source note on every row One sentence about how you know it, which is also what you will say in the interview. Files: `src/db/schema.ts` 3. Detect duplicate roles on title and dates The same job entered twice produces two truths. Files: `src/evidence/dedupe.ts` ### Done when - [ ] Unapproved rows cannot enter a document - [ ] Every row has dates and a source note - [ ] Duplicate roles are offered as a merge - [ ] Previous versions of an edited row stay visible ### Watch out - Letting a resume import become evidence directly means every later document is tailored from an approximate parse of your own history, complete with a metric that was mangled and a date that slipped, and you will not notice because it looks like something you wrote. Imports land as drafts and become evidence only when a person approves each row. ## Phase 2 · Extracted text is a suggestion Nothing imported becomes data until a person has seen what came out. ### Steps 1. Show extraction beside the source Two panes, and the failure becomes obvious in a second. Files: `src/imports/review.tsx` 2. Warn on likely column interleaving Detect short alternating fragments and say which page. Files: `src/imports/extract.ts`, `tests/two-column.test.ts` 3. Record what was kept and what was dropped So a bad import can be understood later rather than repeated. Files: `src/imports/approve.ts` ### Done when - [ ] Extraction is always reviewed before storage - [ ] A two column fixture raises a warning with the page - [ ] An empty extraction is reported, not stored - [ ] Approval records kept and discarded lines ### Watch out - Text pulled out of a PDF arrives in the order the document's structure gives, not the order the eye follows: the W3C technique on PDF reading order notes that if a two column document is not properly tagged a reader goes straight across both columns and treats them as one, so an untagged two column CV extracts as interleaved half sentences that look like a typing error rather than a parsing failure. Show the extraction for approval and mark the pages where the order looks wrong. ## Phase 3 · Coverage, not a score A role shows which requirements you can evidence and which you cannot. ### Steps 1. Make requirements editable rows Extraction proposes; the person applying decides. Files: `src/requirements.ts` 2. Link evidence per requirement with a strength Strength is a judgement, recorded as one. Files: `src/coverage.ts` 3. Refuse to compute an overall number There is no function that does it, not a setting that hides it. Files: `src/coverage.ts`, `tests/no-score.test.ts` ### Done when - [ ] No overall percentage exists anywhere - [ ] Every requirement shows its state and evidence - [ ] Requirements can be edited after extraction - [ ] Evidence links resolve from a requirement ### Watch out - A single match percentage is the number everybody wants and the one thing here that cannot be honest, because it implies knowledge of a process you cannot observe and it hides which specific requirement has nothing behind it. Show the three states per requirement with the evidence attached, and leave the summarising to the person applying. ## Phase 4 · A document assembled from approved rows Every rendered line traces to an evidence row and adds nothing to it. ### Steps 1. Block new numbers and proper nouns in rephrasing A token level comparison against the source row catches the common cases. Files: `src/rephrase/guard.ts`, `tests/no-invention.test.ts` 2. Show the difference before saving The person approves the change, not the idea of the change. Files: `src/rephrase/review.tsx` 3. Render from structured data only So what you send is what the data says, in an order you chose. Files: `src/render/document.ts` ### Done when - [ ] Lines without evidence links cannot render - [ ] Invented numbers are blocked before saving - [ ] Differences are shown and can be rejected - [ ] Evidence ids can be listed per rendered line ### Watch out - A model asked to make a bullet sound stronger will quietly add 'by 40 percent' because that is what strong bullets look like, and the invented figure then gets defended in an interview by somebody who believes they wrote it. Compare the rephrasing against the source row, block any new number, date or proper noun, and show the difference before saving. ## Not in this build - Mass applying. - Live interview assistance. - Any screening score or rejection probability. - Proprietary recruiter and salary databases. ## After v1, if you want it - Interview question practice with your own answers stored against evidence rows, reviewed by you rather than scored - A second document type, such as a covering letter, assembled by the same evidence rules - A record of which variants led to interviews, which is the only signal here worth collecting
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Evidence, roles, variants and applications. DATABASE_PATH=./data/career.db # Required. Rendered CVs and letters. Deleted with their application. DOCUMENTS_DIR=./data/documents # Required. Loopback. This folder holds your salary history and your rejections. BIND_ADDRESS=127.0.0.1 # Required. The due view depends on it, and follow ups are the point of the tracker. USER_TIMEZONE=Europe/London # Required. JSON and CSV exports, which are how you leave. EXPORT_DIR=./exports # Required. The only permitted mode. There is no generate mode to switch to. MODEL_MODE=rephrase-only # Optional · secret. Optional. Unset disables rephrasing and nothing else. OPENAI_API_KEY=sk-...redacted
# Careerflow · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Careerflow, organize applications and create evidence-grounded resume and LinkedIn improvement checklists. The hard boundary is browser tooling, templates, coaching content, ai workflows, and hosted sync, plus data, distribution, and coaching. ## Product outcome A workspace that will not put a word on your CV you did not write, shows exactly which requirements you can evidence rather than inventing a score, treats an imported PDF as a suggestion, and keeps the follow up dates that actually decide how a search goes. ## 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 - Mass applying. - Live interview assistance. - Any screening score or rejection probability. - Proprietary recruiter and salary databases. - browser tooling, templates, coaching content, AI workflows, and hosted sync - proprietary recruiter data - job-board distribution - human coaching - automated application networks ## Success criteria - Unapproved evidence is confirmed unusable in documents - A two column import is confirmed to warn - No overall score is confirmed to exist - Invented numbers are confirmed blocked in rephrasing - Everything except rephrasing is confirmed to work without a key
# Build brief · Careerflow 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 workspace where every line of a tailored CV traces to evidence you wrote yourself to replace Careerflow. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Three refusals, and they are what makes the rest of this worth using. Nothing here invents experience. A tailored document is assembled only from evidence rows the person wrote and approved, and every rendered line shows which row it came from. A model may rephrase approved text; it may not add a skill, a date, a metric or an employer. No screening score. The widely repeated claim that most applications are thrown out by software before a human sees them traces back to a vendor that shut down in 2013 and is contradicted by recruiter surveys, so a percentage predicting it would be a made up number with a decimal point on it. Report coverage instead: which stated requirements have evidence behind them, which have partial evidence, and which have none. And no mass applying and no live interview assistance. Both make a person worse at this while making them feel faster, and both are the features that turn a workspace into a liability. ### Stack (fixed, do not substitute) - Runtime: Next.js with TypeScript and SQLite through Drizzle, bound to loopback · this ends up being the most sensitive folder on the machine after your mail - Evidence: Structured rows with an approval flag, the only source of document text · a line with no evidence row behind it cannot be rendered at all - Imports: PDF and text imports held as drafts with extraction warnings shown · extracted text is a suggestion, and multi column layouts extract badly - Model: Optional, restricted to rephrasing approved text, with the difference shown before saving · the useful part is wording and the dangerous part is addition - Output: Documents rendered from structured data, never from edited free text · so the file you send contains exactly what you approved ### Data model (create this before Phase 1) - evidence(id, kind, title, organisation, start_date, end_date, body, metrics_json, source_note, approved_at) · nothing is usable until it is approved - imports(id, kind, filename, extracted_text, warnings_json, approved_at) · an import is a draft until a person confirms it line by line - roles(id, company, title, description_text, source_url, imported_at) · the job, kept as text you can quote from - requirements(role_id, ordinal, text, kind) · extracted from the description and editable, because the extraction is a suggestion - coverage(role_id, requirement_ordinal, evidence_id, strength, note) · no rows means not covered, which is a perfectly good answer - variants(id, role_id, name, created_at) and variant_items(variant_id, ordinal, evidence_id, rendered_text) · every rendered line points at one evidence row - applications(id, role_id, variant_id, stage, applied_at, next_action_at, outcome, notes) · contacts and activity hang off this row ### Phase 1 · An evidence bank you actually wrote Build: Structured evidence rows for roles, projects, skills and metrics, each with dates, a body and a source note saying how you know it. Nothing becomes usable until it is approved. Duplicate detection on titles and date ranges, because the same job gets entered twice. Trap: Letting a resume import become evidence directly means every later document is tailored from an approximate parse of your own history, complete with a metric that was mangled and a date that slipped, and you will not notice because it looks like something you wrote. Imports land as drafts and become evidence only when a person approves each row. Done when: An unapproved evidence row cannot be selected into a document, enforced in the schema and tested. Every row has a date range and a source note, both required. Entering the same role twice is detected and offered as a merge rather than silently duplicated. Editing a row keeps the previous version visible, because a metric restated is worth checking. Do not build yet: No imports, coverage, documents or tracker. ### Phase 2 · Extracted text is a suggestion Build: Import a job description or an old CV from PDF or text. Show the extracted text next to the page for confirmation. Detect likely column interleaving and warn. Require approval before any extracted line becomes a requirement or an evidence draft. Trap: Text pulled out of a PDF arrives in the order the document's structure gives, not the order the eye follows: the W3C technique on PDF reading order notes that if a two column document is not properly tagged a reader goes straight across both columns and treats them as one, so an untagged two column CV extracts as interleaved half sentences that look like a typing error rather than a parsing failure. Show the extraction for approval and mark the pages where the order looks wrong. Done when: The extracted text is shown beside the source for every import and nothing is stored as data before approval. A two column fixture PDF raises a column interleaving warning naming the page. An import that extracts no text at all says so plainly rather than creating an empty draft. Approving an import records which lines were kept and which were discarded. Do not build yet: No coverage, documents, tracker or export yet. ### Phase 3 · Coverage, not a score Build: Turn the description into an editable list of requirements. For each, link zero or more evidence rows with a strength and a note. Render covered, partly covered and not covered with the evidence shown. Compute no overall percentage anywhere. Trap: A single match percentage is the number everybody wants and the one thing here that cannot be honest, because it implies knowledge of a process you cannot observe and it hides which specific requirement has nothing behind it. Show the three states per requirement with the evidence attached, and leave the summarising to the person applying. Done when: No code path produces an overall match percentage or score, verified by review. Each requirement shows its state and the evidence rows behind it, and an uncovered requirement is shown as plainly as a covered one. Requirements are editable, because the extraction is a suggestion. A requirement with evidence attached links through to that evidence row. Do not build yet: No document rendering, tracker or export yet. ### Phase 4 · A document assembled from approved rows Build: A variant selects and orders evidence rows for a role. A model may rephrase a row's text within limits, with the difference shown before it is saved. Reject a rephrasing that introduces a number, a date, an organisation or a skill absent from the source row. Render from the structured variant rather than from free text. Trap: A model asked to make a bullet sound stronger will quietly add 'by 40 percent' because that is what strong bullets look like, and the invented figure then gets defended in an interview by somebody who believes they wrote it. Compare the rephrasing against the source row, block any new number, date or proper noun, and show the difference before saving. Done when: A rendered line with no evidence link cannot exist, enforced by the renderer and tested. A rephrasing that introduces a percentage absent from the source is blocked, tested with a fixture. The difference between original and rephrased text is shown before saving and can be rejected. Exported documents list, on request, the evidence id behind every line. Do not build yet: No tracker, export or deletion yet. ### Phase 5 · The tracker, the export and the day you stop Build: Applications with stages, contacts, a next action date and an activity log. A view of what is due. Export evidence and documents as JSON and the tracker as CSV. With no API key configured everything except rephrasing still works. Deleting an application removes its documents too. Trap: A tracker where the follow up lives in a free text note means the follow up does not happen, and the whole reason to keep a tracker was the fortnight between an interview and the silence. Make the next action date a column with a due view, and make an application without one visible as an omission. Done when: The due view lists every application with a next action date on or before today, tested across a zone boundary. With no API key set, evidence, coverage, rendering from unmodified text, and the tracker all still work and the interface says which feature is unavailable. The export round trips: re-importing reproduces evidence, variants and applications. Deleting an application removes its rendered documents from disk, asserted by a test. Do not build yet: Nothing after this. ### Out of scope (and why) - Mass applying to many roles at once, which produces worse outcomes than fifteen considered applications and is a good way to be blocked by a job board. - Live interview assistance, which is a deception with a technical wrapper and is not what a workspace should ever be. - Any screening score or rejection probability, because there is no observable ground truth and the widely quoted figure behind the idea does not hold up. - Proprietary recruiter databases and salary data, which you cannot obtain honestly and cannot verify if you could. ### README must contain - That every rendered line traces to an approved evidence row, and that a model may rephrase but never add - That no screening score is produced, and why coverage per requirement replaces it - That extracted PDF text is shown for approval, and that untagged multi column documents extract out of reading order - That everything except rephrasing works with no API key configured - Where documents are written on disk and that deleting an application deletes them
# Architecture · Careerflow ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Next.js with TypeScript and SQLite through Drizzle, bound to loopback | this ends up being the most sensitive folder on the machine after your mail | | Evidence | Structured rows with an approval flag, the only source of document text | a line with no evidence row behind it cannot be rendered at all | | Imports | PDF and text imports held as drafts with extraction warnings shown | extracted text is a suggestion, and multi column layouts extract badly | | Model | Optional, restricted to rephrasing approved text, with the difference shown before saving | the useful part is wording and the dangerous part is addition | | Output | Documents rendered from structured data, never from edited free text | so the file you send contains exactly what you approved | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Evidence | Approved rows with dates and source notes. | Any store, provided approval stays a gate on every document query. | | Imports | Drafts, extraction warnings and approval. | Any extractor, provided its output is reviewed before storage. | | Coverage | Per requirement states and evidence links. | Not swappable for a score, in any presentation. | | Documents | Variants, guarded rephrasing and rendering. | Any renderer, provided lines keep their evidence links. | | Tracker | Stages, next actions and activity. | Any interface, provided the next action stays a date column. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_PATH` · required · Evidence, roles, variants and applications. - `DOCUMENTS_DIR` · required · Rendered CVs and letters. Deleted with their application. - `BIND_ADDRESS` · required · Loopback. This folder holds your salary history and your rejections. - `USER_TIMEZONE` · required · The due view depends on it, and follow ups are the point of the tracker. - `EXPORT_DIR` · required · JSON and CSV exports, which are how you leave. - `MODEL_MODE` · required · The only permitted mode. There is no generate mode to switch to. - `OPENAI_API_KEY` · optional, secret · Optional. Unset disables rephrasing and nothing else. ## 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 · Careerflow product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Next.js with TypeScript and SQLite through Drizzle, bound to loopback, Structured rows with an approval flag, the only source of document text, PDF and text imports held as drafts with extraction warnings shown, Optional, restricted to rephrasing approved text, with the difference shown before saving, Documents rendered from structured data, never from edited free text. - 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 - Letting a resume import become evidence directly means every later document is tailored from an approximate parse of your own history, complete with a metric that was mangled and a date that slipped, and you will not notice because it looks like something you wrote. Imports land as drafts and become evidence only when a person approves each row. - Text pulled out of a PDF arrives in the order the document's structure gives, not the order the eye follows: the W3C technique on PDF reading order notes that if a two column document is not properly tagged a reader goes straight across both columns and treats them as one, so an untagged two column CV extracts as interleaved half sentences that look like a typing error rather than a parsing failure. Show the extraction for approval and mark the pages where the order looks wrong. - A single match percentage is the number everybody wants and the one thing here that cannot be honest, because it implies knowledge of a process you cannot observe and it hides which specific requirement has nothing behind it. Show the three states per requirement with the evidence attached, and leave the summarising to the person applying. - A model asked to make a bullet sound stronger will quietly add 'by 40 percent' because that is what strong bullets look like, and the invented figure then gets defended in an interview by somebody who believes they wrote it. Compare the rephrasing against the source row, block any new number, date or proper noun, and show the difference before saving. - A tracker where the follow up lives in a free text note means the follow up does not happen, and the whole reason to keep a tracker was the fortnight between an interview and the silence. Make the next action date a column with a due view, and make an application without one visible as an omission.
# Delivery milestones · Careerflow Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · An evidence bank you actually wrote Every fact about your career exists once, dated, with a note on where it came from. ### Steps 1. Make approval a gate, not a label Unapproved rows are invisible to every document query. Files: `src/evidence.ts`, `tests/approval-gate.test.ts` 2. Require a source note on every row One sentence about how you know it, which is also what you will say in the interview. Files: `src/db/schema.ts` 3. Detect duplicate roles on title and dates The same job entered twice produces two truths. Files: `src/evidence/dedupe.ts` ### Done when - [ ] Unapproved rows cannot enter a document - [ ] Every row has dates and a source note - [ ] Duplicate roles are offered as a merge - [ ] Previous versions of an edited row stay visible ### Watch out - Letting a resume import become evidence directly means every later document is tailored from an approximate parse of your own history, complete with a metric that was mangled and a date that slipped, and you will not notice because it looks like something you wrote. Imports land as drafts and become evidence only when a person approves each row. ## M2 · Extracted text is a suggestion Nothing imported becomes data until a person has seen what came out. ### Steps 1. Show extraction beside the source Two panes, and the failure becomes obvious in a second. Files: `src/imports/review.tsx` 2. Warn on likely column interleaving Detect short alternating fragments and say which page. Files: `src/imports/extract.ts`, `tests/two-column.test.ts` 3. Record what was kept and what was dropped So a bad import can be understood later rather than repeated. Files: `src/imports/approve.ts` ### Done when - [ ] Extraction is always reviewed before storage - [ ] A two column fixture raises a warning with the page - [ ] An empty extraction is reported, not stored - [ ] Approval records kept and discarded lines ### Watch out - Text pulled out of a PDF arrives in the order the document's structure gives, not the order the eye follows: the W3C technique on PDF reading order notes that if a two column document is not properly tagged a reader goes straight across both columns and treats them as one, so an untagged two column CV extracts as interleaved half sentences that look like a typing error rather than a parsing failure. Show the extraction for approval and mark the pages where the order looks wrong. ## M3 · Coverage, not a score A role shows which requirements you can evidence and which you cannot. ### Steps 1. Make requirements editable rows Extraction proposes; the person applying decides. Files: `src/requirements.ts` 2. Link evidence per requirement with a strength Strength is a judgement, recorded as one. Files: `src/coverage.ts` 3. Refuse to compute an overall number There is no function that does it, not a setting that hides it. Files: `src/coverage.ts`, `tests/no-score.test.ts` ### Done when - [ ] No overall percentage exists anywhere - [ ] Every requirement shows its state and evidence - [ ] Requirements can be edited after extraction - [ ] Evidence links resolve from a requirement ### Watch out - A single match percentage is the number everybody wants and the one thing here that cannot be honest, because it implies knowledge of a process you cannot observe and it hides which specific requirement has nothing behind it. Show the three states per requirement with the evidence attached, and leave the summarising to the person applying. ## M4 · A document assembled from approved rows Every rendered line traces to an evidence row and adds nothing to it. ### Steps 1. Block new numbers and proper nouns in rephrasing A token level comparison against the source row catches the common cases. Files: `src/rephrase/guard.ts`, `tests/no-invention.test.ts` 2. Show the difference before saving The person approves the change, not the idea of the change. Files: `src/rephrase/review.tsx` 3. Render from structured data only So what you send is what the data says, in an order you chose. Files: `src/render/document.ts` ### Done when - [ ] Lines without evidence links cannot render - [ ] Invented numbers are blocked before saving - [ ] Differences are shown and can be rejected - [ ] Evidence ids can be listed per rendered line ### Watch out - A model asked to make a bullet sound stronger will quietly add 'by 40 percent' because that is what strong bullets look like, and the invented figure then gets defended in an interview by somebody who believes they wrote it. Compare the rephrasing against the source row, block any new number, date or proper noun, and show the difference before saving. ## M5 · The tracker, the export and the day you stop (production only) Follow ups happen, everything leaves in an open format, and it all runs without a model. ### Steps 1. Make the next action a date column Not a note, because notes are not queryable and do not nag. Files: `src/applications.ts` 2. Keep every feature working without a key The model is an improvement, never a dependency. Files: `src/model/optional.ts` 3. Delete documents with the application Rendered CVs on disk outlive the row that explains them. Files: `src/delete.ts`, `tests/delete-documents.test.ts` ### Done when - [ ] The due view is correct across a zone boundary - [ ] Everything except rephrasing works with no key - [ ] Export and re-import round trip - [ ] Deleting an application removes its documents ### Watch out - A tracker where the follow up lives in a free text note means the follow up does not happen, and the whole reason to keep a tracker was the fortnight between an interview and the silence. Make the next action date a column with a due view, and make an application without one visible as an omission.
# Operations · Careerflow ## Backup The database, the documents directory and the latest export together. This is personal data about you and about the people you spoke to, so encrypt the backup and keep it somewhere you would keep your tax records. ## Restore Restore all three, then confirm that a rendered document still resolves every evidence id and that the due view shows the follow ups you expected. Missing evidence links mean the documents directory came from a different backup than the database. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Applications with no next action date, evidence rows edited since a document was rendered from them, and rephrasings blocked by the guard. The middle one is how you find a CV in the wild that no longer matches what you would say. ## Incident checklist The incident is a claim on a sent document that you cannot support. The evidence links exist so that you can find out in twenty seconds which row it came from and whether you ever approved it. 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 - [ ] Unapproved evidence is confirmed unusable in documents - [ ] A two column import is confirmed to warn - [ ] No overall score is confirmed to exist - [ ] Invented numbers are confirmed blocked in rephrasing - [ ] Everything except rephrasing is confirmed to work without a key ## Launch constraint Do not market omitted Careerflow 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. Evidence, roles, variants and applications. DATABASE_PATH=./data/career.db # Required. Rendered CVs and letters. Deleted with their application. DOCUMENTS_DIR=./data/documents # Required. Loopback. This folder holds your salary history and your rejections. BIND_ADDRESS=127.0.0.1 # Required. The due view depends on it, and follow ups are the point of the tracker. USER_TIMEZONE=Europe/London # Required. JSON and CSV exports, which are how you leave. EXPORT_DIR=./exports # Required. The only permitted mode. There is no generate mode to switch to. MODEL_MODE=rephrase-only # Optional · secret. Optional. Unset disables rephrasing and nothing else. OPENAI_API_KEY=sk-...redacted
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for Careerflow 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 tooling, templates, coaching content, AI workflows, and hosted 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
Careerflow pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| basic | $0/user | $0/user | 1 resume; other resume, LinkedIn, tracker and AI tools are labeled limited without numeric caps. |
| premium | $23.99/user | $14.41/user | Unlimited AI resumes, ATS checks and the paid core toolkit. |
| premium plus | $44.99/user | $24.99/user | Premium features plus mock-interview and interview-analysis tools. |
free tier1 resume; other free tools are described as limited, with no numeric cap published
billingweekly + monthly + quarterly + annual; recurring
hidden costsThe cheapest headline annual equivalents require full-year prepayment; weekly and quarterly options cost materially more per month.
verified 2026-08-14 · source ↗
Vibecode Careerflow
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal Careerflow replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does Careerflow cost?
Careerflow costs about $23.99/month (Premium, checked 2026-07-31), which is $287.88 per year. That's what you save by replacing it with one prompt.
What do I lose by replacing Careerflow?
Honestly: browser tooling, templates, coaching content, AI workflows, and hosted 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 Careerflow?
Yes: Reactive Resume (Application board, files, contacts, follow-ups and resume tailoring; LinkedIn critique remains a human chore.) JobSync (Applications, tasks, activity logs and tailored documents in one local-first job-hunt dashboard.) The prompt is for when you want it exactly your way.