sight 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/CLAUDE.md +29 -0
- data/README.md +2 -1
- data/lib/sight/annotation.rb +5 -0
- data/lib/sight/annotation_formatter.rb +30 -0
- data/lib/sight/app.rb +64 -1
- data/lib/sight/cli.rb +9 -2
- data/lib/sight/git.rb +1 -4
- data/lib/sight/version.rb +1 -1
- data/lib/sight.rb +2 -0
- metadata +7 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6e488680c7d4ecc42d8cd02b8d2731a315755b35df2a9cf319ed3f0d413e2809
|
|
4
|
+
data.tar.gz: 375af2bad773014e984f0b89c3333c839dd84bfc8e8cd610357c78bcfd052962
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 76d83afcd3ac25b084fc22661d9561fd75bd16ef662237b5cf2e1d784225a442ac682740a312a711ae169e16eac1f8d997ff3e68b44fb1222a00a9fd3e077c6a
|
|
7
|
+
data.tar.gz: 934ab59d60fb58ac4029718705d1551fd6ede47ff0dfcadfefdf45fdd30eddc2060dcc815988060a796cf55cf993e0b9ba82b86426b1bc8043ed974631006099
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2026-03-04
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Comment on hunks with `c` — annotations are printed to stdout on quit as markdown
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Removed initial-commit diff fallback from `Git.diff`
|
|
12
|
+
|
|
3
13
|
## [0.2.0] - 2026-03-04
|
|
4
14
|
|
|
5
15
|
### Changed
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
- `bundle exec rake test` — run all tests
|
|
8
|
+
- `ruby -Ilib:test test/test_cli.rb` — run a single test file
|
|
9
|
+
- `ruby -Ilib:test test/test_cli.rb -n test_help_flag` — run a single test method
|
|
10
|
+
- `bundle exec standardrb` — lint
|
|
11
|
+
- `bundle exec standardrb --fix` — lint and auto-fix
|
|
12
|
+
- `bundle exec rake` — run tests + lint (default task)
|
|
13
|
+
|
|
14
|
+
## Architecture
|
|
15
|
+
|
|
16
|
+
TUI for closing the loop on AI-generated code changes — browse diffs, jump between hunks, and annotate them. Entry point: `exe/sight` → `CLI.run` → `App.new(files).run`.
|
|
17
|
+
|
|
18
|
+
**Data flow**: `Git.diff` (raw string) → `DiffParser.parse` (returns `DiffFile[]`) → `App` (curses TUI)
|
|
19
|
+
|
|
20
|
+
**Key structs** (all in `diff_parser.rb`): `DiffFile(path, hunks)`, `Hunk(context, lines)`, `DiffLine(type, content, lineno, old_lineno)`. `DisplayLine(type, text, lineno)` is the render-side equivalent in `display_line.rb`.
|
|
21
|
+
|
|
22
|
+
**App** renders per-file views with hunk-based navigation (j/k). Active hunk is highlighted; inactive hunks render in dark gray (color pair 5, color 240).
|
|
23
|
+
|
|
24
|
+
## Conventions
|
|
25
|
+
|
|
26
|
+
- Ruby >= 3.2, uses `frozen_string_literal` in all files
|
|
27
|
+
- Linter: StandardRB (ruby_version: 3.4 in `.standard.yml`)
|
|
28
|
+
- Tests: Minitest with stubs/mocks, no test framework beyond minitest
|
|
29
|
+
- Single runtime dependency: `curses ~1.4`
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sight
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TUI for closing the loop on AI-generated code changes.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -25,6 +25,7 @@ sight
|
|
|
25
25
|
| `n` | Next file |
|
|
26
26
|
| `p` | Previous file |
|
|
27
27
|
| `?` | Toggle help |
|
|
28
|
+
| `c` | Comment on hunk |
|
|
28
29
|
| `q` / `Esc` | Quit |
|
|
29
30
|
|
|
30
31
|
## Development
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sight
|
|
4
|
+
module AnnotationFormatter
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def format(annotations)
|
|
8
|
+
return "" if annotations.empty?
|
|
9
|
+
|
|
10
|
+
grouped = annotations.group_by(&:file_path)
|
|
11
|
+
grouped.map { |path, anns| format_file(path, anns) }.join("\n")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def format_file(path, annotations)
|
|
15
|
+
out = "## File: #{path}\n\n"
|
|
16
|
+
annotations.each do |ann|
|
|
17
|
+
context = ann.hunk.context ? " #{ann.hunk.context}" : ""
|
|
18
|
+
out += "Hunk (@@#{context}):\n"
|
|
19
|
+
out += "```diff\n"
|
|
20
|
+
ann.hunk.lines.each do |line|
|
|
21
|
+
next unless %i[add del].include?(line.type)
|
|
22
|
+
out += "#{line.content}\n"
|
|
23
|
+
end
|
|
24
|
+
out += "```\n"
|
|
25
|
+
out += "> #{ann.comment}\n\n"
|
|
26
|
+
end
|
|
27
|
+
out
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/sight/app.rb
CHANGED
|
@@ -4,7 +4,7 @@ require "curses"
|
|
|
4
4
|
|
|
5
5
|
module Sight
|
|
6
6
|
class App
|
|
7
|
-
attr_reader :files, :file_lines
|
|
7
|
+
attr_reader :files, :file_lines, :annotations
|
|
8
8
|
attr_accessor :file_idx, :offset, :hunk_idx
|
|
9
9
|
|
|
10
10
|
def initialize(files)
|
|
@@ -14,6 +14,7 @@ module Sight
|
|
|
14
14
|
@offset = 0
|
|
15
15
|
@hunk_idx = 0
|
|
16
16
|
@hunk_offsets_cache = {}
|
|
17
|
+
@annotations = []
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def run
|
|
@@ -146,6 +147,7 @@ module Sight
|
|
|
146
147
|
when "k" then jump_hunk(-1)
|
|
147
148
|
when "n" then jump_file(1)
|
|
148
149
|
when "p" then jump_file(-1)
|
|
150
|
+
when "c" then annotate_hunk
|
|
149
151
|
when "?" then show_help
|
|
150
152
|
end
|
|
151
153
|
true
|
|
@@ -157,6 +159,7 @@ module Sight
|
|
|
157
159
|
["n", "Next file"],
|
|
158
160
|
["p", "Previous file"],
|
|
159
161
|
["q / Esc", "Quit"],
|
|
162
|
+
["c", "Comment on hunk"],
|
|
160
163
|
["?", "Toggle this help"]
|
|
161
164
|
].freeze
|
|
162
165
|
|
|
@@ -196,6 +199,66 @@ module Sight
|
|
|
196
199
|
end
|
|
197
200
|
end
|
|
198
201
|
|
|
202
|
+
def annotate_hunk
|
|
203
|
+
hunk = files[file_idx].hunks[hunk_idx]
|
|
204
|
+
return unless hunk
|
|
205
|
+
comment = prompt_comment("Comment on hunk")
|
|
206
|
+
return unless comment
|
|
207
|
+
@annotations << Annotation.new(
|
|
208
|
+
file_path: files[file_idx].path,
|
|
209
|
+
type: :hunk,
|
|
210
|
+
hunk: hunk,
|
|
211
|
+
comment: comment
|
|
212
|
+
)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def prompt_comment(title)
|
|
216
|
+
win = Curses.stdscr
|
|
217
|
+
max_width = 80
|
|
218
|
+
width = [Curses.cols * 2 / 3, 50].max
|
|
219
|
+
width = [width, max_width].min
|
|
220
|
+
height = 5
|
|
221
|
+
top = (Curses.lines - height) / 2
|
|
222
|
+
left = (Curses.cols - width) / 2
|
|
223
|
+
|
|
224
|
+
draw_box(win, top, left, width, height, title, [""])
|
|
225
|
+
win.setpos(top + 3, left + 3)
|
|
226
|
+
win.refresh
|
|
227
|
+
|
|
228
|
+
Curses.curs_set(1)
|
|
229
|
+
text = ""
|
|
230
|
+
field_width = width - 6
|
|
231
|
+
redraw_field = -> {
|
|
232
|
+
win.setpos(top + 3, left + 3)
|
|
233
|
+
win.addstr(" " * field_width)
|
|
234
|
+
visible = (text.length > field_width) ? text[-field_width..] : text
|
|
235
|
+
win.setpos(top + 3, left + 3)
|
|
236
|
+
win.addstr(visible)
|
|
237
|
+
}
|
|
238
|
+
loop do
|
|
239
|
+
ch = Curses.getch
|
|
240
|
+
case ch
|
|
241
|
+
when 10, 13, Curses::KEY_ENTER
|
|
242
|
+
break
|
|
243
|
+
when 27
|
|
244
|
+
text = nil
|
|
245
|
+
break
|
|
246
|
+
when Curses::KEY_BACKSPACE, 127, 8
|
|
247
|
+
unless text.empty?
|
|
248
|
+
text = text[0..-2]
|
|
249
|
+
redraw_field.call
|
|
250
|
+
end
|
|
251
|
+
else
|
|
252
|
+
text += ch.chr if ch.is_a?(Integer) && ch >= 32 && ch < 127
|
|
253
|
+
text += ch if ch.is_a?(String) && ch.length == 1
|
|
254
|
+
redraw_field.call
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
Curses.curs_set(0)
|
|
258
|
+
|
|
259
|
+
text&.strip&.empty? ? nil : text&.strip
|
|
260
|
+
end
|
|
261
|
+
|
|
199
262
|
def hunk_offsets
|
|
200
263
|
@hunk_offsets_cache[file_idx] ||= begin
|
|
201
264
|
offsets = []
|
data/lib/sight/cli.rb
CHANGED
|
@@ -9,7 +9,9 @@ module Sight
|
|
|
9
9
|
puts "Usage: sight"
|
|
10
10
|
puts "Interactive git diff viewer (staged + unstaged)"
|
|
11
11
|
puts
|
|
12
|
-
puts "Keys: j/k hunks, n/p files, ? help, q quit"
|
|
12
|
+
puts "Keys: j/k hunks, n/p files, c comment, ? help, q quit"
|
|
13
|
+
puts
|
|
14
|
+
puts "Annotations are printed to stdout on quit."
|
|
13
15
|
return
|
|
14
16
|
end
|
|
15
17
|
|
|
@@ -25,7 +27,12 @@ module Sight
|
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
files = DiffParser.parse(raw)
|
|
28
|
-
App.new(files)
|
|
30
|
+
app = App.new(files)
|
|
31
|
+
app.run
|
|
32
|
+
|
|
33
|
+
unless app.annotations.empty?
|
|
34
|
+
puts AnnotationFormatter.format(app.annotations)
|
|
35
|
+
end
|
|
29
36
|
end
|
|
30
37
|
end
|
|
31
38
|
end
|
data/lib/sight/git.rb
CHANGED
|
@@ -6,10 +6,7 @@ module Sight
|
|
|
6
6
|
|
|
7
7
|
def diff
|
|
8
8
|
output, success = run_cmd(["git", "diff", "--no-color", "HEAD"])
|
|
9
|
-
unless success
|
|
10
|
-
output, success = run_cmd(["git", "diff", "--no-color", "--cached"], err: [:child, :out])
|
|
11
|
-
raise Error, "git diff failed: #{output}" unless success
|
|
12
|
-
end
|
|
9
|
+
raise Error, "git diff failed" unless success
|
|
13
10
|
output
|
|
14
11
|
end
|
|
15
12
|
|
data/lib/sight/version.rb
CHANGED
data/lib/sight.rb
CHANGED
|
@@ -4,6 +4,8 @@ require_relative "sight/version"
|
|
|
4
4
|
require_relative "sight/display_line"
|
|
5
5
|
require_relative "sight/diff_parser"
|
|
6
6
|
require_relative "sight/git"
|
|
7
|
+
require_relative "sight/annotation"
|
|
8
|
+
require_relative "sight/annotation_formatter"
|
|
7
9
|
require_relative "sight/cli"
|
|
8
10
|
require_relative "sight/app"
|
|
9
11
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sight
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ariel Rzezak
|
|
@@ -23,8 +23,8 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '1.4'
|
|
26
|
-
description:
|
|
27
|
-
|
|
26
|
+
description: Browse diffs by hunk, comment on changes, and close the feedback loop
|
|
27
|
+
with AI agents.
|
|
28
28
|
email:
|
|
29
29
|
- arzezak@gmail.com
|
|
30
30
|
executables:
|
|
@@ -33,12 +33,15 @@ extensions: []
|
|
|
33
33
|
extra_rdoc_files: []
|
|
34
34
|
files:
|
|
35
35
|
- CHANGELOG.md
|
|
36
|
+
- CLAUDE.md
|
|
36
37
|
- CODE_OF_CONDUCT.md
|
|
37
38
|
- LICENSE.txt
|
|
38
39
|
- README.md
|
|
39
40
|
- Rakefile
|
|
40
41
|
- exe/sight
|
|
41
42
|
- lib/sight.rb
|
|
43
|
+
- lib/sight/annotation.rb
|
|
44
|
+
- lib/sight/annotation_formatter.rb
|
|
42
45
|
- lib/sight/app.rb
|
|
43
46
|
- lib/sight/cli.rb
|
|
44
47
|
- lib/sight/diff_parser.rb
|
|
@@ -70,5 +73,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
70
73
|
requirements: []
|
|
71
74
|
rubygems_version: 4.0.7
|
|
72
75
|
specification_version: 4
|
|
73
|
-
summary:
|
|
76
|
+
summary: TUI for closing the loop on AI-generated code changes
|
|
74
77
|
test_files: []
|