@motivation-labs/crosscheck 0.7.0 → 0.7.1-beta.af4dcf4.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/dist/cli.js CHANGED
@@ -13,6 +13,7 @@ import { runDiagnose } from './commands/diagnose.js';
13
13
  import { runOptimize } from './commands/optimize.js';
14
14
  import { runImpact } from './commands/impact.js';
15
15
  import { runIssue } from './commands/issue.js';
16
+ import { runRun } from './commands/run.js';
16
17
  const __dirname = dirname(fileURLToPath(import.meta.url));
17
18
  const { version } = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8'));
18
19
  const invokedAs = basename(process.argv[1] ?? 'crosscheck').replace(/\.js$/, '');
@@ -29,11 +30,12 @@ program
29
30
  .action((opts) => runInit(opts.config));
30
31
  program
31
32
  .command('onboard')
32
- .description('Interactive setup — configure deployment persona, scope, and routing')
33
- .option('-c, --config <path>', 'config file path')
34
- .option('--personal', 'skip questionnaire and write personal-mode config')
35
- .option('--team', 'skip questionnaire and write team-mode config')
36
- .option('--reconfigure', 're-run setup even if config already exists')
33
+ .description('Guided setup — select repos to monitor and write config')
34
+ .option('-c, --config <path>', 'config file path to write')
35
+ .option('-y, --yes', 'skip confirmation prompts, accept defaults')
36
+ .option('--personal', 'pre-select personal deployment mode, skip persona prompt')
37
+ .option('--team', 'pre-select team deployment mode, skip persona prompt')
38
+ .option('--reconfigure', 're-run setup (accepted for compatibility; onboard always reconfigures)')
37
39
  .action((opts) => void runOnboard(opts));
38
40
  program
39
41
  .command('serve')
@@ -42,6 +44,7 @@ program
42
44
  .option('--personal', 'personal mode this session only (does not save to config)')
43
45
  .option('--team', 'team mode this session only (does not save to config)')
44
46
  .option('--reconfigure', 're-run deployment setup and save new choice to config')
47
+ .option('--no-backtrace', 'skip the startup scan for unreviewed open PRs this session')
45
48
  .action((opts) => void runServe(opts));
46
49
  program
47
50
  .command('watch')
@@ -50,6 +53,7 @@ program
50
53
  .option('--personal', 'personal mode this session only (does not save to config)')
51
54
  .option('--team', 'team mode this session only (does not save to config)')
52
55
  .option('--reconfigure', 're-run deployment setup and save new choice to config')
56
+ .option('--no-backtrace', 'skip the startup scan for unreviewed open PRs this session')
53
57
  .action((opts) => void runWatch(opts));
54
58
  program
55
59
  .command('review <pr-url>')
@@ -57,6 +61,14 @@ program
57
61
  .option('-c, --config <path>', 'config file path')
58
62
  .option('-r, --reviewer <vendor>', 'force a specific reviewer: codex | claude (bypasses auto-detection)')
59
63
  .action((prUrl, opts) => void runReview(prUrl, opts.config, opts.reviewer));
64
+ program
65
+ .command('run <pr-url>')
66
+ .description('Execute the full configured workflow against a single PR (review → fix → recheck)')
67
+ .option('-c, --config <path>', 'config file path')
68
+ .option('-r, --reviewer <vendor>', 'force a specific reviewer: codex | claude (bypasses attribution detection)')
69
+ .option('--steps <list>', 'run only these step types, comma-separated: review,fix,recheck')
70
+ .option('--dry-run', 'run the review but do not post a comment or apply fixes')
71
+ .action((prUrl, opts) => void runRun(prUrl, opts));
60
72
  program
61
73
  .command('status')
62
74
  .description('Show auth state, config summary, and CLI versions')
