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.
Files changed (124) hide show
  1. docugraph-0.2.1/.github/workflows/bench.yml +79 -0
  2. {docugraph-0.1.1 → docugraph-0.2.1}/.github/workflows/ci.yml +8 -3
  3. docugraph-0.2.1/CHANGELOG.md +117 -0
  4. {docugraph-0.1.1 → docugraph-0.2.1}/PKG-INFO +79 -1
  5. {docugraph-0.1.1 → docugraph-0.2.1}/README.md +78 -0
  6. docugraph-0.2.1/WP-docugraph-0.2.1-security.md +230 -0
  7. docugraph-0.2.1/benchmarks/retrieval/MANIFEST.json +13 -0
  8. docugraph-0.2.1/benchmarks/retrieval/baselines/bge-small-en-v1.5.json +6520 -0
  9. docugraph-0.2.1/benchmarks/retrieval/corpus.jsonl +70 -0
  10. docugraph-0.2.1/benchmarks/retrieval/queries.jsonl +56 -0
  11. docugraph-0.2.1/docs/SECURITY.md +83 -0
  12. {docugraph-0.1.1 → docugraph-0.2.1}/docs/configuration.md +43 -0
  13. {docugraph-0.1.1 → docugraph-0.2.1}/pyproject.toml +1 -1
  14. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/core/config.py +6 -0
  15. docugraph-0.2.1/src/docugraph/evals/__init__.py +12 -0
  16. docugraph-0.2.1/src/docugraph/evals/arms.py +111 -0
  17. docugraph-0.2.1/src/docugraph/evals/compare.py +106 -0
  18. docugraph-0.2.1/src/docugraph/evals/dataset.py +210 -0
  19. docugraph-0.2.1/src/docugraph/evals/metrics.py +123 -0
  20. docugraph-0.2.1/src/docugraph/evals/runner.py +169 -0
  21. docugraph-0.2.1/src/docugraph/ingestion/crawler.py +557 -0
  22. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/ingestion/git_indexer.py +184 -18
  23. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/interfaces/cli.py +185 -1
  24. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/interfaces/mcp_server.py +141 -18
  25. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/retrieval/hybrid_search.py +7 -3
  26. docugraph-0.2.1/src/docugraph/security/__init__.py +21 -0
  27. docugraph-0.2.1/src/docugraph/security/url_policy.py +280 -0
  28. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/storage/graph_store.py +14 -0
  29. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/storage/vector_store.py +122 -21
  30. {docugraph-0.1.1 → docugraph-0.2.1}/tests/e2e/test_mcp_server.py +102 -0
  31. docugraph-0.2.1/tests/integration/test_bench_runner.py +126 -0
  32. docugraph-0.2.1/tests/integration/test_crawl_antibot.py +156 -0
  33. docugraph-0.2.1/tests/integration/test_crawl_policy.py +427 -0
  34. docugraph-0.2.1/tests/integration/test_crawl_redirect.py +185 -0
  35. docugraph-0.2.1/tests/unit/test_bench_dataset.py +133 -0
  36. docugraph-0.2.1/tests/unit/test_bench_metrics.py +95 -0
  37. docugraph-0.2.1/tests/unit/test_cli_git.py +44 -0
  38. docugraph-0.2.1/tests/unit/test_crawl_guard.py +703 -0
  39. docugraph-0.2.1/tests/unit/test_git_indexer_args.py +400 -0
  40. {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_hybrid_graph_status.py +7 -0
  41. docugraph-0.2.1/tests/unit/test_telemetry_default.py +133 -0
  42. docugraph-0.2.1/tests/unit/test_url_policy.py +418 -0
  43. {docugraph-0.1.1 → docugraph-0.2.1}/uv.lock +3 -3
  44. docugraph-0.1.1/docs/SECURITY.md +0 -45
  45. docugraph-0.1.1/src/docugraph/ingestion/crawler.py +0 -305
  46. {docugraph-0.1.1 → docugraph-0.2.1}/.dockerignore +0 -0
  47. {docugraph-0.1.1 → docugraph-0.2.1}/.github/dependabot.yml +0 -0
  48. {docugraph-0.1.1 → docugraph-0.2.1}/.github/workflows/docker-publish.yml +0 -0
  49. {docugraph-0.1.1 → docugraph-0.2.1}/.github/workflows/live-provider-smoke.yml +0 -0
  50. {docugraph-0.1.1 → docugraph-0.2.1}/.github/workflows/live-reranker.yml +0 -0
  51. {docugraph-0.1.1 → docugraph-0.2.1}/.github/workflows/release.yml +0 -0
  52. {docugraph-0.1.1 → docugraph-0.2.1}/.gitignore +0 -0
  53. {docugraph-0.1.1 → docugraph-0.2.1}/.pre-commit-config.yaml +0 -0
  54. {docugraph-0.1.1 → docugraph-0.2.1}/.python-version +0 -0
  55. {docugraph-0.1.1 → docugraph-0.2.1}/LICENSE +0 -0
  56. {docugraph-0.1.1 → docugraph-0.2.1}/NOTICE +0 -0
  57. {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/README.md +0 -0
  58. {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/hooks/docugraph-auto-index.sh +0 -0
  59. {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/hooks/docugraph-auto-search.sh +0 -0
  60. {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/hooks/docugraph-post-compact.sh +0 -0
  61. {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/hooks/docugraph-pre-compact.sh +0 -0
  62. {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/hooks/docugraph-rule-check.sh +0 -0
  63. {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/hooks/docugraph-session-end.sh +0 -0
  64. {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/hooks/docugraph-session-start.sh +0 -0
  65. {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/install.sh +0 -0
  66. {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/settings.json +0 -0
  67. {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/skills/architecture/SKILL.md +0 -0
  68. {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/skills/context/SKILL.md +0 -0
  69. {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/skills/docs/SKILL.md +0 -0
  70. {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/skills/index/SKILL.md +0 -0
  71. {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/skills/recall/SKILL.md +0 -0
  72. {docugraph-0.1.1 → docugraph-0.2.1}/claude-integration/skills/remember/SKILL.md +0 -0
  73. {docugraph-0.1.1 → docugraph-0.2.1}/docker/Dockerfile +0 -0
  74. {docugraph-0.1.1 → docugraph-0.2.1}/docker/docker-compose.yml +0 -0
  75. {docugraph-0.1.1 → docugraph-0.2.1}/docker-compose.yml +0 -0
  76. {docugraph-0.1.1 → docugraph-0.2.1}/docs/quickstart.md +0 -0
  77. {docugraph-0.1.1 → docugraph-0.2.1}/examples/crawl_and_search.py +0 -0
  78. {docugraph-0.1.1 → docugraph-0.2.1}/examples/index_local_repo.py +0 -0
  79. {docugraph-0.1.1 → docugraph-0.2.1}/examples/mcp_client_example.py +0 -0
  80. {docugraph-0.1.1 → docugraph-0.2.1}/scripts/live_provider_smoke.py +0 -0
  81. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/__init__.py +0 -0
  82. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/_version.py +0 -0
  83. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/agents/__init__.py +0 -0
  84. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/agents/coding_agent.py +0 -0
  85. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/agents/tools/__init__.py +0 -0
  86. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/agents/tools/graph_query.py +0 -0
  87. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/agents/tools/memory_ops.py +0 -0
  88. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/agents/tools/search_code.py +0 -0
  89. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/agents/tools/search_docs.py +0 -0
  90. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/core/__init__.py +0 -0
  91. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/core/embeddings.py +0 -0
  92. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/core/llm.py +0 -0
  93. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/core/models.py +0 -0
  94. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/ingestion/__init__.py +0 -0
  95. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/ingestion/api_docs.py +0 -0
  96. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/ingestion/chunker.py +0 -0
  97. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/ingestion/local_files.py +0 -0
  98. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/interfaces/__init__.py +0 -0
  99. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/retrieval/__init__.py +0 -0
  100. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/retrieval/context_builder.py +0 -0
  101. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/retrieval/reranker.py +0 -0
  102. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/storage/__init__.py +0 -0
  103. {docugraph-0.1.1 → docugraph-0.2.1}/src/docugraph/storage/memory_store.py +0 -0
  104. {docugraph-0.1.1 → docugraph-0.2.1}/tests/e2e/__init__.py +0 -0
  105. {docugraph-0.1.1 → docugraph-0.2.1}/tests/e2e/test_cli.py +0 -0
  106. {docugraph-0.1.1 → docugraph-0.2.1}/tests/e2e/test_hooks.py +0 -0
  107. {docugraph-0.1.1 → docugraph-0.2.1}/tests/e2e/tools_snapshot.json +0 -0
  108. {docugraph-0.1.1 → docugraph-0.2.1}/tests/integration/__init__.py +0 -0
  109. {docugraph-0.1.1 → docugraph-0.2.1}/tests/integration/test_graph_store_init.py +0 -0
  110. {docugraph-0.1.1 → docugraph-0.2.1}/tests/integration/test_memory_store.py +0 -0
  111. {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/__init__.py +0 -0
  112. {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_chunker.py +0 -0
  113. {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_cloud_provider_sdks.py +0 -0
  114. {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_docs_consistency.py +0 -0
  115. {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_embeddings.py +0 -0
  116. {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_event_loop_acquisition.py +0 -0
  117. {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_graph_embedder_adapter.py +0 -0
  118. {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_graphiti_llm_client.py +0 -0
  119. {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_llm_preflight.py +0 -0
  120. {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_models.py +0 -0
  121. {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_no_model_ids_in_source.py +0 -0
  122. {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_reranker.py +0 -0
  123. {docugraph-0.1.1 → docugraph-0.2.1}/tests/unit/test_vector_store.py +0 -0
  124. {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
- EXPECTED = "0.1.1" # the version this repo publishes; pinned deliberately
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.1.1
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