diff --git a/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.i18n.yaml b/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.i18n.yaml index 433b12fa8c..7f3153a5b2 100644 --- a/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-26-published-dependency-faces.md -2026-08-26-published-dependency-faces.md: 04b02afe372b2f3d90729f17e837b40ff1e0e6fa -2026-08-26-published-dependency-faces.zh.md: 468fb443e2e94bf23f8c61201f43c2a82f7f509b +2026-08-26-published-dependency-faces.md: a4c745e8bc811027791317b334d285274f24dd20 +2026-08-26-published-dependency-faces.zh.md: 73b8dd28f40a63ea2a13544be535be7832138ba2 diff --git a/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.md b/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.md index 04b02afe37..a4c745e8bc 100644 --- a/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.md +++ b/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.md @@ -30,11 +30,40 @@ Workspace imports used by the Client bundle, type-only imports, module augmentat The verifier reads source manifests and source files, so it runs on a clean tree without built `lib/`. An unclassified Host runtime export is a policy violation that blocks all `--fix` writes; a maintainer must review the export and classify it, change the source relationship, or change the package selection. Once source safety passes, `--fix` performs only the section and range changes implied by the classification and removes stale peer metadata. +### Maintainer workflow + +Run the verifier without `--fix` for a read-only check of package selection, export classifications, dependency sections, workspace ranges, and peer metadata. An unclassified runtime import reports one clickable `path:line:column` diagnostic per imported export. + +```sh +pnpm run verify-package-dependencies +``` + +Classify each new Host runtime export in [`package-dependency-policy.ts`](../../../../scripts/package-dependency-policy.ts) before generating manifests. `safeHostDependencyExports` permits an ordinary dependency; `peerRequiredHostExports` keeps the whole provider package edge in matching peer and development sections. An export may appear in exactly one table. After refactoring a peer-required export so duplicate package copies are safe, move that exact specifier and export to the safe table; an edge becomes an ordinary dependency only after none of its imported exports remain peer-required. + +Generate managed manifest sections, refresh the lockfile separately, and review both results. `--fix` writes nothing while a policy violation exists; after success it prints the ordinary-dependency and peer-required edge lists, but does not update the lockfile. + +```sh +pnpm run verify-package-dependencies -- --fix +pnpm install --lockfile-only +git diff -- packages pnpm-lock.yaml +``` + +Measure the working-tree graph and a Git ref through the local metadata-only registry. Each run creates a fresh consumer and npm cache, executes `npm install --package-lock-only`, rejects archive downloads, and leaves the repository unchanged. `--runs` controls repetitions, `--timeout-ms` bounds each run, and optional `--max-ms` makes the command fail when the slowest run exceeds a threshold. + +```sh +pnpm run benchmark:npm-resolution -- --runs=5 --timeout-ms=300000 +pnpm run benchmark:npm-resolution -- --ref=origin/master --runs=5 --timeout-ms=300000 +``` + +Rank the next Host package by applying the current policy in memory, measuring a baseline, trying each reachable unconfigured package, and serially retesting the fastest coarse candidates. Positive `gainSeconds` is `baseline median - candidate median`; `--candidates` limits the roster, `--jobs` controls coarse concurrency, and neither phase writes manifests. A selected candidate still requires export classification before it joins `hostPackages`. + +```sh +pnpm run benchmark:npm-resolution:next -- --runs=1 --finalist-runs=5 --finalists=5 --jobs=8 --timeout-ms=120000 +``` + ### Performance verification -[`benchmark-next-package-dependency`](../../../../scripts/benchmark-next-package-dependency.ts) applies the current policy to an in-memory local registry, measures the current CLI graph, and tries every reachable unconfigured Host package one at a time. Concurrent runs provide a coarse shortlist; finalists run serially because npm's peer-placement search can take different paths when metadata request completion order changes. - -The benchmark is manual rather than a CI gate. It performs metadata-only installs in fresh consumers, so its relative results identify peer relays without measuring registry latency or archive downloads. +[`benchmark-npm-resolution`](../../../../scripts/benchmark-npm-resolution.ts) and [`benchmark-next-package-dependency`](../../../../scripts/benchmark-next-package-dependency.ts) remain manual rather than CI gates because resolver time varies with machine load and metadata completion order. Their fresh-consumer, metadata-only runs isolate npm's dependency-tree calculation from registry latency and archive downloads, so relative results identify peer relays without creating a release-time performance promise. ## Alternatives considered diff --git a/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.zh.md b/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.zh.md index 468fb443e2..73b8dd28f4 100644 --- a/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.zh.md +++ b/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.zh.md @@ -30,11 +30,40 @@ Client bundle 使用的 workspace import、纯类型 import、模块扩充、`ds 验证器读取源码 manifest 和源码文件,因此可以在没有已构建 `lib/` 的干净工作树上运行。未分类的 Host 运行期导出属于策略违规,会阻止 `--fix` 的全部写入;维护者必须审查该导出,并选择分类该导出、修改源码关系或修改选包范围。源码安全检查通过后,`--fix` 只执行分类所确定的区段与范围变更,并删除失效的 peer 元数据。 +### 维护流程 + +不带 `--fix` 运行验证器,会以只读方式检查选包范围、导出分类、依赖区段、workspace range 与 peer metadata。未分类的运行期 import 会按每个导出分别报告可点击的 `path:line:column` 诊断。 + +```sh +pnpm run verify-package-dependencies +``` + +生成 manifest 前,在 [`package-dependency-policy.ts`](../../../../scripts/package-dependency-policy.ts) 中分类每个新增 Host 运行期导出。`safeHostDependencyExports` 允许普通 dependency;`peerRequiredHostExports` 让整个提供包依赖边保留在范围一致的 peer 与开发区段。一个导出只能出现在一个表中。把 peer-required 导出重构到重复安装安全后,将该精确 specifier 与导出移入 safe 表;只有当一条依赖边的所有 import 都不再使用 peer-required 导出时,它才会成为普通 dependency。 + +生成受管 manifest 区段后,单独刷新 lockfile,并审查两部分结果。存在策略违规时,`--fix` 不写任何文件;成功后,它会分别打印普通 dependency 与 peer-required 依赖边,但不会更新 lockfile。 + +```sh +pnpm run verify-package-dependencies -- --fix +pnpm install --lockfile-only +git diff -- packages pnpm-lock.yaml +``` + +通过仅 metadata 的本地 registry 测量工作树依赖图与 Git ref。每轮都会创建全新 consumer 与 npm cache,执行 `npm install --package-lock-only`,拒绝下载包归档,并保持仓库不变。`--runs` 控制重复次数,`--timeout-ms` 限制单轮耗时,可选 `--max-ms` 会在最慢一轮超过阈值时让命令失败。 + +```sh +pnpm run benchmark:npm-resolution -- --runs=5 --timeout-ms=300000 +pnpm run benchmark:npm-resolution -- --ref=origin/master --runs=5 --timeout-ms=300000 +``` + +计算下一项 Host 包时,命令会在内存中应用当前策略、测量 baseline、逐个尝试可达且未配置的包,并串行复测粗筛中最快的候选。正数 `gainSeconds` 等于 `baseline median - candidate median`;`--candidates` 限定名册,`--jobs` 控制粗筛并发度,两个阶段都不写 manifest。选中的候选仍需先完成导出分类,才能加入 `hostPackages`。 + +```sh +pnpm run benchmark:npm-resolution:next -- --runs=1 --finalist-runs=5 --finalists=5 --jobs=8 --timeout-ms=120000 +``` + ### 性能验证 -[`benchmark-next-package-dependency`](../../../../scripts/benchmark-next-package-dependency.ts) 把当前策略应用到内存中的本地 registry,测量当前 CLI 依赖图,并逐个尝试每个可达且未配置的 Host 包。并发运行用于得到粗筛名单;由于 metadata 请求的完成顺序会让 npm 的 peer 放置搜索走不同路径,最终候选会串行复测。 - -Benchmark 是手动诊断工具而非 CI 门禁。它在全新 consumer 中执行仅 metadata 的安装,因此相对结果可以定位 peer 中继,但不测量 registry 延迟或包归档下载。 +[`benchmark-npm-resolution`](../../../../scripts/benchmark-npm-resolution.ts) 与 [`benchmark-next-package-dependency`](../../../../scripts/benchmark-next-package-dependency.ts) 保持为手动工具而非 CI 门禁,因为 resolver 耗时会随机器负载和 metadata 完成顺序变化。它们通过全新 consumer 和仅 metadata 的运行,把 npm 依赖树计算与 registry 延迟、包归档下载分离,因此相对结果可以定位 peer 中继,但不构成发布时性能承诺。 ## 考虑过的替代方案 diff --git a/scripts/package-dependency-policy.ts b/scripts/package-dependency-policy.ts index 1a172daafc..4160acd422 100644 --- a/scripts/package-dependency-policy.ts +++ b/scripts/package-dependency-policy.ts @@ -6,6 +6,7 @@ const CLIENT_FACE_INCLUDE: readonly string[] = [] /** Packages exempted from automatic Client/Host treatment despite declaring `dsh.client`. */ const CLIENT_FACE_EXCLUDE: readonly string[] = [ '@deepseek-ai/dsh-api-session-controller', + '@deepseek-ai/dsh-api-workspace-controller', ] /** Host-only packages whose peer relays are deliberately flattened. */ @@ -14,11 +15,40 @@ const HOST_DEPENDENCY_PACKAGES: readonly string[] = [ '@deepseek-ai/dsh-session', ] +/** + * Runtime exports whose values remain valid when npm installs another package copy. + */ +const SAFE_HOST_DEPENDENCY_EXPORTS = { + '@deepseek-ai/dsh-api-session-controller/remote-events': ['SESSION_CONTROLLER_REMOTE_EVENTS'], + '@deepseek-ai/dsh-credentials': ['credentialKey'], + '@deepseek-ai/dsh-host-apiproxy': ['toFetchHandler'], + '@deepseek-ai/dsh-host-apiproxy/api': ['RpcId', 'clientRequestSchema'], + '@deepseek-ai/dsh-llm': ['MessageId', 'callConfigEquals', 'deepFreeze', 'freezeMessage'], + '@deepseek-ai/dsh-llm/brand': ['ToolCallId'], + '@deepseek-ai/dsh-session': ['isJsonValue'], + '@deepseek-ai/dsh-settings': ['settingsNamespace'], + '@deepseek-ai/dsh-system-prompt': ['FIRST_PARTY_SECTION_ORDER'], + '@deepseek-ai/dsh-timeout': ['MAX_TIMER_DELAY_MS'], + '@deepseek-ai/dsh-util-crypto': ['randomUUID'], + '@deepseek-ai/schemastery': ['default'], +} as const satisfies HostDependencyExports + +/** Runtime exports that require every consumer to resolve the provider's shared peer instance. */ +const PEER_REQUIRED_HOST_EXPORTS = { + '@deepseek-ai/dsh-scope': ['carrierKeyOf', 'scopeOf', 'scopeTarget'], + '@deepseek-ai/dsh-typert-protocol': ['TypertLookupFailure', 'TypertRemoteFailure', 'remoteMethods'], +} as const satisfies HostDependencyExports + +/** Exact import specifier to reviewed runtime exports. */ +type HostDependencyExports = Readonly> + /** Complete configurable input to package dependency classification. */ export interface PackageDependencyPolicy { readonly clientFaceInclude: readonly string[] readonly clientFaceExclude: readonly string[] readonly hostPackages: readonly string[] + readonly safeHostDependencyExports: HostDependencyExports + readonly peerRequiredHostExports: HostDependencyExports } /** Repository dependency policy consumed by verification and benchmarking. */ @@ -26,6 +56,8 @@ export const PACKAGE_DEPENDENCY_POLICY: PackageDependencyPolicy = { clientFaceInclude: CLIENT_FACE_INCLUDE, clientFaceExclude: CLIENT_FACE_EXCLUDE, hostPackages: HOST_DEPENDENCY_PACKAGES, + safeHostDependencyExports: SAFE_HOST_DEPENDENCY_EXPORTS, + peerRequiredHostExports: PEER_REQUIRED_HOST_EXPORTS, } function isRecord(value: unknown): value is Record {