reslop 0.2.6 → 0.2.7

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +11 -1
  2. package/lib/ansi.js +42 -2
  3. package/lib/api.js +1 -1
  4. package/lib/cli.js +21 -1
  5. package/lib/dashboard/model.js +28 -3
  6. package/lib/dashboard/npm.js +116 -19
  7. package/lib/files.js +13 -7
  8. package/lib/find.js +61 -0
  9. package/lib/git-deps.js +1 -0
  10. package/lib/git-worktree.js +22 -4
  11. package/lib/keys.js +26 -1
  12. package/lib/render/chrome.js +257 -79
  13. package/lib/render/dash-blocks.js +297 -46
  14. package/lib/render/dash-table.js +6 -1
  15. package/lib/render/files.js +23 -4
  16. package/lib/render/frame.js +4 -0
  17. package/lib/render/notes.js +13 -2
  18. package/lib/render/packages.js +93 -0
  19. package/lib/render/primitives.js +13 -6
  20. package/lib/render/repos.js +226 -0
  21. package/lib/render/search.js +25 -0
  22. package/lib/render/tiles.js +107 -12
  23. package/lib/review.js +5 -4
  24. package/lib/session/actions.js +136 -30
  25. package/lib/session/branches.js +3 -5
  26. package/lib/session/commit.js +31 -4
  27. package/lib/session/compose.js +19 -4
  28. package/lib/session/dashboard.js +160 -15
  29. package/lib/session/dispatch.js +38 -4
  30. package/lib/session/files-pane.js +17 -2
  31. package/lib/session/find.js +94 -0
  32. package/lib/session/lifecycle.js +27 -0
  33. package/lib/session/navigation.js +2 -0
  34. package/lib/session/packages.js +143 -0
  35. package/lib/session/pointer.js +14 -0
  36. package/lib/session/tasks.js +3 -3
  37. package/lib/session/view.js +15 -2
  38. package/lib/session/watch.js +5 -0
  39. package/lib/session/workspace.js +455 -0
  40. package/lib/session.js +14 -0
  41. package/lib/workspace.js +166 -0
  42. package/package.json +1 -1
package/lib/session.js CHANGED
@@ -36,6 +36,8 @@ const branches = require('./session/branches.js');
36
36
  const { BranchController } = branches;
37
37
  const npm = require('./session/npm.js');
38
38
  const { NpmController } = npm;
39
+ const packages = require('./session/packages.js');
40
+ const { PackageController } = packages;
39
41
  const accessors = require('./session/accessors.js');
40
42
  const { bindAccessors, bindGetters } = accessors;
41
43
  const view = require('./session/view.js');
@@ -46,6 +48,8 @@ const dispatch = require('./session/dispatch.js');
46
48
  const { dispatchAction, confirmChoice, handleEvent } = dispatch;
47
49
  const dashboard = require('./session/dashboard.js');
48
50
  const { Dashboard } = dashboard;
51
+ const workspace = require('./session/workspace.js');
52
+ const { Workspace } = workspace;
49
53
 
50
54
  const PROGRESS_MS = 160;
51
55
 
@@ -98,6 +102,8 @@ const assignOptions = (session, options) => {
98
102
  session.auditMap = options.auditMap;
99
103
  session.updateOpts = options.update;
100
104
  session.dashboardHome = options.startPane === 'dashboard';
105
+ session.workspaceRoot = options.workspace ? options.workspace.root : '';
106
+ session.workspaceInfo = options.workspace ?? null;
101
107
  };
102
108
 
