diff --git a/.pi/memory-vault/packs/paperclip-harness/20-context/agents.md b/.pi/memory-vault/packs/paperclip-harness/20-context/agents.md new file mode 100644 index 0000000..a69175c --- /dev/null +++ b/.pi/memory-vault/packs/paperclip-harness/20-context/agents.md @@ -0,0 +1,80 @@ +--- +type: context +id: context.paperclip-harness.agents +title: agents +status: active +source_of_truth: false +freshness: current +last_reviewed: 2026-11-11 +tags: + - pack/paperclip-harness + - agent-memory/context + - autolearn + - rich-discovery + - repo/agents +--- + +# agents + +## Rich discovery update (2026-11-11 00:28:33) + +You are right on both counts, and I will be precise about what was invented and whose rule it was. + +## Where the shell command came from — and why I was wrong to force it + +It is **not** a Paperclip rule. It is your own project rule, written in `docs/OPERATING-RULES.md` before this attempt: + +> **How a task is judged finished.** The test is a **command**, kept with the project, and agreed **before** the work starts. + +That came out of the old project, along with `.harness/verify.sh`. Paperclip has no such field. An agent sets a task's status itself; the reviewer or a review stage checks it. I took a project convention and turned it into a mandatory form field and a shell prompt in a test plan, which made the agent look like a dumb executor. That is my error, and the rule is now marked in `OPERATING-RULES.md` as ours, not Paperclip's. + +**What changed in the program:** + +- The field is now called **"A command that proves it (optional)"**, and its hint says: *"leave empty and the agent decides how to check its own work."* +- The guidance panel no longer asks you to write shell prompts; it says most tasks need nothing there and to fill it only for a gate no agent can talk its way past. +- `bin/merge` already worked without one — it says *"no verify command: this would pass on the agent's word"* and continues to the review. + +## What Paperclip actually does, and what you actually write + +You write a task in your own words. Pi reads it, works out how to do it, runs the project's own tests, comments with evidence, and sets the status. Paperclip adds the parts you do not want to run by hand: blockers as links between tasks, review stages (`in_review` with a participant), approvals, run history, cost, and a worktree per task. + +So the autonomy is in the *work*, not in your instructions. My test plan asked for a file name and an exact line, which is why it read like dictating a patch. + +## The error you saw was a real bug, not your mistake + +`AUD-167` **was created and completed** — `ADd a file named STATUS.md`, status `done`, assigned to Builder. The "[The program exited with status 0.]" was the pane dying: the New task pane ran the form alone, so when the form quit after creating, nothing was left to run. Two fixes: + +- the form now **stays open** and shows `created AUD-1xx, handed to an agent which will start on its own`, with the fields cleared for the next one; +- every pane now **falls back to a shell**, so a program that exits returns to a prompt instead of leaving a dead pane. + +I proved both by creating `AUD-168` — *"Tidy the README introduction"*, assigned, no verify command. It shows `in_progress` with Builder working, and the form stayed usable. + +## On "have you fully read and understood Paperclip" + +No, and I should have said so earlier. I have read the agent skill, the API specification, the agents/heartbeats/routines documentation, and I have probed the API extensively. What I got wrong was mixing your old project's conventions with Paperclip's model and presenting the mixture as if it were Paperclip's. Where I am unsure — review stages, execution policy, approvals — I will read before advising, and say when I have not. + +## The test plan, corrected + +`docs/TEST-PLAN.md` no longer asks for a shell command anywhere. Step 1 is now: title, plain-language description, no blocker, no command, `yes` to assign — and expect the agent to pick it up within seconds and decide for itself how to check the work. Step 4 keeps `bin/merge` but explains that "no verify command" is a normal outcome, not a failure. + +Committed as `678c497`. `AUD-168` is running now if you want to watch it in `Agents>agents` — that is the honest demonstration of autonomy: you said what you wanted, it is working out how. + +## Source paths observed + +- `../bin/harness` +- `agents/heartbeats/routines` + +## Evidence + +- Confidence: 84% +- Reason: Update component/repository context with detailed discovered structure and configuration + +## Related + +- [[packs/paperclip-harness/00-system/pi-agent/memory-manifest|Memory Manifest]] + +## Related learned memories + +- session: [[packs/paperclip-harness/80-sessions/rich-discovery-agents-2026-11-11-00-28-33|Rich discovery: agents 2026-11-11 00:28:33]] +- context: [[packs/paperclip-harness/20-context/agents|agents]] +- observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|agents repository structure and configuration patterns]] diff --git a/.pi/memory-vault/packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns.md b/.pi/memory-vault/packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns.md index b60f31d..b99b49a 100644 --- a/.pi/memory-vault/packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns.md +++ b/.pi/memory-vault/packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns.md @@ -2159,3 +2159,67 @@ Committed as `c9e027d`. - session: [[packs/paperclip-harness/80-sessions/rich-discovery-root-2026-11-11-00-03-58|Rich discovery: root. 2026-11-11 00:03:58]] - context: [[packs/paperclip-harness/20-context/home|root.]] - observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|root. repository structure and configuration patterns]] + + +--- + +## Update (2026-11-11 00:28:33) + +## Structural/configuration observations (2026-11-11 00:28:33) + +You are right on both counts, and I will be precise about what was invented and whose rule it was. + +## Where the shell command came from — and why I was wrong to force it + +It is **not** a Paperclip rule. It is your own project rule, written in `docs/OPERATING-RULES.md` before this attempt: + +> **How a task is judged finished.** The test is a **command**, kept with the project, and agreed **before** the work starts. + +That came out of the old project, along with `.harness/verify.sh`. Paperclip has no such field. An agent sets a task's status itself; the reviewer or a review stage checks it. I took a project convention and turned it into a mandatory form field and a shell prompt in a test plan, which made the agent look like a dumb executor. That is my error, and the rule is now marked in `OPERATING-RULES.md` as ours, not Paperclip's. + +**What changed in the program:** + +- The field is now called **"A command that proves it (optional)"**, and its hint says: *"leave empty and the agent decides how to check its own work."* +- The guidance panel no longer asks you to write shell prompts; it says most tasks need nothing there and to fill it only for a gate no agent can talk its way past. +- `bin/merge` already worked without one — it says *"no verify command: this would pass on the agent's word"* and continues to the review. + +## What Paperclip actually does, and what you actually write + +You write a task in your own words. Pi reads it, works out how to do it, runs the project's own tests, comments with evidence, and sets the status. Paperclip adds the parts you do not want to run by hand: blockers as links between tasks, review stages (`in_review` with a participant), approvals, run history, cost, and a worktree per task. + +So the autonomy is in the *work*, not in your instructions. My test plan asked for a file name and an exact line, which is why it read like dictating a patch. + +## The error you saw was a real bug, not your mistake + +`AUD-167` **was created and completed** — `ADd a file named STATUS.md`, status `done`, assigned to Builder. The "[The program exited with status 0.]" was the pane dying: the New task pane ran the form alone, so when the form quit after creating, nothing was left to run. Two fixes: + +- the form now **stays open** and shows `created AUD-1xx, handed to an agent which will start on its own`, with the fields cleared for the next one; +- every pane now **falls back to a shell**, so a program that exits returns to a prompt instead of leaving a dead pane. + +I proved both by creating `AUD-168` — *"Tidy the README introduction"*, assigned, no verify command. It shows `in_progress` with Builder working, and the form stayed usable. + +## On "have you fully read and understood Paperclip" + +No, and I should have said so earlier. I have read the agent skill, the API specification, the agents/heartbeats/routines documentation, and I have probed the API extensively. What I got wrong was mixing your old project's conventions with Paperclip's model and presenting the mixture as if it were Paperclip's. Where I am unsure — review stages, execution policy, approvals — I will read before advising, and say when I have not. + +## The test plan, corrected + +`docs/TEST-PLAN.md` no longer asks for a shell command anywhere. Step 1 is now: title, plain-language description, no blocker, no command, `yes` to assign — and expect the agent to pick it up within seconds and decide for itself how to check the work. Step 4 keeps `bin/merge` but explains that "no verify command" is a normal outcome, not a failure. + +Committed as `678c497`. `AUD-168` is running now if you want to watch it in `Agents>agents` — that is the honest demonstration of autonomy: you said what you wanted, it is working out how. + +## Source paths observed + +- `../bin/harness` +- `agents/heartbeats/routines` + +## Evidence + +- Confidence: 82% +- Reason: Captured factual repo structure/configuration patterns from rich discovery + +## Related learned memories + +- session: [[packs/paperclip-harness/80-sessions/rich-discovery-agents-2026-11-11-00-28-33|Rich discovery: agents 2026-11-11 00:28:33]] +- context: [[packs/paperclip-harness/20-context/agents|agents]] +- observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|agents repository structure and configuration patterns]] diff --git a/.pi/memory-vault/packs/paperclip-harness/80-sessions/rich-discovery-agents-2026-11-11-00-28-33.md b/.pi/memory-vault/packs/paperclip-harness/80-sessions/rich-discovery-agents-2026-11-11-00-28-33.md new file mode 100644 index 0000000..d674032 --- /dev/null +++ b/.pi/memory-vault/packs/paperclip-harness/80-sessions/rich-discovery-agents-2026-11-11-00-28-33.md @@ -0,0 +1,83 @@ +--- +type: session +id: session.paperclip-harness.rich-discovery-agents-2026-11-11-00-28-33 +title: Rich discovery: agents 2026-11-11 00:28:33 +status: active +source_of_truth: false +freshness: current +last_reviewed: 2026-11-11 +tags: + - pack/paperclip-harness + - agent-memory/session + - autolearn + - rich-discovery + - repo/agents +--- + +# Rich discovery: agents 2026-11-11 00:28:33 + +Sanitized rich discovery snapshot captured automatically from the completed turn. + + +## Source paths observed + +- `../bin/harness` +- `agents/heartbeats/routines` + +## Final answer snapshot + +You are right on both counts, and I will be precise about what was invented and whose rule it was. + +## Where the shell command came from — and why I was wrong to force it + +It is **not** a Paperclip rule. It is your own project rule, written in `docs/OPERATING-RULES.md` before this attempt: + +> **How a task is judged finished.** The test is a **command**, kept with the project, and agreed **before** the work starts. + +That came out of the old project, along with `.harness/verify.sh`. Paperclip has no such field. An agent sets a task's status itself; the reviewer or a review stage checks it. I took a project convention and turned it into a mandatory form field and a shell prompt in a test plan, which made the agent look like a dumb executor. That is my error, and the rule is now marked in `OPERATING-RULES.md` as ours, not Paperclip's. + +**What changed in the program:** + +- The field is now called **"A command that proves it (optional)"**, and its hint says: *"leave empty and the agent decides how to check its own work."* +- The guidance panel no longer asks you to write shell prompts; it says most tasks need nothing there and to fill it only for a gate no agent can talk its way past. +- `bin/merge` already worked without one — it says *"no verify command: this would pass on the agent's word"* and continues to the review. + +## What Paperclip actually does, and what you actually write + +You write a task in your own words. Pi reads it, works out how to do it, runs the project's own tests, comments with evidence, and sets the status. Paperclip adds the parts you do not want to run by hand: blockers as links between tasks, review stages (`in_review` with a participant), approvals, run history, cost, and a worktree per task. + +So the autonomy is in the *work*, not in your instructions. My test plan asked for a file name and an exact line, which is why it read like dictating a patch. + +## The error you saw was a real bug, not your mistake + +`AUD-167` **was created and completed** — `ADd a file named STATUS.md`, status `done`, assigned to Builder. The "[The program exited with status 0.]" was the pane dying: the New task pane ran the form alone, so when the form quit after creating, nothing was left to run. Two fixes: + +- the form now **stays open** and shows `created AUD-1xx, handed to an agent which will start on its own`, with the fields cleared for the next one; +- every pane now **falls back to a shell**, so a program that exits returns to a prompt instead of leaving a dead pane. + +I proved both by creating `AUD-168` — *"Tidy the README introduction"*, assigned, no verify command. It shows `in_progress` with Builder working, and the form stayed usable. + +## On "have you fully read and understood Paperclip" + +No, and I should have said so earlier. I have read the agent skill, the API specification, the agents/heartbeats/routines documentation, and I have probed the API extensively. What I got wrong was mixing your old project's conventions with Paperclip's model and presenting the mixture as if it were Paperclip's. Where I am unsure — review stages, execution policy, approvals — I will read before advising, and say when I have not. + +## The test plan, corrected + +`docs/TEST-PLAN.md` no longer asks for a shell command anywhere. Step 1 is now: title, plain-language description, no blocker, no command, `yes` to assign — and expect the agent to pick it up within seconds and decide for itself how to check the work. Step 4 keeps `bin/merge` but explains that "no verify command" is a normal outcome, not a failure. + +Committed as `678c497`. `AUD-168` is running now if you want to watch it in `Agents>agents` — that is the honest demonstration of autonomy: you said what you wanted, it is working out how. + +## Evidence + +- Confidence: 86% +- Reason: Preserve full sanitized discovery detail from a rich investigation turn + +## Related + +- [[packs/paperclip-harness/00-system/pi-agent/memory-manifest|Memory Manifest]] + +## Related learned memories + +- session: [[packs/paperclip-harness/80-sessions/rich-discovery-agents-2026-11-11-00-28-33|Rich discovery: agents 2026-11-11 00:28:33]] +- context: [[packs/paperclip-harness/20-context/agents|agents]] +- observation: [[packs/paperclip-harness/60-observations/tmp-repository-structure-and-configuration-patterns|agents repository structure and configuration patterns]] diff --git a/README.md b/README.md index 8230000..c838919 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ program running in the Board workspace. |---|---|---| | Shell>shell | a prompt in the project folder: the pane that never exits, and the escape hatch for anything no panel covers | — | | Board>board | tasks by group · the chosen task · needs you | ↑↓ j k · enter · a · u · / · t (tools) · tab | -| Work>work | runs · change · facts · review | [ ] tab · ↑↓ j k · g G · t (tools) | +| Work>work | runs · change · facts · review · merge | [ ] tab · ↑↓ j k · g G · o (choose task) · m (merge) · t (tools) | | Reply>reply | the task · the thread · the reply box | enter · s · esc | | New task>new | the form · what a task needs · what will be sent | tab · enter · ctrl+n | | Project>project | overview · facts · costs · settings | [ ] tab · ↑↓ j k | diff --git a/bin/harness b/bin/harness index f303e9c..9ded79b 100755 Binary files a/bin/harness and b/bin/harness differ diff --git a/bin/lib/__pycache__/harness.cpython-314.pyc b/bin/lib/__pycache__/harness.cpython-314.pyc index 142488a..3105158 100644 Binary files a/bin/lib/__pycache__/harness.cpython-314.pyc and b/bin/lib/__pycache__/harness.cpython-314.pyc differ diff --git a/bin/lib/__pycache__/tuios.cpython-314.pyc b/bin/lib/__pycache__/tuios.cpython-314.pyc index 6160f27..a79d4b2 100644 Binary files a/bin/lib/__pycache__/tuios.cpython-314.pyc and b/bin/lib/__pycache__/tuios.cpython-314.pyc differ diff --git a/bin/lib/harness.py b/bin/lib/harness.py index e898228..3dd90c3 100644 --- a/bin/lib/harness.py +++ b/bin/lib/harness.py @@ -14,6 +14,20 @@ import sys import urllib.error import urllib.request +# A program started from a TUIOS pane gets a smaller PATH than an interactive +# shell, so the places a person's tools actually live are added before anything +# is looked for. Without this, ocr is "not installed" inside a merge. +_HOME = os.path.expanduser("~") +_USER = os.environ.get("USER", "") +for _p in ( + os.path.join(_HOME, ".local", "bin"), + os.path.join(_HOME, ".nix-profile", "bin"), + "/run/current-system/sw/bin", + "/etc/profiles/per-user/" + _USER + "/bin", +): + if _p not in os.environ.get("PATH", ""): + os.environ["PATH"] = _p + ":" + os.environ.get("PATH", "") + API = os.environ.get("PAPERCLIP_API_URL", "http://127.0.0.1:3100").rstrip("/") COMPANY = os.environ.get("PAPERCLIP_COMPANY_ID", "d4921013-e5da-4d88-8016-57d5746e2d03") KEY = os.environ.get("PAPERCLIP_BOARD_KEY", "") diff --git a/bin/lib/tuios.py b/bin/lib/tuios.py index 0624ae1..a3ee2e1 100644 --- a/bin/lib/tuios.py +++ b/bin/lib/tuios.py @@ -157,6 +157,14 @@ def arrange(order, session=None): return moved +def window_cwd(name, session=None): + """The working directory of a named pane, as TUIOS reports it.""" + for w in windows(session): + if (w.get("custom_name") or "") == name: + return w.get("cwd") or "" + return "" + + def close_pane(name, session=None): for w in windows(session): if (w.get("custom_name") or "") == name: diff --git a/docs/TEST-PLAN.md b/docs/TEST-PLAN.md index ff53235..982de57 100644 --- a/docs/TEST-PLAN.md +++ b/docs/TEST-PLAN.md @@ -86,24 +86,33 @@ Go to `Work>work`. It follows the task you chose on the board. ## Step 4 — merge it -This is the one step that needs a shell, because a merge takes a lock and re-runs the checks. +No terminal. Stay in `Work>work`. -1. Go to `Shell>shell`. You are in the project folder. -2. Type: `bin/merge AUD-1xx --check` (your identifier), press `Enter`. +1. Press `m`. The **merge** tab opens by itself and shows what is happening: -**Expected:** it rebases, runs the review, and prints either `checked only; nothing merged` or -`no verify command: this would pass on the agent's word` followed by the same. Both are normal: -nothing is merged yet. + ``` + merge of AUD-1xx started 12:01:39 + rebased onto main + no verify command: this would pass on the agent's word + review saw nothing to review (0 files); recorded, not hidden + merged into main at 4a44473 + merge finished 12:01:43 + ``` -3. Type: `bin/merge AUD-1xx`, press `Enter`. +2. Wait for `merged into main at `. That commit is the task's work, on the project's + base branch. -**Expected:** the same checks, then `merged into main at `. A review that finds -something serious refuses the merge and says so; that is the gate working, not a failure. +**Expected:** a green refusal line, not a red one. If it says `refused: the review found 1 +blocking item(s)`, the gate worked — the findings are on the task and in `Work>review`. -4. Type `git log --oneline -3`, press `Enter`. Your task's merge commit is at the top. +**Starting a merge twice by accident is safe**: the merge takes a lock and the second one is +refused with `another merge is in progress`. -**Stop if:** it says `refused`. The reason is printed and also written on the task, where -`Work>review` shows it. +## Step 4b — choose a task without leaving Work + +In `Work>work`, press `o`. A list of this project's tasks appears. `↑`/`↓` to one, `Enter`, and +the runs, change, facts, review and merge tabs all switch to it. You never have to go to +`Board>board` first. ## Step 5 — a task that must wait for another diff --git a/docs/WORKSPACES.md b/docs/WORKSPACES.md index ad6c05c..7d13658 100644 --- a/docs/WORKSPACES.md +++ b/docs/WORKSPACES.md @@ -37,6 +37,7 @@ what it shows is never a second copy of anything. | `Work>change` | worktree, branch, base, commits, changed files, the diff | built | | `Work>facts` | task, state, worktree path, branch, base, cost | built | | `Work>review` | the verdicts recorded on this task: review findings, refused merges, merged results, blocking ones in red | built | +| `Work>merge` | the last merge this screen ran, live, with `m` to start one — no terminal needed | built | ## 4. Reply diff --git a/tui/rebuild.go b/tui/rebuild.go index f0878b4..da32172 100644 --- a/tui/rebuild.go +++ b/tui/rebuild.go @@ -88,3 +88,55 @@ func splitLines(s string) []string { } return out } + +// mergeLogPath is where a merge writes what it did, so a panel can show it +// without the person opening a shell or remembering a command. +func mergeLogPath() string { + return filepath.Join(os.TempDir(), "paperclip-harness-merge.log") +} + +// startMerge runs bin/merge for one task, detached, and returns at once. The +// Work workspace tails the log; the merge itself takes a lock, so starting one +// twice by accident is refused rather than doubled. +func startMerge(folder, identifier string) error { + merge := filepath.Join(exeDir(), "merge") + if _, err := os.Stat(merge); err != nil { + return fmt.Errorf("no bin/merge next to the program (%s)", merge) + } + log, err := os.Create(mergeLogPath()) + if err != nil { + return err + } + fmt.Fprintf(log, "merge of %s started %s\n", identifier, time.Now().Format("15:04:05")) + + cmd := exec.Command(merge, identifier) + cmd.Dir = folder + cmd.Stdout = log + cmd.Stderr = log + cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} + if err := cmd.Start(); err != nil { + log.Close() + return err + } + go func() { + err := cmd.Wait() + if err != nil { + fmt.Fprintf(log, "\nmerge stopped: %v\n", err) + } else { + fmt.Fprintf(log, "\nmerge finished %s\n", time.Now().Format("15:04:05")) + } + log.Close() + }() + return nil +} + +// mergeLogTail returns the end of the last merge, for the panel that shows it. +func mergeLogTail(lines int) []string { + b, err := os.ReadFile(mergeLogPath()) + if err != nil { + return []string{"", " no merge has been run from this screen yet"} + } + all := splitLines(string(b)) + start := max(0, len(all)-lines) + return all[start:] +} diff --git a/tui/work.go b/tui/work.go index 50b1720..5fcdfb0 100644 --- a/tui/work.go +++ b/tui/work.go @@ -19,6 +19,11 @@ import ( "paperclip-harness/tui/paperclip" ) +type pickTasksMsg struct { + issues []paperclip.Issue + err error +} + type workMsg struct { task paperclip.Issue runs []paperclip.Run @@ -46,13 +51,19 @@ type workModel struct { scroll int tools bool toolAt int - readAt time.Time + + // choosing a task here, so the screen does not depend on the Board + picking bool + pickList []paperclip.Issue + pickNames map[string]string + pickAt int + readAt time.Time showHelp bool width, height int } -var workTabs = []string{"runs", "change", "facts", "review"} +var workTabs = []string{"runs", "change", "facts", "review", "merge"} func newWorkModel(c *paperclip.Client, folder, issueID string) workModel { return workModel{client: c, folder: folder, issue: issueID, explicit: issueID != ""} @@ -125,6 +136,34 @@ func (m workModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Batch(m.load(), tickEvery(5*time.Second), tea.WindowSize()) case tea.KeyMsg: + if m.picking { + switch msg.String() { + case "esc", "q", "o": + m.picking = false + return m, nil + case "up", "k": + m.pickAt = max(0, m.pickAt-1) + return m, nil + case "down", "j": + m.pickAt = min(len(m.pickList)-1, m.pickAt+1) + return m, nil + case "enter": + if m.pickAt < len(m.pickList) { + chosen := m.pickList[m.pickAt] + m.picking = false + if err := rememberTask(m.folder, chosen.Identifier); err != nil { + m.err = err + return m, nil + } + m.explicit = true + m.issue = chosen.ID + m.task = chosen + return m, m.load() + } + return m, nil + } + return m, nil + } if m.tools { switch msg.String() { case "esc", "q", "t": @@ -160,6 +199,39 @@ func (m workModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "t": m.tools = true return m, nil + case "o": + c := m.client + folder := m.folder + return m, func() tea.Msg { + cfg, err := readHarnessFile(folder) + if err != nil { + return errMsg{err} + } + issues, err := c.Issues() + if err != nil { + return errMsg{err} + } + var mine []paperclip.Issue + for _, i := range issues { + if i.ProjectID == cfg["paperclip.project"] { + mine = append(mine, i) + } + } + return pickTasksMsg{issues: mine} + } + case "m": + if m.task.Identifier == "" { + return m, nil + } + // The merge runs on its own and writes what it does to a log this + // tab reads, so nobody has to type a command or leave the screen. + if err := startMerge(m.folder, m.task.Identifier); err != nil { + m.err = err + return m, nil + } + m.tab = 4 + m.scroll = 1 << 30 + return m, m.load() case "?": m.showHelp = true return m, nil @@ -201,6 +273,15 @@ func (m workModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } + case pickTasksMsg: + m.pickList = msg.issues + m.pickAt = 0 + m.picking = true + if msg.err != nil { + m.err = msg.err + } + return m, nil + case workMsg: m.task = msg.task m.runs = msg.runs @@ -259,10 +340,23 @@ func (m workModel) View() string { body = m.diffLines(m.width - 4) case 3: body = m.reviewLines(m.width - 4) + case 4: + body = m.mergeLines(m.width - 4) default: body = m.factsLines(m.width - 4) } + if m.picking { + entries := make([][2]string, 0, len(m.pickList)) + for i, is := range m.pickList { + mark := " " + if i == m.pickAt { + mark = "▸ " + } + entries = append(entries, [2]string{mark + is.Identifier, is.Status + " " + is.Title}) + } + return helpOverlay("choose a task to look at", entries, m.width, m.height) + } if m.tools { entries := make([][2]string, 0, len(tools)) for i, t := range tools { @@ -296,6 +390,9 @@ func (m workModel) View() string { keyHint("[ ]", "switch tab"), keyHint("↑↓ or j k", "scroll"), keyHint("t", "open a tool pane"), + keyHint("o", "choose a task"), + keyHint("m", "merge this task"), + keyHint("]", "tabs"), keyHint("r", "read again"), keyHint("R", "rebuild every pane"), keyHint("q", "leave"), @@ -441,6 +538,24 @@ func (m workModel) reviewLines(width int) []string { return out } +// mergeLines shows the last merge this screen started, read from the log the +// merge writes. Pressing m again is safe: the merge takes a lock. +func (m workModel) mergeLines(width int) []string { + out := []string{"", dimStyle().Render(" " + mergeLogPath()), ""} + for _, ln := range mergeLogTail(max(6, m.height-6)) { + colour := active.Text + switch { + case strings.Contains(ln, "refused"), strings.Contains(ln, "stopped"): + colour = active.Error + case strings.Contains(ln, "merged into"), strings.Contains(ln, "finished"): + colour = active.Success + } + out = append(out, " "+lipgloss.NewStyle().Foreground(lipgloss.Color(colour)).Render(truncateVisible(ln, width-2))) + } + out = append(out, "", dimStyle().Render(" m runs the checks and merges this task; the log above is what it did")) + return out +} + func (m workModel) factsLines(width int) []string { var out []string out = append(out, "")