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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24b700f118e81b323388d9dc1413bcb493a8c8625664099fcff2eeb524fbe4e7
4
- data.tar.gz: c0b1e78505b39f82909607431c5c7cf27a56545292abb0967887f2a60be04420
3
+ metadata.gz: 6e488680c7d4ecc42d8cd02b8d2731a315755b35df2a9cf319ed3f0d413e2809
4
+ data.tar.gz: 375af2bad773014e984f0b89c3333c839dd84bfc8e8cd610357c78bcfd052962
5
5
  SHA512:
6
- metadata.gz: b3e9f75deef46a4da613023322b833f0bbf86c7f2cc5f3ccde44f30fd7bd0cb1712d4925c5ed18a865e4ff6fe488d787eaba38939c52cea2e0a9d1f62fec4a97
7
- data.tar.gz: 6530759983f83fed70bab30adb12fa6bae9990f5554e91170e5547cbeb58514759eb649fe322b1857d895ec0051017ab53ee2b9cbc35bb918b66199c9fbfbf51
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
- Terminal UI for browsing git diffs interactively with colors, hunk navigation, and file navigation.
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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sight
4
+ Annotation = Struct.new(:file_path, :type, :hunk, :comment, keyword_init: true)
5
+ end
@@ -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).run
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sight
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
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.2.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: A TUI tool to browse git diffs interactively with colors, file navigation,
27
- and scrolling.
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: Interactive git diff viewer for the terminal
76
+ summary: TUI for closing the loop on AI-generated code changes
74
77
  test_files: []