deepdoc 3.0.0__tar.gz → 3.3.0__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 (154) hide show
  1. {deepdoc-3.0.0 → deepdoc-3.3.0}/PKG-INFO +9 -5
  2. {deepdoc-3.0.0 → deepdoc-3.3.0}/README.md +3 -4
  3. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/__init__.py +1 -1
  4. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/answer_mixin.py +3 -3
  5. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/deep_research.py +33 -3
  6. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/retrieval_mixin.py +3 -1
  7. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/routes.py +13 -86
  8. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/scaffold.py +4 -8
  9. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/service.py +39 -98
  10. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/settings.py +5 -6
  11. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/cli.py +5 -5
  12. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/config.py +5 -6
  13. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/generator/__init__.py +0 -2
  14. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/generator/generation.py +0 -8
  15. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/generator/post_processors.py +0 -96
  16. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/pipeline_v2.py +7 -9
  17. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/planner/heuristics.py +0 -65
  18. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/planner/nav_shaping.py +18 -47
  19. deepdoc-3.3.0/deepdoc/site/builder/mkdocs_builder.py +2148 -0
  20. deepdoc-3.3.0/deepdoc/site/builder/vendor/highlight.min.js +1232 -0
  21. deepdoc-3.3.0/deepdoc/site/builder/vendor/hljs-theme.css +10 -0
  22. deepdoc-3.3.0/deepdoc/site/builder/vendor/markdown-it.min.js +2 -0
  23. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc.egg-info/PKG-INFO +9 -5
  24. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc.egg-info/SOURCES.txt +3 -0
  25. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc.egg-info/requires.txt +6 -0
  26. {deepdoc-3.0.0 → deepdoc-3.3.0}/pyproject.toml +12 -1
  27. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_chatbot_config.py +6 -6
  28. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_chatbot_query.py +20 -18
  29. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_cli_serve.py +1 -1
  30. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_mkdocs_builder.py +57 -49
  31. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_planner_granularity.py +137 -3
  32. deepdoc-3.0.0/deepdoc/site/builder/mkdocs_builder.py +0 -560
  33. {deepdoc-3.0.0 → deepdoc-3.3.0}/LICENSE +0 -0
  34. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/__main__.py +0 -0
  35. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/benchmark_v2.py +0 -0
  36. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/call_graph.py +0 -0
  37. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/changelog_writer.py +0 -0
  38. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/__init__.py +0 -0
  39. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/chunker.py +0 -0
  40. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/constants.py +0 -0
  41. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/docs_summary.py +0 -0
  42. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/embeddings.py +0 -0
  43. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/indexer.py +0 -0
  44. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/linking.py +0 -0
  45. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/live_fallback_mixin.py +0 -0
  46. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/persistence.py +0 -0
  47. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/providers.py +0 -0
  48. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/source_archive.py +0 -0
  49. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/symbol_index.py +0 -0
  50. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/chatbot/types.py +0 -0
  51. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/generator/consistency.py +0 -0
  52. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/generator/evidence.py +0 -0
  53. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/generator/validation.py +0 -0
  54. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/llm/__init__.py +0 -0
  55. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/llm/client.py +0 -0
  56. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/llm/json_utils.py +0 -0
  57. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/llm/litellm_compat.py +0 -0
  58. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/llm/retry.py +0 -0
  59. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/manifest.py +0 -0
  60. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/openapi.py +0 -0
  61. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/__init__.py +0 -0
  62. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/api_detector.py +0 -0
  63. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/base.py +0 -0
  64. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/go_parser.py +0 -0
  65. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/js_ts_parser.py +0 -0
  66. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/php_parser.py +0 -0
  67. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/python_parser.py +0 -0
  68. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/registry.py +0 -0
  69. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/routes/__init__.py +0 -0
  70. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/routes/base.py +0 -0
  71. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/routes/common.py +0 -0
  72. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/routes/detector.py +0 -0
  73. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/routes/django.py +0 -0
  74. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/routes/express.py +0 -0
  75. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/routes/falcon.py +0 -0
  76. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/routes/fastify.py +0 -0
  77. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/routes/go.py +0 -0
  78. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/routes/js_shared.py +0 -0
  79. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/routes/laravel.py +0 -0
  80. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/routes/nestjs.py +0 -0
  81. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/routes/python_shared.py +0 -0
  82. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/routes/registry.py +0 -0
  83. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/routes/repo_resolver.py +0 -0
  84. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/parser/vue_parser.py +0 -0
  85. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/persistence_v2.py +0 -0
  86. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/planner/__init__.py +0 -0
  87. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/planner/bucket_injection.py +0 -0
  88. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/planner/bucket_refinement.py +0 -0
  89. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/planner/common.py +0 -0
  90. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/planner/endpoint_refs.py +0 -0
  91. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/planner/engine.py +0 -0
  92. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/planner/flow_candidates.py +0 -0
  93. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/planner/specializations.py +0 -0
  94. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/planner/topology.py +0 -0
  95. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/planner/utils.py +0 -0
  96. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/prompts/__init__.py +0 -0
  97. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/prompts/bucket_types.py +0 -0
  98. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/prompts/page_types.py +0 -0
  99. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/prompts/selectors.py +0 -0
  100. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/prompts/system.py +0 -0
  101. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/prompts/update.py +0 -0
  102. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/py.typed +0 -0
  103. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/scanner/__init__.py +0 -0
  104. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/scanner/artifacts.py +0 -0
  105. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/scanner/clustering.py +0 -0
  106. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/scanner/common.py +0 -0
  107. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/scanner/database.py +0 -0
  108. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/scanner/endpoints.py +0 -0
  109. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/scanner/integrations.py +0 -0
  110. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/scanner/runtime.py +0 -0
  111. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/scanner/utils.py +0 -0
  112. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/site/__init__.py +0 -0
  113. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/site/builder/__init__.py +0 -0
  114. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/site/builder/common.py +0 -0
  115. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/site/builder/mdx_utils.py +0 -0
  116. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/smart_update_v2.py +0 -0
  117. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/source_metadata.py +0 -0
  118. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/updater_v2.py +0 -0
  119. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc/v2_models.py +0 -0
  120. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc.egg-info/dependency_links.txt +0 -0
  121. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc.egg-info/entry_points.txt +0 -0
  122. {deepdoc-3.0.0 → deepdoc-3.3.0}/deepdoc.egg-info/top_level.txt +0 -0
  123. {deepdoc-3.0.0 → deepdoc-3.3.0}/setup.cfg +0 -0
  124. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_benchmark_scorecard.py +0 -0
  125. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_call_graph.py +0 -0
  126. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_changelog.py +0 -0
  127. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_chatbot_embeddings.py +0 -0
  128. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_chatbot_eval.py +0 -0
  129. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_chatbot_index.py +0 -0
  130. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_chatbot_persistence.py +0 -0
  131. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_chatbot_providers.py +0 -0
  132. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_chatbot_relationship.py +0 -0
  133. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_chatbot_scaffold.py +0 -0
  134. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_chatbot_source_archive.py +0 -0
  135. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_classify.py +0 -0
  136. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_cli_generate.py +0 -0
  137. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_cli_update.py +0 -0
  138. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_consistency_pass.py +0 -0
  139. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_flow_candidates.py +0 -0
  140. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_framework_fixtures.py +0 -0
  141. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_framework_support.py +0 -0
  142. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_generation_evidence.py +0 -0
  143. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_internal_docs_metadata.py +0 -0
  144. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_litellm_compat.py +0 -0
  145. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_llm_json_utils.py +0 -0
  146. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_llm_retry.py +0 -0
  147. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_parallel_pipeline.py +0 -0
  148. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_parser_ranges.py +0 -0
  149. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_planner_consolidation.py +0 -0
  150. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_route_registry.py +0 -0
  151. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_runtime_scan.py +0 -0
  152. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_smart_update.py +0 -0
  153. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_stale.py +0 -0
  154. {deepdoc-3.0.0 → deepdoc-3.3.0}/tests/test_state.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepdoc
