hiiro 0.1.366 → 0.1.368

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.
data/docs/why-t.md CHANGED
@@ -44,13 +44,13 @@ tt fix-checkout add Inspect --help output
44
44
  t fix-checkout todo
45
45
  ```
46
46
 
47
- Task display and todo listing show every todo with its ID, status, and text. To remove one, use its displayed ID, such as `t fix-checkout todo rm 42`. The ID must belong to that scope. Adding or removing todos does not change the task's next action or status, and `t` does not choose the next todo for you.
47
+ Task display and todo listing show every todo with its ID, status, and text. To remove one, use its displayed ID, such as `t todo rm fix-checkout 42`, or print just its text with `t todo show 42`. The ID must belong to that scope. Adding or removing todos does not change the task's next action or status, and `t` does not choose the next todo for you.
48
48
 
49
- Task references prefer an exact name, then a unique case-sensitive prefix. Ambiguous prefixes fail. If `todo add` finds no task, it creates one with the usual name and notes-home rules before adding the item. Other commands never create unknown tasks except explicit `t NAME new`, which creates that exact name rather than resolving a prefix.
49
+ Task references prefer an exact name, then a unique case-sensitive prefix. Ambiguous prefixes fail. A first word that names no task is treated as payload for the current task, so `t todo add fix build` never creates a task named `fix`. Only `t new NAME` creates tasks, and it creates that exact name rather than resolving a prefix.
50
50
 
51
- `tt TASK ...` delegates to `t TASK todo ...`. Bare `tt` lists current-task todos, and `tt help` shows todo help. Use `t - todo` or `tt -` to list orphan todos, with `add` and `rm` in that scope. `-` is not a task and works only for todos.
51
+ `tt ...` is `t todo ...`. Bare `tt` lists current-task todos, and `tt help` shows todo help. Use `t todo -` or `tt add - TEXT` for orphan todos. `-` is not a task and works only for todos.
52
52
 
53
- Every word after `add` is literal text, including flags and `--`, except that leading `add -h` or `add --help` shows help. Empty text fails before any task is created.
53
+ Every word after the task in `add` is literal text, including flags and `--`, except a leading `-h` or `--help`, which shows help. Empty text fails.
54
54
 
55
55
  Todos share the existing database table with `h todo`. `t` does not rewrite `todo.yml` or change `h task` behavior. Keep the longer investigation in a document.
56
56
 
@@ -73,7 +73,7 @@ That saves the explanation and changes the task's status to `waiting`. The next
73
73
  }
74
74
  ```
75
75
 
76
- Bare `t` lists every task and status, including done and archived work. Active and waiting rows show their next-action and blocker text, so you can review what needs attention without opening every terminal.
76
+ Bare `t` (or `t ls` / `t list`) lists every task and status, including done and archived work. Each name carries its open todo count, such as `prez (3)`, and active and waiting rows show their next-action and blocker text, so you can review what needs attention without opening every terminal.
77
77
 
78
78
  When the answer arrives:
79
79
 
@@ -205,26 +205,24 @@ The workspace identifies running terminals, not a separate persisted-session sto
205
205
 
206
206
  ## Context can save typing without making scripts guess
207
207
 
208
- The first argument is always the task reference. `t TASK` shows the task, and `t TASK COMMAND...` runs a command in that task's scope. Only exact root `t help` is special: it prints usage and native scoped help without task lookup. Names such as `new`, `show`, `edit`, and `he` remain task names at the root.
209
-
210
- Use `.` when the calling Herdr workspace, directory, or saved fallback identifies the task:
208
+ The command comes first, and the task is optional: `t show fix-checkout` names it, `t show` uses the current task. A first word that names a task, exactly or by unique prefix, is the task; anything else is payload for the current task, the same rule the old `h task` commands used. `-t TASK` forces a task, `-f` picks one with a fuzzy finder, and `.` stands for the current task when a payload could look like a name:
211
209
 
212
210
  ```bash
213
- t . current
214
- t .
215
- t . next
216
- t . next 'Check the new test against the original failing payload'
211
+ t current
212
+ t show
213
+ t next
214
+ t next . 'Check the new test against the original failing payload'
217
215
  ```
218
216
 
