hiiro 0.1.362 → 0.1.363
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/.DS_Store +0 -0
- data/CHANGELOG.md +8 -0
- data/README.md +55 -0
- data/docs/h-task.md +111 -8
- data/lib/hiiro/herdr.rb +17 -6
- data/lib/hiiro/options.rb +8 -3
- data/lib/hiiro/task_record.rb +51 -0
- data/lib/hiiro/tasks.rb +39 -31
- data/lib/hiiro/version.rb +1 -1
- data/lib/hiiro.rb +12 -3
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f52debf7400300975671eefe3d6bac6766179c179f40cecdafd91d611ae9500b
|
|
4
|
+
data.tar.gz: 2a5edf1448073cffb3afa07620c92a5e478fd057537f8f16c15d8234697bc28f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 22521e0b7c2be0f03b371e5471840eb81e504557b81b0d2a8f45242871160fa1960406d742d79ffc704f3333a3aa606be03519253bc0bf98beca3a9b49fd7ef7
|
|
7
|
+
data.tar.gz: 6c102bd975d77c9eadaeee63810bc04fefceb09f77b046caa1c7e05486c48928287be23815f9bd36665ad065324724e636e67d3ab0856c36cbd0a5634b7b13d4
|
data/.DS_Store
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
5
|
### Added
|
|
6
|
+
- Add the `t` task CLI with `--task`/`-t` selection, durable task status and next actions, document homes, resource references, and Herdr workspace/tab/pane commands.
|
|
7
|
+
- Allow block-only command dispatch with `external_commands: false`, keeping legacy `t-*` executables out of the task CLI.
|
|
6
8
|
- Add `Hiiro::Herdr`, a JSON-backed adapter for Herdr workspaces, tabs, panes, notifications, and command execution.
|
|
7
9
|
- Persist Herdr workspace/tab/pane IDs for invocations, branches, queued prompts, services, and tracked PRs, with read fallbacks for legacy metadata.
|
|
8
10
|
|
|
9
11
|
### Changed
|
|
12
|
+
- Keep task command definitions and helpers in `~/bin/t`, using `add_cmd` and native Hiiro help instead of a library-level TaskCLI and custom help template.
|
|
13
|
+
- Preserve task records and resource references when detaching or pruning worktrees.
|
|
10
14
|
- Move task, project, queue, service, background, Claude, app navigation, PR attach, title, and notification workflows from tmux to Herdr.
|
|
11
15
|
- Keep `h session` and `h window` as compatibility commands for Herdr workspaces and tabs.
|
|
12
16
|
|
|
@@ -15,6 +19,10 @@
|
|
|
15
19
|
- Remove the old `Hiiro::Tmux` adapter and tmux-specific command tests.
|
|
16
20
|
|
|
17
21
|
### Fixed
|
|
22
|
+
- Preserve `add_cmd` declaration locations and argument metadata in generated help; allow command groups to pass arguments and help through to child commands.
|
|
23
|
+
- Make undeclared `add_cmd opts:` entries boolean flags without consuming positional arguments; preserve explicit options and reserve conflicting short aliases.
|
|
24
|
+
- Show selected command options for `add_cmd -h`/`--help` without executing the command block.
|
|
25
|
+
- Focus the exact Herdr pane through the socket API and read the CLI's plain-text pane output without JSON parsing.
|
|
18
26
|
- Restore the missing `Hiiro::Bins` helper so `require "hiiro"` boots and commands like `h jumplist record` dispatch correctly.
|
|
19
27
|
- Make Hiiro's Ruby requirement explicit as Ruby 3.2+ and have rbenv-wide gem installs skip incompatible Ruby versions.
|
|
20
28
|
- Update the publish script to preserve the Ruby support constant, run only on supported Ruby, and install releases only into compatible rbenv versions.
|
data/README.md
CHANGED
|
@@ -59,6 +59,23 @@ h ping
|
|
|
59
59
|
# => pong
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
### Task CLI
|
|
63
|
+
|
|
64
|
+
`~/bin/t` contains the task commands and helpers, using Hiiro's `add_cmd` DSL
|
|
65
|
+
and existing task records. `t new NAME` creates a task and its notes directory
|
|
66
|
+
without creating a Git worktree. Select a task with `--task NAME` or `-t NAME`.
|
|
67
|
+
Root and group help are generated by Hiiro; leaf command help lists its selected options.
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
t new investigation
|
|
71
|
+
t next -t investigation "Inspect the failing request"
|
|
72
|
+
t doc new findings -t investigation
|
|
73
|
+
t doc open findings -t investigation
|
|
74
|
+
t show investigation
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
See [Task commands](docs/h-task.md) for the full command reference.
|
|
78
|
+
|
|
62
79
|
## Subcommands
|
|
63
80
|
|
|
64
81
|
### Base Commands
|
|
@@ -181,6 +198,44 @@ add_subcmd(:pwd) do |*args|
|
|
|
181
198
|
end
|
|
182
199
|
```
|
|
183
200
|
|
|
201
|
+
### Commands with declared options
|
|
202
|
+
|
|
203
|
+
Use `add_cmd` to expose a command's options in help. Undeclared names in `opts:`
|
|
204
|
+
become boolean flags with a default of `false`. A flag does not consume the next
|
|
205
|
+
positional argument.
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
Hiiro.run do
|
|
209
|
+
add_option :task, short: :t, desc: 'Task name'
|
|
210
|
+
|
|
211
|
+
add_cmd :test, opts: %i[a b c d task] do
|
|
212
|
+
puts 'a was set' if opts.a
|
|
213
|
+
puts opts.task if opts.task
|
|
214
|
+
puts opts.args
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Here, `test -a payload -t investigation` sets `opts.a` to `true`, leaves
|
|
220
|
+
`payload` in `opts.args`, and sets `opts.task` to `investigation`.
|
|
221
|
+
`test -h` and `test --help` display the selected options without running the
|
|
222
|
+
command block.
|
|
223
|
+
|
|
224
|
+
Automatic flags use their first letter as a short alias only when it is unique
|
|
225
|
+
among the command's automatic flags and does not conflict with a selected
|
|
226
|
+
explicit option or `-h`. Every automatic flag has a long form. Explicit flags
|
|
227
|
+
and value options keep their definitions.
|
|
228
|
+
|
|
229
|
+
For a command that delegates to `make_child`, use `add_cmd(..., passthrough: true)`.
|
|
230
|
+
It forwards control without parsing the parent's arguments or intercepting the
|
|
231
|
+
child's `--help`. The child declares and parses its own options. Generated help
|
|
232
|
+
uses the original command block's source location and declared `args:`, not the
|
|
233
|
+
internal wrapper's signature.
|
|
234
|
+
|
|
235
|
+
For a CLI that should dispatch only registered blocks, pass
|
|
236
|
+
`external_commands: false` to `Hiiro.run` and its `make_child` calls. This
|
|
237
|
+
prevents unrelated same-prefix executables on `PATH` from taking precedence.
|
|
238
|
+
|
|
184
239
|
## Writing Plugins
|
|
185
240
|
|
|
186
241
|
Plugins are Ruby modules that extend Hiiro instances:
|
data/docs/h-task.md
CHANGED
|
@@ -1,8 +1,111 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Task commands
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`t` manages durable tasks. A task can be a coding project, an investigation, or administrative work. It does not need Git or Herdr.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Task records and resource references live in `~/.config/hiiro/hiiro.db`, in the existing `tasks` table and the `task_resources` table. `h task` uses the same records. Its YAML file is a backup, not a separate task store.
|
|
6
|
+
|
|
7
|
+
## t
|
|
8
|
+
|
|
9
|
+
The command implementation lives in `~/bin/t`, not a `Hiiro::TaskCLI` library class. Commands use `add_cmd` with per-command argument and option declarations. Running `t` or `t doc` displays Hiiro's generated subcommand table, including declaration locations. Leaf help, such as `t directory add --help`, displays only that command's options without executing it. There is no separate task help template. `t` does not run `h task` or discover legacy `t-*` executables.
|
|
10
|
+
|
|
11
|
+
Every task command accepts `-t TASK` or `--task TASK` before or after the command. Explicit task names are exact and take precedence over the current directory or workspace. Conflicting explicit names are errors.
|
|
12
|
+
|
|
13
|
+
Without a selector, `t` considers the current directory inside a task home, an attached directory, or a legacy worktree. In a Herdr terminal, it also queries the current workspace. If the contexts identify different tasks, the command fails without changing task data. A shared directory therefore requires an explicit selector. Outside Herdr, an unrelated focused workspace does not affect task selection.
|
|
14
|
+
|
|
15
|
+
### Task records
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
t list [--all]
|
|
19
|
+
t show [TASK]
|
|
20
|
+
t current
|
|
21
|
+
t new TASK
|
|
22
|
+
t next [TEXT...] [--clear]
|
|
23
|
+
t status [active|waiting|done|archived]
|
|
24
|
+
t waiting [TEXT...] [--clear]
|
|
25
|
+
t done
|
|
26
|
+
t archive
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`list` shows active and waiting tasks. `--all` includes completed and archived tasks. `next`, `waiting`, and `status` without arguments display the current value.
|
|
30
|
+
|
|
31
|
+
`new TASK` creates a record and `~/notes/work/TASK`. It never creates a Git worktree, moves code, or launches Herdr. Repeating `new` keeps the existing record and ensures its home exists. Names contain 1-120 ASCII letters, digits, dots, underscores, or hyphens and start with a letter or digit. Existing names that contain other characters remain usable, with those characters percent-encoded in the computed home directory name.
|
|
32
|
+
|
|
33
|
+
Setting waiting text changes status to `waiting`. Clearing that text changes a waiting task back to `active`. `done` and `archive` change status and record timestamps. They never remove a task home, a file, a directory, a link, or a workspace. `status active` reopens a task.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
t new audit-invoices
|
|
37
|
+
t next -t audit-invoices 'Compare the September export'
|
|
38
|
+
t waiting -t audit-invoices 'Finance approval'
|
|
39
|
+
t waiting -t audit-invoices --clear
|
|
40
|
+
t done -t audit-invoices
|
|
41
|
+
t show audit-invoices
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Directory, link, PR, and file references
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
t directory add PATH [--primary] [--label LABEL]
|
|
48
|
+
t directory list
|
|
49
|
+
t directory open [ID|PATH|LABEL]
|
|
50
|
+
t link add URL [--kind general|issue|thread] [--label LABEL]
|
|
51
|
+
t link list [--kind general|issue|thread]
|
|
52
|
+
t link open [ID|URL|LABEL]
|
|
53
|
+
t pr add URL [--label LABEL]
|
|
54
|
+
t pr list
|
|
55
|
+
t pr open [ID|URL|LABEL]
|
|
56
|
+
t file add PATH [--label LABEL]
|
|
57
|
+
t file list
|
|
58
|
+
t file open [ID|PATH|LABEL]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Directory and file attachments must already exist. `add` stores their canonical paths without moving or copying anything. Repeating an identical attachment does not create another reference. `--primary` marks an attached directory as the default code directory for new workspace tabs and panes.
|
|
62
|
+
|
|
63
|
+
Links must be absolute HTTP or HTTPS URLs. `link list` includes PR references unless a kind filter is present. PRs use the same resource storage as other links and do not require a Git repository or provider API.
|
|
64
|
+
|
|
65
|
+
`file list` also discovers files in the task home, including documents, without registration. Home files can be opened by a relative path or an unambiguous basename. An omitted open selector works only when exactly one resource matches. Otherwise, the command requires an ID, path, URL, or unique label.
|
|
66
|
+
|
|
67
|
+
`open` uses the operating system's default application. Attachments remain references even after task completion.
|
|
68
|
+
|
|
69
|
+
### Documents
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
t doc new NAME [TITLE...]
|
|
73
|
+
t doc list
|
|
74
|
+
t doc open [NAME]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`doc new` creates a Markdown file in the task home with an initial heading. It never overwrites an existing file. Documents have a stable task-ID prefix, such as `task-42-investigation.md`, to avoid collisions in `mdoc`'s shared HTML output directory. `doc open investigation` accepts the short name and invokes `mdoc`. Existing Markdown files in the task home also appear without registration.
|
|
78
|
+
|
|
79
|
+
Task creation, metadata, references, and document creation/listing work without Git or Herdr. Document reading requires `mdoc` on `PATH` and its existing configuration.
|
|
80
|
+
|
|
81
|
+
### Herdr workspaces, tabs, and panes
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
t workspace open [--directory PATH]
|
|
85
|
+
t workspace show
|
|
86
|
+
t tab list
|
|
87
|
+
t tab new [LABEL] [--directory PATH] [--command COMMAND]
|
|
88
|
+
t tab open ID|LABEL
|
|
89
|
+
t pane list
|
|
90
|
+
t pane open ID|LABEL
|
|
91
|
+
t pane read ID|LABEL
|
|
92
|
+
t pane run ID|LABEL COMMAND...
|
|
93
|
+
t pane split ID|LABEL [--direction right|down] [--directory PATH] [--command COMMAND]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
These commands require a running Herdr server. `workspace open` focuses the workspace with the task's label or creates one. A new workspace starts in the explicit directory, the primary code directory, the legacy worktree, or the task home, in that order. `--directory` changes that operation's start directory without changing stored attachments.
|
|
97
|
+
|
|
98
|
+
`workspace show` queries the current tabs and panes. Tab and pane selectors must belong to the selected task's workspace. Duplicate labels require a live ID. No pane or tab ID is stored as durable task identity. Task workspace labels follow Herdr's dot-to-underscore normalization. Colliding task or workspace labels are errors rather than fuzzy matches.
|
|
99
|
+
|
|
100
|
+
Use `--` before literal command arguments that begin with a dash:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
t pane run -t audit-invoices PANE_ID -- printf '%s\n' --example
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## h task
|
|
107
|
+
|
|
108
|
+
The existing `h task` commands below retain their coding-worktree operations. Unlike `t new`, `h task start` can create a worktree for a new task. For a task without a worktree, path resolution uses its task home. Starting an existing noncoding task switches to that home without creating a worktree.
|
|
6
109
|
|
|
7
110
|
## Synopsis
|
|
8
111
|
|
|
@@ -225,19 +328,19 @@ h task path -a feat bug # tasks whose name starts with "feat" or "bug"
|
|
|
225
328
|
|
|
226
329
|
### prune
|
|
227
330
|
|
|
228
|
-
|
|
331
|
+
Detach worktree associations whose directories are missing. The task record, metadata, and resource references remain. Tasks without worktrees are not pruned. Defaults to a dry-run.
|
|
229
332
|
|
|
230
333
|
**Options**
|
|
231
334
|
|
|
232
335
|
| Flag | Short | Description | Default |
|
|
233
336
|
|------|-------|-------------|---------|
|
|
234
|
-
| `--force` | `-f` |
|
|
337
|
+
| `--force` | `-f` | Detach missing worktrees | false |
|
|
235
338
|
|
|
236
339
|
**Examples**
|
|
237
340
|
|
|
238
341
|
```bash
|
|
239
342
|
h task prune # show what would be pruned
|
|
240
|
-
h task prune -f #
|
|
343
|
+
h task prune -f # detach missing worktrees, retaining task records
|
|
241
344
|
```
|
|
242
345
|
|
|
243
346
|
---
|
|
@@ -264,7 +367,7 @@ h task queue hadd
|
|
|
264
367
|
|
|
265
368
|
### resume
|
|
266
369
|
|
|
267
|
-
|
|
370
|
+
Associate an available worktree with a new task or an existing task whose worktree was detached, then switch to it. Existing task metadata is preserved. With no argument, opens a fuzzyfind selector over available worktrees.
|
|
268
371
|
|
|
269
372
|
**Examples**
|
|
270
373
|
|
|
@@ -438,7 +541,7 @@ h task st
|
|
|
438
541
|
|
|
439
542
|
### stop
|
|
440
543
|
|
|
441
|
-
|
|
544
|
+
Detach a task's worktree association and those of its subtasks. The task records, metadata, workspace labels, and resources remain. The former worktree path becomes a directory reference, and the worktree is available for reuse through `resume`. With no arguments, opens a fuzzyfind selector.
|
|
442
545
|
|
|
443
546
|
**Examples**
|
|
444
547
|
|
data/lib/hiiro/herdr.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
require 'json'
|
|
2
|
+
require 'socket'
|
|
3
|
+
require 'timeout'
|
|
2
4
|
|
|
3
5
|
class Hiiro
|
|
4
6
|
class Herdr
|
|
@@ -487,11 +489,20 @@ class Hiiro
|
|
|
487
489
|
alias kill_pane close_pane
|
|
488
490
|
|
|
489
491
|
def focus_pane(ref)
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
492
|
+
status = @executor.capture('herdr', 'status', 'server')
|
|
493
|
+
socket_path = status.lines.find { |line| line.start_with?('socket: ') }&.delete_prefix('socket: ')&.strip
|
|
494
|
+
return false unless socket_path
|
|
495
|
+
|
|
496
|
+
request = { id: 'hiiro:pane:focus', method: 'pane.focus', params: { pane_id: ref } }
|
|
497
|
+
Timeout.timeout(5) do
|
|
498
|
+
UNIXSocket.open(socket_path) do |socket|
|
|
499
|
+
socket.puts(JSON.generate(request))
|
|
500
|
+
response = JSON.parse(socket.gets || '{}')
|
|
501
|
+
response.dig('result', 'pane', 'focused') == true
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
rescue SystemCallError, IOError, JSON::ParserError, Timeout::Error
|
|
505
|
+
false
|
|
495
506
|
end
|
|
496
507
|
alias select_pane focus_pane
|
|
497
508
|
|
|
@@ -546,7 +557,7 @@ class Hiiro
|
|
|
546
557
|
|
|
547
558
|
args = ['pane', 'read', pane_id, '--source', source]
|
|
548
559
|
args += ['--lines', lines.to_s] if lines
|
|
549
|
-
|
|
560
|
+
@executor.capture('herdr', *args)
|
|
550
561
|
end
|
|
551
562
|
alias capture_pane read_pane
|
|
552
563
|
|
data/lib/hiiro/options.rb
CHANGED
|
@@ -86,13 +86,18 @@ class Hiiro
|
|
|
86
86
|
public
|
|
87
87
|
|
|
88
88
|
def select(names)
|
|
89
|
+
names = names.map(&:to_sym).uniq
|
|
89
90
|
subset = self.class.setup {}
|
|
91
|
+
reserved_shorts = ['h'] + names.filter_map { |name| @definitions[name]&.short }
|
|
92
|
+
auto_shorts = names.reject { |name| @definitions.key?(name) }.map { |name| name.to_s[0] }.tally
|
|
90
93
|
names.each do |name|
|
|
91
|
-
defn = @definitions[name
|
|
94
|
+
defn = @definitions[name]
|
|
92
95
|
if defn
|
|
93
|
-
subset.definitions[name
|
|
96
|
+
subset.definitions[name] = defn
|
|
94
97
|
else
|
|
95
|
-
|
|
98
|
+
short = name.to_s[0]
|
|
99
|
+
short = nil if reserved_shorts.include?(short) || auto_shorts[short] > 1
|
|
100
|
+
subset.flag(name, short: short, desc: "auto-created flag: #{name}")
|
|
96
101
|
end
|
|
97
102
|
end
|
|
98
103
|
subset
|
data/lib/hiiro/task_record.rb
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
require 'sequel'
|
|
2
|
+
require 'uri'
|
|
2
3
|
|
|
3
4
|
class Hiiro
|
|
4
5
|
class TaskRecord < Sequel::Model(:tasks)
|
|
5
6
|
Hiiro::DB.register(self)
|
|
6
7
|
|
|
8
|
+
METADATA_COLUMNS = %i[status next_action waiting_on primary_directory updated_at completed_at archived_at].freeze
|
|
9
|
+
STATUSES = %w[active waiting done archived].freeze
|
|
10
|
+
|
|
7
11
|
def self.create_table!(db)
|
|
8
12
|
db.create_table?(:tasks) do
|
|
9
13
|
primary_key :id
|
|
@@ -17,6 +21,37 @@ class Hiiro
|
|
|
17
21
|
end
|
|
18
22
|
end
|
|
19
23
|
|
|
24
|
+
|
|
25
|
+
def self.migrate!(db)
|
|
26
|
+
columns = db.schema(:tasks).map(&:first)
|
|
27
|
+
METADATA_COLUMNS.each do |column|
|
|
28
|
+
db.alter_table(:tasks) { add_column column, String } unless columns.include?(column)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.home_for(name)
|
|
33
|
+
component = URI::DEFAULT_PARSER.escape(name.to_s, /[^A-Za-z0-9._-]/)
|
|
34
|
+
component = component.gsub('.', '%2E') if %w[. ..].include?(component)
|
|
35
|
+
raise ArgumentError, 'Task name cannot be empty' if component.empty?
|
|
36
|
+
|
|
37
|
+
File.join(Dir.home, 'notes', 'work', component)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def home
|
|
41
|
+
self.class.home_for(name)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def task_status
|
|
45
|
+
status || 'active'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def task_attributes
|
|
49
|
+
values.reject { |key, _| key == :id }.compact
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def resources
|
|
53
|
+
TaskResource.where(task_id: id).order(:id)
|
|
54
|
+
end
|
|
20
55
|
def self.top_level
|
|
21
56
|
where(Sequel.~(Sequel.like(:name, '%/%')))
|
|
22
57
|
end
|
|
@@ -34,4 +69,20 @@ class Hiiro
|
|
|
34
69
|
order(:name).all
|
|
35
70
|
end
|
|
36
71
|
end
|
|
72
|
+
|
|
73
|
+
class TaskResource < Sequel::Model(:task_resources)
|
|
74
|
+
Hiiro::DB.register(self)
|
|
75
|
+
|
|
76
|
+
def self.create_table!(db)
|
|
77
|
+
db.create_table?(:task_resources) do
|
|
78
|
+
primary_key :id
|
|
79
|
+
foreign_key :task_id, :tasks, null: false, on_delete: :cascade
|
|
80
|
+
String :kind, null: false
|
|
81
|
+
String :target, null: false
|
|
82
|
+
String :label
|
|
83
|
+
String :created_at
|
|
84
|
+
unique [:task_id, :kind, :target]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
37
88
|
end
|
data/lib/hiiro/tasks.rb
CHANGED
|
@@ -89,6 +89,7 @@ class Hiiro
|
|
|
89
89
|
return nil unless target
|
|
90
90
|
|
|
91
91
|
return target.path if target.is_a?(FallbackTarget)
|
|
92
|
+
return target.home unless target.tree_name
|
|
92
93
|
|
|
93
94
|
tree = environment.find_tree(target.tree_name)
|
|
94
95
|
return tree.path if tree
|
|
@@ -250,8 +251,8 @@ class Hiiro
|
|
|
250
251
|
return
|
|
251
252
|
end
|
|
252
253
|
|
|
253
|
-
config.
|
|
254
|
-
subtasks(task).each { |st| config.
|
|
254
|
+
config.detach_tree(task.name)
|
|
255
|
+
subtasks(task).each { |st| config.detach_tree(st.name) }
|
|
255
256
|
|
|
256
257
|
puts "Stopped task '#{task.name}' (worktree available for reuse)"
|
|
257
258
|
end
|
|
@@ -265,12 +266,14 @@ class Hiiro
|
|
|
265
266
|
# Derive a default task name from the tree name: "foo/main" -> "foo"
|
|
266
267
|
task_name ||= tree.name.end_with?('/main') ? tree.name.chomp('/main') : tree.name
|
|
267
268
|
|
|
268
|
-
|
|
269
|
-
|
|
269
|
+
existing = task_by_name(task_name)
|
|
270
|
+
if existing&.tree_name
|
|
271
|
+
puts "Task '#{task_name}' already has a worktree"
|
|
270
272
|
return
|
|
271
273
|
end
|
|
272
274
|
|
|
273
|
-
|
|
275
|
+
attributes = existing ? existing.to_h : { name: task_name }
|
|
276
|
+
task = Task.new(**attributes.merge(tree: tree.name, session: task_name))
|
|
274
277
|
config.save_task(task)
|
|
275
278
|
puts "Resumed task '#{task_name}' from worktree '#{tree.name}'"
|
|
276
279
|
|
|
@@ -706,27 +709,26 @@ class Hiiro
|
|
|
706
709
|
end
|
|
707
710
|
|
|
708
711
|
def save_task(task)
|
|
709
|
-
TaskRecord.
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
r.app = task.respond_to?(:app) ? task.app : nil
|
|
713
|
-
r.color_index = task.color_index
|
|
714
|
-
end.update(
|
|
715
|
-
tree: task.tree_name,
|
|
716
|
-
session: task.session_name,
|
|
717
|
-
app: task.respond_to?(:app) ? task.app : nil,
|
|
718
|
-
color_index: task.color_index
|
|
719
|
-
)
|
|
712
|
+
record = TaskRecord.find_by_name(task.name) || TaskRecord.new(name: task.name, created_at: Time.now.iso8601)
|
|
713
|
+
record.set(task.to_h.merge(tree: task.tree_name, session: task.session_name, color_index: task.color_index))
|
|
714
|
+
record.save
|
|
720
715
|
save_tasks_yaml_backup
|
|
721
716
|
rescue => e
|
|
722
717
|
warn "Failed to save task to DB: #{e}"
|
|
723
718
|
end
|
|
724
719
|
|
|
725
|
-
def
|
|
726
|
-
TaskRecord.
|
|
720
|
+
def detach_tree(name)
|
|
721
|
+
record = TaskRecord.find_by_name(name)
|
|
722
|
+
return unless record&.tree
|
|
723
|
+
|
|
724
|
+
path = record.tree.start_with?('/') ? record.tree : File.join(Hiiro::WORK_DIR, record.tree)
|
|
725
|
+
Hiiro::DB.connection.transaction do
|
|
726
|
+
TaskResource.find_or_create(task_id: record.id, kind: 'directory', target: path)
|
|
727
|
+
record.update(tree: nil, updated_at: Time.now.iso8601)
|
|
728
|
+
end
|
|
727
729
|
save_tasks_yaml_backup
|
|
728
730
|
rescue => e
|
|
729
|
-
warn "Failed to
|
|
731
|
+
warn "Failed to detach task worktree: #{e}"
|
|
730
732
|
end
|
|
731
733
|
|
|
732
734
|
private
|
|
@@ -735,7 +737,7 @@ class Hiiro
|
|
|
735
737
|
rows = TaskRecord.all_as_list
|
|
736
738
|
return fallback_load_tasks_from_yaml if rows.empty?
|
|
737
739
|
{ 'tasks' => rows.map { |r|
|
|
738
|
-
|
|
740
|
+
r.task_attributes.transform_keys(&:to_s)
|
|
739
741
|
}}
|
|
740
742
|
rescue => e
|
|
741
743
|
warn "Failed to load tasks from DB: #{e}. Falling back to YAML."
|
|
@@ -801,7 +803,7 @@ class Hiiro
|
|
|
801
803
|
|
|
802
804
|
def save_tasks_yaml_backup(data = nil)
|
|
803
805
|
data ||= { 'tasks' => TaskRecord.all_as_list.map { |r|
|
|
804
|
-
|
|
806
|
+
r.task_attributes.transform_keys(&:to_s)
|
|
805
807
|
}}
|
|
806
808
|
FileUtils.mkdir_p(File.dirname(tasks_file))
|
|
807
809
|
File.write(tasks_file, YAML.dump(data))
|
|
@@ -1213,33 +1215,33 @@ class Hiiro
|
|
|
1213
1215
|
|
|
1214
1216
|
h.add_subcmd(:prune) do |*raw_args|
|
|
1215
1217
|
opts = Hiiro::Options.parse(raw_args) {
|
|
1216
|
-
flag(:force, short: :f, desc: '
|
|
1218
|
+
flag(:force, short: :f, desc: 'Detach missing worktrees (default is dry-run)')
|
|
1217
1219
|
}
|
|
1218
1220
|
|
|
1219
1221
|
to_remove = tm.environment.all_tasks.select do |task|
|
|
1220
|
-
next
|
|
1222
|
+
next false unless task.tree_name
|
|
1221
1223
|
path = tm.resolve_path(task)
|
|
1222
1224
|
path.nil? || !Dir.exist?(path)
|
|
1223
1225
|
end
|
|
1224
1226
|
|
|
1225
1227
|
if to_remove.empty?
|
|
1226
|
-
puts "No
|
|
1228
|
+
puts "No missing task worktrees"
|
|
1227
1229
|
next
|
|
1228
1230
|
end
|
|
1229
1231
|
|
|
1230
1232
|
to_remove.each do |task|
|
|
1231
1233
|
path = task.tree_name ? tm.resolve_path(task) : '(no tree)'
|
|
1232
1234
|
if opts.force
|
|
1233
|
-
tm.config.
|
|
1234
|
-
puts "
|
|
1235
|
+
tm.config.detach_tree(task.name)
|
|
1236
|
+
puts "Detached missing worktree: #{task.name} (#{path}); task retained"
|
|
1235
1237
|
else
|
|
1236
|
-
puts "Would
|
|
1238
|
+
puts "Would detach missing worktree: #{task.name} (#{path})"
|
|
1237
1239
|
end
|
|
1238
1240
|
end
|
|
1239
1241
|
|
|
1240
1242
|
unless opts.force
|
|
1241
1243
|
puts
|
|
1242
|
-
puts "Re-run with -f to
|
|
1244
|
+
puts "Re-run with -f to detach these worktree references."
|
|
1243
1245
|
end
|
|
1244
1246
|
end
|
|
1245
1247
|
|
|
@@ -1486,13 +1488,19 @@ class Hiiro
|
|
|
1486
1488
|
end
|
|
1487
1489
|
|
|
1488
1490
|
class Task
|
|
1489
|
-
attr_reader :name, :tree_name, :session_name, :color_index
|
|
1491
|
+
attr_reader :name, :tree_name, :session_name, :color_index, :app, :created_at, *TaskRecord::METADATA_COLUMNS
|
|
1490
1492
|
|
|
1491
|
-
def initialize(name:, tree: nil, session: nil, color_index: nil, **
|
|
1493
|
+
def initialize(name:, tree: nil, session: nil, color_index: nil, **attributes)
|
|
1492
1494
|
@name = name
|
|
1493
1495
|
@tree_name = tree
|
|
1494
1496
|
@session_name = session || name
|
|
1495
1497
|
@color_index = color_index
|
|
1498
|
+
@attributes = attributes.slice(:app, :created_at, *TaskRecord::METADATA_COLUMNS)
|
|
1499
|
+
@attributes.each { |key, value| instance_variable_set("@#{key}", value) }
|
|
1500
|
+
end
|
|
1501
|
+
|
|
1502
|
+
def home
|
|
1503
|
+
TaskRecord.home_for(name)
|
|
1496
1504
|
end
|
|
1497
1505
|
|
|
1498
1506
|
def parent_name
|
|
@@ -1537,7 +1545,7 @@ class Hiiro
|
|
|
1537
1545
|
end
|
|
1538
1546
|
|
|
1539
1547
|
def to_h
|
|
1540
|
-
h =
|
|
1548
|
+
h = @attributes.merge(name: name)
|
|
1541
1549
|
h[:tree] = tree_name if tree_name
|
|
1542
1550
|
h[:session] = session_name if session_name != name
|
|
1543
1551
|
h[:color_index] = color_index unless color_index.nil?
|
data/lib/hiiro/version.rb
CHANGED
data/lib/hiiro.rb
CHANGED
|
@@ -355,11 +355,16 @@ class Hiiro
|
|
|
355
355
|
end
|
|
356
356
|
end
|
|
357
357
|
|
|
358
|
-
def add_cmd(*names, args: [], opts: [], &block)
|
|
358
|
+
def add_cmd(*names, args: [], opts: [], passthrough: false, &block)
|
|
359
359
|
cmd_opts = options.select(opts)
|
|
360
360
|
|
|
361
361
|
wrapper = lambda do |*raw_args|
|
|
362
|
+
next instance_eval(&block) if passthrough
|
|
362
363
|
@opts = cmd_opts.parse(raw_args)
|
|
364
|
+
if @opts.help?
|
|
365
|
+
puts @opts.help_text
|
|
366
|
+
next
|
|
367
|
+
end
|
|
363
368
|
instance_eval(&block)
|
|
364
369
|
end
|
|
365
370
|
|
|
@@ -368,7 +373,8 @@ class Hiiro
|
|
|
368
373
|
name, wrapper,
|
|
369
374
|
subcmd_args: args,
|
|
370
375
|
subcmd_opts: cmd_opts,
|
|
371
|
-
**global_values
|
|
376
|
+
**global_values,
|
|
377
|
+
source_location: block.source_location&.join(':')
|
|
372
378
|
)
|
|
373
379
|
end
|
|
374
380
|
end
|
|
@@ -605,6 +611,8 @@ class Hiiro
|
|
|
605
611
|
end
|
|
606
612
|
|
|
607
613
|
def all_bins
|
|
614
|
+
return [] unless hiiro.global_values.fetch(:external_commands, true)
|
|
615
|
+
|
|
608
616
|
pattern = format('{%s}/%s-*', paths.join(?,), bin_name)
|
|
609
617
|
|
|
610
618
|
Dir.glob(pattern).map { |path| Bin.new(bin_name, path) }
|
|
@@ -740,13 +748,14 @@ class Hiiro
|
|
|
740
748
|
end
|
|
741
749
|
|
|
742
750
|
def location
|
|
743
|
-
handler.source_location&.join(':')
|
|
751
|
+
values[:source_location] || handler.source_location&.join(':')
|
|
744
752
|
end
|
|
745
753
|
|
|
746
754
|
def params_string
|
|
747
755
|
if subcmd_args.any?
|
|
748
756
|
return subcmd_args.map { |a| "<#{a}>" }.join(' ')
|
|
749
757
|
end
|
|
758
|
+
return nil if subcmd_opts
|
|
750
759
|
|
|
751
760
|
return nil unless handler.respond_to?(:parameters)
|
|
752
761
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hiiro
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.363
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joshua Toyota
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: pry
|