@lorekit/cli 1.68.0 → 1.70.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 +34 -0
- package/skill/lorekit-memory/SKILL.md +20 -2
- package/skill/lorekit-memory/references/scope-resolution.md +23 -1
- package/skill/lorekit-setup/SKILL.md +153 -92
- package/skill/lorekit-setup/rules/ci-state-records.md +1 -1
- package/skill/lorekit-setup/rules/cold-start-seeding.md +102 -0
- package/skill/lorekit-setup/rules/compiled-invariants.md +9 -8
- package/skill/lorekit-setup/rules/loop-health.md +117 -0
- package/skill/lorekit-setup/rules/proving-improvement.md +111 -0
- package/skill/lorekit-setup/rules/self-improvement-loops.md +196 -41
- package/skill/lorekit-setup/rules/team-and-portfolio.md +107 -0
- package/skill/lorekit-setup/templates/README.md +26 -0
- package/skill/lorekit-setup/templates/ci-job.md +90 -0
- package/skill/lorekit-setup/templates/code-changing-agent.md +89 -0
- package/skill/lorekit-setup/templates/multi-step-orchestrator.md +76 -0
- package/skill/lorekit-setup/templates/reviewer-reconcile-host.md +86 -0
- package/src/commands/invariants.mjs +17 -10
- package/src/commands/lint.mjs +4 -2
- package/src/commands/show.mjs +244 -3
- package/src/shared/candidates-pure.mjs +94 -5
- package/src/shared/lessons-view.mjs +71 -0
- 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
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Team & portfolio — from one loop to a practice
|
|
2
|
+
|
|
3
|
+
Everything else in this skill is written for one person wiring one host. But the store
|
|
4
|
+
is shared, and that changes the economics: a lesson one person's loop writes can rescue
|
|
5
|
+
another person's host, the same pain relearned by three people is a louder signal than
|
|
6
|
+
any single run, and a new hire can inherit years of accumulated workflow intelligence on
|
|
7
|
+
day one. This file is how a team turns scattered loops into a compounding practice.
|
|
8
|
+
|
|
9
|
+
Some of this is guidance a person acts on today; some names a dashboard/product surface
|
|
10
|
+
that would make it turnkey (flagged **[product]**).
|
|
11
|
+
|
|
12
|
+
## Contents
|
|
13
|
+
|
|
14
|
+
- [The rediscovery signal: where a team loop pays off](#the-rediscovery-signal-where-a-team-loop-pays-off)
|
|
15
|
+
- [The maturity model](#the-maturity-model)
|
|
16
|
+
- [Promotion as review](#promotion-as-review)
|
|
17
|
+
- [Onboarding: inherit the lore](#onboarding-inherit-the-lore)
|
|
18
|
+
- [Cross-host portability](#cross-host-portability)
|
|
19
|
+
- [Governance is mostly self-executing](#governance-is-mostly-self-executing)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## The rediscovery signal: where a team loop pays off
|
|
24
|
+
|
|
25
|
+
At single-host scale, `seen_count` is a promotion trigger. At team scale it is a
|
|
26
|
+
**rediscovery counter**: the same lesson climbing in count across **distinct authors and
|
|
27
|
+
scopes** is proof a pain is *shared*, not personal — and shared pain is exactly where a
|
|
28
|
+
team-wide loop or rule pays off most.
|
|
29
|
+
|
|
30
|
+
Find it with the tools that already exist:
|
|
31
|
+
|
|
32
|
+
- `lorekit dedupe` — near-duplicate lessons written independently by different people are
|
|
33
|
+
one shared lesson wearing several names; the cluster's summed `seen_count` is the team
|
|
34
|
+
signal.
|
|
35
|
+
- `lorekit invariants candidates` — ranks clusters by summed `seen_count` × distinct
|
|
36
|
+
scopes, which is precisely "how many people, in how many places, hit this."
|
|
37
|
+
- **[product]** A dashboard "rediscovery radar" that surfaces cross-author clusters with
|
|
38
|
+
a one-click "wire a loop here" would make this turnkey.
|
|
39
|
+
|
|
40
|
+
## The maturity model
|
|
41
|
+
|
|
42
|
+
The FAST → SLOW → compiled-invariant rungs describe one loop's lifecycle. Aggregated
|
|
43
|
+
across every host, they become a **portfolio view** that shows where the team's loops are
|
|
44
|
+
thin:
|
|
45
|
+
|
|
46
|
+
| Level | State | Evidence |
|
|
47
|
+
| ----- | ----- | -------- |
|
|
48
|
+
| **L0** | No loop | The host has no `loop::<host>-lessons` bucket |
|
|
49
|
+
| **L1** | Fast advisories only | Lessons accrue and are read; nothing promoted yet |
|
|
50
|
+
| **L2** | Slow promotions landing | Recurring lessons hardened into host rules (`status::promoted`) |
|
|
51
|
+
| **L3** | Compiled invariant | A mechanically-checked `obligations-map` entry, `gating` |
|
|
52
|
+
|
|
53
|
+
Plot each workflow's level against how many teammates actually run it. A high-traffic
|
|
54
|
+
workflow stuck at L0/L1 is the team's biggest missed compounding. **[product]** A
|
|
55
|
+
maturity board on `/insights` would render this from the tags and counters that already
|
|
56
|
+
exist.
|
|
57
|
+
|
|
58
|
+
## Promotion as review
|
|
59
|
+
|
|
60
|
+
Promoting a `repo::` lesson to a team rule is a governance moment, and it maps cleanly
|
|
61
|
+
onto a process the team already trusts — code review:
|
|
62
|
+
|
|
63
|
+
1. A promotion-eligible lesson (`seen_count >= 3` or `status::structural`) enters a
|
|
64
|
+
"pending team rule" state.
|
|
65
|
+
2. A **second engineer** reviews it, exactly as they would a PR. The gate the loop
|
|
66
|
+
computes automatically becomes the human checkpoint.
|
|
67
|
+
3. On approval, the promotion is a normal reviewed edit to the repo's rules/docs; add a
|
|
68
|
+
`status::promoted` tag so it stops re-suggesting and stands as an audit trail.
|
|
69
|
+
4. On rejection, write a lesson about **why it was not generalized** — that reasoning is
|
|
70
|
+
itself reusable.
|
|
71
|
+
|
|
72
|
+
## Onboarding: inherit the lore
|
|
73
|
+
|
|
74
|
+
Onboarding content does not need to be written — it is the exact bucket an agent already
|
|
75
|
+
reads at SessionStart, re-aimed at a human. Generate a new hire's "what this team learned
|
|
76
|
+
the hard way" brief from:
|
|
77
|
+
|
|
78
|
+
- the highest-`seen_count` and `status::promoted` lessons per repo (rules first),
|
|
79
|
+
- the `codebase-knowledge` hotspots and invariants (where the bodies are buried),
|
|
80
|
+
- ordered by rung: promoted rules, then advisories.
|
|
81
|
+
|
|
82
|
+
Because it is derived from the live store, it is never stale and needs no maintenance.
|
|
83
|
+
|
|
84
|
+
## Cross-host portability
|
|
85
|
+
|
|
86
|
+
Eight people on Claude Code, Cursor, and Codex normally fragment knowledge into per-tool
|
|
87
|
+
silos. The shared store lets host-diversity be an *asset* instead:
|
|
88
|
+
|
|
89
|
+
- **Host-tag every lesson** (the `host` field). A lesson read by a *different* host than
|
|
90
|
+
wrote it is portable; one only ever read within its authoring host may be host-local.
|
|
91
|
+
- **A lesson proven across two hosts is stronger evidence** it is a real team rule than
|
|
92
|
+
one seen within a single tool — weight it higher at promotion.
|
|
93
|
+
- At promotion to a team rule, **strip host-specific phrasing** ("in Claude Code, …") so
|
|
94
|
+
the rule reads for any host.
|
|
95
|
+
|
|
96
|
+
## Governance is mostly self-executing
|
|
97
|
+
|
|
98
|
+
The failure mode of "team governance" is that it assumes a human process watching loops
|
|
99
|
+
decay — which solo devs and small teams skip, so entrenchment guards rot. Lean on the
|
|
100
|
+
mechanisms that need no staffing:
|
|
101
|
+
|
|
102
|
+
- **TTL expiry** drops stale lessons automatically — decay is built in, not a chore.
|
|
103
|
+
- **The recurrence gate** means a single bad run can never rewrite a host.
|
|
104
|
+
- **The injection cap** bounds read cost no matter how the bucket grows.
|
|
105
|
+
|
|
106
|
+
The only step that genuinely needs a human is **promotion review** above. Keep the
|
|
107
|
+
staffed surface that small; let the defaults do the rest.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Loop recipe cards
|
|
2
|
+
|
|
3
|
+
Copy-paste starters for wiring a self-improvement loop into a host. Pick the card
|
|
4
|
+
that matches your host, paste its read/write steps at the seams it names, and fill in
|
|
5
|
+
`<host>` / `<owner>/<repo>`. Each card already bakes in the five non-negotiables from
|
|
6
|
+
the skill's [Non-negotiables](../SKILL.md#non-negotiables-do-not-optimize-these-away):
|
|
7
|
+
a per-host **injection cap**, a **TTL**, the **advisory-only** contract, the **body
|
|
8
|
+
contract** (pure markdown), and a **fire-once check** so you prove the loop closes
|
|
9
|
+
before you trust it.
|
|
10
|
+
|
|
11
|
+
| Card | Use it for | Shape |
|
|
12
|
+
| ---- | ---------- | ----- |
|
|
13
|
+
| [code-changing-agent.md](./code-changing-agent.md) | A host that edits code and can fail in recurring ways (an autonomous workflow, a fix/implement agent) | Lessons loop + the shared `codebase-knowledge` read/write |
|
|
14
|
+
| [reviewer-reconcile-host.md](./reviewer-reconcile-host.md) | A host that posts durable outputs at a shared target it revisits (a PR reviewer, a triager, a linter that files tickets) | Lessons loop + the reconcile-on-re-run Signal bucket |
|
|
15
|
+
| [multi-step-orchestrator.md](./multi-step-orchestrator.md) | A pipeline that fails in classifiable ways (wrong triage, a missed step, a false-green gate) | The plain two-tier lessons loop |
|
|
16
|
+
| [ci-job.md](./ci-job.md) | A deterministic job that needs last-run state (flaky set, last deployed SHA, a baseline) | JSON state records, not lessons |
|
|
17
|
+
|
|
18
|
+
Every card is a *starting point*, not a spec. The authoritative rules are one level up:
|
|
19
|
+
[self-improvement-loops.md](../rules/self-improvement-loops.md) (lessons),
|
|
20
|
+
[ci-state-records.md](../rules/ci-state-records.md) (state records),
|
|
21
|
+
[proving-improvement.md](../rules/proving-improvement.md) (proof),
|
|
22
|
+
[loop-health.md](../rules/loop-health.md) (maintenance).
|
|
23
|
+
|
|
24
|
+
**`N` — the injection cap — is `5` in every card by default.** Raise it only with a
|
|
25
|
+
reason; a loop that reads 30 lessons into every run is why agents learn to ignore
|
|
26
|
+
injected lore. See [Non-negotiables #2](../SKILL.md#non-negotiables-do-not-optimize-these-away).
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Recipe card — CI job (state record, not a lesson)
|
|
2
|
+
|
|
3
|
+
For a **deterministic job** — a GitHub Actions workflow, a cron script, a release
|
|
4
|
+
pipeline — that needs to know *what happened last time*: which tests flaked, the last
|
|
5
|
+
benchmark number, the last deployed SHA. This is **not** a lessons loop: nothing here
|
|
6
|
+
is authored or interpreted by a model, so there is no `seen_count`, no promotion, no
|
|
7
|
+
entrenchment guard. A different, smaller set of guards applies. Full rule:
|
|
8
|
+
[ci-state-records.md](../rules/ci-state-records.md).
|
|
9
|
+
|
|
10
|
+
Fill in: `<job>` (e.g. `test`), `<slug>` (e.g. `flaky-tests`), `<owner>/<repo>`.
|
|
11
|
+
|
|
12
|
+
> Use `actions/cache` instead if **only** the next CI run will ever read this. LoreKit
|
|
13
|
+
> earns its place only when the fact is *also* useful to an agent or a human.
|
|
14
|
+
|
|
15
|
+
## Bucket
|
|
16
|
+
|
|
17
|
+
- Tag `ci::<job>-state` (deliberately NOT `loop::…`). Key `ci-state::<slug>`, one slug
|
|
18
|
+
per fact, **overwritten in place** — never one key per run.
|
|
19
|
+
- Taxonomy: pass `--kind bus --host ci` explicitly, or the record leaks into every
|
|
20
|
+
session's SessionStart digest as a raw JSON blob.
|
|
21
|
+
|
|
22
|
+
## Record shape (versioned JSON)
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"v": 1,
|
|
27
|
+
"updated_by_run": "https://github.com/<owner>/<repo>/actions/runs/123",
|
|
28
|
+
"commit": "0f4a1c9…",
|
|
29
|
+
"data": { "flaky": ["src/queue.test.ts::retries on 429"], "consecutive_green": 3 }
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Read step — early in the job
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
set -euo pipefail
|
|
37
|
+
STATE_JSON='{}'
|
|
38
|
+
if lorekit show --scope "repo::<owner>/<repo>" --key 'ci-state::<slug>' --remote --json > state.json 2>&1; then
|
|
39
|
+
cat state.json # always echo — never `|| true`
|
|
40
|
+
STATE_JSON=$(jq -r '.remote.record.value // "{}"' state.json)
|
|
41
|
+
else
|
|
42
|
+
cat state.json
|
|
43
|
+
echo "No prior state (first run, or LoreKit unreachable) — continuing with defaults."
|
|
44
|
+
fi
|
|
45
|
+
VERSION=$(jq -r '.v // 0' <<<"$STATE_JSON")
|
|
46
|
+
[ "$VERSION" = "1" ] || { echo "State schema v${VERSION} != v1 — rebuilding."; STATE_JSON='{}'; }
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`lorekit show` exits 1 on a miss — that is the first run, branch on it; do not `|| true`
|
|
50
|
+
it away (that swallows a real auth/network failure too).
|
|
51
|
+
|
|
52
|
+
## Write step — end of job (`if: always()` if state should survive a failing run)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
set -euo pipefail
|
|
56
|
+
jq -nc \
|
|
57
|
+
--arg run "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
|
|
58
|
+
--arg sha "${GITHUB_SHA}" \
|
|
59
|
+
--argjson data "$NEW_DATA" \
|
|
60
|
+
'{v: 1, updated_by_run: $run, commit: $sha, data: $data}' \
|
|
61
|
+
| lorekit write \
|
|
62
|
+
--scope "repo::<owner>/<repo>" --key 'ci-state::<slug>' \
|
|
63
|
+
--tags 'ci::<job>-state' --kind bus --host ci \
|
|
64
|
+
--ttl-days 7 --remote --json \
|
|
65
|
+
| tee write.log \
|
|
66
|
+
|| echo "LoreKit write failed (exit $?) — not failing the build; see output above."
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`--ttl-days` is **not optional** and should be short (7 for daily jobs) — it refreshes
|
|
70
|
+
every write, so it expires only when the *job* goes silent. See
|
|
71
|
+
[TTL is a liveness guard](../rules/ci-state-records.md#ttl-is-a-liveness-guard).
|
|
72
|
+
|
|
73
|
+
## Tokens
|
|
74
|
+
|
|
75
|
+
Write with a **write-only `lk_wo_*`** token (a leaked CI token then cannot exfiltrate
|
|
76
|
+
the team's lore); read with `lk_ro_*`; use `lk_rw_*` only where one job genuinely needs
|
|
77
|
+
both.
|
|
78
|
+
|
|
79
|
+
## Guards (do not skip)
|
|
80
|
+
|
|
81
|
+
Bounded cardinality (one key per fact) · no secrets, ever (build the payload from an
|
|
82
|
+
allow-list, never `env | jq -R`) · explicit short TTL · never on the critical path
|
|
83
|
+
(a store outage logs and continues) · last-write-wins (serialise with a `concurrency`
|
|
84
|
+
group) · version every record. Full list:
|
|
85
|
+
[Guards](../rules/ci-state-records.md#guards-do-not-skip-these).
|
|
86
|
+
|
|
87
|
+
## Verify
|
|
88
|
+
|
|
89
|
+
Run the job twice; confirm `lorekit list --kind bus --host ci` still shows **one** row
|
|
90
|
+
per fact (key count must not grow with run count).
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Recipe card — code-changing agent
|
|
2
|
+
|
|
3
|
+
For a host that **edits code** and fails in recurring ways: an autonomous workflow, a
|
|
4
|
+
fix-bug agent, an implement-suggestion worker. It wants two reads — its own lessons
|
|
5
|
+
*and* the shared `codebase-knowledge` for the files it is about to touch — and two
|
|
6
|
+
writes — a lesson on failure, and a verified structural fact back to
|
|
7
|
+
`codebase-knowledge`.
|
|
8
|
+
|
|
9
|
+
Fill in: `<host>` (e.g. `fix-bug`), `<owner>/<repo>`. `N = 5`.
|
|
10
|
+
|
|
11
|
+
## Bucket
|
|
12
|
+
|
|
13
|
+
- Lessons: tag `loop::<host>-lessons`, key `<host>-lessons::<slug>`.
|
|
14
|
+
- Shared: tag `codebase-knowledge` (read by every code-touching loop; see
|
|
15
|
+
[the shared layer](../rules/self-improvement-loops.md#shared-codebase-knowledge-the-standard-cross-loop-layer)).
|
|
16
|
+
|
|
17
|
+
## Read step — at the start of the run AND at the plan/apply seam
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
# 1. Own lessons, narrow-to-broad, capped at N per scope:
|
|
21
|
+
memory.list { scope: "repo::<owner>/<repo>", tags: ["loop::<host>-lessons"], limit: 5 }
|
|
22
|
+
memory.list { scope: "global", tags: ["loop::<host>-lessons"], limit: 5 }
|
|
23
|
+
|
|
24
|
+
# 2. When the run names a subsystem/error, one targeted search:
|
|
25
|
+
memory.search { q: "<keywords>", scopes: ["repo::<owner>/*", "global"], limit: 5 }
|
|
26
|
+
|
|
27
|
+
# 3. At the plan/apply seam — once you have the concrete file/symbol list:
|
|
28
|
+
memory.list { scope: "repo::<owner>/<repo>", tags: ["codebase-knowledge"], limit: 100 }
|
|
29
|
+
# keep ONLY hotspot::<path> / knowledge::<symbol>@<path> whose <path>/<symbol>
|
|
30
|
+
# this run will touch. Apply as PLANNING INPUT: raise coverage on a hotspot,
|
|
31
|
+
# design around a known invariant. Advisory; re-verify against the code.
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Match each lesson's **Applies when** line against the current run; apply matching
|
|
35
|
+
**Do this instead** lines as *considerations*, not commands. On a `repo::` vs `global`
|
|
36
|
+
collision, `repo::` wins. An absent `codebase-knowledge` record is never evidence of
|
|
37
|
+
safety.
|
|
38
|
+
|
|
39
|
+
## Write step — on failure / at end of run
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
# Dedup first so a recurrence UPDATEs in place:
|
|
43
|
+
memory.search { q: "<key words of the lesson>", scopes: ["repo::<owner>/<repo>", "global"], limit: 10 }
|
|
44
|
+
|
|
45
|
+
memory.write {
|
|
46
|
+
scope: "<global | repo::<owner>/<repo>>", # repo:: if it names a repo path/term, else global
|
|
47
|
+
key: "<host>-lessons::<slug>",
|
|
48
|
+
value: "<markdown lesson body — no hidden blocks>",
|
|
49
|
+
tags: ["loop::<host>-lessons", "source::<trigger>"],
|
|
50
|
+
trigger: "<stuck-loop | command-failure | gotcha | near-miss | assumption-wrong | paid-off>",
|
|
51
|
+
ttl_days: 90
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If the host **verified** a structural fact this run (an invariant a symbol holds, a
|
|
56
|
+
file that produced a defect), write it back to `codebase-knowledge` under the
|
|
57
|
+
multi-writer contract — `verified_at_sha`, `source_agent`, merge-not-clobber, raise
|
|
58
|
+
care never suppress:
|
|
59
|
+
[write side](../rules/self-improvement-loops.md#write-side--how-the-layer-fills-and-why-many-writers-stay-safe).
|
|
60
|
+
|
|
61
|
+
## Lesson body — copy this shape
|
|
62
|
+
|
|
63
|
+
```markdown
|
|
64
|
+
# <one-line takeaway — what to do, not what it is about>
|
|
65
|
+
|
|
66
|
+
**Applies when:** <concrete signal — file glob, task type, tool name, error shape>
|
|
67
|
+
|
|
68
|
+
**What happened:** <the concrete observable>
|
|
69
|
+
**Why:** <root cause, or "unknown">
|
|
70
|
+
**Do this instead:** <prescriptive, testable instruction>
|
|
71
|
+
**Promotion target:** <the host rule this would harden if promoted, or "none">
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Fire-once check (do this before trusting the loop)
|
|
75
|
+
|
|
76
|
+
1. Trigger the failure this loop is meant to catch.
|
|
77
|
+
2. Confirm the lesson landed: `memory.list { scope: "<expected>", tags: ["loop::<host>-lessons"], limit: 10 }`.
|
|
78
|
+
3. Re-run over the same situation; confirm it surfaces in the read step and biases the run.
|
|
79
|
+
|
|
80
|
+
## Cold start (optional, capped)
|
|
81
|
+
|
|
82
|
+
Pre-seed a *handful* of real lessons from git history / prior review threads so run one
|
|
83
|
+
is not empty — [cold-start-seeding.md](../rules/cold-start-seeding.md). Do **not** seed
|
|
84
|
+
a loop whose lift you intend to measure; it erases the baseline.
|
|
85
|
+
|
|
86
|
+
## Prove it
|
|
87
|
+
|
|
88
|
+
Declare the immunity re-challenge at wiring time: after you promote a lesson to a rule,
|
|
89
|
+
its failure signature should stop recurring — [proving-improvement.md](../rules/proving-improvement.md).
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Recipe card — multi-step orchestrator
|
|
2
|
+
|
|
3
|
+
For a **pipeline that fails in classifiable ways** but does not post durable outputs at
|
|
4
|
+
a shared target and does not (necessarily) edit code: a triage → plan → execute
|
|
5
|
+
workflow, a release orchestrator, a batch processor. The plain two-tier lessons loop
|
|
6
|
+
fits it exactly. This is the card to start from when no other card matches.
|
|
7
|
+
|
|
8
|
+
Fill in: `<host>` (e.g. `deploy`), `<owner>/<repo>`. `N = 5`.
|
|
9
|
+
|
|
10
|
+
## Bucket
|
|
11
|
+
|
|
12
|
+
Tag `loop::<host>-lessons`, key `<host>-lessons::<slug>`. One bucket per host.
|
|
13
|
+
|
|
14
|
+
## Read step — start of every run
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
memory.list { scope: "repo::<owner>/<repo>", tags: ["loop::<host>-lessons"], limit: 5 }
|
|
18
|
+
memory.list { scope: "global", tags: ["loop::<host>-lessons"], limit: 5 }
|
|
19
|
+
# when the run names a stage/error, one targeted search:
|
|
20
|
+
memory.search { q: "<keywords>", scopes: ["repo::<owner>/*", "global"], limit: 5 }
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Match each **Applies when** against the current run; apply matching **Do this instead**
|
|
24
|
+
lines as considerations. `repo::` beats `global` on a collision.
|
|
25
|
+
|
|
26
|
+
## Write step — at the host's EXISTING failure points
|
|
27
|
+
|
|
28
|
+
Do not add a new reflection stage — hook the points the host already detects (a stuck
|
|
29
|
+
loop, a repeated failure, a gate that should have caught something, a near-miss, a
|
|
30
|
+
guess that paid off). Not on smooth successes.
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
memory.search { q: "<key words of the lesson>", scopes: ["repo::<owner>/<repo>", "global"], limit: 10 }
|
|
34
|
+
|
|
35
|
+
memory.write {
|
|
36
|
+
scope: "<global | repo::<owner>/<repo>>",
|
|
37
|
+
key: "<host>-lessons::<slug>",
|
|
38
|
+
value: "<markdown lesson body — no hidden blocks>",
|
|
39
|
+
tags: ["loop::<host>-lessons", "source::<trigger>"], # + "status::structural" when it is
|
|
40
|
+
trigger: "<stuck-loop | command-failure | gotcha | near-miss | assumption-wrong | paid-off>",
|
|
41
|
+
ttl_days: 90
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Same `scope` + `key` UPDATEs in place — the store increments `seen_count` for you and
|
|
46
|
+
re-passing `ttl_days` refreshes the expiry. Never hand-write a count into the body.
|
|
47
|
+
|
|
48
|
+
## Lesson body — copy this shape
|
|
49
|
+
|
|
50
|
+
```markdown
|
|
51
|
+
# <one-line takeaway — what to do, not what it is about>
|
|
52
|
+
|
|
53
|
+
**Applies when:** <concrete signal — stage name, task type, tool name, error shape>
|
|
54
|
+
|
|
55
|
+
**What happened:** <the concrete observable>
|
|
56
|
+
**Why:** <root cause, or "unknown">
|
|
57
|
+
**Do this instead:** <prescriptive, testable instruction>
|
|
58
|
+
**Promotion target:** <the host step this would harden if promoted, or "none">
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Promotion
|
|
62
|
+
|
|
63
|
+
When a lesson hits `seen_count >= 3` (read the column, not the body) or carries
|
|
64
|
+
`status::structural`, surface a one-line promotion suggestion — never act silently.
|
|
65
|
+
See [Promotion](../rules/self-improvement-loops.md#promotion-fast--slow).
|
|
66
|
+
|
|
67
|
+
## Fire-once check
|
|
68
|
+
|
|
69
|
+
1. Force the failure this loop targets.
|
|
70
|
+
2. `memory.list { scope: "<expected>", tags: ["loop::<host>-lessons"], limit: 10 }` — confirm the lesson.
|
|
71
|
+
3. Re-run; confirm it surfaces and biases the run.
|
|
72
|
+
|
|
73
|
+
## Cold start & proof
|
|
74
|
+
|
|
75
|
+
Optional capped seeding: [cold-start-seeding.md](../rules/cold-start-seeding.md).
|
|
76
|
+
Required proof — the immunity re-challenge: [proving-improvement.md](../rules/proving-improvement.md).
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Recipe card — reviewer / reconcile host
|
|
2
|
+
|
|
3
|
+
For a host that **produces durable outputs at a shared target it revisits**: a PR
|
|
4
|
+
reviewer posting comment threads it re-reviews on every push, a triager filing issues
|
|
5
|
+
it re-scans, a linter opening tickets. A plain read/write loop is not enough — stale
|
|
6
|
+
outputs pile up at the target and the signal about which outputs were *useful* is
|
|
7
|
+
thrown away. This card adds the **reconcile-on-re-run** flow on top of the lessons
|
|
8
|
+
loop, feeding a second **Signal** bucket.
|
|
9
|
+
|
|
10
|
+
Fill in: `<host>` (e.g. `reviewer`), `<signal>` (e.g. `comment-relevance`),
|
|
11
|
+
`<owner>/<repo>`. `N = 5`.
|
|
12
|
+
|
|
13
|
+
## Buckets (two)
|
|
14
|
+
|
|
15
|
+
- Lessons: tag `loop::<host>-lessons`, key `<host>-lessons::<slug>` — how to review better.
|
|
16
|
+
- Signal: tag `loop::<host>-<signal>`, key `<host>-<signal>::<pattern-fingerprint>` —
|
|
17
|
+
which of the host's OUTPUT PATTERNS get acted on vs declined at this target.
|
|
18
|
+
|
|
19
|
+
## Read step — start of run
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
# Own lessons (capped at N per scope):
|
|
23
|
+
memory.list { scope: "repo::<owner>/<repo>", tags: ["loop::<host>-lessons"], limit: 5 }
|
|
24
|
+
memory.list { scope: "global", tags: ["loop::<host>-lessons"], limit: 5 }
|
|
25
|
+
|
|
26
|
+
# Signal bucket — suppress reliably-declined patterns, reinforce reliably-resolved ones:
|
|
27
|
+
memory.list { scope: "repo::<owner>/<repo>", tags: ["loop::<host>-<signal>"], limit: 20 }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Reconcile step — at the re-run seam, gated on "a prior output exists at this target"
|
|
31
|
+
|
|
32
|
+
For each prior output the host itself authored, classify against the current target:
|
|
33
|
+
|
|
34
|
+
| Outcome | Meaning | Evidence |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| **resolved** | acted on — the flagged thing is handled | the region changed and the finding no longer reproduces, or the owner acknowledged it |
|
|
37
|
+
| **declined** | explicitly rejected | a "won't fix" / "by design" reply, a 👎 |
|
|
38
|
+
| **still-open** | still reproduces this run | the host re-produces the same output |
|
|
39
|
+
|
|
40
|
+
Then:
|
|
41
|
+
|
|
42
|
+
1. **Clean up** `resolved` + `declined` at the source (resolve the thread, close the
|
|
43
|
+
ticket). **Never** touch a `still-open` output. Only ever touch outputs the host
|
|
44
|
+
authored. Cleanup is idempotent and non-fatal (a cleanup error is logged, never
|
|
45
|
+
fails the run).
|
|
46
|
+
2. **Record the outcome** to the Signal bucket, keyed by a stable pattern fingerprint
|
|
47
|
+
(never a line number or a drifting id):
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
memory.write {
|
|
51
|
+
scope: "repo::<owner>/<repo>",
|
|
52
|
+
key: "<host>-<signal>::<pattern-fingerprint>",
|
|
53
|
+
value: "<markdown: the pattern, and resolved-vs-declined evidence — no hidden blocks>",
|
|
54
|
+
tags: ["loop::<host>-<signal>", "status::<resolved | declined>"],
|
|
55
|
+
trigger: "reconcile",
|
|
56
|
+
ttl_days: 90
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`still-open` writes nothing — there is no outcome yet. **Absence of confirmation is not
|
|
61
|
+
resolution**: if a re-run did not re-scan the region a prior output covers, it is
|
|
62
|
+
`still-open`, not `resolved`.
|
|
63
|
+
|
|
64
|
+
## Write step (lessons) — on friction, as usual
|
|
65
|
+
|
|
66
|
+
Same as any lessons loop — see [code-changing-agent.md](./code-changing-agent.md#write-step--on-failure--at-end-of-run)
|
|
67
|
+
for the write shape.
|
|
68
|
+
|
|
69
|
+
## Fire-once check
|
|
70
|
+
|
|
71
|
+
1. Produce an output at a test target, then resolve it at the source by hand.
|
|
72
|
+
2. Re-run; confirm the host classifies it `resolved`, cleans it up, and writes a
|
|
73
|
+
positive Signal record for its pattern.
|
|
74
|
+
3. Start a third run; confirm the Signal record surfaces and reinforces that pattern.
|
|
75
|
+
|
|
76
|
+
## Reference implementation
|
|
77
|
+
|
|
78
|
+
The `agent-skills` `pr-reviewer` agent: it resolves its own addressed PR threads on
|
|
79
|
+
each commit-triggered re-review and records the fixed/declined outcome to a
|
|
80
|
+
`reviewer-comment-relevance` bucket. Full flow:
|
|
81
|
+
[the reconcile-on-re-run flow](../rules/self-improvement-loops.md#the-reconcile-on-re-run-flow-resolve--record).
|
|
82
|
+
|
|
83
|
+
## Prove it
|
|
84
|
+
|
|
85
|
+
[proving-improvement.md](../rules/proving-improvement.md) — for this host the signal is
|
|
86
|
+
the **decline rate** of the host's outputs trending down over runs.
|
|
@@ -14,15 +14,16 @@
|
|
|
14
14
|
//
|
|
15
15
|
// Criteria (pure scoring in `../shared/candidates-pure.mjs`):
|
|
16
16
|
// - summed seen_count across a cluster's members >= --min-seen-count
|
|
17
|
-
// (default 3), OR any member
|
|
18
|
-
//
|
|
17
|
+
// (default 3), OR any member already declares a non-"active" status —
|
|
18
|
+
// a `status::<value>` tag, or a legacy `<!-- meta: ... status=... -->`
|
|
19
|
+
// comment for lessons written before tags were the convention
|
|
19
20
|
// - ranked by (summed seen_count × distinct scopes), descending
|
|
20
21
|
//
|
|
21
22
|
// What this deliberately does NOT do:
|
|
22
|
-
// - classify
|
|
23
|
-
//
|
|
24
|
-
// printed verbatim, never interpreted — "parses into
|
|
25
|
-
// trigger" is the human step the compile pipeline protects.
|
|
23
|
+
// - classify an applicability signal into a glob/command/error-shape. The raw
|
|
24
|
+
// string (a lesson's `**Applies when:**` line, or a legacy meta comment's
|
|
25
|
+
// `trigger-context`) is printed verbatim, never interpreted — "parses into
|
|
26
|
+
// a detectable trigger" is the human step the compile pipeline protects.
|
|
26
27
|
// - check `compiled_to`. No such field exists yet (no schema, no server
|
|
27
28
|
// support — see the kickoff's Open Questions), so a candidate already
|
|
28
29
|
// compiled into an obligations-map.mjs entry can still surface here. A
|
|
@@ -150,7 +151,13 @@ function buildCandidates(entries, { threshold, minSeenCount }) {
|
|
|
150
151
|
...cl,
|
|
151
152
|
members: cl.members.map((m) => {
|
|
152
153
|
const raw = byAddress.get(`${m.scope}::${m.key}`);
|
|
153
|
-
return {
|
|
154
|
+
return {
|
|
155
|
+
scope: m.scope,
|
|
156
|
+
key: m.key,
|
|
157
|
+
seenCount: seenCountOf(raw),
|
|
158
|
+
value: raw?.value ?? '',
|
|
159
|
+
tags: Array.isArray(raw?.tags) ? raw.tags : [],
|
|
160
|
+
};
|
|
154
161
|
}),
|
|
155
162
|
}));
|
|
156
163
|
return rankCandidates(clusters, { minSeenCount, resolveClass: resolveRecurrenceClass });
|
|
@@ -221,7 +228,7 @@ async function candidates(args) {
|
|
|
221
228
|
heading('LoreKit invariants candidates');
|
|
222
229
|
log(` project: ${c.dim(root)}`);
|
|
223
230
|
log(` scopes: ${scopes.join(' → ')}`);
|
|
224
|
-
log(` ${c.dim(`criteria: summed seen_count >= ${minSeenCount}, or a member's
|
|
231
|
+
log(` ${c.dim(`criteria: summed seen_count >= ${minSeenCount}, or a member's status is non-"active"`)}`);
|
|
225
232
|
|
|
226
233
|
if (offlineSection.available && offlineSection.popCapped) {
|
|
227
234
|
log(` ${c.yellow('!')} population cap (${POP_CAP}) reached for Offline — results are partial. Narrow with --key-prefix, --since, or --max.`);
|
|
@@ -281,8 +288,8 @@ function renderSection(header, section) {
|
|
|
281
288
|
}
|
|
282
289
|
for (const m of cand.members) {
|
|
283
290
|
const fields = [`seen_count=${m.seenCount}`];
|
|
284
|
-
if (m.
|
|
285
|
-
if (m.
|
|
291
|
+
if (m.status) fields.push(`status=${m.status}`);
|
|
292
|
+
if (m.appliesWhen) fields.push(`applies-when=${JSON.stringify(m.appliesWhen)}`);
|
|
286
293
|
log(` ${c.cyan('-')} ${m.scope}::${m.key} ${c.dim(`(${fields.join(', ')})`)}`);
|
|
287
294
|
}
|
|
288
295
|
}
|
package/src/commands/lint.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// `lorekit lint` — flag low-quality lessons across the applicable scopes and
|
|
2
2
|
// both stores. Each finding names the rule it violated (empty/whitespace value,
|
|
3
3
|
// suspiciously short value, untrimmed value, empty key, volatile key, malformed
|
|
4
|
-
// scope
|
|
5
|
-
// `
|
|
4
|
+
// scope, unkinded state record, hidden metadata — an HTML comment / front-matter /
|
|
5
|
+
// `key=value` header in a body that should be pure markdown). The rules are pure
|
|
6
|
+
// predicates in `lessons-view.mjs` (`LINT_RULES` / `lintEntry`), each
|
|
7
|
+
// independently unit-tested.
|
|
6
8
|
//
|
|
7
9
|
// Exit convention: `lint` exits NON-ZERO (1) when any finding exists, so it is
|
|
8
10
|
// usable as a CI gate (`lorekit lint || fail`); a clean run — or a run where the
|