dsh-grill-first 0.0.0-stage → 0.1.3

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 ADDED
@@ -0,0 +1,51 @@
1
+ # 更新日志
2
+
3
+ 本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
4
+
5
+ ## 0.1.3
6
+
7
+ 文档修正版。
8
+
9
+ ### 修复
10
+
11
+ - **README 补回「npm 命令行」安装方式**:0.1.1 时期因为包还没发到 npm,这一节被临时删掉了;现在补回来,并同步更新安装示例里的 tgz 版本号。
12
+
13
+ ## 0.1.2
14
+
15
+ 首个 npm 发布版本(0.1.1 曾进入 npm 暂存队列但未获批,正式首发顺延为 0.1.2)。功能与 0.1.1 完全相同。
16
+
17
+ ### 发布
18
+
19
+ - 首次发布到 npm,可直接 `npm install dsh-grill-first`。
20
+ - 顺延版本号是为了避开 npm 上已存在的 0.1.1 暂存记录。
21
+
22
+ ## 0.1.1
23
+
24
+ 修复确认卡片在长清单下"点不到开工选项"的问题。
25
+
26
+ ### 修复
27
+
28
+ - **确认卡片改为"短标题 + 长正文"**:`question` 字段只放 60 字以内的任务标题,完整四段清单移入 `detail` 字段。
29
+ 此前长清单整段塞进标题,而卡片标题栏高度固定且不滚动,标题一长就会把唯一可收缩的选项区压成 0 高——
30
+ 用户看不到也点不到"确认,按此开工 / 要改",只能关掉卡片手动打字。`detail` 在卡片内部的可滚动区渲染,
31
+ 超过 6000 字时截断并提示"完整内容见上面这条消息"。
32
+ - **兜底通道同步加固**:结构化提问不可用时的提示语,要求模型改用 `ask_user_question` 时同样遵守"短标题 + 清单进 detail"。
33
+
34
+ ## 0.1.0
35
+
36
+ 首个版本。
37
+
38
+ ### 新增
39
+
40
+ - **开工前需求澄清**:`grill_start` 判定任务是否需要澄清,返回该问什么(`focus_hints`)与已知偏好(`memory_context`);`grill_confirm` 用中文四段清单(目标 / 范围 / 约束 / 验收标准)请用户确认,确认后才放行任务。
41
+ - **协议注入**:向系统提示注入约 600 字的中文协作协议,让任意模型都按"先问清楚再动手"的流程工作。
42
+ - **需求门禁**:`gateMode` 三档(`off` / `warn` / `strict`);`strict` 下未确认需求前拦截变更类工具(写文件、跑命令、委派子代理等),只读工具一律放行;新用户消息到达时门禁重置。
43
+ - **两层记忆**:全局层(通用偏好)+ 项目层(项目背景与任务历史),只记录用户确认过的内容,下次追问时自动避免重复提问。
44
+ - **跳过机制**:消息命中 `bypassKeywords`(默认「直接做 / 跳过提问 / 不用问 / skip」)即跳过;`enabled: false` 全局静默。
45
+ - **7 项配置**:`enabled`、`gateMode`、`maxQuestionsPerRound`、`maxRounds`、`intensity`、`bypassKeywords`、`memoryEnabled`,均带中文说明,可在 DSH 设置界面直接改。
46
+ - 工具与配置说明全部中文。
47
+
48
+ ### 说明
49
+
50
+ - 门禁只对根 agent 生效,子代理与团队成员不受影响。
51
+ - 需求清单只在对话中确认,**不会**在工作区生成任何需求文档。
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 InoriMutsumi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,167 @@
1
- # Temporary Holding Version
1
+ # dsh-grill-first
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ ![tests](https://img.shields.io/badge/tests-91%20passing-brightgreen)
4
+ ![license](https://img.shields.io/badge/license-MIT-blue)
5
+ ![DSH plugin](https://img.shields.io/badge/DSH-plugin-blueviolet)
6
+
7
+ > DSH(DeepSeek Harness)插件:**开工前先刨根问底**——把模糊需求问成明确需求,你在对话里点头之后,主任务才开工。
8
+
9
+ ## 它解决什么问题
10
+
11
+ AI 助手最常见的失败不是"不会做",而是"做完了才发现做的不是你要的"。这个插件在**任何新任务开工前**插入一道需求澄清环节:
12
+
13
+ 1. 模型先把你的任务原文交给 `grill_start` 判定;
14
+ 2. 任务模糊(缺目标 / 范围 / 约束 / 验收标准)→ 它按轮追问你,每轮 3 个问题,最多 3 轮,每轮都基于你上一轮的回答继续深挖;
15
+ 3. 问清后整理成中文四段清单(目标 / 范围 / 约束 / 验收标准),用 `grill_confirm` 请你确认;
16
+ 4. 你点"确认,按此开工"→ 本次任务放行;你点"要改"→ 它按你的反馈修订后再来一次;
17
+ 5. 一句话就能说清的小任务、以及带「直接做 / 跳过提问 / 不用问 / skip」字样的消息,**不会被打扰**。
18
+
19
+ 严格档位(`gateMode: "strict"`)下,需求没确认前模型无法执行写文件、跑命令这类**变更类**操作——从机制上保证"没问清就不许动手"。
20
+
21
+ ## 安装
22
+
23
+ 装插件前请先退出正在跑的任务。三种方式任选:
24
+
25
+ **方式一:DSH 插件市场**(推荐)
26
+
27
+ 在 DSH 设置 → 插件里搜索 `dsh-grill-first` 一键安装。
28
+
29
+ **方式二:npm 命令行**
30
+
31
+ ```bash
32
+ dsh plugin --profile <你的 profile 名> add dsh-grill-first
33
+ ```
34
+
35
+ **方式三:本地包 / GitHub 仓库**
36
+
37
+ ```bash
38
+ dsh plugin --profile <你的 profile 名> add /绝对路径/dsh-grill-first-0.1.3.tgz
39
+ dsh plugin --profile <你的 profile 名> add github:InoriMutsumi/dsh-grill-first
40
+ ```
41
+
42
+ 装完**即生效**:本机实测 DSH 的 live profile 会热加载新插件,不需要重启(若你的 profile 是启动式、或列表里没出现,再完全退出 DSH 重开一次)。
43
+
44
+ ## 使用
45
+
46
+ 装好之后不需要你做什么:它靠两件事自动工作。
47
+
48
+ - **协议提示段**:插件往系统提示里注入一小段中文协议(约 600 字),告诉模型"接到新任务先调 `grill_start`"。你在系统提示里能看到 `【开工前提问协议(grill-first)】` 就说明装好了。
49
+ - **工具**:模型会用这两个工具推进澄清流程。
50
+
51
+ | 工具 | 作用 |
52
+ |---|---|
53
+ | `grill_start` | 判定任务是否需要澄清;返回 `verdict`、`focus_hints`(该问什么)、`memory_context`(已知偏好,避免重复问) |
54
+ | `grill_confirm` | 把四段式需求清单交给你确认;确认后放行任务,并把确认过的内容写进记忆 |
55
+
56
+ ### 想跳过提问时
57
+
58
+ - 消息里带上「**直接做**」「**跳过提问**」「**不用问**」「**skip**」中任意一个 → 立刻开工;
59
+ - 或者在设置里把 `enabled` 关掉 / 把 `gateMode` 调成 `off` → 整个插件静默。
60
+
61
+ ## 配置项
62
+
63
+ DSH 设置界面 → 插件 → dsh-grill-first,共 7 项:
64
+
65
+ | 配置 | 类型 | 默认 | 说明 |
66
+ |---|---|---|---|
67
+ | `enabled` | 布尔 | `true` | 总开关。关闭后既不追问也不拦截工具 |
68
+ | `gateMode` | `off` / `warn` / `strict` | `warn` | `off` 不拦截;`warn` 只在日志里记录"需求未确认";`strict` 未确认前拦截变更类工具 |
69
+ | `maxQuestionsPerRound` | 1-6 | `3` | 每轮最多问几个问题 |
70
+ | `maxRounds` | 1-10 | `3` | 最多追问几轮 |
71
+ | `intensity` | `gentle` / `standard` / `grill` | `standard` | 追问力度:只问关键点 / 标准 / 刨根问底 |
72
+ | `bypassKeywords` | 字符串数组 | `["直接做","跳过提问","不用问","skip"]` | 命中即跳过提问 |
73
+ | `memoryEnabled` | 布尔 | `true` | 是否启用两层记忆 |
74
+
75
+ > 默认是 `warn` 而不是 `strict`:默认档位保证升级插件不会突然卡住你已有的工作流。想要硬门禁请手动切 `strict`。
76
+
77
+ ### 怎么改配置
78
+
79
+ DSH 设置页里的插件条目目前只提供「启用 / 停用」开关(客户端的插件页不渲染第三方插件的配置表单),要改上面 7 项请编辑 **profile 的补丁层** `cordis.patch.yml`:
80
+
81
+ ```yaml
82
+ # <profile 目录>/cordis.patch.yml
83
+ - id: dsh-grill-first
84
+ name: dsh-grill-first
85
+ config:
86
+ gateMode: strict # off | warn | strict
87
+ maxQuestionsPerRound: 3 # 1-6
88
+ maxRounds: 3 # 1-10
89
+ intensity: standard # gentle | standard | grill
90
+ memoryEnabled: true
91
+ bypassKeywords: [直接做, 跳过提问, 不用问, skip]
92
+ ```
93
+
94
+ 改完重启 DSH 生效。只写想改的项也可以,其余项用默认值。
95
+
96
+ ## 两层记忆
97
+
98
+ 只记录**你确认过**的内容,不记录对话原文,也不会把需求清单写成文件。
99
+
100
+ | 层 | 位置 | 内容 |
101
+ |---|---|---|
102
+ | 全局层 | `<profile 目录>/grill-first/global.json`(不可写时退化到 `~/.dsh-grill-first/global.json`) | 通用偏好,如"注释一律用中文" |
103
+ | 项目层 | `<工作区>/.dsh/grill-first/memory.json` | 项目背景、任务历史(任务名 + 需求摘要 + 日期) |
104
+
105
+ 下次追问时,`grill_start` 会把这些内容作为 `memory_context` 返回,模型就不会重复问你同一件事。
106
+
107
+ ## 开发
108
+
109
+ ```bash
110
+ npm install # 装开发依赖(schemastery 由宿主提供,仅开发期需要)
111
+ npm run build # TypeScript → dist/
112
+ npm test # 构建 + 94 个单测 / 端到端场景测试
113
+ node scripts/selfcheck.mjs # 静态自检:中文 JSDoc、any 用量、需求→代码位置
114
+ ```
115
+
116
+ 推送到 GitHub 后 `.github/workflows/ci.yml` 会自动跑上面那两条(`npm ci` + `npm test`)。`dist/` 是**故意提交**的:这样 `dsh plugin add github:…` 装完即可用,不需要在你的机器上编译。
117
+
118
+ 代码结构:
119
+
120
+ ```
121
+ src/
122
+ index.ts 插件装配入口(工具 + 协议段 + 门禁)
123
+ config.ts 配置归一化(不依赖宿主,可单测)
124
+ configSchema.ts Schemastery 配置声明(设置界面用,7 项)
125
+ version.ts 从 package.json 读版本号,供 grill_start 返回
126
+ judge/heuristics.ts 任务模糊度判定(纯函数)
127
+ prompt/protocol.ts 注入系统提示的中文协议文本
128
+ tools/grillStart.ts grill_start 工具
129
+ tools/grillConfirm.ts grill_confirm 工具
130
+ gate/preExecute.ts 变更类工具门禁(tools/pre-execute + 新消息重置)
131
+ memory/store.ts 两层记忆的读写
132
+ ```
133
+
134
+ ## 兼容性与适配
135
+
136
+ 本插件对外只有两处依赖,都不归 DSH 的"插件版本门禁"管:
137
+
138
+ - **peer 依赖**:只有 `@deepseek-ai/schemastery`(`^3.18.1`,宿主自带;DSH 0.2.1-rc.2 里解析到的实际版本是 3.18.5)。
139
+ - **宿主扩展点**:cordis 上下文 API —— `ctx.tools`(注册工具)、`ctx.get('userQuestions')`(结构化提问通道)、`ctx.inject(['systemPrompt'])`、事件 `tools/pre-execute` 与 `agent/inbox/inserted`。
140
+
141
+ 它**不 import 任何 `@deepseek-ai/dsh*` 运行时包**,所以既没有 `engines.dsh`,也没有 `@deepseek-ai/dsh` peer。DSH 的 `plugin-compatibility` 检查(`dsh-app-boot` 里的 `evaluatePluginCompatibility`)只在插件声明了 `engines.dsh` 或 `@deepseek-ai/dsh*` peer 时才介入,判不过才会被拦;本插件两条都不沾,因此升 DSH 一般不需要改代码。
142
+
143
+ > 以后若要 import harness 内部包,请显式声明 peer 范围并带上预发布分支,例如
144
+ > `">=0.2.0-rc.2 <0.3.0-0 || >=0.2.1-rc.1 <0.3.0-0"`。
145
+ > DSH 自己的门禁用 `semver.satisfies(..., { includePrerelease: true })` 判定,`^0.2.0-rc.2` 能匹配 `0.2.1-rc.2`;
146
+ > 但标准 npm 语义(不加 `includePrerelease`)会判不匹配,所以上游要求写显式分支。
147
+
148
+ 已验证的宿主版本:`0.2.0-rc.2`(0.1.x 开发期)、`0.2.1-rc.2`(2026-10-17 复核:`typecheck`、94 个单测、`verify-installed` 一致,且实测加载运行)。升完 DSH 后的自检三步:
149
+
150
+ ```bash
151
+ node scripts/verify-installed.mjs # 安装拷贝与仓库产物是否一致
152
+ # 调一次 grill_start:返回里的 version 就是当前正在跑的插件版本
153
+ # 再执行一个需要确认的任务:确认门禁仍然会拦(而不是悄悄放行)
154
+ ```
155
+
156
+ ## 已知限制
157
+
158
+ - 门禁只对**根 agent**(能直接跟你对话的那个)生效;子 agent / 团队成员不拦,否则正常的委派工作流会被卡死。因此**委派类工具本身**(`subagent`、`workflow`、`spawn_teammate`…)在未确认前也会被拦,防止"派个子代理替自己动手"绕过门禁。
159
+ - `warn` 档位只能"记录不拦截":DSH 的 `tools/pre-execute` 事件没有"放行但同时给模型提示"这种返回形态。
160
+ - 记忆是本地文件,不含加密;删掉对应 json 即清空。
161
+ - `grill_confirm` 优先走 DSH 的结构化提问通道;该通道不可用时(例如子 agent 内部)会返回 `needs_user_confirmation: true`,由模型改用内置提问工具兜底。
162
+ - **改代码后必须重启 DSH**:DSH 加载的是 profile 里那份安装拷贝,而且插件**代码**改动不会热生效(实测:`cordis.patch.yml` 里的**配置**改动是热生效的,包代码不是)。判断运行中的是哪一版:调一次 `grill_start`,返回里的 `version` 字段就是当前在跑的版本;看不到 `version` 说明跑的是 0.1.0 之前的旧代码。
163
+ - 跳过关键词里**英文按词边界匹配**(`skip` 能命中 `skip`、`Skip this`、`(skip)`,但不会命中 `skipList`、`skip_list`、`bypass-keyword` 这类标识符);中文关键词按子串匹配(`直接做` 命中 `那你直接做好了`)。
164
+
165
+ ## 许可
166
+
167
+ MIT,见 [LICENSE](./LICENSE)。
package/README.repo.md ADDED
@@ -0,0 +1,161 @@
1
+ # dsh-grill-first
2
+
3
+ ![tests](https://img.shields.io/badge/tests-91%20passing-brightgreen)
4
+ ![license](https://img.shields.io/badge/license-MIT-blue)
5
+ ![DSH plugin](https://img.shields.io/badge/DSH-plugin-blueviolet)
6
+
7
+ > DSH(DeepSeek Harness)插件:**开工前先刨根问底**——把模糊需求问成明确需求,你在对话里点头之后,主任务才开工。
8
+
9
+ ## 它解决什么问题
10
+
11
+ AI 助手最常见的失败不是"不会做",而是"做完了才发现做的不是你要的"。这个插件在**任何新任务开工前**插入一道需求澄清环节:
12
+
13
+ 1. 模型先把你的任务原文交给 `grill_start` 判定;
14
+ 2. 任务模糊(缺目标 / 范围 / 约束 / 验收标准)→ 它按轮追问你,每轮 3 个问题,最多 3 轮,每轮都基于你上一轮的回答继续深挖;
15
+ 3. 问清后整理成中文四段清单(目标 / 范围 / 约束 / 验收标准),用 `grill_confirm` 请你确认;
16
+ 4. 你点"确认,按此开工"→ 本次任务放行;你点"要改"→ 它按你的反馈修订后再来一次;
17
+ 5. 一句话就能说清的小任务、以及带「直接做 / 跳过提问 / 不用问 / skip」字样的消息,**不会被打扰**。
18
+
19
+ 严格档位(`gateMode: "strict"`)下,需求没确认前模型无法执行写文件、跑命令这类**变更类**操作——从机制上保证"没问清就不许动手"。
20
+
21
+ ## 安装
22
+
23
+ 装插件前请先退出正在跑的任务。两种方式任选:
24
+
25
+ **方式一:DSH 插件市场**(推荐)
26
+
27
+ 在 DSH 设置 → 插件里搜索 `dsh-grill-first` 一键安装。
28
+
29
+ **方式二:本地包 / GitHub 仓库**
30
+
31
+ ```bash
32
+ dsh plugin --profile <你的 profile 名> add /绝对路径/dsh-grill-first-0.1.1.tgz
33
+ dsh plugin --profile <你的 profile 名> add github:InoriMutsumi/dsh-grill-first
34
+ ```
35
+
36
+ 装完**即生效**:本机实测 DSH 的 live profile 会热加载新插件,不需要重启(若你的 profile 是启动式、或列表里没出现,再完全退出 DSH 重开一次)。
37
+
38
+ ## 使用
39
+
40
+ 装好之后不需要你做什么:它靠两件事自动工作。
41
+
42
+ - **协议提示段**:插件往系统提示里注入一小段中文协议(约 600 字),告诉模型"接到新任务先调 `grill_start`"。你在系统提示里能看到 `【开工前提问协议(grill-first)】` 就说明装好了。
43
+ - **工具**:模型会用这两个工具推进澄清流程。
44
+
45
+ | 工具 | 作用 |
46
+ |---|---|
47
+ | `grill_start` | 判定任务是否需要澄清;返回 `verdict`、`focus_hints`(该问什么)、`memory_context`(已知偏好,避免重复问) |
48
+ | `grill_confirm` | 把四段式需求清单交给你确认;确认后放行任务,并把确认过的内容写进记忆 |
49
+
50
+ ### 想跳过提问时
51
+
52
+ - 消息里带上「**直接做**」「**跳过提问**」「**不用问**」「**skip**」中任意一个 → 立刻开工;
53
+ - 或者在设置里把 `enabled` 关掉 / 把 `gateMode` 调成 `off` → 整个插件静默。
54
+
55
+ ## 配置项
56
+
57
+ DSH 设置界面 → 插件 → dsh-grill-first,共 7 项:
58
+
59
+ | 配置 | 类型 | 默认 | 说明 |
60
+ |---|---|---|---|
61
+ | `enabled` | 布尔 | `true` | 总开关。关闭后既不追问也不拦截工具 |
62
+ | `gateMode` | `off` / `warn` / `strict` | `warn` | `off` 不拦截;`warn` 只在日志里记录"需求未确认";`strict` 未确认前拦截变更类工具 |
63
+ | `maxQuestionsPerRound` | 1-6 | `3` | 每轮最多问几个问题 |
64
+ | `maxRounds` | 1-10 | `3` | 最多追问几轮 |
65
+ | `intensity` | `gentle` / `standard` / `grill` | `standard` | 追问力度:只问关键点 / 标准 / 刨根问底 |
66
+ | `bypassKeywords` | 字符串数组 | `["直接做","跳过提问","不用问","skip"]` | 命中即跳过提问 |
67
+ | `memoryEnabled` | 布尔 | `true` | 是否启用两层记忆 |
68
+
69
+ > 默认是 `warn` 而不是 `strict`:默认档位保证升级插件不会突然卡住你已有的工作流。想要硬门禁请手动切 `strict`。
70
+
71
+ ### 怎么改配置
72
+
73
+ DSH 设置页里的插件条目目前只提供「启用 / 停用」开关(客户端的插件页不渲染第三方插件的配置表单),要改上面 7 项请编辑 **profile 的补丁层** `cordis.patch.yml`:
74
+
75
+ ```yaml
76
+ # <profile 目录>/cordis.patch.yml
77
+ - id: dsh-grill-first
78
+ name: dsh-grill-first
79
+ config:
80
+ gateMode: strict # off | warn | strict
81
+ maxQuestionsPerRound: 3 # 1-6
82
+ maxRounds: 3 # 1-10
83
+ intensity: standard # gentle | standard | grill
84
+ memoryEnabled: true
85
+ bypassKeywords: [直接做, 跳过提问, 不用问, skip]
86
+ ```
87
+
88
+ 改完重启 DSH 生效。只写想改的项也可以,其余项用默认值。
89
+
90
+ ## 两层记忆
91
+
92
+ 只记录**你确认过**的内容,不记录对话原文,也不会把需求清单写成文件。
93
+
94
+ | 层 | 位置 | 内容 |
95
+ |---|---|---|
96
+ | 全局层 | `<profile 目录>/grill-first/global.json`(不可写时退化到 `~/.dsh-grill-first/global.json`) | 通用偏好,如"注释一律用中文" |
97
+ | 项目层 | `<工作区>/.dsh/grill-first/memory.json` | 项目背景、任务历史(任务名 + 需求摘要 + 日期) |
98
+
99
+ 下次追问时,`grill_start` 会把这些内容作为 `memory_context` 返回,模型就不会重复问你同一件事。
100
+
101
+ ## 开发
102
+
103
+ ```bash
104
+ npm install # 装开发依赖(schemastery 由宿主提供,仅开发期需要)
105
+ npm run build # TypeScript → dist/
106
+ npm test # 构建 + 94 个单测 / 端到端场景测试
107
+ node scripts/selfcheck.mjs # 静态自检:中文 JSDoc、any 用量、需求→代码位置
108
+ ```
109
+
110
+ 推送到 GitHub 后 `.github/workflows/ci.yml` 会自动跑上面那两条(`npm ci` + `npm test`)。`dist/` 是**故意提交**的:这样 `dsh plugin add github:…` 装完即可用,不需要在你的机器上编译。
111
+
112
+ 代码结构:
113
+
114
+ ```
115
+ src/
116
+ index.ts 插件装配入口(工具 + 协议段 + 门禁)
117
+ config.ts 配置归一化(不依赖宿主,可单测)
118
+ configSchema.ts Schemastery 配置声明(设置界面用,7 项)
119
+ version.ts 从 package.json 读版本号,供 grill_start 返回
120
+ judge/heuristics.ts 任务模糊度判定(纯函数)
121
+ prompt/protocol.ts 注入系统提示的中文协议文本
122
+ tools/grillStart.ts grill_start 工具
123
+ tools/grillConfirm.ts grill_confirm 工具
124
+ gate/preExecute.ts 变更类工具门禁(tools/pre-execute + 新消息重置)
125
+ memory/store.ts 两层记忆的读写
126
+ ```
127
+
128
+ ## 兼容性与适配
129
+
130
+ 本插件对外只有两处依赖,都不归 DSH 的"插件版本门禁"管:
131
+
132
+ - **peer 依赖**:只有 `@deepseek-ai/schemastery`(`^3.18.1`,宿主自带;DSH 0.2.1-rc.2 里解析到的实际版本是 3.18.5)。
133
+ - **宿主扩展点**:cordis 上下文 API —— `ctx.tools`(注册工具)、`ctx.get('userQuestions')`(结构化提问通道)、`ctx.inject(['systemPrompt'])`、事件 `tools/pre-execute` 与 `agent/inbox/inserted`。
134
+
135
+ 它**不 import 任何 `@deepseek-ai/dsh*` 运行时包**,所以既没有 `engines.dsh`,也没有 `@deepseek-ai/dsh` peer。DSH 的 `plugin-compatibility` 检查(`dsh-app-boot` 里的 `evaluatePluginCompatibility`)只在插件声明了 `engines.dsh` 或 `@deepseek-ai/dsh*` peer 时才介入,判不过才会被拦;本插件两条都不沾,因此升 DSH 一般不需要改代码。
136
+
137
+ > 以后若要 import harness 内部包,请显式声明 peer 范围并带上预发布分支,例如
138
+ > `">=0.2.0-rc.2 <0.3.0-0 || >=0.2.1-rc.1 <0.3.0-0"`。
139
+ > DSH 自己的门禁用 `semver.satisfies(..., { includePrerelease: true })` 判定,`^0.2.0-rc.2` 能匹配 `0.2.1-rc.2`;
140
+ > 但标准 npm 语义(不加 `includePrerelease`)会判不匹配,所以上游要求写显式分支。
141
+
142
+ 已验证的宿主版本:`0.2.0-rc.2`(0.1.x 开发期)、`0.2.1-rc.2`(2026-10-17 复核:`typecheck`、94 个单测、`verify-installed` 一致,且实测加载运行)。升完 DSH 后的自检三步:
143
+
144
+ ```bash
145
+ node scripts/verify-installed.mjs # 安装拷贝与仓库产物是否一致
146
+ # 调一次 grill_start:返回里的 version 就是当前正在跑的插件版本
147
+ # 再执行一个需要确认的任务:确认门禁仍然会拦(而不是悄悄放行)
148
+ ```
149
+
150
+ ## 已知限制
151
+
152
+ - 门禁只对**根 agent**(能直接跟你对话的那个)生效;子 agent / 团队成员不拦,否则正常的委派工作流会被卡死。因此**委派类工具本身**(`subagent`、`workflow`、`spawn_teammate`…)在未确认前也会被拦,防止"派个子代理替自己动手"绕过门禁。
153
+ - `warn` 档位只能"记录不拦截":DSH 的 `tools/pre-execute` 事件没有"放行但同时给模型提示"这种返回形态。
154
+ - 记忆是本地文件,不含加密;删掉对应 json 即清空。
155
+ - `grill_confirm` 优先走 DSH 的结构化提问通道;该通道不可用时(例如子 agent 内部)会返回 `needs_user_confirmation: true`,由模型改用内置提问工具兜底。
156
+ - **改代码后必须重启 DSH**:DSH 加载的是 profile 里那份安装拷贝,而且插件**代码**改动不会热生效(实测:`cordis.patch.yml` 里的**配置**改动是热生效的,包代码不是)。判断运行中的是哪一版:调一次 `grill_start`,返回里的 `version` 字段就是当前在跑的版本;看不到 `version` 说明跑的是 0.1.0 之前的旧代码。
157
+ - 跳过关键词里**英文按词边界匹配**(`skip` 能命中 `skip`、`Skip this`、`(skip)`,但不会命中 `skipList`、`skip_list`、`bypass-keyword` 这类标识符);中文关键词按子串匹配(`直接做` 命中 `那你直接做好了`)。
158
+
159
+ ## 许可
160
+
161
+ MIT,见 [LICENSE](./LICENSE)。
@@ -0,0 +1,4 @@
1
+ # DSH bundle 补丁:把本插件插入当前 profile 的插件表(格式参照已装插件 dsh-find-plugins)。
2
+ - insert:
3
+ - id: dsh-grill-first
4
+ name: 'dsh-grill-first'
@@ -0,0 +1,28 @@
1
+ /** 门禁档位:off=不拦截、warn=只记录、strict=拦截变更类工具。 */
2
+ export type GateMode = 'off' | 'warn' | 'strict';
3
+ /** 追问力度。 */
4
+ export type Intensity = 'gentle' | 'standard' | 'grill';
5
+ /** 归一化后的插件配置。 */
6
+ export interface GrillConfig {
7
+ /** 总开关(R4):false 时任何任务都不触发提问,也不拦截。 */
8
+ enabled: boolean;
9
+ /** 门禁档位。 */
10
+ gateMode: GateMode;
11
+ /** 每轮最多问几个问题(R2)。 */
12
+ maxQuestionsPerRound: number;
13
+ /** 最多追问几轮(R2)。 */
14
+ maxRounds: number;
15
+ /** 追问力度。 */
16
+ intensity: Intensity;
17
+ /** 跳过关键词(R4)。 */
18
+ bypassKeywords: string[];
19
+ /** 是否启用两层记忆(R5)。 */
20
+ memoryEnabled: boolean;
21
+ }
22
+ /** 默认配置(与工作表 3.7 的默认值一致)。 */
23
+ export declare const DEFAULT_CONFIG: GrillConfig;
24
+ /**
25
+ * 把任意来源的配置归一化成合法配置(脏数据一律退回默认值,不抛错)。
26
+ * @param raw 来自 cordis.yml / 设置界面的原始配置。
27
+ */
28
+ export declare function normalizeConfig(raw: unknown): GrillConfig;
package/dist/config.js ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * 插件配置的类型定义与归一化。
3
+ *
4
+ * 这里刻意不 import Schemastery:单测可以直接引用本模块,
5
+ * 只有 `configSchema.ts`(设置界面用的 schema)才依赖宿主提供的 `@deepseek-ai/schemastery`。
6
+ */
7
+ import { DEFAULT_BYPASS_KEYWORDS } from './judge/heuristics.js';
8
+ /** 默认配置(与工作表 3.7 的默认值一致)。 */
9
+ export const DEFAULT_CONFIG = {
10
+ enabled: true,
11
+ gateMode: 'warn',
12
+ maxQuestionsPerRound: 3,
13
+ maxRounds: 3,
14
+ intensity: 'standard',
15
+ bypassKeywords: [...DEFAULT_BYPASS_KEYWORDS],
16
+ memoryEnabled: true,
17
+ };
18
+ /** 取整并夹到 [min, max];非法值返回 fallback。 */
19
+ function clampInt(value, min, max, fallback) {
20
+ const num = typeof value === 'number' ? value : Number(value);
21
+ if (!Number.isFinite(num) || !Number.isInteger(num))
22
+ return fallback;
23
+ return Math.min(max, Math.max(min, num));
24
+ }
25
+ /** 读布尔;非布尔返回 fallback。 */
26
+ function pickBoolean(value, fallback) {
27
+ return typeof value === 'boolean' ? value : fallback;
28
+ }
29
+ /** 读枚举;不在白名单内返回 fallback。 */
30
+ function pickEnum(value, allowed, fallback) {
31
+ return typeof value === 'string' && allowed.includes(value) ? value : fallback;
32
+ }
33
+ /** 读字符串数组;接受单个字符串;非法返回 fallback。 */
34
+ function pickStringList(value, fallback) {
35
+ if (typeof value === 'string') {
36
+ const parts = value
37
+ .split(/[,,\n]/)
38
+ .map((item) => item.trim())
39
+ .filter((item) => item !== '');
40
+ return parts.length > 0 ? parts : [...fallback];
41
+ }
42
+ if (Array.isArray(value)) {
43
+ const parts = value.filter((item) => typeof item === 'string' && item.trim() !== '');
44
+ return parts.length > 0 ? parts : [...fallback];
45
+ }
46
+ return [...fallback];
47
+ }
48
+ /**
49
+ * 把任意来源的配置归一化成合法配置(脏数据一律退回默认值,不抛错)。
50
+ * @param raw 来自 cordis.yml / 设置界面的原始配置。
51
+ */
52
+ export function normalizeConfig(raw) {
53
+ const input = (raw ?? {});
54
+ return {
55
+ enabled: pickBoolean(input.enabled, DEFAULT_CONFIG.enabled),
56
+ gateMode: pickEnum(input.gateMode, ['off', 'warn', 'strict'], DEFAULT_CONFIG.gateMode),
57
+ maxQuestionsPerRound: clampInt(input.maxQuestionsPerRound, 1, 6, DEFAULT_CONFIG.maxQuestionsPerRound),
58
+ maxRounds: clampInt(input.maxRounds, 1, 10, DEFAULT_CONFIG.maxRounds),
59
+ intensity: pickEnum(input.intensity, ['gentle', 'standard', 'grill'], DEFAULT_CONFIG.intensity),
60
+ bypassKeywords: pickStringList(input.bypassKeywords, DEFAULT_CONFIG.bypassKeywords),
61
+ memoryEnabled: pickBoolean(input.memoryEnabled, DEFAULT_CONFIG.memoryEnabled),
62
+ };
63
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * 设置界面用的配置 schema(7 个配置项,工作表 3.7)。
3
+ *
4
+ * Schemastery 由 DSH 宿主提供:这里声明为 `peerDependencies`(与已装插件 dsh-univer-office 一致——
5
+ * 它的入口 `lib/index.js` 顶部就是 `import z from "@deepseek-ai/schemastery"`)。
6
+ * 开发期从 npm 安装同版本(`@deepseek-ai/schemastery@3.18.4`)以拿到真实类型,不再手写占位声明。
7
+ * 故意与 `config.ts` 分开:本文件是唯一依赖宿主的模块,单测只引 `config.ts`。
8
+ */
9
+ import z from '@deepseek-ai/schemastery';
10
+ /** 插件配置 schema:改了这里,设置界面与 cordis.yml 校验同时生效。 */
11
+ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
12
+ enabled: z<boolean, boolean, "defined">;
13
+ gateMode: z<"off" | "warn" | "strict", "off" | "warn" | "strict", "defined">;
14
+ maxQuestionsPerRound: z<number, number, "defined">;
15
+ maxRounds: z<number, number, "defined">;
16
+ intensity: z<"gentle" | "standard" | "grill", "gentle" | "standard" | "grill", "defined">;
17
+ bypassKeywords: z<string[], string[], "defined">;
18
+ memoryEnabled: z<boolean, boolean, "defined">;
19
+ }>>, Schemastery.ObjectT<NoInfer<{
20
+ enabled: z<boolean, boolean, "defined">;
21
+ gateMode: z<"off" | "warn" | "strict", "off" | "warn" | "strict", "defined">;
22
+ maxQuestionsPerRound: z<number, number, "defined">;
23
+ maxRounds: z<number, number, "defined">;
24
+ intensity: z<"gentle" | "standard" | "grill", "gentle" | "standard" | "grill", "defined">;
25
+ bypassKeywords: z<string[], string[], "defined">;
26
+ memoryEnabled: z<boolean, boolean, "defined">;
27
+ }>>, "plain">;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * 设置界面用的配置 schema(7 个配置项,工作表 3.7)。
3
+ *
4
+ * Schemastery 由 DSH 宿主提供:这里声明为 `peerDependencies`(与已装插件 dsh-univer-office 一致——
5
+ * 它的入口 `lib/index.js` 顶部就是 `import z from "@deepseek-ai/schemastery"`)。
6
+ * 开发期从 npm 安装同版本(`@deepseek-ai/schemastery@3.18.4`)以拿到真实类型,不再手写占位声明。
7
+ * 故意与 `config.ts` 分开:本文件是唯一依赖宿主的模块,单测只引 `config.ts`。
8
+ */
9
+ import z from '@deepseek-ai/schemastery';
10
+ import { DEFAULT_BYPASS_KEYWORDS } from './judge/heuristics.js';
11
+ /** 门禁档位 schema。 */
12
+ const gateModeSchema = z.union([z.const('off'), z.const('warn'), z.const('strict')]).default('warn');
13
+ /** 追问力度 schema。 */
14
+ const intensitySchema = z.union([z.const('gentle'), z.const('standard'), z.const('grill')]).default('standard');
15
+ /** 插件配置 schema:改了这里,设置界面与 cordis.yml 校验同时生效。 */
16
+ export const Config = z.object({
17
+ enabled: z.boolean().default(true).description('总开关:关闭后任何任务都不再追问,也不拦截工具。'),
18
+ gateMode: gateModeSchema.description('门禁档位:off 不拦截 / warn 只记录 / strict 未确认需求前拦截写类工具。'),
19
+ maxQuestionsPerRound: z
20
+ .natural()
21
+ .max(6)
22
+ .default(3)
23
+ .description('每轮最多问几个问题(1-6)。'),
24
+ maxRounds: z.natural().max(10).default(3).description('最多追问几轮(1-10)。'),
25
+ intensity: intensitySchema.description('追问力度:gentle 只问关键点 / standard 标准 / grill 刨根问底。'),
26
+ bypassKeywords: z
27
+ .array(z.string())
28
+ .default([...DEFAULT_BYPASS_KEYWORDS])
29
+ .description('跳过关键词:用户消息里出现这些词就不再追问,直接开工。'),
30
+ memoryEnabled: z.boolean().default(true).description('是否启用两层记忆(全局偏好 + 项目背景/历史)。'),
31
+ });
@@ -0,0 +1,70 @@
1
+ /**
2
+ * 需求门禁(R3/R4 的执行层):未确认需求前拦住"实质变更类"工具。
3
+ *
4
+ * 三点设计约束(阶段 1 实测):
5
+ * 1. `tools/pre-execute` 是 waterfall:不拦截时必须显式 `return next()`;
6
+ * 2. 只有"能跟人对话的根 agent"才纳入门禁——子 agent/teammate 由父 agent 派生,
7
+ * 它们没有人类对话方(`userQuestions.ask` 对它们会抛 DELEGATED_CALLER),
8
+ * 若一并拦截会把正常的委派工作流卡死;
9
+ * 3. warn 模式只记录不拦截(pre-execute 没有"放行 + 附带提示"这种返回形态)。
10
+ */
11
+ import type { GrillContext } from '../types.js';
12
+ /** 门禁档位。 */
13
+ export type GateMode = 'off' | 'warn' | 'strict';
14
+ /** 门禁状态:open = 已确认或无需确认;closed = 尚未确认。 */
15
+ export type GateState = 'open' | 'closed';
16
+ /** 变更类工具名单(宁松勿紧)。 */
17
+ export declare const MUTATING_TOOLS: readonly string[];
18
+ /** 只读工具白名单:即使名字落在 univer_/ops_ 前缀里也一律放行。 */
19
+ export declare const READ_ONLY_TOOLS: readonly string[];
20
+ /** 拦截时给模型看的拒绝文案(工作表 3.6 原文)。 */
21
+ export declare const GATE_DENY_REASON = "\u9700\u6C42\u5C1A\u672A\u786E\u8BA4\uFF1A\u8BF7\u5148\u8C03\u7528 grill_confirm \u5B8C\u6210\u9700\u6C42\u786E\u8BA4\uFF0C\u6216\u8BA9\u7528\u6237\u8BF4'\u76F4\u63A5\u505A'\u8DF3\u8FC7\u3002";
22
+ /**
23
+ * 判断某个工具名是否属于"会真正改动工作区/交付物"的变更类工具。
24
+ * @param name 工具名。
25
+ */
26
+ export declare function isMutatingTool(name: string): boolean;
27
+ /** 门禁判定结果。 */
28
+ export interface GateDecision {
29
+ action: 'allow' | 'deny';
30
+ reason?: string;
31
+ }
32
+ /**
33
+ * 门禁核心判定(纯函数,可单测)。
34
+ * @param input 工具名 + 当前门禁状态 + 档位。
35
+ */
36
+ export declare function decideGate(input: {
37
+ toolName: string;
38
+ state: GateState;
39
+ mode: GateMode;
40
+ }): GateDecision;
41
+ /** 每个 agent 的门禁状态表(agent 级隔离)。 */
42
+ export declare class GateRegistry {
43
+ private readonly states;
44
+ /** 读取状态;未登记的 agent 默认 closed(新会话即"未确认")。 */
45
+ stateOf(agentId: string): GateState;
46
+ /** 放行该 agent。 */
47
+ open(agentId: string): void;
48
+ /** 拦截该 agent(新用户消息到达时重置)。 */
49
+ close(agentId: string): void;
50
+ /** 清空(插件卸载时调用)。 */
51
+ clear(): void;
52
+ }
53
+ /**
54
+ * 判断某个 agent 是否是"根 agent"(能直接跟人对话)。
55
+ * agents 服务不可用时返回 false —— 门禁退化为不拦截,避免误伤子 agent。
56
+ */
57
+ export declare function isRootAgent(ctx: GrillContext, agentId: string): boolean;
58
+ /** 注册门禁所需的回调。 */
59
+ export interface GateOptions {
60
+ mode: GateMode;
61
+ /** warn 模式下的提示回调(默认写日志)。 */
62
+ onWarning?: (toolName: string, agentId: string) => void;
63
+ }
64
+ /**
65
+ * 把门禁挂到宿主事件上:`tools/pre-execute` 拦截 + `agent/inbox/inserted` 重置。
66
+ * @param ctx 插件上下文。
67
+ * @param gates 门禁状态表。
68
+ * @param options 档位与提示回调。
69
+ */
70
+ export declare function registerGate(ctx: GrillContext, gates: GateRegistry, options: GateOptions): void;