kodevu 0.1.19 → 0.1.21
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 +1 -1
- package/package.json +2 -3
- package/src/config.js +15 -6
- package/src/progress-ui.js +18 -262
- package/config.example.json +0 -12
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ If you want a config file, run `npx kodevu init` to create `./config.json` in th
|
|
|
32
32
|
npx kodevu init
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
This creates `config.json` in the current directory from
|
|
35
|
+
This creates `config.json` in the current directory from built-in defaults.
|
|
36
36
|
You only need this when you want to override defaults such as `reviewer` or output paths.
|
|
37
37
|
|
|
38
38
|
If you want a different path:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kodevu",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Poll SVN revisions or Git commits, send each change diff to a reviewer CLI, and write configurable review reports.",
|
|
6
6
|
"bin": {
|
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"src",
|
|
11
|
-
"README.md"
|
|
12
|
-
"config.example.json"
|
|
11
|
+
"README.md"
|
|
13
12
|
],
|
|
14
13
|
"scripts": {
|
|
15
14
|
"start": "node src/index.js",
|
package/src/config.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
-
import { constants as fsConstants } from "node:fs";
|
|
3
2
|
import os from "node:os";
|
|
4
3
|
import path from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
4
|
import { findCommandOnPath } from "./shell.js";
|
|
7
5
|
|
|
8
6
|
const defaultStorageDir = path.join(os.homedir(), ".kodevu");
|
|
@@ -16,10 +14,21 @@ const defaultConfig = {
|
|
|
16
14
|
commandTimeoutMs: 600000,
|
|
17
15
|
prompt:
|
|
18
16
|
"请严格审查当前变更,优先指出 bug、回归风险、兼容性问题、安全问题、边界条件缺陷和缺失测试。请使用简体中文输出 Markdown;如果没有明确缺陷,请写“未发现明确缺陷”,并补充剩余风险。",
|
|
19
|
-
maxRevisionsPerRun:
|
|
17
|
+
maxRevisionsPerRun: 5,
|
|
20
18
|
outputFormats: ["markdown"]
|
|
21
19
|
};
|
|
22
20
|
|
|
21
|
+
const configTemplate = {
|
|
22
|
+
target: "C:/path/to/your/repository-or-subdirectory",
|
|
23
|
+
reviewer: defaultConfig.reviewer,
|
|
24
|
+
prompt: defaultConfig.prompt,
|
|
25
|
+
outputDir: "~/.kodevu",
|
|
26
|
+
stateFilePath: "~/.kodevu/state.json",
|
|
27
|
+
commandTimeoutMs: defaultConfig.commandTimeoutMs,
|
|
28
|
+
maxRevisionsPerRun: defaultConfig.maxRevisionsPerRun,
|
|
29
|
+
outputFormats: defaultConfig.outputFormats
|
|
30
|
+
};
|
|
31
|
+
|
|
23
32
|
function resolveConfigPath(baseDir, value) {
|
|
24
33
|
if (!value) {
|
|
25
34
|
return value;
|
|
@@ -264,12 +273,12 @@ Config highlights:
|
|
|
264
273
|
|
|
265
274
|
export async function initConfig(targetPath = "config.json") {
|
|
266
275
|
const absoluteTargetPath = path.resolve(targetPath);
|
|
267
|
-
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
268
|
-
const templatePath = path.join(packageRoot, "config.example.json");
|
|
269
276
|
|
|
270
277
|
await fs.mkdir(path.dirname(absoluteTargetPath), { recursive: true });
|
|
278
|
+
|
|
279
|
+
const content = JSON.stringify(configTemplate, null, 2) + "\n";
|
|
271
280
|
try {
|
|
272
|
-
await fs.
|
|
281
|
+
await fs.writeFile(absoluteTargetPath, content, { flag: "wx" });
|
|
273
282
|
} catch (error) {
|
|
274
283
|
if (error?.code === "EEXIST") {
|
|
275
284
|
throw new Error(`Config file already exists: ${absoluteTargetPath}`);
|
package/src/progress-ui.js
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import readline from "node:readline";
|
|
2
|
-
|
|
3
|
-
const SPINNER_FRAMES = ["|", "/", "-", "\\"];
|
|
4
|
-
const ELLIPSIS = "...";
|
|
5
|
-
const MIN_DYNAMIC_WIDTH = 60;
|
|
6
|
-
|
|
7
1
|
function clampProgress(value) {
|
|
8
2
|
if (!Number.isFinite(value)) {
|
|
9
3
|
return 0;
|
|
@@ -12,78 +6,6 @@ function clampProgress(value) {
|
|
|
12
6
|
return Math.max(0, Math.min(1, value));
|
|
13
7
|
}
|
|
14
8
|
|
|
15
|
-
function getCharacterWidth(character) {
|
|
16
|
-
const codePoint = character.codePointAt(0);
|
|
17
|
-
|
|
18
|
-
if (!codePoint || codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f)) {
|
|
19
|
-
return 0;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (
|
|
23
|
-
codePoint >= 0x1100 &&
|
|
24
|
-
(
|
|
25
|
-
codePoint <= 0x115f ||
|
|
26
|
-
codePoint === 0x2329 ||
|
|
27
|
-
codePoint === 0x232a ||
|
|
28
|
-
(codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
|
|
29
|
-
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
30
|
-
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
31
|
-
(codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
|
32
|
-
(codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
|
|
33
|
-
(codePoint >= 0xff00 && codePoint <= 0xff60) ||
|
|
34
|
-
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
35
|
-
(codePoint >= 0x1f300 && codePoint <= 0x1f64f) ||
|
|
36
|
-
(codePoint >= 0x1f900 && codePoint <= 0x1f9ff) ||
|
|
37
|
-
(codePoint >= 0x20000 && codePoint <= 0x3fffd)
|
|
38
|
-
)
|
|
39
|
-
) {
|
|
40
|
-
return 2;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return 1;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function getDisplayWidth(text) {
|
|
47
|
-
let width = 0;
|
|
48
|
-
|
|
49
|
-
for (const character of text) {
|
|
50
|
-
width += getCharacterWidth(character);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return width;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function truncateLine(line, maxWidth) {
|
|
57
|
-
if (!Number.isFinite(maxWidth) || maxWidth <= 0) {
|
|
58
|
-
return "";
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (getDisplayWidth(line) <= maxWidth) {
|
|
62
|
-
return line;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (maxWidth <= 3) {
|
|
66
|
-
return ".".repeat(maxWidth);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const targetWidth = maxWidth - getDisplayWidth(ELLIPSIS);
|
|
70
|
-
let result = "";
|
|
71
|
-
let width = 0;
|
|
72
|
-
|
|
73
|
-
for (const character of line) {
|
|
74
|
-
const nextWidth = width + getCharacterWidth(character);
|
|
75
|
-
|
|
76
|
-
if (nextWidth > targetWidth) {
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
result += character;
|
|
81
|
-
width = nextWidth;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return `${result}${ELLIPSIS}`;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
9
|
class ProgressItem {
|
|
88
10
|
constructor(display, label) {
|
|
89
11
|
this.display = display;
|
|
@@ -91,20 +13,13 @@ class ProgressItem {
|
|
|
91
13
|
this.progress = 0;
|
|
92
14
|
this.stage = "";
|
|
93
15
|
this.active = false;
|
|
94
|
-
this.
|
|
16
|
+
this.lastStatusLine = "";
|
|
95
17
|
}
|
|
96
18
|
|
|
97
19
|
start(stage = "starting") {
|
|
98
20
|
this.active = true;
|
|
99
21
|
this.stage = stage;
|
|
100
|
-
|
|
101
|
-
if (!this.display.canRenderDynamically()) {
|
|
102
|
-
this.writeFallback(`... ${this.label}: ${stage}`);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
this.display.start();
|
|
107
|
-
this.display.activate(this);
|
|
22
|
+
this.writeStatus();
|
|
108
23
|
}
|
|
109
24
|
|
|
110
25
|
update(progress, stage) {
|
|
@@ -114,17 +29,11 @@ class ProgressItem {
|
|
|
114
29
|
this.stage = stage;
|
|
115
30
|
}
|
|
116
31
|
|
|
117
|
-
|
|
118
|
-
this.writeFallback(`... ${this.label}: ${this.stage}`);
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
this.display.start();
|
|
123
|
-
this.display.activate(this);
|
|
32
|
+
this.writeStatus();
|
|
124
33
|
}
|
|
125
34
|
|
|
126
35
|
log(message) {
|
|
127
|
-
this.display.
|
|
36
|
+
this.display.writeLine(message);
|
|
128
37
|
}
|
|
129
38
|
|
|
130
39
|
succeed(message) {
|
|
@@ -138,189 +47,36 @@ class ProgressItem {
|
|
|
138
47
|
finish(prefix, progress, message) {
|
|
139
48
|
this.progress = clampProgress(progress);
|
|
140
49
|
this.active = false;
|
|
141
|
-
this.display.
|
|
142
|
-
this.display.writeStaticLine(this.buildFinalLine(prefix, message));
|
|
50
|
+
this.display.writeLine(`${prefix} ${message}`);
|
|
143
51
|
}
|
|
144
52
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
53
|
+
writeStatus() {
|
|
54
|
+
const line = this.buildStatusLine();
|
|
148
55
|
|
|
149
|
-
|
|
150
|
-
return this.buildStatusLine(prefix, message);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
buildStatusLine(prefix, suffix) {
|
|
154
|
-
const availableWidth = this.display.getAvailableWidth();
|
|
155
|
-
const pct = `${Math.round(this.progress * 100)}`.padStart(3, " ");
|
|
156
|
-
const suffixText = suffix ? ` | ${suffix}` : "";
|
|
157
|
-
return truncateLine(`${prefix} ${pct}% ${this.label}${suffixText}`, availableWidth);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
writeFallback(line) {
|
|
161
|
-
if (line === this.lastFallbackLine) {
|
|
56
|
+
if (line === this.lastStatusLine) {
|
|
162
57
|
return;
|
|
163
58
|
}
|
|
164
59
|
|
|
165
|
-
this.
|
|
166
|
-
this.display.
|
|
60
|
+
this.lastStatusLine = line;
|
|
61
|
+
this.display.writeLine(line);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
buildStatusLine() {
|
|
65
|
+
const pct = `${Math.round(this.progress * 100)}`.padStart(3, " ");
|
|
66
|
+
return `[progress] ${pct}% ${this.label}${this.stage ? ` | ${this.stage}` : ""}`;
|
|
167
67
|
}
|
|
168
68
|
}
|
|
169
69
|
|
|
170
70
|
export class ProgressDisplay {
|
|
171
71
|
constructor(options = {}) {
|
|
172
72
|
this.stream = options.stream || process.stdout;
|
|
173
|
-
this.enabled = Boolean(this.stream.isTTY);
|
|
174
|
-
this.frameIndex = 0;
|
|
175
|
-
this.timer = null;
|
|
176
|
-
this.items = [];
|
|
177
|
-
this.currentItem = null;
|
|
178
|
-
this.statusVisible = false;
|
|
179
|
-
this.lastStatusWidth = 0;
|
|
180
|
-
this.resizeAttached = false;
|
|
181
|
-
this.handleResize = this.handleResize.bind(this);
|
|
182
73
|
}
|
|
183
74
|
|
|
184
75
|
createItem(label) {
|
|
185
|
-
|
|
186
|
-
this.items.push(item);
|
|
187
|
-
return item;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
start() {
|
|
191
|
-
if (!this.enabled || this.timer) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
this.attachResizeHandler();
|
|
196
|
-
this.timer = setInterval(() => {
|
|
197
|
-
this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
|
|
198
|
-
this.render();
|
|
199
|
-
}, 120);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
activate(item) {
|
|
203
|
-
this.currentItem = item;
|
|
204
|
-
this.render();
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
deactivate(item) {
|
|
208
|
-
if (this.currentItem === item) {
|
|
209
|
-
this.currentItem = this.items.findLast((candidate) => candidate.active) || null;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
this.stopIfIdle();
|
|
213
|
-
this.clearStatusLine();
|
|
214
|
-
|
|
215
|
-
if (this.currentItem) {
|
|
216
|
-
this.render();
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
log(message) {
|
|
221
|
-
if (!this.canRenderDynamically()) {
|
|
222
|
-
this.stream.write(`${message}\n`);
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
this.clearStatusLine();
|
|
227
|
-
this.stream.write(`${truncateLine(message, this.getAvailableWidth())}\n`);
|
|
228
|
-
this.render();
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
writeStaticLine(message) {
|
|
232
|
-
if (!this.canRenderDynamically()) {
|
|
233
|
-
this.stream.write(`${message}\n`);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
this.clearStatusLine();
|
|
238
|
-
this.stream.write(`${truncateLine(message, this.getAvailableWidth())}\n`);
|
|
239
|
-
this.render();
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
render() {
|
|
243
|
-
if (!this.canRenderDynamically() || !this.currentItem?.active) {
|
|
244
|
-
this.clearStatusLine();
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
this.clearStatusLine();
|
|
249
|
-
const line = this.currentItem.renderLine(this.frameIndex);
|
|
250
|
-
this.stream.write(line);
|
|
251
|
-
this.statusVisible = true;
|
|
252
|
-
this.lastStatusWidth = getDisplayWidth(line);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
clearStatusLine() {
|
|
256
|
-
if (!this.enabled || !this.statusVisible) {
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const rows = Math.max(1, Math.ceil(this.lastStatusWidth / Math.max(this.stream.columns || 1, 1)));
|
|
261
|
-
|
|
262
|
-
readline.moveCursor(this.stream, 0, -Math.max(rows - 1, 0));
|
|
263
|
-
|
|
264
|
-
for (let index = 0; index < rows; index += 1) {
|
|
265
|
-
readline.clearLine(this.stream, 0);
|
|
266
|
-
readline.cursorTo(this.stream, 0);
|
|
267
|
-
|
|
268
|
-
if (index < rows - 1) {
|
|
269
|
-
readline.moveCursor(this.stream, 0, 1);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
readline.moveCursor(this.stream, 0, -Math.max(rows - 1, 0));
|
|
274
|
-
readline.cursorTo(this.stream, 0);
|
|
275
|
-
this.statusVisible = false;
|
|
276
|
-
this.lastStatusWidth = 0;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
stopIfIdle() {
|
|
280
|
-
if (this.items.some((item) => item.active)) {
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (this.timer) {
|
|
285
|
-
clearInterval(this.timer);
|
|
286
|
-
this.timer = null;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
this.detachResizeHandler();
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
getAvailableWidth() {
|
|
293
|
-
const columns = this.stream.columns || 80;
|
|
294
|
-
return Math.max(columns - 1, 1);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
canRenderDynamically() {
|
|
298
|
-
return this.enabled && this.getAvailableWidth() >= MIN_DYNAMIC_WIDTH;
|
|
76
|
+
return new ProgressItem(this, label);
|
|
299
77
|
}
|
|
300
78
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
this.render();
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
attachResizeHandler() {
|
|
310
|
-
if (typeof this.stream.on !== "function" || this.resizeAttached) {
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
this.stream.on("resize", this.handleResize);
|
|
315
|
-
this.resizeAttached = true;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
detachResizeHandler() {
|
|
319
|
-
if (typeof this.stream.off !== "function" || !this.resizeAttached) {
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
this.stream.off("resize", this.handleResize);
|
|
324
|
-
this.resizeAttached = false;
|
|
79
|
+
writeLine(message) {
|
|
80
|
+
this.stream.write(`${message}\n`);
|
|
325
81
|
}
|
|
326
82
|
}
|
package/config.example.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"target": "C:/path/to/your/repository-or-subdirectory",
|
|
3
|
-
"reviewer": "auto",
|
|
4
|
-
"prompt": "请严格审查当前变更,优先指出 bug、回归风险、兼容性问题、安全问题、边界条件缺陷和缺失测试。请使用简体中文输出 Markdown;如果没有明确缺陷,请写“未发现明确缺陷”,并补充剩余风险。",
|
|
5
|
-
"outputDir": "~/.kodevu",
|
|
6
|
-
"stateFilePath": "~/.kodevu/state.json",
|
|
7
|
-
"commandTimeoutMs": 600000,
|
|
8
|
-
"maxRevisionsPerRun": 5,
|
|
9
|
-
"outputFormats": [
|
|
10
|
-
"markdown"
|
|
11
|
-
]
|
|
12
|
-
}
|