deepdoc 2.0.0__tar.gz → 2.1.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 (152) hide show
  1. {deepdoc-2.0.0 → deepdoc-2.1.0}/PKG-INFO +9 -11
  2. {deepdoc-2.0.0 → deepdoc-2.1.0}/README.md +8 -10
  3. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/__init__.py +1 -1
  4. deepdoc-2.1.0/deepdoc/changelog_writer.py +161 -0
  5. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/answer_mixin.py +8 -6
  6. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/persistence.py +1 -1
  7. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/providers.py +108 -3
  8. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/retrieval_mixin.py +1 -1
  9. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/routes.py +23 -4
  10. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/service.py +0 -7
  11. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/settings.py +8 -6
  12. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/cli.py +52 -7
  13. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/config.py +17 -11
  14. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/generator/evidence.py +2 -6
  15. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/generator/generation.py +11 -14
  16. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/generator/post_processors.py +14 -3
  17. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/generator/validation.py +1 -0
  18. deepdoc-2.1.0/deepdoc/llm/client.py +177 -0
  19. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/routes/nestjs.py +16 -5
  20. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/routes/repo_resolver.py +1 -1
  21. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/persistence_v2.py +1 -3
  22. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/pipeline_v2.py +5 -0
  23. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/planner/bucket_injection.py +1 -1
  24. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/planner/bucket_refinement.py +4 -0
  25. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/planner/engine.py +13 -1
  26. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/planner/heuristics.py +17 -211
  27. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/planner/nav_shaping.py +18 -1
  28. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/prompts/bucket_types.py +37 -27
  29. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/scanner/artifacts.py +1 -1
  30. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/scanner/common.py +1 -1
  31. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/scanner/database.py +2 -2
  32. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/scanner/utils.py +2 -2
  33. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/site/builder/engine.py +15 -0
  34. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/site/builder/scaffold_files.py +3 -2
  35. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/smart_update_v2.py +24 -10
  36. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc.egg-info/PKG-INFO +9 -11
  37. {deepdoc-2.0.0 → deepdoc-2.1.0}/pyproject.toml +1 -1
  38. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_changelog.py +3 -1
  39. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_chatbot_index.py +1 -1
  40. deepdoc-2.0.0/deepdoc/changelog_writer.py +0 -106
  41. deepdoc-2.0.0/deepdoc/llm/client.py +0 -84
  42. {deepdoc-2.0.0 → deepdoc-2.1.0}/LICENSE +0 -0
  43. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/__main__.py +0 -0
  44. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/_legacy_types.py +0 -0
  45. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/benchmark_v2.py +0 -0
  46. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/call_graph.py +0 -0
  47. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/__init__.py +0 -0
  48. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/chunker.py +0 -0
  49. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/deep_research.py +0 -0
  50. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/docs_summary.py +0 -0
  51. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/embeddings.py +0 -0
  52. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/indexer.py +0 -0
  53. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/linking.py +0 -0
  54. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/live_fallback_mixin.py +0 -0
  55. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/scaffold.py +0 -0
  56. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/source_archive.py +0 -0
  57. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/symbol_index.py +0 -0
  58. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/chatbot/types.py +0 -0
  59. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/generator/__init__.py +0 -0
  60. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/llm/__init__.py +0 -0
  61. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/llm/json_utils.py +0 -0
  62. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/llm/litellm_compat.py +0 -0
  63. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/manifest.py +0 -0
  64. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/openapi.py +0 -0
  65. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/__init__.py +0 -0
  66. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/api_detector.py +0 -0
  67. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/base.py +0 -0
  68. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/go_parser.py +0 -0
  69. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/js_ts_parser.py +0 -0
  70. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/php_parser.py +0 -0
  71. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/python_parser.py +0 -0
  72. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/registry.py +0 -0
  73. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/routes/__init__.py +0 -0
  74. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/routes/base.py +0 -0
  75. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/routes/common.py +0 -0
  76. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/routes/detector.py +0 -0
  77. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/routes/django.py +0 -0
  78. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/routes/express.py +0 -0
  79. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/routes/falcon.py +0 -0
  80. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/routes/fastify.py +0 -0
  81. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/routes/go.py +0 -0
  82. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/routes/js_shared.py +0 -0
  83. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/routes/laravel.py +0 -0
  84. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/routes/python_shared.py +0 -0
  85. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/routes/registry.py +0 -0
  86. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/parser/vue_parser.py +0 -0
  87. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/planner/__init__.py +0 -0
  88. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/planner/common.py +0 -0
  89. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/planner/endpoint_refs.py +0 -0
  90. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/planner/flow_candidates.py +0 -0
  91. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/planner/specializations.py +0 -0
  92. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/planner/topology.py +0 -0
  93. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/planner/utils.py +0 -0
  94. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/prompts/__init__.py +0 -0
  95. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/prompts/page_types.py +0 -0
  96. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/prompts/selectors.py +0 -0
  97. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/prompts/system.py +0 -0
  98. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/prompts/update.py +0 -0
  99. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/prompts_v2.py +0 -0
  100. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/py.typed +0 -0
  101. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/scanner/__init__.py +0 -0
  102. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/scanner/clustering.py +0 -0
  103. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/scanner/endpoints.py +0 -0
  104. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/scanner/integrations.py +0 -0
  105. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/scanner/runtime.py +0 -0
  106. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/site/__init__.py +0 -0
  107. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/site/builder/__init__.py +0 -0
  108. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/site/builder/chatbot_components.py +0 -0
  109. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/site/builder/common.py +0 -0
  110. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/site/builder/mdx_utils.py +0 -0
  111. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/site/builder/templates.py +0 -0
  112. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/source_metadata.py +0 -0
  113. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/updater_v2.py +0 -0
  114. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc/v2_models.py +0 -0
  115. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc.egg-info/SOURCES.txt +0 -0
  116. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc.egg-info/dependency_links.txt +0 -0
  117. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc.egg-info/entry_points.txt +0 -0
  118. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc.egg-info/requires.txt +0 -0
  119. {deepdoc-2.0.0 → deepdoc-2.1.0}/deepdoc.egg-info/top_level.txt +0 -0
  120. {deepdoc-2.0.0 → deepdoc-2.1.0}/setup.cfg +0 -0
  121. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_benchmark_scorecard.py +0 -0
  122. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_call_graph.py +0 -0
  123. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_chatbot_config.py +0 -0
  124. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_chatbot_embeddings.py +0 -0
  125. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_chatbot_eval.py +0 -0
  126. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_chatbot_persistence.py +0 -0
  127. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_chatbot_providers.py +0 -0
  128. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_chatbot_query.py +0 -0
  129. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_chatbot_relationship.py +0 -0
  130. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_chatbot_scaffold.py +0 -0
  131. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_chatbot_source_archive.py +0 -0
  132. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_classify.py +0 -0
  133. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_cli_generate.py +0 -0
  134. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_cli_serve.py +0 -0
  135. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_cli_update.py +0 -0
  136. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_flow_candidates.py +0 -0
  137. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_framework_fixtures.py +0 -0
  138. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_framework_support.py +0 -0
  139. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_fumadocs_builder.py +0 -0
  140. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_generation_evidence.py +0 -0
  141. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_internal_docs_metadata.py +0 -0
  142. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_litellm_compat.py +0 -0
  143. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_llm_json_utils.py +0 -0
  144. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_parallel_pipeline.py +0 -0
  145. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_parser_ranges.py +0 -0
  146. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_planner_consolidation.py +0 -0
  147. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_planner_granularity.py +0 -0
  148. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_route_registry.py +0 -0
  149. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_runtime_scan.py +0 -0
  150. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_smart_update.py +0 -0
  151. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_stale.py +0 -0
  152. {deepdoc-2.0.0 → deepdoc-2.1.0}/tests/test_state.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepdoc
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: Auto-generate beautiful docs from any codebase
5
5
  Author: Pranav Kumar
