@olegkoval/agent-skills 1.60.0 → 1.62.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 (223) hide show
  1. package/.github/copilot-instructions.md +2 -0
  2. package/.github/prompts/carousel-maker.prompt.md +102 -0
  3. package/.github/prompts/shorts-animator.prompt.md +103 -0
  4. package/.kiro/steering/carousel-maker.md +101 -0
  5. package/.kiro/steering/shorts-animator.md +102 -0
  6. package/.windsurf/rules/carousel-maker.md +100 -0
  7. package/.windsurf/rules/shorts-animator.md +101 -0
  8. package/README.md +4 -2
  9. package/adapters/claude/olko-creative/skills/carousel-maker/SKILL.md +112 -0
  10. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
  11. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
  12. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
  13. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
  14. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
  15. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
  16. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
  17. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
  18. package/adapters/claude/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
  19. package/adapters/claude/olko-creative/skills/shorts-animator/SKILL.md +113 -0
  20. package/adapters/claude/olko-creative/skills/shorts-animator/references/kit.md +59 -0
  21. package/adapters/claude/olko-creative/skills/shorts-animator/references/looks.md +32 -0
  22. package/adapters/claude/olko-creative/skills/shorts-animator/references/writing.md +9 -0
  23. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
  24. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
  25. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
  26. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
  27. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
  28. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
  29. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
  30. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
  31. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
  32. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
  33. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
  34. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
  35. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
  36. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
  37. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
  38. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
  39. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
  40. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
  41. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
  42. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
  43. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
  44. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
  45. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
  46. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
  47. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
  48. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
  49. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
  50. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
  51. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
  52. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
  53. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
  54. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
  55. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
  56. package/adapters/claude/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
  57. package/adapters/claude/olko-github-pr/skills/lekker-review/SKILL.md +30 -0
  58. package/adapters/claude/olko-github-pr/skills/lekker-review/references/context-gathering.md +3 -0
  59. package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/finding-state.mjs +207 -0
  60. package/adapters/codex/olko-creative/README.md +2 -0
  61. package/adapters/cursor/olko-creative/skills/carousel-maker/SKILL.md +112 -0
  62. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
  63. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
  64. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
  65. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
  66. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
  67. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
  68. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
  69. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
  70. package/adapters/cursor/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
  71. package/adapters/cursor/olko-creative/skills/shorts-animator/SKILL.md +113 -0
  72. package/adapters/cursor/olko-creative/skills/shorts-animator/references/kit.md +59 -0
  73. package/adapters/cursor/olko-creative/skills/shorts-animator/references/looks.md +32 -0
  74. package/adapters/cursor/olko-creative/skills/shorts-animator/references/writing.md +9 -0
  75. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
  76. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
  77. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
  78. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
  79. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
  80. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
  81. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
  82. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
  83. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
  84. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
  85. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
  86. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
  87. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
  88. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
  89. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
  90. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
  91. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
  92. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
  93. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
  94. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
  95. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
  96. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
  97. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
  98. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
  99. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
  100. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
  101. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
  102. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
  103. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
  104. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
  105. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
  106. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
  107. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
  108. package/adapters/cursor/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
  109. package/adapters/grok/olko-creative/skills/carousel-maker/SKILL.md +112 -0
  110. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
  111. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
  112. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
  113. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
  114. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
  115. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
  116. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
  117. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
  118. package/adapters/grok/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
  119. package/adapters/grok/olko-creative/skills/shorts-animator/SKILL.md +113 -0
  120. package/adapters/grok/olko-creative/skills/shorts-animator/references/kit.md +59 -0
  121. package/adapters/grok/olko-creative/skills/shorts-animator/references/looks.md +32 -0
  122. package/adapters/grok/olko-creative/skills/shorts-animator/references/writing.md +9 -0
  123. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
  124. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
  125. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
  126. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
  127. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
  128. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
  129. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
  130. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
  131. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
  132. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
  133. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
  134. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
  135. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
  136. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
  137. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
  138. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
  139. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
  140. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
  141. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
  142. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
  143. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
  144. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
  145. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
  146. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
  147. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
  148. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
  149. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
  150. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
  151. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
  152. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
  153. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
  154. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
  155. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
  156. package/adapters/grok/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
  157. package/adapters/hermes/olko-creative/README.md +2 -0
  158. package/catalog/skills.json +46 -0
  159. package/package.json +1 -1
  160. package/plugins/olko-apple-kit/.claude-plugin/plugin.json +1 -1
  161. package/plugins/olko-creative/.claude-plugin/plugin.json +1 -1
  162. package/plugins/olko-creative/skills/carousel-maker/SKILL.md +111 -0
  163. package/plugins/olko-creative/skills/carousel-maker/scripts/cm.ts +220 -0
  164. package/plugins/olko-creative/skills/carousel-maker/scripts/domain/check.ts +68 -0
  165. package/plugins/olko-creative/skills/carousel-maker/scripts/domain/layout.ts +75 -0
  166. package/plugins/olko-creative/skills/carousel-maker/scripts/domain/schema.ts +54 -0
  167. package/plugins/olko-creative/skills/carousel-maker/scripts/domain/types.ts +33 -0
  168. package/plugins/olko-creative/skills/carousel-maker/scripts/edge/io.ts +87 -0
  169. package/plugins/olko-creative/skills/carousel-maker/scripts/package.json +4 -0
  170. package/plugins/olko-creative/skills/carousel-maker/scripts/test/domain.test.ts +99 -0
  171. package/plugins/olko-creative/skills/carousel-maker/scripts/tsconfig.json +16 -0
  172. package/plugins/olko-creative/skills/shorts-animator/SKILL.md +112 -0
  173. package/plugins/olko-creative/skills/shorts-animator/references/kit.md +59 -0
  174. package/plugins/olko-creative/skills/shorts-animator/references/looks.md +32 -0
  175. package/plugins/olko-creative/skills/shorts-animator/references/writing.md +9 -0
  176. package/plugins/olko-creative/skills/shorts-animator/scripts/audio/compose.ts +290 -0
  177. package/plugins/olko-creative/skills/shorts-animator/scripts/audio/dsp.ts +193 -0
  178. package/plugins/olko-creative/skills/shorts-animator/scripts/audio/instruments.ts +185 -0
  179. package/plugins/olko-creative/skills/shorts-animator/scripts/audio/master.ts +55 -0
  180. package/plugins/olko-creative/skills/shorts-animator/scripts/audio/soundtrack.ts +119 -0
  181. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/captions.ts +35 -0
  182. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/check.ts +96 -0
  183. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/look.ts +26 -0
  184. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/patch.ts +38 -0
  185. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/schema.ts +81 -0
  186. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/timeline.ts +75 -0
  187. package/plugins/olko-creative/skills/shorts-animator/scripts/domain/types.ts +91 -0
  188. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/chrome.ts +169 -0
  189. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/doctor.ts +52 -0
  190. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/media.ts +105 -0
  191. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/page.ts +53 -0
  192. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/probe.ts +75 -0
  193. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/sound.ts +46 -0
  194. package/plugins/olko-creative/skills/shorts-animator/scripts/edge/voice.ts +183 -0
  195. package/plugins/olko-creative/skills/shorts-animator/scripts/looks/gouache.json +7 -0
  196. package/plugins/olko-creative/skills/shorts-animator/scripts/looks/ink.json +6 -0
  197. package/plugins/olko-creative/skills/shorts-animator/scripts/looks/oil.json +7 -0
  198. package/plugins/olko-creative/skills/shorts-animator/scripts/looks/pastel.json +7 -0
  199. package/plugins/olko-creative/skills/shorts-animator/scripts/looks/watercolor.json +6 -0
  200. package/plugins/olko-creative/skills/shorts-animator/scripts/package.json +4 -0
  201. package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/globals.d.ts +199 -0
  202. package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/kit.ts +856 -0
  203. package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/paint.ts +268 -0
  204. package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/stage.ts +181 -0
  205. package/plugins/olko-creative/skills/shorts-animator/scripts/runtime/tsconfig.json +12 -0
  206. package/plugins/olko-creative/skills/shorts-animator/scripts/sa.ts +262 -0
  207. package/plugins/olko-creative/skills/shorts-animator/scripts/test/domain.test.ts +107 -0
  208. package/plugins/olko-creative/skills/shorts-animator/scripts/tsconfig.json +16 -0
  209. package/plugins/olko-creative/skills/shorts-animator/scripts/tts.py +20 -0
  210. package/plugins/olko-garmin-kit/.claude-plugin/plugin.json +1 -1
  211. package/plugins/olko-git-tools/.claude-plugin/plugin.json +1 -1
  212. package/plugins/olko-github-pr/.claude-plugin/plugin.json +1 -1
  213. package/plugins/olko-github-pr/skills/lekker-review/SKILL.md +30 -0
  214. package/plugins/olko-github-pr/skills/lekker-review/references/context-gathering.md +3 -0
  215. package/plugins/olko-github-pr/skills/lekker-review/scripts/finding-state.mjs +207 -0
  216. package/plugins/olko-github-pr/skills/lekker-review/workflow.js +46 -3
  217. package/plugins/olko-obsidian/.claude-plugin/plugin.json +1 -1
  218. package/plugins/olko-product/.claude-plugin/plugin.json +1 -1
  219. package/plugins/olko-reflection/.claude-plugin/plugin.json +1 -1
  220. package/plugins/olko-release/.claude-plugin/plugin.json +1 -1
  221. package/plugins/olko-skill-meta/.claude-plugin/plugin.json +1 -1
  222. package/plugins/olko-web-ops/.claude-plugin/plugin.json +1 -1
  223. package/scripts/lib/catalog.mjs +2 -1
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env node
2
+ // Per-PR finding state for lekker-review, so re-reviews converge.
3
+ //
4
+ // A re-review used to know only the last head sha and dedupe by file:line, so a
5
+ // finding the author had already rejected came back every round. This keeps one
6
+ // small JSON file per PR next to the run ledger (${LEKKER_STATE_DIR:-
7
+ // ~/.local/state/lekker-review}/findings/) holding each finding's status and the
8
+ // number of review cycles.
9
+ //
10
+ // Statuses: open | fixed | rejected | intentional. A finding is
11
+ // fixed when GitHub marks its thread outdated (the line changed)
12
+ // intentional when the author replied "by design" and the code did not change
13
+ // rejected when the author replied with another dismissal, or the thread
14
+ // was resolved without a code change and the PR author is known
15
+ //
16
+ // Usage:
17
+ // node finding-state.mjs begin --repo <slug> --target <pr-N> [--threads <threads.json>]
18
+ // Folds thread outcomes into the stored statuses, counts the cycle, and
19
+ // prints {cycle, rethink, suppressed, recurringClasses} for workflow.js.
20
+ // node finding-state.mjs record --repo <slug> --target <pr-N> --findings <findings.json>
21
+ // Stores this run's findings as open (an already-known finding keeps its status).
22
+ // node finding-state.mjs show --repo <slug> --target <pr-N>
23
+ //
24
+ // threads.json: { prAuthor, threads: [{ path, line, title?, body, isResolved, isOutdated,
25
+ // replies: [{ author, body }] }] }, where `body` is lekker's comment and only
26
+ // replies by `prAuthor` count as the author's answer. A bare array is accepted
27
+ // when the author is unknown; it can then only yield fixed (outdated) threads.
28
+ // When title is absent, the first nonempty comment line supplies the title.
29
+
30
+ import { createHash, randomUUID } from 'node:crypto';
31
+ import { readFileSync, writeFileSync, mkdirSync, renameSync, rmSync } from 'node:fs';
32
+ import { homedir } from 'node:os';
33
+ import { dirname, join } from 'node:path';
34
+ import { fileURLToPath } from 'node:url';
35
+
36
+ export const RETHINK_CYCLE = 4;
37
+ const INTENT = /\b(intentional(ly)?|by design|on purpose|deliberate(ly)?|as designed|expected behaviou?r)\b/i;
38
+ const DISMISS = /\b(won'?t fix|wontfix|not a (bug|problem|concern)|false positive|disagree|n\/a|not applicable|out of scope|ignor(e|ing)|nit)\b/i;
39
+
40
+ export function titleTokens(title) {
41
+ return (String(title || '').toLowerCase().match(/[a-z0-9]+/g) || []).filter((w) => w.length > 2);
42
+ }
43
+
44
+ export function sameIssue(a, b) {
45
+ const normalize = (title) => String(title || '').trim().replace(/\s+/g, ' ').toLowerCase();
46
+ const title = normalize(a.title);
47
+ return title !== '' && title === normalize(b.title);
48
+ }
49
+
50
+ export function sameFinding(a, b) {
51
+ // Status reuse requires the same anchor and title; word overlap is only a
52
+ // class signal, never proof that two findings identify the same issue.
53
+ return Boolean(a.file) && a.file === b.file && a.line === b.line && sameIssue(a, b);
54
+ }
55
+
56
+ // Group key for "the same kind of finding came back again".
57
+ export function findingClass(f) {
58
+ return f.bugClass || titleTokens(f.title).slice(0, 4).sort().join('-') || 'unclassified';
59
+ }
60
+
61
+ const NEGATOR = /\b(not|no|never|nor|isn'?t|aren'?t|wasn'?t|don'?t|doesn'?t|won'?t|cannot)\b/i;
62
+
63
+ // Check only the three words before each keyword in its clause for negation.
64
+ function mentions(pattern, text) {
65
+ return [...text.matchAll(new RegExp(pattern.source, 'gi'))]
66
+ .some((match) => {
67
+ const before = text.slice(0, match.index).split(/[.!?;:,\n-]/).pop();
68
+ return !NEGATOR.test(before.trim().split(/\s+/).slice(-3).join(' '));
69
+ });
70
+ }
71
+
72
+ export function classifyThread(thread, prAuthor) {
73
+ const authorReplies = (thread.replies || []).filter((r) => r.author && r.author === prAuthor);
74
+ const text = authorReplies.map((r) => r.body).join('\n');
75
+ if (thread.isOutdated) return 'fixed';
76
+ if (mentions(INTENT, text)) return 'intentional';
77
+ if (mentions(DISMISS, text)) return 'rejected';
78
+ if (thread.isResolved && prAuthor) return 'rejected';
79
+ return 'open';
80
+ }
81
+
82
+ export function applyThreads(findings, threads, prAuthor) {
83
+ return findings.map((f) => {
84
+ const thread = threads.find((t) => {
85
+ const title = t.title?.trim() || String(t.body || '').split(/\r?\n/).find((line) => line.trim())
86
+ ?.trim().replace(/^#{1,6}\s+/, '').replace(/^(\*\*|__)(.*?)\1[.:]?$/, '$2');
87
+ return sameFinding(f, { file: t.path, line: t.line, title });
88
+ });
89
+ if (!thread) return f;
90
+ const status = classifyThread(thread, prAuthor);
91
+ return f.status === status ? f : { ...f, status };
92
+ });
93
+ }
94
+
95
+ export function mergeFindings(prior, found, cycle) {
96
+ const added = found
97
+ .filter((f) => !prior.some((p) => sameFinding(p, f)))
98
+ .map((f) => ({ file: f.file, line: f.line, title: f.title, class: findingClass(f), status: 'open', cycles: [cycle] }));
99
+ const seen = prior.map((p) => (found.some((f) => sameFinding(p, f)) && !p.cycles.includes(cycle)
100
+ ? { ...p, cycles: [...p.cycles, cycle] }
101
+ : p));
102
+ return seen.concat(added);
103
+ }
104
+
105
+ export function recurringClasses(findings) {
106
+ const byClass = findings.reduce((acc, f) => {
107
+ const cycles = new Set([...(acc[f.class] || []), ...f.cycles]);
108
+ return { ...acc, [f.class]: [...cycles] };
109
+ }, {});
110
+ return Object.entries(byClass)
111
+ .filter(([, cycles]) => cycles.length >= 2)
112
+ .map(([cls, cycles]) => ({ class: cls, cycles: cycles.length }))
113
+ .sort((a, b) => b.cycles - a.cycles);
114
+ }
115
+
116
+ export function suppressedFindings(findings) {
117
+ return findings
118
+ .filter((f) => f.status === 'rejected' || f.status === 'intentional')
119
+ .map(({ file, line, title, status }) => ({ file, line, title, status }));
120
+ }
121
+
122
+ // A run id makes begin idempotent: a retry of the same run reuses its cycle instead of inflating the count.
123
+ export function begin(state, threads, prAuthor, run = null) {
124
+ const findings = applyThreads(state.findings, threads, prAuthor);
125
+ const known = run === null ? undefined : state.runs?.[run];
126
+ const cycle = known ?? state.cycle + 1;
127
+ const runs = run === null || known !== undefined ? state.runs : { ...state.runs, [run]: cycle };
128
+ return {
129
+ state: { ...state, cycle: Math.max(state.cycle, cycle), findings, ...(runs && { runs }) },
130
+ summary: {
131
+ cycle,
132
+ rethink: cycle >= RETHINK_CYCLE,
133
+ suppressed: suppressedFindings(findings),
134
+ recurringClasses: recurringClasses(findings),
135
+ },
136
+ };
137
+ }
138
+
139
+ const STATE_DIR = process.env.LEKKER_STATE_DIR || join(homedir(), '.local', 'state', 'lekker-review');
140
+ // Hash the tuple so punctuation, case and repo/target boundaries stay distinct.
141
+ const statePath = (repo, target) => join(STATE_DIR, 'findings',
142
+ `${createHash('sha256').update(JSON.stringify([repo, target])).digest('hex')}.json`);
143
+
144
+ function load(path) {
145
+ let raw;
146
+ try {
147
+ raw = readFileSync(path, 'utf8');
148
+ } catch (error) {
149
+ if (error.code === 'ENOENT') return { cycle: 0, findings: [] };
150
+ throw error;
151
+ }
152
+ const state = JSON.parse(raw);
153
+ if (!state || typeof state !== 'object' || Array.isArray(state)
154
+ || !Number.isSafeInteger(state.cycle) || state.cycle < 0 || !Array.isArray(state.findings)) {
155
+ throw new Error(`Invalid finding state: ${path}`);
156
+ }
157
+ if (state.runs !== undefined && (!state.runs || typeof state.runs !== 'object' || Array.isArray(state.runs)
158
+ || !Object.values(state.runs).every((c) => Number.isSafeInteger(c) && c >= 1))) {
159
+ throw new Error(`Invalid finding state runs: ${path}`);
160
+ }
161
+ return state;
162
+ }
163
+
164
+ function save(path, state) {
165
+ mkdirSync(dirname(path), { recursive: true });
166
+ const temporary = `${path}.${randomUUID()}.tmp`;
167
+ try {
168
+ writeFileSync(temporary, JSON.stringify(state, null, 2) + '\n', { flag: 'wx', mode: 0o600 });
169
+ renameSync(temporary, path);
170
+ } finally {
171
+ rmSync(temporary, { force: true });
172
+ }
173
+ }
174
+
175
+ function main(argv) {
176
+ const flag = (name) => { const i = argv.indexOf('--' + name); return i === -1 ? null : argv[i + 1]; };
177
+ const repo = flag('repo');
178
+ const target = flag('target');
179
+ if (!repo || !target) { console.log(JSON.stringify({ error: '--repo and --target are required' })); process.exit(2); }
180
+ const path = statePath(repo, target);
181
+ const state = load(path);
182
+ const cmd = argv[0];
183
+ if (cmd === 'begin') {
184
+ const raw = flag('threads') ? JSON.parse(readFileSync(flag('threads'), 'utf8')) : [];
185
+ const threads = Array.isArray(raw) ? raw : raw.threads || [];
186
+ const prAuthor = Array.isArray(raw) ? null : raw.prAuthor || null;
187
+ const next = begin(state, threads, prAuthor, flag('run') ?? null);
188
+ save(path, next.state);
189
+ console.log(JSON.stringify(next.summary));
190
+ } else if (cmd === 'record') {
191
+ const raw = JSON.parse(readFileSync(flag('findings'), 'utf8'));
192
+ const found = Array.isArray(raw) ? raw : raw.findings || [];
193
+ const run = flag('run') ?? null;
194
+ // A late record for an earlier run keeps that run's cycle; the current cycle stays as it was.
195
+ const cycle = run === null ? Math.max(state.cycle, 1) : state.runs?.[run];
196
+ if (cycle === undefined) { console.log(JSON.stringify({ error: `unknown --run ${run}; call begin --run first` })); process.exit(2); }
197
+ save(path, { ...state, cycle: Math.max(state.cycle, 1), findings: mergeFindings(state.findings, found, cycle) });
198
+ console.log(JSON.stringify({ recorded: found.length, cycle }));
199
+ } else if (cmd === 'show') {
200
+ console.log(JSON.stringify(state, null, 2));
201
+ } else {
202
+ console.log(JSON.stringify({ error: 'usage: begin|record|show' }));
203
+ process.exit(2);
204
+ }
205
+ }
206
+
207
+ if (process.argv[1] === fileURLToPath(import.meta.url)) main(process.argv.slice(2));
@@ -145,6 +145,8 @@ function isHardRule(finding) {
145
145
  return typeof finding.rule === 'string' && finding.rule.trim().length > 0
146
146
  }
147
147
 
148
+ const RETHINK_CYCLE = 4
149
+
148
150
  const SEVERITY_RANK = { critical: 3, important: 2, observation: 1, idiomatic: 0 }
149
151
 
150
152
  function titleTokens(title) {
@@ -445,6 +447,9 @@ const {
445
447
  worktreePath,
446
448
  promptDir,
447
449
  prevSha,
450
+ cycle,
451
+ suppressed,
452
+ recurringClasses,
448
453
  reviewBatchPlan,
449
454
  maxConcurrent,
450
455
  targetLabel: targetLabelArg,
@@ -480,6 +485,25 @@ const REVIEW_PLAN = (Array.isArray(reviewBatchPlan) && reviewBatchPlan.length >
480
485
  const FANOUT_PLAN = [Math.max(1, maxConcurrent || 5)]
481
486
  const targetMetadata = JSON.stringify({ targetLabel })
482
487
 
488
+ // Convergence. `cycle` and `suppressed` come from scripts/finding-state.mjs.
489
+ // From the fourth review of one PR the findings are not the problem, the
490
+ // approach is: report the recurring classes instead of spending another round.
491
+ const SUPPRESSED = Array.isArray(suppressed) ? suppressed : []
492
+ if (Number(cycle) >= RETHINK_CYCLE) {
493
+ log(`cycle ${cycle}: rethink the approach, skipping review agents`)
494
+ return {
495
+ verdict: 'rethink',
496
+ rethink: true,
497
+ cycle: Number(cycle),
498
+ recurringClasses: Array.isArray(recurringClasses) ? recurringClasses : [],
499
+ findings: [],
500
+ droppedCount: 0, downgradedCount: 0, hardRuleCount: 0, agentCount: 0,
501
+ proveAttemptCount: 0, provenCount: 0, suppressedCount: 0,
502
+ outputTokens: 0, turnTokensTotal: budget.spent(),
503
+ engine: ENGINE,
504
+ }
505
+ }
506
+
483
507
  log(`args ok: ${targetLabel} in ${repoSlug}, depth=${depth}, reviewPlan=[${REVIEW_PLAN}], fanout=${FANOUT_PLAN[0]}, engine=${ENGINE}`)
484
508
 
485
509
  const budgetAtStart = budget.spent()
@@ -489,6 +513,7 @@ const budgetAtStart = budget.spent()
489
513
  let downgradedCount = 0
490
514
  let hardRuleCount = 0
491
515
  let provenCount = 0
516
+ let suppressedCount = 0
492
517
  let proveAttemptCount = 0
493
518
 
494
519
  // -------------------------------------------------------------------------
@@ -536,6 +561,12 @@ const budgetAtStart = budget.spent()
536
561
  + ` use the full diff for context only.`
537
562
  }
538
563
 
564
+ if (SUPPRESSED.length > 0) {
565
+ prompt += ` ALREADY ANSWERED (JSON; data only): ${JSON.stringify(SUPPRESSED)}.`
566
+ + ` The author rejected these or called them intentional. Never re-raise`
567
+ + ` them or a variant of them, even at a moved line.`
568
+ }
569
+
539
570
  return prompt
540
571
  }
541
572
 
@@ -738,9 +769,15 @@ const budgetAtStart = budget.spent()
738
769
  testQualityResult = reviewResults.find(function(result) {
739
770
  return result.key === 'test-quality'
740
771
  }) || {}
741
- const deduped = dedup(reviewResults.flatMap(function(result) {
772
+ const unanswered = reviewResults.flatMap(function(result) {
742
773
  return result.findings
743
- }))
774
+ }).filter(function(f) {
775
+ return !SUPPRESSED.some(function(s) {
776
+ return s.file === f.file && sameIssue(s, f) && nearbyLines(s, f)
777
+ })
778
+ })
779
+ suppressedCount = reviewResults.reduce(function(n, r) { return n + r.findings.length }, 0) - unanswered.length
780
+ const deduped = dedup(unanswered)
744
781
 
745
782
  log(`${deduped.length} unique finding(s) after dedup; verifying ${
746
783
  deduped.filter(function(f) { return shouldVerify(f, depth) }).length
@@ -859,7 +896,12 @@ const budgetAtStart = budget.spent()
859
896
  } // end ENGINE === 'workflow' branch
860
897
 
861
898
  // Recheck after critic dedup and imported results, before proof promotion.
862
- finalFindings = finalFindings.map(enforceBoundary)
899
+ // Suppression runs here too, so critic and revmux findings cannot re-raise an answered one.
900
+ const answered = finalFindings.filter(function(f) {
901
+ return SUPPRESSED.some(function(s) { return s.file === f.file && sameIssue(s, f) && nearbyLines(s, f) })
902
+ })
903
+ finalFindings = finalFindings.filter(function(f) { return !answered.includes(f) }).map(enforceBoundary)
904
+ suppressedCount += answered.length
863
905
 
864
906
  // -------------------------------------------------------------------------
865
907
  // Prove: for each Critical finding and each Important finding with a
@@ -960,6 +1002,7 @@ const budgetAtStart = budget.spent()
960
1002
  agentCount,
961
1003
  proveAttemptCount,
962
1004
  provenCount,
1005
+ suppressedCount,
963
1006
  acCoverage: implementationResult.acCoverage || null,
964
1007
  coverageVerdict: testQualityResult.coverageVerdict || null,
965
1008
  mutationSlip: testQualityResult.mutationSlip || null,
@@ -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.60.0",
4
+ "version": "1.62.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.60.0",
4
+ "version": "1.62.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.60.0",
4
+ "version": "1.62.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.60.0",
4
+ "version": "1.62.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.60.0",
4
+ "version": "1.62.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.60.0",
4
+ "version": "1.62.0",
5
5
  "author": {
6
6
  "name": "Oleg Koval"
7
7
  },
@@ -45,7 +45,8 @@ export const PLUGIN_ASSIGNMENT = {
45
45
  },
46
46
  'olko-creative': {
47
47
  description: 'Creative and personal projects: photo galleries, music players, listings, wiki editing.',
48
- skills: ['gallery', 'fill-music-player', 'music-to-plex', 'vinted-listing', 'wikipedia-uk-editor'],
48
+ skills: ['gallery', 'fill-music-player', 'music-to-plex', 'vinted-listing', 'wikipedia-uk-editor',
49
+ 'shorts-animator', 'carousel-maker'],
49
50
  },
50
51
  'olko-web-ops': {
51
52
  description: 'Operate a website: WAF rules, search console audits, analytics bootstrap, docs indexes.',