103
109
  const assignParts = (session, options) => {
@@ -115,7 +121,9 @@ const assignParts = (session, options) => {
115
121
  session.commits = new CommitController(session);
116
122
  session.gitBranches = new BranchController(session);
117
123
  session.npm = new NpmController(session);
124
+ session.packages = new PackageController(session);
118
125
  session.dashboard = new Dashboard(session);
126
+ session.workspace = new Workspace(session);
119
127
  session.lifecycle = new Lifecycle(session);
120
128
  };
121
129
 
@@ -160,6 +168,7 @@ class Session {
160
168
  this.commits.reset();
161
169
  this.npm.reset();
162
170
  this.dashboard.reset();
171
+ this.workspace.reset();
163
172
  this.top = this.cwd;
164
173
  this.branch = '';
165
174
  this.status = '';
@@ -234,6 +243,7 @@ class Session {
234
243
  this.term.paint(frame, size, composing ? frame.cursor : null);
235
244
  this.scheduleAutosave();
236
245
  this.dashboard.afterDraw();
246
+ this.workspace.afterDraw();
237
247
  }
238
248
 
239
249
  scheduleAutosave() {
@@ -321,6 +331,7 @@ class Session {
321
331
  if (result.kind === 'commit') this.commits.finishGitCommit();
322
332
  if (result.kind === 'branch') this.gitBranches.finishCreateBranch();
323
333
  if (result.kind === 'npm') this.npm.finishEdit();
334
+ if (result.kind === 'package') this.packages.finishAdd();
324
335
  }
325
336
 
326
337
  selectTemplate(index) {
@@ -385,6 +396,7 @@ class Session {
385
396
  this.uiOpen = true;
386
397
  this.term.enter();
387
398
  try {
399
+ if (this.nav.pane === 'repos') this.workspace.start();
388
400
  this.openLoad();
389
401
  this.openUpdate();
390
402
  this.draw();
@@ -412,6 +424,7 @@ class Session {
412
424
  settled = true;
413
425
  this.lifecycle.stopWatch();
414
426
  this.dashboard.stop();
427
+ this.workspace.stop();
415
428
  this.term.stopListening();
416
429
  this.progress.clear();
417
430
  this.term.clearTimers();
@@ -431,6 +444,7 @@ class Session {
431
444
  this.term.startListening({ onData, onResize: () => this.draw() });
432
445
  this.lifecycle.startWatch();
433
446
  if (this.dashboardHome) this.dashboard.start();
447
+ if (this.nav.pane === 'repos') this.workspace.start();
434
448
  });
435
449
  }
436
450
  }
@@ -0,0 +1,166 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+
6
+ const files = require('./files.js');
7
+ const { REVIEW_DIR } = files;
8
+ const review = require('./review.js');
9
+ const { listReviewNames, latestReviewName, loadReview, noteCounts } = review;
10
+ const worktree = require('./git-worktree.js');
11
+ const { runGitAsync, countUntrackedAdded } = worktree;
12
+ const model = require('./dashboard/model.js');
13
+ const { runName } = model;
14
+ const runs = require('./runs.js');
15
+ const { readRuns } = runs;
16
+
17
+ const emptyDiff = () => ({
18
+ stagedAdded: 0,
19
+ unstagedAdded: 0,
20
+ stagedRemoved: 0,
21
+ unstagedRemoved: 0,
22
+ });
23
+
24
+ const isRepoDir = (dir) => {
25
+ try {
26
+ fs.accessSync(path.join(dir, '.git'));
27
+ return true;
28
+ } catch {
29
+ return false;
30
+ }
31
+ };
32
+
33
+ const listChildRepos = (dir) => {
34
+ let entries;
35
+ try {
36
+ entries = fs.readdirSync(dir, { withFileTypes: true });
37
+ } catch {
38
+ return [];
39
+ }
40
+ const repos = [];
41
+ for (const entry of entries) {
42
+ if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
43
+ const full = path.join(dir, entry.name);
44
+ if (!isRepoDir(full)) continue;
45
+ repos.push({ name: entry.name, dir: full });
46
+ }
47
+ repos.sort((left, right) => left.name.localeCompare(right.name, 'en'));
48
+ return repos;
49
+ };
50
+
51
+ const workspaceAt = (dir) => {
52
+ let stat;
53
+ try {
54
+ stat = fs.statSync(dir);
55
+ } catch {
56
+ return null;
57
+ }
58
+ if (!stat.isDirectory() || isRepoDir(dir)) return null;
59
+ const repos = listChildRepos(dir);
60
+ if (!repos.length) return null;
61
+ return { root: path.resolve(dir), repos };
62
+ };
63
+
64
+ const workspaceFromArgs = (cwd, paths) => {
65
+ if (paths.length > 1) return null;
66
+ const dir = paths.length ? path.resolve(cwd, paths[0]) : cwd;
67
+ if (paths.length) {
68
+ try {
69
+ if (!fs.statSync(dir).isDirectory()) return null;
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+ return workspaceAt(dir);
75
+ };
76
+
77
+ const sumNumstat = (text) => {
78
+ let added = 0;
79
+ let removed = 0;
80
+ for (const line of `${text ?? ''}`.split('\n')) {
81
+ if (!line) continue;
82
+ const parts = line.split('\t');
83
+ const add = Number(parts[0]);
84
+ const del = Number(parts[1]);
85
+ if (Number.isFinite(add)) added += add;
86
+ if (Number.isFinite(del)) removed += del;
87
+ }
88
+ return { added, removed };
89
+ };
90
+
91
+ const readNumstat = async (dir, cached) => {
92
+ const args = cached
93
+ ? ['diff', '--cached', '--numstat']
94
+ : ['diff', '--numstat'];
95
+ try {
96
+ const result = await runGitAsync(args, dir);
97
+ if (result.status !== 0) return { added: 0, removed: 0 };
98
+ return sumNumstat(result.stdout);
99
+ } catch {
100
+ return { added: 0, removed: 0 };
101
+ }
102
+ };
103
+
104
+ const readDiffStat = async (dir) => {
105
+ const staged = await readNumstat(dir, true);
106
+ const open = await readNumstat(dir, false);
107
+ const untracked = await countUntrackedAdded(dir);
108
+ return {
109
+ stagedAdded: staged.added,
110
+ unstagedAdded: open.added + untracked,
111
+ stagedRemoved: staged.removed,
112
+ unstagedRemoved: open.removed,
113
+ };
114
+ };
115
+
116
+ const countCommits = async (dir) => {
117
+ try {
118
+ const result = await runGitAsync(['rev-list', '--count', 'HEAD'], dir);
119
+ if (result.status !== 0) return 0;
120
+ const value = parseInt(result.stdout.trim(), 10);
121
+ return Number.isFinite(value) ? value : 0;
122
+ } catch {
123
+ return 0;
124
+ }
125
+ };
126
+
127
+ const taskStats = (dir) => {
128
+ const latest = latestReviewName(listReviewNames(dir));
129
+ if (!latest) return { done: 0, total: 0 };
130
+ try {
131
+ const file = path.join(dir, REVIEW_DIR, latest);
132
+ const counts = noteCounts(loadReview(file, []));
133
+ return { done: counts.tasksDone, total: counts.tasks };
134
+ } catch {
135
+ return { done: 0, total: 0 };
136
+ }
137
+ };
138
+
139
+ const pickRun = (dir) => {
140
+ const list = readRuns(dir);
141
+ const live = list.find((row) => row.status === 'running');
142
+ const record = live ?? list[0] ?? null;
143
+ if (!record) return null;
144
+ const progress = record.progress ?? {};
145
+ return {
146
+ name: runName(record),
147
+ status: record.status,
148
+ done: progress.done ?? 0,
149
+ failed: progress.failed ?? 0,
150
+ expected: progress.expected ?? 0,
151
+ result: record.result ?? null,
152
+ };
153
+ };
154
+
155
+ module.exports = {
156
+ emptyDiff,
157
+ isRepoDir,
158
+ listChildRepos,
159
+ workspaceAt,
160
+ workspaceFromArgs,
161
+ sumNumstat,
162
+ readDiffStat,
163
+ countCommits,
164
+ taskStats,
165
+ pickRun,
166
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reslop",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Interactive code review and repair planning for AI-assisted changes",
5
5
  "keywords": [
6
6
  "code-review",