foliq 0.1.0a1__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 (133) hide show
  1. foliq-0.1.0a1/.github/workflows/ci.yml +46 -0
  2. foliq-0.1.0a1/.github/workflows/docs.yml +37 -0
  3. foliq-0.1.0a1/.github/workflows/release.yml +142 -0
  4. foliq-0.1.0a1/.gitignore +11 -0
  5. foliq-0.1.0a1/.python-version +1 -0
  6. foliq-0.1.0a1/AGENTS.md +95 -0
  7. foliq-0.1.0a1/CLAUDE.md +95 -0
  8. foliq-0.1.0a1/Cargo.lock +2738 -0
  9. foliq-0.1.0a1/Cargo.toml +33 -0
  10. foliq-0.1.0a1/LICENSE +21 -0
  11. foliq-0.1.0a1/PKG-INFO +248 -0
  12. foliq-0.1.0a1/README.md +229 -0
  13. foliq-0.1.0a1/benchmarks/.gitignore +2 -0
  14. foliq-0.1.0a1/benchmarks/README.md +135 -0
  15. foliq-0.1.0a1/benchmarks/bench/__init__.py +13 -0
  16. foliq-0.1.0a1/benchmarks/bench/__main__.py +55 -0
  17. foliq-0.1.0a1/benchmarks/bench/charts.py +126 -0
  18. foliq-0.1.0a1/benchmarks/bench/datasets.py +187 -0
  19. foliq-0.1.0a1/benchmarks/bench/libraries.py +214 -0
  20. foliq-0.1.0a1/benchmarks/bench/parsing.py +178 -0
  21. foliq-0.1.0a1/benchmarks/bench/scoring.py +55 -0
  22. foliq-0.1.0a1/benchmarks/bench/search.py +228 -0
  23. foliq-0.1.0a1/benchmarks/bench/tools.py +114 -0
  24. foliq-0.1.0a1/benchmarks/bench/worker.py +89 -0
  25. foliq-0.1.0a1/benchmarks/pyproject.toml +25 -0
  26. foliq-0.1.0a1/benchmarks/results/quality-dark.svg +3093 -0
  27. foliq-0.1.0a1/benchmarks/results/quality-docx.json +912 -0
  28. foliq-0.1.0a1/benchmarks/results/quality-html.json +951 -0
  29. foliq-0.1.0a1/benchmarks/results/quality-light.svg +3093 -0
  30. foliq-0.1.0a1/benchmarks/results/quality-pdf.json +573 -0
  31. foliq-0.1.0a1/benchmarks/results/quality-pptx.json +201 -0
  32. foliq-0.1.0a1/benchmarks/results/quality-xlsx.json +336 -0
  33. foliq-0.1.0a1/benchmarks/results/search-quality-dark.svg +1287 -0
  34. foliq-0.1.0a1/benchmarks/results/search-quality-light.svg +1287 -0
  35. foliq-0.1.0a1/benchmarks/results/search-scifact.json +42 -0
  36. foliq-0.1.0a1/benchmarks/results/search-speed-dark.svg +1433 -0
  37. foliq-0.1.0a1/benchmarks/results/search-speed-light.svg +1433 -0
  38. foliq-0.1.0a1/benchmarks/results/speed-dark.svg +3013 -0
  39. foliq-0.1.0a1/benchmarks/results/speed-docx.json +56 -0
  40. foliq-0.1.0a1/benchmarks/results/speed-light.svg +3013 -0
  41. foliq-0.1.0a1/benchmarks/results/speed-pdf.json +77 -0
  42. foliq-0.1.0a1/benchmarks/results/speed-pptx.json +49 -0
  43. foliq-0.1.0a1/benchmarks/results/speed-xlsx.json +56 -0
  44. foliq-0.1.0a1/benchmarks/uv.lock +2705 -0
  45. foliq-0.1.0a1/crates/foliq/Cargo.toml +27 -0
  46. foliq-0.1.0a1/crates/foliq/examples/custom_parser.rs +46 -0
  47. foliq-0.1.0a1/crates/foliq/src/index/citation.rs +46 -0
  48. foliq-0.1.0a1/crates/foliq/src/index/mod.rs +286 -0
  49. foliq-0.1.0a1/crates/foliq/src/index/reading.rs +297 -0
  50. foliq-0.1.0a1/crates/foliq/src/index/search.rs +195 -0
  51. foliq-0.1.0a1/crates/foliq/src/index/sync.rs +281 -0
  52. foliq-0.1.0a1/crates/foliq/src/lib.rs +17 -0
  53. foliq-0.1.0a1/crates/foliq/src/markdown.rs +353 -0
  54. foliq-0.1.0a1/crates/foliq/src/parsers/docx.rs +157 -0
  55. foliq-0.1.0a1/crates/foliq/src/parsers/html.rs +226 -0
  56. foliq-0.1.0a1/crates/foliq/src/parsers/markdown.rs +20 -0
  57. foliq-0.1.0a1/crates/foliq/src/parsers/mod.rs +55 -0
  58. foliq-0.1.0a1/crates/foliq/src/parsers/office_xml.rs +324 -0
  59. foliq-0.1.0a1/crates/foliq/src/parsers/pdf.rs +46 -0
  60. foliq-0.1.0a1/crates/foliq/src/parsers/pptx.rs +166 -0
  61. foliq-0.1.0a1/crates/foliq/src/parsers/text.rs +19 -0
  62. foliq-0.1.0a1/crates/foliq/src/parsers/xlsx/formulas.rs +224 -0
  63. foliq-0.1.0a1/crates/foliq/src/parsers/xlsx/layout.rs +226 -0
  64. foliq-0.1.0a1/crates/foliq/src/parsers/xlsx/mod.rs +119 -0
  65. foliq-0.1.0a1/crates/foliq/src/parsers/xlsx/model.rs +321 -0
  66. foliq-0.1.0a1/crates/foliq/src/parsers/xlsx/read_calamine.rs +112 -0
  67. foliq-0.1.0a1/crates/foliq/src/parsers/xlsx/read_xlsx.rs +542 -0
  68. foliq-0.1.0a1/crates/foliq/src/parsers/xlsx/read_xlsx_extras.rs +403 -0
  69. foliq-0.1.0a1/crates/foliq/src/parsers/xlsx/render.rs +549 -0
  70. foliq-0.1.0a1/crates/foliq/src/parsers/xlsx/tests.rs +190 -0
  71. foliq-0.1.0a1/crates/foliq/src/parsers/xlsx/values.rs +174 -0
  72. foliq-0.1.0a1/crates/foliq/src/sinks/jsonl.rs +43 -0
  73. foliq-0.1.0a1/crates/foliq/src/sinks/mirror.rs +66 -0
  74. foliq-0.1.0a1/crates/foliq/src/sinks/mod.rs +12 -0
  75. foliq-0.1.0a1/crates/foliq/src/sources/folder.rs +106 -0
  76. foliq-0.1.0a1/crates/foliq/src/sources/mod.rs +19 -0
  77. foliq-0.1.0a1/crates/foliq/src/splitter.rs +314 -0
  78. foliq-0.1.0a1/crates/foliq/src/store/mod.rs +5 -0
  79. foliq-0.1.0a1/crates/foliq/src/store/sqlite.rs +540 -0
  80. foliq-0.1.0a1/crates/foliq/src/tokens.rs +102 -0
  81. foliq-0.1.0a1/crates/foliq/tests/index.rs +208 -0
  82. foliq-0.1.0a1/crates/foliq-cli/Cargo.toml +17 -0
  83. foliq-0.1.0a1/crates/foliq-cli/SKILL.md +36 -0
  84. foliq-0.1.0a1/crates/foliq-cli/src/lib.rs +243 -0
  85. foliq-0.1.0a1/crates/foliq-cli/src/main.rs +6 -0
  86. foliq-0.1.0a1/crates/foliq-cli/src/output.rs +164 -0
  87. foliq-0.1.0a1/crates/foliq-core/Cargo.toml +12 -0
  88. foliq-0.1.0a1/crates/foliq-core/src/chunk.rs +33 -0
  89. foliq-0.1.0a1/crates/foliq-core/src/document.rs +338 -0
  90. foliq-0.1.0a1/crates/foliq-core/src/embedder.rs +18 -0
  91. foliq-0.1.0a1/crates/foliq-core/src/error.rs +57 -0
  92. foliq-0.1.0a1/crates/foliq-core/src/lib.rs +41 -0
  93. foliq-0.1.0a1/crates/foliq-core/src/parser.rs +47 -0
  94. foliq-0.1.0a1/crates/foliq-core/src/sink.rs +34 -0
  95. foliq-0.1.0a1/crates/foliq-core/src/source.rs +38 -0
  96. foliq-0.1.0a1/crates/foliq-core/src/splitter.rs +15 -0
  97. foliq-0.1.0a1/crates/foliq-core/src/store.rs +112 -0
  98. foliq-0.1.0a1/docs/access.md +33 -0
  99. foliq-0.1.0a1/docs/agent-tools.md +64 -0
  100. foliq-0.1.0a1/docs/assets/logo-wordmark-dark.svg +7 -0
  101. foliq-0.1.0a1/docs/assets/logo-wordmark-light.svg +7 -0
  102. foliq-0.1.0a1/docs/assets/logo.svg +6 -0
  103. foliq-0.1.0a1/docs/benchmarks.md +51 -0
  104. foliq-0.1.0a1/docs/cli.md +58 -0
  105. foliq-0.1.0a1/docs/formats.md +78 -0
  106. foliq-0.1.0a1/docs/how-it-works.md +65 -0
  107. foliq-0.1.0a1/docs/index.md +102 -0
  108. foliq-0.1.0a1/docs/plugins.md +71 -0
  109. foliq-0.1.0a1/docs/python.md +58 -0
  110. foliq-0.1.0a1/prek.toml +38 -0
  111. foliq-0.1.0a1/pyproject.toml +85 -0
  112. foliq-0.1.0a1/rustfmt.toml +1 -0
  113. foliq-0.1.0a1/scripts/cargo-test.sh +16 -0
  114. foliq-0.1.0a1/scripts/stamp-dev-version.py +47 -0
  115. foliq-0.1.0a1/src/errors.rs +42 -0
  116. foliq-0.1.0a1/src/foliq/__init__.py +36 -0
  117. foliq-0.1.0a1/src/foliq/__main__.py +5 -0
  118. foliq-0.1.0a1/src/foliq/_cli.py +13 -0
  119. foliq-0.1.0a1/src/foliq/_core.pyi +172 -0
  120. foliq-0.1.0a1/src/foliq/plugins.py +70 -0
  121. foliq-0.1.0a1/src/foliq/py.typed +0 -0
  122. foliq-0.1.0a1/src/foliq/tools.py +185 -0
  123. foliq-0.1.0a1/src/json_convert.rs +85 -0
  124. foliq-0.1.0a1/src/lib.rs +38 -0
  125. foliq-0.1.0a1/src/python_index.rs +243 -0
  126. foliq-0.1.0a1/src/python_plugins.rs +256 -0
  127. foliq-0.1.0a1/tests/conftest.py +22 -0
  128. foliq-0.1.0a1/tests/fixtures.py +88 -0
  129. foliq-0.1.0a1/tests/test_cli.py +35 -0
  130. foliq-0.1.0a1/tests/test_formats.py +47 -0
  131. foliq-0.1.0a1/tests/test_index.py +124 -0
  132. foliq-0.1.0a1/uv.lock +915 -0
  133. foliq-0.1.0a1/zensical.toml +66 -0
