Guide
From nothing to a shipped feature
This guide sets up lore in a small app, writes the notes it already needs, and takes one feature, due dates on tasks, through the whole flow. Every block of output below is what lore printed.
Install
With Homebrew, on macOS or Linux, which brings ripgrep too:
brew install apisandipas/lore/lore
Without Homebrew, the install script puts a standalone lore in ~/.local/bin. It needs no Bun or Node, and checks the download against the release’s checksums.
curl -fsSL https://apisandipas.github.io/lore/install.sh | sh
lore finds citations with ripgrep, so install that too: brew install ripgrep or apt install ripgrep. Run the script again to upgrade; LORE_VERSION picks a release. Windows builds are on the releases page.
With Nix, the flake brings Bun and ripgrep itself:
nix run github:apisandipas/lore -- help
To keep it, add the flake as an input and install lore.packages.${system}.default. For work on lore itself, nix develop gives you bun and rg, and bun src/main.ts <command> runs from source.
Set up a repo
Run lore init at the root of a git repo. It writes the schema that says which kinds of note exist, a template for each, and a WORKFLOW.md that the agent skills read first. It never overwrites a file that is already there.
lore init
created .lore/schema.yml
created .lore/WORKFLOW.md
created .lore/templates/Spec.md
created .lore/templates/Decision.md
created .lore/templates/Effort.md
skills: found in ~/.claude/skills, ~/.agents/skills
Read .lore/WORKFLOW.md, then start a spec with lore-spec.
If the lore-* skills are missing, init says how to add them:
npx skills add apisandipas/lore
Your first notes
Start with what a newcomer would get wrong. Our app has an auth package, and a rule about money. A decision is the right note for a rule with a reason: lore new numbers it and fills in the template.
lore new Decision "Money is integer cents"
created docs/decisions/0003-money-is-integer-cents.md
Fill it in. The anchors field claims the code it governs, and the ## Decision section becomes the summary lore shows.
---
type: Decision
status: accepted
anchors: [pkg/billing/**]
---
# Money is integer cents
## Context
Floating-point dollars round differently on the server and in the browser.
## Decision
Money is a whole number of cents everywhere, converted to dollars only for display.
## Consequences
Every price field is an integer, and its name ends in `Cents`.
Code that follows a decision can say so. A comment with lore: and the note’s name is a citation; lore finds it, and lore check tells you if the note it names is ever retired.
// Money is integer cents: lore:decision/0003
export function charge(customerId: string, amountCents: number) {
if (!Number.isInteger(amountCents)) throw new Error("amountCents must be an integer");
return gateway.charge(customerId, amountCents);
}
A directory with rules of its own gets a CONTEXT.md. It needs no frontmatter; its first paragraph is the summary.
# Auth package
Owns session issuance and validation. Never import from pkg/billing.
Ask what governs a file
Before you, or an agent, edit a file, ask lore about it. It reports every note that applies and why: a citation, an anchor, a CONTEXT.md above it, or a note one link away from one of those.
lore context pkg/billing/charge.ts
pkg/billing/charge.ts: 1 note
Money is integer cents (Decision, accepted)
docs/decisions/0003-money-is-integer-cents.md
matched: cited at charge.ts:1; anchor pkg/billing/**
Money is a whole number of cents everywhere, converted to dollars only for display.
--full adds each note’s body, for when a summary is not enough. Add --json to any command for a machine-readable version.
A feature, spec to ship
Now something new: tasks should have due dates. lore’s flow has four steps, and lore next always knows which one you are on. With nothing in progress, it says so:
lore next
nothing in progress; start a spec with lore-spec
1. Say what you want
With an agent, you say “use lore-spec, I want due dates on tasks”, and it interviews you. By hand, it is lore new Spec "Due dates" and an editor. Either way you end up with statements someone could test, and a list of what you are not doing:
---
type: Spec
status: draft
anchors: [src/tasks/**]
---
# Due dates
A task can have a day it is due, and overdue tasks stand out.
## Behaviour
- A task may have a due date (a day, no time).
- Overdue tasks show in red at the top of the list.
## Out of scope
- Reminders or emails.
When it reads right, accept it. lore set checks the value against the schema before writing it.
lore set spec/due-dates status=accepted
updated docs/specs/due-dates.md
status: accepted
lore next
next: lore-plan docs/specs/due-dates.md
Due dates (Spec, accepted)
accepted spec with no effort
2. Plan the work
An effort is the plan: an approach, and a checklist of tasks small enough to finish and test in one sitting. --link fills in the spec it is planned against.
lore new Effort "Due dates" --link specs=spec/due-dates
created docs/efforts/due-dates.md
---
type: Effort
status: planned
specs: ["[[spec/due-dates]]"]
---
# Due dates
## Approach
Store the day as a real date column, sort in the query, and colour in the list view.
## Tasks
- [ ] Add a due_date column to tasks, with a migration
- [ ] Show a date picker when editing a task
- [ ] Sort overdue tasks first and colour them red
Once you agree with the plan, freeze it. lore records the version of each spec the plan was made against, and the effort is ready to build.
lore freeze effort/due-dates
froze docs/efforts/due-dates.md against:
docs/specs/due-dates.md 34dd79dc3cec
lore next
next: lore-build docs/efforts/due-dates.md
Due dates (Effort, planned)
0 of 3 done
next item: Add a due_date column to tasks, with a migration
3. Build it, one task at a time
For each task: read the notes for every file you will touch, do the task, run the tests, tick the box. The next task is always the first open one, so you can stop at any point and lore next will know where you were tomorrow.
lore set effort/due-dates status=active
updated docs/efforts/due-dates.md
status: active
lore context src/tasks/model.ts
src/tasks/model.ts: 1 note
Due dates (Spec, accepted)
docs/specs/due-dates.md
matched: anchor src/tasks/**
A task can have a day it is due, and overdue tasks stand out.
lore tick effort/due-dates
ticked docs/efforts/due-dates.md: Add a due_date column to tasks, with a migration
next: Show a date picker when editing a task (1 of 3 done)
A choice someone might later undo deserves a decision. If the agent stores due dates as real dates because sorting text dates breaks, lore new Decision "Store due dates as dates" keeps that reason next to the code from now on.
4. Ship it
When every box is ticked, lore next names lore-ship. Shipping holds the work against each statement in the spec, makes sure lore check and the tests pass, brings the notes up to date, and sets the effort to done.
When the spec changes
Halfway through, you decide due dates can have a time after all. Edit the spec. Because the plan was frozen, lore notices the plan is now out of date, and stops sending you to build from it:
lore check
docs/efforts/due-dates.md:5: docs/specs/due-dates.md changed since this was frozen; review the plan, then run lore freeze effort/due-dates
7 notes, 2 citations: 1 problem
lore next
next: lore-plan docs/efforts/due-dates.md
Due dates (Effort, active)
docs/specs/due-dates.md changed since it was planned
Revise the tasks, freeze again, and carry on. Once an effort is done, its frozen versions are history and check stops reporting them.
Keep it honest
lore check is the linter for your notes. Put it in CI: it exits 1 when something is wrong.
lore check
docs/specs/search.md:3: 'status' is 'finished'; expected one of draft, accepted, retired
docs/specs/search.md:7: link 'spec/indexing' matches no note
pkg/billing/charge.ts:6: citation 'lore:decision/0009' matches no note
8 notes, 3 citations: 3 problems