dsh-git-badge 0.6.0 → 0.8.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 +198 -0
- package/README.md +12 -4
- package/lib/client.js +569 -136
- package/lib/index.js +554 -90
- package/package.json +2 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this package are documented here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
6
|
+
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
Versions before 0.6.0 predate this file; their history is in `git log` and on
|
|
9
|
+
npm. 0.6.0 is the first release recorded here.
|
|
10
|
+
|
|
11
|
+
## [Unreleased]
|
|
12
|
+
|
|
13
|
+
## [0.8.0] - 2026-09-12
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **A hover card on the input chip.** Resting a pointer on the chip now shows what
|
|
18
|
+
the chip has no room for: the file breakdown (`✎3` split into staged / unstaged
|
|
19
|
+
/ unmerged / untracked, so *ready to commit* is distinguishable from *not staged
|
|
20
|
+
yet*), the last three commits with their ages, and the stash count. When the
|
|
21
|
+
untracked walk fell back to git's collapsed count the card says `(collapsed)`
|
|
22
|
+
rather than presenting an under-count as exact.
|
|
23
|
+
The card consumes the `detail=1` payload the node half has carried unused since
|
|
24
|
+
0.6.0, and it is **lazy**: the extra fields cost a `log -3` plus a stash list, so
|
|
25
|
+
they are fetched only once a pointer rests on the chip and then kept fresh over
|
|
26
|
+
the same SSE path. A badge refresh fires on every file edit, and the everyday
|
|
27
|
+
chip still pays for none of it. It uses the shell's own seeded `Tooltip`
|
|
28
|
+
primitive — no new dependency, no seam patch — and if that primitive is ever
|
|
29
|
+
absent the chip renders uncarded rather than not at all. The boot log reports
|
|
30
|
+
which path was taken (`[dsh-git-badge] hover card = on (shell Tooltip
|
|
31
|
+
primitive).`), because the guard that keeps the badge working would otherwise
|
|
32
|
+
make a missing seed look like a missing feature.
|
|
33
|
+
- **A PR / CI token on the input chip.** When the branch has a GitHub pull
|
|
34
|
+
request the chip carries `PR#142` with the rollup state (`✓` passing, `…`
|
|
35
|
+
running, `✗` failing) and a `draft` marker. It reads the PR through the user's
|
|
36
|
+
own `gh` CLI, so the plugin never handles a credential. The rollup is reduced to
|
|
37
|
+
one worst-case state (a single failure outweighs any number of successes), the
|
|
38
|
+
token carries an accessible name naming the state, and the hover card spells out
|
|
39
|
+
the check state, draft and review decision.
|
|
40
|
+
- **The PR token links to the pull request.** When `gh` reports an http(s) URL the
|
|
41
|
+
token is an anchor that opens the PR in a new tab, so this browser tab keeps the
|
|
42
|
+
conversation. It underlines on hover or focus only — the at-rest signal is the
|
|
43
|
+
pointer cursor — and keeps the chip's own colour instead of the browser's link
|
|
44
|
+
blue, so it still reads as one row. Being an anchor, it is also keyboard-reachable
|
|
45
|
+
now, which the text token was not. The protocol is checked on both halves: only
|
|
46
|
+
`http:`/`https:` is carried or rendered, so a payload value can never become a
|
|
47
|
+
`javascript:` href.
|
|
48
|
+
- `?pr=1` on the status route. PR/CI is asked for by the **chip only**, so a
|
|
49
|
+
sidebar that surveys twenty workspaces never spawns twenty `gh` processes. The
|
|
50
|
+
read is TTL-bounded (~90s per repository), refreshed out of band exactly like
|
|
51
|
+
the existing fetch, and notifies subscribers only when the state actually
|
|
52
|
+
changes.
|
|
53
|
+
- **PR/CI degrades to an absent field, never an error.** No `gh`, a logged-out
|
|
54
|
+
`gh`, a non-GitHub remote, an offline network, a rate limit, unparseable output
|
|
55
|
+
and a timed-out call all render the unchanged chip: a caller cannot tell "no PR"
|
|
56
|
+
from "no `gh`", because neither changes what should be displayed.
|
|
57
|
+
- A `runCli` helper the git and `gh` invocations now share, so `gh` inherits the
|
|
58
|
+
same timeout / missing-binary / exit-code semantics (and `GIT_TERMINAL_PROMPT`'s
|
|
59
|
+
analogue, `GH_PROMPT_DISABLED`, keeps an auth prompt from hanging a refresh).
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- The `detail=1` payload has a consumer at last. Its annotation said "no surface
|
|
64
|
+
consumes this yet"; the input chip's hover card is that surface, so the
|
|
65
|
+
branch/last-commits/stash fields are no longer speculative.
|
|
66
|
+
- `config` gains `prStatus` (`"auto"` / `"off"`), `prTtlMs`, `prTimeoutMs` and a
|
|
67
|
+
`prRunner` test seam mirroring `gitRunner`.
|
|
68
|
+
|
|
69
|
+
## [0.7.0] - 2026-09-12
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
|
|
73
|
+
- **Both surfaces draw the same status mark.** The input chip no longer leads with
|
|
74
|
+
a 🔴/🟡/🟢 emoji dot: it renders the SAME SVG mark as the sidebar row — a
|
|
75
|
+
filled **circle**, or a **tree** for a linked `git worktree` — so the shape says
|
|
76
|
+
*worktree or not* and the fill says *status*, identically wherever the badge
|
|
77
|
+
appears. The `⑂` worktree glyph is gone with the emoji: the tree shape already
|
|
78
|
+
says "worktree". The mark is drawn rather than typed because 🌳 is a **colour
|
|
79
|
+
emoji** — CSS cannot tint its leaves — and the fill comes from the app's own
|
|
80
|
+
`--dsw-alias-state-{success,warn,error}-primary` tokens, so the mark follows
|
|
81
|
+
light/dark and custom themes, which the hardcoded emoji could not. Neither colour
|
|
82
|
+
nor shape is the only channel: the mark carries an accessible name.
|
|
83
|
+
- **The sidebar row shows status, not the branch.** The row keeps the workspace
|
|
84
|
+
name on the left and floats the status mark to the right, with a worktree's
|
|
85
|
+
directory name beside it; the `|` separator is gone. The branch, sync counts and
|
|
86
|
+
operation token stay on the input chip, the surface scoped to the current
|
|
87
|
+
conversation.
|
|
88
|
+
- **The status endpoint no longer accepts a filesystem path.** The caller now says
|
|
89
|
+
*who it is* and the server resolves the workspace itself: `?session=<id>` for the
|
|
90
|
+
input chip, `?workspace=<id>` for a sidebar row. `?path=` is refused with
|
|
91
|
+
`target-required`; unresolvable ids return `session-not-found` or
|
|
92
|
+
`workspace-not-found`. The path was previously caller-chosen (allowlist-bounded
|
|
93
|
+
and read-only, but still chosen by the caller) on a route that answers without
|
|
94
|
+
GUI authentication.
|
|
95
|
+
- A session is resolved through the workspace registry first, so a conversation
|
|
96
|
+
that is no longer live still renders a badge. The live session's directory is
|
|
97
|
+
consulted only for the window before a new session is attached to its workspace,
|
|
98
|
+
and the registry still validates it.
|
|
99
|
+
- The client half no longer requires the `workspaces` service — declared services
|
|
100
|
+
are now just `["slots"]` — and change notifications carry the workspace id, so
|
|
101
|
+
subscribers match on identity rather than on a path.
|
|
102
|
+
|
|
103
|
+
### Added
|
|
104
|
+
|
|
105
|
+
- **A client-half test suite, plus submodule coverage.** `test/client.test.mjs`
|
|
106
|
+
loads the real `lib/client.js` behind a stub module loader and renders both
|
|
107
|
+
registered surfaces, so the mark's shape/colour rules, the row's "never show the
|
|
108
|
+
branch" rule, the right-float and the worktree-name suppression are asserted in
|
|
109
|
+
CI rather than eyeballed. The node half gains a real-submodule fixture: a
|
|
110
|
+
submodule is asserted **not** to be a worktree (its gitfile alone must not decide
|
|
111
|
+
it — worktree-ness is the `commondir` marker) while its out-of-tree git dir is
|
|
112
|
+
still watched, like a linked worktree's.
|
|
113
|
+
- **The input chip now says which working tree a conversation is in.** A linked
|
|
114
|
+
`git worktree`'s directory name is appended after the branch —
|
|
115
|
+
`🌳 main hotfix-tree ↑0 ↓2 ✎3` — because several worktrees of one repository
|
|
116
|
+
otherwise render identical chips and the chip carries no other workspace
|
|
117
|
+
identity. The name is dropped when the branch already implies it (`repo-feat-x`
|
|
118
|
+
on `feat-x` shows just the tree), a main checkout appends nothing, and the
|
|
119
|
+
response reports `isWorktree` / `worktreeName` from one stat of the git dir the
|
|
120
|
+
existing `rev-parse` already returned — no extra git invocation. Naming the
|
|
121
|
+
worktree is chip-only, like the operation token. The value is a directory NAME,
|
|
122
|
+
never a path.
|
|
123
|
+
- **A server-side poll fallback for workspaces whose file watcher failed.** A
|
|
124
|
+
workspace whose recursive `fs.watch` could not be established, or which later
|
|
125
|
+
errored, is polled every 5s on a state key (status + refs fingerprint +
|
|
126
|
+
in-progress operation) instead of depending only on the client's 60s fallback.
|
|
127
|
+
- `seam/apply.sh status` — reports patched / pristine / upstream-landed / drifted
|
|
128
|
+
by hash, exit 1 on drift with the exact rebuild commands.
|
|
129
|
+
|
|
130
|
+
### Fixed
|
|
131
|
+
|
|
132
|
+
- **The input chip now refreshes on change events.** Change events identify the
|
|
133
|
+
*workspace*, and the client matched them against its own target id. A sidebar row
|
|
134
|
+
targets by workspace id so rows kept working, but the chip targets by session id,
|
|
135
|
+
which can never equal a workspace id — so its handler never fired and the chip
|
|
136
|
+
only updated when it remounted (switching conversations) or on the 60s fallback
|
|
137
|
+
poll. The status response now echoes the resolved workspace id and the client
|
|
138
|
+
matches on that. This was a regression in the server-side resolution change.
|
|
139
|
+
- **A linked worktree's badge only refreshed on a file edit.** The watcher watched
|
|
140
|
+
the workspace root, but a linked worktree's git dir lives OUTSIDE it
|
|
141
|
+
(`<main>/.git/worktrees/<name>`), so `git add`, `git commit` and `git checkout`
|
|
142
|
+
there wrote nothing the root watch could see: the badge stayed stale until a
|
|
143
|
+
worktree file changed or the client's 60s poll fired. The per-worktree git dir
|
|
144
|
+
now gets its own watcher, resolved from the `.git` gitfile (which covers
|
|
145
|
+
submodules too), and either watcher erroring drops both and hands over to the
|
|
146
|
+
degraded poll. A regression test that stages a file written before the watch
|
|
147
|
+
existed fails without the second watcher.
|
|
148
|
+
- **The TTL fetch no longer delays the status response.** `ahead`/`behind` are
|
|
149
|
+
read from the local remote-tracking ref, and the fetch that refreshes it was
|
|
150
|
+
awaited before answering, so any request arriving after the 60s TTL had lapsed
|
|
151
|
+
paid a full network fetch first (~3.3s measured) — which is the first request
|
|
152
|
+
after every TTL window, i.e. constantly while editing. Badge updates triggered
|
|
153
|
+
by an edit took seconds for a repo with a remote. The response is now served
|
|
154
|
+
from the refs at hand and the fetch runs out of band; when it succeeds it
|
|
155
|
+
notifies subscribers, which reconverge in ~40ms. Same call, expired TTL: 27ms.
|
|
156
|
+
The trade is that `ahead`/`behind` can lag by up to one fetch — branch, dirty
|
|
157
|
+
state and file counts are never delayed.
|
|
158
|
+
- The plugin's `surfaces:` console line reported the boot-time race instead of the
|
|
159
|
+
outcome: it printed `sidebar rows = off (seam absent)` while row badges were
|
|
160
|
+
visibly rendering, because it sampled the seam declaration before the workspace
|
|
161
|
+
browser published it. It now reports from the registration callback, and the
|
|
162
|
+
boot-time line says it is awaiting the seam rather than declaring it missing.
|
|
163
|
+
|
|
164
|
+
## [0.6.0] - 2026-09-12
|
|
165
|
+
|
|
166
|
+
### Fixed
|
|
167
|
+
|
|
168
|
+
- **`✎n` under-counted new files.** Git collapses a new directory into a single
|
|
169
|
+
status entry, so adding a folder of 3 files displayed `✎1`. Untracked files are
|
|
170
|
+
now counted individually. If walking a very large untracked tree exceeds the time
|
|
171
|
+
budget, the badge falls back to the collapsed count and reports which mode
|
|
172
|
+
answered (`untrackedMode`) instead of silently showing a wrong number.
|
|
173
|
+
|
|
174
|
+
### Added
|
|
175
|
+
|
|
176
|
+
- **An in-progress operation token.** A paused merge, rebase, cherry-pick, revert,
|
|
177
|
+
bisect or sequencer run now appears after the branch name —
|
|
178
|
+
`🟡 main ⚔rebase ↑0 ↓2 ✎3`. A rebase whose conflicts are already staged has no
|
|
179
|
+
unmerged files, so the three-state dot could not express it. The dot contract
|
|
180
|
+
itself is unchanged, and the token is chip-only.
|
|
181
|
+
- **A 41-test node-half suite** (`npm test`) covering the status parser, git
|
|
182
|
+
plumbing against real repositories, request routing, SSE framing and the
|
|
183
|
+
filesystem watcher — runnable without booting DSH.
|
|
184
|
+
- **A CI test workflow** running the suite on Node 20/22/24.
|
|
185
|
+
|
|
186
|
+
### Changed
|
|
187
|
+
|
|
188
|
+
- Publishing runs through **npm trusted publishing** (OIDC) on a published
|
|
189
|
+
release, gated on the suite, with SLSA provenance — no long-lived token.
|
|
190
|
+
- Event stream hardening: `x-accel-buffering: no`, named `change` frames, cleanup
|
|
191
|
+
on disconnect, and an unload disposer that also releases the filesystem watchers.
|
|
192
|
+
- `detail=1` is annotated as having no consumer yet (the hover card remains
|
|
193
|
+
pending). Its comment previously claimed `log -1` while the code ran `log -3`.
|
|
194
|
+
|
|
195
|
+
[Unreleased]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/compare/v0.8.0...HEAD
|
|
196
|
+
[0.8.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.8.0
|
|
197
|
+
[0.7.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.7.0
|
|
198
|
+
[0.6.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.6.0
|
package/README.md
CHANGED
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Git status badges for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness):
|
|
4
4
|
|
|
5
|
-
- **Input-row chip** —
|
|
6
|
-
- **
|
|
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
|
+
- **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
|
+
- **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. The hover card and PR token are chip-only, so they never need the seam.
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
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.)
|
|
11
|
+
|
|
12
|
+
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 (plus optionally one `gh` call per ~90s, chip only). The status API takes **no path**: the caller passes a session id (chip) or workspace id (row) and the server resolves the directory itself.
|
|
9
13
|
|
|
10
14
|
## Install
|
|
11
15
|
|
|
@@ -13,10 +17,14 @@ Event-driven freshness: working trees are watched and updates push over SSE —
|
|
|
13
17
|
dsh plugin --profile web add dsh-git-badge
|
|
14
18
|
```
|
|
15
19
|
|
|
16
|
-
Restart the web process, refresh the browser. Requires Node ≥ 20 and `git` on PATH.
|
|
20
|
+
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.
|
|
17
21
|
|
|
18
22
|
Seam details: [github.com/Kevin-McIsaac/dsh-workspace-git-badge](https://github.com/Kevin-McIsaac/dsh-workspace-git-badge) (SEAM.md / PR.md).
|
|
19
23
|
|
|
24
|
+
## Changelog
|
|
25
|
+
|
|
26
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
27
|
+
|
|
20
28
|
## License
|
|
21
29
|
|
|
22
30
|
MIT
|