deepdoc 0.4.1__tar.gz → 0.4.2__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 (171) hide show
  1. {deepdoc-0.4.1 → deepdoc-0.4.2}/PKG-INFO +1 -1
  2. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/routes/falcon.py +22 -2
  3. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/planner/topology.py +100 -6
  4. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/__init__.py +0 -1
  5. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/next.config.mjs +3 -1
  6. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc.egg-info/PKG-INFO +1 -1
  7. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc.egg-info/SOURCES.txt +0 -2
  8. {deepdoc-0.4.1 → deepdoc-0.4.2}/pyproject.toml +1 -1
  9. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_chatbot_query.py +3 -3
  10. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_framework_support.py +25 -0
  11. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_planner_granularity.py +0 -47
  12. deepdoc-0.4.1/deepdoc/site/builder/mkdocs_builder.py +0 -2150
  13. deepdoc-0.4.1/tests/test_mkdocs_builder.py +0 -920
  14. {deepdoc-0.4.1 → deepdoc-0.4.2}/LICENSE +0 -0
  15. {deepdoc-0.4.1 → deepdoc-0.4.2}/README.md +0 -0
  16. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/__init__.py +0 -0
  17. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/__main__.py +0 -0
  18. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/benchmark_v2.py +0 -0
  19. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/call_graph.py +0 -0
  20. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/changelog_writer.py +0 -0
  21. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/__init__.py +0 -0
  22. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/answer_mixin.py +0 -0
  23. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/chunker.py +0 -0
  24. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/constants.py +0 -0
  25. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/deep_research.py +0 -0
  26. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/docs_summary.py +0 -0
  27. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/embeddings.py +0 -0
  28. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/indexer.py +0 -0
  29. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/linking.py +0 -0
  30. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/live_fallback_mixin.py +0 -0
  31. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/persistence.py +0 -0
  32. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/providers.py +0 -0
  33. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/retrieval_mixin.py +0 -0
  34. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/routes.py +0 -0
  35. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/scaffold.py +0 -0
  36. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/service.py +0 -0
  37. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/settings.py +0 -0
  38. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/source_archive.py +0 -0
  39. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/symbol_index.py +0 -0
  40. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/chatbot/types.py +0 -0
  41. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/cli.py +0 -0
  42. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/config.py +0 -0
  43. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/generator/__init__.py +0 -0
  44. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/generator/consistency.py +0 -0
  45. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/generator/evidence.py +0 -0
  46. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/generator/generation.py +0 -0
  47. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/generator/post_processors.py +0 -0
  48. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/generator/validation.py +0 -0
  49. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/llm/__init__.py +0 -0
  50. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/llm/client.py +0 -0
  51. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/llm/json_utils.py +0 -0
  52. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/llm/litellm_compat.py +0 -0
  53. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/llm/retry.py +0 -0
  54. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/manifest.py +0 -0
  55. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/openapi.py +0 -0
  56. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/__init__.py +0 -0
  57. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/api_detector.py +0 -0
  58. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/base.py +0 -0
  59. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/go_parser.py +0 -0
  60. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/js_ts_parser.py +0 -0
  61. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/php_parser.py +0 -0
  62. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/python_parser.py +0 -0
  63. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/registry.py +0 -0
  64. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/routes/__init__.py +0 -0
  65. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/routes/base.py +0 -0
  66. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/routes/common.py +0 -0
  67. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/routes/detector.py +0 -0
  68. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/routes/django.py +0 -0
  69. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/routes/express.py +0 -0
  70. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/routes/fastify.py +0 -0
  71. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/routes/go.py +0 -0
  72. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/routes/js_shared.py +0 -0
  73. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/routes/laravel.py +0 -0
  74. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/routes/nestjs.py +0 -0
  75. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/routes/python_shared.py +0 -0
  76. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/routes/registry.py +0 -0
  77. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/routes/repo_resolver.py +0 -0
  78. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/parser/vue_parser.py +0 -0
  79. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/persistence_v2.py +0 -0
  80. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/pipeline_v2.py +0 -0
  81. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/planner/__init__.py +0 -0
  82. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/planner/bucket_injection.py +0 -0
  83. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/planner/bucket_refinement.py +0 -0
  84. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/planner/common.py +0 -0
  85. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/planner/endpoint_refs.py +0 -0
  86. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/planner/engine.py +0 -0
  87. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/planner/flow_candidates.py +0 -0
  88. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/planner/heuristics.py +0 -0
  89. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/planner/nav_shaping.py +0 -0
  90. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/planner/specializations.py +0 -0
  91. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/planner/utils.py +0 -0
  92. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/prompts/__init__.py +0 -0
  93. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/prompts/bucket_types.py +0 -0
  94. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/prompts/page_types.py +0 -0
  95. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/prompts/selectors.py +0 -0
  96. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/prompts/system.py +0 -0
  97. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/prompts/update.py +0 -0
  98. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/py.typed +0 -0
  99. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/scanner/__init__.py +0 -0
  100. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/scanner/artifacts.py +0 -0
  101. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/scanner/clustering.py +0 -0
  102. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/scanner/common.py +0 -0
  103. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/scanner/database.py +0 -0
  104. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/scanner/endpoints.py +0 -0
  105. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/scanner/integrations.py +0 -0
  106. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/scanner/runtime.py +0 -0
  107. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/scanner/utils.py +0 -0
  108. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/__init__.py +0 -0
  109. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/common.py +0 -0
  110. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/mdx_utils.py +0 -0
  111. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/next_builder.py +0 -0
  112. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/.gitignore +0 -0
  113. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/app/(main)/[[...slug]]/page.tsx +0 -0
  114. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/app/(main)/layout.tsx +0 -0
  115. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/app/ask/layout.tsx +0 -0
  116. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/app/ask/page.tsx +0 -0
  117. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/app/components/mermaid-runner.tsx +0 -0
  118. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/app/globals.css +0 -0
  119. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/app/layout.tsx +0 -0
  120. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/components/chatbot.tsx +0 -0
  121. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/deepdoc.config.json +0 -0
  122. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/lib/config.ts +0 -0
  123. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/lib/docs.ts +0 -0
  124. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/lib/nav.ts +0 -0
  125. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/package.json +0 -0
  126. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/tsconfig.json +0 -0
  127. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/vendor/highlight.min.js +0 -0
  128. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/vendor/hljs-theme.css +0 -0
  129. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/site/builder/vendor/markdown-it.min.js +0 -0
  130. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/smart_update_v2.py +0 -0
  131. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/source_metadata.py +0 -0
  132. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/updater_v2.py +0 -0
  133. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc/v2_models.py +0 -0
  134. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc.egg-info/dependency_links.txt +0 -0
  135. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc.egg-info/entry_points.txt +0 -0
  136. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc.egg-info/requires.txt +0 -0
  137. {deepdoc-0.4.1 → deepdoc-0.4.2}/deepdoc.egg-info/top_level.txt +0 -0
  138. {deepdoc-0.4.1 → deepdoc-0.4.2}/setup.cfg +0 -0
  139. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_benchmark_scorecard.py +0 -0
  140. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_call_graph.py +0 -0
  141. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_changelog.py +0 -0
  142. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_chatbot_config.py +0 -0
  143. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_chatbot_embeddings.py +0 -0
  144. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_chatbot_eval.py +0 -0
  145. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_chatbot_index.py +0 -0
  146. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_chatbot_persistence.py +0 -0
  147. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_chatbot_providers.py +0 -0
  148. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_chatbot_relationship.py +0 -0
  149. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_chatbot_scaffold.py +0 -0
  150. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_chatbot_source_archive.py +0 -0
  151. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_classify.py +0 -0
  152. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_cli_generate.py +0 -0
  153. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_cli_serve.py +0 -0
  154. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_cli_update.py +0 -0
  155. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_consistency_pass.py +0 -0
  156. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_flow_candidates.py +0 -0
  157. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_framework_fixtures.py +0 -0
  158. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_generation_evidence.py +0 -0
  159. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_internal_docs_metadata.py +0 -0
  160. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_litellm_compat.py +0 -0
  161. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_llm_json_utils.py +0 -0
  162. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_llm_retry.py +0 -0
  163. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_next_builder.py +0 -0
  164. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_parallel_pipeline.py +0 -0
  165. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_parser_ranges.py +0 -0
  166. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_planner_consolidation.py +0 -0
  167. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_route_registry.py +0 -0
  168. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_runtime_scan.py +0 -0
  169. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_smart_update.py +0 -0
  170. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_stale.py +0 -0
  171. {deepdoc-0.4.1 → deepdoc-0.4.2}/tests/test_state.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepdoc
3
- Version: 0.4.1
3
+ Version: 0.4.2
4
4
  Summary: Auto-generate beautiful docs from any codebase
5
5
  Author: Pranav Kumar
6
6
  License: MIT
@@ -114,8 +114,20 @@ def _fallback_falcon_path(path_expr: str) -> str:
114
114
  return "".join(string_parts)
115
115
 
116
116
 
117
- def find_falcon_responders(content: str, class_name: str) -> list[str]:
118
- """Find all on_* methods in a Falcon resource class."""
117
+ def _base_class_name(class_body: str) -> str:
118
+ """Return the first base class name from a class definition line, or ''."""
119
+ m = re.match(r"class\s+\w+\s*\(\s*([A-Za-z_]\w*)", class_body)
120
+ return m.group(1) if m else ""
121
+
122
+
123
+ def find_falcon_responders(
124
+ content: str,
125
+ class_name: str,
126
+ _visited: frozenset[str] | None = None,
127
+ ) -> list[str]:
128
+ """Find all on_* methods in a Falcon resource class, including inherited ones."""
129
+ if _visited is None:
130
+ _visited = frozenset()
119
131
  methods: list[str] = []
120
132
  class_pattern = re.compile(
121
133
  rf"class\s+{re.escape(class_name)}\b.*?(?=\nclass\s|\Z)",
@@ -128,6 +140,14 @@ def find_falcon_responders(content: str, class_name: str) -> list[str]:
128
140
  method_name = match.group(1)
129
141
  http_method = method_name.replace("on_", "").upper()
130
142
  methods.append(http_method)
143
+ base = _base_class_name(class_body)
144
+ if base and base not in _visited:
145
+ inherited = find_falcon_responders(
146
+ content, base, _visited | {class_name}
147
+ )
148
+ for m in inherited:
149
+ if m not in methods:
150
+ methods.append(m)
131
151
  return methods
132
152
 
133
153
 
@@ -41,6 +41,11 @@ _MAX_CLUSTER_DEPTH = 4
41
41
  # docs/planner_tuning.md.
42
42
  _MERGE_JACCARD = 0.60
43
43
 
44
+ # Two clusters whose cross-call ratio exceeds this threshold merge even when
45
+ # their file-Jaccard is low. Prevents tightly coupled micro-clusters from
46
+ # getting separate doc pages.
47
+ _CROSS_EDGE_DENSITY = 0.35
48
+
44
49
 
45
50
  @dataclass
46
51
  class TopologyCluster:
@@ -124,8 +129,16 @@ def build_topology_map(scan: "RepoScan") -> TopologyMap:
124
129
  }
