docugraph 0.1.1__tar.gz → 0.2.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.
- docugraph-0.2.1/.github/workflows/bench.yml +79 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/.github/workflows/ci.yml +8 -3
- docugraph-0.2.1/CHANGELOG.md +117 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/PKG-INFO +79 -1
- {docugraph-0.1.1 → docugraph-0.2.1}/README.md +78 -0
- docugraph-0.2.1/WP-docugraph-0.2.1-security.md +230 -0
- docugraph-0.2.1/benchmarks/retrieval/MANIFEST.json +13 -0
- docugraph-0.2.1/benchmarks/retrieval/baselines/bge-small-en-v1.5.json +6520 -0
- docugraph-0.2.1/benchmarks/retrieval/corpus.jsonl +70 -0
- docugraph-0.2.1/benchmarks/retrieval/queries.jsonl +56 -0
- docugraph-0.2.1/docs/SECURITY.md +83 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/docs/configuration.md +43 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/pyproject.toml +1 -1
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/core/config.py +6 -0
- docugraph-0.2.1/src/docugraph/evals/__init__.py +12 -0
- docugraph-0.2.1/src/docugraph/evals/arms.py +111 -0
- docugraph-0.2.1/src/docugraph/evals/compare.py +106 -0
- docugraph-0.2.1/src/docugraph/evals/dataset.py +210 -0
- docugraph-0.2.1/src/docugraph/evals/metrics.py +123 -0
- docugraph-0.2.1/src/docugraph/evals/runner.py +169 -0
- docugraph-0.2.1/src/docugraph/ingestion/crawler.py +557 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/ingestion/git_indexer.py +184 -18
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/interfaces/cli.py +185 -1
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/interfaces/mcp_server.py +141 -18
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/retrieval/hybrid_search.py +7 -3
- docugraph-0.2.1/src/docugraph/security/__init__.py +21 -0
- docugraph-0.2.1/src/docugraph/security/url_policy.py +280 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/storage/graph_store.py +14 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/storage/vector_store.py +122 -21
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/e2e/test_mcp_server.py +102 -0
- docugraph-0.2.1/tests/integration/test_bench_runner.py +126 -0
- docugraph-0.2.1/tests/integration/test_crawl_antibot.py +156 -0
- docugraph-0.2.1/tests/integration/test_crawl_policy.py +427 -0
- docugraph-0.2.1/tests/integration/test_crawl_redirect.py +185 -0
- docugraph-0.2.1/tests/unit/test_bench_dataset.py +133 -0
- docugraph-0.2.1/tests/unit/test_bench_metrics.py +95 -0
- docugraph-0.2.1/tests/unit/test_cli_git.py +44 -0
- docugraph-0.2.1/tests/unit/test_crawl_guard.py +703 -0
- docugraph-0.2.1/tests/unit/test_git_indexer_args.py +400 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_hybrid_graph_status.py +7 -0
- docugraph-0.2.1/tests/unit/test_telemetry_default.py +133 -0
- docugraph-0.2.1/tests/unit/test_url_policy.py +418 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/uv.lock +3 -3
- docugraph-0.1.1/docs/SECURITY.md +0 -45
- docugraph-0.1.1/src/docugraph/ingestion/crawler.py +0 -305
- {docugraph-0.1.1 → docugraph-0.2.1}/.dockerignore +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/.github/dependabot.yml +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/.github/workflows/docker-publish.yml +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/.github/workflows/live-provider-smoke.yml +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/.github/workflows/live-reranker.yml +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/.github/workflows/release.yml +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/.gitignore +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/.pre-commit-config.yaml +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/.python-version +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/LICENSE +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/NOTICE +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/README.md +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/hooks/docugraph-auto-index.sh +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/hooks/docugraph-auto-search.sh +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/hooks/docugraph-post-compact.sh +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/hooks/docugraph-pre-compact.sh +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/hooks/docugraph-rule-check.sh +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/hooks/docugraph-session-end.sh +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/hooks/docugraph-session-start.sh +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/install.sh +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/settings.json +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/skills/architecture/SKILL.md +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/skills/context/SKILL.md +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/skills/docs/SKILL.md +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/skills/index/SKILL.md +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/skills/recall/SKILL.md +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/skills/remember/SKILL.md +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/docker/Dockerfile +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/docker/docker-compose.yml +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/docker-compose.yml +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/docs/quickstart.md +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/examples/crawl_and_search.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/examples/index_local_repo.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/examples/mcp_client_example.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/scripts/live_provider_smoke.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/__init__.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/_version.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/agents/__init__.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/agents/coding_agent.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/agents/tools/__init__.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/agents/tools/graph_query.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/agents/tools/memory_ops.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/agents/tools/search_code.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/agents/tools/search_docs.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/core/__init__.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/core/embeddings.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/core/llm.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/core/models.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/ingestion/__init__.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/ingestion/api_docs.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/ingestion/chunker.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/ingestion/local_files.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/interfaces/__init__.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/retrieval/__init__.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/retrieval/context_builder.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/retrieval/reranker.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/storage/__init__.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/storage/memory_store.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/e2e/__init__.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/e2e/test_cli.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/e2e/test_hooks.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/e2e/tools_snapshot.json +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/integration/__init__.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/integration/test_graph_store_init.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/integration/test_memory_store.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/__init__.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_chunker.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_cloud_provider_sdks.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_docs_consistency.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_embeddings.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_event_loop_acquisition.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_graph_embedder_adapter.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_graphiti_llm_client.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_llm_preflight.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_models.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_no_model_ids_in_source.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_reranker.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_vector_store.py +0 -0
- {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_version.py +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# The retrieval benchmark, run against the real embedder, weekly and on demand.
|
|
2
|
+
#
|
|
3
|
+
# Fast CI proves the harness with a deterministic fake embedder
|
|
4
|
+
# (tests/integration/test_bench_runner.py) and never downloads weights. What
|
|
5
|
+
# that leaves unexercised is the thing the benchmark exists for: the numbers
|
|
6
|
+
# on the shipped model. This job runs `docugraph bench run` on the real
|
|
7
|
+
# bge-small-en-v1.5 (~130 MB, cached between runs) and holds the result
|
|
8
|
+
# against the committed baseline with `docugraph bench compare`, which exits 1
|
|
9
|
+
# when nDCG@10 -- overall or in any single failure-mode category -- drops by
|
|
10
|
+
# more than the tolerance.
|
|
11
|
+
#
|
|
12
|
+
# The baseline under benchmarks/retrieval/baselines/ is committed, per
|
|
13
|
+
# embedding model, and regenerated deliberately: when the dataset or the
|
|
14
|
+
# retrieval stack changes on purpose, a human reruns the benchmark, reads the
|
|
15
|
+
# delta table, and commits the new baseline with the change that moved it.
|
|
16
|
+
# Until a baseline exists for the model, this job fails -- a comparison
|
|
17
|
+
# against nothing proving nothing would be green that means "checked nothing"
|
|
18
|
+
# (the live-reranker header has the longer form of this argument).
|
|
19
|
+
#
|
|
20
|
+
# Reranking arms are not measured here: the cross-encoder is ~2.3 GB and has
|
|
21
|
+
# its own opt-in workflow; run `docugraph bench run --rerank` locally when a
|
|
22
|
+
# reranker change needs numbers.
|
|
23
|
+
name: Retrieval benchmark
|
|
24
|
+
|
|
25
|
+
on:
|
|
26
|
+
# Manual: Actions -> Retrieval benchmark -> Run workflow.
|
|
27
|
+
workflow_dispatch:
|
|
28
|
+
schedule:
|
|
29
|
+
# Weekly, Monday 06:17 UTC. An off-hour minute, deliberately: scheduled
|
|
30
|
+
# workflows on :00 queue behind everyone else's.
|
|
31
|
+
- cron: "17 6 * * 1"
|
|
32
|
+
|
|
33
|
+
env:
|
|
34
|
+
FASTEMBED_CACHE_PATH: /home/runner/.cache/fastembed
|
|
35
|
+
|
|
36
|
+
jobs:
|
|
37
|
+
bench:
|
|
38
|
+
name: bench run · compare to baseline
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
steps:
|
|
41
|
+
- name: Checkout repository
|
|
42
|
+
uses: actions/checkout@v4
|
|
43
|
+
|
|
44
|
+
- name: Set up Python
|
|
45
|
+
uses: actions/setup-python@v5
|
|
46
|
+
with:
|
|
47
|
+
python-version: '3.12'
|
|
48
|
+
|
|
49
|
+
- name: Install uv
|
|
50
|
+
uses: astral-sh/setup-uv@v4
|
|
51
|
+
|
|
52
|
+
- name: Sync dependencies
|
|
53
|
+
run: uv sync --all-extras
|
|
54
|
+
|
|
55
|
+
- name: Cache embedding model
|
|
56
|
+
uses: actions/cache@v4
|
|
57
|
+
with:
|
|
58
|
+
path: /home/runner/.cache/fastembed
|
|
59
|
+
key: fastembed-bge-small-en-v1.5
|
|
60
|
+
|
|
61
|
+
- name: Run benchmark
|
|
62
|
+
run: uv run docugraph bench run --json bench-report.json
|
|
63
|
+
|
|
64
|
+
- name: Compare against committed baseline
|
|
65
|
+
run: |
|
|
66
|
+
baseline="benchmarks/retrieval/baselines/bge-small-en-v1.5.json"
|
|
67
|
+
if [ ! -f "$baseline" ]; then
|
|
68
|
+
echo "::error::No committed baseline at $baseline. Run 'docugraph bench run --json' locally and commit the report as the baseline; a compare against nothing is not a gate."
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
uv run docugraph bench compare "$baseline" bench-report.json
|
|
72
|
+
|
|
73
|
+
- name: Upload report
|
|
74
|
+
if: always()
|
|
75
|
+
uses: actions/upload-artifact@v4
|
|
76
|
+
with:
|
|
77
|
+
name: bench-report
|
|
78
|
+
path: bench-report.json
|
|
79
|
+
if-no-files-found: ignore
|
|
@@ -163,9 +163,14 @@ jobs:
|
|
|
163
163
|
- name: Assert the handshake
|
|
164
164
|
run: |
|
|
165
165
|
python3 - <<'PY'
|
|
166
|
-
import json, pathlib, sys
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
import json, pathlib, sys, tomllib
|
|
167
|
+
|
|
168
|
+
# Read the expected version from pyproject.toml rather than pinning a
|
|
169
|
+
# literal. pyproject is the single source of the version -- the
|
|
170
|
+
# package, the CLI and this handshake all derive from it -- so a
|
|
171
|
+
# hardcoded constant only goes stale at the next release, which is
|
|
172
|
+
# exactly how it broke: it stayed "0.1.1" into the 0.2.0 release.
|
|
173
|
+
EXPECTED = tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"]
|
|
169
174
|
raw = pathlib.Path("/tmp/handshake.jsonl").read_text()
|
|
170
175
|
|
|
171
176
|
# stdout carries the JSON-RPC stream and nothing else. A stray line
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable changes to docugraph. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
|
|
4
|
+
versions follow [SemVer](https://semver.org/spec/v2.0.0.html).
|
|
5
|
+
|
|
6
|
+
docugraph is pre-1.0: a minor release may change a tool's behaviour or a setting's meaning, and a
|
|
7
|
+
patch release will not. Breaking changes are listed as breaking either way, because a reader of this
|
|
8
|
+
file is entitled to know which of their own setup stops working. The supported interfaces are the MCP
|
|
9
|
+
tools, the CLI commands and the configuration settings; the Python modules behind them are not a
|
|
10
|
+
public API.
|
|
11
|
+
|
|
12
|
+
## [Unreleased]
|
|
13
|
+
|
|
14
|
+
## [0.2.1] - 2026-11-07
|
|
15
|
+
|
|
16
|
+
### Security
|
|
17
|
+
|
|
18
|
+
- **`crawl_url` and `index_git` now judge model-supplied URLs before acting.** Pages must be
|
|
19
|
+
`http`/`https`; git remotes must be `https`, `ssh`, or `user@host:path`. A value beginning with `-`
|
|
20
|
+
is refused as `option_like` rather than passed to git. A host is refused unless it resolves to a
|
|
21
|
+
global address (`private_address` covers loopback, link-local, `0.0.0.0` and the metadata
|
|
22
|
+
address), unless `crawler.allow_private_networks` is on. `crawler.allowed_domains` narrows what may
|
|
23
|
+
be reached. A refusal is a tool error whose text is `Refused: <reason> — <detail>`.
|
|
24
|
+
- **Redirects are enforced hop by hop.** Chromium follows a redirect without re-entering a Playwright
|
|
25
|
+
route handler, so each `Location` is resolved and judged before the next request is made. A page
|
|
26
|
+
whose final URL is refused is dropped and reported (`Dropped N page(s) by policy:`) and never
|
|
27
|
+
indexed.
|
|
28
|
+
- **Service workers cannot bypass the route handler.** The page-context hook removes
|
|
29
|
+
`navigator.serviceWorker` in every frame before page scripts run. Websockets are closed. Routing is
|
|
30
|
+
registered on the browser context, so popups, frames and workers are judged by the same handler.
|
|
31
|
+
- **crawl4ai's anti-bot replay cannot escape the guards.** Its retry navigates through the same hooked
|
|
32
|
+
context and is routed; its browser-less `fallback_fetch_function` would run outside every guard, so
|
|
33
|
+
the retry and the fallback are pinned off in both crawl paths.
|
|
34
|
+
- **An existing clone is updated only from the origin that was judged.** When the clone destination
|
|
35
|
+
already exists, its `origin` must equal the requested URL and pass the same policy; a mismatch is
|
|
36
|
+
refused as `path_escape`. Pull and the fetch fallback carry the same protocol and redirect lockdown
|
|
37
|
+
as the clone.
|
|
38
|
+
- **`index_git` cannot write outside the repository directory.** The name — or the one derived from
|
|
39
|
+
the URL — must be a single path component, and the resolved destination must stay inside the
|
|
40
|
+
configured repos directory. An escaping name is refused as `path_escape`, not renamed.
|
|
41
|
+
- **Git runs hardened under the policy.** `--` precedes the URL and path, refs are validated against
|
|
42
|
+
`git-check-ref-format`, and clones carry `protocol.file.allow=never`, `protocol.ext.allow=never`
|
|
43
|
+
and `http.followRedirects=false`.
|
|
44
|
+
- **A site crawl is capped at 50 pages per MCP tool call**, and `max_pages` below 1 or not an integer
|
|
45
|
+
is an error rather than a crawl.
|
|
46
|
+
- **Graphiti telemetry is off by default.** `GRAPHITI_TELEMETRY_ENABLED` is set to `false` when
|
|
47
|
+
absent; an explicit value is respected.
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- `docugraph index git` now exits non-zero with `Error: Refused: …` when a repository name or ref is
|
|
52
|
+
refused. Previously a refused path reached the generic `RuntimeError` branch, printed a note, and
|
|
53
|
+
exited 0.
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
|
|
57
|
+
- `CrawlerConfig` gains `allow_private_networks` (default `false`) and `allowed_domains` (default
|
|
58
|
+
empty).
|
|
59
|
+
- `crawl_site` now counts pages it **attempts**, not pages that produced a document, so a page that
|
|
60
|
+
fails to scrape still consumes the budget. This changes the CLI too: `docugraph crawl --max-pages`
|
|
61
|
+
now limits attempted pages.
|
|
62
|
+
- Under a URL policy the crawl cache is not read, so a page cached by an earlier unpoliced crawl is
|
|
63
|
+
never served without its final URL being judged. Cached documents are still written.
|
|
64
|
+
|
|
65
|
+
## [0.2.0] - 2026-10-23
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
|
|
69
|
+
- A retrieval-quality benchmark with a committed corpus and a regression gate. It scores
|
|
70
|
+
vector-only, BM25-only, LanceDB-native hybrid, RRF and weighted arms on the same queries and fails
|
|
71
|
+
when nDCG@10 drops past the tolerance.
|
|
72
|
+
|
|
73
|
+
### Fixed
|
|
74
|
+
|
|
75
|
+
- The full-text index was never built, so keyword search silently returned nothing; it is now built
|
|
76
|
+
and BM25 is real. The hybrid path records when it falls back to the vector leg.
|
|
77
|
+
|
|
78
|
+
## [0.1.1] - 2026-10-08
|
|
79
|
+
|
|
80
|
+
Maintenance release — no changes to the package's code or behaviour.
|
|
81
|
+
|
|
82
|
+
### Fixed
|
|
83
|
+
|
|
84
|
+
- The release workflow publishes the container image,
|
|
85
|
+
`ghcr.io/cj-coding-apps/docugraph:v0.1.1` (and `:latest`). 0.1.0 has no image: the workflow built
|
|
86
|
+
the tag from the repository name with its original capitalisation, which GHCR rejects.
|
|
87
|
+
|
|
88
|
+
### Added
|
|
89
|
+
|
|
90
|
+
- `pyproject.toml` declares project URLs, so the PyPI page links to the repository, issues and
|
|
91
|
+
releases.
|
|
92
|
+
|
|
93
|
+
## [0.1.0] - 2026-10-08
|
|
94
|
+
|
|
95
|
+
First public release, on PyPI.
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
|
|
99
|
+
- Documentation RAG for coding agents: crawl or index documentation, build a documentation knowledge
|
|
100
|
+
graph and a repository/branch-scoped agent memory, and serve all of it to MCP clients over stdio.
|
|
101
|
+
The surface is nine tools (`crawl_url`, `index_git`, `search_docs`, `hybrid_search`, `graph_add`,
|
|
102
|
+
`graph_query`, `memory_store`, `memory_recall`, `get_stats`) and a CLI for out-of-band index
|
|
103
|
+
bootstrapping.
|
|
104
|
+
- Embeddings run in-process on ONNX: `embeddings.provider: auto` resolves to fastembed and never
|
|
105
|
+
consults the environment, so documents are not sent anywhere to be embedded unless a cloud provider
|
|
106
|
+
is named.
|
|
107
|
+
- Optional extras: `cloud` (OpenAI, Anthropic and Cohere SDKs), `ollama` (local LLM) and `dev`
|
|
108
|
+
(test and lint tooling).
|
|
109
|
+
|
|
110
|
+
### Notes
|
|
111
|
+
|
|
112
|
+
- Python 3.11–3.13 on Linux. `kuzu` is pinned `==0.11.3`, the final release upstream, and is treated
|
|
113
|
+
as a migration boundary rather than a floor.
|
|
114
|
+
- URL crawling needs Playwright's Chromium (`playwright install chromium`).
|
|
115
|
+
|
|
116
|
+
[Unreleased]: https://github.com/CJ-coding-apps/docugraph/compare/v0.2.1...HEAD
|
|
117
|
+
[0.2.1]: https://github.com/CJ-coding-apps/docugraph/compare/v0.2.0...v0.2.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: docugraph
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Documentation RAG for coding agents: vector search, knowledge graph, agent memory, and an MCP server — torch-free local embeddings via fastembed
|
|
5
5
|
Project-URL: Homepage, https://github.com/CJ-coding-apps/docugraph
|
|
6
6
|
Project-URL: Repository, https://github.com/CJ-coding-apps/docugraph
|
|
@@ -78,6 +78,7 @@ Code and other MCP clients. The CLI is for out-of-band index bootstrapping
|
|
|
78
78
|
- **Agent memory**: persistent, repository/branch-scoped key-value store
|
|
79
79
|
- **Optional reranking**: local multilingual cross-encoder (BAAI/bge-reranker-v2-m3), opt-in
|
|
80
80
|
- **Torch-free**: default embeddings and reranking run on ONNX — no PyTorch in the dependency tree
|
|
81
|
+
- **Measured retrieval**: a committed adversarial benchmark scores every search mode per failure category, with a regression gate (`docugraph bench`)
|
|
81
82
|
|
|
82
83
|
## Quick start
|
|
83
84
|
|
|
@@ -123,6 +124,16 @@ playwright install chromium
|
|
|
123
124
|
This is ~190 MB and is cached, so it is a one-off. `index local`, `index git`,
|
|
124
125
|
`search`, `memory` and `stats` do not need it — only fetching a URL does.
|
|
125
126
|
|
|
127
|
+
**URLs that an agent supplies are treated as untrusted.** `crawl_url` and
|
|
128
|
+
`index_git` refuse, before fetching or cloning, a value that is not `http(s)`
|
|
129
|
+
(or, for git, `https`/`ssh`), that begins with `-`, that is not a public
|
|
130
|
+
address, that leaves an allowlist you set, or that would write outside the
|
|
131
|
+
repository directory. Refusals are one line — `Refused: private_address — …` —
|
|
132
|
+
and appear in the tool result with `isError`. A site crawl is capped at 50 pages
|
|
133
|
+
per call. Set `crawler.allow_private_networks: true` to crawl an intranet, and
|
|
134
|
+
`crawler.allowed_domains` to narrow what may be reached; see
|
|
135
|
+
[docs/configuration.md](docs/configuration.md#url-policy).
|
|
136
|
+
|
|
126
137
|
### MCP integration with Claude Code (primary surface)
|
|
127
138
|
|
|
128
139
|
The MCP server is a console script, so where it lives depends on how you
|
|
@@ -285,8 +296,13 @@ crawler:
|
|
|
285
296
|
max_concurrent: 5
|
|
286
297
|
rate_limit: 2.0
|
|
287
298
|
respect_robots: true
|
|
299
|
+
allow_private_networks: false # opt in to intranet/loopback targets
|
|
300
|
+
allowed_domains: [] # empty = any public host
|
|
288
301
|
```
|
|
289
302
|
|
|
303
|
+
Graphiti telemetry is **off** unless you set `GRAPHITI_TELEMETRY_ENABLED`
|
|
304
|
+
yourself; an explicit value is never overridden.
|
|
305
|
+
|
|
290
306
|
`provider: auto` resolves to **fastembed** (local ONNX) — nothing about your
|
|
291
307
|
environment changes that, so documents are not sent anywhere for embedding
|
|
292
308
|
unless you name a cloud provider. Set `provider: openai` (or `cohere`) to send
|
|
@@ -298,6 +314,27 @@ overridden by environment variable, e.g. `DOCUGRAPH_EMBEDDINGS__MODEL` or
|
|
|
298
314
|
See [docs/configuration.md](https://github.com/CJ-coding-apps/docugraph/blob/main/docs/configuration.md) for the full reference,
|
|
299
315
|
including the embedding-model consistency guard and the graph LLM requirement.
|
|
300
316
|
|
|
317
|
+
## Network activity
|
|
318
|
+
|
|
319
|
+
DocuGraph runs locally, but these connections are possible — each only in the situation named:
|
|
320
|
+
|
|
321
|
+
- **Model downloads.** `fastembed` (the default embedder) downloads its ONNX model from Hugging
|
|
322
|
+
Face on first use, and the reranker (about 2 GB) when you first enable it. Afterwards the model is
|
|
323
|
+
read from the local cache. Nothing about your documents is sent.
|
|
324
|
+
- **Crawl and clone targets.** `crawl_url` fetches the pages you point it at, and `index_git`
|
|
325
|
+
clones the repository URL you give it. The MCP tools treat those values as untrusted: pages must be
|
|
326
|
+
`http`/`https` and resolve to a global address, git remotes must pass the same check, and
|
|
327
|
+
`crawler.allow_private_networks` is the opt-in for an intranet or a local docs server.
|
|
328
|
+
- **A named LLM or embedding provider.** Only if you name one: `embeddings.provider: openai` (or
|
|
329
|
+
`cohere`) sends the text being embedded there, and the graph tools send prompts to the configured
|
|
330
|
+
LLM — OpenAI, Anthropic, or Ollama.
|
|
331
|
+
- **Ollama**, when configured — to your Ollama server, localhost by default.
|
|
332
|
+
- **Graphiti telemetry**, to PostHog, **off unless you opt in** with
|
|
333
|
+
`GRAPHITI_TELEMETRY_ENABLED=true`.
|
|
334
|
+
|
|
335
|
+
CLI crawls and clones are user-directed: they are exempt from the scheme, DNS and address rules,
|
|
336
|
+
but the repository-name containment and the git hardening apply to every caller.
|
|
337
|
+
|
|
301
338
|
## The documentation knowledge graph
|
|
302
339
|
|
|
303
340
|
DocuGraph builds a temporal entity/relationship graph over your docs (Kuzu,
|
|
@@ -308,6 +345,47 @@ required**. However, Graphiti performs entity extraction with an **LLM**, so
|
|
|
308
345
|
OpenAI/Anthropic key). Without one, the graph tools degrade gracefully with a
|
|
309
346
|
clear message; vector and keyword search are unaffected.
|
|
310
347
|
|
|
348
|
+
## Retrieval benchmark
|
|
349
|
+
|
|
350
|
+
Retrieval quality is measured, not asserted. `benchmarks/retrieval/` commits a
|
|
351
|
+
hand-authored documentation corpus for a fictional SDK plus graded relevance
|
|
352
|
+
judgments, built adversarially around seven failure modes: exact-token lookups
|
|
353
|
+
(error codes, parameter names), paraphrase queries with no lexical overlap,
|
|
354
|
+
keyword-stuffed distractor pages, near-sibling semantic distractors, version-
|
|
355
|
+
pinned questions, acronym/synonym gaps, and "X without Y" constraint queries.
|
|
356
|
+
Each query records the trap it sets in a `design_note`, and a manifest pins the
|
|
357
|
+
dataset by hash — editing it is a deliberate act that invalidates baselines.
|
|
358
|
+
The corpus holds 70 documents and 56 queries, eight per failure mode; the
|
|
359
|
+
keyword-trap distractors are built to cover many *distinct* query terms rather
|
|
360
|
+
than repeat one (BM25 saturates a repeated term, so a one-token trap does not
|
|
361
|
+
bind), and the semantic-distractor queries share few exact tokens with their
|
|
362
|
+
target so that only meaning — not lexical overlap — can recover them.
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
# Score every arm (vector-only, BM25-only, LanceDB-native hybrid, RRF, weighted)
|
|
366
|
+
# against a temporary store built from the committed corpus:
|
|
367
|
+
docugraph bench run --json report.json
|
|
368
|
+
|
|
369
|
+
# Hold a report against a committed baseline; exits 1 when nDCG@10 drops by
|
|
370
|
+
# more than the tolerance, overall or in any single category:
|
|
371
|
+
docugraph bench compare benchmarks/retrieval/baselines/bge-small-en-v1.5.json report.json
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Reported metrics are recall@1/5/10, MRR@10, and nDCG@10 (graded, linear gain),
|
|
375
|
+
macro-averaged overall and per category. The per-category table is the point:
|
|
376
|
+
a fusion change that wins on average while losing exact-code lookup shows up
|
|
377
|
+
as the regression it is. Baselines are per embedding model and live in
|
|
378
|
+
`benchmarks/retrieval/baselines/`; the `Retrieval benchmark` workflow re-runs
|
|
379
|
+
the suite weekly against the committed baseline.
|
|
380
|
+
|
|
381
|
+
Honest scope: the corpus is small and purpose-built, so scores are comparable
|
|
382
|
+
across docugraph changes — not with BEIR or any external leaderboard. The
|
|
383
|
+
first thing this benchmark caught predates it: before v0.2.0 no FTS index was
|
|
384
|
+
ever created, so "keyword" search silently returned vector results and
|
|
385
|
+
RRF fused the vector ranking with itself. The benchmark's `fts` arm and the
|
|
386
|
+
hybrid arm's refuse-to-score-on-fallback check exist so that class of silent
|
|
387
|
+
degradation cannot come back unnoticed.
|
|
388
|
+
|
|
311
389
|
### KuzuDB version
|
|
312
390
|
|
|
313
391
|
`kuzu` is pinned to `==0.11.3` in `pyproject.toml` (reached through
|
|
@@ -16,6 +16,7 @@ Code and other MCP clients. The CLI is for out-of-band index bootstrapping
|
|
|
16
16
|
- **Agent memory**: persistent, repository/branch-scoped key-value store
|
|
17
17
|
- **Optional reranking**: local multilingual cross-encoder (BAAI/bge-reranker-v2-m3), opt-in
|
|
18
18
|
- **Torch-free**: default embeddings and reranking run on ONNX — no PyTorch in the dependency tree
|
|
19
|
+
- **Measured retrieval**: a committed adversarial benchmark scores every search mode per failure category, with a regression gate (`docugraph bench`)
|
|
19
20
|
|
|
20
21
|
## Quick start
|
|
21
22
|
|
|
@@ -61,6 +62,16 @@ playwright install chromium
|
|
|
61
62
|
This is ~190 MB and is cached, so it is a one-off. `index local`, `index git`,
|
|
62
63
|
`search`, `memory` and `stats` do not need it — only fetching a URL does.
|
|
63
64
|
|
|
65
|
+
**URLs that an agent supplies are treated as untrusted.** `crawl_url` and
|
|
66
|
+
`index_git` refuse, before fetching or cloning, a value that is not `http(s)`
|
|
67
|
+
(or, for git, `https`/`ssh`), that begins with `-`, that is not a public
|
|
68
|
+
address, that leaves an allowlist you set, or that would write outside the
|
|
69
|
+
repository directory. Refusals are one line — `Refused: private_address — …` —
|
|
70
|
+
and appear in the tool result with `isError`. A site crawl is capped at 50 pages
|
|
71
|
+
per call. Set `crawler.allow_private_networks: true` to crawl an intranet, and
|
|
72
|
+
`crawler.allowed_domains` to narrow what may be reached; see
|
|
73
|
+
[docs/configuration.md](docs/configuration.md#url-policy).
|
|
74
|
+
|
|
64
75
|
### MCP integration with Claude Code (primary surface)
|
|
65
76
|
|
|
66
77
|
The MCP server is a console script, so where it lives depends on how you
|
|
@@ -223,8 +234,13 @@ crawler:
|
|
|
223
234
|
max_concurrent: 5
|
|
224
235
|
rate_limit: 2.0
|
|
225
236
|
respect_robots: true
|
|
237
|
+
allow_private_networks: false # opt in to intranet/loopback targets
|
|
238
|
+
allowed_domains: [] # empty = any public host
|
|
226
239
|
```
|
|
227
240
|
|
|
241
|
+
Graphiti telemetry is **off** unless you set `GRAPHITI_TELEMETRY_ENABLED`
|
|
242
|
+
yourself; an explicit value is never overridden.
|
|
243
|
+
|
|
228
244
|
`provider: auto` resolves to **fastembed** (local ONNX) — nothing about your
|
|
229
245
|
environment changes that, so documents are not sent anywhere for embedding
|
|
230
246
|
unless you name a cloud provider. Set `provider: openai` (or `cohere`) to send
|
|
@@ -236,6 +252,27 @@ overridden by environment variable, e.g. `DOCUGRAPH_EMBEDDINGS__MODEL` or
|
|
|
236
252
|
See [docs/configuration.md](https://github.com/CJ-coding-apps/docugraph/blob/main/docs/configuration.md) for the full reference,
|
|
237
253
|
including the embedding-model consistency guard and the graph LLM requirement.
|
|
238
254
|
|
|
255
|
+
## Network activity
|
|
256
|
+
|
|
257
|
+
DocuGraph runs locally, but these connections are possible — each only in the situation named:
|
|
258
|
+
|
|
259
|
+
- **Model downloads.** `fastembed` (the default embedder) downloads its ONNX model from Hugging
|
|
260
|
+
Face on first use, and the reranker (about 2 GB) when you first enable it. Afterwards the model is
|
|
261
|
+
read from the local cache. Nothing about your documents is sent.
|
|
262
|
+
- **Crawl and clone targets.** `crawl_url` fetches the pages you point it at, and `index_git`
|
|
263
|
+
clones the repository URL you give it. The MCP tools treat those values as untrusted: pages must be
|
|
264
|
+
`http`/`https` and resolve to a global address, git remotes must pass the same check, and
|
|
265
|
+
`crawler.allow_private_networks` is the opt-in for an intranet or a local docs server.
|
|
266
|
+
- **A named LLM or embedding provider.** Only if you name one: `embeddings.provider: openai` (or
|
|
267
|
+
`cohere`) sends the text being embedded there, and the graph tools send prompts to the configured
|
|
268
|
+
LLM — OpenAI, Anthropic, or Ollama.
|
|
269
|
+
- **Ollama**, when configured — to your Ollama server, localhost by default.
|
|
270
|
+
- **Graphiti telemetry**, to PostHog, **off unless you opt in** with
|
|
271
|
+
`GRAPHITI_TELEMETRY_ENABLED=true`.
|
|
272
|
+
|
|
273
|
+
CLI crawls and clones are user-directed: they are exempt from the scheme, DNS and address rules,
|
|
274
|
+
but the repository-name containment and the git hardening apply to every caller.
|
|
275
|
+
|
|
239
276
|
## The documentation knowledge graph
|
|
240
277
|
|
|
241
278
|
DocuGraph builds a temporal entity/relationship graph over your docs (Kuzu,
|
|
@@ -246,6 +283,47 @@ required**. However, Graphiti performs entity extraction with an **LLM**, so
|
|
|
246
283
|
OpenAI/Anthropic key). Without one, the graph tools degrade gracefully with a
|
|
247
284
|
clear message; vector and keyword search are unaffected.
|
|
248
285
|
|
|
286
|
+
## Retrieval benchmark
|
|
287
|
+
|
|
288
|
+
Retrieval quality is measured, not asserted. `benchmarks/retrieval/` commits a
|
|
289
|
+
hand-authored documentation corpus for a fictional SDK plus graded relevance
|
|
290
|
+
judgments, built adversarially around seven failure modes: exact-token lookups
|
|
291
|
+
(error codes, parameter names), paraphrase queries with no lexical overlap,
|
|
292
|
+
keyword-stuffed distractor pages, near-sibling semantic distractors, version-
|
|
293
|
+
pinned questions, acronym/synonym gaps, and "X without Y" constraint queries.
|
|
294
|
+
Each query records the trap it sets in a `design_note`, and a manifest pins the
|
|
295
|
+
dataset by hash — editing it is a deliberate act that invalidates baselines.
|
|
296
|
+
The corpus holds 70 documents and 56 queries, eight per failure mode; the
|
|
297
|
+
keyword-trap distractors are built to cover many *distinct* query terms rather
|
|
298
|
+
than repeat one (BM25 saturates a repeated term, so a one-token trap does not
|
|
299
|
+
bind), and the semantic-distractor queries share few exact tokens with their
|
|
300
|
+
target so that only meaning — not lexical overlap — can recover them.
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
# Score every arm (vector-only, BM25-only, LanceDB-native hybrid, RRF, weighted)
|
|
304
|
+
# against a temporary store built from the committed corpus:
|
|
305
|
+
docugraph bench run --json report.json
|
|
306
|
+
|
|
307
|
+
# Hold a report against a committed baseline; exits 1 when nDCG@10 drops by
|
|
308
|
+
# more than the tolerance, overall or in any single category:
|
|
309
|
+
docugraph bench compare benchmarks/retrieval/baselines/bge-small-en-v1.5.json report.json
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Reported metrics are recall@1/5/10, MRR@10, and nDCG@10 (graded, linear gain),
|
|
313
|
+
macro-averaged overall and per category. The per-category table is the point:
|
|
314
|
+
a fusion change that wins on average while losing exact-code lookup shows up
|
|
315
|
+
as the regression it is. Baselines are per embedding model and live in
|
|
316
|
+
`benchmarks/retrieval/baselines/`; the `Retrieval benchmark` workflow re-runs
|
|
317
|
+
the suite weekly against the committed baseline.
|
|
318
|
+
|
|
319
|
+
Honest scope: the corpus is small and purpose-built, so scores are comparable
|
|
320
|
+
across docugraph changes — not with BEIR or any external leaderboard. The
|
|
321
|
+
first thing this benchmark caught predates it: before v0.2.0 no FTS index was
|
|
322
|
+
ever created, so "keyword" search silently returned vector results and
|
|
323
|
+
RRF fused the vector ranking with itself. The benchmark's `fts` arm and the
|
|
324
|
+
hybrid arm's refuse-to-score-on-fallback check exist so that class of silent
|
|
325
|
+
degradation cannot come back unnoticed.
|
|
326
|
+
|
|
249
327
|
### KuzuDB version
|
|
250
328
|
|
|
251
329
|
`kuzu` is pinned to `==0.11.3` in `pyproject.toml` (reached through
|