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 +4 -4
- data/CHANGELOG.md +4 -1
- data/CLAUDE.md +4 -1
- data/README.md +2 -2
- data/docs/h-task.md +25 -2
- data/docs/h.md +1 -1
- data/lib/hiiro/tasks.rb +92 -16
- 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: 9a368e5a5a2c932aa609faa46c699b3a112e835908fe7788cdab234f583a92ee
|
|
4
|
+
data.tar.gz: 2153cf58429b63859d220615d9a5404fa14e1388ce735bc385d399c2856ca204
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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