3
- Version: 3.0.0
3
+ Version: 3.3.0
4
4
  Summary: Auto-generate beautiful docs from any codebase
5
5
  Author: Pranav Kumar
6
6
  License: MIT
@@ -35,6 +35,11 @@ Requires-Dist: fastapi>=0.115; extra == "chatbot"
35
35
  Requires-Dist: uvicorn>=0.30; extra == "chatbot"
36
36
  Requires-Dist: httpx>=0.27; extra == "chatbot"
37
37
  Requires-Dist: fastembed>=0.3.0; extra == "chatbot"
38
+ Provides-Extra: site
39
+ Requires-Dist: mkdocs>=1.6; extra == "site"
40
+ Requires-Dist: mkdocs-material>=9.5; extra == "site"
41
+ Requires-Dist: pymdown-extensions>=10.0; extra == "site"
42
+ Requires-Dist: mkdocs-swagger-ui-tag>=0.6; extra == "site"
38
43
  Dynamic: license-file
39
44
 
40
45
  # DeepDoc
@@ -129,14 +134,13 @@ pip install click litellm gitpython rich pyyaml jinja2
129
134
  pip install -e . --no-deps
130
135
  ```
131
136
 
132
- To preview or deploy the generated site you also need **MkDocs Material** (pure Python — no Node.js):
137
+ To preview or deploy the generated site you also need **MkDocs Material** (pure Python — no Node.js). Install the `site` extra:
133
138
 
134
139
  ```bash
