@motivation-labs/crosscheck 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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +92 -0
  3. package/crosscheck.config.example.yml +75 -0
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +40 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/commands/init.d.ts +2 -0
  9. package/dist/commands/init.d.ts.map +1 -0
  10. package/dist/commands/init.js +75 -0
  11. package/dist/commands/init.js.map +1 -0
  12. package/dist/commands/review.d.ts +2 -0
  13. package/dist/commands/review.d.ts.map +1 -0
  14. package/dist/commands/review.js +69 -0
  15. package/dist/commands/review.js.map +1 -0
  16. package/dist/commands/serve.d.ts +2 -0
  17. package/dist/commands/serve.d.ts.map +1 -0
  18. package/dist/commands/serve.js +92 -0
  19. package/dist/commands/serve.js.map +1 -0
  20. package/dist/commands/status.d.ts +2 -0
  21. package/dist/commands/status.d.ts.map +1 -0
  22. package/dist/commands/status.js +57 -0
  23. package/dist/commands/status.js.map +1 -0
  24. package/dist/commands/watch.d.ts +2 -0
  25. package/dist/commands/watch.d.ts.map +1 -0
  26. package/dist/commands/watch.js +177 -0
  27. package/dist/commands/watch.js.map +1 -0
  28. package/dist/config/loader.d.ts +5 -0
  29. package/dist/config/loader.d.ts.map +1 -0
  30. package/dist/config/loader.js +34 -0
  31. package/dist/config/loader.js.map +1 -0
  32. package/dist/config/schema.d.ts +267 -0
  33. package/dist/config/schema.d.ts.map +1 -0
  34. package/dist/config/schema.js +47 -0
  35. package/dist/config/schema.js.map +1 -0
  36. package/dist/github/client.d.ts +16 -0
  37. package/dist/github/client.d.ts.map +1 -0
  38. package/dist/github/client.js +89 -0
  39. package/dist/github/client.js.map +1 -0
  40. package/dist/github/detector.d.ts +6 -0
  41. package/dist/github/detector.d.ts.map +1 -0
  42. package/dist/github/detector.js +35 -0
  43. package/dist/github/detector.js.map +1 -0
  44. package/dist/github/webhook.d.ts +27 -0
  45. package/dist/github/webhook.d.ts.map +1 -0
  46. package/dist/github/webhook.js +48 -0
  47. package/dist/github/webhook.js.map +1 -0
  48. package/dist/reviewers/claude.d.ts +7 -0
  49. package/dist/reviewers/claude.d.ts.map +1 -0
  50. package/dist/reviewers/claude.js +66 -0
  51. package/dist/reviewers/claude.js.map +1 -0
  52. package/dist/reviewers/codex.d.ts +7 -0
  53. package/dist/reviewers/codex.d.ts.map +1 -0
  54. package/dist/reviewers/codex.js +59 -0
  55. package/dist/reviewers/codex.js.map +1 -0
  56. package/package.json +60 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Motivation Labs
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,92 @@
1
+ # crosscheck
2
+
3
+ Cross-vendor AI code review orchestrator. When Claude Code opens a PR, Codex reviews it. When Codex opens a PR, Claude reviews it. Runs locally using your existing subscriptions — no separate API billing required.
4
+
5
+ ```bash
6
+ npm install -g @motivation-labs/crosscheck # stable
7
+ npm install -g @motivation-labs/crosscheck@beta # latest features
8
+ crosscheck init
9
+ crosscheck review https://github.com/owner/repo/pull/123 --reviewer codex
10
+ ```
11
+
12
+ ---
13
+
14
+ **[→ Full documentation: get-started.md](./get-started.md)**
15
+
16
+ Covers prerequisites, install, all commands and flags, full config reference, and how it works under the hood.
17
+
18
+ ---
19
+
20
+ ## Quick start
21
+
22
+ **1. Install CLIs and authenticate**
23
+
24
+ ```bash
25
+ # crosscheck
26
+ npm install -g @motivation-labs/crosscheck
27
+
28
+ # Claude Code — uses your claude.ai Pro/Max subscription
29
+ npm install -g @anthropic-ai/claude-code && claude
30
+
31
+ # Codex — uses your ChatGPT Plus/Pro subscription
32
+ npm install -g @openai/codex && codex login --device-auth
33
+
34
+ # GitHub CLI
35
+ brew install gh && gh auth login
36
+ ```
37
+
38
+ **2. Set env vars**
39
+
40
+ ```bash
41
+ export GITHUB_TOKEN=ghp_... # needs repo + pull-requests:write scope
42
+ export CROSSCHECK_WEBHOOK_SECRET=secret # any random string
43
+ ```
44
+
45
+ **3. Init and test**
46
+
47
+ ```bash
48
+ crosscheck init
49
+ crosscheck review https://github.com/owner/repo/pull/123 --reviewer codex
50
+ ```
51
+
52
+ **4. Run continuously**
53
+
54
+ ```bash
55
+ # Local dev — auto-creates smee.io tunnel, auto-registers webhook
56
+ crosscheck watch
57
+
58
+ # Always-on machine — listens on fixed port, you register webhook once
59
+ crosscheck serve
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Goals
65
+
66
+ - **Use your existing subscriptions** — no per-token API billing; runs `claude` and `codex` CLIs locally
67
+ - **Zero infrastructure** — a single command on any machine with both CLIs installed
68
+ - **Config-as-code** — one flat YAML file, readable and writable by coding agents
69
+ - **Two deployment modes** — `watch` for laptops, `serve` for always-on machines
70
+
71
+ ## Non-goals
72
+
73
+ - Not a replacement for human code review
74
+ - Not a merge gate — posts comments, does not block PRs
75
+ - Not a hosted service — runs on your machine, against your CLIs
76
+
77
+ ---
78
+
79
+ ## Requirements
80
+
81
+ | | Install |
82
+ |---|---|
83
+ | Node.js 18+ | [nodejs.org](https://nodejs.org) |
84
+ | Claude Code CLI | `npm install -g @anthropic-ai/claude-code` |
85
+ | Codex CLI | `npm install -g @openai/codex` |
86
+ | GitHub CLI | `brew install gh` |
87
+
88
+ ---
89
+
90
+ ## License
91
+
92
+ MIT
@@ -0,0 +1,75 @@
1
+ # crosscheck.config.yml
2
+ # This file is designed to be easily read and modified by coding agents.
3
+ # All fields are optional — crosscheck uses sensible defaults.
4
+
5
+ # ─── Mode ────────────────────────────────────────────────────────────────────
6
+ # single-vendor: one AI reviews all PRs
7
+ # cross-vendor: each AI reviews the other's PRs (Claude ↔ Codex)
8
+ mode: cross-vendor
9
+
10
+ # ─── Vendors ─────────────────────────────────────────────────────────────────
11
+ vendors:
12
+ codex:
13
+ enabled: true
14
+ model: o4-mini # o4-mini | o3 | gpt-4o (cost/quality tradeoff)
15
+ auth: subscription # subscription (device-auth OAuth) | api-key
16
+
17
+ claude:
18
+ enabled: true
19
+ model: sonnet # haiku | sonnet | opus
20
+ effort: medium # low | medium | high | max
21
+
22
+ # ─── Quality & Focus ─────────────────────────────────────────────────────────
23
+ quality:
24
+ tier: balanced # fast | balanced | thorough
25
+ # fast → quick scan, top issues only (~10s, lowest cost)
26
+ # balanced → full review, all issues with explanations (~30s)
27
+ # thorough → deep multi-pass, security + architecture (~60s+)
28
+
29
+ focus: # optional — narrows review scope
30
+ - security
31
+ - types
32
+ - performance
33
+ # - naming
34
+ # - test-coverage
35
+ # - documentation
36
+
37
+ custom_prompt: | # optional — appended to every review
38
+ Focus on correctness and security. Be concise.
39
+
40
+ # ─── Budget ──────────────────────────────────────────────────────────────────
41
+ budget:
42
+ codex_monthly_usd: 20 # null = unlimited (use if on subscription)
43
+ per_review_usd: 2.00 # hard stop per single review
44
+
45
+ # ── Orgs (watch/serve mode) ───────────────────────────────────────────────────
46
+ # Monitor entire orgs — all repos get covered with one webhook per org.
47
+ # Takes priority over `repos` when both are set.
48
+ orgs:
49
+ - motivation-labs
50
+ - codatta
51
+
52
+ # ─── Repos ───────────────────────────────────────────────────────────────────
53
+ # Omit entirely to auto-detect from `git remote` (useful in watch mode)
54
+ # repos: still works for monitoring specific repos only
55
+ # repos:
56
+ # - owner: acme
57
+ # name: specific-repo
58
+ repos:
59
+ - owner: codatta
60
+ name: my-repo
61
+
62
+ # ─── Routing (cross-vendor mode only) ────────────────────────────────────────
63
+ # Which AI reviews PRs from which author patterns
64
+ routing:
65
+ codex_reviews_patterns:
66
+ - "Generated with \\[Claude Code\\]" # Claude Code attribution footer
67
+ claude_reviews_patterns:
68
+ - "Generated with \\[OpenAI Codex\\]" # Codex attribution footer
69
+ - "Co-Authored-By: codex"
70
+
71
+ # ─── Server (serve mode only) ────────────────────────────────────────────────
72
+ server:
73
+ port: 7891
74
+ webhook_path: /webhook
75
+ # pid_file: ~/.crosscheck/crosscheck.pid # for daemon management
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { runInit } from './commands/init.js';
4
+ import { runServe } from './commands/serve.js';
5
+ import { runWatch } from './commands/watch.js';
6
+ import { runReview } from './commands/review.js';
7
+ import { runStatus } from './commands/status.js';
8
+ const program = new Command();
9
+ program
10
+ .name('crosscheck')
11
+ .description('Cross-vendor AI code review — Claude Code ↔ Codex')
12
+ .version('0.1.0');
13
+ program
14
+ .command('init')
15
+ .description('Check environment, verify CLI auth, write starter config')
16
+ .option('-c, --config <path>', 'path to write config file')
17
+ .action((opts) => runInit(opts.config));
18
+ program
19
+ .command('serve')
20
+ .description('[BETA] Always-on webhook server (mac-mini / home server mode)')
21
+ .option('-c, --config <path>', 'config file path')
22
+ .action((opts) => runServe(opts.config));
23
+ program
24
+ .command('watch')
25
+ .description('Local dev mode — listen for PRs on the current repo')
26
+ .option('-c, --config <path>', 'config file path')
27
+ .action((opts) => void runWatch(opts.config));
28
+ program
29
+ .command('review <pr-url>')
30
+ .description('Manually trigger a review for a single PR URL')
31
+ .option('-c, --config <path>', 'config file path')
32
+ .option('-r, --reviewer <vendor>', 'force a specific reviewer: codex | claude (bypasses auto-detection)')
33
+ .action((prUrl, opts) => void runReview(prUrl, opts.config, opts.reviewer));
34
+ program
35
+ .command('status')
36
+ .description('Show auth state, config summary, and CLI versions')
37
+ .option('-c, --config <path>', 'config file path')
38
+ .action((opts) => void runStatus(opts.config));
39
+ program.parse();
40
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAEhD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;AAE7B,OAAO;KACJ,IAAI,CAAC,YAAY,CAAC;KAClB,WAAW,CAAC,mDAAmD,CAAC;KAChE,OAAO,CAAC,OAAO,CAAC,CAAA;AAEnB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,0DAA0D,CAAC;KACvE,MAAM,CAAC,qBAAqB,EAAE,2BAA2B,CAAC;KAC1D,MAAM,CAAC,CAAC,IAAyB,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;AAE9D,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,+DAA+D,CAAC;KAC5E,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,CAAC;KACjD,MAAM,CAAC,CAAC,IAAyB,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;AAE/D,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,qDAAqD,CAAC;KAClE,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,CAAC;KACjD,MAAM,CAAC,CAAC,IAAyB,EAAE,EAAE,CAAC,KAAK,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;AAEpE,OAAO;KACJ,OAAO,CAAC,iBAAiB,CAAC;KAC1B,WAAW,CAAC,+CAA+C,CAAC;KAC5D,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,CAAC;KACjD,MAAM,CAAC,yBAAyB,EAAE,qEAAqE,CAAC;KACxG,MAAM,CAAC,CAAC,KAAa,EAAE,IAA4C,EAAE,EAAE,CAAC,KAAK,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;AAE7H,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,mDAAmD,CAAC;KAChE,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,CAAC;KACjD,MAAM,CAAC,CAAC,IAAyB,EAAE,EAAE,CAAC,KAAK,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;AAErE,OAAO,CAAC,KAAK,EAAE,CAAA"}
@@ -0,0 +1,2 @@
1
+ export declare function runInit(configPath?: string): Promise<void>;
2
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA8DA,wBAAsB,OAAO,CAAC,UAAU,CAAC,EAAE,MAAM,iBAwBhD"}
@@ -0,0 +1,75 @@
1
+ import { execSync } from 'child_process';
2
+ import { existsSync, writeFileSync, readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import chalk from 'chalk';
5
+ import { checkCodexAuth } from '../reviewers/codex.js';
6
+ import { checkClaudeAuth } from '../reviewers/claude.js';
7
+ async function runChecks() {
8
+ const results = [];
9
+ // Check codex CLI
10
+ try {
11
+ const version = execSync('codex --version 2>&1', { encoding: 'utf8' }).trim();
12
+ const auth = await checkCodexAuth();
13
+ results.push({ label: 'codex CLI', ok: auth.ok, detail: `${version} — ${auth.detail}`, fix: auth.ok ? undefined : 'Run: codex login --device-auth' });
14
+ }
15
+ catch {
16
+ results.push({ label: 'codex CLI', ok: false, detail: 'not found', fix: 'Install: npm install -g @openai/codex' });
17
+ }
18
+ // Check claude CLI
19
+ try {
20
+ const auth = await checkClaudeAuth();
21
+ results.push({ label: 'claude CLI', ok: auth.ok, detail: auth.detail, fix: auth.ok ? undefined : 'Run: claude auth login' });
22
+ }
23
+ catch {
24
+ results.push({ label: 'claude CLI', ok: false, detail: 'not found', fix: 'Install: npm install -g @anthropic-ai/claude-code' });
25
+ }
26
+ // Check gh CLI
27
+ try {
28
+ const version = execSync('gh --version 2>&1', { encoding: 'utf8' }).split('\n')[0];
29
+ const auth = execSync('gh auth status 2>&1', { encoding: 'utf8' });
30
+ results.push({ label: 'gh CLI', ok: auth.includes('Logged in'), detail: version });
31
+ }
32
+ catch {
33
+ results.push({ label: 'gh CLI', ok: false, detail: 'not found or not authed', fix: 'Install: brew install gh && gh auth login' });
34
+ }
35
+ // Check GITHUB_TOKEN
36
+ const token = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
37
+ results.push({ label: 'GITHUB_TOKEN', ok: !!token, detail: token ? 'set' : 'missing', fix: 'Set GITHUB_TOKEN in your shell profile' });
38
+ // Check CROSSCHECK_WEBHOOK_SECRET
39
+ const secret = process.env.CROSSCHECK_WEBHOOK_SECRET ?? process.env.GITHUB_WEBHOOK_SECRET;
40
+ results.push({ label: 'WEBHOOK_SECRET', ok: !!secret, detail: secret ? 'set' : 'missing (only needed for serve/watch)', fix: 'Set CROSSCHECK_WEBHOOK_SECRET' });
41
+ return results;
42
+ }
43
+ function printCheck({ label, ok, detail, fix }) {
44
+ const icon = ok ? chalk.green('✓') : chalk.red('✗');
45
+ const labelStr = chalk.bold(label.padEnd(20));
46
+ console.log(` ${icon} ${labelStr} ${detail}`);
47
+ if (!ok && fix)
48
+ console.log(` ${chalk.dim('→')} ${chalk.yellow(fix)}`);
49
+ }
50
+ export async function runInit(configPath) {
51
+ console.log(chalk.bold('\ncrosscheck — environment check\n'));
52
+ const checks = await runChecks();
53
+ for (const check of checks)
54
+ printCheck(check);
55
+ const failures = checks.filter(c => !c.ok && c.fix);
56
+ if (failures.length > 0) {
57
+ console.log(chalk.yellow(`\n${failures.length} issue(s) need attention before crosscheck can run fully.\n`));
58
+ }
59
+ else {
60
+ console.log(chalk.green('\nAll checks passed.\n'));
61
+ }
62
+ // Write example config if none exists
63
+ const dest = configPath ?? join(process.cwd(), 'crosscheck.config.yml');
64
+ if (!existsSync(dest)) {
65
+ const examplePath = new URL('../../crosscheck.config.example.yml', import.meta.url).pathname;
66
+ if (existsSync(examplePath)) {
67
+ writeFileSync(dest, readFileSync(examplePath));
68
+ console.log(chalk.dim(`Config written to ${dest} — edit to customize.\n`));
69
+ }
70
+ }
71
+ else {
72
+ console.log(chalk.dim(`Config already exists at ${dest}\n`));
73
+ }
74
+ }
75
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function runReview(prUrl: string, configPath?: string, forceReviewer?: string): Promise<void>;
2
+ //# sourceMappingURL=review.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/commands/review.ts"],"names":[],"mappings":"AAmBA,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,iBAyEzF"}
@@ -0,0 +1,69 @@
1
+ import { mkdtempSync, rmSync } from 'fs';
2
+ import { tmpdir } from 'os';
3
+ import { join } from 'path';
4
+ import { execSync } from 'child_process';
5
+ import chalk from 'chalk';
6
+ import ora from 'ora';
7
+ import { createGithubClient, postReviewComment } from '../github/client.js';
8
+ import { detectPROrigin, assignReviewer } from '../github/detector.js';
9
+ import { runCodexReview } from '../reviewers/codex.js';
10
+ import { runClaudeReview } from '../reviewers/claude.js';
11
+ import { loadConfig, getGithubToken } from '../config/loader.js';
12
+ function parsePRUrl(url) {
13
+ const m = url.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
14
+ if (!m)
15
+ return null;
16
+ return { owner: m[1], repo: m[2], number: parseInt(m[3], 10) };
17
+ }
18
+ export async function runReview(prUrl, configPath, forceReviewer) {
19
+ const config = loadConfig(configPath);
20
+ const token = getGithubToken();
21
+ const octokit = createGithubClient(token);
22
+ const parsed = parsePRUrl(prUrl);
23
+ if (!parsed) {
24
+ console.error(chalk.red('Invalid PR URL. Expected: https://github.com/owner/repo/pull/123'));
25
+ process.exit(1);
26
+ }
27
+ const { owner, repo, number } = parsed;
28
+ const spinner = ora(`Fetching PR #${number}...`).start();
29
+ const { data: pr } = await octokit.rest.pulls.get({ owner, repo, pull_number: number });
30
+ spinner.succeed(`PR #${number}: ${pr.title}`);
31
+ let reviewer;
32
+ if (forceReviewer === 'codex' || forceReviewer === 'claude') {
33
+ reviewer = forceReviewer;
34
+ console.log(chalk.dim(` reviewer: ${reviewer} (forced)`));
35
+ }
36
+ else {
37
+ const origin = detectPROrigin(pr.body ?? '', config);
38
+ reviewer = assignReviewer(origin, config);
39
+ if (!reviewer) {
40
+ console.log(chalk.dim(` PR origin: ${origin} — no reviewer assigned (use --reviewer codex|claude to force)`));
41
+ return;
42
+ }
43
+ console.log(chalk.dim(` PR origin: ${origin} → assigned reviewer: ${reviewer}`));
44
+ }
45
+ // Clone the repo into a temp dir
46
+ const tmpDir = mkdtempSync(join(tmpdir(), 'crosscheck-repo-'));
47
+ const spinner2 = ora('Cloning repo for review...').start();
48
+ try {
49
+ execSync(`gh repo clone ${owner}/${repo} ${tmpDir} -- --depth=50 --quiet`, { stdio: 'pipe' });
50
+ execSync(`git fetch origin pull/${number}/head:pr-${number}`, { cwd: tmpDir, stdio: 'pipe' });
51
+ execSync(`git checkout pr-${number}`, { cwd: tmpDir, stdio: 'pipe' });
52
+ spinner2.succeed('Repo ready');
53
+ let reviewText;
54
+ const reviewSpinner = ora(`Running ${reviewer} review...`).start();
55
+ if (reviewer === 'codex') {
56
+ reviewText = await runCodexReview(tmpDir, pr.base.ref, pr.title, config.quality, config.vendors.codex.model, config.vendors.codex.auth, msg => reviewSpinner.text = msg);
57
+ }
58
+ else {
59
+ reviewText = await runClaudeReview(tmpDir, pr.base.ref, pr.title, config.quality, config.vendors.claude, config.budget.per_review_usd, msg => reviewSpinner.text = msg);
60
+ }
61
+ reviewSpinner.succeed('Review complete');
62
+ await postReviewComment(octokit, owner, repo, number, reviewText, reviewer);
63
+ console.log(chalk.green(`\n✓ Review posted to ${prUrl}\n`));
64
+ }
65
+ finally {
66
+ rmSync(tmpDir, { force: true, recursive: true });
67
+ }
68
+ }
69
+ //# sourceMappingURL=review.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.js","sourceRoot":"","sources":["../../src/commands/review.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function runServe(configPath?: string): void;
2
+ //# sourceMappingURL=serve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAoEA,wBAAgB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,QAsC3C"}
@@ -0,0 +1,92 @@
1
+ import { mkdtempSync, rmSync } from 'fs';
2
+ import { tmpdir, hostname } from 'os';
3
+ import { join } from 'path';
4
+ import { execSync } from 'child_process';
5
+ import chalk from 'chalk';
6
+ import { createWebhookServer } from '../github/webhook.js';
7
+ import { createGithubClient, postReviewComment } from '../github/client.js';
8
+ import { detectPROrigin, assignReviewer } from '../github/detector.js';
9
+ import { runCodexReview } from '../reviewers/codex.js';
10
+ import { runClaudeReview } from '../reviewers/claude.js';
11
+ import { loadConfig, getGithubToken, getWebhookSecret } from '../config/loader.js';
12
+ // Deduplication — keyed by owner/repo#pr@sha
13
+ const inFlight = new Set();
14
+ async function handlePR(event, config, token, log) {
15
+ const { pull_request: pr, repository: repo } = event;
16
+ const owner = repo.owner.login;
17
+ const repoName = repo.name;
18
+ const prNumber = event.number;
19
+ const key = `${owner}/${repoName}#${prNumber}@${pr.head.sha}`;
20
+ if (inFlight.has(key)) {
21
+ log(`PR #${prNumber} already in review — skipping duplicate event`);
22
+ return;
23
+ }
24
+ inFlight.add(key);
25
+ log(`PR #${prNumber} ${event.action}: ${pr.title}`);
26
+ const origin = detectPROrigin(pr.body ?? '', config);
27
+ const reviewer = assignReviewer(origin, config);
28
+ if (!reviewer) {
29
+ log(` → origin=${origin}, no reviewer — skipping`);
30
+ inFlight.delete(key);
31
+ return;
32
+ }
33
+ log(` → origin=${origin}, reviewer=${reviewer}`);
34
+ const tmpDir = mkdtempSync(join(tmpdir(), 'crosscheck-repo-'));
35
+ try {
36
+ log(' → cloning...');
37
+ execSync(`gh repo clone ${owner}/${repoName} ${tmpDir} -- --depth=50 --quiet`, { stdio: 'pipe' });
38
+ execSync(`git fetch origin pull/${prNumber}/head:pr-${prNumber}`, { cwd: tmpDir, stdio: 'pipe' });
39
+ execSync(`git checkout pr-${prNumber}`, { cwd: tmpDir, stdio: 'pipe' });
40
+ log(' → running review...');
41
+ let reviewText;
42
+ if (reviewer === 'codex') {
43
+ reviewText = await runCodexReview(tmpDir, pr.base.ref, pr.title, config.quality, config.vendors.codex.model, config.vendors.codex.auth, log);
44
+ }
45
+ else {
46
+ reviewText = await runClaudeReview(tmpDir, pr.base.ref, pr.title, config.quality, config.vendors.claude, config.budget.per_review_usd, log);
47
+ }
48
+ const octokit = createGithubClient(token);
49
+ await postReviewComment(octokit, owner, repoName, prNumber, reviewText, reviewer);
50
+ log(` ✓ review posted to PR #${prNumber}`);
51
+ }
52
+ catch (err) {
53
+ const error = err;
54
+ log(` ✗ review failed: ${error.message ?? 'unknown error'}`);
55
+ }
56
+ finally {
57
+ rmSync(tmpDir, { force: true, recursive: true });
58
+ inFlight.delete(key);
59
+ }
60
+ }
61
+ export function runServe(configPath) {
62
+ const config = loadConfig(configPath);
63
+ const token = getGithubToken();
64
+ const webhookSecret = getWebhookSecret();
65
+ const log = (msg) => console.log(`[${new Date().toISOString()}] ${msg}`);
66
+ const server = createWebhookServer(config, webhookSecret, (event) => { void handlePR(event, config, token, log); }, log);
67
+ server.listen(config.server.port, () => {
68
+ const webhookUrl = `http://${hostname()}:${config.server.port}${config.server.webhook_path}`;
69
+ console.log(chalk.bold('\ncrosscheck serving\n'));
70
+ console.log(chalk.yellow(' ⚠ serve is in beta — report issues at github.com/Motivation-Labs/crosscheck/issues\n'));
71
+ console.log(` mode ${chalk.cyan(config.mode)}`);
72
+ console.log(` quality ${chalk.cyan(config.quality.tier)}`);
73
+ console.log(` port ${chalk.cyan(String(config.server.port))}`);
74
+ console.log(` endpoint ${chalk.cyan(webhookUrl)}`);
75
+ console.log();
76
+ if (config.orgs.length > 0) {
77
+ console.log(chalk.dim('Register the endpoint above as a GitHub org webhook (content-type: application/json).'));
78
+ for (const org of config.orgs) {
79
+ console.log(chalk.dim(` → https://github.com/organizations/${org}/settings/hooks`));
80
+ }
81
+ }
82
+ else {
83
+ console.log(chalk.dim('Register this URL as a GitHub webhook (content-type: application/json).'));
84
+ }
85
+ console.log(chalk.dim('Listening for pull_request events...\n'));
86
+ });
87
+ process.on('SIGINT', () => {
88
+ console.log('\nShutting down...');
89
+ server.close(() => process.exit(0));
90
+ });
91
+ }
92
+ //# sourceMappingURL=serve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function runStatus(configPath?: string): Promise<void>;
2
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAWA,wBAAsB,SAAS,CAAC,UAAU,CAAC,EAAE,MAAM,iBAqDlD"}
@@ -0,0 +1,57 @@
1
+ import { execSync } from 'child_process';
2
+ import chalk from 'chalk';
3
+ import { loadConfig } from '../config/loader.js';
4
+ import { checkCodexAuth } from '../reviewers/codex.js';
5
+ import { checkClaudeAuth } from '../reviewers/claude.js';
6
+ function row(label, value, ok) {
7
+ const indicator = ok === undefined ? ' ' : ok ? chalk.green('✓') : chalk.red('✗');
8
+ console.log(` ${indicator} ${chalk.bold(label.padEnd(22))} ${value}`);
9
+ }
10
+ export async function runStatus(configPath) {
11
+ const config = loadConfig(configPath);
12
+ console.log(chalk.bold('\ncrosscheck status\n'));
13
+ // Auth
14
+ console.log(chalk.dim(' Auth'));
15
+ const [codexAuth, claudeAuth] = await Promise.all([checkCodexAuth(), checkClaudeAuth()]);
16
+ row('codex', codexAuth.detail || 'authenticated', codexAuth.ok);
17
+ row('claude', claudeAuth.detail || 'authenticated', claudeAuth.ok);
18
+ const ghToken = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
19
+ row('GITHUB_TOKEN', ghToken ? 'set' : 'missing', !!ghToken);
20
+ const webhookSecret = process.env.CROSSCHECK_WEBHOOK_SECRET ?? process.env.GITHUB_WEBHOOK_SECRET;
21
+ row('WEBHOOK_SECRET', webhookSecret ? 'set' : 'missing (needed for serve/watch)', !!webhookSecret);
22
+ // Config
23
+ console.log();
24
+ console.log(chalk.dim(' Config'));
25
+ row('mode', config.mode);
26
+ row('quality tier', config.quality.tier);
27
+ row('codex auth', config.vendors.codex.auth);
28
+ row('claude model', config.vendors.claude.model ?? 'default');
29
+ row('per-review budget', config.vendors.codex.auth === 'subscription'
30
+ ? 'subscription (unlimited)'
31
+ : `$${config.budget.per_review_usd.toFixed(2)}`);
32
+ if (config.repos.length > 0) {
33
+ row('repos', config.repos.map(r => `${r.owner}/${r.name}`).join(', '));
34
+ }
35
+ if (config.quality.focus.length > 0) {
36
+ row('focus', config.quality.focus.join(', '));
37
+ }
38
+ // CLI versions
39
+ console.log();
40
+ console.log(chalk.dim(' CLIs'));
41
+ try {
42
+ const codexVer = execSync('codex --version 2>&1', { encoding: 'utf8' }).trim();
43
+ row('codex', codexVer);
44
+ }
45
+ catch {
46
+ row('codex', 'not found', false);
47
+ }
48
+ try {
49
+ const claudeVer = execSync('claude --version 2>&1', { encoding: 'utf8' }).trim();
50
+ row('claude', claudeVer);
51
+ }
52
+ catch {
53
+ row('claude', 'not found', false);
54
+ }
55
+ console.log();
56
+ }
57
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function runWatch(configPath?: string): Promise<void>;
2
+ //# sourceMappingURL=watch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/commands/watch.ts"],"names":[],"mappings":"AAoCA,wBAAsB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,iBAmKjD"}