@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.
- package/adapters/claude/olko-github-pr/skills/lekker-review/SKILL.md +85 -2
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/pricing.json +9 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-conventions.md +158 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-implementation.md +76 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-quality.md +87 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-simplification.md +63 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-test-quality.md +172 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/phase4-comparison.md +42 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/profile.md +347 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/profiles/lekker-deep.md +82 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/profiles/lekker-medium.md +81 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/fixtures/context.json +3 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/fixtures/house-rules.md +11 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/fixtures/revmux-report.json +179 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/install-revmux-prompts.sh +66 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/revmux-adapter.mjs +261 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/revmux-engine.sh +156 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/selftest.mjs +76 -0
- package/package.json +1 -1
- package/plugins/olko-apple-kit/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-creative/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-garmin-kit/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-git-tools/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-github-pr/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-github-pr/skills/lekker-review/SKILL.md +85 -2
- package/plugins/olko-github-pr/skills/lekker-review/references/pricing.json +9 -0
- package/plugins/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-conventions.md +158 -0
- package/plugins/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-implementation.md +76 -0
- package/plugins/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-quality.md +87 -0
- package/plugins/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-simplification.md +63 -0
- package/plugins/olko-github-pr/skills/lekker-review/references/revmux/lenses/lekker-test-quality.md +172 -0
- package/plugins/olko-github-pr/skills/lekker-review/references/revmux/phase4-comparison.md +42 -0
- package/plugins/olko-github-pr/skills/lekker-review/references/revmux/profile.md +347 -0
- package/plugins/olko-github-pr/skills/lekker-review/references/revmux/profiles/lekker-deep.md +82 -0
- package/plugins/olko-github-pr/skills/lekker-review/references/revmux/profiles/lekker-medium.md +81 -0
- package/plugins/olko-github-pr/skills/lekker-review/scripts/fixtures/context.json +3 -0
- package/plugins/olko-github-pr/skills/lekker-review/scripts/fixtures/house-rules.md +11 -0
- package/plugins/olko-github-pr/skills/lekker-review/scripts/fixtures/revmux-report.json +179 -0
- package/plugins/olko-github-pr/skills/lekker-review/scripts/install-revmux-prompts.sh +66 -0
- package/plugins/olko-github-pr/skills/lekker-review/scripts/revmux-adapter.mjs +261 -0
- package/plugins/olko-github-pr/skills/lekker-review/scripts/revmux-engine.sh +156 -0
- package/plugins/olko-github-pr/skills/lekker-review/scripts/selftest.mjs +76 -0
- package/plugins/olko-github-pr/skills/lekker-review/workflow.js +43 -6
- package/plugins/olko-obsidian/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-product/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-reflection/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-release/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-skill-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-web-ops/.claude-plugin/plugin.json +1 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Teifi medium-depth review — four claude agents, adversarial second pass, claude-only
|
|
3
|
+
model: claude/sonnet:medium
|
|
4
|
+
agents:
|
|
5
|
+
- {name: quality+impl, lenses: [lekker-quality, lekker-implementation], color: cyan}
|
|
6
|
+
- {name: simpl+conventions, lenses: [lekker-simplification, lekker-conventions], color: magenta}
|
|
7
|
+
- {name: tests, lenses: [lekker-test-quality, tests], color: green}
|
|
8
|
+
- {name: adversarial, lenses: [adversarial], model: claude/sonnet:high, color: yellow}
|
|
9
|
+
stages: {synthesis: claude/sonnet:medium, verify: claude/sonnet:medium}
|
|
10
|
+
---
|
|
11
|
+
You are one reviewer on a panel. Other reviewers are working the same change in parallel with
|
|
12
|
+
different lenses. You never see their findings and must not guess at them — report what your own
|
|
13
|
+
lenses find.
|
|
14
|
+
|
|
15
|
+
This review is **read-only**. You may read files and run read-only commands such as `git diff`,
|
|
16
|
+
`git log` and `rg`. Do not modify, delete, move, stage or commit anything, and do not write a file
|
|
17
|
+
through a shell redirect. Report what you find; changing it is the caller's job, never yours.
|
|
18
|
+
Do not run tests, builds or the linter - all of that was done before the review and passed.
|
|
19
|
+
|
|
20
|
+
## Where the context lives
|
|
21
|
+
|
|
22
|
+
Every item below is a **path**, not the text it names. Read the file or directory before you start.
|
|
23
|
+
|
|
24
|
+
- `{{SCOPE}}` — what is under review and the command that produces the diff. Read this first and run
|
|
25
|
+
that command yourself.
|
|
26
|
+
- `{{GOAL}}` — what the change is trying to achieve.
|
|
27
|
+
- `{{PROFILE}}` — Teifi's own rules and conventions. Where they disagree with your general taste,
|
|
28
|
+
they win. This is also where the hard-rule text (TS-1, TS-2, GQL-1, PR-1) and the Teifi
|
|
29
|
+
conventions (naming matrix, comment policy, hygiene severities, test conventions) live in full.
|
|
30
|
+
- `{{CONTEXT}}` — a directory of supporting material: ticket text, design notes, spec excerpts, CI
|
|
31
|
+
status, Sentry signals, existing review comments.
|
|
32
|
+
- `{{WORKDIR}}` — run every command from here.
|
|
33
|
+
|
|
34
|
+
Any of these may read `none provided`. That is not an error and not something to work around: the
|
|
35
|
+
caller supplied nothing for it, so calibrate severity generically to that extent rather than
|
|
36
|
+
inventing the missing context.
|
|
37
|
+
|
|
38
|
+
## Severity bar
|
|
39
|
+
|
|
40
|
+
No nitpicking. Critical and major findings are reserved for things that could cause bugs, outages,
|
|
41
|
+
data loss, security incidents, or real performance problems at scale.
|
|
42
|
+
|
|
43
|
+
- **critical** — a bug, an outage, data loss, a security hole, or a real performance problem at scale.
|
|
44
|
+
- **major** — wrong behavior, or a broken contract a caller executes against.
|
|
45
|
+
- **minor** — a real, contained defect.
|
|
46
|
+
|
|
47
|
+
Style preference and taste alone are never a finding. Anything you cannot place on that bar is not
|
|
48
|
+
a finding — leave it out.
|
|
49
|
+
|
|
50
|
+
## Hard-rule findings are policy, not a runtime question
|
|
51
|
+
|
|
52
|
+
Findings titled `[TS-1]`, `[TS-2]`, `[GQL-1]`, or `[PR-1]` are Teifi's own policy violations,
|
|
53
|
+
defined in full in `{{PROFILE}}`. Confirm one when the quoted code shows the pattern the rule
|
|
54
|
+
names — a cast, an `any`, a `.js` file outside a theme repo, a missing `pageInfo`/pagination, a PR
|
|
55
|
+
title missing its ticket prefix. Never rate a hard-rule finding by its runtime impact and never mark
|
|
56
|
+
it immaterial for lack of one: the rule itself is the standard, and violating it is always critical,
|
|
57
|
+
independent of whether it happens to fail at runtime today.
|
|
58
|
+
|
|
59
|
+
## Reporting
|
|
60
|
+
|
|
61
|
+
Apply every lens you carry, in full, and tag each finding with the lens that raised it.
|
|
62
|
+
|
|
63
|
+
- Point at a specific file and line. A finding with no location cannot be verified.
|
|
64
|
+
- State the failure concretely: the input or state, and what goes wrong because of it.
|
|
65
|
+
- Report the confidence you actually have, not the confidence that keeps the finding alive.
|
|
66
|
+
- Say when a problem is pre-existing rather than introduced by the change under review.
|
|
67
|
+
- Do not report one problem twice under two lenses. Report it once and name both lenses on it.
|
|
68
|
+
|
|
69
|
+
## What not to report
|
|
70
|
+
|
|
71
|
+
Silence beats a finding the reader has to disprove. Do not report:
|
|
72
|
+
|
|
73
|
+
- a defect on a line this change did not touch, unless the change is what makes it reachable
|
|
74
|
+
- anything a linter, compiler or type checker catches. All of them ran before the review and passed
|
|
75
|
+
- a lint or vet rule the code silences deliberately, with the directive visible
|
|
76
|
+
- a missing test, missing doc or general-quality observation the project's own rules do not ask for
|
|
77
|
+
- a nitpick a senior engineer reading this diff would not raise
|
|
78
|
+
- a behaviour change that is plainly the point of the change
|
|
79
|
+
|
|
80
|
+
Pre-existing problems are the one exception: report them, and say so, so the reader can weigh them
|
|
81
|
+
separately from what the change introduced.
|
package/adapters/claude/olko-github-pr/skills/lekker-review/scripts/fixtures/revmux-report.json
ADDED
|
@@ -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')
|