exactdocs 0.1.1__tar.gz → 0.2.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 (384) hide show
  1. {exactdocs-0.1.1 → exactdocs-0.2.0}/.gitignore +8 -0
  2. {exactdocs-0.1.1 → exactdocs-0.2.0}/PKG-INFO +10 -2
  3. exactdocs-0.2.0/alembic.ini +5 -0
  4. exactdocs-0.2.0/features/add_library.feature +55 -0
  5. exactdocs-0.2.0/features/add_version.feature +43 -0
  6. exactdocs-0.2.0/features/api.feature +17 -0
  7. exactdocs-0.2.0/features/articles.feature +90 -0
  8. exactdocs-0.2.0/features/catalog.feature +43 -0
  9. exactdocs-0.2.0/features/chunking.feature +24 -0
  10. exactdocs-0.2.0/features/chunks.feature +61 -0
  11. exactdocs-0.2.0/features/environment.py +25 -0
  12. exactdocs-0.2.0/features/fixture_repo.feature +18 -0
  13. exactdocs-0.2.0/features/import.feature +51 -0
  14. exactdocs-0.2.0/features/ingestion.feature +53 -0
  15. exactdocs-0.2.0/features/jobs.feature +26 -0
  16. exactdocs-0.2.0/features/library_readme.feature +24 -0
  17. exactdocs-0.2.0/features/login.feature +47 -0
  18. exactdocs-0.2.0/features/mcp_browse.feature +26 -0
  19. exactdocs-0.2.0/features/mcp_query_docs.feature +33 -0
  20. exactdocs-0.2.0/features/mcp_resolve.feature +29 -0
  21. exactdocs-0.2.0/features/mcp_search.feature +23 -0
  22. exactdocs-0.2.0/features/mcp_stdio.feature +19 -0
  23. exactdocs-0.2.0/features/private_repositories.feature +34 -0
  24. exactdocs-0.2.0/features/read.feature +37 -0
  25. exactdocs-0.2.0/features/removal.feature +42 -0
  26. exactdocs-0.2.0/features/repositories.feature +51 -0
  27. exactdocs-0.2.0/features/search.feature +35 -0
  28. exactdocs-0.2.0/features/serve_requires_git.feature +14 -0
  29. exactdocs-0.2.0/features/setup.feature +29 -0
  30. exactdocs-0.2.0/features/sparse_fetch.feature +12 -0
  31. exactdocs-0.2.0/features/steps/add_library_steps.py +134 -0
  32. exactdocs-0.2.0/features/steps/add_version_steps.py +70 -0
  33. exactdocs-0.2.0/features/steps/articles_steps.py +148 -0
  34. exactdocs-0.2.0/features/steps/chunking_steps.py +60 -0
  35. exactdocs-0.2.0/features/steps/chunks_steps.py +187 -0
  36. exactdocs-0.2.0/features/steps/fixture_repo_steps.py +32 -0
  37. exactdocs-0.2.0/features/steps/http_steps.py +209 -0
  38. exactdocs-0.2.0/features/steps/import_steps.py +283 -0
  39. exactdocs-0.2.0/features/steps/ingestion_steps.py +61 -0
  40. exactdocs-0.2.0/features/steps/jobs_steps.py +110 -0
  41. exactdocs-0.2.0/features/steps/library_readme_steps.py +49 -0
  42. exactdocs-0.2.0/features/steps/login_steps.py +160 -0
  43. exactdocs-0.2.0/features/steps/mcp_query_docs_steps.py +121 -0
  44. exactdocs-0.2.0/features/steps/mcp_resolve_steps.py +111 -0
  45. exactdocs-0.2.0/features/steps/mcp_stdio_steps.py +135 -0
  46. exactdocs-0.2.0/features/steps/mcp_steps.py +159 -0
  47. exactdocs-0.2.0/features/steps/private_repository_steps.py +139 -0
  48. exactdocs-0.2.0/features/steps/read_steps.py +142 -0
  49. exactdocs-0.2.0/features/steps/removal_steps.py +192 -0
  50. exactdocs-0.2.0/features/steps/repository_steps.py +59 -0
  51. exactdocs-0.2.0/features/steps/search_steps.py +272 -0
  52. exactdocs-0.2.0/features/steps/serve_requires_git_steps.py +61 -0
  53. exactdocs-0.2.0/features/steps/setup_steps.py +100 -0
  54. exactdocs-0.2.0/features/steps/sparse_fetch_steps.py +60 -0
  55. exactdocs-0.2.0/features/steps/sync_steps.py +129 -0
  56. exactdocs-0.2.0/features/steps/tokens_steps.py +158 -0
  57. exactdocs-0.2.0/features/sync.feature +40 -0
  58. exactdocs-0.2.0/features/tokens.feature +31 -0
  59. {exactdocs-0.1.1 → exactdocs-0.2.0}/pyproject.toml +8 -0
  60. exactdocs-0.2.0/src/exactdocs/_web/assets/_app-L8dLfZA-.js +1 -0
  61. exactdocs-0.2.0/src/exactdocs/_web/assets/_app.account-BBv6H3nn.js +1 -0
  62. exactdocs-0.2.0/src/exactdocs/_web/assets/_app.index-Ck0Wke8Y.js +1 -0
  63. exactdocs-0.2.0/src/exactdocs/_web/assets/_app.index-Dwh2_XXZ.js +1 -0
  64. exactdocs-0.2.0/src/exactdocs/_web/assets/_app.libraries._owner._repo-BUK4Wra-.js +1 -0
  65. exactdocs-0.2.0/src/exactdocs/_web/assets/_app.libraries._owner._repo-CXtxCHLH.js +1 -0
  66. exactdocs-0.2.0/src/exactdocs/_web/assets/_app.libraries._owner._repo_.settings-C2eefokI.js +1 -0
  67. exactdocs-0.2.0/src/exactdocs/_web/assets/_app.libraries._owner._repo_.settings-COV2Vl80.js +1 -0
  68. exactdocs-0.2.0/src/exactdocs/_web/assets/_app.libraries.new-BYtg6-4L.js +1 -0
  69. exactdocs-0.2.0/src/exactdocs/_web/assets/_app.libraries.new-Ck0Wke8Y.js +1 -0
  70. exactdocs-0.2.0/src/exactdocs/_web/assets/auth-DOqUTQT4.js +1 -0
  71. exactdocs-0.2.0/src/exactdocs/_web/assets/bash-BqpFWvKu.js +1 -0
  72. exactdocs-0.2.0/src/exactdocs/_web/assets/browse-l0znjzw4.js +2 -0
  73. exactdocs-0.2.0/src/exactdocs/_web/assets/c-DBVbJFpC.js +1 -0
  74. exactdocs-0.2.0/src/exactdocs/_web/assets/chevron-down-VvddpmDw.js +1 -0
  75. exactdocs-0.2.0/src/exactdocs/_web/assets/copy-kIv_spkb.js +1 -0
  76. exactdocs-0.2.0/src/exactdocs/_web/assets/cpp-N2d8M7Nc.js +1 -0
  77. exactdocs-0.2.0/src/exactdocs/_web/assets/csharp-yYt0Ikks.js +1 -0
  78. exactdocs-0.2.0/src/exactdocs/_web/assets/css-D8M8GYaw.js +1 -0
  79. exactdocs-0.2.0/src/exactdocs/_web/assets/dialog-D5mMvlm0.js +1 -0
  80. exactdocs-0.2.0/src/exactdocs/_web/assets/diff-BhdZizse.js +1 -0
  81. exactdocs-0.2.0/src/exactdocs/_web/assets/dockerfile-C0zufGsU.js +1 -0
  82. exactdocs-0.2.0/src/exactdocs/_web/assets/es2015-CBjFb0he.js +41 -0
  83. exactdocs-0.2.0/src/exactdocs/_web/assets/form-alert-DQv24T46.js +1 -0
  84. exactdocs-0.2.0/src/exactdocs/_web/assets/go-CYstVVJV.js +1 -0
  85. exactdocs-0.2.0/src/exactdocs/_web/assets/highlight-IMd1IgrZ.js +3 -0
  86. exactdocs-0.2.0/src/exactdocs/_web/assets/index-BMKasA_-.css +2 -0
  87. exactdocs-0.2.0/src/exactdocs/_web/assets/index-CsPrqFYg.js +12 -0
  88. exactdocs-0.2.0/src/exactdocs/_web/assets/info-BS8gDhnL.js +1 -0
  89. exactdocs-0.2.0/src/exactdocs/_web/assets/ini-B1CroMsf.js +1 -0
  90. exactdocs-0.2.0/src/exactdocs/_web/assets/java-Dde-Wnb4.js +1 -0
  91. exactdocs-0.2.0/src/exactdocs/_web/assets/javascript-DMgQAH-8.js +1 -0
  92. exactdocs-0.2.0/src/exactdocs/_web/assets/json-PmyKWHmm.js +1 -0
  93. exactdocs-0.2.0/src/exactdocs/_web/assets/lazyRouteComponent-C9loUZFK.js +1 -0
  94. exactdocs-0.2.0/src/exactdocs/_web/assets/markdown-B1kCpIQK.css +1 -0
  95. exactdocs-0.2.0/src/exactdocs/_web/assets/markdown-BsHCS9sS.js +17 -0
  96. exactdocs-0.2.0/src/exactdocs/_web/assets/markdown-D6FrnlqJ.js +1 -0
  97. exactdocs-0.2.0/src/exactdocs/_web/assets/php-DuZl5e-L.js +2 -0
  98. exactdocs-0.2.0/src/exactdocs/_web/assets/preload-helper-BLdHbfdN.js +1 -0
  99. exactdocs-0.2.0/src/exactdocs/_web/assets/python-gHmu6VPG.js +1 -0
  100. exactdocs-0.2.0/src/exactdocs/_web/assets/ruby-BGpZ3p4_.js +1 -0
  101. exactdocs-0.2.0/src/exactdocs/_web/assets/rust-BIKPVoi6.js +1 -0
  102. exactdocs-0.2.0/src/exactdocs/_web/assets/setup-CSE__qkX.js +1 -0
  103. exactdocs-0.2.0/src/exactdocs/_web/assets/sign-in-C18mr4H8.js +1 -0
  104. exactdocs-0.2.0/src/exactdocs/_web/assets/source-serif-4-cyrillic-ext-wght-normal-DHTKMUil.woff2 +0 -0
  105. exactdocs-0.2.0/src/exactdocs/_web/assets/source-serif-4-cyrillic-wght-normal-B1g8a0vD.woff2 +0 -0
  106. exactdocs-0.2.0/src/exactdocs/_web/assets/source-serif-4-greek-wght-normal-BpXpdlRW.woff2 +0 -0
  107. exactdocs-0.2.0/src/exactdocs/_web/assets/source-serif-4-latin-ext-wght-normal-BVZ5su8S.woff2 +0 -0
  108. exactdocs-0.2.0/src/exactdocs/_web/assets/source-serif-4-latin-wght-normal-DMe4EKeb.woff2 +0 -0
  109. exactdocs-0.2.0/src/exactdocs/_web/assets/source-serif-4-vietnamese-wght-normal-BsaNuasd.woff2 +0 -0
  110. exactdocs-0.2.0/src/exactdocs/_web/assets/sql-DWRKyvJ-.js +1 -0
  111. exactdocs-0.2.0/src/exactdocs/_web/assets/time-CEDhx21E.js +1 -0
  112. exactdocs-0.2.0/src/exactdocs/_web/assets/triangle-alert-CV6_tesq.js +1 -0
  113. exactdocs-0.2.0/src/exactdocs/_web/assets/typescript-Da0fK7fQ.js +1 -0
  114. exactdocs-0.2.0/src/exactdocs/_web/assets/urls-Bj94Pjxc.css +1 -0
  115. exactdocs-0.2.0/src/exactdocs/_web/assets/urls-ghi89a1B.js +1 -0
  116. exactdocs-0.2.0/src/exactdocs/_web/assets/useNavigate-DuwO8yE1.js +1 -0
  117. exactdocs-0.2.0/src/exactdocs/_web/assets/utils-BpYXgVgn.js +2 -0
  118. exactdocs-0.2.0/src/exactdocs/_web/assets/xml-COl5yFSC.js +1 -0
  119. exactdocs-0.2.0/src/exactdocs/_web/assets/yaml-DjOhhVLG.js +1 -0
  120. exactdocs-0.2.0/src/exactdocs/_web/index.html +27 -0
  121. exactdocs-0.2.0/src/exactdocs/bootstrap/__init__.py +12 -0
  122. exactdocs-0.2.0/src/exactdocs/bootstrap/bridges.py +448 -0
  123. exactdocs-0.2.0/src/exactdocs/bootstrap/cli.py +196 -0
  124. exactdocs-0.2.0/src/exactdocs/bootstrap/container.py +245 -0
  125. exactdocs-0.2.0/src/exactdocs/bootstrap/database.py +235 -0
  126. exactdocs-0.2.0/src/exactdocs/bootstrap/http.py +228 -0
  127. exactdocs-0.2.0/src/exactdocs/bootstrap/mcp.py +83 -0
  128. exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/env.py +18 -0
  129. exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/script.py.mako +21 -0
  130. exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_3b8f0d6e2a71_retrieval_search.py +106 -0
  131. exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_5d1c7a93b2e4_identity_accounts.py +28 -0
  132. exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_5f3a9c21d7b4_ingestion_jobs.py +42 -0
  133. exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_5f87041a0ed2_catalog_manifests.py +84 -0
  134. exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_7c2e91d4a6b8_retrieval_chunks.py +95 -0
  135. exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_9b3e5a7c1d42_identity_sessions.py +34 -0
  136. exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_a1c9e4d7b352_ingestion_library_credentials.py +25 -0
  137. exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_a6c2d9e4b3f1_ingestion_job_chunk_counters.py +34 -0
  138. exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_ac89e0a428cd_catalog_libraries.py +35 -0
  139. exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_c3a8e1f94b27_ingestion_job_commit.py +21 -0
  140. exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_c4d8e2f6a1b7_identity_api_tokens.py +38 -0
  141. exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_d7e2a4b9c150_ingestion_job_ref_kind.py +21 -0
  142. exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_e5b1c7a30f68_catalog_library_text_edited.py +26 -0
  143. exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_e7a4b1c9d2f6_retrieval_chunk_id_index.py +22 -0
  144. exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261104_f421064ebb87_catalog_ids_and_version_kinds.py +61 -0
  145. exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions/20261105_a91c4e7b3d02_retrieval_search_path.py +88 -0
  146. exactdocs-0.2.0/src/exactdocs/bootstrap/rest.py +186 -0
  147. exactdocs-0.2.0/src/exactdocs/bootstrap/settings.py +85 -0
  148. exactdocs-0.2.0/src/exactdocs/catalog/adapters/driven/memory.py +283 -0
  149. exactdocs-0.2.0/src/exactdocs/catalog/adapters/driven/sqlite.py +657 -0
  150. exactdocs-0.2.0/src/exactdocs/catalog/adapters/driving/http.py +132 -0
  151. exactdocs-0.2.0/src/exactdocs/catalog/adapters/driving/mcp.py +170 -0
  152. exactdocs-0.2.0/src/exactdocs/catalog/dtos.py +682 -0
  153. exactdocs-0.2.0/src/exactdocs/catalog/errors.py +179 -0
  154. exactdocs-0.2.0/src/exactdocs/catalog/models.py +370 -0
  155. exactdocs-0.2.0/src/exactdocs/catalog/module.py +105 -0
  156. exactdocs-0.2.0/src/exactdocs/catalog/ports/__init__.py +36 -0
  157. exactdocs-0.2.0/src/exactdocs/catalog/ports/driven.py +175 -0
  158. exactdocs-0.2.0/src/exactdocs/catalog/ports/driving.py +227 -0
  159. exactdocs-0.2.0/src/exactdocs/catalog/use_cases.py +505 -0
  160. exactdocs-0.2.0/src/exactdocs/identity/__init__.py +9 -0
  161. exactdocs-0.2.0/src/exactdocs/identity/adapters/driven/argon2id.py +28 -0
  162. exactdocs-0.2.0/src/exactdocs/identity/adapters/driven/memory.py +113 -0
  163. exactdocs-0.2.0/src/exactdocs/identity/adapters/driven/sqlite.py +280 -0
  164. exactdocs-0.2.0/src/exactdocs/identity/adapters/driving/__init__.py +0 -0
  165. exactdocs-0.2.0/src/exactdocs/identity/adapters/driving/http.py +188 -0
  166. exactdocs-0.2.0/src/exactdocs/identity/adapters/driving/mcp.py +31 -0
  167. exactdocs-0.2.0/src/exactdocs/identity/dtos.py +112 -0
  168. exactdocs-0.2.0/src/exactdocs/identity/errors.py +90 -0
  169. exactdocs-0.2.0/src/exactdocs/identity/models.py +112 -0
  170. exactdocs-0.2.0/src/exactdocs/identity/module.py +89 -0
  171. exactdocs-0.2.0/src/exactdocs/identity/ports/__init__.py +39 -0
  172. exactdocs-0.2.0/src/exactdocs/identity/ports/driven.py +105 -0
  173. exactdocs-0.2.0/src/exactdocs/identity/ports/driving.py +114 -0
  174. exactdocs-0.2.0/src/exactdocs/identity/use_cases.py +332 -0
  175. exactdocs-0.2.0/src/exactdocs/ingestion/__init__.py +9 -0
  176. exactdocs-0.2.0/src/exactdocs/ingestion/adapters/__init__.py +1 -0
  177. exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driven/__init__.py +1 -0
  178. exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driven/git_cli.py +726 -0
  179. exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driven/known_hosts.py +63 -0
  180. exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driven/markdown_chunker.py +142 -0
  181. exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driven/memory.py +168 -0
  182. exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driven/secret_box.py +134 -0
  183. exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driven/sqlite.py +308 -0
  184. exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driving/__init__.py +1 -0
  185. exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driving/http.py +247 -0
  186. exactdocs-0.2.0/src/exactdocs/ingestion/adapters/driving/worker.py +58 -0
  187. exactdocs-0.2.0/src/exactdocs/ingestion/dtos.py +414 -0
  188. exactdocs-0.2.0/src/exactdocs/ingestion/errors.py +335 -0
  189. exactdocs-0.2.0/src/exactdocs/ingestion/models.py +1224 -0
  190. exactdocs-0.2.0/src/exactdocs/ingestion/module.py +206 -0
  191. exactdocs-0.2.0/src/exactdocs/ingestion/ports/__init__.py +65 -0
  192. exactdocs-0.2.0/src/exactdocs/ingestion/ports/driven.py +320 -0
  193. exactdocs-0.2.0/src/exactdocs/ingestion/ports/driving.py +169 -0
  194. exactdocs-0.2.0/src/exactdocs/ingestion/use_cases.py +1103 -0
  195. exactdocs-0.2.0/src/exactdocs/py.typed +0 -0
  196. exactdocs-0.2.0/src/exactdocs/retrieval/adapters/__init__.py +0 -0
  197. exactdocs-0.2.0/src/exactdocs/retrieval/adapters/driven/__init__.py +0 -0
  198. exactdocs-0.2.0/src/exactdocs/retrieval/adapters/driven/memory.py +161 -0
  199. exactdocs-0.2.0/src/exactdocs/retrieval/adapters/driven/sqlite.py +363 -0
  200. exactdocs-0.2.0/src/exactdocs/retrieval/adapters/driven/sqlite_fts.py +163 -0
  201. exactdocs-0.2.0/src/exactdocs/retrieval/adapters/driving/__init__.py +0 -0
  202. exactdocs-0.2.0/src/exactdocs/retrieval/adapters/driving/http.py +66 -0
  203. exactdocs-0.2.0/src/exactdocs/retrieval/adapters/driving/mcp.py +298 -0
  204. exactdocs-0.2.0/src/exactdocs/retrieval/dtos.py +478 -0
  205. exactdocs-0.2.0/src/exactdocs/retrieval/errors.py +77 -0
  206. exactdocs-0.2.0/src/exactdocs/retrieval/models.py +512 -0
  207. exactdocs-0.2.0/src/exactdocs/retrieval/module.py +75 -0
  208. exactdocs-0.2.0/src/exactdocs/retrieval/ports/__init__.py +35 -0
  209. exactdocs-0.2.0/src/exactdocs/retrieval/ports/driven.py +145 -0
  210. exactdocs-0.2.0/src/exactdocs/retrieval/ports/driving.py +126 -0
  211. exactdocs-0.2.0/src/exactdocs/retrieval/use_cases.py +492 -0
  212. {exactdocs-0.1.1 → exactdocs-0.2.0}/tests/architecture/test_architecture.py +29 -2
  213. exactdocs-0.2.0/tests/architecture/test_bridge_direction.py +83 -0
  214. exactdocs-0.2.0/tests/bootstrap/snapshots/mcp_tools.json +966 -0
  215. exactdocs-0.2.0/tests/bootstrap/test_add_library_api.py +328 -0
  216. exactdocs-0.2.0/tests/bootstrap/test_api_v1.py +196 -0
  217. exactdocs-0.2.0/tests/bootstrap/test_app.py +386 -0
  218. exactdocs-0.2.0/tests/bootstrap/test_articles_api.py +160 -0
  219. exactdocs-0.2.0/tests/bootstrap/test_articles_tags_api.py +152 -0
  220. exactdocs-0.2.0/tests/bootstrap/test_bridges.py +176 -0
  221. exactdocs-0.2.0/tests/bootstrap/test_cli.py +100 -0
  222. exactdocs-0.2.0/tests/bootstrap/test_context7_contract.py +70 -0
  223. exactdocs-0.2.0/tests/bootstrap/test_context7_session_replay.py +106 -0
  224. exactdocs-0.2.0/tests/bootstrap/test_import.py +503 -0
  225. exactdocs-0.2.0/tests/bootstrap/test_jobs_api.py +153 -0
  226. exactdocs-0.2.0/tests/bootstrap/test_library_patch_api.py +88 -0
  227. exactdocs-0.2.0/tests/bootstrap/test_mcp_browse.py +357 -0
  228. exactdocs-0.2.0/tests/bootstrap/test_model_free.py +112 -0
  229. exactdocs-0.2.0/tests/bootstrap/test_no_outbound.py +537 -0
  230. exactdocs-0.2.0/tests/bootstrap/test_persistence.py +249 -0
  231. exactdocs-0.2.0/tests/bootstrap/test_private_repositories.py +303 -0
  232. exactdocs-0.2.0/tests/bootstrap/test_read.py +348 -0
  233. exactdocs-0.2.0/tests/bootstrap/test_removal_api.py +143 -0
  234. exactdocs-0.2.0/tests/bootstrap/test_repositories_api.py +118 -0
  235. exactdocs-0.2.0/tests/bootstrap/test_search_api.py +205 -0
  236. exactdocs-0.2.0/tests/bootstrap/test_search_migration.py +167 -0
  237. exactdocs-0.2.0/tests/bootstrap/test_session_api.py +278 -0
  238. exactdocs-0.2.0/tests/bootstrap/test_setup.py +127 -0
  239. exactdocs-0.2.0/tests/bootstrap/test_stdio.py +221 -0
  240. exactdocs-0.2.0/tests/bootstrap/test_sync_api.py +167 -0
  241. exactdocs-0.2.0/tests/bootstrap/test_tokens_api.py +283 -0
  242. exactdocs-0.2.0/tests/catalog/test_library_repository.py +229 -0
  243. exactdocs-0.2.0/tests/catalog/test_library_text.py +235 -0
  244. exactdocs-0.2.0/tests/catalog/test_manifest_repository.py +792 -0
  245. exactdocs-0.2.0/tests/catalog/test_manifest_use_cases.py +506 -0
  246. exactdocs-0.2.0/tests/catalog/test_models.py +293 -0
  247. exactdocs-0.2.0/tests/catalog/test_tag_selection.py +39 -0
  248. exactdocs-0.2.0/tests/catalog/test_use_cases.py +229 -0
  249. exactdocs-0.2.0/tests/conftest.py +122 -0
  250. exactdocs-0.2.0/tests/contracts/context7/README.md +47 -0
  251. exactdocs-0.2.0/tests/contracts/context7/query-docs.json +12 -0
  252. exactdocs-0.2.0/tests/contracts/context7/resolve-library-id.json +11 -0
  253. exactdocs-0.2.0/tests/contracts/context7/session.json +27 -0
  254. exactdocs-0.2.0/tests/fixtures/docs-repo/.gitattributes +1 -0
  255. exactdocs-0.2.0/tests/fixtures/docs-repo/README.md +119 -0
  256. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/next/docs/guide/install.md +53 -0
  257. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/next/docs/guide/migration.md +7 -0
  258. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/AGENTS.md +5 -0
  259. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/README.md +7 -0
  260. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/api/auth.md +24 -0
  261. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/api/client.mdx +31 -0
  262. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/archive/old-release.md +3 -0
  263. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/assets/logo.png +0 -0
  264. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/guide/auth.md +25 -0
  265. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/guide/callouts.md +21 -0
  266. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/guide/configuration.md +20 -0
  267. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/guide/setup.md +45 -0
  268. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/index.md +9 -0
  269. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/legacy/binary.md +0 -0
  270. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/legacy/latin1.md +12 -0
  271. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/legacy/undecodable.md +3 -0
  272. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/notes.md +3 -0
  273. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.3.0/docs/reference/naming.md +19 -0
  274. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.4.0/docs/api/client.mdx +38 -0
  275. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.4.0/docs/guide/install.md +45 -0
  276. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.4.0/docs/guide/resilience.md +38 -0
  277. exactdocs-0.2.0/tests/fixtures/docs-repo/commits/v1.4.0/docs/reference/errors.md +12 -0
  278. exactdocs-0.2.0/tests/fixtures/docs-repo/expected-queries.yaml +65 -0
  279. exactdocs-0.2.0/tests/fixtures/docs-repo/history.toml +29 -0
  280. exactdocs-0.2.0/tests/fixtures/fixture_repo.py +260 -0
  281. exactdocs-0.2.0/tests/fixtures/heading_anchors.json +115 -0
  282. exactdocs-0.2.0/tests/fixtures/test_fixture_repo.py +226 -0
  283. exactdocs-0.2.0/tests/identity/test_account_repository.py +81 -0
  284. exactdocs-0.2.0/tests/identity/test_argon2id_hasher.py +43 -0
  285. exactdocs-0.2.0/tests/identity/test_session_repository.py +110 -0
  286. exactdocs-0.2.0/tests/identity/test_sessions.py +256 -0
  287. exactdocs-0.2.0/tests/identity/test_token_repository.py +102 -0
  288. exactdocs-0.2.0/tests/identity/test_tokens.py +224 -0
  289. exactdocs-0.2.0/tests/identity/test_use_cases.py +223 -0
  290. exactdocs-0.2.0/tests/ingestion/conftest.py +85 -0
  291. exactdocs-0.2.0/tests/ingestion/fixtures/blank-title.md +4 -0
  292. exactdocs-0.2.0/tests/ingestion/fixtures/code-first.md +5 -0
  293. exactdocs-0.2.0/tests/ingestion/fixtures/empty-front-matter.md +3 -0
  294. exactdocs-0.2.0/tests/ingestion/fixtures/guide/auth.md +3 -0
  295. exactdocs-0.2.0/tests/ingestion/fixtures/guide/install.md +10 -0
  296. exactdocs-0.2.0/tests/ingestion/fixtures/malformed.md +7 -0
  297. exactdocs-0.2.0/tests/ingestion/fixtures/mdx-page.mdx +13 -0
  298. exactdocs-0.2.0/tests/ingestion/fixtures/not-a-mapping.md +4 -0
  299. exactdocs-0.2.0/tests/ingestion/fixtures/notes.md +1 -0
  300. exactdocs-0.2.0/tests/ingestion/fixtures/setext.md +8 -0
  301. exactdocs-0.2.0/tests/ingestion/test_add_library.py +277 -0
  302. exactdocs-0.2.0/tests/ingestion/test_add_version.py +208 -0
  303. exactdocs-0.2.0/tests/ingestion/test_check_repository.py +123 -0
  304. exactdocs-0.2.0/tests/ingestion/test_chunker.py +298 -0
  305. exactdocs-0.2.0/tests/ingestion/test_credentials.py +361 -0
  306. exactdocs-0.2.0/tests/ingestion/test_git_cli.py +345 -0
  307. exactdocs-0.2.0/tests/ingestion/test_git_credentials.py +357 -0
  308. exactdocs-0.2.0/tests/ingestion/test_git_fetch.py +506 -0
  309. exactdocs-0.2.0/tests/ingestion/test_import_version.py +802 -0
  310. exactdocs-0.2.0/tests/ingestion/test_job_repository.py +231 -0
  311. exactdocs-0.2.0/tests/ingestion/test_job_use_cases.py +267 -0
  312. exactdocs-0.2.0/tests/ingestion/test_job_worker.py +129 -0
  313. exactdocs-0.2.0/tests/ingestion/test_parse_article.py +235 -0
  314. exactdocs-0.2.0/tests/ingestion/test_path_selection.py +172 -0
  315. exactdocs-0.2.0/tests/ingestion/test_readme.py +51 -0
  316. exactdocs-0.2.0/tests/ingestion/test_removal.py +348 -0
  317. exactdocs-0.2.0/tests/ingestion/test_repository_url.py +159 -0
  318. exactdocs-0.2.0/tests/ingestion/test_resolve_ref.py +141 -0
  319. exactdocs-0.2.0/tests/ingestion/test_restart_recovery.py +104 -0
  320. exactdocs-0.2.0/tests/ingestion/test_secret_box.py +310 -0
  321. exactdocs-0.2.0/tests/ingestion/test_sync_library.py +382 -0
  322. exactdocs-0.2.0/tests/ingestion/test_tags.py +84 -0
  323. exactdocs-0.2.0/tests/retrieval/ranking/conftest.py +117 -0
  324. exactdocs-0.2.0/tests/retrieval/ranking/test_ranking.py +72 -0
  325. exactdocs-0.2.0/tests/retrieval/test_chunk_id.py +66 -0
  326. exactdocs-0.2.0/tests/retrieval/test_chunk_store.py +624 -0
  327. exactdocs-0.2.0/tests/retrieval/test_identifier_tokens.py +81 -0
  328. exactdocs-0.2.0/tests/retrieval/test_query_docs.py +255 -0
  329. exactdocs-0.2.0/tests/retrieval/test_resolve_library_id.py +315 -0
  330. exactdocs-0.2.0/tests/retrieval/test_search_docs.py +335 -0
  331. exactdocs-0.2.0/tests/retrieval/test_search_index.py +409 -0
  332. exactdocs-0.2.0/tests/retrieval/test_source_link.py +73 -0
  333. exactdocs-0.1.1/features/catalog.feature +0 -20
  334. exactdocs-0.1.1/features/search.feature +0 -17
  335. exactdocs-0.1.1/features/steps/http_steps.py +0 -56
  336. exactdocs-0.1.1/src/exactdocs/_web/assets/index-B350HwjP.js +0 -10
  337. exactdocs-0.1.1/src/exactdocs/_web/assets/index-CG8rwfz9.css +0 -2
  338. exactdocs-0.1.1/src/exactdocs/_web/assets/routes-CCGZxbYl.js +0 -1
  339. exactdocs-0.1.1/src/exactdocs/_web/index.html +0 -13
  340. exactdocs-0.1.1/src/exactdocs/bootstrap/__init__.py +0 -5
  341. exactdocs-0.1.1/src/exactdocs/bootstrap/bridges.py +0 -27
  342. exactdocs-0.1.1/src/exactdocs/bootstrap/cli.py +0 -48
  343. exactdocs-0.1.1/src/exactdocs/bootstrap/container.py +0 -28
  344. exactdocs-0.1.1/src/exactdocs/bootstrap/http.py +0 -90
  345. exactdocs-0.1.1/src/exactdocs/bootstrap/mcp.py +0 -21
  346. exactdocs-0.1.1/src/exactdocs/bootstrap/settings.py +0 -15
  347. exactdocs-0.1.1/src/exactdocs/catalog/adapters/driven/memory.py +0 -35
  348. exactdocs-0.1.1/src/exactdocs/catalog/adapters/driving/http.py +0 -21
  349. exactdocs-0.1.1/src/exactdocs/catalog/adapters/driving/mcp.py +0 -18
  350. exactdocs-0.1.1/src/exactdocs/catalog/dtos.py +0 -42
  351. exactdocs-0.1.1/src/exactdocs/catalog/errors.py +0 -16
  352. exactdocs-0.1.1/src/exactdocs/catalog/models.py +0 -34
  353. exactdocs-0.1.1/src/exactdocs/catalog/module.py +0 -27
  354. exactdocs-0.1.1/src/exactdocs/catalog/ports/__init__.py +0 -4
  355. exactdocs-0.1.1/src/exactdocs/catalog/ports/driven.py +0 -18
  356. exactdocs-0.1.1/src/exactdocs/catalog/ports/driving.py +0 -21
  357. exactdocs-0.1.1/src/exactdocs/catalog/use_cases.py +0 -33
  358. exactdocs-0.1.1/src/exactdocs/retrieval/adapters/driven/memory.py +0 -26
  359. exactdocs-0.1.1/src/exactdocs/retrieval/adapters/driving/http.py +0 -17
  360. exactdocs-0.1.1/src/exactdocs/retrieval/adapters/driving/mcp.py +0 -28
  361. exactdocs-0.1.1/src/exactdocs/retrieval/dtos.py +0 -53
  362. exactdocs-0.1.1/src/exactdocs/retrieval/errors.py +0 -17
  363. exactdocs-0.1.1/src/exactdocs/retrieval/models.py +0 -34
  364. exactdocs-0.1.1/src/exactdocs/retrieval/module.py +0 -18
  365. exactdocs-0.1.1/src/exactdocs/retrieval/ports/__init__.py +0 -4
  366. exactdocs-0.1.1/src/exactdocs/retrieval/ports/driven.py +0 -26
  367. exactdocs-0.1.1/src/exactdocs/retrieval/ports/driving.py +0 -13
  368. exactdocs-0.1.1/src/exactdocs/retrieval/use_cases.py +0 -22
  369. exactdocs-0.1.1/tests/bootstrap/test_app.py +0 -64
  370. exactdocs-0.1.1/tests/catalog/test_use_cases.py +0 -42
  371. exactdocs-0.1.1/tests/retrieval/test_search_docs.py +0 -54
  372. {exactdocs-0.1.1 → exactdocs-0.2.0}/README.md +0 -0
  373. {exactdocs-0.1.1 → exactdocs-0.2.0}/src/exactdocs/__init__.py +0 -0
  374. {exactdocs-0.1.1 → exactdocs-0.2.0}/src/exactdocs/__main__.py +0 -0
  375. {exactdocs-0.1.1 → exactdocs-0.2.0}/src/exactdocs/_version.py +0 -0
  376. {exactdocs-0.1.1/src/exactdocs/catalog/adapters → exactdocs-0.2.0/src/exactdocs/bootstrap/migrations}/__init__.py +0 -0
  377. {exactdocs-0.1.1/src/exactdocs/catalog/adapters/driven → exactdocs-0.2.0/src/exactdocs/bootstrap/migrations/versions}/__init__.py +0 -0
  378. {exactdocs-0.1.1 → exactdocs-0.2.0}/src/exactdocs/catalog/__init__.py +0 -0
  379. {exactdocs-0.1.1/src/exactdocs/catalog/adapters/driving → exactdocs-0.2.0/src/exactdocs/catalog/adapters}/__init__.py +0 -0
  380. {exactdocs-0.1.1/src/exactdocs/retrieval/adapters → exactdocs-0.2.0/src/exactdocs/catalog/adapters/driven}/__init__.py +0 -0
  381. {exactdocs-0.1.1/src/exactdocs/retrieval/adapters/driven → exactdocs-0.2.0/src/exactdocs/catalog/adapters/driving}/__init__.py +0 -0
  382. {exactdocs-0.1.1/src/exactdocs/retrieval/adapters/driving → exactdocs-0.2.0/src/exactdocs/identity/adapters}/__init__.py +0 -0
  383. /exactdocs-0.1.1/src/exactdocs/py.typed → /exactdocs-0.2.0/src/exactdocs/identity/adapters/driven/__init__.py +0 -0
  384. {exactdocs-0.1.1 → exactdocs-0.2.0}/src/exactdocs/retrieval/__init__.py +0 -0