6
6
  License: MIT
@@ -406,13 +406,13 @@ deepdoc update --deploy # Update + deploy
406
406
  1. Loads the saved sync baseline, plan, and generation ledger from `.deepdoc/`.
407
407
  2. Diffs committed changes from the last synced commit to the current `HEAD`.
408
408
  3. Chooses a strategy automatically:
409
- - incremental update
410
- - targeted replan
411
- - full replan
409
+ - **incremental** — regenerate only the stale bucket pages
410
+ - **targeted replan** — new/deleted files or endpoint structure changes; re-plans affected buckets then regenerates them. Full replan is never triggered automatically for normal code changes.
412
411
  4. Compares the saved scan cache with the current scan so semantic endpoint changes can refresh impacted docs even when ownership files do not line up directly.
413
- 5. Regenerates only the affected bucket pages when safe.
414
- 6. Incrementally refreshes the chatbot corpora from the same update run.
415
- 7. Rebuilds site config and nav afterward.
412
+ 5. Regenerates only the affected bucket pages. Deleted files are cleaned up in-place: orphaned buckets and their MDX pages are removed, partially-emptied buckets are marked stale and regenerated.
413
+ 6. Appends an entry to `.deepdoc/changelog.json` and regenerates `docs/whats-changed.mdx` so the docs site always shows a current commit-by-commit change log.
414
+ 7. Incrementally refreshes the chatbot corpora from the same update run.
415
+ 8. Rebuilds site config and nav afterward.
416
416
 
