Vibecode Fitbod
track this build4 phases, 12 steps, beginner friendly0%The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Fitbod, build moderate workout plans from user-selected equipment and recovery notes. The hard boundary is exercise library, adaptive programming, mobile capture, wearables, and coaching models, plus hardware data, content, coaching, and trust.
You are building a lean indie version of Fitbod. 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 ===== # Fitbod · indie build A training planner whose interesting logic is progression under uncertainty: deciding what to lift next from what was actually lifted, tracking which muscle groups have recovered, and doing both inside caps that make a bug an inconvenience rather than an injury. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with a local application and SQLite | training logs are health data and belong on your own machine | | Progression | Rules over recent performance with hard caps on rate of increase | the caps are what make a logic error harmless | | Recovery | Per muscle group from volume and elapsed time | the useful output is what to train today, which depends on what you trained | | Equipment | A declared inventory constraining every suggestion | a plan requiring a machine you do not have is not a plan | | Safety | Pain and failure handling evaluated before any progression | progression must never be able to run through a pain report | ## 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 local application. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Your caps and your blocked movement list** · free - Why: Both are enforced as constants before any progression runs, and deciding them after the logic exists means fitting them to the logic rather than to safety. - Get it: Write down the maximum weekly load increase, the deload trigger, and which movements you will not program without stated coaching. - Verify: All three are written down and appear as constants in the safety module - [ ] **Your actual equipment inventory** · free - Why: Every generated plan is constrained by it, and the smallest available weight increment determines whether progression is even possible for a given lift. - Get it: List what you have including the real weight increments, which for dumbbells is often much coarser than for a barbell. - Verify: The inventory includes the smallest increment per equipment type - [ ] **Eight weeks of your own training** · free, and it takes eight weeks - Why: Progression behaviour, recovery estimates and the failure and deload paths only become observable across weeks of real sessions with real missed reps. - Get it: Log honestly, including the sessions that went badly, since those are the ones the logic is being tested on. - Verify: Eight weeks exist including at least three failed sessions - [ ] **Where you will send someone reporting pain** · free - Why: The build stops and says to see someone qualified, and a message with nothing behind it is not much better than a suggested substitution. - Get it: Write the message and decide what it points at, whether that is a professional body's directory or a specific instruction. - Verify: A written message exists and names a concrete next step ## 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: - Injury, form or technique assessment. - Nutrition and body composition. - Competitive lifting programmes. - Video form checking. - exercise library, adaptive programming, mobile capture, wearables, and coaching models - wearable hardware data - coach network - clinical validation - large content library and social network If one of those is essential to you, that is the reason to keep paying for Fitbod, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Fitbod 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 strength training planner that adapts to what you actually lifted to replace Fitbod. 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 This tool tells a person to lift a weight, and a wrong instruction here injures someone. It is not a physiotherapist, a coach or a diagnostic tool, and it must not behave as though it might be. Concretely: it never programs through reported pain, it never increases load after a session the user marked as painful or failed, and it caps how fast load can rise regardless of what the progression logic computes. Where a user reports pain it stops programming that movement and says to see someone qualified, rather than suggesting a modification, because suggesting modifications for an undiagnosed pain is exactly the thing an application cannot do responsibly. The build also declines to program the lifts where a technical failure is most dangerous for an unsupervised beginner unless the user states they have been coached, since a barbell moving badly over someone's neck is a different category of risk from a set of dumbbell presses. None of this makes the tool useless. Progressive overload with sensible caps, honest recovery tracking and a plan that responds to real performance is genuinely valuable, and it is what follows. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a local application and SQLite · training logs are health data and belong on your own machine - Progression: Rules over recent performance with hard caps on rate of increase · the caps are what make a logic error harmless - Recovery: Per muscle group from volume and elapsed time · the useful output is what to train today, which depends on what you trained - Equipment: A declared inventory constraining every suggestion · a plan requiring a machine you do not have is not a plan - Safety: Pain and failure handling evaluated before any progression · progression must never be able to run through a pain report ### Data model (create this before Phase 1) - exercises(id, name, primary_muscles_json, secondary_muscles_json, equipment_required, technical_risk, requires_coaching) - equipment(id, kind, available, weight_increments_json) · what you actually have, including the smallest increment - sessions(id, planned_for, completed_at, perceived_effort, notes) - sets(id, session_id, exercise_id, ordinal, weight, reps, completed_reps, rpe, failed, pain_reported) - progressions(exercise_id, current_working_weight, last_increase_at, consecutive_successes, capped_by) - recovery(muscle, last_volume_at, volume_units, estimated_ready_at) - blocks(exercise_id, reason, from_at, cleared_at) · a movement stopped by pain, cleared only deliberately - A set records both prescribed and completed repetitions, because the difference between them is the entire input to progression. ### Phase 1 · Logging a session faster than resting between sets Build: Log sets with weight, prescribed and completed repetitions, and an effort rating. Default to the prescribed values so a completed set is one action. Work with one hand and a locked screen. Trap: A logging flow requiring several taps per set gets abandoned mid-session, and a partially logged session is worse than an unlogged one because progression will read it as a failure and reduce the next prescription. Default to prescribed values, make completion one action, and mark abandoned sessions rather than treating them as data. Done when: Logging a completed set as prescribed is one action, timed while actually training. A partially logged session is marked incomplete rather than read as failure. Entry works one-handed. Correcting a logged set afterwards is possible without re-entering the session. Do not build yet: No progression, recovery or planning. ### Phase 2 · Progression with a ceiling on how fast Build: Increase working weight after consecutive successful sessions, in increments your actual equipment supports. Cap the rate of increase per week regardless of what the rule computes. Reduce after repeated failure rather than holding. Trap: Progression rules derived from repetition performance can compound quickly and prescribe a weight the person has never approached, and by the time it is obviously wrong they are already under the bar. Cap the weekly increase as a hard constant so a logic error produces a slightly wrong prescription rather than a dangerous one. Done when: Load increases only after the required consecutive successes and never exceeds the weekly cap, verified by property testing across many session histories. Increments match the equipment's actual smallest step. Repeated failure reduces load rather than holding it indefinitely. The cap cannot be raised at runtime. Do not build yet: No recovery, planning or safety handling yet. ### Phase 3 · Pain stops the movement, not the plan Build: Record pain per set. A pain report blocks that exercise from future planning, stops any progression for it, and shows a message to see someone qualified. Clearing a block is a deliberate action with a stated waiting period. Trap: Offering a substitute exercise for a painful movement looks helpful and is the tool diagnosing something it cannot see, since the substitution depends entirely on what is actually wrong. Block the movement, say why, and suggest seeing someone rather than suggesting an alternative loaded exercise. Done when: A pain report blocks the exercise from all future planning and halts its progression, tested end to end. No alternative loaded exercise is suggested for a blocked movement, verified by inspecting the output. Clearing a block requires a deliberate action after a stated period. Progression evaluates blocks before anything else, verified in the code path. Do not build yet: No recovery model or planning yet. ### Phase 4 · Knowing what is recovered Build: Track volume per muscle group and estimate readiness from volume and elapsed time. Use it to select exercises. Present it as an estimate with its basis rather than as a measured fact. Trap: Presenting a recovery percentage as though it were measured implies a precision that does not exist, since actual recovery depends on sleep, stress, nutrition and things the application cannot see. Show the estimate with what it was computed from, and let the user override it, since they know things the model does not. Done when: Recovery is estimated per muscle group from volume and elapsed time, shown with its basis. The estimate is labelled as an estimate rather than presented as measured. A user can override readiness and the override is respected. Planning avoids groups estimated as unrecovered without refusing to train them if the user insists. Do not build yet: No plan generation or export yet. ### Phase 5 · A plan you can actually perform Build: Generate a session from available equipment, recovery estimates, blocks and a time budget. Exclude movements flagged as requiring coaching unless the user has stated they have it. Export the training log completely. Trap: Generating a plan that ignores the equipment inventory produces sessions requiring a machine the person does not have, and they will substitute something arbitrary, which makes the progression data meaningless from that point on. Constrain generation by the declared inventory, including the smallest weight increment available. Done when: Generated sessions use only declared equipment including real weight increments. Sessions fit the stated time budget, checked against actual session durations. Movements requiring coaching are excluded unless the user has stated they have it. Export contains sessions, sets, progressions and blocks and re-imports identically. Do not build yet: Nothing after this. ### Out of scope (and why) - Any assessment of injury, form or technique, which requires seeing the person and is not something an application does. - Nutrition and body composition, which is a different tool with its own set of safeguards. - Programming for competitive lifting, which needs a coach and a periodisation model well beyond this. - Video form checking, which is offered by some products and which no automated system does reliably enough to rely on. ### README must contain - That the weekly rate of load increase is capped as a constant so a logic error cannot become an injury - That a pain report blocks the movement entirely and suggests seeing someone qualified rather than a substitute - That recovery is an estimate shown with its basis and can be overridden by the user - That plans are constrained by declared equipment including the smallest available increment - That movements flagged as requiring coaching are excluded unless you have stated you have it ===== AGENTS.md ===== # Agent instructions · Fitbod indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with a local application and SQLite, Rules over recent performance with hard caps on rate of increase, Per muscle group from volume and elapsed time, A declared inventory constraining every suggestion, Pain and failure handling evaluated before any progression. 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 - A logging flow requiring several taps per set gets abandoned mid-session, and a partially logged session is worse than an unlogged one because progression will read it as a failure and reduce the next prescription. Default to prescribed values, make completion one action, and mark abandoned sessions rather than treating them as data. - Progression rules derived from repetition performance can compound quickly and prescribe a weight the person has never approached, and by the time it is obviously wrong they are already under the bar. Cap the weekly increase as a hard constant so a logic error produces a slightly wrong prescription rather than a dangerous one. - Offering a substitute exercise for a painful movement looks helpful and is the tool diagnosing something it cannot see, since the substitution depends entirely on what is actually wrong. Block the movement, say why, and suggest seeing someone rather than suggesting an alternative loaded exercise. - Presenting a recovery percentage as though it were measured implies a precision that does not exist, since actual recovery depends on sleep, stress, nutrition and things the application cannot see. Show the estimate with what it was computed from, and let the user override it, since they know things the model does not. ===== BUILD_PLAN.md ===== # Build plan · Fitbod A training planner whose interesting logic is progression under uncertainty: deciding what to lift next from what was actually lifted, tracking which muscle groups have recovered, and doing both inside caps that make a bug an inconvenience rather than an injury. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Logging a session faster than resting between sets Recording a set takes a couple of seconds while you are training. ### Steps 1. Default to the prescribed values Most sets go as planned. One action for the common case. Files: `src/logging.ts` 2. Record prescribed and completed separately Their difference is the whole input to progression. Files: `src/sets.ts` 3. Mark incomplete sessions Never read an abandoned session as failure. Files: `src/sessions.ts` ### Done when - [ ] A completed set logs in one action - [ ] Incomplete sessions are marked, not read as failure - [ ] Entry works one-handed - [ ] Corrections are possible afterwards ### Watch out - A logging flow requiring several taps per set gets abandoned mid-session, and a partially logged session is worse than an unlogged one because progression will read it as a failure and reduce the next prescription. Default to prescribed values, make completion one action, and mark abandoned sessions rather than treating them as data. ## Phase 2 · Progression with a ceiling on how fast Load increases when earned and never faster than the cap. ### Steps 1. Increase only on earned successes Consecutive, recorded, in real increments your equipment has. Files: `src/progression.ts` 2. Cap the weekly rate as a constant So a logic error is an inconvenience rather than an injury. Files: `src/caps.ts`, `src/caps.test.ts` 3. Deload after repeated failure Holding at a weight that keeps failing is not a plan. Files: `src/progression.ts` ### Done when - [ ] Increases require earned consecutive successes - [ ] Property testing finds no history exceeding the cap - [ ] Increments match real equipment steps - [ ] Repeated failure reduces load ### Watch out - Progression rules derived from repetition performance can compound quickly and prescribe a weight the person has never approached, and by the time it is obviously wrong they are already under the bar. Cap the weekly increase as a hard constant so a logic error produces a slightly wrong prescription rather than a dangerous one. ## Phase 3 · Pain stops the movement, not the plan A reported pain removes that movement and does not get worked around. ### Steps 1. Block the movement on pain Not reduce it, not substitute it. Block it. Files: `src/blocks.ts` 2. Suggest a person, not an exercise Substitution requires knowing what is wrong, which the tool does not. Files: `src/blocks.ts` 3. Evaluate blocks first in progression Before any other rule, so nothing can route around them. Files: `src/progression.ts`, `tests/pain-blocks.test.ts` ### Done when - [ ] A pain report blocks the exercise everywhere - [ ] No alternative loaded exercise is suggested - [ ] Clearing requires a deliberate action after a period - [ ] Blocks are evaluated before other progression rules ### Watch out - Offering a substitute exercise for a painful movement looks helpful and is the tool diagnosing something it cannot see, since the substitution depends entirely on what is actually wrong. Block the movement, say why, and suggest seeing someone rather than suggesting an alternative loaded exercise. ## Phase 4 · Knowing what is recovered Today's plan avoids what you trained hard two days ago. ### Steps 1. Estimate from volume and elapsed time Simple, explicable, and honest about being an estimate. Files: `src/recovery.ts` 2. Show the basis alongside the number A percentage with no basis reads as a measurement. Files: `src/recovery-view.ts` 3. Let the user override They know about their sleep and their week. The model does not. Files: `src/recovery.ts` ### Done when - [ ] Recovery is estimated with its basis shown - [ ] It is labelled an estimate, not a measurement - [ ] User overrides are respected - [ ] Planning avoids unrecovered groups without forbidding them ### Watch out - Presenting a recovery percentage as though it were measured implies a precision that does not exist, since actual recovery depends on sleep, stress, nutrition and things the application cannot see. Show the estimate with what it was computed from, and let the user override it, since they know things the model does not. ## Not in this build - Injury, form or technique assessment. - Nutrition and body composition. - Competitive lifting programmes. - Video form checking. ## After v1, if you want it - Exercise substitution within the same movement pattern for equipment reasons, which is a different question from pain - Session history charts, which are motivating and honest since they show what happened - Warm-up set generation, which is tedious to plan and easy to derive ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Training and pain reports. Health data, so treat backups accordingly. DB_PATH=~/.local/share/training/log.db # Required. A hard cap in the progression path. Not raisable at runtime. MAX_WEEKLY_INCREASE_PERCENT=5 # Required. Consecutive failures before load is reduced rather than held. DELOAD_AFTER_FAILURES=2 # Required. Minimum before a pain block can be cleared, and only deliberately. PAIN_BLOCK_MIN_DAYS=14 # Required. Present as a constant. Substitution requires knowing what is wrong. SUGGEST_SUBSTITUTES_ON_PAIN=never # Required. Plans are generated to fit it, checked against real durations. SESSION_TIME_BUDGET_MINUTES=50
You are building a lean indie version of Fitbod. 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 ===== # Fitbod · indie build A training planner whose interesting logic is progression under uncertainty: deciding what to lift next from what was actually lifted, tracking which muscle groups have recovered, and doing both inside caps that make a bug an inconvenience rather than an injury. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with a local application and SQLite | training logs are health data and belong on your own machine | | Progression | Rules over recent performance with hard caps on rate of increase | the caps are what make a logic error harmless | | Recovery | Per muscle group from volume and elapsed time | the useful output is what to train today, which depends on what you trained | | Equipment | A declared inventory constraining every suggestion | a plan requiring a machine you do not have is not a plan | | Safety | Pain and failure handling evaluated before any progression | progression must never be able to run through a pain report | ## 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 local application. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Your caps and your blocked movement list** · free - Why: Both are enforced as constants before any progression runs, and deciding them after the logic exists means fitting them to the logic rather than to safety. - Get it: Write down the maximum weekly load increase, the deload trigger, and which movements you will not program without stated coaching. - Verify: All three are written down and appear as constants in the safety module - [ ] **Your actual equipment inventory** · free - Why: Every generated plan is constrained by it, and the smallest available weight increment determines whether progression is even possible for a given lift. - Get it: List what you have including the real weight increments, which for dumbbells is often much coarser than for a barbell. - Verify: The inventory includes the smallest increment per equipment type - [ ] **Eight weeks of your own training** · free, and it takes eight weeks - Why: Progression behaviour, recovery estimates and the failure and deload paths only become observable across weeks of real sessions with real missed reps. - Get it: Log honestly, including the sessions that went badly, since those are the ones the logic is being tested on. - Verify: Eight weeks exist including at least three failed sessions - [ ] **Where you will send someone reporting pain** · free - Why: The build stops and says to see someone qualified, and a message with nothing behind it is not much better than a suggested substitution. - Get it: Write the message and decide what it points at, whether that is a professional body's directory or a specific instruction. - Verify: A written message exists and names a concrete next step ## 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: - Injury, form or technique assessment. - Nutrition and body composition. - Competitive lifting programmes. - Video form checking. - exercise library, adaptive programming, mobile capture, wearables, and coaching models - wearable hardware data - coach network - clinical validation - large content library and social network If one of those is essential to you, that is the reason to keep paying for Fitbod, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Fitbod 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 strength training planner that adapts to what you actually lifted to replace Fitbod. 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 This tool tells a person to lift a weight, and a wrong instruction here injures someone. It is not a physiotherapist, a coach or a diagnostic tool, and it must not behave as though it might be. Concretely: it never programs through reported pain, it never increases load after a session the user marked as painful or failed, and it caps how fast load can rise regardless of what the progression logic computes. Where a user reports pain it stops programming that movement and says to see someone qualified, rather than suggesting a modification, because suggesting modifications for an undiagnosed pain is exactly the thing an application cannot do responsibly. The build also declines to program the lifts where a technical failure is most dangerous for an unsupervised beginner unless the user states they have been coached, since a barbell moving badly over someone's neck is a different category of risk from a set of dumbbell presses. None of this makes the tool useless. Progressive overload with sensible caps, honest recovery tracking and a plan that responds to real performance is genuinely valuable, and it is what follows. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a local application and SQLite · training logs are health data and belong on your own machine - Progression: Rules over recent performance with hard caps on rate of increase · the caps are what make a logic error harmless - Recovery: Per muscle group from volume and elapsed time · the useful output is what to train today, which depends on what you trained - Equipment: A declared inventory constraining every suggestion · a plan requiring a machine you do not have is not a plan - Safety: Pain and failure handling evaluated before any progression · progression must never be able to run through a pain report ### Data model (create this before Phase 1) - exercises(id, name, primary_muscles_json, secondary_muscles_json, equipment_required, technical_risk, requires_coaching) - equipment(id, kind, available, weight_increments_json) · what you actually have, including the smallest increment - sessions(id, planned_for, completed_at, perceived_effort, notes) - sets(id, session_id, exercise_id, ordinal, weight, reps, completed_reps, rpe, failed, pain_reported) - progressions(exercise_id, current_working_weight, last_increase_at, consecutive_successes, capped_by) - recovery(muscle, last_volume_at, volume_units, estimated_ready_at) - blocks(exercise_id, reason, from_at, cleared_at) · a movement stopped by pain, cleared only deliberately - A set records both prescribed and completed repetitions, because the difference between them is the entire input to progression. ### Phase 1 · Logging a session faster than resting between sets Build: Log sets with weight, prescribed and completed repetitions, and an effort rating. Default to the prescribed values so a completed set is one action. Work with one hand and a locked screen. Trap: A logging flow requiring several taps per set gets abandoned mid-session, and a partially logged session is worse than an unlogged one because progression will read it as a failure and reduce the next prescription. Default to prescribed values, make completion one action, and mark abandoned sessions rather than treating them as data. Done when: Logging a completed set as prescribed is one action, timed while actually training. A partially logged session is marked incomplete rather than read as failure. Entry works one-handed. Correcting a logged set afterwards is possible without re-entering the session. Do not build yet: No progression, recovery or planning. ### Phase 2 · Progression with a ceiling on how fast Build: Increase working weight after consecutive successful sessions, in increments your actual equipment supports. Cap the rate of increase per week regardless of what the rule computes. Reduce after repeated failure rather than holding. Trap: Progression rules derived from repetition performance can compound quickly and prescribe a weight the person has never approached, and by the time it is obviously wrong they are already under the bar. Cap the weekly increase as a hard constant so a logic error produces a slightly wrong prescription rather than a dangerous one. Done when: Load increases only after the required consecutive successes and never exceeds the weekly cap, verified by property testing across many session histories. Increments match the equipment's actual smallest step. Repeated failure reduces load rather than holding it indefinitely. The cap cannot be raised at runtime. Do not build yet: No recovery, planning or safety handling yet. ### Phase 3 · Pain stops the movement, not the plan Build: Record pain per set. A pain report blocks that exercise from future planning, stops any progression for it, and shows a message to see someone qualified. Clearing a block is a deliberate action with a stated waiting period. Trap: Offering a substitute exercise for a painful movement looks helpful and is the tool diagnosing something it cannot see, since the substitution depends entirely on what is actually wrong. Block the movement, say why, and suggest seeing someone rather than suggesting an alternative loaded exercise. Done when: A pain report blocks the exercise from all future planning and halts its progression, tested end to end. No alternative loaded exercise is suggested for a blocked movement, verified by inspecting the output. Clearing a block requires a deliberate action after a stated period. Progression evaluates blocks before anything else, verified in the code path. Do not build yet: No recovery model or planning yet. ### Phase 4 · Knowing what is recovered Build: Track volume per muscle group and estimate readiness from volume and elapsed time. Use it to select exercises. Present it as an estimate with its basis rather than as a measured fact. Trap: Presenting a recovery percentage as though it were measured implies a precision that does not exist, since actual recovery depends on sleep, stress, nutrition and things the application cannot see. Show the estimate with what it was computed from, and let the user override it, since they know things the model does not. Done when: Recovery is estimated per muscle group from volume and elapsed time, shown with its basis. The estimate is labelled as an estimate rather than presented as measured. A user can override readiness and the override is respected. Planning avoids groups estimated as unrecovered without refusing to train them if the user insists. Do not build yet: No plan generation or export yet. ### Phase 5 · A plan you can actually perform Build: Generate a session from available equipment, recovery estimates, blocks and a time budget. Exclude movements flagged as requiring coaching unless the user has stated they have it. Export the training log completely. Trap: Generating a plan that ignores the equipment inventory produces sessions requiring a machine the person does not have, and they will substitute something arbitrary, which makes the progression data meaningless from that point on. Constrain generation by the declared inventory, including the smallest weight increment available. Done when: Generated sessions use only declared equipment including real weight increments. Sessions fit the stated time budget, checked against actual session durations. Movements requiring coaching are excluded unless the user has stated they have it. Export contains sessions, sets, progressions and blocks and re-imports identically. Do not build yet: Nothing after this. ### Out of scope (and why) - Any assessment of injury, form or technique, which requires seeing the person and is not something an application does. - Nutrition and body composition, which is a different tool with its own set of safeguards. - Programming for competitive lifting, which needs a coach and a periodisation model well beyond this. - Video form checking, which is offered by some products and which no automated system does reliably enough to rely on. ### README must contain - That the weekly rate of load increase is capped as a constant so a logic error cannot become an injury - That a pain report blocks the movement entirely and suggests seeing someone qualified rather than a substitute - That recovery is an estimate shown with its basis and can be overridden by the user - That plans are constrained by declared equipment including the smallest available increment - That movements flagged as requiring coaching are excluded unless you have stated you have it ===== AGENTS.md ===== # Agent instructions · Fitbod indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with a local application and SQLite, Rules over recent performance with hard caps on rate of increase, Per muscle group from volume and elapsed time, A declared inventory constraining every suggestion, Pain and failure handling evaluated before any progression. 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 - A logging flow requiring several taps per set gets abandoned mid-session, and a partially logged session is worse than an unlogged one because progression will read it as a failure and reduce the next prescription. Default to prescribed values, make completion one action, and mark abandoned sessions rather than treating them as data. - Progression rules derived from repetition performance can compound quickly and prescribe a weight the person has never approached, and by the time it is obviously wrong they are already under the bar. Cap the weekly increase as a hard constant so a logic error produces a slightly wrong prescription rather than a dangerous one. - Offering a substitute exercise for a painful movement looks helpful and is the tool diagnosing something it cannot see, since the substitution depends entirely on what is actually wrong. Block the movement, say why, and suggest seeing someone rather than suggesting an alternative loaded exercise. - Presenting a recovery percentage as though it were measured implies a precision that does not exist, since actual recovery depends on sleep, stress, nutrition and things the application cannot see. Show the estimate with what it was computed from, and let the user override it, since they know things the model does not. ===== BUILD_PLAN.md ===== # Build plan · Fitbod A training planner whose interesting logic is progression under uncertainty: deciding what to lift next from what was actually lifted, tracking which muscle groups have recovered, and doing both inside caps that make a bug an inconvenience rather than an injury. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Logging a session faster than resting between sets Recording a set takes a couple of seconds while you are training. ### Steps 1. Default to the prescribed values Most sets go as planned. One action for the common case. Files: `src/logging.ts` 2. Record prescribed and completed separately Their difference is the whole input to progression. Files: `src/sets.ts` 3. Mark incomplete sessions Never read an abandoned session as failure. Files: `src/sessions.ts` ### Done when - [ ] A completed set logs in one action - [ ] Incomplete sessions are marked, not read as failure - [ ] Entry works one-handed - [ ] Corrections are possible afterwards ### Watch out - A logging flow requiring several taps per set gets abandoned mid-session, and a partially logged session is worse than an unlogged one because progression will read it as a failure and reduce the next prescription. Default to prescribed values, make completion one action, and mark abandoned sessions rather than treating them as data. ## Phase 2 · Progression with a ceiling on how fast Load increases when earned and never faster than the cap. ### Steps 1. Increase only on earned successes Consecutive, recorded, in real increments your equipment has. Files: `src/progression.ts` 2. Cap the weekly rate as a constant So a logic error is an inconvenience rather than an injury. Files: `src/caps.ts`, `src/caps.test.ts` 3. Deload after repeated failure Holding at a weight that keeps failing is not a plan. Files: `src/progression.ts` ### Done when - [ ] Increases require earned consecutive successes - [ ] Property testing finds no history exceeding the cap - [ ] Increments match real equipment steps - [ ] Repeated failure reduces load ### Watch out - Progression rules derived from repetition performance can compound quickly and prescribe a weight the person has never approached, and by the time it is obviously wrong they are already under the bar. Cap the weekly increase as a hard constant so a logic error produces a slightly wrong prescription rather than a dangerous one. ## Phase 3 · Pain stops the movement, not the plan A reported pain removes that movement and does not get worked around. ### Steps 1. Block the movement on pain Not reduce it, not substitute it. Block it. Files: `src/blocks.ts` 2. Suggest a person, not an exercise Substitution requires knowing what is wrong, which the tool does not. Files: `src/blocks.ts` 3. Evaluate blocks first in progression Before any other rule, so nothing can route around them. Files: `src/progression.ts`, `tests/pain-blocks.test.ts` ### Done when - [ ] A pain report blocks the exercise everywhere - [ ] No alternative loaded exercise is suggested - [ ] Clearing requires a deliberate action after a period - [ ] Blocks are evaluated before other progression rules ### Watch out - Offering a substitute exercise for a painful movement looks helpful and is the tool diagnosing something it cannot see, since the substitution depends entirely on what is actually wrong. Block the movement, say why, and suggest seeing someone rather than suggesting an alternative loaded exercise. ## Phase 4 · Knowing what is recovered Today's plan avoids what you trained hard two days ago. ### Steps 1. Estimate from volume and elapsed time Simple, explicable, and honest about being an estimate. Files: `src/recovery.ts` 2. Show the basis alongside the number A percentage with no basis reads as a measurement. Files: `src/recovery-view.ts` 3. Let the user override They know about their sleep and their week. The model does not. Files: `src/recovery.ts` ### Done when - [ ] Recovery is estimated with its basis shown - [ ] It is labelled an estimate, not a measurement - [ ] User overrides are respected - [ ] Planning avoids unrecovered groups without forbidding them ### Watch out - Presenting a recovery percentage as though it were measured implies a precision that does not exist, since actual recovery depends on sleep, stress, nutrition and things the application cannot see. Show the estimate with what it was computed from, and let the user override it, since they know things the model does not. ## Not in this build - Injury, form or technique assessment. - Nutrition and body composition. - Competitive lifting programmes. - Video form checking. ## After v1, if you want it - Exercise substitution within the same movement pattern for equipment reasons, which is a different question from pain - Session history charts, which are motivating and honest since they show what happened - Warm-up set generation, which is tedious to plan and easy to derive ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Training and pain reports. Health data, so treat backups accordingly. DB_PATH=~/.local/share/training/log.db # Required. A hard cap in the progression path. Not raisable at runtime. MAX_WEEKLY_INCREASE_PERCENT=5 # Required. Consecutive failures before load is reduced rather than held. DELOAD_AFTER_FAILURES=2 # Required. Minimum before a pain block can be cleared, and only deliberately. PAIN_BLOCK_MIN_DAYS=14 # Required. Present as a constant. Substitution requires knowing what is wrong. SUGGEST_SUBSTITUTES_ON_PAIN=never # Required. Plans are generated to fit it, checked against real durations. SESSION_TIME_BUDGET_MINUTES=50
You are building a production product version of Fitbod. 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 ===== # Fitbod · product brief ## Problem The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Fitbod, build moderate workout plans from user-selected equipment and recovery notes. The hard boundary is exercise library, adaptive programming, mobile capture, wearables, and coaching models, plus hardware data, content, coaching, and trust. ## Product outcome A planner that adapts to what you actually lifted, stops entirely when you report pain, and cannot prescribe a jump large enough to hurt you even if its logic is wrong. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - iOS or Android device - optional HealthKit or Health Connect permission - local storage ## Explicit non-goals for v1 - Injury, form or technique assessment. - Nutrition and body composition. - Competitive lifting programmes. - Video form checking. - exercise library, adaptive programming, mobile capture, wearables, and coaching models - wearable hardware data - coach network - clinical validation - large content library and social network ## Success criteria - Property testing is confirmed to find no history exceeding the weekly cap - A pain report is confirmed to block the movement everywhere - No substitute exercise is confirmed to be suggested for a blocked movement - Plans are confirmed to use only declared equipment and increments - Coached-only movements are confirmed gated ===== BRIEF.md ===== # Build brief · Fitbod 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 strength training planner that adapts to what you actually lifted to replace Fitbod. 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 This tool tells a person to lift a weight, and a wrong instruction here injures someone. It is not a physiotherapist, a coach or a diagnostic tool, and it must not behave as though it might be. Concretely: it never programs through reported pain, it never increases load after a session the user marked as painful or failed, and it caps how fast load can rise regardless of what the progression logic computes. Where a user reports pain it stops programming that movement and says to see someone qualified, rather than suggesting a modification, because suggesting modifications for an undiagnosed pain is exactly the thing an application cannot do responsibly. The build also declines to program the lifts where a technical failure is most dangerous for an unsupervised beginner unless the user states they have been coached, since a barbell moving badly over someone's neck is a different category of risk from a set of dumbbell presses. None of this makes the tool useless. Progressive overload with sensible caps, honest recovery tracking and a plan that responds to real performance is genuinely valuable, and it is what follows. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a local application and SQLite · training logs are health data and belong on your own machine - Progression: Rules over recent performance with hard caps on rate of increase · the caps are what make a logic error harmless - Recovery: Per muscle group from volume and elapsed time · the useful output is what to train today, which depends on what you trained - Equipment: A declared inventory constraining every suggestion · a plan requiring a machine you do not have is not a plan - Safety: Pain and failure handling evaluated before any progression · progression must never be able to run through a pain report ### Data model (create this before Phase 1) - exercises(id, name, primary_muscles_json, secondary_muscles_json, equipment_required, technical_risk, requires_coaching) - equipment(id, kind, available, weight_increments_json) · what you actually have, including the smallest increment - sessions(id, planned_for, completed_at, perceived_effort, notes) - sets(id, session_id, exercise_id, ordinal, weight, reps, completed_reps, rpe, failed, pain_reported) - progressions(exercise_id, current_working_weight, last_increase_at, consecutive_successes, capped_by) - recovery(muscle, last_volume_at, volume_units, estimated_ready_at) - blocks(exercise_id, reason, from_at, cleared_at) · a movement stopped by pain, cleared only deliberately - A set records both prescribed and completed repetitions, because the difference between them is the entire input to progression. ### Phase 1 · Logging a session faster than resting between sets Build: Log sets with weight, prescribed and completed repetitions, and an effort rating. Default to the prescribed values so a completed set is one action. Work with one hand and a locked screen. Trap: A logging flow requiring several taps per set gets abandoned mid-session, and a partially logged session is worse than an unlogged one because progression will read it as a failure and reduce the next prescription. Default to prescribed values, make completion one action, and mark abandoned sessions rather than treating them as data. Done when: Logging a completed set as prescribed is one action, timed while actually training. A partially logged session is marked incomplete rather than read as failure. Entry works one-handed. Correcting a logged set afterwards is possible without re-entering the session. Do not build yet: No progression, recovery or planning. ### Phase 2 · Progression with a ceiling on how fast Build: Increase working weight after consecutive successful sessions, in increments your actual equipment supports. Cap the rate of increase per week regardless of what the rule computes. Reduce after repeated failure rather than holding. Trap: Progression rules derived from repetition performance can compound quickly and prescribe a weight the person has never approached, and by the time it is obviously wrong they are already under the bar. Cap the weekly increase as a hard constant so a logic error produces a slightly wrong prescription rather than a dangerous one. Done when: Load increases only after the required consecutive successes and never exceeds the weekly cap, verified by property testing across many session histories. Increments match the equipment's actual smallest step. Repeated failure reduces load rather than holding it indefinitely. The cap cannot be raised at runtime. Do not build yet: No recovery, planning or safety handling yet. ### Phase 3 · Pain stops the movement, not the plan Build: Record pain per set. A pain report blocks that exercise from future planning, stops any progression for it, and shows a message to see someone qualified. Clearing a block is a deliberate action with a stated waiting period. Trap: Offering a substitute exercise for a painful movement looks helpful and is the tool diagnosing something it cannot see, since the substitution depends entirely on what is actually wrong. Block the movement, say why, and suggest seeing someone rather than suggesting an alternative loaded exercise. Done when: A pain report blocks the exercise from all future planning and halts its progression, tested end to end. No alternative loaded exercise is suggested for a blocked movement, verified by inspecting the output. Clearing a block requires a deliberate action after a stated period. Progression evaluates blocks before anything else, verified in the code path. Do not build yet: No recovery model or planning yet. ### Phase 4 · Knowing what is recovered Build: Track volume per muscle group and estimate readiness from volume and elapsed time. Use it to select exercises. Present it as an estimate with its basis rather than as a measured fact. Trap: Presenting a recovery percentage as though it were measured implies a precision that does not exist, since actual recovery depends on sleep, stress, nutrition and things the application cannot see. Show the estimate with what it was computed from, and let the user override it, since they know things the model does not. Done when: Recovery is estimated per muscle group from volume and elapsed time, shown with its basis. The estimate is labelled as an estimate rather than presented as measured. A user can override readiness and the override is respected. Planning avoids groups estimated as unrecovered without refusing to train them if the user insists. Do not build yet: No plan generation or export yet. ### Phase 5 · A plan you can actually perform Build: Generate a session from available equipment, recovery estimates, blocks and a time budget. Exclude movements flagged as requiring coaching unless the user has stated they have it. Export the training log completely. Trap: Generating a plan that ignores the equipment inventory produces sessions requiring a machine the person does not have, and they will substitute something arbitrary, which makes the progression data meaningless from that point on. Constrain generation by the declared inventory, including the smallest weight increment available. Done when: Generated sessions use only declared equipment including real weight increments. Sessions fit the stated time budget, checked against actual session durations. Movements requiring coaching are excluded unless the user has stated they have it. Export contains sessions, sets, progressions and blocks and re-imports identically. Do not build yet: Nothing after this. ### Out of scope (and why) - Any assessment of injury, form or technique, which requires seeing the person and is not something an application does. - Nutrition and body composition, which is a different tool with its own set of safeguards. - Programming for competitive lifting, which needs a coach and a periodisation model well beyond this. - Video form checking, which is offered by some products and which no automated system does reliably enough to rely on. ### README must contain - That the weekly rate of load increase is capped as a constant so a logic error cannot become an injury - That a pain report blocks the movement entirely and suggests seeing someone qualified rather than a substitute - That recovery is an estimate shown with its basis and can be overridden by the user - That plans are constrained by declared equipment including the smallest available increment - That movements flagged as requiring coaching are excluded unless you have stated you have it ===== ARCHITECTURE.md ===== # Architecture · Fitbod ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with a local application and SQLite | training logs are health data and belong on your own machine | | Progression | Rules over recent performance with hard caps on rate of increase | the caps are what make a logic error harmless | | Recovery | Per muscle group from volume and elapsed time | the useful output is what to train today, which depends on what you trained | | Equipment | A declared inventory constraining every suggestion | a plan requiring a machine you do not have is not a plan | | Safety | Pain and failure handling evaluated before any progression | progression must never be able to run through a pain report | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Logging | Fast set entry with prescribed and completed values. | Any interface, provided the common case is one action. | | Progression | Earned increases inside hard caps. | Any rule set, provided the caps are constants evaluated after it. | | Safety | Pain blocks and coached-movement gating. | Not swappable and evaluated first, so nothing routes around it. | | Recovery | Estimates with a visible basis. | Any model, provided it is presented as an estimate and can be overridden. | | Planning | Sessions constrained by equipment and time. | Any generator, provided the equipment inventory constrains it. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DB_PATH` · required · Training and pain reports. Health data, so treat backups accordingly. - `MAX_WEEKLY_INCREASE_PERCENT` · required · A hard cap in the progression path. Not raisable at runtime. - `DELOAD_AFTER_FAILURES` · required · Consecutive failures before load is reduced rather than held. - `PAIN_BLOCK_MIN_DAYS` · required · Minimum before a pain block can be cleared, and only deliberately. - `SUGGEST_SUBSTITUTES_ON_PAIN` · required · Present as a constant. Substitution requires knowing what is wrong. - `SESSION_TIME_BUDGET_MINUTES` · required · Plans are generated to fit it, checked against real durations. ## 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 · Fitbod product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: TypeScript with a local application and SQLite, Rules over recent performance with hard caps on rate of increase, Per muscle group from volume and elapsed time, A declared inventory constraining every suggestion, Pain and failure handling evaluated before any progression. - 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 - A logging flow requiring several taps per set gets abandoned mid-session, and a partially logged session is worse than an unlogged one because progression will read it as a failure and reduce the next prescription. Default to prescribed values, make completion one action, and mark abandoned sessions rather than treating them as data. - Progression rules derived from repetition performance can compound quickly and prescribe a weight the person has never approached, and by the time it is obviously wrong they are already under the bar. Cap the weekly increase as a hard constant so a logic error produces a slightly wrong prescription rather than a dangerous one. - Offering a substitute exercise for a painful movement looks helpful and is the tool diagnosing something it cannot see, since the substitution depends entirely on what is actually wrong. Block the movement, say why, and suggest seeing someone rather than suggesting an alternative loaded exercise. - Presenting a recovery percentage as though it were measured implies a precision that does not exist, since actual recovery depends on sleep, stress, nutrition and things the application cannot see. Show the estimate with what it was computed from, and let the user override it, since they know things the model does not. - Generating a plan that ignores the equipment inventory produces sessions requiring a machine the person does not have, and they will substitute something arbitrary, which makes the progression data meaningless from that point on. Constrain generation by the declared inventory, including the smallest weight increment available. ===== MILESTONES.md ===== # Delivery milestones · Fitbod Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Logging a session faster than resting between sets Recording a set takes a couple of seconds while you are training. ### Steps 1. Default to the prescribed values Most sets go as planned. One action for the common case. Files: `src/logging.ts` 2. Record prescribed and completed separately Their difference is the whole input to progression. Files: `src/sets.ts` 3. Mark incomplete sessions Never read an abandoned session as failure. Files: `src/sessions.ts` ### Done when - [ ] A completed set logs in one action - [ ] Incomplete sessions are marked, not read as failure - [ ] Entry works one-handed - [ ] Corrections are possible afterwards ### Watch out - A logging flow requiring several taps per set gets abandoned mid-session, and a partially logged session is worse than an unlogged one because progression will read it as a failure and reduce the next prescription. Default to prescribed values, make completion one action, and mark abandoned sessions rather than treating them as data. ## M2 · Progression with a ceiling on how fast Load increases when earned and never faster than the cap. ### Steps 1. Increase only on earned successes Consecutive, recorded, in real increments your equipment has. Files: `src/progression.ts` 2. Cap the weekly rate as a constant So a logic error is an inconvenience rather than an injury. Files: `src/caps.ts`, `src/caps.test.ts` 3. Deload after repeated failure Holding at a weight that keeps failing is not a plan. Files: `src/progression.ts` ### Done when - [ ] Increases require earned consecutive successes - [ ] Property testing finds no history exceeding the cap - [ ] Increments match real equipment steps - [ ] Repeated failure reduces load ### Watch out - Progression rules derived from repetition performance can compound quickly and prescribe a weight the person has never approached, and by the time it is obviously wrong they are already under the bar. Cap the weekly increase as a hard constant so a logic error produces a slightly wrong prescription rather than a dangerous one. ## M3 · Pain stops the movement, not the plan A reported pain removes that movement and does not get worked around. ### Steps 1. Block the movement on pain Not reduce it, not substitute it. Block it. Files: `src/blocks.ts` 2. Suggest a person, not an exercise Substitution requires knowing what is wrong, which the tool does not. Files: `src/blocks.ts` 3. Evaluate blocks first in progression Before any other rule, so nothing can route around them. Files: `src/progression.ts`, `tests/pain-blocks.test.ts` ### Done when - [ ] A pain report blocks the exercise everywhere - [ ] No alternative loaded exercise is suggested - [ ] Clearing requires a deliberate action after a period - [ ] Blocks are evaluated before other progression rules ### Watch out - Offering a substitute exercise for a painful movement looks helpful and is the tool diagnosing something it cannot see, since the substitution depends entirely on what is actually wrong. Block the movement, say why, and suggest seeing someone rather than suggesting an alternative loaded exercise. ## M4 · Knowing what is recovered Today's plan avoids what you trained hard two days ago. ### Steps 1. Estimate from volume and elapsed time Simple, explicable, and honest about being an estimate. Files: `src/recovery.ts` 2. Show the basis alongside the number A percentage with no basis reads as a measurement. Files: `src/recovery-view.ts` 3. Let the user override They know about their sleep and their week. The model does not. Files: `src/recovery.ts` ### Done when - [ ] Recovery is estimated with its basis shown - [ ] It is labelled an estimate, not a measurement - [ ] User overrides are respected - [ ] Planning avoids unrecovered groups without forbidding them ### Watch out - Presenting a recovery percentage as though it were measured implies a precision that does not exist, since actual recovery depends on sleep, stress, nutrition and things the application cannot see. Show the estimate with what it was computed from, and let the user override it, since they know things the model does not. ## M5 · A plan you can actually perform (production only) Today's session fits your equipment, your time and your recovery. ### Steps 1. Constrain by declared equipment Including the smallest increment. A plan you cannot load is not a plan. Files: `src/plan.ts` 2. Respect a time budget Measured against real session durations, not estimated ones. Files: `src/plan.ts` 3. Gate coached movements Unless the user says they have been taught. Unsupervised risk differs by lift. Files: `src/plan.ts` ### Done when - [ ] Sessions use only declared equipment and increments - [ ] Sessions fit the stated time budget - [ ] Coached-only movements are gated - [ ] Export round trips the full log ### Watch out - Generating a plan that ignores the equipment inventory produces sessions requiring a machine the person does not have, and they will substitute something arbitrary, which makes the progression data meaningless from that point on. Constrain generation by the declared inventory, including the smallest weight increment available. ===== OPERATIONS.md ===== # Operations · Fitbod ## Backup A training log is years of data and its value grows with length. It is also health data including pain reports, so back it up encrypted and think about where it lives. ## Restore Restore and confirm pain blocks survived as blocks, since a restore that clears them would resume programming a movement someone stopped for a reason. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a personal tool, the number to watch is how often prescriptions hit the cap. Frequently hitting it means the progression rule is computing increases the cap is quietly rescuing you from. ## Incident checklist A prescription the user could not safely attempt is the failure that matters. The cap is the control, and property testing across many histories is what demonstrates it holds rather than the absence of complaints. 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 - [ ] Property testing is confirmed to find no history exceeding the weekly cap - [ ] A pain report is confirmed to block the movement everywhere - [ ] No substitute exercise is confirmed to be suggested for a blocked movement - [ ] Plans are confirmed to use only declared equipment and increments - [ ] Coached-only movements are confirmed gated ## Launch constraint Do not market omitted Fitbod 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. Training and pain reports. Health data, so treat backups accordingly. DB_PATH=~/.local/share/training/log.db # Required. A hard cap in the progression path. Not raisable at runtime. MAX_WEEKLY_INCREASE_PERCENT=5 # Required. Consecutive failures before load is reduced rather than held. DELOAD_AFTER_FAILURES=2 # Required. Minimum before a pain block can be cleared, and only deliberately. PAIN_BLOCK_MIN_DAYS=14 # Required. Present as a constant. Substitution requires knowing what is wrong. SUGGEST_SUBSTITUTES_ON_PAIN=never # Required. Plans are generated to fit it, checked against real durations. SESSION_TIME_BUDGET_MINUTES=50
# Fitbod · indie build A training planner whose interesting logic is progression under uncertainty: deciding what to lift next from what was actually lifted, tracking which muscle groups have recovered, and doing both inside caps that make a bug an inconvenience rather than an injury. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with a local application and SQLite | training logs are health data and belong on your own machine | | Progression | Rules over recent performance with hard caps on rate of increase | the caps are what make a logic error harmless | | Recovery | Per muscle group from volume and elapsed time | the useful output is what to train today, which depends on what you trained | | Equipment | A declared inventory constraining every suggestion | a plan requiring a machine you do not have is not a plan | | Safety | Pain and failure handling evaluated before any progression | progression must never be able to run through a pain report | ## 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 local application. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Your caps and your blocked movement list** · free - Why: Both are enforced as constants before any progression runs, and deciding them after the logic exists means fitting them to the logic rather than to safety. - Get it: Write down the maximum weekly load increase, the deload trigger, and which movements you will not program without stated coaching. - Verify: All three are written down and appear as constants in the safety module - [ ] **Your actual equipment inventory** · free - Why: Every generated plan is constrained by it, and the smallest available weight increment determines whether progression is even possible for a given lift. - Get it: List what you have including the real weight increments, which for dumbbells is often much coarser than for a barbell. - Verify: The inventory includes the smallest increment per equipment type - [ ] **Eight weeks of your own training** · free, and it takes eight weeks - Why: Progression behaviour, recovery estimates and the failure and deload paths only become observable across weeks of real sessions with real missed reps. - Get it: Log honestly, including the sessions that went badly, since those are the ones the logic is being tested on. - Verify: Eight weeks exist including at least three failed sessions - [ ] **Where you will send someone reporting pain** · free - Why: The build stops and says to see someone qualified, and a message with nothing behind it is not much better than a suggested substitution. - Get it: Write the message and decide what it points at, whether that is a professional body's directory or a specific instruction. - Verify: A written message exists and names a concrete next step ## 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: - Injury, form or technique assessment. - Nutrition and body composition. - Competitive lifting programmes. - Video form checking. - exercise library, adaptive programming, mobile capture, wearables, and coaching models - wearable hardware data - coach network - clinical validation - large content library and social network If one of those is essential to you, that is the reason to keep paying for Fitbod, and the README should say so rather than pretend.
# Build brief · Fitbod 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 strength training planner that adapts to what you actually lifted to replace Fitbod. 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 This tool tells a person to lift a weight, and a wrong instruction here injures someone. It is not a physiotherapist, a coach or a diagnostic tool, and it must not behave as though it might be. Concretely: it never programs through reported pain, it never increases load after a session the user marked as painful or failed, and it caps how fast load can rise regardless of what the progression logic computes. Where a user reports pain it stops programming that movement and says to see someone qualified, rather than suggesting a modification, because suggesting modifications for an undiagnosed pain is exactly the thing an application cannot do responsibly. The build also declines to program the lifts where a technical failure is most dangerous for an unsupervised beginner unless the user states they have been coached, since a barbell moving badly over someone's neck is a different category of risk from a set of dumbbell presses. None of this makes the tool useless. Progressive overload with sensible caps, honest recovery tracking and a plan that responds to real performance is genuinely valuable, and it is what follows. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a local application and SQLite · training logs are health data and belong on your own machine - Progression: Rules over recent performance with hard caps on rate of increase · the caps are what make a logic error harmless - Recovery: Per muscle group from volume and elapsed time · the useful output is what to train today, which depends on what you trained - Equipment: A declared inventory constraining every suggestion · a plan requiring a machine you do not have is not a plan - Safety: Pain and failure handling evaluated before any progression · progression must never be able to run through a pain report ### Data model (create this before Phase 1) - exercises(id, name, primary_muscles_json, secondary_muscles_json, equipment_required, technical_risk, requires_coaching) - equipment(id, kind, available, weight_increments_json) · what you actually have, including the smallest increment - sessions(id, planned_for, completed_at, perceived_effort, notes) - sets(id, session_id, exercise_id, ordinal, weight, reps, completed_reps, rpe, failed, pain_reported) - progressions(exercise_id, current_working_weight, last_increase_at, consecutive_successes, capped_by) - recovery(muscle, last_volume_at, volume_units, estimated_ready_at) - blocks(exercise_id, reason, from_at, cleared_at) · a movement stopped by pain, cleared only deliberately - A set records both prescribed and completed repetitions, because the difference between them is the entire input to progression. ### Phase 1 · Logging a session faster than resting between sets Build: Log sets with weight, prescribed and completed repetitions, and an effort rating. Default to the prescribed values so a completed set is one action. Work with one hand and a locked screen. Trap: A logging flow requiring several taps per set gets abandoned mid-session, and a partially logged session is worse than an unlogged one because progression will read it as a failure and reduce the next prescription. Default to prescribed values, make completion one action, and mark abandoned sessions rather than treating them as data. Done when: Logging a completed set as prescribed is one action, timed while actually training. A partially logged session is marked incomplete rather than read as failure. Entry works one-handed. Correcting a logged set afterwards is possible without re-entering the session. Do not build yet: No progression, recovery or planning. ### Phase 2 · Progression with a ceiling on how fast Build: Increase working weight after consecutive successful sessions, in increments your actual equipment supports. Cap the rate of increase per week regardless of what the rule computes. Reduce after repeated failure rather than holding. Trap: Progression rules derived from repetition performance can compound quickly and prescribe a weight the person has never approached, and by the time it is obviously wrong they are already under the bar. Cap the weekly increase as a hard constant so a logic error produces a slightly wrong prescription rather than a dangerous one. Done when: Load increases only after the required consecutive successes and never exceeds the weekly cap, verified by property testing across many session histories. Increments match the equipment's actual smallest step. Repeated failure reduces load rather than holding it indefinitely. The cap cannot be raised at runtime. Do not build yet: No recovery, planning or safety handling yet. ### Phase 3 · Pain stops the movement, not the plan Build: Record pain per set. A pain report blocks that exercise from future planning, stops any progression for it, and shows a message to see someone qualified. Clearing a block is a deliberate action with a stated waiting period. Trap: Offering a substitute exercise for a painful movement looks helpful and is the tool diagnosing something it cannot see, since the substitution depends entirely on what is actually wrong. Block the movement, say why, and suggest seeing someone rather than suggesting an alternative loaded exercise. Done when: A pain report blocks the exercise from all future planning and halts its progression, tested end to end. No alternative loaded exercise is suggested for a blocked movement, verified by inspecting the output. Clearing a block requires a deliberate action after a stated period. Progression evaluates blocks before anything else, verified in the code path. Do not build yet: No recovery model or planning yet. ### Phase 4 · Knowing what is recovered Build: Track volume per muscle group and estimate readiness from volume and elapsed time. Use it to select exercises. Present it as an estimate with its basis rather than as a measured fact. Trap: Presenting a recovery percentage as though it were measured implies a precision that does not exist, since actual recovery depends on sleep, stress, nutrition and things the application cannot see. Show the estimate with what it was computed from, and let the user override it, since they know things the model does not. Done when: Recovery is estimated per muscle group from volume and elapsed time, shown with its basis. The estimate is labelled as an estimate rather than presented as measured. A user can override readiness and the override is respected. Planning avoids groups estimated as unrecovered without refusing to train them if the user insists. Do not build yet: No plan generation or export yet. ### Phase 5 · A plan you can actually perform Build: Generate a session from available equipment, recovery estimates, blocks and a time budget. Exclude movements flagged as requiring coaching unless the user has stated they have it. Export the training log completely. Trap: Generating a plan that ignores the equipment inventory produces sessions requiring a machine the person does not have, and they will substitute something arbitrary, which makes the progression data meaningless from that point on. Constrain generation by the declared inventory, including the smallest weight increment available. Done when: Generated sessions use only declared equipment including real weight increments. Sessions fit the stated time budget, checked against actual session durations. Movements requiring coaching are excluded unless the user has stated they have it. Export contains sessions, sets, progressions and blocks and re-imports identically. Do not build yet: Nothing after this. ### Out of scope (and why) - Any assessment of injury, form or technique, which requires seeing the person and is not something an application does. - Nutrition and body composition, which is a different tool with its own set of safeguards. - Programming for competitive lifting, which needs a coach and a periodisation model well beyond this. - Video form checking, which is offered by some products and which no automated system does reliably enough to rely on. ### README must contain - That the weekly rate of load increase is capped as a constant so a logic error cannot become an injury - That a pain report blocks the movement entirely and suggests seeing someone qualified rather than a substitute - That recovery is an estimate shown with its basis and can be overridden by the user - That plans are constrained by declared equipment including the smallest available increment - That movements flagged as requiring coaching are excluded unless you have stated you have it
# Agent instructions · Fitbod indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with a local application and SQLite, Rules over recent performance with hard caps on rate of increase, Per muscle group from volume and elapsed time, A declared inventory constraining every suggestion, Pain and failure handling evaluated before any progression. 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 - A logging flow requiring several taps per set gets abandoned mid-session, and a partially logged session is worse than an unlogged one because progression will read it as a failure and reduce the next prescription. Default to prescribed values, make completion one action, and mark abandoned sessions rather than treating them as data. - Progression rules derived from repetition performance can compound quickly and prescribe a weight the person has never approached, and by the time it is obviously wrong they are already under the bar. Cap the weekly increase as a hard constant so a logic error produces a slightly wrong prescription rather than a dangerous one. - Offering a substitute exercise for a painful movement looks helpful and is the tool diagnosing something it cannot see, since the substitution depends entirely on what is actually wrong. Block the movement, say why, and suggest seeing someone rather than suggesting an alternative loaded exercise. - Presenting a recovery percentage as though it were measured implies a precision that does not exist, since actual recovery depends on sleep, stress, nutrition and things the application cannot see. Show the estimate with what it was computed from, and let the user override it, since they know things the model does not.
# Build plan · Fitbod A training planner whose interesting logic is progression under uncertainty: deciding what to lift next from what was actually lifted, tracking which muscle groups have recovered, and doing both inside caps that make a bug an inconvenience rather than an injury. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Logging a session faster than resting between sets Recording a set takes a couple of seconds while you are training. ### Steps 1. Default to the prescribed values Most sets go as planned. One action for the common case. Files: `src/logging.ts` 2. Record prescribed and completed separately Their difference is the whole input to progression. Files: `src/sets.ts` 3. Mark incomplete sessions Never read an abandoned session as failure. Files: `src/sessions.ts` ### Done when - [ ] A completed set logs in one action - [ ] Incomplete sessions are marked, not read as failure - [ ] Entry works one-handed - [ ] Corrections are possible afterwards ### Watch out - A logging flow requiring several taps per set gets abandoned mid-session, and a partially logged session is worse than an unlogged one because progression will read it as a failure and reduce the next prescription. Default to prescribed values, make completion one action, and mark abandoned sessions rather than treating them as data. ## Phase 2 · Progression with a ceiling on how fast Load increases when earned and never faster than the cap. ### Steps 1. Increase only on earned successes Consecutive, recorded, in real increments your equipment has. Files: `src/progression.ts` 2. Cap the weekly rate as a constant So a logic error is an inconvenience rather than an injury. Files: `src/caps.ts`, `src/caps.test.ts` 3. Deload after repeated failure Holding at a weight that keeps failing is not a plan. Files: `src/progression.ts` ### Done when - [ ] Increases require earned consecutive successes - [ ] Property testing finds no history exceeding the cap - [ ] Increments match real equipment steps - [ ] Repeated failure reduces load ### Watch out - Progression rules derived from repetition performance can compound quickly and prescribe a weight the person has never approached, and by the time it is obviously wrong they are already under the bar. Cap the weekly increase as a hard constant so a logic error produces a slightly wrong prescription rather than a dangerous one. ## Phase 3 · Pain stops the movement, not the plan A reported pain removes that movement and does not get worked around. ### Steps 1. Block the movement on pain Not reduce it, not substitute it. Block it. Files: `src/blocks.ts` 2. Suggest a person, not an exercise Substitution requires knowing what is wrong, which the tool does not. Files: `src/blocks.ts` 3. Evaluate blocks first in progression Before any other rule, so nothing can route around them. Files: `src/progression.ts`, `tests/pain-blocks.test.ts` ### Done when - [ ] A pain report blocks the exercise everywhere - [ ] No alternative loaded exercise is suggested - [ ] Clearing requires a deliberate action after a period - [ ] Blocks are evaluated before other progression rules ### Watch out - Offering a substitute exercise for a painful movement looks helpful and is the tool diagnosing something it cannot see, since the substitution depends entirely on what is actually wrong. Block the movement, say why, and suggest seeing someone rather than suggesting an alternative loaded exercise. ## Phase 4 · Knowing what is recovered Today's plan avoids what you trained hard two days ago. ### Steps 1. Estimate from volume and elapsed time Simple, explicable, and honest about being an estimate. Files: `src/recovery.ts` 2. Show the basis alongside the number A percentage with no basis reads as a measurement. Files: `src/recovery-view.ts` 3. Let the user override They know about their sleep and their week. The model does not. Files: `src/recovery.ts` ### Done when - [ ] Recovery is estimated with its basis shown - [ ] It is labelled an estimate, not a measurement - [ ] User overrides are respected - [ ] Planning avoids unrecovered groups without forbidding them ### Watch out - Presenting a recovery percentage as though it were measured implies a precision that does not exist, since actual recovery depends on sleep, stress, nutrition and things the application cannot see. Show the estimate with what it was computed from, and let the user override it, since they know things the model does not. ## Not in this build - Injury, form or technique assessment. - Nutrition and body composition. - Competitive lifting programmes. - Video form checking. ## After v1, if you want it - Exercise substitution within the same movement pattern for equipment reasons, which is a different question from pain - Session history charts, which are motivating and honest since they show what happened - Warm-up set generation, which is tedious to plan and easy to derive
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Training and pain reports. Health data, so treat backups accordingly. DB_PATH=~/.local/share/training/log.db # Required. A hard cap in the progression path. Not raisable at runtime. MAX_WEEKLY_INCREASE_PERCENT=5 # Required. Consecutive failures before load is reduced rather than held. DELOAD_AFTER_FAILURES=2 # Required. Minimum before a pain block can be cleared, and only deliberately. PAIN_BLOCK_MIN_DAYS=14 # Required. Present as a constant. Substitution requires knowing what is wrong. SUGGEST_SUBSTITUTES_ON_PAIN=never # Required. Plans are generated to fit it, checked against real durations. SESSION_TIME_BUDGET_MINUTES=50
# Fitbod · product brief ## Problem The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Fitbod, build moderate workout plans from user-selected equipment and recovery notes. The hard boundary is exercise library, adaptive programming, mobile capture, wearables, and coaching models, plus hardware data, content, coaching, and trust. ## Product outcome A planner that adapts to what you actually lifted, stops entirely when you report pain, and cannot prescribe a jump large enough to hurt you even if its logic is wrong. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - iOS or Android device - optional HealthKit or Health Connect permission - local storage ## Explicit non-goals for v1 - Injury, form or technique assessment. - Nutrition and body composition. - Competitive lifting programmes. - Video form checking. - exercise library, adaptive programming, mobile capture, wearables, and coaching models - wearable hardware data - coach network - clinical validation - large content library and social network ## Success criteria - Property testing is confirmed to find no history exceeding the weekly cap - A pain report is confirmed to block the movement everywhere - No substitute exercise is confirmed to be suggested for a blocked movement - Plans are confirmed to use only declared equipment and increments - Coached-only movements are confirmed gated
# Build brief · Fitbod 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 strength training planner that adapts to what you actually lifted to replace Fitbod. 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 This tool tells a person to lift a weight, and a wrong instruction here injures someone. It is not a physiotherapist, a coach or a diagnostic tool, and it must not behave as though it might be. Concretely: it never programs through reported pain, it never increases load after a session the user marked as painful or failed, and it caps how fast load can rise regardless of what the progression logic computes. Where a user reports pain it stops programming that movement and says to see someone qualified, rather than suggesting a modification, because suggesting modifications for an undiagnosed pain is exactly the thing an application cannot do responsibly. The build also declines to program the lifts where a technical failure is most dangerous for an unsupervised beginner unless the user states they have been coached, since a barbell moving badly over someone's neck is a different category of risk from a set of dumbbell presses. None of this makes the tool useless. Progressive overload with sensible caps, honest recovery tracking and a plan that responds to real performance is genuinely valuable, and it is what follows. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a local application and SQLite · training logs are health data and belong on your own machine - Progression: Rules over recent performance with hard caps on rate of increase · the caps are what make a logic error harmless - Recovery: Per muscle group from volume and elapsed time · the useful output is what to train today, which depends on what you trained - Equipment: A declared inventory constraining every suggestion · a plan requiring a machine you do not have is not a plan - Safety: Pain and failure handling evaluated before any progression · progression must never be able to run through a pain report ### Data model (create this before Phase 1) - exercises(id, name, primary_muscles_json, secondary_muscles_json, equipment_required, technical_risk, requires_coaching) - equipment(id, kind, available, weight_increments_json) · what you actually have, including the smallest increment - sessions(id, planned_for, completed_at, perceived_effort, notes) - sets(id, session_id, exercise_id, ordinal, weight, reps, completed_reps, rpe, failed, pain_reported) - progressions(exercise_id, current_working_weight, last_increase_at, consecutive_successes, capped_by) - recovery(muscle, last_volume_at, volume_units, estimated_ready_at) - blocks(exercise_id, reason, from_at, cleared_at) · a movement stopped by pain, cleared only deliberately - A set records both prescribed and completed repetitions, because the difference between them is the entire input to progression. ### Phase 1 · Logging a session faster than resting between sets Build: Log sets with weight, prescribed and completed repetitions, and an effort rating. Default to the prescribed values so a completed set is one action. Work with one hand and a locked screen. Trap: A logging flow requiring several taps per set gets abandoned mid-session, and a partially logged session is worse than an unlogged one because progression will read it as a failure and reduce the next prescription. Default to prescribed values, make completion one action, and mark abandoned sessions rather than treating them as data. Done when: Logging a completed set as prescribed is one action, timed while actually training. A partially logged session is marked incomplete rather than read as failure. Entry works one-handed. Correcting a logged set afterwards is possible without re-entering the session. Do not build yet: No progression, recovery or planning. ### Phase 2 · Progression with a ceiling on how fast Build: Increase working weight after consecutive successful sessions, in increments your actual equipment supports. Cap the rate of increase per week regardless of what the rule computes. Reduce after repeated failure rather than holding. Trap: Progression rules derived from repetition performance can compound quickly and prescribe a weight the person has never approached, and by the time it is obviously wrong they are already under the bar. Cap the weekly increase as a hard constant so a logic error produces a slightly wrong prescription rather than a dangerous one. Done when: Load increases only after the required consecutive successes and never exceeds the weekly cap, verified by property testing across many session histories. Increments match the equipment's actual smallest step. Repeated failure reduces load rather than holding it indefinitely. The cap cannot be raised at runtime. Do not build yet: No recovery, planning or safety handling yet. ### Phase 3 · Pain stops the movement, not the plan Build: Record pain per set. A pain report blocks that exercise from future planning, stops any progression for it, and shows a message to see someone qualified. Clearing a block is a deliberate action with a stated waiting period. Trap: Offering a substitute exercise for a painful movement looks helpful and is the tool diagnosing something it cannot see, since the substitution depends entirely on what is actually wrong. Block the movement, say why, and suggest seeing someone rather than suggesting an alternative loaded exercise. Done when: A pain report blocks the exercise from all future planning and halts its progression, tested end to end. No alternative loaded exercise is suggested for a blocked movement, verified by inspecting the output. Clearing a block requires a deliberate action after a stated period. Progression evaluates blocks before anything else, verified in the code path. Do not build yet: No recovery model or planning yet. ### Phase 4 · Knowing what is recovered Build: Track volume per muscle group and estimate readiness from volume and elapsed time. Use it to select exercises. Present it as an estimate with its basis rather than as a measured fact. Trap: Presenting a recovery percentage as though it were measured implies a precision that does not exist, since actual recovery depends on sleep, stress, nutrition and things the application cannot see. Show the estimate with what it was computed from, and let the user override it, since they know things the model does not. Done when: Recovery is estimated per muscle group from volume and elapsed time, shown with its basis. The estimate is labelled as an estimate rather than presented as measured. A user can override readiness and the override is respected. Planning avoids groups estimated as unrecovered without refusing to train them if the user insists. Do not build yet: No plan generation or export yet. ### Phase 5 · A plan you can actually perform Build: Generate a session from available equipment, recovery estimates, blocks and a time budget. Exclude movements flagged as requiring coaching unless the user has stated they have it. Export the training log completely. Trap: Generating a plan that ignores the equipment inventory produces sessions requiring a machine the person does not have, and they will substitute something arbitrary, which makes the progression data meaningless from that point on. Constrain generation by the declared inventory, including the smallest weight increment available. Done when: Generated sessions use only declared equipment including real weight increments. Sessions fit the stated time budget, checked against actual session durations. Movements requiring coaching are excluded unless the user has stated they have it. Export contains sessions, sets, progressions and blocks and re-imports identically. Do not build yet: Nothing after this. ### Out of scope (and why) - Any assessment of injury, form or technique, which requires seeing the person and is not something an application does. - Nutrition and body composition, which is a different tool with its own set of safeguards. - Programming for competitive lifting, which needs a coach and a periodisation model well beyond this. - Video form checking, which is offered by some products and which no automated system does reliably enough to rely on. ### README must contain - That the weekly rate of load increase is capped as a constant so a logic error cannot become an injury - That a pain report blocks the movement entirely and suggests seeing someone qualified rather than a substitute - That recovery is an estimate shown with its basis and can be overridden by the user - That plans are constrained by declared equipment including the smallest available increment - That movements flagged as requiring coaching are excluded unless you have stated you have it
# Architecture · Fitbod ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with a local application and SQLite | training logs are health data and belong on your own machine | | Progression | Rules over recent performance with hard caps on rate of increase | the caps are what make a logic error harmless | | Recovery | Per muscle group from volume and elapsed time | the useful output is what to train today, which depends on what you trained | | Equipment | A declared inventory constraining every suggestion | a plan requiring a machine you do not have is not a plan | | Safety | Pain and failure handling evaluated before any progression | progression must never be able to run through a pain report | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Logging | Fast set entry with prescribed and completed values. | Any interface, provided the common case is one action. | | Progression | Earned increases inside hard caps. | Any rule set, provided the caps are constants evaluated after it. | | Safety | Pain blocks and coached-movement gating. | Not swappable and evaluated first, so nothing routes around it. | | Recovery | Estimates with a visible basis. | Any model, provided it is presented as an estimate and can be overridden. | | Planning | Sessions constrained by equipment and time. | Any generator, provided the equipment inventory constrains it. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DB_PATH` · required · Training and pain reports. Health data, so treat backups accordingly. - `MAX_WEEKLY_INCREASE_PERCENT` · required · A hard cap in the progression path. Not raisable at runtime. - `DELOAD_AFTER_FAILURES` · required · Consecutive failures before load is reduced rather than held. - `PAIN_BLOCK_MIN_DAYS` · required · Minimum before a pain block can be cleared, and only deliberately. - `SUGGEST_SUBSTITUTES_ON_PAIN` · required · Present as a constant. Substitution requires knowing what is wrong. - `SESSION_TIME_BUDGET_MINUTES` · required · Plans are generated to fit it, checked against real durations. ## 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 · Fitbod product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: TypeScript with a local application and SQLite, Rules over recent performance with hard caps on rate of increase, Per muscle group from volume and elapsed time, A declared inventory constraining every suggestion, Pain and failure handling evaluated before any progression. - 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 - A logging flow requiring several taps per set gets abandoned mid-session, and a partially logged session is worse than an unlogged one because progression will read it as a failure and reduce the next prescription. Default to prescribed values, make completion one action, and mark abandoned sessions rather than treating them as data. - Progression rules derived from repetition performance can compound quickly and prescribe a weight the person has never approached, and by the time it is obviously wrong they are already under the bar. Cap the weekly increase as a hard constant so a logic error produces a slightly wrong prescription rather than a dangerous one. - Offering a substitute exercise for a painful movement looks helpful and is the tool diagnosing something it cannot see, since the substitution depends entirely on what is actually wrong. Block the movement, say why, and suggest seeing someone rather than suggesting an alternative loaded exercise. - Presenting a recovery percentage as though it were measured implies a precision that does not exist, since actual recovery depends on sleep, stress, nutrition and things the application cannot see. Show the estimate with what it was computed from, and let the user override it, since they know things the model does not. - Generating a plan that ignores the equipment inventory produces sessions requiring a machine the person does not have, and they will substitute something arbitrary, which makes the progression data meaningless from that point on. Constrain generation by the declared inventory, including the smallest weight increment available.
# Delivery milestones · Fitbod Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Logging a session faster than resting between sets Recording a set takes a couple of seconds while you are training. ### Steps 1. Default to the prescribed values Most sets go as planned. One action for the common case. Files: `src/logging.ts` 2. Record prescribed and completed separately Their difference is the whole input to progression. Files: `src/sets.ts` 3. Mark incomplete sessions Never read an abandoned session as failure. Files: `src/sessions.ts` ### Done when - [ ] A completed set logs in one action - [ ] Incomplete sessions are marked, not read as failure - [ ] Entry works one-handed - [ ] Corrections are possible afterwards ### Watch out - A logging flow requiring several taps per set gets abandoned mid-session, and a partially logged session is worse than an unlogged one because progression will read it as a failure and reduce the next prescription. Default to prescribed values, make completion one action, and mark abandoned sessions rather than treating them as data. ## M2 · Progression with a ceiling on how fast Load increases when earned and never faster than the cap. ### Steps 1. Increase only on earned successes Consecutive, recorded, in real increments your equipment has. Files: `src/progression.ts` 2. Cap the weekly rate as a constant So a logic error is an inconvenience rather than an injury. Files: `src/caps.ts`, `src/caps.test.ts` 3. Deload after repeated failure Holding at a weight that keeps failing is not a plan. Files: `src/progression.ts` ### Done when - [ ] Increases require earned consecutive successes - [ ] Property testing finds no history exceeding the cap - [ ] Increments match real equipment steps - [ ] Repeated failure reduces load ### Watch out - Progression rules derived from repetition performance can compound quickly and prescribe a weight the person has never approached, and by the time it is obviously wrong they are already under the bar. Cap the weekly increase as a hard constant so a logic error produces a slightly wrong prescription rather than a dangerous one. ## M3 · Pain stops the movement, not the plan A reported pain removes that movement and does not get worked around. ### Steps 1. Block the movement on pain Not reduce it, not substitute it. Block it. Files: `src/blocks.ts` 2. Suggest a person, not an exercise Substitution requires knowing what is wrong, which the tool does not. Files: `src/blocks.ts` 3. Evaluate blocks first in progression Before any other rule, so nothing can route around them. Files: `src/progression.ts`, `tests/pain-blocks.test.ts` ### Done when - [ ] A pain report blocks the exercise everywhere - [ ] No alternative loaded exercise is suggested - [ ] Clearing requires a deliberate action after a period - [ ] Blocks are evaluated before other progression rules ### Watch out - Offering a substitute exercise for a painful movement looks helpful and is the tool diagnosing something it cannot see, since the substitution depends entirely on what is actually wrong. Block the movement, say why, and suggest seeing someone rather than suggesting an alternative loaded exercise. ## M4 · Knowing what is recovered Today's plan avoids what you trained hard two days ago. ### Steps 1. Estimate from volume and elapsed time Simple, explicable, and honest about being an estimate. Files: `src/recovery.ts` 2. Show the basis alongside the number A percentage with no basis reads as a measurement. Files: `src/recovery-view.ts` 3. Let the user override They know about their sleep and their week. The model does not. Files: `src/recovery.ts` ### Done when - [ ] Recovery is estimated with its basis shown - [ ] It is labelled an estimate, not a measurement - [ ] User overrides are respected - [ ] Planning avoids unrecovered groups without forbidding them ### Watch out - Presenting a recovery percentage as though it were measured implies a precision that does not exist, since actual recovery depends on sleep, stress, nutrition and things the application cannot see. Show the estimate with what it was computed from, and let the user override it, since they know things the model does not. ## M5 · A plan you can actually perform (production only) Today's session fits your equipment, your time and your recovery. ### Steps 1. Constrain by declared equipment Including the smallest increment. A plan you cannot load is not a plan. Files: `src/plan.ts` 2. Respect a time budget Measured against real session durations, not estimated ones. Files: `src/plan.ts` 3. Gate coached movements Unless the user says they have been taught. Unsupervised risk differs by lift. Files: `src/plan.ts` ### Done when - [ ] Sessions use only declared equipment and increments - [ ] Sessions fit the stated time budget - [ ] Coached-only movements are gated - [ ] Export round trips the full log ### Watch out - Generating a plan that ignores the equipment inventory produces sessions requiring a machine the person does not have, and they will substitute something arbitrary, which makes the progression data meaningless from that point on. Constrain generation by the declared inventory, including the smallest weight increment available.
# Operations · Fitbod ## Backup A training log is years of data and its value grows with length. It is also health data including pain reports, so back it up encrypted and think about where it lives. ## Restore Restore and confirm pain blocks survived as blocks, since a restore that clears them would resume programming a movement someone stopped for a reason. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a personal tool, the number to watch is how often prescriptions hit the cap. Frequently hitting it means the progression rule is computing increases the cap is quietly rescuing you from. ## Incident checklist A prescription the user could not safely attempt is the failure that matters. The cap is the control, and property testing across many histories is what demonstrates it holds rather than the absence of complaints. 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 - [ ] Property testing is confirmed to find no history exceeding the weekly cap - [ ] A pain report is confirmed to block the movement everywhere - [ ] No substitute exercise is confirmed to be suggested for a blocked movement - [ ] Plans are confirmed to use only declared equipment and increments - [ ] Coached-only movements are confirmed gated ## Launch constraint Do not market omitted Fitbod 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. Training and pain reports. Health data, so treat backups accordingly. DB_PATH=~/.local/share/training/log.db # Required. A hard cap in the progression path. Not raisable at runtime. MAX_WEEKLY_INCREASE_PERCENT=5 # Required. Consecutive failures before load is reduced rather than held. DELOAD_AFTER_FAILURES=2 # Required. Minimum before a pain block can be cleared, and only deliberately. PAIN_BLOCK_MIN_DAYS=14 # Required. Present as a constant. Substitution requires knowing what is wrong. SUGGEST_SUBSTITUTES_ON_PAIN=never # Required. Plans are generated to fit it, checked against real durations. SESSION_TIME_BUDGET_MINUTES=50
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for Fitbod because people pay for polished mobile capture, trusted content, hardware integration, and coaching rather than the basic logbook. The recurring cost buys sensor permissions, data quality, notifications, safety language, accessibility, sync, privacy, content review, and support, not just the visible interface.
xexercise library, adaptive programming, mobile capture, wearables, and coaching models
xwearable hardware data
xcoach network
xclinical validation
xlarge content library and social network
Don't feel like building it? These folks already made it free.
no votes, no pay-to-list · just what's real
Fitbod pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| fitbod | $15.99/user | $8/user | Full adaptive workouts and access to 1,500+ exercises; 7-day trial |
| family duo | custom | — | 2 members; annual-only family subscription |
| family | custom | — | Up to 5 members; annual-only family subscription |
free tierno permanent free tier; 7-day trial, after which a non-subscriber can view history but can log 0 new workouts
billingindividual monthly + annual; Family Duo and Family are annual only
hidden costsThe trial converts to a paid subscription unless canceled through the purchase channel. Family pricing is shown only inside eligible checkout flows, and occasional Lifetime promotions are not a standing tier.
verified 2026-08-14 · source ↗
Vibecode Fitbod
Kinda. The core of Fitbod is buildable in a weekend with the prompt on this page, but there are real gaps: exercise library, adaptive programming, mobile capture, wearables, and coaching models, wearable hardware data. Read the honest list above before committing.
How much does Fitbod cost?
Fitbod costs about $15.99/month (Fitbod, checked 2026-07-31), which is $191.88 per year.
What do I lose by replacing Fitbod?
Honestly: exercise library, adaptive programming, mobile capture, wearables, and coaching models; wearable hardware data; coach network; clinical validation; large content library and social network. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Fitbod?
Yes: Workout.cool (Pick equipment and muscles; it makes a routine without pretending to know your soul.) wger (Plans, progression and recovery notes in one open-source gym; the recommendations are less clairvoyant.) The prompt is for when you want it exactly your way.