@olegkoval/agent-skills 1.43.1 → 1.44.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 (49) hide show
  1. package/adapters/claude/olko-github-pr/skills/lekker-review/SKILL.md +85 -2
  2. package/adapters/claude/olko-github-pr/skills/lekker-review/references/pricing.json +9 -0
  3. package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-conventions.md +158 -0
  4. package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-implementation.md +76 -0
  5. package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-quality.md +87 -0
  6. package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-simplification.md +63 -0
  7. package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-test-quality.md +172 -0
  8. package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/phase4-comparison.md +42 -0
  9. package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/profile.md +347 -0
  10. package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/profiles/lekker-deep.md +82 -0
  11. package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/profiles/lekker-medium.md +81 -0
  12. package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/fixtures/context.json +3 -0
  13. package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/fixtures/house-rules.md +11 -0
  14. package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/fixtures/revmux-report.json +179 -0
  15. package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/install-revmux-prompts.sh +66 -0
  16. package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/revmux-adapter.mjs +261 -0
  17. package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/revmux-engine.sh +156 -0
  18. package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/selftest.mjs +76 -0
  19. package/package.json +1 -1
  20. package/plugins/olko-apple-kit/.claude-plugin/plugin.json +1 -1
  21. package/plugins/olko-creative/.claude-plugin/plugin.json +1 -1
  22. package/plugins/olko-garmin-kit/.claude-plugin/plugin.json +1 -1
  23. package/plugins/olko-git-tools/.claude-plugin/plugin.json +1 -1
  24. package/plugins/olko-github-pr/.claude-plugin/plugin.json +1 -1
  25. package/plugins/olko-github-pr/skills/lekker-review/SKILL.md +85 -2
  26. package/plugins/olko-github-pr/skills/lekker-review/references/pricing.json +9 -0
  27. package/plugins/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-conventions.md +158 -0
  28. package/plugins/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-implementation.md +76 -0
  29. package/plugins/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-quality.md +87 -0
  30. package/plugins/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-simplification.md +63 -0
  31. package/plugins/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-test-quality.md +172 -0
  32. package/plugins/olko-github-pr/skills/lekker-review/references/revmux/phase4-comparison.md +42 -0
  33. package/plugins/olko-github-pr/skills/lekker-review/references/revmux/profile.md +347 -0
  34. package/plugins/olko-github-pr/skills/lekker-review/references/revmux/profiles/lekker-deep.md +82 -0
  35. package/plugins/olko-github-pr/skills/lekker-review/references/revmux/profiles/lekker-medium.md +81 -0
  36. package/plugins/olko-github-pr/skills/lekker-review/scripts/fixtures/context.json +3 -0
  37. package/plugins/olko-github-pr/skills/lekker-review/scripts/fixtures/house-rules.md +11 -0
  38. package/plugins/olko-github-pr/skills/lekker-review/scripts/fixtures/revmux-report.json +179 -0
  39. package/plugins/olko-github-pr/skills/lekker-review/scripts/install-revmux-prompts.sh +66 -0
  40. package/plugins/olko-github-pr/skills/lekker-review/scripts/revmux-adapter.mjs +261 -0
  41. package/plugins/olko-github-pr/skills/lekker-review/scripts/revmux-engine.sh +156 -0
  42. package/plugins/olko-github-pr/skills/lekker-review/scripts/selftest.mjs +76 -0
  43. package/plugins/olko-github-pr/skills/lekker-review/workflow.js +43 -6
  44. package/plugins/olko-obsidian/.claude-plugin/plugin.json +1 -1
  45. package/plugins/olko-product/.claude-plugin/plugin.json +1 -1
  46. package/plugins/olko-reflection/.claude-plugin/plugin.json +1 -1
  47. package/plugins/olko-release/.claude-plugin/plugin.json +1 -1
  48. package/plugins/olko-skill-meta/.claude-plugin/plugin.json +1 -1
  49. package/plugins/olko-web-ops/.claude-plugin/plugin.json +1 -1
