jeeves-git-commit 3.0.0 → 3.1.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/README.md +5 -0
- data/lib/jeeves/diff.rb +90 -0
- data/lib/jeeves/prompt.rb +16 -2
- data/lib/jeeves/providers.rb +1 -3
- data/lib/jeeves/settings.rb +5 -3
- data/lib/jeeves/version.rb +1 -1
- data/lib/jeeves.rb +4 -2
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c677877fa585dc14ffd287c273bd0a79c82b4592c0f84c5e532c2171b576d061
|
|
4
|
+
data.tar.gz: fcfcd962506dc36115358ae8e5b20afe7273e1a8f531101d4b9c92442bc24b4c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0773d6fa8d0504583fbca079ffcb0a418c0de29b09a8ae799b5ac42b0e917fbba3d7f06b2b1a78ab69ae1255a5f36aa118dccdcc06fab41edb10ad452a35aacf
|
|
7
|
+
data.tar.gz: 05a7466354d8ff50a486d4c3bf4487a68a746fdce0c64209133ccb2f52e30a85a4ddf5637f016393293d2fc40e3137c685ab915559f383f1bb6f017f03b964cb
|
data/README.md
CHANGED
|
@@ -95,6 +95,11 @@ Git and generation failures stop the command with a nonzero exit status.
|
|
|
95
95
|
Jeeves checks that staged changes and HEAD still match what it reviewed before
|
|
96
96
|
committing. See [architecture and Git behavior](docs/architecture.md).
|
|
97
97
|
|
|
98
|
+
Local generation defaults to a 65,536-token context. Oversized diffs are
|
|
99
|
+
automatically shortened, with a warning and omission notices for the model.
|
|
100
|
+
Only the model input is shortened; Git commits the complete staged changes.
|
|
101
|
+
See [input limits](docs/configuration.md#output-checks-and-large-diffs).
|
|
102
|
+
|
|
98
103
|
## Develop
|
|
99
104
|
|
|
100
105
|
```sh
|
data/lib/jeeves/diff.rb
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jeeves
|
|
4
|
+
class Diff
|
|
5
|
+
NOTICE = "[Jeeves: diff shortened. Excerpts may omit files, hunks, or parts of lines. " \
|
|
6
|
+
"Hunk ranges are from the original diff; omitted content is unknown.]\n"
|
|
7
|
+
OMITTED = "\n[... content omitted ...]\n"
|
|
8
|
+
|
|
9
|
+
def initialize(text)
|
|
10
|
+
@text = text
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def fit(limit)
|
|
14
|
+
return @text if @text.bytesize <= limit
|
|
15
|
+
|
|
16
|
+
budget = limit - NOTICE.bytesize
|
|
17
|
+
if budget < OMITTED.bytesize + 8
|
|
18
|
+
raise Error, 'Too little room for a shortened diff. Shorten the prompt or increase ' \
|
|
19
|
+
'GIT_COMMIT_LOCAL_CONTEXT and GIT_COMMIT_MAX_DIFF_BYTES.'
|
|
20
|
+
end
|
|
21
|
+
compact = without_context
|
|
22
|
+
return NOTICE + compact if compact.bytesize <= budget
|
|
23
|
+
|
|
24
|
+
files = compact.split(/(?=^diff --(?:git |cc |combined ))/)
|
|
25
|
+
NOTICE + fit_parts(files, budget) { |file, size| fit_file(file, size) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def without_context
|
|
31
|
+
context_prefix = nil
|
|
32
|
+
@text.each_line.reject do |line|
|
|
33
|
+
context_prefix = nil if line.start_with?('diff --')
|
|
34
|
+
match = line.match(/\A(@{2,}) /)
|
|
35
|
+
context_prefix = ' ' * (match[1].length - 1) if match
|
|
36
|
+
context_prefix && line.start_with?(context_prefix)
|
|
37
|
+
end.join
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def fit_file(file, budget)
|
|
41
|
+
return file if file.bytesize <= budget
|
|
42
|
+
|
|
43
|
+
header = header_for(file)
|
|
44
|
+
body = file.delete_prefix(header)
|
|
45
|
+
hunks = body.split(/(?=^@@)/)
|
|
46
|
+
header + fit_parts(hunks, budget - header.bytesize) do |hunk, size|
|
|
47
|
+
hunk_header = header_for(hunk)
|
|
48
|
+
hunk_header + excerpt(hunk.delete_prefix(hunk_header), size - hunk_header.bytesize)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def header_for(part)
|
|
53
|
+
lines = part.lines
|
|
54
|
+
return lines.first.to_s if lines.first&.start_with?('@@')
|
|
55
|
+
|
|
56
|
+
lines.take_while { |line| !line.start_with?('@@') }.join
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def fit_parts(parts, budget)
|
|
60
|
+
return parts.join if parts.sum(&:bytesize) <= budget
|
|
61
|
+
|
|
62
|
+
# Keep file metadata and hunk headers before sharing space for changes.
|
|
63
|
+
minimums = parts.map { |part| [part.bytesize, header_for(part).bytesize + OMITTED.bytesize + 8].min }
|
|
64
|
+
return excerpt(parts.join, budget) if minimums.sum > budget
|
|
65
|
+
|
|
66
|
+
sizes = minimums.dup
|
|
67
|
+
remaining = budget - sizes.sum
|
|
68
|
+
order = parts.each_index.sort_by { |index| parts[index].bytesize - sizes[index] }
|
|
69
|
+
order.each_with_index do |index, position|
|
|
70
|
+
extra = [parts[index].bytesize - sizes[index], remaining / (order.length - position)].min
|
|
71
|
+
sizes[index] += extra
|
|
72
|
+
remaining -= extra
|
|
73
|
+
end
|
|
74
|
+
parts.each_with_index.map { |part, index| yield part, sizes[index] }.join
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def excerpt(text, budget)
|
|
78
|
+
return text if text.bytesize <= budget
|
|
79
|
+
|
|
80
|
+
available = budget - OMITTED.bytesize
|
|
81
|
+
head_size = (available + 1) / 2
|
|
82
|
+
tail_size = available / 2
|
|
83
|
+
head = text.byteslice(0, head_size).scrub('')
|
|
84
|
+
tail = text.byteslice(-tail_size, tail_size).scrub('')
|
|
85
|
+
head = head[0..head.rindex("\n")] if head.include?("\n")
|
|
86
|
+
tail = tail[(tail.index("\n") + 1)..] if tail.include?("\n")
|
|
87
|
+
head + OMITTED + tail
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
data/lib/jeeves/prompt.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Jeeves
|
|
|
10
10
|
@errors = errors
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def render(diff)
|
|
13
|
+
def render(diff, settings:)
|
|
14
14
|
path = @repository && File.join(@repository, '.jeeves_prompt')
|
|
15
15
|
unless path && File.file?(path)
|
|
16
16
|
path = @global_path
|
|
@@ -19,7 +19,21 @@ module Jeeves
|
|
|
19
19
|
template = File.read(path, encoding: 'UTF-8')
|
|
20
20
|
raise Error, "Prompt must contain {{DIFF}}: #{path}" unless template.include?('{{DIFF}}')
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
limit = settings.max_diff_bytes
|
|
23
|
+
if settings.prompt_budget
|
|
24
|
+
overhead = template.gsub('{{DIFF}}', '').bytesize
|
|
25
|
+
available = (settings.prompt_budget - overhead) / template.scan('{{DIFF}}').length
|
|
26
|
+
if available < 1
|
|
27
|
+
raise Error, 'The prompt leaves no room for a diff. Shorten the prompt or increase GIT_COMMIT_LOCAL_CONTEXT.'
|
|
28
|
+
end
|
|
29
|
+
limit = [limit, available].min
|
|
30
|
+
end
|
|
31
|
+
shortened = Diff.new(diff).fit(limit)
|
|
32
|
+
if shortened != diff
|
|
33
|
+
@errors.puts "Warning: diff shortened from #{diff.bytesize} to #{shortened.bytesize} bytes. " \
|
|
34
|
+
'Some content is omitted; the commit message may miss changes.'
|
|
35
|
+
end
|
|
36
|
+
template.gsub('{{DIFF}}') { shortened }
|
|
23
37
|
end
|
|
24
38
|
|
|
25
39
|
private
|
data/lib/jeeves/providers.rb
CHANGED
|
@@ -80,9 +80,7 @@ module Jeeves
|
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def generate(prompt)
|
|
83
|
-
|
|
84
|
-
budget = @settings.context - 1000 - 256
|
|
85
|
-
if prompt.bytesize > budget
|
|
83
|
+
if prompt.bytesize > @settings.prompt_budget
|
|
86
84
|
raise Error, 'Prompt exceeds the conservative local context budget. Split the changes or increase GIT_COMMIT_LOCAL_CONTEXT.'
|
|
87
85
|
end
|
|
88
86
|
body = {
|
data/lib/jeeves/settings.rb
CHANGED
|
@@ -16,7 +16,7 @@ module Jeeves
|
|
|
16
16
|
end
|
|
17
17
|
raise Error, 'The configured model must not be empty' if @model.strip.empty?
|
|
18
18
|
|
|
19
|
-
@context = integer('GIT_COMMIT_LOCAL_CONTEXT',
|
|
19
|
+
@context = integer('GIT_COMMIT_LOCAL_CONTEXT', 65_536) if @provider == 'ollama'
|
|
20
20
|
@max_diff_bytes = integer('GIT_COMMIT_MAX_DIFF_BYTES', 65_536)
|
|
21
21
|
@message_format = env.fetch('GIT_COMMIT_MESSAGE_FORMAT', 'conventional')
|
|
22
22
|
return if %w[conventional plain].include?(@message_format)
|
|
@@ -27,9 +27,11 @@ module Jeeves
|
|
|
27
27
|
def check_diff!(diff)
|
|
28
28
|
raise Error, 'Diff must be valid UTF-8 text.' unless diff.valid_encoding?
|
|
29
29
|
raise Error, 'No diff content provided.' if diff.strip.empty?
|
|
30
|
-
|
|
30
|
+
end
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
def prompt_budget
|
|
33
|
+
# One byte per token, with room for output and chat framing.
|
|
34
|
+
context - 1000 - 256 if context
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
private
|
data/lib/jeeves/version.rb
CHANGED
data/lib/jeeves.rb
CHANGED
|
@@ -10,6 +10,7 @@ module Jeeves
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
require 'jeeves/settings'
|
|
13
|
+
require 'jeeves/diff'
|
|
13
14
|
require 'jeeves/prompt'
|
|
14
15
|
require 'jeeves/message'
|
|
15
16
|
require 'jeeves/providers'
|
|
@@ -38,7 +39,7 @@ module Jeeves
|
|
|
38
39
|
provider = settings.provider == 'ollama' ? Providers::Ollama.new(settings) : Providers::OpenRouter.new(settings)
|
|
39
40
|
piped = !@input.tty? && !@input.closed?
|
|
40
41
|
if piped
|
|
41
|
-
diff = (@input.read
|
|
42
|
+
diff = (@input.read || +'').force_encoding(Encoding::UTF_8)
|
|
42
43
|
else
|
|
43
44
|
raise Error, 'Not inside a Git working tree.' unless @git.root
|
|
44
45
|
|
|
@@ -47,7 +48,8 @@ module Jeeves
|
|
|
47
48
|
diff = options[:all] && options[:dry_run] ? @git.preview_all : @git.diff
|
|
48
49
|
end
|
|
49
50
|
settings.check_diff!(diff)
|
|
50
|
-
prompt = Prompt.new(home: @env.fetch('HOME') { Dir.home }, repository: @git.root, errors: @errors)
|
|
51
|
+
prompt = Prompt.new(home: @env.fetch('HOME') { Dir.home }, repository: @git.root, errors: @errors)
|
|
52
|
+
.render(diff, settings: settings)
|
|
51
53
|
@errors.puts "Using #{settings.provider}: #{settings.model}" unless piped
|
|
52
54
|
message = Message.prepare(provider.generate(prompt), format: settings.message_format)
|
|
53
55
|
return show(message) if piped || options[:dry_run]
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jeeves-git-commit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin Bishop
|
|
@@ -79,6 +79,7 @@ files:
|
|
|
79
79
|
- bin/jeeves
|
|
80
80
|
- config/prompt
|
|
81
81
|
- lib/jeeves.rb
|
|
82
|
+
- lib/jeeves/diff.rb
|
|
82
83
|
- lib/jeeves/git_repository.rb
|
|
83
84
|
- lib/jeeves/message.rb
|
|
84
85
|
- lib/jeeves/prompt.rb
|