codegraph-engine 3.0.0__1-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 (130) hide show
  1. codegraph/__init__.py +37 -0
  2. codegraph/agent.py +26 -0
  3. codegraph/agent_brain.py +2212 -0
  4. codegraph/agent_capabilities.py +4058 -0
  5. codegraph/agent_rules.py +531 -0
  6. codegraph/api_drift.py +455 -0
  7. codegraph/architecture.py +371 -0
  8. codegraph/async_queue/__init__.py +50 -0
  9. codegraph/async_queue/detectors/__init__.py +48 -0
  10. codegraph/async_queue/detectors/celery.py +160 -0
  11. codegraph/async_queue/detectors/kafka.py +168 -0
  12. codegraph/async_queue/detectors/rabbitmq.py +116 -0
  13. codegraph/async_queue/detectors/redis.py +130 -0
  14. codegraph/async_queue/detectors/sqs.py +135 -0
  15. codegraph/async_queue/models.py +161 -0
  16. codegraph/async_queue/otel_models.py +74 -0
  17. codegraph/async_queue/otel_parser.py +337 -0
  18. codegraph/async_queue/reconciler.py +427 -0
  19. codegraph/async_queue/scanner.py +291 -0
  20. codegraph/async_queue/traversal.py +447 -0
  21. codegraph/audit.py +106 -0
  22. codegraph/binding_resolver.py +616 -0
  23. codegraph/branch_comparison.py +197 -0
  24. codegraph/cache.py +95 -0
  25. codegraph/change_impact.py +547 -0
  26. codegraph/cli.py +2436 -0
  27. codegraph/cli_output.py +205 -0
  28. codegraph/config.py +45 -0
  29. codegraph/constraints.py +238 -0
  30. codegraph/context.py +2182 -0
  31. codegraph/context_freshness.py +351 -0
  32. codegraph/database/__init__.py +72 -0
  33. codegraph/database/extractor.py +3651 -0
  34. codegraph/database/interrogation.py +1494 -0
  35. codegraph/database/models.py +310 -0
  36. codegraph/database/schema_drift.py +862 -0
  37. codegraph/epistemic.py +200 -0
  38. codegraph/errors.py +282 -0
  39. codegraph/evidence/__init__.py +15 -0
  40. codegraph/evidence/citations.py +418 -0
  41. codegraph/evidence_contract.py +486 -0
  42. codegraph/frameworks.py +960 -0
  43. codegraph/frameworks_nestjs.py +502 -0
  44. codegraph/frameworks_nextjs.py +337 -0
  45. codegraph/frameworks_react.py +457 -0
  46. codegraph/freshness.py +335 -0
  47. codegraph/frontend_html.py +109 -0
  48. codegraph/git.py +362 -0
  49. codegraph/git_state.py +545 -0
  50. codegraph/graph/__init__.py +46 -0
  51. codegraph/graph/models.py +56 -0
  52. codegraph/graph/traversal.py +1993 -0
  53. codegraph/indexing/__init__.py +24 -0
  54. codegraph/indexing/classifier.py +564 -0
  55. codegraph/indexing/indexer.py +2578 -0
  56. codegraph/indexing/models.py +427 -0
  57. codegraph/indexing/parser.py +3404 -0
  58. codegraph/indexing/pool.py +136 -0
  59. codegraph/indexing/scanner.py +211 -0
  60. codegraph/indexing/telemetry.py +326 -0
  61. codegraph/indexing/templates.py +418 -0
  62. codegraph/indexing/test_framework.py +116 -0
  63. codegraph/installer.py +1702 -0
  64. codegraph/interrogation.py +2355 -0
  65. codegraph/llm/__init__.py +3 -0
  66. codegraph/llm/base.py +15 -0
  67. codegraph/llm/context.py +20 -0
  68. codegraph/mcp/__init__.py +3 -0
  69. codegraph/mcp/server.py +1476 -0
  70. codegraph/mcp_diagnostics.py +882 -0
  71. codegraph/memory/__init__.py +3 -0
  72. codegraph/memory/store.py +46 -0
  73. codegraph/models.py +310 -0
  74. codegraph/monorepo.py +925 -0
  75. codegraph/observability.py +262 -0
  76. codegraph/optimizer.py +1014 -0
  77. codegraph/planner.py +417 -0
  78. codegraph/process_lifecycle.py +1153 -0
  79. codegraph/py.typed +1 -0
  80. codegraph/query_expansion.py +199 -0
  81. codegraph/ranking.py +408 -0
  82. codegraph/refactor/__init__.py +36 -0
  83. codegraph/refactor/models.py +114 -0
  84. codegraph/refactor/renamer.py +844 -0
  85. codegraph/refactor/transactions.py +414 -0
  86. codegraph/resolver.py +3968 -0
  87. codegraph/resources/__init__.py +46 -0
  88. codegraph/resources/cache.py +117 -0
  89. codegraph/resources/coalescer.py +83 -0
  90. codegraph/resources/debouncer.py +98 -0
  91. codegraph/resources/governor.py +219 -0
  92. codegraph/resources/monitor.py +102 -0
  93. codegraph/resources/policy.py +123 -0
  94. codegraph/retrieval_policy.py +320 -0
  95. codegraph/roblox.py +2174 -0
  96. codegraph/route_composer.py +363 -0
  97. codegraph/route_topology.py +172 -0
  98. codegraph/runtime/__init__.py +51 -0
  99. codegraph/runtime/collector.py +155 -0
  100. codegraph/runtime/db_watcher.py +191 -0
  101. codegraph/runtime/ingestor.py +692 -0
  102. codegraph/runtime/models.py +130 -0
  103. codegraph/runtime/port_inspector.py +304 -0
  104. codegraph/runtime/reconciliation.py +492 -0
  105. codegraph/runtime/runner.py +353 -0
  106. codegraph/runtime/trace_linker.py +175 -0
  107. codegraph/search/__init__.py +25 -0
  108. codegraph/search/hybrid.py +846 -0
  109. codegraph/search/semantic.py +28 -0
  110. codegraph/security/__init__.py +51 -0
  111. codegraph/security/paths.py +189 -0
  112. codegraph/security/redaction.py +633 -0
  113. codegraph/semantic_conflicts.py +270 -0
  114. codegraph/semantic_decorators.py +372 -0
  115. codegraph/structural_diff.py +542 -0
  116. codegraph/styles.py +144 -0
  117. codegraph/symbol_history.py +277 -0
  118. codegraph/target_resolver.py +348 -0
  119. codegraph/task.py +637 -0
  120. codegraph/tool_selection_eval.py +1158 -0
  121. codegraph/tsconfig.py +236 -0
  122. codegraph/ui/__init__.py +6 -0
  123. codegraph/ui/server.py +755 -0
  124. codegraph/watcher.py +323 -0
  125. codegraph_engine-3.0.0.dist-info/METADATA +859 -0
  126. codegraph_engine-3.0.0.dist-info/RECORD +130 -0
  127. codegraph_engine-3.0.0.dist-info/WHEEL +6 -0
  128. codegraph_engine-3.0.0.dist-info/entry_points.txt +2 -0
  129. codegraph_engine-3.0.0.dist-info/licenses/LICENSE +21 -0
  130. codegraph_engine-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,427 @@