417
417
  If git is unavailable, it falls back to hash-based staleness detection for recovery.
418
418
 
@@ -421,7 +421,7 @@ Generation writes quality artifacts under `.deepdoc/`:
421
421
  - `.deepdoc/generation_quality.json` records invalid/degraded pages, coverage metrics, local setup warnings, and consistency summary data.
422
422
  - `.deepdoc/consistency_warnings.json` records warning-only cross-page identifier consistency findings.
423
423
 
424
- Generated MDX pages include provenance frontmatter such as `deepdoc_generated_commit`, `deepdoc_generated_at`, `deepdoc_generated_version`, `deepdoc_status`, and `deepdoc_evidence_files`. The generated Fumadocs site renders a subtle "Last generated from commit ..." badge when this metadata is present.
424
+ Generated MDX pages include provenance frontmatter such as `deepdoc_generated_commit`, `deepdoc_generated_at`, `deepdoc_generated_version`, `deepdoc_status`, `deepdoc_evidence_files`, and `deepdoc_prereqs` (prerequisite page slugs). The generated Fumadocs site renders a subtle "Last generated from commit ..." badge and wires prev/next navigation arrows automatically. Pages with prerequisites show a "Read first:" callout at the top.
425
425
 
426
426
  **Options:**
427
427
 
@@ -740,9 +740,7 @@ site:
740
740
  | `site.favicon` | `""` | Path to favicon |
741
741
  | `site.logo` | `""` | Path to logo |
742
742
  | **Compatibility** | | |
743
- | `compatibility.deprecated_version_warning.enabled` | `true` | Warn when existing generated docs were produced by a deprecated DeepDoc version |
744
- | `compatibility.deprecated_version_warning.minimum_version` | `1.0.0` | Minimum generated DeepDoc version before an upgrade warning appears |
745
- | `compatibility.deprecated_version_warning.upgrade_command` | `python3 -m pip install --upgrade deepdoc` | Command shown in the upgrade warning |
743
+ | `compatibility.deprecated_version_warning.enabled` | `true` | Warn when existing generated docs were produced by a different major version of DeepDoc (e.g. docs from v1.x with CLI v2.x). Suppressed for minor/patch version gaps. |
746
744
 
747
745
  ---
748
746
 
@@ -367,13 +367,13 @@ deepdoc update --deploy # Update + deploy
367
367
  1. Loads the saved sync baseline, plan, and generation ledger from `.deepdoc/`.
368
368
  2. Diffs committed changes from the last synced commit to the current `HEAD`.
369
369
  3. Chooses a strategy automatically:
370
- - incremental update
371
- - targeted replan
372
- - full replan
370
+ - **incremental** — regenerate only the stale bucket pages
371
+ - **targeted replan** — new/deleted files or endpoint structure changes; re-plans affected buckets then regenerates them. Full replan is never triggered automatically for normal code changes.
373
372
  4. Compares the saved scan cache with the current scan so semantic endpoint changes can refresh impacted docs even when ownership files do not line up directly.
374
- 5. Regenerates only the affected bucket pages when safe.
375
- 6. Incrementally refreshes the chatbot corpora from the same update run.
376
- 7. Rebuilds site config and nav afterward.
373
+ 5. Regenerates only the affected bucket pages. Deleted files are cleaned up in-place: orphaned buckets and their MDX pages are removed, partially-emptied buckets are marked stale and regenerated.
374
+ 6. Appends an entry to `.deepdoc/changelog.json` and regenerates `docs/whats-changed.mdx` so the docs site always shows a current commit-by-commit change log.
375
+ 7. Incrementally refreshes the chatbot corpora from the same update run.
376
+ 8. Rebuilds site config and nav afterward.
377
377
 
378
378
  If git is unavailable, it falls back to hash-based staleness detection for recovery.
379
379
 
@@ -382,7 +382,7 @@ Generation writes quality artifacts under `.deepdoc/`:
382
382
  - `.deepdoc/generation_quality.json` records invalid/degraded pages, coverage metrics, local setup warnings, and consistency summary data.
383
383
  - `.deepdoc/consistency_warnings.json` records warning-only cross-page identifier consistency findings.
384
384
 
