reslop 0.1.0 → 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/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 = Object.assign(Object.create(null), {
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+)$/;
@@ -22,11 +18,11 @@ const FEED_SUFFIX = /^(.*) - (.+):(\d+):(\d+):(\d+)$/;
22
18
  const CHECK_ITEM = /^- \[([ xX])\] (.*)$/;
23
19
  const FILE_QUOTE = /^> (.+)$/;
24
20
  const STATUS_LINE = /^status:\s*(\S+)\s*$/;
25
- const GIT_ORIGINS = ['unstaged', 'staged', 'untracked', 'commit'];
26
- const SECTION_LINE = Object.assign(Object.create(null), {
21
+ const GIT_ORIGINS = ['unstaged', 'staged', 'untracked', 'commit', 'pr'];
22
+ const SECTION_LINE = {
27
23
  '### Todo': 'todo',
28
24
  '### Feedback': 'feedback',
29
- });
25
+ };
30
26
 
31
27
  const AGENT_INSTRUCTIONS = [
32
28
  'Execute reviews with `status: ready` or `status: partial`.',
@@ -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) => {
@@ -294,6 +290,32 @@ const feedbackKey = (note) => {
294
290
  return `${file}:${oldStart}:${newStart}:${block}`;
295
291
  };
296
292
 
293
+ const applyImportedNotes = (store, imported) => {
294
+ if (!store || !imported) return;
295
+ const feedbacks = imported.feedback ?? [];
296
+ for (const note of feedbacks) {
297
+ const key = feedbackKey(note);
298
+ const prev = store.feedback.get(key);
299
+ const text = prev ? `${prev.text}\n\n${note.text}` : note.text;
300
+ const done = prev ? prev.done && !!note.done : !!note.done;
301
+ setFeedback(store, key, {
302
+ file: note.file,
303
+ oldStart: note.oldStart ?? 0,
304
+ newStart: note.newStart ?? 0,
305
+ blockId: note.blockId ?? 0,
306
+ origin: note.origin ?? '',
307
+ header: note.header ?? '',
308
+ text,
309
+ done,
310
+ });
311
+ }
312
+ const todos = imported.todos ?? [];
313
+ for (const todo of todos) {
314
+ const added = addTodo(store, todo.file || '', todo.text ?? '');
315
+ if (todo.done) added.done = true;
316
+ }
317
+ };
318
+
297
319
  const storeFeedback = (store, note, key) => {
298
320
  const next = {
299
321
  file: note.file,
@@ -328,25 +350,19 @@ const parseLoc = (raw) => {
328
350
  fileParts = fileParts.slice(1);
329
351
  }
330
352
  if (!fileParts.length) return null;
331
- return {
332
- origin,
333
- file: fileParts.join(':'),
334
- oldStart,
335
- newStart,
336
- blockId,
337
- };
353
+ const file = fileParts.join(':');
354
+ return { origin, file, oldStart, newStart, blockId };
338
355
  };
339
356
 
340
357
  const parseFeedSuffix = (textItem) => {
341
358
  const match = FEED_SUFFIX.exec(textItem);
342
359
  if (!match) return null;
343
- return {
344
- text: match[1],
345
- file: match[2],
346
- oldStart: toInt(match[3]),
347
- newStart: toInt(match[4]),
348
- blockId: toInt(match[5]),
349
- };
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 };
350
366
  };
351
367
 
352
368
  const fileGroup = (groups, file) => {
@@ -364,7 +380,8 @@ const groupReviewNotes = (store) => {
364
380
  if (!todo.text.trim()) continue;
365
381
  fileGroup(groups, todo.file).todos.push(todo);
366
382
  }
367
- for (const [key, note] of store.feedback) {
383
+ for (const key of store.feedback.keys()) {
384
+ const note = store.feedback.get(key);
368
385
  if (!note.text.trim()) continue;
369
386
  fileGroup(groups, note.file).feedback.push({ key, note });
370
387
  }
@@ -389,7 +406,8 @@ const reviewHeader = (store) => {
389
406
  };
390
407
 
391
408
  const emitReviewGroups = (lines, groups) => {
392
- for (const [file, group] of groups) {
409
+ for (const file of groups.keys()) {
410
+ const group = groups.get(file);
393
411
  lines.push(`## ${file}`);
394
412
  lines.push('');
395
413
  for (const todo of group.todos) {
@@ -422,12 +440,9 @@ const parseFeedbackHeading = (line) => {
422
440
  if (!withHead) {
423
441
  return { file, newStart, header: rest, blockId: 0 };
424
442
  }
425
- return {
426
- file,
427
- newStart,
428
- header: withHead[1],
429
- blockId: toInt(withHead[2]),
430
- };
443
+ const header = withHead[1];
444
+ const blockId = toInt(withHead[2]);
445
+ return { file, newStart, header, blockId };
431
446
  };
432
447
 
433
448
  const flushPendingFeedback = (store, pending) => {
@@ -649,19 +664,12 @@ const mergeTodos = (gitItems, todos, extraFiles = []) => {
649
664
  };
650
665
 
651
666
  module.exports = {
652
- REVIEW_DIR,
653
- TEMPLATES_FILE,
654
667
  TEMPLATE_SHOW,
655
668
  AUTOSAVE_MS,
656
- REVIEW_STATUSES,
657
- AGENT_INSTRUCTIONS,
658
- dateStamp,
659
669
  allocateReviewPath,
660
670
  listReviewNames,
661
- compareReviewNames,
662
671
  latestReviewName,
663
672
  parseFrontmatterStatus,
664
- readReviewStatus,
665
673
  resolveReviewPath,
666
674
  rankedTemplates,
667
675
  prefixTemplates,
@@ -674,14 +682,13 @@ module.exports = {
674
682
  addTodo,
675
683
  removeTodo,
676
684
  setTodoText,
677
- isDoneMark,
678
685
  checkLabel,
679
686
  feedbackKey,
687
+ applyImportedNotes,
680
688
  serializeReview,
681
689
  parseReview,
682
690
  loadReview,
683
691
  loadTemplates,
684
692
  flushReview,
685
- makeTodoItem,
686
693
  mergeTodos,
687
694
  };
package/lib/select.js CHANGED
@@ -2,17 +2,9 @@
2
2
 
3
3
  const ansi = require('./ansi.js');
4
4
 
5
- const {
6
- ESC,
7
- RESET,
8
- THEME,
9
- fg: fgSeq,
10
- bg: bgSeq,
11
- seq,
12
- stripAnsi,
13
- graphemes,
14
- graphemeWidth,
15
- } = ansi;
5
+ const { ESC, RESET, THEME } = ansi;
6
+ const { fg: fgSeq, bg: bgSeq, seq } = ansi;
7
+ const { stripAnsi, graphemes, graphemeWidth } = ansi;
16
8
 
17
9
  const CSI_FINAL = /[@-~]/;
18
10
  const SGR_RESET = 0;
@@ -28,12 +20,12 @@ const isRange = (sel) => {
28
20
  };
29
21
 
30
22
  const ordered = (sel) => {
31
- const a = sel.start;
32
- const b = sel.end;
33
- if (a.y < b.y) return [a, b];
34
- if (a.y > b.y) return [b, a];
35
- if (a.x <= b.x) return [a, b];
36
- return [b, a];
23
+ const start = sel.start;
24
+ const end = sel.end;
25
+ if (start.y < end.y) return { first: start, last: end };
26
+ if (start.y > end.y) return { first: end, last: start };
27
+ if (start.x <= end.x) return { first: start, last: end };
28
+ return { first: end, last: start };
37
29
  };
38
30
 
39
31
  const slicePlain = (plain, fromCol, toCol) => {
@@ -51,25 +43,25 @@ const slicePlain = (plain, fromCol, toCol) => {
51
43
  };
52
44
 
53
45
  const lineRange = (rowIndex, sel) => {
54
- const [a, b] = ordered(sel);
46
+ const { first, last } = ordered(sel);
55
47
  const y = rowIndex + 1;
56
- if (y < a.y || y > b.y) return null;
48
+ if (y < first.y || y > last.y) return null;
57
49
  let from = 0;
58
- let to = 1e9;
59
- if (y === a.y) from = a.x - 1;
60
- if (y === b.y) to = b.x;
50
+ let to = Infinity;
51
+ if (y === first.y) from = first.x - 1;
52
+ if (y === last.y) to = last.x;
61
53
  if (to <= from) return null;
62
54
  return { from, to };
63
55
  };
64
56
 
65
57
  const extractText = (rows, sel) => {
66
58
  if (!isRange(sel) || !rows || !rows.length) return '';
67
- const [a, b] = ordered(sel);
59
+ const { first, last } = ordered(sel);
68
60
  const lines = [];
69
- for (let r = a.y - 1; r <= b.y - 1; r++) {
70
- const range = lineRange(r, sel);
61
+ for (let row = first.y - 1; row <= last.y - 1; row++) {
62
+ const range = lineRange(row, sel);
71
63
  if (!range) continue;
72
- const plain = stripAnsi(rows[r] ?? '');
64
+ const plain = stripAnsi(rows[row] ?? '');
73
65
  const chunk = slicePlain(plain, range.from, range.to);
74
66
  lines.push(chunk.replace(/\s+$/g, ''));
75
67
  }
@@ -96,7 +88,7 @@ const applyRgb = (state, field, params, i) => {
96
88
  return 5;
97
89
  };
98
90
 
99
- const SGR = Object.assign(Object.create(null), {
91
+ const SGR = {
100
92
  [SGR_RESET]: (state) => {
101
93
  state.fg = null;
102
94
  state.bg = null;
@@ -112,7 +104,7 @@ const SGR = Object.assign(Object.create(null), {
112
104
  state.bg = null;
113
105
  return 1;
114
106
  },
115
- });
107
+ };
116
108
 
117
109
  const applySgr = (params, state) => {
118
110
  let i = 0;
@@ -210,9 +202,7 @@ const overlayRows = (rows, sel) => {
210
202
 
211
203
  module.exports = {
212
204
  isRange,
213
- ordered,
214
205
  extractText,
215
- overlayRows,
216
206
  overlayRange,
217
- slicePlain,
207
+ overlayRows,
218
208
  };