Skip to content
Merged
2 changes: 1 addition & 1 deletion codewiki/src/be/dependency_analyzer/analyzers/c.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def _get_relative_path(self) -> str:

def _get_component_id(self, name: str) -> str:
module_path = self._get_module_path()
return f"{module_path}.{name}" if module_path else name
return f"{module_path}::{name}" if module_path else name

def _analyze(self):
language_capsule = tree_sitter_c.language()
Expand Down
4 changes: 2 additions & 2 deletions codewiki/src/be/dependency_analyzer/analyzers/cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ def _get_relative_path(self) -> str:
def _get_component_id(self, name: str, parent_class: str = None) -> str:
module_path = self._get_module_path()
if parent_class:
return f"{module_path}.{parent_class}.{name}" if module_path else f"{parent_class}.{name}"
return f"{module_path}.{name}" if module_path else name
return f"{module_path}::{parent_class}.{name}" if module_path else f"{parent_class}.{name}"
return f"{module_path}::{name}" if module_path else name

def _analyze(self):
language_capsule = tree_sitter_cpp.language()
Comment on lines 55 to 62

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 πŸ”΄ cpp.py component IDs use '.' rather than the mandated '::' separator

In _get_component_id (line ~42), changed the module-path/name joins from . to :: so IDs are formatted as module.path::Name (and module.path::ParentClass.name for methods, preserving the parent/child dot for the method-within-class segment as before). This satisfies the module.path::ComponentName contract at the module/component boundary; the parent_class-without-module_path branch (f"{parent_class}.{name}") was left as a dot join since there is no module path to separate from the component name in that case β€” a reviewer should confirm whether that fallback also needs a :: per the exact spec wording.

πŸ€– Prompt for AI agents
In codewiki/src/be/dependency_analyzer/analyzers/cpp.py around line 42, review and complete this code-review fix: cpp.py component IDs use '.' rather than the mandated '::' separator.
What the draft fix changed: In `_get_component_id` (line ~42), changed the module-path/name joins from `.` to `::` so IDs are formatted as `module.path::Name` (and `module.path::ParentClass.name` for methods, preserving the parent/child dot for the method-within-class segment as before). This satisfies the `module.path::ComponentName` contract at the module/component boundary; the parent_class-without-module_path branch (`f"{parent_class}.{name}"`) was left as a dot join since there is no module path to separate from the component name in that case β€” a reviewer should confirm whether that fallback also needs a `::` per the exact spec wording.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟑 65 medium β€” react πŸ‘/πŸ‘Ž to teach the reviewer

Expand Down
3 changes: 2 additions & 1 deletion codewiki/src/be/dependency_analyzer/analyzers/csharp.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def _get_relative_path(self) -> str:

def _get_component_id(self, name: str) -> str:
module_path = self._get_module_path()
return f"{module_path}.{name}" if module_path else name
return f"{module_path}::{name}" if module_path else name

def _analyze(self):
language_capsule = tree_sitter_c_sharp.language()
Comment on lines 53 to 59

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 πŸ”΄ csharp.py component IDs use '.' rather than the mandated '::' separator

Changed _get_component_id in TreeSitterCSharpAnalyzer (line ~39) to join module_path and name with :: instead of ., aligning with the CODEWIKI-005-2 FQDN convention. This is a mechanical fix matching the finding's evidence, but confidence is not higher because downstream consumers/tests that may expect dot-separated IDs (e.g. cross-file resolution logic or snapshot tests elsewhere in the codebase) were not visible/verifiable from this single file, so consistency across the broader system cannot be fully confirmed here.

πŸ€– Prompt for AI agents
In codewiki/src/be/dependency_analyzer/analyzers/csharp.py around line 39, review and complete this code-review fix: csharp.py component IDs use '.' rather than the mandated '::' separator.
What the draft fix changed: Changed `_get_component_id` in `TreeSitterCSharpAnalyzer` (line ~39) to join `module_path` and `name` with `::` instead of `.`, aligning with the CODEWIKI-005-2 FQDN convention. This is a mechanical fix matching the finding's evidence, but confidence is not higher because downstream consumers/tests that may expect dot-separated IDs (e.g. cross-file resolution logic or snapshot tests elsewhere in the codebase) were not visible/verifiable from this single file, so consistency across the broader system cannot be fully confirmed here.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟑 65 medium β€” react πŸ‘/πŸ‘Ž to teach the reviewer

Expand Down Expand Up @@ -303,3 +303,4 @@ def analyze_csharp_file(file_path: str, content: str, repo_path: str = None) ->
analyzer = TreeSitterCSharpAnalyzer(file_path, content, repo_path)
return analyzer.nodes, analyzer.call_relationships


