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

10 KiB

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

# 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.pytruncate, domize, undomize
  • tests/test_hasher.pyget_md5, hash
  • tests/test_epath.pyepath nested dict/list read/write access
  • tests/test_lict.pyLict collection (list+dict hybrid, dirty-sync)
  • tests/test_evalizor.pyEvalizer eval-based template system
  • tests/test_base_algorithm.pyBaseAlgorithm 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.pyElectron: 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.pyConfigDict(UserDict) singleton, TOML lazy loader
  • logger.pyget_logger(name) → hierarchical loggers under heurams.*
  • timer.pyget_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.pytruncate(), 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.pyWTFException
  • version.pyver = "0.5.0", stage = "prototype", codename = "fulcrum"

Important Gotchas

  1. FSRS is not implementedfsrs.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