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,449 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "urn:codedebrief:schema:codedebrief:2.0",
4
+ "title": "CodeDebrief Logical Model",
5
+ "description": "Canonical decision-flow artifact generated from source code.",
6
+ "type": "object",
7
+ "required": [
8
+ "schema_version",
9
+ "generated_at",
10
+ "root",
11
+ "flows",
12
+ "files",
13
+ "metadata"
14
+ ],
15
+ "properties": {
16
+ "schema_version": {
17
+ "const": "2.0"
18
+ },
19
+ "generated_at": {
20
+ "type": "string",
21
+ "format": "date-time"
22
+ },
23
+ "root": {
24
+ "type": "string"
25
+ },
26
+ "flows": {
27
+ "type": "array",
28
+ "items": {
29
+ "$ref": "#/$defs/flow"
30
+ }
31
+ },
32
+ "files": {
33
+ "type": "array",
34
+ "items": {
35
+ "$ref": "#/$defs/file"
36
+ }
37
+ },
38
+ "metadata": {
39
+ "$ref": "#/$defs/project_metadata"
40
+ }
41
+ },
42
+ "$defs": {
43
+ "string_array": {
44
+ "type": "array",
45
+ "items": {
46
+ "type": "string"
47
+ }
48
+ },
49
+ "non_negative_integer": {
50
+ "type": "integer",
51
+ "minimum": 0
52
+ },
53
+ "ratio": {
54
+ "type": "number",
55
+ "minimum": 0,
56
+ "maximum": 1
57
+ },
58
+ "json_value": {
59
+ "description": "Detector-specific structured metadata value.",
60
+ "oneOf": [
61
+ {
62
+ "type": "null"
63
+ },
64
+ {
65
+ "type": "boolean"
66
+ },
67
+ {
68
+ "type": "number"
69
+ },
70
+ {
71
+ "type": "string"
72
+ },
73
+ {
74
+ "type": "array"
75
+ },
76
+ {
77
+ "type": "object"
78
+ }
79
+ ]
80
+ },
81
+ "location": {
82
+ "type": "object",
83
+ "required": ["path", "start_line", "end_line"],
84
+ "properties": {
85
+ "path": {
86
+ "type": "string"
87
+ },
88
+ "start_line": {
89
+ "type": "integer",
90
+ "minimum": 1
91
+ },
92
+ "end_line": {
93
+ "type": "integer",
94
+ "minimum": 1
95
+ }
96
+ },
97
+ "additionalProperties": false
98
+ },
99
+ "node": {
100
+ "type": "object",
101
+ "required": [
102
+ "id",
103
+ "kind",
104
+ "label",
105
+ "location",
106
+ "evidence",
107
+ "detail",
108
+ "metadata"
109
+ ],
110
+ "properties": {
111
+ "id": {
112
+ "type": "string"
113
+ },
114
+ "kind": {
115
+ "enum": ["entry", "action", "decision", "call", "terminal", "error"]
116
+ },
117
+ "label": {
118
+ "type": "string"
119
+ },
120
+ "location": {
121
+ "$ref": "#/$defs/location"
122
+ },
123
+ "evidence": {
124
+ "$ref": "#/$defs/evidence"
125
+ },
126
+ "detail": {
127
+ "type": "string"
128
+ },
129
+ "metadata": {
130
+ "type": "object"
131
+ }
132
+ },
133
+ "additionalProperties": false
134
+ },
135
+ "edge": {
136
+ "type": "object",
137
+ "required": ["id", "source", "target", "label", "evidence"],
138
+ "properties": {
139
+ "id": {
140
+ "type": "string"
141
+ },
142
+ "source": {
143
+ "type": "string"
144
+ },
145
+ "target": {
146
+ "type": "string"
147
+ },
148
+ "label": {
149
+ "type": "string"
150
+ },
151
+ "evidence": {
152
+ "$ref": "#/$defs/evidence"
153
+ }
154
+ },
155
+ "additionalProperties": false
156
+ },
157
+ "flow": {
158
+ "type": "object",
159
+ "required": [
160
+ "id",
161
+ "name",
162
+ "symbol",
163
+ "language",
164
+ "framework",
165
+ "entry_kind",
166
+ "is_entrypoint",
167
+ "location",
168
+ "nodes",
169
+ "edges",
170
+ "calls",
171
+ "called_by",
172
+ "tests",
173
+ "metadata"
174
+ ],
175
+ "properties": {
176
+ "id": {
177
+ "type": "string"
178
+ },
179
+ "name": {
180
+ "type": "string"
181
+ },
182
+ "symbol": {
183
+ "type": "string"
184
+ },
185
+ "language": {
186
+ "enum": [
187
+ "python",
188
+ "typescript",
189
+ "javascript",
190
+ "go",
191
+ "java",
192
+ "csharp",
193
+ "php",
194
+ "c",
195
+ "cpp",
196
+ "rust",
197
+ "ruby"
198
+ ]
199
+ },
200
+ "framework": {
201
+ "type": "string"
202
+ },
203
+ "entry_kind": {
204
+ "type": "string"
205
+ },
206
+ "is_entrypoint": {
207
+ "type": "boolean"
208
+ },
209
+ "location": {
210
+ "$ref": "#/$defs/location"
211
+ },
212
+ "nodes": {
213
+ "type": "array",
214
+ "items": {
215
+ "$ref": "#/$defs/node"
216
+ }
217
+ },
218
+ "edges": {
219
+ "type": "array",
220
+ "items": {
221
+ "$ref": "#/$defs/edge"
222
+ }
223
+ },
224
+ "calls": {
225
+ "type": "array",
226
+ "items": {
227
+ "type": "string"
228
+ }
229
+ },
230
+ "called_by": {
231
+ "type": "array",
232
+ "items": {
233
+ "type": "string"
234
+ }
235
+ },
236
+ "tests": {
237
+ "type": "array",
238
+ "items": {
239
+ "type": "string"
240
+ }
241
+ },
242
+ "metadata": {
243
+ "type": "object"
244
+ }
245
+ },
246
+ "additionalProperties": false
247
+ },
248
+ "file": {
249
+ "type": "object",
250
+ "required": ["path", "language", "sha256", "flow_ids"],
251
+ "properties": {
252
+ "path": {
253
+ "type": "string"
254
+ },
255
+ "language": {
256
+ "enum": [
257
+ "python",
258
+ "typescript",
259
+ "javascript",
260
+ "go",
261
+ "java",
262
+ "csharp",
263
+ "php",
264
+ "c",
265
+ "cpp",
266
+ "rust",
267
+ "ruby"
268
+ ]
269
+ },
270
+ "sha256": {
271
+ "type": "string",
272
+ "pattern": "^[a-f0-9]{64}$"
273
+ },
274
+ "flow_ids": {
275
+ "type": "array",
276
+ "items": {
277
+ "type": "string"
278
+ }
279
+ },
280
+ "dependencies": {
281
+ "type": "array",
282
+ "items": {
283
+ "type": "string"
284
+ }
285
+ }
286
+ },
287
+ "additionalProperties": false
288
+ },
289
+ "evidence": {
290
+ "enum": ["VERIFIED", "INFERRED", "POTENTIAL_GAP"]
291
+ },
292
+ "project_metadata": {
293
+ "type": "object",
294
+ "properties": {
295
+ "flow_count": {
296
+ "$ref": "#/$defs/non_negative_integer"
297
+ },
298
+ "entrypoint_count": {
299
+ "$ref": "#/$defs/non_negative_integer"
300
+ },
301
+ "languages": {
302
+ "$ref": "#/$defs/string_array"
303
+ },
304
+ "scopes": {
305
+ "type": "object",
306
+ "additionalProperties": {
307
+ "$ref": "#/$defs/non_negative_integer"
308
+ }
309
+ },
310
+ "enums": {
311
+ "type": "object",
312
+ "additionalProperties": {
313
+ "type": "object",
314
+ "additionalProperties": {
315
+ "$ref": "#/$defs/string_array"
316
+ }
317
+ }
318
+ },
319
+ "quality": {
320
+ "$ref": "#/$defs/quality"
321
+ },
322
+ "language_capabilities": {
323
+ "type": "object",
324
+ "additionalProperties": {
325
+ "$ref": "#/$defs/language_capability"
326
+ }
327
+ },
328
+ "skipped_files": {
329
+ "$ref": "#/$defs/skipped_files"
330
+ }
331
+ },
332
+ "additionalProperties": true
333
+ },
334
+ "quality": {
335
+ "type": "object",
336
+ "properties": {
337
+ "files": {
338
+ "type": "object"
339
+ },
340
+ "flows": {
341
+ "type": "object"
342
+ },
343
+ "calls": {
344
+ "type": "object"
345
+ },
346
+ "labels": {
347
+ "type": "object"
348
+ },
349
+ "source_locations": {
350
+ "type": "object"
351
+ },
352
+ "graph": {
353
+ "type": "object"
354
+ },
355
+ "languages": {
356
+ "type": "object"
357
+ }
358
+ },
359
+ "additionalProperties": true
360
+ },
361
+ "language_capability": {
362
+ "type": "object",
363
+ "required": ["status", "frontend", "suffixes", "features", "limitations"],
364
+ "properties": {
365
+ "status": {
366
+ "type": "string"
367
+ },
368
+ "frontend": {
369
+ "type": "string"
370
+ },
371
+ "suffixes": {
372
+ "$ref": "#/$defs/string_array"
373
+ },
374
+ "features": {
375
+ "type": "object",
376
+ "additionalProperties": {
377
+ "type": "string"
378
+ }
379
+ },
380
+ "limitations": {
381
+ "type": "object",
382
+ "additionalProperties": {
383
+ "type": "string"
384
+ }
385
+ }
386
+ },
387
+ "additionalProperties": true
388
+ },
389
+ "skipped_files": {
390
+ "oneOf": [
391
+ {
392
+ "type": "array",
393
+ "items": {
394
+ "type": "object",
395
+ "required": ["path", "language", "reason"],
396
+ "properties": {
397
+ "path": {
398
+ "type": "string"
399
+ },
400
+ "language": {
401
+ "type": "string"
402
+ },
403
+ "reason": {
404
+ "type": "string"
405
+ }
406
+ },
407
+ "additionalProperties": true
408
+ }
409
+ },
410
+ {
411
+ "type": "object",
412
+ "required": ["total", "by_reason", "sample"],
413
+ "properties": {
414
+ "total": {
415
+ "$ref": "#/$defs/non_negative_integer"
416
+ },
417
+ "by_reason": {
418
+ "type": "object",
419
+ "additionalProperties": {
420
+ "$ref": "#/$defs/non_negative_integer"
421
+ }
422
+ },
423
+ "sample": {
424
+ "type": "array",
425
+ "items": {
426
+ "type": "object",
427
+ "required": ["path", "language", "reason"],
428
+ "properties": {
429
+ "path": {
430
+ "type": "string"
431
+ },
432
+ "language": {
433
+ "type": "string"
434
+ },
435
+ "reason": {
436
+ "type": "string"
437
+ }
438
+ },
439
+ "additionalProperties": true
440
+ }
441
+ }
442
+ },
443
+ "additionalProperties": true
444
+ }
445
+ ]
446
+ }
447
+ },
448
+ "additionalProperties": false
449
+ }
codedebrief/util.py ADDED
@@ -0,0 +1,65 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import re
6
+ from pathlib import Path
7
+ from typing import Any, cast
8
+
9
+
10
+ def stable_id(*parts: str, length: int = 16) -> str:
11
+ payload = "\x1f".join(parts).encode("utf-8")
12
+ return hashlib.sha256(payload).hexdigest()[:length]
13
+
14
+
15
+ def file_sha256(path: Path) -> str:
16
+ digest = hashlib.sha256()
17
+ with path.open("rb") as handle:
18
+ for chunk in iter(lambda: handle.read(1024 * 1024), b""):
19
+ digest.update(chunk)
20
+ return digest.hexdigest()
21
+
22
+
23
+ def read_json(path: Path) -> dict[str, Any]:
24
+ text = path.read_text(encoding="utf-8")
25
+ try:
26
+ return cast(dict[str, Any], json.loads(text))
27
+ except json.JSONDecodeError as error:
28
+ # Name the offending file: a bare "Expecting value: line 1 column 1" gives a
29
+ # caller no way to find which cache/model file is corrupt.
30
+ raise ValueError(f"invalid JSON in {path}: {error}") from error
31
+
32
+
33
+ def write_json(path: Path, data: dict[str, Any]) -> None:
34
+ path.parent.mkdir(parents=True, exist_ok=True)
35
+ path.write_text(
36
+ json.dumps(data, indent=2, ensure_ascii=False, sort_keys=False) + "\n",
37
+ encoding="utf-8",
38
+ )
39
+
40
+
41
+ def compact_text(value: str, limit: int = 100) -> str:
42
+ value = re.sub(r"\s+", " ", value).strip()
43
+ if len(value) <= limit:
44
+ return value
45
+ return value[: limit - 1].rstrip() + "..."
46
+
47
+
48
+ def metadata_scope_names(metadata: dict[str, Any]) -> list[str]:
49
+ scopes = metadata.get("scope", [])
50
+ if isinstance(scopes, str):
51
+ return [scopes] if scopes else []
52
+ if not isinstance(scopes, (list, tuple, set)):
53
+ return []
54
+ names: list[str] = []
55
+ for scope in scopes:
56
+ if scope is None:
57
+ continue
58
+ name = scope if isinstance(scope, str) else str(scope)
59
+ if name:
60
+ names.append(name)
61
+ return names
62
+
63
+
64
+ def relpath(path: Path, root: Path) -> str:
65
+ return path.resolve().relative_to(root.resolve()).as_posix()