lore workflows
August 27, 2026 · View on GitHub
The step-by-step procedures for all seven lore commands. Load this file when executing any lore <command>; SKILL.md routes each user request to the section below. Each section also points to the reference that backs it (entry format, marker conventions, summary/audit templates, config, platform mirrors, history).
init — Initialize the memory bank
Runs once per project (or to start over).
- Resolve targets and takeover check. Targets are determined by the resolution algorithm — see
references/platform-mirrors.md.initalways asks the user via multi-select which agents they use (pre-selected: agents whose platform files already exist or are already lore mirrors), so additional agents can be added even when files were detected; the resolution algorithm's silent return-on-detect applies tomirror/compress, notinit. Explicitmirror_targetsin.lore/.config.jsonoverrides auto-detect (Replace semantics). For each resolved target:- If the file does not exist -> no action; it will be created later in step 7.
- If the file exists AND contains a
## Loresection -> it's already a lore mirror; note it and continue (its My notes will be processed as seed in step 5). - If the file exists AND does NOT contain a
## Loresection -> it's likely from the agent's native/initor hand-written. Show the user:- (a) Take over — rewrite the file as a two-section mirror. The existing content becomes the My notes section (preserved verbatim, treated as seed knowledge in step 5).
- (b) Preserve as-is — leave the file alone. Remove it from
mirror_targetsfor this project (lore won't write to it)..lore/is still generated normally; the user can readSUMMARY.mddirectly or merge manually later. - (c) Abort — exit init. Nothing is created. The user can decide later.
- Repeat for each resolved target before proceeding.
- Check if
.lore/already exists. If yes, warn and ask: archive the current one and re-init, or abort? - Detect monorepo structure (per
references/monorepo-detection.md). Propose scope list to the user; let them rename / merge / split before proceeding. No monorepo ->_global/only. - Scan the project (per scope if applicable):
- Top-level structure, entry points, package manager, language version
- Config files:
package.json,pyproject.toml,Cargo.toml,tsconfig.json,Dockerfile,Makefile, CI README*,CONTRIBUTING*, existing docs- Key dependencies from lockfiles
- Write proposals to
.lore/draft/mirroring the target layout (_global/and per-scope subdirs). Classify scanned facts per the Layer semantics table inSKILL.md, and apply the same layer checks as sync step 3: a picked-over-alternative with a reason is aDECentry, not ARCH; a rule future agents must follow is aCONVentry, not ARCH or code comments. Every entry gets#added:<today>and a deterministic hash-based ID (seereferences/entry-format.md). - For any mirror file that already has a
## Loresection (from step 0), read its My notes section as user-supplied seed knowledge. Parse as atomic bullets into the right layer/scope. - Stop and show the user a summary: which scopes, how many entries per layer per scope, sample of 5-10 entries, and what mirror files will be (re)generated (or skipped per step 0).
- On user confirmation:
mv .lore/draft/* .lore/, run an initialcompressto generateSUMMARY.md, then (re)generate platform mirrors per the two-section structure — auto-create missing files, refresh Lore sections, leave My notes sections intact. Skip any target the user chose "preserve as-is" in step 0. - On user rejection:
rm -rf .lore/draft/. Nothing persists.
The draft/ directory gives a clean rollback path: nothing in .lore/ is real until the user approves.
sync — Update after a change
Runs after the user completes a feature, refactor, or bug fix.
Trigger threshold — only propose sync when at least one is true:
git diff --stat HEADshows 50+ changed lines across 2+ directories- A new top-level module / directory / dependency was added or removed
- A new convention was explicitly discussed (e.g. user said "from now on we use X")
- The user explicitly invokes
syncregardless of diff size
Pure typo fixes, lockfile-only changes, README rewording, or tweaks below the 50-line / 2-directory threshold do not warrant sync.
Compress threshold check (silent, runs before sync proposal):
- Total entry count across all files > 500, or
SUMMARY.mdis missing, orSUMMARY.mdlastLast compressed:date is > 30 days ago
If any of these are true, the skill appends a [COMPRESS NOTICE] to the sync proposal. It does not block the sync — the user can defer.
Procedure:
-
Detect the delta from two sources, combined and de-duplicated:
git diff <last_sync_sha>..HEADif.lore/.config.json#last_sync_shais set and reachable from any local ref. This captures every commit since the last successfulsync.git diff HEAD(current working tree vs.HEAD) — always included when HEAD exists. Captures the net uncommitted changes to tracked files, including staged and unstaged changes. Baregit diffcompares the working tree to the index and misses staged-only changes.- Re-scan new files: inspect added paths in the diffs and enumerate untracked files with
git ls-files --others --exclude-standard. Read their current contents; untracked files are not included ingit diff HEAD. - Fallback when
last_sync_shais absent (older config) or no longer reachable (e.g. aftergit rebaseor a force-push that orphaned the SHA): usegit diff HEADalone and emit a one-line[WARN]to stderr noting that incremental sync is degraded. Working tree alone will not pick up commits made before the next sync ran — the user should re-runsyncaftergit pull --rebaseto re-establish the baseline. - Empty repo (no commits yet):
last_sync_shaisnull; do not run HEAD-based diffs. Enumerate files withgit ls-files --cached --others --exclude-standard, de-duplicate paths, and scan existing files from the working tree. This includes staged and untracked files and reads the latest content if a file changed again after staging; an indexed path missing from the working tree is not a current fact.
-
Determine target scope(s) for each change. Use
git diff --name-only <last_sync_sha>..HEAD(when the baseline is valid) plusgit diff --name-only HEADand the untracked paths from step 1; for an empty repo, use its enumerated paths. Map files -> scopes (e.g.frontend/src/...->scopes/frontend/). Cross-scope changes (root config files) ->_global/. If a change introduces a scope with no directory under.lore/scopes/yet, createscopes/<name>/ARCHITECTURE.md,DECISIONS.md, andCONVENTIONS.md(same layout asinit) and route the entries there. -
Classify each change into one layer:
- New module, new dependency, new file structure ->
ARCHITECTURE.md - "We picked X over Y because Z" ->
DECISIONS.md - New lint rule, new naming pattern, new "we never do X" ->
CONVENTIONS.md - Boundary: follow the Layer semantics in
SKILL.md. The choice itself ("we use X") ->ARCHITECTURE.md; a short inline reason may stay with that fact within the entry length limit. Alternatives or tradeoffs ("why X over Y") ->DECISIONS.md. When recording both the fact and a separate decision, cross-reference their IDs. - Decision check (mandatory before step 4). Check whether the change records an alternative considered or a tradeoff made. If so, emit that rationale as a
DECcandidate and keep the architectural fact in ARCH, cross-referenced by ID. Words such asreason:,because, andfor <purpose>are prompts to inspect the meaning, not automatic split triggers: "Use Next.js App Router; reason: streaming + RSC" may remain one ARCH entry. Detailed rationale that needs its own entry belongs in DEC. Do not invent alternatives or reasoning absent from the sources. If no DEC is warranted, state that in the proposal so the user can review the classification. - Convention check (mandatory before step 4). Ask explicitly for every change: does it introduce or change a rule future agents must follow — a lint/format/tool-config policy, a naming or structural pattern ("every X must Y"), a "we never do X", or an implicit rule visible in code (guard/validation logic,
must/requiredchecks, new or updated tool config)? If yes, that rule is aCONVcandidate; it must not be silently folded into an ARCH entry or left only in code and comments. Signals:must/never/always/required, changes to lint or tool config (e.g..eslintrc*,pyproject.tomltool sections,tsconfig.jsoncompiler options), repeated structural patterns, "from now on..." statements. If you conclude no CONV is warranted, state that explicitly in the proposal so the user can veto.
- New module, new dependency, new file structure ->
-
For each candidate entry:
- Contradicts an existing entry in the same scope/layer -> mark the old one
#stale:<today>and#superseded-by:<new-id>(where<new-id>is the entry in this proposal that replaces it). Emit anALERT. - No replacement entry exists yet (user is removing a fact without substituting) -> mark the old one
#stale:<today>only; the chain can be backfilled later. - Refines an existing entry -> if the body is unchanged, update tags only (bump
#verified:<today>) and keep the ID. If the body changes, write a new entry with a freshly hashed ID and mark the old one#stale:<today>+#superseded-by:<new-id>— the ID hashes the body, so a body rewrite always produces a new ID (seereferences/entry-format.md). - Genuinely new -> append with
#added:<today>and a new hash ID.
- Contradicts an existing entry in the same scope/layer -> mark the old one
-
De-duplicate before appending: for each candidate, run
python skill/scripts/find_duplicates.py --json --candidate "<entry body>"(when installed, use<skill>/scripts/find_duplicates.py). Pass only the body, without its ID or status tags, using safe argument quoting. For text that is awkward to quote, use--candidate-file <utf8-text-file>. Without a candidate argument, the script compares only entries already on disk.- Inspect pairs containing
CANDIDATE-unsaved; the output can also contain existing-vs-existing pairs. Compare candidates in the same proposal with each other as well, since unsaved candidates are not in the script's entry index. - Treat matches as hints, not proof of equivalence. Only skip a candidate and bump an existing entry's
#verifiedwhen they express the same fact in the applicable scope and the existing entry is active (seereferences/entry-format.md). A match to a stale or superseded entry must not suppress a current candidate or revive the old entry via a tags-only update. Apply the trust rules in step 6; keep meaningfully different facts.
- Inspect pairs containing
-
Apply trust level (controlled by
.lore/.config.json#sync_trust, default"medium"):Change type highmedium(default)lowDe-duplicate hit (same fact already present) auto-apply auto-apply confirm REFINED, tags only (body unchanged) auto-apply auto-apply confirm REFINED, body changed (new ID + supersede link) auto-apply confirm confirm NEWentryauto-apply confirm confirm STALEmarkauto-apply confirm confirm ALERTconfirm confirm confirm Auto-applied changes are written without per-change confirmation and reported at the end. Confirmation-required changes are bundled into a single diff proposal and shown together.
-
Generate the proposed diff (for any confirmation-required changes) using the
[NEW]/[STALE]/[REFINED]/[ALERT]/[COMPRESS NOTICE]markers. Seereferences/stale-new-markers.mdfor the full convention and user reply semantics. -
Stop and wait for user confirmation for any pending changes. Auto-applied changes need no confirmation.
-
After the user accepts, write to
.lore/*only. Do not regenerate platform mirrors fromsync(unlesssync_updates_mirror: trueis set in.lore/.config.json) — this is intentional. See "Mirror update triggers" inSKILL.mdand the dedicatedlore mirrorcommand. -
Update
.lore/.config.json#last_sync_shato the currentgit rev-parse HEAD. Idempotent: re-running sync without new commits writes the same SHA. If HEAD does not exist (empty repo), set tonull. The field is optional and additive; older configs without it keep working through the fallback in step 1.
Source priority (when sources disagree):
- Git diff of changed code (most reliable — shows what actually happened)
- Static scan of new files (reliable for facts, not for intent)
- Conversation context (lowest priority — see below)
- Test/build output (auxiliary — only consulted if 1-3 are ambiguous)
Conversation context is opt-in. The skill does not automatically mine chat messages for memory updates. It only extracts from conversation when the user explicitly says things like "note this down" / "remember this" / "this is important". Reason: chat context is high-noise, and silent extraction creates false entries.
query — Answer from memory
Read-only.
- Determine which scope(s) the question targets:
- "this project" / "the whole codebase" / unspecified ->
_global/first, then SUMMARY.md - "frontend" / "in the web app" / "the React side" ->
scopes/frontend/ - "backend" / "the API" ->
scopes/backend/ - If ambiguous, search SUMMARY.md for clues.
- "this project" / "the whole codebase" / unspecified ->
- Grep the target files for relevant entries. If multi-layer or multi-scope, check all relevant ones.
- For current-state answers, skip entries with
#stale:<date>or#superseded-by:<id>. Apply the active-entry rule inreferences/entry-format.md, also used bycompress. A stale entry without a successor is still excluded. For explicit historical questions, read it as historical evidence and label its status; usehistoryfor commit context andhistory --follow-superseded <id>when a replacement chain exists. - SUMMARY is an index, not a source of truth for a claim. A one-line summary is a locating hint; when citing a fact or making a decision, read the full referenced entry (including its tags) in
_global/orscopes/first.
- For current-state answers, skip entries with
- If found: answer concisely, citing fully-qualified entry IDs (e.g.
[scopes/frontend/DECISIONS.md#DEC-2026-02-03-7c19]). Mention#verifieddate. - If not found but inferable from the code: say so explicitly ("Not in memory, but inferable from
frontend/src/store/index.ts..."). Offer to add it. - Never fabricate an entry. If memory doesn't have it, say it doesn't have it.
audit — Check memory vs. reality
Read-only with respect to canonical memory. It reports drift without changing entries or SUMMARY.md, but it does write the dated report described below.
- For each entry in
_global/*andscopes/*/*, find the code/config it claims to describe (scoped to the relevant scope's source tree) and compare against current state. - Also flag: entries whose reference date —
#verifiedif present, else#added— is older than 90 days. Runpython skill/scripts/find_stale.py --days=90 --json(or<skill>/scripts/find_stale.pywhen installed) to enumerate them mechanically. - Write the report to
.lore/audit/audit-YYYY-MM-DD.md, organized by scope. Do not mark anything as stale in the main files. Do not emit ALERT blocks. Seereferences/audit-template.mdfor the full report format and severity definitions. - Stop. User reviews the report and decides what to do. To act on findings, the user runs
sync.
This separation keeps audit honest: it observes, it does not edit. ALERT noise is contained to sync and query, where the agent is about to act on the memory.
compress — Build the top-level summary
Long-term compression. Generates SUMMARY.md and, when auto_mirror: true (or the user accepts the per-target prompt), regenerates platform mirrors. Underlying ARCHITECTURE / DECISIONS / CONVENTIONS files are untouched.
- Run
python skill/scripts/list_entries.py --json(or<skill>/scripts/list_entries.pywhen installed) to enumerate every entry. Use the JSON output as the input for the selection step. - Exclude entries with
#stale:<date>or#superseded-by:<id>using the active-entry rule inreferences/entry-format.md. Optionally runpython skill/scripts/find_stale.py --jsonto highlight long-unverified entries for review; age alone does not make an entry inactive. - For each (scope, layer) pair, pick 3-5 most important entries using the selection rule in
references/summary-template.md. - Write
SUMMARY.mdper the template inreferences/summary-template.md. (This is the only file written on the canonical.lore/side.) - If
auto_mirror: truein config, regenerate platform mirrors (this is one of the three mirror update triggers — see "Mirror update triggers" inSKILL.md). Ifauto_mirror: false, ask per target and only write the mirrors the user accepts. Content-based dedup: if the new Lore section equals the current one, skip the write. The My notes section is always preserved. - Stop. Once mirror regeneration has either written or been declined per target,
compressis done.
Compress is idempotent. Running it twice produces the same SUMMARY.md content (modulo the date stamp). Re-running after new syncs picks up new entries automatically.
mirror — Regenerate platform mirrors
Regenerate all configured platform mirrors from the current state of .lore/*. Content-based dedup skips targets whose Lore section is unchanged.
- Read current
.lore/SUMMARY.mdand the scope-tagged index. - For each configured mirror target (per
references/platform-mirrors.md), read the existing file and detect the section boundary. - Validate the two-section structure for each target: if it lacks the
---separator, lacks a## My notessection, or is a user-notes-only file without## Lore, stop for that target and ask the user how to proceed — never overwrite an anomalous file silently (section detection rules:references/platform-mirrors.md). - For each target, compare the new Lore section content against the existing one. Skip writing if content is identical (content-based dedup; avoids empty
git diff). - If different, replace the Lore section; preserve the My notes section verbatim. If the user asked to wipe My notes, archive it to
.lore/.archive/<file>-<date>.mdfirst. - Stop. Report: "Mirror updated:
<file>" or "No changes needed:<file>" per target.
This command exists because most users want sync to be fast and unobtrusive, but occasionally need the agent-facing files to reflect recent knowledge. mirror is that explicit "publish to agent view" step. Structure validation happens automatically during each regeneration (step 3), and a user-requested My notes wipe is handled as a normal conversation request.
history — Show git commits related to a memory entry
Read-only. Surfaces the git history that backs a memory entry, a file, or a scope, so the agent can answer "why does this decision exist?" with a pointer to the actual commits rather than a guess.
When to trigger: only when the user explicitly invokes lore history or names a subcommand ("show me the git history", "show me the commits behind this entry"). Generic "history" or "git log" alone does not trigger — defer to the user's intent.
| User says (examples) | Command |
|---|---|
| "lore history DEC-2026-02-03-7c19" | lore history <entry-id> |
| "lore history frontend/src/store/index.ts" | lore history <file-path> |
| "lore history --scope=frontend" | lore history --scope=<name> |
Procedure (entry form):
- Resolve project root (
.lore/must exist), confirm git repo + git CLI on PATH. - Load the entry index (
list_entries.py --json), locate the entry, derive#addedas the default--since(fallback1970-01-01). - Resolve the code file (backtick path in entry text -> scope directory -> project root), run
git log, render Markdown or JSON, print to stdout. - Stop. No files are written.
Data source contract: local git CLI only. No GitHub / GitLab API. No LLM call. The agent invoking the command does the semantic work (interpreting commit messages, deciding relevance).
Relationship to other commands: fills the previously-empty cell of "read git history" (other commands read either the current file system or git diff only).
Supported flags: --since=<YYYY-MM-DD>, --follow-superseded, --json. Full dispatch rules, --since normalization (same-day commit safety), output format, and the error/exit-code table live in references/history-command.md.
Cross-workflow notes
Who writes what:
| File | Written by |
|---|---|
.lore/SUMMARY.md | compress (and by init, via its initial compress) |
.lore/{_global,scopes/<scope>}/<LAYER>.md | init, sync, manual edits |
.lore/.config.json | init, manual edits |
.lore/audit/audit-<date>.md | audit |
.lore/draft/ | init (proposals; moved into .lore/ on confirm, removed on reject) |
<project-root>/<platform files> | init, mirror, compress (if auto_mirror: true), sync (if sync_updates_mirror: true) |
What remains protected: changes outside the configured sync_trust allowance wait for confirmation; auto-applied sync changes are reported; platform mirrors are not rewritten on every sync by default; compress never deletes entries; init never overwrites user-written platform files without explicit takeover.
Typical sequence: init -> [sync <-> query <-> audit] (interchangeable, agent picks by context) -> compress (when SUMMARY.md grows stale) -> mirror (or auto via compress if auto_mirror: true).