hiiro 0.1.265 → 0.1.266
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/CHANGELOG.md +1 -1
- data/bin/h-pane +54 -53
- data/bin/h-pr +109 -0
- data/lib/hiiro/git/pr.rb +6 -2
- data/lib/hiiro/queue.rb +54 -22
- data/lib/hiiro/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7eecd997945d2c597a4b6e9c28ec2d8b485871ae91f304695cddeec40414c605
|
|
4
|
+
data.tar.gz: ccb6d7acb90b1d794c650ddad420d7c113baa9e8819ae2f2ec205e1fec6cf652
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0f8a631772992bfc9f61d9a8ea25c16e993459f1792441a963d7e2ff1563b3bcce9ce4bbf213225ebc0eb7e03527be0a316599417b34832b24056e30649e7547
|
|
7
|
+
data.tar.gz: 78a971962958be80d9936c0606329a54b5a6a4ec6cf4a8185f07d1b0bf3d61c9d504eba8e9bebd6e07627e71feb89ba968ab216e8ace23852fde2200390a7623
|
data/CHANGELOG.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Done.
|
|
1
|
+
Done. CHANGELOG.md updated with v0.1.266 section at the top.
|
data/bin/h-pane
CHANGED
|
@@ -107,63 +107,11 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
|
|
|
107
107
|
end
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
add_subcmd(:sw) { |target = nil, *args|
|
|
110
|
+
add_subcmd(:sw, :switch) { |target = nil, *args|
|
|
111
111
|
target ||= fuzzyfind_from_map(tmux.panes(all: true).name_map)
|
|
112
112
|
tmux.open_session(target) if target
|
|
113
113
|
}
|
|
114
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
|
|
129
|
-
end
|
|
130
|
-
|
|
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
115
|
add_subcmd(:home) { |*args|
|
|
168
116
|
run_child(:home) do |h|
|
|
169
117
|
h.add_subcmd(:ls) {
|
|
@@ -204,6 +152,59 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
|
|
|
204
152
|
puts "Home pane '#{name}' not found"
|
|
205
153
|
end
|
|
206
154
|
}
|
|
155
|
+
|
|
156
|
+
h.add_subcmd(:switch) { |name = nil|
|
|
157
|
+
homes = load_homes.call
|
|
158
|
+
|
|
159
|
+
if name.nil?
|
|
160
|
+
if homes.empty?
|
|
161
|
+
puts "No home panes configured. Use 'h pane home add <name> <session> [path]'"
|
|
162
|
+
next
|
|
163
|
+
end
|
|
164
|
+
display_map = homes.each_with_object({}) do |(n, cfg), map|
|
|
165
|
+
label = "#{n} [#{cfg['session']}#{cfg['path'] ? " #{cfg['path']}" : ''}]"
|
|
166
|
+
map[label] = n
|
|
167
|
+
end
|
|
168
|
+
name = fuzzyfind_from_map(display_map)
|
|
169
|
+
next unless name
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
cfg = homes[name]
|
|
173
|
+
unless cfg
|
|
174
|
+
matched = homes.keys.select { |k| k.start_with?(name) }
|
|
175
|
+
case matched.size
|
|
176
|
+
when 1
|
|
177
|
+
name = matched.first
|
|
178
|
+
cfg = homes[name]
|
|
179
|
+
when 0
|
|
180
|
+
puts "Home pane '#{name}' not found"
|
|
181
|
+
next
|
|
182
|
+
else
|
|
183
|
+
puts "Ambiguous home pane '#{name}': #{matched.join(', ')}"
|
|
184
|
+
next
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
session = cfg['session']
|
|
189
|
+
path = cfg['path']
|
|
190
|
+
|
|
191
|
+
if !tmux.session_exists?(session)
|
|
192
|
+
tmux.new_session(session, detached: true, start_directory: path, window_name: name)
|
|
193
|
+
else
|
|
194
|
+
window_names = `tmux list-windows -t #{session.shellescape} -F '\#{window_name}' 2>/dev/null`.lines(chomp: true)
|
|
195
|
+
unless window_names.include?(name)
|
|
196
|
+
dir = path || tmux.find_session(session)&.path
|
|
197
|
+
tmux.new_window(name: name, target: session, start_directory: dir)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
target = "#{session}:#{name}"
|
|
202
|
+
if tmux.in_tmux?
|
|
203
|
+
system('tmux', 'switch-client', '-t', target)
|
|
204
|
+
else
|
|
205
|
+
system('tmux', 'attach-session', '-t', target)
|
|
206
|
+
end
|
|
207
|
+
}
|
|
207
208
|
end
|
|
208
209
|
}
|
|
209
210
|
|
data/bin/h-pr
CHANGED
|
@@ -1863,6 +1863,115 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
|
|
|
1863
1863
|
end
|
|
1864
1864
|
end
|
|
1865
1865
|
|
|
1866
|
+
add_subcmd(:dep) do |*args|
|
|
1867
|
+
run_child(:dep) do |h|
|
|
1868
|
+
h.add_subcmd(:add) { |ref = nil, *dep_refs|
|
|
1869
|
+
if ref.nil? || dep_refs.empty?
|
|
1870
|
+
puts "Usage: h pr dep add <pr> <dep1> [dep2 ...]"
|
|
1871
|
+
next
|
|
1872
|
+
end
|
|
1873
|
+
|
|
1874
|
+
pr_number = resolve_pr.call(ref)
|
|
1875
|
+
next unless pr_number
|
|
1876
|
+
|
|
1877
|
+
dep_numbers = dep_refs.map { |r| resolve_pr.call(r) }.compact
|
|
1878
|
+
if dep_numbers.empty?
|
|
1879
|
+
puts "No valid dependency PRs resolved"
|
|
1880
|
+
next
|
|
1881
|
+
end
|
|
1882
|
+
|
|
1883
|
+
pinned = pinned_manager.load_pinned
|
|
1884
|
+
pr = pinned.find { |p| p.number.to_s == pr_number.to_s }
|
|
1885
|
+
unless pr
|
|
1886
|
+
puts "PR ##{pr_number} is not tracked — pin it first with: h pr track #{pr_number}"
|
|
1887
|
+
next
|
|
1888
|
+
end
|
|
1889
|
+
|
|
1890
|
+
pr.depends_on = (Array(pr.depends_on) + dep_numbers.map(&:to_i)).uniq.sort
|
|
1891
|
+
pinned_manager.save_pinned(pinned)
|
|
1892
|
+
puts "PR ##{pr_number} now depends on: #{pr.depends_on.map { |n| "##{n}" }.join(', ')}"
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
h.add_subcmd(:rm) { |ref = nil, *dep_refs|
|
|
1896
|
+
if ref.nil?
|
|
1897
|
+
puts "Usage: h pr dep rm <pr> [dep1 dep2 ...] (omit deps to clear all)"
|
|
1898
|
+
next
|
|
1899
|
+
end
|
|
1900
|
+
|
|
1901
|
+
pr_number = resolve_pr.call(ref)
|
|
1902
|
+
next unless pr_number
|
|
1903
|
+
|
|
1904
|
+
pinned = pinned_manager.load_pinned
|
|
1905
|
+
pr = pinned.find { |p| p.number.to_s == pr_number.to_s }
|
|
1906
|
+
unless pr
|
|
1907
|
+
puts "PR ##{pr_number} not in tracked list"
|
|
1908
|
+
next
|
|
1909
|
+
end
|
|
1910
|
+
|
|
1911
|
+
if dep_refs.empty?
|
|
1912
|
+
pr.depends_on = nil
|
|
1913
|
+
puts "Cleared all dependencies from ##{pr_number}"
|
|
1914
|
+
else
|
|
1915
|
+
dep_numbers = dep_refs.map { |r| resolve_pr.call(r) }.compact.map(&:to_i)
|
|
1916
|
+
pr.depends_on = (Array(pr.depends_on) - dep_numbers).then { |d| d.empty? ? nil : d }
|
|
1917
|
+
puts "Removed dep(s) #{dep_numbers.map { |n| "##{n}" }.join(', ')} from ##{pr_number}"
|
|
1918
|
+
puts " Remaining: #{Array(pr.depends_on).map { |n| "##{n}" }.join(', ').then { |s| s.empty? ? '(none)' : s }}"
|
|
1919
|
+
end
|
|
1920
|
+
|
|
1921
|
+
pinned_manager.save_pinned(pinned)
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
h.add_subcmd(:ls) { |ref = nil|
|
|
1925
|
+
pinned = pinned_manager.load_pinned
|
|
1926
|
+
|
|
1927
|
+
if ref
|
|
1928
|
+
pr_number = resolve_pr.call(ref)
|
|
1929
|
+
next unless pr_number
|
|
1930
|
+
pr = pinned.find { |p| p.number.to_s == pr_number.to_s }
|
|
1931
|
+
unless pr
|
|
1932
|
+
puts "PR ##{pr_number} not in tracked list"
|
|
1933
|
+
next
|
|
1934
|
+
end
|
|
1935
|
+
deps = Array(pr.depends_on)
|
|
1936
|
+
if deps.empty?
|
|
1937
|
+
puts "##{pr_number} has no dependencies"
|
|
1938
|
+
else
|
|
1939
|
+
puts "##{pr_number}: #{pr.title}"
|
|
1940
|
+
puts " depends on:"
|
|
1941
|
+
deps.each do |dep_num|
|
|
1942
|
+
dep = pinned.find { |p| p.number == dep_num }
|
|
1943
|
+
label = dep ? " ##{dep_num}: #{dep.title}" : " ##{dep_num} (not tracked)"
|
|
1944
|
+
puts label
|
|
1945
|
+
end
|
|
1946
|
+
end
|
|
1947
|
+
else
|
|
1948
|
+
with_deps = pinned.select { |p| Array(p.depends_on).any? }
|
|
1949
|
+
if with_deps.empty?
|
|
1950
|
+
puts "No dependency relationships configured."
|
|
1951
|
+
puts "Use: h pr dep add <pr> <dep1> [dep2 ...]"
|
|
1952
|
+
else
|
|
1953
|
+
with_deps.each do |pr|
|
|
1954
|
+
puts "##{pr.number}: #{pr.title}"
|
|
1955
|
+
Array(pr.depends_on).each do |dep_num|
|
|
1956
|
+
dep = pinned.find { |p| p.number == dep_num }
|
|
1957
|
+
label = dep ? " └─ ##{dep_num}: #{dep.title}" : " └─ ##{dep_num} (not tracked)"
|
|
1958
|
+
puts label
|
|
1959
|
+
end
|
|
1960
|
+
end
|
|
1961
|
+
end
|
|
1962
|
+
end
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
h.add_subcmd(:config) {
|
|
1966
|
+
puts Hiiro::Git::Pr::PINNED_FILE
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
h.add_subcmd(:edit) {
|
|
1970
|
+
edit_files Hiiro::Git::Pr::PINNED_FILE
|
|
1971
|
+
}
|
|
1972
|
+
end
|
|
1973
|
+
end
|
|
1974
|
+
|
|
1866
1975
|
add_subcmd(:config) do |*args|
|
|
1867
1976
|
make_child do
|
|
1868
1977
|
add_subcmd(:path) do
|
data/lib/hiiro/git/pr.rb
CHANGED
|
@@ -9,7 +9,7 @@ class Hiiro
|
|
|
9
9
|
attr_accessor :number, :title, :state, :url, :head_branch, :base_branch,
|
|
10
10
|
:repo, :slot, :is_draft, :mergeable, :review_decision,
|
|
11
11
|
:checks, :check_runs, :reviews, :last_checked, :pinned_at, :updated_at,
|
|
12
|
-
:task, :worktree, :tmux_session, :tags, :assigned, :authored
|
|
12
|
+
:task, :worktree, :tmux_session, :tags, :assigned, :authored, :depends_on
|
|
13
13
|
|
|
14
14
|
# Load all pinned PRs from YAML, returning an array of Pr instances.
|
|
15
15
|
def self.pinned_prs
|
|
@@ -58,6 +58,7 @@ class Hiiro
|
|
|
58
58
|
tags: hash['tags'],
|
|
59
59
|
assigned: hash['assigned'],
|
|
60
60
|
authored: hash['authored'],
|
|
61
|
+
depends_on: hash['depends_on'],
|
|
61
62
|
)
|
|
62
63
|
end
|
|
63
64
|
|
|
@@ -163,7 +164,8 @@ class Hiiro
|
|
|
163
164
|
repo: nil, slot: nil, is_draft: nil, mergeable: nil, review_decision: nil,
|
|
164
165
|
checks: nil, check_runs: nil, reviews: nil, last_checked: nil,
|
|
165
166
|
pinned_at: nil, updated_at: nil,
|
|
166
|
-
task: nil, worktree: nil, tmux_session: nil, tags: nil, assigned: nil, authored: nil
|
|
167
|
+
task: nil, worktree: nil, tmux_session: nil, tags: nil, assigned: nil, authored: nil,
|
|
168
|
+
depends_on: nil)
|
|
167
169
|
@number = number
|
|
168
170
|
@title = title
|
|
169
171
|
@state = state
|
|
@@ -187,6 +189,7 @@ class Hiiro
|
|
|
187
189
|
@tags = tags
|
|
188
190
|
@assigned = assigned
|
|
189
191
|
@authored = authored
|
|
192
|
+
@depends_on = depends_on ? Array(depends_on).map(&:to_i) : nil
|
|
190
193
|
end
|
|
191
194
|
|
|
192
195
|
def open? = state&.upcase == 'OPEN'
|
|
@@ -233,6 +236,7 @@ class Hiiro
|
|
|
233
236
|
'tags' => (Array(tags).empty? ? nil : tags),
|
|
234
237
|
'assigned' => assigned,
|
|
235
238
|
'authored' => authored,
|
|
239
|
+
'depends_on' => (Array(depends_on).empty? ? nil : depends_on),
|
|
236
240
|
}.compact
|
|
237
241
|
end
|
|
238
242
|
|
data/lib/hiiro/queue.rb
CHANGED
|
@@ -138,12 +138,16 @@ class Hiiro
|
|
|
138
138
|
# Determine target tmux session and working directory from frontmatter
|
|
139
139
|
target_session = TMUX_SESSION
|
|
140
140
|
working_dir = Dir.pwd
|
|
141
|
+
tree_root = nil
|
|
141
142
|
|
|
142
143
|
if prompt_obj
|
|
143
144
|
if prompt_obj.task
|
|
144
145
|
target_session = prompt_obj.session_name
|
|
145
146
|
tree = prompt_obj.task.tree
|
|
146
|
-
|
|
147
|
+
if tree
|
|
148
|
+
working_dir = tree.path
|
|
149
|
+
tree_root = tree.path
|
|
150
|
+
end
|
|
147
151
|
elsif prompt_obj.session
|
|
148
152
|
target_session = prompt_obj.session.name
|
|
149
153
|
working_dir = prompt_obj.session.path || working_dir
|
|
@@ -151,6 +155,22 @@ class Hiiro
|
|
|
151
155
|
|
|
152
156
|
if prompt_obj.tree
|
|
153
157
|
working_dir = prompt_obj.tree.path
|
|
158
|
+
tree_root = prompt_obj.tree.path
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Resolve app + dir frontmatter on top of whatever tree root we have
|
|
162
|
+
if prompt_obj.app_name
|
|
163
|
+
env = Environment.current rescue nil
|
|
164
|
+
app = env&.find_app(prompt_obj.app_name)
|
|
165
|
+
if app
|
|
166
|
+
root = tree_root || git_root_of(working_dir)
|
|
167
|
+
app_dir = File.join(root, app.relative_path)
|
|
168
|
+
working_dir = prompt_obj.rel_dir ? File.join(app_dir, prompt_obj.rel_dir) : app_dir
|
|
169
|
+
else
|
|
170
|
+
warn "hq: app '#{prompt_obj.app_name}' not found — falling back to tree/session dir"
|
|
171
|
+
end
|
|
172
|
+
elsif prompt_obj.rel_dir
|
|
173
|
+
working_dir = File.join(tree_root || working_dir, prompt_obj.rel_dir)
|
|
154
174
|
end
|
|
155
175
|
end
|
|
156
176
|
|
|
@@ -298,6 +318,11 @@ class Hiiro
|
|
|
298
318
|
windows.include?(wname)
|
|
299
319
|
end
|
|
300
320
|
|
|
321
|
+
def git_root_of(dir)
|
|
322
|
+
root = `git -C #{Shellwords.shellescape(dir)} rev-parse --show-toplevel 2>/dev/null`.strip
|
|
323
|
+
root.empty? ? dir : root
|
|
324
|
+
end
|
|
325
|
+
|
|
301
326
|
def slugify(text)
|
|
302
327
|
text.downcase.gsub(/[^a-z0-9]+/, '-').gsub(/^-|-$/, '')[0, 60]
|
|
303
328
|
end
|
|
@@ -491,17 +516,17 @@ class Hiiro
|
|
|
491
516
|
elsif args.any?
|
|
492
517
|
content = args.join(' ')
|
|
493
518
|
else
|
|
494
|
-
# Pre-fill with frontmatter template
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
519
|
+
# Pre-fill with frontmatter template
|
|
520
|
+
fm_lines = ["---"]
|
|
521
|
+
fm_lines << "task_name: #{ti[:task_name]}" if ti&.dig(:task_name)
|
|
522
|
+
fm_lines << "tree_name: #{ti[:tree_name]}" if ti&.dig(:tree_name)
|
|
523
|
+
fm_lines << "session_name: #{ti[:session_name]}" if ti&.dig(:session_name)
|
|
524
|
+
fm_lines << "ignore: true" if opts.ignore
|
|
525
|
+
fm_lines << "# app: <partial-app-name> (run claude from this app's directory)"
|
|
526
|
+
fm_lines << "# dir: <relative-path> (subdir within app or tree root)"
|
|
527
|
+
fm_lines << "---"
|
|
528
|
+
fm_lines << ""
|
|
529
|
+
fm_content = fm_lines.join("\n")
|
|
505
530
|
|
|
506
531
|
input = InputFile.md_file(hiiro: h, content: fm_content, append: !!fm_content, prefix: 'hq-')
|
|
507
532
|
input.edit
|
|
@@ -553,16 +578,15 @@ class Hiiro
|
|
|
553
578
|
path = File.join(q.queue_dirs[:wip], "#{name}.md")
|
|
554
579
|
|
|
555
580
|
unless File.exist?(path)
|
|
556
|
-
|
|
557
|
-
if ti
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
end
|
|
581
|
+
fm_lines = ["---"]
|
|
582
|
+
fm_lines << "task_name: #{ti[:task_name]}" if ti&.dig(:task_name)
|
|
583
|
+
fm_lines << "tree_name: #{ti[:tree_name]}" if ti&.dig(:tree_name)
|
|
584
|
+
fm_lines << "session_name: #{ti[:session_name]}" if ti&.dig(:session_name)
|
|
585
|
+
fm_lines << "# app: <partial-app-name> (run claude from this app's directory)"
|
|
586
|
+
fm_lines << "# dir: <relative-path> (subdir within app or tree root)"
|
|
587
|
+
fm_lines << "---"
|
|
588
|
+
fm_lines << ""
|
|
589
|
+
File.write(path, fm_lines.join("\n"))
|
|
566
590
|
end
|
|
567
591
|
|
|
568
592
|
h.edit_files(path)
|
|
@@ -731,6 +755,14 @@ class Hiiro
|
|
|
731
755
|
doc.front_matter['tree_name']
|
|
732
756
|
end
|
|
733
757
|
|
|
758
|
+
def app_name
|
|
759
|
+
doc.front_matter['app']
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
def rel_dir
|
|
763
|
+
doc.front_matter['dir']
|
|
764
|
+
end
|
|
765
|
+
|
|
734
766
|
def session_name
|
|
735
767
|
doc.front_matter['session_name'] || task&.session_name
|
|
736
768
|
end
|
data/lib/hiiro/version.rb
CHANGED