@@ -0,0 +1,156 @@
1
+ #!/bin/bash
2
+ # revmux-engine.sh -- lekker-review's revmux caller (Phase 2 of the revmux spike).
3
+ #
4
+ # Usage:
5
+ # revmux-engine.sh --task SLUG --run NAME --depth medium|deep --workdir DIR \
6
+ # --diff-file FILE --context-file FILE --profile-file FILE --out FILE \
7
+ # [--tasks-dir DIR] [--config-dir DIR]
8
+ #
9
+ # Builds one revmux round (scope.md, goal.md, input/context/, input/profile.md),
10
+ # runs revmux --no-tui, writes its stdout JSON to --out, and exits with revmux's
11
+ # own code (0 = no findings, 1 = findings, 2 = tool error). A round name that
12
+ # already exists is revmux's own error -- this script never deletes anything to
13
+ # work around it.
14
+ set -euo pipefail
15
+
16
+ TASK=""
17
+ RUN=""
18
+ DEPTH=""
19
+ WORKDIR=""
20
+ DIFF_FILE=""
21
+ CONTEXT_FILE=""
22
+ PROFILE_FILE=""
23
+ OUT=""
24
+ TASKS_DIR="${LEKKER_REVMUX_TASKS_DIR:-$HOME/code-reviews/revmux-tasks}"
25
+ CONFIG_DIR="${REVMUX_CONFIG_DIR:-$HOME/.config/revmux}"
26
+
27
+ while [[ $# -gt 0 ]]; do
28
+ case "$1" in
29
+ --task) TASK="$2"; shift 2 ;;
30
+ --run) RUN="$2"; shift 2 ;;
31
+ --depth) DEPTH="$2"; shift 2 ;;
32
+ --workdir) WORKDIR="$2"; shift 2 ;;
33
+ --diff-file) DIFF_FILE="$2"; shift 2 ;;
34
+ --context-file) CONTEXT_FILE="$2"; shift 2 ;;
35
+ --profile-file) PROFILE_FILE="$2"; shift 2 ;;
36
+ --out) OUT="$2"; shift 2 ;;
37
+ --tasks-dir) TASKS_DIR="$2"; shift 2 ;;
38
+ --config-dir) CONFIG_DIR="$2"; shift 2 ;;
39
+ *) printf 'revmux-engine: unknown arg %s\n' "$1" >&2; exit 2 ;;
40
+ esac
41
+ done
42
+
43
+ for req in TASK RUN DEPTH WORKDIR DIFF_FILE CONTEXT_FILE PROFILE_FILE OUT; do
44
+ if [[ -z "${!req}" ]]; then
45
+ printf 'revmux-engine: missing required --%s\n' "$(printf '%s' "$req" | tr 'A-Z' 'a-z' | tr '_' '-')" >&2
46
+ exit 2
47
+ fi
48
+ done
49
+
50
+ for input in DIFF_FILE CONTEXT_FILE PROFILE_FILE; do
51
+ if [[ ! -r "${!input}" ]]; then
52
+ printf 'revmux-engine: --%s must be a readable file: %s\n' \
53
+ "$(printf '%s' "$input" | tr 'A-Z' 'a-z' | tr '_' '-')" "${!input}" >&2
54
+ exit 2
55
+ fi
56
+ done
57
+
58
+ WORKTREE_JSON="$(dirname "$CONTEXT_FILE")/worktree.json"
59
+ if [[ ! -r "$WORKTREE_JSON" ]]; then
60
+ printf 'revmux-engine: worktree configuration must be readable: %s\n' "$WORKTREE_JSON" >&2
61
+ exit 2
62
+ fi
63
+
64
+ case "$DEPTH" in
65
+ medium|deep) ;;
66
+ *)
67
+ printf 'revmux-engine: depth "%s" is not supported by this engine (only medium|deep -- scan stays on the workflow engine, it has no worktree)\n' "$DEPTH" >&2
68
+ exit 2
69
+ ;;
70
+ esac
71
+
72
+ PROFILE_NAME="lekker-${DEPTH}"
73
+
74
+ command -v revmux >/dev/null 2>&1 || { printf 'revmux-engine: revmux binary not found on PATH\n' >&2; exit 2; }
75
+ command -v jq >/dev/null 2>&1 || { printf 'revmux-engine: jq not found on PATH\n' >&2; exit 2; }
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # Codex guard: refuse to run a profile whose resolved text names a codex/
79
+ # runner. Company does not support the codex CLI (revmux is claude-only here).
80
+ # ---------------------------------------------------------------------------
81
+ PROFILE_TEXT_FILE="$CONFIG_DIR/prompts/profiles/${PROFILE_NAME}.md"
82
+ if [[ ! -f "$PROFILE_TEXT_FILE" ]]; then
83
+ printf 'revmux-engine: cannot resolve profile text at %s to run the codex guard -- refusing to proceed\n' "$PROFILE_TEXT_FILE" >&2
84
+ exit 2
85
+ fi
86
+ if grep -q 'codex/' "$PROFILE_TEXT_FILE"; then
87
+ printf 'revmux-engine: profile %s names a codex/ runner -- codex is not supported here, refusing to run\n' "$PROFILE_TEXT_FILE" >&2
88
+ exit 2
89
+ fi
90
+
91
+ mkdir -p "$TASKS_DIR"
92
+
93
+ NEW_JSON="$(revmux --tasks-dir "$TASKS_DIR" --config-dir "$CONFIG_DIR" new --task "$TASK" --run "$RUN")"
94
+
95
+ resolve_new_path() {
96
+ local key="$1"
97
+ local value
98
+ if ! value="$(printf '%s' "$NEW_JSON" | jq -er --arg key "$key" \
99
+ '.[$key] | select(type == "string" and length > 0)')" \
100
+ || [[ -z "$value" || "$value" == "null" ]]; then
101
+ printf 'revmux-engine: revmux new returned an invalid %s path\n' "$key" >&2
102
+ return 2
103
+ fi
104
+ printf '%s' "$value"
105
+ }
106
+
107
+ SCOPE_FILE="$(resolve_new_path scope)"
108
+ GOAL_FILE="$(resolve_new_path goal)"
109
+ PROFILE_INPUT_FILE="$(resolve_new_path profile)"
110
+ CONTEXT_DIR="$(resolve_new_path context)"
111
+
112
+ mkdir -p "$CONTEXT_DIR"
113
+
114
+ TARGET_LABEL="$(jq -r '.reviewTarget // "unknown target"' "$CONTEXT_FILE" 2>/dev/null || printf 'unknown target')"
115
+
116
+ BASE_REF=""
117
+ BASE_REF="$(jq -r '.mergeBase // .baseRef // empty' "$WORKTREE_JSON" 2>/dev/null || true)"
118
+
119
+ {
120
+ printf '# Scope\n\n'
121
+ printf 'Under review: %s\n\n' "$TARGET_LABEL"
122
+ printf 'Diff file (authoritative): %s\n\n' "$DIFF_FILE"
123
+ if [[ -n "$BASE_REF" ]]; then
124
+ printf 'Reproduce inside WORKDIR with:\n\n git -C %s diff %s...HEAD\n\n' "$WORKDIR" "$BASE_REF"
125
+ else
126
+ printf 'No base ref was recorded for this worktree; the diff file above is authoritative, do not attempt to reconstruct it from git.\n\n'
127
+ fi
128
+ } > "$SCOPE_FILE"
129
+
130
+ {
131
+ printf '# Goal\n\n'
132
+ printf '## PR title\n\n%s\n\n' "$(jq -r '.reviewTarget // "none provided"' "$CONTEXT_FILE" 2>/dev/null || printf 'none provided')"
133
+ printf '## Ticket / acceptance criteria\n\n%s\n\n' "$(jq -r '.acList // "none provided"' "$CONTEXT_FILE" 2>/dev/null || printf 'none provided')"
134
+ printf '## Scoping decisions\n\n%s\n\n' "$(jq -r '.scopingDecisions // "none provided"' "$CONTEXT_FILE" 2>/dev/null || printf 'none provided')"
135
+ } > "$GOAL_FILE"
136
+
137
+ cp "$CONTEXT_FILE" "$CONTEXT_DIR/context.json"
138
+ cp "$DIFF_FILE" "$CONTEXT_DIR/pr.diff"
139
+ cp "$WORKTREE_JSON" "$CONTEXT_DIR/worktree.json"
140
+ cp "$PROFILE_FILE" "$PROFILE_INPUT_FILE"
141
+
142
+ REVMUX_OUT="$OUT"
143
+ set +e
144
+ revmux --task "$TASK" --run "$RUN" --profile "$PROFILE_NAME" \
145
+ --workdir "$WORKDIR" --tasks-dir "$TASKS_DIR" --config-dir "$CONFIG_DIR" \
146
+ --no-tui > "$REVMUX_OUT"
147
+ CODE=$?
148
+ set -e
149
+
150
+ case "$CODE" in
151
+ 0) printf 'revmux exit 0 (no findings)\n' >&2 ;;
152
+ 1) printf 'revmux exit 1 (findings reported)\n' >&2 ;;
153
+ *) printf 'revmux exit %s (tool error)\n' "$CODE" >&2 ;;
154
+ esac
155
+
156
+ exit "$CODE"
@@ -183,5 +183,81 @@ if (!hasRouting) {
183
183
  }
