kodevu 0.1.5 → 0.1.6

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
@@ -12,8 +12,8 @@ A Node.js tool that polls new SVN revisions or Git commits, fetches each change
12
12
  - generate a unified diff for that single revision or commit
13
13
  - send the diff and change metadata to the configured reviewer CLI
14
14
  - allow the reviewer to inspect related local repository files in read-only mode when a local workspace is available
15
- - write the result to `reports/`
16
- 5. Update `data/state.json` so the same change is not reviewed twice.
15
+ - write the result to `~/.kodevu/`
16
+ 5. Update `~/.kodevu/state.json` so the same change is not reviewed twice.
17
17
 
18
18
  ## Setup
19
19
 
@@ -22,6 +22,7 @@ npx kodevu init
22
22
  ```
23
23
 
24
24
  This creates `config.json` in the current directory from the packaged `config.example.json`.
25
+ You only need this when you want to override defaults such as `reviewer` or output paths.
25
26
 
26
27
  If you want a different path:
27
28
 
@@ -29,51 +30,52 @@ If you want a different path:
29
30
  npx kodevu init --config ./config.current.json
30
31
  ```
31
32
 
32
- Then edit `config.json` and set `target`.
33
+ Then edit `config.json` if you want custom settings.
33
34
 
34
- `config.json` is the default config file. If you do not pass `--config`, Kodevu will load `./config.json` from the current directory.
35
+ If you do not pass `--config`, Kodevu will try to load `./config.json` from the current directory only when that file exists. Otherwise it runs with built-in defaults.
35
36
 
36
37
  ## Run
37
38
 
38
- Run one cycle:
39
+ Run once:
39
40
 
40
41
  ```bash
41
- npx kodevu --once
42
+ npx kodevu /path/to/your/repo
42
43
  ```
43
44
 
44
- Run one cycle with debug logs:
45
+ Run once with debug logs:
45
46
 
46
47
  ```bash
47
- npx kodevu --once --debug
48
+ npx kodevu /path/to/your/repo --debug
48
49
  ```
49
50
 
50
- Start the scheduler:
51
+ Use a custom config path only when needed:
51
52
 
52
53
  ```bash
53
- npx kodevu
54
+ npx kodevu --config ./config.current.json
54
55
  ```
55
56
 
56
- Use a custom config path only when needed:
57
+ Or combine a config file with a positional target override:
57
58
 
58
59
  ```bash
59
- npx kodevu --config ./config.current.json --once
60
+ npx kodevu /path/to/your/repo --config ./config.current.json
60
61
  ```
61
62
 
62
63
  `--debug` / `-d` is a CLI-only switch. It is not read from `config.json`.
63
64
 
64
65
  ## Config
65
66
 
66
- - `target`: required repository target
67
+ - `target`: required repository target; can be provided by config or as the CLI positional argument
67
68
  - `reviewer`: `codex` or `gemini`; default `codex`
68
- - `pollCron`: cron schedule, default every 10 minutes
69
69
  - `reviewPrompt`: saved into the report as review context
70
- - `outputDir`: report output directory; default `./reports`
70
+ - `outputDir`: report output directory; default `~/.kodevu`
71
+ - `stateFilePath`: review state file path; default `~/.kodevu/state.json`
71
72
  - `commandTimeoutMs`: timeout for a single review command execution in milliseconds
72
73
  - `maxRevisionsPerRun`: cap the number of pending changes per polling cycle
73
74
 
74
75
  Internal defaults:
75
76
 
76
- - review state is always stored in `./data/state.json`, and first run starts from the current latest change instead of replaying full history
77
+ - by default, review reports and state are stored under `~/.kodevu`; first run starts from the current latest change instead of replaying full history
78
+ - if `./config.json` is absent, Kodevu still runs with built-in defaults as long as you pass a positional `target`
77
79
  - Kodevu invokes `git`, `svn`, and the configured reviewer CLI from `PATH`; debug logging is enabled only by passing `--debug` or `-d`
78
80
 
79
81
  ## Target Rules
