jeeves-git-commit 3.0.0 → 3.1.1
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 +20 -5
- data/lib/jeeves/diff.rb +90 -0
- data/lib/jeeves/prompt.rb +16 -2
- data/lib/jeeves/providers.rb +1 -3
- data/lib/jeeves/runtime.rb +4 -4
- data/lib/jeeves/settings.rb +5 -3
- data/lib/jeeves/version.rb +1 -1
- data/lib/jeeves.rb +4 -2
- metadata +2 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4bf456f354f172b140a241db27b10459ae17c6b96748f3cab435f5dd7024c6ad
|
|
4
|
+
data.tar.gz: d8764cc9556d4b8e3d220310c43ec7163fa6195e959445240025d7ab94a9ae4d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 36df36dafc54fd9a2c609d238417f2f8740e7090c255590a84aa87da755726e63a5745f7fc0e8e64653c934f3b3beb3b8417e8f92c388cba2bce43c41c689b6a
|
|
7
|
+
data.tar.gz: 50077793c804ab2d87e6681b3c07f18ee4be560487c628b9fa249d6bce4c66a58a50eb3beb4ffbce267e1303deeb5fc8cf1c818fea2cf782d4986f6e994b3dfe
|
data/README.md
CHANGED
|
@@ -17,7 +17,7 @@ never going to make the missing endpoint appear.
|
|
|
17
17
|
|
|
18
18
|
## Install
|
|
19
19
|
|
|
20
|
-
Use Ruby 3.3
|
|
20
|
+
Use Ruby 3.3 or newer and Git. Development uses Homebrew's current Ruby.
|
|
21
21
|
|
|
22
22
|
```sh
|
|
23
23
|
gem install jeeves-git-commit
|
|
@@ -50,7 +50,7 @@ Plain `jeeves` then uses local generation; no per-command flag or cloud key is
|
|
|
50
50
|
needed. See [configuration](docs/configuration.md) for installation, fish
|
|
51
51
|
syntax, all settings, prompt customization, and input limits.
|
|
52
52
|
|
|
53
|
-
Version 3 requires Ruby 3.3
|
|
53
|
+
Version 3 requires Ruby 3.3 or newer. It validates conventional commit
|
|
54
54
|
subjects by default; use `GIT_COMMIT_MESSAGE_FORMAT=plain` for a custom format.
|
|
55
55
|
Existing personal prompts are preserved. See the [release notes](docs/releases.md).
|
|
56
56
|
|
|
@@ -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
|
|
@@ -108,13 +113,15 @@ bin/doctor
|
|
|
108
113
|
```
|
|
109
114
|
|
|
110
115
|
`bin/check` runs fast foundation checks. `bin/check --documents-only` checks
|
|
111
|
-
Markdown. `bin/check --full` adds
|
|
116
|
+
Markdown. `bin/check --full` adds `bin/check-application`: Ruby tests, lint,
|
|
112
117
|
and gem build. Application checks are not part of the two routine modes.
|
|
113
118
|
Built gems go in `gems/`. Publishing with `rake push` is an explicit release
|
|
114
119
|
action; it is never part of normal validation.
|
|
115
120
|
|
|
116
|
-
- [Development workflow](docs/development-workflow.md): setup,
|
|
117
|
-
|
|
121
|
+
- [Development workflow](docs/development-workflow.md): setup, validation,
|
|
122
|
+
runtime policy, and releases.
|
|
123
|
+
- [Project Starter foundation](docs/foundation/README.md): knowledge search,
|
|
124
|
+
worktrees, hooks, task tracking, and shared engineering policy.
|
|
118
125
|
- [Documents](docs/README.md) and [memory](memory/README.md).
|
|
119
126
|
- [GitHub Issues](https://github.com/jubishop/Jeeves/issues): bugs and planned work.
|
|
120
127
|
- [Agent instructions](AGENTS.md).
|
|
@@ -124,3 +131,11 @@ action; it is never part of normal validation.
|
|
|
124
131
|
|
|
125
132
|
Jeeves uses the [MIT license](LICENSE). The adopted project foundation retains
|
|
126
133
|
its separate [license notice](LICENSE.project-starter).
|
|
134
|
+
|
|
135
|
+
## Local task tracking
|
|
136
|
+
|
|
137
|
+
Use `td` for local tasks, progress, blockers, and session handoffs. After cloning,
|
|
138
|
+
install td and run `td init` in the primary checkout. Use `td status` or
|
|
139
|
+
`td monitor` to inspect progress. Follow the [task workflow](docs/foundation/task-tracking.md)
|
|
140
|
+
for setup, review, worktrees, and local data. Keep GitHub Issues for shared scope
|
|
141
|
+
and acceptance criteria, linked from related td tasks.
|
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/runtime.rb
CHANGED
|
@@ -4,12 +4,12 @@ require 'rubygems/requirement'
|
|
|
4
4
|
|
|
5
5
|
module Jeeves
|
|
6
6
|
module Runtime
|
|
7
|
-
REQUIREMENT = Gem::Requirement.new('>= 3.3'
|
|
7
|
+
REQUIREMENT = Gem::Requirement.new('>= 3.3').freeze
|
|
8
8
|
|
|
9
|
-
def self.check!
|
|
10
|
-
return if REQUIREMENT.satisfied_by?(Gem::Version.new(
|
|
9
|
+
def self.check!(version = RUBY_VERSION)
|
|
10
|
+
return if REQUIREMENT.satisfied_by?(Gem::Version.new(version))
|
|
11
11
|
|
|
12
|
-
abort "Jeeves requires Ruby #{REQUIREMENT}; found #{
|
|
12
|
+
abort "Jeeves requires Ruby #{REQUIREMENT}; found #{version}. Select the latest stable Ruby with your version manager."
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
end
|
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.1
|
|
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
|
|
@@ -98,9 +99,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
98
99
|
- - ">="
|
|
99
100
|
- !ruby/object:Gem::Version
|
|
100
101
|
version: '3.3'
|
|
101
|
-
- - "<"
|
|
102
|
-
- !ruby/object:Gem::Version
|
|
103
|
-
version: '4.1'
|
|
104
102
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
105
103
|
requirements:
|
|
106
104
|
- - ">="
|