Repository Wiki
deepseek-ai/deepseek-harness

构建、测试快照与性能基准

本页说明仓库中用于构建本地 npm registry 元数据、执行依赖解析测试快照,以及比较 npm 解析性能的基准工具。当前实现的核心是 scripts/benchmark-npm-resolution.ts 与 scripts/benchmark-next-package-dependency.ts;仓库中的运行入口说明位于 BENCHMARK.md。

Purpose and Scope

本页覆盖以下闭环:

  • 从 workspace、已安装的 pnpm virtual store 或指定 Git ref 收集 package manifest;
  • 将 workspace 协议范围转换为发布到 registry 后的 semver 范围;
  • 构建仅包含元数据的本地 registry index;
  • 通过 npm 生成依赖解析结果和 package-lock 布局,作为“测试快照”式的可检查结果;
  • 测量解析耗时、registry 请求、archive 请求和未知包;
  • 使用粗筛选、候选包注入和 finalist 重复测量来寻找最能减少 peer resolution 的 Host package。

本页不覆盖 Python SDK 的安装细节、jsonrpc-agent 的业务行为,也不把一般 package dependency policy 检查扩展为本页主题。BENCHMARK.md 只给出了运行前置条件;更完整的 SDK 使用方式应留在对应的 SDK 文档页。

Overview

该能力把“真实发布包的依赖元数据”与“npm 的解析算法”隔离开来:工具不直接下载待测包 archive,而是启动本地 HTTP registry,提供从 workspace manifest 和已安装依赖中构造出的 package metadata。解析完成后,工具读取 npm 输出的 package-lock,并将 registry 请求、archive 请求、未知包和耗时作为观测结果返回。

基准脚本进一步在内存中复制 registry index,对候选 Host package 应用经过 source-derived policy 修复的 manifest,然后重复执行 npm resolution。这样可以比较“加入某个 Host package 后,解析是否更快”,而不修改工作区文件或真实 registry 数据。

Architecture

Loading diagram...

图中的 buildRegistryIndex、applyFactsToRegistry、benchmarkNpmResolution 和 NpmPackageLockResolution 都是源码中的真实符号。RegistryIndex 先成为解析服务的元数据来源,再被 npm 解析过程消费;package-lock 既是 npm 选择出的安装布局,也是基准结果中可进一步检查的快照对象。

Source: benchmark-npm-resolution.ts Source: benchmark-next-package-dependency.ts

元数据索引与发布语义

RegistryIndex 的数据边界

scripts/benchmark-npm-resolution.ts 将 registry index 定义为 Map<packageName, Map<version, RegistryVersion>> 的只读视图。每个 RegistryVersion 至少包含 name 与 version,并可携带 dependencies、optionalDependencies、peerDependencies、peerDependenciesMeta、engines、平台限制和 bin 信息。构建时只保留 PUBLISHED_FIELDS 中的字段,因此基准关注的是 npm 解析真正需要的已发布元数据,而不是完整工作区文件。

工作区依赖不能直接把 workspace:*、workspace:^ 和 workspace:~ 交给 registry。publishWorkspaceRange 按当前 workspace 版本转换它们:* 变成精确版本,^ 和 ~ 分别变成对应的 semver 范围;其他 workspace: 前缀则去掉前缀。这一步模拟了 pnpm pack 后发布 manifest 的依赖表达,避免基准因使用了非 registry 语法而失真。

typescript
1export function publishWorkspaceRange(range: string, targetVersion: string): string { 2 if (range === 'workspace:*') return targetVersion 3 if (range === 'workspace:^') return `^${targetVersion}` 4 if (range === 'workspace:~') return `~${targetVersion}` 5 if (range.startsWith('workspace:')) return range.slice('workspace:'.length) 6 return range 7}

Source: benchmark-npm-resolution.ts

