# dsh-project-prompt

[English](README.md) | **中文**

[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)（DSH）的私有项目级提示词插件。

有些指引属于你的机器，而不属于仓库：与环境绑定的端到端测试方法、仅内网可达的入口、个人工作流偏好。`AGENTS.md` 是要提交共享的，放这些内容不合适。本插件把这类文本**放在 `$DSH_HOME`，绝不进仓库**，并在工作区命中规则的每个会话中注入——包括所有子代理和该仓库的任意 `git worktree`。

```mermaid
flowchart LR
    subgraph repo["git 仓库（任意 clone / worktree / 子目录）"]
        wt1["主检出"]
        wt2["linked worktree A"]
        wt3["worktree B"]
    end
    cfg["$DSH_HOME/cordis.patch.yml\nrules（机器本地，私有）"]
    plugin["dsh-project-prompt\nagent 生命周期监听器"]
    sp["会话系统提示词\n（或首条注入消息）"]
    cfg --> plugin
    wt1 & wt2 & wt3 -->|按 remote / repo / path 匹配| plugin
    plugin -->|section / inject| sp
```

## 特性

- **三种匹配键** —— 按 git `remote` URL（任意位置的 clone 都命中）、本地主仓库路径、或 cwd 前缀匹配；同一规则内多键为「或」关系。
- **感知 worktree** —— linked worktree 经 `.git` 文件的 `gitdir:` 指针回溯主仓库，规则写一次，之后新建的 worktree 及其子目录全部命中。
- **两种注入模式** —— `section`（稳定的系统提示词段，KV cache 友好，支持 `{{cwd}}`/`{{model}}`）与 `inject`（首条上下文消息，不做模板插值——文本含字面量 `{{...}}` 花括号（如 Helm/Go 模板）时使用）。
- **覆盖子代理** —— 子代理继承会话 cwd，委派出去的工作同样遵守规则。
- **加载期快速失败** —— 规则写错（缺 `text`、section 模式出现未知 `{{var}}`）在插件加载时抛错，而不是每个请求都炸。
- **零依赖** —— 单个 ESM 文件，只 import Node 内置模块；无构建、无产物。

## 环境要求

- DeepSeek Harness ≥ `0.1.1-rc.2`（已在 `0.1.5-rc.2` 与 `0.1.7-rc.1` 上验证）。依赖 agent 生命周期边：0.1.5 及以前是 `agent/session-start`，0.1.7 起是 `agent/created`；另用 `systemPrompt.section` 与 `agent.inject`。
- Node.js ≥ 18（DSH 本身当前要求 ≥ 22）。

## 安装

用 DSH 的插件命令安装到某个 profile：

```bash
dsh plugin --profile web add dsh-project-prompt
# 或直接从 GitHub 仓库安装：
dsh plugin --profile web add github:imroc/dsh-project-prompt
```

安装后重启 DSH —— bundle 在启动时参与组合。

卸载：

```bash
dsh plugin --profile web remove dsh-project-prompt
```

## 配置

规则写在 `$DSH_HOME/cordis.patch.yml`（默认 `~/.dsh/cordis.patch.yml`）——机器本地层，对**本机所有 profile** 生效。覆盖 bundle 安装的行：

```yaml
- id: project-prompt
  config:
    rules:
      # 该仓库不管 clone 到哪、开多少 worktree 都命中
      - remote: github.com/my-org/my-project
        text: |
          ## 本项目 E2E 测试方法（环境相关，私有）

          1. 测试环境入口：http://e2e.internal.example.net（仅内网可达）
          2. 首次跑 E2E 前先执行 `make e2e-prepare`。
          3. 用例失败时优先排查 ……

      # 按本地主仓库路径匹配；文本含字面量 {{...}} 花括号，须用 inject 模式
      - repo: /home/me/dev/another-project
        mode: inject
        text: |
          {{ .Values.replicas }} 这类字面量在这里原样保留。

      # 按目录前缀匹配
      - path: /home/me/dev
        text: |
          ...
```

该文件受监听——保存即热重载插件行（对之后新开的会话生效）。

### 规则参考

