Date: January 2026 Version: 1.0.0 Status: Functional, Release Candidate
Lockin is a macOS terminal-based focus timer with a persistent background engine. The project is functional and ready for public release with the understanding that it's macOS-only and has gaps in test coverage and CI infrastructure. The architecture is sound—using SQLite as a message bus between CLI and engine is a pragmatic choice that handles all edge cases well.
Strengths:
- Clean architecture with clear separation of concerns
- Robust state persistence (survives crashes, terminal closes)
- Well-documented code and architecture
- Polished terminal UI using Rich library
Weaknesses:
- Limited test coverage (database only, no CLI/engine tests)
- macOS-only (uses LaunchAgent and osascript)
| Feature | Status | Notes |
|---|---|---|
| Work sessions | ✅ | Start, quit, abandon tracking |
| Break sessions | ✅ | Short/long breaks, custom durations |
| Decision window | ✅ | 3-minute window to decide after session |
| Overtime/bonus time | ✅ | Configurable max overtime |
| Session persistence | ✅ | Survives terminal close, crashes |
| macOS notifications | ✅ | Uses osascript |
| Statistics | ✅ | Week/month/year views |
| Configuration | ✅ | 11 config options with validation |
| Session log | ✅ | View and delete recent sessions |
| Streak tracking | ✅ | Today's streak with 60-min gap rule |
| Context | Controls |
|---|---|
| Running session | q quit, d detach |
| Decision window | q quit, b break, B custom break, c continue |
| Break session | q quit, s switch short, l switch long, w start work |
short_break_minutes(default: 5)long_break_minutes(default: 15)long_break_every(default: 4)work_default_minutes(default: 25)min_work_minutes(default: 5)min_break_minutes(default: 2)work_decision_minutes(default: 3)auto_attach(default: false)work_overtime_enabled(default: true)work_overtime_max_minutes(default: 60, 0=unlimited)break_overtime_contributes(default: false)
The persistent engine + ephemeral CLI pattern is well-suited for this use case:
- Engine runs as LaunchAgent, always available
- CLI can start/stop without affecting timers
- SQLite acts as both persistence and IPC
Component sizes (lines of code):
cli.py: 774 lines — Terminal UI, the largest moduleengine.py: 353 lines — State machinedatabase.py: 342 lines — SQLite layer__main__.py: 310 lines — CLI entry pointconfig.py: 135 lines — Configuration- Total: ~1,950 lines Python
- Duration validation (1-1440 minutes)
- Config key validation against whitelist
- Config value validation (type, range)
- Session type validation
- State corruption recovery (invalid states reset to idle)
- Database errors: Transaction rollback
- Engine errors: 5-second backoff, continues running
- Notifications: Silently fail (non-critical)
- Invalid CLI args: User-friendly error messages
-
Race conditions: CLI can queue commands while engine processes others. Mostly harmless due to single-session constraint, but edge cases possible.
-
Clock jumps: System sleep/wake or time changes could cause unexpected behavior. No explicit handling.
-
Database locking: SQLite handles concurrency, but high-frequency polling (1 Hz from CLI + engine) could cause transient lock contention.
| Document | Status | Quality |
|---|---|---|
| README.md | ✅ | Concise, covers all commands |
| CONTRIBUTING.md | ✅ | Clear dev setup, architecture overview |
| CLAUDE.md | ✅ | AI assistant context (unique to this project) |
| CHANGELOG.md | ✅ | Documents 1.0.0 features |
| Document | Status | Quality |
|---|---|---|
| ARCHITECTURE.md | ✅ | Comprehensive, includes diagrams |
| Inline comments | ✅ | Docstrings on all public methods |
| Type hints | ✅ | Used throughout |
- User guide with screenshots
- Troubleshooting guide (mentioned in ARCHITECTURE.md but doesn't exist)
- Release notes / changelog entries
Unit Tests (test_database.py): 5 tests
- Database initialization
- Session logging
- Streak calculation
- Today's stats
- Config management
Integration Tests (final_verification.py): 5 tests
- Duration validation
- State validation/corruption recovery
- Config validation
- Date parsing error handling
- Complete workflow (start → quit → start new)
| Component | Coverage | Priority |
|---|---|---|
| Engine state machine | None | High |
| CLI rendering | None | Medium |
| Command processing | Partial | High |
| Break switching | None | Medium |
| Overtime logic | None | High |
| Midnight rollover | None | Low |
- Uses pytest
- Temporary databases for isolation
- No mocking (tests use real DB operations)
- GitHub Actions CI (runs on Python 3.9 and 3.12, macOS)
- Ruff for linting and formatting
The install.sh script:
- Detects uv or falls back to pip
- Creates isolated venv in
~/.lockin/ - Installs as editable package
- Sets up LaunchAgent
- Adds to PATH
Issues:
- Requires manual
source ~/.zshrcafter install - No update mechanism
- No way to install specific version
- macOS security warnings (see below)
- Not on PyPI or Homebrew (shell script installation only)
The uninstall.sh script:
- Stops LaunchAgent
- Optionally preserves data
- Reminds about PATH cleanup
Hard dependencies on:
launchctl/ LaunchAgentosascriptfor notifications- Paths like
~/Library/LaunchAgents
- Single session only: By design, but could frustrate users wanting parallel tracking
- No data export: Stats are view-only, no CSV/JSON export
- No undo: Deleted sessions cannot be recovered
- No pause: Sessions can only be quit, not paused
- 60-minute streak gap: Hardcoded, not configurable
- macOS only: Architecture is portable, but system integrations are not
- No sync: Data is local only
- No backup: User must manually backup
~/.lockin/lockin.db - Terminal required: No GUI, no menu bar app
- No themes: Fixed color scheme
- No resize handling: Progress bar width is fixed at 40 chars
- No accessibility: No screen reader support
-
macOS Gatekeeper warnings: Downloaded scripts are quarantined. Users must run
xattr -r -d com.apple.quarantine install.shor right-click → Open to bypass. -
Corporate endpoint protection: Security software (CrowdStrike, Jamf, etc.) may flag the LaunchAgent or Python process as suspicious. This is difficult to address without code signing.
-
No code signing: The app is unsigned, which triggers macOS warnings and corporate security tools. Signing requires an Apple Developer account ($99/year) and adds build complexity.
Workaround documented: Users in restricted environments can skip the LaunchAgent and run lockin-engine manually in a terminal tab.
- Some duplicate code between CLI and engine for break recommendation
- Magic numbers (60-minute streak gap, 40-char progress bar)
- No engine tests
- No CLI tests
- Hardcoded en-US strings (no i18n)
- No automated security scanning
This project is entirely AI-generated and managed (with human direction). This creates unique considerations for long-term maintainability.
What works well:
CLAUDE.mdprovides project context and development commandsARCHITECTURE.mddocuments design decisions and patterns- Consistent commit message format (
[component] description) - Clear module separation makes targeted changes easier
- Tests verify core functionality works
What's missing:
| Gap | Risk | Solution |
|---|---|---|
| No release process documentation | AI won't know how to version/tag releases | Document in CLAUDE.md |
| No CI operation guide | AI won't know how to interpret/fix CI failures | Document expected CI behavior |
| No PyPI publishing guide | AI won't know how to publish new versions | Document publishing steps |
| CHANGELOG not maintained | AI has no record of what changed when | Keep CHANGELOG updated |
| No "how to debug" guide | AI may struggle with production issues | Document common debugging steps |
When a new AI session starts, context is empty. The AI must reconstruct understanding from:
- Source code
- Documentation (README, CLAUDE.md, ARCHITECTURE.md)
- Git history
- Test output
Current gaps that cause friction:
- No documented release/versioning procedure
- No guide for "how to ship a fix to PyPI" (once published)
- No incident response guide ("user reports X, here's how to debug")
- Test coverage is low, so AI can't verify changes don't break things
-
Expand CLAUDE.md with operational procedures:
- How to create a release (version bump, tag, publish)
- How to interpret and fix CI failures
- How to debug common issues
-
Keep CHANGELOG.md updated — serves as institutional memory
-
Increase test coverage — gives AI confidence that changes work
-
Document "recipes" for common tasks:
- Adding a new CLI command
- Adding a new config option
- Debugging engine state issues
-
Use descriptive commit messages — AI can learn from git history
Tasks to prepare for public release and community contributions.
| Task | Effort | Impact |
|---|---|---|
| ✅ Done | High | |
| ✅ Done | High | |
| Add engine unit tests | 1-2 days | High |
| ✅ Done | Medium | |
| ✅ Done | Medium | |
| ✅ Done | Medium | |
| ✅ Done | Medium |
Make the tool easier to install for a wider audience.
| Task | Effort | Impact |
|---|---|---|
Add lockin setup command (replace install.sh logic) |
1 day | High |
| Publish to PyPI | 0.5 day | High |
Add lockin setup --uninstall |
0.5 day | Medium |
Why lockin setup? The current install.sh works but isn't compatible with standard Python distribution (pip/PyPI). Refactoring LaunchAgent installation into a CLI command enables pip install lockin && lockin setup — cleaner and more portable.
PyPI benefits:
pip install lockinis familiar to Python users- Enables
pipx install lockinfor isolated installs - Automatic dependency resolution
- Version management via pip
Quality-of-life improvements.
| Task | Effort | Impact |
|---|---|---|
| Export stats to CSV | 0.5 day | Medium |
| Configurable streak gap | 0.5 day | Low |
| Responsive progress bar width | 0.5 day | Low |
Add lockin pause command |
1 day | Medium |
Broaden user base.
| Task | Effort | Impact |
|---|---|---|
| Linux support (systemd + notify-send) | 2-3 days | High |
| Windows support (Task Scheduler) | 2-3 days | Medium |
| Docker/container support | 1 day | Low |
Nice-to-have features and broader distribution.
| Task | Effort | Impact |
|---|---|---|
| Homebrew tap | 1 day | Medium |
| Data backup/restore commands | 1 day | Medium |
| Import from other timers | 1-2 days | Low |
| Custom notification sounds | 0.5 day | Low |
| Pomodoro preset mode | 1 day | Medium |
| Menu bar status (macOS) | 2-3 days | Medium |
Homebrew tap: Once PyPI distribution is working, a Homebrew tap (brew install tomhilder/tap/lockin) provides a more native macOS installation experience. Lower priority than PyPI since it's macOS-only and Homebrew users can still use pip.
| Metric | Value |
|---|---|
| Lines of Python | ~1,950 |
| Lines of docs | ~1,500 |
| Unit tests | 5 |
| Integration tests | 5 |
| Config options | 11 |
| CLI commands | 8 |
| Supported platforms | 1 (macOS) |
| Human-written code | 0% |
-
Set up GitHub Actions first— Done. CI runs pytest and ruff on push/PR. -
Tag a release— Done. v1.0.0 released. -
Write engine tests — The state machine is the heart of the app. Tests catch regressions and document expected behavior for contributors.
-
Document operational procedures — Every new system (CI, PyPI, versioning) needs a corresponding update to CLAUDE.md so future AI sessions know how to operate it.
-
Keep CHANGELOG.md current — This is institutional memory. When context is lost, the changelog tells the story of what changed and why.
-
Don't over-engineer — The current architecture handles its scope well. Resist adding complexity unless there's clear user demand.
-
Linux support is high-value — Many terminal-focused developers use Linux. The architecture is portable; only LaunchAgent and osascript need platform alternatives.
This report assesses the Lockin project as of January 2026. It is intended to guide development prioritization and inform potential contributors (human or AI) about the project's current state.