@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 +29 -8
- package/package.json +1 -1
- package/skill/lorekit-groom/rules/grooming-pass.md +10 -0
- package/skill/lorekit-memory/SKILL.md +26 -3
- package/skill/lorekit-memory/references/scope-resolution.md +23 -1
- package/skill/lorekit-memory/rules/intake.md +41 -0
- package/skill/lorekit-memory/rules/retrospective.md +7 -0
- package/skill/lorekit-setup/SKILL.md +42 -1
- package/skill/lorekit-setup/rules/ci-state-records.md +1 -1
- package/skill/lorekit-setup/rules/compiled-invariants.md +9 -8
- package/skill/lorekit-setup/rules/self-improvement-loops.md +278 -37
- package/src/commands/invariants.mjs +17 -10
- package/src/commands/show.mjs +244 -3
- package/src/shared/candidates-pure.mjs +94 -5
- package/src/shared/mirror-pairs.mjs +9 -0
- package/src/shared/scope-precedence.mjs +82 -0
- package/src/store/local.mjs +95 -1
- package/src/store/remote.mjs +60 -3
- package/src/surfaces.generated.mjs +14 -17
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
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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
|
@@ -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
|
|
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
|
|
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
|
|
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 "
|
|
38
|
-
concrete" requirement in the lessons loop exists for: a lesson with a vague
|
|
39
|
-
|
|
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
|
|
118
|
-
|
|
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
|
|
131
|
-
|
|
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.
|