codedebrief 0.11.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 (48) hide show
  1. codedebrief/__init__.py +12 -0
  2. codedebrief/analysis/__init__.py +16 -0
  3. codedebrief/analysis/common.py +527 -0
  4. codedebrief/analysis/discovery.py +100 -0
  5. codedebrief/analysis/languages/__init__.py +6 -0
  6. codedebrief/analysis/languages/_common.py +68 -0
  7. codedebrief/analysis/languages/c.py +96 -0
  8. codedebrief/analysis/languages/cpp.py +146 -0
  9. codedebrief/analysis/languages/csharp.py +137 -0
  10. codedebrief/analysis/languages/go.py +157 -0
  11. codedebrief/analysis/languages/java.py +158 -0
  12. codedebrief/analysis/languages/php.py +83 -0
  13. codedebrief/analysis/languages/ruby.py +75 -0
  14. codedebrief/analysis/languages/rust.py +96 -0
  15. codedebrief/analysis/project.py +373 -0
  16. codedebrief/analysis/python.py +939 -0
  17. codedebrief/analysis/registry.py +320 -0
  18. codedebrief/analysis/treesitter.py +884 -0
  19. codedebrief/analysis/typescript.py +1019 -0
  20. codedebrief/artifacts.py +49 -0
  21. codedebrief/cli.py +585 -0
  22. codedebrief/config.py +226 -0
  23. codedebrief/doctor.py +175 -0
  24. codedebrief/install.py +441 -0
  25. codedebrief/mcp_server.py +2720 -0
  26. codedebrief/model.py +189 -0
  27. codedebrief/py.typed +1 -0
  28. codedebrief/quality.py +392 -0
  29. codedebrief/query.py +641 -0
  30. codedebrief/render/__init__.py +6 -0
  31. codedebrief/render/assets/generated/codedebrief-viewer-runtime.iife.js +10 -0
  32. codedebrief/render/assets/panels.js +462 -0
  33. codedebrief/render/assets/shell.js +1649 -0
  34. codedebrief/render/assets/styles.css +1715 -0
  35. codedebrief/render/assets/tree.js +616 -0
  36. codedebrief/render/html.py +191 -0
  37. codedebrief/render/markdown.py +153 -0
  38. codedebrief/render/payload.py +326 -0
  39. codedebrief/render/snapshot.py +769 -0
  40. codedebrief/schema/codedebrief.schema.json +449 -0
  41. codedebrief/util.py +65 -0
  42. codedebrief/validation.py +214 -0
  43. codedebrief-0.11.0.dist-info/METADATA +426 -0
  44. codedebrief-0.11.0.dist-info/RECORD +48 -0
  45. codedebrief-0.11.0.dist-info/WHEEL +4 -0
  46. codedebrief-0.11.0.dist-info/entry_points.txt +2 -0
  47. codedebrief-0.11.0.dist-info/licenses/LICENSE +176 -0
  48. codedebrief-0.11.0.dist-info/licenses/NOTICE +9 -0
