leetcode-sensei/
├── src/ # All CLI source code
│ ├── sensei.py # Entry point — command routing
│ ├── new.py # sensei new
│ ├── mark.py # sensei mark
│ ├── hint.py # sensei hint
│ ├── lopen.py # sensei open
│ ├── revisit.py # sensei revisit + collect_problems()
│ ├── rebalance.py # sensei rebalance
│ ├── progress.py # sensei progress
│ └── utils.py # Shared helpers (parse_metadata, find_match, …)
├── tests/
│ ├── conftest.py # Shared fixtures (temp_workspace, sample_problem_file, …)
│ ├── test_utils.py # Unit tests for utils.py
│ ├── test_commands.py # Integration tests (subprocess)
│ └── test_commands_direct.py # Direct-call tests (higher coverage)
├── problems/ # User's tracked LeetCode solutions (not shipped)
├── pyproject.toml # Build config, test settings, dependencies
├── AGENTS.md # AI coaching protocol — read before touching SRS logic
├── ROADMAP.md # Planned features
└── CONTRIBUTING.md # This file
git clone https://github.com/Overkill-Labs/leetcode-sensei.git
cd leetcode-sensei
pip install -e ".[test]"That's it. No Docker, no database, no config files.
Verify the install:
sensei --help
pytestEvery command is a self-contained module in src/. sensei.py routes by command name and strips its own argv before delegating:
sys.argv = [sys.argv[0]] + sys.argv[2:] # remove "sensei" and the subcommand
new.main() # receives clean argvCommands that don't need argv stripping (status, show, progress) are handled directly in sensei.py before the strip line.
collect_problems(root) in revisit.py is the central data source. It walks problems/, parses metadata from every .py file via parse_metadata(), and returns a sorted list of dicts. Most commands build on top of this — avoid duplicating that logic.
Problem files are plain Python. The metadata block is a set of module-level assignments that parse_metadata() reads with ast.walk:
last_solved = "2026-08-10"
revisit_in_days = 7
times_reviewed = 4
difficulty = "medium"
topic_tags = ["dynamic-programming"]- Create
src/<command>.pywith amain()function - Add
import <command>tosrc/sensei.py - Add a routing branch in
sensei.main():- Before the
sys.argvstrip if the command doesn't need subcommand args - After the strip (in the
if cmd == ...chain) if it does
- Before the
- Add it to
pyproject.tomlunderpy-modules - Add it to the help text in
sensei.main() - Write tests in
tests/test_commands_direct.py
pytest # full suite with coverage
pytest tests/test_commands_direct.py -v # just the direct tests
pytest -k "progress" # filter by nameCoverage target: >85% overall. New modules should hit >90%.
Use test_commands_direct.py for new tests — it calls main() functions directly, which gives better coverage than subprocess calls.
Fixtures are in conftest.py:
temp_workspace— empty temp dir, cwd changed to itinitialized_workspace— temp dir withproblems/createdsample_problem_file— single valid problem filemultiple_problems— three problems with different due states (overdue, due today, future)
For commands that hit external services (LeetCode GraphQL, browser), mock them:
with patch("new.fetch_leetcode_metadata", return_value={...}):
new.main()This repo uses Conventional Commits. Every commit message must follow:
<type>(<scope>): <short description>
| Type | When to use |
|---|---|
feat |
New user-facing feature |
fix |
Bug fix |
chore |
Maintenance (deps, config, build, non-feature code) |
docs |
Documentation only |
test |
Adding or fixing tests |
refactor |
Code restructure with no behavior change |
perf |
Performance improvement |
Use the area of the codebase affected:
| Scope | Covers |
|---|---|
cli |
New or changed command |
srs |
SRS algorithm logic (intervals, progression gate, load smoothing) |
build |
pyproject.toml, packaging |
agents |
AGENTS.md coaching protocol |
docs |
README.md, ROADMAP.md, CONTRIBUTING.md |
feat(cli): add sensei progress dashboard
fix(srs): cap progression gate at correct tier boundary
chore(build): add progress to py-modules
test: add coverage for url mode in sensei new
docs(agents): document sensei new url form
refactor(cli): extract _scaffold() from new.main()
- Subject line: lowercase, no period, ≤72 characters
- Use imperative mood: "add" not "added", "fix" not "fixes"
- One logical change per commit — if you need "and" in the subject, split it
- No
Co-Authored-Bytrailers
Releases are cut by pushing a version tag. The CI pipeline picks it up and publishes to PyPI automatically.
# 1. Bump version in pyproject.toml
# 2. Commit
git add pyproject.toml
git commit -m "chore(build): bump version to X.Y.Z"
# 3. Tag and push
git tag -a vX.Y.Z -m "vX.Y.Z"
git push
git push origin vX.Y.ZVersion scheme: MAJOR.MINOR.PATCH
PATCH— bug fixes, doc updates, test additionsMINOR— new commands or features, backward-compatible changesMAJOR— breaking changes to CLI interface or problem file format
The SRS algorithm (intervals, progression gate, load smoothing) is intentionally conservative. Changes here affect real review schedules.
Before touching mark.py or any interval logic:
- Read the relevant sections of
AGENTS.md— the coaching agent depends on this behavior being well-defined - Update the rating table in
AGENTS.mdif intervals change - Update
tests/test_commands.py::TestComputeIntervalandTestProgressionGate - Note the change in the commit body with a reason
Do not change the metadata field names or their order without updating utils.parse_metadata() and all tests that create fixture files. The format is the stable public interface between the CLI and user data.
Current required fields: last_solved, revisit_in_days, difficulty, topic_tags
Optional: times_reviewed (defaults to 0 if absent)