Hi devs,
I’d like to propose that we run the xwiki-review skill automatically, once a day, over everything pushed since the previous run — and possibly on PRs too. Below: what it checks, what it actually found today on the last 24h of master, where the output would go, and what it costs. The cost is real and it’s the main thing I want your opinion on.
What xwiki-review checks
It’s not “an LLM reads the diff”. It fans out eleven narrow reviewers over the same change, each reading it through one lens, each required to ground every finding in a rule this project actually holds — an OKF file, a skill, a CLAUDE.md, or a code fact. Generic best-practice folklore is rejected by construction.
| Angle | Runs when |
|---|---|
| Conventions | any source file (code style, comments, commit messages, logging) |
| Architecture | any .java (component system, module deps, oldcore placement) |
| Backward compatibility | .java outside test/legacy (@since, @Unstable, signatures) |
| Defensive conventions | always — see note below |
| Performance | always |
| Tests | always (a change with no test is itself the finding) |
| Accessibility | .vm, .html, .vue, .js, .less, UI .xml |
| i18n & UX | any user-facing string, .properties, .vm, sheets |
| Documentation | new/changed public API, user-visible behaviour |
| Data & migration | *.hbm.xml, R*XWIKI*.java, Solr schema, XClass, XAR pages |
| Spec conformance | when a JIRA key is found — diff vs. the issue’s own text |
Then — and this is the part that makes it usable — every finding is handed to an independent skeptic whose job is to refute it. The skeptic must open the cited file and confirm the rule actually says what was claimed, including any clause that limits its scope. Only confirmed and certain survive; everything else is dropped silently. A review nobody trusts is worse than no review, so the whole protocol is built around suppressing false positives rather than maximising findings.
One deliberate design note on the “defensive conventions” angle: it checks mechanisms, not attacks — is the escaper there, is the check there, is the value treated as untrusted — phrased exactly as a linter would. It does not assess exploitability and never names a problem class. That’s because CI logs on a public repo are public, so there is no safe place in an automated pipeline to say that something is a hole. It covers the conventions; it explicitly does not replace a security review and must not be described as one.
Real example: today’s run on the last 24h of master
I ran it this morning over b88754f93a5..c291686077 — 7 commits, 48 files, +1082/−87 (XWIKI-24796 Live Data/PDF export, XWIKI-22936 SearchSuggestCode, and two [Misc] test-framework commits).
12 raw findings → 5 survived the skeptic stage. All five are minor. Five angles (architecture, defensive conventions, performance, accessibility, data & migration) came back clean from the outset.
What survived:
- A dead event listener, plus a comment that says otherwise.
searchSuggest.jsnow lazily creates the suggest widget onmouseenterorfocusof the quick-search field. But that field is rendereddisabled, andaction-menus.lesscollapses it towidth:0; padding:0while the button isaria-expanded='false'— a zero-area disabled control can’t receive a pointer, andmouseenterdoesn’t bubble. So themouseenterhalf is unreachable 100% of the time in Flamingo, and the comment claiming the pointer is taken into account is wrong. Nothing breaks (minchars:3and a catch-up call cover it) — but the intended optimisation isn’t there. Three of the eleven reviewers found this independently. Documentation in Release Notes = N/Aon XWIKI-24796, which ships three new public@Unstablescript-service methods ($services.uix.render()/renderExtensions()). The OKF rule reservesN/Afor a fix with no user- or developer-visible impact. A new scripting API is developer-visible, so 18.8.0’s release notes would carry no record of it.@param filtersdoesn’t document thatnullis accepted on the newrenderExtensions(), although the implementation branches on it and a test asserts it. Worse, the@seetarget’s own@paramcalls the filters “Optional” — on a method that NPEs on a null map. A reader following the link draws the opposite of the truth.- A new member on
BasePagewith no@since— page objects in*-test-*modules carry@since“whatever the visibility”, and Revapi is skipped in that tree so nothing in the build catches it. - A new test importing
javax.injectwhile the new class it tests usesjakarta.inject— both files added in the same commit.
Equally interesting is what the skeptics killed, because that’s the noise you’d otherwise be reading:
- “XWIKI-24796 should be split or retitled” (raised as a blocker-ish major) — refuted: the doc fields were already filled, and no rule governs issue breadth.
- “The logging call breaks the
[]convention” — refuted: the value is already bracketed, and applying the suggested fix would produce the double brackets the same rule explicitly forbids. The skeptic also found that a committer-run logging audit had deliberately left an equivalent pattern alone. - “The JS change is untested” — refuted: the quote had been truncated before the strategy page’s “tests that do not rely on the DOM” clause, which routes this change to a functional test. And
SearchSuggestITturns out to depend on the new code. - “Failed fetch permanently disables suggestions” — pre-existing, identical before the change.
Four of the seven dropped findings were plausible-sounding and wrong. Without the skeptic stage this would be a nuisance, not a tool.
Where the digest gets published
It has to be somewhere publicly visible where you can answer inline, so the forum — this category. The alternatives don’t work for a daily range: GitHub commit comments are next to the code but unreadable as a stream and impossible to discuss, and JIRA is per-issue so a range spanning several issues has nowhere to land. The forum is the only one of the three where replying is natural.
Concretely:
- One topic per month, one reply per day — e.g. “Automated review digests — September 2026”. A single permanent thread would mean that anyone who replies once gets notified every day forever, and they’d mute it, which kills the
/
signal the whole tuning loop depends on. A topic per day would isolate notifications properly and give a scannable list, but it’s ~250 topics/year and really wants its own subcategory — worth revisiting if the volume justifies it. - Posted by a dedicated bot account, not a committer’s. A digest arriving daily under a personal account reads as though that person wrote each one and floods their activity feed. Discourse supports this, and it’s worth settling before the first automated run since authorship can’t be migrated afterwards.
- Quiet days don’t post. Most days will have no blockers and no majors. Posting “nothing found” twenty times a month is how people learn to skip it — better to post only when something survives verification, plus a one-line monthly tally so the silent days are still accounted for.
Here is what one actually looks like, using today’s run:
Example digest — master, 2026-09-08
Automated review — generated by Claude Code. It can be wrong. Treat every point below as a question, not a verdict: verify the claim against the code before acting on it, and dismiss anything that does not hold.
Automated review — master, 2026-09-07 → 2026-09-08
Reviewed 48 files (7 commits, +1082/−87) across all 11 angles. 12 raw findings, 5 after verification.
Range: b88754f93a5..c291686077 · XWIKI-24796, XWIKI-22936, 2 × [Misc]
Blockers — none.
Major — none.
Minor
-
The
mouseenterhalf of the new lazy-init guard is unreachable, and its comment states the opposite — the field is rendereddisabledand collapsed towidth:0; padding:0whilearia-expanded='false', so it cannot receive a pointer, andmouseenterdoes not bubble. Nothing breaks (minchars:3plus the catch-up call cover it); the intended pre-warm is simply absent. Rule:okf/conventions/code-comments.md
searchSuggest.js#L208-L221 -
Documentation in Release NotesisN/Aon XWIKI-24796, which ships three new public@Unstablescript-service methods —N/Ais reserved for a fix with no user- or developer-visible impact. Rule:okf/servers/jira.md(scoped “when closing”; the issue is still In Progress, so this is a heads-up, not a breach)
UIExtensionScriptService.java#L160-L170 -
@param filtersdoes not document thatnullis accepted, though the implementation branches on it andUIExtensionScriptServiceTest:198asserts it — and the@seetarget’s own@paramcalls the filters “Optional” on a method that NPEs on a null map. Rule:xwiki-javadocskill, “documentnull/ empty / absent behaviour”
UIExtensionScriptService.java#L208-L212 -
New member on
BasePagecarries no@since—okf/conventions/versioning.mdcovers page objects in*-test-*modules “whatever the module and whatever the visibility”, and Revapi is skipped in that tree (xwiki-platform-test/pom.xml:37) so the build cannot catch it.
BasePage.java#L774-L780 -
New test imports
javax.injectwhile the new class under test usesjakarta.inject— both files added in the same commit. Rule:okf/conventions/code-style.md§javax→jakarta, and the org-wideCLAUDE.md
UIExtensionRendererTest.java#L25-L28
No angles were skipped. Architecture, defensive conventions, performance, accessibility and data & migration returned no findings.
7 findings dropped in verification
| Finding | Verdict | Why |
|---|---|---|
| XWIKI-24796 should be split or retitled | false | Doc fields already filled; no rule governs issue breadth |
Logging call breaks the [] convention |
false | Value already bracketed; the fix would produce the double brackets the same rule forbids |
| The JS change is untested | false | Citation truncated before the “tests that do not rely on the DOM” clause; SearchSuggestIT depends on the new code |
| Failed fetch permanently disables suggestions | false | Pre-existing; identical before the change |
| Page test does not exercise the wiki-UIX chain | false | Cited rule governs commit-message format, not test depth |
xhtml/1.0 → html/5.0 is scope creep |
unverified | Two of three cited sites were already html/5.0 |
e.g. in new Javadoc |
nitpick | Real rule, but 84 existing occurrences vs 131 “for example” |
Cost: ~1.83M tokens across 20 subagents (11 reviewers, 9 skeptics).
Was this useful? React
or
— the reactions are read back monthly to tune the reviewer.
In the real thing every finding carries a full-SHA permalink into the code and a link to the rule it cites; I’ve trimmed those to filenames here to keep the example readable.
The cost
This is the honest downside. Today’s run consumed ~1.83M tokens across 20 subagents (11 reviewers averaging ~110k, 9 skeptics averaging ~68k), plus orchestration — call it ~2M tokens for one day’s worth of commits. Daily, that’s roughly 60M tokens/month for master alone. Running it on PRs as well would multiply that by our PR volume.
So the question isn’t “is it good” — I think today’s run answers that — it’s “is it worth that much”.
Proposal
Start narrow and measure:
- Daily routine on
master(mainfor the other repos), covering everything since the previous run, publishing one digest to this category as described above. Skip automatically when the diff is only a license/xar:format/l10n sync or a Renovate bump — the skill already gates on this. - Not on PRs initially. Revisit once we have a month of daily data on the true-positive rate.
- A
/
on each digest, read back monthly, to tune which angles earn their keep. If an angle produces nothing but noise for a month, drop it from the routing and the cost falls with it. - Keep it advisory. It’s a second pair of eyes, never a gate, and never a substitute for human review.
Open questions
- Is ~60M tokens/month acceptable for
masteronly? Who pays — is this a shared org budget or does it run on one committer’s account? - Should it run per-commit-range daily, or only on days with substantial changes (say >200 changed lines)?
- Any objection to a bot account posting daily in this category — and does someone with forum admin rights want to create it?
- Several angles found no rule to cite for things they wanted to flag — e.g. the OKF has no home at all for front-end/network performance (lazy loading, cache headers, request count per page view), which is exactly what XWIKI-22936 was about. Those become candidate conventions. Are we happy for the routine to feed the OKF that way?
- For those of you not on Claude Code — the skill is a plain Markdown protocol, so in principle another tool could run it. Worth keeping it tool-agnostic?
WDYT?