1
+ """Data models for the indexing, symbol identity, and reference resolution pipeline.
2
+
3
+ Normalization rules for canonical symbol IDs:
4
+ 1. Path separators ('\\' and '/') are normalized to '/'.
5
+ 2. Leading './' and '/' prefixes are stripped.
6
+ 3. Supported source extensions (.py, .pyi, .ts, .tsx, .js, .jsx, .mjs, .cjs) are stripped.
7
+ 4. Package index filenames ('/__init__' for Python, '/index' for JS/TS) are collapsed to
8
+ their parent package path (e.g. 'src/auth/__init__.py' -> 'src.auth',
9
+ 'src/auth/index.ts' -> 'src.auth'). Root-level '__init__.py' or 'index.ts' retain
10
+ '__init__' or 'index'.
11
+ 5. Path segments are joined with '.' to form the normalized module name.
12
+ 6. Canonical ID is constructed deterministically as:
13
+ - f"{module}.{scope}.{name}" when scope is non-empty
14
+ - f"{module}.{name}" when scope is empty
15
+ Line numbers are never part of the canonical identity.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import posixpath
20
+ from dataclasses import dataclass, field
21
+ from functools import lru_cache
22
+
23
+ _SOURCE_EXTENSIONS = (
24
+ ".server.luau",
25
+ ".client.luau",
26
+ ".server.lua",
27
+ ".client.lua",
28
+ ".d.ts",
29
+ ".pyi",
30
+ ".tsx",
31
+ ".jsx",
32
+ ".mjs",
33
+ ".cjs",
34
+ ".luau",
35
+ ".lua",
36
+ ".py",
37
+ ".ts",
38
+ ".js",
39
+ ".html",
40
+ ".htm",
41
+ ".jinja",
42
+ ".jinja2",
43
+ ".njk",
44
+ ".ejs",
45
+ )
46
+
47
+
48
+ @lru_cache(maxsize=65536)
49
+ def normalize_module(file_path: str, language: str = "") -> str:
50
+ """Deterministically normalize a repository-relative file path into a dotted module ID."""
51
+ del language # normalization is uniform across Python/JS/TS/Luau after extension/index rules
52
+ cleaned = file_path.replace("\\", "/").strip()
53
+ while cleaned.startswith("./"):
54
+ cleaned = cleaned[2:]
55
+ cleaned = cleaned.lstrip("/")
56
+ if not cleaned:
57
+ return "root"
58
+
59
+ cleaned = posixpath.normpath(cleaned)
60
+ if cleaned in (".", ""):
61
+ return "root"
62
+
63
+ lower = cleaned.lower()
64
+ for ext in _SOURCE_EXTENSIONS:
65
+ if lower.endswith(ext):
66
+ cleaned = cleaned[: -len(ext)]
67
+ break
68
+
69
+ if cleaned.endswith("/__init__"):
70
+ cleaned = cleaned[: -len("/__init__")]
71
+ elif cleaned.endswith("/index"):
72
+ cleaned = cleaned[: -len("/index")]
73
+ elif cleaned.endswith("/init"):
74
+ cleaned = cleaned[: -len("/init")]
75
+
76
+ parts = [p for p in cleaned.split("/") if p and p != "."]
77
+ return ".".join(parts) if parts else "root"
78
+
79
+
80
+ def build_canonical_id(module: str, scope: str, name: str) -> str:
81
+ """Build a deterministic canonical symbol ID from normalized module, scope, and name."""
82
+ mod = module.strip(".")
83
+ scp = scope.strip(".")
84
+ nm = name.strip(".") or "default"
85
+ if mod and scp:
86
+ return f"{mod}.{scp}.{nm}"
87
+ if mod:
88
+ return f"{mod}.{nm}"
89
+ if scp:
90
+ return f"{scp}.{nm}"
91
+ return nm
92
+
93
+
94
+ @dataclass(frozen=True)
95
+ class Symbol:
96
+ name: str
97
+ qualified_name: str
98
+ kind: str # function | method | class | interface | type | variable
99
+ start_line: int
100
+ end_line: int
101
+ file_path: str
102
+ decorators: list[str] = field(default_factory=list)
103
+ id: str = ""
104
+ canonical_id: str = ""
105
+ language: str = "python"
106
+ module: str = ""
107
+ path: str = ""
108
+ scope: str = ""
109
+ signature: str = ""
110
+ content_hash: str = ""
111
+ parent_symbol_id: str | None = None
112
+ visibility: str = ""
113
+ return_type: str | None = None
114
+ parameter_count: int | None = None
115
+ documentation: str | None = None
116
+
117
+ def __post_init__(self) -> None:
118
+ path_val = self.path or self.file_path
119
+ object.__setattr__(self, "path", path_val)
120
+
121
+ mod_val = self.module or normalize_module(path_val, self.language)
122
+ object.__setattr__(self, "module", mod_val)
123
+
124
+ scope_val = self.scope
125
+ if not scope_val and "." in self.qualified_name:
126
+ scope_val = self.qualified_name.rsplit(".", 1)[0]
127
+ object.__setattr__(self, "scope", scope_val)
128
+
129
+ canon_val = self.canonical_id or build_canonical_id(mod_val, scope_val, self.name)
130
+ object.__setattr__(self, "canonical_id", canon_val)
131
+
132
+ id_val = self.id or canon_val
133
+ object.__setattr__(self, "id", id_val)
134
+
135
+ if self.parent_symbol_id is None and scope_val:
136
+ parent_id = f"{mod_val}.{scope_val}" if mod_val else scope_val
137
+ object.__setattr__(self, "parent_symbol_id", parent_id)
138
+
139
+ if not self.visibility:
140
+ is_private = self.name.startswith("_") and not (
141
+ self.name.startswith("__") and self.name.endswith("__")
142
+ )
143
+ object.__setattr__(self, "visibility", "private" if is_private else "public")
144
+
145
+ def as_dict(self) -> dict[str, object]:
146
+ return {
147
+ "id": self.id,
148
+ "canonical_id": self.canonical_id,
149
+ "name": self.name,
150
+ "qualified_name": self.qualified_name,
151
+ "kind": self.kind,
152
+ "language": self.language,
153
+ "module": self.module,
154
+ "path": self.path,
155
+ "file": self.path,
156
+ "file_path": self.file_path,
157
+ "scope": self.scope,
158
+ "signature": self.signature,
159
+ "start_line": self.start_line,
160
+ "end_line": self.end_line,
161
+ "content_hash": self.content_hash,
162
+ "parent_symbol_id": self.parent_symbol_id,
163
+ "visibility": self.visibility,
164
+ "decorators": list(self.decorators),
165
+ "decorator": ",".join(self.decorators),
166
+ "return_type": self.return_type,
167
+ "parameter_count": self.parameter_count,
168
+ "documentation": self.documentation,
169
+ }
170
+
171
+ def __getitem__(self, key: str) -> object:
172
+ return self.as_dict()[key]
173
+
174
+ def get(self, key: str, default: object = None) -> object:
175
+ return self.as_dict().get(key, default)
176
+
177
+ def __contains__(self, key: str) -> bool:
178
+ return key in self.as_dict()
179
+
180
+
181
+ @dataclass(frozen=True)
182
+ class ImportRef:
183
+ module: str
184
+ source_file: str
185
+ name: str | None = None # imported name (from X import name)
186
+ alias: str | None = None # as Y
187
+ full: str | None = None # module.name combined
188
+ line: int = 1
189
+ source_module: str = ""
190
+ imported_module: str = ""
191
+ imported_name: str | None = None
192
+ local_name: str = ""
193
+ import_type: str = "" # named | alias | namespace | default | module | reexport
194
+ is_reexport: bool = False
195
+ exported_name: str | None = None
196
+
197
+ def __post_init__(self) -> None:
198
+ src_mod = self.source_module or normalize_module(self.source_file)
199
+ object.__setattr__(self, "source_module", src_mod)
200
+
201
+ imp_mod = self.imported_module or self.module
202
+ object.__setattr__(self, "imported_module", imp_mod)
203
+
204
+ imp_name = self.imported_name if self.imported_name is not None else self.name
205
+ object.__setattr__(self, "imported_name", imp_name)
206
+
207
+ if not self.local_name:
208
+ if self.alias:
209
+ loc = self.alias
210
+ elif imp_name and imp_name != "*":
211
+ loc = imp_name
212
+ else:
213
+ loc = imp_mod.lstrip(".").split("/")[0].split(".")[0]
214
+ object.__setattr__(self, "local_name", loc)
215
+
216
+ if not self.import_type:
217
+ if self.is_reexport:
218
+ itype = "reexport"
219
+ elif self.alias and imp_name == "*":
220
+ itype = "namespace"
221
+ elif self.alias and imp_name is None:
222
+ itype = "namespace"
223
+ elif self.alias:
224
+ itype = "alias"
225
+ elif imp_name == "default":
226
+ itype = "default"
227
+ elif imp_name:
228
+ itype = "named"
229
+ else:
230
+ itype = "module"
231
+ object.__setattr__(self, "import_type", itype)
232
+
233
+ if self.full is None:
234
+ full_val = f"{imp_mod}.{imp_name}" if (imp_mod and imp_name) else imp_mod
235
+ object.__setattr__(self, "full", full_val)
236
+
237
+ def as_dict(self) -> dict[str, object]:
238
+ return {
239
+ "source_file": self.source_file,
240
+ "source_module": self.source_module,
241
+ "module": self.module,
242
+ "imported_module": self.imported_module,
243
+ "name": self.name,
244
+ "imported_name": self.imported_name,
245
+ "alias": self.alias,
246
+ "local_name": self.local_name,
247
+ "import_type": self.import_type,
248
+ "is_reexport": self.is_reexport,
249
+ "exported_name": self.exported_name or self.alias or self.imported_name,
250
+ "full": self.full,
251
+ "line": self.line,
252
+ }
253
+
254
+ def __getitem__(self, key: str) -> object:
255
+ return self.as_dict()[key]
256
+
257
+ def get(self, key: str, default: object = None) -> object:
258
+ return self.as_dict().get(key, default)
259
+
260
+
261
+ @dataclass(frozen=True)
262
+ class CallRef:
263
+ callee: str
264
+ source_file: str
265
+ confidence: str = "LOW"
266
+ qualified_callee: str | None = None
267
+ line: int = 1
268
+ end_line: int = 0
269
+ caller_symbol: str | None = None # qualified_name within file, or None for module scope
270
+ caller_canonical_id: str | None = None
271
+ receiver: str | None = None
272
+
273
+ def __post_init__(self) -> None:
274
+ if self.end_line <= 0:
275
+ object.__setattr__(self, "end_line", self.line)
276
+ if self.caller_canonical_id is None:
277
+ mod = normalize_module(self.source_file)
278
+ if self.caller_symbol:
279
+ object.__setattr__(self, "caller_canonical_id", f"{mod}.{self.caller_symbol}")
280
+ else:
281
+ object.__setattr__(self, "caller_canonical_id", mod)
282
+
283
+
284
+ @dataclass(frozen=True)
285
+ class InheritanceRef:
286
+ source_symbol: str # qualified_name in source_file
287
+ base_name: str # raw base/interface identifier (e.g. Base or mod.Base)
288
+ relationship: str # EXTENDS | IMPLEMENTS
289
+ source_file: str
290
+ line: int = 1
291
+ source_canonical_id: str = ""
292
+
293
+ def __post_init__(self) -> None:
294
+ if not self.source_canonical_id:
295
+ mod = normalize_module(self.source_file)
296
+ object.__setattr__(self, "source_canonical_id", f"{mod}.{self.source_symbol}")
297
+
298
+
299
+ @dataclass(frozen=True)
300
+ class Reference:
301
+ source_symbol_id: str
302
+ target_symbol_id: str | None
303
+ relationship: str # CALLS | REFERENCES | IMPORTS | EXPORTS | REEXPORTS | EXTENDS | IMPLEMENTS | USES | UNRESOLVED_REFERENCE
304
+ confidence: str # HIGH | MEDIUM | LOW | UNKNOWN
305
+ path: str
306
+ start_line: int
307
+ end_line: int
308
+ evidence: str
309
+ source_hash: str = ""
310
+ indexed_commit: str | None = None
311
+ evidence_status: str = "current"
312
+
313
+ def as_dict(self) -> dict[str, object]:
314
+ return {
315
+ "source_symbol_id": self.source_symbol_id,
316
+ "target_symbol_id": self.target_symbol_id,
317
+ "source": self.source_symbol_id,
318
+ "target": self.target_symbol_id,
319
+ "symbol": self.source_symbol_id,
320
+ "callee": self.target_symbol_id.split(".")[-1] if self.target_symbol_id else None,
321
+ "qualified_callee": self.target_symbol_id,
322
+ "relationship": self.relationship,
323
+ "confidence": self.confidence,
324
+ "path": self.path,
325
+ "file": self.path,
326
+ "start_line": self.start_line,
327
+ "end_line": self.end_line,
328
+ "line": self.start_line,
329
+ "evidence": self.evidence,
330
+ "source_hash": self.source_hash,
331
+ "indexed_commit": self.indexed_commit,
332
+ "evidence_status": self.evidence_status,
333
+ }
334
+
335
+ def __getitem__(self, key: str) -> object:
336
+ return self.as_dict()[key]
337
+
338
+ def get(self, key: str, default: object = None) -> object:
339
+ return self.as_dict().get(key, default)
340
+
341
+ def __contains__(self, key: str) -> bool:
342
+ return key in self.as_dict()
343
+
344
+
345
+ @dataclass(frozen=True)
346
+ class Chunk:
347
+ file_path: str
348
+ language: str
349
+ symbol: str | None
350
+ symbol_type: str | None
351
+ start_line: int
352
+ end_line: int
353
+ content: str
354
+ content_hash: str
355
+
356
+
357
+ @dataclass(frozen=True)
358
+ class BindingRef:
359
+ """Local variable alias or expression binding for conservative data-flow resolution."""
360
+
361
+ target_name: str
362
+ file_path: str
363
+ line: int
364
+ column: int | None = None
365
+ scope: str = ""
366
+ expr_kind: str = "IDENTIFIER" # IDENTIFIER | ATTRIBUTE | DICT_LITERAL | LIST_LITERAL | SUBSCRIPT | DYNAMIC
367
+ source_expr: str = ""
368
+ is_conditional: bool = False
369
+ base_expr: str = ""
370
+ attr_name: str = ""
371
+ dict_entries: tuple[tuple[str, str], ...] = ()
372
+ list_entries: tuple[str, ...] = ()
373
+ subscript_target: str | None = None
374
+ subscript_key: str | None = None
375
+ subscript_index: int | None = None
376
+
377
+ def as_dict(self) -> dict[str, object]:
378
+ return {
379
+ "target_name": self.target_name,
380
+ "file_path": self.file_path,
381
+ "line": self.line,
382
+ "column": self.column,
383
+ "scope": self.scope,
384
+ "expr_kind": self.expr_kind,
385
+ "source_expr": self.source_expr,
386
+ "is_conditional": self.is_conditional,
387
+ "base_expr": self.base_expr,
388
+ "attr_name": self.attr_name,
389
+ "dict_entries": list(self.dict_entries),
390
+ "list_entries": list(self.list_entries),
391
+ "subscript_target": self.subscript_target,
392
+ "subscript_key": self.subscript_key,
393
+ "subscript_index": self.subscript_index,
394
+ }
395
+
396
+
397
+ @dataclass(frozen=True)
398
+ class RegistrationRef:
399
+ """Explicit registry, dispatch mapping, or event listener fact."""
400
+
401
+ pattern: str # CALL_REGISTER | DICT_ASSIGN | EVENT_ON | SUBSCRIBE | COMMAND
402
+ registry_expr: str
403
+ key_or_event: str
404
+ target_expr: str
405
+ file_path: str
406
+ line: int
407
+ scope: str = ""
408
+ is_conditional: bool = False
409
+ is_dynamic_key: bool = False
410
+ is_dynamic_target: bool = False
411
+ evidence: str = ""
412
+
413
+ def as_dict(self) -> dict[str, object]:
414
+ return {
415
+ "pattern": self.pattern,
416
+ "registry_expr": self.registry_expr,
417
+ "key_or_event": self.key_or_event,
418
+ "target_expr": self.target_expr,
419
+ "file_path": self.file_path,
420
+ "line": self.line,
421
+ "scope": self.scope,
422
+ "is_conditional": self.is_conditional,
423
+ "is_dynamic_key": self.is_dynamic_key,
424
+ "is_dynamic_target": self.is_dynamic_target,
425
+ "evidence": self.evidence,
426
+ }
427
+