hiiro 0.1.313 → 0.1.315

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: d0760d73ced611f47a517d2faa6cacca8efeb97060d8a4ed4fb3731e0a32dc27
4
- data.tar.gz: 44833c289d4d8519b840597cd8eb276085918b39a2e4dd97a4ea280b22fc747a
3
+ metadata.gz: 7a1893f514eefa2df7e5fc6730f984468b57fd94e732a7b70e131476f613fc6f
4
+ data.tar.gz: ad813ddfe3c1b575967610f9df94a70351e6a7273b31ee02e5a98e253ff4a23d
5
5
  SHA512:
6
- metadata.gz: a2cf3d9a87608e3e37a571e2e177f42383e691c80a7a1a7325ed3fa42a4ab7daf7ebc8d193db753cd9026ae57a56e0d1e75cea31a99332c539dcaf099b48c331
7
- data.tar.gz: 68694cb3fb0de4bcf5be5d2af5e374be9eb48e356ea61215cf6fb23d2a803b04d67b44c00c002e2ef05542557fe5a33bd1c594ef93ce24bbb0198351744ed006
6
+ metadata.gz: 661dd8c86142d82fb38343c38b8a92f54a4a21e32ab6ed4c45b59543bfafbf3180510e8b25f6900a3360b8b1474e8b4e55783c1dbb586b28a1944e5842c05493
7
+ data.tar.gz: 522f493875610863168d00a7b5b2afa1797314f9222f022a98b7719903ad63757e8de806b57dc361f5ed03f883f1cbb0b0c2e6a616165fed85b88796f71553aa
data/CHANGELOG.md CHANGED
@@ -1,6 +1,15 @@
1
- ```markdown
2
1
  # Changelog
3
2
 
3
+ ## [0.1.315] - 2026-04-01
4
+
5
+ ### Fixed
6
+ - Remove redundant `exit 0` statement from publish script
7
+
8
+ ## [0.1.314] - 2026-04-01
9
+
10
+ ### Fixed
11
+ - Rename `Gem` class to `RubyGem` in publish script to avoid conflict with Ruby stdlib
12
+
4
13
  ## [0.1.313] - 2026-04-01
5
14
 
6
15
  ### Fixed
@@ -195,4 +204,3 @@
195
204
 
196
205
  ### Changed
197
206
  - Filter logic changes for PR management
198
- ```
data/exe/h CHANGED
@@ -225,7 +225,7 @@ Hiiro.run(*ARGV, cwd: Dir.pwd, tasks: true) do
225
225
  end
226
226
 
227
227
  unless found
228
- system('say', 'hiiro publish timed out')
228
+ system('terminal-notifier', '-title', 'hiiro publish', '-message', 'Timed out waiting for RubyGems')
229
229
  exit 1
230
230
  end
231
231
 
