@olegkoval/agent-skills 1.40.0 → 1.41.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.
Files changed (46) hide show
  1. package/.github/prompts/open-source-publisher.prompt.md +3 -4
  2. package/.kiro/steering/open-source-publisher.md +3 -4
  3. package/.windsurf/rules/open-source-publisher.md +3 -4
  4. package/adapters/claude/olko-github-pr/skills/lekker-review/SKILL.md +15 -17
  5. package/adapters/claude/olko-github-pr/skills/lekker-review/references/agents/implementation.md +2 -1
  6. package/adapters/claude/olko-github-pr/skills/lekker-review/references/agents/prover.md +13 -10
  7. package/adapters/claude/olko-github-pr/skills/lekker-review/references/agents/quality.md +1 -1
  8. package/adapters/claude/olko-github-pr/skills/lekker-review/references/agents/test-quality.md +7 -0
  9. package/adapters/claude/olko-github-pr/skills/lekker-review/references/agents/triage-quality.md +1 -1
  10. package/adapters/claude/olko-github-pr/skills/lekker-review/references/agents/verifier.md +12 -9
  11. package/adapters/claude/olko-github-pr/skills/lekker-review/references/artifact-page.md +6 -1
  12. package/adapters/claude/olko-github-pr/skills/lekker-review/references/output-format.md +5 -8
  13. package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/selftest.mjs +174 -0
  14. package/adapters/claude/olko-reflection/skills/self-critique/scripts/critique-nudge.mjs +3 -0
  15. package/adapters/claude/olko-release/skills/open-source-publisher/SKILL.md +4 -5
  16. package/adapters/cursor/olko-reflection/skills/self-critique/scripts/critique-nudge.mjs +3 -0
  17. package/adapters/cursor/olko-release/skills/open-source-publisher/SKILL.md +4 -5
  18. package/adapters/grok/olko-reflection/skills/self-critique/scripts/critique-nudge.mjs +3 -0
  19. package/adapters/grok/olko-release/skills/open-source-publisher/SKILL.md +4 -5
  20. package/package.json +1 -1
  21. package/plugins/olko-apple-kit/.claude-plugin/plugin.json +1 -1
  22. package/plugins/olko-creative/.claude-plugin/plugin.json +1 -1
  23. package/plugins/olko-garmin-kit/.claude-plugin/plugin.json +1 -1
  24. package/plugins/olko-git-tools/.claude-plugin/plugin.json +1 -1
  25. package/plugins/olko-github-pr/.claude-plugin/plugin.json +1 -1
  26. package/plugins/olko-github-pr/skills/lekker-review/README.md +9 -9
  27. package/plugins/olko-github-pr/skills/lekker-review/SKILL.md +15 -17
  28. package/plugins/olko-github-pr/skills/lekker-review/references/agents/implementation.md +2 -1
  29. package/plugins/olko-github-pr/skills/lekker-review/references/agents/prover.md +13 -10
  30. package/plugins/olko-github-pr/skills/lekker-review/references/agents/quality.md +1 -1
  31. package/plugins/olko-github-pr/skills/lekker-review/references/agents/test-quality.md +7 -0
  32. package/plugins/olko-github-pr/skills/lekker-review/references/agents/triage-quality.md +1 -1
  33. package/plugins/olko-github-pr/skills/lekker-review/references/agents/verifier.md +12 -9
  34. package/plugins/olko-github-pr/skills/lekker-review/references/artifact-page.md +6 -1
  35. package/plugins/olko-github-pr/skills/lekker-review/references/output-format.md +5 -8
  36. package/plugins/olko-github-pr/skills/lekker-review/scripts/selftest.mjs +174 -0
  37. package/plugins/olko-github-pr/skills/lekker-review/workflow.js +217 -75
  38. package/plugins/olko-obsidian/.claude-plugin/plugin.json +1 -1
  39. package/plugins/olko-product/.claude-plugin/plugin.json +1 -1
  40. package/plugins/olko-reflection/.claude-plugin/plugin.json +1 -1
  41. package/plugins/olko-reflection/skills/self-critique/scripts/critique-nudge.mjs +3 -0
  42. package/plugins/olko-release/.claude-plugin/plugin.json +1 -1
  43. package/plugins/olko-release/skills/open-source-publisher/SKILL.md +4 -5
  44. package/plugins/olko-skill-meta/.claude-plugin/plugin.json +1 -1
  45. package/plugins/olko-web-ops/.claude-plugin/plugin.json +1 -1
  46. package/scripts/lib/catalog.mjs +3 -0
@@ -31,12 +31,11 @@ Use this skill to audit whether an OSS repository is ready to publish, then help
31
31
  5. For existing usable pieces, do not propose replacement by default. Ask a change-oriented question only when useful, for example:
32
32
  - "You already have a terminal-style GitHub Pages site. Do you want to keep it or restyle it?"
33
33
  - "You already have an icon and social card. Do you want a refresh, or should I leave them as-is?"
34
- - "You already have a static social card. Do you want me to animate it with pixel2motion, or leave it static?"
35
34
  - "You already have release automation. Do you want me to audit only, or also tighten it?"
36
35
  6. Ask only for choices needed to fix missing or weak pieces:
37
36
  - If GitHub Pages is missing or weak, ask for style: `oldschool linux`, `terminal`, `modern`, `brutalist`, `glassmorphism`, `y2k`, `hacker`, or custom.
38
37
  - If donation wiring is missing, ask whether to enable it: `none`, `GitHub Sponsors`, `Ko-fi`, `Buy Me a Coffee`, `Open Collective`, `Thanks.dev`, or custom URL.
39
- - If the repo has a generated static social image or hero card, ask whether to animate it using `nolangz/pixel2motion`.
38
+ - If the repo has a generated logo or simple hero artwork supported by `nolangz/pixel2motion`, ask whether to animate it or leave it static.
40
39
  - If the repo has no clear product essence, ask for a one-sentence positioning statement.
