Files
2026Technology-Competition/docs/superpowers/specs/2026-07-24-custom-rule-dedup-enhancement-design.md
T
范智鹏 effcf30802 feat: 适配器 i18n + Provider 动态注册 + SetupView 重构 + jars 资源
- 适配器 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 依赖及测试用例
2026-07-28 22:57:15 +08:00

7.1 KiB
Raw Blame History

自定义规则导入去重增强方案

1. 架构概览

现状

static-rules.json ──→ buildDedupPromptSection() ──→ 嵌入 AI 提示词 → AI 判断 exact/overlap/none

目标

static-rules.json ──┐
                    ├──→ buildDedupPromptSection(existingRules) ──→ AI 判断 exact/overlap/none
已导入自定义规则 ────┘
  ↑
loadActiveRules() ── 自动加载 .code-review/rules/*.yaml

数据流

ImportService.convert()
  │  ① 调用 loadActiveRules(workspaceRoot) 加载已有自定义规则
  │  ② 将 existingRules 传递给 converter.convert()
  │     │
  │     └─ converter.buildSystemPrompt(existingRules)
  │           │
  │           └─ buildDedupPromptSection(existingRules)
  │                 │
  │                 ├─ 输出静态 linter 规则(不变)
  │                 └─ 追加已导入自定义规则,ID 前缀 custom/
  │
  │  ③ AI 返回结果,含 duplicateOf: custom/xxx、duplicateLevel 等字段
  │  ④ parseImportableYaml() 解析,字段不变
  │
  ▼
showImportPreview()
  │  ⑤ 识别 duplicateOf 的 custom/ 前缀,显示不同文案
  │
  ▼
buildFinalYaml()
  │  ⑥ 注释头文案区分 custom/ 前缀
  │
  ▼
applyConversion() → 写入 .yaml

2. 文件变更清单

文件 变更类型 说明
src/rules/converters/prompt-builder.ts 修改 buildDedupPromptSection() 增加 existingCustomRules 参数
src/rules/converters/converter.ts 修改 convert() 增加可选参数 existingRules
src/rules/converters/md-converter.ts 修改 传递 existingRulesbuildSystemPrompt()
src/rules/converters/txt-converter.ts 修改 同上
src/rules/converters/excel-converter.ts 修改 同上
src/rules/converters/docx-converter.ts 修改 同上
src/rules/converters/pptx-converter.ts 修改 同上
src/rules/import-service.ts 修改 convert() 加载已有规则;buildFinalYaml() 格式化 custom 注释头
src/rules/import-preview.ts 修改 识别 custom/ 前缀显示区分文案
src/test/import-dedup.test.ts 修改 追加测试用例(自定义规则 exact/overlap/none

3. 关键接口变更

3.1 prompt-builder.ts

import type { CustomRule } from '../../types';

// 新增参数
export function buildDedupPromptSection(existingCustomRules?: CustomRule[]): string;

输出格式变更:

## 已知规则清单(用于重复检测)

### 内置 Linter 规则
#### eslint (XX 条)
- eslint/rule-id: description
...

### 已导入的自定义规则 (N 条)
- custom/my-rule: 禁止 console.log
- custom/no-var: 使用 const/let 替代 var

判定时请精确匹配上述规则 ID,而非模糊匹配分类。

3.2 converter.ts

import type { CustomRule } from '../../types';

export interface RuleConverter {
  supportedExtensions: string[];
  convert(
    srcPath: string,
    context: vscode.ExtensionContext,
    existingRules?: CustomRule[],       // 新增参数
  ): Promise<string | null>;
}

3.3 Converters5 个文件,模式一致)

每个 converter 的 buildSystemPrompt 改为接受 existingRules 参数并向下传递。

改前:

function buildSystemPrompt(): string {
  return `...${buildDedupPromptSection()}...`;
}

改后:

function buildSystemPrompt(existingRules?: CustomRule[]): string {
  return `...${buildDedupPromptSection(existingRules)}...`;
}

对应 convert() 方法:

async convert(srcPath: string, context: vscode.ExtensionContext, existingRules?: CustomRule[]): Promise<string | null> {
  const content = fs.readFileSync(srcPath, 'utf-8');
  return convertContentWithAI(content, context, buildSystemPrompt(existingRules));
}

3.4 import-service.ts

ImportService.convert() — 新增加载已有规则

import { loadActiveRules } from './yaml-parser';

async convert(srcPath: string, context: vscode.ExtensionContext): Promise<ConversionResult> {
  const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
  const existingRules = workspaceRoot ? loadActiveRules(workspaceRoot) : [];

  const ext = path.extname(srcPath).toLowerCase();
  const converter = this.converters.get(ext);
  // ...
  const yamlContent = await converter.convert(srcPath, context, existingRules);
  // ...
}

buildFinalYaml() — 注释头区分 custom 前缀

在生成 # [DUPLICATE] 注释头时,判断 duplicateOf 是否以 custom/ 开头:

  • custom/xxx# [DUPLICATE: exact] 重复自定义规则 xxx(检测目标完全一致)
  • eslint/xxx# [DUPLICATE: exact] 重复 eslint/xxx(检测目标完全一致)(不变)
  • overlap 同理

3.5 import-preview.ts

新增辅助函数,在 renderRuleCard 中调用:

function formatDuplicateOf(dupOf: string | undefined): { type: 'custom' | 'linter'; ruleName: string } {
  if (!dupOf) return { type: 'linter', ruleName: 'unknown' };
  if (dupOf.startsWith('custom/')) {
    return { type: 'custom', ruleName: dupOf.slice(7) };
  }
  return { type: 'linter', ruleName: dupOf };
}

UI 显示规则:

duplicateOf type 显示文案
custom/no-console-log custom 重复:自定义规则 no-console-log
eslint/no-console linter 重复:eslint/no-console(不变)
overlap + custom custom 与自定义规则 no-console-log 部分重叠
overlap + linter linter 与 eslint/no-console 部分重叠(不变)

4. 不变的部分

  • ImportableRule 接口(字段不变,duplicateOf 的值新加 custom/ 前缀由 AI 输出)
  • PreviewDecision 接口
  • ConversionResult 接口
  • YamlConverter(YAML 直接复制不走 AI,不需要去重)
  • parseImportableYaml()(解析逻辑不变,duplicateOf 字段值变化不影响解析)
  • buildFinalYaml() 的保留逻辑(去重字段剥离、# 注释前缀)不变

5. 影响范围

正面

  • 导入新规则时自动对比已有自定义规则,避免重复导入
  • 已导入规则之间交叉重复也可检测(规则文件 A 和 B 之间有重复 ID/语义)

风险与应对

  • 提示词长度增加:如果已有规则很多(>50条),prompt 会变长。应对:当前实测 1788 条静态规则 + 50 条自定义规则约 80KB token,主流模型可承受。如果未来规则量过大,可考虑只传 ID 列表。
  • 自参考:正在导入的文件尚未写入 .code-review/rules/,不会出现自己检测自己的情况。
  • custom/ 前缀规范:需要 AI 理解并准确输出,已在 prompt 中明确指定格式,并在已有示例中示范。

测试覆盖

需要在 src/test/import-dedup.test.ts 中新增:

  1. 自定义规则 exact → 默认注释
  2. 自定义规则 overlap → 默认保留
  3. 自定义规则 exact 用户恢复 → 取消注释
  4. 混合场景(部分与 linter 重复、部分与 custom 重复)