code-atlas-forge 1.1.0__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 (181) hide show
  1. code_atlas/__init__.py +6 -0
  2. code_atlas/__main__.py +6 -0
  3. code_atlas/api/__init__.py +5 -0
  4. code_atlas/api/handlers.py +1514 -0
  5. code_atlas/api/server.py +394 -0
  6. code_atlas/cli.py +2084 -0
  7. code_atlas/config.py +149 -0
  8. code_atlas/context/__init__.py +66 -0
  9. code_atlas/context/contracts.py +277 -0
  10. code_atlas/context/domain.py +760 -0
  11. code_atlas/context/engine.py +442 -0
  12. code_atlas/context/expand.py +133 -0
  13. code_atlas/context/renderers.py +71 -0
  14. code_atlas/context/tokenizer.py +37 -0
  15. code_atlas/contracts/__init__.py +38 -0
  16. code_atlas/contracts/enums.py +67 -0
  17. code_atlas/contracts/models.py +175 -0
  18. code_atlas/contracts/ports.py +70 -0
  19. code_atlas/discovery/__init__.py +25 -0
  20. code_atlas/discovery/gitignore.py +119 -0
  21. code_atlas/discovery/identity.py +52 -0
  22. code_atlas/discovery/models.py +204 -0
  23. code_atlas/discovery/scanner.py +495 -0
  24. code_atlas/domains/__init__.py +107 -0
  25. code_atlas/domains/api/__init__.py +28 -0
  26. code_atlas/domains/api/adapter.py +405 -0
  27. code_atlas/domains/api/common.py +308 -0
  28. code_atlas/domains/api/consumers.py +224 -0
  29. code_atlas/domains/api/emit.py +231 -0
  30. code_atlas/domains/api/openapi.py +398 -0
  31. code_atlas/domains/api/routes.py +582 -0
  32. code_atlas/domains/api/yaml_subset.py +683 -0
  33. code_atlas/domains/aws/__init__.py +28 -0
  34. code_atlas/domains/aws/adapter.py +382 -0
  35. code_atlas/domains/aws/common.py +261 -0
  36. code_atlas/domains/aws/configs.py +1164 -0
  37. code_atlas/domains/aws/emit.py +411 -0
  38. code_atlas/domains/aws/pyfiles.py +2220 -0
  39. code_atlas/domains/aws/sqlbridge.py +448 -0
  40. code_atlas/domains/cicd/__init__.py +50 -0
  41. code_atlas/domains/cicd/adapter.py +762 -0
  42. code_atlas/domains/cicd/classify.py +136 -0
  43. code_atlas/domains/cicd/workflow.py +504 -0
  44. code_atlas/domains/cicd/yamlmini.py +946 -0
  45. code_atlas/domains/cli_surface.py +739 -0
  46. code_atlas/domains/contracts.py +274 -0
  47. code_atlas/domains/crossresolve.py +1208 -0
  48. code_atlas/domains/evidence.py +57 -0
  49. code_atlas/domains/identity.py +149 -0
  50. code_atlas/domains/model.py +202 -0
  51. code_atlas/domains/registry.py +250 -0
  52. code_atlas/domains/resolution.py +65 -0
  53. code_atlas/domains/spark/__init__.py +31 -0
  54. code_atlas/domains/spark/adapter.py +146 -0
  55. code_atlas/domains/spark/emit.py +344 -0
  56. code_atlas/domains/spark/extract.py +108 -0
  57. code_atlas/domains/spark/sqllit.py +192 -0
  58. code_atlas/domains/spark/walk.py +1943 -0
  59. code_atlas/domains/sql/__init__.py +30 -0
  60. code_atlas/domains/sql/adapter.py +382 -0
  61. code_atlas/domains/sql/extract.py +1271 -0
  62. code_atlas/domains/sql/nodes.py +264 -0
  63. code_atlas/domains/sql/parser.py +1510 -0
  64. code_atlas/domains/sql/tokens.py +318 -0
  65. code_atlas/domains/summarize.py +126 -0
  66. code_atlas/domains/terraform/__init__.py +20 -0
  67. code_atlas/domains/terraform/adapter.py +127 -0
  68. code_atlas/domains/terraform/catalog.py +43 -0
  69. code_atlas/domains/terraform/extract.py +1277 -0
  70. code_atlas/domains/terraform/hcl.py +538 -0
  71. code_atlas/domains/terraform/refs.py +181 -0
  72. code_atlas/domains/terraform/tfjson.py +163 -0
  73. code_atlas/domains/terraform/tfvars.py +98 -0
  74. code_atlas/domains/validation.py +120 -0
  75. code_atlas/env_override.py +27 -0
  76. code_atlas/eval/__init__.py +26 -0
  77. code_atlas/eval/domain_eval.py +681 -0
  78. code_atlas/eval/ground_truth.py +688 -0
  79. code_atlas/eval/impact_truth.py +758 -0
  80. code_atlas/eval/metrics.py +178 -0
  81. code_atlas/eval/pack_value.py +610 -0
  82. code_atlas/federation/__init__.py +72 -0
  83. code_atlas/federation/federation.py +676 -0
  84. code_atlas/federation/model.py +355 -0
  85. code_atlas/federation/store.py +194 -0
  86. code_atlas/gitbin.py +48 -0
  87. code_atlas/graph/__init__.py +20 -0
  88. code_atlas/graph/builder.py +378 -0
  89. code_atlas/graph/identity.py +54 -0
  90. code_atlas/graph/queries.py +47 -0
  91. code_atlas/graph/validation.py +92 -0
  92. code_atlas/impact/__init__.py +37 -0
  93. code_atlas/impact/blast_radius.py +171 -0
  94. code_atlas/impact/contract_analyzer.py +104 -0
  95. code_atlas/impact/contracts.py +315 -0
  96. code_atlas/impact/diff_reader.py +142 -0
  97. code_atlas/impact/domain_traversal.py +768 -0
  98. code_atlas/impact/engine.py +392 -0
  99. code_atlas/impact/packs.py +128 -0
  100. code_atlas/impact/report.py +136 -0
  101. code_atlas/impact/revision_binding.py +220 -0
  102. code_atlas/impact/risk_classifier.py +83 -0
  103. code_atlas/impact/snapshot_resolver.py +136 -0
  104. code_atlas/impact/symbol_diff.py +210 -0
  105. code_atlas/impact/test_selector.py +166 -0
  106. code_atlas/indexing/__init__.py +5 -0
  107. code_atlas/indexing/cache.py +396 -0
  108. code_atlas/indexing/change_detector.py +278 -0
  109. code_atlas/indexing/coordinator.py +1032 -0
  110. code_atlas/indexing/dependency_planner.py +268 -0
  111. code_atlas/indexing/persist.py +436 -0
  112. code_atlas/indexing/reuse.py +85 -0
  113. code_atlas/install/__init__.py +7 -0
  114. code_atlas/install/cli.py +168 -0
  115. code_atlas/install/lifecycle.py +286 -0
  116. code_atlas/mcp.py +674 -0
  117. code_atlas/observability/__init__.py +19 -0
  118. code_atlas/observability/logging.py +199 -0
  119. code_atlas/parsers/__init__.py +43 -0
  120. code_atlas/parsers/contracts.py +209 -0
  121. code_atlas/parsers/engine.py +125 -0
  122. code_atlas/parsers/languages/__init__.py +1 -0
  123. code_atlas/parsers/languages/python.py +391 -0
  124. code_atlas/parsers/languages/tsx.py +28 -0
  125. code_atlas/parsers/languages/typescript.py +909 -0
  126. code_atlas/parsers/registry.py +51 -0
  127. code_atlas/parsers/tree_sitter_runtime.py +118 -0
  128. code_atlas/resolvers/__init__.py +33 -0
  129. code_atlas/resolvers/bootstrap.py +246 -0
  130. code_atlas/resolvers/capabilities.py +33 -0
  131. code_atlas/resolvers/contracts.py +133 -0
  132. code_atlas/resolvers/coordinator.py +96 -0
  133. code_atlas/resolvers/enrichment.py +318 -0
  134. code_atlas/resolvers/lsp/__init__.py +26 -0
  135. code_atlas/resolvers/lsp/adapter.py +256 -0
  136. code_atlas/resolvers/lsp/lifecycle.py +251 -0
  137. code_atlas/resolvers/lsp/spawn.py +92 -0
  138. code_atlas/resolvers/lsp/transport.py +240 -0
  139. code_atlas/resolvers/registry.py +33 -0
  140. code_atlas/resolvers/scip/__init__.py +49 -0
  141. code_atlas/resolvers/scip/decoder.py +216 -0
  142. code_atlas/resolvers/scip/importer.py +248 -0
  143. code_atlas/resolvers/scip/loader.py +90 -0
  144. code_atlas/resolvers/scip/mapper.py +271 -0
  145. code_atlas/resolvers/scip/provenance.py +211 -0
  146. code_atlas/resolvers/scip/resolver.py +289 -0
  147. code_atlas/resolvers/scip/validation.py +101 -0
  148. code_atlas/resolvers/syntactic/__init__.py +8 -0
  149. code_atlas/resolvers/syntactic/resolver.py +254 -0
  150. code_atlas/search/__init__.py +32 -0
  151. code_atlas/search/contracts.py +176 -0
  152. code_atlas/search/domain.py +561 -0
  153. code_atlas/search/engine.py +530 -0
  154. code_atlas/search/federated.py +276 -0
  155. code_atlas/search/graph.py +282 -0
  156. code_atlas/search/hybrid.py +151 -0
  157. code_atlas/search/intent.py +48 -0
  158. code_atlas/search/lexical.py +176 -0
  159. code_atlas/search/refs.py +359 -0
  160. code_atlas/search/structural.py +505 -0
  161. code_atlas/search/tokenizer.py +43 -0
  162. code_atlas/search/vector/__init__.py +35 -0
  163. code_atlas/search/vector/contracts.py +76 -0
  164. code_atlas/search/vector/embeddings.py +96 -0
  165. code_atlas/search/vector/index.py +169 -0
  166. code_atlas/search/vector/provider.py +50 -0
  167. code_atlas/storage/__init__.py +14 -0
  168. code_atlas/storage/db.py +47 -0
  169. code_atlas/storage/migrations.py +552 -0
  170. code_atlas/storage/store.py +1045 -0
  171. code_atlas/ui/__init__.py +29 -0
  172. code_atlas/ui/i18n.py +143 -0
  173. code_atlas/ui/kit.py +689 -0
  174. code_atlas/ui/wizard.py +317 -0
  175. code_atlas_forge-1.1.0.dist-info/METADATA +246 -0
  176. code_atlas_forge-1.1.0.dist-info/RECORD +181 -0
  177. code_atlas_forge-1.1.0.dist-info/WHEEL +5 -0
  178. code_atlas_forge-1.1.0.dist-info/entry_points.txt +2 -0
  179. code_atlas_forge-1.1.0.dist-info/licenses/LICENSE +202 -0
  180. code_atlas_forge-1.1.0.dist-info/licenses/NOTICE +17 -0
  181. code_atlas_forge-1.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,278 @@
