hiiro 0.1.263 → 0.1.265

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: 931f2b0401b24ebade952d44bf4e8b1089cf742608fb78e5c69f926d81d0d069
4
- data.tar.gz: 5c3d810913d64619f280ebd4ba2ce3b9f1d9b4eed42c0ce7c245dde83da229b6
3
+ metadata.gz: dc901c630e69f036ba17ebb63bf913618ea825ce6746d5db20df55177fd0b9ba
4
+ data.tar.gz: 29ef84306acd767569975e56c2c44e10868f7490040b94d80d73f20db7ade326
5
5
  SHA512:
6
- metadata.gz: bbdecb53d48ce153eaff2e43f1ab7663a7aa882d8ce96b9f4342bc0e304bee91ac11ee6b465fba22d1a75e8298a813fdee24aaed28b911ae97ff8dd24c9d1041
7
- data.tar.gz: c1ff45b7c9f2675b570fc28b4d16b3ef97be71e94c4bb6aefb5568d0487d2e56d2da51ac7f134db5b219e26b6e2008833a1f00bcc3d3e9ed93921d9250cfca11
6
+ metadata.gz: 053fa8e529cb941c97447e0540cbaa2ebb48634a47911627760cd01af2f5f3251d403b69a8296443f33531efcf039bfcba738ee5f02065a475583da1bb8c7a70
7
+ data.tar.gz: 40073e682ab750f5e5633ea511a500bc2537708aa2abcb392b994cbd30a9c580227058859829f10838669ce5f790693d34473a480d680d11191f6b57227275ab
data/CHANGELOG.md CHANGED
@@ -1,16 +1 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [0.1.263] - 2026-03-18
9
-
10
- ### Fixed
11
- - fix(pr): fix undefined method 'display_pinned' in tags subcommand
12
-
13
- ## [0.1.262] - 2026-03-17
14
-
15
- ### Added
16
- - Initial release
1
+ Done. The CHANGELOG.md has been updated with v0.1.265 released today (2026-03-18), with all the unreleased features now captured in that release. An empty Unreleased section remains at the top for future entries.
data/bin/h-pane CHANGED
@@ -1,10 +1,22 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'hiiro'
4
+ require 'yaml'
4
5
 
5
6
  Hiiro.run(*ARGV, plugins: [Pins]) do
6
7
  tmux = tmux_client
7
8
 
9
+ home_config_path = Hiiro::Config.path('pane_homes.yml')
10
+
11
+ load_homes = -> {
12
+ return {} unless File.exist?(home_config_path)
13
+ YAML.load_file(home_config_path) || {}
14
+ }
15
+
16
+ save_homes = ->(config) {
17
+ File.write(home_config_path, config.to_yaml)
18
+ }
19
+
8
20
  add_subcmd(:ls) { |*args|
9
21
  if args.empty?
10
22
  tmux.panes.each { |p| puts p.to_s }
@@ -95,13 +107,103 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
95
107
  end
96
108
  }
97
109
 
