Files
HeurAMS/AGENTS.md
2026-04-25 01:36:52 +08:00

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`