cicada-mcp 0.1.7__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cicada/ascii_art.py +60 -0
- cicada/clean.py +195 -60
- cicada/cli.py +757 -0
- cicada/colors.py +27 -0
- cicada/command_logger.py +14 -16
- cicada/dead_code_analyzer.py +12 -19
- cicada/extractors/__init__.py +6 -6
- cicada/extractors/base.py +3 -3
- cicada/extractors/call.py +11 -15
- cicada/extractors/dependency.py +39 -51
- cicada/extractors/doc.py +8 -9
- cicada/extractors/function.py +12 -24
- cicada/extractors/module.py +11 -15
- cicada/extractors/spec.py +8 -12
- cicada/find_dead_code.py +15 -39
- cicada/formatter.py +37 -91
- cicada/git_helper.py +22 -34
- cicada/indexer.py +122 -107
- cicada/interactive_setup.py +490 -0
- cicada/keybert_extractor.py +286 -0
- cicada/keyword_search.py +22 -30
- cicada/keyword_test.py +127 -0
- cicada/lightweight_keyword_extractor.py +5 -13
- cicada/mcp_entry.py +683 -0
- cicada/mcp_server.py +103 -209
- cicada/parser.py +9 -9
- cicada/pr_finder.py +15 -19
- cicada/pr_indexer/__init__.py +3 -3
- cicada/pr_indexer/cli.py +4 -9
- cicada/pr_indexer/github_api_client.py +22 -37
- cicada/pr_indexer/indexer.py +17 -29
- cicada/pr_indexer/line_mapper.py +8 -12
- cicada/pr_indexer/pr_index_builder.py +22 -34
- cicada/setup.py +189 -87
- cicada/utils/__init__.py +9 -9
- cicada/utils/call_site_formatter.py +4 -6
- cicada/utils/function_grouper.py +4 -4
- cicada/utils/hash_utils.py +12 -15
- cicada/utils/index_utils.py +15 -15
- cicada/utils/path_utils.py +24 -29
- cicada/utils/signature_builder.py +3 -3
- cicada/utils/subprocess_runner.py +17 -19
- cicada/utils/text_utils.py +1 -2
- cicada/version_check.py +2 -5
- {cicada_mcp-0.1.7.dist-info → cicada_mcp-0.2.0.dist-info}/METADATA +144 -55
- cicada_mcp-0.2.0.dist-info/RECORD +53 -0
- cicada_mcp-0.2.0.dist-info/entry_points.txt +4 -0
- cicada/install.py +0 -741
- cicada_mcp-0.1.7.dist-info/RECORD +0 -47
- cicada_mcp-0.1.7.dist-info/entry_points.txt +0 -9
- {cicada_mcp-0.1.7.dist-info → cicada_mcp-0.2.0.dist-info}/WHEEL +0 -0
- {cicada_mcp-0.1.7.dist-info → cicada_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {cicada_mcp-0.1.7.dist-info → cicada_mcp-0.2.0.dist-info}/top_level.txt +0 -0
cicada/extractors/module.py
CHANGED
|
@@ -31,9 +31,7 @@ def _find_modules_recursive(node, source_code: bytes, modules: list):
|
|
|
31
31
|
|
|
32
32
|
# Check if this is a defmodule call
|
|
33
33
|
if target and arguments:
|
|
34
|
-
target_text = source_code[target.start_byte : target.end_byte].decode(
|
|
35
|
-
"utf-8"
|
|
36
|
-
)
|
|
34
|
+
target_text = source_code[target.start_byte : target.end_byte].decode("utf-8")
|
|
37
35
|
|
|
38
36
|
if target_text == "defmodule":
|
|
39
37
|
# Extract module name from arguments
|
|
@@ -41,9 +39,9 @@ def _find_modules_recursive(node, source_code: bytes, modules: list):
|
|
|
41
39
|
|
|
42
40
|
for arg_child in arguments.children:
|
|
43
41
|
if arg_child.type == "alias":
|
|
44
|
-
module_name = source_code[
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
module_name = source_code[arg_child.start_byte : arg_child.end_byte].decode(
|
|
43
|
+
"utf-8"
|
|
44
|
+
)
|
|
47
45
|
break
|
|
48
46
|
|
|
49
47
|
if module_name and do_block:
|
|
@@ -84,17 +82,15 @@ def _find_moduledoc_recursive(node, source_code: bytes) -> str | None:
|
|
|
84
82
|
# Check if this is a moduledoc attribute
|
|
85
83
|
for call_child in operand.children:
|
|
86
84
|
if call_child.type == "identifier":
|
|
87
|
-
attr_name = source_code[
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
attr_name = source_code[call_child.start_byte : call_child.end_byte].decode(
|
|
86
|
+
"utf-8"
|
|
87
|
+
)
|
|
90
88
|
|
|
91
89
|
if attr_name == "moduledoc":
|
|
92
90
|
# Extract the documentation string from the arguments
|
|
93
91
|
for arg_child in operand.children:
|
|
94
92
|
if arg_child.type == "arguments":
|
|
95
|
-
doc_string = extract_string_from_arguments(
|
|
96
|
-
arg_child, source_code
|
|
97
|
-
)
|
|
93
|
+
doc_string = extract_string_from_arguments(arg_child, source_code)
|
|
98
94
|
if doc_string:
|
|
99
95
|
return doc_string
|
|
100
96
|
|
|
@@ -106,9 +102,9 @@ def _find_moduledoc_recursive(node, source_code: bytes) -> str | None:
|
|
|
106
102
|
is_defmodule = False
|
|
107
103
|
for call_child in child.children:
|
|
108
104
|
if call_child.type == "identifier":
|
|
109
|
-
target_text = source_code[
|
|
110
|
-
|
|
111
|
-
|
|
105
|
+
target_text = source_code[call_child.start_byte : call_child.end_byte].decode(
|
|
106
|
+
"utf-8"
|
|
107
|
+
)
|
|
112
108
|
if target_text == "defmodule":
|
|
113
109
|
is_defmodule = True
|
|
114
110
|
break
|
cicada/extractors/spec.py
CHANGED
|
@@ -27,9 +27,9 @@ def _find_specs_recursive(node, source_code: bytes, specs: dict):
|
|
|
27
27
|
# Check if this is a spec attribute
|
|
28
28
|
for call_child in operand.children:
|
|
29
29
|
if call_child.type == "identifier":
|
|
30
|
-
attr_name = source_code[
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
attr_name = source_code[call_child.start_byte : call_child.end_byte].decode(
|
|
31
|
+
"utf-8"
|
|
32
|
+
)
|
|
33
33
|
|
|
34
34
|
if attr_name == "spec":
|
|
35
35
|
# Extract the spec definition
|
|
@@ -45,9 +45,9 @@ def _find_specs_recursive(node, source_code: bytes, specs: dict):
|
|
|
45
45
|
is_defmodule_or_def = False
|
|
46
46
|
for call_child in child.children:
|
|
47
47
|
if call_child.type == "identifier":
|
|
48
|
-
target_text = source_code[
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
target_text = source_code[call_child.start_byte : call_child.end_byte].decode(
|
|
49
|
+
"utf-8"
|
|
50
|
+
)
|
|
51
51
|
if target_text in ["defmodule", "def", "defp"]:
|
|
52
52
|
is_defmodule_or_def = True
|
|
53
53
|
break
|
|
@@ -95,9 +95,7 @@ def _parse_spec(spec_node, source_code: bytes) -> dict | None:
|
|
|
95
95
|
fc_child.start_byte : fc_child.end_byte
|
|
96
96
|
].decode("utf-8")
|
|
97
97
|
elif fc_child.type == "arguments":
|
|
98
|
-
param_types = _extract_param_types(
|
|
99
|
-
fc_child, source_code
|
|
100
|
-
)
|
|
98
|
+
param_types = _extract_param_types(fc_child, source_code)
|
|
101
99
|
|
|
102
100
|
if func_name:
|
|
103
101
|
return {
|
|
@@ -137,9 +135,7 @@ def match_specs_to_functions(functions: list, specs: dict) -> list:
|
|
|
137
135
|
args_with_types = []
|
|
138
136
|
for i, arg_name in enumerate(func["args"]):
|
|
139
137
|
if i < len(spec["param_types"]):
|
|
140
|
-
args_with_types.append(
|
|
141
|
-
{"name": arg_name, "type": spec["param_types"][i]}
|
|
142
|
-
)
|
|
138
|
+
args_with_types.append({"name": arg_name, "type": spec["param_types"][i]})
|
|
143
139
|
else:
|
|
144
140
|
args_with_types.append({"name": arg_name, "type": None})
|
|
145
141
|
func["args_with_types"] = args_with_types
|
cicada/find_dead_code.py
CHANGED
|
@@ -10,10 +10,9 @@ Author: Cursor(Auto)
|
|
|
10
10
|
import argparse
|
|
11
11
|
import json
|
|
12
12
|
import sys
|
|
13
|
-
from pathlib import Path
|
|
14
13
|
|
|
15
14
|
from cicada.dead_code_analyzer import DeadCodeAnalyzer
|
|
16
|
-
from cicada.utils import load_index
|
|
15
|
+
from cicada.utils import get_index_path, load_index
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
def format_markdown(results: dict) -> str:
|
|
@@ -34,9 +33,7 @@ def format_markdown(results: dict) -> str:
|
|
|
34
33
|
f"(skipped {summary['skipped_impl']} with @impl, "
|
|
35
34
|
f"{summary['skipped_files']} in test/script files)"
|
|
36
35
|
)
|
|
37
|
-
lines.append(
|
|
38
|
-
f"Found **{summary['total_candidates']} potentially unused functions**\n"
|
|
39
|
-
)
|
|
36
|
+
lines.append(f"Found **{summary['total_candidates']} potentially unused functions**\n")
|
|
40
37
|
|
|
41
38
|
candidates = results["candidates"]
|
|
42
39
|
|
|
@@ -46,9 +43,7 @@ def format_markdown(results: dict) -> str:
|
|
|
46
43
|
label = f" HIGH CONFIDENCE ({count} function{'s' if count != 1 else ''}) "
|
|
47
44
|
bar_length = 80
|
|
48
45
|
padding = (bar_length - len(label)) // 2
|
|
49
|
-
lines.append(
|
|
50
|
-
f"\n{'═' * padding}{label}{'═' * (bar_length - padding - len(label))}"
|
|
51
|
-
)
|
|
46
|
+
lines.append(f"\n{'═' * padding}{label}{'═' * (bar_length - padding - len(label))}")
|
|
52
47
|
lines.append("Functions with zero usage in codebase\n")
|
|
53
48
|
|
|
54
49
|
# Group by module
|
|
@@ -62,9 +57,7 @@ def format_markdown(results: dict) -> str:
|
|
|
62
57
|
lines.append(f"### {module}")
|
|
63
58
|
lines.append(f"{funcs[0]['file']}\n")
|
|
64
59
|
for func in funcs:
|
|
65
|
-
lines.append(
|
|
66
|
-
f"- `{func['function']}/{func['arity']}` (line {func['line']})"
|
|
67
|
-
)
|
|
60
|
+
lines.append(f"- `{func['function']}/{func['arity']}` (line {func['line']})")
|
|
68
61
|
lines.append("")
|
|
69
62
|
|
|
70
63
|
# Medium confidence
|
|
@@ -73,9 +66,7 @@ def format_markdown(results: dict) -> str:
|
|
|
73
66
|
label = f" MEDIUM CONFIDENCE ({count} function{'s' if count != 1 else ''}) "
|
|
74
67
|
bar_length = 80
|
|
75
68
|
padding = (bar_length - len(label)) // 2
|
|
76
|
-
lines.append(
|
|
77
|
-
f"\n{'═' * padding}{label}{'═' * (bar_length - padding - len(label))}"
|
|
78
|
-
)
|
|
69
|
+
lines.append(f"\n{'═' * padding}{label}{'═' * (bar_length - padding - len(label))}")
|
|
79
70
|
lines.append(
|
|
80
71
|
"Functions with zero usage, but module has behaviors/uses (possible callbacks)\n"
|
|
81
72
|
)
|
|
@@ -101,9 +92,7 @@ def format_markdown(results: dict) -> str:
|
|
|
101
92
|
lines.append("")
|
|
102
93
|
|
|
103
94
|
for func in funcs:
|
|
104
|
-
lines.append(
|
|
105
|
-
f"- `{func['function']}/{func['arity']}` (line {func['line']})"
|
|
106
|
-
)
|
|
95
|
+
lines.append(f"- `{func['function']}/{func['arity']}` (line {func['line']})")
|
|
107
96
|
lines.append("")
|
|
108
97
|
|
|
109
98
|
# Low confidence
|
|
@@ -112,9 +101,7 @@ def format_markdown(results: dict) -> str:
|
|
|
112
101
|
label = f" LOW CONFIDENCE ({count} function{'s' if count != 1 else ''}) "
|
|
113
102
|
bar_length = 80
|
|
114
103
|
padding = (bar_length - len(label)) // 2
|
|
115
|
-
lines.append(
|
|
116
|
-
f"\n{'═' * padding}{label}{'═' * (bar_length - padding - len(label))}"
|
|
117
|
-
)
|
|
104
|
+
lines.append(f"\n{'═' * padding}{label}{'═' * (bar_length - padding - len(label))}")
|
|
118
105
|
lines.append(
|
|
119
106
|
"Functions with zero usage, but module passed as value (possible dynamic calls)\n"
|
|
120
107
|
)
|
|
@@ -139,9 +126,7 @@ def format_markdown(results: dict) -> str:
|
|
|
139
126
|
lines.append("")
|
|
140
127
|
|
|
141
128
|
for func in funcs:
|
|
142
|
-
lines.append(
|
|
143
|
-
f"- `{func['function']}/{func['arity']}` (line {func['line']})"
|
|
144
|
-
)
|
|
129
|
+
lines.append(f"- `{func['function']}/{func['arity']}` (line {func['line']})")
|
|
145
130
|
lines.append("")
|
|
146
131
|
|
|
147
132
|
if summary["total_candidates"] == 0:
|
|
@@ -205,18 +190,12 @@ Confidence Levels:
|
|
|
205
190
|
low - Zero usage, but module passed as value (possible dynamic calls)
|
|
206
191
|
|
|
207
192
|
Examples:
|
|
208
|
-
cicada
|
|
209
|
-
cicada
|
|
210
|
-
cicada
|
|
193
|
+
cicada find-dead-code # Show high confidence candidates
|
|
194
|
+
cicada find-dead-code --min-confidence low # Show all candidates
|
|
195
|
+
cicada find-dead-code --format json # Output as JSON
|
|
211
196
|
""",
|
|
212
197
|
)
|
|
213
198
|
|
|
214
|
-
parser.add_argument(
|
|
215
|
-
"--index",
|
|
216
|
-
default=".cicada/index.json",
|
|
217
|
-
help="Path to index file (default: .cicada/index.json)",
|
|
218
|
-
)
|
|
219
|
-
|
|
220
199
|
parser.add_argument(
|
|
221
200
|
"--format",
|
|
222
201
|
choices=["markdown", "json"],
|
|
@@ -233,11 +212,11 @@ Examples:
|
|
|
233
212
|
|
|
234
213
|
args = parser.parse_args()
|
|
235
214
|
|
|
236
|
-
# Load index
|
|
237
|
-
index_path =
|
|
215
|
+
# Load index from centralized storage
|
|
216
|
+
index_path = get_index_path(".")
|
|
238
217
|
if not index_path.exists():
|
|
239
218
|
print(f"Error: Index file not found: {index_path}", file=sys.stderr)
|
|
240
|
-
print(
|
|
219
|
+
print("\nRun 'cicada index' first to create the index.", file=sys.stderr)
|
|
241
220
|
sys.exit(1)
|
|
242
221
|
|
|
243
222
|
try:
|
|
@@ -258,10 +237,7 @@ Examples:
|
|
|
258
237
|
results = filter_by_confidence(results, args.min_confidence)
|
|
259
238
|
|
|
260
239
|
# Format output
|
|
261
|
-
if args.format == "json"
|
|
262
|
-
output = format_json(results)
|
|
263
|
-
else:
|
|
264
|
-
output = format_markdown(results)
|
|
240
|
+
output = format_json(results) if args.format == "json" else format_markdown(results)
|
|
265
241
|
|
|
266
242
|
print(output)
|
|
267
243
|
|
cicada/formatter.py
CHANGED
|
@@ -6,13 +6,13 @@ This module provides formatting utilities for Cicada MCP server responses,
|
|
|
6
6
|
supporting both Markdown and JSON output formats.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
import argparse
|
|
9
10
|
import json
|
|
10
11
|
import sys
|
|
11
12
|
from pathlib import Path
|
|
12
|
-
from typing import
|
|
13
|
-
import argparse
|
|
13
|
+
from typing import Any
|
|
14
14
|
|
|
15
|
-
from cicada.utils import
|
|
15
|
+
from cicada.utils import CallSiteFormatter, FunctionGrouper, SignatureBuilder
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class ModuleFormatter:
|
|
@@ -24,7 +24,7 @@ class ModuleFormatter:
|
|
|
24
24
|
|
|
25
25
|
@staticmethod
|
|
26
26
|
def format_module_markdown(
|
|
27
|
-
module_name: str, data:
|
|
27
|
+
module_name: str, data: dict[str, Any], private_functions: str = "exclude"
|
|
28
28
|
) -> str:
|
|
29
29
|
"""
|
|
30
30
|
Format module data as Markdown.
|
|
@@ -69,9 +69,7 @@ class ModuleFormatter:
|
|
|
69
69
|
if public_grouped and private_functions != "only":
|
|
70
70
|
lines.extend(["", "Public:", ""])
|
|
71
71
|
# Sort by line number instead of function name
|
|
72
|
-
for (_, _), clauses in sorted(
|
|
73
|
-
public_grouped.items(), key=lambda x: x[1][0]["line"]
|
|
74
|
-
):
|
|
72
|
+
for (_, _), clauses in sorted(public_grouped.items(), key=lambda x: x[1][0]["line"]):
|
|
75
73
|
# Use the first clause for display (they all have same name/arity)
|
|
76
74
|
func = clauses[0]
|
|
77
75
|
func_sig = SignatureBuilder.build(func)
|
|
@@ -81,9 +79,7 @@ class ModuleFormatter:
|
|
|
81
79
|
if private_grouped and private_functions in ["include", "only"]:
|
|
82
80
|
lines.extend(["", "Private:", ""])
|
|
83
81
|
# Sort by line number instead of function name
|
|
84
|
-
for (_, _), clauses in sorted(
|
|
85
|
-
private_grouped.items(), key=lambda x: x[1][0]["line"]
|
|
86
|
-
):
|
|
82
|
+
for (_, _), clauses in sorted(private_grouped.items(), key=lambda x: x[1][0]["line"]):
|
|
87
83
|
# Use the first clause for display (they all have same name/arity)
|
|
88
84
|
func = clauses[0]
|
|
89
85
|
func_sig = SignatureBuilder.build(func)
|
|
@@ -104,7 +100,7 @@ class ModuleFormatter:
|
|
|
104
100
|
|
|
105
101
|
@staticmethod
|
|
106
102
|
def format_module_json(
|
|
107
|
-
module_name: str, data:
|
|
103
|
+
module_name: str, data: dict[str, Any], private_functions: str = "exclude"
|
|
108
104
|
) -> str:
|
|
109
105
|
"""
|
|
110
106
|
Format module data as JSON.
|
|
@@ -203,9 +199,7 @@ Module names are case-sensitive and must match exactly (e.g., `MyApp.User`, not
|
|
|
203
199
|
return json.dumps(error_result, indent=2)
|
|
204
200
|
|
|
205
201
|
@staticmethod
|
|
206
|
-
def format_function_results_markdown(
|
|
207
|
-
function_name: str, results: list[Dict[str, Any]]
|
|
208
|
-
) -> str:
|
|
202
|
+
def format_function_results_markdown(function_name: str, results: list[dict[str, Any]]) -> str:
|
|
209
203
|
"""
|
|
210
204
|
Format function search results as Markdown.
|
|
211
205
|
|
|
@@ -254,7 +248,7 @@ No functions matching `{function_name}` were found in the index.
|
|
|
254
248
|
else:
|
|
255
249
|
lines = [
|
|
256
250
|
f"Functions matching {function_name}",
|
|
257
|
-
|
|
251
|
+
"",
|
|
258
252
|
f"Found {len(consolidated_results)} match(es):",
|
|
259
253
|
]
|
|
260
254
|
|
|
@@ -287,18 +281,14 @@ No functions matching `{function_name}` were found in the index.
|
|
|
287
281
|
# Add documentation if present
|
|
288
282
|
if func.get("doc"):
|
|
289
283
|
if len(consolidated_results) == 1:
|
|
290
|
-
lines.extend(
|
|
291
|
-
["", f"{indent}Documentation:", "", f"{indent}{func['doc']}"]
|
|
292
|
-
)
|
|
284
|
+
lines.extend(["", f"{indent}Documentation:", "", f"{indent}{func['doc']}"])
|
|
293
285
|
else:
|
|
294
286
|
lines.extend(["", "Documentation:", "", func["doc"]])
|
|
295
287
|
|
|
296
288
|
# Add examples if present
|
|
297
289
|
if func.get("examples"):
|
|
298
290
|
if len(consolidated_results) == 1:
|
|
299
|
-
lines.extend(
|
|
300
|
-
["", f"{indent}Examples:", "", f"{indent}{func['examples']}"]
|
|
301
|
-
)
|
|
291
|
+
lines.extend(["", f"{indent}Examples:", "", f"{indent}{func['examples']}"])
|
|
302
292
|
else:
|
|
303
293
|
lines.extend(["", "Examples:", "", func["examples"]])
|
|
304
294
|
|
|
@@ -321,23 +311,17 @@ No functions matching `{function_name}` were found in the index.
|
|
|
321
311
|
if has_examples:
|
|
322
312
|
# Separate into code and test call sites WITH examples
|
|
323
313
|
code_sites_with_examples = [
|
|
324
|
-
s
|
|
325
|
-
for s in call_sites_with_examples
|
|
326
|
-
if "test" not in s["file"].lower()
|
|
314
|
+
s for s in call_sites_with_examples if "test" not in s["file"].lower()
|
|
327
315
|
]
|
|
328
316
|
test_sites_with_examples = [
|
|
329
|
-
s
|
|
330
|
-
for s in call_sites_with_examples
|
|
331
|
-
if "test" in s["file"].lower()
|
|
317
|
+
s for s in call_sites_with_examples if "test" in s["file"].lower()
|
|
332
318
|
]
|
|
333
319
|
|
|
334
320
|
lines.append(f"{indent}Usage Examples:")
|
|
335
321
|
|
|
336
322
|
if code_sites_with_examples:
|
|
337
323
|
# Group code sites by caller
|
|
338
|
-
grouped_code = CallSiteFormatter.group_by_caller(
|
|
339
|
-
code_sites_with_examples
|
|
340
|
-
)
|
|
324
|
+
grouped_code = CallSiteFormatter.group_by_caller(code_sites_with_examples)
|
|
341
325
|
code_count = sum(len(site["lines"]) for site in grouped_code)
|
|
342
326
|
lines.append(f"{indent}Code ({code_count}):")
|
|
343
327
|
for site in grouped_code:
|
|
@@ -350,12 +334,8 @@ No functions matching `{function_name}` were found in the index.
|
|
|
350
334
|
|
|
351
335
|
# Show consolidated line numbers only if multiple lines
|
|
352
336
|
if len(site["lines"]) > 1:
|
|
353
|
-
line_list = ", ".join(
|
|
354
|
-
|
|
355
|
-
)
|
|
356
|
-
lines.append(
|
|
357
|
-
f"{indent}- {caller} at {site['file']}{line_list}"
|
|
358
|
-
)
|
|
337
|
+
line_list = ", ".join(f":{line}" for line in site["lines"])
|
|
338
|
+
lines.append(f"{indent}- {caller} at {site['file']}{line_list}")
|
|
359
339
|
else:
|
|
360
340
|
lines.append(f"{indent}- {caller} at {site['file']}")
|
|
361
341
|
|
|
@@ -371,9 +351,7 @@ No functions matching `{function_name}` were found in the index.
|
|
|
371
351
|
if code_sites_with_examples:
|
|
372
352
|
lines.append("") # Blank line between sections
|
|
373
353
|
# Group test sites by caller
|
|
374
|
-
grouped_test = CallSiteFormatter.group_by_caller(
|
|
375
|
-
test_sites_with_examples
|
|
376
|
-
)
|
|
354
|
+
grouped_test = CallSiteFormatter.group_by_caller(test_sites_with_examples)
|
|
377
355
|
test_count = sum(len(site["lines"]) for site in grouped_test)
|
|
378
356
|
lines.append(f"{indent}Test ({test_count}):")
|
|
379
357
|
for site in grouped_test:
|
|
@@ -386,12 +364,8 @@ No functions matching `{function_name}` were found in the index.
|
|
|
386
364
|
|
|
387
365
|
# Show consolidated line numbers only if multiple lines
|
|
388
366
|
if len(site["lines"]) > 1:
|
|
389
|
-
line_list = ", ".join(
|
|
390
|
-
|
|
391
|
-
)
|
|
392
|
-
lines.append(
|
|
393
|
-
f"{indent}- {caller} at {site['file']}{line_list}"
|
|
394
|
-
)
|
|
367
|
+
line_list = ", ".join(f":{line}" for line in site["lines"])
|
|
368
|
+
lines.append(f"{indent}- {caller} at {site['file']}{line_list}")
|
|
395
369
|
else:
|
|
396
370
|
lines.append(f"{indent}- {caller} at {site['file']}")
|
|
397
371
|
|
|
@@ -419,14 +393,10 @@ No functions matching `{function_name}` were found in the index.
|
|
|
419
393
|
if remaining_call_sites:
|
|
420
394
|
# Separate into code and test
|
|
421
395
|
remaining_code = [
|
|
422
|
-
s
|
|
423
|
-
for s in remaining_call_sites
|
|
424
|
-
if "test" not in s["file"].lower()
|
|
396
|
+
s for s in remaining_call_sites if "test" not in s["file"].lower()
|
|
425
397
|
]
|
|
426
398
|
remaining_test = [
|
|
427
|
-
s
|
|
428
|
-
for s in remaining_call_sites
|
|
429
|
-
if "test" in s["file"].lower()
|
|
399
|
+
s for s in remaining_call_sites if "test" in s["file"].lower()
|
|
430
400
|
]
|
|
431
401
|
|
|
432
402
|
lines.append("")
|
|
@@ -447,12 +417,8 @@ No functions matching `{function_name}` were found in the index.
|
|
|
447
417
|
else:
|
|
448
418
|
caller = site["calling_module"]
|
|
449
419
|
|
|
450
|
-
line_list = ", ".join(
|
|
451
|
-
|
|
452
|
-
)
|
|
453
|
-
lines.append(
|
|
454
|
-
f"{indent}- {caller} at {site['file']}{line_list}"
|
|
455
|
-
)
|
|
420
|
+
line_list = ", ".join(f":{line}" for line in site["lines"])
|
|
421
|
+
lines.append(f"{indent}- {caller} at {site['file']}{line_list}")
|
|
456
422
|
|
|
457
423
|
if remaining_test:
|
|
458
424
|
if remaining_code:
|
|
@@ -471,17 +437,11 @@ No functions matching `{function_name}` were found in the index.
|
|
|
471
437
|
else:
|
|
472
438
|
caller = site["calling_module"]
|
|
473
439
|
|
|
474
|
-
line_list = ", ".join(
|
|
475
|
-
|
|
476
|
-
)
|
|
477
|
-
lines.append(
|
|
478
|
-
f"{indent}- {caller} at {site['file']}{line_list}"
|
|
479
|
-
)
|
|
440
|
+
line_list = ", ".join(f":{line}" for line in site["lines"])
|
|
441
|
+
lines.append(f"{indent}- {caller} at {site['file']}{line_list}")
|
|
480
442
|
else:
|
|
481
443
|
# Separate into code and test call sites
|
|
482
|
-
code_sites = [
|
|
483
|
-
s for s in call_sites if "test" not in s["file"].lower()
|
|
484
|
-
]
|
|
444
|
+
code_sites = [s for s in call_sites if "test" not in s["file"].lower()]
|
|
485
445
|
test_sites = [s for s in call_sites if "test" in s["file"].lower()]
|
|
486
446
|
|
|
487
447
|
call_count = len(call_sites)
|
|
@@ -504,9 +464,7 @@ No functions matching `{function_name}` were found in the index.
|
|
|
504
464
|
|
|
505
465
|
# Show consolidated line numbers
|
|
506
466
|
line_list = ", ".join(f":{line}" for line in site["lines"])
|
|
507
|
-
lines.append(
|
|
508
|
-
f"{indent}- {caller} at {site['file']}{line_list}"
|
|
509
|
-
)
|
|
467
|
+
lines.append(f"{indent}- {caller} at {site['file']}{line_list}")
|
|
510
468
|
|
|
511
469
|
if test_sites:
|
|
512
470
|
if code_sites:
|
|
@@ -525,9 +483,7 @@ No functions matching `{function_name}` were found in the index.
|
|
|
525
483
|
|
|
526
484
|
# Show consolidated line numbers
|
|
527
485
|
line_list = ", ".join(f":{line}" for line in site["lines"])
|
|
528
|
-
lines.append(
|
|
529
|
-
f"{indent}- {caller} at {site['file']}{line_list}"
|
|
530
|
-
)
|
|
486
|
+
lines.append(f"{indent}- {caller} at {site['file']}{line_list}")
|
|
531
487
|
lines.append("")
|
|
532
488
|
else:
|
|
533
489
|
lines.extend([f"{indent}*No call sites found*"])
|
|
@@ -535,9 +491,7 @@ No functions matching `{function_name}` were found in the index.
|
|
|
535
491
|
return "\n".join(lines)
|
|
536
492
|
|
|
537
493
|
@staticmethod
|
|
538
|
-
def format_function_results_json(
|
|
539
|
-
function_name: str, results: list[Dict[str, Any]]
|
|
540
|
-
) -> str:
|
|
494
|
+
def format_function_results_json(function_name: str, results: list[dict[str, Any]]) -> str:
|
|
541
495
|
"""
|
|
542
496
|
Format function search results as JSON.
|
|
543
497
|
|
|
@@ -593,9 +547,7 @@ No functions matching `{function_name}` were found in the index.
|
|
|
593
547
|
return json.dumps(output, indent=2)
|
|
594
548
|
|
|
595
549
|
@staticmethod
|
|
596
|
-
def format_module_usage_markdown(
|
|
597
|
-
module_name: str, usage_results: Dict[str, Any]
|
|
598
|
-
) -> str:
|
|
550
|
+
def format_module_usage_markdown(module_name: str, usage_results: dict[str, Any]) -> str:
|
|
599
551
|
"""
|
|
600
552
|
Format module usage results as Markdown.
|
|
601
553
|
|
|
@@ -624,9 +576,7 @@ No functions matching `{function_name}` were found in the index.
|
|
|
624
576
|
if imp["alias_name"] != module_name.split(".")[-1]
|
|
625
577
|
else ""
|
|
626
578
|
)
|
|
627
|
-
lines.append(
|
|
628
|
-
f"- `{imp['importing_module']}` {alias_info} — `{imp['file']}`"
|
|
629
|
-
)
|
|
579
|
+
lines.append(f"- `{imp['importing_module']}` {alias_info} — `{imp['file']}`")
|
|
630
580
|
lines.append("")
|
|
631
581
|
|
|
632
582
|
# Show imports section
|
|
@@ -674,9 +624,7 @@ No functions matching `{function_name}` were found in the index.
|
|
|
674
624
|
lines.append("")
|
|
675
625
|
|
|
676
626
|
for call in fc["calls"]:
|
|
677
|
-
alias_info = (
|
|
678
|
-
f" (via `{call['alias_used']}`)" if call["alias_used"] else ""
|
|
679
|
-
)
|
|
627
|
+
alias_info = f" (via `{call['alias_used']}`)" if call["alias_used"] else ""
|
|
680
628
|
# Show unique line numbers for this function
|
|
681
629
|
line_list = ", ".join(f":{line}" for line in sorted(call["lines"]))
|
|
682
630
|
lines.append(
|
|
@@ -692,9 +640,7 @@ No functions matching `{function_name}` were found in the index.
|
|
|
692
640
|
return "\n".join(lines)
|
|
693
641
|
|
|
694
642
|
@staticmethod
|
|
695
|
-
def format_module_usage_json(
|
|
696
|
-
module_name: str, usage_results: Dict[str, Any]
|
|
697
|
-
) -> str:
|
|
643
|
+
def format_module_usage_json(module_name: str, usage_results: dict[str, Any]) -> str:
|
|
698
644
|
"""
|
|
699
645
|
Format module usage results as JSON.
|
|
700
646
|
|
|
@@ -726,7 +672,7 @@ No functions matching `{function_name}` were found in the index.
|
|
|
726
672
|
|
|
727
673
|
@staticmethod
|
|
728
674
|
def format_keyword_search_results_markdown(
|
|
729
|
-
_keywords: list[str], results: list[
|
|
675
|
+
_keywords: list[str], results: list[dict[str, Any]]
|
|
730
676
|
) -> str:
|
|
731
677
|
"""
|
|
732
678
|
Format keyword search results as Markdown.
|
|
@@ -814,9 +760,9 @@ class JSONFormatter:
|
|
|
814
760
|
data = json.loads(json_string)
|
|
815
761
|
return json.dumps(data, indent=self.indent, sort_keys=self.sort_keys)
|
|
816
762
|
except json.JSONDecodeError as e:
|
|
817
|
-
raise ValueError(f"Invalid JSON: {e}")
|
|
763
|
+
raise ValueError(f"Invalid JSON: {e}") from e
|
|
818
764
|
|
|
819
|
-
def format_file(self, input_path: Path, output_path:
|
|
765
|
+
def format_file(self, input_path: Path, output_path: Path | None = None) -> str:
|
|
820
766
|
"""
|
|
821
767
|
Format a JSON file.
|
|
822
768
|
|
|
@@ -835,7 +781,7 @@ class JSONFormatter:
|
|
|
835
781
|
raise FileNotFoundError(f"Input file not found: {input_path}")
|
|
836
782
|
|
|
837
783
|
# Read the input file
|
|
838
|
-
with open(input_path
|
|
784
|
+
with open(input_path) as f:
|
|
839
785
|
json_string = f.read()
|
|
840
786
|
|
|
841
787
|
# Format the JSON
|