16 changes: 13 additions & 3 deletions codewiki/src/be/dependency_analyzer/analyzers/java.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
"""Java analyzer for the dependency analysis pipeline.

This module uses tree-sitter to parse Java source files and extract
structural components (classes, interfaces, enums, records, annotations,
methods) as well as call/relationship information (inheritance, interface
implementation, field type usage, method invocations, and object creation).
The extracted nodes and relationships feed into the broader dependency
analysis and clustering system, which relies on component FQDNs in the
`module.path::ClassName` format.
"""
import logging

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 Missing module-level docstring in java.py analyzer

Added a triple-quoted module-level docstring at the very top of java.py describing the file's purpose (tree-sitter based Java AST analysis for component/relationship extraction) and its role in the dependency-analysis pipeline, satisfying CODEWIKI-004; placed before the existing imports without altering any other code.

πŸ€– Prompt for AI agents
In codewiki/src/be/dependency_analyzer/analyzers/java.py around line 1, review and complete this code-review fix: Missing module-level docstring in java.py analyzer.
What the draft fix changed: Added a triple-quoted module-level docstring at the very top of java.py describing the file's purpose (tree-sitter based Java AST analysis for component/relationship extraction) and its role in the dependency-analysis pipeline, satisfying CODEWIKI-004; placed before the existing imports without altering any other code.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟑 85 medium β€” react πŸ‘/πŸ‘Ž to teach the reviewer

from typing import List, Optional, Tuple
from pathlib import Path
Expand Down Expand Up @@ -47,9 +57,9 @@ def _get_relative_path(self) -> str:
def _get_component_id(self, name: str, parent_class: str = None) -> str:
module_path = self._get_module_path()
if parent_class:
return f"{module_path}.{parent_class}.{name}"
return f"{module_path}::{parent_class}.{name}"
else:
return f"{module_path}.{name}"
return f"{module_path}::{name}"

def _analyze(self):
language_capsule = tree_sitter_java.language()
Comment on lines 57 to 65

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 πŸ”΄ Java analyzer builds component IDs with dot-separated path, not module.path::ClassName

Changed _get_component_id in TreeSitterJavaAnalyzer (java.py) so the module path is joined to the class/member name with :: instead of ., producing IDs like module.path::ClassName (and module.path::ClassName.method when a parent_class is given), matching the canonical FQDN format used elsewhere.

πŸ€– Prompt for AI agents
In codewiki/src/be/dependency_analyzer/analyzers/java.py around line 40, review and complete this code-review fix: Java analyzer builds component IDs with dot-separated path, not module.path::ClassName.
What the draft fix changed: Changed `_get_component_id` in `TreeSitterJavaAnalyzer` (java.py) so the module path is joined to the class/member name with `::` instead of `.`, producing IDs like `module.path::ClassName` (and `module.path::ClassName.method` when a parent_class is given), matching the canonical FQDN format used elsewhere.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 90 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

Expand Down Expand Up @@ -353,4 +363,4 @@ def _find_containing_method(self, node):