@@ -81,7 +83,6 @@ Internal defaults:
81
83
  - For SVN, `target` can be a working copy path or repository URL.
82
84
  - For Git, `target` must be a local repository path or a subdirectory inside a local repository.
83
85
  - The tool tries Git first for existing local paths, then falls back to SVN.
84
- - Legacy `svnTarget` is still accepted for backward compatibility.
85
86
 
86
87
  ## Notes
87
88
 
@@ -92,5 +93,5 @@ Internal defaults:
92
93
  - For remote SVN URLs without a local working copy, the review still relies on the diff and change metadata only.
93
94
  - SVN reports keep the `r123.md` naming style.
94
95
  - Git reports are written as `git-<short-commit-hash>.md`.
95
- - `data/state.json` stores per-project checkpoints keyed by repository identity; only the v2 multi-project structure is supported.
96
+ - `~/.kodevu/state.json` stores per-project checkpoints keyed by repository identity; only the v2 multi-project structure is supported.
96
97
  - If the reviewer command exits non-zero or times out, the report is still written, but the state is not advanced so the change can be retried later.
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "target": "C:/path/to/your/repository-or-subdirectory",
3
3
  "reviewer": "codex",
4
- "pollCron": "*/10 * * * *",
5
4
  "reviewPrompt": "请严格审查当前变更,优先指出 bug、回归风险、兼容性问题、安全问题、边界条件缺陷和缺失测试。请使用简体中文输出 Markdown;如果没有明确缺陷,请写“未发现明确缺陷”,并补充剩余风险。",
6
- "outputDir": "./reports",
5
+ "outputDir": "~/.kodevu",
6
+ "stateFilePath": "~/.kodevu/state.json",
7
7
  "commandTimeoutMs": 600000,
8
8
  "maxRevisionsPerRun": 5
9
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodevu",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
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": {
@@ -13,7 +13,7 @@
13
13
  ],
14
14
  "scripts": {
15
15
  "start": "node src/index.js",
16
- "once": "node src/index.js --once",
16
+ "once": "node src/index.js",
17
17
  "check": "node --check src/index.js && node --check src/config.js && node --check src/review-runner.js && node --check src/svn-client.js && node --check src/git-client.js && node --check src/vcs-client.js && node --check src/shell.js"
18
18
  },
19
19
  "engines": {
@@ -22,7 +22,6 @@
22
22
  "dependencies": {
23
23
  "cross-spawn": "^7.0.6",
24
24
  "fast-xml-parser": "^5.2.5",
25
- "iconv-lite": "^0.6.3",
26
- "node-cron": "^4.2.1"
25
+ "iconv-lite": "^0.6.3"
27
26
  }
28
27
  }
package/src/config.js CHANGED
@@ -1,24 +1,48 @@
1
1
  import fs from "node:fs/promises";
2
2
  import { constants as fsConstants } from "node:fs";
3
+ import os from "node:os";
3
4
  import path from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
6
 
7
+ const defaultStorageDir = path.join(os.homedir(), ".kodevu");
8
+
6
9
  const defaultConfig = {
7
10
  reviewer: "codex",
8
11
  target: "",
9
- pollCron: "*/10 * * * *",
10
- outputDir: "./reports",
12
+ outputDir: defaultStorageDir,
13
+ stateFilePath: path.join(defaultStorageDir, "state.json"),
11
14
  commandTimeoutMs: 600000,
12
15
  reviewPrompt:
13
16
  "请严格审查当前变更,优先指出 bug、回归风险、兼容性问题、安全问题、边界条件缺陷和缺失测试。请使用简体中文输出 Markdown;如果没有明确缺陷,请写“未发现明确缺陷”,并补充剩余风险。",
14
17
  maxRevisionsPerRun: 20
15
18
  };
16
19
 
