@jayyuen66/dsh-plugin-shared 0.1.9 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -66
- package/README.zh-CN.md +98 -91
- package/config/tsconfig.client.base.json +1 -1
- package/dist/build/build-client.js +2 -2
- package/dist/build/build-host.js +1 -1
- package/dist/build/canonicalize-region-paths-DowoRhQK.js +1 -0
- package/dist/canonicalize-region-paths.js +1 -1
- package/dist/card-ui.js +105 -0
- package/dist/config/oxlint.base.js +1 -1
- package/dist/config/vitest.base.js +1 -1
- package/dist/jsonl.js +3 -3
- package/dist/project-key.js +1 -1
- package/dist/types/build-artifacts.d.ts +11 -11
- package/dist/types/build-client.d.ts +20 -20
- package/dist/types/build-host.d.ts +4 -4
- package/dist/types/build-types.d.ts +4 -4
- package/dist/types/canonicalize-region-paths.d.ts +1 -1
- package/dist/types/card-apply.d.ts +9 -9
- package/dist/types/card-ui/field.d.ts +33 -0
- package/dist/types/card-ui/hint.d.ts +11 -0
- package/dist/types/card-ui/inputs.d.ts +33 -0
- package/dist/types/card-ui/layout.d.ts +19 -0
- package/dist/types/card-ui/save-bar.d.ts +16 -0
- package/dist/types/card-ui/select.d.ts +15 -0
- package/dist/types/card-ui/styles.d.ts +2 -0
- package/dist/types/card-ui.d.ts +10 -0
- package/dist/types/config/oxlint.base.d.ts +118 -38
- package/dist/types/config/vitest.base.d.ts +3 -3
- package/dist/types/errors.d.ts +6 -6
- package/dist/types/job-outcome.d.ts +12 -12
- package/dist/types/jsonl.d.ts +6 -6
- package/dist/types/lesson-bus.d.ts +2 -2
- package/dist/types/locale.d.ts +9 -9
- package/dist/types/project-key.d.ts +6 -5
- package/dist/types/record.d.ts +16 -16
- package/dist/types/text.d.ts +4 -4
- package/dist/types/tool-events.d.ts +50 -21
- package/package.json +41 -22
- package/dist/build/canonicalize-region-paths-BxladpX3.js +0 -1
package/README.md
CHANGED
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
<h1 align="center">dsh-plugin-shared</h1>
|
|
6
6
|
|
|
7
|
-
<p align="center"><em>The shared library behind the dsh plugin family:
|
|
7
|
+
<p align="center"><em>The shared library behind the dsh plugin family: a tool-event record table, card state helpers, a unified settings-card UI kit, a project key and a set of locale helpers — imported by plugins, never enabled by users.</em></p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
10
|
<a href="https://www.npmjs.com/package/@jayyuen66/dsh-plugin-shared"><img src="https://img.shields.io/npm/v/@jayyuen66/dsh-plugin-shared?style=flat-square&logo=npm&logoColor=white" alt="npm version" /></a>
|
|
11
|
-
<a href="https://www.npmjs.com/package/@jayyuen66/dsh-plugin-shared"><img src="https://img.shields.io/npm/
|
|
11
|
+
<a href="https://www.npmjs.com/package/@jayyuen66/dsh-plugin-shared"><img src="https://img.shields.io/npm/d18m/@jayyuen66/dsh-plugin-shared?style=flat-square" alt="npm downloads (total)" /></a>
|
|
12
12
|
<a href="https://github.com/JayYuen666/dsh-plugin-shared/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/JayYuen666/dsh-plugin-shared/ci.yml?branch=main&style=flat-square&label=ci" alt="ci" /></a>
|
|
13
13
|
<img src="https://img.shields.io/badge/license-MIT-4c1?style=flat-square" alt="MIT" />
|
|
14
14
|
<img src="https://img.shields.io/badge/node-%3E%3D22.12-4c1?style=flat-square&logo=nodedotjs&logoColor=white" alt="node" />
|
|
15
|
-
<img src="https://img.shields.io/badge/dsh-%5E0.2.1--rc.
|
|
15
|
+
<img src="https://img.shields.io/badge/dsh-%5E0.2.1--rc.3-658EFF?style=flat-square" alt="dsh" />
|
|
16
16
|
<a href="https://github.com/deepseek-ai/deepseek-harness"><img src="https://img.shields.io/badge/powered_by_dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white" alt="powered by dsh" /></a>
|
|
17
17
|
</p>
|
|
18
18
|
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<a href="./README.zh-CN.md"><img src="https://img.shields.io/badge/lang-简体中文-658EFF?style=flat-square" alt="简体中文" /></a>
|
|
28
28
|
</p>
|
|
29
29
|
|
|
30
|
-
**A [dsh](https://github.com/deepseek-ai/deepseek-harness) library package: the runtime
|
|
30
|
+
**A [dsh](https://github.com/deepseek-ai/deepseek-harness) library package: the runtime modules and lint/test baselines shared by one author's plugin family. It is only imported by plugin packages; users never enable it.**
|
|
31
31
|
|
|
32
32
|
## <img src="https://api.iconify.design/lucide/file-text.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Contents
|
|
33
33
|
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
- [Verify it is live](#verify-it-is-live)
|
|
37
37
|
- [What it does not provide](#what-it-does-not-provide)
|
|
38
38
|
- [Modules](#modules)
|
|
39
|
-
- [The config
|
|
39
|
+
- [The config exports](#the-config-exports)
|
|
40
40
|
- [Known boundaries](#known-boundaries)
|
|
41
41
|
- [Data and privacy](#data-and-privacy)
|
|
42
42
|
- [Versioning and compatibility](#versioning-and-compatibility)
|
|
@@ -46,19 +46,19 @@
|
|
|
46
46
|
|
|
47
47
|
## <img src="https://api.iconify.design/lucide/shield.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> What it does
|
|
48
48
|
|
|
49
|
-
- **It is a library, not a plugin.** `package.json` carries no `dsh` field and the package has no `cordis.patch.yml`, so a copy
|
|
49
|
+
- **It is a library, not a plugin.** `package.json` carries no `dsh` field and the package has no `cordis.patch.yml`, so a copy in `node_modules` is never registered as a plugin. There is no settings card, no client side and no route of its own.
|
|
50
50
|
- **It holds the parts the plugins repeated verbatim:**
|
|
51
|
-
- The tool-event
|
|
52
|
-
- Client card
|
|
53
|
-
- Runtime
|
|
54
|
-
- Build-time and
|
|
55
|
-
- **It
|
|
51
|
+
- The tool-event record table (`./lib/tool-events`): one scan from `SessionEvent[]` to call and result tables, PTC sub-calls included, plus `editPathOf` for the edit targets the record can see.
|
|
52
|
+
- Client card base components (`./lib/card-apply`, `./lib/card-diff`): an apply idempotency guard across HMR, and a form-value diff that treats `null`, `undefined` and an absent key as the same value.
|
|
53
|
+
- Runtime utilities (`./lib/project-key`, `./lib/locale`, `./lib/lesson-bus`, `./lib/text`, `./lib/record`, `./lib/errors`, `./lib/jsonl`, `./lib/job-outcome`): the small logic every plugin would otherwise rewrite.
|
|
54
|
+
- Build-time and check baselines (`./lib/canonicalize-region-paths`, `./config/*`): artifact path normalization plus the oxlint, vitest and tsconfig starting points for every consumer.
|
|
55
|
+
- **It handles the generic part; domain decisions stay out.** Which tools count as edits, path filtering, budgets and when a check should fail stay in the plugins. The shared layer makes no judgement about the domain handed to it.
|
|
56
56
|
|
|
57
57
|
### What belongs here
|
|
58
58
|
|
|
59
|
-
- **Verbatim duplication is the admission rule.** The same body in two or more places. A family that looks similar but differs in failure semantics, caching or override
|
|
59
|
+
- **Verbatim duplication is the admission rule.** The same body in two or more places. A family that looks similar but differs in failure semantics, caching or override behavior stays where it is.
|
|
60
60
|
- **Domain judgement stays out.** A predicate that depends on a plugin's own tool list, policy or user-facing wording is not boilerplate, even when it appears twice.
|
|
61
|
-
- **The published
|
|
61
|
+
- **The published interface is a commitment.** Adding a module means adding a subpath, a barrel re-export (except the build-time helper), a builder entry, the build `include` and a test; the consistency checks in Development fail if one is missed.
|
|
62
62
|
|
|
63
63
|
## <img src="https://api.iconify.design/lucide/package.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Install
|
|
64
64
|
|
|
@@ -74,52 +74,54 @@ Declare it as an ordinary dependency of your plugin package and import it by bar
|
|
|
74
74
|
import { isRecord, fieldOf } from "@jayyuen66/dsh-plugin-shared/lib/record";
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
End users do not install or enable it separately: any consumer package pulls it in. When it is missing, the failure is `ERR_MODULE_NOT_FOUND` on a `@jayyuen66/dsh-plugin-shared/...` specifier, never a silent downgrade. There is no default export; import named
|
|
77
|
+
End users do not install or enable it separately: any consumer package pulls it in. When it is missing, the failure is `ERR_MODULE_NOT_FOUND` on a `@jayyuen66/dsh-plugin-shared/...` specifier, never a silent downgrade. There is no default export; import named exports from the barrel or a subpath.
|
|
78
78
|
|
|
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.
|
|
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
|
-
|
|
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
|
+
|
|
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
|
-
- **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
|
|
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
|
|
93
|
-
- **
|
|
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`, so a host anywhere in the supported window satisfies it. `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 the rc and alpha host lines; the range carries no spaces because the CI type probe splits the peer list on whitespace. `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
|
|
|
98
|
-
A consumer profile can
|
|
100
|
+
A consumer profile can lock this package with a pnpm `overrides` entry. Lock it below the version that introduced a `./lib/...` subpath and the host boots with `N entries did not activate` plus `does not provide an export named ...`: the package installs, never loads, and no per-repository CI run sees it. Check this package's `exports` and CHANGELOG before locking.
|
|
99
101
|
|
|
100
102
|
### What ships
|
|
101
103
|
|
|
102
104
|
- `main` and `exports` point at `dist/` only; there is no second, source-shaped manifest. Node throws `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING` for `.ts` inside `node_modules`, which is exactly where a published package is loaded from, so an artifact is mandatory.
|
|
103
105
|
- `files` is `icon.svg`, `dist`, `config/*.json` and `README.zh-CN.md`. npm always adds `package.json`, `LICENSE` and `README.md`; the Chinese copy is listed explicitly so it ships too.
|
|
104
106
|
- `publishConfig` carries `access` and `registry` and nothing else. Entry fields there are a trap: pnpm merges them onto the top level when publishing and npm does not, so one publisher would ship a package whose entry points name files that are not in the tarball.
|
|
105
|
-
- `build` deletes `dist/` first, then emits the runtime artifacts, the config artifacts, the type declarations and the generated barrel declaration. Both builders minify; `test/dist-size.test.ts` keeps
|
|
107
|
+
- `build` deletes `dist/` first, then emits the runtime artifacts, the config artifacts, the type declarations and the generated barrel declaration. Both builders minify; `test/dist-size.test.ts` keeps the runtime and config artifacts inside their budgets.
|
|
106
108
|
- `prepare` runs `build`. It does not run when a consumer installs from the registry; it exists so packing, publishing and workspace links always ship a fresh `dist/`.
|
|
107
|
-
- Adding a
|
|
109
|
+
- Adding a module means touching five places: the barrel in `lib/index.ts`, `exports`, the `facets` array in `build-host.mjs`, `include` in `tsconfig.build.json`, and a `test/<module>.test.ts`. The consistency checks in Development fail if one is missed.
|
|
108
110
|
|
|
109
111
|
## <img src="https://api.iconify.design/lucide/circle-check.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Verify it is live
|
|
110
112
|
|
|
111
113
|
1. **A subpath resolves.** From the consuming package, `node -e 'import("@jayyuen66/dsh-plugin-shared/lib/record").then((m) => console.log(typeof m.isRecord))'` prints `function`. `ERR_MODULE_NOT_FOUND` means the dependency is not installed; `does not provide an export named ...` means the installed copy is older than the subpath you import.
|
|
112
|
-
2. **The barrel resolves.** The same check against the bare package name returns the runtime
|
|
113
|
-
3. **A consumer host boots.** Start the profile that declares the plugin. A boot log containing `N entries did not activate` and `does not provide an export named ...` means the profile's `overrides`
|
|
114
|
+
2. **The barrel resolves.** The same check against the bare package name returns the runtime modules. `canonicalize-region-paths` is the one module deliberately absent from the barrel; import it by subpath when a build script needs it.
|
|
115
|
+
3. **A consumer host boots.** Start the profile that declares the plugin. A boot log containing `N entries did not activate` and `does not provide an export named ...` means the profile's `overrides` version is below the version that contains the subpath the consumer imports — raise the version, do not patch the plugin.
|
|
114
116
|
4. **Local source edits reach the consumer.** Consumers load `dist/`, not `lib/`. Run `pnpm run build` after editing; `test/build-host.test.ts` compares the runtime artifacts on disk against an in-memory build and fails when they are stale. "The code changed but nothing happened" is almost always a stale artifact, not a caching bug.
|
|
115
117
|
|
|
116
118
|
## <img src="https://api.iconify.design/lucide/circle-slash.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> What it does not provide
|
|
117
119
|
|
|
118
120
|
- **No plugin registration.** No `dsh` field, no `cordis.patch.yml`, no layer in `--dump-config`; `dsh plugin add` is the wrong action.
|
|
119
|
-
- **No settings card or client
|
|
120
|
-
- **No HTTP route and no trust
|
|
121
|
+
- **No settings card or client side.** The package has no browser entry and no client-side state.
|
|
122
|
+
- **No HTTP route and no request-trust checks.** The host's `/api` gateway owns authentication, the Host / Origin checks and body limits for the plugins that do register a route.
|
|
121
123
|
- **No CLI subcommand and no model tool.**
|
|
122
|
-
- **No domain judgement.** It does not know which tool counts as an edit, which path is secret, how large a budget is, or when a
|
|
124
|
+
- **No domain judgement.** It does not know which tool counts as an edit, which path is secret, how large a budget is, or when a check should fail. Those belong to the calling plugin.
|
|
123
125
|
|
|
124
126
|
## <img src="https://api.iconify.design/lucide/package.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Modules
|
|
125
127
|
|
|
@@ -127,45 +129,50 @@ The subpath column is the in-package specifier; value imports prefix it with the
|
|
|
127
129
|
|
|
128
130
|
| Subpath | Provides |
|
|
129
131
|
| --- | --- |
|
|
130
|
-
| `.` | Barrel over the runtime
|
|
131
|
-
| `./lib/tool-events` | `scanToolEvents` returns the `calls` / `results` tables, PTC sub-calls included; `toolEventRowsOf`
|
|
132
|
-
| `./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
|
|
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. |
|
|
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. |
|
|
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
|
-
| `./lib/
|
|
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). |
|
|
137
|
+
| `./lib/project-key` | `deriveProjectKey` + `ProjectKeyOptions`: cwd → `last-segment-<first 8 hex of sha256(norm)>`, with `default` for a non-string, an empty string or a normalized root. `realpathSync` resolves symlinks; a failure falls back to the resolved path. Windows `\` and `/` produce the same key. |
|
|
135
138
|
| `./lib/locale` | `Locale` (an alias of the official `BuiltInLocaleId`), `DEFAULT_LOCALE`, `resolveLocale`, `resolveLocalePreference`, `messagesFor`, `MessagesCatalog`, plus `LOCALE_SETTINGS_NAMESPACE` / `LOCALE_PREFERENCE_FIELD`. Host-side dictionaries only; the client uses the official locale package. |
|
|
136
|
-
| `./lib/lesson-bus` | `settleLessonCall`: routes synchronous throws and asynchronous rejections into one `onFailure` exit. Function-valued thenables count as awaitable; an `onFailure` that itself throws or rejects is
|
|
137
|
-
| `./lib/text` | `truncateEnd` delegates to the official `truncateWithoutSplittingSurrogatePair`; `truncateStart` is the back-cut form the official
|
|
138
|
-
| `./lib/record` | `isRecord`, `fieldOf`, `hasMethods`. `fieldOf` reads through the prototype chain by design (cordis service methods
|
|
139
|
-
| `./lib/errors` | `errorText` follows the official `@deepseek-ai/dsh-llm` `errorChain` semantics (cause chain, `AggregateError` members, empty message → `name`, cross-realm `message`,
|
|
140
|
-
| `./lib/jsonl` | `shrinkJsonlTail`:
|
|
141
|
-
| `./lib/job-outcome` | `jobOutcomeOf` + `SettledProcess` + `JobOutcome` map a
|
|
142
|
-
| `./lib/canonicalize-region-paths` | Build-time string helper that
|
|
139
|
+
| `./lib/lesson-bus` | `settleLessonCall`: routes synchronous throws and asynchronous rejections into one `onFailure` exit. Function-valued thenables count as awaitable; an `onFailure` that itself throws or rejects is handled in the same place, so it never splits into "thrown at the caller" or "unhandled rejection". |
|
|
140
|
+
| `./lib/text` | `truncateEnd` delegates to the official `truncateWithoutSplittingSurrogatePair`; `truncateStart` is the back-cut form the official exports do not provide. Both are surrogate-pair safe; a budget below one code unit (including `<= 0` and NaN) returns an empty string. |
|
|
141
|
+
| `./lib/record` | `isRecord`, `fieldOf`, `hasMethods`. `fieldOf` reads through the prototype chain by design (cordis service methods are defined there); a read that hits a malicious getter or Proxy trap returns `undefined` instead of throwing. `hasMethods` requires every named method to be callable and does not accept `Object.prototype` members as a service implementation. |
|
|
142
|
+
| `./lib/errors` | `errorText` follows the official `@deepseek-ai/dsh-llm` `errorChain` semantics (cause chain, `AggregateError` members, empty message → `name`, cross-realm `message`, malicious getters render as a placeholder) in a zero-dependency local implementation. `isSettingsConflict` recognizes the host's settings-conflict pattern from `code` / `name`, so a cross-realm error is still recognized. |
|
|
143
|
+
| `./lib/jsonl` | `shrinkJsonlTail`: pure decision logic for JSONL tail shrinking. The trigger policy and error handling stay with the caller; the byte budget is the caller's responsibility to keep finite. |
|
|
144
|
+
| `./lib/job-outcome` | `jobOutcomeOf` + `SettledProcess` + `JobOutcome` map a finished subprocess onto the official job registry's outcome; `killProc` + `KillableProcess` release a child handle and ignore a `kill()` that throws or rejects (for example, on an already-reaped process). |
|
|
145
|
+
| `./lib/canonicalize-region-paths` | Build-time string helper that rewrites rolldown's `//#region <path>` markers into a `process.cwd()`-independent form. Exported as a subpath so build scripts can import it without putting it in the runtime barrel. |
|
|
146
|
+
|
|
147
|
+
`card-ui` is the one runtime module that is deliberately subpath-only: adding it to the barrel would make every host-side importer resolve `react`.
|
|
143
148
|
|
|
144
|
-
## <img src="https://api.iconify.design/lucide/sliders-horizontal.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> The config
|
|
149
|
+
## <img src="https://api.iconify.design/lucide/sliders-horizontal.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> The config exports
|
|
145
150
|
|
|
146
151
|
These are lint/test baselines for plugin authors, not runtime code. They pull in tooling your package must provide itself:
|
|
147
152
|
|
|
148
153
|
| Subpath | Provides | You must install |
|
|
149
154
|
| --- | --- | --- |
|
|
150
|
-
| `./config/oxlint` | `definePluginConfig`, `makeTestFileOverrides`, `OFF_JUSTIFICATIONS` and the shared rule baseline | `oxlint
|
|
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` |
|
|
151
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"`) |
|
|
152
157
|
| `./config/tsconfig.base.json`, `./config/tsconfig.client.base.json` | Plain JSON for `extends` | Nothing |
|
|
153
158
|
|
|
154
|
-
Those toolchain packages are **not** declared as `peerDependencies`: npm 7+ tries to satisfy optional peers too, and `oxlint`'s own `peerOptional vite-plus`
|
|
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.
|
|
155
160
|
|
|
156
|
-
-
|
|
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`.
|
|
157
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`.
|
|
158
|
-
- `definePackageConfig` refuses an empty `coverageInclude` and refuses a `test.coverage` block: either one would silently drop the four thresholds or the
|
|
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.
|
|
159
165
|
|
|
160
166
|
## <img src="https://api.iconify.design/lucide/triangle-alert.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Known boundaries
|
|
161
167
|
|
|
162
|
-
|
|
168
|
+
One entry per class; each is fixed by a test at its current behavior.
|
|
163
169
|
|
|
164
|
-
- **`fieldOf` reads the prototype chain on purpose.** `Object.hasOwn` would make every cordis service method invisible. Code that reads a user settings value must use the own-property form instead; `resolveLocalePreference` is the example and is
|
|
165
|
-
- **`claimApply` is a process-wide flag, not a per-context lock.** A second context applying the same card on one page is
|
|
166
|
-
- **`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
|
|
167
|
-
- **`jobOutcomeOf` copies the host bash tool's wording.** The tests
|
|
170
|
+
- **`fieldOf` reads the prototype chain on purpose.** `Object.hasOwn` would make every cordis service method invisible. Code that reads a user settings value must use the own-property form instead; `resolveLocalePreference` is the example and is covered by tests in both directions.
|
|
171
|
+
- **`claimApply` is a process-wide flag, not a per-context lock.** A second context applying the same card on one page is rejected. A consumer that needs multiple contexts must manage its contexts itself rather than "fixing" this protection.
|
|
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.
|
|
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.
|
|
168
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.
|
|
169
176
|
|
|
170
177
|
## <img src="https://api.iconify.design/lucide/lock.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Data and privacy
|
|
171
178
|
|
|
@@ -179,28 +186,28 @@ The package is imported into a host process; the table describes run time.
|
|
|
179
186
|
| Network | No outbound request, no HTTP server, no subprocess. |
|
|
180
187
|
| Dynamic code | No `eval`, no `new Function`, no dynamic `import()`. |
|
|
181
188
|
| Process memory | The only mutable cross-instance state is the `claimApply` slot on `globalThis`, restored when the effect disposes or when claiming fails. |
|
|
182
|
-
| Logging | `console.error` appears only when an `onFailure` callback in `lesson-bus` itself throws; the test setup
|
|
189
|
+
| Logging | `console.error` appears only when an `onFailure` callback in `lesson-bus` itself throws; the test setup checks every `[shared/*]` line against a template list. |
|
|
183
190
|
|
|
184
191
|
## <img src="https://api.iconify.design/lucide/layers.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Versioning and compatibility
|
|
185
192
|
|
|
186
|
-
- **Semantic versioning applies to the public
|
|
187
|
-
- **`deriveProjectKey`'s hash algorithm, the 8-hex slice and the trailing-segment whitespace cleaning are frozen.** Changing one digit
|
|
188
|
-
- **Windows path normalization changed the
|
|
193
|
+
- **Semantic versioning applies to the public interface**: subpaths, exported value symbols, exported types, and error/reason enums. `0.x` ranges admit patches only, so touching any of those is already breaking under the ranges consumers wrote.
|
|
194
|
+
- **`deriveProjectKey`'s hash algorithm, the 8-hex slice and the trailing-segment whitespace cleaning are frozen.** Changing one digit leaves existing lessons and memories under a key that no longer matches.
|
|
195
|
+
- **Windows path normalization changed the key shape.** The same directory now yields `last-segment-<hash>` whether it is spelled with `\` or `/`, and a drive root or UNC share root falls into `default`. Keys written before normalization on Windows no longer match; POSIX keys are byte-for-byte unchanged and fixed by literal assertions.
|
|
189
196
|
- **`Locale` is an alias of the official `BuiltInLocaleId`**, so it tracks the host's `LOCALE_IDS` instead of a hand-written union.
|
|
190
|
-
- **`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
|
|
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.
|
|
191
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`.
|
|
192
|
-
- **`./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
|
|
193
|
-
- **The declarations are
|
|
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".
|
|
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.
|
|
194
201
|
|
|
195
202
|
## <img src="https://api.iconify.design/lucide/circle-help.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> FAQ
|
|
196
203
|
|
|
197
204
|
| Question | Answer |
|
|
198
205
|
| --- | --- |
|
|
199
|
-
| Should I `dsh plugin add` it? | No. It has neither a config layer nor a client
|
|
200
|
-
| A consumer boots with `does not provide an export named ...` and `N entries did not activate`. | The profile is serving an older copy of this package than the consumer imports. Raise the profile's `overrides`
|
|
206
|
+
| Should I `dsh plugin add` it? | No. It has neither a config layer nor a client side, so there is nothing to enable. Declare it as a dependency of the plugin package. |
|
|
207
|
+
| A consumer boots with `does not provide an export named ...` and `N entries did not activate`. | The profile is serving an older copy of this package than the consumer imports. Raise the profile's `overrides` entry to a version that contains the subpath the consumer imports; check this package's `exports` and CHANGELOG. No per-repository CI run can see this state. |
|
|
201
208
|
| I changed `lib/*.ts` and the consumer still runs the old behavior. | Consumers load `dist/`. Run `pnpm run build`; `test/build-host.test.ts` fails if the runtime artifact is stale. A workspace link does not bypass the artifact. |
|
|
202
209
|
| Why is `@deepseek-ai/dsh-output-retention` a required peer while the rest are optional? | It is the only host package imported for value (`truncateEnd`). The rest are type-only, except the bare `@deepseek-ai/dsh` peer, which only declares the host line. |
|
|
203
|
-
| Why isn't `oxlint` a `peerDependency`? | npm 7+ would try to satisfy it, and `oxlint`'s optional `vite-plus` peer
|
|
210
|
+
| Why isn't `oxlint` a `peerDependency`? | npm 7+ would try to satisfy it, and `oxlint`'s optional `vite-plus` peer locks `vitest` outside the range this baseline needs, producing `ERESOLVE` for a plain install. The requirement is documented instead. |
|
|
204
211
|
| Want a helper added here? | First confirm the body repeats verbatim in two or more packages. Domain judgement stays in the plugins. |
|
|
205
212
|
|
|
206
213
|
## <img src="https://api.iconify.design/lucide/wrench.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Development
|
|
@@ -213,14 +220,14 @@ pnpm run build # runtime artifacts, config artifacts, declarations
|
|
|
213
220
|
```
|
|
214
221
|
|
|
215
222
|
- The suite splits by the class of defect a stub cannot see:
|
|
216
|
-
- **
|
|
217
|
-
- **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.
|
|
218
|
-
- **Artifact
|
|
219
|
-
- **Config
|
|
220
|
-
- **Log
|
|
221
|
-
- **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-*`
|
|
223
|
+
- **Module behavior**: one `test/<module>.test.ts` per runtime module.
|
|
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.
|
|
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`.
|
|
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.
|
|
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.
|
|
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.
|
|
222
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.
|
|
223
|
-
- CI runs the
|
|
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.
|
|
224
231
|
|
|
225
232
|
## <img src="https://api.iconify.design/lucide/scale.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> License
|
|
226
233
|
|