219
- A named reference bypasses context lookup. For `.`, the calling Herdr workspace wins over a conflicting current directory. The directory is next, followed by the saved task. Ambiguous matches and invalid, stale, or conflicting Herdr IDs are errors.
217
+ The calling Herdr workspace wins over a conflicting current directory. The directory is next, followed by the saved task. Ambiguous matches and invalid, stale, or conflicting Herdr IDs are errors.
220
218
 
221
219
  You can set that fallback without moving terminal focus:
222
220
 
223
221
  ```bash
224
- t fix-checkout current
222
+ t use fix-checkout
225
223
  ```
226
224
 
227
- `t . current` only prints the selected name. Reads and workspace opens through `.` leave the saved fallback unchanged. A deleted saved task causes an error if selection reaches it. Bare `t` always lists all tasks rather than selecting one.
225
+ `t current` only prints the selected name. Reads leave the saved fallback unchanged; `t switch NAME` saves it after a successful switch. A deleted saved task causes an error if selection reaches it. Bare `t` always lists all tasks rather than selecting one.
228
226
 
229
227
  ## An agent can leave you a useful place to resume
230
228
 
@@ -284,16 +282,16 @@ You do not need to use every command for this to pay off.
284
282
 
285
283
  | Moment | Useful command | What it gives you |
286
284
  |---|---|---|
287
- | You capture a new piece of work | `t TASK new` | An exact named record and notes home |
288
- | You capture another step | `tt TASK add 'Compare the retry settings'` | A todo without replacing the next action; creates a task if no name or prefix matches |
285
+ | You capture a new piece of work | `t new TASK` | An exact named record and notes home |
286
+ | You capture another step | `tt add 'Compare the retry settings'` | A todo on the current task without replacing the next action |
289
287
  | You decide what to work on | `t` | Every task and status, with next-action and blocker text |
290
- | You resume a task | `t TASK` | The context you recorded for it |
291
- | You want a fallback outside task directories and Herdr | `t TASK current` | Saved selection without moving terminal focus |
292
- | You need its terminals | `t TASK switch` | The associated workspace and a saved fallback after success |
293
- | You want a fresh AI session | `t TASK omp` | A new focused tab running the native tool |
294
- | You are about to switch away | `t TASK next 'The next concrete action'` | An instruction for your next session |
295
- | Someone else is blocking progress | `t TASK waiting 'Who or what I need'` | A waiting state and explanation |
296
- | The work is complete | `t TASK done` | A done status without deleting the supporting context |
288
+ | You resume a task | `t show TASK` | The context you recorded for it |
289
+ | You want a fallback outside task directories and Herdr | `t use TASK` | Saved selection without moving terminal focus |
290
+ | You need its terminals | `t switch TASK` | The associated workspace and a saved fallback after success |
291
+ | You want a fresh AI session | `t omp TASK` | A new focused tab running the native tool |
292
+ | You are about to switch away | `t next 'The next concrete action'` | An instruction for your next session |
293
+ | Someone else is blocking progress | `t waiting 'Who or what I need'` | A waiting state and explanation |
294
+ | The work is complete | `t done TASK` | A done status without deleting the supporting context |
297
295
 
298
296
  Start with task names and useful next actions. Add documents, links, and terminal organization when they make a particular task easier to resume.
299
297
 
@@ -303,4 +301,4 @@ Start with task names and useful next actions. Add documents, links, and termina
303
301
 
304
302
  ## Read next
305
303
 
306
- [The complete t command reference](t.md) covers every subcommand, option, alias, state transition, storage location, and Herdr/worktree side effect. The canonical implementation is the repository's `bin/t`, with `~/bin/t` as its symlink. Gem installation does not install the launcher.
304
+ [The complete t command reference](t.md) covers every subcommand, option, alias, state transition, storage location, and Herdr/worktree side effect. The implementation is `Hiiro::TaskCli` in `lib/hiiro/task_cli.rb`, launched by the `t` and `tt` gem executables in `exe/`.
data/exe/h CHANGED
@@ -97,6 +97,7 @@ Hiiro.run(*ARGV, cwd: Dir.pwd, tasks: true) do
97
97
  'github' => %w[pr],
98
98
  'claude' => %w[claude],
99
99
  'hiiro' => %w[bin plugin],
100
+ 'task' => %w[task],
100
101
  'other' => %w[app bg config link misc project todo],
101
102
  }
102
103
 