buildRegistryIndex(root, ref?) 有两种输入路径:没有 ref 时,从工作树 glob 找 workspace manifest;有 ref 时,通过 git ls-tree 找指定 ref 中的 manifest,再用 git cat-file --batch 批量读取内容。外部依赖则从 pnpm virtual store 的 manifest glob 中收集。由此,基准可以测当前工作树,也可以复现历史 Git ref,而不需要将历史版本检出到工作区。

Source-derived policy 的应用

next-package benchmark 先读取依赖状态。如果存在 policyViolations,主流程立即抛出错误,不继续测量。否则,它克隆基础 index,并对每条 PackageDependencyFacts 调用 applyFactsToRegistry。该函数深拷贝 manifest,运行 repairPackageDependencyManifest,按照 workspace 当前版本重写依赖范围,然后覆盖 registry 中同名同版本的 dependencies、optionalDependencies、peerDependencies 和 peer metadata。若目标版本不存在,函数抛出明确错误,防止基准悄悄使用不完整的 registry。

typescript
1export function applyFactsToRegistry( 2 index: Map<string, Map<string, MutableRegistryManifest>>, 3 facts: PackageDependencyFacts, 4 workspaceVersions: ReadonlyMap<string, string>, 5): void { 6 const source = structuredClone(facts.manifest) 7 repairPackageDependencyManifest({ ...facts, manifest: source }) 8 const version = workspaceVersions.get(source.name ?? '') 9 const target = version === undefined ? undefined : index.get(source.name ?? '')?.get(version) 10 if (target === undefined) throw new Error(`local registry has no ${source.name ?? 'unnamed package'}@${version ?? 'unknown'}`) 11 for (const field of ['dependencies', 'optionalDependencies', 'peerDependencies'] as const) { 12 const values = publishedSection(source[field], workspaceVersions) 13 if (values !== undefined) target[field] = values 14 else if (field === 'dependencies') delete target.dependencies 15 else if (field === 'optionalDependencies') delete target.optionalDependencies 16 else delete target.peerDependencies 17 } 18 if (source.peerDependenciesMeta === undefined) delete target.peerDependenciesMeta 19 else target.peerDependenciesMeta = structuredClone(source.peerDependenciesMeta) as Record<string, { optional?: boolean }> 20}

Source: benchmark-next-package-dependency.ts

这种“先 clone、再修复、最后覆盖”的顺序很重要:每个候选项都应基于相同基础 registry 独立比较,不能把前一个候选的修改泄漏给后一个候选。

核心流程:解析快照与基准测量

Loading diagram...

Source: benchmark-npm-resolution.ts

一次解析的结果类型 BenchmarkRun 包含四类观测:durationMs、registryRequests、archiveRequests 和 unknownPackages。扩展类型 NpmPackageLockResolution 再附加 packageLock,其 packages 字段记录 npm 最终选择的安装路径布局。这个设计把“性能数字”和“解析是否得到预期布局”放在同一个结果中,便于测试脚本同时检查速度与正确性。

next-package benchmark 的 measure 函数按指定次数串行执行 benchmarkNpmResolution,每次明确拒绝 archiveRequests > 0 的结果,并把毫秒转换为保留两位小数的秒数。串行重复让同一候选的测量次数可解释;候选之间则通过 mapConcurrent 使用有限 worker 并发,提高整体搜索速度。

typescript
1async function measure( 2 index: RegistryIndex, 3 targetVersion: string, 4 runs: number, 5 timeoutMs: number, 6): Promise<number[]> { 7 const seconds: number[] = [] 8 for (let run = 0; run < runs; run += 1) { 9 const result = await benchmarkNpmResolution(index, targetVersion, timeoutMs) 10 if (result.archiveRequests > 0) throw new Error('metadata-only benchmark requested package archives') 11 seconds.push(Number((result.durationMs / 1000).toFixed(2))) 12 } 13 return seconds 14}

Source: benchmark-next-package-dependency.ts

候选发现与两阶段搜索

