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.
- codedebrief/__init__.py +12 -0
- codedebrief/analysis/__init__.py +16 -0
- codedebrief/analysis/common.py +527 -0
- codedebrief/analysis/discovery.py +100 -0
- codedebrief/analysis/languages/__init__.py +6 -0
- codedebrief/analysis/languages/_common.py +68 -0
- codedebrief/analysis/languages/c.py +96 -0
- codedebrief/analysis/languages/cpp.py +146 -0
- codedebrief/analysis/languages/csharp.py +137 -0
- codedebrief/analysis/languages/go.py +157 -0
- codedebrief/analysis/languages/java.py +158 -0
- codedebrief/analysis/languages/php.py +83 -0
- codedebrief/analysis/languages/ruby.py +75 -0
- codedebrief/analysis/languages/rust.py +96 -0
- codedebrief/analysis/project.py +373 -0
- codedebrief/analysis/python.py +939 -0
- codedebrief/analysis/registry.py +320 -0
- codedebrief/analysis/treesitter.py +884 -0
- codedebrief/analysis/typescript.py +1019 -0
- codedebrief/artifacts.py +49 -0
- codedebrief/cli.py +585 -0
- codedebrief/config.py +226 -0
- codedebrief/doctor.py +175 -0
- codedebrief/install.py +441 -0
- codedebrief/mcp_server.py +2720 -0
- codedebrief/model.py +189 -0
- codedebrief/py.typed +1 -0
- codedebrief/quality.py +392 -0
- codedebrief/query.py +641 -0
- codedebrief/render/__init__.py +6 -0
- codedebrief/render/assets/generated/codedebrief-viewer-runtime.iife.js +10 -0
- codedebrief/render/assets/panels.js +462 -0
- codedebrief/render/assets/shell.js +1649 -0
- codedebrief/render/assets/styles.css +1715 -0
- codedebrief/render/assets/tree.js +616 -0
- codedebrief/render/html.py +191 -0
- codedebrief/render/markdown.py +153 -0
- codedebrief/render/payload.py +326 -0
- codedebrief/render/snapshot.py +769 -0
- codedebrief/schema/codedebrief.schema.json +449 -0
- codedebrief/util.py +65 -0
- codedebrief/validation.py +214 -0
- codedebrief-0.11.0.dist-info/METADATA +426 -0
- codedebrief-0.11.0.dist-info/RECORD +48 -0
- codedebrief-0.11.0.dist-info/WHEEL +4 -0
- codedebrief-0.11.0.dist-info/entry_points.txt +2 -0
- codedebrief-0.11.0.dist-info/licenses/LICENSE +176 -0
- 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
|