@lorekit/cli 1.69.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/package.json +1 -1
- package/skill/lorekit-groom/rules/grooming-pass.md +34 -0
- package/skill/lorekit-setup/SKILL.md +141 -104
- package/skill/lorekit-setup/rules/cold-start-seeding.md +102 -0
- 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 +52 -4
- 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/lint.mjs +4 -2
- package/src/shared/lessons-view.mjs +71 -0
|
@@ -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.
|
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
|
|
@@ -253,6 +253,36 @@ export function diffGroups(offline = {}, remote = {}) {
|
|
|
253
253
|
// too terse to carry a durable observation (e.g. "yes", "fixed", "todo").
|
|
254
254
|
export const MIN_VALUE_LEN = 12;
|
|
255
255
|
|
|
256
|
+
// Blank the INTERIOR of fenced code blocks (``` or ~~~ fences) so example content
|
|
257
|
+
// — a lesson documenting an `<!-- MARKER -->` or pasting a ```yaml front-matter
|
|
258
|
+
// sample — is never mistaken for hidden metadata. Such content renders as VISIBLE
|
|
259
|
+
// fenced text, the opposite of the digest-hidden block `hidden-metadata` targets,
|
|
260
|
+
// and `lint` is a CI gate, so a false positive there fails a legitimate lesson.
|
|
261
|
+
// Line positions are preserved (interiors and fence lines become empty) so the
|
|
262
|
+
// heading-anchored checks still see the real document structure. A closing fence
|
|
263
|
+
// is ≥3 of the SAME character as the opener with no trailing info string
|
|
264
|
+
// (CommonMark); an opener may carry an info string (```yaml). Pure.
|
|
265
|
+
function stripFencedCode(value) {
|
|
266
|
+
let fence = null; // the active fence character (` or ~), or null when outside a block
|
|
267
|
+
return String(value)
|
|
268
|
+
.split('\n')
|
|
269
|
+
.map((line) => {
|
|
270
|
+
const m = line.match(/^\s*(`{3,}|~{3,})/);
|
|
271
|
+
if (fence === null) {
|
|
272
|
+
if (m) {
|
|
273
|
+
fence = m[1][0];
|
|
274
|
+
return '';
|
|
275
|
+
}
|
|
276
|
+
return line;
|
|
277
|
+
}
|
|
278
|
+
if (m && m[1][0] === fence && line.trim().replace(/[`~\s]/g, '') === '') {
|
|
279
|
+
fence = null;
|
|
280
|
+
}
|
|
281
|
+
return '';
|
|
282
|
+
})
|
|
283
|
+
.join('\n');
|
|
284
|
+
}
|
|
285
|
+
|
|
256
286
|
// The lint rule set: each a pure predicate over a normalized entry returning a
|
|
257
287
|
// short reason string when it FIRES, or null when the entry is clean. Kept as
|
|
258
288
|
// discrete named functions so each rule is independently unit-testable and the
|
|
@@ -335,6 +365,47 @@ export const LINT_RULES = {
|
|
|
335
365
|
if (parsed === null || typeof parsed !== 'object') return null; // a bare scalar — short-value's to catch when short, otherwise unjudged.
|
|
336
366
|
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
367
|
},
|
|
368
|
+
// A lesson body is markdown for humans — no HTML comment, no front-matter, no
|
|
369
|
+
// `key=value` header. The `<!-- meta: seen_count=… status=… trigger-context=… -->`
|
|
370
|
+
// block is a REPUDIATED legacy convention this repo's lorekit-setup skill once
|
|
371
|
+
// prescribed (still parsed as a read-fallback in `candidates-pure.mjs` /
|
|
372
|
+
// `commands/invariants.mjs`, and skipped by the digest preview in `core/lessons.mjs`).
|
|
373
|
+
// It is wrong on the merits: an HTML comment renders to nothing, so a human sees a
|
|
374
|
+
// lesson starting mid-sentence, while a baked-in `seen_count`/`status`/`trigger`
|
|
375
|
+
// silently disagrees with the store's own column/tag/field. Conservative like the
|
|
376
|
+
// other rules — three concrete shapes only, never a fuzzy "looks like metadata":
|
|
377
|
+
// 1. any HTML comment (`<!--`), the strongest and most common offender;
|
|
378
|
+
// 2. a body that OPENS with a YAML front-matter block (`---` as the first line);
|
|
379
|
+
// 3. a machine-metadata header (`meta:`, `seen_count`, `status=`, `expires`,
|
|
380
|
+
// `ttl(_days)`, `trigger-context`) appearing BEFORE the first `#` title, so a
|
|
381
|
+
// legitimate prose line mid-body never trips it.
|
|
382
|
+
// All three run against a fence-stripped copy (`stripFencedCode`) so a lesson that
|
|
383
|
+
// DOCUMENTS one of these shapes inside a code block is never flagged.
|
|
384
|
+
'hidden-metadata': (e) => {
|
|
385
|
+
const raw = String(e.value ?? '');
|
|
386
|
+
if (!raw.trim()) return null; // an empty value is `empty-value`'s to report.
|
|
387
|
+
const v = stripFencedCode(raw);
|
|
388
|
+
if (!v.trim()) return null; // nothing outside code fences — no prose metadata to flag.
|
|
389
|
+
if (v.includes('<!--')) {
|
|
390
|
+
return "value contains an HTML comment — lesson bodies are pure markdown; move seen_count → the column, status → a status:: tag, trigger → the trigger field";
|
|
391
|
+
}
|
|
392
|
+
const lines = v.split('\n');
|
|
393
|
+
const firstNonEmpty = lines.find((l) => l.trim() !== '');
|
|
394
|
+
if (firstNonEmpty !== undefined && firstNonEmpty.trim() === '---') {
|
|
395
|
+
return 'value opens with a front-matter block — lesson bodies carry no front-matter; every stored fact belongs in its own write field';
|
|
396
|
+
}
|
|
397
|
+
const headingIdx = lines.findIndex((l) => /^\s*#/.test(l));
|
|
398
|
+
if (headingIdx > 0) {
|
|
399
|
+
const metaHeader = lines
|
|
400
|
+
.slice(0, headingIdx)
|
|
401
|
+
.filter((l) => l.trim() !== '')
|
|
402
|
+
.find((l) => /^\s*(meta\b|seen_count\b|status\s*=|expires\b|ttl(?:_days)?\b|trigger[-_]context\b)/i.test(l));
|
|
403
|
+
if (metaHeader) {
|
|
404
|
+
return `value has a machine-metadata header before the title ('${metaHeader.trim().slice(0, 40)}') — move it to the store's own fields`;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
},
|
|
338
409
|
};
|
|
339
410
|
|
|
340
411
|
// Run every lint rule against one normalized entry, returning the findings it
|