@jayyuen66/dsh-plugin-shared 0.2.0 → 0.2.2

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 CHANGED
@@ -79,19 +79,21 @@ End users do not install or enable it separately: any consumer package pulls it
79
79
  | Requirement | Value |
80
80
  | --- | --- |
81
81
  | Node.js | `>=22.12.0` — the publish-side floor the plugin family shares, so a package declaring the same `engines.node` never rejects this library |
82
- | dsh | `^0.2.1-rc.2`, declared as an **optional** peer (importing this package does not need a host) |
82
+ | dsh | `^0.2.1-rc.3`, declared as an **optional** peer (importing this package does not need a host) |
83
83
  | Module format | ESM only. `require()` works only where Node supports `require(esm)` |
84
84
  | Tree shaking | `"sideEffects": false` |
85
85
 
86
+ - **The host window rolls.** This package is verified against the three most recently released host versions at publish time and re-verified once the window moves; the ranges in `engines.dsh` and in the peers are an install gate and a compile-time discipline, not the verification scope.
87
+
86
88
  ### Dependency requirements
87
89
 
88
90
  `dependencies` is empty; every `@deepseek-ai/*` entry lives only in `peerDependencies` and `devDependencies`. `test/publish-manifest.test.ts` enforces this, and the reason is a boot-time failure rather than style:
89
91
 
90
92
  - **A host package under `dependencies` disables the consumer's host.** Profiles install with `nodeLinker: hoisted` and `autoInstallPeers: false`, so a listed host package is materialized as a real directory next to the copy the host process already loaded. `@deepseek-ai/dsh-tools` keys its tool-runtime scheduler with a plain `Symbol` (not `Symbol.for`), so symbol identity is per module instance: the host reads the scheduler from the duplicate and gets `undefined`, and every native tool call throws. Build, typecheck and tests pass up to that point; the invariant has to be stated in the manifest.
91
93
  - **`@deepseek-ai/dsh-output-retention` is the one required peer.** It is a value import (`truncateEnd`), provided by the host at runtime, and marked `"optional": false` in `peerDependenciesMeta` rather than left implicit — a peer left out of that table is a hard requirement for npm 7+.
92
- - **The remaining peers are optional.** Except for the bare `@deepseek-ai/dsh` peer, which only declares the host line, they carry type declarations only. Keeping them in `peerDependencies` keeps the published `.d.ts` consistent with the real dependencies; `devDependencies` let this repository compile and test. Profiles never install peers, so no duplicate appears.
93
- - **Version requirements follow the host.** `dsh-*` and `cordis` are locked exactly to what the host bundle declares, so the same copy is reused. `test/publish-manifest.test.ts` also requires every `dsh-*` locked version to match the `@deepseek-ai/dsh` peer version.
94
- - Known upstream limitation: `@deepseek-ai/dsh-llm`'s declarations import `@deepseek-ai/dsh-attachment`, which that package does not declare. It is reached through `dsh-session`, so a consumer running with `skipLibCheck: false` sees `TS2307` from that package, not from this one.
94
+ - **The remaining peers are optional.** Except for the bare `@deepseek-ai/dsh` peer, which only declares the host line, they carry type declarations only. `devDependencies` holds the exact development line, including `@deepseek-ai/dsh-llm` and `@deepseek-ai/dsh-util-values`, whose types the local PTC declarations name, and `@deepseek-ai/dsh-tool-executor-ptc`, whose types only the parity check reads. Profiles never install peers, so no duplicate appears.
95
+ - **Version requirements separate the install gate from the build baseline.** Every `@deepseek-ai/dsh-*` peer uses the same `^0.2.1-rc.3` range as `engines.dsh`, and that range deliberately names one host line. npm resolves each peer on its own, so a peer listing both lines (`^0.2.1-rc.3||^0.2.2-alpha.2`) lets the required `@deepseek-ai/dsh-output-retention` come from the alpha line while cordis comes from the rc line, and a clean install stops at `ERESOLVE`. The supported window is not narrowed by this: the host compares `engines.dsh` with `includePrerelease: true`, under which `^0.2.1-rc.3` is true for `0.2.1-rc.3`, `0.2.1-rc.4`, `0.2.1-rc.5` and `0.2.2-alpha.2`. `devDependencies` pin the exact line this repository compiles and tests against (`0.2.2-alpha.2`) and must stay uniform: one `dsh-session` version per tree keeps the nominal types recognizable. `@deepseek-ai/cordis` stays exact in `devDependencies` (`4.0.6-alpha.1`) and its peer range `^4.0.5||^4.0.6-alpha.1` covers both host lines, because one package cannot mix lines the way the host family does; union ranges carry no spaces around `||`, so one spec stays one argument wherever a spec list is split on blanks. `test/publish-manifest.test.ts` checks each of these separately.
96
+ - Known upstream limitation: `@deepseek-ai/dsh-llm`'s declarations import `@deepseek-ai/dsh-attachment`, which that package does not declare. It is reached through `dsh-session`, so a consumer running with `skipLibCheck: false` sees `TS2307` from that package, not from this one. The published `./lib/tool-events` declaration also names `@deepseek-ai/dsh-util-values`, which is reached through `dsh-session` / `dsh-tools`; a consumer running with `skipLibCheck: false` must have that package resolvable as well.
95
97
 
96
98
  ### Shared version floor
97
99
 
@@ -128,7 +130,7 @@ The subpath column is the in-package specifier; value imports prefix it with the
128
130
  | Subpath | Provides |
129
131
  | --- | --- |
130
132
  | `.` | Barrel over the runtime modules below. `canonicalize-region-paths` deliberately stays out, so a runtime consumer does not add an import for it; the client-only `card-ui` stays out for the same reason plus one more: the root barrel must not bring `react` into every consumer. |
131
- | `./lib/tool-events` | `scanToolEvents` returns the `calls` / `results` tables, PTC sub-calls included; `toolEventRowsOf` handles a single event and already carries normalized `arguments` and `badArguments`. `editPathOf` returns a `kind`-discriminated `EditTarget` with only `write` / `read-view`; the path is `undefined` when it cannot be read. Record fields are `readonly`. Re-exports the official `SessionEvent` and the two PTC event types. |
133
+ | `./lib/tool-events` | `scanToolEvents` returns the `calls` / `results` tables, PTC sub-calls included; `toolEventRowsOf` handles a single event and already carries normalized `arguments` and `badArguments`. `editPathOf` returns a `kind`-discriminated `EditTarget` with only `write` / `read-view`; the path is `undefined` when it cannot be read. Record fields are `readonly`. Re-exports the official `SessionEvent`; `PtcDispatchStartEventData` / `PtcDispatchEventData` are declared in this package and augment `SessionEventMap` locally, because the host moved that augmentation between packages inside the supported window. |
132
134
  | `./lib/card-apply` | `claimApply` + `CardApplyCtx`: an apply idempotency guard built on an **own** flag on `globalThis` (a symbol, or `true` written by an outside holder) with `ctx.effect` cleanup. The flag is process-wide, so a second context on the same page is rejected by design. |
133
135
  | `./lib/card-diff` | `diffTouched`: which touched fields really differ from the snapshot, compared by form value. `undefined`, `null` and an absent key are one value; arrays and objects compare element by element; cycles terminate. |
134
136
  | `./lib/card-ui` | Client-only settings-card kit: `CardForm`, `Section`, `FieldGrid`, `StatusBar`, `Field`, `FieldLabel`, `InfoTip`, `ToggleField`, `NumberField`, `TextField`, `ListField`, `SelectField`, `SaveBar`, `splitHint` and the `CARD_UI_CSS` string. Built with `createElement` only; `react` is an optional peer and the subpath never enters the root barrel. React 18 runtime APIs only (`useId`, `useState`, `useEffect`, refs). |
@@ -150,13 +152,14 @@ These are lint/test baselines for plugin authors, not runtime code. They pull in
150
152
 
151
153
  | Subpath | Provides | You must install |
152
154
  | --- | --- | --- |
153
- | `./config/oxlint` | `definePluginConfig`, `makeTestFileOverrides`, `OFF_JUSTIFICATIONS` and the shared rule baseline | `oxlint`; `eslint-plugin-sonarjs` if you want the sonarjs rules enabled |
155
+ | `./config/oxlint` | `definePluginConfig`, `makeTestFileOverrides`, `OFF_JUSTIFICATIONS` and the shared rule baseline | `oxlint`, `eslint-plugin-sonarjs`, `eslint-plugin-regexp`, `eslint-plugin-zod` (requires zod 4), `@e18e/eslint-plugin` |
154
156
  | `./config/vitest.base` | `definePackageConfig`: four 100% coverage thresholds, a 20-second test timeout, the shared coverage exclusions and the test include globs | `vitest` and `@vitest/coverage-v8` (the baseline sets `coverage.provider: "v8"`) |
155
157
  | `./config/tsconfig.base.json`, `./config/tsconfig.client.base.json` | Plain JSON for `extends` | Nothing |
156
158
 
157
159
  Those toolchain packages are **not** declared as `peerDependencies`: npm 7+ tries to satisfy optional peers too, and `oxlint`'s own `peerOptional vite-plus` locks `vitest` to a version disjoint from `>=5.0.2`, which made a plain `npm install` of this package fail with `ERESOLVE`. The requirement is stated here instead of in the manifest.
158
160
 
159
- - The sonarjs entry point is resolved lazily inside `definePluginConfig()`. Importing `./config/oxlint` without sonarjs succeeds; calling `definePluginConfig()` without sonarjs and without a sonarjs entry in `jsPlugins` throws a message naming both specifiers it tried and how to provide your own entry.
161
+ - All four JS plugin entry points (sonarjs / regexp / zod / e18e) are resolved lazily inside `definePluginConfig()`. Importing `./config/oxlint` without any of them succeeds; calling `definePluginConfig()` throws only when that plugin is neither installed nor supplied through `jsPlugins`, and the message names the specifiers it tried plus how to provide your own entry.
162
+ - `e18e/prefer-static-regex` is enabled only on production paths (`lib/**`, `src/**`, `host.ts`, `cli-main.ts`); regex literals in tests and build scripts are not treated as defects. The measured counts and rationale live in `OFF_JUSTIFICATIONS`.
160
163
  - Every rule turned `off` in the baseline needs a `kind` + `measured` + `note` entry in `OFF_JUSTIFICATIONS`, or configuration loading throws. A package-level exception goes in that package's own `offReasons`.
161
164
  - `definePackageConfig` refuses an empty `coverageInclude` and refuses a `test.coverage` block: either one would silently drop the four thresholds or the coverage statistics. An exception belongs in the package that needs it, next to its own `coverage` override.
162
165
 
@@ -169,6 +172,7 @@ One entry per class; each is fixed by a test at its current behavior.
169
172
  - **`shrinkJsonlTail` does not normalize the budget.** `NaN` and `+Infinity` disable shrinking entirely; `0`, negative values and `-Infinity` still shrink to at most two lines. The caller owns the trigger policy (including "0 means never trim") and the error handling.
170
173
  - **`jobOutcomeOf` copies the host bash tool's wording.** The tests check the literal `exit code: N` / `signal: NAME` strings, not equality with the upstream function, which is not importable from the published package. If upstream changes its wording, this package keeps passing and its display text stays its own.
171
174
  - **`editPathOf` knows one read-only spelling.** Only `str_replace_editor` with `command: "view"` is `read-view`; every other tool that carries `file_path` is `write` without further inspection.
175
+ - **The PTC payload interfaces are a local copy.** The host moved that `SessionEventMap` augmentation between host packages inside the supported window, so `lib/tool-events.ts` declares the two interfaces and augments the map itself. `test/tool-events-host-parity.ts` turns drift against the development baseline into a `pnpm typecheck` failure; a runtime assertion cannot, because the types are erased.
172
176
 
173
177
  ## <img src="https://api.iconify.design/lucide/lock.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Data and privacy
174
178
 
@@ -193,7 +197,7 @@ The package is imported into a host process; the table describes run time.
193
197
  - **`errorText` returns display text, not a parseable format.** It follows the official `errorChain` semantics: a cause chain renders as `outer: inner`, an `AggregateError` appends `[e1; e2]`, an empty message falls back to `Error.name`, and a cross-realm `Error` yields its own `message` instead of `String()`'s `"Error: ..."`. Callers that regex it are outside the supported usage.
194
198
  - **`editPathOf` returns a `kind`-discriminated `EditTarget` with only `write` / `read-view`.** `"skip"` was never produced; keeping it in the type only pushed consumers into unreachable branches. When no path can be read it still returns the matching arm with `path: undefined`.
195
199
  - **`./lib/http` and `./lib/trust` are gone.** The plugin family's settings-card data plane runs on the official Typert Remote, whose `/api` gateway owns authentication, the Host / DNS-rebinding / Origin checks and the body cap. A plugin that still wants a raw `webServer` route has to write these checks itself — the published package no longer offers a second, divergent answer to "is this request allowed".
196
- - **The declarations are coupled to the exact host versions.** Upgrading the host means updating the exact `@deepseek-ai/*` versions in this package and republishing; widening a range would let a consumer load type declarations this package was not built against.
200
+ - **The declarations are compiled against one host line and installed across a peer window.** `devDependencies` lock the exact `@deepseek-ai/*` line used for build and test; the dsh peer family and `engines.dsh` share the supported range, so a host inside that window is accepted without a second build. Moving the window means updating the development pins, re-running the parity check and republishing. Widening the peer range past the verified window is not supported.
197
201
 
198
202
  ## <img src="https://api.iconify.design/lucide/circle-help.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> FAQ
199
203
 
@@ -217,11 +221,11 @@ pnpm run build # runtime artifacts, config artifacts, declarations
217
221
 
218
222
  - The suite splits by the class of defect a stub cannot see:
219
223
  - **Module behavior**: one `test/<module>.test.ts` per runtime module.
220
- - **Surface parity**: `test/index.test.ts` (barrel ↔ `lib/` value exports), `test/publish-manifest.test.ts` (`exports` ↔ `lib/` in both directions) and `test/build-host.test.ts` (`build-host.mjs` `facets` ↔ `lib/`). Each catches an omission the other two cannot see.
224
+ - **Surface parity**: `test/index.test.ts` (barrel ↔ `lib/` value exports), `test/publish-manifest.test.ts` (`exports` ↔ `lib/` in both directions) and `test/build-host.test.ts` (`build-host.mjs` `facets` ↔ `lib/`). Each catches an omission the other two cannot see. `test/tool-events-host-parity.ts` is the declaration-side counterpart: it makes the locally declared PTC payloads and the development baseline mutually assignable, and it has no runtime case because a type assertion is erased before vitest can judge it; `pnpm typecheck` runs it.
221
225
  - **Artifact checks**: `test/build-host.test.ts` compares the runtime artifacts on disk with an in-memory build byte for byte; `test/dist-size.test.ts` holds the runtime and config artifacts to their byte budgets; the same test requires the official value import to stay a bare specifier and rejects runtime specifiers for `dsh-jobs` / `dsh-shell`.
222
226
  - **Config checks**: `test/config-oxlint.test.ts` compares the vitest rule snapshot with `oxlint --rules` and requires every `off` to be justified; `test/vitest-base.test.ts` requires the merge behavior that keeps the four thresholds and the coverage statistics.
223
227
  - **Log records**: `test/setup-logs.ts` takes over `console.*`; every `[shared/*]` line must match `test/log-templates.ts` or the run fails. `test/log-templates.test.ts` checks both directions, so adding or deleting a log without updating the record list fails.
224
- - **Published shape**: `test/publish-manifest.test.ts` requires every export target to be inside `files`, entry points to be artifacts rather than source, `publishConfig` to carry no entry fields, every peer to be stated in `peerDependenciesMeta`, `dsh-*` versions to be exact and uniform, and the source, docs and artifacts to carry no dates, absolute paths, home-directory references or collaboration-only wording.
228
+ - **Published shape**: `test/publish-manifest.test.ts` requires every export target to be inside `files`, entry points to be artifacts rather than source, `publishConfig` to carry no entry fields, every peer to be stated in `peerDependenciesMeta`, every `dsh-*` development pin to be exact and uniform, every dsh peer range to equal `engines.dsh`, and the source, docs and artifacts to carry no dates, absolute paths, home-directory references or collaboration-only wording.
225
229
  - Coverage thresholds are **100%** across lines, statements, functions and branches, so a new branch needs a case that reaches it. `test/vitest-base.test.ts` refuses an empty `coverageInclude` because that would make the four thresholds pass over nothing.
226
230
  - CI runs the checks on Linux, Windows and macOS across a Node 22/24 matrix, with a weekly scheduled run to catch upstream changes. The install matrix packs with both npm and pnpm, installs into a clean directory without peer-resolution flags, imports every subpath and type-checks a small consumer import under both `skipLibCheck` settings.
227
231
 
package/README.zh-CN.md CHANGED
@@ -79,19 +79,21 @@ import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
79
79
  | 要求 | 值 |
80
80
  | --- | --- |
81
81
  | Node.js | `>=22.12.0`——插件家族共用的发布侧下限,声明同一个 `engines.node` 的包不会因为这个库而被拒 |
