reslop 0.1.8 → 0.2.0

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 (95) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/README.md +27 -38
  3. package/bin/reslop.js +2 -2
  4. package/lib/ansi.js +6 -2
  5. package/lib/api.js +2 -2
  6. package/lib/capabilities.js +10 -14
  7. package/lib/cli.js +28 -47
  8. package/lib/clipboard.js +1 -1
  9. package/lib/deps-diff.js +712 -0
  10. package/lib/deps.js +556 -32
  11. package/lib/detect.js +1 -7
  12. package/lib/diff/align.js +7 -10
  13. package/lib/diff/diff.js +53 -0
  14. package/lib/diff/display.js +5 -4
  15. package/lib/diff/inline.js +26 -21
  16. package/lib/diff/items.js +6 -4
  17. package/lib/diff/patch.js +1 -3
  18. package/lib/diff/unit.js +392 -0
  19. package/lib/files.js +34 -8
  20. package/lib/{git/branches.js → git-branches.js} +103 -10
  21. package/lib/{git/dependencies.js → git-deps.js} +111 -38
  22. package/lib/git.js +592 -65
  23. package/lib/github.js +28 -197
  24. package/lib/gitlab.js +24 -195
  25. package/lib/highlight/bash.js +1 -3
  26. package/lib/highlight/css.js +1 -3
  27. package/lib/highlight/js.js +11 -14
  28. package/lib/highlight.js +6 -9
  29. package/lib/keys.js +22 -9
  30. package/lib/manifest.js +330 -0
  31. package/lib/remote.js +374 -0
  32. package/lib/render/branches.js +34 -66
  33. package/lib/render/chrome.js +57 -22
  34. package/lib/render/commits.js +85 -0
  35. package/lib/render/diff.js +16 -16
  36. package/lib/render/files.js +29 -37
  37. package/lib/render/frame.js +29 -13
  38. package/lib/render/notes.js +4 -3
  39. package/lib/render/primitives.js +46 -1
  40. package/lib/{render.js → render/render.js} +6 -7
  41. package/lib/render/unit.js +41 -0
  42. package/lib/review.js +750 -14
  43. package/lib/select.js +5 -10
  44. package/lib/session/accessors.js +9 -1
  45. package/lib/session/actions.js +93 -36
  46. package/lib/session/branches.js +130 -144
  47. package/lib/session/change-actions.js +206 -124
  48. package/lib/session/commit.js +217 -62
  49. package/lib/session/compose.js +158 -13
  50. package/lib/session/dispatch.js +109 -55
  51. package/lib/session/files-pane.js +145 -142
  52. package/lib/session/items.js +131 -34
  53. package/lib/session/lifecycle.js +82 -42
  54. package/lib/session/load.js +19 -33
  55. package/lib/session/navigation.js +15 -3
  56. package/lib/session/ops.js +28 -41
  57. package/lib/session/pointer.js +58 -54
  58. package/lib/session/review.js +23 -37
  59. package/lib/session/terminal.js +6 -12
  60. package/lib/session/todos.js +8 -12
  61. package/lib/session/unit.js +208 -0
  62. package/lib/session/update.js +33 -46
  63. package/lib/session/view.js +82 -11
  64. package/lib/session/watch.js +11 -19
  65. package/lib/session.js +29 -226
  66. package/lib/source.js +3 -3
  67. package/lib/update.js +40 -75
  68. package/lib/{git/proc.js → utilities.js} +75 -11
  69. package/lib/wrap.js +1 -3
  70. package/package.json +2 -2
  71. package/lib/deps/changes.js +0 -331
  72. package/lib/deps/items.js +0 -131
  73. package/lib/deps/manifest.js +0 -145
  74. package/lib/deps/merge.js +0 -120
  75. package/lib/deps/proposals.js +0 -164
  76. package/lib/deps/reports.js +0 -180
  77. package/lib/deps/rows.js +0 -177
  78. package/lib/deps/scan.js +0 -138
  79. package/lib/deps/usage.js +0 -68
  80. package/lib/deps/versions.js +0 -119
  81. package/lib/diff.js +0 -33
  82. package/lib/git/changes.js +0 -164
  83. package/lib/git/read.js +0 -253
  84. package/lib/git/run.js +0 -100
  85. package/lib/npm/commands.js +0 -100
  86. package/lib/remote/changes.js +0 -37
  87. package/lib/remote/headers.js +0 -36
  88. package/lib/remote/http.js +0 -122
  89. package/lib/remote/notes.js +0 -94
  90. package/lib/review/format.js +0 -434
  91. package/lib/review/storage.js +0 -150
  92. package/lib/review/store.js +0 -178
  93. package/lib/review/templates.js +0 -49
  94. package/lib/session/origins.js +0 -7
  95. package/lib/sys.js +0 -84
