kodevu 0.1.32 → 0.1.33

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/README.md CHANGED
@@ -2,98 +2,88 @@
2
2
 
3
3
  A Node.js tool that fetches Git commits or SVN revisions, sends the diff to a supported AI reviewer CLI, and writes review results to report files.
4
4
 
5
- ## Workflow
5
+ ## Pure & Zero Config
6
6
 
7
- 1. Detect the repository type automatically (Git or SVN).
8
- 2. Fetch the specified revision(s) as requested by the user:
9
- - A single specific revision/commit via `--rev`.
10
- - The latest $N$ revisions/commits via `--last` (default: 1).
11
- 3. For each change:
12
- - Load metadata and changed paths from SVN or Git.
13
- - Generate a unified diff for that single revision or commit.
14
- - Send the diff and change metadata to the configured reviewer CLI.
15
- - Allow the reviewer to inspect related local repository files in read-only mode when a local workspace is available.
16
- - Write the result to `~/.kodevu/` (Markdown by default; optional JSON via config).
7
+ Kodevu is designed to be stateless and requires no configuration files. It relies entirely on command-line arguments and environment variables.
17
8
 
18
- **Note**: Kodevu is stateless. It does not track which changes have been reviewed previously.
9
+ 1. **Automatic Detection**: Detects repository type (Git/SVN), language, and available reviewers.
10
+ 2. **Stateless**: Does not track history; reviews exactly what you ask for.
11
+ 3. **Flexible**: Every setting can be overridden via CLI flags or ENV vars.
19
12
 
20
- ## Quick start
13
+ ## Quick Start
21
14
 
22
15
  Review the latest commit in your repository:
23
16
 
24
17
  ```bash
25
- npx kodevu /path/to/your/repo
18
+ npx kodevu .
26
19
  ```
27
20
 
28
21
  Review the latest 3 commits:
29
22
 
30
23
  ```bash
31
- npx kodevu /path/to/your/repo --last 3
24
+ npx kodevu . --last 3
32
25
  ```
33
26
 
34
27
  Review a specific commit:
35
28
 
36
29
  ```bash
37
- npx kodevu /path/to/your/repo --rev abc1234
30
+ npx kodevu . --rev abc1234
38
31
  ```
39
32
 
40
- Review reports are written to `~/.kodevu/` as Markdown (`.md`) by default.
33
+ Reports are written to `~/.kodevu/` by default.
41
34
 
42
- ## Setup
43
-
44
- If you want to customize settings beyond the defaults:
35
+ ## Usage
45
36
 
46
37
  ```bash
47
- npx kodevu init
38
+ npx kodevu [target] [options]
48
39
  ```
49
40
 
50
- This creates `config.json` in the current directory. You only need this when you want to override defaults such as `reviewer` or output paths.
41
+ ### Options
51
42
 
52
- ## Run
43
+ - `target`: Repository path (Git) or SVN URL/Working copy (default: `.`).
44
+ - `--reviewer, -r`: `codex`, `gemini`, `copilot`, or `auto` (default: `auto`).
45
+ - `--rev, -v`: A specific revision or commit hash to review.
46
+ - `--last, -n`: Number of latest revisions to review (default: 1).
47
+ - `--lang, -l`: Output language (e.g., `zh`, `en`, `auto`).
48
+ - `--prompt, -p`: Additional instructions for the reviewer. Use `@file.txt` to read from a file.
49
+ - `--output, -o`: Report output directory (default: `~/.kodevu`).
50
+ - `--format, -f`: Output formats (e.g., `markdown`, `json`, or `markdown,json`).
51
+ - `--debug, -d`: Print debug information.
53
52
 
54
- Specify the output language (e.g., Chinese):
53
+ ### Environment Variables
55
54
 
56
- ```bash
57
- npx kodevu /path/to/your/repo --lang zh
58
- ```
55
+ You can set these in your shell to change default behavior without typing flags every time:
59
56
 
60
- Run with debug logs:
57
+ - `KODEVU_REVIEWER`: Default reviewer.
58
+ - `KODEVU_LANG`: Default language.
59
+ - `KODEVU_OUTPUT_DIR`: Default output directory.
60
+ - `KODEVU_PROMPT`: Default prompt instructions.
61
+ - `KODEVU_TIMEOUT`: Reviewer execution timeout in milliseconds.
61
62
 