82
- | dsh | `^0.2.1-rc.2`,声明为 **optional** peer(import 本包不需要宿主存在) |
82
+ | dsh | `^0.2.1-rc.3`,声明为 **optional** peer(import 本包不需要宿主存在) |
83
83
  | 模块形态 | 仅 ESM。`require()` 只在支持 `require(esm)` 的 Node 上可用 |
84
84
  | Tree shaking | `"sideEffects": false` |
85
85
 
86
+ - **宿主窗口是滚动的。** 本包在发布时最近的三个已发布宿主版本上逐版取证,窗口滚动后重新取证;`engines.dsh` 与 peer 里的范围只是装载门与编译期纪律,不等于取证范围。
87
+
86
88
  ### 依赖要求
87
89
 
88
90
  `dependencies` 为空;所有 `@deepseek-ai/*` 只出现在 `peerDependencies` 与 `devDependencies` 里。`test/publish-manifest.test.ts` 固定了这条要求,理由是一次启动期故障,不是风格偏好:
89
91
 
90
92
  - **宿主包写进 `dependencies` 会让消费方的宿主无法工作。** profile 用 `nodeLinker: hoisted` 与 `autoInstallPeers: false` 安装,于是列在 `dependencies` 里的宿主包会被安装成真实目录,与宿主进程已加载的那份并存。`@deepseek-ai/dsh-tools` 的工具运行期调度键是普通 `Symbol`(不是 `Symbol.for`),符号身份按模块实例计算:宿主从副本上读那个键得到 `undefined`,于是每一次原生工具调用都抛错。构建、类型检查与测试在此之前全部通过;这条不变式只能写在 manifest 上。
91
93
  - **`@deepseek-ai/dsh-output-retention` 是唯一必需 peer。** 它是值导入(`truncateEnd`),运行期由宿主提供;在 `peerDependenciesMeta` 里明写成 `"optional": false`,不靠"没写就等于必需",因为没写进那张表对 npm 7+ 就是硬要求。
92
- - **其余 peer 都是 optional。** 除裸 `@deepseek-ai/dsh`(只声明宿主版本线)外,它们只承载类型声明。放在 `peerDependencies` 里让发布出去的 `.d.ts` 与实际依赖一致;`devDependencies` 让本仓能编译与测试。profile 不装 peer,所以不会产生副本。
93
- - **版本要求跟着宿主走。** `dsh-*` 与 `cordis` 精确锁定在宿主声明的版本上,保证复用同一份。`test/publish-manifest.test.ts` 还要求每条 `dsh-*` 锁定版本与 `@deepseek-ai/dsh` peer 的版本一致。
94
- - 已知上游限制:`@deepseek-ai/dsh-llm` 的声明文件引用了它自己没声明的 `@deepseek-ai/dsh-attachment`。它经 `dsh-session` 传递进来,所以开 `skipLibCheck: false` 的消费方会看到来自那个包的 `TS2307`,不是来自本包。
94
+ - **其余 peer 都是 optional。** 除裸 `@deepseek-ai/dsh`(只声明宿主版本线)外,它们只承载类型声明。`devDependencies` 保存精确的开发线,其中包括本包 PTC 声明所引用的 `@deepseek-ai/dsh-llm` 与 `@deepseek-ai/dsh-util-values`,以及只有对照断言会读其类型的 `@deepseek-ai/dsh-tool-executor-ptc`。profile 不装 peer,所以不会产生副本。
95
+ - **版本要求把装载门与构建基线分开。** 每条 `@deepseek-ai/dsh-*` peer 与 `engines.dsh` 使用同一条 `^0.2.1-rc.3` 范围,而这条范围刻意只写一条宿主线:npm 对每条 peer 独立解析,把两条线并列(`^0.2.1-rc.3||^0.2.2-alpha.2`)会让必需 peer `@deepseek-ai/dsh-output-retention` 取到 alpha 线、cordis 取到 rc 线,干净目录安装止步于 `ERESOLVE`。支持窗口并没有因此变窄——宿主比较 `engines.dsh` 时带 `includePrerelease: true`,在该语义下 `^0.2.1-rc.3` 对 `0.2.1-rc.3`、`0.2.1-rc.4`、`0.2.1-rc.5`、`0.2.2-alpha.2` 全为真。`devDependencies` 精确锁定本仓编译与测试用的那一线(`0.2.2-alpha.2`),且必须统一:一棵树里只有一份 `dsh-session`,nominal 类型才互相认得。`@deepseek-ai/cordis` 在 `devDependencies` 里精确锁定 `4.0.6-alpha.1`,peer 范围 `^4.0.5||^4.0.6-alpha.1` 同时覆盖 rc 线与 alpha 线——单独一枚包不存在宿主家族那种混线问题。含多条比较符的范围在 `||` 两侧不带空格:一枚说明符因此是一个不含空白的 token,在任何按空白切分参数表的安装命令里都不会被拆开(CI 的类型面 peer 安装按行读取)。`test/publish-manifest.test.ts` 分别检查这三件事。
96
+ - 已知上游限制:`@deepseek-ai/dsh-llm` 的声明文件引用了它自己没声明的 `@deepseek-ai/dsh-attachment`。它经 `dsh-session` 传递进来,所以开 `skipLibCheck: false` 的消费方会看到来自那个包的 `TS2307`,不是来自本包。发布出去的 `./lib/tool-events` 声明还引用了 `@deepseek-ai/dsh-util-values`,它经 `dsh-session` / `dsh-tools` 传递进来;开 `skipLibCheck: false` 的消费方同样必须能解析到这个包。
95
97
 
96
98
  ### shared 的版本下限
97
99
 
@@ -128,7 +130,7 @@ import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
128
130
  | 子路径 | 提供什么 |
129
131
  | --- | --- |
130
132
  | `.` | 下面列出的运行期模块的 barrel。`canonicalize-region-paths` 刻意不进,运行期消费方不必为它增加一次导入;客户端专用的 `card-ui` 同样不进,理由还多一条:根 barrel 不得把 `react` 带进每个消费方 |
131
- | `./lib/tool-events` | `scanToolEvents` 返回 `calls` / `results` 两张表,PTC 子调用一并合并;`toolEventRowsOf` 处理单个事件,交回的记录已带归一后的 `arguments` 与 `badArguments`。`editPathOf` 返回按 `kind` 判别的 `EditTarget`,只有 `write` / `read-view` 两个分支;路径取不到时为 `undefined`。记录字段一律 `readonly`。同时再导出官方 `SessionEvent` 与两个 PTC 事件类型 |
133
+ | `./lib/tool-events` | `scanToolEvents` 返回 `calls` / `results` 两张表,PTC 子调用一并合并;`toolEventRowsOf` 处理单个事件,交回的记录已带归一后的 `arguments` 与 `badArguments`。`editPathOf` 返回按 `kind` 判别的 `EditTarget`,只有 `write` / `read-view` 两个分支;路径取不到时为 `undefined`。记录字段一律 `readonly`。同时再导出官方 `SessionEvent`;`PtcDispatchStartEventData` / `PtcDispatchEventData` 在本包内声明并自行增强 `SessionEventMap`,因为宿主在支持窗口内换过承载这份增强的包 |
132
134
  | `./lib/card-apply` | `claimApply` + `CardApplyCtx`:保证 apply 幂等的保护逻辑,建立在 `globalThis` 上的**自有**标记(symbol,或外部写入的 `true`)加 `ctx.effect` 清理。标记是进程级的,所以同一页面的第二个 ctx 会被拒绝——这是有意的设计 |
133
135
  | `./lib/card-diff` | `diffTouched`:touched 层里哪些字段真的与快照不同,按表单值比较。`undefined`、`null` 与键缺失是同一种值;数组与对象逐元素比较;循环引用也能正常结束 |
134
136
  | `./lib/card-ui` | 客户端专用设置卡组件:`CardForm`、`Section`、`FieldGrid`、`StatusBar`、`Field`、`FieldLabel`、`InfoTip`、`ToggleField`、`NumberField`、`TextField`、`ListField`、`SelectField`、`SaveBar`、`splitHint` 与样式字符串 `CARD_UI_CSS`。只用 `createElement`;`react` 是 optional peer,该子路径永不进根 barrel。只用 React 18 运行时 API(`useId`、`useState`、`useEffect`、refs) |
@@ -150,13 +152,14 @@ import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
150
152
 
151
153
  | 子路径 | 提供什么 | 你需要装什么 |
152
154
  | --- | --- | --- |
153
- | `./config/oxlint` | `definePluginConfig`、`makeTestFileOverrides`、`OFF_JUSTIFICATIONS` 与共享规则基线 | `oxlint`;要启用 sonarjs 规则还要 `eslint-plugin-sonarjs` |
155
+ | `./config/oxlint` | `definePluginConfig`、`makeTestFileOverrides`、`OFF_JUSTIFICATIONS` 与共享规则基线 | `oxlint`、`eslint-plugin-sonarjs`、`eslint-plugin-regexp`、`eslint-plugin-zod`(需 zod 4)、`@e18e/eslint-plugin` |
154
156
  | `./config/vitest.base` | `definePackageConfig`:四项 100% 覆盖率阈值、20 秒测试超时、共享覆盖率排除表与用例匹配范围 | `vitest` 与 `@vitest/coverage-v8`(基线把 `coverage.provider` 固定为 `"v8"`) |
155
157
  | `./config/tsconfig.base.json`、`./config/tsconfig.client.base.json` | 给 `extends` 用的纯 JSON | 什么都不用 |
156
158
 
157
159
  这些工具链包**不**写进 `peerDependencies`:npm 7+ 连 optional peer 都会去满足,而 `oxlint` 自己的 `peerOptional vite-plus` 把 `vitest` 锁定在一个与 `>=5.0.2` 无交集的版本上,结果是本包的普通 `npm install` 直接 `ERESOLVE` 失败。这条约束因此写在文档里,而不是 manifest 里。
158
160
 
159
- - sonarjs 的入口在 `definePluginConfig()` 内部惰性解析。没装 sonarjs 时导入 `./config/oxlint` 仍然成功;既没装 sonarjs、`jsPlugins` 里也没带 sonarjs 入口时,调用 `definePluginConfig()` 才抛错,消息里写明试过哪两个说明符,以及自备入口的方法。
161
+ - 四个 JS plugin(sonarjs / regexp / zod / e18e)的入口都在 `definePluginConfig()` 内部惰性解析。没装任何一条时导入 `./config/oxlint` 仍然成功;调用 `definePluginConfig()` 且该插件既没装、`jsPlugins` 里也没带入口时才抛错,消息里写明试过哪些说明符,以及自备入口的方法。
162
+ - `e18e/prefer-static-regex` 只在生产路径开启(`lib/**`、`src/**`、`host.ts`、`cli-main.ts`);测试与构建脚本里的内联正则不按缺陷处理,理由与实测条数在 `OFF_JUSTIFICATIONS`。
160
163
  - 基线里每条关闭为 `off` 的规则都要在 `OFF_JUSTIFICATIONS` 里有 `kind` + `measured` + `note`,否则配置加载即抛。包级例外写进那个包自己的 `offReasons`。
161
164
  - `definePackageConfig` 拒绝空 `coverageInclude`,也拒绝 `test.coverage` 块:两者任一都会让四项阈值或统计范围静默消失。要例外就在需要例外的包自己的 `coverage` 覆盖项旁边写明理由。
162
165
 
@@ -169,6 +172,7 @@ import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
169
172
  - **`shrinkJsonlTail` 不归一预算。** `NaN` 与 `+Infinity` 会整段关掉收缩;`0`、负数与 `-Infinity` 仍会收缩到最多两行。触发策略(包括"0 表示永不裁剪")与错误处理都归调用方。
170
173
  - **`jobOutcomeOf` 抄的是宿主 bash 工具的文案。** 测试检查的是 `exit code: N` / `signal: NAME` 这些字面量,不是与上游函数逐字相等——那个函数无法从已发布包里 import。上游哪天改措辞,本包不会报错,显示文本仍是本包自己的。
171
174
  - **`editPathOf` 只认一种只读写法。** 只有 `str_replace_editor` 且 `command: "view"` 才是 `read-view`;其余带 `file_path` 的工具一律按 `write` 处理,不再做其他判断。
175
+ - **PTC 载荷接口是一份本地副本。** 宿主在支持窗口内换过承载 `SessionEventMap` 增强的包,所以 `lib/tool-events.ts` 自己声明两个接口并自行增强映射。`test/tool-events-host-parity.ts` 把与开发基线的漂移变成 `pnpm typecheck` 失败;运行期断言做不到这一点,因为类型已被擦除。
172
176
 
173
177
  ## <img src="https://api.iconify.design/lucide/lock.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 数据与隐私
174
178
 
@@ -193,7 +197,7 @@ import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
193
197
  - **`errorText` 返回的是展示文本,不是可解析格式。** 它对齐官方 `errorChain` 的语义:cause 链拼成 `外: 内`、`AggregateError` 附 `[e1; e2]`、空 message 回退 `Error.name`、跨 realm 的 Error 取其自带 `message` 而不是 `String()` 的 `"Error: ..."`。拿它做正则匹配的调用方超出了受支持的用法。
194
198
  - **`editPathOf` 返回按 `kind` 判别的 `EditTarget`,只有 `write` / `read-view` 两个分支。** `"skip"` 从来不会被产出,留在类型里只会迫使调用方写永不执行的分支。路径取不到时仍返回所属分支,`path` 为 `undefined`。
195
199
  - **`./lib/http` 与 `./lib/trust` 已删除。** 插件家族的设置卡数据通道运行在官方 Typert Remote 上,认证、Host / DNS 重绑定 / Origin 校验与 body 上限由宿主的 `/api` 网关承担。还要自建 `webServer` 路由的插件得自己写这些检查——发布内容不再提供第二套"这个请求准不准"的答案。
196
- - **声明文件与宿主版本锁定是耦合的。** 升级宿主意味着同步更新本包里精确的 `@deepseek-ai/*` 版本并重新发布;放宽成范围会让消费方载入一份本包没有对着它构建过的类型声明。
200
+ - **声明文件对着一条宿主线编译,却装进一个 peer 窗口。** `devDependencies` 锁死构建与测试用的精确 `@deepseek-ai/*` 版本;`dsh` peer 家族与 `engines.dsh` 共用支持范围,窗口内的宿主无需第二份构建即可装载。窗口滚动意味着更新开发钉版、重跑对照断言并重新发布;把范围放宽到取证窗口之外不在支持范围内。
197
201
 
198
202
  ## <img src="https://api.iconify.design/lucide/circle-help.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> 常见问题
199
203
 
@@ -217,11 +221,11 @@ pnpm run build # 运行期产物、配置产物、声明文件
217
221
 
218
222
  - 测试按「stub 覆盖不到的那类缺陷」分层:
219
223
  - **模块行为**:每个运行期模块一份 `test/<模块>.test.ts`。
220
- - **接口一致性(surface parity)**:`test/index.test.ts`(barrel ↔ `lib/` 的值导出)、`test/publish-manifest.test.ts`(`exports` ↔ `lib/` 双向)与 `test/build-host.test.ts`(`build-host.mjs` 的 `facets` ↔ `lib/`)。三条检查各抓一种另外两条发现不了的漏项。
224
+ - **接口一致性(surface parity)**:`test/index.test.ts`(barrel ↔ `lib/` 的值导出)、`test/publish-manifest.test.ts`(`exports` ↔ `lib/` 双向)与 `test/build-host.test.ts`(`build-host.mjs` 的 `facets` ↔ `lib/`)。三条检查各抓一种另外两条发现不了的漏项。`test/tool-events-host-parity.ts` 是声明侧的对应物:它要求本包内声明的 PTC 载荷与开发基线双向可赋值,且刻意没有运行期用例——类型断言在 vitest 看到它之前就已被擦除;判它的是 `pnpm typecheck`。
221
225
  - **产物检查**:`test/build-host.test.ts` 把磁盘上的运行期产物与内存构建逐字节比对;`test/dist-size.test.ts` 把运行期产物与配置产物各自限制在字节预算内;同一份测试还固定官方值导入必须保持裸说明符,并拒绝为 `dsh-jobs` / `dsh-shell` 留下运行期说明符。
222
226
  - **配置检查**:`test/config-oxlint.test.ts` 把 vitest 规则快照与 `oxlint --rules` 的实际输出对照,并要求每条 `off` 都有依据;`test/vitest-base.test.ts` 固定合并行为,保证四项阈值与统计范围不会被局部改配抹掉。
223
227
  - **日志记录**:`test/setup-logs.ts` 接管 `console.*`;每条 `[shared/*]` 日志都要匹配 `test/log-templates.ts` 里的模板片段,否则测试立即失败。`test/log-templates.test.ts` 双向校验,增删日志却不改记录清单都会失败。