@@ -12,6 +12,9 @@ coverage.xml
12
12
  htmlcov/
13
13
  reports/
14
14
 
15
+ # Zensical output
16
+ site/
17
+
15
18
  # Built web UI bundled into the wheel / image (see web/README.md)
16
19
  packages/exactdocs/src/exactdocs/_web/
17
20
 
@@ -31,3 +34,8 @@ Thumbs.db
31
34
 
32
35
  # Claude Code local settings
33
36
  .claude/settings.local.json
37
+
38
+ # Browser end-to-end run (e2e/)
39
+ e2e/test-results/
40
+ e2e/shots/
41
+ e2e/node_modules/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: exactdocs
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: Self-hosted, version-accurate documentation for AI coding agents (MCP server).
5
5
  Project-URL: Repository, https://github.com/SirCypkowskyy/ExactDocs
6
6
  Project-URL: Issues, https://github.com/SirCypkowskyy/ExactDocs/issues
@@ -12,13 +12,21 @@ Classifier: Framework :: FastAPI
12
12
  Classifier: Programming Language :: Python :: 3.14
13
13
  Classifier: Typing :: Typed
14
14
  Requires-Python: >=3.14
15
+ Requires-Dist: aiosqlite>=0.21
16
+ Requires-Dist: alembic>=1.16
17
+ Requires-Dist: argon2-cffi>=25
18
+ Requires-Dist: cryptography>=46
15
19
  Requires-Dist: cyclopts>=4