41
40
  7. Implement only approved, missing, or weak work in this order:
42
41
  - OSS governance and support files
@@ -200,8 +199,8 @@ Use `magick` or another renderer when `rsvg-convert` is unavailable.
200
199
 
201
200
  Some repos benefit from a short animated preview. Keep this opt-in.
202
201
 
203
- - If you already generated a static image, ask: `Do you want me to animate the static image using pixel2motion?`
204
- - If the user says yes, use `nolangz/pixel2motion` as the optional dependency for logo animation, animated HTML demos, and GIF/video previews.
202
+ - After the user opts in during workflow step 6, animate only the approved generated logo or simple artwork.
203
+ - Use `nolangz/pixel2motion` as the optional dependency for that animation, animated HTML demos, and GIF/video previews.
205
204
  - Do not auto-animate by default.
206
205
 
207
206
  ## README Standard
@@ -30,12 +30,11 @@ Use this skill to audit whether an OSS repository is ready to publish, then help
30
30
  5. For existing usable pieces, do not propose replacement by default. Ask a change-oriented question only when useful, for example:
31
31
  - "You already have a terminal-style GitHub Pages site. Do you want to keep it or restyle it?"
32
32
  - "You already have an icon and social card. Do you want a refresh, or should I leave them as-is?"
33
- - "You already have a static social card. Do you want me to animate it with pixel2motion, or leave it static?"
34
33
  - "You already have release automation. Do you want me to audit only, or also tighten it?"
35
34
  6. Ask only for choices needed to fix missing or weak pieces:
36
35
  - If GitHub Pages is missing or weak, ask for style: `oldschool linux`, `terminal`, `modern`, `brutalist`, `glassmorphism`, `y2k`, `hacker`, or custom.
37
36
  - If donation wiring is missing, ask whether to enable it: `none`, `GitHub Sponsors`, `Ko-fi`, `Buy Me a Coffee`, `Open Collective`, `Thanks.dev`, or custom URL.
38
- - If the repo has a generated static social image or hero card, ask whether to animate it using `nolangz/pixel2motion`.
37
+ - If the repo has a generated logo or simple hero artwork supported by `nolangz/pixel2motion`, ask whether to animate it or leave it static.
39
38
  - If the repo has no clear product essence, ask for a one-sentence positioning statement.
40
39
  7. Implement only approved, missing, or weak work in this order:
41
40
  - OSS governance and support files
@@ -199,8 +198,8 @@ Use `magick` or another renderer when `rsvg-convert` is unavailable.
199
198
 
200
199
  Some repos benefit from a short animated preview. Keep this opt-in.
201
200
 
202
- - If you already generated a static image, ask: `Do you want me to animate the static image using pixel2motion?`
203
- - If the user says yes, use `nolangz/pixel2motion` as the optional dependency for logo animation, animated HTML demos, and GIF/video previews.
201
+ - After the user opts in during workflow step 6, animate only the approved generated logo or simple artwork.
202
+ - Use `nolangz/pixel2motion` as the optional dependency for that animation, animated HTML demos, and GIF/video previews.
204
203
  - Do not auto-animate by default.
205
204
 
206
205
  ## README Standard
@@ -29,12 +29,11 @@ Use this skill to audit whether an OSS repository is ready to publish, then help
29
29
  5. For existing usable pieces, do not propose replacement by default. Ask a change-oriented question only when useful, for example:
30
30
  - "You already have a terminal-style GitHub Pages site. Do you want to keep it or restyle it?"
31
31
  - "You already have an icon and social card. Do you want a refresh, or should I leave them as-is?"
32
- - "You already have a static social card. Do you want me to animate it with pixel2motion, or leave it static?"
33
32
  - "You already have release automation. Do you want me to audit only, or also tighten it?"
34
33
  6. Ask only for choices needed to fix missing or weak pieces:
35
34
  - If GitHub Pages is missing or weak, ask for style: `oldschool linux`, `terminal`, `modern`, `brutalist`, `glassmorphism`, `y2k`, `hacker`, or custom.
36
35
  - If donation wiring is missing, ask whether to enable it: `none`, `GitHub Sponsors`, `Ko-fi`, `Buy Me a Coffee`, `Open Collective`, `Thanks.dev`, or custom URL.
37
- - If the repo has a generated static social image or hero card, ask whether to animate it using `nolangz/pixel2motion`.
36
+ - If the repo has a generated logo or simple hero artwork supported by `nolangz/pixel2motion`, ask whether to animate it or leave it static.
38
37
  - If the repo has no clear product essence, ask for a one-sentence positioning statement.
39
38
  7. Implement only approved, missing, or weak work in this order:
40
39
  - OSS governance and support files
@@ -198,8 +197,8 @@ Use `magick` or another renderer when `rsvg-convert` is unavailable.
198
197
 
199
198
  Some repos benefit from a short animated preview. Keep this opt-in.
200
199
 
201
- - If you already generated a static image, ask: `Do you want me to animate the static image using pixel2motion?`
202
- - If the user says yes, use `nolangz/pixel2motion` as the optional dependency for logo animation, animated HTML demos, and GIF/video previews.
200
+ - After the user opts in during workflow step 6, animate only the approved generated logo or simple artwork.
201
+ - Use `nolangz/pixel2motion` as the optional dependency for that animation, animated HTML demos, and GIF/video previews.
203
202
  - Do not auto-animate by default.
204
203
 
205
204
  ## README Standard
@@ -5,7 +5,7 @@ description: >
5
5
  isolated worktree, gathers context from your issue tracker, chat, docs, CI
6
6
  checks, and (optionally) production monitoring, runs 5 parallel specialized
