From 7840ab15ed6364e79e439c0a4844536294cdfad6 Mon Sep 17 00:00:00 2001 From: karitham Date: Sat, 26 Sep 2026 13:26:29 +0200 Subject: [PATCH] dev/tools: add tuicr --- modules/dev/tools/default.nix | 5 +- modules/dev/tools/tuicr.nix | 88 +++++++++++ modules/dev/tools/tuicr/SKILL.md | 255 +++++++++++++++++++++++++++++++ modules/opencode/default.nix | 1 + 4 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 modules/dev/tools/tuicr.nix create mode 100644 modules/dev/tools/tuicr/SKILL.md diff --git a/modules/dev/tools/default.nix b/modules/dev/tools/default.nix index 45428e4..8b01c70 100644 --- a/modules/dev/tools/default.nix +++ b/modules/dev/tools/default.nix @@ -48,5 +48,8 @@ in }; }; - imports = [ ./direnv.nix ]; + imports = [ + ./direnv.nix + ./tuicr.nix + ]; } diff --git a/modules/dev/tools/tuicr.nix b/modules/dev/tools/tuicr.nix new file mode 100644 index 0000000..5056f8e --- /dev/null +++ b/modules/dev/tools/tuicr.nix @@ -0,0 +1,88 @@ +{ + config, + lib, + pkgs, + ... +}: +let + tuicr-wrapper = pkgs.writeShellScriptBin "tuicr-wrapper-zellij" '' + set -euo pipefail + + if [[ -z "''${ZELLIJ:-}" ]]; then + echo "Error: not running inside zellij" >&2 + exit 1 + fi + + if [[ $# -lt 1 ]]; then + echo "Usage: tuicr-wrapper-zellij [-- tuicr-args...]" >&2 + exit 1 + fi + + target_dir="$1" + shift + + if [[ "''${1:-}" == "--" ]]; then + shift + fi + + if [[ $# -eq 0 ]]; then + echo "Error: pass a scope after --, e.g. -- -w or -- -r main..HEAD" >&2 + exit 1 + fi + + if ! git -C "$target_dir" rev-parse --git-dir &>/dev/null \ + && ! command -v jj &>/dev/null; then + echo "Error: not a git or jj repository: $target_dir" >&2 + exit 1 + fi + + direction="''${TUICR_PANE_DIRECTION:-right}" + + fifo=$(mktemp -u "/tmp/tuicr-fifo.XXXXXX") + mkfifo "$fifo" + + output_file="" + tuicr_cmd="tuicr" + + if tuicr --help 2>&1 | grep -q -- '--stdout'; then + output_file=$(mktemp /tmp/tuicr-output.XXXXXX) + tuicr_cmd="$tuicr_cmd --stdout > '$output_file'" + fi + + zellij action new-pane \ + --direction "$direction" \ + --name "tuicr" \ + --close-on-exit \ + -- bash -c "cd '$target_dir' && $tuicr_cmd \$@; echo done > '$fifo'" \ + -- "$@" + + read -r _ < "$fifo" + rm -f "$fifo" + + zellij action focus-previous-pane 2>/dev/null || true + + if [[ -n "$output_file" ]] && [[ -s "$output_file" ]]; then + echo "" + echo "=== TUICR INSTRUCTIONS ===" + cat "$output_file" + echo "=== END TUICR INSTRUCTIONS ===" + rm -f "$output_file" + fi + ''; +in +{ + config = lib.mkIf config.dev.enable { + home.packages = [ + pkgs.tuicr + tuicr-wrapper + ]; + + xdg.configFile."tuicr/config.toml".source = (pkgs.formats.toml { }).generate "tuicr-config.toml" { + theme = "catppuccin-macchiato"; + comment_vim = true; + scroll_offset = 5; + no_update_check = true; + editor = "hx"; + }; + }; +} diff --git a/modules/dev/tools/tuicr/SKILL.md b/modules/dev/tools/tuicr/SKILL.md new file mode 100644 index 0000000..17219b1 --- /dev/null +++ b/modules/dev/tools/tuicr/SKILL.md @@ -0,0 +1,255 @@ +--- +name: tuicr +description: Use tuicr's review CLI to read and add comments in active TUI review sessions, and launch tuicr in a Zellij split pane when the user needs an interactive review. +--- + +# tuicr Review Workflow + +Use `tuicr review` as the default agent interface. The TUI is where the human +reviews code; the CLI is how the agent discovers active sessions, reads user +comments, and, only when appropriate, adds agent-authored comments. + +## Core Rule + +First decide which workflow the user is asking for: + +1. **User-led review of agent-generated changes** + - The user wants to inspect the patch and write comments in tuicr. + - Your job is to open or find the session, then retrieve the user's comments + with `tuicr review comments` when they say comments are ready. If you are + explicitly waiting while the user reviews, poll the same command + periodically and look for new comment IDs. + - Do not add your own review comments, do not preemptively review your own + patch, and do not impersonate the user's comments. + +2. **Agent review of an AI-generated patch** + - The user wants you to understand, critique, or summarize a patch. + - You may inspect the patch and propose findings. + - If you can confidently identify this workflow and the target session, add + findings directly with `tuicr review add` and an explicit `--username` + identifying the agent. Ask first when the workflow or session is ambiguous. + +If the user's intent is ambiguous, ask which workflow they want. + +## Attach To A Session + +1. Determine the repository directory from the user's request, current working + directory, or recent file operations. Ask if it is ambiguous. + +2. List persisted sessions: + + ```bash + tuicr review list --repo /path/to/repo # checkout + its repo's PR sessions + tuicr review list --repo owner/repo # all sessions for a forge repo + tuicr review list --all # every session across all repos + ``` + + `--repo` is a selector: a checkout path also surfaces PR sessions for that + checkout's `origin` repo, and a forge coordinate like `owner/repo` matches + local and PR sessions by owner/repo. Each row carries a `kind` (`local` or + `pr`) and a usable `slug`. Use `--all` when you don't know the repo. + + `[]` with exit 0 also means "not a repo root" — a subdirectory returns it + too. Pass the root, then `--all`, before concluding nothing is open. + +3. Choose the session: + - If the CLI clearly reports exactly one relevant active session with + `"active": true`, attach to it. + - If multiple sessions are active, or the correct session is not clear, ask + the user which slug to use. One repo can hold a worktree and a + commit-range session at once, and adding to the wrong one exits 0. + - If the user provided a slug or session JSON path, use it directly. + - For a PR review, pass the PR slug from the listing (e.g. + `gh:owner/repo/pr/N`) to `--session`; it is self-contained and needs no + `--repo`. + - If there is no active session, start or wait for one as described below. + - Until active-session discovery is formalized as a stable protocol, treat + `"active": true` as a convenience signal. If slug resolution fails, ask + the user for the slug or repo path used by the session. + +The CLI works even if the agent is not running inside Zellij, +so do not require a multiplexer just to connect to an existing active session. + +## Start A Session + +When the user needs an interactive tuicr pane and no active session exists: + +| Environment | Action | +| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `$ZELLIJ` is set | Run `tuicr-wrapper-zellij /path/to/repo -- ` | +| None is set | Tell the user you are waiting for them to start `tuicr` in the repo, then attach with `tuicr review list` after they say it is ready | + +`` is `-w` for uncommitted working-tree changes or `-r ` for a +commit range — always pass one explicitly so the user is never left to pick +staged/unstaged/commit-range manually in the TUI. + +tuicr supports both git and Jujutsu (jj) repositories, and jj workspaces may +have no `.git` directory at all. Do not pre-check the directory with +`git rev-parse` or refuse to launch because git does not recognize it; always +run the wrapper and let it validate the repository. + +If your tool supports command timeouts, use a long timeout, such as 10 minutes, +because the Zellij wrapper waits for the TUI to exit. + +## Reconstruct The Diff + +To review a patch yourself, rebuild the diff the user sees. The slug's source +segment says which: + +| Slug segment | Diff | +| ----------------------------------------------- | -------------------------------------- | +| `worktree/`, `staged-and-unstaged/` | `git diff HEAD` | +| `staged/` | `git diff --cached` | +| `unstaged/` | `git diff` | +| `commits/..` | `git diff ~1..` | +| `pr/` | `gh pr diff ` | +| `pristine` | none; every tracked file shown in full | + +Range endpoints are inclusive and printed oldest-first, so `` without +`~1` drops the first commit. Check the file count against the listing row's +`file_count`; a mismatch means every line number you derive will be wrong. + +## Read User Comments + +This is the main review loop for user-led review. + +There is no push stream from tuicr to the agent. Read comments by running the +CLI on demand. After the user says comments are ready, or after the TUI exits, +run: + +```bash +tuicr review comments --repo /path/to/repo --session +``` + +The command emits JSON. Each comment includes fields like: + +- `id` +- `location` +- `path` +- `start_line` +- `end_line` +- `side` +- `comment_type` +- `author` +- `lifecycle_state` +- `content` + +Treat these comments as the user's review feedback: + +- `issue`: blocking problem to fix first +- `suggestion`: consider implementing or explain why not +- `note`: answer or acknowledge +- `praise`: no action required + +If you are waiting during an active review, poll this command about every 30 +seconds and compare comment IDs with the previous result. Read immediately when +the user says comments are ready. Stop polling once the user says the review is +done or your tooling would block other work. + +An empty result does not by itself mean the review didn't happen. On exit, +tuicr always prints a line like `tuicr-summary: reviewed 3/3 files, 0 comments +added` to stderr (visible in the pane's scrollback), and `tuicr review list` +reports the same `reviewed_count`/`file_count` for the session. If +`reviewed_count` equals `file_count`, zero comments is a legitimate "nothing to +flag" outcome — treat the review as complete, don't ask the user to confirm. +Only ask whether the user saved comments in the intended session, or whether +another active session should be selected, when `reviewed_count` is less than +`file_count` (the user quit before reviewing everything) or you can't find a +`tuicr-summary:` line at all. If the review may have continued while you were +working, rerun `tuicr review comments` before claiming completion. + +## Add Agent Comments + +Only add comments when the workflow allows it and, for agent-authored review, +after the user approves writing them into tuicr. + +Defaults: + +- Prefer line comments when a specific file and line are known. +- Use file comments for file-scoped feedback. +- Use review-level comments only for whole-review summaries. +- Use `--type issue` for problems by default. +- Use `suggestion`, `note`, or `praise` when that better matches the intent. +- Pass `--username` so agent comments are visually distinguishable. + +Examples: + +```bash +tuicr review add --repo /path/to/repo --session \ + --target-file src/main.rs \ + --line 42 \ + --side new \ + --type issue \ + --username "LongCat" \ + "Handle the empty case here." +``` + +```bash +tuicr review add --repo /path/to/repo --session \ + --target-file src/main.rs \ + --type suggestion \ + --username "LongCat" \ + "Consider splitting this file-level concern into a helper." +``` + +Omit `--target-file` for a review-level comment. Add `--end-line` for a range +comment. Use `--side old` for removed lines and `--side new` for added or +unchanged lines in the new file. + +For structured input, use `--input` with literal JSON, `@path/to/file.json`, or +`-` for stdin. Supported target types are `review`, `file`, `line`, and +`line_range`. One object per call — an array is a parse error. The file key is +`file`, not `path`. `target.type` is inferred from the fields present: + +```bash +tuicr review add --session --username "LongCat" --input \ + '{"file":"src/main.rs","line":42,"side":"new","comment_type":"issue","content":"Handle the empty case."}' +``` + +Then verify. A line outside the diff stores, prints back, and exits 0, but +never renders — invisible to the user, successful-looking to you. Re-read +`tuicr review comments` and check each `start_line` exists on the side you gave +(`new` for added or unchanged, `old` for removed). Check `author` to distinguish +your comments from the user's, and keep the returned `id`s to identify the exact +comments in later reads. + +## Legacy Export Output + +Older wrapper-driven flows may emit: + +```text +=== TUICR INSTRUCTIONS === +... +=== END TUICR INSTRUCTIONS === +``` + +If present, process those instructions. Otherwise prefer +`tuicr review comments`; it is the primary source of review feedback. If the +wrapper mentions clipboard export, ask the user to paste it only when the CLI +comments are unavailable. + +## Zellij Tips + +- Switch panes: `Alt` + arrow keys +- Close tuicr: press `q` +- Resize panes: `Ctrl-n`, then arrow keys +- Toggle fullscreen: `Alt-f` +- Cycle stacked panes: `Alt` + `[` / `]` + +## Error Handling + +| Situation | Action | +| -------------------------------------------------------- | ----------------------------------------------------------------- | +| Multiple plausible active sessions | Ask which session slug to use | +| No active session, Zellij available | Start a new tuicr pane with the zellij wrapper | +| No active session, no Zellij | Tell the user you are waiting for them to start `tuicr` | +| `tuicr` not installed | Tell the user to install tuicr | +| Not a repository | Ask for the correct repo directory | +| Comments are empty, but `reviewed_count` == `file_count` | Treat as a completed review with nothing to flag — don't ask | +| Comments are empty and `reviewed_count` < `file_count` | Confirm the selected session or ask the user to save/add comments | + +## When Not To Use + +- The user only wants raw `git diff` output. +- The user explicitly asks for a non-tuicr review workflow. +- The task is remote PR review and no tuicr PR session is involved. diff --git a/modules/opencode/default.nix b/modules/opencode/default.nix index e3aa5f1..12f38ad 100644 --- a/modules/opencode/default.nix +++ b/modules/opencode/default.nix @@ -119,6 +119,7 @@ in name = "opencode-skills"; paths = [ ./skills + ../dev/tools/tuicr # self'.packages.strands-agents-sops-skills ]; } -- 2.51.2