cicada-mcp 0.1.4__tar.gz → 0.1.7__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.
Potentially problematic release.
This version of cicada-mcp might be problematic. Click here for more details.
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/PKG-INFO +30 -3
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/README.md +29 -2
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/indexer.py +60 -42
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/install.py +22 -5
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/mcp_server.py +7 -23
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/setup.py +23 -9
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada_mcp.egg-info/PKG-INFO +30 -3
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada_mcp.egg-info/SOURCES.txt +1 -1
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada_mcp.egg-info/entry_points.txt +1 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/pyproject.toml +4 -2
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_indexer_comprehensive.py +5 -5
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_install_comprehensive.py +19 -5
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_install_detection.py +29 -3
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_keyword_search.py +10 -2
- cicada_mcp-0.1.7/tests/test_mcp_e2e.py +773 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_setup.py +24 -4
- cicada_mcp-0.1.4/cicada/keyword_extractor.py +0 -364
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/LICENSE +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/__init__.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/clean.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/command_logger.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/dead_code_analyzer.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/extractors/__init__.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/extractors/base.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/extractors/call.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/extractors/dependency.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/extractors/doc.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/extractors/function.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/extractors/module.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/extractors/spec.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/find_dead_code.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/formatter.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/git_helper.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/keyword_search.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/lightweight_keyword_extractor.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/mcp_tools.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/parser.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/pr_finder.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/pr_indexer/__init__.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/pr_indexer/cli.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/pr_indexer/github_api_client.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/pr_indexer/indexer.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/pr_indexer/line_mapper.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/pr_indexer/pr_index_builder.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/__init__.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/call_site_formatter.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/function_grouper.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/hash_utils.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/index_utils.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/path_utils.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/signature_builder.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/storage.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/subprocess_runner.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/utils/text_utils.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada/version_check.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada_mcp.egg-info/dependency_links.txt +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada_mcp.egg-info/requires.txt +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/cicada_mcp.egg-info/top_level.txt +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/setup.cfg +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_acceptance.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_call_sites.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_claude_md_update.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_clean.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_command_logger.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_dead_code_analyzer.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_e2e.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_find_dead_code.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_formatter.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_git_extended_history.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_git_helper.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_github_api_client.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_gitignore_integration.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_hash_utils.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_index_utils.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_install_summary.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_intelligent_mcp_config.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_line_mapper.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_mcp_git_integration.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_mcp_logging_integration.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_mcp_pr_tools.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_mcp_server_core.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_mcp_server_formatting.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_mcp_server_pr_history.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_module_usage_categories.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_multi_editor_setup.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_parser.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_parser_comprehensive.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_path_utils.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_pr_finder.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_pr_index_builder.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_pr_indexer_integration.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_pr_indexer_unit.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_search_function.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_server_cli.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_signal_handling.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_stdout_redirect.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_storage.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_subprocess_runner.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_test_files_filter.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_version_check.py +0 -0
- {cicada_mcp-0.1.4 → cicada_mcp-0.1.7}/tests/test_with_aliases.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cicada-mcp
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: An Elixir module search MCP server
|
|
5
5
|
Author-email: wende <wende@hey.com>
|
|
6
6
|
Maintainer-email: wende <wende@hey.com>
|
|
@@ -138,7 +138,7 @@ cicada claude # or: cicada cursor, cicada vs
|
|
|
138
138
|
|
|
139
139
|
**Available commands after installation:**
|
|
140
140
|
- `cicada [claude|cursor|vs]` - One-command setup per project
|
|
141
|
-
- `cicada-
|
|
141
|
+
- `cicada-mcp` - MCP server (auto-started by editor)
|
|
142
142
|
- `cicada-index` - Re-index code with custom options (medium/large spaCy models)
|
|
143
143
|
- `cicada-index-pr` - Index pull requests for PR attribution
|
|
144
144
|
- `cicada-install` - Legacy setup (creates `.cicada/` in repo)
|
|
@@ -169,6 +169,33 @@ uvx --from git+https://github.com/wende/cicada.git@latest cicada vs
|
|
|
169
169
|
|
|
170
170
|
Once you're convinced, install permanently with `uv tool install` above!
|
|
171
171
|
|
|
172
|
+
### Quick Setup for Cursor and Claude Code
|
|
173
|
+
|
|
174
|
+
**For Cursor:**
|
|
175
|
+
|
|
176
|
+
Click the install button at the top of this README or visit:
|
|
177
|
+
[](https://cursor.com/en-US/install-mcp?name=cicada&config=eyJjb21tYW5kIjoidXZ4IC0tZnJvbSBnaXQraHR0cHM6Ly9naXRodWIuY29tL3dlbmRlL2NpY2FkYS5naXRAbGF0ZXN0IGNpY2FkYS1zZXJ2ZXIgLiJ9)
|
|
178
|
+
|
|
179
|
+
**For Claude Code:**
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Option 1: Using claude mcp add command
|
|
183
|
+
claude mcp add cicada -- uvx --from git+https://github.com/wende/cicada.git@latest cicada-mcp ./path/to/your/codebase
|
|
184
|
+
|
|
185
|
+
# Option 2: Using setup script
|
|
186
|
+
uvx --from git+https://github.com/wende/cicada.git@latest cicada claude
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Then for both editors,** run these commands in your codebase to generate keyword lookup and GitHub PR lookup databases:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# Generate keyword lookup database
|
|
193
|
+
uvx --from git+https://github.com/wende/cicada.git@latest cicada-index .
|
|
194
|
+
|
|
195
|
+
# Generate GitHub PR lookup database
|
|
196
|
+
uvx --from git+https://github.com/wende/cicada.git@latest cicada-index-pr .
|
|
197
|
+
```
|
|
198
|
+
|
|
172
199
|
---
|
|
173
200
|
|
|
174
201
|
## Quick Start
|
|
@@ -221,7 +248,7 @@ your-project/
|
|
|
221
248
|
{
|
|
222
249
|
"mcpServers": {
|
|
223
250
|
"cicada": {
|
|
224
|
-
"command": "cicada-
|
|
251
|
+
"command": "cicada-mcp",
|
|
225
252
|
"env": {
|
|
226
253
|
"CICADA_REPO_PATH": "/path/to/project",
|
|
227
254
|
"CICADA_CONFIG_DIR": "/home/user/.cicada/projects/<hash>"
|
|
@@ -94,7 +94,7 @@ cicada claude # or: cicada cursor, cicada vs
|
|
|
94
94
|
|
|
95
95
|
**Available commands after installation:**
|
|
96
96
|
- `cicada [claude|cursor|vs]` - One-command setup per project
|
|
97
|
-
- `cicada-
|
|
97
|
+
- `cicada-mcp` - MCP server (auto-started by editor)
|
|
98
98
|
- `cicada-index` - Re-index code with custom options (medium/large spaCy models)
|
|
99
99
|
- `cicada-index-pr` - Index pull requests for PR attribution
|
|
100
100
|
- `cicada-install` - Legacy setup (creates `.cicada/` in repo)
|
|
@@ -125,6 +125,33 @@ uvx --from git+https://github.com/wende/cicada.git@latest cicada vs
|
|
|
125
125
|
|
|
126
126
|
Once you're convinced, install permanently with `uv tool install` above!
|
|
127
127
|
|
|
128
|
+
### Quick Setup for Cursor and Claude Code
|
|
129
|
+
|
|
130
|
+
**For Cursor:**
|
|
131
|
+
|
|
132
|
+
Click the install button at the top of this README or visit:
|
|
133
|
+
[](https://cursor.com/en-US/install-mcp?name=cicada&config=eyJjb21tYW5kIjoidXZ4IC0tZnJvbSBnaXQraHR0cHM6Ly9naXRodWIuY29tL3dlbmRlL2NpY2FkYS5naXRAbGF0ZXN0IGNpY2FkYS1zZXJ2ZXIgLiJ9)
|
|
134
|
+
|
|
135
|
+
**For Claude Code:**
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# Option 1: Using claude mcp add command
|
|
139
|
+
claude mcp add cicada -- uvx --from git+https://github.com/wende/cicada.git@latest cicada-mcp ./path/to/your/codebase
|
|
140
|
+
|
|
141
|
+
# Option 2: Using setup script
|
|
142
|
+
uvx --from git+https://github.com/wende/cicada.git@latest cicada claude
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Then for both editors,** run these commands in your codebase to generate keyword lookup and GitHub PR lookup databases:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Generate keyword lookup database
|
|
149
|
+
uvx --from git+https://github.com/wende/cicada.git@latest cicada-index .
|
|
150
|
+
|
|
151
|
+
# Generate GitHub PR lookup database
|
|
152
|
+
uvx --from git+https://github.com/wende/cicada.git@latest cicada-index-pr .
|
|
153
|
+
```
|
|
154
|
+
|
|
128
155
|
---
|
|
129
156
|
|
|
130
157
|
## Quick Start
|
|
@@ -177,7 +204,7 @@ your-project/
|
|
|
177
204
|
{
|
|
178
205
|
"mcpServers": {
|
|
179
206
|
"cicada": {
|
|
180
|
-
"command": "cicada-
|
|
207
|
+
"command": "cicada-mcp",
|
|
181
208
|
"env": {
|
|
182
209
|
"CICADA_REPO_PATH": "/path/to/project",
|
|
183
210
|
"CICADA_CONFIG_DIR": "/home/user/.cicada/projects/<hash>"
|
|
@@ -102,7 +102,8 @@ class ElixirIndexer:
|
|
|
102
102
|
if not repo_path_obj.exists():
|
|
103
103
|
raise ValueError(f"Repository path does not exist: {repo_path_obj}")
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
if self.verbose:
|
|
106
|
+
print(f"Indexing repository: {repo_path_obj}")
|
|
106
107
|
|
|
107
108
|
# Set up signal handlers for graceful interruption
|
|
108
109
|
signal.signal(signal.SIGINT, self._handle_interrupt)
|
|
@@ -118,20 +119,22 @@ class ElixirIndexer:
|
|
|
118
119
|
)
|
|
119
120
|
|
|
120
121
|
keyword_extractor = LightweightKeywordExtractor(
|
|
121
|
-
verbose=
|
|
122
|
+
verbose=self.verbose, model_size=spacy_model
|
|
122
123
|
)
|
|
123
124
|
except Exception as e:
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
if self.verbose:
|
|
126
|
+
print(f"Warning: Could not initialize keyword extractor: {e}")
|
|
127
|
+
print("Continuing without keyword extraction...")
|
|
126
128
|
extract_keywords = False
|
|
127
129
|
|
|
128
130
|
# Find all Elixir files
|
|
129
131
|
elixir_files = self._find_elixir_files(repo_path_obj)
|
|
130
132
|
total_files = len(elixir_files)
|
|
131
133
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
if self.verbose:
|
|
135
|
+
print(f"Found {total_files} Elixir files")
|
|
136
|
+
if extract_keywords:
|
|
137
|
+
print("Keyword extraction enabled")
|
|
135
138
|
|
|
136
139
|
# Parse all files
|
|
137
140
|
all_modules = {}
|
|
@@ -222,7 +225,10 @@ class ElixirIndexer:
|
|
|
222
225
|
files_processed += 1
|
|
223
226
|
|
|
224
227
|
# Progress reporting
|
|
225
|
-
if
|
|
228
|
+
if (
|
|
229
|
+
self.verbose
|
|
230
|
+
and files_processed % self.PROGRESS_REPORT_INTERVAL == 0
|
|
231
|
+
):
|
|
226
232
|
print(f" Processed {files_processed}/{total_files} files...")
|
|
227
233
|
|
|
228
234
|
# Check for interruption after each file
|
|
@@ -230,7 +236,8 @@ class ElixirIndexer:
|
|
|
230
236
|
break
|
|
231
237
|
|
|
232
238
|
except Exception as e:
|
|
233
|
-
|
|
239
|
+
if self.verbose:
|
|
240
|
+
print(f" Skipping {file_path}: {e}")
|
|
234
241
|
# Check for interruption even after error
|
|
235
242
|
if self._check_and_report_interruption(files_processed, total_files):
|
|
236
243
|
break
|
|
@@ -258,12 +265,14 @@ class ElixirIndexer:
|
|
|
258
265
|
from cicada.utils.path_utils import ensure_gitignore_has_cicada
|
|
259
266
|
|
|
260
267
|
if ensure_gitignore_has_cicada(repo_path_obj):
|
|
261
|
-
|
|
268
|
+
if self.verbose:
|
|
269
|
+
print("✓ Added .cicada/ to .gitignore")
|
|
262
270
|
|
|
263
271
|
save_index(index, output_path_obj, create_dirs=True)
|
|
264
272
|
|
|
265
273
|
# Compute and save hashes for all PROCESSED files for future incremental updates
|
|
266
|
-
|
|
274
|
+
if self.verbose:
|
|
275
|
+
print("Computing file hashes for incremental updates...")
|
|
267
276
|
# Only hash files that were actually processed
|
|
268
277
|
processed_files = [
|
|
269
278
|
str(f.relative_to(repo_path_obj)) for f in elixir_files[:files_processed]
|
|
@@ -272,30 +281,31 @@ class ElixirIndexer:
|
|
|
272
281
|
save_file_hashes(str(output_path_obj.parent), file_hashes)
|
|
273
282
|
|
|
274
283
|
# Report completion status
|
|
275
|
-
if self.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
284
|
+
if self.verbose:
|
|
285
|
+
if self._interrupted:
|
|
286
|
+
print(f"\n✓ Partial index saved!")
|
|
287
|
+
print(
|
|
288
|
+
f" Processed: {files_processed}/{total_files} files ({files_processed/total_files*100:.1f}%)"
|
|
289
|
+
)
|
|
290
|
+
print(f" Modules: {len(all_modules)}")
|
|
291
|
+
print(f" Functions: {total_functions}")
|
|
292
|
+
print(
|
|
293
|
+
f"\n💡 Run the command again to continue indexing remaining {total_files - files_processed} file(s)"
|
|
294
|
+
)
|
|
295
|
+
else:
|
|
296
|
+
print(f"\nIndexing complete!")
|
|
297
|
+
print(f" Modules: {len(all_modules)}")
|
|
298
|
+
print(f" Functions: {total_functions}")
|
|
289
299
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
300
|
+
# Report keyword extraction failures if any
|
|
301
|
+
if extract_keywords and keyword_extraction_failures > 0:
|
|
302
|
+
print(
|
|
303
|
+
f"\n⚠️ Warning: Keyword extraction failed for {keyword_extraction_failures} module(s) or function(s)"
|
|
304
|
+
)
|
|
305
|
+
print(" Some documentation may not be indexed for keyword search.")
|
|
296
306
|
|
|
297
|
-
|
|
298
|
-
|
|
307
|
+
print(f"\nIndex saved to: {output_path_obj}")
|
|
308
|
+
print(f"Hashes saved to: {output_path_obj.parent}/hashes.json")
|
|
299
309
|
|
|
300
310
|
return index
|
|
301
311
|
|
|
@@ -351,7 +361,8 @@ class ElixirIndexer:
|
|
|
351
361
|
str(repo_path_obj), str(output_path_obj), extract_keywords, spacy_model
|
|
352
362
|
)
|
|
353
363
|
|
|
354
|
-
|
|
364
|
+
if self.verbose:
|
|
365
|
+
print(f"Performing incremental index of: {repo_path_obj}")
|
|
355
366
|
|
|
356
367
|
# Set up signal handlers for graceful interruption
|
|
357
368
|
signal.signal(signal.SIGINT, self._handle_interrupt)
|
|
@@ -364,7 +375,8 @@ class ElixirIndexer:
|
|
|
364
375
|
relative_files = [str(f.relative_to(repo_path_obj)) for f in elixir_files]
|
|
365
376
|
|
|
366
377
|
# Detect file changes
|
|
367
|
-
|
|
378
|
+
if self.verbose:
|
|
379
|
+
print("Detecting file changes...")
|
|
368
380
|
new_files, modified_files, deleted_files = detect_file_changes(
|
|
369
381
|
relative_files, existing_hashes, str(repo_path_obj)
|
|
370
382
|
)
|
|
@@ -377,10 +389,14 @@ class ElixirIndexer:
|
|
|
377
389
|
print("No changes detected. Index is up to date.")
|
|
378
390
|
return existing_index
|
|
379
391
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
392
|
+
if self.verbose:
|
|
393
|
+
print(f"Changes detected:")
|
|
394
|
+
if self.verbose:
|
|
395
|
+
print(f" New files: {len(new_files)}")
|
|
396
|
+
if self.verbose:
|
|
397
|
+
print(f" Modified files: {len(modified_files)}")
|
|
398
|
+
if self.verbose:
|
|
399
|
+
print(f" Deleted files: {len(deleted_files)}")
|
|
384
400
|
|
|
385
401
|
if files_to_process:
|
|
386
402
|
print(f"\nProcessing {len(files_to_process)} changed file(s)...")
|
|
@@ -394,7 +410,7 @@ class ElixirIndexer:
|
|
|
394
410
|
)
|
|
395
411
|
|
|
396
412
|
keyword_extractor = LightweightKeywordExtractor(
|
|
397
|
-
verbose=
|
|
413
|
+
verbose=self.verbose, model_size=spacy_model
|
|
398
414
|
)
|
|
399
415
|
except Exception as e:
|
|
400
416
|
print(f"Warning: Could not initialize keyword extractor: {e}")
|
|
@@ -502,13 +518,15 @@ class ElixirIndexer:
|
|
|
502
518
|
}
|
|
503
519
|
|
|
504
520
|
# Merge with existing index
|
|
505
|
-
|
|
521
|
+
if self.verbose:
|
|
522
|
+
print("\nMerging with existing index...")
|
|
506
523
|
merged_index = merge_indexes_incremental(
|
|
507
524
|
existing_index, new_index, deleted_files
|
|
508
525
|
)
|
|
509
526
|
|
|
510
527
|
# Update hashes for all current files
|
|
511
|
-
|
|
528
|
+
if self.verbose:
|
|
529
|
+
print("Updating file hashes...")
|
|
512
530
|
updated_hashes = dict(existing_hashes)
|
|
513
531
|
|
|
514
532
|
# Compute hashes only for files that were actually processed
|
|
@@ -251,7 +251,15 @@ def detect_installation_method():
|
|
|
251
251
|
".local/share/uv/tools" in script_path_str
|
|
252
252
|
or ".local/bin/cicada-" in script_path_str
|
|
253
253
|
):
|
|
254
|
-
# Installed via uv tool install
|
|
254
|
+
# Installed via uv tool install - check for cicada-mcp first
|
|
255
|
+
if shutil.which("cicada-mcp"):
|
|
256
|
+
return (
|
|
257
|
+
"cicada-mcp",
|
|
258
|
+
[],
|
|
259
|
+
None,
|
|
260
|
+
"uv tool install (ensure ~/.local/bin is in PATH)",
|
|
261
|
+
)
|
|
262
|
+
# Fall back to cicada-server for backwards compatibility
|
|
255
263
|
return (
|
|
256
264
|
"cicada-server",
|
|
257
265
|
[],
|
|
@@ -259,7 +267,11 @@ def detect_installation_method():
|
|
|
259
267
|
"uv tool install (ensure ~/.local/bin is in PATH)",
|
|
260
268
|
)
|
|
261
269
|
|
|
262
|
-
# Check if cicada-
|
|
270
|
+
# Check if cicada-mcp is in PATH first (from uv tool install)
|
|
271
|
+
if shutil.which("cicada-mcp"):
|
|
272
|
+
return ("cicada-mcp", [], None, "uv tool install (permanent, fast)")
|
|
273
|
+
|
|
274
|
+
# Fall back to cicada-server for backwards compatibility
|
|
263
275
|
if shutil.which("cicada-server"):
|
|
264
276
|
return ("cicada-server", [], None, "uv tool install (permanent, fast)")
|
|
265
277
|
|
|
@@ -279,8 +291,13 @@ def check_tools_in_path():
|
|
|
279
291
|
"""Check if cicada tools are in PATH."""
|
|
280
292
|
import shutil
|
|
281
293
|
|
|
282
|
-
|
|
294
|
+
# Check for cicada-mcp (new) or cicada-server (backwards compat)
|
|
295
|
+
has_mcp_server = shutil.which("cicada-mcp") or shutil.which("cicada-server")
|
|
296
|
+
tools = ["cicada-index"]
|
|
283
297
|
visible_tools = [tool for tool in tools if shutil.which(tool)]
|
|
298
|
+
if has_mcp_server:
|
|
299
|
+
visible_tools.insert(0, "cicada-mcp/cicada-server")
|
|
300
|
+
tools.insert(0, "cicada-mcp/cicada-server")
|
|
284
301
|
|
|
285
302
|
if len(visible_tools) == len(tools):
|
|
286
303
|
return "all_visible"
|
|
@@ -351,8 +368,8 @@ def create_mcp_config(repo_path, _cicada_dir, _python_bin):
|
|
|
351
368
|
print(f"✓ MCP configuration updated at {mcp_config_path}")
|
|
352
369
|
|
|
353
370
|
# Show what was configured
|
|
354
|
-
if command
|
|
355
|
-
print("✅ Using '
|
|
371
|
+
if command in ("cicada-mcp", "cicada-server"):
|
|
372
|
+
print(f"✅ Using '{command}' command (fast, no paths needed)")
|
|
356
373
|
else:
|
|
357
374
|
print(f"ℹ️ Using Python: {command}")
|
|
358
375
|
|
|
@@ -1496,43 +1496,27 @@ def _auto_setup_if_needed():
|
|
|
1496
1496
|
# Already set up, nothing to do
|
|
1497
1497
|
return
|
|
1498
1498
|
|
|
1499
|
-
# Setup needed - create storage and index
|
|
1500
|
-
print("=" * 60, file=sys.stderr)
|
|
1501
|
-
print("Cicada: First-time setup detected", file=sys.stderr)
|
|
1502
|
-
print("=" * 60, file=sys.stderr)
|
|
1503
|
-
print(file=sys.stderr)
|
|
1504
|
-
|
|
1499
|
+
# Setup needed - create storage and index (silent mode)
|
|
1505
1500
|
# Validate it's an Elixir project
|
|
1506
1501
|
if not (repo_path / "mix.exs").exists():
|
|
1507
1502
|
print(
|
|
1508
|
-
f"Error: {repo_path} does not appear to be an Elixir project",
|
|
1503
|
+
f"Error: {repo_path} does not appear to be an Elixir project (mix.exs not found)",
|
|
1509
1504
|
file=sys.stderr,
|
|
1510
1505
|
)
|
|
1511
|
-
print("(mix.exs not found)", file=sys.stderr)
|
|
1512
1506
|
sys.exit(1)
|
|
1513
1507
|
|
|
1514
1508
|
try:
|
|
1515
1509
|
# Create storage directory
|
|
1516
1510
|
storage_dir = create_storage_dir(repo_path)
|
|
1517
|
-
print(f"Repository: {repo_path}", file=sys.stderr)
|
|
1518
|
-
print(f"Storage: {storage_dir}", file=sys.stderr)
|
|
1519
|
-
print(file=sys.stderr)
|
|
1520
|
-
|
|
1521
|
-
# Index repository
|
|
1522
|
-
index_repository(repo_path)
|
|
1523
|
-
print(file=sys.stderr)
|
|
1524
1511
|
|
|
1525
|
-
#
|
|
1526
|
-
|
|
1527
|
-
print(file=sys.stderr)
|
|
1512
|
+
# Index repository (silent mode)
|
|
1513
|
+
index_repository(repo_path, verbose=False)
|
|
1528
1514
|
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
print("=" * 60, file=sys.stderr)
|
|
1532
|
-
print(file=sys.stderr)
|
|
1515
|
+
# Create config.yaml (silent mode)
|
|
1516
|
+
create_config_yaml(repo_path, storage_dir, verbose=False)
|
|
1533
1517
|
|
|
1534
1518
|
except Exception as e:
|
|
1535
|
-
print(f"
|
|
1519
|
+
print(f"Cicada auto-setup error: {e}", file=sys.stderr)
|
|
1536
1520
|
sys.exit(1)
|
|
1537
1521
|
|
|
1538
1522
|
|
|
@@ -102,9 +102,15 @@ def get_mcp_config_for_editor(
|
|
|
102
102
|
# Detect installation method
|
|
103
103
|
import shutil
|
|
104
104
|
|
|
105
|
+
# Check for cicada-mcp first (new name), fall back to cicada-server (backwards compat)
|
|
106
|
+
has_cicada_mcp = shutil.which("cicada-mcp") is not None
|
|
105
107
|
has_cicada_server = shutil.which("cicada-server") is not None
|
|
106
108
|
|
|
107
|
-
if
|
|
109
|
+
if has_cicada_mcp:
|
|
110
|
+
command = "cicada-mcp"
|
|
111
|
+
args = []
|
|
112
|
+
cwd = None
|
|
113
|
+
elif has_cicada_server:
|
|
108
114
|
command = "cicada-server"
|
|
109
115
|
args = []
|
|
110
116
|
cwd = None
|
|
@@ -157,13 +163,16 @@ def get_mcp_config_for_editor(
|
|
|
157
163
|
return config_path, config
|
|
158
164
|
|
|
159
165
|
|
|
160
|
-
def create_config_yaml(
|
|
166
|
+
def create_config_yaml(
|
|
167
|
+
repo_path: Path, storage_dir: Path, verbose: bool = True
|
|
168
|
+
) -> None:
|
|
161
169
|
"""
|
|
162
170
|
Create config.yaml in storage directory.
|
|
163
171
|
|
|
164
172
|
Args:
|
|
165
173
|
repo_path: Path to the repository
|
|
166
174
|
storage_dir: Path to the storage directory
|
|
175
|
+
verbose: Whether to print progress messages (default: True)
|
|
167
176
|
"""
|
|
168
177
|
config_path = get_config_path(repo_path)
|
|
169
178
|
index_path = get_index_path(repo_path)
|
|
@@ -178,22 +187,24 @@ storage:
|
|
|
178
187
|
with open(config_path, "w") as f:
|
|
179
188
|
f.write(config_content)
|
|
180
189
|
|
|
181
|
-
|
|
190
|
+
if verbose:
|
|
191
|
+
print(f"✓ Config file created at {config_path}")
|
|
182
192
|
|
|
183
193
|
|
|
184
|
-
def index_repository(repo_path: Path) -> None:
|
|
194
|
+
def index_repository(repo_path: Path, verbose: bool = True) -> None:
|
|
185
195
|
"""
|
|
186
196
|
Index the repository with keyword extraction enabled.
|
|
187
197
|
|
|
188
198
|
Args:
|
|
189
199
|
repo_path: Path to the repository
|
|
200
|
+
verbose: Whether to print progress messages (default: True)
|
|
190
201
|
|
|
191
202
|
Raises:
|
|
192
203
|
Exception: If indexing fails
|
|
193
204
|
"""
|
|
194
205
|
try:
|
|
195
206
|
index_path = get_index_path(repo_path)
|
|
196
|
-
indexer = ElixirIndexer(verbose=
|
|
207
|
+
indexer = ElixirIndexer(verbose=verbose)
|
|
197
208
|
|
|
198
209
|
# Index with keyword extraction enabled by default
|
|
199
210
|
# Note: Using 'small' model for compatibility with uvx
|
|
@@ -205,10 +216,12 @@ def index_repository(repo_path: Path) -> None:
|
|
|
205
216
|
spacy_model="small",
|
|
206
217
|
)
|
|
207
218
|
|
|
208
|
-
|
|
219
|
+
if verbose:
|
|
220
|
+
print(f"✓ Repository indexed at {index_path}")
|
|
209
221
|
except Exception as e:
|
|
210
|
-
|
|
211
|
-
|
|
222
|
+
if verbose:
|
|
223
|
+
print(f"Error: Failed to index repository: {e}")
|
|
224
|
+
print("Please check that the repository contains valid Elixir files.")
|
|
212
225
|
raise
|
|
213
226
|
|
|
214
227
|
|
|
@@ -275,7 +288,8 @@ def setup(editor: EditorType, repo_path: Path | None = None) -> None:
|
|
|
275
288
|
import shutil
|
|
276
289
|
from cicada import __version__
|
|
277
290
|
|
|
278
|
-
|
|
291
|
+
# Check for either cicada-mcp or cicada-server (backwards compat)
|
|
292
|
+
if not (shutil.which("cicada-mcp") or shutil.which("cicada-server")):
|
|
279
293
|
print("💡 Tip: For best experience, install Cicada permanently:")
|
|
280
294
|
print(
|
|
281
295
|
f" uv tool install git+https://github.com/wende/cicada.git@v{__version__}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cicada-mcp
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: An Elixir module search MCP server
|
|
5
5
|
Author-email: wende <wende@hey.com>
|
|
6
6
|
Maintainer-email: wende <wende@hey.com>
|
|
@@ -138,7 +138,7 @@ cicada claude # or: cicada cursor, cicada vs
|
|
|
138
138
|
|
|
139
139
|
**Available commands after installation:**
|
|
140
140
|
- `cicada [claude|cursor|vs]` - One-command setup per project
|
|
141
|
-
- `cicada-
|
|
141
|
+
- `cicada-mcp` - MCP server (auto-started by editor)
|
|
142
142
|
- `cicada-index` - Re-index code with custom options (medium/large spaCy models)
|
|
143
143
|
- `cicada-index-pr` - Index pull requests for PR attribution
|
|
144
144
|
- `cicada-install` - Legacy setup (creates `.cicada/` in repo)
|
|
@@ -169,6 +169,33 @@ uvx --from git+https://github.com/wende/cicada.git@latest cicada vs
|
|
|
169
169
|
|
|
170
170
|
Once you're convinced, install permanently with `uv tool install` above!
|
|
171
171
|
|
|
172
|
+
### Quick Setup for Cursor and Claude Code
|
|
173
|
+
|
|
174
|
+
**For Cursor:**
|
|
175
|
+
|
|
176
|
+
Click the install button at the top of this README or visit:
|
|
177
|
+
[](https://cursor.com/en-US/install-mcp?name=cicada&config=eyJjb21tYW5kIjoidXZ4IC0tZnJvbSBnaXQraHR0cHM6Ly9naXRodWIuY29tL3dlbmRlL2NpY2FkYS5naXRAbGF0ZXN0IGNpY2FkYS1zZXJ2ZXIgLiJ9)
|
|
178
|
+
|
|
179
|
+
**For Claude Code:**
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Option 1: Using claude mcp add command
|
|
183
|
+
claude mcp add cicada -- uvx --from git+https://github.com/wende/cicada.git@latest cicada-mcp ./path/to/your/codebase
|
|
184
|
+
|
|
185
|
+
# Option 2: Using setup script
|
|
186
|
+
uvx --from git+https://github.com/wende/cicada.git@latest cicada claude
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Then for both editors,** run these commands in your codebase to generate keyword lookup and GitHub PR lookup databases:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# Generate keyword lookup database
|
|
193
|
+
uvx --from git+https://github.com/wende/cicada.git@latest cicada-index .
|
|
194
|
+
|
|
195
|
+
# Generate GitHub PR lookup database
|
|
196
|
+
uvx --from git+https://github.com/wende/cicada.git@latest cicada-index-pr .
|
|
197
|
+
```
|
|
198
|
+
|
|
172
199
|
---
|
|
173
200
|
|
|
174
201
|
## Quick Start
|
|
@@ -221,7 +248,7 @@ your-project/
|
|
|
221
248
|
{
|
|
222
249
|
"mcpServers": {
|
|
223
250
|
"cicada": {
|
|
224
|
-
"command": "cicada-
|
|
251
|
+
"command": "cicada-mcp",
|
|
225
252
|
"env": {
|
|
226
253
|
"CICADA_REPO_PATH": "/path/to/project",
|
|
227
254
|
"CICADA_CONFIG_DIR": "/home/user/.cicada/projects/<hash>"
|
|
@@ -10,7 +10,6 @@ cicada/formatter.py
|
|
|
10
10
|
cicada/git_helper.py
|
|
11
11
|
cicada/indexer.py
|
|
12
12
|
cicada/install.py
|
|
13
|
-
cicada/keyword_extractor.py
|
|
14
13
|
cicada/keyword_search.py
|
|
15
14
|
cicada/lightweight_keyword_extractor.py
|
|
16
15
|
cicada/mcp_server.py
|
|
@@ -71,6 +70,7 @@ tests/test_install_summary.py
|
|
|
71
70
|
tests/test_intelligent_mcp_config.py
|
|
72
71
|
tests/test_keyword_search.py
|
|
73
72
|
tests/test_line_mapper.py
|
|
73
|
+
tests/test_mcp_e2e.py
|
|
74
74
|
tests/test_mcp_git_integration.py
|
|
75
75
|
tests/test_mcp_logging_integration.py
|
|
76
76
|
tests/test_mcp_pr_tools.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cicada-mcp"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.7"
|
|
8
8
|
description = "An Elixir module search MCP server"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -59,7 +59,8 @@ Changelog = "https://github.com/wende/cicada/blob/main/CHANGELOG.md"
|
|
|
59
59
|
Documentation = "https://github.com/wende/cicada#readme"
|
|
60
60
|
|
|
61
61
|
[project.scripts]
|
|
62
|
-
cicada-
|
|
62
|
+
cicada-mcp = "cicada.mcp_server:main"
|
|
63
|
+
cicada-server = "cicada.mcp_server:main" # Backwards compatibility
|
|
63
64
|
cicada = "cicada.setup:main"
|
|
64
65
|
cicada-install = "cicada.install:main"
|
|
65
66
|
cicada-clean = "cicada.clean:main"
|
|
@@ -84,6 +85,7 @@ asyncio_mode = "auto"
|
|
|
84
85
|
asyncio_default_fixture_loop_scope = "function"
|
|
85
86
|
filterwarnings = [
|
|
86
87
|
"ignore:Importing 'parser.split_arg_string' is deprecated.*:DeprecationWarning",
|
|
88
|
+
"ignore:coroutine 'async_main' was never awaited:RuntimeWarning",
|
|
87
89
|
]
|
|
88
90
|
|
|
89
91
|
[tool.coverage.run]
|
|
@@ -421,7 +421,7 @@ class TestElixirIndexerKeywordExtraction:
|
|
|
421
421
|
MockKeywordExtractor,
|
|
422
422
|
)
|
|
423
423
|
|
|
424
|
-
indexer = ElixirIndexer()
|
|
424
|
+
indexer = ElixirIndexer(verbose=True)
|
|
425
425
|
|
|
426
426
|
# Create a test file with documentation
|
|
427
427
|
test_file = tmp_path / "test.ex"
|
|
@@ -572,7 +572,7 @@ class TestElixirIndexerProgressReporting:
|
|
|
572
572
|
|
|
573
573
|
def test_progress_reporting_multiple_files(self, tmp_path, capsys):
|
|
574
574
|
"""Test that progress is reported every 10 files"""
|
|
575
|
-
indexer = ElixirIndexer()
|
|
575
|
+
indexer = ElixirIndexer(verbose=True)
|
|
576
576
|
|
|
577
577
|
# Create 15 test files
|
|
578
578
|
for i in range(15):
|
|
@@ -688,7 +688,7 @@ class TestElixirIndexerGitignoreIntegration:
|
|
|
688
688
|
|
|
689
689
|
def test_first_run_creates_gitignore_entry(self, tmp_path, capsys):
|
|
690
690
|
"""Test that first run adds .cicada/ to .gitignore"""
|
|
691
|
-
indexer = ElixirIndexer()
|
|
691
|
+
indexer = ElixirIndexer(verbose=True)
|
|
692
692
|
|
|
693
693
|
# Create a .gitignore file
|
|
694
694
|
gitignore = tmp_path / ".gitignore"
|
|
@@ -743,7 +743,7 @@ class TestElixirIndexerSignalHandling:
|
|
|
743
743
|
self, tmp_path, monkeypatch, capsys
|
|
744
744
|
):
|
|
745
745
|
"""Test that interrupted indexing saves partial progress"""
|
|
746
|
-
indexer = ElixirIndexer()
|
|
746
|
+
indexer = ElixirIndexer(verbose=True)
|
|
747
747
|
|
|
748
748
|
# Create multiple test files
|
|
749
749
|
for i in range(5):
|
|
@@ -973,7 +973,7 @@ class TestElixirIndexerAdditionalEdgeCases:
|
|
|
973
973
|
BrokenKeywordExtractor,
|
|
974
974
|
)
|
|
975
975
|
|
|
976
|
-
indexer = ElixirIndexer()
|
|
976
|
+
indexer = ElixirIndexer(verbose=True)
|
|
977
977
|
|
|
978
978
|
test_file = tmp_path / "test.ex"
|
|
979
979
|
test_file.write_text(
|