@jayyuen66/dsh-quality-gate 0.1.3 → 0.1.5
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 +40 -0
- package/LICENSE +1 -1
- package/README.md +15 -14
- package/README.zh-CN.md +14 -13
- package/THIRD_PARTY_NOTICES.md +4 -1
- package/client.js +602 -270
- package/cordis.patch.yml +4 -2
- package/docs/DETAIL.md +24 -0
- package/docs/DETAIL.zh.md +24 -0
- package/host.js +353 -84
- package/locale/en.json +7 -0
- package/locale/zh.json +7 -0
- package/package.json +52 -34
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,44 @@
|
|
|
3
3
|
格式遵循 [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),版本号遵循
|
|
4
4
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html)。最新版本在最上。
|
|
5
5
|
|
|
6
|
+
## 0.1.5
|
|
7
|
+
|
|
8
|
+
一轮跨面审查修复:凭据 URL 判据重建、设置卡改逐字段校验、门禁探测与反馈投递收口;
|
|
9
|
+
注入配额语义改为按会话跨回合累计。
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **凭据 URL 判据重建**(新增 `lib/credential-url.ts`,`lib/gateway-target.ts` 改为转导出):IPv4-mapped 回环地址按末 32 位数值判(`::ffff:7fa:0` 不再误判回环);`resolveGatewayUrl` 对带 userinfo/query/hash 的基址回空串(fail-closed)。
|
|
14
|
+
- **设置卡逐字段校验**(`src/client-entry.ts`):`projectCardValues` 改逐字段解析,坏 URL 不再清空整卡;网关地址经 `isCredentialSafeUrl` refine,保存前拦 `invalidGatewayUrl`;凭据引用名按 REF_PATTERN 复判 `invalidKeyRef`;数字字段弃用宿主 zod 缺失的 `.validate`,改 `inBounds` 边界自判(宿主冻结 zod 4.4.3 下不再崩)。
|
|
15
|
+
- **台账读口**(`lib/edit-ledger-reader.ts`):publish/effect 收进 try,失败先撤读口、再释放投影、最后报原始错;重复 dispose 幂等。
|
|
16
|
+
- **台账游标与卸载**(`host.ts`):游标只推进到台账实际覆盖末端(`EDIT_LEDGER_MAX` 截断不再永久跳号);异步读窗后复查卸载旗,dispose 后不再新建 flight。
|
|
17
|
+
- **门禁探测**(`lib/gate-detect.ts`):workspace 根缺 check 脚本不再降级发 tsc(solution-style 恒绿);同 root 去重;清单加 `.ruff.toml`。
|
|
18
|
+
- **网关反馈**(`lib/gateway-feedback.ts`):基址做 http(s)/query/userinfo 校验并复用凭据判据;非法 serviceId 回落 default;空正文不投递;错误正文按 content-type 才入日志。
|
|
19
|
+
- **反馈内容**(`lib/feedback-content.ts`):command 非数组容错、空 failure 不入库、正文追加 prompt-injection 隔离标注;`renderTemplate` 占位符放宽到 `{[^{}]+}`。
|
|
20
|
+
- **散修**:`lib/accumulator.ts` 路径 trim+normalize 归一;`lib/edit-ledger.ts` `strictObject`→`object`、failEntry 幂等、空 callId 不标失败;`pack-check.mjs` 改用官方 `builtinModules`、模板插值排除、产物缺席/无入口显形、win32 走 ComSpec;`build-host.mjs` 加空产物守卫。
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- **注入配额语义**:由「每回合每根」改为「每会话跨回合累计、干净通过重置」(`host.ts`、`cordis.patch.yml` 与卡片中英文案同步)。
|
|
25
|
+
- **发布链加固**(`.github/workflows/publish.yml`):分支守卫比完整 ref、token 形态校验、tarball 内 name/version 与预检逐字比对、查 registry 重复版本、直接发布那枚 tarball、发布后可见性轮询;CI 侧 actionlint 按 glob 逐文件豁免、codeql 对 fork PR 只对 analyze 步容错。
|
|
26
|
+
- **构建与元数据**:package.json 删 `prepack`、typecheck 四 program 串联;coverage 分母加 `src/ui-messages.ts`;oxlint `syncWrites` 改按文件 `syncIoScopes`;author 改对象形;`@deepseek-ai/schemastery` peer 放宽为 `^3.18.6-alpha.1`;catalog 的 shared 抬到 0.2.2;devDependencies 新增 `@deepseek-ai/dsh-bash-local` / `@deepseek-ai/dsh-subprocess-local` 与 fast-check 4.10.2。
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **测试三层**:`test/e2e/real-tsc.e2e.test.ts`(真 tsc 进程)、`test/platform/guards.platform.test.ts`、`test/property/gate-invariants.property.test.ts`(fast-check)、`test/client-zod-surface.test.ts`(禁 `.validate` 的门禁)。
|
|
31
|
+
|
|
32
|
+
## 0.1.4
|
|
33
|
+
|
|
34
|
+
这一版收窄回合门禁的作用域:子包自己的 workspace 门禁不再被外层伞工作区顶掉,改单仓的回合不再跑整棵树。
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- **单仓安装态下两份 zod 的 `ZodType` 不兼容**:宿主投影包自带 zod 4.4.3,本包 catalog 是
|
|
39
|
+
4.6.7;`editLedgerProjection.stateSchema` 经宿主声明的类型做一次窄桥接,运行期仍交给宿主
|
|
40
|
+
`safeParse`,不改依赖版本。
|
|
41
|
+
- **子包自己的 workspace 门禁被外层伞工作区顶掉。** 编辑一个自带 `pnpm-workspace.yaml` 且声明了 `check` 的子包时,根探测会继续上溯到外层伞工作区,把「改一个仓」放大成「整棵树跑一遍 `pnpm check`」:回合收口的预算被单条命令吃光,改本仓的回合反而反复超时。现在子包自己产得出 `pnpm check` 时就地执行、不再上溯;外层升级只在子包自己产不出门禁时发生。`test/host-integrity.test.ts` 新增一条伞工作区用例钉住这一面。
|
|
42
|
+
- **脚本内部的 exit 126/127 被误报成「门禁工具缺失」。** 126/127 是整条命令的退出码,不是「工具没装」的充分证据:`pnpm check` 的 check 脚本里缺命令时 pnpm 同样以 127 退出(实测 `pnpm run check` 对不存在的脚本命令回 `[ELIFECYCLE] Command failed with exit code 127`),tsc 的脚本也可能因缺 devDependency 回 127。原先这两档一律按 not-run 注入「command not found (tool not installed) — the gate did not run」,项目自己的坏脚本被当成环境问题放掉。现在只有 stderr **点名门禁命令自己的首 token**(`sh: pnpm: command not found`、`command not found: tsc`、Windows 的 `'pnpm' is not recognized`)才判工具不可用,其余 126/127 按代码失败上报并附证据;126 同理只在点名「不可执行」时判 not-run。`test/host.test.ts` 与 `test/host-integrity.test.ts` 的 126/127 用例补上点名证据,并新增一条「脚本内 127 仍报代码失败」的用例。
|
|
43
|
+
|
|
6
44
|
## 0.1.3
|
|
7
45
|
|
|
8
46
|
这一版把判定面与构建/CI 面一起收口:ruff 不再对只写了 `pyproject.toml` 的工程发门禁,卡片保存期间的编辑不再被静默丢弃,记忆反馈的文案与来源一致,Windows 与发布链的静默降级补上判据。宿主线抬到 `0.2.1-rc.3`。
|
|
@@ -24,6 +62,8 @@
|
|
|
24
62
|
|
|
25
63
|
### Changed
|
|
26
64
|
|
|
65
|
+
- **宿主开发线抬到 `0.2.2-alpha.2`**:devDependencies 与 catalog/overrides 同值;`engines.dsh` 与
|
|
66
|
+
`@deepseek-ai/dsh*` peer 写支持窗口 `^0.2.1-rc.3`。
|
|
27
67
|
- **`ruff` 门禁的可发条件**(见 Fixed 第一条)是**判定规则**的变化:只有 `pyproject.toml` 的 Python 工程从此不再自动获得 ruff 门禁。两份 README 的命令表同步。
|
|
28
68
|
- **宿主线抬到 `0.2.1-rc.3`**:`engines.dsh`、peer/dev 依赖、overrides 与 README 徽标/正文同值。
|
|
29
69
|
- **构建编排统一进 shared 的 `lib/build-*`**:三个构建脚本只剩薄配置层,产物仍落 `dist/`;CI/CodeQL/Scorecard/Dependabot 与 Windows 矩阵补齐。
|
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
10
|
<a href="https://www.npmjs.com/package/@jayyuen66/dsh-quality-gate"><img src="https://img.shields.io/npm/v/@jayyuen66/dsh-quality-gate?style=flat-square&logo=npm&logoColor=white" alt="npm version" /></a>
|
|
11
|
-
<a href="https://www.npmjs.com/package/@jayyuen66/dsh-quality-gate"><img src="https://img.shields.io/npm/
|
|
11
|
+
<a href="https://www.npmjs.com/package/@jayyuen66/dsh-quality-gate"><img src="https://img.shields.io/npm/d18m/@jayyuen66/dsh-quality-gate?style=flat-square" alt="npm downloads (total)" /></a>
|
|
12
12
|
<a href="https://github.com/JayYuen666/dsh-quality-gate/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/JayYuen666/dsh-quality-gate/ci.yml?branch=main&style=flat-square&label=ci" alt="ci" /></a>
|
|
13
13
|
<img src="https://img.shields.io/badge/license-MIT-4c1?style=flat-square" alt="MIT" />
|
|
14
14
|
<img src="https://img.shields.io/badge/node-%3E%3D22.12-4c1?style=flat-square&logo=nodedotjs&logoColor=white" alt="node" />
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
- **Turn-end, not in-flight.** The hook is the host event `agent/turn-stopping`: read this session's **successful** edits → walk each file up to the nearest manifest root → detect the gate → run it. A failure is injected back via `agent.followup`; the host still checks the inbox after turn-stopping, so a repair instruction opens a new turn.
|
|
51
51
|
- **One run has exactly three outcomes**: `pass` (it really finished with exit 0), `code-failure` (it ran and reported code errors), `not-run` (it never ran). A check that did not truly complete is never recorded as passed; and "could not run" is never conflated with "code error" — the former injects "please confirm manually", writes no memory and never reaches the lesson bus as `gate-failure`.
|
|
52
52
|
- **Only commands the project itself declares.** One of four whitelist commands per root; without a declaration the gate is simply not issued — better no gate than an invented command that fails deterministically (see "The four gate commands").
|
|
53
|
-
- **Runaway protection.** Repair injections are quota'd per session across turns; once spent, one user-visible release note is injected and auto-repair stops. The total gate time is budget-capped; a timeout
|
|
53
|
+
- **Runaway protection.** Repair injections are quota'd per session across turns; once spent, one user-visible release note is injected and auto-repair stops. The total gate time is budget-capped; a timeout is injected as "could not run" instead of passing silently.
|
|
54
54
|
- **It does not edit code, does not block dangerous commands (that is danger-guard's job) and does not write any file.**
|
|
55
55
|
|
|
56
56
|
## <img src="https://api.iconify.design/lucide/package.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Install
|
|
@@ -62,9 +62,10 @@ dsh plugin --profile web add @jayyuen66/dsh-quality-gate
|
|
|
62
62
|
Remove with `dsh plugin --profile web remove @jayyuen66/dsh-quality-gate`. The package is on the public npm registry, so installation needs no credentials.
|
|
63
63
|
|
|
64
64
|
- **Requires Node.js `>= 22.12`.** The artifacts use newer runtime faces such as `AbortSignal.any`.
|
|
65
|
-
- **Requires a host `dsh` on the `0.2.1-rc.3` line.** The requirement the host reads is `engines.dsh` (`^0.2.1-rc.3`), and it **decides alone**; pre-release versions participate in range comparison, and `0.3.0` and later need the explicit, risk-accepting grant `dsh plugin --profile <p> allow-version <pkg>@<ver> --dsh-version <exact> --accept-risk`.
|
|
65
|
+
- **Requires a host `dsh` on the `0.2.1-rc.3` / `0.2.2-alpha.2` line.** The requirement the host reads is `engines.dsh` (`^0.2.1-rc.3`), and it **decides alone**; pre-release versions participate in range comparison, and `0.3.0` and later need the explicit, risk-accepting grant `dsh plugin --profile <p> allow-version <pkg>@<ver> --dsh-version <exact> --accept-risk`. The support window rolls: this package is verified against the three most recently released host versions at publish time and re-verified once the window moves, so the version number in this bullet is not a standing promise.
|
|
66
66
|
- **The runtime dependency `@jayyuen66/dsh-plugin-shared` must already be on the same registry**, otherwise the whole set 404s.
|
|
67
67
|
- **`typescript` is a real `dependencies` entry**: the fallback entry for the `tsc` gate — the project's own install wins, then the copy shipped with this package; no network action at runtime, never an `npx` auto-download.
|
|
68
|
+
- **`zod` is also a real `dependencies` entry**: the edit-ledger projection state and the settings card's value/draft faces use it for shape validation, versioned through the umbrella catalog. The client artifact keeps `require("zod")` externalized via the host's `PLATFORM_MODULES`.
|
|
68
69
|
- **`@deepseek-ai/schemastery` is resolved from the host, not installed by this package**: only the host's fork resolves `.volatile()` fields into live references; a public schemastery turns "edit a setting" into "restart to apply". Two validator functions of `@deepseek-ai/dsh-credentials` are value imports, so that package sits in `peerDependencies` — no `@deepseek-ai/*` under `dependencies` is a publishing contract (`test/publish-manifest.test.ts` enforces it: a host package there materializes a second copy in the consumer's profile, next to the running host).
|
|
69
70
|
- Installing a local directory needs pnpm ≥ 12.3.0 (12.0.0 has a known bug in that path).
|
|
70
71
|
|
|
@@ -102,13 +103,13 @@ The whitelist is locked at type level (the `GateCommand` union in `lib/gate-dete
|
|
|
102
103
|
|
|
103
104
|
| Command | When it is issued |
|
|
104
105
|
| --- | --- |
|
|
105
|
-
| `pnpm check` | The root has both `pnpm-workspace.yaml` and `package.json`, and the root scripts declare a non-empty `check`.
|
|
106
|
+
| `pnpm check` | The root has both `pnpm-workspace.yaml` and `package.json`, and the root scripts declare a non-empty `check`. A workspace root without `check` gets no gate at all — it does not fall back to `tsc` (see below) |
|
|
106
107
|
| `cargo check` | The root has a `Cargo.toml` |
|
|
107
108
|
| `ruff check .` | The root has a `ruff.toml`, or a `pyproject.toml` with a `[tool.ruff]` section. A bare `pyproject.toml` does not count — it is Python metadata, not a ruff opt-in |
|
|
108
|
-
| `tsc --noEmit` |
|
|
109
|
+
| `tsc --noEmit` | A **non-workspace** root with both `package.json` and `tsconfig.json`; without `tsconfig.json` it is not a TS project. A workspace root does not qualify: its tsconfig is usually solution-style (`files: []` + `references`), and `tsc --noEmit` checks no subproject and exits 0 |
|
|
109
110
|
|
|
110
111
|
- `tsc` is only a logical name in the command: the entry is resolved at runtime — the project's own `typescript` first, then the copy shipped with this package, finally `tsc` on PATH (reported as not-run by the "tool unavailable" exit); never an `npx` auto-download.
|
|
111
|
-
- Root detection: from each edited file, up to `maxRootDepth` (default 10) levels to the nearest manifest root; a monorepo package is upgraded to the workspace root holding `pnpm-workspace.yaml` — but only when that root **yields a gate**; otherwise the package keeps its own root (an outer workspace without a `check` script must not swallow the package's `tsc`/`ruff` gate).
|
|
112
|
+
- Root detection: from each edited file, up to `maxRootDepth` (default 10) levels to the nearest manifest root; a package that is **itself** a workspace root yielding `pnpm check` keeps its own root (a change in one package must not fan out into the whole umbrella workspace), and otherwise a monorepo package is upgraded to the workspace root holding `pnpm-workspace.yaml` — but only when that root **yields a gate**; otherwise the package keeps its own root (an outer workspace without a `check` script must not swallow the package's `tsc`/`ruff` gate).
|
|
112
113
|
- Budget and concurrency: total budget `gateBudgetMs`; each root gets the smaller of `max(10s, budget / root count)` and the time actually left; a tail with under 5s left does not start and is injected as "did not run". Gates run sequentially and only one failure is injected at a time.
|
|
113
114
|
- Evidence caps: 64 KiB per stream stays in memory (the tail is kept; the first error is the root cause), and when truncated a bounded 4 KiB head excerpt is read back from the spill file; the per-session edit accumulator holds 500 files, evicting oldest-first with a warning.
|
|
114
115
|
|
|
@@ -120,7 +121,7 @@ The namespace `quality-gate` is the settings entry id; the fields projected into
|
|
|
120
121
|
| --- | --- | --- | --- |
|
|
121
122
|
| `enabled` | boolean | `true` | Master switch; off means no turn-end verification at all |
|
|
122
123
|
| `maxInjectsPerTurn` | natural, 1–5 | `2` | Repair-injection quota, accumulated per session across turns |
|
|
123
|
-
| `gateBudgetMs` | natural, 10000–600000 | `300000` | Total gate budget; a timeout
|
|
124
|
+
| `gateBudgetMs` | natural, 10000–600000 | `300000` | Total gate budget; a timeout is injected as "could not run" instead of passing silently |
|
|
124
125
|
| `memoryFeedback` | boolean | `false` | Additionally persist code-level failures to the memory gateway |
|
|
125
126
|
| `memoryGatewayUrl` | string | `""` (unset) | Gateway base URL; the setting wins, empty falls back to the env `TDAI_GATEWAY_URL` |
|
|
126
127
|
| `memoryGatewayKeyRef` | string (credential ref name) | `TDAI_GATEWAY_KEY` | Ref name of the gateway key; the value lives in the host credential service, never in settings |
|
|
@@ -146,9 +147,9 @@ Settings persist in the host's settings file under the dsh data directory; the c
|
|
|
146
147
|
| --- | --- |
|
|
147
148
|
| Zero outbound by default | `memoryFeedback` off plus an empty URL are a double lock — with either empty nothing is pushed and the credential store is not even queried |
|
|
148
149
|
| What leaves the machine | When enabled, only the command, the project root and the full error text (single-lined, untruncated) — sent to **your own** gateway |
|
|
149
|
-
| URL and credential safety | Credentials go to `https` or loopback addresses only (`
|
|
150
|
+
| URL and credential safety | Credentials go to `https` or loopback addresses only (`127.0.0.0/8`, IPv6 loopback, `localhost`); `localhost` depends on the local hosts/DNS and is a deliberate fail-open — if hosts points elsewhere, credentials go there. Plain `http` to a non-loopback host is refused as unconfigured, with the reason stated. A base URL that does not parse as a legal `http(s)` URL counts as unconfigured too; URLs with userinfo are rejected |
|
|
150
151
|
| The key | The value lives in the host credential service (a credentials file under `$DSH_HOME`); settings store only the ref name, and a ref name that is not a valid POSIX variable name counts as unconfigured |
|
|
151
|
-
| Failure handling | Push failures only warn
|
|
152
|
+
| Failure handling | Push failures only warn and never block the turn: configuration/unsafe-URL downgrades are reported once per load; HTTP non-2xx, timeouts and delivery errors are logged on every failure (deduplicating them would hide a persistent fault) |
|
|
152
153
|
| Disk reads | Manifest presence, the root `package.json` scripts, `pyproject.toml` section headers, the head of a spilled truncated file; no file writes, no telemetry |
|
|
153
154
|
| Memory | Session ledgers (edit accounting / injection quota / event cursors) live in process memory and are cleared when the plugin unloads |
|
|
154
155
|
|
|
@@ -156,7 +157,7 @@ Settings persist in the host's settings file under the dsh data directory; the c
|
|
|
156
157
|
|
|
157
158
|
- **The whitelist has exactly four commands and cannot be extended via settings.** Roots checked by other tools (`go test`, `mypy`, `eslint`, …) get no gate.
|
|
158
159
|
- **Only successful edits count**, and only writes through `edit` / `write` / `str_replace_editor` (`view` is read-only). Files outside the extension whitelist (md/txt/images/`.sh`) are not counted — none of the gate commands can validate them, and a `.sh` would only drag the root resolution somewhere else and produce a false failure.
|
|
159
|
-
- **Out-of-process subagents (ACP / claude-code / codex) emit no dsh events**, so they are outside the gate by construction; in-process subagents are skipped and their edits
|
|
160
|
+
- **Out-of-process subagents (ACP / claude-code / codex) emit no dsh events**, so they are outside the gate by construction; in-process subagents are skipped too, and their edits land in their own session ledger rather than the parent's — there is currently no guarantee that the parent turn verifies them (they can be missed).
|
|
160
161
|
- **Never talks over the user**: no injection when the inbox has pending items, the turn signal is aborted, or the plugin is unloading. Plan mode is not exempt — a planning turn that touched code is gated the same way.
|
|
161
162
|
- **When evidence is truncated with no spill file, the first error may already be gone**; the text says the evidence is incomplete instead of pretending otherwise.
|
|
162
163
|
- **When the sandbox policy cannot be resolved, no gate runs at all** (injected as not-run); it never falls back to the deployment-level policy — that would run the gate outside the sandbox, get it denied, and produce a false repair instruction.
|
|
@@ -165,12 +166,12 @@ Settings persist in the host's settings file under the dsh data directory; the c
|
|
|
165
166
|
|
|
166
167
|
| Question | Answer |
|
|
167
168
|
| --- | --- |
|
|
168
|
-
| Why did my pnpm project not run `pnpm check`? | Its root `package.json
|
|
169
|
-
| The gate said "could not run" — is my code broken? | No. Sandbox policy denial, a runner that will not start, a timeout, a cancellation, exit 126/127
|
|
169
|
+
| Why did my pnpm project not run `pnpm check`? | Its root must have both `pnpm-workspace.yaml` and `package.json`, with a non-empty `check` script in root scripts. Without `pnpm-workspace.yaml` the next candidate applies (a non-workspace root may reach `tsc`/`ruff`/`cargo`); a workspace root without `check` gets no gate at all (a solution-style tsconfig would make `tsc` permanently green). Deliberate trade: a false failure costs more than a missed check |
|
|
170
|
+
| The gate said "could not run" — is my code broken? | No. Sandbox policy denial, a runner that will not start, a timeout, a cancellation, an exhausted budget, and exit 126/127 **whose stderr names the gate command itself** (tool not installed) all land here. The injected text explicitly says **do not change code because of it**, and it is reported separately as `gate-not-run`. A 126/127 raised *inside* your own `check` script is a code failure and is reported as such |
|
|
170
171
|
| Can it loop forever? | Once the quota is spent it injects one user-visible release note, stops auto-repair, and only counts again after a fully clean pass |
|
|
171
172
|
| Stop it temporarily? | Turn off `enabled` on the card, or run `dsh plugin --profile web remove @jayyuen66/dsh-quality-gate` |
|
|
172
173
|
| Which `tsc` is used? | The project's own install first, then the `typescript` shipped with this package, and PATH only as the last resort — never an `npx` download |
|
|
173
|
-
| What about code edited by subagents? | In-process subagents are
|
|
174
|
+
| What about code edited by subagents? | In-process subagents are skipped as well, and their edits land in their own session ledger rather than the parent's — there is currently no guarantee that the parent turn verifies them (they can be missed); out-of-process subagents emit no dsh events, so they are outside the gate by construction |
|
|
174
175
|
|
|
175
176
|
## <img src="https://api.iconify.design/lucide/wrench.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Development
|
|
176
177
|
|
|
@@ -179,7 +180,7 @@ pnpm install
|
|
|
179
180
|
pnpm run check # typecheck (three tsconfigs) → lint → client build → host build → tests (with coverage) → tarball inspection → format
|
|
180
181
|
```
|
|
181
182
|
|
|
182
|
-
- Coverage is measured over `host.ts` and `
|
|
183
|
+
- Coverage is measured over `host.ts`, `lib/**` and `src/ui-messages.ts`, with all four thresholds at 100%. The dictionary is plain data driven headlessly by `test/client-card.test.ts`, so counting it keeps the threshold on the part that is fully verified; `client-entry.ts` is excluded because most of it is the render surface, which needs a DOM harness this package deliberately omits (`react-dom`/`jsdom` are not pulled in). Counting it would turn the thresholds into a permanent red.
|
|
183
184
|
- `test/publish-manifest.test.ts` enforces the publishing shape: entry points resolve, no `@deepseek-ai/*` under `dependencies`, no dates or local paths in the artifacts, with comments and documents scanned the same way. `test/dist-size.test.ts` guards the byte budget of both artifacts; `check:pack` inspects a real `pnpm pack` tarball.
|
|
184
185
|
|
|
185
186
|
## <img src="https://api.iconify.design/lucide/scale.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> License
|
package/README.zh-CN.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
10
|
<a href="https://www.npmjs.com/package/@jayyuen66/dsh-quality-gate"><img src="https://img.shields.io/npm/v/@jayyuen66/dsh-quality-gate?style=flat-square&logo=npm&logoColor=white" alt="npm version" /></a>
|
|
11
|
-
<a href="https://www.npmjs.com/package/@jayyuen66/dsh-quality-gate"><img src="https://img.shields.io/npm/
|
|
11
|
+
<a href="https://www.npmjs.com/package/@jayyuen66/dsh-quality-gate"><img src="https://img.shields.io/npm/d18m/@jayyuen66/dsh-quality-gate?style=flat-square" alt="npm downloads (total)" /></a>
|
|
12
12
|
<a href="https://github.com/JayYuen666/dsh-quality-gate/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/JayYuen666/dsh-quality-gate/ci.yml?branch=main&style=flat-square&label=ci" alt="ci" /></a>
|
|
13
13
|
<img src="https://img.shields.io/badge/license-MIT-4c1?style=flat-square" alt="MIT" />
|
|
14
14
|
<img src="https://img.shields.io/badge/node-%3E%3D22.12-4c1?style=flat-square&logo=nodedotjs&logoColor=white" alt="node" />
|
|
@@ -62,9 +62,10 @@ dsh plugin --profile web add @jayyuen66/dsh-quality-gate
|
|
|
62
62
|
卸载:`dsh plugin --profile web remove @jayyuen66/dsh-quality-gate`。包在公共 npm 上,安装不需要凭据,也不需要配作用域源。
|
|
63
63
|
|
|
64
64
|
- **要求 Node.js `>= 22.12`。** 产物用到 `AbortSignal.any` 这类较新的运行面。
|
|
65
|
-
- **要求宿主 `dsh` 在 `0.2.1-rc.3` 这条线上。** 宿主读取的版本要求是 `engines.dsh`(`^0.2.1-rc.3`),它**单独决定**兼容性;预发布版本参与范围比较,`0.3.0` 及以后要走显式担责的授权 `dsh plugin --profile <p> allow-version <pkg>@<ver> --dsh-version <exact> --accept-risk
|
|
65
|
+
- **要求宿主 `dsh` 在 `0.2.1-rc.3` / `0.2.2-alpha.2` 这条线上。** 宿主读取的版本要求是 `engines.dsh`(`^0.2.1-rc.3`),它**单独决定**兼容性;预发布版本参与范围比较,`0.3.0` 及以后要走显式担责的授权 `dsh plugin --profile <p> allow-version <pkg>@<ver> --dsh-version <exact> --accept-risk`。支持窗口是滚动的:本包在发布时最近的三个已发布宿主版本上逐版取证,窗口滚动后重新取证,不把这一句里的版本号当长期承诺。
|
|
66
66
|
- **运行时依赖 `@jayyuen66/dsh-plugin-shared` 必须已在同一 registry 上**,否则整组 404。
|
|
67
67
|
- **`typescript` 是真实的 `dependencies`**:`tsc` 门禁的兜底入口——工程自己装的那份优先,其次随包安装的这份,运行期无网络动作,从不走 `npx` 的自动下载。
|
|
68
|
+
- **`zod` 也是真实的 `dependencies`**:编辑台账投影状态与设置卡的值面/草稿面用它校验形状;版本走伞根 catalog。client 产物按宿主 `PLATFORM_MODULES` 保持 `require("zod")` 外部化。
|
|
68
69
|
- **`@deepseek-ai/schemastery` 由宿主解析、不由本包安装**:只有宿主 fork 的那一枚能把 `.volatile()` 字段解析成活引用,换成公共 schemastery 就变成改设置要重启才生效。`@deepseek-ai/dsh-credentials` 的两枚校验函数是值导入,因此落在 `peerDependencies`——`dependencies` 里不放任何 `@deepseek-ai/*` 是发布契约(`test/publish-manifest.test.ts` 钉着:写在 `dependencies` 里的宿主包会在消费方 profile 里物化出第二份,与运行中的宿主并存)。
|
|
69
70
|
- 本地目录形态安装需 pnpm ≥ 12.3.0(12.0.0 的本地目录 `add` 有已知缺陷)。
|
|
70
71
|
|
|
@@ -104,13 +105,13 @@ dsh plugin --profile web add @jayyuen66/dsh-quality-gate
|
|
|
104
105
|
|
|
105
106
|
| 命令 | 什么时候发 |
|
|
106
107
|
| --- | --- |
|
|
107
|
-
| `pnpm check` | 根同时有 `pnpm-workspace.yaml` 与 `package.json`,且根 scripts 声明了非空 `check
|
|
108
|
+
| `pnpm check` | 根同时有 `pnpm-workspace.yaml` 与 `package.json`,且根 scripts 声明了非空 `check`。workspace 根没声明 `check` 时一条门禁都不发——它不会降级到 `tsc`(见下) |
|
|
108
109
|
| `cargo check` | 根有 `Cargo.toml` |
|
|
109
110
|
| `ruff check .` | 根有 `ruff.toml`,或 `pyproject.toml` 里有 `[tool.ruff]` 一族段。只有 `pyproject.toml` 不算——那是 Python 元数据文件,不是「用 ruff」的声明 |
|
|
110
|
-
| `tsc --noEmit` | 根同时有 `package.json` 与 `tsconfig.json`;缺 `tsconfig.json` 不算 TS
|
|
111
|
+
| `tsc --noEmit` | **非 workspace** 根同时有 `package.json` 与 `tsconfig.json`;缺 `tsconfig.json` 不算 TS 项目。workspace 根的 tsconfig 多为 solution-style(`files: []` + `references`),`tsc --noEmit` 一个子工程都不检查就退 0,因此不在此列 |
|
|
111
112
|
|
|
112
113
|
- `tsc` 在命令里只是逻辑名:入口在运行期解析——工程自己装的 `typescript` 优先,其次随包那份,最后 PATH 上的 `tsc`(由「工具不可用」出口报成未执行);绝不走 `npx` 的自动下载。
|
|
113
|
-
- 根探测:从编辑文件向上最多 `maxRootDepth`(默认 10
|
|
114
|
+
- 根探测:从编辑文件向上最多 `maxRootDepth`(默认 10)层找最近清单根;子包**自己就是** workspace 根且能产出 `pnpm check` 时就地执行(改一个包不得把整棵伞工作区拖进来);否则 monorepo 子包升到 `pnpm-workspace.yaml` 所在的工作区根——但只在该根**产得出门禁**时升级,再否则保留子包自己的根(外层没有 `check` 脚本的 workspace 不得吃掉子包的 `tsc` / `ruff` 门禁)。
|
|
114
115
|
- 预算与并发:总预算 `gateBudgetMs`;每根取 `max(10s, 预算/根数)` 与实际剩余的较小值;剩余不足 5s 的尾段不再起跑,按「未执行」注入清单。门禁顺序执行,一次只注入一个失败。
|
|
115
116
|
- 证据上限:每条流内存留存 64 KiB(截断保尾部,首条错误才是根因),被截断时从落盘文件有界读回 4 KiB 头部;单会话编辑记账 500 个文件,超出按插入序淘汰并 warn。
|
|
116
117
|
|
|
@@ -122,7 +123,7 @@ dsh plugin --profile web add @jayyuen66/dsh-quality-gate
|
|
|
122
123
|
| --- | --- | --- | --- |
|
|
123
124
|
| `enabled` | boolean | `true` | 总开关;关掉即收口不再核验 |
|
|
124
125
|
| `maxInjectsPerTurn` | natural,1–5 | `2` | 修复指令注入配额,按会话跨回合累计 |
|
|
125
|
-
| `gateBudgetMs` | natural,10000–600000 | `300000` |
|
|
126
|
+
| `gateBudgetMs` | natural,10000–600000 | `300000` | 门禁总预算;超时按「未执行」注入,不静默放行 |
|
|
126
127
|
| `memoryFeedback` | boolean | `false` | 是否把代码型失败额外沉淀到记忆网关 |
|
|
127
128
|
| `memoryGatewayUrl` | string | `""`(未配置) | 网关基址;设置值优先,留空回落 env `TDAI_GATEWAY_URL` |
|
|
128
129
|
| `memoryGatewayKeyRef` | string(凭据引用名) | `TDAI_GATEWAY_KEY` | 网关密钥的引用名;值走宿主凭据服务,不落设置 |
|
|
@@ -148,9 +149,9 @@ dsh plugin --profile web add @jayyuen66/dsh-quality-gate
|
|
|
148
149
|
| --- | --- |
|
|
149
150
|
| 默认零外发 | `memoryFeedback` 关 + 地址空是双保险,任一为空都不推送、也不去查凭据 |
|
|
150
151
|
| 出域内容 | 开启后只把命令、项目根与全文报错(单行化、不截断)发往**你自己配的**网关 |
|
|
151
|
-
| 地址与凭据安全 | 凭据只发 `https` 或回环地址(`
|
|
152
|
+
| 地址与凭据安全 | 凭据只发 `https` 或回环地址(`127.0.0.0/8`、IPv6 回环、`localhost`);其中 `localhost` 依赖本机 hosts/DNS,属有意的 fail-open——hosts 被指向远端时凭据会发往远端。明文 `http` 非回环按未配置拒绝并说明理由。地址解析不出合法 `http(s)` URL 同样按未配置;带 userinfo 的地址一律拒绝 |
|
|
152
153
|
| 密钥 | 值由宿主凭据服务持有(`$DSH_HOME` 下的凭据文件),设置里只存引用名;引用名不合 POSIX 变量名语法即视为未配置 |
|
|
153
|
-
| 失败处理 | 推送失败只 warn
|
|
154
|
+
| 失败处理 | 推送失败只 warn、绝不阻断收口:配置缺失/地址不安全的降级每次装载只说一次;HTTP 非 2xx、超时与投递异常则每次失败各记一条(它们不该被去重掩盖) |
|
|
154
155
|
| 读盘 | 清单存在性、根 `package.json` 的 scripts、`pyproject.toml` 的段头、截断落盘文件的头部;不写任何文件、不遥测 |
|
|
155
156
|
| 内存 | 会话级账本(编辑记账/注入配额/事件游标)都在进程内存,插件卸载即清 |
|
|
156
157
|
|
|
@@ -158,7 +159,7 @@ dsh plugin --profile web add @jayyuen66/dsh-quality-gate
|
|
|
158
159
|
|
|
159
160
|
- **白名单只有四条命令,不能经设置扩展。** 用别的检查器(`go test`、`mypy`、`eslint`…)的根得不到门禁。
|
|
160
161
|
- **只记成功编辑**,且只认 `edit` / `write` / `str_replace_editor` 的写入(`view` 只读不算)。扩展名白名单之外的文件(md/txt/图片/`.sh`)不计——没有一条门禁命令能校验它们,`.sh` 只会把根解析到别处跑出假失败。
|
|
161
|
-
- **out-of-process 子代理(ACP / claude-code / codex)不发 dsh 事件**,天然不在门禁面内;in-process
|
|
162
|
+
- **out-of-process 子代理(ACP / claude-code / codex)不发 dsh 事件**,天然不在门禁面内;in-process 子代理的回合也跳过门禁,且其编辑落在各自会话的台账,不进入父会话的编辑台账——当前不保证被父会话核验(可能漏检)。
|
|
162
163
|
- **不抢话**:inbox 已有待处理消息、回合信号已取消或插件已卸载时不注入。plan mode 不豁免——规划回合改了代码同样收口。
|
|
163
164
|
- **证据被截断且没有落盘文件时,首个错误可能已随截断丢弃**;文案如实说明证据不完整,不假装完整。
|
|
164
165
|
- **沙箱策略解析失败时一门都不执行**(按未执行注入),绝不回落部署级策略——那会让门禁写在沙箱外被拒、产出假的失败指令。
|
|
@@ -167,12 +168,12 @@ dsh plugin --profile web add @jayyuen66/dsh-quality-gate
|
|
|
167
168
|
|
|
168
169
|
| 问题 | 回答 |
|
|
169
170
|
| --- | --- |
|
|
170
|
-
| pnpm 工程为什么没跑 `pnpm check`? |
|
|
171
|
-
| 门禁说「未能执行」,是代码错了吗? | 不是。沙箱策略拒绝、runner
|
|
171
|
+
| pnpm 工程为什么没跑 `pnpm check`? | 根必须同时有 `pnpm-workspace.yaml` 与 `package.json`,且根 scripts 声明了非空 `check` 脚本;缺 `pnpm-workspace.yaml` 时按下一候选判(非 workspace 根可能落到 `tsc` / `ruff` / `cargo`),而 workspace 根没声明 `check` 时一条门禁都不发(solution-style tsconfig 会让 `tsc` 恒绿)。刻意取舍:假失败比漏检查更贵 |
|
|
172
|
+
| 门禁说「未能执行」,是代码错了吗? | 不是。沙箱策略拒绝、runner 起不来、超时、取消、预算不足,以及 stderr **点名门禁命令自身**(工具没装)的 exit 126/127 都属此类。注入文案明确要求**不要为此修改代码**,并以 `gate-not-run` 单独上报总线。若 126/127 来自你自己的 `check` 脚本内部,那是代码失败,按代码失败上报 |
|
|
172
173
|
| 会不会无限修下去? | 配额耗尽后注入一次用户可见的放行说明并停止自动修复,直到某次全部干净通过才重新计数 |
|
|
173
174
|
| 临时停掉? | 设置卡关 `enabled`,或 `dsh plugin --profile web remove @jayyuen66/dsh-quality-gate` |
|
|
174
175
|
| `tsc` 用哪个版本? | 工程自己装的那份优先,其次随包安装的 `typescript`,都不在才回落 PATH——从不经 `npx` 下载 |
|
|
175
|
-
| 子代理改的代码呢? | in-process
|
|
176
|
+
| 子代理改的代码呢? | in-process 子代理的回合同样跳过门禁,其编辑落在各自会话的台账、不进入父会话台账——当前不保证被父会话核验(可能漏检);out-of-process 子代理不发 dsh 事件,天然不在门禁面内 |
|
|
176
177
|
|
|
177
178
|
## <img src="https://api.iconify.design/lucide/wrench.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 开发
|
|
178
179
|
|
|
@@ -181,7 +182,7 @@ pnpm install
|
|
|
181
182
|
pnpm run check # typecheck(三个 tsconfig)→ lint → client 构建 → host 构建 → 测试(含覆盖率)→ tarball 体检 → 格式
|
|
182
183
|
```
|
|
183
184
|
|
|
184
|
-
-
|
|
185
|
+
- 覆盖率统计 `host.ts`、`lib/**` 与 `src/ui-messages.ts`,四项阈值 100%。字典是纯数据、`test/client-card.test.ts` 无头就能跑满,计进分母才能让阈值守住它;`client-entry.ts` 不计——它的大头是渲染面,要 DOM 基座才能测,本包刻意不引 `react-dom` 与 `jsdom`,整体计进来会让阈值变成常红。
|
|
185
186
|
- `test/publish-manifest.test.ts` 钉发布形态:入口可解析、`@deepseek-ai/*` 不进 `dependencies`、产物内容无日期与本机路径,注释与文档面同受其扫描。`test/dist-size.test.ts` 守两个产物的体积预算;`check:pack` 对 `pnpm pack` 的真 tarball 做体检。
|
|
186
187
|
|
|
187
188
|
## <img src="https://api.iconify.design/lucide/scale.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 许可
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -45,8 +45,11 @@
|
|
|
45
45
|
profile 里那一份实例,因此不存在"把第三方源码打进发布物"的情形,本文件也就不需要附各包的
|
|
46
46
|
许可证全文。
|
|
47
47
|
|
|
48
|
-
- `@jayyuen66/dsh-plugin-shared`(MIT
|
|
48
|
+
- `@jayyuen66/dsh-plugin-shared`(MIT)——普通依赖。`lib/locale`、`lib/tool-events`、
|
|
49
49
|
`lib/lesson-bus`、`lib/record`、`lib/project-key` 五个子路径来自它。
|
|
50
|
+
- `zod`(MIT)——普通依赖。`lib/edit-ledger.ts` 的投影状态与 `src/client-entry.ts` 的设置卡
|
|
51
|
+
值面/草稿面用它做形状校验;client 产物按宿主 `PLATFORM_MODULES` 保持 `require("zod")` 外部化。
|
|
52
|
+
- `typescript`(Apache-2.0)——普通依赖,`tsc` 门禁的兜底入口。
|
|
50
53
|
- `@deepseek-ai/*`(一律 MIT,由宿主提供,声明为 peer / devDependency,**不进** `dependencies`)
|
|
51
54
|
——`dsh`、`cordis`、`schemastery`、`dsh-settings`、`dsh-shell`、`dsh-sandbox`、
|
|
52
55
|
`dsh-sandbox-policy`、`dsh-session`、`dsh-session-query`、`dsh-session-projection`、
|