@lorekit/cli 1.63.1 → 1.64.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
@@ -419,11 +419,17 @@ body below a small length threshold), **untrimmed-value** (real content with
419
419
  surrounding whitespace), **empty-key** (blank key), **volatile-key** (the key
420
420
  carries a per-sighting identifier — a run of 6+ digits such as a GitHub comment
421
421
  id, or a `pr<n>` / `issue<n>` segment — so it never collides, never dedups, and
422
- freezes `seen_count` at 1), and **malformed-scope** (e.g.
423
- a single `:` where `::` is expected). `lint` **exits non-zero (1) when any issue
424
- is found**, so it is usable as a CI gate (`lorekit lint || exit 1`); a clean run —
425
- or one where only a store is unavailable exits 0. The pure rule predicates live
426
- in `lessons-view.mjs` and are unit-tested one rule at a time.
422
+ freezes `seen_count` at 1), **malformed-scope** (e.g.
423
+ a single `:` where `::` is expected), and **unkinded-state-record** (the value
424
+ parses as a JSON object/array but `kind` is unset bare scalars are excluded,
425
+ caught by `short-value` only when short; a longer bare scalar is judged by no
426
+ rule because the SessionStart digest excludes non-lesson kinds by *kind*,
427
+ not by shape, so an un-kinded state record renders as a raw JSON blob inline
428
+ in every session's digest; set `--kind bus`/`--kind signal`).
429
+ `lint` **exits non-zero (1) when any issue is found**, so it is usable as a CI
430
+ gate (`lorekit lint || exit 1`); a clean run — or one where only a store is
431
+ unavailable — exits 0. The pure rule predicates live in `lessons-view.mjs` and
432
+ are unit-tested one rule at a time.
427
433
 
428
434
  ### `lorekit dedupe`
429
435
 
@@ -703,7 +709,9 @@ loops: a per-user **home** tier plus an opt-in per-repo **project** tier.
703
709
 
704
710
  Each tier is foldered by canonical scope, one markdown file per lesson, with
705
711
  YAML frontmatter (`scope, key, tags, source_agent, trigger, created, updated,
706
- archived_at`) and the lesson as the body:
712
+ archived_at, origin_repo, origin_branch, origin_commit, origin_pr,
713
+ expires_at, seen_count, kind, host` — the same column set as `format.mjs`'s
714
+ `FIELDS`) and the lesson as the body:
707
715
 
708
716
  ```
709
717
  ~/.lorekit/ <repo>/.lorekit/ (opt-in)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lorekit/cli",
3
- "version": "1.63.1",
3
+ "version": "1.64.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": {
@@ -56,6 +56,10 @@ Findings are structural, not semantic — each names its rule:
56
56
  frozen at 1. Re-key it onto the structural pattern and move the identifier
57
57
  into the body.
58
58
  - **malformed-scope** — the scope string is invalid.
59
+ - **unkinded-state-record** — the value parses as a JSON object/array but the
60
+ entry has no `kind`, so it renders as a raw JSON blob in every SessionStart
61
+ digest instead of being excluded by `isGeneralLesson`. Set `--kind bus` or
62
+ `--kind signal` on the record.
59
63
 
60
64
  These are the cheapest wins and the least controversial, so clear them first.
61
65
  For each: either **fix it in place** (rewrite a too-short value into a real
@@ -4,8 +4,10 @@ description: >
4
4
  Sets up a self-improvement loop for a skill, workflow, or agent using LoreKit,
5
5
  so a host gets better across runs by reading its own accumulated lessons at
6
6
  the start of every run and hardening the proven ones into permanent rules.
7
- Designs the two tiers (a fast episodic tier of LoreKit lessons, advisory-only;
8
- a slow procedural tier that promotes a recurring lesson into a host rule),
7
+ Designs the lessons loop (a fast episodic tier of LoreKit lessons, advisory-only;
8
+ a slow procedural tier that promotes a recurring lesson into a host rule; and,
9
+ for the rarer judgement-free, independently-checkable case, a third rung that
10
+ compiles a recurring lesson into a mechanically-enforced CI invariant instead),
9
11
  chooses the lesson bucket (tag + key namespace) and scopes, and installs the
10
12
  entrenchment guards that stop a learning loop from reinforcing its own
11
13
  mistakes. Also covers the non-LLM case: giving a deterministic job (a GitHub
@@ -311,6 +311,30 @@ export const LINT_RULES = {
311
311
  const reason = scopeIssue(e.scope);
312
312
  return reason ? `malformed scope: ${reason}` : null;
313
313
  },
314
+ // A CI state record (ci-state-records.md) is JSON on an entry left with no
315
+ // `kind`. `fetchLessons`'s `isGeneralLesson(kind)` filter — `kind == null ||
316
+ // kind === 'lesson'` — is a hard exclusion by kind, not by shape, so a record
317
+ // like this is NOT excluded from the SessionStart digest: it renders inline as
318
+ // a raw JSON blob, unreadable next to prose lessons. Conservative by design,
319
+ // like `volatile-key`: only a value that PARSES as a JSON object or array
320
+ // fires — a bare scalar (a plain lesson body that happens to be a number or a
321
+ // quoted string) is excluded. A short bare scalar is caught by `short-value`;
322
+ // a bare scalar at or above `MIN_VALUE_LEN` (e.g. a 20-char quoted string) is
323
+ // judged by no rule at all — a known gap, not a claim that `short-value`
324
+ // covers every bare scalar — and this rule has no opinion on it either way.
325
+ 'unkinded-state-record': (e) => {
326
+ if (e.kind != null) return null; // an explicit kind is exactly what this rule exists to require.
327
+ const v = String(e.value ?? '').trim();
328
+ if (!v) return null; // an empty value is `empty-value`'s to report.
329
+ let parsed;
330
+ try {
331
+ parsed = JSON.parse(v);
332
+ } catch {
333
+ return null; // not JSON — an ordinary prose lesson, not this rule's concern.
334
+ }
335
+ if (parsed === null || typeof parsed !== 'object') return null; // a bare scalar — short-value's to catch when short, otherwise unjudged.
336
+ return 'value is a JSON object/array with no kind set — it renders as a raw JSON blob in every SessionStart digest; set --kind bus or --kind signal';
337
+ },
314
338
  };
315
339
 
316
340
  // Run every lint rule against one normalized entry, returning the findings it
@@ -34,6 +34,13 @@ export const FIELDS = [
34
34
  // columns above: a file written before this existed decodes it as absent,
35
35
  // which the read projection reports as 0 rather than inventing a count.
36
36
  'seen_count',
37
+ // Taxonomy — `kind` ('lesson'/'bus'/'signal') and `host` (which agent loop
38
+ // wrote it), mirroring the hosted `memories.kind`/`memories.host` columns.
39
+ // Appended like the columns above: a file written before these existed
40
+ // decodes them as absent, which `normalizeEntry` in lessons-view.mjs falls
41
+ // back to inferring from tags for, same as an absent remote value.
42
+ 'kind',
43
+ 'host',
37
44
  ];
38
45
 
39
46
  // Serialize an entry ({ ...columns, value }) into file text.
@@ -100,7 +100,7 @@ class LocalStore {
100
100
  // contract's error surfacing.
101
101
  async write({
102
102
  scope, key, value, tags, source_agent, trigger, created_at, ttl_days, clear_ttl,
103
- origin_repo, origin_branch, origin_commit, origin_pr,
103
+ origin_repo, origin_branch, origin_commit, origin_pr, kind, host,
104
104
  } = {}) {
105
105
  const now = new Date().toISOString();
106
106
  const existing = this._findByKey(scope, key);
@@ -129,6 +129,10 @@ class LocalStore {
129
129
  origin_branch: origin_branch ?? existing?.entry.origin_branch ?? null,
130
130
  origin_commit: origin_commit ?? existing?.entry.origin_commit ?? null,
131
131
  origin_pr: origin_pr ?? existing?.entry.origin_pr ?? null,
132
+ // Taxonomy, same last-known-wins provenance as origin above: an
133
+ // omitted --kind/--host on a re-write must not erase a prior value.
134
+ kind: kind ?? existing?.entry.kind ?? null,
135
+ host: host ?? existing?.entry.host ?? null,
132
136
  created,
133
137
  updated: existing ? now : override || now,
134
138
  archived_at: null,
@@ -175,6 +179,8 @@ class LocalStore {
175
179
  origin_branch: entry.origin_branch ?? null,
176
180
  origin_commit: entry.origin_commit ?? null,
177
181
  origin_pr: entry.origin_pr ?? null,
182
+ kind: entry.kind ?? null,
183
+ host: entry.host ?? null,
178
184
  created: entry.created ?? now,
179
185
  updated: entry.updated ?? now,
180
186
  archived_at: entry.archived_at ?? null,