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 = { '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: 重复的规则 ID(linter 如 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 を出力', '', '単に「トピックが類似している」だけでは重複とみなされません。例:', '- 「未使用変数は削除すべき」→ exact(eslint/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'); }