data/exe/t ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'hiiro'
4
+
5
+ Hiiro.run(*ARGV, external_commands: false, builtin_commands: false) do
6
+ Hiiro::TaskCli.setup(self)
7
+ end
data/exe/tt ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'hiiro'
4
+
5
+ Hiiro.run(*ARGV, external_commands: false, builtin_commands: false) do
6
+ Hiiro::TaskCli.setup_todo(self)
7
+ end
@@ -0,0 +1,47 @@
1
+ # Hiiro task actions for Herdr. Install with `h herdr install`, which copies this
2
+ # directory to ~/.config/hiiro/herdr-plugin and links it. Every action opens the
3
+ # popup pane with HIIRO_HERDR_ACTION set; `h herdr popup` does the work there.
4
+ id = "hiiro"
5
+ name = "Hiiro tasks"
6
+ version = "0.1.0"
7
+ min_herdr_version = "0.7.4"
8
+ description = "Fuzzy task switcher, todo capture, and task shell/editor popups backed by the t CLI."
9
+ platforms = ["linux", "macos"]
10
+
11
+ [[actions]]
12
+ id = "switch"
13
+ title = "Switch to task…"
14
+ contexts = ["workspace"]
15
+ command = ["h", "herdr", "action", "switch"]
16
+
17
+ [[actions]]
18
+ id = "todo-add"
19
+ title = "Add todo to task…"
20
+ contexts = ["workspace"]
21
+ command = ["h", "herdr", "action", "todo-add"]
22
+
23
+ [[actions]]
24
+ id = "todos"
25
+ title = "Copy a todo from the current task…"
26
+ contexts = ["workspace"]
27
+ command = ["h", "herdr", "action", "todos"]
28
+
29
+ [[actions]]
30
+ id = "shell"
31
+ title = "Shell in the current task directory"
32
+ contexts = ["workspace"]
33
+ command = ["h", "herdr", "action", "shell"]
34
+
35
+ [[actions]]
36
+ id = "nvim"
37
+ title = "nvim in the current task notes"
38
+ contexts = ["workspace"]
39
+ command = ["h", "herdr", "action", "nvim"]
40
+
41
+ [[panes]]
42
+ id = "popup"
43
+ title = "hiiro"
44
+ placement = "popup"
45
+ width = "70%"
46
+ height = "70%"
47
+ command = ["h", "herdr", "popup"]
@@ -0,0 +1,92 @@
1
+ class Hiiro
2
+ # Resolves the current task record shared by `t` (via TaskScope) and
3
+ # Environment#task (via `h` commands):
4
+ #
5
+ # 1. Herdr workspace, when HERDR_* env vars identify one
6
+ # 2. Working directory inside a task home, code directory, or registered directory
7
+ # 3. Saved `t TASK current` pin (only when pin: true)
8
+ #
9
+ # resolve! raises Hiiro::Error with a user-facing message; resolve returns nil instead.
10
+ class CurrentTask
11
+ def initialize(herdr: nil, cwd: Dir.pwd, pin: true, tasks: nil)
12
+ @herdr = herdr
13
+ @cwd = cwd
14
+ @pin = pin
15
+ @tasks = tasks
16
+ end
17
+
18
+ def resolve
19
+ resolve!
20
+ rescue Hiiro::Error
21
+ nil
22
+ end
23
+
24
+ def resolve!
25
+ herdr_task || directory_task || pinned_task
26
+ end
27
+
28
+ private
29
+
30
+ def tasks
31
+ @tasks ||= TaskRecord.all_as_list
32
+ end
33
+
34
+ def herdr_task
35
+ return nil unless @herdr && (ENV['HERDR_WORKSPACE_ID'] || ENV['HERDR_PANE_ID'] || ENV['HERDR_ENV'] == '1')
36
+
37
+ herdr = @herdr.call
38
+ unless herdr.server_running?
39
+ raise Error, 'Herdr is not running; start Herdr for workspace/tab/pane commands, or supply a task name for task data'
40
+ end
41
+ pane = herdr.get_pane(ENV['HERDR_PANE_ID']) if ENV['HERDR_PANE_ID']
42
+ raise Error, 'Cannot resolve the current Herdr pane; supply a task name' if ENV['HERDR_PANE_ID'] && !pane
43
+
44
+ workspace_id = ENV['HERDR_WORKSPACE_ID'] || pane&.workspace_id
45
+ raise Error, 'Conflicting Herdr pane/workspace context; supply a task name' if pane && workspace_id != pane.workspace_id
46
+
47
+ workspace = workspace_id ? herdr.get_workspace(workspace_id) : herdr.current_workspace
48
+ raise Error, 'Cannot resolve the current Herdr workspace; supply a task name' unless workspace
49
+
50
+ matches = tasks.select { |task| workspace_label(task) == workspace.name }
51
+ raise Error, "Ambiguous workspace task: #{matches.map(&:name).join(', ')}" if matches.length > 1
52
+
53
+ matches.first
54
+ end
55
+
56
+ def directory_task
57
+ cwd = File.realpath(@cwd)
58
+ registered = TaskResource.where(kind: 'directory').select_map([:task_id, :target]).group_by(&:first)
59
+ matches = tasks.select do |task|
60
+ paths = [task.home, code_directory(task), *registered.fetch(task.id, []).map(&:last)].compact
61
+ paths.any? { |path| inside?(cwd, path) }
62
+ end
63
+ raise Error, "Ambiguous directory task: #{matches.map(&:name).join(', ')}" if matches.length > 1
64
+
65
+ matches.first
66
+ end
67
+
68
+ def pinned_task
69
+ raise Error, 'No current task; supply a task name or run t use NAME' unless @pin
70
+
71
+ saved = PinRecord.find_key('t', 'current_task')
72
+ raise Error, 'No current task; supply a task name or run t use NAME' unless saved
73
+
74
+ TaskRecord[saved.value] || raise(Error, 'Saved task no longer exists; run t use NAME')
75
+ end
76
+
77
+ def workspace_label(task)
78
+ (task.session || task.name).tr('.', '_')
79
+ end
80
+
81
+ def code_directory(task)
82
+ task.primary_directory || (task.tree && (task.tree.start_with?('/') ? task.tree : File.join(Hiiro::WORK_DIR, task.tree)))
83
+ end
84
+
85
+ def inside?(path, directory)
86
+ return false unless Dir.exist?(directory)
87
+
88
+ root = File.realpath(directory)
89
+ path == root || path.start_with?(root == File::SEPARATOR ? root : root + File::SEPARATOR)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,5 @@
1
+ class Hiiro
2
+ # Raise for expected user-facing failures (bad arguments, missing records).
3
+ # Hiiro#run prints only the message for these, with no backtrace.
4
+ class Error < StandardError; end
5
+ end
@@ -26,7 +26,7 @@ class Hiiro
26
26
 
