Bundle
dsh-plugin-prompt-optimizer
Prompt optimizer for DeepSeek Harness: structural analysis, ambiguity and redundancy detection, and an engineering-grade rewrite.
- Source
- LiWenzhuo001
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 6 days ago
Readme
# 提示词优化(dsh-plugin-prompt-optimizer)
**像 WorkBuddy 的「优化提示词」一样:在输入框旁点一下,草稿被改写成更清晰的版本,还能一键还原。**
用户往往只写一句「帮我优化一下那个东西,弄得好看点,尽快」。点一下按钮,插件把这句话交给模型,用一套提示词工程规则改写成目标明确、约束具体、可验收的提示词,**直接替换输入框里的草稿**;不满意就再点一次还原。同一个能力也提供给模型工具和斜杠命令。
- 包名:`dsh-plugin-prompt-optimizer` 版本:`0.1.0` 引擎版本常量:`VERSION = '0.1.0'`
- 运行环境:Node.js ≥ 22;DeepSeek Harness(`dsh`)≥ `0.2.0-rc.2`(见 [§9 版本兼容矩阵](#9-版本兼容矩阵))
- 改写流程与 WorkBuddy 的 `llm:enhancePrompt`(agent 名 `enhance-prompt`)**同源**:同样的系统提示词、同样的用户模板、同样的「只返回改写后的提示词」约束、同样的去引号后处理
- 模型不可用时**自动回退**到内置的确定性规则引擎,按钮不会变成死路
**一行安装**:
```powershell
dsh plugin --profile desktop add dsh-plugin-prompt-optimizer
```
也可以完全不敲命令:DSH 侧栏「**插件**」→「**添加插件**」→ 输入包名 `dsh-plugin-prompt-optimizer` → 安装。
> 装完必须**完全退出并重启 DSH** —— 组合包只在启动时加载。详见 [§3 安装](#3-安装)。
---
## 目录
1. [它解决什么问题](#1-它解决什么问题)
2. [三种使用方式](#2-三种使用方式)
3. [安装](#3-安装)
4. [配置项](#4-配置项)
5. [分析维度与评分口径](#5-分析维度与评分口径)
6. [已知边界](#6-已知边界)
7. [开发](#7-开发)
8. [目录结构](#8-目录结构)
9. [版本兼容矩阵](#9-版本兼容矩阵)
10. [排障](#10-排障)
11. [发版约定](#11-发版约定)
---
## 1. 它解决什么问题
一次点击完成的事:
| 阶段 | 做什么 | 对应实现 |
| --- | --- | --- |
| 输入 | 输入框里的草稿(原样,不做预处理) | 浏览器半区读取 `useInput` |
| 改写 | 按提示词工程规则重写:明确目标、补上下文、设约束、定输出格式、加验收标准 | Host 调用模型,系统提示词见 `ENHANCE_SYSTEM_TEMPLATE` |
| 落地 | 改写结果**替换**输入框草稿,并保留原文作为备份 | `inputActions.setDraft(...)` |
| 还原 | 再点一次按钮回到原文;改过草稿后备份自动失效 | 芯片的 revert 状态 |
| 诊断(可选) | 本地确定性分析:分节、模糊表述、冗余、缺失维度、0–100 评分 | `/optimize --analyze`、`analyze_prompt` 工具 |
| 回退 | 模型不可用/失败时用本地规则改写,并说明降级原因 | `fallbackToRules`(默认开) |
两条实现原则:
- **浏览器半区不做模型调用**。它把草稿 POST 给 Host 的私有路由 `/prompt-optimizer/enhance`,由 Host 用部署里已配置的模型路由去改写。这样浏览器里没有凭据、没有路由选择逻辑,也不会在会话记录里留下任何事件——点按钮不会往对话流里插东西。
- **规则引擎只服务 Host**。它现在负责「诊断」和「回退改写」,因此**不再打进浏览器包**(浏览器的包从 ~175KB 降到 ~17KB)。
---
## 2. 三种使用方式
### 2.1 输入框按钮(与 WorkBuddy 一致的主路径)
输入框工具行左侧的「**优化提示词**」芯片(槽位 `conversation.input.left`,注册 id `prompt-optimizer`,`order: 20`)。它有四个状态,与参考实现的按钮一一对应:
| 状态 | 外观 | 点击行为 | 提示文案 |
| --- | --- | --- | --- |
| 空闲 | 图标 +「优化提示词」 | 把当前草稿发给模型改写 | 优化提示词 |
| 进行中 | 转圈 +「优化中」 | **取消**这次调用(同时中止模型的请求) | 正在优化…(点击取消) |
| 已改写 | 回退箭头 +「还原」 | 把改写前的原文写回输入框 | 还原为优化前的提示词 |
| 出错 | 红色 +「重试」 | 再试一次 | 错误信息原文 |
细节:
- **草稿为空时按钮不显示**(与参考实现一致:没有内容就没有可优化的东西)。
- **改写是原地替换**:不弹面板、不插入对话流,改完就能直接发送。
- **备份会失效**:如果你在改写后又手动编辑了草稿,按钮自动回到空闲态,不会再提供「还原」(避免把过期的原文写回去)。
- **失败不破坏草稿**:任何失败都只体现在按钮的提示文案里,输入框内容保持原样。
- 全程中文界面,颜色走 DSH 主题变量,浅色/深色都可用。
### 2.2 斜杠命令
```text
/optimize [--analyze|--offline] <原始提示词>
```
| 用法 | 行为 |
| --- | --- |
| `/optimize <提示词>` | 模型改写,只返回改写后的提示词全文(可直接发送) |
| `/optimize --analyze <提示词>` | **只诊断不改写**:本地规则分析报告(评分、结构、问题清单、冗余、维度覆盖) |
| `/optimize --offline <提示词>` | 用本地规则改写,不调用模型 |
前置标记不会进入提示词,也不会出现在结果里。不带参数(或只有空白)时返回用法说明与示例。
模型不可用时会自动回退,并在结果末尾注明:
```text
---
(模型不可用,已回退到本地规则改写:<原因>)
```
### 2.3 模型工具(Agent 可自行调用)
两个工具都用**原始 JSON Schema** 定义参数,并在执行时自行校验。
#### `optimize_prompt`
> 优化一段提示词:调用模型把它改写成更清晰、更具体、更可执行的版本。默认只返回改写后的提示词全文。
| 参数 | 类型 | 必填 | 取值 | 说明 |
| --- | --- | --- | --- | --- |
| `prompt` | string | ✅ | — | 原始提示词全文;把用户的原话原样放进来 |
| `language` | string | — | `'auto'`(默认)/ `'zh'` / `'en'` | 语言提示;决定本地规则回退时改写正文的语言 |
| `level` | string | — | `'standard'` / `'strict'` / `'concise'` | 改写力度(**仅作用于本地规则回退**) |
| `taskType` | string | — | `code` / `bugfix` / `refactor` / `explain` / `write` / `analyze` / `plan` / `translate` / `generic` | 任务类型(**仅作用于本地规则回退**) |
| `output` | string | — | `'prompt'`(默认)/ `'full'` | `prompt` 只给改写全文;`full` 额外给评估摘要与截断提示 |
#### `analyze_prompt`
> 分析一段提示词的结构、模糊表述与冗余信息,返回工程规范度评分与问题清单。只诊断不改写,**不消耗模型调用**。
| 参数 | 类型 | 必填 | 取值 |
| --- | --- | --- | --- |
| `prompt` | string | ✅ | — |
| `language` | string | — | `'auto'`(默认)/ `'zh'` / `'en'` |
**返回的规范值**(`optimize_prompt`)始终包含 `input`、完整的 `analysis`、`optimized`,以及:
| 字段 | 含义 |
| --- | --- |
| `source` | `'llm'`(模型改写)或 `'rules'`(本地回退) |
| `model` | `source: 'llm'` 时的 `provider/model` |
| `modelFallbackReason` | `source: 'rules'` 且发生过模型失败时的原因 |
| `changes` / `rationale` | 仅本地回退时有意义;模型路径下 `changes` 为 `[]`(模型不提供逐条变更) |
| `truncated` / `originalChars` / `maxInputChars` | 截断元信息 |
也就是说:**改写走模型,诊断永远在本地**,程序化调用方两种信息都能拿到。
`level` 对本地回退改写的影响:
| 取值 | 行为 |
| --- | --- |
| `standard` | 默认:保留 2 条目标/背景、3 条约束、完整输出格式与验收项(仅在缺少 `examples` 维度时不强行添加示例小节) |
| `strict` | 在约束中加入「禁止事项」、在验收中加入「回答前自检」,并强制包含示例小节 |
| `concise` | 目标/背景各保留 1 条、约束 2 条,输出格式与验收各截取 3 条,待确认问题最多 2 条;当目标已明确且评分 ≥ 50 时不再输出「待确认问题」小节 |
---
## 3. 安装
本包是一个标准的 DSH **组合包(bundle)**:它通过 `package.json` 的 `dsh.bundle.patch` 指向自己的 `cordis.patch.yml`,并通过 `dsh.client` 声明浏览器半区。
```json
"dsh": {
"bundle": { "patch": "./cordis.patch.yml" },
"client": { "platform": "web", "inject": ["@deepseek-ai/dsh-client-ui-conversation"] }
}
```
### 3.1 `cordis.patch.yml` 的作用
它就是这个 bundle 的 **patch 层**:只往 profile 的组合树里**插入一行**,不替换任何官方行。
```yaml
# Prompt optimizer bundle: adds one plugin row, replaces nothing.
- insert:
- id: prompt-optimizer
name: dsh-plugin-prompt-optimizer
```
- `id: prompt-optimizer` —— 这一行在 profile 树里的条目 id,也是配置与禁用覆盖的定位键。
- `name: dsh-plugin-prompt-optimizer` —— 该行的模块名,即包名。
### 3.2 装到哪里:profile 的 `package.json` 与 `dsh.profile.bundles`
每个 profile 有自己的目录(`$DSH_HOME/profiles/<name>`,本机 desktop profile 为 `C:\Users\15733\.dsh\profiles\desktop`)。安装一个组合包会做两件事:
1. 在该 profile 的 `package.json` 里新增一条 `dependencies`;
2. 把包名**追加**到 `dsh.profile.bundles` 数组末尾。
```json
{
"dependencies": { "dsh-plugin-prompt-optimizer": "..." },
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"dsh-plugin-prompt-optimizer"
]
}
}
}
```
**顺序语义。** profile 的组合树是「按 patch 层依次叠加」合成出来的,顺序为:先按 `dsh.profile.bundles` 的数组顺序叠加每个 bundle 的 patch 层,再叠加 profile 自己的 `cordis.patch.yml`,最后是 `--patch` 覆盖层。新安装的包总是被**追加到数组末尾**,因此本插件行位于所有官方 bundle 之后,不会覆盖官方行为。
### 3.3 安装入口
**方式一:从 npm 安装(推荐)**
```powershell
# 最新版
dsh plugin --profile desktop add dsh-plugin-prompt-optimizer
# 或钉住一个具体版本(升级/回滚都用它)
dsh plugin --profile desktop add dsh-plugin-prompt-optimizer@0.1.0
```
不敲命令也行:DSH 侧栏「**插件**」→「**添加插件**」→ 输入包名 `dsh-plugin-prompt-optimizer` → 安装。
**方式二:从本地路径 / tgz 安装(开发与离线场景)**
图形界面的「添加插件」同样接受下列任意一种 spec:
- **本地绝对路径**,例如 `E:\plug\dsh-plugin-prompt-optimizer`
- 压缩包(tgz),例如 `npm pack` 产出的 `dsh-plugin-prompt-optimizer-0.1.0.tgz`
DSH 的 CLI 形式为 `dsh plugin --profile <profile> <pnpm-args...>`,即在该 profile 目录里执行 pnpm,并在安装后自动把新组合包追加进 `dsh.profile.bundles`。例如:
```powershell
dsh plugin --profile desktop add E:\plug\dsh-plugin-prompt-optimizer
```
安装完成后对话框会提供「立即启用」。DSH 目前不支持插件自动更新:升级需要先卸载再安装新版本。注意两点:
- desktop profile 需要先启动过一次 DSH 完成初始化;
- 运行该命令前应**完全退出 DSH**。本机 `dsh` 位于 `E:\DeepSeek\resources\runtime\cli\bin\dsh.cmd`。
**方式三:本包自带的零依赖安装脚本(无需 pnpm / 无需联网)**
因为本插件不依赖任何 `@deepseek-ai/*` 包(那些包在 Harness 的 asar 里,树外包解析不到),安装它就是「拷目录 + 改两处清单」,不需要 pnpm、不需要联网、也没有安装脚本:
```powershell
cd E:\plug\dsh-plugin-prompt-optimizer
# 先干跑,看清会改哪些文件
node tools/install.mjs --profile desktop --dry-run
# 真正安装(会先把待改文件备份到 <profile>\prompt-optimizer-backup-<时间戳>\)
node tools/install.mjs --profile desktop
# 卸载(逐行只删本插件那一行,其他插件行原样保留)
node tools/install.mjs --profile desktop --uninstall
```
它会做三件事,且**幂等**(重复执行不会写入重复行):
1. 把 `package.json`、`cordis.patch.yml`、`lib/`、`src/`、`docs/`、`tools/`、`README.md` 拷到 `<profile>\node_modules\dsh-plugin-prompt-optimizer\`;
2. 在 profile 的 `package.json` 里写入 `"dsh-plugin-prompt-optimizer": "link:<本包绝对路径>"`,并把包名追加进 `dsh.profile.bundles`(不覆盖既有顺序,`dsh-plugin-whale-pet` 等既有包保持原位);
3. 在 profile 的 `cordis.patch.yml` 末尾追加一行 insert(已存在则跳过)。
`--home <dir>` 可指定别的 DSH home,便于在一次性 profile 里试装。
### 3.4 必须重启 DSH 应用
**新装的组合包不会在运行中的实例里生效,必须完全退出并重新打开 DSH 应用。**
DSH 自己的安装完成文案就是「**已安装,下次启动后加载。**」;只有重启后 Host 半区(模型工具、`/optimize` 命令、私有路由)和浏览器半区(输入框芯片)才会一起加载。
> 关闭窗口不等于退出应用——请从托盘/菜单彻底退出 DSH 后再重新打开。
---
## 4. 配置项
配置写在 profile 的 `cordis.patch.yml` 里,通过行 id `prompt-optimizer` 定位到本插件的行,再用 `config` 覆盖。字段与默认值均取自 `src/host/config.js` 的 `normalizeConfig()`:
| 字段 | 类型 | 默认值 | 作用 |
| --- | --- | --- | --- |
| `enableTools` | boolean | `true` | 是否注册两个模型工具 `analyze_prompt` / `optimize_prompt`。**只有显式传 `false` 才关闭**(判定为 `raw.enableTools !== false`) |
| `enableCommand` | boolean | `true` | 是否注册斜杠命令 `/optimize`。同样只有显式传 `false` 才关闭 |
| `enableRoute` | boolean | `true` | 是否注册浏览器半区调用的私有路由 `/prompt-optimizer/enhance`。关掉之后输入框按钮会失效,模型工具与命令照常可用 |
| `provider` / `model` | string | 不设置 | 显式指定改写用的模型路由。**不设置时跟随部署当前选中的默认模型**(`agentDefaultModel.currentSelection()`)。两个必须成对出现才有意义;同时给出时覆盖默认选择 |
| `maxTokens` | number | `1024` | 单次改写调用的输出上限。取值必须是有限且 > 0 的数,否则回退 1024 |
| `timeoutMs` | number | `45000` | 单次改写调用的超时(毫秒)。超时按失败处理并触发回退 |
| `systemPrompt` | string | 内置模板 | 覆盖系统提示词。默认使用与 WorkBuddy 同源的提示词工程模板(见 `src/host/llm.js` 的 `ENHANCE_SYSTEM_TEMPLATE`) |
| `fallbackToRules` | boolean | `true` | 模型不可用/失败时是否回退到本地规则改写。传 `false` 则把错误直接抛给调用方(工具调用会失败,按钮提示错误) |
| `maxInputChars` | number | `20000` | 改写前先截断到该长度。取值必须是有限且 > 0 的数,否则回退到 20000;小数会向下取整。**只能收紧**:引擎自身的上限是 `MAX_INPUT = 20000`,设成大于 20000 没有意义 |
| `defaultLevel` | string | `'standard'` | 本地规则回退时的默认改写力度,取值 `standard` / `strict` / `concise`;非法值回退 `standard` |
| `defaultOutput` | string | `'prompt'` | `optimize_prompt` 未传 `output` 时的默认输出形态,取值 `prompt` / `full`;非法值回退 `prompt`。`/optimize` 命令始终直出,不受此项影响 |
配置示例(追加到 `$DSH_HOME/profiles/<profile>/cordis.patch.yml`):
```yaml
# 覆盖本插件行的配置(行 id 必须与 cordis.patch.yml 中的 insert id 一致)
- id: prompt-optimizer
name: dsh-plugin-prompt-optimizer
config:
enableTools: true
enableCommand: true
# 固定用某个模型改写(不写则跟随当前默认模型)
provider: deepseek-account
model: deepseek-flash
maxTokens: 1024
timeoutMs: 45000
fallbackToRules: true
maxInputChars: 20000
defaultLevel: standard
defaultOutput: prompt
```
只想默认看到完整报告(评分 + 摘要)的写法:
```yaml
- id: prompt-optimizer
name: dsh-plugin-prompt-optimizer
config:
defaultOutput: full
```
该配置只影响 `optimize_prompt` 工具;`/optimize` 命令与输入框按钮始终是直出。单次调用仍可用 `output: 'prompt'` 覆盖回来。
只注册命令、不注册模型工具的写法:
```yaml
- id: prompt-optimizer
name: dsh-plugin-prompt-optimizer
config:
enableTools: false
```
改完配置同样需要重启 DSH 应用。
---
## 5. 分析维度与评分口径
以下全部对应 `src/engine/` 中的真实实现(按主题分模块;`index.js` 只做再导出)。
### 5.1 结构识别:槽位标签
`structure[].label` 的取值域为 `goal` `context` `constraints` `output` `acceptance` `examples` `background` `unknown`。标题匹配大小写不敏感,允许 `#` / `##` / `**` / 数字前缀 / 中英冒号;匹配表按「顺序即优先级」生效(更具体的槽位排在前面):
| 标签 | 命中关键词(正则) |
| --- | --- |
| `context` | 背景、上下文、现状、环境、前提、context、background、current state |
| `constraints` | 约束、限制、边界、规范、技术栈、禁止、不能、不允许、constraint、limit、boundary、stack、restriction |
| `output` | 输出、交付、格式、返回、呈现、output、deliverable、format |
| `acceptance` | 验收、标准、测试、完成条件、自检、acceptance、criteria、test、definition of done、done when |
| `examples` | 示例、例子、参考、example、sample、reference |
| `goal` | 目标、任务、需求、要求、要做、目的、goal、task、objective、requirement |
| `background` | 说明、补充、备注、note、remark |
结构类问题(`findings[].kind === 'structure'`):
| id | 触发条件 | 严重度 |
| --- | --- | --- |
| `structure.no-sections` | 没有标题,且带标签的条目少于 2 条 | 非空白字符 ≥ 200 时 `high`,否则 `medium` |
| `structure.run-on-blob` | 无标题无列表、行数 ≤ 3,且逗号分句 ≥ 3 或某行 ≥ 80 字符 | `medium` |
| `structure.bullet-soup` | 无标题,但同级条目 ≥ 6 条 | `low` |
### 5.2 模糊表述
八个类别(`findings[].kind === 'vagueness'`)。`vague.referent` 与 `vague.quality` 的严重度取决于是否已有具体锚点(`specificityHits === 0` 时为 `high`,否则 `medium`):
| id | 类别 | 严重度 |
| --- | --- | --- |
| `vague.object-missing` | 出现动作但没有可解析的处理对象(如「帮我优化一下」) | `high` |
| `vague.referent` | 指代不明确 | `high` / `medium` |
| `vague.quality` | 质量要求无法验收(主观词) | `high` / `medium` |
| `vague.hedge` | 不确定的表述 | `medium` |
| `vague.urgency` | 只有紧迫感,没有时间点 | `medium` |
| `vague.scope` | 范围没有边界 | `medium` |
| `vague.quantifier` | 数量与范围含糊 | `low` |
| `vague.intensifier` | 程度词没有基准 | `low` |
**真实命中词表各举 3 例**(与 `VAGUE_PATTERNS` 逐字一致):
| 类别 | 中文真实例子 | 英文真实例子 |
| --- | --- | --- |
| 质量要求无法验收 | 优化、好看、更好 | optimize、improve、nice |
| 只有紧迫感没有时间点 | 尽快、赶紧、马上 | asap、urgent、quickly |
| 指代不明确 | 那个、这个、它 | it、this、that |
| 不确定的表述 | 可能、大概、尽量 | maybe、perhaps、roughly |
| 数量范围含糊 | 一些、等等、之类 | some、several、etc |
| 程度词没有基准 | 非常、特别、挺 | very、really、quite |
| 范围没有边界 | 一切、所有内容、基本上 | everything、whatever、overall |
> 说明:英文词表使用 `\b` 词边界(避免 `it` 命中 `with`),中文使用字面量匹配。另有一个反向指标 `QUANTIFIED_RE`(数字、`不超过`、`至少`、`p95` 等)用于抑制误报。
### 5.3 冗余检测
六个类别(`findings[].kind === 'redundancy'`,同时出现在 `redundancy[]` 分组里):
| 分组 | id | 检测规则 | 严重度 |
| --- | --- | --- | --- |
| 重复的句子 | `redundancy.duplicate-sentence` | 句子规范化(去空白与标点、转小写)后完全相同,且出现 ≥ 2 次 | `high` |
| 同一动作反复要求 | `redundancy.repeated-imperative` | 同一个任务动词在不同句子里出现 ≥ 3 次 | `medium` |
| 目标重复表述 | `redundancy.goal-restated` | 两个含任务动词的句子,词元 Jaccard 相似度 ≥ 0.45,最多报 2 组 | `medium` |
| 同一约束换了说法 | `redundancy.constraint-restated` | 两个含约束线索的句子,词元 Jaccard 相似度 ≥ 0.45,最多报 2 组 | `medium` |
| 流水账连接词 | `redundancy.filler-chain` | `首先/然后/接着/其次/再者/最后/第一/第二/第三/其一/其二` 或 `first/then/next/second/third/finally/lastly` 命中 ≥ 2 个 | `low` |
| 客套铺垫 | `redundancy.politeness` | 礼貌词加权合计 ≥ 2。词表与权重:谢谢 2、多谢 2、感谢 2、辛苦了 2、麻烦 2、拜托 2、劳驾 2、你好 1、您好 1、请 1;thanks 2、thank you 2、thx 2、appreciate it 2、kindly 1、please 1 | `low` |
### 5.4 缺失要素
六个工程维度:`goal` `context` `constraints` `output` `acceptance` `examples`(`dimensions.present` / `dimensions.missing`)。每个缺失维度产生一条 `finding`,id 形如 `missing.<维度>`:
| 维度 | 严重度 | 判定依据 |
| --- | --- | --- |
| `goal` | `high` | 有 `goal` 槽位标签,或「存在任务动词且存在可解析对象」 |
| `output` | `high` | `output` 槽位标签,或内容线索正则命中 |
| `acceptance` | `high` | `acceptance` 槽位标签,或内容线索正则命中 |
| `context` | `medium` | `context` 槽位标签,或内容线索正则命中 |
| `constraints` | `medium` | `constraints` 槽位标签,或内容线索正则命中 |
| `examples` | `medium` | `examples` 槽位标签,或内容线索正则命中 |
### 5.5 风格类问题
| id | 类别 | 严重度 |
| --- | --- | --- |
| `style.truncated` | 输入超长已截断 | `high`(Host 补写时为 `medium`,见 6.2) |
| `style.empty-input` | 输入为空 | `high` |
| `style.wall-of-text` | 一行里多个句子,且既无空行也无列表 | `medium` |
| `style.no-output-format` | 没有任何输出形式线索 | `medium` |
| `style.shouting` | 拉丁字母 ≥ 20 且大写占比 ≥ 60% 且 ≥ 2 个全大写词 | `low` |
| `style.politeness` | 礼貌词加权合计 ≥ 4 | `low` |
| `style.code-fence-no-lang` | 代码围栏后没有语言名 | `low` |
### 5.6 评分口径
评分公式(源码注释原文,`computeScore()`):
```text
score = 62
+ 4 * presentCount // dimensions.present 的数量(0..6)
+ min(6, 2 * specificityHits) // 具体锚点(路径 / 版本 / 标识符 / 数字 / 引号)
+ min(6, floor(charsNoSpace/100)) // 内容量:太短的提示词信息不足
- min(18, 3 * vagueHits) // 模糊表述越多,分越低
- min(15, 3 * redundancyHits) // 冗余越多,分越低
- 4 * missingHigh // 缺失 goal / output / acceptance
- 2 * missingMedium // 缺失 context / constraints / examples
- 3 * structureProblems // 结构类 finding 数
- 2 * styleProblems // 风格类 finding 数
```
公式刻意写成线性可加形式以保证单调性:补维度、加锚点、加内容只会加分;加模糊、加冗余、加缺失只会减分。最终结果 `clamp(round(score), 0, 100)`。
清晰度档位(`clarityOf()`):
| `metrics.clarity` | 区间 |
| --- | --- |
| `high`(面板显示「清晰」) | `score >= 75` |
| `medium`(面板显示「一般」) | `50 <= score < 75` |
| `low`(面板显示「模糊」) | `score < 50` |
问题清单的排序:先按严重度 `high > medium > low`,再按 `id` 字典序,结果稳定可复现。
---
## 6. 已知边界
### 6.1 确定性本地规则引擎
- **不做语义理解**:引擎完全基于正则表达式与词表,不理解意图。它可以稳定地发现「缺目标」「缺验收」「有主观词」「同一句重复三遍」这类**形式问题**,但无法判断需求本身是否合理。
- **改写需要模型**:模型路径下会真实发起一次 LLM 调用(走部署里配置的 provider/model),消耗 token;调用失败或没有可用路由时按 `fallbackToRules` 处理。
- **浏览器半区只发一次同源请求**:POST 到本插件自己的路由 `/prompt-optimizer/enhance`,请求体只有 `{ text }`;不发往任何第三方地址,不读凭据。
- **本地分析与回退是确定性的**:规则引擎不使用 `Date`、`Math.random`、`Intl`;同一输入必得同一输出,可在任意进程重复。模型改写则**不保证**可重复(取决于模型)。
- **可能误报**:例如「优化」既是任务动词,也在主观质量词表里,因此「优化 XX 模块」在缺少其他信息时仍可能被标为质量要求无法验收——这是本地分析的刻意保守取舍。
- **`evidence` 绝不编造**:每条 `findings[].evidence` 都必须是原文的真实子串,过滤器 (`sanitizeFinding`) 会丢弃任何不满足该条件的片段,因此证据为空数组是正常情况。
- **不抛异常**:引擎对空串、纯空白、纯 emoji、代码围栏、CRLF、制表符、超长输入都返回合法结构。
但**插件层会拒绝空输入**:`analyze_prompt` / `optimize_prompt` 对缺失、非 string、或只含空白的 `prompt` 抛出中文 `Error`;`/optimize` 无参数返回用法说明;HTTP 路由对空白文本回 400 `empty_input`。
- **模型输出的后处理**:与参考实现一致,会剥掉一层包裹引号(`"…"`、`'…'`、`“…”`、`「…」`、`` `…` ``)以及整段的 Markdown 围栏;结果为空白时报错并触发回退。
### 6.2 超长输入的截断行为
存在**两层**上限,且插件层会被**硬性夹取**到引擎层的天花板:
| 层 | 常量 / 配置 | 行为 |
| --- | --- | --- |
| 插件层(Host) | `config.maxInputChars`,默认 `20000`,生效值 = `min(配置值, 20000)` | 交给模型/引擎前先截断;若发生截断,补写一条 `style.truncated`(`severity: 'medium'`,去重后按严重度重新排序),detail 中给出原文长度与生效上限 |
| 引擎层 | `MAX_INPUT = 20000` | 输入超过 20000 字符时截断到 20000,并产生 `style.truncated`(`severity: 'high'`) |
关键结论:
- `maxInputChars` **只能收紧、不能放宽**。写成 `50000` 也会被夹到 `20000`——因为引擎自身在 `20000` 处硬截断,放宽配置只会让报告与实际被分析的内容不一致。
- 因此通过插件调用时,到达引擎的字符串已经不超过上限,引擎自身那条 `style.truncated` 通常不触发,实际看到的是 Host 补写的那条。
- 返回值的截断字段以**实际发生的事**为准:`truncated` / `originalChars` / `maxInputChars` 由 Host 的截断与引擎返回的 `analysis.input` 长度共同核对。若某次引擎分析了比传入文本更短的前缀,`truncated` 一定会是 `true`,`maxInputChars` 报告真实的分析窗口,绝不出现「悄悄少分析了却没提示」。
- 直出模式下若发生截断,改写正文之后会附一行说明(这是直出模式唯一多出来的内容)。
直接用 Node 调用引擎(不经过插件)时,则只会看到引擎自己那条 `high` 的截断 finding。参见 `docs/EXAMPLES.md` 中「截断行为」一节。
### 6.3 中英混合的判定方式
`detectLanguage(cjkCount, latinLetters)`:统计 CJK 字符数与拉丁字母数,取 `ratio = cjkCount / (cjkCount + latinLetters)`。
| 条件 | 结果 |
| --- | --- |
| 两者都为 0,或拉丁字母数为 0 | `'zh'` |
| CJK 数为 0 | `'en'` |
| `ratio >= 0.7` | `'zh'` |
| `ratio <= 0.3` | `'en'` |
| 其余(0.3 < ratio < 0.7) | `'mixed'` |
`'mixed'` 时改写正文使用**中文**(只有判定为 `'en'` 时才输出英文提示词)。此时如果强制传入 `language: 'en'`,改写正文会切换为英文。
### 6.4 其它
- 任务类型由关键词加权打分判定,同分时按固定优先级 `bugfix > refactor > translate > code > explain > analyze > plan > write > generic`。
- 浏览器芯片在渲染与请求期捕获全部异常:模型失败、网络失败、返回体不是 JSON、剪贴板不可用,都只体现在按钮的提示文案里,**绝不会把草稿弄丢,也不会让输入框崩溃**。
- 插件不写文件、不读环境变量、不起定时器、不访问除自身路由以外的网络地址。
---
## 7. 开发
在包根目录 `E:\plug\dsh-plugin-prompt-optimizer` 下执行。
### `node tools/build-host.mjs`
把 Host 半区源码构建成包入口:
- 读 `src/host.js`(拆分后的入口,只负责装配 `src/host/*.js`),把其中**所有**相对 import 说明符 `from './…'` 改写为 `from '../src/…'`(因为产物位于上一层目录 `lib/`),加上 `// GENERATED from src/host.js — do not edit.` 横幅,写到 `lib/index.js`。
- 写文件前先校验每个被 import 的 `src/…` 模块真实存在,缺失就**非零退出且不写文件**,避免发布一个静默损坏的 `lib/index.js`。
- 其余内容**逐字节复制**——不打包、不转译、无依赖。
- 幂等:内容没变时只打印 `unchanged`。
- 可选参数:`node tools/build-host.mjs <包根目录>` 可构建另一份 checkout。
实测输出:
```text
build-host: E:\plug\dsh-plugin-prompt-optimizer\src\host.js -> E:\plug\dsh-plugin-prompt-optimizer\lib\index.js (3511 bytes, unchanged, 4 module imports)
```
### `node tools/build-client.mjs`
零依赖打包器,把浏览器半区做成自包含 classic script:
- 规则引擎已收敛为 **host-only**:客户端不再内联 `src/engine/`,浏览器半区只包含交互层。
- 读 `src/client/index.js`(以 CJS 形式编写),包装成接收 `(require, exports, module, __styleCss)` 的工厂体。
- 读 `src/client/style.css`,内联为 JS 字符串常量 `__styleCss`。
- 产出 `lib/client.js`,整体形如 `window.__ModuleLoader__.load({ id: "dsh-plugin-prompt-optimizer", factory: (require) => { ... return exports } })`。
- 缺少任一预期标记就**非零退出并给出明确信息**。
- 幂等:连续构建两次字节一致,产物中不含时间戳。
实测输出:
```text
[build-client] ok
engine (not referenced by the client; host-only)
client src\client\index.js 12832 B
style src\client\style.css 2885 B
output lib\client.js 18149 B
```
### `node --test`
运行 `tests/` 下的三个测试套件(引擎、Host、客户端),使用 Node 内置测试运行器,无第三方依赖。当前 68 项全部通过。
```powershell
cd E:\plug\dsh-plugin-prompt-optimizer
node --test
```
> **注意(已实测)**:在本机 Node **v24.12.0** 上,`node --test tests/` 这种**带目录参数**的写法会失败——Node 把 `tests/` 当成模块入口去加载,报 `Error: Cannot find module '...\tests'`,结果是 `pass 0 / fail 1`。请改用下面任一写法,它们都能正常运行全部 68 项:
>
> ```powershell
> node --test # 不带参数,自动发现 tests/
> node --test "tests/**/*.test.mjs" # glob 形式
> npm test # package.json 的 test 脚本
> ```
`package.json` 中的脚本:
```json
"scripts": {
"build": "node tools/build-host.mjs && node tools/build-client.mjs",
"test": "node --test \"tests/**/*.test.mjs\""
}
```
---
## 8. 目录结构
```text
dsh-plugin-prompt-optimizer/
├── package.json # dsh.bundle + dsh.client 双声明
├── cordis.patch.yml # 组合包 patch 层:只插入本插件行
├── README.md # 本文档
├── CHANGELOG.md # 版本变更(Keep a Changelog)
├── LICENSE # MIT
├── docs/
│ ├── INTERFACES.md # 冻结接口契约
│ ├── EXAMPLES.md # 真实运行得到的改写前后对照
│ └── SECURITY.md # 安全面:数据流向与不做什么
├── lib/
│ ├── index.js # Host 半区产物(由 src/host.js 生成)
│ └── client.js # 浏览器半区产物(自包含 classic script)
├── src/
│ ├── engine/ # 分析 / 改写引擎(零依赖纯 ESM,按主题分模块)
│ │ ├── index.js # 入口:文档 + 再导出(浏览器/工具 import 面)
│ │ ├── constants.js # 冻结词汇表:版本 / 上限 / 任务类型 / 规则表
│ │ ├── text.js # 基础文本工具:预处理 / 行结构 / 围栏
│ │ ├── anchors.js # 锚点抽取:文件路径 / 版本号 / 技术名词 / 目标
│ │ ├── detectors.js # 检测器:模糊 / 冗余 / 缺失维度 / 结构 / 风格
│ │ ├── analysis.js # analyzePrompt:评分聚合
│ │ └── rewrite.js # optimizePrompt:改写生成
│ ├── host.js # Host 半区入口(装配 src/host/*.js)
│ ├── host/ # Host 半区按职责分模块
│ │ ├── constants.js # 冻结词汇:枚举 / 默认值 / 文案表
│ │ ├── utils.js # 无依赖小工具
│ │ ├── config.js # 配置归一 / 输入规整 / 截断披露
│ │ ├── pipeline.js # analyze / optimize 的公共执行管线
│ │ ├── render.js # 报告渲染(Markdown 文本)
│ │ ├── llm.js # 模型调用:选型 / 流式拼接 / 失败归一
│ │ ├── route.js # /prompt-optimizer/enhance 私有路由
│ │ ├── tools.js # analyze_prompt / optimize_prompt 工具定义
│ │ └── command.js # /optimize 命令定义
│ └── client/
│ ├── index.js # 浏览器半区源码(React.createElement,CJS 语义)
│ └── style.css # 芯片样式(构建时内联)
├── tools/
│ ├── build-host.mjs # src/host.js -> lib/index.js(相对导入改写 + 模块存在性校验)
│ ├── build-client.mjs # src/client + style.css -> lib/client.js
│ ├── release-check.mjs # 发版门禁:构建确定性 / 测试 / 契约 / 打包清单
│ └── install.mjs # 装入 profile(含 --dry-run / --uninstall)
└── tests/
├── engine.test.mjs
├── host.test.mjs
└── client.test.mjs
```
想直接看真实运行效果,请阅读 [`docs/EXAMPLES.md`](docs/EXAMPLES.md)——其中每个「改写后」都是实际运行引擎得到的逐字输出,并附有可复现命令。
---
## 9. 版本兼容矩阵
`package.json` 的 `engines.dsh` 声明**最低支持**版本;下表记录**实际验证过**的版本(生态既有约定:声明最低、矩阵记实测)。
| 插件版本 | 适配 dsh 版本 | 验证范围 | 备注 |
| --- | --- | --- | --- |
| `0.1.0` | `>=0.2.0-rc.2` | 实测 `0.2.0-rc.2`(DSH Desktop,Node 24.21.0 / pnpm 11.7.0) | 首发版本。宿主三面(工具 / 命令 / 私有路由)+ 浏览器芯片均已在本机真实 runtime 验证 |
`engines.node` 为 `>=22`。
**验证证据**(可复核,命令见仓库 `verify/VERIFICATION.md` 与 `verify/` 目录):
| 检查 | 结果 |
| --- | --- |
| 包内测试(引擎 / Host / Client) | 68 / 68 通过 |
| 真实注册表契约(本机已安装的 `dsh-tools` 真实验证器) | 31 / 31 通过 |
| 包契约(真实扫描器规则) | 17 / 17 通过 |
| 安装产物完整性 | 26 项全过 |
| 引擎对抗性探测 | `failures=0 fuzzThrows=0` |
| 构建确定性 | 连续两次构建字节一致 |
---
## 10. 排障
按「症状 → 原因 → 处理」排列。多数问题都能在这六条里找到答案。
### 10.1 装完了但侧栏里没有 / 芯片没出现
**原因**:组合包只在 **DSH 启动时**扫描加载,运行中的实例不会热加载新 bundle。
**处理**:从托盘/菜单**彻底退出** DSH(关窗口不算退出)再重新打开;然后在侧栏「插件」里确认条目为启用状态。
### 10.2 点击芯片变成红色的「重试」
**原因**:改写请求失败。鼠标悬停在芯片上会显示具体原因(tooltip 就是错误文本)。
**处理**:按 tooltip 分类——
- 模型相关(未配置模型、额度/鉴权失败、超时):插件会自动回退到本地规则引擎改写(`fallbackToRules` 默认开);想直接拿到本地结果可把 `fallbackToRules` 保持默认,或在命令里用 `/optimize --offline`。
- `写回输入框失败`:输入框被别的插件/状态占用,草稿未改动,可重试或手动复制。
### 10.3 提示「请求被中止 / operation was aborted」
**原因**:改写过程中请求被取消——你点了芯片取消、关掉了页面,或**运行中的进程还是旧版本插件**(`0.1.0` 之前的构建把「请求体读完」误判成「客户端断开」,导致每次点击必然中止)。
**处理**:确认装的是 `0.1.0` 或更新版本,并且**重启过** DSH。自查命令:
```powershell
Invoke-WebRequest -Uri "http://127.0.0.1:19387/prompt-optimizer/enhance" -Method POST `
-Body '{"text":"优化当前项目"}' -ContentType 'application/json' -UseBasicParsing |
Select-Object -ExpandProperty Content
```
期望返回 `{"ok":true,...}`。
### 10.4 超长提示词被截断
**原因**:Host 先按 `maxInputChars` 截断(默认 20000,与引擎 `MAX_INPUT` 同值),且该值**只能收紧、不能放宽**分析上限。
**处理**:这是设计行为,报告里会显式披露截断(补一条 `style.truncated` finding)。需要完整分析请先自行精简输入。
### 10.5 `dsh plugin add` 报网络/依赖错误
**原因**:profile 的 registry 是镜像(如 npmmirror),与本包无关;或安装时 DSH 正在运行。
**处理**:完全退出 DSH 后重试;国内网络可保留镜像 registry;也可改用本包自带的零依赖安装脚本([§3.3 方式三](#33-安装入口)),它不联网、不用 pnpm。
### 10.6 升级后没变化
**原因**:DSH 目前**不支持插件自动更新**。
**处理**:先卸载再安装目标版本,然后重启:
```powershell
dsh plugin --profile desktop remove dsh-plugin-prompt-optimizer
dsh plugin --profile desktop add dsh-plugin-prompt-optimizer@0.1.0
```
---
## 11. 发版约定
### 11.1 发版前置门禁(缺一不发)
```powershell
cd E:\plug\dsh-plugin-prompt-optimizer
npm run release:check # 构建确定性 + 68 项测试 + 包契约 + 打包清单 + 版本一致性
```
`tools/release-check.mjs` 逐项检查并在任一项失败时**非零退出**:
1. `node tools/build-host.mjs && node tools/build-client.mjs` 成功,且**连续两次构建产物字节一致**(构建确定性是一票否决项);
2. `node --test "tests/**/*.test.mjs"` 全绿;
3. 包契约检查通过(`verify/check-package-contract.mjs`);
4. `npm pack --dry-run` 清单与 `files` 白名单一致,且**不含** `tests/`、`verify/`、备份目录;
5. `package.json` 的 `version`、`CHANGELOG.md` 的最新条目、本文档 §9 兼容矩阵三者一致;
6. `engines.node` / `engines.dsh` 已声明。
### 11.2 版本号与 CHANGELOG
- **SemVer**。`0.x` 阶段允许 minor 破坏性变更,但必须在 `CHANGELOG.md` 里**显著标注**。
- 每次发版必在 `CHANGELOG.md` 顶部新增条目(Added / Changed / Fixed / Removed),并同步本文档 §9 的兼容矩阵。
### 11.3 发布与回滚
```powershell
# 1) 发布(需要先 npm login,且账号对 dsh-plugin-prompt-optimizer 有发布权限)
npm publish
# 2) 打 tag 并推送
git tag v0.1.0 && git push origin main --tags
```
**回滚**:npm 上的版本不能删除(`unpublish` 受限),正确做法是**钉住上一个可用版本**或发布一个新的修订版:
```powershell
# 用户侧回滚到上一个版本
dsh plugin --profile desktop remove dsh-plugin-prompt-optimizer
dsh plugin --profile desktop add dsh-plugin-prompt-optimizer@<上一个版本>
```
若某版本有严重缺陷,可在 npm 上把它标记为废弃(不影响已安装用户,但阻止新装):
```powershell
npm deprecate dsh-plugin-prompt-optimizer@<坏版本> "请升级到 <好版本>:<原因>"
```
### 11.4 发布后必做
- 在**干净 profile**(或另一台机)上仅按本文档 §3 从零安装一次,重启后确认插件可见且启用;
- 记录该次「从零安装」的实际输出,作为 `G4 发布就绪` 的证据。
Install
dsh plugin --profile web add github:LiWenzhuo001/dsh-plugin-prompt-optimizer
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-plugin-prompt-optimizer from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.