@olegkoval/agent-skills 1.43.0 → 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,179 @@
1
+ {
2
+ "findings": [
3
+ {
4
+ "id": "f1",
5
+ "file": "web/services/order-sync.ts",
6
+ "line": 42,
7
+ "end_line": 44,
8
+ "severity": "critical",
9
+ "confidence": 0.9,
10
+ "title": "Race between webhook retry and manual sync writes duplicate fulfillment",
11
+ "body": "Two concurrent writers can both pass the guard and insert a fulfillment row.",
12
+ "fix": "Take a row lock on the order before the check-then-insert.",
13
+ "sources": ["quality+impl"],
14
+ "lenses": ["lekker-quality"],
15
+ "verdict": "confirmed"
16
+ },
17
+ {
18
+ "id": "f2",
19
+ "file": "web/services/pricing.ts",
20
+ "line": 88,
21
+ "end_line": 90,
22
+ "severity": "major",
23
+ "confidence": 0.75,
24
+ "title": "Discount stacking ignores currency rounding",
25
+ "body": "Two agents independently flagged the same rounding gap on stacked discounts.",
26
+ "fix": "Round once at the end, not after each discount.",
27
+ "sources": ["quality+impl", "simpl+conventions"],
28
+ "lenses": ["lekker-quality", "lekker-simplification"],
29
+ "verdict": "refined"
30
+ },
31
+ {
32
+ "id": "f3",
33
+ "file": "web/services/logger.ts",
34
+ "line": 12,
35
+ "end_line": 12,
36
+ "severity": "minor",
37
+ "confidence": 0.6,
38
+ "title": "Log helper name does not follow the naming matrix",
39
+ "body": "getLogger performs no I/O, per teifi-conventions section 1 it should not be prefixed get.",
40
+ "fix": "Rename to buildLogger.",
41
+ "sources": ["simpl+conventions"],
42
+ "lenses": ["lekker-conventions"],
43
+ "verdict": "confirmed"
44
+ },
45
+ {
46
+ "id": "f4",
47
+ "file": "web/services/webhook.ts",
48
+ "line": 5,
49
+ "end_line": 5,
50
+ "severity": "minor",
51
+ "confidence": 0.4,
52
+ "title": "Unused import left over from a refactor",
53
+ "body": "Import of `crypto` is unused after the signature check moved.",
54
+ "fix": "Remove the unused import.",
55
+ "sources": ["adversarial"],
56
+ "lenses": ["adversarial"],
57
+ "verdict": "immaterial"
58
+ },
59
+ {
60
+ "id": "f5",
61
+ "file": "web/services/refund.ts",
62
+ "line": 60,
63
+ "end_line": 61,
64
+ "severity": "major",
65
+ "confidence": 0.5,
66
+ "title": "Refund total may double-count a tip",
67
+ "body": "Speculative -- verifier could not reproduce a path where the tip line is summed twice.",
68
+ "fix": "n/a",
69
+ "sources": ["adversarial"],
70
+ "lenses": ["adversarial"],
71
+ "verdict": "rejected"
72
+ },
73
+ {
74
+ "id": "f6",
75
+ "file": "web/services/inventory.ts",
76
+ "line": 21,
77
+ "end_line": 21,
78
+ "severity": "minor",
79
+ "confidence": 0.7,
80
+ "title": "[TS-1] Unsafe cast on inventory adjustment payload",
81
+ "body": "const qty = payload as any; -- a bare `as any` cast on external webhook input, no runtime type check follows it.",
82
+ "fix": "Parse payload with the zod schema and drop the cast.",
83
+ "sources": ["quality+impl"],
84
+ "lenses": ["lekker-quality"],
85
+ "verdict": "rejected"
86
+ },
87
+ {
88
+ "id": "f7",
89
+ "file": "web/services/inventory.ts",
90
+ "line": 30,
91
+ "end_line": 30,
92
+ "severity": "minor",
93
+ "confidence": 0.3,
94
+ "title": "[TS-1] Type safety concern in inventory handler",
95
+ "body": "This function handles any inventory update and could fail for any malformed input; consider adding validation.",
96
+ "fix": "Add input validation.",
97
+ "sources": ["quality+impl"],
98
+ "lenses": ["lekker-quality"],
99
+ "verdict": "rejected"
100
+ },
101
+ {
102
+ "id": "f8",
103
+ "file": "web/services/session.ts",
104
+ "line": 14,
105
+ "end_line": 14,
106
+ "severity": "minor",
107
+ "confidence": 0.8,
108
+ "title": "[SEC-7] Session token is logged",
109
+ "body": "The configured security rule forbids `logger.info(sessionToken)`.",
110
+ "fix": "Remove the token from the log payload.",
111
+ "sources": ["quality+impl"],
112
+ "lenses": ["lekker-quality"],
113
+ "verdict": "rejected"
114
+ },
115
+ {
116
+ "id": "f9",
117
+ "file": "web/services/session.ts",
118
+ "line": 20,
119
+ "end_line": 20,
120
+ "severity": "minor",
121
+ "confidence": 0.3,
122
+ "title": "[SEC-7] Possible session concern",
123
+ "body": "The session handling might expose sensitive data in some circumstances.",
124
+ "fix": "Review the session handling.",
125
+ "sources": ["quality+impl"],
126
+ "lenses": ["lekker-quality"],
127
+ "verdict": "rejected"
128
+ },
129
+ {
130
+ "id": "f10",
131
+ "file": "web/services/order-sync.test.ts",
132
+ "line": 55,
133
+ "end_line": 55,
134
+ "severity": "major",
135
+ "confidence": 0.85,
136
+ "title": "Operator-flip mutation escapes the happy-path assertion",
137
+ "body": "Changing the retry guard from && to || still passes the only assertion.",
138
+ "fix": "Add a case that exercises one false operand.",
139
+ "sources": ["tests"],
140
+ "lenses": ["lekker-test-quality"],
141
+ "verdict": "confirmed"
142
+ },
143
+ {
144
+ "id": "f11",
145
+ "file": "web/services/order-sync.test.ts",
146
+ "line": 70,
147
+ "end_line": 70,
148
+ "severity": "minor",
149
+ "confidence": 0.8,
150
+ "title": "Mock assertion freezes the client call shape",
151
+ "body": "The test asserts toHaveBeenCalledWith instead of the observable result.",
152
+ "fix": "Assert the returned synchronization state.",
153
+ "sources": ["tests"],
154
+ "lenses": ["lekker-test-quality"],
155
+ "verdict": "confirmed"
156
+ }
157
+ ],
158
+ "pre_existing": [
159
+ {
160
+ "file": "web/services/order-sync.ts",
161
+ "line": 200,
162
+ "title": "Repo-wide: no retry backoff on the fulfillment API client",
163
+ "body": "Pre-existing since before this diff; noted for awareness only.",
164
+ "lenses": ["lekker-implementation"]
165
+ }
166
+ ],
167
+ "open_questions": [
168
+ "Is the webhook retry expected to be idempotent, or does the client dedupe upstream?"
169
+ ],
170
+ "sources": {
171
+ "agents": [
172
+ { "name": "quality+impl", "executor": "claude", "actual_model": "claude-sonnet-5", "effort": "medium", "tokens": 42000, "raised": 4, "degraded": false },
173
+ { "name": "simpl+conventions", "executor": "claude", "actual_model": "claude-sonnet-5", "effort": "medium", "tokens": 31000, "raised": 2, "degraded": false },
174
+ { "name": "adversarial", "executor": "claude", "actual_model": "claude-sonnet-5", "effort": "high", "tokens": 18000, "raised": 2, "degraded": true },
175
+ { "name": "malformed-cost", "executor": "claude", "actual_model": "claude-sonnet-5", "effort": "medium", "tokens": "unknown", "raised": 0, "degraded": false }
176
+ ]
177
+ },
178
+ "stats": { "tokens": 91000 }
179
+ }
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env bash
2
+ # Installs lekker's revmux lenses + profiles into ~/.config/revmux (layer 2),
3
+ # idempotently. --check verifies without writing.
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
8
+ SRC_LENSES="$REPO_ROOT/references/revmux/lenses"
9
+ SRC_PROFILES="$REPO_ROOT/references/revmux/profiles"
10
+
11
+ CONFIG_DIR="${REVMUX_CONFIG_DIR:-$HOME/.config/revmux}"
12
+ DST_LENSES="$CONFIG_DIR/lenses"
13
+ DST_PROFILES="$CONFIG_DIR/prompts/profiles"
14
+
15
+ CHECK_MODE=0
16
+ if [ "${1:-}" = "--check" ]; then
17
+ CHECK_MODE=1
18
+ fi
19
+
20
+ if grep -rln 'codex' "$SRC_LENSES" "$SRC_PROFILES" >/dev/null 2>&1; then
21
+ echo "refusing to install: codex reference found under references/revmux/" >&2
22
+ exit 1
23
+ fi
24
+
25
+ FAIL=0
26
+
27
+ sync_one() {
28
+ local src="$1" dst="$2"
29
+ local name
30
+ name="$(basename "$src")"
31
+
32
+ if [ "$CHECK_MODE" -eq 1 ]; then
33
+ if [ ! -f "$dst" ]; then
34
+ echo "missing: $name"
35
+ FAIL=1
36
+ elif ! diff -q "$src" "$dst" >/dev/null 2>&1; then
37
+ echo "differs: $name"
38
+ FAIL=1
39
+ fi
40
+ return
41
+ fi
42
+
43
+ mkdir -p "$(dirname "$dst")"
44
+ if [ ! -f "$dst" ]; then
45
+ cp "$src" "$dst"
46
+ echo "installed: $name"
47
+ elif diff -q "$src" "$dst" >/dev/null 2>&1; then
48
+ echo "unchanged: $name"
49
+ else
50
+ echo "updated: $name"
51
+ diff -u "$dst" "$src" || true
52
+ cp "$src" "$dst"
53
+ fi
54
+ }
55
+
56
+ for f in "$SRC_LENSES"/*.md; do
57
+ sync_one "$f" "$DST_LENSES/$(basename "$f")"
58
+ done
59
+
60
+ for f in "$SRC_PROFILES"/*.md; do
61
+ sync_one "$f" "$DST_PROFILES/$(basename "$f")"
62
+ done
63
+
64
+ if [ "$CHECK_MODE" -eq 1 ]; then
65
+ exit "$FAIL"
66
+ fi
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env node
2
+ // revmux-adapter.mjs -- turns a revmux report into lekker's findings JSON.
3
+ //
4
+ // Usage: node scripts/revmux-adapter.mjs <revmux.json> [--pricing FILE] [--context FILE] > findings.json
5
+ import { readFileSync } from 'node:fs'
6
+ import { dirname, resolve } from 'node:path'
7
+ import { fileURLToPath } from 'node:url'
8
+
9
+ const args = process.argv.slice(2)
10
+ const reportPath = args[0]
11
+ if (!reportPath) {
12
+ console.error('usage: revmux-adapter.mjs <revmux.json> [--pricing FILE] [--context FILE]')
13
+ process.exit(2)
14
+ }
15
+ function flagValue(name) {
16
+ const i = args.indexOf(name)
17
+ if (i === -1) return null
18
+ const value = args[i + 1]
19
+ if (!value || value.startsWith('--')) {
20
+ console.error(`revmux-adapter: ${name} requires a file path`)
21
+ process.exit(2)
22
+ }
23
+ return value
24
+ }
25
+ const pricingPath = flagValue('--pricing')
26
+ const contextPath = flagValue('--context')
27
+
28
+ function loadJson(label, path) {
29
+ try {
30
+ return JSON.parse(readFileSync(path, 'utf8'))
31
+ } catch (error) {
32
+ console.error(`revmux-adapter: cannot read or parse ${label} JSON at ${path}: ${error.message}`)
33
+ process.exit(2)
34
+ }
35
+ }
36
+
37
+ const report = loadJson('report', reportPath)
38
+ const pricing = pricingPath ? loadJson('pricing', pricingPath) : {}
39
+ const contextJson = contextPath ? loadJson('context', contextPath) : null
40
+
41
+ function configuredHardRules() {
42
+ const defaultPath = fileURLToPath(new URL('../references/house-rules.md', import.meta.url))
43
+ const configuredPath = (contextJson && typeof contextJson.houseRulesFile === 'string')
44
+ ? contextJson.houseRulesFile
45
+ : defaultPath
46
+ const houseRulesPath = contextPath && !configuredPath.startsWith('/')
47
+ ? resolve(dirname(contextPath), configuredPath)
48
+ : configuredPath
49
+
50
+ let text
51
+ try {
52
+ text = readFileSync(houseRulesPath, 'utf8')
53
+ } catch (error) {
54
+ console.error(`revmux-adapter: cannot read configured house rules at ${houseRulesPath}: ${error.message}`)
55
+ process.exit(2)
56
+ }
57
+
58
+ const tags = new Set()
59
+ for (const match of text.matchAll(/^#{2,6}\s+([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+)\b/gm)) {
60
+ if (!match[1].startsWith('EXAMPLE-')) tags.add(match[1])
61
+ }
62
+ return tags
63
+ }
64
+
65
+ const HARD_RULES = configuredHardRules()
66
+
67
+ function hardRuleCorroborated(finding) {
68
+ const evidence = [finding.badCode, finding.description, finding.title]
69
+ .map(function(x) { return String(x || '') }).join('\n')
70
+ const file = String(finding.file || '')
71
+ const code = String(finding.badCode || '')
72
+
73
+ switch (finding.rule) {
74
+ case 'TS-1':
75
+ return /\bas\s+(?:[A-Z_$][\w$]*|string|number|boolean|bigint|symbol|object|unknown|never|any|const)\b/.test(code)
76
+ || /:\s*any\b|<\s*any[\s,>]|\bany\[\]/.test(code)
77
+ case 'TS-2':
78
+ return /\.js$/.test(file)
79
+ case 'GQL-1':
80
+ return /\bnodes\b|\bpageInfo\b|\bedges\b/.test(evidence)
81
+ case 'PR-1':
82
+ return /pr\s*(title|description)/i.test(file + '\n' + evidence)
83
+ default:
84
+ return code.trim().length > 0
85
+ }
86
+ }
87
+
88
+ function isHardRule(finding) {
89
+ if (typeof finding.rule !== 'string' || !HARD_RULES.has(finding.rule)) {
90
+ return false
91
+ }
92
+ return hardRuleCorroborated(finding)
93
+ }
94
+
95
+ function extractRule(title) {
96
+ const m = /^\[([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+)\]/.exec(String(title || ''))
97
+ return m && HARD_RULES.has(m[1]) ? m[1] : undefined
98
+ }
99
+
100
+ function mapSeverity(f) {
101
+ if (f.severity === 'critical') return 'critical'
102
+ if (f.severity === 'major') return 'important'
103
+ if (f.severity === 'minor') {
104
+ return (f.lenses || []).includes('lekker-conventions') ? 'idiomatic' : 'observation'
105
+ }
106
+ return 'observation'
107
+ }
108
+
109
+ // Pull an inline code snippet out of the body so hardRuleCorroborated has
110
+ // something to check against; revmux findings carry no separate badCode field.
111
+ function extractBadCode(body) {
112
+ const text = String(body || '')
113
+ const backtick = /`([^`]+)`/.exec(text)
114
+ if (backtick) return backtick[1]
115
+ const dashCode = /--\s*(.+)$/.exec(text.split('\n')[0])
116
+ return dashCode ? dashCode[1] : ''
117
+ }
118
+
119
+ const findings = []
120
+ let droppedCount = 0
121
+ let hardRuleCount = 0
122
+
123
+ for (const f of report.findings || []) {
124
+ const rule = extractRule(f.title)
125
+ const badCode = extractBadCode(f.body)
126
+ const base = {
127
+ file: f.file,
128
+ line: f.line,
129
+ severity: mapSeverity(f),
130
+ title: f.title,
131
+ description: f.body,
132
+ badCode,
133
+ fix: f.fix,
134
+ confidence: f.confidence,
135
+ rule,
136
+ dimension: f.lenses || [],
137
+ }
138
+ if ((f.sources || []).length >= 2) {
139
+ base.agreedBy = f.sources
140
+ }
141
+
142
+ const rejectedOrImmaterial = f.verdict === 'rejected' || f.verdict === 'immaterial'
143
+
144
+ if (!rejectedOrImmaterial) {
145
+ base.verifierReasoning = `revmux verify: ${f.verdict}`
146
+ findings.push(base)
147
+ continue
148
+ }
149
+
150
+ // A hard-rule tag whose own quoted code corroborates it is Critical
151
+ // regardless of revmux's verdict.
152
+ if (isHardRule({ rule, badCode, description: f.body, title: f.title, file: f.file })) {
153
+ hardRuleCount++
154
+ base.severity = 'critical'
155
+ base.verifierReasoning = `hard rule ${rule}: corroborated by quoted code, re-promoted despite revmux verdict "${f.verdict}"`
156
+ findings.push(base)
157
+ continue
158
+ }
159
+
160
+ droppedCount++
161
+ }
162
+
163
+ const reportFindings = findings.slice()
164
+
165
+ for (const pe of report.pre_existing || []) {
166
+ findings.push({
167
+ file: pe.file,
168
+ line: pe.line,
169
+ severity: 'observation',
170
+ title: pe.title,
171
+ description: `Pre-existing: ${pe.body}`,
172
+ badCode: extractBadCode(pe.body),
173
+ fix: '',
174
+ dimension: pe.lenses || [],
175
+ })
176
+ }
177
+
178
+ const questions = report.open_questions || []
179
+
180
+ const implementationFindings = reportFindings.filter(function(f) {
181
+ return f.dimension.includes('lekker-implementation')
182
+ })
183
+ const testQualityFindings = reportFindings.filter(function(f) {
184
+ return f.dimension.includes('lekker-test-quality')
185
+ })
186
+ const mutationFindings = testQualityFindings.filter(function(f) {
187
+ return /mutation|operator flip|off-by-one/i.test(`${f.title}\n${f.description}`)
188
+ })
189
+ const mockFindings = testQualityFindings.filter(function(f) {
190
+ return /\bmock(?:ing|ed|s)?\b|\bspy(?:ing)?\b|toHaveBeenCalled/i.test(`${f.title}\n${f.description}`)
191
+ })
192
+
193
+ const acCoverage = implementationFindings.length > 0
194
+ ? implementationFindings.map(function(f) { return `${f.title}: ${f.description}` }).join('\n')
195
+ : 'No acceptance-criteria gaps were reported by the revmux implementation lens.'
196
+ const coverageVerdict = testQualityFindings.length > 0
197
+ ? `${testQualityFindings.length} test-quality finding(s): ${testQualityFindings.map(function(f) { return f.title }).join('; ')}`
198
+ : 'No test-quality gaps were reported by the revmux test-quality lens.'
199
+ const mutationSlip = mutationFindings.length > 0
200
+ ? mutationFindings.map(function(f) { return f.description }).join('\n')
201
+ : 'No mutation-slip gap was reported by the revmux test-quality lens.'
202
+ const mockSmells = mockFindings.map(function(f) {
203
+ return { file: f.file, line: f.line, description: f.description, fix: f.fix }
204
+ })
205
+
206
+ const agentsIn = (report.sources && report.sources.agents) || []
207
+ const pricingMissing = []
208
+ let totalUsd = 0
209
+ let anyUsdKnown = false
210
+
211
+ const agents = agentsIn.map(function(a) {
212
+ const priceEntry = pricing[a.actual_model]
213
+ let usd = null
214
+ if (priceEntry) {
215
+ const rate = (typeof priceEntry.output === 'number') ? priceEntry.output : priceEntry.blended
216
+ if (typeof a.tokens === 'number' && Number.isFinite(a.tokens) && typeof rate === 'number' && Number.isFinite(rate)) {
217
+ usd = (a.tokens / 1e6) * rate
218
+ anyUsdKnown = true
219
+ totalUsd += usd
220
+ }
221
+ } else {
222
+ pricingMissing.push(a.actual_model)
223
+ }
224
+ return {
225
+ name: a.name,
226
+ model: a.actual_model,
227
+ effort: a.effort,
228
+ tokens: a.tokens,
229
+ usd,
230
+ raised: a.raised,
231
+ degraded: Boolean(a.degraded),
232
+ }
233
+ })
234
+
235
+ const degraded = agents.filter(function(a) { return a.degraded }).map(function(a) { return a.name })
236
+ const totalTokens = (report.stats && report.stats.tokens) || agentsIn.reduce(function(s, a) { return s + (a.tokens || 0) }, 0)
237
+
238
+ const output = {
239
+ engine: 'revmux',
240
+ findings,
241
+ questions,
242
+ droppedCount,
243
+ downgradedCount: 0,
244
+ hardRuleCount,
245
+ agentCount: agentsIn.length,
246
+ degraded,
247
+ agents,
248
+ totalTokens,
249
+ totalUsd: anyUsdKnown ? totalUsd : null,
250
+ acCoverage,
251
+ coverageVerdict,
252
+ mutationSlip,
253
+ mockSmells,
254
+ stats: { durationMs: (report.stats && report.stats.durationMs) || null },
255
+ }
256
+
257
+ if (pricingMissing.length > 0) {
258
+ output.pricingMissing = Array.from(new Set(pricingMissing))
259
+ }
260
+
261
+ process.stdout.write(JSON.stringify(output, null, 2) + '\n')
@@ -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"