codeboarding 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. agents/__init__.py +0 -0
  2. agents/abstraction_agent.py +150 -0
  3. agents/agent.py +467 -0
  4. agents/agent_responses.py +363 -0
  5. agents/cluster_methods_mixin.py +281 -0
  6. agents/constants.py +13 -0
  7. agents/dependency_discovery.py +159 -0
  8. agents/details_agent.py +174 -0
  9. agents/llm_config.py +309 -0
  10. agents/meta_agent.py +105 -0
  11. agents/planner_agent.py +105 -0
  12. agents/prompts/__init__.py +85 -0
  13. agents/prompts/abstract_prompt_factory.py +63 -0
  14. agents/prompts/claude_prompts.py +381 -0
  15. agents/prompts/deepseek_prompts.py +389 -0
  16. agents/prompts/gemini_flash_prompts.py +362 -0
  17. agents/prompts/glm_prompts.py +407 -0
  18. agents/prompts/gpt_prompts.py +470 -0
  19. agents/prompts/kimi_prompts.py +400 -0
  20. agents/prompts/prompt_factory.py +179 -0
  21. agents/tools/__init__.py +8 -0
  22. agents/tools/base.py +96 -0
  23. agents/tools/get_external_deps.py +47 -0
  24. agents/tools/get_method_invocations.py +47 -0
  25. agents/tools/read_cfg.py +60 -0
  26. agents/tools/read_docs.py +132 -0
  27. agents/tools/read_file.py +90 -0
  28. agents/tools/read_file_structure.py +156 -0
  29. agents/tools/read_git_diff.py +131 -0
  30. agents/tools/read_packages.py +60 -0
  31. agents/tools/read_source.py +105 -0
  32. agents/tools/read_structure.py +49 -0
  33. agents/tools/toolkit.py +119 -0
  34. agents/validation.py +383 -0
  35. caching/__init__.py +4 -0
  36. caching/cache.py +29 -0
  37. caching/meta_cache.py +227 -0
  38. codeboarding-0.9.0.dist-info/METADATA +223 -0
  39. codeboarding-0.9.0.dist-info/RECORD +126 -0
  40. codeboarding-0.9.0.dist-info/WHEEL +5 -0
  41. codeboarding-0.9.0.dist-info/entry_points.txt +3 -0
  42. codeboarding-0.9.0.dist-info/licenses/LICENSE +21 -0
  43. codeboarding-0.9.0.dist-info/top_level.txt +18 -0
  44. core/__init__.py +101 -0
  45. core/plugin_loader.py +46 -0
  46. core/protocols.py +27 -0
  47. core/registry.py +46 -0
  48. diagram_analysis/__init__.py +4 -0
  49. diagram_analysis/analysis_json.py +346 -0
  50. diagram_analysis/diagram_generator.py +486 -0
  51. diagram_analysis/file_coverage.py +212 -0
  52. diagram_analysis/incremental/__init__.py +63 -0
  53. diagram_analysis/incremental/component_checker.py +236 -0
  54. diagram_analysis/incremental/file_manager.py +217 -0
  55. diagram_analysis/incremental/impact_analyzer.py +238 -0
  56. diagram_analysis/incremental/io_utils.py +281 -0
  57. diagram_analysis/incremental/models.py +72 -0
  58. diagram_analysis/incremental/path_patching.py +164 -0
  59. diagram_analysis/incremental/reexpansion.py +166 -0
  60. diagram_analysis/incremental/scoped_analysis.py +227 -0
  61. diagram_analysis/incremental/updater.py +464 -0
  62. diagram_analysis/incremental/validation.py +48 -0
  63. diagram_analysis/manifest.py +152 -0
  64. diagram_analysis/version.py +6 -0
  65. duckdb_crud.py +125 -0
  66. github_action.py +172 -0
  67. health/__init__.py +3 -0
  68. health/checks/__init__.py +11 -0
  69. health/checks/circular_deps.py +48 -0
  70. health/checks/cohesion.py +93 -0
  71. health/checks/coupling.py +140 -0
  72. health/checks/function_size.py +85 -0
  73. health/checks/god_class.py +167 -0
  74. health/checks/inheritance.py +104 -0
  75. health/checks/instability.py +77 -0
  76. health/checks/unused_code_diagnostics.py +338 -0
  77. health/config.py +172 -0
  78. health/constants.py +19 -0
  79. health/models.py +186 -0
  80. health/runner.py +236 -0
  81. install.py +518 -0
  82. logging_config.py +105 -0
  83. main.py +529 -0
  84. monitoring/__init__.py +12 -0
  85. monitoring/callbacks.py +163 -0
  86. monitoring/context.py +158 -0
  87. monitoring/mixin.py +16 -0
  88. monitoring/paths.py +47 -0
  89. monitoring/stats.py +50 -0
  90. monitoring/writers.py +172 -0
  91. output_generators/__init__.py +0 -0
  92. output_generators/html.py +163 -0
  93. output_generators/html_template.py +382 -0
  94. output_generators/markdown.py +140 -0
  95. output_generators/mdx.py +171 -0
  96. output_generators/sphinx.py +175 -0
  97. repo_utils/__init__.py +277 -0
  98. repo_utils/change_detector.py +289 -0
  99. repo_utils/errors.py +6 -0
  100. repo_utils/git_diff.py +74 -0
  101. repo_utils/ignore.py +341 -0
  102. static_analyzer/__init__.py +335 -0
  103. static_analyzer/analysis_cache.py +699 -0
  104. static_analyzer/analysis_result.py +269 -0
  105. static_analyzer/cluster_change_analyzer.py +391 -0
  106. static_analyzer/cluster_helpers.py +79 -0
  107. static_analyzer/constants.py +166 -0
  108. static_analyzer/git_diff_analyzer.py +224 -0
  109. static_analyzer/graph.py +746 -0
  110. static_analyzer/incremental_orchestrator.py +671 -0
  111. static_analyzer/java_config_scanner.py +232 -0
  112. static_analyzer/java_utils.py +227 -0
  113. static_analyzer/lsp_client/__init__.py +12 -0
  114. static_analyzer/lsp_client/client.py +1642 -0
  115. static_analyzer/lsp_client/diagnostics.py +62 -0
  116. static_analyzer/lsp_client/java_client.py +517 -0
  117. static_analyzer/lsp_client/language_settings.py +97 -0
  118. static_analyzer/lsp_client/typescript_client.py +235 -0
  119. static_analyzer/programming_language.py +152 -0
  120. static_analyzer/reference_resolve_mixin.py +166 -0
  121. static_analyzer/scanner.py +95 -0
  122. static_analyzer/typescript_config_scanner.py +54 -0
  123. tool_registry.py +433 -0
  124. user_config.py +134 -0
  125. utils.py +56 -0
  126. vscode_constants.py +124 -0
