codebase-index 1.8.0__tar.gz → 1.9.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.
- codebase_index-1.9.0/.claude/skills/codebase-index/.skill_version +1 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.claude-plugin/plugin.json +1 -1
- codebase_index-1.9.0/.codex/skills/codebase-index/.skill_version +1 -0
- codebase_index-1.9.0/.opencode/skills/codebase-index/.skill_version +1 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/CHANGELOG.md +83 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/PKG-INFO +14 -8
- {codebase_index-1.8.0 → codebase_index-1.9.0}/README.md +13 -7
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/ARCHITECTURE.md +4 -3
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/BENCHMARKS.md +23 -4
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/RETRIEVAL.md +66 -17
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/RETRIEVAL_PIPELINE.md +18 -9
- {codebase_index-1.8.0 → codebase_index-1.9.0}/requirements.lock +1 -1
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/__init__.py +1 -1
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/mcp/server.py +1 -1
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/retrieval/diversity.py +47 -12
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/retrieval/fusion.py +38 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/retrieval/pipeline.py +13 -6
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/retrieval/priors.py +18 -2
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/retrieval/searchers.py +30 -8
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/retrieval/tuning.py +41 -1
- codebase_index-1.9.0/tests/eval/README.md +99 -0
- codebase_index-1.9.0/tests/eval/gen_queries.py +253 -0
- codebase_index-1.9.0/tests/eval/harness.py +419 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/eval/metrics.py +78 -0
- codebase_index-1.9.0/tests/eval/queries/self_repo_git.yml +627 -0
- codebase_index-1.9.0/tests/eval/run_eval.py +186 -0
- codebase_index-1.9.0/tests/test_diversity.py +120 -0
- codebase_index-1.9.0/tests/test_fusion.py +145 -0
- codebase_index-1.9.0/tests/test_gen_queries.py +152 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_priors.py +9 -1
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_symbol_ranking.py +76 -0
- codebase_index-1.9.0/tests/test_tuning_contract.py +141 -0
- codebase_index-1.8.0/.claude/skills/codebase-index/.skill_version +0 -1
- codebase_index-1.8.0/.codex/skills/codebase-index/.skill_version +0 -1
- codebase_index-1.8.0/.opencode/skills/codebase-index/.skill_version +0 -1
- codebase_index-1.8.0/tests/eval/harness.py +0 -257
- codebase_index-1.8.0/tests/eval/run_eval.py +0 -104
- codebase_index-1.8.0/tests/test_diversity.py +0 -49
- codebase_index-1.8.0/tests/test_fusion.py +0 -66
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.claude/skills/codebase-index/SKILL.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.claude/skills/codebase-index/examples/hooks/settings.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.claude/skills/codebase-index/references/commands.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.claude/skills/codebase-index/references/response-contract.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.claude/skills/codebase-index/scripts/cbx +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.claude/skills/codebase-index/scripts/cbx.ps1 +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.claude-plugin/marketplace.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.codex/skills/codebase-index/SKILL.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.codex/skills/codebase-index/examples/hooks/settings.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.codex/skills/codebase-index/references/commands.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.codex/skills/codebase-index/references/response-contract.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.codex/skills/codebase-index/scripts/cbx +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.codex/skills/codebase-index/scripts/cbx.ps1 +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.editorconfig +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.gitattributes +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.github/FUNDING.yml +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.github/ISSUE_TEMPLATE/skill_listing_request.yml +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.github/workflows/ci.yml +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.github/workflows/release.yml +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.gitignore +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.opencode/agents/codebase-index.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.opencode/commands/codebase-index.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.opencode/skills/codebase-index/SKILL.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.opencode/skills/codebase-index/examples/hooks/settings.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.opencode/skills/codebase-index/references/commands.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.opencode/skills/codebase-index/references/response-contract.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.opencode/skills/codebase-index/scripts/cbx +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/.opencode/skills/codebase-index/scripts/cbx.ps1 +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/AGENTS.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/CODE_OF_CONDUCT.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/CONTRIBUTING.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/LICENSE +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/ROADMAP.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/SECURITY.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/adapters/claude.ps1 +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/adapters/claude.sh +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/adapters/codex.ps1 +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/adapters/codex.sh +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/adapters/opencode.ps1 +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/adapters/opencode.sh +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/assets/demo.png +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/assets/mark.png +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/assets/social-preview.png +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/bin/cbx +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/bin/cbx.ps1 +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/bin/codebase-index +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/bin/codebase-index.ps1 +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/COMPARISON.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/DATABASE_SCHEMA.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/FAQ.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/INSTALLATION.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/LANGUAGES.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/MCP.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/PRODUCT_UPGRADE_PLAN.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/QUICKSTART.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/RELEASE_CHECKLIST.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/ROADMAP.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/SCHEMA.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/SECURITY.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/SECURITY_MODEL.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/SEO.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/SKILL_DESIGN.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/installer.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/superpowers/plans/2026-05-29-benchmark-index-improvements.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/superpowers/plans/2026-05-29-generalize-skill-research-discipline.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/superpowers/plans/2026-05-29-m1-storage-discovery.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/superpowers/plans/2026-05-29-m2-fts-lexical.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/superpowers/plans/2026-05-29-m3-treesitter-symbols.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/superpowers/plans/2026-05-29-m4-hybrid-search.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/superpowers/plans/2026-05-29-m5-graph-impact.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/superpowers/plans/2026-05-29-m6-embeddings-vector.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/superpowers/plans/2026-05-29-m7-5-plugin-one-command-install.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/superpowers/plans/2026-05-29-m7-skill-packaging.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/superpowers/plans/2026-05-29-m8-hooks-watch.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/superpowers/plans/2026-05-29-m9-release.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/superpowers/plans/2026-05-29-multilang-symbols-make-skill-win.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/superpowers/plans/2026-06-24-snippet-skeletonization.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/superpowers/specs/2026-05-29-plugin-one-command-install-design.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/superpowers/specs/2026-06-14-typed-framework-edges-design.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/docs/superpowers/specs/2026-06-24-snippet-skeletonization-design.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/examples/config.example.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/examples/demo-project/README.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/examples/hooks/settings.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/examples/queries.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/examples/sample-queries.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/examples/sample-retrieval-output.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/hooks/hooks.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/install.ps1 +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/install.sh +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/lib/common.ps1 +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/lib/common.sh +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/pyproject.toml +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/scripts/bootstrap.ps1 +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/scripts/bootstrap.sh +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/scripts/gen_assets.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/scripts/release_smoke.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/scripts/sync_skill_copies.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/skill/SKILL.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/skill/examples/basic-usage.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/skill/examples/claude-md-example.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/skill/examples/hooks-example.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/skill/references/commands.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/skill/references/response-contract.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/skill/scripts/bootstrap.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/skill/scripts/cbx +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/skill/scripts/cbx.ps1 +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/skill/scripts/doctor.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/skill/scripts/install.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/skill/scripts/smoke_test.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/skills/codebase-index/SKILL.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/skills/codebase-index/references/commands.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/skills/codebase-index/references/response-contract.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/__main__.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/cli.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/config.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/discovery/__init__.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/discovery/classify.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/discovery/ignore.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/discovery/walker.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/doctor.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/embeddings/__init__.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/embeddings/backend.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/embeddings/external.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/embeddings/local.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/embeddings/noop.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/graph/__init__.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/graph/analysis.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/graph/builder.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/graph/expand.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/graph/export.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/graph/navigate.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/graph/retrieval.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/indexer/__init__.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/indexer/doc_chunks.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/indexer/freshness.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/indexer/pipeline.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/mcp/__init__.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/models.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/output/__init__.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/output/json.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/output/markdown.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/output/redact.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/parsers/__init__.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/parsers/base.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/parsers/languages.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/parsers/line_chunker.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/parsers/symbol_chunks.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/parsers/treesitter.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/retrieval/__init__.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/retrieval/budget.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/retrieval/fuzzy.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/retrieval/intent.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/retrieval/lexical.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/retrieval/rerank.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/retrieval/skeleton.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/retrieval/types.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/scaffold.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/service.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/skill_template/SKILL.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/skill_template/examples/hooks/settings.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/skill_template/references/commands.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/skill_template/references/response-contract.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/skill_template/scripts/cbx +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/skill_template/scripts/cbx.ps1 +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/skill_update.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/storage/__init__.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/storage/db.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/storage/repo.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/storage/schema.sql +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/watch/__init__.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/src/codebase_index/watch/watcher.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/__init__.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/benchmark_honest.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/benchmark_honest_RESULTS.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/benchmark_honest_newtowny.txt +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/benchmark_public.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/benchmark_public_RESULTS.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/benchmark_real_repo.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/conftest.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/eval/__init__.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/eval/queries/self_repo.yml +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/fixtures/README.md +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/fixtures/expected_answers.yml +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/fixtures/multilang/sample.go +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/fixtures/multilang/sample.java +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/fixtures/multilang/sample.rs +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/fixtures/sample_repo/.env +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/fixtures/sample_repo/.gitignore +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/fixtures/sample_repo/huge.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/fixtures/sample_repo/logo.png +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/fixtures/sample_repo/node_modules/leftpad/index.js +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/fixtures/sample_repo/secrets.pem +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/fixtures/sample_repo/src/api/service.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/fixtures/sample_repo/src/auth/token.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/fixtures/sample_repo/src/models/user.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/fixtures/sample_repo/src/schema.generated.ts +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/fixtures/sample_repo/web/app.ts +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/architecture.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/describe_refresh.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/explain_auth.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/impact_user_model.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/mcp_architecture.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/mcp_describe_symbol.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/mcp_explain_code.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/mcp_find_refs.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/mcp_find_symbol.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/mcp_healthcheck.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/mcp_impact_of.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/mcp_impact_of_diff.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/mcp_index_stats.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/mcp_path_between.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/mcp_search_code.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/path_renew_to_token.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/refs_refresh_access_token.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/search_token.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/stats.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden/symbol_user.json +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/golden_utils.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/installer/smoke.ps1 +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/installer/smoke.sh +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_analysis.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_benchmark_comparison.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_bootstrap.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_budget.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_chunker.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_classify.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_clean_cli.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_cli.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_cli_golden.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_config.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_diff_impact.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_discovery.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_doc_chunks.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_doctor.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_embeddings_backend.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_embeddings_external.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_embeddings_local.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_freshness.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_fuzzy.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_graph.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_graph_coverage.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_graph_export_cli.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_graph_retrieval.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_hooks_merge.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_hybrid_ranking.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_ignore.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_impact_cli.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_impact_decay.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_index_cli.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_init_cli.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_intent.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_languages.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_lexical.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_mcp_golden.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_mcp_server.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_multilang_symbols.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_navigate.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_output.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_packaging.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_parse_guardrails.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_perf_smoke.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_pipeline.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_pipeline_search.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_pipeline_vectors.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_plugin_manifest.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_plugin_skill_parity.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_plugin_wrappers.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_public_benchmark.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_quality_benchmark.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_redact.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_repo_search.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_rerank.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_retrieval_types.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_scaffold.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_search_cli.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_searchers.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_seeded_index.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_service.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_skeleton.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_skill_update.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_storage.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_symbol_cli.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_symbol_refs.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_sync_skill_copies.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_treesitter.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_update.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_update_cli.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_vector_search.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_vectors_storage.py +0 -0
- {codebase_index-1.8.0 → codebase_index-1.9.0}/tests/test_watcher.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.9.0
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "codebase-index",
|
|
4
4
|
"displayName": "Codebase Index",
|
|
5
5
|
"description": "Give Claude a precise local map of your codebase: find implementations, trace behavior, and predict change impact with file-line evidence.",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.9.0",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "codebase-index contributors"
|
|
9
9
|
},
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.9.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.9.0
|
|
@@ -6,6 +6,89 @@ All notable changes to this project are documented here. The format is based on
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.9.0] - 2026-09-02
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Objective ground truth from git history.** `tests/eval/gen_queries.py` mints a
|
|
14
|
+
retrieval benchmark from any git repository by pairing a human-written commit
|
|
15
|
+
subject with the files that commit actually changed. Unlike docstring-derived
|
|
16
|
+
benchmarks the query text is not copied into the document being retrieved, so the
|
|
17
|
+
set is leak-free by construction; merges, reverts, releases, version bumps,
|
|
18
|
+
sweeping refactors, changelog-style answers and benchmark scaffolding are all
|
|
19
|
+
filtered out.
|
|
20
|
+
- **Multi-corpus, multi-language evaluation.** `run_eval.py --corpus REPO:QUERIES`
|
|
21
|
+
pools several repositories into one benchmark so ranking changes are validated
|
|
22
|
+
outside this repository and outside Python. The 1.9.0 defaults were measured on
|
|
23
|
+
305 queries across Python, Java and TypeScript corpora.
|
|
24
|
+
- **Significance testing.** Every non-baseline row now reports a paired bootstrap
|
|
25
|
+
95% confidence interval and a paired permutation p-value (both seeded, therefore
|
|
26
|
+
reproducible). Query sets of this size have a noise floor of several MRR points;
|
|
27
|
+
signals now ship on the strength of that test rather than the sign of a delta.
|
|
28
|
+
- **Context-noise metrics.** The report adds mean emitted snippet tokens, duplicate
|
|
29
|
+
rate of returned results, and p99 latency alongside the existing IR metrics.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- **Fusion now scores cross-retriever agreement.** RRF fuses on
|
|
34
|
+
`(path, line-bucket)`, so a symbol definition at line 40 and a lexical hit at line
|
|
35
|
+
120 in the same file fused as two unrelated candidates — two retrievers agreeing
|
|
36
|
+
on a file produced two weak results instead of one strong one, and cross-source
|
|
37
|
+
agreement never reached the score. Each candidate now also receives, at
|
|
38
|
+
`file_agreement_weight` (0.4), the RRF mass of every retriever that found its file
|
|
39
|
+
at another locator, excluding retrievers already counted at that locator.
|
|
40
|
+
Ablatable via `RetrievalTuning(file_agreement=False)`.
|
|
41
|
+
- **Documentation demotion deepened** from -0.05 to -0.20. Prose describing a
|
|
42
|
+
feature matches a natural-language question more literally than the code
|
|
43
|
+
implementing it, so design notes and plans were displacing the modules they
|
|
44
|
+
describe. Because this only reorders prose relative to code, documentation-seeking
|
|
45
|
+
queries improved as well (category MRR 0.579 → 0.612). Generated/vendor paths move
|
|
46
|
+
to -0.25 so they remain the least-preferred role, and `MAX_ABS_PRIOR` now pins the
|
|
47
|
+
invariant that priors stay tiebreakers.
|
|
48
|
+
- **Fuzzy identifier matching is now a recall fallback.** It runs only when the
|
|
49
|
+
precise symbol lookup named no symbol and returned fewer than
|
|
50
|
+
`fuzzy_fallback_min` (3) rows. It moved no ranking metric across 305 queries while
|
|
51
|
+
accounting for ~20% of query latency; typo and acronym recall is unchanged because
|
|
52
|
+
those are exactly the queries where the precise lookup comes up empty.
|
|
53
|
+
- **Candidate over-fetch is explicit.** `candidate_pool_multiplier` (default 2)
|
|
54
|
+
replaces the implicit widening that happened whenever dedup or MMR was enabled.
|
|
55
|
+
Making it explicit revealed that the quality previously credited to SimHash dedup
|
|
56
|
+
was really the wider pool; dedup is retained for what it does measurably do, which
|
|
57
|
+
is cutting the duplicate rate of returned snippets from ~1.6% to ~0%.
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- **Synonym matches were reported as exact symbol matches.** `is_exact` came from
|
|
62
|
+
SQL and was relative to whichever needle retrieved the row, so a synonym
|
|
63
|
+
expansion ("config" for "configuration") marked an unrelated symbol as an exact
|
|
64
|
+
match — worth a +0.20 rerank bonus and an unconditional `high` confidence.
|
|
65
|
+
Exactness is now judged against the terms the user actually typed.
|
|
66
|
+
- **Duplicate suppression was order-dependent on ties.** Equal-scoring duplicates
|
|
67
|
+
handed the slot to whichever copy arrived last, contradicting the documented
|
|
68
|
+
"ties favor input order" and making the retained snippet depend on retriever
|
|
69
|
+
emission order.
|
|
70
|
+
|
|
71
|
+
### Performance
|
|
72
|
+
|
|
73
|
+
- Query latency roughly halves: p50 78.6 ms → 51.2 ms, p95 193.2 ms → 94.9 ms,
|
|
74
|
+
p99 277.1 ms → 152.2 ms on the pooled three-repository benchmark, from the fuzzy
|
|
75
|
+
fallback plus a SimHash fingerprint that folds repeated tokens by multiplicity and
|
|
76
|
+
caches token digests. Fingerprint output is bit-for-bit unchanged.
|
|
77
|
+
- Mean emitted snippet tokens fall 1183 → 1091 per query.
|
|
78
|
+
|
|
79
|
+
### Retrieval quality
|
|
80
|
+
|
|
81
|
+
Pooled over 305 queries (Python, Java, TypeScript), v1.8.0 → 1.9.0:
|
|
82
|
+
|
|
83
|
+
| Metric | v1.8.0 | 1.9.0 | Δ | p |
|
|
84
|
+
|---|---|---|---|---|
|
|
85
|
+
| MRR | 0.564 | 0.591 | +0.027 | <0.001 |
|
|
86
|
+
| MAP | 0.433 | 0.461 | +0.028 | <0.001 |
|
|
87
|
+
| nDCG@10 | 0.503 | 0.527 | +0.024 | <0.001 |
|
|
88
|
+
| recall@5 | 0.529 | 0.560 | +0.031 | <0.001 |
|
|
89
|
+
| P@5 | 0.182 | 0.192 | +0.009 | 0.001 |
|
|
90
|
+
| hit@3 | 0.649 | 0.666 | +0.016 | 0.124 |
|
|
91
|
+
|
|
9
92
|
## [1.8.0] - 2026-09-02
|
|
10
93
|
|
|
11
94
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codebase-index
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.0
|
|
4
4
|
Summary: A precise local codebase map for AI coding agents: find, trace, and predict with file-line evidence.
|
|
5
5
|
Project-URL: Homepage, https://github.com/denfry/codebase-index
|
|
6
6
|
Project-URL: Documentation, https://github.com/denfry/codebase-index/tree/main/docs
|
|
@@ -321,7 +321,7 @@ See [MCP.md](docs/MCP.md) for client configuration.
|
|
|
321
321
|
|
|
322
322
|
## Project status
|
|
323
323
|
|
|
324
|
-
The latest released line is **1.
|
|
324
|
+
The latest released line is **1.9.0**. It includes:
|
|
325
325
|
|
|
326
326
|
- hybrid and optional vector retrieval;
|
|
327
327
|
- Tree-sitter symbol extraction across the documented language tiers;
|
|
@@ -330,15 +330,21 @@ The latest released line is **1.8.0**. It includes:
|
|
|
330
330
|
- shortest dependency paths and node descriptions;
|
|
331
331
|
- token-budgeted and skeletonized retrieval packets;
|
|
332
332
|
- benchmark-calibrated lexical expansion, fuzzy identifier matching, and source-aware ranking;
|
|
333
|
+
- rank fusion that scores cross-retriever agreement at file level, not just at a locator;
|
|
333
334
|
- bounded, intent-directed graph discovery with optional diversity and duplicate suppression;
|
|
334
335
|
- CLI, Skill, plugin, and MCP delivery;
|
|
335
336
|
- incremental updates, watch hooks, diagnostics, skill rollback, and diff-aware
|
|
336
|
-
impact analysis
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
337
|
+
impact analysis;
|
|
338
|
+
- a multi-repository retrieval evaluation with leak-free git-derived ground truth,
|
|
339
|
+
one-signal ablations, and paired significance tests
|
|
340
|
+
([tests/eval](tests/eval/README.md)).
|
|
341
|
+
|
|
342
|
+
Every shipped ranking signal has to survive that evaluation: 1.9.0 removed the
|
|
343
|
+
cost of two signals that could not demonstrate a benefit and rejected several
|
|
344
|
+
plausible ones outright (IDF-weighted coverage, stemming, graph propagation, MMR,
|
|
345
|
+
a file-length prior). Planned work is deliberately separated from shipped
|
|
346
|
+
capability. The next product priorities are typed framework edges and an even more
|
|
347
|
+
direct task-context workflow. See the [roadmap](docs/ROADMAP.md).
|
|
342
348
|
|
|
343
349
|
## Documentation
|
|
344
350
|
|
|
@@ -271,7 +271,7 @@ See [MCP.md](docs/MCP.md) for client configuration.
|
|
|
271
271
|
|
|
272
272
|
## Project status
|
|
273
273
|
|
|
274
|
-
The latest released line is **1.
|
|
274
|
+
The latest released line is **1.9.0**. It includes:
|
|
275
275
|
|
|
276
276
|
- hybrid and optional vector retrieval;
|
|
277
277
|
- Tree-sitter symbol extraction across the documented language tiers;
|
|
@@ -280,15 +280,21 @@ The latest released line is **1.8.0**. It includes:
|
|
|
280
280
|
- shortest dependency paths and node descriptions;
|
|
281
281
|
- token-budgeted and skeletonized retrieval packets;
|
|
282
282
|
- benchmark-calibrated lexical expansion, fuzzy identifier matching, and source-aware ranking;
|
|
283
|
+
- rank fusion that scores cross-retriever agreement at file level, not just at a locator;
|
|
283
284
|
- bounded, intent-directed graph discovery with optional diversity and duplicate suppression;
|
|
284
285
|
- CLI, Skill, plugin, and MCP delivery;
|
|
285
286
|
- incremental updates, watch hooks, diagnostics, skill rollback, and diff-aware
|
|
286
|
-
impact analysis
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
287
|
+
impact analysis;
|
|
288
|
+
- a multi-repository retrieval evaluation with leak-free git-derived ground truth,
|
|
289
|
+
one-signal ablations, and paired significance tests
|
|
290
|
+
([tests/eval](tests/eval/README.md)).
|
|
291
|
+
|
|
292
|
+
Every shipped ranking signal has to survive that evaluation: 1.9.0 removed the
|
|
293
|
+
cost of two signals that could not demonstrate a benefit and rejected several
|
|
294
|
+
plausible ones outright (IDF-weighted coverage, stemming, graph propagation, MMR,
|
|
295
|
+
a file-length prior). Planned work is deliberately separated from shipped
|
|
296
|
+
capability. The next product priorities are typed framework edges and an even more
|
|
297
|
+
direct task-context workflow. See the [roadmap](docs/ROADMAP.md).
|
|
292
298
|
|
|
293
299
|
## Documentation
|
|
294
300
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## 1. Overview
|
|
4
4
|
|
|
5
|
-
`codebase-index` is a **local-first** code intelligence layer for AI coding agents. In `1.
|
|
5
|
+
`codebase-index` is a **local-first** code intelligence layer for AI coding agents. In `1.9.0`
|
|
6
6
|
it has two shipped faces:
|
|
7
7
|
|
|
8
8
|
1. **A Claude Code Skill** (`.claude/skills/codebase-index/SKILL.md`) that Claude auto-invokes for
|
|
@@ -97,8 +97,9 @@ codebase-index/
|
|
|
97
97
|
├── graph/ # builder.py (edge resolution), expand.py (impact),
|
|
98
98
|
│ # export.py (HTML graph)
|
|
99
99
|
├── storage/ # db.py (pragmas, schema, version guard), schema.sql, repo.py
|
|
100
|
-
├── retrieval/ # intent.py, searchers.py, fusion.py, rerank.py,
|
|
101
|
-
│ #
|
|
100
|
+
├── retrieval/ # intent.py, searchers.py, fusion.py, rerank.py, priors.py,
|
|
101
|
+
│ # lexical.py, fuzzy.py, diversity.py, skeleton.py,
|
|
102
|
+
│ # tuning.py (ablation contract), budget.py, pipeline.py, types.py
|
|
102
103
|
├── embeddings/ # backend.py, noop.py (default), local.py, external.py — opt-in
|
|
103
104
|
├── output/ # markdown.py, json.py, redact.py
|
|
104
105
|
├── watch/ # watcher.py (optional, watchdog-based)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# Benchmarks
|
|
2
2
|
|
|
3
|
-
`codebase-index` has
|
|
3
|
+
`codebase-index` has four benchmark surfaces. Read them with their status in
|
|
4
4
|
mind — the whole point of this page is to keep evidence and aspiration separate.
|
|
5
5
|
|
|
6
6
|
| Surface | What it is | Status | Use it as |
|
|
7
7
|
|---|---|---|---|
|
|
8
|
+
| Retrieval eval (`tests/eval/`) | Ranking quality vs a fixed baseline across multiple real repositories, with significance tests | **Proven (relative)** | The gate for ranking changes; measures *deltas*, not absolute superiority |
|
|
8
9
|
| Public suite (`tests/benchmark_public.py`) | Deterministic synthetic multi-language fixture with the full metric framework | **Toy/synthetic** | CI regression gate + metric shape, **not** product-quality evidence |
|
|
9
10
|
| Smoke/perf (`test_perf_smoke.py`, `test_benchmark_comparison.py`) | Latency + output-size guards on a tiny fixture | **Toy/smoke** | Regression checks only |
|
|
10
11
|
| Honest real-repo (`tests/benchmark_honest.py`) | 55k LOC Java repo, recall@3 vs disciplined `rg` baseline, symmetric token accounting | **Proven (one repo)** | The only headline product-quality number we stand behind today |
|
|
@@ -15,15 +16,33 @@ Do not write, imply, or ship any of these until a run with published logs exists
|
|
|
15
16
|
|
|
16
17
|
- Any 10k / 100k / 1M LOC scale or speed claim (no real run at that size).
|
|
17
18
|
- "Beats Cursor / Sourcegraph / Codebase-Memory MCP" — no head-to-head exists.
|
|
18
|
-
- Per-language quality claims beyond Java
|
|
19
|
+
- Per-language *absolute* quality claims beyond Java. The retrieval eval covers
|
|
20
|
+
Python, Java and TypeScript, but it measures this system against its own earlier
|
|
21
|
+
versions — it says a change helped, not that the product beats an alternative.
|
|
19
22
|
- Generic "Nx faster" / "Nx fewer tokens" without naming the baseline and repo.
|
|
20
|
-
- Latency claims — the honest run explicitly does not
|
|
21
|
-
(Python process start dominates; real `rg` is tens of ms).
|
|
23
|
+
- Latency claims against external tools — the honest run explicitly does not
|
|
24
|
+
headline latency (Python process start dominates; real `rg` is tens of ms).
|
|
25
|
+
Version-over-version latency from the retrieval eval is in-process and is only
|
|
26
|
+
comparable to other runs of that harness.
|
|
22
27
|
|
|
23
28
|
The defensible headline today is exactly: **on one 55k LOC Java repo, recall@3 was
|
|
24
29
|
70% (index) vs 40% (`rg`+window), using ~13× fewer answer tokens.** Everything
|
|
25
30
|
else is roadmap.
|
|
26
31
|
|
|
32
|
+
## Retrieval evaluation (ranking gate)
|
|
33
|
+
|
|
34
|
+
See [tests/eval/README.md](../tests/eval/README.md) for the full protocol. In short:
|
|
35
|
+
ground truth comes from hand-written queries verified against the source tree and
|
|
36
|
+
from commit-subject → changed-files pairs mined from git history (leak-free: the
|
|
37
|
+
query text is not in the indexed corpus). Corpora are pooled across languages, one
|
|
38
|
+
index per corpus is shared by every variant, and every non-baseline row carries a
|
|
39
|
+
paired bootstrap CI and permutation p-value.
|
|
40
|
+
|
|
41
|
+
`1.9.0` was measured over 305 queries across Python, Java and TypeScript corpora
|
|
42
|
+
against `1.8.0`: MRR +0.027, MAP +0.028, nDCG@10 +0.024, recall@5 +0.031 (all
|
|
43
|
+
p < 0.001), with p50 latency 78.6 ms → 51.2 ms. These are version-over-version
|
|
44
|
+
ranking deltas on those corpora, not a universal quality claim.
|
|
45
|
+
|
|
27
46
|
## Public benchmark suite
|
|
28
47
|
|
|
29
48
|
Run:
|
|
@@ -65,6 +65,11 @@ source)` list so fusion is source-agnostic.
|
|
|
65
65
|
first when the query clearly names a path.
|
|
66
66
|
- **Symbol** — query against `symbols` (exact, identifier parts, bounded fuzzy matching). Carries
|
|
67
67
|
`kind` (function/class/method/...) and signature. Primary for `locate_impl` / `find_refs`.
|
|
68
|
+
Fuzzy identifier matching (acronym / concatenation / edit distance) runs only as a **recall
|
|
69
|
+
fallback**, when the precise lookup named no symbol and returned fewer than
|
|
70
|
+
`fuzzy_fallback_min` rows. Measured over 305 queries on three repositories it moved no ranking
|
|
71
|
+
metric while costing ~20% of query latency, so it is kept for typos and abbreviations but no
|
|
72
|
+
longer runs when the query already spelled its identifier correctly.
|
|
68
73
|
- **FTS** — FTS5 `bm25()` over the `fts_chunks` virtual table (chunk text + symbol names +
|
|
69
74
|
summaries indexed). Query-time camelCase/snake_case splitting, small down-weighted synonym
|
|
70
75
|
expansion, and soft coverage scoring make natural-language questions robust without weakening
|
|
@@ -79,28 +84,66 @@ source)` list so fusion is source-agnostic.
|
|
|
79
84
|
scores:
|
|
80
85
|
|
|
81
86
|
```
|
|
82
|
-
RRF(d) = Σ_r w_r / (k + rank_r(d))
|
|
87
|
+
RRF(d) = Σ_r w_r · k / (k + rank_r(d)) # k ≈ 60, w_r = per-intent retriever weight
|
|
83
88
|
```
|
|
84
89
|
|
|
85
90
|
- Robust to scale differences between BM25 and cosine.
|
|
86
91
|
- Per-intent weights `w_r` let `locate_impl` favor the symbol list and `how_it_works` favor FTS.
|
|
92
|
+
- Scaled by `k` so fused scores and the reranker's bounded bonuses share an O(1) scale. This is a
|
|
93
|
+
monotonic rescale; fusion order is unchanged.
|
|
87
94
|
- Ties broken by rerank features (next step).
|
|
88
95
|
|
|
89
|
-
|
|
96
|
+
### Cross-locator file agreement
|
|
97
|
+
|
|
98
|
+
Fusion keys on `(path, line-bucket)`, not `(path, start, end)`, because different retrievers report
|
|
99
|
+
different line ranges for the same place. Bucketing alone was not enough: a symbol defined at line
|
|
100
|
+
40 and a lexical hit at line 120 are genuinely different locators, so a file that **two retrievers
|
|
101
|
+
agreed on** still fused as two separate candidates, each carrying one retriever's evidence — and
|
|
102
|
+
cross-source agreement, the entire point of RRF, never fired.
|
|
103
|
+
|
|
104
|
+
Each candidate therefore also receives, at weight `file_agreement_weight`, the RRF mass of every
|
|
105
|
+
retriever that found its *file* at some other locator:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
score(d) = RRF(d) + α · Σ_{r ∉ sources(d)} w_r · k / (k + best_rank_r(path(d)))
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Retrievers already counted at the candidate's own locator are excluded, so nothing double-counts,
|
|
112
|
+
and the term is bounded by the same weights as fusion itself. `α = 0.4`; the 0.3–0.6 plateau peaks
|
|
113
|
+
there. Set `RetrievalTuning(file_agreement=False)` to recover plain locator-only RRF.
|
|
90
114
|
|
|
91
|
-
|
|
115
|
+
## 4. Reranking (`retrieval/rerank.py`)
|
|
92
116
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
|
97
|
-
|
|
98
|
-
|
|
|
99
|
-
|
|
|
100
|
-
|
|
|
117
|
+
A lightweight, explainable feature score (no external model required) layered on the fused order.
|
|
118
|
+
Every term is bounded, so reranking reorders near-neighbours rather than overruling retrieval:
|
|
119
|
+
|
|
120
|
+
| Feature | Effect | Intuition |
|
|
121
|
+
|---|---:|---|
|
|
122
|
+
| Exact symbol match | +0.20 | the user named a specific symbol |
|
|
123
|
+
| Symbol definition kind | +0.05 | a `def`/`class` outranks an incidental mention |
|
|
124
|
+
| Symbol name among query terms | +0.05 | the name was asked for, not just matched |
|
|
125
|
+
| Path term match | +0.05 | the user supplied a location clue |
|
|
126
|
+
| Graph centrality (`in_degree`) | ≤ +0.08 | `log1p`-damped, so a god class cannot dominate |
|
|
127
|
+
| Reference-count fallback | ≤ +0.04 | for names too common to resolve a precise `in_degree` |
|
|
128
|
+
| Source role prior | −0.25…+0.08 | see below |
|
|
129
|
+
| Generated, or test on a non-test query | −0.15 | supporting evidence, not the answer |
|
|
130
|
+
|
|
131
|
+
### Source role priors (`retrieval/priors.py`)
|
|
132
|
+
|
|
133
|
+
| Role | Prior | Rationale |
|
|
134
|
+
|---|---:|---|
|
|
135
|
+
| Implementation | +0.08 | the answer to a code question is usually code |
|
|
136
|
+
| Test | −0.06 | flips to +0.05 when the query or intent is test-oriented |
|
|
137
|
+
| Documentation | −0.20 | prose *about* a feature matches a natural-language question more literally than the code implementing it, so design notes and plans crowded out the modules they describe |
|
|
138
|
+
| Generated / vendor / build | −0.25 | never the answer; kept strictly below documentation |
|
|
139
|
+
|
|
140
|
+
The documentation prior deepened from −0.05 in 1.8.0. Because it only reorders prose relative to
|
|
141
|
+
code — never below other prose — documentation-seeking queries improved too (MRR 0.579 → 0.612).
|
|
142
|
+
At −0.35 that reverses and the `docs` category collapses, so the optimum is interior, not a
|
|
143
|
+
"more is better" knob. `MAX_ABS_PRIOR` caps every prior so this stays a tiebreaker.
|
|
101
144
|
|
|
102
145
|
The reranker also produces the human-readable **`reason`** string per result
|
|
103
|
-
(e.g. *"exact symbol match ·
|
|
146
|
+
(e.g. *"exact symbol match · 4 callers · in src/auth/"*).
|
|
104
147
|
|
|
105
148
|
## 5. Graph expansion (`graph/retrieval.py`; `graph/expand.py` for impact APIs)
|
|
106
149
|
|
|
@@ -119,11 +162,17 @@ Expanded nodes retain edge confidence and receive distance-decayed scores so see
|
|
|
119
162
|
|
|
120
163
|
## 6. Diversity and duplicate control
|
|
121
164
|
|
|
122
|
-
`retrieval.diversity` provides bounded MMR selection and SimHash near-duplicate
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
165
|
+
`retrieval.diversity` provides bounded MMR selection and SimHash near-duplicate suppression.
|
|
166
|
+
|
|
167
|
+
MMR is disabled in the shipped default: it moved no ranking metric on the benchmark and roughly
|
|
168
|
+
doubled p50 latency. Callers that need broader snippet coverage can enable
|
|
169
|
+
`RetrievalTuning(mmr=True)`.
|
|
170
|
+
|
|
171
|
+
SimHash duplicate suppression stays on, but on noise grounds rather than ranking grounds: it does
|
|
172
|
+
not move MRR, and it takes the duplicate rate of returned snippets from ~1.6% to ~0%. The
|
|
173
|
+
over-fetch that feeds selection is now an explicit `candidate_pool_multiplier` rather than an
|
|
174
|
+
implicit side effect of enabling dedup — an earlier ablation credited dedup with a quality win that
|
|
175
|
+
was really the wider pool doing the work.
|
|
127
176
|
|
|
128
177
|
## 7. Token budgeting (`retrieval/budget.py`)
|
|
129
178
|
|
|
@@ -97,17 +97,21 @@ direct lexical or symbol evidence.
|
|
|
97
97
|
|
|
98
98
|
## 6. Diversity and duplicate control
|
|
99
99
|
|
|
100
|
-
SimHash suppresses near-duplicate snippets independently of MMR.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
SimHash suppresses near-duplicate snippets independently of MMR. It does not move
|
|
101
|
+
ranking metrics; it earns its place by taking the duplicate rate of returned
|
|
102
|
+
snippets from ~1.6% to ~0%. Bounded Maximal Marginal Relevance is available
|
|
103
|
+
through `RetrievalTuning(mmr=True)`; the shipped default keeps relevance-only
|
|
104
|
+
ordering because MMR moved no metric and roughly doubled p50 latency.
|
|
105
|
+
|
|
106
|
+
The over-fetch that feeds these selection stages is `candidate_pool_multiplier`
|
|
107
|
+
(default 2), an explicit knob rather than a side effect of enabling dedup.
|
|
104
108
|
|
|
105
109
|
## 7. Reciprocal Rank Fusion (RRF)
|
|
106
110
|
|
|
107
111
|
Combines ranked lists from the enabled retrievers:
|
|
108
112
|
|
|
109
113
|
```
|
|
110
|
-
RRF_score(d) = Σ w_r · k / (k + rank_r(d))
|
|
114
|
+
RRF_score(d) = Σ_r w_r · k / (k + rank_r(d))
|
|
111
115
|
```
|
|
112
116
|
|
|
113
117
|
The implementation multiplies textbook RRF by `k` so fusion and bounded rerank
|
|
@@ -118,8 +122,13 @@ Where:
|
|
|
118
122
|
- `rank_r(d)` is the rank of document `d` in retriever `r`
|
|
119
123
|
- `w_r` is the intent/tuning weight for retriever `r`
|
|
120
124
|
|
|
121
|
-
|
|
122
|
-
|
|
125
|
+
Candidates are keyed by `(path, line-bucket)`, so co-located hits merge. Because a
|
|
126
|
+
symbol definition and a lexical hit in the same file are often *not* co-located,
|
|
127
|
+
each candidate additionally receives — at `file_agreement_weight` (0.4) — the RRF
|
|
128
|
+
mass of every retriever that found its file at a different locator. Retrievers
|
|
129
|
+
already counted at that locator are excluded, so nothing double-counts. Without
|
|
130
|
+
this, two retrievers agreeing on a file produced two weak candidates instead of one
|
|
131
|
+
strong one, and cross-source agreement never affected the score.
|
|
123
132
|
|
|
124
133
|
## 8. Reranking
|
|
125
134
|
|
|
@@ -132,8 +141,8 @@ After fusion, apply bounded explainable boosts and penalties:
|
|
|
132
141
|
| Path term match | +0.05 | User supplied a location clue |
|
|
133
142
|
| Degree / reference evidence | up to +0.08 | Stable structural tiebreaker |
|
|
134
143
|
| Implementation source prior | +0.08 | Prefer source over prose/tests |
|
|
135
|
-
| Documentation source prior | -0.
|
|
136
|
-
| Generated/vendor/build | -0.
|
|
144
|
+
| Documentation source prior | -0.20 | Prose describing a feature outmatches the code lexically |
|
|
145
|
+
| Generated/vendor/build | -0.25 | Suppress low-value derived code |
|
|
137
146
|
| Test path on non-test query | -0.06 | Keep tests as supporting evidence |
|
|
138
147
|
|
|
139
148
|
## 9. Confidence
|
|
@@ -30,7 +30,7 @@ if TYPE_CHECKING:
|
|
|
30
30
|
from ..config import Config
|
|
31
31
|
|
|
32
32
|
try:
|
|
33
|
-
from mcp.server.fastmcp import FastMCP
|
|
33
|
+
from mcp.server.fastmcp import FastMCP # type: ignore[attr-defined]
|
|
34
34
|
except ImportError as exc: # pragma: no cover
|
|
35
35
|
raise ImportError(
|
|
36
36
|
"MCP server needs the optional extra: pip install codebase-index[mcp]"
|
|
@@ -10,7 +10,9 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
import hashlib
|
|
12
12
|
import math
|
|
13
|
+
from collections import Counter
|
|
13
14
|
from collections.abc import Callable, Iterable, Sequence
|
|
15
|
+
from functools import lru_cache
|
|
14
16
|
|
|
15
17
|
from .types import Candidate
|
|
16
18
|
|
|
@@ -90,6 +92,18 @@ def normalize_code_tokens(content: str | None) -> tuple[str, ...]:
|
|
|
90
92
|
return tuple(tokens)
|
|
91
93
|
|
|
92
94
|
|
|
95
|
+
@lru_cache(maxsize=1 << 16)
|
|
96
|
+
def _token_digest(token: str) -> int:
|
|
97
|
+
"""64-bit blake2b digest of one token. Pure, so caching is safe and bounded.
|
|
98
|
+
|
|
99
|
+
Source chunks repeat identifiers and keywords heavily both inside a chunk and
|
|
100
|
+
across the candidate pool, and hashing dominated fingerprint cost.
|
|
101
|
+
"""
|
|
102
|
+
return int.from_bytes(
|
|
103
|
+
hashlib.blake2b(token.encode("utf-8"), digest_size=8).digest(), "big", signed=False
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
93
107
|
def token_fingerprint(tokens: Iterable[str] | str | None) -> int:
|
|
94
108
|
"""Compute a stable unsigned 64-bit SimHash for tokens or source content."""
|
|
95
109
|
if tokens is None:
|
|
@@ -100,12 +114,14 @@ def token_fingerprint(tokens: Iterable[str] | str | None) -> int:
|
|
|
100
114
|
if not values:
|
|
101
115
|
return 0
|
|
102
116
|
|
|
117
|
+
# Weighting distinct tokens by multiplicity is arithmetically identical to
|
|
118
|
+
# summing each occurrence, and collapses the per-occurrence hash and 64-bit
|
|
119
|
+
# accumulation loop to per-distinct-token work. Output is bit-for-bit unchanged.
|
|
103
120
|
weights = [0] * 64
|
|
104
|
-
for token in values:
|
|
105
|
-
|
|
106
|
-
hashed = int.from_bytes(digest, "big", signed=False)
|
|
121
|
+
for token, count in Counter(str(value) for value in values).items():
|
|
122
|
+
hashed = _token_digest(token)
|
|
107
123
|
for bit in range(64):
|
|
108
|
-
weights[bit] +=
|
|
124
|
+
weights[bit] += count if (hashed >> bit) & 1 else -count
|
|
109
125
|
|
|
110
126
|
fingerprint = 0
|
|
111
127
|
for bit, weight in enumerate(weights):
|
|
@@ -133,8 +149,14 @@ def _score_value(candidate: Candidate) -> float:
|
|
|
133
149
|
|
|
134
150
|
|
|
135
151
|
def _same_or_better(left: Candidate, right: Candidate) -> bool:
|
|
136
|
-
"""Whether left
|
|
137
|
-
|
|
152
|
+
"""Whether `left` (the incumbent) should stay the representative.
|
|
153
|
+
|
|
154
|
+
Ties favor the incumbent, i.e. input order. Candidates arrive sorted by score,
|
|
155
|
+
so a later duplicate is never strictly better; deciding ties by ">" instead
|
|
156
|
+
handed the slot to whichever equal-scoring copy happened to arrive last, making
|
|
157
|
+
the retained snippet depend on retriever emission order.
|
|
158
|
+
"""
|
|
159
|
+
return _score_value(left) >= _score_value(right)
|
|
138
160
|
|
|
139
161
|
|
|
140
162
|
def deduplicate(candidates: Sequence[Candidate], hamming_distance: int = 3) -> list[Candidate]:
|
|
@@ -146,14 +168,21 @@ def deduplicate(candidates: Sequence[Candidate], hamming_distance: int = 3) -> l
|
|
|
146
168
|
fingerprints: list[int | None] = []
|
|
147
169
|
|
|
148
170
|
for candidate in candidates:
|
|
149
|
-
|
|
171
|
+
# Only the fingerprint is needed here; building the token set as well
|
|
172
|
+
# doubled the per-candidate cost of the pipeline's hottest stage. "No
|
|
173
|
+
# fingerprint" means "no tokens", never "fingerprint happened to be 0".
|
|
174
|
+
tokens = normalize_code_tokens(candidate.content)
|
|
175
|
+
fingerprint = token_fingerprint(tokens) if tokens else None
|
|
150
176
|
if fingerprint is None:
|
|
151
177
|
representatives.append(candidate)
|
|
152
178
|
fingerprints.append(None)
|
|
153
179
|
continue
|
|
154
180
|
|
|
181
|
+
# Carry each match's fingerprint alongside its index: it is known non-None
|
|
182
|
+
# by construction here, and re-reading it from the list later would lose
|
|
183
|
+
# that guarantee.
|
|
155
184
|
matches = [
|
|
156
|
-
index
|
|
185
|
+
(index, existing)
|
|
157
186
|
for index, existing in enumerate(fingerprints)
|
|
158
187
|
if existing is not None and simhash_distance(fingerprint, existing) <= threshold
|
|
159
188
|
]
|
|
@@ -162,17 +191,23 @@ def deduplicate(candidates: Sequence[Candidate], hamming_distance: int = 3) -> l
|
|
|
162
191
|
fingerprints.append(fingerprint)
|
|
163
192
|
continue
|
|
164
193
|
|
|
165
|
-
|
|
194
|
+
indices = [index for index, _ in matches]
|
|
195
|
+
first = indices[0]
|
|
166
196
|
winner = candidate
|
|
167
|
-
|
|
197
|
+
# The winner is either this candidate or an incumbent, and both
|
|
198
|
+
# fingerprints are already known — recomputing one from content was pure
|
|
199
|
+
# duplicated work.
|
|
200
|
+
winner_fingerprint = fingerprint
|
|
201
|
+
for index, existing in matches:
|
|
168
202
|
incumbent = representatives[index]
|
|
169
203
|
if _same_or_better(incumbent, winner):
|
|
170
204
|
winner = incumbent
|
|
171
|
-
|
|
205
|
+
winner_fingerprint = existing
|
|
206
|
+
for index in reversed(indices):
|
|
172
207
|
representatives.pop(index)
|
|
173
208
|
fingerprints.pop(index)
|
|
174
209
|
representatives.insert(first, winner)
|
|
175
|
-
fingerprints.insert(first,
|
|
210
|
+
fingerprints.insert(first, winner_fingerprint)
|
|
176
211
|
|
|
177
212
|
return representatives
|
|
178
213
|
|
|
@@ -13,6 +13,14 @@ Two deliberate departures from the textbook formula:
|
|
|
13
13
|
retrievers report different line ranges for the same place; an exact key almost
|
|
14
14
|
never coincides across sources, so cross-source agreement — RRF's whole point —
|
|
15
15
|
would never fire. `agreeing_sources` is therefore counted at file granularity.
|
|
16
|
+
* Cross-locator agreement is scored, not merely counted. Bucketing alone does not
|
|
17
|
+
rescue agreement: a symbol defined at line 40 and a lexical hit at line 120 sit
|
|
18
|
+
in different buckets of the same file, so two retrievers pointing at one file
|
|
19
|
+
still fused as two unrelated candidates, each carrying a single retriever's
|
|
20
|
+
evidence. `file_agreement` adds the missing evidence back at a discount — a
|
|
21
|
+
candidate also receives, weighted by `file_agreement_weight`, the RRF mass of
|
|
22
|
+
every retriever that found its *file* somewhere else. Retrievers already
|
|
23
|
+
counted at the candidate's own locator are excluded, so nothing double-counts.
|
|
16
24
|
|
|
17
25
|
On merge, the candidate carrying the most signal (symbol > fts > path) is kept as
|
|
18
26
|
the representative so downstream rerank/snippet logic has the richest fields.
|
|
@@ -36,11 +44,21 @@ def fuse(
|
|
|
36
44
|
*,
|
|
37
45
|
weights: dict[str, float],
|
|
38
46
|
k: int,
|
|
47
|
+
file_agreement: float = 0.0,
|
|
39
48
|
) -> list[Candidate]:
|
|
49
|
+
"""Fuse per-source ranked lists into one ordered candidate list.
|
|
50
|
+
|
|
51
|
+
`file_agreement` is the discount applied to same-file, different-locator
|
|
52
|
+
evidence; 0.0 reproduces plain locator-only RRF.
|
|
53
|
+
"""
|
|
40
54
|
accum: dict[tuple, float] = {}
|
|
41
55
|
rep: dict[tuple, Candidate] = {}
|
|
42
56
|
seen: set[tuple] = set()
|
|
43
57
|
file_sources: dict[str, set[str]] = {}
|
|
58
|
+
# Best (lowest) rank each source achieved for each file, and which sources
|
|
59
|
+
# already contributed at each fused locator.
|
|
60
|
+
best_file_rank: dict[tuple[str, str], int] = {}
|
|
61
|
+
key_sources: dict[tuple, set[str]] = {}
|
|
44
62
|
|
|
45
63
|
for source, candidates in lists.items():
|
|
46
64
|
w = weights.get(source, 0.0)
|
|
@@ -48,7 +66,12 @@ def fuse(
|
|
|
48
66
|
continue
|
|
49
67
|
for rank, cand in enumerate(candidates):
|
|
50
68
|
file_sources.setdefault(cand.path, set()).add(source)
|
|
69
|
+
file_key = (source, cand.path)
|
|
70
|
+
previous = best_file_rank.get(file_key)
|
|
71
|
+
if previous is None or rank < previous:
|
|
72
|
+
best_file_rank[file_key] = rank
|
|
51
73
|
key = cand.fuse_key()
|
|
74
|
+
key_sources.setdefault(key, set()).add(source)
|
|
52
75
|
# One contribution per source per locator: a file matching three FTS
|
|
53
76
|
# chunks in the same bucket is one lexical signal, not three.
|
|
54
77
|
if (source, key) in seen:
|
|
@@ -57,6 +80,21 @@ def fuse(
|
|
|
57
80
|
accum[key] = accum.get(key, 0.0) + w * k / (k + rank)
|
|
58
81
|
rep[key] = _richer(rep[key], cand) if key in rep else cand
|
|
59
82
|
|
|
83
|
+
if file_agreement > 0.0:
|
|
84
|
+
for key, base in accum.items():
|
|
85
|
+
path = rep[key].path
|
|
86
|
+
own = key_sources[key]
|
|
87
|
+
extra = 0.0
|
|
88
|
+
for source in file_sources.get(path, ()):
|
|
89
|
+
if source in own:
|
|
90
|
+
continue
|
|
91
|
+
w = weights.get(source, 0.0)
|
|
92
|
+
if w <= 0.0:
|
|
93
|
+
continue
|
|
94
|
+
extra += w * k / (k + best_file_rank[(source, path)])
|
|
95
|
+
if extra:
|
|
96
|
+
accum[key] = base + file_agreement * extra
|
|
97
|
+
|
|
60
98
|
fused = [_replace(rep[key], score=score) for key, score in accum.items()]
|
|
61
99
|
fused.sort(key=lambda c: c.score, reverse=True)
|
|
62
100
|
return [_replace(c, agreeing_sources=len(file_sources[c.path])) for c in fused]
|