reslop 0.1.4 → 0.1.6
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/CHANGELOG.md +19 -1
- package/README.md +19 -20
- package/lib/cli.js +20 -16
- package/lib/detect.js +2 -40
- package/lib/diff.js +71 -2
- package/lib/files.js +5 -1
- package/lib/git.js +322 -12
- package/lib/highlight/bash.js +228 -0
- package/lib/highlight/core.js +194 -0
- package/lib/highlight/css.js +76 -0
- package/lib/highlight/csv.js +66 -0
- package/lib/highlight/dart.js +261 -0
- package/lib/highlight/dot.js +54 -0
- package/lib/highlight/html.js +90 -0
- package/lib/highlight/js.js +571 -0
- package/lib/highlight/json.js +54 -0
- package/lib/highlight/log.js +60 -0
- package/lib/highlight/txt.js +41 -0
- package/lib/highlight.js +32 -1299
- package/lib/keys.js +98 -26
- package/lib/render.js +574 -84
- package/lib/review.js +145 -3
- package/lib/select.js +7 -12
- package/lib/session.js +759 -50
- package/lib/sys.js +4 -0
- package/lib/update.js +302 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased][unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.1.6][] - 2026-09-15
|
|
6
|
+
|
|
7
|
+
- Auto updates for patch/minor, y/n per new major
|
|
8
|
+
- Lists branches, new branch, git pull, git push
|
|
9
|
+
- Show TUI progress for long background operations
|
|
10
|
+
- Fix Windows spawn EINVAL when running `npm.cmd`
|
|
11
|
+
- Highlight Dart diffs; refactor syntax highlighting
|
|
12
|
+
- Multiple TUI/TUX improvements and fixes
|
|
13
|
+
|
|
14
|
+
## [0.1.5][] - 2026-09-14
|
|
15
|
+
|
|
16
|
+
- Added `r` to reload diffs from disk
|
|
17
|
+
- Added `e` to edit added lines in place
|
|
18
|
+
- Added `d` on quit to discard a new review file
|
|
19
|
+
- Added `c` to commit, amend, or fixup staged changes
|
|
20
|
+
- Reassign hotkeys to free needed keys for new operations
|
|
21
|
+
|
|
5
22
|
## [0.1.4][] - 2026-09-13
|
|
6
23
|
|
|
7
24
|
- Support a GitLab MR by URL and import review comments
|
|
@@ -41,7 +58,8 @@
|
|
|
41
58
|
- Feedback and todos that produce a repair plan for an agent
|
|
42
59
|
- Intra-line highlighting in unified, mixed, and side-by-side layouts
|
|
43
60
|
|
|
44
|
-
[unreleased]: https://github.com/tshemsedinov/reslop/compare/v0.1.
|
|
61
|
+
[unreleased]: https://github.com/tshemsedinov/reslop/compare/v0.1.5...HEAD
|
|
62
|
+
[0.1.5]: https://github.com/tshemsedinov/reslop/compare/v0.1.4...v0.1.5
|
|
45
63
|
[0.1.4]: https://github.com/tshemsedinov/reslop/compare/v0.1.3...v0.1.4
|
|
46
64
|
[0.1.3]: https://github.com/tshemsedinov/reslop/compare/v0.1.2...v0.1.3
|
|
47
65
|
[0.1.2]: https://github.com/tshemsedinov/reslop/compare/v0.1.1...v0.1.2
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Review → Plan → Repair → Verify
|
|
|
18
18
|
|
|
19
19
|
- Review uncommitted git diffs, a given commit, a GitHub PR, or a GitLab MR.
|
|
20
20
|
- Stage, unstage, or revert each contiguous block of diff lines.
|
|
21
|
-
- Leave feedback and
|
|
21
|
+
- Leave feedback, todos, and in-place code proposals that become a repair plan.
|
|
22
22
|
- Import GitHub PR and GitLab MR review comments into the local plan for AI.
|
|
23
23
|
- Review each npm dependency once across `package.json` and the lockfile.
|
|
24
24
|
- Propose unused removals, npm audit fixes, and outdated updates as diffs.
|
|
@@ -38,7 +38,7 @@ Works on Linux, macOS, and Windows. On Windows use Windows Terminal.
|
|
|
38
38
|
- `reslop 7ac260c` show that commit (read-only)
|
|
39
39
|
- `reslop https://github.com/metarhia/metacom/pull/555` GitHub/GitLab PR/MR
|
|
40
40
|
- `reslop -n` start a new review even if the latest is still editing
|
|
41
|
-
- `reslop -r` read-only
|
|
41
|
+
- `reslop -r` read-only mode
|
|
42
42
|
|
|
43
43
|
Reviews go in `.review/YYYY-MM-DD-NN.md` with frontmatter `status`:
|
|
44
44
|
|
|
@@ -49,24 +49,23 @@ Reviews go in `.review/YYYY-MM-DD-NN.md` with frontmatter `status`:
|
|
|
49
49
|
|
|
50
50
|
## Hotkeys
|
|
51
51
|
|
|
52
|
-
| Key | Action
|
|
53
|
-
| ------------------- |
|
|
54
|
-
| `a`
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `t`
|
|
59
|
-
| `
|
|
60
|
-
| `m` |
|
|
61
|
-
| `⏎` | Open the selected file
|
|
62
|
-
| `
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
| `Ctrl-
|
|
66
|
-
| `Ctrl-
|
|
67
|
-
|
|
|
68
|
-
|
|
|
69
|
-
| `q` | Quit |
|
|
52
|
+
| Key | Action |
|
|
53
|
+
| ------------------- | ----------------------------------------------- |
|
|
54
|
+
| `a` / `u` / `d` | Stage / unstage / drop this diff |
|
|
55
|
+
| `c` | Commit: then `c` commit / `a` amend / `f` fixup |
|
|
56
|
+
| `b` | List branches; `b, n` new branch |
|
|
57
|
+
| `p` / `s` | git pull/push |
|
|
58
|
+
| `f` / `t` / `e` | Add feedback / todo / edit diff text |
|
|
59
|
+
| `r` | Reload diffs from disk |
|
|
60
|
+
| `m` | Mode: unified / mixed / side-by-side |
|
|
61
|
+
| `⏎` | Open the selected file |
|
|
62
|
+
| `→` / `j` | Next remaining diff / next file |
|
|
63
|
+
| `←` / `k` | Previous remaining diff / previous file |
|
|
64
|
+
| `Ctrl-e` / `Ctrl-y` | Scroll one line down / up |
|
|
65
|
+
| `Ctrl-f` / `Ctrl-b` | Scroll page down / up |
|
|
66
|
+
| `Ctrl-d` / `Ctrl-u` | Scroll half-page down / up |
|
|
67
|
+
| `⌫` / `Del` | Remove the selected todo |
|
|
68
|
+
| `Esc` / `q` | Quit |
|
|
70
69
|
|
|
71
70
|
## License
|
|
72
71
|
|
package/lib/cli.js
CHANGED
|
@@ -143,29 +143,33 @@ const openLocalSession = (proc, deps, args, selected, env) => {
|
|
|
143
143
|
return session;
|
|
144
144
|
};
|
|
145
145
|
|
|
146
|
-
const
|
|
146
|
+
const remoteRepo = (deps, selected, env, cwd) => {
|
|
147
|
+
if (deps.repo) return deps.repo;
|
|
147
148
|
const remote = REMOTE_CHANGE[selected.kind];
|
|
148
|
-
const cwd = deps.cwd ?? proc.cwd();
|
|
149
149
|
const loader = deps[remote.load] ?? remote.fallback;
|
|
150
150
|
const token = remote.token(env);
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
151
|
+
const loadRemote = async () => {
|
|
152
|
+
const loaded = await loader(remote.target(selected), {
|
|
153
|
+
cwd,
|
|
154
|
+
paths: selected.paths,
|
|
155
|
+
token,
|
|
156
|
+
fetch: deps.fetch,
|
|
157
|
+
});
|
|
158
|
+
const items = loaded.items ?? [];
|
|
159
|
+
return { ...loaded, items: [...items] };
|
|
160
|
+
};
|
|
161
|
+
const repo = createLoadedSource({ items: [] });
|
|
162
|
+
repo.loadAsync = loadRemote;
|
|
163
|
+
return repo;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const openRemoteSession = (proc, deps, args, selected, env) => {
|
|
167
|
+
const cwd = deps.cwd ?? proc.cwd();
|
|
160
168
|
return startSession(
|
|
161
169
|
sessionOptions(proc, deps, args, env, {
|
|
162
|
-
repo,
|
|
170
|
+
repo: remoteRepo(deps, selected, env, cwd),
|
|
163
171
|
paths: selected.paths,
|
|
164
|
-
repoName: repository,
|
|
165
|
-
sourceLabel: loaded.sourceLabel,
|
|
166
|
-
change,
|
|
167
172
|
}),
|
|
168
|
-
{ load: true },
|
|
169
173
|
);
|
|
170
174
|
};
|
|
171
175
|
|
package/lib/detect.js
CHANGED
|
@@ -2,40 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
md: 'md',
|
|
7
|
-
markdown: 'md',
|
|
8
|
-
js: 'js',
|
|
9
|
-
javascript: 'js',
|
|
10
|
-
mjs: 'mjs',
|
|
11
|
-
cjs: 'js',
|
|
12
|
-
ts: 'ts',
|
|
13
|
-
typescript: 'ts',
|
|
14
|
-
jsx: 'jsx',
|
|
15
|
-
tsx: 'tsx',
|
|
16
|
-
json: 'json',
|
|
17
|
-
csv: 'csv',
|
|
18
|
-
html: 'html',
|
|
19
|
-
htm: 'html',
|
|
20
|
-
css: 'css',
|
|
21
|
-
sh: 'bash',
|
|
22
|
-
bash: 'bash',
|
|
23
|
-
shell: 'bash',
|
|
24
|
-
zsh: 'bash',
|
|
25
|
-
txt: 'txt',
|
|
26
|
-
text: 'txt',
|
|
27
|
-
plain: 'txt',
|
|
28
|
-
log: 'log',
|
|
29
|
-
env: 'dot',
|
|
30
|
-
ini: 'dot',
|
|
31
|
-
conf: 'dot',
|
|
32
|
-
cfg: 'dot',
|
|
33
|
-
yaml: 'dot',
|
|
34
|
-
yml: 'dot',
|
|
35
|
-
toml: 'dot',
|
|
36
|
-
py: 'py',
|
|
37
|
-
python: 'py',
|
|
38
|
-
};
|
|
5
|
+
const { mapLang } = require('./highlight.js');
|
|
39
6
|
|
|
40
7
|
const PROSE_BASENAMES = [
|
|
41
8
|
'license',
|
|
@@ -53,11 +20,6 @@ const fileExt = (filePath) => {
|
|
|
53
20
|
return base.slice(dot + 1).toLowerCase();
|
|
54
21
|
};
|
|
55
22
|
|
|
56
|
-
const mapLang = (name) => {
|
|
57
|
-
if (!name) return 'txt';
|
|
58
|
-
return LANG[name.toLowerCase()] || 'txt';
|
|
59
|
-
};
|
|
60
|
-
|
|
61
23
|
const isProseBasename = (base) => {
|
|
62
24
|
const name = (base || '').toLowerCase();
|
|
63
25
|
if (PROSE_BASENAMES.includes(name)) return true;
|
|
@@ -79,7 +41,7 @@ const detectLang = (filePath) => {
|
|
|
79
41
|
if (rule.test(base)) return rule.lang;
|
|
80
42
|
}
|
|
81
43
|
const ext = fileExt(base);
|
|
82
|
-
if (ext) return
|
|
44
|
+
if (ext) return mapLang(ext);
|
|
83
45
|
return 'dot';
|
|
84
46
|
};
|
|
85
47
|
|
package/lib/diff.js
CHANGED
|
@@ -773,10 +773,78 @@ const windowAroundBlock = (lines, blockId, radius) => {
|
|
|
773
773
|
return lines.slice(from, to);
|
|
774
774
|
};
|
|
775
775
|
|
|
776
|
-
const
|
|
776
|
+
const insertAddIndex = (lines) => {
|
|
777
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
778
|
+
if (isCtxType(lines[i].type)) continue;
|
|
779
|
+
return i + 1;
|
|
780
|
+
}
|
|
781
|
+
return lines.length;
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
const taggedAdd = (text, blockId, start, end) => ({
|
|
785
|
+
type: 'add',
|
|
786
|
+
text,
|
|
787
|
+
noNl: false,
|
|
788
|
+
blockId,
|
|
789
|
+
wrap: true,
|
|
790
|
+
editStart: start,
|
|
791
|
+
editEnd: end,
|
|
792
|
+
editLast: true,
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
const replaceAdds = (lines, overlay, blockId) => {
|
|
796
|
+
const text = overlay.text ?? '';
|
|
797
|
+
const keepEmpty = overlay.keepEmpty === true;
|
|
798
|
+
if (text === '' && !keepEmpty) {
|
|
799
|
+
return lines.filter((line) => line.type !== 'add');
|
|
800
|
+
}
|
|
801
|
+
const texts = text.split('\n');
|
|
802
|
+
const tagged = [];
|
|
803
|
+
let offset = 0;
|
|
804
|
+
for (let i = 0; i < texts.length; i++) {
|
|
805
|
+
const row = texts[i];
|
|
806
|
+
const start = offset;
|
|
807
|
+
const end = start + row.length;
|
|
808
|
+
const isLast = i === texts.length - 1;
|
|
809
|
+
tagged.push(taggedAdd(row, blockId, start, end));
|
|
810
|
+
offset = isLast ? end : end + 1;
|
|
811
|
+
}
|
|
812
|
+
const out = [];
|
|
813
|
+
let placed = 0;
|
|
814
|
+
let lastAdd = -1;
|
|
815
|
+
for (const line of lines) {
|
|
816
|
+
if (line.type !== 'add') {
|
|
817
|
+
out.push(line);
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
if (placed >= tagged.length) continue;
|
|
821
|
+
out.push(tagged[placed]);
|
|
822
|
+
lastAdd = out.length - 1;
|
|
823
|
+
placed += 1;
|
|
824
|
+
}
|
|
825
|
+
const extras = tagged.slice(placed);
|
|
826
|
+
if (!extras.length) return out;
|
|
827
|
+
const at = lastAdd >= 0 ? lastAdd + 1 : insertAddIndex(out);
|
|
828
|
+
out.splice(at, 0, ...extras);
|
|
829
|
+
return out;
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
const blockAddText = (hunk, blockId) => {
|
|
833
|
+
if (!hunk) return '';
|
|
834
|
+
const parts = [];
|
|
835
|
+
for (const line of hunk.lines) {
|
|
836
|
+
if (line.type !== 'add') continue;
|
|
837
|
+
if (line.blockId !== blockId) continue;
|
|
838
|
+
parts.push(line.text);
|
|
839
|
+
}
|
|
840
|
+
return parts.join('\n');
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
const displayLines = (hunk, blockId, layout = 'unified', radius, overlay) => {
|
|
777
844
|
const flat = flattenBlock(hunk, blockId, 'new');
|
|
845
|
+
const source = overlay ? replaceAdds(flat, overlay, blockId) : flat;
|
|
778
846
|
const around = radius ?? DISPLAY_CONTEXT;
|
|
779
|
-
const windowed = windowAroundBlock(
|
|
847
|
+
const windowed = windowAroundBlock(source, blockId, around);
|
|
780
848
|
const lines = windowed.map((line) => ({ ...line }));
|
|
781
849
|
const painted = attachInline(lines);
|
|
782
850
|
const align = LAYOUT_ALIGN[layout];
|
|
@@ -877,6 +945,7 @@ module.exports = {
|
|
|
877
945
|
countSides,
|
|
878
946
|
formatPatch,
|
|
879
947
|
displayLines,
|
|
948
|
+
blockAddText,
|
|
880
949
|
synthesizeNewFile,
|
|
881
950
|
itemsFromFiles,
|
|
882
951
|
refreshIndexPatches,
|
package/lib/files.js
CHANGED
|
@@ -57,6 +57,7 @@ const fileEntries = (items) => {
|
|
|
57
57
|
path: rel,
|
|
58
58
|
origins: [],
|
|
59
59
|
firstIndex: i,
|
|
60
|
+
openIndex: i,
|
|
60
61
|
remaining: 0,
|
|
61
62
|
added: 0,
|
|
62
63
|
removed: 0,
|
|
@@ -72,7 +73,10 @@ const fileEntries = (items) => {
|
|
|
72
73
|
group.added += delta.added;
|
|
73
74
|
group.removed += delta.removed;
|
|
74
75
|
if (origin === 'staged') group.staged += 1;
|
|
75
|
-
if (origin === 'unstaged')
|
|
76
|
+
if (origin === 'unstaged') {
|
|
77
|
+
if (group.unstaged === 0) group.openIndex = i;
|
|
78
|
+
group.unstaged += 1;
|
|
79
|
+
}
|
|
76
80
|
}
|
|
77
81
|
const entries = [...groups.values()];
|
|
78
82
|
for (const group of entries) {
|