@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 CHANGED
@@ -342,7 +342,7 @@ When adding or changing a skill:
342
342
 
343
343
  ### Shared context store
344
344
 
345
- ![How the agent context store works: the five resolution steps, the three skills that use the store, and the four states every run ends in](docs/assets/context-store-card.png)
345
+ ![How the agent context store works: the five resolution steps, the three skills that use the store, the four states every run ends in, and how the store is read](docs/assets/context-store-card.png)
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
- liftConst('HARD_RULES'),
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
- console.log('\nhard rules: a tag must be corroborated to skip verification')
102
- // Regression: any agent could bypass the verifier by writing rule: "TS-1".
103
- // Observed live - a test-coverage finding and a comment-policy finding both did.
104
- check('TS-1 on a comment-policy finding is NOT exempt',
105
- isHardRule({ rule: 'TS-1', file: 'a.ts', line: 6, title: 'Comment restates the function', badCode: '/** Sum a cart. */', description: 'a comment earns its place' }), false)
106
- check('TS-1 on a test-coverage finding is NOT exempt',
107
- isHardRule({ rule: 'TS-1', file: 'a.ts', line: 8, title: 'No test coverage', badCode: 'for (let i = 1;', description: 'zero test files added' }), false)
108
- check('an unknown rule string is NOT exempt',
109
- isHardRule({ rule: 'MADE-UP', file: 'a.ts', line: 1, title: 't', badCode: 'x as Foo', description: '' }), false)
110
- // TS-1 is judged on quoted code only: prose is full of `as` and `any`.
111
- check('the word "any" in PROSE alone is NOT exempt',
112
- isHardRule({ rule: 'TS-1', file: 'a.ts', line: 1, title: 'fails on any cart with items', badCode: 'total += lines[i].price', description: 'any agent could trip this' }), false)
113
- check('the phrase "such as" in prose alone is NOT exempt',
114
- isHardRule({ rule: 'TS-1', file: 'a.ts', line: 1, title: 'issue', badCode: 'const n = 1', description: 'a primitive such as String is used' }), false)
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: genuine violations must STILL be exempt')
117
- check('TS-1 with a real cast', isHardRule({ rule: 'TS-1', file: 'a.ts', line: 1, title: 'cast', badCode: 'const x = y as Foo;', description: '' }), true)
118
- check('TS-1 with a real any', isHardRule({ rule: 'TS-1', file: 'a.ts', line: 1, title: 'any', badCode: 'function f(x: any) {}', description: '' }), true)
119
- check('TS-2 with a .js path', isHardRule({ rule: 'TS-2', file: 'web/thing.js', line: 1, title: 'js added', badCode: '', description: '' }), true)
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
- check('TS-1 corroborated by an any annotation',
130
- isHardRule({ rule: 'TS-1', file: 'a.ts', line: 1, title: 'any', badCode: 'function f(x: any) {}', description: '' }), true)
131
- check('TS-1 corroborated by an any[] ',
132
- isHardRule({ rule: 'TS-1', file: 'a.ts', line: 1, title: 'any', badCode: 'const xs: any[] = []', description: '' }), true)
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 by depth')
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
- check('scan verifies nothing', [crit, imp, obs].map(f => shouldVerify(f, 'scan')), [false, false, false])
137
- check('medium verifies criticals only', [crit, imp, obs].map(f => shouldVerify(f, 'medium')), [true, false, false])
138
- check('deep verifies crit + important', [crit, imp, obs].map(f => shouldVerify(f, 'deep')), [true, true, false])
139
- check('a corroborated hard rule is never verified',
140
- shouldVerify({ severity: 'critical', rule: 'TS-2', file: 'x.js', badCode: '', description: '', title: '' }, 'deep'), false)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olegkoval/agent-skills",
3
- "version": "1.41.0",
3
+ "version": "1.41.2",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "olko-apple-kit",
3
3
  "description": "Build and ship Apple platform apps: macOS menubar apps, App Store submissions.",
