sight 0.4.0 → 0.5.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 +15 -0
- data/CLAUDE.md +3 -4
- data/README.md +16 -3
- data/RELEASING.md +7 -0
- data/lib/sight/annotation_formatter.rb +9 -9
- data/lib/sight/app.rb +8 -13
- data/lib/sight/cli.rb +27 -65
- data/lib/sight/version.rb +1 -1
- data/lib/sight.rb +0 -1
- metadata +2 -2
- data/lib/sight/cursor_hook_installer.rb +0 -62
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 32b20a4a62fbca9fee67a4354538a8e033dc079b945c7aa5a6f6d23f63c0fac6
|
|
4
|
+
data.tar.gz: 4be26d309ed3cb0649bc093a78326d6f9a4a2ce2f2f3f3ec9943b713bac9f372
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7acf8bc51543269963f35953c5e7ffe7452483e655330bd5d9c314c394a575f6710233fe26b117eaa7ca00491c0d4f561c879e2c7ab160fcb32a6d3db15bf7e4
|
|
7
|
+
data.tar.gz: 696a3493aa44d9e7913913df11f9ef583de057e15bc210811027121a629534d486c7cb7c197180c146644aadba2eea331db8d49315c83da9197d2a4116f472ca
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.0] - 2026-03-07
|
|
4
|
+
|
|
5
|
+
### Removed
|
|
6
|
+
|
|
7
|
+
- Cursor hook support (use Cursor rules instead)
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Refactored CLI dispatch to use case statement with extracted `open` and `print_help` methods
|
|
12
|
+
- DRYed up install/uninstall hook agent lookup
|
|
13
|
+
- Use `Set` for commented hunk lines lookup
|
|
14
|
+
- Extracted `hunk_end_offset` to deduplicate boundary logic
|
|
15
|
+
- Use `<<` for string building in `AnnotationFormatter`
|
|
16
|
+
- Simplified prompt comment width clamping
|
|
17
|
+
|
|
3
18
|
## [0.4.0] - 2026-03-06
|
|
4
19
|
|
|
5
20
|
### Added
|
data/CLAUDE.md
CHANGED
|
@@ -13,7 +13,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
13
13
|
|
|
14
14
|
## Architecture
|
|
15
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`.
|
|
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` → `CLI.open` → `App.new(files).run`.
|
|
17
17
|
|
|
18
18
|
**Data flow**: `Git.diff` (raw string) → `DiffParser.parse` (returns `DiffFile[]`) → `App` (curses TUI).
|
|
19
19
|
Untracked files are added via `Git.untracked_files` → `DiffParser.build_untracked`.
|
|
@@ -24,10 +24,9 @@ Untracked files are added via `Git.untracked_files` → `DiffParser.build_untrac
|
|
|
24
24
|
|
|
25
25
|
**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).
|
|
26
26
|
|
|
27
|
-
**Hook system**:
|
|
27
|
+
**Hook system**: `sight install-hook claude` / `sight uninstall-hook claude`.
|
|
28
28
|
`ClaudeHookInstaller` manages a Claude Code `UserPromptSubmit` hook in `~/.config/claude/settings.json`.
|
|
29
|
-
|
|
30
|
-
Both read `.git/sight/pending-review`, output annotations, and delete the file. Hidden subcommands: `hook-run`, `cursor-hook-run`.
|
|
29
|
+
Reads `.git/sight/pending-review`, outputs annotations, and deletes the file. Hidden subcommand: `hook-run`.
|
|
31
30
|
|
|
32
31
|
## Conventions
|
|
33
32
|
|
data/README.md
CHANGED
|
@@ -34,18 +34,31 @@ sight
|
|
|
34
34
|
|
|
35
35
|
Install a hook so annotations are automatically fed as context in your next message:
|
|
36
36
|
|
|
37
|
+
**Claude Code** (~/.config/claude/settings.json):
|
|
38
|
+
|
|
37
39
|
```bash
|
|
38
|
-
sight install-hook claude
|
|
39
|
-
sight install-hook cursor # Cursor (~/.cursor/hooks.json)
|
|
40
|
+
sight install-hook claude
|
|
40
41
|
```
|
|
41
42
|
|
|
42
43
|
When you quit sight after annotating, the next message you send will include your annotations.
|
|
43
44
|
|
|
45
|
+
The hook runs on every prompt but is a no-op when there's no pending review — it produces no output and adds nothing to the agent's context.
|
|
46
|
+
|
|
44
47
|
To remove:
|
|
45
48
|
|
|
46
49
|
```bash
|
|
47
50
|
sight uninstall-hook claude
|
|
48
|
-
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Cursor** — Create a rule in `.cursor/rules/sight.mdc`:
|
|
54
|
+
|
|
55
|
+
```markdown
|
|
56
|
+
---
|
|
57
|
+
description: When the user asks to review their sight annotations or code review comments
|
|
58
|
+
alwaysApply: false
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
Read `.git/sight/pending-review`, address the annotations, then delete the file.
|
|
49
62
|
```
|
|
50
63
|
|
|
51
64
|
## Development
|
data/RELEASING.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
1. Update `lib/sight/version.rb` and `CHANGELOG.md`
|
|
4
|
+
2. Run `bundle install` to update `Gemfile.lock`
|
|
5
|
+
3. Run `bundle exec rake` to verify tests and lint pass
|
|
6
|
+
4. Commit: `git commit -am "Release vX.Y.Z"`
|
|
7
|
+
5. Run `bundle exec rake release` — tags, builds, and pushes to RubyGems
|
|
@@ -8,21 +8,21 @@ module Sight
|
|
|
8
8
|
return "" if annotations.empty?
|
|
9
9
|
|
|
10
10
|
grouped = annotations.group_by(&:file_path)
|
|
11
|
-
grouped.map { |path,
|
|
11
|
+
grouped.map { |path, file_annotations| format_file(path, file_annotations) }.join("\n")
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def format_file(path, annotations)
|
|
15
15
|
out = "## File: #{path}\n\n"
|
|
16
|
-
annotations.each do |
|
|
17
|
-
context =
|
|
18
|
-
out
|
|
19
|
-
out
|
|
20
|
-
|
|
16
|
+
annotations.each do |annotation|
|
|
17
|
+
context = annotation.hunk.context ? " #{annotation.hunk.context}" : ""
|
|
18
|
+
out << "Hunk (@@#{context}):\n"
|
|
19
|
+
out << "```diff\n"
|
|
20
|
+
annotation.hunk.lines.each do |line|
|
|
21
21
|
next unless %i[add del].include?(line.type)
|
|
22
|
-
out
|
|
22
|
+
out << "#{line.content}\n"
|
|
23
23
|
end
|
|
24
|
-
out
|
|
25
|
-
out
|
|
24
|
+
out << "```\n"
|
|
25
|
+
out << "> #{annotation.comment}\n\n"
|
|
26
26
|
end
|
|
27
27
|
out
|
|
28
28
|
end
|
data/lib/sight/app.rb
CHANGED
|
@@ -92,11 +92,7 @@ module Sight
|
|
|
92
92
|
content_width = width - gutter - 3
|
|
93
93
|
|
|
94
94
|
selected_start = hunk_offsets[hunk_idx]
|
|
95
|
-
selected_end =
|
|
96
|
-
hunk_offsets[hunk_idx + 1]
|
|
97
|
-
else
|
|
98
|
-
lines.size
|
|
99
|
-
end
|
|
95
|
+
selected_end = hunk_end_offset(hunk_idx)
|
|
100
96
|
|
|
101
97
|
commented_lines = commented_hunk_lines
|
|
102
98
|
|
|
@@ -254,9 +250,7 @@ module Sight
|
|
|
254
250
|
|
|
255
251
|
def prompt_comment(title)
|
|
256
252
|
win = Curses.stdscr
|
|
257
|
-
|
|
258
|
-
width = [Curses.cols * 2 / 3, 50].max
|
|
259
|
-
width = [width, max_width].min
|
|
253
|
+
width = (Curses.cols * 2 / 3).clamp(50, 80)
|
|
260
254
|
height = 5
|
|
261
255
|
top = (Curses.lines - height) / 2
|
|
262
256
|
left = (Curses.cols - width) / 2
|
|
@@ -311,6 +305,10 @@ module Sight
|
|
|
311
305
|
end
|
|
312
306
|
end
|
|
313
307
|
|
|
308
|
+
def hunk_end_offset(idx)
|
|
309
|
+
(idx + 1 < hunk_offsets.size) ? hunk_offsets[idx + 1] : lines.size
|
|
310
|
+
end
|
|
311
|
+
|
|
314
312
|
def hunk_commented?(file_index, hunk_index)
|
|
315
313
|
path = files[file_index].path
|
|
316
314
|
hunk = files[file_index].hunks[hunk_index]
|
|
@@ -318,13 +316,10 @@ module Sight
|
|
|
318
316
|
end
|
|
319
317
|
|
|
320
318
|
def commented_hunk_lines
|
|
321
|
-
|
|
322
|
-
hunk_offsets.each_with_index do |offset, hunk_index|
|
|
319
|
+
hunk_offsets.each_with_index.each_with_object(Set.new) do |(offset, hunk_index), set|
|
|
323
320
|
next unless hunk_commented?(file_idx, hunk_index)
|
|
324
|
-
|
|
325
|
-
(offset...hunk_end).each { |i| result << i }
|
|
321
|
+
(offset...hunk_end_offset(hunk_index)).each { |i| set << i }
|
|
326
322
|
end
|
|
327
|
-
result
|
|
328
323
|
end
|
|
329
324
|
|
|
330
325
|
def scroll(delta)
|
data/lib/sight/cli.rb
CHANGED
|
@@ -1,49 +1,33 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "json"
|
|
4
|
-
|
|
5
3
|
module Sight
|
|
6
4
|
module CLI
|
|
7
5
|
module_function
|
|
8
6
|
|
|
9
7
|
def run(argv)
|
|
10
|
-
if argv.include?("--help") || argv.include?("-h")
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
puts " uninstall-hook <agent> Remove hook (claude, cursor)"
|
|
19
|
-
return
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
if argv.include?("--version") || argv.include?("-v")
|
|
23
|
-
puts "sight #{VERSION}"
|
|
24
|
-
return
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
if argv[0] == "install-hook"
|
|
28
|
-
install_hook(argv[1])
|
|
29
|
-
return
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
if argv[0] == "uninstall-hook"
|
|
33
|
-
uninstall_hook(argv[1])
|
|
34
|
-
return
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
if argv.include?("hook-run")
|
|
38
|
-
run_hook
|
|
39
|
-
return
|
|
8
|
+
return print_help if argv.include?("--help") || argv.include?("-h")
|
|
9
|
+
return puts("sight #{VERSION}") if argv.include?("--version") || argv.include?("-v")
|
|
10
|
+
|
|
11
|
+
case argv[0]
|
|
12
|
+
when "install-hook" then install_hook(argv[1])
|
|
13
|
+
when "uninstall-hook" then uninstall_hook(argv[1])
|
|
14
|
+
when "hook-run" then run_hook
|
|
15
|
+
else open
|
|
40
16
|
end
|
|
17
|
+
end
|
|
41
18
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
19
|
+
def print_help
|
|
20
|
+
puts "Usage: sight"
|
|
21
|
+
puts "Interactive git diff viewer (staged + unstaged)"
|
|
22
|
+
puts
|
|
23
|
+
puts "Keys: j/k hunks, n/p files, c comment, ? help, q quit"
|
|
24
|
+
puts
|
|
25
|
+
puts "Subcommands:"
|
|
26
|
+
puts " install-hook <agent> Install hook (claude)"
|
|
27
|
+
puts " uninstall-hook <agent> Remove hook (claude)"
|
|
28
|
+
end
|
|
46
29
|
|
|
30
|
+
def open
|
|
47
31
|
Git.clear_pending_review
|
|
48
32
|
|
|
49
33
|
raw = Git.diff
|
|
@@ -71,26 +55,19 @@ module Sight
|
|
|
71
55
|
end
|
|
72
56
|
|
|
73
57
|
AGENTS = {
|
|
74
|
-
"claude" => ClaudeHookInstaller
|
|
75
|
-
"cursor" => CursorHookInstaller
|
|
58
|
+
"claude" => ClaudeHookInstaller
|
|
76
59
|
}.freeze
|
|
77
60
|
|
|
78
61
|
def install_hook(agent)
|
|
79
|
-
|
|
80
|
-
unless installer
|
|
81
|
-
warn "Unknown agent: #{agent.inspect}. Use: claude, cursor"
|
|
82
|
-
return
|
|
83
|
-
end
|
|
84
|
-
installer.install
|
|
62
|
+
resolve_installer(agent)&.install
|
|
85
63
|
end
|
|
86
64
|
|
|
87
65
|
def uninstall_hook(agent)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
installer.uninstall
|
|
66
|
+
resolve_installer(agent)&.uninstall
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def resolve_installer(agent)
|
|
70
|
+
AGENTS.fetch(agent) { warn "Unknown agent: #{agent.inspect}. Use: claude" }
|
|
94
71
|
end
|
|
95
72
|
|
|
96
73
|
def run_hook
|
|
@@ -107,20 +84,5 @@ module Sight
|
|
|
107
84
|
puts content
|
|
108
85
|
File.delete(file)
|
|
109
86
|
end
|
|
110
|
-
|
|
111
|
-
def run_cursor_hook
|
|
112
|
-
$stdin.read
|
|
113
|
-
git_dir = Git.repo_dir
|
|
114
|
-
rescue Error
|
|
115
|
-
nil
|
|
116
|
-
else
|
|
117
|
-
file = File.join(git_dir, "sight", "pending-review")
|
|
118
|
-
return unless File.exist?(file)
|
|
119
|
-
|
|
120
|
-
content = File.read(file)
|
|
121
|
-
message = "The user has just finished reviewing your code changes in sight. Here are their annotations:\n\n#{content}"
|
|
122
|
-
puts JSON.generate(user_message: message)
|
|
123
|
-
File.delete(file)
|
|
124
|
-
end
|
|
125
87
|
end
|
|
126
88
|
end
|
data/lib/sight/version.rb
CHANGED
data/lib/sight.rb
CHANGED
|
@@ -7,7 +7,6 @@ require_relative "sight/git"
|
|
|
7
7
|
require_relative "sight/annotation"
|
|
8
8
|
require_relative "sight/annotation_formatter"
|
|
9
9
|
require_relative "sight/claude_hook_installer"
|
|
10
|
-
require_relative "sight/cursor_hook_installer"
|
|
11
10
|
require_relative "sight/cli"
|
|
12
11
|
require_relative "sight/app"
|
|
13
12
|
|
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.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ariel Rzezak
|
|
@@ -37,6 +37,7 @@ files:
|
|
|
37
37
|
- CODE_OF_CONDUCT.md
|
|
38
38
|
- LICENSE.txt
|
|
39
39
|
- README.md
|
|
40
|
+
- RELEASING.md
|
|
40
41
|
- Rakefile
|
|
41
42
|
- exe/sight
|
|
42
43
|
- lib/sight.rb
|
|
@@ -45,7 +46,6 @@ files:
|
|
|
45
46
|
- lib/sight/app.rb
|
|
46
47
|
- lib/sight/claude_hook_installer.rb
|
|
47
48
|
- lib/sight/cli.rb
|
|
48
|
-
- lib/sight/cursor_hook_installer.rb
|
|
49
49
|
- lib/sight/diff_parser.rb
|
|
50
50
|
- lib/sight/display_line.rb
|
|
51
51
|
- lib/sight/git.rb
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
require "fileutils"
|
|
5
|
-
|
|
6
|
-
module Sight
|
|
7
|
-
module CursorHookInstaller
|
|
8
|
-
HOOK_COMMAND = "sight cursor-hook-run"
|
|
9
|
-
|
|
10
|
-
module_function
|
|
11
|
-
|
|
12
|
-
def hooks_path
|
|
13
|
-
File.join(Dir.home, ".cursor", "hooks.json")
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def install(path: hooks_path)
|
|
17
|
-
config = if File.exist?(path)
|
|
18
|
-
JSON.parse(File.read(path))
|
|
19
|
-
else
|
|
20
|
-
{"version" => 1, "hooks" => {}}
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
config["version"] ||= 1
|
|
24
|
-
hooks = config["hooks"] ||= {}
|
|
25
|
-
prompt_hooks = hooks["beforeSubmitPrompt"] ||= []
|
|
26
|
-
|
|
27
|
-
if prompt_hooks.any? { |h| hook_is_sight?(h) }
|
|
28
|
-
puts "sight hook already installed"
|
|
29
|
-
return
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
prompt_hooks << {"command" => HOOK_COMMAND}
|
|
33
|
-
|
|
34
|
-
FileUtils.mkdir_p(File.dirname(path))
|
|
35
|
-
File.write(path, JSON.pretty_generate(config) + "\n")
|
|
36
|
-
puts "Installed sight hook into #{path}"
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def uninstall(path: hooks_path)
|
|
40
|
-
unless File.exist?(path)
|
|
41
|
-
puts "No hooks file found"
|
|
42
|
-
return
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
config = JSON.parse(File.read(path))
|
|
46
|
-
prompt_hooks = config.dig("hooks", "beforeSubmitPrompt")
|
|
47
|
-
|
|
48
|
-
unless prompt_hooks&.any? { |h| hook_is_sight?(h) }
|
|
49
|
-
puts "No sight hook found"
|
|
50
|
-
return
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
prompt_hooks.reject! { |h| hook_is_sight?(h) }
|
|
54
|
-
File.write(path, JSON.pretty_generate(config) + "\n")
|
|
55
|
-
puts "Uninstalled sight hook"
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def hook_is_sight?(entry)
|
|
59
|
-
entry["command"]&.include?("sight")
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|