dsh-git-badge 0.7.0 → 0.9.0

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.
package/CHANGELOG.md CHANGED
@@ -8,14 +8,158 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
8
8
  Versions before 0.6.0 predate this file; their history is in `git log` and on
9
9
  npm. 0.6.0 is the first release recorded here.
10
10
 
11
- ## [Unreleased]
11
+ ## [0.9.0] - 2026-09-14
12
+
13
+ ### Added
14
+
15
+ - **The seam patcher ships in the package** as the `dsh-git-badge-seam` bin, so a
16
+ market install can add the sidebar session-row badges without cloning the
17
+ repository: `npx dsh-git-badge-seam apply`, then restart. Pure Node (no
18
+ python3), anchor-based like the repo tooling it replaces, with the same
19
+ refusal semantics: it writes nothing unless every anchor resolves exactly
20
+ once, upgrades its own older artifacts by marker revision, and reverts to the
21
+ bytes as found before patching. When the seam is absent the plugin's boot log
22
+ now says so and names the command.
23
+
24
+ ### Added
25
+
26
+ - **A session's badge follows the worktree its work is in.** A conversation's
27
+ directory is fixed when it is created, and DSH records no session→worktree link
28
+ at all (`attachSession` requires the stored cwd to equal the workspace path), so
29
+ a conversation started in the main checkout reported `main` — including "no pull
30
+ request" — while its work and its PR lived in a linked worktree.
31
+ Now, when a **session**-targeted badge resolves to a main checkout whose
32
+ repository has exactly one linked worktree whose branch has an **open** pull
33
+ request, the badge describes that worktree instead: its branch, dirty counts,
34
+ ahead/behind, tree-shaped mark, and PR/CI token. The `workspace` id in the
35
+ response is unchanged, so SSE attribution is unaffected.
36
+ The selection is deliberately the only one that cannot be ambiguous — one open
37
+ PR is a candidate, two are a reason to say nothing — the worktree is **named**
38
+ on the chip even when the branch would make the name look redundant, the mark's
39
+ accessible name states that the checkout was inferred, and the hover card gains
40
+ a `checkout` row carrying the conversation's OWN directory (branch · files ·
41
+ sync) so the main checkout's state stays visible. A workspace-targeted sidebar
42
+ row never follows a worktree: a row surveys the checkout the registry owns.
43
+ - **An inferred worktree is watched.** Its working tree and git dir get the same
44
+ event-driven treatment as a registered workspace's, and its events carry the
45
+ **owning workspace id**, so the badge refreshes within ~1s of a stage, commit or
46
+ checkout in the tree — including a worktree that is not itself a registered
47
+ workspace, which the main checkout's recursive watch would never see.
48
+ - **Identical concurrent status reads are collapsed** into one `git status` per
49
+ directory, which is the shape a burst of badges mounting together produces.
50
+ `statusCacheMs` (default 0) optionally extends that to serial bursts; the 0
51
+ default keeps a read taken after a mutation honest. New config: `worktreeStatus`
52
+ (`"auto"` | `"off"`), `prListLimit`, `statusCacheMs`.
53
+ - The repository-wide open-PR read (`gh pr list`) happens at most once per TTL per
54
+ repository, and never at all for a repository with no linked worktree.
55
+
56
+ ### Changed
57
+
58
+ - **The sidebar session row is an ACTION token, not a status badge.** It shows
59
+ the one thing that conversation needs — `merge`, `fix CI`, `review`, `resolve`,
60
+ `pull`, `push` — and nothing when there is nothing to do. `merge` keys on
61
+ GitHub's own `mergeStateStatus: CLEAN` rather than a verdict assembled here; a
62
+ draft, blocked, behind, unstable or unknown PR stays silent, because waiting is
63
+ not an action. This is deliberately a trade: the row no longer shows local
64
+ status or the worktree's tree shape, and the hover line — which now names the
65
+ pull request as well as the checkout — is where that detail lives. The word floats right so a column of actions lines
66
+ up down the sidebar, sits heavier than the timestamp beside it, and is coloured
67
+ by SEVERITY with the app's own state tokens — error for `resolve` and `fix CI`,
68
+ warn for `review`, success for `merge`, quiet secondary for `pull` and `push` —
69
+ so the loud rows keep meaning something. The chip is unchanged. Revert this
70
+ commit to go back to the mark-plus-token row.
71
+ - **The sidebar badge moved from the workspace row to the session row.** A
72
+ workspace row cannot know which worktree its conversations are using, so a
73
+ per-workspace badge could only guess or report the main checkout. Each session
74
+ row now carries the status mark plus the PR/CI token when the branch has one
75
+ (`🌳 PR#391 ✓`), and never a branch — hovering the row adds a line naming the
76
+ checkout that badge describes (`checkout: hotfix-tree on feat/x`, and for an
77
+ inferred worktree, why it was followed). Rows still issue no request of their
78
+ own beyond the badge's: the hover line resolves to the same cache key, so a card
79
+ that mounts for every row cannot multiply git or forge work.
80
+ - The `seam/` patch now declares `sidebar.workspaces.sessionRow` and
81
+ `sidebar.workspaces.sessionRow.detail` instead of the old
82
+ `sidebar.workspaces.row` pair, and the row is rendered from the **tree** call
83
+ site only, which keeps the flat and search lists badge-free with no guard of
84
+ their own. `seam/apply.sh` recognises its own older artifacts by a marker
85
+ comment (and the previous hash), so rebuilding the patch installs it instead of
86
+ being skipped as "upstream landed" — and `revert` now restores the upstream
87
+ baseline rather than an older patch.
88
+
89
+ ### Fixed
90
+
91
+ - **A failing seam occupant could blank the whole sidebar.** The host guards each
92
+ registered *entry* with an error boundary, but not the outlet a row renders
93
+ itself, so a throw in an occupant's render path reached the workspace browser —
94
+ and the shell abdicates that entry, removing the entire sidebar region about a
95
+ second after boot. The seam patch now wraps both session-row renders in its own
96
+ boundary: a broken occupant loses its badge and logs the error instead.
97
+ - **The PR cache served the wrong branch.** `prStatusFor` was keyed by toplevel
98
+ only, so a checkout that changed branch inside the TTL window was served the
99
+ previous branch's pull request. It is now keyed by toplevel **and** branch.
100
+
101
+ ## [0.8.0] - 2026-09-12
102
+
103
+ ### Added
104
+
105
+ - **A hover card on the input chip.** Resting a pointer on the chip now shows what
106
+ the chip has no room for: the file breakdown (`✎3` split into staged / unstaged
107
+ / unmerged / untracked, so *ready to commit* is distinguishable from *not staged
108
+ yet*), the last three commits with their ages, and the stash count. When the
109
+ untracked walk fell back to git's collapsed count the card says `(collapsed)`
110
+ rather than presenting an under-count as exact.
111
+ The card consumes the `detail=1` payload the node half has carried unused since
112
+ 0.6.0, and it is **lazy**: the extra fields cost a `log -3` plus a stash list, so
113
+ they are fetched only once a pointer rests on the chip and then kept fresh over
114
+ the same SSE path. A badge refresh fires on every file edit, and the everyday
115
+ chip still pays for none of it. It uses the shell's own seeded `Tooltip`
116
+ primitive — no new dependency, no seam patch — and if that primitive is ever
117
+ absent the chip renders uncarded rather than not at all. The boot log reports
118
+ which path was taken (`[dsh-git-badge] hover card = on (shell Tooltip
119
+ primitive).`), because the guard that keeps the badge working would otherwise
120
+ make a missing seed look like a missing feature.
121
+ - **A PR / CI token on the input chip.** When the branch has a GitHub pull
122
+ request the chip carries `PR#142` with the rollup state (`✓` passing, `…`
123
+ running, `✗` failing) and a `draft` marker. It reads the PR through the user's
124
+ own `gh` CLI, so the plugin never handles a credential. The rollup is reduced to
125
+ one worst-case state (a single failure outweighs any number of successes), the
126
+ token carries an accessible name naming the state, and the hover card spells out
127
+ the check state, draft and review decision.
128
+ - **The PR token links to the pull request.** When `gh` reports an http(s) URL the
129
+ token is an anchor that opens the PR in a new tab, so this browser tab keeps the
130
+ conversation. It underlines on hover or focus only — the at-rest signal is the
131
+ pointer cursor — and keeps the chip's own colour instead of the browser's link
132
+ blue, so it still reads as one row. Being an anchor, it is also keyboard-reachable
133
+ now, which the text token was not. The protocol is checked on both halves: only
134
+ `http:`/`https:` is carried or rendered, so a payload value can never become a
135
+ `javascript:` href.
136
+ - `?pr=1` on the status route. PR/CI is asked for by the **chip only**, so a
137
+ sidebar that surveys twenty workspaces never spawns twenty `gh` processes. The
138
+ read is TTL-bounded (~90s per repository), refreshed out of band exactly like
139
+ the existing fetch, and notifies subscribers only when the state actually
140
+ changes.
141
+ - **PR/CI degrades to an absent field, never an error.** No `gh`, a logged-out
142
+ `gh`, a non-GitHub remote, an offline network, a rate limit, unparseable output
143
+ and a timed-out call all render the unchanged chip: a caller cannot tell "no PR"
144
+ from "no `gh`", because neither changes what should be displayed.
145
+ - A `runCli` helper the git and `gh` invocations now share, so `gh` inherits the
146
+ same timeout / missing-binary / exit-code semantics (and `GIT_TERMINAL_PROMPT`'s
147
+ analogue, `GH_PROMPT_DISABLED`, keeps an auth prompt from hanging a refresh).
148
+
149
+ ### Changed
150
+
151
+ - The `detail=1` payload has a consumer at last. Its annotation said "no surface
152
+ consumes this yet"; the input chip's hover card is that surface, so the
153
+ branch/last-commits/stash fields are no longer speculative.
154
+ - `config` gains `prStatus` (`"auto"` / `"off"`), `prTtlMs`, `prTimeoutMs` and a
155
+ `prRunner` test seam mirroring `gitRunner`.
12
156
 