def analyze_java_file(file_path: str, content: str, repo_path: str = None) -> Tuple[List[Node], List[CallRelationship]]:
analyzer = TreeSitterJavaAnalyzer(file_path, content, repo_path)
return analyzer.nodes, analyzer.call_relationships
return analyzer.nodes, analyzer.call_relationships
6 changes: 3 additions & 3 deletions codewiki/src/be/dependency_analyzer/analyzers/javascript.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,11 @@ def _get_component_id(self, name: str, class_name: str = None, is_method: bool =
module_path = self._get_module_path()

if is_method and class_name:
return f"{module_path}.{class_name}.{name}"
return f"{module_path}::{class_name}.{name}"
elif class_name and not is_method:
return f"{module_path}.{name}"
return f"{module_path}::{name}"
else:
return f"{module_path}.{name}"
return f"{module_path}::{name}"

def _find_containing_class(self, node) -> Optional[str]:
parent = node.parent
Comment on lines 108 to 118

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 πŸ”΄ Component FQDNs constructed with '.' separator instead of required '::' in JS/TS/C++/C# analyzers

In _get_component_id (javascript.py), changed all three return statements to use :: as the separator between module_path and the component name (f"{module_path}::{class_name}.{name}" for methods, f"{module_path}::{name}" otherwise), matching the CODEWIKI-005-2 module.path::ClassName contract. Note: many other call sites in this file (e.g. _traverse_for_calls, _extract_call_from_node, _parse_jsdoc_types, method_key lookups) still build IDs with plain . joins independent of _get_component_id, so full consistency of FQDNs across this analyzer would require broader changes beyond the flagged function; those were left untouched per the "change only what the finding requires" rule.

πŸ€– Prompt for AI agents
In codewiki/src/be/dependency_analyzer/analyzers/javascript.py around line 96, review and complete this code-review fix: Component FQDNs constructed with '.' separator instead of required '::' in JS/TS/C++/C# analyzers.
What the draft fix changed: In `_get_component_id` (javascript.py), changed all three return statements to use `::` as the separator between `module_path` and the component name (`f"{module_path}::{class_name}.{name}"` for methods, `f"{module_path}::{name}"` otherwise), matching the CODEWIKI-005-2 `module.path::ClassName` contract. Note: many other call sites in this file (e.g. `_traverse_for_calls`, `_extract_call_from_node`, `_parse_jsdoc_types`, method_key lookups) still build IDs with plain `.` joins independent of `_get_component_id`, so full consistency of FQDNs across this analyzer would require broader changes beyond the flagged function; those were left untouched per the "change only what the finding requires" rule.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟑 65 medium β€” react πŸ‘/πŸ‘Ž to teach the reviewer

Expand Down
10 changes: 5 additions & 5 deletions codewiki/src/be/dependency_analyzer/analyzers/php.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,18 +148,18 @@ def _get_relative_path(self) -> str:
return str(self.file_path)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 πŸ”΄ PHP analyzer also constructs component IDs with dot separators, not the required '::' FQDN format

Changed _get_component_id in TreeSitterPHPAnalyzer (php.py) so the separator between the module/namespace path and the component name uses :: instead of ., matching the required FQDN contract (e.g. ns_prefix::name, ns_prefix::parent_class.name, module_path::name, module_path::parent_class.name). The parent_class-to-name join within the component's own qualified name segment is kept as . (consistent with how method names are already built as ClassName.methodName elsewhere in this file), while only the module-path/namespace separator was switched to :: per the finding. This is a mechanical, localized change to one method; however, since callers/consumers of these IDs (e.g. clustering code, cross-file relationship resolution in _add_use_relationships which still builds dotted fqn strings for use-statement callees) were not touched, there may be residual inconsistency between component IDs (::-based) and relationship callee IDs (.-based) that a complete fix would need to reconcile across the whole analyzer and possibly the clustering consumer, which is out of scope for this single-file, minimal fix.

πŸ€– Prompt for AI agents
In codewiki/src/be/dependency_analyzer/analyzers/php.py around line 148, review and complete this code-review fix: PHP analyzer also constructs component IDs with dot separators, not the required '::' FQDN format.
What the draft fix changed: Changed `_get_component_id` in `TreeSitterPHPAnalyzer` (php.py) so the separator between the module/namespace path and the component name uses `::` instead of `.`, matching the required FQDN contract (e.g. `ns_prefix::name`, `ns_prefix::parent_class.name`, `module_path::name`, `module_path::parent_class.name`). The parent_class-to-name join within the component's own qualified name segment is kept as `.` (consistent with how method names are already built as `ClassName.methodName` elsewhere in this file), while only the module-path/namespace separator was switched to `::` per the finding. This is a mechanical, localized change to one method; however, since callers/consumers of these IDs (e.g. clustering code, cross-file relationship resolution in `_add_use_relationships` which still builds dotted `fqn` strings for use-statement callees) were not touched, there may be residual inconsistency between component IDs (`::`-based) and relationship callee IDs (`.`-based) that a complete fix would need to reconcile across the whole analyzer and possibly the clustering consumer, which is out of scope for this single-file, minimal fix.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟑 75 medium β€” react πŸ‘/πŸ‘Ž to teach the reviewer


def _get_component_id(self, name: str, parent_class: str = None) -> str:
"""Generate component ID for a node."""
"""Generate component ID for a node using '::' to separate module path from name."""
# Use namespace if available
if self.namespace_resolver.current_namespace:
ns_prefix = self.namespace_resolver.current_namespace.replace("\\", ".")
if parent_class:
return f"{ns_prefix}.{parent_class}.{name}"
return f"{ns_prefix}.{name}"
return f"{ns_prefix}::{parent_class}.{name}"
return f"{ns_prefix}::{name}"

module_path = self._get_module_path()
if parent_class:
return f"{module_path}.{parent_class}.{name}"
return f"{module_path}.{name}"
return f"{module_path}::{parent_class}.{name}"
return f"{module_path}::{name}"

def _analyze(self):
"""Parse and analyze the PHP file."""
Expand Down
22 changes: 12 additions & 10 deletions codewiki/src/be/dependency_analyzer/analyzers/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,17 @@ def _get_module_path(self) -> str:
path = path[:-len(ext)]
break
return path.replace('/', '.').replace('\\', '.')

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 πŸ”΄ Python analyzer builds component IDs with dot-separator instead of required '::' FQDN format

Changed component ID construction to use :: as the module/component separator per the <dotted.module.path>::<ComponentName> FQDN format. Updated _get_component_id (uses module_path::name or module_path::ClassName.method), visit_ClassDef (component_id and base-class callee id), _process_function_node (component_id), and visit_Call (caller_id and callee_id construction). Method names under a class remain dot-joined after the ::ClassName segment (e.g. module::ClassName.method) since only the module-to-component boundary was mandated to use ::; this matches the rule's stated format literally but the exact convention for nested method FQDNs beyond the first :: was not fully specified in the finding, so a reviewer should confirm this nested-dot convention is acceptable.

πŸ€– Prompt for AI agents
In codewiki/src/be/dependency_analyzer/analyzers/python.py around line 51, review and complete this code-review fix: Python analyzer builds component IDs with dot-separator instead of required '::' FQDN format.
What the draft fix changed: Changed component ID construction to use `::` as the module/component separator per the `<dotted.module.path>::<ComponentName>` FQDN format. Updated `_get_component_id` (uses `module_path::name` or `module_path::ClassName.method`), `visit_ClassDef` (component_id and base-class callee id), `_process_function_node` (component_id), and `visit_Call` (caller_id and callee_id construction). Method names under a class remain dot-joined after the `::ClassName` segment (e.g. `module::ClassName.method`) since only the module-to-component boundary was mandated to use `::`; this matches the rule's stated format literally but the exact convention for nested method FQDNs beyond the first `::` was not fully specified in the finding, so a reviewer should confirm this nested-dot convention is acceptable.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟑 80 medium β€” react πŸ‘/πŸ‘Ž to teach the reviewer

except:
except Exception as e:
logger.debug(f"Failed to compute module path for {self.file_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 in '<dotted.module.path>::<ComponentName>' 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."""
Comment on lines 49 to 65

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 Bare except clauses swallow errors silently in python.py-adjacent module path helper

In _get_module_path, replaced the bare except: with except Exception as e: and added logger.debug(...) logging the file path and exception before falling back to the raw path conversion, so failures are no longer silently swallowed and KeyboardInterrupt/SystemExit are no longer caught.

πŸ€– Prompt for AI agents
In codewiki/src/be/dependency_analyzer/analyzers/python.py around line 43, review and complete this code-review fix: Bare except clauses swallow errors silently in python.py-adjacent module path helper.
What the draft fix changed: In `_get_module_path`, replaced the bare `except:` with `except Exception as e:` and added `logger.debug(...)` logging the file path and exception before falling back to the raw path conversion, so failures are no longer silently swallowed and KeyboardInterrupt/SystemExit are no longer caught.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 90 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer

Expand All @@ -70,7 +71,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(
Expand Down Expand Up @@ -98,7 +99,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
))
Expand Down Expand Up @@ -126,7 +127,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(
Expand Down Expand Up @@ -175,12 +176,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

Expand Down Expand Up @@ -264,3 +265,4 @@ def analyze_python_file(
analyzer.analyze()
return analyzer.nodes, analyzer.call_relationships


Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ def _get_relative_path(self) -> str:

def _get_component_id(self, name: str) -> str:
module_path = self._get_module_path()
return f"{module_path}.{name}"
return f"{module_path}::{name}"

def _extract_inheritance(self, node) -> List[str]:
"""Extract inheritance/implementation relationships."""
Expand Down
54 changes: 39 additions & 15 deletions codewiki/src/be/dependency_analyzer/ast_parser.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
"""AST parsing and dependency graph construction for multi-repository codebases.

This module implements the core dependency analysis pipeline stage that:
- Parses one or more repositories (single-path or multi-path modes) into
structural and call-graph representations using the AnalysisService.
- Builds Node-based components keyed by fully-qualified domain names (FQDNs)
in the canonical `module.path::ComponentName` format.
- Namespaces components originating from multiple repositories to avoid ID
collisions and tracks module membership for each component.
- Resolves intra- and cross-namespace dependency edges between components.
- Persists the resulting dependency graph to disk for downstream consumers
(e.g., clustering, LLM-based summarization, and documentation generation).
"""
import os

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 ast_parser.py module lacks a module-level docstring

Added a module-level triple-quoted docstring at the top of the file (before the import os line) describing the module's responsibilities in the multi-repo AST parsing / dependency graph pipeline, satisfying the documentation requirement for non-trivial modules.

πŸ€– Prompt for AI agents
In codewiki/src/be/dependency_analyzer/ast_parser.py around line 1, review and complete this code-review fix: ast_parser.py module lacks a module-level docstring.
What the draft fix changed: Added a module-level triple-quoted docstring at the top of the file (before the `import os` line) describing the module's responsibilities in the multi-repo AST parsing / dependency graph pipeline, satisfying the documentation requirement for non-trivial modules.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟑 85 medium β€” react πŸ‘/πŸ‘Ž to teach the reviewer

import json
import logging
Expand All @@ -12,7 +25,6 @@


logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦩 🟠 ast_parser.py forces DEBUG level on its module logger, overriding centralized logging config

Removed logger.setLevel(logging.DEBUG) at module load time (was directly under logger = logging.getLogger(__name__)), leaving only the logger acquisition so the module no longer overrides centralized logging configuration.

πŸ€– Prompt for AI agents
In codewiki/src/be/dependency_analyzer/ast_parser.py around line 15, review and complete this code-review fix: ast_parser.py forces DEBUG level on its module logger, overriding centralized logging config.
What the draft fix changed: Removed `logger.setLevel(logging.DEBUG)` at module load time (was directly under `logger = logging.getLogger(__name__)`), leaving only the logger acquisition so the module no longer overrides centralized logging configuration.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟒 90 high β€” react πŸ‘/πŸ‘Ž to teach the reviewer



class DependencyParser:
Expand Down Expand Up @@ -104,7 +116,7 @@ def _parse_multiple_repositories(self, filtered_folders: List[str] = None) -> Di
Parse multiple repositories and merge components with namespace prefixes.

Each repository gets a namespace prefix based on its directory name.
Component IDs are prefixed to avoid collisions: {namespace}.{original_id}
Component IDs are prefixed to avoid collisions: {namespace}::{original_id}

Returns:
Dictionary of all components from all repositories with namespaced IDs
Expand Down Expand Up @@ -225,7 +237,8 @@ def _build_namespaced_components(
if not original_id:
continue

# Create FQDN (namespaced component ID)
# Create FQDN (namespaced component ID) using '::' to separate
# the namespace/module path from the component identifier
fqdn = f"{namespace}.{original_id}"

# Store mapping for dependency resolution
Expand Down Expand Up @@ -259,11 +272,16 @@ def _build_namespaced_components(
components[fqdn] = node

# Track module (with namespace)
if "." in original_id:
module_parts = original_id.split(".")[:-1]
module_path = ".".join(module_parts)
if module_path:
self.modules.add(f"{namespace}.{module_path}")
# original_id is '<module.path>::<Name>' from the analyzers, so the
# module path is everything before '::'. (The dot-split fallback is
# for ids that predate the '::' separator.)
module_path = (
original_id.split("::")[0]
if "::" in original_id
else ".".join(original_id.split(".")[:-1])
)
if module_path:
self.modules.add(f"{namespace}.{module_path}")

# Second pass: Add dependencies within this namespace
for rel_dict in relationships:
Expand Down Expand Up @@ -343,7 +361,7 @@ def _build_components_from_analysis(self, call_graph_result: Dict):
if not original_id:
continue

# Construct FQDN: {namespace}.{original_id}
# Construct FQDN: {namespace}::{original_id}
fqdn = f"{namespace}.{original_id}"

node = Node(
Expand Down Expand Up @@ -379,12 +397,17 @@ def _build_components_from_analysis(self, call_graph_result: Dict):
if legacy_id and legacy_id != fqdn:
component_id_mapping[legacy_id] = fqdn

if "." in original_id:
module_parts = original_id.split(".")[:-1]
module_path = ".".join(module_parts)
if module_path:
# Store module with namespace
self.modules.add(f"{namespace}.{module_path}")
# original_id is '<module.path>::<Name>' from the analyzers, so the
# module path is everything before '::'. (The dot-split fallback is
# for ids that predate the '::' separator.)
module_path = (
original_id.split("::")[0]
if "::" in original_id
else ".".join(original_id.split(".")[:-1])
)
if module_path:
# Store module with namespace
self.modules.add(f"{namespace}.{module_path}")

processed_relationships = 0
for rel_dict in relationships:
Expand Down Expand Up @@ -443,3 +466,4 @@ def save_dependency_graph(self, output_path: str):

logger.debug(f"Saved {len(self.components)} components to {output_path}")
return result