reviewflow 3.19.1 → 3.19.2

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 (65) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/main/cli.d.ts +1 -83
  3. package/dist/main/cli.d.ts.map +1 -1
  4. package/dist/main/cli.js +24 -606
  5. package/dist/main/cli.js.map +1 -1
  6. package/dist/main/commands/discover.command.d.ts +17 -0
  7. package/dist/main/commands/discover.command.d.ts.map +1 -0
  8. package/dist/main/commands/discover.command.js +76 -0
  9. package/dist/main/commands/discover.command.js.map +1 -0
  10. package/dist/main/commands/followupImportants.command.d.ts +14 -0
  11. package/dist/main/commands/followupImportants.command.d.ts.map +1 -0
  12. package/dist/main/commands/followupImportants.command.js +31 -0
  13. package/dist/main/commands/followupImportants.command.js.map +1 -0
  14. package/dist/main/commands/init.command.d.ts +29 -0
  15. package/dist/main/commands/init.command.d.ts.map +1 -0
  16. package/dist/main/commands/init.command.js +231 -0
  17. package/dist/main/commands/init.command.js.map +1 -0
  18. package/dist/main/commands/logs.command.d.ts +10 -0
  19. package/dist/main/commands/logs.command.d.ts.map +1 -0
  20. package/dist/main/commands/logs.command.js +42 -0
  21. package/dist/main/commands/logs.command.js.map +1 -0
  22. package/dist/main/commands/start.command.d.ts +21 -0
  23. package/dist/main/commands/start.command.d.ts.map +1 -0
  24. package/dist/main/commands/start.command.js +80 -0
  25. package/dist/main/commands/start.command.js.map +1 -0
  26. package/dist/main/commands/status.command.d.ts +9 -0
  27. package/dist/main/commands/status.command.d.ts.map +1 -0
  28. package/dist/main/commands/status.command.js +26 -0
  29. package/dist/main/commands/status.command.js.map +1 -0
  30. package/dist/main/commands/stop.command.d.ts +11 -0
  31. package/dist/main/commands/stop.command.d.ts.map +1 -0
  32. package/dist/main/commands/stop.command.js +30 -0
  33. package/dist/main/commands/stop.command.js.map +1 -0
  34. package/dist/main/commands/validate.command.d.ts +11 -0
  35. package/dist/main/commands/validate.command.d.ts.map +1 -0
  36. package/dist/main/commands/validate.command.js +54 -0
  37. package/dist/main/commands/validate.command.js.map +1 -0
  38. package/dist/main/shared/cliConstants.d.ts +5 -0
  39. package/dist/main/shared/cliConstants.d.ts.map +1 -0
  40. package/dist/main/shared/cliConstants.js +86 -0
  41. package/dist/main/shared/cliConstants.js.map +1 -0
  42. package/dist/shared/services/pidFileManager.d.ts +6 -0
  43. package/dist/shared/services/pidFileManager.d.ts.map +1 -1
  44. package/dist/shared/services/pidFileManager.js.map +1 -1
  45. package/dist/tests/units/main/executeDiscover.test.js +1 -1
  46. package/dist/tests/units/main/executeDiscover.test.js.map +1 -1
  47. package/dist/tests/units/main/executeFollowupImportants.test.d.ts +2 -0
  48. package/dist/tests/units/main/executeFollowupImportants.test.d.ts.map +1 -0
  49. package/dist/tests/units/main/executeFollowupImportants.test.js +48 -0
  50. package/dist/tests/units/main/executeFollowupImportants.test.js.map +1 -0
  51. package/dist/tests/units/main/executeInit.test.js +1 -1
  52. package/dist/tests/units/main/executeInit.test.js.map +1 -1
  53. package/dist/tests/units/main/executeLogs.test.js +1 -1
  54. package/dist/tests/units/main/executeLogs.test.js.map +1 -1
  55. package/dist/tests/units/main/executeStart.test.js +1 -1
  56. package/dist/tests/units/main/executeStart.test.js.map +1 -1
  57. package/dist/tests/units/main/executeStatus.test.js +1 -1
  58. package/dist/tests/units/main/executeStatus.test.js.map +1 -1
  59. package/dist/tests/units/main/executeStop.test.js +1 -1
  60. package/dist/tests/units/main/executeStop.test.js.map +1 -1
  61. package/dist/tests/units/main/executeValidate.test.d.ts +2 -0
  62. package/dist/tests/units/main/executeValidate.test.d.ts.map +1 -0
  63. package/dist/tests/units/main/executeValidate.test.js +76 -0
  64. package/dist/tests/units/main/executeValidate.test.js.map +1 -0
  65. package/package.json +1 -1
