cicada-mcp 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. cicada/_version_hash.py +4 -0
  2. cicada/cli.py +6 -748
  3. cicada/commands.py +1255 -0
  4. cicada/dead_code/__init__.py +1 -0
  5. cicada/{find_dead_code.py → dead_code/finder.py} +2 -1
  6. cicada/dependency_analyzer.py +147 -0
  7. cicada/entry_utils.py +92 -0
  8. cicada/extractors/base.py +9 -9
  9. cicada/extractors/call.py +17 -20
  10. cicada/extractors/common.py +64 -0
  11. cicada/extractors/dependency.py +117 -235
  12. cicada/extractors/doc.py +2 -49
  13. cicada/extractors/function.py +10 -14
  14. cicada/extractors/keybert.py +228 -0
  15. cicada/extractors/keyword.py +191 -0
  16. cicada/extractors/module.py +6 -10
  17. cicada/extractors/spec.py +8 -56
  18. cicada/format/__init__.py +20 -0
  19. cicada/{ascii_art.py → format/ascii_art.py} +1 -1
  20. cicada/format/formatter.py +1145 -0
  21. cicada/git_helper.py +134 -7
  22. cicada/indexer.py +322 -89
  23. cicada/interactive_setup.py +251 -323
  24. cicada/interactive_setup_helpers.py +302 -0
  25. cicada/keyword_expander.py +437 -0
  26. cicada/keyword_search.py +208 -422
  27. cicada/keyword_test.py +383 -16
  28. cicada/mcp/__init__.py +10 -0
  29. cicada/mcp/entry.py +17 -0
  30. cicada/mcp/filter_utils.py +107 -0
  31. cicada/mcp/pattern_utils.py +118 -0
  32. cicada/{mcp_server.py → mcp/server.py} +819 -73
  33. cicada/mcp/tools.py +473 -0
  34. cicada/pr_finder.py +2 -3
  35. cicada/pr_indexer/indexer.py +3 -2
  36. cicada/setup.py +167 -35
  37. cicada/tier.py +225 -0
  38. cicada/utils/__init__.py +9 -2
  39. cicada/utils/fuzzy_match.py +54 -0
  40. cicada/utils/index_utils.py +9 -0
  41. cicada/utils/path_utils.py +18 -0
  42. cicada/utils/text_utils.py +52 -1
  43. cicada/utils/tree_utils.py +47 -0
  44. cicada/version_check.py +99 -0
  45. cicada/watch_manager.py +320 -0
  46. cicada/watcher.py +431 -0
  47. cicada_mcp-0.3.0.dist-info/METADATA +541 -0
  48. cicada_mcp-0.3.0.dist-info/RECORD +70 -0
  49. cicada_mcp-0.3.0.dist-info/entry_points.txt +4 -0
  50. cicada/formatter.py +0 -864
  51. cicada/keybert_extractor.py +0 -286
  52. cicada/lightweight_keyword_extractor.py +0 -290
  53. cicada/mcp_entry.py +0 -683
  54. cicada/mcp_tools.py +0 -291
  55. cicada_mcp-0.2.0.dist-info/METADATA +0 -735
  56. cicada_mcp-0.2.0.dist-info/RECORD +0 -53
  57. cicada_mcp-0.2.0.dist-info/entry_points.txt +0 -4
  58. /cicada/{dead_code_analyzer.py → dead_code/analyzer.py} +0 -0
  59. /cicada/{colors.py → format/colors.py} +0 -0
  60. {cicada_mcp-0.2.0.dist-info → cicada_mcp-0.3.0.dist-info}/WHEEL +0 -0
  61. {cicada_mcp-0.2.0.dist-info → cicada_mcp-0.3.0.dist-info}/licenses/LICENSE +0 -0
  62. {cicada_mcp-0.2.0.dist-info → cicada_mcp-0.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,118 @@
1
+ """
2
+ Helper utilities for wildcard and OR pattern handling within MCP tools.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import contextlib
8
+ import fnmatch
9
+ from collections.abc import Iterable
10
+ from dataclasses import dataclass
11
+ from typing import Any
12
+
13
+ __all__ = [
14
+ "FunctionPattern",
15
+ "has_wildcards",
16
+ "match_any_pattern",
17
+ "match_wildcard",
18
+ "matches_pattern",
19
+ "parse_function_patterns",
20
+ "split_or_patterns",
21
+ ]
22
+
23
+
24
+ def has_wildcards(pattern: str) -> bool:
25
+ """Return True if the supplied pattern contains wildcard characters."""
26
+ return "*" in pattern or "|" in pattern
27
+
28
+
29
+ def match_wildcard(pattern: str, text: str) -> bool:
30
+ """
31
+ Check if text matches a wildcard pattern.
32
+
33
+ Supports * (matches any characters) only, not ?.
34
+ """
35
+ if "?" in pattern:
36
+ return False
37
+ return fnmatch.fnmatch(text.lower(), pattern.lower())
38
+
39
+
40
+ def matches_pattern(pattern: str | None, text: str) -> bool:
41
+ """Evaluate whether text satisfies a (possibly wildcard) pattern."""
42
+ if not pattern or pattern == "*":
43
+ return True
44
+ if "*" in pattern:
45
+ return match_wildcard(pattern, text)
46
+ return pattern.lower() == text.lower()
47
+
48
+
49
+ def match_any_pattern(patterns: Iterable[str], text: str) -> bool:
50
+ """Return True if the text matches any of the provided patterns."""
51
+ return any(matches_pattern(pattern.strip(), text) for pattern in patterns if pattern.strip())
52
+
53
+
54
+ def split_or_patterns(pattern: str) -> list[str]:
55
+ """Split a pattern by the OR symbol (|)."""
56
+ return [p.strip() for p in pattern.split("|")]
57
+
58
+
59
+ @dataclass(frozen=True)
60
+ class FunctionPattern:
61
+ """Structured representation of a parsed function search pattern."""
62
+
63
+ file: str | None = None
64
+ module: str | None = None
65
+ name: str = "*"
66
+ arity: int | None = None
67
+
68
+ @classmethod
69
+ def from_string(cls, pattern: str) -> FunctionPattern:
70
+ """Parse a raw pattern string into a `FunctionPattern` instance."""
71
+ raw = pattern.strip()
72
+ if not raw:
73
+ raw = "*"
74
+
75
+ file_pattern: str | None = None
76
+ module_pattern: str | None = None
77
+ name_pattern = raw
78
+ arity: int | None = None
79
+
80
+ if ":" in name_pattern:
81
+ file_candidate, remainder = name_pattern.split(":", 1)
82
+ if "/" in file_candidate or file_candidate.endswith((".ex", ".exs")):
83
+ file_pattern = file_candidate
84
+ name_pattern = remainder
85
+
86
+ if "." in name_pattern:
87
+ module_pattern, name_pattern = name_pattern.rsplit(".", 1)
88
+
89
+ if "/" in name_pattern:
90
+ name_part, arity_part = name_pattern.rsplit("/", 1)
91
+ with contextlib.suppress(ValueError):
92
+ arity = int(arity_part)
93
+ name_pattern = name_part
94
+
95
+ if not name_pattern:
96
+ name_pattern = "*"
97
+
98
+ return cls(file=file_pattern, module=module_pattern, name=name_pattern, arity=arity)
99
+
100
+ def matches(self, module_name: str, file_path: str, func: dict[str, Any]) -> bool:
101
+ """Return True if the function entry satisfies this pattern."""
102
+ return (
103
+ matches_pattern(self.module, module_name)
104
+ and matches_pattern(self.file, file_path)
105
+ and matches_pattern(self.name, func["name"])
106
+ and (self.arity is None or func["arity"] == self.arity)
107
+ )
108
+
109
+
110
+ def parse_function_patterns(raw: str | None) -> list[FunctionPattern]:
111
+ """Split and parse a compound OR pattern string into FunctionPattern objects."""
112
+ if not raw:
113
+ return [FunctionPattern()]
114
+
115
+ patterns = [
116
+ FunctionPattern.from_string(part) for part in split_or_patterns(raw) if part.strip()
117
+ ]
118
+ return patterns or [FunctionPattern()]