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,462 @@
1
+ // Right-column panels: analyzer quality and source/details. Both panels subscribe to
2
+ // the shared codeDebrief selection store so canvas, tree, and source stay synchronized.
3
+ //
4
+ // SECURITY: source text is inserted only with textContent/createTextNode. Source code
5
+ // is never interpreted as markup.
6
+ (function () {
7
+ const codeDebrief = window.CodeDebrief;
8
+ if (!codeDebrief) return;
9
+
10
+ const model = codeDebrief.model || {};
11
+ const byId = codeDebrief.byId || new Map();
12
+ const flows = codeDebrief.flows || [];
13
+ const scopeFlows = model.scopes || {};
14
+ const quality = (model.metadata && model.metadata.quality) || null;
15
+ const sourceFiles = model.source_files || {};
16
+
17
+ const sourcePanel = document.getElementById("sourcePanel");
18
+ const sourceBody = document.getElementById("source");
19
+ const sourceFileEl = document.getElementById("sourceFile");
20
+ const qualityPanel = document.getElementById("qualityPanel");
21
+ const qualityBody = document.getElementById("quality");
22
+ const qualityCountEl = document.getElementById("qualityCount");
23
+ const liveRegion = document.getElementById("panelStatus");
24
+
25
+ let sourceStatus = "";
26
+ let pendingFocus = null;
27
+
28
+ function el(tag, className, text) {
29
+ const node = document.createElement(tag);
30
+ if (className) node.className = className;
31
+ if (text != null) node.textContent = text;
32
+ return node;
33
+ }
34
+
35
+ function clear(node) {
36
+ if (node) node.replaceChildren();
37
+ }
38
+
39
+ function metricValue(value) {
40
+ if (value == null || value === "") return "0";
41
+ if (typeof value === "number") return String(value);
42
+ return String(value);
43
+ }
44
+
45
+ function ratioPercent(value) {
46
+ return typeof value === "number" ? Math.round(value * 100) + "%" : "0%";
47
+ }
48
+
49
+ function qualityMetric(label, value, tone) {
50
+ const item = el("div", "quality-metric" + (tone ? " " + tone : ""));
51
+ item.append(el("span", "quality-label", label), el("strong", "", metricValue(value)));
52
+ return item;
53
+ }
54
+
55
+ function qualitySignal(label, value, tone) {
56
+ const row = el("div", "quality-signal" + (tone ? " " + tone : ""));
57
+ row.append(el("span", "quality-label", label), el("span", "quality-value", metricValue(value)));
58
+ return row;
59
+ }
60
+
61
+ function countPairs(counts, limit) {
62
+ if (!counts || typeof counts !== "object") return [];
63
+ return Object.keys(counts)
64
+ .map(key => [key, counts[key]])
65
+ .filter(([, value]) => Number(value) > 0)
66
+ .sort((a, b) => Number(b[1]) - Number(a[1]) || String(a[0]).localeCompare(String(b[0])))
67
+ .slice(0, limit);
68
+ }
69
+
70
+ function renderQuality() {
71
+ if (!qualityPanel || !qualityBody) return;
72
+ clear(qualityBody);
73
+ if (!quality || typeof quality !== "object") {
74
+ qualityPanel.hidden = true;
75
+ return;
76
+ }
77
+ qualityPanel.hidden = false;
78
+ const files = quality.files || {};
79
+ const flowQuality = quality.flows || {};
80
+ const calls = quality.calls || {};
81
+ const labels = quality.labels || {};
82
+ const source = quality.source_locations || {};
83
+ const graph = quality.graph || {};
84
+ const languagesQuality = quality.languages || {};
85
+ const skipped = (files.skipped && typeof files.skipped === "object") ? files.skipped : { total: 0 };
86
+ const parseErrors = (files.parse_errors && typeof files.parse_errors === "object")
87
+ ? files.parse_errors
88
+ : { total: 0 };
89
+
90
+ if (qualityCountEl) qualityCountEl.textContent = ratioPercent(source.coverage);
91
+
92
+ const metrics = el("div", "quality-metrics");
93
+ metrics.append(
94
+ qualityMetric("Files", files.total),
95
+ qualityMetric("Flows", flowQuality.total),
96
+ qualityMetric("Entrypoints", flowQuality.entrypoints),
97
+ qualityMetric("Source", ratioPercent(source.coverage))
98
+ );
99
+ qualityBody.appendChild(metrics);
100
+
101
+ const signals = el("div", "quality-signals");
102
+ const unresolved = Number(calls.unresolved || 0);
103
+ const ambiguous = Number(calls.ambiguous || 0);
104
+ const generic = Number(labels.generic_nodes || 0);
105
+ const skippedTotal = Number(skipped.total || 0);
106
+ const parseWarnings = Number(parseErrors.total || 0);
107
+ const huge = Array.isArray(flowQuality.huge) ? flowQuality.huge.length : 0;
108
+ const languageAttention = Array.isArray(languagesQuality.attention)
109
+ ? languagesQuality.attention.length
110
+ : 0;
111
+ signals.append(
112
+ qualitySignal("Call resolution", ratioPercent(calls.resolution_rate), unresolved || ambiguous ? "attention" : ""),
113
+ qualitySignal("Skipped files", skippedTotal, skippedTotal ? "attention" : ""),
114
+ qualitySignal("Parse warnings", parseWarnings, parseWarnings ? "attention" : ""),
115
+ qualitySignal("Unresolved calls", unresolved, unresolved ? "attention" : ""),
116
+ qualitySignal("Ambiguous calls", ambiguous, ambiguous ? "attention" : ""),
117
+ qualitySignal("Generic labels", generic + " · " + ratioPercent(labels.generic_ratio), generic ? "attention" : ""),
118
+ qualitySignal("Language attention", languageAttention, languageAttention ? "attention" : ""),
119
+ qualitySignal("Graph density", graph.edge_to_node_ratio, graph.dense_graph_warning ? "attention" : "")
120
+ );
121
+ if (huge) signals.append(qualitySignal("Huge flows", huge, "attention"));
122
+ qualityBody.appendChild(signals);
123
+
124
+ const languages = countPairs(flowQuality.by_language || files.by_language, 8);
125
+ if (languages.length) {
126
+ const chips = el("div", "quality-chips");
127
+ languages.forEach(([language, count]) => {
128
+ chips.appendChild(el("span", "quality-chip", language + " " + count));
129
+ });
130
+ qualityBody.appendChild(chips);
131
+ }
132
+ }
133
+
134
+ function flushAnnounce() {
135
+ if (liveRegion) liveRegion.textContent = sourceStatus;
136
+ }
137
+
138
+ function afterCascade(fn) {
139
+ if (typeof queueMicrotask === "function") queueMicrotask(fn);
140
+ else if (typeof setTimeout === "function") setTimeout(fn, 0);
141
+ else fn();
142
+ }
143
+
144
+ function afterFlowOpen(fn) {
145
+ afterCascade(() => {
146
+ if (typeof setTimeout === "function") setTimeout(fn, 0);
147
+ else fn();
148
+ });
149
+ }
150
+
151
+ function cssAttr(value) {
152
+ return String(value).replace(/(["\\])/g, "\\$1");
153
+ }
154
+
155
+ function restorePendingFocus() {
156
+ if (!pendingFocus || !sourceBody) return;
157
+ const target = sourceBody.querySelector('.code-line[data-line="' + cssAttr(pendingFocus.id) + '"]');
158
+ if (target && typeof target.focus === "function") target.focus();
159
+ pendingFocus = null;
160
+ }
161
+
162
+ function relevantFlowIds(sel) {
163
+ if (sel.flowId && byId.has(sel.flowId)) return new Set([sel.flowId]);
164
+ if (sel.path) {
165
+ const prefix = sel.path;
166
+ const ids = new Set();
167
+ flows.forEach(flow => {
168
+ const path = (flow.location && flow.location.path) || "";
169
+ if (path === prefix || path.startsWith(prefix + "/")) ids.add(flow.id);
170
+ });
171
+ return ids;
172
+ }
173
+ if (sel.scope && Object.prototype.hasOwnProperty.call(scopeFlows, sel.scope)) {
174
+ return new Set(scopeFlows[sel.scope] || []);
175
+ }
176
+ return null;
177
+ }
178
+
179
+ function sourceFlowFor(sel) {
180
+ if (sel.flowId && byId.has(sel.flowId)) return byId.get(sel.flowId);
181
+ if (!sel.path) return null;
182
+ const ids = relevantFlowIds(sel);
183
+ if (!ids || !ids.size) return null;
184
+ let best = null;
185
+ ids.forEach(id => {
186
+ const flow = byId.get(id);
187
+ if (!flow || !flow.location) return;
188
+ if (
189
+ !best ||
190
+ flow.location.path < best.location.path ||
191
+ (flow.location.path === best.location.path &&
192
+ flow.location.start_line < best.location.start_line)
193
+ ) {
194
+ best = flow;
195
+ }
196
+ });
197
+ return best;
198
+ }
199
+
200
+ function resolveFlowSource(flow) {
201
+ const ref = flow && flow.source;
202
+ if (!ref || !ref.path) return null;
203
+ const file = sourceFiles[ref.path];
204
+ if (!file || !Array.isArray(file.lines)) return null;
205
+ const from = ref.start_line;
206
+ if (from == null) return null;
207
+ const to = ref.end_line != null ? ref.end_line : from;
208
+ const offset = from - file.start_line;
209
+ if (offset < 0) return null;
210
+ const lines = file.lines.slice(offset, offset + (to - from + 1));
211
+ if (!lines.length) return null;
212
+ return {
213
+ elided: !!ref.elided,
214
+ lines: lines,
215
+ start_line: from,
216
+ total: to - from + 1,
217
+ };
218
+ }
219
+
220
+ function buildLineToNode(flow) {
221
+ const loc = flow.location || {};
222
+ const flowSpan =
223
+ loc.start_line != null && loc.end_line != null
224
+ ? loc.end_line - loc.start_line
225
+ : null;
226
+ const lineToNode = new Map();
227
+ (flow.nodes || []).forEach(node => {
228
+ const nloc = node.location || {};
229
+ const from = nloc.start_line;
230
+ if (from == null) return;
231
+ const to = nloc.end_line != null ? nloc.end_line : from;
232
+ const span = to - from;
233
+ if (flowSpan != null && span >= flowSpan) return;
234
+ for (let line = from; line <= to; line++) {
235
+ const current = lineToNode.get(line);
236
+ if (!current || span < current.span) lineToNode.set(line, { id: node.id, span: span });
237
+ }
238
+ });
239
+ return lineToNode;
240
+ }
241
+
242
+ function selectedSourceRange(flow, sel) {
243
+ if (sel.nodeId) {
244
+ const node = codeDebrief.nodeById ? codeDebrief.nodeById(flow.id, sel.nodeId) : null;
245
+ if (node && node.location) {
246
+ const from = node.location.start_line;
247
+ return { from: from, to: node.location.end_line != null ? node.location.end_line : from };
248
+ }
249
+ }
250
+ if (
251
+ sel.line != null &&
252
+ (!sel.path || !flow.location || !flow.location.path || sel.path === flow.location.path)
253
+ ) {
254
+ return { from: sel.line, to: sel.endLine != null ? sel.endLine : sel.line };
255
+ }
256
+ if (sel.flowId === flow.id && flow.location && flow.location.start_line != null) {
257
+ const from = flow.location.start_line;
258
+ return { from: from, to: flow.location.end_line != null ? flow.location.end_line : from };
259
+ }
260
+ return null;
261
+ }
262
+
263
+ function renderSource(sel) {
264
+ if (!sourceBody) return;
265
+ const flow = sourceFlowFor(sel || {});
266
+ clear(sourceBody);
267
+ if (sourceFileEl) sourceFileEl.textContent = "";
268
+ if (sourcePanel) sourcePanel.hidden = !flow;
269
+
270
+ if (!flow) {
271
+ sourceBody.appendChild(el("p", "panel-empty", "Select a flow or node to view its source."));
272
+ sourceStatus = "no source selected";
273
+ return;
274
+ }
275
+
276
+ const snippet = resolveFlowSource(flow);
277
+ const location = flow.location || {};
278
+ const fileLabel = location.path
279
+ ? location.path + (location.start_line ? ":" + location.start_line : "")
280
+ : flow.name || flow.id;
281
+ if (sourceFileEl) sourceFileEl.textContent = fileLabel;
282
+
283
+ if (!snippet) {
284
+ sourceBody.appendChild(el("p", "panel-empty", "Source lines are not embedded for this flow."));
285
+ sourceStatus = "source unavailable";
286
+ return;
287
+ }
288
+
289
+ sourceStatus = "source " + fileLabel;
290
+ const pre = el("pre", "code-lines");
291
+ const lineToNode = buildLineToNode(flow);
292
+ const selected = selectedSourceRange(flow, sel || {});
293
+ let firstHighlight = null;
294
+
295
+ snippet.lines.forEach((text, index) => {
296
+ const lineNo = snippet.start_line + index;
297
+ const lineEl = el("div", "code-line");
298
+ lineEl.setAttribute("data-line", String(lineNo));
299
+ const node = lineToNode.get(lineNo);
300
+ const nodeId = node && node.id;
301
+ if (nodeId) {
302
+ lineEl.setAttribute("data-node-id", nodeId);
303
+ lineEl.tabIndex = 0;
304
+ lineEl.setAttribute("role", "button");
305
+ lineEl.setAttribute("aria-label", "Select source line " + lineNo);
306
+ }
307
+ if (selected && lineNo >= selected.from && lineNo <= selected.to) {
308
+ lineEl.classList.add("selected");
309
+ if (!firstHighlight) firstHighlight = lineEl;
310
+ }
311
+
312
+ const gutter = el("span", "code-gutter", String(lineNo));
313
+ gutter.setAttribute("aria-hidden", "true");
314
+ lineEl.append(gutter, el("span", "code-text", text.length ? text : " "));
315
+
316
+ if (nodeId) {
317
+ const activateLine = () => {
318
+ pendingFocus = { id: lineNo };
319
+ const publishLineSelection = () => codeDebrief.select({
320
+ endLine: lineNo,
321
+ flowId: flow.id,
322
+ line: lineNo,
323
+ nodeId: nodeId,
324
+ path: flow.location.path,
325
+ });
326
+ if (codeDebrief.selectFlow) {
327
+ codeDebrief.selectFlow(flow.id);
328
+ afterFlowOpen(publishLineSelection);
329
+ } else {
330
+ publishLineSelection();
331
+ }
332
+ };
333
+ lineEl.addEventListener("click", activateLine);
334
+ lineEl.addEventListener("keydown", event => {
335
+ if (event.key === "Enter" || event.key === " ") {
336
+ event.preventDefault();
337
+ activateLine();
338
+ }
339
+ });
340
+ }
341
+ pre.appendChild(lineEl);
342
+ });
343
+ sourceBody.appendChild(pre);
344
+
345
+ if (snippet.elided) {
346
+ const dropped = snippet.total - snippet.lines.length;
347
+ sourceBody.appendChild(
348
+ el("p", "code-elided", dropped + " more line" + (dropped === 1 ? "" : "s") + " not shown")
349
+ );
350
+ }
351
+ if (firstHighlight && typeof firstHighlight.scrollIntoView === "function") {
352
+ firstHighlight.scrollIntoView({ block: "nearest" });
353
+ }
354
+ }
355
+
356
+ renderQuality();
357
+
358
+ function onSelection(sel) {
359
+ renderSource(sel || {});
360
+ flushAnnounce();
361
+ if (pendingFocus) afterCascade(restorePendingFocus);
362
+ }
363
+ if (codeDebrief.onSelection) codeDebrief.onSelection(onSelection);
364
+ onSelection(codeDebrief.selection || {});
365
+
366
+ const fsToggle = document.getElementById("fullscreenToggle");
367
+ const mainEl = document.querySelector("main");
368
+ const body = document.body;
369
+ const fsApiSupported = !!(
370
+ mainEl &&
371
+ (mainEl.requestFullscreen ||
372
+ mainEl.webkitRequestFullscreen ||
373
+ mainEl.msRequestFullscreen)
374
+ );
375
+
376
+ function fsElement() {
377
+ return document.fullscreenElement || document.webkitFullscreenElement || null;
378
+ }
379
+ let fallbackActive = false;
380
+ function isMaximized() {
381
+ return fallbackActive || fsElement() === mainEl;
382
+ }
383
+ codeDebrief.fullscreenFallbackActive = () => fallbackActive;
384
+
385
+ function reflectFsState() {
386
+ const on = isMaximized();
387
+ if (fsToggle) {
388
+ fsToggle.setAttribute("aria-pressed", on ? "true" : "false");
389
+ fsToggle.title = on ? "Exit full screen (Esc)" : "Full screen (Esc to exit)";
390
+ }
391
+ if (on) body.setAttribute("data-fullscreen", "");
392
+ else body.removeAttribute("data-fullscreen");
393
+ if (codeDebrief.updateViewBox) codeDebrief.updateViewBox();
394
+ }
395
+
396
+ function requestFs() {
397
+ if (!mainEl) return Promise.resolve();
398
+ const req =
399
+ mainEl.requestFullscreen ||
400
+ mainEl.webkitRequestFullscreen ||
401
+ mainEl.msRequestFullscreen;
402
+ try {
403
+ const result = req.call(mainEl);
404
+ return result && typeof result.then === "function" ? result : Promise.resolve();
405
+ } catch (_) {
406
+ return Promise.reject();
407
+ }
408
+ }
409
+
410
+ function exitFs() {
411
+ const exit =
412
+ document.exitFullscreen ||
413
+ document.webkitExitFullscreen ||
414
+ document.msExitFullscreen;
415
+ if (exit) {
416
+ try {
417
+ const result = exit.call(document);
418
+ return result && typeof result.then === "function" ? result : Promise.resolve();
419
+ } catch (_) {}
420
+ }
421
+ return Promise.resolve();
422
+ }
423
+
424
+ function enterMaximize() {
425
+ if (fsApiSupported) {
426
+ requestFs().then(reflectFsState, () => {
427
+ fallbackActive = true;
428
+ reflectFsState();
429
+ });
430
+ } else {
431
+ fallbackActive = true;
432
+ reflectFsState();
433
+ }
434
+ }
435
+
436
+ function exitMaximize() {
437
+ fallbackActive = false;
438
+ if (fsElement() === mainEl) {
439
+ exitFs().then(reflectFsState, reflectFsState);
440
+ } else {
441
+ reflectFsState();
442
+ }
443
+ }
444
+
445
+ function toggleFullscreen() {
446
+ if (isMaximized()) exitMaximize();
447
+ else enterMaximize();
448
+ }
449
+
450
+ codeDebrief.toggleFullscreen = toggleFullscreen;
451
+
452
+ if (fsToggle) fsToggle.addEventListener("click", toggleFullscreen);
453
+ ["fullscreenchange", "webkitfullscreenchange"].forEach(eventName => {
454
+ document.addEventListener(eventName, reflectFsState);
455
+ });
456
+ document.addEventListener("keydown", event => {
457
+ if (event.key !== "Escape") return;
458
+ const target = event.target;
459
+ if (target && /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName || "")) return;
460
+ if (fallbackActive) exitMaximize();
461
+ });
462
+ })();