package/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  ## [Unreleased][unreleased]
4
4
 
5
+ ## [0.2.0][] - 2026-09-22
6
+
7
+ - Add a full-size file editor
8
+ - Optimize codebase and reorganize code structure
9
+ - Add pull and push on the commits list screen
10
+ - Fix disk watcher startup on macOS
11
+ - Fix Windows temp-path comparison and same-size file reload polling
12
+
13
+ ## [0.1.9][] - 2026-09-19
14
+
15
+ - Remove `r` reload hotkey and hint; auto-reload covers disk changes
16
+ - Improve branch list TUI/TUX
17
+ - Right-align the time column in the branch list
18
+ - Refresh the branch list after push, pull, and rebase
19
+ - Keep diff viewer order when staging or unstaging a block
20
+ - Add a commits list screen with commit operation
21
+ - Show staged/total rather than staged/unstaged in files list
22
+ - Show file ages in the file list right-aligned
23
+ - Sort the file list by path from the repo root
24
+ - Update file-list hotkeys and hints
25
+ - Fix dependency updates, locks and audit
26
+
5
27
  ## [0.1.8][] - 2026-09-17
6
28
 
7
29
  - Simplify session controllers, input dispatch, and loading orchestration
@@ -83,7 +105,9 @@
83
105
  - Feedback and todos that produce a repair plan for an agent
84
106
  - Intra-line highlighting in unified, mixed, and side-by-side layouts
85
107
 
86
- [unreleased]: https://github.com/tshemsedinov/reslop/compare/v0.1.8...HEAD
108
+ [unreleased]: https://github.com/tshemsedinov/reslop/compare/v0.2.0...HEAD
109
+ [0.2.0]: https://github.com/tshemsedinov/reslop/compare/v0.1.9...v0.2.0
110
+ [0.1.9]: https://github.com/tshemsedinov/reslop/compare/v0.1.8...v0.1.9
87
111
  [0.1.8]: https://github.com/tshemsedinov/reslop/compare/v0.1.7...v0.1.8
88
112
  [0.1.7]: https://github.com/tshemsedinov/reslop/compare/v0.1.6...v0.1.7
89
113
  [0.1.6]: https://github.com/tshemsedinov/reslop/compare/v0.1.5...v0.1.6
package/README.md CHANGED
@@ -1,4 +1,6 @@
1
- # 👁️ reslop — Review AI generated code and plan changes
1
+ # 👁️ reslop
2
+
3
+ ## Review AI generated code and plan changes
2
4
 