1
+ """Detector de mudanças manifesto-vs-snapshot (lf-0019).
2
+
3
+ Compara o scan atual com o inventário do último snapshot READY e
4
+ produz um ``ChangeSet`` tipado — insumo do ``InvalidationPlanner``.
5
+
6
+ Regras inegociáveis:
7
+
8
+ - ``RENAMED`` **somente** quando um path deletado e um adicionado têm
9
+ sha256 idêntico — evidência de conteúdo, nunca heurística de nome.
10
+ - Bump de versão de parser/extrator/grammar reclassifica os arquivos da
11
+ linguagem afetada como ``MODIFIED``.
12
+ - Rename-of-case está reservado para lf-0021 e não é tratado aqui.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ from dataclasses import dataclass
19
+ from enum import StrEnum
20
+
21
+ from code_atlas.discovery.models import ScanResult, SourceFile
22
+
23
+
24
+ class ChangeKind(StrEnum):
25
+ ADDED = "added"
26
+ MODIFIED = "modified"
27
+ DELETED = "deleted"
28
+ RENAMED = "renamed"
29
+ UNCHANGED = "unchanged"
30
+ ANALYZER_CHANGED = "analyzer-changed"
31
+ RESOLVER_CHANGED = "resolver-changed"
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class FileChange:
36
+ """Mudança tipada de um arquivo, com razão auditável."""
37
+
38
+ path: str
39
+ kind: ChangeKind
40
+ reason: str
41
+ old_path: str = "" # preenchido para RENAMED (path deletado)
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class ChangeSet:
46
+ """Resultado do diff manifesto ↔ snapshot anterior."""
47
+
48
+ changes: tuple[FileChange, ...]
49
+ analyzer_changes: tuple[str, ...] = () # chaves de versão alteradas
50
+ resolver_changes: tuple[str, ...] = ()
51
+
52
+ def by_kind(self, kind: ChangeKind) -> tuple[FileChange, ...]:
53
+ return tuple(c for c in self.changes if c.kind == kind)
54
+
55
+ def for_path(self, path: str) -> FileChange | None:
56
+ for c in self.changes:
57
+ if c.path == path:
58
+ return c
59
+ return None
60
+
61
+ def counts(self) -> dict[str, int]:
62
+ out: dict[str, int] = {}
63
+ for c in self.changes:
64
+ out[str(c.kind)] = out.get(str(c.kind), 0) + 1
65
+ return dict(sorted(out.items()))
66
+
67
+ def to_dict(self) -> dict[str, object]:
68
+ return {
69
+ "changes": [
70
+ {
71
+ "path": c.path,
72
+ "kind": str(c.kind),
73
+ "reason": c.reason,
74
+ **({"old_path": c.old_path} if c.old_path else {}),
75
+ }
76
+ for c in sorted(self.changes, key=lambda x: (x.path, str(x.kind)))
77
+ ],
78
+ "analyzer_changes": sorted(self.analyzer_changes),
79
+ "resolver_changes": sorted(self.resolver_changes),
80
+ }
81
+
82
+ def to_json(self) -> str:
83
+ return json.dumps(self.to_dict(), sort_keys=True)
84
+
85
+
86
+ @dataclass
87
+ class _PriorFile:
88
+ path: str
89
+ language: str
90
+ sha256: str | None
91
+ parse_status: str
92
+
93
+
94
+ def _affected_languages(
95
+ prior_versions: dict[str, str], current_versions: dict[str, str]
96
+ ) -> tuple[tuple[str, ...], tuple[str, ...]]:
97
+ """Chaves de versão alteradas → (analyzer_keys, resolver_keys).
98
+
99
+ Versões ``parser:<lang>`` classificam arquivos daquela linguagem;
100
+ ``resolver:<id>`` afeta relações de todas as linguagens suportadas.
101
+ """
102
+ analyzer: list[str] = []
103
+ resolver: list[str] = []
104
+ for key in sorted(set(prior_versions) | set(current_versions)):
105
+ if prior_versions.get(key) == current_versions.get(key):
106
+ continue
107
+ if key.startswith("resolver:"):
108
+ resolver.append(key)
109
+ else:
110
+ analyzer.append(key)
111
+ return tuple(analyzer), tuple(resolver)
112
+
113
+
114
+ def detect_changes(
115
+ scan: ScanResult,
116
+ prior_inventory: list[dict[str, object]],
117
+ prior_versions: dict[str, str],
118
+ current_versions: dict[str, str],
119
+ ) -> ChangeSet:
120
+ """Diff do manifesto atual contra o inventário do snapshot anterior.
121
+
122
+ ``prior_inventory`` vem de ``Store.snapshot_inventory`` — dicts com
123
+ ``path``/``language``/``sha256``/``parse_status``.
124
+ """
125
+ prior = {
126
+ str(r["path"]): _PriorFile(
127
+ path=str(r["path"]),
128
+ language=str(r["language"]),
129
+ sha256=r["sha256"] if isinstance(r["sha256"], str) else None,
130
+ parse_status=str(r.get("parse_status", "unparsed")),
131
+ )
132
+ for r in prior_inventory
133
+ }
134
+ current = {f.path: f for f in scan.files}
135
+
136
+ analyzer_keys, resolver_keys = _affected_languages(
137
+ prior_versions, current_versions
138
+ )
139
+ bumped_langs = {
140
+ key.split(":", 1)[1] for key in analyzer_keys if key.startswith("parser:")
141
+ }
142
+ # analyzer não-linguístico mudou (scanner/scan_mode): sem prova de
143
+ # extração idêntica → todos os arquivos pré-existentes reclassificam.
144
+ global_bump = any(not k.startswith("parser:") for k in analyzer_keys)
145
+
146
+ changes, added = _classify_present(
147
+ current, prior, bumped_langs, global_bump,
148
+ analyzer_keys, prior_versions, current_versions,
149
+ )
150
+ deleted = {p: pf for p, pf in prior.items() if p not in current}
151
+ _mark_deleted(changes, deleted)
152
+ _mark_renames(changes, added, deleted)
153
+ _mark_added_bumps(changes, added, bumped_langs)
154
+
155
+ return ChangeSet(
156
+ changes=tuple(sorted(changes, key=lambda c: (c.path, str(c.kind)))),
157
+ analyzer_changes=analyzer_keys,
158
+ resolver_changes=resolver_keys,
159
+ )
160
+
161
+
162
+ def _classify_present(
163
+ current: dict[str, SourceFile],
164
+ prior: dict[str, _PriorFile],
165
+ bumped_langs: set[str],
166
+ global_bump: bool,
167
+ analyzer_keys: tuple[str, ...],
168
+ prior_versions: dict[str, str],
169
+ current_versions: dict[str, str],
170
+ ) -> tuple[list[FileChange], dict[str, SourceFile]]:
171
+ """Classifica arquivos presentes: ADDED / MODIFIED / UNCHANGED."""
172
+ changes: list[FileChange] = []
173
+ added: dict[str, SourceFile] = {}
174
+ for path in sorted(current):
175
+ f = current[path]
176
+ prev = prior.get(path)
177
+ if prev is None:
178
+ added[path] = f
179
+ changes.append(
180
+ FileChange(path, ChangeKind.ADDED, "not in previous snapshot")
181
+ )
182
+ continue
183
+ if global_bump or prev.language in bumped_langs:
184
+ key = (
185
+ f"parser:{prev.language}"
186
+ if prev.language in bumped_langs
187
+ else analyzer_keys[0]
188
+ )
189
+ changes.append(
190
+ FileChange(
191
+ path, ChangeKind.MODIFIED,
192
+ f"analyzer version changed "
193
+ f"({prior_versions.get(key)} -> "
194
+ f"{current_versions.get(key)})",
195
+ )
196
+ )
197
+ elif f.sha256 is None or prev.sha256 is None:
198
+ # sem hash não há prova de conteúdo idêntico → conservador
199
+ changes.append(
200
+ FileChange(
201
+ path, ChangeKind.MODIFIED,
202
+ "content hash unavailable (cannot prove identical)",
203
+ )
204
+ )
205
+ elif prev.sha256 != f.sha256:
206
+ changes.append(
207
+ FileChange(path, ChangeKind.MODIFIED, "content hash differs")
208
+ )
209
+ else:
210
+ changes.append(
211
+ FileChange(path, ChangeKind.UNCHANGED, "identical content hash")
212
+ )
213
+ return changes, added
214
+
215
+
216
+ def _mark_deleted(
217
+ changes: list[FileChange], deleted: dict[str, _PriorFile]
218
+ ) -> None:
219
+ for path in sorted(deleted):
220
+ changes.append(
221
+ FileChange(path, ChangeKind.DELETED, "absent from current manifest")
222
+ )
223
+
224
+
225
+ def _swap_added(changes: list[FileChange], path: str, new: FileChange) -> None:
226
+ """Substitui a entrada ADDED de ``path`` (rename/bump a reclassifica).
227
+ No-op se o path já foi reclassificado (ex.: virou RENAMED)."""
228
+ for i, c in enumerate(changes):
229
+ if c.path == path and c.kind == ChangeKind.ADDED:
230
+ changes[i] = new
231
+ return
232
+
233
+
234
+ def _mark_renames(
235
+ changes: list[FileChange],
236
+ added: dict[str, SourceFile],
237
+ deleted: dict[str, _PriorFile],
238
+ ) -> None:
239
+ """Renames — evidência estrita: hash deletado == hash adicionado.
240
+ Um path deletado casa no máximo um adicionado (primeiro em ordem)."""
241
+ sha_to_deleted: dict[str, list[str]] = {}
242
+ for p in sorted(deleted):
243
+ sha = deleted[p].sha256
244
+ if sha:
245
+ sha_to_deleted.setdefault(sha, []).append(p)
246
+ for path in sorted(added):
247
+ sha = added[path].sha256
248
+ if not sha:
249
+ continue
250
+ cands = sha_to_deleted.get(sha) or []
251
+ if cands:
252
+ old = cands.pop(0)
253
+ _swap_added(
254
+ changes, path,
255
+ FileChange(
256
+ path, ChangeKind.RENAMED,
257
+ f"content-identical to deleted {old}", old_path=old,
258
+ ),
259
+ )
260
+
261
+
262
+ def _mark_added_bumps(
263
+ changes: list[FileChange],
264
+ added: dict[str, SourceFile],
265
+ bumped_langs: set[str],
266
+ ) -> None:
267
+ """Arquivos novos de linguagem com bump levam razão enriquecida."""
268
+ for path in sorted(added):
269
+ f = added[path]
270
+ if f.language in bumped_langs:
271
+ _swap_added(
272
+ changes, path,
273
+ FileChange(
274
+ path, ChangeKind.ADDED,
275
+ "not in previous snapshot; analyzer bumped for "
276
+ f"{f.language}",
277
+ ),
278
+ )