@@ -0,0 +1,46 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, dev]
6
+ pull_request:
7
+ workflow_dispatch:
8
+ workflow_call: # release.yml reruns these checks on the tagged commit
9
+
10
+ jobs:
11
+ checks:
12
+ name: Checks and tests (Python ${{ matrix.python-version }})
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ python-version: ["3.11", "3.14"]
18
+ steps:
19
+ - uses: actions/checkout@v5
20
+ - uses: dtolnay/rust-toolchain@stable
21
+ with:
22
+ components: rustfmt, clippy
23
+ - uses: Swatinem/rust-cache@v2
24
+ - uses: astral-sh/setup-uv@v7
25
+ with:
26
+ python-version: ${{ matrix.python-version }}
27
+ enable-cache: true
28
+ - run: uv sync --frozen
29
+ # The same hooks developers run on commit and on push (see prek.toml).
30
+ - name: Commit hooks (ruff, cargo fmt, file checks)
31
+ run: uv run --frozen prek run --all-files --show-diff-on-failure
32
+ - name: Push hooks (ty, clippy, cargo test, pytest)
33
+ run: uv run --frozen prek run --all-files --stage pre-push
34
+
35
+ binary:
36
+ name: Standalone binary (${{ matrix.os }})
37
+ runs-on: ${{ matrix.os }}
38
+ strategy:
39
+ fail-fast: false
40
+ matrix:
41
+ os: [macos-latest, windows-latest]
42
+ steps:
43
+ - uses: actions/checkout@v5
44
+ - uses: dtolnay/rust-toolchain@stable
45
+ - uses: Swatinem/rust-cache@v2
46
+ - run: cargo test --locked -p foliq -p foliq-core -p foliq-cli
@@ -0,0 +1,37 @@
1
+ name: Docs
2
+
3
+ # Builds the documentation site (zensical.toml, docs/) and publishes it to GitHub Pages.
4
+ # Needs Settings > Pages > Source: "GitHub Actions".
5
+ on:
6
+ push:
7
+ branches: [main]
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+ pages: write
13
+ id-token: write
14
+
15
+ concurrency:
16
+ group: pages
17
+ cancel-in-progress: true
18
+
19
+ jobs:
20
+ deploy:
21
+ runs-on: ubuntu-latest
22
+ environment:
23
+ name: github-pages
24
+ url: ${{ steps.deployment.outputs.page_url }}
25
+ steps:
26
+ - uses: actions/checkout@v5
27
+ - uses: astral-sh/setup-uv@v7
28
+ with:
29
+ enable-cache: true
30
+ - uses: actions/configure-pages@v5
31
+ # Only the docs tools: building the site doesn't need the Rust extension.
32
+ - run: uv run --frozen --only-group docs zensical build --clean
33
+ - uses: actions/upload-pages-artifact@v4
34
+ with:
35
+ path: site
36
+ - id: deployment
37
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,142 @@
1
+ name: Release
2
+
3
+ # Every push to main publishes a development build to TestPyPI, like `0.1.0a1.dev42`
4
+ # (the version in Cargo.toml plus `.dev<run number>`, since an index never takes a version twice).
5
+ # Push a tag like `v0.1.0a1` (the Python form of the Cargo.toml version) to publish that version
6
+ # to PyPI and create a GitHub release.
7
+ on:
8
+ push:
9
+ branches: [main]
10
+ tags: ["v*"]
11
+ workflow_dispatch: # the same as a push to main: a development build to TestPyPI
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ # A newer push to main replaces a development build still in progress; releases always finish.
17
+ concurrency:
18
+ group: release-${{ github.ref }}
19
+ cancel-in-progress: ${{ github.ref_type != 'tag' }}
20
+
21
+ jobs:
22
+ ci:
23
+ name: CI
24
+ uses: ./.github/workflows/ci.yml
25
+
26
+ sdist:
27
+ name: Source distribution
28
+ runs-on: ubuntu-latest
29
+ steps:
30
+ - uses: actions/checkout@v5
31
+ with:
32
+ fetch-depth: 0 # to check that the tag is on main
33
+ - uses: astral-sh/setup-uv@v7
34
+ - name: Check the tag matches the version and is on main
35
+ if: github.ref_type == 'tag'
36
+ run: |
37
+ # Cargo.toml says `0.1.0-alpha.1`; the tag uses the Python form, `v0.1.0a1`.
38
+ version=$(uv run --no-project --with packaging python -c 'import tomllib; from packaging.version import Version; print(Version(tomllib.load(open("Cargo.toml", "rb"))["workspace"]["package"]["version"]))')
39
+ [ "$GITHUB_REF_NAME" = "v$version" ] || { echo "::error::Tag $GITHUB_REF_NAME but Cargo.toml says $version"; exit 1; }
40
+ git merge-base --is-ancestor "$GITHUB_SHA" origin/main || { echo "::error::$GITHUB_REF_NAME is not on main"; exit 1; }
41
+ - name: Stamp a development version
42
+ if: github.ref_type != 'tag'
43
+ run: uv run --no-project --python 3.12 scripts/stamp-dev-version.py ${{ github.run_number }}
44
+ - uses: PyO3/maturin-action@v1
45
+ with:
46
+ command: sdist
47
+ args: --out dist
48
+ - uses: actions/upload-artifact@v6
49
+ with:
50
+ name: dist-sdist
51
+ path: dist
52
+
53
+ wheels:
54
+ name: Wheel (${{ matrix.target }}, ${{ matrix.runner }} ${{ matrix.manylinux }})
55
+ runs-on: ${{ matrix.runner }}
56
+ strategy:
57
+ matrix:
58
+ # The extension uses the stable ABI (abi3-py311): one wheel per platform covers every CPython >= 3.11.
59
+ include:
60
+ - { runner: ubuntu-latest, target: x86_64, manylinux: auto, smoke: true }
61
+ - { runner: ubuntu-24.04-arm, target: aarch64, manylinux: auto, smoke: true }
62
+ - { runner: ubuntu-latest, target: x86_64, manylinux: musllinux_1_2 }
63
+ - { runner: ubuntu-24.04-arm, target: aarch64, manylinux: musllinux_1_2 }
64
+ - { runner: macos-latest, target: aarch64, smoke: true }
65
+ - { runner: macos-latest, target: x86_64 } # cross-compiled from Apple Silicon
66
+ - { runner: windows-latest, target: x64, smoke: true }
67
+ steps:
68
+ - uses: actions/checkout@v5
69
+ - uses: astral-sh/setup-uv@v7
70
+ - name: Stamp a development version
71
+ if: github.ref_type != 'tag'
72
+ run: uv run --no-project --python 3.12 scripts/stamp-dev-version.py ${{ github.run_number }}
73
+ - uses: PyO3/maturin-action@v1
74
+ with:
75
+ target: ${{ matrix.target }}
76
+ manylinux: ${{ matrix.manylinux }}
77
+ args: --release --locked --strip --out dist
78
+ # Install the wheel on the oldest supported Python, import it and run the CLI.
79
+ - name: Smoke test
80
+ if: matrix.smoke
81
+ shell: bash
82
+ run: |
83
+ uv run --isolated --no-project --python 3.11 --with dist/*.whl python -c "import foliq; print(foliq.__version__)"
84
+ uvx --python 3.11 --from dist/*.whl foliq --version
85
+ - uses: actions/upload-artifact@v6
86
+ with:
87
+ name: dist-${{ strategy.job-index }}
88
+ path: dist
89
+
90
+ pypi:
91
+ name: Publish to PyPI
92
+ if: github.ref_type == 'tag'
93
+ needs: [ci, sdist, wheels]
94
+ runs-on: ubuntu-latest
95
+ environment:
96
+ name: pypi
97
+ url: https://pypi.org/p/foliq
98
+ permissions:
99
+ id-token: write # trusted publishing: no API token anywhere
100
+ steps:
101
+ - uses: actions/download-artifact@v7
102
+ with:
103
+ pattern: dist-*
104
+ path: dist
105
+ merge-multiple: true
106
+ - uses: pypa/gh-action-pypi-publish@release/v1
107
+
108
+ testpypi:
109
+ name: Publish to TestPyPI
110
+ if: github.ref_type != 'tag'
111
+ needs: [ci, sdist, wheels]
112
+ runs-on: ubuntu-latest
113
+ environment:
114
+ name: testpypi
115
+ url: https://test.pypi.org/p/foliq
116
+ permissions:
117
+ id-token: write
118
+ steps:
119
+ - uses: actions/download-artifact@v7
120
+ with:
121
+ pattern: dist-*
122
+ path: dist
123
+ merge-multiple: true
124
+ - uses: pypa/gh-action-pypi-publish@release/v1
125
+ with:
126
+ repository-url: https://test.pypi.org/legacy/
127
+
128
+ github-release:
129
+ name: GitHub release
130
+ needs: pypi
131
+ runs-on: ubuntu-latest
132
+ permissions:
133
+ contents: write
134
+ steps:
135
+ - name: Create the release with generated notes
136
+ env:
137
+ GH_TOKEN: ${{ github.token }}
138
+ GH_REPO: ${{ github.repository }}
139
+ run: |
140
+ flags="--generate-notes --verify-tag"
141
+ if [[ "$GITHUB_REF_NAME" =~ (a|b|rc)[0-9]+$ ]]; then flags+=" --prerelease"; fi
142
+ gh release create "$GITHUB_REF_NAME" $flags
@@ -0,0 +1,11 @@
1
+ /target
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.so
5
+ *.pyd
6
+ .venv/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ dist/
10
+ .foliq/
11
+ site/
@@ -0,0 +1 @@
1
+ 3.11
@@ -0,0 +1,95 @@
1
+ # Notes for coding agents
2
+
3
+ foliq indexes documents (PDF, Word, PowerPoint, Excel, HTML, Markdown) and lets
4
+ AI agents search, read and cite them. It is a Rust engine with a CLI and a
5
+ Python API. Read this file before changing anything.
6
+
7
+ ## Philosophy
8
+
9
+ ### 1. Small core, everything pluggable (like pi)
10
+
11
+ foliq is a small engine with sockets, in the spirit of the pi coding agent:
12
+ the core does little, does it fast, and every part can be replaced.
13
+
14
+ - Each socket is one small Rust trait in `crates/foliq-core`: `Source`,
15
+ `Parser`, `Splitter`, `Store`, `Sink` (and `Embedder`, used from 0.2).
16
+ Built-in plugins implement the same traits as user plugins. The engine
17
+ never special-cases a built-in.
18
+ - Every socket can be filled from Rust or from Python. Python classes plug in
19
+ through `foliq.Index(parsers=..., splitter=..., sinks=...)`. Rust plugins
20
+ depend on `foliq-core` only.
21
+ - The `foliq` command runs Rust code only. It never starts Python (like ruff
22
+ or uv). Python plugins work through `import foliq` (like polars).
23
+ - Rust does the heavy work: parsing, splitting, token counting, storage,
24
+ search, sync. Files are parsed in parallel and only changed files are
25
+ re-processed.
26
+ - Start small. Add a feature when someone needs it, as a plugin if it can be
27
+ one. Prefer one obvious way to do each thing. If a change makes the core
28
+ bigger, ask whether it could be a plugin instead.
29
+
30
+ ### 2. Clean, organized, consistent code that reads well the first time
31
+
32
+ - Names say what things are, in plain words: `document`, `chunks`,
33
+ `heading_path`, `content_hash`, `is_visible_to`. No abbreviations
34
+ (`doc`, `cfg`, `ctx`, `idx`) and no Rust jargon in names.
35
+ - **Rust must be readable by a Python developer who has never seen Rust.**
36
+ Prefer simple, straight-line code over clever generics, macros, lifetimes
37
+ or iterator chains. When a Rust feature would surprise a Python developer
38
+ (`Option`, `?`, `Arc`, traits, `#[serde(...)]`), add a short comment,
39
+ ideally comparing it to Python.
40
+ - Every file starts with a `//!` comment saying what it does. Every public
41
+ item has a `///` comment. Comments explain *why*, not *what*.
42
+ - One job per file. Module docs list the files of a folder in a small table
43
+ (see `crates/foliq/src/index/mod.rs`).
44
+ - Be consistent: same words for the same things everywhere (a "reference" is
45
+ `collection/path#section`, a "citation" adds `(p4, L10-22)`, a "principal"
46
+ is who is asking). The Python API, the CLI and the Rust API use the same
47
+ names.
48
+ - The same rules apply to Python: small functions, clear names, no magic.
49
+
50
+ ## Map
51
+
52
+ | Path | What it is |
53
+ |-----------------------|----------------------------------------------------------------|
54
+ | `crates/foliq-core` | Types (`Document`, `Chunk`...) and plugin traits. Small, stable |
55
+ | `crates/foliq` | The engine: parsers, folder source, splitter, SQLite store, sinks, `Index` |
56
+ | `crates/foliq-cli` | The `foliq` command (`run(args)`), and `SKILL.md` for agents |
57
+ | `src/*.rs` | The Python extension `foliq._core` (PyO3) |
58
+ | `src/foliq` | The Python package: tools for agents, plugin protocols, CLI entry |
59
+ | `tests` | Python tests, with generated DOCX/PPTX/XLSX/PDF fixtures |
60
+ | `benchmarks` | Speed and quality against other libraries, on public corpora (own uv project) |
61
+ | `docs` | The documentation site (Zensical, `zensical.toml`) |
62
+
63
+ ## Key ideas
64
+
65
+ - Every format becomes Markdown, the one canonical text of a document.
66
+ Headings, tables and pages (`<!-- page N -->`) are found in that text, and
67
+ everything (elements, sections, chunks) is a byte range of it.
68
+ - Sections are numbered like an outline (`2.3`). References and citations use
69
+ those numbers, so an agent can go from a search hit to the full section.
70
+ - Access rules live on documents (`access`: a list of principals; empty means
71
+ everyone). The store filters before ranking, so results never leak.
72
+ `index.tools(principal=...)` binds the principal; it is never a tool argument.
73
+ - Sync is incremental: version (mtime and size), then content hash, then the
74
+ parser and splitter fingerprints decide what is re-processed. A source that
75
+ can't be listed deletes nothing.
76
+
77
+ ## Workflow
78
+
79
+ - Tools: uv, maturin, ruff, ty, pytest, cargo fmt, clippy. Hooks run with prek
80
+ (`prek.toml`): `uv run prek install` once.
81
+ - `uv run pytest` builds the extension and runs the Python tests.
82
+ `scripts/cargo-test.sh --workspace` runs the Rust tests.
83
+ - Tests use real files in temporary folders, built by `tests/fixtures.py`.
84
+ Avoid mocks.
85
+ - Docs: `uv run --only-group docs zensical serve`. Update them with every
86
+ user-facing change. Code examples live in files and are included with
87
+ `--8<--`, so they are compiled and tested.
88
+ - The version lives in one place: `[workspace.package]` in `Cargo.toml`.
89
+ - Releases (`.github/workflows/release.yml`): every push to main publishes a
90
+ development build to TestPyPI (`0.1.0a1.dev42`, stamped by
91
+ `scripts/stamp-dev-version.py`). To publish to PyPI, bump the version with
92
+ `cargo set-version --bump alpha` (or `beta`, `rc`, `release`, `patch`...;
93
+ from `cargo install cargo-edit`), which updates Cargo.toml and Cargo.lock in
94
+ SemVer form (`0.1.0-alpha.2`). Merge to main, then push the tag in Python
95
+ form (`git tag v0.1.0a2 && git push origin v0.1.0a2`).
@@ -0,0 +1,95 @@
1
+ # Notes for coding agents
2
+
3
+ foliq indexes documents (PDF, Word, PowerPoint, Excel, HTML, Markdown) and lets
4
+ AI agents search, read and cite them. It is a Rust engine with a CLI and a
5
+ Python API. Read this file before changing anything.
6
+
7
+ ## Philosophy
8
+
9
+ ### 1. Small core, everything pluggable (like pi)
10
+
11
+ foliq is a small engine with sockets, in the spirit of the pi coding agent:
12
+ the core does little, does it fast, and every part can be replaced.
13
+
14
+ - Each socket is one small Rust trait in `crates/foliq-core`: `Source`,
15
+ `Parser`, `Splitter`, `Store`, `Sink` (and `Embedder`, used from 0.2).
16
+ Built-in plugins implement the same traits as user plugins. The engine
17
+ never special-cases a built-in.
18
+ - Every socket can be filled from Rust or from Python. Python classes plug in
19
+ through `foliq.Index(parsers=..., splitter=..., sinks=...)`. Rust plugins
20
+ depend on `foliq-core` only.
21
+ - The `foliq` command runs Rust code only. It never starts Python (like ruff
22
+ or uv). Python plugins work through `import foliq` (like polars).
23
+ - Rust does the heavy work: parsing, splitting, token counting, storage,
24
+ search, sync. Files are parsed in parallel and only changed files are
25
+ re-processed.
26
+ - Start small. Add a feature when someone needs it, as a plugin if it can be
27
+ one. Prefer one obvious way to do each thing. If a change makes the core
28
+ bigger, ask whether it could be a plugin instead.
29
+
30
+ ### 2. Clean, organized, consistent code that reads well the first time
31
+
32
+ - Names say what things are, in plain words: `document`, `chunks`,
33
+ `heading_path`, `content_hash`, `is_visible_to`. No abbreviations
34
+ (`doc`, `cfg`, `ctx`, `idx`) and no Rust jargon in names.
35
+ - **Rust must be readable by a Python developer who has never seen Rust.**
36
+ Prefer simple, straight-line code over clever generics, macros, lifetimes
37
+ or iterator chains. When a Rust feature would surprise a Python developer
38
+ (`Option`, `?`, `Arc`, traits, `#[serde(...)]`), add a short comment,
39
+ ideally comparing it to Python.
40
+ - Every file starts with a `//!` comment saying what it does. Every public
41
+ item has a `///` comment. Comments explain *why*, not *what*.
42
+ - One job per file. Module docs list the files of a folder in a small table
43
+ (see `crates/foliq/src/index/mod.rs`).
44
+ - Be consistent: same words for the same things everywhere (a "reference" is
45
+ `collection/path#section`, a "citation" adds `(p4, L10-22)`, a "principal"
46
+ is who is asking). The Python API, the CLI and the Rust API use the same
47
+ names.
48
+ - The same rules apply to Python: small functions, clear names, no magic.
49
+
50
+ ## Map
51
+
52
+ | Path | What it is |
53
+ |-----------------------|----------------------------------------------------------------|
54
+ | `crates/foliq-core` | Types (`Document`, `Chunk`...) and plugin traits. Small, stable |
55
+ | `crates/foliq` | The engine: parsers, folder source, splitter, SQLite store, sinks, `Index` |
56
+ | `crates/foliq-cli` | The `foliq` command (`run(args)`), and `SKILL.md` for agents |
57
+ | `src/*.rs` | The Python extension `foliq._core` (PyO3) |
58
+ | `src/foliq` | The Python package: tools for agents, plugin protocols, CLI entry |
59
+ | `tests` | Python tests, with generated DOCX/PPTX/XLSX/PDF fixtures |
60
+ | `benchmarks` | Speed and quality against other libraries, on public corpora (own uv project) |
61
+ | `docs` | The documentation site (Zensical, `zensical.toml`) |
62
+
63
+ ## Key ideas
64
+
65
+ - Every format becomes Markdown, the one canonical text of a document.
66
+ Headings, tables and pages (`<!-- page N -->`) are found in that text, and
67
+ everything (elements, sections, chunks) is a byte range of it.
68
+ - Sections are numbered like an outline (`2.3`). References and citations use
69
+ those numbers, so an agent can go from a search hit to the full section.
70
+ - Access rules live on documents (`access`: a list of principals; empty means
71
+ everyone). The store filters before ranking, so results never leak.
72
+ `index.tools(principal=...)` binds the principal; it is never a tool argument.
73
+ - Sync is incremental: version (mtime and size), then content hash, then the
74
+ parser and splitter fingerprints decide what is re-processed. A source that
75
+ can't be listed deletes nothing.
76
+
77
+ ## Workflow
78
+
79
+ - Tools: uv, maturin, ruff, ty, pytest, cargo fmt, clippy. Hooks run with prek
80
+ (`prek.toml`): `uv run prek install` once.
81
+ - `uv run pytest` builds the extension and runs the Python tests.
82
+ `scripts/cargo-test.sh --workspace` runs the Rust tests.
83
+ - Tests use real files in temporary folders, built by `tests/fixtures.py`.
84
+ Avoid mocks.
85
+ - Docs: `uv run --only-group docs zensical serve`. Update them with every
86
+ user-facing change. Code examples live in files and are included with
87
+ `--8<--`, so they are compiled and tested.
88
+ - The version lives in one place: `[workspace.package]` in `Cargo.toml`.
89
+ - Releases (`.github/workflows/release.yml`): every push to main publishes a
90
+ development build to TestPyPI (`0.1.0a1.dev42`, stamped by
91
+ `scripts/stamp-dev-version.py`). To publish to PyPI, bump the version with
92
+ `cargo set-version --bump alpha` (or `beta`, `rc`, `release`, `patch`...;
93
+ from `cargo install cargo-edit`), which updates Cargo.toml and Cargo.lock in
94
+ SemVer form (`0.1.0-alpha.2`). Merge to main, then push the tag in Python
95
+ form (`git tag v0.1.0a2 && git push origin v0.1.0a2`).