@lorekit/cli 1.67.0 → 1.69.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 CHANGED
@@ -277,6 +277,25 @@ missing. It exits **non-zero** when the key is found in no readable store, so it
277
277
  fits scripts. `--json` emits the full normalized record(s) and which store each
278
278
  came from. Both a scope and a key are required (else a usage error).
279
279
 
280
+ #### Batch mode: several references in one call
281
+
282
+ Pass **two or more** references and `show` fetches all of them in one pass
283
+ instead of one invocation per lesson:
284
+
285
+ ```bash
286
+ lorekit show global::prefer-guard-clauses repo::acme/widget::build-flags --json
287
+ ```
288
+
289
+ Each positional must itself parse as a complete `<scope>::<key>` reference —
290
+ that is what tells this apart from the existing `show <scope> <key>`
291
+ two-positional form, whose first positional is a **bare** scope with no `::`.
292
+ Results are reported in the same order the references were given, and
293
+ `--json` emits `{ "results": [{ "scope", "key", "offline", "remote", "diverged" }, …] }`
294
+ instead of the single-reference top-level shape. The remote half of a batch is
295
+ ONE round trip (`POST /memories/read`), not one request per reference; the
296
+ offline half still resolves each reference through the two-tier store, so a
297
+ project-tier lesson still shadows a same-key home-tier one.
298
+
280
299
  ### Addressing a memory: `<scope::key>`
281
300
 
282
301
  `show`, `write` and `link` all take a memory the same way, and the single-token
