205 lines
10 KiB
Markdown
205 lines
10 KiB
Markdown
# 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 `<DAYSTAMP>`/`<TIMESTAMP>` 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`
|