@@ -0,0 +1,320 @@
1
+ """The language registry: the single place that maps a language to its file suffixes
2
+ and an analyzer factory.
3
+
4
+ Adding a language is one `LanguageSpec` entry here plus its analyzer (a dedicated class
5
+ for Python, or a `LanguageProfile` for the profile-driven tree-sitter engine). Discovery
6
+ and the project loop dispatch through this registry, so neither needs to change.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from collections.abc import Callable
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+ from typing import Protocol
15
+
16
+ from codedebrief.config import CodeDebriefConfig
17
+ from codedebrief.model import FileAnalysis
18
+
19
+
20
+ class LanguageAnalyzer(Protocol):
21
+ """Every language front-end turns one source file into a `FileAnalysis`."""
22
+
23
+ def analyze(self, path: Path) -> FileAnalysis: ...
24
+
25
+
26
+ AnalyzerFactory = Callable[[Path, CodeDebriefConfig], LanguageAnalyzer]
27
+
28
+
29
+ @dataclass(frozen=True, slots=True)
30
+ class LanguageSpec:
31
+ id: str
32
+ suffixes: tuple[str, ...]
33
+ factory: AnalyzerFactory
34
+
35
+
36
+ def _make_python(root: Path, config: CodeDebriefConfig) -> LanguageAnalyzer:
37
+ from codedebrief.analysis.python import PythonAnalyzer
38
+
39
+ return PythonAnalyzer(root, config)
40
+
41
+
42
+ def _make_typescript(root: Path, config: CodeDebriefConfig) -> LanguageAnalyzer:
43
+ from codedebrief.analysis.typescript import TypeScriptAnalyzer
44
+
45
+ return TypeScriptAnalyzer(root, config)
46
+
47
+
48
+ def _make_go(root: Path, config: CodeDebriefConfig) -> LanguageAnalyzer:
49
+ # Lazy import so the Go grammar (.so) loads only when a .go file is analyzed.
50
+ from codedebrief.analysis.languages.go import build_analyzer
51
+
52
+ return build_analyzer(root, config)
53
+
54
+
55
+ def _make_java(root: Path, config: CodeDebriefConfig) -> LanguageAnalyzer:
56
+ from codedebrief.analysis.languages.java import build_analyzer
57
+
58
+ return build_analyzer(root, config)
59
+
60
+
61
+ def _make_csharp(root: Path, config: CodeDebriefConfig) -> LanguageAnalyzer:
62
+ from codedebrief.analysis.languages.csharp import build_analyzer
63
+
64
+ return build_analyzer(root, config)
65
+
66
+
67
+ def _make_php(root: Path, config: CodeDebriefConfig) -> LanguageAnalyzer:
68
+ from codedebrief.analysis.languages.php import build_analyzer
69
+
70
+ return build_analyzer(root, config)
71
+
72
+
73
+ def _make_c(root: Path, config: CodeDebriefConfig) -> LanguageAnalyzer:
74
+ from codedebrief.analysis.languages.c import build_analyzer
75
+
76
+ return build_analyzer(root, config)
77
+
78
+
79
+ def _make_cpp(root: Path, config: CodeDebriefConfig) -> LanguageAnalyzer:
80
+ from codedebrief.analysis.languages.cpp import build_analyzer
81
+
82
+ return build_analyzer(root, config)
83
+
84
+
85
+ def _make_rust(root: Path, config: CodeDebriefConfig) -> LanguageAnalyzer:
86
+ from codedebrief.analysis.languages.rust import build_analyzer
87
+
88
+ return build_analyzer(root, config)
89
+
90
+
91
+ def _make_ruby(root: Path, config: CodeDebriefConfig) -> LanguageAnalyzer:
92
+ from codedebrief.analysis.languages.ruby import build_analyzer
93
+
94
+ return build_analyzer(root, config)
95
+
96
+
97
+ # The order is the dispatch precedence when two specs claim the same suffix (none do today).
98
+ LANGUAGES: tuple[LanguageSpec, ...] = (
99
+ LanguageSpec("python", (".py",), _make_python),
100
+ # JavaScript reuses the TypeScript analyzer (grammar superset) to keep the
101
+ # Next.js / React entry-point detection; the IR labels it "javascript".
102
+ LanguageSpec("typescript", (".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"), _make_typescript),
103
+ LanguageSpec("go", (".go",), _make_go),
104
+ LanguageSpec("java", (".java",), _make_java),
105
+ LanguageSpec("csharp", (".cs",), _make_csharp),
106
+ LanguageSpec("php", (".php",), _make_php),
107
+ LanguageSpec("c", (".c", ".h"), _make_c),
108
+ LanguageSpec("cpp", (".cc", ".cpp", ".cxx", ".hh", ".hpp", ".hxx", ".ipp", ".tpp"), _make_cpp),
109
+ LanguageSpec("rust", (".rs",), _make_rust),
110
+ LanguageSpec("ruby", (".rb",), _make_ruby),
111
+ )
112
+
113
+ _BY_SUFFIX: dict[str, LanguageSpec] = {
114
+ suffix: spec for spec in LANGUAGES for suffix in spec.suffixes
115
+ }
116
+ _BY_ID: dict[str, LanguageSpec] = {spec.id: spec for spec in LANGUAGES}
117
+
118
+ _FEATURES: tuple[str, ...] = (
119
+ "functions_methods",
120
+ "entrypoint_heuristics",
121
+ "decisions",
122
+ "switch_match",
123
+ "loops",
124
+ "calls",
125
+ "returns_throws",
126
+ "expression_bodied_functions",
127
+ "try_catch",
128
+ "test_detection",
129
+ "enum_harvest",
130
+ "qualified_call_links",
131
+ "import_dependencies",
132
+ )
133
+
134
+ _BASE_FEATURES: dict[str, str] = {
135
+ "functions_methods": "supported",
136
+ "entrypoint_heuristics": "supported",
137
+ "decisions": "supported",
138
+ "switch_match": "supported",
139
+ "loops": "supported",
140
+ "calls": "supported",
141
+ "returns_throws": "supported",
142
+ "expression_bodied_functions": "not_supported",
143
+ "try_catch": "supported",
144
+ "test_detection": "supported",
145
+ "enum_harvest": "not_supported",
146
+ "qualified_call_links": "partial",
147
+ "import_dependencies": "not_supported",
148
+ }
149
+
150
+ _LANGUAGE_OVERRIDES: dict[str, dict[str, str]] = {
151
+ "javascript": {
152
+ "enum_harvest": "not_supported",
153
+ "expression_bodied_functions": "supported",
154
+ "import_dependencies": "supported",
155
+ },
156
+ "typescript": {
157
+ "enum_harvest": "supported",
158
+ "expression_bodied_functions": "supported",
159
+ "import_dependencies": "supported",
160
+ },
161
+ "python": {
162
+ "enum_harvest": "supported",
163
+ "qualified_call_links": "supported",
164
+ "import_dependencies": "supported",
165
+ },
166
+ "c": {
167
+ "entrypoint_heuristics": "partial",
168
+ "try_catch": "not_supported",
169
+ "qualified_call_links": "not_supported",
170
+ },
171
+ "cpp": {"entrypoint_heuristics": "partial", "qualified_call_links": "not_supported"},
172
+ "csharp": {"entrypoint_heuristics": "partial", "qualified_call_links": "not_supported"},
173
+ "go": {
174
+ "entrypoint_heuristics": "partial",
175
+ "try_catch": "not_supported",
176
+ "qualified_call_links": "supported",
177
+ "import_dependencies": "supported",
178
+ },
179
+ "java": {
180
+ "entrypoint_heuristics": "partial",
181
+ "qualified_call_links": "partial",
182
+ "import_dependencies": "supported",
183
+ },
184
+ "php": {"entrypoint_heuristics": "partial", "qualified_call_links": "not_supported"},
185
+ "ruby": {
186
+ "entrypoint_heuristics": "partial",
187
+ "try_catch": "not_supported",
188
+ "qualified_call_links": "not_supported",
189
+ },
190
+ "rust": {
191
+ "entrypoint_heuristics": "partial",
192
+ "returns_throws": "partial",
193
+ "try_catch": "not_supported",
194
+ "qualified_call_links": "not_supported",
195
+ },
196
+ }
197
+
198
+ _LIMITATION_NOTES: dict[str, dict[str, str]] = {
199
+ "entrypoint_heuristics": {
200
+ "partial": "Entrypoints use visibility/export heuristics rather than framework adapters.",
201
+ "not_supported": "No entrypoint heuristics are available for this language.",
202
+ },
203
+ "expression_bodied_functions": {
204
+ "not_supported": "Expression-bodied definitions are not modeled as return branches.",
205
+ },
206
+ "try_catch": {
207
+ "not_supported": "Error-boundary control flow is not modeled for this language.",
208
+ },
209
+ "enum_harvest": {
210
+ "not_supported": "Declared enum or closed-set extraction is not available.",
211
+ },
212
+ "qualified_call_links": {
213
+ "partial": "Common qualified calls are linked, but dynamic dispatch may remain unresolved.",
214
+ "not_supported": "Receiver or module calls are recorded but not statically linked.",
215
+ },
216
+ "import_dependencies": {
217
+ "not_supported": "Dependency-aware impact does not follow imports for this language yet.",
218
+ },
219
+ "returns_throws": {
220
+ "partial": "Return flow is modeled, but throw/raise semantics are incomplete.",
221
+ },
222
+ }
223
+
224
+ _FRONTENDS: dict[str, str] = {
225
+ "javascript": "typescript_tree_sitter",
226
+ "typescript": "typescript_tree_sitter",
227
+ "python": "python_ast",
228
+ "c": "tree_sitter_profile",
229
+ "cpp": "tree_sitter_profile",
230
+ "csharp": "tree_sitter_profile",
231
+ "go": "tree_sitter_profile",
232
+ "java": "tree_sitter_profile",
233
+ "php": "tree_sitter_profile",
234
+ "ruby": "tree_sitter_profile",
235
+ "rust": "tree_sitter_profile",
236
+ }
237
+
238
+ _STATUSES: dict[str, str] = dict.fromkeys(
239
+ (
240
+ "javascript",
241
+ "typescript",
242
+ "python",
243
+ "c",
244
+ "cpp",
245
+ "csharp",
246
+ "go",
247
+ "java",
248
+ "php",
249
+ "ruby",
250
+ "rust",
251
+ ),
252
+ "supported",
253
+ )
254
+
255
+
256
+ def supported_suffixes() -> frozenset[str]:
257
+ return frozenset(_BY_SUFFIX)
258
+
259
+
260
+ def supported_language_ids() -> tuple[str, ...]:
261
+ ids = []
262
+ for spec in LANGUAGES:
263
+ ids.append(spec.id)
264
+ # JavaScript files are parsed by the TypeScript grammar, but the IR labels
265
+ # plain JS files as "javascript" so consumers can distinguish them.
266
+ if spec.id == "typescript":
267
+ ids.append("javascript")
268
+ return tuple(ids)
269
+
270
+
271
+ def language_capability_matrix() -> dict[str, dict[str, object]]:
272
+ """Return a coarse, deterministic analyzer support matrix for agents and UI."""
273
+ matrix: dict[str, dict[str, object]] = {}
274
+ for language in supported_language_ids():
275
+ features = dict(_BASE_FEATURES)
276
+ features.update(_LANGUAGE_OVERRIDES.get(language, {}))
277
+ matrix[language] = {
278
+ "status": _STATUSES[language],
279
+ "frontend": _FRONTENDS[language],
280
+ "suffixes": list(_suffixes_for_language(language)),
281
+ "features": {key: features[key] for key in _FEATURES},
282
+ "limitations": _capability_limitations(features),
283
+ }
284
+ return matrix
285
+
286
+
287
+ def _capability_limitations(features: dict[str, str]) -> dict[str, str]:
288
+ limitations: dict[str, str] = {}
289
+ for feature in _FEATURES:
290
+ status = features[feature]
291
+ note = _LIMITATION_NOTES.get(feature, {}).get(status)
292
+ if note is not None:
293
+ limitations[feature] = note
294
+ return limitations
295
+
296
+
297
+ def spec_for_path(path: Path) -> LanguageSpec | None:
298
+ return _BY_SUFFIX.get(path.suffix.lower())
299
+
300
+
301
+ def language_for(path: Path) -> str:
302
+ spec = spec_for_path(path)
303
+ if spec is None:
304
+ raise ValueError(f"Unsupported source file: {path}")
305
+ return spec.id
306
+
307
+
308
+ def _suffixes_for_language(language: str) -> tuple[str, ...]:
309
+ if language == "javascript":
310
+ return (".js", ".jsx", ".mjs", ".cjs")
311
+ if language == "typescript":
312
+ return (".ts", ".tsx")
313
+ return spec_for_language(language).suffixes
314
+
315
+
316
+ def spec_for_language(language: str) -> LanguageSpec:
317
+ spec = _BY_ID.get(language)
318
+ if spec is None:
319
+ raise ValueError(f"Unknown language: {language}")
320
+ return spec