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/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "dsh-git-badge",
3
- "version": "0.14.1",
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
  }
@@ -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