revu-ai 0.1.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 (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +166 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +252 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/concurrency.d.ts +1 -0
  7. package/dist/concurrency.js +31 -0
  8. package/dist/concurrency.js.map +1 -0
  9. package/dist/config.d.ts +18 -0
  10. package/dist/config.js +70 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/discovery.d.ts +3 -0
  13. package/dist/discovery.js +39 -0
  14. package/dist/discovery.js.map +1 -0
  15. package/dist/index.d.ts +9 -0
  16. package/dist/index.js +6 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/init.d.ts +26 -0
  19. package/dist/init.js +50 -0
  20. package/dist/init.js.map +1 -0
  21. package/dist/mcp/aggregator.d.ts +11 -0
  22. package/dist/mcp/aggregator.js +47 -0
  23. package/dist/mcp/aggregator.js.map +1 -0
  24. package/dist/mcp/server.d.ts +14 -0
  25. package/dist/mcp/server.js +105 -0
  26. package/dist/mcp/server.js.map +1 -0
  27. package/dist/mcp/tools.d.ts +33 -0
  28. package/dist/mcp/tools.js +32 -0
  29. package/dist/mcp/tools.js.map +1 -0
  30. package/dist/output/github.d.ts +2 -0
  31. package/dist/output/github.js +36 -0
  32. package/dist/output/github.js.map +1 -0
  33. package/dist/output/json.d.ts +2 -0
  34. package/dist/output/json.js +9 -0
  35. package/dist/output/json.js.map +1 -0
  36. package/dist/output/pretty.d.ts +2 -0
  37. package/dist/output/pretty.js +142 -0
  38. package/dist/output/pretty.js.map +1 -0
  39. package/dist/prompts/init-system.d.ts +4 -0
  40. package/dist/prompts/init-system.js +148 -0
  41. package/dist/prompts/init-system.js.map +1 -0
  42. package/dist/prompts/init-user.d.ts +5 -0
  43. package/dist/prompts/init-user.js +20 -0
  44. package/dist/prompts/init-user.js.map +1 -0
  45. package/dist/prompts/review-system.d.ts +6 -0
  46. package/dist/prompts/review-system.js +61 -0
  47. package/dist/prompts/review-system.js.map +1 -0
  48. package/dist/prompts/review-user.d.ts +2 -0
  49. package/dist/prompts/review-user.js +10 -0
  50. package/dist/prompts/review-user.js.map +1 -0
  51. package/dist/providers/claude-code.d.ts +9 -0
  52. package/dist/providers/claude-code.js +481 -0
  53. package/dist/providers/claude-code.js.map +1 -0
  54. package/dist/providers/registry.d.ts +8 -0
  55. package/dist/providers/registry.js +60 -0
  56. package/dist/providers/registry.js.map +1 -0
  57. package/dist/providers/types.d.ts +70 -0
  58. package/dist/providers/types.js +2 -0
  59. package/dist/providers/types.js.map +1 -0
  60. package/dist/refs.d.ts +9 -0
  61. package/dist/refs.js +81 -0
  62. package/dist/refs.js.map +1 -0
  63. package/dist/runner.d.ts +23 -0
  64. package/dist/runner.js +106 -0
  65. package/dist/runner.js.map +1 -0
  66. package/dist/types.d.ts +68 -0
  67. package/dist/types.js +8 -0
  68. package/dist/types.js.map +1 -0
  69. package/examples/github-workflow.yml +38 -0
  70. package/package.json +73 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PKWadsworth
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # revu
2
+
3
+ Parallel AI code review for any git repo. You drop Markdown rule files (`*.revu.md`) anywhere in your project; on each run, `revu` spawns a separate Claude agent **per rule file** in parallel. Each agent reviews the current diff *only through the lens of its assigned rules* and reports findings back through a sidecar MCP server. The aggregated findings come out as JSON, pretty terminal output, or GitHub Actions PR annotations.
4
+
5
+ The point: instead of one giant "be a good reviewer" prompt, you write narrow, scoped rule files (dead code, contract enforcement, dependency hygiene, naming conventions, etc.) and they run independently. Most rule agents will silently exit on any given diff because the changes don't touch their scope.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ # zero-install (npx / bunx / pnpm dlx — pick your runner)
11
+ npx revu-ai init
12
+ bunx revu-ai init
13
+ pnpm dlx revu-ai init
14
+
15
+ # or install globally
16
+ npm i -g revu-ai
17
+ pnpm add -g revu-ai
18
+ bun add -g revu-ai
19
+
20
+ # or as a project devDep
21
+ npm i -D revu-ai
22
+ pnpm add -D revu-ai
23
+ ```
24
+
25
+ Set `ANTHROPIC_API_KEY` in your environment.
26
+
27
+ ## Quick start
28
+
29
+ ```bash
30
+ cd my-project
31
+ revu-ai init # spawn an agent to inspect the repo and scaffold rule files
32
+ revu-ai list # show what would be reviewed
33
+ revu-ai # run it
34
+ ```
35
+
36
+ `revu-ai init` spawns a Claude agent that inspects your repo (CLAUDE.md, README, manifests, lint configs, top-level structure) and writes a curated set of `.revu.md` files. Globals go in `.revu/<topic>.revu.md`; rules scoped to a sub-service go alongside it (e.g. `services/auth/openapi.revu.md`). The agent searches for *implicit contracts* first — places where two parts of the codebase must be kept in sync but the type system doesn't enforce it — because those are the highest-value rules.
37
+
38
+ The agent is opinionated: language-aware (uses your project's actual logger / docstring style / convention idioms), refuses to restate things your linter and type-checker already enforce, and writes one concern per file. Re-run with `--force` to overwrite. Calibrate the wall-clock cap with `--timeout-ms` (default 10min).
39
+
40
+ ## Writing rule files
41
+
42
+ A rule file is a plain Markdown document that describes a narrow review concern. Anything you'd put in a focused PR-review prompt works. Keep them small and single-purpose.
43
+
44
+ ```markdown
45
+ # Logging discipline
46
+
47
+ Flag any newly added log statement that:
48
+
49
+ - Logs PII (email, user IDs, auth tokens)
50
+ - Uses `console.log` instead of the project logger (`src/log.ts`)
51
+ - Lacks structured fields (we want `{ event, ... }`, not freeform strings)
52
+
53
+ ## Severity
54
+
55
+ - `critical` for anything that logs auth tokens
56
+ - `high` for anything that logs PII
57
+ - `medium` for `console.log` instead of the project logger
58
+ ```
59
+
60
+ The agent is told to only report findings that match the rule. If your diff has no logging changes, this rule will silently pass.
61
+
62
+ ## How it works
63
+
64
+ ```
65
+ revu CLI
66
+ ├─ discover *.revu.md (respects .gitignore)
67
+ ├─ resolve review target (default: origin/main...HEAD)
68
+ ├─ start MCP sidecar on a random localhost port
69
+ ├─ for each rule, in parallel:
70
+ │ spawn Claude agent (Claude Agent SDK)
71
+ │ ├─ system prompt = rule contents + reporting protocol
72
+ │ ├─ user prompt = "review the changes between <base> and <head>"
73
+ │ ├─ tools = Read, Grep, Glob, read-only Bash, mcp__revu__report_finding
74
+ │ └─ runs `git diff` itself to inspect the changes
75
+ └─ aggregate findings, emit output, exit
76
+ ```
77
+
78
+ The runner doesn't pre-compute the diff and stuff it into the prompt — agents inspect the changes via their own `git diff` calls. This sidesteps token-budget and large-file problems that the agent already handles natively.
79
+
80
+ Bash is gated to read-only commands (`git diff/log/show/status`, `cat`, `head`, `tail`, `ls`, `find`, `wc`, etc.). Mutating git operations (`push`, `commit`, `checkout`, …) and shell metacharacters (`>`, `&&`, `;`, backticks) are rejected.
81
+
82
+ ## CLI
83
+
84
+ ```
85
+ revu-ai [options] # run a review (default command)
86
+ revu-ai init [--dir .revu] # scaffold starter rules + config
87
+ revu-ai list # show discovered rule files
88
+
89
+ Options:
90
+ --base <ref> # diff base; default: auto-detected origin/main
91
+ --working-tree # review uncommitted changes instead of branch
92
+ --staged # review staged changes only
93
+ --pattern <glob> # rule file glob; default: **/*.revu.md
94
+ --provider <name> # default: claude-code
95
+ --model <id> # passed to provider
96
+ --concurrency <n> # max parallel agents; default: min(8, ruleCount)
97
+ --output <fmt> # pretty | json | github (default: auto)
98
+ --output-file <path> # also write output to a file
99
+ --fail-on <severity> # exit-code threshold; default: high
100
+ --force # skip the no-changes pre-flight short-circuit
101
+ --config <path> # config file; default: revu.config.json
102
+ ```
103
+
104
+ Exit codes: `0` clean, `1` findings ≥ `--fail-on`, `2` runner / agent error.
105
+
106
+ ## Configuration
107
+
108
+ `revu.config.json` mirrors the CLI flags (CLI wins on conflict):
109
+
110
+ ```json
111
+ {
112
+ "pattern": "**/*.revu.md",
113
+ "provider": "claude-code",
114
+ "model": "claude-sonnet-4-6",
115
+ "concurrency": 8,
116
+ "output": "auto",
117
+ "failOn": "high"
118
+ }
119
+ ```
120
+
121
+ ## Output
122
+
123
+ The JSON shape is the stable contract for downstream tooling:
124
+
125
+ ```json
126
+ {
127
+ "schemaVersion": 1,
128
+ "runId": "...",
129
+ "startedAt": "...",
130
+ "completedAt": "...",
131
+ "reviewTarget": { "mode": "ref-range", "base": "origin/main", "baseSha": "...",
132
+ "head": "HEAD", "headSha": "...", "changedFiles": ["..."] },
133
+ "rules": [{ "id": "dead-code", "path": ".revu/dead-code.revu.md",
134
+ "ok": true, "durationMs": 12345, "findingCount": 1 }],
135
+ "findings": [{ "ruleId": "dead-code", "severity": "high",
136
+ "path": "src/foo.ts", "line": 42, "lineEnd": 47,
137
+ "message": "...", "category": "unused-export" }]
138
+ }
139
+ ```
140
+
141
+ ## GitHub Actions
142
+
143
+ A starter workflow lives at `examples/github-workflow.yml`. Drop it into `.github/workflows/revu.yml` and add `ANTHROPIC_API_KEY` as a repo secret. With `--output github`, findings render as PR annotations.
144
+
145
+ ## Custom providers
146
+
147
+ The default reviewer is Claude Code via `@anthropic-ai/claude-agent-sdk`. The `ReviewAgent` interface in `src/providers/types.ts` is the swap-out boundary:
148
+
149
+ ```ts
150
+ import { registerProvider } from "revu-ai";
151
+
152
+ registerProvider("my-provider", (cfg) => ({
153
+ name: "my-provider",
154
+ async run(input) {
155
+ // Connect to input.mcp.url with Authorization: Bearer <input.mcp.authToken>
156
+ // and X-Revu-Rule-Id: <input.ruleId>. Call mcp__report_finding for each finding.
157
+ // Return { ruleId, ok, durationMs }.
158
+ },
159
+ }));
160
+ ```
161
+
162
+ Then `revu-ai --provider my-provider` (or set it in `revu.config.json`).
163
+
164
+ ## License
165
+
166
+ MIT.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,252 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ import { Command } from "commander";
4
+ import { findRepoRoot } from "./refs.js";
5
+ const pkg = createRequire(import.meta.url)("../package.json");
6
+ import { loadConfig } from "./config.js";
7
+ import { listRules, run, RevuExit } from "./runner.js";
8
+ import { emitJson } from "./output/json.js";
9
+ import { emitPretty } from "./output/pretty.js";
10
+ import { emitGithub } from "./output/github.js";
11
+ const COLOR = process.stderr.isTTY && !process.env.NO_COLOR;
12
+ const c = {
13
+ reset: "\x1b[0m",
14
+ dim: "\x1b[2m",
15
+ bold: "\x1b[1m",
16
+ red: "\x1b[31m",
17
+ yellow: "\x1b[33m",
18
+ green: "\x1b[32m",
19
+ blue: "\x1b[34m",
20
+ magenta: "\x1b[35m",
21
+ cyan: "\x1b[36m",
22
+ gray: "\x1b[90m",
23
+ };
24
+ const paint = (color, s) => (COLOR ? `${c[color]}${s}${c.reset}` : s);
25
+ const SEV_COLOR = {
26
+ aesthetic: "gray",
27
+ low: "blue",
28
+ medium: "yellow",
29
+ high: "red",
30
+ critical: "magenta",
31
+ };
32
+ const SEV_LABEL = {
33
+ aesthetic: "nit ",
34
+ low: "low ",
35
+ medium: "med ",
36
+ high: "high",
37
+ critical: "CRIT",
38
+ };
39
+ const program = new Command();
40
+ program
41
+ .name(pkg.name)
42
+ .description("Parallel AI code review with per-rule Claude agents")
43
+ .version(pkg.version);
44
+ program
45
+ .command("list")
46
+ .description("List rule files that would be reviewed")
47
+ .option("--pattern <glob>", "rule file glob")
48
+ .option("--config <path>", "config file path")
49
+ .action(async (opts) => {
50
+ const cwd = process.cwd();
51
+ const repoRoot = findRepoRoot(cwd);
52
+ const cfg = loadConfig(repoRoot, { pattern: opts.pattern, config: opts.config });
53
+ const rules = await listRules(cwd, cfg.pattern);
54
+ if (rules.length === 0) {
55
+ console.log(`No rule files found matching ${cfg.pattern}`);
56
+ return;
57
+ }
58
+ for (const r of rules) {
59
+ console.log(`${r.ruleId}\t${r.relPath}`);
60
+ }
61
+ });
62
+ program
63
+ .command("init")
64
+ .description("Spawn an agent to inspect the repo and scaffold curated .revu.md rule files")
65
+ .option("--force", "overwrite existing rule files in .revu/")
66
+ .option("--provider <name>", "scaffold provider (default: claude-code)")
67
+ .option("--model <id>", "model id passed to provider")
68
+ .option("--timeout-ms <ms>", "scaffold agent wall-clock timeout (default: 600000 = 10min)", parseIntOpt)
69
+ .action(async (opts) => {
70
+ const { runInit, InitRefusedError } = await import("./init.js");
71
+ const showProgress = process.stderr.isTTY && !process.env.REVU_DEBUG;
72
+ try {
73
+ const result = await runInit({
74
+ cwd: process.cwd(),
75
+ force: opts.force ?? false,
76
+ provider: opts.provider ?? "claude-code",
77
+ ...(opts.model !== undefined ? { model: opts.model } : {}),
78
+ timeoutMs: opts.timeoutMs ?? 600_000,
79
+ onStart: ({ repoRoot }) => process.stderr.write(`${paint("cyan", "▶")} ${paint("bold", "scaffolding rule files")} ${paint("dim", repoRoot)}\n`),
80
+ onActivity: showProgress
81
+ ? (a) => {
82
+ if (a.kind === "tool" && a.name === "Write") {
83
+ // Each Write gets its own "✱ created" line via onFileWritten — skip the dim activity.
84
+ return;
85
+ }
86
+ if (a.kind === "tool") {
87
+ process.stderr.write(` ${paint("dim", "↳")} ${paint("cyan", a.name ?? "")}${paint("dim", `(${a.detail})`)}\n`);
88
+ }
89
+ else if (a.detail) {
90
+ process.stderr.write(` ${paint("dim", "…")} ${paint("dim", a.detail)}\n`);
91
+ }
92
+ }
93
+ : undefined,
94
+ onFileWritten: showProgress
95
+ ? (rel) => process.stderr.write(` ${paint("bold", paint("green", "✱ created"))} ${paint("bold", rel)}\n`)
96
+ : undefined,
97
+ });
98
+ if (!result.ok) {
99
+ const label = result.timedOut ? "⏱ scaffold timed out" : "✗ scaffold failed";
100
+ const color = result.timedOut ? "yellow" : "red";
101
+ process.stderr.write(`${paint(color, paint("bold", label))} ${paint(color, result.errorMessage ?? "?")}\n`);
102
+ if (result.filesWritten.length > 0) {
103
+ process.stderr.write(`${paint("dim", `(${result.filesWritten.length} file(s) written before exit)`)}\n`);
104
+ for (const f of result.filesWritten)
105
+ process.stderr.write(` ${paint("dim", f)}\n`);
106
+ }
107
+ process.exit(2);
108
+ }
109
+ const globals = result.filesWritten.filter((f) => f.startsWith(".revu/")).sort();
110
+ const locals = result.filesWritten.filter((f) => !f.startsWith(".revu/")).sort();
111
+ process.stderr.write(`${paint("bold", paint("green", "✓"))} ${paint("bold", String(result.filesWritten.length))} ${paint("dim", `rule file${result.filesWritten.length === 1 ? "" : "s"} created`)} ${paint("dim", `(${result.durationMs}ms)`)}\n`);
112
+ if (globals.length > 0) {
113
+ process.stderr.write(` ${paint("bold", "globals")}\n`);
114
+ for (const f of globals)
115
+ process.stderr.write(` ${f}\n`);
116
+ }
117
+ if (locals.length > 0) {
118
+ process.stderr.write(` ${paint("bold", "locals")}\n`);
119
+ for (const f of locals)
120
+ process.stderr.write(` ${f}\n`);
121
+ }
122
+ process.exit(0);
123
+ }
124
+ catch (e) {
125
+ if (e instanceof InitRefusedError) {
126
+ process.stderr.write(`${paint("yellow", paint("bold", "revu init:"))} ${e.message.replace(/^revu init: /, "")}\n`);
127
+ process.exit(1);
128
+ }
129
+ process.stderr.write(`${paint("red", paint("bold", "revu init:"))} ${e.message}\n`);
130
+ process.exit(2);
131
+ }
132
+ });
133
+ program
134
+ .command("run", { isDefault: true })
135
+ .description("Run a review")
136
+ .option("--base <ref>", "review base ref (default: auto-detect main)")
137
+ .option("--working-tree", "review uncommitted working-tree changes")
138
+ .option("--staged", "review staged changes only")
139
+ .option("--pattern <glob>", "rule file glob")
140
+ .option("--provider <name>", "review provider (default: claude-code)")
141
+ .option("--model <id>", "model id passed to provider")
142
+ .option("--concurrency <n>", "max parallel agents", parseIntOpt)
143
+ .option("--output <fmt>", "pretty | json | github")
144
+ .option("--output-file <path>", "additionally write output to a file")
145
+ .option("--fail-on <severity>", "exit non-zero threshold (default: high)")
146
+ .option("--timeout-ms <ms>", "per-rule wall-clock timeout in ms (default: 300000 = 5min); 0 disables", parseIntOpt0Allowed)
147
+ .option("--force", "ignore the no-changes pre-flight skip")
148
+ .option("--config <path>", "config file path")
149
+ .action(async (opts) => {
150
+ const cwd = process.cwd();
151
+ const repoRoot = findRepoRoot(cwd);
152
+ const cfg = loadConfig(repoRoot, opts);
153
+ try {
154
+ const showProgress = process.stderr.isTTY && !process.env.REVU_DEBUG;
155
+ const { report, exitCode } = await run(cwd, cfg, {
156
+ onRuleStart: (id) => process.stderr.write(`${paint("cyan", "▶")} ${paint("bold", id)}\n`),
157
+ onActivity: showProgress
158
+ ? (id, a) => {
159
+ if (a.kind === "tool" && a.name === "mcp__revu__report_finding") {
160
+ // Findings get their own line via onFinding — skip the tool-use noise.
161
+ return;
162
+ }
163
+ if (a.kind === "tool") {
164
+ process.stderr.write(` ${paint("dim", id)} ${paint("dim", "↳")} ${paint("cyan", a.name ?? "")}${paint("dim", `(${a.detail})`)}\n`);
165
+ }
166
+ else if (a.detail) {
167
+ process.stderr.write(` ${paint("dim", id)} ${paint("dim", "…")} ${paint("dim", a.detail)}\n`);
168
+ }
169
+ }
170
+ : undefined,
171
+ onFinding: showProgress
172
+ ? (f) => {
173
+ const loc = f.line !== undefined ? `:${f.line}` : "";
174
+ const sev = paint(SEV_COLOR[f.severity], SEV_LABEL[f.severity]);
175
+ process.stderr.write(` ${paint("dim", f.ruleId)} ${paint("bold", paint(SEV_COLOR[f.severity], "✱"))} ${sev} ${paint("bold", f.path)}${paint("dim", loc)}\n`);
176
+ }
177
+ : undefined,
178
+ onRuleEnd: (r) => {
179
+ const dur = paint("dim", `(${r.durationMs}ms)`);
180
+ let status;
181
+ if (r.timedOut) {
182
+ const count = r.findingCount > 0 ? ` ${r.findingCount} partial finding(s)` : "";
183
+ status = `${paint("yellow", "⏱ timed out")}${paint("dim", count)}`;
184
+ }
185
+ else if (!r.ok) {
186
+ status = `${paint("red", "✗ error:")} ${paint("red", r.errorMessage ?? "?")}`;
187
+ }
188
+ else if (r.findingCount === 0) {
189
+ status = paint("green", "✓ clean");
190
+ }
191
+ else {
192
+ status = `${paint("green", "✓")} ${paint("bold", String(r.findingCount))} ${paint("dim", `finding${r.findingCount === 1 ? "" : "s"}`)}`;
193
+ }
194
+ process.stderr.write(` ${paint("bold", r.id)} ${status} ${dur}\n`);
195
+ },
196
+ });
197
+ const fmt = cfg.output === "auto" ? (process.stdout.isTTY ? "pretty" : "json") : cfg.output;
198
+ const outFile = cfg.outputFile;
199
+ if (fmt === "json")
200
+ emitJson(report, outFile);
201
+ else if (fmt === "github")
202
+ emitGithub(report, outFile);
203
+ else
204
+ emitPretty(report, outFile);
205
+ // If every rule errored, emit a stderr line with the actual cause so
206
+ // it's visible even when stdout is being piped/captured.
207
+ const failed = report.rules.filter((r) => !r.ok);
208
+ if (failed.length === report.rules.length && failed.length > 0) {
209
+ const messages = new Set(failed.map((r) => r.errorMessage ?? "unknown"));
210
+ const prefix = paint("red", paint("bold", "revu:"));
211
+ if (messages.size === 1) {
212
+ process.stderr.write(`${prefix} all ${failed.length} rule agents failed: ${paint("red", String([...messages][0]))}\n`);
213
+ }
214
+ else {
215
+ process.stderr.write(`${prefix} all ${failed.length} rule agents failed ${paint("dim", "(mixed errors; see report)")}\n`);
216
+ }
217
+ }
218
+ const timedOut = report.rules.filter((r) => r.timedOut);
219
+ if (timedOut.length > 0) {
220
+ process.stderr.write(`${paint("yellow", paint("bold", "revu:"))} ${timedOut.length} rule(s) timed out — partial findings included in the report\n`);
221
+ }
222
+ process.exit(exitCode);
223
+ }
224
+ catch (e) {
225
+ if (e instanceof RevuExit) {
226
+ if (e.exitCode === 0)
227
+ console.log(e.message);
228
+ else
229
+ console.error(e.message);
230
+ process.exit(e.exitCode);
231
+ }
232
+ console.error(e.stack ?? String(e));
233
+ process.exit(2);
234
+ }
235
+ });
236
+ function parseIntOpt(value) {
237
+ const n = Number.parseInt(value, 10);
238
+ if (!Number.isFinite(n) || n <= 0)
239
+ throw new Error(`Invalid integer: ${value}`);
240
+ return n;
241
+ }
242
+ function parseIntOpt0Allowed(value) {
243
+ const n = Number.parseInt(value, 10);
244
+ if (!Number.isFinite(n) || n < 0)
245
+ throw new Error(`Invalid integer: ${value}`);
246
+ return n;
247
+ }
248
+ program.parseAsync(process.argv).catch((e) => {
249
+ console.error(e.stack ?? String(e));
250
+ process.exit(2);
251
+ });
252
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function createLimiter(max: number): <T>(fn: () => Promise<T>) => Promise<T>;
@@ -0,0 +1,31 @@
1
+ export function createLimiter(max) {
2
+ if (max < 1)
3
+ throw new Error(`concurrency limit must be >= 1, got ${max}`);
4
+ let active = 0;
5
+ const queue = [];
6
+ const next = () => {
7
+ if (active >= max)
8
+ return;
9
+ const job = queue.shift();
10
+ if (job) {
11
+ active++;
12
+ job();
13
+ }
14
+ };
15
+ return (fn) => new Promise((resolve, reject) => {
16
+ const run = () => {
17
+ fn().then((v) => {
18
+ active--;
19
+ resolve(v);
20
+ next();
21
+ }, (e) => {
22
+ active--;
23
+ reject(e);
24
+ next();
25
+ });
26
+ };
27
+ queue.push(run);
28
+ next();
29
+ });
30
+ }
31
+ //# sourceMappingURL=concurrency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"concurrency.js","sourceRoot":"","sources":["../src/concurrency.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,IAAI,GAAG,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,GAAG,EAAE,CAAC,CAAC;IAC3E,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,MAAM,KAAK,GAAsB,EAAE,CAAC;IAEpC,MAAM,IAAI,GAAG,GAAG,EAAE;QAChB,IAAI,MAAM,IAAI,GAAG;YAAE,OAAO;QAC1B,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;QAC1B,IAAI,GAAG,EAAE,CAAC;YACR,MAAM,EAAE,CAAC;YACT,GAAG,EAAE,CAAC;QACR,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,CAAI,EAAoB,EAAc,EAAE,CAC7C,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACjC,MAAM,GAAG,GAAG,GAAG,EAAE;YACf,EAAE,EAAE,CAAC,IAAI,CACP,CAAC,CAAC,EAAE,EAAE;gBACJ,MAAM,EAAE,CAAC;gBACT,OAAO,CAAC,CAAC,CAAC,CAAC;gBACX,IAAI,EAAE,CAAC;YACT,CAAC,EACD,CAAC,CAAC,EAAE,EAAE;gBACJ,MAAM,EAAE,CAAC;gBACT,MAAM,CAAC,CAAC,CAAC,CAAC;gBACV,IAAI,EAAE,CAAC;YACT,CAAC,CACF,CAAC;QACJ,CAAC,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChB,IAAI,EAAE,CAAC;IACT,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { RevuConfig } from "./types.js";
2
+ export declare const DEFAULT_CONFIG: RevuConfig;
3
+ export interface CliOverrides {
4
+ base?: string;
5
+ workingTree?: boolean;
6
+ staged?: boolean;
7
+ pattern?: string;
8
+ provider?: string;
9
+ model?: string;
10
+ concurrency?: number;
11
+ output?: "pretty" | "json" | "github";
12
+ outputFile?: string;
13
+ failOn?: string;
14
+ force?: boolean;
15
+ config?: string;
16
+ timeoutMs?: number;
17
+ }
18
+ export declare function loadConfig(repoRoot: string, overrides: CliOverrides): RevuConfig;
package/dist/config.js ADDED
@@ -0,0 +1,70 @@
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ const SEVERITIES = new Set([
4
+ "aesthetic",
5
+ "low",
6
+ "medium",
7
+ "high",
8
+ "critical",
9
+ ]);
10
+ export const DEFAULT_CONFIG = {
11
+ pattern: "**/*.revu.md",
12
+ workingTree: false,
13
+ staged: false,
14
+ provider: "claude-code",
15
+ output: "auto",
16
+ failOn: "high",
17
+ force: false,
18
+ timeoutMs: 300_000,
19
+ };
20
+ export function loadConfig(repoRoot, overrides) {
21
+ const file = overrides.config
22
+ ? resolve(repoRoot, overrides.config)
23
+ : resolve(repoRoot, "revu.config.json");
24
+ let fromFile = {};
25
+ if (existsSync(file)) {
26
+ try {
27
+ fromFile = JSON.parse(readFileSync(file, "utf8"));
28
+ }
29
+ catch (e) {
30
+ throw new Error(`Failed to parse ${file}: ${e.message}`);
31
+ }
32
+ }
33
+ const merged = {
34
+ ...DEFAULT_CONFIG,
35
+ ...fromFile,
36
+ };
37
+ if (overrides.pattern !== undefined)
38
+ merged.pattern = overrides.pattern;
39
+ if (overrides.base !== undefined)
40
+ merged.base = overrides.base;
41
+ if (overrides.workingTree !== undefined)
42
+ merged.workingTree = overrides.workingTree;
43
+ if (overrides.staged !== undefined)
44
+ merged.staged = overrides.staged;
45
+ if (overrides.provider !== undefined)
46
+ merged.provider = overrides.provider;
47
+ if (overrides.model !== undefined)
48
+ merged.model = overrides.model;
49
+ if (overrides.concurrency !== undefined)
50
+ merged.concurrency = overrides.concurrency;
51
+ if (overrides.output !== undefined)
52
+ merged.output = overrides.output;
53
+ if (overrides.outputFile !== undefined)
54
+ merged.outputFile = overrides.outputFile;
55
+ if (overrides.force !== undefined)
56
+ merged.force = overrides.force;
57
+ if (overrides.timeoutMs !== undefined)
58
+ merged.timeoutMs = overrides.timeoutMs;
59
+ if (overrides.failOn !== undefined) {
60
+ if (!SEVERITIES.has(overrides.failOn)) {
61
+ throw new Error(`Invalid --fail-on value: ${overrides.failOn}. Expected one of: ${[...SEVERITIES].join(", ")}`);
62
+ }
63
+ merged.failOn = overrides.failOn;
64
+ }
65
+ if (merged.workingTree && merged.staged) {
66
+ throw new Error("--working-tree and --staged are mutually exclusive");
67
+ }
68
+ return merged;
69
+ }
70
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { RuleFile } from "./types.js";
2
+ export declare function discoverRules(repoRoot: string, pattern: string): Promise<RuleFile[]>;
3
+ export declare function _testing_deriveRuleId(rel: string): string;
@@ -0,0 +1,39 @@
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ import { join, resolve, sep } from "node:path";
3
+ import fg from "fast-glob";
4
+ import ignoreImport from "ignore";
5
+ // `ignore` is published as CJS; under NodeNext its default export is the factory itself.
6
+ const ignore = ignoreImport.default ?? ignoreImport;
7
+ const ALWAYS_IGNORE = ["node_modules/**", ".git/**", "dist/**", "build/**"];
8
+ export async function discoverRules(repoRoot, pattern) {
9
+ const ig = ignore();
10
+ const gitignorePath = join(repoRoot, ".gitignore");
11
+ if (existsSync(gitignorePath)) {
12
+ ig.add(readFileSync(gitignorePath, "utf8"));
13
+ }
14
+ const matches = await fg(pattern, {
15
+ cwd: repoRoot,
16
+ dot: true,
17
+ ignore: ALWAYS_IGNORE,
18
+ onlyFiles: true,
19
+ });
20
+ const filtered = matches.filter((rel) => !ig.ignores(rel));
21
+ return filtered.map((rel) => {
22
+ const abs = resolve(repoRoot, rel);
23
+ return {
24
+ ruleId: deriveRuleId(rel),
25
+ absPath: abs,
26
+ relPath: rel,
27
+ content: readFileSync(abs, "utf8"),
28
+ };
29
+ });
30
+ }
31
+ function deriveRuleId(relPath) {
32
+ const withoutSuffix = relPath.replace(/\.revu\.md$/i, "");
33
+ const parts = withoutSuffix.split(sep).filter((p) => p && p !== ".");
34
+ return parts.join("/");
35
+ }
36
+ export function _testing_deriveRuleId(rel) {
37
+ return deriveRuleId(rel);
38
+ }
39
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAY,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,EAAE,MAAM,WAAW,CAAC;AAC3B,OAAO,YAAY,MAAM,QAAQ,CAAC;AAGlC,yFAAyF;AACzF,MAAM,MAAM,GAAI,YAA6D,CAAC,OAAO,IAAI,YAAY,CAAC;AAEtG,MAAM,aAAa,GAAG,CAAC,iBAAiB,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;AAE5E,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB,EAAE,OAAe;IACnE,MAAM,EAAE,GAAI,MAAyF,EAAE,CAAC;IACxG,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IACnD,IAAI,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC9B,EAAE,CAAC,GAAG,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,EAAE;QAChC,GAAG,EAAE,QAAQ;QACb,GAAG,EAAE,IAAI;QACT,MAAM,EAAE,aAAa;QACrB,SAAS,EAAE,IAAI;KAChB,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IAE3D,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAY,EAAE;QACpC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACnC,OAAO;YACL,MAAM,EAAE,YAAY,CAAC,GAAG,CAAC;YACzB,OAAO,EAAE,GAAG;YACZ,OAAO,EAAE,GAAG;YACZ,OAAO,EAAE,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC;SACnC,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,YAAY,CAAC,OAAe;IACnC,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;IAC1D,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;IACrE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC"}