7
7
  review agents (quality/implementation/simplification/conventions/test-quality),
8
- verifies every finding against the diff, then outputs a single unified markdown
8
+ verifies every verdict-affecting finding against the diff, then outputs a single unified markdown
9
9
  review: file + risk + bad code + why it's wrong + fix, ready to paste directly
10
10
  into GitHub. Saves every review to ~/code-reviews/*.md. Covers business logic,
11
11
  scalability, complexity, data integrity, security, integration contracts, error
@@ -79,14 +79,11 @@ the 5 specialist agents still run and still find real bugs. They just don't
79
79
  have a codified "always Critical" rule list to check against.
80
80
 
81
81
  If any hard rule you define carries a `rule` tag (e.g. `"TS-1"`), reviewer
82
- agents attach that tag to matching findings and the workflow **skips
83
- adversarial verification** for them. This is deliberate: the verifier's five
84
- challenges ask runtime-failure questions ("does this fail on a normal
85
- execution?", "can you write the failing test?") that a standards violation
86
- can never answer, so verifying them systematically drops the very findings
87
- your policy declares non-negotiable. A tagged finding keeps its Critical
88
- severity; the workflow returns how many were exempted as `hardRuleCount`, and
89
- each carries a `verifierReasoning` saying so.
82
+ agents attach that tag to matching findings. The verifier checks the diff
83
+ anchor and rule applicability, but skips its five runtime-failure challenges.
84
+ Those challenges cannot evaluate a standards violation. A tagged finding keeps
85
+ Critical severity only when both rule-specific checks pass. The workflow
86
+ returns the number checked as `hardRuleCount`.
90
87
 
91
88
  `${CLAUDE_PLUGIN_ROOT}` below refers to this skill's own installed directory:
92
89
  resolve every `references/...` and script path relative to it.
@@ -150,7 +147,7 @@ treat as a full review and leave PREV_SHA unset. Also grep the same file for
150
147
  | Context: chat/docs/framework-docs/monitoring/prior-review-memory (optional, MCP-dependent) | skip | included | included + broader recall |
151
148
  | Worktree + static checks | skip (WORKTREE_PATH=null) unless `--fix` | included | included |
152
149
  | Review agents | 2 triage (haiku) | 5 specialists (sonnet) | 5 specialists (sonnet) |
153
- | Per-finding verification | none | Criticals only (hard rules exempt) | Criticals + Importants (hard rules exempt) |
150
+ | Per-finding verification | Criticals + Importants | Criticals + Importants | Criticals + Importants |
154
151
  | Completeness critic | skip | skip | included |
155
152
  | Proof-of-bug (failing test per Critical) | skip | included (max 5) | included (max 5) |
156
153
  | Living review artifact | included | included | included |
@@ -298,8 +295,9 @@ The workflow runs three phases:
298
295
  description/badCode/fix of the set - a Critical is never demoted by an
299
296
  Observation someone else filed at the same line - and records every
300
297
  contributing dimension in `agreedBy`.
301
- - **Verify:** scan verifies nothing; medium verifies Criticals; deep verifies
302
- Criticals + Importants. Hard-rule findings (`rule` set) are always exempt.
298
+ - **Verify:** every Critical and Important is checked at every depth because it
299
+ can affect the verdict. Hard-rule findings (`rule` set) use the verifier's
300
+ diff-anchor and rule-applicability checks instead of runtime challenges.
303
301
  Each verifier runs the five-challenge adversarial refutation from
304
302
  `references/agents/verifier.md` against one finding, returns
305
303
  `{verdict, newSeverity?, reasoning}`. Verifiers run `maxConcurrent` at a time
@@ -311,13 +309,13 @@ The workflow runs three phases:
311
309
  one prover agent (`references/agents/prover.md`, sonnet, max 5, run
312
310
  `maxConcurrent` at a time) that writes a test asserting the CORRECT behaviour,
313
311
  runs it in the worktree, and captures it failing because of the bug. The proof rides on the finding as
314
- `proof: {attempted, proven, reason, testCode?, testCommand?, redOutput?}`.
312
+ `proof: {attempted, proven, outcome, reason, testCode?, testCommand?, redOutput?}`.
315
313
  A proof that comes back GREEN (code behaved correctly) is counter-evidence -
316
- Step 3 must downgrade or explicitly justify the finding, never ignore it.
314
+ the workflow automatically downgrades the finding from Critical to Important.
317
315
  Hard-rule findings are never proved (policy violations have no failing test).
318
316
 
319
317
  Findings have schema:
320
- `{file, line, severity, title, description, badCode, fix, rule?, precedent?, agreedBy?, verifierReasoning?, proof?}`
318
+ `{file, line, severity, title, description, badCode, fix, rule?, precedent?, agreedBy?, verificationStatus?, verifierReasoning?, proof?}`
321
319
  `badCode` and `fix` are schema-required: an empty string is allowed only on
322
320
  `observation` / `idiomatic` findings.
323
321
 
@@ -326,7 +324,7 @@ provers on `sonnet`, housekeeping on `haiku`. Only the synthesis in Step 3 runs
326
324
  on the session model.
327
325
 
328
326
  Return value from the workflow:
329
- `{findings, droppedCount, downgradedCount, hardRuleCount, proveAttemptCount, provenCount, agentCount, outputTokens, turnTokensTotal}`
327
+ `{findings, droppedCount, downgradedCount, hardRuleCount, proveAttemptCount, provenCount, acCoverage, coverageVerdict, mutationSlip, mockSmells, agentCount, outputTokens, turnTokensTotal}`
330
328
  `outputTokens` is this workflow's own output spend; `turnTokensTotal` is the
331
329
  whole turn's shared pool (main loop included).
332
330
 
@@ -411,7 +409,7 @@ requirements:
411
409
  - When `PREV_SHA` is set: include `## 🔁 Since last review` comparing
412
410
  `PREV_REVIEW_FILE` findings against the new head - list each as fixed or
413
411
  still open, before any new findings.
414
- - Test Quality section: populate from the test-quality agent's fields
412
+ - Test Quality section: populate from the workflow return fields
415
413
  (`coverageVerdict`, `mutationSlip`, `mockSmells`).
416
414
  - Idiomatic section: populated from severity=idiomatic findings only.
417
415
  - **💰 Review Cost block:** `outputTokens` from the workflow return is the
@@ -8,6 +8,7 @@ Axes to cover:
8
8
  - Business Logic / AC coverage: for each AC in the list below, mark
9
9
  ✅ met / ⚠️ partial / ❌ missing. Scope creep is also worth flagging.
10
10
  AC_LIST: read key "acList" from CONTEXT_FILE.
11
+ Return the complete status summary in the structured `acCoverage` field.
11
12
  - Scalability: N+1 queries, missing pagination, unbounded in-memory
12
13
  collections, missing rate-limit handling, cron jobs without overlap guard,
13
14
  missing DB indexes for new query patterns.
@@ -23,7 +24,7 @@ Axes to cover:
23
24
  Set `rule: "GQL-1"` on any Critical finding raised under this axis.
24
25
 
25
26
  Setting rule tags the finding as a house hard rule: it keeps its Critical
26
- severity and skips adversarial verification. Only set it for a genuine GQL-1
27
+ severity only after rule-specific validation and skips runtime challenges. Only set it for a genuine GQL-1
27
28
  violation: never to shield an ordinary finding from verification.
28
29
 
29
30
  CI_STATUS: read key "ciStatus" from the JSON file CONTEXT_FILE.
@@ -1,6 +1,6 @@
1
1
  # prover -- lekker-review agent prompt
2
2
  # Receives: one FINDING as JSON, WORKTREE_PATH path, DIFF_FILE path, CONTEXT_FILE path
3
- # Returns: PROOF_SCHEMA { attempted: boolean, proven: boolean, reason: string, testCode?: string, testCommand?: string, redOutput?: string }
3
+ # Returns: PROOF_SCHEMA { attempted: boolean, proven: boolean, outcome: 'proven'|'passed'|'inconclusive'|'not_attempted', reason: string, testCode?: string, testCommand?: string, redOutput?: string }
4
4
 
5
5
  ## Mission
6
6
 
@@ -29,8 +29,9 @@ explicitly.
29
29
  ## Step 1 -- Testability gate
30
30
 
31
31
  Read the FINDING, the real code at `file:line` in `WORKTREE_PATH`, and the diff
32
- context in `DIFF_FILE`. Return `attempted: false` with an honest one-sentence
33
- `reason` when any of these hold:
32
+ context in `DIFF_FILE`. Return `attempted: false, proven: false,
33
+ outcome: "not_attempted"` with an honest one-sentence `reason` when any of these
34
+ hold:
34
35
 
35
36
  - The failure path requires live IO (Shopify/BC/Salesforce API, a real DB, the
36
37
  network) and the repo has no test infra to fake it cheaply.
@@ -77,25 +78,26 @@ Exactly one run command, scoped to your file only:
77
78
  Set the Bash tool's `timeout` parameter to 120000 for this call.
78
79
 
79
80
  If the runner hangs or the environment fails (missing config, transform
80
- errors), that is `attempted: true, proven: false` with the reason. Report
81
- honestly -- never retry more than once for a pure environment issue (e.g. a
82
- wrong config flag), and never loop.
81
+ errors), that is `attempted: true, proven: false, outcome: "inconclusive"`
82
+ with the reason. Report honestly -- never retry more than once for a pure
83
+ environment issue (e.g. a wrong config flag), and never loop.
83
84
 
84
85
  ---
85
86
 
86
87
  ## Step 4 -- Judge the outcome
87
88
 
88
89
  - **Test FAILS, and the mismatch matches what the finding predicts** ->
89
- `proven: true`. `redOutput` = the failure excerpt, trimmed to the
90
+ `proven: true, outcome: "proven"`. `redOutput` = the failure excerpt, trimmed to the
90
91
  informative ~15 lines (expected vs received + the failing assertion line).
91
92
  `testCode` = the full test file content. `testCommand` = the exact command
92
93
  you ran.
93
- - **Test PASSES** -> the finding did not reproduce. `proven: false`, and
94
+ - **Test PASSES** -> the finding did not reproduce. `proven: false,
95
+ outcome: "passed"`, and
94
96
  `reason` states plainly that the code behaved correctly for the tested
95
97
  input. This is important review signal, not a failure of yours. Do NOT alter
96
98
  the test to force a failure.
97
99
  - **Test fails for an unrelated reason** (import error, env issue) ->
98
- `proven: false`, honest `reason`.
100
+ `proven: false, outcome: "inconclusive"`, honest `reason`.
99
101
 
100
102
  ---
101
103
 
@@ -124,6 +126,7 @@ Return EXACTLY one JSON object matching PROOF_SCHEMA:
124
126
  {
125
127
  "attempted": true | false,
126
128
  "proven": true | false,
129
+ "outcome": "proven" | "passed" | "inconclusive" | "not_attempted",
127
130
  "reason": "<one or two sentences: why not attempted, why it proved, or why it didn't reproduce>",
128
131
  "testCode": "<full test file content -- only when attempted>",
129
132
  "testCommand": "<exact command run -- only when attempted>",
@@ -131,5 +134,5 @@ Return EXACTLY one JSON object matching PROOF_SCHEMA:
131
134
  }
132
135
  ```
133
136
 
134
- `attempted: false` implies `proven: false` and omits `testCode`/`testCommand`/
137
+ `attempted: false` implies `proven: false, outcome: "not_attempted"` and omits `testCode`/`testCommand`/
135
138
  `redOutput`. Do not narrate outside the object.
@@ -56,7 +56,7 @@ Axes to cover:
56
56
  or a `node_modules` path.
57
57
 
58
58
  Setting rule tags the finding as a house hard rule: it keeps its Critical
59
- severity and skips adversarial verification. Only set it for a genuine
59
+ severity only after rule-specific validation and skips runtime challenges. Only set it for a genuine
60
60
  TS-1/TS-2 violation: never to shield an ordinary finding from verification.
61
61
 
62
62
  CI_STATUS: read key "ciStatus" from the JSON file CONTEXT_FILE.
@@ -141,6 +141,13 @@ criticism without a fix is incomplete.
141
141
  Report the mutation-slip analysis as a single paragraph under a
142
142
  "**Mutation-slip risk:**" heading, not as line items.
143
143
 
144
+ Return the review summary through these structured fields in addition to
145
+ `findings`:
146
+ - `coverageVerdict`: one concise overall coverage verdict.
147
+ - `mutationSlip`: the mutation-slip risk paragraph.
148
+ - `mockSmells`: every mock-smell issue as `{file, line, description, fix}`.
149
+ Return an empty array when there are no mock smells.
150
+
144
151
  EXISTING_REVIEWS: read key "existingReviews" from CONTEXT_FILE (awareness only, skip findings already raised)
145
152
 
146
153
  PROJECT_RULES to verify: read key "projectRules" from CONTEXT_FILE.
@@ -20,7 +20,7 @@ Axes to cover (Critical/Important only):
20
20
  set `rule: "GQL-1"` when Critical
21
21
 
22
22
  Setting rule tags the finding as a house hard rule: it keeps its Critical
23
- severity and skips adversarial verification. Only set it for a genuine
23
+ severity only after rule-specific validation and skips runtime challenges. Only set it for a genuine
24
24
  TS-1/TS-2/GQL-1 violation: never to shield an ordinary finding from
25
25
  verification.
26
26
 
@@ -1,5 +1,5 @@
1
1
  # verifier -- lekker-review agent prompt
2
- # Receives: one FINDING as JSON, DIFF_FILE path, CONTEXT_FILE path, WORKTREE_PATH path
2
+ # Receives: one FINDING as JSON, DIFF_FILE path, CONTEXT_FILE path, WORKTREE_PATH path, HOUSE_RULES_FILE path
3
3
  # Returns: VERDICT_SCHEMA { verdict: 'confirmed'|'downgraded'|'dropped', newSeverity?, reasoning }
4
4
 
5
5
  ## Mindset
@@ -18,17 +18,19 @@ verified yourself are not confirmed findings.
18
18
 
19
19
  You have received one candidate finding as JSON (field `FINDING` in your task
20
20
  message). Run all applicable verification checks below against the finding using
21
- `DIFF_FILE`, `CONTEXT_FILE`, and `WORKTREE_PATH` (read from the task message).
21
+ `DIFF_FILE`, `CONTEXT_FILE`, `WORKTREE_PATH`, and `HOUSE_RULES_FILE` (read from
22
+ the task message).
22
23
 
23
24
  When in doubt, drop. A Critical must pass ALL FIVE challenges to remain Critical
24
- -- unless Step 0 exempts it as a house hard rule, which is the one exception.
25
+ unless Step 0 validates it through the house hard-rule path.
25
26
 
26
27
  ---
27
28
 
28
- ## Step 0 -- Hard-rule exemption (check this FIRST)
29
+ ## Step 0 -- Hard-rule validation (check this FIRST)
29
30
 
30
- If the FINDING JSON has a `rule` field set to `TS-1`, `TS-2`, `GQL-1`, or
31
- `PR-1`, do NOT run the five adversarial challenges below. They ask
31
+ If the FINDING JSON has any non-empty `rule` field, including built-in tags
32
+ such as `TS-1`, `TS-2`, `GQL-1`, or `PR-1`, do NOT run the five adversarial
33
+ challenges below. They ask
32
34
  runtime-failure questions that a standards violation can never answer, and
33
35
  answering them honestly would drop a finding that house-rules policy declares
34
36
  Critical on standards grounds rather than on runtime behaviour.
@@ -41,8 +43,9 @@ Instead run exactly two checks:
41
43
  ```bash
42
44
  grep "^+" "$DIFF_FILE" | grep "<snippet>"
43
45
  ```
44
- 2. **Rule applicability**: the code really does violate the rule as written
45
- in `references/house-rules.md` -- e.g. an `as const` is not a type cast in
46
+ 2. **Rule applicability**: read the exact `HOUSE_RULES_FILE` path from the task
47
+ message, then confirm the code really violates the rule as written there --
48
+ e.g. an `as const` is not a type cast in
46
49
  the TS-1 sense; a `nodes` query that legitimately fetches a single known
47
50
  node with a documented comment may satisfy GQL-1; a `.js` file inside a
48
51
  Liquid theme repo is exempt from TS-2.
@@ -253,7 +256,7 @@ the inference is the crux of the claim
253
256
  ## Outcome rules
254
257
 
255
258
  The one exception to "a Critical must pass ALL FIVE challenges" is the
256
- hard-rule path in Step 0: a `rule`-tagged finding is judged solely on the
259
+ hard-rule path in Step 0: a `rule`-tagged finding is validated solely on the
257
260
  anchor + rule-applicability checks and never runs the five challenges.
258
261
 
259
262
  A Critical finding must pass ALL FIVE challenges. Any failure downgrades:
@@ -18,7 +18,7 @@ All of the following are already in hand after Step 3 of SKILL.md:
18
18
  - `findings.json` path (scratchpad) -- each finding carries: `file`, `line`,
19
19
  `severity`, `title`, `description`, `badCode`, `fix`, `rule?`, `precedent?`,
20
20
  `agreedBy?`, `verifierReasoning?`, `proof?` (proof = `{attempted, proven,
21
- reason, testCode?, testCommand?, redOutput?}`).
21
+ outcome, reason, testCode?, testCommand?, redOutput?}`).
22
22
  - The saved review file path: `~/code-reviews/YYYY-MM-DD-pr-N-repo.md`.
23
23
  - PR metadata: `REPO_SLUG`, `PR_NUMBER`, `PR_URL`, title, author, `headRefName`
24
24
  → `baseRefName`, head sha, depth, verdict, `isDraft`, `mergeStateStatus`, CI
@@ -96,6 +96,11 @@ Hard requirements for the HTML page:
96
96
  `<pre>`, a one-line explanation that the test asserts correct behavior, and
97
97
  `testCode` collapsed behind its own `<details>`. This is the page's
98
98
  centerpiece -- make it prominent (e.g. a red left border) but not garish.
99
+ When `proof.outcome === 'passed'`, render a distinct
100
+ "COUNTER-EVIDENCE -- focused proof test passed" panel with `reason`,
101
+ `testCommand`, and `testCode` collapsed behind its own `<details>`. Explain
102
+ that the workflow downgraded the finding to Important; do not present the
103
+ passing input as proof that every related input is safe.
99
104
  - **Test Quality + Review Cost sections**, mirrored from the review file,
100
105
  kept concise (verdict + gaps + cost table; no need to reproduce every
101
106
  sentence).
@@ -217,7 +217,7 @@ if empty.)*
217
217
  Depth: <⚡ scan | 🔍 medium | 🔬 deep>
218
218
  Diff size: ~<N> lines (~<N> tokens)
219
219
  Agents run: <N total>: <breakdown, e.g. "5 reviewers (sonnet) + 6 verifiers (sonnet) + 2 provers (sonnet) + 1 critic (sonnet)">
220
- Verify skipped: <N> hard-rule finding(s) exempt from adversarial verification (omit the line when 0)
220
+ Rule checks: <N> hard-rule finding(s) validated with rule-specific checks (omit the line when 0)
221
221
  Context sources: <the subset of issue-tracker / chat / docs / framework-docs / monitoring / CI / prior-review-memory actually used>
222
222
 
223
223
  Output tokens: <N> ← ACTUAL workflow spend, from the workflow's outputTokens return value
@@ -253,10 +253,7 @@ Reviewer agents, verifiers, provers, and the critic all run on sonnet; triage an
253
253
  self-contained but silently breaks callers.
254
254
  - Blank line between findings. No double blank lines.
255
255
  - No trailing whitespace, no HTML tags, no ANSI escapes.
256
- - **Proof counter-evidence rule**: when `proof.attempted` is true but `proven`
257
- is false because the code behaved correctly for the tested input, the
258
- synthesis MUST treat that as counter-evidence: either downgrade the finding
259
- or state in the finding body why the proof attempt doesn't exonerate it
260
- (e.g. the tested input wasn't the one that actually breaks). A finding whose
261
- proof came back green cannot silently stay Critical. When `attempted` is
262
- false, say nothing: untestable is not evidence either way.
256
+ - **Proof counter-evidence rule**: when `proof.outcome` is `passed`, the
257
+ workflow has already downgraded the finding from Critical to Important.
258
+ Explain the passing proof in the finding body. When `attempted` is false,
259
+ say nothing: untestable is not evidence either way.
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env node
2
+ // Zero-agent regression test for lekker-review's PURE logic.
3
+ //
4
+ // Why this exists: every defect found in the 2026-08-31 hardening pass was in
5
+ // pure, synchronous code - the dedup bucket key, the hard-rule exemption gate,
6
+ // the model/effort routing - yet the only way to exercise any of it was a live
7
+ // workflow run costing ~7 agents and 70+ seconds. This runs the same logic in
8
+ // milliseconds with no agents at all. Run it after ANY edit to workflow.js:
9
+ //
10
+ // node ~/.claude/skills/lekker-review/scripts/selftest.mjs
11
+ //
12
+ // It lifts the real functions out of workflow.js by source extraction rather
13
+ // than importing, because workflow.js is written for the Workflow harness (top
14
+ // level `return`, an injected `args` global) and is not importable as a module.
15
+ import { readFileSync } from 'node:fs'
16
+ import { fileURLToPath } from 'node:url'
17
+ import { dirname, join } from 'node:path'
18
+
19
+ const SKILL = dirname(dirname(fileURLToPath(import.meta.url)))
20
+ // Optional arg: a different workflow.js to test. Used to prove this suite
21
+ // actually discriminates - point it at a pre-fix backup and it must FAIL.
22
+ const target = process.argv[2] || join(SKILL, 'workflow.js')
23
+ const src = readFileSync(target, 'utf8')
24
+ console.log(`selftest target: ${target}`)
25
+
26
+ function lift(name) {
27
+ const i = src.indexOf(`function ${name}`)
28
+ if (i === -1) throw new Error(`selftest: function ${name} not found in workflow.js - was it renamed?`)
29
+ let d = 0, j = i
30
+ for (;; j++) {
31
+ if (src[j] === '{') d++
32
+ else if (src[j] === '}') { d--; if (d === 0) break }
33
+ }
34
+ return src.slice(i, j + 1)
35
+ }
36
+
37
+ function liftConst(name) {
38
+ const m = new RegExp(`^const ${name} = .*$`, 'm').exec(src)
39
+ if (!m) throw new Error(`selftest: const ${name} not found in workflow.js`)
40
+ return m[0]
41
+ }
42
+
43
+ const preamble = [
44
+ liftConst('HARD_RULES'),
45
+ (() => { try { return liftConst('SAME_ISSUE_LINE_WINDOW') } catch { return 'const SAME_ISSUE_LINE_WINDOW = 30' } })(),
46
+ "const SEVERITY_RANK = { observation: 0, idiomatic: 1, important: 2, critical: 3 }",
47
+ ...['titleTokens', 'sameIssue', 'nearbyLines', 'spanWithinWindow', 'hardRuleCorroborated',
48
+ 'isHardRule', 'longest', 'mergeFindings', 'dedup', 'shouldVerify'].map(n => {
49
+ try { return lift(n) } catch { return `function ${n}() { throw new Error('${n} absent from this workflow.js') }` }
50
+ }),
51
+ ].join('\n')
52
+
53
+ const { dedup, isHardRule, shouldVerify, sameIssue } =
54
+ new Function(preamble + '\nreturn { dedup, isHardRule, shouldVerify, sameIssue }')()
55
+
56
+ let failed = 0
57
+ function check(name, actual, expected) {
58
+ const a = JSON.stringify(actual), e = JSON.stringify(expected)
59
+ if (a === e) { console.log(` ok ${name}`) }
60
+ else { console.log(` FAIL ${name}\n expected ${e}\n actual ${a}`); failed++ }
61
+ }
62
+
63
+ console.log('\ndedup: the same defect anchored at different lines must merge')
64
+ // Regression: bucketing on `file:line` meant these two were never compared,
65
+ // despite a title similarity of 0.64 against a 0.4 threshold. Observed live.
66
+ const dupes = [
67
+ { file: 'src/total.ts', line: 14, severity: 'critical', title: 'Off-by-one loop skips the first cart line', badCode: 'for (let i = 1;', description: 'aaa' },
68
+ { file: 'src/total.ts', line: 9, severity: 'critical', title: 'cartTotal skips the first line item (off-by-one loop start)', badCode: 'for (let i = 1;', description: 'bb' },
69
+ ]
70
+ check('two anchors, one issue -> 1 finding', dedup(dupes).length, 1)
71
+ check('merge keeps the highest severity', dedup([
72
+ { file: 'a.ts', line: 3, severity: 'observation', title: 'Off-by-one loop skips first line', badCode: '', description: '' },
73
+ { file: 'a.ts', line: 5, severity: 'critical', title: 'Off-by-one loop skips the first line', badCode: '', description: '' },
74
+ ])[0].severity, 'critical')
75
+
76
+ console.log('\ndedup: distinct issues must NOT be merged')
77
+ check('similar titles 390 lines apart stay separate', dedup([
78
+ { file: 'big.ts', line: 10, severity: 'important', title: 'Missing pagination on the products query', badCode: '', description: '' },
79
+ { file: 'big.ts', line: 400, severity: 'important', title: 'Missing pagination on the orders query', badCode: '', description: '' },
80
+ ]).length, 2)
81
+ check('same line, unrelated titles stay separate', dedup([
82
+ { file: 'a.ts', line: 7, severity: 'important', title: 'Unbounded retry loop hides throttling', badCode: '', description: '' },
83
+ { file: 'a.ts', line: 7, severity: 'important', title: 'Metafield namespace hardcoded in the query', badCode: '', description: '' },
84
+ ]).length, 2)
85
+ // Grouping must not depend on arrival order. With only g[0] compared, findings
86
+ // at 25, 50 and 1 all joined when 25 arrived first, spanning 49 lines.
87
+ const spanCase = [
88
+ { file: 'a.ts', line: 25, severity: 'important', title: 'Missing pagination on the query', badCode: '', description: '' },
89
+ { file: 'a.ts', line: 50, severity: 'important', title: 'Missing pagination on the query', badCode: '', description: '' },
90
+ { file: 'a.ts', line: 1, severity: 'important', title: 'Missing pagination on the query', badCode: '', description: '' },
91
+ ]
92
+ check('a group never spans more than the window (25, 50, 1)', dedup(spanCase).length, 2)
93
+ check('the same set in a different order gives the same answer',
94
+ dedup([spanCase[2], spanCase[0], spanCase[1]]).length, dedup(spanCase).length)
95
+
96
+ check('different files never merge', dedup([
97
+ { file: 'a.ts', line: 7, severity: 'critical', title: 'Off-by-one loop skips the first line', badCode: '', description: '' },
98
+ { file: 'b.ts', line: 7, severity: 'critical', title: 'Off-by-one loop skips the first line', badCode: '', description: '' },
99
+ ]).length, 2)
100
+
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)
115
+
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)
128
+ }
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)
133
+
134
+ console.log('\nverification scope by depth')
135
+ 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)
141
+
142
+ // Per-host model routing is optional: a deployment may pin models per host via
143
+ // a MODEL_TABLE, or leave every agent() call to name its own model. Test it
144
+ // only when it is present, so this suite runs against either shape.
145
+ const routingAssign = /const\s+MODEL\s*=\s*MODEL_TABLE\s*\[\s*HOST\s*\]/.exec(src)
146
+ const hasRouting = Boolean(routingAssign)
147
+ // A guard that can silently disable itself is worse than no guard. If the file
148
+ // clearly HAS a MODEL_TABLE but the assignment did not parse, that is a failure,
149
+ // not a reason to skip.
150
+ if (!hasRouting && /MODEL_TABLE/.test(src)) {
151
+ check('MODEL_TABLE is present but its assignment was not recognised', false, true)
152
+ }
153
+ if (!hasRouting) {
154
+ console.log('\nmodel + effort routing: not configured in this workflow.js, skipped')
155
+ } else {
156
+ console.log('\nmodel + effort routing is pinned per host, never inherited')
157
+ const routing = new Function('input', [
158
+ src.slice(src.indexOf('const HOST = '), routingAssign.index + routingAssign[0].length),
159
+ 'return { HOST, MODEL }',
160
+ ].join('\n'))
161
+ check('an absent host arg falls back to the default table', routing({}).MODEL, routing({ host: 'claude' }).MODEL)
162
+ check('an unknown host falls back to the default, not an invalid model', routing({ host: 'nonsense' }).HOST, 'claude')
163
+ check('host matching is case-insensitive', routing({ host: 'CODEX' }).HOST, 'codex')
164
+ // Every role must resolve to a non-empty model, and effort must be set.
165
+ for (const host of ['claude', 'codex']) {
166
+ const m = routing({ host }).MODEL
167
+ const roles = Object.keys(m).filter(k => k !== 'effort')
168
+ check(`${host}: every role resolves to a model`, roles.every(r => typeof m[r] === 'string' && m[r].length > 0), true)
169
+ check(`${host}: effort is set`, typeof m.effort === 'string' && m.effort.length > 0, true)
170
+ }
171
+ }
172
+
173
+ console.log(failed === 0 ? '\nall checks passed\n' : `\n${failed} check(s) FAILED\n`)
174
+ process.exit(failed === 0 ? 0 : 1)
@@ -11,6 +11,9 @@ import { readFileSync } from 'node:fs';
11
11
  const THRESHOLD = 1500;
12
12
  const OFFER_MARKER = /critical-thinking review/i; // the offer line must contain this; doubles as a loop guard
13
13
 
14
+ /**
15
+ * Exits the hook cleanly when conditions for offering a critique are not met.
16
+ */
14
17
  function bail() { process.exit(0); } // any uncertainty => do nothing, never block the user
