reslop 0.1.1 → 0.1.2

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/lib/render.js CHANGED
@@ -2,20 +2,19 @@
2
2
 
3
3
  const ansi = require('./ansi.js');
4
4
  const { displayLines } = require('./diff.js');
5
- const { tokenize, overlayTokens } = require('./highlight.js');
5
+ const highlight = require('./highlight.js');
6
6
  const { detectLang } = require('./detect.js');
7
7
  const keys = require('./keys.js');
8
- const { ACTIONS, actionHot, actionLetter, buttonWord } = keys;
9
- const { FILES_DISABLED } = keys;
10
8
  const { overlayRows } = require('./select.js');
11
9
  const { itemPath } = require('./files.js');
12
- const { isDoneMark } = require('./review.js');
13
10
  const wrap = require('./wrap.js');
14
- const { wrapPlain, wrapDoc, wrapMultiline, cursorInWrap, wrapMove } = wrap;
15
11
 
16
- const { THEME, CODE_FG, codeFg, paint, visibleWidth, truncateVisible, fg } =
17
- ansi;
18
- const { RESET, ESC, EL, seq } = ansi;
12
+ const { tokenize, overlayTokens } = highlight;
13
+ const { ACTIONS, ACTION_IDS, actionLetter, buttonWord } = keys;
14
+ const { FILES_DISABLED } = keys;
15
+ const { wrapPlain, wrapMultiline, cursorInWrap } = wrap;
16
+ const { THEME, CODE_FG, codeFg, paint, visibleWidth } = ansi;
17
+ const { truncateVisible, fg, RESET, ESC, EL, seq } = ansi;
19
18
 
