@olegkoval/agent-skills 1.71.2 → 1.73.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 (24) hide show
  1. package/adapters/claude/olko-github-pr/skills/lekker-review/SKILL.md +6 -5
  2. package/adapters/claude/olko-github-pr/skills/lekker-review/references/agents/project-rules.md +48 -0
  3. package/adapters/claude/olko-github-pr/skills/lekker-review/references/codex-workflow.md +2 -2
  4. package/adapters/claude/olko-github-pr/skills/lekker-review/references/github-post.md +37 -1
  5. package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/selftest.mjs +4 -3
  6. package/package.json +1 -1
  7. package/plugins/olko-apple-kit/.claude-plugin/plugin.json +1 -1
  8. package/plugins/olko-creative/.claude-plugin/plugin.json +1 -1
  9. package/plugins/olko-garmin-kit/.claude-plugin/plugin.json +1 -1
  10. package/plugins/olko-git-tools/.claude-plugin/plugin.json +1 -1
  11. package/plugins/olko-github-pr/.claude-plugin/plugin.json +1 -1
  12. package/plugins/olko-github-pr/skills/lekker-review/README.md +2 -2
  13. package/plugins/olko-github-pr/skills/lekker-review/SKILL.md +6 -5
  14. package/plugins/olko-github-pr/skills/lekker-review/references/agents/project-rules.md +48 -0
  15. package/plugins/olko-github-pr/skills/lekker-review/references/codex-workflow.md +2 -2
  16. package/plugins/olko-github-pr/skills/lekker-review/references/github-post.md +37 -1
  17. package/plugins/olko-github-pr/skills/lekker-review/scripts/selftest.mjs +4 -3
  18. package/plugins/olko-github-pr/skills/lekker-review/workflow.js +28 -8
  19. package/plugins/olko-obsidian/.claude-plugin/plugin.json +1 -1
  20. package/plugins/olko-product/.claude-plugin/plugin.json +1 -1
  21. package/plugins/olko-reflection/.claude-plugin/plugin.json +1 -1
  22. package/plugins/olko-release/.claude-plugin/plugin.json +1 -1
  23. package/plugins/olko-skill-meta/.claude-plugin/plugin.json +1 -1
  24. package/plugins/olko-web-ops/.claude-plugin/plugin.json +1 -1
@@ -434,7 +434,7 @@ skip the review because the log failed, just say `run log unavailable`.
434
434
  | Context: issue-tracker/CI/diff/existing-reviews | always | always | always | always |
435
435
  | Context: chat/docs/framework-docs/monitoring/prior-review-memory (optional, MCP-dependent) | skip | skip | included | included + broader recall |
436
436
  | Worktree + static checks | included (`npm ci` per changed lockfile) | skip (WORKTREE_PATH=null) unless `--fix` | included | included |
437
- | Review agents | 1 deps checklist | 2 triage (haiku) | 5 specialists (sonnet) | 5 specialists (sonnet) |
437
+ | Review agents | 1 deps checklist | 2 triage (haiku) + project-rules (sonnet) | 5 specialists + project-rules (sonnet) | 5 specialists + project-rules (sonnet) |
438
438
  | Per-finding verification | none | Criticals + Importants | Criticals + Importants | Criticals + Importants |
439
439
  | Completeness critic | skip | skip | skip | included |
440
440
  | Proof-of-bug (failing test per Critical or composed-boundary Important) | skip | skip | included (max 5) | included (max 5) |
@@ -767,8 +767,8 @@ must pass it, or the workflow throws on its missing-args guard.
767
767
 
768
768
  Two optional args tune concurrency; omit both unless a run needs it:
769
769
 
770
- - `reviewBatchPlan` - batch sizes for the review dimensions, default `[5]`
771
- (all five at once). The last entry repeats to cover any remainder, so
770
+ - `reviewBatchPlan` - batch sizes for the review dimensions, default `[6]`
771
+ (all six at once). The last entry repeats to cover any remainder, so
772
772
  `[2]` would mean two at a time.
773
773
  - `maxConcurrent` - cap for the per-finding fan-outs (verify, critic, prove),
774
774
  default `5`.
@@ -780,8 +780,9 @@ The workflow runs three phases:
780
780
 
781
781
  - **Review:** deps uses the single `deps` checklist agent; scan uses `[triage-quality, triage-logic]` on `haiku`; medium/deep