15
18
 
16
19
  let raw = '';
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: open-source-publisher
3
- description: Prepare an open-source repository for polished public publishing. Use when a user asks to publish, open-source, launch, polish, package, brand, or make a GitHub project presentable with a minimal project icon, social preview image, GitHub Pages landing page, standardized README, essential shields, CI/CD quality gates, release automation checks, and optional donation setup. Prefer the external `logo-generator` skill for icon generation when available, and `nolangz/pixel2motion` for optional motion previews of generated static images.
3
+ description: Prepare an open-source repository for polished public publishing. Use when a user asks to publish, open-source, launch, polish, package, brand, or make a GitHub project presentable with a minimal project icon, social preview image, GitHub Pages landing page, standardized README, essential shields, CI/CD quality gates, release automation checks, and optional donation setup. Prefer the external `logo-generator` skill for icon generation when available, and `nolangz/pixel2motion` for optional motion previews of supported logos or simple artwork.
4
4
  license: MIT
5
5
  allowed-tools: Bash, Read, Write, Edit, WebSearch, WebFetch
6
6
  compatibility: Codex, Claude Code, Cursor, GitHub Copilot, Windsurf, Kiro, and other Agent Skills compatible tools. Requires a writable git repository; browser or image rendering tools are useful for visual validation.
