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
codegraph/freshness.py ADDED
@@ -0,0 +1,335 @@
1
+ """Repository Freshness & Dependency-Aware Invalidation Engine.
2
+
3
+ Tracks:
4
+ current Git HEAD, indexed Git HEAD, file hashes, parser version,
5
+ schema version, index generation, timestamps, and parse_failed state.
6
+
7
+ Provides dependency-aware invalidation so localized file updates only
8
+ invalidate affected symbols, references, graph edges, evidence, and cached
9
+ context packets rather than rebuilding the entire repository.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import hashlib
14
+ import shlex
15
+ import sqlite3
16
+ import subprocess
17
+ from dataclasses import dataclass, field
18
+ from enum import StrEnum
19
+ from pathlib import Path
20
+
21
+
22
+ class FreshnessStatus(StrEnum):
23
+ FRESH = "FRESH"
24
+ STALE = "STALE"
25
+ PARTIALLY_STALE = "PARTIALLY_STALE"
26
+ UNKNOWN = "UNKNOWN"
27
+
28
+
29
+ @dataclass
30
+ class FreshnessReport:
31
+ status: FreshnessStatus
32
+ indexed_commit: str | None
33
+ current_commit: str | None
34
+ index_timestamp: int | None
35
+ modified_files: list[str] = field(default_factory=list)
36
+ deleted_files: list[str] = field(default_factory=list)
37
+ parse_failed_files: list[str] = field(default_factory=list)
38
+ parser_version: str | None = None
39
+ index_generation: int = 0
40
+ detail: str = ""
41
+
42
+ def as_dict(self) -> dict[str, object]:
43
+ return {
44
+ "status": self.status.value,
45
+ "indexed_commit": self.indexed_commit,
46
+ "current_commit": self.current_commit,
47
+ "index_timestamp": self.index_timestamp,
48
+ "modified_files": self.modified_files,
49
+ "deleted_files": self.deleted_files,
50
+ "parse_failed_files": self.parse_failed_files,
51
+ "parser_version": self.parser_version,
52
+ "index_generation": self.index_generation,
53
+ "detail": self.detail,
54
+ }
55
+
56
+
57
+ def _run_git(repository: Path, *args: str, timeout: int = 10) -> str | None:
58
+ """Run an allowlisted read-only git command inside repository."""
59
+ if not shlex.split("git")[0]: # pragma: no cover
60
+ return None
61
+ try:
62
+ result = subprocess.run(
63
+ ["git", *args],
64
+ cwd=str(repository),
65
+ capture_output=True,
66
+ text=True,
67
+ encoding="utf-8",
68
+ errors="replace",
69
+ timeout=timeout,
70
+ )
71
+ return result.stdout.strip() if result.returncode == 0 else None
72
+ except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
73
+ return None
74
+
75
+
76
+ def current_commit(repository: Path) -> str | None:
77
+ """Return the current HEAD commit SHA, or None if not a git repository."""
78
+ return _run_git(repository, "rev-parse", "HEAD")
79
+
80
+
81
+ def current_branch(repository: Path) -> str | None:
82
+ """Return the current git branch name, or None."""
83
+ return _run_git(repository, "rev-parse", "--abbrev-ref", "HEAD")
84
+
85
+
86
+ def _get_meta(con: sqlite3.Connection, key: str) -> str | None:
87
+ for table in ("metadata", "codegraph_meta"):
88
+ try:
89
+ row = con.execute(
90
+ f"SELECT value FROM {table} WHERE key=?", (key,)
91
+ ).fetchone()
92
+ if row and row[0] != "":
93
+ return str(row[0])
94
+ except sqlite3.OperationalError:
95
+ continue
96
+ return None
97
+
98
+
99
+ def indexed_commit(con: sqlite3.Connection) -> str | None:
100
+ """Read the commit SHA recorded when the index was last built."""
101
+ return _get_meta(con, "indexed_commit")
102
+
103
+
104
+ def index_timestamp(con: sqlite3.Connection) -> int | None:
105
+ """Return unix timestamp of the last indexing run."""
106
+ val = _get_meta(con, "index_timestamp")
107
+ try:
108
+ return int(val) if val is not None else None
109
+ except ValueError:
110
+ return None
111
+
112
+
113
+ def index_generation(con: sqlite3.Connection) -> int:
114
+ """Return the monotonic index generation counter."""
115
+ val = _get_meta(con, "index_generation")
116
+ try:
117
+ return int(val) if val is not None else 0
118
+ except ValueError:
119
+ return 0
120
+
121
+
122
+ def save_commit(con: sqlite3.Connection, commit: str | None, timestamp: int) -> None:
123
+ """Persist commit SHA and timestamp into metadata tables."""
124
+ _ensure_meta_table(con)
125
+ for table in ("codegraph_meta", "metadata"):
126
+ for key, val in [
127
+ ("indexed_commit", commit or ""),
128
+ ("index_timestamp", str(timestamp)),
129
+ ]:
130
+ try:
131
+ con.execute(
132
+ f"INSERT INTO {table}(key, value) VALUES(?,?) "
133
+ "ON CONFLICT(key) DO UPDATE SET value=excluded.value",
134
+ (key, val),
135
+ )
136
+ except sqlite3.OperationalError:
137
+ pass
138
+
139
+
140
+ def _ensure_meta_table(con: sqlite3.Connection) -> None:
141
+ con.execute(
142
+ "CREATE TABLE IF NOT EXISTS codegraph_meta "
143
+ "(key TEXT PRIMARY KEY, value TEXT NOT NULL)"
144
+ )
145
+ con.execute(
146
+ "CREATE TABLE IF NOT EXISTS metadata "
147
+ "(key TEXT PRIMARY KEY, value TEXT NOT NULL)"
148
+ )
149
+
150
+
151
+ def compute_invalidated_dependents(
152
+ con: sqlite3.Connection, changed_paths: set[str]
153
+ ) -> dict[str, list[str]]:
154
+ """Given a set of modified/deleted file paths, compute dependent files and symbols that must be invalidated."""
155
+ from codegraph.indexing.models import normalize_module
156
+
157
+ if not changed_paths:
158
+ return {
159
+ "files": [],
160
+ "modules": [],
161
+ "symbols": [],
162
+ "dependent_files": [],
163
+ }
164
+
165
+ changed_modules = {normalize_module(p) for p in changed_paths}
166
+ invalidated_symbols: set[str] = set()
167
+ dependent_files: set[str] = set()
168
+
169
+ for p in changed_paths:
170
+ try:
171
+ rows = con.execute(
172
+ "SELECT canonical_id, qualified_name FROM symbols WHERE path=?", (p,)
173
+ ).fetchall()
174
+ for r in rows:
175
+ invalidated_symbols.add(str(r["canonical_id"] or r["qualified_name"]))
176
+ except sqlite3.OperationalError:
177
+ pass
178
+
179
+ for mod in changed_modules:
180
+ try:
181
+ imp_rows = con.execute(
182
+ "SELECT DISTINCT source_path FROM imports "
183
+ "WHERE resolved_module=? OR imported_module=? OR module=? OR module LIKE ?",
184
+ (mod, mod, mod, f"%{mod.split('.')[-1]}"),
185
+ ).fetchall()
186
+ for r in imp_rows:
187
+ if r["source_path"] not in changed_paths:
188
+ dependent_files.add(str(r["source_path"]))
189
+ except sqlite3.OperationalError:
190
+ pass
191
+
192
+ return {
193
+ "files": sorted(changed_paths),
194
+ "modules": sorted(changed_modules),
195
+ "symbols": sorted(invalidated_symbols),
196
+ "dependent_files": sorted(dependent_files),
197
+ }
198
+
199
+
200
+ def check_freshness(
201
+ repository: Path, con: sqlite3.Connection
202
+ ) -> FreshnessReport:
203
+ """Compare index state against current filesystem state."""
204
+ _ensure_meta_table(con)
205
+
206
+ idx_commit = indexed_commit(con)
207
+ cur_commit = current_commit(repository)
208
+ idx_ts = index_timestamp(con)
209
+ parser_ver = _get_meta(con, "parser_version")
210
+ gen = index_generation(con)
211
+
212
+ try:
213
+ file_count = con.execute("SELECT count(*) FROM files").fetchone()[0]
214
+ except sqlite3.OperationalError:
215
+ return FreshnessReport(
216
+ status=FreshnessStatus.UNKNOWN,
217
+ indexed_commit=idx_commit,
218
+ current_commit=cur_commit,
219
+ index_timestamp=idx_ts,
220
+ parser_version=parser_ver,
221
+ index_generation=gen,
222
+ detail="No index found. Run `codegraph index` first.",
223
+ )
224
+
225
+ if file_count == 0:
226
+ return FreshnessReport(
227
+ status=FreshnessStatus.UNKNOWN,
228
+ indexed_commit=idx_commit,
229
+ current_commit=cur_commit,
230
+ index_timestamp=idx_ts,
231
+ parser_version=parser_ver,
232
+ index_generation=gen,
233
+ detail="Index is empty. Run `codegraph index` first.",
234
+ )
235
+
236
+ modified: list[str] = []
237
+ deleted: list[str] = []
238
+ parse_failed: list[str] = []
239
+
240
+ # Fast path 1: Git commit diff (branch switch or commit advance)
241
+ git_diff_handled = False
242
+ if cur_commit and idx_commit and cur_commit != idx_commit:
243
+ diff_out = _run_git(repository, "diff", "--name-status", idx_commit, cur_commit)
244
+ if diff_out is not None:
245
+ git_diff_handled = True
246
+ for line in diff_out.splitlines():
247
+ parts = line.strip().split(maxsplit=1)
248
+ if len(parts) == 2:
249
+ action, fpath = parts[0], parts[1]
250
+ if action.startswith("D"):
251
+ deleted.append(fpath)
252
+ else:
253
+ modified.append(fpath)
254
+
255
+ # Fast path 2: Git status when HEAD commit is unchanged
256
+ if not git_diff_handled and cur_commit and idx_commit and cur_commit == idx_commit:
257
+ status_out = _run_git(repository, "status", "--porcelain", "--untracked-files=no")
258
+ if status_out is not None and not status_out.strip():
259
+ # Working tree has zero tracked changes against HEAD
260
+ git_diff_handled = True
261
+ try:
262
+ pf_rows = con.execute("SELECT path FROM files WHERE status='parse_failed'").fetchall()
263
+ for r in pf_rows:
264
+ parse_failed.append(str(r[0]))
265
+ modified.append(str(r[0]))
266
+ except sqlite3.OperationalError:
267
+ pass
268
+
269
+ if not git_diff_handled:
270
+ try:
271
+ rows = con.execute("SELECT path, hash, status, indexed_at FROM files").fetchall()
272
+ except sqlite3.OperationalError:
273
+ try:
274
+ rows = con.execute("SELECT path, hash, status, 0 AS indexed_at FROM files").fetchall()
275
+ except sqlite3.OperationalError:
276
+ rows = con.execute("SELECT path, hash, 'ok' AS status, 0 AS indexed_at FROM files").fetchall()
277
+
278
+ for row in rows:
279
+ rel = str(row["path"])
280
+ if row["status"] == "parse_failed":
281
+ parse_failed.append(rel)
282
+ if rel not in modified:
283
+ modified.append(rel)
284
+ abs_path = repository / rel
285
+ if not abs_path.exists():
286
+ deleted.append(rel)
287
+ else:
288
+ try:
289
+ st = abs_path.stat()
290
+ # Mtime check: skip reading & hashing files strictly older than indexing time
291
+ idx_at = int(row["indexed_at"] or 0)
292
+ if idx_at > 0 and int(st.st_mtime) < idx_at and row["status"] != "parse_failed":
293
+ continue
294
+ digest = hashlib.sha256(
295
+ abs_path.read_text(encoding="utf-8", errors="replace").encode()
296
+ ).hexdigest()
297
+ if digest != row["hash"] and rel not in modified:
298
+ modified.append(rel)
299
+ except OSError:
300
+ if rel not in modified:
301
+ modified.append(rel)
302
+
303
+ changed = len(modified) + len(deleted)
304
+ if changed == 0 and not parse_failed and (idx_commit == cur_commit or cur_commit is None):
305
+ status = FreshnessStatus.FRESH
306
+ detail = "Index matches current repository state."
307
+ elif parse_failed and changed == len(parse_failed):
308
+ status = FreshnessStatus.PARTIALLY_STALE
309
+ detail = f"{len(parse_failed)} file(s) had syntax errors; last-known-good index retained (stale)."
310
+ elif changed == 0:
311
+ status = FreshnessStatus.PARTIALLY_STALE
312
+ detail = f"Commit changed ({_short(idx_commit)} → {_short(cur_commit)}) but no file hashes differ."
313
+ elif changed < file_count:
314
+ status = FreshnessStatus.PARTIALLY_STALE
315
+ detail = f"{changed} file(s) changed since indexing."
316
+ else:
317
+ status = FreshnessStatus.STALE
318
+ detail = f"All {file_count} indexed files may have changed. Re-index recommended."
319
+
320
+ return FreshnessReport(
321
+ status=status,
322
+ indexed_commit=idx_commit,
323
+ current_commit=cur_commit,
324
+ index_timestamp=idx_ts,
325
+ modified_files=modified,
326
+ deleted_files=deleted,
327
+ parse_failed_files=parse_failed,
328
+ parser_version=parser_ver,
329
+ index_generation=gen,
330
+ detail=detail,
331
+ )
332
+
333
+
334
+ def _short(sha: str | None) -> str:
335
+ return sha[:8] if sha else "unknown"
@@ -0,0 +1,109 @@
1
+ """HTML Template and Web Asset Linkage Analyzer (Pillar 4).
2
+
3
+ Extracts deterministic facts from HTML documents:
4
+ - Script bundle inclusions (<script src="..."> -> LOADS_SCRIPT)
5
+ - Stylesheet links (<link rel="stylesheet" href="..."> -> LOADS_STYLESHEET)
6
+ - DOM mount containers (<div id="root">, <div id="app">)
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from dataclasses import dataclass
12
+
13
+ from codegraph.indexing.models import BindingRef, Symbol
14
+
15
+ _SCRIPT_SRC_RE = re.compile(
16
+ r"""<script\b[^>]*\bsrc\s*=\s*['"]([^'"]+)['"]""",
17
+ re.IGNORECASE,
18
+ )
19
+
20
+ _LINK_STYLESHEET_RE = re.compile(
21
+ r"""<link\b[^>]*\bhref\s*=\s*['"]([^'"]+)['"][^>]*\brel\s*=\s*['"]stylesheet['"]|<link\b[^>]*\brel\s*=\s*['"]stylesheet['"][^>]*\bhref\s*=\s*['"]([^'"]+)['"]""",
22
+ re.IGNORECASE,
23
+ )
24
+
25
+ _ELEMENT_ID_RE = re.compile(
26
+ r"""<([A-Za-z0-9_-]+)\b[^>]*\bid\s*=\s*['"]([^'"]+)['"]""",
27
+ re.IGNORECASE,
28
+ )
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class HtmlParseResult:
33
+ symbols: tuple[Symbol, ...] = ()
34
+ bindings: tuple[BindingRef, ...] = ()
35
+
36
+
37
+ def is_html_file(file_path: str) -> bool:
38
+ """Check if file is an HTML document or template."""
39
+ lower = file_path.lower()
40
+ return lower.endswith((".html", ".htm"))
41
+
42
+
43
+ def parse_html_file(content: str, file_path: str) -> HtmlParseResult:
44
+ """Extract script, stylesheet, and DOM mount container references from HTML."""
45
+ if not is_html_file(file_path):
46
+ return HtmlParseResult()
47
+
48
+ clean_file = file_path.replace("\\", "/").lstrip("./")
49
+ symbols: list[Symbol] = []
50
+ bindings: list[BindingRef] = []
51
+
52
+ for idx, line in enumerate(content.splitlines()):
53
+ lineno = idx + 1
54
+
55
+ # <script src="...">
56
+ for m_script in _SCRIPT_SRC_RE.finditer(line):
57
+ src_val = m_script.group(1).lstrip("/")
58
+ bindings.append(
59
+ BindingRef(
60
+ target_name=src_val,
61
+ file_path=clean_file,
62
+ line=lineno,
63
+ scope="",
64
+ expr_kind="HTML_LOADS_SCRIPT",
65
+ source_expr=clean_file,
66
+ base_expr="LOADS_SCRIPT",
67
+ )
68
+ )
69
+
70
+ # <link rel="stylesheet" href="...">
71
+ for m_link in _LINK_STYLESHEET_RE.finditer(line):
72
+ href_val = (m_link.group(1) or m_link.group(2)).lstrip("/")
73
+ bindings.append(
74
+ BindingRef(
75
+ target_name=href_val,
76
+ file_path=clean_file,
77
+ line=lineno,
78
+ scope="",
79
+ expr_kind="HTML_LOADS_STYLESHEET",
80
+ source_expr=clean_file,
81
+ base_expr="LOADS_STYLESHEET",
82
+ )
83
+ )
84
+
85
+ # <div id="...">
86
+ for m_elem in _ELEMENT_ID_RE.finditer(line):
87
+ elem_id = m_elem.group(2)
88
+ canon_id = f"{clean_file}#{elem_id}"
89
+ symbols.append(
90
+ Symbol(
91
+ id=canon_id,
92
+ canonical_id=canon_id,
93
+ name=f"#{elem_id}",
94
+ qualified_name=f"#{elem_id}",
95
+ kind="dom_element",
96
+ language="html",
97
+ module=clean_file,
98
+ path=clean_file,
99
+ file_path=clean_file,
100
+ scope="",
101
+ start_line=lineno,
102
+ end_line=lineno,
103
+ )
104
+ )
105
+
106
+ return HtmlParseResult(
107
+ symbols=tuple(symbols),
108
+ bindings=tuple(bindings),
109
+ )