3
5
  [![ci status](https://github.com/tshemsedinov/reslop/workflows/Testing%20CI/badge.svg)](https://github.com/tshemsedinov/reslop/actions?query=workflow%3A%22Testing+CI%22+branch%3Amain)
4
6
  [![snyk](https://snyk.io/test/github/tshemsedinov/reslop/badge.svg)](https://snyk.io/test/github/tshemsedinov/reslop)
@@ -16,16 +18,19 @@ for the agent.
16
18
  Review → Plan → Repair → Verify
17
19
  ```
18
20
 
19
- - Leave feedback, issues/todos, and in-place code proposals as a repair plan.
20
- - Review uncommitted git diffs, a given commit, a GitHub PR, or a GitLab MR.
21
- - Stage, unstage, or revert each contiguous block of diff lines.
22
- - Auto-reload local diffs when files change, keeping the current screen.
23
- - Import GitHub PR and GitLab MR review comments into the local plan for AI.
24
- - Commit, amend, or fixup staged changes from the file list.
25
- - Branches: checkout, create, rebase, drop, pull and push.
26
- - Review each npm dependency once across `package.json` and the lockfile.
27
- - Propose unused removals, npm audit fixes, and outdated updates as diffs.
28
- - Code highlighting, diff compare: unified, mixed, and side-by-side layouts.
21
+ - Leave feedback, issues/todos, and in-place code proposals as a repair plan
22
+ - Review uncommitted git diffs, a given commit, a GitHub PR, or a GitLab MR
23
+ - File-scope review mode: not just diffs, with editor mode
24
+ - Stage, unstage, or revert each contiguous block of diff lines
25
+ - Auto-reload local diffs when files change, keeping the current screen
26
+ - Import GitHub PR and GitLab MR review comments into the local plan for AI
27
+ - Commits list: commit, amend, fixup, drop, pull, and push
28
+ - Branches: checkout, create, rebase, drop, pull, and push
29
+ - Review each npm dependency once across `package.json` and the lockfile
30
+ - Propose unused removals, npm audit fixes, and outdated updates as diffs
31
+ - Code highlighting and intra-line diffs: unified, mixed, and side-by-side
32
+ - Auto-update patch and minor releases; confirm a new major
33
+ - Work with git, npm and fetch in background
29
34
 
30
35
  ## Install
31
36
 
@@ -51,35 +56,19 @@ Reviews go in `.review/YYYY-MM-DD-NN.md` with frontmatter `status`:
51
56
  - `partial`: AI started; some items remain
52
57
  - `done`: all items marked `[x]`
53
58
 
54
- ## Hotkeys
55
-
56
- | Key | Action |
57
- | ------------------- | --------------------------------------------- |
58
- | `a` / `u` / `d` | Stage / unstage / drop this diff |
59
- | `c` | File list: `c` commit / `a` amend / `f` fixup |
60
- | `b` | List branches; `n` new; `r` rebase; `d` drop |
61
- | `p` / `s` | git pull/push |
62
- | `f` / `t` / `e` | Feedback / repo todo / edit added lines |
63
- | `m` | Mode: unified / mixed / side-by-side |
64
- | `⏎` | Open the selected file |
65
- | `→` / `j` | Next remaining diff / file |
66
- | `←` / `k` | Previous remaining diff / file |
67
- | `Ctrl-e` / `Ctrl-y` | Scroll one line down / up |
68
- | `Ctrl-f` / `Ctrl-b` | Scroll page down / up |
69
- | `Ctrl-d` / `Ctrl-u` | Scroll half-page down / up |
70
- | `⌫` / `Del` | Remove the selected todo |
71
- | `Esc` / `q` | Quit |
72
-
73
59
  ## Future
74
60
 
75
- - Import issues from GitHub and GitLab.
76
- - Security and code-quality audit; propose a repair plan.
77
- - Send anonymized code blocks for expert review.
78
- - Send questions and the repair plan to experts for approval.
79
- - Ask experts.
80
- - Verify the codebase.
81
- - Apply refactoring skills.
82
- - Call agents, harnesses, and IDEs to execute prepared plans.
61
+ - Import issues from GitHub and GitLab
62
+ - Search, blame, and file history in the review
63
+ - Security and code-quality audit; propose a repair plan
64
+ - Send anonymized code blocks for expert review
65
+ - Send questions and the repair plan to experts for approval
66
+ - Ask experts
67
+ - Verify the codebase
68
+ - Apply refactoring skills
69
+ - Call agents, harnesses, and IDEs to execute prepared plans
70
+ - Squash, cherry-pick, and interactive rebase
71
+ - Merge, rename, and conflict resolution
83
72
 
84
73
  ## License
85
74
 
package/bin/reslop.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- const { run, failLine } = require('../lib/cli.js');
4
+ const { run, errorMessage } = require('../lib/cli.js');
5
5
  const { LEAVE_TERM } = require('../lib/session.js');
6
6
 
7
7
  const fail = (reason) => {
@@ -10,7 +10,7 @@ const fail = (reason) => {
10
10
  } catch {
11
11
  // ignore
12
12
  }
13
- process.stderr.write(failLine(reason));
13
+ process.stderr.write(`reslop: ${errorMessage(reason)}\n`);
14
14
  process.exit(1);
15
15
  };
16
16
 
package/lib/ansi.js CHANGED
@@ -67,6 +67,7 @@ const THEME = {
67
67
  buttonFg: [0x6a, 0x6a, 0x7e],
68
68
  buttonBg: [0x2a, 0x2a, 0x40],
69
69
  buttonHotFg: [0xff, 0xff, 0xff],
70
+ currentBg: [0xa8, 0xcc, 0xb4],
70
71
  checkFg: [0xf4, 0xf4, 0xfc],
71
72
  checkBg: [0x52, 0x52, 0x74],
72
73
  checkDoneFg: [0xf0, 0xff, 0xf4],
@@ -75,6 +76,7 @@ const THEME = {
75
76
  warnFg: [0xe8, 0xc8, 0x4a],
76
77
  mutedFg: [0x7a, 0x7a, 0x8a],
77
78
  shaFg: [0x56, 0x9c, 0xd6],
79
+ shaDarkFg: [0x15, 0x4a, 0x8c],
78
80
  stagedDelLineFg: [0xa0, 0xa0, 0xa8],
79
81
  stagedDelLineBg: [0x20, 0x20, 0x26],
80
82
  stagedDelCharFg: [0xf0, 0xf0, 0xf4],
@@ -156,8 +158,10 @@ const paint = (text, fgRgb, bgRgb, color, bold) => {
156
158
  const stripAnsi = (text) => text.replace(ANSI_RE, '');
157
159
 
158
160
  const graphemes = (text) => {
159
- const parts = [];
160
- for (const { segment } of SEGMENTER.segment(text)) parts.push(segment);
161
+ const parts = new Array(text.length);
162
+ let n = 0;
163
+ for (const { segment } of SEGMENTER.segment(text)) parts[n++] = segment;
164
+ parts.length = n;
161
165
  return parts;
162
166
  };
163
167
 
package/lib/api.js CHANGED
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  const ansi = require('./ansi.js');
4
- const diff = require('./diff.js');
4
+ const diff = require('./diff/diff.js');
5
5
  const git = require('./git.js');
6
6
  const keys = require('./keys.js');
7
- const render = require('./render.js');
7
+ const render = require('./render/render.js');
8
8
  const highlight = require('./highlight.js');
9
9
  const detect = require('./detect.js');
10
10
  const github = require('./github.js');
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- const fs = require('node:fs');
4
-
5
3
  const READ_METHODS = [
6
4
  'load',
7
5
  'loadAsync',
8
6
  'loadExtras',
9
7
  'toplevel',
10
8
  'resolveRev',
9
+ 'listFiles',
10
+ 'fileText',
11
11
  ];
12
12
 
13
13
  const CHANGE_METHODS = [
@@ -17,6 +17,7 @@ const CHANGE_METHODS = [
17
17
  'revert',
18
18
  'revertFile',
19
19
  'edit',
20
+ 'writeFile',
20
21
  ];
21
22
 
22
23
  const BRANCH_METHODS = [
@@ -25,6 +26,7 @@ const BRANCH_METHODS = [
25
26
  'hasStaged',
26
27
  'lastMessage',
27
28
  'listBranches',
29
+ 'listCommits',
28
30
  'currentBranch',
29
31
  'checkout',
30
32
  'checkoutAsync',
@@ -34,6 +36,8 @@ const BRANCH_METHODS = [
34
36
  'rebaseAsync',
35
37
  'drop',
36
38
  'dropAsync',
39
+ 'dropCommit',
40
+ 'dropCommitAsync',
37
41
  'pull',
38
42
  'pullAsync',
39
43
  'push',
@@ -43,9 +47,9 @@ const BRANCH_METHODS = [
43
47
  const SOURCE_CAPS = {
44
48
  local: { read: true, changes: true, branches: true, review: true },
45
49
  commit: { read: true, changes: false, branches: false, review: true },
46
- 'github-pr': { read: true, changes: false, branches: false, review: true },
47
- 'gitlab-mr': { read: true, changes: false, branches: false, review: true },
48
- 'read-only': { read: true, changes: false, branches: false, review: true },
50
+ pr: { read: true, changes: false, branches: false, review: true },
51
+ mr: { read: true, changes: false, branches: false, review: true },
52
+ readonly: { read: true, changes: false, branches: false, review: true },
49
53
  };
50
54
 
51
55
  const withReadOnly = (caps, readOnly) => {
@@ -83,16 +87,9 @@ const sessionCapabilities = (options) => {
83
87
  }
84
88
  const repoCaps = options.repo && options.repo.capabilities;
85
89
  if (repoCaps) return withReadOnly(repoCaps, readOnly);
86
- return capabilitiesFor(readOnly ? 'read-only' : 'local');
90
+ return capabilitiesFor(readOnly ? 'readonly' : 'local');
87
91
  };
88
92
 
89
- const createReviewStorage = (io = {}) => ({
90
- readdirSync: io.readdirSync ?? fs.readdirSync,
91
- readFileSync: io.readFileSync ?? fs.readFileSync,
92
- writeFileSync: io.writeFileSync ?? fs.writeFileSync,
93
- mkdirSync: io.mkdirSync ?? fs.mkdirSync,
94
- });
95
-
96
93
  module.exports = {
97
94
  READ_METHODS,
98
95
  CHANGE_METHODS,
@@ -102,5 +99,4 @@ module.exports = {
102
99
  withReadOnly,
103
100
  attachCapabilities,
104
101
  sessionCapabilities,
105
- createReviewStorage,
106
102
  };
package/lib/cli.js CHANGED
@@ -1,8 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const fs = require('node:fs');
4
3
  const path = require('node:path');
5
4
 
5
+ const { exists } = require('metautil');
6
+
6
7
  const { createGitRepo } = require('./git.js');
7
8
  const { githubToken, loadPullRequest } = require('./github.js');
8
9
  const { gitlabToken, loadMergeRequest } = require('./gitlab.js');
@@ -17,13 +18,13 @@ const RANGE_REV = /[~^]/;
17
18
  const USAGE = 'Usage: reslop [-n] [-r] [path | commit | pr-url | mr-url]';
18
19
 
19
20
  const REMOTE_CHANGE = {
20
- 'github-pr': {
21
+ pr: {
21
22
  load: 'loadPullRequest',
22
23
  fallback: loadPullRequest,
23
24
  token: githubToken,
24
25
  target: (selected) => selected.pr,
25
26
  },
26
- 'gitlab-mr': {
27
+ mr: {
27
28
  load: 'loadMergeRequest',
28
29
  fallback: loadMergeRequest,
29
30
  token: gitlabToken,
@@ -38,16 +39,8 @@ const errorMessage = (reason) => {
38
39
  return `${reason}`;
39
40
  };
40
41
 
41
- const failLine = (reason) => `reslop: ${errorMessage(reason)}\n`;
42
-
43
42
  const fail = (stderr, reason) => {
44
- stderr.write(failLine(reason));
45
- return 1;
46
- };
47
-
48
- const failUsage = (stderr, reason) => {
49
- fail(stderr, reason);
50
- stderr.write(`${USAGE}\n`);
43
+ stderr.write(`reslop: ${errorMessage(reason)}\n`);
51
44
  return 1;
52
45
  };
53
46
 
@@ -79,19 +72,10 @@ const looksLikeRev = (spec) => {
79
72
  return RANGE_REV.test(spec);
80
73
  };
81
74
 
82
- const pathExists = (cwd, rel, statSync) => {
83
- try {
84
- statSync(path.resolve(cwd, rel));
85
- return true;
86
- } catch {
87
- return false;
88
- }
89
- };
90
-
91
- const resolveScope = (cwd, paths, repo, statSync = fs.statSync) => {
75
+ const resolveScope = async (cwd, paths, repo) => {
92
76
  if (!paths.length) return { rev: null, paths };
93
77
  const first = paths[0];
94
- if (pathExists(cwd, first, statSync)) {
78
+ if (await exists(path.resolve(cwd, first))) {
95
79
  return { rev: null, paths };
96
80
  }
97
81
  const resolved = repo.resolveRev(cwd, first);
@@ -107,14 +91,11 @@ const resolveScope = (cwd, paths, repo, statSync = fs.statSync) => {
107
91
  return { rev: null, paths };
108
92
  };
109
93
 
110
- const isColorEnabled = (stdout, env) => stdout.isTTY === true && !env.NO_COLOR;
111
-
112
94
  const sessionOptions = (proc, deps, args, env, extra) => ({
113
95
  cwd: deps.cwd ?? proc.cwd(),
114
96
  stdin: proc.stdin,
115
97
  stdout: proc.stdout,
116
- color: isColorEnabled(proc.stdout, env),
117
- getSize: deps.getSize,
98
+ color: proc.stdout.isTTY === true && !env.NO_COLOR,
118
99
  startPane: 'files',
119
100
  newReview: args.newReview,
120
101
  readOnly: args.readOnly,
@@ -122,22 +103,21 @@ const sessionOptions = (proc, deps, args, env, extra) => ({
122
103
  ...extra,
123
104
  });
124
105
 
125
- const openLocalSession = (proc, deps, args, selected, env) => {
106
+ const openLocalSession = async (proc, deps, args, selected, env) => {
126
107
  const cwd = deps.cwd ?? proc.cwd();
127
108
  const rawRepo = deps.repo ?? createGitRepo();
128
- const statSync = deps.statSync ?? fs.statSync;
129
- const scope = resolveScope(cwd, selected.paths, rawRepo, statSync);
109
+ const scope = await resolveScope(cwd, selected.paths, rawRepo);
130
110
  const kind = scope.rev ? 'commit' : 'local';
131
111
  const capabilities = capabilitiesFor(kind, { readOnly: args.readOnly });
132
112
  const repo = attachCapabilities(rawRepo, capabilities);
133
- const session = new Session(
134
- sessionOptions(proc, deps, args, env, {
135
- repo,
136
- paths: scope.paths,
137
- rev: scope.rev,
138
- capabilities,
139
- }),
140
- );
113
+ const extra = {
114
+ repo,
115
+ paths: scope.paths,
116
+ rev: scope.rev,
117
+ capabilities,
118
+ };
119
+ const options = sessionOptions(proc, deps, args, env, extra);
120
+ const session = new Session(options);
141
121
  session.ensureRepo();
142
122
  return session;
143
123
  };
@@ -168,13 +148,12 @@ const openRemoteSession = (proc, deps, args, selected, env) => {
168
148
  const capabilities = capabilitiesFor(selected.kind, {
169
149
  readOnly: args.readOnly,
170
150
  });
171
- return new Session(
172
- sessionOptions(proc, deps, args, env, {
173
- repo: remoteRepo(deps, selected, env, cwd),
174
- paths: selected.paths,
175
- capabilities,
176
- }),
177
- );
151
+ const repo = remoteRepo(deps, selected, env, cwd);
152
+ const extra = { repo, paths: selected.paths, capabilities };
153
+ const options = sessionOptions(proc, deps, args, env, extra);
154
+ const session = new Session(options);
155
+ session.ensureRepo();
156
+ return session;
178
157
  };
179
158
 
180
159
  const loadSession = async (proc, deps, args) => {
@@ -200,7 +179,9 @@ const run = async (proc, deps = {}) => {
200
179
  try {
201
180
  args = parseArgv(proc.argv.slice(2));
202
181
  } catch (error) {
203
- return failUsage(stderr, error);
182
+ fail(stderr, error);
183
+ stderr.write(`${USAGE}\n`);
184
+ return 1;
204
185
  }
205
186
  let session;
206
187
  try {
@@ -224,7 +205,7 @@ const run = async (proc, deps = {}) => {
224
205
  };
225
206
 
226
207
  module.exports = {
227
- failLine,
208
+ errorMessage,
228
209
  parseArgv,
229
210
  resolveScope,
230
211
  run,
package/lib/clipboard.js CHANGED
@@ -3,7 +3,7 @@
3
3
  const { spawnSync } = require('node:child_process');
4
4
 
5
5
  const { ESC } = require('./ansi.js');
6
- const { clipTools, spawnBase } = require('./sys.js');
6
+ const { clipTools, spawnBase } = require('./utilities.js');
7
7
 
8
8
  const CLIP_TIMEOUT_MS = 1000;
9
9