@@ -44,12 +44,11 @@ Use this skill to audit whether an OSS repository is ready to publish, then help
44
44
  5. For existing usable pieces, do not propose replacement by default. Ask a change-oriented question only when useful, for example:
45
45
  - "You already have a terminal-style GitHub Pages site. Do you want to keep it or restyle it?"
46
46
  - "You already have an icon and social card. Do you want a refresh, or should I leave them as-is?"
47
- - "You already have a static social card. Do you want me to animate it with pixel2motion, or leave it static?"
48
47
  - "You already have release automation. Do you want me to audit only, or also tighten it?"
49
48
  6. Ask only for choices needed to fix missing or weak pieces:
50
49
  - If GitHub Pages is missing or weak, ask for style: `oldschool linux`, `terminal`, `modern`, `brutalist`, `glassmorphism`, `y2k`, `hacker`, or custom.
51
50
  - If donation wiring is missing, ask whether to enable it: `none`, `GitHub Sponsors`, `Ko-fi`, `Buy Me a Coffee`, `Open Collective`, `Thanks.dev`, or custom URL.
52
- - If the repo has a generated static social image or hero card, ask whether to animate it using `nolangz/pixel2motion`.
51
+ - If the repo has a generated logo or simple hero artwork supported by `nolangz/pixel2motion`, ask whether to animate it or leave it static.
53
52
  - If the repo has no clear product essence, ask for a one-sentence positioning statement.
