fndr 0.0.1__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 (255) hide show
  1. fndr-0.0.1/.github/FUNDING.yml +1 -0
  2. fndr-0.0.1/.github/workflows/ci.yml +60 -0
  3. fndr-0.0.1/.github/workflows/release.yml +210 -0
  4. fndr-0.0.1/.github/workflows/security.yml +73 -0
  5. fndr-0.0.1/.gitignore +30 -0
  6. fndr-0.0.1/.gitleaksignore +6 -0
  7. fndr-0.0.1/.pre-commit-config.yaml +56 -0
  8. fndr-0.0.1/.python-version +1 -0
  9. fndr-0.0.1/CONTRIBUTING.md +40 -0
  10. fndr-0.0.1/LICENSE +21 -0
  11. fndr-0.0.1/Makefile +27 -0
  12. fndr-0.0.1/PKG-INFO +527 -0
  13. fndr-0.0.1/README.md +491 -0
  14. fndr-0.0.1/SECURITY.md +121 -0
  15. fndr-0.0.1/docs/apps.md +124 -0
  16. fndr-0.0.1/fnd/__init__.py +9 -0
  17. fndr-0.0.1/fnd/__main__.py +4 -0
  18. fndr-0.0.1/fnd/_perms.py +75 -0
  19. fndr-0.0.1/fnd/apps.py +820 -0
  20. fndr-0.0.1/fnd/cache.py +220 -0
  21. fndr-0.0.1/fnd/cascade.py +515 -0
  22. fndr-0.0.1/fnd/cli.py +750 -0
  23. fndr-0.0.1/fnd/config.py +792 -0
  24. fndr-0.0.1/fnd/dismissed_pdfs.py +55 -0
  25. fndr-0.0.1/fnd/explain.py +181 -0
  26. fndr-0.0.1/fnd/extract/__init__.py +51 -0
  27. fndr-0.0.1/fnd/extract/_docling_daemon.py +156 -0
  28. fndr-0.0.1/fnd/extract/_docling_helper.py +89 -0
  29. fndr-0.0.1/fnd/extract/_limits.py +39 -0
  30. fndr-0.0.1/fnd/extract/_ooxml.py +46 -0
  31. fndr-0.0.1/fnd/extract/_worker.py +485 -0
  32. fndr-0.0.1/fnd/extract/base.py +78 -0
  33. fndr-0.0.1/fnd/extract/docx.py +317 -0
  34. fndr-0.0.1/fnd/extract/markdown.py +221 -0
  35. fndr-0.0.1/fnd/extract/pdf.py +694 -0
  36. fndr-0.0.1/fnd/extract/plain.py +57 -0
  37. fndr-0.0.1/fnd/extract/pptx.py +232 -0
  38. fndr-0.0.1/fnd/extras.py +423 -0
  39. fndr-0.0.1/fnd/filter_dsl.py +445 -0
  40. fndr-0.0.1/fnd/frontmatter.py +185 -0
  41. fndr-0.0.1/fnd/fusion.py +528 -0
  42. fndr-0.0.1/fnd/index.py +302 -0
  43. fndr-0.0.1/fnd/index_runner.py +674 -0
  44. fndr-0.0.1/fnd/layered.py +282 -0
  45. fndr-0.0.1/fnd/matching.py +291 -0
  46. fndr-0.0.1/fnd/meta_blob.py +51 -0
  47. fndr-0.0.1/fnd/migrate.py +128 -0
  48. fndr-0.0.1/fnd/opener.py +210 -0
  49. fndr-0.0.1/fnd/query.py +589 -0
  50. fndr-0.0.1/fnd/query_dsl.py +214 -0
  51. fndr-0.0.1/fnd/render.py +407 -0
  52. fndr-0.0.1/fnd/rerank.py +246 -0
  53. fndr-0.0.1/fnd/schema.py +120 -0
  54. fndr-0.0.1/fnd/seen_log.py +55 -0
  55. fndr-0.0.1/fnd/state.py +103 -0
  56. fndr-0.0.1/fnd/struct.py +21 -0
  57. fndr-0.0.1/fnd/synonyms.py +139 -0
  58. fndr-0.0.1/fnd/tui/__init__.py +5 -0
  59. fndr-0.0.1/fnd/tui/_md_flat.py +104 -0
  60. fndr-0.0.1/fnd/tui/_md_hybrid.py +302 -0
  61. fndr-0.0.1/fnd/tui/_perf.py +76 -0
  62. fndr-0.0.1/fnd/tui/actions.py +318 -0
  63. fndr-0.0.1/fnd/tui/app.py +5619 -0
  64. fndr-0.0.1/fnd/tui/ax_permission_screen.py +150 -0
  65. fndr-0.0.1/fnd/tui/config_recovery_screen.py +248 -0
  66. fndr-0.0.1/fnd/tui/cost_estimate.py +238 -0
  67. fndr-0.0.1/fnd/tui/extras_install_progress.py +393 -0
  68. fndr-0.0.1/fnd/tui/failure_log.py +138 -0
  69. fndr-0.0.1/fnd/tui/first_reindex_warning.py +201 -0
  70. fndr-0.0.1/fnd/tui/indexer_modal.py +999 -0
  71. fndr-0.0.1/fnd/tui/lazy_trailing.py +113 -0
  72. fndr-0.0.1/fnd/tui/line_buffer.py +729 -0
  73. fndr-0.0.1/fnd/tui/live_progress.py +135 -0
  74. fndr-0.0.1/fnd/tui/menu.py +2382 -0
  75. fndr-0.0.1/fnd/tui/open_with_screen.py +293 -0
  76. fndr-0.0.1/fnd/tui/preview_dispatcher.py +77 -0
  77. fndr-0.0.1/fnd/tui/preview_scrollbar.py +328 -0
  78. fndr-0.0.1/fnd/tui/progress.py +198 -0
  79. fndr-0.0.1/fnd/tui/settings_screen.py +4193 -0
  80. fndr-0.0.1/fnd/tui/upgrade_banner.py +202 -0
  81. fndr-0.0.1/fnd/tui/widgets/__init__.py +5 -0
  82. fndr-0.0.1/fnd/tui/widgets/detail_strip.py +79 -0
  83. fndr-0.0.1/fnd/walk.py +183 -0
  84. fndr-0.0.1/pyproject.toml +124 -0
  85. fndr-0.0.1/pyrightconfig.json +14 -0
  86. fndr-0.0.1/ruff.toml +62 -0
  87. fndr-0.0.1/tests/__init__.py +0 -0
  88. fndr-0.0.1/tests/_pilot_wait.py +96 -0
  89. fndr-0.0.1/tests/conftest.py +218 -0
  90. fndr-0.0.1/tests/fixtures/_build.py +195 -0
  91. fndr-0.0.1/tests/fixtures/docs/methods.docx +0 -0
  92. fndr-0.0.1/tests/fixtures/malformed/README.md +15 -0
  93. fndr-0.0.1/tests/fixtures/notes/index.md +23 -0
  94. fndr-0.0.1/tests/fixtures/papers/test.pdf +0 -0
  95. fndr-0.0.1/tests/fixtures/plain/short.txt +6 -0
  96. fndr-0.0.1/tests/fixtures/slides/deck.pptx +0 -0
  97. fndr-0.0.1/tests/fuzz/__init__.py +0 -0
  98. fndr-0.0.1/tests/fuzz/test_extractor_fuzz.py +171 -0
  99. fndr-0.0.1/tests/test_actions_keymap.py +178 -0
  100. fndr-0.0.1/tests/test_app_defaults_picker.py +124 -0
  101. fndr-0.0.1/tests/test_app_defaults_picker_ui.py +91 -0
  102. fndr-0.0.1/tests/test_app_flat_buffer_path.py +391 -0
  103. fndr-0.0.1/tests/test_apps_registry.py +641 -0
  104. fndr-0.0.1/tests/test_ax_permission_modal.py +174 -0
  105. fndr-0.0.1/tests/test_cache_cli.py +116 -0
  106. fndr-0.0.1/tests/test_cli_collection_add.py +159 -0
  107. fndr-0.0.1/tests/test_cli_migrate.py +98 -0
  108. fndr-0.0.1/tests/test_cli_routing.py +120 -0
  109. fndr-0.0.1/tests/test_cli_search_meta.py +71 -0
  110. fndr-0.0.1/tests/test_clone_source.py +152 -0
  111. fndr-0.0.1/tests/test_collection_name_validation.py +66 -0
  112. fndr-0.0.1/tests/test_collection_tristate_toggle.py +238 -0
  113. fndr-0.0.1/tests/test_collections.py +165 -0
  114. fndr-0.0.1/tests/test_config_recovery_screen.py +52 -0
  115. fndr-0.0.1/tests/test_config_sources.py +120 -0
  116. fndr-0.0.1/tests/test_config_validate_filter.py +52 -0
  117. fndr-0.0.1/tests/test_config_write_collection.py +126 -0
  118. fndr-0.0.1/tests/test_config_write_setting.py +94 -0
  119. fndr-0.0.1/tests/test_cost_estimate.py +154 -0
  120. fndr-0.0.1/tests/test_cursor_on_expand.py +72 -0
  121. fndr-0.0.1/tests/test_delete_source.py +94 -0
  122. fndr-0.0.1/tests/test_delete_source_e2e.py +164 -0
  123. fndr-0.0.1/tests/test_detail_strip_advisory.py +159 -0
  124. fndr-0.0.1/tests/test_escape_cascade.py +61 -0
  125. fndr-0.0.1/tests/test_extract_pdf_subprocess.py +85 -0
  126. fndr-0.0.1/tests/test_extract_pdf_wedge_detector.py +113 -0
  127. fndr-0.0.1/tests/test_extract_pool_fd_recovery.py +52 -0
  128. fndr-0.0.1/tests/test_extract_pptx_docx.py +42 -0
  129. fndr-0.0.1/tests/test_extract_safety.py +204 -0
  130. fndr-0.0.1/tests/test_extraction_cache.py +253 -0
  131. fndr-0.0.1/tests/test_extractor_docx_emits_markdown.py +133 -0
  132. fndr-0.0.1/tests/test_extractor_md_preserves_source.py +187 -0
  133. fndr-0.0.1/tests/test_extractor_pptx_emits_markdown.py +130 -0
  134. fndr-0.0.1/tests/test_extras_cli.py +115 -0
  135. fndr-0.0.1/tests/test_extras_detection.py +137 -0
  136. fndr-0.0.1/tests/test_extras_install_repair.py +63 -0
  137. fndr-0.0.1/tests/test_filename_elision.py +129 -0
  138. fndr-0.0.1/tests/test_filter_dsl.py +282 -0
  139. fndr-0.0.1/tests/test_first_reindex_warning.py +98 -0
  140. fndr-0.0.1/tests/test_frontmatter.py +99 -0
  141. fndr-0.0.1/tests/test_frontmatter_limits.py +56 -0
  142. fndr-0.0.1/tests/test_full_document_preview.py +169 -0
  143. fndr-0.0.1/tests/test_fuzzy_toggle.py +145 -0
  144. fndr-0.0.1/tests/test_fuzzy_toggle_tui.py +96 -0
  145. fndr-0.0.1/tests/test_get_file_chunks_parallel.py +148 -0
  146. fndr-0.0.1/tests/test_grouped_search.py +48 -0
  147. fndr-0.0.1/tests/test_highlight_covers_fuzzy_and_synonyms.py +247 -0
  148. fndr-0.0.1/tests/test_highlights_toggle.py +137 -0
  149. fndr-0.0.1/tests/test_hit_field_preservation.py +73 -0
  150. fndr-0.0.1/tests/test_index_meta_blob.py +70 -0
  151. fndr-0.0.1/tests/test_index_per_source_filter.py +56 -0
  152. fndr-0.0.1/tests/test_index_query_roundtrip.py +53 -0
  153. fndr-0.0.1/tests/test_index_runner.py +334 -0
  154. fndr-0.0.1/tests/test_indexer_modal_status_lines.py +31 -0
  155. fndr-0.0.1/tests/test_keybindings_help_overlay.py +211 -0
  156. fndr-0.0.1/tests/test_keybindings_provider.py +153 -0
  157. fndr-0.0.1/tests/test_lazy_mount_on_scroll.py +281 -0
  158. fndr-0.0.1/tests/test_line_buffer.py +578 -0
  159. fndr-0.0.1/tests/test_markdown_heading_marker.py +181 -0
  160. fndr-0.0.1/tests/test_md_line_tracking.py +87 -0
  161. fndr-0.0.1/tests/test_meta_blob.py +67 -0
  162. fndr-0.0.1/tests/test_migrate.py +35 -0
  163. fndr-0.0.1/tests/test_open_with_screen.py +301 -0
  164. fndr-0.0.1/tests/test_opener.py +155 -0
  165. fndr-0.0.1/tests/test_opener_injection.py +103 -0
  166. fndr-0.0.1/tests/test_pdf_docling_daemon.py +210 -0
  167. fndr-0.0.1/tests/test_pdf_extract_structured.py +75 -0
  168. fndr-0.0.1/tests/test_pdf_extras_optional.py +85 -0
  169. fndr-0.0.1/tests/test_pdf_page_label.py +260 -0
  170. fndr-0.0.1/tests/test_perms.py +66 -0
  171. fndr-0.0.1/tests/test_phase_5_6_polish.py +273 -0
  172. fndr-0.0.1/tests/test_phase_5_7_polish.py +176 -0
  173. fndr-0.0.1/tests/test_phase_5_8_scroll_to_match.py +205 -0
  174. fndr-0.0.1/tests/test_phase_5_9_stem_highlight.py +138 -0
  175. fndr-0.0.1/tests/test_phase_7_rerank.py +383 -0
  176. fndr-0.0.1/tests/test_phase_8_cascade_synonyms.py +256 -0
  177. fndr-0.0.1/tests/test_phase_9_fusion.py +358 -0
  178. fndr-0.0.1/tests/test_pizza_highlight.py +40 -0
  179. fndr-0.0.1/tests/test_pr_review_fixes.py +293 -0
  180. fndr-0.0.1/tests/test_preview_dispatcher.py +103 -0
  181. fndr-0.0.1/tests/test_preview_focus_class_toggle.py +110 -0
  182. fndr-0.0.1/tests/test_preview_leak_probe.py +90 -0
  183. fndr-0.0.1/tests/test_preview_load_debounce.py +114 -0
  184. fndr-0.0.1/tests/test_preview_markdown_widget.py +457 -0
  185. fndr-0.0.1/tests/test_preview_prefetch.py +285 -0
  186. fndr-0.0.1/tests/test_preview_scrolls_to_match.py +311 -0
  187. fndr-0.0.1/tests/test_query_dsl.py +136 -0
  188. fndr-0.0.1/tests/test_query_dsl_split.py +65 -0
  189. fndr-0.0.1/tests/test_query_limits.py +38 -0
  190. fndr-0.0.1/tests/test_query_metadata_filter.py +124 -0
  191. fndr-0.0.1/tests/test_render.py +61 -0
  192. fndr-0.0.1/tests/test_results_preview_arrow_bridge.py +81 -0
  193. fndr-0.0.1/tests/test_schema_bump.py +28 -0
  194. fndr-0.0.1/tests/test_schema_meta_blob.py +142 -0
  195. fndr-0.0.1/tests/test_settings_async_loading.py +195 -0
  196. fndr-0.0.1/tests/test_settings_cache_maintenance.py +334 -0
  197. fndr-0.0.1/tests/test_settings_confirm_body.py +94 -0
  198. fndr-0.0.1/tests/test_settings_description_invariants.py +131 -0
  199. fndr-0.0.1/tests/test_settings_extras_install_progress.py +224 -0
  200. fndr-0.0.1/tests/test_settings_hint_per_kind.py +160 -0
  201. fndr-0.0.1/tests/test_settings_indexing.py +252 -0
  202. fndr-0.0.1/tests/test_settings_indexing_search.py +100 -0
  203. fndr-0.0.1/tests/test_settings_input_focus_flow.py +199 -0
  204. fndr-0.0.1/tests/test_settings_menu_p2.py +269 -0
  205. fndr-0.0.1/tests/test_settings_nav_fixes.py +107 -0
  206. fndr-0.0.1/tests/test_settings_p3_keybindings_invoke.py +112 -0
  207. fndr-0.0.1/tests/test_settings_p3_reveal.py +75 -0
  208. fndr-0.0.1/tests/test_settings_p3_search.py +251 -0
  209. fndr-0.0.1/tests/test_settings_p3_visual.py +651 -0
  210. fndr-0.0.1/tests/test_settings_p3_wizard.py +481 -0
  211. fndr-0.0.1/tests/test_settings_structured_pdf.py +257 -0
  212. fndr-0.0.1/tests/test_settings_visual_language.py +333 -0
  213. fndr-0.0.1/tests/test_sidebar_preview_regressions.py +251 -0
  214. fndr-0.0.1/tests/test_smoke.py +10 -0
  215. fndr-0.0.1/tests/test_source_app_config.py +158 -0
  216. fndr-0.0.1/tests/test_source_form_app_picker.py +124 -0
  217. fndr-0.0.1/tests/test_state.py +113 -0
  218. fndr-0.0.1/tests/test_tui.py +121 -0
  219. fndr-0.0.1/tests/test_tui_metadata_filter.py +68 -0
  220. fndr-0.0.1/tests/test_update_all_collections.py +136 -0
  221. fndr-0.0.1/tests/test_ux_a2_footer.py +92 -0
  222. fndr-0.0.1/tests/test_ux_a_pane_titles.py +115 -0
  223. fndr-0.0.1/tests/test_ux_b_persistence_and_collapse.py +131 -0
  224. fndr-0.0.1/tests/test_ux_b_smart_collapse.py +110 -0
  225. fndr-0.0.1/tests/test_ux_c_score_bars.py +56 -0
  226. fndr-0.0.1/tests/test_ux_d_collections_panel.py +136 -0
  227. fndr-0.0.1/tests/test_ux_f_filters_panel.py +221 -0
  228. fndr-0.0.1/tests/test_ux_g_preview_polish.py +161 -0
  229. fndr-0.0.1/tests/test_ux_j_cascade_fallback.py +101 -0
  230. fndr-0.0.1/tests/test_ux_j_fusion_regression.py +127 -0
  231. fndr-0.0.1/tests/test_ux_j_scrollbar_markers.py +225 -0
  232. fndr-0.0.1/tests/test_uxp4_cli_explain.py +94 -0
  233. fndr-0.0.1/tests/test_uxp4_explain_trace.py +228 -0
  234. fndr-0.0.1/tests/test_uxp4_intent_line.py +133 -0
  235. fndr-0.0.1/tests/test_uxp4_preview_worker.py +257 -0
  236. fndr-0.0.1/tests/test_uxp4_strong_signal.py +153 -0
  237. fndr-0.0.1/tests/test_uxp4_tui_explain.py +105 -0
  238. fndr-0.0.1/tests/test_vault_detect.py +55 -0
  239. fndr-0.0.1/tests/test_walk_icloud.py +42 -0
  240. fndr-0.0.1/tests/test_walk_per_source.py +95 -0
  241. fndr-0.0.1/tests/test_walk_symlinks.py +52 -0
  242. fndr-0.0.1/tests/workflows/README.md +52 -0
  243. fndr-0.0.1/tests/workflows/__init__.py +11 -0
  244. fndr-0.0.1/tests/workflows/conftest.py +89 -0
  245. fndr-0.0.1/tests/workflows/test_cache_actions.py +92 -0
  246. fndr-0.0.1/tests/workflows/test_cache_hits_across_runs.py +81 -0
  247. fndr-0.0.1/tests/workflows/test_delete_collection.py +52 -0
  248. fndr-0.0.1/tests/workflows/test_first_reindex_warning.py +114 -0
  249. fndr-0.0.1/tests/workflows/test_install_pdf_structure.py +108 -0
  250. fndr-0.0.1/tests/workflows/test_per_collection_update.py +59 -0
  251. fndr-0.0.1/tests/workflows/test_toggles.py +84 -0
  252. fndr-0.0.1/tests/workflows/test_uninstall_pdf_structure.py +87 -0
  253. fndr-0.0.1/tests/workflows/test_update_all.py +98 -0
  254. fndr-0.0.1/tests/workflows/test_update_all_index_docs.py +117 -0
  255. fndr-0.0.1/uv.lock +1155 -0
