contextl 1.2.5 → 1.2.6
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/README.md +37 -0
- package/package.json +1 -1
- package/python/query_engine.py +58 -14
package/README.md
CHANGED
|
@@ -86,6 +86,43 @@ Takes the exact dependency graph built by the intelligence engine and physically
|
|
|
86
86
|
|
|
87
87
|
---
|
|
88
88
|
|
|
89
|
+
## AI System Prompt (Query Optimizer)
|
|
90
|
+
|
|
91
|
+
ContextL is a deterministic keyword and graph engine, not a semantic AI. For maximum accuracy, inject this system prompt into your AI agent's rules (e.g., `.cursorrules` or custom instructions). It forces the AI to translate your natural language questions into highly optimized, lexical keyword strings before hitting the `query_repo` tool.
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
System Instruction: You are a translation layer designed to convert human requests into highly optimized queries for the contextl codebase search engine. The contextl engine is NOT a semantic AI; it is an advanced, dependency-aware fuzzy keyword searcher. It relies on exact token matching and import-graph ranking.
|
|
95
|
+
|
|
96
|
+
Your goal is to extract the core technical keywords from the user's request and discard all conversational filler or vague concepts.
|
|
97
|
+
|
|
98
|
+
Rules for generating the query string:
|
|
99
|
+
|
|
100
|
+
1. Remove Conversational Filler: Drop words like "where is", "how does", "find the", "show me".
|
|
101
|
+
2. Translate Concepts to Code: If the user asks for a concept, translate it into the exact keywords, classes, or library syntax a developer would type.
|
|
102
|
+
Example: "routing" ➡️ createRouter Route routeTree
|
|
103
|
+
Example: "entry point" ➡️ main SpringBootApplication bootstrap web.xml init
|
|
104
|
+
Example: "database schema" ➡️ Entity Column Table model schema
|
|
105
|
+
3. Include Known Framework Terms: If you know the language or framework of the codebase, append its standard terminology.
|
|
106
|
+
4. Target File Types/Structures: If looking for configurations, use terms like config properties yaml xml json.
|
|
107
|
+
5. Keep it space-separated: Output a single string of space-separated keywords without punctuation.
|
|
108
|
+
|
|
109
|
+
Examples:
|
|
110
|
+
|
|
111
|
+
User: "Where are the main react components and routing defined?"
|
|
112
|
+
Optimized Query: createRouter Route routeTree components layout
|
|
113
|
+
|
|
114
|
+
User: "Find the application entry point and main method."
|
|
115
|
+
Optimized Query: public static void main bootstrap init ApplicationContext configuration
|
|
116
|
+
|
|
117
|
+
User: "How is the user authentication handled?"
|
|
118
|
+
Optimized Query: login auth authenticate jwt token session passport
|
|
119
|
+
|
|
120
|
+
Input: [Insert human request here]
|
|
121
|
+
Output: [Return ONLY the optimized keyword string]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
89
126
|
## How the ranking works
|
|
90
127
|
|
|
91
128
|
1. **Keyword match** — does the filename contain query terms?
|
package/package.json
CHANGED
package/python/query_engine.py
CHANGED
|
@@ -60,6 +60,38 @@ class RankedFile:
|
|
|
60
60
|
)
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
@dataclass
|
|
64
|
+
class FileFeatures:
|
|
65
|
+
tokens: set[str]
|
|
66
|
+
annotations: set[str]
|
|
67
|
+
declarations: set[str]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _extract_file_features(content: str, extension: str) -> FileFeatures:
|
|
71
|
+
"""Extract structural heuristics and lowercase tokens from a file."""
|
|
72
|
+
# 1. Declarations: class X, interface Y, enum Z
|
|
73
|
+
decl_pattern = r"\b(?:class|interface|enum)\s+([A-Za-z0-9_]+)"
|
|
74
|
+
declarations = {m.group(1).lower() for m in re.finditer(decl_pattern, content)}
|
|
75
|
+
|
|
76
|
+
# 2. Annotations: @Controller, @Injectable
|
|
77
|
+
annotations = {m.group(1).lower() for m in re.finditer(r"@([A-Za-z0-9_]+)", content)}
|
|
78
|
+
|
|
79
|
+
# 3. Base tokens
|
|
80
|
+
tokens = set(re.findall(r"[a-z0-9]+", content.lower()))
|
|
81
|
+
|
|
82
|
+
# 4. CamelCase / PascalCase Splitting for structural languages
|
|
83
|
+
if extension in {".java", ".ts", ".tsx"}:
|
|
84
|
+
for word in re.findall(r"[A-Za-z]+", content):
|
|
85
|
+
if not word.islower() and not word.isupper() and len(word) > 2:
|
|
86
|
+
# Split CamelCase
|
|
87
|
+
sub = re.sub(r"([a-z])([A-Z])", r"\1 \2", word).lower()
|
|
88
|
+
for part in re.findall(r"[a-z0-9]+", sub):
|
|
89
|
+
if len(part) > 1:
|
|
90
|
+
tokens.add(part)
|
|
91
|
+
|
|
92
|
+
return FileFeatures(tokens=tokens, annotations=annotations, declarations=declarations)
|
|
93
|
+
|
|
94
|
+
|
|
63
95
|
def _tokenize(text: str) -> list[str]:
|
|
64
96
|
"""Lowercase, split on non-alphanumeric, remove stop words."""
|
|
65
97
|
tokens = re.findall(r"[a-z0-9]+", text.lower())
|
|
@@ -106,18 +138,26 @@ def _keyword_score(query_terms: list[str], file_path: str, idf_weights: dict[str
|
|
|
106
138
|
return score, matched
|
|
107
139
|
|
|
108
140
|
|
|
109
|
-
def _content_score(query_terms: list[str],
|
|
141
|
+
def _content_score(query_terms: list[str], features: "FileFeatures", idf_weights: dict[str, float]) -> tuple[float, list[str]]:
|
|
110
142
|
"""
|
|
111
143
|
Score how often query terms appear in the file's source code.
|
|
112
|
-
|
|
144
|
+
Applies massive multipliers for architectural declarations and annotations.
|
|
113
145
|
"""
|
|
114
|
-
|
|
115
|
-
matched = [t for t in query_terms if t in content_tokens]
|
|
116
|
-
|
|
117
|
-
if not query_terms:
|
|
146
|
+
if not features or not query_terms:
|
|
118
147
|
return 0.0, []
|
|
119
148
|
|
|
120
|
-
|
|
149
|
+
matched = [t for t in query_terms if t in features.tokens]
|
|
150
|
+
|
|
151
|
+
score = 0.0
|
|
152
|
+
for t in matched:
|
|
153
|
+
base_weight = idf_weights.get(t, 1.0)
|
|
154
|
+
multiplier = 1.0
|
|
155
|
+
if t in features.declarations:
|
|
156
|
+
multiplier = 10.0
|
|
157
|
+
elif t in features.annotations:
|
|
158
|
+
multiplier = 5.0
|
|
159
|
+
score += base_weight * multiplier
|
|
160
|
+
|
|
121
161
|
max_score = sum(idf_weights.get(t, 1.0) for t in query_terms)
|
|
122
162
|
if max_score > 0:
|
|
123
163
|
score = score / max_score
|
|
@@ -179,23 +219,27 @@ def query(
|
|
|
179
219
|
return []
|
|
180
220
|
|
|
181
221
|
# --- Pre-computation: Global TF-IDF ---
|
|
222
|
+
import pathlib
|
|
182
223
|
total_files = len(repo_graph.nodes)
|
|
183
224
|
document_frequency = {term: 0 for term in query_terms}
|
|
184
225
|
|
|
185
|
-
|
|
226
|
+
file_features_cache = {}
|
|
186
227
|
for path, node in repo_graph.nodes.items():
|
|
187
228
|
abs_path = node.path if hasattr(node, "absolute_path") else str(__import__("pathlib").Path(repo_graph.root) / path)
|
|
229
|
+
extension = pathlib.Path(path).suffix.lower()
|
|
230
|
+
|
|
188
231
|
try:
|
|
189
|
-
content = open(abs_path, encoding="utf-8").read()
|
|
190
|
-
|
|
191
|
-
|
|
232
|
+
content = open(abs_path, encoding="utf-8").read()
|
|
233
|
+
features = _extract_file_features(content, extension)
|
|
234
|
+
file_features_cache[path] = features
|
|
235
|
+
|
|
192
236
|
path_tokens = set(_path_tokens(path))
|
|
193
|
-
combined_tokens =
|
|
237
|
+
combined_tokens = features.tokens.union(path_tokens)
|
|
194
238
|
for term in query_terms:
|
|
195
239
|
if term in combined_tokens:
|
|
196
240
|
document_frequency[term] += 1
|
|
197
241
|
except Exception:
|
|
198
|
-
|
|
242
|
+
file_features_cache[path] = None
|
|
199
243
|
|
|
200
244
|
idf_weights = {}
|
|
201
245
|
for term in query_terms:
|
|
@@ -209,7 +253,7 @@ def query(
|
|
|
209
253
|
|
|
210
254
|
for path, node in repo_graph.nodes.items():
|
|
211
255
|
kscore, kterms = _keyword_score(query_terms, path, idf_weights)
|
|
212
|
-
cscore, cterms = _content_score(query_terms,
|
|
256
|
+
cscore, cterms = _content_score(query_terms, file_features_cache.get(path), idf_weights)
|
|
213
257
|
|
|
214
258
|
keyword_scores[path] = kscore
|
|
215
259
|
content_scores[path] = cscore
|