184
184
  }
185
185
 
186
+ console.log('\nrevmux adapter: fixture mapping')
187
+ {
188
+ const { execFileSync, spawnSync } = await import('node:child_process')
189
+ const fixturePath = join(SKILL, 'scripts', 'fixtures', 'revmux-report.json')
190
+ const contextPath = join(SKILL, 'scripts', 'fixtures', 'context.json')
191
+ const pricingPath = join(SKILL, 'references', 'pricing.json')
192
+ const adapterPath = join(SKILL, 'scripts', 'revmux-adapter.mjs')
193
+ const adapterArgs = [adapterPath, fixturePath, '--pricing', pricingPath, '--context', contextPath]
194
+ const out = execFileSync('node', adapterArgs, { encoding: 'utf8' })
195
+ const adapted = JSON.parse(out)
196
+
197
+ check('engine tag', adapted.engine, 'revmux')
198
+ check('critical confirmed kept critical',
199
+ adapted.findings.some(f => f.title.includes('Race between webhook retry') && f.severity === 'critical'), true)
200
+ check('major refined -> important, agreedBy from 2 sources',
201
+ (() => {
202
+ const f = adapted.findings.find(f => f.title.includes('Discount stacking'))
203
+ return f && f.severity === 'important' && Array.isArray(f.agreedBy) && f.agreedBy.length === 2
204
+ })(), true)
205
+ check('minor + lekker-conventions lens -> idiomatic',
206
+ adapted.findings.find(f => f.title.includes('naming matrix'))?.severity, 'idiomatic')
207
+ check('immaterial verdict dropped, not re-promoted',
208
+ adapted.findings.some(f => f.title.includes('Unused import')), false)
209
+ check('rejected non-hard-rule dropped',
210
+ adapted.findings.some(f => f.title.includes('double-count a tip')), false)
211
+ check('TS-1 rejected but corroborated by quoted code IS re-promoted to critical',
212
+ (() => {
213
+ const f = adapted.findings.find(f => f.title.includes('Unsafe cast on inventory adjustment'))
214
+ return f && f.severity === 'critical' && f.rule === 'TS-1'
215
+ })(), true)
216
+ check('TS-1 rejected with no corroborating code stays dropped',
217
+ adapted.findings.some(f => f.title.includes('Type safety concern in inventory handler')), false)
218
+ check('prose without an extracted snippet leaves badCode empty',
219
+ adapted.findings.find(f => f.title.includes('no retry backoff'))?.badCode, '')
220
+ check('configured custom hard rule with quoted code is re-promoted',
221
+ adapted.findings.find(f => f.title.includes('Session token is logged'))?.severity, 'critical')
222
+ check('configured custom hard rule without quoted code stays dropped',
223
+ adapted.findings.some(f => f.title.includes('Possible session concern')), false)
224
+ check('pre_existing becomes observation with Pre-existing: prefix',
225
+ (() => {
226
+ const f = adapted.findings.find(f => f.title.includes('no retry backoff'))
227
+ return f && f.severity === 'observation' && f.description.startsWith('Pre-existing:')
228
+ })(), true)
229
+ check('open_questions pass through as questions',
230
+ adapted.questions.length, 1)
231
+ check('degraded agent surfaced by name',
232
+ adapted.degraded, ['adversarial'])
233
+ check('droppedCount counts four rejected or immaterial findings', adapted.droppedCount, 4)
234
+ check('hardRuleCount counts built-in and custom re-promotions', adapted.hardRuleCount, 2)
235
+ check('totalUsd computed from known pricing', typeof adapted.totalUsd === 'number' && adapted.totalUsd > 0, true)
236
+ check('malformed token rows do not make totalUsd NaN', Number.isFinite(adapted.totalUsd), true)
237
+ check('malformed token rows keep their own usd unknown',
238
+ adapted.agents.find(a => a.name === 'malformed-cost')?.usd, null)
239
+ check('no pricingMissing when all models are priced', adapted.pricingMissing, undefined)
240
+ check('test-quality coverage verdict is populated from attributed findings',
241
+ adapted.coverageVerdict.includes('Operator-flip mutation'), true)
242
+ check('mutation-slip summary is populated from attributed findings',
243
+ adapted.mutationSlip.includes('retry guard'), true)
244
+ check('mock smells are structured from attributed findings', adapted.mockSmells.length, 1)
245
+
246
+ const noPricingOut = execFileSync('node', [adapterPath, fixturePath, '--context', contextPath], { encoding: 'utf8' })
247
+ const noPricingAdapted = JSON.parse(noPricingOut)
248
+ check('without a pricing file every model is pricingMissing',
249
+ noPricingAdapted.pricingMissing?.includes('claude-sonnet-5'), true)
250
+ check('without a pricing file totalUsd is null', noPricingAdapted.totalUsd, null)
251
+
252
+ for (const [label, badArgs] of [
253
+ ['report', [adapterPath, join(SKILL, 'SKILL.md')]],
254
+ ['pricing', [adapterPath, fixturePath, '--pricing', join(SKILL, 'SKILL.md'), '--context', contextPath]],
255
+ ]) {
256
+ const failedRun = spawnSync('node', badArgs, { encoding: 'utf8' })
257
+ check(`invalid ${label} JSON exits 2`, failedRun.status, 2)
258
+ check(`invalid ${label} JSON has a clear error`, failedRun.stderr.includes(`${label} JSON`), true)
259
+ }
260
+ }
261
+
186
262
  console.log(failed === 0 ? '\nall checks passed\n' : `\n${failed} check(s) FAILED\n`)
