Guides
Architecture
How the code is organised, what each file is for, and how a request flows through it. The program is about 6,300 lines of standard-library Python (plus a 540-line web page) and about 3,200 lines of tests, with no build step.
The layers
Packages depend only downwards. A test (tests/test_architecture.py) fails if a lower layer imports a higher one, if core imports anything from the command line, HTTP or the browser, or if a root script grows beyond a thin wrapper.
cli web mcpserver interfaces: each is a way to drive Lore
\ | /
+----------+----------+
| |
discovery reviews infer rules from code / from code review
\ /
+------ core ------+ the domain: what a rule is, how it is stored, chosen and exported
root scripts (install.py, memorize.py, ...) only call the main() of one interface
core imports nothing of Lore's own other than itself.
discovery and reviews import core only.
cli, web, mcpserver may import all three, and none imports another interface.
The repository at a glance
| Path | What it is |
install.py memorize.py train.py discover.py learn.py review.py serve.py mcp_server.py | Entry points: thin wrappers (16 lines or fewer) that call one interface's main(). Their paths are stable, so client configurations and docs can point at them. They find their sibling folders, so there is nothing to install. |
core/ | The domain. |
discovery/ | Infers rules from code and tooling. |
reviews/ | Learns from pull-request review comments; decides whether a repository is yours. |
cli/ | One module per command, plus shared plumbing. |
web/ | The local web UI. |
mcpserver/ | The MCP server. |
tests/ | The test suite (python3 -m unittest discover -s tests). |
docs/ | This site: plain static files for GitHub Pages. |
assets/ | The logo. |
README.md, README-overly-explained.md, CONTRIBUTING.md, SECURITY.md, LICENSE | Short README, the long single-page guide, how to contribute, how to report a vulnerability, MIT. |
core/: the domain
| File | Responsibility |
models.py | The vocabulary: Scope (global, section, glob, with level_for(path)), Rule (all the fields from concepts, with JSON round-tripping that preserves unknown keys), Status, Outcome, Level. |
store.py | Low-level storage: the data folder (~/.lore or $LORE_HOME), project-name validation, atomic JSON writes (temporary file, then rename) and the cross-process lock helper. |
projects.py | Project and the registry of registered projects; finds the project whose root contains a path (the deepest wins); refuses a name used by another folder. |
registration.py | Putting a project on the map; the safe automatic registration of git projects (git_root, register_automatically), never touching the home folder, system folders or Lore's own data. |
mapper.py | Builds the structural map (sections, languages, manifests) without reading file contents; section_of(path) is the single definition of a section. |
globs.py | Glob matching: **, *, ?, {a,b}, and bare patterns that match anywhere. |
repository.py | RuleRepository: the only code that reads or writes rules.json. Adds with dedupe and contradiction detection, edits, approves, tags, pins and deletes, all under a lock. Validates tags. |
sanitize.py | Hygiene for everything that can reach an agent: hidden-character detection, clean_rule_text, tag and scope validation, reveal and label for showing untrusted text safely. |
risk.py | The attack-pattern screen (check(text) returns reasons). |
textutil.py | Normalising, near-duplicate (similar) and contradiction (contradicts) tests, short ids. |
search.py | BM25 ranking with identifier splitting, stemming, aliases and the topic vocabulary. |
resolver.py | Which rules apply to which files for which task: eligibility, selection, ordering, skipping what was sent, the held-back list. Also find for topic search. |
usage.py | UsageLog: how often each rule was handed to an agent, in its own file with its own lock. |
trainer.py | Turns instruction documents into scoped rules; the bulk-import statistics (TrainStats). |
export.py | The static fallback: writes active rules as Copilot instruction files, refusing symlinks and outside paths. |
config.py | Settings from ~/.lore/config.json with safe defaults. |
instructions.py | The text agents are given about Lore, built for the approval mode. |
discovery/: rules from code
| File | Responsibility |
__init__.py | discover() and the Discoverer that runs the detectors in order. |
detectors.py | The detectors: EditorConfigDetector, ManifestDetector, StyleDetector, NamingDetector, LanguageMarkerDetector, FrameworkMarkerDetector. Each inspects a codebase and returns candidates. |
codebase.py | The codebase a detector inspects: its files, source texts and manifest facts, each computed lazily once. |
scanner.py | Which files count as the team's own code (not vendored, minified, generated or huge) and how they are read. |
ecosystems.py | Regex readers for manifests and linter configs across ecosystems, and the table of about 90 recognised frameworks and tools. |
markers.py | The extension point: FileMarker, OccurrenceMarker, AbsentMarker, ChoiceMarker, Option, and marker_from_dict for JSON. |
models.py | Thresholds and Candidate. |
registry.py | Finds every LANGUAGE in languages/ and every FRAMEWORKS in frameworks/ automatically. |
languages/ | One module per language: c, cpp, csharp, dart, elixir, go, java, javascript, kotlin, lua, php, python, ruby, rust, scala, svelte, swift, typescript, vue (19), plus base.py (Language). |
frameworks/ | One module per language family, 19 frameworks in all, plus base.py (Framework). |
reviews/: rules from code review
| File | Responsibility |
github.py | Reads review comments through gh api; parses pull-request specs (12, 15-18); keeps GitHub's author_association. |
models.py | ReviewComment (with is_team) and Proposal. |
text.py | Turns review markdown into comparable words and a comment into a rule-shaped sentence. |
cluster.py | Filters comments, groups those asking for the same thing, infers a scope, and builds proposals. |
service.py | The whole pipeline (learn()), shared by the CLI, the MCP tool and the web UI; also the candidate brief for agents. |
ledger.py | Remembers which comments were proposed or dismissed, and when review feedback was last read or tried. |
trust.py | Decides whether a repository is yours (no remote, or you can push), with a weekly cache and manual overrides. |
The interfaces
| File | Responsibility |
cli/common.py | The parser, CliError and the @entry decorator that turns it into error: … and exit code 2, project lookup, scope parsing. |
cli/install.py memorize.py train.py discover.py learn.py review.py | One module per command: see the command pages. |
web/server.py | HTTP: security checks (token, Host, Origin, content type, size), routing, JSON in and out. |
web/api.py | Everything the page can do, with no HTTP in sight, so it is tested directly. |
web/runner.py | Runs Lore's own scripts as subprocesses, never through a shell, one at a time, with a timeout. |
web/validation.py | Request validation helpers and the one ApiError type. |
web/index.html | The single-page UI, read from disk on every request. |
mcpserver/protocol.py | Generic MCP over stdio (JSON-RPC 2.0). Knows nothing about Lore. |
mcpserver/tools.py | Lore's eight tools, the session object, and create_server(). |
How a request flows
get_rules
protocol.py reads the line and calls the tool's handler.
tools.get_rules finds the project from the paths (projects.find_for_path), registering a new git project if needed (registration).
resolver.resolve loads active rules through RuleRepository, drops any with hidden characters, keeps those whose scope (models.Scope.level_for, globs, mapper) covers a path, and, for a long list, ranks them against the task (search.rank), skipping what the session already has.
UsageLog.record counts what was sent; tools adds the one-time notes (it may call trust.get and read Ledger), and returns the text.
record_feedback and propose_rules
tools works out the project from the files and marks it as one the agent is working on.
RuleRepository.add validates the text (sanitize) and tags (risk), compares with existing rules for duplicates and contradictions (textutil), and writes under a lock.
tools._activate applies the gate: config (approval mode and limit), the working-project check, trust.get, Rule.risks, contradictions. Passing rules go through set_status(..., auto=True).
Learning from review
reviews.service.learn fetches comments through github.py (the only place that runs gh), records the attempt in the ledger, and filters and groups them (cluster, text).
- It returns proposals. The CLI saves them as pending; the MCP tool turns them into a brief for the agent, which proposes the ones it keeps through
propose_rules.
The tests
| File | Covers |
support.py | Shared fixtures: a throw-away LORE_HOME, a registered project, a stand-in gh, helpers to run the scripts. |
test_architecture.py | The layering rules and the thin root scripts. |
test_core.py | Globs, mapper, scopes, rules, repository, usage, resolver, export, trainer, projects, storage. |
test_discovery.py | The registry, every marker, frameworks, extensibility. |
test_reviews.py | Review text, clustering, the learn command, the ledger. |
test_cli.py | The commands end to end as subprocesses. |
test_mcp.py | The protocol, the tools over the real script, and locate. |
test_agent_tools.py | Search, selection, tagging, proposing, discovery and the learn flow through the server. |
test_automation.py | Settings, automatic registration, server instructions, the one-time notes. |
test_install.py | The installer against fake clients and a fake home. |
test_safety.py | Hygiene, contradictions, risk patterns, hostile names, export safety. |
test_review_fixes.py | Regressions from the code review: the right project, trust, the review reminder, tool schemas. |
test_web.py | The API, the HTTP layer and every tool card. |
Design principles
- Standard library only. No dependencies to install, audit or update.
- Deterministic at request time. Serving rules involves no model and no network; the agent's judgement is captured once, as tags.
- One writer per file.
RuleRepository owns rules.json, UsageLog owns usage.json, Ledger owns review-ledger.json. Writes are atomic and locked.
- Untrusted text is never trusted. Anything from a repository, a reviewer or an agent passes through
sanitize before it is stored or shown, and is refused rather than repaired.
- Interfaces stay thin. Behaviour lives in the layers below, so the CLI, the web UI and the MCP server cannot drift apart.