package/dist/main/cli.js CHANGED
@@ -1,438 +1,26 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync, realpathSync, existsSync, mkdirSync, writeFileSync, readdirSync, copyFileSync } from 'node:fs';
3
- import { join, dirname, resolve } from 'node:path';
2
+ import { realpathSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
- import { execSync } from 'node:child_process';
6
- import { homedir } from 'node:os';
7
5
  import { parseCliArgs } from '../cli/parseCliArgs.js';
8
- import { validateDependencies, checkDependency } from '../shared/services/dependencyChecker.js';
9
- import { startServer } from '../main/server.js';
10
- import { StartDaemonUseCase } from '../modules/cli-configuration/usecases/cli/startDaemon.usecase.js';
11
- import { StopDaemonUseCase } from '../modules/cli-configuration/usecases/cli/stopDaemon.usecase.js';
12
- import { QueryStatusUseCase } from '../modules/cli-configuration/usecases/cli/queryStatus.usecase.js';
13
- import { ReadLogsUseCase } from '../modules/cli-configuration/usecases/cli/readLogs.usecase.js';
14
- import { FollowupImportantsUseCase } from '../modules/cli-configuration/usecases/cli/followupImportants.usecase.js';
6
+ import { executeStart, createStartDependencies } from '../main/commands/start.command.js';
7
+ import { executeStop, createStopDependencies } from '../main/commands/stop.command.js';
8
+ import { executeStatus, createStatusDependencies } from '../main/commands/status.command.js';
9
+ import { executeLogs, createLogsDependencies } from '../main/commands/logs.command.js';
10
+ import { executeFollowupImportants, createFollowupImportantsDependencies } from '../main/commands/followupImportants.command.js';
11
+ import { executeInit, createInitDependencies } from '../main/commands/init.command.js';
12
+ import { executeDiscover, createDiscoverDependencies } from '../main/commands/discover.command.js';
13
+ import { executeValidate, createValidateDependencies } from '../main/commands/validate.command.js';
14
+ import { readVersion, printHelp, getGitRemoteUrl } from '../main/shared/cliConstants.js';
15
15
  import { readPidFile, writePidFile, removePidFile } from '../shared/services/pidFileManager.js';
16
16
  import { isProcessRunning } from '../shared/services/processChecker.js';
17
- import { PID_FILE_PATH, LOG_FILE_PATH } from '../shared/services/daemonPaths.js';
18
- import { spawnDaemon } from '../shared/services/daemonSpawner.js';
19
- import { logFileExists, readLastLines, watchLogFile } from '../shared/services/logFileReader.js';
20
- import { green, red, yellow, dim, bold } from '../shared/services/ansiColors.js';
21
- import { formatStartupBanner } from '../cli/startupBanner.js';
22
- import { openInBrowser } from '../shared/services/browserOpener.js';
23
- import { loadConfig } from '../frameworks/config/configLoader.js';
24
- import { getConfigDir } from '../shared/services/configDir.js';
25
- import { generateWebhookSecret, truncateSecret } from '../shared/services/secretGenerator.js';
26
- import { DiscoverRepositoriesUseCase } from '../modules/cli-configuration/usecases/cli/discoverRepositories.usecase.js';
27
- import { ConfigureMcpUseCase } from '../modules/cli-configuration/usecases/cli/configureMcp.usecase.js';
28
- import { WriteInitConfigUseCase } from '../modules/cli-configuration/usecases/cli/writeInitConfig.usecase.js';
29
- import { ValidateConfigUseCase } from '../modules/cli-configuration/usecases/cli/validateConfig.usecase.js';
30
- import { AddRepositoriesToConfigUseCase } from '../modules/cli-configuration/usecases/cli/addRepositoriesToConfig.usecase.js';
31
- import { formatInitSummary } from '../cli/formatters/initSummary.js';
32
- import { resolveMcpServerPath } from '../frameworks/claude/claudeInvoker.js';
33
- import { checkInitPrerequisites } from '../modules/cli-configuration/usecases/cli/checkInitPrerequisites.js';
34
- const currentDir = dirname(fileURLToPath(import.meta.url));
35
- function readVersion() {
36
- const packageJsonPath = join(currentDir, '..', '..', 'package.json');
37
- const raw = readFileSync(packageJsonPath, 'utf-8');
38
- return JSON.parse(raw).version;
39
- }
40
- function printHelp() {
41
- console.log(`reviewflow - Automated code review for GitLab/GitHub
42
-
43
- Usage:
44
- reviewflow [command] [options]
45
-
46
- Commands:
47
- init Interactive setup wizard
48
- start Start the review server (default)
49
- stop Stop the running daemon
50
- status Show server status
51
- logs Show daemon logs
52
- validate Validate configuration
53
- discover Scan and add repositories to existing config
54
- followup-importants Trigger followups for pending-approval MRs with Important issues
55
-
56
- Discover options:
57
- --scan-path <path> Custom scan path (repeatable)
58
- --max-depth <n> Max directory depth (default: 3)
59
-
60
- Init options:
61
- -y, --yes Accept all defaults (non-interactive)
62
- --skip-mcp Skip MCP server configuration
63
- --show-secrets Display full webhook secrets
64
- --scan-path <path> Custom scan path (repeatable)
65
-
66
- Start options:
67
- -d, --daemon Run as background daemon
68
- -p, --port <port> Server port (default: from config)
69
- -o, --open Open dashboard in default browser
70
- --skip-dependency-check Skip external dependency verification
71
-
72
- Stop options:
73
- -f, --force Force stop (SIGKILL instead of SIGTERM)
74
-
75
- Status options:
76
- --json Output status as JSON
77
-
78
- Logs options:
79
- -f, --follow Follow log output (tail -f)
80
- -n, --lines <count> Number of lines to show (default: 20)
81
-
82
- Validate options:
83
- --fix Auto-fix correctable issues
84
-
85
- Followup-importants options:
86
- -p, --project <path> Scan specific project only
87
- -y, --yes Skip confirmation prompt
88
-
89
- General options:
90
- -v, --version Show version
91
- -h, --help Show this help
92
- `);
93
- }
94
- function showBanner(port, daemonPid, open, deps) {
95
- const { enabledPlatforms } = deps.loadStartupInfo();
96
- const banner = formatStartupBanner({ port, enabledPlatforms, daemonPid });
97
- for (const line of banner.lines) {
98
- deps.log(line);
99
- }
100
- if (open) {
101
- deps.openInBrowser(banner.dashboardUrl);
102
- }
103
- }
104
- export function executeStart(skipDependencyCheck, daemon, port, open, deps) {
105
- const resolvedPort = port ?? deps.loadStartupInfo().defaultPort;
106
- if (daemon) {
107
- const usecase = new StartDaemonUseCase(deps.startDaemonDeps);
108
- const result = usecase.execute({ daemon: true, port });
109
- switch (result.status) {
110
- case 'started':
111
- showBanner(resolvedPort, result.pid, open, deps);
112
- break;
113
- case 'already-running':
114
- deps.log(yellow(`Server already running (PID: ${result.pid}, port: ${result.port})`));
115
- break;
116
- case 'foreground':
117
- break;
118
- }
119
- return;
120
- }
121
- if (!skipDependencyCheck) {
122
- const missing = deps.validateDependencies();
123
- if (missing.length > 0) {
124
- deps.error('Missing dependencies:');
125
- for (const dep of missing) {
126
- deps.error(` - ${dep.name}: ${dep.installUrl}`);
127
- }
128
- deps.exit(1);
129
- return;
130
- }
131
- }
132
- const startForeground = async () => {
133
- try {
134
- await deps.startServer(port);
135
- showBanner(resolvedPort, null, open, deps);
136
- }
137
- catch (err) {
138
- deps.error('Fatal error:', err);
139
- deps.exit(1);
140
- }
141
- };
142
- startForeground();
143
- }
144
- export function executeStop(force, deps) {
145
- const usecase = new StopDaemonUseCase(deps.stopDaemonDeps);
146
- const result = usecase.execute({ force });
147
- switch (result.status) {
148
- case 'stopped':
149
- deps.log(green(`Server stopped (PID: ${result.pid})`));
150
- break;
151
- case 'not-running':
152
- deps.log(yellow('Server is not running'));
153
- break;
154
- case 'failed':
155
- deps.error(red(`Failed to stop server: ${result.reason}`));
156
- deps.exit(1);
157
- break;
158
- }
159
- }
160
- export function executeStatus(json, deps) {
161
- const usecase = new QueryStatusUseCase(deps.queryStatusDeps);
162
- const result = usecase.execute();
163
- if (json) {
164
- deps.log(JSON.stringify(result));
165
- if (result.status === 'stopped')
166
- deps.exit(1);
167
- return;
168
- }
169
- if (result.status === 'running') {
170
- deps.log(green(bold('ReviewFlow is running')));
171
- deps.log(dim(` PID: ${result.pid}`));
172
- deps.log(dim(` Port: ${result.port}`));
173
- deps.log(dim(` Started at: ${result.startedAt}`));
174
- }
175
- else {
176
- deps.log(red('ReviewFlow is not running'));
177
- deps.exit(1);
178
- }
179
- }
180
- export function executeLogs(follow, lines, deps) {
181
- const onLine = (line) => deps.log(line);
182
- const usecase = new ReadLogsUseCase(deps.readLogsDeps);
183
- const result = usecase.execute({ follow, lines, onLine });
184
- switch (result.status) {
185
- case 'no-logs':
186
- deps.error(yellow('No log file found. Start the daemon first.'));
187
- deps.exit(1);
188
- break;
189
- case 'read':
190
- for (const line of result.lines) {
191
- deps.log(line);
192
- }
193
- break;
194
- case 'following':
195
- for (const line of result.initialLines) {
196
- deps.log(line);
197
- }
198
- process.on('SIGINT', () => {
199
- result.stop();
200
- process.exit(0);
201
- });
202
- break;
203
- }
204
- }
205
- async function executeFollowupImportants(project) {
206
- const pidData = readPidFile(PID_FILE_PATH);
207
- if (!pidData || !isProcessRunning(pidData.pid)) {
208
- console.error(red('Server is not running. Start with: reviewflow start'));
209
- process.exit(1);
210
- }
211
- const usecase = new FollowupImportantsUseCase({
212
- serverPort: pidData.port,
213
- log: console.log,
214
- error: console.error,
215
- fetch: globalThis.fetch,
216
- });
217
- await usecase.execute({ project });
218
- }
219
- const DEFAULT_SCAN_PATHS = [
220
- join(homedir(), 'Documents'),
221
- join(homedir(), 'Projects'),
222
- join(homedir(), 'Development'),
223
- join(homedir(), 'dev'),
224
- join(homedir(), 'repos'),
225
- ];
226
- function getGitRemoteUrl(localPath) {
227
- try {
228
- const result = execSync('git remote get-url origin', {
229
- cwd: localPath,
230
- encoding: 'utf-8',
231
- timeout: 5000,
232
- });
233
- return result.trim().replace(/\.git$/, '');
234
- }
235
- catch {
236
- return null;
237
- }
238
- }
239
- const WELCOME_BANNER = `
240
- Welcome to ReviewFlow!
241
- Automated code review powered by Claude Code.
242
- `;
243
- export async function executeInit(yes, skipMcp, showSecrets, scanPaths, deps) {
244
- deps.log(WELCOME_BANNER);
245
- const prereqResult = deps.checkPrerequisites();
246
- if (prereqResult.status === 'node-version-too-low') {
247
- deps.log(red(`Node.js >= ${prereqResult.required} is required (found: ${prereqResult.found})`));
248
- deps.exit(1);
249
- return;
250
- }
251
- if (prereqResult.status === 'claude-not-installed') {
252
- deps.log(red(`Claude CLI is not installed. Install it from: ${prereqResult.installUrl}`));
253
- deps.exit(1);
254
- return;
255
- }
256
- const configDir = deps.getConfigDir();
257
- const configPath = join(configDir, 'config.json');
258
- if (deps.existsSync(configPath) && !yes) {
259
- const overwrite = await deps.confirmOverwrite(configPath);
260
- if (!overwrite) {
261
- deps.log(yellow('Init cancelled.'));
262
- return;
263
- }
264
- }
265
- let port = 3847;
266
- let gitlabUsername = '';
267
- let githubUsername = '';
268
- if (yes) {
269
- deps.log(dim('Non-interactive mode: using defaults'));
270
- }
271
- else {
272
- const platform = await deps.promptPlatform();
273
- port = await deps.promptPort();
274
- if (platform === 'gitlab' || platform === 'both') {
275
- gitlabUsername = await deps.promptGitlabUsername();
276
- }
277
- if (platform === 'github' || platform === 'both') {
278
- githubUsername = await deps.promptGithubUsername();
279
- }
280
- }
281
- const gitlabSecret = deps.generateWebhookSecret();
282
- const githubSecret = deps.generateWebhookSecret();
283
- deps.log('');
284
- deps.log(bold('Webhook secrets generated:'));
285
- if (showSecrets) {
286
- deps.log(` GitLab: ${gitlabSecret}`);
287
- deps.log(` GitHub: ${githubSecret}`);
288
- }
289
- else {
290
- deps.log(` GitLab: ${deps.truncateSecret(gitlabSecret, 16)}`);
291
- deps.log(` GitHub: ${deps.truncateSecret(githubSecret, 16)}`);
292
- deps.log(dim(' Use --show-secrets to display full values'));
293
- }
294
- const pathsToScan = scanPaths.length > 0 ? scanPaths : DEFAULT_SCAN_PATHS;
295
- let selectedRepos = [];
296
- const shouldScan = yes || await deps.confirmScanRepositories();
297
- if (shouldScan) {
298
- deps.log(dim('\nScanning for repositories...'));
299
- const discovered = deps.discoverRepositories(pathsToScan, 3);
300
- deps.log(` Found ${discovered.repositories.length} repositories`);
301
- if (discovered.repositories.length > 0) {
302
- if (yes) {
303
- selectedRepos = discovered.repositories.map(r => ({
304
- name: r.name,
305
- localPath: r.localPath,
306
- enabled: true,
307
- }));
308
- }
309
- else {
310
- const selected = await deps.selectRepositories(discovered.repositories);
311
- selectedRepos = selected.map(r => ({
312
- name: r.name,
313
- localPath: r.localPath,
314
- enabled: true,
315
- }));
316
- }
317
- }
318
- }
319
- let mcpStatus = 'skipped';
320
- if (!skipMcp) {
321
- deps.log(dim('\nConfiguring MCP server...'));
322
- try {
323
- mcpStatus = deps.configureMcp();
324
- }
325
- catch {
326
- mcpStatus = 'failed';
327
- }
328
- deps.log(` MCP: ${mcpStatus}`);
329
- }
330
- const result = deps.writeConfig({
331
- configDir,
332
- port,
333
- gitlabUsername,
334
- githubUsername,
335
- repositories: selectedRepos,
336
- gitlabWebhookSecret: gitlabSecret,
337
- githubWebhookSecret: githubSecret,
338
- });
339
- const summary = deps.formatSummary({
340
- configPath: result.configPath,
341
- envPath: result.envPath,
342
- port,
343
- repositoryCount: selectedRepos.length,
344
- mcpStatus,
345
- gitlabUsername,
346
- githubUsername,
347
- });
348
- deps.log(green(summary));
349
- }
350
- export async function executeDiscover(scanPaths, maxDepth, deps) {
351
- const configPath = deps.getConfigPath();
352
- const pathsToScan = scanPaths.length > 0 ? scanPaths : DEFAULT_SCAN_PATHS;
353
- deps.log(dim('\nScanning for repositories...'));
354
- const discoverer = new DiscoverRepositoriesUseCase({
355
- existsSync: deps.existsSync,
356
- readdirSync: deps.readdirSync,
357
- getGitRemoteUrl: deps.getGitRemoteUrl,
358
- });
359
- const discovered = discoverer.execute({ scanPaths: pathsToScan, maxDepth });
360
- deps.log(` Found ${discovered.repositories.length} repositories`);
361
- if (discovered.repositories.length === 0) {
362
- deps.log(yellow('No new repositories found.'));
363
- return;
364
- }
365
- const selected = await deps.selectRepositories(discovered.repositories);
366
- if (selected.length === 0) {
367
- deps.log(yellow('No repositories selected.'));
368
- return;
369
- }
370
- const adder = new AddRepositoriesToConfigUseCase({
371
- readFileSync: deps.readFileSync,
372
- writeFileSync: deps.writeFileSync,
373
- existsSync: deps.existsSync,
374
- });
375
- const result = adder.execute({
376
- configPath,
377
- newRepositories: selected.map(repo => ({
378
- name: repo.name,
379
- localPath: repo.localPath,
380
- enabled: true,
381
- })),
382
- });
383
- if (result.added.length > 0) {
384
- deps.log(green(`\n Added ${result.added.length} repositories:`));
385
- for (const repo of result.added) {
386
- deps.log(green(` + ${repo.name} (${repo.localPath})`));
387
- }
388
- }
389
- if (result.skipped.length > 0) {
390
- deps.log(dim(` Skipped ${result.skipped.length} already configured`));
391
- }
392
- }
393
- export function executeValidate(fix) {
394
- const configDir = getConfigDir();
395
- const configPath = join(configDir, 'config.json');
396
- const envPath = join(configDir, '.env');
397
- const cwdConfigPath = join(process.cwd(), 'config.json');
398
- const resolvedConfigPath = existsSync(cwdConfigPath) ? cwdConfigPath : configPath;
399
- const resolvedEnvPath = existsSync(join(process.cwd(), '.env')) ? join(process.cwd(), '.env') : envPath;
400
- const validator = new ValidateConfigUseCase({
401
- existsSync,
402
- readFileSync,
403
- });
404
- const result = validator.execute({ configPath: resolvedConfigPath, envPath: resolvedEnvPath });
405
- switch (result.status) {
406
- case 'not-found':
407
- console.log(red('No configuration found.'));
408
- console.log(dim(`Looked in: ${resolvedConfigPath}`));
409
- console.log(`Run ${bold('reviewflow init')} to create one.`);
410
- process.exit(1);
411
- break;
412
- case 'valid':
413
- console.log(green(bold('Configuration is valid!')));
414
- console.log(dim(` Config: ${resolvedConfigPath}`));
415
- console.log(dim(` Env: ${resolvedEnvPath}`));
416
- break;
417
- case 'invalid':
418
- console.log(red(bold('Configuration has issues:')));
419
- for (const issue of result.issues) {
420
- const prefix = issue.severity === 'error' ? red('ERROR') : yellow('WARN');
421
- console.log(` ${prefix} [${issue.field}]: ${issue.message}`);
422
- }
423
- if (fix) {
424
- console.log(dim('\n--fix flag detected, but no auto-fixable issues implemented yet.'));
425
- }
426
- process.exit(1);
427
- break;
428
- }
429
- }
17
+ import { PID_FILE_PATH } from '../shared/services/daemonPaths.js';
430
18
  function createPidFileDeps() {
431
19
  return {
432
20
  readPidFile: () => readPidFile(PID_FILE_PATH),
433
21
  writePidFile: (content) => writePidFile(PID_FILE_PATH, content),
434
22
  removePidFile: () => removePidFile(PID_FILE_PATH),
435
- isProcessRunning: (pid) => isProcessRunning(pid),
23
+ isProcessRunning,
436
24
  };
437
25
  }
438
26
  const isDirectlyExecuted = process.argv[1] &&
@@ -446,199 +34,29 @@ if (isDirectlyExecuted) {
446
34
  case 'help':
447
35
  printHelp();
448
36
  break;
449
- case 'start': {
450
- const pidDeps = createPidFileDeps();
451
- executeStart(args.skipDependencyCheck, args.daemon, args.port, args.open, {
452
- validateDependencies,
453
- startServer: (port) => startServer({ portOverride: port }),
454
- exit: process.exit,
455
- error: console.error,
456
- log: console.log,
457
- startDaemonDeps: {
458
- ...pidDeps,
459
- spawnDaemon,
460
- },
461
- loadStartupInfo: () => {
462
- try {
463
- const config = loadConfig();
464
- const enabledPlatforms = [...new Set(config.repositories.filter(r => r.enabled).map(r => r.platform))];
465
- return { enabledPlatforms, defaultPort: config.server.port };
466
- }
467
- catch {
468
- return { enabledPlatforms: [], defaultPort: 3000 };
469
- }
470
- },
471
- openInBrowser,
472
- });
37
+ case 'start':
38
+ executeStart(args.skipDependencyCheck, args.daemon, args.port, args.open, createStartDependencies(createPidFileDeps()));
473
39
  break;
474
- }
475
- case 'stop': {
476
- const pidDeps = createPidFileDeps();
477
- executeStop(args.force, {
478
- stopDaemonDeps: {
479
- ...pidDeps,
480
- killProcess: (pid, signal) => process.kill(pid, signal),
481
- },
482
- log: console.log,
483
- error: console.error,
484
- exit: process.exit,
485
- });
40
+ case 'stop':
41
+ executeStop(args.force, createStopDependencies(createPidFileDeps()));
486
42
  break;
487
- }
488
- case 'status': {
489
- const pidDeps = createPidFileDeps();
490
- executeStatus(args.json, {
491
- queryStatusDeps: pidDeps,
492
- log: console.log,
493
- exit: process.exit,
494
- });
43
+ case 'status':
44
+ executeStatus(args.json, createStatusDependencies(createPidFileDeps()));
495
45
  break;
496
- }
497
46
  case 'logs':
498
- executeLogs(args.follow, args.lines, {
499
- readLogsDeps: {
500
- logFileExists: () => logFileExists(LOG_FILE_PATH),
501
- readLastLines: (count) => readLastLines(LOG_FILE_PATH, count),
502
- watchFile: (onLine) => watchLogFile(LOG_FILE_PATH, onLine),
503
- },
504
- log: console.log,
505
- error: console.error,
506
- exit: process.exit,
507
- });
47
+ executeLogs(args.follow, args.lines, createLogsDependencies());
508
48
  break;
509
49
  case 'init':
510
- executeInit(args.yes, args.skipMcp, args.showSecrets, args.scanPaths, {
511
- log: console.log,
512
- exit: process.exit,
513
- getConfigDir,
514
- existsSync,
515
- checkPrerequisites: () => checkInitPrerequisites({
516
- executeCommand: execSync,
517
- getNodeMajorVersion: () => Number(process.versions.node.split('.')[0]),
518
- }),
519
- confirmOverwrite: async (configPath) => {
520
- const { confirm } = await import('@inquirer/prompts');
521
- return confirm({
522
- message: `Config already exists at ${configPath}. Overwrite?`,
523
- default: false,
524
- });
525
- },
526
- promptPlatform: async () => {
527
- const { select } = await import('@inquirer/prompts');
528
- return select({
529
- message: 'Which platform(s) do you use?',
530
- choices: [
531
- { name: 'GitLab', value: 'gitlab' },
532
- { name: 'GitHub', value: 'github' },
533
- { name: 'Both', value: 'both' },
534
- ],
535
- });
536
- },
537
- promptPort: async () => {
538
- const { number: numberPrompt } = await import('@inquirer/prompts');
539
- const portAnswer = await numberPrompt({
540
- message: 'Server port:',
541
- default: 3847,
542
- validate: (value) => {
543
- if (value === undefined || value < 1 || value > 65535)
544
- return 'Port must be between 1 and 65535';
545
- return true;
546
- },
547
- });
548
- return portAnswer ?? 3847;
549
- },
550
- promptGitlabUsername: async () => {
551
- const { input } = await import('@inquirer/prompts');
552
- return input({ message: 'GitLab username:', default: '' });
553
- },
554
- promptGithubUsername: async () => {
555
- const { input } = await import('@inquirer/prompts');
556
- return input({ message: 'GitHub username:', default: '' });
557
- },
558
- confirmScanRepositories: async () => {
559
- const { confirm } = await import('@inquirer/prompts');
560
- return confirm({ message: 'Scan for repositories?', default: true });
561
- },
562
- selectRepositories: async (repositories) => {
563
- const { checkbox } = await import('@inquirer/prompts');
564
- return checkbox({
565
- message: 'Select repositories to configure:',
566
- choices: repositories.map(r => ({
567
- name: `${r.name} ${dim(`(${r.localPath})`)}${r.hasReviewConfig ? green(' [configured]') : ''}`,
568
- value: r,
569
- checked: r.hasReviewConfig,
570
- })),
571
- });
572
- },
573
- generateWebhookSecret,
574
- truncateSecret,
575
- discoverRepositories: (scanPaths, maxDepth) => {
576
- const discoverer = new DiscoverRepositoriesUseCase({
577
- existsSync,
578
- readdirSync: (path) => readdirSync(path, { withFileTypes: true }).map(d => ({
579
- name: d.name,
580
- isDirectory: () => d.isDirectory(),
581
- })),
582
- getGitRemoteUrl,
583
- });
584
- return discoverer.execute({ scanPaths, maxDepth });
585
- },
586
- configureMcp: () => {
587
- const mcpUseCase = new ConfigureMcpUseCase({
588
- isClaudeInstalled: () => checkDependency({ name: 'Claude', command: 'claude --version' }),
589
- readFileSync,
590
- writeFileSync,
591
- existsSync,
592
- copyFileSync,
593
- resolveMcpServerPath: () => {
594
- try {
595
- return resolveMcpServerPath();
596
- }
597
- catch {
598
- return join(dirname(fileURLToPath(import.meta.url)), '..', 'mcpServer.js');
599
- }
600
- },
601
- settingsPath: join(homedir(), '.claude', 'settings.json'),
602
- });
603
- return mcpUseCase.execute();
604
- },
605
- writeConfig: (input) => {
606
- const writer = new WriteInitConfigUseCase({ mkdirSync, writeFileSync });
607
- return writer.execute(input);
608
- },
609
- formatSummary: formatInitSummary,
610
- });
50
+ executeInit(args.yes, args.skipMcp, args.showSecrets, args.scanPaths, createInitDependencies(getGitRemoteUrl));
611
51
  break;
612
52
  case 'discover':
613
- executeDiscover(args.scanPaths, args.maxDepth, {
614
- existsSync,
615
- readFileSync,
616
- writeFileSync,
617
- readdirSync: (path) => readdirSync(path, { withFileTypes: true }).map(d => ({
618
- name: d.name,
619
- isDirectory: () => d.isDirectory(),
620
- })),
621
- getGitRemoteUrl,
622
- getConfigPath: () => join(getConfigDir(), 'config.json'),
623
- log: console.log,
624
- selectRepositories: async (repositories) => {
625
- const { checkbox } = await import('@inquirer/prompts');
626
- return checkbox({
627
- message: 'Select repositories to add:',
628
- choices: repositories.map(r => ({
629
- name: `${r.name} ${dim(`(${r.localPath})`)}${r.hasReviewConfig ? green(' [configured]') : ''}`,
630
- value: r,
631
- checked: r.hasReviewConfig,
632
- })),
633
- });
634
- },
635
- });
53
+ executeDiscover(args.scanPaths, args.maxDepth, createDiscoverDependencies(getGitRemoteUrl));
636
54
  break;
637
55
  case 'validate':
638
- executeValidate(args.fix);
56
+ executeValidate(args.fix, createValidateDependencies());
639
57
  break;
640
58
  case 'followup-importants':
641
- executeFollowupImportants(args.project);
59
+ executeFollowupImportants(args.project, createFollowupImportantsDependencies());
642
60
  break;
643
61
  }
644
62
  }