135
- pip install mkdocs-material
136
- pip install mkdocs-swagger-ui-tag # only when your repo has an OpenAPI/Swagger spec
140
+ pip install 'deepdoc[site]' # mkdocs-material + pymdown-extensions + mkdocs-swagger-ui-tag
137
141
  ```
138
142
 
139
- `deepdoc serve` / `deepdoc deploy` will tell you the exact `pip install` command if MkDocs is missing.
143
+ `deepdoc serve` / `deepdoc deploy` will tell you to run `pip install 'deepdoc[site]'` if MkDocs is missing.
140
144
 
141
145
  ### Verify installation
142
146
 
@@ -90,14 +90,13 @@ pip install click litellm gitpython rich pyyaml jinja2
90
90
  pip install -e . --no-deps
91
91
  ```
92
92
 
93
- To preview or deploy the generated site you also need **MkDocs Material** (pure Python — no Node.js):
93
+ To preview or deploy the generated site you also need **MkDocs Material** (pure Python — no Node.js). Install the `site` extra:
94
94
 
95
95
  ```bash
96
- pip install mkdocs-material
97
- pip install mkdocs-swagger-ui-tag # only when your repo has an OpenAPI/Swagger spec
96
+ pip install 'deepdoc[site]' # mkdocs-material + pymdown-extensions + mkdocs-swagger-ui-tag
98
97
  ```
99
98
 
100
- `deepdoc serve` / `deepdoc deploy` will tell you the exact `pip install` command if MkDocs is missing.
99
+ `deepdoc serve` / `deepdoc deploy` will tell you to run `pip install 'deepdoc[site]'` if MkDocs is missing.
101
100
 
102
101
  ### Verify installation
103
102
 
@@ -1,3 +1,3 @@
1
1
  """DeepDoc — Auto-generate beautiful docs from any codebase."""
2
2
 
3
- __version__ = "3.0.0"
3
+ __version__ = "3.3.0"
@@ -668,15 +668,15 @@ class AnswerMixin:
668
668
  reason=str(row.get("reason", "") or ""),
669
669
  )
670
670
  )
671
- if mode == "code_deep" and not evidence:
672
- diagnostics.warnings.append("No source/config evidence was available for Code Deep.")
671
+ if mode == "deep" and not evidence:
672
+ diagnostics.warnings.append("No source/config evidence was available for deep mode.")
673
673
  return evidence, diagnostics
674
674
 
675
675
  def _evidence_role(self, row: dict[str, Any], *, source_kind: str, mode: str) -> str:
676
676
  reason = str(row.get("reason", "") or "")
677
677
  if source_kind == "config" or row.get("artifact_type"):
678
678
  return "config"
679
- if reason in {"investigation_step", "research_step"} or mode == "code_deep":
679
+ if reason in {"investigation_step", "research_step"} or mode == "deep":
680
680
  return "implementation"
681
681
  if reason == "mentioned_source":
682
682
  return "supporting"
@@ -426,7 +426,7 @@ class DeepResearcher:
426
426
  step: int,
427
427
  ) -> ResearchStep:
428
428
  """Run a Tool-Using ReAct loop for a single sub-question."""