20
+ function resolveConfigPath(baseDir, value) {
21
+ if (!value) {
22
+ return value;
23
+ }
24
+
25
+ if (typeof value !== "string") {
26
+ return path.resolve(baseDir, String(value));
27
+ }
28
+
29
+ if (value === "~") {
30
+ return os.homedir();
31
+ }
32
+
33
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
34
+ return path.join(os.homedir(), value.slice(2));
35
+ }
36
+
37
+ return path.isAbsolute(value) ? value : path.resolve(baseDir, value);
38
+ }
39
+
17
40
  export function parseCliArgs(argv) {
18
41
  const args = {
19
42
  command: "run",
20
43
  configPath: "config.json",
21
- once: false,
44
+ configExplicitlySet: false,
45
+ target: "",
22
46
  debug: false,
23
47
  help: false,
24
48
  commandExplicitlySet: false
@@ -33,11 +57,6 @@ export function parseCliArgs(argv) {
33
57
  continue;
34
58
  }
35
59
 
36
- if (value === "--once") {
37
- args.once = true;
38
- continue;
39
- }
40
-
41
60
  if (value === "--help" || value === "-h") {
42
61
  args.help = true;
43
62
  continue;
@@ -54,9 +73,17 @@ export function parseCliArgs(argv) {
54
73
  throw new Error(`Missing value for ${value}`);
55
74
  }
56
75
  args.configPath = configPath;
76
+ args.configExplicitlySet = true;
57
77
  index += 1;
58
78
  continue;
59
79
  }
80
+
81
+ if (!value.startsWith("-") && args.command === "run" && !args.target) {
82
+ args.target = value;
83
+ continue;
84
+ }
85
+
86
+ throw new Error(`Unexpected argument: ${value}`);
60
87
  }
61
88
 
62
89
  delete args.commandExplicitlySet;
@@ -65,40 +92,54 @@ export function parseCliArgs(argv) {
65
92
 
66
93
  export async function loadConfig(configPath, cliArgs = {}) {
67
94
  const absoluteConfigPath = path.resolve(configPath);
68
- const raw = await fs.readFile(absoluteConfigPath, "utf8");
95
+ let loadedConfig = {};
96
+ let loadedConfigPath = null;
97
+ let baseDir = process.cwd();
98
+
99
+ try {
100
+ const raw = await fs.readFile(absoluteConfigPath, "utf8");
101
+ loadedConfig = JSON.parse(raw);
102
+ loadedConfigPath = absoluteConfigPath;
103
+ baseDir = path.dirname(absoluteConfigPath);
104
+ } catch (error) {
105
+ if (!(error?.code === "ENOENT" && !cliArgs.configExplicitlySet)) {
106
+ throw error;
107
+ }
108
+ }
109
+
69
110
  const config = {
70
111
  ...defaultConfig,
71
- ...JSON.parse(raw)
112
+ ...loadedConfig
72
113
  };
73
114
 
74
- if (!config.target && config.svnTarget) {
75
- config.target = config.svnTarget;
115
+ if (cliArgs.target) {
116
+ config.target = cliArgs.target;
76
117
  }
77
118
 
78
119
  if (!config.target) {
79
- throw new Error(`Missing required config field "target" (or legacy "svnTarget") in ${absoluteConfigPath}`);
120
+ throw new Error('Missing target. Pass `npx kodevu <repo-path>` or set "target" in config.json.');
80
121
  }
81
122
 
82
123
  config.reviewer = String(config.reviewer || "codex").toLowerCase();
83
124
  config.debug = Boolean(cliArgs.debug);
84
125
 
85
126
  if (!["codex", "gemini"].includes(config.reviewer)) {
86
- throw new Error(`"reviewer" must be one of "codex" or "gemini" in ${absoluteConfigPath}`);
127
+ throw new Error(`"reviewer" must be one of "codex" or "gemini"${loadedConfigPath ? ` in ${loadedConfigPath}` : ""}`);
87
128
  }
88
129
 
89
- config.configPath = absoluteConfigPath;
90
- config.baseDir = path.dirname(absoluteConfigPath);
91
- config.outputDir = path.resolve(config.baseDir, config.outputDir);
92
- config.stateFilePath = path.resolve(config.baseDir, "./data/state.json");
130
+ config.configPath = loadedConfigPath;
131
+ config.baseDir = baseDir;
132
+ config.outputDir = resolveConfigPath(config.baseDir, config.outputDir);
133
+ config.stateFilePath = resolveConfigPath(config.baseDir, config.stateFilePath);
93
134
  config.maxRevisionsPerRun = Number(config.maxRevisionsPerRun);
94
135
  config.commandTimeoutMs = Number(config.commandTimeoutMs);
95
136
 
96
137
  if (!Number.isInteger(config.maxRevisionsPerRun) || config.maxRevisionsPerRun <= 0) {
97
- throw new Error(`"maxRevisionsPerRun" must be a positive integer in ${absoluteConfigPath}`);
138
+ throw new Error(`"maxRevisionsPerRun" must be a positive integer${loadedConfigPath ? ` in ${loadedConfigPath}` : ""}`);
98
139
  }
99
140
 
100
141
  if (!Number.isInteger(config.commandTimeoutMs) || config.commandTimeoutMs <= 0) {
101
- throw new Error(`"commandTimeoutMs" must be a positive integer in ${absoluteConfigPath}`);
142
+ throw new Error(`"commandTimeoutMs" must be a positive integer${loadedConfigPath ? ` in ${loadedConfigPath}` : ""}`);
102
143
  }
103
144
 
104
145
  return config;
@@ -110,18 +151,19 @@ export function printHelp() {
110
151
  Usage:
111
152
  kodevu init
112
153
  npx kodevu init
113
- kodevu [--config config.json] [--once]
114
- npx kodevu [--config config.json] [--once]
154
+ kodevu <target> [--debug]
155
+ npx kodevu <target> [--debug]
156
+ kodevu [--config config.json]
157
+ npx kodevu [--config config.json]
115
158
 
116
159
  Options:
117
- --config, -c Path to config json. Default: ./config.json in the current directory
160
+ --config, -c Optional config json path. If omitted, ./config.json is loaded only when present
118
161
  --debug, -d Print extra debug information to the console
119
- --once Run one polling cycle and exit
120
162
  --help, -h Show help
121
163
 
122
164
  Config highlights:
123
165
  reviewer codex | gemini
124
- target Repository target path (Git) or SVN working copy / URL
166
+ target Repository target path (Git) or SVN working copy / URL; CLI positional target overrides config
125
167
  `);
126
168
  }
127
169
 
package/src/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import cron from "node-cron";
4
3
  import { initConfig, loadConfig, parseCliArgs, printHelp } from "./config.js";
5
4
  import { runReviewCycle } from "./review-runner.js";
6
5
 
@@ -38,43 +37,16 @@ if (config.debug) {
38
37
  configPath: config.configPath,
39
38
  reviewer: config.reviewer,
40
39
  target: config.target,
41
- pollCron: config.pollCron,
42
40
  outputDir: config.outputDir,
43
- debug: config.debug,
44
- once: cliArgs.once
41
+ debug: config.debug
45
42
  })}`
46
43
  );
47
44
  }
48
45
 
49
- let running = false;
50
-
51
- async function runOnce() {
52
- if (running) {
53
- console.log("A review cycle is already running, skipping this trigger.");
54
- return;
55
- }
56
-
57
- running = true;
58
-
59
- try {
60
- await runReviewCycle(config);
61
- } catch (error) {
62
- console.error(error?.stack || String(error));
63
- process.exitCode = 1;
64
- } finally {
65
- running = false;
66
- }
67
- }
68
-
69
- if (cliArgs.once) {
70
- await runOnce();
71
- process.exit(process.exitCode || 0);
46
+ try {
47
+ await runReviewCycle(config);
48
+ } catch (error) {
49
+ console.error(error?.stack || String(error));
50
+ process.exitCode = 1;
72
51
  }
73
-
74
- await runOnce();
75
-
76
- console.log(`Scheduler started. Cron: ${config.pollCron}`);
77
-
78
- cron.schedule(config.pollCron, () => {
79
- void runOnce();
80
- });
52
+ process.exit(process.exitCode || 0);