63
+ ## Examples
64
+
65
+ **Review with a custom prompt from a file:**
62
66
  ```bash
63
- npx kodevu /path/to/your/repo --debug
67
+ npx kodevu . --prompt @my-rules.txt
64
68
  ```
65
69
 
66
- Use a custom config path:
67
-
70
+ **Generate JSON reports in a local folder:**
68
71
  ```bash
69
- npx kodevu --config ./my-config.json
72
+ npx kodevu . --format json --output ./review-reports
70
73
  ```
71
74
 
72
- ## Config / CLI Options
73
-
74
- - `target`: Repository target path or SVN URL. (CLI positional argument overrides config)
75
- - `reviewer`: `codex`, `gemini`, `copilot`, or `auto` (default: `auto`).
76
- - `rev`: A specific revision or commit hash to review.
77
- - `last`: Number of latest revisions to review (default: 1 if `rev` is not set).
78
- - `lang`: Output language for the review (e.g., `zh`, `en`, `auto`).
79
- - `prompt`: Additional instructions for the reviewer.
80
- - `outputDir`: Report output directory (default: `~/.kodevu`).
81
- - `outputFormats`: Report formats to generate (supports `markdown` and `json`; default: `["markdown"]`).
82
- - `commandTimeoutMs`: Timeout for a single review command execution in milliseconds.
83
- - `maxRevisionsPerRun`: Cap the number of revisions handled in one run.
84
-
85
- ## Target Rules
86
-
87
- - For SVN, `target` can be a working copy path or repository URL.
88
- - For Git, `target` must be a local repository path or a subdirectory inside a local repository.
89
- - The tool tries Git first, then falls back to SVN.
75
+ **Set a persistent reviewer via ENV:**
76
+ ```bash
77
+ export KODEVU_REVIEWER=gemini
78
+ npx kodevu .
79
+ ```
90
80
 
91
- ## Notes
81
+ ## How it Works
92
82
 
93
- - `reviewer: "auto"` probes `codex`, `gemini`, and `copilot` in `PATH`, then selects an available one.
94
- - Large diffs are truncated based on internal limits to fit AI context windows.
95
- - For Git targets and local SVN working copies, the reviewer command runs from the repository workspace so it can inspect related files beyond the diff.
96
- - If the reviewer command exits non-zero or times out, the report is still written containing the error details.
83
+ - **Git Targets**: `target` must be a local repository or subdirectory.
84
+ - **SVN Targets**: `target` can be a working copy path or repository URL.
85
+ - **Reviewer "auto"**: Probes `codex`, `gemini`, and `copilot` in your `PATH` and selects one.
86
+ - **Contextual Review**: For local repositories, the reviewer can inspect related files beyond the diff to provide deeper insights.
97
87
 
98
88
  ## License
99
89
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodevu",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Poll SVN revisions or Git commits, send each change diff to a reviewer CLI, and write configurable review reports.",
package/src/config.js CHANGED
@@ -6,7 +6,6 @@ import { findCommandOnPath } from "./shell.js";
6
6
  const defaultStorageDir = path.join(os.homedir(), ".kodevu");
7
7
  const SUPPORTED_REVIEWERS = ["codex", "gemini", "copilot"];
8
8
 
