dirsql 0.4.30__tar.gz → 0.4.32__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 (207) hide show
  1. {dirsql-0.4.30 → dirsql-0.4.32}/Cargo.lock +2 -2
  2. {dirsql-0.4.30 → dirsql-0.4.32}/PKG-INFO +1 -1
  3. {dirsql-0.4.30/packages/rust → dirsql-0.4.32}/docs/howto/search-by-meaning.md +41 -19
  4. {dirsql-0.4.30/packages/rust → dirsql-0.4.32}/docs/howto/search-indexes.md +27 -2
  5. {dirsql-0.4.30 → dirsql-0.4.32}/docs/reference/cli.md +44 -0
  6. {dirsql-0.4.30 → dirsql-0.4.32}/docs/reference/sdk.md +19 -0
  7. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/Cargo.toml +1 -1
  8. {dirsql-0.4.30 → dirsql-0.4.32/packages/python}/docs/howto/search-by-meaning.md +41 -19
  9. {dirsql-0.4.30 → dirsql-0.4.32/packages/python}/docs/howto/search-indexes.md +27 -2
  10. {dirsql-0.4.30/packages/rust → dirsql-0.4.32/packages/python}/docs/reference/cli.md +44 -0
  11. {dirsql-0.4.30/packages/rust → dirsql-0.4.32/packages/python}/docs/reference/sdk.md +19 -0
  12. dirsql-0.4.32/packages/python/e2e-attestations/claude-tackle-957-lrm0z6.json +7 -0
  13. dirsql-0.4.32/packages/python/e2e-attestations/claude-tackle-975-n8mc2h.json +7 -0
  14. dirsql-0.4.32/packages/python/testing-conventions.toml +34 -0
  15. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/Cargo.toml +1 -1
  16. {dirsql-0.4.30/packages/python → dirsql-0.4.32/packages/rust}/docs/howto/search-by-meaning.md +41 -19
  17. {dirsql-0.4.30/packages/python → dirsql-0.4.32/packages/rust}/docs/howto/search-indexes.md +27 -2
  18. {dirsql-0.4.30/packages/python → dirsql-0.4.32/packages/rust}/docs/reference/cli.md +44 -0
  19. {dirsql-0.4.30/packages/python → dirsql-0.4.32/packages/rust}/docs/reference/sdk.md +19 -0
  20. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/lib.rs +83 -14
  21. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/matcher.rs +48 -0
  22. dirsql-0.4.32/packages/rust/src/progress.rs +590 -0
  23. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/scanner.rs +24 -0
  24. dirsql-0.4.30/packages/python/testing-conventions.toml +0 -15
  25. {dirsql-0.4.30 → dirsql-0.4.32}/Cargo.toml +0 -0
  26. {dirsql-0.4.30 → dirsql-0.4.32}/README.md +0 -0
  27. {dirsql-0.4.30 → dirsql-0.4.32}/dirsql/__init__.py +0 -0
  28. {dirsql-0.4.30 → dirsql-0.4.32}/dirsql/_async.py +0 -0
  29. {dirsql-0.4.30 → dirsql-0.4.32}/dirsql/_dirsql.pyi +0 -0
  30. {dirsql-0.4.30 → dirsql-0.4.32}/dirsql/cli/__init__.py +0 -0
  31. {dirsql-0.4.30 → dirsql-0.4.32}/dirsql/cli/discover_plugins/__init__.py +0 -0
  32. {dirsql-0.4.30 → dirsql-0.4.32}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
  33. {dirsql-0.4.30 → dirsql-0.4.32}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
  34. {dirsql-0.4.30 → dirsql-0.4.32}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
  35. {dirsql-0.4.30 → dirsql-0.4.32}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
  36. {dirsql-0.4.30 → dirsql-0.4.32}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
  37. {dirsql-0.4.30 → dirsql-0.4.32}/dirsql/cli/main.py +0 -0
  38. {dirsql-0.4.30 → dirsql-0.4.32}/dirsql/cli/resolve_config_extensions.py +0 -0
  39. {dirsql-0.4.30 → dirsql-0.4.32}/dirsql/py.typed +0 -0
  40. {dirsql-0.4.30 → dirsql-0.4.32}/dirsql/resolve_config_extensions.py +0 -0
  41. {dirsql-0.4.30 → dirsql-0.4.32}/dirsql/resolve_extension.py +0 -0
  42. {dirsql-0.4.30 → dirsql-0.4.32}/docs/.claude/CLAUDE.md +0 -0
  43. {dirsql-0.4.30 → dirsql-0.4.32}/docs/.vitepress/config.ts +0 -0
  44. {dirsql-0.4.30 → dirsql-0.4.32}/docs/.vitepress/theme/index.ts +0 -0
  45. {dirsql-0.4.30 → dirsql-0.4.32}/docs/.vitepress/theme/lang.ts +0 -0
  46. {dirsql-0.4.30 → dirsql-0.4.32}/docs/AGENTS.md +0 -0
  47. {dirsql-0.4.30 → dirsql-0.4.32}/docs/explanation.md +0 -0
  48. {dirsql-0.4.30 → dirsql-0.4.32}/docs/getting-started.md +0 -0
  49. {dirsql-0.4.30 → dirsql-0.4.32}/docs/howto/columns-from-paths.md +0 -0
  50. {dirsql-0.4.30 → dirsql-0.4.32}/docs/howto/define-tables.md +0 -0
  51. {dirsql-0.4.30 → dirsql-0.4.32}/docs/howto/embed.md +0 -0
  52. {dirsql-0.4.30 → dirsql-0.4.32}/docs/howto/extract-from-contents.md +0 -0
  53. {dirsql-0.4.30 → dirsql-0.4.32}/docs/howto/load-extension.md +0 -0
  54. {dirsql-0.4.30 → dirsql-0.4.32}/docs/howto/parse-files-into-columns.md +0 -0
  55. {dirsql-0.4.30 → dirsql-0.4.32}/docs/howto/persist.md +0 -0
  56. {dirsql-0.4.30 → dirsql-0.4.32}/docs/howto/query-json.md +0 -0
  57. {dirsql-0.4.30 → dirsql-0.4.32}/docs/howto/query-without-config.md +0 -0
  58. {dirsql-0.4.30 → dirsql-0.4.32}/docs/howto/react-to-changes.md +0 -0
  59. {dirsql-0.4.30 → dirsql-0.4.32}/docs/howto/skip-files.md +0 -0
  60. {dirsql-0.4.30 → dirsql-0.4.32}/docs/howto/write-a-plugin.md +0 -0
  61. {dirsql-0.4.30 → dirsql-0.4.32}/docs/index.md +0 -0
  62. {dirsql-0.4.30 → dirsql-0.4.32}/docs/package.json +0 -0
  63. {dirsql-0.4.30 → dirsql-0.4.32}/docs/plugins.md +0 -0
  64. {dirsql-0.4.30 → dirsql-0.4.32}/docs/pnpm-lock.yaml +0 -0
  65. {dirsql-0.4.30 → dirsql-0.4.32}/docs/pnpm-workspace.yaml +0 -0
  66. {dirsql-0.4.30 → dirsql-0.4.32}/docs/reference/columns.md +0 -0
  67. {dirsql-0.4.30 → dirsql-0.4.32}/docs/reference/config.md +0 -0
  68. {dirsql-0.4.30 → dirsql-0.4.32}/docs/reference/hooks.md +0 -0
  69. {dirsql-0.4.30 → dirsql-0.4.32}/docs/reference/http-api.md +0 -0
  70. {dirsql-0.4.30 → dirsql-0.4.32}/docs/reference/path-tables.md +0 -0
  71. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/CHANGELOG.md +0 -0
  72. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/MIGRATIONS.md +0 -0
  73. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/README.md +0 -0
  74. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  75. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
  76. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  77. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
  78. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  79. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
  80. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
  81. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
  82. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  83. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
  84. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
  85. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
  86. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
  87. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
  88. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
  89. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
  90. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
  91. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-08-20-curtaincall-dev-dependency.md +0 -0
  92. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/2026-08-20-declared-table-name.md +0 -0
  93. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/changelog.d/README.md +0 -0
  94. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/conftest.py +0 -0
  95. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/.claude/CLAUDE.md +0 -0
  96. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/.vitepress/config.ts +0 -0
  97. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  98. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  99. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/AGENTS.md +0 -0
  100. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/explanation.md +0 -0
  101. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/getting-started.md +0 -0
  102. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/howto/columns-from-paths.md +0 -0
  103. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/howto/define-tables.md +0 -0
  104. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/howto/embed.md +0 -0
  105. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/howto/extract-from-contents.md +0 -0
  106. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/howto/load-extension.md +0 -0
  107. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
  108. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/howto/persist.md +0 -0
  109. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/howto/query-json.md +0 -0
  110. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/howto/query-without-config.md +0 -0
  111. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/howto/react-to-changes.md +0 -0
  112. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/howto/skip-files.md +0 -0
  113. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/howto/write-a-plugin.md +0 -0
  114. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/index.md +0 -0
  115. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/package.json +0 -0
  116. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/plugins.md +0 -0
  117. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/pnpm-lock.yaml +0 -0
  118. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/pnpm-workspace.yaml +0 -0
  119. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/reference/columns.md +0 -0
  120. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/reference/config.md +0 -0
  121. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/reference/hooks.md +0 -0
  122. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/reference/http-api.md +0 -0
  123. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/docs/reference/path-tables.md +0 -0
  124. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/e2e-attestations/cc-keen-knuth-f11gyq.json +0 -0
  125. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
  126. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +0 -0
  127. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/e2e-attestations/claude-epic-953-slice2-reedline.json +0 -0
  128. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/e2e-attestations/claude-epic-953-slice3-format.json +0 -0
  129. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/e2e-attestations/claude-epic-953-stacked-prs-x59wm3.json +0 -0
  130. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +0 -0
  131. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/e2e-attestations/claude-issue-951-0eease.json +0 -0
  132. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/e2e-attestations/claude-issue-962-0jjrfv.json +0 -0
  133. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/e2e-attestations/claude-issue-986-red-test-e7sjrb.json +0 -0
  134. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/e2e-attestations/claude-tackle-956-xzen74.json +0 -0
  135. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  136. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  137. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  138. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
  139. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
  140. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
  141. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  142. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
  143. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
  144. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/migrations.d/2026-08-20-declared-table-name.md +0 -0
  145. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/migrations.d/README.md +0 -0
  146. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/src/lib.rs +0 -0
  147. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/tests/__init__.py +0 -0
  148. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/tests/conftest.py +0 -0
  149. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/tests/e2e/__init__.py +0 -0
  150. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
  151. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
  152. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/tests/integration/__init__.py +0 -0
  153. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/tests/integration/binding/__init__.py +0 -0
  154. {dirsql-0.4.30 → dirsql-0.4.32}/packages/python/tests/integration/hermetic/__init__.py +0 -0
  155. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/CHANGELOG.md +0 -0
  156. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/MIGRATIONS.md +0 -0
  157. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/README.md +0 -0
  158. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/benches/db_bench.rs +0 -0
  159. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/benches/differ_bench.rs +0 -0
  160. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/benches/matcher_bench.rs +0 -0
  161. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/benches/scanner_bench.rs +0 -0
  162. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/explanation.md +0 -0
  163. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/getting-started.md +0 -0
  164. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/howto/columns-from-paths.md +0 -0
  165. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/howto/define-tables.md +0 -0
  166. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/howto/embed.md +0 -0
  167. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/howto/extract-from-contents.md +0 -0
  168. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/howto/load-extension.md +0 -0
  169. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
  170. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/howto/persist.md +0 -0
  171. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/howto/query-json.md +0 -0
  172. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/howto/query-without-config.md +0 -0
  173. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/howto/react-to-changes.md +0 -0
  174. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/howto/skip-files.md +0 -0
  175. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/howto/write-a-plugin.md +0 -0
  176. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/index.md +0 -0
  177. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/plugins.md +0 -0
  178. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/reference/columns.md +0 -0
  179. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/reference/config.md +0 -0
  180. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/reference/hooks.md +0 -0
  181. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/reference/http-api.md +0 -0
  182. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/docs/reference/path-tables.md +0 -0
  183. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/bin/dirsql.rs +0 -0
  184. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/cli/execute.rs +0 -0
  185. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/cli/init.rs +0 -0
  186. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/cli/mod.rs +0 -0
  187. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/cli/repl.rs +0 -0
  188. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/cli/router.rs +0 -0
  189. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/cli/run.rs +0 -0
  190. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/cli/serialize.rs +0 -0
  191. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/cli/server.rs +0 -0
  192. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/cli/table.rs +0 -0
  193. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/command.rs +0 -0
  194. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/config.rs +0 -0
  195. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/db.rs +0 -0
  196. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/default_config.toml +0 -0
  197. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/differ.rs +0 -0
  198. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/functions.rs +0 -0
  199. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/infer.rs +0 -0
  200. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/parsed_cache.rs +0 -0
  201. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/parsed_vtab.rs +0 -0
  202. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/path_table.rs +0 -0
  203. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/persist.rs +0 -0
  204. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/sql_literal.rs +0 -0
  205. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/vtab.rs +0 -0
  206. {dirsql-0.4.30 → dirsql-0.4.32}/packages/rust/src/watcher.rs +0 -0
  207. {dirsql-0.4.30 → dirsql-0.4.32}/pyproject.toml +0 -0