224
- - **发布形态**:`test/publish-manifest.test.ts` 要求入口目标都在 `files` 覆盖内、入口指向产物而不是源码、`publishConfig` 不带入口字段、每条 peer 都在 `peerDependenciesMeta` 里明确标注、`dsh-*` 版本精确且统一,并要求源码、文档与产物里没有日期、绝对路径、家目录指涉或只在协作当时成立的措辞。
228
+ - **发布形态**:`test/publish-manifest.test.ts` 要求入口目标都在 `files` 覆盖内、入口指向产物而不是源码、`publishConfig` 不带入口字段、每条 peer 都在 `peerDependenciesMeta` 里明确标注、`dsh-*` 开发钉版精确且统一、dsh peer 范围与 `engines.dsh` 相同,并要求源码、文档与产物里没有日期、绝对路径、家目录指涉或只在协作当时成立的措辞。
225
229
  - 覆盖率四项阈值都是 **100%**(lines / statements / functions / branches),所以新增分支必须有能走到它的用例。`test/vitest-base.test.ts` 拒绝空 `coverageInclude`,因为那会让四项阈值在空统计范围上全部通过。
226
230
  - CI 在 Linux、Windows 与 macOS 上运行同一组检查,Node 版本矩阵覆盖 22 与 24,并有一周一次的定时运行来发现上游变化。安装矩阵会分别用 npm 与 pnpm 打包,在干净目录里不带 peer 解析选项安装,逐子路径 import,并在两种 `skipLibCheck` 设置下对一小段消费方导入代码做类型检查。
227
231
 