@@ -239,11 +239,11 @@ Hiiro.run(*ARGV, cwd: Dir.pwd, tasks: true) do
239
239
  output.match?(/hiiro \\([^)]*#{Regexp.escape(expected)}/)
240
240
  end
241
241
  if mismatched.empty?
242
- system('say', 'hiiro updated')
242
+ system('terminal-notifier', '-title', 'hiiro updated', '-message', "v\#{expected} installed across all rbenv versions")
243
243
  elsif mismatched.size == versions.size
244
- system('say', 'no versions updated')
244
+ system('terminal-notifier', '-title', 'hiiro update failed', '-message', "v\#{expected} not installed in any rbenv version")
245
245
  else
246
- system('say', "\#{mismatched.size} version\#{mismatched.size == 1 ? '' : 's'} not updated")
246
+ system('terminal-notifier', '-title', 'hiiro updated', '-message', "v\#{expected} installed — \#{mismatched.size} version\#{mismatched.size == 1 ? '' : 's'} skipped")
247
247
  end
248
248
  RUBY
249
249
 
data/lib/hiiro/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Hiiro
2
- VERSION = "0.1.313"
2
+ VERSION = "0.1.315"
3
3
  end
data/sa ADDED
@@ -0,0 +1 @@
1
+ /Users/josh/proj/hiiro/script/available
data/script/available ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "pry"
6
+
7
+ # ─── RubyGems API ────────────────────────────────────────────────────────────
8
+
9
+ class RubyGems
10
+ def self.published_versions(gem_name)
11
+ uri = URI("https://rubygems.org/api/v1/versions/#{gem_name}.json")
12
+ json_version = JSON.parse(Net::HTTP.get(uri)).map { |v| v["number"] }
13
+ rescue => e
14
+ puts "Warning: could not fetch versions from RubyGems (#{e.message}), falling back to local"
15
+ require "hiiro"
16
+ [Hiiro::VERSION]
17
+ end
18
+
19
+ def self.wait_for(gem_name, version, max_attempts: 40)
20
+ attempts = 0
21
+ loop do
22
+ sleep(attempts == 0 ? 5 : 15)
23
+ attempts += 1
24
+ versions = published_versions(gem_name)
25
+ return true if versions.include?(version)
26
+ puts "Waiting for v#{version} on RubyGems (attempt #{attempts}/#{max_attempts})..."
27
+ return false if attempts >= max_attempts
28
+ end
29
+ rescue => e
30
+ puts "RubyGems poll error: #{e.message}"
31
+ false
32
+ end
33
+ end
34
+
35
+ class HiiroSetup
36
+ def self.latest_version1 = RubyGems.published_versions('hiiro').first
37
+ def self.latest_version2 = `gem search -r hiiro`.lines.map(&:strip).select{|ln| ln.match?(/\d\.\d/) }.first[/\d+\.\d+[^)]+/]
38
+ end
39
+
40
+ json_version = HiiroSetup.latest_version1
41
+ cli_version = HiiroSetup.latest_version2
42
+
43
+ puts
44
+ puts(json_version:,cli_version:)
45
+ puts
46
+
47
+ puts 'done'
48
+
data/script/publish CHANGED
@@ -3,202 +3,238 @@
3
3
  require "net/http"
4
4
  require "json"
5
5
 
6
- # Parse -t flag for pre-release
7
- pre_release = ARGV.include?('-t') || `git branch --show-current`.strip != 'main'
8
-
9
- # Check for uncommitted changes before we modify anything
10
- has_pending_changes = !`git status --porcelain`.strip.empty?
11
-
12
- # Ensure local branch is up to date with remote
13
- system('git', 'fetch', 'origin')
14
- behind = `git rev-list HEAD..origin/main --count`.strip.to_i
15
- if behind > 0
16
- puts "ERROR: Local branch is #{behind} commit(s) behind origin/main. Pull before publishing."
17
- exit 1
18
- end
6
+ # ─── RubyGems API ────────────────────────────────────────────────────────────
19
7
 
20
- puts ""
21
- # Fetch all published versions from RubyGems
22
- def fetch_versions
23
- uri = URI("https://rubygems.org/api/v1/versions/hiiro.json")
24
- response = Net::HTTP.get(uri)
25
- JSON.parse(response).map { |v| v["number"] }
26
- rescue => e
27
- puts "Warning: Could not fetch versions from RubyGems: #{e.message}"
28
- puts "Falling back to local version"
29
- require "hiiro"
30
- [Hiiro::VERSION]
8
+ class RubyGems
9
+ def self.published_versions(gem_name)
10
+ uri = URI("https://rubygems.org/api/v1/versions/#{gem_name}.json")
11
+ JSON.parse(Net::HTTP.get(uri)).map { |v| v["number"] }
12
+ rescue => e
13
+ puts "Warning: could not fetch versions from RubyGems (#{e.message}), falling back to local"
14
+ require "hiiro"
15
+ [Hiiro::VERSION]
16
+ end
17
+ def self.cli_version = `gem search -r hiiro`.lines.map(&:strip).select{|ln| ln.match?(/\d\.\d/) }.first&.[](/(?:[(])\d[^)]+/)
18
+
19
+ def self.wait_for(gem_name, version, max_attempts: 40)
20
+ attempts = 0
21
+ loop do
22
+ sleep(attempts == 0 ? 5 : 15)
23
+ attempts += 1
24
+ versions = published_versions(gem_name)
25
+ return true if versions.include?(version) && cli_version && cli_version == version
26
+ puts "Waiting for v#{version} on RubyGems (attempt #{attempts}/#{max_attempts})..."
27
+ return false if attempts >= max_attempts
28
+ end
29
+ rescue => e
30
+ puts "RubyGems poll error: #{e.message}"
31
+ false
32
+ end
31
33
  end