98
- add_subcmd(:sw, :switch) { |target = nil, *args|
99
- if target.nil?
100
- target = fuzzyfind_from_map(tmux.panes(all: true).name_map)
110
+ add_subcmd(:sw) { |target = nil, *args|
111
+ target ||= fuzzyfind_from_map(tmux.panes(all: true).name_map)
112
+ tmux.open_session(target) if target
113
+ }
114
+
115
+ add_subcmd(:switch) { |name = nil|
116
+ homes = load_homes.call
117
+
118
+ if name.nil?
119
+ if homes.empty?
120
+ puts "No home panes configured. Use 'h pane home add <name> <session> [path]'"
121
+ next
122
+ end
123
+ display_map = homes.each_with_object({}) do |(n, cfg), h|
124
+ label = "#{n} [#{cfg['session']}#{cfg['path'] ? " #{cfg['path']}" : ''}]"
125
+ h[label] = n
126
+ end
127
+ name = fuzzyfind_from_map(display_map)
128
+ next unless name
101
129
  end
102
130
 
103
- if target
104
- tmux.open_session(target)
131
+ cfg = homes[name]
132
+ unless cfg
133
+ matched = homes.keys.select { |k| k.start_with?(name) }
134
+ case matched.size
135
+ when 1
136
+ name = matched.first
137
+ cfg = homes[name]
138
+ when 0
139
+ puts "Home pane '#{name}' not found"
140
+ next
141
+ else
142
+ puts "Ambiguous home pane '#{name}': #{matched.join(', ')}"
143
+ next
144
+ end
145
+ end
146
+
147
+ session = cfg['session']
148
+ path = cfg['path']
149
+
150
+ if !tmux.session_exists?(session)
151
+ tmux.new_session(session, detached: true, start_directory: path, window_name: name)
152
+ else
153
+ window_names = `tmux list-windows -t #{session.shellescape} -F '\#{window_name}' 2>/dev/null`.lines(chomp: true)
154
+ unless window_names.include?(name)
155
+ tmux.new_window(name: name, target: session, start_directory: path)
156
+ end
157
+ end
158
+
159
+ target = "#{session}:#{name}"
160
+ if tmux.in_tmux?
161
+ system('tmux', 'switch-client', '-t', target)
162
+ else
163
+ system('tmux', 'attach-session', '-t', target)
164
+ end
165
+ }
166
+
167
+ add_subcmd(:home) { |*args|
168
+ run_child(:home) do |h|
169
+ h.add_subcmd(:ls) {
170
+ homes = load_homes.call
171
+ if homes.empty?
172
+ puts "No home panes configured."
173
+ puts "Use: h pane home add <name> <session> [path]"
174
+ else
175
+ homes.each do |name, cfg|
176
+ path_str = cfg['path'] ? " #{cfg['path']}" : ''
177
+ puts "#{name.ljust(20)} #{cfg['session']}#{path_str}"
178
+ end
179
+ end
180
+ }
181
+
182
+ h.add_subcmd(:add) { |name, session, path = nil|
183
+ unless name && session
184
+ puts "Usage: h pane home add <name> <session> [path]"
185
+ next
186
+ end
187
+ homes = load_homes.call
188
+ homes[name] = { 'session' => session }
189
+ homes[name]['path'] = File.expand_path(path) if path
190
+ save_homes.call(homes)
191
+ puts "Added '#{name}' → #{session}#{path ? " (#{File.expand_path(path)})" : ''}"
192
+ }
193
+
194
+ h.add_subcmd(:rm) { |name|
195
+ unless name
196
+ puts "Usage: h pane home rm <name>"
197
+ next
198
+ end
199
+ homes = load_homes.call
200
+ if homes.delete(name)
201
+ save_homes.call(homes)
202
+ puts "Removed home pane '#{name}'"
203
+ else
204
+ puts "Home pane '#{name}' not found"
205
+ end
206
+ }
105
207
  end
106
208
  }
107
209
 
data/bin/h-pr CHANGED
@@ -1543,19 +1543,26 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
1543
1543
 
1544
1544
  add_subcmd(:tags) do
1545
1545
  pinned = pinned_manager.load_pinned
1546
- by_tag = Hash.new { |h, k| h[k] = [] }
1547
1546
 
1548
- pinned.each do |pr|
1549
- Array(pr.tags).each { |t| by_tag[t] << pr }
1547
+ if pinned.empty?
1548
+ puts "No tracked PRs."
1549
+ next
1550
1550
  end
1551
1551
 
1552
- if by_tag.empty?
1553
- puts "No tagged PRs."
1554
- next
1552
+ by_tag = Hash.new { |h, k| h[k] = [] }
1553
+ untagged = []
1554
+
1555
+ pinned.each do |pr|
1556
+ tags = Array(pr.tags)
1557
+ if tags.empty?
1558
+ untagged << pr
1559
+ else
1560
+ tags.each { |t| by_tag[t] << pr }
1561
+ end
1555
1562
  end
1556
1563
 
1557
- by_tag.sort.each do |tag, prs|
1558
- puts "\e[30;104m#{tag}\e[0m (#{prs.length})"
1564
+ render_group = lambda do |label, prs, current_tag|
1565
+ puts label
1559
1566
  slot_w = prs.map { |pr| (pr.slot || 1).to_s.length }.max
1560
1567
  succ_w = prs.filter_map { |pr| pr.checks&.dig('success')&.to_i&.to_s&.length }.max || 1
1561
1568
  total_w = prs.filter_map { |pr| pr.checks&.dig('total')&.to_i&.to_s&.length }.max || 1
@@ -1563,9 +1570,20 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
1563
1570
  crs_w = prs.map { |pr| [pr.reviews&.dig('changes_requested').to_i.to_s.length, 1].max }.max
1564
1571
  widths = { slot: slot_w, succ: succ_w, total: total_w, as: as_w, crs: crs_w }
1565
1572
  prs.each_with_index do |pr, idx|
1566
- puts " #{pinned_manager.display_pinned(pr, idx, widths: widths, oneline: true)}"
1573
+ line = pinned_manager.display_pinned(pr, idx, widths: widths, oneline: true)
1574
+ if current_tag
1575
+ other = Array(pr.tags) - [current_tag]
1576
+ line += " " + other.map { |t| "\e[30;104m#{t}\e[0m" }.join(' ') if other.any?
1577
+ end
1578
+ puts " #{line}"
1567
1579
  end
1568
1580
  end
1581
+
1582
+ by_tag.sort.each do |tag, prs|
1583
+ render_group.call("\e[30;104m#{tag}\e[0m (#{prs.length})", prs, tag)
1584
+ end
1585
+
1586
+ render_group.call("(untagged) (#{untagged.length})", untagged, nil) if untagged.any?
1569
1587
  end
1570
1588
 
1571
1589
  # === Multi-PR action commands (mCMD) ===
data/lib/hiiro/queue.rb CHANGED
@@ -232,32 +232,42 @@ class Hiiro
232
232
  }
233
233
  end
234
234
 
235
- def select_task(hiiro)
236
- env = Environment.current rescue nil
237
- return nil unless env
235
+ def select_task_or_session(hiiro)
236
+ mapping = {}
238
237
 
239
- tasks = env.all_tasks.sort_by(&:name)
240
- return nil if tasks.empty?
238
+ env = Environment.current rescue nil
239
+ if env
240
+ env.all_tasks.sort_by(&:name).each do |task|
241
+ line = format("task %-25s tree: %s", task.name, task.tree_name || '(none)')
242
+ mapping[line] = { type: :task, task: task }
243
+ end
244
+ end
241
245
 
242
- mapping = tasks.each_with_object({}) do |task, h|
243
- line = format("%-25s tree: %-20s", task.name, task.tree_name || '(none)')
244
- h[line] = task
246
+ sessions = Hiiro::Tmux::Sessions.fetch rescue nil
247
+ if sessions
248
+ sessions.names.sort.each do |name|
249
+ mapping[format("session %s", name)] = { type: :session, name: name }
250
+ end
245
251
  end
246
252
 
253
+ return nil if mapping.empty?
254
+
247
255
  hiiro.fuzzyfind_from_map(mapping)
248
256
  end
249
257
 
250
258
  def resolve_task_info(opts, hiiro, default_task_info)
251
- task_name = if opts.choose
252
- select_task(hiiro)&.name
259
+ if opts.choose
260
+ selection = select_task_or_session(hiiro)
261
+ if selection
262
+ case selection[:type]
263
+ when :task then task_info_for(selection[:task].name)
264
+ when :session then { session_name: selection[:name] }
265
+ end
266
+ else
267
+ default_task_info
268
+ end
253
269
  elsif opts.task
254
- opts.task
255
- else
256
- nil
257
- end
258
-
259
- if task_name
260
- task_info_for(task_name)
270
+ task_info_for(opts.task)
261
271
  else
262
272
  default_task_info
263
273
  end
@@ -653,6 +663,14 @@ class Hiiro
653
663
  puts "Cleaned #{count} files"
654
664
  }
