sift-engine 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sift_engine-0.2.0/PKG-INFO +211 -0
- sift_engine-0.2.0/README.md +173 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/pyproject.toml +9 -3
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/browser.py +1 -1
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/cli.py +724 -261
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/config.py +48 -0
- sift_engine-0.2.0/sift/fetch.py +772 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/mcp_server.py +2 -2
- sift_engine-0.2.0/sift/quality.py +95 -0
- sift_engine-0.2.0/sift/sources/impersonate.py +159 -0
- sift_engine-0.2.0/sift_engine.egg-info/PKG-INFO +211 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift_engine.egg-info/SOURCES.txt +3 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift_engine.egg-info/requires.txt +3 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_browser_contract.py +5 -5
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_browser_real.py +1 -3
- sift_engine-0.2.0/tests/test_tiered_transport.py +435 -0
- sift_engine-0.1.0/PKG-INFO +0 -342
- sift_engine-0.1.0/README.md +0 -306
- sift_engine-0.1.0/sift/fetch.py +0 -459
- sift_engine-0.1.0/sift_engine.egg-info/PKG-INFO +0 -342
- {sift_engine-0.1.0 → sift_engine-0.2.0}/LICENSE +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/__init__.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/agent_loop/__init__.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/agent_loop/agent.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/agent_loop/judge.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/agent_loop/questions.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/agent_loop/report.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/agent_loop/runner.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/agent_loop/tools.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/__init__.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/aggregate_report.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/drift.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/fixtures/__init__.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/fixtures/sites.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/full_suite.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/per_stage/__init__.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/per_stage/commit.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/per_stage/extract.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/per_stage/fetch.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/per_stage/mcp.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/per_stage/plan.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/per_stage/publish.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/per_stage/seed.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/report.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/runner.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/scoring/__init__.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/scoring/fidelity.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/scoring/structural.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/bench/scoring/use_case.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/cli.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/determinism.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/efficiency.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/facts_coverage.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/facts_validation.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/llm_judge.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/performance.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/sampler.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/evals/structural.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/setup.cfg +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/__init__.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/_io.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/agent_surface.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/classify.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/commit.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/decide.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/extract.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/extract_code.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/extract_next_state.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/extract_strategy.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/facts.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/index_profile.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/integrity.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/manifest.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/normalize.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/paths.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/plan.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/publish.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/purge.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/registry.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/sites/__init__.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/sites/ato.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/sites/augov.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/sites/generic.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/sites/generic_browser.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/sites/mdn.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/sites/python_docs.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/sites/stripe.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/sources/__init__.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/sources/firecrawl.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/sources/sitemap.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift/status.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift_engine.egg-info/dependency_links.txt +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift_engine.egg-info/entry_points.txt +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/sift_engine.egg-info/top_level.txt +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_agent_loop.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_agent_surface.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_backup.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_classify.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_commit_chain.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_config.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_consolidate_md.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_decide.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_eval_bench_b3.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_eval_bench_b5.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_eval_bench_drift.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_eval_bench_scoring.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_evals.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_excludes_and_pdf.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_extract_anchors.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_extract_code.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_extract_next_state.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_extract_strategy.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_facts.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_facts_coverage.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_fetch_browser.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_firecrawl_scrape.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_gates.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_integrity.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_manifest.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_markdown_passthrough.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_mcp_index.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_mcp_multi_index.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_mcp_server.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_mcp_verify.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_normalize.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_only_urls.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_pre_freeze_hardening.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_production_hardening.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_publish_integrity.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_purge.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_re_extract.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_registry.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_sites.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_sources_firecrawl.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_sources_sitemap_auto.py +0 -0
- {sift_engine-0.1.0 → sift_engine-0.2.0}/tests/test_user_agent_warnings.py +0 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sift-engine
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Sift — deterministic website indexing for grep-first LLM agents
|
|
5
|
+
Author: Deval Shah
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/dvlshah/sift
|
|
8
|
+
Project-URL: Repository, https://github.com/dvlshah/sift
|
|
9
|
+
Project-URL: Issues, https://github.com/dvlshah/sift/issues
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: httpx>=0.27
|
|
17
|
+
Requires-Dist: trafilatura>=1.12
|
|
18
|
+
Requires-Dist: aiolimiter>=1.1
|
|
19
|
+
Requires-Dist: click>=8.1
|
|
20
|
+
Requires-Dist: lxml>=5.0
|
|
21
|
+
Requires-Dist: selectolax>=0.3
|
|
22
|
+
Requires-Dist: mcp>=1.0
|
|
23
|
+
Requires-Dist: pypdf>=4.0
|
|
24
|
+
Requires-Dist: jsonschema>=4
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
|
|
28
|
+
Requires-Dist: respx>=0.21; extra == "dev"
|
|
29
|
+
Provides-Extra: evals
|
|
30
|
+
Requires-Dist: anthropic>=0.40; extra == "evals"
|
|
31
|
+
Requires-Dist: pydantic>=2.0; extra == "evals"
|
|
32
|
+
Requires-Dist: jsonschema>=4; extra == "evals"
|
|
33
|
+
Provides-Extra: browser
|
|
34
|
+
Requires-Dist: playwright>=1.40; extra == "browser"
|
|
35
|
+
Provides-Extra: impersonate
|
|
36
|
+
Requires-Dist: curl_cffi>=0.7; extra == "impersonate"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
<div align="center">
|
|
40
|
+
|
|
41
|
+
<img src="assets/sift.gif" alt="sift" width="480">
|
|
42
|
+
|
|
43
|
+
**Give your coding agent a complete, always-fresh copy of any website — with proof of every answer.**
|
|
44
|
+
|
|
45
|
+
[](https://github.com/dvlshah/sift/actions/workflows/tests.yml)
|
|
46
|
+
[](https://pypi.org/project/sift-engine/)
|
|
47
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
48
|
+
[](https://www.python.org/downloads/)
|
|
49
|
+
[](https://modelcontextprotocol.io/)
|
|
50
|
+
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
sift crawls a site into local markdown + structured facts your agent greps, reads, and cites — files on disk, not vectors, read over MCP. Every page is **content-hashed and dated**, so answers trace back to the exact source and snapshot. Re-run anytime; only changed pages are refetched. Self-hosted.
|
|
54
|
+
|
|
55
|
+
## ❌ Without sift
|
|
56
|
+
|
|
57
|
+
Your agent's built-in web-fetch gives you:
|
|
58
|
+
|
|
59
|
+
- **Stale** answers — the provider's crawler cached that page weeks ago
|
|
60
|
+
- **Partial** answers — just the one page it happened to land on
|
|
61
|
+
- **Unprovable** answers — no record of what the source said, or when
|
|
62
|
+
|
|
63
|
+
## ✅ With sift
|
|
64
|
+
|
|
65
|
+
- **Always fresh** — you control the crawl; conditional GETs refetch only what changed
|
|
66
|
+
- **Provable** — content-hash + date on every page; cite the source, hash, and snapshot
|
|
67
|
+
- **Complete & grep-native** — the whole site on disk, not a handful of retrieved snippets
|
|
68
|
+
- **Self-hosted** — any `http(s)` site you can reach, public or internal; your data stays yours
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Get started
|
|
73
|
+
|
|
74
|
+
Requires Python 3.11+.
|
|
75
|
+
|
|
76
|
+
### 🤖 Let your coding agent set it up — recommended
|
|
77
|
+
|
|
78
|
+
Paste one prompt into **Claude Code, Cursor, Codex, or any MCP-aware agent**. It explains sift, asks which site to index, then installs the engine, builds an index, wires up MCP, and shows you how to query it — end to end.
|
|
79
|
+
|
|
80
|
+
<details>
|
|
81
|
+
<summary><b>📋 Copy the one-paste setup prompt</b></summary>
|
|
82
|
+
|
|
83
|
+
````text
|
|
84
|
+
Set up **sift** in this project — a deterministic, content-hashed website indexer that
|
|
85
|
+
serves a verifiable markdown corpus to AI agents over MCP (https://github.com/dvlshah/sift).
|
|
86
|
+
Do the steps in order. Needs Python 3.11+.
|
|
87
|
+
|
|
88
|
+
0 — EXPLAIN, THEN ASK ME (do this BEFORE installing anything)
|
|
89
|
+
First, explain sift to me in 4–6 plain-English lines — assume I've never heard of it:
|
|
90
|
+
• WHAT it is: it crawls a whole website/docs site into local markdown files (plus structured
|
|
91
|
+
facts) that you, the agent, can grep / read / cite — files on disk, not a vector database.
|
|
92
|
+
• CORE FEATURES: (1) complete — the full site, not the few pages a live web-fetch happens to
|
|
93
|
+
land on; (2) verifiable — every page is content-hashed + dated, so any answer can be proved
|
|
94
|
+
back to the exact source and snapshot; (3) always-current — re-running the crawl refetches
|
|
95
|
+
only what changed; (4) read over MCP — you query it with provenance, read-only by default.
|
|
96
|
+
• WHY IT'S NEEDED: a one-off scrape gives you 3 pages with no proof of what they said or when.
|
|
97
|
+
sift keeps an agent correct about an evolving body of docs AND able to prove what it cited.
|
|
98
|
+
(If someone only needs one page once, sift is the wrong tool — a plain fetch is fine.)
|
|
99
|
+
Then ASK ME: "Which website or docs site do you want to index locally?" — wait for my answer
|
|
100
|
+
and use it as TARGET_SITE everywhere below. Do not run any commands until I answer.
|
|
101
|
+
|
|
102
|
+
1 — INSTALL THE ENGINE
|
|
103
|
+
```bash
|
|
104
|
+
pip install sift-engine # adds the `sift`, `sift-mcp`, `sift-evals` commands
|
|
105
|
+
sift --version && which sift-mcp # confirm it's on PATH
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
2 — INSTALL THE SIFT SKILL INTO THIS REPO
|
|
109
|
+
Download the skill so you (the agent) know how to build, operate, and query an index.
|
|
110
|
+
Read .claude/skills/sift/SKILL.md after downloading — it is the source of truth for the rest.
|
|
111
|
+
```bash
|
|
112
|
+
SKILL=https://raw.githubusercontent.com/dvlshah/sift/v0.2.0/.claude/skills/sift
|
|
113
|
+
mkdir -p .claude/skills/sift/reference
|
|
114
|
+
curl -fsSL $SKILL/SKILL.md -o .claude/skills/sift/SKILL.md
|
|
115
|
+
for f in cli config mcp-tools; do curl -fsSL $SKILL/reference/$f.md -o .claude/skills/sift/reference/$f.md; done
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
3 — BUILD A SMALL STARTER INDEX (using the TARGET_SITE I gave you in step 0)
|
|
119
|
+
Write a sift.toml (generic profile + host allow-list = the host of TARGET_SITE), then build a
|
|
120
|
+
capped, publishable smoke-test index:
|
|
121
|
+
```bash
|
|
122
|
+
cat > sift.toml <<'TOML'
|
|
123
|
+
[site]
|
|
124
|
+
profile = "sift.sites.generic:GenericProfile"
|
|
125
|
+
[seed]
|
|
126
|
+
host_allow = ["HOST_OF_TARGET_SITE"] # e.g. docs.example.com — derive from TARGET_SITE
|
|
127
|
+
TOML
|
|
128
|
+
sift init --root ./sift-index
|
|
129
|
+
sift seed --root ./sift-index --config sift.toml --from-domain TARGET_SITE
|
|
130
|
+
sift run --root ./sift-index --config sift.toml --limit 25 --coverage-base planned
|
|
131
|
+
sift verify --root ./sift-index --skip-signature
|
|
132
|
+
```
|
|
133
|
+
Drop `--limit 25 --coverage-base planned` for a full crawl once extraction looks good.
|
|
134
|
+
Hardened / bot-blocked host (Cloudflare/Akamai/Imperva) → add `--impersonate-fallback`
|
|
135
|
+
(free, TLS-fingerprint impersonation; `pip install 'sift-engine[impersonate]'`).
|
|
136
|
+
JS-rendered SPA → `pip install 'sift-engine[browser]' && python -m playwright install chromium`,
|
|
137
|
+
then add `[browser]\nenabled = true` (it joins the ladder as a free render tier).
|
|
138
|
+
Still blocked (JS-challenge edges) → `--firecrawl-fallback` (paid; needs FIRECRAWL_API_KEY).
|
|
139
|
+
These compose into one escalation ladder: native → impersonate → browser → Firecrawl.
|
|
140
|
+
|
|
141
|
+
4 — WIRE THE READ-ONLY MCP SERVER
|
|
142
|
+
Use the ABSOLUTE path to ./sift-index. Add this to the project's .mcp.json (Claude Code / Cursor / Codex):
|
|
143
|
+
```json
|
|
144
|
+
{ "mcpServers": { "sift": { "command": "sift-mcp", "args": ["--root", "ABSOLUTE/PATH/TO/sift-index"] } } }
|
|
145
|
+
```
|
|
146
|
+
Claude Code shortcut: `claude mcp add sift -- sift-mcp --root "$(pwd)/sift-index"`
|
|
147
|
+
Then restart the MCP client so the `sift` tools load.
|
|
148
|
+
|
|
149
|
+
5 — SHOW ME THE QUERY LOOP
|
|
150
|
+
Call the `snapshot_status` tool to confirm the index is published, then explain the loop:
|
|
151
|
+
snapshot_status first → grep_corpus to locate → read_md / read_facts to drill in →
|
|
152
|
+
cite source_url + content_hash + fetched_at. Mention that re-running `sift run` refreshes the
|
|
153
|
+
index, and that the /sift skill covers building, operating, and querying in depth.
|
|
154
|
+
|
|
155
|
+
(TARGET_SITE = the site I name when you ask in step 0, e.g. https://docs.example.com)
|
|
156
|
+
````
|
|
157
|
+
|
|
158
|
+
</details>
|
|
159
|
+
|
|
160
|
+
### 🧑 Or do it yourself
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
pip install sift-engine
|
|
164
|
+
|
|
165
|
+
# the ATO sitemap below uses a bundled profile, so this runs with zero config
|
|
166
|
+
sift init --root ./index
|
|
167
|
+
sift seed --root ./index --from-sitemap https://www.ato.gov.au/sitemap.xml
|
|
168
|
+
sift run --root ./index --limit 25 --coverage-base planned # smoke-test first
|
|
169
|
+
sift verify --root ./index --skip-signature
|
|
170
|
+
sift-mcp --root ./index # serve the index to your agent
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Then point your agent at it (use an absolute path):
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"mcpServers": {
|
|
178
|
+
"sift": { "command": "sift-mcp", "args": ["--root", "/abs/path/to/index"] }
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Indexing your own site? Add a `sift.toml` (generic profile + host allow-list) — see [Configuration](.claude/skills/sift/reference/config.md).
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## How it works
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
seed → plan → fetch → extract → commit → publish
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Five idempotent phases. `publish` runs 5 verification gates, then atomically swaps the `current/` snapshot and writes a Merkle root over every page hash. Deterministic: **same input → same `content_hash` → same Merkle root** — so any reader re-verifies a page in O(1), or the whole snapshot end-to-end with `sift verify`.
|
|
194
|
+
|
|
195
|
+
Your agent reads the published snapshot **read-only** over MCP: `snapshot_status` → `grep_corpus` → `read_md` / `read_facts` → cite source + hash + date.
|
|
196
|
+
|
|
197
|
+
## Docs
|
|
198
|
+
|
|
199
|
+
- **[CLI reference](.claude/skills/sift/reference/cli.md)** — every command and flag
|
|
200
|
+
- **[Configuration & site profiles](.claude/skills/sift/reference/config.md)** — `sift.toml` and the `SiteProfile` contract
|
|
201
|
+
- **[MCP tools](.claude/skills/sift/reference/mcp-tools.md)** — parameters, output caps, multi-index mode
|
|
202
|
+
- **[Corpus format & integrity contract](./corpus.contract.md)** — on-disk layout and what each read tool returns
|
|
203
|
+
- **[Contributing](./CONTRIBUTING.md)** · **[Security](./SECURITY.md)**
|
|
204
|
+
|
|
205
|
+
> **Open core, Apache-2.0.** This repo is the full open-source engine (pipeline + MCP server) and runs standalone. A hosted platform built on it is in development.
|
|
206
|
+
|
|
207
|
+
**Status — v0.2.0**; tests green on Python 3.11–3.13. Adds the tiered fetch transport (native → curl_cffi → browser → Firecrawl) for hardened and JS-rendered sites. Known limits: no run-dir GC yet, stdout-only logging, stdio-only MCP transport. Issues & roadmap → [GitHub Issues](https://github.com/dvlshah/sift/issues).
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
[Apache-2.0](./LICENSE) — © 2026 Deval Shah.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="assets/sift.gif" alt="sift" width="480">
|
|
4
|
+
|
|
5
|
+
**Give your coding agent a complete, always-fresh copy of any website — with proof of every answer.**
|
|
6
|
+
|
|
7
|
+
[](https://github.com/dvlshah/sift/actions/workflows/tests.yml)
|
|
8
|
+
[](https://pypi.org/project/sift-engine/)
|
|
9
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
10
|
+
[](https://www.python.org/downloads/)
|
|
11
|
+
[](https://modelcontextprotocol.io/)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
sift crawls a site into local markdown + structured facts your agent greps, reads, and cites — files on disk, not vectors, read over MCP. Every page is **content-hashed and dated**, so answers trace back to the exact source and snapshot. Re-run anytime; only changed pages are refetched. Self-hosted.
|
|
16
|
+
|
|
17
|
+
## ❌ Without sift
|
|
18
|
+
|
|
19
|
+
Your agent's built-in web-fetch gives you:
|
|
20
|
+
|
|
21
|
+
- **Stale** answers — the provider's crawler cached that page weeks ago
|
|
22
|
+
- **Partial** answers — just the one page it happened to land on
|
|
23
|
+
- **Unprovable** answers — no record of what the source said, or when
|
|
24
|
+
|
|
25
|
+
## ✅ With sift
|
|
26
|
+
|
|
27
|
+
- **Always fresh** — you control the crawl; conditional GETs refetch only what changed
|
|
28
|
+
- **Provable** — content-hash + date on every page; cite the source, hash, and snapshot
|
|
29
|
+
- **Complete & grep-native** — the whole site on disk, not a handful of retrieved snippets
|
|
30
|
+
- **Self-hosted** — any `http(s)` site you can reach, public or internal; your data stays yours
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Get started
|
|
35
|
+
|
|
36
|
+
Requires Python 3.11+.
|
|
37
|
+
|
|
38
|
+
### 🤖 Let your coding agent set it up — recommended
|
|
39
|
+
|
|
40
|
+
Paste one prompt into **Claude Code, Cursor, Codex, or any MCP-aware agent**. It explains sift, asks which site to index, then installs the engine, builds an index, wires up MCP, and shows you how to query it — end to end.
|
|
41
|
+
|
|
42
|
+
<details>
|
|
43
|
+
<summary><b>📋 Copy the one-paste setup prompt</b></summary>
|
|
44
|
+
|
|
45
|
+
````text
|
|
46
|
+
Set up **sift** in this project — a deterministic, content-hashed website indexer that
|
|
47
|
+
serves a verifiable markdown corpus to AI agents over MCP (https://github.com/dvlshah/sift).
|
|
48
|
+
Do the steps in order. Needs Python 3.11+.
|
|
49
|
+
|
|
50
|
+
0 — EXPLAIN, THEN ASK ME (do this BEFORE installing anything)
|
|
51
|
+
First, explain sift to me in 4–6 plain-English lines — assume I've never heard of it:
|
|
52
|
+
• WHAT it is: it crawls a whole website/docs site into local markdown files (plus structured
|
|
53
|
+
facts) that you, the agent, can grep / read / cite — files on disk, not a vector database.
|
|
54
|
+
• CORE FEATURES: (1) complete — the full site, not the few pages a live web-fetch happens to
|
|
55
|
+
land on; (2) verifiable — every page is content-hashed + dated, so any answer can be proved
|
|
56
|
+
back to the exact source and snapshot; (3) always-current — re-running the crawl refetches
|
|
57
|
+
only what changed; (4) read over MCP — you query it with provenance, read-only by default.
|
|
58
|
+
• WHY IT'S NEEDED: a one-off scrape gives you 3 pages with no proof of what they said or when.
|
|
59
|
+
sift keeps an agent correct about an evolving body of docs AND able to prove what it cited.
|
|
60
|
+
(If someone only needs one page once, sift is the wrong tool — a plain fetch is fine.)
|
|
61
|
+
Then ASK ME: "Which website or docs site do you want to index locally?" — wait for my answer
|
|
62
|
+
and use it as TARGET_SITE everywhere below. Do not run any commands until I answer.
|
|
63
|
+
|
|
64
|
+
1 — INSTALL THE ENGINE
|
|
65
|
+
```bash
|
|
66
|
+
pip install sift-engine # adds the `sift`, `sift-mcp`, `sift-evals` commands
|
|
67
|
+
sift --version && which sift-mcp # confirm it's on PATH
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
2 — INSTALL THE SIFT SKILL INTO THIS REPO
|
|
71
|
+
Download the skill so you (the agent) know how to build, operate, and query an index.
|
|
72
|
+
Read .claude/skills/sift/SKILL.md after downloading — it is the source of truth for the rest.
|
|
73
|
+
```bash
|
|
74
|
+
SKILL=https://raw.githubusercontent.com/dvlshah/sift/v0.2.0/.claude/skills/sift
|
|
75
|
+
mkdir -p .claude/skills/sift/reference
|
|
76
|
+
curl -fsSL $SKILL/SKILL.md -o .claude/skills/sift/SKILL.md
|
|
77
|
+
for f in cli config mcp-tools; do curl -fsSL $SKILL/reference/$f.md -o .claude/skills/sift/reference/$f.md; done
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
3 — BUILD A SMALL STARTER INDEX (using the TARGET_SITE I gave you in step 0)
|
|
81
|
+
Write a sift.toml (generic profile + host allow-list = the host of TARGET_SITE), then build a
|
|
82
|
+
capped, publishable smoke-test index:
|
|
83
|
+
```bash
|
|
84
|
+
cat > sift.toml <<'TOML'
|
|
85
|
+
[site]
|
|
86
|
+
profile = "sift.sites.generic:GenericProfile"
|
|
87
|
+
[seed]
|
|
88
|
+
host_allow = ["HOST_OF_TARGET_SITE"] # e.g. docs.example.com — derive from TARGET_SITE
|
|
89
|
+
TOML
|
|
90
|
+
sift init --root ./sift-index
|
|
91
|
+
sift seed --root ./sift-index --config sift.toml --from-domain TARGET_SITE
|
|
92
|
+
sift run --root ./sift-index --config sift.toml --limit 25 --coverage-base planned
|
|
93
|
+
sift verify --root ./sift-index --skip-signature
|
|
94
|
+
```
|
|
95
|
+
Drop `--limit 25 --coverage-base planned` for a full crawl once extraction looks good.
|
|
96
|
+
Hardened / bot-blocked host (Cloudflare/Akamai/Imperva) → add `--impersonate-fallback`
|
|
97
|
+
(free, TLS-fingerprint impersonation; `pip install 'sift-engine[impersonate]'`).
|
|
98
|
+
JS-rendered SPA → `pip install 'sift-engine[browser]' && python -m playwright install chromium`,
|
|
99
|
+
then add `[browser]\nenabled = true` (it joins the ladder as a free render tier).
|
|
100
|
+
Still blocked (JS-challenge edges) → `--firecrawl-fallback` (paid; needs FIRECRAWL_API_KEY).
|
|
101
|
+
These compose into one escalation ladder: native → impersonate → browser → Firecrawl.
|
|
102
|
+
|
|
103
|
+
4 — WIRE THE READ-ONLY MCP SERVER
|
|
104
|
+
Use the ABSOLUTE path to ./sift-index. Add this to the project's .mcp.json (Claude Code / Cursor / Codex):
|
|
105
|
+
```json
|
|
106
|
+
{ "mcpServers": { "sift": { "command": "sift-mcp", "args": ["--root", "ABSOLUTE/PATH/TO/sift-index"] } } }
|
|
107
|
+
```
|
|
108
|
+
Claude Code shortcut: `claude mcp add sift -- sift-mcp --root "$(pwd)/sift-index"`
|
|
109
|
+
Then restart the MCP client so the `sift` tools load.
|
|
110
|
+
|
|
111
|
+
5 — SHOW ME THE QUERY LOOP
|
|
112
|
+
Call the `snapshot_status` tool to confirm the index is published, then explain the loop:
|
|
113
|
+
snapshot_status first → grep_corpus to locate → read_md / read_facts to drill in →
|
|
114
|
+
cite source_url + content_hash + fetched_at. Mention that re-running `sift run` refreshes the
|
|
115
|
+
index, and that the /sift skill covers building, operating, and querying in depth.
|
|
116
|
+
|
|
117
|
+
(TARGET_SITE = the site I name when you ask in step 0, e.g. https://docs.example.com)
|
|
118
|
+
````
|
|
119
|
+
|
|
120
|
+
</details>
|
|
121
|
+
|
|
122
|
+
### 🧑 Or do it yourself
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
pip install sift-engine
|
|
126
|
+
|
|
127
|
+
# the ATO sitemap below uses a bundled profile, so this runs with zero config
|
|
128
|
+
sift init --root ./index
|
|
129
|
+
sift seed --root ./index --from-sitemap https://www.ato.gov.au/sitemap.xml
|
|
130
|
+
sift run --root ./index --limit 25 --coverage-base planned # smoke-test first
|
|
131
|
+
sift verify --root ./index --skip-signature
|
|
132
|
+
sift-mcp --root ./index # serve the index to your agent
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Then point your agent at it (use an absolute path):
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"mcpServers": {
|
|
140
|
+
"sift": { "command": "sift-mcp", "args": ["--root", "/abs/path/to/index"] }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Indexing your own site? Add a `sift.toml` (generic profile + host allow-list) — see [Configuration](.claude/skills/sift/reference/config.md).
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## How it works
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
seed → plan → fetch → extract → commit → publish
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Five idempotent phases. `publish` runs 5 verification gates, then atomically swaps the `current/` snapshot and writes a Merkle root over every page hash. Deterministic: **same input → same `content_hash` → same Merkle root** — so any reader re-verifies a page in O(1), or the whole snapshot end-to-end with `sift verify`.
|
|
156
|
+
|
|
157
|
+
Your agent reads the published snapshot **read-only** over MCP: `snapshot_status` → `grep_corpus` → `read_md` / `read_facts` → cite source + hash + date.
|
|
158
|
+
|
|
159
|
+
## Docs
|
|
160
|
+
|
|
161
|
+
- **[CLI reference](.claude/skills/sift/reference/cli.md)** — every command and flag
|
|
162
|
+
- **[Configuration & site profiles](.claude/skills/sift/reference/config.md)** — `sift.toml` and the `SiteProfile` contract
|
|
163
|
+
- **[MCP tools](.claude/skills/sift/reference/mcp-tools.md)** — parameters, output caps, multi-index mode
|
|
164
|
+
- **[Corpus format & integrity contract](./corpus.contract.md)** — on-disk layout and what each read tool returns
|
|
165
|
+
- **[Contributing](./CONTRIBUTING.md)** · **[Security](./SECURITY.md)**
|
|
166
|
+
|
|
167
|
+
> **Open core, Apache-2.0.** This repo is the full open-source engine (pipeline + MCP server) and runs standalone. A hosted platform built on it is in development.
|
|
168
|
+
|
|
169
|
+
**Status — v0.2.0**; tests green on Python 3.11–3.13. Adds the tiered fetch transport (native → curl_cffi → browser → Firecrawl) for hardened and JS-rendered sites. Known limits: no run-dir GC yet, stdout-only logging, stdio-only MCP transport. Issues & roadmap → [GitHub Issues](https://github.com/dvlshah/sift/issues).
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
[Apache-2.0](./LICENSE) — © 2026 Deval Shah.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sift-engine"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Sift — deterministic website indexing for grep-first LLM agents"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -39,12 +39,18 @@ evals = [
|
|
|
39
39
|
"jsonschema>=4",
|
|
40
40
|
]
|
|
41
41
|
# Optional browser-rendering stack for SPA fetch (see docs/design/browser-fetch.md).
|
|
42
|
-
# Install with: pip install 'sift[browser]' && python -m playwright install chromium
|
|
43
|
-
# History:
|
|
42
|
+
# Install with: pip install 'sift-engine[browser]' && python -m playwright install chromium
|
|
43
|
+
# History: an early prototype bundled crawl4ai; swapped to bare Playwright after
|
|
44
44
|
# reproducible hangs on analytics-heavy SPAs. See archive/crawl4ai/README.md.
|
|
45
45
|
browser = [
|
|
46
46
|
"playwright>=1.40",
|
|
47
47
|
]
|
|
48
|
+
# Tier-2 escalation transport: TLS/HTTP2 fingerprint impersonation. Defeats most
|
|
49
|
+
# Cloudflare/Akamai/Imperva fingerprint blocks with no browser and no per-request
|
|
50
|
+
# cost. Install with: pip install 'sift-engine[impersonate]'
|
|
51
|
+
impersonate = [
|
|
52
|
+
"curl_cffi>=0.7",
|
|
53
|
+
]
|
|
48
54
|
|
|
49
55
|
[project.urls]
|
|
50
56
|
Homepage = "https://github.com/dvlshah/sift"
|
|
@@ -71,7 +71,7 @@ class BrowserFetchError(Exception):
|
|
|
71
71
|
def _install_hint() -> str:
|
|
72
72
|
return (
|
|
73
73
|
"playwright is not installed. Install with:\n"
|
|
74
|
-
" pip install 'sift[browser]' && python -m playwright install chromium"
|
|
74
|
+
" pip install 'sift-engine[browser]' && python -m playwright install chromium"
|
|
75
75
|
)
|
|
76
76
|
|
|
77
77
|
|