code2docs 3.0.34__tar.gz → 3.0.35__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.
Files changed (68) hide show
  1. {code2docs-3.0.34 → code2docs-3.0.35}/PKG-INFO +9 -10
  2. {code2docs-3.0.34 → code2docs-3.0.35}/README.md +7 -5
  3. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/__init__.py +1 -1
  4. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/analyzers/endpoint_detector.py +32 -1
  5. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/llm_helper.py +15 -2
  6. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs.egg-info/PKG-INFO +9 -10
  7. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs.egg-info/requires.txt +0 -2
  8. {code2docs-3.0.34 → code2docs-3.0.35}/pyproject.toml +8 -21
  9. {code2docs-3.0.34 → code2docs-3.0.35}/tests/test_analyzers.py +14 -0
  10. {code2docs-3.0.34 → code2docs-3.0.35}/tests/test_llm_helper.py +24 -0
  11. {code2docs-3.0.34 → code2docs-3.0.35}/LICENSE +0 -0
  12. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/__main__.py +0 -0
  13. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/analyzers/__init__.py +0 -0
  14. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/analyzers/dependency_scanner.py +0 -0
  15. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/analyzers/docstring_extractor.py +0 -0
  16. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/analyzers/markdown_validator.py +0 -0
  17. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/analyzers/project_scanner.py +0 -0
  18. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/base.py +0 -0
  19. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/cli.py +0 -0
  20. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/config.py +0 -0
  21. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/examples/advanced_usage.py +0 -0
  22. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/examples/quickstart.py +0 -0
  23. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/formatters/__init__.py +0 -0
  24. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/formatters/badges.py +0 -0
  25. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/formatters/markdown.py +0 -0
  26. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/formatters/toc.py +0 -0
  27. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/generators/__init__.py +0 -0
  28. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/generators/_registry_adapters.py +0 -0
  29. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/generators/_source_links.py +0 -0
  30. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/generators/api_changelog_gen.py +0 -0
  31. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/generators/api_reference_gen.py +0 -0
  32. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/generators/architecture_gen.py +0 -0
  33. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/generators/changelog_gen.py +0 -0
  34. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/generators/code2llm_gen.py +0 -0
  35. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/generators/config_docs_gen.py +0 -0
  36. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/generators/contributing_gen.py +0 -0
  37. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/generators/coverage_gen.py +0 -0
  38. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/generators/depgraph_gen.py +0 -0
  39. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/generators/examples_gen.py +0 -0
  40. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/generators/getting_started_gen.py +0 -0
  41. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/generators/mkdocs_gen.py +0 -0
  42. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/generators/module_docs_gen.py +0 -0
  43. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/generators/org_readme_gen.py +0 -0
  44. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/generators/readme_gen.py +0 -0
  45. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/registry.py +0 -0
  46. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/sync/__init__.py +0 -0
  47. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/sync/differ.py +0 -0
  48. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/sync/updater.py +0 -0
  49. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/sync/watcher.py +0 -0
  50. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/templates/api_module.md.j2 +0 -0
  51. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/templates/architecture.md.j2 +0 -0
  52. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/templates/example_usage.py.j2 +0 -0
  53. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/templates/index.md.j2 +0 -0
  54. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/templates/module_doc.md.j2 +0 -0
  55. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs/templates/readme.md.j2 +0 -0
  56. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs.egg-info/SOURCES.txt +0 -0
  57. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs.egg-info/dependency_links.txt +0 -0
  58. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs.egg-info/entry_points.txt +0 -0
  59. {code2docs-3.0.34 → code2docs-3.0.35}/code2docs.egg-info/top_level.txt +0 -0
  60. {code2docs-3.0.34 → code2docs-3.0.35}/setup.cfg +0 -0
  61. {code2docs-3.0.34 → code2docs-3.0.35}/tests/test_cli.py +0 -0
  62. {code2docs-3.0.34 → code2docs-3.0.35}/tests/test_code2docs.py +0 -0
  63. {code2docs-3.0.34 → code2docs-3.0.35}/tests/test_config.py +0 -0
  64. {code2docs-3.0.34 → code2docs-3.0.35}/tests/test_formatters.py +0 -0
  65. {code2docs-3.0.34 → code2docs-3.0.35}/tests/test_generators.py +0 -0
  66. {code2docs-3.0.34 → code2docs-3.0.35}/tests/test_markdown_validator.py +0 -0
  67. {code2docs-3.0.34 → code2docs-3.0.35}/tests/test_registry.py +0 -0
  68. {code2docs-3.0.34 → code2docs-3.0.35}/tests/test_sync.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code2docs