@@ -0,0 +1,289 @@
1
+ """
2
+ Rename-aware change detection using git.
3
+
4
+ This module provides enhanced change detection that properly tracks:
5
+ - File renames (with similarity percentage)
6
+ - File modifications
7
+ - Added/deleted files
8
+ - Copy detection
9
+
10
+ Uses `git diff --name-status -M -C` for accurate rename tracking.
11
+ """
12
+
13
+ import logging
14
+ import subprocess
15
+ from dataclasses import dataclass, field
16
+ from enum import Enum
17
+ from pathlib import Path
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class ChangeType(Enum):
23
+ """Git change status types."""
24
+
25
+ ADDED = "A"
26
+ COPIED = "C"
27
+ DELETED = "D"
28
+ MODIFIED = "M"
29
+ RENAMED = "R"
30
+ TYPE_CHANGED = "T"
31
+ UNMERGED = "U"
32
+ UNKNOWN = "X"
33
+
34
+
35
+ @dataclass
36
+ class DetectedChange:
37
+ """A detected file change with rename tracking."""
38
+
39
+ change_type: ChangeType
40
+ file_path: str # Current/new path
41
+ old_path: str | None = None # For renames/copies: the original path
42
+ similarity: int | None = None # For renames/copies: 0-100%
43
+
44
+ def is_rename(self) -> bool:
45
+ return self.change_type == ChangeType.RENAMED
46
+
47
+ def is_content_change(self) -> bool:
48
+ """Returns True if file content was modified (not just metadata/rename)."""
49
+ return self.change_type in (ChangeType.MODIFIED, ChangeType.ADDED)
50
+
51
+ def is_structural(self) -> bool:
52
+ """Returns True if this affects file existence (add/delete)."""
53
+ return self.change_type in (ChangeType.ADDED, ChangeType.DELETED)
54
+
55
+
56
+ @dataclass
57
+ class ChangeSet:
58
+ """Collection of detected changes with helper methods."""
59
+
60
+ changes: list[DetectedChange] = field(default_factory=list)
61
+ base_ref: str = ""
62
+ target_ref: str = "HEAD"
63
+
64
+ @property
65
+ def renames(self) -> dict[str, str]:
66
+ """Get rename mapping: old_path -> new_path."""
67
+ return {c.old_path: c.file_path for c in self.changes if c.is_rename() and c.old_path}
68
+
69
+ @property
70
+ def modified_files(self) -> list[str]:
71
+ """Get list of modified file paths."""
72
+ return [c.file_path for c in self.changes if c.change_type == ChangeType.MODIFIED]
73
+
74
+ @property
75
+ def added_files(self) -> list[str]:
76
+ """Get list of added file paths."""
77
+ return [c.file_path for c in self.changes if c.change_type == ChangeType.ADDED]
78
+
79
+ @property
80
+ def deleted_files(self) -> list[str]:
81
+ """Get list of deleted file paths."""
82
+ return [c.file_path for c in self.changes if c.change_type == ChangeType.DELETED]
83
+
84
+ @property
85
+ def all_affected_files(self) -> set[str]:
86
+ """Get all files that were affected (current paths)."""
87
+ return {c.file_path for c in self.changes}
88
+
89
+ @property
90
+ def all_old_paths(self) -> set[str]:
91
+ """Get all old paths (for renames)."""
92
+ return {c.old_path for c in self.changes if c.old_path}
93
+
94
+ def is_empty(self) -> bool:
95
+ return len(self.changes) == 0
96
+
97
+ def has_structural_changes(self) -> bool:
98
+ """Returns True if any files were added or deleted."""
99
+ return any(c.is_structural() for c in self.changes)
100
+
101
+ def has_only_renames(self) -> bool:
102
+ """Returns True if all changes are pure renames."""
103
+ return all(c.is_rename() for c in self.changes) and len(self.changes) > 0
104
+
105
+
106
+ def detect_changes(
107
+ repo_dir: Path,
108
+ base_ref: str,
109
+ target_ref: str = "HEAD",
110
+ exclude_patterns: list[str] | None = None,
111
+ fetch_missing_refs: bool = True,
112
+ ) -> ChangeSet:
113
+ """
114
+ Detect file changes between two refs using rename-aware git diff.
115
+
116
+ Args:
117
+ repo_dir: Path to the git repository
118
+ base_ref: Base reference (commit hash, tag, or branch)
119
+ target_ref: Target reference (default: HEAD, includes working tree)
120
+ exclude_patterns: List of path patterns to exclude (e.g., [".codeboarding/"])
121
+
122
+ Returns:
123
+ ChangeSet with all detected changes
124
+
125
+ Example:
126
+ changes = detect_changes(repo_path, "abc1234")
127
+ for rename_old, rename_new in changes.renames.items():
128
+ print(f"Renamed: {rename_old} -> {rename_new}")
129
+ """
130
+ changes: list[DetectedChange] = []
131
+
132
+ # Default exclusions for analysis output directories
133
+ if exclude_patterns is None:
134
+ exclude_patterns = [".codeboarding/", ".codeboarding\\"]
135
+
136
+ cmd = [
137
+ "git",
138
+ "diff",
139
+ "--name-status",
140
+ "-M",
141
+ "-C",
142
+ "--find-renames=50%",
143
+ base_ref,
144
+ target_ref,
145
+ ]
146
+
147
+ def _run_diff() -> subprocess.CompletedProcess:
148
+ return subprocess.run(
149
+ cmd,
150
+ cwd=repo_dir,
151
+ capture_output=True,
152
+ text=True,
153
+ check=True,
154
+ )
155
+
156
+ try:
157
+ result = _run_diff()
158
+ except subprocess.CalledProcessError as e:
159
+ stderr_lower = (e.stderr or "").lower()
160
+ if fetch_missing_refs and "bad object" in stderr_lower:
161
+ logger.warning("Git diff failed due to missing ref (%s); fetching refs and retrying once", e.stderr.strip())
162
+ try:
163
+ subprocess.run(
164
+ ["git", "fetch", "--all", "--prune", "--tags"],
165
+ cwd=repo_dir,
166
+ capture_output=True,
167
+ text=True,
168
+ check=True,
169
+ )
170
+ result = _run_diff()
171
+ except subprocess.CalledProcessError as fetch_err:
172
+ logger.error(f"Git fetch/diff retry failed: {fetch_err.stderr}")
173
+ return ChangeSet(changes=changes, base_ref=base_ref, target_ref=target_ref)
174
+ else:
175
+ logger.error(f"Git diff failed: {e.stderr}")
176
+ return ChangeSet(changes=changes, base_ref=base_ref, target_ref=target_ref)
177
+ except FileNotFoundError:
178
+ logger.error("Git not found in PATH")
179
+ return ChangeSet(changes=changes, base_ref=base_ref, target_ref=target_ref)
180
+
181
+ for line in result.stdout.strip().split("\n"):
182
+ if not line:
183
+ continue
184
+
185
+ change = _parse_status_line(line)
186
+ if change:
187
+ # Skip excluded patterns
188
+ should_skip = False
189
+ for pattern in exclude_patterns:
190
+ if change.file_path.startswith(pattern):
191
+ should_skip = True
192
+ break
193
+ if change.old_path and change.old_path.startswith(pattern):
194
+ should_skip = True
195
+ break
196
+
197
+ if should_skip:
198
+ logger.debug(f"Skipping excluded path: {change.file_path}")
199
+ continue
200
+
201
+ changes.append(change)
202
+ logger.debug(f"Detected change: {change.change_type.name} {change.file_path}")
203
+
204
+ return ChangeSet(changes=changes, base_ref=base_ref, target_ref=target_ref)
205
+
206
+
207
+ def detect_changes_from_commit(repo_dir: Path, base_commit: str) -> ChangeSet:
208
+ """
209
+ Detect changes from a specific commit to current working tree.
210
+
211
+ This includes both committed and uncommitted changes.
212
+ """
213
+ return detect_changes(repo_dir, base_commit, "HEAD")
214
+
215
+
216
+ def detect_uncommitted_changes(repo_dir: Path) -> ChangeSet:
217
+ """
218
+ Detect only uncommitted changes (staged + unstaged).
219
+ """
220
+ return detect_changes(repo_dir, "HEAD", "")
221
+
222
+
223
+ def _parse_status_line(line: str) -> DetectedChange | None:
224
+ """
225
+ Parse a git diff --name-status line.
226
+
227
+ Format examples:
228
+ M file.py # Modified
229
+ A newfile.py # Added
230
+ D oldfile.py # Deleted
231
+ R100 old.py new.py # Renamed (100% similar)
232
+ R075 old.py new.py # Renamed (75% similar)
233
+ C100 src.py copy.py # Copied
234
+
235
+ Returns DetectedChange or None if parsing fails.
236
+ """
237
+ parts = line.split("\t")
238
+ if len(parts) < 2:
239
+ return None
240
+
241
+ status = parts[0]
242
+ status_char = status[0].upper()
243
+
244
+ try:
245
+ change_type = ChangeType(status_char)
246
+ except ValueError:
247
+ logger.warning(f"Unknown git status: {status_char}")
248
+ return None
249
+
250
+ # Handle renames and copies (have similarity percentage and two paths)
251
+ if change_type in (ChangeType.RENAMED, ChangeType.COPIED):
252
+ if len(parts) < 3:
253
+ return None
254
+
255
+ # Extract similarity percentage (e.g., "R100" -> 100)
256
+ similarity = None
257
+ if len(status) > 1:
258
+ try:
259
+ similarity = int(status[1:])
260
+ except ValueError:
261
+ pass
262
+
263
+ return DetectedChange(
264
+ change_type=change_type,
265
+ file_path=parts[2], # New path
266
+ old_path=parts[1], # Old path
267
+ similarity=similarity,
268
+ )
269
+
270
+ # Simple change (A, M, D, T)
271
+ return DetectedChange(
272
+ change_type=change_type,
273
+ file_path=parts[1],
274
+ )
275
+
276
+
277
+ def get_current_commit(repo_dir: Path) -> str | None:
278
+ """Get the current HEAD commit hash."""
279
+ try:
280
+ result = subprocess.run(
281
+ ["git", "rev-parse", "HEAD"],
282
+ cwd=repo_dir,
283
+ capture_output=True,
284
+ text=True,
285
+ check=True,
286
+ )
287
+ return result.stdout.strip()
288
+ except subprocess.CalledProcessError:
289
+ return None
repo_utils/errors.py ADDED
@@ -0,0 +1,6 @@
1
+ class NoGithubTokenFoundError(Exception):
2
+ pass
3
+
4
+
5
+ class RepoDontExistError(Exception):
6
+ pass
repo_utils/git_diff.py ADDED
@@ -0,0 +1,74 @@
1
+ import logging
2
+ from dataclasses import dataclass, field
3
+ from pathlib import Path
4
+
5
+
6
+ @dataclass
7
+ class FileChange:
8
+ """
9
+ Container for the changes made to a single file.
10
+ """
11
+
12
+ filename: str
13
+ additions: int
14
+ deletions: int
15
+ added_lines: list[str] = field(default_factory=list)
16
+ removed_lines: list[str] = field(default_factory=list)
17
+
18
+ def llm_str(self):
19
+ """
20
+ Returns a string representation of the file change suitable for LLM processing.
21
+ """
22
+ return f"File: {self.filename}, Added lines: +{self.additions}, Removed lines: -{self.deletions}"
23
+
24
+
25
+ def get_git_diff(repo_dir: Path, version: str) -> list[FileChange]:
26
+ """
27
+ Get the git diff between a specific version and the current working tree (uncommitted changes included).
28
+
29
+ :param repo_dir: Path to the repository directory.
30
+ :param version: The commit hash or tag to compare against.
31
+ :return: A list of FileChange objects describing the differences.
32
+ """
33
+ changes: list[FileChange] = []
34
+
35
+ try:
36
+ from git import Repo
37
+
38
+ repo = Repo(repo_dir)
39
+
40
+ # Compare the specified version to the working tree (including staged + unstaged changes)
41
+ diff_index = repo.git.diff(version, "--patch")
42
+
43
+ # Group diff by file using parsing logic
44
+ current_file = None
45
+ added: list[str] = []
46
+ removed: list[str] = []
47
+ for line in diff_index.splitlines():
48
+ if line.startswith("diff --git"):
49
+ # Save previous file change, if any
50
+ if current_file:
51
+ changes.append(current_file)
52
+ # Start a new file
53
+ added, removed = [], []
54
+ filename = line.split(" b/")[-1]
55
+ current_file = FileChange(filename=filename, additions=0, deletions=0)
56
+ elif line.startswith("+++ ") or line.startswith("--- ") or line.startswith("@@"):
57
+ continue
58
+ elif line.startswith("+"):
59
+ added.append(line[1:])
60
+ elif line.startswith("-"):
61
+ removed.append(line[1:])
62
+ if current_file:
63
+ current_file.additions = len(added)
64
+ current_file.deletions = len(removed)
65
+ current_file.added_lines = added
66
+ current_file.removed_lines = removed
67
+
68
+ # Append the last file if it exists
69
+ if current_file:
70
+ changes.append(current_file)
71
+
72
+ except Exception as e:
73
+ logging.error(f"Error obtaining git diff: {e}")
74
+ return changes