54
53
  7. Implement only approved, missing, or weak work in this order:
55
54
  - OSS governance and support files
@@ -213,8 +212,8 @@ Use `magick` or another renderer when `rsvg-convert` is unavailable.
213
212
 
214
213
  Some repos benefit from a short animated preview. Keep this opt-in.
215
214
 
216
- - If you already generated a static image, ask: `Do you want me to animate the static image using pixel2motion?`
217
- - If the user says yes, use `nolangz/pixel2motion` as the optional dependency for logo animation, animated HTML demos, and GIF/video previews.
215
+ - After the user opts in during workflow step 6, animate only the approved generated logo or simple artwork.
216
+ - Use `nolangz/pixel2motion` as the optional dependency for that animation, animated HTML demos, and GIF/video previews.
218
217
  - Do not auto-animate by default.
219
218
 
220
219
  ## README Standard
@@ -11,6 +11,9 @@ import { readFileSync } from 'node:fs';
11
11
  const THRESHOLD = 1500;
12
12
  const OFFER_MARKER = /critical-thinking review/i; // the offer line must contain this; doubles as a loop guard
13
13
 
14
+ /**
15
+ * Exits the hook cleanly when conditions for offering a critique are not met.
16
+ */
14
17
  function bail() { process.exit(0); } // any uncertainty => do nothing, never block the user
15
18
 
16
19
  let raw = '';