385
- Generated MDX pages include provenance frontmatter such as `deepdoc_generated_commit`, `deepdoc_generated_at`, `deepdoc_generated_version`, `deepdoc_status`, and `deepdoc_evidence_files`. The generated Fumadocs site renders a subtle "Last generated from commit ..." badge when this metadata is present.
385
+ Generated MDX pages include provenance frontmatter such as `deepdoc_generated_commit`, `deepdoc_generated_at`, `deepdoc_generated_version`, `deepdoc_status`, `deepdoc_evidence_files`, and `deepdoc_prereqs` (prerequisite page slugs). The generated Fumadocs site renders a subtle "Last generated from commit ..." badge and wires prev/next navigation arrows automatically. Pages with prerequisites show a "Read first:" callout at the top.
386
386
 
387
387
  **Options:**
388
388
 
@@ -701,9 +701,7 @@ site:
701
701
  | `site.favicon` | `""` | Path to favicon |
702
702
  | `site.logo` | `""` | Path to logo |
703
703
  | **Compatibility** | | |
704
- | `compatibility.deprecated_version_warning.enabled` | `true` | Warn when existing generated docs were produced by a deprecated DeepDoc version |
705
- | `compatibility.deprecated_version_warning.minimum_version` | `1.0.0` | Minimum generated DeepDoc version before an upgrade warning appears |
706
- | `compatibility.deprecated_version_warning.upgrade_command` | `python3 -m pip install --upgrade deepdoc` | Command shown in the upgrade warning |
704
+ | `compatibility.deprecated_version_warning.enabled` | `true` | Warn when existing generated docs were produced by a different major version of DeepDoc (e.g. docs from v1.x with CLI v2.x). Suppressed for minor/patch version gaps. |
707
705
 
708
706
  ---
709
707
 
@@ -1,3 +1,3 @@
1
1
  """DeepDoc — Auto-generate beautiful docs from any codebase."""
2
2
 