9
-
10
9
  const defaultConfig = {
11
10
  reviewer: "auto",
12
11
  target: "",
@@ -21,61 +20,36 @@ const defaultConfig = {
21
20
  last: 0
22
21
  };
23
22
 
24
- const configTemplate = {
25
- target: "C:/path/to/your/repository-or-subdirectory",
26
- reviewer: defaultConfig.reviewer,
27
- lang: defaultConfig.lang,
28
- prompt: defaultConfig.prompt,
29
- outputDir: "~/.kodevu",
30
- logsDir: "~/.kodevu/logs",
31
- commandTimeoutMs: defaultConfig.commandTimeoutMs,
32
- maxRevisionsPerRun: defaultConfig.maxRevisionsPerRun,
33
- outputFormats: defaultConfig.outputFormats,
34
- rev: "",
35
- last: 1
23
+ const ENV_MAP = {
24
+ KODEVU_REVIEWER: "reviewer",
25
+ KODEVU_LANG: "lang",
26
+ KODEVU_OUTPUT_DIR: "outputDir",
27
+ KODEVU_PROMPT: "prompt",
28
+ KODEVU_TIMEOUT: "commandTimeoutMs",
29
+ KODEVU_MAX_REVISIONS: "maxRevisionsPerRun",
30
+ KODEVU_FORMATS: "outputFormats"
36
31
  };
37
32
 
38
- function resolveConfigPath(baseDir, value) {
39
- if (!value) {
40
- return value;
41
- }
42
-
43
- if (typeof value !== "string") {
44
- return path.resolve(baseDir, String(value));
45
- }
46
-
47
- if (value === "~") {
48
- return os.homedir();
49
- }
50
-
33
+ function resolvePath(value) {
34
+ if (!value) return value;
35
+ if (value === "~") return os.homedir();
51
36
  if (value.startsWith("~/") || value.startsWith("~\\")) {
52
37
  return path.join(os.homedir(), value.slice(2));
53
38
  }
54
-
55
- return path.isAbsolute(value) ? value : path.resolve(baseDir, value);
39
+ return path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
56
40
  }
57
41
 
58
-
59
- function normalizeOutputFormats(outputFormats, loadedConfigPath) {
42
+ function normalizeOutputFormats(outputFormats) {
60
43
  const source = outputFormats == null ? ["markdown"] : outputFormats;
61
- const values = Array.isArray(source) ? source : [source];
44
+ const values = Array.isArray(source) ? source : String(source).split(",");
62
45
  const normalized = [...new Set(values.map((item) => String(item || "").trim().toLowerCase()).filter(Boolean))];
63
46
  const supported = ["markdown", "json"];
64
47
  const invalid = normalized.filter((item) => !supported.includes(item));
65
48
 
66
49
  if (invalid.length > 0) {
67
- throw new Error(
68
- `"outputFormats" contains unsupported value(s): ${invalid.join(", ")}. Use any of: ${supported.join(", ")}${
69
- loadedConfigPath ? ` in ${loadedConfigPath}` : ""
70
- }`
71
- );
72
- }
73
-
74
- if (normalized.length === 0) {
75
- throw new Error(`"outputFormats" must include at least one format${loadedConfigPath ? ` in ${loadedConfigPath}` : ""}`);
50
+ throw new Error(`Unsupported output format(s): ${invalid.join(", ")}. Use: ${supported.join(", ")}`);
76
51
  }
77
-
78
- return normalized;
52
+ return normalized.length === 0 ? ["markdown"] : normalized;
79
53
  }
80
54
 
81
55
  function detectLanguage() {
@@ -88,46 +62,28 @@ function detectLanguage() {
88
62
  }
89
63
  })();
90
64
 
91
- // On Windows, the LANG environment variable is often set to 'en_US.UTF-8' by default
92
- // in many shells (like Git Bash/MSYS2), regardless of the actual OS display language.
93
- // We prefer the system locale (Intl) on Windows if it's Chinese.
94
- if (os.platform() === "win32" && intlLocale.startsWith("zh")) {
95
- return "zh";
96
- }
97
-
98
- if (envLang) {
99
- if (envLang.startsWith("zh")) return "zh";
100
- if (envLang.startsWith("en")) return "en";
101
- return envLang.split(/[._-]/)[0];
102
- }
103
-
104
- if (intlLocale) {
105
- if (intlLocale.startsWith("zh")) return "zh";
106
- if (intlLocale.startsWith("en")) return "en";
107
- return intlLocale.split("-")[0];
108
- }
109
-
65
+ if (os.platform() === "win32" && intlLocale.startsWith("zh")) return "zh";
66
+ if (envLang.startsWith("zh")) return "zh";
67
+ if (envLang.startsWith("en")) return "en";
68
+ if (envLang) return envLang.split(/[._-]/)[0];
69
+ if (intlLocale.startsWith("zh")) return "zh";
70
+ if (intlLocale.startsWith("en")) return "en";
71
+ if (intlLocale) return intlLocale.split("-")[0];
110
72
  return "en";
111
73
  }
112
74
 
113
- async function resolveAutoReviewers(debug, loadedConfigPath) {
75
+ async function resolveAutoReviewers(debug) {
114
76
  const availableReviewers = [];
115
-
116
77
  for (const reviewerName of SUPPORTED_REVIEWERS) {
117
78
  const commandPath = await findCommandOnPath(reviewerName, { debug });
118
- if (commandPath) {
119
- availableReviewers.push({ reviewerName, commandPath });
120
- }
79
+ if (commandPath) availableReviewers.push({ reviewerName, commandPath });
121
80
  }
122
81
 
123
82
  if (availableReviewers.length === 0) {
124
- throw new Error(
125
- `No reviewer CLI was found in PATH for "reviewer": "auto". Install one of: ${SUPPORTED_REVIEWERS.join(", ")}${
126
- loadedConfigPath ? ` (${loadedConfigPath})` : ""
127
- }`
128
- );
83
+ throw new Error(`No reviewer CLI found in PATH. Install one of: ${SUPPORTED_REVIEWERS.join(", ")}`);
129
84
  }
130
85
 
86
+ // Shuffle for variety
131
87
  for (let i = availableReviewers.length - 1; i > 0; i--) {
132
88
  const j = Math.floor(Math.random() * (i + 1));
133
89
  [availableReviewers[i], availableReviewers[j]] = [availableReviewers[j], availableReviewers[i]];
@@ -138,9 +94,6 @@ async function resolveAutoReviewers(debug, loadedConfigPath) {
138
94
 
139
95
  export function parseCliArgs(argv) {
140
96
  const args = {
141
- command: "run",
142
- configPath: "config.json",
143
- configExplicitlySet: false,
144
97
  target: "",
145
98
  debug: false,
146
99
  help: false,
@@ -149,18 +102,13 @@ export function parseCliArgs(argv) {
149
102
  prompt: "",
150
103
  rev: "",
151
104
  last: "",
152
- commandExplicitlySet: false
105
+ outputDir: "",
106
+ outputFormats: ""
153
107
  };
154
108
 
155
109
  for (let index = 0; index < argv.length; index += 1) {
156
110
  const value = argv[index];
157
111
 
158
- if (value === "init" && !args.commandExplicitlySet && index === 0) {
159
- args.command = "init";
160
- args.commandExplicitlySet = true;
161
- continue;
162
- }
163
-
164
112
  if (value === "--help" || value === "-h") {
165
113
  args.help = true;
166
114
  continue;
@@ -171,68 +119,59 @@ export function parseCliArgs(argv) {
171
119
  continue;
172
120
  }
173
121
 
174
- if (value === "--config" || value === "-c") {
175
- const configPath = argv[index + 1];
176
- if (!configPath || configPath.startsWith("-")) {
177
- throw new Error(`Missing value for ${value}`);
178
- }
179
- args.configPath = configPath;
180
- args.configExplicitlySet = true;
181
- index += 1;
182
- continue;
183
- }
122
+ const nextValue = argv[index + 1];
123
+ const hasNextValue = nextValue && !nextValue.startsWith("-");
184
124
 
185
125
  if (value === "--reviewer" || value === "-r") {
186
- const reviewer = argv[index + 1];
187
- if (!reviewer || reviewer.startsWith("-")) {
188
- throw new Error(`Missing value for ${value}`);
189
- }
190
- args.reviewer = reviewer;
126
+ if (!hasNextValue) throw new Error(`Missing value for ${value}`);
127
+ args.reviewer = nextValue;
191
128
  index += 1;
192
129
  continue;
193
130
  }
194
131
 
195
132
  if (value === "--prompt" || value === "-p") {
196
- const prompt = argv[index + 1];
197
- if (!prompt || prompt.startsWith("-")) {
198
- throw new Error(`Missing value for ${value}`);
199
- }
200
- args.prompt = prompt;
133
+ if (!hasNextValue) throw new Error(`Missing value for ${value}`);
134
+ args.prompt = nextValue;
201
135
  index += 1;
202
136
  continue;
203
137
  }
204
138
 
205
139
  if (value === "--lang" || value === "-l") {
206
- const lang = argv[index + 1];
207
- if (!lang || lang.startsWith("-")) {
208
- throw new Error(`Missing value for ${value}`);
209
- }
210
- args.lang = lang;
140
+ if (!hasNextValue) throw new Error(`Missing value for ${value}`);
141
+ args.lang = nextValue;
211
142
  index += 1;
212
143
  continue;
213
144
  }
214
145
 
215
146
  if (value === "--rev" || value === "-v") {
216
- const rev = argv[index + 1];
217
- if (!rev || rev.startsWith("-")) {
218
- throw new Error(`Missing value for ${value}`);
219
- }
220
- args.rev = rev;
147
+ if (!hasNextValue) throw new Error(`Missing value for ${value}`);
148
+ args.rev = nextValue;
221
149
  index += 1;
222
150
  continue;
223
151
  }
224
152
 
225
153
  if (value === "--last" || value === "-n") {
226
- const last = argv[index + 1];
227
- if (!last || last.startsWith("-")) {
228
- throw new Error(`Missing value for ${value}`);
229
- }
230
- args.last = last;
154
+ if (!hasNextValue) throw new Error(`Missing value for ${value}`);
155
+ args.last = nextValue;
156
+ index += 1;
157
+ continue;
158
+ }
159
+
160
+ if (value === "--output" || value === "-o") {
161
+ if (!hasNextValue) throw new Error(`Missing value for ${value}`);
162
+ args.outputDir = nextValue;
231
163
  index += 1;
232
164
  continue;
233
165
  }
234
166
 
235
- if (!value.startsWith("-") && args.command === "run" && !args.target) {
167
+ if (value === "--format" || value === "-f") {
168
+ if (!hasNextValue) throw new Error(`Missing value for ${value}`);
169
+ args.outputFormats = nextValue;
170
+ index += 1;
171
+ continue;
172
+ }
173
+
174
+ if (!value.startsWith("-") && !args.target) {
236
175
  args.target = value;
237
176
  continue;
238
177
  }
@@ -240,100 +179,78 @@ export function parseCliArgs(argv) {
240
179
  throw new Error(`Unexpected argument: ${value}`);
241
180
  }
242
181
 
243
- delete args.commandExplicitlySet;
244
182
  return args;
245
183
  }
246
184
 
247
- export async function loadConfig(configPath, cliArgs = {}) {
248
- const absoluteConfigPath = path.resolve(configPath);
249
- let loadedConfig = {};
250
- let loadedConfigPath = null;
251
- let baseDir = process.cwd();
252
-
253
- try {
254
- const raw = await fs.readFile(absoluteConfigPath, "utf8");
255
- loadedConfig = JSON.parse(raw);
256
- loadedConfigPath = absoluteConfigPath;
257
- baseDir = path.dirname(absoluteConfigPath);
258
- } catch (error) {
259
- if (!(error?.code === "ENOENT" && !cliArgs.configExplicitlySet)) {
260
- throw error;
185
+ export async function resolveConfig(cliArgs = {}) {
186
+ const config = { ...defaultConfig };
187
+
188
+ // 1. Merge Environment Variables
189
+ for (const [envVar, configKey] of Object.entries(ENV_MAP)) {
190
+ if (process.env[envVar] !== undefined) {
191
+ config[configKey] = process.env[envVar];
261
192
  }
262
193
  }
263
194
 
264
- const config = {
265
- ...defaultConfig,
266
- ...loadedConfig
195
+ // 2. Merge CLI Arguments
196
+ const cliMapping = {
197
+ target: "target",
198
+ reviewer: "reviewer",
199
+ prompt: "prompt",
200
+ lang: "lang",
201
+ rev: "rev",
202
+ last: "last",
203
+ outputDir: "outputDir",
204
+ outputFormats: "outputFormats"
267
205
  };
268
206
 
269
- if (cliArgs.target) {
270
- config.target = cliArgs.target;
271
- }
272
-
273
- if (cliArgs.reviewer) {
274
- config.reviewer = cliArgs.reviewer;
275
- }
276
-
277
- if (cliArgs.prompt) {
278
- config.prompt = cliArgs.prompt;
279
- }
280
-
281
- if (cliArgs.lang) {
282
- config.lang = cliArgs.lang;
283
- }
284
-
285
- if (cliArgs.rev) {
286
- config.rev = cliArgs.rev;
287
- }
288
-
289
- if (cliArgs.last) {
290
- config.last = cliArgs.last;
207
+ for (const [cliKey, configKey] of Object.entries(cliMapping)) {
208
+ if (cliArgs[cliKey]) {
209
+ config[configKey] = cliArgs[cliKey];
210
+ }
291
211
  }
292
212
 
293
213
  if (!config.target) {
294
- throw new Error('Missing target. Pass `npx kodevu <repo-path>` or set "target" in config.json.');
214
+ config.target = process.cwd();
295
215
  }
296
216
 
297
217
  config.debug = Boolean(cliArgs.debug);
298
218
  config.reviewer = String(config.reviewer || "auto").toLowerCase();
299
219
  config.lang = String(config.lang || "auto").toLowerCase();
300
-
301
220
  config.resolvedLang = config.lang === "auto" ? detectLanguage() : config.lang;
302
221
 
222
+ // Handle @file prompt
223
+ if (config.prompt.startsWith("@")) {
224
+ const promptPath = resolvePath(config.prompt.slice(1));
225
+ try {
226
+ config.prompt = await fs.readFile(promptPath, "utf8");
227
+ } catch (err) {
228
+ throw new Error(`Failed to read prompt file: ${promptPath} (${err.message})`);
229
+ }
230
+ }
231
+
303
232
  if (config.reviewer === "auto") {
304
- const availableReviewers = await resolveAutoReviewers(config.debug, loadedConfigPath);
233
+ const availableReviewers = await resolveAutoReviewers(config.debug);
305
234
  const selectedReviewer = availableReviewers[0];
306
235
  config.reviewer = selectedReviewer.reviewerName;
307
236
  config.reviewerCommandPath = selectedReviewer.commandPath;
308
237
  config.fallbackReviewers = availableReviewers.map(r => r.reviewerName).slice(1);
309
238
  config.reviewerWasAutoSelected = true;
310
239
  } else if (!SUPPORTED_REVIEWERS.includes(config.reviewer)) {
311
- throw new Error(
312
- `"reviewer" must be one of "codex", "gemini", "copilot", or "auto"${loadedConfigPath ? ` in ${loadedConfigPath}` : ""}`
313
- );
240
+ throw new Error(`"reviewer" must be one of: ${SUPPORTED_REVIEWERS.join(", ")}, or "auto"`);
314
241
  }
315
242
 
316
- config.configPath = loadedConfigPath;
317
- config.baseDir = baseDir;
318
- config.outputDir = resolveConfigPath(config.baseDir, config.outputDir);
319
- config.logsDir = resolveConfigPath(config.baseDir, config.logsDir);
243
+ config.outputDir = resolvePath(config.outputDir);
244
+ config.logsDir = path.join(config.outputDir, "logs");
320
245
  config.maxRevisionsPerRun = Number(config.maxRevisionsPerRun);
321
246
  config.commandTimeoutMs = Number(config.commandTimeoutMs);
322
247
  config.last = Number(config.last);
323
- config.outputFormats = normalizeOutputFormats(config.outputFormats, loadedConfigPath);
248
+ config.outputFormats = normalizeOutputFormats(config.outputFormats);
324
249
 
325
250
  if (!config.rev && (isNaN(config.last) || config.last <= 0)) {
326
251
  config.last = 1;
327
252
  }
328
253
 
329
- if (!Number.isInteger(config.maxRevisionsPerRun) || config.maxRevisionsPerRun <= 0) {
330
- throw new Error(`"maxRevisionsPerRun" must be a positive integer${loadedConfigPath ? ` in ${loadedConfigPath}` : ""}`);
331
- }
332
-
333
- if (!Number.isInteger(config.commandTimeoutMs) || config.commandTimeoutMs <= 0) {
334
- throw new Error(`"commandTimeoutMs" must be a positive integer${loadedConfigPath ? ` in ${loadedConfigPath}` : ""}`);
335
- }
336
-
337
254
  return config;
338
255
  }
339
256
 
@@ -341,44 +258,25 @@ export function printHelp() {
341
258
  console.log(`Kodevu
342
259
 
343
260
  Usage:
344
- kodevu init
345
- npx kodevu init
346
- kodevu <target> [--debug]
347
- npx kodevu <target> [--debug]
348
- kodevu [--config config.json]
349
- npx kodevu [--config config.json]
261
+ npx kodevu [target] [options]
350
262
 
351
263
  Options:
352
- --config, -c Optional config json path. If omitted, ./config.json is loaded only when present
353
- --reviewer, -r Override reviewer (codex | gemini | copilot | auto)
354
- --prompt, -p Override prompt
355
- --lang, -l Override output language (e.g. zh, en, auto)
356
- --rev, -v Review a specific revision or commit hash
357
- --last, -n Review the latest N revisions (default: 1)
358
- --debug, -d Print extra debug information to the console
359
- --help, -h Show help
360
-
361
- Config highlights:
362
- reviewer codex | gemini | copilot | auto
363
- target Repository target path (Git) or SVN working copy / URL; CLI positional target overrides config
364
- outputFormats ["markdown"] by default; set to include "json" when needed
264
+ --target, <path> Target repository path (default: current directory)
265
+ --reviewer, -r Reviewer (codex | gemini | copilot | auto, default: auto)
266
+ --prompt, -p Additional instructions or @file.txt to read from file
267
+ --lang, -l Output language (e.g. zh, en, auto)
268
+ --rev, -v Review a specific revision or commit hash
269
+ --last, -n Review the latest N revisions (default: 1)
270
+ --output, -o Output directory (default: ~/.kodevu)
271
+ --format, -f Output formats (markdown, json, comma-separated)
272
+ --debug, -d Print extra debug information
273
+ --help, -h Show help
274
+
275
+ Environment Variables:
276
+ KODEVU_REVIEWER Default reviewer
277
+ KODEVU_LANG Default language
278
+ KODEVU_OUTPUT_DIR Default output directory
279
+ KODEVU_PROMPT Default prompt text
280
+ KODEVU_TIMEOUT Reviewer timeout in ms
365
281
  `);
366
282
  }
367
-
368
- export async function initConfig(targetPath = "config.json") {
369
- const absoluteTargetPath = path.resolve(targetPath);
370
-
371
- await fs.mkdir(path.dirname(absoluteTargetPath), { recursive: true });
372
-
373
- const content = JSON.stringify(configTemplate, null, 2) + "\n";
374
- try {
375
- await fs.writeFile(absoluteTargetPath, content, { flag: "wx" });
376
- } catch (error) {
377
- if (error?.code === "EEXIST") {
378
- throw new Error(`Config file already exists: ${absoluteTargetPath}`);
379
- }
380
- throw error;
381
- }
382
-
383
- return absoluteTargetPath;
384
- }
package/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { initConfig, loadConfig, parseCliArgs, printHelp } from "./config.js";
3
+ import { resolveConfig, parseCliArgs, printHelp } from "./config.js";
4
4
  import { runReviewCycle } from "./review-runner.js";
5
5
  import { logger } from "./logger.js";
6
6
 
@@ -19,19 +19,8 @@ if (cliArgs.help) {
19
19
  process.exit(0);
20
20
  }
21
21
 
22
- if (cliArgs.command === "init") {
23
- try {
24
- const createdPath = await initConfig(cliArgs.configPath);
25
- console.log(`Created config: ${createdPath}`);
26
- process.exit(0);
27
- } catch (error) {
28
- console.error(error?.stack || String(error));
29
- process.exit(1);
30
- }
31
- }
32
-
33
22
  try {
34
- const config = await loadConfig(cliArgs.configPath, cliArgs);
23
+ const config = await resolveConfig(cliArgs);
35
24
  logger.init(config);
36
25
 
37
26
  if (config.reviewerWasAutoSelected) {
@@ -42,8 +31,7 @@ try {
42
31
 
43
32
  if (config.debug) {
44
33
  logger.debug(
45
- `Loaded config: ${JSON.stringify({
46
- configPath: config.configPath,
34
+ `Resolved config: ${JSON.stringify({
47
35
  reviewer: config.reviewer,
48
36
  reviewerCommandPath: config.reviewerCommandPath,
49
37
  reviewerWasAutoSelected: config.reviewerWasAutoSelected,
@@ -59,7 +47,6 @@ try {
59
47
  await runReviewCycle(config);
60
48
  logger.info("Session completed successfully.");
61
49
  } catch (error) {
62
- // If config was loaded, logger might be initialized, otherwise it will fall back to stderr
63
50
  logger.error("Session failed with error", error);
64
51
  process.exitCode = 1;
65
52
  }