open-context-engine 0.1.0

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 (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +177 -0
  3. package/assets/brand/logo-lockup-dark.svg +14 -0
  4. package/assets/brand/logo-lockup.svg +14 -0
  5. package/assets/brand/logo.svg +9 -0
  6. package/bin/opencontextengine.mjs +64 -0
  7. package/docs/QUICKSTART.md +192 -0
  8. package/docs/RERANKER_API.md +32 -0
  9. package/package.json +81 -0
  10. package/requirements.txt +2 -0
  11. package/scripts/mcp-opencontextengine.mjs +37 -0
  12. package/scripts/retrieval-server.py +135 -0
  13. package/src/client.mjs +37 -0
  14. package/src/config.mjs +49 -0
  15. package/src/environment.mjs +10 -0
  16. package/src/eval/remote-models.mjs +70 -0
  17. package/src/mcp.mjs +58 -0
  18. package/src/retrieval/batched.py +220 -0
  19. package/src/retrieval/cascade.py +142 -0
  20. package/src/retrieval/engine.py +195 -0
  21. package/src/retrieval/entities.py +187 -0
  22. package/src/retrieval/languages/__init__.py +129 -0
  23. package/src/retrieval/languages/files.py +90 -0
  24. package/src/retrieval/languages/go.py +154 -0
  25. package/src/retrieval/languages/go_ast.go +204 -0
  26. package/src/retrieval/languages/go_types.go +169 -0
  27. package/src/retrieval/languages/python.py +113 -0
  28. package/src/retrieval/languages/schema.py +81 -0
  29. package/src/retrieval/languages/text.py +39 -0
  30. package/src/retrieval/languages/typescript.mjs +233 -0
  31. package/src/retrieval/languages/typescript.py +23 -0
  32. package/src/retrieval/live.py +273 -0
  33. package/src/retrieval/reranker.py +83 -0
  34. package/src/retrieval/routed.py +35 -0
  35. package/src/runtime.mjs +60 -0
  36. package/src/service.mjs +77 -0
  37. package/src/setup.mjs +66 -0
  38. package/src/workspaces.mjs +59 -0
@@ -0,0 +1,142 @@
1
+ """One neural ranking stage after cheap, parallel facet/structure retrieval.
2
+
3
+ Query-independent code structure is prepared once. Query facets interact with
4
+ stored code vectors numerically; they do not trigger separate neural reranks.
5
+ Selection considers both individual spans and complete small function bundles.
6
+ """
7
+ from collections import defaultdict
8
+ import math
9
+ import time
10
+
11
+ import numpy as np
12
+
13
+ from engine import Engine, document, post
14
+
15
+ VERSION = 'structural-cascade-v2'
16
+ POLICY = {'candidateLimit': 96, 'facetSeeds': 4, 'neighborsPerSeed': 10,
17
+ 'facetPool': 24, 'maxFunctionBudgetFraction': .45,
18
+ 'embeddingScoreTemperature': .06, 'functionCompletionFactor': 1.25}
19
+
20
+
21
+ class CascadeEngine(Engine):
22
+ def __init__(self, *args, **kwargs):
23
+ super().__init__(*args, **kwargs)
24
+ self.functions = defaultdict(list)
25
+ for u in self.units:
26
+ if u['kind'] == 'function':
27
+ self.functions[u['symbol']].append(u['id'])
28
+ for group in self.functions.values():
29
+ group.sort(key=lambda i: self.units[i]['start'])
30
+
31
+ def recall(self, queries, dense, facets):
32
+ pools, fused = [], defaultdict(float)
33
+ local_scores = []
34
+ for col, query in enumerate(queries):
35
+ lexical = self.lexical(query + (' ' + ' '.join(facets[col-1]['terms']) if col else ''))
36
+ local = defaultdict(float)
37
+ for scores in (dense[:, col], lexical):
38
+ for rank, uid in enumerate(np.argsort(-scores)[:60]):
39
+ if scores[uid] > 0:
40
+ local[int(uid)] += 1 / (30 + rank)
41
+ pool = sorted(local, key=lambda i: (-local[i], i))[:POLICY['facetPool']]
42
+ pools.append(pool)
43
+ local_scores.append(local)
44
+ for uid, score in local.items():
45
+ # Max preserves a facet-specific specialist; mean rewards breadth.
46
+ fused[uid] = max(fused[uid], score)
47
+ for uid in list(fused):
48
+ fused[uid] += .35 * sum(s.get(uid, 0) for s in local_scores) / len(queries)
49
+ seeds = set(uid for pool in pools for uid in pool[:POLICY['facetSeeds']])
50
+ expanded = set()
51
+ graph_prior = defaultdict(float)
52
+ for uid in seeds:
53
+ neighbors = set(self.units[uid]['edges'] + self.incoming[uid])
54
+ ranked = sorted(neighbors, key=lambda i: (-fused[i], i))[:POLICY['neighborsPerSeed']]
55
+ for neighbor in ranked:
56
+ expanded.add(neighbor)
57
+ graph_prior[neighbor] = max(graph_prior[neighbor], fused[uid] * .5)
58
+ candidates = set().union(*(set(pool) for pool in pools), expanded)
59
+ ordered = sorted(candidates, key=lambda i: (-(fused[i] + graph_prior[i]), i))
60
+ # Retain direct hits from every facet before applying the shared ceiling.
61
+ protected = list(dict.fromkeys(uid for pool in pools for uid in pool[:12]))
62
+ retained = list(dict.fromkeys(protected + ordered))[:POLICY['candidateLimit']]
63
+ return retained, expanded, len(candidates)
64
+
65
+ def pack(self, retained, scores, affinity, budget):
66
+ position = {uid: i for i, uid in enumerate(retained)}
67
+ actions = [(uid,) for uid in retained]
68
+ for group in self.functions.values():
69
+ if len(group) > 1 and all(uid in position for uid in group) and sum(self.costs[uid] for uid in group) <= budget * POLICY['maxFunctionBudgetFraction']:
70
+ actions.append(tuple(group))
71
+ relevance = np.asarray([scores[uid] for uid in retained])
72
+ values = relevance[:, None] * (.35 + .65 * affinity)
73
+ covered = np.zeros(affinity.shape[1])
74
+ selected, selected_set, trace, spent = [], set(), [], 0
75
+ while actions:
76
+ choices = []
77
+ for action in actions:
78
+ fresh = [uid for uid in action if uid not in selected_set]
79
+ cost = sum(self.costs[uid] for uid in fresh)
80
+ if not fresh or spent + cost > budget:
81
+ continue
82
+ indices = [position[uid] for uid in fresh]
83
+ benefit = values[indices].max(axis=0)
84
+ base = float(relevance[indices].max())
85
+ gain = .7 * float(np.mean(benefit / (1 + covered))) + .3 * base
86
+ if len(action) > 1:
87
+ gain *= POLICY['functionCompletionFactor']
88
+ gain /= (max(120, cost) / 300) ** .35
89
+ choices.append((gain, action, fresh, cost, benefit))
90
+ if not choices:
91
+ break
92
+ gain, action, fresh, cost, benefit = max(choices, key=lambda c: (c[0], -c[2][0]))
93
+ if gain < .015:
94
+ break
95
+ actions.remove(action)
96
+ selected.extend(fresh)
97
+ selected_set.update(fresh)
98
+ spent += cost
99
+ covered += benefit
100
+ trace.append({'ids': fresh, 'functionBundle': len(action) > 1,
101
+ 'tokens': cost, 'gain': gain, 'scores': [scores[i] for i in fresh]})
102
+ raw = '\n'.join(self.render(self.units[uid]) for uid in selected)
103
+ return raw, trace
104
+
105
+ def search(self, plan, budget=4000):
106
+ start = time.monotonic()
107
+ facets = plan['facets']
108
+ queries = [plan['intent']] + [f['question'] for f in facets]
109
+ # Encode repeated intent/facet text once, without caching across requests.
110
+ unique_queries = list(dict.fromkeys(queries))
111
+ result = post(self.embed_url + '/embeddings', {'model': 'Qwen3-Embedding-4B',
112
+ 'input': ['Instruct: Retrieve source code implementing the requested behavior.\nQuery: ' + q for q in unique_queries]}, self.embedding_key)
113
+ unique_vectors = [r['embedding'] for r in sorted(result['data'], key=lambda r: r['index'])]
114
+ vectors = np.asarray([unique_vectors[unique_queries.index(q)] for q in queries], dtype=np.float32)
115
+ embedded_at = time.monotonic()
116
+ dense = self.vectors @ vectors.T
117
+ retained, expanded, candidate_count = self.recall(queries, dense, facets)
118
+ recalled_at = time.monotonic()
119
+ reranked = post(self.reranker['baseUrl'] + '/rerank', {'model': self.reranker['model'],
120
+ 'query': plan['intent'], 'documents': [document(self.units[uid], 5000) for uid in retained]}, self.reranker['apiKey'])
121
+ rows = reranked['results']
122
+ if len(rows) != len(retained) or {r['index'] for r in rows} != set(range(len(retained))) or any(not math.isfinite(r['relevance_score']) or not 0 <= r['relevance_score'] <= 1 for r in rows):
123
+ raise ValueError('Invalid rerank mapping or scores')
124
+ scores = {retained[r['index']]: r['relevance_score'] for r in rows}
125
+ ranked_at = time.monotonic()
126
+ # Soft maximum relative to each facet's best candidate avoids assuming
127
+ # cosine similarities are calibrated cross-language probabilities.
128
+ facet_dense = dense[retained, 1:]
129
+ affinity = np.exp(np.minimum(0, facet_dense - facet_dense.max(axis=0)) / POLICY['embeddingScoreTemperature'])
130
+ raw, selected = self.pack(retained, scores, affinity, budget)
131
+ finished = time.monotonic()
132
+ return raw, {'version': VERSION, 'elapsedMs': round((finished-start)*1000),
133
+ 'tokens': len(self.encoding.encode(raw)), 'candidateCount': candidate_count,
134
+ 'rerankedCount': len(retained), 'expandedCount': len(expanded),
135
+ 'modelRequests': {'embedding': 1, 'rerank': 1}, 'queryCache': False,
136
+ 'timingMs': {'embedding': round((embedded_at-start)*1000), 'recall': round((recalled_at-embedded_at)*1000),
137
+ 'rerank': round((ranked_at-recalled_at)*1000), 'rerankModel': reranked.get('meta', {}).get('elapsed_ms'),
138
+ 'selection': round((finished-ranked_at)*1000)},
139
+ 'rerankInputTokens': reranked.get('usage', {}).get('input_tokens'),
140
+ 'candidates': [{'id': uid, 'score': scores[uid], 'path': self.units[uid]['path'],
141
+ 'start': self.units[uid]['start'], 'end': self.units[uid]['end']} for uid in retained],
142
+ 'selected': selected, 'plan': plan, 'policy': POLICY}
@@ -0,0 +1,195 @@
1
+ """Language-neutral retrieval over snapshot-bound source units. Models are called over HTTP, never loaded.
2
+
3
+ AST spans, conservative static links, lexical/dense fusion, facet reranking and
4
+ whole-span packing are repository independent. No evaluation labels are read.
5
+ """
6
+ from collections import Counter, defaultdict
7
+ import hashlib
8
+ import json
9
+ import math
10
+ import re
11
+ import time
12
+ from pathlib import Path
13
+ from urllib.request import Request, urlopen
14
+
15
+ import numpy as np
16
+ import tiktoken
17
+
18
+ from languages import source_units, adapter_manifest
19
+
20
+ VERSION = 'structural-units-v2'
21
+
22
+
23
+ def terms(text):
24
+ text = re.sub(r'([a-z])([A-Z])', r'\1 \2', text)
25
+ return re.findall(r'[a-z][a-z0-9]+', text.lower().replace('_', ' '))
26
+
27
+
28
+ def post(url, payload, key='local-only', timeout=120):
29
+ request = Request(url, json.dumps(payload).encode(), {
30
+ 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + key})
31
+ with urlopen(request, timeout=timeout) as response:
32
+ return json.load(response)
33
+
34
+
35
+ def document(u, limit=2200):
36
+ return ('Path: ' + u['path'] + '\nSymbol: ' + u['name'] + '\n' + u['text'])[:limit]
37
+
38
+
39
+ class Engine:
40
+ def __init__(self, units, vectors, embed_url, reranker, embedding_key='local-only', embedding_model='Qwen3-Embedding-4B'):
41
+ self.units, self.vectors = units, vectors
42
+ self.embed_url, self.reranker = embed_url, reranker
43
+ self.embedding_key = embedding_key
44
+ self.embedding_model = embedding_model
45
+ self.encoding = tiktoken.get_encoding('cl100k_base')
46
+ self.postings = defaultdict(list)
47
+ lengths = []
48
+ for u in units:
49
+ counts = Counter(terms((u['path'] + ' ' + u['name'] + ' ') * 3 + u['text']))
50
+ lengths.append(sum(counts.values()))
51
+ for term, frequency in counts.items():
52
+ self.postings[term].append((u['id'], frequency))
53
+ self.lengths = np.asarray(lengths)
54
+ self.average_length = max(1, self.lengths.mean())
55
+ self.incoming = defaultdict(list)
56
+ for u in units:
57
+ for target in u['edges']:
58
+ self.incoming[target].append(u['id'])
59
+ self.costs = [len(self.encoding.encode(self.render(u))) + 2 for u in units]
60
+
61
+ @staticmethod
62
+ def render(u):
63
+ # Text units preserve Unicode separators inside a physical source line.
64
+ lines = u['text'].split('\n') if u.get('language') in {'text', 'javascript', 'typescript', 'go'} else u['text'].splitlines()
65
+ return f"Path: {u['path']}\n" + '\n'.join(f'{i}\t{line}' for i, line in
66
+ enumerate(lines, u['start'])) + '\n'
67
+
68
+ def lexical(self, query):
69
+ scores = np.zeros(len(self.units))
70
+ for term in set(terms(query)):
71
+ matches = self.postings.get(term, [])
72
+ if not matches:
73
+ continue
74
+ ids, tf = np.asarray(matches).T
75
+ idf = math.log(1 + (len(self.units) - len(ids) + .5) / (len(ids) + .5))
76
+ scores[ids] += idf * tf * 2.2 / (tf + 1.2 * (.25 + .75 * self.lengths[ids] / self.average_length))
77
+ return scores
78
+
79
+ def rerank(self, query, ids):
80
+ if not ids:
81
+ return {}
82
+ data = post(self.reranker['baseUrl'] + '/rerank', {'model': self.reranker['model'],
83
+ 'query': query, 'documents': [document(self.units[i], 5000) for i in ids]}, self.reranker['apiKey'])
84
+ if len(data['results']) != len(ids):
85
+ raise ValueError('Incomplete rerank response')
86
+ return {ids[r['index']]: r['relevance_score'] for r in data['results']}
87
+
88
+ def search(self, plan, budget=4000):
89
+ start = time.monotonic()
90
+ facets = plan['facets']
91
+ queries = [plan['intent']] + [f['question'] for f in facets]
92
+ embedded = post(self.embed_url + '/embeddings', {'model': self.embedding_model,
93
+ 'input': ['Instruct: Retrieve source code implementing the requested behavior.\nQuery: ' + q for q in queries]}, self.embedding_key)
94
+ qvectors = np.asarray([r['embedding'] for r in sorted(embedded['data'], key=lambda r: r['index'])], dtype=np.float32)
95
+ dense = self.vectors @ qvectors.T
96
+ pools, fused = [], defaultdict(float)
97
+ for col, query in enumerate(queries):
98
+ lexical = self.lexical(query + (' ' + ' '.join(facets[col-1]['terms']) if col else ''))
99
+ local = defaultdict(float)
100
+ for scores in (dense[:, col], lexical):
101
+ for rank, uid in enumerate(np.argsort(-scores)[:40]):
102
+ if scores[uid] > 0:
103
+ local[int(uid)] += 1 / (30 + rank)
104
+ pool = sorted(local, key=local.get, reverse=True)[:28]
105
+ pools.append(pool)
106
+ for uid, score in local.items():
107
+ fused[uid] += score
108
+ facet_scores = []
109
+ for query, pool in zip(queries, pools):
110
+ facet_scores.append(self.rerank(query, pool))
111
+ seeds = set()
112
+ for scores in facet_scores:
113
+ seeds.update(sorted(scores, key=scores.get, reverse=True)[:3])
114
+ candidates = set().union(*(set(s) for s in facet_scores))
115
+ expanded = set()
116
+ for uid in seeds:
117
+ # Only statically resolved links. Dynamic receivers are not guessed.
118
+ neighbors = self.units[uid]['edges'] + self.incoming[uid]
119
+ expanded.update(sorted(set(neighbors), key=lambda x: fused[x], reverse=True)[:10])
120
+ candidates.update(expanded)
121
+ ranked = sorted(candidates, key=lambda uid: max(s.get(uid, 0) for s in facet_scores) + min(.15, fused[uid]), reverse=True)
122
+ retained = ranked[:80]
123
+ # Ensure graph-only discoveries get a chance in the joint rerank.
124
+ retained = list(dict.fromkeys(retained[:64] + sorted(expanded, key=lambda x: fused[x], reverse=True)[:16]))
125
+ overall = self.rerank(plan['intent'], retained)
126
+ # Score newly discovered graph spans against each facet, too.
127
+ for col, facet in enumerate(facets, 1):
128
+ missing = [uid for uid in retained if uid not in facet_scores[col]]
129
+ facet_scores[col].update(self.rerank(facet['question'], missing))
130
+ covered = np.zeros(len(facets))
131
+ selected, spent, trace = [], 0, []
132
+ available = set(retained)
133
+ while available:
134
+ choices = []
135
+ for uid in available:
136
+ if spent + self.costs[uid] > budget:
137
+ continue
138
+ values = np.asarray([s.get(uid, 0) for s in facet_scores[1:]])
139
+ # Diminishing coverage gain keeps later stages from being crowded out.
140
+ gain = float(np.sum(values / (1 + covered))) / len(facets)
141
+ gain = .7 * gain + .3 * overall.get(uid, 0)
142
+ gain /= (max(120, self.costs[uid]) / 300) ** .35
143
+ choices.append((gain, uid, values))
144
+ if not choices:
145
+ break
146
+ gain, uid, values = max(choices, key=lambda item: (item[0], -item[1]))
147
+ if gain < .015:
148
+ break
149
+ available.remove(uid)
150
+ selected.append(uid)
151
+ spent += self.costs[uid]
152
+ covered += values
153
+ trace.append({'id': uid, 'overall': overall.get(uid, 0), 'facets': values.tolist(),
154
+ 'tokens': self.costs[uid], 'gain': gain, 'graphExpanded': uid in expanded})
155
+ raw = '\n'.join(self.render(self.units[uid]) for uid in selected)
156
+ return raw, {'elapsedMs': round((time.monotonic()-start)*1000), 'tokens': len(self.encoding.encode(raw)),
157
+ 'candidateCount': len(candidates), 'rerankedCount': len(retained), 'expandedCount': len(expanded),
158
+ 'selected': trace, 'plan': plan}
159
+
160
+
161
+ def build_index(root, snapshot, state, embed_url):
162
+ state = Path(state)
163
+ state.mkdir(parents=True, exist_ok=True)
164
+ adapters = adapter_manifest(snapshot['files'], snapshot.get('languageOptions'))
165
+ identity = hashlib.sha256((VERSION + json.dumps(snapshot, sort_keys=True)
166
+ + json.dumps(adapters, sort_keys=True)).encode()).hexdigest()
167
+ metadata = state / 'metadata.json'
168
+ if metadata.exists() and json.loads(metadata.read_text())['identity'] == identity:
169
+ return json.loads((state / 'units.json').read_text()), np.load(state / 'vectors.npy'), {
170
+ **json.loads(metadata.read_text()), 'cacheHit': True}
171
+ start = time.monotonic()
172
+ selection = {}
173
+ units = source_units(root, snapshot['files'], language_options=snapshot.get('languageOptions'), report=selection)
174
+ if not units:
175
+ raise ValueError('No indexable nonempty source text in snapshot')
176
+ vectors = []
177
+ for offset in range(0, len(units), 64):
178
+ result = post(embed_url + '/embeddings', {'model': 'Qwen3-Embedding-4B',
179
+ 'input': [document(u) for u in units[offset:offset+64]]}, timeout=300)
180
+ rows = sorted(result['data'], key=lambda r: r['index'])
181
+ vectors.extend(r['embedding'] for r in rows)
182
+ if offset % 512 == 0:
183
+ print(json.dumps({'stage': 'indexing', 'done': len(vectors), 'total': len(units)}), flush=True)
184
+ matrix = np.asarray(vectors, dtype=np.float32)
185
+ if matrix.shape != (len(units), 1024) or not np.isfinite(matrix).all():
186
+ raise ValueError('Invalid index vectors')
187
+ (state / 'units.json').write_text(json.dumps(units))
188
+ np.save(state / 'vectors.npy', matrix)
189
+ summary = {'identity': identity, 'version': VERSION, 'units': len(units),
190
+ 'files': len(snapshot['files']), 'nonemptyFiles': len({u['path'] for u in units}),
191
+ 'selection': selection,
192
+ 'languageAdapters': adapters, 'languageUnits': dict(Counter(u['language'] for u in units)),
193
+ 'resolvedEdges': sum(len(u['edges']) for u in units), 'indexingMs': round((time.monotonic()-start)*1000), 'cacheHit': False}
194
+ metadata.write_text(json.dumps(summary, indent=2))
195
+ return units, matrix, summary
@@ -0,0 +1,187 @@
1
+ """Retrieve and assemble source entities, retaining their constituent spans.
2
+
3
+ Each entity has multiple stored vectors (one per source span). Cheap maximum
4
+ interaction with query facets locates entities; a single neural ranking stage
5
+ scores their representative code. Static links then supply bounded evidence
6
+ completion without another model call. No task-specific symbols or labels.
7
+ """
8
+ from collections import defaultdict
9
+ import math
10
+ import time
11
+ import numpy as np
12
+
13
+ from engine import Engine, post
14
+
15
+ VERSION = 'entity-cascade-v3'
16
+ POLICY = {'candidateEntities': 80, 'perFacetEntities': 32, 'anchorEntities': 8,
17
+ 'graphHops': 2, 'maxAssembledEntities': 128, 'maxAtomicBudgetFraction': .65,
18
+ 'facetTemperature': .06, 'documentChars': 5000}
19
+
20
+
21
+ class EntityEngine(Engine):
22
+ def __init__(self, *args, **kwargs):
23
+ super().__init__(*args, **kwargs)
24
+ groups = defaultdict(list)
25
+ for u in self.units:
26
+ groups[(u['symbol'], u['kind'])].append(u['id'])
27
+ self.entities = []
28
+ self.unit_entity = np.zeros(len(self.units), dtype=np.int32)
29
+ for (symbol, kind), ids in groups.items():
30
+ eid = len(self.entities)
31
+ ids.sort(key=lambda i: self.units[i]['start'])
32
+ self.entities.append({'symbol': symbol, 'kind': kind, 'ids': ids,
33
+ 'cost': sum(self.costs[uid] for uid in ids)})
34
+ self.unit_entity[ids] = eid
35
+ self.links = [dict() for _ in self.entities]
36
+ for u in self.units:
37
+ source = int(self.unit_entity[u['id']])
38
+ for target_unit in u['edges']:
39
+ target = int(self.unit_entity[target_unit])
40
+ if source == target:
41
+ continue
42
+ other = self.units[target_unit]
43
+ # Parent context is weaker than a statically resolved callee.
44
+ forward = .45 if u['owner'] == other['symbol'] else .85
45
+ reverse = .65 if u['owner'] == other['symbol'] else .55
46
+ self.links[source][target] = max(self.links[source].get(target, 0), forward)
47
+ self.links[target][source] = max(self.links[target].get(source, 0), reverse)
48
+
49
+ def aggregate(self, unit_scores):
50
+ shape = (len(self.entities),) + unit_scores.shape[1:]
51
+ scores = np.full(shape, -np.inf, dtype=np.float32)
52
+ np.maximum.at(scores, self.unit_entity, unit_scores)
53
+ return scores
54
+
55
+ def candidate_entities(self, queries, facets, dense):
56
+ fused, pools = defaultdict(float), []
57
+ for col, query in enumerate(queries):
58
+ lex = self.aggregate(self.lexical(query + (' ' + ' '.join(facets[col-1]['terms']) if col else '')))
59
+ local = defaultdict(float)
60
+ for scores in [dense[:, col], lex]:
61
+ for rank, eid in enumerate(np.argsort(-scores)[:80]):
62
+ if scores[eid] > 0:
63
+ local[int(eid)] += 1 / (30 + rank)
64
+ ordered = sorted(local, key=lambda i: (-local[i], i))[:POLICY['perFacetEntities']]
65
+ pools.append(ordered)
66
+ for eid, score in local.items():
67
+ fused[eid] = max(fused[eid], score)
68
+ protected = list(dict.fromkeys(eid for pool in pools for eid in pool[:12]))
69
+ available = set(eid for pool in pools for eid in pool)
70
+ ordered = sorted(available, key=lambda i: (-fused[i], i))
71
+ return list(dict.fromkeys(protected + ordered))[:POLICY['candidateEntities']]
72
+
73
+ def representation(self, eid, unit_dense):
74
+ entity = self.entities[eid]
75
+ ids = entity['ids']
76
+ # Long functions are represented by their strongest matching code spans,
77
+ # not always their prefix. Every selected span remains source anchored.
78
+ ranked = sorted(ids, key=lambda i: (-float(unit_dense[i].max()), i))
79
+ header = self.units[ids[0]]
80
+ parts = [f"Path: {header['path']}\nSymbol: {header['name']}\n"]
81
+ remaining = POLICY['documentChars'] - len(parts[0])
82
+ for uid in ranked:
83
+ u = self.units[uid]
84
+ value = f"Lines {u['start']}-{u['end']}:\n{u['text']}\n"
85
+ parts.append(value[:remaining])
86
+ remaining -= min(len(value), remaining)
87
+ if remaining <= 0:
88
+ break
89
+ return ''.join(parts)
90
+
91
+ def complete(self, candidates, scores, dense):
92
+ expanded_scores = dict(scores)
93
+ provenance = {}
94
+ anchors = sorted(candidates, key=lambda i: (-scores[i], i))[:POLICY['anchorEntities']]
95
+ frontier = {eid: scores[eid] for eid in anchors if scores[eid] >= .15}
96
+ visited = set(frontier)
97
+ for hop in range(POLICY['graphHops']):
98
+ next_frontier = {}
99
+ for source, confidence in frontier.items():
100
+ neighbors = self.links[source]
101
+ # Rank by semantic compatibility before following broad class links.
102
+ ordered = sorted(neighbors, key=lambda i: (-float(dense[i].max()), i))[:16]
103
+ for target in ordered:
104
+ compatibility = float(np.exp(min(0, float(dense[target].max()-dense[source].max())) / .12))
105
+ proposal = confidence * neighbors[target] * (.4 + .6 * compatibility)
106
+ if proposal > expanded_scores.get(target, 0):
107
+ expanded_scores[target] = proposal
108
+ provenance[target] = {'anchor': source, 'hop': hop+1, 'kind': 'static-relationship', 'inferredScore': proposal}
109
+ if target not in visited and proposal > .05:
110
+ next_frontier[target] = max(next_frontier.get(target, 0), proposal)
111
+ visited.update(next_frontier)
112
+ frontier = next_frontier
113
+ selected = sorted(expanded_scores, key=lambda i: (-expanded_scores[i], i))[:POLICY['maxAssembledEntities']]
114
+ return selected, expanded_scores, provenance
115
+
116
+ def pack_entities(self, entities, scores, dense, unit_dense, budget):
117
+ affinity = np.exp(np.minimum(0, dense[:, 1:]-dense[:, 1:].max(axis=0)) / POLICY['facetTemperature'])
118
+ covered = np.zeros(affinity.shape[1])
119
+ actions = []
120
+ for eid in entities:
121
+ entity = self.entities[eid]
122
+ if entity['cost'] <= budget * POLICY['maxAtomicBudgetFraction']:
123
+ actions.append((eid, tuple(entity['ids'])))
124
+ else:
125
+ # Oversized entities remain accessible at their original AST spans.
126
+ actions.extend((eid, (uid,)) for uid in entity['ids'])
127
+ spent, selected, trace = 0, [], []
128
+ while actions:
129
+ best = None
130
+ for eid, ids in actions:
131
+ cost = sum(self.costs[i] for i in ids)
132
+ if spent+cost > budget:
133
+ continue
134
+ facet = affinity[eid]
135
+ if len(ids) == 1 and len(self.entities[eid]['ids']) > 1:
136
+ facet = np.exp(np.minimum(0, unit_dense[ids[0], 1:]-dense[:, 1:].max(axis=0)) / POLICY['facetTemperature'])
137
+ value = scores[eid] * (.35 + .65 * facet)
138
+ gain = .7 * float(np.mean(value/(1+covered))) + .3 * scores[eid]
139
+ gain /= (max(120, cost)/300) ** .35
140
+ choice = (gain, eid, ids, cost, value)
141
+ if best is None or gain > best[0]:
142
+ best = choice
143
+ if best is None or best[0] < .015:
144
+ break
145
+ gain, eid, ids, cost, value = best
146
+ actions.remove((eid, ids))
147
+ selected.extend(ids)
148
+ spent += cost
149
+ covered += value
150
+ trace.append({'entity': eid, 'ids': list(ids), 'symbol': self.entities[eid]['symbol'],
151
+ 'tokens': cost, 'score': scores[eid], 'completeEntity': len(ids)==len(self.entities[eid]['ids'])})
152
+ return '\n'.join(self.render(self.units[i]) for i in selected), trace
153
+
154
+ def search(self, plan, budget=4000):
155
+ start = time.monotonic()
156
+ facets = plan['facets']
157
+ queries = [plan['intent']] + [f['question'] for f in facets]
158
+ unique = list(dict.fromkeys(queries))
159
+ result = post(self.embed_url+'/embeddings', {'model':'Qwen3-Embedding-4B',
160
+ 'input':['Instruct: Retrieve source code implementing the requested behavior.\nQuery: '+q for q in unique]}, self.embedding_key)
161
+ vectors = [r['embedding'] for r in sorted(result['data'],key=lambda r:r['index'])]
162
+ embedded = time.monotonic()
163
+ unit_dense = self.vectors @ np.asarray([vectors[unique.index(q)] for q in queries],dtype=np.float32).T
164
+ dense = self.aggregate(unit_dense)
165
+ candidates = self.candidate_entities(queries,facets,dense)
166
+ documents = [self.representation(eid,unit_dense) for eid in candidates]
167
+ recalled = time.monotonic()
168
+ response = post(self.reranker['baseUrl']+'/rerank',{'model':self.reranker['model'],
169
+ 'query':plan['intent'],'documents':documents},self.reranker['apiKey'])
170
+ rows = response['results']
171
+ if len(rows)!=len(candidates) or {r['index'] for r in rows}!=set(range(len(candidates))) or any(not math.isfinite(r['relevance_score']) or not 0<=r['relevance_score']<=1 for r in rows):
172
+ raise ValueError('Invalid rerank response')
173
+ scores = {candidates[r['index']]:r['relevance_score'] for r in rows}
174
+ ranked = time.monotonic()
175
+ assembled, expanded_scores, provenance = self.complete(candidates,scores,dense)
176
+ raw,selected = self.pack_entities(assembled,expanded_scores,dense,unit_dense,budget)
177
+ finished = time.monotonic()
178
+ return raw,{'version':VERSION,'elapsedMs':round((finished-start)*1000),'tokens':len(self.encoding.encode(raw)),
179
+ 'queryCache':False,'modelRequests':{'embedding':1,'rerank':1},'candidateCount':len(candidates),
180
+ 'expandedCount':len(provenance),'assembledCount':len(assembled),'policy':POLICY,'plan':plan,
181
+ 'timingMs':{'embedding':round((embedded-start)*1000),'recall':round((recalled-embedded)*1000),
182
+ 'rerank':round((ranked-recalled)*1000),'rerankModel':response.get('meta',{}).get('elapsed_ms'),
183
+ 'selection':round((finished-ranked)*1000)},'rerankInputTokens':response.get('usage',{}).get('input_tokens'),
184
+ 'candidates':[{'entity':eid,'ids':self.entities[eid]['ids'],'symbol':self.entities[eid]['symbol'],
185
+ 'score':expanded_scores[eid],'source':'neural+graph' if eid in scores and eid in provenance else 'neural' if eid in scores else 'graph',
186
+ 'spans':[{'path':self.units[i]['path'],'start':self.units[i]['start'],'end':self.units[i]['end']} for i in self.entities[eid]['ids']]} for eid in assembled],
187
+ 'selected':selected,'provenance':provenance}
@@ -0,0 +1,129 @@
1
+ """Snapshot-bound language adapters; downstream retrieval consumes only CodeUnit."""
2
+ from collections import defaultdict
3
+ from copy import deepcopy
4
+ import hashlib
5
+ import json
6
+ from pathlib import Path, PurePosixPath
7
+ import sys
8
+
9
+ from . import python, typescript, text, go
10
+ from .files import path_exclusion, read_text
11
+ from .schema import SCHEMA_VERSION, SourceFile, validate_units
12
+
13
+ ADAPTERS = {'python': python, 'typescript': typescript, 'javascript': typescript, 'go': go, 'text': text}
14
+ EXTENSIONS = {'.py': 'python', '.ts': 'typescript', '.tsx': 'typescript',
15
+ '.mts': 'typescript', '.cts': 'typescript',
16
+ '.go': 'go', '.js': 'javascript', '.jsx': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript'}
17
+
18
+
19
+ def language_for(path):
20
+ return EXTENSIONS.get(PurePosixPath(path).suffix.lower(), 'text')
21
+
22
+
23
+ def adapter_manifest(files, language_options=None):
24
+ languages = sorted({language_for(file['path']) for file in files})
25
+ options = language_options or {}
26
+ if not isinstance(options, dict) or set(options) - set(ADAPTERS):
27
+ raise ValueError('Unknown language options')
28
+ paths = [Path(__file__), Path(__file__).with_name('schema.py'), Path(__file__).with_name('files.py')]
29
+ for language in languages:
30
+ paths.append(Path(ADAPTERS[language].__file__))
31
+ if language in {'typescript', 'javascript'}:
32
+ paths.append(Path(typescript.__file__).with_suffix('.mjs'))
33
+ if language == 'go':
34
+ paths.append(Path(go.__file__).with_name('go_ast.go'))
35
+ paths.append(Path(go.__file__).with_name('go_types.go'))
36
+ return {'schemaVersion': SCHEMA_VERSION, 'languages': languages, 'options': options,
37
+ 'parsers': {language: (f'python-ast-{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}'
38
+ if language == 'python' else f'typescript-{typescript.COMPILER_VERSION}'
39
+ if language in {'typescript', 'javascript'} else go.compiler()[1]
40
+ if language == 'go' else text.VERSION)
41
+ for language in languages},
42
+ 'sourceSha256': {path.name: hashlib.sha256(path.read_bytes()).hexdigest() for path in paths}}
43
+
44
+
45
+ def source_units(root, files, max_lines=65, language_options=None, report=None, cache=None):
46
+ if type(max_lines) is not int or max_lines < 1:
47
+ raise ValueError('max_lines must be a positive integer')
48
+ root = Path(root).resolve()
49
+ groups, sources, seen, excluded = defaultdict(list), [], set(), []
50
+ options = language_options or {}
51
+ adapter_manifest(files, options) # Validate configuration before reading source.
52
+ for file in files:
53
+ name = file['path']
54
+ path = PurePosixPath(name)
55
+ if (path.is_absolute() or '..' in path.parts or '\\' in name or str(path) != name
56
+ or name in seen):
57
+ raise ValueError('Invalid or duplicate snapshot path: ' + name)
58
+ seen.add(name)
59
+ resolved = (root/name).resolve()
60
+ if not resolved.is_relative_to(root):
61
+ raise ValueError('Source escapes snapshot root: ' + name)
62
+ reason = path_exclusion(name)
63
+ if (root/name).is_symlink():
64
+ reason = 'symlink'
65
+ if reason:
66
+ excluded.append({'path': name, 'reason': reason})
67
+ continue
68
+ raw, content, reason = read_text(resolved)
69
+ if reason:
70
+ excluded.append({'path': name, 'reason': reason})
71
+ continue
72
+ if hashlib.sha256(raw).hexdigest() != file['sha256']:
73
+ raise ValueError('Source changed: ' + name)
74
+ source = SourceFile(name, content, file['sha256'])
75
+ sources.append(source)
76
+ language = language_for(name)
77
+ groups['typescript' if language == 'javascript' else language].append(source)
78
+ by_path = defaultdict(list)
79
+ identities = {}
80
+ pending_cache = {}
81
+ for language, subset in groups.items():
82
+ settings = options.get(language)
83
+ if language == 'go':
84
+ settings = go.settings(settings, sources)
85
+ if language == 'typescript' and 'javascript' in options:
86
+ if settings is not None and settings != options['javascript']:
87
+ raise ValueError('JavaScript and TypeScript share compiler options')
88
+ settings = options['javascript']
89
+ # Bound cache lifetime to the current snapshot. Structural languages are
90
+ # invalidated together so imports and callers in unchanged files refresh.
91
+ batches = [[source] for source in subset] if cache is not None and language == 'text' else [subset]
92
+ units = []
93
+ for batch in batches:
94
+ key = (language, batch[0].path if language == 'text' else '')
95
+ fingerprint = (hashlib.sha256(json.dumps([
96
+ [(s.path, s.sha256) for s in batch], max_lines, settings,
97
+ adapter_manifest([{'path': s.path} for s in batch], options),
98
+ ], sort_keys=True).encode()).hexdigest() if cache is not None else None)
99
+ previous = cache.get(key) if cache is not None else None
100
+ extracted = (deepcopy(previous[1]) if previous and previous[0] == fingerprint
101
+ else ADAPTERS[language].extract(batch, max_lines, settings))
102
+ if cache is not None:
103
+ pending_cache[key] = (fingerprint, deepcopy(extracted))
104
+ offset = len(units)
105
+ for unit in extracted:
106
+ unit['id'] += offset
107
+ for relation in unit['relations']:
108
+ relation['target'] += offset
109
+ unit['edges'] = [target + offset for target in unit['edges']]
110
+ units.extend(extracted)
111
+ validate_units(units, subset)
112
+ for unit in units:
113
+ by_path[unit['path']].append(unit)
114
+ identities[id(unit)] = (language, unit['id'])
115
+ units = [unit for source in sources for unit in by_path[source.path]]
116
+ remap = {identities[id(unit)]: i for i, unit in enumerate(units)}
117
+ for i, unit in enumerate(units):
118
+ unit['id'] = i
119
+ for relation in unit['relations']:
120
+ relation['target'] = remap[(identities[id(unit)][0], relation['target'])]
121
+ unit['edges'] = sorted({relation['target'] for relation in unit['relations']})
122
+ validate_units(units, sources)
123
+ if cache is not None:
124
+ cache.clear()
125
+ cache.update(pending_cache)
126
+ if report is not None:
127
+ report.update(inputFiles=len(files), acceptedFiles=len(sources), excluded=excluded,
128
+ fallbackFiles=sum(language_for(source.path) == 'text' for source in sources))
129
+ return units