3
- __version__ = "2.0.0"
3
+ __version__ = "2.1.0"
@@ -0,0 +1,161 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from .persistence_v2 import append_changelog_entry, load_changelog, load_plan, save_plan
6
+
7
+ _STRATEGY_LABEL = {
8
+ "incremental": "Incremental update",
9
+ "targeted_replan": "Targeted replan",
10
+ "full_replan": "Full replan",
11
+ "full_generate": "Full generation",
12
+ }
13
+
14
+
15
+ def record_and_write(
16
+ repo_root: Path,
17
+ output_dir: Path,
18
+ *,
19
+ commit: str,
20
+ commit_message: str,
21
+ commit_date: str,
22
+ strategy: str,
23
+ pages_updated: list[str],
24
+ files_changed: list[str],
25
+ is_initial: bool = False,
26
+ ) -> None:
27
+ """Append one changelog entry and regenerate whats-changed.mdx."""
28
+ entry = {
29
+ "commit": commit[:8],
30
+ "date": commit_date,
31
+ "commit_message": commit_message,
32
+ "strategy": strategy,
33
+ "pages_updated": pages_updated,
34
+ "files_changed": files_changed[:20],
35
+ "is_initial": is_initial,
36
+ }
37
+ append_changelog_entry(repo_root, entry)
38
+ write_whats_changed_page(repo_root, output_dir)
39
+
40
+
41
+ def write_whats_changed_page(repo_root: Path, output_dir: Path) -> None:
42
+ """Write docs/whats-changed.mdx from .deepdoc/changelog.json."""
43
+ entries = load_changelog(repo_root)
44
+ mdx = _build_mdx(entries)
45
+ output_dir.mkdir(parents=True, exist_ok=True)
46
+ (output_dir / "whats-changed.mdx").write_text(mdx, encoding="utf-8")
47
+ _ensure_in_nav(repo_root)
48
+
49
+
50
+ def _build_mdx(entries: list[dict]) -> str:
51
+ lines = [
52
+ "---",
53
+ 'title: "What\'s Changed"',
54
+ 'description: "A commit-by-commit log of every documentation update — which pages changed, which source files triggered the change, and how the update was handled."',
55
+ "---",
56
+ "",
57
+ "# What's Changed",
58
+ "",
59
+ "Every time you run `deepdoc generate` or `deepdoc update`, this page is regenerated automatically.",
60
+ "Each entry shows the commit that triggered the run, the strategy DeepDoc chose, the pages that were",
61
+ "updated, and the source files that caused the change.",
62
+ "",
63
+ ]
64
+
65
+ if not entries:
66
+ lines.append(
67
+ "<Callout>No changelog entries yet. Run `deepdoc generate` to create the first entry.</Callout>"
68
+ )
69
+ return "\n".join(lines)
70
+
71
+ lines.append("<Accordions>")
72
+ for entry in entries:
73
+ date = entry.get("date", "")
74
+ msg = entry.get("commit_message", "update")
75
+ sha = entry.get("commit", "")
76
+ pages = entry.get("pages_updated", [])
77
+ files = entry.get("files_changed", [])
78
+ strategy = entry.get("strategy", "")
79
+ is_initial = entry.get("is_initial", False)
80
+ strategy_label = _STRATEGY_LABEL.get(strategy, strategy)
81
+
82
+ title = f"{date} — {msg[:72]} ({sha})"
83
+ lines.append(f'<Accordion title="{title}">')
84
+ lines.append("")
85
+
86
+ # Commit metadata row
87
+ lines.append("| | |")
88
+ lines.append("|---|---|")
89
+ lines.append(f"| **Commit** | `{sha}` |")
90
+ lines.append(f"| **Date** | {date} |")
91
+ lines.append(f"| **Strategy** | {strategy_label} |")
92
+ lines.append(f"| **Message** | {msg} |")
93
+ lines.append("")
94
+
95
+ if is_initial:
96
+ lines.append(
97
+ f"**Initial generation** — {len(pages)} page(s) created from scratch."
98
+ )
99
+ if pages:
100
+ lines.append("")
101
+ lines.append("**Pages generated:**")
102
+ lines.append("")
103
+ for s in pages:
104
+ lines.append(f"- [{_slug_to_title(s)}](/{s})")
105
+ else:
106
+ # Pages updated
107
+ if pages:
108
+ lines.append(f"**{len(pages)} page(s) updated:**")
109
+ lines.append("")
110
+ for s in pages:
111
+ lines.append(f"- [{_slug_to_title(s)}](/{s})")
112
+ else:
113
+ lines.append(
114
+ "<Callout type='info'>No pages were regenerated — only metadata or chatbot corpora were refreshed.</Callout>"
115
+ )
116
+
117
+ # Source files that changed
118
+ if files:
119
+ lines.append("")
120
+ lines.append(f"**Source files that triggered this update ({len(files)}):**")
121
+ lines.append("")
122
+ for f in files[:20]:
123
+ lines.append(f"- `{f}`")
124
+ if len(files) > 20:
125
+ lines.append(f"- *...and {len(files) - 20} more*")
126
+
127
+ # What the strategy means
128
+ lines.append("")
129
+ if strategy == "incremental":
130
+ lines.append(
131
+ "> **Incremental update** — only the pages whose source files changed were regenerated. All other pages were left untouched."
132
+ )
133
+ elif strategy == "targeted_replan":
134
+ lines.append(
135
+ "> **Targeted replan** — new or deleted files were detected. DeepDoc re-evaluated which buckets own those files, updated the plan, and regenerated affected pages."
136
+ )
137
+ elif strategy == "full_replan":
138
+ lines.append(
139
+ "> **Full replan** — the engine schema changed or a force-replan was requested. All buckets were re-planned and regenerated."
140
+ )
141
+
142
+ lines.append("")
143
+ lines.append("</Accordion>")
144
+
145
+ lines.append("</Accordions>")
146
+ return "\n".join(lines)
147
+
148
+
149
+ def _slug_to_title(slug: str) -> str:
150
+ return slug.replace("-", " ").title()
151
+
152
+
153
+ def _ensure_in_nav(repo_root: Path) -> None:
154
+ """Add whats-changed to Start Here section of the saved plan if not already there."""
155
+ plan = load_plan(repo_root)
156
+ if plan is None or not hasattr(plan, "nav_structure"):
157
+ return
158
+ section = plan.nav_structure.setdefault("Start Here", [])
159
+ if "whats-changed" not in section:
160
+ section.append("whats-changed")
161
+ save_plan(plan, repo_root)
@@ -570,13 +570,14 @@ class AnswerMixin:
570
570
  reason="mentioned_source",
571
571
  )
572
572
 
573
- by_path: dict[str, dict[str, Any]] = {}
573
+ seen_ranges: set[tuple[str, int, int]] = set()
574
+ result: list[dict[str, Any]] = []
574
575
  for row in rows:
575
- path = row["file_path"]
576
- existing = by_path.get(path)
577
- if not existing or float(row.get("score", 0.0)) > float(existing.get("score", 0.0)):
578
- by_path[path] = row
579
- return list(by_path.values())[:8]
576
+ key = (row["file_path"], row.get("start_line", 0), row.get("end_line", 0))
577
+ if key not in seen_ranges:
578
+ seen_ranges.add(key)
579
+ result.append(row)
580
+ return result[:8]
580
581
 
