Vibecode Memtime
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 Memtime, record local app activity privately and convert selected blocks into timesheet entries. The hard boundary is calendar and project integrations, team reporting, sync, and enterprise controls, plus ubiquitous capture, coaching, and team workflow.
You are building a lean indie version of Memtime. 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 ===== # Memtime · indie build A local activity recorder that watches which application and document you were in, groups the day into blocks, and lets you turn selected blocks into timesheet entries. The engineering is in grouping a fragmented day into something recognisable, and the design constraint is that raw capture never leaves the machine. 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 | | --- | --- | --- | | Capture | A local agent polling the focused window and idle state | this is the only way to reconstruct a day you did not track as it happened | | Storage | SQLite on the machine, encrypted at rest | raw capture is the most sensitive data in the build | | Grouping | Segmentation into blocks by application, document and gap | a raw event stream is unreadable and useless for reconstruction | | Approval | Nothing becomes a timesheet entry without an explicit action | the whole model is recall and review, not automatic reporting | | Export | Approved entries only, in a format your billing system takes | the boundary between capture and export is the privacy boundary | ## 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 agent and the review interface. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A way to read the focused window and idle time** · free - Why: This is platform-specific and on some systems requires an accessibility or screen recording permission, which changes what you must tell the user. - Get it: Whatever your platform provides, and find out which permission it needs before designing the onboarding. - Verify: The focused application, its window title and idle seconds can all be read - [ ] **Your exclusion list, written before capture** · free - Why: Deciding what not to record after you have a month of recordings is deciding it too late, since the data already exists. - Get it: List the applications and title patterns that must never be stored: banking, health, private messaging, anything belonging to someone else. Err wide. - Verify: The list exists and is loaded by the sampler before the first run - [ ] **A week of your own real working days** · free, and it takes a week - Why: Segmentation thresholds and block summarisation can only be tuned against a real fragmented day, and a synthetic one is far tidier than reality. - Get it: Run the capture for a week and reconstruct each day from memory to compare against. - Verify: A week of capture exists and you have tried to reconstruct at least three days from it ## 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: - Team deployment with manager visibility. - Automatic project assignment from window content. - Screenshot capture. - Mobile and browser tab level detail. - calendar and project integrations, team reporting, sync, and enterprise controls - cross-device history - team timesheets and payroll - behavioral datasets - blocking across every platform If one of those is essential to you, that is the reason to keep paying for Memtime, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Memtime 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 passive activity recorder that turns your day into timesheet entries you approve to replace Memtime. 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 Passive capture records window titles, and window titles are not metadata. They contain document names, message subjects, the URL of every page, the name of the person you were messaging and the title of the medical appointment in your calendar. A day of window titles is a more revealing record of someone than a day of screenshots would be easy to search. So: capture stays on the machine, it never syncs raw, only approved timesheet entries leave, there is a pause that is genuinely a pause, and there is an exclusion list applied before anything is written to disk rather than filtered at display. If you are considering deploying this to other people, the answer is that a tool which records what your colleagues type into their window titles and reports it to their manager is surveillance regardless of what it is called. Build it for yourself. If it must be for a team, the raw capture must never be visible to anyone but the person it came from, and that has to be an architectural guarantee rather than a policy. ### Stack (fixed, do not substitute) - Capture: A local agent polling the focused window and idle state · this is the only way to reconstruct a day you did not track as it happened - Storage: SQLite on the machine, encrypted at rest · raw capture is the most sensitive data in the build - Grouping: Segmentation into blocks by application, document and gap · a raw event stream is unreadable and useless for reconstruction - Approval: Nothing becomes a timesheet entry without an explicit action · the whole model is recall and review, not automatic reporting - Export: Approved entries only, in a format your billing system takes · the boundary between capture and export is the privacy boundary ### Data model (create this before Phase 1) - samples(at, app, window_title, document, idle_seconds) · the raw stream, local only, never exported - blocks(id, started_at, ended_at, app, title_summary, sample_count) · derived by segmentation - exclusions(id, kind, pattern, created_at) · kind is app or title. Applied before writing a sample - projects(id, name, client, billable, archived_at) - entries(id, project_id, started_at, ended_at, description, approved_at, exported_at) - entry_blocks(entry_id, block_id) · which capture a timesheet entry was built from, kept locally - The link from an entry back to its blocks stays on the machine. What leaves is the entry alone. ### Phase 1 · Capture, with the exclusions applied first Build: An agent sampling the focused application, window title and idle time at an interval. Apply an exclusion list before writing, so an excluded window produces no row at all. A pause that stops sampling entirely. Encrypt the store at rest. Trap: Filtering excluded applications at display time means the sensitive titles are still on disk, still in backups, and still readable by anything that can open the database, which is exactly the outcome the exclusion was meant to prevent. Apply exclusions in the sampler, before the write. Done when: An excluded application produces no row at all, verified by inspecting the database directly rather than the interface. Pausing stops sampling and the gap is visible rather than interpolated. The store is encrypted at rest. A day of sampling costs a negligible amount of processor time, measured. Do not build yet: No grouping, projects or export. ### Phase 2 · Turning a fragmented day into blocks Build: Segment samples into blocks by application and document, merging across short interruptions and splitting on real gaps. Summarise each block's titles into something readable. Treat idle time as a boundary rather than as work. Trap: Segmenting on every application change produces four hundred blocks for a normal day, which is unreadable and defeats the purpose, while merging too aggressively produces one block called various and tells you nothing. The gap threshold and the merge rules are the actual design work here and they need tuning against your own real day. Done when: A real day produces a readable number of blocks, checked against your own memory of that day. A brief switch to another application does not split a block. A genuine gap does split one. Idle periods appear as gaps rather than as time spent in whatever was focused. Do not build yet: No projects or approval yet. ### Phase 3 · Reconstructing a day you did not track Build: A timeline view of blocks with a scrubber, showing what was on screen at any moment. Select one or several blocks and turn them into a timesheet entry against a project. Nothing becomes an entry without that action. Trap: Automatically assigning blocks to projects by matching window titles produces confident, wrong timesheets, and a wrong timesheet that was never reviewed is worse than an empty one because it gets invoiced. Suggest a project, never assign one, and require the approval action for every entry. Done when: A day is reconstructed into entries in a few minutes, timed by hand. Selecting several blocks produces one entry with the combined duration. Suggestions appear but nothing is assigned without approval, verified by leaving the day untouched and confirming no entries exist. An entry's duration cannot exceed the blocks it came from. Do not build yet: No rounding, export or reporting yet. ### Phase 4 · Rounding and billing rules, stated honestly Build: Per-project rounding and minimum increments applied at export, showing the actual and rounded durations side by side. Never round the stored entry. Handle an entry crossing midnight and a day with a daylight saving transition. Trap: Rounding the stored entry destroys the actual figure, so you can never answer a client asking what the real time was, and repeated rounding on edits compounds the error. Store actual, round at export, and show both. Done when: Rounding applies at export with actual and rounded shown together. The stored entry is never modified by rounding, verified by exporting twice and comparing. An entry crossing midnight is handled per the project's rule. A day with a daylight saving transition produces correct durations, tested on both transitions. Do not build yet: No reporting or team export yet. ### Phase 5 · Retention, and the boundary that must hold Build: Retention deleting raw samples after a short period while keeping approved entries. Export containing entries only. Assert in the test suite that no export path can emit a window title. A deletion that removes everything on request. Trap: An export or a crash report that includes window titles breaks the entire privacy model in one line of code, and it will be added by someone trying to make debugging easier. Assert it in the suite: scan every export and log path for the raw fields and fail the build if they appear. Done when: Raw samples are deleted after the retention period while approved entries survive, tested by advancing the clock. A test asserts no export or log path emits a window title, and fails when one is deliberately added. Deletion removes samples, blocks, entries and backups. Retention runs without the application being open. Do not build yet: Nothing after this. ### Out of scope (and why) - Team deployment with manager visibility, which turns this from a memory aid into surveillance and is a different product with different ethics. - Automatic project assignment from window content, which is the feature request that arrives first and the one that produces confidently wrong invoices. - Screenshot capture, which is a categorically larger intrusion than titles and is not made acceptable by being local. - Mobile and browser tab level detail, where the platform restrictions are severe and the privacy cost rises sharply. ### README must contain - That exclusions are applied in the sampler so excluded windows never reach the disk - That raw capture never leaves the machine and a test asserts no export path can emit a title - That nothing becomes a timesheet entry without an explicit approval - That rounding happens at export and the stored entry keeps the actual duration - That raw samples expire on a short retention while approved entries persist ===== AGENTS.md ===== # Agent instructions · Memtime indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A local agent polling the focused window and idle state, SQLite on the machine, encrypted at rest, Segmentation into blocks by application, document and gap, Nothing becomes a timesheet entry without an explicit action, Approved entries only, in a format your billing system takes. 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 - Filtering excluded applications at display time means the sensitive titles are still on disk, still in backups, and still readable by anything that can open the database, which is exactly the outcome the exclusion was meant to prevent. Apply exclusions in the sampler, before the write. - Segmenting on every application change produces four hundred blocks for a normal day, which is unreadable and defeats the purpose, while merging too aggressively produces one block called various and tells you nothing. The gap threshold and the merge rules are the actual design work here and they need tuning against your own real day. - Automatically assigning blocks to projects by matching window titles produces confident, wrong timesheets, and a wrong timesheet that was never reviewed is worse than an empty one because it gets invoiced. Suggest a project, never assign one, and require the approval action for every entry. - Rounding the stored entry destroys the actual figure, so you can never answer a client asking what the real time was, and repeated rounding on edits compounds the error. Store actual, round at export, and show both. ===== BUILD_PLAN.md ===== # Build plan · Memtime A local activity recorder that watches which application and document you were in, groups the day into blocks, and lets you turn selected blocks into timesheet entries. The engineering is in grouping a fragmented day into something recognisable, and the design constraint is that raw capture never leaves the machine. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Capture, with the exclusions applied first Your day is recorded, minus everything you said not to record. ### Steps 1. Sample focus and idle at an interval Application, title, document, idle seconds. Nothing else. Files: `src/capture.ts` 2. Apply exclusions before writing In the sampler. Display filtering leaves the data on disk. Files: `src/exclude.ts`, `src/exclude.test.ts` 3. Make pause a real pause No sampling, and a visible gap rather than an interpolated one. Files: `src/capture.ts` ### Done when - [ ] An excluded app produces no row in the database - [ ] Pause stops sampling and leaves a visible gap - [ ] The store is encrypted at rest - [ ] Sampling costs negligible processor time ### Watch out - Filtering excluded applications at display time means the sensitive titles are still on disk, still in backups, and still readable by anything that can open the database, which is exactly the outcome the exclusion was meant to prevent. Apply exclusions in the sampler, before the write. ## Phase 2 · Turning a fragmented day into blocks A day of thousands of samples reads as a dozen recognisable blocks. ### Steps 1. Segment with a tuned gap threshold Tune it against your own day. This is the design work, not a constant to guess. Files: `src/segment.ts`, `src/segment.test.ts` 2. Merge across brief interruptions Checking a message does not end a block of work. Files: `src/segment.ts` 3. Summarise titles readably A block needs a label you recognise, not a list of forty window titles. Files: `src/summarise.ts` ### Done when - [ ] A real day produces a readable number of blocks - [ ] A brief switch does not split a block - [ ] A genuine gap does split one - [ ] Idle appears as a gap rather than as work ### Watch out - Segmenting on every application change produces four hundred blocks for a normal day, which is unreadable and defeats the purpose, while merging too aggressively produces one block called various and tells you nothing. The gap threshold and the merge rules are the actual design work here and they need tuning against your own real day. ## Phase 3 · Reconstructing a day you did not track You can sit down at five and rebuild the day accurately. ### Steps 1. Show the day as a scrubable timeline The point is recall. You need to see what was on screen at four o'clock. Files: `src/timeline.ts` 2. Select blocks into one entry Combining several blocks is the normal case, not the exception. Files: `src/entries.ts` 3. Suggest, never assign An unreviewed automatic timesheet gets invoiced. That is the failure. Files: `src/suggest.ts` ### Done when - [ ] A day reconstructs into entries in a few minutes - [ ] Several blocks combine into one entry - [ ] No entry exists without approval - [ ] An entry cannot exceed its source blocks ### Watch out - Automatically assigning blocks to projects by matching window titles produces confident, wrong timesheets, and a wrong timesheet that was never reviewed is worse than an empty one because it gets invoiced. Suggest a project, never assign one, and require the approval action for every entry. ## Phase 4 · Rounding and billing rules, stated honestly Exported time matches the rule you agreed with the client. ### Steps 1. Round at export only Store actual. A client asking what the real figure was deserves an answer. Files: `src/rounding.ts`, `src/rounding.test.ts` 2. Show both figures Actual and rounded, side by side. Hiding the difference is how disputes start. Files: `src/export.ts` 3. Handle midnight and daylight transitions Both are ordinary. Both break naive duration arithmetic. Files: `src/duration.ts` ### Done when - [ ] Rounding applies at export and shows both figures - [ ] The stored entry is unchanged by rounding - [ ] An entry crossing midnight is handled - [ ] Both daylight transitions produce correct durations ### Watch out - Rounding the stored entry destroys the actual figure, so you can never answer a client asking what the real time was, and repeated rounding on edits compounds the error. Store actual, round at export, and show both. ## Not in this build - Team deployment with manager visibility. - Automatic project assignment from window content. - Screenshot capture. - Mobile and browser tab level detail. ## After v1, if you want it - Calendar events shown alongside blocks, which fills the gaps capture cannot see - A weekly reconstruction prompt, since the recall value decays quickly - Invoice generation from approved entries, once the rounding rules are trustworthy ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Raw capture. The most sensitive file this build produces. Encrypted at rest. DB_PATH=~/.local/share/memtime/capture.db # Required · secret. Encrypts the capture store. Without it the raw titles sit in plain text. DB_KEY=a passphrase or keychain reference # Required. How often focus is sampled. Shorter is more accurate and more intrusive. SAMPLE_INTERVAL_SECONDS=15 # Required. When you are considered away. Blocks end at idle rather than continuing. IDLE_THRESHOLD_SECONDS=180 # Required. How long raw samples live. Short, because their usefulness drops fast and their sensitivity does not. RAW_RETENTION_DAYS=30 # Required. Loaded by the sampler before the first write. Not a display filter. EXCLUSIONS_PATH=./config/exclusions.txt
You are building a lean indie version of Memtime. 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 ===== # Memtime · indie build A local activity recorder that watches which application and document you were in, groups the day into blocks, and lets you turn selected blocks into timesheet entries. The engineering is in grouping a fragmented day into something recognisable, and the design constraint is that raw capture never leaves the machine. 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 | | --- | --- | --- | | Capture | A local agent polling the focused window and idle state | this is the only way to reconstruct a day you did not track as it happened | | Storage | SQLite on the machine, encrypted at rest | raw capture is the most sensitive data in the build | | Grouping | Segmentation into blocks by application, document and gap | a raw event stream is unreadable and useless for reconstruction | | Approval | Nothing becomes a timesheet entry without an explicit action | the whole model is recall and review, not automatic reporting | | Export | Approved entries only, in a format your billing system takes | the boundary between capture and export is the privacy boundary | ## 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 agent and the review interface. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A way to read the focused window and idle time** · free - Why: This is platform-specific and on some systems requires an accessibility or screen recording permission, which changes what you must tell the user. - Get it: Whatever your platform provides, and find out which permission it needs before designing the onboarding. - Verify: The focused application, its window title and idle seconds can all be read - [ ] **Your exclusion list, written before capture** · free - Why: Deciding what not to record after you have a month of recordings is deciding it too late, since the data already exists. - Get it: List the applications and title patterns that must never be stored: banking, health, private messaging, anything belonging to someone else. Err wide. - Verify: The list exists and is loaded by the sampler before the first run - [ ] **A week of your own real working days** · free, and it takes a week - Why: Segmentation thresholds and block summarisation can only be tuned against a real fragmented day, and a synthetic one is far tidier than reality. - Get it: Run the capture for a week and reconstruct each day from memory to compare against. - Verify: A week of capture exists and you have tried to reconstruct at least three days from it ## 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: - Team deployment with manager visibility. - Automatic project assignment from window content. - Screenshot capture. - Mobile and browser tab level detail. - calendar and project integrations, team reporting, sync, and enterprise controls - cross-device history - team timesheets and payroll - behavioral datasets - blocking across every platform If one of those is essential to you, that is the reason to keep paying for Memtime, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Memtime 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 passive activity recorder that turns your day into timesheet entries you approve to replace Memtime. 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 Passive capture records window titles, and window titles are not metadata. They contain document names, message subjects, the URL of every page, the name of the person you were messaging and the title of the medical appointment in your calendar. A day of window titles is a more revealing record of someone than a day of screenshots would be easy to search. So: capture stays on the machine, it never syncs raw, only approved timesheet entries leave, there is a pause that is genuinely a pause, and there is an exclusion list applied before anything is written to disk rather than filtered at display. If you are considering deploying this to other people, the answer is that a tool which records what your colleagues type into their window titles and reports it to their manager is surveillance regardless of what it is called. Build it for yourself. If it must be for a team, the raw capture must never be visible to anyone but the person it came from, and that has to be an architectural guarantee rather than a policy. ### Stack (fixed, do not substitute) - Capture: A local agent polling the focused window and idle state · this is the only way to reconstruct a day you did not track as it happened - Storage: SQLite on the machine, encrypted at rest · raw capture is the most sensitive data in the build - Grouping: Segmentation into blocks by application, document and gap · a raw event stream is unreadable and useless for reconstruction - Approval: Nothing becomes a timesheet entry without an explicit action · the whole model is recall and review, not automatic reporting - Export: Approved entries only, in a format your billing system takes · the boundary between capture and export is the privacy boundary ### Data model (create this before Phase 1) - samples(at, app, window_title, document, idle_seconds) · the raw stream, local only, never exported - blocks(id, started_at, ended_at, app, title_summary, sample_count) · derived by segmentation - exclusions(id, kind, pattern, created_at) · kind is app or title. Applied before writing a sample - projects(id, name, client, billable, archived_at) - entries(id, project_id, started_at, ended_at, description, approved_at, exported_at) - entry_blocks(entry_id, block_id) · which capture a timesheet entry was built from, kept locally - The link from an entry back to its blocks stays on the machine. What leaves is the entry alone. ### Phase 1 · Capture, with the exclusions applied first Build: An agent sampling the focused application, window title and idle time at an interval. Apply an exclusion list before writing, so an excluded window produces no row at all. A pause that stops sampling entirely. Encrypt the store at rest. Trap: Filtering excluded applications at display time means the sensitive titles are still on disk, still in backups, and still readable by anything that can open the database, which is exactly the outcome the exclusion was meant to prevent. Apply exclusions in the sampler, before the write. Done when: An excluded application produces no row at all, verified by inspecting the database directly rather than the interface. Pausing stops sampling and the gap is visible rather than interpolated. The store is encrypted at rest. A day of sampling costs a negligible amount of processor time, measured. Do not build yet: No grouping, projects or export. ### Phase 2 · Turning a fragmented day into blocks Build: Segment samples into blocks by application and document, merging across short interruptions and splitting on real gaps. Summarise each block's titles into something readable. Treat idle time as a boundary rather than as work. Trap: Segmenting on every application change produces four hundred blocks for a normal day, which is unreadable and defeats the purpose, while merging too aggressively produces one block called various and tells you nothing. The gap threshold and the merge rules are the actual design work here and they need tuning against your own real day. Done when: A real day produces a readable number of blocks, checked against your own memory of that day. A brief switch to another application does not split a block. A genuine gap does split one. Idle periods appear as gaps rather than as time spent in whatever was focused. Do not build yet: No projects or approval yet. ### Phase 3 · Reconstructing a day you did not track Build: A timeline view of blocks with a scrubber, showing what was on screen at any moment. Select one or several blocks and turn them into a timesheet entry against a project. Nothing becomes an entry without that action. Trap: Automatically assigning blocks to projects by matching window titles produces confident, wrong timesheets, and a wrong timesheet that was never reviewed is worse than an empty one because it gets invoiced. Suggest a project, never assign one, and require the approval action for every entry. Done when: A day is reconstructed into entries in a few minutes, timed by hand. Selecting several blocks produces one entry with the combined duration. Suggestions appear but nothing is assigned without approval, verified by leaving the day untouched and confirming no entries exist. An entry's duration cannot exceed the blocks it came from. Do not build yet: No rounding, export or reporting yet. ### Phase 4 · Rounding and billing rules, stated honestly Build: Per-project rounding and minimum increments applied at export, showing the actual and rounded durations side by side. Never round the stored entry. Handle an entry crossing midnight and a day with a daylight saving transition. Trap: Rounding the stored entry destroys the actual figure, so you can never answer a client asking what the real time was, and repeated rounding on edits compounds the error. Store actual, round at export, and show both. Done when: Rounding applies at export with actual and rounded shown together. The stored entry is never modified by rounding, verified by exporting twice and comparing. An entry crossing midnight is handled per the project's rule. A day with a daylight saving transition produces correct durations, tested on both transitions. Do not build yet: No reporting or team export yet. ### Phase 5 · Retention, and the boundary that must hold Build: Retention deleting raw samples after a short period while keeping approved entries. Export containing entries only. Assert in the test suite that no export path can emit a window title. A deletion that removes everything on request. Trap: An export or a crash report that includes window titles breaks the entire privacy model in one line of code, and it will be added by someone trying to make debugging easier. Assert it in the suite: scan every export and log path for the raw fields and fail the build if they appear. Done when: Raw samples are deleted after the retention period while approved entries survive, tested by advancing the clock. A test asserts no export or log path emits a window title, and fails when one is deliberately added. Deletion removes samples, blocks, entries and backups. Retention runs without the application being open. Do not build yet: Nothing after this. ### Out of scope (and why) - Team deployment with manager visibility, which turns this from a memory aid into surveillance and is a different product with different ethics. - Automatic project assignment from window content, which is the feature request that arrives first and the one that produces confidently wrong invoices. - Screenshot capture, which is a categorically larger intrusion than titles and is not made acceptable by being local. - Mobile and browser tab level detail, where the platform restrictions are severe and the privacy cost rises sharply. ### README must contain - That exclusions are applied in the sampler so excluded windows never reach the disk - That raw capture never leaves the machine and a test asserts no export path can emit a title - That nothing becomes a timesheet entry without an explicit approval - That rounding happens at export and the stored entry keeps the actual duration - That raw samples expire on a short retention while approved entries persist ===== AGENTS.md ===== # Agent instructions · Memtime indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A local agent polling the focused window and idle state, SQLite on the machine, encrypted at rest, Segmentation into blocks by application, document and gap, Nothing becomes a timesheet entry without an explicit action, Approved entries only, in a format your billing system takes. 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 - Filtering excluded applications at display time means the sensitive titles are still on disk, still in backups, and still readable by anything that can open the database, which is exactly the outcome the exclusion was meant to prevent. Apply exclusions in the sampler, before the write. - Segmenting on every application change produces four hundred blocks for a normal day, which is unreadable and defeats the purpose, while merging too aggressively produces one block called various and tells you nothing. The gap threshold and the merge rules are the actual design work here and they need tuning against your own real day. - Automatically assigning blocks to projects by matching window titles produces confident, wrong timesheets, and a wrong timesheet that was never reviewed is worse than an empty one because it gets invoiced. Suggest a project, never assign one, and require the approval action for every entry. - Rounding the stored entry destroys the actual figure, so you can never answer a client asking what the real time was, and repeated rounding on edits compounds the error. Store actual, round at export, and show both. ===== BUILD_PLAN.md ===== # Build plan · Memtime A local activity recorder that watches which application and document you were in, groups the day into blocks, and lets you turn selected blocks into timesheet entries. The engineering is in grouping a fragmented day into something recognisable, and the design constraint is that raw capture never leaves the machine. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Capture, with the exclusions applied first Your day is recorded, minus everything you said not to record. ### Steps 1. Sample focus and idle at an interval Application, title, document, idle seconds. Nothing else. Files: `src/capture.ts` 2. Apply exclusions before writing In the sampler. Display filtering leaves the data on disk. Files: `src/exclude.ts`, `src/exclude.test.ts` 3. Make pause a real pause No sampling, and a visible gap rather than an interpolated one. Files: `src/capture.ts` ### Done when - [ ] An excluded app produces no row in the database - [ ] Pause stops sampling and leaves a visible gap - [ ] The store is encrypted at rest - [ ] Sampling costs negligible processor time ### Watch out - Filtering excluded applications at display time means the sensitive titles are still on disk, still in backups, and still readable by anything that can open the database, which is exactly the outcome the exclusion was meant to prevent. Apply exclusions in the sampler, before the write. ## Phase 2 · Turning a fragmented day into blocks A day of thousands of samples reads as a dozen recognisable blocks. ### Steps 1. Segment with a tuned gap threshold Tune it against your own day. This is the design work, not a constant to guess. Files: `src/segment.ts`, `src/segment.test.ts` 2. Merge across brief interruptions Checking a message does not end a block of work. Files: `src/segment.ts` 3. Summarise titles readably A block needs a label you recognise, not a list of forty window titles. Files: `src/summarise.ts` ### Done when - [ ] A real day produces a readable number of blocks - [ ] A brief switch does not split a block - [ ] A genuine gap does split one - [ ] Idle appears as a gap rather than as work ### Watch out - Segmenting on every application change produces four hundred blocks for a normal day, which is unreadable and defeats the purpose, while merging too aggressively produces one block called various and tells you nothing. The gap threshold and the merge rules are the actual design work here and they need tuning against your own real day. ## Phase 3 · Reconstructing a day you did not track You can sit down at five and rebuild the day accurately. ### Steps 1. Show the day as a scrubable timeline The point is recall. You need to see what was on screen at four o'clock. Files: `src/timeline.ts` 2. Select blocks into one entry Combining several blocks is the normal case, not the exception. Files: `src/entries.ts` 3. Suggest, never assign An unreviewed automatic timesheet gets invoiced. That is the failure. Files: `src/suggest.ts` ### Done when - [ ] A day reconstructs into entries in a few minutes - [ ] Several blocks combine into one entry - [ ] No entry exists without approval - [ ] An entry cannot exceed its source blocks ### Watch out - Automatically assigning blocks to projects by matching window titles produces confident, wrong timesheets, and a wrong timesheet that was never reviewed is worse than an empty one because it gets invoiced. Suggest a project, never assign one, and require the approval action for every entry. ## Phase 4 · Rounding and billing rules, stated honestly Exported time matches the rule you agreed with the client. ### Steps 1. Round at export only Store actual. A client asking what the real figure was deserves an answer. Files: `src/rounding.ts`, `src/rounding.test.ts` 2. Show both figures Actual and rounded, side by side. Hiding the difference is how disputes start. Files: `src/export.ts` 3. Handle midnight and daylight transitions Both are ordinary. Both break naive duration arithmetic. Files: `src/duration.ts` ### Done when - [ ] Rounding applies at export and shows both figures - [ ] The stored entry is unchanged by rounding - [ ] An entry crossing midnight is handled - [ ] Both daylight transitions produce correct durations ### Watch out - Rounding the stored entry destroys the actual figure, so you can never answer a client asking what the real time was, and repeated rounding on edits compounds the error. Store actual, round at export, and show both. ## Not in this build - Team deployment with manager visibility. - Automatic project assignment from window content. - Screenshot capture. - Mobile and browser tab level detail. ## After v1, if you want it - Calendar events shown alongside blocks, which fills the gaps capture cannot see - A weekly reconstruction prompt, since the recall value decays quickly - Invoice generation from approved entries, once the rounding rules are trustworthy ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Raw capture. The most sensitive file this build produces. Encrypted at rest. DB_PATH=~/.local/share/memtime/capture.db # Required · secret. Encrypts the capture store. Without it the raw titles sit in plain text. DB_KEY=a passphrase or keychain reference # Required. How often focus is sampled. Shorter is more accurate and more intrusive. SAMPLE_INTERVAL_SECONDS=15 # Required. When you are considered away. Blocks end at idle rather than continuing. IDLE_THRESHOLD_SECONDS=180 # Required. How long raw samples live. Short, because their usefulness drops fast and their sensitivity does not. RAW_RETENTION_DAYS=30 # Required. Loaded by the sampler before the first write. Not a display filter. EXCLUSIONS_PATH=./config/exclusions.txt
You are building a production product version of Memtime. 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 ===== # Memtime · product brief ## Problem The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Memtime, record local app activity privately and convert selected blocks into timesheet entries. The hard boundary is calendar and project integrations, team reporting, sync, and enterprise controls, plus ubiquitous capture, coaching, and team workflow. ## Product outcome A recorder that lets you rebuild a day you forgot to track, where the raw record of what you looked at never leaves your machine and nothing reaches a timesheet without you putting it there. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - desktop OS - notification permission - optional accessibility permission for app-name tracking - local SQLite ## Explicit non-goals for v1 - Team deployment with manager visibility. - Automatic project assignment from window content. - Screenshot capture. - Mobile and browser tab level detail. - calendar and project integrations, team reporting, sync, and enterprise controls - cross-device history - team timesheets and payroll - behavioral datasets - blocking across every platform ## Success criteria - An excluded application is confirmed to produce no database row - The suite is confirmed to fail when a raw field is added to an export path - No entry is confirmed to exist without approval - The stored entry is confirmed unchanged by rounding - Raw retention is confirmed to delete samples while entries survive ===== BRIEF.md ===== # Build brief · Memtime 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 passive activity recorder that turns your day into timesheet entries you approve to replace Memtime. 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 Passive capture records window titles, and window titles are not metadata. They contain document names, message subjects, the URL of every page, the name of the person you were messaging and the title of the medical appointment in your calendar. A day of window titles is a more revealing record of someone than a day of screenshots would be easy to search. So: capture stays on the machine, it never syncs raw, only approved timesheet entries leave, there is a pause that is genuinely a pause, and there is an exclusion list applied before anything is written to disk rather than filtered at display. If you are considering deploying this to other people, the answer is that a tool which records what your colleagues type into their window titles and reports it to their manager is surveillance regardless of what it is called. Build it for yourself. If it must be for a team, the raw capture must never be visible to anyone but the person it came from, and that has to be an architectural guarantee rather than a policy. ### Stack (fixed, do not substitute) - Capture: A local agent polling the focused window and idle state · this is the only way to reconstruct a day you did not track as it happened - Storage: SQLite on the machine, encrypted at rest · raw capture is the most sensitive data in the build - Grouping: Segmentation into blocks by application, document and gap · a raw event stream is unreadable and useless for reconstruction - Approval: Nothing becomes a timesheet entry without an explicit action · the whole model is recall and review, not automatic reporting - Export: Approved entries only, in a format your billing system takes · the boundary between capture and export is the privacy boundary ### Data model (create this before Phase 1) - samples(at, app, window_title, document, idle_seconds) · the raw stream, local only, never exported - blocks(id, started_at, ended_at, app, title_summary, sample_count) · derived by segmentation - exclusions(id, kind, pattern, created_at) · kind is app or title. Applied before writing a sample - projects(id, name, client, billable, archived_at) - entries(id, project_id, started_at, ended_at, description, approved_at, exported_at) - entry_blocks(entry_id, block_id) · which capture a timesheet entry was built from, kept locally - The link from an entry back to its blocks stays on the machine. What leaves is the entry alone. ### Phase 1 · Capture, with the exclusions applied first Build: An agent sampling the focused application, window title and idle time at an interval. Apply an exclusion list before writing, so an excluded window produces no row at all. A pause that stops sampling entirely. Encrypt the store at rest. Trap: Filtering excluded applications at display time means the sensitive titles are still on disk, still in backups, and still readable by anything that can open the database, which is exactly the outcome the exclusion was meant to prevent. Apply exclusions in the sampler, before the write. Done when: An excluded application produces no row at all, verified by inspecting the database directly rather than the interface. Pausing stops sampling and the gap is visible rather than interpolated. The store is encrypted at rest. A day of sampling costs a negligible amount of processor time, measured. Do not build yet: No grouping, projects or export. ### Phase 2 · Turning a fragmented day into blocks Build: Segment samples into blocks by application and document, merging across short interruptions and splitting on real gaps. Summarise each block's titles into something readable. Treat idle time as a boundary rather than as work. Trap: Segmenting on every application change produces four hundred blocks for a normal day, which is unreadable and defeats the purpose, while merging too aggressively produces one block called various and tells you nothing. The gap threshold and the merge rules are the actual design work here and they need tuning against your own real day. Done when: A real day produces a readable number of blocks, checked against your own memory of that day. A brief switch to another application does not split a block. A genuine gap does split one. Idle periods appear as gaps rather than as time spent in whatever was focused. Do not build yet: No projects or approval yet. ### Phase 3 · Reconstructing a day you did not track Build: A timeline view of blocks with a scrubber, showing what was on screen at any moment. Select one or several blocks and turn them into a timesheet entry against a project. Nothing becomes an entry without that action. Trap: Automatically assigning blocks to projects by matching window titles produces confident, wrong timesheets, and a wrong timesheet that was never reviewed is worse than an empty one because it gets invoiced. Suggest a project, never assign one, and require the approval action for every entry. Done when: A day is reconstructed into entries in a few minutes, timed by hand. Selecting several blocks produces one entry with the combined duration. Suggestions appear but nothing is assigned without approval, verified by leaving the day untouched and confirming no entries exist. An entry's duration cannot exceed the blocks it came from. Do not build yet: No rounding, export or reporting yet. ### Phase 4 · Rounding and billing rules, stated honestly Build: Per-project rounding and minimum increments applied at export, showing the actual and rounded durations side by side. Never round the stored entry. Handle an entry crossing midnight and a day with a daylight saving transition. Trap: Rounding the stored entry destroys the actual figure, so you can never answer a client asking what the real time was, and repeated rounding on edits compounds the error. Store actual, round at export, and show both. Done when: Rounding applies at export with actual and rounded shown together. The stored entry is never modified by rounding, verified by exporting twice and comparing. An entry crossing midnight is handled per the project's rule. A day with a daylight saving transition produces correct durations, tested on both transitions. Do not build yet: No reporting or team export yet. ### Phase 5 · Retention, and the boundary that must hold Build: Retention deleting raw samples after a short period while keeping approved entries. Export containing entries only. Assert in the test suite that no export path can emit a window title. A deletion that removes everything on request. Trap: An export or a crash report that includes window titles breaks the entire privacy model in one line of code, and it will be added by someone trying to make debugging easier. Assert it in the suite: scan every export and log path for the raw fields and fail the build if they appear. Done when: Raw samples are deleted after the retention period while approved entries survive, tested by advancing the clock. A test asserts no export or log path emits a window title, and fails when one is deliberately added. Deletion removes samples, blocks, entries and backups. Retention runs without the application being open. Do not build yet: Nothing after this. ### Out of scope (and why) - Team deployment with manager visibility, which turns this from a memory aid into surveillance and is a different product with different ethics. - Automatic project assignment from window content, which is the feature request that arrives first and the one that produces confidently wrong invoices. - Screenshot capture, which is a categorically larger intrusion than titles and is not made acceptable by being local. - Mobile and browser tab level detail, where the platform restrictions are severe and the privacy cost rises sharply. ### README must contain - That exclusions are applied in the sampler so excluded windows never reach the disk - That raw capture never leaves the machine and a test asserts no export path can emit a title - That nothing becomes a timesheet entry without an explicit approval - That rounding happens at export and the stored entry keeps the actual duration - That raw samples expire on a short retention while approved entries persist ===== ARCHITECTURE.md ===== # Architecture · Memtime ## Stack | Part | Choice | Why | | --- | --- | --- | | Capture | A local agent polling the focused window and idle state | this is the only way to reconstruct a day you did not track as it happened | | Storage | SQLite on the machine, encrypted at rest | raw capture is the most sensitive data in the build | | Grouping | Segmentation into blocks by application, document and gap | a raw event stream is unreadable and useless for reconstruction | | Approval | Nothing becomes a timesheet entry without an explicit action | the whole model is recall and review, not automatic reporting | | Export | Approved entries only, in a format your billing system takes | the boundary between capture and export is the privacy boundary | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Capture | Sampling and applying exclusions before the write. | Platform-specific. Applying exclusions in the sampler rather than at display is the requirement. | | Segmentation | Turning samples into readable blocks. | Any algorithm, tuned against a real day rather than a synthetic one. | | Approval | Blocks becoming entries only by an action. | Not swappable for automatic assignment, which produces wrong invoices. | | Rounding | Billing rules applied at export. | Any rules, provided the stored entry keeps the actual duration. | | Boundary | Keeping raw capture off every export path. | Not a module to swap but an assertion in the suite that must never be deleted. | ## 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 · Raw capture. The most sensitive file this build produces. Encrypted at rest. - `DB_KEY` · required, secret · Encrypts the capture store. Without it the raw titles sit in plain text. - `SAMPLE_INTERVAL_SECONDS` · required · How often focus is sampled. Shorter is more accurate and more intrusive. - `IDLE_THRESHOLD_SECONDS` · required · When you are considered away. Blocks end at idle rather than continuing. - `RAW_RETENTION_DAYS` · required · How long raw samples live. Short, because their usefulness drops fast and their sensitivity does not. - `EXCLUSIONS_PATH` · required · Loaded by the sampler before the first write. Not a display filter. ## 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 · Memtime product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A local agent polling the focused window and idle state, SQLite on the machine, encrypted at rest, Segmentation into blocks by application, document and gap, Nothing becomes a timesheet entry without an explicit action, Approved entries only, in a format your billing system takes. - 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 - Filtering excluded applications at display time means the sensitive titles are still on disk, still in backups, and still readable by anything that can open the database, which is exactly the outcome the exclusion was meant to prevent. Apply exclusions in the sampler, before the write. - Segmenting on every application change produces four hundred blocks for a normal day, which is unreadable and defeats the purpose, while merging too aggressively produces one block called various and tells you nothing. The gap threshold and the merge rules are the actual design work here and they need tuning against your own real day. - Automatically assigning blocks to projects by matching window titles produces confident, wrong timesheets, and a wrong timesheet that was never reviewed is worse than an empty one because it gets invoiced. Suggest a project, never assign one, and require the approval action for every entry. - Rounding the stored entry destroys the actual figure, so you can never answer a client asking what the real time was, and repeated rounding on edits compounds the error. Store actual, round at export, and show both. - An export or a crash report that includes window titles breaks the entire privacy model in one line of code, and it will be added by someone trying to make debugging easier. Assert it in the suite: scan every export and log path for the raw fields and fail the build if they appear. ===== MILESTONES.md ===== # Delivery milestones · Memtime Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Capture, with the exclusions applied first Your day is recorded, minus everything you said not to record. ### Steps 1. Sample focus and idle at an interval Application, title, document, idle seconds. Nothing else. Files: `src/capture.ts` 2. Apply exclusions before writing In the sampler. Display filtering leaves the data on disk. Files: `src/exclude.ts`, `src/exclude.test.ts` 3. Make pause a real pause No sampling, and a visible gap rather than an interpolated one. Files: `src/capture.ts` ### Done when - [ ] An excluded app produces no row in the database - [ ] Pause stops sampling and leaves a visible gap - [ ] The store is encrypted at rest - [ ] Sampling costs negligible processor time ### Watch out - Filtering excluded applications at display time means the sensitive titles are still on disk, still in backups, and still readable by anything that can open the database, which is exactly the outcome the exclusion was meant to prevent. Apply exclusions in the sampler, before the write. ## M2 · Turning a fragmented day into blocks A day of thousands of samples reads as a dozen recognisable blocks. ### Steps 1. Segment with a tuned gap threshold Tune it against your own day. This is the design work, not a constant to guess. Files: `src/segment.ts`, `src/segment.test.ts` 2. Merge across brief interruptions Checking a message does not end a block of work. Files: `src/segment.ts` 3. Summarise titles readably A block needs a label you recognise, not a list of forty window titles. Files: `src/summarise.ts` ### Done when - [ ] A real day produces a readable number of blocks - [ ] A brief switch does not split a block - [ ] A genuine gap does split one - [ ] Idle appears as a gap rather than as work ### Watch out - Segmenting on every application change produces four hundred blocks for a normal day, which is unreadable and defeats the purpose, while merging too aggressively produces one block called various and tells you nothing. The gap threshold and the merge rules are the actual design work here and they need tuning against your own real day. ## M3 · Reconstructing a day you did not track You can sit down at five and rebuild the day accurately. ### Steps 1. Show the day as a scrubable timeline The point is recall. You need to see what was on screen at four o'clock. Files: `src/timeline.ts` 2. Select blocks into one entry Combining several blocks is the normal case, not the exception. Files: `src/entries.ts` 3. Suggest, never assign An unreviewed automatic timesheet gets invoiced. That is the failure. Files: `src/suggest.ts` ### Done when - [ ] A day reconstructs into entries in a few minutes - [ ] Several blocks combine into one entry - [ ] No entry exists without approval - [ ] An entry cannot exceed its source blocks ### Watch out - Automatically assigning blocks to projects by matching window titles produces confident, wrong timesheets, and a wrong timesheet that was never reviewed is worse than an empty one because it gets invoiced. Suggest a project, never assign one, and require the approval action for every entry. ## M4 · Rounding and billing rules, stated honestly Exported time matches the rule you agreed with the client. ### Steps 1. Round at export only Store actual. A client asking what the real figure was deserves an answer. Files: `src/rounding.ts`, `src/rounding.test.ts` 2. Show both figures Actual and rounded, side by side. Hiding the difference is how disputes start. Files: `src/export.ts` 3. Handle midnight and daylight transitions Both are ordinary. Both break naive duration arithmetic. Files: `src/duration.ts` ### Done when - [ ] Rounding applies at export and shows both figures - [ ] The stored entry is unchanged by rounding - [ ] An entry crossing midnight is handled - [ ] Both daylight transitions produce correct durations ### Watch out - Rounding the stored entry destroys the actual figure, so you can never answer a client asking what the real time was, and repeated rounding on edits compounds the error. Store actual, round at export, and show both. ## M5 · Retention, and the boundary that must hold (production only) Raw capture expires, approved entries persist, and nothing raw ever leaves. ### Steps 1. Expire raw samples on a short period They are the sensitive data and their value drops to nothing within weeks. Files: `src/retention.ts` 2. Assert the boundary in the suite Scan export and log paths for raw fields. Fail the build if any appear. Files: `tests/no-raw-export.test.ts` 3. Delete completely on request Samples, blocks, entries, backups. Everything. Files: `src/deletion.ts` ### Done when - [ ] Raw samples expire while entries survive - [ ] A test fails when a raw field is added to an export path - [ ] Deletion removes everything including backups - [ ] Retention runs without the application open ### Watch out - An export or a crash report that includes window titles breaks the entire privacy model in one line of code, and it will be added by someone trying to make debugging easier. Assert it in the suite: scan every export and log path for the raw fields and fail the build if they appear. ===== OPERATIONS.md ===== # Operations · Memtime ## Backup Approved entries are the valuable data and are safe to back up. Raw capture is sensitive and short-lived, so consider excluding it from backups entirely rather than encrypting it and hoping. ## Restore Restore entries and confirm retention resumes, since a restore that reinstates expired raw samples has recreated exactly the data the retention policy existed to remove. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a personal tool the thing to check is that retention actually ran, because a silently failing retention job leaves months of window titles on disk while the interface claims a thirty day policy. ## Incident checklist A window title appearing in an export, a log or a crash report is the incident. The suite assertion is the control, and if it was removed to make debugging easier that is the change to revert. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] An excluded application is confirmed to produce no database row - [ ] The suite is confirmed to fail when a raw field is added to an export path - [ ] No entry is confirmed to exist without approval - [ ] The stored entry is confirmed unchanged by rounding - [ ] Raw retention is confirmed to delete samples while entries survive ## Launch constraint Do not market omitted Memtime 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. Raw capture. The most sensitive file this build produces. Encrypted at rest. DB_PATH=~/.local/share/memtime/capture.db # Required · secret. Encrypts the capture store. Without it the raw titles sit in plain text. DB_KEY=a passphrase or keychain reference # Required. How often focus is sampled. Shorter is more accurate and more intrusive. SAMPLE_INTERVAL_SECONDS=15 # Required. When you are considered away. Blocks end at idle rather than continuing. IDLE_THRESHOLD_SECONDS=180 # Required. How long raw samples live. Short, because their usefulness drops fast and their sensitivity does not. RAW_RETENTION_DAYS=30 # Required. Loaded by the sampler before the first write. Not a display filter. EXCLUSIONS_PATH=./config/exclusions.txt
# Memtime · indie build A local activity recorder that watches which application and document you were in, groups the day into blocks, and lets you turn selected blocks into timesheet entries. The engineering is in grouping a fragmented day into something recognisable, and the design constraint is that raw capture never leaves the machine. 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 | | --- | --- | --- | | Capture | A local agent polling the focused window and idle state | this is the only way to reconstruct a day you did not track as it happened | | Storage | SQLite on the machine, encrypted at rest | raw capture is the most sensitive data in the build | | Grouping | Segmentation into blocks by application, document and gap | a raw event stream is unreadable and useless for reconstruction | | Approval | Nothing becomes a timesheet entry without an explicit action | the whole model is recall and review, not automatic reporting | | Export | Approved entries only, in a format your billing system takes | the boundary between capture and export is the privacy boundary | ## 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 agent and the review interface. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A way to read the focused window and idle time** · free - Why: This is platform-specific and on some systems requires an accessibility or screen recording permission, which changes what you must tell the user. - Get it: Whatever your platform provides, and find out which permission it needs before designing the onboarding. - Verify: The focused application, its window title and idle seconds can all be read - [ ] **Your exclusion list, written before capture** · free - Why: Deciding what not to record after you have a month of recordings is deciding it too late, since the data already exists. - Get it: List the applications and title patterns that must never be stored: banking, health, private messaging, anything belonging to someone else. Err wide. - Verify: The list exists and is loaded by the sampler before the first run - [ ] **A week of your own real working days** · free, and it takes a week - Why: Segmentation thresholds and block summarisation can only be tuned against a real fragmented day, and a synthetic one is far tidier than reality. - Get it: Run the capture for a week and reconstruct each day from memory to compare against. - Verify: A week of capture exists and you have tried to reconstruct at least three days from it ## 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: - Team deployment with manager visibility. - Automatic project assignment from window content. - Screenshot capture. - Mobile and browser tab level detail. - calendar and project integrations, team reporting, sync, and enterprise controls - cross-device history - team timesheets and payroll - behavioral datasets - blocking across every platform If one of those is essential to you, that is the reason to keep paying for Memtime, and the README should say so rather than pretend.
# Build brief · Memtime 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 passive activity recorder that turns your day into timesheet entries you approve to replace Memtime. 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 Passive capture records window titles, and window titles are not metadata. They contain document names, message subjects, the URL of every page, the name of the person you were messaging and the title of the medical appointment in your calendar. A day of window titles is a more revealing record of someone than a day of screenshots would be easy to search. So: capture stays on the machine, it never syncs raw, only approved timesheet entries leave, there is a pause that is genuinely a pause, and there is an exclusion list applied before anything is written to disk rather than filtered at display. If you are considering deploying this to other people, the answer is that a tool which records what your colleagues type into their window titles and reports it to their manager is surveillance regardless of what it is called. Build it for yourself. If it must be for a team, the raw capture must never be visible to anyone but the person it came from, and that has to be an architectural guarantee rather than a policy. ### Stack (fixed, do not substitute) - Capture: A local agent polling the focused window and idle state · this is the only way to reconstruct a day you did not track as it happened - Storage: SQLite on the machine, encrypted at rest · raw capture is the most sensitive data in the build - Grouping: Segmentation into blocks by application, document and gap · a raw event stream is unreadable and useless for reconstruction - Approval: Nothing becomes a timesheet entry without an explicit action · the whole model is recall and review, not automatic reporting - Export: Approved entries only, in a format your billing system takes · the boundary between capture and export is the privacy boundary ### Data model (create this before Phase 1) - samples(at, app, window_title, document, idle_seconds) · the raw stream, local only, never exported - blocks(id, started_at, ended_at, app, title_summary, sample_count) · derived by segmentation - exclusions(id, kind, pattern, created_at) · kind is app or title. Applied before writing a sample - projects(id, name, client, billable, archived_at) - entries(id, project_id, started_at, ended_at, description, approved_at, exported_at) - entry_blocks(entry_id, block_id) · which capture a timesheet entry was built from, kept locally - The link from an entry back to its blocks stays on the machine. What leaves is the entry alone. ### Phase 1 · Capture, with the exclusions applied first Build: An agent sampling the focused application, window title and idle time at an interval. Apply an exclusion list before writing, so an excluded window produces no row at all. A pause that stops sampling entirely. Encrypt the store at rest. Trap: Filtering excluded applications at display time means the sensitive titles are still on disk, still in backups, and still readable by anything that can open the database, which is exactly the outcome the exclusion was meant to prevent. Apply exclusions in the sampler, before the write. Done when: An excluded application produces no row at all, verified by inspecting the database directly rather than the interface. Pausing stops sampling and the gap is visible rather than interpolated. The store is encrypted at rest. A day of sampling costs a negligible amount of processor time, measured. Do not build yet: No grouping, projects or export. ### Phase 2 · Turning a fragmented day into blocks Build: Segment samples into blocks by application and document, merging across short interruptions and splitting on real gaps. Summarise each block's titles into something readable. Treat idle time as a boundary rather than as work. Trap: Segmenting on every application change produces four hundred blocks for a normal day, which is unreadable and defeats the purpose, while merging too aggressively produces one block called various and tells you nothing. The gap threshold and the merge rules are the actual design work here and they need tuning against your own real day. Done when: A real day produces a readable number of blocks, checked against your own memory of that day. A brief switch to another application does not split a block. A genuine gap does split one. Idle periods appear as gaps rather than as time spent in whatever was focused. Do not build yet: No projects or approval yet. ### Phase 3 · Reconstructing a day you did not track Build: A timeline view of blocks with a scrubber, showing what was on screen at any moment. Select one or several blocks and turn them into a timesheet entry against a project. Nothing becomes an entry without that action. Trap: Automatically assigning blocks to projects by matching window titles produces confident, wrong timesheets, and a wrong timesheet that was never reviewed is worse than an empty one because it gets invoiced. Suggest a project, never assign one, and require the approval action for every entry. Done when: A day is reconstructed into entries in a few minutes, timed by hand. Selecting several blocks produces one entry with the combined duration. Suggestions appear but nothing is assigned without approval, verified by leaving the day untouched and confirming no entries exist. An entry's duration cannot exceed the blocks it came from. Do not build yet: No rounding, export or reporting yet. ### Phase 4 · Rounding and billing rules, stated honestly Build: Per-project rounding and minimum increments applied at export, showing the actual and rounded durations side by side. Never round the stored entry. Handle an entry crossing midnight and a day with a daylight saving transition. Trap: Rounding the stored entry destroys the actual figure, so you can never answer a client asking what the real time was, and repeated rounding on edits compounds the error. Store actual, round at export, and show both. Done when: Rounding applies at export with actual and rounded shown together. The stored entry is never modified by rounding, verified by exporting twice and comparing. An entry crossing midnight is handled per the project's rule. A day with a daylight saving transition produces correct durations, tested on both transitions. Do not build yet: No reporting or team export yet. ### Phase 5 · Retention, and the boundary that must hold Build: Retention deleting raw samples after a short period while keeping approved entries. Export containing entries only. Assert in the test suite that no export path can emit a window title. A deletion that removes everything on request. Trap: An export or a crash report that includes window titles breaks the entire privacy model in one line of code, and it will be added by someone trying to make debugging easier. Assert it in the suite: scan every export and log path for the raw fields and fail the build if they appear. Done when: Raw samples are deleted after the retention period while approved entries survive, tested by advancing the clock. A test asserts no export or log path emits a window title, and fails when one is deliberately added. Deletion removes samples, blocks, entries and backups. Retention runs without the application being open. Do not build yet: Nothing after this. ### Out of scope (and why) - Team deployment with manager visibility, which turns this from a memory aid into surveillance and is a different product with different ethics. - Automatic project assignment from window content, which is the feature request that arrives first and the one that produces confidently wrong invoices. - Screenshot capture, which is a categorically larger intrusion than titles and is not made acceptable by being local. - Mobile and browser tab level detail, where the platform restrictions are severe and the privacy cost rises sharply. ### README must contain - That exclusions are applied in the sampler so excluded windows never reach the disk - That raw capture never leaves the machine and a test asserts no export path can emit a title - That nothing becomes a timesheet entry without an explicit approval - That rounding happens at export and the stored entry keeps the actual duration - That raw samples expire on a short retention while approved entries persist
# Agent instructions · Memtime indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A local agent polling the focused window and idle state, SQLite on the machine, encrypted at rest, Segmentation into blocks by application, document and gap, Nothing becomes a timesheet entry without an explicit action, Approved entries only, in a format your billing system takes. 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 - Filtering excluded applications at display time means the sensitive titles are still on disk, still in backups, and still readable by anything that can open the database, which is exactly the outcome the exclusion was meant to prevent. Apply exclusions in the sampler, before the write. - Segmenting on every application change produces four hundred blocks for a normal day, which is unreadable and defeats the purpose, while merging too aggressively produces one block called various and tells you nothing. The gap threshold and the merge rules are the actual design work here and they need tuning against your own real day. - Automatically assigning blocks to projects by matching window titles produces confident, wrong timesheets, and a wrong timesheet that was never reviewed is worse than an empty one because it gets invoiced. Suggest a project, never assign one, and require the approval action for every entry. - Rounding the stored entry destroys the actual figure, so you can never answer a client asking what the real time was, and repeated rounding on edits compounds the error. Store actual, round at export, and show both.
# Build plan · Memtime A local activity recorder that watches which application and document you were in, groups the day into blocks, and lets you turn selected blocks into timesheet entries. The engineering is in grouping a fragmented day into something recognisable, and the design constraint is that raw capture never leaves the machine. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Capture, with the exclusions applied first Your day is recorded, minus everything you said not to record. ### Steps 1. Sample focus and idle at an interval Application, title, document, idle seconds. Nothing else. Files: `src/capture.ts` 2. Apply exclusions before writing In the sampler. Display filtering leaves the data on disk. Files: `src/exclude.ts`, `src/exclude.test.ts` 3. Make pause a real pause No sampling, and a visible gap rather than an interpolated one. Files: `src/capture.ts` ### Done when - [ ] An excluded app produces no row in the database - [ ] Pause stops sampling and leaves a visible gap - [ ] The store is encrypted at rest - [ ] Sampling costs negligible processor time ### Watch out - Filtering excluded applications at display time means the sensitive titles are still on disk, still in backups, and still readable by anything that can open the database, which is exactly the outcome the exclusion was meant to prevent. Apply exclusions in the sampler, before the write. ## Phase 2 · Turning a fragmented day into blocks A day of thousands of samples reads as a dozen recognisable blocks. ### Steps 1. Segment with a tuned gap threshold Tune it against your own day. This is the design work, not a constant to guess. Files: `src/segment.ts`, `src/segment.test.ts` 2. Merge across brief interruptions Checking a message does not end a block of work. Files: `src/segment.ts` 3. Summarise titles readably A block needs a label you recognise, not a list of forty window titles. Files: `src/summarise.ts` ### Done when - [ ] A real day produces a readable number of blocks - [ ] A brief switch does not split a block - [ ] A genuine gap does split one - [ ] Idle appears as a gap rather than as work ### Watch out - Segmenting on every application change produces four hundred blocks for a normal day, which is unreadable and defeats the purpose, while merging too aggressively produces one block called various and tells you nothing. The gap threshold and the merge rules are the actual design work here and they need tuning against your own real day. ## Phase 3 · Reconstructing a day you did not track You can sit down at five and rebuild the day accurately. ### Steps 1. Show the day as a scrubable timeline The point is recall. You need to see what was on screen at four o'clock. Files: `src/timeline.ts` 2. Select blocks into one entry Combining several blocks is the normal case, not the exception. Files: `src/entries.ts` 3. Suggest, never assign An unreviewed automatic timesheet gets invoiced. That is the failure. Files: `src/suggest.ts` ### Done when - [ ] A day reconstructs into entries in a few minutes - [ ] Several blocks combine into one entry - [ ] No entry exists without approval - [ ] An entry cannot exceed its source blocks ### Watch out - Automatically assigning blocks to projects by matching window titles produces confident, wrong timesheets, and a wrong timesheet that was never reviewed is worse than an empty one because it gets invoiced. Suggest a project, never assign one, and require the approval action for every entry. ## Phase 4 · Rounding and billing rules, stated honestly Exported time matches the rule you agreed with the client. ### Steps 1. Round at export only Store actual. A client asking what the real figure was deserves an answer. Files: `src/rounding.ts`, `src/rounding.test.ts` 2. Show both figures Actual and rounded, side by side. Hiding the difference is how disputes start. Files: `src/export.ts` 3. Handle midnight and daylight transitions Both are ordinary. Both break naive duration arithmetic. Files: `src/duration.ts` ### Done when - [ ] Rounding applies at export and shows both figures - [ ] The stored entry is unchanged by rounding - [ ] An entry crossing midnight is handled - [ ] Both daylight transitions produce correct durations ### Watch out - Rounding the stored entry destroys the actual figure, so you can never answer a client asking what the real time was, and repeated rounding on edits compounds the error. Store actual, round at export, and show both. ## Not in this build - Team deployment with manager visibility. - Automatic project assignment from window content. - Screenshot capture. - Mobile and browser tab level detail. ## After v1, if you want it - Calendar events shown alongside blocks, which fills the gaps capture cannot see - A weekly reconstruction prompt, since the recall value decays quickly - Invoice generation from approved entries, once the rounding rules are trustworthy
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Raw capture. The most sensitive file this build produces. Encrypted at rest. DB_PATH=~/.local/share/memtime/capture.db # Required · secret. Encrypts the capture store. Without it the raw titles sit in plain text. DB_KEY=a passphrase or keychain reference # Required. How often focus is sampled. Shorter is more accurate and more intrusive. SAMPLE_INTERVAL_SECONDS=15 # Required. When you are considered away. Blocks end at idle rather than continuing. IDLE_THRESHOLD_SECONDS=180 # Required. How long raw samples live. Short, because their usefulness drops fast and their sensitivity does not. RAW_RETENTION_DAYS=30 # Required. Loaded by the sampler before the first write. Not a display filter. EXCLUSIONS_PATH=./config/exclusions.txt
# Memtime · product brief ## Problem The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Memtime, record local app activity privately and convert selected blocks into timesheet entries. The hard boundary is calendar and project integrations, team reporting, sync, and enterprise controls, plus ubiquitous capture, coaching, and team workflow. ## Product outcome A recorder that lets you rebuild a day you forgot to track, where the raw record of what you looked at never leaves your machine and nothing reaches a timesheet without you putting it there. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - desktop OS - notification permission - optional accessibility permission for app-name tracking - local SQLite ## Explicit non-goals for v1 - Team deployment with manager visibility. - Automatic project assignment from window content. - Screenshot capture. - Mobile and browser tab level detail. - calendar and project integrations, team reporting, sync, and enterprise controls - cross-device history - team timesheets and payroll - behavioral datasets - blocking across every platform ## Success criteria - An excluded application is confirmed to produce no database row - The suite is confirmed to fail when a raw field is added to an export path - No entry is confirmed to exist without approval - The stored entry is confirmed unchanged by rounding - Raw retention is confirmed to delete samples while entries survive
# Build brief · Memtime 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 passive activity recorder that turns your day into timesheet entries you approve to replace Memtime. 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 Passive capture records window titles, and window titles are not metadata. They contain document names, message subjects, the URL of every page, the name of the person you were messaging and the title of the medical appointment in your calendar. A day of window titles is a more revealing record of someone than a day of screenshots would be easy to search. So: capture stays on the machine, it never syncs raw, only approved timesheet entries leave, there is a pause that is genuinely a pause, and there is an exclusion list applied before anything is written to disk rather than filtered at display. If you are considering deploying this to other people, the answer is that a tool which records what your colleagues type into their window titles and reports it to their manager is surveillance regardless of what it is called. Build it for yourself. If it must be for a team, the raw capture must never be visible to anyone but the person it came from, and that has to be an architectural guarantee rather than a policy. ### Stack (fixed, do not substitute) - Capture: A local agent polling the focused window and idle state · this is the only way to reconstruct a day you did not track as it happened - Storage: SQLite on the machine, encrypted at rest · raw capture is the most sensitive data in the build - Grouping: Segmentation into blocks by application, document and gap · a raw event stream is unreadable and useless for reconstruction - Approval: Nothing becomes a timesheet entry without an explicit action · the whole model is recall and review, not automatic reporting - Export: Approved entries only, in a format your billing system takes · the boundary between capture and export is the privacy boundary ### Data model (create this before Phase 1) - samples(at, app, window_title, document, idle_seconds) · the raw stream, local only, never exported - blocks(id, started_at, ended_at, app, title_summary, sample_count) · derived by segmentation - exclusions(id, kind, pattern, created_at) · kind is app or title. Applied before writing a sample - projects(id, name, client, billable, archived_at) - entries(id, project_id, started_at, ended_at, description, approved_at, exported_at) - entry_blocks(entry_id, block_id) · which capture a timesheet entry was built from, kept locally - The link from an entry back to its blocks stays on the machine. What leaves is the entry alone. ### Phase 1 · Capture, with the exclusions applied first Build: An agent sampling the focused application, window title and idle time at an interval. Apply an exclusion list before writing, so an excluded window produces no row at all. A pause that stops sampling entirely. Encrypt the store at rest. Trap: Filtering excluded applications at display time means the sensitive titles are still on disk, still in backups, and still readable by anything that can open the database, which is exactly the outcome the exclusion was meant to prevent. Apply exclusions in the sampler, before the write. Done when: An excluded application produces no row at all, verified by inspecting the database directly rather than the interface. Pausing stops sampling and the gap is visible rather than interpolated. The store is encrypted at rest. A day of sampling costs a negligible amount of processor time, measured. Do not build yet: No grouping, projects or export. ### Phase 2 · Turning a fragmented day into blocks Build: Segment samples into blocks by application and document, merging across short interruptions and splitting on real gaps. Summarise each block's titles into something readable. Treat idle time as a boundary rather than as work. Trap: Segmenting on every application change produces four hundred blocks for a normal day, which is unreadable and defeats the purpose, while merging too aggressively produces one block called various and tells you nothing. The gap threshold and the merge rules are the actual design work here and they need tuning against your own real day. Done when: A real day produces a readable number of blocks, checked against your own memory of that day. A brief switch to another application does not split a block. A genuine gap does split one. Idle periods appear as gaps rather than as time spent in whatever was focused. Do not build yet: No projects or approval yet. ### Phase 3 · Reconstructing a day you did not track Build: A timeline view of blocks with a scrubber, showing what was on screen at any moment. Select one or several blocks and turn them into a timesheet entry against a project. Nothing becomes an entry without that action. Trap: Automatically assigning blocks to projects by matching window titles produces confident, wrong timesheets, and a wrong timesheet that was never reviewed is worse than an empty one because it gets invoiced. Suggest a project, never assign one, and require the approval action for every entry. Done when: A day is reconstructed into entries in a few minutes, timed by hand. Selecting several blocks produces one entry with the combined duration. Suggestions appear but nothing is assigned without approval, verified by leaving the day untouched and confirming no entries exist. An entry's duration cannot exceed the blocks it came from. Do not build yet: No rounding, export or reporting yet. ### Phase 4 · Rounding and billing rules, stated honestly Build: Per-project rounding and minimum increments applied at export, showing the actual and rounded durations side by side. Never round the stored entry. Handle an entry crossing midnight and a day with a daylight saving transition. Trap: Rounding the stored entry destroys the actual figure, so you can never answer a client asking what the real time was, and repeated rounding on edits compounds the error. Store actual, round at export, and show both. Done when: Rounding applies at export with actual and rounded shown together. The stored entry is never modified by rounding, verified by exporting twice and comparing. An entry crossing midnight is handled per the project's rule. A day with a daylight saving transition produces correct durations, tested on both transitions. Do not build yet: No reporting or team export yet. ### Phase 5 · Retention, and the boundary that must hold Build: Retention deleting raw samples after a short period while keeping approved entries. Export containing entries only. Assert in the test suite that no export path can emit a window title. A deletion that removes everything on request. Trap: An export or a crash report that includes window titles breaks the entire privacy model in one line of code, and it will be added by someone trying to make debugging easier. Assert it in the suite: scan every export and log path for the raw fields and fail the build if they appear. Done when: Raw samples are deleted after the retention period while approved entries survive, tested by advancing the clock. A test asserts no export or log path emits a window title, and fails when one is deliberately added. Deletion removes samples, blocks, entries and backups. Retention runs without the application being open. Do not build yet: Nothing after this. ### Out of scope (and why) - Team deployment with manager visibility, which turns this from a memory aid into surveillance and is a different product with different ethics. - Automatic project assignment from window content, which is the feature request that arrives first and the one that produces confidently wrong invoices. - Screenshot capture, which is a categorically larger intrusion than titles and is not made acceptable by being local. - Mobile and browser tab level detail, where the platform restrictions are severe and the privacy cost rises sharply. ### README must contain - That exclusions are applied in the sampler so excluded windows never reach the disk - That raw capture never leaves the machine and a test asserts no export path can emit a title - That nothing becomes a timesheet entry without an explicit approval - That rounding happens at export and the stored entry keeps the actual duration - That raw samples expire on a short retention while approved entries persist
# Architecture · Memtime ## Stack | Part | Choice | Why | | --- | --- | --- | | Capture | A local agent polling the focused window and idle state | this is the only way to reconstruct a day you did not track as it happened | | Storage | SQLite on the machine, encrypted at rest | raw capture is the most sensitive data in the build | | Grouping | Segmentation into blocks by application, document and gap | a raw event stream is unreadable and useless for reconstruction | | Approval | Nothing becomes a timesheet entry without an explicit action | the whole model is recall and review, not automatic reporting | | Export | Approved entries only, in a format your billing system takes | the boundary between capture and export is the privacy boundary | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Capture | Sampling and applying exclusions before the write. | Platform-specific. Applying exclusions in the sampler rather than at display is the requirement. | | Segmentation | Turning samples into readable blocks. | Any algorithm, tuned against a real day rather than a synthetic one. | | Approval | Blocks becoming entries only by an action. | Not swappable for automatic assignment, which produces wrong invoices. | | Rounding | Billing rules applied at export. | Any rules, provided the stored entry keeps the actual duration. | | Boundary | Keeping raw capture off every export path. | Not a module to swap but an assertion in the suite that must never be deleted. | ## 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 · Raw capture. The most sensitive file this build produces. Encrypted at rest. - `DB_KEY` · required, secret · Encrypts the capture store. Without it the raw titles sit in plain text. - `SAMPLE_INTERVAL_SECONDS` · required · How often focus is sampled. Shorter is more accurate and more intrusive. - `IDLE_THRESHOLD_SECONDS` · required · When you are considered away. Blocks end at idle rather than continuing. - `RAW_RETENTION_DAYS` · required · How long raw samples live. Short, because their usefulness drops fast and their sensitivity does not. - `EXCLUSIONS_PATH` · required · Loaded by the sampler before the first write. Not a display filter. ## 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 · Memtime product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A local agent polling the focused window and idle state, SQLite on the machine, encrypted at rest, Segmentation into blocks by application, document and gap, Nothing becomes a timesheet entry without an explicit action, Approved entries only, in a format your billing system takes. - 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 - Filtering excluded applications at display time means the sensitive titles are still on disk, still in backups, and still readable by anything that can open the database, which is exactly the outcome the exclusion was meant to prevent. Apply exclusions in the sampler, before the write. - Segmenting on every application change produces four hundred blocks for a normal day, which is unreadable and defeats the purpose, while merging too aggressively produces one block called various and tells you nothing. The gap threshold and the merge rules are the actual design work here and they need tuning against your own real day. - Automatically assigning blocks to projects by matching window titles produces confident, wrong timesheets, and a wrong timesheet that was never reviewed is worse than an empty one because it gets invoiced. Suggest a project, never assign one, and require the approval action for every entry. - Rounding the stored entry destroys the actual figure, so you can never answer a client asking what the real time was, and repeated rounding on edits compounds the error. Store actual, round at export, and show both. - An export or a crash report that includes window titles breaks the entire privacy model in one line of code, and it will be added by someone trying to make debugging easier. Assert it in the suite: scan every export and log path for the raw fields and fail the build if they appear.
# Delivery milestones · Memtime Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Capture, with the exclusions applied first Your day is recorded, minus everything you said not to record. ### Steps 1. Sample focus and idle at an interval Application, title, document, idle seconds. Nothing else. Files: `src/capture.ts` 2. Apply exclusions before writing In the sampler. Display filtering leaves the data on disk. Files: `src/exclude.ts`, `src/exclude.test.ts` 3. Make pause a real pause No sampling, and a visible gap rather than an interpolated one. Files: `src/capture.ts` ### Done when - [ ] An excluded app produces no row in the database - [ ] Pause stops sampling and leaves a visible gap - [ ] The store is encrypted at rest - [ ] Sampling costs negligible processor time ### Watch out - Filtering excluded applications at display time means the sensitive titles are still on disk, still in backups, and still readable by anything that can open the database, which is exactly the outcome the exclusion was meant to prevent. Apply exclusions in the sampler, before the write. ## M2 · Turning a fragmented day into blocks A day of thousands of samples reads as a dozen recognisable blocks. ### Steps 1. Segment with a tuned gap threshold Tune it against your own day. This is the design work, not a constant to guess. Files: `src/segment.ts`, `src/segment.test.ts` 2. Merge across brief interruptions Checking a message does not end a block of work. Files: `src/segment.ts` 3. Summarise titles readably A block needs a label you recognise, not a list of forty window titles. Files: `src/summarise.ts` ### Done when - [ ] A real day produces a readable number of blocks - [ ] A brief switch does not split a block - [ ] A genuine gap does split one - [ ] Idle appears as a gap rather than as work ### Watch out - Segmenting on every application change produces four hundred blocks for a normal day, which is unreadable and defeats the purpose, while merging too aggressively produces one block called various and tells you nothing. The gap threshold and the merge rules are the actual design work here and they need tuning against your own real day. ## M3 · Reconstructing a day you did not track You can sit down at five and rebuild the day accurately. ### Steps 1. Show the day as a scrubable timeline The point is recall. You need to see what was on screen at four o'clock. Files: `src/timeline.ts` 2. Select blocks into one entry Combining several blocks is the normal case, not the exception. Files: `src/entries.ts` 3. Suggest, never assign An unreviewed automatic timesheet gets invoiced. That is the failure. Files: `src/suggest.ts` ### Done when - [ ] A day reconstructs into entries in a few minutes - [ ] Several blocks combine into one entry - [ ] No entry exists without approval - [ ] An entry cannot exceed its source blocks ### Watch out - Automatically assigning blocks to projects by matching window titles produces confident, wrong timesheets, and a wrong timesheet that was never reviewed is worse than an empty one because it gets invoiced. Suggest a project, never assign one, and require the approval action for every entry. ## M4 · Rounding and billing rules, stated honestly Exported time matches the rule you agreed with the client. ### Steps 1. Round at export only Store actual. A client asking what the real figure was deserves an answer. Files: `src/rounding.ts`, `src/rounding.test.ts` 2. Show both figures Actual and rounded, side by side. Hiding the difference is how disputes start. Files: `src/export.ts` 3. Handle midnight and daylight transitions Both are ordinary. Both break naive duration arithmetic. Files: `src/duration.ts` ### Done when - [ ] Rounding applies at export and shows both figures - [ ] The stored entry is unchanged by rounding - [ ] An entry crossing midnight is handled - [ ] Both daylight transitions produce correct durations ### Watch out - Rounding the stored entry destroys the actual figure, so you can never answer a client asking what the real time was, and repeated rounding on edits compounds the error. Store actual, round at export, and show both. ## M5 · Retention, and the boundary that must hold (production only) Raw capture expires, approved entries persist, and nothing raw ever leaves. ### Steps 1. Expire raw samples on a short period They are the sensitive data and their value drops to nothing within weeks. Files: `src/retention.ts` 2. Assert the boundary in the suite Scan export and log paths for raw fields. Fail the build if any appear. Files: `tests/no-raw-export.test.ts` 3. Delete completely on request Samples, blocks, entries, backups. Everything. Files: `src/deletion.ts` ### Done when - [ ] Raw samples expire while entries survive - [ ] A test fails when a raw field is added to an export path - [ ] Deletion removes everything including backups - [ ] Retention runs without the application open ### Watch out - An export or a crash report that includes window titles breaks the entire privacy model in one line of code, and it will be added by someone trying to make debugging easier. Assert it in the suite: scan every export and log path for the raw fields and fail the build if they appear.
# Operations · Memtime ## Backup Approved entries are the valuable data and are safe to back up. Raw capture is sensitive and short-lived, so consider excluding it from backups entirely rather than encrypting it and hoping. ## Restore Restore entries and confirm retention resumes, since a restore that reinstates expired raw samples has recreated exactly the data the retention policy existed to remove. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a personal tool the thing to check is that retention actually ran, because a silently failing retention job leaves months of window titles on disk while the interface claims a thirty day policy. ## Incident checklist A window title appearing in an export, a log or a crash report is the incident. The suite assertion is the control, and if it was removed to make debugging easier that is the change to revert. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] An excluded application is confirmed to produce no database row - [ ] The suite is confirmed to fail when a raw field is added to an export path - [ ] No entry is confirmed to exist without approval - [ ] The stored entry is confirmed unchanged by rounding - [ ] Raw retention is confirmed to delete samples while entries survive ## Launch constraint Do not market omitted Memtime 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. Raw capture. The most sensitive file this build produces. Encrypted at rest. DB_PATH=~/.local/share/memtime/capture.db # Required · secret. Encrypts the capture store. Without it the raw titles sit in plain text. DB_KEY=a passphrase or keychain reference # Required. How often focus is sampled. Shorter is more accurate and more intrusive. SAMPLE_INTERVAL_SECONDS=15 # Required. When you are considered away. Blocks end at idle rather than continuing. IDLE_THRESHOLD_SECONDS=180 # Required. How long raw samples live. Short, because their usefulness drops fast and their sensitivity does not. RAW_RETENTION_DAYS=30 # Required. Loaded by the sampler before the first write. Not a display filter. EXCLUSIONS_PATH=./config/exclusions.txt
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for Memtime because the timer is simple, but people pay for frictionless capture and a history they trust enough to invoice or change habits from. The recurring cost buys idle detection, permissions, app metadata, privacy, notifications, sync, reports, integrations, and backups, not just the visible interface.
xcalendar and project integrations, team reporting, sync, and enterprise controls
xcross-device history
xteam timesheets and payroll
xbehavioral datasets
xblocking across every platform
Don't feel like building it? These folks already made it free.
all 3 free alternatives to Memtime →· no votes, no pay-to-list · just what's real
Memtime pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| basic | — | $14/user | 1 user license; private automatic activity capture; no numeric project cap published. |
| connect | — | $21/user | 1 user license; adds integrations; no numeric project cap published. |
| premium | — | $29/user | 1 user license; priority service and advanced features; no numeric usage cap published. |
| enterprise | custom | — | Custom contract for 50+ seats. |
free tierno free tier
billingquarterly + annual + 2-year; no month-to-month plan; Enterprise custom
hidden costsThe shortest commitment is 3 months, so the displayed quarterly monthly-equivalent still requires paying a quarter at a time.
verified 2026-08-14 · source ↗
Vibecode Memtime
Kinda. The core of Memtime is buildable in a weekend with the prompt on this page, but there are real gaps: calendar and project integrations, team reporting, sync, and enterprise controls, cross-device history. Read the honest list above before committing.
How much does Memtime cost?
Memtime costs about $14/month (Basic, checked 2026-08-14), which is $168 per year.
What do I lose by replacing Memtime?
Honestly: calendar and project integrations, team reporting, sync, and enterprise controls; cross-device history; team timesheets and payroll; behavioral datasets; blocking across every platform. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Memtime?
Yes: Clockify Free (Five users get timers and a private auto-tracker; reports stop at 31-day windows and CSV export is paid.) ManicTime Free (One computer gets automatic app, site and document history, tags and timesheet export; cloud sync is the billable part.) Toggl Track Free (Fast timers plus a private desktop activity trail you can promote into entries; formal approvals cost extra.) All 3 curated free alternatives are at vibecodeit.com/memtime/alternatives. The prompt is for when you want it exactly your way.