kodevu 0.1.8 → 0.1.10

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
@@ -66,7 +66,7 @@ npx kodevu /path/to/your/repo --config ./config.current.json
66
66
 
67
67
  - `target`: required repository target; can be provided by config or as the CLI positional argument
68
68
  - `reviewer`: `codex`, `gemini`, or `auto`; default `auto`
69
- - `reviewPrompt`: saved into the report as review context
69
+ - `prompt`: saved into the report as review context
70
70
  - `outputDir`: report output directory; default `~/.kodevu`
71
71
  - `stateFilePath`: review state file path; default `~/.kodevu/state.json`
72
72
  - `commandTimeoutMs`: timeout for a single review command execution in milliseconds
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "target": "C:/path/to/your/repository-or-subdirectory",
3
3
  "reviewer": "auto",
4
- "reviewPrompt": "请严格审查当前变更,优先指出 bug、回归风险、兼容性问题、安全问题、边界条件缺陷和缺失测试。请使用简体中文输出 Markdown;如果没有明确缺陷,请写“未发现明确缺陷”,并补充剩余风险。",
4
+ "prompt": "请严格审查当前变更,优先指出 bug、回归风险、兼容性问题、安全问题、边界条件缺陷和缺失测试。请使用简体中文输出 Markdown;如果没有明确缺陷,请写“未发现明确缺陷”,并补充剩余风险。",
5
5
  "outputDir": "~/.kodevu",
6
6
  "stateFilePath": "~/.kodevu/state.json",
7
7
  "commandTimeoutMs": 600000,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodevu",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "description": "Poll SVN revisions or Git commits, send each change diff to a reviewer CLI, and write Markdown review reports.",
