From 35b09b290fa5cb8cace619ae2ca5188a981ac3db Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 05:06:46 +0000 Subject: [PATCH 1/4] fix(CODEWIKI-005-2): 7 review findings across 4 files --- .../dependency_analyzer/analyzers/python.py | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/codewiki/src/be/dependency_analyzer/analyzers/python.py b/codewiki/src/be/dependency_analyzer/analyzers/python.py index deda7935..3cf43b33 100644 --- a/codewiki/src/be/dependency_analyzer/analyzers/python.py +++ b/codewiki/src/be/dependency_analyzer/analyzers/python.py @@ -1,3 +1,12 @@ +"""Python AST-based dependency analyzer. + +This module implements an AST-based analyzer for Python source files, extracting +classes, functions, and their call relationships for use in the dependency +analysis pipeline. Component IDs are generated as module::ClassName style +fully-qualified names to remain consistent with the dependency graph and +clustering system's expected FQDN format. +""" + import ast import logging import warnings @@ -49,16 +58,17 @@ def _get_module_path(self) -> str: path = path[:-len(ext)] break return path.replace('/', '.').replace('\\', '.') - except: + except Exception as e: + logger.debug(f"Falling back to raw file_path for module path: {e}") return str(self.file_path).replace('/', '.').replace('\\', '.') def _get_component_id(self, name: str) -> str: - """Generate dot-separated component ID.""" + """Generate component ID using '::' separator for module::ClassName FQDN format.""" module_path = self._get_module_path() if self.current_class_name: - return f"{module_path}.{self.current_class_name}.{name}" + return f"{module_path}::{self.current_class_name}.{name}" else: - return f"{module_path}.{name}" + return f"{module_path}::{name}" def generic_visit(self, node): """Override generic_visit to continue AST traversal.""" @@ -70,7 +80,7 @@ def visit_ClassDef(self, node: ast.ClassDef): base_classes = [self._extract_base_class_name(base) for base in node.bases] base_classes = [name for name in base_classes if name is not None] - component_id = f"{self._get_module_path()}.{node.name}" + component_id = f"{self._get_module_path()}::{node.name}" relative_path = self._get_relative_path() class_node = Node( @@ -98,7 +108,7 @@ def visit_ClassDef(self, node: ast.ClassDef): if base_name in self.top_level_nodes: self.call_relationships.append(CallRelationship( caller=component_id, - callee=f"{self._get_module_path()}.{base_name}", + callee=f"{self._get_module_path()}::{base_name}", call_line=node.lineno, is_resolved=True )) @@ -126,7 +136,7 @@ def _process_function_node(self, node: ast.FunctionDef | ast.AsyncFunctionDef): """Process function definition - only add to nodes if it's top-level.""" if not self.current_class_name: - component_id = f"{self._get_module_path()}.{node.name}" + component_id = f"{self._get_module_path()}::{node.name}" relative_path = self._get_relative_path() func_node = Node( @@ -175,12 +185,12 @@ def visit_Call(self, node: ast.Call): call_name = self._get_call_name(node.func) if call_name: if self.current_class_name: - caller_id = f"{self._get_module_path()}.{self.current_class_name}" + caller_id = f"{self._get_module_path()}::{self.current_class_name}" else: - caller_id = f"{self._get_module_path()}.{self.current_function_name}" + caller_id = f"{self._get_module_path()}::{self.current_function_name}" if call_name in self.top_level_nodes: - callee_id = f"{self._get_module_path()}.{call_name}" + callee_id = f"{self._get_module_path()}::{call_name}" else: callee_id = call_name @@ -264,3 +274,4 @@ def analyze_python_file( analyzer.analyze() return analyzer.nodes, analyzer.call_relationships + From 13a760faad14c1bec9b6a5eafcc52b39e4ec4d5e Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 05:06:47 +0000 Subject: [PATCH 2/4] fix(CODEWIKI-005-2): 7 review findings across 4 files --- FQDN_NORMALIZATION_FIX.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/FQDN_NORMALIZATION_FIX.py b/FQDN_NORMALIZATION_FIX.py index 196d6e2e..c1402de7 100644 --- a/FQDN_NORMALIZATION_FIX.py +++ b/FQDN_NORMALIZATION_FIX.py @@ -8,6 +8,11 @@ 4. Partial path matching for complex Java packages Replace the normalization loop in cluster_modules.py:212-233 with this code. + +NOTE: This is a scratch/reference module, not imported anywhere in the +codebase. It documents a proposed patch for cluster_modules.py and should be +merged into that file (or moved to docs/ or a PR description) rather than +kept as standalone code at the repository root. """ from typing import Dict, List @@ -130,6 +135,7 @@ def normalize_component_ids_enhanced( # This handles cases where LLM includes partial path # Example: "deps.openframe-oss-lib.src.main.java.Class" # should match "openframe-oss-lib.different.path.java.Class" + suffix_matches: List[str] = [] if '.' in comp_id: # Try matching last 2-4 segments segments = comp_id.split('.') From e4f016831444ba10ef4e1d118400efa6e359021d Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 05:06:48 +0000 Subject: [PATCH 3/4] fix(CODEWIKI-005-2): 7 review findings across 4 files --- verify_fqdn_implementation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/verify_fqdn_implementation.py b/verify_fqdn_implementation.py index 262c1062..80ec49b6 100755 --- a/verify_fqdn_implementation.py +++ b/verify_fqdn_implementation.py @@ -29,7 +29,7 @@ def check_ast_parser(): checks = { 'FQDN comment in multi-path': '# FQDN metadata fields' in content, - 'fqdn = f"{namespace}.{original_id}"': 'fqdn = f"{namespace}.{original_id}"' in content, + 'fqdn = f"{namespace}::{original_id}"': 'fqdn = f"{namespace}::{original_id}"' in content, 'components[fqdn] = node': 'components[fqdn] = node' in content, 'id=fqdn': 'id=fqdn' in content, 'short_id=original_id': 'short_id=original_id' in content, From 69f8a5f839045a61dd46c743600888730b339fe2 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 05:06:49 +0000 Subject: [PATCH 4/4] fix(CODEWIKI-005-2): 7 review findings across 4 files --- codewiki/src/be/cluster_modules.py | 57 +++++++++++++++++++----------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/codewiki/src/be/cluster_modules.py b/codewiki/src/be/cluster_modules.py index e7b1fa78..31c2159c 100644 --- a/codewiki/src/be/cluster_modules.py +++ b/codewiki/src/be/cluster_modules.py @@ -20,8 +20,18 @@ def extract_module_hint(fqdn: str) -> str: "openframe-oss-lib.openframe-api-service-core..." → "api-service" "main-repo.src/services/auth.py::AuthService" → "auth" """ + if '::' not in fqdn: + logger.warning( + f"FQDN '{fqdn}' does not conform to the required 'module.path::ClassName' " + f"format (missing '::' separator); rejecting dot-only FQDN for module hint extraction" + ) + return "unknown" + + # Only operate on the module/path portion before the '::' separator + module_part = fqdn.split('::')[0] + # Strategy 1: Look for service-like patterns (openframe-api-service → api-service) - parts = fqdn.split('.') + parts = module_part.split('.') for part in parts: if '-service' in part or '-api' in part: # Extract meaningful part (e.g., "openframe-api-service" → "api-service") @@ -30,13 +40,10 @@ def extract_module_hint(fqdn: str) -> str: return '-'.join(segments[-2:]) # Strategy 2: Extract from file path (src/services/auth.py → auth) - if '::' in fqdn: - file_path = fqdn.split('::')[0] - # Get last meaningful directory or file name - path_parts = file_path.replace('\\', '/').split('/') - for part in reversed(path_parts): - if part and part not in ['src', 'main', 'java', 'com']: - return part.replace('.py', '').replace('.java', '').replace('.ts', '') + path_parts = module_part.replace('\\', '/').split('/') + for part in reversed(path_parts): + if part and part not in ['src', 'main', 'java', 'com']: + return part.replace('.py', '').replace('.java', '').replace('.ts', '') # Fallback: Use first segment return parts[0] if parts else "unknown" @@ -50,26 +57,34 @@ def extract_package_hint(fqdn: str) -> str: "...src.main.java.com.openframe.api.controller.Class" → "controller" "main-repo.src/models/device.py::DeviceModel" → "models" """ + if '::' not in fqdn: + logger.warning( + f"FQDN '{fqdn}' does not conform to the required 'module.path::ClassName' " + f"format (missing '::' separator); rejecting dot-only FQDN for package hint extraction" + ) + return "core" + + # Only operate on the module/path portion before the '::' separator + module_part = fqdn.split('::')[0] + # Strategy 1: Look for common package patterns common_packages = ['controller', 'service', 'repository', 'model', 'dto', 'config', 'util', 'helper', 'handler', 'processor'] - fqdn_lower = fqdn.lower() + module_part_lower = module_part.lower() for pkg in common_packages: - if pkg in fqdn_lower: + if pkg in module_part_lower: return pkg # Strategy 2: Extract from file path structure - if '::' in fqdn: - file_path = fqdn.split('::')[0] - path_parts = file_path.replace('\\', '/').split('/') - # Look for meaningful directory names - for part in reversed(path_parts[:-1]): # Skip filename - if part and part not in ['src', 'main', 'java', 'com', 'org']: - return part - - # Fallback: Extract from path - parts = fqdn.split('.') + path_parts = module_part.replace('\\', '/').split('/') + # Look for meaningful directory names + for part in reversed(path_parts[:-1]): # Skip filename + if part and part not in ['src', 'main', 'java', 'com', 'org']: + return part + + # Fallback: Extract from dotted module path + parts = module_part.split('.') if len(parts) >= 2: return parts[-2] @@ -517,4 +532,4 @@ def _find_best_path_match(llm_id: str, candidates: List[str]) -> Optional[str]: DeprecationWarning, stacklevel=2 ) - return None \ No newline at end of file + return None