187
263
  process.exit(failed === 0 ? 0 : 1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olegkoval/agent-skills",
3
- "version": "1.43.1",
3
+ "version": "1.44.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.43.1",
4
+ "version": "1.44.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.43.1",
4
+ "version": "1.44.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.43.1",
4
+ "version": "1.44.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.43.1",
4
+ "version": "1.44.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.43.1",
4
+ "version": "1.44.0",
5
5
  "author": {
6
6
  "name": "Oleg Koval"
7
7
  },
@@ -55,6 +55,10 @@ FAANG-grade code review. Isolated worktree checkout, full context gathering
55
55
  you have MCP tools configured for), then 5 parallel specialized review
56
56
  agents, a finding-verification pass, and one unified markdown output.
57
57
 
58
+ An optional `--engine revmux` flag can replace the Step 2 Workflow-tool pipeline
59
+ with revmux (profiles and lenses under `references/revmux/`); default stays
60
+ `workflow`, and both engines feed the same Step 3 synthesis.
61
+
58
62
  Two modes, same review engine:
59
63
 
60
64
  | Mode | Target | Entry |
@@ -232,6 +236,12 @@ in Step 4. Use it for a read-only pre-push look.
232
236
  **`--no-artifact` flag:** parse and store as `ARTIFACT=false` (default true).