@@ -540,7 +540,7 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
540
540
 
541
541
  [[package]]
542
542
  name = "dirsql"
543
- version = "0.4.30"
543
+ version = "0.4.32"
544
544
  dependencies = [
545
545
  "assert_cmd",
546
546
  "axum",
@@ -583,7 +583,7 @@ dependencies = [
583
583
 
584
584
  [[package]]
585
585
  name = "dirsql-py-ext"
586
- version = "0.4.30"
586
+ version = "0.4.32"
587
587
  dependencies = [
588
588
  "dirsql",
589
589
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.4.30
3
+ Version: 0.4.32
4
4
  Requires-Dist: tomli>=2 ; python_full_version < '3.11'
5
5
  Requires-Dist: bin-shim>=0.1
6
6
  Summary: Ephemeral SQL index over a local directory
@@ -3,10 +3,10 @@
3
3
  Ask a question in plain language and get the closest documents back — even
4
4
  when they share no keywords with it. Install
5
5
  [`dirsql-plugin-embeddings`](../plugins.md#dirsql-plugin-embeddings) and
6
- semantic search becomes plain SQL: the plugin's `embed()` function turns
7
- text into vectors, [`sqlite-vec`](https://github.com/asg017/sqlite-vec)'s
8
- `vec_distance_cosine()` measures distance, and `ORDER BY … LIMIT` does the
9
- ranking. No config, no API keys, no services — the model runs locally.
6
+ semantic search becomes plain SQL: the plugin's `embed()` function turns text
7
+ into vectors and loads [`sqlite-vec`](https://github.com/asg017/sqlite-vec),
8
+ which does the distance math. No API keys, no services — the model runs
9
+ locally.
10
10
 
11
11
  Suppose short notes live in `notes/*.md`:
12
12
 
@@ -40,7 +40,32 @@ The first-ever run downloads the model (on the order of a hundred megabytes,
40
40
  with progress on stderr); after that it loads from the local cache. Results
41
41
  print one `path<TAB>distance` line per match, closest first.
42
42
 
43
- ## The SQL behind it
43
+ ## Which shape
44
+
45
+ The one-liner embeds every matched file on every run. That is the right trade
46
+ for a question you ask once, and the wrong one for a corpus you search
47
+ repeatedly — where a stored [`vec0` index](./search-indexes.md#vector-search-vec0)
48
+ embeds each file once, at ingest, and a query embeds only the question:
49
+
50
+ | | This page: path-table | [Vector index](./search-indexes.md#vector-search-vec0) |
51
+ |---|---|---|
52
+ | Setup | none | a `[[table]]` with a `ddl` batch |
53
+ | Freshness | always current — the walk is the read | watcher-maintained; survives restarts under [`--persist`](./persist.md) |
54
+ | Per query | one `embed()` round trip **per matched file**, plus a walk that reads every file's content | one `embed()` round trip **total**, plus an in-process KNN scan |
55
+ | Top-k | `ORDER BY … LIMIT k` | `MATCH … AND k = …` |
56
+ | Good for | a one-off question, a small corpus, an ad-hoc glob | a corpus you query repeatedly |
57
+
58
+ The index only pays off when the table outlives the query — under `--persist`,
59
+ or inside a long-running [`dirsql server`](../reference/cli.md). A one-shot
60
+ `dirsql query` against an ephemeral index rebuilds the table, and therefore
61
+ re-embeds the corpus, before it answers; that is strictly more work than the
62
+ subquery below. The full recipe — the width probe, the `ddl` batch, both
63
+ triggers, and what a model-id edit costs — is
64
+ [Add a search index to a table](./search-indexes.md#vector-search-vec0).
65
+
66
+ The rest of this page is the zero-setup shape.
67
+
68
+ ## The SQL behind the one-liner
44
69
 
45
70
  The one-liner generates and runs ordinary `dirsql` SQL, and you can write it
46
71
  yourself when you want more than ranked paths — a different projection, a
@@ -79,7 +104,9 @@ Reading the query inside-out:
79
104
  and its distance are NULL too — and SQLite sorts NULLs *first* ascending,
80
105
  so without this line the unrankable files take the top-k slots.
81
106
  4. `vec_distance_cosine(...)` computes cosine distance between the two
82
- vectors; `ORDER BY distance LIMIT 3` keeps the three nearest.
107
+ vectors; `ORDER BY distance LIMIT 3` keeps the three nearest. There is no
108
+ `vec0` table here, so `sqlite-vec`'s `MATCH … AND k = …` does not apply —
109
+ for a plain expression, `ORDER BY … LIMIT k` *is* its documented top-k.
83
110
 
84
111
  Structured files compose with SQL's JSON operators — embed one field instead
85
112
  of the whole file:
@@ -93,24 +120,19 @@ ORDER BY vec_distance_cosine(emb, embed('local private models'))
93
120
  LIMIT 10
94
121
  ```
95
122
 
96
- ::: tip Top-k is `LIMIT k`
97
- If you know `sqlite-vec` you may reach for its `MATCH … AND k = 10` idiom.
98
- That syntax belongs to `sqlite-vec`'s `vec0` virtual table, which the table
99
- above does not use: a `[[table]]`'s own `name` is always a per-file row table.
100
- For plain expressions, `sqlite-vec`'s own documented pattern is exactly what
101
- this guide uses: `ORDER BY vec_distance_cosine(...) LIMIT k`. To get the `vec0`
102
- idiom instead, declare the `vec0` table alongside the row table in the same
103
- [`ddl` batch](../reference/config.md#batch-ddl) and fill it from a trigger.
104
- :::
123
+ The same projection works as an `on-file` hook feeding the indexed shape:
124
+ parse the field in the hook, store it as a column, and the trigger embeds it
125
+ once instead of on every query.
105
126
 
106
127
  ## Repeat runs are cheap
107
128
 
108
129
  Computed vectors are cached on disk, keyed on content and model
109
130
  ([vector cache](../plugins.md#vector-cache)) — re-running a search over
110
- unchanged files skips the model entirely and re-embeds only what changed.
111
- And the plugin costs nothing when idle: a query that never calls `embed()`
112
- spawns no worker and loads no model
113
- ([zero cost when unused](../plugins.md#zero-cost-when-unused)).
131
+ unchanged files skips the model entirely and re-embeds only what changed. That
132
+ takes the *inference* out of the shape above, but not the walk or the per-file
133
+ round trip; only a stored index removes those. And the plugin costs nothing
134
+ when idle: a query that never calls `embed()` spawns no worker and loads no
135
+ model ([zero cost when unused](../plugins.md#zero-cost-when-unused)).
114
136
 
115
137
  ## How `embed()` gets into SQL
116
138
 
@@ -155,8 +155,10 @@ ddl = '''
155
155
  CREATE TABLE notes (slug TEXT, title TEXT, body TEXT);
156
156
 
157
157
  -- Width must equal what the probe printed for the model id named below.
158
- CREATE VIRTUAL TABLE notes_vec USING vec0(embedding float[512]);
159
- CREATE TRIGGER notes_vi AFTER INSERT ON notes BEGIN
158
+ CREATE VIRTUAL TABLE notes_vec
159
+ USING vec0(embedding float[512] distance_metric=cosine);
160
+ CREATE TRIGGER notes_vi AFTER INSERT ON notes
161
+ WHEN new.body IS NOT NULL BEGIN
160
162
  INSERT INTO notes_vec(rowid, embedding)
161
163
  VALUES (new.rowid, embed(new.body, 'minishlab/potion-retrieval-32M'));
162
164
  END;
@@ -169,6 +171,29 @@ END;
169
171
  The delete side is an ordinary `DELETE`, not FTS5's `'delete'` command row —
170
172
  `vec0` is a normal-looking table that way.
171
173
 
174
+ Two clauses in there are load-bearing:
175
+
176
+ - **`distance_metric=cosine`.** `vec0` defaults to L2, which is not the metric
177
+ the rest of dirsql's semantic search uses — `vec_distance_cosine()` backs
178
+ both the plugin's one-liner and
179
+ [Search documents by meaning](./search-by-meaning.md). The two agree only for
180
+ vectors of equal length, so as soon as documents embed to vectors of
181
+ different magnitude they rank differently: against three notes, L2 returned
182
+ `a, c, b` where cosine returned `a, b, c`. Declaring the metric makes
183
+ `distance` the number `vec_distance_cosine()` would compute.
184
+ - **`WHEN new.body IS NOT NULL`.** `embed(NULL)` is `NULL`, and `vec0` rejects
185
+ a NULL vector outright — so without the guard a single row whose text is
186
+ missing fails the **whole** table load, not just its own insert:
187
+
188
+ ```
189
+ dirsql query: failed to load config: SQLite error: Inserted vector for the
190
+ "embedding" column is invalid: Input must have type BLOB (compact format) or
191
+ TEXT (JSON), found NULL
192
+ ```
193
+
194
+ With it, that row still lands in `notes`; only its vector is skipped. FTS5
195
+ needs no such guard — it indexes a NULL happily.
196
+
172
197
  Query it with `sqlite-vec`'s KNN form, joined back on `rowid`:
173
198
 
174
199
  ```bash
@@ -460,3 +460,47 @@ Turn discovery off with either:
460
460
 
461
461
  A plugin that declares itself but is missing its module or its `dirsql.toml`
462
462
  fragment is a launcher error naming the package — never a silent skip.
463
+
464
+ ## Progress reporting
465
+
466
+ Building the index over a large tree is not instant: the walk visits every
467
+ file, then each matched file costs one `on-file` round trip plus whatever the
468
+ table's `ddl` fires on insert. On a big corpus that is minutes. dirsql reports
469
+ the two phases on **stderr** while they run:
470
+
471
+ ```
472
+ dirsql: scanning 128413 files
473
+ dirsql: indexing 9204/41231 files (22%)
474
+ ```
475
+
476
+ Each line is rewritten in place. When a phase ends its line is erased and
477
+ replaced by one summary of what it cost:
478
+
479
+ ```
480
+ dirsql: scanned 128413 files in 4.2s
481
+ dirsql: indexed 41231 files in 3m12s
482
+ ```
483
+
484
+ stdout is untouched — it carries the query result and nothing else.
485
+
486
+ By default this is **terminal-only, and only for work slow enough to wonder
487
+ about**: a phase that finishes in under half a second prints nothing at all,
488
+ and a run whose stderr is a pipe or a file prints nothing regardless of how
489
+ long it takes. `dirsql "…" 2>run.log` and `dirsql "…" | jq` are byte-for-byte
490
+ what they were before.
491
+
492
+ Override with `DIRSQL_PROGRESS`:
493
+
494
+ | Value | Effect |
495
+ |---|---|
496
+ | unset, or `auto` | Report only on a terminal, and only once a phase has run for half a second. The default. |
497
+ | `always`, `1`, `true` | Report from the first update, terminal or not. Use it to watch a scan whose stderr is redirected. |
498
+ | `never`, `0`, `false` | Report nothing, ever. |
499
+
500
+ Values are case-insensitive and surrounding whitespace is ignored; anything
501
+ unrecognized reads as `auto`, so a typo cannot stop a scan from running.
502
+
503
+ The setting is read by the **core**, not the CLI, so it governs an index built
504
+ from any SDK as well — a Python or TypeScript program that builds a `DirSQL`
505
+ with a terminal attached gets the same two phases on stderr, and the same
506
+ silence when piped.
@@ -396,3 +396,22 @@ not fit that range is a hard error, not a lossy conversion: Python raises
396
396
  A TypeScript `bigint` **within** `i64` range maps to `INTEGER`. Only a real
397
397
  `bytes`/`bytearray` (Python) or `Buffer`/`Uint8Array` (TypeScript) maps to
398
398
  `BLOB` — a list/array of integers does not.
399
+
400
+ ## Progress on construction
401
+
402
+ Constructing a `DirSQL` walks the tree and ingests every matched file, which
403
+ on a large corpus is the slowest thing your program does. The core reports
404
+ both phases on **stderr** while they run, then erases the live line and leaves
405
+ one summary of what each cost:
406
+
407
+ ```
408
+ dirsql: indexed 41231 files in 3m12s
409
+ ```
410
+
411
+ This is terminal-only by default, and only for a phase that runs longer than
412
+ half a second — a program whose stderr is a pipe, a file, or a log collector
413
+ sees nothing, whatever the scan costs. Set `DIRSQL_PROGRESS=never` to
414
+ guarantee silence even on a terminal, or `DIRSQL_PROGRESS=always` to report
415
+ regardless. The full table is in the
416
+ [CLI reference](cli.md#progress-reporting); the setting lives in the shared
417
+ core, so it behaves identically from all three SDKs.
@@ -4,7 +4,7 @@ name = "dirsql-py-ext"
4
4
  # pypi/maturin handler can rewrite it via `write-version` before
5
5
  # `maturin build`. `pyproject.toml` declares `dynamic = ["version"]`
6
6
  # and maturin reads this field. Mirrors `packages/rust/Cargo.toml`.
7
- version = "0.4.30"
7
+ version = "0.4.32"
8
8
  # abi3 note: the `abi3-py310` pyo3 feature (below) builds ONE stable-ABI
9
9
  # `cp310-abi3` wheel per platform that loads on every CPython >= 3.10
10
10
  # (matching `pyproject.toml`'s `requires-python = ">=3.10"`), instead of
@@ -3,10 +3,10 @@
3
3
  Ask a question in plain language and get the closest documents back — even
4
4
  when they share no keywords with it. Install
5
5
  [`dirsql-plugin-embeddings`](../plugins.md#dirsql-plugin-embeddings) and
6
- semantic search becomes plain SQL: the plugin's `embed()` function turns
7
- text into vectors, [`sqlite-vec`](https://github.com/asg017/sqlite-vec)'s
8
- `vec_distance_cosine()` measures distance, and `ORDER BY … LIMIT` does the
9
- ranking. No config, no API keys, no services — the model runs locally.
6
+ semantic search becomes plain SQL: the plugin's `embed()` function turns text
7
+ into vectors and loads [`sqlite-vec`](https://github.com/asg017/sqlite-vec),
8
+ which does the distance math. No API keys, no services — the model runs
9
+ locally.
10
10
 
11
11
  Suppose short notes live in `notes/*.md`:
12
12
 
@@ -40,7 +40,32 @@ The first-ever run downloads the model (on the order of a hundred megabytes,
40
40
  with progress on stderr); after that it loads from the local cache. Results
41
41
  print one `path<TAB>distance` line per match, closest first.
42
42
 
43
- ## The SQL behind it
43
+ ## Which shape
44
+
45
+ The one-liner embeds every matched file on every run. That is the right trade
46
+ for a question you ask once, and the wrong one for a corpus you search
47
+ repeatedly — where a stored [`vec0` index](./search-indexes.md#vector-search-vec0)
48
+ embeds each file once, at ingest, and a query embeds only the question:
49
+
50
+ | | This page: path-table | [Vector index](./search-indexes.md#vector-search-vec0) |
51
+ |---|---|---|
52
+ | Setup | none | a `[[table]]` with a `ddl` batch |
53
+ | Freshness | always current — the walk is the read | watcher-maintained; survives restarts under [`--persist`](./persist.md) |
54
+ | Per query | one `embed()` round trip **per matched file**, plus a walk that reads every file's content | one `embed()` round trip **total**, plus an in-process KNN scan |
55
+ | Top-k | `ORDER BY … LIMIT k` | `MATCH … AND k = …` |
56
+ | Good for | a one-off question, a small corpus, an ad-hoc glob | a corpus you query repeatedly |
57
+
58
+ The index only pays off when the table outlives the query — under `--persist`,
59
+ or inside a long-running [`dirsql server`](../reference/cli.md). A one-shot
60
+ `dirsql query` against an ephemeral index rebuilds the table, and therefore
61
+ re-embeds the corpus, before it answers; that is strictly more work than the
62
+ subquery below. The full recipe — the width probe, the `ddl` batch, both
63
+ triggers, and what a model-id edit costs — is
64
+ [Add a search index to a table](./search-indexes.md#vector-search-vec0).
65
+
66
+ The rest of this page is the zero-setup shape.
67
+
68
+ ## The SQL behind the one-liner
44
69
 
45
70
  The one-liner generates and runs ordinary `dirsql` SQL, and you can write it
46
71
  yourself when you want more than ranked paths — a different projection, a
@@ -79,7 +104,9 @@ Reading the query inside-out:
79
104
  and its distance are NULL too — and SQLite sorts NULLs *first* ascending,
80
105
  so without this line the unrankable files take the top-k slots.
81
106
  4. `vec_distance_cosine(...)` computes cosine distance between the two
82
- vectors; `ORDER BY distance LIMIT 3` keeps the three nearest.
107
+ vectors; `ORDER BY distance LIMIT 3` keeps the three nearest. There is no
108
+ `vec0` table here, so `sqlite-vec`'s `MATCH … AND k = …` does not apply —
109
+ for a plain expression, `ORDER BY … LIMIT k` *is* its documented top-k.
83
110
 
84
111
  Structured files compose with SQL's JSON operators — embed one field instead
85
112
  of the whole file:
@@ -93,24 +120,19 @@ ORDER BY vec_distance_cosine(emb, embed('local private models'))
93
120
  LIMIT 10
94
121
  ```
95
122
 
96
- ::: tip Top-k is `LIMIT k`
97
- If you know `sqlite-vec` you may reach for its `MATCH … AND k = 10` idiom.
98
- That syntax belongs to `sqlite-vec`'s `vec0` virtual table, which the table
99
- above does not use: a `[[table]]`'s own `name` is always a per-file row table.
100
- For plain expressions, `sqlite-vec`'s own documented pattern is exactly what
101
- this guide uses: `ORDER BY vec_distance_cosine(...) LIMIT k`. To get the `vec0`
102
- idiom instead, declare the `vec0` table alongside the row table in the same
103
- [`ddl` batch](../reference/config.md#batch-ddl) and fill it from a trigger.
104
- :::
123
+ The same projection works as an `on-file` hook feeding the indexed shape:
124
+ parse the field in the hook, store it as a column, and the trigger embeds it
125
+ once instead of on every query.
105
126
 
106
127
  ## Repeat runs are cheap
107
128
 
108
129
  Computed vectors are cached on disk, keyed on content and model
109
130
  ([vector cache](../plugins.md#vector-cache)) — re-running a search over
110
- unchanged files skips the model entirely and re-embeds only what changed.
111
- And the plugin costs nothing when idle: a query that never calls `embed()`
112
- spawns no worker and loads no model
113
- ([zero cost when unused](../plugins.md#zero-cost-when-unused)).
131
+ unchanged files skips the model entirely and re-embeds only what changed. That
132
+ takes the *inference* out of the shape above, but not the walk or the per-file
133
+ round trip; only a stored index removes those. And the plugin costs nothing
134
+ when idle: a query that never calls `embed()` spawns no worker and loads no
135
+ model ([zero cost when unused](../plugins.md#zero-cost-when-unused)).
114
136
 
115
137
  ## How `embed()` gets into SQL
116
138
 
@@ -155,8 +155,10 @@ ddl = '''
155
155
  CREATE TABLE notes (slug TEXT, title TEXT, body TEXT);
156
156
 
157
157
  -- Width must equal what the probe printed for the model id named below.
158
- CREATE VIRTUAL TABLE notes_vec USING vec0(embedding float[512]);
159
- CREATE TRIGGER notes_vi AFTER INSERT ON notes BEGIN
158
+ CREATE VIRTUAL TABLE notes_vec
159
+ USING vec0(embedding float[512] distance_metric=cosine);
160
+ CREATE TRIGGER notes_vi AFTER INSERT ON notes
161
+ WHEN new.body IS NOT NULL BEGIN
160
162
  INSERT INTO notes_vec(rowid, embedding)
161
163
  VALUES (new.rowid, embed(new.body, 'minishlab/potion-retrieval-32M'));
162
164
  END;
@@ -169,6 +171,29 @@ END;
169
171
  The delete side is an ordinary `DELETE`, not FTS5's `'delete'` command row —
170
172
  `vec0` is a normal-looking table that way.
171
173
 
174
+ Two clauses in there are load-bearing:
175
+
176
+ - **`distance_metric=cosine`.** `vec0` defaults to L2, which is not the metric
177
+ the rest of dirsql's semantic search uses — `vec_distance_cosine()` backs
178
+ both the plugin's one-liner and
179
+ [Search documents by meaning](./search-by-meaning.md). The two agree only for
180
+ vectors of equal length, so as soon as documents embed to vectors of
181
+ different magnitude they rank differently: against three notes, L2 returned
182
+ `a, c, b` where cosine returned `a, b, c`. Declaring the metric makes
183
+ `distance` the number `vec_distance_cosine()` would compute.
184
+ - **`WHEN new.body IS NOT NULL`.** `embed(NULL)` is `NULL`, and `vec0` rejects
185
+ a NULL vector outright — so without the guard a single row whose text is
186
+ missing fails the **whole** table load, not just its own insert:
187
+
188
+ ```
189
+ dirsql query: failed to load config: SQLite error: Inserted vector for the
190
+ "embedding" column is invalid: Input must have type BLOB (compact format) or
191
+ TEXT (JSON), found NULL
192
+ ```
193
+
194
+ With it, that row still lands in `notes`; only its vector is skipped. FTS5
195
+ needs no such guard — it indexes a NULL happily.
196
+
172
197
  Query it with `sqlite-vec`'s KNN form, joined back on `rowid`:
173
198
 
174
199
  ```bash
@@ -460,3 +460,47 @@ Turn discovery off with either:
460
460
 
461
461
  A plugin that declares itself but is missing its module or its `dirsql.toml`
462
462
  fragment is a launcher error naming the package — never a silent skip.
463
+
464
+ ## Progress reporting
465
+
466
+ Building the index over a large tree is not instant: the walk visits every
467
+ file, then each matched file costs one `on-file` round trip plus whatever the
468
+ table's `ddl` fires on insert. On a big corpus that is minutes. dirsql reports
469
+ the two phases on **stderr** while they run:
470
+
471
+ ```
472
+ dirsql: scanning 128413 files
473
+ dirsql: indexing 9204/41231 files (22%)
474
+ ```
475
+
476
+ Each line is rewritten in place. When a phase ends its line is erased and
477
+ replaced by one summary of what it cost:
478
+
479
+ ```
480
+ dirsql: scanned 128413 files in 4.2s
481
+ dirsql: indexed 41231 files in 3m12s
482
+ ```
483
+
484
+ stdout is untouched — it carries the query result and nothing else.
485
+
486
+ By default this is **terminal-only, and only for work slow enough to wonder
487
+ about**: a phase that finishes in under half a second prints nothing at all,
488
+ and a run whose stderr is a pipe or a file prints nothing regardless of how
489
+ long it takes. `dirsql "…" 2>run.log` and `dirsql "…" | jq` are byte-for-byte
490
+ what they were before.
491
+
492
+ Override with `DIRSQL_PROGRESS`:
493
+
494
+ | Value | Effect |
495
+ |---|---|
496
+ | unset, or `auto` | Report only on a terminal, and only once a phase has run for half a second. The default. |
497
+ | `always`, `1`, `true` | Report from the first update, terminal or not. Use it to watch a scan whose stderr is redirected. |
498
+ | `never`, `0`, `false` | Report nothing, ever. |
499
+
500
+ Values are case-insensitive and surrounding whitespace is ignored; anything
501
+ unrecognized reads as `auto`, so a typo cannot stop a scan from running.
502
+
503
+ The setting is read by the **core**, not the CLI, so it governs an index built
504
+ from any SDK as well — a Python or TypeScript program that builds a `DirSQL`
505
+ with a terminal attached gets the same two phases on stderr, and the same
506
+ silence when piped.
@@ -396,3 +396,22 @@ not fit that range is a hard error, not a lossy conversion: Python raises
396
396
  A TypeScript `bigint` **within** `i64` range maps to `INTEGER`. Only a real
397
397
  `bytes`/`bytearray` (Python) or `Buffer`/`Uint8Array` (TypeScript) maps to
398
398
  `BLOB` — a list/array of integers does not.
399
+
400
+ ## Progress on construction
401
+
402
+ Constructing a `DirSQL` walks the tree and ingests every matched file, which
403
+ on a large corpus is the slowest thing your program does. The core reports
404
+ both phases on **stderr** while they run, then erases the live line and leaves
405
+ one summary of what each cost:
406
+
407
+ ```
408
+ dirsql: indexed 41231 files in 3m12s
409
+ ```
410
+
411
+ This is terminal-only by default, and only for a phase that runs longer than
412
+ half a second — a program whose stderr is a pipe, a file, or a log collector
413
+ sees nothing, whatever the scan costs. Set `DIRSQL_PROGRESS=never` to
414
+ guarantee silence even on a terminal, or `DIRSQL_PROGRESS=always` to report
415
+ regardless. The full table is in the
416
+ [CLI reference](cli.md#progress-reporting); the setting lives in the shared
417
+ core, so it behaves identically from all three SDKs.
@@ -0,0 +1,7 @@
1
+ {
2
+ "command": "uv run python -m pytest tests/e2e/ -x -q",
3
+ "ran_at": 1787261032,
4
+ "exit_code": 0,
5
+ "commit": "eed954811817292c5c3dad0577eb90ab551e2e73",
6
+ "branch": "claude/tackle-957-lrm0z6"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "command": "uv run python -m pytest tests/e2e/ -x -q",
3
+ "ran_at": 1787287690,
4
+ "exit_code": 0,
5
+ "commit": "8e3939201856c8bcaf2b7c2c951371711c753dbb",
6
+ "branch": "claude/tackle-975-n8mc2h"
7
+ }
@@ -0,0 +1,34 @@
1
+ # Per-package config so `[e2e]` can be per-package: it is one global table
2
+ # applied to every caller reading a given config, so "the core plus MY binding"
3
+ # has nowhere else to live. Folded back into the root file, a napi change would
4
+ # start staling this package.
5
+ #
6
+ # The PyO3 glue is in scope because the console script runs the CLI in-process
7
+ # through it (#721), so every e2e case crosses it. packages/ts/napi is not: it
8
+ # cannot reach this suite.
9
+ [e2e]
10
+ extra_scope = ["packages/rust/src", "packages/python/src"]
11
+
12
+ [python]
13
+ one_function_per_file = { max_lines = 100 }
14
+
15
+ # Same floor as the root file, which this lane no longer reads.
16
+ [python.coverage]
17
+ fail_under = 100
18
+ branch = true
19
+
20
+ # The hermetic integration tier is defined by patching the first-party core
21
+ # boundary: `dirsql._async._RustDirSQL` is the PyO3 class, and replacing it is
22
+ # what lets these tests run SQLite-free and native-build-free. There is no
23
+ # third-party target to re-root onto, so `no-first-party-patch` has nothing to
24
+ # say about this seam. Every other patch in both files targets stdlib and stays
25
+ # gated.
26
+ [[python.exempt]]
27
+ path = "tests/integration/hermetic/dirsql_test.py"
28
+ rules = ["no-first-party-patch"]
29
+ reason = "the hermetic tier IS the _RustDirSQL core-boundary patch; no third-party target exists to move it to"
30
+
31
+ [[python.exempt]]
32
+ path = "tests/integration/hermetic/extensions_test.py"
33
+ rules = ["no-first-party-patch"]
34
+ reason = "the hermetic tier IS the _RustDirSQL core-boundary patch; no third-party target exists to move it to"
@@ -6,7 +6,7 @@ description = "Ephemeral SQL index over a local directory"
6
6
  # version stays for the binding crates (publish = false). The literal
7
7
  # intentionally lags the published version: putitoutthere rewrites it to
8
8
  # the planned release version at build time and never commits it back.
9
- version = "0.4.30"
9
+ version = "0.4.32"
10
10
  edition.workspace = true
11
11
  # Literal `license` (not `license.workspace = true`) because
12
12
  # putitoutthere's preflight check reads `[package].license` directly
@@ -3,10 +3,10 @@
3
3
  Ask a question in plain language and get the closest documents back — even
4
4
  when they share no keywords with it. Install
5
5
  [`dirsql-plugin-embeddings`](../plugins.md#dirsql-plugin-embeddings) and
6
- semantic search becomes plain SQL: the plugin's `embed()` function turns
7
- text into vectors, [`sqlite-vec`](https://github.com/asg017/sqlite-vec)'s
8
- `vec_distance_cosine()` measures distance, and `ORDER BY … LIMIT` does the
9
- ranking. No config, no API keys, no services — the model runs locally.
6
+ semantic search becomes plain SQL: the plugin's `embed()` function turns text
7
+ into vectors and loads [`sqlite-vec`](https://github.com/asg017/sqlite-vec),
8
+ which does the distance math. No API keys, no services — the model runs
9
+ locally.
10
10
 
11
11
  Suppose short notes live in `notes/*.md`:
12
12
 
@@ -40,7 +40,32 @@ The first-ever run downloads the model (on the order of a hundred megabytes,
40
40
  with progress on stderr); after that it loads from the local cache. Results
41
41
  print one `path<TAB>distance` line per match, closest first.
42
42
 
43
- ## The SQL behind it
43
+ ## Which shape
44
+
45
+ The one-liner embeds every matched file on every run. That is the right trade
46
+ for a question you ask once, and the wrong one for a corpus you search
47
+ repeatedly — where a stored [`vec0` index](./search-indexes.md#vector-search-vec0)
48
+ embeds each file once, at ingest, and a query embeds only the question:
49
+
50
+ | | This page: path-table | [Vector index](./search-indexes.md#vector-search-vec0) |
51
+ |---|---|---|
52
+ | Setup | none | a `[[table]]` with a `ddl` batch |
53
+ | Freshness | always current — the walk is the read | watcher-maintained; survives restarts under [`--persist`](./persist.md) |
54
+ | Per query | one `embed()` round trip **per matched file**, plus a walk that reads every file's content | one `embed()` round trip **total**, plus an in-process KNN scan |
55
+ | Top-k | `ORDER BY … LIMIT k` | `MATCH … AND k = …` |
56
+ | Good for | a one-off question, a small corpus, an ad-hoc glob | a corpus you query repeatedly |
57
+
58
+ The index only pays off when the table outlives the query — under `--persist`,
59
+ or inside a long-running [`dirsql server`](../reference/cli.md). A one-shot
60
+ `dirsql query` against an ephemeral index rebuilds the table, and therefore
61
+ re-embeds the corpus, before it answers; that is strictly more work than the
62
+ subquery below. The full recipe — the width probe, the `ddl` batch, both
63
+ triggers, and what a model-id edit costs — is
64
+ [Add a search index to a table](./search-indexes.md#vector-search-vec0).
65
+
66
+ The rest of this page is the zero-setup shape.
67
+
68
+ ## The SQL behind the one-liner
44
69
 
45
70
  The one-liner generates and runs ordinary `dirsql` SQL, and you can write it
46
71
  yourself when you want more than ranked paths — a different projection, a
@@ -79,7 +104,9 @@ Reading the query inside-out:
79
104
  and its distance are NULL too — and SQLite sorts NULLs *first* ascending,
80
105
  so without this line the unrankable files take the top-k slots.
81
106
  4. `vec_distance_cosine(...)` computes cosine distance between the two
82
- vectors; `ORDER BY distance LIMIT 3` keeps the three nearest.
107
+ vectors; `ORDER BY distance LIMIT 3` keeps the three nearest. There is no
108
+ `vec0` table here, so `sqlite-vec`'s `MATCH … AND k = …` does not apply —
109
+ for a plain expression, `ORDER BY … LIMIT k` *is* its documented top-k.
83
110
 
84
111
  Structured files compose with SQL's JSON operators — embed one field instead
85
112
  of the whole file:
@@ -93,24 +120,19 @@ ORDER BY vec_distance_cosine(emb, embed('local private models'))
93
120
  LIMIT 10
94
121
  ```
95
122
 
96
- ::: tip Top-k is `LIMIT k`
97
- If you know `sqlite-vec` you may reach for its `MATCH … AND k = 10` idiom.
98
- That syntax belongs to `sqlite-vec`'s `vec0` virtual table, which the table
99
- above does not use: a `[[table]]`'s own `name` is always a per-file row table.
100
- For plain expressions, `sqlite-vec`'s own documented pattern is exactly what
101
- this guide uses: `ORDER BY vec_distance_cosine(...) LIMIT k`. To get the `vec0`
102
- idiom instead, declare the `vec0` table alongside the row table in the same
103
- [`ddl` batch](../reference/config.md#batch-ddl) and fill it from a trigger.
104
- :::
123
+ The same projection works as an `on-file` hook feeding the indexed shape:
124
+ parse the field in the hook, store it as a column, and the trigger embeds it
125
+ once instead of on every query.
105
126
 
106
127
  ## Repeat runs are cheap
107
128
 
108
129
  Computed vectors are cached on disk, keyed on content and model
109
130
  ([vector cache](../plugins.md#vector-cache)) — re-running a search over
110
- unchanged files skips the model entirely and re-embeds only what changed.
111
- And the plugin costs nothing when idle: a query that never calls `embed()`
112
- spawns no worker and loads no model
113
- ([zero cost when unused](../plugins.md#zero-cost-when-unused)).
131
+ unchanged files skips the model entirely and re-embeds only what changed. That
132
+ takes the *inference* out of the shape above, but not the walk or the per-file
133
+ round trip; only a stored index removes those. And the plugin costs nothing
134
+ when idle: a query that never calls `embed()` spawns no worker and loads no
135
+ model ([zero cost when unused](../plugins.md#zero-cost-when-unused)).
114
136
 
115
137
  ## How `embed()` gets into SQL
116
138