feat: 规则预过滤 + 自定义规则去重增强 + 导入预览编辑 + Word/PPT 导入 + UI 优化
- 规则预过滤:根据文件语言注入匹配的自定义规则,跳过不相关规则 - 导入去重:对比已有规则,重复项自动注释/标注 - 导入预览编辑:支持在导入前编辑规则字段(severity、description 等) - Word/PPT 导入:新增 DocxConverter、PptxConverter - UI:审查报告移除设置按钮;自定义规则标签显示注入比例
This commit is contained in:
@@ -98,3 +98,14 @@
|
||||
| 2026-07-21 21:50 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 新增 Excel 导入支持:ExcelConverter + 更新文件选择过滤器 + 注册到 ImportService | 无 | src/rules/converters/excel-converter.ts src/views/setupView.ts package.json | deepseek-v4-flash |
|
||||
| 2026-07-21 22:04 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | Excel 导入改为全 AI 转换:移除固定列名校验,转为 Markdown 表格 → AI 识别 → YAML | 无 | src/rules/converters/excel-converter.ts docs/superpowers/specs/2026-07-21-excel-ai-converter-design.md | deepseek-v4-flash |
|
||||
| 2026-07-22 20:40 | ① 用户提出 → ⑤ 编码实现 → ⑥ 审查验证 | 三 tab 修复按钮一致性:merger 中 fixableLinterIndices 取消 suggestion 过滤(全部可修复);AI 审查 section header 增加"全部修复"按钮;底部无效"批量修复"按钮移除 | 无 | src/merger/merger.ts src/panel/webview.ts | deepseek-v4-flash |
|
||||
| 2026-07-23 20:06 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 | Part A 语言预过滤 + Part B 导入去重:新建 rule-filter.ts(软过滤+语言别名+JSP并集)、static-rules.json(简化版占位)、prompt-builder.ts、import-types.ts、import-preview.ts(Webview三分区预览);改造 import-service.ts 拆分 convert/applyConversion + convertContentWithAI 扩展签名;Converter 接口改签名返回 yaml 内容;三个 Converter 各自注入去重 prompt;setupView.ts addRule 分支 YAML直接复制/非YAML转换预览;新增 rule-filter.test.ts(15用例)+ import-dedup.test.ts(9用例);tsconfig.json 加 resolveJsonModule | 设计文档 docs/superpowers/specs/2026-07-23-rule-prefilter-design.md(用户提供已定稿方案) | tsconfig.json src/types.ts src/rules/rule-filter.ts src/rules/yaml-parser.ts src/activation/commands.ts src/rules/static-rules.json src/rules/converters/prompt-builder.ts src/rules/import-types.ts src/rules/import-service.ts src/rules/import-preview.ts src/rules/converters/converter.ts src/rules/converters/yaml-converter.ts src/rules/converters/md-converter.ts src/rules/converters/txt-converter.ts src/rules/converters/excel-converter.ts src/views/setupView.ts src/test/rule-filter.test.ts src/test/import-dedup.test.ts | deepseek-v4-pro |
|
||||
| 2026-07-23 20:59 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | Bug fix: AI 生成规则超时 — timeoutMs 硬编码 30s 改为 getAITimeout() * 1000(默认 300s);AbortError 专用友好提示 | 无 | src/rules/import-service.ts | deepseek-v4-flash |
|
||||
| 2026-07-23 21:21 | ⑤ 编码实现 | 第三批体验增强:merger.ts MergedReport/MergeInput 新增 customRuleFilterInfo;commands.ts 传入过滤统计;webview.ts 自定义规则 Tab 展示"注入 X/Y 条"及 skippedRequestA 跳过提示 | 无 | src/merger/merger.ts src/activation/commands.ts src/panel/webview.ts | deepseek-v4-pro |
|
||||
| 2026-07-23 21:50 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | static-rules.json 从 14 条占位规则替换为完整规则集(441 条:ESLint 61, ts-eslint 24, Stylelint 12, PMD Java 259, PMD JSP 10, sql-lint 75),数据源来自 node_modules 和项目文件及 sqlfluff 输出 | 暂时生成脚本 scripts/generate-rules-json.mjs(已删除) | src/rules/static-rules.json | deepseek-v4-flash |
|
||||
| 2026-07-24 18:52 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | Excel 多 Sheet 支持:遍历所有 Sheet,含 ## SheetName 标题分隔,合并为一份 Markdown 后统一由 AI 转换为 YAML | 无 | src/rules/converters/excel-converter.ts docs/superpowers/specs/2026-07-24-excel-multi-sheet-design.md | deepseek-v4-flash |
|
||||
| 2026-07-24 19:22 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | Word/PPT 导入:DocxConverter (mammoth extractRawText → AI)、PptxConverter (officeparser parseOffice → toText → AI);注册到 setupView;文件过滤器增加 docx/pptx;依赖 mammoth + officeparser + @types/mammoth | mammoth 类型方案:先考虑 require()/@ts-ignore → 最终用 .d.ts;pptx-converter.ts 类型先用 Awaited<ReturnType<…>> → 改为 OfficeParserAST | src/rules/converters/docx-converter.ts src/rules/converters/pptx-converter.ts src/types/mammoth.d.ts src/views/setupView.ts package.json docs/superpowers/specs/2026-07-24-word-ppt-import-design.md | deepseek-v4-flash |
|
||||
| 2026-07-24 23:48 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 去重依据追加已导入自定义规则:prompt-builder.ts buildDedupPromptSection() 新增 existingCustomRules 参数输出 custom/ 前缀规则列表;converter 接口 + 5 个 converter + yaml-converter 透传 existingRules;import-service.ts convert() 调用 loadActiveRules() 加载已有规则、buildFinalYaml() 注释头区分 custom/ 前缀文案;import-preview.ts 预览面板自定义规则区分显示 | 提示词格式迭代:标题从"静态分析规则清单"改为"已知规则清单";duplicateOf 字段描述从"linter 规则 ID"扩展为"linter 如 …;自定义如 …";buildFinalYaml 空格处理:先试 dupDisplay 变量合并 → 改条件分支分别输出避免空格问题 | src/rules/converters/prompt-builder.ts src/rules/converters/converter.ts src/rules/converters/md-converter.ts 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/converters/yaml-converter.ts src/rules/import-service.ts src/rules/import-preview.ts docs/superpowers/specs/2026-07-24-custom-rule-dedup-enhancement-design.md | deepseek-v4-pro |
|
||||
| 2026-07-25 22:57 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 规则预览编辑:规则卡片折叠/展开编辑表单(severity下拉框、description/message多行文本、languages/excludeLanguages标签输入、id只读);renderRulesToYaml 从 rules 数组生成 YAML;无编辑时回退原始逻辑;补充5个编辑场景测试 | import-preview.ts 初版尝试 extension 端实时追踪 modifiedRules(updateRule 消息),后改为 confirm 时 webview 端一次性收集 DOM 值发送;测试断言 'java' 子串匹配 'javascript' 误判 + 注释行缩进不匹配,各迭代一次修复 | src/rules/import-types.ts src/rules/import-service.ts src/rules/import-preview.ts src/test/import-dedup.test.ts docs/superpowers/specs/2026-07-25-import-preview-edit-design.md | deepseek-v4-flash |
|
||||
| 2026-07-25 23:08 | ① 用户提出 → ⑤ 编码实现 | 预览编辑表单 UI 优化:id 字段添加标签说明;每个字段标签后加括号注释(如 severity(严重级别)) | 无 | src/rules/import-preview.ts | deepseek-v4-flash |
|
||||
| 2026-07-25 23:45 | ① 用户提出 → ⑤ 编码实现 | 预览编辑表单布局调整:移除顶部多余 id 大标题;id 标签与值同行 + 切换按钮同行右对齐;id 样式简化为纯文本 | id 位置迭代:一行 → 上下 → 一行 + 按钮同行 | src/rules/import-preview.ts | deepseek-v4-flash |
|
||||
| 2026-07-25 23:50 | ① 用户提出 → ⑤ 编码实现 → ⑥ 审查验证 | 去除代码审查报告画面底部的设置按钮(消息类型、HTML 按钮、消息处理 handler 全部移除) | 无 | src/panel/webview.ts | deepseek-v4-flash-free |
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "vscode-code-reviewer",
|
||||
"displayName": "净码特工 · Code Purifier",
|
||||
"description": "AI 驱动的代码审查与规范检查一体化 VSCode 插件",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"engines": {
|
||||
"vscode": "^1.120.0"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as vscode from 'vscode';
|
||||
import { Orchestrator } from '../orchestrator/orchestrator';
|
||||
import { runAIReview } from '../ai/engine';
|
||||
import { loadActiveRules } from '../rules/yaml-parser';
|
||||
import { filterAndSummarize } from '../rules/rule-filter';
|
||||
import { mergeResults, MergedReport } from '../merger/merger';
|
||||
import { reportToMarkdown } from '../utils/report';
|
||||
import { getApiKey } from '../config';
|
||||
@@ -38,9 +39,10 @@ export function registerCommands(
|
||||
|
||||
progress.report({ message: '运行 AI 审查...' });
|
||||
|
||||
const customRules = loadActiveRules(workspaceRoot);
|
||||
const allRules = loadActiveRules(workspaceRoot);
|
||||
const filterResult = filterAndSummarize(allRules, document);
|
||||
const code = document.getText();
|
||||
const aiResult = await runAIReview(context, code, staticResult.diagnostics, customRules);
|
||||
const aiResult = await runAIReview(context, code, staticResult.diagnostics, filterResult.relevant);
|
||||
|
||||
currentReport = mergeResults({
|
||||
staticDiagnostics: staticResult.diagnostics,
|
||||
@@ -53,6 +55,12 @@ export function registerCommands(
|
||||
filePath: document.uri.fsPath,
|
||||
language: document.languageId,
|
||||
adapterIds: staticResult.adapterIds,
|
||||
customRuleFilterInfo: {
|
||||
totalActive: allRules.length,
|
||||
injected: filterResult.relevant.length,
|
||||
filteredOut: filterResult.filteredOut.length,
|
||||
skippedRequestA: filterResult.skippedRequestA,
|
||||
},
|
||||
});
|
||||
|
||||
const panel = ReviewPanel.createOrShow(context.extensionUri);
|
||||
|
||||
@@ -18,6 +18,12 @@ export interface MergedReport {
|
||||
adapterNames: string[];
|
||||
fixableLinterIndices: number[];
|
||||
fixableCustomIndices: number[];
|
||||
customRuleFilterInfo?: {
|
||||
totalActive: number;
|
||||
injected: number;
|
||||
filteredOut: number;
|
||||
skippedRequestA: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface MergeInput {
|
||||
@@ -31,6 +37,12 @@ interface MergeInput {
|
||||
filePath: string;
|
||||
language: string;
|
||||
adapterIds: string[];
|
||||
customRuleFilterInfo?: {
|
||||
totalActive: number;
|
||||
injected: number;
|
||||
filteredOut: number;
|
||||
skippedRequestA: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export function mergeResults(input: MergeInput): MergedReport {
|
||||
@@ -74,5 +86,6 @@ export function mergeResults(input: MergeInput): MergedReport {
|
||||
adapterNames: input.adapterIds,
|
||||
fixableLinterIndices,
|
||||
fixableCustomIndices,
|
||||
customRuleFilterInfo: input.customRuleFilterInfo,
|
||||
};
|
||||
}
|
||||
|
||||
+16
-7
@@ -2,7 +2,7 @@ import * as vscode from 'vscode';
|
||||
import { MergedReport } from '../merger/merger';
|
||||
|
||||
interface PanelMessage {
|
||||
type: 'navigate' | 'rerun' | 'export' | 'settings' | 'fix' | 'fixAll';
|
||||
type: 'navigate' | 'rerun' | 'export' | 'fix' | 'fixAll';
|
||||
line?: number;
|
||||
ruleId?: string;
|
||||
source?: 'linter' | 'custom' | 'ai';
|
||||
@@ -118,6 +118,11 @@ export class ReviewPanel {
|
||||
|
||||
const linterToolName = report.adapterNames.length > 0 ? report.adapterNames.join(' + ') : '静态分析';
|
||||
|
||||
const customFilterInfo = report.customRuleFilterInfo;
|
||||
const customFilterLabel = customFilterInfo
|
||||
? `(注入 ${customFilterInfo.injected}/${customFilterInfo.totalActive} 条)`
|
||||
: '';
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
@@ -235,7 +240,7 @@ ${errorBox}
|
||||
|
||||
<div class="tab-bar">
|
||||
<button class="tab active" data-tab="linter" onclick="switchTab('linter')">🔧 ${report.adapterNames.length > 0 ? report.adapterNames.join(' + ') : '静态分析'} ${tabCount(linterErrors, linterWarnings, linterInfos)}</button>
|
||||
<button class="tab" data-tab="custom" onclick="switchTab('custom')">📋 自定义规则 ${tabCount(customErrors, customWarnings, customInfos)}</button>
|
||||
<button class="tab" data-tab="custom" onclick="switchTab('custom')">📋 自定义规则 ${tabCount(customErrors, customWarnings, customInfos)} <span style="font-size:11px;color:var(--vscode-descriptionForeground);">${customFilterLabel}</span></button>
|
||||
<button class="tab" data-tab="ai" onclick="switchTab('ai')">🤖 AI 审查 ${tabCount(aiErrors, aiWarnings, aiInfos)}</button>
|
||||
</div>
|
||||
|
||||
@@ -252,7 +257,7 @@ ${errorBox}
|
||||
<div class="actions">
|
||||
<button class="btn btn-primary" onclick="send('rerun')">🔄 重新审查</button>
|
||||
<button class="btn" onclick="send('export')">📄 导出报告</button>
|
||||
<button class="btn" onclick="send('settings')">⚙️ 设置</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -287,11 +292,18 @@ ${errorBox}
|
||||
}
|
||||
|
||||
private buildCustomList(report: MergedReport, fixableSet: Set<number>): string {
|
||||
const filterInfo = report.customRuleFilterInfo;
|
||||
if (filterInfo?.skippedRequestA) {
|
||||
return '<div class="empty">当前文件语言无匹配的自定义规则,已跳过规则评估</div>';
|
||||
}
|
||||
if (report.customRuleDiagnostics.length === 0) {
|
||||
return '<div class="empty">未发现规则违规</div>';
|
||||
}
|
||||
const hasFixable = fixableSet.size > 0;
|
||||
return `<div class="section-header"><span class="section-header-title">自定义规则 · ${report.customRuleCount} 个问题</span>${hasFixable ? '<button class="btn" onclick="send(\'fixAll\')">全部修复</button>' : ''}</div>`
|
||||
const filterLabel = filterInfo
|
||||
? `(注入 ${filterInfo.injected}/${filterInfo.totalActive} 条规则)`
|
||||
: '';
|
||||
return `<div class="section-header"><span class="section-header-title">自定义规则 · ${report.customRuleCount} 个问题${filterLabel}</span>${hasFixable ? '<button class="btn" onclick="send(\'fixAll\')">全部修复</button>' : ''}</div>`
|
||||
+ report.customRuleDiagnostics.map((d, i) => this.buildIssueItem(d.severity, d.ruleId, d.message, d.range.start.line, 'custom', d.suggestion, fixableSet.has(i), undefined, false)).join('');
|
||||
}
|
||||
|
||||
@@ -377,9 +389,6 @@ ${errorBox}
|
||||
case 'export':
|
||||
vscode.commands.executeCommand('codeReviewer.exportReport');
|
||||
break;
|
||||
case 'settings':
|
||||
vscode.commands.executeCommand('codeReviewer.openSetup');
|
||||
break;
|
||||
case 'fix':
|
||||
vscode.commands.executeCommand('codeReviewer.fixIssue', message);
|
||||
break;
|
||||
|
||||
@@ -8,6 +8,7 @@ interface RuleYamlItem {
|
||||
description: string;
|
||||
message: string;
|
||||
languages?: string[];
|
||||
excludeLanguages?: string[];
|
||||
}
|
||||
|
||||
function parseYamlSimple(content: string): object[] {
|
||||
@@ -75,6 +76,7 @@ export function loadActiveRules(workspaceRoot: string): CustomRule[] {
|
||||
description: item.description,
|
||||
message: item.message,
|
||||
languages: item.languages,
|
||||
excludeLanguages: item.excludeLanguages,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ export interface CustomRule {
|
||||
description: string;
|
||||
message: string;
|
||||
languages?: string[];
|
||||
excludeLanguages?: string[];
|
||||
}
|
||||
|
||||
export type Severity = 'error' | 'warning' | 'info';
|
||||
|
||||
+31
-2
@@ -6,10 +6,13 @@ import { getApiKey, setApiKey } from '../config/secret';
|
||||
import { createProvider, getAllProviderMeta, getProviderModels } from '../ai/factory';
|
||||
import { listRuleFiles } from '../rules/yaml-parser';
|
||||
import { ImportService } from '../rules/import-service';
|
||||
import { showImportPreview } from '../rules/import-preview';
|
||||
import { YamlConverter } from '../rules/converters/yaml-converter';
|
||||
import { MdConverter } from '../rules/converters/md-converter';
|
||||
import { TxtConverter } from '../rules/converters/txt-converter';
|
||||
import { ExcelConverter } from '../rules/converters/excel-converter';
|
||||
import { DocxConverter } from '../rules/converters/docx-converter';
|
||||
import { PptxConverter } from '../rules/converters/pptx-converter';
|
||||
|
||||
const languageLabels: Record<string, string> = {
|
||||
'zh-CN': '中文(简体)',
|
||||
@@ -28,6 +31,8 @@ export class SetupViewProvider implements vscode.WebviewViewProvider {
|
||||
this.importService.registerConverter(new MdConverter());
|
||||
this.importService.registerConverter(new TxtConverter());
|
||||
this.importService.registerConverter(new ExcelConverter());
|
||||
this.importService.registerConverter(new DocxConverter());
|
||||
this.importService.registerConverter(new PptxConverter());
|
||||
}
|
||||
|
||||
resolveWebviewView(
|
||||
@@ -187,11 +192,12 @@ export class SetupViewProvider implements vscode.WebviewViewProvider {
|
||||
const result = await vscode.window.showOpenDialog({
|
||||
canSelectMany: false,
|
||||
openLabel: '选择规则文件',
|
||||
filters: { '规则文件': ['yaml', 'yml', 'md', 'txt', 'xlsx', 'xls'] },
|
||||
filters: { '规则文件': ['yaml', 'yml', 'md', 'txt', 'xlsx', 'xls', 'docx', 'pptx'] },
|
||||
});
|
||||
if (!result || result.length === 0) { return; }
|
||||
|
||||
const srcPath = result[0].fsPath;
|
||||
const ext = path.extname(srcPath).toLowerCase();
|
||||
|
||||
const rulesDir = path.join(workspaceRoot, '.code-review', 'rules');
|
||||
if (!fs.existsSync(rulesDir)) {
|
||||
@@ -206,7 +212,30 @@ export class SetupViewProvider implements vscode.WebviewViewProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.importService.convert(srcPath, yamlPath, this.context);
|
||||
if (ext === '.yaml' || ext === '.yml') {
|
||||
fs.copyFileSync(srcPath, yamlPath);
|
||||
vscode.window.showInformationMessage(`规则文件已导入: ${yamlFileName}`);
|
||||
} else {
|
||||
try {
|
||||
const conversion = await this.importService.convert(srcPath, this.context);
|
||||
|
||||
const decision = await showImportPreview(conversion);
|
||||
if (!decision || !decision.confirmed) {
|
||||
vscode.window.showInformationMessage('导入已取消');
|
||||
return;
|
||||
}
|
||||
|
||||
this.importService.applyConversion(conversion, decision, yamlPath);
|
||||
vscode.window.showInformationMessage(
|
||||
`规则已导入: ${yamlFileName}(${conversion.rules.length} 条,` +
|
||||
`${conversion.exactCount} 条完全重复已注释,` +
|
||||
`${conversion.overlapCount} 条部分重叠已标注)`
|
||||
);
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
vscode.window.showErrorMessage(`规则导入失败: ${msg}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async resetConfig(): Promise<void> {
|
||||
|
||||
+2
-1
@@ -10,7 +10,8 @@
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true
|
||||
/* Additional Checks */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
Reference in New Issue
Block a user