13
157
  ## [0.7.0] - 2026-09-12
14
158
 
15
159
  ### Changed
16
160
 
17
161
  - **Both surfaces draw the same status mark.** The input chip no longer leads with
18
- a 🔴/🟡/🟢 emoji dot: it renders the SAME 12px SVG mark as the sidebar row — a
162
+ a 🔴/🟡/🟢 emoji dot: it renders the SAME SVG mark as the sidebar row — a
19
163
  filled **circle**, or a **tree** for a linked `git worktree` — so the shape says
20
164
  *worktree or not* and the fill says *status*, identically wherever the badge
21
165
  appears. The `⑂` worktree glyph is gone with the emoji: the tree shape already
@@ -136,6 +280,7 @@ npm. 0.6.0 is the first release recorded here.
136
280
  - `detail=1` is annotated as having no consumer yet (the hover card remains
137
281
  pending). Its comment previously claimed `log -1` while the code ran `log -3`.
138
282
 
139
- [Unreleased]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/compare/v0.7.0...HEAD
283
+ [Unreleased]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/compare/v0.8.0...HEAD
284
+ [0.8.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.8.0
140
285
  [0.7.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.7.0
141
286
  [0.6.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.6.0
package/README.md CHANGED
@@ -3,11 +3,19 @@
3
3
  Git status badges for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness):
4
4
 
5
5
  - **Input-row chip** — `● main ↑0 ↓2 ✎3` next to the access picker, showing the git state of the workspace the current conversation is attached to. Works on every install.
6
- - **Sidebar row badges** — the same status mark floated to the right of each workspace row (`Project 1 ●`), with a linked worktree's name beside it (`Project 2 hotfix-tree 🌳`). Requires the `sidebar.workspaces.row` seam; without it the plugin degrades to chip-only and logs what's active to the browser console.
6
+ - **Hover card** — rest a pointer on the chip for what won't fit: the file breakdown (`✎3` split into staged / unstaged / untracked), the last three commits, and the stash. Fetched only on hover, so the everyday badge pays nothing for it. Works on every install.
7
+ - **PR / CI token** — `● main ↑0 ↓2 PR#142 ✗` when the branch has a GitHub pull request, read through your own `gh` CLI (the plugin never handles a credential). The token **links to the PR** — it opens in a new tab, and underlines when you hover or focus it. Invisible when `gh` cannot answer: not installed, not logged in, no GitHub remote, offline or rate-limited all render the unchanged chip, and a token with no URL to link to stays plain text.
8
+ - **Worktree-aware** — a conversation's directory is fixed when it is created, so a conversation that does its work in a linked `git worktree` would otherwise keep reporting the main checkout. When exactly one linked worktree of that repository has an open pull request, the badge describes **that worktree** instead — its branch, counts, tree mark and PR/CI token (`🌳 chore/global-skills-tiering ↑0 ↓2 PR#391 ✓` rather than `● main`). The hover card names the tree in a `worktree` row, says it was inferred, and adds a `checkout` row with the conversation's own directory, so the main checkout's branch and dirty state stay one hover away. One candidate only — two open PRs across worktrees is a reason to say nothing rather than guess — and a session row always reports the checkout that session is working in.
9
+ - **Sidebar session-row action token** — each session row shows the ONE thing that conversation needs from you, or nothing at all: `merge`, `fix CI`, `review`, `resolve`, `pull`, `push`. It is triage rather than status: `merge` appears only when GitHub itself reports `mergeStateStatus: CLEAN`, and every waiting state (draft, blocked, behind, checks still running) stays silent. Hovering the row adds a line naming the checkout and the pull request the action refers to. The token floats to the right of the row, is set heavier than the timestamp beside it, and is coloured by how much it needs you — red for a conflict or a failing build, amber for a requested review, green for `merge`, quiet grey for routine sync. Requires the `sidebar.workspaces.sessionRow` seam; without it the plugin degrades to chip-only and logs what's active to the browser console.
10
+ - **Nothing on workspace rows.** A workspace cannot know which worktree its conversations are using, so a per-workspace badge could only guess or report the main checkout. The seam is on the session row for that reason.
7
11
 
8
- The leading **mark** is identical on both surfaces: a circle filled with the status colour, or a **tree** when the workspace is a linked `git worktree`. (`●`/`🌳` stand in for it here — it is a drawn SVG so that it can take the theme's status colours.)
12
+ The leading **mark** belongs to the input chip: a circle filled with the status colour, or a **tree** when the checkout is a linked `git worktree`. The sidebar row deliberately carries no mark — see the row bullet above, where one word does the job a colour cannot. (`●`/`🌳` stand in for it here — it is a drawn SVG so that it can take the theme's status colours.)
9
13
 
10
- Event-driven freshness: working trees are watched and updates push over SSE — badges flip within ~1s of any commit, stage, or edit. One `git status --porcelain=v2` call per sample. The status API takes **no path**: the caller passes a session id (chip) or workspace id (row) and the server resolves the directory itself.
14
+ Event-driven freshness: working trees are watched and updates push over SSE — badges flip within ~1s of any commit, stage, or edit, including inside a linked worktree that is only inferred. One `git status --porcelain=v2` per sample, shared by every surface pointing at the same checkout, plus at most one `gh` read per repository-and-branch per ~90s and one repository-wide open-PR list per ~90s. The status API takes **no path**: the caller passes a session id (chip and sidebar rows) or a workspace id, and the server resolves the directory itself.
15
+
16
+ Diagrams, the hover card's full field list and a walkthrough for git newcomers live
17
+ in the [repo README](https://github.com/Kevin-McIsaac/dsh-workspace-git-badge) —
18
+ this file is the npm-facing summary.
11
19
 
12
20
  ## Install
13
21
 
@@ -15,7 +23,7 @@ Event-driven freshness: working trees are watched and updates push over SSE —
15
23
  dsh plugin --profile web add dsh-git-badge
16
24
  ```
17
25
 
18
- Restart the web process, refresh the browser. Requires Node ≥ 20 and `git` on PATH.
26
+ Restart the web process, refresh the browser. Requires Node ≥ 20 and `git` on PATH. `gh`, on PATH and authenticated, is optional — it only enables the PR/CI token.
19
27
 
20
28
  Seam details: [github.com/Kevin-McIsaac/dsh-workspace-git-badge](https://github.com/Kevin-McIsaac/dsh-workspace-git-badge) (SEAM.md / PR.md).
21
29