125
130
 
126
131
  # ── 3. Foundational files ─────────────────────────────────────────────
132
+ # Augment call indegree with import indegree so that heavily-imported files
133
+ # (models.py, constants.py, exceptions.py) are treated as foundational even
134
+ # when they have few direct call-graph edges.
135
+ import_indegree, file_imports = _build_import_maps(scan, repo_files)
136
+ effective_indegree = {
137
+ f: max(file_indegree.get(f, 0), import_indegree.get(f, 0))
138
+ for f in repo_files
139
+ }
127
140
  threshold = max(3, int(len(repo_files) * _FOUNDATIONAL_FRACTION))
128
- foundational_set = {f for f, deg in file_indegree.items() if deg >= threshold}
141
+ foundational_set = {f for f, deg in effective_indegree.items() if deg >= threshold}
129
142
 
130
143
  # ── 4. Entry-point files ──────────────────────────────────────────────
131
144
  entry_point_files: set[str] = set()
@@ -211,7 +224,9 @@ def build_topology_map(scan: "RepoScan") -> TopologyMap:
211
224
  if f not in file_cluster_id and f not in foundational_set
212
225
  ]
213
226
  for f in sorted(unassigned):
214
- best_cid = _best_cluster_for_orphan(f, file_calls, file_called_by, proto)
227
+ best_cid = _best_cluster_for_orphan(
228
+ f, file_calls, file_called_by, proto, file_imports
229
+ )
215
230
  if best_cid:
216
231
  file_cluster_id[f] = best_cid
217
232
  proto[best_cid].add(f)
@@ -221,7 +236,7 @@ def build_topology_map(scan: "RepoScan") -> TopologyMap:
221
236
  proto[fallback].add(f)
