---
description: extras 的 enrichment 模块——user prompt enrichment 小框架：解析本轮用户提示词中的路径引用，运行 provider 注入 per-path relates 上下文（ctx.enrichment 注册表 + Web 配置页开关）；内嵌 parse / tree 两个纯库
---

# enrichment 模块（`@catheadowl/dsh-extras` 一行）

**价值**：让 dsh 会话在用户提到某个路径时自动获得该路径的定向上下文（面包屑、关联说明等），而不是让模型盲猜或用户手动粘贴——注入只按路径聚合、不改写用户消息、不阻断轮次。

**与宿主的关系**：挂在 dsh 的 `agent/pre-step` 拦截点上做一个 provider 注册表（`ctx.enrichment`），本模块只实现承载层，不内置 cognition 或面包屑等业务逻辑——那些由其他插件/模块作为 provider 注册。为什么是框架而不是各插件直挂 pre-step：一个注册面 + 统一 runner（once 去重 / 预算 / 超时 / 降级 / 可见性纪律），代替每个注入者重复造注入管线、互相不知情地抢占上下文。与宿主的整体关系见包根 [docs/host.md](../../docs/host.md)；本模块的完整论证见 [docs/why-enrichment.md](docs/why-enrichment.md)。

安装本包见[包根 README](../../README.md)（`dsh plugin add`，enrichment 是其中一行）。链路一句话：pre-step 解析 user prompt 中的路径提及 → provider 产出 **relates**（对被提及路径追加的关联上下文条目，`value` / `href`）→ 聚合去重预算后随会话注入。

## 提供面

| 面 | 说明 |
|---|---|
| `ctx.enrichment` | `register(provider)` / `registerRelates(provider)` / `list()` / `listViews()` / `introspect()` / `disabledIds()` / `setDisabled(names)` / `run(options)` / `clearSession(sessionId)`——消费插件经 `ctx.inject(['enrichment'], …)` 软依赖注册（结构类型镜像，零 import；声明式入口 `registerRelates` 由框架物化为 provider 并复用整套 runner） |
| `agent/pre-step` driver | 收集本 step 的 subject（prompt 路径解析 + 上一步 pending touch 的 `touchSubjects` 投影），按 provider 的 `sources` 订阅过滤喂入，运行 provider，向当前 step 的 admitted 请求批追加 relates 上下文 |
| `tools/result` sensor | 框架统一持有的 touch 信号监听：闭集 `{read, edit}`、错误/中止/无 agent 剔除、嵌套上浮到根执行、按 session cwd 归一到项目相对键空间；touch 时对产出 subjects 摘 once 账（失效面向所有声明者），原始 touch 记入 per-session pending、turn 边界丢弃残余 |
| Typert Remote `enrichment` | `list` / `setDisabled`：插件页 enrichment 行（Configure）配置面（provider 开关）；`introspect`：只读自省快照（`sources` / `effectiveEnabled` / `disabledBy`，见 [docs/contract.md](docs/contract.md)「自省快照」）。Typert Remote 是宿主的 Web RPC 面 |
| client 半 | `plugins.row.config` slot（键 `@catheadowl/dsh-extras#enrichment`）：扁平 provider 列表 + 开关，localStorage 持久化（经 extras 嵌套 client 锚点包 `@catheadowl/dsh-extras-client` 的合成 bundle 装载，见 `modules/client/README.md`） |

## Quickstart（声明式 provider 注册）

消费插件不 import 本包：结构镜像 provider 声明的字段子集，经 `ctx.inject` 条件注入注册（未装 enrichment 行时你的插件照常加载、只是不注册）。

```ts
import type { Context } from '@deepseek-ai/cordis'

const notesProvider = {
  name: 'my-plugin-notes',
  kind: 'my-notes',          // 稳定 kind；mode 默认 'once'（session 内每 path 一次）
  async resolve({ path }) {
    const note = path.kind === 'directory' ? await loadNoteFor(path.path) : undefined
    return note ? { value: note } : undefined
  },
}

export function apply(ctx: Context): void {
  void ctx.inject(['enrichment'], (enrichmentCtx) => {
    return (enrichmentCtx as unknown as {
      enrichment: { registerRelates(provider: typeof notesProvider): unknown }
    }).enrichment.registerRelates(notesProvider)
  })
}
```

注册配方与 tool-touch 信号源（`sources` / `touchSubjects`）的完整用法见 [docs/cookbook.md](docs/cookbook.md)；字段级契约见 [docs/contract.md](docs/contract.md)。

## provider 形状

```ts
interface EnrichmentProvider {
  name: string
  priority?: number
  timeoutMs?: number
  mode?: 'always' | 'once'
  run(input: EnrichmentInput): Promise<RelatesContribution[]>
}
```

provider 只返回结构化 contribution，不拼最终 prompt，不改写用户消息，不阻断轮次。
排序为 `priority` 升序，再按注册顺序；重复 provider name fail loud。

`mode` 默认 `'always'`（每轮都跑都注入）；`'once'` 按 `(sessionId, provider, key)` 在 session 内去重，只记**实际渲染**的 item；surface replace（compact 等）触发 `clearSession` 清账后可重新注入。完整 once 记账规则见 [docs/contract.md](docs/contract.md)。

声明式面（`registerRelates`）让消费者只写单 path 的 `resolve` + 一个稳定 `kind`，框架物化为 imperative provider 并复用同一 runner（once ledger / 聚合 / 预算 / 超时 / 降级 / 渲染）。默认 `once`，`mode: 'always'` 显式 opt-in；显式 `'once'` 与空 `kind` 在注册期 fail loud。注册示例见上方 Quickstart 与 [docs/cookbook.md](docs/cookbook.md)。

provider 开关：插件页 enrichment 行（Configure）按 provider name 全局开关，是纯执行过滤（被关 provider 不进 pre-step 执行路径），不触碰 once 账本；细节见 [docs/contract.md](docs/contract.md)。

完整注入契约（once 记账、声明式 `subjectOf` 重键、定序、开关过滤点）见 [docs/contract.md](docs/contract.md)。

本机命令（check-types / build / test 与组合测试前置）见 [docs/development.md](docs/development.md)。

## 模块内库

文档入口：[docs/README.md](docs/README.md)。

- `src/parse/`：fuzzy/parse/resolve 纯库，契约文档 [docs/parse.md](docs/parse.md)；
- `src/tree/`：gitignore-aware 枚举（vendored `ignore`），契约文档 [docs/tree.md](docs/tree.md)。两库不再单独发布（抽取规则：第二个外部消费者出现时再抽）。

## 边界

- `src/parse` 是模块内纯库，不作为插件形态提供。
- `ctx.fileReferences` 仍是 host/file candidate seam，不被替代。
- 业务 provider 不内置在本行：面包屑、认知链接等由各注册方插件/模块自注册（各自文档负责清单）。声明式契约见 [docs/contract.md](docs/contract.md)。
- v0 不做 prompt rewrite / blocking / provider 注册参数编辑 UI（priority / kind / mode 在配置面只读展示，编辑是另一个问题域）。provider 开关配置面已落地（见上文）。
