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.
- {cortexcode-0.5.0 → cortexcode-0.6.0}/PKG-INFO +29 -1
- {cortexcode-0.5.0 → cortexcode-0.6.0}/README.md +23 -0
- cortexcode-0.6.0/cortexcode/advanced_analysis/__init__.py +17 -0
- cortexcode-0.6.0/cortexcode/advanced_analysis/advanced_analysis.py +19 -0
- cortexcode-0.6.0/cortexcode/advanced_analysis/advanced_analysis_cycles.py +67 -0
- cortexcode-0.6.0/cortexcode/advanced_analysis/advanced_analysis_docs.py +126 -0
- cortexcode-0.6.0/cortexcode/advanced_analysis/advanced_analysis_duplicates.py +158 -0
- cortexcode-0.6.0/cortexcode/advanced_analysis/advanced_analysis_endpoints.py +205 -0
- cortexcode-0.6.0/cortexcode/advanced_analysis/advanced_analysis_search.py +85 -0
- cortexcode-0.6.0/cortexcode/advanced_analysis/advanced_analysis_security.py +119 -0
- cortexcode-0.6.0/cortexcode/advanced_analysis.py +19 -0
- cortexcode-0.6.0/cortexcode/ai_docs/__init__.py +15 -0
- cortexcode-0.6.0/cortexcode/ai_docs/config.py +130 -0
- cortexcode-0.6.0/cortexcode/ai_docs/doc_cache.py +40 -0
- cortexcode-0.6.0/cortexcode/ai_docs/doc_generator.py +193 -0
- cortexcode-0.6.0/cortexcode/ai_docs/doc_lookup.py +27 -0
- cortexcode-0.6.0/cortexcode/ai_docs/doc_models.py +12 -0
- cortexcode-0.6.0/cortexcode/ai_docs/explainer.py +237 -0
- cortexcode-0.6.0/cortexcode/ai_docs/llm_client.py +289 -0
- cortexcode-0.6.0/cortexcode/ai_docs/page_generator.py +357 -0
- cortexcode-0.6.0/cortexcode/ai_docs/prompts.py +271 -0
- cortexcode-0.6.0/cortexcode/ai_docs/report_runner.py +251 -0
- cortexcode-0.6.0/cortexcode/analysis/__init__.py +11 -0
- cortexcode-0.6.0/cortexcode/analysis/analysis_complexity.py +147 -0
- cortexcode-0.6.0/cortexcode/analysis/analysis_dead_code.py +83 -0
- cortexcode-0.6.0/cortexcode/analysis/analysis_impact.py +62 -0
- cortexcode-0.6.0/cortexcode/analysis.py +10 -0
- cortexcode-0.6.0/cortexcode/cli/__init__.py +63 -0
- cortexcode-0.6.0/cortexcode/cli/cli_ai_docs.py +28 -0
- cortexcode-0.6.0/cortexcode/cli/cli_complexity.py +62 -0
- cortexcode-0.6.0/cortexcode/cli/cli_config.py +68 -0
- cortexcode-0.6.0/cortexcode/cli/cli_context.py +34 -0
- cortexcode-0.6.0/cortexcode/cli/cli_dashboard.py +31 -0
- cortexcode-0.6.0/cortexcode/cli/cli_dead_code.py +41 -0
- cortexcode-0.6.0/cortexcode/cli/cli_diagrams.py +26 -0
- cortexcode-0.6.0/cortexcode/cli/cli_diff.py +57 -0
- cortexcode-0.6.0/cortexcode/cli/cli_docs.py +37 -0
- cortexcode-0.6.0/cortexcode/cli/cli_explain.py +23 -0
- cortexcode-0.6.0/cortexcode/cli/cli_find.py +40 -0
- cortexcode-0.6.0/cortexcode/cli/cli_impact.py +50 -0
- cortexcode-0.6.0/cortexcode/cli/cli_index.py +110 -0
- cortexcode-0.6.0/cortexcode/cli/cli_report.py +19 -0
- cortexcode-0.6.0/cortexcode/cli/cli_scan.py +34 -0
- cortexcode-0.6.0/cortexcode/cli/cli_search.py +61 -0
- cortexcode-0.6.0/cortexcode/cli/cli_servers.py +10 -0
- cortexcode-0.6.0/cortexcode/cli/cli_stats.py +20 -0
- cortexcode-0.6.0/cortexcode/cli/cli_support.py +30 -0
- cortexcode-0.6.0/cortexcode/cli/cli_watch.py +10 -0
- cortexcode-0.6.0/cortexcode/cli/cli_wiki.py +161 -0
- cortexcode-0.6.0/cortexcode/cli/cli_workspace.py +103 -0
- cortexcode-0.6.0/cortexcode/config.py +159 -0
- cortexcode-0.6.0/cortexcode/context/__init__.py +13 -0
- cortexcode-0.6.0/cortexcode/context/context_format.py +17 -0
- cortexcode-0.5.0/cortexcode/context.py → cortexcode-0.6.0/cortexcode/context/context_query.py +43 -146
- cortexcode-0.6.0/cortexcode/context/context_tokens.py +68 -0
- cortexcode-0.6.0/cortexcode/context.py +12 -0
- cortexcode-0.6.0/cortexcode/diagrams/__init__.py +26 -0
- cortexcode-0.6.0/cortexcode/diagrams/architecture.py +60 -0
- cortexcode-0.6.0/cortexcode/diagrams/call_graph.py +71 -0
- cortexcode-0.6.0/cortexcode/diagrams/class_diagram.py +63 -0
- cortexcode-0.6.0/cortexcode/diagrams/dependencies.py +35 -0
- cortexcode-0.6.0/cortexcode/diagrams/directory_tree.py +28 -0
- cortexcode-0.6.0/cortexcode/diagrams/entities.py +83 -0
- cortexcode-0.6.0/cortexcode/diagrams/file_tree.py +33 -0
- cortexcode-0.6.0/cortexcode/diagrams/imports.py +44 -0
- cortexcode-0.6.0/cortexcode/diagrams/save.py +53 -0
- cortexcode-0.6.0/cortexcode/diagrams/sequence.py +47 -0
- cortexcode-0.6.0/cortexcode/diagrams/state.py +22 -0
- cortexcode-0.6.0/cortexcode/diagrams/utils.py +24 -0
- cortexcode-0.6.0/cortexcode/docs/__init__.py +99 -0
- cortexcode-0.6.0/cortexcode/docs/diagrams.py +105 -0
- cortexcode-0.6.0/cortexcode/docs/generator.py +96 -0
- cortexcode-0.6.0/cortexcode/docs/javascript.py +6 -0
- cortexcode-0.5.0/cortexcode/docs/javascript.py → cortexcode-0.6.0/cortexcode/docs/javascript_sections.py +24 -2
- cortexcode-0.6.0/cortexcode/indexer.py +213 -0
- cortexcode-0.6.0/cortexcode/indexing/__init__.py +58 -0
- cortexcode-0.6.0/cortexcode/indexing/build.py +51 -0
- cortexcode-0.6.0/cortexcode/indexing/calls.py +25 -0
- cortexcode-0.6.0/cortexcode/indexing/config.py +24 -0
- cortexcode-0.6.0/cortexcode/indexing/defaults.py +21 -0
- cortexcode-0.6.0/cortexcode/indexing/dispatch.py +42 -0
- cortexcode-0.6.0/cortexcode/indexing/entities.py +87 -0
- cortexcode-0.6.0/cortexcode/indexing/extensions.py +2 -0
- cortexcode-0.6.0/cortexcode/indexing/extractor_mixin.py +355 -0
- cortexcode-0.6.0/cortexcode/indexing/extractors/__init__.py +9 -0
- cortexcode-0.6.0/cortexcode/indexing/extractors/csharp.py +90 -0
- cortexcode-0.6.0/cortexcode/indexing/extractors/dart.py +165 -0
- cortexcode-0.6.0/cortexcode/indexing/extractors/generic.py +139 -0
- cortexcode-0.6.0/cortexcode/indexing/extractors/java.py +91 -0
- cortexcode-0.6.0/cortexcode/indexing/extractors/javascript.py +194 -0
- cortexcode-0.6.0/cortexcode/indexing/extractors/kotlin.py +92 -0
- cortexcode-0.6.0/cortexcode/indexing/extractors/swift.py +112 -0
- cortexcode-0.6.0/cortexcode/indexing/filtering.py +74 -0
- cortexcode-0.6.0/cortexcode/indexing/frameworks.py +236 -0
- cortexcode-0.6.0/cortexcode/indexing/gitignore.py +65 -0
- cortexcode-0.6.0/cortexcode/indexing/imports_exports.py +172 -0
- cortexcode-0.6.0/cortexcode/indexing/incremental.py +57 -0
- cortexcode-0.6.0/cortexcode/indexing/languages.py +48 -0
- cortexcode-0.6.0/cortexcode/indexing/metadata.py +100 -0
- cortexcode-0.6.0/cortexcode/indexing/nodes.py +8 -0
- cortexcode-0.6.0/cortexcode/indexing/output.py +19 -0
- cortexcode-0.6.0/cortexcode/indexing/params.py +27 -0
- cortexcode-0.6.0/cortexcode/indexing/parsers.py +20 -0
- cortexcode-0.6.0/cortexcode/indexing/pipeline.py +104 -0
- cortexcode-0.6.0/cortexcode/indexing/profile.py +271 -0
- cortexcode-0.6.0/cortexcode/indexing/resolution.py +191 -0
- cortexcode-0.6.0/cortexcode/indexing/routes.py +120 -0
- cortexcode-0.6.0/cortexcode/indexing/session.py +21 -0
- cortexcode-0.6.0/cortexcode/indexing/storage.py +13 -0
- cortexcode-0.6.0/cortexcode/indexing/walk.py +32 -0
- cortexcode-0.6.0/cortexcode/knowledge/__init__.py +21 -0
- cortexcode-0.6.0/cortexcode/knowledge/build.py +119 -0
- cortexcode-0.6.0/cortexcode/knowledge/citations.py +46 -0
- cortexcode-0.6.0/cortexcode/knowledge/concepts.py +241 -0
- cortexcode-0.6.0/cortexcode/knowledge/models.py +111 -0
- cortexcode-0.6.0/cortexcode/knowledge/snippets.py +99 -0
- cortexcode-0.6.0/cortexcode/knowledge/usage.py +86 -0
- cortexcode-0.6.0/cortexcode/main.py +486 -0
- cortexcode-0.6.0/cortexcode/mcp/__init__.py +20 -0
- cortexcode-0.6.0/cortexcode/mcp/mcp_protocol.py +11 -0
- cortexcode-0.6.0/cortexcode/mcp/mcp_registry.py +206 -0
- cortexcode-0.6.0/cortexcode/mcp/mcp_server.py +78 -0
- cortexcode-0.6.0/cortexcode/mcp/mcp_tool_handlers.py +245 -0
- cortexcode-0.6.0/cortexcode/mcp/mcp_transport.py +61 -0
- cortexcode-0.6.0/cortexcode/performance/__init__.py +28 -0
- cortexcode-0.6.0/cortexcode/performance/performance_config.py +176 -0
- cortexcode-0.6.0/cortexcode/performance/performance_index_storage.py +67 -0
- cortexcode-0.6.0/cortexcode/performance/performance_preview.py +60 -0
- cortexcode-0.6.0/cortexcode/performance.py +32 -0
- {cortexcode-0.5.0/cortexcode/docs → cortexcode-0.6.0/cortexcode/reports}/__init__.py +7 -9
- cortexcode-0.6.0/cortexcode/reports/html/__init__.py +4 -0
- cortexcode-0.5.0/cortexcode/docs/generator.py → cortexcode-0.6.0/cortexcode/reports/html/dashboard.py +64 -321
- cortexcode-0.6.0/cortexcode/reports/html/dashboard_fragments.py +134 -0
- cortexcode-0.6.0/cortexcode/reports/html/view_model.py +126 -0
- cortexcode-0.6.0/cortexcode/reports/markdown/__init__.py +15 -0
- cortexcode-0.6.0/cortexcode/reports/markdown/api.py +58 -0
- cortexcode-0.6.0/cortexcode/reports/markdown/flows.py +57 -0
- cortexcode-0.6.0/cortexcode/reports/markdown/insights.py +81 -0
- cortexcode-0.6.0/cortexcode/reports/markdown/readme.py +74 -0
- cortexcode-0.6.0/cortexcode/reports/markdown/structure.py +19 -0
- cortexcode-0.6.0/cortexcode/reports/markdown/tech.py +95 -0
- cortexcode-0.6.0/cortexcode/reports/site/__init__.py +5 -0
- cortexcode-0.6.0/cortexcode/reports/site/generator.py +570 -0
- cortexcode-0.6.0/cortexcode/terminal/__init__.py +30 -0
- cortexcode-0.6.0/cortexcode/terminal/analysis.py +63 -0
- cortexcode-0.6.0/cortexcode/terminal/completion.py +39 -0
- cortexcode-0.6.0/cortexcode/terminal/headers.py +36 -0
- cortexcode-0.6.0/cortexcode/terminal/prompts.py +11 -0
- cortexcode-0.6.0/cortexcode/terminal/reports.py +209 -0
- cortexcode-0.6.0/cortexcode/terminal/stats.py +49 -0
- {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode.egg-info/PKG-INFO +29 -1
- cortexcode-0.6.0/cortexcode.egg-info/SOURCES.txt +169 -0
- cortexcode-0.6.0/cortexcode.egg-info/entry_points.txt +2 -0
- {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode.egg-info/requires.txt +5 -0
- {cortexcode-0.5.0 → cortexcode-0.6.0}/pyproject.toml +7 -2
- cortexcode-0.5.0/cortexcode/advanced_analysis.py +0 -816
- cortexcode-0.5.0/cortexcode/analysis.py +0 -331
- cortexcode-0.5.0/cortexcode/cli.py +0 -845
- cortexcode-0.5.0/cortexcode/indexer.py +0 -1884
- cortexcode-0.5.0/cortexcode/mcp_server.py +0 -597
- cortexcode-0.5.0/cortexcode.egg-info/SOURCES.txt +0 -30
- cortexcode-0.5.0/cortexcode.egg-info/entry_points.txt +0 -2
- {cortexcode-0.5.0 → cortexcode-0.6.0}/LICENSE +0 -0
- {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/__init__.py +0 -0
- {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/dashboard.py +0 -0
- {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/docs/html_generators.py +0 -0
- {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/docs/templates.py +0 -0
- {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/docs.py +0 -0
- {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/git_diff.py +0 -0
- {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/lsp_server.py +0 -0
- {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/plugins.py +0 -0
- {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/semantic_search.py +0 -0
- {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/vuln_scan.py +0 -0
- {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/watcher.py +0 -0
- {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode/workspace.py +0 -0
- {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode.egg-info/dependency_links.txt +0 -0
- {cortexcode-0.5.0 → cortexcode-0.6.0}/cortexcode.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|