import re import ast import difflib from typing import Dict, Any, List, Optional, Set, Tuple from validators.base import BaseValidator, Validator class DesignImplementationValidator(BaseValidator, Validator): """設計書と実装コードの整合性を検証するクラス""" def __init__(self, client=None): """設計・実装検証クラスの初期化""" super().__init__(client) self.validation_results = {} async def validate(self, content, context=None): """設計書と実装コードの整合性を検証する(インターフェース実装)""" # contentが設計書、contextが実装コードの場合 if context and isinstance(content, str) and isinstance(context, str): design_doc = content implementation_code = context else: # 単一コンテンツの場合、設計書と実装コードを分離 design_code_parts = self._separate_design_and_code(content) if design_code_parts: design_doc, implementation_code = design_code_parts else: # 分離できない場合は、元のコンテンツをそのまま返す return content # 検証実行 validation_result = self.validate_design_implementation(design_doc, implementation_code) self.validation_results = validation_result # 検証結果に基づいて注釈を追加 if not validation_result.get("is_compliant", True): implementation_code = self._add_compliance_notes(implementation_code, validation_result) return implementation_code def get_result_summary(self): """検証結果の要約を返す(インターフェース実装)""" if not self.validation_results: return "設計書と実装コードの整合性はまだ検証されていません。" is_compliant = self.validation_results.get("is_compliant", True) compliance_score = self.validation_results.get("compliance_score", 0.0) if is_compliant: return f"設計書と実装コードは整合しています(整合性スコア: {compliance_score}/10.0)" missing_components = self.validation_results.get("missing_components", []) missing_methods = self.validation_results.get("missing_methods", []) missing_requirements = self.validation_results.get("missing_requirements", []) summary = f"設計書と実装コードに不整合があります(整合性スコア: {compliance_score}/10.0)\n\n" if missing_components: summary += "不足しているコンポーネント:\n" summary += "\n".join([f"- {component}" for component in missing_components[:3]]) if len(missing_components) > 3: summary += f"\n...他{len(missing_components) - 3}個" summary += "\n\n" if missing_methods: summary += "不足しているメソッド:\n" summary += "\n".join([f"- {method}" for method in missing_methods[:3]]) if len(missing_methods) > 3: summary += f"\n...他{len(missing_methods) - 3}個" summary += "\n\n" if missing_requirements: summary += "未実装の要件:\n" summary += "\n".join([f"- {req}" for req in missing_requirements[:3]]) if len(missing_requirements) > 3: summary += f"\n...他{len(missing_requirements) - 3}個" return summary def _separate_design_and_code(self, content: str) -> Optional[Tuple[str, str]]: """コンテンツから設計書部分と実装コード部分を分離する""" # 設計書セクションのマーカー design_markers = [ "# 設計書", "## 設計書", "# 設計", "## 設計", "# アーキテクチャ設計", "# 詳細設計", "# Design Document", "## Design Document", "# Architecture", "## Architecture" ] # 実装コードセクションのマーカー code_markers = [ "# 実装", "## 実装", "# 実装コード", "## 実装コード", "# Implementation", "## Implementation", "# Code", "## Code", "```python", "```java", "```javascript", "```cpp" ] # 設計書と実装コードの開始位置を検出 design_start = -1 code_start = -1 for marker in design_markers: if marker in content: design_start = content.find(marker) break for marker in code_markers: if marker in content: marker_pos = content.find(marker) if marker_pos > design_start: # 設計書の後に出現するコードマーカーのみ考慮 code_start = marker_pos break # 設計書と実装コードの両方が見つかった場合 if design_start >= 0 and code_start > design_start: design_doc = content[design_start:code_start].strip() implementation_code = content[code_start:].strip() return design_doc, implementation_code # 分離できない場合 return None def _add_compliance_notes(self, implementation_code: str, validation_result: Dict[str, Any]) -> str: """検証結果に基づいて実装コードに注釈を追加する""" notes = ["# 設計書との整合性の問題点:"] # 各種不足要素を注釈として追加 missing_components = validation_result.get("missing_components", []) if missing_components: notes.append("# 不足しているコンポーネント:") for component in missing_components: notes.append(f"# - {component}") missing_methods = validation_result.get("missing_methods", []) if missing_methods: notes.append("# 不足しているメソッド:") for method in missing_methods: notes.append(f"# - {method}") missing_requirements = validation_result.get("missing_requirements", []) if missing_requirements: notes.append("# 未実装の要件:") for req in missing_requirements: notes.append(f"# - {req}") # 提案があれば追加 suggestion = validation_result.get("suggestion") if suggestion: notes.append("# 提案:") for line in suggestion.split("\n"): notes.append(f"# {line}") # 注釈を実装コードの先頭に追加 notes_str = "\n".join(notes) return f"{notes_str}\n\n{implementation_code}" def validate_design_implementation(self, design_doc: str, implementation_code: str) -> Dict[str, Any]: """設計書と実装コードの整合性を検証する""" result = { "is_compliant": True, "issues": [], "missing_components": [], "missing_methods": [], "missing_requirements": [], "extra_components": [], "compliance_score": 0.0, "suggestion": None } # 設計書から要素を抽出 design_components = self._extract_design_components(design_doc) # 実装コードから要素を抽出 implementation_components = self._extract_implementation_components(implementation_code) # 設計書と実装の整合性を検証 self._validate_consistency(design_components, implementation_components, result) # 検証結果に基づいてコンプライアンススコアを計算 self._calculate_compliance_score(result) # 改善のための提案を生成 self._generate_suggestions(result) return result def _extract_design_components(self, design_doc: str) -> Dict[str, Any]: """設計書からコンポーネント、クラス、メソッド、要件などを抽出する""" components = { "classes": [], "interfaces": [], "methods": {}, "attributes": {}, "requirements": [], "dependencies": [], "data_structures": [] } # 1. クラス定義の抽出 class_pattern = r'(class\s+(\w+)(?:\s*\(\s*\w+\s*\))?\s*:)' for match in re.finditer(class_pattern, design_doc): class_name = match.group(2) components["classes"].append(class_name) components["methods"][class_name] = [] components["attributes"][class_name] = [] # クラス定義の範囲を特定 class_start = match.end() next_class_match = re.search(class_pattern, design_doc[class_start:]) class_end = class_start + next_class_match.start() if next_class_match else len(design_doc) class_body = design_doc[class_start:class_end] # メソッド定義の抽出 method_pattern = r'def\s+(\w+)\s*\((.*?)\)' for method_match in re.finditer(method_pattern, class_body): method_name = method_match.group(1) components["methods"][class_name].append(method_name) # 属性の抽出(単純なパターンマッチング) attribute_pattern = r'self\.(\w+)\s*=' for attr_match in re.finditer(attribute_pattern, class_body): attr_name = attr_match.group(1) if attr_name not in components["attributes"][class_name]: components["attributes"][class_name].append(attr_name) # 2. インターフェース定義の抽出(ABC, Protocolなど) interface_pattern = r'(class\s+(\w+)(?:\s*\(\s*(?:ABC|Protocol)\s*\))?\s*:)' for match in re.finditer(interface_pattern, design_doc): interface_name = match.group(2) if interface_name not in components["classes"]: # クラスとして既に登録されていない場合 components["interfaces"].append(interface_name) components["methods"][interface_name] = [] # インターフェース定義の範囲を特定 interface_start = match.end() next_interface_match = re.search(interface_pattern, design_doc[interface_start:]) interface_end = interface_start + next_interface_match.start() if next_interface_match else len(design_doc) interface_body = design_doc[interface_start:interface_end] # メソッド定義の抽出 method_pattern = r'def\s+(\w+)\s*\((.*?)\)' for method_match in re.finditer(method_pattern, interface_body): method_name = method_match.group(1) components["methods"][interface_name].append(method_name) # 3. 要件の抽出 requirement_sections = [ "要件", "機能要件", "非機能要件", "制約", "前提条件", "Requirements", "Functional Requirements", "Non-functional Requirements" ] for section in requirement_sections: section_match = re.search(rf'{section}[::]\s*(.*?)(?=\n\n|\n#|\Z)', design_doc, re.DOTALL) if section_match: section_text = section_match.group(1).strip() # 箇条書きの抽出 bullet_patterns = [ r'[-*•]\s*(.*?)(?=\n[-*•]|\n\n|\Z)', # ハイフン、アスタリスク、中黒 r'\d+\.\s*(.*?)(?=\n\d+\.|\n\n|\Z)' # 番号付きリスト ] for pattern in bullet_patterns: for req_match in re.finditer(pattern, section_text, re.DOTALL): requirement = req_match.group(1).strip() if requirement and requirement not in components["requirements"]: components["requirements"].append(requirement) # 4. データ構造の抽出 data_structure_patterns = [ r'(class|struct)\s+(\w+)', # クラスまたは構造体 r'(enum)\s+(\w+)', # 列挙型 r'(type|typedef)\s+(\w+)' # 型定義 ] for pattern in data_structure_patterns: for match in re.finditer(pattern, design_doc): structure_type = match.group(1) structure_name = match.group(2) if structure_name not in components["classes"] and structure_name not in components["interfaces"]: components["data_structures"].append(structure_name) # 5. 依存関係の抽出 dependency_patterns = [ r'依存[::]\s*(.*?)(?=\n\n|\Z)', r'Dependencies[::]\s*(.*?)(?=\n\n|\Z)', r'利用[::]\s*(.*?)(?=\n\n|\Z)', r'Uses[::]\s*(.*?)(?=\n\n|\Z)', r'import\s+(\w+)', r'from\s+(\w+)\s+import' ] for pattern in dependency_patterns: for match in re.finditer(pattern, design_doc): dependency = match.group(1).strip() if dependency and dependency not in components["dependencies"]: components["dependencies"].append(dependency) return components def _extract_implementation_components(self, implementation_code: str) -> Dict[str, Any]: """実装コードからコンポーネント、クラス、メソッド、属性などを抽出する""" components = { "classes": [], "interfaces": [], "methods": {}, "attributes": {}, "imports": [], "functions": [] } try: # ASTを使ってPythonコードを解析 tree = ast.parse(implementation_code) # インポート文の抽出 for node in ast.walk(tree): if isinstance(node, ast.Import): for name in node.names: components["imports"].append(name.name) elif isinstance(node, ast.ImportFrom): if node.module: components["imports"].append(node.module) # クラスの抽出 for node in ast.walk(tree): if isinstance(node, ast.ClassDef): class_name = node.name # インターフェース(抽象クラス)かどうかを判定 is_interface = False for base in node.bases: if isinstance(base, ast.Name) and base.id in ["ABC", "Protocol"]: is_interface = True break if is_interface: components["interfaces"].append(class_name) else: components["classes"].append(class_name) components["methods"][class_name] = [] components["attributes"][class_name] = [] # メソッドと属性の抽出 for item in node.body: if isinstance(item, ast.FunctionDef): components["methods"][class_name].append(item.name) elif isinstance(item, ast.Assign): for target in item.targets: if isinstance(target, ast.Name): components["attributes"][class_name].append(target.id) # トップレベル関数の抽出 for node in ast.walk(tree): if isinstance(node, ast.FunctionDef) and node.name not in [m for methods in components["methods"].values() for m in methods]: if not any(isinstance(parent, ast.ClassDef) for parent in ast.iter_child_nodes(tree)): components["functions"].append(node.name) except SyntaxError as e: print(f"[Warning] Syntax error during code analysis: {e}") # 構文エラーの場合は正規表現ベースのフォールバック解析を使用 self._fallback_extract_implementation_components(implementation_code, components) return components def _fallback_extract_implementation_components(self, code: str, components: Dict[str, Any]) -> None: """構文エラーがある場合のフォールバック抽出メソッド(正規表現ベース)""" # クラス定義の抽出 class_pattern = r'class\s+(\w+)(?:\s*\(.*?\))?\s*:' for match in re.finditer(class_pattern, code): class_name = match.group(1) if class_name not in components["classes"]: components["classes"].append(class_name) components["methods"][class_name] = [] components["attributes"][class_name] = [] # クラス本体の抽出 class_start = match.end() # 簡易的なブロック終了の検出(インデントベース) class_body = "" in_class = True indent_level = None for line in code[class_start:].split('\n'): if not line.strip(): class_body += line + '\n' continue current_indent = len(line) - len(line.lstrip()) if indent_level is None: if current_indent > 0: # クラス本体の最初の行 indent_level = current_indent class_body += line + '\n' continue if current_indent >= indent_level: class_body += line + '\n' else: break # クラスブロックの終了 # メソッド定義の抽出 method_pattern = r'def\s+(\w+)\s*\(' for method_match in re.finditer(method_pattern, class_body): method_name = method_match.group(1) if method_name not in components["methods"][class_name]: components["methods"][class_name].append(method_name) # 属性の抽出 attribute_pattern = r'self\.(\w+)\s*=' for attr_match in re.finditer(attribute_pattern, class_body): attr_name = attr_match.group(1) if attr_name not in components["attributes"][class_name]: components["attributes"][class_name].append(attr_name) # インポート文の抽出 import_pattern = r'import\s+(\w+)' for match in re.finditer(import_pattern, code): import_name = match.group(1) if import_name not in components["imports"]: components["imports"].append(import_name) from_import_pattern = r'from\s+(\w+)\s+import' for match in re.finditer(from_import_pattern, code): import_name = match.group(1) if import_name not in components["imports"]: components["imports"].append(import_name) # トップレベル関数の抽出 function_pattern = r'^def\s+(\w+)\s*\(' for match in re.finditer(function_pattern, code, re.MULTILINE): func_name = match.group(1) if func_name not in components["functions"]: components["functions"].append(func_name) def _validate_consistency(self, design: Dict[str, Any], implementation: Dict[str, Any], result: Dict[str, Any]) -> None: """設計と実装の整合性を検証する""" # 1. クラスの整合性チェック for class_name in design["classes"]: if class_name not in implementation["classes"] and class_name not in implementation["interfaces"]: result["is_compliant"] = False result["missing_components"].append(f"Class: {class_name}") result["issues"].append(f"設計書で定義されているクラス '{class_name}' が実装に存在しません。") # 2. インターフェースの整合性チェック for interface_name in design["interfaces"]: if interface_name not in implementation["interfaces"] and interface_name not in implementation["classes"]: result["is_compliant"] = False result["missing_components"].append(f"Interface: {interface_name}") result["issues"].append(f"設計書で定義されているインターフェース '{interface_name}' が実装に存在しません。") # 3. メソッドの整合性チェック for class_name, methods in design["methods"].items(): if class_name in implementation["methods"]: for method_name in methods: if method_name not in implementation["methods"][class_name]: result["is_compliant"] = False result["missing_methods"].append(f"{class_name}.{method_name}") result["issues"].append(f"設計書で定義されているメソッド '{class_name}.{method_name}' が実装に存在しません。") # 4. 属性の整合性チェック(厳密でない - 属性は実装時に追加されることもある) for class_name, attrs in design["attributes"].items(): if class_name in implementation["attributes"]: for attr_name in attrs: if attr_name not in implementation["attributes"][class_name]: # 属性の不一致は警告レベル result["issues"].append(f"設計書で定義されている属性 '{class_name}.{attr_name}' が実装に存在しません(警告)。") # 5. 要件の実装検証(キーワードベースの簡易検証) code_text = implementation_code if hasattr(self, 'implementation_code') else "" for req in design["requirements"]: # 要件からキーワードを抽出 keywords = self._extract_keywords(req) # キーワードが実装に含まれているかチェック if keywords and not all(keyword.lower() in code_text.lower() for keyword in keywords if len(keyword) > 3): result["is_compliant"] = False result["missing_requirements"].append(req) result["issues"].append(f"要件 '{req}' が実装に反映されていない可能性があります。") # 6. 実装に存在する余分なコンポーネントを確認 for class_name in implementation["classes"]: if class_name not in design["classes"] and class_name not in design["interfaces"]: result["extra_components"].append(f"Class: {class_name}") # 余分なコンポーネントは警告レベル result["issues"].append(f"実装に存在するクラス '{class_name}' が設計書に定義されていません(警告)。") def _extract_keywords(self, text: str) -> List[str]: """テキストから重要なキーワードを抽出する""" # ストップワードを定義 stop_words = { "a", "an", "the", "this", "that", "these", "those", "is", "are", "was", "were", "be", "been", "being", "have", "has", "had", "do", "does", "did", "will", "would", "shall", "should", "can", "could", "may", "might", "must", "and", "or", "but", "if", "then", "else", "when", "where", "why", "how", "all", "any", "both", "each", "few", "more", "most", "other", "some", "such", "no", "nor", "not", "only", "own", "same", "so", "than", "too", "very", "を", "に", "は", "が", "で", "と", "から", "まで", "より", "して", "する", "ます", "です" } # 単語分割(英語と日本語の両方に対応) words = [] # 英単語の抽出 english_words = re.findall(r'\b[a-zA-Z]+\b', text.lower()) words.extend(english_words) # 日本語単語の簡易抽出(文字単位の分割) japanese_text = re.sub(r'[a-zA-Z0-9\s]', '', text) for char in japanese_text: if char not in "、。!?「」『』()[]【】…:;": words.append(char) # ストップワードと短すぎる単語を除外 keywords = [w for w in words if w not in stop_words and len(w) > 1] # 重複を削除 unique_keywords = list(set(keywords)) return unique_keywords def _calculate_compliance_score(self, result: Dict[str, Any]) -> None: """コンプライアンススコアを計算する(0.0〜10.0の範囲)""" # 初期スコア score = 10.0 # 不足しているコンポーネントごとに減点 score -= len(result["missing_components"]) * 2.0 # 不足しているメソッドごとに減点 score -= len(result["missing_methods"]) * 1.0 # 不足している要件ごとに減点 score -= len(result["missing_requirements"]) * 1.5 # 余分なコンポーネントごとに軽く減点 score -= len(result["extra_components"]) * 0.5 # スコアの範囲を調整 result["compliance_score"] = max(0.0, min(10.0, score)) # コンプライアンス判定(スコアが7.0以上で合格) result["is_compliant"] = result["compliance_score"] >= 7.0 def _generate_suggestions(self, result: Dict[str, Any]) -> None: """検証結果に基づいて改善提案を生成する""" if result["is_compliant"]: if result["compliance_score"] == 10.0: result["suggestion"] = "設計書と実装は完全に一致しています。素晴らしい実装です。" else: result["suggestion"] = "設計書と実装は概ね一致していますが、いくつかの軽微な問題があります。詳細は issues リストを確認してください。" else: # 不足コンポーネントの提案 if result["missing_components"]: result["suggestion"] = f"設計書で定義されている以下のコンポーネントを実装してください: {', '.join(result['missing_components'][:3])}" if len(result["missing_components"]) > 3: result["suggestion"] += f" ... 他 {len(result['missing_components']) - 3} 件" # 不足メソッドの提案 elif result["missing_methods"]: result["suggestion"] = f"設計書で定義されている以下のメソッドを実装してください: {', '.join(result['missing_methods'][:3])}" if len(result["missing_methods"]) > 3: result["suggestion"] += f" ... 他 {len(result['missing_methods']) - 3} 件" # 不足要件の提案 elif result["missing_requirements"]: result["suggestion"] = "以下の要件が実装に反映されていない可能性があります。要件を満たす機能を実装してください。" for i, req in enumerate(result["missing_requirements"][:3]): result["suggestion"] += f"\n{i+1}. {req}" if len(result["missing_requirements"]) > 3: result["suggestion"] += f"\n... 他 {len(result['missing_requirements']) - 3} 件" # 一般的な提案 else: result["suggestion"] = "設計書と実装の間にいくつかの不一致があります。issues リストを確認して修正してください。"