3
- Version: 3.0.34
3
+ Version: 3.0.35
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
@@ -11,14 +11,13 @@ Keywords: documentation,auto-docs,readme-generator,api-reference,code-analysis,s
11
11
  Classifier: Development Status :: 3 - Alpha
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.9
15
14
  Classifier: Programming Language :: Python :: 3.10
16
15
  Classifier: Programming Language :: Python :: 3.11
17
16
  Classifier: Programming Language :: Python :: 3.12
18
17
  Classifier: Programming Language :: Python :: 3.13
19
18
  Classifier: Topic :: Software Development :: Documentation
20
19
  Classifier: Topic :: Software Development :: Code Generators
21
- Requires-Python: >=3.9
20
+ Requires-Python: >=3.10
22
21
  Description-Content-Type: text/markdown
23
22
  License-File: LICENSE
24
23
  Requires-Dist: code2llm>=0.5.0
@@ -44,24 +43,24 @@ Requires-Dist: ruff>=0.1; extra == "dev"
44
43
  Requires-Dist: mypy>=1.0; extra == "dev"
45
44
  Requires-Dist: build>=1.0; extra == "dev"
46
45
  Requires-Dist: twine>=5.0; extra == "dev"
47
- Requires-Dist: goal>=2.1.0; extra == "dev"
48
46
  Requires-Dist: costs>=0.1.20; extra == "dev"
49
- Requires-Dist: pfix>=0.1.60; extra == "dev"
50
47
  Provides-Extra: all
51
48
  Requires-Dist: code2docs[git,llm,mkdocs,watch]; extra == "all"
52
49
  Dynamic: license-file
53
50
 
54
51
  ## AI Cost Tracking
55
52
 
56
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-yellow) ![AI Model](https://img.shields.io/badge/AI%20Model-openrouter%2Fqwen%2Fqwen3-coder-next-lightgrey)
53
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-3.0.35-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
54
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$5.36-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-45.9h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
57
55
 
58
- This project uses AI-generated code. Total cost: **$7.5000** with **77** AI commits.
56
+ - 🤖 **LLM usage:** $5.3635 (99 commits)
57
+ - 👤 **Human dev:** ~$4589 (45.9h @ $100/h, 30min dedup)
59
58
 
60
- Generated on 2026-06-29 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/models/openrouter/qwen/qwen3-coder-next)
59
+ Generated on 2026-10-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
61
60
 
62
61
  ---
63
62
 