655
665
 
666
+ h.add_subcmd(:sadd) { |*args|
667
+ exec('h', 'queue', 'add', '-s', *args)
668
+ }
669
+
670
+ h.add_subcmd(:tadd) { |*args|
671
+ exec('h', 'task', 'queue', 'add', *args)
672
+ }
673
+
656
674
  h.add_subcmd(:dir) {
657
675
  q.queue_dirs
658
676
  puts DIR
data/lib/hiiro/tasks.rb CHANGED
@@ -194,6 +194,28 @@ class Hiiro
194
194
  puts "Stopped task '#{task.name}' (worktree available for reuse)"
195
195
  end
196
196
 
197
+ def resume_task(tree, task_name: nil)
198
+ unless tree
199
+ puts "No available worktree selected"
200
+ return
201
+ end
202
+
203
+ # Derive a default task name from the tree name: "foo/main" -> "foo"
204
+ task_name ||= tree.name.end_with?('/main') ? tree.name.chomp('/main') : tree.name
205
+
206
+ if task_by_name(task_name)
207
+ puts "Task '#{task_name}' already exists"
208
+ return
209
+ end
210
+
211
+ color_index = Hiiro::TaskColors.next_index(config.tasks.map(&:color_index).compact)
212
+ task = Task.new(name: task_name, tree: tree.name, session: task_name, color_index: color_index)
213
+ config.save_task(task)
214
+ puts "Resumed task '#{task_name}' from worktree '#{tree.name}'"
215
+
216
+ switch_to_task(task)
217
+ end
218
+
197
219
  def list(tags_filter: [])
198
220
  tag_store = Hiiro::Tags.new(:task)
199
221
  items = tasks
@@ -940,6 +962,30 @@ class Hiiro
940
962
  tm.stop_task(task)
941
963
  end
942
964
 
965
+ h.add_subcmd(:resume) do |tree_name=nil|
966
+ available = tm.environment.all_trees.reject { |t|
967
+ tm.environment.all_tasks.any? { |task| task.tree_name == t.name }
968
+ }
969
+
970
+ if available.empty?
971
+ puts "No available worktrees to resume"
972
+ next
973
+ end
974
+
975
+ tree = if tree_name
976
+ Hiiro::Matcher.new(available, :name).by_prefix(tree_name).first&.item
977
+ else
978
+ h.fuzzyfind_from_map(available.each_with_object({}) { |t, m| m[t.name] = t })
979
+ end
980
+
981
+ unless tree
982
+ puts tree_name ? "No available worktree matching '#{tree_name}'" : "No worktree selected"
983
+ next
984
+ end
985
+
986
+ tm.resume_task(tree)
987
+ end
988
+
943
989
  h.add_subcmd(:edit) do
944
990
  h.edit_files(__FILE__)
945
991
  end
data/lib/hiiro/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Hiiro
2
- VERSION = "0.1.263"
2
+ VERSION = "0.1.265"
3
3
  end
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.263
4
+ version: 0.1.265
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Toyota