LiWenzhuo001/dsh-plugin-prompt-optimizer ↗★ 1

dsh-plugin-prompt-optimizer

Prompt optimizer for DeepSeek Harness: structural analysis, ambiguity and redundancy detection, and an engineering-grade rewrite. 适合需要对输入框草稿进行工程级改写和结构化优化以提升AI回复质量的用户。

패키지
dsh-plugin-prompt-optimizer
호환성
미검증
버전
0.1.0
라이선스
MIT
최근 업데이트
2026. 10. 1.

설치

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:LiWenzhuo001/dsh-plugin-prompt-optimizer

2. 三种使用方式

2.1 输入框按钮(与 WorkBuddy 一致的主路径)

输入框工具行左侧的「优化提示词」芯片(槽位 conversation.input.left,注册 id prompt-optimizer,order: 20)。它有四个状态,与参考实现的按钮一一对应:

状态外观点击行为提示文案
空闲图标 +「优化提示词」把当前草稿发给模型改写优化提示词
进行中转圈 +「优化中」取消这次调用(同时中止模型的请求)正在优化…(点击取消)
已改写回退箭头 +「还原」把改写前的原文写回输入框还原为优化前的提示词
出错红色 +「重试」再试一次错误信息原文

细节:

  • 草稿为空时按钮不显示(与参考实现一致:没有内容就没有可优化的东西)。
  • 改写是原地替换:不弹面板、不插入对话流,改完就能直接发送。
  • 备份会失效:如果你在改写后又手动编辑了草稿,按钮自动回到空闲态,不会再提供「还原」(避免把过期的原文写回去)。
  • 失败不破坏草稿:任何失败都只体现在按钮的提示文案里,输入框内容保持原样。
  • 全程中文界面,颜色走 DSH 主题变量,浅色/深色都可用。

2.2 斜杠命令

/optimize [--analyze|--offline] 
用法行为
/optimize 模型改写,只返回改写后的提示词全文(可直接发送)
/optimize --analyze 只诊断不改写:本地规则分析报告(评分、结构、问题清单、冗余、维度覆盖)
/optimize --offline 用本地规则改写,不调用模型

前置标记不会进入提示词,也不会出现在结果里。不带参数(或只有空白)时返回用法说明与示例。

模型不可用时会自动回退,并在结果末尾注明:

---
(模型不可用,已回退到本地规则改写:)

2.3 模型工具(Agent 可自行调用)

两个工具都用原始 JSON Schema 定义参数,并在执行时自行校验。

optimize_prompt

优化一段提示词:调用模型把它改写成更清晰、更具体、更可执行的版本。默认只返回改写后的提示词全文。

参数类型必填取值说明
promptstring✅—原始提示词全文;把用户的原话原样放进来
languagestring—'auto'(默认)/ 'zh' / 'en'语言提示;决定本地规则回退时改写正文的语言
levelstring—'standard' / 'strict' / 'concise'改写力度(仅作用于本地规则回退)
taskTypestring—code / bugfix / refactor / explain / write / analyze / plan / translate / generic任务类型(仅作用于本地规则回退)
outputstring—'prompt'(默认)/ 'full'prompt 只给改写全文;full 额外给评估摘要与截断提示
analyze_prompt

分析一段提示词的结构、模糊表述与冗余信息,返回工程规范度评分与问题清单。只诊断不改写,不消耗模型调用。

参数类型必填取值
promptstring✅—
languagestring—'auto'(默认)/ 'zh' / 'en'

返回的规范值(optimize_prompt)始终包含 input、完整的 analysis、optimized,以及:

字段含义
source'llm'(模型改写)或 'rules'(本地回退)
modelsource: 'llm' 时的 provider/model
modelFallbackReasonsource: 'rules' 且发生过模型失败时的原因
changes / rationale仅本地回退时有意义;模型路径下 changes 为 [](模型不提供逐条变更)
truncated / originalChars / maxInputChars截断元信息

也就是说:改写走模型,诊断永远在本地,程序化调用方两种信息都能拿到。

level 对本地回退改写的影响:

取值行为
standard默认:保留 2 条目标/背景、3 条约束、完整输出格式与验收项(仅在缺少 examples 维度时不强行添加示例小节)
strict在约束中加入「禁止事项」、在验收中加入「回答前自检」,并强制包含示例小节
concise目标/背景各保留 1 条、约束 2 条,输出格式与验收各截取 3 条,待确认问题最多 2 条;当目标已明确且评分 ≥ 50 时不再输出「待确认问题」小节

4. 配置项

配置写在 profile 的 cordis.patch.yml 里,通过行 id prompt-optimizer 定位到本插件的行,再用 config 覆盖。字段与默认值均取自 src/host/config.js 的 normalizeConfig():

字段类型默认值作用
enableToolsbooleantrue是否注册两个模型工具 analyze_prompt / optimize_prompt。只有显式传 false 才关闭(判定为 raw.enableTools !== false)
enableCommandbooleantrue是否注册斜杠命令 /optimize。同样只有显式传 false 才关闭
enableRoutebooleantrue是否注册浏览器半区调用的私有路由 /prompt-optimizer/enhance。关掉之后输入框按钮会失效,模型工具与命令照常可用
provider / modelstring不设置显式指定改写用的模型路由。不设置时跟随部署当前选中的默认模型(agentDefaultModel.currentSelection())。两个必须成对出现才有意义;同时给出时覆盖默认选择
maxTokensnumber1024单次改写调用的输出上限。取值必须是有限且 > 0 的数,否则回退 1024
timeoutMsnumber45000单次改写调用的超时(毫秒)。超时按失败处理并触发回退
systemPromptstring内置模板覆盖系统提示词。默认使用与 WorkBuddy 同源的提示词工程模板(见 src/host/llm.js 的 ENHANCE_SYSTEM_TEMPLATE)
fallbackToRulesbooleantrue模型不可用/失败时是否回退到本地规则改写。传 false 则把错误直接抛给调用方(工具调用会失败,按钮提示错误)
maxInputCharsnumber20000改写前先截断到该长度。取值必须是有限且 > 0 的数,否则回退到 20000;小数会向下取整。只能收紧:引擎自身的上限是 MAX_INPUT = 20000,设成大于 20000 没有意义
defaultLevelstring'standard'本地规则回退时的默认改写力度,取值 standard / strict / concise;非法值回退 standard
defaultOutputstring'prompt'optimize_prompt 未传 output 时的默认输出形态,取值 prompt / full;非法值回退 prompt。/optimize 命令始终直出,不受此项影响

配置示例(追加到 $DSH_HOME/profiles/ /cordis.patch.yml):


# 覆盖本插件行的配置(行 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

只想默认看到完整报告(评分 + 摘要)的写法:

- id: prompt-optimizer
  name: dsh-plugin-prompt-optimizer
  config:
    defaultOutput: full

该配置只影响 optimize_prompt 工具;/optimize 命令与输入框按钮始终是直出。单次调用仍可用 output: 'prompt' 覆盖回来。

只注册命令、不注册模型工具的写法:

- 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 1low

5.4 缺失要素

六个工程维度:goal context constraints output acceptance examples(dimensions.present / dimensions.missing)。每个缺失维度产生一条 finding,id 形如 missing.:

维度严重度判定依据
goalhigh有 goal 槽位标签,或「存在任务动词且存在可解析对象」
outputhighoutput 槽位标签,或内容线索正则命中
acceptancehighacceptance 槽位标签,或内容线索正则命中
contextmediumcontext 槽位标签,或内容线索正则命中
constraintsmediumconstraints 槽位标签,或内容线索正则命中
examplesmediumexamples 槽位标签,或内容线索正则命中

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礼貌词加权合计 ≥ 4low
style.code-fence-no-lang代码围栏后没有语言名low

5.6 评分口径

评分公式(源码注释原文,computeScore()):

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 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 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。

实测输出:

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 } })。
  • 缺少任一预期标记就非零退出并给出明确信息。
  • 幂等:连续构建两次字节一致,产物中不含时间戳。

实测输出:

[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 项全部通过。

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 项:

node --test                              # 不带参数,自动发现 tests/
node --test "tests/**/*.test.mjs"        # glob 形式
npm test                                 # package.json 的 test 脚本

package.json 中的脚本:

"scripts": {
  "build": "node tools/build-host.mjs && node tools/build-client.mjs",
  "test": "node --test \"tests/**/*.test.mjs\""
}

8. 目录结构

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 语义)
│       └──