32
34
 
33
- # Parse version string into comparable components
34
- def parse_version(version)
35
- parts = version.split(?.)
36
- is_pre = parts.length >= 5 && parts[-2] == 'pre'
37
- if is_pre
38
- { major: parts[0].to_i, minor: parts[1].to_i, patch: parts[2].to_i, pre: true, pre_num: parts[-1].to_i }
39
- else
40
- { major: parts[0].to_i, minor: parts[1].to_i, patch: parts[2].to_i, pre: false, pre_num: 0 }
35
+ # ─── Version bumping ─────────────────────────────────────────────────────────
36
+
37
+ class Version
38
+ attr_reader :major, :minor, :patch, :pre, :pre_num
39
+
40
+ def self.parse(str)
41
+ parts = str.split(".")
42
+ pre = parts.length >= 5 && parts[-2] == "pre"
43
+ new(parts[0].to_i, parts[1].to_i, parts[2].to_i, pre, pre ? parts[-1].to_i : 0)
44
+ end
45
+
46
+ def self.latest(versions)
47
+ versions.max_by { |v| parse(v).sort_key }
41
48
  end
42
- end
43
49
 
44
- # Find the latest version (prioritize release over pre for same patch)
45
- def latest_version(versions)
46
- versions.max_by do |v|
47
- p = parse_version(v)
48
- # Sort by major, minor, patch, then non-pre before pre, then pre_num
49
- [p[:major], p[:minor], p[:patch], p[:pre] ? 0 : 1, p[:pre_num]]
50
+ def self.bump(current_str, pre_release:)
51
+ v = parse(current_str)
52
+ if v.pre
53
+ pre_release ? "#{v.major}.#{v.minor}.#{v.patch}.pre.#{v.pre_num + 1}"
54
+ : "#{v.major}.#{v.minor}.#{v.patch}"
55
+ else
56
+ pre_release ? "#{v.major}.#{v.minor}.#{v.patch + 1}.pre.1"
57
+ : "#{v.major}.#{v.minor}.#{v.patch + 1}"
58
+ end
59
+ end
60
+
61
+ def initialize(major, minor, patch, pre, pre_num)
62
+ @major, @minor, @patch, @pre, @pre_num = major, minor, patch, pre, pre_num
50
63
  end
64
+
65
+ def sort_key = [major, minor, patch, pre ? 0 : 1, pre_num]
51
66
  end
52
67
 
53
- versions = fetch_versions
54
- current = latest_version(versions)
55
- puts "Latest published version: #{current}"
68
+ # ─── Git ─────────────────────────────────────────────────────────────────────
56
69
 
57
- parts = current.split(?.)
58
- is_pre = parts.length >= 5 && parts[-2] == 'pre'
70
+ class Git
71
+ def self.current_branch = `git branch --show-current`.strip
72
+ def self.pending_changes? = !`git status --porcelain`.strip.empty?
73
+ def self.diff = `git diff HEAD`
74
+ def self.status = `git status --short`
59
75
 
60
- if is_pre
61
- major, minor, patch = parts[0..2]
62
- pre_num = parts[-1].to_i
63
- if pre_release
64
- # pre -> pre: increment pre number
65
- new_version = [major, minor, patch, 'pre', pre_num + 1].join(?.)
66
- else
67
- # pre -> release: remove pre, keep same version number
68
- new_version = [major, minor, patch].join(?.)
76
+ def self.ensure_up_to_date!
77
+ system("git", "fetch", "origin")
78
+ behind = `git rev-list HEAD..origin/main --count`.strip.to_i
79
+ if behind > 0
80
+ puts "ERROR: #{behind} commit(s) behind origin/main pull before publishing."
81
+ exit 1
82
+ end
69
83
  end