package/dist/cli.js.map CHANGED
@@ -1 +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,YAAY,EAAE,MAAM,IAAI,CAAA;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAA;AACnC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAClD,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;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAE9C,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AACzD,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC,CAAwB,CAAA;AAE/G,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;AAChF,MAAM,WAAW,GAAG,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAA;AAE5D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;AAE7B,OAAO;KACJ,IAAI,CAAC,WAAW,CAAC;KACjB,WAAW,CAAC,mDAAmD,CAAC;KAChE,OAAO,CAAC,OAAO,OAAO,EAAE,CAAC,CAAA;AAE5B,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,SAAS,CAAC;KAClB,WAAW,CAAC,sEAAsE,CAAC;KACnF,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,CAAC;KACjD,MAAM,CAAC,YAAY,EAAE,mDAAmD,CAAC;KACzE,MAAM,CAAC,QAAQ,EAAE,+CAA+C,CAAC;KACjE,MAAM,CAAC,eAAe,EAAE,4CAA4C,CAAC;KACrE,MAAM,CAAC,CAAC,IAAoF,EAAE,EAAE,CAAC,KAAK,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;AAE1H,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,+DAA+D,CAAC;KAC5E,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,CAAC;KACjD,MAAM,CAAC,YAAY,EAAE,2DAA2D,CAAC;KACjF,MAAM,CAAC,QAAQ,EAAE,uDAAuD,CAAC;KACzE,MAAM,CAAC,eAAe,EAAE,uDAAuD,CAAC;KAChF,MAAM,CAAC,CAAC,IAAoF,EAAE,EAAE,CAAC,KAAK,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;AAExH,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,wDAAwD,CAAC;KACrE,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,CAAC;KACjD,MAAM,CAAC,YAAY,EAAE,2DAA2D,CAAC;KACjF,MAAM,CAAC,QAAQ,EAAE,uDAAuD,CAAC;KACzE,MAAM,CAAC,eAAe,EAAE,uDAAuD,CAAC;KAChF,MAAM,CAAC,CAAC,IAAoF,EAAE,EAAE,CAAC,KAAK,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;AAExH,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;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,WAAW,CAAC,2FAA2F,CAAC;KACxG,MAAM,CAAC,QAAQ,EAAE,4BAA4B,CAAC;KAC9C,MAAM,CAAC,gBAAgB,EAAE,sDAAsD,CAAC;KAChF,MAAM,CAAC,CAAC,IAAwC,EAAE,EAAE,CAAC,KAAK,WAAW,CAAC,IAAI,CAAC,CAAC,CAAA;AAE/E,OAAO;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,WAAW,CAAC,gEAAgE,CAAC;KAC7E,MAAM,CAAC,SAAS,EAAE,kEAAkE,CAAC;KACrF,MAAM,CAAC,WAAW,EAAE,8CAA8C,CAAC;KACnE,MAAM,CAAC,kBAAkB,EAAE,wCAAwC,CAAC;KACpE,MAAM,CAAC,gBAAgB,EAAE,wCAAwC,CAAC;KAClE,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,CAAC;KACjD,MAAM,CAAC,CAAC,IAA4F,EAAE,EAAE,CAAC,KAAK,WAAW,CAAC,IAAI,CAAC,CAAC,CAAA;AAEnI,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,8EAA8E,CAAC;KAC3F,MAAM,CAAC,QAAQ,EAAE,4BAA4B,CAAC;KAC9C,MAAM,CAAC,gBAAgB,EAAE,sDAAsD,CAAC;KAChF,MAAM,CAAC,SAAS,EAAE,mCAAmC,CAAC;KACtD,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,CAAC;KACjD,MAAM,CAAC,CAAC,IAA0E,EAAE,EAAE,CAAC,KAAK,SAAS,CAAC,IAAI,CAAC,CAAC,CAAA;AAE/G,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,2FAA2F,CAAC;KACxG,MAAM,CAAC,gBAAgB,EAAE,2EAA2E,CAAC;KACrG,MAAM,CAAC,WAAW,EAAE,oCAAoC,CAAC;KACzD,MAAM,CAAC,WAAW,EAAE,6CAA6C,CAAC;KAClE,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,CAAC;KACjD,MAAM,CAAC,CAAC,IAA0E,EAAE,EAAE,CAAC,KAAK,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;AAE9G,OAAO,CAAC,KAAK,EAAE,CAAA"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,9 @@
1
1
  export interface OnboardOpts {
2
+ config?: string;
3
+ yes?: boolean;
2
4
  personal?: boolean;
3
5
  team?: boolean;
4
6
  reconfigure?: boolean;
5
- config?: string;
6
7
  }
7
- export declare function runOnboard(opts: OnboardOpts): Promise<void>;
8
+ export declare function runOnboard(opts?: OnboardOpts): Promise<void>;
8
9
  //# sourceMappingURL=onboard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AA6FD,wBAAsB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAqOjE"}
1
+ {"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AA6DD,wBAAsB,UAAU,CAAC,IAAI,GAAE,WAAgB,iBA6KtD"}
@@ -1,285 +1,237 @@
1
- import { existsSync, mkdirSync } from 'fs';
2
- import { join } from 'path';
1
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
3
  import { homedir } from 'os';
4
- import { createInterface } from 'readline';
5
4
  import chalk from 'chalk';
