# AGENTS.md — HeurAMS Codebase Guide ## Project Overview **HeurAMS** (潜进, v0.5.0 "fulcrum/支点") is a Heuristic Auxiliary Memory Scheduler — an open spaced-repetition scheduling platform. Licensed AGPL-3.0. Written in Python 3.10+. The project uses a physics-inspired data model: **nucleon** (memory content) + **electron** (algorithm state) + **orbital** (review strategy) = **atom** (runtime memory unit). ## Essential Commands ```bash # Setup (uv recommended) uv sync # Install all deps & dev env python3 -m pip install -e . # Native pip alternative # Run uv run heurams # Verify CLI (just prints help) uv run tui # Launch Textual TUI python3 -m heurams.interface # Native pip alternative for TUI # Formatting black . --workers=1 # Python (single-thread flag needed) autoflake --in-place --remove-all-unused-imports --recursive ./src/ --exclude __init__.py mdformat --number . # Markdown # Tests (pytest, 128 tests across 9 test files as of v0.5.0) ```bash uv run pytest # Run all tests uv run pytest -v --tb=short # Verbose, short tracebacks uv run pytest tests/test_sm2.py # Single test file uv run pytest -k "test_interval" # Filter by name uv run pytest --cov=heurams --cov-report=term # Coverage report (via pytest-cov) ``` Test structure: - `tests/conftest.py` — Shared fixtures: `timer_context` (deterministic time overrides), `sample_algodata_sm2`, `sample_algodata_nsp0` - `tests/test_textproc.py` — `truncate`, `domize`, `undomize` - `tests/test_hasher.py` — `get_md5`, `hash` - `tests/test_epath.py` — `epath` nested dict/list read/write access - `tests/test_lict.py` — `Lict` collection (list+dict hybrid, dirty-sync) - `tests/test_evalizor.py` — `Evalizer` eval-based template system - `tests/test_base_algorithm.py` — `BaseAlgorithm` defaults, methods, integrity - `tests/test_sm2.py` — SM-2: defaults, revisor (efactor/rept/interval logic), is_due, dates - `tests/test_nsp0.py` — NSP-0: defaults, revisor (important/interval), is_due - `tests/test_electron.py` — `Electron`: init, activation, revisor delegation, dict/list access, `from_data` ``` ## Code Organization ``` src/heurams/ ├── __init__.py, __main__.py, context.py # Entry point, global ContextVar config ├── interface/ — Textual TUI (screens/, widgets/, css/) ├── kernel/ — Core logic │ ├── algorithms/ — SM-2, NSP-0, SM-15M, FSRS (stub) │ ├── particles/ — Atom, Electron, Nucleon, Orbital data models │ ├── puzzles/ — MCQ, Cloze, Recognition puzzle generators │ ├── reactor/ — Router → Procession → Expander state machines │ ├── repolib/ — Repo class (loads/saves repos from TOML/JSON dirs) │ └── auxiliary/ — Evalizer (eval-based template), Lict (dict+list hybrid) ├── services/ — Config, logger, timer, audio, TTS, favorites, attic, hasher, epath ├── providers/ — Pluggable drivers (TTS: edgetts/base; Audio: playsound/termux) ├── unifront/ — Unified frontend session (mostly stub) └── tools/ — csv2payload, zmqclient ``` Outside `src/`: - `data/config/` — Hierarchical TOML config (see below) - `data/repo/` — Memory repos (one TOML dir per repo set) - `tests/` — Empty, no test infrastructure yet ## Architecture & Data Flow ### Layers (bottom-up) 1. **Kernel** — Algorithms, data models, puzzle generators, scheduling reactor 2. **Services** — Config, logging, timer, TTS, audio, favorites, persistence 3. **Providers** — Swappable backends (EdgeTTS, playsound, OpenAI LLM stub) 4. **Interface** — Textual TUI (Textual 8.x, CSS-based styling in `.tcss` files) 5. **Unifront** — Abstract frontend session (unimplemented) ### Review Flow ``` Router (global state machine) └─ contains → Procession(s) — one per phase (quick_review → recognition → final_review) └─ contains → Expander (per atom) — expands puzzle schedule into individual puzzle widgets └─ wraps → Puzzle widget (MCQ, Cloze, Recognition) ``` States managed via the `transitions` library (states defined in `states.py`). ## Key Patterns & Conventions ### Logging Every module creates its own logger with `get_logger(__name__)`. Log files rotate at 10MB, up to 5 backups. Logs append to `heurams.log` in the working directory. ### Config System (`ConfigDict`) Singleton per path. Lazy-loads TOML files on access. Directory convention: - `_.toml` files = default values for that directory level (merged into parent) - Files named `*.toml` = lazy-loaded on key access - Directories = recursive sub-configs - Access config via `config_var.get()["section"]["key"]` ### Context Management ```python from heurams.context import config_var, ConfigContext, rootdir, workdir config_var.get() # Current config (thread-safe) with ConfigContext(test_config_provider): # Scoped config override ... ``` ### State Machines (`transitions` library) Three finite state machines with explicit state enums in `states.py`: - **RouterState**: unsure → quick_review → recognition → final_review → finished - **ProcessionState**: active → finished - **ExpanderState**: exammode → retronly ### Physics Metaphor Data Model - **Nucleon**: Read-only content container. Wraps payload + typedef via `Evalizer` (eval-based template system). Created from `(ident, (payload, common))` tuples. - **Electron**: Algorithm-specific memory state. Wraps algodata dict with `BaseAlgorithm` interface. Created from `(ident, algodata, algo_name)` tuples. - **Orbital**: Review strategy dict defining puzzle probabilities per phase. - **Atom**: Runtime assembly of nucleon + electron + orbital + runtime flags. The primary object the UI works with. ### `Lict` Collection A `MutableSequence` subclass that maintains both list-like and dict-like access simultaneously. Used extensively for repo data. Appends are `(key, value)` tuples — keys must be unique strings. Supports lazy sync between internal list and dict representations. ### `Evalizer` Template System Recursively traverses data structures, evaluating strings prefixed with `eval:` via Python's `eval()` in a controlled namespace. Used in nucleon initialization. **TODO/warning**: noted for being risk-prone. ### Repo (Memory Repository) A directory of TOML/JSON files: - `manifest.toml` — title, author, package name, description - `typedef.toml` — Common metadata, puzzle definitions, annotations - `payload.toml` — Individual memory items (keyed by ident) - `algodata.json` — Algorithm state (keyed by ident, persistent) - `schedule.toml` — Orbital/review schedule definition Repos are loaded from `data/repo/` via `Repo.from_repodir()`. Created from directory structure, no database required. ## Algorithms | Name | File | Status | |------|------|--------| | SM-2 | `sm2.py` | Working — Classic SuperMemo 1987 | | NSP-0 | `nsp0.py` | Working — Non-spaced filtering scheduler | | SM-15M | `sm15m.py` + `sm15m_calc.py` | Working — Ported from CoffeeScript SM-15 | | FSRS | `fsrs.py` | **Stub only** — "尚未实现" | | Base | `base.py` | Abstract base with defaults | All algorithms are class-method-based (`@classmethod`), registered in `algorithms/__init__.py` dict. ## Services - **config.py** — `ConfigDict(UserDict)` singleton, TOML lazy loader - **logger.py** — `get_logger(name)` → hierarchical loggers under `heurams.*` - **timer.py** — `get_daystamp()` / `get_timestamp()` with configurable overrides - **epath.py** — Dot-notation access to nested dicts (`epath(dct, "a.b.c")`) - **attic.py** — Struct-like pickle persistence (per-ident, supports ``/`` placeholders) - **hasher.py** — MD5 hashing - **textproc.py** — `truncate()`, `domize()`, `undomize()` - **audio_service.py** — Routes through configured audio provider - **tts_service.py** — Routes through configured TTS provider - **favorite_service.py** — Favorite manager with JSON5 persistence (singleton) - **exceptions.py** — `WTFException` - **version.py** — `ver = "0.5.0"`, `stage = "prototype"`, `codename = "fulcrum"` ## Important Gotchas 1. **FSRS is not implemented** — `fsrs.py` is just a `logger.info("尚未实现")` stub. 2. **No tests** — The `tests/` directory is completely empty. There is no pytest config or test runner. Any code added should establish test infrastructure. 3. **`black --workers=1`** — The multi-threaded worker flag has compatibility issues on some platforms. 4. **`autoflake` must exclude `__init__.py`** — Unused-import removal breaks `__init__.py` re-exports. 5. **ConfigDict is a singleton per path** — Creating `ConfigDict` with the same path returns the same instance. Don't create instances with default dict argument (raises `WTFException`). 6. **`eval()` in Evalizer** — The template system uses `eval()` under the hood. Marked as high-risk in comments. Any changes should prioritize replacement. 7. **Don't run `interface/__main__.py` directly** — It will misconfigure Python context. Always run via `python -m heurams.interface` or `uv run tui`. 8. **No fast-forward merges** — Project policy requires non-fast-forward merging only (`git config merge.ff false`). 9. **`id()` is avoided for repo lookup** — The dashboard explicitly notes `id()` can be reused, so it uses `repolink` dict keyed by package name string. 10. **Termux support** — There's a separate audio provider for Termux; the project maintains pip-compatibility specifically for Termux (where uv doesn't work well). 11. **ZMQ debug server** — Optional debug feature that opens a REP socket for remote code execution via pickle. Disabled by default. 12. **Commit messages** — Follow Conventional Commits spec. Written in Chinese or English. ## Provider System Providers are swappable implementations registered in `__init__.py` dicts: - **TTS**: `edgetts` (Microsoft Edge TTS), `basetts` (stub) - **Audio**: `playsound` (cross-platform), `termux` (Android Termux) - **LLM**: `openai` (OpenAI-compatible API), no LLM provider is fully implemented yet Selection is via `data/config/services/*.toml` (e.g., `provider = "edgetts"`). ## Dependencies (pyproject.toml) Core: `psutil`, `tabulate`, `textual>=8.2.3`, `toml`, `transitions`, `zmq` Optional (in requirements.txt, commented out in pyproject.toml): `edge-tts`, `jieba`, `openai`, `playsound`