70
- else
71
- major, minor, patch = parts[0..2]
72
- if pre_release
73
- # release -> pre: increment patch and add pre.1
74
- new_version = [major, minor, patch.to_i + 1, 'pre', 1].join(?.)
75
- else
76
- # release -> release: increment patch
77
- new_version = [major, minor, patch.to_i + 1].join(?.)
84
+
85
+ def self.commits_since_last_tag
86
+ last_tag = `git describe --tags --abbrev=0 2>/dev/null`.strip
87
+ last_tag.empty? ? `git log --oneline` : `git log #{last_tag}..HEAD --oneline`
88
+ end
89
+
90
+ def self.stage_and_commit(files, message)
91
+ system("git", "reset", "HEAD")
92
+ files.each { |f| system("git", "add", "--", f) }
93
+ system("git", "commit", "-m", message)
94
+ puts " → #{message}"
95
+ end
96
+
97
+ def self.commit_all(message)
98
+ system("git", "add", "--all")
99
+ system("git", "commit", "-m", message)
100
+ end
101
+
102
+ def self.tag(version)
103
+ system("git", "tag", "-a", "v#{version}", "-m", "v#{version}")
104
+ puts "Tagged v#{version}"
105
+ end
106
+
107
+ def self.push
108
+ if system("git", "push", "origin", "main", "--follow-tags")
109
+ puts "Pushed to origin/main"
110
+ else
111
+ branch = "publish-v#{version}"
112
+ system("git", "checkout", "-b", branch)
113
+ system("git", "push", "origin", branch, "--follow-tags") \
114
+ ? puts("Push to main failed — pushed to origin/#{branch} instead")
115
+ : puts("ERROR: unable to push to origin")
116
+ system("git", "checkout", "main")
117
+ end
78
118
  end
79
119
  end
80
120
 
81
- puts "New version: #{new_version}"
121
+ # ─── Claude AI calls ─────────────────────────────────────────────────────────
82
122
 
83
- if has_pending_changes
84
- puts ""
85
- puts "Generating commit plan for pending changes..."
123
+ class Claude
124
+ MODEL = "claude-haiku-4-5-20251001"
86
125
 
87
- diff = `git diff HEAD`
88
- status = `git status --short`
126
+ Result = Struct.new(:commits, :changelog)
89
127
 
90
- commit_prompt = <<~PROMPT
91
- Analyze these git changes and create a logical commit plan.
128
+ # Single call: returns a Result with commit plan + updated changelog
129
+ def self.prepare_release(version:, diff:, status:, git_log:, existing_changelog:)
130
+ prompt = <<~PROMPT
131
+ You are preparing a release for the Ruby gem "hiiro" version #{version}.
132
+ Do two things and return them as a single JSON object — no explanation, no markdown fences.
92
133
 
93
- git status:
94
- #{status.strip}
134
+ ## 1. Commit plan
135
+ Analyze the pending git changes below and group them into logical commits.
136
+ Use conventional commit format (feat:, fix:, chore:, refactor:, docs:, etc.).
137
+ Only include files that appear in the git status output.
95
138
 
96
- git diff:
97
- #{diff.strip}
139
+ git status:
140
+ #{status.strip}
98
141
 
99
- Group related changes into logical commits. Output a JSON array only, no explanation, no markdown fences:
100
- [
101
- {"message": "commit message", "files": ["path/to/file"]},
102
- ...
103
- ]
142
+ git diff:
143
+ #{diff.strip}
104
144
 
105
- Use conventional commit format (feat:, fix:, chore:, refactor:, docs:, etc.).
106
- Only include files that appear in the status output above.
107
- PROMPT
145
+ ## 2. Changelog
146
+ Write a complete updated CHANGELOG.md. Add a new section for v#{version} at the top
147
+ with today's date. Group changes into Added, Changed, Fixed, Removed where appropriate.
148
+ Be concise.
108
149
 
