diff --git a/.agents/notes/proposed/feature/2026-10-02-plugin-market-index-format.i18n.yaml b/.agents/notes/proposed/feature/2026-10-02-plugin-market-index-format.i18n.yaml index f169408955..1da0b47262 100644 --- a/.agents/notes/proposed/feature/2026-10-02-plugin-market-index-format.i18n.yaml +++ b/.agents/notes/proposed/feature/2026-10-02-plugin-market-index-format.i18n.yaml @@ -9,8 +9,8 @@ en: ce5e4a362f558af6 zh: 29d93e0655b99217 /agent-note-plugin-market-index-format/proposal: - en: 91b1df0d57ed60bb - zh: 3fbe3e01c0ad03dc + en: 7b728d6cbf6f16d6 + zh: 73e79d35b0dbfbad /agent-note-plugin-market-index-format/proposal/example: en: 1b1c960c966cf912 zh: 97be87cb14893c26 @@ -18,29 +18,29 @@ en: 4fac5a9b8261342d zh: f9168a8a4209feab /agent-note-plugin-market-index-format/proposal/plugin-fields: - en: cf572978bea19f5a - zh: 80a6d9507c468958 + en: dbb59e8f9807b1d5 + zh: 030754d30a3c1861 /agent-note-plugin-market-index-format/proposal/version-fields: - en: 83480978017dbcf7 - zh: 9a5bb55554f90caa + en: d6c0fa99e1cbb068 + zh: 3d2a7b85299c679a /agent-note-plugin-market-index-format/proposal/install-arguments: en: c748cdc4d8ce349a zh: 5740fa0d6106e637 /agent-note-plugin-market-index-format/proposal/dsh-compatibility: - en: a6ae732431422eb7 - zh: 3750ecdc33f75b79 + en: 00fe507b9b5dfea7 + zh: 55de6aaeb3865e84 /agent-note-plugin-market-index-format/proposal/generator-obligations: - en: b7185a5fc36af7d3 - zh: a6fd69988607c324 + en: 8e848a102fe9d86e + zh: feff226ef8321e00 /agent-note-plugin-market-index-format/proposal/reader-rules: - en: 7eb0df505ef26cde - zh: dfe1f56554489c0f + en: 92903cbe87d9c0ca + zh: 54af6cb33e9da252 /agent-note-plugin-market-index-format/alternatives-considered: - en: 001d2d67f4f725cb - zh: 5a0adbc3d1397eed + en: 3d7cf519bc8e0593 + zh: 328dc0747836eaa8 /agent-note-plugin-market-index-format/acceptance-criteria: - en: 4ffec806e7a8445e - zh: a68fee8a99f185db + en: 753abee6adf39812 + zh: 1102bcfdd48f7e90 /agent-note-plugin-market-index-format/risks: - en: 19f22cc4a00b97e8 - zh: 585eea737ad6af40 + en: 41170d483b17df80 + zh: 1bfd8f44e0f09036 diff --git a/.agents/notes/proposed/feature/2026-10-02-plugin-market-index-format.md b/.agents/notes/proposed/feature/2026-10-02-plugin-market-index-format.md index 402cdb835f..7faf8edc98 100644 --- a/.agents/notes/proposed/feature/2026-10-02-plugin-market-index-format.md +++ b/.agents/notes/proposed/feature/2026-10-02-plugin-market-index-format.md @@ -16,8 +16,6 @@ An index must therefore carry everything a list page displays: localized title a Separate the **index** from the **index generator**. DSH defines and reads one JSON index format. How an index is produced, and from which source list, belongs to the generator. DSH's reader, its Discover page, and a reference generator are separate changes that consume this format. Indexes list [profile plugin bundles](../../implemented/architecture/2026-08-05-profile-plugin-bundles.md) and install them through [guided plugin installation](../../implemented/architecture/2026-09-15-guided-plugin-installation.md). -Package authors use [npm declarations](https://docs.npmjs.com/cli/v11/configuring-npm/package-json/). An index caches selected package metadata and adds localized display data, source pins, and market metadata. Readers and generators use maintained npm parsers and normalizers. They do not introduce another author-maintained package identity, compatibility declaration, or author field. - ### Example ```json @@ -56,15 +54,13 @@ Package authors use [npm declarations](https://docs.npmjs.com/cli/v11/configurin "version": "0.2.0-rc.2", "publishTimestamp": 1790476800, "source": { "type": "npm" }, - "engines": { "dsh": "0.2.0-rc.2" }, - "peerDependencies": { "@deepseek-ai/dsh-tools": "0.2.0-rc.2" } + "engines": { "dsh": "0.2.0-rc.2" } }, { "version": "0.2.0-rc.1", "publishTimestamp": 1789872000, "source": { "type": "npm" }, - "engines": { "dsh": "0.2.0-rc.1" }, - "peerDependencies": { "@deepseek-ai/dsh-tools": "0.2.0-rc.1" } + "engines": { "dsh": "0.2.0-rc.1" } } ] }, @@ -82,7 +78,8 @@ Package authors use [npm declarations](https://docs.npmjs.com/cli/v11/configurin "url": "https://github.com/author/dsh-plugins", "commit": "3f9c2e1d8a4b6c0e7f1a2b3c4d5e6f708192a3b4", "path": "packages/foo" - } + }, + "engines": { "dsh": "*" } } ] } @@ -113,27 +110,26 @@ Category, label, and sort-key ids are non-empty strings. Readers look ids up as |---|---|---| | `name` | yes | Package name accepted by npm's maintained package-name validator for new packages; unique within the index. | | `title` | no | `LocalizedText`; a reader displays `name` when absent. | -| `description` | no | `LocalizedText` for catalog display; see Generator obligations for its derivation. | +| `description` | no | `LocalizedText`. | | `icon` | no | Either `data:;base64,`, where `` is `image/svg+xml`, `image/png`, `image/jpeg`, or `image/webp`, `` is canonically padded base64, and the decoded payload is at most 256 KiB; or a URL reference whose resolution against the index URL is an `https:` URL or has the index URL's origin. | -| `author` | no | Display name containing a non-whitespace character; see Generator obligations for the npm author projection. | +| `author` | no | String with at least one non-whitespace character. | | `homepage` | no | Absolute `https:` or `http:` URL of a page about the plugin. | | `metadata` | no | Object holding the three values below. | | `metadata.category` | no | One declared category id. | | `metadata.labels` | no | Array of distinct declared label ids. | | `metadata.sortKeys` | no | Object mapping a declared sort-key id → finite number. | -| `dist-tags` | no | Object mapping npm distribution-tag names to exact version strings listed in `versions`. Absent means no recorded channels. | -| `versions` | yes | Non-empty array of versions; see below. Array order does not select a default version. | +| `dist-tags` | no | Object mapping npm distribution-tag names to exact listed version strings; absent means no recorded channels. | +| `versions` | yes | Non-empty array of versions; see below. Array order does not select the installation version. | ### Version fields | Field | Required | Rule | |---|---|---| -| `version` | yes | Exact package version parsed by npm's `node-semver`; preserve its complete string, including build metadata. The exact string is unique within the plugin. | +| `version` | yes | Exact version parsed by npm's `node-semver`; preserve the complete string, including Git build metadata. The exact string is unique within the plugin. | | `publishTimestamp` | yes | Integer Unix timestamp in seconds, in `[0, 10^11)`. | | `source` | yes | Where `version` is installed from; see below. | -| `engines` | no | npm manifest object mapping engine names to requirement strings, copied without range rewriting. | -| `engines.dsh` | no | Requirement string for the running DSH process. An empty or invalid range is incompatible; absence adds no host requirement. | -| `peerDependencies` | no | npm manifest object mapping package names to requirement strings, copied without range rewriting. DSH peers constrain compatibility; other peers remain package-manager requirements. | +| `engines` | yes | Object containing `dsh`. | +| `engines.dsh` | yes | Non-blank valid npm SemVer range declared in the source manifest; copied unchanged. `*` explicitly declares support for any DSH version. | A relative `icon` reference resolves against the index URL, as an HTML image reference resolves against its page. For an index at `https://market.example/dsh/index.json`, `"icon": "icons/foo.svg"` names `https://market.example/dsh/icons/foo.svg`. A generator can therefore publish the index and its icon files as one directory on any static host without knowing the final URL. Absolute icon URLs on other origins must use `https:`; the same-origin allowance lets an `http:` index on a loopback or intranet host serve its own icons. Readers resolve the reference when they read the index, so clients receive absolute URLs. @@ -151,15 +147,9 @@ A client installs one version with exactly these pnpm arguments: ### DSH compatibility -An index carries the manifest's `peerDependencies` and `engines` as separate declarations. DSH tests every `@deepseek-ai/dsh` and `@deepseek-ai/dsh-*` peer against the running DSH version. A declared `engines.dsh` adds a host requirement; it does not replace those peers. The version is compatible only when every applicable requirement matches. Missing declarations add no requirement. +Every market-listed version must declare valid `engines.dsh`. The reader and market-origin installation evaluate that range against the running DSH version with npm's `node-semver` and `includePrerelease: true`. There is no peer fallback or range synthesis. npm package peer resolution remains outside market host compatibility. -This preserves the existing [app-boot peer check](../../../../packages/boot/app-boot/src/plugin-compatibility.ts), which receives the running DSH version explicitly. Peer declarations do not identify that process; npm's package resolution in a profile remains separate from DSH's check against its running runtime. - -Implementing this proposal extends that evaluator with `engines.dsh` enforcement and engine-aware exemptions. The current evaluator checks peers only. The index reader and installation must both use the extended evaluator before this format is deployed. - -The index reader and installation use one DSH compatibility evaluator with npm's `node-semver` parser and `includePrerelease: true`. Empty or invalid DSH ranges are incompatible. The existing peer rule maps `workspace:*`, `workspace:^`, and `workspace:~` to the running DSH version; other `workspace:` peer ranges are incompatible. Engine ranges do not use that peer mapping. Exact local `name@version` exemptions apply to both kinds of DSH requirement and are not published in the index. - -Generators preserve each requirement string. Readers evaluate the strings separately; they do not concatenate or expand their ranges. npm owns package peer resolution and its Node/npm engine checks. DSH owns enforcement of its custom `engines.dsh` key, as recorded in the [public manifest decision](../../implemented/architecture/2026-09-10-public-package-manifest.md). +Installation rechecks the source manifest before accepting a market-origin package. An exact local `name@version` exemption may permit a valid range that rejects the runtime; it cannot make a missing or invalid declaration eligible. Exemptions are not published in the index. ### Generator obligations @@ -167,11 +157,11 @@ A reader cannot verify these rules; a generator that violates them publishes a m - `name` and each `version` equal the package manifest of the installed source. - `publishTimestamp` is the registry publish time of `version` for npm sources and the committer time of `commit` for Git sources. -- `engines` and `peerDependencies` copy the source manifest. npm sources use published metadata, including npm's publication rewriting. Git sources use the pinned manifest. Invalid DSH ranges remain visible as incompatible requirements; they are not omitted or converted into another declaration. -- `dist-tags` snapshots the registry's [distribution tags](https://docs.npmjs.com/cli/v11/commands/npm-dist-tag/). A generator includes each retained tag's target version, or omits that tag when its target is outside the listed bundle versions. For Git sources, the publisher or market supplies the same exact tag-to-version mapping. +- `engines.dsh` copies the manifest's declared range unchanged. A version with a missing, blank, or invalid range is omitted with a diagnostic. A plugin with no eligible version is omitted. +- `dist-tags` snapshots npm's [distribution tags](https://docs.npmjs.com/cli/v11/commands/npm-dist-tag/), retaining only targets present among the eligible listed versions. For Git sources, the publisher or market supplies the same exact tag-to-version mapping. - Each listed version declares `dsh.bundle`. Installation independently refuses a package without a bundle patch. -- Package-derived display fields come from the exact version named by `dist-tags.latest`. Without that tag, generators omit those display fields and readers use the package-name fallback. `title`, `description`, and `icon` follow installed-plugin display metadata rules, subject to this format's `LocalizedText` requirements. Localized text is catalog display data, not another npm manifest declaration. `homepage` copies the selected manifest's `homepage` and is absent without one. -- npm's `author` may be a person object or shorthand string. For an object, generators copy its `name` when it contains a non-whitespace character. For a shorthand string, they use a maintained npm person normalizer and take its `name` under the same condition. They omit an absent or whitespace-only name and do not copy email or URL into this display field. They normalize a fresh projection, leaving the source manifest untouched. +- `title`, `description`, and `icon` come from the highest listed version's locale metadata, manifest fields, and icon under the installed-plugin display rules. Use npm's `node-semver.rsort()` order, retaining input order for ties. This metadata choice does not select the installation version. `homepage` copies that version's manifest `homepage` and is absent without one. +- `author` comes from the same highest listed version's npm author declaration. Copy an object's `name`; parse shorthand strings with a maintained npm person normalizer. Omit an absent or whitespace-only name and contact fields. Normalize a fresh projection, leaving the source manifest untouched. ### Reader rules @@ -179,17 +169,13 @@ A reader rejects the whole index when the document violates any rule in Root fie A reader ignores members that this note does not define, at every level; an unknown `source.type` value is rejected. Format 1 can then gain optional members without breaking existing readers; a change that existing readers would misinterpret uses a new `format` value. -Entries in `engines`, `peerDependencies`, and `dist-tags` are declared data, not unknown members to discard. Their values must be strings; a non-string value invalidates the index. A DSH requirement string can be empty or syntactically invalid and remains visible as incompatible. A distribution-tag name must make `name@tag` a registry tag spec under npm's maintained `npm-package-arg` parser, rather than a version or range. Tag targets match listed version strings exactly. - -npm sources use canonical published registry version identifiers: the string equals npm's `node-semver.valid()` result, without build metadata. npm's publishing normalization does not provide separately selectable build-metadata variants. Git sources can retain such variants because their commits pin the installed source independently. Readers reject non-canonical npm source versions instead of claiming an exact build selection that npm does not provide. +Tag names must parse as registry tag specs under npm's maintained `npm-package-arg`; targets are exact listed version strings. npm sources use canonical published identifiers (`node-semver.valid(version) === version`); Git sources retain build metadata and pin their exact source by commit. Categories, labels, and sort keys are opaque: a reader filters and sorts by them, displays their `title`, and never interprets an id. Readers display declarations in the order of their localized `title`. When sorting plugins, a plugin without a value for the selected sort key follows all plugins with a value in either direction, and equal values keep the index order. Sorting by publish time uses a plugin's greatest `publishTimestamp`. -A reader evaluates each version's declared requirements with the rule in DSH compatibility. It orders versions by npm SemVer precedence; equally ranked versions keep their index order for display only. Full version strings identify entries, including versions that differ only in build metadata. +A reader evaluates the required `engines.dsh` range under DSH compatibility. Versions use npm's `node-semver.rsort()` order, retaining index order for ties. Exact tag targets, not this display order, select installation versions. -The initial channel is `latest`. Its exact tag target is the version selected by the publisher or market, even when another version has higher SemVer precedence. A client installs the listed exact version or commit, never `name@latest`. If `latest` is absent, version selection is required. If its target is incompatible, the client reports that requirement and offers other versions for explicit selection. It does not switch channels or select another release or prerelease automatically. - -Clients identify prereleases when users choose an exact version or channel, and in the installation confirmation. A publisher may deliberately tag a prerelease as `latest`; its presence does not authorize installation without that explicit confirmation. Clients attribute display fields, labels, categories, and scores to the index URL. A market label such as `official` is not DSH verification, and scores from different markets do not imply a shared scale. +The initial channel is `latest`. Its exact target is the publisher- or market-selected version. Clients install that recorded version or commit, never `name@latest`. Missing or incompatible `latest` requires explicit version or channel selection; clients do not switch automatically. Installation confirmation identifies the version, selected channel if any, prerelease status, index URL, and source. ## Alternatives considered @@ -207,11 +193,9 @@ Clients identify prereleases when users choose an exact version or channel, and **Inline icons only.** A list without extra requests needs inline icons, but 256 KiB of raw bytes is about 341 KiB as base64, so a 100-plugin index could reach about 35 MB. URL icons trade extra requests, and disclosure of the user's IP address to the image host, for a small index. -**Combining peer ranges into one synthetic `engines.dsh`.** This creates another compatibility declaration and requires range-intersection logic. Preserving the npm maps lets the reader and installer evaluate the same requirements, including hyphen ranges and `||` alternatives. +**Inferring host compatibility from peer dependencies.** The market defines a new standard requiring an author-declared `engines.dsh`. Existing packages must add that declaration before listing; no fallback, workspace mapping, or range intersection is needed. -**Letting `engines.dsh` override peers.** Host and package requirements express different obligations. Replacing package requirements with a broad host range can hide an incompatible DSH peer. - -**Copying the complete npm person object into the display field.** The catalog needs a name. A documented projection supports npm's original object and shorthand declarations while omitting unused contact fields. +**Copying the complete npm person object into the display field.** The list needs a name. The generator supports npm's original declaration while omitting unused contact fields. **Opaque sort values for time, including ISO date strings.** Publish time has fixed semantics that clients format and sort, so it is a typed field. Without time, sort values can be numbers only, which needs no per-key value-type rule. @@ -219,32 +203,26 @@ Clients identify prereleases when users choose an exact version or channel, and **Declaration order as display order.** JavaScript reorders integer-like object keys, so declaration order would need either an id pattern or array declarations. Clients sort declarations by their localized title instead. -**Dropping workspace or invalid peer ranges.** A generator would then hide requirements that installation reads. Retaining their literal strings lets the shared checker apply the existing workspace rules and report incompatibility. +**Selecting the greatest compatible version.** npm's `latest` can name an older release. Inferring a target from SemVer ranking loses the recorded channel choice and is ambiguous for equally ranked builds. -**Selecting the greatest compatible version.** npm's `latest` tag is publisher-controlled and can name an older release. Replacing it with version ranking loses that choice and leaves equal-precedence builds without a unique target. - -**Automatic prerelease fallback.** Missing or incompatible channel targets do not indicate that the user chose a prerelease. A client requires explicit version or channel selection instead. +**Automatic prerelease fallback.** Missing or incompatible channel targets do not mean that the user selected a prerelease. **Rejecting unknown members.** Every optional addition would then require a new `format` value and break every deployed reader. ## Acceptance criteria -- A validator accepts the example above and rejects one violation of each structural rule in Root fields, Plugin fields, Version fields, and the `source` forms, with a diagnostic naming the JSON path. Invalid DSH range strings are accepted structurally and evaluated as incompatible. +- A validator accepts the example above and rejects one violation of each rule in Root fields, Plugin fields, Version fields, and the `source` forms, with a diagnostic naming the JSON path. - Install arguments built from every example version are accepted by DSH's install-spec parser and by pnpm. - A validator ignores unknown members at every level. -- Index and installation compatibility match for the same manifest, runtime version, and local exemptions. Cases cover hyphen ranges, `||` alternatives, workspace shorthands, invalid ranges, a broad engine range with an incompatible DSH peer, and compatible peers with an unsatisfied or empty `engines.dsh`. -- Exact `dist-tags` targets select the same source when equally ranked build-metadata versions are reordered. Missing tag targets are rejected; absent or incompatible `latest` never causes an implicit version or channel switch. -- Author objects and shorthand strings produce the declared display name without contact fields or version normalization. -- A Git version parser validates `1.0.0+one` and preserves that complete string for tag lookup, installation identity, and local exemption keys. Its commit selects the exact source; npm source versions with build metadata are rejected. -- Installation confirmation identifies the selected version, channel when selected, prerelease status, index URL, and source. Market labels and scores remain attributed claims. +- Generator and validator refuse missing, blank, and invalid `engines.dsh`; omitted versions leave no dangling tag target. Index and market-origin installation agree on valid ranges, including hyphen ranges, prereleases, and valid ranges that reject the runtime. Exemptions never bypass the declaration requirement. +- Exact tag targets select the same source after equally ranked Git build versions are reordered. Missing targets are rejected; absent or incompatible `latest` causes no automatic switch. +- npm author objects and shorthand strings produce the declared name without contact fields or mutation of source identity. ## Risks -- An index is unsigned. A market can use a misleading `title`, such as one claiming to be official; clients must show the index URL and each version's `source`. -- A Git version's identity and requirements are claims about the repository. A repository can claim any name, including an `@deepseek-ai/dsh-` name, and the installed manifest decides what is actually installed. +- An index is unsigned. Titles, authors, labels, and scores are market claims; an `official` label is not DSH verification. Clients must show the index URL and each version's `source`. +- A Git version's `name`, `version`, and `engines.dsh` are claims about the repository. A repository can claim any name, including an `@deepseek-ai/dsh-` name, and the installed manifest decides what is actually installed. - An npm version's `source.registry` is equally a claim: it directs the install of any `name`, including an `@deepseek-ai/dsh-` name, to a registry the market chooses, and that registry decides what is installed. - URL icons disclose the user's IP address to image hosts. - Pinned versions and distribution tags stay stale until the index is regenerated. -- Version pinning does not authenticate a market's identity or guarantee the bytes an arbitrary registry returns. Installed package checks remain independent of the catalog. -- Readers, generators, and installation must adopt this revised proposal together before deployment; mixing the original and revised format-1 rules can change compatibility results and version selection. - Market text reaches users, and models through any client tool that lists plugins, without review by DSH. diff --git a/.agents/notes/proposed/feature/2026-10-02-plugin-market-index-format.zh.md b/.agents/notes/proposed/feature/2026-10-02-plugin-market-index-format.zh.md index 503f20df4f..29813bef25 100644 --- a/.agents/notes/proposed/feature/2026-10-02-plugin-market-index-format.zh.md +++ b/.agents/notes/proposed/feature/2026-10-02-plugin-market-index-format.zh.md @@ -16,8 +16,6 @@ DSH 只能从用户已知的 spec 安装第三方组合包(bundle):npm 包 将**索引**与**索引生成器**分开。DSH 定义并读取一种 JSON 索引格式。索引如何生成、基于哪份源列表生成,由生成器负责。DSH 的读取器、Discover 页面和参考生成器是使用此格式的独立变更。索引列出[profile 插件组合包](../../implemented/architecture/2026-08-05-profile-plugin-bundles.zh.md),并通过[引导式插件安装](../../implemented/architecture/2026-09-15-guided-plugin-installation.zh.md)安装它们。 -包作者使用 [npm 声明](https://docs.npmjs.com/cli/v11/configuring-npm/package-json/)。索引缓存选取的包元数据,并增加本地化显示数据、固定的来源和市场元数据。读取方和生成器使用持续维护的 npm 解析器和归一化工具。它们不引入另一份由作者独立维护的包身份、兼容性声明或作者字段。 - ### 示例 ```json @@ -56,15 +54,13 @@ DSH 只能从用户已知的 spec 安装第三方组合包(bundle):npm 包 "version": "0.2.0-rc.2", "publishTimestamp": 1790476800, "source": { "type": "npm" }, - "engines": { "dsh": "0.2.0-rc.2" }, - "peerDependencies": { "@deepseek-ai/dsh-tools": "0.2.0-rc.2" } + "engines": { "dsh": "0.2.0-rc.2" } }, { "version": "0.2.0-rc.1", "publishTimestamp": 1789872000, "source": { "type": "npm" }, - "engines": { "dsh": "0.2.0-rc.1" }, - "peerDependencies": { "@deepseek-ai/dsh-tools": "0.2.0-rc.1" } + "engines": { "dsh": "0.2.0-rc.1" } } ] }, @@ -82,7 +78,8 @@ DSH 只能从用户已知的 spec 安装第三方组合包(bundle):npm 包 "url": "https://github.com/author/dsh-plugins", "commit": "3f9c2e1d8a4b6c0e7f1a2b3c4d5e6f708192a3b4", "path": "packages/foo" - } + }, + "engines": { "dsh": "*" } } ] } @@ -111,29 +108,28 @@ DSH 只能从用户已知的 spec 安装第三方组合包(bundle):npm 包 | 字段 | 必填 | 规则 | |---|---|---| -| `name` | 是 | npm 持续维护的包名校验器接受的新包名称;在索引内唯一。 | +| `name` | 是 | npm 维护的包名校验器接受的新包名称;在索引内唯一。 | | `title` | 否 | `LocalizedText`;缺省时读取器显示 `name`。 | -| `description` | 否 | 用于市场列表显示的 `LocalizedText`;派生规则见生成器义务。 | +| `description` | 否 | `LocalizedText`。 | | `icon` | 否 | 以下两种形式之一:`data:;base64,`,其中 `` 为 `image/svg+xml`、`image/png`、`image/jpeg` 或 `image/webp`,`` 是按规范填充的 base64,解码后的载荷不超过 256 KiB;或者 URL 引用,它相对于索引 URL 解析的结果是 `https:` URL,或与索引 URL 同源。 | -| `author` | 否 | 至少包含一个非空白字符的显示名称;npm 作者投影规则见生成器义务。 | +| `author` | 否 | 至少包含一个非空白字符的字符串。 | | `homepage` | 否 | 介绍该插件的页面的绝对 `https:` 或 `http:` URL。 | | `metadata` | 否 | 包含下列三个值的对象。 | | `metadata.category` | 否 | 一个已声明的分类 id。 | | `metadata.labels` | 否 | 由互不相同的已声明标签 id 组成的数组。 | | `metadata.sortKeys` | 否 | 对象,将已声明的排序键 id 映射为有限数值。 | -| `dist-tags` | 否 | 对象,将 npm 分发标签名映射为 `versions` 中列出的精确版本字符串。缺省表示没有记录的渠道。 | -| `versions` | 是 | 非空的版本数组;见下文。数组顺序不决定默认版本。 | +| `dist-tags` | 否 | 将 npm 发布标签名称映射为已列出的精确版本字符串的对象;缺省表示未记录发布渠道。 | +| `versions` | 是 | 非空的版本数组;见下文。数组顺序不选择安装版本。 | ### 版本字段 | 字段 | 必填 | 规则 | |---|---|---| -| `version` | 是 | 由 npm 的 `node-semver` 解析的精确包版本;保留完整字符串,包括版本编译信息。精确字符串在插件内唯一。 | +| `version` | 是 | npm 的 `node-semver` 解析的精确版本;保留完整字符串,包括 Git 版本编译信息。精确字符串在插件内唯一。 | | `publishTimestamp` | 是 | 以秒为单位的整数 Unix 时间戳,取值范围为 `[0, 10^11)`。 | | `source` | 是 | `version` 的安装来源;见下文。 | -| `engines` | 否 | npm manifest 对象,将引擎名称映射为要求字符串,复制时不改写范围。 | -| `engines.dsh` | 否 | 对当前运行的 DSH 进程的要求字符串。空范围或无效范围不兼容;缺省不增加宿主要求。 | -| `peerDependencies` | 否 | npm manifest 对象,将包名映射为要求字符串,复制时不改写范围。DSH 对等依赖(peer dependency)约束兼容性;其他对等依赖仍由包管理器处理。 | +| `engines` | 是 | 包含 `dsh` 的对象。 | +| `engines.dsh` | 是 | 源 manifest 声明的非空白、有效 npm SemVer 范围;原样复制。`*` 显式声明支持任意 DSH 版本。 | 相对的 `icon` 引用相对于索引 URL 解析,与 HTML 图片引用相对于所在页面解析的方式相同。对于位于 `https://market.example/dsh/index.json` 的索引,`"icon": "icons/foo.svg"` 指向 `https://market.example/dsh/icons/foo.svg`。因此,生成器可以把索引和图标文件作为一个目录发布到任意静态主机,无需知道最终 URL。指向其他源的绝对图标 URL 必须使用 `https:`;同源规则允许位于 loopback 或内网主机上的 `http:` 索引提供自己的图标。读取器在读取索引时解析该引用,因此客户端收到的是绝对 URL。 @@ -151,15 +147,9 @@ DSH 只能从用户已知的 spec 安装第三方组合包(bundle):npm 包 ### DSH 兼容性 -索引将 manifest 的 `peerDependencies` 和 `engines` 作为两份独立声明保留。DSH 用当前运行的 DSH 版本检验每个 `@deepseek-ai/dsh` 与 `@deepseek-ai/dsh-*` 对等依赖。声明的 `engines.dsh` 增加一条宿主要求,不替代这些对等依赖。只有满足每条适用要求时,该版本才兼容。缺少声明不增加要求。 +市场列出的每个版本都必须声明有效的 `engines.dsh`。读取器与市场来源的安装使用 npm 的 `node-semver` 和 `includePrerelease: true`,根据当前运行的 DSH 版本求值。不存在对等依赖回退或范围合成。npm 包的对等依赖解析不属于市场宿主兼容性检查。 -这保留了既有的 [app-boot 对等依赖检查](../../../../packages/boot/app-boot/src/plugin-compatibility.ts),该检查显式接收当前运行的 DSH 版本。对等依赖声明不标识该进程;profile 中 npm 的包解析与 DSH 针对当前运行时的检查相互独立。 - -实现本提案时,须为该求值器增加 `engines.dsh` 强制检查及对应的引擎豁免处理。当前求值器只检查对等依赖。部署本格式前,索引读取器与安装检查都必须使用扩展后的求值器。 - -索引读取器和安装检查使用同一个 DSH 兼容性求值器,采用 npm 的 `node-semver` 解析器和 `includePrerelease: true`。空的或无效的 DSH 范围不兼容。既有对等依赖规则将 `workspace:*`、`workspace:^` 和 `workspace:~` 映射为当前运行的 DSH 版本;其他 `workspace:` 对等依赖范围不兼容。引擎范围不采用这种对等依赖映射。精确的本地 `name@version` 豁免适用于两种 DSH 要求,但不发布到索引中。 - -生成器保留每条要求字符串。读取器分别求值,不拼接或展开范围。npm 负责包的对等依赖解析及其 Node/npm 引擎检查。DSH 负责强制检查自定义 `engines.dsh` 键,规则记录在[公共 manifest 决策](../../implemented/architecture/2026-09-10-public-package-manifest.zh.md)中。 +安装接受市场来源的包前,会重新检查源 manifest。精确的本地 `name@version` 豁免可以允许有效范围拒绝当前运行时的情况,但不能使缺失或无效声明符合列出条件。豁免不发布在索引中。 ### 生成器义务 @@ -167,29 +157,25 @@ DSH 只能从用户已知的 spec 安装第三方组合包(bundle):npm 包 - `name` 和每个 `version` 与所安装来源中的包 manifest 一致。 - 对于 npm 来源,`publishTimestamp` 是 `version` 在 registry 中的发布时间;对于 Git 来源,它是 `commit` 的提交者时间。 -- `engines` 和 `peerDependencies` 复制来源 manifest 的声明。npm 来源使用已发布元数据,包括 npm 发布时的改写。Git 来源使用固定提交中的 manifest。无效的 DSH 范围作为不兼容要求保留,不省略,也不转换成其他声明。 -- `dist-tags` 保存 registry [分发标签](https://docs.npmjs.com/cli/v11/commands/npm-dist-tag/)的快照。生成器包含每个保留标签的目标版本;目标不在列出的组合包版本中时,省略该标签。对于 Git 来源,发布者或市场提供同样的精确标签到版本的映射。 +- `engines.dsh` 原样复制 manifest 声明的范围。缺失、空白或无效范围的版本会被省略并给出诊断。没有合格版本的插件会被省略。 +- `dist-tags` 是 npm [发布标签](https://docs.npmjs.com/cli/v11/commands/npm-dist-tag/)的快照,只保留指向合格且已列出版本的目标。对于 Git 来源,发布者或市场提供同样的精确标签到版本映射。 - 列出的每个版本都声明了 `dsh.bundle`。安装时也会独立拒绝没有组合包 patch 的包。 -- 来自包的显示字段取自 `dist-tags.latest` 指向的精确版本。没有该标签时,生成器省略这些显示字段,读取器回退到包名。`title`、`description` 和 `icon` 遵循已安装插件的显示元数据规则,同时满足本格式的 `LocalizedText` 要求。本地化文本是市场列表显示数据,不是另一份 npm manifest 声明。`homepage` 复制所选 manifest 的 `homepage`,manifest 没有该字段时省略。 -- npm 的 `author` 可以是人员对象或简写字符串。对于对象,生成器复制其包含非空白字符的 `name`。对于简写字符串,生成器使用持续维护的 npm 人员归一化工具,在相同条件下取结果中的 `name`。名称不存在或只有空白时省略,不将 email 或 URL 复制到该显示字段。生成器归一化一份新建的投影,不改动来源 manifest。 +- `title`、`description` 和 `icon` 来自所列最高版本的语言元数据、manifest 字段和图标,采用已安装插件的显示规则。使用 npm 的 `node-semver.rsort()` 顺序,相等时保留输入顺序。元数据选择不选择安装版本。`homepage` 复制该版本 manifest 的 `homepage`,manifest 没有该字段时省略。 +- `author` 来自同一最高版本的 npm 作者声明。复制对象的 `name`;使用维护中的 npm person 归一化器解析简写字符串。省略缺失或全空白的名称以及联系字段。归一化新建的投影,不修改源 manifest。 ### 读取器规则 -文档违反根字段、插件字段、版本字段或 `source` 形式中的任何规则时,读取器拒绝整个索引,包括引用了未声明的分类、标签或排序键,分发标签指向未列出的版本,`name` 或精确 `version` 字符串重复,以及 `format` 不是 `1`。每个此类缺陷都是生成器 bug,静默丢弃条目会掩盖它。读取器可以限制文档大小。 +文档违反根字段、插件字段、版本字段或 `source` 形式中的任何规则时,读取器拒绝整个索引,包括引用了未声明的分类、标签或排序键,发布标签指向未列出的目标,`name` 或精确的 `version` 字符串重复,以及 `format` 不是 `1`。每个此类缺陷都是生成器 bug,静默丢弃条目会掩盖它。读取器可以限制文档大小。 读取器在每一层都忽略本 Agent Note 未定义的成员;未知的 `source.type` 值会被拒绝。这样格式 1 可以增加可选成员而不破坏现有读取器;现有读取器会误解的变更使用新的 `format` 值。 -`engines`、`peerDependencies` 和 `dist-tags` 中的条目是声明的数据,不是应当丢弃的未知成员。其值必须为字符串;非字符串值使索引无效。DSH 要求字符串可以为空或语法无效,但仍作为不兼容要求保留。分发标签名必须使 `name@tag` 在 npm 持续维护的 `npm-package-arg` 解析器中成为 registry 标签 spec,而非版本或范围。标签目标与所列版本字符串精确匹配。 - -npm 来源使用规范的已发布 registry 版本标识:字符串等于 npm 的 `node-semver.valid()` 结果,不含版本编译信息。npm 发布时的归一化不提供仅版本编译信息不同的可独立选择版本。Git 来源可以保留此类版本,因为提交独立固定了所安装的来源。读取器拒绝不规范的 npm 来源版本,避免声称 npm 并不提供的精确构建选择。 +标签名称必须能被 npm 维护的 `npm-package-arg` 解析为 registry 标签 spec;目标是已列出的精确版本字符串。npm 来源使用规范发布标识(`node-semver.valid(version) === version`);Git 来源保留版本编译信息并用提交固定精确来源。 分类、标签和排序键对读取器不透明:读取器按它们筛选和排序,显示其 `title`,从不解释 id 的含义。读取器按本地化 `title` 对声明排序后显示。对插件排序时,无论排序方向如何,没有所选排序键值的插件都排在所有有值的插件之后,值相同的插件保持索引顺序。按发布时间排序时使用插件最大的 `publishTimestamp`。 -读取器按 DSH 兼容性一节的规则评估每个版本的声明要求。它按 npm SemVer 优先级对版本排序;优先级相同的版本保留索引顺序,该顺序只用于显示。完整版本字符串标识条目,包括仅版本编译信息不同的版本。 +读取器按 DSH 兼容性一节的规则求值必填的 `engines.dsh` 范围。版本使用 npm 的 `node-semver.rsort()` 顺序,相等时保留索引顺序。安装版本由精确的标签目标选择,而不是此显示顺序。 -初始渠道为 `latest`。标签的精确目标是发布者或市场选定的版本,即使另一个版本具有更高的 SemVer 优先级。客户端安装索引中列出的精确版本或提交,不使用 `name@latest`。没有 `latest` 时,需要选择版本。目标不兼容时,客户端显示该要求,并提供其他版本供显式选择。它不自动切换渠道,也不自动选择其他正式版本或先行版本。 - -用户选择精确版本或渠道时,以及安装确认中,客户端标识先行版本。发布者可以有意将先行版本标记为 `latest`;标签的存在不授权跳过该显式确认而安装。客户端将显示字段、标签、分类和评分归属于索引 URL。市场提供的 `official` 等标签不表示 DSH 验证,不同市场的评分也不隐含相同尺度。 +初始渠道是 `latest`。它的精确目标是发布者或市场选择的版本。客户端安装记录的版本或提交,绝不安装 `name@latest`。缺失或不兼容的 `latest` 需要显式选择版本或渠道;客户端不会自动切换。安装确认标明版本、所选渠道(如果有)、先行版本状态、索引 URL 和来源。 ## 考虑过的替代方案 @@ -207,11 +193,9 @@ npm 来源使用规范的已发布 registry 版本标识:字符串等于 npm **只允许内联图标。** 不发额外请求的列表需要内联图标,但 256 KiB 原始字节编码为 base64 后约为 341 KiB,因此 100 个插件的索引可能达到约 35 MB。URL 图标以额外请求和向图片主机暴露用户 IP 地址为代价,换取较小的索引。 -**将对等依赖范围合成为一个 `engines.dsh`。** 这会创建另一份兼容性声明,并需要范围求交逻辑。保留 npm 映射后,读取器与安装检查可以求值相同要求,包括连字符范围和 `||` 分支。 +**从对等依赖推断宿主兼容性。** 市场定义要求作者声明 `engines.dsh` 的新标准。现有包必须添加此声明才能列出;不需要回退、workspace 映射或范围求交。 -**让 `engines.dsh` 覆盖对等依赖。** 宿主要求与包要求表达不同义务。用宽泛的宿主范围替代包要求,可能掩盖不兼容的 DSH 对等依赖。 - -**将完整 npm 人员对象复制到显示字段。** 市场列表需要名称。记录规则的投影支持 npm 的原始对象与简写声明,同时省略未使用的联系字段。 +**将完整 npm person 对象复制到显示字段。** 列表需要名称。生成器支持 npm 的原始声明,同时省略不用的联系字段。 **用不透明排序值表示时间,包括 ISO 日期字符串。** 发布时间具有固定语义,客户端会对其格式化和排序,因此它是一个有类型的字段。排除时间后,排序值可以只用数值,无需为每个键规定值类型。 @@ -219,32 +203,26 @@ npm 来源使用规范的已发布 registry 版本标识:字符串等于 npm **以声明顺序作为显示顺序。** JavaScript 会重排类整数的对象键,因此要保留声明顺序,就需要限定 id 格式或改用数组声明。客户端改为按本地化标题对声明排序。 -**丢弃 workspace 或无效的对等依赖范围。** 生成器会隐藏安装检查要读取的要求。保留原始字符串后,共享检查器可以应用既有 workspace 规则并报告不兼容。 +**选择最高兼容版本。** npm 的 `latest` 可以指向较旧的发布版本。从 SemVer 排名推断目标会丢失记录的渠道选择,而且在构建排名相等时存在歧义。 -**选择最高兼容版本。** npm 的 `latest` 标签由发布者控制,可以指向较旧的发布版本。用版本排序替代它,会丢失这个选择,也会使优先级相同的构建没有唯一目标。 - -**自动回退到先行版本。** 渠道目标缺失或不兼容,不表示用户选择了先行版本。客户端要求显式选择版本或渠道。 +**自动回退到先行版本。** 渠道目标缺失或不兼容,不表示用户选择了先行版本。 **拒绝未知成员。** 这样每次增加可选成员都需要新的 `format` 值,并会破坏所有已部署的读取器。 ## 验收标准 -- 校验器接受上面的示例,并对根字段、插件字段、版本字段和 `source` 形式中的每条结构规则各拒绝一个违例,诊断信息给出对应的 JSON 路径。无效的 DSH 范围字符串通过结构校验,但求值结果为不兼容。 +- 校验器接受上面的示例,并对根字段、插件字段、版本字段和 `source` 形式中的每条规则各拒绝一个违例,诊断信息给出对应的 JSON 路径。 - 根据每个示例版本构建的安装参数可被 DSH 的安装 spec 解析器和 pnpm 接受。 - 校验器在每一层都忽略未知成员。 -- 对相同的 manifest、运行时版本和本地豁免,索引与安装检查的兼容性结果一致。用例覆盖连字符范围、`||` 分支、workspace 简写、无效范围、宽泛的引擎范围与不兼容的 DSH 对等依赖同时存在,以及对等依赖兼容而 `engines.dsh` 不满足或为空的情况。 -- 调整优先级相同的版本编译信息条目的顺序后,精确的 `dist-tags` 目标仍选择同一来源。读取器拒绝未列出的标签目标;`latest` 缺失或不兼容时,不隐式切换版本或渠道。 -- 作者对象与简写字符串产生声明的显示名称,不携带联系字段,也不归一化版本。 -- Git 版本解析器校验 `1.0.0+one`,并保留完整字符串供标签查找、安装身份和本地豁免键使用。提交选择精确来源;带有版本编译信息的 npm 来源版本被拒绝。 -- 安装确认标识所选版本、所选渠道(如有)、先行版本状态、索引 URL 和来源。市场标签与评分继续标明归属。 +- 生成器和校验器拒绝缺失、空白和无效的 `engines.dsh`;省略版本后不留下悬空的标签目标。索引和市场来源的安装对有效范围结果一致,包括连字符范围、先行版本以及拒绝当前运行时的有效范围。豁免绝不绕过声明要求。 +- 排名相等的 Git 构建版本重新排序后,精确标签目标仍选择同一来源。缺失的目标被拒绝;`latest` 缺省或不兼容时不自动切换。 +- npm 作者对象与简写字符串产生声明的名称,不包含联系字段,也不修改源身份。 ## 风险 -- 索引没有签名。市场可能使用误导性的 `title`,例如声称自己是官方市场;客户端必须显示索引 URL 以及每个版本的 `source`。 -- Git 版本的身份与要求是关于仓库的声明。仓库可以声明任意名称,包括 `@deepseek-ai/dsh-` 名称,实际安装的内容由已安装的 manifest 决定。 +- 索引没有签名。标题、作者、标签和评分是市场的声明;`official` 标签不是 DSH 验证。客户端必须显示索引 URL 以及每个版本的 `source`。 +- Git 版本的 `name`、`version` 和 `engines.dsh` 是关于仓库的声明。仓库可以声明任意名称,包括 `@deepseek-ai/dsh-` 名称,实际安装的内容由已安装的 manifest 决定。 - npm 版本的 `source.registry` 同样是一种声明:它把任意 `name`(包括 `@deepseek-ai/dsh-` 名称)的安装引向市场选择的 registry,由该 registry 决定实际安装的内容。 - URL 图标会向图片主机暴露用户的 IP 地址。 -- 固定版本与分发标签在索引重新生成之前一直保持陈旧。 -- 版本固定不验证市场身份,也不保证任意 registry 返回的字节。已安装包的检查与市场列表保持独立。 -- 读取器、生成器和安装检查必须在部署前一同采用这份修订提案;混用原始与修订后的格式 1 规则,可能改变兼容性结果和版本选择。 +- 固定版本和发布标签在索引重新生成之前一直保持陈旧。 - 市场文本未经 DSH 审核即到达用户,并通过任何列出插件的客户端工具到达模型。