@@ -523,10 +542,11 @@ lorekit invariants candidates --scope repo::owner/repo
523
542
  ```
524
543
 
525
544
  A cluster is a candidate when the **summed `seen_count`** across its members
526
- is at least `--min-seen-count` (default `3`), or any member's own
527
- `<!-- meta: seen_count=… status=… trigger-context="…" -->` comment (the
528
- convention documented in the `lorekit-setup` skill's
529
- `self-improvement-loops.md`) already declares a non-`"active"` status.
545
+ is at least `--min-seen-count` (default `3`), or any member already declares a
546
+ non-`"active"` status. Status is read from a `status::<value>` **tag** — the
547
+ canonical home, per the `lorekit-setup` skill's `self-improvement-loops.md` —
548
+ falling back to a legacy `<!-- meta: … status=… -->` body comment for lessons
549
+ written before tags were the convention; the tag wins when both are present.
530
550
  Candidates are ranked by (summed `seen_count` × distinct scopes), descending.
531
551
  **For each candidate it prints every memory the merge would collapse** —
532
552
  that list is the whole point of the command.
@@ -536,10 +556,11 @@ whose members already resolve to a named recurrence class is flagged as a
536
556
  stronger case ("this should join an existing invariant") than a merely
537
557
  similar one ("this might be a new class").
538
558
 
539
- It deliberately does **not** classify a `trigger-context` into a
540
- glob/command/error-shape — that judgment is the human step the compile
541
- pipeline's "never auto-compile, never auto-gate" rule protects, so the raw
542
- string is printed, never interpreted — and it does **not** know about
559
+ It deliberately does **not** classify a member's applicability signal — its
560
+ visible `**Applies when:**` body line, or a legacy `trigger-context` meta
561
+ field — into a glob/command/error-shape. That judgment is the human step the
562
+ compile pipeline's "never auto-compile, never auto-gate" rule protects, so the
563
+ raw string is printed, never interpreted — and it does **not** know about
543
564
  `compiled_to` (no such field exists yet, so an already-compiled candidate can
544
565
  still surface here — a known, named gap, not a silent omission).
545
566
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lorekit/cli",
3
- "version": "1.67.0",
3
+ "version": "1.69.0",
4
4
  "description": "Install the LoreKit shared-memory skill and run health checks for the LoreKit MCP server.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -175,3 +175,13 @@ new count. Then either move to the next scope or close out the pass.
175
175
  - **Someone else's lesson deserves more caution.** A lesson tagged from another
176
176
  agent or a teammate is not automatically yours to delete — when in doubt,
177
177
  archive rather than hard-delete, and flag it in the plan.
178
+ - **`codebase-knowledge` is durable structure, not an audit log.** The shared
179
+ `codebase-knowledge` bucket (tag `codebase-knowledge`, `knowledge::<symbol>@<path>`
180
+ and `hotspot::<path>` keys — see the `lorekit-setup` skill) is a live,
181
+ multi-loop signal store: every LoreKit loop in the repo both reads and feeds it.
182
+ Groom it like a signal store, never like the audit-log prose it superficially
183
+ resembles. Lint its structure and dedupe genuine key collisions, but do **not**
184
+ mass-archive by age or volume, and do **not** re-key a `knowledge::<symbol>@<path>`
185
+ entry off its structural key — that key is the join a reader matches against.
186
+ A stale fact carries `verified_at_sha`; prefer letting its TTL expire or the
187
+ owning loop overwrite it over hand-removing it.
@@ -75,7 +75,12 @@ Read at the moments where prior lessons change what you do:
75
75
 
76
76
  Follow [rules/intake.md](./rules/intake.md).
77
77
  The short version: resolve the current scope, list lessons narrow-to-broad,
78
- and treat matches as *considerations*, not commands.
78
+ and treat matches as *considerations*, not commands. When a run is about to
79
+ change code, intake step 6 reads the standard shared **`codebase-knowledge`**
80
+ bucket — the cross-loop `hotspot::<path>` / `knowledge::<symbol>@<path>` record
81
+ for the files this run will touch, matched by `symbol@path`. A run that verifies
82
+ a structural fact contributes it back, so the layer compounds across every
83
+ LoreKit loop in the repo (contract in the `lorekit-setup` skill).
79
84
 
80
85
  ## When to write (retrospective)
81
86
 
@@ -115,9 +120,9 @@ Full resolution rules: [references/scope-resolution.md](./references/scope-resol
115
120
 
116
121
  | Tool | Use | Token |
117
122
  |------|-----|-------|
118
- | `memory.list` | List lessons for one scope (newest first, tag filter) | read |
123
+ | `memory.list` | List lessons for one scope (newest first, tag filter). `scope` is optional — omit it to list across every scope you can see | read |
119
124
  | `memory.search` | Full-text search across scopes (supports `repo::owner/*`) | read |
120
- | `memory.read` | Read one lesson by scope + key | read |
125
+ | `memory.read` | Read one lesson by key (`scope` optional — see below) — or several at once via `refs: ["scope::key", …]` (one round trip, up to 32) | read |
121
126
  | `memory.write` | Store or update a lesson (same scope+key updates in place) | read+write |
122
127
 
123
128
  Write tools need write permission (`lk_rw_*` or `lk_wo_*`); read tools need read
@@ -125,6 +130,24 @@ permission (`lk_rw_*` or `lk_ro_*`). A read-only token cannot write and a
125
130
  write-only token cannot read — if a call fails with an authorization error,
126
131
  report it and move on; do not retry.
127
132
 
133
+ Holding only a bare key, with no scope? Call `memory.read { key }` without a
134
+ `scope`. It resolves across every scope you can see, preferring the most
135
+ specific — `project`, then `branch`, then `repo`, then `global` — and the
136
+ response's `scope` field names the one that answered. If the key also exists
137
+ elsewhere, `other_scopes` lists those: that is your cue to pass an explicit
138
+ `scope` next time, because only you know which one you meant. An omitted scope
139
+ does NOT mean `global`. The same applies to `memory.list` and
140
+ `memory.list_archived`, which simply list across everything instead of
141
+ resolving to one winner.
142
+
143
+ Already know the exact `scope::key` refs you need — from a prior `memory.list`
144
+ or `memory.search`, or from citations another lesson names — and need more than
145
+ one? Pass them all as `memory.read`'s `refs` array instead of one call per
146
+ lesson. Each ref resolves independently: an unresolved one lands in the
147
+ response's `missing` list rather than failing the whole call, and matching is
148
+ against the **verbatim stored scope** (no case-folding), unlike a single
149
+ `scope`/`key` read.
150
+
128
151
  Every lesson this skill writes carries the tag `skill::lorekit-memory` plus a
129
152
  `source::<trigger>` tag (for example `source::stuck-loop`) so lessons are
130
153
  easy to find and audit later.
@@ -37,6 +37,27 @@ Query most specific first, then merge; more specific scopes win on key collision
37
37
  branch::{owner}/{repo}::{branch} → repo::{owner}/{repo} → project::{name} → global
38
38
  ```
39
39
 
40
+ ## Reading without a scope
41
+
42
+ `memory.read`, `memory.list` and `memory.list_archived` all take an **optional**
43
+ `scope`. Omitting it does not mean `global` — it means "every scope I can see".
44
+
45
+ `memory.read { key }` resolves the key across all of them and returns ONE
46
+ lesson, preferring the most specific scope type:
47
+
48
+ ```text
49
+ project::{name} → branch::{owner}/{repo}::{branch} → repo::{owner}/{repo} → global
50
+ ```
51
+
52
+ (Same precedence as the read order above, ranked by scope TYPE rather than by
53
+ your own scopes — a hosted call has no working directory to derive them from.)
54
+ Ties within a type break by most-recently-updated. The response's `scope` names
55
+ the scope that answered; `other_scopes`, when present, lists the ones it
56
+ shadowed — pass an explicit `scope` next time if that is not the one you meant.
57
+
58
+ `memory.list` / `memory.list_archived` simply widen instead: every entry carries
59
+ its own `scope`, and nothing is resolved away.
60
+
40
61
  ## Write scope (retrospective)
41
62
 
42
63
  Use the narrowest scope that correctly describes where the lesson applies:
@@ -54,7 +75,8 @@ Use the narrowest scope that correctly describes where the lesson applies:
54
75
  ```
55
76
 
56
77
  Wildcards work **only** in `memory.search` — not in `memory.list`,
57
- `memory.read`, or `memory.write`.
78
+ `memory.read`, or `memory.write`. On the two read tools, omitting `scope`
79
+ entirely (above) is the way to reach past a single scope.
58
80
 
59
81
  ## Validation rules
60
82
 
@@ -59,3 +59,44 @@ If lessons matched, note them in one or two lines before proceeding
59
59
  ("LoreKit: 2 relevant lessons — worktree naming, migration order").
60
60
  If nothing matched, say nothing and continue.
61
61
  If the MCP tools are not connected, note it once and continue without them.
62
+
63
+ ## 6. When the run will change code — read the shared codebase-knowledge
64
+
65
+ Steps 1–4 read **your own** bucket. When this run is about to change code, do one
66
+ more read — not optional, it is how the cross-loop synergy reaches you: the shared
67
+ **`codebase-knowledge`** bucket. This is the repo-scoped, cross-loop record of what
68
+ the codebase has taught every LoreKit loop that touched it
69
+ (`knowledge::<symbol>@<path>` facts, `hotspot::<path>` counters). Its keys are
70
+ **structural** (`symbol@path`, a file path) rather than prose, so you match them
71
+ against the concrete files and symbols this run will touch and pull only the
72
+ records that apply. Read it for exactly the files you will change:
73
+
74
+ ```text
75
+ memory.list { scope: "repo::{owner}/{repo}", tags: ["codebase-knowledge"], limit: 100 }
76
+ # keep only hotspot::<path> / knowledge::<symbol>@<path> whose <path> (and <symbol>)
77
+ # the current change will actually touch — a known regression hotspot or a
78
+ # repeatedly-flagged symbol is then planned with that history in hand.
79
+ ```
80
+
81
+ If this run **verifies** a structural fact about a symbol or file (a consumer
82
+ count you swept, an invariant you confirmed, a defect you fixed at a SHA),
83
+ contribute it back so the next code-changer benefits — write it to
84
+ `codebase-knowledge` under the write contract in the `lorekit-setup` skill,
85
+ `rules/self-improvement-loops.md § Shared codebase-knowledge`. The layer fills
86
+ because its readers also feed it.
87
+
88
+ Four rules make this safe, and they are non-negotiable:
89
+
90
+ - Match **structurally and narrowly** — the paths/symbols of this run, never the
91
+ whole bucket.
92
+ - It **raises care, never lowers a bar** — plan more coverage on a hotspot; an
93
+ absent record is not evidence of safety.
94
+ - A fact may be **stale** (it carries the writer's `verified_at_sha`) — treat it
95
+ as a consideration and re-verify against the code.
96
+ - **Read-only.** Never write another host's bucket; write ownership stays with
97
+ its one owner.
98
+
99
+ Do **not** cross-read another host's `loop::<host>-lessons` — those are prose
100
+ advice with no structural key to match on. The full contract and when to wire a
101
+ cross-read into a host is the `lorekit-setup` skill,
102
+ `rules/self-improvement-loops.md § Cross-bucket reads (targeted, read-only)`.
@@ -14,6 +14,13 @@ Ask the 30-second question:
14
14
  If no, stop — write nothing. Empty retrospectives are skipped.
15
15
  If yes, continue.
16
16
 
17
+ > A retrospective records **prose advice** ("last time X went wrong when Y"). A
18
+ > *structural fact* this run verified about a symbol or file — a consumer count
19
+ > you swept, an invariant you confirmed, a defect you fixed at a SHA — is a
20
+ > different thing: it belongs in the shared **`codebase-knowledge`** bucket under
21
+ > a `knowledge::<symbol>@<path>` key, not here. See the intake step 6 write-back
22
+ > note and the `lorekit-setup` skill, `§ Shared codebase-knowledge`.
23
+
17
24
  ## 2. Phrase it as an observation
18
25
 
19
26
  Write what happened and what worked, not a commandment.
@@ -66,7 +66,7 @@ mechanically-checked rule instead of text a reader has to notice.
66
66
  | **Compiled invariant** | A declarative `obligations-map.mjs` entry a CI gate checks against a changed-file set — see [rules/compiled-invariants.md](./rules/compiled-invariants.md) | **Yes** | Enforced once `gating`; most lessons never qualify |
67
67
 
68
68
  A recurrence gate connects the first two: a lesson that recurs (`seen_count >= 3`)
69
- or is marked `status=structural` becomes promotion-eligible. Entrenchment guards
69
+ or carries the `status::structural` tag becomes promotion-eligible. Entrenchment guards
70
70
  keep the fast tier from reinforcing its own wrong conclusions. The third rung has
71
71
  its own, stricter gate — the compilability test — and most promotion-eligible
72
72
  lessons stop at the second rung because they fail it.
@@ -93,6 +93,47 @@ It covers: when to add a loop (and when not to), the bucket convention (tag
93
93
  read/write steps, the promotion gate, the entrenchment guards, a wiring
94
94
  checklist, and an interactive setup flow.
95
95
 
96
+ ### A lesson body is markdown for humans — nothing hidden
97
+
98
+ Whatever host you wire, its write step carries one non-negotiable contract: the
99
+ `value` is **markdown a person reads**, and it carries no HTML comment, no
100
+ front-matter, no JSON blob, and no `key=value` header. Every fact the store
101
+ already models goes in its own write field, never restated in the prose:
102
+
103
+ | Fact | Field, not prose |
104
+ | ---- | ---------------- |
105
+ | Recurrence | `seen_count` — the store increments it on every overwrite |
106
+ | Expiry | `ttl_days` (`clear_ttl` to make permanent) |
107
+ | Status (`structural` / `promoted`) | a `status::<value>` tag |
108
+ | Owning host, bucket kind | `host`, `kind` |
109
+ | Repo / branch / commit / PR | the `origin_*` fields |
110
+ | What triggered the write | `trigger` |
111
+
112
+ A hidden block does not just look untidy — it is *wrong*. A `seen_count=1` baked
113
+ into prose is stale the first time the lesson recurs, while the column that
114
+ governs promotion moves without it, and a markdown reader is shown a lesson that
115
+ begins mid-sentence. Structure the prose instead: a takeaway title, a visible
116
+ **Applies when** line, then short bold-labelled paragraphs under ~1,500
117
+ characters. The full shape, the field-by-field rationale, and the writing rules
118
+ are [the lesson record](./rules/self-improvement-loops.md#the-lesson-record).
119
+
120
+ ## The shared codebase-knowledge layer (automatic cross-loop synergy)
121
+
122
+ A per-host lessons bucket is private to one host. There is also **one shared
123
+ bucket every code-touching host reads and, under a contract, writes**:
124
+ `codebase-knowledge` — a repo-scoped, structurally-keyed record
125
+ (`knowledge::<symbol>@<path>` facts, `hotspot::<path>` counters) of what the
126
+ codebase has taught every LoreKit loop that touched it. Because the name is fixed
127
+ and the key is structural, a loop wired by one person compounds with a loop wired
128
+ by another: a host about to change code reads the history for exactly the files it
129
+ will touch, and a host that verifies a structural fact contributes it back. That
130
+ is the synergy that appears for a user who wired a single skill and nothing else.
131
+
132
+ Wire it whenever a host changes code (read at its plan/apply seam) or verifies a
133
+ durable structural fact (write under the contract). The full specification — the
134
+ bucket table, the automatic read side, and the seven-bullet multi-writer write
135
+ contract — is [rules/self-improvement-loops.md § Shared codebase-knowledge](./rules/self-improvement-loops.md#shared-codebase-knowledge-the-standard-cross-loop-layer).
136
+
96
137
  ## Set up CI state (deterministic hosts)
97
138
 
98
139
  Follow [rules/ci-state-records.md](./rules/ci-state-records.md). It covers: when
@@ -58,7 +58,7 @@ this rule should say so out loud rather than sell the store.
58
58
  | | Lesson (`self-improvement-loops.md`) | CI state record (this file) |
59
59
  | --- | --- | --- |
60
60
  | Author | a model, at the end of a run | a script, deterministically |
61
- | Value | prose + a `meta:` comment | JSON (an object, not a bare scalar) |
61
+ | Value | markdown prose only — metadata lives in the store's own fields | JSON (an object, not a bare scalar) |
62
62
  | How a reader uses it | **advisory** — a consideration that can be overridden | **authoritative** — parsed and branched on |
63
63
  | Recurrence / promotion | yes (`seen_count`, human-gated hardening) | n/a — nothing is inferred, so nothing needs gating |
64
64
  | Entrenchment risk | high — the reason those guards exist | none; the risks are cardinality and secrets instead |
@@ -34,9 +34,9 @@ A lesson is a compile candidate only if **all four** hold:
34
34
  1. **Trigger detectable without judgement.** The condition that should fire the
35
35
  check can be recognized from a file path, a diff shape, or another mechanical
36
36
  signal — not "whether this edit is the kind that matters," which needs a
37
- reader's judgement. This is exactly what the existing "trigger-context must be
38
- concrete" requirement in the lessons loop exists for: a lesson with a vague
39
- trigger-context was never going to compile, so demanding concreteness at
37
+ reader's judgement. This is exactly what the existing "**Applies when** must
38
+ be concrete" requirement in the lessons loop exists for: a lesson with a vague
39
+ applicability signal was never going to compile, so demanding concreteness at
40
40
  write time is what keeps the door open later.
41
41
  2. **Assertion statable as must-remain-true.** The rule has to be expressible as
42
42
  an invariant ("if A changed, B must also be in the changed set"), not a
@@ -114,8 +114,9 @@ A read-only survey over the memory store. It reuses `dedupe`'s Jaccard clusterin
114
114
  to find near-duplicate lessons, then ranks the resulting clusters by
115
115
  `(summed seen_count × distinct scopes)`, descending. A cluster is worth
116
116
  reporting when the summed `seen_count` across its members is at least
117
- `--min-seen-count` (default 3), or any member's `meta` comment already declares a
118
- non-`active` status. For each candidate it prints every memory the merge would
117
+ `--min-seen-count` (default 3), or any member already declares a non-`active`
118
+ status — read from a `status::<value>` tag, falling back to a legacy `meta`
119
+ comment for lessons written before tags were the convention. For each candidate it prints every memory the merge would
119
120
  collapse — deliberately the default view, not hidden behind `--verbose`, because
120
121
  that list *is* the point of the command: a human decides from it whether the
121
122
  cluster deserves a hand-written entry.
@@ -127,9 +128,9 @@ you to notice by hand.
127
128
 
128
129
  Two things it deliberately does **not** do, on purpose:
129
130
 
130
- - **It does not classify trigger-contexts.** A lesson's raw `trigger-context`
131
- string, when present, is printed verbatim — never interpreted into a
132
- glob/command/error-shape. Turning "parses into a detectable trigger" from a
131
+ - **It does not classify applicability signals.** A lesson's raw signal
132
+ (its **Applies when** line, or a legacy `trigger-context` meta field) is
133
+ printed verbatim — never interpreted into a glob/command/error-shape. Turning "parses into a detectable trigger" from a
133
134
  string into an actual predicate is the human step the compile pipeline
134
135
  protects; automating it would be exactly the "auto-compile" this pipeline
135
136
  refuses to do.