cortexcode 0.5.0__tar.gz → 0.6.0__tar.gz

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 (178) hide show
  1. {cortexcode-0.5.0 → cortexcode-0.6.0}/PKG-INFO +29 -1
  2. {cortexcode-0.5.0 → cortexcode-0.6.0}/README.md +23 -0
  3. cortexcode-0.6.0/cortexcode/advanced_analysis/__init__.py +17 -0
  4. cortexcode-0.6.0/cortexcode/advanced_analysis/advanced_analysis.py +19 -0
  5. cortexcode-0.6.0/cortexcode/advanced_analysis/advanced_analysis_cycles.py +67 -0
  6. cortexcode-0.6.0/cortexcode/advanced_analysis/advanced_analysis_docs.py +126 -0
  7. cortexcode-0.6.0/cortexcode/advanced_analysis/advanced_analysis_duplicates.py +158 -0
  8. cortexcode-0.6.0/cortexcode/advanced_analysis/advanced_analysis_endpoints.py +205 -0
  9. cortexcode-0.6.0/cortexcode/advanced_analysis/advanced_analysis_search.py +85 -0
  10. cortexcode-0.6.0/cortexcode/advanced_analysis/advanced_analysis_security.py +119 -0
  11. cortexcode-0.6.0/cortexcode/advanced_analysis.py +19 -0
  12. cortexcode-0.6.0/cortexcode/ai_docs/__init__.py +15 -0
  13. cortexcode-0.6.0/cortexcode/ai_docs/config.py +130 -0
  14. cortexcode-0.6.0/cortexcode/ai_docs/doc_cache.py +40 -0
  15. cortexcode-0.6.0/cortexcode/ai_docs/doc_generator.py +193 -0
  16. cortexcode-0.6.0/cortexcode/ai_docs/doc_lookup.py +27 -0
  17. cortexcode-0.6.0/cortexcode/ai_docs/doc_models.py +12 -0
  18. cortexcode-0.6.0/cortexcode/ai_docs/explainer.py +237 -0
  19. cortexcode-0.6.0/cortexcode/ai_docs/llm_client.py +289 -0
  20. cortexcode-0.6.0/cortexcode/ai_docs/page_generator.py +357 -0
  21. cortexcode-0.6.0/cortexcode/ai_docs/prompts.py +271 -0
  22. cortexcode-0.6.0/cortexcode/ai_docs/report_runner.py +251 -0
  23. cortexcode-0.6.0/cortexcode/analysis/__init__.py +11 -0
  24. cortexcode-0.6.0/cortexcode/analysis/analysis_complexity.py +147 -0
  25. cortexcode-0.6.0/cortexcode/analysis/analysis_dead_code.py +83 -0
  26. cortexcode-0.6.0/cortexcode/analysis/analysis_impact.py +62 -0
  27. cortexcode-0.6.0/cortexcode/analysis.py +10 -0
  28. cortexcode-0.6.0/cortexcode/cli/__init__.py +63 -0
  29. cortexcode-0.6.0/cortexcode/cli/cli_ai_docs.py +28 -0
  30. cortexcode-0.6.0/cortexcode/cli/cli_complexity.py +62 -0
  31. cortexcode-0.6.0/cortexcode/cli/cli_config.py +68 -0
  32. cortexcode-0.6.0/cortexcode/cli/cli_context.py +34 -0
  33. cortexcode-0.6.0/cortexcode/cli/cli_dashboard.py +31 -0
  34. cortexcode-0.6.0/cortexcode/cli/cli_dead_code.py +41 -0
  35. cortexcode-0.6.0/cortexcode/cli/cli_diagrams.py +26 -0
  36. cortexcode-0.6.0/cortexcode/cli/cli_diff.py +57 -0
  37. cortexcode-0.6.0/cortexcode/cli/cli_docs.py +37 -0
  38. cortexcode-0.6.0/cortexcode/cli/cli_explain.py +23 -0
  39. cortexcode-0.6.0/cortexcode/cli/cli_find.py +40 -0
  40. cortexcode-0.6.0/cortexcode/cli/cli_impact.py +50 -0
  41. cortexcode-0.6.0/cortexcode/cli/cli_index.py +110 -0
  42. cortexcode-0.6.0/cortexcode/cli/cli_report.py +19 -0
  43. cortexcode-0.6.0/cortexcode/cli/cli_scan.py +34 -0
  44. cortexcode-0.6.0/cortexcode/cli/cli_search.py +61 -0
  45. cortexcode-0.6.0/cortexcode/cli/cli_servers.py +10 -0
  46. cortexcode-0.6.0/cortexcode/cli/cli_stats.py +20 -0
  47. cortexcode-0.6.0/cortexcode/cli/cli_support.py +30 -0
  48. cortexcode-0.6.0/cortexcode/cli/cli_watch.py +10 -0
  49. cortexcode-0.6.0/cortexcode/cli/cli_wiki.py +161 -0
  50. cortexcode-0.6.0/cortexcode/cli/cli_workspace.py +103 -0
  51. cortexcode-0.6.0/cortexcode/config.py +159 -0
  52. cortexcode-0.6.0/cortexcode/context/__init__.py +13 -0
  53. cortexcode-0.6.0/cortexcode/context/context_format.py +17 -0
  54. cortexcode-0.5.0/cortexcode/context.py → cortexcode-0.6.0/cortexcode/context/context_query.py +43 -146
  55. cortexcode-0.6.0/cortexcode/context/context_tokens.py +68 -0
  56. cortexcode-0.6.0/cortexcode/context.py +12 -0
  57. cortexcode-0.6.0/cortexcode/diagrams/__init__.py +26 -0
  58. cortexcode-0.6.0/cortexcode/diagrams/architecture.py +60 -0
  59. cortexcode-0.6.0/cortexcode/diagrams/call_graph.py +71 -0
  60. cortexcode-0.6.0/cortexcode/diagrams/class_diagram.py +63 -0
  61. cortexcode-0.6.0/cortexcode/diagrams/dependencies.py +35 -0
  62. cortexcode-0.6.0/cortexcode/diagrams/directory_tree.py +28 -0
  63. cortexcode-0.6.0/cortexcode/diagrams/entities.py +83 -0
  64. cortexcode-0.6.0/cortexcode/diagrams/file_tree.py +33 -0
  65. cortexcode-0.6.0/cortexcode/diagrams/imports.py +44 -0
  66. cortexcode-0.6.0/cortexcode/diagrams/save.py +53 -0
  67. cortexcode-0.6.0/cortexcode/diagrams/sequence.py +47 -0
  68. cortexcode-0.6.0/cortexcode/diagrams/state.py +22 -0
  69. cortexcode-0.6.0/cortexcode/diagrams/utils.py +24 -0
  70. cortexcode-0.6.0/cortexcode/docs/__init__.py +99 -0
  71. cortexcode-0.6.0/cortexcode/docs/diagrams.py +105 -0
  72. cortexcode-0.6.0/cortexcode/docs/generator.py +96 -0
  73. cortexcode-0.6.0/cortexcode/docs/javascript.py +6 -0
  74. cortexcode-0.5.0/cortexcode/docs/javascript.py → cortexcode-0.6.0/cortexcode/docs/javascript_sections.py +24 -2
  75. cortexcode-0.6.0/cortexcode/indexer.py +213 -0
  76. cortexcode-0.6.0/cortexcode/indexing/__init__.py +58 -0
  77. cortexcode-0.6.0/cortexcode/indexing/build.py +51 -0
  78. cortexcode-0.6.0/cortexcode/indexing/calls.py +25 -0
  79. cortexcode-0.6.0/cortexcode/indexing/config.py +24 -0
  80. cortexcode-0.6.0/cortexcode/indexing/defaults.py +21 -0
  81. cortexcode-0.6.0/cortexcode/indexing/dispatch.py +42 -0
  82. cortexcode-0.6.0/cortexcode/indexing/entities.py +87 -0
  83. cortexcode-0.6.0/cortexcode/indexing/extensions.py +2 -0
  84. cortexcode-0.6.0/cortexcode/indexing/extractor_mixin.py +355 -0
  85. cortexcode-0.6.0/cortexcode/indexing/extractors/__init__.py +9 -0
  86. cortexcode-0.6.0/cortexcode/indexing/extractors/csharp.py +90 -0
  87. cortexcode-0.6.0/cortexcode/indexing/extractors/dart.py +165 -0
  88. cortexcode-0.6.0/cortexcode/indexing/extractors/generic.py +139 -0
  89. cortexcode-0.6.0/cortexcode/indexing/extractors/java.py +91 -0
  90. cortexcode-0.6.0/cortexcode/indexing/extractors/javascript.py +194 -0
  91. cortexcode-0.6.0/cortexcode/indexing/extractors/kotlin.py +92 -0
  92. cortexcode-0.6.0/cortexcode/indexing/extractors/swift.py +112 -0
  93. cortexcode-0.6.0/cortexcode/indexing/filtering.py +74 -0
  94. cortexcode-0.6.0/cortexcode/indexing/frameworks.py +236 -0
  95. cortexcode-0.6.0/cortexcode/indexing/gitignore.py +65 -0
  96. cortexcode-0.6.0/cortexcode/indexing/imports_exports.py +172 -0
  97. cortexcode-0.6.0/cortexcode/indexing/incremental.py +57 -0
  98. cortexcode-0.6.0/cortexcode/indexing/languages.py +48 -0
  99. cortexcode-0.6.0/cortexcode/indexing/metadata.py +100 -0
  100. cortexcode-0.6.0/cortexcode/indexing/nodes.py +8 -0
  101. cortexcode-0.6.0/cortexcode/indexing/output.py +19 -0
  102. cortexcode-0.6.0/cortexcode/indexing/params.py +27 -0
  103. cortexcode-0.6.0/cortexcode/indexing/parsers.py +20 -0
  104. cortexcode-0.6.0/cortexcode/indexing/pipeline.py +104 -0
  105. cortexcode-0.6.0/cortexcode/indexing/profile.py +271 -0
  106. cortexcode-0.6.0/cortexcode/indexing/resolution.py +191 -0
  107. cortexcode-0.6.0/cortexcode/indexing/routes.py +120 -0
  108. cortexcode-0.6.0/cortexcode/indexing/session.py +21 -0
  109. cortexcode-0.6.0/cortexcode/indexing/storage.py +13 -0
  110. cortexcode-0.6.0/cortexcode/indexing/walk.py +32 -0
  111. cortexcode-0.6.0/cortexcode/knowledge/__init__.py +21 -0
  112. cortexcode-0.6.0/cortexcode/knowledge/build.py +119 -0
  113. cortexcode-0.6.0/cortexcode/knowledge/citations.py +46 -0
  114. cortexcode-0.6.0/cortexcode/knowledge/concepts.py +241 -0
  115. cortexcode-0.6.0/cortexcode/knowledge/models.py +111 -0
  116. cortexcode-0.6.0/cortexcode/knowledge/snippets.py +99 -0
  117. cortexcode-0.6.0/cortexcode/knowledge/usage.py +86 -0
  118. cortexcode-0.6.0/cortexcode/main.py +486 -0
  119. cortexcode-0.6.0/cortexcode/mcp/__init__.py +20 -0
  120. cortexcode-0.6.0/cortexcode/mcp/mcp_protocol.py +11 -0
  121. cortexcode-0.6.0/cortexcode/mcp/mcp_registry.py +206 -0
  122. cortexcode-0.6.0/cortexcode/mcp/mcp_server.py +78 -0
  123. cortexcode-0.6.0/cortexcode/mcp/mcp_tool_handlers.py +245 -0
  124. cortexcode-0.6.0/cortexcode/mcp/mcp_transport.py +61 -0
  125. cortexcode-0.6.0/cortexcode/performance/__init__.py +28 -0
  126. cortexcode-0.6.0/cortexcode/performance/performance_config.py +176 -0
  127. cortexcode-0.6.0/cortexcode/performance/performance_index_storage.py +67 -0
  128. cortexcode-0.6.0/cortexcode/performance/performance_preview.py +60 -0
  129. cortexcode-0.6.0/cortexcode/performance.py +32 -0
  130. {cortexcode-0.5.0/cortexcode/docs → cortexcode-0.6.0/cortexcode/reports}/__init__.py +7 -9
  131. cortexcode-0.6.0/cortexcode/reports/html/__init__.py +4 -0
  132. cortexcode-0.5.0/cortexcode/docs/generator.py → cortexcode-0.6.0/cortexcode/reports/html/dashboard.py +64 -321
  133. cortexcode-0.6.0/cortexcode/reports/html/dashboard_fragments.py +134 -0
  134. cortexcode-0.6.0/cortexcode/reports/html/view_model.py +126 -0
  135. cortexcode-0.6.0/cortexcode/reports/markdown/__init__.py +15 -0
  136. cortexcode-0.6.0/cortexcode/reports/markdown/api.py +58 -0
  137. cortexcode-0.6.0/cortexcode/reports/markdown/flows.py +57 -0
  138. cortexcode-0.6.0/cortexcode/reports/markdown/insights.py +81 -0
  139. cortexcode-0.6.0/cortexcode/reports/markdown/readme.py +74 -0
  140. cortexcode-0.6.0/cortexcode/reports/markdown/structure.py +19 -0
  141. cortexcode-0.6.0/cortexcode/reports/markdown/tech.py +95 -0
  142. cortexcode-0.6.0/cortexcode/reports/site/__init__.py +5 -0
  143. cortexcode-0.6.0/cortexcode/reports/site/generator.py +570 -0
  144. cortexcode-0.6.0/cortexcode/terminal/__init__.py +30 -0
  145. cortexcode-0.6.0/cortexcode/terminal/analysis.py +63 -0
  146. cortexcode-0.6.0/cortexcode/terminal/completion.py +39 -0
  147. cortexcode-0.6.0/cortexcode/terminal/headers.py +36 -0
  148. cortexcode-0.6.0/cortexcode/terminal/prompts.py +11 -0
  149. cortexcode-0.6.0/cortexcode/terminal/reports.py +209 -0
  150. cortexcode-0.6.0/cortexcode/terminal/stats.py +49 -0
  151. {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode.egg-info/PKG-INFO +29 -1
  152. cortexcode-0.6.0/cortexcode.egg-info/SOURCES.txt +169 -0
  153. cortexcode-0.6.0/cortexcode.egg-info/entry_points.txt +2 -0
  154. {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode.egg-info/requires.txt +5 -0
  155. {cortexcode-0.5.0 → cortexcode-0.6.0}/pyproject.toml +7 -2
  156. cortexcode-0.5.0/cortexcode/advanced_analysis.py +0 -816
  157. cortexcode-0.5.0/cortexcode/analysis.py +0 -331
  158. cortexcode-0.5.0/cortexcode/cli.py +0 -845
  159. cortexcode-0.5.0/cortexcode/indexer.py +0 -1884
  160. cortexcode-0.5.0/cortexcode/mcp_server.py +0 -597
  161. cortexcode-0.5.0/cortexcode.egg-info/SOURCES.txt +0 -30
  162. cortexcode-0.5.0/cortexcode.egg-info/entry_points.txt +0 -2
  163. {cortexcode-0.5.0 → cortexcode-0.6.0}/LICENSE +0 -0
  164. {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/__init__.py +0 -0
  165. {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/dashboard.py +0 -0
  166. {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/docs/html_generators.py +0 -0
  167. {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/docs/templates.py +0 -0
  168. {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/docs.py +0 -0
  169. {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/git_diff.py +0 -0
  170. {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/lsp_server.py +0 -0
  171. {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/plugins.py +0 -0
  172. {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/semantic_search.py +0 -0
  173. {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/vuln_scan.py +0 -0
  174. {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/watcher.py +0 -0
  175. {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/workspace.py +0 -0
  176. {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode.egg-info/dependency_links.txt +0 -0
  177. {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode.egg-info/top_level.txt +0 -0
  178. {cortexcode-0.5.0 → cortexcode-0.6.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cortexcode
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Lightweight code indexing for AI assistants — save 90%+ tokens with structured context
5
5
  Author-email: Naveen <naveen_joshi07@outlook.com>
6
6
  License: MIT
@@ -34,12 +34,17 @@ Requires-Dist: tree-sitter-c-sharp>=0.23.0
34
34
  Requires-Dist: click>=8.1.0
35
35
  Requires-Dist: watchdog>=4.0.0
36
36
  Requires-Dist: rich>=13.0.0
37
+ Requires-Dist: pyyaml>=6.0.0
37
38
  Provides-Extra: dev
38
39
  Requires-Dist: pytest>=8.0.0; extra == "dev"
39
40
  Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
40
41
  Requires-Dist: ruff>=0.3.0; extra == "dev"
41
42
  Provides-Extra: ai
42
43
  Requires-Dist: tiktoken>=0.7.0; extra == "ai"
44
+ Requires-Dist: openai>=1.0.0; extra == "ai"
45
+ Requires-Dist: anthropic>=0.18.0; extra == "ai"
46
+ Requires-Dist: google-generativeai>=0.4.0; extra == "ai"
47
+ Requires-Dist: requests>=2.31.0; extra == "ai"
43
48
  Provides-Extra: mobile
44
49
  Requires-Dist: tree-sitter-kotlin>=0.1.0; extra == "mobile"
45
50
  Requires-Dist: tree-sitter-swift>=0.6.0; extra == "mobile"
@@ -172,6 +177,27 @@ Generate a full interactive documentation site with:
172
177
  cortexcode docs --open
173
178
  ```
174
179
 
180
+ ### CodeWiki — AI-Powered Documentation Site
181
+
182
+ Generate a multi-page **CodeWiki** documentation site powered by AI (Gemini, OpenAI, Anthropic, or Ollama):
183
+
184
+ ```bash
185
+ cortexcode wiki # Generate with default AI provider
186
+ cortexcode wiki --provider google # Use Gemini
187
+ cortexcode wiki --open # Generate and open in browser
188
+ cortexcode wiki --no-modules # Skip per-module pages (faster)
189
+ ```
190
+
191
+ **Features:**
192
+ - **AI-generated pages** — Overview, Architecture, Code Flows, API Reference, Concepts Guide
193
+ - **Per-module docs** — Each Python/JS file gets AI-generated documentation
194
+ - **Mermaid diagrams** — Auto-generated flow diagrams
195
+ - **Concept mapping** — Maps technical concepts to symbols and files
196
+ - **Concept search** — Ask "how does authentication work?" and get grounded answers
197
+ - **Token tracking** — See exactly how many tokens each page used
198
+
199
+ **Output:** `.cortexcode/wiki/index.html` — Open directly or serve locally.
200
+
175
201
  ### Incremental Indexing
176
202
 
177
203
  Only re-index files that changed since last run:
@@ -225,6 +251,8 @@ npm install && npm run compile
225
251
  | `cortexcode watch` | Auto-reindex on file changes |
226
252
  | `cortexcode mcp` | Start MCP server for AI agent integration |
227
253
  | `cortexcode lsp` | Start Language Server Protocol server |
254
+ | `cortexcode wiki` | Generate CodeWiki documentation site with AI |
255
+ | `cortexcode ask` | Ask a natural language question about the codebase |
228
256
 
229
257
  ## How AI Agents Use This
230
258
 
@@ -125,6 +125,27 @@ Generate a full interactive documentation site with:
125
125
  cortexcode docs --open
126
126
  ```
127
127
 
128
+ ### CodeWiki — AI-Powered Documentation Site
129
+
130
+ Generate a multi-page **CodeWiki** documentation site powered by AI (Gemini, OpenAI, Anthropic, or Ollama):
131
+
132
+ ```bash
133
+ cortexcode wiki # Generate with default AI provider
134
+ cortexcode wiki --provider google # Use Gemini
135
+ cortexcode wiki --open # Generate and open in browser
136
+ cortexcode wiki --no-modules # Skip per-module pages (faster)
137
+ ```
138
+
139
+ **Features:**
140
+ - **AI-generated pages** — Overview, Architecture, Code Flows, API Reference, Concepts Guide
141
+ - **Per-module docs** — Each Python/JS file gets AI-generated documentation
142
+ - **Mermaid diagrams** — Auto-generated flow diagrams
143
+ - **Concept mapping** — Maps technical concepts to symbols and files
144
+ - **Concept search** — Ask "how does authentication work?" and get grounded answers
145
+ - **Token tracking** — See exactly how many tokens each page used
146
+
147
+ **Output:** `.cortexcode/wiki/index.html` — Open directly or serve locally.
148
+
128
149
  ### Incremental Indexing
129
150
 
130
151
  Only re-index files that changed since last run:
@@ -178,6 +199,8 @@ npm install && npm run compile
178
199
  | `cortexcode watch` | Auto-reindex on file changes |
179
200
  | `cortexcode mcp` | Start MCP server for AI agent integration |
180
201
  | `cortexcode lsp` | Start Language Server Protocol server |
202
+ | `cortexcode wiki` | Generate CodeWiki documentation site with AI |
203
+ | `cortexcode ask` | Ask a natural language question about the codebase |
181
204
 
182
205
  ## How AI Agents Use This
183
206
 
@@ -0,0 +1,17 @@
1
+ """Advanced analysis modules."""
2
+
3
+ from cortexcode.advanced_analysis.advanced_analysis_cycles import find_circular_dependencies
4
+ from cortexcode.advanced_analysis.advanced_analysis_docs import generate_docs_summary
5
+ from cortexcode.advanced_analysis.advanced_analysis_duplicates import find_duplicates
6
+ from cortexcode.advanced_analysis.advanced_analysis_endpoints import find_api_endpoints
7
+ from cortexcode.advanced_analysis.advanced_analysis_search import search_symbols_by_semantics
8
+ from cortexcode.advanced_analysis.advanced_analysis_security import scan_security_issues
9
+
10
+ __all__ = [
11
+ "find_circular_dependencies",
12
+ "generate_docs_summary",
13
+ "find_duplicates",
14
+ "find_api_endpoints",
15
+ "search_symbols_by_semantics",
16
+ "scan_security_issues",
17
+ ]
@@ -0,0 +1,19 @@
1
+ """Advanced code analysis — duplication, security, circular deps, API endpoints, doc generation."""
2
+
3
+ from cortexcode.advanced_analysis_cycles import detect_circular_deps
4
+ from cortexcode.advanced_analysis_docs import generate_api_docs
5
+ from cortexcode.advanced_analysis_duplicates import detect_duplicates
6
+ from cortexcode.advanced_analysis_endpoints import extract_endpoints
7
+ from cortexcode.advanced_analysis_search import fuzzy_search, regex_search
8
+ from cortexcode.advanced_analysis_security import security_scan
9
+
10
+
11
+ __all__ = [
12
+ "fuzzy_search",
13
+ "regex_search",
14
+ "detect_duplicates",
15
+ "security_scan",
16
+ "detect_circular_deps",
17
+ "extract_endpoints",
18
+ "generate_api_docs",
19
+ ]
@@ -0,0 +1,67 @@
1
+ from typing import Any
2
+
3
+
4
+ def detect_circular_deps(index: dict) -> list[dict[str, Any]]:
5
+ """Detect circular dependencies in file imports and call graph."""
6
+ results = []
7
+
8
+ file_deps = index.get("file_dependencies", {})
9
+ file_cycles = _find_cycles(file_deps)
10
+ for cycle in file_cycles:
11
+ results.append({
12
+ "type": "file_import",
13
+ "cycle": cycle,
14
+ "length": len(cycle),
15
+ "severity": "high" if len(cycle) <= 2 else "medium",
16
+ })
17
+
18
+ call_graph = index.get("call_graph", {})
19
+ symbol_cycles = _find_cycles(call_graph)
20
+ for cycle in symbol_cycles:
21
+ if len(cycle) <= 5:
22
+ results.append({
23
+ "type": "call_cycle",
24
+ "cycle": cycle,
25
+ "length": len(cycle),
26
+ "severity": "medium" if len(cycle) <= 2 else "low",
27
+ })
28
+
29
+ results.sort(key=lambda x: x["length"])
30
+ return results
31
+
32
+
33
+ def _find_cycles(graph: dict[str, list]) -> list[list[str]]:
34
+ """Find all cycles in a directed graph using DFS."""
35
+ cycles = []
36
+ visited = set()
37
+ path = []
38
+ path_set = set()
39
+
40
+ def dfs(node: str):
41
+ if node in path_set:
42
+ idx = path.index(node)
43
+ cycle = path[idx:] + [node]
44
+ min_idx = cycle.index(min(cycle[:-1]))
45
+ normalized = cycle[min_idx:-1] + cycle[:min_idx] + [cycle[min_idx]]
46
+ if normalized not in cycles:
47
+ cycles.append(normalized)
48
+ return
49
+
50
+ if node in visited:
51
+ return
52
+
53
+ visited.add(node)
54
+ path.append(node)
55
+ path_set.add(node)
56
+
57
+ for neighbor in graph.get(node, []):
58
+ if neighbor in graph:
59
+ dfs(neighbor)
60
+
61
+ path.pop()
62
+ path_set.discard(node)
63
+
64
+ for node in graph:
65
+ dfs(node)
66
+
67
+ return cycles
@@ -0,0 +1,126 @@
1
+ from pathlib import Path
2
+ from typing import Any
3
+
4
+
5
+ def generate_api_docs(index: dict, project_root: str | None = None) -> dict[str, Any]:
6
+ """Generate API documentation from function signatures and docstrings."""
7
+ files = index.get("files", {})
8
+ root = Path(project_root) if project_root else None
9
+
10
+ modules: list[dict] = []
11
+
12
+ for rel_path, file_data in files.items():
13
+ if not isinstance(file_data, dict):
14
+ continue
15
+
16
+ symbols = file_data.get("symbols", [])
17
+ if not symbols:
18
+ continue
19
+
20
+ source_lines = None
21
+ if root:
22
+ try:
23
+ source_lines = (root / rel_path).read_text(encoding="utf-8").split("\n")
24
+ except (OSError, UnicodeDecodeError):
25
+ pass
26
+
27
+ classes = []
28
+ functions = []
29
+
30
+ for sym in symbols:
31
+ name = sym.get("name", "")
32
+ sym_type = sym.get("type", "")
33
+ line = sym.get("line", 0)
34
+ params = sym.get("params", [])
35
+ doc = sym.get("doc", "")
36
+
37
+ if not doc and source_lines and line > 0:
38
+ doc = _extract_docstring(source_lines, line - 1)
39
+
40
+ entry = {
41
+ "name": name,
42
+ "type": sym_type,
43
+ "line": line,
44
+ "params": params,
45
+ "doc": doc or "",
46
+ "calls": sym.get("calls", []),
47
+ "framework": sym.get("framework"),
48
+ }
49
+
50
+ if sym_type == "class":
51
+ classes.append(entry)
52
+ elif sym_type in ("function", "method"):
53
+ functions.append(entry)
54
+
55
+ if classes or functions:
56
+ modules.append({
57
+ "file": rel_path,
58
+ "classes": classes,
59
+ "functions": functions,
60
+ "imports": file_data.get("imports", []),
61
+ })
62
+
63
+ total_documented = sum(
64
+ 1 for module in modules
65
+ for item in module["functions"] + module["classes"]
66
+ if item["doc"]
67
+ )
68
+ total_symbols = sum(
69
+ len(module["functions"]) + len(module["classes"])
70
+ for module in modules
71
+ )
72
+
73
+ return {
74
+ "modules": modules,
75
+ "total_modules": len(modules),
76
+ "total_symbols": total_symbols,
77
+ "documented": total_documented,
78
+ "undocumented": total_symbols - total_documented,
79
+ "coverage_pct": round(total_documented / max(total_symbols, 1) * 100, 1),
80
+ }
81
+
82
+
83
+ def _extract_docstring(lines: list[str], start_idx: int) -> str:
84
+ """Extract docstring from the line after a function/class definition."""
85
+ for line_index in range(start_idx + 1, min(start_idx + 5, len(lines))):
86
+ stripped = lines[line_index].strip()
87
+ if not stripped:
88
+ continue
89
+
90
+ if stripped.startswith('"""') or stripped.startswith("'''"):
91
+ quote = stripped[:3]
92
+ if stripped.endswith(quote) and len(stripped) > 6:
93
+ return stripped[3:-3].strip()
94
+ doc_lines = [stripped[3:]]
95
+ for doc_line_index in range(line_index + 1, min(line_index + 20, len(lines))):
96
+ line = lines[doc_line_index].strip()
97
+ if line.endswith(quote):
98
+ doc_lines.append(line[:-3])
99
+ return "\n".join(doc_lines).strip()
100
+ doc_lines.append(line)
101
+ break
102
+
103
+ if stripped.startswith("/**"):
104
+ doc_lines = []
105
+ for doc_line_index in range(line_index, min(line_index + 20, len(lines))):
106
+ line = lines[doc_line_index].strip()
107
+ if line.endswith("*/"):
108
+ line = line[:-2].strip()
109
+ if line.startswith("/**"):
110
+ line = line[3:].strip()
111
+ elif line.startswith("*"):
112
+ line = line[1:].strip()
113
+ if line:
114
+ doc_lines.append(line)
115
+ return "\n".join(doc_lines).strip()
116
+ if line.startswith("/**"):
117
+ line = line[3:].strip()
118
+ elif line.startswith("*"):
119
+ line = line[1:].strip()
120
+ if line:
121
+ doc_lines.append(line)
122
+ break
123
+
124
+ break
125
+
126
+ return ""
@@ -0,0 +1,158 @@
1
+ import hashlib
2
+ import re
3
+ from difflib import SequenceMatcher
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+
8
+ def detect_duplicates(index: dict, project_root: str | None = None, min_lines: int = 5) -> list[dict[str, Any]]:
9
+ """Find duplicate or very similar code blocks.
10
+
11
+ Compares function bodies by normalizing whitespace and variable names,
12
+ then computing similarity scores.
13
+ """
14
+ files = index.get("files", {})
15
+ root = Path(project_root) if project_root else None
16
+
17
+ functions: list[dict] = []
18
+ for rel_path, file_data in files.items():
19
+ if not isinstance(file_data, dict):
20
+ continue
21
+
22
+ source_lines = None
23
+ if root:
24
+ try:
25
+ source_lines = (root / rel_path).read_text(encoding="utf-8").split("\n")
26
+ except (OSError, UnicodeDecodeError):
27
+ continue
28
+
29
+ if not source_lines:
30
+ continue
31
+
32
+ for sym in file_data.get("symbols", []):
33
+ if sym.get("type") not in ("function", "method"):
34
+ continue
35
+
36
+ line = sym.get("line", 0)
37
+ if line <= 0:
38
+ continue
39
+
40
+ body = _extract_function_body(source_lines, line - 1)
41
+ if len(body.split("\n")) < min_lines:
42
+ continue
43
+
44
+ normalized = _normalize_code(body)
45
+ functions.append({
46
+ "name": sym.get("name", ""),
47
+ "file": rel_path,
48
+ "line": line,
49
+ "body": body,
50
+ "normalized": normalized,
51
+ "hash": hashlib.md5(normalized.encode()).hexdigest(),
52
+ })
53
+
54
+ hash_groups: dict[str, list] = {}
55
+ for func in functions:
56
+ func_hash = func["hash"]
57
+ if func_hash not in hash_groups:
58
+ hash_groups[func_hash] = []
59
+ hash_groups[func_hash].append(func)
60
+
61
+ duplicates = []
62
+ seen_pairs = set()
63
+
64
+ for func_hash, group in hash_groups.items():
65
+ if len(group) > 1:
66
+ duplicates.append({
67
+ "type": "exact",
68
+ "similarity": 1.0,
69
+ "functions": [
70
+ {"name": func["name"], "file": func["file"], "line": func["line"]}
71
+ for func in group
72
+ ],
73
+ "lines": len(group[0]["body"].split("\n")),
74
+ })
75
+ for func in group:
76
+ seen_pairs.add((func["file"], func["line"]))
77
+
78
+ for index_position, first_func in enumerate(functions):
79
+ if (first_func["file"], first_func["line"]) in seen_pairs:
80
+ continue
81
+ for second_func in functions[index_position + 1:]:
82
+ if (second_func["file"], second_func["line"]) in seen_pairs:
83
+ continue
84
+ if first_func["hash"] == second_func["hash"]:
85
+ continue
86
+
87
+ similarity = SequenceMatcher(None, first_func["normalized"], second_func["normalized"]).ratio()
88
+ if similarity > 0.8:
89
+ duplicates.append({
90
+ "type": "near",
91
+ "similarity": round(similarity, 3),
92
+ "functions": [
93
+ {"name": first_func["name"], "file": first_func["file"], "line": first_func["line"]},
94
+ {"name": second_func["name"], "file": second_func["file"], "line": second_func["line"]},
95
+ ],
96
+ "lines": max(
97
+ len(first_func["body"].split("\n")),
98
+ len(second_func["body"].split("\n")),
99
+ ),
100
+ })
101
+
102
+ duplicates.sort(key=lambda x: x["similarity"], reverse=True)
103
+ return duplicates
104
+
105
+
106
+ def _extract_function_body(lines: list[str], start_idx: int) -> str:
107
+ """Extract function body from source lines."""
108
+ if start_idx >= len(lines):
109
+ return ""
110
+
111
+ start_line = lines[start_idx]
112
+ start_indent = len(start_line) - len(start_line.lstrip())
113
+ indent_based = "def " in start_line or start_line.strip().endswith(":")
114
+
115
+ body = [lines[start_idx]]
116
+ brace_depth = 0
117
+
118
+ for line_index in range(start_idx + 1, min(start_idx + 300, len(lines))):
119
+ line = lines[line_index]
120
+ stripped = line.strip()
121
+
122
+ if not stripped:
123
+ body.append(line)
124
+ continue
125
+
126
+ if indent_based:
127
+ current_indent = len(line) - len(line.lstrip())
128
+ if current_indent <= start_indent and stripped and not stripped.startswith((")", "]", "}")):
129
+ break
130
+ else:
131
+ brace_depth += stripped.count("{") - stripped.count("}")
132
+ if brace_depth <= 0 and len(body) > 1:
133
+ body.append(line)
134
+ break
135
+
136
+ body.append(line)
137
+
138
+ return "\n".join(body)
139
+
140
+
141
+ def _normalize_code(code: str) -> str:
142
+ """Normalize code for comparison — remove comments, normalize whitespace, replace identifiers."""
143
+ lines = []
144
+ for line in code.split("\n"):
145
+ stripped = line.strip()
146
+ if stripped.startswith("#") or stripped.startswith("//"):
147
+ continue
148
+ stripped = re.sub(r'#.*$', '', stripped)
149
+ stripped = re.sub(r'//.*$', '', stripped)
150
+ stripped = stripped.strip()
151
+ if stripped:
152
+ lines.append(stripped)
153
+
154
+ result = "\n".join(lines)
155
+ result = re.sub(r'"[^"]*"', '"STR"', result)
156
+ result = re.sub(r"'[^']*'", "'STR'", result)
157
+ result = re.sub(r'\b\d+\b', 'NUM', result)
158
+ return result