16
- Requires-Dist: exactdocs-kernel==0.1.1
20
+ Requires-Dist: exactdocs-kernel==0.2.0
17
21
  Requires-Dist: fastapi>=0.120
18
22
  Requires-Dist: fastmcp>=4
23
+ Requires-Dist: markdown-it-py>=4
19
24
  Requires-Dist: mcp>=2
25
+ Requires-Dist: platformdirs>=4
20
26
  Requires-Dist: pydantic-settings>=2.11
21
27
  Requires-Dist: pydantic>=2.12
28
+ Requires-Dist: pyyaml>=6
29
+ Requires-Dist: sqlalchemy[asyncio]>=2.0
22
30
  Requires-Dist: uvicorn[standard]>=0.38
23
31
  Description-Content-Type: text/markdown
24
32
 
@@ -0,0 +1,5 @@
1
+ # Only for authoring revisions: just migration "what changed"
2
+ # (migrations run in code at startup, see exactdocs.bootstrap.database).
3
+ [alembic]
4
+ script_location = %(here)s/src/exactdocs/bootstrap/migrations
5
+ file_template = %%(year)d%%(month).2d%%(day).2d_%%(rev)s_%%(slug)s
@@ -0,0 +1,55 @@
1
+ Feature: Adding a library with its doc selection
2
+ As an admin
3
+ I want to create a library from a repository, choosing what to import and which ref
4
+ So that every import says exactly which commit it took
5
+
6
+ Background:
7
+ Given ExactDocs is running with the demo catalog
8
+
9
+ Scenario: Without a ref the default branch is imported
10
+ When a user adds the fixture repository with paths "docs/" and no ref
11
+ Then the library has a version for the default branch
12
+ And that version records the branch head commit SHA
13
+ And an import job is queued for that version
14
+
15
+ Scenario Outline: A tag, a branch or a commit is pinned to its commit
16
+ When a user adds the fixture repository with paths "docs/" and the ref "<ref>"
17
+ Then the version is "<ref>" and records the commit of "<commit>"
18
+
19
+ Examples:
20
+ | ref | commit |
21
+ | v1.3.0 | v1.3.0 |
22
+ | next | next |
23
+
24
+ Scenario: A short commit SHA becomes the full one
25
+ When a user adds the fixture repository with paths "docs/" and the short SHA of "v1.4.0"
26
+ Then the version is the full SHA of "v1.4.0"
27
+
28
+ Scenario: An unknown ref creates nothing
29
+ When a user adds the fixture repository with paths "docs/" and the ref "v9.9.9"
30
+ Then the request fails with "unknown_ref"
31
+ And the library "/fixture/docs-repo" does not exist
32
+
33
+ Scenario: Exclusions are kept with the library and win over inclusions
34
+ When a user adds the fixture repository with paths "docs/**" and exclusion "docs/archive/**"
35
+ Then the library keeps the inclusion "docs/**" and the exclusion "docs/archive/**"
36
+
37
+ Scenario: Tag rules are kept with the library, normalized
38
+ When a user adds the fixture repository with paths "docs/**" and the tag rule "docs/api/** -> API"
39
+ Then the library keeps the tag rule "docs/api/** -> api"
40
+
41
+ Scenario: A tag rule that cannot work is refused
42
+ When a user adds the fixture repository with paths "docs/**" and the tag rule "docs/api/** -> not a tag!"
43
+ Then the request fails with "invalid_tag"
44
+
45
+ Scenario: A pattern that cannot work is refused
46
+ When a user adds the fixture repository with paths "../secrets" and no ref
47
+ Then the request fails with "invalid_path_pattern"
48
+
49
+ Scenario: A taken ID asks for another one
50
+ When a user adds the fixture repository as "/acme/payments"
51
+ Then the request fails with "library_id_taken"
52
+
53
+ Scenario: The picker lists the tags and branches
54
+ When a user lists the refs of the fixture repository
55
+ Then the default branch is "main", the branches are "main, next" and the tags are "v1.3.0, v1.4.0"
@@ -0,0 +1,43 @@
1
+ Feature: Adding another version to a library
2
+ As a team working on projects pinned to different releases
3
+ I want to add a version next to the ones a library has
4
+ So that every version is imported with exactly the same settings
5
+
6
+ Background:
7
+ Given ExactDocs imports the fixture docs repository
8
+
9
+ Scenario: A second version uses the library's settings
10
+ Given the fixture repository is imported at "v1.4.0" with paths "docs/**" excluding "docs/archive/**"
11
+ When the user adds the version "v1.3.0"
12
+ Then both "v1.3.0" and "v1.4.0" are listed
13
+ And "/fixture/docs-repo/v1.3.0" has the article "docs/guide/auth.md"
14
+ And "/fixture/docs-repo/v1.3.0" has no article "docs/archive/old-release.md"
15
+
16
+ Scenario: The answer points at the import job
17
+ Given "/fixture/docs-repo/v1.4.0" is imported
18
+ When the user adds the version "v1.3.0"
19
+ Then the response says the import was accepted and points to its job
20
+
21
+ Scenario: A newer release becomes the default version
22
+ Given "/fixture/docs-repo/v1.3.0" is imported
23
+ When the user adds the version "v1.4.0"
24
+ Then the default version is "v1.4.0"
25
+
26
+ Scenario: A branch does not take the default from a release
27
+ Given "/fixture/docs-repo/v1.4.0" is imported
28
+ When the user adds the version "next"
29
+ Then the default version is "v1.4.0"
30
+
31
+ Scenario: A version the library already has is refused
32
+ Given "/fixture/docs-repo/v1.4.0" is imported
33
+ When the user adds the version "v1.4.0"
34
+ Then adding the version fails with "version_exists"
35
+
36
+ Scenario: An unknown ref queues nothing
37
+ Given "/fixture/docs-repo/v1.4.0" is imported
38
+ When the user adds the version "v9.9.9"
39
+ Then adding the version fails with "unknown_ref"
40
+
41
+ Scenario: An unknown library is not found
42
+ When the user adds "v1.3.0" to the library "/nobody/nothing"
43
+ Then the library is not found
@@ -0,0 +1,17 @@
1
+ Feature: Versioned REST API
2
+ As a developer of the web UI or the CLI
3
+ I want one documented API with stable conventions
4
+ So that every client behaves the same against any instance
5
+
6
+ Background:
7
+ Given ExactDocs is running with the demo catalog
8
+
9
+ Scenario: The API describes itself
10
+ When I fetch the OpenAPI description
11
+ Then every operation has a stable operation ID and documented error responses
12
+
13
+ Scenario: Errors have one shape
14
+ When I request the library "/vuejs/docs"
15
+ Then the response status is 404
16
+ And the error code is "not_found"
17
+ And the error has a message
@@ -0,0 +1,90 @@
1
+ Feature: Version manifests
2
+ As an AI coding agent
3
+ I want a version's articles to change all at once, never piece by piece
4
+ So that I never read half of a re-synced version
5
+
6
+ Background:
7
+ Given ExactDocs is running with the demo catalog
8
+ And "/acme/payments" version "main" has the active manifest at commit "aaa111":
9
+ | path | title | content |
10
+ | guide/install.md | Install | install, old |
11
+ | guide/auth.md | Auth | auth |
12
+ | api/client.md | Client | client |
13
+
14
+ Scenario: Reading an article returns the active manifest, not a pending one
15
+ Given "/acme/payments" version "main" has a pending manifest at commit "bbb222":
16
+ | path | title | content |
17
+ | guide/install.md | Install | install, new |
18
+ When I read the article "guide/install.md" of "/acme/payments" version "main"
19
+ Then the response status is 200
20
+ And the article says "install, old" at commit "aaa111"
21
+
22
+ Scenario: Activating a manifest swaps the whole version at once
23
+ Given "/acme/payments" version "main" has a pending manifest at commit "bbb222":
24
+ | path | title | content |
25
+ | guide/install.md | Install | install, new |
26
+ | guide/new.md | New | brand new |
27
+ When the pending manifest is activated
28
+ And I read the article "guide/install.md" of "/acme/payments" version "main"
29
+ Then the article says "install, new" at commit "bbb222"
30
+ When I list the articles of "/acme/payments" version "main"
31
+ Then the articles are "guide/install.md, guide/new.md" at commit "bbb222"
32
+ When I request the version "/acme/payments/main"
33
+ Then the version used is "main" of "/acme/payments"
34
+ And the commit of the version is "bbb222"
35
+
36
+ Scenario: Browsing without a ref uses the default version
37
+ Given "/acme/payments" version "v1.4.0" has the active manifest at commit "ccc333":
38
+ | path | title | content |
39
+ | guide/install.md | Install | install, v1.4 |
40
+ When I read the article "guide/install.md" of "/acme/payments" without a ref
41
+ Then the article says "install, v1.4" at commit "ccc333"
42
+
43
+ Scenario: Articles can be listed under a path prefix
44
+ When I list the articles under "guide/" of "/acme/payments" version "main"
45
+ Then the articles are "guide/auth.md, guide/install.md" at commit "aaa111"
46
+
47
+ Scenario: A long list is paged with a cursor
48
+ When I list 2 articles of "/acme/payments" version "main"
49
+ Then the articles are "api/client.md, guide/auth.md" at commit "aaa111"
50
+ And there is a next page
51
+
52
+ Scenario: An article that is not in the version is not found
53
+ When I read the article "guide/missing.md" of "/acme/payments" version "main"
54
+ Then the response status is 404
55
+ And the error code is "not_found"
56
+
57
+ Scenario: A version without an active manifest has no articles
58
+ When I read the article "guide/install.md" of "/acme/payments" version "next"
59
+ Then the response status is 404
60
+ And the error code is "not_found"
61
+
62
+ Scenario Outline: Listing filters by tags before paging
63
+ Given "/acme/payments" version "main" has the active manifest at commit "ddd444":
64
+ | path | title | content | tags |
65
+ | adr/0001.md | ADR | adr | adr |
66
+ | api/auth.md | Auth | auth | api,auth |
67
+ | api/pay.md | Pay | pay | api |
68
+ | guide/faq.md | FAQ | faq | |
69
+ When I filter the articles of "/acme/payments" version "main" by <match> of tags "<tags>"
70
+ Then the articles are "<paths>" at commit "ddd444"
71
+
72
+ Examples:
73
+ | match | tags | paths |
74
+ | any | adr, API | adr/0001.md, api/auth.md, api/pay.md |
75
+ | all | api,auth | api/auth.md |
76
+
77
+ Scenario: A filtered list is paged over the matches only
78
+ Given "/acme/payments" version "main" has the active manifest at commit "ddd444":
79
+ | path | title | content | tags |
80
+ | a.md | A | a | |
81
+ | b.md | B | b | api |
82
+ | c.md | C | c | |
83
+ | d.md | D | d | api |
84
+ When I filter 1 articles of "/acme/payments" version "main" by any of tags "api"
85
+ Then the articles are "b.md" at commit "ddd444"
86
+ And there is a next page
87
+
88
+ Scenario: A filter that is not a tag is refused
89
+ When I filter the articles of "/acme/payments" version "main" by any of tags "not a tag!"
90
+ Then the response status is 422
@@ -0,0 +1,43 @@
1
+ Feature: Library catalog
2
+ As an AI coding agent
3
+ I want to address a library and a version by a predictable ID
4
+ So that I always get the version I asked for, never a plausible neighbour
5
+
6
+ Background:
7
+ Given ExactDocs is running with the demo catalog
8
+
9
+ Scenario: Listing indexed libraries shows each default version
10
+ When I list the libraries over the REST API
11
+ Then the response status is 200
12
+ And the libraries are:
13
+ | id | versions | default version |
14
+ | /acme/ledger | main | main |
15
+ | /acme/payments | main, next, v1.3.0, v1.4.0 | v1.4.0 |
16
+
17
+ Scenario: A library ID without a ref means its default version
18
+ When I request the library "/acme/payments"
19
+ Then the response status is 200
20
+ And the default version of "/acme/payments" is "v1.4.0"
21
+
22
+ Scenario: The library ID is not case-sensitive
23
+ When I request the library "/Acme/Payments"
24
+ Then the default version of "/acme/payments" is "v1.4.0"
25
+
26
+ Scenario: A ref names exactly that version
27
+ When I request the version "/acme/payments/v1.3.0"
28
+ Then the version used is "v1.3.0" of "/acme/payments"
29
+
30
+ Scenario: Refs are case-sensitive and never approximated
31
+ When I request the version "/acme/payments/V1.3.0"
32
+ Then the response status is 404
33
+ And the error code is "not_found"
34
+
35
+ Scenario: A malformed ID is rejected
36
+ When I request the library "/acme/pay$ments"
37
+ Then the response status is 422
38
+ And the error code is "validation_error"
39
+
40
+ Scenario: Asking for a library that is not indexed
41
+ When I request the library "/vuejs/docs"
42
+ Then the response status is 404
43
+ And the error code is "not_found"
@@ -0,0 +1,24 @@
1
+ Feature: Heading-based chunks
2
+ As an AI coding agent
3
+ I want articles cut at their headings and never inside a code block or table
4
+ So that a search result is a section I can use as it is
5
+
6
+ Background:
7
+ Given the fixture docs repository is built and served
8
+
9
+ Scenario: A section is addressed by its heading path
10
+ When I chunk "docs/guide/install.md" of version "v1.4.0"
11
+ Then there is a chunk with the heading path "Guide > Install > Docker"
12
+ And that chunk keeps its fenced "bash" block whole
13
+
14
+ Scenario: The install table stays in one chunk
15
+ When I chunk "docs/guide/install.md" of version "v1.4.0"
16
+ Then the chunk "Guide > Install" holds the whole platform table
17
+
18
+ Scenario: Repeated headings are told apart
19
+ When I chunk "docs/guide/install.md" of version "v1.4.0"
20
+ Then the chunks "Guide > Install > Docker > Troubleshooting" and "Guide > Install > From source > Troubleshooting" have anchors "troubleshooting" and "troubleshooting-1"
21
+
22
+ Scenario: A parser change re-chunks
23
+ Given the parser version is bumped
24
+ Then the chunking-config hash differs from before
@@ -0,0 +1,61 @@
1
+ Feature: Version-scoped chunk IDs
2
+ As an AI coding agent
3
+ I want a chunk ID to name exactly one chunk of one version
4
+ So that read_chunk is unambiguous and an ID survives a re-sync when its section did not change
5
+
6
+ Background:
7
+ Given ExactDocs is running with the demo catalog
8
+
9
+ Scenario: An unchanged article keeps its chunk IDs when the branch is re-synced
10
+ Given "/acme/payments" version "main" is indexed as "first" at commit "aaa111":
11
+ | path | text |
12
+ | guide/auth.md | auth |
13
+ | guide/install.md | install v1 |
14
+ When "/acme/payments" version "main" is indexed as "second" at commit "bbb222":
15
+ | path | text |
16
+ | guide/auth.md | auth |
17
+ | guide/install.md | install v2 |
18
+ Then every chunk of "guide/auth.md" has the same ID in "first" and "second"
19
+ And no chunk of "guide/install.md" has the same ID in "first" and "second"
20
+
21
+ Scenario: The same content in two versions is stored once under different chunk IDs
22
+ Given "/acme/payments" version "v1.3.0" is indexed as "old" at commit "aaa111":
23
+ | path | text |
24
+ | guide/install.md | install |
25
+ When "/acme/payments" version "v1.4.0" is indexed as "new" at commit "bbb222":
26
+ | path | text |
27
+ | guide/install.md | install |
28
+ Then "new" stored no chunk set of its own
29
+ And no chunk of "guide/install.md" has the same ID in "old" and "new"
30
+
31
+ Scenario: Indexing the same manifest twice stores nothing new
32
+ Given "/acme/payments" version "main" is indexed as "first" at commit "aaa111":
33
+ | path | text |
34
+ | guide/install.md | install |
35
+ When the manifest "first" is indexed again
36
+ Then nothing new was stored
37
+ And every chunk of "guide/install.md" has the same ID in "first" and "first again"
38
+
39
+ Scenario: A chunk can be read only once its manifest is active
40
+ Given "/acme/payments" version "main" is indexed as "first" at commit "aaa111":
41
+ | path | text |
42
+ | guide/install.md | install |
43
+ When I read the second chunk of "guide/install.md" in "first"
44
+ Then the chunk cannot be found
45
+ When the manifest "first" is activated
46
+ And I read the second chunk of "guide/install.md" in "first"
47
+ Then the chunk says "install two" and its previous chunk is the first one
48
+
49
+ Scenario: A chunk ID from a superseded manifest says it is stale
50
+ Given "/acme/payments" version "main" is indexed as "first" at commit "aaa111":
51
+ | path | text |
52
+ | guide/install.md | install v1 |
53
+ When the manifest "first" is activated
54
+ And "/acme/payments" version "main" is indexed as "second" at commit "bbb222":
55
+ | path | text |
56
+ | guide/install.md | install v2 |
57
+ When the manifest "second" is activated
58
+ And I read the second chunk of "guide/install.md" in "first"
59
+ Then the chunk is stale and says how to find the current one
60
+ When I read the second chunk of "guide/install.md" in "second"
61
+ Then the chunk says "install v2 two" and its previous chunk is the first one
@@ -0,0 +1,25 @@
1
+ """Behave hooks: build the fixture docs repository once per run and serve it on 127.0.0.1."""
2
+
3
+ import sys
4
+ import tempfile
5
+ from contextlib import ExitStack
6
+ from importlib import import_module
7
+ from pathlib import Path
8
+
9
+ from behave.runner import Context
10
+
11
+ # The fixture builder is test support shared with pytest, not part of the package. behave has no
12
+ # pythonpath option; import_module keeps ruff (E402) and ty from needing the path at lint time.
13
+ sys.path.insert(0, str(Path(__file__).parents[1] / "tests" / "fixtures"))
14
+ fixture_repo = import_module("fixture_repo")
15
+
16
+
17
+ def before_all(context: Context) -> None:
18
+ context.stack = ExitStack()
19
+ base = Path(context.stack.enter_context(tempfile.TemporaryDirectory(prefix="exactdocs-fx-")))
20
+ context.fixture_repo = context.stack.enter_context(fixture_repo.served(base))
21
+ context.fixture_git = fixture_repo.git
22
+
23
+
24
+ def after_all(context: Context) -> None:
25
+ context.stack.close()
@@ -0,0 +1,18 @@
1
+ Feature: A versioned fixture docs repository
2
+ Import and search are asserted against one stable git repository, served over both transports
3
+ that the importer supports.
4
+
5
+ Background:
6
+ Given the fixture docs repository is built and served
7
+
8
+ Scenario Outline: Both git servers advertise the same versions
9
+ When I list the remote refs of the fixture repository over <transport>
10
+ Then the remote has the tags "v1.3.0, v1.4.0" and the branch "next"
11
+
12
+ Examples:
13
+ | transport |
14
+ | file |
15
+ | http |
16
+
17
+ Scenario: An identifier exists in one version only
18
+ Then "retry_policy" occurs in version "v1.4.0" but not in version "v1.3.0"
@@ -0,0 +1,51 @@
1
+ Feature: An import publishes a complete version or nothing
2
+ As an AI coding agent
3
+ I want a version to appear all at once, with the commit it was imported from
4
+ So that I never read half of a version, and a failed import never hides what was served before
5
+
6
+ Background:
7
+ Given ExactDocs imports the fixture docs repository
8
+
9
+ Scenario: An import publishes the whole version at once
10
+ When "docs/" of the fixture repository at "v1.3.0" is imported
11
+ Then the import succeeds and reports every file as processed
12
+ And "/fixture/docs-repo/v1.3.0" lists every selected article
13
+ And every article and chunk carries the commit SHA of "v1.3.0"
14
+
15
+ Scenario: One bad file does not fail the import
16
+ When "docs/" of the fixture repository at "v1.3.0" is imported
17
+ Then the import succeeds
18
+ And the job lists these skipped files:
19
+ | path | code | reason |
20
+ | docs/legacy/binary.md | binary | binary file |
21
+ | docs/legacy/undecodable.md | undecodable | not text in any supported encoding |
22
+ | docs/reference/oversized.md | too_large | larger than 2097152 bytes |
23
+
24
+ Scenario: A Latin-1 file is decoded, not skipped
25
+ When "docs/" of the fixture repository at "v1.3.0" is imported
26
+ Then the article "docs/legacy/latin1.md" of "/fixture/docs-repo/v1.3.0" keeps its accented characters
27
+
28
+ Scenario: A failed import leaves the previous state served
29
+ Given "/fixture/docs-repo/next" is imported
30
+ When a re-import of "next" fails before activation
31
+ Then "/fixture/docs-repo/next" still serves the previous commit
32
+
33
+ Scenario: Versions stay apart
34
+ Given "/fixture/docs-repo/v1.3.0" is imported
35
+ When "docs/" of the fixture repository at "v1.4.0" is imported
36
+ Then "/fixture/docs-repo/v1.4.0" has the article "docs/guide/resilience.md"
37
+ And "/fixture/docs-repo/v1.3.0" has no article "docs/guide/resilience.md"
38
+
39
+ Scenario: Re-importing a commit changes nothing
40
+ Given "/fixture/docs-repo/v1.3.0" is imported
41
+ When "v1.3.0" is imported again
42
+ Then "/fixture/docs-repo/v1.3.0" lists the same articles and chunks as before
43
+
44
+ Scenario: Exclusions win over inclusions
45
+ When "docs/**" of the fixture repository at "v1.3.0" is imported excluding "docs/archive/**"
46
+ Then "/fixture/docs-repo/v1.3.0" has no article "docs/archive/old-release.md"
47
+ And "/fixture/docs-repo/v1.3.0" has the article "docs/guide/auth.md"
48
+
49
+ Scenario: Front-matter and path-rule tags combine
50
+ When "docs/**" of the fixture repository at "v1.3.0" is imported with the tag rule "docs/api/** -> api"
51
+ Then the article "docs/api/auth.md" of "/fixture/docs-repo/v1.3.0" is tagged "api" and "auth"
@@ -0,0 +1,53 @@
1
+ Feature: Safe article parsing
2
+ As an AI coding agent
3
+ I want article titles to be right and hidden instructions to be gone
4
+ So that I can trust what I read from a doc repository
5
+
6
+ Scenario: The article title follows front matter, then H1, then file name
7
+ Given the article "guide/install.md":
8
+ """
9
+ ---
10
+ title: Install ExactDocs
11
+ ---
12
+ # Getting started
13
+ """
14
+ And the article "guide/auth.md":
15
+ """
16
+ # Authentication
17
+ """
18
+ And the article "notes.md":
19
+ """
20
+ Just some notes.
21
+ """
22
+ When the articles are imported
23
+ Then the title of "guide/install.md" is "Install ExactDocs"
24
+ And the title of "guide/auth.md" is "Authentication"
25
+ And the title of "notes.md" is "notes"
26
+
27
+ Scenario: Hidden characters are removed on import
28
+ Given the article "hidden.md" contains a zero-width space and a bidi override
29
+ When the articles are imported
30
+ Then the stored Markdown of "hidden.md" contains neither
31
+ And the stored Markdown of "hidden.md" is otherwise identical to the source
32
+
33
+ Scenario: Malformed front matter does not stop the import
34
+ Given the article "broken.md":
35
+ """
36
+ ---
37
+ title: [unclosed
38
+ ---
39
+ # Still here
40
+ """
41
+ When the articles are imported
42
+ Then the title of "broken.md" is "Still here"
43
+ And "broken.md" is listed with the problem "malformed front matter"
44
+
45
+ Scenario: MDX components are kept as text
46
+ Given the article "tabs.mdx":
47
+ """
48
+ import { Tabs } from '@site/components'
49
+
50
+ <Tabs>npm i exactdocs</Tabs>
51
+ """
52
+ When the articles are imported
53
+ Then the stored Markdown of "tabs.mdx" is identical to the source
@@ -0,0 +1,26 @@
1
+ Feature: Background jobs
2
+ As someone importing a large documentation repository
3
+ I want long tasks to run in the background and show how far they got
4
+ So that a request never blocks and I can tell a slow import from a stuck one
5
+
6
+ Background:
7
+ Given ExactDocs is running
8
+
9
+ Scenario: Seeing how far a job got
10
+ Given a job over 10 files of "/acme/payments" that has finished 4
11
+ When I request that job
12
+ Then the response status is 200
13
+ And the job is running with 4 of 10 files done
14
+
15
+ Scenario: Listing the jobs of a library, newest first
16
+ Given a finished job "job-1" of "/acme/payments"
17
+ And a finished job "job-2" of "/acme/payments"
18
+ And a finished job "job-3" of "/acme/ledger"
19
+ When I list the jobs of the library "/acme/payments"
20
+ Then the response status is 200
21
+ And the jobs are, newest first: "job-2", "job-1"
22
+
23
+ Scenario: Asking for a job that does not exist
24
+ When I request the job "no-such-job"
25
+ Then the response status is 404
26
+ And the error code is "not_found"
@@ -0,0 +1,24 @@
1
+ Feature: A library gets its title and description from the root README
2
+ As an AI coding agent
3
+ I want libraries to carry a meaningful title and description
4
+ So that I can pick the right library in "resolve-library-id" without anyone typing them
5
+
6
+ Background:
7
+ Given ExactDocs imports the fixture docs repository
8
+
9
+ Scenario: Defaults come from the root README even outside the selection
10
+ When "docs/" of the fixture repository at "v1.3.0" is imported
11
+ Then the title of the library is the first heading of the README
12
+ And the description of the library is the first paragraph of the README
13
+
14
+ Scenario: The user can edit them
15
+ Given "/fixture/docs-repo/v1.3.0" is imported
16
+ When the user changes the title to "Payments SDK"
17
+ Then "list_libraries" shows the title "Payments SDK"
18
+
19
+ Scenario: An edited title survives a re-import
20
+ Given "/fixture/docs-repo/v1.3.0" is imported
21
+ And the user changes the title to "Payments SDK"
22
+ When "v1.3.0" is imported again
23
+ Then the title of the library is "Payments SDK"
24
+ And the description of the library is the first paragraph of the README
@@ -0,0 +1,47 @@
1
+ Feature: Admin session
2
+ As the admin of an instance
3
+ I want to sign in to the web UI with a session that expires and cannot be stolen by script
4
+ So that only I can use the instance from a browser
5
+
6
+ Background:
7
+ Given a fresh instance
8
+ And the admin "ada" was set up with the password "correct horse battery staple"
9
+
10
+ Scenario: Signing in and out
11
+ Then the libraries are not reachable
12
+ When the admin signs in with the password "correct horse battery staple"
13
+ Then the UI session is active for "ada"
14
+ And the session cookie is HttpOnly and SameSite=Lax
15
+ And the libraries are reachable
16
+ When the admin signs out
17
+ Then the libraries are no longer reachable with that session
18
+ And the UI session is not active
19
+
20
+ Scenario: A wrong password and an unknown user are told apart by nobody
21
+ When the admin signs in with the password "wrong horse battery staple"
22
+ Then the sign-in is refused with "invalid_credentials"
23
+ When "nobody" signs in with the password "correct horse battery staple"
24
+ Then the sign-in is refused with "invalid_credentials"
25
+ And no session cookie was set
26
+
27
+ Scenario: A session cookie never authenticates MCP
28
+ Given the admin is signed in to the UI
29
+ When the browser calls the MCP endpoint with the session cookie only
30
+ Then the MCP endpoint treats the call exactly like one without the cookie
31
+
32
+ Scenario: Another site cannot sign the admin in or out
33
+ Given the admin is signed in to the UI
34
+ When a page on "https://evil.example" signs the admin out
35
+ Then the request is forbidden
36
+ And the libraries are reachable
37
+ When a page on "https://evil.example" signs in with the password "correct horse battery staple"
38
+ Then the request is forbidden
39
+
40
+ Scenario: Resetting the password signs the admin out everywhere
41
+ Given the admin is signed in to the UI
42
+ When the operator runs "exactdocs admin reset-password" with the username "ada" and the password "a brand new passphrase"
43
+ Then the libraries are no longer reachable with that session
44
+ When the admin signs in with the password "correct horse battery staple"
45
+ Then the sign-in is refused with "invalid_credentials"
46
+ When the admin signs in with the password "a brand new passphrase"
47
+ Then the UI session is active for "ada"