6
- import { loadConfig, resolveConfigPath, getGithubToken, detectGitHubLogin, writeOnboardConfig, } from '../config/loader.js';
7
- import { listUserOrgs, fetchActiveRepos } from '../github/client.js';
8
- import { promptRepoPicker, promptOrgPicker } from '../lib/repo-picker.js';
9
- import { runChecks } from './init.js';
10
- // ── Prompt helpers ────────────────────────────────────────────────────────────
11
- async function ask(question) {
12
- if (!process.stdin.isTTY)
13
- return '';
5
+ import { createInterface } from 'readline';
6
+ import yaml from 'js-yaml';
7
+ import { getGithubToken, loadConfig, resolveConfigPath, detectGitHubLogin, promptDeploymentMode, patchDeploymentConfig, } from '../config/loader.js';
8
+ import { listUserRepos, listUserOrgs, listOrgRepos } from '../github/client.js';
9
+ import { checkCodexAuth } from '../reviewers/codex.js';
10
+ import { checkClaudeAuth } from '../reviewers/claude.js';
11
+ import { execSync } from 'child_process';
12
+ import { promptRepoPicker } from '../lib/repo-picker.js';
13
+ function ask(question) {
14
14
  return new Promise(resolve => {
15
15
  const rl = createInterface({ input: process.stdin, output: process.stdout });
16
16
  rl.question(question, answer => { rl.close(); resolve(answer.trim()); });
17
17
  });
18
18
  }
19
- // Print numbered choices and return the chosen 1-based index (defaultIdx if user presses Enter).
20
- async function pickOne(prompt, options, defaultIdx) {
21
- console.log(`\n${prompt}\n`);
22
- options.forEach(o => console.log(o));
23
- console.log();
24
- const answer = await ask(chalk.dim(` Choice [${defaultIdx}]: `));
25
- const n = parseInt(answer, 10);
26
- return Number.isInteger(n) && n >= 1 && n <= options.length ? n : defaultIdx;
27
- }
28
- async function confirmWrite() {
29
- const answer = await ask(' Write config? [Y/n]: ');
30
- return answer === '' || /^y/i.test(answer);
31
- }
32
- async function confirmOptIn(question) {
33
- const answer = await ask(`${question} [y/N]: `);
34
- return /^y/i.test(answer);
35
- }
36
- // ── Fast-mode: skip all questions ────────────────────────────────────────────
37
- async function runFastMode(deployment, configPath, login, orgs) {
38
- const users = deployment === 'personal' && login ? [login] : [];
39
- const allowedAuthors = deployment === 'personal' && login ? [login] : [];
40
- const authorRoutes = deployment === 'personal' && login ? { [login]: 'claude' } : {};
41
- writeOnboardConfig(configPath, {
42
- deployment, login, orgs, users, repos: [],
43
- allowedAuthors, authorRoutes,
44
- autoFix: false, deliveryMode: 'pull_request',
45
- brand: { service_name: 'crosscheck', comment_header: '', comment_footer: '', reviewer_attribution: '' },
46
- });
47
- console.log(chalk.green(`\n ✓ config written (${deployment} mode, auto-detected scopes)\n`));
48
- if (orgs.length)
49
- console.log(` ${'orgs'.padEnd(12)}${orgs.join(', ')}`);
50
- if (users.length)
51
- console.log(` ${'users'.padEnd(12)}${users.join(', ')}`);
52
- console.log(chalk.dim(`\n config ${configPath}`));
53
- console.log(chalk.dim(' Run crosscheck watch to start.\n'));
54
- }
55
- // ── Branding step (shared by all personas) ────────────────────────────────────
56
- async function askBranding(existingBrand) {
57
- const brand = {
58
- service_name: existingBrand?.service_name ?? 'crosscheck',
59
- comment_header: existingBrand?.comment_header ?? '',
60
- comment_footer: existingBrand?.comment_footer ?? '',
61
- reviewer_attribution: existingBrand?.reviewer_attribution ?? '',
62
- };
63
- const wantBranding = await confirmOptIn('\nAdd custom branding to review comments? (for teams, services, or personal flair)');
64
- if (!wantBranding)
65
- return brand;
66
- const name = await ask(` Name or label shown in comments (${brand.service_name}): `);
67
- if (name)
68
- brand.service_name = name;
69
- const header = await ask(' Comment header (prepended to every review, Enter to skip): ');
70
- brand.comment_header = header;
71
- const footer = await ask(' Comment footer (appended to every review, Enter to skip): ');
72
- brand.comment_footer = footer;
73
- const attr = await ask(' Reviewer attribution line (replaces "Reviewed by {vendor}", Enter to skip): ');
74
- brand.reviewer_attribution = attr;
75
- return brand;
19
+ async function checkEnv() {
20
+ let aiCliCount = 0;
21
+ try {
22
+ execSync('codex --version 2>&1', { encoding: 'utf8' });
23
+ const auth = await checkCodexAuth();
24
+ if (auth.ok)
25
+ aiCliCount++;
26
+ const icon = auth.ok ? chalk.green('✓') : chalk.red('✗');
27
+ console.log(` ${icon} ${'codex CLI'.padEnd(20)} ${auth.detail}`);
28
+ if (!auth.ok)
29
+ console.log(` ${chalk.dim('→')} ${chalk.yellow('Run: codex login --device-auth')}`);
30
+ }
31
+ catch {
32
+ console.log(` ${chalk.red('✗')} ${'codex CLI'.padEnd(20)} not found`);
33
+ console.log(` ${chalk.dim('→')} ${chalk.yellow('Install: npm install -g @openai/codex')}`);
34
+ }
35
+ try {
36
+ const auth = await checkClaudeAuth();
37
+ if (auth.ok)
38
+ aiCliCount++;
39
+ const icon = auth.ok ? chalk.green('') : chalk.red('✗');
40
+ console.log(` ${icon} ${'claude CLI'.padEnd(20)} ${auth.detail}`);
41
+ if (!auth.ok)
42
+ console.log(` ${chalk.dim('→')} ${chalk.yellow('Run: claude auth login')}`);
43
+ }
44
+ catch {
45
+ console.log(` ${chalk.red('')} ${'claude CLI'.padEnd(20)} not found`);
46
+ console.log(` ${chalk.dim('→')} ${chalk.yellow('Install: npm install -g @anthropic-ai/claude-code')}`);
47
+ }
48
+ const envToken = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
49
+ let ghAuthed = false;
50
+ try {
51
+ execSync('gh --version 2>&1', { encoding: 'utf8' });
52
+ let authOutput = '';
53
+ try {
54
+ authOutput = execSync('gh auth status 2>&1', { encoding: 'utf8' });
55
+ }
56
+ catch { /* GITHUB_TOKEN in use */ }
57
+ ghAuthed = authOutput.includes('Logged in') || !!envToken;
58
+ const icon = ghAuthed ? chalk.green('') : chalk.red('✗');
59
+ console.log(` ${icon} ${'gh CLI'.padEnd(20)} ${ghAuthed ? 'authenticated' : 'not authenticated'}`);
60
+ if (!ghAuthed)
61
+ console.log(` ${chalk.dim('→')} ${chalk.yellow('Run: gh auth login')}`);
62
+ }
63
+ catch {
64
+ console.log(` ${chalk.red('✗')} ${'gh CLI'.padEnd(20)} not found`);
65
+ console.log(` ${chalk.dim('→')} ${chalk.yellow('Install: brew install gh && gh auth login')}`);
66
+ }
67
+ if (aiCliCount === 0) {
68
+ console.log(chalk.red('\nAt least one AI CLI (codex or claude) must be authenticated.\n'));
69
+ return false;
70
+ }
71
+ if (!ghAuthed) {
72
+ console.log(chalk.red('\nGitHub auth is required to fetch repos and register webhooks.\n'));
73
+ return false;
74
+ }
75
+ return true;
76
76
  }
77
- // ── Main ──────────────────────────────────────────────────────────────────────
78
- export async function runOnboard(opts) {
77
+ export async function runOnboard(opts = {}) {
78
+ if (!process.stdin.isTTY) {
79
+ console.error(chalk.red('onboard requires an interactive terminal.'));
80
+ console.error(chalk.dim('Run crosscheck init and edit crosscheck.config.yml manually.'));
81
+ process.exit(1);
82
+ }
79
83
  console.log(chalk.bold('\ncrosscheck onboard\n'));
80
- // Resolve config path prefer explicit, then project-local, then global default
81
- const configPath = resolveConfigPath(opts.config) ?? join(homedir(), '.crosscheck', 'config.yml');
82
- const hasFile = existsSync(configPath);
83
- const existing = hasFile ? loadConfig(configPath) : null;
84
- // If already fully configured and user didn't request reconfigure or fast-mode, show summary
85
- if (hasFile && existing?.deployment && !opts.reconfigure && !opts.personal && !opts.team) {
86
- console.log(chalk.dim(` Found existing config at ${configPath}\n`));
87
- console.log(` ${'deployment'.padEnd(14)}${existing.deployment}`);
88
- if (existing.orgs.length)
89
- console.log(` ${'orgs'.padEnd(14)}${existing.orgs.join(', ')}`);
90
- if (existing.users.length)
91
- console.log(` ${'users'.padEnd(14)}${existing.users.join(', ')}`);
92
- if (existing.repos.length)
93
- console.log(` ${'repos'.padEnd(14)}${existing.repos.length} configured`);
94
- console.log();
95
- console.log(chalk.dim(' Already configured. Run crosscheck watch to start.'));
96
- console.log(chalk.dim(' Use --reconfigure to change settings.\n'));
97
- return;
84
+ // ── Step 1: Auth check ─────────────────────────────────────────────────────
85
+ console.log(chalk.bold('Step 1 environment check'));
86
+ const ok = await checkEnv();
87
+ if (!ok)
88
+ process.exit(1);
89
+ console.log();
90
+ // ── Step 2: Deployment mode ────────────────────────────────────────────────
91
+ console.log(chalk.bold('Step 2 — deployment mode'));
92
+ const configPath = opts.config ?? resolveConfigPath() ?? join(homedir(), '.crosscheck', 'config.yml');
93
+ const existingConfig = existsSync(configPath) ? loadConfig(configPath) : null;
94
+ const currentDeployment = existingConfig?.deployment;
95
+ let deployment;
96
+ if (opts.personal) {
97
+ deployment = 'personal';
98
+ console.log(` Mode: ${chalk.cyan('personal')} (--personal flag)`);
99
+ }
100
+ else if (opts.team) {
101
+ deployment = 'team';
102
+ console.log(` Mode: ${chalk.cyan('team')} (--team flag)`);
98
103
  }
99
- // ── Step 0: Environment checks (compact) ──────────────────────────────────
100
- console.log(chalk.dim('Checking environment...\n'));
101
- const { results: checks } = await runChecks();
102
- for (const c of checks) {
103
- const icon = c.ok ? chalk.green('✓') : chalk.red('✗');
104
- const detail = c.ok ? chalk.dim(c.detail) : chalk.yellow(c.detail);
105
- console.log(` ${icon} ${c.label.padEnd(22)} ${detail}`);
106
- if (!c.ok && c.fix)
107
- console.log(` ${chalk.dim('→')} ${chalk.dim(c.fix)}`);
104
+ else if (currentDeployment && !opts.yes) {
105
+ const keep = await ask(` Current mode: ${chalk.cyan(currentDeployment)}. Keep this? [Y/n]: `);
106
+ deployment = keep.toLowerCase() === 'n' ? await promptDeploymentMode() : currentDeployment;
108
107
  }
109
- const failures = checks.filter(c => !c.ok && c.fix);
110
- console.log(failures.length === 0
111
- ? chalk.green('\n environment ready — proceeding to setup\n')
112
- : chalk.yellow(`\n ⚠ ${failures.length} issue(s) to address — see above; continuing setup\n`));
113
- // ── Resolve GitHub identity ──────────────────────────────────────────────
114
- let token = '';
115
- let login = '';
116
- let detectedOrgs = [];
108
+ else if (currentDeployment && opts.yes) {
109
+ deployment = currentDeployment;
110
+ console.log(` Using existing mode: ${chalk.cyan(deployment)}`);
111
+ }
112
+ else {
113
+ deployment = await promptDeploymentMode();
114
+ }
115
+ console.log();
116
+ // ── Step 3: Repo selection ─────────────────────────────────────────────────
117
+ console.log(chalk.bold('Step 3 — select repos to monitor'));
118
+ let token;
117
119
  try {
118
120
  token = getGithubToken();
119
- login = detectGitHubLogin() ?? '';
120
- detectedOrgs = await listUserOrgs(token);
121
121
  }
122
- catch {
123
- console.log(chalk.dim(' (GitHub not authenticated scope detection skipped)\n'));
122
+ catch (err) {
123
+ console.error(chalk.red(err instanceof Error ? err.message : String(err)));
124
+ process.exit(1);
124
125
  }
125
- // ── Fast mode: skip questionnaire, write immediately ─────────────────────
126
- if (opts.personal || opts.team) {
127
- const deployment = opts.personal ? 'personal' : 'team';
128
- mkdirSync(join(homedir(), '.crosscheck'), { recursive: true });
129
- await runFastMode(deployment, configPath, login, detectedOrgs);
130
- return;
126
+ const login = detectGitHubLogin() ?? '';
127
+ console.log(chalk.dim(` Fetching repos for ${login || 'your account'}...`));
128
+ // Fetch personal repos and org repos in parallel
129
+ const [personalRepos, orgs] = await Promise.all([
130
+ login ? listUserRepos(login, token, true).catch(() => []) : Promise.resolve([]),
131
+ listUserOrgs(token).catch(() => []),
132
+ ]);
133
+ const orgRepoLists = await Promise.all(orgs.map(org => listOrgRepos(org, token).catch(() => [])));
134
+ // Build flat list of "owner/repo" strings for the picker
135
+ const allRepos = [];
136
+ for (const r of personalRepos)
137
+ allRepos.push(`${r.owner}/${r.name}`);
138
+ for (let i = 0; i < orgs.length; i++) {
139
+ for (const r of orgRepoLists[i])
140
+ allRepos.push(`${r.owner}/${r.name}`);
131
141
  }
132
- // ── Step 1: Persona ───────────────────────────────────────────────────────
133
- const currentPersonaDefault = existing?.deployment === 'team' ? 2 : 1;
134
- const personaIdx = await pickOne('How will you use crosscheck?', [
135
- ` ${chalk.bold('[1] personal')} — I author PRs; review only my own work across my repos and orgs`,
136
- ` ${chalk.bold('[2] team')} — shared CR workflow; review PRs from multiple authors in org repos`,
137
- ], currentPersonaDefault);
138
- const deployment = personaIdx === 2 ? 'team' : 'personal';
139
- let orgs = [];
140
- let users = [];
141
- let repos = [];
142
- let allowedAuthors = [];
143
- let authorRoutes = {};
144
- let autoFix = false;
145
- let deliveryMode = 'pull_request';
146
- // ── Personal path ─────────────────────────────────────────────────────────
147
- if (deployment === 'personal') {
148
- // Step 2: Scope
149
- const orgPreview = detectedOrgs.slice(0, 2).map(o => `github.com/${o}/*`).join(', ') || 'your org repos';
150
- const orgSuffix = detectedOrgs.length ? ` (detected: ${detectedOrgs.join(', ')})` : '';
151
- const existingScope = existing
152
- ? (existing.users.length > 0 && existing.orgs.length > 0 ? 3
153
- : existing.users.length > 0 ? 1
154
- : existing.orgs.length > 0 ? 2
155
- : existing.repos.length > 0 ? 1 // curated repos-only = personal scope
156
- : 3)
157
- : 3;
158
- const scopeIdx = await pickOne(`What should crosscheck monitor?${orgSuffix}`, [
159
- ` ${chalk.bold('[1] My personal repos only')} — github.com/${login || 'you'}/* — side projects you own directly`,
160
- ` ${chalk.bold('[2] My org repos only')} — ${orgPreview}`,
161
- ` ${chalk.bold('[3] Both personal repos + orgs')} — everything across your GitHub account ← recommended`,
162
- ], existingScope);
163
- const includePersonal = scopeIdx !== 2;
164
- const includeOrgs = scopeIdx !== 1;
165
- if (includeOrgs && detectedOrgs.length > 0) {
166
- orgs = await promptOrgPicker(detectedOrgs, existing?.orgs.length ? existing.orgs : undefined);
167
- }
168
- if (includePersonal && token && login) {
169
- if (scopeIdx === 1) {
170
- // Curated mode (UC-01): pick specific repos
171
- console.log(chalk.dim('\n Fetching your repos...'));
172
- try {
173
- const repoList = await fetchActiveRepos(login, token);
174
- const existingNames = existing?.repos.map(r => `${r.owner}/${r.name}`) ?? [];
175
- const picked = await promptRepoPicker(repoList, existingNames.length ? existingNames : undefined);
176
- repos = picked.map(full => {
177
- const [owner, name] = full.split('/');
178
- return { owner: owner ?? login, name: name ?? full };
179
- });
180
- }
181
- catch {
182
- console.log(chalk.dim(' (Could not fetch repos — add them manually to config.repos)\n'));
183
- }
184
- }
185
- else {
186
- // "Both" mode (UC-02): monitor all personal repos via users field
187
- users = [login];
188
- }
189
- }
190
- else if (includePersonal) {
191
- users = login ? [login] : [];
142
+ // Pre-select repos already in config
143
+ const currentRepoKeys = new Set((existingConfig?.repos ?? []).map(r => `${r.owner}/${r.name}`));
144
+ const currentOrgs = new Set(existingConfig?.orgs ?? []);
145
+ // If running with --yes, keep existing selection
146
+ let selectedRepos;
147
+ let selectedOrgs;
148
+ if (opts.yes && existingConfig) {
149
+ selectedRepos = [...currentRepoKeys];
150
+ selectedOrgs = [...currentOrgs];
151
+ console.log(` Using existing repo selection (${selectedRepos.length} repos, ${selectedOrgs.length} orgs)`);
152
+ }
153
+ else {
154
+ if (allRepos.length === 0) {
155
+ console.log(chalk.yellow(' No repos found. You can add repos manually in your config file.'));
156
+ selectedRepos = [];
157
+ selectedOrgs = [];
192
158
  }
193
- // Step 3: Author filter
194
- const currentFilterDefault = existing?.routing?.allowed_authors?.length === 0 ? 2 : 1;
195
- const filterIdx = await pickOne('Whose PRs should be reviewed?', [
196
- ` ${chalk.bold('[1] Only mine')} (author = ${login || 'you'}) ← recommended`,
197
- ` ${chalk.bold('[2] Everyone')} in the monitored scope`,
198
- ], currentFilterDefault);
199
- if (filterIdx === 1) {
200
- if (login) {
201
- allowedAuthors = [login];
202
- authorRoutes = { [login]: 'claude' };
159
+ else {
160
+ // Pre-mark already-configured repos as selected via a sorted list
161
+ // where configured repos appear first
162
+ const sorted = [
163
+ ...allRepos.filter(r => currentRepoKeys.has(r)),
164
+ ...allRepos.filter(r => !currentRepoKeys.has(r)),
165
+ ];
166
+ console.log(chalk.dim(` Found ${sorted.length} repos. Use arrows + space to select, enter to confirm.\n`));
167
+ const picked = await promptRepoPicker(sorted, {
168
+ title: 'Select repos to monitor:',
169
+ initialSelected: [...currentRepoKeys],
170
+ });
171
+ console.log();
172
+ // Check org offer: if ≥3 repos from the same real org, offer to monitor the entire org.
173
+ // Only count owners that appear in the fetched org list — excludes the personal login.
174
+ const orgSet = new Set(orgs);
175
+ const orgCounts = {};
176
+ for (const r of picked) {
177
+ const owner = r.split('/')[0];
178
+ if (orgSet.has(owner))
179
+ orgCounts[owner] = (orgCounts[owner] ?? 0) + 1;
203
180
  }
204
- else {
205
- // Login undetectable — writing an empty allowed_authors would disable filtering entirely.
206
- // Prompt so the user can provide their handle manually.
207
- const manual = await ask(' GitHub login (needed for author filter, Enter to skip): ');
208
- if (manual) {
209
- allowedAuthors = [manual];
210
- authorRoutes = { [manual]: 'claude' };
211
- }
212
- else {
213
- console.log(chalk.yellow(' ⚠ allowed_authors left empty — all authors in scope will be reviewed.'));
214
- console.log(chalk.dim(' Add routing.allowed_authors to your config to restrict later.\n'));
181
+ const orgOffers = Object.entries(orgCounts).filter(([, count]) => count >= 3).map(([org]) => org);
182
+ selectedOrgs = [...currentOrgs];
183
+ selectedRepos = picked;
184
+ for (const org of orgOffers) {
185
+ if (currentOrgs.has(org))
186
+ continue;
187
+ const answer = opts.yes ? 'n' : await ask(` Monitor all of ${chalk.cyan(org)} instead of individual repos? [y/N]: `);
188
+ if (answer.toLowerCase() === 'y') {
189
+ selectedOrgs.push(org);
190
+ selectedRepos = selectedRepos.filter(r => !r.startsWith(`${org}/`));
215
191
  }
216
192
  }
217
193
  }
218
- // ── Team path ─────────────────────────────────────────────────────────────
219
194
  }
220
- else {
221
- // Step 2: Org picker
222
- if (detectedOrgs.length > 0) {
223
- orgs = await promptOrgPicker(detectedOrgs, existing?.orgs.length ? existing.orgs : undefined);
224
- }
225
- else {
226
- const manual = await ask(' Enter org names (comma-separated, or Enter to skip): ');
227
- orgs = manual ? manual.split(',').map(s => s.trim()).filter(Boolean) : [];
228
- }
229
- // Step 3: Author filter
230
- const authorIdx = await pickOne('Whose PRs should be reviewed?', [
231
- ` ${chalk.bold('[1] All authors')} (no filter) — review every PR in the org`,
232
- ` ${chalk.bold('[2] Specific logins')} — restrict to listed team members`,
233
- ], 1);
234
- if (authorIdx === 2) {
235
- const raw = await ask(' Enter logins (comma-separated): ');
236
- allowedAuthors = raw.split(',').map(s => s.trim()).filter(Boolean);
237
- }
238
- // Step 4: Review depth
239
- const depthIdx = await pickOne('How deep should the CR workflow go?', [
240
- ` ${chalk.bold('[1] CR only')} — post review comments; humans apply fixes`,
241
- ` ${chalk.bold('[2] CR + Auto-fix')} — crosscheck also proposes and commits fixes`,
242
- ], 1);
243
- if (depthIdx === 2) {
244
- autoFix = true;
245
- const deliveryIdx = await pickOne('How should auto-fixes be delivered?', [
246
- ` ${chalk.bold('[1] Open a fix PR')} (human reviews and merges before merge) ← recommended`,
247
- ` ${chalk.bold('[2] Push directly')} onto the PR branch`,
248
- ], 1);
249
- deliveryMode = deliveryIdx === 2 ? 'commit' : 'pull_request';
250
- }
195
+ // ── Step 4: Confirm and write ──────────────────────────────────────────────
196
+ console.log(chalk.bold('Step 4 review and write config'));
197
+ console.log();
198
+ console.log(` deployment ${chalk.cyan(deployment)}`);
199
+ if (selectedOrgs.length > 0) {
200
+ console.log(` orgs ${selectedOrgs.map(o => chalk.cyan(o)).join(', ')}`);
201
+ }
202
+ if (selectedRepos.length > 0) {
203
+ console.log(` repos ${selectedRepos.slice(0, 5).map(r => chalk.cyan(r)).join(', ')}${selectedRepos.length > 5 ? chalk.dim(` +${selectedRepos.length - 5} more`) : ''}`);
251
204
  }
252
- // ── Optional branding (all paths) ─────────────────────────────────────────
253
- const brand = await askBranding(existing?.brand);
254
- // ── Confirmation preview ──────────────────────────────────────────────────
255
- const COL = 14;
256
- console.log(chalk.bold('\n Your crosscheck config:\n'));
257
- console.log(` ${'persona'.padEnd(COL)}${deployment}`);
258
- if (orgs.length)
259
- console.log(` ${'orgs'.padEnd(COL)}${orgs.join(', ')}`);
260
- if (users.length)
261
- console.log(` ${'users'.padEnd(COL)}${users.join(', ')}`);
262
- if (repos.length)
263
- console.log(` ${'repos'.padEnd(COL)}${repos.map(r => `${r.owner}/${r.name}`).join(', ')}`);
264
- console.log(` ${'filter'.padEnd(COL)}${allowedAuthors.length ? `author = ${allowedAuthors.join(', ')}` : 'all authors'}`);
265
- if (autoFix)
266
- console.log(` ${'auto-fix'.padEnd(COL)}${deliveryMode}`);
267
- if (brand.service_name !== 'crosscheck')
268
- console.log(` ${'brand'.padEnd(COL)}${brand.service_name}`);
269
- console.log(` ${'config'.padEnd(COL)}${configPath}`);
205
+ if (selectedOrgs.length === 0 && selectedRepos.length === 0) {
206
+ console.log(` ${chalk.yellow('No repos or orgs selected. Config will have empty scope.')}`);
207
+ }
208
+ console.log(` config ${chalk.dim(configPath)}`);
270
209
  console.log();
271
- const ok = await confirmWrite();
272
- if (!ok) {
273
- console.log(chalk.dim(' Cancelled. No files written.\n'));
274
- return;
210
+ if (!opts.yes) {
211
+ const confirm = await ask(` Write to config? [Y/n]: `);
212
+ if (confirm.toLowerCase() === 'n') {
213
+ console.log(chalk.dim(' Aborted — no changes written.'));
214
+ return;
215
+ }
275
216
  }
276
- // ── Write ─────────────────────────────────────────────────────────────────
277
- writeOnboardConfig(configPath, {
278
- deployment, login, orgs, users, repos,
279
- allowedAuthors, authorRoutes,
280
- autoFix, deliveryMode, brand,
217
+ mkdirSync(dirname(configPath), { recursive: true });
218
+ // patchDeploymentConfig handles deployment, orgs, users, allowed_authors, author_routes
219
+ patchDeploymentConfig(configPath, deployment, login, selectedOrgs, true);
220
+ // Patch repos after — load the file written by patchDeploymentConfig and add repos
221
+ const raw = (yaml.load(readFileSync(configPath, 'utf8')) ?? {});
222
+ raw.repos = selectedRepos.map(r => {
223
+ const [owner, name] = r.split('/');
224
+ return { owner, name };
281
225
  });
282
- console.log(chalk.green(`\n ✓ config written ${configPath}`));
283
- console.log(chalk.dim(' Run crosscheck watch to start.\n'));
226
+ // Explicit repo/org selections take precedence over the `users` expansion in watch/serve.
227
+ // Clear `users` whenever the user has set any explicit scope — repos OR orgs.
228
+ // Without this, org-only selections still trigger the personal-user expansion in watch/serve.
229
+ if (selectedRepos.length > 0 || selectedOrgs.length > 0)
230
+ delete raw.users;
231
+ writeFileSync(configPath, yaml.dump(raw, { lineWidth: -1, noRefs: true }));
232
+ console.log(chalk.green(` ✓ config written to ${configPath}`));
233
+ console.log();
234
+ // ── Step 5: Next step hint ────────────────────────────────────────────────
235
+ console.log(chalk.dim(' Run crosscheck watch to start monitoring.\n'));
284
236
  }
285
237
  //# sourceMappingURL=onboard.js.map