@@ -1,2 +1,2 @@
1
- import{asRecord as e,closeQuietly as t,packageNameOf as n,readManifest as r,singleEntryChunk as i}from"./build-artifacts.js";import{t as a}from"./canonicalize-region-paths-BxladpX3.js";import{readFile as o}from"node:fs/promises";import s from"node:path";const c=[`react`,`react/jsx-runtime`,`react-dom`,`react-dom/client`];function l(e){let t=e.replaceAll(/^\/\/(?:#region .*| .*)$/gmu,e=>e.replaceAll(`\\`,`/`).replace(/^\/\/(?:#region | )?(?:(?:\.\.?\/)+)?node_modules\/\.pnpm\/[^/]+\/node_modules\//u,e=>e.startsWith(`//#region`)?`//#region node_modules/`:`// node_modules/`)),n=/node_modules\/\.pnpm\//u.exec(t);if(n!==null){let e=Math.max(0,n.index-40);throw Error(`产物里仍有 pnpm store 路径未折叠(出现在 …${JSON.stringify(t.slice(e,e+100))}…)。折叠判据与 rolldown 写下的形式不再匹配,client 产物的字节会随安装布局变化。`)}return t}async function u(e){return o(e,`utf8`)}function d(e){let t=e.name;if(typeof t!=`string`||t===``)throw Error(`package.json 的 name 缺失或非字符串。client 产物的 id 必须等于 dsh client 模块表的键(bundle 包名)。`);if(!/^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/u.test(t))throw Error(`package.json 的 name ${JSON.stringify(t)} 不符合 npm 包名字符集。`);return t}function f(e){let t=new Set(e);return e=>{if(t.has(e))return!0;let r=n(e);if(r===`react`||r===`react-dom`)throw Error(`${e} 是 react 家族说明符但不在外置清单里。外置会得到宿主模块表里没有的键(加载期报错),内联会得到第二份 React(hooks 静默失效);真要引入子路径,先把它加进外置清单并确认宿主模块表里有同名项。`);return!1}}async function p(n){let o=d(e(await r(n.root))),p=n.entry??s.join(`src`,`client-entry.ts`),m=n.minify??!1,h=n.comments??!m,g={format:`cjs`,...m?{minify:!0}:{},...h?{}:{comments:!1}},_=await n.rolldown({input:s.join(n.root,p),external:f(n.externals??c),platform:`browser`,cwd:n.root,resolve:{extensions:[`.tsx`,`.ts`,`.jsx`,`.js`,`.mjs`,`.json`]}}),v;try{let{output:e}=await _.generate(g),t=i(e,p);v=l(a(t.code.trimEnd(),n.root,process.cwd()))}catch(e){throw await t(_),e}return await _.close(),await n.verifyName?.({pkgName:o,body:v,readSource:e=>u(s.join(n.root,e))}),[n.header,`window.__ModuleLoader__.load({`,` id: ${JSON.stringify(o)},`,` factory: (require) => {`,` 'use strict';`,` const module = { exports: {} };`,` let exports = module.exports;`,v,` return module.exports;`,` },`,`});`,``].join(`
2
- `)}export{c as REACT_RUNTIME_MODULES,p as buildClientBundle,f as clientExternalPredicate,l as collapseStorePaths};
1
+ import{asRecord as e,closeQuietly as t,packageNameOf as n,readManifest as r,singleEntryChunk as i}from"./build-artifacts.js";import{t as a}from"./canonicalize-region-paths-DowoRhQK.js";import{readFile as o}from"node:fs/promises";import s from"node:path";const c=[`react`,`react/jsx-runtime`,`react-dom`,`react-dom/client`],l=/^\/\/(?:#region | )?(?:\.\.?\/)*node_modules\/\.pnpm\/[^/]+\/node_modules\//u,u=/node_modules\/\.pnpm\//u,d=/^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/u;function f(e){let t=e.replaceAll(/^\/\/(?:#region .*| .*)$/gmu,e=>e.replaceAll(`\\`,`/`).replace(l,e=>e.startsWith(`//#region`)?`//#region node_modules/`:`// node_modules/`)),n=u.exec(t);if(n!==null){let e=Math.max(0,n.index-40);throw Error(`产物里仍有 pnpm store 路径未折叠(出现在 …${JSON.stringify(t.slice(e,e+100))}…)。折叠判据与 rolldown 写下的形式不再匹配,client 产物的字节会随安装布局变化。`)}return t}async function p(e){return o(e,`utf8`)}function m(e){let t=e.name;if(typeof t!=`string`||t===``)throw Error(`package.json 的 name 缺失或非字符串。client 产物的 id 必须等于 dsh client 模块表的键(bundle 包名)。`);if(!d.test(t))throw Error(`package.json 的 name ${JSON.stringify(t)} 不符合 npm 包名字符集。`);return t}function h(e){let t=new Set(e);return e=>{if(t.has(e))return!0;let r=n(e);if(r===`react`||r===`react-dom`)throw Error(`${e} 是 react 家族说明符但不在外置清单里。外置会得到宿主模块表里没有的键(加载期报错),内联会得到第二份 React(hooks 静默失效);真要引入子路径,先把它加进外置清单并确认宿主模块表里有同名项。`);return!1}}async function g(n){let o=m(e(await r(n.root))),l=n.entry??s.join(`src`,`client-entry.ts`),u=n.minify??!1,d=n.comments??!u,g={format:`cjs`,...u?{minify:!0}:{},...d?{}:{comments:!1}},_=await n.rolldown({input:s.join(n.root,l),external:h(n.externals??c),platform:`browser`,cwd:n.root,resolve:{extensions:[`.tsx`,`.ts`,`.jsx`,`.js`,`.mjs`,`.json`]}}),v;try{let{output:e}=await _.generate(g),t=i(e,l);v=f(a(t.code.trimEnd(),n.root,process.cwd()))}catch(e){throw await t(_),e}return await _.close(),await n.verifyName?.({pkgName:o,body:v,readSource:e=>p(s.join(n.root,e))}),[n.header,`window.__ModuleLoader__.load({`,` id: ${JSON.stringify(o)},`,` factory: (require) => {`,` 'use strict';`,` const module = { exports: {} };`,` let exports = module.exports;`,v,` return module.exports;`,` },`,`});`,``].join(`
2
+ `)}export{c as REACT_RUNTIME_MODULES,g as buildClientBundle,h as clientExternalPredicate,f as collapseStorePaths};
@@ -1 +1 @@
1
- import{asRecord as e,closeQuietly as t,isRecord as n,packageNameOf as r,readManifest as i,singleEntryChunk as a}from"./build-artifacts.js";import{t as o}from"./canonicalize-region-paths-BxladpX3.js";import s from"node:path";function c(e,t){let r=e[t];if(r===void 0)return[];if(!n(r))throw TypeError(`package.json 的 ${t} 不是对象(实为 ${Array.isArray(r)?`array`:typeof r})。折成空集会让外部化名单失效、依赖全部内联进产物,所以拒绝继续。`);return Object.keys(r).map(e=>{if(typeof r[e]!=`string`)throw TypeError(`package.json 的 ${t}.${e} 不是字符串范围(收到 ${typeof r[e]})。静默跳过会让这个包被当成内部依赖整包内联,而内联恰好复制它的模块级状态。`);return e})}function l(e,t){let n=[...c(e,`dependencies`),...c(e,`peerDependencies`)];return t&&n.push(...c(e,`optionalDependencies`)),new Set(n)}function u(e){return Array.isArray(e)&&e.length>0&&e.every(e=>typeof e==`string`&&e.length>0)}async function d(n){let c=l(e(await i(n.root)),n.externalizeOptional??!0);if(c.size===0)throw Error(`package.json 的 dependencies / peerDependencies 都取不到字符串条目,外部化集合为空——依赖会被整包内联。`);let d=e=>c.has(r(e)),f=n.resolveExtensions??[`.ts`,`.mjs`,`.js`],p=n.minify??!1,m=n.comments??!p,h={format:`esm`,...p?{minify:!0}:{},...m?{}:{comments:!1}},g=async e=>{let r=await n.rolldown({input:s.join(n.root,e.source),external:d,platform:`node`,cwd:n.root,resolve:{extensions:[...f]},...n.plugins===void 0?{}:{plugins:n.plugins}}),i;try{let{output:t}=await r.generate(h),c=a(t,e.source);if(!u(c.moduleIds))throw TypeError(`rolldown 为 ${e.source} 产出的 entry chunk 的 moduleIds 不是"非空字符串数组"`);let l=o(c.code.trimEnd(),n.root,process.cwd());i={source:e.source,output:e.output,file:s.join(n.root,e.output),code:`${e.header}${l}\n`,inlinedModules:c.moduleIds}}catch(e){throw await t(r),e}return await r.close(),i};return Promise.all(n.entries.map(e=>g(e)))}export{d as buildHostEntries};
1
+ import{asRecord as e,closeQuietly as t,isRecord as n,packageNameOf as r,readManifest as i,singleEntryChunk as a}from"./build-artifacts.js";import{t as o}from"./canonicalize-region-paths-DowoRhQK.js";import s from"node:path";function c(e,t){let r=e[t];if(r===void 0)return[];if(!n(r))throw TypeError(`package.json 的 ${t} 不是对象(实为 ${Array.isArray(r)?`array`:typeof r})。折成空集会让外部化名单失效、依赖全部内联进产物,所以拒绝继续。`);return Object.keys(r).map(e=>{if(typeof r[e]!=`string`)throw TypeError(`package.json 的 ${t}.${e} 不是字符串范围(收到 ${typeof r[e]})。静默跳过会让这个包被当成内部依赖整包内联,而内联恰好复制它的模块级状态。`);return e})}function l(e,t){let n=[...c(e,`dependencies`),...c(e,`peerDependencies`)];return t&&n.push(...c(e,`optionalDependencies`)),new Set(n)}function u(e){return Array.isArray(e)&&e.length>0&&e.every(e=>typeof e==`string`&&e.length>0)}async function d(n){let c=l(e(await i(n.root)),n.externalizeOptional??!0);if(c.size===0)throw Error(`package.json 的 dependencies / peerDependencies 都取不到字符串条目,外部化集合为空——依赖会被整包内联。`);let d=e=>c.has(r(e)),f=n.resolveExtensions??[`.ts`,`.mjs`,`.js`],p=n.minify??!1,m=n.comments??!p,h={format:`esm`,...p?{minify:!0}:{},...m?{}:{comments:!1}},g=async e=>{let r=await n.rolldown({input:s.join(n.root,e.source),external:d,platform:`node`,cwd:n.root,resolve:{extensions:[...f]},...n.plugins===void 0?{}:{plugins:n.plugins}}),i;try{let{output:t}=await r.generate(h),c=a(t,e.source);if(!u(c.moduleIds))throw TypeError(`rolldown 为 ${e.source} 产出的 entry chunk 的 moduleIds 不是"非空字符串数组"`);let l=o(c.code.trimEnd(),n.root,process.cwd());i={source:e.source,output:e.output,file:s.join(n.root,e.output),code:`${e.header}${l}\n`,inlinedModules:c.moduleIds}}catch(e){throw await t(r),e}return await r.close(),i};return Promise.all(n.entries.map(e=>g(e)))}export{d as buildHostEntries};
@@ -0,0 +1 @@
1
+ const e=/^\/\/#region .*$/gmu,t=/^[^\s'"`]+$/u,n=/^(?:\.\.?\/)+/u,r=/[/\\]+/u,i=/[/\\]+$/u,a=/^[A-Za-z]:[\\/]/u;function o(e){let t=e.replace(i,``),n=Math.max(t.lastIndexOf(`/`),t.lastIndexOf(`\\`));return n===-1?t:t.slice(n+1)}function s(e){let t=e.replace(i,``),n=Math.max(t.lastIndexOf(`/`),t.lastIndexOf(`\\`));return n===-1?t:t.slice(0,n)}function c(e){let t=o(e);return[o(s(e)),t].filter(e=>e.length>0)}function l(e,t){let n=e;for(let e of t)n.startsWith(`${e}/`)&&(n=n.slice(e.length+1));return n}function u(e){return e.split(r)}function d(e){let t=[...e];for(;t.length>1&&t.at(-1)===``;)t.pop();return t}function f(e){return e.startsWith(`/`)||e.startsWith(`\\`)||a.test(e)}function p(e){let t=[];for(let n=0;n<e;n+=1)t.push(`..`);return t}function m(e,t){let n=f(e);if(n!==f(t))return null;let r=d(u(e)),i=d(u(t));if(n&&r[0]!==i[0])return null;let a=0;for(;a<r.length&&a<i.length&&r[a]===i[a];)a+=1;return[...p(r.length-a),...i.slice(a)]}function h(e){if(e===null||e.length===0)return``;let t=0;for(;t<e.length&&e[t]===`..`;)t+=1;return t===e.length?``:e.slice(t).join(`/`)}function g(e,t){return{own:h(m(e,t)),parent:h(m(e,s(t)))}}function _(e,t){return t.own.length>0&&e.startsWith(`${t.own}/`)?e.slice(t.own.length+1):t.parent.length>0&&e.startsWith(`${t.parent}/`)?e.slice(t.parent.length+1):e}function v(e,r,i){let a=e.slice(10);if(!t.test(a))return e;let o=a.replace(n,``);return`//#region ${i===null?l(o,r):_(o,i)}`}function y(t,n,r){let i=c(n);if(r===void 0)return i.length===0?t:t.replaceAll(e,e=>v(e,i,null));let a=g(r,n);return t.replaceAll(e,e=>v(e,i,a))}export{y as t};
@@ -1 +1 @@
1
- const e=/^\/\/#region .*$/gmu,t=/^[^\s'"`]+$/u,n=/^(?:\.\.?\/)+/u,r=/[/\\]+/u;function i(e){let t=e.replace(/[/\\]+$/u,``),n=Math.max(t.lastIndexOf(`/`),t.lastIndexOf(`\\`));return n===-1?t:t.slice(n+1)}function a(e){let t=e.replace(/[/\\]+$/u,``),n=Math.max(t.lastIndexOf(`/`),t.lastIndexOf(`\\`));return n===-1?t:t.slice(0,n)}function o(e){let t=i(e);return[i(a(e)),t].filter(e=>e.length>0)}function s(e,t){let n=e;for(let e of t)n.startsWith(`${e}/`)&&(n=n.slice(e.length+1));return n}function c(e){return e.split(r)}function l(e){let t=[...e];for(;t.length>1&&t.at(-1)===``;)t.pop();return t}function u(e){return e.startsWith(`/`)||e.startsWith(`\\`)||/^[A-Za-z]:[\\/]/u.test(e)}function d(e,t){let n=u(e);if(n!==u(t))return null;let r=l(c(e)),i=l(c(t));if(n&&r[0]!==i[0])return null;let a=0;for(;a<r.length&&a<i.length&&r[a]===i[a];)a+=1;return[...Array.from({length:r.length-a},()=>`..`),...i.slice(a)]}function f(e){if(e===null||e.length===0)return``;let t=0;for(;t<e.length&&e[t]===`..`;)t+=1;return t===e.length?``:e.slice(t).join(`/`)}function p(e,t){return{own:f(d(e,t)),parent:f(d(e,a(t)))}}function m(e,t){return t.own.length>0&&e.startsWith(`${t.own}/`)?e.slice(t.own.length+1):t.parent.length>0&&e.startsWith(`${t.parent}/`)?e.slice(t.parent.length+1):e}function h(e,r,i){let a=e.slice(10);if(!t.test(a))return e;let o=a.replace(n,``);return`//#region ${i===null?s(o,r):m(o,i)}`}function g(t,n,r){let i=o(n);if(r===void 0)return i.length===0?t:t.replaceAll(e,e=>h(e,i,null));let a=p(r,n);return t.replaceAll(e,e=>h(e,i,a))}export{g as canonicalizeRegionPaths};
1
+ const e=/^\/\/#region .*$/gmu,t=/^[^\s'"`]+$/u,n=/^(?:\.\.?\/)+/u,r=/[/\\]+/u,i=/[/\\]+$/u,a=/^[A-Za-z]:[\\/]/u;function o(e){let t=e.replace(i,``),n=Math.max(t.lastIndexOf(`/`),t.lastIndexOf(`\\`));return n===-1?t:t.slice(n+1)}function s(e){let t=e.replace(i,``),n=Math.max(t.lastIndexOf(`/`),t.lastIndexOf(`\\`));return n===-1?t:t.slice(0,n)}function c(e){let t=o(e);return[o(s(e)),t].filter(e=>e.length>0)}function l(e,t){let n=e;for(let e of t)n.startsWith(`${e}/`)&&(n=n.slice(e.length+1));return n}function u(e){return e.split(r)}function d(e){let t=[...e];for(;t.length>1&&t.at(-1)===``;)t.pop();return t}function f(e){return e.startsWith(`/`)||e.startsWith(`\\`)||a.test(e)}function p(e){let t=[];for(let n=0;n<e;n+=1)t.push(`..`);return t}function m(e,t){let n=f(e);if(n!==f(t))return null;let r=d(u(e)),i=d(u(t));if(n&&r[0]!==i[0])return null;let a=0;for(;a<r.length&&a<i.length&&r[a]===i[a];)a+=1;return[...p(r.length-a),...i.slice(a)]}function h(e){if(e===null||e.length===0)return``;let t=0;for(;t<e.length&&e[t]===`..`;)t+=1;return t===e.length?``:e.slice(t).join(`/`)}function g(e,t){return{own:h(m(e,t)),parent:h(m(e,s(t)))}}function _(e,t){return t.own.length>0&&e.startsWith(`${t.own}/`)?e.slice(t.own.length+1):t.parent.length>0&&e.startsWith(`${t.parent}/`)?e.slice(t.parent.length+1):e}function v(e,r,i){let a=e.slice(10);if(!t.test(a))return e;let o=a.replace(n,``);return`//#region ${i===null?l(o,r):_(o,i)}`}function y(t,n,r){let i=c(n);if(r===void 0)return i.length===0?t:t.replaceAll(e,e=>v(e,i,null));let a=g(r,n);return t.replaceAll(e,e=>v(e,i,a))}export{y as canonicalizeRegionPaths};
@@ -1 +1 @@
1
- import{createRequire as e}from"node:module";import{defineConfig as t}from"oxlint";function n(e){return(typeof e==`string`?e:`${e.name} ${e.specifier}`).includes(`sonarjs`)}const r=`同 no-unsafe-member-access`,i={"vitest/*":{kind:`仅限特定文件`,measured:7,note:`用例结构类判断在非用例文件上无从计算`},"vitest/no-hooks":{kind:`上游不推荐`,measured:181,note:`禁用 hook 本身;本仓按官方 vitest 用法大量使用 before*/after*`},"vitest/no-conditional-in-test":{kind:`仅限特定文件`,measured:523,note:`平台/宿主版本分支是刻意的(macOS 与 Linux 的家目录形态、0.1.7 与 0.1.6 的 API 差异)`},"vitest/max-expects":{kind:`上游不推荐`,measured:163,note:`给单条用例的断言条数设上限属风格偏好,不指向缺陷`},"vitest/prefer-expect-assertions":{kind:`上游不推荐`,measured:2902,note:`要求每个 async 用例手写 expect.assertions(n),是与断言强度无关的计数要求`},"vitest/require-test-timeout":{kind:`上游不推荐`,measured:2892,note:`要求每条用例显式 timeout;本仓用泵式假时钟,超时由 13 份 vitest 配置统一给出`},"vitest/prefer-importing-vitest-globals":{kind:`实测有害`,measured:0,note:`autofix 方向是删掉显式 import 改回裸全局;13 份 vitest 配置都没开 globals:true,应用后立即出现 3605 处 TS2593、测试全部失效`},"vitest/no-importing-vitest-globals":{kind:`实测有害`,measured:133,note:`与上一条同族、方向相反,两条同时开启就是把两种写法都判违规`},"vitest/prefer-import-in-mock":{kind:`实测有害`,measured:6,note:"动态形态按完整模块导出校验工厂返回值,而 @types/node 的 os、@types/react 都是 `export =` 形状、类型上没有 default;实测三处 TS2769+TS2740/2741,react 那处无论怎么展开都补不出 default"},"vitest/prefer-lowercase-title":{kind:`仅限特定文件`,measured:171,note:`只在用例文件里生效:它带 autofix,实测一轮 oxlint --fix 静默把 260 条标题改成了小写(含 nO_VERIFY 这种坏名字)。包级 titlePrefixes 只留真正的技术名(GET/CSRF/组件名…):非技术名(工单号一类)不该借这条豁免留在标题里,而中文开头的标题天然满足要求(无大小写),免检范围能收多小就收多小。实测 oxlint 前缀一命中就把整条标题免检,所以陈旧条目由配置基线自检按『免检范围未被使用』指出,不靠人工巡检`},"vitest/prefer-called-times":{kind:`与已启用规则互斥`,measured:0,note:`实测核对(同文件四种写法各跑一次):toHaveBeenCalledTimes(1) 被 prefer-called-once 判违规、toHaveBeenCalledOnce() 被本条判违规、expect(spy.mock.calls).toHaveLength(1) 被 prefer-to-have-been-called-times 判违规,只有 n>=2 能同时满足三条 ⇒ 三条同开等于「单次调用」这条断言无法书写(实测逼得消费方改用 assert.equal(spy.mock.calls.length, 1) 绕行)。保留 prefer-called-once 与 prefer-to-have-been-called-times 两条方向一致的组合,关闭本条`},"vitest/prefer-describe-function-title":{kind:`与已启用规则互斥`,measured:48,note:"要求 `describe(fn)`,而 valid-title 要求字符串字面量(oxlint 1.85 的 valid-title 是 DummyRule、无任何选项);上游 recommended 收的是 valid-title,取它,26 处 `describe(fn)` 按代码改成同名字符串"},"vitest/prefer-to-be-truthy":{kind:`实测有害`,measured:117,note:`把 toStrictEqual(true) 放宽成 toTruthy(),接受任意真值是削弱断言`},"vitest/prefer-to-be-falsy":{kind:`实测有害`,measured:89,note:`同族,放宽成 toFalsy() 同样是削弱断言`},"import/no-default-export":{kind:`与官方形态冲突`,measured:38,note:`官方 cordis 入口就是 default export(vendor/loader/src/index.ts 的 unwrapExports 先 unwrap default),12 条正是 12 个 bundle 入口`},"import/no-named-export":{kind:`与官方形态冲突`,measured:895,note:`13 个包与上游 deepseek-harness 一样以具名导出 + 子路径说明符对外提供接口`},"import/prefer-default-export":{kind:`与官方形态冲突`,measured:58,note:`与 no-default-export 同族、方向与 cordis 入口相反`},"import/no-nodejs-modules":{kind:`与官方形态冲突`,measured:448,note:"宿主侧就运行在 node 上,`node:` 内置模块是运行环境本身而不是越界"},"import/no-named-as-default-member":{kind:`与官方形态冲突`,measured:47,note:`对具名导出的默认对象做成员访问是本仓消费官方声明的常态`},"import/no-anonymous-default-export":{kind:`与官方形态冲突`,measured:12,note:`官方 cordis 入口是匿名对象 default export(registry.ts 的 RegistryService.plugin() 读 name/Config,isApplicable() 判 apply),改成具名等于改官方形态`},"eslint/no-duplicate-imports":{kind:`实测有害`,measured:166,note:`ESLint 官方已废弃(不区分 import type),由 import/no-duplicates 取代`},"import/group-exports":{kind:`上游不推荐`,measured:766,note:`要求单点导出,与具名导出的接口形态冲突`},"import/exports-last":{kind:`上游不推荐`,measured:528,note:`要求导出语句在文件末尾`},"import/no-relative-parent-imports":{kind:`上游不推荐`,measured:277,note:`包内 lib/ 与 test/ 的相对父级引用是既有结构`},"import/max-dependencies":{kind:`上游不推荐`,measured:25,note:`给 import 条数设上限;宿主侧消费官方服务天然依赖多`},"node/no-process-env":{kind:`上游不推荐`,measured:51,note:`环境变量是本仓的输入来源(DSH_HOME、profile 目录、构建期开关);eslint-plugin-n 的 recommended 也不含本条`},"eslint/no-magic-numbers":{kind:`上游不推荐`,measured:5774,note:`把 HTTP 状态码/毫秒阈值抽成常量的计数规则`},"eslint/sort-keys":{kind:`上游不推荐`,measured:4101,note:`对象键排序;成对字面量与 locale 字典的可读性会一起受损`},"eslint/sort-imports":{kind:`上游不推荐`,measured:675,note:`字母序 import,与 oxfmt 的分组形态冲突`},"eslint/capitalized-comments":{kind:`上游不推荐`,measured:2388,note:`本仓注释以中文为主,「首字母大写」对中文注释无从判定`},"eslint/no-ternary":{kind:`上游不推荐`,measured:1024,note:`禁三元;改写成 if/else 并不会更正确`},"eslint/no-undefined":{kind:`上游不推荐`,measured:2009,note:"禁 `undefined` 字面量,与本仓大量显式可选返回形态冲突"},"eslint/no-void":{kind:`上游不推荐`,measured:172,note:"禁 `void` 前缀,而 `void promise` 正是满足 no-floating-promises 的官方写法"},"unicorn/no-null":{kind:`上游不推荐`,measured:788,note:`禁 null;官方声明与 JSON 边界大量用 null,改成 undefined 会改变序列化形态`},"typescript/prefer-readonly-parameter-types":{kind:`上游不推荐`,measured:2433,note:`要求每个入参类型全 readonly;官方声明(SessionEvent/ConfigForm 等)是可变的,改不动也不该改`},"oxc/no-async-await":{kind:`与已启用规则互斥`,measured:2160,note:`禁 async/await,而同表里 promise-function-async、require-await、no-floating-promises 都在要求用 async`},"oxc/no-optional-chaining":{kind:`与已启用规则互斥`,measured:1354,note:"禁 `?.`,而 typescript/prefer-optional-chain 正在要求把 && 链改成 `?.`"},"oxc/no-rest-spread-properties":{kind:`与已启用规则互斥`,measured:550,note:`禁对象 spread,而 ES2024 目标与官方声明的投影写法都靠它`},"typescript/no-explicit-any":{kind:`仅限特定文件`,measured:1,note:`用例里的桩件面(mini-react 之类)需要 any 才塞得进官方签名`},"typescript/no-non-null-assertion":{kind:`仅限特定文件`,measured:536,note:"用例里 `!` 是「前驱条件已成立」的最短写法"},"typescript/no-unsafe-type-assertion":{kind:`仅限特定文件`,measured:844,note:"桩件需要 `as never` / `as unknown as` 跨官方名义类型"},"typescript/require-await":{kind:`仅限特定文件`,measured:467,note:`官方 async 契约的同步实现与测试替身撑不住这条;prod 侧的条数按代码修,不进豁免`},"eslint/require-await":{kind:`与官方形态冲突`,measured:481,note:`它与 typescript/require-await 是同一条判断的两份实现,不是两条:实测核对(一段无 await 的 async 函数、--type-aware)单开各自都报、同开报两次,即 2 errors / 1 defect。按 typescript-eslint 官方对 extension rule 的处理方式裁决:留带类型信息的扩展那条、关基线那条。覆盖范围没有因此变小:全仓逐行去重实测 514 行两点同报、typescript 独有 0 行、eslint 独有只落在用例范围与配置自身的中间态;而 --type-aware 实测对 .mjs 也会由扩展那条报出,所以 scripts/*.mjs 面不需要基线那条补位。用例文件由 TEST_OVERRIDES 整族豁免`},"eslint/max-statements":{kind:`仅限特定文件`,measured:549,note:`用例体天然长(given/when/then 展开)`},"eslint/max-lines-per-function":{kind:`仅限特定文件`,measured:365,note:`同上`},"eslint/max-lines":{kind:`仅限特定文件`,measured:54,note:`单个用例文件的长度不作为缺陷信号`},"node/no-sync":{kind:`仅限特定文件`,measured:299,note:`构建脚本与门禁脚本按设计同步读;生产代码的写侧由每包 syncWrites 逐项登记,不在豁免里`},"node/no-top-level-await":{kind:`仅限特定文件`,measured:53,note:`脚本以 node 直接执行,顶层 await 是 ESM 常态;prod 侧 0 条`},"typescript/strict-boolean-expressions":{kind:`仅限特定文件`,measured:0,note:`scripts/*.mjs 无类型面,隐式 any 上无从判定`},"typescript/no-implied-eval":{kind:`仅限特定文件`,measured:0,note:`同上:这条要类型信息才能判`},"typescript/no-unsafe-member-access":{kind:`仅限特定文件`,measured:0,note:`无类型标注的脚本里每个值都落进隐式 any`},"typescript/no-unsafe-call":{kind:`仅限特定文件`,measured:0,note:r},"typescript/no-unsafe-assignment":{kind:`仅限特定文件`,measured:0,note:r},"typescript/no-unsafe-return":{kind:`仅限特定文件`,measured:0,note:r},"typescript/no-unsafe-argument":{kind:`仅限特定文件`,measured:0,note:r},"typescript/require-array-sort-compare":{kind:`仅限特定文件`,measured:0,note:`无类型脚本里「比较器缺失」判不出来`},"unicorn/no-process-exit":{kind:`仅限特定文件`,measured:2,note:`CLI 脚本的 process.exit 就是它的返回方式`},"eslint/no-continue":{kind:`仅限特定文件`,measured:53,note:`只对 scripts/ 豁免:TS 侧实测 0 条(包代码本来就不用 continue),908 行的门禁脚本改成嵌套 if 只会更难读`},"unicorn/max-nested-calls":{kind:`仅限特定文件`,measured:59,note:`只对 scripts/ 豁免:TS 侧实测 0 条;CLI 脚本里链式 map/filter/join 的深度由数据形状决定,不指向缺陷`},"unicorn/no-useless-undefined":{kind:`仅限特定文件`,measured:0,note:`脚本侧同上调;prod 仍按配置项 checkArguments:false 生效`},"react/set-state-in-effect":{kind:`与官方形态冲突`,measured:16,note:`卡片用 effect 同步宿主态到 store 是挂载适配器形态;Compiler 建议的 render 期推导要改写 12 份适配器的状态流`},"react/no-deriving-state-in-effects":{kind:`与官方形态冲突`,measured:7,note:`适配器形态里「props/state 派生值在 effect 对齐宿主」是挂载动作,不是可下沉到 render 的纯派生`},"react/exhaustive-effect-dependencies":{kind:`与官方形态冲突`,measured:8,note:`Compiler 版依赖审计对适配器的内联闭包全量追问;与 react-hooks/exhaustive-deps(保留并已修)形成双门,留后者`},"react/hooks":{kind:`与官方形态冲突`,measured:13,note:`「同一 hook 函数跨渲染同引用」在适配器的内联闭包形态下整体不成立;收敛批次把闭包提升后再回来开`},"react/refs":{kind:`与官方形态冲突`,measured:1,note:`挂载适配器在 render 期读 ref 对齐宿主布局;收敛批次改成挂载后读`},"react/todo":{kind:`上游不推荐`,measured:6,note:`报的是 React 编译器 BuildHIR 自己不支持 try/catch(-finally) 的上游 TODO,不是代码缺陷`},"react/rules-of-hooks":{kind:`仅限特定文件`,measured:3,note:`只对用例文件豁免(TEST_FILE_OVERRIDES):测试架在组件树外直接驱动卡片适配 hook(useCardOf / useSyncExternalStore 桩),调用次数顺序由桩件固定。键用表载前缀 react/(诊断文案显示 react-hooks(...) 是显示名,配置键以 --rules 的 Source 列为准)`}},a=[`typescript/no-empty-object-type`],o=[`existsSync`,`readFileSync`,`readdirSync`,`readSync`,`realpathSync`,`statSync`],s=[`eslint`,`typescript`,`unicorn`,`oxc`,`node`,`promise`,`vitest`,`import`,`react`,`jsx-a11y`],c=[`eslint-plugin-sonarjs`,`eslint-plugin-sonarjs/cjs/plugin.js`];function l(){let t=e(import.meta.url),n=[];for(let e of c)try{return t.resolve(e)}catch{n.push(e)}throw Error(`oxlint 基线要挂 eslint-plugin-sonarjs,但从本包解析不到它(试过 ${n.join(`、`)})。要么在本包 devDependencies 里装 eslint-plugin-sonarjs,要么把你自己解析到的 sonarjs 入口经 definePluginConfig({ jsPlugins: [...] }) 传进来(入口路径须指向 sonarjs:SONARJS_RULES 无条件引用它,换成别家的插件无法满足这一条)。`)}function u(e,t=l){let r=e??[],i=null;try{i=t()}catch(e){if(!r.some(e=>n(e)))throw e}return i===null?[...r]:[i,...r.filter(e=>e!==i)]}const d={"sonarjs/no-identical-functions":`error`,"sonarjs/no-duplicated-branches":`error`,"sonarjs/no-all-duplicated-branches":`error`,"sonarjs/no-collapsible-if":`error`,"sonarjs/no-identical-conditions":`error`,"sonarjs/no-identical-expressions":`error`,"sonarjs/no-duplicate-string":`error`,"sonarjs/cognitive-complexity":`error`,"sonarjs/max-switch-cases":`error`,"sonarjs/no-inverted-boolean-check":`error`,"sonarjs/no-duplicate-in-composite":`error`,"sonarjs/duplicates-in-character-class":`error`,"sonarjs/no-useless-catch":`error`,"sonarjs/no-extra-arguments":`error`,"sonarjs/no-redundant-assignments":`error`,"sonarjs/prefer-single-boolean-return":`error`,"sonarjs/label-position":`error`},f={correctness:`error`,suspicious:`error`,style:`error`,perf:`error`,restriction:`error`,pedantic:`error`,nursery:`error`},p={typeAware:!0,typeCheck:!0,denyWarnings:!0,maxWarnings:0,reportUnusedDisableDirectives:`error`,respectEslintDisableDirectives:!1},m=[`**/node_modules/**`,`.toolchain/**`,`coverage/**`,`dist/**`,`client.js`,`host.js`],h=/* @__PURE__ */ `vitest/consistent-each-for.vitest/consistent-test-filename.vitest/consistent-test-it.vitest/consistent-vitest-vi.vitest/expect-expect.vitest/hoisted-apis-on-top.vitest/max-expects.vitest/max-nested-describe.vitest/no-alias-methods.vitest/no-commented-out-tests.vitest/no-conditional-expect.vitest/no-conditional-in-test.vitest/no-conditional-tests.vitest/no-disabled-tests.vitest/no-duplicate-hooks.vitest/no-focused-tests.vitest/no-hooks.vitest/no-identical-title.vitest/no-import-node-test.vitest/no-importing-vitest-globals.vitest/no-interpolation-in-snapshots.vitest/no-large-snapshots.vitest/no-mocks-import.vitest/no-restricted-matchers.vitest/no-restricted-vi-methods.vitest/no-standalone-expect.vitest/no-test-prefixes.vitest/no-test-return-statement.vitest/no-unneeded-async-expect-function.vitest/padding-around-after-all-blocks.vitest/padding-around-test-blocks.vitest/prefer-called-exactly-once-with.vitest/prefer-called-once.vitest/prefer-called-times.vitest/prefer-called-with.vitest/prefer-comparison-matcher.vitest/prefer-describe-function-title.vitest/prefer-each.vitest/prefer-equality-matcher.vitest/prefer-expect-assertions.vitest/prefer-expect-resolves.vitest/prefer-expect-type-of.vitest/prefer-hooks-in-order.vitest/prefer-hooks-on-top.vitest/prefer-import-in-mock.vitest/prefer-importing-vitest-globals.vitest/prefer-lowercase-title.vitest/prefer-mock-promise-shorthand.vitest/prefer-mock-return-shorthand.vitest/prefer-snapshot-hint.vitest/prefer-spy-on.vitest/prefer-strict-boolean-matchers.vitest/prefer-strict-equal.vitest/prefer-to-be.vitest/prefer-to-be-falsy.vitest/prefer-to-be-object.vitest/prefer-to-be-truthy.vitest/prefer-to-contain.vitest/prefer-to-have-been-called-times.vitest/prefer-to-have-length.vitest/prefer-todo.vitest/require-awaited-expect-poll.vitest/require-hook.vitest/require-local-test-context-for-concurrent-snapshots.vitest/require-mock-type-parameters.vitest/require-test-timeout.vitest/require-to-throw-message.vitest/require-top-level-describe.vitest/valid-describe-callback.vitest/valid-expect.vitest/valid-expect-in-promise.vitest/valid-title.vitest/warn-todo`.split(`.`),g=/* @__PURE__ */ new Set([`vitest/max-expects`,`vitest/no-conditional-in-test`,`vitest/no-hooks`,`vitest/no-importing-vitest-globals`,`vitest/prefer-called-times`,`vitest/prefer-describe-function-title`,`vitest/prefer-expect-assertions`,`vitest/prefer-import-in-mock`,`vitest/prefer-importing-vitest-globals`,`vitest/prefer-to-be-falsy`,`vitest/prefer-to-be-truthy`,`vitest/require-test-timeout`]),_={"eslint/one-var":[`error`,`never`],"eslint/func-style":[`error`,`declaration`,{allowArrowFunctions:!0,overrides:{namedExports:`ignore`}}],"eslint/id-length":[`error`,{exceptions:[`a`,`i`,`t`,`x`,`y`,`z`]}],"eslint/init-declarations":[`error`,`never`,{ignoreForLoopInit:!0}],"eslint/max-lines":[`error`,{max:2e3,skipBlankLines:!0,skipComments:!0}],"eslint/max-lines-per-function":[`error`,{max:120,skipBlankLines:!0,skipComments:!0}],"eslint/complexity":`error`,"eslint/max-params":[`error`,{max:5}],"eslint/max-statements":[`error`,50],"eslint/no-console":[`error`,{allow:[`warn`,`error`,`info`]}],"eslint/no-warning-comments":[`error`,{terms:[`todo`,`fixme`,`xxx`,`hack`],location:`start`}],"eslint/no-param-reassign":`error`,"eslint/no-underscore-dangle":[`error`,{allow:[`__memoryInsightCardApplied`,`__memoryTdaiCardApplied`,`__sessionRescueApplied`,`__wukilDevToolsCardApplied`]}],"eslint/no-magic-numbers":`off`,"eslint/sort-keys":`off`,"eslint/sort-imports":`off`,"eslint/capitalized-comments":`off`,"eslint/no-ternary":`off`,"eslint/no-undefined":`off`,"eslint/no-void":`off`,"eslint/no-duplicate-imports":`off`,"eslint/require-await":`off`,"import/no-named-export":`off`,"import/group-exports":`off`,"import/exports-last":`off`,"import/prefer-default-export":`off`,"import/no-nodejs-modules":`off`,"import/no-relative-parent-imports":`off`,"import/max-dependencies":`off`,"import/no-default-export":`off`,"import/no-anonymous-default-export":`off`,"import/no-named-as-default-member":`off`,"import/consistent-type-specifier-style":[`error`,`prefer-top-level`],"import/no-duplicates":`error`,"import/no-cycle":`error`,"import/named":`error`,"import/first":`error`,"import/newline-after-import":`error`,"node/no-process-env":`off`,"node/no-sync":[`error`,{ignores:o}],"node/no-top-level-await":`error`,"oxc/no-async-await":`off`,"oxc/no-optional-chaining":`off`,"oxc/no-rest-spread-properties":`off`,"promise/always-return":`error`,"promise/no-promise-in-callback":[`error`,{exemptDeclarations:!0}],"promise/no-return-wrap":`error`,"promise/param-names":`error`,"promise/prefer-await-to-then":`error`,"typescript/consistent-type-imports":[`error`,{disallowTypeAnnotations:!1}],"typescript/explicit-function-return-type":[`error`,{allowDirectConstAssertionInArrowFunctions:!0,allowExpressions:!0,allowHigherOrderFunctions:!0,allowIIFEs:!0,allowTypedFunctionExpressions:!0,allowedNames:[`handler`,`callback`]}],"typescript/no-explicit-any":`error`,"typescript/no-floating-promises":`error`,"typescript/no-unsafe-assignment":`error`,"typescript/no-unsafe-type-assertion":`error`,"typescript/no-non-null-assertion":`error`,"typescript/require-await":`error`,"typescript/prefer-readonly-parameter-types":`off`,"typescript/promise-function-async":[`error`,{checkArrowFunctions:!1}],"typescript/consistent-return":[`error`,{treatUndefinedAsUnspecified:!0}],"typescript/only-throw-error":`error`,"unicorn/no-array-reduce":[`error`,{allowSimpleOperations:!0}],"unicorn/no-null":`off`,"unicorn/no-useless-undefined":[`error`,{checkArguments:!1}],"react/set-state-in-effect":`off`,"react/no-deriving-state-in-effects":`off`,"react/exhaustive-effect-dependencies":`off`,"react/hooks":`off`,"react/refs":`off`,"react/todo":`off`,...Object.fromEntries(h.map(e=>[e,`off`]))},v=[`**/*.test.ts`,`**/*.test.tsx`],y=[`**/__tests__/**/*.spec.ts`,`**/test/**/*.spec.ts`],b={files:[...v,`**/test/**`,`**/__tests__/**`],rules:{"typescript/no-non-null-assertion":`off`,"typescript/no-unsafe-type-assertion":`off`,"typescript/no-explicit-any":`off`,"typescript/require-await":`off`,"eslint/max-statements":`off`,"eslint/max-lines":`off`,"eslint/max-lines-per-function":`off`,"node/no-top-level-await":`off`}},x=[`expect`,`expectTypeOf`,`assert`,`assertType`],S=/^[A-Za-z_$][\w$]*$/u,C={files:[...v,...y],rules:{...Object.fromEntries(h.filter(e=>!g.has(e)).map(e=>[e,`error`])),"vitest/expect-expect":[`error`,{assertFunctionNames:x}],"react/rules-of-hooks":`off`}};function w(e={}){let t=e.assertionHelpers??[],n=e.titlePrefixes??[],r=[...t,...n].filter(e=>!S.test(e));if(r.length>0)throw Error(`assertionHelpers / titlePrefixes 只收裸标识符,不接受通配或带点形式(前者等于让 vitest/expect-expect 失去作用):${r.join(`, `)}`);let i=[.../* @__PURE__ */ new Set([...x,...t])];return{...C,rules:{...C.rules,"vitest/expect-expect":[`error`,{assertFunctionNames:i}],...n.length===0?{}:{"vitest/prefer-lowercase-title":[`error`,{allowedPrefixes:[...new Set(n)]}]}}}}const T={files:[`scripts/**`,`*.mjs`,`*.mts`],rules:{"eslint/no-continue":`off`,"unicorn/max-nested-calls":`off`,"eslint/id-length":[`error`,{exceptions:[`a`,`i`,`t`,`x`,`y`,`z`,`n`,`p`,`s`,`v`,`e`,`f`,`ok`,`fs`,`id`]}],"unicorn/no-process-exit":`off`,"unicorn/no-useless-undefined":`off`,"typescript/no-non-null-assertion":`off`,"node/no-sync":`off`,"node/no-top-level-await":`off`,"typescript/no-unsafe-member-access":`off`,"typescript/no-unsafe-call":`off`,"typescript/no-unsafe-assignment":`off`,"typescript/no-unsafe-return":`off`,"typescript/no-unsafe-argument":`off`,"typescript/strict-boolean-expressions":`off`,"typescript/no-implied-eval":`off`,"typescript/require-array-sort-compare":`off`}};function E(e){let t={...i,...e};return e=>{let n=t[e];if(n!==void 0)return n;let r=e.indexOf(`/`);return r===-1?void 0:t[`${e.slice(0,r)}/*`]}}function D(e,t,n){if(t===void 0)return;let r=[];for(let[e,i]of Object.entries(t))if((Array.isArray(i)?i[0]:i)===`off`){let t=n(e);(t===void 0||t.note.trim().length===0)&&r.push(e)}if(r.length>0)throw Error(`${e} 里这些规则被关成 off 但没有依据(OFF_JUSTIFICATIONS 查不到,或 note 为空):${r.join(`, `)}。要么在代码里修,要么给它补一条 kind + measured + note。`)}function O(e={}){let{syncWrites:n=[],extraRules:r={},extraOverrides:i=[],extraIgnorePatterns:a=[],jsPlugins:c,offReasons:l={},assertionHelpers:h=[],titlePrefixes:g=[]}=e,v=E(l),y={..._,...d,...n.length>0?{"node/no-sync":[`error`,{ignores:[.../* @__PURE__ */ new Set([...o,...n])]}]}:{},...r},x=[b,w({assertionHelpers:h,titlePrefixes:g}),T,...i];D(`rules`,y,v);for(let[e,t]of x.entries())D(`overrides[${e}]`,t.rules,v);return t({plugins:s,env:{node:!0,es2024:!0,browser:!0},options:p,categories:f,ignorePatterns:[...m,...a],jsPlugins:u(c),rules:y,overrides:x})}export{_ as BASE_RULES,i as OFF_JUSTIFICATIONS,o as READONLY_SYNC_ALLOWLIST,a as RETIRED_OFFS,T as SCRIPTS_OVERRIDES,C as TEST_FILE_OVERRIDES,b as TEST_OVERRIDES,h as VITEST_ALL_RULES,x as VITEST_ASSERTION_DEFAULTS,O as definePluginConfig,w as makeTestFileOverrides,u as resolveJsPlugins};
1
+ import{createRequire as e}from"node:module";import{defineConfig as t}from"oxlint";const n=`同 no-unsafe-member-access`,r={"vitest/*":{kind:`仅限特定文件`,measured:7,note:`用例结构类判断在非用例文件上无从计算`},"vitest/no-hooks":{kind:`上游不推荐`,measured:181,note:`禁用 hook 本身;本仓按官方 vitest 用法大量使用 before*/after*`},"vitest/no-conditional-in-test":{kind:`仅限特定文件`,measured:523,note:`平台/宿主版本分支是刻意的(macOS 与 Linux 的家目录形态、0.1.7 与 0.1.6 的 API 差异)`},"vitest/max-expects":{kind:`上游不推荐`,measured:163,note:`给单条用例的断言条数设上限属风格偏好,不指向缺陷`},"vitest/prefer-expect-assertions":{kind:`上游不推荐`,measured:2902,note:`要求每个 async 用例手写 expect.assertions(n),是与断言强度无关的计数要求`},"vitest/require-test-timeout":{kind:`上游不推荐`,measured:2892,note:`要求每条用例显式 timeout;本仓用泵式假时钟,超时由 13 份 vitest 配置统一给出`},"vitest/prefer-importing-vitest-globals":{kind:`实测有害`,measured:0,note:`autofix 方向是删掉显式 import 改回裸全局;13 份 vitest 配置都没开 globals:true,应用后立即出现 3605 处 TS2593、测试全部失效`},"vitest/no-importing-vitest-globals":{kind:`实测有害`,measured:133,note:`与上一条同族、方向相反,两条同时开启就是把两种写法都判违规`},"vitest/prefer-import-in-mock":{kind:`实测有害`,measured:6,note:"动态形态按完整模块导出校验工厂返回值,而 @types/node 的 os、@types/react 都是 `export =` 形状、类型上没有 default;实测三处 TS2769+TS2740/2741,react 那处无论怎么展开都补不出 default"},"vitest/prefer-lowercase-title":{kind:`仅限特定文件`,measured:171,note:`只在用例文件里生效:它带 autofix,实测一轮 oxlint --fix 静默把 260 条标题改成了小写(含 nO_VERIFY 这种坏名字)。包级 titlePrefixes 只留真正的技术名(GET/CSRF/组件名…):非技术名(工单号一类)不该借这条豁免留在标题里,而中文开头的标题天然满足要求(无大小写),免检范围能收多小就收多小。实测 oxlint 前缀一命中就把整条标题免检,所以陈旧条目由配置基线自检按『免检范围未被使用』指出,不靠人工巡检`},"vitest/prefer-called-times":{kind:`与已启用规则互斥`,measured:0,note:`实测核对(同文件四种写法各跑一次):toHaveBeenCalledTimes(1) 被 prefer-called-once 判违规、toHaveBeenCalledOnce() 被本条判违规、expect(spy.mock.calls).toHaveLength(1) 被 prefer-to-have-been-called-times 判违规,只有 n>=2 能同时满足三条 ⇒ 三条同开等于「单次调用」这条断言无法书写(实测逼得消费方改用 assert.equal(spy.mock.calls.length, 1) 绕行)。保留 prefer-called-once 与 prefer-to-have-been-called-times 两条方向一致的组合,关闭本条`},"vitest/prefer-describe-function-title":{kind:`与已启用规则互斥`,measured:48,note:"要求 `describe(fn)`,而 valid-title 要求字符串字面量(oxlint 1.85 的 valid-title 是 DummyRule、无任何选项);上游 recommended 收的是 valid-title,取它,26 处 `describe(fn)` 按代码改成同名字符串"},"vitest/prefer-to-be-truthy":{kind:`实测有害`,measured:117,note:`把 toStrictEqual(true) 放宽成 toTruthy(),接受任意真值是削弱断言`},"vitest/prefer-to-be-falsy":{kind:`实测有害`,measured:89,note:`同族,放宽成 toFalsy() 同样是削弱断言`},"regexp/no-super-linear-backtracking":{kind:`仅限特定文件`,measured:23,note:`测试夹具里的宽正则只跑短输入,ReDoS 不构成运行风险;生产侧 2 处与脚本侧 1 处保持开启并按代码修`},"e18e/prefer-static-regex":{kind:`仅限特定文件`,measured:1778,note:`只对生产代码开(PROD_OVERRIDES):生产 78 处按代码提到模块作用域;测试 1672 处与脚本 28 处里的内联正则不构成性能问题`},"e18e/ban-dependencies":{kind:`仅限特定文件`,measured:1,note:`唯一命中是消费方脚本对 js-yaml 的 mark.snippet 的依赖,换 yaml 不是等价替换;脚本面关闭,生产代码仍生效`},"import/no-default-export":{kind:`与官方形态冲突`,measured:38,note:`官方 cordis 入口就是 default export(vendor/loader/src/index.ts 的 unwrapExports 先 unwrap default),12 条正是 12 个 bundle 入口`},"import/no-named-export":{kind:`与官方形态冲突`,measured:895,note:`13 个包与上游 deepseek-harness 一样以具名导出 + 子路径说明符对外提供接口`},"import/prefer-default-export":{kind:`与官方形态冲突`,measured:58,note:`与 no-default-export 同族、方向与 cordis 入口相反`},"import/no-nodejs-modules":{kind:`与官方形态冲突`,measured:448,note:"宿主侧就运行在 node 上,`node:` 内置模块是运行环境本身而不是越界"},"import/no-named-as-default-member":{kind:`与官方形态冲突`,measured:47,note:`对具名导出的默认对象做成员访问是本仓消费官方声明的常态`},"import/no-anonymous-default-export":{kind:`与官方形态冲突`,measured:12,note:`官方 cordis 入口是匿名对象 default export(registry.ts 的 RegistryService.plugin() 读 name/Config,isApplicable() 判 apply),改成具名等于改官方形态`},"eslint/no-duplicate-imports":{kind:`实测有害`,measured:166,note:`ESLint 官方已废弃(不区分 import type),由 import/no-duplicates 取代`},"import/group-exports":{kind:`上游不推荐`,measured:766,note:`要求单点导出,与具名导出的接口形态冲突`},"import/exports-last":{kind:`上游不推荐`,measured:528,note:`要求导出语句在文件末尾`},"import/no-relative-parent-imports":{kind:`上游不推荐`,measured:277,note:`包内 lib/ 与 test/ 的相对父级引用是既有结构`},"import/max-dependencies":{kind:`上游不推荐`,measured:25,note:`给 import 条数设上限;宿主侧消费官方服务天然依赖多`},"node/no-process-env":{kind:`上游不推荐`,measured:51,note:`环境变量是本仓的输入来源(DSH_HOME、profile 目录、构建期开关);eslint-plugin-n 的 recommended 也不含本条`},"eslint/no-magic-numbers":{kind:`上游不推荐`,measured:5774,note:`把 HTTP 状态码/毫秒阈值抽成常量的计数规则`},"eslint/sort-keys":{kind:`上游不推荐`,measured:4101,note:`对象键排序;成对字面量与 locale 字典的可读性会一起受损`},"eslint/sort-imports":{kind:`上游不推荐`,measured:675,note:`字母序 import,与 oxfmt 的分组形态冲突`},"eslint/capitalized-comments":{kind:`上游不推荐`,measured:2388,note:`本仓注释以中文为主,「首字母大写」对中文注释无从判定`},"eslint/no-ternary":{kind:`上游不推荐`,measured:1024,note:`禁三元;改写成 if/else 并不会更正确`},"eslint/no-undefined":{kind:`上游不推荐`,measured:2009,note:"禁 `undefined` 字面量,与本仓大量显式可选返回形态冲突"},"eslint/no-void":{kind:`上游不推荐`,measured:172,note:"禁 `void` 前缀,而 `void promise` 正是满足 no-floating-promises 的官方写法"},"unicorn/no-null":{kind:`上游不推荐`,measured:788,note:`禁 null;官方声明与 JSON 边界大量用 null,改成 undefined 会改变序列化形态`},"typescript/prefer-readonly-parameter-types":{kind:`上游不推荐`,measured:2433,note:`要求每个入参类型全 readonly;官方声明(SessionEvent/ConfigForm 等)是可变的,改不动也不该改`},"oxc/no-async-await":{kind:`与已启用规则互斥`,measured:2160,note:`禁 async/await,而同表里 promise-function-async、require-await、no-floating-promises 都在要求用 async`},"oxc/no-optional-chaining":{kind:`与已启用规则互斥`,measured:1354,note:"禁 `?.`,而 typescript/prefer-optional-chain 正在要求把 && 链改成 `?.`"},"oxc/no-rest-spread-properties":{kind:`与已启用规则互斥`,measured:550,note:`禁对象 spread,而 ES2024 目标与官方声明的投影写法都靠它`},"typescript/no-explicit-any":{kind:`仅限特定文件`,measured:1,note:`用例里的桩件面(mini-react 之类)需要 any 才塞得进官方签名`},"typescript/no-non-null-assertion":{kind:`仅限特定文件`,measured:536,note:"用例里 `!` 是「前驱条件已成立」的最短写法"},"typescript/no-unsafe-type-assertion":{kind:`仅限特定文件`,measured:844,note:"桩件需要 `as never` / `as unknown as` 跨官方名义类型"},"typescript/require-await":{kind:`仅限特定文件`,measured:467,note:`官方 async 契约的同步实现与测试替身撑不住这条;prod 侧的条数按代码修,不进豁免`},"eslint/require-await":{kind:`与官方形态冲突`,measured:481,note:`它与 typescript/require-await 是同一条判断的两份实现,不是两条:实测核对(一段无 await 的 async 函数、--type-aware)单开各自都报、同开报两次,即 2 errors / 1 defect。按 typescript-eslint 官方对 extension rule 的处理方式裁决:留带类型信息的扩展那条、关基线那条。覆盖范围没有因此变小:全仓逐行去重实测 514 行两点同报、typescript 独有 0 行、eslint 独有只落在用例范围与配置自身的中间态;而 --type-aware 实测对 .mjs 也会由扩展那条报出,所以 scripts/*.mjs 面不需要基线那条补位。用例文件由 TEST_OVERRIDES 整族豁免`},"eslint/max-statements":{kind:`仅限特定文件`,measured:549,note:`用例体天然长(given/when/then 展开)`},"eslint/max-lines-per-function":{kind:`仅限特定文件`,measured:365,note:`同上`},"eslint/max-lines":{kind:`仅限特定文件`,measured:54,note:`单个用例文件的长度不作为缺陷信号`},"node/no-sync":{kind:`仅限特定文件`,measured:299,note:`构建脚本与门禁脚本按设计同步读;写侧豁免按文件生效(syncIoScopes),不在这一层的豁免里`},"node/no-top-level-await":{kind:`仅限特定文件`,measured:53,note:`脚本以 node 直接执行,顶层 await 是 ESM 常态;prod 侧 0 条`},"typescript/strict-boolean-expressions":{kind:`仅限特定文件`,measured:0,note:`scripts/*.mjs 无类型面,隐式 any 上无从判定`},"typescript/no-implied-eval":{kind:`仅限特定文件`,measured:0,note:`同上:这条要类型信息才能判`},"typescript/no-unsafe-member-access":{kind:`仅限特定文件`,measured:0,note:`无类型标注的脚本里每个值都落进隐式 any`},"typescript/no-unsafe-call":{kind:`仅限特定文件`,measured:0,note:n},"typescript/no-unsafe-assignment":{kind:`仅限特定文件`,measured:0,note:n},"typescript/no-unsafe-return":{kind:`仅限特定文件`,measured:0,note:n},"typescript/no-unsafe-argument":{kind:`仅限特定文件`,measured:0,note:n},"typescript/require-array-sort-compare":{kind:`仅限特定文件`,measured:0,note:`无类型脚本里「比较器缺失」判不出来`},"unicorn/no-process-exit":{kind:`仅限特定文件`,measured:2,note:`CLI 脚本的 process.exit 就是它的返回方式`},"eslint/no-continue":{kind:`仅限特定文件`,measured:53,note:`只对 scripts/ 豁免:TS 侧实测 0 条(包代码本来就不用 continue),908 行的门禁脚本改成嵌套 if 只会更难读`},"unicorn/max-nested-calls":{kind:`仅限特定文件`,measured:59,note:`只对 scripts/ 豁免:TS 侧实测 0 条;CLI 脚本里链式 map/filter/join 的深度由数据形状决定,不指向缺陷`},"unicorn/no-useless-undefined":{kind:`仅限特定文件`,measured:0,note:`脚本侧同上调;prod 仍按配置项 checkArguments:false 生效`},"react/set-state-in-effect":{kind:`与官方形态冲突`,measured:16,note:`卡片用 effect 同步宿主态到 store 是挂载适配器形态;Compiler 建议的 render 期推导要改写 12 份适配器的状态流`},"react/no-deriving-state-in-effects":{kind:`与官方形态冲突`,measured:7,note:`适配器形态里「props/state 派生值在 effect 对齐宿主」是挂载动作,不是可下沉到 render 的纯派生`},"react/exhaustive-effect-dependencies":{kind:`与官方形态冲突`,measured:8,note:`Compiler 版依赖审计对适配器的内联闭包全量追问;与 react-hooks/exhaustive-deps(保留并已修)形成双门,留后者`},"react/hooks":{kind:`与官方形态冲突`,measured:13,note:`「同一 hook 函数跨渲染同引用」在适配器的内联闭包形态下整体不成立;收敛批次把闭包提升后再回来开`},"react/refs":{kind:`与官方形态冲突`,measured:1,note:`挂载适配器在 render 期读 ref 对齐宿主布局;收敛批次改成挂载后读`},"react/todo":{kind:`上游不推荐`,measured:6,note:`报的是 React 编译器 BuildHIR 自己不支持 try/catch(-finally) 的上游 TODO,不是代码缺陷`},"react/rules-of-hooks":{kind:`仅限特定文件`,measured:3,note:`只对用例文件豁免(TEST_FILE_OVERRIDES):测试架在组件树外直接驱动卡片适配 hook(useCardOf / useSyncExternalStore 桩),调用次数顺序由桩件固定。键用表载前缀 react/(诊断文案显示 react-hooks(...) 是显示名,配置键以 --rules 的 Source 列为准)`}},i=[`typescript/no-empty-object-type`],a=[`existsSync`,`readFileSync`,`readdirSync`,`readSync`,`realpathSync`,`statSync`],o=[`eslint`,`typescript`,`unicorn`,`oxc`,`node`,`promise`,`vitest`,`import`,`react`,`jsx-a11y`],s={label:`sonarjs`,packageName:`eslint-plugin-sonarjs`,extraCandidates:[`eslint-plugin-sonarjs/cjs/plugin.js`]},c=[s,{label:`regexp`,packageName:`eslint-plugin-regexp`},{label:`zod`,packageName:`eslint-plugin-zod`},{label:`e18e`,packageName:`@e18e/eslint-plugin`}];function l(e,t){return(typeof e==`string`?e:`${e.name} ${e.specifier}`).includes(t.packageName)}function u(t){let n=e(import.meta.url),r=[];for(let e of[t.packageName,...t.extraCandidates??[]])try{return n.resolve(e)}catch{r.push(e)}throw Error(`oxlint 基线要挂 ${t.packageName},但从本包解析不到它(试过 ${r.join(`、`)})。要么在本包 devDependencies 里装 ${t.packageName},要么把你自己解析到的 ${t.label} 入口经 definePluginConfig({ jsPlugins: [...] }) 传进来(入口路径须指向 ${t.packageName},换成别家的插件无法满足这条规则表)。`)}function d(e,t=()=>u(s)){let n=e??[],r=null;try{r=t()}catch(e){if(!n.some(e=>l(e,s)))throw e}return r===null?[...n]:[r,...n.filter(e=>e!==r)]}function f(e,t={}){let n=d(e);for(let r of c.filter(e=>e!==s))if(!(e??[]).some(e=>l(e,r))){let e=(t[r.label]??(()=>u(r)))();n=[e,...n.filter(t=>t!==e)]}return n}const p={"sonarjs/no-identical-functions":`error`,"sonarjs/no-duplicated-branches":`error`,"sonarjs/no-all-duplicated-branches":`error`,"sonarjs/no-collapsible-if":`error`,"sonarjs/no-identical-conditions":`error`,"sonarjs/no-identical-expressions":`error`,"sonarjs/no-duplicate-string":`error`,"sonarjs/cognitive-complexity":`error`,"sonarjs/max-switch-cases":`error`,"sonarjs/no-inverted-boolean-check":`error`,"sonarjs/no-duplicate-in-composite":`error`,"sonarjs/duplicates-in-character-class":`error`,"sonarjs/no-useless-catch":`error`,"sonarjs/no-extra-arguments":`error`,"sonarjs/no-redundant-assignments":`error`,"sonarjs/prefer-single-boolean-return":`error`,"sonarjs/label-position":`error`},m={"regexp/confusing-quantifier":`error`,"regexp/control-character-escape":`error`,"regexp/match-any":`error`,"regexp/negation":`error`,"regexp/no-contradiction-with-assertion":`error`,"regexp/no-dupe-characters-character-class":`error`,"regexp/no-dupe-disjunctions":`error`,"regexp/no-empty-alternative":`error`,"regexp/no-empty-capturing-group":`error`,"regexp/no-empty-character-class":`error`,"regexp/no-empty-group":`error`,"regexp/no-empty-lookarounds-assertion":`error`,"regexp/no-empty-string-literal":`error`,"regexp/no-escape-backspace":`error`,"regexp/no-extra-lookaround-assertions":`error`,"regexp/no-invalid-regexp":`error`,"regexp/no-invisible-character":`error`,"regexp/no-lazy-ends":`error`,"regexp/no-legacy-features":`error`,"regexp/no-misleading-capturing-group":`error`,"regexp/no-misleading-unicode-character":`error`,"regexp/no-missing-g-flag":`error`,"regexp/no-non-standard-flag":`error`,"regexp/no-obscure-range":[`error`,{allowed:[`alphanumeric`,`一-鿿`,`一-龥`]}],"regexp/no-optional-assertion":`error`,"regexp/no-potentially-useless-backreference":`error`,"regexp/no-super-linear-backtracking":`error`,"regexp/no-trivially-nested-assertion":`error`,"regexp/no-trivially-nested-quantifier":`error`,"regexp/no-unused-capturing-group":`error`,"regexp/no-useless-assertions":`error`,"regexp/no-useless-backreference":`error`,"regexp/no-useless-character-class":`error`,"regexp/no-useless-dollar-replacements":`error`,"regexp/no-useless-escape":`error`,"regexp/no-useless-flag":`error`,"regexp/no-useless-lazy":`error`,"regexp/no-useless-non-capturing-group":`error`,"regexp/no-useless-quantifier":`error`,"regexp/no-useless-range":`error`,"regexp/no-useless-set-operand":`error`,"regexp/no-useless-string-literal":`error`,"regexp/no-useless-two-nums-quantifier":`error`,"regexp/no-zero-quantifier":`error`,"regexp/optimal-lookaround-quantifier":`error`,"regexp/optimal-quantifier-concatenation":`error`,"regexp/prefer-character-class":`error`,"regexp/prefer-d":`error`,"regexp/prefer-plus-quantifier":`error`,"regexp/prefer-predefined-assertion":`error`,"regexp/prefer-question-quantifier":`error`,"regexp/prefer-range":`error`,"regexp/prefer-set-operation":`error`,"regexp/prefer-star-quantifier":`error`,"regexp/prefer-unicode-codepoint-escapes":`error`,"regexp/prefer-w":`error`,"regexp/simplify-set-operations":`error`,"regexp/sort-flags":`error`,"regexp/strict":`error`,"regexp/use-ignore-case":`error`},h={"zod/consistent-import":[`error`,{syntax:`named`}],"zod/no-any-schema":`error`,"zod/no-coerce-boolean":`error`,"zod/no-conflicting-checks":`error`,"zod/no-duplicate-schema-methods":`error`,"zod/no-empty-custom-schema":`error`,"zod/no-native-enum":`error`,"zod/no-number-schema-with-finite":`error`,"zod/no-number-schema-with-int":`error`,"zod/no-number-schema-with-is-finite":`error`,"zod/no-number-schema-with-is-int":`error`,"zod/no-number-schema-with-safe":`error`,"zod/no-number-schema-with-step":`error`,"zod/no-optional-and-default-together":`error`,"zod/no-promise-schema":`error`,"zod/no-schema-with-is-nullable":`error`,"zod/no-schema-with-is-optional":`error`,"zod/no-throw-in-refine":`error`,"zod/no-transform-in-record-key":`error`,"zod/prefer-top-level-string-formats":`error`,"zod/require-brand-type-parameter":`error`,"zod/require-error-message":`error`},g={"e18e/ban-dependencies":`error`,"e18e/prefer-array-fill":`error`,"e18e/prefer-array-from-map":`error`,"e18e/prefer-array-to-reversed":`error`,"e18e/prefer-array-to-sorted":`error`,"e18e/prefer-array-to-spliced":`error`,"e18e/prefer-static-regex":`off`,"e18e/prefer-timer-args":`error`,"e18e/prefer-url-canparse":`error`},_={correctness:`error`,suspicious:`error`,style:`error`,perf:`error`,restriction:`error`,pedantic:`error`,nursery:`error`},v={typeAware:!0,typeCheck:!0,denyWarnings:!0,maxWarnings:0,reportUnusedDisableDirectives:`error`,respectEslintDisableDirectives:!1},y=[`**/node_modules/**`,`.toolchain/**`,`coverage/**`,`dist/**`,`client.js`,`host.js`],b=/* @__PURE__ */ `vitest/consistent-each-for.vitest/consistent-test-filename.vitest/consistent-test-it.vitest/consistent-vitest-vi.vitest/expect-expect.vitest/hoisted-apis-on-top.vitest/max-expects.vitest/max-nested-describe.vitest/no-alias-methods.vitest/no-commented-out-tests.vitest/no-conditional-expect.vitest/no-conditional-in-test.vitest/no-conditional-tests.vitest/no-disabled-tests.vitest/no-duplicate-hooks.vitest/no-focused-tests.vitest/no-hooks.vitest/no-identical-title.vitest/no-import-node-test.vitest/no-importing-vitest-globals.vitest/no-interpolation-in-snapshots.vitest/no-large-snapshots.vitest/no-mocks-import.vitest/no-restricted-matchers.vitest/no-restricted-vi-methods.vitest/no-standalone-expect.vitest/no-test-prefixes.vitest/no-test-return-statement.vitest/no-unneeded-async-expect-function.vitest/padding-around-after-all-blocks.vitest/padding-around-test-blocks.vitest/prefer-called-exactly-once-with.vitest/prefer-called-once.vitest/prefer-called-times.vitest/prefer-called-with.vitest/prefer-comparison-matcher.vitest/prefer-describe-function-title.vitest/prefer-each.vitest/prefer-equality-matcher.vitest/prefer-expect-assertions.vitest/prefer-expect-resolves.vitest/prefer-expect-type-of.vitest/prefer-hooks-in-order.vitest/prefer-hooks-on-top.vitest/prefer-import-in-mock.vitest/prefer-importing-vitest-globals.vitest/prefer-lowercase-title.vitest/prefer-mock-promise-shorthand.vitest/prefer-mock-return-shorthand.vitest/prefer-snapshot-hint.vitest/prefer-spy-on.vitest/prefer-strict-boolean-matchers.vitest/prefer-strict-equal.vitest/prefer-to-be.vitest/prefer-to-be-falsy.vitest/prefer-to-be-object.vitest/prefer-to-be-truthy.vitest/prefer-to-contain.vitest/prefer-to-have-been-called-times.vitest/prefer-to-have-length.vitest/prefer-todo.vitest/require-awaited-expect-poll.vitest/require-hook.vitest/require-local-test-context-for-concurrent-snapshots.vitest/require-mock-type-parameters.vitest/require-test-timeout.vitest/require-to-throw-message.vitest/require-top-level-describe.vitest/valid-describe-callback.vitest/valid-expect.vitest/valid-expect-in-promise.vitest/valid-title.vitest/warn-todo`.split(`.`),x=/* @__PURE__ */ new Set([`vitest/max-expects`,`vitest/no-conditional-in-test`,`vitest/no-hooks`,`vitest/no-importing-vitest-globals`,`vitest/prefer-called-times`,`vitest/prefer-describe-function-title`,`vitest/prefer-expect-assertions`,`vitest/prefer-import-in-mock`,`vitest/prefer-importing-vitest-globals`,`vitest/prefer-to-be-falsy`,`vitest/prefer-to-be-truthy`,`vitest/require-test-timeout`]),S={"eslint/one-var":[`error`,`never`],"eslint/func-style":[`error`,`declaration`,{allowArrowFunctions:!0,overrides:{namedExports:`ignore`}}],"eslint/id-length":[`error`,{exceptions:[`a`,`i`,`t`,`x`,`y`,`z`]}],"eslint/init-declarations":[`error`,`never`,{ignoreForLoopInit:!0}],"eslint/max-lines":[`error`,{max:2e3,skipBlankLines:!0,skipComments:!0}],"eslint/max-lines-per-function":[`error`,{max:120,skipBlankLines:!0,skipComments:!0}],"eslint/complexity":`error`,"eslint/max-params":[`error`,{max:5}],"eslint/max-statements":[`error`,50],"eslint/no-console":[`error`,{allow:[`warn`,`error`,`info`]}],"eslint/no-warning-comments":[`error`,{terms:[`todo`,`fixme`,`xxx`,`hack`],location:`start`}],"eslint/no-param-reassign":`error`,"eslint/no-underscore-dangle":[`error`,{allow:[`__memoryInsightCardApplied`,`__memoryTdaiCardApplied`,`__sessionRescueApplied`,`__wukilDevToolsCardApplied`]}],"eslint/no-magic-numbers":`off`,"eslint/sort-keys":`off`,"eslint/sort-imports":`off`,"eslint/capitalized-comments":`off`,"eslint/no-ternary":`off`,"eslint/no-undefined":`off`,"eslint/no-void":`off`,"eslint/no-duplicate-imports":`off`,"eslint/require-await":`off`,"import/no-named-export":`off`,"import/group-exports":`off`,"import/exports-last":`off`,"import/prefer-default-export":`off`,"import/no-nodejs-modules":`off`,"import/no-relative-parent-imports":`off`,"import/max-dependencies":`off`,"import/no-default-export":`off`,"import/no-anonymous-default-export":`off`,"import/no-named-as-default-member":`off`,"import/consistent-type-specifier-style":[`error`,`prefer-top-level`],"import/no-duplicates":`error`,"import/no-cycle":`error`,"import/named":`error`,"import/first":`error`,"import/newline-after-import":`error`,"node/no-process-env":`off`,"node/no-sync":[`error`,{ignores:a}],"node/no-top-level-await":`error`,"oxc/no-async-await":`off`,"oxc/no-optional-chaining":`off`,"oxc/no-rest-spread-properties":`off`,"promise/always-return":`error`,"promise/no-promise-in-callback":[`error`,{exemptDeclarations:!0}],"promise/no-return-wrap":`error`,"promise/param-names":`error`,"promise/prefer-await-to-then":`error`,"typescript/consistent-type-imports":[`error`,{disallowTypeAnnotations:!1}],"typescript/explicit-function-return-type":[`error`,{allowDirectConstAssertionInArrowFunctions:!0,allowExpressions:!0,allowHigherOrderFunctions:!0,allowIIFEs:!0,allowTypedFunctionExpressions:!0,allowedNames:[`handler`,`callback`]}],"typescript/no-explicit-any":`error`,"typescript/no-floating-promises":`error`,"typescript/no-unsafe-assignment":`error`,"typescript/no-unsafe-type-assertion":`error`,"typescript/no-non-null-assertion":`error`,"typescript/require-await":`error`,"typescript/prefer-readonly-parameter-types":`off`,"typescript/promise-function-async":[`error`,{checkArrowFunctions:!1}],"typescript/consistent-return":[`error`,{treatUndefinedAsUnspecified:!0}],"typescript/only-throw-error":`error`,"unicorn/no-array-reduce":[`error`,{allowSimpleOperations:!0}],"unicorn/no-null":`off`,"unicorn/no-useless-undefined":[`error`,{checkArguments:!1}],"react/set-state-in-effect":`off`,"react/no-deriving-state-in-effects":`off`,"react/exhaustive-effect-dependencies":`off`,"react/hooks":`off`,"react/refs":`off`,"react/todo":`off`,...m,...h,...g,...Object.fromEntries(b.map(e=>[e,`off`]))},C=[`**/*.test.ts`,`**/*.test.tsx`],w=[`**/__tests__/**/*.spec.ts`,`**/test/**/*.spec.ts`],T={files:[...C,`**/test/**`,`**/__tests__/**`],rules:{"typescript/no-non-null-assertion":`off`,"typescript/no-unsafe-type-assertion":`off`,"typescript/no-explicit-any":`off`,"typescript/require-await":`off`,"eslint/max-statements":`off`,"eslint/max-lines":`off`,"eslint/max-lines-per-function":`off`,"node/no-top-level-await":`off`,"regexp/no-super-linear-backtracking":`off`}},E={files:[`lib/**`,`src/**`,`host.ts`,`cli-main.ts`],rules:{"e18e/prefer-static-regex":`error`}},D=[`expect`,`expectTypeOf`,`assert`,`assertType`],O=/^[A-Za-z_$][\w$]*$/u,k={files:[...C,...w],rules:{...Object.fromEntries(b.filter(e=>!x.has(e)).map(e=>[e,`error`])),"vitest/expect-expect":[`error`,{assertFunctionNames:D}],"react/rules-of-hooks":`off`}};function A(e={}){let t=e.assertionHelpers??[],n=e.titlePrefixes??[],r=[...t,...n].filter(e=>!O.test(e));if(r.length>0)throw Error(`assertionHelpers / titlePrefixes 只收裸标识符,不接受通配或带点形式(前者等于让 vitest/expect-expect 失去作用):${r.join(`, `)}`);let i=[.../* @__PURE__ */ new Set([...D,...t])];return{...k,rules:{...k.rules,"vitest/expect-expect":[`error`,{assertFunctionNames:i}],...n.length===0?{}:{"vitest/prefer-lowercase-title":[`error`,{allowedPrefixes:[...new Set(n)]}]}}}}const j={files:[`scripts/**`,`*.mjs`,`*.mts`],rules:{"eslint/no-continue":`off`,"unicorn/max-nested-calls":`off`,"eslint/id-length":[`error`,{exceptions:[`a`,`i`,`t`,`x`,`y`,`z`,`n`,`p`,`s`,`v`,`e`,`f`,`ok`,`fs`,`id`]}],"unicorn/no-process-exit":`off`,"unicorn/no-useless-undefined":`off`,"typescript/no-non-null-assertion":`off`,"node/no-sync":`off`,"node/no-top-level-await":`off`,"e18e/ban-dependencies":`off`,"typescript/no-unsafe-member-access":`off`,"typescript/no-unsafe-call":`off`,"typescript/no-unsafe-assignment":`off`,"typescript/no-unsafe-return":`off`,"typescript/no-unsafe-argument":`off`,"typescript/strict-boolean-expressions":`off`,"typescript/no-implied-eval":`off`,"typescript/require-array-sort-compare":`off`}};function M(e){let t={...r,...e};return e=>{let n=t[e];if(n!==void 0)return n;let r=e.indexOf(`/`);return r===-1?void 0:t[`${e.slice(0,r)}/*`]}}function N(e,t,n){if(t===void 0)return;let r=[];for(let[e,i]of Object.entries(t))if((Array.isArray(i)?i[0]:i)===`off`){let t=n(e);(t===void 0||t.note.trim().length===0)&&r.push(e)}if(r.length>0)throw Error(`${e} 里这些规则被关成 off 但没有依据(OFF_JUSTIFICATIONS 查不到,或 note 为空):${r.join(`, `)}。要么在代码里修,要么给它补一条 kind + measured + note。`)}function P(e={}){let{syncIoScopes:n=[],extraRules:r={},extraOverrides:i=[],extraIgnorePatterns:s=[],jsPlugins:c,offReasons:l={},assertionHelpers:u=[],titlePrefixes:d=[]}=e,m=M(l),h={...S,...p,...r},g=n.map((e,t)=>{if(e.files.length===0||e.apis.length===0)throw Error(`syncIoScopes[${String(t)}] 要 files 与 apis 同时非空,否则这条 override 是死配置。`);return{files:[...e.files],rules:{"node/no-sync":[`error`,{ignores:[.../* @__PURE__ */ new Set([...a,...e.apis])]}]}}}),b=[T,A({assertionHelpers:u,titlePrefixes:d}),j,E,...g,...i];N(`rules`,h,m);for(let[e,t]of b.entries())N(`overrides[${e}]`,t.rules,m);return t({plugins:o,env:{node:!0,es2024:!0,browser:!0},options:v,categories:_,ignorePatterns:[...y,...s],jsPlugins:f(c),rules:h,overrides:b})}export{S as BASE_RULES,g as E18E_RULES,r as OFF_JUSTIFICATIONS,E as PROD_OVERRIDES,a as READONLY_SYNC_ALLOWLIST,m as REGEXP_RULES,i as RETIRED_OFFS,j as SCRIPTS_OVERRIDES,k as TEST_FILE_OVERRIDES,T as TEST_OVERRIDES,b as VITEST_ALL_RULES,D as VITEST_ASSERTION_DEFAULTS,h as ZOD_RULES,P as definePluginConfig,A as makeTestFileOverrides,f as resolveBundledJsPlugins,d as resolveJsPlugins};
package/dist/jsonl.js CHANGED
@@ -1,3 +1,3 @@
1
- function e(e,t){let n=e;for(;Buffer.byteLength(n,`utf8`)>t;){let e=n.split(`
2
- `);if(e.length<=2)break;n=e.slice(Math.floor(e.length/2)).join(`
3
- `).replace(/^(?:\r?\n)+/u,``)}return n}export{e as shrinkJsonlTail};
1
+ const e=/^(?:\r?\n)+/u;function t(t,n){let r=t;for(;Buffer.byteLength(r,`utf8`)>n;){let t=r.split(`
2
+ `);if(t.length<=2)break;r=t.slice(Math.floor(t.length/2)).join(`
3
+ `).replace(e,``)}return r}export{t as shrinkJsonlTail};
@@ -1 +1 @@
1
- import{realpathSync as e}from"node:fs";import{createHash as t}from"node:crypto";import n from"node:path";const r=/[/\\]+/gu,i=/\/{2,}/gu;function a(e){return e===`\\`?r:i}const o=/^[A-Za-z]:$/u;function s(e){if(!e.startsWith(String.raw`\\`))return!1;let t=e.replace(/^\\\\[?.]\\(?:UNC\\)?/u,``).replace(/^\\\\/u,``);return/^[^\\]+\\[^\\]+$/u.test(t)}function c(r,i){if(typeof r!=`string`)return`default`;let c=r.trim();if(c===``)return`default`;let l=i?.realpath??e,u=i?.sep??n.sep,d=(u===`\\`?n.win32:n.posix).resolve(c);try{d=l(d)}catch{}let f=d.replace(/[/\\]+$/u,``),p=d.replaceAll(a(u),`/`).replace(/\/+$/u,``);return p===``||o.test(p)||s(f)?`default`:`${p.slice(p.lastIndexOf(`/`)+1).replaceAll(/\s+/gu,`_`)}-${t(`sha256`).update(p,`utf8`).digest(`hex`).slice(0,8)}`}export{c as deriveProjectKey};
1
+ import{realpathSync as e}from"node:fs";import{createHash as t}from"node:crypto";import n from"node:path";const r=/[/\\]+/gu,i=/\/{2,}/gu;function a(e){return e===`\\`?r:i}const o=/^[A-Za-z]:$/u,s=/^\\\\[?.]\\(?:UNC\\)?/u,c=/^\\\\/u,l=/^[^\\]+\\[^\\]+$/u,u=/[/\\]+$/u,d=/\/+$/u;function f(e){if(!e.startsWith(String.raw`\\`))return!1;let t=e.replace(s,``).replace(c,``);return l.test(t)}function p(r,i){if(typeof r!=`string`)return`default`;let s=r.trim();if(s===``)return`default`;let c=i?.realpath??e,l=i?.sep??n.sep,p=(l===`\\`?n.win32:n.posix).resolve(s);try{p=c(p)}catch{}let m=p.replace(u,``),h=p.replaceAll(a(l),`/`).replace(d,``);return h===``||o.test(h)||f(m)?`default`:`${h.slice(h.lastIndexOf(`/`)+1).replaceAll(/\s+/gu,`_`)}-${t(`sha256`).update(h,`utf8`).digest(`hex`).slice(0,8)}`}export{p as deriveProjectKey};
@@ -46,9 +46,9 @@ export declare const OFF_JUSTIFICATIONS: Record<string, OffJustification>;
46
46
  export declare const RETIRED_OFFS: string[];
47
47
  export declare const READONLY_SYNC_ALLOWLIST: string[];
48
48
  /**
49
- * 组装 `jsPlugins`:本包解析到的 sonarjs 入口在前,调用方自带的在后并去重。
49
+ * 组装 sonarjs 的入口:本包解析到的入口在前,调用方自带的在后并去重。
50
50
  *
51
- * 为什么要有这一层而不是直接 `[resolveSonarjsEntry(), ...(jsPlugins ?? [])]`:
51
+ * 为什么要有这一层而不是直接 `[resolvePluginEntry(SONARJS_PLUGIN), ...(jsPlugins ?? [])]`:
52
52
  * 那写法无条件先求值,于是「调用方自带入口」这条官方预留的退路在缺 sonarjs 时**用不了**——
53
53
  * 抛错发生在调用方的入口被用上之前,报错信息给的补救办法照做必然再报同一条。
54
54
  * 分支规则:
@@ -70,6 +70,77 @@ export declare const READONLY_SYNC_ALLOWLIST: string[];
70
70
  * @returns 交给 oxlint 的 jsPlugins 数组
71
71
  */
72
72
  export declare function resolveJsPlugins(callerPlugins: NonNullable<OxlintConfig["jsPlugins"]> | undefined, resolve?: () => string): NonNullable<OxlintConfig["jsPlugins"]>;
73
+ /**
74
+ * 组装全部常挂插件的 `jsPlugins`:sonarjs 走 {@link resolveJsPlugins} 的退路语义,
75
+ * 其余三条在调用方没有自带入口时从本包解析并前置。
76
+ *
77
+ * 调用方自带入口的判定按路径/别名子串(`pointsAtPlugin`):这是能落进字符串判断的全部信号。
78
+ * 自带入口时**不再解析**本包那一份,避免同一个插件被挂两遍(两份插件对象会让同一条规则报两次)。
79
+ *
80
+ * 导出并把解析器做成可注入,是为了缺包分支在本仓可测:本仓 devDependencies 装着这四个包,
81
+ * 真实解析永远成功,失败支只能靠 stub 走到。
82
+ *
83
+ * @param callerPlugins - 调用方经 `jsPlugins` 传入的入口
84
+ * @param resolvers - 按插件 label 注入的入口解析器(生产用默认实现,测试注桩)
85
+ * @returns 交给 oxlint 的 jsPlugins 数组
86
+ */
87
+ export declare function resolveBundledJsPlugins(callerPlugins: NonNullable<OxlintConfig["jsPlugins"]> | undefined, resolvers?: Record<string, () => string>): NonNullable<OxlintConfig["jsPlugins"]>;
88
+ /**
89
+ * `eslint-plugin-regexp` recommended 里带 `regexp/` 前缀的全部规则(63 条)。
90
+ *
91
+ * 不带它同 preset 的 4 条裸 eslint 规则(no-control-regex / no-misleading-character-class /
92
+ * no-regex-spaces / prefer-regex-literals):oxlint 的内建 eslint 插件已有同名规则,重复挂只会
93
+ * 把同一个缺陷报两遍。
94
+ *
95
+ * 实测(oxlint 1.90、9 包,含既有代码):95 条命中,prod 34 / test 56 / 其余 5;其中 56 条所属
96
+ * 规则带 fixer(实测 oxlint 会执行 JS plugin 的 fixer,用 prefer-w 验过)。剩余非 fixer 的
97
+ * `no-super-linear-backtracking` 在测试文件里由 TEST_OVERRIDES 收窄(理由见 OFF_JUSTIFICATIONS)。
98
+ *
99
+ * `no-obscure-range` 允许本仓刻意使用的两个 CJK 范围:把 `[一-鿿]` 改写成 `\u4e00-\u9fff` 只会
100
+ * 让中文正则更难读,而这两个范围的语义在项目里是明确的。
101
+ */
102
+ export declare const REGEXP_RULES: RuleTable;
103
+ /**
104
+ * `eslint-plugin-zod` recommended 的 23 条里去掉 `consistent-schema-var-name` 后的 22 条
105
+ * (针对 zod 4)。
106
+ *
107
+ * 实测(oxlint 1.90、9 包):
108
+ * - `consistent-import` 默认要求 namespace import,与本仓统一的 `import { z } from "zod"` 冲突;
109
+ * 按参考仓的做法把 syntax 设成 named 后归零。
110
+ * - `no-number-schema-with-int` 5 条(`z.number().int()` → `z.int()`,fixer 可修)。
111
+ * - `require-error-message` 5 条待按代码补错误文案。
112
+ * - `consistent-schema-var-name` 71 条**不挂**:本仓的 schema 常量是大写词(`MAX_DENIES_SCHEMA`、
113
+ * `STATS_SNAPSHOT_SCHEMA`),规则的默认口径是把 `Schema` 后缀再拼一次,得到
114
+ * `MAX_DENIES_SCHEMASchema` 这类坏名字;把它当命名缺陷改 71 处只是风格迁移,不指向缺陷。
115
+ *
116
+ * 该插件只对真的 import zod 的文件生效;本仓九个包都声明了 zod,规则面不会空转。
117
+ */
118
+ export declare const ZOD_RULES: RuleTable;
119
+ /**
120
+ * `@e18e/eslint-plugin` recommended 里**不与 oxlint 内建规则重复**的那一族。
121
+ *
122
+ * 刻意不挂的 9 条:
123
+ * - 实测与内建规则同义,挂上会把同一条缺陷报两遍:`prefer-array-at`(unicorn/prefer-at)、
124
+ * `prefer-array-some`(unicorn/prefer-array-some)、`prefer-date-now`(unicorn/prefer-date-now)、
125
+ * `prefer-includes`(unicorn/prefer-includes / typescript/prefer-includes)、
126
+ * `prefer-nullish-coalescing`(typescript/prefer-nullish-coalescing)、
127
+ * `prefer-object-has-own`(eslint/prefer-object-has-own)、`prefer-regex-test`
128
+ * (unicorn/prefer-regexp-test)、`prefer-spread-syntax`(unicorn/prefer-spread;某个消费方的
129
+ * sidecar 已为后者做过文件级 off,再挂本条会把那个冲突原样带回来)。
130
+ * - `prefer-string-fromcharcode` 与已启用的 `unicorn/prefer-code-point` **方向相反**:前者的
131
+ * fixer 把 `String.fromCodePoint` 改回 `String.fromCharCode`,后者要求反过来。实测在一个
132
+ * 消费方上执行 `--fix` 会把 15 处 `fromCodePoint` 改写成 `fromCharCode`,其中含非 BMP
133
+ * 码点时运行语义会变,故整条不挂(不是"重复",是互斥)。
134
+ *
135
+ * 实测非重复规则当前命中:`prefer-array-from-map` 25、`prefer-array-fill` 5(都带 fixer);
136
+ * `ban-dependencies` 唯一命中落在某个消费方的脚本,由 SCRIPTS_OVERRIDES 收窄(理由见
137
+ * OFF_JUSTIFICATIONS)。
138
+ *
139
+ * `prefer-static-regex` 全仓 1778 条(生产 78、测试 1672、其余 28),且没有 fixer:基线全局
140
+ * off,只在 PROD_OVERRIDES 的生产文件上开。测试里的内联正则不构成性能问题,生产侧按代码提到
141
+ * 模块作用域。
142
+ */
143
+ export declare const E18E_RULES: RuleTable;
73
144
  /**
74
145
  * oxlint 认识的 vitest 规则全集(取自 `oxlint --rules` 的表格,73 条)。整族在基线 off,再由
75
146
  * TEST_FILE_OVERRIDES 在用例文件上 on —— 理由见 OFF_JUSTIFICATIONS 里的 `vitest/*`。
@@ -88,6 +159,15 @@ export declare const BASE_RULES: RuleTable;
88
159
  * TEST_HELPER_OVERRIDES 因此作废。
89
160
  */
90
161
  export declare const TEST_OVERRIDES: OxlintOverride;
162
+ /**
163
+ * 生产代码面的 override:目前只有 `e18e/prefer-static-regex`。
164
+ *
165
+ * 该规则在全仓 1778 条(生产 78、测试 1672、其余 28),没有 fixer;全局 off、这里 on,
166
+ * 既保住「生产代码里的内联正则提到模块作用域」这条要求,又不把测试断言与构建脚本逼成
167
+ * 一堆模块级常量。`lib/**` 是各包的运行期模块(shared 的 build-* helper 也在 lib 下,
168
+ * 它们随包发布,按生产代码对待);`host.ts` / `cli-main.ts` 是包根的生产入口。
169
+ */
170
+ export declare const PROD_OVERRIDES: OxlintOverride;
91
171
  /**
92
172
  * vitest 的用例结构类判断**只**在这里生效。
93
173
  * `expect-expect` 的 `assertFunctionNames` 取 vitest flavor 的官方默认
@@ -122,8 +202,29 @@ export interface TestFileOverridesOptions {
122
202
  */
123
203
  export declare function makeTestFileOverrides(opts?: TestFileOverridesOptions): OxlintOverride;
124
204
  export declare const SCRIPTS_OVERRIDES: OxlintOverride;
205
+ /**
206
+ * 一条按文件生效的同步 API 豁免。
207
+ *
208
+ * `files` 是 oxlint 的 override 匹配式(包内相对路径),`apis` 是这些文件里除基线只读名单之外
209
+ * 还允许出现的同步方法名 —— 名单不外溢:同包里另一个文件用同一个 API 照样报。
210
+ */
211
+ export interface SyncIoScope {
212
+ readonly files: readonly string[];
213
+ readonly apis: readonly string[];
214
+ }
125
215
  export interface PluginLintOptions {
126
- syncWrites?: string[];
216
+ /**
217
+ * 同步 API 的**文件级**豁免,一枚 scope 对应一条 override。
218
+ *
219
+ * 换掉的是早前的包级扁平名单 `syncWrites`:那张表把 `writeFileSync` / `rmSync` 登记进去之后,
220
+ * 整个包任何位置的同步写都不再报,而配套注释写的是「谁用谁声明」—— 结构与说法不是一回事。
221
+ * 这里让豁免跟着 `files` 生效。
222
+ *
223
+ * 生成的 override 必须自带基线只读名单:oxlint 对同一条规则在 override 里是**整条设置替换**,
224
+ * 实测(oxlint 1.90)override 只写 `ignores: ["readFileSync"]` 时,基线豁免过的 `existsSync`
225
+ * 在那个文件里就变成了报的。
226
+ */
227
+ syncIoScopes?: SyncIoScope[];
127
228
  extraRules?: RuleTable;
128
229
  extraOverrides?: OxlintOverride[];
129
230
  extraIgnorePatterns?: string[];
@@ -1,6 +1,35 @@
1
1
  import type { SessionEvent } from "@deepseek-ai/dsh-session";
2
+ import type { ToolCallId } from "@deepseek-ai/dsh-llm/brand";
3
+ import type { ContentBlock } from "@deepseek-ai/dsh-llm/types";
4
+ import type { JsonValue } from "@deepseek-ai/dsh-util-values";
2
5
  export type { SessionEvent } from "@deepseek-ai/dsh-session";
3
- export type { PtcDispatchEventData, PtcDispatchStartEventData } from "@deepseek-ai/dsh-tools/types";
6
+ /** 一枚 PTC 子调度**开始**时记下的载荷。字段与宿主声明逐位同形,理由见文件头。 */
7
+ export interface PtcDispatchStartEventData {
8
+ rootCallId: ToolCallId;
9
+ parentCallId: ToolCallId;
10
+ subCallId: ToolCallId;
11
+ name: string;
12
+ arguments: unknown;
13
+ }
14
+ /** 一枚 PTC 子调度**结算**时记下的载荷:结算面沿用 `tool/result` 自己的词表。 */
15
+ export interface PtcDispatchEventData extends PtcDispatchStartEventData {
16
+ isError: boolean;
17
+ content: ContentBlock[];
18
+ /** 可重放的工具侧呈现元数据,与模型可见内容分开记账。 */
19
+ meta?: JsonValue;
20
+ /** 可选的失败身份与原始用户可见细节,不在模型可见内容里。 */
21
+ error?: {
22
+ name: string;
23
+ code: string;
24
+ reason?: string;
25
+ };
26
+ }
27
+ declare module "@deepseek-ai/dsh-session/types" {
28
+ interface SessionEventMap {
29
+ "tool/ptc-dispatch-start": PtcDispatchStartEventData;
30
+ "tool/ptc-dispatch": PtcDispatchEventData;
31
+ }
32
+ }
4
33
  /**
5
34
  * 一条 tool/call 的记录。
6
35
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jayyuen66/dsh-plugin-shared",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "dsh 自研插件的共享工具库:tool 事件流记账扫描 + 卡片 apply/差异骨架 + 统一设置卡 UI 组件 + 官方作业结局映射 + 项目键派生 + 代理对安全截断。作为库包被 import,不由用户启用",
5
5
  "keywords": [
6
6
  "cordis",
@@ -119,21 +119,26 @@
119
119
  "registry": "https://registry.npmjs.org"
120
120
  },
121
121
  "devDependencies": {
122
- "@deepseek-ai/cordis": "4.0.5",
123
- "@deepseek-ai/dsh-brand": "0.2.1-rc.3",
124
- "@deepseek-ai/dsh-client-locale": "0.2.1-rc.3",
125
- "@deepseek-ai/dsh-jobs": "0.2.1-rc.3",
126
- "@deepseek-ai/dsh-llm": "0.2.1-rc.3",
127
- "@deepseek-ai/dsh-output-retention": "0.2.1-rc.3",
128
- "@deepseek-ai/dsh-scope": "0.2.1-rc.3",
129
- "@deepseek-ai/dsh-session": "0.2.1-rc.3",
130
- "@deepseek-ai/dsh-shell": "0.2.1-rc.3",
131
- "@deepseek-ai/dsh-tools": "0.2.1-rc.3",
132
- "@types/node": "26.6.5",
122
+ "@deepseek-ai/cordis": "4.0.6-alpha.1",
123
+ "@deepseek-ai/dsh-brand": "0.2.2-alpha.2",
124
+ "@deepseek-ai/dsh-client-locale": "0.2.2-alpha.2",
125
+ "@deepseek-ai/dsh-jobs": "0.2.2-alpha.2",
126
+ "@deepseek-ai/dsh-llm": "0.2.2-alpha.2",
127
+ "@deepseek-ai/dsh-output-retention": "0.2.2-alpha.2",
128
+ "@deepseek-ai/dsh-scope": "0.2.2-alpha.2",
129
+ "@deepseek-ai/dsh-session": "0.2.2-alpha.2",
130
+ "@deepseek-ai/dsh-shell": "0.2.2-alpha.2",
131
+ "@deepseek-ai/dsh-tool-executor-ptc": "0.2.2-alpha.2",
132
+ "@deepseek-ai/dsh-tools": "0.2.2-alpha.2",
133
+ "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2",
134
+ "@e18e/eslint-plugin": "0.8.1",
135
+ "@types/node": "26.6.6",
133
136
  "@types/react": "19.3.0",
134
137
  "@types/react-dom": "19.3.0",
135
138
  "@vitest/coverage-v8": "5.0.5",
139
+ "eslint-plugin-regexp": "3.3.2",
136
140
  "eslint-plugin-sonarjs": "4.2.2",
141
+ "eslint-plugin-zod": "5.2.2",
137
142
  "happy-dom": "21.0.4",
138
143
  "oxfmt": "0.75.0",
139
144
  "oxlint": "1.90.0",
@@ -146,14 +151,14 @@
146
151
  "vitest": "5.0.5"
147
152
  },
148
153
  "peerDependencies": {
149
- "@deepseek-ai/cordis": "4.0.5",
154
+ "@deepseek-ai/cordis": "^4.0.5||^4.0.6-alpha.1",
150
155
  "@deepseek-ai/dsh": "^0.2.1-rc.3",
151
- "@deepseek-ai/dsh-client-locale": "0.2.1-rc.3",
152
- "@deepseek-ai/dsh-jobs": "0.2.1-rc.3",
153
- "@deepseek-ai/dsh-output-retention": "0.2.1-rc.3",
154
- "@deepseek-ai/dsh-session": "0.2.1-rc.3",
155
- "@deepseek-ai/dsh-shell": "0.2.1-rc.3",
156
- "@deepseek-ai/dsh-tools": "0.2.1-rc.3",
156
+ "@deepseek-ai/dsh-client-locale": "^0.2.1-rc.3",
157
+ "@deepseek-ai/dsh-jobs": "^0.2.1-rc.3",
158
+ "@deepseek-ai/dsh-output-retention": "^0.2.1-rc.3",
159
+ "@deepseek-ai/dsh-session": "^0.2.1-rc.3",
160
+ "@deepseek-ai/dsh-shell": "^0.2.1-rc.3",
161
+ "@deepseek-ai/dsh-tools": "^0.2.1-rc.3",
157
162
  "react": "^19.3.0"
158
163
  },
159
164
  "peerDependenciesMeta": {
@@ -1 +0,0 @@
1
- const e=/^\/\/#region .*$/gmu,t=/^[^\s'"`]+$/u,n=/^(?:\.\.?\/)+/u,r=/[/\\]+/u;function i(e){let t=e.replace(/[/\\]+$/u,``),n=Math.max(t.lastIndexOf(`/`),t.lastIndexOf(`\\`));return n===-1?t:t.slice(n+1)}function a(e){let t=e.replace(/[/\\]+$/u,``),n=Math.max(t.lastIndexOf(`/`),t.lastIndexOf(`\\`));return n===-1?t:t.slice(0,n)}function o(e){let t=i(e);return[i(a(e)),t].filter(e=>e.length>0)}function s(e,t){let n=e;for(let e of t)n.startsWith(`${e}/`)&&(n=n.slice(e.length+1));return n}function c(e){return e.split(r)}function l(e){let t=[...e];for(;t.length>1&&t.at(-1)===``;)t.pop();return t}function u(e){return e.startsWith(`/`)||e.startsWith(`\\`)||/^[A-Za-z]:[\\/]/u.test(e)}function d(e,t){let n=u(e);if(n!==u(t))return null;let r=l(c(e)),i=l(c(t));if(n&&r[0]!==i[0])return null;let a=0;for(;a<r.length&&a<i.length&&r[a]===i[a];)a+=1;return[...Array.from({length:r.length-a},()=>`..`),...i.slice(a)]}function f(e){if(e===null||e.length===0)return``;let t=0;for(;t<e.length&&e[t]===`..`;)t+=1;return t===e.length?``:e.slice(t).join(`/`)}function p(e,t){return{own:f(d(e,t)),parent:f(d(e,a(t)))}}function m(e,t){return t.own.length>0&&e.startsWith(`${t.own}/`)?e.slice(t.own.length+1):t.parent.length>0&&e.startsWith(`${t.parent}/`)?e.slice(t.parent.length+1):e}function h(e,r,i){let a=e.slice(10);if(!t.test(a))return e;let o=a.replace(n,``);return`//#region ${i===null?s(o,r):m(o,i)}`}function g(t,n,r){let i=o(n);if(r===void 0)return i.length===0?t:t.replaceAll(e,e=>h(e,i,null));let a=p(r,n);return t.replaceAll(e,e=>h(e,i,a))}export{g as t};