kodevu 0.1.57 → 0.1.59
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 +24 -2
- package/SKILL.md +17 -1
- package/package.json +1 -1
- package/src/config.js +45 -1
- package/src/review-runner.js +19 -4
package/README.md
CHANGED
|
@@ -6,11 +6,11 @@ A Node.js tool that fetches Git commits or SVN revisions, sends the diff to a su
|
|
|
6
6
|
|
|
7
7
|
## Pure & Zero Config
|
|
8
8
|
|
|
9
|
-
Kodevu is designed to be stateless and requires no configuration
|
|
9
|
+
Kodevu is designed to be stateless and requires no mandatory configuration. All settings work out-of-the-box via command-line arguments and environment variables, with an optional persistent config file for convenience.
|
|
10
10
|
|
|
11
11
|
1. **Automatic Detection**: Detects repository type (Git/SVN), language, and available reviewers.
|
|
12
12
|
2. **Stateless**: Does not track history; reviews exactly what you ask for.
|
|
13
|
-
3. **Flexible**: Every setting can be
|
|
13
|
+
3. **Flexible**: Every setting can be set via config file, ENV var, or CLI flag, with CLI taking highest priority.
|
|
14
14
|
|
|
15
15
|
## Quick Start
|
|
16
16
|
|
|
@@ -69,6 +69,28 @@ You can set these in your shell to change default behavior without typing flags
|
|
|
69
69
|
- `KODEVU_OPENAI_ORG`: Optional organization ID for `openai`.
|
|
70
70
|
- `KODEVU_OPENAI_PROJECT`: Optional project ID for `openai`.
|
|
71
71
|
|
|
72
|
+
### Configuration File
|
|
73
|
+
|
|
74
|
+
For persistent settings that survive across shells and AI tools, create `~/.kodevu/config.json`:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"reviewer": "openai",
|
|
79
|
+
"openaiApiKey": "sk-...",
|
|
80
|
+
"openaiBaseUrl": "https://your-gateway.example.com/v1",
|
|
81
|
+
"openaiModel": "gpt-4o",
|
|
82
|
+
"lang": "zh"
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The file is optional and silently ignored if absent. **Priority order** (highest wins):
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
CLI flags > Environment variables > Config file > Built-in defaults
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Supported keys: `reviewer`, `lang`, `outputDir`, `prompt`, `commandTimeoutMs`, `outputFormats`, `openaiApiKey`, `openaiBaseUrl`, `openaiModel`, `openaiOrganization`, `openaiProject`.
|
|
93
|
+
|
|
72
94
|
## Examples
|
|
73
95
|
|
|
74
96
|
### Selecting Revisions
|
package/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: A tool to fetch Git/SVN diffs, send them to an AI reviewer, and gen
|
|
|
5
5
|
|
|
6
6
|
# Kodevu Skill
|
|
7
7
|
|
|
8
|
-
Kodevu is 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. It
|
|
8
|
+
Kodevu is 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. It supports an optional persistent config file at `~/.kodevu/config.json` for settings that should survive across sessions.
|
|
9
9
|
|
|
10
10
|
## Usage
|
|
11
11
|
|
|
@@ -71,6 +71,22 @@ All options can also be set via environment variables to avoid repetitive flags:
|
|
|
71
71
|
- `KODEVU_OPENAI_BASE_URL` – Base URL for `openai`.
|
|
72
72
|
- `KODEVU_OPENAI_MODEL` – Model for `openai`.
|
|
73
73
|
|
|
74
|
+
### Configuration File
|
|
75
|
+
|
|
76
|
+
For persistent settings that survive across shells and AI tool invocations, create `~/.kodevu/config.json`:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"reviewer": "openai",
|
|
81
|
+
"openaiApiKey": "sk-...",
|
|
82
|
+
"openaiBaseUrl": "https://your-gateway.example.com/v1",
|
|
83
|
+
"openaiModel": "gpt-4o",
|
|
84
|
+
"lang": "zh"
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The file is optional and silently ignored if absent. Priority: **CLI flags > ENV vars > config file > defaults**.
|
|
89
|
+
|
|
74
90
|
## Working with Target Repositories
|
|
75
91
|
|
|
76
92
|
- **Git**: `target` must be a local repository or subdirectory.
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -17,7 +17,7 @@ const defaultConfig = {
|
|
|
17
17
|
lang: "auto",
|
|
18
18
|
outputDir: defaultStorageDir,
|
|
19
19
|
logsDir: path.join(defaultStorageDir, "logs"),
|
|
20
|
-
commandTimeoutMs:
|
|
20
|
+
commandTimeoutMs: 120000,
|
|
21
21
|
prompt: "",
|
|
22
22
|
maxRevisionsPerRun: 5,
|
|
23
23
|
outputFormats: ["markdown"],
|
|
@@ -46,6 +46,9 @@ const ENV_MAP = {
|
|
|
46
46
|
KODEVU_OPENAI_PROJECT: "openaiProject"
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
const CONFIG_FILE_KEYS = new Set(Object.values(ENV_MAP));
|
|
50
|
+
const defaultConfigFilePath = path.join(defaultStorageDir, "config.json");
|
|
51
|
+
|
|
49
52
|
function resolvePath(value) {
|
|
50
53
|
if (!value) return value;
|
|
51
54
|
if (value === "~") return os.homedir();
|
|
@@ -55,6 +58,33 @@ function resolvePath(value) {
|
|
|
55
58
|
return path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
async function loadConfigFile(configPath = defaultConfigFilePath) {
|
|
62
|
+
const resolvedPath = resolvePath(configPath);
|
|
63
|
+
let content;
|
|
64
|
+
try {
|
|
65
|
+
content = await fs.readFile(resolvedPath, "utf8");
|
|
66
|
+
} catch (err) {
|
|
67
|
+
if (err.code === "ENOENT") return {};
|
|
68
|
+
throw new Error(`Failed to read config file ${resolvedPath}: ${err.message}`);
|
|
69
|
+
}
|
|
70
|
+
let parsed;
|
|
71
|
+
try {
|
|
72
|
+
parsed = JSON.parse(content);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
throw new Error(`Invalid JSON in config file ${resolvedPath}: ${err.message}`);
|
|
75
|
+
}
|
|
76
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
77
|
+
throw new Error(`Config file ${resolvedPath} must contain a JSON object`);
|
|
78
|
+
}
|
|
79
|
+
const result = {};
|
|
80
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
81
|
+
if (CONFIG_FILE_KEYS.has(key)) {
|
|
82
|
+
result[key] = value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
|
|
58
88
|
function normalizeOutputFormats(outputFormats) {
|
|
59
89
|
const source = outputFormats == null ? ["markdown"] : outputFormats;
|
|
60
90
|
const values = Array.isArray(source) ? source : String(source).split(",");
|
|
@@ -260,6 +290,14 @@ export function parseCliArgs(argv) {
|
|
|
260
290
|
export async function resolveConfig(cliArgs = {}) {
|
|
261
291
|
const config = { ...defaultConfig };
|
|
262
292
|
|
|
293
|
+
// 0. Merge Config File (lowest priority: overridden by env vars and CLI args)
|
|
294
|
+
const fileConfig = await loadConfigFile();
|
|
295
|
+
for (const key of CONFIG_FILE_KEYS) {
|
|
296
|
+
if (fileConfig[key] !== undefined && fileConfig[key] !== "") {
|
|
297
|
+
config[key] = fileConfig[key];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
263
301
|
// 1. Merge Environment Variables
|
|
264
302
|
for (const [envVar, configKey] of Object.entries(ENV_MAP)) {
|
|
265
303
|
if (process.env[envVar] !== undefined) {
|
|
@@ -388,6 +426,12 @@ Environment Variables:
|
|
|
388
426
|
KODEVU_OPENAI_MODEL Model for reviewer=openai
|
|
389
427
|
KODEVU_OPENAI_ORG Organization ID for reviewer=openai
|
|
390
428
|
KODEVU_OPENAI_PROJECT Project ID for reviewer=openai
|
|
429
|
+
|
|
430
|
+
Config File:
|
|
431
|
+
~/.kodevu/config.json Optional persistent settings (overridden by env vars and CLI flags)
|
|
432
|
+
Supported keys: reviewer, lang, outputDir, prompt, commandTimeoutMs, outputFormats,
|
|
433
|
+
openaiApiKey, openaiBaseUrl, openaiModel, openaiOrganization, openaiProject
|
|
434
|
+
Example: { "reviewer": "openai", "openaiApiKey": "sk-...", "openaiModel": "gpt-4o" }
|
|
391
435
|
`);
|
|
392
436
|
}
|
|
393
437
|
|
package/src/review-runner.js
CHANGED
|
@@ -105,7 +105,8 @@ async function reviewChange(config, backend, targetInfo, changeId, progress) {
|
|
|
105
105
|
reviewer: reviewerName,
|
|
106
106
|
console: false
|
|
107
107
|
});
|
|
108
|
-
//
|
|
108
|
+
// Store the result so the final throw can include stderr/exit code details
|
|
109
|
+
reviewerResult = err?.result ?? null;
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
if (reviewerName !== reviewersToTry[reviewersToTry.length - 1]) {
|
|
@@ -120,9 +121,23 @@ async function reviewChange(config, backend, targetInfo, changeId, progress) {
|
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
if (!reviewerResult || reviewerResult.code !== 0 || reviewerResult.timedOut) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
const displayName = reviewer?.displayName || config.reviewer;
|
|
125
|
+
const reasonParts = [`${displayName} failed for ${details.displayId}`];
|
|
126
|
+
|
|
127
|
+
if (reviewerResult?.timedOut) {
|
|
128
|
+
reasonParts.push("(timed out)");
|
|
129
|
+
} else if (reviewerResult?.code != null && reviewerResult.code !== 0) {
|
|
130
|
+
reasonParts.push(`(exit code: ${reviewerResult.code})`);
|
|
131
|
+
} else if (!reviewerResult) {
|
|
132
|
+
reasonParts.push("(reviewer produced no result)");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const detail = (reviewerResult?.stderr || reviewerResult?.stdout || "").trim();
|
|
136
|
+
if (detail) {
|
|
137
|
+
reasonParts.push(`\n${detail}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
throw new Error(reasonParts.join(" "));
|
|
126
141
|
}
|
|
127
142
|
|
|
128
143
|
progress?.update(0.82, "writing report");
|