233
237
  Skips Step 3.5 (living review artifact) silently.
234
238
 
239
+ **`--engine revmux|workflow` flag:** parse and store as `ENGINE`, default
240
+ `workflow`. `revmux` is allowed only at depth `medium` or `deep` - revmux's
241
+ scripts reject `scan` (see Depth gate). If depth resolves to `scan` (explicit
242
+ or auto), force `ENGINE=workflow` regardless of the flag and note `engine
243
+ forced to workflow - revmux needs medium/deep` in the review header.
244
+
235
245
  **Re-review detection:** run
236
246
  `ls ~/code-reviews/*-<TARGET_SLUG>-<repo-short-name>.md 2>/dev/null | sort | tail -1`
237
247
  (`TARGET_SLUG` = `pr-<PR_NUMBER>` in pr mode, `branch-<LOCAL_BRANCH sanitized to
@@ -420,7 +430,9 @@ their prompts wholesale - the workflow script delivers it by path.
420
430
 
421
431
  ---
422
432
 
423
- ## Step 2 - Workflow (review + verify + critic)
433
+ ## Step 2 - Review engine (review + verify + critic)
434
+
435
+ ### ENGINE=workflow (default)
424
436
 
425
437
  Invoke the Workflow tool:
426
438
 
@@ -499,12 +511,61 @@ provers on `sonnet`, housekeeping on `haiku`. Only the synthesis in Step 3 runs
499
511
  on the session model.
500
512
 
501
513
  Return value from the workflow:
502
- `{findings, droppedCount, downgradedCount, hardRuleCount, proveAttemptCount, provenCount, acCoverage, coverageVerdict, mutationSlip, mockSmells, agentCount, outputTokens, turnTokensTotal}`
514
+ `{engine, findings, droppedCount, downgradedCount, hardRuleCount, proveAttemptCount, provenCount, acCoverage, coverageVerdict, mutationSlip, mockSmells, agentCount, outputTokens, turnTokensTotal}`
503
515
  `outputTokens` is this workflow's own output spend; `turnTokensTotal` is the
504
516
  whole turn's shared pool (main loop included).
505
517
 
506
518
  Wait for the workflow to complete before proceeding to Step 3.
507
519
 
520
+ ### ENGINE=revmux
521
+
522
+ revmux replaces Review, Dedup, Verify, and Critic with its own multi-agent
523
+ round; Prove still runs inside `workflow.js`. Steps:
524
+
525
+ 1. `TASK_SLUG` = `<repo-short-name>-<TARGET_SLUG>`, `RUN` = `01-review`.
526
+ 2. Run the engine:
527
+
528
+ ```bash
529
+ ${CLAUDE_PLUGIN_ROOT}/scripts/revmux-engine.sh \
530
+ --task <TASK_SLUG> --run <RUN> --depth <depth> \
531
+ --workdir <worktreePath> \
532
+ --diff-file <scratchpad>/pr.diff \
533
+ --context-file <scratchpad>/context.json \
534
+ --profile-file ${CLAUDE_PLUGIN_ROOT}/references/revmux/profile.md \
535
+ --config-dir ~/.config/revmux \
536
+ --out <scratchpad>/revmux.json
537
+ ```
538
+
539
+ 3. Adapt the report:
540
+
541
+ ```bash
542
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/revmux-adapter.mjs \
543
+ <scratchpad>/revmux.json --pricing \
544
+ ${CLAUDE_PLUGIN_ROOT}/references/pricing.json \
545
+ --context <scratchpad>/context.json \
546
+ > <scratchpad>/findings.json
547
+ ```
548
+
549
+ 4. Invoke Workflow(`workflow.js`) with the same args as the `ENGINE=workflow`
550
+ path above, plus `engine: "revmux"` and
551
+ `findingsFile: "<scratchpad>/findings.json"`. It skips Review/Dedup/
552
+ Verify/Critic, runs Prove only, and its return adds `engine, questions,
553
+ agents, degraded, totalTokens, totalUsd` on top of the usual keys.
554
+ 5. **Fallback to `ENGINE=workflow` for this run** when either holds: the
555
+ engine script exits `2` (tool error - also raised for a missing profile
556
+ file, the codex guard, or an unsupported depth), or every row in the
557
+ adapter's `agents` array has `degraded: true`. State the fallback in the
558
+ review header (`**Note:** revmux unavailable - fell back to workflow
559
+ engine`) and re-run the `ENGINE=workflow` path from the top of Step 2.
560
+
561
+ **Trust boundary:** always pass `--config-dir ~/.config/revmux` explicitly.
562
+ Without it revmux also reads the reviewed repo's checked-in `.revmux/`, which
563
+ is executed as prompts. `~/.config/revmux` is where
564
+ `scripts/install-revmux-prompts.sh` puts the lekker profiles and lenses; run it
565
+ with `--check` when the engine exits 2 complaining about a missing profile. The task archive lives under the tasks dir
566
+ (`LEKKER_REVMUX_TASKS_DIR`, default `~/code-reviews/revmux-tasks`) and is not
567
+ committed anywhere.
568
+
508
569
  ---
509
570
 
510
571
  ## Step 3 - Synthesize and output
@@ -600,6 +661,17 @@ requirements:
600
661
  cost. Input tokens are estimated (diff tokens x agent passes + context
601
662
  + prompt files). Use the pricing table in `references/output-format.md`.
602
663
  Real numbers only - no `<N>` placeholders.
664
+ When `ENGINE=revmux`: build the rows from the adapter's `agents` array
665
+ (`name`, `model`, `tokens`, `usd` per row) and report `totalUsd` as the
666
+ total. Read `pricingMissing`; when it is non-empty, explicitly mark `totalUsd`
667
+ as incomplete and name the unpriced models. State plainly that the USD figures are an API list-price estimate
668
+ computed from `references/pricing.json`'s placeholder prices
669
+ (`verified: false`) until that file is verified against real invoices.
670
+ Prove-phase tokens still come from the workflow return's `outputTokens`,
671
+ same as the workflow engine. When `questions` (from the adapter) is
672
+ non-empty, render them under a `## Questions for the author` section. When
673
+ `degraded` is non-empty, add one banner line in the review header naming
674
+ the degraded agents, e.g. `⚠️ degraded: implementation, test-quality`.
603
675
 
604
676
  **Save the review:**
605
677
 
@@ -857,6 +929,17 @@ do verification inline per `references/agents/verifier.md`, and state this
857
929
  fallback in the review output under a
858
930
  `**Note:** Workflow tool unavailable - ran agents directly` line in the header.
859
931
 
932
+ - `ENGINE=revmux` specific: on an exit-2 from `revmux-engine.sh`, or an
933
+ adapter report where every agent came back `degraded`, fall back to
934
+ `ENGINE=workflow` for that run (Step 2) rather than retrying revmux - this
935
+ is a fallback, not a retry loop, so it does not count against the
936
+ two-identical-failures rule above.
937
+ - Never delete or rename anything under the revmux tasks dir
938
+ (`LEKKER_REVMUX_TASKS_DIR`, default `~/code-reviews/revmux-tasks`). A
939
+ duplicate run name (same `TASK_SLUG`/`RUN` pair) is revmux's own hard
940
+ error, by design - pick the next `NN-...` name (e.g. `03-...`) rather than
941
+ clearing the old one.
942
+
860
943
  Fix-mode specific:
861
944
  - Never claim a fix landed without a `git log` / `git status` receipt from the
862
945
  worktree. The review's proposed `fix` text is not an applied fix.
@@ -0,0 +1,9 @@
1
+ {
2
+ "note": "USD per million tokens, keyed by revmux's actual_model. revmux runs `claude --print` under subscription (Max plan) auth, so nothing here is a real API bill; this is an API-list-price ESTIMATE for the cost block only. Where a model exposes separate input/output prices, usd = tokens/1e6 * output price (output-only, not blended), because revmux reports a single combined `tokens` figure per agent with no input/output split to weight a blend. verified:false entries are not confirmed against a current price sheet (no local claude-api pricing doc found on this machine as of 2026-09-09) and must be checked before this cost block is trusted for a real decision.",
3
+ "claude-opus-5": { "input": 15, "output": 75, "blended": 45, "verified": false },
4
+ "claude-sonnet-5": { "input": 3, "output": 15, "blended": 9, "verified": false },
5
+ "claude-haiku-4-5-20251001": { "input": 1, "output": 5, "blended": 3, "verified": false },
6
+ "opus": { "input": 15, "output": 75, "blended": 45, "verified": false },
7
+ "sonnet": { "input": 3, "output": 15, "blended": 9, "verified": false },
8
+ "haiku": { "input": 1, "output": 5, "blended": 3, "verified": false }
9
+ }
@@ -0,0 +1,158 @@
1
+ ---
2
+ description: deviations from Teifi's own codebase conventions — the "strong teammate" non-blocking lens
3
+ ---
4
+ ## Lens: lekker-conventions
5
+
6
+ Review the change for deviations from Teifi's established codebase
7
+ conventions and idioms. This is the "strong teammate" lens: the suggestions a
8
+ senior Teifi engineer leaves — non-blocking, but they make the code match how
9
+ the rest of the codebase is written. Look beyond the diff only for
10
+ convention-specific precedent and reuse searches. Other lenses may inspect the
11
+ runtime context they need for their own cross-file checks.
12
+
13
+ Read `{{PROFILE}}` now, before forming any opinion — it carries the Teifi
14
+ conventions text. Its §1 (naming matrix), §2 (comment policy), §5 (commit
15
+ hygiene) and §6 (generated code) are yours — they are the house style, so a
16
+ deviation needs NO codebase precedent beyond that file (the file IS the
17
+ precedent; cite the section, e.g. "teifi-conventions §1 verbs").
18
+ Everything else in this lens still requires a cited precedent from the code.
19
+
20
+ Axes to cover:
21
+ - Naming (teifi-conventions §1): every symbol the diff INTRODUCES against the
22
+ matrix — boolean without `is`/`has`, async I/O named `get`, a row lock or a
23
+ throw-on-miss or a cache read absent from the name (`…ForUpdate`,
24
+ `…OrThrow`, `…Cached`), a collidable component without its domain prefix, a
25
+ bare generic noun (`line`, `node`, `row`) where the domain has two variants in
26
+ scope. NEVER flag a boundary name (DB column, GraphQL/oRPC field, enum value,
27
+ route string, wire key) — renaming it breaks callers outside the diff.
28
+ - Comments (teifi-conventions §2): one finding per over-commenting offender the
29
+ diff ADDED, with the deletion as the fix. Never a vague "too many comments",
30
+ never a pre-existing comment, never a lint/type pragma or a genuine
31
+ non-obvious "why".
32
+ - Generated code (teifi-conventions §6): a changed `.sql` / `.graphql` /
33
+ `.json` schema / `prisma/schema.prisma` whose generated artifact is absent
34
+ from the diff (or the reverse) — major. Raise NO naming, comment, or
35
+ complexity finding inside a `generated/` directory. A hand-rolled `fetch` to
36
+ the Shopify Admin GraphQL endpoint instead of the genql client — major.
37
+ - Commits (teifi-conventions §5): non-conventional or vague commit subjects, and
38
+ any Claude Code / assistant mention in the commit or PR text.
39
+ - Type-system idioms:
40
+ * A hand-written interface/type that duplicates an existing Zod schema —
41
+ should be `z.infer<typeof zSchema>` so the schema stays the single source
42
+ of truth. (Grep for a matching z-schema in the same feature folder.)
43
+ * Raw `string` used for a Shopify GID or an entity id where a branded
44
+ `ID<'Customer'>` (or similar) type exists and is used elsewhere.
45
+ * A union typed as `as readonly string[]` / a hand-rolled `is...` guard where
46
+ a `z.enum([...])` + `z.infer` would give validation, narrowing, and the
47
+ options array in one declaration.
48
+ * An unnecessary `satisfies` / redundant type annotation the compiler already
49
+ infers.
50
+ * A GID validated/parsed inline where a shared helper exists (e.g.
51
+ `zNamespacedGid`). Grep the shared libs and the repo before asserting.
52
+ - Reuse (search the worktree AND sibling Teifi repos before flagging):
53
+ * Inline fetch/client logic that should reuse — or be promoted into — a
54
+ shared client (e.g. a company-switcher client) that already exists or that
55
+ the codebase clearly wants.
56
+ * A util/helper that already exists elsewhere being re-implemented inline.
57
+ * A symbol defined locally that is (or should be) exported from a shared
58
+ module — "are we not exporting this somewhere?"
59
+ - Consistency:
60
+ * Cache-key / composite-key separators that disagree with the repo's
61
+ prevailing choice (e.g. `:` vs `::`). Grep existing key-building code to
62
+ find the prevailing pattern, then flag the deviation.
63
+ * Ad-hoc error throwing where the repo has an idiom (e.g. `throw new
64
+ HttpError('...', 403)` instead of a bare string / generic Error).
65
+ * Naming/casing that breaks the convention used by sibling files.
66
+
67
+ MANDATORY SWEEP — do this FIRST, before forming any opinion:
68
+
69
+ The axes above are symptom-driven: they only fire once you already suspect a
70
+ duplication. That is how a re-implemented helper slips through — nobody thinks to
71
+ look. So run these enumerations mechanically, whether or not anything looks wrong.
72
+
73
+ 1. **Sibling sweep for every file the diff ADDS.** For each added file, list its
74
+ directory and read the exports of its neighbours. A helper that solves the same
75
+ problem is usually sitting in the same folder.
76
+
77
+ ```bash
78
+ REVIEW_CONFIG="{{CONTEXT}}/worktree.json"
79
+ WORKTREE="$(jq -er '.worktreePath | select(type == "string" and length > 0)' "$REVIEW_CONFIG")"
80
+ BASE_REV="$(jq -er '(.mergeBase // .baseRef) | select(type == "string" and length > 0)' "$REVIEW_CONFIG")"
81
+ REPO_ROOT="$(jq -er '.repoRoot | select(type == "string" and length > 0)' "$REVIEW_CONFIG")"
82
+ SIBLING_ROOT="$(dirname "$REPO_ROOT")"
83
+
84
+ git -C "$WORKTREE" diff --name-status "$BASE_REV"...HEAD | awk '$1=="A"{print $2}' |
85
+ while IFS= read -r added_file; do
86
+ added_dir="$WORKTREE/$(dirname "$added_file")"
87
+ ls -la "$added_dir" # what already lives beside it
88
+ grep -rn "^export " "$added_dir"/*.ts "$added_dir"/*.tsx 2>/dev/null |
89
+ grep -v -F -- "$added_file"
90
+ done
91
+ grep -rn "^export " "$SIBLING_ROOT" --include='*.ts' --include='*.tsx' \
92
+ --exclude-dir=node_modules 2>/dev/null
93
+ ```
94
+
95
+ A new `foo/bar-thing.ts` next to an existing `foo/thing.ts` is a finding waiting
96
+ to happen. Read the neighbour, do not just note its name.
97
+
98
+ 2. **New-symbol sweep.** For every function/const the diff exports, search the repo
99
+ for something that already does that job, by BEHAVIOUR not just by name. Names
100
+ rarely match; behaviour does.
101
+
102
+ ```bash
103
+ git -C "$WORKTREE" diff --unified=0 "$BASE_REV"...HEAD | \
104
+ grep '^+' | grep -E 'export (function|const) ' # collect new symbols
105
+ # then for each, search by what it does, e.g. a locale normaliser:
106
+ grep -rln "toLowerCase()\|normalize\|isoCode\|split('-')" "$WORKTREE" \
107
+ --include='*.ts' --include='*.tsx'
108
+ ```
109
+
110
+ Pick 2 or 3 behavioural keywords from the new function's body and grep those.
111
+ Reviewing the diff alone cannot catch this.
112
+
113
+ 3. **State what you swept.** In your output, name the directories you listed and the
114
+ behavioural greps you ran, even when they found nothing. A sweep that is not
115
+ reported did not happen, and the next reviewer cannot tell "no duplication exists"
116
+ from "nobody looked".
117
+
118
+ HARD RULES:
119
+ - Only raise a finding when the better pattern PROVABLY ALREADY EXISTS. Cite it:
120
+ the file:line where the helper/type/convention lives, or the sibling file that
121
+ does it the idiomatic way. If you cannot find a concrete precedent, DROP the
122
+ finding — "this would be nicer as X" on taste alone is not allowed.
123
+ - Every finding must still trace to a `+` line in the diff (the deviation must
124
+ be code this change added/changed). The supporting precedent may live outside the
125
+ diff; the deviation may not.
126
+ - These are suggestions, not blockers. Do not inflate severity. Report each as
127
+ `file:line — <deviation> (precedent: <file:line of the existing pattern>)`.
128
+ - ONE EXCEPTION to non-blocking: if the re-implementation DIVERGES in behaviour
129
+ from the helper it duplicates, that is not a style nit, it is two spellings of
130
+ the same value that disagree, and it belongs on lekker-quality's severity
131
+ scale rather than this lens's. Diff the two implementations before deciding:
132
+ same inputs, same outputs? If a real input produces different results, say so
133
+ explicitly and give the input.
134
+
135
+ To find precedents, you may run:
136
+
137
+ ```bash
138
+ grep -rn "<symbol or pattern>" "$WORKTREE" --include='*.ts' --include='*.tsx'
139
+ find "$SIBLING_ROOT" \( -name "*.ts" -o -name "*.tsx" \) ! -path "*/node_modules/*" \
140
+ | xargs grep -l "<symbol>" 2>/dev/null | head
141
+ ```
142
+
143
+ Always include *.tsx. Extension and frontend code lives in .tsx, so a search that
144
+ omits it silently reports "no precedent exists" for whole directories.
145
+
146
+ A mined record of what other bots (Greptile / Gemini / CodeRabbit) have
147
+ commented on in this repo may be present under `{{CONTEXT}}`. Use it as a prior,
148
+ not as a checklist: a high count means "frequently raised here", not "correct" —
149
+ never raise a finding because a bot once said it, only because it is true here.
150
+
151
+ Rules:
152
+ - Report file:line — description with a precedent citation. No positive
153
+ observations. No taste-only suggestions.
154
+ - Quote the verbatim offending line(s) — never paraphrased, never reconstructed
155
+ from memory — and give a concrete drop-in fix, or when the fix is
156
+ architectural, a minimal skeleton plus one sentence on what else must change.
157
+ - A finding you cannot quote and cannot fix is a finding you have not proven —
158
+ drop it instead.