kodevu 0.1.15 → 0.1.17

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.15",
3
+ "version": "0.1.17",
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": {
@@ -1,8 +1,6 @@
1
1
  import readline from "node:readline";
2
2
 
3
3
  const SPINNER_FRAMES = ["|", "/", "-", "\\"];
4
- const DEFAULT_BAR_WIDTH = 24;
5
- const MIN_BAR_WIDTH = 8;
6
4
 
7
5
  function clampProgress(value) {
8
6
  if (!Number.isFinite(value)) {
@@ -12,21 +10,6 @@ function clampProgress(value) {
12
10
  return Math.max(0, Math.min(1, value));
13
11
  }
14
12
 
15
- function buildBar(progress, width) {
16
- const safeProgress = clampProgress(progress);
17
- const filled = Math.max(Math.round(safeProgress * width), safeProgress > 0 ? 1 : 0);
18
-
19
- if (filled >= width) {
20
- return `[${"=".repeat(width)}]`;
21
- }
22
-
23
- if (filled === 0) {
24
- return `[${" ".repeat(width)}]`;
25
- }
26
-
27
- return `[${"=".repeat(Math.max(filled - 1, 0))}>${" ".repeat(width - filled)}]`;
28
- }
29
-
30
13
  function truncateLine(line, maxWidth) {
31
14
  if (!Number.isFinite(maxWidth) || maxWidth <= 0) {
32
15
  return "";
@@ -44,10 +27,9 @@ function truncateLine(line, maxWidth) {
44
27
  }
45
28
 
46
29
  class ProgressItem {
47
- constructor(display, label, options = {}) {
30
+ constructor(display, label) {
48
31
  this.display = display;
49
32
  this.label = label;
50
- this.barWidth = options.barWidth || DEFAULT_BAR_WIDTH;
51
33
  this.progress = 0;
52
34
  this.stage = "";
53
35
  this.active = false;
@@ -55,8 +37,8 @@ class ProgressItem {
55
37
  }
56
38
 
57
39
  start(stage = "starting") {
58
- this.stage = stage;
59
40
  this.active = true;
41
+ this.stage = stage;
60
42
 
61
43
  if (!this.display.enabled) {
62
44
  this.writeFallback(`... ${this.label}: ${stage}`);
@@ -64,7 +46,7 @@ class ProgressItem {
64
46
  }
65
47
 
66
48
  this.display.start();
67
- this.display.render();
49
+ this.display.activate(this);
68
50
  }
69
51
 
70
52
  update(progress, stage) {
@@ -79,7 +61,7 @@ class ProgressItem {
79
61
  return;
80
62
  }
81
63
 
82
- this.display.render();
64
+ this.display.activate(this);
83
65
  }
84
66
 
85
67
  log(message) {
@@ -97,28 +79,23 @@ class ProgressItem {
97
79
  finish(prefix, progress, message) {
98
80
  this.progress = clampProgress(progress);
99
81
  this.active = false;
100
- this.display.stopIfIdle();
101
- this.display.writeStaticLine(this.buildLine(prefix, message));
82
+ this.display.deactivate(this);
83
+ this.display.writeStaticLine(this.buildFinalLine(prefix, message));
102
84
  }
103
85
 
104
86
  renderLine(frameIndex) {
105
- const spinner = SPINNER_FRAMES[frameIndex];
106
- return this.buildLine(spinner, this.stage);
87
+ return this.buildStatusLine(SPINNER_FRAMES[frameIndex], this.stage);
107
88
  }
108
89
 
109
- buildLine(prefix, suffix) {
90
+ buildFinalLine(prefix, message) {
91
+ return this.buildStatusLine(prefix, message);
92
+ }
93
+
94
+ buildStatusLine(prefix, suffix) {
110
95
  const availableWidth = this.display.getAvailableWidth();
111
96
  const pct = `${Math.round(this.progress * 100)}`.padStart(3, " ");
112
- const reservedWidth = prefix.length + this.label.length + pct.length + 6;
113
- const dynamicBarWidth = Math.min(
114
- this.barWidth,
115
- Math.max(MIN_BAR_WIDTH, availableWidth - reservedWidth - (suffix ? suffix.length + 1 : 0))
116
- );
117
- const bar = buildBar(this.progress, dynamicBarWidth);
118
- return truncateLine(
119
- `${prefix} ${this.label} ${bar} ${pct}%${suffix ? ` ${suffix}` : ""}`,
120
- availableWidth
121
- );
97
+ const suffixText = suffix ? ` | ${suffix}` : "";
98
+ return truncateLine(`${prefix} ${pct}% ${this.label}${suffixText}`, availableWidth);
122
99
  }
123
100
 
124
101
  writeFallback(line) {
@@ -138,12 +115,14 @@ export class ProgressDisplay {
138
115
  this.frameIndex = 0;
139
116
  this.timer = null;
140
117
  this.items = [];
141
- this.renderedLineCount = 0;
118
+ this.currentItem = null;
119
+ this.statusVisible = false;
120
+ this.resizeAttached = false;
142
121
  this.handleResize = this.handleResize.bind(this);
143
122
  }
144
123
 
145
- createItem(label, options = {}) {
146
- const item = new ProgressItem(this, label, options);
124
+ createItem(label) {
125
+ const item = new ProgressItem(this, label);
147
126
  this.items.push(item);
148
127
  return item;
149
128
  }
@@ -154,21 +133,38 @@ export class ProgressDisplay {
154
133
  }
155
134
 
156
135
  this.attachResizeHandler();
157
-
158
136
  this.timer = setInterval(() => {
159
137
  this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
160
138
  this.render();
161
139
  }, 120);
162
140
  }
163
141
 
142
+ activate(item) {
143
+ this.currentItem = item;
144
+ this.render();
145
+ }
146
+
147
+ deactivate(item) {
148
+ if (this.currentItem === item) {
149
+ this.currentItem = this.items.findLast((candidate) => candidate.active) || null;
150
+ }
151
+
152
+ this.stopIfIdle();
153
+ this.clearStatusLine();
154
+
155
+ if (this.currentItem) {
156
+ this.render();
157
+ }
158
+ }
159
+
164
160
  log(message) {
165
161
  if (!this.enabled) {
166
162
  this.stream.write(`${message}\n`);
167
163
  return;
168
164
  }
169
165
 
170
- this.clearRender();
171
- this.stream.write(`${message}\n`);
166
+ this.clearStatusLine();
167
+ this.stream.write(`${truncateLine(message, this.getAvailableWidth())}\n`);
172
168
  this.render();
173
169
  }
174
170
 
@@ -178,48 +174,29 @@ export class ProgressDisplay {
178
174
  return;
179
175
  }
180
176
 
181
- this.clearRender();
177
+ this.clearStatusLine();
182
178
  this.stream.write(`${truncateLine(message, this.getAvailableWidth())}\n`);
183
179
  this.render();
184
180
  }
185
181
 
186
182
  render() {
187
- if (!this.enabled) {
183
+ if (!this.enabled || !this.currentItem?.active) {
188
184
  return;
189
185
  }
190
186
 
191
- const activeItems = this.items.filter((item) => item.active);
192
- this.clearRender();
193
-
194
- if (activeItems.length === 0) {
195
- this.renderedLineCount = 0;
196
- return;
197
- }
198
-
199
- const lines = activeItems.map((item) => item.renderLine(this.frameIndex));
200
- this.stream.write(lines.join("\n"));
201
- this.renderedLineCount = lines.length;
187
+ this.clearStatusLine();
188
+ this.stream.write(this.currentItem.renderLine(this.frameIndex));
189
+ this.statusVisible = true;
202
190
  }
203
191
 
204
- clearRender() {
205
- if (!this.enabled || this.renderedLineCount === 0) {
192
+ clearStatusLine() {
193
+ if (!this.enabled || !this.statusVisible) {
206
194
  return;
207
195
  }
208
196
 
209
- readline.moveCursor(this.stream, 0, -Math.max(this.renderedLineCount - 1, 0));
210
-
211
- for (let index = 0; index < this.renderedLineCount; index += 1) {
212
- readline.clearLine(this.stream, 0);
213
- readline.cursorTo(this.stream, 0);
214
-
215
- if (index < this.renderedLineCount - 1) {
216
- readline.moveCursor(this.stream, 0, 1);
217
- }
218
- }
219
-
220
- readline.moveCursor(this.stream, 0, -Math.max(this.renderedLineCount - 1, 0));
197
+ readline.clearLine(this.stream, 0);
221
198
  readline.cursorTo(this.stream, 0);
222
- this.renderedLineCount = 0;
199
+ this.statusVisible = false;
223
200
  }
224
201
 
225
202
  stopIfIdle() {
@@ -237,7 +214,7 @@ export class ProgressDisplay {
237
214
 
238
215
  getAvailableWidth() {
239
216
  const columns = this.stream.columns || 80;
240
- return Math.max(columns - 1, 20);
217
+ return Math.max(columns - 1, 1);
241
218
  }
242
219
 
243
220
  handleResize() {
@@ -592,24 +592,16 @@ export async function runReviewCycle(config) {
592
592
 
593
593
  console.log(`Reviewing ${backend.displayName} ${backend.changeName}s ${formatChangeList(backend, changeIdsToReview)}`);
594
594
  const progressDisplay = new ProgressDisplay();
595
- const overallProgress = progressDisplay.createItem(
596
- `${backend.displayName} ${backend.changeName} batch`,
597
- { barWidth: 30 }
598
- );
599
- overallProgress.start("0/" + changeIdsToReview.length + " completed");
595
+ const progress = progressDisplay.createItem(`${backend.displayName} ${backend.changeName} batch`);
596
+ progress.start("0/" + changeIdsToReview.length + " completed");
600
597
 
601
598
  for (const [index, changeId] of changeIdsToReview.entries()) {
602
599
  debugLog(config, `Starting review for ${backend.formatChangeId(changeId)}.`);
603
600
  const displayId = backend.formatChangeId(changeId);
604
- const progress = progressDisplay.createItem(
605
- `${backend.displayName} ${backend.changeName} ${displayId} (${index + 1}/${changeIdsToReview.length})`
606
- );
607
- progress.start("queued");
608
- updateOverallProgress(overallProgress, index, changeIdsToReview.length, 0, `starting ${displayId}`);
601
+ updateOverallProgress(progress, index, changeIdsToReview.length, 0, `starting ${displayId}`);
609
602
 
610
603
  const syncOverallProgress = (fraction, stage) => {
611
- progress.update(fraction, stage);
612
- updateOverallProgress(overallProgress, index, changeIdsToReview.length, fraction, `${displayId} | ${stage}`);
604
+ updateOverallProgress(progress, index, changeIdsToReview.length, fraction, `${displayId} | ${stage}`);
613
605
  };
614
606
 
615
607
  let result;
@@ -618,8 +610,7 @@ export async function runReviewCycle(config) {
618
610
  result = await reviewChange(config, backend, targetInfo, changeId, { update: syncOverallProgress, log: (message) => progress.log(message) });
619
611
  syncOverallProgress(0.94, "saving checkpoint");
620
612
  } catch (error) {
621
- progress.fail(`failed at ${displayId}`);
622
- overallProgress.fail(`failed at ${displayId} (${index}/${changeIdsToReview.length} completed)`);
613
+ progress.fail(`failed at ${displayId} (${index}/${changeIdsToReview.length} completed)`);
623
614
  throw error;
624
615
  }
625
616
 
@@ -631,11 +622,11 @@ export async function runReviewCycle(config) {
631
622
  await saveState(config.stateFilePath, updateProjectState(stateFile, targetInfo, nextProjectState));
632
623
  stateFile.projects[targetInfo.stateKey] = nextProjectState;
633
624
  debugLog(config, `Saved checkpoint for ${backend.formatChangeId(changeId)} to ${config.stateFilePath}.`);
634
- progress.succeed(`reviewed ${displayId}: ${outputLabels.join(" | ") || "(no report file generated)"}`);
635
- updateOverallProgress(overallProgress, index + 1, changeIdsToReview.length, 0, `finished ${displayId}`);
625
+ progress.log(`[done] reviewed ${displayId}: ${outputLabels.join(" | ") || "(no report file generated)"}`);
626
+ updateOverallProgress(progress, index + 1, changeIdsToReview.length, 0, `finished ${displayId}`);
636
627
  }
637
628
 
638
- overallProgress.succeed(`completed ${changeIdsToReview.length}/${changeIdsToReview.length}`);
629
+ progress.succeed(`completed ${changeIdsToReview.length}/${changeIdsToReview.length}`);
639
630
 
640
631
  const remainingChanges = await backend.getPendingChangeIds(
641
632
  config,