dsh-slash-command-filesystem
v0.1.7-rc.2
Published
Local filesystem slash-command provider for the DeepSeek Harness
Maintainers
Readme
dsh-slash-command-filesystem
把存放在约定目录下的 Markdown 命令文档加载并注册为 DeepSeek Harness(DSH)原生命令的宿主插件。
在约定目录放一份 .md,重启后即可用 /name args 调用;文档正文按规范展开为提示词,作为一轮用户消息送入模型。新增、修改、删除文档在运行期即可生效。
- 目标宿主版本:DSH
0.1.7-rc.2(依赖声明与engines.dsh同为^0.1.7-rc.2,两处同源)。0.1.5-rc.2/0.1.6-alpha.*/0.1.7-alpha.*/0.1.7-rc.1已退役:app-boot 的 peer 兼容性门禁会在装载前把本插件的行置为disabled(03 §10.6) - 插件类型:宿主单半(命令发现与展开全部在 Host 侧完成,客户端菜单由官方
@deepseek-ai/dsh-client-ui-commands自动渲染) - 不修改任何官方包
它做什么
| 能力 | 说明 |
| --- | --- |
| 多根扫描 | 项目级、用户级与自定义目录,带优先级裁决与同名去重 |
| 规范解析 | Claude Code 的 frontmatter 字段、参数占位符、! shell 片段、@ 文件引用、${CLAUDE_*} 变量 |
| 原生命令注册 | 注册进 ctx.commands,出现在 / 菜单、支持参数输入提示、生命周期写入会话日志 |
| 提示词展开 | 展开结果作为一轮用户消息经 Agent.followup() 投递 |
| 运行期跟随 | 目录变化自动重扫(文件监听 + 模型写文件工具联动) |
| 按会话收窄 | 项目命令只进该会话自己的命令作用域,A 项目的命令对 B 项目的会话不可见 |
| 运行日志 | 每次发现输出一行 info 汇总(候选数 / 注册数 / 跳过数 / 命令名列表 / 不存在的扫描根) |
它不做什么:不处理 SKILL.md 目录形态技能(属官方 @deepseek-ai/dsh-skill-filesystem)、不提供客户端 UI、不实现 Claude Code 的 Skill 工具(模型主动调用)语义。
安装
profile(host)与 agent preset 两种平面都支持,任选一处挂载。行为由同一段代码承担——命令注册与项目根解析都与「插件挂在哪一层」无关——差别只有配置粒度与共享根命令的可见范围,选择建议见 docs/02 §21.1。
# 方式一:agent preset(嵌进某条 @deepseek-ai/dsh-agent-preset 声明的 config.plugins)
# 配置按 preset 各一份,共享根命令只对该 preset 的会话可见。
- id: slash-command-filesystem
name: dsh-slash-command-filesystem
config:
allow:
- mbd:*# 方式二:profile(并入 $DSH_HOME/profiles/<name>/cordis.patch.yml)
# 配置进程级一份,共享根命令对进程内所有会话可见。
- insert:
- id: slash-command-filesystem
name: dsh-slash-command-filesystem
config:
allow:
- mbd:*方式二的片段随包发布为 cordis.patch.yml(也是 dsh.bundle 指向的补丁层,因此把本包列入 profile 的 dsh.profile.bundles 即可自动插入);方式一的片段随包发布为 preset.row.yml。
两处同时挂会重复扫描、重复建监听,并让同一会话的作用域层出现重名注册(第二次被跳过并记 warn),因此任选一处。
name 是包名:preset 从已安装的 harness 目录解析,profile 从 profile 目录解析——把包装到对应可解析的位置即可(容器里的做法见 docs/05 §1.1)。
命令目录不必写死:项目根由各会话自己的工作目录决定,同 preset / 同进程的会话各自扫描自己的项目。容器部署见 docker/ 与 docs/05 真机验收记录 §1。
共享根与项目根的差异
| 维度 | 共享根 | 项目根 |
| --- | --- | --- |
| 组成 | customCommandDirs[]、$DSH_HOME/commands、$DSH_AGENTS_HOME/commands、~/.claude/commands、bundled 目录 | 各会话工作目录向上找到含 .git 的目录,取 .dsh/commands 与 .agents/commands;projectRoots[] 是额外项目根 |
| 注册作用域 | 挂载行所在的那一层(preset 行 → 该 preset 的 standing 层;profile 行 → 全局层) | 各会话自己的作用域层(agent.ctx.inject(['commands'], …)) |
| 可见性 | preset 行 → 该 preset 的全部会话;profile 行 → 进程内所有会话 | 只有该项目根的会话;项目命令对同名的共享命令有遮蔽作用,A 项目的命令对 B 项目的会话不可见 |
| 扫描与监听次数 | 只扫描并监听一次 | 同一项目根的多个会话共享一次扫描与一个监听句柄(owner 引用计数) |
| 何时建立与释放 | 插件激活时建立,插件卸载时释放 | 该项目根的首个会话出现时建立;最后一个会话结束时释放并关闭监听 |
没有工作目录的会话只拿到共享根命令。同时建立监听的项目根数量上限由 maxWatchedProjects 控制(0 表示不限制),超出的项目只做一次性扫描、不实时更新。
快速开始
mkdir -p ~/.agents/commands
cat > ~/.agents/commands/git-commit.md <<'MD'
---
description: 按仓库规范生成提交信息
argument-hint: "[<补充说明>]"
---
请阅读暂存区改动并生成一条提交信息。
当前分支: !`git branch --show-current`
暂存统计: !`git diff --cached --stat`
补充说明:$ARGUMENTS
MD重启 DSH 后,在会话输入框键入 /git-commit 修复登录超时:
- 客户端把该行认领为命令(因为插件为它声明了参数提示);
- 插件展开正文,执行两个
!片段并替换$ARGUMENTS; - 展开结果作为一轮用户消息送入模型,格式如下:
<slash-command name="git-commit" source="git-commit">
请阅读暂存区改动并生成一条提交信息。
…(展开后的正文)
</slash-command>子目录会形成命名空间:~/.agents/commands/mbd/git-push.md 注册为 /mbd-git-push,其规范 ID 为 mbd:git-push(显示在菜单描述里)。
DSH 的命令名文法不允许冒号,因此规范 ID 里的
:会被扁平化为-;allow/deny过滤仍按规范 ID 书写(见下)。
扫描根与优先级
rank 小者优先;同名文档由低 rank 者胜出,其余记为 shadowed。两类根落在两个作用域层,裁决只在同一层内进行。
共享根(与工作目录无关,注册进 preset 的 standing 层,只扫描并监听一次):
| 顺序 | 路径 | source | rank | 默认 |
| --- | --- | --- | --- | --- |
| 1 | customCommandDirs[] | custom | 300 + i | — |
| 2 | $DSH_HOME/commands | user-dsh | 400 | ✅ |
| 3 | $DSH_AGENTS_HOME/commands(默认 ~/.agents/commands) | user-agents | 500 | ✅ |
| 4 | ~/.claude/commands | claude | 600 | ⬜ 需显式开启 |
| 5 | bundledCommandDir | bundled | 700 | — |
precedenceMode: 'claude-code' 时两个用户级根的 rank 变为 50 / 51,即用户级优先于项目级。
项目根(由会话工作目录决定,注册进该会话自己的作用域层):
| 顺序 | 路径 | source | rank | 默认 |
| --- | --- | --- | --- | --- |
| 1 | <项目根>/.dsh/commands | project-dsh | 100 | ✅ |
| 2 | <项目根>/.agents/commands | project-agents | 200 | ✅ |
| 3 | projectRoots[] 各自的 .dsh/commands | project-dsh | 250 + i | — |
「项目根」取 projectRoot 配置;未配置时从该会话的工作目录(agent.session.header.cwd)向上寻找 .git,找不到则回退为工作目录本身。
两类根的可见性与监听生命周期见上文「共享根与项目根的差异」;设计依据见 docs/02 §21。
配置
# preset 声明行 config.plugins 里的一条(或 profile 补丁层的 insert 列表里的一条)
- id: slash-command-filesystem
name: dsh-slash-command-filesystem
config:
# 附加扫描目录:字符串,或带命名空间的对象
customCommandDirs:
- ~/work/team-commands
- { path: ~/.claude/plugins/mbd/commands, namespace: mbd }
# 允许列表:空数组表示全部加载;支持 * 与 ?,可加 source/ 前缀限定来源
allow:
- mbd:git*
- project-dsh/*
deny:
- mbd:git-push
# 显式项目根(默认按各会话工作目录探测;写了则对全部会话生效)
# projectRoot: ~/work/repo
# 关闭 `!` 片段执行(不受信任的仓库建议关闭)
# enableShellExecution: false常用字段:
| 字段 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| customCommandDirs | (string \| {path, namespace})[] | [] | 附加扫描目录,顺序即优先级 |
| allow / deny | string[] | [] | 模糊匹配;deny 优先。* 跨越 : 与 /,匹配不区分大小写 |
| allowMatchTargets | ('id'\|'name'\|'path')[] | ['id','name'] | 参与匹配的字段 |
| projectRoot / projectRoots | string / string[] | 自动探测 | 前者覆盖各会话的探测结果,后者追加额外的项目根 |
| includeDefaultRoots | boolean | true | 是否包含默认的用户级根 |
| includeProjectAgentsRoot | boolean | true | 是否扫描 <项目根>/.agents/commands |
| includeClaudeCompatRoot | boolean | false | 是否扫描 ~/.claude/commands |
| subdirectoryNamespace | boolean | true | 子目录是否形成命名空间 |
| collisionPolicy | 'rank-first'\|'skip'\|'error' | 'rank-first' | 同名裁决 |
| enableShellExecution | boolean | true | 是否执行 ! 片段 |
| shellTimeoutMs | number | 120000 | 单条 shell 超时;下界 1,要关闭 ! 片段请用 enableShellExecution |
| enableFileReferences | boolean | true | 是否展开 @ 文件引用 |
| enableStackedInvocation | boolean | true | 是否支持 /a /b args 堆叠调用 |
| watch | boolean | true | 是否监听目录变化 |
| scanMaxDepth | number | 8 | 目录递归深度上限;0 表示只扫扫描根顶层、不进入子目录 |
| maxCommands | number | 512 | 注册命令总数上限;0 表示不限制 |
| maxFileBytes | number | 1048576 | 单个命令文档字节上限;0 表示不限制 |
| maxShellOutputBytes | number | 262144 | 单条 shell 输出字节上限;0 表示不限制(执行器自身上限) |
| maxShellCommands | number | 16 | 单次展开的 ! 片段条数上限;0 表示不限制 |
| maxFileReferenceBytes | number | 262144 | 单份 @ 引用的字节上限;0 表示不限制 |
| maxFileReferences | number | 32 | @ 引用份数上限;0 表示不限制 |
| maxExpandedBytes | number | 262144 | 展开结果总字节上限;0 表示不限制 |
| maxStackedCommands | number | 6 | 堆叠命令数上限(含首个);0 表示不限制 |
| maxNameLength | number | 64 | 注册名长度上限;下界 8(需容纳截断片段 + 连字符 + 6 位哈希) |
| descriptionMaxChars / descriptionFallbackMaxChars | number | 1024 / 200 | 描述长度上限;0 表示不限制 |
| maxWatchedProjects | number | 128 | 同时建立监听的项目根上限;0 表示不限制。超出的项目只做一次性扫描、不实时更新,并记一条 warn。计数只统计真正建立了监听的项目,项目释放后名额归还 |
完整字段表(50 余项,含全部上限与开关)见 docs/02 §10;
各数值字段的下界与 0 的含义见 docs/02 §10.9。
安全声明
加载一个命令目录,等价于信任该目录中的任意 shell 片段。 请在使用前阅读本节。
| 风险 | 本插件的处理 |
| --- | --- |
| 文档里的 !`cmd` 会执行真实进程 | 经 ctx.shell 执行,因此继承 DSH 的沙箱与审批策略;可用 enableShellExecution: false 全局关闭(片段替换为策略占位符) |
| 项目仓库里的命令文档随代码分发 | 项目级根(.dsh/commands、.agents/commands)建议纳入代码审查;不受信任的仓库请设 enableShellExecution: false |
| 命令参数被当作代码执行 | 不会发生:结构(哪里是 !、@、${})只来自文档原文,用户参数只在已识别的结构内部替换,从不参与结构判定 |
| shell 输出里含占位符 | 不会被二次扫描 |
| 文档伪装成官方命令 | 同名时让位:注册表同层同名必然抛错,本插件捕获后跳过该文档并记为 conflict,官方命令不受影响 |
| 巨量文档或巨型展开撑爆上下文 | 全部有上限(文件 1 MiB、展开 256 KiB、shell 输出 256 KiB、shell 条数 16、文件引用 32 份等),超限截断并附提示 |
allowed-tools / disallowed-tools 在 DSH 中没有 per-invocation 工具白名单,本插件只把它们解析并映射为 DSH 工具名后,作为模型可见的软提示注入,不提升任何权限。
与 Claude Code 的差异
| 项 | Claude Code | 本插件 |
| --- | --- | --- |
| 命令名里的 : | 一等公民(mbd:git) | 扁平化为 mbd-git(DSH 命令名文法不允许冒号) |
| 展开顺序 | 参数 → ! → 变量 | 结构只来自原文,参数在结构内部替换(更严格,见「安全声明」) |
| shell 非零退出 | 默认任何非零即失败,搜索类命令退出码 1 例外 | 默认 strict;可用 shellExitCodePolicy: 'search-tolerant' 复刻例外 |
| @ 文件缺失 | 规范未定义 | 替换为占位文本,不中止 |
| model / effort / hooks / context: fork | 生效 | 解析但不生效,可选在注入文本头部标注 |
| 优先级 | 个人 > 项目 | 项目 > 用户(与官方 skill-filesystem 一致;precedenceMode 可切换) |
完整差异清单(含 11 项能力缺口与对策)见 docs/02 §15。
开发
pnpm install
pnpm typecheck # 类型
pnpm lint # 静态检查(oxlint)
pnpm check:style # 行长 / 尾换行 / 行尾空白
pnpm test # 单元与集成测试
pnpm test:coverage # 覆盖率门禁(逐文件 100%)
pnpm verify:claims # docs/03 的官方落点断言(无 harness 快照时自动跳过)
pnpm build # 构建到 lib/pnpm verify:claims 会按行核验 docs/03 的每条 文件:行 断言,
harness 快照升级后行号漂移即失败(推送前自动执行)。提交前 lefthook 跑 staged 检查;
推送与 CI(push 到 main、PR 与手动触发)执行上表全部门禁。
发布到 npm
包发到官方源——本机默认 registry 是镜像源,只发镜像源不会同步到 npmjs:
npm login --registry https://registry.npmjs.org
npm publish --tag latest --registry https://registry.npmjs.orgprepublishOnly 会在发布前重跑 pnpm build,因此 lib/ 不会陈旧。版本号的唯一来源是
package.json 的 version;宿主兼容范围由 engines.dsh 与全部 peerDependencies 声明
(当前 ^0.1.7-rc.2)。预发布版必须显式 --tag(npm 直接拒绝未指定标签的预发布发布),
该标签决定 latest 指向谁;发布还需要 2FA 认证。完整发布清单见 AGENTS.md。
.
├── docker/ # Dockerfile、compose 与环境变量模板
├── docs/ # 规范分析、设计、源码落点、任务清单、真机验收
├── scripts/ # 门禁脚本与容器验收脚本
├── src/ # 源码(宿主单半,10 个文件)
│ ├── index.ts # 入口:平面判定、会话生命周期订阅、首轮发现
│ ├── config.ts # 配置 schema 与归一化
│ ├── types.ts # 纯类型(不计入覆盖率门禁)
│ ├── errors.ts # 错误类与中文消息常量
│ ├── fs.ts # 文件访问原语、项目根探测、扫描根集合构建
│ ├── document.ts # frontmatter 切分与字段映射
│ ├── catalog.ts # 发现:扫描 + 筛选 + 同名裁决、内容指纹
│ ├── sessions.ts # 会话编排:共享根 / 项目根、作用域注册、owner 引用计数
│ ├── watch.ts # 目录监听与去抖重扫调度
│ └── execute.ts # 命令处理器、全部展开器、工具名映射、并发信号量
└── tests/ # 与被测代码同层归属的测试编码规范见 AGENTS.md 与 docs/CODING_STANDARDS.md。
文档
| 文档 | 内容 |
| --- | --- |
| docs/README.md | 文档索引与关键结论 |
| docs/01 Claude Code SlashCommand 规范 | 外部规范整理,含 21 条未确认项 |
| docs/02 加载与注册设计 | 本插件的完整设计 |
| docs/03 官方源码落点与契约清单 | 设计到官方 file:line 的映射与升级哨兵 |
| docs/04 任务清单 | 里程碑与任务分解 |
| docs/05 容器构建部署与真机验收 | 镜像构建部署、端口与数据卷约定、十一项真机断言,以及两平面/监听上限/双挂的实测记录 |
| docs/06 上游缺陷反馈 | / 菜单失效的复现、根因与修复建议(涉事插件非本插件) |
