@lorekit/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +320 -0
- package/bin/lorekit.mjs +117 -0
- package/package.json +36 -0
- package/skill/lorekit-memory/SKILL.md +139 -0
- package/skill/lorekit-memory/references/scope-resolution.md +65 -0
- package/skill/lorekit-memory/rules/intake.md +61 -0
- package/skill/lorekit-memory/rules/retrospective.md +85 -0
- package/src/adapters/claude.mjs +41 -0
- package/src/adapters/codex.mjs +36 -0
- package/src/adapters/cursor.mjs +42 -0
- package/src/config.mjs +132 -0
- package/src/control.mjs +152 -0
- package/src/core/failure.mjs +28 -0
- package/src/core/lessons.mjs +60 -0
- package/src/core/record.mjs +27 -0
- package/src/core/state.mjs +27 -0
- package/src/doctor.mjs +251 -0
- package/src/hook.mjs +106 -0
- package/src/install.mjs +95 -0
- package/src/mcp-server.mjs +233 -0
- package/src/mcp.mjs +89 -0
- package/src/migrate.mjs +149 -0
- package/src/scope.mjs +59 -0
- package/src/store/format.mjs +99 -0
- package/src/store/index.mjs +18 -0
- package/src/store/local.mjs +313 -0
- package/src/store/remote.mjs +90 -0
- package/src/util.mjs +77 -0
package/README.md
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# @lorekit/cli
|
|
2
|
+
|
|
3
|
+
Install the **LoreKit shared-memory skill** into a project and run health
|
|
4
|
+
checks against your LoreKit MCP server — a small, zero-dependency Node CLI.
|
|
5
|
+
|
|
6
|
+
LoreKit gives coding agents a shared, persistent memory: lessons one agent
|
|
7
|
+
learns are stored centrally and read by every other agent, in every session,
|
|
8
|
+
CI included. This CLI wires an agent up to it in two commands.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx @lorekit/cli install
|
|
14
|
+
npx @lorekit/cli doctor
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Requires Node 18+ (for the built-in `fetch`). No dependencies.
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
### `lorekit install`
|
|
22
|
+
|
|
23
|
+
Scaffolds the `lorekit-memory` skill into `.claude/skills/lorekit-memory/` and
|
|
24
|
+
adds (or updates) a `lorekit` server in the project's `.mcp.json`, preserving
|
|
25
|
+
any other MCP servers already configured.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
lorekit install \
|
|
29
|
+
--endpoint https://<project-ref>.supabase.co/functions/v1/mcp \
|
|
30
|
+
--token lk_rw_your_token
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If run in a TTY without `--endpoint` / `--token`, it prompts for them.
|
|
34
|
+
Use `--yes` for non-interactive environments (endpoint required via flag/env).
|
|
35
|
+
Use `--force` to overwrite an existing skill copy.
|
|
36
|
+
|
|
37
|
+
### `lorekit doctor`
|
|
38
|
+
|
|
39
|
+
Verifies the setup and prints a status report:
|
|
40
|
+
|
|
41
|
+
- Node runtime is 18+
|
|
42
|
+
- the `lorekit-memory` skill is installed
|
|
43
|
+
- the **resolved memory mode and which source decided it**, plus any active
|
|
44
|
+
deny constraints
|
|
45
|
+
- for `local`: the store path, entry count, and whether it is committed or
|
|
46
|
+
gitignored
|
|
47
|
+
- for `remote`: `.mcp.json` has a `lorekit` server, the endpoint is real (not
|
|
48
|
+
the `<project-ref>` placeholder), the token and its permission tier
|
|
49
|
+
(`lk_rw_*` vs `lk_ro_*`), and that the endpoint is reachable
|
|
50
|
+
- for `off`: a note that memory is disabled
|
|
51
|
+
- the git-derived read/write scopes for the current directory
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
lorekit doctor # config + connectivity checks
|
|
55
|
+
lorekit doctor --deep # also does a write → read → delete round-trip (needs lk_rw_*)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Exit code is non-zero if any check fails, so it fits CI gates.
|
|
59
|
+
|
|
60
|
+
### `lorekit hook`
|
|
61
|
+
|
|
62
|
+
The **shared hook engine** behind the Claude Code / Cursor / Codex plugins.
|
|
63
|
+
It is not run by hand — the plugins wire it into their hook config. It reads
|
|
64
|
+
the host framework's JSON on stdin and prints that host's injection format on
|
|
65
|
+
stdout (lessons at session start; a nudge on failure or at end of turn),
|
|
66
|
+
always exiting 0 so it can never block the host agent.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
lorekit hook --adapter <claude|cursor|codex> --event <SessionStart|Stop|…>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
One engine serves all three hosts; each `--adapter` only reshapes input/output
|
|
73
|
+
to that host's contract. See [`plugins/`](../../plugins/) for the bundles.
|
|
74
|
+
|
|
75
|
+
### `lorekit mcp`
|
|
76
|
+
|
|
77
|
+
A **local stdio MCP server**. It exposes LoreKit's `memory.*` tools backed by
|
|
78
|
+
the store the [control model](#memory-modes--the-control-model) resolves, so an
|
|
79
|
+
agent's `.mcp.json` can point at the CLI instead of `mcp-remote <url>` — giving
|
|
80
|
+
the model discoverable, autonomous `memory.*` tool calls **offline against the
|
|
81
|
+
local `.lorekit/` store** (no network, Bash-restricted contexts included).
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
lorekit mcp # serve on stdin/stdout using the resolved mode
|
|
85
|
+
lorekit mcp --mode local --store .lorekit
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
It speaks JSON-RPC 2.0 over newline-delimited stdin/stdout (the MCP stdio
|
|
89
|
+
transport, hand-rolled — zero dependencies) and is **not run by hand**: only
|
|
90
|
+
JSON-RPC frames reach stdout. It serves whatever mode resolves — `local` serves
|
|
91
|
+
the `.lorekit/` files directly, `remote` passes calls through to the hosted
|
|
92
|
+
endpoint, and `off` advertises no tools. Tools advertised: `memory.write`,
|
|
93
|
+
`memory.read`, `memory.list`, `memory.search`, `memory.delete`,
|
|
94
|
+
`memory.archive`.
|
|
95
|
+
|
|
96
|
+
Wire it into `.mcp.json` as an alternative to the `mcp-remote <url>` transport —
|
|
97
|
+
this variant needs no endpoint or token for local mode:
|
|
98
|
+
|
|
99
|
+
```jsonc
|
|
100
|
+
{
|
|
101
|
+
"mcpServers": {
|
|
102
|
+
"lorekit": {
|
|
103
|
+
"command": "npx",
|
|
104
|
+
"args": ["-y", "@lorekit/cli", "mcp"]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Memory modes & the control model
|
|
111
|
+
|
|
112
|
+
Memory has a controllable backend. Three **modes**:
|
|
113
|
+
|
|
114
|
+
| Mode | Where lessons live | Notes |
|
|
115
|
+
|------|--------------------|-------|
|
|
116
|
+
| `off` | nowhere | Memory is disabled — every hook event and store op is a silent no-op. |
|
|
117
|
+
| `local` | markdown files in two tiers (see below) | **Local means _not_ on the hosted website** — local lessons never sync to the LoreKit dashboard. That is the point of local: private-by-default, greppable, git-native. |
|
|
118
|
+
| `remote` | the LoreKit MCP server (hosted) | The shared, cross-machine backend. Reads stay silent until an endpoint + token are configured. This is the default. |
|
|
119
|
+
|
|
120
|
+
### Local store layout — two tiers
|
|
121
|
+
|
|
122
|
+
Local mode mirrors the two-tier model used by the `aw` / persistent-memory
|
|
123
|
+
loops: a per-user **home** tier plus an opt-in per-repo **project** tier.
|
|
124
|
+
|
|
125
|
+
| Tier | Path | Availability |
|
|
126
|
+
|------|------|--------------|
|
|
127
|
+
| **home** | `~/.lorekit/` (override with `LOREKIT_HOME`) | Always available — per-user, cross-repo. |
|
|
128
|
+
| **project** | `<repo>/.lorekit/` (override with `LOREKIT_STORE`) | **Opt-in:** active only when the directory exists. Create it once to start persisting repo/branch lessons in the project. |
|
|
129
|
+
|
|
130
|
+
Each tier is foldered by canonical scope, one markdown file per lesson, with
|
|
131
|
+
YAML frontmatter (`scope, key, tags, source_agent, trigger, created, updated,
|
|
132
|
+
archived_at`) and the lesson as the body:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
~/.lorekit/ <repo>/.lorekit/ (opt-in)
|
|
136
|
+
├── config.json ├── global/
|
|
137
|
+
├── global/ ├── repo/<owner>/<repo>/
|
|
138
|
+
├── repo/<o>/<r>/ └── branch/<owner>/<repo>/<branch>/
|
|
139
|
+
└── branch/<o>/<r>/… └── <slug-of-key>.md
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Read = two-tier merge.** For each scope in the read order, entries from both
|
|
143
|
+
tiers are unioned and the **project tier wins on a key collision** (closer scope
|
|
144
|
+
wins), consistent with the remote narrow→broad merge.
|
|
145
|
+
|
|
146
|
+
**Write routing by scope:**
|
|
147
|
+
|
|
148
|
+
- `global` → **home** tier.
|
|
149
|
+
- `repo::` / `branch::` → **project** tier when it exists (opted-in), else the
|
|
150
|
+
**home** tier.
|
|
151
|
+
|
|
152
|
+
To start persisting repo/branch lessons in the project, create
|
|
153
|
+
`<repo>/.lorekit/` (or run `lorekit migrate --to project --yes`). Commit it to
|
|
154
|
+
share lessons with your team (git-native sharing); add it to `.gitignore` to
|
|
155
|
+
keep them private to your checkout.
|
|
156
|
+
|
|
157
|
+
**Delete / archive across tiers.** Deleting or archiving a key removes the
|
|
158
|
+
closest (project) copy first; a broader **home** copy, if any, remains and takes
|
|
159
|
+
a second delete. This is deliberate — one `delete` never reaches through and
|
|
160
|
+
erases your cross-repo home lesson.
|
|
161
|
+
|
|
162
|
+
> **Same tiering, two realizations (local ⟷ remote).** The tiered, closer-wins
|
|
163
|
+
> merge-read is identical in both backends. Local expresses "universal vs
|
|
164
|
+
> team-shared" as two physical **locations** (home dir vs committed project
|
|
165
|
+
> dir); remote expresses the same distinction through the canonical **scopes**
|
|
166
|
+
> in one shared DB (`global` ≈ your cross-repo home, `repo::` ≈ team-shared) —
|
|
167
|
+
> sharing comes from the account/token, so remote needs no second location.
|
|
168
|
+
> Different mechanism, same concept.
|
|
169
|
+
|
|
170
|
+
### `lorekit migrate` — relocation / rename tool
|
|
171
|
+
|
|
172
|
+
Moved or renamed a local store (e.g. an old `.lore/`)? `migrate` re-writes its
|
|
173
|
+
entries into the current two-tier layout so lessons are never stranded:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
lorekit migrate --from .lore # dry-run: preview counts per scope
|
|
177
|
+
lorekit migrate --from .lore --yes # apply, routing each entry by scope
|
|
178
|
+
lorekit migrate --from .lore --to project --yes # force all entries into the project tier
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Dry-run (preview) by default; `--yes` (or `--apply`) applies. Idempotent — a
|
|
182
|
+
re-run is a no-op. It reads LoreKit's own on-disk format only (it does **not**
|
|
183
|
+
import persistent-memory's `~/.agent-memory/<bucket>/` format).
|
|
184
|
+
|
|
185
|
+
### The control model — two layers, deny-wins
|
|
186
|
+
|
|
187
|
+
Two config layers decide the mode:
|
|
188
|
+
|
|
189
|
+
- **User / machine** — env `LOREKIT_MODE`, `LOREKIT_HOME`, `LOREKIT_STORE`,
|
|
190
|
+
`LOREKIT_DENY` (and `LOREKIT_MCP_URL` / `LOREKIT_TOKEN` for remote), plus a
|
|
191
|
+
user config file `~/.lorekit/config.json`.
|
|
192
|
+
- **Repo / team** — a `.lorekit.json` at the repo root (and/or the existing
|
|
193
|
+
`lorekit` block in `.mcp.json` for the connection).
|
|
194
|
+
|
|
195
|
+
Both files share one schema:
|
|
196
|
+
|
|
197
|
+
```jsonc
|
|
198
|
+
{
|
|
199
|
+
"mode": "local", // select a mode (off | local | remote)
|
|
200
|
+
"store": ".lorekit", // project-tier store dir (relative to repo root, or absolute)
|
|
201
|
+
"deny": ["remote"] // forbid modes outright — deny always wins
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Precedence (a _selection_ within what is allowed):**
|
|
206
|
+
`env LOREKIT_MODE` → user config `mode` → repo config `mode` → built-in default
|
|
207
|
+
(`remote`).
|
|
208
|
+
|
|
209
|
+
**Constraints (`deny`) always win.** Denies are a **union** across every source
|
|
210
|
+
and only ever accumulate — a user-level hard opt-out is a **ceiling the repo
|
|
211
|
+
cannot override**:
|
|
212
|
+
|
|
213
|
+
- A user who declares `"deny": ["remote"]` (privacy / compliance) can never be
|
|
214
|
+
flipped to remote by any repo default or env flag — they resolve to `local`
|
|
215
|
+
(if they selected it) or `off`, never `remote`.
|
|
216
|
+
- A repo or CI job that declares `"deny": ["local"]` (no `.lorekit/` in the tree)
|
|
217
|
+
makes local unselectable there — an env `LOREKIT_MODE=local` is capped, and
|
|
218
|
+
resolution falls through to `remote`, or `off` if both are denied.
|
|
219
|
+
|
|
220
|
+
`off` is never deniable, so it is always the terminal fallback. Run
|
|
221
|
+
`lorekit doctor` to see the resolved mode, **which source decided it**, and any
|
|
222
|
+
active deny constraints.
|
|
223
|
+
|
|
224
|
+
## Options
|
|
225
|
+
|
|
226
|
+
| Flag | Meaning |
|
|
227
|
+
|------|---------|
|
|
228
|
+
| `-d, --dir <path>` | Target project root (default: cwd) |
|
|
229
|
+
| `-e, --endpoint <url>` | LoreKit MCP endpoint |
|
|
230
|
+
| `-t, --token <token>` | LoreKit token |
|
|
231
|
+
| `--mode <mode>` | Memory mode override for `doctor`: `off` / `local` / `remote` |
|
|
232
|
+
| `--store <path>` | Local project-tier store directory (default `.lorekit`) |
|
|
233
|
+
| `--from <path>` | Source store to migrate from (`migrate`) |
|
|
234
|
+
| `--to <tier>` | Migration destination tier: `home` / `project` (`migrate`; default routes by scope) |
|
|
235
|
+
| `--apply` | Apply the migration — alias of `--yes` (`migrate`) |
|
|
236
|
+
| `-y, --yes` | Non-interactive / apply; never prompt |
|
|
237
|
+
| `--force` | Overwrite existing skill files (`install`) |
|
|
238
|
+
| `--deep` | Write/read/delete round-trip (`doctor`) |
|
|
239
|
+
| `--adapter <name>` | Host framework for `hook`: `claude` / `cursor` / `codex` |
|
|
240
|
+
| `--event <name>` | Host hook event for `hook` (else read from the stdin payload) |
|
|
241
|
+
| `-h, --help` | Help |
|
|
242
|
+
| `-v, --version` | Version |
|
|
243
|
+
|
|
244
|
+
## Environment variables
|
|
245
|
+
|
|
246
|
+
| Variable | Purpose |
|
|
247
|
+
|----------|---------|
|
|
248
|
+
| `LOREKIT_MODE` | select a mode: `off` / `local` / `remote` |
|
|
249
|
+
| `LOREKIT_DENY` | comma-separated modes to forbid (deny-wins); e.g. `remote` |
|
|
250
|
+
| `LOREKIT_HOME` | home-tier root + config directory (default `~/.lorekit`) |
|
|
251
|
+
| `LOREKIT_STORE` | project-tier store directory (default `.lorekit`) |
|
|
252
|
+
| `LOREKIT_MCP_URL` / `LOREKIT_ENDPOINT` | endpoint fallback |
|
|
253
|
+
| `LOREKIT_TOKEN` | token fallback |
|
|
254
|
+
| `NO_COLOR` | disable colored output |
|
|
255
|
+
|
|
256
|
+
## What the skill does
|
|
257
|
+
|
|
258
|
+
The installed `lorekit-memory` skill teaches an agent to:
|
|
259
|
+
|
|
260
|
+
- **Read** scoped lessons at the start of a task, on first navigation into
|
|
261
|
+
unfamiliar code, and before risky operations (narrow-to-broad scope merge).
|
|
262
|
+
- **Write** a lesson when something goes wrong — a stuck loop, a repeated
|
|
263
|
+
failure, a gotcha, a near-miss, or a costly wrong assumption — phrased as an
|
|
264
|
+
observation and scoped to the narrowest namespace that fits.
|
|
265
|
+
|
|
266
|
+
This mirrors the read-on-start / write-on-failure loop of the `aw`
|
|
267
|
+
autonomous-workflow agent. See the skill's own `SKILL.md` for the full
|
|
268
|
+
protocol.
|
|
269
|
+
|
|
270
|
+
The **skill** is model-invoked (the agent chooses to use it). For a
|
|
271
|
+
**deterministic** guarantee — lessons injected on every session start, a nudge
|
|
272
|
+
on every tool failure — use the framework plugins in [`plugins/`](../../plugins/),
|
|
273
|
+
which fire the `lorekit hook` engine on host lifecycle events. The skill and
|
|
274
|
+
the hooks compose: hooks guarantee the *timing*, the skill supplies the
|
|
275
|
+
*authoring judgment*.
|
|
276
|
+
|
|
277
|
+
## Testing & validating across frameworks
|
|
278
|
+
|
|
279
|
+
`npm test` (or `node --test test/*.test.mjs`) runs four layers, so you can
|
|
280
|
+
validate all three integrations without launching each agent by hand:
|
|
281
|
+
|
|
282
|
+
1. **Unit** — scope parsing, failure heuristic, lesson formatting, adapter
|
|
283
|
+
mapping/emit.
|
|
284
|
+
2. **Engine end-to-end** — spawns the real `lorekit hook` binary for every
|
|
285
|
+
adapter/event, including a mock MCP server that proves the `SessionStart`
|
|
286
|
+
read path injects lessons, plus throttling and bad-input handling.
|
|
287
|
+
3. **Cross-framework conformance** — replays payload **fixtures** through the
|
|
288
|
+
binary and asserts the stdout matches each host's documented contract
|
|
289
|
+
(`hookSpecificOutput.additionalContext` for Claude/Codex, `followup_message`
|
|
290
|
+
for Cursor).
|
|
291
|
+
4. **Wiring** — runs `claude plugin validate` on the Claude bundle (skipped if
|
|
292
|
+
the `claude` CLI is absent) and structurally validates the Cursor and Codex
|
|
293
|
+
configs; also asserts the vendored skill is in sync with its source.
|
|
294
|
+
|
|
295
|
+
### Harvesting real fixtures (one run per framework)
|
|
296
|
+
|
|
297
|
+
Layer 3 ships with documented seed fixtures under `test/fixtures/`. To prove
|
|
298
|
+
conformance against what each framework *actually* sends, record real payloads
|
|
299
|
+
once by pointing its hook command at the recorder:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
# Temporarily set this env for the hook command in the framework's config,
|
|
303
|
+
# then drive the agent through a session start, a failing command, and a stop:
|
|
304
|
+
LOREKIT_HOOK_RECORD=/abs/path/to/packages/cli/test/fixtures \
|
|
305
|
+
npx @lorekit/cli hook --adapter claude --event SessionStart
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Each invocation overwrites `test/fixtures/<adapter>-<event>.json` with the real
|
|
309
|
+
payload. Commit the updated fixtures; the conformance tests then run offline
|
|
310
|
+
forever. This reduces manual validation to a single capture pass per tool.
|
|
311
|
+
|
|
312
|
+
> The one thing no offline test can cover is a real model loop (the agent
|
|
313
|
+
> actually consuming the injected context). `claude plugin validate` confirms
|
|
314
|
+
> the real Claude CLI accepts the wiring; for a true live check, install the
|
|
315
|
+
> plugin and start one session per tool.
|
|
316
|
+
|
|
317
|
+
## Security note
|
|
318
|
+
|
|
319
|
+
`install` writes your token into `.mcp.json`. Keep that file out of version
|
|
320
|
+
control (LoreKit's root `.gitignore` already ignores `.mcp.json`).
|
package/bin/lorekit.mjs
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// LoreKit CLI — install the shared-memory skill and run health checks.
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { parseArgs, log, err, c } from '../src/util.mjs';
|
|
5
|
+
import { install } from '../src/install.mjs';
|
|
6
|
+
import { doctor } from '../src/doctor.mjs';
|
|
7
|
+
import { hook } from '../src/hook.mjs';
|
|
8
|
+
import { migrate } from '../src/migrate.mjs';
|
|
9
|
+
import { mcpServer } from '../src/mcp-server.mjs';
|
|
10
|
+
|
|
11
|
+
const VERSION = '1.0.0';
|
|
12
|
+
|
|
13
|
+
const HELP = `${c.bold('lorekit')} — shared persistent memory for coding agents
|
|
14
|
+
|
|
15
|
+
${c.bold('Usage')}
|
|
16
|
+
npx @lorekit/cli <command> [options]
|
|
17
|
+
|
|
18
|
+
${c.bold('Commands')}
|
|
19
|
+
install Scaffold the lorekit-memory skill into .claude/skills and
|
|
20
|
+
add the LoreKit server to .mcp.json.
|
|
21
|
+
doctor Verify the skill install, MCP connectivity, token, and scope.
|
|
22
|
+
migrate Relocate a LoreKit-format local store into the current layout.
|
|
23
|
+
Dry-run by default; pass --yes to apply. Idempotent.
|
|
24
|
+
hook Hook engine for Claude Code / Cursor / Codex. Reads the host's
|
|
25
|
+
JSON on stdin and injects lessons or a retrospective nudge.
|
|
26
|
+
Not run by hand — wired into a plugin's hook config.
|
|
27
|
+
mcp Local stdio MCP server. Exposes the memory.* tools backed by the
|
|
28
|
+
resolved store (local .lorekit/ offline, or remote passthrough) so
|
|
29
|
+
.mcp.json can point at the CLI instead of mcp-remote. Speaks
|
|
30
|
+
JSON-RPC on stdin/stdout — not run by hand.
|
|
31
|
+
|
|
32
|
+
${c.bold('Options')}
|
|
33
|
+
-d, --dir <path> Target project root (default: current directory)
|
|
34
|
+
-e, --endpoint <url> LoreKit MCP endpoint
|
|
35
|
+
-t, --token <token> LoreKit token (lk_rw_* to allow writes, lk_ro_* read-only)
|
|
36
|
+
--mode <mode> Memory mode: off | local | remote (doctor override)
|
|
37
|
+
--store <path> Local project-tier store directory (default: .lorekit)
|
|
38
|
+
--from <path> Source store to migrate from (migrate)
|
|
39
|
+
--to <tier> Migration destination tier: home | project (migrate;
|
|
40
|
+
default routes each entry by scope)
|
|
41
|
+
--apply Apply the migration (alias of --yes) (migrate)
|
|
42
|
+
-y, --yes Non-interactive / apply; never prompt
|
|
43
|
+
--force Overwrite existing skill files (install)
|
|
44
|
+
--deep Do a write→read→delete round-trip (doctor)
|
|
45
|
+
--adapter <name> Host framework for hook: claude | cursor | codex
|
|
46
|
+
--event <name> Host hook event (else read from stdin payload)
|
|
47
|
+
-h, --help Show this help
|
|
48
|
+
-v, --version Print the version
|
|
49
|
+
|
|
50
|
+
${c.bold('Environment')}
|
|
51
|
+
LOREKIT_MODE off | local | remote (select a mode)
|
|
52
|
+
LOREKIT_DENY comma list of forbidden modes (deny-wins)
|
|
53
|
+
LOREKIT_HOME home-tier root + config dir (default ~/.lorekit)
|
|
54
|
+
LOREKIT_STORE project-tier store directory (default .lorekit)
|
|
55
|
+
LOREKIT_MCP_URL / LOREKIT_ENDPOINT endpoint fallback
|
|
56
|
+
LOREKIT_TOKEN token fallback
|
|
57
|
+
NO_COLOR disable colored output
|
|
58
|
+
|
|
59
|
+
${c.bold('Examples')}
|
|
60
|
+
npx @lorekit/cli install --endpoint https://ref.supabase.co/functions/v1/mcp --token lk_rw_xxx
|
|
61
|
+
npx @lorekit/cli doctor --deep
|
|
62
|
+
npx @lorekit/cli migrate --from .lore # preview a rename
|
|
63
|
+
npx @lorekit/cli migrate --from .lore --to project --yes
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
async function main() {
|
|
67
|
+
const argv = process.argv.slice(2);
|
|
68
|
+
const args = parseArgs(argv, {
|
|
69
|
+
aliases: { d: 'dir', e: 'endpoint', t: 'token', y: 'yes', h: 'help', v: 'version' },
|
|
70
|
+
booleans: ['yes', 'force', 'deep', 'apply', 'help', 'version'],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// `hook` is machine-facing: it must never print help/errors to stdout
|
|
74
|
+
// (that would corrupt the JSON the host parses). Handle it before the
|
|
75
|
+
// help/usage branch and always resolve to exit 0.
|
|
76
|
+
if (args._[0] === 'hook') {
|
|
77
|
+
return hook(args);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// `mcp` is machine-facing too: only JSON-RPC frames may reach stdout, so it
|
|
81
|
+
// must bypass the help/usage branch. It serves stdio until the client closes.
|
|
82
|
+
if (args._[0] === 'mcp') {
|
|
83
|
+
return mcpServer(args);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (args.version) {
|
|
87
|
+
log(VERSION);
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const command = args._[0];
|
|
92
|
+
|
|
93
|
+
if (args.help || !command) {
|
|
94
|
+
log(HELP);
|
|
95
|
+
return command ? 0 : args.help ? 0 : 1;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
switch (command) {
|
|
99
|
+
case 'install':
|
|
100
|
+
return install(args);
|
|
101
|
+
case 'doctor':
|
|
102
|
+
return doctor(args);
|
|
103
|
+
case 'migrate':
|
|
104
|
+
return migrate(args);
|
|
105
|
+
default:
|
|
106
|
+
err(`${c.red('Unknown command:')} ${command}\n`);
|
|
107
|
+
log(HELP);
|
|
108
|
+
return 1;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
main()
|
|
113
|
+
.then((code) => process.exit(code ?? 0))
|
|
114
|
+
.catch((e) => {
|
|
115
|
+
err(`${c.red('Error:')} ${e && e.stack ? e.stack : e}`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lorekit/cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Install the LoreKit shared-memory skill and run health checks for the LoreKit MCP server.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"lorekit": "bin/lorekit.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"src",
|
|
13
|
+
"skill",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"lorekit",
|
|
24
|
+
"mcp",
|
|
25
|
+
"claude",
|
|
26
|
+
"agent",
|
|
27
|
+
"memory",
|
|
28
|
+
"skill",
|
|
29
|
+
"cli"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"start": "node bin/lorekit.mjs",
|
|
33
|
+
"doctor": "node bin/lorekit.mjs doctor",
|
|
34
|
+
"test": "node --test test/*.test.mjs"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lorekit-memory
|
|
3
|
+
description: >
|
|
4
|
+
Shared persistent memory for coding agents, backed by LoreKit's MCP server.
|
|
5
|
+
Reads scoped lessons at the start of a task or when navigating into
|
|
6
|
+
unfamiliar code (narrow-to-broad scope resolution across branch, repo,
|
|
7
|
+
project, and global), and writes a lesson when something goes wrong — a
|
|
8
|
+
stuck loop, a repeated command failure, a surprising gotcha, a near-miss,
|
|
9
|
+
or a wrong assumption that cost time. Lessons are phrased as observations
|
|
10
|
+
(never rigid rules), scoped to the narrowest namespace that fits, and
|
|
11
|
+
deduplicated on write. Use at task start, before risky operations, and
|
|
12
|
+
after any failure or retrospective. Triggers on "read lessons", "check
|
|
13
|
+
memory", "what do we know about", "remember this", "save a lesson",
|
|
14
|
+
"record this gotcha", "capture this lesson", "/lorekit-memory".
|
|
15
|
+
user-invocable: true
|
|
16
|
+
argument-hint: '[read|write] [scope-hint or lesson]'
|
|
17
|
+
license: MIT
|
|
18
|
+
metadata:
|
|
19
|
+
author: mthines
|
|
20
|
+
version: '1.0.0'
|
|
21
|
+
workflow_type: shared-memory-intake-and-retrospective
|
|
22
|
+
tags:
|
|
23
|
+
- lorekit
|
|
24
|
+
- persistent-memory
|
|
25
|
+
- shared-memory
|
|
26
|
+
- mcp
|
|
27
|
+
- lessons
|
|
28
|
+
- self-improvement
|
|
29
|
+
- retrospective
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# LoreKit Memory
|
|
33
|
+
|
|
34
|
+
Give every agent a shared, persistent memory.
|
|
35
|
+
Lessons live in LoreKit (a Supabase-backed MCP server), so what one agent
|
|
36
|
+
learns on one machine — or in CI — is available to every agent, everywhere,
|
|
37
|
+
in the next session.
|
|
38
|
+
|
|
39
|
+
This skill has two jobs, mirroring the read-on-start / write-on-failure loop
|
|
40
|
+
of the `aw` autonomous-workflow agent:
|
|
41
|
+
|
|
42
|
+
1. **Read** scoped lessons at the start of a task and before risky steps.
|
|
43
|
+
2. **Write** a lesson when something goes wrong, so the next run avoids it.
|
|
44
|
+
|
|
45
|
+
Both jobs run through LoreKit's `memory.*` MCP tools.
|
|
46
|
+
If those tools are not connected, this skill is a no-op — say so once and
|
|
47
|
+
continue the task; never block work because memory is unavailable.
|
|
48
|
+
|
|
49
|
+
> **Modes.** Memory has a controllable backend (`lorekit doctor` shows the
|
|
50
|
+
> resolved one): `remote` (the hosted LoreKit server — the default), `local`
|
|
51
|
+
> (markdown files in two tiers — a per-user **home** tier at `~/.lorekit/` and
|
|
52
|
+
> an opt-in per-repo **project** tier at `<repo>/.lorekit/`), or `off`
|
|
53
|
+
> (disabled). **`local` means _not_ on the hosted website** — local lessons stay
|
|
54
|
+
> on disk and never sync to the LoreKit dashboard. `global` lessons go to home;
|
|
55
|
+
> `repo`/`branch` lessons go to the project tier once you create `<repo>/.lorekit/`
|
|
56
|
+
> (commit it to share with your team, or gitignore it to keep it private), else
|
|
57
|
+
> home. See the `@lorekit/cli` README for the control model, the two-tier merge,
|
|
58
|
+
> and precedence/deny rules.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## When to read (intake)
|
|
63
|
+
|
|
64
|
+
Read at the moments where prior lessons change what you do:
|
|
65
|
+
|
|
66
|
+
- **Task start** — before planning any non-trivial change.
|
|
67
|
+
- **Navigation** — the first time you open an unfamiliar package, module, or subsystem.
|
|
68
|
+
- **Before a risky operation** — migrations, deploys, worktree/branch surgery, force pushes, anything hard to reverse.
|
|
69
|
+
|
|
70
|
+
Follow [rules/intake.md](./rules/intake.md).
|
|
71
|
+
The short version: resolve the current scope, list lessons narrow-to-broad,
|
|
72
|
+
and treat matches as *considerations*, not commands.
|
|
73
|
+
|
|
74
|
+
## When to write (retrospective)
|
|
75
|
+
|
|
76
|
+
Write when a run produces a durable observation worth carrying forward.
|
|
77
|
+
The trigger is friction, not success. Ask the 30-second question:
|
|
78
|
+
|
|
79
|
+
> Was there a stuck loop, a repeated failure, a surprise, a near-miss, or a
|
|
80
|
+
> guess that paid off — something a future run would benefit from knowing?
|
|
81
|
+
|
|
82
|
+
If yes, write it. If the answer is genuinely nothing, write nothing —
|
|
83
|
+
empty retrospectives are skipped entirely.
|
|
84
|
+
|
|
85
|
+
Follow [rules/retrospective.md](./rules/retrospective.md).
|
|
86
|
+
The short version: phrase the lesson as an observation, pick the narrowest
|
|
87
|
+
scope that fits, check for a near-duplicate first, then `memory.write`.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Scope in one line
|
|
92
|
+
|
|
93
|
+
Lessons are partitioned by a canonical scope string (`::` is the only separator):
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
global universal principles
|
|
97
|
+
project::{name} monorepo-wide
|
|
98
|
+
repo::{owner}/{repo} this repository's codebase
|
|
99
|
+
branch::{owner}/{repo}::{branch} short-lived, this branch only
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Read narrow-to-broad and merge; write to the narrowest scope that correctly
|
|
103
|
+
describes where the lesson applies.
|
|
104
|
+
Full resolution rules: [references/scope-resolution.md](./references/scope-resolution.md).
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## The MCP tools
|
|
109
|
+
|
|
110
|
+
| Tool | Use | Token |
|
|
111
|
+
|------|-----|-------|
|
|
112
|
+
| `memory.list` | List lessons for one scope (newest first, tag filter) | read |
|
|
113
|
+
| `memory.search` | Full-text search across scopes (supports `repo::owner/*`) | read |
|
|
114
|
+
| `memory.read` | Read one lesson by scope + key | read |
|
|
115
|
+
| `memory.write` | Store or update a lesson (same scope+key updates in place) | read+write |
|
|
116
|
+
|
|
117
|
+
Write tools need an `lk_rw_*` token; read tools accept `lk_rw_*` or `lk_ro_*`.
|
|
118
|
+
A read-only token cannot write — if a write fails with an authorization error,
|
|
119
|
+
report it and move on; do not retry.
|
|
120
|
+
|
|
121
|
+
Every lesson this skill writes carries the tag `skill::lorekit-memory` plus a
|
|
122
|
+
`source::<trigger>` tag (for example `source::stuck-loop`) so lessons are
|
|
123
|
+
easy to find and audit later.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Setup
|
|
128
|
+
|
|
129
|
+
Install the skill and configure the MCP endpoint with the LoreKit CLI:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
npx @lorekit/cli install
|
|
133
|
+
npx @lorekit/cli doctor
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`install` scaffolds this skill into `.claude/skills/` and adds the LoreKit
|
|
137
|
+
server to `.mcp.json`.
|
|
138
|
+
`doctor` verifies connectivity, token permission, and scope detection.
|
|
139
|
+
See the CLI's own README for flags and troubleshooting.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Scope resolution
|
|
2
|
+
|
|
3
|
+
A scope string names the namespace a lesson belongs to.
|
|
4
|
+
`::` is the **only** valid separator — a single `:` returns a 400 error.
|
|
5
|
+
All segments are lowercased by the server.
|
|
6
|
+
|
|
7
|
+
## The four scope types
|
|
8
|
+
|
|
9
|
+
| Type | Format | Example |
|
|
10
|
+
|------|--------|---------|
|
|
11
|
+
| Global | `global` | `global` |
|
|
12
|
+
| Project (monorepo) | `project::{name}` | `project::agent-skills` |
|
|
13
|
+
| Repository | `repo::{owner}/{repo}` | `repo::mthines/lorekit` |
|
|
14
|
+
| Branch | `branch::{owner}/{repo}::{branch}` | `branch::mthines/lorekit::feat/x` |
|
|
15
|
+
|
|
16
|
+
## Deriving scope from the working directory
|
|
17
|
+
|
|
18
|
+
1. Read the `origin` remote URL and normalize it to `owner/repo`:
|
|
19
|
+
- `git@github.com:mthines/lorekit.git` → `mthines/lorekit`
|
|
20
|
+
- `https://github.com/mthines/lorekit.git` → `mthines/lorekit`
|
|
21
|
+
- strip a trailing `.git`, lowercase the result.
|
|
22
|
+
2. Read the current branch: `git rev-parse --abbrev-ref HEAD`.
|
|
23
|
+
3. Compose:
|
|
24
|
+
- `repo::mthines/lorekit`
|
|
25
|
+
- `branch::mthines/lorekit::{branch}`
|
|
26
|
+
4. If there is no git remote, there is no repo/branch scope — use `global`,
|
|
27
|
+
and optionally `project::{basename-of-repo-root}` for a monorepo.
|
|
28
|
+
|
|
29
|
+
The LoreKit CLI's `doctor` command prints the scope it derives, which is a
|
|
30
|
+
quick way to confirm the agent will read and write to the right place.
|
|
31
|
+
|
|
32
|
+
## Read order (intake)
|
|
33
|
+
|
|
34
|
+
Query most specific first, then merge; more specific scopes win on key collisions:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
branch::{owner}/{repo}::{branch} → repo::{owner}/{repo} → project::{name} → global
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Write scope (retrospective)
|
|
41
|
+
|
|
42
|
+
Use the narrowest scope that correctly describes where the lesson applies:
|
|
43
|
+
|
|
44
|
+
- Branch-scoped lessons do not pollute the repo's lesson set.
|
|
45
|
+
- Repo-scoped lessons do not pollute global.
|
|
46
|
+
- Only truly universal lessons belong in `global`.
|
|
47
|
+
|
|
48
|
+
## Wildcards (search only)
|
|
49
|
+
|
|
50
|
+
`memory.search` accepts an owner-level wildcard in `scopes`:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{ "scopes": ["repo::mthines/*", "global"] }
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Wildcards work **only** in `memory.search` — not in `memory.list`,
|
|
57
|
+
`memory.read`, or `memory.write`.
|
|
58
|
+
|
|
59
|
+
## Validation rules
|
|
60
|
+
|
|
61
|
+
1. `::` is the only separator; a single `:` → 400.
|
|
62
|
+
2. `repo::` must include a `/` (owner/repo); `repo::mthines` → 400.
|
|
63
|
+
3. `branch::` must have exactly two `::` separators.
|
|
64
|
+
4. Only `global`, `project`, `repo`, `branch` prefixes are valid.
|
|
65
|
+
5. Segments are trimmed and lowercased on ingest.
|