581
582
  def _apply_evidence_contract(
582
583
  self,
@@ -774,6 +775,7 @@ class AnswerMixin:
774
775
  for text in texts:
775
776
  for raw_path, raw_start, raw_end in pattern.findall(str(text or "")):
776
777
  path = raw_path.strip("`'\".,:;()[]{}")
778
+ path = re.sub(r"^\./", "", path)
777
779
  if self._is_reference_doc_path(path):
778
780
  continue
779
781
  if not self._is_code_workspace_path(path, allow_config=True):
@@ -440,7 +440,7 @@ def similarity_search(
440
440
  return [
441
441
  RetrievedChunk(record=records[idx], score=float(score))
442
442
  for score, idx in zip(scores[0], order[0], strict=False)
443
- if idx >= 0 and idx < len(records)
443
+ if idx >= 0 and idx < len(records) and score > -0.5
444
444
  ]
445
445
  except Exception:
446
446
  pass
@@ -185,7 +185,82 @@ class LiteLLMEmbeddingClient:
185
185
 
186
186
 
187
187
  def build_chat_client(cfg: dict[str, Any]) -> LiteLLMChatClient:
188
- return LiteLLMChatClient(get_chatbot_cfg(cfg).get("answer", {}))
188
+ answer_cfg = get_chatbot_cfg(cfg).get("answer", {})
189
+ provider = (answer_cfg.get("provider") or "").strip()
190
+ model = (answer_cfg.get("model") or "").strip()
191
+
192
+ # If chatbot.answer is not explicitly configured, inherit from the doc-gen llm.* config.
193
+ # This means a single deepdoc init --provider X covers both doc generation and chatbot.
194
+ if not provider or not model:
195
+ llm_cfg = cfg.get("llm", {})
196
+ llm_provider = (llm_cfg.get("provider") or "").strip()
197
+ llm_model = (llm_cfg.get("model") or "").strip()
198
+ if llm_provider and llm_model:
199
+ answer_cfg = {
200
+ **answer_cfg,
201
+ "provider": llm_provider,
202
+ "model": llm_model,
203
+ "api_key_env": llm_cfg.get("api_key_env") or answer_cfg.get("api_key_env", ""),
204
+ "base_url": llm_cfg.get("base_url") or answer_cfg.get("base_url", ""),
205
+ "api_version": llm_cfg.get("api_version") or answer_cfg.get("api_version", ""),
206
+ }
207
+ provider, model = llm_provider, llm_model
208
+
209
+ if not provider or not model:
210
+ raise ValueError(
211
+ "\n\n"
212
+ "╔══════════════════════════════════════════════════════════════════════╗\n"
213
+ "║ CHATBOT NOT CONFIGURED — ACTION REQUIRED ║\n"
214
+ "╠══════════════════════════════════════════════════════════════════════╣\n"
215
+ "║ ║\n"
216
+ "║ No LLM is configured for the chatbot. ║\n"
217
+ "║ ║\n"
218
+ "║ OPTION 1 — reuse your doc-gen LLM (zero extra config): ║\n"
219
+ "║ Just make sure llm.provider and llm.model are set. ║\n"
220
+ "║ The chatbot will automatically use the same provider and key. ║\n"
221
+ "║ ║\n"
222
+ "║ OPTION 2 — use a separate (e.g. cheaper) model for chat: ║\n"
223
+ "║ ║\n"
224
+ "║ chatbot: ║\n"
225
+ "║ answer: ║\n"
226
+ "║ provider: <your-provider> # openai, anthropic, azure, etc. ║\n"
227
+ "║ model: <your-model> # matching model name ║\n"
228
+ "║ api_key_env: <YOUR_KEY_ENV> # env var holding your key ║\n"
229
+ "║ ║\n"
230
+ "║ Any LiteLLM-compatible provider works: ║\n"
231
+ "║ https://docs.litellm.ai/docs/providers ║\n"
232
+ "╚══════════════════════════════════════════════════════════════════════╝\n"
233
+ )
234
+ is_azure = provider.lower() == "azure" or model.lower().startswith("azure/")
235
+ if is_azure:
236
+ base_url = (answer_cfg.get("base_url") or "").strip()
237
+ api_version = (answer_cfg.get("api_version") or "").strip()
238
+ missing = []
239
+ if not base_url or base_url.startswith("https://<"):
240
+ missing.append("base_url (your Azure OpenAI endpoint URL)")
241
+ if not api_version:
242
+ missing.append("api_version (e.g. 2024-02-01)")
243
+ if missing:
244
+ items = "\n".join(f"║ • {item:<64}║" for item in missing)
245
+ raise ValueError(
246
+ "\n\n"
247
+ "╔══════════════════════════════════════════════════════════════════════╗\n"
248
+ "║ AZURE CHATBOT NOT FULLY CONFIGURED — ACTION REQUIRED ║\n"
249
+ "╠══════════════════════════════════════════════════════════════════════╣\n"
250
+ "║ ║\n"
251
+ "║ Azure OpenAI requires additional settings that are missing: ║\n"
252
+ "║ ║\n"
253
+ f"{items}\n"
254
+ "║ ║\n"
255
+ "║ Add them to .deepdoc.yaml under llm: or chatbot.answer: ║\n"
256
+ "║ ║\n"
257
+ "║ llm: ║\n"
258
+ "║ base_url: https://<resource>.openai.azure.com ║\n"
259
+ "║ api_version: 2024-02-01 ║\n"
260
+ "╚══════════════════════════════════════════════════════════════════════╝\n"
261
+ )
262
+
263
+ return LiteLLMChatClient(answer_cfg)
189
264
 
190
265
 
191
266
  def build_embedding_client(
@@ -198,8 +273,36 @@ def build_embedding_client(
198
273
 
199
274
  if backend == "fastembed":
200
275
  return FastembedEmbeddingClient(embeddings_cfg)
201
- else:
202
- return LiteLLMEmbeddingClient(embeddings_cfg)
276
+
277
+ provider = (embeddings_cfg.get("provider") or "").strip()
278
+ model = (embeddings_cfg.get("model") or "").strip()
279
+ if not provider or not model:
280
+ raise ValueError(
281
+ "\n\n"
282
+ "╔══════════════════════════════════════════════════════════════════════╗\n"
283
+ "║ EMBEDDINGS NOT CONFIGURED — ACTION REQUIRED ║\n"
284
+ "╠══════════════════════════════════════════════════════════════════════╣\n"
285
+ "║ ║\n"
286
+ "║ chatbot.embeddings.backend is set to 'litellm' but ║\n"
287
+ "║ chatbot.embeddings.provider and .model are not set. ║\n"
288
+ "║ ║\n"
289
+ "║ Either switch to the local (no-API-key) backend: ║\n"
290
+ "║ ║\n"
291
+ "║ chatbot: ║\n"
292
+ "║ embeddings: ║\n"
293
+ "║ backend: fastembed # runs locally, no key needed ║\n"
294
+ "║ ║\n"
295
+ "║ Or configure a cloud embedding provider: ║\n"
296
+ "║ ║\n"
297
+ "║ chatbot: ║\n"
298
+ "║ embeddings: ║\n"
299
+ "║ backend: litellm ║\n"
300
+ "║ provider: <your-provider> # e.g. openai, azure ║\n"
301
+ "║ model: <your-model> # e.g. text-embedding-3-large ║\n"
302
+ "║ api_key_env: <YOUR_KEY_ENV> # env var holding your key ║\n"
303
+ "╚══════════════════════════════════════════════════════════════════════╝\n"
304
+ )
305
+ return LiteLLMEmbeddingClient(embeddings_cfg)
203
306
 
204
307
 
205
308
  class FastembedEmbeddingClient:
@@ -207,6 +310,8 @@ class FastembedEmbeddingClient:
207
310
 
208
311
  def __init__(self, service_cfg: dict[str, Any]) -> None:
209
312
  self.service_cfg = service_cfg
313
+ # Fallback model if not set in config — explicit choice, not a vendor endorsement.
314
+ # Full model list: https://qdrant.github.io/fastembed/examples/Supported_Models/
210
315
  self.model = service_cfg.get(
211
316
  "fastembed_model", "nomic-ai/nomic-embed-text-v1.5"
212
317
  )
@@ -213,7 +213,7 @@ class RetrievalMixin:
213
213
  hits = [
214
214
  RetrievedChunk(
215
215
  record=by_chunk_id[chunk_id],
216
- score=max(1.0, self._lexical_score(by_chunk_id[chunk_id], query_signals)),
216
+ score=self._lexical_score(by_chunk_id[chunk_id], query_signals),
217
217
  )
218
218
  for chunk_id in chunk_ids
219
219
  if chunk_id in by_chunk_id
@@ -8,7 +8,7 @@ import threading
8
8
  from pathlib import Path
9
9
  from typing import Any
10
10
 
11
- from pydantic import BaseModel, Field
11
+ from pydantic import BaseModel, Field, field_validator
12
12
 
13
13
  from .settings import chatbot_allowed_origins
14
14
 
@@ -19,6 +19,13 @@ class QueryRequest(BaseModel):
19
19
  question: str
20
20
  history: list[dict[str, str]] = Field(default_factory=list)
21
21
 
22
+ @field_validator("question")
23
+ @classmethod
24
+ def question_not_empty(cls, v: str) -> str:
25
+ if not v or not v.strip():
26
+ raise ValueError("question must not be empty")
27
+ return v
28
+
22
29
 
23
30
  class DeepResearchRequest(QueryRequest):
24
31
  """Incoming deep-research payload."""
@@ -110,7 +117,11 @@ def create_fastapi_app(repo_root: Path, cfg: dict[str, Any]):
110
117
 
111
118
  def event_stream():
112
119
  while True:
113
- item = tokens.get()
120
+ try:
121
+ item = tokens.get(timeout=30)
122
+ except queue.Empty:
123
+ yield "event: ping\ndata: {}\n\n"
124
+ continue
114
125
  if item is None:
115
126
  break
116
127
  event_name, payload = item
@@ -154,7 +165,11 @@ def create_fastapi_app(repo_root: Path, cfg: dict[str, Any]):
154
165
 
155
166
  def event_stream():
156
167
  while True:
157
- item = tokens.get()
168
+ try:
169
+ item = tokens.get(timeout=30)
170
+ except queue.Empty:
171
+ yield "event: ping\ndata: {}\n\n"
172
+ continue
158
173
  if item is None:
159
174
  break
160
175
  event_name, payload = item
@@ -223,7 +238,11 @@ def create_fastapi_app(repo_root: Path, cfg: dict[str, Any]):
223
238
 
224
239
  def event_stream():
225
240
  while True:
226
- item = events.get()
241
+ try:
242
+ item = events.get(timeout=30)
243
+ except queue.Empty:
244
+ yield "event: ping\ndata: {}\n\n"
245
+ continue
227
246
  if item is None:
228
247
  break
229
248
  event_name, payload = item
@@ -535,13 +535,6 @@ class ChatbotQueryService(RetrievalMixin, AnswerMixin, LiveFallbackMixin):
535
535
  trace_callback=emit,
536
536
  )
537
537
  response["trace"] = trace
538
- response["file_inventory"] = self._collect_file_inventory(
539
- question,
540
- response,
541
- result.all_sources,
542
- retrieval_cfg,
543
- trace,
544
- )
545
538
  response["response_mode"] = "code_deep"
546
539
  response["research_mode"] = "code_deep"
547
540
  response = self._finalize_answer_response(question, response, mode="code_deep")
@@ -20,8 +20,8 @@ DEFAULT_CHATBOT_CONFIG: dict[str, Any] = {
20
20
  ],
21
21
  },
22
22
  "answer": {
23
- "provider": "azure",
24
- "model": "azure/gpt-4o-mini",
23
+ "provider": "", # must be set in .deepdoc.yaml — run: deepdoc init --with-chatbot
24
+ "model": "",
25
25
  "api_key_env": "DEEPDOC_CHAT_API_KEY",
26
26
  "base_url": "",
27
27
  "api_version": "",
@@ -31,11 +31,13 @@ DEFAULT_CHATBOT_CONFIG: dict[str, Any] = {
31
31
  "continuation_context_chars": 12000,
32
32
  },
33
33
  "embeddings": {
34
- "backend": "litellm", # default: use configured litellm model (text-embedding-3-small, Azure, etc.)
35
- "fastembed_model": "nomic-ai/nomic-embed-text-v1.5", # only use if backend="fastembed"; 8192-token context, 768-dim
34
+ "backend": "fastembed", # local by default no API key needed; set to "litellm" for cloud embeddings
35
+ # Local model — runs offline, no API key. Explicit choice; swap to any model from:
36
+ # https://qdrant.github.io/fastembed/examples/Supported_Models/
37
+ "fastembed_model": "nomic-ai/nomic-embed-text-v1.5",
36
38
  "fastembed_batch_size": 4,
37
- "provider": "azure",
38
- "model": "azure/text-embedding-3-large",
39
+ "provider": "", # only required when backend="litellm"
40
+ "model": "",
39
41
  "api_key_env": "DEEPDOC_EMBED_API_KEY",
40
42
  "base_url": "",
41
43
  "api_version": "",