@olegkoval/agent-skills 1.41.0 → 1.41.2
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 +1 -1
- package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/selftest.mjs +50 -37
- package/docs/agent-context-store.md +46 -0
- package/docs/assets/context-store-card.html +24 -0
- package/docs/assets/context-store-card.png +0 -0
- package/package.json +1 -1
- package/plugins/olko-apple-kit/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-creative/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-garmin-kit/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-git-tools/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-github-pr/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-github-pr/skills/lekker-review/scripts/selftest.mjs +50 -37
- package/plugins/olko-obsidian/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-product/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-reflection/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-release/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-skill-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-web-ops/.claude-plugin/plugin.json +1 -1
package/README.md
CHANGED
|
@@ -342,7 +342,7 @@ When adding or changing a skill:
|
|
|
342
342
|
|
|
343
343
|
### Shared context store
|
|
344
344
|
|
|
345
|
-

|
|
346
346
|
|
|
347
347
|
`retro-analysis` and `shared-knowledge-artifact` share one private GitHub repository,
|
|
348
348
|
resolved through the `context-repo` skill the first time either one runs. It finds a
|
|
@@ -41,9 +41,17 @@ function liftConst(name) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const preamble = [
|
|
44
|
-
|
|
44
|
+
// HARD_RULES is absent from the current workflow.js: the allowlist went away
|
|
45
|
+
// with the exemption it gated. It is still lifted-with-a-fallback rather than
|
|
46
|
+
// dropped, because this suite must stay runnable against an OLDER workflow.js
|
|
47
|
+
// (see the target argument above) to prove it discriminates. Without the
|
|
48
|
+
// fallback the old file throws ReferenceError instead of reporting FAIL, and a
|
|
49
|
+
// suite that crashes on the pre-fix input has proved nothing.
|
|
50
|
+
(() => { try { return liftConst('HARD_RULES') } catch { return 'const HARD_RULES = []' } })(),
|
|
45
51
|
(() => { try { return liftConst('SAME_ISSUE_LINE_WINDOW') } catch { return 'const SAME_ISSUE_LINE_WINDOW = 30' } })(),
|
|
46
52
|
"const SEVERITY_RANK = { observation: 0, idiomatic: 1, important: 2, critical: 3 }",
|
|
53
|
+
// hardRuleCorroborated is likewise gone from the current file and kept in this
|
|
54
|
+
// list for the same reason: an older workflow.js calls it from isHardRule.
|
|
47
55
|
...['titleTokens', 'sameIssue', 'nearbyLines', 'spanWithinWindow', 'hardRuleCorroborated',
|
|
48
56
|
'isHardRule', 'longest', 'mergeFindings', 'dedup', 'shouldVerify'].map(n => {
|
|
49
57
|
try { return lift(n) } catch { return `function ${n}() { throw new Error('${n} absent from this workflow.js') }` }
|
|
@@ -98,46 +106,51 @@ check('different files never merge', dedup([
|
|
|
98
106
|
{ file: 'b.ts', line: 7, severity: 'critical', title: 'Off-by-one loop skips the first line', badCode: '', description: '' },
|
|
99
107
|
]).length, 2)
|
|
100
108
|
|
|
101
|
-
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
// This section used to assert that a `rule` tag had to be corroborated against a
|
|
110
|
+
// hardcoded HARD_RULES list before it could SKIP verification. That guard existed
|
|
111
|
+
// because a fabricated tag could buy exemption. It is gone because the exemption
|
|
112
|
+
// is gone: shouldVerify now returns true for every Critical and Important finding,
|
|
113
|
+
// and a hard rule takes the verifier's rule-specific anchor and applicability path
|
|
114
|
+
// instead of its runtime challenges, validated against the configured canonical
|
|
115
|
+
// rules file. A hardcoded list also could not recognise a custom house rule, which
|
|
116
|
+
// the current design supports on purpose.
|
|
117
|
+
//
|
|
118
|
+
// So the tag no longer buys a free pass; it selects a verification path. The
|
|
119
|
+
// predicate is correspondingly narrow: does this finding claim a rule at all.
|
|
120
|
+
console.log('\nhard rules: the tag selects a verification path, it does not skip one')
|
|
121
|
+
// Regression, still worth holding: a finding with no rule tag must never be
|
|
122
|
+
// treated as one. That is what routes it to the runtime challenges.
|
|
123
|
+
check('no rule tag is not a hard rule',
|
|
124
|
+
isHardRule({ file: 'a.ts', line: 1, title: 't', badCode: 'x as Foo', description: '' }), false)
|
|
125
|
+
check('an empty rule tag is not a hard rule',
|
|
126
|
+
isHardRule({ rule: '', file: 'a.ts', line: 1, title: 't', badCode: '', description: '' }), false)
|
|
127
|
+
check('a whitespace-only rule tag is not a hard rule',
|
|
128
|
+
isHardRule({ rule: ' ', file: 'a.ts', line: 1, title: 't', badCode: '', description: '' }), false)
|
|
129
|
+
check('a non-string rule tag is not a hard rule',
|
|
130
|
+
isHardRule({ rule: 1, file: 'a.ts', line: 1, title: 't', badCode: '', description: '' }), false)
|
|
115
131
|
|
|
116
|
-
console.log('\nhard rules:
|
|
117
|
-
|
|
118
|
-
check(
|
|
119
|
-
|
|
120
|
-
check('GQL-1 with a nodes query',isHardRule({ rule: 'GQL-1', file: 'q.graphql', line: 1, title: 'no pageInfo', badCode: 'products { nodes { id } }', description: '' }), true)
|
|
121
|
-
check('PR-1 anchored on the PR title', isHardRule({ rule: 'PR-1', file: 'PR title', line: 1, title: 'missing prefix', badCode: '', description: '' }), true)
|
|
122
|
-
// A cast to a lowercase built-in is as much a TS-1 violation as a cast to a
|
|
123
|
-
// named type. Missing it sent a genuine hard rule to a verifier that cannot
|
|
124
|
-
// answer a policy claim, where it could be dropped.
|
|
125
|
-
for (const cast of ['x as string', 'x as number', 'x as unknown as Foo', 'x as const', 'x as boolean']) {
|
|
126
|
-
check(`TS-1 corroborated by \`${cast}\``,
|
|
127
|
-
isHardRule({ rule: 'TS-1', file: 'a.ts', line: 1, title: 'cast', badCode: cast, description: '' }), true)
|
|
132
|
+
console.log('\nhard rules: built-in and custom tags both route to rule-specific verification')
|
|
133
|
+
for (const rule of ['TS-1', 'TS-2', 'GQL-1', 'PR-1']) {
|
|
134
|
+
check(`built-in ${rule} is a hard rule`,
|
|
135
|
+
isHardRule({ rule, file: 'a.ts', line: 1, title: 't', badCode: '', description: '' }), true)
|
|
128
136
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
137
|
+
// A house rule defined in the operator's canonical rules file cannot be known to
|
|
138
|
+
// this workflow. Rejecting it here is what a hardcoded list did, and it silently
|
|
139
|
+
// disabled every custom rule a deployment configured.
|
|
140
|
+
check('a custom house rule is a hard rule',
|
|
141
|
+
isHardRule({ rule: 'SEC-1', file: 'a.ts', line: 1, title: 't', badCode: '', description: '' }), true)
|
|
133
142
|
|
|
134
|
-
console.log('\nverification scope
|
|
143
|
+
console.log('\nverification scope: depth controls breadth, never the trust bar')
|
|
135
144
|
const crit = { severity: 'critical' }, imp = { severity: 'important' }, obs = { severity: 'observation' }
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
check('
|
|
139
|
-
check('
|
|
140
|
-
|
|
145
|
+
// shouldVerify takes one argument now. Depth used to be able to switch verification
|
|
146
|
+
// off entirely, which meant a scan-depth review shipped unverified Criticals.
|
|
147
|
+
check('shouldVerify takes the finding alone', shouldVerify.length, 1)
|
|
148
|
+
check('critical and important are verified, observation is not',
|
|
149
|
+
[crit, imp, obs].map(f => shouldVerify(f)), [true, true, false])
|
|
150
|
+
check('a hard rule is NOT exempt from verification',
|
|
151
|
+
shouldVerify({ severity: 'critical', rule: 'TS-2', file: 'x.js', badCode: '', description: '', title: '' }), true)
|
|
152
|
+
check('a custom hard rule is NOT exempt either',
|
|
153
|
+
shouldVerify({ severity: 'critical', rule: 'SEC-1', file: 'x.ts', badCode: '', description: '', title: '' }), true)
|
|
141
154
|
|
|
142
155
|
// Per-host model routing is optional: a deployment may pin models per host via
|
|
143
156
|
// a MODEL_TABLE, or leave every agent() call to name its own model. Test it
|
|
@@ -229,6 +229,52 @@ into it:
|
|
|
229
229
|
where they do not conflict with this contract. It cannot override lease acquisition,
|
|
230
230
|
validation, append-only writes, secret handling, or the private-repository requirement.
|
|
231
231
|
|
|
232
|
+
## Reading the store
|
|
233
|
+
|
|
234
|
+
Everything above is the write side, and it is enforced: a lease, a validator, and CI
|
|
235
|
+
that runs the validator again on push. The read side had none of that. For a while the
|
|
236
|
+
only instruction to read the ledger was one sentence in `AGENTS.md`, which is a rule an
|
|
237
|
+
agent has to remember rather than a mechanism. It did not hold. Two notes written on
|
|
238
|
+
2026-08-25 described failures that both recurred by 2026-08-31: one about stating the
|
|
239
|
+
denominator behind a percentage, one about cache reads dominating a long-session bill.
|
|
240
|
+
Recording a lesson and delivering it are different jobs.
|
|
241
|
+
|
|
242
|
+
The store therefore ships a reader, `recipes/tools/ledger-index`, built on the shape
|
|
243
|
+
that already works for per-user memory: a cheap index is always in context, and the
|
|
244
|
+
body is fetched on demand. The index is titles only, roughly 1.9k tokens for 84 notes,
|
|
245
|
+
grouped by scope with traps first. The ledger itself is around 50KB and does not belong
|
|
246
|
+
in every session.
|
|
247
|
+
|
|
248
|
+
```text
|
|
249
|
+
ledger-index the index, grouped by scope
|
|
250
|
+
ledger-index --show n41 one note in full, body and Why
|
|
251
|
+
ledger-index --kind trap --scope shell
|
|
252
|
+
ledger-index --count
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
It resolves the ledger through the same pointer `context-repo` writes, so a machine
|
|
256
|
+
that has resolved the store once needs no further configuration. An explicitly named
|
|
257
|
+
`--ledger` or `LEDGER_PATH` that does not exist is an error rather than a fallback:
|
|
258
|
+
silently reading a different ledger than the one you asked for is the failure this
|
|
259
|
+
whole document exists to prevent.
|
|
260
|
+
|
|
261
|
+
Hosts wire it in their own way and neither owns the format. A Claude `SessionStart`
|
|
262
|
+
hook injects the index once per session; a Codex prompt shells out to the same binary.
|
|
263
|
+
The reference hook is `recipes/hooks/ledger-index.sh`, which prints nothing and exits 0
|
|
264
|
+
when the store is absent, so it can never fail a session.
|
|
265
|
+
|
|
266
|
+
The index states its own horizon. It reads from disk and never fetches, because a
|
|
267
|
+
session start is the wrong place for a network call, so the header carries the commit,
|
|
268
|
+
its age in days, and a plain statement that a newer commit may exist upstream. Past
|
|
269
|
+
three days it prints the `pull --rebase` command for that clone. Age of `HEAD` conflates
|
|
270
|
+
a quiet repository with a checkout that is behind, and only a fetch separates them, so
|
|
271
|
+
the limit is stated rather than implied.
|
|
272
|
+
|
|
273
|
+
One consequence is worth making explicit. The fixed clone path is not a convention, it
|
|
274
|
+
is the point: a second working copy of the store is the copy nothing keeps current, and
|
|
275
|
+
it will be the one an index or an `AGENTS.md` symlink ends up reading. Resolve through
|
|
276
|
+
the pointer and keep one checkout.
|
|
277
|
+
|
|
232
278
|
## What is never committed
|
|
233
279
|
|
|
234
280
|
Raw session content, tokens, private prompts, and customer data never go into the
|
|
@@ -367,6 +367,30 @@
|
|
|
367
367
|
<p class="proof-line">A push that appears to succeed is <strong>not proof</strong>. <strong>CREATED</strong> is claimed only after a <strong>fresh read-back agrees</strong>.</p>
|
|
368
368
|
</section>
|
|
369
369
|
|
|
370
|
+
<section class="state-strip" aria-labelledby="read-title">
|
|
371
|
+
<p class="strip-label" id="read-title">AND ONE WAY IT IS READ</p>
|
|
372
|
+
<div class="state-row">
|
|
373
|
+
<div class="state-item">
|
|
374
|
+
<p class="state-name">LEDGER-INDEX</p>
|
|
375
|
+
<p class="state-detail">titles only, grouped by scope, traps first</p>
|
|
376
|
+
</div>
|
|
377
|
+
<div class="state-item">
|
|
378
|
+
<p class="state-name">--SHOW n41</p>
|
|
379
|
+
<p class="state-detail">one note in full, body and Why</p>
|
|
380
|
+
</div>
|
|
381
|
+
<div class="state-item">
|
|
382
|
+
<p class="state-name">--KIND --SCOPE</p>
|
|
383
|
+
<p class="state-detail">narrow it to the task in front of you</p>
|
|
384
|
+
</div>
|
|
385
|
+
<div class="state-item">
|
|
386
|
+
<p class="state-name">DATED HEADER</p>
|
|
387
|
+
<p class="state-detail">read from disk, no fetch, age stated</p>
|
|
388
|
+
</div>
|
|
389
|
+
<p class="state-copy">A cheap index is always in context; the body is fetched on demand. The ledger never enters a session whole.</p>
|
|
390
|
+
</div>
|
|
391
|
+
<p class="proof-line">A rule that is <strong>recorded but not delivered</strong> is a diary. The index is a <strong>table of contents</strong>, never the rule itself.</p>
|
|
392
|
+
</section>
|
|
393
|
+
|
|
370
394
|
<footer class="closing-band">
|
|
371
395
|
<p class="closing-line">One consented store gives every skill durable context with explicit verification.</p>
|
|
372
396
|
<div class="metadata-line">
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -41,9 +41,17 @@ function liftConst(name) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const preamble = [
|
|
44
|
-
|
|
44
|
+
// HARD_RULES is absent from the current workflow.js: the allowlist went away
|
|
45
|
+
// with the exemption it gated. It is still lifted-with-a-fallback rather than
|
|
46
|
+
// dropped, because this suite must stay runnable against an OLDER workflow.js
|
|
47
|
+
// (see the target argument above) to prove it discriminates. Without the
|
|
48
|
+
// fallback the old file throws ReferenceError instead of reporting FAIL, and a
|
|
49
|
+
// suite that crashes on the pre-fix input has proved nothing.
|
|
50
|
+
(() => { try { return liftConst('HARD_RULES') } catch { return 'const HARD_RULES = []' } })(),
|
|
45
51
|
(() => { try { return liftConst('SAME_ISSUE_LINE_WINDOW') } catch { return 'const SAME_ISSUE_LINE_WINDOW = 30' } })(),
|
|
46
52
|
"const SEVERITY_RANK = { observation: 0, idiomatic: 1, important: 2, critical: 3 }",
|
|
53
|
+
// hardRuleCorroborated is likewise gone from the current file and kept in this
|
|
54
|
+
// list for the same reason: an older workflow.js calls it from isHardRule.
|
|
47
55
|
...['titleTokens', 'sameIssue', 'nearbyLines', 'spanWithinWindow', 'hardRuleCorroborated',
|
|
48
56
|
'isHardRule', 'longest', 'mergeFindings', 'dedup', 'shouldVerify'].map(n => {
|
|
49
57
|
try { return lift(n) } catch { return `function ${n}() { throw new Error('${n} absent from this workflow.js') }` }
|
|
@@ -98,46 +106,51 @@ check('different files never merge', dedup([
|
|
|
98
106
|
{ file: 'b.ts', line: 7, severity: 'critical', title: 'Off-by-one loop skips the first line', badCode: '', description: '' },
|
|
99
107
|
]).length, 2)
|
|
100
108
|
|
|
101
|
-
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
// This section used to assert that a `rule` tag had to be corroborated against a
|
|
110
|
+
// hardcoded HARD_RULES list before it could SKIP verification. That guard existed
|
|
111
|
+
// because a fabricated tag could buy exemption. It is gone because the exemption
|
|
112
|
+
// is gone: shouldVerify now returns true for every Critical and Important finding,
|
|
113
|
+
// and a hard rule takes the verifier's rule-specific anchor and applicability path
|
|
114
|
+
// instead of its runtime challenges, validated against the configured canonical
|
|
115
|
+
// rules file. A hardcoded list also could not recognise a custom house rule, which
|
|
116
|
+
// the current design supports on purpose.
|
|
117
|
+
//
|
|
118
|
+
// So the tag no longer buys a free pass; it selects a verification path. The
|
|
119
|
+
// predicate is correspondingly narrow: does this finding claim a rule at all.
|
|
120
|
+
console.log('\nhard rules: the tag selects a verification path, it does not skip one')
|
|
121
|
+
// Regression, still worth holding: a finding with no rule tag must never be
|
|
122
|
+
// treated as one. That is what routes it to the runtime challenges.
|
|
123
|
+
check('no rule tag is not a hard rule',
|
|
124
|
+
isHardRule({ file: 'a.ts', line: 1, title: 't', badCode: 'x as Foo', description: '' }), false)
|
|
125
|
+
check('an empty rule tag is not a hard rule',
|
|
126
|
+
isHardRule({ rule: '', file: 'a.ts', line: 1, title: 't', badCode: '', description: '' }), false)
|
|
127
|
+
check('a whitespace-only rule tag is not a hard rule',
|
|
128
|
+
isHardRule({ rule: ' ', file: 'a.ts', line: 1, title: 't', badCode: '', description: '' }), false)
|
|
129
|
+
check('a non-string rule tag is not a hard rule',
|
|
130
|
+
isHardRule({ rule: 1, file: 'a.ts', line: 1, title: 't', badCode: '', description: '' }), false)
|
|
115
131
|
|
|
116
|
-
console.log('\nhard rules:
|
|
117
|
-
|
|
118
|
-
check(
|
|
119
|
-
|
|
120
|
-
check('GQL-1 with a nodes query',isHardRule({ rule: 'GQL-1', file: 'q.graphql', line: 1, title: 'no pageInfo', badCode: 'products { nodes { id } }', description: '' }), true)
|
|
121
|
-
check('PR-1 anchored on the PR title', isHardRule({ rule: 'PR-1', file: 'PR title', line: 1, title: 'missing prefix', badCode: '', description: '' }), true)
|
|
122
|
-
// A cast to a lowercase built-in is as much a TS-1 violation as a cast to a
|
|
123
|
-
// named type. Missing it sent a genuine hard rule to a verifier that cannot
|
|
124
|
-
// answer a policy claim, where it could be dropped.
|
|
125
|
-
for (const cast of ['x as string', 'x as number', 'x as unknown as Foo', 'x as const', 'x as boolean']) {
|
|
126
|
-
check(`TS-1 corroborated by \`${cast}\``,
|
|
127
|
-
isHardRule({ rule: 'TS-1', file: 'a.ts', line: 1, title: 'cast', badCode: cast, description: '' }), true)
|
|
132
|
+
console.log('\nhard rules: built-in and custom tags both route to rule-specific verification')
|
|
133
|
+
for (const rule of ['TS-1', 'TS-2', 'GQL-1', 'PR-1']) {
|
|
134
|
+
check(`built-in ${rule} is a hard rule`,
|
|
135
|
+
isHardRule({ rule, file: 'a.ts', line: 1, title: 't', badCode: '', description: '' }), true)
|
|
128
136
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
137
|
+
// A house rule defined in the operator's canonical rules file cannot be known to
|
|
138
|
+
// this workflow. Rejecting it here is what a hardcoded list did, and it silently
|
|
139
|
+
// disabled every custom rule a deployment configured.
|
|
140
|
+
check('a custom house rule is a hard rule',
|
|
141
|
+
isHardRule({ rule: 'SEC-1', file: 'a.ts', line: 1, title: 't', badCode: '', description: '' }), true)
|
|
133
142
|
|
|
134
|
-
console.log('\nverification scope
|
|
143
|
+
console.log('\nverification scope: depth controls breadth, never the trust bar')
|
|
135
144
|
const crit = { severity: 'critical' }, imp = { severity: 'important' }, obs = { severity: 'observation' }
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
check('
|
|
139
|
-
check('
|
|
140
|
-
|
|
145
|
+
// shouldVerify takes one argument now. Depth used to be able to switch verification
|
|
146
|
+
// off entirely, which meant a scan-depth review shipped unverified Criticals.
|
|
147
|
+
check('shouldVerify takes the finding alone', shouldVerify.length, 1)
|
|
148
|
+
check('critical and important are verified, observation is not',
|
|
149
|
+
[crit, imp, obs].map(f => shouldVerify(f)), [true, true, false])
|
|
150
|
+
check('a hard rule is NOT exempt from verification',
|
|
151
|
+
shouldVerify({ severity: 'critical', rule: 'TS-2', file: 'x.js', badCode: '', description: '', title: '' }), true)
|
|
152
|
+
check('a custom hard rule is NOT exempt either',
|
|
153
|
+
shouldVerify({ severity: 'critical', rule: 'SEC-1', file: 'x.ts', badCode: '', description: '', title: '' }), true)
|
|
141
154
|
|
|
142
155
|
// Per-host model routing is optional: a deployment may pin models per host via
|
|
143
156
|
// a MODEL_TABLE, or leave every agent() call to name its own model. Test it
|