Files
2026Technology-Competition/src/rules/converters/prompt-builder.ts
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

474 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import staticRules from '../static-rules.json';
import type { CustomRule } from '../../types';
import { getLanguage } from '../../i18n/messages';
export type PromptInputType = 'freeform' | 'spreadsheet';
type Lang = 'zh-CN' | 'en' | 'ja';
interface PromptStrings {
role: (inputDesc: string) => string;
inputToleranceTitle: string;
inputToleranceLines: string[];
nonRuleFilterTitle: string;
nonRuleFilterLines: string[];
fieldDefsTitle: string;
fieldDefsLines: string[];
languageRulesTitle: string;
languageRulesLines: string[];
exampleTitle: string;
example1Input: string;
example1Output: string;
example2Input: string;
example2Output: string;
staticAnalysisTitle: string;
staticAnalysisLines: string[];
finalInstruction: string;
dedupHeader: string;
dedupLinterLabel: (name: string, count: number) => string;
dedupCustomLabel: (count: number) => string;
dedupFooter: string;
outputLang: string;
}
const p: Record<Lang, PromptStrings> = {
'zh-CN': {
role: (inputDesc) => `你是一个代码审查规则转换器。将用户提供的${inputDesc},转换为结构化的 YAML 格式,用于代码审查工具。`,
inputToleranceTitle: '## 输入容忍说明',
inputToleranceLines: [
'用户输入可能有多种形态,你必须接受并处理以下任一形式:',
'- 自然语言段落(一段或多段话描述规则)',
'- 无序列表(每条规则一行或一段)',
'- 表格(列名不固定,从语义推断)',
'- 混合形式(段落 + 列表 + 表格组合)',
'',
'不得因输入格式非标准而拒绝转换。应主动从松散描述中提取规则语义。',
'',
'- 按输入文档的段落或列表项顺序处理,保持原文顺序输出',
'- 不要合并或拆分原文中已是独立条目的规则',
'- 一个段落包含多条规则时才拆分,单条规则不要拆成多条',
],
nonRuleFilterTitle: '## 非规则内容过滤',
nonRuleFilterLines: [
'用户输入中可能混入项目介绍、背景说明、代码示例、章节标题等非规则内容。你必须:',
'- 识别并跳过非规则内容,仅将真正的编码规则转为 YAML 条目',
'- 代码示例、项目介绍等仅作为理解规则语义的上下文,自身不输出为规则',
'- 若某段内容无法判断为规则(既无规则意图也无违反提示),直接忽略,不强行转换',
],
fieldDefsTitle: '## 字段定义',
fieldDefsLines: [
'每条规则需要包含以下字段:',
'- id: 规则唯一标识(kebab-case 英文,语义化、简短,如 no-console-log、avoid-magic-number',
' **必须**基于规则描述内容自动生成语义化的 id',
' 即使输入中无显式 id 标识,也必须根据 description/message 的语义推断出合适的 id',
' 多条规则之间 id 不得重复',
' id 只能使用 description/message 中已有的英文单词或短语,转换为 kebab-case',
' 不要自行创造原文中没有的英文词汇',
' 如果输入全中文,从语义提取核心关键词翻译为简短英文(2-4 个词)',
'- severity: 严重级别(error / warning / info',
' **必须**输出。按规则语义推断:',
' error: 会导致 bug / 安全问题 / 数据损坏',
' warning: 潜在问题 / 不良实践',
' info: 风格 / 可读性建议',
' 即使输入中无显式严重级别,也必须根据规则后果的严重程度推断',
' 如果无法从输入中确定严重级别,默认填 warning',
' 只有明确涉及安全、数据泄露、崩溃风险时才填 error',
'- description: 规则简短描述',
' **必须**输出。若输入中不明显,从 message 的内容反向推导出简短描述',
'- message: 违反时的提示消息',
' **必须**输出。若输入中不明显,从 description 的内容推导出违反提示',
' description 与 message 语义可相近,无需强行区分口吻,但两者都必须填写',
'- languages: 适用语言数组(可选,如 [javascript, typescript]',
'- excludeLanguages: 明确排除的语言数组(可选,如 [css, sql]',
'- duplicateOf: 重复的规则 IDlinter 如 eslint/no-console;自定义如 custom/my-rule',
'- duplicateLevel: 重复程度(exact / overlap / none',
'- duplicateReason: 重复/重叠原因说明(overlap 档必填)',
],
languageRulesTitle: '## 语言字段规则(严格遵守)',
languageRulesLines: [
'对于每条规则的 languages 字段:',
'1. 规则描述中含明确语言关键词(如 "Java"、"JavaScript"、"CSS")→ 使用 languages 白名单',
'2. 规则适用于大多数语言,只有少数不适用 → 使用 excludeLanguages 黑名单',
'3. 无法确定适用语言,或规则为通用规范 → languages 与 excludeLanguages 均留空',
'4. languages 和 excludeLanguages 不可同时非空',
'5. 语言名使用小写:java, javascript, typescript, css, sql, plsql, jsp',
'6. 严禁猜测。留空比猜测错误更安全。',
],
exampleTitle: '## 输入输出示例',
example1Input: '不要用 console.log,生产环境会泄露信息。还有不要留下未使用的变量,看着乱。',
example1Output: [
'- 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',
].join('\n'),
example2Input: [
'本项目是一个电商后台管理系统,主要使用 Java + Spring Boot 开发。',
'代码规范要求:Service 层方法必须有日志记录,方便排查问题。',
'示例代码:',
' public void createOrder(Order order) { ... }',
'另外,Controller 层返回值统一用 Result 包装,不要直接返回 Map。',
].join('\n'),
example2Output: [
'- 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',
].join('\n'),
staticAnalysisTitle: '## 静态分析重复检测',
staticAnalysisLines: [
'对于每条规则,判断其检测目标与触发条件是否与上述某个 linter 规则或自定义规则重复:',
'',
'- **exact**:检测目标与触发条件完全一致(会报出同样的问题行)→ 输出 duplicateOf + duplicateLevel: exact',
'- **overlap**:检测目标相同,但本规则有额外要求或更窄范围 → 输出 duplicateOf + duplicateLevel: overlap + duplicateReason',
'- **none**:检测目标不同 → 输出 duplicateLevel: none',
'',
'仅"话题相似"不算重复。例如:',
'- "未使用变量应删除" → exact(重复 eslint/no-unused-vars',
'- "禁止在 console.log 中输出敏感信息" → none(检测目标不同)',
'',
'仅输出 YAML,不要额外说明。',
],
finalInstruction: '只输出 YAML 内容,不要输出 markdown 代码块标记,不要输出解释性文字',
dedupHeader: '## 已知规则清单(用于重复检测)',
dedupLinterLabel: (name, count) => `### ${name} (${count} 条)`,
dedupCustomLabel: (count) => `### 已导入的自定义规则 (${count} 条)`,
dedupFooter: '判定时请精确匹配上述规则 ID,而非模糊匹配分类。',
outputLang: '输出语言:zh-CN',
},
en: {
role: (inputDesc) => `You are a code review rule converter. Convert the ${inputDesc} provided by the user into structured YAML format for a code review tool. All descriptions and messages must be in English.`,
inputToleranceTitle: '## Input Tolerance',
inputToleranceLines: [
'User input may come in various forms. You must accept and process any of the following:',
'- Natural language paragraphs (one or more paragraphs describing rules)',
'- Unordered lists (one rule per line or paragraph)',
'- Tables (column names may vary; infer from semantics)',
'- Mixed forms (paragraphs + lists + tables)',
'',
'Do not reject conversion due to non-standard input format. Actively extract rule semantics from loose descriptions.',
'',
'- Process in the order of the input document paragraphs or list items, preserving original order',
'- Do not merge or split entries that are already independent rules in the original text',
'- Only split when a single paragraph contains multiple rules; do not split a single rule into multiple',
],
nonRuleFilterTitle: '## Non-Rule Content Filtering',
nonRuleFilterLines: [
'User input may contain project introductions, background info, code examples, section titles, etc. You must:',
'- Identify and skip non-rule content; only convert actual coding rules into YAML entries',
'- Code examples, project descriptions etc. serve only as context for understanding rule semantics; do not output them as rules',
'- If content cannot be identified as a rule (no rule intent or violation hint), ignore it; do not force conversion',
],
fieldDefsTitle: '## Field Definitions',
fieldDefsLines: [
'Each rule must include the following fields:',
'- id: Unique rule identifier (kebab-case English, semantic and concise, e.g., no-console-log, avoid-magic-number)',
' **Must** generate a semantic id based on the rule description content',
' Even if no explicit id is present in the input, infer a suitable id from the description/message semantics',
' IDs must not be duplicated across rules',
' id must use only English words or phrases already present in description/message, converted to kebab-case',
' Do not invent English words not found in the original text',
' If input is entirely in Chinese, extract core semantic keywords and translate to short English (2-4 words)',
'- severity: Severity level (error / warning / info)',
' **Must** output. Infer based on rule semantics:',
' error: causes bugs / security issues / data corruption',
' warning: potential issues / bad practices',
' info: style / readability suggestions',
' Even if no explicit severity is given, infer from the rule\'s impact',
' If severity cannot be determined from input, default to warning',
' Only use error when the rule clearly involves security, data leakage, or crash risk',
'- description: Short rule description',
' **Must** output. If not obvious from input, derive from message content',
'- message: Violation message',
' **Must** output. If not obvious from input, derive from description content',
' description and message may be semantically similar; no need to force different tones, but both must be filled',
'- languages: Applicable language array (optional, e.g., [javascript, typescript])',
'- excludeLanguages: Explicitly excluded language array (optional, e.g., [css, sql])',
'- duplicateOf: Duplicate rule ID (linter e.g., eslint/no-console; custom e.g., custom/my-rule)',
'- duplicateLevel: Duplicate level (exact / overlap / none)',
'- duplicateReason: Duplicate/overlap reason (required for overlap)',
],
languageRulesTitle: '## Language Field Rules (Strict)',
languageRulesLines: [
'For each rule\'s languages field:',
'1. If rule description mentions specific languages (e.g., "Java", "JavaScript", "CSS") → use languages whitelist',
'2. If rule applies to most languages, with few exceptions → use excludeLanguages blacklist',
'3. If applicable language cannot be determined, or rule is general → leave both languages and excludeLanguages empty',
'4. languages and excludeLanguages must not both be non-empty simultaneously',
'5. Use lowercase language names: java, javascript, typescript, css, sql, plsql, jsp',
'6. Never guess. Leaving empty is safer than guessing incorrectly.',
],
exampleTitle: '## Input/Output Examples',
example1Input: 'Do not use console.log, it leaks information in production. Also do not leave unused variables, they look messy.',
example1Output: [
'- id: no-console-log',
' severity: warning',
' description: Forbid using console.log',
' message: Use a logger tool instead of console.log',
' duplicateLevel: none',
'- id: no-unused-vars',
' severity: warning',
' description: Forbid unused variables',
' message: Unused variables should be deleted or commented out',
' duplicateOf: eslint/no-unused-vars',
' duplicateLevel: exact',
].join('\n'),
example2Input: [
'This project is an e-commerce backend, mainly using Java + Spring Boot.',
'Coding rules: Service layer methods must have logging for debugging.',
'Example code:',
' public void createOrder(Order order) { ... }',
'Also, Controller layer return values should use Result wrapper, do not return Map directly.',
].join('\n'),
example2Output: [
'- id: require-service-logging',
' severity: warning',
' description: Service layer methods must have logging',
' message: Service method missing logging, add for debugging',
' languages: [java]',
' duplicateLevel: none',
'- id: require-result-wrapper',
' severity: warning',
' description: Controller return values must use Result wrapper',
' message: Use Result wrapper for return values, do not return Map directly',
' languages: [java]',
' duplicateLevel: none',
].join('\n'),
staticAnalysisTitle: '## Static Analysis Duplicate Detection',
staticAnalysisLines: [
'For each rule, determine whether its detection target and trigger conditions duplicate any linter rule or custom rule above:',
'',
'- **exact**: Detection target and trigger conditions are completely identical (would flag the same line) → output duplicateOf + duplicateLevel: exact',
'- **overlap**: Same detection target but this rule has additional requirements or narrower scope → output duplicateOf + duplicateLevel: overlap + duplicateReason',
'- **none**: Different detection targets → output duplicateLevel: none',
'',
'"Same topic" alone does not count as duplicate. For example:',
'- "Unused variables should be deleted" → exact (duplicate of eslint/no-unused-vars)',
'- "Do not output sensitive info in console.log" → none (different detection target)',
'',
'Output YAML only, no extra explanation.',
],
finalInstruction: 'All descriptions and messages must be written in English.\nOutput YAML only, no markdown code fences, no explanatory text',
dedupHeader: '## Known Rules (for duplicate detection)',
dedupLinterLabel: (name, count) => `### ${name} (${count} rules)`,
dedupCustomLabel: (count) => `### Imported custom rules (${count} rules)`,
dedupFooter: 'Match exactly by rule ID above, not by fuzzy category matching.',
outputLang: 'Output language: en',
},
ja: {
role: (inputDesc) => `あなたはコードレビュールール変換ツールです。ユーザーが提供した${inputDesc}を、コードレビューツール用の構造化YAML形式に変換してください。すべての説明とメッセージは日本語で出力してください。`,
inputToleranceTitle: '## 入力許容について',
inputToleranceLines: [
'ユーザー入力は様々な形式である可能性があります。以下の形式を受け入れ、処理する必要があります:',
'- 自然言語の段落(1つ以上の段落でルールを記述)',
'- 順不同リスト(各ルールが1行または1段落)',
'- テーブル(列名は固定されていません。意味から推測してください)',
'- 混合形式(段落 + リスト + テーブルの組み込み)',
'',
'非標準的な入力形式であっても変換を拒否してはいけません。緩やかな記述からルールの意味を積極的に抽出してください。',
'',
'- 入力ドキュメントの段落またはリスト項目の順序で処理し、原文の順序を保持する',
'- 原文ですでに独立したエントリであるルールを結合または分割しない',
'- 単一の段落に複数のルールが含まれる場合のみ分割し、単一ルールを複数に分割しない',
],
nonRuleFilterTitle: '## 非ルールコンテンツのフィルタリング',
nonRuleFilterLines: [
'ユーザー入力には、プロジェクト紹介、背景説明、コード例、セクションタイトルなどの非ルールコンテンツが混入している可能性があります。以下を行う必要があります:',
'- 非ルールコンテンツを識別してスキップし、実際のコーディングルールのみをYAMLエントリに変換する',
'- コード例やプロジェクト紹介などはルール意味理解のコンテキストとしてのみ使用し、これら自体をルールとして出力しない',
'- ルールと判断できない内容(ルール意図も違反のヒントもない場合)は無視し、無理に変換しない',
],
fieldDefsTitle: '## フィールド定義',
fieldDefsLines: [
'各ルールには以下のフィールドが必要です:',
'- id: ルールの一意識別子(kebab-caseの英語、意味的で簡潔、例:no-console-log、avoid-magic-number',
' **必須** ルール説明内容に基づいて意味的なidを自動生成する',
' 入力に明示的なidがない場合でも、description/messageの意味から適切なidを推測する',
' 複数ルール間でidが重複してはいけない',
' idはdescription/messageにすでに存在する英単語またはフレーズのみを使用し、kebab-caseに変換する',
' 原文にない英単語を独自に作成しない',
' 入力がすべて日本語の場合は、セマンティクスから核心キーワードを抽出し、短い英語(2〜4語)に翻訳する',
'- severity: 重大度(error / warning / info',
' **必須**で出力。ルールの意味に従って推測:',
' error: バグ/セキュリティ問題/データ破損を引き起こす',
' warning: 潜在的な問題/悪い慣行',
' info: スタイル/可読性の提案',
' 入力に明示的な重大度がない場合でも、ルールの影響の重大さから推測する',
' 入力から重大度を判断できない場合は、デフォルトでwarningとする',
' セキュリティ、データ漏洩、クラッシュリスクに明確に関連する場合のみerrorとする',
'- description: ルールの簡単な説明',
' **必須**で出力。入力で不明確な場合、messageの内容から逆算して短い説明を導出',
'- message: 違反時のメッセージ',
' **必須**で出力。入力で不明確な場合、descriptionの内容から違反メッセージを導出',
' descriptionとmessageは意味的に近くても構いません。口調を無理に区別する必要はありませんが、両方とも必須です',
'- languages: 対象言語配列(オプション、例:[javascript, typescript]',
'- excludeLanguages: 明示的に除外する言語配列(オプション、例:[css, sql]',
'- duplicateOf: 重複するルールID(リンター例:eslint/no-console、カスタム例:custom/my-rule',
'- duplicateLevel: 重複レベル(exact / overlap / none',
'- duplicateReason: 重複/重複理由の説明(overlapの場合は必須)',
],
languageRulesTitle: '## 言語フィールドルール(厳守)',
languageRulesLines: [
'各ルールのlanguagesフィールドについて:',
'1. ルール説明に明確な言語キーワードがある場合(例:「Java」「JavaScript」「CSS」)→ languagesにホワイトリストを使用',
'2. ルールがほとんどの言語に適用され、一部のみ適用外の場合 → excludeLanguagesにブラックリストを使用',
'3. 適用言語が判断できない場合、またはルールが汎用の場合 → languagesとexcludeLanguagesの両方を空にする',
'4. languagesとexcludeLanguagesは同時に空であってはいけない',
'5. 言語名は小文字を使用:java, javascript, typescript, css, sql, plsql, jsp',
'6. 推測は厳禁。空のままにする方が誤った推測より安全です。',
],
exampleTitle: '## 入出力例',
example1Input: 'console.logは本番環境で情報漏洩するため使用しないでください。また、未使用の変数は残さないでください。散らかって見えます。',
example1Output: [
'- 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',
].join('\n'),
example2Input: [
'本プロジェクトはECサイト管理システムで、主にJava + Spring Bootを使用しています。',
'コード規約:Service層のメソッドには必ずログ記録が必要です。問題調査のためです。',
'コード例:',
' public void createOrder(Order order) { ... }',
'また、Controller層の戻り値は統一してResultでラップし、Mapを直接返さないでください。',
].join('\n'),
example2Output: [
'- 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',
].join('\n'),
staticAnalysisTitle: '## 静的解析重複検出',
staticAnalysisLines: [
'各ルールについて、その検出対象とトリガー条件が上記のリンタールールまたはカスタムルールと重複するか判断:',
'',
'- **exact**: 検出対象とトリガー条件が完全に一致(同じ問題行を報告する)→ duplicateOf + duplicateLevel: exact を出力',
'- **overlap**: 検出対象は同じだが、このルールに追加要件やより狭い範囲がある → duplicateOf + duplicateLevel: overlap + duplicateReason を出力',
'- **none**: 検出対象が異なる → duplicateLevel: none を出力',
'',
'単に「トピックが類似している」だけでは重複とみなされません。例:',
'- 「未使用変数は削除すべき」→ exacteslint/no-unused-varsと重複)',
'- 「console.logで機密情報を出力しない」→ none(検出対象が異なる)',
'',
'YAMLのみを出力し、追加説明は不要です。',
],
finalInstruction: 'すべてのdescriptionとmessageは日本語で出力してください。\nYAML のみ出力、マークダウンコードブロックなし、説明テキストなし',
dedupHeader: '## 既知ルール一覧(重複検出用)',
dedupLinterLabel: (name, count) => `### ${name}${count} 件)`,
dedupCustomLabel: (count) => `### インポート済みカスタムルール(${count} 件)`,
dedupFooter: '上記ルールIDで正確にマッチングしてください。曖昧なカテゴリマッチングは避けてください。',
outputLang: '出力言語:ja',
},
};
function getLang(): Lang {
const lang = getLanguage();
if (lang === 'en' || lang === 'ja') { return lang; }
return 'zh-CN';
}
function buildDedupPromptSection(existingCustomRules?: CustomRule[], lang?: Lang): string {
const l = lang ?? getLang();
const s = p[l];
const lines: string[] = [s.dedupHeader];
for (const [linter, rules] of Object.entries(staticRules.rules)) {
lines.push(s.dedupLinterLabel(linter, rules.length));
for (const rule of rules) {
lines.push(`- ${rule.id}: ${rule.description}`);
}
lines.push('');
}
if (existingCustomRules && existingCustomRules.length > 0) {
lines.push(s.dedupCustomLabel(existingCustomRules.length));
for (const rule of existingCustomRules) {
lines.push(`- custom/${rule.id}: ${rule.description}`);
}
lines.push('');
}
lines.push(s.dedupFooter);
return lines.join('\n');
}
export function buildSystemPrompt(inputType: PromptInputType, existingRules?: CustomRule[]): string {
const lang = getLang();
const s = p[lang];
const inputDesc = inputType === 'spreadsheet'
? (lang === 'zh-CN' ? '表格规则数据' : lang === 'ja' ? '表形式のルールデータ' : 'spreadsheet rule data')
: (lang === 'zh-CN' ? '自然语言规则描述' : lang === 'ja' ? '自然言語のルール記述' : 'natural language rule description');
const parts: string[] = [
s.role(inputDesc),
'',
s.inputToleranceTitle,
...s.inputToleranceLines,
'',
s.nonRuleFilterTitle,
...s.nonRuleFilterLines,
'',
s.fieldDefsTitle,
...s.fieldDefsLines,
'',
s.languageRulesTitle,
...s.languageRulesLines,
'',
s.exampleTitle,
'',
`${lang === 'zh-CN' ? '示例 1 — 松散段落输入:' : lang === 'ja' ? '例1 — 緩やかな段落入力:' : 'Example 1 — Loose paragraph input:'}`,
`${lang === 'zh-CN' ? '输入' : lang === 'ja' ? '入力' : 'Input'}${s.example1Input}`,
`${lang === 'zh-CN' ? '输出' : lang === 'ja' ? '出力' : 'Output'}`,
s.example1Output,
'',
`${lang === 'zh-CN' ? '示例 2 — 含非规则内容的混合输入:' : lang === 'ja' ? '例2 — 非ルールコンテンツを含む混合入力:' : 'Example 2 — Mixed input with non-rule content:'}`,
`${lang === 'zh-CN' ? '输入' : lang === 'ja' ? '入力' : 'Input'}${s.example2Input}`,
`${lang === 'zh-CN' ? '输出' : lang === 'ja' ? '出力' : 'Output'}`,
s.example2Output,
'',
buildDedupPromptSection(existingRules, lang),
'',
s.staticAnalysisTitle,
...s.staticAnalysisLines,
'',
s.finalInstruction,
s.outputLang,
];
return parts.join('\n');
}