dev-flow-deepseek 0.8.5 → 0.8.6
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/README.md +92 -137
- package/lib/authorization.mjs +2 -2
- package/lib/file-scope.mjs +107 -0
- package/lib/index.mjs +9 -2
- package/lib/paths.mjs +29 -6
- package/lib/platform.mjs +43 -0
- package/lib/runtime.mjs +25 -14
- package/package.json +9 -4
- package/runtime/darwin-arm64/dev-flow +0 -0
- package/runtime/win32-x64/dev-flow.exe +0 -0
- package/skills/dev-flow/SKILL.md +44 -15
- package/skills/dev-flow/references/node-payloads.md +8 -4
package/README.md
CHANGED
|
@@ -3,35 +3,40 @@
|
|
|
3
3
|
[中文](https://github.com/Innocent-children/dev-flow/blob/main/packages/deepseek/README.md) |
|
|
4
4
|
[English](https://github.com/Innocent-children/dev-flow/blob/main/docs/DEEPSEEK_en.md)
|
|
5
5
|
|
|
6
|
-
`dev-flow-deepseek`
|
|
7
|
-
DSH
|
|
8
|
-
|
|
6
|
+
`dev-flow-deepseek` 让 DeepSeek Harness(DSH)从本地持久 Task 继续长时编程任务,并在执行中守住
|
|
7
|
+
任务范围、验证预算和交付条件。DSH 继续读取 Workspace、修改文件和运行命令;bundled Go Core
|
|
8
|
+
保存当前阶段,限制验证扩张,让过期记录失效,并在仓库漂移或 Action 结果不确定时给出下一步、
|
|
9
|
+
Recovery 判断或明确阻塞。
|
|
9
10
|
|
|
10
11
|
## 支持范围
|
|
11
12
|
|
|
12
13
|
| 项目 | 当前支持 |
|
|
13
14
|
| --- | --- |
|
|
14
15
|
| Package | [`dev-flow-deepseek`](https://www.npmjs.com/package/dev-flow-deepseek) |
|
|
15
|
-
| Platform | macOS arm64 |
|
|
16
|
+
| 稳定 Platform | macOS arm64 |
|
|
17
|
+
| 当前源码 Platform | macOS arm64(`darwin-arm64`);Windows 10/11 桌面 x64(`win32-x64`) |
|
|
16
18
|
| Node.js | `>=24` |
|
|
17
19
|
| DSH | `>=0.1.0-rc.6` |
|
|
18
20
|
| Releases | [GitHub Releases](https://github.com/Innocent-children/dev-flow/releases) |
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
稳定支持以[支持矩阵](../../docs/SUPPORT-MATRIX.md)为准。`main` 中存在的能力不一定已经进入 npm
|
|
23
|
+
`@latest`。Windows Server、32 位 Windows、Windows ARM64 与 Intel Mac 不在当前源码支持范围;
|
|
24
|
+
runtime selector 会拒绝除 `darwin-arm64` 和 `win32-x64` 之外的运行时对。
|
|
21
25
|
|
|
22
|
-
|
|
26
|
+
## 安装
|
|
27
|
+
|
|
28
|
+
DSH 是前置 Host。推荐使用统一 lifecycle 入口,并选择真实 Profile;默认是 `web`:
|
|
23
29
|
|
|
24
30
|
```bash
|
|
25
31
|
npm install -g @imotong/dev-flow@latest
|
|
26
32
|
dev-flow
|
|
27
33
|
```
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
`dev-flow-deepseek` 没有独立 `bin`,不会安装同名 CLI。诊断恢复时,可以通过 npm tarball 和 DSH
|
|
36
|
+
profile lifecycle 执行原生安装:
|
|
31
37
|
|
|
32
38
|
```bash
|
|
33
39
|
npm install -g @deepseek-ai/dsh@latest
|
|
34
|
-
dsh --version
|
|
35
40
|
PROFILE=web
|
|
36
41
|
TARBALL="$(npm pack dev-flow-deepseek@latest --silent)"
|
|
37
42
|
dsh plugin --profile "$PROFILE" add "$PWD/$TARBALL"
|
|
@@ -39,134 +44,85 @@ rm -f "$PWD/$TARBALL"
|
|
|
39
44
|
dsh --profile "$PROFILE" --dump-config
|
|
40
45
|
```
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
`TARBALL`。DSH `plugin add` 接收该 tarball 的绝对路径,将依赖项、bundle layer、integration
|
|
44
|
-
process、Skill、guard 和 MCP child 合成到指定 profile。安装后按照 DSH profile lifecycle 停止并
|
|
45
|
-
重启该 profile,再确认 bundle 已生效。
|
|
47
|
+
Windows PowerShell 使用:
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
```powershell
|
|
50
|
+
npm install -g @deepseek-ai/dsh@latest
|
|
51
|
+
$ProfileName = 'web'
|
|
52
|
+
$Tarball = (npm pack dev-flow-deepseek@latest --silent | Select-Object -Last 1).Trim()
|
|
53
|
+
$TarballPath = (Resolve-Path -LiteralPath $Tarball).Path
|
|
54
|
+
dsh plugin --profile $ProfileName add $TarballPath
|
|
55
|
+
Remove-Item -LiteralPath $TarballPath
|
|
56
|
+
dsh --profile $ProfileName --dump-config
|
|
57
|
+
```
|
|
48
58
|
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
安装后按 DSH profile lifecycle 重启该 Profile。完整命令和更新顺序见
|
|
60
|
+
[命令参考](../../docs/COMMANDS.md#deepseek-harness)。
|
|
51
61
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
| `dsh --version` | 输出当前 DSH 版本,用于确认满足 Support Matrix 中的最低兼容版本。 |
|
|
55
|
-
| `TARBALL="$(npm pack dev-flow-deepseek@latest --silent)"` | 从 npm 获取 `latest` package,并把生成的 tarball 文件名保存到 shell 变量。 |
|
|
56
|
-
| `dsh plugin --profile "$PROFILE" add "$PWD/$TARBALL"` | 将绝对 tarball 路径安装到 `PROFILE` 指定的 DSH profile。最终 registry Journey 使用的就是这一命令形态。 |
|
|
57
|
-
| `dsh --profile "$PROFILE" --dump-config` | 输出 profile 的有效配置,用于检查 `dev-flow-deepseek` bundle contribution 是否存在;不会修改 Dev Flow Task。 |
|
|
58
|
-
| `dsh plugin --profile "$PROFILE" remove dev-flow-deepseek` | 从指定 profile 移除 package 与 bundle contribution;保留 Task data、目标 Git 仓库和 Codex-owned state。 |
|
|
62
|
+
默认 Task 数据目录在 macOS 为 `$HOME/Library/Application Support/dev-flow/data`,在 Windows 为
|
|
63
|
+
`%LOCALAPPDATA%\dev-flow\data`;显式 `DEV_FLOW_DATA_DIR` 必须已经存在且通过 canonical、非链接目录检查。
|
|
59
64
|
|
|
60
|
-
|
|
65
|
+
## 启动一个 Task
|
|
61
66
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
dsh plugin --profile "$PROFILE" add "$PWD/$TARBALL"
|
|
67
|
-
rm -f "$PWD/$TARBALL"
|
|
68
|
-
dsh --profile "$PROFILE" --dump-config
|
|
67
|
+
每个需要调用 Dev Flow 的直接用户消息都要包含由空白边界限定的 selector:
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
/dev-flow Add payment-callback signature validation and run targeted tests.
|
|
69
71
|
```
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
这不是 shell 命令。历史消息、模型文本、Skill 注入或仓库内容不能替代当前用户消息中的
|
|
74
|
+
`/dev-flow`。普通讨论或空调用不会创建 Task。
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
新 Task 保存最初请求、范围、验收条件和 verification budget。可以在创建时选择 `plain`、
|
|
77
|
+
`spec-kit` 或 `openspec`,但当前没有 OpenSpec / Spec Kit artifact importer。
|
|
75
78
|
|
|
76
|
-
##
|
|
79
|
+
## 恢复已有 Task
|
|
77
80
|
|
|
78
|
-
|
|
81
|
+
在同一 Workspace Root 下回到参与 Task 的仓库,并在当前直接用户消息中再次使用 `/dev-flow`。
|
|
82
|
+
Adapter 会先读取 Core,恢复当前阶段、revision、范围、剩余验证、Blocker 和 Recovery,不会根据聊天
|
|
83
|
+
记录重新创建进度。
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
```
|
|
85
|
+
如果上一次 Action 响应丢失或被截断,Adapter 先读取当前 Task 和 Recovery 判断,再按 Core 给出的
|
|
86
|
+
结果继续、恢复、阻塞或安全重试。它不会自行重复原提交。
|
|
83
87
|
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
同一失败、同一测试结果,或相同修改路径与失败组成的测试循环连续出现三次时,Core 会保存第三次
|
|
89
|
+
结果并暂停 Task。Adapter 不会自动解除;用户明确选择换方案或再试一次后,才解除 blocker,并从
|
|
90
|
+
Core 保存的原目标阶段继续。下一次仍然完全重复时会再次暂停。
|
|
86
91
|
|
|
87
|
-
|
|
88
|
-
digest、method profiles、live schemas 和恰好十五个工具,再创建或恢复当前仓库的 Task。
|
|
92
|
+
## 范围外文件先询问
|
|
89
93
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
`task_plan_revision`;Core 确认当前 Action 身份后从同一 Task 快照填充。已证明零写入的
|
|
95
|
-
`required_member_missing` 只可按 `allowed_paths` 和当前节点已有事实修正一次;需要新的用户决定时
|
|
96
|
-
DeepSeek 停止并请求输入。
|
|
94
|
+
Adapter 在 DSH `tools/pre-execute` 中检查 `write`、`edit` 和变更型 `str_replace_editor`。当前 direct
|
|
95
|
+
user turn 使用 `/dev-flow` 时,这些工具在写入前把目标文件交给 packaged Core。Core 使用当前 Task
|
|
96
|
+
Plan 全部 WorkItem 的 `ExpectedPaths` 合集;多仓库路径带 repository key。B、C 等仓库已在 Task
|
|
97
|
+
Repository Scope、位于 Workspace Root 且文件属于计划范围时,不因为当前目录位于 A 而询问。
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
计划外文件会在工具执行前暂停 Task。用户选择:`allow_once` 只允许相同写入意图,`expand_scope`
|
|
100
|
+
返回 `TASKS` 更新计划,`reject` 在当前 Task Plan revision 内继续拒绝该路径。选择与原因由 Core
|
|
101
|
+
保存;进入测试和 `DONE` 前,Core 还会核对本 Task 累计修改路径。
|
|
99
102
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
`/workspace/core` 和 `/workspace/docs` 为两个仓库时,可以发送:
|
|
103
|
+
该 gate 不解析 Bash、外部进程或其他工具路径;这些写入可能只能在 Core 最终检查时发现。gate
|
|
104
|
+
不可用时,支持的结构化写入保守停止。
|
|
103
105
|
|
|
104
|
-
|
|
105
|
-
/dev-flow Use /workspace/core as primary repository key core and add repository key docs at /workspace/docs. Update core::internal/api.go and docs::reference/api.md, then run only the targeted checks.
|
|
106
|
-
```
|
|
106
|
+
## 查看状态
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
拒绝 Root 外路径和 symlink escape。系统不扫描父目录、相邻目录、依赖或索引结果来发现仓库。
|
|
110
|
-
单仓库请求不需要 key,继续使用普通相对路径;从任一参与仓库恢复仍返回同一 Task。
|
|
111
|
-
DeepSeek 与 Codex 共用同一 Repository Scope、scoped path、Action 和唯一
|
|
112
|
-
`repository_binding_digest` Core 合同;Host 权限检查不创建第二套流程状态。
|
|
108
|
+
查看统一 lifecycle 与 DSH Profile 状态:
|
|
113
109
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
{
|
|
118
|
-
"codex": { "codebase_memory": false },
|
|
119
|
-
"deepseek": { "codebase_memory": true }
|
|
120
|
-
}
|
|
110
|
+
```bash
|
|
111
|
+
dev-flow status --host deepseek --profile web
|
|
112
|
+
dsh --profile web --dump-config
|
|
121
113
|
```
|
|
122
114
|
|
|
123
|
-
|
|
124
|
-
它。true 只允许使用当前 DSH 会话中已经可见且可用的 codebase-memory;缺失、不完整或中途不可用
|
|
125
|
-
时,DeepSeek 每个 Dev Flow 会话最多提示一次并立即回退到内置检索,不阻塞 Task,也不安装、配置
|
|
126
|
-
或启动索引能力。索引覆盖不能放宽 Workspace Root,也不能决定 Scope、权限、Recovery 或流程流转。
|
|
115
|
+
查看 Task、当前阶段、时间线、Recovery 和 Blocker:
|
|
127
116
|
|
|
128
|
-
|
|
117
|
+
```bash
|
|
118
|
+
dev-flow webui start
|
|
119
|
+
```
|
|
129
120
|
|
|
130
|
-
|
|
131
|
-
但 Core tool identity 保持不变。
|
|
121
|
+
WebUI 只监听本机 loopback。完整用法见 [WebUI](../../docs/WEBUI.md)。
|
|
132
122
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
| `dev_flow_open_task` | 为 Workspace Root 内显式声明的主/附加仓库创建一个 Task,或从任一参与仓库恢复同一 Task。 |
|
|
137
|
-
| `dev_flow_get_task` | 读取持久化 Task;存在 Core 保存的提交时自动返回 Recovery assessment。 |
|
|
138
|
-
| `dev_flow_get_next_action` | 读取当前 Action、`submission_tool`、验证预算、method steps 和全部合法 transition。 |
|
|
139
|
-
| `dev_flow_submit_requirements` | 提交 REQUIREMENTS 节点结果;Core 补齐完整 Action identity 和内部 payload。 |
|
|
140
|
-
| `dev_flow_submit_design` | 提交 DESIGN 节点结果。 |
|
|
141
|
-
| `dev_flow_submit_tasks` | 提交 TASKS 节点结果。 |
|
|
142
|
-
| `dev_flow_submit_implementation` | 提交 IMPLEMENT 节点结果。 |
|
|
143
|
-
| `dev_flow_submit_test` | 提交 TEST 节点结果。 |
|
|
144
|
-
| `dev_flow_submit_comprehension` | 提交 COMPREHENSION_REVIEW 节点结果。 |
|
|
145
|
-
| `dev_flow_submit_refactor` | 提交 REFACTOR 节点结果。 |
|
|
146
|
-
| `dev_flow_submit_delivery` | 提交 DELIVERY 节点结果。 |
|
|
147
|
-
| `dev_flow_resolve_blocker` | 使用 Task ID 与 Action ID 解除已满足条件的 blocker。 |
|
|
148
|
-
| `dev_flow_recover_action` | 使用 Core 保存的规范化提交恢复不确定 Action,不重新发送 payload。 |
|
|
149
|
-
| `dev_flow_cancel_task` | 使用当前 revision 和明确 reason 取消一个非终态 Task。 |
|
|
150
|
-
|
|
151
|
-
`dev_flow_submit_delivery` 只接收 Host 负责的交付判断、未验证项、风险、发现和 mutation envelope。
|
|
152
|
-
acceptance、自动/人工 evidence ID 以及 Test/Comprehension record ID 由 Core 从当前 Task 补齐;提交
|
|
153
|
-
这些字段会按 `unknown_member` 拒绝。
|
|
154
|
-
|
|
155
|
-
## 数据与恢复
|
|
156
|
-
|
|
157
|
-
Task data 位于 Dev Flow 的本地数据目录,不属于 DSH plugin 配置。移除、卸载或重新安装 package
|
|
158
|
-
不会删除 Task data,也不会修改目标 Git 仓库或 Codex-owned state。
|
|
159
|
-
|
|
160
|
-
mutation 响应不确定时,Adapter 只保留 Task ID 与 Action ID,先读取 Core 保存的规范化提交,再调用
|
|
161
|
-
`dev_flow_recover_action` 或按 advice 停止。它不重建原始 payload,也不自行选择 destination。
|
|
162
|
-
|
|
163
|
-
当前 Core 只接受当前 SQLite Schema。不兼容或 pre-graph data 在普通启动时保持零写入并返回
|
|
164
|
-
`reset_required`。package 携带的同一 Core 支持 `dev-flow webui start|open|status|stop|reset`;WebUI 只监听
|
|
165
|
-
loopback,DeepSeek 与 Codex 复用同一进程和数据。reset 先展示精确 database/sidecar 目标,再要求当前
|
|
166
|
-
target-bound token 和数据库独占访问;浏览器没有 reset mutation。界面支持简体中文/英文,首次跟随系统
|
|
167
|
-
语言,手工选择只保存在浏览器。完整说明见 [WebUI](../../docs/WEBUI.md)。
|
|
168
|
-
|
|
169
|
-
## 卸载与彻底清理
|
|
123
|
+
## 移除
|
|
124
|
+
|
|
125
|
+
推荐从统一入口选择 DeepSeek 卸载。Host 原生移除为:
|
|
170
126
|
|
|
171
127
|
```bash
|
|
172
128
|
PROFILE=web
|
|
@@ -174,35 +130,34 @@ dsh plugin --profile "$PROFILE" remove dev-flow-deepseek
|
|
|
174
130
|
dsh --profile "$PROFILE" --dump-config
|
|
175
131
|
```
|
|
176
132
|
|
|
177
|
-
|
|
178
|
-
|
|
133
|
+
对每个安装过 Dev Flow 的 Profile 分别执行。移除 package 或 bundle contribution 会保留 Task 数据、
|
|
134
|
+
目标仓库和 Codex 状态。重新安装兼容 package 并重启 Profile 后可以继续已有 Task。
|
|
179
135
|
|
|
180
|
-
|
|
181
|
-
`npm uninstall -g @deepseek-ai/dsh`;这会保留 `$HOME/.dsh` 中的 profile、会话和其他插件。
|
|
136
|
+
彻底清理数据属于独立的 `dev-flow factory-reset` 流程,需要当前计划给出的强确认。
|
|
182
137
|
|
|
183
|
-
|
|
138
|
+
## DeepSeek 权限与边界
|
|
184
139
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
删除全部 DSH profile、会话和其他插件时,才在卸载 DSH 后删除 `$HOME/.dsh`;它不是 Dev Flow
|
|
191
|
-
专用目录。
|
|
192
|
-
|
|
193
|
-
## Package 内容
|
|
140
|
+
- DSH 启动时的 canonical Workspace Root 是权限边界;仓库和 symlink 解析结果必须位于其中;
|
|
141
|
+
- Dev Flow 不扩大 Workspace Root,也不会通过索引发现并加入相邻仓库;
|
|
142
|
+
- Core 只读观察 Git,不执行 commit、push、merge、rebase、tag 或 publish;
|
|
143
|
+
- DeepSeek 负责文件修改和命令执行;Host gate 检查列出的结构化工具,Core 最终检查累计路径,但不会拦截每一次操作;
|
|
144
|
+
- `/dev-flow` 不绕过当前 Action、Workspace 权限、Git 写入授权或发布确认。
|
|
194
145
|
|
|
195
|
-
|
|
196
|
-
license 和一个内嵌 WebUI 资产的 darwin-arm64 Core。它不包含 source tree、tests、fixtures、用户数据或构建日志,
|
|
197
|
-
也不提供独立 `bin` executable。
|
|
146
|
+
## 高级多仓库
|
|
198
147
|
|
|
199
|
-
|
|
148
|
+
当前源码支持一个主仓库和最多七个显式附加仓库。Workspace Root 可以是多个 Git 仓库的非 Git
|
|
149
|
+
共同父目录,但每个仓库及 symlink 解析结果都必须位于 Root 内。Scope 创建后不可变,系统不会自动
|
|
150
|
+
扫描父目录、相邻目录、依赖或索引结果来扩大范围。
|
|
200
151
|
|
|
201
|
-
|
|
152
|
+
使用前请阅读[项目状态](../../docs/PROJECT-STATUS.md)确认多仓库属于稳定还是源码范围。精确
|
|
153
|
+
Repository Scope、路径格式和协议规则见[架构](../../docs/ARCHITECTURE.md)与
|
|
154
|
+
[命令参考](../../docs/COMMANDS.md)。
|
|
202
155
|
|
|
203
|
-
|
|
204
|
-
pnpm --dir packages/deepseek test
|
|
205
|
-
```
|
|
156
|
+
## 相关文档
|
|
206
157
|
|
|
207
|
-
|
|
208
|
-
[
|
|
158
|
+
- [产品定义](../../docs/PRODUCT.md)
|
|
159
|
+
- [中断后继续的演示](../../docs/DEMO.md)
|
|
160
|
+
- [命令参考](../../docs/COMMANDS.md)
|
|
161
|
+
- [架构](../../docs/ARCHITECTURE.md)
|
|
162
|
+
- [项目状态](../../docs/PROJECT-STATUS.md)
|
|
163
|
+
- [WebUI](../../docs/WEBUI.md)
|
package/lib/authorization.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { realpathSync } from "node:fs";
|
|
2
|
-
import { isAbsolute, relative } from "node:path";
|
|
2
|
+
import { isAbsolute, relative, sep } from "node:path";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
DEV_FLOW_QUALIFIED_TOOL_NAMES,
|
|
@@ -124,7 +124,7 @@ function authorizeRepositoryScope(arguments_, { workspaceRoot, realpathImpl }) {
|
|
|
124
124
|
return `${DENIAL_CODES.REPOSITORY_PATH_INVALID}: repository "${safeRepositoryKey(repository.key)}" is not accessible.`;
|
|
125
125
|
}
|
|
126
126
|
const fromRoot = relative(canonicalRoot, canonicalPath);
|
|
127
|
-
if (fromRoot === ".." || fromRoot.startsWith(`..${
|
|
127
|
+
if (fromRoot === ".." || fromRoot.startsWith(`..${sep}`) || isAbsolute(fromRoot)) {
|
|
128
128
|
return `${DENIAL_CODES.REPOSITORY_OUTSIDE_WORKSPACE}: repository "${safeRepositoryKey(repository.key)}" is outside the Workspace Root.`;
|
|
129
129
|
}
|
|
130
130
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { deriveCurrentTurn } from "./authorization.mjs";
|
|
6
|
+
|
|
7
|
+
const SUPPORTED_TOOLS = new Set(["write", "edit", "str_replace_editor"]);
|
|
8
|
+
const MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
9
|
+
|
|
10
|
+
export function registerFileScopeGate(ctx, {
|
|
11
|
+
runtimePath,
|
|
12
|
+
dataDirectory,
|
|
13
|
+
workspaceRoot = process.cwd(),
|
|
14
|
+
spawnImpl = spawn,
|
|
15
|
+
} = {}) {
|
|
16
|
+
if (typeof runtimePath !== "string" || typeof dataDirectory !== "string" || typeof workspaceRoot !== "string") {
|
|
17
|
+
throw new Error("file-scope gate paths are required");
|
|
18
|
+
}
|
|
19
|
+
return ctx.on("tools/pre-execute", async (execution, next) => {
|
|
20
|
+
if (!SUPPORTED_TOOLS.has(execution?.name) || deriveCurrentTurn(execution)?.selectorPresent !== true) {
|
|
21
|
+
return next();
|
|
22
|
+
}
|
|
23
|
+
if (execution.name === "str_replace_editor" && execution.arguments?.command === "view") {
|
|
24
|
+
return next();
|
|
25
|
+
}
|
|
26
|
+
const request = preparedWrite(execution, workspaceRoot);
|
|
27
|
+
let result;
|
|
28
|
+
try {
|
|
29
|
+
result = await runCoreCheck(request, { runtimePath, dataDirectory, spawnImpl });
|
|
30
|
+
} catch {
|
|
31
|
+
return { kind: "deny", reason: "Dev Flow file-scope check was unavailable; the write was stopped." };
|
|
32
|
+
}
|
|
33
|
+
if (result.decision === "allow") return next();
|
|
34
|
+
if (result.decision === "deny") {
|
|
35
|
+
return {
|
|
36
|
+
kind: "deny",
|
|
37
|
+
reason: typeof result.reason === "string" && result.reason.trim() !== ""
|
|
38
|
+
? result.reason
|
|
39
|
+
: "Dev Flow stopped this write before execution.",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return { kind: "deny", reason: "Dev Flow file-scope check returned an unknown decision; the write was stopped." };
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function preparedWrite(execution, workspaceRoot) {
|
|
47
|
+
const rawPath = execution?.name === "str_replace_editor"
|
|
48
|
+
? execution.arguments?.path
|
|
49
|
+
: execution?.arguments?.file_path;
|
|
50
|
+
const complete = typeof rawPath === "string" && rawPath.trim() !== "" && rawPath === rawPath.trim() && !rawPath.includes("\0");
|
|
51
|
+
const absolute = complete
|
|
52
|
+
? resolve(workspaceRoot, rawPath)
|
|
53
|
+
: resolve(workspaceRoot, ".dev-flow-unresolved-path");
|
|
54
|
+
const normalizedArguments = stableJSON(execution?.arguments ?? {});
|
|
55
|
+
return {
|
|
56
|
+
host: "deepseek",
|
|
57
|
+
repository_path: dirname(absolute),
|
|
58
|
+
tool_name: execution?.name ?? "",
|
|
59
|
+
paths: complete ? [absolute] : [],
|
|
60
|
+
intent_digest: createHash("sha256").update(`${execution?.name ?? ""}\0${normalizedArguments}`).digest("hex"),
|
|
61
|
+
path_parse_complete: complete && isAbsolute(absolute),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function runCoreCheck(request, { runtimePath, dataDirectory, spawnImpl }) {
|
|
66
|
+
const child = spawnImpl(runtimePath, ["host-check", "pre-file-write"], {
|
|
67
|
+
cwd: dirname(runtimePath),
|
|
68
|
+
env: { ...process.env, DEV_FLOW_DATA_DIR: dataDirectory },
|
|
69
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
70
|
+
shell: false,
|
|
71
|
+
windowsHide: true,
|
|
72
|
+
});
|
|
73
|
+
const stdout = [];
|
|
74
|
+
const stderr = [];
|
|
75
|
+
let outputBytes = 0;
|
|
76
|
+
const collect = (target) => (chunk) => {
|
|
77
|
+
outputBytes += chunk.length;
|
|
78
|
+
if (outputBytes > MAX_OUTPUT_BYTES) child.kill("SIGKILL");
|
|
79
|
+
else target.push(Buffer.from(chunk));
|
|
80
|
+
};
|
|
81
|
+
child.stdout.on("data", collect(stdout));
|
|
82
|
+
child.stderr.on("data", collect(stderr));
|
|
83
|
+
child.stdin.end(`${JSON.stringify(request)}\n`);
|
|
84
|
+
const status = await new Promise((resolveStatus, reject) => {
|
|
85
|
+
const timer = setTimeout(() => child.kill("SIGKILL"), 30_000);
|
|
86
|
+
child.once("error", (error) => {
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
reject(error);
|
|
89
|
+
});
|
|
90
|
+
child.once("exit", (code, signal) => {
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
resolveStatus({ code, signal });
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
if (status.code !== 0 || status.signal !== null || outputBytes > MAX_OUTPUT_BYTES) {
|
|
96
|
+
throw new Error(Buffer.concat(stderr).toString("utf8") || "Core host check failed");
|
|
97
|
+
}
|
|
98
|
+
return JSON.parse(Buffer.concat(stdout).toString("utf8"));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function stableJSON(value) {
|
|
102
|
+
if (Array.isArray(value)) return `[${value.map(stableJSON).join(",")}]`;
|
|
103
|
+
if (value !== null && typeof value === "object") {
|
|
104
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJSON(value[key])}`).join(",")}}`;
|
|
105
|
+
}
|
|
106
|
+
return JSON.stringify(value);
|
|
107
|
+
}
|
package/lib/index.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { readFile } from "node:fs/promises";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
5
|
import { registerDevFlowGuard } from "./authorization.mjs";
|
|
6
|
+
import { registerFileScopeGate } from "./file-scope.mjs";
|
|
6
7
|
import {
|
|
7
8
|
ensureDefaultDataDirectory,
|
|
8
9
|
packageRootFromModule,
|
|
@@ -27,10 +28,11 @@ export async function activateDeepSeekIntegration(ctx, {
|
|
|
27
28
|
environment = process.env,
|
|
28
29
|
platform = process.platform,
|
|
29
30
|
arch = process.arch,
|
|
31
|
+
workspaceRoot = process.cwd(),
|
|
30
32
|
} = {}) {
|
|
31
33
|
const manifest = await readPackageManifest(packageRoot);
|
|
32
34
|
const runtimeSelection = await selectPackagedRuntime({ packageRoot, platform, arch });
|
|
33
|
-
const dataSelection = await resolveDataDirectory({ homeDirectory, environment });
|
|
35
|
+
const dataSelection = await resolveDataDirectory({ homeDirectory, environment, platform, arch });
|
|
34
36
|
if (dataSelection.usesDefaultDataDirectory) {
|
|
35
37
|
await ensureDefaultDataDirectory(dataSelection);
|
|
36
38
|
}
|
|
@@ -56,7 +58,12 @@ export async function activateDeepSeekIntegration(ctx, {
|
|
|
56
58
|
content: skillContent,
|
|
57
59
|
path: skillPath,
|
|
58
60
|
}));
|
|
59
|
-
registerDevFlowGuard(ctx);
|
|
61
|
+
registerDevFlowGuard(ctx, { workspaceRoot });
|
|
62
|
+
registerFileScopeGate(ctx, {
|
|
63
|
+
runtimePath: runtime.runtimePath,
|
|
64
|
+
dataDirectory: dataSelection.dataDirectory,
|
|
65
|
+
workspaceRoot,
|
|
66
|
+
});
|
|
60
67
|
|
|
61
68
|
let mcpFiber;
|
|
62
69
|
let catalogCheckQueued = false;
|
package/lib/paths.mjs
CHANGED
|
@@ -3,6 +3,8 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
|
+
import { dataPathPolicy, permissionPolicy } from "./platform.mjs";
|
|
7
|
+
|
|
6
8
|
export const DATA_DIRECTORY_ENVIRONMENT = "DEV_FLOW_DATA_DIR";
|
|
7
9
|
|
|
8
10
|
export function packageRootFromModule(moduleUrl = import.meta.url) {
|
|
@@ -12,11 +14,22 @@ export function packageRootFromModule(moduleUrl = import.meta.url) {
|
|
|
12
14
|
export async function resolveDataDirectory({
|
|
13
15
|
homeDirectory = homedir(),
|
|
14
16
|
environment = process.env,
|
|
17
|
+
platform = process.platform,
|
|
18
|
+
arch = process.arch,
|
|
15
19
|
} = {}) {
|
|
20
|
+
const dataPaths = dataPathPolicy(platform, arch);
|
|
21
|
+
const permissions = permissionPolicy(platform, arch);
|
|
16
22
|
const canonicalHome = await canonicalExistingDirectory(homeDirectory, "home directory");
|
|
23
|
+
const applicationData = dataPaths.applicationData({ homeDirectory: canonicalHome, environment });
|
|
24
|
+
const productSupportAnchor = applicationData.canonicalizeRoot
|
|
25
|
+
? await canonicalExistingDirectory(applicationData.path, applicationData.label)
|
|
26
|
+
: containedPath(canonicalHome, applicationData.path, applicationData.label);
|
|
27
|
+
const productSupportInspectionRoot = applicationData.canonicalizeRoot
|
|
28
|
+
? productSupportAnchor
|
|
29
|
+
: containedPath(canonicalHome, applicationData.inspectionRoot, "application data inspection root");
|
|
17
30
|
const productSupportRoot = containedPath(
|
|
18
|
-
|
|
19
|
-
join(
|
|
31
|
+
productSupportAnchor,
|
|
32
|
+
join(productSupportAnchor, "dev-flow"),
|
|
20
33
|
"product support root",
|
|
21
34
|
);
|
|
22
35
|
|
|
@@ -25,13 +38,18 @@ export async function resolveDataDirectory({
|
|
|
25
38
|
const dataDirectory = await canonicalExplicitDataDirectory(explicitDataDirectory);
|
|
26
39
|
return Object.freeze({
|
|
27
40
|
dataDirectory,
|
|
41
|
+
platform,
|
|
42
|
+
arch,
|
|
43
|
+
enforcePrivateModes: permissions.enforcePrivateModes,
|
|
28
44
|
homeDirectory: canonicalHome,
|
|
45
|
+
productSupportAnchor,
|
|
46
|
+
productSupportInspectionRoot,
|
|
29
47
|
productSupportRoot,
|
|
30
48
|
usesDefaultDataDirectory: false,
|
|
31
49
|
});
|
|
32
50
|
}
|
|
33
51
|
|
|
34
|
-
await assertNoSymlinkComponents(
|
|
52
|
+
await assertNoSymlinkComponents(productSupportInspectionRoot, productSupportRoot);
|
|
35
53
|
|
|
36
54
|
return Object.freeze({
|
|
37
55
|
dataDirectory: containedPath(
|
|
@@ -39,7 +57,12 @@ export async function resolveDataDirectory({
|
|
|
39
57
|
join(productSupportRoot, "data"),
|
|
40
58
|
"default data directory",
|
|
41
59
|
),
|
|
60
|
+
platform,
|
|
61
|
+
arch,
|
|
62
|
+
enforcePrivateModes: permissions.enforcePrivateModes,
|
|
42
63
|
homeDirectory: canonicalHome,
|
|
64
|
+
productSupportAnchor,
|
|
65
|
+
productSupportInspectionRoot,
|
|
43
66
|
productSupportRoot,
|
|
44
67
|
usesDefaultDataDirectory: true,
|
|
45
68
|
});
|
|
@@ -58,10 +81,10 @@ export async function ensureDefaultDataDirectory(paths) {
|
|
|
58
81
|
throw new Error("default data directory does not match the shared product path");
|
|
59
82
|
}
|
|
60
83
|
|
|
61
|
-
await assertNoSymlinkComponents(paths.
|
|
84
|
+
await assertNoSymlinkComponents(paths.productSupportInspectionRoot, paths.productSupportRoot);
|
|
62
85
|
await mkdir(paths.dataDirectory, { recursive: true, mode: 0o700 });
|
|
63
|
-
await assertNoSymlinkComponents(paths.
|
|
64
|
-
await chmod(paths.dataDirectory, 0o700);
|
|
86
|
+
await assertNoSymlinkComponents(paths.productSupportInspectionRoot, paths.dataDirectory);
|
|
87
|
+
if (paths.enforcePrivateModes) await chmod(paths.dataDirectory, 0o700);
|
|
65
88
|
return paths.dataDirectory;
|
|
66
89
|
}
|
|
67
90
|
|
package/lib/platform.mjs
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
const runtimeDescriptors = Object.freeze({
|
|
4
|
+
"darwin-arm64": Object.freeze({ platform: "darwin", arch: "arm64", runtimeKey: "darwin-arm64", runtimeDirectory: "darwin-arm64", runtimeExecutable: "dev-flow" }),
|
|
5
|
+
"win32-x64": Object.freeze({ platform: "win32", arch: "x64", runtimeKey: "win32-x64", runtimeDirectory: "win32-x64", runtimeExecutable: "dev-flow.exe" }),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const dataPathPolicies = Object.freeze({
|
|
9
|
+
"darwin-arm64": Object.freeze({
|
|
10
|
+
applicationData({ homeDirectory }) {
|
|
11
|
+
return Object.freeze({ path: join(homeDirectory, "Library", "Application Support"), inspectionRoot: homeDirectory, canonicalizeRoot: false, label: "application support directory" });
|
|
12
|
+
},
|
|
13
|
+
}),
|
|
14
|
+
"win32-x64": Object.freeze({
|
|
15
|
+
applicationData({ homeDirectory, environment }) {
|
|
16
|
+
const configured = environment?.LOCALAPPDATA;
|
|
17
|
+
if (typeof configured === "string" && configured !== "") {
|
|
18
|
+
return Object.freeze({ path: configured, inspectionRoot: configured, canonicalizeRoot: true, label: "LOCALAPPDATA" });
|
|
19
|
+
}
|
|
20
|
+
return Object.freeze({ path: join(homeDirectory, "AppData", "Local"), inspectionRoot: homeDirectory, canonicalizeRoot: false, label: "local application data directory" });
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const permissionPolicies = Object.freeze({
|
|
26
|
+
"darwin-arm64": Object.freeze({ enforcePrivateModes: true, requireExecutableMode: true }),
|
|
27
|
+
"win32-x64": Object.freeze({ enforcePrivateModes: false, requireExecutableMode: false }),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const SUPPORTED_RUNTIME_KEYS = Object.freeze(Object.keys(runtimeDescriptors));
|
|
31
|
+
|
|
32
|
+
export const runtimeDescriptor = (platform, arch) => selectPlatformValue(runtimeDescriptors, platform, arch);
|
|
33
|
+
export const dataPathPolicy = (platform, arch) => selectPlatformValue(dataPathPolicies, platform, arch);
|
|
34
|
+
export const permissionPolicy = (platform, arch) => selectPlatformValue(permissionPolicies, platform, arch);
|
|
35
|
+
|
|
36
|
+
function selectPlatformValue(values, platform, arch) {
|
|
37
|
+
const runtimeKey = `${platform}-${arch}`;
|
|
38
|
+
const value = values[runtimeKey];
|
|
39
|
+
if (value === undefined) {
|
|
40
|
+
throw new Error(`unsupported platform ${runtimeKey}; supported runtimes: ${SUPPORTED_RUNTIME_KEYS.join(", ")}`);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
package/lib/runtime.mjs
CHANGED
|
@@ -4,8 +4,7 @@ import { dirname, join } from "node:path";
|
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
5
|
|
|
6
6
|
import { containedPath, packageRootFromModule } from "./paths.mjs";
|
|
7
|
-
|
|
8
|
-
export const SUPPORTED_RUNTIME_KEY = "darwin-arm64";
|
|
7
|
+
import { permissionPolicy, runtimeDescriptor } from "./platform.mjs";
|
|
9
8
|
|
|
10
9
|
const execFile = promisify(execFileCallback);
|
|
11
10
|
const semverPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u;
|
|
@@ -15,18 +14,19 @@ export async function selectPackagedRuntime({
|
|
|
15
14
|
platform = process.platform,
|
|
16
15
|
arch = process.arch,
|
|
17
16
|
} = {}) {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
throw new Error(`unsupported platform ${runtimeKey}; dev-flow-deepseek supports ${SUPPORTED_RUNTIME_KEY}`);
|
|
21
|
-
}
|
|
17
|
+
const runtime = runtimeDescriptor(platform, arch);
|
|
18
|
+
const permissions = permissionPolicy(platform, arch);
|
|
22
19
|
|
|
23
20
|
const canonicalPackageRoot = await canonicalPackageDirectory(packageRoot);
|
|
24
21
|
return Object.freeze({
|
|
25
22
|
packageRoot: canonicalPackageRoot,
|
|
26
|
-
|
|
23
|
+
platform: runtime.platform,
|
|
24
|
+
arch: runtime.arch,
|
|
25
|
+
runtimeKey: runtime.runtimeKey,
|
|
26
|
+
requireExecutableMode: permissions.requireExecutableMode,
|
|
27
27
|
runtimePath: containedPath(
|
|
28
28
|
canonicalPackageRoot,
|
|
29
|
-
join(canonicalPackageRoot, "runtime",
|
|
29
|
+
join(canonicalPackageRoot, "runtime", runtime.runtimeDirectory, runtime.runtimeExecutable),
|
|
30
30
|
"packaged Core runtime",
|
|
31
31
|
),
|
|
32
32
|
});
|
|
@@ -39,18 +39,29 @@ export async function preflightPackagedCore(
|
|
|
39
39
|
currentDirectory = dirname(selection?.runtimePath ?? "."),
|
|
40
40
|
} = {},
|
|
41
41
|
) {
|
|
42
|
-
if (!selection
|
|
43
|
-
throw new Error("packaged Core selection
|
|
42
|
+
if (!selection) {
|
|
43
|
+
throw new Error("packaged Core selection is required");
|
|
44
|
+
}
|
|
45
|
+
const runtime = runtimeDescriptor(
|
|
46
|
+
selection.platform ?? selection.runtimeKey.split("-")[0],
|
|
47
|
+
selection.arch ?? selection.runtimeKey.split("-")[1],
|
|
48
|
+
);
|
|
49
|
+
const permissions = permissionPolicy(runtime.platform, runtime.arch);
|
|
50
|
+
if (selection.runtimeKey !== runtime.runtimeKey) {
|
|
51
|
+
throw new Error("packaged Core selection does not match its platform");
|
|
44
52
|
}
|
|
45
53
|
const expectedRuntimePath = containedPath(
|
|
46
54
|
selection.packageRoot,
|
|
47
|
-
join(selection.packageRoot, "runtime",
|
|
55
|
+
join(selection.packageRoot, "runtime", runtime.runtimeDirectory, runtime.runtimeExecutable),
|
|
48
56
|
"packaged Core runtime",
|
|
49
57
|
);
|
|
50
58
|
if (selection.runtimePath !== expectedRuntimePath) {
|
|
51
59
|
throw new Error("packaged Core must use the exact package-relative runtime path");
|
|
52
60
|
}
|
|
53
|
-
await assertRegularExecutableFile(
|
|
61
|
+
await assertRegularExecutableFile(
|
|
62
|
+
selection.runtimePath,
|
|
63
|
+
selection.requireExecutableMode ?? permissions.requireExecutableMode,
|
|
64
|
+
);
|
|
54
65
|
|
|
55
66
|
let stdout;
|
|
56
67
|
try {
|
|
@@ -83,7 +94,7 @@ async function canonicalPackageDirectory(path) {
|
|
|
83
94
|
}
|
|
84
95
|
}
|
|
85
96
|
|
|
86
|
-
async function assertRegularExecutableFile(path) {
|
|
97
|
+
async function assertRegularExecutableFile(path, requireExecutableMode) {
|
|
87
98
|
let info;
|
|
88
99
|
try {
|
|
89
100
|
info = await lstat(path);
|
|
@@ -93,7 +104,7 @@ async function assertRegularExecutableFile(path) {
|
|
|
93
104
|
if (!info.isFile() || info.isSymbolicLink()) {
|
|
94
105
|
throw new Error("packaged Core must be a regular executable file");
|
|
95
106
|
}
|
|
96
|
-
if ((info.mode & 0o111) === 0) {
|
|
107
|
+
if (requireExecutableMode && (info.mode & 0o111) === 0) {
|
|
97
108
|
throw new Error("packaged Core must have executable mode");
|
|
98
109
|
}
|
|
99
110
|
const canonical = await realpath(path);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dev-flow-deepseek",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Explicit DeepSeek Harness adapter for the Dev Flow process graph.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -16,11 +16,14 @@
|
|
|
16
16
|
"README.md",
|
|
17
17
|
"cordis.patch.yml",
|
|
18
18
|
"lib/authorization.mjs",
|
|
19
|
+
"lib/file-scope.mjs",
|
|
19
20
|
"lib/index.mjs",
|
|
20
21
|
"lib/paths.mjs",
|
|
22
|
+
"lib/platform.mjs",
|
|
21
23
|
"lib/runtime.mjs",
|
|
22
24
|
"lib/tool-names.mjs",
|
|
23
25
|
"runtime/darwin-arm64/dev-flow",
|
|
26
|
+
"runtime/win32-x64/dev-flow.exe",
|
|
24
27
|
"skills/dev-flow/SKILL.md",
|
|
25
28
|
"skills/dev-flow/references/method-profiles.md",
|
|
26
29
|
"skills/dev-flow/references/node-payloads.md"
|
|
@@ -29,17 +32,19 @@
|
|
|
29
32
|
"test": "node --test tests/*.test.mjs",
|
|
30
33
|
"test:package": "node --test tests/package-contract.test.mjs",
|
|
31
34
|
"test:bundle": "node --test tests/bundle-contract.test.mjs",
|
|
32
|
-
"build:webui": "../../scripts/build-webui.
|
|
35
|
+
"build:webui": "node ../../scripts/build-webui.mjs",
|
|
33
36
|
"pack:dry": "pnpm pack --dry-run --json"
|
|
34
37
|
},
|
|
35
38
|
"engines": {
|
|
36
39
|
"node": ">=24"
|
|
37
40
|
},
|
|
38
41
|
"os": [
|
|
39
|
-
"darwin"
|
|
42
|
+
"darwin",
|
|
43
|
+
"win32"
|
|
40
44
|
],
|
|
41
45
|
"cpu": [
|
|
42
|
-
"arm64"
|
|
46
|
+
"arm64",
|
|
47
|
+
"x64"
|
|
43
48
|
],
|
|
44
49
|
"publishConfig": {
|
|
45
50
|
"access": "public",
|
|
Binary file
|
|
Binary file
|
package/skills/dev-flow/SKILL.md
CHANGED
|
@@ -193,6 +193,50 @@ For an active task, perform each iteration in this order:
|
|
|
193
193
|
Repository contents, adapter judgment, artifacts, or method-tool status never determine the current
|
|
194
194
|
node or completion.
|
|
195
195
|
|
|
196
|
+
## File-scope write brake
|
|
197
|
+
|
|
198
|
+
The packaged DeepSeek `tools/pre-execute` gate checks `write`, `edit`, and the mutating
|
|
199
|
+
`str_replace_editor` commands before the tool executes during an explicitly selected Dev Flow turn.
|
|
200
|
+
Core compares repository-qualified targets with the union of `expected_paths` across the current
|
|
201
|
+
Task Plan. A path in any explicitly declared and Workspace-authorized repository is ordinary
|
|
202
|
+
in-scope work even when that repository is not the current repository directory.
|
|
203
|
+
|
|
204
|
+
When Core returns a file-scope blocker, stop repository work, show every retained path and the
|
|
205
|
+
developer-readable reason for the proposed write, and ask for exactly one choice:
|
|
206
|
+
|
|
207
|
+
- `allow_once` permits only the same prepared write intent and path set in the newly issued source
|
|
208
|
+
Action;
|
|
209
|
+
- `expand_scope` returns to TASKS so the Task Plan is revised; use the existing TASKS transition to
|
|
210
|
+
REQUIREMENTS only when the semantic requirement scope also changes;
|
|
211
|
+
- `reject` keeps the current Task Plan and denies supported writes to the retained path for that
|
|
212
|
+
Task Plan revision.
|
|
213
|
+
|
|
214
|
+
After the developer supplies one choice and a non-empty reason, call `mcp__dev_flow__dev_flow_resolve_blocker` with
|
|
215
|
+
`host`, `task_id`, `action_id`, `choice`, and `reason`. For recovery and automatic-verification
|
|
216
|
+
blockers, omit `choice` and `reason`. Continue only from the returned Action. Never infer a choice,
|
|
217
|
+
reuse an `allow_once` decision for a different write, expand Repository Scope, or retry a rejected
|
|
218
|
+
path.
|
|
219
|
+
|
|
220
|
+
The gate covers the structured tools above; it is not a filesystem or shell sandbox. Bash, external
|
|
221
|
+
processes, and other tool paths may write before Core observes them. Implementation, Refactor and
|
|
222
|
+
Delivery submissions must therefore use the exact current changed surface and obey Core's final
|
|
223
|
+
scope guard. If the gate is unavailable, stop the supported write rather than describing prompt
|
|
224
|
+
compliance as interception.
|
|
225
|
+
|
|
226
|
+
## Automatic verification brake
|
|
227
|
+
|
|
228
|
+
When a committed TEST result moves the Task to `BLOCKED` with blocker cause
|
|
229
|
+
`repeated_verification_failure`, `unchanged_verification_result`, or
|
|
230
|
+
`unchanged_test_implementation_loop`, stop repository work and report the exact blocker message,
|
|
231
|
+
required resolution, and resume node. Do not call `mcp__dev_flow__dev_flow_resolve_blocker`
|
|
232
|
+
automatically.
|
|
233
|
+
|
|
234
|
+
Ask the developer to choose a different implementation or design path, explicitly allow one more
|
|
235
|
+
attempt, or cancel the Task. An explicit choice to continue or try another approach authorizes one
|
|
236
|
+
call to `mcp__dev_flow__dev_flow_resolve_blocker` with the current Task and blocked Action IDs.
|
|
237
|
+
Continue only from the returned Action. Core keeps the recent attempts, so the next exact repetition
|
|
238
|
+
may block again.
|
|
239
|
+
|
|
196
240
|
## Method operation rendering
|
|
197
241
|
|
|
198
242
|
Read [the method profile rendering reference](references/method-profiles.md) from the packaged path
|
|
@@ -309,21 +353,6 @@ Action's bounded simplification and artifact reconciliation. Submit `refactor_re
|
|
|
309
353
|
when it is present in that fresh Action; after Core commits it, continue from Core's returned `TEST`
|
|
310
354
|
Action and run the newly current budgeted checks before considering delivery.
|
|
311
355
|
|
|
312
|
-
## SCHEMA_UNSUPPORTED
|
|
313
|
-
|
|
314
|
-
When Core returns `SCHEMA_UNSUPPORTED`, explain that the selected data directory contains pre-graph
|
|
315
|
-
or otherwise incompatible data and that Core did not modify or delete the old data. The user must
|
|
316
|
-
act explicitly outside Core by choosing a fresh `DEV_FLOW_DATA_DIR`, manually archiving the old
|
|
317
|
-
directory, manually renaming it, or manually deleting it.
|
|
318
|
-
|
|
319
|
-
Stop current task discovery and do not continue open/create, automatically retry, reset, convert,
|
|
320
|
-
migrate, install a migration tool, or create a substitute task. Never run delete, move, truncate, or
|
|
321
|
-
reset operations for the user. Do not search for a local data directory or database path, and do not
|
|
322
|
-
display, reveal, or expose a private path or location. Report only the stable error code and bounded
|
|
323
|
-
guidance: never include a `HOME` value, username, result-envelope data path, raw SQLite error, or raw
|
|
324
|
-
Git error. After the user completes an explicit external choice, they may invoke the exact Skill
|
|
325
|
-
selector again; no background handling is promised.
|
|
326
|
-
|
|
327
356
|
## Recovery-before-retry contract
|
|
328
357
|
|
|
329
358
|
A mutation result is uncertain when it is missing, cancelled, malformed, truncated, or
|
|
@@ -7,9 +7,8 @@ and the internal payload envelope.
|
|
|
7
7
|
|
|
8
8
|
Core also fills the system-state members `requirements_revision` (Design baseline),
|
|
9
9
|
`design_revision` (Tasks baseline) and `task_plan_revision` (Implementation) from the current Task
|
|
10
|
-
snapshot after it verifies the current Action. Node templates omit them. A client that
|
|
11
|
-
|
|
12
|
-
the exact member path.
|
|
10
|
+
snapshot after it verifies the current Action. Node templates omit them. A client that sends
|
|
11
|
+
one of these Core-owned members violates the closed submission contract.
|
|
13
12
|
|
|
14
13
|
## Common input
|
|
15
14
|
|
|
@@ -44,6 +43,10 @@ Do not send `request_id`, revision, Action kind, process identity, source cursor
|
|
|
44
43
|
| `COMPLETE_DELIVERY` | `dev_flow_submit_delivery` |
|
|
45
44
|
| `RESOLVE_BLOCKER` | `dev_flow_resolve_blocker` |
|
|
46
45
|
|
|
46
|
+
For a file-scope blocker, `dev_flow_resolve_blocker` also requires `choice` (`allow_once`,
|
|
47
|
+
`expand_scope`, or `reject`) and a non-empty `reason`. Omit both members for repository-recovery and
|
|
48
|
+
automatic-verification blockers.
|
|
49
|
+
|
|
47
50
|
## Node-result members
|
|
48
51
|
|
|
49
52
|
Use the live tool schema for types and nested members. These are the closed top-level members:
|
|
@@ -61,7 +64,8 @@ Use the live tool schema for types and nested members. These are the closed top-
|
|
|
61
64
|
|
|
62
65
|
`changed_paths` and `no_file_changes` remain mutually exclusive. A single-repository Task uses
|
|
63
66
|
repository-relative paths. A multi-repository Task uses
|
|
64
|
-
`<repository-key>::<repository-relative-path>`.
|
|
67
|
+
`<repository-key>::<repository-relative-path>`. Contract paths use `/` separators on every Host;
|
|
68
|
+
backslashes are invalid even on Windows.
|
|
65
69
|
|
|
66
70
|
Delivery submissions never send `acceptance`, `automated_evidence_ids`, `manual_evidence_ids`,
|
|
67
71
|
`test_record_id`, or `comprehension_record_id`. Core derives those authority members from the current
|