kodevu 0.1.61 → 0.1.63

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodevu",
3
- "version": "0.1.61",
3
+ "version": "0.1.63",
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
@@ -31,6 +31,13 @@ const defaultConfig = {
31
31
  openaiProject: ""
32
32
  };
33
33
 
34
+ function mkCliError(message) {
35
+ const e = new Error(message);
36
+ // exit code 2 = usage / configuration errors
37
+ e.exitCode = 2;
38
+ return e;
39
+ }
40
+
34
41
  const ENV_MAP = {
35
42
  KODEVU_REVIEWER: "reviewer",
36
43
  KODEVU_LANG: "lang",
@@ -65,16 +72,16 @@ async function loadConfigFile(configPath = defaultConfigFilePath) {
65
72
  content = await fs.readFile(resolvedPath, "utf8");
66
73
  } catch (err) {
67
74
  if (err.code === "ENOENT") return {};
68
- throw new Error(`Failed to read config file ${resolvedPath}: ${err.message}`);
75
+ throw mkCliError(`Failed to read config file ${resolvedPath}: ${err.message}`);
69
76
  }
70
77
  let parsed;
71
78
  try {
72
79
  parsed = JSON.parse(content);
73
80
  } catch (err) {
74
- throw new Error(`Invalid JSON in config file ${resolvedPath}: ${err.message}`);
81
+ throw mkCliError(`Invalid JSON in config file ${resolvedPath}: ${err.message}`);
75
82
  }
76
83
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
77
- throw new Error(`Config file ${resolvedPath} must contain a JSON object`);
84
+ throw mkCliError(`Config file ${resolvedPath} must contain a JSON object`);
78
85
  }
79
86
  const result = {};
