- 适配器 i18n 接入(eslint/pmd/sql-lint/stylelint) - Provider 动态注册机制(registry.ts + providers.json + factory 重构) - SetupView 全面重构(setupView.ts 新增 600+ 行) - i18n 消息扩展(messages.ts +210 行) - 规则导入流程优化(import-service / prompt-builder) - 新增 PMD jars 依赖及测试用例
14 KiB
自定义规则导入体验改进设计
背景与问题
当前自定义规则导入功能支持六种文件格式,其中五种(.md / .txt / .docx / .xlsx / .pptx)经由 AI 转换为结构化 YAML,.yaml / .yml 则直接复制落盘。转换流水线本身已跑通,但用户体验存在一个隐性断层:文件扩展名虽宽松,为了让 AI 准确识别规则字段,用户实际上仍需按某种"事实上的格式"组织内容——Excel 要表头语义化、文本要分段清晰、字段顺序要合理。格式门槛从文件扩展名转移到了内容结构上,用户不知道该怎么写,AI 转换结果也因此不稳定。
具体表现有两点。其一,系统提示词只规定了输出字段(id / severity / description / message 等),对输入几乎没有任何描述与容忍说明,AI 遇到松散内容时行为不可预期。其二,解析侧 parseImportableYaml 对必填字段采取"缺一即丢弃"的静默策略,severity 缺失或非法、id 缺失都会让整条规则消失,用户在预览面板根本看不到被丢弃的规则,无从修正。
目标
降低输入内容组织门槛,让 AI 能处理自然语言段落、松散列表、混合表格等各种写法;同时给用户最小化的内容指引,消除"不知道怎么组织内容"的困惑。核心字段(id / severity)由 AI 基于规则内容自动推断,解析侧提供兜底,预览阶段可人工修正。
方案概览
三处改动协同发挥作用:
- 提示词增强 — 明确告知 AI 接受松散输入,主动推断 id 与 severity
- 解析侧兜底 — severity 缺失降级为 warning,id 缺失生成占位符,丢弃可见
- 预览与入口 — id 改可编辑,占位 id 高亮提示,导入入口加简短指引
设计原则是"AI 推断为主,解析兜底为底,人工预览为终"。AI 推断承担大部分场景,解析兜底保证下限不崩,预览阶段保留人工否决权。
改动一:提示词增强
位置:src/rules/converters/prompt-builder.ts 与六个转换器的 buildSystemPrompt。
当前提示词只规定了输出字段清单,对输入形态只字未提。增强内容如下。
输入容忍说明
在系统提示词开头增加输入形态描述,告知 AI 接受以下任一形态:
- 自然语言段落(一段话描述多条规则)
- 无序列表(每条规则一行或一段)
- 表格(列名不固定,语义可推断)
- 混合形式(段落 + 列表 + 表格组合)
明确要求 AI 不得因输入形态非标准而拒绝转换,应主动从松散描述中提取规则语义。
非规则内容过滤
导入文件的内容不全是规则,可能混入项目介绍、背景说明、代码示例、章节标题等非规则内容。增加要求:
- AI 应识别并跳过非规则内容,只将真正的规则转为 YAML 条目
- 代码示例、项目介绍等仅作为上下文帮助理解规则语义,本身不输出为规则
- 若某段内容无法判断为规则(既无规则意图也无违反提示),直接忽略,不强行转成规则
- 此要求与解析侧兜底呼应:即使 AI 误将非规则内容输出为条目,解析时因 description 与 message 同时缺失会被丢弃
id 推断要求
当前提示词仅说明"id: 规则唯一标识(kebab-case 英文)",未要求 AI 主动生成。改为:
- 每条规则必须输出
id,基于规则描述内容自动生成 - id 规范:kebab-case 英文、语义化、简短(如
no-console-log、avoid-magic-number、require-error-handling) - 输入中即使无显式 id 标识,也要根据 description / message 的语义推断出合适的 id
- 多条规则间 id 不得重复
severity 推断要求
当前提示词仅列出 severity 三档,未说明缺失时如何处理。增加:
- severity 必须输出,按规则语义推断:
error(会导致 bug / 安全问题 / 数据损坏)、warning(潜在问题 / 不良实践)、info(风格 / 可读性建议) - 输入中即使无显式严重级别,也要根据规则描述的后果严重程度推断
description / message 互推要求
当前提示词将 description 与 message 视为两个独立必填字段。两者本意相近:description 偏"规则是什么",message 偏"违反时说什么"。增加:
- 两者必须至少输出一项;若输入只暗示了规则内容而未区分"描述"与"提示",AI 应据已有信息推断并补全另一项
- description 缺失时,由 message 反推简短描述;message 缺失时,由 description 推导违反提示
- 两者语义可相近,无需强行区分口吻
输入输出对照示例
在提示词中附 2-3 个"松散输入 → 标准输出"的对照示例,锚定 AI 的行为预期。示例应覆盖不同输入形态,包括含非规则内容的混合输入:
输入(松散段落):
"不要用 console.log,生产环境会泄露信息。还有不要留下未使用的变量,看着乱。"
输出:
- id: no-console-log
severity: warning
description: 禁止使用 console.log
message: 请使用 logger 工具替代 console.log
duplicateLevel: none
- id: no-unused-vars
severity: warning
description: 禁止未使用的变量
message: 未使用的变量应删除或注释
duplicateOf: eslint/no-unused-vars
duplicateLevel: exact
输入(含非规则内容的混合输入):
"本项目是一个电商后台管理系统,主要使用 Java + Spring Boot 开发。
代码规范要求:Service 层方法必须有日志记录,方便排查问题。
示例代码:
public void createOrder(Order order) { ... }
另外,Controller 层返回值统一用 Result 包装,不要直接返回 Map。"
输出:
- id: require-service-logging
severity: warning
description: Service 层方法必须有日志记录
message: Service 方法缺少日志记录,请补充以便排查问题
languages: [java]
duplicateLevel: none
- id: require-result-wrapper
severity: warning
description: Controller 返回值必须用 Result 包装
message: 请用 Result 包装返回值,不要直接返回 Map
languages: [java]
duplicateLevel: none
第二个示例中,项目介绍与代码示例均未转为规则,仅提取出两条真正的代码规范。示例的作用是让 AI 理解"一段话也能拆成多条规则"且"非规则内容应跳过",而非要求用户按此格式输入。
改动二:解析侧兜底
位置:src/rules/import-service.ts 的 parseImportableYaml。
当前行为
parseImportableYaml 过滤条件为 item.id && item.severity && item.description && item.message,缺任一即丢弃,且丢弃不可见。
新行为
| 字段 | 缺失或非法时 | 处理 |
|---|---|---|
id |
缺失 | 生成占位 id rule-${序号}(rule-1、rule-2…) |
severity |
缺失或非三档之一 | 降级为 warning |
description / message |
仅一项缺失 | 用存在的那个填充缺失项(description↔message 互为兜底) |
description 与 message |
同时缺失 | 丢弃该条 |
description 与 message 本意相近,均为规则的文本表达:前者偏"规则是什么",后者偏"违反时说什么"。两者只要有一项存在,即可作为另一项的兜底来源(直接复制,或由 AI 在转换阶段据一项推断另一项);两者同时缺失,说明这段内容根本不是一条规则,丢弃合理。id 与 severity 属于"可推断字段",缺失时兜底补全而非丢弃。
丢弃无需可视化
被丢弃的内容因"既无 description 也无 message",本就不是规则,不属于"字段不全的规则",因此不设 droppedCount 字段,预览面板也不提示丢弃数。解析后的 ImportableRule 数组只包含确为规则的内容,预览展示与确认流程保持简洁。
改动三:预览与入口
位置:src/rules/import-preview.ts、src/views/setupView.ts、src/i18n/messages.ts。
预览面板 id 可编辑
当前预览面板的 id 显示为只读 <span>(import-preview.ts 的 renderRuleCard)。改为 <input> 元素:
- id 输入框可编辑,用户可修改 AI 生成的 id 或补充占位 id
- 占位 id(
rule-N)用高亮样式提示"请补充或确认"(如橙色边框 + 提示文案) collectEditedRules()已收集 id,无需改动收集逻辑
确认校验扩展
当前 validate() 仅校验 description 与 message 非空。扩展为:
- id 非空(占位 id
rule-N视为非空,但高亮提示用户确认) - description 非空
- message 非空
任一为空则阻止提交,在 validationError 区域显示具体提示。
导入入口指引
setupView.ts 的文件选择按钮旁增加一行简短说明,告知用户:
- 支持自由文本:一段话、列表、表格均可
- 每条规则尽量说清"查什么 + 违反时提示什么"
- 不强制字段顺序和格式,id 与严重级别可由系统自动推断
文案走 i18n,新增对应 key。
字段处理策略总表
改动完成后,四个核心字段的全链路处理策略如下:
| 字段 | AI 推断 | 解析兜底 | 预览可编辑 | 确认校验 |
|---|---|---|---|---|
id |
必生成,基于内容语义 | 缺则占位 rule-N |
是(改为 input) | 非空 |
severity |
必输出,按语义推断 | 缺/非法→warning |
是(已可) | — |
description |
必输出,缺失时由 message 推 | 缺则用 message 填充 | 是(已可) | 非空 |
message |
必输出,缺失时由 description 推 | 缺则用 description 填充 | 是(已可) | 非空 |
description 与 message 同时缺失时丢弃该条(非规则,不提示)。languages / excludeLanguages / duplicateOf / duplicateLevel / duplicateReason 字段处理逻辑不变,维持现状。
改动文件清单
| 文件 | 改动内容 |
|---|---|
src/rules/converters/prompt-builder.ts |
提示词增强:输入容忍说明 + 非规则内容过滤 + id/severity 推断要求 + description/message 互推要求 + 对照示例 |
src/rules/converters/md-converter.ts |
同步 buildSystemPrompt 增强内容(与 prompt-builder 对齐) |
src/rules/converters/txt-converter.ts |
同上 |
src/rules/converters/docx-converter.ts |
同上 |
src/rules/converters/excel-converter.ts |
同上,表格输入说明略调 |
src/rules/converters/pptx-converter.ts |
同上 |
src/rules/import-service.ts |
parseImportableYaml 放宽:severity 兜底、id 占位、description/message 互填 |
src/rules/import-preview.ts |
id 改可编辑 input + 占位 id 高亮 + 确认校验加 id 非空 |
src/views/setupView.ts |
导入入口加简短内容指引文案 |
src/i18n/messages.ts |
新增文案 key:占位提示、指引文案 |
注:六个转换器的 buildSystemPrompt 当前各自重复了完整提示词。改动一时可考虑将公共部分抽到 prompt-builder.ts 统一构建,减少重复;但为控制改动范围,本次仍保持各转换器独立维护提示词,仅同步增强内容。是否抽公共函数留待实现阶段判断。
测试更新
现有测试核查
| 测试文件 | 核查点 |
|---|---|
src/test/import-dedup.test.ts |
是否依赖"缺 id 即丢弃"的旧行为;若是,同步调整断言 |
src/test/rule-filter.test.ts |
是否依赖 severity 严格校验;loadActiveRules 行为不变,预期无影响 |
新增测试
| 测试场景 | 验证点 |
|---|---|
| severity 缺失 | 解析后降级为 warning,规则保留 |
| severity 非法值 | 解析后降级为 warning,规则保留 |
| id 缺失 | 解析后生成占位 id rule-N,规则保留 |
| description 缺失、message 存在 | 解析后 description 由 message 填充,规则保留 |
| message 缺失、description 存在 | 解析后 message 由 description 填充,规则保留 |
| description 与 message 同时缺失 | 该条被丢弃(非规则) |
| 正常完整输入 | 行为不变,无回归 |
不改动的部分
- 文件格式扩展名范围不变(仍六种)
- YAML 直传路径不变(不经 AI,本就最宽松)
- 预览 Webview 的整体布局与交互逻辑不变(仅 id 区域改动)
- 去重检测逻辑不变(
prompt-builder.ts的buildDedupPromptSection不动) - 规则应用阶段不变(
rule-filter.ts的filterForDocument不动)
风险与权衡
AI 推断的不确定性
改动一依赖 AI 行为,效果有不确定性:AI 可能生成不理想的 id(如过长、非 kebab-case、语义不准)。缓解措施:解析兜底保证下限(规则不丢失),预览阶段用户可逐条修正 id。即使 AI 推断不完美,也比"规则直接消失"好。
放宽丢弃条件的副作用
改动二放宽了丢弃条件,可能引入语义不完整的规则(如 id 为占位符、severity 兜底为 warning 但实际应是 error、description 与 message 文本雷同)。缓解措施:预览阶段用户仍可逐条删除/注释/修正,占位 id 高亮提示用户确认。可控。
提示词膨胀
六个转换器各维护一份完整提示词,改动一需同步六处,提示词长度增加。缓解措施:实现阶段评估是否抽公共函数到 prompt-builder.ts,若改动范围可控则保持现状。
兼容性
预览面板 id 改可编辑为向后兼容改动(原 <span> 改 <input>,收集逻辑已支持)。parseImportableYaml 放宽丢弃条件不影响现有调用方,原"完整字段才进入预览"的规则仍正常通过。
验证顺序
按项目规范 lint → compile → test:
npm run lint— ESLint 通过npm run compile— TypeScript 编译通过npm test— 全部测试通过,含新增测试用例- F5 启动 Extension Dev Host,手动验证:
- 导入松散文本(一段话描述多条规则),确认 AI 正确拆分并生成 id
- 导入缺 severity 的 YAML,确认降级为 warning 且规则保留
- 预览面板修改 id,确认可编辑
- 确认校验阻止空 id 提交