discoverBenchmarkCandidates 从 @deepseek-ai/dsh 开始做队列遍历。对每个可达包,它合并普通依赖、可选依赖以及非 optional peer dependency,并把尚未访问的依赖加入队列。遍历结束后,只保留:不是 policy package、存在 release manifest、且当前 manifest 至少含有一个非 @deepseek-ai/cordis peer dependency 的包。这使搜索空间限定在实际可达、可发布并与目标 peer-resolution 问题相关的 Host package。

next-package 的选项默认值来自实际解析代码:粗筛 coarseRuns 为 1,finalist 测量为 3,保留 finalist 数为 5,jobs 默认是 min(8, availableParallelism()),超时默认 120000 ms。若传入 --candidates,每个候选必须属于自动发现集合,否则直接报错。

typescript
1return { 2 ...(values.candidates === undefined 3 ? {} 4 : { candidates: values.candidates.split(',').filter(Boolean) }), 5 coarseRuns: parsePositiveIntegerOption(values.runs, 1, '--runs'), 6 finalistRuns: parsePositiveIntegerOption(values['finalist-runs'], 3, '--finalist-runs'), 7 finalists: parsePositiveIntegerOption(values.finalists, 5, '--finalists'), 8 jobs: parsePositiveIntegerOption(values.jobs, Math.min(8, availableParallelism()), '--jobs'), 9 timeoutMs: parsePositiveIntegerOption(values['timeout-ms'], 120_000, '--timeout-ms'), 10}

Source: benchmark-next-package-dependency.ts

配置与 CLI 参数

工具选项类型默认值语义
benchmark-npm-resolution--refstring未设置从指定 Git ref 读取 workspace manifests
benchmark-npm-resolution--runs正整数1单次基准重复次数
benchmark-npm-resolution--timeout-ms正整数300000单次 npm resolution 超时
benchmark-npm-resolution--max-ms正整数,可选未设置可选的最大耗时限制
benchmark-next-package-dependency--candidates逗号分隔字符串自动发现指定待测 Host packages
benchmark-next-package-dependency--runs正整数1候选粗筛重复次数
benchmark-next-package-dependency--finalist-runs正整数3finalist 重复次数
benchmark-next-package-dependency--finalists正整数5保留 finalist 数量
benchmark-next-package-dependency--jobs正整数min(8, availableParallelism())候选间并发 worker 数
benchmark-next-package-dependency--timeout-ms正整数120000候选测量超时

所有正整数选项都经过 parsePositiveIntegerOption 校验:缺省时使用 fallback;显式值必须是 safe integer、至少为 1,且字符串不能包含额外格式,否则抛出带选项名的错误。--max-ms 在底层脚本中是可选限制;它与 --timeout-ms 不应混为同一概念。

API Reference

parseBenchmarkOptions(args: readonly string[]): BenchmarkOptions

解析底层 npm resolution benchmark 的 --ref、--runs、--timeout-ms 和 --max-ms。返回经验证的 BenchmarkOptions;非法正整数会抛出 Error。

buildRegistryIndex(root: string, ref?: string): RegistryIndex

从工作树或指定 Git ref 构建 registry 元数据索引。root 必须是包含 workspace 与 pnpm virtual store 的仓库根目录;指定 ref 时,读取的是 Git 对象中的 manifest,而非工作树版本。

benchmarkNpmResolution(index: RegistryIndex, targetVersion: string, timeoutMs?: number): Promise<BenchmarkRun>

使用给定的 registry metadata 对目标版本执行 npm 解析,并返回耗时、请求统计、未知包及解析结果相关观测。底层实现的具体 HTTP server 生命周期和 npm 子进程细节在本次读取范围之外;这些细节不在本页臆测。

parseNextPackageBenchmarkOptions(args: readonly string[]): Options

解析候选搜索的重复次数、候选数、并发数和超时。它还将逗号分隔的 --candidates 转换为去除空值的数组。

失败模式、边界条件与并发