27
27
  def self.fetch(repo_path: nil)
28
28
  output = if repo_path
29
- `git -C #{repo_path.shellescape} worktree list --porcelain 2>/dev/null`
29
+ `git -C #{Hiiro::Git.repo_dir(repo_path).shellescape} worktree list --porcelain 2>/dev/null`
30
30
  else
31
31
  `git worktree list --porcelain 2>/dev/null`
32
32
  end
data/lib/hiiro/git.rb CHANGED
@@ -7,6 +7,12 @@ require_relative 'git/pr'
7
7
 
8
8
  class Hiiro
9
9
  class Git
10
+ # Directory to pass to `git -C` for a repo path. REPO_PATH points at
11
+ # ~/work/.git, which is a gitfile in the bare layout, so use its parent.
12
+ def self.repo_dir(repo_path)
13
+ File.file?(repo_path.to_s) ? File.dirname(repo_path) : repo_path
14
+ end
15
+
10
16
  def self.root
11
17
  `git rev-parse --show-toplevel 2>/dev/null`.strip
12
18
  end
@@ -128,7 +134,7 @@ class Hiiro
128
134
 
129
135
  def add_worktree_detached(path, repo_path: nil)
130
136
  if repo_path
131
- run_system('-C', repo_path, 'worktree', 'add', '--detach', path)
137
+ run_system('-C', self.class.repo_dir(repo_path), 'worktree', 'add', '--detach', path)
132
138
  else
133
139
  run_system('worktree', 'add', '--detach', path)
134
140
  end
@@ -136,7 +142,7 @@ class Hiiro
136
142
 
137
143
  def move_worktree(from, to, repo_path: nil)
138
144
  if repo_path
139
- run_system('-C', repo_path, 'worktree', 'move', from, to)
145
+ run_system('-C', self.class.repo_dir(repo_path), 'worktree', 'move', from, to)
140
146
  else
141
147
  run_system('worktree', 'move', from, to)
142
148
  end