contextl 1.2.43 → 1.2.46
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.
- package/package.json +1 -1
- package/python/import_parser.py +13 -3
- package/python/installer.py +20 -3
- package/python/query_engine.py +38 -14
- package/python/scanner.py +1 -1
package/package.json
CHANGED
package/python/import_parser.py
CHANGED
|
@@ -593,10 +593,20 @@ def _find_file_in_repo(
|
|
|
593
593
|
py_idx = cand + "/__init__.py"
|
|
594
594
|
if py_idx in file_index:
|
|
595
595
|
return py_idx
|
|
596
|
-
|
|
596
|
+
|
|
597
|
+
suffix_ext = "/" + py_ext
|
|
598
|
+
suffix_idx = "/" + py_idx
|
|
599
|
+
matches = []
|
|
600
|
+
|
|
597
601
|
for path in file_index:
|
|
598
|
-
if path.endswith(
|
|
599
|
-
|
|
602
|
+
if path.endswith(suffix_ext) or path == py_ext:
|
|
603
|
+
matches.append(path)
|
|
604
|
+
elif path.endswith(suffix_idx) or path == py_idx:
|
|
605
|
+
matches.append(path)
|
|
606
|
+
|
|
607
|
+
if matches:
|
|
608
|
+
matches.sort(key=lambda p: (p.count("/"), 0 if p.endswith("__init__.py") else 1))
|
|
609
|
+
return matches[0]
|
|
600
610
|
|
|
601
611
|
# Fallback for Python cross-language imports (e.g. from tests to .ts/.js core logic)
|
|
602
612
|
for ext in [".ts", ".tsx", ".js", ".jsx", ".java", ".go", ".rs", ".cpp", ".cc", ".cxx", ".h", ".hpp", ".c"]:
|
package/python/installer.py
CHANGED
|
@@ -2,9 +2,11 @@ import os
|
|
|
2
2
|
import json
|
|
3
3
|
import platform
|
|
4
4
|
import sys
|
|
5
|
+
import tempfile
|
|
6
|
+
import shutil
|
|
5
7
|
from pathlib import Path
|
|
6
8
|
|
|
7
|
-
def install_mcp():
|
|
9
|
+
def install_mcp(include_vscode_workspace: bool = False):
|
|
8
10
|
home = Path.home()
|
|
9
11
|
system = platform.system()
|
|
10
12
|
|
|
@@ -42,6 +44,16 @@ def install_mcp():
|
|
|
42
44
|
# 4. Cursor
|
|
43
45
|
targets.append(home / ".cursor" / "mcp.json")
|
|
44
46
|
|
|
47
|
+
# 5. Claude Code
|
|
48
|
+
targets.append(home / ".claude.json")
|
|
49
|
+
|
|
50
|
+
# 6. Windsurf
|
|
51
|
+
targets.append(home / ".codeium" / "windsurf" / "mcp_config.json")
|
|
52
|
+
|
|
53
|
+
# 7. VS Code (Workspace level)
|
|
54
|
+
if include_vscode_workspace:
|
|
55
|
+
targets.append(Path.cwd() / ".vscode" / "mcp.json")
|
|
56
|
+
|
|
45
57
|
success_count = 0
|
|
46
58
|
|
|
47
59
|
for path in targets:
|
|
@@ -71,9 +83,14 @@ def install_mcp():
|
|
|
71
83
|
"args": []
|
|
72
84
|
}
|
|
73
85
|
|
|
74
|
-
#
|
|
75
|
-
|
|
86
|
+
# Backup existing config
|
|
87
|
+
shutil.copy(path, path.with_suffix(path.suffix + ".bak"))
|
|
88
|
+
|
|
89
|
+
# Write config atomically
|
|
90
|
+
fd, tmp_path = tempfile.mkstemp(dir=path.parent, suffix=".tmp")
|
|
91
|
+
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
76
92
|
json.dump(config, f, indent=2)
|
|
93
|
+
os.replace(tmp_path, path)
|
|
77
94
|
|
|
78
95
|
print(f"[SUCCESS] Successfully injected ContextL MCP Server into: {path}")
|
|
79
96
|
success_count += 1
|
package/python/query_engine.py
CHANGED
|
@@ -82,6 +82,41 @@ def _path_tokens(path: str) -> list[str]:
|
|
|
82
82
|
tokens.extend(re.findall(r"[a-z0-9]+", sub))
|
|
83
83
|
return [t for t in tokens if t not in STOP_WORDS and len(t) > 1]
|
|
84
84
|
|
|
85
|
+
COMMON_ABBREVS = {
|
|
86
|
+
"db": "database",
|
|
87
|
+
"auth": "authentication",
|
|
88
|
+
"config": "configuration",
|
|
89
|
+
"pkg": "package",
|
|
90
|
+
"app": "application",
|
|
91
|
+
"svc": "service",
|
|
92
|
+
"repo": "repository",
|
|
93
|
+
"mgr": "manager",
|
|
94
|
+
"impl": "implementation",
|
|
95
|
+
"dir": "directory",
|
|
96
|
+
"req": "request",
|
|
97
|
+
"res": "response",
|
|
98
|
+
"env": "environment",
|
|
99
|
+
"src": "source",
|
|
100
|
+
"lib": "library",
|
|
101
|
+
"bin": "binary",
|
|
102
|
+
"init": "initialize",
|
|
103
|
+
"util": "utility",
|
|
104
|
+
"utils": "utilities",
|
|
105
|
+
"msg": "message",
|
|
106
|
+
"err": "error",
|
|
107
|
+
"ctx": "context",
|
|
108
|
+
"fn": "function",
|
|
109
|
+
"cmd": "command",
|
|
110
|
+
"ctrl": "controller",
|
|
111
|
+
"mid": "middleware"
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
def _is_match(term: str, target: str) -> bool:
|
|
115
|
+
term_canon = COMMON_ABBREVS.get(term, term)
|
|
116
|
+
if term_canon in target or target in term_canon or target.startswith(term_canon) or term_canon.startswith(target):
|
|
117
|
+
return True
|
|
118
|
+
return False
|
|
119
|
+
|
|
85
120
|
|
|
86
121
|
def _keyword_score(query_terms: list[str], file_path: str, idf_weights: dict[str, float]) -> tuple[float, list[str]]:
|
|
87
122
|
"""
|
|
@@ -100,10 +135,7 @@ def _keyword_score(query_terms: list[str], file_path: str, idf_weights: dict[str
|
|
|
100
135
|
score += 4.0 * idf_weights.get(term, 1.0) # Massive signal
|
|
101
136
|
matched.append(term)
|
|
102
137
|
# Substring or abbreviation match on filename
|
|
103
|
-
elif len(term) >= 2 and any(
|
|
104
|
-
term in ft or ft in term or ft.startswith(term) or term.startswith(ft)
|
|
105
|
-
for ft in filename_toks
|
|
106
|
-
):
|
|
138
|
+
elif len(term) >= 2 and any(_is_match(term, ft) for ft in filename_toks):
|
|
107
139
|
score += 3.0 * idf_weights.get(term, 1.0)
|
|
108
140
|
matched.append(term)
|
|
109
141
|
# Exact match on path
|
|
@@ -112,10 +144,7 @@ def _keyword_score(query_terms: list[str], file_path: str, idf_weights: dict[str
|
|
|
112
144
|
if term not in matched:
|
|
113
145
|
matched.append(term)
|
|
114
146
|
# Substring or abbreviation match on path
|
|
115
|
-
elif len(term) >= 2 and any(
|
|
116
|
-
term in pt or pt in term or pt.startswith(term) or term.startswith(pt)
|
|
117
|
-
for pt in path_toks
|
|
118
|
-
):
|
|
147
|
+
elif len(term) >= 2 and any(_is_match(term, pt) for pt in path_toks):
|
|
119
148
|
score += 1.5 * idf_weights.get(term, 1.0)
|
|
120
149
|
if term not in matched:
|
|
121
150
|
matched.append(term)
|
|
@@ -330,13 +359,8 @@ def query(
|
|
|
330
359
|
path_tokens = set(_path_tokens(path))
|
|
331
360
|
combined_tokens = content_tokens.union(path_tokens)
|
|
332
361
|
for term in query_terms:
|
|
333
|
-
if term in combined_tokens:
|
|
362
|
+
if any(_is_match(term, ct) for ct in combined_tokens):
|
|
334
363
|
document_frequency[term] += 1
|
|
335
|
-
elif len(term) >= 2:
|
|
336
|
-
for ct in combined_tokens:
|
|
337
|
-
if term in ct or (len(ct) >= 2 and ct in term):
|
|
338
|
-
document_frequency[term] += 1
|
|
339
|
-
break
|
|
340
364
|
|
|
341
365
|
avgdl = total_tokens_count / total_files if total_files > 0 else 1.0
|
|
342
366
|
|
package/python/scanner.py
CHANGED
|
@@ -18,7 +18,7 @@ ignore_dirs = {
|
|
|
18
18
|
".git", "node_modules", "__pycache__", "venv", ".venv",
|
|
19
19
|
"env", ".env", ".next", ".nuxt", "out", "build", "dist",
|
|
20
20
|
"coverage", ".nyc_output", ".pytest_cache", "npm", "pypi",
|
|
21
|
-
"vendor", "target", ".cargo", "Pods", "third_party"
|
|
21
|
+
"vendor", "target", ".cargo", "Pods", "third_party", "deps"
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
def _should_ignore(path: str) -> bool:
|