输入和一致性错误

  • parsePositiveIntegerOption 对非正整数、超出 safe integer 的值、带前导或尾随格式差异的字符串抛出错误;因此 CLI 不会把模糊参数静默解释成其他次数。
  • buildRegistryIndex 在读取 Git ref 时会检查 git cat-file --batch 的 header、对象大小、missing 标记和结尾换行;读取缺失或截断对象会失败,而不是生成不完整索引。
  • applyFactsToRegistry 找不到 workspace package 对应的 registry name/version 时立即抛错。这避免把 policy 修复应用到错误版本。
  • benchmark-next-package-dependency 在发现 policy violation 时停止;显式传入不可达或未配置的 candidate 也会停止。
  • metadata-only 基准一旦观测到 archive 请求就失败。该约束是核心正确性检查:如果发生 archive 请求,测量的就不再是纯依赖元数据解析。

并发边界

mapConcurrent 创建不超过 min(jobs, values.length) 个 worker,共享递增的 next 游标,每个 worker 处理一个候选后继续领取下一个。结果按原候选索引写回,因此并发不会改变输出排序。源码没有显示跨进程共享状态或持久化缓存;可以确认的并发范围仅是同一进程内候选 benchmark 的 worker 并发。

性能与运营注意事项

  • 底层 benchmark 默认单次超时 300 秒;候选 benchmark 默认 120 秒。运营上应根据 CI 预算显式设置 timeout,而不是假设两个脚本相同。
  • 粗筛与 finalist 分离,目的在于先低成本比较所有候选,再对少数候选重复测量并计算 median。源码中 Measurement 持有原始秒数和 medianSeconds,因此最终排序应优先使用中位数而不是单个样本。
  • structuredClone 用于复制 manifest 和 registry index,换取候选之间的隔离;代价是内存复制,但避免了基准污染。
  • availableParallelism() 只用于设置候选搜索的默认 worker 上限,并额外限制为 8。它不是 npm 解析过程的并发配置。
  • BENCHMARK.md 要求使用独立 workspace 和 session ID 执行独立 benchmark task;该操作约束是当前仓库运行说明中明确给出的隔离策略。

测试快照的解释边界

本实现没有在已读取源码中发现一个名为 snapshot 的持久化文件格式或独立 snapshot writer。可以确认的“快照”对象是 NpmPackageLockResolution.packageLock:它记录 npm 在本次解析中选出的 package path 与 manifest 关系,并与请求计数和耗时一起返回。若需要断言依赖树,建议围绕该 packageLock.packages 结构进行测试;具体测试断言和 fixture 不在本次源代码摘录中,因此不在此虚构测试命令或预期内容。

扩展点

  1. 增加发布字段:如果解析场景需要新的 package.json 发布字段,应在 PUBLISHED_FIELDS 增加字段,并确认 PackageManifest、RegistryVersion 和复制逻辑都能保留它。
  2. 改变 workspace 转换策略:修改 publishWorkspaceRange 时必须同时考虑 workspace manifest 的发布语义与 registry 解析兼容性;这会直接影响所有 benchmark 结果。
  3. 增加候选筛选策略:候选发现当前以从 @deepseek-ai/dsh 可达、具有 release manifest 和非 Cordis peer 为边界。新增筛选条件应保持可解释,并避免把不可达 package 纳入比较。
  4. 扩展观测结果:BenchmarkRun 已将耗时、registry/archive 请求与 unknown packages 分开表达;可在此基础上增加观测字段,但应保持 metadata-only 检查清晰可见。

运行入口与相关链接

BENCHMARK.md 的现有说明要求先按照 Python SDK 指南安装 SDK 并运行 jsonrpc-agent minimal variant,然后为独立 benchmark 使用独立 workspace 和 session ID。底层 benchmark 的参数与实现请以 benchmark-npm-resolution.ts 为准;候选搜索和 policy 注入请以 benchmark-next-package-dependency.ts 为准。