429
- max_iterations = 3
429
+ max_iterations = 5
430
430
  chunks = self._retrieve_for_question(
431
431
  question,
432
432
  history,
@@ -468,8 +468,9 @@ class DeepResearcher:
468
468
  "You have access to the following initial evidence chunks from the codebase.\n\n"
469
469
  "If the evidence is sufficient, provide your final answer in plain text.\n"
470
470
  "If you need to explore the codebase further, you can use the following tools by outputting a JSON object and NOTHING else:\n"
471
- '1. read_file: `{"action": "read_file", "path": "file/path.py", "start": 10, "end": 50}`\n'
472
- '2. grep: `{"action": "grep", "pattern": "def main"}`\n\n'
471
+ '1. search: `{"action": "search", "query": "what you want to find"}` — semantic search over the code index\n'
472
+ '2. read_file: `{"action": "read_file", "path": "file/path.py", "start": 10, "end": 50}`\n'
473
+ '3. grep: `{"action": "grep", "pattern": "def main"}`\n\n'
473
474
  "Answer ONLY based on evidence. Prefer a detailed, implementation-level walkthrough.\n\n"
474
475
  "## STRICT GROUNDING RULES\n"
475
476
  "- Do NOT invent, fabricate, or hallucinate any code, function names, class names, "
@@ -632,6 +633,35 @@ class DeepResearcher:
632
633
  results.append("... [truncated due to length]")
633
634
  return "\n".join(results)
634
635
 
636
+ elif action == "search":
637
+ query = str(tool_call.get("query", "")).strip()
638
+ if not query or len(query) < 3:
639
+ return "Error: search requires a non-empty 'query' (min 3 characters)."
640
+ try:
641
+ retrieve_context = getattr(self.service, "retrieve_context", None)
642
+ if not callable(retrieve_context):
643
+ return "Error: search tool unavailable."
644
+ context = retrieve_context(query, [], mode="fast")
645
+ code_hits = context.get("code_hits", [])[:8]
646
+ if not code_hits:
647
+ return f"No code matches found for '{query}'."
648
+ parts = []
649
+ for i, hit in enumerate(code_hits, 1):
650
+ record = getattr(hit, "record", hit)
651
+ source = (
652
+ getattr(record, "file_path", None)
653
+ or getattr(record, "doc_path", None)
654
+ or "unknown"
655
+ )
656
+ text = getattr(record, "text", "")[:1600]
657
+ score = round(float(getattr(hit, "score", 0.0)), 3)
658
+ parts.append(f"[{i}] {source} (score={score}):\n{text}")
659
+ if source not in sources_used:
660
+ sources_used.append(source)
661
+ return "\n\n".join(parts)
662
+ except Exception as e:
663
+ return f"Error: Search failed - {e}"
664
+
635
665
  return f"Error: Unknown action '{action}'"
636
666
 
637
667
  def _synthesise(
@@ -780,8 +780,10 @@ class RetrievalMixin:
780
780
  source_kind = record.source_kind or ""
781
781
  if source_kind == "product":
782
782
  priority += 0.8 if supporting_requested else 2.5
783
- elif source_kind in {"config", "docs", "ops", "tooling"}:
783
+ elif source_kind in {"config", "ops", "tooling"}:
784
784
  priority += 1.2
785
+ elif source_kind == "docs":
786
+ priority += 0.2
785
787
  elif source_kind in {"test", "fixture", "example", "generated"}:
786
788
  priority += 4.5 if supporting_requested else -1.0
787
789
 
@@ -27,14 +27,8 @@ class QueryRequest(BaseModel):
27
27
  return v
28
28
 
29
29
 
30
- class DeepResearchRequest(QueryRequest):
31
- """Incoming deep-research payload."""
32
-
33
- max_rounds: int = Field(default=3, ge=1, le=6)
34
-
35
-
36
- class CodeDeepRequest(QueryRequest):
37
- """Incoming code-deep payload."""
30
+ class DeepRequest(QueryRequest):
31
+ """Incoming deep mode payload."""
38
32
 
39
33
  max_rounds: int = Field(default=4, ge=1, le=8)
40
34
 
@@ -73,10 +67,10 @@ def create_fastapi_app(repo_root: Path, cfg: dict[str, Any]):
73
67
  },
74
68
  )
75
69
 
76
- @app.post("/deep-research")
77
- def deep_research(request: DeepResearchRequest = Body(...)) -> dict[str, Any]:
70
+ @app.post("/deep")
71
+ def deep(request: DeepRequest = Body(...)) -> dict[str, Any]:
78
72
  try:
79
- return service.deep_research(
73
+ return service.deep(
80
74
  request.question,
81
75
  request.history,
82
76
  max_rounds=request.max_rounds,
@@ -85,7 +79,7 @@ def create_fastapi_app(repo_root: Path, cfg: dict[str, Any]):
85
79
  return JSONResponse(
86
80
  status_code=500,
87
81
  content={
88
- "error": "chatbot_deep_research_failed",
82
+ "error": "chatbot_deep_failed",
89
83
  "detail": str(exc),
90
84
  },
91
85
  )
@@ -138,96 +132,29 @@ def create_fastapi_app(repo_root: Path, cfg: dict[str, Any]):
138
132
  },
139
133
  )
140
134
 
141
- @app.post("/deep-research/stream")
142
- def deep_research_stream(request: DeepResearchRequest = Body(...)):
143
- tokens: queue.Queue[tuple[str, dict[str, Any]] | None] = queue.Queue()
135
+ @app.post("/deep/stream")
136
+ def deep_stream(request: DeepRequest = Body(...)):
137
+ events: queue.Queue[tuple[str, dict[str, Any]] | None] = queue.Queue()
144
138
 
145
139
  def on_token(text: str) -> None:
146
- tokens.put(("token", {"text": text}))
147
-
148
- def run() -> None:
149
- try:
150
- result = service.deep_research(
151
- request.question,
152
- request.history,
153
- max_rounds=request.max_rounds,
154
- token_callback=on_token,
155
- )
156
- tokens.put(("result", result))
157
- except Exception as exc:
158
- tokens.put(("error", {"error": "chatbot_deep_research_failed", "detail": str(exc)}))
159
- finally:
160
- tokens.put(("done", {"status": "done"}))
161
- tokens.put(None)
162
-
163
- thread = threading.Thread(target=run, daemon=True)
164
- thread.start()
165
-
166
- def event_stream():
167
- while True:
168
- try:
169
- item = tokens.get(timeout=30)
170
- except queue.Empty:
171
- yield "event: ping\ndata: {}\n\n"
172
- continue
173
- if item is None:
174
- break
175
- event_name, payload = item
176
- yield f"event: {event_name}\n"
177
- yield f"data: {json.dumps(payload)}\n\n"
178
-
179
- return StreamingResponse(
180
- event_stream(),
181
- media_type="text/event-stream",
182
- headers={
183
- "Cache-Control": "no-cache",
184
- "Connection": "keep-alive",
185
- "X-Accel-Buffering": "no",
186
- },
187
- )
188
-
189
- @app.post("/code-deep")
190
- def code_deep(request: CodeDeepRequest = Body(...)) -> dict[str, Any]:
191
- try:
192
- return service.code_deep(
193
- request.question,
194
- request.history,
195
- max_rounds=request.max_rounds,
196
- )
197
- except Exception as exc:
198
- return JSONResponse(
199
- status_code=500,
200
- content={
201
- "error": "chatbot_code_deep_failed",
202
- "detail": str(exc),
203
- },
204
- )
205
-
206
- @app.post("/code-deep/stream")
207
- def code_deep_stream(request: CodeDeepRequest = Body(...)):
208
- events: queue.Queue[tuple[str, dict[str, Any]] | None] = queue.Queue()
140
+ events.put(("token", {"text": text}))
209
141
 
210
142
  def emit(event: dict[str, Any]) -> None:
211
143
  events.put(("trace", event))
212
144
 
213
145
  def run() -> None:
214
146
  try:
