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

GitHub 插件同步 YumenoSayuri/dsh-github-sync

DeepSeek Harness
YumenoSayuri21 天前有提交已提供中文文档扫描:含敏感能力

把工作区里的 DSH 插件按「干净副本」推送到各自独立的 GitHub 仓库:/git…

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

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

该插件未发布到 npm,按作者文档从 GitHub 源安装即可(静态检查只覆盖 npm 路径,不代表插件不可用)。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add YumenoSayuri/dsh-github-sync
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

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

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

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装

仓库 package.json 标记 private,未发布到 npm,需从源码安装

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包@local/dsh-github-sync(未发布到 npm,仅可源码安装)
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/9 17:36:42

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

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

可执行外部命令读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 48 处,展示前 46 条)
  • 读取本地文件
    main.mjs:111
    return JSON.parse(readFileSync(join(HERE, 'package.json'), 'utf8')).version ?? '0.0.0'
  • 读取本地文件
    core/config.js:186
    if (!existsSync(path)) return undefined
  • 读取本地文件
    core/config.js:189
    text = readFileSync(path, 'utf8')
  • 读取本地文件
    core/config.js:276
    const stat = statSync(path)
  • 读取密钥环境变量
    core/config.js:421
    const envToken = process.env[envName] ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN
  • 读取本地文件
    core/config.js:448
    bundle: existsSync(BUNDLE_CONFIG) ? BUNDLE_CONFIG : undefined,
  • 读取本地文件
    core/config.js:449
    user: existsSync(USER_CONFIG) ? USER_CONFIG : undefined,
  • 写入本地文件
    core/config.js:548
    mkdirSync(USER_DIR, { recursive: true })
  • 写入本地文件
    core/config.js:553
    writeFileSync(USER_CONFIG, `${JSON.stringify(next, null, 2)}\n`, 'utf8')
  • 写入本地文件
    core/config.js:572
    writeFileSync(USER_CONFIG, `${JSON.stringify(next, null, 2)}\n`, 'utf8')
  • 读取本地文件
    core/config.js:587
    if (existsSync(USER_CONFIG)) return USER_CONFIG
  • 写入本地文件
    core/config.js:588
    mkdirSync(USER_DIR, { recursive: true })
  • 写入本地文件
    core/config.js:612
    writeFileSync(USER_CONFIG, `${JSON.stringify(skeleton, null, 2)}\n`, 'utf8')
  • 读取本地文件
    core/sync.js:108
    if (existsSync(candidate)) return { ok: true, resolved: candidate }
  • 读取本地文件
    core/sync.js:203
    if (!looksLikePath(value) && !existsSync(candidate)) {
  • 写入本地文件
    core/sync.js:587
    writeFileSync(STATUS_FILE, `${JSON.stringify({ ...value, updatedAt: new Date().toISOString() }, null, 2)}\n`, 'utf8')
  • 读取本地文件
    core/discover.js:31
    text = readFileSync(join(dir, 'package.json'), 'utf8')
  • 读取本地文件
    core/discover.js:140
    info = statSync(absolute)
  • 读取本地文件
    core/discover.js:262
    exists = statSync(root).isDirectory()
  • 读取本地文件
    core/discover.js:273
    children = readdirSync(root, { withFileTypes: true })
  • 发起网络请求
    core/github.js:32
    response = await fetch(`${apiBase}${path}`, {
  • 读取本地文件
    core/git.js:44
    return readFileSync(join(manifest.dir, file.path), 'utf8')
  • 执行外部命令来自配置/变量
    core/git.js:169
    execFile(
  • 读取本地文件
    core/git.js:312
    entries = readdirSync(dir, { withFileTypes: true })
  • 读取本地文件
    core/git.js:324
    if (readdirSync(childAbs).length === 0) {
  • 删除本地文件递归
    core/git.js:325
    rmSync(childAbs, { recursive: true, force: true })
  • 删除本地文件
    core/git.js:331
    rmSync(childAbs, { force: true })
  • 写入本地文件
    core/git.js:344
    mkdirSync(dirname(target), { recursive: true })
  • 读取本地文件
    core/git.js:349
    const stat = statSync(target)
  • 写入本地文件
    core/git.js:357
    copyFileSync(item.sourcePath, target)
  • 读取本地文件
    core/git.js:363
    const existing = readFileSync(target, 'utf8')
  • 写入本地文件
    core/git.js:369
    writeFileSync(target, item.content, 'utf8')
  • 写入本地文件
    core/git.js:423
    mkdirSync(dirname(helperPath), { recursive: true })
  • 写入本地文件
    core/git.js:424
    writeFileSync(helperPath, credentialHelperSource(tokenFile), 'utf8')
  • 读取本地文件
    core/git.js:427
    const firstPublish = !existsSync(join(mirror, '.git'))
  • 写入本地文件
    core/git.js:428
    mkdirSync(dirname(mirror), { recursive: true })
  • 删除本地文件递归
    core/git.js:576
    rmSync(mirror, { recursive: true, force: true })
  • 读取本地文件
    core/license.js:79
    if (existsSync(cacheFile)) {
  • 读取本地文件
    core/license.js:81
    const cached = JSON.parse(readFileSync(cacheFile, 'utf8'))
  • 发起网络请求
    core/license.js:94
    response = await fetch(`${apiBase}/licenses/${encodeURIComponent(normalized)}`, {
  • 写入本地文件
    core/license.js:128
    mkdirSync(cacheDir, { recursive: true })
  • 写入本地文件
    core/license.js:129
    writeFileSync(cacheFile, `${JSON.stringify({ spdxId: json.spdx_id, name: json.name, body }, null, 2)}\n`, 'utf8')
  • 读取本地文件
    core/manifest.js:253
    children = readdirSync(absolute, { withFileTypes: true })
  • 读取本地文件
    core/manifest.js:261
    patterns = [...inherited, ...parseIgnoreFile(readFileSync(join(absolute, '.gitignore'), 'utf8'))]
  • 读取本地文件
    core/manifest.js:321
    info = statSync(childAbs)
  • 读取本地文件
    core/manifest.js:487
    text = readFileSync(absolute, 'utf8')
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

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

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 按需触发/git指令
  • token仅存本地
展开完整介绍(75 字)↓

把工作区里的 DSH 插件按「干净副本」推送到各自独立的 GitHub 仓库:/git 指令按需触发,平时不注入任何提示词,token 只存本地一次。

README

由 DeepSeek 最新模型翻译生成
GitHub 同步插件(@local/dsh-github-sync) 专为 DSH 插件开发者设计:把本地工作区里的插件作为干净副本推送到各自独立的 GitHub 仓库,并给新仓库打上 dsh-plugin topic。 核心设计与特性 1. 绝对防误触 / 双阶段确认机制(自然交互 + 零日常污染): - 平时不注入任何系统提示词,完全不消耗日常聊天的 Context 和 token(通过标准 Skill 机制按需加载)。 - 插件自身绝不自动上传,也没有任何后台轮询/保存即上传的钩子。 - 全面支持自然语言驱动:无需再像旧版那样死板地连发两次特定 /git 语法,用户自然表达同步意图即可唤醒执行。 - 双阶段安全确认(Tool Guard):即使 AI 产生幻觉试图直接调用 github_sync_push,若未经用户确认,调用会被工具层强拦截拒绝,强制要求先跑 github_sync_plan 汇报改动,并在获得用户明确肯定后带上 confirm: true 才能执行真实推送。 2. 凭据安全(Token 只存一次,且不可能被推上去): - GitHub Token 只放在机器级用户目录:%USERPROFILE%\.dsh\github-sync\config.json。
展开完整文档(README 剩余 1 节)
GitHub 同步插件(@local/dsh-github-sync)

专为 DSH 插件开发者设计:把本地工作区里的插件作为干净副本推送到各自独立的 GitHub 仓库,并给新仓库打上 dsh-plugin topic。

核心设计与特性

1. 绝对防误触 / 双阶段确认机制(自然交互 + 零日常污染):
- 平时不注入任何系统提示词,完全不消耗日常聊天的 Context 和 token(通过标准 Skill 机制按需加载)。
- 插件自身绝不自动上传,也没有任何后台轮询/保存即上传的钩子。
- 全面支持自然语言驱动:无需再像旧版那样死板地连发两次特定 /git 语法,用户自然表达同步意图即可唤醒执行。
- 双阶段安全确认(Tool Guard):即使 AI 产生幻觉试图直接调用 github_sync_push,若未经用户确认,调用会被工具层强拦截拒绝,强制要求先跑 github_sync_plan 汇报改动,并在获得用户明确肯定后带上 confirm: true 才能执行真实推送。

2. 凭据安全(Token 只存一次,且不可能被推上去):
- GitHub Token 只放在机器级用户目录:%USERPROFILE%\.dsh\github-sync\config.json。
- 该文件不在任何工作区/仓库里,绝不进 git,所有 DSH 会话和 profile 自动复用。
- 不要把 token 写进插件目录里的 sync.config.json:那个文件在干净副本范围内,等于把密钥放到待上传目录。若真写了,插件会:① 在状态里告警;② 由密钥扫描把含密钥的文件从干净副本中剔除并列为阻断项,绝不推上去。
- 推送时通过临时生成的凭据助手(Credential Helper)把 token 交给 git,命令行参数、remote URL、报错信息里都不会出现明文(报错统一脱敏)。
- 密钥扫描覆盖:GitHub 经典/细粒度 token、Slack token、AWS Access Key、sk- 风格密钥、Google API key、私钥文件头及 .pem, .key, .crt, .cert, .token, .secret, .auth 等敏感扩展名。文档里的占位符(如 ghp_xxxx)不会误报。

3. 干净副本(Clean Copy):
- 自动排除运行时与非必要文件:.git/、node_modules/、cache/、dist/、build/、coverage/、.status.json、.local.json、.env、_scratch/、_research/ 等。
- 符号链接不跟随(避免把仓库外的内容复制进去)。
- 插件若自带 .gitignore 或配置了 include 白名单,一律尊重(include 白名单也不会绕过密钥扫描)。
- 智能增量同步:发布时镜像工作树与干净副本进行增量 diff 比对,未变动文件完全不擦写磁盘,大幅减少 I/O 磨损并杜绝 Windows 文件句柄占用冲突(EBUSY);干净副本里删除的文件会作为删除提交出现在历史中。
- 单文件大小沿用 GitHub 自己的数字,不自定:默认上限 maxFileBytes = 100 MiB,正是 GitHub 的硬上限(官方原文:GitHub blocks files larger than 100 MiB);超过 50 MiB 按 GitHub 的规则给警告但仍可推(官方原文:you will receive a warning from Git)。超过上限是阻断项而非静默漏掉——静默丢文件会发布出一个悄悄坏掉的插件。密钥扫描的范围与上限一致,大文本文件无法靠体积绕过扫描。

4. 提交署名归你(不是工具的假身份):
- 提交的作者署名默认按 token 所属账号派生,邮箱用 GitHub 的 noreply 形式 <ID>+<用户名>@users.noreply.github.com:既能关联到你的头像,又不公开真实邮箱。
- 想用自己的邮箱就配 git.userEmail(git.userName 可选)。前提是该邮箱已在 GitHub 账号里验证过:GitHub 靠邮箱归属提交,没验证过的邮箱会让提交显示成一个没有头像、点不开的陌生人。
- 注意 git 的性质:写进提交的邮箱是公开的。不想公开就用默认的 noreply 形式。

5. 标准 git 流程:增量提交、真实历史、普通推送:
- 每个插件一个常驻本地镜像仓库(clone),存放在 %USERPROFILE%\.dsh\github-sync\mirrors\<仓库名>,.git 保留 → 历史正常累积。
- 每次发布 = 把干净副本同步进镜像 → 一个真实提交 → 普通 git push(不强制)。git log / git blame / git revert 全部可用。
- 推送前先 fetch 并以远端 tip 为基准提交,所以别人在 GitHub 网页上的修改、或另一台克隆推上来的提交,都会被保留,你的提交落在它们上面(而不是把它们抹掉)。
- 无变更不提交:干净副本与远端一致时不产生空提交,结果为 unchanged。
- 推送被拒绝就如实报错,绝不偷偷强推。拒绝信息会区分「远端有本地没有的提交(竞态,重跑即可)」和「分支受保护(要去改仓库设置)」两种情况。
- force 保留为显式选项(原本的快照语义):生成一个孤立根提交(干净副本)并强推,远端历史被替换。用途是"这个提交必须从历史里消失"(比如误提交了密钥),不是日常发布。
- 镜像坏了/想重新对齐远端:删掉该镜像目录即可,下次会自动重建。

6. 开源协议(按需生成,不替你选):
- 插件目录里已有 LICENSE / COPYING 等文件时直接沿用。
- 缺失且配置了 github.license 时,从 GitHub 官方接口 GET /licenses/{key} 取权威正文(不自己编),替换版权行后作为 LICENSE 写进同一个提交。正文会缓存到 licensesDir,之后不再依赖网络。
- 未配置时只告警:选协议是法律决定,不该由工具替你定。
- 占位符按各协议实际形态替换(MIT/ISC/BSD 用 [year]/[fullname],Apache-2.0 用 [yyyy],MPL-2.0 无占位符则原样保留)。

7. 自适应:先判定「这是什么」,再套对应规则:
- 工具会先判断目标是不是 DSH 插件,依据是 package.json 的 dsh.bundle / dsh.client,或关键词含 dsh(cordis.patch.yml 同样算),并把这个判定和它的依据报出来(状态、试运行、/git 说明里都写),所以判断是可见、可反驳的,不靠猜。
- 不是所有目录都套同一条规则:dsh- 前缀、dsh-plugin topic 只对判定为 DSH 插件的目标生效;普通项目不套用,也不会被打上 DSH 的标签。
- 工作区扫描只认带 DSH 指纹的目录——这是刻意的:否则它会把你的笔记、图片堆、node_modules 一起当成待发布物。

- 扫描结果 ≠ 可发布范围(重要)。扫描器只回答「工作区里有什么看起来像 DSH 插件」,它不是权限的来源——权限来自你(或正在那个项目里工作的会话)的要求。所以 github_sync_plan / github_sync_push 的 plugins 参数也可以直接写目录路径(相对或绝对),扫描没认出来的目录照样能发布。详见下一条。

8. 按路径发布:任何目录都能推:
- plugins 参数接受三种写法,按顺序解析:插件名(仓库名 / 目录名 / 包名)→ 目录路径 → 都失败才报错。
- 所以 /git 推送 my-app(工作区里同名目录)和 /git 推送 ../my-app(工作区外)都能用。适合自己的普通项目、别的宿主的插件,或任何你不想为了发布而改目录名/加 DSH 标记的东西。
- 发布流程完全一样:干净副本(排除 node_modules、cache、.env、.status.json…)、密钥扫描、单文件上限、真实提交与普通推送。唯一的差别是不套 DSH 的命名约定,也不打 dsh-plugin topic(除非你显式要求)。
- 计划里会明确标注「显式给出的路径,不在扫描结果里」,并且如果这个目录正好是扫描根目录本身,会额外提醒「会把该目录下所有内容当成一个仓库」。
- 每个目标的仓库名默认取目录名;想改就在配置里写 plugins(键可用请求时的原字符串 / 绝对路径 / 目录名任一):"plugins": { "my-app": { "repo": "my-app-gh", "visibility": "public" } }。
- 唯一会拒绝的路径是与 DSH 自己的目录(~/.dsh,存着 token、会话记录、发布镜像)相重叠的目录——例如 ~、~/.dsh、~/.dsh/profiles。这是为了保护密钥,不是约定。

9. 命名约定:dsh- 前缀(建议,不是硬墙):
- DSH 插件的文件夹建议带 dsh- 前缀,仓库名默认就等于那个文件夹名(文件夹 = 插件在磁盘上的身份)。所以 dsh-sticker/ → 仓库 dsh-sticker。
- 不合规时默认只警告(naming: "warn"):仓库名会照文件夹名走,在 dsh-plugin 汇总里不太像同类——这是外观问题,不是数据问题,所以不值得拒绝发布。警告里会给出确切的改名建议(例如 nova-preset → dsh-nova-preset)以及改名的代价。
- 想恢复硬拦就设 naming: "block";naming: "off" 则完全不检查。
- 任何情况下都不会静默改名——只报告,由你决定。
- 包名(package.json 的 name)与文件夹名不一致不影响判定:规则只认文件夹。
- 有意的例外:显式写 plugins.<目录名>.repo 即按你的值走,不再判定合规。
- 注意:改文件夹名会打断 profile 里按路径建立的 link: 依赖,改完需要在插件管理里重新安装/链接。

10. 联合投稿(GitHub topics):
- 默认给判定为 DSH 插件的仓库打上 dsh-plugin topic,全部汇总到 https://github.com/topics/dsh-plugin —— 这就是「联合投稿」的落地方式,不需要任何中心化注册表。
- 改 github.topics 可加自己的标签;写 [] 表示不打标签;单个插件可用 plugins.<名>.topics 覆盖。
- topics 页只收录公开仓库:私有仓库打了标签也不会出现在那里。要真正「被联合投稿收录」,仓库得是 public。
- topics 在代码推送成功之后才设置,所以即使打标签失败也不会影响代码上传。

快速配置使用

1. 填写 GitHub 账号与 Token(只需一次)

编辑用户配置文件(不在任何仓库里): %USERPROFILE%\.dsh\github-sync\config.json (插件首次加载会自动生成带注释的模板,所以通常直接打开改就行。)

{
"github": {
"owner": "你的GitHub用户名", "token": "ghp_xxxx 或 github_pat_xxxx",
"topics": ["dsh-plugin"],
"license": "MIT",
"copyright": "你的署名或组织名" } }

owner 也可以写在插件目录的 sync.config.json 里;用户文件里的空字符串不会覆盖插件目录里已填的值(留空即「未指定」,要清空请写 null)。
github.license 留空表示不自动生成(只在状态里告警);github.copyright 留空则用 github.owner。
想让提交署自己的名字/邮箱,再加一段 "git": { "userName": "你的名字", "userEmail": "你的邮箱" };该邮箱要先在 GitHub 账号里验证过才会关联到你的头像。留空则自动用账号的 noreply 邮箱(可关联、且不公开真实邮箱)。

Token 权限要求(依据 GitHub REST 官方文档核实)

经典 Token(Classic)— 只勾 repo 就够:
- repo 已包含 public_repo,所以勾了它就同时覆盖公开仓库与私有仓库。
- POST /user/repos(在自己账号下建仓)官方原文:need the public_repo or repo scope to create a public repository, and repo scope to create a private repository. → repo 一个就够。
- POST /orgs/{org}/repos(在组织下建仓)官方原文同上,但额外要求:The authenticated user must be a member of the organization.
- repo 同时给到代码读写,所以推送也用它。
- 勾 repo 时 UI 会自动带上 repo:status、repo_deployment、public_repo、repo:invite,无害,不用管。security_events、delete_repo、write:packages 都不需要。
- 唯一例外:若某插件目录含 .github/workflows/*.yml,需要额外勾 workflow(授予「添加和更新 Actions 工作流文件」)。首次推送到空仓库时不存在「同名同内容的旧分支」,那条豁免不成立。

细粒度 Token(Fine-grained):
- 推送代码:Contents: Read and write。
- 自动新建仓库:Administration: write 或 Repository creation: write(官方为「满足其一」)。
- 打 topics:Administration: write。
- 组织启用了 SAML SSO 时,token 需单独授权给该组织,否则 403。

权限不足时不用猜:报错会直接打出 HTTP 状态码与对应提示(401 认证失败 / 403 权限不足 / 404 看不到)。分支保护导致推送被拒时会明确说是分支保护,而不是伪装成别的错误。

2. 在会话中使用(两阶段确认)

得益于 v0.5.0 的双阶段安全防护,你既可以自然对话唤起,也可以随时发起预览:

帮我看看有哪些插件改动了 → 自动调用 github_sync_status / github_sync_plan,只读汇报把 dsh-sticker 推到 GitHub → 自动先执行试运行 Plan,向你汇报改动文件与大小,等待确认推吧 / 确认推送 → 收到你的肯定答复后,才真正执行增量提交与远程推送

/git 命令依然作为快捷入口保留,你可以根据习惯随时使用。

3. 命令与工具说明

- /git [要求]:快捷触发入口(留空只读调查,带意图发起同步)。
- github_sync_status:只读查看配置、token 来源、git 可用性、目标清单与判定结果(哪些是 DSH 插件、哪些不是)、协议与镜像位置(只读,不联网)。
- github_sync_plan:试运行(Dry Run),列出将提交/排除的文件与原因、会打的 topic、协议是沿用还是生成。verbose 可看全部排除项(只读,不提交也不推送)。
- github_sync_push:真正的提交与推送。默认增量提交 + 普通 push;force: true 才会用单次快照替换远端历史。仓库不存在时按 visibility 自动创建,随后打 topics。需要 confirm: true 确认参数或前置授权。

常见的几件运维事

| 想做的事 | 怎么做 |
| 只想看会发生什么 | github_sync_plan(或 github_sync_push 加 dryRun: true),都不提交、不推送 |
| 重跑一次被拒的推送 | 直接再推一次:会先 fetch 远端再提交,通常就正常了 |
| 某个提交必须从历史里消失 | github_sync_push 带 force: true(远端历史被替换成单次快照) |
| 镜像状态坏了 | 删掉 %USERPROFILE%\.dsh\github-sync\mirrors\<仓库名>,下次自动重建 |
| 某个插件不想带协议 | 该插件目录放自己的 LICENSE(会被沿用),或给该插件设 license: "" |

改代码之后:模块缓存(实测结论)

DSH 的 Loader 按模块 URL 缓存,并且不看内容与修改时间:文件内容改掉之后,运行中的进程仍然复用旧模块。实测过两次,结论是:

- 只改入口文件名不够。 单独把入口改名、模块目录原样不动,宿主依旧跑旧逻辑 —— 因为被 import 的模块 URL 没变。
- 每个改过内容的文件都必须换 URL。 要么重启 DSH(最省心,推荐),要么把改动的文件/目录一起改名,再重新 enable 一次 bundle。
- cordis.patch.yml 里的 name 指向入口文件,入口改名时同步更新;package.json 的 exports / files 也要跟着改。

实际操作最省事的流程:改完代码 → 重启 DSH。只有在不能重启时才用改名法。

目录结构

main.mjs 入口:系统提示词闸门 + /git 指令 + 三个工具 + 守卫 core/config.js 配置合并、凭据定位、命名与 topic 规范化、告警 core/discover.js 插件发现 + dsh- 前缀约定(文件夹即仓库名) core/manifest.js 干净副本清单 + 密钥扫描 + glob/gitignore 匹配 core/license.js 协议检测与生成(正文取自 GitHub 官方接口并缓存) core/git.js 常驻镜像、增量提交、普通/强制推送、凭据助手 core/github.js REST:建仓、查仓库、打 topics core/sync.js 计划 / 执行编排(命名阻断、协议、topics、结果汇总) core/prompt.js /git 闸门(按会话、按轮次授权)与工具守卫 sync.config.json 插件自带配置(只放通用默认值) sync.status.json 运行期诊断(自动生成,永不推送)(src/、lib/、internal/ 与 index.js、host.mjs、plugin.mjs 都是同一份代码的早期名字:因为上面的模块缓存机制,每次改动都得换 URL,所以历史名被留在了旧代次里。今后请优先重启 DSH,别靠改名。)

发布约定

这个插件自己也遵守它检查别人的那两条约定(/git 的说明与 Skill 规范中均有明确要求):

1. 有改动就升 package.json 的 version。文件变了而版本没变,插件会在推送结果里警告。
2. README 必须全篇同步维护:
- 检查并修改所有与当前版本行为、机制、架构不一致的旧描述,确保正文机制与现版本严格吻合;
- 末尾保留「版本历史」小节,按版本倒序记录,每条必须标注当前系统真实时间(格式:年-月-日 时:分),绝不可臆造。升了版本但 README 搜不到该版本号,插件同样会警告。

📌 版本历史

v0.5.0(2026-10-13 21:40)

- 双阶段确认机制(解绑死板 /git 门禁):移除必须在聊天框显式输入 /git 才能推送的硬性拦截,全面支持自然语言对话驱动。真实推送工具 github_sync_push 引入 confirm: true 参数校验,未确认前调用会自动拦截并提示先汇报 Plan,彻底杜绝 Agent 误推送。
- 智能增量镜像同步:重构 core/git.js 中的文件同步逻辑,从暴力的“全盘物理清空再全量重新写入”升级为“智能比对大小与 mtime 的增量同步”,大幅削减高频磁盘 I/O 并根绝 Windows 文件句柄占用冲突(EBUSY)。
- 标准 Skill 规范支持:建立 dsh-github-sync 标准技能,做到日常对话 0 Token 消耗,并在需要同步时自然激活。
- 扩展敏感文件扩展名扫描:将 .pem, .key, .crt, .cert, .token, .secret, .auth 等扩展名纳入密钥扫描范围,消除漏检隐患。

v0.4.0(2026-10-07 00:05)

- 按路径发布:扫描结果不再是可发布范围的上限。github_sync_plan / github_sync_push 的 plugins 参数现在也接受目录路径(相对或绝对),解析顺序是「插件名 → 目录路径 → 报错」。原来只认扫描结果的写法把发现当成了授权,而那两件事毫无关系:扫描器只是便利设施(列一下工作区里有什么),发布权限来自人的要求。
- 于是自己的普通项目、别的宿主的插件、任何不想为发布而改名或加 DSH 标记的目录都可以直接推;会话本身就在该项目开发环境里时,直接给出目录即可。
- 显式目标不套 DSH 命名约定、不打 dsh-plugin topic;其余流程完全一致(干净副本、密钥扫描、单文件上限、真实提交与普通推送)。仓库名默认取目录名,可在 plugins 里用「请求原字符串 / 绝对路径 / 目录名」任一键覆盖。
- 新增一条安全拒绝(不是约定):与 DSH 自己的目录(~/.dsh,含 token、会话记录、发布镜像)相重叠的路径一律拒绝,避免误把凭据推上去。目录恰好是扫描根目录本身时会额外提醒。
- 修掉两个实现缺陷:①"没有匹配的插件"曾在解析显式路径之前就发出,导致同一个计划里既有正常条目又有假报错;②读取 package.json 复用了扫描器的函数(无 DSH 标记即返回 undefined),导致普通项目的名字与版本读不到(显示 0.0.0)。现在"读清单"与"判定是不是插件"是两件事。
- /git 说明新增一节「范围不等于扫描结果」。

v0.3.0(2026-10-06 18:40)

- /git 改成两阶段:/git 本身只进入调查轮(只读),推送工具会被拒绝并说明"要上传请再发一条带推送意图的 /git";只有带推送意图(推送 / 上传 / 发布 / 同步 / push…)才是推送轮。这样"看看会推什么"不再被迫变成一次上传,而上传依然只可能发生在人明确要求的当轮。明说"先别推送/只看"会覆盖掉句中的"推送"。
- 判定代替一律套用:工具先判定目标是不是 DSH 插件(依据 package.json 的 dsh.bundle / dsh.client,或关键词含 dsh),并把判定结果与依据报在状态、试运行与 /git 说明里;dsh- 前缀与 dsh-plugin topic 只对 DSH 插件生效,普通项目不套用、不打 DSH 标签。
- dsh- 前缀从硬拦改为默认警告:新增 naming: "warn" | "block" | "off",默认 warn(仍可发布,警告里给出确切改名建议与改名代价);要恢复原来的硬拦设 block。任何情况下都不会静默改名。
- /git 的说明里新增一节讲清"先判定再套规则",并明确:硬性要求只有两条——不要静默改名,不要静默替人决定协议。
- 顺带清理:core/discover.js 末尾多余的 export default(同一类 unwrapExports 坑)。

v0.2.2(2026-10-06 17:20)

- 让"消息工厂"的解析变成可失败但不致命:v0.2.1 改成静态 import { createUserMessage } from '@deepseek-ai/dsh-llm',但实测该裸模块名从插件目录解析不到(宿主的包在 app.asar 内的 /dsh/node_modules/,插件在外部,Node 只会从插件目录往上找 node_modules)。静态 import 解析失败会让整个 bundle 加载失败——/git 闸门与三个工具会一起消失,比原 bug 更严重。
- 现在是加载期后台尝试 + 失败容忍:能解析就用宿主官方工厂,解析不到就用语义等价的本地实现(deepFreeze(structuredClone({ ...input, role: 'user', id: randomUUID() })),与宿主 createMessage 逐字对应)。两条路径产出的消息都带非空 id,都不会破坏会话回放。
- 新增测试 ghsync-steer-test.mjs:驱动真实 bundle 与真实 apply(),断言真正进入收件箱的消息具备 id/role/source/content 且被冻结、两次注入 id 不同。故意不桩掉 @deepseek-ai/dsh-llm——「这个包能否解析」正是必须不影响结果的那件事。
- 顺带记录一个宿主自身的坑:dsh-agent/README.md 里 steer() 的官方示例只写了 content 与 source(没有 id),而 steer(input) 的实现是原样 inbox.splice(input)、不补 id,所以照文档写出来的插件会写出回放时校验失败的会话事件。宿主自己的调用方(dsh-api-session-controller)都是先用 createUserMessage 造消息再 steer,照它做才对。
- 补齐自动生成的用户配置模板:模板原先只有 github.owner / token / topics,新装的人根本不知道还能配提交署名和开源协议。现在模板同时列出 git.userName / git.userEmail 与 github.license / github.copyright,并注明「留空就按账号派生 noreply 邮箱(可关联头像、不公开真实邮箱)」「自填邮箱需先在 GitHub 账号里验证过」。默认值仍安全留白:协议留空 = 不替你选协议,只提醒。

- 顺带清理:core/prompt.js、core/config.js 末尾多余的 export default(unwrapExports 的 exports.default ?? exports 会因此把具名导出整片遮掉,属同一类坑)。

v0.2.1(2026-10-06 12:36)

- 修复 /git 注入的消息缺 id,导致会话历史加载失败:/git 之前用裸对象 { role, content, source } 调 agent.steer(),而会话日志按原样记录 user/message,缺少 id 的事件在重启回放时被校验器拒绝——报 session event at seq N lacks an identified message,整个会话的历史加载都会失败(不只是 /git 那一轮)。
- 现在改用 harness 工厂 createUserMessage(来自 @deepseek-ai/dsh-llm)构造消息,由它生成并冻结 id,与其余用户消息一致。
- 仅影响 /git 注入的轮次;已写坏的历史事件需要单独修补,插件本身无法回改。

v0.2.0(2026-10-06 11:31)

- 改用标准 git 流程:每个插件一个常驻本地镜像(%USERPROFILE%\.dsh\github-sync\mirrors\<仓库名>),每次发布是增量提交并普通推送,历史累积、可用 git log/blame/revert;无变更不产生空提交。
- 推送前先 fetch 并以远端 tip 为基准提交,所以别人在网页或别处推上来的提交会被保留,不会被覆盖。
- 强推保留为显式选项 force:生成孤立根提交(干净副本)替换远端历史,用于「某个提交必须消失」(例如误提交密钥),不是日常发布。
- 新增开源协议支持:插件已有 LICENSE 则沿用;缺失且配置了 github.license 时从 GitHub 官方接口取正文(替换版权行、缓存到本地)写进同一提交;未配置只告警——选协议是法律决定,不替用户定。
- 提交署名归账号:默认按 token 所属账号派生,用 <ID>+<用户名>@users.noreply.github.com,可关联头像且不公开真实邮箱;可用 git.userName/git.userEmail 覆盖。
- 单文件上限对齐 GitHub 官方数字:默认 maxFileBytes = 100 MiB(官方硬上限),超过 50 MiB 按官方规则警告但可推;超限是阻断项而非静默漏掉。密钥扫描范围与上限一致。
- 推送被拒时区分竞态(重跑即可)与分支保护(需去仓库设置改),不再笼统报错。
- 提交内容严格等于干净副本:干净副本里没有的文件会在下次提交中作为删除出现在历史里。
- 新增上述两条发布约定检查(有改动没升版本 / 升了版本 README 没记)。

v0.1.0(2026-10-05 16:16)

- 首次发布。把插件作为干净副本推送到独立 GitHub 仓库,单次快照强制推送。
- /git 单轮授权闸门:平时零注入,插件绝不自动上传,未授权轮的推送工具调用会被硬拦截。
- token 只存机器级用户目录,经临时凭据助手交给 git,不进命令行、URL 或报错文本;发布前做密钥扫描。
- dsh- 前缀命名约定(文件夹即仓库名),不合规列为阻断项并给出确切改名建议。
- 默认给仓库打 dsh-plugin topic(联合投稿汇总页),在代码推送成功后设置。
预览分享卡片长什么样 ↓YumenoSayuri/dsh-github-sync
分享 · DeepSeek Harness Hub
YumenoSayuri/dsh-github-sync

把工作区里的 DSH 插件按「干净副本」推送到各自独立的 GitHub 仓库:/git…

这是你的仓库?认领条目

回到顶部