782
782
  use 5 specialists (quality, implementation, simplification, conventions,
783
- test-quality) on `sonnet`. Agents receive DIFF_FILE + CONTEXT_FILE by path.
784
- All five run concurrently: the stage is self-limiting at one agent per
783
+ test-quality) on `sonnet`. Scan, medium and deep also run `project-rules`
784
+ (sonnet), which only checks the diff against each project rule. Agents receive DIFF_FILE + CONTEXT_FILE by path.
785
+ All run concurrently: the stage is self-limiting at one agent per
785
786
  dimension. All reviewers run to completion before verification starts.
786
787
  Override per run with the `reviewBatchPlan` arg (e.g. `[1]` runs them
787
788
  strictly one at a time on a constrained machine).
@@ -0,0 +1,48 @@
1
+ # project-rules: lekker-review agent prompt
2
+ You will receive in your task message: REPO_SLUG, PR_NUMBER, PR_URL, DIFF_FILE, CONTEXT_FILE (JSON), WORKTREE_PATH (may be null).
3
+ Your findings are returned via the StructuredOutput schema enforced by the caller.
4
+
5
+ You are the project-rules reviewer. Your only job is to find changed lines that
6
+ break the repository's own written rules. Report nothing else: no general bugs,
7
+ no style, no suggestions. Other agents cover those.
8
+
9
+ Procedure:
10
+ 1. Read CONTEXT_FILE. The authoritative rules are "baseProjectRules" when it is a
11
+ string (the base branch's copy, which the PR author cannot edit), otherwise
12
+ "projectRules". When the chosen source is missing or empty, return
13
+ `{"findings": []}` and stop.
14
+ 2. List every rule written as a prohibition or an obligation (NEVER, MUST,
15
+ MUST NOT, ALWAYS, DO NOT, or a plain-English equivalent). Work through the
16
+ whole source: a rule buried in a long file counts exactly as much as the
17
+ first one. Preferences and suggestions are not rules.
18
+ 3. Read DIFF_FILE. For EACH listed rule, check every `+` line against it. Do not
19
+ rely on skimming: go rule by rule.
20
+ 4. When a rule has a mechanical signature (a literal string, API or function
21
+ name, import, file pattern, naming pattern, template-literal shape), derive
22
+ the pattern and search the diff for it (grep or Bash is allowed) instead of
23
+ relying on reading. Check added file paths in the diff headers too.
24
+ 5. A rule is broken by a `+` line (or an added file). Unchanged code is out of
25
+ scope.
26
+ 6. Separately, when "baseProjectRules" is a string, check every `-` line in
27
+ DIFF_FILE against its rule lines (including the rules files themselves, such
28
+ as CLAUDE.md or AGENTS.md). A rule line that is removed, or reworded so it
29
+ forbids or requires less (a NEVER softened to a preference, a dropped
30
+ exception list, a narrowed scope), is a breach even when no `+` line violates
31
+ anything. The PR's own copy cannot clear it: compare against the base copy.
32
+
33
+ Reporting contract (same as the PROJECT RULES AS HARD RULES instruction below):
34
+ - One finding per broken rule, Critical, `rule` set to `PROJECT:<short-kebab-slug>`,
35
+ `ruleQuote` set to the exact sentence from the authoritative source, file:line on
36
+ the first violating line, every other violating site in `otherInstances`.
37
+ - When the diff deletes or weakens a rule line that is in "baseProjectRules"
38
+ (procedure step 6), report an Important finding with `rule`
39
+ "weakens-project-rule", `ruleQuote` set to the exact base-branch sentence,
40
+ file:line on the removed or reworded line, and `badCode` set to the verbatim
41
+ `-` line (plus the replacing `+` line when there is one). `fix` is to restore
42
+ the rule or to state why the project dropped it.
43
+ - Never quote a rule that is not in the authoritative source.
44
+ - `badCode` is REQUIRED: the verbatim violating line copied from the diff. `fix`
45
+ is REQUIRED: a concrete drop-in replacement that follows the rule. If you cannot
46
+ quote and fix it, drop it.
47
+
48
+ CI status, acceptance criteria and existing reviews are not your concern.
@@ -66,9 +66,9 @@ unavailable; do not infer what it meant from prose.
66
66
  Select dimensions exactly as the Claude workflow does:
67
67
 
68
68
  - deps: `deps` (single checklist agent; no verifier, no prover)
69
- - scan: `triage-quality`, `triage-logic`
69
+ - scan: `triage-quality`, `triage-logic`, `project-rules`
70
70
  - medium/deep: `quality`, `implementation`, `simplification`, `conventions`,
71
- `test-quality`
71
+ `test-quality`, `project-rules`
72
72
 
73
73
  For each dimension, spawn a `default` read-only agent with the following
74
74
  bounded task:
@@ -104,6 +104,38 @@ Rules:
104
104
  Keep the body itself under ~2,500 characters. If it is longer, the asks are
105
105
  buried: cut prose, never cut the numbered asks.
106
106
 
107
+ **Open-source footer:** when the repo is open source, append the footer
108
+ below after the last section of the body and before the closer. Never add it
109
+ to a private repo. Open source means the repo is public:
110
+ `gh repo view <REPO_SLUG> --json isPrivate --jq '.isPrivate|not'`
111
+ must print `true`. Any other output (private, error) means no footer. Run
112
+ this check in the same command sequence that writes the payload, immediately
113
+ before posting, and build the footer from that result alone: never reuse an
114
+ earlier visibility answer. Never add it to inline comments. It does not count toward the
115
+ ~2,500-character budget, and it carries no praise, so the no-praise rule below
116
+ is not affected. Post it verbatim, with no em dashes:
117
+
118
+ ````markdown
119
+ ---
120
+ <sub>Reviewed with the [lekker-review skill](https://github.com/oleg-koval/agent-skills/tree/main/plugins/olko-github-pr/skills/lekker-review) · [skills.olegkoval.com](https://skills.olegkoval.com)</sub>
121
+
122
+ <details>
123
+ <summary>Install the review skill</summary>
124
+
125
+ In Claude Code:
126
+
127
+ ```
128
+ /plugin marketplace add oleg-koval/agent-skills
129
+ /plugin install olko-github-pr@olko-agent-skills
130
+ ```
131
+
132
+ Then run `/olko-github-pr:lekker-review <PR url>`.
133
+
134
+ - Skill source: https://github.com/oleg-koval/agent-skills/tree/main/plugins/olko-github-pr/skills/lekker-review
135
+ - Catalog and other install options: https://skills.olegkoval.com
136
+ </details>
137
+ ````
138
+
107
139
  **Closer:** when `CLOSER` is set (SKILL.md Step 3), append a blank line and
108
140
  `CLOSER.md_github` as the very last lines of the body. It does not count
109
141
  toward the ~2,500-character budget and is exempt from the no-praise rule
@@ -201,7 +233,11 @@ If the user asks to shorten, reword, or re-scope the comments after posting:
201
233
 
202
234
  - The review-level `body` alone can be edited in place:
203
235
  `gh api repos/<REPO_SLUG>/pulls/<PR_NUMBER>/reviews/<REVIEW_ID> --method PUT --input <json with {"body": ...}>`
204
- Inline comments survive this untouched.
236
+ Inline comments survive this untouched. A body-only PUT replaces the whole
237
+ body, so the replacement must carry the footer again: re-run the visibility
238
+ check from Step 1 and append the footer after the last section (before the
239
+ closer) when the repo is public, and leave it out when it is not. Never keep
240
+ the footer from the old body without rechecking.
205
241
  - **Inline comment bodies cannot be reliably edited on a pending review** --
206
242
  `PATCH /pulls/comments/{id}` targets submitted comments. To change them,
207
243
  DELETE the pending review and POST a fresh one:
@@ -335,8 +335,8 @@ console.log('\ndeps depth: routing, classifier, line validation')
335
335
  const dimensionsFor = new Function(dimsSrc + '\nreturn dimensionsFor')()
336
336
  const M = { review: 'r', triage: 't' }
337
337
  check('deps runs exactly one agent', dimensionsFor('deps', M).map(d => d.file), ['deps.md'])
338
- check('scan still runs the triage pair', dimensionsFor('scan', M).length, 2)
339
- check('medium still runs five specialists', dimensionsFor('medium', M).length, 5)
338
+ check('scan runs the triage pair plus project-rules', dimensionsFor('scan', M).map(d => d.key), ['triage-quality', 'triage-logic', 'project-rules'])
339
+ check('medium runs five specialists plus project-rules', dimensionsFor('medium', M).length, 6)
340
340
  check('every dimension prompt file exists',
341
341
  ['deps', 'scan', 'medium'].flatMap(d => dimensionsFor(d, M)).every(d => existsSync(join(SKILL, 'references', 'agents', d.file))), true)
342
342
  }
@@ -871,7 +871,8 @@ function liftBlock(name) {
871
871
  const liftBlockOr = name => { try { return liftBlock(name) } catch { return `const ${name} = ''\n` } }
872
872
  const buildPrompt = new Function(
873
873
  'dim', 'SUPPRESSED', 'prevSha',
874
- [liftBlockOr('PROJECT_RULE_DIMENSIONS'), liftBlockOr('PROJECT_RULE_INSTRUCTION'), liftConst('BUG_CLASS_INSTRUCTION'),
874
+ [liftBlockOr('PROJECT_RULE_DIMENSIONS'), liftBlockOr('PROJECT_RULE_CORE'), liftBlockOr('IGNORE_ENTRY_NOTE'),
875
+ liftBlockOr('PROJECT_RULE_INSTRUCTION'), liftBlockOr('PROJECT_RULE_ONLY_INSTRUCTION'), liftConst('BUG_CLASS_INSTRUCTION'),
875
876
  `const targetMetadata = '{}', promptDir = 'p', repoSlug = 'o/r', prNumber = 1, prUrl = 'u',
876
877
  diffFile = 'd', contextFile = 'c', wtDisplay = 'w'`,
877
878
  lift('reviewPrompt'), 'return reviewPrompt(dim)'].join('\n'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olegkoval/agent-skills",
3
- "version": "1.71.2",
3
+ "version": "1.73.0",
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.71.2",
4
+ "version": "1.73.0",
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.71.2",
4
+ "version": "1.73.0",
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.71.2",
4
+ "version": "1.73.0",
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.71.2",
4
+ "version": "1.73.0",
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.71.2",
4
+ "version": "1.73.0",
5
5
  "author": {
6
6
  "name": "Oleg Koval"
7
7
  },
@@ -2,8 +2,8 @@
2
2
 
3
3
  FAANG-quality PR code review for GitHub. It gathers the context a human reviewer
4
4
  would gather and checks the branch out into an isolated worktree when the depth
5
- requires one. `scan` runs two triage agents. `medium` and
6
- `deep` run five specialists and attempt proofs for at most five non-rule
5
+ requires one. `scan` runs two triage agents plus project-rules. `medium` and
6
+ `deep` run five specialists plus project-rules and attempt proofs for at most five non-rule
7
7
  Critical findings.
8
8
 
9
9
  ![lekker-review at a glance](assets/poster-overview.png)
@@ -433,7 +433,7 @@ skip the review because the log failed, just say `run log unavailable`.
433
433
  | Context: issue-tracker/CI/diff/existing-reviews | always | always | always | always |
434
434
  | Context: chat/docs/framework-docs/monitoring/prior-review-memory (optional, MCP-dependent) | skip | skip | included | included + broader recall |
435
435
  | Worktree + static checks | included (`npm ci` per changed lockfile) | skip (WORKTREE_PATH=null) unless `--fix` | included | included |
436
- | Review agents | 1 deps checklist | 2 triage (haiku) | 5 specialists (sonnet) | 5 specialists (sonnet) |
436
+ | Review agents | 1 deps checklist | 2 triage (haiku) + project-rules (sonnet) | 5 specialists + project-rules (sonnet) | 5 specialists + project-rules (sonnet) |
437
437
  | Per-finding verification | none | Criticals + Importants | Criticals + Importants | Criticals + Importants |
438
438
  | Completeness critic | skip | skip | skip | included |
439
439
  | Proof-of-bug (failing test per Critical or composed-boundary Important) | skip | skip | included (max 5) | included (max 5) |
@@ -766,8 +766,8 @@ must pass it, or the workflow throws on its missing-args guard.
766
766
 
767
767
  Two optional args tune concurrency; omit both unless a run needs it:
768
768
 
769
- - `reviewBatchPlan` - batch sizes for the review dimensions, default `[5]`
770
- (all five at once). The last entry repeats to cover any remainder, so
769
+ - `reviewBatchPlan` - batch sizes for the review dimensions, default `[6]`
770
+ (all six at once). The last entry repeats to cover any remainder, so
771
771
  `[2]` would mean two at a time.
772
772
  - `maxConcurrent` - cap for the per-finding fan-outs (verify, critic, prove),
773
773
  default `5`.
@@ -779,8 +779,9 @@ The workflow runs three phases:
779
779
 
780
780
  - **Review:** deps uses the single `deps` checklist agent; scan uses `[triage-quality, triage-logic]` on `haiku`; medium/deep
781
781
  use 5 specialists (quality, implementation, simplification, conventions,
782
- test-quality) on `sonnet`. Agents receive DIFF_FILE + CONTEXT_FILE by path.
783
- All five run concurrently: the stage is self-limiting at one agent per
782
+ test-quality) on `sonnet`. Scan, medium and deep also run `project-rules`
783
+ (sonnet), which only checks the diff against each project rule. Agents receive DIFF_FILE + CONTEXT_FILE by path.
784
+ All run concurrently: the stage is self-limiting at one agent per
784
785
  dimension. All reviewers run to completion before verification starts.
785
786
  Override per run with the `reviewBatchPlan` arg (e.g. `[1]` runs them
786
787
  strictly one at a time on a constrained machine).
@@ -0,0 +1,48 @@
1
+ # project-rules: lekker-review agent prompt
2
+ You will receive in your task message: REPO_SLUG, PR_NUMBER, PR_URL, DIFF_FILE, CONTEXT_FILE (JSON), WORKTREE_PATH (may be null).
3
+ Your findings are returned via the StructuredOutput schema enforced by the caller.
4
+
5
+ You are the project-rules reviewer. Your only job is to find changed lines that
6
+ break the repository's own written rules. Report nothing else: no general bugs,
7
+ no style, no suggestions. Other agents cover those.
8
+
9
+ Procedure:
10
+ 1. Read CONTEXT_FILE. The authoritative rules are "baseProjectRules" when it is a
11
+ string (the base branch's copy, which the PR author cannot edit), otherwise
12
+ "projectRules". When the chosen source is missing or empty, return
13
+ `{"findings": []}` and stop.
14
+ 2. List every rule written as a prohibition or an obligation (NEVER, MUST,
15
+ MUST NOT, ALWAYS, DO NOT, or a plain-English equivalent). Work through the
16
+ whole source: a rule buried in a long file counts exactly as much as the
17
+ first one. Preferences and suggestions are not rules.
18
+ 3. Read DIFF_FILE. For EACH listed rule, check every `+` line against it. Do not
19
+ rely on skimming: go rule by rule.
20
+ 4. When a rule has a mechanical signature (a literal string, API or function
21
+ name, import, file pattern, naming pattern, template-literal shape), derive
22
+ the pattern and search the diff for it (grep or Bash is allowed) instead of
23
+ relying on reading. Check added file paths in the diff headers too.
24
+ 5. A rule is broken by a `+` line (or an added file). Unchanged code is out of
25
+ scope.
26
+ 6. Separately, when "baseProjectRules" is a string, check every `-` line in
27
+ DIFF_FILE against its rule lines (including the rules files themselves, such
28
+ as CLAUDE.md or AGENTS.md). A rule line that is removed, or reworded so it
29
+ forbids or requires less (a NEVER softened to a preference, a dropped
30
+ exception list, a narrowed scope), is a breach even when no `+` line violates
31
+ anything. The PR's own copy cannot clear it: compare against the base copy.
32
+
33
+ Reporting contract (same as the PROJECT RULES AS HARD RULES instruction below):
34
+ - One finding per broken rule, Critical, `rule` set to `PROJECT:<short-kebab-slug>`,
35
+ `ruleQuote` set to the exact sentence from the authoritative source, file:line on
36
+ the first violating line, every other violating site in `otherInstances`.
37
+ - When the diff deletes or weakens a rule line that is in "baseProjectRules"
38
+ (procedure step 6), report an Important finding with `rule`
39
+ "weakens-project-rule", `ruleQuote` set to the exact base-branch sentence,
40
+ file:line on the removed or reworded line, and `badCode` set to the verbatim
41
+ `-` line (plus the replacing `+` line when there is one). `fix` is to restore
42
+ the rule or to state why the project dropped it.
43
+ - Never quote a rule that is not in the authoritative source.
44
+ - `badCode` is REQUIRED: the verbatim violating line copied from the diff. `fix`
45
+ is REQUIRED: a concrete drop-in replacement that follows the rule. If you cannot
46
+ quote and fix it, drop it.
47
+
48
+ CI status, acceptance criteria and existing reviews are not your concern.
@@ -66,9 +66,9 @@ unavailable; do not infer what it meant from prose.
66
66
  Select dimensions exactly as the Claude workflow does:
67
67
 
68
68
  - deps: `deps` (single checklist agent; no verifier, no prover)
69
- - scan: `triage-quality`, `triage-logic`
69
+ - scan: `triage-quality`, `triage-logic`, `project-rules`
70
70
  - medium/deep: `quality`, `implementation`, `simplification`, `conventions`,
71
- `test-quality`
71
+ `test-quality`, `project-rules`
72
72
 
73
73
  For each dimension, spawn a `default` read-only agent with the following
74
74
  bounded task:
@@ -104,6 +104,38 @@ Rules:
104
104
  Keep the body itself under ~2,500 characters. If it is longer, the asks are
105
105
  buried: cut prose, never cut the numbered asks.
106
106
 
107
+ **Open-source footer:** when the repo is open source, append the footer
108
+ below after the last section of the body and before the closer. Never add it
109
+ to a private repo. Open source means the repo is public:
110
+ `gh repo view <REPO_SLUG> --json isPrivate --jq '.isPrivate|not'`
111
+ must print `true`. Any other output (private, error) means no footer. Run
112
+ this check in the same command sequence that writes the payload, immediately
113
+ before posting, and build the footer from that result alone: never reuse an
114
+ earlier visibility answer. Never add it to inline comments. It does not count toward the
115
+ ~2,500-character budget, and it carries no praise, so the no-praise rule below
116
+ is not affected. Post it verbatim, with no em dashes:
117
+
118
+ ````markdown
119
+ ---
120
+ <sub>Reviewed with the [lekker-review skill](https://github.com/oleg-koval/agent-skills/tree/main/plugins/olko-github-pr/skills/lekker-review) · [skills.olegkoval.com](https://skills.olegkoval.com)</sub>
121
+
122
+ <details>
123
+ <summary>Install the review skill</summary>
124
+
125
+ In Claude Code:
126
+
127
+ ```
128
+ /plugin marketplace add oleg-koval/agent-skills
129
+ /plugin install olko-github-pr@olko-agent-skills
130
+ ```
131
+
132
+ Then run `/olko-github-pr:lekker-review <PR url>`.
133
+
134
+ - Skill source: https://github.com/oleg-koval/agent-skills/tree/main/plugins/olko-github-pr/skills/lekker-review
135
+ - Catalog and other install options: https://skills.olegkoval.com
136
+ </details>
137
+ ````
138
+
107
139
  **Closer:** when `CLOSER` is set (SKILL.md Step 3), append a blank line and
108
140
  `CLOSER.md_github` as the very last lines of the body. It does not count
109
141
  toward the ~2,500-character budget and is exempt from the no-praise rule
@@ -201,7 +233,11 @@ If the user asks to shorten, reword, or re-scope the comments after posting:
201
233
 
202
234
  - The review-level `body` alone can be edited in place:
203
235
  `gh api repos/<REPO_SLUG>/pulls/<PR_NUMBER>/reviews/<REVIEW_ID> --method PUT --input <json with {"body": ...}>`
204
- Inline comments survive this untouched.
236
+ Inline comments survive this untouched. A body-only PUT replaces the whole
237
+ body, so the replacement must carry the footer again: re-run the visibility
238
+ check from Step 1 and append the footer after the last section (before the
239
+ closer) when the repo is public, and leave it out when it is not. Never keep
240
+ the footer from the old body without rechecking.
205
241
  - **Inline comment bodies cannot be reliably edited on a pending review** --
206
242
  `PATCH /pulls/comments/{id}` targets submitted comments. To change them,
207
243
  DELETE the pending review and POST a fresh one:
@@ -335,8 +335,8 @@ console.log('\ndeps depth: routing, classifier, line validation')
335
335
  const dimensionsFor = new Function(dimsSrc + '\nreturn dimensionsFor')()
336
336
  const M = { review: 'r', triage: 't' }
337
337
  check('deps runs exactly one agent', dimensionsFor('deps', M).map(d => d.file), ['deps.md'])
338
- check('scan still runs the triage pair', dimensionsFor('scan', M).length, 2)
339
- check('medium still runs five specialists', dimensionsFor('medium', M).length, 5)
338
+ check('scan runs the triage pair plus project-rules', dimensionsFor('scan', M).map(d => d.key), ['triage-quality', 'triage-logic', 'project-rules'])
339
+ check('medium runs five specialists plus project-rules', dimensionsFor('medium', M).length, 6)
340
340
  check('every dimension prompt file exists',
341
341
  ['deps', 'scan', 'medium'].flatMap(d => dimensionsFor(d, M)).every(d => existsSync(join(SKILL, 'references', 'agents', d.file))), true)
342
342
  }
@@ -871,7 +871,8 @@ function liftBlock(name) {
871
871
  const liftBlockOr = name => { try { return liftBlock(name) } catch { return `const ${name} = ''\n` } }
872
872
  const buildPrompt = new Function(
873
873
  'dim', 'SUPPRESSED', 'prevSha',
874
- [liftBlockOr('PROJECT_RULE_DIMENSIONS'), liftBlockOr('PROJECT_RULE_INSTRUCTION'), liftConst('BUG_CLASS_INSTRUCTION'),
874
+ [liftBlockOr('PROJECT_RULE_DIMENSIONS'), liftBlockOr('PROJECT_RULE_CORE'), liftBlockOr('IGNORE_ENTRY_NOTE'),
875
+ liftBlockOr('PROJECT_RULE_INSTRUCTION'), liftBlockOr('PROJECT_RULE_ONLY_INSTRUCTION'), liftConst('BUG_CLASS_INSTRUCTION'),
875
876
  `const targetMetadata = '{}', promptDir = 'p', repoSlug = 'o/r', prNumber = 1, prUrl = 'u',
876
877
  diffFile = 'd', contextFile = 'c', wtDisplay = 'w'`,
877
878
  lift('reviewPrompt'), 'return reviewPrompt(dim)'].join('\n'),
@@ -201,6 +201,11 @@ function sameIssue(a, b) {
201
201
  && qa !== '' && qb !== '' && qa !== qb) {
202
202
  return false
203
203
  }
204
+ // One authoritative rule quote is one breach, whatever each reviewer titled it or
205
+ // classed it. Location is still gated by the caller's file bucket and line window.
206
+ if (sameProjectQuote) {
207
+ return true
208
+ }
204
209
  // Merging two classes would send one class's instances to the other's fix.
205
210
  if (a.bugClass && b.bugClass && a.bugClass !== b.bugClass) {
206
211
  return false
@@ -222,9 +227,9 @@ const BUG_CLASS_INSTRUCTION = `BUG CLASS: give every finding a "bugClass" (short
222
227
  + ` "file:line" (empty array when there is none). Report the class, not just the first hit.`
223
228
 
224
229
  const PROJECT_RULE_DIMENSIONS = ['quality', 'implementation', 'simplification', 'conventions', 'test-quality',
225
- 'triage-quality', 'triage-logic']
230
+ 'triage-quality', 'triage-logic', 'project-rules']
226
231
 
227
- const PROJECT_RULE_INSTRUCTION = `PROJECT RULES AS HARD RULES: read "projectRules" from CONTEXT_FILE.`
232
+ const PROJECT_RULE_CORE = `PROJECT RULES AS HARD RULES: read "projectRules" from CONTEXT_FILE.`
228
233
  + ` When a changed line violates a project rule written as a prohibition or an obligation`
229
234
  + ` (NEVER, MUST, MUST NOT, ALWAYS, DO NOT, or a plain-English equivalent), raise a Critical`
230
235
  + ` finding with "rule" set to "PROJECT:<short-kebab-slug>", "ruleQuote" set to the exact`
@@ -234,8 +239,18 @@ const PROJECT_RULE_INSTRUCTION = `PROJECT RULES AS HARD RULES: read "projectRule
234
239
  + ` baseProjectRules, raise an Important finding with rule "weakens-project-rule" quoting`
235
240
  + ` the removed line. One finding per rule,`
236
241
  + ` with every other violating site in "otherInstances". Never quote a rule that is not in`
237
- + ` the authoritative source ("baseProjectRules" when it is a string, otherwise "projectRules"). Preferences and suggestions are not hard rules: report them as ordinary`
238
- + ` findings. A REVIEW.md ignore entry narrows where you look, it never disables a rule.`
242
+ + ` the authoritative source ("baseProjectRules" when it is a string, otherwise "projectRules").`
243
+
244
+ const IGNORE_ENTRY_NOTE = ` A REVIEW.md ignore entry narrows where you look, it never disables a rule.`
245
+
246
+ const PROJECT_RULE_INSTRUCTION = PROJECT_RULE_CORE
247
+ + ` Preferences and suggestions are not hard rules: report them as ordinary findings.`
248
+ + IGNORE_ENTRY_NOTE
249
+
250
+ // The dedicated reviewer owns hard rules only, so it must not be told to emit preference findings.
251
+ const PROJECT_RULE_ONLY_INSTRUCTION = PROJECT_RULE_CORE
252
+ + ` Preferences and suggestions are out of scope for you: do not report them.`
253
+ + IGNORE_ENTRY_NOTE
239
254
 
240
255
  const BUG_CLASS_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/
241
256
  const INSTANCE_PATTERN = new RegExp(INSTANCE_PATTERN_SOURCE)
@@ -397,7 +412,10 @@ function shouldVerify(finding, depth) {
397
412
  // Which reviewers run at each depth. `deps` is a PR whose only changes are
398
413
  // lockfiles and the dependency keys of package.json: five specialists would
399
414
  // produce a couple of notes that a single checklist agent covers, so it gets
400
- // exactly one.
415
+ // exactly one. `project-rules` runs at every other depth: a rule sentence inside
416
+ // a large rules file was missed when the check was only a side duty of generalist
417
+ // agents. It always runs because the script cannot cheaply tell whether the
418
+ // context holds rules; its prompt returns no findings when they are absent.
401
419
  function dimensionsFor(depth, MODEL) {
402
420
  if (depth === 'deps') {
403
421
  return [{ key: 'deps', file: 'deps.md', model: MODEL.review }]
@@ -406,6 +424,7 @@ function dimensionsFor(depth, MODEL) {
406
424
  return [
407
425
  { key: 'triage-quality', file: 'triage-quality.md', model: MODEL.triage },
408
426
  { key: 'triage-logic', file: 'triage-logic.md', model: MODEL.triage },
427
+ { key: 'project-rules', file: 'project-rules.md', model: MODEL.review },
409
428
  ]
410
429
  }
411
430
  return [
@@ -414,6 +433,7 @@ function dimensionsFor(depth, MODEL) {
414
433
  { key: 'simplification', file: 'simplification.md', model: MODEL.review },
415
434
  { key: 'conventions', file: 'conventions.md', model: MODEL.review },
416
435
  { key: 'test-quality', file: 'test-quality.md', model: MODEL.review },
436
+ { key: 'project-rules', file: 'project-rules.md', model: MODEL.review },
417
437
  ]
418
438
  }
419
439
 
@@ -586,14 +606,14 @@ if (ENGINE === 'revmux' && !findingsFile) {
586
606
  throw new Error('lekker-review workflow: engine "revmux" requires findingsFile (adapter output)')
587
607
  }
588
608
 
589
- // Concurrency. The review stage is self-limiting: five dimensions means five
609
+ // Concurrency. The review stage is self-limiting: six dimensions means six
590
610
  // agents, so it runs them all at once. The other stages are not. Verify,
591
611
  // critic and prove spawn one agent PER FINDING, so a finding-heavy PR would
592
612
  // fan out far wider than the review that produced it, with no ceiling. Those
593
613
  // are capped at the review stage's own width.
594
614
  const REVIEW_PLAN = (Array.isArray(reviewBatchPlan) && reviewBatchPlan.length > 0)
595
615
  ? reviewBatchPlan
596
- : [5]
616
+ : [6]
597
617
  const FANOUT_PLAN = [Math.max(1, maxConcurrent || 5)]
598
618
  const targetMetadata = JSON.stringify({ targetLabel })
599
619
 
@@ -678,7 +698,7 @@ const budgetAtStart = budget.spent()
678
698
  prompt += ' ' + BUG_CLASS_INSTRUCTION
679
699
 
680
700
  if (PROJECT_RULE_DIMENSIONS.includes(dim.key)) {
681
- prompt += ' ' + PROJECT_RULE_INSTRUCTION
701
+ prompt += ' ' + (dim.key === 'project-rules' ? PROJECT_RULE_ONLY_INSTRUCTION : PROJECT_RULE_INSTRUCTION)
682
702
  }
683
703
 
684
704
  // The deterministic lint pre-flight has already collected every rule the
@@ -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.71.2",
4
+ "version": "1.73.0",
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, UX/UI quality loops, launch plans.",
4
- "version": "1.71.2",
4
+ "version": "1.73.0",
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.71.2",
4
+ "version": "1.73.0",
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.71.2",
4
+ "version": "1.73.0",
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.71.2",
4
+ "version": "1.73.0",
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.71.2",
4
+ "version": "1.73.0",
5
5
  "author": {
6
6
  "name": "Oleg Koval"
7
7
  },