code2docs 3.0.26__tar.gz → 3.0.27__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.
- {code2docs-3.0.26 → code2docs-3.0.27}/PKG-INFO +6 -6
- {code2docs-3.0.26 → code2docs-3.0.27}/README.md +5 -5
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/__init__.py +1 -1
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/analyzers/docstring_extractor.py +2 -4
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/analyzers/endpoint_detector.py +1 -2
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/analyzers/project_scanner.py +1 -2
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/cli.py +1 -1
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/examples/advanced_usage.py +0 -1
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/examples/quickstart.py +0 -2
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/formatters/markdown.py +1 -1
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/generators/api_changelog_gen.py +2 -2
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/generators/api_reference_gen.py +1 -1
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/generators/architecture_gen.py +1 -1
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/generators/changelog_gen.py +1 -1
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/generators/code2llm_gen.py +1 -1
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/generators/config_docs_gen.py +1 -2
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/generators/contributing_gen.py +1 -1
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/generators/depgraph_gen.py +1 -1
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/generators/examples_gen.py +33 -13
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/generators/getting_started_gen.py +1 -1
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/generators/org_readme_gen.py +1 -1
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/generators/readme_gen.py +34 -6
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/sync/differ.py +1 -1
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs.egg-info/PKG-INFO +6 -6
- {code2docs-3.0.26 → code2docs-3.0.27}/pyproject.toml +1 -1
- {code2docs-3.0.26 → code2docs-3.0.27}/tests/test_analyzers.py +2 -2
- {code2docs-3.0.26 → code2docs-3.0.27}/tests/test_config.py +1 -2
- {code2docs-3.0.26 → code2docs-3.0.27}/tests/test_formatters.py +0 -1
- {code2docs-3.0.26 → code2docs-3.0.27}/tests/test_generators.py +61 -13
- {code2docs-3.0.26 → code2docs-3.0.27}/tests/test_llm_helper.py +7 -8
- {code2docs-3.0.26 → code2docs-3.0.27}/tests/test_registry.py +0 -3
- {code2docs-3.0.26 → code2docs-3.0.27}/tests/test_sync.py +0 -1
- {code2docs-3.0.26 → code2docs-3.0.27}/LICENSE +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/__main__.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/analyzers/__init__.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/analyzers/dependency_scanner.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/base.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/config.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/formatters/__init__.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/formatters/badges.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/formatters/toc.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/generators/__init__.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/generators/_registry_adapters.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/generators/_source_links.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/generators/coverage_gen.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/generators/mkdocs_gen.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/generators/module_docs_gen.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/llm_helper.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/registry.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/sync/__init__.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/sync/updater.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/sync/watcher.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/templates/api_module.md.j2 +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/templates/architecture.md.j2 +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/templates/example_usage.py.j2 +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/templates/index.md.j2 +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/templates/module_doc.md.j2 +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs/templates/readme.md.j2 +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs.egg-info/SOURCES.txt +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs.egg-info/dependency_links.txt +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs.egg-info/entry_points.txt +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs.egg-info/requires.txt +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/code2docs.egg-info/top_level.txt +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/setup.cfg +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/tests/test_cli.py +0 -0
- {code2docs-3.0.26 → code2docs-3.0.27}/tests/test_code2docs.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code2docs
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.27
|
|
4
4
|
Summary: Auto-generate and sync project documentation from source code analysis
|
|
5
5
|
Author-email: Tom Sapletta <tom@sapletta.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -56,17 +56,17 @@ Dynamic: license-file
|
|
|
56
56
|
|
|
57
57
|
## AI Cost Tracking
|
|
58
58
|
|
|
59
|
-
    
|
|
60
60
|
  
|
|
61
61
|
|
|
62
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
62
|
+
- 🤖 **LLM usage:** $7.5000 (59 commits)
|
|
63
63
|
- 👤 **Human dev:** ~$1857 (18.6h @ $100/h, 30min dedup)
|
|
64
64
|
|
|
65
|
-
Generated on 2026-04-
|
|
65
|
+
Generated on 2026-04-09 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
66
66
|
|
|
67
67
|
---
|
|
68
68
|
|
|
69
|
-
  
