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/dist/refs.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { ResolvedTarget, ReviewTarget } from "./types.js";
2
+ export declare function findRepoRoot(cwd: string): string;
3
+ export interface ResolveOptions {
4
+ base?: string;
5
+ workingTree: boolean;
6
+ staged: boolean;
7
+ }
8
+ export declare function resolveTarget(repoRoot: string, opts: ResolveOptions): ResolvedTarget;
9
+ export declare function isReviewEmpty(target: ReviewTarget, repoRoot: string): boolean;
package/dist/refs.js ADDED
@@ -0,0 +1,81 @@
1
+ import { execFileSync } from "node:child_process";
2
+ export function findRepoRoot(cwd) {
3
+ return git(["rev-parse", "--show-toplevel"], cwd).trim();
4
+ }
5
+ export function resolveTarget(repoRoot, opts) {
6
+ if (opts.workingTree) {
7
+ return {
8
+ target: { mode: "working-tree" },
9
+ changedFiles: changedFilesFor({ mode: "working-tree" }, repoRoot),
10
+ };
11
+ }
12
+ if (opts.staged) {
13
+ return {
14
+ target: { mode: "staged" },
15
+ changedFiles: changedFilesFor({ mode: "staged" }, repoRoot),
16
+ };
17
+ }
18
+ const base = opts.base ?? autoDetectBase(repoRoot);
19
+ const target = { mode: "ref-range", base, head: "HEAD" };
20
+ return {
21
+ target,
22
+ baseSha: tryGit(["rev-parse", base], repoRoot),
23
+ headSha: tryGit(["rev-parse", "HEAD"], repoRoot),
24
+ changedFiles: changedFilesFor(target, repoRoot),
25
+ };
26
+ }
27
+ function autoDetectBase(repoRoot) {
28
+ for (const candidate of ["origin/main", "origin/master"]) {
29
+ if (tryGit(["rev-parse", "--verify", candidate], repoRoot)) {
30
+ return candidate;
31
+ }
32
+ }
33
+ const symref = tryGit(["symbolic-ref", "refs/remotes/origin/HEAD"], repoRoot);
34
+ if (symref) {
35
+ return symref.replace(/^refs\/remotes\//, "");
36
+ }
37
+ return "HEAD~1";
38
+ }
39
+ export function isReviewEmpty(target, repoRoot) {
40
+ try {
41
+ if (target.mode === "ref-range") {
42
+ git(["diff", "--quiet", `${target.base}...${target.head}`], repoRoot);
43
+ }
44
+ else if (target.mode === "working-tree") {
45
+ git(["diff", "--quiet", "HEAD"], repoRoot);
46
+ }
47
+ else {
48
+ git(["diff", "--quiet", "--staged"], repoRoot);
49
+ }
50
+ return true;
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ }
56
+ function changedFilesFor(target, repoRoot) {
57
+ let args;
58
+ if (target.mode === "ref-range") {
59
+ args = ["diff", "--name-only", `${target.base}...${target.head}`];
60
+ }
61
+ else if (target.mode === "working-tree") {
62
+ args = ["diff", "--name-only", "HEAD"];
63
+ }
64
+ else {
65
+ args = ["diff", "--name-only", "--staged"];
66
+ }
67
+ const out = tryGit(args, repoRoot) ?? "";
68
+ return out.split("\n").map((s) => s.trim()).filter(Boolean);
69
+ }
70
+ function git(args, cwd) {
71
+ return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
72
+ }
73
+ function tryGit(args, cwd) {
74
+ try {
75
+ return git(args, cwd).trim();
76
+ }
77
+ catch {
78
+ return undefined;
79
+ }
80
+ }
81
+ //# sourceMappingURL=refs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refs.js","sourceRoot":"","sources":["../src/refs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { Finding, RevuConfig, RunReport, RuleResult } from "./types.js";
2
+ import type { ReviewActivity } from "./providers/types.js";
3
+ export interface RunnerResult {
4
+ report: RunReport;
5
+ exitCode: number;
6
+ }
7
+ export declare class RevuExit extends Error {
8
+ readonly exitCode: number;
9
+ constructor(message: string, exitCode: number);
10
+ }
11
+ export interface RunHooks {
12
+ onRuleStart?: (ruleId: string, relPath: string) => void;
13
+ onRuleEnd?: (result: RuleResult) => void;
14
+ /** Fires whenever a rule agent uses a tool or emits text. */
15
+ onActivity?: (ruleId: string, activity: ReviewActivity) => void;
16
+ /** Fires for each finding the moment it's reported through the MCP sidecar. */
17
+ onFinding?: (finding: Finding) => void;
18
+ }
19
+ export declare function run(cwd: string, config: RevuConfig, hooks?: RunHooks): Promise<RunnerResult>;
20
+ export declare function listRules(cwd: string, pattern: string): Promise<{
21
+ relPath: string;
22
+ ruleId: string;
23
+ }[]>;
package/dist/runner.js ADDED
@@ -0,0 +1,106 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { findRepoRoot, resolveTarget, isReviewEmpty } from "./refs.js";
3
+ import { discoverRules } from "./discovery.js";
4
+ import { startSidecar } from "./mcp/server.js";
5
+ import { getProviderFactory } from "./providers/registry.js";
6
+ import { createLimiter } from "./concurrency.js";
7
+ import { SEVERITY_ORDER } from "./types.js";
8
+ export class RevuExit extends Error {
9
+ exitCode;
10
+ constructor(message, exitCode) {
11
+ super(message);
12
+ this.exitCode = exitCode;
13
+ this.name = "RevuExit";
14
+ }
15
+ }
16
+ export async function run(cwd, config, hooks = {}) {
17
+ const startedAt = new Date().toISOString();
18
+ const runId = randomUUID();
19
+ const repoRoot = findRepoRoot(cwd);
20
+ const rules = await discoverRules(repoRoot, config.pattern);
21
+ if (rules.length === 0) {
22
+ throw new RevuExit(`No rule files found matching ${config.pattern}`, 0);
23
+ }
24
+ const resolved = resolveTarget(repoRoot, {
25
+ base: config.base,
26
+ workingTree: config.workingTree,
27
+ staged: config.staged,
28
+ });
29
+ if (!config.force && isReviewEmpty(resolved.target, repoRoot)) {
30
+ throw new RevuExit("No changes to review.", 0);
31
+ }
32
+ const sidecar = await startSidecar({ repoRoot });
33
+ const unsubscribeFindings = hooks.onFinding
34
+ ? sidecar.aggregator.onAdd(hooks.onFinding)
35
+ : () => { };
36
+ const factory = getProviderFactory(config.provider);
37
+ const provider = factory({ ...(config.model ? { model: config.model } : {}) });
38
+ const concurrency = config.concurrency ?? Math.min(8, rules.length);
39
+ const limit = createLimiter(concurrency);
40
+ const ruleResults = [];
41
+ try {
42
+ await Promise.all(rules.map((rule) => limit(async () => {
43
+ hooks.onRuleStart?.(rule.ruleId, rule.relPath);
44
+ const result = await provider.run({
45
+ ruleId: rule.ruleId,
46
+ rulesFilePath: rule.absPath,
47
+ rulesContent: rule.content,
48
+ reviewTarget: resolved.target,
49
+ repoRoot,
50
+ mcp: { url: sidecar.url, authToken: sidecar.authToken },
51
+ timeoutMs: config.timeoutMs,
52
+ ...(hooks.onActivity
53
+ ? { onActivity: (a) => hooks.onActivity?.(rule.ruleId, a) }
54
+ : {}),
55
+ });
56
+ const ruleResult = {
57
+ id: result.ruleId,
58
+ path: rule.relPath,
59
+ ok: result.ok,
60
+ durationMs: result.durationMs,
61
+ findingCount: sidecar.aggregator.countFor(result.ruleId),
62
+ ...(result.errorMessage ? { errorMessage: result.errorMessage } : {}),
63
+ ...(result.timedOut ? { timedOut: true } : {}),
64
+ };
65
+ ruleResults.push(ruleResult);
66
+ hooks.onRuleEnd?.(ruleResult);
67
+ })));
68
+ }
69
+ finally {
70
+ unsubscribeFindings();
71
+ await sidecar.shutdown();
72
+ }
73
+ const findings = sidecar.aggregator.all();
74
+ const report = {
75
+ schemaVersion: 1,
76
+ runId,
77
+ startedAt,
78
+ completedAt: new Date().toISOString(),
79
+ reviewTarget: { ...resolved, mode: resolved.target.mode },
80
+ rules: ruleResults.sort((a, b) => a.id.localeCompare(b.id)),
81
+ findings: findings.sort(findingSort),
82
+ };
83
+ const exitCode = computeExitCode(findings, ruleResults, config.failOn);
84
+ return { report, exitCode };
85
+ }
86
+ function computeExitCode(findings, rules, failOn) {
87
+ if (rules.some((r) => !r.ok))
88
+ return 2;
89
+ const threshold = SEVERITY_ORDER[failOn];
90
+ const triggered = findings.some((f) => SEVERITY_ORDER[f.severity] >= threshold);
91
+ return triggered ? 1 : 0;
92
+ }
93
+ function findingSort(a, b) {
94
+ const sevDiff = SEVERITY_ORDER[b.severity] - SEVERITY_ORDER[a.severity];
95
+ if (sevDiff !== 0)
96
+ return sevDiff;
97
+ if (a.path !== b.path)
98
+ return a.path.localeCompare(b.path);
99
+ return (a.line ?? 0) - (b.line ?? 0);
100
+ }
101
+ export async function listRules(cwd, pattern) {
102
+ const repoRoot = findRepoRoot(cwd);
103
+ const rules = await discoverRules(repoRoot, pattern);
104
+ return rules.map((r) => ({ relPath: r.relPath, ruleId: r.ruleId }));
105
+ }
106
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ export type Severity = "aesthetic" | "low" | "medium" | "high" | "critical";
2
+ export declare const SEVERITY_ORDER: Record<Severity, number>;
3
+ export interface RuleFile {
4
+ ruleId: string;
5
+ absPath: string;
6
+ relPath: string;
7
+ content: string;
8
+ }
9
+ export type ReviewTarget = {
10
+ mode: "ref-range";
11
+ base: string;
12
+ head: string;
13
+ } | {
14
+ mode: "working-tree";
15
+ } | {
16
+ mode: "staged";
17
+ };
18
+ export interface ResolvedTarget {
19
+ target: ReviewTarget;
20
+ baseSha?: string;
21
+ headSha?: string;
22
+ changedFiles: string[];
23
+ }
24
+ export interface Finding {
25
+ ruleId: string;
26
+ severity: Severity;
27
+ path: string;
28
+ line?: number;
29
+ lineEnd?: number;
30
+ message: string;
31
+ category?: string;
32
+ }
33
+ export interface RuleResult {
34
+ id: string;
35
+ path: string;
36
+ ok: boolean;
37
+ durationMs: number;
38
+ findingCount: number;
39
+ errorMessage?: string;
40
+ /** True if this rule was stopped by the per-rule timeout. */
41
+ timedOut?: boolean;
42
+ }
43
+ export interface RunReport {
44
+ schemaVersion: 1;
45
+ runId: string;
46
+ startedAt: string;
47
+ completedAt: string;
48
+ reviewTarget: ResolvedTarget & {
49
+ mode: ReviewTarget["mode"];
50
+ };
51
+ rules: RuleResult[];
52
+ findings: Finding[];
53
+ }
54
+ export interface RevuConfig {
55
+ pattern: string;
56
+ base?: string;
57
+ workingTree: boolean;
58
+ staged: boolean;
59
+ provider: string;
60
+ model?: string;
61
+ concurrency?: number;
62
+ output: "pretty" | "json" | "github" | "auto";
63
+ outputFile?: string;
64
+ failOn: Severity;
65
+ force: boolean;
66
+ /** Per-agent wall-clock timeout in ms. Default 300_000 (5 minutes). */
67
+ timeoutMs: number;
68
+ }
package/dist/types.js ADDED
@@ -0,0 +1,8 @@
1
+ export const SEVERITY_ORDER = {
2
+ aesthetic: 0,
3
+ low: 1,
4
+ medium: 2,
5
+ high: 3,
6
+ critical: 4,
7
+ };
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,cAAc,GAA6B;IACtD,SAAS,EAAE,CAAC;IACZ,GAAG,EAAE,CAAC;IACN,MAAM,EAAE,CAAC;IACT,IAAI,EAAE,CAAC;IACP,QAAQ,EAAE,CAAC;CACZ,CAAC"}
@@ -0,0 +1,38 @@
1
+ # Drop this into `.github/workflows/revu.yml` in any repo where you want
2
+ # revu to run on PRs. Findings render as PR annotations because the `github`
3
+ # formatter emits workflow commands.
4
+ #
5
+ # You'll need to set ANTHROPIC_API_KEY (or your provider's equivalent) as
6
+ # a repository secret. The workflow uses --base origin/$BASE_REF so revu
7
+ # reviews the full PR.
8
+ name: revu
9
+
10
+ on:
11
+ pull_request:
12
+
13
+ permissions:
14
+ contents: read
15
+ pull-requests: write # so annotations land on the PR
16
+
17
+ jobs:
18
+ review:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ with:
23
+ # Need full history so origin/<base> is available for the diff.
24
+ fetch-depth: 0
25
+
26
+ - uses: actions/setup-node@v4
27
+ with:
28
+ node-version: 20
29
+
30
+ - run: npm install -g revu-ai
31
+
32
+ - name: Run revu
33
+ env:
34
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
35
+ BASE_REF: ${{ github.event.pull_request.base.ref }}
36
+ run: |
37
+ git fetch origin "$BASE_REF":"refs/remotes/origin/$BASE_REF"
38
+ revu-ai --base "origin/$BASE_REF" --output github --fail-on high
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "revu-ai",
3
+ "version": "0.1.0",
4
+ "description": "Parallel AI code review with per-rule Claude agents",
5
+ "type": "module",
6
+ "bin": {
7
+ "revu-ai": "dist/cli.js"
8
+ },
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "examples",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "dev": "tsx src/cli.ts",
26
+ "typecheck": "tsc --noEmit",
27
+ "test": "vitest run",
28
+ "test:watch": "vitest",
29
+ "prepack": "pnpm run build",
30
+ "prepublishOnly": "pnpm run typecheck && pnpm run test && pnpm run build"
31
+ },
32
+ "engines": {
33
+ "node": ">=20"
34
+ },
35
+ "keywords": [
36
+ "code-review",
37
+ "ai",
38
+ "claude",
39
+ "anthropic",
40
+ "cli",
41
+ "git",
42
+ "diff",
43
+ "review",
44
+ "static-analysis",
45
+ "mcp",
46
+ "agent"
47
+ ],
48
+ "author": "PKWadsworth",
49
+ "license": "MIT",
50
+ "homepage": "https://github.com/PKWadsworth/revu#readme",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/PKWadsworth/revu.git"
54
+ },
55
+ "bugs": {
56
+ "url": "https://github.com/PKWadsworth/revu/issues"
57
+ },
58
+ "dependencies": {
59
+ "@anthropic-ai/claude-agent-sdk": "^0.1.0",
60
+ "@modelcontextprotocol/sdk": "^1.0.0",
61
+ "commander": "^12.1.0",
62
+ "fast-glob": "^3.3.2",
63
+ "ignore": "^5.3.2",
64
+ "zod": "^3.23.8"
65
+ },
66
+ "devDependencies": {
67
+ "@types/node": "^22.0.0",
68
+ "tsx": "^4.19.0",
69
+ "typescript": "^5.6.0",
70
+ "vitest": "^2.1.0"
71
+ },
72
+ "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
73
+ }