dsh-git-badge 0.14.1 → 0.16.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 +165 -0
- package/README.md +2 -2
- package/lib/client.js +604 -72
- package/lib/index.js +387 -187
- package/package.json +8 -3
- package/seam/checkout.js +82 -0
- package/seam/store.js +66 -1
- package/skill/gh/SKILL.md +46 -0
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-git-badge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Git status badges for DeepSeek Harness: an input-row chip on every install, plus sidebar session-row badges (git status, PR/CI action tokens) after one manual step: `npx dsh-git-badge apply`, then restart dsh web. Event-driven freshness over SSE.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"dsh-git-badge-seam": "seam/apply.js"
|
|
8
|
+
"dsh-git-badge-seam": "seam/apply.js",
|
|
9
|
+
"dsh-git-badge-checkout": "seam/checkout.js"
|
|
9
10
|
},
|
|
10
11
|
"exports": {
|
|
11
12
|
".": "./lib/index.js",
|
|
@@ -56,10 +57,14 @@
|
|
|
56
57
|
"package.json",
|
|
57
58
|
"seam/anchors.js",
|
|
58
59
|
"seam/apply.js",
|
|
60
|
+
"seam/checkout.js",
|
|
59
61
|
"seam/postinstall.js",
|
|
60
62
|
"seam/skill.js",
|
|
61
63
|
"seam/store.js",
|
|
62
64
|
"seam/stub-index.js",
|
|
63
65
|
"skill/gh/SKILL.md"
|
|
64
|
-
]
|
|
66
|
+
],
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"@deepseek-ai/schemastery": "^3.18.2"
|
|
69
|
+
}
|
|
65
70
|
}
|
package/seam/checkout.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* dsh-git-badge-checkout — tell the badge which checkout THIS session is working
|
|
4
|
+
* in.
|
|
5
|
+
*
|
|
6
|
+
* WHY THIS EXISTS: a dsh session's cwd is immutable creation metadata — it is
|
|
7
|
+
* the main checkout forever, even after the agent does its work in a linked
|
|
8
|
+
* worktree. The badge therefore cannot know, on its own, that a session moved
|
|
9
|
+
* into `.wt/<name>`; it can only infer a worktree from an open pull request.
|
|
10
|
+
* This command closes that gap: the agent registers the checkout explicitly, and
|
|
11
|
+
* the node half prefers the registration over inference.
|
|
12
|
+
*
|
|
13
|
+
* The registration is advisory and self-expiring (24h) — never let it fail the
|
|
14
|
+
* work that earned it:
|
|
15
|
+
*
|
|
16
|
+
* usage: npx dsh-git-badge-checkout <path> register this session
|
|
17
|
+
* npx dsh-git-badge-checkout --clear forget this session
|
|
18
|
+
*
|
|
19
|
+
* The path is validated as a real worktree of its repository before it is
|
|
20
|
+
* written, so a typo cannot point the badge at an unrelated directory.
|
|
21
|
+
*/
|
|
22
|
+
import { execFileSync } from "node:child_process";
|
|
23
|
+
import { basename, resolve } from "node:path";
|
|
24
|
+
import { clearSessionCheckout, writeSessionCheckout } from "./store.js";
|
|
25
|
+
|
|
26
|
+
function usage(message) {
|
|
27
|
+
if (message !== void 0) console.error(message);
|
|
28
|
+
console.error("usage: dsh-git-badge-checkout <path> | --clear");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Worktree paths git lists for the repo containing `dir`. */
|
|
32
|
+
function worktreePaths(dir) {
|
|
33
|
+
const out = execFileSync("git", ["-C", dir, "worktree", "list", "--porcelain"], { encoding: "utf8" });
|
|
34
|
+
return out
|
|
35
|
+
.split("\n")
|
|
36
|
+
.filter((line) => line.startsWith("worktree "))
|
|
37
|
+
.map((line) => resolve(line.slice("worktree ".length).trim()));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function branchOf(dir) {
|
|
41
|
+
try {
|
|
42
|
+
return execFileSync("git", ["-C", dir, "rev-parse", "--abbrev-ref", "HEAD"], { encoding: "utf8" }).trim();
|
|
43
|
+
} catch {
|
|
44
|
+
return "unknown";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const sessionId = process.env.DSH_SESSION_ID;
|
|
49
|
+
if (sessionId === void 0 || sessionId === "") {
|
|
50
|
+
usage("no DSH_SESSION_ID in the environment — run this from inside a dsh session.");
|
|
51
|
+
process.exit(2);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const arg = process.argv[2];
|
|
55
|
+
if (arg === "--clear") {
|
|
56
|
+
clearSessionCheckout(sessionId);
|
|
57
|
+
console.log("[dsh-git-badge] checkout registration cleared for this session.");
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
if (arg === void 0 || arg === "") {
|
|
61
|
+
usage();
|
|
62
|
+
process.exit(2);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const path = resolve(arg);
|
|
66
|
+
let paths;
|
|
67
|
+
try {
|
|
68
|
+
paths = worktreePaths(path);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error(`[dsh-git-badge] ${path} is not a git worktree: ${String(error?.message ?? error)}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
if (!paths.includes(path)) {
|
|
74
|
+
console.error(`[dsh-git-badge] ${path} is not a worktree of its repository (git lists: ${paths.join(", ")})`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!writeSessionCheckout(sessionId, path)) {
|
|
79
|
+
console.error("[dsh-git-badge] could not write the registration; the badge keeps its inferred checkout.");
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
console.log(`[dsh-git-badge] this session's checkout is now ${basename(path)} (${branchOf(path)}).`);
|
package/seam/store.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* Two out-of-band stores live here, both keyed files under dataDir():
|
|
3
|
+
*
|
|
4
|
+
* restart-pending.json install/applied a host patch → the UI offers a restart
|
|
5
|
+
* session-checkouts.json which checkout a SESSION is working in — the one
|
|
6
|
+
* fact dsh cannot supply (a session's cwd is immutable
|
|
7
|
+
* creation metadata and always the main checkout, so a
|
|
8
|
+
* session working in a linked worktree is invisible
|
|
9
|
+
* without an explicit registration)
|
|
10
|
+
*
|
|
2
11
|
* The restart-pending marker — the bridge from install-time (node side) to the
|
|
3
12
|
* running UI.
|
|
4
13
|
*
|
|
@@ -68,4 +77,60 @@ export function clearRestartMarker() {
|
|
|
68
77
|
} catch {
|
|
69
78
|
// Absent is the goal; any error leaves the marker to be cleared later.
|
|
70
79
|
}
|
|
71
|
-
}
|
|
80
|
+
}
|
|
81
|
+
/** How long a session-checkout registration stays trustworthy. */
|
|
82
|
+
export const SESSION_CHECKOUT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
83
|
+
|
|
84
|
+
function sessionCheckoutsPath() {
|
|
85
|
+
return join(dataDir(), "session-checkouts.json");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Every registration, `{ sessionId: { path, at } }`. Never throws. */
|
|
89
|
+
export function readSessionCheckouts() {
|
|
90
|
+
try {
|
|
91
|
+
if (!existsSync(sessionCheckoutsPath())) return {};
|
|
92
|
+
const raw = JSON.parse(readFileSync(sessionCheckoutsPath(), "utf8"));
|
|
93
|
+
return raw !== null && typeof raw === "object" && typeof raw.sessions === "object" && raw.sessions !== null
|
|
94
|
+
? raw.sessions
|
|
95
|
+
: {};
|
|
96
|
+
} catch {
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Register the checkout a session is working in. Written by the agent (the
|
|
103
|
+
* git-worktree and /gh checkout paths), read by the node half.
|
|
104
|
+
*/
|
|
105
|
+
export function writeSessionCheckout(sessionId, path) {
|
|
106
|
+
try {
|
|
107
|
+
mkdirSync(dataDir(), { recursive: true });
|
|
108
|
+
const sessions = readSessionCheckouts();
|
|
109
|
+
sessions[sessionId] = { path, at: new Date().toISOString() };
|
|
110
|
+
writeFileSync(
|
|
111
|
+
sessionCheckoutsPath(),
|
|
112
|
+
JSON.stringify({ schema: "dsh-git-badge/session-checkout/v1", sessions }),
|
|
113
|
+
"utf8",
|
|
114
|
+
);
|
|
115
|
+
return true;
|
|
116
|
+
} catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Forget one session's registration (worktree removed, or the session left). */
|
|
122
|
+
export function clearSessionCheckout(sessionId) {
|
|
123
|
+
try {
|
|
124
|
+
const sessions = readSessionCheckouts();
|
|
125
|
+
if (!(sessionId in sessions)) return true;
|
|
126
|
+
delete sessions[sessionId];
|
|
127
|
+
writeFileSync(
|
|
128
|
+
sessionCheckoutsPath(),
|
|
129
|
+
JSON.stringify({ schema: "dsh-git-badge/session-checkout/v1", sessions }),
|
|
130
|
+
"utf8",
|
|
131
|
+
);
|
|
132
|
+
return true;
|
|
133
|
+
} catch {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
package/skill/gh/SKILL.md
CHANGED
|
@@ -59,6 +59,52 @@ unstaged / untracked / unmerged), plus any in-progress operation marker
|
|
|
59
59
|
- **`/gh checks <n>`** — `gh pr checks <n>` and summarise: what fails, what is
|
|
60
60
|
pending, and the single most useful next step.
|
|
61
61
|
|
|
62
|
+
- **`/gh sync`** — the branch has diverged (local commits AND upstream
|
|
63
|
+
changes): `git fetch`, then rebase the local commits onto
|
|
64
|
+
`upstream/<branch>`, then `git push --force-with-lease`. The force-push
|
|
65
|
+
ALWAYS needs an explicit user yes first, with a one-line summary of what
|
|
66
|
+
would move.
|
|
67
|
+
|
|
68
|
+
- **`/gh merge <n>`** — squash-merge pull request `<n>` (`gh pr merge <n>
|
|
69
|
+
--squash`), the repo's stated convention. Confirm with the user before
|
|
70
|
+
merging; afterwards report the merge and offer to update the local main.
|
|
71
|
+
|
|
72
|
+
- **`/gh stash pop`** — with stashes present: show the top entry's subject,
|
|
73
|
+
confirm with the user, then `git stash pop`. If pop conflicts, report the
|
|
74
|
+
conflict verbatim and note that the stash is NOT dropped — suggest
|
|
75
|
+
`git stash apply` as the retry that keeps it.
|
|
76
|
+
|
|
77
|
+
- **`/gh checkout <branch>`** — switch to an existing local branch. Establish
|
|
78
|
+
state first: uncommitted changes may block or travel with the checkout — say
|
|
79
|
+
what is dirty and confirm the switch when work would carry over. Unknown
|
|
80
|
+
branch → say so and list the closest local names.
|
|
81
|
+
|
|
82
|
+
If the switch lands you in — or out of — a linked worktree, register where you
|
|
83
|
+
are so the session badge follows the right checkout:
|
|
84
|
+
`npx dsh-git-badge-checkout "$(git rev-parse --show-toplevel)"` (best effort;
|
|
85
|
+
ignore failure). Clearing back to the main checkout is the same command run
|
|
86
|
+
against the main worktree — the badge treats the main checkout as the default.
|
|
87
|
+
A session's cwd is fixed at creation, so this registration is the ONLY signal
|
|
88
|
+
the badge has.
|
|
89
|
+
|
|
90
|
+
- **`/gh order`** — reconfigure which action class the badge ranks first. The
|
|
91
|
+
order lives in the `git-badge` settings namespace, edited in the UI at
|
|
92
|
+
**Settings → Plugins → Git Badge** (an ordered list with up/down controls).
|
|
93
|
+
When the request comes through the agent instead, read the current value from
|
|
94
|
+
the settings document (`~/.dsh/settings.yaml`, the `git-badge:` section) and
|
|
95
|
+
change that `order` list — any subset, any order; unlisted categories rank
|
|
96
|
+
after in default order. Never invent categories. The older
|
|
97
|
+
`~/.dsh/git-badge-next.json` is only a fallback for hosts without the
|
|
98
|
+
settings stack; do not edit it when the namespace is present. Typical
|
|
99
|
+
invocations: "/gh order commit first", "/gh order never suggest checks
|
|
100
|
+
before push".
|
|
101
|
+
|
|
102
|
+
## Handoff
|
|
103
|
+
|
|
104
|
+
One verb per invocation is this skill's whole job. Anything multi-step around a
|
|
105
|
+
pull request — review, merge, verify it landed, clean up the branch — belongs to
|
|
106
|
+
the `gh-pr` skill; hand off rather than improvise a lifecycle.
|
|
107
|
+
|
|
62
108
|
## Hard rules
|
|
63
109
|
|
|
64
110
|
- Never `git reset --hard`, `git clean`, `git checkout -- .`, `git push
|