🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

Git 侧栏面板 Ckarefulon/dsh-lite-git

DeepSeek 客户端
Ckarefulon11 天前有提交已提供中文文档扫描:含敏感能力兼容 / 相关生态

给 DSH 用的 GitHub Desktop 风格 Git 面板——装在 DSH 自带的右侧栏里,和「文件 / 文档…

30.7/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/22 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

相关生态插件(非 dsh 原生,按其对应运行时安装)
git clone https://github.com/Ckarefulon/dsh-lite-git.git

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
生态应用
桌面端 / Web 外壳,不以 dsh plugin add 安装
装得上吗
本站已在 dsh 0.2.1-rc.5 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 11 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件发起外部网络请求
查看扫描证据(共 23 处,展示前 22 条)
  • 发起网络请求
    lib/client.js:1188
    response = await fetch(RPC_ROUTE, {
  • 读取本地文件
    lib/host/open.js:179
    stats = await stat(target)
  • 读取本地文件
    lib/host/repo.js:122
    stats = await stat(absoluteIn(repoRoot, path))
  • 读取本地文件
    lib/host/repo.js:133
    bytes = await readFile(absoluteIn(repoRoot, path))
  • 读取本地文件
    lib/host/repo.js:202
    return (await stat(join(dir, 'FETCH_HEAD'))).mtime.toISOString()
  • 读取本地文件
    lib/host/repo.js:239
    await stat(candidate)
  • 发起网络请求
    lib/host/repo.js:518
    async function fetch(repoRoot, signal) {
  • 删除本地文件
    lib/host/repo.js:764
    await rm(absoluteIn(repoRoot, file.path), { force: true })
  • 读取本地文件
    lib/host/repo.js:799
    const remaining = await readdir(directory)
  • 删除本地文件
    lib/host/repo.js:800
    if (remaining.length === 0) await rm(directory, { force: true })
  • 读取本地文件
    lib/host/repo.js:830
    (await stat(join(current.path, '.git')).then(
  • 发起网络请求
    lib/host/routes.js:225
    const outcome = kind === 'push' ? await repo.push(repoRoot, request.signal) : await repo.fetch(repoRoot, request.signal)
  • 发起网络请求
    lib/host/routes.js:570
    async fetch(request) {
  • 读取本地文件
    lib/host/store.js:145
    const parsed = JSON.parse(await readFile(path, 'utf8'))
  • 写入本地文件
    lib/host/store.js:157
    await mkdir(dirname(path), { recursive: true })
  • 写入本地文件
    lib/host/store.js:158
    await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, 'utf8')
  • 写入本地文件
    lib/host/store.js:159
    await rename(temporary, path)
  • 写入本地文件
    lib/host/store.js:336
    await mkdir(trashDir(), { recursive: true })
  • 写入本地文件
    lib/host/store.js:337
    await writeFile(join(trashDir(), blobName), diffText, 'utf8')
  • 读取本地文件
    lib/host/store.js:364
    return await readFile(join(trashDir(), blob), 'utf8')
  • 删除本地文件
    lib/host/store.js:376
    await rm(join(trashDir(), name), { force: true })
  • 读取本地文件
    lib/host/store.js:410
    for (const name of await readdir(trashDir())) {
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/schemastery
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

DSH 右侧栏的 GitHub Desktop 风格 Git 面板:勾选暂存、提交、提交并推送、丢弃到回收站,还有 diff 预览。

README

由 DeepSeek 最新模型翻译生成
dsh-lite-git 给 DSH 用的 GitHub Desktop 风格 Git 面板——装在 DSH 自带的右侧栏里,和「文件 / 文档 / 聊天」并排。 变更文件列表、勾选提交、Summary + Description、Commit / Commit and Push、一个会随分支状态变化的 Push/Fetch 按钮、舍弃更改进插件自己的回收站、双击看 diff——都在这一栏里。 English: README.en.md 0.7.0 起改名为 dsh-lite-git(原 dsh-gitbar-lite)。包的 id、RPC 路由、状态目录都跟着换了,所以升级时要把旧的卸掉: dsh plugin --profile <你的 profile> remove dsh-gitbar-lite,再按下面的「安装」装新的; 旧的 <DSH_HOME>/dsh-gitbar-lite/(仓库绑定、脚本、回收站)可以整个搬到 <DSH_HOME>/dsh-lite-git/ 继续用。 为什么是「右侧栏的一个 tab」 DSH 的右栏本来就是一个常驻的标签区。Git 面板放这里,意味着它和会话是同一份上下文:当前会话的工作区就是它看的仓库,切换会话就跟着切,不用再挑一次目录。
展开完整文档(README 剩余 1 节)
dsh-lite-git

给 DSH 用的 GitHub Desktop 风格 Git 面板——装在 DSH 自带的右侧栏里,和「文件 / 文档 / 聊天」并排。

变更文件列表、勾选提交、Summary + Description、Commit / Commit and Push、一个会随分支状态变化的 Push/Fetch 按钮、舍弃更改进插件自己的回收站、双击看 diff——都在这一栏里。

English: README.en.md

0.7.0 起改名为 dsh-lite-git(原 dsh-gitbar-lite)。包的 id、RPC 路由、状态目录都跟着换了,所以升级时要把旧的卸掉:
dsh plugin --profile <你的 profile> remove dsh-gitbar-lite,再按下面的「安装」装新的;
旧的 <DSH_HOME>/dsh-gitbar-lite/(仓库绑定、脚本、回收站)可以整个搬到 <DSH_HOME>/dsh-lite-git/ 继续用。

为什么是「右侧栏的一个 tab」

DSH 的右栏本来就是一个常驻的标签区。Git 面板放这里,意味着它和会话是同一份上下文:当前会话的工作区就是它看的仓库,切换会话就跟着切,不用再挑一次目录。

面板很窄(右栏最小 300px,窄视口下会自动全屏),所以它是按窄栏设计的:文件列表单列、路径按「文件名 + 目录」两级显示、提交框只留一个 Summary 输入框加一个可展开的小三角。

功能

| 功能 | 说明 |
| 变更文件列表 | 修改 / 新增 / 删除 / 重命名 / 未跟踪都能识别,每行带 +n −n 行数统计 |
| 勾选 | 勾选就是真的 git add,取消就是 git restore --staged。终端里 git status 看到的就是同一件事 |
| 半暂存 | 一个文件索引和工作区都有改动时,勾选框显示成「半选」,点一下把它整体重述为勾上或没勾 |
| 全选 | 列表标题左边一个三态框:全选 / 取消全选,部分勾选时显示半选。冲突文件不参与,和自动勾选同一条规则 |
| 自动勾选 | 刷新时不看上一次,只看这一次新出现的:列表重读之后,屏幕上新冒出来的改动自动勾上,已经在列表里的文件保持你留下的样子——所以刷新不会把你刚取消勾的文件勾回来。冲突文件、半暂存的文件不参与。第一次打开(或提交完)沿用老规则:索引里一个文件都没有才全部勾上;索引非空说明别处已经做过选择,不动 |
| Summary / Description | Summary 常驻一行;最右边一个小三角展开 Description |
| Commit | 只提交勾上的(因为勾选写进了索引) |
| Commit and Push | 提交后立刻推送;推送失败不会说「提交失败」——提交已经发生了,面板会明说「已提交 xxx,但推送失败」 |
| 上下文按钮 | 有本地未推送提交时是 Push,没有上游时是 Publish,否则是 Fetch |
| Push 前先 Fetch | 推送前会先 git fetch --prune,然后才推。已经落后就拒绝推并说明落后几个——git 的 push 从不合并,早晚都是被拒,先知道比推完再被拒有用。fetch 本身失败不阻断推送 |
| 自动 Fetch | 面板可见且仓库有远端时按 GitHub Desktop 的口径自己抓:默认 1 小时,每次到点重新判断,下一次从上一次完成后开始排,并加随机抖动防止多个面板同步。节奏是 autoFetchMs(0 关掉),并且用 FETCH_HEAD 的 mtime 判断"是不是刚抓过"——你在终端里抓也算 |
| 面板获得焦点 | 每次重新看向面板都刷新一遍列表,并跑一遍脚本填 Summary:打开面板、切回这个标签、从对话点回面板、切回 DSH 窗口、浏览器标签回来,都算。手写的 Summary 不会被覆盖——只有空着的、或者还留着上次脚本填的那句话时才自动填 |
| 舍弃更改 | 单个文件或「全部舍弃」;动手前先把这次改动的 diff 归档,确认框里可以勾「彻底删除」 |
| 回收站 | 独立 tab。每次舍弃存一份 diff,并记下是对哪个提交(abc1234 初始提交)取的 diff |
| diff 预览 | 双击文件,或点回收站里的一条,在右栏开一个 diff 标签页——永远只占一个,再点就复用 |
| 用别的程序打开 | diff 页右上角一个「打开」按钮,用上次那个程序打开这个文件;旁边的小箭头列出所有能选的:系统默认程序、记事本、在文件夹中显示。程序在宿主那台机器上启动(所以手机远程看的时候,窗口开在电脑上),面板会给一句「已用 记事本 打开」。想加自己的编辑器就在配置里写 openWith |
| 仓库绑定 | 工作区本身是 Git 仓库就自动绑定并记住;不是就让你从工作区里扫出来的仓库里挑,或直接填路径 |

前置条件

| 需要什么 | 说明 |
| DSH >= 0.2.0-rc.2 | 右侧栏 tab API 是这一版的行为 |
| Node >= 22 | 宿主半边用到 AbortSignal.any 与 node:test |
| 本机有一个 git | 默认从 PATH 找;找不到时面板会明确说「本机找不到 git」,不会静默失败。装在别处可以用 gitPath 指定 |
| 凭据 | Push 用的是系统 git 的凭据(credential helper)。先在终端里成功 push 过一次,插件就跟着能推 |

插件不会自己存任何凭据。宿主半边不读 $DSH_HOME 里的密钥,也不写。

安装

dsh plugin --profile <你的 profile> add dsh-lite-git

装完重启 DSH。然后在一个会话里打开右侧栏,引导页上会多一张 「Git 更改」 卡片,点它就进面板。

从本地克隆安装 / 卸载 / 装完打不开怎么办

本地目录安装(改代码时用这个):

dsh plugin --profile desktop add link:/绝对路径/dsh-lite-git

卸载:

dsh plugin --profile <你的 profile> remove dsh-lite-git

装完 DSH 起不来:先跑 node scripts/check-client-bundle.mjs .。DSH 把
exports["./client"] 按精确文件路径读取,不做扩展名补全,也没有目录回退;指向不存在的文件会直接抛 MissingClientBundleError,中止整个 Host 启动,而不是「这个插件不显示」。这个脚本就是复刻那条判定,让你不必靠重启去试。

使用

右栏引导页 ┌──────────────────────────────────┐ │ ▸ Git 更改 │ ← 引导卡片,点开面板 │ 查看改动、勾选要提交的文件… │ └──────────────────────────────────┘ ↓ 点开 ┌──────────────────────────────────┐ │ dsh-lite-git main ↑1 ↓0 │ ← 仓库名 / 分支 / 领先落后
│ [Push] [↻] [🗄] [⑂] │ ← 上下文按钮、刷新、回收站、换绑
│ 更改 5 个文件 · 已勾选 2 [全部舍弃] │ │ ☑ M a.txt +3 −1 ↩│ ← 双击 = 看 diff;↩ = 舍弃这个文件 │ ▣ M src/big.ts +12 −4 ↩│ ← ▣ = 半暂存 │ ☐ ? new note.md +7 ↩│ │ ☐ D docs/old.md −31 ↩│ │ ☐ R moved.txt ↩│ │ ┌────────────────────────────┐ ˅ │ ← ˅ 展开 Description │ │ Summary(必填) │ │ │ └────────────────────────────┘ │
│ [ Commit ] [ Commit and Push ]│
└──────────────────────────────────┘

- 勾选:点一下写进索引,再点一下撤出。半选状态点一下会整体勾上,再点整体撤出。
- 双击文件行 → 右栏开 diff 标签页;再双击别的文件,复用同一个标签页。
- 舍弃(行尾 ↩ 或「全部舍弃」)→ 弹确认框:

舍弃「a.txt」的更改?
· a.txt ☐ 彻底删除(不存入回收站)这次的 diff 会存进插件的回收站,之后还能回看是改了什么。
[ 取消 ] [ 舍弃 ]

默认不勾「彻底删除」:这次改动的 diff 会存进回收站。勾上就什么都不留。这个勾选框会记住你上一次的选择。
- 回收站(🗄):按时间列出记录,每条显示「多久之前 · 单个文件/全部更改 · 文件数 · +n −n · 基于 abc1234 初始提交」。
点一条看 diff,点 ↺ 把它恢复回工作区(恢复出来的文件回到列表时是勾上的),行尾的 🗑 删掉它,右上「清空回收站」一次清掉(只清当前仓库,勾上「显示全部仓库」就清全部)。
- 换绑(⑂):解除当前绑定。工作区是仓库会重新自动绑定;不是的话回到选择界面。

舍弃到底做了什么

顺序是固定的,不能换:

1. 按当前改动生成 diff(已跟踪的文件走 git diff HEAD;未跟踪的新文件由插件自己合成一份 new-file diff,因为 git diff HEAD 看不见它们,而舍弃未跟踪文件就是删掉它——不先合成,回收站里就只剩一条「删了一个文件」的空记录)。
2. 存进回收站(除非勾了「彻底删除」)。
3. 已跟踪的 git restore --source=HEAD --staged --worktree;未跟踪的直接删文件,并把因此变空的目录一起清掉。

冲突中的文件会被拒绝舍弃,只给一句提示。在合并中途 git restore 到 HEAD 有扰动合并状态的风险,不值得为省一句话去冒。

用脚本填 Summary

脚本就在面板里写:展开 Description 的小三角,下面有一个「填 Summary 的脚本」按钮,点开是一个编辑框——写进去、保存、运行并填入,全在这里,不用去翻配置文件。编辑框一开始是空的,什么都不会预先填进去(占位提示里给了一个例子);保存、运行、刷新都会给一条短暂提示,不用猜刚才那一下到底按到没有。每次重新看向面板都会自动跑一次(打开面板、切回这个标签、从对话点回面板、切回 DSH 窗口都算)——把侧边栏开着,去对话里让 agent 干活,回来,Summary 已经填好了;不过你自己手写过的 Summary 不会被冲掉,那时想更新就点星星按钮。配了脚本之后,Summary 框旁边还会多一个星星按钮,一点就填。

脚本以仓库根目录为工作目录运行,不喂任何 stdin。所以「取 CHANGELOG 最顶上的版本号」就是:

grep -m1 '^## ' CHANGELOG.md | sed 's/^## //'

输出即 Summary。要同时填 Description,就在输出里单独放一行 ---,该行之后的全部内容是 Description:

echo "1.4.2"
echo "---"
echo "Bumped the patch version."

关于 shell:Windows 上优先用 Git 自带的 sh(grep/sed/awk 都可用),并把它的 usr/bin 补回 PATH——否则那些工具会「command not found」。机器上没有 sh 才退回 cmd.exe,那时脚本要按 cmd 语法写。非 Windows 就是 sh。

脚本失败不弹错误横幅,退出码会以一条短暂提示显示;它的 stderr 原样保留,方便你自己判断。

脚本是按仓库存的(键是仓库路径,文件在 <DSH_HOME>/dsh-lite-git/scripts.json):A 项目配的脚本不会跑到 B 项目里。一个从没配过脚本的仓库,编辑框是空的——不预先填任何脚本,占位提示只给个例子;清空保存之后它也真的空着。

配置

| 字段 | 默认 | 说明 |
| gitPath | "" | 指定 git 可执行文件;空 = 从 PATH 找 |
| stateDir | "" | 插件状态目录;空 = <DSH_HOME>/dsh-lite-git |
| timeoutMs | 30000 | 单条 git 命令超时。推到慢远端可以调大 |
| maxDiffBytes | 524288 | 一份归档 diff 的上限;超出按行截断并记下丢了多少行 |
| maxFileBytes | 2097152 | 比这大的未跟踪文件只列出来,不读内容、不归档、不出 diff |
| trashLimit | 200 | 回收站保留多少条;超出丢弃最旧的连同它的 diff 文件 |
| scanDepth | 3 | 「不是仓库时」扫描工作区找仓库的目录深度 |
| autoFetchMs | 300000 | 面板自己多久 fetch 一次;0 = 关掉,只用手动按钮 |
| summaryScript | "" | 填 Summary 的脚本。以仓库根为工作目录运行,输出即 Summary;单独一行 --- 之后算 Description |
| openWith | [] | diff 页右上角「用…打开」的下拉里,额外多出来的程序。每项 { label, command, args },args 里 {path} 换成文件、{dir} 换成所在目录,都不写就把路径接在最后(例:- label: VS Code / command: code / args: ['{path}'])。每项已经是一个参数,不是 shell 命令行,所以带空格的路径不会被拆开。系统自带的那三个(系统默认程序 / 记事本 / 在文件夹中显示)不用写,一直都在 |

每个字段都是 volatile 的,所以在设置页改了立刻生效、不用重启。

状态存在哪:<DSH_HOME>/dsh-lite-git/(没有 DSH_HOME 就回落 ~/.dsh)—— bindings.json(工作区 → 仓库)、prefs.json(那两个勾选偏好)、trash/index.json + trash/<id>.diff。
回收站只存那一个 diff,不额外留一份文件副本:恢复时用 git apply 把它重放回去,那既是面板显示的记录,也是回放所需的全部内容;重放完再把这些路径 git add 进索引,所以恢复出来的文件回到列表时是勾上的。都是先写临时文件再 rename,所以写到一半崩了也不会把文件截断。

工作原理

lib/shared.js 线协议字面量(浏览器半边手抄一份,自检脚本负责比对) lib/index.js 插件入口:Config、apply、把路由挂到 connection.fetch lib/host/git.js git 执行层:走 ctx.subprocess,解析可执行文件、限流输出、清洗环境 lib/host/parse.js porcelain v2 / numstat 解析(纯函数) lib/host/diff.js diff 合成、按行截断、按文件切分(纯函数) lib/host/repo.js 仓库操作:status / stage / commit / push / fetch / fileDiff / discard lib/host/store.js 绑定、偏好、回收站(原子写 + 按文件串行化) lib/host/workspace.js 会话 → 工作区目录 lib/host/routes.js 单条 /api 路由 + action 分发 + 错误码映射 lib/client.js 浏览器半边:三个 tab 类型 + 面板/回收站/diff,手写、无构建

几个值得写下来的决定:

- 一条 /api 路由 + action 字段,不是一条路由一个操作。connection.fetch 按精确 pathname 匹配,而每次调用本来就要带 sessionId;分开写只会把十五个路径字面量摊到两种语言里。
- 必须挂在 connection.fetch 上,绝不挂 webServer 的 exact 路由:web server 会先匹配 exact 路由,而同路径的 exact 路由会遮蔽 /api 前缀路由——Host/Origin fence、浏览器 cookie 校验、operator 身份全在那条前缀路由上。
用 exact 就等于开了一个不需要鉴权的 git 接口。测试与自检脚本都专门盯这一条,并且反过来验证这条断言是有牙齿的。
- diff tab 用 dsh-resource:// 固定地址,不是 page kind。重新打开同一个资源地址会让宿主聚焦已开的那个 tab,并把新的 params 导航进去;page kind 只在当前 dock pane 内复用,被浮动出去就会多开一个。固定地址本身就是「永远只占一个」。
- 不发 -m,用 -F - 把提交信息从 stdin 送进去:Summary 里有引号、有换行、以 - 开头,都只是文本,不是参数。
- :(literal) pathspec:否则舍弃一个叫 docs 的文件会顺带动 docs/ 里的所有东西,a[1].txt 会被当成通配。
- 不 import @deepseek-ai/dsh-client-ui-primitives:浏览器半边只 require('react')。
一个会抛错的 import 会把整个 slot entry 变成空白,而官方插件开发清单也要求客户端半边不导入 Harness Client 包。
需要的那几个控件手写,颜色全部走 --dsw-alias- token;图标是逐 path 抄宿主 IconBranchOutlineRegular 等 artwork,所以它和旁边的宿主图标是一套东西。

更多细节(线协议全表、状态文件格式、为什么这么排序、哪些没验证)在 docs/CONTRACT.md。

验证

npm test # 单测 + 真 git 的 e2e + 产物自检
npm run test:e2e # 只跑真 git 的 e2e(23 个小节,各自造自己的夹具,294 项断言)
npm run check # 只用产物自检(不启 DSH 也能发现会让 DSH 起不来的问题)
npm run test:live # 探测一个正在运行的 DSH(不在 npm test 里,需要跑着应用)

| 套件 | 覆盖什么 |
| test/parse.test.mjs | porcelain v2 与 numstat 解析,含重命名、暂存删除、冲突、非 ASCII 路径、带空格的路径 |
| test/diff.test.mjs | diff 合成(含无尾换行、二进制、CRLF)、按行截断不切断多字节字符、按文件切分 |
| test/store.test.mjs | 绑定键归一、绑定/偏好/回收站往返、坏文件容错、并发写不丢更新 |
| test/index.test.mjs | Config 字段与默认值、路由载体与「只注册一次」、错误码映射 |
| test/client.test.mjs | 按 manifest 的确切路径加载真实 bundle;三个 tab 类型的 id/kind/guide/keepMounted;每个 slot key 都是注册过的 id;字典与错误码齐全;无 hook 的三个组件按渲染树断言 |
| test/seats.test.mjs | 把每个注册进来的 seat 都渲染一遍,含隐藏态和无 session 态。这是补盲区的:client.test.mjs 从不渲染三个 body,而 body 渲染期抛错就是整个面板变白(真实发生过——一个 const 被依赖数组提前引用,ReferenceError 打白了面板,而其余测试全绿) |
| test/focus.test.mjs | 唯一会跑 effect 的套件:拿真实 bundle 跑在一个小号 React 上(state / effect / 依赖比较 / ref),断言「重新看向面板」真的发生了——打开读一次、回来再读一次、冷却期内的连发只读一次、切走再切回也算——以及重读之后勾的是哪几个文件:新出现的才勾,取消过的保持没勾,索引非空时不覆盖别人的选择。别的套件都不跑 effect,所以这类 bug 它们看不见 |
| test/e2e.mjs | 真实 git、真实 handler、真实路由层:勾选真的写索引(之后读 git status 回来比对)、提交、push 到本地裸远端、推送被拒、舍弃单文件/全部/重命名/暂存删除、冲突拒绝、回收站增删清空、按仓库过滤、扫描深度 |
| test/check.mjs | manifest 指向的文件都存在且进得了 tarball、patch 层只有一行且没有密钥、两份线协议字面量一致、字典与 locale/.json 一致、每个 t('字面量') 都能查到、README/workflow 的存在性与内容 |
| scripts/check-client-bundle.mjs | 复刻 client-modules 的判定:exports["./client"] 指向的文件是否真的存在 |

没有真浏览器 harness,这是有意的:本插件不做任何 DOM 量测、不增强宿主 DOM、没有 portal 到 document.body 的东西,而那套夹具的忠实性本身就是负担(同家族的插件曾因为夹具抄错一条 CSS 规则,把 bug 固化成绿色)。
test/client.test.mjs 里无 hook 的三个组件是按渲染树断言的,覆盖了勾选状态、确认框默认值、候选人列表这些真正的 UI 规则。
如果以后 UI 长出依赖 :hover 或需要量测的东西,这里就该重新考虑——那条判断写进了 docs/CONTRACT.md。

已知限制

- 一个工作区绑一个仓库,一个仓库一次推一个远端(有 origin 就优先它)。不做子模块、worktree、分支切换、历史列表、stash。
- Fetch 用的是 git fetch --prune,会删掉远端已经删除的分支对应的 remote-tracking ref(和 GitHub Desktop 一致)。
- 提交会跑你自己的 commit hook。卡住的 hook 是你自己的 hook;timeoutMs 是唯一的兜底。
- 回收站只存那一个 diff。恢复是把它 git apply 回去,所以能恢复出一模一样的字节(CRLF、无尾换行、 unicode、行尾空格都验过)。但它只能在「工作区还是舍弃后的样子」时重放:HEAD 变了,或那些文件又被改过,按钮就变灰并说明原因——否则就是拿旧改动盖掉新改动。
恢复出来的文件回到列表时是勾上的:重放之后宿主会把这些路径 git add 进索引,和「任何改动都默认勾选」是同一条规则,也和本插件「勾选就是真的索引」一致——终端里 git status 看到的就是这样。
恢复成功后这条记录会被移除:它是舍弃的逆操作,放回去之后档案就没有用处了,而且再重放一次会把同一个改动应用两遍。
- 二进制文件不归档内容,只记一句 Binary files … differ。这很诚实,但那不是备份。
- 超过 maxFileBytes 的未跟踪文件被舍弃时,归档里只有一段说明。确认框目前不会特别提示这一点。
- 「舍弃未跟踪文件」是直接删除(不进系统回收站)——这是说好的设计:插件自己的回收站承接它,不勾「彻底删除」时能看到内容。
- 「用…打开」是在宿主那台机器上启动程序的:手机 / 另一台电脑远程看的时候,窗口开在持有仓库的那台机器上,不是开在你面前这块屏幕上。它也只作用于仓库里的文件:回收站记录的 diff、已经被删掉的文件都没有磁盘上的东西可以递过去。

发布(维护者)

版本号就是发布按钮:

1. 改 package.json 的 version。
2. 推到 main。

.github/workflows/publish.yml 会跑 npm test,然后 npm view <name>@<version>——该版本已经在线就静默跳过(绿色的 no-op,并提示你 bump)。所以漏改版本号不会静默重发,也不会变成一次红色失败。

认证是 npm 的 Trusted Publishing(OIDC):没有任何 token secret,id-token: write 就是全部凭据。

一次性配置:在 npmjs.com 上绑定 Trusted Publisher

包的页面 → Settings → Trusted Publisher,填:

| 字段 | 值 |
| Publisher | GitHub Actions |
| Repository | Ckarefulon/dsh-lite-git |
| Workflow filename | publish.yml |
| Environment | (留空) |
首次发布必须手工 npm publish——Trusted Publisher 只能绑到一个已经存在于 registry 上的包名。

工作流刻意不用 actions/setup-node 的 registry-url:它会往 ~/.npmrc 写
_authToken=${NODE_AUTH_TOKEN},而这个字面占位符会被当成凭据发出去。registry 由 publishConfig 提供。

另外它会先把 npm 升到 ^11.5.1:Trusted Publishing 是 npm CLI 的能力,Node 22 自带的 npm 10 不支持 OIDC 交换,在 npm 10 上会以 ENEEDAUTH 失败——那看起来像缺 token,其实是 npm 太旧。

许可

MIT,见 LICENSE。

「GitHub Desktop」是 GitHub, Inc. 的产品名称;本插件不是 GitHub 的产品,也与 GitHub 无关,只是借用了那种布局。
预览分享卡片长什么样 ↓Ckarefulon/dsh-lite-git
分享 · DeepSeek Harness Hub
Ckarefulon/dsh-lite-git

给 DSH 用的 GitHub Desktop 风格 Git 面板——装在 DSH 自带的右侧栏里,和「文件 / 文档…

回到顶部