6
6
  "bin": {
package/src/config.js CHANGED
@@ -14,7 +14,7 @@ const defaultConfig = {
14
14
  outputDir: defaultStorageDir,
15
15
  stateFilePath: path.join(defaultStorageDir, "state.json"),
16
16
  commandTimeoutMs: 600000,
17
- reviewPrompt:
17
+ prompt:
18
18
  "请严格审查当前变更,优先指出 bug、回归风险、兼容性问题、安全问题、边界条件缺陷和缺失测试。请使用简体中文输出 Markdown;如果没有明确缺陷,请写“未发现明确缺陷”,并补充剩余风险。",
19
19
  maxRevisionsPerRun: 20
20
20
  };
@@ -39,7 +39,7 @@ function resolveConfigPath(baseDir, value) {
39
39
  return path.isAbsolute(value) ? value : path.resolve(baseDir, value);
40
40
  }
41
41
 
42
- async function resolveAutoReviewer(debug, loadedConfigPath) {
42
+ async function resolveAutoReviewers(debug, loadedConfigPath) {
43
43
  const availableReviewers = [];
44
44
 
45
45
  for (const reviewerName of SUPPORTED_REVIEWERS) {
@@ -57,7 +57,12 @@ async function resolveAutoReviewer(debug, loadedConfigPath) {
57
57
  );
58
58
  }
59
59
 
60
- return availableReviewers[Math.floor(Math.random() * availableReviewers.length)];
60
+ for (let i = availableReviewers.length - 1; i > 0; i--) {
61
+ const j = Math.floor(Math.random() * (i + 1));
62
+ [availableReviewers[i], availableReviewers[j]] = [availableReviewers[j], availableReviewers[i]];
63
+ }
64
+
65
+ return availableReviewers;
61
66
  }
62
67
 
63
68
  export function parseCliArgs(argv) {
@@ -68,6 +73,8 @@ export function parseCliArgs(argv) {
68
73
  target: "",
69
74
  debug: false,
70
75
  help: false,
76
+ reviewer: "",
77
+ prompt: "",
71
78
  commandExplicitlySet: false
72
79
  };
73
80
 
@@ -101,6 +108,26 @@ export function parseCliArgs(argv) {
101
108
  continue;
102
109
  }
103
110
 
111
+ if (value === "--reviewer" || value === "-r") {
112
+ const reviewer = argv[index + 1];
113
+ if (!reviewer || reviewer.startsWith("-")) {
114
+ throw new Error(`Missing value for ${value}`);
115
+ }
116
+ args.reviewer = reviewer;
117
+ index += 1;
118
+ continue;
119
+ }
120
+
121
+ if (value === "--prompt" || value === "-p") {
122
+ const prompt = argv[index + 1];
123
+ if (!prompt || prompt.startsWith("-")) {
124
+ throw new Error(`Missing value for ${value}`);
125
+ }
126
+ args.prompt = prompt;
127
+ index += 1;
128
+ continue;
129
+ }
130
+
104
131
  if (!value.startsWith("-") && args.command === "run" && !args.target) {
105
132
  args.target = value;
106
133
  continue;
@@ -139,6 +166,14 @@ export async function loadConfig(configPath, cliArgs = {}) {
139
166
  config.target = cliArgs.target;
140
167
  }
141
168
 
169
+ if (cliArgs.reviewer) {
170
+ config.reviewer = cliArgs.reviewer;
171
+ }
172
+
173
+ if (cliArgs.prompt) {
174
+ config.prompt = cliArgs.prompt;
175
+ }
176
+
142
177
  if (!config.target) {
143
178
  throw new Error('Missing target. Pass `npx kodevu <repo-path>` or set "target" in config.json.');
144
179
  }
@@ -147,9 +182,11 @@ export async function loadConfig(configPath, cliArgs = {}) {
147
182
  config.reviewer = String(config.reviewer || "auto").toLowerCase();
148
183
 
149
184
  if (config.reviewer === "auto") {
150
- const selectedReviewer = await resolveAutoReviewer(config.debug, loadedConfigPath);
185
+ const availableReviewers = await resolveAutoReviewers(config.debug, loadedConfigPath);
186
+ const selectedReviewer = availableReviewers[0];
151
187
  config.reviewer = selectedReviewer.reviewerName;
152
188
  config.reviewerCommandPath = selectedReviewer.commandPath;
189
+ config.fallbackReviewers = availableReviewers.map(r => r.reviewerName).slice(1);
153
190
  config.reviewerWasAutoSelected = true;
154
191
  } else if (!SUPPORTED_REVIEWERS.includes(config.reviewer)) {
155
192
  throw new Error(
@@ -188,6 +225,8 @@ Usage:
188
225
 
189
226
  Options:
190
227
  --config, -c Optional config json path. If omitted, ./config.json is loaded only when present
228
+ --reviewer, -r Override reviewer (codex | gemini | auto)
229
+ --prompt, -p Override prompt
191
230
  --debug, -d Print extra debug information to the console
192
231
  --help, -h Show help
193
232
 
@@ -284,7 +284,7 @@ function buildPrompt(config, backend, targetInfo, details, reviewDiffPayload) {
284
284
  const canReadRelatedFiles = backend.kind === "git" || Boolean(targetInfo.workingCopyPath);
285
285
 
286
286
  return [
287
- config.reviewPrompt,
287
+ config.prompt,
288
288
  canReadRelatedFiles
289
289
  ? `You are running inside a read-only workspace rooted at: ${workspaceRoot}`
290
290
  : "No local repository workspace is available for this review run.",
@@ -402,14 +402,38 @@ async function reviewChange(config, backend, targetInfo, changeId) {
402
402
  }
403
403
 
404
404
  const diffText = await backend.getChangeDiff(config, targetInfo, changeId);
405
- const { reviewer, diffPayloads, result: reviewerResult } = await runReviewerPrompt(
406
- config,
407
- backend,
408
- targetInfo,
409
- details,
410
- diffText
411
- );
412
- const report = buildReport(config, backend, targetInfo, details, diffPayloads, reviewer, reviewerResult);
405
+ const reviewersToTry = [config.reviewer, ...(config.fallbackReviewers || [])];
406
+
407
+ let reviewer;
408
+ let diffPayloads;
409
+ let reviewerResult;
410
+ let currentReviewerConfig;
411
+
412
+ for (const reviewerName of reviewersToTry) {
413
+ currentReviewerConfig = { ...config, reviewer: reviewerName };
414
+ debugLog(config, `Trying reviewer: ${reviewerName}`);
415
+
416
+ const res = await runReviewerPrompt(
417
+ currentReviewerConfig,
418
+ backend,
419
+ targetInfo,
420
+ details,
421
+ diffText
422
+ );
423
+ reviewer = res.reviewer;
424
+ diffPayloads = res.diffPayloads;
425
+ reviewerResult = res.result;
426
+
427
+ if (reviewerResult.code === 0 && !reviewerResult.timedOut) {
428
+ break;
429
+ }
430
+
431
+ if (reviewerName !== reviewersToTry[reviewersToTry.length - 1]) {
432
+ console.log(`${reviewer.displayName} failed for ${details.displayId}; trying next reviewer...`);
433
+ }
434
+ }
435
+
436
+ const report = buildReport(currentReviewerConfig, backend, targetInfo, details, diffPayloads, reviewer, reviewerResult);
413
437
  const outputFile = path.join(config.outputDir, backend.getReportFileName(changeId));
414
438
  await writeTextFile(outputFile, report);
415
439