|
|
70
70
|
|
|
71
71
|
> Auto-generate and sync project documentation from source code analysis.
|
|
72
72
|
|
|
@@ -206,7 +206,7 @@ code2docs can update only specific sections of an existing README using markers:
|
|
|
206
206
|
```markdown
|
|
207
207
|
<!-- code2docs:start --># code2docs
|
|
208
208
|
|
|
209
|
-
   
|
|
210
210
|
> **276** functions | **57** classes | **51** files | CC̄ = 3.8
|
|
211
211
|
|
|
212
212
|
> Auto-generated project documentation from source code analysis.
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
## AI Cost Tracking
|
|
5
5
|
|
|
6
|
-
    
|
|
7
7
|
  
|
|
8
8
|
|
|
9
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
9
|
+
- 🤖 **LLM usage:** $7.5000 (59 commits)
|
|
10
10
|
- 👤 **Human dev:** ~$1857 (18.6h @ $100/h, 30min dedup)
|
|
11
11
|
|
|
12
|
-
Generated on 2026-04-
|
|
12
|
+
Generated on 2026-04-09 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
  
|
|
17
17
|
|
|
18
18
|
> Auto-generate and sync project documentation from source code analysis.
|
|
19
19
|
|
|
@@ -153,7 +153,7 @@ code2docs can update only specific sections of an existing README using markers:
|
|
|
153
153
|
```markdown
|
|
154
154
|
<!-- code2docs:start --># code2docs
|
|
155
155
|
|
|
156
|
-
   