4
- "version": "1.41.0",
4
+ "version": "1.41.2",
5
5
  "author": {
6
6
  "name": "Oleg Koval"
7
7
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "olko-creative",
3
3
  "description": "Creative and personal projects: photo galleries, music players, listings, wiki editing.",
4
- "version": "1.41.0",
4
+ "version": "1.41.2",
5
5
  "author": {
6
6
  "name": "Oleg Koval"
7
7
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "olko-garmin-kit",
3
3
  "description": "Build, test and publish Garmin Connect IQ watch faces.",
4
- "version": "1.41.0",
4
+ "version": "1.41.2",
5
5
  "author": {
6
6
  "name": "Oleg Koval"
7
7
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "olko-git-tools",
3
3
  "description": "Everyday git and GitHub CLI operations: conventional commits, branch hygiene.",
4
- "version": "1.41.0",
4
+ "version": "1.41.2",
5
5
  "author": {
6
6
  "name": "Oleg Koval"
7
7
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "olko-github-pr",
3
3
  "description": "Drive GitHub pull requests to merge-ready: review-bot loops, CI fixes, descriptions, dependency triage.",
4
- "version": "1.41.0",
4
+ "version": "1.41.2",
5
5
  "author": {
6
6
  "name": "Oleg Koval"
7
7
  },
@@ -41,9 +41,17 @@ function liftConst(name) {
41
41
  }
42
42
 
43
43
  const preamble = [
44
- liftConst('HARD_RULES'),
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
- console.log('\nhard rules: a tag must be corroborated to skip verification')
102
- // Regression: any agent could bypass the verifier by writing rule: "TS-1".
103
- // Observed live - a test-coverage finding and a comment-policy finding both did.
104
- check('TS-1 on a comment-policy finding is NOT exempt',
105
- isHardRule({ rule: 'TS-1', file: 'a.ts', line: 6, title: 'Comment restates the function', badCode: '/** Sum a cart. */', description: 'a comment earns its place' }), false)
106
- check('TS-1 on a test-coverage finding is NOT exempt',
107
- isHardRule({ rule: 'TS-1', file: 'a.ts', line: 8, title: 'No test coverage', badCode: 'for (let i = 1;', description: 'zero test files added' }), false)
108
- check('an unknown rule string is NOT exempt',
109
- isHardRule({ rule: 'MADE-UP', file: 'a.ts', line: 1, title: 't', badCode: 'x as Foo', description: '' }), false)
110
- // TS-1 is judged on quoted code only: prose is full of `as` and `any`.
111
- check('the word "any" in PROSE alone is NOT exempt',
112
- isHardRule({ rule: 'TS-1', file: 'a.ts', line: 1, title: 'fails on any cart with items', badCode: 'total += lines[i].price', description: 'any agent could trip this' }), false)
113
- check('the phrase "such as" in prose alone is NOT exempt',
114
- isHardRule({ rule: 'TS-1', file: 'a.ts', line: 1, title: 'issue', badCode: 'const n = 1', description: 'a primitive such as String is used' }), false)
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: genuine violations must STILL be exempt')
117
- check('TS-1 with a real cast', isHardRule({ rule: 'TS-1', file: 'a.ts', line: 1, title: 'cast', badCode: 'const x = y as Foo;', description: '' }), true)
118
- check('TS-1 with a real any', isHardRule({ rule: 'TS-1', file: 'a.ts', line: 1, title: 'any', badCode: 'function f(x: any) {}', description: '' }), true)
119
- check('TS-2 with a .js path', isHardRule({ rule: 'TS-2', file: 'web/thing.js', line: 1, title: 'js added', badCode: '', description: '' }), true)
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
- check('TS-1 corroborated by an any annotation',
130
- isHardRule({ rule: 'TS-1', file: 'a.ts', line: 1, title: 'any', badCode: 'function f(x: any) {}', description: '' }), true)
131
- check('TS-1 corroborated by an any[] ',
132
- isHardRule({ rule: 'TS-1', file: 'a.ts', line: 1, title: 'any', badCode: 'const xs: any[] = []', description: '' }), true)
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 by depth')
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
- check('scan verifies nothing', [crit, imp, obs].map(f => shouldVerify(f, 'scan')), [false, false, false])
137
- check('medium verifies criticals only', [crit, imp, obs].map(f => shouldVerify(f, 'medium')), [true, false, false])
138
- check('deep verifies crit + important', [crit, imp, obs].map(f => shouldVerify(f, 'deep')), [true, true, false])
139
- check('a corroborated hard rule is never verified',
140
- shouldVerify({ severity: 'critical', rule: 'TS-2', file: 'x.js', badCode: '', description: '', title: '' }, 'deep'), false)
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "olko-obsidian",
3
3
  "description": "Keep an Obsidian vault in sync with work: PR sync, task rollover, morning routine.",
4
- "version": "1.41.0",
4
+ "version": "1.41.2",
5
5
  "author": {
6
6
  "name": "Oleg Koval"
7
7
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "olko-product",
3
3
  "description": "Take a product idea to a shippable build: MVP passes, full-stack scaffolds, launch plans.",
4
- "version": "1.41.0",
4
+ "version": "1.41.2",
5
5
  "author": {
6
6
  "name": "Oleg Koval"
7
7
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "olko-reflection",
3
3
  "description": "Look back and improve: self-critique, retrospectives, performance review, rapid learning.",
4
- "version": "1.41.0",
4
+ "version": "1.41.2",
5
5
  "author": {
6
6
  "name": "Oleg Koval"
7
7
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "olko-release",
3
3
  "description": "Ship a release: semantic-release setup, changelogs, store listing copy, release-day routine.",
4
- "version": "1.41.0",
4
+ "version": "1.41.2",
5
5
  "author": {
6
6
  "name": "Oleg Koval"
7
7
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "olko-skill-meta",
3
3
  "description": "Author and maintain agent skills and the AI toolchain itself.",
4
- "version": "1.41.0",
4
+ "version": "1.41.2",
5
5
  "author": {
6
6
  "name": "Oleg Koval"
7
7
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "olko-web-ops",
3
3
  "description": "Operate a website: WAF rules, search console audits, analytics bootstrap, docs indexes.",
4
- "version": "1.41.0",
4
+ "version": "1.41.2",
5
5
  "author": {
6
6
  "name": "Oleg Koval"
7
7
  },