64
- ![version](https://img.shields.io/badge/version-3.0.34-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![docs](https://img.shields.io/badge/docs-auto--generated-blueviolet)
63
+ ![version](https://img.shields.io/badge/version-3.0.35-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![docs](https://img.shields.io/badge/docs-auto--generated-blueviolet)
65
64
 
66
65
  > Auto-generate and sync project documentation from source code analysis.
67
66
 
@@ -198,7 +197,7 @@ code2docs can update only specific sections of an existing README using markers:
198
197
  ```markdown
199
198
  <!-- code2docs:start --># code2docs
200
199
 
201
- ![version](https://img.shields.io/badge/version-3.0.34-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![coverage](https://img.shields.io/badge/coverage-unknown-lightgrey) ![functions](https://img.shields.io/badge/functions-276-green)
200
+ ![version](https://img.shields.io/badge/version-3.0.35-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![coverage](https://img.shields.io/badge/coverage-unknown-lightgrey) ![functions](https://img.shields.io/badge/functions-276-green)
202
201
  > **276** functions | **57** classes | **51** files | CC̄ = 3.8
203
202
 
204
203
  > Auto-generated project documentation from source code analysis.
@@ -1,14 +1,16 @@
1
1
  ## AI Cost Tracking
2
2
 
3
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-yellow) ![AI Model](https://img.shields.io/badge/AI%20Model-openrouter%2Fqwen%2Fqwen3-coder-next-lightgrey)
3
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-3.0.35-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
4
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$5.36-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-45.9h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
4
5
 
5
- This project uses AI-generated code. Total cost: **$7.5000** with **77** AI commits.
6
+ - 🤖 **LLM usage:** $5.3635 (99 commits)
7
+ - 👤 **Human dev:** ~$4589 (45.9h @ $100/h, 30min dedup)
6
8
 
7
- Generated on 2026-06-29 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/models/openrouter/qwen/qwen3-coder-next)
9
+ Generated on 2026-10-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
8
10
 
9
11
  ---
10
12
 
11
- ![version](https://img.shields.io/badge/version-3.0.34-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![docs](https://img.shields.io/badge/docs-auto--generated-blueviolet)
13
+ ![version](https://img.shields.io/badge/version-3.0.35-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![docs](https://img.shields.io/badge/docs-auto--generated-blueviolet)
12
14
 
13
15
  > Auto-generate and sync project documentation from source code analysis.
14
16
 
@@ -145,7 +147,7 @@ code2docs can update only specific sections of an existing README using markers:
145
147
  ```markdown
146
148
  <!-- code2docs:start --># code2docs
147
149
 
148
- ![version](https://img.shields.io/badge/version-3.0.34-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![coverage](https://img.shields.io/badge/coverage-unknown-lightgrey) ![functions](https://img.shields.io/badge/functions-276-green)
150
+ ![version](https://img.shields.io/badge/version-3.0.35-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![coverage](https://img.shields.io/badge/coverage-unknown-lightgrey) ![functions](https://img.shields.io/badge/functions-276-green)
149
151
  > **276** functions | **57** classes | **51** files | CC̄ = 3.8
150
152
 
151
153
  > 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.34"
8
+ __version__ = "3.0.35"
9
9
  __author__ = "Tom Sapletta"
10
10
  __all__ = ["Code2DocsConfig", "generate_readme", "generate_docs", "analyze_and_document"]
11
11
 
@@ -1,5 +1,6 @@
1
1
  """Detect web framework endpoints (Flask, FastAPI, Django) from AST analysis."""
2
2
 
3
+ import os
3
4
  import re
4
5
  from dataclasses import dataclass, field
5
6
  from pathlib import Path
@@ -23,6 +24,36 @@ class Endpoint:
23
24
  return_type: Optional[str] = None
24
25
 
25
26
 
27
+ # Directories never searched for Django urls.py: virtualenvs, vendored packages,
28
+ # VCS/worktree copies and build output. Walking them dominated runtime (~40s on
29
+ # a workspace with vendored site-packages). Any dot-directory is skipped too.
30
+ _SKIP_DIRS = frozenset(
31
+ {
32
+ "venv",
33
+ "env",
34
+ "node_modules",
35
+ "site-packages",
36
+ "__pycache__",
37
+ "dist",
38
+ "build",
39
+ "target",
40
+ "bower_components",
41
+ }
42
+ )
43
+
44
+
45
+ def _iter_urls_files(root: Path):
46
+ """Yield ``urls.py`` files under *root*, pruning irrelevant directories."""
47
+ for dirpath, dirnames, filenames in os.walk(root):
48
+ dirnames[:] = [
49
+ d
50
+ for d in dirnames
51
+ if d not in _SKIP_DIRS and not d.startswith(".") and not d.endswith(".egg-info")
52
+ ]
53
+ if "urls.py" in filenames:
54
+ yield Path(dirpath) / "urls.py"
55
+
56
+
26
57
  class EndpointDetector:
27
58
  """Detects web endpoints from decorator patterns in source code."""
28
59
 
@@ -91,7 +122,7 @@ class EndpointDetector:
91
122
  endpoints: List[Endpoint] = []
92
123
  project = Path(project_path)
93
124
 
94
- for urls_file in project.rglob("urls.py"):
125
+ for urls_file in _iter_urls_files(project):
95
126
  try:
96
127
  source = urls_file.read_text(encoding="utf-8")
97
128
  for match in self.DJANGO_URL_PATTERN.finditer(source):
@@ -4,6 +4,7 @@ If litellm is not installed or LLM is not configured, all methods return None
4
4
  and generators fall back to algorithm-based template output.
5
5
  """
6
6
 
7
+ import importlib.util
7
8
  import logging
8
9
  from typing import Optional
9
10
 
@@ -29,6 +30,18 @@ def _get_litellm():
29
30
  return _litellm if _litellm is not False else None
30
31
 
31
32
 
33
+ def _litellm_installed() -> bool:
34
+ """Cheap availability check: locate litellm without importing it.
35
+
36
+ Importing litellm costs ~10s, so it must only happen when a completion is
37
+ actually requested (see ``_get_litellm`` in ``LLMHelper.complete``).
38
+ """
39
+ try:
40
+ return importlib.util.find_spec("litellm") is not None
41
+ except (ImportError, ValueError):
42
+ return False
43
+
44
+
32
45
  class LLMHelper:
33
46
  """Thin wrapper around litellm for documentation generation.
34
47
 
@@ -45,7 +58,7 @@ class LLMHelper:
45
58
  """Check if LLM is configured and litellm is installed."""
46
59
  if self._available is None:
47
60
  self._available = (
48
- self.config.enabled and bool(self.config.model) and _get_litellm() is not None
61
+ self.config.enabled and bool(self.config.model) and _litellm_installed()
49
62
  )
50
63
  if self._available:
51
64
  logger.info("LLM enabled: model=%s", self.config.model)
@@ -54,7 +67,7 @@ class LLMHelper:
54
67
  "LLM disabled (enabled=%s, model=%s, litellm=%s)",
55
68
  self.config.enabled,
56
69
  bool(self.config.model),
57
- _get_litellm() is not None,
70
+ _litellm_installed(),
58
71
  )
59
72
  return self._available
60
73
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code2docs
3
- Version: 3.0.34
3
+ Version: 3.0.35
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
@@ -11,14 +11,13 @@ Keywords: documentation,auto-docs,readme-generator,api-reference,code-analysis,s
11
11
  Classifier: Development Status :: 3 - Alpha
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.9
15
14
  Classifier: Programming Language :: Python :: 3.10
16
15
  Classifier: Programming Language :: Python :: 3.11
17
16
  Classifier: Programming Language :: Python :: 3.12
18
17
  Classifier: Programming Language :: Python :: 3.13
19
18
  Classifier: Topic :: Software Development :: Documentation
20
19
  Classifier: Topic :: Software Development :: Code Generators
21
- Requires-Python: >=3.9
20
+ Requires-Python: >=3.10
22
21
  Description-Content-Type: text/markdown
23
22
  License-File: LICENSE
24
23
  Requires-Dist: code2llm>=0.5.0
@@ -44,24 +43,24 @@ Requires-Dist: ruff>=0.1; extra == "dev"
44
43
  Requires-Dist: mypy>=1.0; extra == "dev"
45
44
  Requires-Dist: build>=1.0; extra == "dev"
46
45
  Requires-Dist: twine>=5.0; extra == "dev"
47
- Requires-Dist: goal>=2.1.0; extra == "dev"
48
46
  Requires-Dist: costs>=0.1.20; extra == "dev"
49
- Requires-Dist: pfix>=0.1.60; extra == "dev"
50
47
  Provides-Extra: all
51
48
  Requires-Dist: code2docs[git,llm,mkdocs,watch]; extra == "all"
52
49
  Dynamic: license-file
53
50
 
54
51
  ## AI Cost Tracking
55
52
 
56
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-yellow) ![AI Model](https://img.shields.io/badge/AI%20Model-openrouter%2Fqwen%2Fqwen3-coder-next-lightgrey)
53
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-3.0.35-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
54
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$5.36-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-45.9h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
57
55
 
58
- This project uses AI-generated code. Total cost: **$7.5000** with **77** AI commits.
56
+ - 🤖 **LLM usage:** $5.3635 (99 commits)
57
+ - 👤 **Human dev:** ~$4589 (45.9h @ $100/h, 30min dedup)
59
58
 
60
- Generated on 2026-06-29 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/models/openrouter/qwen/qwen3-coder-next)
59
+ Generated on 2026-10-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
61
60
 
62
61
  ---
63
62
 
64
- ![version](https://img.shields.io/badge/version-3.0.34-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![docs](https://img.shields.io/badge/docs-auto--generated-blueviolet)
63
+ ![version](https://img.shields.io/badge/version-3.0.35-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![docs](https://img.shields.io/badge/docs-auto--generated-blueviolet)
65
64
 
66
65
  > Auto-generate and sync project documentation from source code analysis.
67
66
 
@@ -198,7 +197,7 @@ code2docs can update only specific sections of an existing README using markers:
198
197
  ```markdown
199
198
  <!-- code2docs:start --># code2docs
200
199
 
201
- ![version](https://img.shields.io/badge/version-3.0.34-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![coverage](https://img.shields.io/badge/coverage-unknown-lightgrey) ![functions](https://img.shields.io/badge/functions-276-green)
200
+ ![version](https://img.shields.io/badge/version-3.0.35-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![coverage](https://img.shields.io/badge/coverage-unknown-lightgrey) ![functions](https://img.shields.io/badge/functions-276-green)
202
201
  > **276** functions | **57** classes | **51** files | CC̄ = 3.8
203
202
 
204
203
  > Auto-generated project documentation from source code analysis.
@@ -15,9 +15,7 @@ ruff>=0.1
15
15
  mypy>=1.0
16
16
  build>=1.0
17
17
  twine>=5.0
18
- goal>=2.1.0
19
18
  costs>=0.1.20
20
- pfix>=0.1.60
21
19
 
22
20
  [git]
23
21
  gitpython>=3.1
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code2docs"
7
- version = "3.0.34"
7
+ version = "3.0.35"
8
8
  description = "Auto-generate and sync project documentation from source code analysis"
9
9
  readme = "README.md"
10
- requires-python = ">=3.9"
10
+ requires-python = ">=3.10"
11
11
  license = "Apache-2.0"
12
12
  authors = [
13
13
  {name = "Tom Sapletta", email = "tom@sapletta.com"},
@@ -25,7 +25,6 @@ classifiers = [
25
25
  "Development Status :: 3 - Alpha",
26
26
  "Intended Audience :: Developers",
27
27
  "Programming Language :: Python :: 3",
28
- "Programming Language :: Python :: 3.9",
29
28
  "Programming Language :: Python :: 3.10",
30
29
  "Programming Language :: Python :: 3.11",
31
30
  "Programming Language :: Python :: 3.12",
@@ -54,9 +53,7 @@ dev = [
54
53
  "mypy>=1.0",
55
54
  "build>=1.0",
56
55
  "twine>=5.0",
57
- "goal>=2.1.0",
58
56
  "costs>=0.1.20",
59
- "pfix>=0.1.60",
60
57
  ]
61
58
  all = ["code2docs[llm,git,watch,mkdocs]"]
62
59
 
@@ -90,22 +87,6 @@ include = ["code2docs*"]
90
87
  [tool.setuptools.package-data]
91
88
  code2docs = ["templates/*.j2"]
92
89
 
93
- [tool.pfix]
94
- # Self-healing Python configuration
95
- model = "openrouter/qwen/qwen3-coder-next"
96
- auto_apply = true
97
- auto_install_deps = true
98
- auto_restart = false
99
- max_retries = 3
100
- create_backups = false
101
- git_auto_commit = false
102
-
103
- [tool.pfix.runtime_todo]
104
- enabled = true
105
- todo_file = "TODO.md"
106
- min_severity = "low"
107
- deduplicate = true
108
-
109
90
  [tool.costs]
110
91
  # AI Cost tracking configuration
111
92
  badge = true
@@ -118,3 +99,9 @@ max_commits = 500
118
99
 
119
100
  # Cost thresholds for badge colors (USD)
120
101
  badge_color_thresholds = { low = 1.0, medium = 5.0, high = 10.0, critical = 50.0 }
102
+
103
+ [tool.uv.dependency-groups.automation]
104
+ requires-python = ">=3.12"
105
+
106
+ [dependency-groups]
107
+ automation = ["goal>=2.1.302"]
@@ -136,3 +136,17 @@ class TestEndpointDetector:
136
136
  )
137
137
  ep = detector._parse_decorator(func.decorators[0], func)
138
138
  assert ep is None
139
+
140
+
141
+ def test_endpoint_detector_skips_vendored_and_hidden_dirs(tmp_path):
142
+ from code2docs.analyzers.endpoint_detector import EndpointDetector
143
+
144
+ (tmp_path / "app").mkdir()
145
+ (tmp_path / "app" / "urls.py").write_text("urlpatterns = [path('real/', v)]\n")
146
+ for skipped in (".subactor/lib/site-packages/x", "venv/lib", "node_modules/y", ".worktrees/t1"):
147
+ d = tmp_path / skipped
148
+ d.mkdir(parents=True)
149
+ (d / "urls.py").write_text("urlpatterns = [path('vendored/', v)]\n")
150
+
151
+ paths = [e.path for e in EndpointDetector()._scan_django_urls(str(tmp_path))]
152
+ assert paths == ["real/"]
@@ -270,3 +270,27 @@ class TestGeneratorFallback:
270
270
  content = gen.generate()
271
271
  assert "Getting Started" in content
272
272
  assert "Installation" in content
273
+
274
+
275
+ # ── Performance regression tests ─────────────────────────────────────────
276
+
277
+
278
+ class TestLazyLitellmImport:
279
+ """Availability checks must not import litellm (~10s)."""
280
+
281
+ def test_disabled_helper_never_imports_litellm(self):
282
+ from code2docs import llm_helper
283
+ from code2docs.config import LLMConfig
284
+
285
+ with patch("code2docs.llm_helper._get_litellm") as get_litellm:
286
+ assert LLMHelper(LLMConfig(enabled=False)).available is False
287
+ get_litellm.assert_not_called()
288
+
289
+ def test_enabled_availability_uses_find_spec_not_import(self):
290
+ from code2docs.config import LLMConfig
291
+
292
+ with patch("code2docs.llm_helper._litellm_installed", return_value=True), patch(
293
+ "code2docs.llm_helper._get_litellm"
294
+ ) as get_litellm:
295
+ assert LLMHelper(LLMConfig(enabled=True, model="x")).available is True
296
+ get_litellm.assert_not_called()
File without changes
File without changes
File without changes
File without changes
File without changes