20
19
  const STYLES = {
21
20
  del: {
@@ -32,6 +31,27 @@ const STYLES = {
32
31
  chBg: THEME.addCharBg,
33
32
  mark: '+',
34
33
  },
34
+ warn: {
35
+ baseFg: THEME.delLineFg,
36
+ baseBg: THEME.delLineBg,
37
+ chFg: THEME.delCharFg,
38
+ chBg: THEME.delCharBg,
39
+ mark: ' ',
40
+ },
41
+ note: {
42
+ baseFg: THEME.chromeFg,
43
+ baseBg: THEME.noteBg,
44
+ chFg: THEME.chromeFg,
45
+ chBg: THEME.noteBg,
46
+ mark: ' ',
47
+ },
48
+ noteSep: {
49
+ baseFg: THEME.mutedFg,
50
+ baseBg: THEME.chromeBg,
51
+ chFg: THEME.mutedFg,
52
+ chBg: THEME.chromeBg,
53
+ mark: ' ',
54
+ },
35
55
  ctx: {
36
56
  baseFg: THEME.ctxFg,
37
57
  baseBg: THEME.ctxBg,
@@ -56,6 +76,15 @@ const STAGED_STYLES = {
56
76
  chBg: THEME.stagedAddCharBg,
57
77
  mark: '+',
58
78
  },
79
+ warn: {
80
+ baseFg: THEME.stagedDelLineFg,
81
+ baseBg: THEME.stagedDelLineBg,
82
+ chFg: THEME.stagedDelCharFg,
83
+ chBg: THEME.stagedDelCharBg,
84
+ mark: ' ',
85
+ },
86
+ note: STYLES.note,
87
+ noteSep: STYLES.noteSep,
59
88
  ctx: STYLES.ctx,
60
89
  };
61
90
 
@@ -76,7 +105,6 @@ const FILE_STATUS_FG = {
76
105
 
77
106
  const INFO_STATUS = [
78
107
  'copied',
79
- 'skipped',
80
108
  'staged',
81
109
  'unstaged',
82
110
  'not staged',
@@ -85,7 +113,6 @@ const INFO_STATUS = [
85
113
  'read only',
86
114
  'last block',
87
115
  'first block',
88
- 'all skipped',
89
116
  'first file',
90
117
  'last file',
91
118
  'nothing to review',
@@ -107,10 +134,14 @@ const BUTTON_GAP = ' ';
107
134
  const LOGO = '👁️';
108
135
  const FILE_MARK = '▶';
109
136
  const FILE_LIST_PAD = 1;
137
+ const CHECK_DONE = 'xX';
110
138
  const TODO_CAPTION = 'TODO:';
111
139
  const INPUT_MAX = 4;
112
140
  const NOTE_MAX = 4;
113
141
  const NOTE_PAD = 1;
142
+ const SOFT_WRAP = ['warn', 'note'];
143
+ const DEL_CHAR_BG = ansi.bg(THEME.delCharBg);
144
+ const ADD_CHAR_BG = ansi.bg(THEME.addCharBg);
114
145
 
115
146
  const lineStyles = (origin) => (origin === 'staged' ? STAGED_STYLES : STYLES);
116
147
 
@@ -137,7 +168,32 @@ const paintSpans = (pieces, lineStyle, budget, color) => {
137
168
  return { text: out, used };
138
169
  };
139
170
 
171
+ const paintAuditNote = (line, width, color) => {
172
+ const style = STYLES.note;
173
+ const gutterW = Math.min(GUTTER_W, Math.max(0, width));
174
+ const label = `${style.mark} `.slice(0, gutterW);
175
+ const gutter = paint(label, style.baseFg, style.baseBg, color);
176
+ const budget = Math.max(0, width - gutterW);
177
+ const text = truncateVisible(line.text ?? '', budget);
178
+ const used = visibleWidth(text);
179
+ let out = gutter;
180
+ if (text) out += paint(text, style.baseFg, style.baseBg, color);
181
+ if (used < budget) {
182
+ out += fill(budget - used, style.baseFg, style.baseBg, color);
183
+ }
184
+ return out;
185
+ };
186
+
187
+ const paintAuditRule = (width, color) => {
188
+ const style = STYLES.noteSep;
189
+ const n = Math.max(0, width);
190
+ const bar = '─'.repeat(n);
191
+ return paint(bar, style.baseFg, style.baseBg, color);
192
+ };
193
+
140
194
  const paintDiffLine = (line, width, color, origin, lang) => {
195
+ if (line.type === 'noteSep') return paintAuditRule(width, color);
196
+ if (line.type === 'note') return paintAuditNote(line, width, color);
141
197
  const styles = lineStyles(origin);
142
198
  const style = styles[line.type] ?? styles.ctx;
143
199
  const diffSpans = line.spans ?? [{ text: line.text, changed: false }];
@@ -183,19 +239,14 @@ const paintSplitBlank = (width, color) => {
183
239
  );
184
240
  };
185
241
 
186
- const FILL_KIND = {
187
- files: (width, color) => {
242
+ const paintBodyFill = (width, color, kind) => {
243
+ if (kind === 'files') {
188
244
  const inner = Math.max(1, width - 1);
189
245
  const main = fill(inner, THEME.ctxFg, THEME.ctxBg, color);
190
246
  const edge = fill(1, THEME.ctxFg, THEME.ctxBg, color);
191
247
  return main + edge;
192
- },
193
- split: (width, color) => paintSplitBlank(width, color),
194
- };
195
-
196
- const paintBodyFill = (width, color, kind) => {
197
- const paint = FILL_KIND[kind];
198
- if (paint) return paint(width, color);
248
+ }
249
+ if (kind === 'split') return paintSplitBlank(width, color);
199
250
  return fill(width, THEME.ctxFg, THEME.ctxBg, color);
200
251
  };
201
252
 
@@ -215,22 +266,18 @@ const splitCheck = (text) => {
215
266
  if (!match) return null;
216
267
  const start = match.index;
217
268
  const token = match[0];
218
- return {
219
- before: text.slice(0, start),
220
- mark: match[1],
221
- token,
222
- after: text.slice(start + token.length),
223
- };
269
+ const before = text.slice(0, start);
270
+ const mark = match[1];
271
+ const after = text.slice(start + token.length);
272
+ return { before, mark, token, after };
224
273
  };
225
274
 
226
275
  const paintCheckBar = (text, width, fgRgb, bgRgb, color) => {
227
276
  const clipped = truncateVisible(text, width);
277
+ const parts = color ? splitCheck(clipped) : null;
278
+ if (!parts) return paintBar(text, width, fgRgb, bgRgb, color);
228
279
  const pad = Math.max(0, width - visibleWidth(clipped));
229
- const filled = clipped + ' '.repeat(pad);
230
- if (!color) return filled;
231
- const parts = splitCheck(clipped);
232
- if (!parts) return `${seq(fgRgb, bgRgb)}${EL}${filled}${RESET}`;
233
- const done = isDoneMark(parts.mark);
280
+ const done = CHECK_DONE.includes(parts.mark);
234
281
  const chipFg = done ? THEME.checkDoneFg : THEME.checkFg;
235
282
  const chipBg = done ? THEME.checkDoneBg : THEME.checkBg;
236
283
  const after = `${parts.after}${' '.repeat(pad)}`;
@@ -241,10 +288,15 @@ const paintCheckBar = (text, width, fgRgb, bgRgb, color) => {
241
288
  return out;
242
289
  };
243
290
 
244
- const originLabel = (origin, revShort, sourceLabel) => {
245
- if (origin === 'commit') return revShort || 'commit';
246
- if (origin === 'pr') return sourceLabel || 'pr';
247
- if (origin === 'todo') return 'todo';
291
+ const ORIGIN_LABEL = {
292
+ commit: (view) => view.revShort || 'commit',
293
+ pr: (view) => view.sourceLabel || 'pr',
294
+ todo: () => 'todo',
295
+ };
296
+
297
+ const originLabel = (origin, view) => {
298
+ const named = ORIGIN_LABEL[origin];
299
+ if (named) return named(view);
248
300
  return origin ?? 'unknown';
249
301
  };
250
302
 
@@ -281,7 +333,7 @@ const headerTarget = (view) => {
281
333
  if (!view.item) return { path: '', origin: '', n: 0, total: view.total ?? 0 };
282
334
  return {
283
335
  path: itemPath(view.item),
284
- origin: originLabel(view.item.origin, view.revShort, view.sourceLabel),
336
+ origin: originLabel(view.item.origin, view),
285
337
  n: view.index + 1,
286
338
  total: view.total,
287
339
  };
@@ -295,17 +347,20 @@ const headerPrefix = (view) => {
295
347
  return ` ${LOGO} reslop: `;
296
348
  };
297
349
 
350
+ const headerSuffix = ({ origin, n, total }) =>
351
+ origin ? ` ${origin} ${n}/${total}` : ` ${n}/${total}`;
352
+
298
353
  const headerText = (view) => {
299
354
  const prefix = headerPrefix(view);
300
- const { path, origin, n, total } = headerTarget(view);
301
- const suffix = origin ? ` ${origin} ${n}/${total}` : ` ${n}/${total}`;
302
- return `${prefix}${path}${suffix}`;
355
+ const target = headerTarget(view);
356
+ return `${prefix}${target.path}${headerSuffix(target)}`;
303
357
  };
304
358
 
305
359
  const headerPieces = (view, width) => {
306
360
  const repo = view.repoName || '';
307
- const { path, origin, n, total } = headerTarget(view);
308
- const suffix = origin ? ` ${origin} ${n}/${total}` : ` ${n}/${total}`;
361
+ const target = headerTarget(view);
362
+ const path = target.path;
363
+ const suffix = headerSuffix(target);
309
364
  const right = ' ';
310
365
  const chrome = ` ${LOGO} reslop: `;
311
366
  const prefixW = visibleWidth(headerPrefix(view));
@@ -336,7 +391,7 @@ const headerPieces = (view, width) => {
336
391
  const paintHeader = (view, width, color) => {
337
392
  const pieces = headerPieces(view, width);
338
393
  if (!color) {
339
- return pieces.map((p) => p.text).join('');
394
+ return pieces.map((piece) => piece.text).join('');
340
395
  }
341
396
  const bgRgb = THEME.headerBg;
342
397
  let out = `${seq(THEME.headerChromeFg, bgRgb)}${EL}`;
@@ -355,7 +410,8 @@ const bodyLines = (item, layout) => {
355
410
  return [{ type: 'ctx', text, spans: [{ text, changed: false }] }];
356
411
  }
357
412
  if (!item.hunk) return [];
358
- return displayLines(item.hunk, item.blockId, layout);
413
+ const radius = item.dep ? Number.MAX_SAFE_INTEGER : undefined;
414
+ return displayLines(item.hunk, item.blockId, layout, radius);
359
415
  };
360
416
 
361
417
  const sourceEmpty = (line) => !line || !(line.text ?? '').trim();
@@ -373,26 +429,6 @@ const dropLeadingEmpty = (lines, split) => {
373
429
  return i ? lines.slice(i) : lines;
374
430
  };
375
431
 
376
- const helpLines = () => {
377
- const lines = [' keys and buttons', ''];
378
- for (const action of ACTIONS) {
379
- const keys = actionHot(action);
380
- let label = action.label;
381
- if (action.id === 'layout') {
382
- label = 'Mode (unified / mixed / side)';
383
- }
384
- lines.push(` ${keys.padEnd(16)} ${label}`);
385
- }
386
- lines.push(' enter open file / save note');
387
- lines.push(' esc files, then quit');
388
- lines.push(' del, backspace remove todo');
389
- lines.push(' j, k, wheel scroll / move');
390
- lines.push(' drag copy');
391
- lines.push(' ctrl-z, ctrl-y undo / redo');
392
- lines.push(' ?, h help');
393
- return lines;
394
- };
395
-
396
432
  const BUTTON_MARK = {
397
433
  bgRgb: THEME.buttonBg,
398
434
  fgRgb: THEME.buttonFg,
@@ -429,15 +465,16 @@ const layoutButtons = (width, compact = false, disabled = []) => {
429
465
  const hits = [];
430
466
  const parts = [];
431
467
  let x = 0;
432
- for (const action of ACTIONS) {
468
+ for (const id of ACTION_IDS) {
469
+ const action = { id, ...ACTIONS[id] };
433
470
  const letter = actionLetter(action);
434
471
  const word = buttonWord(action);
435
472
  const label = short ? letter : word;
436
473
  const piece = `${BUTTON_GAP}${label}`;
437
474
  const w = visibleWidth(piece);
438
475
  if (x + w > width && parts.length) break;
439
- const isDisabled = disabled.includes(action.id);
440
- if (!isDisabled) hits.push({ id: action.id, x0: x, x1: x + w });
476
+ const isDisabled = disabled.includes(id);
477
+ if (!isDisabled) hits.push({ id, x0: x, x1: x + w });
441
478
  parts.push({ action, piece, letter, label, disabled: isDisabled });
442
479
  x += w;
443
480
  }
@@ -445,7 +482,7 @@ const layoutButtons = (width, compact = false, disabled = []) => {
445
482
  };
446
483
  if (compact) return build(true);
447
484
  const words = build(false);
448
- if (words.parts.length === ACTIONS.length) return words;
485
+ if (words.parts.length === ACTION_IDS.length) return words;
449
486
  return build(true);
450
487
  };
451
488
 
@@ -467,26 +504,38 @@ const paintButtons = (layout, width, color) => {
467
504
  return out;
468
505
  };
469
506
 
507
+ const COUNTS_KIND = {
508
+ pr: ({ counts, notes, sourceLabel }) => {
509
+ const n = counts.pr ?? 0;
510
+ const label = sourceLabel || 'pr';
511
+ return ` pr ${label} ${n} ${notes}`;
512
+ },
513
+ commit: ({ counts, notes, revShort }) => {
514
+ const n = counts.commit ?? 0;
515
+ return ` commit ${revShort} ${n} ${notes}`;
516
+ },
517
+ worktree: ({ counts, notes }) => {
518
+ const staged = counts.staged ?? 0;
519
+ const unstaged = counts.unstaged ?? 0;
520
+ const untracked = counts.untracked ?? 0;
521
+ const gitCounts = `unstaged ${unstaged} untracked ${untracked}`;
522
+ const git = `staged ${staged} ${gitCounts}`;
523
+ return ` ${git} ${notes}`;
524
+ },
525
+ };
526
+
527
+ const countsKind = (sourceKind, revShort) => {
528
+ if (sourceKind === 'pr') return 'pr';
529
+ if (revShort) return 'commit';
530
+ return 'worktree';
531
+ };
532
+
470
533
  const countsLine = (counts, revShort, sourceKind, sourceLabel) => {
471
- const skipped = counts.skipped ?? 0;
472
534
  const feedback = counts.feedback ?? 0;
473
535
  const todo = counts.todo ?? 0;
474
536
  const notes = `feedback ${feedback} todo ${todo}`;
475
- const extra = skipped ? ` skipped ${skipped}` : '';
476
- if (sourceKind === 'pr') {
477
- const n = counts.pr ?? 0;
478
- const label = sourceLabel || 'pr';
479
- return ` pr ${label} ${n} ${notes}${extra}`;
480
- }
481
- if (revShort) {
482
- const n = counts.commit ?? 0;
483
- return ` commit ${revShort} ${n} ${notes}${extra}`;
484
- }
485
- const staged = counts.staged ?? 0;
486
- const unstaged = counts.unstaged ?? 0;
487
- const untracked = counts.untracked ?? 0;
488
- const git = `staged ${staged} unstaged ${unstaged} untracked ${untracked}`;
489
- return ` ${git} ${notes}${extra}`;
537
+ const kind = countsKind(sourceKind, revShort);
538
+ return COUNTS_KIND[kind]({ counts, notes, sourceLabel, revShort });
490
539
  };
491
540
 
492
541
  const paintQuitPrompt = (width, color) => {
@@ -669,8 +718,7 @@ const paintIdleNote = (view, width, color) => {
669
718
  const text = view.noteText;
670
719
  if (!text) return empty;
671
720
  const inner = noteInnerWidth(width);
672
- const prefix = view.noteKind ? `${view.noteKind}: ` : '';
673
- const wrapped = wrapMultiline(`${prefix}${text}`, inner);
721
+ const wrapped = wrapMultiline(`feedback: ${text}`, inner);
674
722
  const rows = [];
675
723
  for (const line of wrapped.slice(0, NOTE_MAX)) {
676
724
  const painted = paintNoteLine(
@@ -688,7 +736,7 @@ const paintIdleNote = (view, width, color) => {
688
736
 
689
737
  const paintNotePanel = (view, width, color, cap) => {
690
738
  const empty = { rows: [], cursor: null, templateHits: [] };
691
- if (view.help || (view.pane ?? 'diff') === 'files') return empty;
739
+ if ((view.pane ?? 'diff') === 'files') return empty;
692
740
  const compose = view.compose;
693
741
  if (compose && compose.kind === 'feedback') {
694
742
  return paintComposeNote(view, width, color, cap);
@@ -696,16 +744,6 @@ const paintNotePanel = (view, width, color, cap) => {
696
744
  return paintIdleNote(view, width, color);
697
745
  };
698
746
 
699
- const paintBodyHelp = (width, color) => {
700
- const body = [];
701
- for (const line of helpLines()) {
702
- for (const row of wrapPlain(line, width)) {
703
- body.push(paintBar(row, width, THEME.chromeFg, THEME.ctxBg, color));
704
- }
705
- }
706
- return body;
707
- };
708
-
709
747
  const paintBodyFiles = (view, width, color, bodyH, headerLines) => {
710
748
  const files = view.files ?? [];
711
749
  const cols = fileColumns(files);
@@ -753,10 +791,39 @@ const paintBodyTodo = (view, width, color) => {
753
791
  return { body, todoOwners, cursor };
754
792
  };
755
793
 
794
+ const wrapSoftLine = (line, width) => {
795
+ const gutterW = Math.min(GUTTER_W, Math.max(0, width));
796
+ const budget = Math.max(1, width - gutterW);
797
+ const rows = wrapPlain(line.text ?? '', budget);
798
+ return rows.map((text) => {
799
+ const spans = [{ text, changed: false }];
800
+ return { ...line, text, spans };
801
+ });
802
+ };
803
+
804
+ const expandSoftRows = (lines, width, split) => {
805
+ const wrapWidth = split ? splitWidths(width).leftW : width;
806
+ const out = [];
807
+ for (const row of lines) {
808
+ const line = split ? row.left || row.right : row;
809
+ if (!line || !SOFT_WRAP.includes(line.type)) {
810
+ out.push(row);
811
+ continue;
812
+ }
813
+ const parts = wrapSoftLine(line, wrapWidth);
814
+ for (const part of parts) {
815
+ if (split) out.push({ left: part, right: part });
816
+ else out.push(part);
817
+ }
818
+ }
819
+ return out;
820
+ };
821
+
756
822
  const paintBodyDiff = (item, layout, width, color) => {
757
823
  const lang = detectLang(itemPath(item));
758
824
  const splitBody = layout === 'side' && !item.file.isBinary;
759
- const lines = dropLeadingEmpty(bodyLines(item, layout), splitBody);
825
+ const raw = dropLeadingEmpty(bodyLines(item, layout), splitBody);
826
+ const lines = expandSoftRows(raw, width, splitBody);
760
827
  const body = [];
761
828
  if (splitBody) {
762
829
  for (const row of lines) {
@@ -771,7 +838,6 @@ const paintBodyDiff = (item, layout, width, color) => {
771
838
  };
772
839
 
773
840
  const bodyKind = (view) => {
774
- if (view.help) return 'help';
775
841
  if ((view.pane ?? 'diff') === 'files') return 'files';
776
842
  if (!view.item) return 'empty';
777
843
  if (view.item.origin === 'todo') return 'todo';
@@ -779,11 +845,6 @@ const bodyKind = (view) => {
779
845
  };
780
846
 
781
847
  const BODY_PAINT = {
782
- help: (view, width, color) => ({
783
- body: paintBodyHelp(width, color),
784
- fileHits: [],
785
- todoOwners: [],
786
- }),
787
848
  files: (view, width, color, bodyH, headerLines) => ({
788
849
  ...paintBodyFiles(view, width, color, bodyH, headerLines),
789
850
  todoOwners: [],
@@ -837,10 +898,10 @@ const windowBody = (view, width, color, bodyH, headerLines, paintedBody) => {
837
898
  const todoOwners = paintedBody.todoOwners;
838
899
  const splitBody = paintedBody.splitBody === true;
839
900
  const pane = view.pane ?? 'diff';
840
- const listPane = pane === 'files' && !view.help;
901
+ const listPane = pane === 'files';
841
902
  const fillKind = bodyFillKind(listPane, splitBody);
842
903
  let shift = 0;
843
- if (!listPane && !view.help && body.length > 0) {
904
+ if (!listPane && body.length > 0) {
844
905
  body.unshift(paintBodyFill(width, color, fillKind));
845
906
  if (todoOwners.length) todoOwners.unshift(null);
846
907
  shift = 1;
@@ -961,36 +1022,18 @@ const presentRows = (rows, options = {}) => {
961
1022
  return parts.join('');
962
1023
  };
963
1024
 
964
- const DEL_CHAR_BG = ansi.bg(THEME.delCharBg);
965
- const ADD_CHAR_BG = ansi.bg(THEME.addCharBg);
966
- const DEL_LINE_FG = ansi.fg(THEME.delLineFg);
967
- const ADD_LINE_FG = ansi.fg(THEME.addLineFg);
968
-
969
1025
  module.exports = {
970
- LOGO,
971
- FILE_MARK,
972
1026
  QUIT_FINISH,
973
1027
  QUIT_CONTINUE,
974
1028
  QUIT_PROMPT,
975
- STYLES,
976
- STAGED_STYLES,
977
- renderFrame,
978
- presentCursor,
979
- presentRows,
980
- layoutButtons,
1029
+ DEL_CHAR_BG,
1030
+ ADD_CHAR_BG,
981
1031
  paintDiffLine,
982
- paintSplitRow,
983
- bodyLines,
984
1032
  headerText,
985
- wrapPlain,
986
- wrapDoc,
987
- wrapMultiline,
988
- cursorInWrap,
989
- wrapMove,
990
- noteInnerWidth,
1033
+ layoutButtons,
991
1034
  todoInnerWidth,
992
- DEL_CHAR_BG,
993
- ADD_CHAR_BG,
994
- DEL_LINE_FG,
995
- ADD_LINE_FG,
1035
+ noteInnerWidth,
1036
+ renderFrame,
1037
+ presentCursor,
1038
+ presentRows,
996
1039
  };
package/lib/review.js CHANGED
@@ -3,17 +3,13 @@
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
 
6
- const { itemPath } = require('./files.js');
6
+ const { itemPath, REVIEW_DIR } = require('./files.js');
7
7
 
8
- const REVIEW_DIR = '.review';
9
8
  const TEMPLATES_FILE = 'templates.json';
10
9
  const TEMPLATE_SHOW = 5;
11
10
  const AUTOSAVE_MS = 3000;
12
11
  const REVIEW_NAME = /^(\d{4}-\d{2}-\d{2})-(\d+)\.md$/;
13
12
  const REVIEW_STATUSES = ['editing', 'ready', 'partial', 'done'];
14
- const STATUS_ALIAS = {
15
- pending: 'ready',
16
- };
17
13
  const FEED_HEAD = /^### Feedback `([^`]+)` \+(\d+) \((.*)\)\s*$/;
18
14
  const FEED_BLOCK = /^(.*), block (\d+)$/;
19
15
  const FEED_BLOCK_ONLY = /^block (\d+)$/;
@@ -94,7 +90,7 @@ const latestReviewName = (names) => {
94
90
 
95
91
  const canonicalStatus = (raw) => {
96
92
  if (!raw) return '';
97
- const name = STATUS_ALIAS[raw] ?? raw;
93
+ const name = raw === 'pending' ? 'ready' : raw;
98
94
  if (REVIEW_STATUSES.includes(name)) return name;
99
95
  return '';
100
96
  };
@@ -116,7 +112,8 @@ const parseFrontmatterStatus = (text) => {
116
112
 
117
113
  const readReviewStatus = (reviewPath, readFileSync = fs.readFileSync) => {
118
114
  try {
119
- return parseFrontmatterStatus(readFileSync(reviewPath, 'utf8'));
115
+ const text = readFileSync(reviewPath, 'utf8');
116
+ return parseFrontmatterStatus(text);
120
117
  } catch {
121
118
  return '';
122
119
  }
@@ -133,10 +130,9 @@ const resolveReviewPath = (dir, date, existingNames, options = {}) => {
133
130
  if (status === 'editing') return { reviewPath, resume: true };
134
131
  }
135
132
  }
136
- return {
137
- reviewPath: allocateReviewPath(dir, date, existingNames),
138
- resume: false,
139
- };
133
+ const reviewPath = allocateReviewPath(dir, date, existingNames);
134
+ const resume = false;
135
+ return { reviewPath, resume };
140
136
  };
141
137
 
142
138
  const rankedTemplates = (templates) => {
@@ -354,25 +350,19 @@ const parseLoc = (raw) => {
354
350
  fileParts = fileParts.slice(1);
355
351
  }
356
352
  if (!fileParts.length) return null;
357
- return {
358
- origin,
359
- file: fileParts.join(':'),
360
- oldStart,
361
- newStart,
362
- blockId,
363
- };
353
+ const file = fileParts.join(':');
354
+ return { origin, file, oldStart, newStart, blockId };
364
355
  };
365
356
 
366
357
  const parseFeedSuffix = (textItem) => {
367
358
  const match = FEED_SUFFIX.exec(textItem);
368
359
  if (!match) return null;
369
- return {
370
- text: match[1],
371
- file: match[2],
372
- oldStart: toInt(match[3]),
373
- newStart: toInt(match[4]),
374
- blockId: toInt(match[5]),
375
- };
360
+ const text = match[1];
361
+ const file = match[2];
362
+ const oldStart = toInt(match[3]);
363
+ const newStart = toInt(match[4]);
364
+ const blockId = toInt(match[5]);
365
+ return { text, file, oldStart, newStart, blockId };
376
366
  };
377
367
 
378
368
  const fileGroup = (groups, file) => {
@@ -390,7 +380,8 @@ const groupReviewNotes = (store) => {
390
380
  if (!todo.text.trim()) continue;
391
381
  fileGroup(groups, todo.file).todos.push(todo);
392
382
  }
393
- for (const [key, note] of store.feedback) {
383
+ for (const key of store.feedback.keys()) {
384
+ const note = store.feedback.get(key);
394
385
  if (!note.text.trim()) continue;
395
386
  fileGroup(groups, note.file).feedback.push({ key, note });
396
387
  }
@@ -415,7 +406,8 @@ const reviewHeader = (store) => {
415
406
  };
416
407
 
417
408
  const emitReviewGroups = (lines, groups) => {
418
- for (const [file, group] of groups) {
409
+ for (const file of groups.keys()) {
410
+ const group = groups.get(file);
419
411
  lines.push(`## ${file}`);
420
412
  lines.push('');
421
413
  for (const todo of group.todos) {
@@ -448,12 +440,9 @@ const parseFeedbackHeading = (line) => {
448
440
  if (!withHead) {
449
441
  return { file, newStart, header: rest, blockId: 0 };
450
442
  }
451
- return {
452
- file,
453
- newStart,
454
- header: withHead[1],
455
- blockId: toInt(withHead[2]),
456
- };
443
+ const header = withHead[1];
444
+ const blockId = toInt(withHead[2]);
445
+ return { file, newStart, header, blockId };
457
446
  };
458
447
 
459
448
  const flushPendingFeedback = (store, pending) => {
@@ -675,19 +664,12 @@ const mergeTodos = (gitItems, todos, extraFiles = []) => {
675
664
  };
676
665
 
677
666
  module.exports = {
678
- REVIEW_DIR,
679
- TEMPLATES_FILE,
680
667
  TEMPLATE_SHOW,
681
668
  AUTOSAVE_MS,
682
- REVIEW_STATUSES,
683
- AGENT_INSTRUCTIONS,
684
- dateStamp,
685
669
  allocateReviewPath,
686
670
  listReviewNames,
687
- compareReviewNames,
688
671
  latestReviewName,
689
672
  parseFrontmatterStatus,
690
- readReviewStatus,
691
673
  resolveReviewPath,
692
674
  rankedTemplates,
693
675
  prefixTemplates,
@@ -700,7 +682,6 @@ module.exports = {
700
682
  addTodo,
701
683
  removeTodo,
702
684
  setTodoText,
703
- isDoneMark,
704
685
  checkLabel,
705
686
  feedbackKey,
706
687
  applyImportedNotes,
@@ -709,6 +690,5 @@ module.exports = {
709
690
  loadReview,
710
691
  loadTemplates,
711
692
  flushReview,
712
- makeTodoItem,
713
693
  mergeTodos,
714
694
  };