@@ -0,0 +1 @@
1
+ custom: ["https://buymeacoffee.com/ben.dev.au"]
@@ -0,0 +1,60 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ test:
14
+ name: pytest (full suite, serial)
15
+ # macOS so the path resolution (platformdirs uses
16
+ # ``~/Library/Application Support/fnd`` here) matches what users
17
+ # actually run. Free tier on public repos; private repos use the
18
+ # 10x multiplier so keep an eye on the minutes budget while the
19
+ # repo is still private.
20
+ runs-on: macos-14
21
+ timeout-minutes: 30
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v3
26
+ with:
27
+ enable-cache: true
28
+ - name: Set up Python 3.13
29
+ run: uv python install 3.13
30
+ - name: Sync deps (frozen lockfile)
31
+ run: uv sync --frozen
32
+ - name: Test
33
+ # Serial, no --testmon. CI is the gold-standard catch-all
34
+ # behind the local pre-commit hook, which uses --testmon for
35
+ # speed and may skip tests the developer's diff doesn't reach.
36
+ run: uv run pytest -q -m "not slow"
37
+
38
+ format:
39
+ name: ruff-format
40
+ # Format check is a separate small job so a formatting miss is
41
+ # obvious in the PR check list without re-running the slow tests.
42
+ runs-on: ubuntu-latest
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+ - uses: astral-sh/setup-uv@v3
46
+ - run: uv python install 3.13
47
+ - run: uv sync --frozen --group dev
48
+ - name: Ruff format (check only)
49
+ run: uv run ruff format --check .
50
+
51
+ typecheck:
52
+ name: pyright (strict)
53
+ runs-on: ubuntu-latest
54
+ steps:
55
+ - uses: actions/checkout@v4
56
+ - uses: astral-sh/setup-uv@v3
57
+ - run: uv python install 3.13
58
+ - run: uv sync --frozen
59
+ - name: Pyright
60
+ run: uv run pyright
@@ -0,0 +1,210 @@
1
+ name: release
2
+
3
+ # Triggered on any `v*` tag. Builds sdist + wheel, emits SBOM and a
4
+ # SLSA-style build-provenance attestation, publishes to PyPI via a
5
+ # Trusted Publisher (OIDC — no API token in repo secrets), and opens
6
+ # a bump-PR against the Homebrew tap with the new sdist SHA-256.
7
+
8
+ on:
9
+ push:
10
+ tags:
11
+ - "v*"
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ jobs:
17
+ build:
18
+ name: build sdist + wheel
19
+ runs-on: ubuntu-latest
20
+ permissions:
21
+ contents: read
22
+ # `id-token: write` lets the build job mint an OIDC token for
23
+ # the attestation step (and later for PyPI Trusted Publisher).
24
+ id-token: write
25
+ # `attestations: write` lets actions/attest-build-provenance
26
+ # record the SLSA-style provenance for the artifacts we produce.
27
+ attestations: write
28
+ outputs:
29
+ sdist-name: ${{ steps.meta.outputs.sdist }}
30
+ sdist-sha256: ${{ steps.meta.outputs.sha256 }}
31
+ version: ${{ steps.meta.outputs.version }}
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+ with:
35
+ fetch-depth: 0
36
+
37
+ - name: Guard — release tag matches pyproject version
38
+ # Fail fast (before build/publish) if the `vX.Y.Z` tag and the
39
+ # declared version have drifted. pyproject is the single source;
40
+ # this just stops a mismatched tag from shipping.
41
+ run: |
42
+ set -euo pipefail
43
+ tag="${GITHUB_REF_NAME#v}"
44
+ ver=$(python3 -c "import tomllib;print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
45
+ [ "$tag" = "$ver" ] || { echo "::error::tag v$tag != pyproject version $ver — bump pyproject.toml before tagging"; exit 1; }
46
+ echo "tag and pyproject version agree: $ver"
47
+
48
+ - name: Install uv
49
+ uses: astral-sh/setup-uv@v3
50
+ with:
51
+ enable-cache: true
52
+
53
+ - run: uv python install 3.13
54
+
55
+ - name: Build sdist + wheel
56
+ run: uv build --sdist --wheel --out-dir dist
57
+
58
+ - name: Generate CycloneDX SBOM (S2)
59
+ # SBOM lives outside dist/ so the PyPI publish step (which
60
+ # uploads the whole packages-dir via twine) doesn't try to
61
+ # upload a JSON file as a distribution and fail.
62
+ run: |
63
+ mkdir -p sbom
64
+ uv tool install cyclonedx-bom
65
+ uv export --format requirements-txt --no-hashes -o requirements.frozen.txt
66
+ uv tool run cyclonedx-py requirements requirements.frozen.txt \
67
+ --output-format JSON \
68
+ --output-file sbom/sbom.cdx.json
69
+
70
+ - name: Attest build provenance (S2)
71
+ uses: actions/attest-build-provenance@v1
72
+ with:
73
+ subject-path: |
74
+ dist/*.tar.gz
75
+ dist/*.whl
76
+
77
+ - name: Compute sdist metadata
78
+ id: meta
79
+ run: |
80
+ set -euo pipefail
81
+ sdist=$(ls dist/*.tar.gz | head -n1)
82
+ sha=$(shasum -a 256 "$sdist" | awk '{print $1}')
83
+ # PEP 625 sdist name is `{name}-{version}.tar.gz` and the name
84
+ # part carries no `-` (normalised to `_`), so the first `-`
85
+ # splits name from version — name-agnostic (fndr, fnd_x, …).
86
+ base=$(basename "$sdist" .tar.gz)
87
+ ver="${base#*-}"
88
+ echo "sdist=$(basename "$sdist")" >> "$GITHUB_OUTPUT"
89
+ echo "sha256=$sha" >> "$GITHUB_OUTPUT"
90
+ echo "version=$ver" >> "$GITHUB_OUTPUT"
91
+
92
+ - name: Upload dist artifacts
93
+ uses: actions/upload-artifact@v4
94
+ with:
95
+ name: dist
96
+ path: dist/
97
+
98
+ - name: Upload SBOM artifact
99
+ uses: actions/upload-artifact@v4
100
+ with:
101
+ name: sbom
102
+ path: sbom/
103
+
104
+ publish-pypi:
105
+ name: publish to PyPI (Trusted Publisher)
106
+ needs: build
107
+ runs-on: ubuntu-latest
108
+ environment: pypi
109
+ permissions:
110
+ contents: read
111
+ id-token: write
112
+ steps:
113
+ - name: Download dist
114
+ uses: actions/download-artifact@v4
115
+ with:
116
+ name: dist
117
+ path: dist/
118
+
119
+ - name: Publish (OIDC, no token)
120
+ uses: pypa/gh-action-pypi-publish@release/v1
121
+ with:
122
+ # Configure the Trusted Publisher at
123
+ # https://pypi.org/manage/account/publishing/ pointing at
124
+ # this repo + `release.yml` workflow + `pypi` environment.
125
+ packages-dir: dist
126
+ skip-existing: true
127
+
128
+ release-notes:
129
+ name: publish GitHub Release
130
+ needs: build
131
+ runs-on: ubuntu-latest
132
+ permissions:
133
+ contents: write
134
+ steps:
135
+ - uses: actions/checkout@v4
136
+ - name: Download dist
137
+ uses: actions/download-artifact@v4
138
+ with:
139
+ name: dist
140
+ path: dist/
141
+ - name: Download SBOM
142
+ uses: actions/download-artifact@v4
143
+ with:
144
+ name: sbom
145
+ path: sbom/
146
+ - name: Create GitHub Release
147
+ env:
148
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
149
+ run: |
150
+ set -euo pipefail
151
+ tag="${GITHUB_REF##*/}"
152
+ # Attach sdist, wheel, and SBOM; the provenance attestation
153
+ # is fetched separately via `gh attestation verify` and is
154
+ # not uploaded as a release asset (GitHub stores it).
155
+ gh release create "$tag" \
156
+ --title "fnd $tag" \
157
+ --notes "See CHANGELOG / commit log for details. SHA-256: ${{ needs.build.outputs.sdist-sha256 }}" \
158
+ dist/*.tar.gz dist/*.whl sbom/sbom.cdx.json
159
+
160
+ bump-homebrew-tap:
161
+ name: bump homebrew-tap formula
162
+ # Needs publish-pypi so the sdist exists when we resolve its URL.
163
+ needs: [build, publish-pypi]
164
+ runs-on: ubuntu-latest
165
+ permissions:
166
+ contents: read
167
+ steps:
168
+ - name: Open formula bump PR
169
+ env:
170
+ # HOMEBREW_TAP_PAT: fine-scoped PAT with PR-write to the tap
171
+ # (the default GITHUB_TOKEN can't push cross-repo). Both live
172
+ # on THIS repo, not the tap — the workflow runs here.
173
+ # HOMEBREW_TAP_REPO = ben-dev-au/homebrew-tap.
174
+ GH_TOKEN: ${{ secrets.HOMEBREW_TAP_PAT }}
175
+ TAP_REPO: ${{ vars.HOMEBREW_TAP_REPO }}
176
+ VERSION: ${{ needs.build.outputs.version }}
177
+ if: ${{ vars.HOMEBREW_TAP_REPO != '' }}
178
+ run: |
179
+ set -euo pipefail
180
+ # Resolve url + sha256 from PyPI's JSON API — one source of
181
+ # truth, immune to name normalisation and the hashed storage
182
+ # path. The index can lag the publish by a few seconds, so retry.
183
+ meta=""
184
+ for i in 1 2 3 4 5 6; do
185
+ meta=$(curl -fsSL "https://pypi.org/pypi/fndr/${VERSION}/json" \
186
+ | python3 -c "import sys,json;u=next(x for x in json.load(sys.stdin)['urls'] if x['packagetype']=='sdist');print(u['url']);print(u['digests']['sha256'])") \
187
+ && [ -n "$meta" ] && break
188
+ echo "PyPI JSON not ready (attempt $i); retrying…" >&2
189
+ sleep 10
190
+ done
191
+ [ -n "$meta" ] || { echo "could not resolve sdist for fndr $VERSION" >&2; exit 1; }
192
+ new_url=$(printf '%s\n' "$meta" | sed -n 1p)
193
+ new_sha=$(printf '%s\n' "$meta" | sed -n 2p)
194
+
195
+ tmp=$(mktemp -d)
196
+ git clone "https://x-access-token:$GH_TOKEN@github.com/$TAP_REPO.git" "$tmp/tap"
197
+ cd "$tmp/tap"
198
+ formula=Formula/fnd.rb
199
+ sed -i.bak "s|^ url \".*\"| url \"$new_url\"|" "$formula"
200
+ sed -i.bak "s|^ sha256 \".*\"| sha256 \"$new_sha\"|" "$formula"
201
+ rm -f "$formula.bak"
202
+ branch="bump/fnd-$VERSION"
203
+ git checkout -b "$branch"
204
+ git add "$formula"
205
+ git -c user.email="actions@github.com" -c user.name="fnd-release-bot" \
206
+ commit -m "fnd $VERSION"
207
+ git push -u origin "$branch"
208
+ gh pr create \
209
+ --title "fnd $VERSION" \
210
+ --body "Automated bump from ben-dev-au/fnd@${GITHUB_REF##*/}. sha256 \`$new_sha\`."
@@ -0,0 +1,73 @@
1
+ name: security
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ schedule:
9
+ # Weekly Monday 09:00 UTC — catches CVEs published since the last
10
+ # human-driven push.
11
+ - cron: "0 9 * * 1"
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ jobs:
17
+ pip-audit:
18
+ name: pip-audit (lockfile)
19
+ runs-on: macos-14
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@v3
24
+ with:
25
+ enable-cache: true
26
+ - name: Set up Python 3.13
27
+ run: uv python install 3.13
28
+ - name: Sync deps (frozen lockfile)
29
+ run: uv sync --frozen --all-extras --group dev
30
+ - name: Install pip-audit
31
+ run: uv tool install pip-audit
32
+ - name: Export resolved requirements
33
+ # `--no-emit-project` drops the local `fnd` package (which uv
34
+ # emits as `-e .`); pip-audit can't look up an unpublished
35
+ # editable install and would fail the whole audit. Once Phase
36
+ # 0 of the publish runbook lands `fnd` on PyPI, the flag stays
37
+ # — we don't need to audit ourselves, only our deps.
38
+ run: |
39
+ uv export \
40
+ --format requirements-txt \
41
+ --no-hashes \
42
+ --no-emit-project \
43
+ -o requirements.frozen.txt
44
+ - name: Audit
45
+ run: uv tool run pip-audit -r requirements.frozen.txt --strict
46
+
47
+ ruff-security:
48
+ name: ruff (bandit ruleset)
49
+ runs-on: macos-14
50
+ steps:
51
+ - uses: actions/checkout@v4
52
+ - uses: astral-sh/setup-uv@v3
53
+ - run: uv python install 3.13
54
+ - run: uv sync --frozen --group dev
55
+ - name: Ruff (project config includes select=S)
56
+ run: uv run ruff check .
57
+
58
+ gitleaks:
59
+ name: gitleaks
60
+ runs-on: ubuntu-latest
61
+ permissions:
62
+ contents: read
63
+ # gitleaks-action posts a PR-summary comment on findings; without
64
+ # `pull-requests: write` the action fails with
65
+ # `Resource not accessible by integration`.
66
+ pull-requests: write
67
+ steps:
68
+ - uses: actions/checkout@v4
69
+ with:
70
+ fetch-depth: 0
71
+ - uses: gitleaks/gitleaks-action@v2
72
+ env:
73
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
fndr-0.0.1/.gitignore ADDED
@@ -0,0 +1,30 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .pyright/
7
+ .mypy_cache/
8
+ dist/
9
+ build/
10
+ *.egg-info/
11
+ .DS_Store
12
+ .coverage
13
+ htmlcov/
14
+ # pytest-testmon per-developer test-impact cache
15
+ .testmondata
16
+ .testmondata-journal
17
+ .testmondata-shm
18
+ .testmondata-wal
19
+ .vscode/
20
+ .idea/
21
+ tests/fixtures/.cache/
22
+ # Index data is never committed (lives in user's Library on macOS)
23
+ index/
24
+ # Per-user Claude Code config / scheduled-task state
25
+ .claude/
26
+ # Per-user operational notes (release runbooks, account setup) and
27
+ # all developer-only tooling. Lives in a separate private repo at
28
+ # dev/. Not part of the public repo.
29
+ private_dev_docs/
30
+ dev/
@@ -0,0 +1,6 @@
1
+ # False positive: `key = "corruptkey--v1"` is a literal cache-key
2
+ # fixture for the corrupt-JSON test. Renamed in a later commit on
3
+ # this branch, but the historical commit still trips the
4
+ # generic-api-key heuristic because gitleaks scans the full commit
5
+ # range, not just HEAD.
6
+ ac26e70dc720c7a3b44a5a09497a395814514b4f:tests/test_extraction_cache.py:generic-api-key:84
@@ -0,0 +1,56 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v4.6.0
4
+ hooks:
5
+ - id: check-toml
6
+ - id: check-yaml
7
+ - id: end-of-file-fixer
8
+ - id: trailing-whitespace
9
+ - id: check-added-large-files
10
+ args: ["--maxkb=2000"]
11
+
12
+ - repo: https://github.com/astral-sh/ruff-pre-commit
13
+ # Keep in sync with the ruff pin in pyproject.toml dev deps; CI runs
14
+ # `uv run ruff format --check .` so drift here will reformat files
15
+ # locally that CI then rejects.
16
+ rev: v0.15.14
17
+ hooks:
18
+ - id: ruff-format
19
+ - id: ruff
20
+ args: ["--fix"]
21
+
22
+ - repo: local
23
+ hooks:
24
+ - id: pyright
25
+ name: pyright (strict)
26
+ entry: uv run pyright
27
+ language: system
28
+ types: [python]
29
+ pass_filenames: false
30
+
31
+ - id: pytest-fast
32
+ name: "pytest (testmon: only tests whose code paths changed)"
33
+ # --testmon caches per-test code dependencies in
34
+ # ``.testmondata`` (gitignored). First run pays the full suite
35
+ # cost while the baseline is built; subsequent commits only
36
+ # re-run tests whose tracked files changed in the diff. CI
37
+ # runs the full serial suite without --testmon as the
38
+ # gold-standard catch-all.
39
+ entry: uv run pytest -q -m "not slow" --testmon
40
+ language: system
41
+ types: [python]
42
+ pass_filenames: false
43
+
44
+ - id: harness-indexing
45
+ name: "harness: indexer status lines (synthetic, fast)"
46
+ # Pre-push only - runs the synthetic indexer harness when
47
+ # touching code that controls how PDFs reach the index.
48
+ # Catches cascading worker death and counter regressions
49
+ # without paying the cost on every commit. Real-corpus
50
+ # validation lives behind FND_AUDIT_REAL_CORPUS=1 and is
51
+ # opt-in via `pre-commit run --hook-stage manual`.
52
+ stages: [pre-push]
53
+ entry: uv run python dev/tools/workflow_audit_tmux.py indexer_status_lines indexer_status_lines_md_only
54
+ language: system
55
+ files: ^(fnd/extract/|fnd/index|fnd/walk\.py|fnd/tui/indexer_modal\.py|dev/tools/workflow_audit_tmux\.py)
56
+ pass_filenames: false
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,40 @@
1
+ # Contributing to fnd
2
+
3
+ Thanks for your interest. fnd is a macOS-only document-search CLI/TUI; it's
4
+ early but actively developed. Bug reports, app-catalogue entries, and focused
5
+ PRs are all welcome.
6
+
7
+ ## Development setup
8
+
9
+ Requires macOS, Python 3.13, and [uv](https://docs.astral.sh/uv/).
10
+
11
+ ```sh
12
+ git clone https://github.com/ben-dev-au/fnd.git
13
+ cd fnd
14
+ make sync # uv sync --all-extras --group dev
15
+ make install-hooks # pre-commit hooks (ruff + pyright)
16
+ ```
17
+
18
+ Run fnd from the checkout with `uv run python -m fnd <command>`.
19
+
20
+ ## Before opening a PR
21
+
22
+ ```sh
23
+ make test # full pytest suite
24
+ make lint # ruff + pyright (strict)
25
+ make fmt # auto-format and apply safe fixes
26
+ ```
27
+
28
+ CI runs the same checks on macOS. Keep PRs scoped to one change; match the
29
+ surrounding code's style and comment density.
30
+
31
+ ## Adding an app to the "Open with…" catalogue
32
+
33
+ Third-party app integrations live in [`docs/apps.md`](docs/apps.md). To
34
+ contribute one, add your `[apps.<id>]` config block to the catalogue; see that
35
+ page for the schema and safety rules.
36
+
37
+ ## Security
38
+
39
+ Please **do not** open public issues for security-sensitive findings. See
40
+ [`SECURITY.md`](SECURITY.md) for private reporting and the threat model.
fndr-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ben Davidson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
fndr-0.0.1/Makefile ADDED
@@ -0,0 +1,27 @@
1
+ .PHONY: sync test lint fmt tui snapshot install-hooks
2
+
3
+ sync:
4
+ uv sync --all-extras --group dev
5
+
6
+ test:
7
+ uv run pytest -q
8
+
9
+ test-fast:
10
+ uv run pytest -q -m "not slow"
11
+
12
+ lint:
13
+ uv run ruff check .
14
+ uv run pyright
15
+
16
+ fmt:
17
+ uv run ruff format .
18
+ uv run ruff check --fix .
19
+
20
+ tui:
21
+ uv run textual run --dev fnd.tui.app:FNDApp
22
+
23
+ snapshot:
24
+ uv run pytest --snapshot-update
25
+
26
+ install-hooks:
27
+ uv run pre-commit install