80
87
  for (const [key, value] of Object.entries(parsed)) {
@@ -93,7 +100,7 @@ function normalizeOutputFormats(outputFormats) {
93
100
  const invalid = normalized.filter((item) => !supported.includes(item));
94
101
 
95
102
  if (invalid.length > 0) {
96
- throw new Error(`Unsupported output format(s): ${invalid.join(", ")}. Use: ${supported.join(", ")}`);
103
+ throw mkCliError(`Unsupported output format(s): ${invalid.join(", ")}. Use: ${supported.join(", ")}`);
97
104
  }
98
105
  return normalized.length === 0 ? ["markdown"] : normalized;
99
106
  }
@@ -132,7 +139,7 @@ async function resolveAutoReviewers(debug) {
132
139
  }
133
140
 
134
141
  if (availableReviewers.length === 0) {
135
- throw new Error(`No reviewer CLI found in PATH. Install one of: ${AUTO_SUPPORTED_REVIEWERS.join(", ")}`);
142
+ throw mkCliError(`No reviewer CLI found in PATH. Install one of: ${AUTO_SUPPORTED_REVIEWERS.join(", ")}`);
136
143
  }
137
144
 
138
145
  // Shuffle for variety
@@ -187,28 +194,28 @@ export function parseCliArgs(argv) {
187
194
  const hasNextValue = nextValue && !nextValue.startsWith("-");
188
195
 
189
196
  if (value === "--reviewer" || value === "-r") {
190
- if (!hasNextValue) throw new Error(`Missing value for ${value}`);
197
+ if (!hasNextValue) throw mkCliError(`Missing value for ${value}`);
191
198
  args.reviewer = nextValue;
192
199
  index += 1;
193
200
  continue;
194
201
  }
195
202
 
196
203
  if (value === "--prompt" || value === "-p") {
197
- if (!hasNextValue) throw new Error(`Missing value for ${value}`);
204
+ if (!hasNextValue) throw mkCliError(`Missing value for ${value}`);
198
205
  args.prompt = nextValue;
199
206
  index += 1;
200
207
  continue;
201
208
  }
202
209
 
203
210
  if (value === "--lang" || value === "-l") {
204
- if (!hasNextValue) throw new Error(`Missing value for ${value}`);
211
+ if (!hasNextValue) throw mkCliError(`Missing value for ${value}`);
205
212
  args.lang = nextValue;
206
213
  index += 1;
207
214
  continue;
208
215
  }
209
216
 
210
217
  if (value === "--rev" || value === "-v") {
211
- if (!hasNextValue) throw new Error(`Missing value for ${value}`);
218
+ if (!hasNextValue) throw mkCliError(`Missing value for ${value}`);
212
219
  args.rev = nextValue;
213
220
  index += 1;
214
221
  continue;
@@ -216,7 +223,7 @@ export function parseCliArgs(argv) {
216
223
 
217
224
  if (value === "--last" || value === "-n") {
218
225
  const hasLastValue = nextValue !== undefined && /^-?\d+$/.test(nextValue);
219
- if (!hasLastValue) throw new Error(`Missing value for ${value}`);
226
+ if (!hasLastValue) throw mkCliError(`Missing value for ${value}`);
220
227
  args.last = nextValue;
221
228
  index += 1;
222
229
  continue;
@@ -228,49 +235,49 @@ export function parseCliArgs(argv) {
228
235
  }
229
236
 
230
237
  if (value === "--output" || value === "-o") {
231
- if (!hasNextValue) throw new Error(`Missing value for ${value}`);
238
+ if (!hasNextValue) throw mkCliError(`Missing value for ${value}`);
232
239
  args.outputDir = nextValue;
233
240
  index += 1;
234
241
  continue;
235
242
  }
236
243
 
237
244
  if (value === "--format" || value === "-f") {
238
- if (!hasNextValue) throw new Error(`Missing value for ${value}`);
245
+ if (!hasNextValue) throw mkCliError(`Missing value for ${value}`);
239
246
  args.outputFormats = nextValue;
240
247
  index += 1;
241
248
  continue;
242
249
  }
243
250
 
244
251
  if (value === "--openai-api-key") {
245
- if (!hasNextValue) throw new Error(`Missing value for ${value}`);
252
+ if (!hasNextValue) throw mkCliError(`Missing value for ${value}`);
246
253
  args.openaiApiKey = nextValue;
247
254
  index += 1;
248
255
  continue;
249
256
  }
250
257
 
251
258
  if (value === "--openai-base-url") {
252
- if (!hasNextValue) throw new Error(`Missing value for ${value}`);
259
+ if (!hasNextValue) throw mkCliError(`Missing value for ${value}`);
253
260
  args.openaiBaseUrl = nextValue;
254
261
  index += 1;
255
262
  continue;
256
263
  }
257
264
 
258
265
  if (value === "--openai-model") {
259
- if (!hasNextValue) throw new Error(`Missing value for ${value}`);
266
+ if (!hasNextValue) throw mkCliError(`Missing value for ${value}`);
260
267
  args.openaiModel = nextValue;
261
268
  index += 1;
262
269
  continue;
263
270
  }
264
271
 
265
272
  if (value === "--openai-org") {
266
- if (!hasNextValue) throw new Error(`Missing value for ${value}`);
273
+ if (!hasNextValue) throw mkCliError(`Missing value for ${value}`);
267
274
  args.openaiOrganization = nextValue;
268
275
  index += 1;
269
276
  continue;
270
277
  }
271
278
 
272
279
  if (value === "--openai-project") {
273
- if (!hasNextValue) throw new Error(`Missing value for ${value}`);
280
+ if (!hasNextValue) throw mkCliError(`Missing value for ${value}`);
274
281
  args.openaiProject = nextValue;
275
282
  index += 1;
276
283
  continue;
@@ -281,7 +288,7 @@ export function parseCliArgs(argv) {
281
288
  continue;
282
289
  }
283
290
 
284
- throw new Error(`Unexpected argument: ${value}`);
291
+ throw mkCliError(`Unexpected argument: ${value}`);
285
292
  }
286
293
 
287
294
  return args;
@@ -328,11 +335,11 @@ export async function resolveConfig(cliArgs = {}) {
328
335
  }
329
336
 
330
337
  if (cliArgs.rev && cliArgs.last) {
331
- throw new Error("Parameters --rev and --last are mutually exclusive. Please specify only one.");
338
+ throw mkCliError("Parameters --rev and --last are mutually exclusive. Please specify only one.");
332
339
  }
333
340
 
334
341
  if (cliArgs.uncommitted && (cliArgs.rev || cliArgs.last)) {
335
- throw new Error("Parameter --uncommitted is mutually exclusive with --rev and --last.");
342
+ throw mkCliError("Parameter --uncommitted is mutually exclusive with --rev and --last.");
336
343
  }
337
344
 
338
345
  if (!config.target) {
@@ -351,7 +358,7 @@ export async function resolveConfig(cliArgs = {}) {
351
358
  try {
352
359
  config.prompt = await fs.readFile(promptPath, "utf8");
353
360
  } catch (err) {
354
- throw new Error(`Failed to read prompt file: ${promptPath} (${err.message})`);
361
+ throw mkCliError(`Failed to read prompt file: ${promptPath} (${err.message})`);
355
362
  }
356
363
  }
357
364
 
@@ -363,7 +370,7 @@ export async function resolveConfig(cliArgs = {}) {
363
370
  config.fallbackReviewers = availableReviewers.map(r => r.reviewerName).slice(1);
364
371
  config.reviewerWasAutoSelected = true;
365
372
  } else if (!SUPPORTED_REVIEWERS.includes(config.reviewer)) {
366
- throw new Error(`"reviewer" must be one of: ${SUPPORTED_REVIEWERS.join(", ")}, or "auto"`);
373
+ throw mkCliError(`"reviewer" must be one of: ${SUPPORTED_REVIEWERS.join(", ")}, or "auto"`);
367
374
  }
368
375
 
369
376
  config.outputDir = resolvePath(config.outputDir);
@@ -384,7 +391,7 @@ export async function resolveConfig(cliArgs = {}) {
384
391
  }
385
392
 
386
393
  if (config.reviewer === "openai" && !config.openaiApiKey) {
387
- throw new Error('Reviewer "openai" requires an API key. Set KODEVU_OPENAI_API_KEY or pass --openai-api-key.');
394
+ throw mkCliError('Reviewer "openai" requires an API key. Set KODEVU_OPENAI_API_KEY or pass --openai-api-key.');
388
395
  }
389
396
 
390
397
  return config;
package/src/index.js CHANGED
@@ -11,7 +11,9 @@ try {
11
11
  } catch (error) {
12
12
  console.error(error?.message || String(error));
13
13
  printHelp();
14
- process.exit(1);
14
+ // CLI/usage errors -> exit code 2
15
+ const code = Number(error?.exitCode) || 2;
16
+ process.exit(code);
15
17
  }
16
18
 
17
19
  if (cliArgs.help) {
@@ -81,6 +83,7 @@ try {
81
83
  scope: "session",
82
84
  console: true
83
85
  });
84
- process.exitCode = 1;
86
+ // Map any attached exitCode (from thrown errors) to process exitCode, fallback to 1
87
+ process.exitCode = Number(error?.exitCode) || 1;
85
88
  }
86
89
  process.exit(process.exitCode || 0);
@@ -137,7 +137,14 @@ async function reviewChange(config, backend, targetInfo, changeId, progress) {
137
137
  reasonParts.push(`\n${detail}`);
138
138
  }
139
139
 
140
- throw new Error(reasonParts.join(" "));
140
+ const err = new Error(reasonParts.join(" "));
141
+ // Prefer reviewer-provided exit code when available (HTTP status or child exit code), otherwise 3
142
+ try {
143
+ err.exitCode = reviewerResult?.code || 3;
144
+ } catch {
145
+ err.exitCode = 3;
146
+ }
147
+ throw err;
141
148
  }
142
149
 
143
150
  progress?.update(0.82, "writing report");
package/src/shell.js CHANGED
@@ -65,6 +65,13 @@ export async function runCommand(command, args = [], options = {}) {
65
65
  args,
66
66
  cwd: cwd || ""
67
67
  });
68
+ // Mark spawn/runtime errors as runtime failures (exit code 3)
69
+ try {
70
+ if (!err || typeof err !== "object") err = new Error(String(err));
71
+ if (err.exitCode == null) err.exitCode = 3;
72
+ } catch (e) {
73
+ // ignore
74
+ }
68
75
  reject(err);
69
76
  });
70
77
 
@@ -108,6 +115,8 @@ export async function runCommand(command, args = [], options = {}) {
108
115
  `Command failed: ${command} ${args.join(" ")}\n${result.stderr || result.stdout}`.trim()
109
116
  );
110
117
  error.result = result;
118
+ // Map command failures to exit code 3 (runtime / external command failure)
119
+ error.exitCode = 3;
111
120
  reject(error);
112
121
  return;
113
122
  }