gem_hadar 2.10.0 → 2.11.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/.all_images.yml +1 -1
- data/Rakefile +2 -1
- data/gem_hadar.gemspec +6 -5
- data/lib/gem_hadar/changelog_generator.rb +292 -0
- data/lib/gem_hadar/github.rb +1 -2
- data/lib/gem_hadar/ollama_support.rb +72 -0
- data/lib/gem_hadar/prompt_template.rb +46 -0
- data/lib/gem_hadar/rvm_config.rb +0 -3
- data/lib/gem_hadar/utils.rb +81 -41
- data/lib/gem_hadar/version.rb +1 -1
- data/lib/gem_hadar/version_spec.rb +144 -0
- data/lib/gem_hadar/warn.rb +0 -2
- data/lib/gem_hadar.rb +183 -120
- metadata +23 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d873e582074092072360c3800181732ab53d2e27a5e7663262c36b60bd3f96a6
|
|
4
|
+
data.tar.gz: 6074b1585ff125a9ee21f18e0231ea34840a24ec1246cfce41ff1ca58ad60603
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 57c0aeac59a2447bfd6073cbf4ea7f4411e3930249e863d0f6a9dffa171933a40d94646e1eb4a5a03dc5fd1519178bb613fa0a1c9b02b684e918af36e42fc0d7
|
|
7
|
+
data.tar.gz: 648d51b596c0f5b50a58d4c0e9189775356ad7de3657fe959bfe31af67b17f9634c4d89c2ee2672f2d4dc92e4f7d4be5297cec607cbb51ce22af73280d15d898
|
data/.all_images.yml
CHANGED
data/Rakefile
CHANGED
|
@@ -12,7 +12,7 @@ GemHadar do
|
|
|
12
12
|
description 'This library contains some useful functionality to support the development of Ruby Gems'
|
|
13
13
|
test_dir 'spec'
|
|
14
14
|
ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', '.AppleDouble',
|
|
15
|
-
|
|
15
|
+
'.bundle', '.yardoc', 'doc', 'tags', 'cscope.out'
|
|
16
16
|
package_ignore '.gitignore', 'VERSION'
|
|
17
17
|
readme 'README.md'
|
|
18
18
|
|
|
@@ -25,6 +25,7 @@ GemHadar do
|
|
|
25
25
|
dependency 'tins', '~> 1.0'
|
|
26
26
|
dependency 'term-ansicolor', '~> 1.0'
|
|
27
27
|
dependency 'ollama-ruby', '~> 1.17'
|
|
28
|
+
dependency 'infobar', '~> 0.11'
|
|
28
29
|
dependency 'mize'
|
|
29
30
|
dependency 'rake'
|
|
30
31
|
dependency 'yard'
|
data/gem_hadar.gemspec
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
# stub: gem_hadar 2.
|
|
2
|
+
# stub: gem_hadar 2.11.0 ruby lib
|
|
3
3
|
|
|
4
4
|
Gem::Specification.new do |s|
|
|
5
5
|
s.name = "gem_hadar".freeze
|
|
6
|
-
s.version = "2.
|
|
6
|
+
s.version = "2.11.0".freeze
|
|
7
7
|
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
|
9
9
|
s.require_paths = ["lib".freeze]
|
|
@@ -12,8 +12,8 @@ Gem::Specification.new do |s|
|
|
|
12
12
|
s.description = "This library contains some useful functionality to support the development of Ruby Gems".freeze
|
|
13
13
|
s.email = "flori@ping.de".freeze
|
|
14
14
|
s.executables = ["gem_hadar".freeze]
|
|
15
|
-
s.extra_rdoc_files = ["README.md".freeze, "lib/gem_hadar.rb".freeze, "lib/gem_hadar/github.rb".freeze, "lib/gem_hadar/prompt_template.rb".freeze, "lib/gem_hadar/rvm_config.rb".freeze, "lib/gem_hadar/setup.rb".freeze, "lib/gem_hadar/simplecov.rb".freeze, "lib/gem_hadar/template_compiler.rb".freeze, "lib/gem_hadar/utils.rb".freeze, "lib/gem_hadar/version.rb".freeze, "lib/gem_hadar/warn.rb".freeze]
|
|
16
|
-
s.files = [".all_images.yml".freeze, ".github/workflows/static.yml".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "bin/gem_hadar".freeze, "gem_hadar.gemspec".freeze, "lib/gem_hadar.rb".freeze, "lib/gem_hadar/github.rb".freeze, "lib/gem_hadar/github_workflows/static.yml.erb".freeze, "lib/gem_hadar/prompt_template.rb".freeze, "lib/gem_hadar/rvm_config.rb".freeze, "lib/gem_hadar/setup.rb".freeze, "lib/gem_hadar/simplecov.rb".freeze, "lib/gem_hadar/template_compiler.rb".freeze, "lib/gem_hadar/utils.rb".freeze, "lib/gem_hadar/version.rb".freeze, "lib/gem_hadar/warn.rb".freeze, "spec/gem_hadar_spec.rb".freeze, "spec/spec_helper.rb".freeze]
|
|
15
|
+
s.extra_rdoc_files = ["README.md".freeze, "lib/gem_hadar.rb".freeze, "lib/gem_hadar/changelog_generator.rb".freeze, "lib/gem_hadar/github.rb".freeze, "lib/gem_hadar/ollama_support.rb".freeze, "lib/gem_hadar/prompt_template.rb".freeze, "lib/gem_hadar/rvm_config.rb".freeze, "lib/gem_hadar/setup.rb".freeze, "lib/gem_hadar/simplecov.rb".freeze, "lib/gem_hadar/template_compiler.rb".freeze, "lib/gem_hadar/utils.rb".freeze, "lib/gem_hadar/version.rb".freeze, "lib/gem_hadar/version_spec.rb".freeze, "lib/gem_hadar/warn.rb".freeze]
|
|
16
|
+
s.files = [".all_images.yml".freeze, ".github/workflows/static.yml".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "bin/gem_hadar".freeze, "gem_hadar.gemspec".freeze, "lib/gem_hadar.rb".freeze, "lib/gem_hadar/changelog_generator.rb".freeze, "lib/gem_hadar/github.rb".freeze, "lib/gem_hadar/github_workflows/static.yml.erb".freeze, "lib/gem_hadar/ollama_support.rb".freeze, "lib/gem_hadar/prompt_template.rb".freeze, "lib/gem_hadar/rvm_config.rb".freeze, "lib/gem_hadar/setup.rb".freeze, "lib/gem_hadar/simplecov.rb".freeze, "lib/gem_hadar/template_compiler.rb".freeze, "lib/gem_hadar/utils.rb".freeze, "lib/gem_hadar/version.rb".freeze, "lib/gem_hadar/version_spec.rb".freeze, "lib/gem_hadar/warn.rb".freeze, "spec/gem_hadar_spec.rb".freeze, "spec/spec_helper.rb".freeze]
|
|
17
17
|
s.homepage = "https://github.com/flori/gem_hadar".freeze
|
|
18
18
|
s.licenses = ["MIT".freeze]
|
|
19
19
|
s.rdoc_options = ["--title".freeze, "GemHadar - Library for the development of Ruby Gems".freeze, "--main".freeze, "README.md".freeze]
|
|
@@ -23,13 +23,14 @@ Gem::Specification.new do |s|
|
|
|
23
23
|
|
|
24
24
|
s.specification_version = 4
|
|
25
25
|
|
|
26
|
-
s.add_development_dependency(%q<gem_hadar>.freeze, ["~> 2.
|
|
26
|
+
s.add_development_dependency(%q<gem_hadar>.freeze, ["~> 2.10".freeze])
|
|
27
27
|
s.add_development_dependency(%q<all_images>.freeze, [">= 0".freeze])
|
|
28
28
|
s.add_development_dependency(%q<rspec>.freeze, ["~> 3.13".freeze])
|
|
29
29
|
s.add_development_dependency(%q<simplecov>.freeze, [">= 0".freeze])
|
|
30
30
|
s.add_runtime_dependency(%q<tins>.freeze, ["~> 1.0".freeze])
|
|
31
31
|
s.add_runtime_dependency(%q<term-ansicolor>.freeze, ["~> 1.0".freeze])
|
|
32
32
|
s.add_runtime_dependency(%q<ollama-ruby>.freeze, ["~> 1.17".freeze])
|
|
33
|
+
s.add_runtime_dependency(%q<infobar>.freeze, ["~> 0.11".freeze])
|
|
33
34
|
s.add_runtime_dependency(%q<mize>.freeze, [">= 0".freeze])
|
|
34
35
|
s.add_runtime_dependency(%q<rake>.freeze, [">= 0".freeze])
|
|
35
36
|
s.add_runtime_dependency(%q<yard>.freeze, [">= 0".freeze])
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'tempfile'
|
|
3
|
+
|
|
4
|
+
class GemHadar
|
|
5
|
+
# A class that generates changelog entries by analyzing Git history and AI
|
|
6
|
+
# processing
|
|
7
|
+
#
|
|
8
|
+
# The ChangelogGenerator class provides functionality to create structured
|
|
9
|
+
# changelog entries based on Git commit history. It can generate individual
|
|
10
|
+
# changelog entries for specific version ranges or create complete changelogs
|
|
11
|
+
# including all version entries. The class integrates with AI models to
|
|
12
|
+
# produce human-readable changelog content by processing Git logs through
|
|
13
|
+
# configured prompts and models.
|
|
14
|
+
#
|
|
15
|
+
# @example Generating a changelog entry for a version range
|
|
16
|
+
# generator = GemHadar::ChangelogGenerator.new
|
|
17
|
+
# entry = generator.generate('v1.0.0', 'v1.2.0')
|
|
18
|
+
#
|
|
19
|
+
# @example Generating a complete changelog
|
|
20
|
+
# generator = GemHadar::ChangelogGenerator.new
|
|
21
|
+
# generator.generate_full(STDOUT)
|
|
22
|
+
#
|
|
23
|
+
# @example Adding changelog entries to an existing file
|
|
24
|
+
# GemHadar::ChangelogGenerator.add_to_file('CHANGELOG.md')
|
|
25
|
+
class ChangelogGenerator
|
|
26
|
+
include GemHadar::Utils
|
|
27
|
+
include GemHadar::PromptTemplate
|
|
28
|
+
|
|
29
|
+
def initialize(gem_hadar)
|
|
30
|
+
@gem_hadar = gem_hadar
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The generate method creates a changelog entry by analyzing Git history
|
|
34
|
+
# and AI processing.
|
|
35
|
+
#
|
|
36
|
+
# This method retrieves the Git log for a specified range of commits,
|
|
37
|
+
# processes the log through an AI model using configured prompts, and
|
|
38
|
+
# formats the result into a markdown changelog entry with a date header.
|
|
39
|
+
#
|
|
40
|
+
# @param from [ String ] the starting version or commit reference for the
|
|
41
|
+
# Git log range
|
|
42
|
+
# @param to [ String ] the ending version or commit reference for the Git
|
|
43
|
+
# log range, defaults to 'HEAD'
|
|
44
|
+
#
|
|
45
|
+
# @return [ String ] a formatted markdown changelog entry including date
|
|
46
|
+
# and AI-generated content
|
|
47
|
+
# @return [ String ] a minimal changelog entry with just date and version
|
|
48
|
+
# when no changes are found
|
|
49
|
+
def generate(from, to = 'HEAD')
|
|
50
|
+
from_spec = GemHadar::VersionSpec[from]
|
|
51
|
+
to_spec = GemHadar::VersionSpec[to]
|
|
52
|
+
|
|
53
|
+
range = "#{from_spec.tag}..#{to_spec.tag}"
|
|
54
|
+
|
|
55
|
+
log = `git log #{range}`
|
|
56
|
+
$?.success? or raise "Failed to get git log for range #{range}"
|
|
57
|
+
|
|
58
|
+
date = `git log -n1 --pretty='format:%cd' --date=short #{to_spec.tag.inspect}`.chomp
|
|
59
|
+
|
|
60
|
+
if log.strip.empty?
|
|
61
|
+
return "\n## #{date} #{to_spec.without_prefix.to_s}\n"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
system = xdg_config('gem_hadar', 'changelog_system_prompt.txt', default_changelog_system_prompt)
|
|
65
|
+
prompt_template = xdg_config('gem_hadar', 'changelog_prompt.txt', default_changelog_prompt)
|
|
66
|
+
prompt = prompt_template % { log_diff: log }
|
|
67
|
+
|
|
68
|
+
response = ollama_generate(system:, prompt:)
|
|
69
|
+
|
|
70
|
+
changes = response.gsub(/\t/, ' ')
|
|
71
|
+
|
|
72
|
+
return "\n## #{date} #{to_spec.tag}\n\n#{changes}\n"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# The generate_range method creates a changelog for a specific version
|
|
76
|
+
# range by processing Git log differences and AI-generated content
|
|
77
|
+
#
|
|
78
|
+
# This method retrieves version tags within a specified range, filters them
|
|
79
|
+
# based on the provided version boundaries, generates changelog entries
|
|
80
|
+
# for each version in the range, and writes the complete changelog to the
|
|
81
|
+
# provided output stream
|
|
82
|
+
#
|
|
83
|
+
# @param output [ IO ] the output stream to which the changelog will be
|
|
84
|
+
# written
|
|
85
|
+
# @param from [ String ] the starting version or commit reference for the
|
|
86
|
+
# range
|
|
87
|
+
# @param to [ String ] the ending version or commit reference for the
|
|
88
|
+
# range, defaults to 'HEAD'
|
|
89
|
+
def generate_range(output, from, to)
|
|
90
|
+
from_spec = GemHadar::VersionSpec[from]
|
|
91
|
+
to_spec = GemHadar::VersionSpec[to]
|
|
92
|
+
|
|
93
|
+
versions = read_versions
|
|
94
|
+
|
|
95
|
+
unless versions.any?
|
|
96
|
+
raise "No version tags found in repository"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
versions = versions.select do |v|
|
|
100
|
+
v.version >= from_spec.version && v.version <= to_spec.version
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
changelog = generate_changelog(versions)
|
|
104
|
+
|
|
105
|
+
output << changelog.join("")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# The generate_full method creates a complete changelog by processing all
|
|
109
|
+
# version tags in the repository and generating entries for each
|
|
110
|
+
# consecutive pair of versions
|
|
111
|
+
#
|
|
112
|
+
# This method retrieves all semantic version tags from the Git repository,
|
|
113
|
+
# sorts them, and generates changelog entries for each pair of consecutive
|
|
114
|
+
# versions. It also adds an initial entry for the first version with a
|
|
115
|
+
# "Start" marker
|
|
116
|
+
#
|
|
117
|
+
# @param output [ IO ] the output stream to which the complete changelog
|
|
118
|
+
# will be written
|
|
119
|
+
#
|
|
120
|
+
# @return [ String ] a complete changelog including all version entries and
|
|
121
|
+
# a header
|
|
122
|
+
#
|
|
123
|
+
# @raise [ RuntimeError ] if no version tags are found in the repository
|
|
124
|
+
def generate_full(output)
|
|
125
|
+
versions = read_versions
|
|
126
|
+
|
|
127
|
+
unless versions.any?
|
|
128
|
+
raise "No version tags found in repository"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
first_version = versions.first
|
|
132
|
+
date = `git log -n1 --pretty='format:%cd' --date=short #{first_version.tag.to_s}`.chomp
|
|
133
|
+
changelog = ["\n## #{date} #{first_version.tag.to_s}\n\n* Start\n"]
|
|
134
|
+
|
|
135
|
+
changelog = generate_changelog(versions, changelog:)
|
|
136
|
+
|
|
137
|
+
changelog << "# Changes\n"
|
|
138
|
+
|
|
139
|
+
output << changelog.join("")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# The add_to_file method appends new changelog entries to an existing
|
|
143
|
+
# changelog file
|
|
144
|
+
#
|
|
145
|
+
# This method identifies the highest version already present in the
|
|
146
|
+
# changelog file, retrieves all subsequent version tags from the Git
|
|
147
|
+
# repository, and generates changelog entries for each consecutive pair of
|
|
148
|
+
# versions. It then inserts these entries into the file after the existing
|
|
149
|
+
# content, maintaining the chronological order of changes.
|
|
150
|
+
#
|
|
151
|
+
# @param filename [ String ] the path to the changelog file to which
|
|
152
|
+
# entries will be added
|
|
153
|
+
def add_to_file(filename)
|
|
154
|
+
highest_version = find_highest_version_spec(filename)
|
|
155
|
+
|
|
156
|
+
if highest_version
|
|
157
|
+
versions = read_versions
|
|
158
|
+
versions = versions.drop_while { |t| t.version < highest_version.version }
|
|
159
|
+
else
|
|
160
|
+
raise ArgumentError, "Could not find highest version in #{filename.inspect}"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
return if versions.size < 2
|
|
164
|
+
|
|
165
|
+
changelog = generate_changelog(versions)
|
|
166
|
+
return if changelog.empty?
|
|
167
|
+
inject_into_filename(filename, changelog)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
private
|
|
171
|
+
|
|
172
|
+
# The ollama_generate method delegates AI generation requests to the
|
|
173
|
+
# associated GemHadar instance.
|
|
174
|
+
#
|
|
175
|
+
# This method acts as a proxy that forwards the provided options to the
|
|
176
|
+
# ollama_generate method of the parent GemHadar object, enabling AI-powered
|
|
177
|
+
# text generation using configured Ollama models.
|
|
178
|
+
#
|
|
179
|
+
# @param opts [ Hash ] the options to pass to the AI generation method
|
|
180
|
+
#
|
|
181
|
+
# @return [ String, nil ] the generated response from the AI model or nil
|
|
182
|
+
# if generation fails
|
|
183
|
+
def ollama_generate(**opts)
|
|
184
|
+
@gem_hadar.ollama_generate(**opts)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# The read_versions method retrieves and processes semantic version tags
|
|
188
|
+
# from the Git repository.
|
|
189
|
+
#
|
|
190
|
+
# This method fetches all Git tags from the repository, filters them to
|
|
191
|
+
# include only those that match semantic versioning patterns (containing
|
|
192
|
+
# three numeric components separated by dots), removes any 'v' prefix from
|
|
193
|
+
# the tags, and sorts the resulting version specifications
|
|
194
|
+
# in ascending order according to semantic versioning rules.
|
|
195
|
+
#
|
|
196
|
+
# @return [ Array<GemHadar::VersionSpec> ] an array of VersionSpec objects
|
|
197
|
+
# representing the semantic versions found in the repository, sorted in
|
|
198
|
+
# ascending order
|
|
199
|
+
def read_versions
|
|
200
|
+
tags = `git tag`.lines.grep(/^v?\d+\.\d+\.\d+$/).map(&:chomp)
|
|
201
|
+
|
|
202
|
+
versions = tags.map { |tag| GemHadar::VersionSpec[tag] }
|
|
203
|
+
versions = versions.sort_by(&:version)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# The generate_changelog method creates a series of changelog entries by
|
|
207
|
+
# processing consecutive version pairs.
|
|
208
|
+
#
|
|
209
|
+
# This method takes an array of version specifications, iterates through
|
|
210
|
+
# them in pairs, and generates AI-powered changelog entries for each range.
|
|
211
|
+
# It uses the ollama_generate method to produce content for each version
|
|
212
|
+
# interval and collects the results in reverse order.
|
|
213
|
+
#
|
|
214
|
+
# @param versions [ Array<GemHadar::VersionSpec> ] an array of version
|
|
215
|
+
# specifications to process
|
|
216
|
+
#
|
|
217
|
+
# @return [ Array<String> ] an array of changelog entry strings in reverse
|
|
218
|
+
# chronological order
|
|
219
|
+
def generate_changelog(versions, changelog: [])
|
|
220
|
+
versions = versions.each_cons(2).
|
|
221
|
+
with_infobar(total: versions.size - 1, label: 'Change')
|
|
222
|
+
versions.each do |range_from, range_to|
|
|
223
|
+
changelog << generate(range_from, range_to)
|
|
224
|
+
+infobar
|
|
225
|
+
end
|
|
226
|
+
changelog.reverse
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# The inject_into_filename method inserts changelog entries into a
|
|
230
|
+
# specified file.
|
|
231
|
+
#
|
|
232
|
+
# This method reads an existing file line by line and identifies the
|
|
233
|
+
# location of a "# Changes" header. When this header is found, it inserts
|
|
234
|
+
# the provided changelog entries immediately after the header
|
|
235
|
+
# and before the next empty line.
|
|
236
|
+
#
|
|
237
|
+
# @param filename [ String ] the path to the file into which changelog
|
|
238
|
+
# entries will be injected
|
|
239
|
+
# @param changelog [ Array<String> ] an array of changelog entry strings to
|
|
240
|
+
# be inserted into the file
|
|
241
|
+
#
|
|
242
|
+
# @see GemHadar::ChangelogGenerator#add_to_file
|
|
243
|
+
def inject_into_filename(filename, changelog)
|
|
244
|
+
File.open(filename) do |input|
|
|
245
|
+
File.secure_write(filename) do |output|
|
|
246
|
+
start_add = nil
|
|
247
|
+
input.each do |line|
|
|
248
|
+
if start_add.nil? && line =~ /^# Changes$/
|
|
249
|
+
start_add = true
|
|
250
|
+
output.puts line
|
|
251
|
+
next
|
|
252
|
+
end
|
|
253
|
+
if start_add && line =~ /^$/
|
|
254
|
+
changelog.each do |entry|
|
|
255
|
+
output.puts entry
|
|
256
|
+
end
|
|
257
|
+
output.puts line
|
|
258
|
+
start_add = false
|
|
259
|
+
next
|
|
260
|
+
end
|
|
261
|
+
output.puts line
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# The find_highest_version_spec method extracts version specifications from
|
|
268
|
+
# a changelog file and returns the highest version found
|
|
269
|
+
#
|
|
270
|
+
# This method reads through the specified file line by line, scanning for
|
|
271
|
+
# lines that match the pattern of a changelog entry header with a version
|
|
272
|
+
# number, and collects all found version specifications. It then determines
|
|
273
|
+
# and returns the version specification with
|
|
274
|
+
# the highest version number
|
|
275
|
+
#
|
|
276
|
+
# @param filename [ String ] the path to the changelog file to process
|
|
277
|
+
#
|
|
278
|
+
# @return [ GemHadar::VersionSpec, nil ] the highest version specification
|
|
279
|
+
# found in the file, or nil if no versions are found
|
|
280
|
+
def find_highest_version_spec(filename)
|
|
281
|
+
File.open(filename, ?r) do |input|
|
|
282
|
+
specs = []
|
|
283
|
+
input.each do |line|
|
|
284
|
+
line.scan(/^## \d{4}-\d{2}-\d{2} v(\d+\.\d+\.\d+)$/) do
|
|
285
|
+
specs << GemHadar::VersionSpec[$1]
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
specs.max_by(&:version)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
data/lib/gem_hadar/github.rb
CHANGED
|
@@ -6,8 +6,6 @@ require 'json'
|
|
|
6
6
|
# This module contains the ReleaseCreator class which handles the creation of
|
|
7
7
|
# GitHub releases through the GitHub Releases API. It manages authentication,
|
|
8
8
|
# request construction, and response processing for release operations.
|
|
9
|
-
#
|
|
10
|
-
# @see GemHadar::GitHub::ReleaseCreator
|
|
11
9
|
module GemHadar::GitHub
|
|
12
10
|
end
|
|
13
11
|
|
|
@@ -74,6 +72,7 @@ class GemHadar::GitHub::ReleaseCreator
|
|
|
74
72
|
# details about the created release
|
|
75
73
|
#
|
|
76
74
|
# @raise [ RuntimeError ] if the GitHub API request fails with a non-success status code
|
|
75
|
+
# @raise [ JSON::ParserError ] if the API response cannot be parsed as JSON
|
|
77
76
|
def perform(tag_name:, target_commitish:, body:, name: tag_name, draft: false, prerelease: false)
|
|
78
77
|
uri = URI("#{self.class.github_api_url}/repos/#@owner/#@repo/releases")
|
|
79
78
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'ollama'
|
|
2
|
+
|
|
3
|
+
# A module that provides Ollama AI integration support for GemHadar.
|
|
4
|
+
#
|
|
5
|
+
# This module includes methods for initializing an Ollama client, retrieving
|
|
6
|
+
# the configured AI model name, and fetching model options for AI generation.
|
|
7
|
+
# It also provides a method to generate responses
|
|
8
|
+
# from Ollama AI models using the configured settings.
|
|
9
|
+
module GemHadar::OllamaSupport
|
|
10
|
+
# The ollama method initializes and returns an Ollama client instance.
|
|
11
|
+
#
|
|
12
|
+
# This method constructs an Ollama client by first determining the base URL
|
|
13
|
+
# from the OLLAMA_URL environment variable, falling back to the OLLAMA_HOST
|
|
14
|
+
# environment variable with a default value of 'localhost:11434'. It then
|
|
15
|
+
# creates a client configuration hash including read and connect timeouts
|
|
16
|
+
# before instantiating and returning a new Ollama::Client object.
|
|
17
|
+
#
|
|
18
|
+
# @return [ Ollama::Client ] a configured Ollama client instance ready for use
|
|
19
|
+
def ollama
|
|
20
|
+
base_url = ENV['OLLAMA_URL'] || "http://%s" % ENV.fetch('OLLAMA_HOST', 'localhost:11434')
|
|
21
|
+
client_config = {
|
|
22
|
+
base_url: base_url,
|
|
23
|
+
read_timeout: 600,
|
|
24
|
+
connect_timeout: 60
|
|
25
|
+
}
|
|
26
|
+
Ollama::Client.new(**client_config)
|
|
27
|
+
end
|
|
28
|
+
memoize method: :ollama
|
|
29
|
+
|
|
30
|
+
# The ollama_model method retrieves the name of the Ollama AI model to be
|
|
31
|
+
# used for generating responses.
|
|
32
|
+
#
|
|
33
|
+
# It first checks the OLLAMA_MODEL environment variable for a custom model
|
|
34
|
+
# specification. If the environment variable is not set, it falls back to
|
|
35
|
+
# using the default model name, which is determined by the
|
|
36
|
+
# ollama_model_default dsl method.
|
|
37
|
+
#
|
|
38
|
+
# @return [ String ] the name of the Ollama AI model to be used
|
|
39
|
+
def ollama_model
|
|
40
|
+
ENV.fetch('OLLAMA_MODEL', ollama_model_default)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
alias model ollama_model
|
|
44
|
+
|
|
45
|
+
# The options method retrieves and configures the Ollama model options for AI
|
|
46
|
+
# generation.
|
|
47
|
+
#
|
|
48
|
+
# This method fetches the JSON configuration for the Ollama model from the
|
|
49
|
+
# OLLAMA_MODEL_OPTIONS environment variable, or uses an empty JSON object if
|
|
50
|
+
# the variable is not set. It then merges default values for temperature,
|
|
51
|
+
# top_p, and min_p parameters to ensure consistent AI response
|
|
52
|
+
# characteristics.
|
|
53
|
+
#
|
|
54
|
+
# @return [ Hash ] a hash containing the merged Ollama model options
|
|
55
|
+
# including default values for temperature, top_p, and min_p
|
|
56
|
+
def options
|
|
57
|
+
options = JSON(ENV.fetch('OLLAMA_MODEL_OPTIONS', '{}'))
|
|
58
|
+
options.merge!("temperature" => 0, "top_p" => 1, "min_p" => 0.1)
|
|
59
|
+
end
|
|
60
|
+
memoize method: :options
|
|
61
|
+
|
|
62
|
+
# Generates a response from an AI model using the Ollama::Client.
|
|
63
|
+
#
|
|
64
|
+
# @param [String] system The system prompt for the AI model.
|
|
65
|
+
# @param [String] prompt The user prompt to generate a response to.
|
|
66
|
+
# @return [String, nil] The generated response or nil if generation fails.
|
|
67
|
+
def ollama_generate(system:, prompt:)
|
|
68
|
+
ollama.generate(
|
|
69
|
+
model:, system:, prompt:, options:, stream: false, think: false
|
|
70
|
+
).response
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -96,4 +96,50 @@ module GemHadar::PromptTemplate
|
|
|
96
96
|
'build'.
|
|
97
97
|
EOT
|
|
98
98
|
end
|
|
99
|
+
|
|
100
|
+
# The default_changelog_system_prompt method returns the system prompt used
|
|
101
|
+
# for generating changelog entries.
|
|
102
|
+
#
|
|
103
|
+
# This prompt instructs the AI model to act as a Ruby programmer who creates
|
|
104
|
+
# markdown-formatted changelog entries for new releases. The generated
|
|
105
|
+
# content helps users understand what has changed in the software while
|
|
106
|
+
# maintaining a professional tone and format.
|
|
107
|
+
#
|
|
108
|
+
# @return [ String ] the system prompt for changelog generation
|
|
109
|
+
def default_changelog_system_prompt
|
|
110
|
+
<<~EOT
|
|
111
|
+
You are a Ruby programmer generating a change log entry in markdown syntax,
|
|
112
|
+
summarizing the code changes for a new version in a professional way.
|
|
113
|
+
EOT
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# The default_changelog_prompt method returns the prompt template used for
|
|
117
|
+
# generating changelog entries.
|
|
118
|
+
#
|
|
119
|
+
# This prompt instructs the AI model to create a structured changelog entry
|
|
120
|
+
# based on Git commit history. It provides detailed guidelines for formatting
|
|
121
|
+
# the output, including how to summarize changes, mark code elements with
|
|
122
|
+
# appropriate markdown syntax, and exclude trivial updates like version bumps
|
|
123
|
+
# while focusing on significant functional changes.
|
|
124
|
+
def default_changelog_prompt
|
|
125
|
+
<<~EOT
|
|
126
|
+
Generate a changelog entry for the following Git commit history:
|
|
127
|
+
|
|
128
|
+
%{log_diff}
|
|
129
|
+
|
|
130
|
+
- Summarize the changes in the following git log messages as bullet points.
|
|
131
|
+
- Don't mention the version of the change set
|
|
132
|
+
- Skip bullet points about version bumps.
|
|
133
|
+
- List significant changes as bullet points using markdown when applicable.
|
|
134
|
+
- Mark all names and values for variables, methods, functions, and
|
|
135
|
+
constants, you see in the messages as markdown code surrounded by
|
|
136
|
+
backtick characters.
|
|
137
|
+
- Mark all version numbers you see in the messages as markdown bold
|
|
138
|
+
surrounded by two asterisk characters.
|
|
139
|
+
- Don't refer to single commits by sha1 hash.
|
|
140
|
+
- Don't add information about changes you are not sure about.
|
|
141
|
+
- Don't output any additional chatty remarks, notes, introductions,
|
|
142
|
+
communications, etc.
|
|
143
|
+
EOT
|
|
144
|
+
end
|
|
99
145
|
end
|
data/lib/gem_hadar/rvm_config.rb
CHANGED
|
@@ -37,7 +37,6 @@ class GemHadar
|
|
|
37
37
|
# an argument, it sets the Ruby version to be used.
|
|
38
38
|
#
|
|
39
39
|
# @return [ String ] the Ruby version string configured for RVM use
|
|
40
|
-
# @see GemHadar::RvmConfig
|
|
41
40
|
dsl_accessor :use do `rvm tools strings`.split(/\n/).full?(:last) || 'ruby' end
|
|
42
41
|
|
|
43
42
|
# The gemset method retrieves or sets the RVM gemset name for the project.
|
|
@@ -49,8 +48,6 @@ class GemHadar
|
|
|
49
48
|
# When called with an argument, it sets the gemset name to be used with RVM.
|
|
50
49
|
#
|
|
51
50
|
# @return [ String ] the RVM gemset name configured for the project
|
|
52
|
-
# @see GemHadar::RvmConfig#use
|
|
53
|
-
# @see GemHadar::RvmConfig
|
|
54
51
|
dsl_accessor :gemset do @outer_scope.name end
|
|
55
52
|
end
|
|
56
53
|
end
|