hiiro 0.1.308.pre.1 → 0.1.308.pre.2

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: 5133c5b05e6f2040b092c49a8ff75a1e665f9506d5a6b7afc9078dcd9c3d20a2
4
- data.tar.gz: bea25caa37de50a810b76b72b8eebe5eb1fd37141542aaea21da1932e3dbae9d
3
+ metadata.gz: 5908ae11780b1bb36df988cc84a6246a0fd6baf0a62491990d8a3603b0a10b93
4
+ data.tar.gz: afb4b4b786c1cd4e41a8c09c082cfa2f7aba9ee20f8fed33cc4292202c7a4fe0
5
5
  SHA512:
6
- metadata.gz: 321291e9a574a533d3647c7c4a54040fdbf993c174923958a4d1fdb7936bee373c838c689ca10fb50b48940d4a9aefc662c4a72714442271c7180eccf82d748f
7
- data.tar.gz: 15d46bd5f44affa4fb6418f6f76e61cef848a4ade2a975cba450825aeb730da8362fbc0a885241bad31bae4969f752063de95d9143ab72127576850cf3c4e804
6
+ metadata.gz: f159eb57fac642d228e6e5fa9ab9d5d9d13fe0442150f333462c69100f345ce5c5175efc90da208e6febda0c0d5a3acd4e26cfd3b2a263e407d479af7e98a3d3
7
+ data.tar.gz: 11e4b59102767d7052eaee3f0117dcf72beb0ff28c8aa8efe7f545cbe24f9352d99055909e5e8ceb3c68146b6b1cf32c47d25a468fc0c419f36ac24561f0d553
data/CHANGELOG.md CHANGED
@@ -1,6 +1,18 @@
1
1
  ```markdown
2
2
  # Changelog
3
3
 
4
+ ## [0.1.308.pre.2] - 2026-03-31
5
+
6
+ ### Added
7
+ - `--pre`/`-p` flag to `h install` and `h update` for installing/updating pre-release versions
8
+
9
+ ### Changed
10
+ - Merge `prs` and `pinned_prs` tables into single `prs` table in SQLite schema
11
+ - Refactor PR storage to use unified table structure
12
+
13
+ ### Fixed
14
+ - YAML migration for todos, prs, pinned_prs, and tags to correctly handle merged schema
15
+
4
16
  ## [0.1.308.pre.1] - 2026-03-31
5
17
 
6
18
  ### Added
data/bin/h-db CHANGED
@@ -163,6 +163,14 @@ Hiiro.run(*ARGV) do
163
163
  puts " tar xvzf #{File.basename(archive)}"
164
164
  end
165
165
 
166
+ add_subcmd(:remigrate) do |*args|
167
+ opts = Hiiro::Options.parse(args) do
168
+ option(:only, desc: 'Comma-separated list of tables to remigrate (e.g. todos,tags,pinned_prs)')
169
+ end
170
+ tables = opts[:only]&.split(',')&.map(&:strip)
171
+ Hiiro::DB.remigrate!(only: tables)
172
+ end
173
+
166
174
  add_subcmd(:restore) do
167
175
  config_dir = File.expand_path('~/.config/hiiro')
168
176
  archives = Dir.glob(File.join(config_dir, 'sqlite-migration.*.tar.gz')).sort
data/exe/h CHANGED
@@ -3,12 +3,12 @@
3
3
  require "hiiro"
4
4
  require "fileutils"
5
5
 
6
- def update_hiiro(version=nil)
6
+ def update_hiiro(version=nil, pre: false)
7
7
  ver = version || Hiiro::Rbenv.current_version
8
8
  puts
9
- puts "INSTALLING hiiro for RUBY #{ver}"
9
+ puts "INSTALLING hiiro for RUBY #{ver} #{pre.inspect}"
10
10
 
11
- Hiiro::Rbenv.install_gem('hiiro', version: ver)
11
+ Hiiro::Rbenv.install_gem('hiiro', pre: pre, version: ver)
12
12
  Hiiro::Rbenv.run('h', 'setup', version: ver)
13
13
  end
14
14
 
@@ -32,20 +32,21 @@ Hiiro.run(*ARGV, cwd: Dir.pwd, tasks: true) do
32
32
  add_subcmd(:install, :update) { |*args|
33
33
  opts = Hiiro::Options.setup {
34
34
  flag(:all, short: :a)
35
+ flag(:pre, short: :p)
35
36
  }.parse!(ARGV)
36
37
 
37
38
  if opts.all
38
39
  Hiiro::Rbenv.versions.each do |ver|
39
40
  shell_cmd = [
40
- "(RBENV_VERSION=#{ver.shellescape} rbenv exec gem update hiiro",
41
- "|| RBENV_VERSION=#{ver.shellescape} rbenv exec gem install hiiro)",
41
+ "(RBENV_VERSION=#{ver.shellescape} rbenv exec gem update hiiro #{opts.pre ? '--pre' : ''}",
42
+ "|| RBENV_VERSION=#{ver.shellescape} rbenv exec gem install hiiro #{opts.pre ? '--pre' : ''})",
42
43
  "&& RBENV_VERSION=#{ver.shellescape} rbenv exec h setup",
43
44
  ].join(' ')
44
45
  puts "Queuing update for Ruby #{ver}..."
45
46
  Hiiro::Background.run('sh', '-c', shell_cmd)
46
47
  end
47
48
  else
48
- update_hiiro
49
+ update_hiiro(pre: opts.pre)
49
50
  end
50
51
  }
51
52
 
@@ -0,0 +1,36 @@
1
+ require 'sequel'
2
+
3
+ class Hiiro
4
+ class CheckRun < Sequel::Model(:check_runs)
5
+ Hiiro::DB.register(self)
6
+
7
+ def self.create_table!(db)
8
+ db.create_table?(:check_runs) do
9
+ primary_key :id
10
+ Integer :pr_number, null: false
11
+ String :name
12
+ String :url
13
+ String :status # COMPLETED, IN_PROGRESS, QUEUED, WAITING, PENDING
14
+ String :conclusion # SUCCESS, FAILURE, CANCELLED, SKIPPED, NEUTRAL, TIMED_OUT
15
+ String :updated_at
16
+ index :pr_number
17
+ end
18
+ end
19
+
20
+ def self.for_pr(number) = where(pr_number: number.to_i).all
21
+ def self.upsert_for_pr(number, runs)
22
+ where(pr_number: number.to_i).delete
23
+ Array(runs).each do |run|
24
+ next unless run.is_a?(Hash)
25
+ insert(
26
+ pr_number: number.to_i,
27
+ name: run['name']&.to_s,
28
+ url: (run['url'] || run['detailsUrl'])&.to_s,
29
+ status: run['status']&.to_s,
30
+ conclusion: run['conclusion']&.to_s,
31
+ updated_at: Time.now.iso8601
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
data/lib/hiiro/db.rb CHANGED
@@ -69,6 +69,18 @@ class Hiiro
69
69
  connection[:schema_migrations].insert_conflict.insert(name: 'full_migration', ran_at: Time.now.iso8601)
70
70
  end
71
71
 
72
+ ALL_IMPORTERS = %i[todos links branches prs pinned_prs tasks assignments apps projects pane_homes pins reminders tags].freeze
73
+
74
+ # Re-run import for specific tables (or all if none specified).
75
+ # Useful after a migration that partially failed. The YAML source files must
76
+ # still exist (they are only renamed to .bak on successful import).
77
+ def remigrate!(only: nil)
78
+ base = Hiiro::Config::BASE_DIR
79
+ targets = only ? Array(only).map(&:to_sym) & ALL_IMPORTERS : ALL_IMPORTERS
80
+ targets.each { |name| send(:"import_#{name}", base) }
81
+ puts "Remigration complete: #{targets.join(', ')}"
82
+ end
83
+
72
84
  private
73
85
 
74
86
  def migrate_yaml!
@@ -105,6 +117,8 @@ class Hiiro
105
117
  File.rename(path, path + '.bak') if File.exist?(path)
106
118
  end
107
119
 
120
+ TODO_COLUMNS = %w[id text status tags task_name subtask_name tree branch session created_at updated_at].freeze
121
+
108
122
  def import_todos(base)
109
123
  path = File.join(base, 'todo.yml')
110
124
  data = load_yaml(path)
@@ -113,7 +127,35 @@ class Hiiro
113
127
  todos = data.is_a?(Hash) ? (data['todos'] || []) : []
114
128
  todos.each do |row|
115
129
  next unless row.is_a?(Hash)
116
- connection[:todos].insert(row.transform_keys(&:to_s))
130
+ r = row.transform_keys(&:to_s)
131
+
132
+ # Map legacy boolean started/done fields to status string
133
+ status = if r['done'] == true || r['status'] == 'done'
134
+ 'done'
135
+ elsif r['started'] == true || r['status'] == 'started'
136
+ 'started'
137
+ elsif r['skip'] == true || r['status'] == 'skip'
138
+ 'skip'
139
+ elsif r['status']
140
+ r['status'].to_s
141
+ else
142
+ 'not_started'
143
+ end
144
+
145
+ record = {
146
+ 'text' => r['text']&.to_s,
147
+ 'status' => status,
148
+ 'tags' => r['tags']&.to_s,
149
+ 'task_name' => r['task_name']&.to_s,
150
+ 'subtask_name' => r['subtask_name']&.to_s,
151
+ 'tree' => r['tree']&.to_s,
152
+ 'branch' => r['branch']&.to_s,
153
+ 'session' => r['session']&.to_s,
154
+ 'created_at' => r['created_at']&.to_s,
155
+ 'updated_at' => r['updated_at']&.to_s,
156
+ }.compact
157
+ record['id'] = r['id'].to_i if r['id']
158
+ connection[:todos].insert(record)
117
159
  end
118
160
  bak(path)
119
161
  rescue => e
@@ -153,6 +195,10 @@ class Hiiro
153
195
  warn "Hiiro::DB: failed to import branches: #{e}"
154
196
  end
155
197
 
198
+ # Old prs.yml from TrackedPr (worktree-tracked PRs, not pinned). Now imported
199
+ # into the merged :prs table with pinned: false.
200
+ TRACKED_PR_COLUMNS = %w[number title url branch state worktree task tmux_json created_at updated_at].freeze
201
+
156
202
  def import_prs(base)
157
203
  path = File.join(base, 'prs.yml')
158
204
  data = load_yaml(path)
@@ -164,27 +210,65 @@ class Hiiro
164
210
  normalized = row.transform_keys(&:to_s)
165
211
  tmux = normalized.delete('tmux')
166
212
  normalized['tmux_json'] = ::JSON.generate(tmux) if tmux
167
- connection[:prs].insert(normalized)
213
+ record = normalized.select { |k, _| TRACKED_PR_COLUMNS.include?(k) }
214
+ record['pinned'] = false
215
+ connection[:prs].insert(record) unless record.empty?
168
216
  end
169
217
  bak(path)
170
218
  rescue => e
171
219
  warn "Hiiro::DB: failed to import prs: #{e}"
172
220
  end
173
221
 
222
+ # Maps YAML camelCase/legacy keys to the merged prs schema column names.
223
+ PINNED_PR_KEY_MAP = {
224
+ 'headRefName' => 'head_ref_name',
225
+ 'checks' => 'checks_json',
226
+ 'statusCheckRollup' => 'check_runs_json',
227
+ 'reviews' => 'reviews_json',
228
+ 'tags' => 'tags_json',
229
+ 'depends_on' => 'depends_on_json',
230
+ }.freeze
231
+
232
+ PINNED_PR_COLUMNS = %w[
233
+ number title state url head_ref_name branch repo slot pinned is_draft mergeable
234
+ review_decision checks_json check_runs_json reviews_json task worktree
235
+ tmux_session tmux_json tags_json assigned authored depends_on_json last_checked
236
+ pinned_at created_at updated_at
237
+ ].freeze
238
+
174
239
  def import_pinned_prs(base)
175
240
  path = File.join(base, 'pinned_prs.yml')
176
241
  data = load_yaml(path)
177
242
  return unless data
178
243
 
179
244
  prs = data.is_a?(Array) ? data : []
180
- json_fields = %w[checks statusCheckRollup reviews tags depends_on]
181
245
  prs.each do |row|
182
246
  next unless row.is_a?(Hash)
183
247
  normalized = row.transform_keys(&:to_s)
184
- json_fields.each do |field|
185
- normalized[field] = ::JSON.generate(normalized[field]) if normalized.key?(field)
248
+
249
+ # Rename camelCase/legacy keys to schema column names
250
+ PINNED_PR_KEY_MAP.each do |from, to|
251
+ normalized[to] = normalized.delete(from) if normalized.key?(from)
252
+ end
253
+
254
+ # JSON-encode any complex values in *_json columns
255
+ %w[checks_json check_runs_json reviews_json tags_json depends_on_json].each do |col|
256
+ v = normalized[col]
257
+ normalized[col] = ::JSON.generate(v) if v && !v.is_a?(String)
258
+ end
259
+
260
+ # Filter to known schema columns, mark as pinned
261
+ record = normalized.select { |k, _| PINNED_PR_COLUMNS.include?(k) }
262
+ record['pinned'] = true
263
+ next if record.empty?
264
+
265
+ connection[:prs].insert(record)
266
+
267
+ # Populate check_runs table from check_runs_json if present
268
+ if (runs_json = record['check_runs_json'])
269
+ runs = ::JSON.parse(runs_json) rescue nil
270
+ Hiiro::CheckRun.upsert_for_pr(record['number'], runs) if runs && record['number']
186
271
  end
187
- connection[:pinned_prs].insert(normalized)
188
272
  end
189
273
  bak(path)
190
274
  rescue => e
@@ -321,6 +405,14 @@ class Hiiro
321
405
  warn "Hiiro::DB: failed to import reminders: #{e}"
322
406
  end
323
407
 
408
+ # Maps old YAML tag namespace to the Sequel model class name used in taggable_type.
409
+ TAG_NAMESPACE_TO_TYPE = {
410
+ 'branch' => 'Branch',
411
+ 'pr' => 'PinnedPr',
412
+ 'link' => 'Link',
413
+ 'task' => 'TaskRecord',
414
+ }.freeze
415
+
324
416
  def import_tags(base)
325
417
  path = File.join(base, 'tags.yml')
326
418
  data = load_yaml(path)
@@ -328,13 +420,14 @@ class Hiiro
328
420
 
329
421
  data.each do |namespace, keys|
330
422
  next unless keys.is_a?(Hash)
423
+ taggable_type = TAG_NAMESPACE_TO_TYPE[namespace.to_s] || namespace.to_s.capitalize
331
424
  keys.each do |key, tags|
332
425
  next unless tags.is_a?(Array)
333
426
  tags.each do |tag|
334
427
  connection[:tags].insert(
335
- 'namespace' => namespace.to_s,
336
- 'key' => key.to_s,
337
- 'tag' => tag.to_s
428
+ 'name' => tag.to_s,
429
+ 'taggable_type' => taggable_type,
430
+ 'taggable_id' => key.to_s
338
431
  )
339
432
  end
340
433
  end
@@ -1,19 +1,28 @@
1
1
  require 'sequel'
2
2
 
3
3
  class Hiiro
4
- class PinnedPr < Sequel::Model(:pinned_prs)
4
+ class PinnedPr < Sequel::Model(:prs)
5
5
  Hiiro::DB.register(self)
6
6
 
7
7
  def self.create_table!(db)
8
- db.create_table?(:pinned_prs) do
8
+ # Drop the old TrackedPr schema (prs table without head_ref_name) so we can
9
+ # create the merged schema. TrackedPr rows are migration artifacts only — no
10
+ # live code reads from them.
11
+ if db.table_exists?(:prs) && !db.schema(:prs).any? { |col, _| col == :head_ref_name }
12
+ db.drop_table(:prs)
13
+ end
14
+
15
+ db.create_table?(:prs) do
9
16
  primary_key :id
10
17
  Integer :number
11
18
  String :title
12
19
  String :state
13
20
  String :url
14
21
  String :head_ref_name
22
+ String :branch # from old TrackedPr
15
23
  String :repo
16
24
  Integer :slot
25
+ TrueClass :pinned # true = pinned/monitored, false/nil = just tracked
17
26
  TrueClass :is_draft
18
27
  String :mergeable
19
28
  String :review_decision
@@ -23,14 +32,24 @@ class Hiiro
23
32
  String :task
24
33
  String :worktree
25
34
  String :tmux_session
35
+ String :tmux_json # from old TrackedPr: JSON { session, window, pane }
26
36
  String :tags_json # JSON array of strings
27
37
  TrueClass :assigned
28
38
  TrueClass :authored
29
39
  String :depends_on_json # JSON array of PR numbers
30
40
  String :last_checked
31
41
  String :pinned_at
42
+ String :created_at
32
43
  String :updated_at
33
44
  end
45
+
46
+ # Migrate data from legacy pinned_prs table if it still exists
47
+ if db.table_exists?(:pinned_prs) && db[:pinned_prs].count > 0
48
+ db[:pinned_prs].each do |row|
49
+ db[:prs].insert(row.reject { |k, _| k == :id }.merge(pinned: true))
50
+ end
51
+ end
52
+ db.drop_table?(:pinned_prs)
34
53
  end
35
54
 
36
55
  # --- JSON virtual accessors ---
@@ -54,11 +73,17 @@ class Hiiro
54
73
  # --- Class methods ---
55
74
 
56
75
  def self.by_slot
57
- order(:slot)
76
+ order(:slot).where(pinned: true)
58
77
  end
59
78
 
60
79
  def self.find_by_number(n)
61
- where(number: n).first
80
+ where(number: n, pinned: true).first
81
+ end
82
+
83
+ # Sync the check_runs table for this PR from the cached check_runs_json blob.
84
+ def sync_check_runs
85
+ runs = Hiiro::DB::JSON.load(check_runs_json)
86
+ Hiiro::CheckRun.upsert_for_pr(number, runs) if number && runs
62
87
  end
63
88
 
64
89
  # Build an in-memory PinnedPr from a Hiiro::Git::Pr (does NOT save).
@@ -71,6 +96,7 @@ class Hiiro
71
96
  head_ref_name: pr.head_branch,
72
97
  repo: pr.repo,
73
98
  slot: pr.slot,
99
+ pinned: true,
74
100
  is_draft: pr.is_draft,
75
101
  mergeable: pr.mergeable,
76
102
  review_decision: pr.review_decision,
@@ -89,11 +89,14 @@ class Hiiro
89
89
 
90
90
  def save_pinned(prs)
91
91
  Hiiro::DB.connection.transaction do
92
- Hiiro::PinnedPr.dataset.delete
92
+ Hiiro::PinnedPr.where(pinned: true).delete
93
93
  prs.each do |pr|
94
94
  pinned = pr.is_a?(Hiiro::PinnedPr) ? pr : Hiiro::PinnedPr.from_git_pr(pr)
95
95
  attrs = pinned.to_hash.reject { |k, _| k == :id }
96
- Hiiro::PinnedPr.insert(attrs)
96
+ attrs[:pinned] = true
97
+ saved = Hiiro::PinnedPr.new(attrs)
98
+ saved.save
99
+ saved.sync_check_runs
97
100
  end
98
101
  end
99
102
  # Dual-write YAML for backward compat
@@ -130,11 +133,14 @@ class Hiiro
130
133
  updates[:assigned] = pr.assigned unless pr.assigned.nil?
131
134
  updates[:authored] = pr.authored unless pr.authored.nil?
132
135
  existing.update(updates)
136
+ existing.sync_check_runs if updates.key?(:check_runs_json)
133
137
  else
134
138
  pinned = pr.is_a?(Hiiro::PinnedPr) ? pr : Hiiro::PinnedPr.from_git_pr(pr)
135
- pinned.slot = Hiiro::PinnedPr.max(:slot).to_i + 1
139
+ pinned.slot = Hiiro::PinnedPr.max(:slot).to_i + 1
136
140
  pinned.pinned_at = Time.now.iso8601
141
+ pinned.pinned = true
137
142
  pinned.save
143
+ pinned.sync_check_runs
138
144
  end
139
145
 
140
146
  # Dual-write YAML
data/lib/hiiro/rbenv.rb CHANGED
@@ -69,17 +69,17 @@ class Hiiro
69
69
  end
70
70
 
71
71
  # Install or update a gem in the given version.
72
- def install_gem(gem_name, version: current_version)
72
+ def install_gem(gem_name, version: current_version, pre: false)
73
73
  if gem_installed?(gem_name, version: version)
74
- run('gem', 'update', gem_name, version: version)
74
+ run('gem', 'update', gem_name, pre ? '--pre' : '', version: version)
75
75
  else
76
- run('gem', 'install', gem_name, version: version)
76
+ run('gem', 'install', gem_name, pre ? '--pre' : '', version: version)
77
77
  end
78
78
  end
79
79
 
80
80
  # Install or update a gem across all installed versions.
81
- def install_gem_in_all(gem_name)
82
- versions.each { |ver| install_gem(gem_name, version: ver) }
81
+ def install_gem_in_all(gem_name, pre: false)
82
+ versions.each { |ver| install_gem(gem_name, pre: pre, version: ver) }
83
83
  end
84
84
 
85
85
  # --- Path helpers ---
data/lib/hiiro/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Hiiro
2
- VERSION = "0.1.308.pre.1"
2
+ VERSION = "0.1.308.pre.2"
3
3
  end
data/lib/hiiro.rb CHANGED
@@ -21,7 +21,7 @@ require_relative "hiiro/input_file"
21
21
  require_relative "hiiro/paths"
22
22
  require_relative "hiiro/link"
23
23
  require_relative "hiiro/branch"
24
- require_relative "hiiro/tracked_pr"
24
+ require_relative "hiiro/check_run"
25
25
  require_relative "hiiro/tags"
26
26
  require_relative "hiiro/queue"
27
27
  require_relative "hiiro/task_record"
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.308.pre.1
4
+ version: 0.1.308.pre.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Toyota
@@ -289,6 +289,7 @@ files:
289
289
  - lib/hiiro/background.rb
290
290
  - lib/hiiro/bins.rb
291
291
  - lib/hiiro/branch.rb
292
+ - lib/hiiro/check_run.rb
292
293
  - lib/hiiro/config.rb
293
294
  - lib/hiiro/db.rb
294
295
  - lib/hiiro/effects.rb
@@ -334,7 +335,6 @@ files:
334
335
  - lib/hiiro/tmux/window.rb
335
336
  - lib/hiiro/tmux/windows.rb
336
337
  - lib/hiiro/todo.rb
337
- - lib/hiiro/tracked_pr.rb
338
338
  - lib/hiiro/version.rb
339
339
  - notes
340
340
  - obsidian_slides.md
@@ -1,30 +0,0 @@
1
- require 'sequel'
2
-
3
- class Hiiro
4
- class TrackedPr < Sequel::Model(:prs)
5
- Hiiro::DB.register(self)
6
-
7
- def self.create_table!(db)
8
- db.create_table?(:prs) do
9
- primary_key :id
10
- Integer :number
11
- String :title
12
- String :url
13
- String :branch
14
- String :state
15
- String :worktree
16
- String :task
17
- String :tmux_json # JSON: { session, window, pane }
18
- String :created_at
19
- end
20
- end
21
-
22
- def tmux = Hiiro::DB::JSON.load(tmux_json) || {}
23
- def tmux=(v) ; self.tmux_json = Hiiro::DB::JSON.dump(v); end
24
-
25
- def self.for_task(t) = where(task: t).all
26
- def self.for_worktree(w) = where(worktree: w).all
27
- def self.open = where(state: 'OPEN').all
28
- def self.find_by_number(n) = where(number: n).first
29
- end
30
- end