codedocent 1.0.2__tar.gz → 1.0.3__tar.gz
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.
- {codedocent-1.0.2/codedocent.egg-info → codedocent-1.0.3}/PKG-INFO +21 -3
- codedocent-1.0.2/PKG-INFO → codedocent-1.0.3/README.md +10 -10
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent/graph.py +45 -5
- codedocent-1.0.2/README.md → codedocent-1.0.3/codedocent.egg-info/PKG-INFO +28 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/pyproject.toml +1 -1
- {codedocent-1.0.2 → codedocent-1.0.3}/tests/test_graph.py +100 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/LICENSE +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent/__init__.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent/__main__.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent/analyzer.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent/cli.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent/cloud_ai.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent/editor.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent/gui.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent/ollama_utils.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent/parser.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent/quality.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent/renderer.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent/scanner.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent/server.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent/templates/architecture.html +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent/templates/base.html +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent/templates/interactive.html +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent.egg-info/SOURCES.txt +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent.egg-info/dependency_links.txt +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent.egg-info/entry_points.txt +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent.egg-info/requires.txt +4 -4
- {codedocent-1.0.2 → codedocent-1.0.3}/codedocent.egg-info/top_level.txt +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/setup.cfg +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/tests/test_analyzer.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/tests/test_cli.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/tests/test_cloud_ai.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/tests/test_editor.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/tests/test_gui.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/tests/test_parser.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/tests/test_renderer.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/tests/test_scanner.py +0 -0
- {codedocent-1.0.2 → codedocent-1.0.3}/tests/test_server.py +0 -0
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: codedocent
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
4
4
|
Summary: Code visualization for non-programmers
|
|
5
5
|
License: MIT
|
|
6
6
|
Requires-Python: >=3.10
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
|
-
Provides-Extra: dev
|
|
9
8
|
License-File: LICENSE
|
|
9
|
+
Requires-Dist: tree-sitter>=0.23
|
|
10
|
+
Requires-Dist: tree-sitter-language-pack>=0.13
|
|
11
|
+
Requires-Dist: radon>=6.0
|
|
12
|
+
Requires-Dist: pathspec>=0.11
|
|
13
|
+
Requires-Dist: jinja2>=3.1
|
|
14
|
+
Requires-Dist: ollama>=0.4
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
17
|
+
Dynamic: license-file
|
|
10
18
|
|
|
11
19
|
# codedocent
|
|
12
20
|
|
|
@@ -87,6 +95,16 @@ codedocent /path/to/code --cloud groq # use Groq
|
|
|
87
95
|
codedocent /path/to/code --cloud custom --endpoint https://my-llm/v1/chat/completions
|
|
88
96
|
```
|
|
89
97
|
|
|
98
|
+
## GUI launcher
|
|
99
|
+
|
|
100
|
+

|
|
101
|
+
|
|
102
|
+
If you prefer clicking over typing, `codedocent --gui` opens a graphical launcher. Pick a folder, choose your AI backend (cloud or local Ollama), select a model, and choose a mode — Interactive, Full export, Text tree, or Architecture. Hit Go.
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
codedocent --gui
|
|
106
|
+
```
|
|
107
|
+
|
|
90
108
|
## How it works
|
|
91
109
|
|
|
92
110
|
Parses code structure with tree-sitter, scores quality with static analysis, and sends individual blocks to a cloud AI provider or local Ollama model for plain English summaries and pseudocode. Interactive mode analyzes on click — typically 1-2 seconds per block. Full mode analyzes everything upfront into a self-contained HTML file you can share. Architecture mode builds a dependency graph from import statements and renders it as a zoomable D3 visualization.
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: codedocent
|
|
3
|
-
Version: 1.0.2
|
|
4
|
-
Summary: Code visualization for non-programmers
|
|
5
|
-
License: MIT
|
|
6
|
-
Requires-Python: >=3.10
|
|
7
|
-
Description-Content-Type: text/markdown
|
|
8
|
-
Provides-Extra: dev
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
|
|
11
1
|
# codedocent
|
|
12
2
|
|
|
13
3
|
**Code visualization for non-programmers.**
|
|
@@ -87,6 +77,16 @@ codedocent /path/to/code --cloud groq # use Groq
|
|
|
87
77
|
codedocent /path/to/code --cloud custom --endpoint https://my-llm/v1/chat/completions
|
|
88
78
|
```
|
|
89
79
|
|
|
80
|
+
## GUI launcher
|
|
81
|
+
|
|
82
|
+

|
|
83
|
+
|
|
84
|
+
If you prefer clicking over typing, `codedocent --gui` opens a graphical launcher. Pick a folder, choose your AI backend (cloud or local Ollama), select a model, and choose a mode — Interactive, Full export, Text tree, or Architecture. Hit Go.
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
codedocent --gui
|
|
88
|
+
```
|
|
89
|
+
|
|
90
90
|
## How it works
|
|
91
91
|
|
|
92
92
|
Parses code structure with tree-sitter, scores quality with static analysis, and sends individual blocks to a cloud AI provider or local Ollama model for plain English summaries and pseudocode. Interactive mode analyzes on click — typically 1-2 seconds per block. Full mode analyzes everything upfront into a self-contained HTML file you can share. Architecture mode builds a dependency graph from import statements and renders it as a zoomable D3 visualization.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import ast
|
|
5
6
|
import os
|
|
6
7
|
import sys
|
|
7
8
|
from dataclasses import dataclass, field
|
|
@@ -389,9 +390,11 @@ def get_module_graph(root: CodeNode, project_root: str) -> dict:
|
|
|
389
390
|
imp, fnode.filepath, project_files, project_modules_top,
|
|
390
391
|
)
|
|
391
392
|
if resolved is None:
|
|
392
|
-
# External dependency
|
|
393
|
+
# External dependency (skip stdlib)
|
|
393
394
|
if not imp.is_relative:
|
|
394
|
-
|
|
395
|
+
top = imp.module.split(".")[0]
|
|
396
|
+
if top not in _get_stdlib_modules():
|
|
397
|
+
external_deps.add(top)
|
|
395
398
|
continue
|
|
396
399
|
if resolved != fnode.filepath:
|
|
397
400
|
key = (fnode.filepath, resolved)
|
|
@@ -582,6 +585,30 @@ def get_file_dependencies(
|
|
|
582
585
|
}
|
|
583
586
|
|
|
584
587
|
|
|
588
|
+
# ---------------------------------------------------------------------------
|
|
589
|
+
# Docstring extraction
|
|
590
|
+
# ---------------------------------------------------------------------------
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def _extract_module_docstring(source: str) -> str:
|
|
594
|
+
"""Extract the module-level docstring from Python source.
|
|
595
|
+
|
|
596
|
+
Returns the first line of the docstring, truncated to 80 characters.
|
|
597
|
+
Returns an empty string if no docstring is found.
|
|
598
|
+
"""
|
|
599
|
+
try:
|
|
600
|
+
tree = ast.parse(source)
|
|
601
|
+
docstring = ast.get_docstring(tree)
|
|
602
|
+
if docstring:
|
|
603
|
+
first_line = docstring.split("\n")[0].strip()
|
|
604
|
+
if len(first_line) > 80:
|
|
605
|
+
return first_line[:77] + "..."
|
|
606
|
+
return first_line
|
|
607
|
+
except SyntaxError:
|
|
608
|
+
pass
|
|
609
|
+
return ""
|
|
610
|
+
|
|
611
|
+
|
|
585
612
|
# ---------------------------------------------------------------------------
|
|
586
613
|
# Context export
|
|
587
614
|
# ---------------------------------------------------------------------------
|
|
@@ -637,20 +664,33 @@ def export_module_md(
|
|
|
637
664
|
if graph is None:
|
|
638
665
|
return None
|
|
639
666
|
|
|
667
|
+
# Build filepath -> docstring lookup for Purpose column
|
|
668
|
+
docstrings: dict[str, str] = {}
|
|
669
|
+
for node in graph["nodes"]:
|
|
670
|
+
if node["node_type"] == "external":
|
|
671
|
+
continue
|
|
672
|
+
fnode = _find_file_node(root, node["id"])
|
|
673
|
+
if fnode and fnode.source and fnode.language == "python":
|
|
674
|
+
docstrings[node["id"]] = _extract_module_docstring(fnode.source)
|
|
675
|
+
|
|
640
676
|
mod_name = os.path.basename(module_path)
|
|
641
677
|
lines = [f"# Module: {mod_name}\n"]
|
|
642
678
|
|
|
643
679
|
# Files table
|
|
644
680
|
lines.append("## Files\n")
|
|
645
|
-
lines.append("| File | Lines | Quality |")
|
|
646
|
-
lines.append("
|
|
681
|
+
lines.append("| File | Lines | Quality | Purpose |")
|
|
682
|
+
lines.append("|------|-------|---------|---------|")
|
|
647
683
|
for node in graph["nodes"]:
|
|
648
684
|
if node["node_type"] == "external":
|
|
649
685
|
continue
|
|
650
686
|
quality_label = {"clean": "OK", "complex": "Complex", "warning": "Warning"}.get(
|
|
651
687
|
node["quality"], "OK"
|
|
652
688
|
)
|
|
653
|
-
|
|
689
|
+
purpose = docstrings.get(node["id"], "") or "\u2014"
|
|
690
|
+
lines.append(
|
|
691
|
+
f"| {node['name']} | {node['line_count']} "
|
|
692
|
+
f"| {quality_label} | {purpose} |"
|
|
693
|
+
)
|
|
654
694
|
|
|
655
695
|
# Internal dependencies
|
|
656
696
|
internal_edges = [
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codedocent
|
|
3
|
+
Version: 1.0.3
|
|
4
|
+
Summary: Code visualization for non-programmers
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: tree-sitter>=0.23
|
|
10
|
+
Requires-Dist: tree-sitter-language-pack>=0.13
|
|
11
|
+
Requires-Dist: radon>=6.0
|
|
12
|
+
Requires-Dist: pathspec>=0.11
|
|
13
|
+
Requires-Dist: jinja2>=3.1
|
|
14
|
+
Requires-Dist: ollama>=0.4
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
1
19
|
# codedocent
|
|
2
20
|
|
|
3
21
|
**Code visualization for non-programmers.**
|
|
@@ -77,6 +95,16 @@ codedocent /path/to/code --cloud groq # use Groq
|
|
|
77
95
|
codedocent /path/to/code --cloud custom --endpoint https://my-llm/v1/chat/completions
|
|
78
96
|
```
|
|
79
97
|
|
|
98
|
+
## GUI launcher
|
|
99
|
+
|
|
100
|
+

|
|
101
|
+
|
|
102
|
+
If you prefer clicking over typing, `codedocent --gui` opens a graphical launcher. Pick a folder, choose your AI backend (cloud or local Ollama), select a model, and choose a mode — Interactive, Full export, Text tree, or Architecture. Hit Go.
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
codedocent --gui
|
|
106
|
+
```
|
|
107
|
+
|
|
80
108
|
## How it works
|
|
81
109
|
|
|
82
110
|
Parses code structure with tree-sitter, scores quality with static analysis, and sends individual blocks to a cloud AI provider or local Ollama model for plain English summaries and pseudocode. Interactive mode analyzes on click — typically 1-2 seconds per block. Full mode analyzes everything upfront into a self-contained HTML file you can share. Architecture mode builds a dependency graph from import statements and renders it as a zoomable D3 visualization.
|
|
@@ -9,6 +9,7 @@ import pytest
|
|
|
9
9
|
from codedocent.graph import (
|
|
10
10
|
ImportInfo,
|
|
11
11
|
_extract_full_imports,
|
|
12
|
+
_extract_module_docstring,
|
|
12
13
|
_resolve_import,
|
|
13
14
|
_find_modules,
|
|
14
15
|
_aggregate_quality,
|
|
@@ -408,6 +409,14 @@ class TestGetModuleGraph:
|
|
|
408
409
|
graph = get_module_graph(root, "/tmp/project")
|
|
409
410
|
assert "pytest" in graph["external"]
|
|
410
411
|
|
|
412
|
+
def test_stdlib_excluded_from_external_deps(self):
|
|
413
|
+
"""Stdlib modules should not appear in external dependencies."""
|
|
414
|
+
root = _make_multi_module_tree()
|
|
415
|
+
graph = get_module_graph(root, "/tmp/project")
|
|
416
|
+
stdlib_names = {"os", "sys", "json", "pathlib", "collections", "re"}
|
|
417
|
+
for dep in graph["external"]:
|
|
418
|
+
assert dep not in stdlib_names, f"stdlib module '{dep}' in external deps"
|
|
419
|
+
|
|
411
420
|
def test_module_stats(self):
|
|
412
421
|
root = _make_multi_module_tree()
|
|
413
422
|
graph = get_module_graph(root, "/tmp/project")
|
|
@@ -485,6 +494,45 @@ class TestGetFileGraph:
|
|
|
485
494
|
assert gn["node_id"] in lookup
|
|
486
495
|
|
|
487
496
|
|
|
497
|
+
# ---------------------------------------------------------------------------
|
|
498
|
+
# Docstring extraction tests
|
|
499
|
+
# ---------------------------------------------------------------------------
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
class TestExtractModuleDocstring:
|
|
503
|
+
"""Tests for _extract_module_docstring."""
|
|
504
|
+
|
|
505
|
+
def test_extracts_triple_double_quote_docstring(self):
|
|
506
|
+
source = '"""Parse source files into a tree."""\nimport os\n'
|
|
507
|
+
assert _extract_module_docstring(source) == "Parse source files into a tree."
|
|
508
|
+
|
|
509
|
+
def test_extracts_triple_single_quote_docstring(self):
|
|
510
|
+
source = "'''Helper utilities for scanning.'''\nimport sys\n"
|
|
511
|
+
assert _extract_module_docstring(source) == "Helper utilities for scanning."
|
|
512
|
+
|
|
513
|
+
def test_returns_empty_for_no_docstring(self):
|
|
514
|
+
source = "import os\nx = 1\n"
|
|
515
|
+
assert _extract_module_docstring(source) == ""
|
|
516
|
+
|
|
517
|
+
def test_returns_first_line_of_multiline(self):
|
|
518
|
+
source = '"""First line.\n\nMore detail here.\n"""\nimport os\n'
|
|
519
|
+
assert _extract_module_docstring(source) == "First line."
|
|
520
|
+
|
|
521
|
+
def test_truncates_long_docstring(self):
|
|
522
|
+
long_text = "A" * 100
|
|
523
|
+
source = f'"""{long_text}"""\n'
|
|
524
|
+
result = _extract_module_docstring(source)
|
|
525
|
+
assert len(result) <= 80
|
|
526
|
+
assert result.endswith("...")
|
|
527
|
+
|
|
528
|
+
def test_returns_empty_for_syntax_error(self):
|
|
529
|
+
source = "def foo(:\n"
|
|
530
|
+
assert _extract_module_docstring(source) == ""
|
|
531
|
+
|
|
532
|
+
def test_returns_empty_for_empty_source(self):
|
|
533
|
+
assert _extract_module_docstring("") == ""
|
|
534
|
+
|
|
535
|
+
|
|
488
536
|
# ---------------------------------------------------------------------------
|
|
489
537
|
# Context export tests
|
|
490
538
|
# ---------------------------------------------------------------------------
|
|
@@ -511,6 +559,16 @@ class TestExportArchitectureMd:
|
|
|
511
559
|
md = export_architecture_md(root, "/tmp/project")
|
|
512
560
|
assert "pytest" in md
|
|
513
561
|
|
|
562
|
+
def test_excludes_stdlib_from_external_deps(self):
|
|
563
|
+
"""Stdlib modules like os, sys should not appear in External Dependencies."""
|
|
564
|
+
root = _make_multi_module_tree()
|
|
565
|
+
md = export_architecture_md(root, "/tmp/project")
|
|
566
|
+
# The test tree includes files that import os/sys but those are stdlib
|
|
567
|
+
for line in md.splitlines():
|
|
568
|
+
if line.startswith("- "):
|
|
569
|
+
dep = line[2:].strip()
|
|
570
|
+
assert dep not in ("os", "sys", "json", "pathlib")
|
|
571
|
+
|
|
514
572
|
|
|
515
573
|
class TestExportModuleMd:
|
|
516
574
|
"""Tests for export_module_md."""
|
|
@@ -522,6 +580,12 @@ class TestExportModuleMd:
|
|
|
522
580
|
assert "# Module: codedocent" in md
|
|
523
581
|
assert "## Files" in md
|
|
524
582
|
|
|
583
|
+
def test_contains_purpose_column(self):
|
|
584
|
+
root = _make_multi_module_tree()
|
|
585
|
+
md = export_module_md(root, "/tmp/project", "codedocent")
|
|
586
|
+
assert md is not None
|
|
587
|
+
assert "| File | Lines | Quality | Purpose |" in md
|
|
588
|
+
|
|
525
589
|
def test_returns_none_for_invalid_module(self):
|
|
526
590
|
root = _make_multi_module_tree()
|
|
527
591
|
result = export_module_md(root, "/tmp/project", "nonexistent")
|
|
@@ -534,6 +598,42 @@ class TestExportModuleMd:
|
|
|
534
598
|
assert "parser.py" in md
|
|
535
599
|
assert "server.py" in md
|
|
536
600
|
|
|
601
|
+
def test_shows_docstring_in_purpose(self):
|
|
602
|
+
"""Files with module docstrings should show them in the Purpose column."""
|
|
603
|
+
init_node = _make_file_node(
|
|
604
|
+
name="__init__.py",
|
|
605
|
+
filepath="mypkg/__init__.py",
|
|
606
|
+
source='"""My package init."""\n',
|
|
607
|
+
node_id="init",
|
|
608
|
+
)
|
|
609
|
+
core_node = _make_file_node(
|
|
610
|
+
name="core.py",
|
|
611
|
+
filepath="mypkg/core.py",
|
|
612
|
+
source='"""Core business logic for the app."""\nimport os\nx = 1\n',
|
|
613
|
+
node_id="core",
|
|
614
|
+
)
|
|
615
|
+
nodc_node = _make_file_node(
|
|
616
|
+
name="utils.py",
|
|
617
|
+
filepath="mypkg/utils.py",
|
|
618
|
+
source="import os\nx = 1\n",
|
|
619
|
+
node_id="utils",
|
|
620
|
+
)
|
|
621
|
+
dir_node = _make_dir_node(
|
|
622
|
+
name="mypkg",
|
|
623
|
+
children=[init_node, core_node, nodc_node],
|
|
624
|
+
filepath="/tmp/project/mypkg",
|
|
625
|
+
)
|
|
626
|
+
root = _make_dir_node(
|
|
627
|
+
name="project",
|
|
628
|
+
children=[dir_node],
|
|
629
|
+
filepath="/tmp/project",
|
|
630
|
+
)
|
|
631
|
+
md = export_module_md(root, "/tmp/project", "mypkg")
|
|
632
|
+
assert md is not None
|
|
633
|
+
assert "Core business logic for the app" in md
|
|
634
|
+
# utils.py has no docstring -> em dash
|
|
635
|
+
assert "\u2014" in md
|
|
636
|
+
|
|
537
637
|
|
|
538
638
|
# ---------------------------------------------------------------------------
|
|
539
639
|
# File dependency lookup tests
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|