@jayyuen66/dsh-quality-gate 0.1.0 → 0.1.1
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 +103 -0
- package/README.md +4 -105
- package/README.zh-CN.md +102 -0
- package/THIRD_PARTY_NOTICES.md +61 -0
- package/client.js +2 -2
- package/cordis.patch.yml +2 -2
- package/host.js +47 -49
- package/package.json +46 -38
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
格式遵循 [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),版本号遵循
|
|
4
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html)。最新版本在最上。
|
|
5
|
+
|
|
6
|
+
## 0.1.1
|
|
7
|
+
|
|
8
|
+
宿主生态与工具链跟到发布线,发布面的判据补严。**门禁判定规则、四条命令白名单、注入文案与
|
|
9
|
+
默认设置都没改**,改的是「跟谁对齐」与「发出去的东西长什么样」。
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- `@deepseek-ai/*` 家族从 `0.2.1-alpha.1` 升到 `0.2.1-alpha.2`:devDependencies、
|
|
14
|
+
peerDependencies 的 `@deepseek-ai/dsh`、`pnpm-workspace.yaml` 的 `overrides`、`engines.dsh`
|
|
15
|
+
四处同值(`test/publish-manifest.test.ts` 要求 `dsh-*` 与 peer 逐项相等)。
|
|
16
|
+
`@deepseek-ai/cordis` `4.0.5-alpha.1` 与 `@deepseek-ai/schemastery` `3.18.5-alpha.1` **不动**:
|
|
17
|
+
那是 alpha.2 自己钉的两枚(`~4.0.5-alpha.1` / `~3.18.5-alpha.1`),registry 上没有更新的 alpha。
|
|
18
|
+
- 上游逐包比对过发布件:本包消费的声明面没有 breaking。`dsh-agent` / `dsh-credentials` /
|
|
19
|
+
`dsh-settings` / `dsh-shell` / `dsh-sandbox-policy` / `dsh-package-manifest` 的 shipped 内容
|
|
20
|
+
与 alpha.1 逐字节相同;`dsh-session` / `dsh-session-query` / `dsh-client-ui-plugin-manager`
|
|
21
|
+
只有增量。打包契约(`cordis.patch.yml`、`dsh.bundle.patch`、`dsh.client`、`manifestVersion: 1`)
|
|
22
|
+
不变,因此 `host.ts` 无必须的适配改动。
|
|
23
|
+
- `@jayyuen66/dsh-plugin-shared` `^0.1.2` → `^0.1.4`。这一枚改变了**发布物形状**:0.1.2 把
|
|
24
|
+
`@deepseek-ai/dsh-jobs` / `dsh-tools` / `dsh-session` 等宿主包列在自己的 `dependencies` 里,
|
|
25
|
+
消费方 profile(`nodeLinker: hoisted` + `autoInstallPeers: false`)于是会物化出第二份宿主
|
|
26
|
+
类型面——正是「`@deepseek-ai/*` 不进 dependencies」那条门禁管不到的依赖树下一层。0.1.3 把它们
|
|
27
|
+
挪进 `peerDependencies`,0.1.4 再把那几枚 peer 钉到 `0.2.1-alpha.2`。
|
|
28
|
+
- 入口面:`main` / `exports` 指向构建产物 `host.js` / `client.js`,入口覆盖不再写在
|
|
29
|
+
`publishConfig` 里(pnpm 发布时合并到顶层、npm 忽略并打印 Unknown publishConfig——同一仓库
|
|
30
|
+
换个发布器就会发出一个 import 必失败的包)。
|
|
31
|
+
- 工具链(`pnpm-workspace.yaml` 的 `catalog`)跟到发布线:`@types/node` 26.6.3→26.6.5、
|
|
32
|
+
`vitest` 与 `@vitest/coverage-v8` 5.0.2→5.0.3、`vite` 8.3.1→8.3.4、`rolldown` 1.2.11→1.2.13、
|
|
33
|
+
`oxlint` 1.86.0→1.87.0、`oxfmt` 0.71.0→0.72.0、`oxlint-tsgolint` 7.0.2003→7.0.2004。
|
|
34
|
+
`typescript` 7.0.2、`react` / `@types/react` 19.3.0、`eslint-plugin-sonarjs` 4.2.2 已在发布线。
|
|
35
|
+
- 新增 `engines.node`(`>=22.12.0`):产物用到 `AbortSignal.any` 这类较新的运行面。
|
|
36
|
+
- `tsconfig.client.json` 删掉 `"types": ["node"]` 这枚本地覆盖:shared 的 client 预设本来就是
|
|
37
|
+
`types: []`,这一枚覆盖把 Node 全局又塞回了 client 半。删掉后 client 工程与宿主预设同形,
|
|
38
|
+
类型检查照绿——client 面从未依赖 Node 全局。
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- 文档拆分:`README.md` 为英文入口、`README.zh-CN.md` 为中文,两行头部互链。
|
|
43
|
+
- `CHANGELOG.md` 与 `THIRD_PARTY_NOTICES.md`(ECC `stop-format-typecheck` /
|
|
44
|
+
`post-edit-accumulator` 的机制来源与差异表),两者一并写进 `files`。
|
|
45
|
+
- `test/publish-manifest.test.ts` 的发布面门禁:
|
|
46
|
+
- 「仓库根目录每个 `.md` 都必须出现在发布面里」——只有 readme 形态的文件靠发布器无条件附带,
|
|
47
|
+
`CHANGELOG.md` 之类不写进 `files` 就发不出去,而"文档写了但读者看不到"此前无人能发现。
|
|
48
|
+
- `ALWAYS_SHIPPED` 改为按 `pnpm pack --dry-run` 实测的四枚,删掉不成立的条目。
|
|
49
|
+
- 构建产物的内容扫描:随包发布的 `host.js` / `client.js` 里不许出现日期、本机绝对路径、
|
|
50
|
+
pnpm 的 store 目录名。
|
|
51
|
+
- `engines.dsh` 必须等于 peer `@deepseek-ai/dsh` 去掉区间算子的下界;`engines.node` 不得
|
|
52
|
+
低于宿主发布线。
|
|
53
|
+
- 注释门禁(同一文件)从「只禁完整日期」扩到:只到月份的日期、指向作者主目录的本机路径、
|
|
54
|
+
pnpm 的 store 目录名、内部工作项编号(字母+数字形态)、指向未随包发布的笔记的行号指针、
|
|
55
|
+
上游源码仓的工作区路径,以及描述工作过程而非约束本身的那一类词(黑名单逐条见
|
|
56
|
+
`test/publish-manifest.test.ts` 的 `FORBIDDEN_IN_COMMENTS`)。据此把 `host.ts`、`lib/*.ts`、
|
|
57
|
+
`src/*.ts` 的注释重写了一遍:只留约束本身,删掉过程记录。指向已安装依赖的声明文件行号不在
|
|
58
|
+
禁面内——版本钉住之后那是人人可查的证据。
|
|
59
|
+
- `pack-check.mjs` + `check:pack` 脚本:对 `pnpm pack` 产出的**真 tarball** 做体检——入口与
|
|
60
|
+
无条件附带项都在包里、源码与本地状态目录没混进去、`@deepseek-ai/*` 没落进 `dependencies`、
|
|
61
|
+
代码面里每个裸说明符都由 dependencies ∪ peerDependencies 提供、产物内容不含日期与本机路径。
|
|
62
|
+
已接进 `check`,因此本地与 CI 同一口径。
|
|
63
|
+
- `test/dist-size.test.ts`:两个构建产物的字节预算。判据落在体积上是因为「依赖被内联进产物」
|
|
64
|
+
这一类回归没有更早的报警器——类型检查与单测都不看产物。
|
|
65
|
+
- `ci.yml`:push / PR 上跑 `pnpm install --frozen-lockfile` + `pnpm run check`(含上面那条
|
|
66
|
+
tarball 体检),并断言门禁没有改写受版本控制的文件。运行时只跑 node 24,与发布流程同一条线。
|
|
67
|
+
|
|
68
|
+
### Fixed
|
|
69
|
+
|
|
70
|
+
- `@deepseek-ai/dsh-credentials` 的声明位置与注释不一致:注释说它落在 `dependencies`,
|
|
71
|
+
实际由「`@deepseek-ai/*` 不进 dependencies」的门禁禁掉。注释按真实机制改写——它靠
|
|
72
|
+
**peerDependencies** 进入 `build-host.mjs` 的外部化面(外部化集合 = dependencies ∪
|
|
73
|
+
peerDependencies),因此产物保留裸说明符、官方函数体不被内联。
|
|
74
|
+
- README 里「另一枚运行期依赖是 `@deepseek-ai/schemastery`」的措辞:那一枚由宿主解析、
|
|
75
|
+
不由本包安装,文档改为按真实来源描述,并补上宿主版本要求。
|
|
76
|
+
|
|
77
|
+
### Not changed, deliberately
|
|
78
|
+
|
|
79
|
+
- 四条门禁命令的白名单与优先级、`not-run` 与 `code-failure` 的分界、注入配额、预算分摊、
|
|
80
|
+
默认设置值、双语文案内容。
|
|
81
|
+
- `@deepseek-ai/*` 一律 type-only(`dsh-credentials` 的两枚纯字符串函数除外)。
|
|
82
|
+
- 覆盖率阈值仍由 `@jayyuen66/dsh-plugin-shared/config/vitest.base` 单源给出(100%),
|
|
83
|
+
本包不放宽;`coverageInclude` 仍只覆盖 host 半(`host.ts` + `lib/*.ts`),client 半没有
|
|
84
|
+
DOM 环境可跑,见 `test/client-card.test.ts` 的说明。
|
|
85
|
+
|
|
86
|
+
### Tests
|
|
87
|
+
|
|
88
|
+
- 门禁全套通过:typecheck(两个 tsconfig)、`oxlint`(0 warning / 0 error)、两个 rolldown
|
|
89
|
+
构建、`vitest run --coverage`、`oxfmt --check`。
|
|
90
|
+
- 构建产物在真实 Node 上直接 `import()` 装载成功,裸说明符全部解析得到。
|
|
91
|
+
- `pnpm pack` 的 tarball 逐项目检:入口指向构建产物、`@deepseek-ai/*` 不在
|
|
92
|
+
`dependencies`、两份 README 与两份新文档都在发布面里。
|
|
93
|
+
|
|
94
|
+
## 0.1.0
|
|
95
|
+
|
|
96
|
+
`@jayyuen66` 作用域下的首个公共 npm 发布。
|
|
97
|
+
|
|
98
|
+
### Added
|
|
99
|
+
|
|
100
|
+
- 回合收口质量门禁:`agent/turn-stopping` 按项目形态跑 `pnpm check` / `cargo check` /
|
|
101
|
+
`ruff check .` / `npx tsc --noEmit`,未过则注入修复指令开新回合。
|
|
102
|
+
- 显式三态判定(`pass` / `code-failure` / `not-run`)与会话级配额、总预算、编辑记账。
|
|
103
|
+
- 设置卡(web client 面)与中英双语文案;记忆网关反馈默认关闭,密钥走宿主凭据通道。
|
package/README.md
CHANGED
|
@@ -1,108 +1,6 @@
|
|
|
1
1
|
# @jayyuen66/dsh-quality-gate
|
|
2
2
|
|
|
3
|
-
[中文](
|
|
4
|
-
|
|
5
|
-
## 中文
|
|
6
|
-
|
|
7
|
-
### 它做什么
|
|
8
|
-
|
|
9
|
-
- 回合收口质量门禁:本轮改过代码/配置文件时,在回合收口前自动跑该项目自己声明的检查命令;不通过就经 `agent.followup` 开新回合、把修复指令注入给模型。
|
|
10
|
-
- 一次检查只有三种结论:`pass`(真的跑完且 exit 0)、`code-failure`(跑了且报代码错)、`not-run`(没跑成)。一条没真正跑完的检查绝不会被记成通过。
|
|
11
|
-
- 它不改代码、不拦截危险命令(那是 danger-guard 的职责)、也不写任何文件。
|
|
12
|
-
|
|
13
|
-
### 触发时机与不触发条件
|
|
14
|
-
|
|
15
|
-
- 触发点:宿主事件 `agent/turn-stopping`。流程是读增量事件窗 → 取本会话**成功**的编辑 → 每个文件向上找最近清单根 → 探测门禁 → 执行。
|
|
16
|
-
- 编辑口径:`edit` / `write` / `str_replace_editor` 的写入;`view` 只读不算。
|
|
17
|
-
- 清单根:最多向上找 10 层;monorepo 子包升到 `pnpm-workspace.yaml` 所在的工作区根。
|
|
18
|
-
- 不触发:
|
|
19
|
-
- `enabled: false`;本回合没有可检编辑(只改 md/txt/图片/`.sh`,或被拒、执行失败的编辑)。
|
|
20
|
-
- 子代理会话(`origin === "subagent"` 或 `delegationDepth > 0`);inbox 已有待处理消息(不抢话)。
|
|
21
|
-
- 探测不出门禁的根;回合 signal 已取消或插件已卸载。
|
|
22
|
-
- 宿主没有 shell 服务时 warn 一次并放行;plan mode 不豁免——规划回合改了代码同样收口。
|
|
23
|
-
|
|
24
|
-
### 安装
|
|
25
|
-
|
|
26
|
-
```sh
|
|
27
|
-
dsh plugin --profile web add @jayyuen66/dsh-quality-gate
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
- 包在公共 npm(`registry.npmjs.org`)上,安装侧不需要凭据。
|
|
31
|
-
- 运行时依赖 `@jayyuen66/dsh-plugin-shared` 必须已在同一 registry 上,否则整组 404。
|
|
32
|
-
- 另一枚运行期依赖是 `@deepseek-ai/schemastery`(宿主 fork):0.1.7 的 `.volatile()` 活引用只有它解析得出来,装公共 `schemastery` 会让改设置要重启才生效。
|
|
33
|
-
- 本地目录形态安装需 pnpm ≥ 12.3.0(12.0.0 的本地目录 `add` 有已知缺陷)。
|
|
34
|
-
|
|
35
|
-
### 在 dsh 里启用
|
|
36
|
-
|
|
37
|
-
- 装载由包内 `cordis.patch.yml` 负责(`package.json` 的 `dsh.bundle.patch` 指向它),插一行 `- id: quality-gate`、`name` 为本包名;`dsh plugin --profile web add/remove` 维护该行。
|
|
38
|
-
- Host 半只硬依赖宿主的 `settings`(`default.inject = ["settings"]`);`shell` / `sandboxPolicy` / `lessonLoop` / `credentials` / `sessionQuery` 一律 `ctx.get` 可选读,缺失不会让插件加载失败。
|
|
39
|
-
- 优先级:设置卡的运行时值 > 该行 `config:` > 包内置默认。client 半(`dsh.client.platform = web`、`immediately: true`)往插件管理页的 `plugins.bundle.config` 槽注入一张卡(key = 本包名;`configForms.get()` 与 settings 命名空间用的是裸条目 id `quality-gate`)。
|
|
40
|
-
- 卡的保存是显式的:改动先留在本地暂存,点「保存」才逐键写 settings(状态条提示「有未保存的修改,点「保存」生效」)。
|
|
41
|
-
- 写入通道把 `Promise<boolean>` 受理位原样交给卡片——首个 `false` 即停手、显示 `saveRejected` 那行并保留未落盘的改动(旧版是盲 `setTimeout` 清暂存,宿主拒写时卡仍显示已保存);写入 reject 显示 `saveFailed`,「撤销」丢弃暂存。
|
|
42
|
-
|
|
43
|
-
### 设置的检查命令
|
|
44
|
-
|
|
45
|
-
四条白名单命令在类型层锁死(只有这四条能进执行面),每根择一,优先级 workspace > Rust > Python > 裸 TS:
|
|
46
|
-
|
|
47
|
-
- `pnpm check`:根同时有 `pnpm-workspace.yaml` 与 `package.json`,且根 scripts 声明了非空 `check`;没声明就退回下一候选——宁可不给门禁,也不替项目发明一条稳定假失败的命令。
|
|
48
|
-
- `cargo check`(`Cargo.toml`)、`ruff check .`(`pyproject.toml` 或 `ruff.toml`)、`npx tsc --noEmit`(`package.json` + `tsconfig.json`,缺 `tsconfig.json` 不算 TS 项目)。
|
|
49
|
-
- 预算与并发:总预算 `gateBudgetMs` 封顶,每根取 `max(10s, 预算/根数)` 与实际剩余的小值。
|
|
50
|
-
- 剩余不足 5s 的尾段不再起跑,按「未执行」注入清单。
|
|
51
|
-
- 门禁顺序执行,一次只注入一个失败。
|
|
52
|
-
- 证据上限:每条流内存留存 64 KiB,被截断时从落盘文件有界读回 4 KiB 头部(首条错误才是根因);单会话编辑记账 500 个文件,超出按插入序淘汰并 warn。
|
|
53
|
-
|
|
54
|
-
### 设置项
|
|
55
|
-
|
|
56
|
-
- `enabled`:boolean,默认 `true`。关掉即收口不再核验。
|
|
57
|
-
- `maxInjectsPerTurn`:1–5,默认 `2`。修复指令注入配额,按会话跨回合累计。
|
|
58
|
-
- `gateBudgetMs`:10000–600000,默认 `300000`。超时按失败注入,不静默放行。
|
|
59
|
-
- `memoryFeedback`:boolean,默认 `false`。是否把代码型失败额外沉淀到记忆网关。
|
|
60
|
-
- `memoryGatewayUrl`:string,默认空 = 不启用;设置值优先,留空回落 env `TDAI_GATEWAY_URL`。
|
|
61
|
-
- `memoryGatewayKeyRef`:string(凭据引用名),默认 `TDAI_GATEWAY_KEY`;设置里只存引用名。
|
|
62
|
-
- 另有两枚非 volatile 的部署值:设置卡上没有它们的行,只在注册行的 `config:` 上给(cordis 交进 apply 的是值而非引用,改值随重启生效)。
|
|
63
|
-
- `maxRootDepth` 默认 `10`——「最多向上找 10 层」说的就是它;`gateStdoutMaxBytes` 默认 `65536`——「每条流内存留存 64 KiB」说的就是它。
|
|
64
|
-
|
|
65
|
-
### 对外接口
|
|
66
|
-
|
|
67
|
-
- 无 HTTP 面:本包不注册任何路由或端点,也没有供外部调用的接口。
|
|
68
|
-
- 对宿主:settings 命名空间 `quality-gate` 是 0.1.7 的隐式注册(= 条目 id,可编辑字段由条目 `Config` 上标了 `.volatile()` 的项决定,本包不再调 `settings.register`),schema 与行 `config` 共用同一份 `Config`。
|
|
69
|
-
- 只剩页面策略要声明:`svc.inject(["settings"], …)` 的子上下文里 `settings.configure({ auto: false }, svc.fiber)`——本包自带卡片,别让宿主再生成一份自动表单。
|
|
70
|
-
- 注入的 followup 为 `role: "user"`、`source: { kind: "plugin:quality-gate" }`、id 为 `quality-gate-` 前缀加一个 UUID。
|
|
71
|
-
- 日志走宿主具名 logger(`ctx.logger("quality-gate")`,本包用的方法面是 `info` / `warn` / `error`),该服务缺席才回退 `console`。
|
|
72
|
-
- 注入给模型的文案只有四类,消息统一带 `[quality-gate]` 前缀与命令/根上下文:
|
|
73
|
-
- 修复指令。
|
|
74
|
-
- 门禁「未能执行」说明。
|
|
75
|
-
- 配额耗尽后的放行说明。
|
|
76
|
-
- 嵌在「未能执行」里的预算不足清单。
|
|
77
|
-
- 两语文案随官方 settings 命名空间 `locale` 的偏好,未注册即中文,改语言下一回合生效。
|
|
78
|
-
- 卡片文案另走官方 client locale:`ctx.locale.register(NS, UI_MESSAGES)` 交字典、`ctx.locale.bind(NS)` 出取文案函数,切语言由宿主重渲染 slot、不必重载页面。
|
|
79
|
-
- 对总线:`ctx.get("lessonLoop")?.report({ source: "quality-gate", category: "gate-failure" | "gate-not-run", cwd, sessionId, turn, signature: 命令串, detail: 全文证据, evidence: { root, command, kind } })`。
|
|
80
|
-
- 可选外发:向 `memoryGatewayUrl` 配的地址发 `POST /v2/conversation/add`,5s 超时封顶。
|
|
81
|
-
- 请求头带 Bearer 令牌与 `x-tdai-service-id`(env 可覆盖,默认 `default`)。
|
|
82
|
-
- body 为 `session_id: "dsh-agent"` + 一条 `assistant` 消息,`agent_id` 取会话 cwd 的尾目录名加 8 位 sha256 前缀(与 lesson-loop 项目分桶同源)。
|
|
83
|
-
|
|
84
|
-
### 数据与隐私
|
|
85
|
-
|
|
86
|
-
- 默认零外发:`memoryFeedback` 关 + 地址空是双保险,任一为空都不推送、也不去查凭据。
|
|
87
|
-
- 开启后送出的只有命令、项目根与全文报错(不截断),落到你自配的网关。
|
|
88
|
-
- 失败只 warn 且每次装载只说一次,绝不阻断收口。
|
|
89
|
-
- 密钥值由宿主凭据服务持有(`$DSH_HOME` 下的凭据文件),引用名不合 POSIX 环境变量语法即视为未配置。
|
|
90
|
-
- 与 lesson-loop 的关系是**可选**的:总线缺失时报告直接丢弃,门禁照常运行;装了才多一份教训沉淀,`memoryFeedback` 开关不影响总线上报。
|
|
91
|
-
- 本包只读盘(清单存在性、根 `package.json` 的 scripts、截断落盘文件的头部),不写文件、不遥测。
|
|
92
|
-
|
|
93
|
-
### 常见问题
|
|
94
|
-
|
|
95
|
-
- pnpm 工程为什么没跑 `pnpm check`?根 `package.json` 没声明非空 `check` 脚本,这是刻意取舍:假失败比漏检查更贵。
|
|
96
|
-
- 门禁说「未能执行」是代码错了吗?不是。
|
|
97
|
-
- 沙箱策略拒绝、runner 起不来、超时、取消、exit 126/127、`npx` 抓到 stub 包、预算不足都属此类。
|
|
98
|
-
- 注入文案明确要求「不要为此修改代码」,不写记忆,并以 `gate-not-run` 单独上报总线。
|
|
99
|
-
- 会不会无限修?配额耗尽后注入一次用户可见的「放行说明」并停止自动修复,直到某次全部干净通过才重新计数。
|
|
100
|
-
- 临时停掉:设置卡关 `enabled`,或 `dsh plugin --profile web remove @jayyuen66/dsh-quality-gate`。
|
|
101
|
-
- 子代理改的代码呢?in-process 子代理跳过,由父会话下一回合统一核验。
|
|
102
|
-
- out-of-process 子代理(ACP/claude-code/codex)不发 dsh 事件,天然不在门禁面内。
|
|
103
|
-
- 许可 MIT,仓库见 `package.json` 的 `repository.url`。
|
|
104
|
-
|
|
105
|
-
## English
|
|
3
|
+
[中文](./README.zh-CN.md)
|
|
106
4
|
|
|
107
5
|
### What it does
|
|
108
6
|
|
|
@@ -128,8 +26,9 @@ dsh plugin --profile web add @jayyuen66/dsh-quality-gate
|
|
|
128
26
|
```
|
|
129
27
|
|
|
130
28
|
- The packages live on the public npm registry, so installs need no credentials.
|
|
29
|
+
- Requires a host at `@deepseek-ai/dsh` 0.2.1-alpha.2 or newer (`engines.dsh`); the plugin is compiled against that host's type surface.
|
|
131
30
|
- The runtime dependency `@jayyuen66/dsh-plugin-shared` must already be on the same registry, otherwise the whole set 404s.
|
|
132
|
-
-
|
|
31
|
+
- `@deepseek-ai/schemastery` is resolved from the host, not installed by this package: the host's own fork is the only one whose `resolve` wraps `.volatile()` fields into live references, so a public `schemastery` would turn "edit a setting" into "restart to apply".
|
|
133
32
|
- Installing a local directory needs pnpm ≥ 12.3.0 (12.0.0 has a known bug in that path).
|
|
134
33
|
|
|
135
34
|
### Enabling it in dsh
|
|
@@ -139,7 +38,7 @@ dsh plugin --profile web add @jayyuen66/dsh-quality-gate
|
|
|
139
38
|
- Precedence: card runtime value > the line's `config:` > built-in default.
|
|
140
39
|
- The client half (`dsh.client.platform = web`, `immediately: true`) injects a card into the `plugins.bundle.config` slot of the plugin page (key = this package name, while `configForms.get()` uses the bare entry id `quality-gate`).
|
|
141
40
|
- Saving on the card is staged: edits stay in local pending state until Save writes them key by key into settings.
|
|
142
|
-
- The write channel hands the card the `Promise<boolean>` acceptance bit verbatim: the first `false` stops the loop, shows the `saveRejected` line and keeps the unwritten changes - the
|
|
41
|
+
- The write channel hands the card the `Promise<boolean>` acceptance bit verbatim: the first `false` stops the loop, shows the `saveRejected` line and keeps the unwritten changes - swallowing the acceptance bit would let the card claim "saved" while the host had refused. A rejecting write shows `saveFailed`; Revert discards the pending edits.
|
|
143
42
|
|
|
144
43
|
### The commands it runs
|
|
145
44
|
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# @jayyuen66/dsh-quality-gate
|
|
2
|
+
|
|
3
|
+
[English](./README.md)
|
|
4
|
+
|
|
5
|
+
### 它做什么
|
|
6
|
+
|
|
7
|
+
- 回合收口质量门禁:本轮改过代码/配置文件时,在回合收口前自动跑该项目自己声明的检查命令;不通过就经 `agent.followup` 开新回合、把修复指令注入给模型。
|
|
8
|
+
- 一次检查只有三种结论:`pass`(真的跑完且 exit 0)、`code-failure`(跑了且报代码错)、`not-run`(没跑成)。一条没真正跑完的检查绝不会被记成通过。
|
|
9
|
+
- 它不改代码、不拦截危险命令(那是 danger-guard 的职责)、也不写任何文件。
|
|
10
|
+
|
|
11
|
+
### 触发时机与不触发条件
|
|
12
|
+
|
|
13
|
+
- 触发点:宿主事件 `agent/turn-stopping`。流程是读增量事件窗 → 取本会话**成功**的编辑 → 每个文件向上找最近清单根 → 探测门禁 → 执行。
|
|
14
|
+
- 编辑口径:`edit` / `write` / `str_replace_editor` 的写入;`view` 只读不算。
|
|
15
|
+
- 清单根:最多向上找 10 层;monorepo 子包升到 `pnpm-workspace.yaml` 所在的工作区根。
|
|
16
|
+
- 不触发:
|
|
17
|
+
- `enabled: false`;本回合没有可检编辑(只改 md/txt/图片/`.sh`,或被拒、执行失败的编辑)。
|
|
18
|
+
- 子代理会话(`origin === "subagent"` 或 `delegationDepth > 0`);inbox 已有待处理消息(不抢话)。
|
|
19
|
+
- 探测不出门禁的根;回合 signal 已取消或插件已卸载。
|
|
20
|
+
- 宿主没有 shell 服务时 warn 一次并放行;plan mode 不豁免——规划回合改了代码同样收口。
|
|
21
|
+
|
|
22
|
+
### 安装
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
dsh plugin --profile web add @jayyuen66/dsh-quality-gate
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- 包在公共 npm(`registry.npmjs.org`)上,安装侧不需要凭据。
|
|
29
|
+
- 宿主需 `@deepseek-ai/dsh` 0.2.1-alpha.2 起(`engines.dsh`):本包按这一版宿主的类型面编译。
|
|
30
|
+
- 运行时依赖 `@jayyuen66/dsh-plugin-shared` 必须已在同一 registry 上,否则整组 404。
|
|
31
|
+
- `@deepseek-ai/schemastery` 由宿主解析、不由本包安装:只有宿主 fork 的那一枚能把 `.volatile()` 字段解析成活引用,换成公共 `schemastery` 就变成改设置要重启才生效。
|
|
32
|
+
- 本地目录形态安装需 pnpm ≥ 12.3.0(12.0.0 的本地目录 `add` 有已知缺陷)。
|
|
33
|
+
|
|
34
|
+
### 在 dsh 里启用
|
|
35
|
+
|
|
36
|
+
- 装载由包内 `cordis.patch.yml` 负责(`package.json` 的 `dsh.bundle.patch` 指向它),插一行 `- id: quality-gate`、`name` 为本包名;`dsh plugin --profile web add/remove` 维护该行。
|
|
37
|
+
- Host 半只硬依赖宿主的 `settings`(`default.inject = ["settings"]`);`shell` / `sandboxPolicy` / `lessonLoop` / `credentials` / `sessionQuery` 一律 `ctx.get` 可选读,缺失不会让插件加载失败。
|
|
38
|
+
- 优先级:设置卡的运行时值 > 该行 `config:` > 包内置默认。client 半(`dsh.client.platform = web`、`immediately: true`)往插件管理页的 `plugins.bundle.config` 槽注入一张卡(key = 本包名;`configForms.get()` 与 settings 命名空间用的是裸条目 id `quality-gate`)。
|
|
39
|
+
- 卡的保存是显式的:改动先留在本地暂存,点「保存」才逐键写 settings(状态条提示「有未保存的修改,点「保存」生效」)。
|
|
40
|
+
- 写入通道把 `Promise<boolean>` 受理位原样交给卡片——首个 `false` 即停手、显示 `saveRejected` 那行并保留未落盘的改动(吞掉受理位会让宿主拒写时卡仍显示已保存);写入 reject 显示 `saveFailed`,「撤销」丢弃暂存。
|
|
41
|
+
|
|
42
|
+
### 设置的检查命令
|
|
43
|
+
|
|
44
|
+
四条白名单命令在类型层锁死(只有这四条能进执行面),每根择一,优先级 workspace > Rust > Python > 裸 TS:
|
|
45
|
+
|
|
46
|
+
- `pnpm check`:根同时有 `pnpm-workspace.yaml` 与 `package.json`,且根 scripts 声明了非空 `check`;没声明就退回下一候选——宁可不给门禁,也不替项目发明一条稳定假失败的命令。
|
|
47
|
+
- `cargo check`(`Cargo.toml`)、`ruff check .`(`pyproject.toml` 或 `ruff.toml`)、`npx tsc --noEmit`(`package.json` + `tsconfig.json`,缺 `tsconfig.json` 不算 TS 项目)。
|
|
48
|
+
- 预算与并发:总预算 `gateBudgetMs` 封顶,每根取 `max(10s, 预算/根数)` 与实际剩余的小值。
|
|
49
|
+
- 剩余不足 5s 的尾段不再起跑,按「未执行」注入清单。
|
|
50
|
+
- 门禁顺序执行,一次只注入一个失败。
|
|
51
|
+
- 证据上限:每条流内存留存 64 KiB,被截断时从落盘文件有界读回 4 KiB 头部(首条错误才是根因);单会话编辑记账 500 个文件,超出按插入序淘汰并 warn。
|
|
52
|
+
|
|
53
|
+
### 设置项
|
|
54
|
+
|
|
55
|
+
- `enabled`:boolean,默认 `true`。关掉即收口不再核验。
|
|
56
|
+
- `maxInjectsPerTurn`:1–5,默认 `2`。修复指令注入配额,按会话跨回合累计。
|
|
57
|
+
- `gateBudgetMs`:10000–600000,默认 `300000`。超时按失败注入,不静默放行。
|
|
58
|
+
- `memoryFeedback`:boolean,默认 `false`。是否把代码型失败额外沉淀到记忆网关。
|
|
59
|
+
- `memoryGatewayUrl`:string,默认空 = 不启用;设置值优先,留空回落 env `TDAI_GATEWAY_URL`。
|
|
60
|
+
- `memoryGatewayKeyRef`:string(凭据引用名),默认 `TDAI_GATEWAY_KEY`;设置里只存引用名。
|
|
61
|
+
- 另有两枚非 volatile 的部署值:设置卡上没有它们的行,只在注册行的 `config:` 上给(cordis 交进 apply 的是值而非引用,改值随重启生效)。
|
|
62
|
+
- `maxRootDepth` 默认 `10`——「最多向上找 10 层」说的就是它;`gateStdoutMaxBytes` 默认 `65536`——「每条流内存留存 64 KiB」说的就是它。
|
|
63
|
+
|
|
64
|
+
### 对外接口
|
|
65
|
+
|
|
66
|
+
- 无 HTTP 面:本包不注册任何路由或端点,也没有供外部调用的接口。
|
|
67
|
+
- 对宿主:settings 命名空间 `quality-gate` 是 0.1.7 的隐式注册(= 条目 id,可编辑字段由条目 `Config` 上标了 `.volatile()` 的项决定,本包不再调 `settings.register`),schema 与行 `config` 共用同一份 `Config`。
|
|
68
|
+
- 只剩页面策略要声明:`svc.inject(["settings"], …)` 的子上下文里 `settings.configure({ auto: false }, svc.fiber)`——本包自带卡片,别让宿主再生成一份自动表单。
|
|
69
|
+
- 注入的 followup 为 `role: "user"`、`source: { kind: "plugin:quality-gate" }`、id 为 `quality-gate-` 前缀加一个 UUID。
|
|
70
|
+
- 日志走宿主具名 logger(`ctx.logger("quality-gate")`,本包用的方法面是 `info` / `warn` / `error`),该服务缺席才回退 `console`。
|
|
71
|
+
- 注入给模型的文案只有四类,消息统一带 `[quality-gate]` 前缀与命令/根上下文:
|
|
72
|
+
- 修复指令。
|
|
73
|
+
- 门禁「未能执行」说明。
|
|
74
|
+
- 配额耗尽后的放行说明。
|
|
75
|
+
- 嵌在「未能执行」里的预算不足清单。
|
|
76
|
+
- 两语文案随官方 settings 命名空间 `locale` 的偏好,未注册即中文,改语言下一回合生效。
|
|
77
|
+
- 卡片文案另走官方 client locale:`ctx.locale.register(NS, UI_MESSAGES)` 交字典、`ctx.locale.bind(NS)` 出取文案函数,切语言由宿主重渲染 slot、不必重载页面。
|
|
78
|
+
- 对总线:`ctx.get("lessonLoop")?.report({ source: "quality-gate", category: "gate-failure" | "gate-not-run", cwd, sessionId, turn, signature: 命令串, detail: 全文证据, evidence: { root, command, kind } })`。
|
|
79
|
+
- 可选外发:向 `memoryGatewayUrl` 配的地址发 `POST /v2/conversation/add`,5s 超时封顶。
|
|
80
|
+
- 请求头带 Bearer 令牌与 `x-tdai-service-id`(env 可覆盖,默认 `default`)。
|
|
81
|
+
- body 为 `session_id: "dsh-agent"` + 一条 `assistant` 消息,`agent_id` 取会话 cwd 的尾目录名加 8 位 sha256 前缀(与 lesson-loop 项目分桶同源)。
|
|
82
|
+
|
|
83
|
+
### 数据与隐私
|
|
84
|
+
|
|
85
|
+
- 默认零外发:`memoryFeedback` 关 + 地址空是双保险,任一为空都不推送、也不去查凭据。
|
|
86
|
+
- 开启后送出的只有命令、项目根与全文报错(不截断),落到你自配的网关。
|
|
87
|
+
- 失败只 warn 且每次装载只说一次,绝不阻断收口。
|
|
88
|
+
- 密钥值由宿主凭据服务持有(`$DSH_HOME` 下的凭据文件),引用名不合 POSIX 环境变量语法即视为未配置。
|
|
89
|
+
- 与 lesson-loop 的关系是**可选**的:总线缺失时报告直接丢弃,门禁照常运行;装了才多一份教训沉淀,`memoryFeedback` 开关不影响总线上报。
|
|
90
|
+
- 本包只读盘(清单存在性、根 `package.json` 的 scripts、截断落盘文件的头部),不写文件、不遥测。
|
|
91
|
+
|
|
92
|
+
### 常见问题
|
|
93
|
+
|
|
94
|
+
- pnpm 工程为什么没跑 `pnpm check`?根 `package.json` 没声明非空 `check` 脚本,这是刻意取舍:假失败比漏检查更贵。
|
|
95
|
+
- 门禁说「未能执行」是代码错了吗?不是。
|
|
96
|
+
- 沙箱策略拒绝、runner 起不来、超时、取消、exit 126/127、`npx` 抓到 stub 包、预算不足都属此类。
|
|
97
|
+
- 注入文案明确要求「不要为此修改代码」,不写记忆,并以 `gate-not-run` 单独上报总线。
|
|
98
|
+
- 会不会无限修?配额耗尽后注入一次用户可见的「放行说明」并停止自动修复,直到某次全部干净通过才重新计数。
|
|
99
|
+
- 临时停掉:设置卡关 `enabled`,或 `dsh plugin --profile web remove @jayyuen66/dsh-quality-gate`。
|
|
100
|
+
- 子代理改的代码呢?in-process 子代理跳过,由父会话下一回合统一核验。
|
|
101
|
+
- out-of-process 子代理(ACP/claude-code/codex)不发 dsh 事件,天然不在门禁面面内。
|
|
102
|
+
- 许可 MIT,仓库见 `package.json` 的 `repository.url`。
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
本包自身编写的代码以 MIT 发布,见 [`LICENSE`](./LICENSE)。本文件记录**机制来源**与
|
|
4
|
+
**运行期依赖**:本包的哪一部分来自第三方、在什么意义上「来自」,以及本包自身代码与第三方代码
|
|
5
|
+
的边界在哪里。
|
|
6
|
+
|
|
7
|
+
## 机制来源:ECC(Everything Claude Code)
|
|
8
|
+
|
|
9
|
+
- 上游项目:`ECC`(Everything Claude Code)
|
|
10
|
+
- 上游仓库:https://github.com/affaan-m/ECC
|
|
11
|
+
- 上游许可:MIT,版权归 Affaan Mustafa 所有
|
|
12
|
+
- 上游机制名:`stop-format-typecheck`(回合收口跑项目自己的检查)、`post-edit-accumulator`
|
|
13
|
+
(编辑记账)
|
|
14
|
+
|
|
15
|
+
### 移植的是什么
|
|
16
|
+
|
|
17
|
+
**机制与设计思路**,两处:
|
|
18
|
+
|
|
19
|
+
1. **回合收口跑项目声明的检查**(`host.ts` 的 `agent/turn-stopping` 批处理、
|
|
20
|
+
`lib/gate-detect.ts` 按清单根择一)——收口时按项目形态运行它自己声明的检查命令,失败则把
|
|
21
|
+
报错回给模型。
|
|
22
|
+
2. **编辑记账**(`lib/accumulator.ts`)——不逐文件跑检查(太贵),而是回合收口时一次算总账:
|
|
23
|
+
本轮编辑过的代码/配置文件按插入序累计,容量到上限按插入序淘汰。
|
|
24
|
+
|
|
25
|
+
### 本包在其之上加了什么
|
|
26
|
+
|
|
27
|
+
上游是「一条 hook 命令」的形状,本包是跑在 dsh 宿主里的常驻插件,两者不等价:
|
|
28
|
+
|
|
29
|
+
| 维度 | 上游 ECC | 本包 |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| 结论形状 | 退出码二值 | 显式三态 `pass` / `code-failure` / `not-run`,没真正跑完的检查永不记为通过 |
|
|
32
|
+
| 失败处理 | 回显给人 | 经 `agent.followup` 开新回合注入修复指令,带跨回合配额与放行说明 |
|
|
33
|
+
| 预算 | 无 | 会话总预算封顶、按根分摊、尾段不足则注入「未执行」清单 |
|
|
34
|
+
| 证据 | 直接回显 | 每条流有内存上限,截断时从落盘文件有界读回**头部**(首条错误才是根因) |
|
|
35
|
+
| 项目定位 | 当前工作目录 | 从编辑路径向上找最近清单根(monorepo 子包升到工作区根),多根各自择一 |
|
|
36
|
+
| 沙箱 | 不涉及 | 按会话解析沙箱策略后执行;策略拒绝与 runner 失败归 `not-run`,不污染记忆与自进化闭环 |
|
|
37
|
+
| 语言 | 单语 | 注入文案与设置卡中英双语,随宿主 locale 偏好 |
|
|
38
|
+
|
|
39
|
+
没有复制上游的任何一行实现代码:`host.ts` / `lib/*.ts` 全部为本包编写,语言、结构、错误处理
|
|
40
|
+
都与上游不同。
|
|
41
|
+
|
|
42
|
+
## 运行期依赖
|
|
43
|
+
|
|
44
|
+
本包产物(`host.js`、`client.js`)把依赖**外部化**:产物里保留裸说明符,运行时解析到宿主
|
|
45
|
+
profile 里那一份实例,因此不存在"把第三方源码打进发布物"的情形,本文件也就不需要附各包的
|
|
46
|
+
许可证全文。
|
|
47
|
+
|
|
48
|
+
- `@jayyuen66/dsh-plugin-shared`(MIT)——唯一的普通依赖。`lib/locale`、`lib/tool-events`、
|
|
49
|
+
`lib/lesson-bus`、`lib/record`、`lib/project-key` 五个子路径来自它。
|
|
50
|
+
- `@deepseek-ai/*`(一律 MIT,由宿主提供,声明为 peer / devDependency,**不进** `dependencies`)
|
|
51
|
+
——`dsh`、`cordis`、`schemastery`、`dsh-settings`、`dsh-shell`、`dsh-sandbox`、
|
|
52
|
+
`dsh-sandbox-policy`、`dsh-session`、`dsh-session-query`、`dsh-session-projection`、
|
|
53
|
+
`dsh-session-title`、`dsh-agent`、`dsh-llm`、`dsh-credentials`、`dsh-scope`、
|
|
54
|
+
`dsh-subprocess`、`dsh-package-manifest`、`dsh-api-remotes`、`dsh-client-locale`、
|
|
55
|
+
`dsh-client-store`、`dsh-client-ui-layout`、`dsh-client-ui-plugin-manager`、
|
|
56
|
+
`dsh-client-ui-renderer`、`dsh-client-ui-settings`、`dsh-client-ui-slots`。
|
|
57
|
+
除 `dsh-credentials`(值导入 `credentialRef` / `isCredentialRefName`)与 `schemastery`
|
|
58
|
+
(值导入 `Schema`)之外,其余都只作类型消费。
|
|
59
|
+
|
|
60
|
+
依赖形状由 `test/publish-manifest.test.ts` 钉住:`@deepseek-ai/*` 出现在 `dependencies` 即失败
|
|
61
|
+
——那会在消费方 profile 里物化出第二份宿主类型面。
|
package/client.js
CHANGED
|
@@ -199,9 +199,9 @@ function SaveBar(props) {
|
|
|
199
199
|
}
|
|
200
200
|
/**
|
|
201
201
|
* 逐键 await 写入并**消费受理位**(0.1.7 的 set/unset 回 Promise<boolean>:
|
|
202
|
-
* true=宿主受理;false
|
|
202
|
+
* true=宿主受理;false=拒绝或写入被跳过(官方语义:"refuses stale revisions";
|
|
203
203
|
* 传输失败才 reject)。false 或 rejection 都**不清 touched**——否则宿主拒写时
|
|
204
|
-
* 卡片仍显示"已保存"
|
|
204
|
+
* 卡片仍显示"已保存",用户修改被静默丢弃。
|
|
205
205
|
*/
|
|
206
206
|
async function writeTouched(channels) {
|
|
207
207
|
const { t, set, unset, keys, touched, setBusy, setSaveError, setTouched } = channels;
|
package/cordis.patch.yml
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# quality-gate
|
|
1
|
+
# quality-gate:回合收口质量门禁。
|
|
2
2
|
# ECC(MIT, github.com/affaan-m/ECC)stop-format-typecheck 移植:
|
|
3
3
|
# - 编辑记账:本回合编辑过的代码/配置文件(O(1),md 等非代码面豁免);
|
|
4
4
|
# - agent/turn-stopping 批处理:从编辑路径向上探测最近清单根
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# 设置卡:enabled / 注入配额 / 门禁预算 / 记忆反馈(开关 + 网关地址 + 密钥凭据引用,
|
|
12
12
|
# 记忆反馈默认关闭且地址默认为空 = 不启用;密钥走 dsh 凭据通道,不落设置)。
|
|
13
13
|
#
|
|
14
|
-
#
|
|
14
|
+
# 组合包层:本文件由 package.json 的 dsh.bundle.patch
|
|
15
15
|
# 指向,随 `dsh plugin --profile web add/remove ./plugins/quality-gate` 维护。
|
|
16
16
|
# 行按裸包名引用本包(client-modules 只扫描裸包名条目的 dsh.client 声明)。
|
|
17
17
|
# 注意 pnpm ≥12.3.0:12.0.0 的 add 本地目录有 #14437 bug。
|
package/host.js
CHANGED
|
@@ -30,7 +30,7 @@ const DEFAULT_MAX_FILES = 500;
|
|
|
30
30
|
var EditAccumulator = class {
|
|
31
31
|
files = /* @__PURE__ */ new Set();
|
|
32
32
|
maxFiles;
|
|
33
|
-
/** 容量淘汰累计丢弃数:drain 时清零(
|
|
33
|
+
/** 容量淘汰累计丢弃数:drain 时清零(500-cap 静默淘汰要可观测)。 */
|
|
34
34
|
dropped = 0;
|
|
35
35
|
constructor(options = {}) {
|
|
36
36
|
this.maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
|
|
@@ -56,7 +56,7 @@ var EditAccumulator = class {
|
|
|
56
56
|
this.files.delete(oldest);
|
|
57
57
|
this.dropped += 1;
|
|
58
58
|
}
|
|
59
|
-
/** 自上次 drain
|
|
59
|
+
/** 自上次 drain 以来容量淘汰丢弃的文件数(>0 时 host 侧 console.warn)。 */
|
|
60
60
|
droppedCount() {
|
|
61
61
|
return this.dropped;
|
|
62
62
|
}
|
|
@@ -190,8 +190,7 @@ function renderTemplate(text, params) {
|
|
|
190
190
|
//#endregion
|
|
191
191
|
//#region lib/feedback-content.ts
|
|
192
192
|
/** 门禁失败 → 记忆正文:单行化(防换行噪音)。
|
|
193
|
-
*
|
|
194
|
-
* 多根错误的中间根因吃掉;记忆网关的入库限额由网关侧把关,这里送全文。
|
|
193
|
+
* 不截断:截尾会吃掉多根错误里的中间根因;记忆网关的入库限额由网关侧把关,这里送全文。
|
|
195
194
|
* 正文语言随官方 locale 偏好走(messages 由调用方按回合取出后传进来)——这条文本会被
|
|
196
195
|
* auto-recall 注入后续会话的上下文,是模型读的,不是日志。 */
|
|
197
196
|
function buildFeedbackText({ command, root, failure }, messages) {
|
|
@@ -213,9 +212,9 @@ function envOr(key, fallback) {
|
|
|
213
212
|
}
|
|
214
213
|
/**
|
|
215
214
|
* 网关基址:设置值优先,留空时回落到 TDAI_GATEWAY_URL(逃生门),**两处都空则为空串**。
|
|
216
|
-
* 空串即"未配置"
|
|
217
|
-
*
|
|
218
|
-
*
|
|
215
|
+
* 空串即"未配置"——调用方据此不启用记忆反馈。默认值不是任何本机地址:把某个开发机的
|
|
216
|
+
* 端口写死成默认,等于让别人的每次门禁失败都去连一个不存在的服务。设置优先于 env 的理由:
|
|
217
|
+
* 设置卡是用户显式配置面,env 只能兜底,
|
|
219
218
|
* 否则用户改了设置卡仍被一个看不见的环境变量越权。
|
|
220
219
|
*/
|
|
221
220
|
function resolveGatewayUrl(settingUrl) {
|
|
@@ -266,7 +265,7 @@ async function pushGateFeedback(input, target, messages) {
|
|
|
266
265
|
//#region host.ts
|
|
267
266
|
/**
|
|
268
267
|
* 常见项目根标记文件:向上找最近的含清单目录。
|
|
269
|
-
* ruff.toml
|
|
268
|
+
* ruff.toml 必须在列:只有 ruff 配置、无 pyproject.toml 的 Python
|
|
270
269
|
* 工程若不在此登记,gate-detect 的 has("ruff.toml") 就是死码——那类工程根本得不到根,
|
|
271
270
|
* 也就永远没有门禁。
|
|
272
271
|
*/
|
|
@@ -292,14 +291,15 @@ const GATE_SOURCE_KIND = "plugin:quality-gate";
|
|
|
292
291
|
const GATE_KIND_CODE_FAILURE = "code-failure";
|
|
293
292
|
/** 设置面(= profile 条目 `quality-gate` 的可编辑表单)与 loader 行 config 共用同一份
|
|
294
293
|
* schema(单源,防漂移)。六个字段**全部** `.volatile()`:没有任何 volatile 字段的条目
|
|
295
|
-
* 会被宿主 describe() 整条跳过(
|
|
296
|
-
*
|
|
294
|
+
* 会被宿主 describe() 整条跳过(installed @deepseek-ai/dsh-settings/lib/index.js:413-418
|
|
295
|
+
* 只在 volatileForm(schema) 有值时才登记条目),写入则抛
|
|
296
|
+
* `has no volatile fields`(:505-506)。
|
|
297
297
|
* 两个策略陷阱(都锁在下面对应的行内注释里,改默认前先读):
|
|
298
298
|
* - `memoryGatewayUrl` 的默认是**空串**而不是缺省:`""` + feedback-off = 永不推送,
|
|
299
299
|
* 换成 undefined 会把"未配置"变成"字段不存在",行为不再等价;
|
|
300
300
|
* - `memoryGatewayKeyRef` 的 `.role('credential-ref')` 与 `.volatile()` **共存**:role 只
|
|
301
|
-
* 写 meta.role(fork 的 role/volatile
|
|
302
|
-
*
|
|
301
|
+
* 写 meta.role(fork 的 role/volatile 都走 `extra`=`{...meta, k: v}` 展开,见
|
|
302
|
+
* installed @deepseek-ai/schemastery/src/index.ts:385-389),不会抹掉 meta.volatile。 */
|
|
303
303
|
const Config = Schema.object({
|
|
304
304
|
enabled: Schema.boolean().default(true).volatile(),
|
|
305
305
|
maxInjectsPerTurn: Schema.natural().min(1).max(5).default(2).volatile(),
|
|
@@ -348,13 +348,12 @@ function pruneToSessionBudget(map, keep) {
|
|
|
348
348
|
}
|
|
349
349
|
}
|
|
350
350
|
/**
|
|
351
|
-
*
|
|
352
|
-
* (delegationDepth>0 或 origin==='subagent')跳过门禁。
|
|
351
|
+
* 根会话判定:子代理会话(delegationDepth>0 或 origin==='subagent')跳过门禁。
|
|
353
352
|
*
|
|
354
|
-
* agent/turn-stopping 对每个 agent(含 in-process fork/spawn
|
|
355
|
-
*
|
|
353
|
+
* agent/turn-stopping 对每个 agent(含 in-process fork/spawn 子代理)都触发,不过滤的后果有
|
|
354
|
+
* 两条:①父会话与子代理对同一项目根各跑一遍门禁(token/耗时翻倍);
|
|
356
355
|
* ②失败注入文案面向用户,却 followup 进子代理——结算前到达会多拖一轮子代理回合,
|
|
357
|
-
* 结算后到达则滞留 inbox
|
|
356
|
+
* 结算后到达则滞留 inbox 被丢弃(门禁静默失效)。所以只对根会话收口。
|
|
358
357
|
*
|
|
359
358
|
* out-of-process 子代理(ACP/claude-code/codex)
|
|
360
359
|
* 在独立进程运行,不发射 dsh 的 agent 事件,其编辑天然不在任何门禁面内;in-process
|
|
@@ -374,7 +373,7 @@ function inboxHasPending(inbox) {
|
|
|
374
373
|
* (官方 agent.ts:turn-stopping 在 running 相内 serial 分发,转 idle 发生在回合结束之后),
|
|
375
374
|
* 故绝不能用 status 判 idle。改用数据信号:turn signal 已取消、或 inbox 有待处理
|
|
376
375
|
* (用户接管/已排队工作)→ 不注入(对齐官方 "Data decides" 语义)。
|
|
377
|
-
* 插件卸载的拦截不在这里:
|
|
376
|
+
* 插件卸载的拦截不在这里:disposer abort 在飞门禁批次,迟到结果在批次层就地丢弃
|
|
378
377
|
* (见 processGate),到不了注入点——卸载收口比"注入前拦一下"更强。
|
|
379
378
|
*/
|
|
380
379
|
function preInjectCheck(agent, signal) {
|
|
@@ -433,11 +432,11 @@ function headExcerpt(file) {
|
|
|
433
432
|
* 证据文本。
|
|
434
433
|
*
|
|
435
434
|
* tsc/cargo 等错误自顶向下,**首条才是根因**,而 harness CollectedOutput 截断时保留的是
|
|
436
|
-
* **尾部**(
|
|
437
|
-
*
|
|
435
|
+
* **尾部**(installed @deepseek-ai/dsh-subprocess/lib/types/types.d.ts:18-23)——
|
|
436
|
+
* 于是不读 `truncated` 就把根因错误静默丢掉,注入给模型的"修这些错"只剩残尾。必须:
|
|
438
437
|
* ①显式标注截断;②从 spillPath 有界读回头部摘录放在最前;③无落盘文件时明确说明
|
|
439
438
|
* 首个错误可能已丢弃,让模型与用户都知道证据不完整。
|
|
440
|
-
*
|
|
439
|
+
* 未截断时不叠加第二层截断(不为省 token 降能力,上限由 shell 的
|
|
441
440
|
* stdoutMaxBytes 把关)。
|
|
442
441
|
*/
|
|
443
442
|
function evidenceText(messages, stream, label) {
|
|
@@ -479,7 +478,7 @@ const skippedGateLine = (messages, gate) => renderTemplate(messages.skippedGateI
|
|
|
479
478
|
command: gate.command.join(" "),
|
|
480
479
|
root: gate.root
|
|
481
480
|
});
|
|
482
|
-
/** 预算耗尽后剩余门禁的"未执行"
|
|
481
|
+
/** 预算耗尽后剩余门禁的"未执行"清单:只 warn 等于静默通过,所以要注入给模型与用户。 */
|
|
483
482
|
const skippedText = (messages, gates) => renderTemplate(messages.gatesSkipped, {
|
|
484
483
|
budgetMs: MIN_USEFUL_GATE_MS,
|
|
485
484
|
list: gates.map((gate) => skippedGateLine(messages, gate)).join("\n")
|
|
@@ -535,7 +534,7 @@ function interruptedRunOutcome(messages, budgetMs, res) {
|
|
|
535
534
|
*
|
|
536
535
|
* 环境问题非代码问题:走失败注入会让模型去"修"装不了的工具(配合配额环更糟)。
|
|
537
536
|
* 但它同样是"没跑成的检查",所以按 not-run 注入"请手动确认",不再算作通过
|
|
538
|
-
*
|
|
537
|
+
* ——没跑成 ≠ 通过。
|
|
539
538
|
*/
|
|
540
539
|
function unavailableToolOutcome(messages, gate, res, log) {
|
|
541
540
|
let outcome;
|
|
@@ -552,10 +551,11 @@ function unavailableToolOutcome(messages, gate, res, log) {
|
|
|
552
551
|
/**
|
|
553
552
|
* 把一次 shell.execute().result() 结果分类成三态(纯函数:不进 ctx,每条分支都可测)。
|
|
554
553
|
*
|
|
555
|
-
* harness 的 ShellRunResult.sandbox 与退出码**正交**上报(
|
|
554
|
+
* harness 的 ShellRunResult.sandbox 与退出码**正交**上报(installed
|
|
555
|
+
* @deepseek-ai/dsh-shell/lib/types/types.d.ts:28:
|
|
556
556
|
* "Facts are reported independently of process exit status so callers can distinguish
|
|
557
|
-
* command failures from policy denials and runner failures"
|
|
558
|
-
*
|
|
557
|
+
* command failures from policy denials and runner failures"),所以必须读它——不读就会把
|
|
558
|
+
* 策略拒绝当成代码失败注入"修这些错"。
|
|
559
559
|
*
|
|
560
560
|
* 判定序(三支各自成函数,顺序与 warn 次数都不变):沙箱面拦截 → 进程没跑完 →
|
|
561
561
|
* 工具装不上(126/127)→ exit 0(部分生效沙箱只 warn)→ npx stub 横幅 → 代码失败。
|
|
@@ -661,7 +661,7 @@ function collectEdits(events) {
|
|
|
661
661
|
const noCallId = [];
|
|
662
662
|
const failed = /* @__PURE__ */ new Set();
|
|
663
663
|
const { calls, results } = scanToolEvents(events);
|
|
664
|
-
for (const call of calls) if (
|
|
664
|
+
for (const call of calls) if (isEditTool(call.name) && !call.badArguments) {
|
|
665
665
|
const target = editPathOf(call);
|
|
666
666
|
if (target.kind === "write") storeEdit(call.callId, {
|
|
667
667
|
name: call.name,
|
|
@@ -676,7 +676,7 @@ function collectEdits(events) {
|
|
|
676
676
|
};
|
|
677
677
|
}
|
|
678
678
|
/** 单文件向上找最近的清单根;hitDepthLimit 标记是否因超深而无根(供可观测 warn)。
|
|
679
|
-
* maxDepth 来自 Config 的 maxRootDepth
|
|
679
|
+
* maxDepth 来自 Config 的 maxRootDepth(非 volatile 部署值)。 */
|
|
680
680
|
function findManifestRoot(file, maxDepth) {
|
|
681
681
|
let dir = path.dirname(file);
|
|
682
682
|
let found;
|
|
@@ -746,8 +746,8 @@ const injectMessage = (text) => ({
|
|
|
746
746
|
/**
|
|
747
747
|
* 总线上报(自进化闭环):门禁失败是事实,无论注入与否都要沉淀
|
|
748
748
|
* ——修复环里同一门禁反复失败会形成 violation 序列,度量"提醒有没有用"。
|
|
749
|
-
* 但"没跑成"以 gate-not-run
|
|
750
|
-
*
|
|
749
|
+
* 但"没跑成"以 gate-not-run 单独上报:环境/策略问题不得被学成代码教训——假的失败指令
|
|
750
|
+
* 一旦既进注入又进记忆/lesson-loop,闭环就学会了错的东西。
|
|
751
751
|
* report 自身容错(同步抛错与异步拒绝经 settleLessonCall 落进同一条 warn);
|
|
752
752
|
* 总线缺失(未装 lesson-loop)时 get 返回 undefined 直接跳过。
|
|
753
753
|
*/
|
|
@@ -828,9 +828,8 @@ async function pushFeedbackToMemory(get, notice, cfg, messages, input) {
|
|
|
828
828
|
notice.log.warn(`[quality-gate] memory feedback skipped: ${messageOf(error)}`);
|
|
829
829
|
}
|
|
830
830
|
}
|
|
831
|
-
/**
|
|
832
|
-
*
|
|
833
|
-
* packages/llm/llm-deepseek/src/config.ts 的 `plainOptions(config)`。 */
|
|
831
|
+
/** 一次读全的解析后设置快照(`scope.get()` 的等价物):门禁按回合现读,纯值往下传,
|
|
832
|
+
* 不必让每个消费方都持有引用——所有 `.volatile()` 引用一次 `.get()` 成普通值。 */
|
|
834
833
|
function settingsOf(config) {
|
|
835
834
|
return {
|
|
836
835
|
enabled: config.enabled.get(),
|
|
@@ -858,7 +857,7 @@ function isGateHost(value) {
|
|
|
858
857
|
return typeof get === "function" && typeof on === "function" && typeof effect === "function" && typeof inject === "function" && isRecord(fiber) && typeof fieldOf(settings, "describe") === "function" && typeof fieldOf(settings, "configure") === "function";
|
|
859
858
|
}
|
|
860
859
|
/**
|
|
861
|
-
* 从编辑文件路径向上找最近的清单根(层数上限 = Config 的 maxRootDepth
|
|
860
|
+
* 从编辑文件路径向上找最近的清单根(层数上限 = Config 的 maxRootDepth,非 volatile
|
|
862
861
|
* 部署值),每个根探一次真实盘上事实(清单名 + 已声明脚本)。找不到清单根的文件跳过
|
|
863
862
|
* (无门禁面)。
|
|
864
863
|
*/
|
|
@@ -879,8 +878,8 @@ function projectRootsOf(files, maxDepth, log) {
|
|
|
879
878
|
/**
|
|
880
879
|
* 执行一个门禁:只有 exitCode 0 才是 pass。
|
|
881
880
|
* 超时/取消/信号/策略拒绝/工具装不上都走 classifyRun 的显式三态;
|
|
882
|
-
* **catch
|
|
883
|
-
*
|
|
881
|
+
* **catch 一律不报通过**:workdir 不存在、spawn 失败、SandboxUnavailableError
|
|
882
|
+
* 都会落进 catch,而被当 PASS 就是漏门禁。
|
|
884
883
|
*/
|
|
885
884
|
async function runGate(messages, shell, gate, budgetMs, context) {
|
|
886
885
|
const { policy, signal, stdoutMaxBytes, log } = context;
|
|
@@ -914,17 +913,17 @@ async function runGate(messages, shell, gate, budgetMs, context) {
|
|
|
914
913
|
* **不可写成 `svc.shell`**:cordis ctx 的 Proxy get 陷阱对未列入 `inject` 的属性
|
|
915
914
|
* 直接抛 `cannot get property "shell" without inject`(见 cordis reflect.ts 的
|
|
916
915
|
* `ReflectService.handler.get`),于是下面的 `=== undefined` 判定永远走不到,
|
|
917
|
-
*
|
|
916
|
+
* 无 shell 时的静默放行分支形同虚设,且每回合结束都冒一个 turn 错误卡。
|
|
918
917
|
* shell 服务由 host 组合(@deepseek-ai/dsh-shell 的 Service 构造即注册),
|
|
919
|
-
* 正常环境下 get
|
|
918
|
+
* 正常环境下 get 命中;异常环境才走静默放行。
|
|
920
919
|
*
|
|
921
920
|
* **也不加 `?? svc.shell` 兜底**:shell 真缺失时右值会重新触发同一个抛错,
|
|
922
|
-
*
|
|
921
|
+
* 兜底反而把静默放行再打断。mock 需实现 get() 才有意义。
|
|
923
922
|
*/
|
|
924
923
|
function getShell(rt) {
|
|
925
924
|
return rt.svc.get("shell");
|
|
926
925
|
}
|
|
927
|
-
/**
|
|
926
|
+
/** 取本会话的解析后沙箱策略。`session` 交官方 `Session` 类本身:
|
|
928
927
|
* 官方 `SandboxPolicyRequest.session` 就是这个类(@deepseek-ai/dsh-sandbox-policy/lib/
|
|
929
928
|
* types/index.d.ts:52),而 `Session` 带私有字段、按名义比 —— 本包那个只服务退役中同步
|
|
930
929
|
* 读面的 SessionRef 视图交不进去,编译器原话:`Type 'SessionRef' is missing the following
|
|
@@ -966,9 +965,9 @@ function accumulatorOf(rt, sessionId) {
|
|
|
966
965
|
}
|
|
967
966
|
/**
|
|
968
967
|
* 从事件流读本回合**成功**编辑的文件(按官方观察面给的增量窗),记进本会话的账。
|
|
969
|
-
*
|
|
970
|
-
*
|
|
971
|
-
*
|
|
968
|
+
* 被 danger-guard 拒绝或执行失败的编辑,其 tool/call 仍在事件流里——照记就会对未真正
|
|
969
|
+
* 改动的文件(若落在独立项目根)多跑一次门禁。所以 tool/call 建 callId→{name,path},
|
|
970
|
+
* tool/result 的 isError 标记失败 callId,只记成功的。
|
|
972
971
|
* turn-stopping 时本回合 call+result 同处一个增量窗,单次扫描即可关联;无 callId 的事件
|
|
973
972
|
* (异常/旧形状/测试 mock)保守直记,避免漏门禁。
|
|
974
973
|
* fork 前缀修复(源码核对 0.1.3 后落地):子代理会话 fork 自父会话时,
|
|
@@ -1053,7 +1052,7 @@ function handleGateFailure(scope, target, problem) {
|
|
|
1053
1052
|
}
|
|
1054
1053
|
}
|
|
1055
1054
|
/** 卸载收口的读法:两次判读之间隔着 `await runGate(...)`,那期间 disposer 的 abort() 是真会
|
|
1056
|
-
*
|
|
1055
|
+
* 发生的(卸载收口由测试钉住);直接读 `gateAbort.signal.aborted` 会被控制流分析钉在前一次
|
|
1057
1056
|
* 判读的 false 上(属性窄不跨 await 复位),后一处守卫就被判成恒假。经函数取值 ⇒ 每读都按
|
|
1058
1057
|
* 声明的 boolean 算,守卫才是必要的。 */
|
|
1059
1058
|
function gateAborted(rt) {
|
|
@@ -1096,14 +1095,13 @@ async function processGate(state, left) {
|
|
|
1096
1095
|
if (!handleGateFailure(scope, gate, outcome)) await processGate(state, left.slice(1));
|
|
1097
1096
|
}
|
|
1098
1097
|
/**
|
|
1099
|
-
*
|
|
1098
|
+
* 一趟门禁批次:按总预算封顶(perGate 保底 10s 会超支)——跟踪已花时间,每门
|
|
1100
1099
|
* min(perGate, remaining)。尾段不足最小可用预算(真实 tsc/pnpm 冷启动也要数秒,几百 ms
|
|
1101
|
-
* 必超时 → 假"超时失败"
|
|
1102
|
-
* 通过(审计 MEDIUM 项)。
|
|
1100
|
+
* 必超时 → 假"超时失败"注入)时**注入未执行说明**——只 console.warn 等于静默通过。
|
|
1103
1101
|
*
|
|
1104
|
-
*
|
|
1102
|
+
* 防御收口:①整段顶层 try/catch——批次自身故障只记一条 error,绝不产生
|
|
1105
1103
|
* unhandledRejection;②批次 promise 由调用方登记进在飞账本,disposer abort 后 await 收敛
|
|
1106
|
-
* (
|
|
1104
|
+
* (dispose 必须终止并等待在飞工作)。
|
|
1107
1105
|
*/
|
|
1108
1106
|
async function runGateBatch(input) {
|
|
1109
1107
|
const { scope } = input;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jayyuen66/dsh-quality-gate",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "dsh 插件:回合收口质量门禁(在 agent/turn-stopping 按项目形态跑 pnpm check / cargo check / ruff check / tsc,未过则注入修复指令开新轮)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "JayYuen",
|
|
@@ -12,7 +12,11 @@
|
|
|
12
12
|
"icon.svg",
|
|
13
13
|
"host.js",
|
|
14
14
|
"client.js",
|
|
15
|
-
"cordis.patch.yml"
|
|
15
|
+
"cordis.patch.yml",
|
|
16
|
+
"README.md",
|
|
17
|
+
"README.zh-CN.md",
|
|
18
|
+
"CHANGELOG.md",
|
|
19
|
+
"THIRD_PARTY_NOTICES.md"
|
|
16
20
|
],
|
|
17
21
|
"type": "module",
|
|
18
22
|
"main": "host.js",
|
|
@@ -27,48 +31,50 @@
|
|
|
27
31
|
"registry": "https://registry.npmjs.org"
|
|
28
32
|
},
|
|
29
33
|
"dependencies": {
|
|
30
|
-
"@
|
|
31
|
-
"@deepseek-ai/schemastery": "3.18.4",
|
|
32
|
-
"@jayyuen66/dsh-plugin-shared": "^0.1.0"
|
|
34
|
+
"@jayyuen66/dsh-plugin-shared": "^0.1.4"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
|
-
"@deepseek-ai/cordis": "4.0.
|
|
36
|
-
"@deepseek-ai/dsh-agent": "0.2.
|
|
37
|
-
"@deepseek-ai/dsh-api-remotes": "0.2.
|
|
38
|
-
"@deepseek-ai/dsh-client-locale": "0.2.
|
|
39
|
-
"@deepseek-ai/dsh-client-store": "0.2.
|
|
40
|
-
"@deepseek-ai/dsh-client-ui-layout": "0.2.
|
|
41
|
-
"@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.
|
|
42
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.2.
|
|
43
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.2.
|
|
44
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.2.
|
|
45
|
-
"@deepseek-ai/dsh-
|
|
46
|
-
"@deepseek-ai/dsh-
|
|
47
|
-
"@deepseek-ai/dsh-
|
|
48
|
-
"@deepseek-ai/dsh-sandbox
|
|
49
|
-
"@deepseek-ai/dsh-
|
|
50
|
-
"@deepseek-ai/dsh-
|
|
51
|
-
"@deepseek-ai/dsh-session
|
|
52
|
-
"@deepseek-ai/dsh-session-
|
|
53
|
-
"@deepseek-ai/dsh-session-
|
|
54
|
-
"@deepseek-ai/dsh-
|
|
55
|
-
"@deepseek-ai/dsh-
|
|
56
|
-
"@deepseek-ai/dsh-
|
|
57
|
-
"@
|
|
37
|
+
"@deepseek-ai/cordis": "4.0.5-alpha.1",
|
|
38
|
+
"@deepseek-ai/dsh-agent": "0.2.1-alpha.2",
|
|
39
|
+
"@deepseek-ai/dsh-api-remotes": "0.2.1-alpha.2",
|
|
40
|
+
"@deepseek-ai/dsh-client-locale": "0.2.1-alpha.2",
|
|
41
|
+
"@deepseek-ai/dsh-client-store": "0.2.1-alpha.2",
|
|
42
|
+
"@deepseek-ai/dsh-client-ui-layout": "0.2.1-alpha.2",
|
|
43
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.1-alpha.2",
|
|
44
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.1-alpha.2",
|
|
45
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.2.1-alpha.2",
|
|
46
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-alpha.2",
|
|
47
|
+
"@deepseek-ai/dsh-credentials": "0.2.1-alpha.2",
|
|
48
|
+
"@deepseek-ai/dsh-llm": "0.2.1-alpha.2",
|
|
49
|
+
"@deepseek-ai/dsh-package-manifest": "0.2.1-alpha.2",
|
|
50
|
+
"@deepseek-ai/dsh-sandbox": "0.2.1-alpha.2",
|
|
51
|
+
"@deepseek-ai/dsh-sandbox-policy": "0.2.1-alpha.2",
|
|
52
|
+
"@deepseek-ai/dsh-scope": "0.2.1-alpha.2",
|
|
53
|
+
"@deepseek-ai/dsh-session": "0.2.1-alpha.2",
|
|
54
|
+
"@deepseek-ai/dsh-session-projection": "0.2.1-alpha.2",
|
|
55
|
+
"@deepseek-ai/dsh-session-query": "0.2.1-alpha.2",
|
|
56
|
+
"@deepseek-ai/dsh-session-title": "0.2.1-alpha.2",
|
|
57
|
+
"@deepseek-ai/dsh-settings": "0.2.1-alpha.2",
|
|
58
|
+
"@deepseek-ai/dsh-shell": "0.2.1-alpha.2",
|
|
59
|
+
"@deepseek-ai/dsh-subprocess": "0.2.1-alpha.2",
|
|
60
|
+
"@deepseek-ai/schemastery": "3.18.5-alpha.1",
|
|
61
|
+
"@types/node": "26.6.5",
|
|
58
62
|
"@types/react": "19.3.0",
|
|
59
|
-
"@vitest/coverage-v8": "5.0.
|
|
63
|
+
"@vitest/coverage-v8": "5.0.3",
|
|
60
64
|
"eslint-plugin-sonarjs": "4.2.2",
|
|
61
|
-
"oxfmt": "0.
|
|
62
|
-
"oxlint": "1.
|
|
63
|
-
"oxlint-tsgolint": "7.0.
|
|
65
|
+
"oxfmt": "0.72.0",
|
|
66
|
+
"oxlint": "1.87.0",
|
|
67
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
64
68
|
"react": "^19.3.0",
|
|
65
|
-
"rolldown": "1.2.
|
|
69
|
+
"rolldown": "1.2.13",
|
|
66
70
|
"typescript": "7.0.2",
|
|
67
|
-
"vite": "8.3.
|
|
68
|
-
"vitest": "5.0.
|
|
71
|
+
"vite": "8.3.4",
|
|
72
|
+
"vitest": "5.0.3"
|
|
69
73
|
},
|
|
70
74
|
"peerDependencies": {
|
|
71
|
-
"@deepseek-ai/dsh": "^0.2.
|
|
75
|
+
"@deepseek-ai/dsh": "^0.2.1-alpha.2",
|
|
76
|
+
"@deepseek-ai/dsh-credentials": "0.2.1-alpha.2",
|
|
77
|
+
"@deepseek-ai/schemastery": "3.18.5-alpha.1"
|
|
72
78
|
},
|
|
73
79
|
"peerDependenciesMeta": {
|
|
74
80
|
"@deepseek-ai/dsh": {
|
|
@@ -77,7 +83,8 @@
|
|
|
77
83
|
},
|
|
78
84
|
"icon": "icon.svg",
|
|
79
85
|
"engines": {
|
|
80
|
-
"dsh": ">=0.2.
|
|
86
|
+
"dsh": ">=0.2.1-alpha.2",
|
|
87
|
+
"node": ">=22.12.0"
|
|
81
88
|
},
|
|
82
89
|
"dsh": {
|
|
83
90
|
"manifestVersion": 1,
|
|
@@ -93,7 +100,8 @@
|
|
|
93
100
|
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.client.json",
|
|
94
101
|
"lint": "oxlint .",
|
|
95
102
|
"test": "vitest run --coverage",
|
|
96
|
-
"check": "
|
|
103
|
+
"check:pack": "node pack-check.mjs",
|
|
104
|
+
"check": "npm run --loglevel=warn typecheck && npm run --loglevel=warn lint && npm run --loglevel=warn build:client && npm run --loglevel=warn build && npm run --loglevel=warn test && npm run --loglevel=warn check:pack && npm run --loglevel=warn fmt:check",
|
|
97
105
|
"build:client": "node build-client.mjs",
|
|
98
106
|
"build": "node build-host.mjs",
|
|
99
107
|
"fmt:check": "oxfmt --check ."
|