109
- response = IO.popen(['claude', '-p', '--model', 'claude-haiku-4-5-20251001'], 'r+') { |io|
110
- io.write(commit_prompt)
111
- io.close_write
112
- io.read
113
- }
150
+ Recent commits (for changelog context):
151
+ #{git_log.strip}
114
152
 
115
- begin
116
- json_str = response.strip.gsub(/\A```(?:json)?\n?/, '').gsub(/\n?```\z/, '')
117
- commits = JSON.parse(json_str)
118
-
119
- system('git', 'add', '--all')
120
- commits.each do |commit|
121
- system('git', 'reset', 'HEAD')
122
- commit['files'].each { |f| system('git', 'add', '--', f) }
123
- system('git', 'commit', '-m', commit['message'])
124
- puts " #{commit['message']}"
125
- end
126
- rescue => e
127
- puts "Warning: could not parse claude commit plan (#{e.message}), committing all as one"
128
- system('git', 'add', '--all')
129
- system('git', 'commit', '-m', 'chore: pre-publish changes')
153
+ Existing CHANGELOG.md:
154
+ #{existing_changelog.empty? ? "(none)" : existing_changelog}
155
+
156
+ ## Output format (JSON only, no extra text)
157
+ {
158
+ "commits": [
159
+ {"message": "commit message", "files": ["path/to/file"]},
160
+ ...
161
+ ],
162
+ "changelog": "full CHANGELOG.md content here"
163
+ }
164
+ PROMPT
165
+
166
+ raw = IO.popen(["claude", "-p", "--model", MODEL], "r+") { |io|
167
+ io.write(prompt)
168
+ io.close_write
169
+ io.read
170
+ }.strip.gsub(/\A```(?:json)?\n?/, "").gsub(/\n?```\z/, "")
171
+
172
+ parsed = JSON.parse(raw)
173
+ Result.new(parsed["commits"], parsed["changelog"])
130
174
  end
131
175
  end
132
176
 
133
- puts ""
134
- puts "Generating CHANGELOG.md..."
135
- last_tag = `git describe --tags --abbrev=0 2>/dev/null`.strip
136
- commit_log = last_tag.empty? ? `git log --oneline` : `git log #{last_tag}..HEAD --oneline`
137
- existing_changelog = File.exist?('CHANGELOG.md') ? File.read('CHANGELOG.md') : ''
177
+ # ─── Gem publishing ──────────────────────────────────────────────────────────
138
178
 
139
- changelog_prompt = <<~PROMPT
140
- Update or create a CHANGELOG.md for the Ruby gem "hiiro" version #{new_version}.
179
+ class RubyGem
180
+ def self.build(version)
181
+ system("gem", "build", "hiiro.gemspec") || abort("ERROR: gem build failed")
182
+ end
183
+
184
+ def self.push(version)
185
+ system("gem", "push", "hiiro-#{version}.gem")
186
+ end
187
+ end
141
188
 