215
- result = service.code_deep(
147
+ result = service.deep(
216
148
  request.question,
217
149
  request.history,
218
150
  max_rounds=request.max_rounds,
219
151
  trace_callback=emit,
152
+ token_callback=on_token,
220
153
  )
221
154
  events.put(("result", result))
222
155
  except Exception as exc:
223
156
  events.put(
224
- (
225
- "error",
226
- {
227
- "error": "chatbot_code_deep_failed",
228
- "detail": str(exc),
229
- },
230
- )
157
+ ("error", {"error": "chatbot_deep_failed", "detail": str(exc)})
231
158
  )
232
159
  finally:
233
160
  events.put(("done", {"status": "done"}))
@@ -63,16 +63,12 @@ def _app_py() -> str:
63
63
  def _query():
64
64
  return JSONResponse(status_code=503, content={"error": "startup_failed", "detail": _detail})
65
65
 
66
- @app.post("/deep-research")
67
- def _deep_research():
66
+ @app.post("/deep")
67
+ def _deep():
68
68
  return JSONResponse(status_code=503, content={"error": "startup_failed", "detail": _detail})
69
69
 
70
- @app.post("/code-deep")
71
- def _code_deep():
72
- return JSONResponse(status_code=503, content={"error": "startup_failed", "detail": _detail})
73
-
74
- @app.post("/code-deep/stream")
75
- def _code_deep_stream():
70
+ @app.post("/deep/stream")
71
+ def _deep_stream():
76
72
  return JSONResponse(status_code=503, content={"error": "startup_failed", "detail": _detail})
77
73
  """
78
74
  )
@@ -49,7 +49,7 @@ from .constants import (
49
49
  )
50
50
  from .live_fallback_mixin import LiveFallbackMixin
51
51
  from .retrieval_mixin import RetrievalMixin
52
- from .routes import create_fastapi_app, QueryRequest, DeepResearchRequest, CodeDeepRequest
52
+ from .routes import create_fastapi_app, QueryRequest, DeepRequest
53
53
 
54
54
 
55
55
  class ChatbotQueryService(RetrievalMixin, AnswerMixin, LiveFallbackMixin):
@@ -106,7 +106,7 @@ class ChatbotQueryService(RetrievalMixin, AnswerMixin, LiveFallbackMixin):
106
106
  ):
107
107
  logger.warning(
108
108
  "Source archive empty or unreadable at %s while indexed corpora "
109
- "exist. Live-fallback, evidence verification, and code-deep file "
109
+ "exist. Live-fallback, evidence verification, and deep mode file "
110
110
  "inventory will be degraded. Run `deepdoc update` to rebuild.",
111
111
  self.index_dir / "source_archive.json.gz",
112
112
  )
@@ -446,46 +446,24 @@ class ChatbotQueryService(RetrievalMixin, AnswerMixin, LiveFallbackMixin):
446
446
  **self._workspace_defaults(),
447
447
  }
448
448
 
449
- def deep_research(
449
+ def deep(
450
450
  self,
451
451
  question: str,
452
452
  history: list[dict[str, str]] | None = None,
453
- max_rounds: int = 3,
453
+ max_rounds: int = 4,
454
454
  *,
455
+ trace_callback: Callable[[dict[str, Any]], None] | None = None,
455
456
  token_callback: "Callable[[str], None] | None" = None,
456
457
  ) -> dict[str, Any]:
457
- """Run a DeepResearch session: decompose retrieve synthesise.
458
-
459
- Returns a ResearchResult with a comprehensive answer and source citations.
460
- Requires an LLM client to be configured (llm= at construction time or via settings).
461
-
462
- Args:
463
- question: The research question to answer.
464
- max_rounds: Maximum number of sub-questions to explore.
458
+ """Run an agentic deep research session over the codebase.
465
459
 
466
- Returns:
467
- ResearchResult with fields: original_question, steps, final_answer, all_sources, confidence.
460
+ Decomposes the question into sub-questions, retrieves code-heavy evidence,
461
+ runs a tool-using ReAct loop (search / read_file / grep) for each, then
462
+ synthesises a comprehensive answer with source citations and a file inventory.
468
463
  """
469
- retrieval_cfg = self.chat_cfg["retrieval"]
470
- _, response = self._run_research_mode(
471
- question,
472
- history,
473
- mode="deep",
474
- max_rounds=max_rounds,
475
- top_k=max(8, retrieval_cfg.get("deep_research_top_k", 10)),
476
- token_callback=token_callback,
477
- )
478
- return response
464
+ from .deep_research import DeepResearcher
479
465
 
480
- def code_deep(
481
- self,
482
- question: str,
483
- history: list[dict[str, str]] | None = None,
484
- max_rounds: int = 4,
485
- *,
486
- trace_callback: Callable[[dict[str, Any]], None] | None = None,
487
- ) -> dict[str, Any]:
488
- retrieval_cfg = self._retrieval_profile("code_deep")
466
+ retrieval_cfg = self._retrieval_profile("deep")
489
467
  trace: list[dict[str, Any]] = []
490
468
 
491
469
  def emit(event: dict[str, Any]) -> None:
@@ -496,56 +474,19 @@ class ChatbotQueryService(RetrievalMixin, AnswerMixin, LiveFallbackMixin):
496
474
  if callable(callback):
497
475
  callback(payload)
498
476
 
499
- result, response = self._run_research_mode(
500
- question,
501
- history,
502
- mode="code_deep",
503
- max_rounds=max_rounds,
504
- top_k=max(
505
- 10,
506
- int(retrieval_cfg.get("code_deep_top_k", retrieval_cfg["top_k_code"])),
507
- ),
508
- trace_callback=emit,
509
- )
510
- response["trace"] = trace
511
- response["response_mode"] = "code_deep"
512
- response["research_mode"] = "code_deep"
513
- response = self._finalize_answer_response(question, response, mode="code_deep")
514
- response["trace"] = trace
515
- response["file_inventory"] = self._collect_file_inventory(
516
- question,
517
- response,
518
- result.all_sources,
519
- retrieval_cfg,
520
- trace,
521
- )
522
- return response
523
-
524
- def _run_research_mode(
525
- self,
526
- question: str,
527
- history: list[dict[str, str]] | None,
528
- *,
529
- mode: str,
530
- max_rounds: int,
531
- top_k: int,
532
- trace_callback: Callable[[dict[str, Any]], None] | None = None,
533
- token_callback: "Callable[[str], None] | None" = None,
534
- ) -> tuple[Any, dict[str, Any]]:
535
- from .deep_research import DeepResearcher
536
-
477
+ top_k = max(10, int(retrieval_cfg.get("deep_top_k_code", retrieval_cfg["top_k_code"])))
537
478
  researcher = DeepResearcher(
538
479
  service=self,
539
480
  llm=self._llm,
540
481
  top_k=top_k,
541
482
  max_rounds=max_rounds,
542
- mode=mode,
543
- trace_callback=trace_callback,
483
+ mode="deep",
484
+ trace_callback=emit,
544
485
  )
545
486
  researcher.synthesis_token_callback = token_callback
546
487
  result = researcher.research(question, history=history or [])
547
- query_mode = "deep" if mode == "deep" else "code_deep"
548
- response = self.query(question, history, mode=query_mode)
488
+
489
+ response = self.query(question, history, mode="deep")
549
490
  response.update(
550
491
  {
551
492
  "answer": result.final_answer,
@@ -554,13 +495,17 @@ class ChatbotQueryService(RetrievalMixin, AnswerMixin, LiveFallbackMixin):
554
495
  sum(step.chunks_used for step in result.steps),
555
496
  ),
556
497
  "confidence": result.confidence,
557
- "research_mode": mode,
498
+ "research_mode": "deep",
558
499
  "research_sources": result.all_sources,
559
- "response_mode": mode,
500
+ "response_mode": "deep",
560
501
  }
561
502
  )
