contextl 1.2.24 → 1.2.26
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 +37 -12
- package/python/query_engine.py +10 -7
package/package.json
CHANGED
package/python/import_parser.py
CHANGED
|
@@ -19,6 +19,18 @@ import sys
|
|
|
19
19
|
from pathlib import Path, PurePosixPath
|
|
20
20
|
from dataclasses import dataclass, field
|
|
21
21
|
|
|
22
|
+
PYTHON_STDLIB_AND_COMMON_PACKAGES = {
|
|
23
|
+
"os", "sys", "re", "math", "json", "datetime", "typing", "collections",
|
|
24
|
+
"itertools", "functools", "pathlib", "logging", "argparse", "subprocess",
|
|
25
|
+
"unittest", "pytest", "numpy", "pandas", "requests", "urllib", "asyncio",
|
|
26
|
+
"time", "random", "hashlib", "base64", "csv", "xml", "html", "http",
|
|
27
|
+
"concurrent", "multiprocessing", "threading", "sqlite3", "flask", "django",
|
|
28
|
+
"fastapi", "sqlalchemy", "pydantic", "dataclasses", "uuid", "socket",
|
|
29
|
+
"struct", "tempfile", "shutil", "glob", "copy", "warnings", "traceback",
|
|
30
|
+
"contextlib", "io", "sysconfig", "importlib", "pkgutil", "runpy",
|
|
31
|
+
"celery", "redis", "psycopg2", "boto3", "jinja2", "pytest_mock",
|
|
32
|
+
}
|
|
33
|
+
|
|
22
34
|
from scanner import ScannedFile, ScanResult
|
|
23
35
|
|
|
24
36
|
|
|
@@ -142,7 +154,7 @@ def _ts_imports_js_ts(root: "Node") -> list[str]:
|
|
|
142
154
|
return child.text.decode("utf-8", errors="replace")
|
|
143
155
|
# Fallback: strip quotes manually
|
|
144
156
|
raw = node.text.decode("utf-8", errors="replace")
|
|
145
|
-
return raw.strip("'
|
|
157
|
+
return raw.strip('"\'`')
|
|
146
158
|
return None
|
|
147
159
|
|
|
148
160
|
def visit(node: "Node"):
|
|
@@ -883,6 +895,10 @@ def parse_imports(scan_result: ScanResult) -> ParseResult:
|
|
|
883
895
|
else:
|
|
884
896
|
if scanned_file.extension == ".rs" and locals().get("is_likely_external", False):
|
|
885
897
|
continue
|
|
898
|
+
if scanned_file.extension == ".py":
|
|
899
|
+
base_pkg = raw.split(".")[0]
|
|
900
|
+
if base_pkg in PYTHON_STDLIB_AND_COMMON_PACKAGES:
|
|
901
|
+
continue
|
|
886
902
|
result.unresolved.append((scanned_file.path, raw))
|
|
887
903
|
|
|
888
904
|
return result
|
|
@@ -1495,11 +1511,18 @@ def extract_skeleton(file_path: str) -> dict:
|
|
|
1495
1511
|
try:
|
|
1496
1512
|
path = Path(file_path)
|
|
1497
1513
|
ext = path.suffix.lower()
|
|
1514
|
+
|
|
1515
|
+
lang_map = {
|
|
1516
|
+
".py": "python", ".ts": "typescript", ".tsx": "tsx",
|
|
1517
|
+
".js": "javascript", ".jsx": "jsx", ".java": "java",
|
|
1518
|
+
".rs": "rust", ".go": "go", ".cpp": "cpp",
|
|
1519
|
+
}
|
|
1520
|
+
derived_language = lang_map.get(ext, ext.lstrip("."))
|
|
1498
1521
|
|
|
1499
1522
|
if not _TS_AVAILABLE:
|
|
1500
1523
|
return {
|
|
1501
1524
|
"file": str(path),
|
|
1502
|
-
"language":
|
|
1525
|
+
"language": derived_language,
|
|
1503
1526
|
"error": "tree-sitter not installed — run: pip install tree-sitter tree-sitter-python tree-sitter-typescript ...",
|
|
1504
1527
|
"classes": [],
|
|
1505
1528
|
"functions": [],
|
|
@@ -1511,7 +1534,7 @@ def extract_skeleton(file_path: str) -> dict:
|
|
|
1511
1534
|
if parser is None:
|
|
1512
1535
|
return {
|
|
1513
1536
|
"file": str(path),
|
|
1514
|
-
"language":
|
|
1537
|
+
"language": derived_language,
|
|
1515
1538
|
"error": f"Unsupported file type: {ext}",
|
|
1516
1539
|
"classes": [],
|
|
1517
1540
|
"functions": [],
|
|
@@ -1526,7 +1549,7 @@ def extract_skeleton(file_path: str) -> dict:
|
|
|
1526
1549
|
if handler is None:
|
|
1527
1550
|
return {
|
|
1528
1551
|
"file": str(path),
|
|
1529
|
-
"language":
|
|
1552
|
+
"language": derived_language,
|
|
1530
1553
|
"error": f"No skeleton handler for {ext}",
|
|
1531
1554
|
"classes": [],
|
|
1532
1555
|
"functions": [],
|
|
@@ -1534,12 +1557,6 @@ def extract_skeleton(file_path: str) -> dict:
|
|
|
1534
1557
|
"docstrings": {},
|
|
1535
1558
|
}
|
|
1536
1559
|
|
|
1537
|
-
lang_map = {
|
|
1538
|
-
".py": "python", ".ts": "typescript", ".tsx": "tsx",
|
|
1539
|
-
".js": "javascript", ".jsx": "jsx", ".java": "java",
|
|
1540
|
-
".rs": "rust", ".go": "go", ".cpp": "cpp",
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
1560
|
if ext == ".py":
|
|
1544
1561
|
body = handler(tree.root_node, source_bytes)
|
|
1545
1562
|
else:
|
|
@@ -1547,14 +1564,22 @@ def extract_skeleton(file_path: str) -> dict:
|
|
|
1547
1564
|
|
|
1548
1565
|
return {
|
|
1549
1566
|
"file": str(path),
|
|
1550
|
-
"language":
|
|
1567
|
+
"language": derived_language,
|
|
1551
1568
|
**body,
|
|
1552
1569
|
}
|
|
1553
1570
|
|
|
1554
1571
|
except Exception as e:
|
|
1572
|
+
# Fallback if path is not yet defined
|
|
1573
|
+
lang = "unknown"
|
|
1574
|
+
if 'derived_language' in locals():
|
|
1575
|
+
lang = derived_language
|
|
1576
|
+
elif 'file_path' in locals():
|
|
1577
|
+
ext = Path(file_path).suffix.lower()
|
|
1578
|
+
lang = lang_map.get(ext, ext.lstrip(".")) if 'lang_map' in locals() else ext.lstrip(".")
|
|
1579
|
+
|
|
1555
1580
|
return {
|
|
1556
1581
|
"file": str(file_path),
|
|
1557
|
-
"language":
|
|
1582
|
+
"language": lang,
|
|
1558
1583
|
"error": str(e),
|
|
1559
1584
|
"classes": [],
|
|
1560
1585
|
"functions": [],
|
package/python/query_engine.py
CHANGED
|
@@ -96,7 +96,7 @@ def _keyword_score(query_terms: list[str], file_path: str, idf_weights: dict[str
|
|
|
96
96
|
for term in query_terms:
|
|
97
97
|
# Exact match on filename
|
|
98
98
|
if term in filename_toks:
|
|
99
|
-
score +=
|
|
99
|
+
score += 4.0 * idf_weights.get(term, 1.0) # Massive signal
|
|
100
100
|
matched.append(term)
|
|
101
101
|
# Substring or abbreviation match on filename
|
|
102
102
|
elif len(term) >= 2 and any(
|
|
@@ -104,11 +104,11 @@ def _keyword_score(query_terms: list[str], file_path: str, idf_weights: dict[str
|
|
|
104
104
|
all(c in iter(ft) for c in term)
|
|
105
105
|
for ft in filename_toks
|
|
106
106
|
):
|
|
107
|
-
score +=
|
|
107
|
+
score += 3.0 * idf_weights.get(term, 1.0)
|
|
108
108
|
matched.append(term)
|
|
109
109
|
# Exact match on path
|
|
110
110
|
elif term in path_toks:
|
|
111
|
-
score += 0
|
|
111
|
+
score += 2.0 * idf_weights.get(term, 1.0) # Strong signal
|
|
112
112
|
if term not in matched:
|
|
113
113
|
matched.append(term)
|
|
114
114
|
# Substring or abbreviation match on path
|
|
@@ -117,14 +117,16 @@ def _keyword_score(query_terms: list[str], file_path: str, idf_weights: dict[str
|
|
|
117
117
|
all(c in iter(pt) for c in term)
|
|
118
118
|
for pt in path_toks
|
|
119
119
|
):
|
|
120
|
-
score +=
|
|
120
|
+
score += 1.5 * idf_weights.get(term, 1.0)
|
|
121
121
|
if term not in matched:
|
|
122
122
|
matched.append(term)
|
|
123
123
|
|
|
124
124
|
# Normalize by max possible score (sum of all IDFs)
|
|
125
125
|
max_score = sum(idf_weights.get(t, 1.0) for t in query_terms)
|
|
126
126
|
if max_score > 0:
|
|
127
|
-
|
|
127
|
+
base_normalized = min(1.0, sum(idf_weights.get(t, 1.0) for t in matched) / max_score)
|
|
128
|
+
bonus = score - sum(idf_weights.get(t, 1.0) for t in matched)
|
|
129
|
+
score = base_normalized + max(0, bonus)
|
|
128
130
|
|
|
129
131
|
return score, matched
|
|
130
132
|
|
|
@@ -362,8 +364,9 @@ def query(
|
|
|
362
364
|
content_scores[path] = cscore
|
|
363
365
|
matched_terms[path] = list(set(kterms + cterms))
|
|
364
366
|
|
|
365
|
-
# Base combination:
|
|
366
|
-
|
|
367
|
+
# Base combination: heavily weight filename/path matches (70%) over content matches (30%)
|
|
368
|
+
# This prevents transitive consumers of a file from out-ranking the file itself
|
|
369
|
+
combined = (kscore * 0.7) + (cscore * 0.3)
|
|
367
370
|
|
|
368
371
|
# Tiebreaker: Slightly penalize deep paths so src/x.py wins over build/npm/python/x.py
|
|
369
372
|
depth_penalty = 0.0001 * path.count("/")
|