142
- Recent commits (since #{last_tag.empty? ? 'the beginning' : last_tag}):
143
- #{commit_log.strip}
189
+ # ─── Main ─────────────────────────────────────────────────────────────────────
144
190
 
145
- Existing CHANGELOG.md:
146
- #{existing_changelog.empty? ? '(none)' : existing_changelog}
191
+ pre_release = ARGV.include?("-t") || Git.current_branch != "main"
192
+ pending_before = Git.pending_changes?
147
193
 
148
- Write the complete updated CHANGELOG.md. Add a new section for v#{new_version} at the top
149
- with today's date. Group changes into Added, Changed, Fixed, Removed where appropriate.
150
- Be concise. Output only the file content, no explanation.
151
- PROMPT
194
+ Git.ensure_up_to_date!
152
195
 
153
- new_changelog = IO.popen(['claude', '-p', '--model', 'claude-haiku-4-5-20251001'], 'r+') { |io|
154
- io.write(changelog_prompt)
155
- io.close_write
156
- io.read
157
- }
196
+ current_version = Version.latest(RubyGems.published_versions("hiiro"))
197
+ new_version = Version.bump(current_version, pre_release: pre_release)
198
+ puts "Latest: #{current_version} → New: #{new_version}"
158
199
 
159
- if new_changelog && !new_changelog.strip.empty?
160
- File.write('CHANGELOG.md', new_changelog)
161
- puts "Updated CHANGELOG.md"
162
- else
163
- puts "Warning: could not generate CHANGELOG.md"
164
- end
200
+ puts "\nAsking Claude to plan commits and generate changelog..."
201
+ release = Claude.prepare_release(
202
+ version: new_version,
203
+ diff: pending_before ? Git.diff : "",
204
+ status: pending_before ? Git.status : "",
205
+ git_log: Git.commits_since_last_tag,
206
+ existing_changelog: File.exist?("CHANGELOG.md") ? File.read("CHANGELOG.md") : ""
207
+ )
165
208
 
166
- puts ""
167
- File.open('lib/hiiro/version.rb', 'w+') do |f|
168
- f.puts 'class Hiiro'
169
- f.puts " VERSION = #{new_version.inspect}"
170
- f.puts 'end'
209
+ if pending_before
210
+ begin
211
+ system("git", "add", "--all")
212
+ release.commits.each { |c| Git.stage_and_commit(c["files"], c["message"]) }
213
+ rescue => e
214
+ puts "Warning: could not apply Claude commit plan (#{e.message}) — committing all as one"
215
+ Git.commit_all("chore: pre-publish changes")
216
+ end
171
217
  end
172
218
 
173
- built = system('gem', 'build', 'hiiro.gemspec')
174
- puts "\nERROR: unable to build gem\n" unless built
219
+ File.write("CHANGELOG.md", release.changelog) if release.changelog && !release.changelog.strip.empty?
220
+ File.write("lib/hiiro/version.rb", "class Hiiro\n VERSION = #{new_version.inspect}\nend\n")
175
221
 
176
- puts ""
177
- pushed = system('gem', 'push', "hiiro-#{new_version}.gem")
178
- puts "\nERROR: unable to push\n" unless pushed
222
+ RubyGem.build(new_version)
179
223
 
180
- puts ""
181
- system 'git', 'add', '--all'
182
- system 'git', 'commit', '-m', "publishing v#{new_version}"
224
+ unless RubyGem.push(new_version)
225
+ abort("ERROR: gem push failed — skipping git commit, tag, and push")
226
+ end
183
227
 
184
- system 'git', 'tag', '-a', "v#{new_version}", '-m', "v#{new_version}"
185
- puts "Tagged v#{new_version}"
228
+ Git.commit_all("publishing v#{new_version}")
229
+ Git.tag(new_version)
230
+ Git.push
186
231
 
187
- puts ""
188
- # Try to push to origin main (tags follow via push.followTags), fallback to a branch if needed
189
- if system('git', 'push', 'origin', 'main', '--follow-tags')
190
- puts "Pushed to origin/main"
232
+ puts "\nWaiting for v#{new_version} to appear on RubyGems..."
233
+ if RubyGems.wait_for("hiiro", new_version)
234
+ puts "v#{new_version} available running h update -a"
235
+ update_args = ["update", "-a"]
236
+ update_args << "--pre" if pre_release
237
+ system("h", *update_args)
191
238
  else
192
- branch_name = "publish-v#{new_version}"
193
- system 'git', 'checkout', '-b', branch_name
194
- if system('git', 'push', 'origin', branch_name, '--follow-tags')
195
- puts "Push to main failed. Pushed to origin/#{branch_name} instead"
196
- else
197
- puts "\nERROR: unable to push to origin\n"
198
- end
199
- system 'git', 'checkout', 'main'
239
+ puts "WARNING: timed out waiting for v#{new_version} on RubyGems — run `h update -a` manually"
200
240
  end
201
-
202
- puts ""
203
- puts "Running: h delayed_update #{new_version}"
204
- system('h', 'delayed_update', new_version)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hiiro
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.313
4
+ version: 0.1.315
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Toyota
@@ -342,6 +342,8 @@ files:
342
342
  - plugins/pins.rb
343
343
  - plugins/project.rb
344
344
  - record-demo.sh
345
+ - sa
346
+ - script/available
345
347
  - script/compare
346
348
  - script/diff
347
349
  - script/diffhome