562
- response = self._finalize_answer_response(question, response, mode=mode)
563
- return result, response
503
+ response = self._finalize_answer_response(question, response, mode="deep")
504
+ response["trace"] = trace
505
+ response["file_inventory"] = self._collect_file_inventory(
506
+ question, response, result.all_sources, retrieval_cfg, trace,
507
+ )
508
+ return response
564
509
 
565
510
  def _collect_file_inventory(
566
511
  self,
@@ -570,7 +515,7 @@ class ChatbotQueryService(RetrievalMixin, AnswerMixin, LiveFallbackMixin):
570
515
  retrieval_cfg: dict[str, Any],
571
516
  trace: list[dict[str, Any]],
572
517
  ) -> list[dict[str, Any]]:
573
- limit = max(8, int(retrieval_cfg.get("code_deep_file_inventory_limit", 18)))
518
+ limit = max(8, int(retrieval_cfg.get("deep_file_inventory_limit", 18)))
574
519
  inventory: dict[str, dict[str, Any]] = {}
575
520
 
576
521
  def add_entry(
@@ -696,21 +641,17 @@ class ChatbotQueryService(RetrievalMixin, AnswerMixin, LiveFallbackMixin):
696
641
 
697
642
  def _retrieval_profile(self, mode: str) -> dict[str, Any]:
698
643
  base = deepcopy(self.chat_cfg["retrieval"])
699
- mode_name = mode.strip().lower() if isinstance(mode, str) else "default"
644
+ mode_name = mode.strip().lower() if isinstance(mode, str) else "fast"
645
+
700
646
  if mode_name == "default":
701
647
  return base
702
- if mode_name == "deep":
703
- deep_prompt_chars = base.get("deep_mode_max_prompt_chars")
704
- if isinstance(deep_prompt_chars, int) and deep_prompt_chars > 0:
705
- base["max_prompt_chars"] = deep_prompt_chars
706
- return base
707
648
 
708
- if mode_name == "code_deep":
709
- code_prompt_chars = base.get("code_deep_mode_max_prompt_chars")
710
- if isinstance(code_prompt_chars, int) and code_prompt_chars > 0:
711
- base["max_prompt_chars"] = code_prompt_chars
649
+ if mode_name == "deep":
650
+ prompt_chars = base.get("deep_mode_max_prompt_chars")
651
+ if isinstance(prompt_chars, int) and prompt_chars > 0:
652
+ base["max_prompt_chars"] = prompt_chars
712
653
 
713
- code_top_k = base.get("code_deep_top_k")
654
+ code_top_k = base.get("deep_top_k_code")
714
655
  if isinstance(code_top_k, int) and code_top_k > 0:
715
656
  base["top_k_code"] = max(base.get("top_k_code", code_top_k), code_top_k)
716
657
  base["candidate_top_k_code"] = max(
@@ -718,18 +659,17 @@ class ChatbotQueryService(RetrievalMixin, AnswerMixin, LiveFallbackMixin):
718
659
  code_top_k * 2,
719
660
  )
720
661
 
721
- relationship_top_k = base.get("code_deep_top_k_relationship")
722
- if isinstance(relationship_top_k, int) and relationship_top_k > 0:
662
+ rel_top_k = base.get("deep_top_k_relationship")
663
+ if isinstance(rel_top_k, int) and rel_top_k > 0:
723
664
  base["top_k_relationship"] = max(
724
- base.get("top_k_relationship", relationship_top_k),
725
- relationship_top_k,
665
+ base.get("top_k_relationship", rel_top_k), rel_top_k,
726
666
  )
727
667
  base["candidate_top_k_relationship"] = max(
728
- base.get("candidate_top_k_relationship", relationship_top_k * 2),
729
- relationship_top_k * 2,
668
+ base.get("candidate_top_k_relationship", rel_top_k * 2),
669
+ rel_top_k * 2,
730
670
  )
731
671
 
732
- docs_cap = base.get("code_deep_top_k_docs")
672
+ docs_cap = base.get("deep_top_k_docs")
733
673
  if isinstance(docs_cap, int) and docs_cap > 0:
734
674
  base["top_k_docs"] = min(base.get("top_k_docs", docs_cap), docs_cap)
735
675
 
@@ -739,6 +679,7 @@ class ChatbotQueryService(RetrievalMixin, AnswerMixin, LiveFallbackMixin):
739
679
  base["rerank"] = True
740
680
  return base
741
681
 
682
+ # fast mode (default for any unrecognised mode string)
742
683
  fast_prompt_chars = base.get("fast_mode_max_prompt_chars")
743
684
  if isinstance(fast_prompt_chars, int) and fast_prompt_chars > 0:
744
685
  base["max_prompt_chars"] = fast_prompt_chars
@@ -72,12 +72,11 @@ DEFAULT_CHATBOT_CONFIG: dict[str, Any] = {
72
72
  "fast_mode_use_llm_retrieval_steps": False,
73
73
  "fast_mode_iterative_retrieval": False,
74
74
  "fast_mode_max_prompt_chars": 90000,
75
- "deep_mode_max_prompt_chars": 140000,
76
- "code_deep_mode_max_prompt_chars": 180000,
77
- "code_deep_top_k": 16,
78
- "code_deep_top_k_relationship": 12,
79
- "code_deep_top_k_docs": 4,
80
- "code_deep_file_inventory_limit": 18,
75
+ "deep_mode_max_prompt_chars": 180000,
76
+ "deep_top_k_code": 16,
77
+ "deep_top_k_relationship": 12,
78
+ "deep_top_k_docs": 4,
79
+ "deep_file_inventory_limit": 18,
81
80
  "lexical_retrieval": True,
82
81
  "lexical_candidate_limit": 24,
83
82
  "query_expansion": True,
@@ -974,7 +974,7 @@ def serve(port):
974
974
 
975
975
  \b
976
976
  Run `deepdoc generate` first so the generated MkDocs site and docs exist.
977
- Requires MkDocs Material: pip install mkdocs-material
977
+ Requires the site extra: pip install 'deepdoc[site]'
978
978
  """
979
979
  _load_or_exit()
980
980
  cfg = _load_or_exit()
@@ -1026,7 +1026,7 @@ def serve(port):
1026
1026
  pass
1027
1027
  except FileNotFoundError:
1028
1028
  console.print(
1029
- "[red]mkdocs not found. Install it: [bold]pip install mkdocs-material[/bold][/red]"
1029
+ "[red]mkdocs not found. Install it: [bold]pip install 'deepdoc[site]'[/bold][/red]"
1030
1030
  )
1031
1031
  sys.exit(1)
1032
1032
  finally:
@@ -1107,7 +1107,7 @@ def _deploy():
1107
1107
  console.print("[red]Build failed.[/red]")
1108
1108
  except FileNotFoundError:
1109
1109
  console.print(
1110
- "[red]mkdocs not found. Install it: [bold]pip install mkdocs-material[/bold][/red]"
1110
+ "[red]mkdocs not found. Install it: [bold]pip install 'deepdoc[site]'[/bold][/red]"
1111
1111
  )
1112
1112
  sys.exit(1)
1113
1113
 
@@ -1132,9 +1132,9 @@ def _ensure_mkdocs_installed(cfg: dict) -> None:
1132
1132
  missing.append("mkdocs-swagger-ui-tag")
1133
1133
 
1134
1134
  if missing:
1135
- pkgs = " ".join(dict.fromkeys(missing))
1136
1135
  raise click.ClickException(
1137
- f"MkDocs is not installed. Install it with:\n pip install {pkgs}"
1136
+ "MkDocs is not installed. Install the site extra with:\n"
1137
+ " pip install 'deepdoc[site]'"
1138
1138
  )
1139
1139
 
1140
1140
 
@@ -227,12 +227,11 @@ DEFAULT_CONFIG: dict[str, Any] = {
227
227
  "fast_mode_use_llm_retrieval_steps": False,
228
228
  "fast_mode_iterative_retrieval": False,
229
229
  "fast_mode_max_prompt_chars": 90000,
230
- "deep_mode_max_prompt_chars": 140000,
231
- "code_deep_mode_max_prompt_chars": 180000,
232
- "code_deep_top_k": 16,
233
- "code_deep_top_k_relationship": 12,
234
- "code_deep_top_k_docs": 4,
235
- "code_deep_file_inventory_limit": 18,
230
+ "deep_mode_max_prompt_chars": 180000,
231
+ "deep_top_k_code": 16,
232
+ "deep_top_k_relationship": 12,
233
+ "deep_top_k_docs": 4,
234
+ "deep_file_inventory_limit": 18,
236
235
  "lexical_retrieval": True,
237
236
  "lexical_candidate_limit": 24,
238
237
  "query_expansion": True,
@@ -13,11 +13,9 @@ from .post_processors import (
13
13
  build_internal_doc_link_maps,
14
14
  fix_file_references,
15
15
  fix_mermaid_diagrams,
16
- normalize_code_fence_languages,
17
16
  normalize_explanatory_lines_outside_fences,
18
17
  normalize_html_code_blocks,
19
18
  repair_internal_doc_links,
20
- repair_split_object_code_fences,
21
19
  repair_dangling_plain_fences,
22
20
  repair_unbalanced_code_fences,
23
21
  )