222
237
 
223
238
  # ── 7. Merge high-overlap clusters ───────────────────────────────────
224
- merged = _merge_proto_clusters(proto, file_cluster_id)
239
+ merged = _merge_proto_clusters(proto, file_cluster_id, file_calls)
225
240
 
226
241
  # ── 8. Build TopologyCluster objects ─────────────────────────────────
227
242
  clusters: list[TopologyCluster] = []
@@ -313,10 +328,12 @@ def _empty_map() -> TopologyMap:
313
328
  def _merge_proto_clusters(
314
329
  proto: dict[str, set[str]],
315
330
  file_cluster_id: dict[str, str],
331
+ file_calls: dict[str, set[str]] | None = None,
316
332
  ) -> dict[str, set[str]]:
317
- """Merge clusters whose file Jaccard overlap exceeds _MERGE_JACCARD."""
333
+ """Merge clusters whose file Jaccard overlap or cross-call density is high."""
318
334
  cluster_ids = list(proto.keys())
319
335
  parent: dict[str, str] = {}
336
+ _fc = file_calls or {}
320
337
 
321
338
  def find(cid: str) -> str:
322
339
  while cid in parent:
@@ -347,6 +364,12 @@ def _merge_proto_clusters(
347
364
  jaccard = len(si & sj) / len(si | sj)
348
365
  if jaccard >= _MERGE_JACCARD:
349
366
  union(ci, cj)
367
+ continue
368
+ # Also merge when cross-call traffic between the two clusters is dense
369
+ cross = sum(1 for f in si for c in _fc.get(f, set()) if c in sj)
370
+ cross += sum(1 for f in sj for c in _fc.get(f, set()) if c in si)
371
+ if cross and cross / min(len(si), len(sj)) >= _CROSS_EDGE_DENSITY:
372
+ union(ci, cj)
350
373
 
351
374
  # Re-assign file_cluster_id to canonical roots
352
375
  for f in list(file_cluster_id.keys()):
@@ -355,14 +378,79 @@ def _merge_proto_clusters(
355
378
  return {find(cid): files for cid, files in proto.items() if files}
356
379
 
357
380
 
381
+ def _build_import_maps(
382
+ scan: "RepoScan",
383
+ repo_files: set[str],
384
+ ) -> tuple[dict[str, int], dict[str, set[str]]]:
385
+ """Build import indegree and file→imported-files adjacency in one pass.
386
+
387
+ Resolves relative and absolute imports to repo file paths; ignores
388
+ third-party/stdlib. Returns (indegree, file_imports) where:
389
+ - indegree[f] = number of distinct repo files that import f
390
+ - file_imports[f] = set of repo files that f imports
391
+ """
392
+ indegree: dict[str, int] = defaultdict(int)
393
+ file_imports: dict[str, set[str]] = defaultdict(set)
394
+
395
+ _suffix_index: dict[str, list[str]] = defaultdict(list)
396
+ for rp in repo_files:
397
+ no_ext = re.sub(r"\.[^/]+$", "", rp)
398
+ _suffix_index[no_ext].append(rp)
399
+ _suffix_index[no_ext.replace("/", ".")].append(rp)
400
+
401
+ import_re = re.compile(
402
+ r"^(?:from\s+([\w.]+)\s+import|import\s+([\w.]+))", re.MULTILINE
403
+ )
404
+
405
+ for src_path, pf in scan.parsed_files.items():
406
+ if src_path not in repo_files:
407
+ continue
408
+ pkg_dir = src_path.rsplit("/", 1)[0] if "/" in src_path else ""
409
+
410
+ for imp_str in pf.imports:
411
+ m = import_re.match(imp_str.strip())
412
+ if not m:
413
+ continue
414
+ module = (m.group(1) or m.group(2) or "").strip()
415
+ if not module:
416
+ continue
417
+
418
+ candidates: list[str] = []
419
+
420
+ if module.startswith("."):
421
+ dots = len(module) - len(module.lstrip("."))
422
+ rel_mod = module[dots:].replace(".", "/")
423
+ base = pkg_dir
424
+ for _ in range(dots - 1):
425
+ base = base.rsplit("/", 1)[0] if "/" in base else ""
426
+ rel_path = f"{base}/{rel_mod}".lstrip("/") if rel_mod else base
427
+ for ext in (".py", "/__init__.py"):
428
+ candidates.append(rel_path + ext)
429
+ else:
430
+ as_path = module.replace(".", "/")
431
+ for ext in (".py", "/__init__.py"):
432
+ candidates.append(as_path + ext)
433
+ candidates.extend(_suffix_index.get(module, []))
434
+ candidates.extend(_suffix_index.get(as_path, []))
435
+
436
+ for candidate in candidates:
437
+ if candidate in repo_files and candidate != src_path:
438
+ indegree[candidate] += 1
439
+ file_imports[src_path].add(candidate)
440
+ break
441
+
442
+ return dict(indegree), dict(file_imports)
443
+
444
+
358
445
  def _best_cluster_for_orphan(
359
446
  file_path: str,
360
447
  file_calls: dict[str, set[str]],
361
448
  file_called_by: dict[str, set[str]],
362
449
  proto: dict[str, set[str]],
450
+ import_targets: dict[str, set[str]] | None = None,
363
451
  ) -> str | None:
364
- """Find the cluster an unassigned file has the most call relationships with."""
365
- scores: dict[str, int] = defaultdict(int)
452
+ """Find the cluster an unassigned file has the most relationships with."""
453
+ scores: dict[str, float] = defaultdict(float)
366
454
  cluster_file_index: dict[str, str] = {}
367
455
  for cid, files in proto.items():
368
456
  for f in files:
@@ -378,6 +466,12 @@ def _best_cluster_for_orphan(
378
466
  if cid:
379
467
  scores[cid] += 1
380
468
 
469
+ # Import edges provide a weaker signal than call edges
470
+ for imp_target in (import_targets or {}).get(file_path, set()):
471
+ cid = cluster_file_index.get(imp_target)
472
+ if cid:
473
+ scores[cid] += 0.5
474
+
381
475
  if not scores:
382
476
  return None
383
477
  return max(scores, key=lambda c: scores[c])
@@ -1,4 +1,3 @@
1
1
  from .next_builder import build_next_from_plan
2
- from .mkdocs_builder import build_mkdocs_from_plan # kept for compatibility
3
2
  from .mdx_utils import _first_mdx_heading, _split_leading_frontmatter, _frontmatter_has_yaml_fields, _ensure_md_frontmatter
4
3
  from .common import *
@@ -1,8 +1,10 @@
1
1
  /** @type {import('next').NextConfig} */
2
2
  const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
3
+ const isProd = process.env.NODE_ENV === 'production';
3
4
 
4
5
  const config = {
5
- output: 'export',
6
+ // Static export only for production builds — dev mode needs dynamic rendering
7
+ ...(isProd ? { output: 'export' } : {}),
6
8
  basePath,
7
9
  assetPrefix: basePath || undefined,
8
10
  images: { unoptimized: true },
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepdoc
3
- Version: 0.4.1
3
+ Version: 0.4.2
4
4
  Summary: Auto-generate beautiful docs from any codebase
5
5
  Author: Pranav Kumar
6
6
  License: MIT
@@ -109,7 +109,6 @@ deepdoc/site/__init__.py
109
109
  deepdoc/site/builder/__init__.py
110
110
  deepdoc/site/builder/common.py
111
111
  deepdoc/site/builder/mdx_utils.py
112
- deepdoc/site/builder/mkdocs_builder.py
113
112
  deepdoc/site/builder/next_builder.py
114
113
  deepdoc/site/builder/next_template/.gitignore
115
114
  deepdoc/site/builder/next_template/deepdoc.config.json
@@ -156,7 +155,6 @@ tests/test_internal_docs_metadata.py
156
155
  tests/test_litellm_compat.py
157
156
  tests/test_llm_json_utils.py
158
157
  tests/test_llm_retry.py
159
- tests/test_mkdocs_builder.py
160
158
  tests/test_next_builder.py
161
159
  tests/test_parallel_pipeline.py
162
160
  tests/test_parser_ranges.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deepdoc"
7
- version = "0.4.1"
7
+ version = "0.4.2"
8
8
  description = "Auto-generate beautiful docs from any codebase"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -2368,9 +2368,9 @@ def test_system_prompt_contains_project_name(tmp_path: Path) -> None:
2368
2368
  prompt = service._system_prompt()
2369
2369
 
2370
2370
  assert "MyProject" in prompt
2371
- assert "file path and line range" in prompt
2372
- assert "Sources" in prompt
2373
- assert "## Summary" in prompt
2371
+ assert "evidence" in prompt.lower()
2372
+ assert "cite" in prompt.lower()
2373
+ assert "summary" in prompt.lower()
2374
2374
 
2375
2375
 
2376
2376
  def test_query_service_passes_loaded_indexes_into_similarity_search(
@@ -263,6 +263,31 @@ app.add_route('/login', Login())
263
263
  assert endpoint.middleware == ["AuthMiddleware", "TraceMiddleware"]
264
264
 
265
265
 
266
+ def test_falcon_inherited_responders_from_base_class():
267
+ content = """
268
+ import falcon
269
+
270
+ class BaseResource:
271
+ def on_get(self, req, res):
272
+ pass
273
+ def on_delete(self, req, res):
274
+ pass
275
+
276
+ class UserResource(BaseResource):
277
+ def on_post(self, req, res):
278
+ pass
279
+
280
+ app = falcon.App()
281
+ app.add_route('/users', UserResource())
282
+ """
283
+ endpoints = detect_endpoints(Path("app.py"), content, "python")
284
+ user_endpoints = [ep for ep in endpoints if ep.path == "/users"]
285
+ methods = {ep.method for ep in user_endpoints}
286
+ assert "GET" in methods, "inherited on_get not detected"
287
+ assert "POST" in methods, "own on_post not detected"
288
+ assert "DELETE" in methods, "inherited on_delete not detected"
289
+
290
+
266
291
  def test_scan_repo_detects_vue_projects_and_sfc_signals(tmp_path):
267
292
  repo_root = tmp_path / "vue-app"
268
293
  component_dir = repo_root / "src" / "components"
@@ -26,7 +26,6 @@ from deepdoc.planner.nav_shaping import _shape_plan_nav
26
26
  from deepdoc.planner.specializations import _ensure_database_runtime_and_interface_buckets
27
27
  from deepdoc.planner.topology import TopologyCluster, TopologyMap
28
28
  from deepdoc.prompts import PROMPT_STYLE_TEMPLATES
29
- from deepdoc.site.builder import build_mkdocs_from_plan
30
29
 
31
30
 
32
31
  def _make_scan(
@@ -1228,52 +1227,6 @@ def test_validate_coverage_prefers_semantic_attachment_over_module_bucket() -> N
1228
1227
  )
1229
1228
 
1230
1229
 
1231
- def test_recursive_nav_builder_supports_three_levels(tmp_path: Path) -> None:
1232
- repo_root = tmp_path / "repo"
1233
- repo_root.mkdir()
1234
- output_dir = repo_root / "docs"
1235
- output_dir.mkdir()
1236
-
1237
- overview = DocBucket(
1238
- bucket_type="system",
1239
- title="Overview",
1240
- slug="overview",
1241
- section="Overview",
1242
- description="Overview page",
1243
- generation_hints={"is_introduction_page": True},
1244
- )
1245
- flash = DocBucket(
1246
- bucket_type="architecture_component",
1247
- title="Flash Attention",
1248
- slug="flash-attention",
1249
- section="Model Architecture > Attention > Flash Attention",
1250
- description="Flash attention internals",
1251
- )
1252
- plan = DocPlan(
1253
- buckets=[overview, flash],
1254
- nav_structure={
1255
- "Model Architecture > Attention > Flash Attention": ["flash-attention"]
1256
- },
1257
- skipped_files=[],
1258
- )
1259
-
1260
- (output_dir / "flash-attention.md").write_text(
1261
- "# Flash Attention\n", encoding="utf-8"
1262
- )
1263
-
1264
- build_mkdocs_from_plan(
1265
- repo_root,
1266
- output_dir,
1267
- {"project_name": "Demo", "site": {"repo_url": ""}},
1268
- plan,
1269
- has_openapi=False,
1270
- )
1271
-
1272
- mkdocs_yml = (repo_root / "site" / "mkdocs.yml").read_text(encoding="utf-8")
1273
- # Nested " > " sections become nested nav groups in mkdocs.yml.
1274
- assert "Model Architecture:" in mkdocs_yml
1275
- assert "Attention:" in mkdocs_yml
1276
- assert "Flash Attention: flash-attention.md" in mkdocs_yml
1277
1230
 
1278
1231
 
1279
1232
  def test_prompt_templates_and_prompts_include_new_granularity_support() -> None: