hiiro 0.1.353 → 0.1.354

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: bbd7598e65b8521905ec67a960f09700d7260c5329e066e6cfcbb4661c180c99
4
- data.tar.gz: 189ca275fa4cc2af210c1d67c8976bc4b90343e68eb4a0fe86ebedea1421bb9f
3
+ metadata.gz: 9a368e5a5a2c932aa609faa46c699b3a112e835908fe7788cdab234f583a92ee
4
+ data.tar.gz: 2153cf58429b63859d220615d9a5404fa14e1388ce735bc385d399c2856ca204
5
5
  SHA512:
6
- metadata.gz: 50b6f3d077471531303d04f9ef234ff4ade40dbea85b52f575e6c9e3c48088ba1318dd0798eaeef15acdeb4ce0ca8fcf13100d4963fb5174dccdefaf87de228f
7
- data.tar.gz: 4c852f5cea47667811dd2dd461b5c1bdfe3935cc7fecad2c95c7d3d43f28d3c7aea0554ea415cfda812e776b047bf4dee9ef77b34fdb0ffc5b806170864e2705
6
+ metadata.gz: 422c65b26c19e47db3a28eb5643f9b525ba862dbfcdab1d75ada76f43b1c3add1bc70ce97183d4934a06403fc6bb4fa244b8821127c0a0e6ec8b9778e8e022a0
7
+ data.tar.gz: 1b005ce7ebb9acec393d6587d939ef1623c5d69a1d8869bf9540ca1e4e4095008bc4d6a3f0fa76bbcac6ac7849059b8166ae2e6fb08073ed31d48d186356459f
data/CHANGELOG.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ### Added
6
+ - `h task from <worktree-path> <task-name>` registers an existing git worktree from any path as a Hiiro task and switches to it.
7
+
5
8
  ## [0.1.353] - 2026-04-26
6
9
 
7
10
  ### Added
@@ -329,4 +332,4 @@
329
332
  - `h db cleanup` subcommand to preview and prune duplicate rows from SQLite tables
330
333
 
331
334
  ### Fixed
332
- - Prevent duplicate pinned_prs during import with `insert_conflict` and per-row rescue
335
+ - Prevent duplicate pinned_prs during import with `insert_conflict` and per-row rescue
data/CLAUDE.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
4
 
5
+ ## Safety
6
+
7
+ - NEVER commit secrets, tokens, api keys, etc. do not include any work-related content or information
8
+
5
9
  ## Project Overview
6
10
 
7
11
  Hiiro is a lightweight CLI framework for Ruby that enables building multi-command tools similar to `git` or `docker`. It provides subcommand dispatch, abbreviation matching (e.g., `h ex hel` matches `h example hello`), and a plugin system.
@@ -546,4 +550,3 @@ All config lives in `~/.config/hiiro/`:
546
550
  - lib/hiiro/tmux.rb