|
|
157
157
|
> **276** functions | **57** classes | **51** files | CC̄ = 3.8
|
|
158
158
|
|
|
159
159
|
> Auto-generated project documentation from source code analysis.
|
|
@@ -5,7 +5,7 @@ Uses code2llm's AnalysisResult to produce human-readable documentation:
|
|
|
5
5
|
README.md, API references, module docs, examples, and architecture diagrams.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = '3.0.
|
|
8
|
+
__version__ = '3.0.27'
|
|
9
9
|
__author__ = 'Tom Sapletta'
|
|
10
10
|
__all__ = ['Code2DocsConfig', 'generate_readme', 'generate_docs', 'analyze_and_document']
|
|
11
11
|
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
"""Extract and analyze docstrings from source code."""
|
|
2
2
|
|
|
3
|
-
import ast
|
|
4
3
|
from dataclasses import dataclass, field
|
|
5
|
-
from
|
|
6
|
-
from typing import Dict, List, Optional, Tuple
|
|
4
|
+
from typing import Dict, List, Optional
|
|
7
5
|
|
|
8
|
-
from code2llm.api import AnalysisResult
|
|
6
|
+
from code2llm.api import AnalysisResult
|
|
9
7
|
|
|
10
8
|
|
|
11
9
|
@dataclass
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
"""Detect web framework endpoints (Flask, FastAPI, Django) from AST analysis."""
|
|
2
2
|
|
|
3
|
-
import ast
|
|
4
3
|
import re
|
|
5
4
|
from dataclasses import dataclass, field
|
|
6
5
|
from pathlib import Path
|
|
7
|
-
from typing import
|
|
6
|
+
from typing import List, Optional
|
|
8
7
|
|
|
9
8
|
from code2llm.api import AnalysisResult, FunctionInfo
|
|
10
9
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"""Wrapper around code2llm's ProjectAnalyzer for documentation purposes."""
|
|
2
2
|
|
|
3
|
-
from pathlib import Path
|
|
4
3
|
from typing import Optional
|
|
5
4
|
|
|
6
|
-
from code2llm.api import Config,
|
|
5
|
+
from code2llm.api import Config, AnalysisResult, analyze
|
|
7
6
|
|
|
8
7
|
from ..config import Code2DocsConfig
|
|
9
8
|
|
|
@@ -32,7 +32,7 @@ def main() -> None:
|
|
|
32
32
|
@click.option('--output', '-o', default=None, help='Output directory for docs')
|
|
33
33
|
@click.option('--verbose', '-v', is_flag=True, help='Verbose output')
|
|
34
34
|
@click.option('--dry-run', is_flag=True, help='Show what would be generated without writing')
|
|
35
|
-
@click.option('--llm', 'llm_model', default=None, help='Enable LLM-assisted generation (e.g. openai/gpt-
|
|
35
|
+
@click.option('--llm', 'llm_model', default=None, help='Enable LLM-assisted generation (e.g. openai/gpt-5.4-mini, ollama/llama3)')
|
|
36
36
|
@click.option('--org-name', default=None, help='Organization name for org-mode README generation')
|
|
37
37
|
def generate(project_path, config_path, readme_only, sections, output, verbose, dry_run, llm_model, org_name) -> None:
|
|
38
38
|
"""Generate documentation (default command)."""
|
|
@@ -5,10 +5,8 @@ Minimal working examples for the most common use cases.
|
|
|
5
5
|
Run: python examples/quickstart.py
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from pathlib import Path
|
|
9
8
|
|
|
10
9
|
from code2docs import Code2DocsConfig
|
|
11
|
-
from code2docs import analyze_and_document
|
|
12
10
|
from code2docs import generate_docs
|
|
13
11
|
from code2docs import generate_readme
|
|
14
12
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""API changelog generator — diff function/class signatures between versions."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
from dataclasses import dataclass
|
|
4
|
+
from dataclasses import dataclass
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Dict, List, Optional
|
|
6
|
+
from typing import Dict, List, Optional
|
|
7
7
|
|
|
8
8
|
from code2llm.api import AnalysisResult
|
|
9
9
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Configuration documentation generator."""
|
|
2
2
|
|
|
3
3
|
from dataclasses import fields, MISSING
|
|
4
|
-
from typing import Any
|
|
5
4
|
|
|
6
5
|
from code2llm.api import AnalysisResult
|
|
7
6
|
|
|
@@ -55,7 +54,7 @@ class ConfigDocsGenerator:
|
|
|
55
54
|
"watch": "Enable file watcher for auto-resync",
|
|
56
55
|
"ignore": "Glob patterns to ignore during sync",
|
|
57
56
|
"enabled": "Enable LLM-assisted documentation generation",
|
|
58
|
-
"model": "LLM model identifier (litellm format, e.g. `openai/gpt-
|
|
57
|
+
"model": "LLM model identifier (litellm format, e.g. `openai/gpt-5.4-mini`, `ollama/llama3`)",
|
|
59
58
|
"api_key": "API key for the LLM provider (use `.env` or env var `CODE2DOCS_LLM_API_KEY`)",
|
|
60
59
|
"api_base": "Custom API base URL (for self-hosted or proxy endpoints)",
|
|
61
60
|
"max_tokens": "Maximum tokens per LLM call",
|
|
@@ -1,12 +1,32 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
3
2
|
PORT_4 = 4
|
|
4
3
|
CONSTANT_5 = 5
|
|
5
4
|
CONSTANT_50 = 50
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
|
|
7
|
+
PORT_4 = PORT_4
|
|
8
|
+
CONSTANT_5 = CONSTANT_5
|
|
9
|
+
CONSTANT_50 = CONSTANT_50
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
PORT_4 = PORT_4
|
|
13
|
+
CONSTANT_5 = CONSTANT_5
|
|
14
|
+
CONSTANT_50 = CONSTANT_50
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
PORT_4 = PORT_4
|
|
18
|
+
CONSTANT_5 = CONSTANT_5
|
|
19
|
+
CONSTANT_50 = CONSTANT_50
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
PORT_4 = PORT_4
|
|
24
|
+
CONSTANT_5 = CONSTANT_5
|
|
25
|
+
CONSTANT_50 = CONSTANT_50
|
|
26
|
+
|
|
27
|
+
PORT_4 = PORT_4
|
|
28
|
+
CONSTANT_5 = CONSTANT_5
|
|
29
|
+
CONSTANT_50 = CONSTANT_50
|
|
10
30
|
|
|
11
31
|
"""Auto-generate usage examples from public signatures and entry points."""
|
|
12
32
|
|
|
@@ -154,7 +174,7 @@ class ExamplesGenerator:
|
|
|
154
174
|
source = self.config.source or "./"
|
|
155
175
|
output = self.config.output or "./docs"
|
|
156
176
|
if config_cls:
|
|
157
|
-
lines.append('# ' + '=' *
|
|
177
|
+
lines.append('# ' + '=' * CONSTANT_50)
|
|
158
178
|
lines.append("# Example 1: Configuration")
|
|
159
179
|
lines.append('# ' + '=' * CONSTANT_50)
|
|
160
180
|
lines.append("")
|
|
@@ -168,7 +188,7 @@ class ExamplesGenerator:
|
|
|
168
188
|
|
|
169
189
|
# --- Example 2: Quick generate ---
|
|
170
190
|
lines.append("")
|
|
171
|
-
lines.append('# ' + '=' *
|
|
191
|
+
lines.append('# ' + '=' * CONSTANT_50)
|
|
172
192
|
lines.append("# Example 2: Generate documentation")
|
|
173
193
|
lines.append('# ' + '=' * CONSTANT_50)
|
|
174
194
|
lines.append("")
|
|
@@ -195,7 +215,7 @@ class ExamplesGenerator:
|
|
|
195
215
|
scanner_cls = self._find_class_by_name("ProjectScanner")
|
|
196
216
|
if scanner_cls:
|
|
197
217
|
lines.append("")
|
|
198
|
-
lines.append('# ' + '=' *
|
|
218
|
+
lines.append('# ' + '=' * CONSTANT_50)
|
|
199
219
|
lines.append("# Example 3: Analyze a project programmatically")
|
|
200
220
|
lines.append('# ' + '=' * CONSTANT_50)
|
|
201
221
|
lines.append("")
|
|
@@ -231,13 +251,13 @@ class ExamplesGenerator:
|
|
|
231
251
|
# --- Individual generators ---
|
|
232
252
|
gen_classes = self._find_generator_classes()
|
|
233
253
|
if gen_classes:
|
|
234
|
-
lines.append('# ' + '=' *
|
|
254
|
+
lines.append('# ' + '=' * CONSTANT_50)
|
|
235
255
|
lines.append("# Using individual generators")
|
|
236
256
|
lines.append('# ' + '=' * CONSTANT_50)
|
|
237
257
|
lines.append("")
|
|
238
258
|
lines.append(f"from {pkg} import Code2DocsConfig")
|
|
239
259
|
lines.append(f"from {pkg}.analyzers.project_scanner import ProjectScanner")
|
|
240
|
-
for cls in gen_classes[:
|
|
260
|
+
for cls in gen_classes[:PORT_4]:
|
|
241
261
|
mod = cls.module or pkg
|
|
242
262
|
if not mod.startswith(pkg):
|
|
243
263
|
mod = f"{pkg}.{mod}"
|
|
@@ -250,7 +270,7 @@ class ExamplesGenerator:
|
|
|
250
270
|
lines.append('result = scanner.analyze(f"./{project_name_adv}") if project_name_adv != "." else scanner.analyze("./")')
|
|
251
271
|
lines.append("")
|
|
252
272
|
|
|
253
|
-
for i, cls in enumerate(gen_classes[:
|
|
273
|
+
for i, cls in enumerate(gen_classes[:PORT_4], start=2):
|
|
254
274
|
gen_name = cls.name[0].lower() + cls.name[1:]
|
|
255
275
|
gen_name = gen_name.replace("Generator", "_gen")
|
|
256
276
|
lines.append(f"# Step {i}: Generate with {cls.name}")
|
|
@@ -280,7 +300,7 @@ class ExamplesGenerator:
|
|
|
280
300
|
fmt_funcs = [f for f in fmt_funcs if f]
|
|
281
301
|
if fmt_funcs:
|
|
282
302
|
lines.append("")
|
|
283
|
-
lines.append('# ' + '=' *
|
|
303
|
+
lines.append('# ' + '=' * CONSTANT_50)
|
|
284
304
|
lines.append("# Formatters")
|
|
285
305
|
lines.append('# ' + '=' * CONSTANT_50)
|
|
286
306
|
lines.append("")
|
|
@@ -315,7 +335,7 @@ class ExamplesGenerator:
|
|
|
315
335
|
differ_cls = self._find_class_by_name("Differ")
|
|
316
336
|
if differ_cls:
|
|
317
337
|
lines.append("")
|
|
318
|
-
lines.append('# ' + '=' *
|
|
338
|
+
lines.append('# ' + '=' * CONSTANT_50)
|
|
319
339
|
lines.append("# Sync — detect and apply changes")
|
|
320
340
|
lines.append('# ' + '=' * CONSTANT_50)
|
|
321
341
|
lines.append("")
|
|
@@ -406,7 +426,7 @@ class ExamplesGenerator:
|
|
|
406
426
|
"""Build a realistic argument string for a function call."""
|
|
407
427
|
args = [a for a in func.args if a not in ("self", "cls")]
|
|
408
428
|
parts = []
|
|
409
|
-
for arg in args[:
|
|
429
|
+
for arg in args[:CONSTANT_5]:
|
|
410
430
|
val = self._get_example_value(arg)
|
|
411
431
|
if not val or val == '"..."':
|
|
412
432
|
# Try type hint
|
|
@@ -5,6 +5,34 @@ CONSTANT_15 = 15
|
|
|
5
5
|
CONSTANT_20 = 20
|
|
6
6
|
CONSTANT_30 = 30
|
|
7
7
|
|
|
8
|
+
|
|
9
|
+
CONSTANT_3 = CONSTANT_3
|
|
10
|
+
CONSTANT_5 = CONSTANT_5
|
|
11
|
+
CONSTANT_15 = CONSTANT_15
|
|
12
|
+
CONSTANT_20 = CONSTANT_20
|
|
13
|
+
CONSTANT_30 = CONSTANT_30
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
CONSTANT_3 = CONSTANT_3
|
|
17
|
+
CONSTANT_5 = CONSTANT_5
|
|
18
|
+
CONSTANT_15 = CONSTANT_15
|
|
19
|
+
CONSTANT_20 = CONSTANT_20
|
|
20
|
+
CONSTANT_30 = CONSTANT_30
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
CONSTANT_3 = CONSTANT_3
|
|
24
|
+
CONSTANT_5 = CONSTANT_5
|
|
25
|
+
CONSTANT_15 = CONSTANT_15
|
|
26
|
+
CONSTANT_20 = CONSTANT_20
|
|
27
|
+
CONSTANT_30 = CONSTANT_30
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
CONSTANT_3 = CONSTANT_3
|
|
31
|
+
CONSTANT_5 = CONSTANT_5
|
|
32
|
+
CONSTANT_15 = CONSTANT_15
|
|
33
|
+
CONSTANT_20 = CONSTANT_20
|
|
34
|
+
CONSTANT_30 = CONSTANT_30
|
|
35
|
+
|
|
8
36
|
"""README.md generator from AnalysisResult."""
|
|
9
37
|
import re
|
|
10
38
|
from pathlib import Path
|
|
@@ -90,7 +118,7 @@ class ReadmeGenerator:
|
|
|
90
118
|
def _generate_description(self, project_name: str, entry_points: List[str]) -> str:
|
|
91
119
|
"""Generate project description: LLM if available, else package docstring."""
|
|
92
120
|
if self.llm.available:
|
|
93
|
-
modules_summary = ', '.join(sorted(self.result.modules.keys())[:
|
|
121
|
+
modules_summary = ', '.join(sorted(self.result.modules.keys())[:CONSTANT_15])
|
|
94
122
|
eps_str = ', '.join(entry_points[:10])
|
|
95
123
|
llm_desc = self.llm.generate_project_description(project_name, modules_summary, eps_str)
|
|
96
124
|
if llm_desc:
|
|
@@ -143,7 +171,7 @@ class ReadmeGenerator:
|
|
|
143
171
|
result = subprocess.run(['git', 'shortlog', '-sne', 'HEAD'], capture_output=True, text=True, cwd=self.result.project_path)
|
|
144
172
|
if result.returncode == 0:
|
|
145
173
|
contributors = []
|
|
146
|
-
for line in result.stdout.strip().split('\n')[:
|
|
174
|
+
for line in result.stdout.strip().split('\n')[:CONSTANT_5]:
|
|
147
175
|
parts = line.split('\t')
|
|
148
176
|
if len(parts) >= 2:
|
|
149
177
|
contributors.append(parts[1])
|
|
@@ -257,7 +285,7 @@ class ReadmeGenerator:
|
|
|
257
285
|
lang_map = {'python': 'python', 'javascript': 'javascript', 'typescript': 'typescript', 'rust': 'rust', 'go': 'go'}
|
|
258
286
|
code_lang = lang_map.get(lang, lang)
|
|
259
287
|
parts.append(f'```{code_lang}')
|
|
260
|
-
parts.append(f"// Entry points: {', '.join(entry_points[:
|
|
288
|
+
parts.append(f"// Entry points: {', '.join(entry_points[:CONSTANT_3])}")
|
|
261
289
|
parts.append('```\n')
|
|
262
290
|
return '\n'.join(parts)
|
|
263
291
|
|
|
@@ -265,12 +293,12 @@ class ReadmeGenerator:
|
|
|
265
293
|
def _build_api_section(_project_name: str, context: Dict) -> str:
|
|
266
294
|
"""Build API overview section with classes and functions."""
|
|
267
295
|
parts = ['## API Overview\n']
|
|
268
|
-
for name, cls in list(context.get('public_classes', {}).items())[:
|
|
296
|
+
for name, cls in list(context.get('public_classes', {}).items())[:CONSTANT_20]:
|
|
269
297
|
doc = f' — {cls.docstring.splitlines()[0]}' if cls.docstring else ''
|
|
270
298
|
parts.append(f'- **`{cls.name}`**{doc}')
|
|
271
299
|
parts.append('')
|
|
272
|
-
for name, func in list(context.get('public_functions', {}).items())[:
|
|
273
|
-
args_str = ', '.join(func.args[:
|
|
300
|
+
for name, func in list(context.get('public_functions', {}).items())[:CONSTANT_30]:
|
|
301
|
+
args_str = ', '.join(func.args[:CONSTANT_5])
|
|
274
302
|
ret = f' → {func.returns}' if func.returns else ''
|
|
275
303
|
parts.append(f'- `{func.name}({args_str}){ret}`')
|
|
276
304
|
parts.append('')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code2docs
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.27
|
|
4
4
|
Summary: Auto-generate and sync project documentation from source code analysis
|
|
5
5
|
Author-email: Tom Sapletta <tom@sapletta.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -56,17 +56,17 @@ Dynamic: license-file
|
|
|
56
56
|
|
|
57
57
|
## AI Cost Tracking
|
|
58
58
|
|
|
59
|
-
    
|
|
60
60
|
  
|
|
61
61
|
|
|
62
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
62
|
+
- 🤖 **LLM usage:** $7.5000 (59 commits)
|
|
63
63
|
- 👤 **Human dev:** ~$1857 (18.6h @ $100/h, 30min dedup)
|
|
64
64
|
|
|
65
|
-
Generated on 2026-04-
|
|
65
|
+
Generated on 2026-04-09 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
66
66
|
|
|
67
67
|
---
|
|
68
68
|
|
|
69
|
-
  
|
|
70
70
|
|
|
71
71
|
> Auto-generate and sync project documentation from source code analysis.
|
|
72
72
|
|
|
@@ -206,7 +206,7 @@ code2docs can update only specific sections of an existing README using markers:
|
|
|
206
206
|
```markdown
|
|
207
207
|
<!-- code2docs:start --># code2docs
|
|
208
208
|
|
|
209
|
-
   
|
|
210
210
|
> **276** functions | **57** classes | **51** files | CC̄ = 3.8
|
|
211
211
|
|
|
212
212
|
> Auto-generated project documentation from source code analysis.
|
|
@@ -5,9 +5,9 @@ from pathlib import Path
|
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
7
|
|
|
8
|
-
from code2docs.analyzers.docstring_extractor import DocstringExtractor
|
|
8
|
+
from code2docs.analyzers.docstring_extractor import DocstringExtractor
|
|
9
9
|
from code2docs.analyzers.dependency_scanner import DependencyScanner
|
|
10
|
-
from code2docs.analyzers.endpoint_detector import EndpointDetector
|
|
10
|
+
from code2docs.analyzers.endpoint_detector import EndpointDetector
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class TestDocstringExtractor:
|
|
@@ -10,16 +10,64 @@ CONSTANT_30 = 30
|
|
|
10
10
|
CONSTANT_50 = 50
|
|
11
11
|
CONSTANT_80 = 80
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
|
|
14
|
+
CONSTANT_3 = CONSTANT_3
|
|
15
|
+
PORT_4 = PORT_4
|
|
16
|
+
CONSTANT_5 = CONSTANT_5
|
|
17
|
+
CONSTANT_6 = CONSTANT_6
|
|
18
|
+
CONSTANT_8 = CONSTANT_8
|
|
19
|
+
CONSTANT_15 = CONSTANT_15
|
|
20
|
+
CONSTANT_20 = CONSTANT_20
|
|
21
|
+
CONSTANT_30 = CONSTANT_30
|
|
22
|
+
CONSTANT_50 = CONSTANT_50
|
|
23
|
+
CONSTANT_80 = CONSTANT_80
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
CONSTANT_3 = CONSTANT_3
|
|
27
|
+
PORT_4 = PORT_4
|
|
28
|
+
CONSTANT_5 = CONSTANT_5
|
|
29
|
+
CONSTANT_6 = CONSTANT_6
|
|
30
|
+
CONSTANT_8 = CONSTANT_8
|
|
31
|
+
CONSTANT_15 = CONSTANT_15
|
|
32
|
+
CONSTANT_20 = CONSTANT_20
|
|
33
|
+
CONSTANT_30 = CONSTANT_30
|
|
34
|
+
CONSTANT_50 = CONSTANT_50
|
|
35
|
+
CONSTANT_80 = CONSTANT_80
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
CONSTANT_3 = CONSTANT_3
|
|
39
|
+
PORT_4 = PORT_4
|
|
40
|
+
CONSTANT_5 = CONSTANT_5
|
|
41
|
+
CONSTANT_6 = CONSTANT_6
|
|
42
|
+
CONSTANT_8 = CONSTANT_8
|
|
43
|
+
CONSTANT_15 = CONSTANT_15
|
|
44
|
+
CONSTANT_20 = CONSTANT_20
|
|
45
|
+
CONSTANT_30 = CONSTANT_30
|
|
46
|
+
CONSTANT_50 = CONSTANT_50
|
|
47
|
+
CONSTANT_80 = CONSTANT_80
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
CONSTANT_3 = CONSTANT_3
|
|
51
|
+
PORT_4 = PORT_4
|
|
52
|
+
CONSTANT_5 = CONSTANT_5
|
|
53
|
+
CONSTANT_6 = CONSTANT_6
|
|
54
|
+
CONSTANT_8 = CONSTANT_8
|
|
55
|
+
CONSTANT_15 = CONSTANT_15
|
|
56
|
+
CONSTANT_20 = CONSTANT_20
|
|
57
|
+
CONSTANT_30 = CONSTANT_30
|
|
58
|
+
CONSTANT_50 = CONSTANT_50
|
|
59
|
+
CONSTANT_80 = CONSTANT_80
|
|
60
|
+
|
|
61
|
+
CONSTANT_3 = CONSTANT_3
|
|
62
|
+
PORT_4 = PORT_4
|
|
63
|
+
CONSTANT_5 = CONSTANT_5
|
|
64
|
+
CONSTANT_6 = CONSTANT_6
|
|
65
|
+
CONSTANT_8 = CONSTANT_8
|
|
66
|
+
CONSTANT_15 = CONSTANT_15
|
|
67
|
+
CONSTANT_20 = CONSTANT_20
|
|
68
|
+
CONSTANT_30 = CONSTANT_30
|
|
69
|
+
CONSTANT_50 = CONSTANT_50
|
|
70
|
+
CONSTANT_80 = CONSTANT_80
|
|
23
71
|
'Integration tests for generators using mock AnalysisResult.'
|
|
24
72
|
import tempfile
|
|
25
73
|
from pathlib import Path
|
|
@@ -31,8 +79,8 @@ def _make_result() -> AnalysisResult:
|
|
|
31
79
|
result = AnalysisResult(project_path='/tmp/mockproject')
|
|
32
80
|
result.modules = {'mylib.core': ModuleInfo(name='mylib.core', file='/tmp/mockproject/mylib/core.py', imports=['os', 'json', 'mylib.utils'], functions=['process', 'validate'], classes=['Engine']), 'mylib.utils': ModuleInfo(name='mylib.utils', file='/tmp/mockproject/mylib/utils.py', imports=['re'], functions=['slugify', 'sanitize'], classes=[])}
|
|
33
81
|
result.classes = {'mylib.core.Engine': ClassInfo(name='Engine', qualified_name='mylib.core.Engine', file='/tmp/mockproject/mylib/core.py', line=10, module='mylib.core', bases=['BaseEngine'], methods=['run', 'stop'], docstring='Main processing engine.')}
|
|
34
|
-
result.functions = {'mylib.core.process': FunctionInfo(name='process', qualified_name='mylib.core.process', file='/tmp/mockproject/mylib/core.py', line=
|
|
35
|
-
result.stats = {'files_processed': 2, 'functions_found':
|
|
82
|
+
result.functions = {'mylib.core.process': FunctionInfo(name='process', qualified_name='mylib.core.process', file='/tmp/mockproject/mylib/core.py', line=CONSTANT_50, module='mylib.core', args=['data', 'config'], returns='Result', docstring='Process input data.', complexity={'cyclomatic': CONSTANT_5}), 'mylib.core.validate': FunctionInfo(name='validate', qualified_name='mylib.core.validate', file='/tmp/mockproject/mylib/core.py', line=CONSTANT_80, module='mylib.core', args=['schema', 'payload'], returns='bool', docstring='Validate payload against schema.', complexity={'cyclomatic': CONSTANT_8}), 'mylib.core.Engine.run': FunctionInfo(name='run', qualified_name='mylib.core.Engine.run', file='/tmp/mockproject/mylib/core.py', line=CONSTANT_15, module='mylib.core', is_method=True, args=['self', 'input'], returns='Output', docstring='Run the engine.', complexity={'cyclomatic': CONSTANT_3}), 'mylib.core.Engine.stop': FunctionInfo(name='stop', qualified_name='mylib.core.Engine.stop', file='/tmp/mockproject/mylib/core.py', line=CONSTANT_30, module='mylib.core', is_method=True, args=['self'], returns=None, docstring='Stop the engine.', complexity={'cyclomatic': 1}), 'mylib.utils.slugify': FunctionInfo(name='slugify', qualified_name='mylib.utils.slugify', file='/tmp/mockproject/mylib/utils.py', line=CONSTANT_5, module='mylib.utils', args=['text'], returns='str', docstring='Convert text to slug.', complexity={'cyclomatic': 2}), 'mylib.utils.sanitize': FunctionInfo(name='sanitize', qualified_name='mylib.utils.sanitize', file='/tmp/mockproject/mylib/utils.py', line=CONSTANT_20, module='mylib.utils', args=['html'], returns='str', docstring=None, complexity={'cyclomatic': PORT_4})}
|
|
83
|
+
result.stats = {'files_processed': 2, 'functions_found': CONSTANT_6, 'classes_found': 1}
|
|
36
84
|
result.entry_points = ['mylib.core.process']
|
|
37
85
|
return result
|
|
38
86
|
|
|
@@ -227,7 +275,7 @@ class TestArchitectureGenerator:
|
|
|
227
275
|
gen = ArchitectureGenerator(config, result)
|
|
228
276
|
content = gen.generate()
|
|
229
277
|
assert 'Architecture' in content
|
|
230
|
-
assert len(content) >
|
|
278
|
+
assert len(content) > CONSTANT_50
|
|
231
279
|
|
|
232
280
|
def test_contains_mermaid(self) -> None:
|
|
233
281
|
from code2docs.generators.architecture_gen import ArchitectureGenerator
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from unittest.mock import patch, MagicMock
|
|
4
4
|
|
|
5
|
-
import pytest
|
|
6
5
|
|
|
7
6
|
from code2docs.config import LLMConfig, Code2DocsConfig
|
|
8
|
-
from code2docs.llm_helper import LLMHelper
|
|
7
|
+
from code2docs.llm_helper import LLMHelper
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
# ── LLMConfig tests ──────────────────────────────────────────────────────
|
|
@@ -28,7 +27,7 @@ class TestLLMConfig:
|
|
|
28
27
|
|
|
29
28
|
def test_from_env_with_model(self):
|
|
30
29
|
env = {
|
|
31
|
-
"CODE2DOCS_LLM_MODEL": "openai/gpt-
|
|
30
|
+
"CODE2DOCS_LLM_MODEL": "openai/gpt-5.4-mini",
|
|
32
31
|
"CODE2DOCS_LLM_API_KEY": "sk-test123",
|
|
33
32
|
"CODE2DOCS_LLM_MAX_TOKENS": "2048",
|
|
34
33
|
"CODE2DOCS_LLM_TEMPERATURE": "0.5",
|
|
@@ -36,7 +35,7 @@ class TestLLMConfig:
|
|
|
36
35
|
with patch.dict("os.environ", env, clear=True):
|
|
37
36
|
cfg = LLMConfig.from_env()
|
|
38
37
|
assert cfg.enabled is True
|
|
39
|
-
assert cfg.model == "openai/gpt-
|
|
38
|
+
assert cfg.model == "openai/gpt-5.4-mini"
|
|
40
39
|
assert cfg.api_key == "sk-test123"
|
|
41
40
|
assert cfg.max_tokens == 2048
|
|
42
41
|
assert cfg.temperature == 0.5
|
|
@@ -59,7 +58,7 @@ class TestLLMHelperDisabled:
|
|
|
59
58
|
"""Test that LLMHelper returns None for everything when disabled."""
|
|
60
59
|
|
|
61
60
|
def test_not_available_when_disabled(self):
|
|
62
|
-
cfg = LLMConfig(enabled=False, model="openai/gpt-
|
|
61
|
+
cfg = LLMConfig(enabled=False, model="openai/gpt-5.4-mini")
|
|
63
62
|
helper = LLMHelper(cfg)
|
|
64
63
|
assert helper.available is False
|
|
65
64
|
|
|
@@ -103,7 +102,7 @@ class TestLLMHelperEnabled:
|
|
|
103
102
|
@staticmethod
|
|
104
103
|
def _make_helper():
|
|
105
104
|
cfg = LLMConfig(
|
|
106
|
-
enabled=True, model="openai/gpt-
|
|
105
|
+
enabled=True, model="openai/gpt-5.4-mini",
|
|
107
106
|
api_key="sk-test", max_tokens=512, temperature=0.2,
|
|
108
107
|
)
|
|
109
108
|
return LLMHelper(cfg)
|
|
@@ -128,7 +127,7 @@ class TestLLMHelperEnabled:
|
|
|
128
127
|
assert result == "Hello world"
|
|
129
128
|
mock_litellm.completion.assert_called_once()
|
|
130
129
|
call_kwargs = mock_litellm.completion.call_args[1]
|
|
131
|
-
assert call_kwargs["model"] == "openai/gpt-
|
|
130
|
+
assert call_kwargs["model"] == "openai/gpt-5.4-mini"
|
|
132
131
|
assert call_kwargs["max_tokens"] == 512
|
|
133
132
|
assert call_kwargs["temperature"] == 0.2
|
|
134
133
|
assert len(call_kwargs["messages"]) == 2
|
|
@@ -162,7 +161,7 @@ class TestLLMHelperEnabled:
|
|
|
162
161
|
|
|
163
162
|
def test_api_key_passed_when_set(self):
|
|
164
163
|
cfg = LLMConfig(
|
|
165
|
-
enabled=True, model="openai/gpt-
|
|
164
|
+
enabled=True, model="openai/gpt-5.4-mini",
|
|
166
165
|
api_key="sk-secret", max_tokens=1024, temperature=0.3,
|
|
167
166
|
)
|
|
168
167
|
helper = LLMHelper(cfg)
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
"""Tests for BaseGenerator, GeneratorRegistry, and adapter integration."""
|
|
2
2
|
|
|
3
|
-
import tempfile
|
|
4
3
|
from pathlib import Path
|
|
5
|
-
from typing import Optional
|
|
6
4
|
|
|
7
|
-
import pytest
|
|
8
5
|
|
|
9
6
|
from code2llm.core.models import (
|
|
10
7
|
AnalysisResult, FunctionInfo, ClassInfo, ModuleInfo,
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|