| 键 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `remote` | string | — | 匹配 git remote URL（origin）。比较前做归一化：忽略协议、`user@`、scp 风格 `:` 分隔符、尾部 `.git` 与斜杠、大小写。`git@github.com:u/r.git` ≡ `https://github.com/u/r`。也支持 `host/org/repo` 后缀形式。 |
| `repo` | string | — | git **主**仓库的本地绝对路径。其任意 linked worktree 与任意层级子目录都命中（worktree 经 `.git` gitdir 指针回溯）。 |
| `path` | string | — | cwd 前缀：会话工作区等于该目录或位于其下。 |
| `text` | string | 必填 | 要注入的提示词文本。 |
| `mode` | `section` \| `inject` | `section` | 注入模式，见下。 |
| `sectionName` | string | `project-prompt`（同一会话命中多条 section 规则时自动递增） | 系统提示词段名。 |
| `order` | number | `50` | 系统提示词段顺序（DSH 惯例：`0` 人设，`100–199` 工具指引）。 |

一条规则至少需要 `remote` / `repo` / `path` 三者之一；命中的多条规则全部生效。

### 注入模式

| | `section`（默认） | `inject` |
| --- | --- | --- |
| 落点 | agent 作用域 `systemPrompt` 服务上的系统提示词段 | 首条 user 侧上下文消息（`AGENTS.md` 内容走的同一条路） |
| 可见性 | 该会话每次请求 | 该会话每次请求 |
| 模板插值 | 有 —— `{{cwd}}`、`{{model}}` 会解析；其它完整的 `{{...}}` 组会**抛错** | 无 —— 花括号按字面量保留 |
| 适用 | 稳定指引；KV cache 友好 | 含字面量 `{{...}}` 的文本（Helm/Go 模板、Terraform 等） |

插件在加载期校验 section 文本：未知 `{{var}}` 会让加载失败，错误信息会提示把该规则改为 `mode: inject`。

## 工作原理

1. 每个新会话（新建、恢复、clear/compact 之后——每次 publish 都产生新的 agent 作用域）在**首个模型请求之前**走 agent 生命周期边：0.1.5 及以前是 emit 模式的 `agent/session-start`，0.1.7 起是 serial 并会被首个请求 await 的 `agent/created`。插件两边都订阅，只做一次。
2. 插件读取 `agent.session.header.cwd` 并逐条匹配规则；git 仓库身份通过从 cwd 向上找 `.git` 得出（`.git` 是文件说明是 linked worktree，其 `gitdir:` 指针回溯主仓库根与 remote URL）。
3. 命中后，规则文本注册在 `agent.ctx`——agent 作用域上下文——只对该 agent 生效、随其销毁自动撤销。
4. 该会话的每次模型请求都会重新组装系统提示词（或重读注入消息），规则文本全程在场。

「模型可见⟺已记录」：注入的 section 会出现在会话日志（`$DSH_HOME/sessions/…`）记录的请求头里，这也是验证规则是否生效的方法。

### 验证安装

1. 确认行已组合：`dsh --profile web --dump-config | grep -A3 project-prompt`。
2. 在命中目录新开会话，问一个规则文本应当影响回答的问题。
3. 或检查会话日志：该会话的 `session.jsonl.zstd` 中记录了含你 section 的完整系统提示词。

## 限制

- 匹配依据是路径/remote 身份而非内容：同一 remote 的不同 clone 都会命中 `remote` 规则（这通常正是目的）。
- 子模块目录（`.git` 指向 `.git/modules/…`）不做回溯；请用 `path` 规则。
- 规则是静态配置——本插件刻意不从仓库内读文件（那会重新引入共享状态）。工作区内的文件请用 DSH 内建的 `AGENTS.md`/`CLAUDE.md` 加载机制。

## 开发

```bash
git clone github:imroc/dsh-project-prompt && cd dsh-project-prompt
npm test          # node --test；git fixture 在临时目录中自建
node --check index.js
```

插件是单个零依赖 ESM 文件；提交的源码即发布产物（无构建、无 `prepare` 脚本——这也是 git 安装无需 `pnpm allowBuilds` 的原因）。保持这一点的约束说明见 [AGENTS.md](AGENTS.md)。

## 许可证

[MIT](LICENSE) © [roc](https://imroc.cc)