547
551
  - lib/hiiro/tmux/*
548
552
 
549
-
data/README.md CHANGED
@@ -65,7 +65,7 @@ h ping
65
65
  | `h setup` | Install plugins and subcommands to system paths |
66
66
  | `h edit` | Open the h script in your editor |
67
67
  | `h alert` | macOS desktop notifications via terminal-notifier |
68
- | `h task` | Task management across git worktrees (via Tasks plugin) |
68
+ | `h task` | Task management across git worktrees, including existing external worktrees (via Tasks plugin) |
69
69
  | `h subtask` | Subtask management within tasks (via Tasks plugin) |
70
70
 
71
71
  ### External Subcommands
@@ -116,7 +116,7 @@ Plugins are Ruby modules loaded from `~/.config/hiiro/plugins/`:
116
116
  |--------|-------------|
117
117
  | Pins | Per-command YAML key-value storage |
118
118
  | Project | Project directory navigation with tmux session management |
119
- | Tasks | Task lifecycle management across git worktrees with subtask support |
119
+ | Tasks | Task lifecycle management across git worktrees with external-worktree registration and subtask support |
120
120
  | Notify | macOS desktop notifications via terminal-notifier |
121
121
 
122
122
  ## Adding Subcommands
data/docs/h-task.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # h task
2
2
 
3
- Manage top-level tasks. Each task is a worktree + tmux session pair. Tasks enable parallel development across multiple features — each with an isolated checkout and its own tmux session.
3
+ Manage top-level tasks. Each task is a worktree + tmux session pair. Tasks enable parallel development across multiple features — each with an isolated checkout and its own tmux session. `h task start` creates worktrees under Hiiro's default work root; `h task from` can register an existing worktree from any repo/path.
4
4
 
5
5
  Tasks are stored in `~/.config/hiiro/tasks/tasks.yml` (SQLite-backed with YAML backup).
6
6
 
@@ -164,6 +164,30 @@ h task file add myapp path/to/file.rb
164
164
 
165
165
  ---
166
166
 
167
+ ### from
168
+
169
+ Register an existing git worktree as a Hiiro task and switch to it. The path is normalized to the worktree root with `git rev-parse --show-toplevel`, then stored as the task's tree path.
170
+
171
+ **Examples**
172
+
173
+ ```bash
174
+ h task from ~/ic_repos/other_repo_base_dir other-task
175
+ h task path other-task
176
+ h task switch other-task
177
+ ```
178
+
179
+ Stored task shape:
180
+
181
+ ```js
182
+ {
183
+ name: "other-task",
184
+ tree: "/Users/josh/ic_repos/other_repo_base_dir",
185
+ session: "other-task"
186
+ }
187
+ ```
188
+
189
+ ---
190
+
167
191
  ### ls / list
168
192
 
169
193
  List all tasks with their worktree, branch, and session. Also shows available (unassigned) worktrees and extra tmux sessions.
@@ -548,4 +572,3 @@ h task untag my-feature # remove all tags
548
572
  List worktrees for the current task. Delegates to `h wtree`.
549
573
 
550
574
  ---
551
-
data/docs/h.md CHANGED
@@ -30,7 +30,7 @@ These subcommands are defined directly in `exe/h` or loaded from `lib/`:
30
30
  | [`h service`](h-service.md) | Dev service management with tmux, env files, and service groups | `lib/hiiro/service_manager.rb` |
31
31
  | `h setup` | Install plugins and bin scripts to `~/bin` | `exe/h` |
32
32
  | [`h subtask`](h-subtask.md) | Subtask management scoped to the current parent task | `lib/hiiro/tasks.rb` |
33
- | [`h task`](h-task.md) | Task management — worktree + tmux session pairs for parallel development | `lib/hiiro/tasks.rb` |
33
+ | [`h task`](h-task.md) | Task management — worktree + tmux session pairs, including existing external worktrees | `lib/hiiro/tasks.rb` |
34
34
  | `h version` | Print installed hiiro version (`-a` for all rbenv versions) | `exe/h` |
35
35
 
36
36
  ## External subcommands
data/lib/hiiro/tasks.rb CHANGED
@@ -91,7 +91,10 @@ class Hiiro
91
91
  return target.path if target.is_a?(FallbackTarget)
92
92
 
93
93
  tree = environment.find_tree(target.tree_name)
94
- tree ? tree.path : File.join(Hiiro::WORK_DIR, target.tree_name)
94
+ return tree.path if tree
95
+ return target.tree_name if target.absolute_tree?
96
+
97
+ File.join(Hiiro::WORK_DIR, target.tree_name)
95
98
  end
96
99
 
97
100
  def task_by_tree(tree_name)
@@ -116,6 +119,43 @@ class Hiiro
116
119
 
117
120
  # --- Actions ---
118
121
 
122
+ def task_from_worktree(path, name)
123
+ if path.nil? || name.nil? || name.empty?
124
+ puts "Usage: h #{scope} from <worktree-path> <task-name>"
125
+ return
126
+ end
127
+
128
+ root = git_worktree_root(path)
129
+ unless root
130
+ puts "ERROR: '#{path}' is not inside a git worktree"
131
+ return
132
+ end
133
+
134
+ existing = task_by_name(name)
135
+ if existing
136
+ existing_path = resolve_path(existing)
137
+ if same_path?(existing_path, root)
138
+ puts "Task '#{existing.name}' already uses '#{root}'. Switching..."
139
+ return existing
140
+ end
141
+
142
+ puts "ERROR: Task '#{existing.name}' already exists for '#{existing_path}'"
143
+ return
144
+ end
145
+
146
+ existing_for_path = environment.all_tasks.find { |task| same_path?(resolve_path(task), root) }
147
+ if existing_for_path
148
+ puts "Worktree '#{root}' is already registered as task '#{existing_for_path.name}'. Switching..."
149
+ return existing_for_path
150
+ end
151
+
152
+ color_index = Hiiro::TaskColors.next_index(config.tasks.map(&:color_index).compact)
153
+ task = Task.new(name: name, tree: root, session: name, color_index: color_index)
154
+ config.save_task(task)
155
+ puts "Added task '#{name}' from worktree '#{root}'"
156
+ task
157
+ end
158
+
119
159
  def start_task(name, app_name: nil, sparse_groups: [])
120
160
  existing = task_by_name(name)
121
161
  if existing
@@ -185,8 +225,7 @@ class Hiiro
185
225
  return
186
226
  end
187
227
 
188
- tree = environment.find_tree(task.tree_name)
189
- tree_path = tree ? tree.path : File.join(Hiiro::WORK_DIR, task.tree_name)
228
+ tree_path = resolve_path(task)
190
229
 
191
230
  session_name = task.session_name
192
231
  session_exists = system('tmux', 'has-session', '-t', "=#{session_name}", err: File::NULL)
@@ -369,8 +408,7 @@ class Hiiro
369
408
 
370
409
  puts "Task: #{task.name}"
371
410
  puts "Worktree: #{task.tree_name}"
372
- tree = environment.find_tree(task.tree_name)
373
- puts "Path: #{tree&.path || '(unknown)'}"
411
+ puts "Path: #{resolve_path(task) || '(unknown)'}"
374
412
  puts "Session: #{task.session_name}"
375
413
  puts "Parent: #{task.parent_name}" if task.subtask?
376
414
  end
@@ -445,9 +483,7 @@ class Hiiro
445
483
  return
446
484
  end
447
485
 
448
- tree = environment.find_tree(task.tree_name)
449
- path = tree ? tree.path : File.join(Hiiro::WORK_DIR, task.tree_name)
450
- send_cd(path)
486
+ send_cd(resolve_path(task))
451
487
  end
452
488
 
453
489
  def cd_to_app(app_name = nil)
@@ -458,8 +494,7 @@ class Hiiro
458
494
  end
459
495
 
460
496
  if app_name.nil? || app_name.empty?
461
- tree = environment.find_tree(task.tree_name)
462
- send_cd(tree&.path || File.join(Hiiro::WORK_DIR, task.tree_name))
497
+ send_cd(resolve_path(task))
463
498
  return
464
499
  end
465
500
 
@@ -473,8 +508,7 @@ class Hiiro
473
508
  def app_path(app_name = nil)
474
509
  task = current_task
475
510
  tree_root = if task
476
- tree = environment.find_tree(task.tree_name)
477
- tree&.path || File.join(Hiiro::WORK_DIR, task.tree_name)
511
+ resolve_path(task)
478
512
  else
479
513
  Hiiro::Git.new(nil, Dir.pwd).root
480
514
  end
@@ -596,14 +630,31 @@ class Hiiro
596
630
  end
597
631
  end
598
632
 
633
+ def git_worktree_root(path)
634
+ expanded = File.expand_path(path.to_s)
635
+ return nil unless Dir.exist?(expanded)
636
+
637
+ root = IO.popen(['git', '-C', expanded, 'rev-parse', '--show-toplevel'], err: File::NULL, &:read).to_s.strip
638
+ return nil if root.empty?
639
+
640
+ File.expand_path(root)
641
+ rescue
642
+ nil
643
+ end
644
+
645
+ def same_path?(left, right)
646
+ return false if left.nil? || right.nil?
647
+
648
+ File.expand_path(left) == File.expand_path(right)
649
+ end
650
+
599
651
  def find_available_tree
600
652
  assigned_tree_names = environment.all_tasks.map(&:tree_name)
601
653
  environment.all_trees.find { |tree| !assigned_tree_names.include?(tree.name) }
602
654
  end
603
655
 
604
656
  def resolve_app(app_name, task)
605
- tree = environment.find_tree(task.tree_name)
606
- tree_root = tree ? tree.path : File.join(Hiiro::WORK_DIR, task.tree_name)
657
+ tree_root = resolve_path(task)
607
658
 
608
659
  result = environment.app_matcher.find_all(app_name)
609
660
 
@@ -1036,6 +1087,13 @@ class Hiiro
1036
1087
 
1037
1088
  h.add_subcmd(:apps) { tm.list_apps }
1038
1089
 
1090
+ if tm.scope == :task
1091
+ h.add_subcmd(:from) do |path = nil, task_name = nil|
1092
+ task = tm.task_from_worktree(path, task_name)
1093
+ tm.switch_to_task(task) if task
1094
+ end
1095
+ end
1096
+
1039
1097
  h.add_subcmd(:cd) do |*raw_args|
1040
1098
  opts = Hiiro::Options.parse(raw_args, &task_opts_block)
1041
1099
  task, positional = resolve_task.call(opts, opts.args)
@@ -1419,6 +1477,13 @@ class Hiiro
1419
1477
  class Tree
1420
1478
  attr_reader :path, :head, :branch
1421
1479
 
1480
+ def self.from_path(path)
1481
+ expanded = File.expand_path(path.to_s)
1482
+ branch = Hiiro::Git.new(nil, expanded).branch if Dir.exist?(expanded)
1483
+ branch = nil if branch.nil? || branch.empty? || branch == 'HEAD'
1484
+ new(path: expanded, branch: branch)
1485
+ end
1486
+
1422
1487
  def self.all(repo_path: Hiiro::REPO_PATH)
1423
1488
  git = Hiiro::Git.new(nil, repo_path)
1424
1489
  git.worktrees(repo_path: repo_path).filter_map do |wt|
@@ -1485,6 +1550,10 @@ class Hiiro
1485
1550
  !subtask?
1486
1551
  end
1487
1552
 
1553
+ def absolute_tree?
1554
+ tree_name.to_s.start_with?('/')
1555
+ end
1556
+
1488
1557
  def tree
1489
1558
  @tree ||= Environment.current&.find_tree(tree_name)
1490
1559
  end
@@ -1649,7 +1718,7 @@ class Hiiro
1649
1718
  t = tree
1650
1719
  all_tasks.find { |task|
1651
1720
  (s && task.session_name == s.name) ||
1652
- (t && task.tree_name == t.name)
1721
+ (t && (task.tree_name == t.name || task.tree_name == t.path))
1653
1722
  }
1654
1723
  end
1655
1724
  end
@@ -1659,7 +1728,7 @@ class Hiiro
1659
1728
  end
1660
1729
 
1661
1730
  def tree
1662
- @tree ||= all_trees.find { |t| t.match?(path) }
1731
+ @tree ||= all_trees.find { |t| t.match?(path) } || external_task_tree
1663
1732
  end
1664
1733
 
1665
1734
  def find_task(abbreviated)
@@ -1682,6 +1751,8 @@ class Hiiro
1682
1751
 
1683
1752
  def find_tree(abbreviated)
1684
1753
  return nil if abbreviated.nil?
1754
+ return Tree.from_path(abbreviated) if abbreviated.to_s.start_with?('/') && Dir.exist?(abbreviated)
1755
+
1685
1756
  tree_matcher.find(abbreviated).first&.item
1686
1757
  end
1687
1758
 
@@ -1694,5 +1765,10 @@ class Hiiro
1694
1765
  return nil if abbreviated.nil?
1695
1766
  app_matcher.find(abbreviated).first&.item
1696
1767
  end
1768
+
1769
+ def external_task_tree
1770
+ task = all_tasks.find { |t| t.absolute_tree? && t.tree_name && (path == t.tree_name || path.start_with?(t.tree_name + '/')) }
1771
+ Tree.from_path(task.tree_name) if task
1772
+ end
1697
1773
  end
1698
1774
  end
data/lib/hiiro/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Hiiro
2
- VERSION = "0.1.353"
2
+ VERSION = "0.1.354"
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.353
4
+ version: 0.1.354
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Toyota