boost-skill-cli 1.0.322__tar.gz → 1.0.324__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.
- {boost_skill_cli-1.0.322/boost_skill_cli.egg-info → boost_skill_cli-1.0.324}/PKG-INFO +1 -1
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/_version.py +2 -2
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/rag.py +217 -19
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324/boost_skill_cli.egg-info}/PKG-INFO +1 -1
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_skill_cli.egg-info/SOURCES.txt +1 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/bm25-index-is-one-json-blob.md +32 -1
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/near-duplicate-items-eat-the-result-slots.md +37 -1
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap.html +69 -2
- boost_skill_cli-1.0.324/tests/unit/test_content_dedupe.py +144 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_rag.py +97 -12
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/.gitattributes +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/.gitignore +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/.gitleaks.toml +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/.htmlvalidate.json +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/.lighthouserc.json +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/.lycheeignore +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/.markdownlint-cli2.jsonc +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/.pre-commit-config.yaml +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/.stylelintrc.json +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/.vale/styles/boost/Terminology.yml +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/.vale/styles/config/vocabularies/boost/accept.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/.vale.ini +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/CLAUDE.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/CODE_OF_CONDUCT.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/CONTRIBUTING.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/LICENSE +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/MANIFEST.in +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/Makefile +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/README.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/SECURITY.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/__init__.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/__main__.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/cli.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/cliparse.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/commands/__init__.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/commands/_common.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/commands/bmad.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/commands/configuration.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/commands/discovery.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/commands/hooks.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/commands/info.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/commands/intelligence.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/commands/pkg.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/commands/quality.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/commands/run.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/commands/safety.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/commands/taps.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/commands/team.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/__init__.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/adapters.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/agents.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/ai.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/capabilities.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/catalog.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/claude_settings.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/config.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/dense.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/ed25519.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/embed.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/faithfulness.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/frontmatter.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/gitutil.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/imperative.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/injectscan.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/installscan.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/integrity.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/journal.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/localembed.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/lockfile.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/logs.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/mcp.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/mcpdecl.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/mcphost.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/minisign.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/nethttp.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/output.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/paths.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/policy.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/projectlock.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/provenance.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/registry.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/resolve.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/rules.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/scopes.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/secretscan.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/selfupdate.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/serve.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/stackprobe.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/staleness.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/store.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/trustaudit.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/typosquat.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/updatediff.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/util.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/core/workflows.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/data/registries.json +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/errors.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_cli/spin.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_skill_cli.egg-info/dependency_links.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_skill_cli.egg-info/entry_points.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_skill_cli.egg-info/requires.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/boost_skill_cli.egg-info/top_level.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/codecov.yml +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/.claude/settings.local.json +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/DEBUGGING.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/adapters.html +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/architecture/README.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/architecture/c4-components-core.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/architecture/c4-containers.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/architecture/c4-context.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/architecture/c4-dynamic-install.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/carousel/gifs/distill.gif +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/carousel/gifs/doctor.gif +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/carousel/gifs/explain.gif +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/carousel/gifs/hooks.gif +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/carousel/gifs/install.gif +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/carousel/gifs/pulse.gif +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/carousel/gifs/search.gif +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/carousel/gifs/tap.gif +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/carousel/tapes/distill.tape +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/carousel/tapes/doctor.tape +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/carousel/tapes/explain.tape +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/carousel/tapes/hooks.tape +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/carousel/tapes/install.tape +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/carousel/tapes/pulse.tape +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/carousel/tapes/search.tape +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/carousel/tapes/tap.tape +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/carousel.html +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/commands.html +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/demo.gif +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/demo.tape +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/design-roadmap.html +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/eval.html +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/index.html +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/mcp-hub.html +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/rag-architecture.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D01.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D02.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D03.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D04.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D05.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D06.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D07.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D08.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D09.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D10.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D11.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D12.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D13.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D14.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D15.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D16.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D17.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D18.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D19.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D20.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D21.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D22.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D23.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D24.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D25.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/BOOST-D26.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/accessibility-audit-pa11y-ci-axe-core.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/actionlint-skips-shellcheck-silently.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/adapter-conformance-langgraph-leg-never-ran.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/ai-bridge-silent-failure-logging.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/ambiguous-tap-short-name-resolution.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/atomic-corruption-safe-lock-file-writes.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/atomic-skill-install-temp-dir-swap.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/autonomous-ship-workflow-and-isolated-worktree.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/boost-run-live-agents.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/bring-commands-under-mutation-testing.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/broken-link-and-anchor-checking-lychee.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/browse-crashes-selecting-a-rule-or-workflow.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/build-provenance-slsa-attestations.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/cache-the-catalog-entry-set-across-rag-queries.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/capability-manifest-and-least-privilege-policy.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/ci-job-timeout-minutes.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/ci-summary-step-fails-itself.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/ci-time-is-now-runner-queueing.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/clean-env-install-smoke-pip-and-pipx.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/code-scanning-rule-can-be-restored.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/codeql-job-rename-stranded-merge-protection.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/command-carousel-page.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/command-reference-docs-site.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/complexity-and-dead-code-radar-xenon-vulture.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/consolidate-skill-staleness-drift-logic-into-cor.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/coverage-dashboard-codecov-free-for-oss.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/coverage-guided-fuzzing-atheris-oss-fuzz.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/crash-listing-branch-untested.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/crash-recorder-error-paths-core-logs-py.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/demo-cannot-open-its-own-pr.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/demo-gif-workflow-has-never-succeeded.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/dense-search-fallback-and-stale-tap-pruning.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/dependabot-missing-pip-ecosystem.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/dependabot-regeneration-drops-platform-pins.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/dependabot-root-pip-entry-duplicates-requirements.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/dependency-cve-gate-pip-audit.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/docsite-chrome-and-content-audit.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/docstring-coverage-interrogate.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/doctor-checks-rules-and-workflows.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/end-of-options-guard-on-git-commands.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/eval-corpus-cannot-see-real-retrieval.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/extension-free-tests-core-dense-py.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/extract-mcp-http-servers-out-of-configuration-py.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/finish-mutation-hardening-across-core.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/fix-self-update-version-detection-dead-branch.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/fork-safe-network-proxy-handler.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/framework-adapter-langgraph.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/framework-adapter-multi-agent.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/framework-adapters-boost-adapt.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/frontmatter-scalar-over-coercion.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/gemini-cli-agent-target.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/github-community-health-files.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/github-pages-deploy-is-broken-every-push.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/golden-set-statistical-power.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/hash-pinned-reproducible-toolchain-uv-lock.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/html-validation-html-validate.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/install-docs-never-mentioned-upgrading.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/install-from-path-bypasses-policy-and-pin-checks.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/install-resolves-skill-dependencies.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/install-scope-user-or-project.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/installed-skill-trust-audit.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/integrity-verification-boost-verify.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/internal-architecture-diagrams.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/invocation-pid-logging-for-crash-correlation.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/journal-rotation-race-and-handle-leak.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/keyless-dense-tier-local-static-embeddings.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/keyless-semantic-search-for-everyone.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/layering-guard-import-linter.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/license-compliance-scanning.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/lighthouse-ci-on-the-pages-site.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/lint-tap-misscopes-rule-workflow-entries.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/list-rules-and-workflows.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/localize-the-stored-bm25-snippet.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/lock-invariant-cannot-parse-extras-pins.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/lockfile-enforcement-and-commit-pinning.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/log-timestamps-mislabeled-utc.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/lowest-version-resolution-uv-resolution-lowest-d.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/main-has-no-branch-protection.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/main-ruleset-ref-pattern-has-literal-quotes.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/make-lint-masks-actionlint-failures.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/make-the-roadmaps-discoverable.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/markdown-consistency-markdownlint-cli2.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/markdownlint-lints-the-fuzz-corpus.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/mcp-aware-skills.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/mcp-check-skills-before-starting-a-task.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/mcp-hub-diagram-node-text-overflow.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/mcp-install-skips-the-injection-scan.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/mcp-launch-objc-fork-safety.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/mcp-one-benefit-nameable-task.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/mcp-register-names-server-before-env.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/mcp-search-before-reinventing.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/mcp-servers-ignore-install-scope.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/mcp-startup-self-harden-fork-safety.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/memoize-config-load-in-process.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/merge-queue-would-deadlock-most-required-checks.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/merged-loop-branches-are-never-pruned.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/modernization-smells-refurb-pyupgrade.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/mutation-gate-was-the-whole-critical-path.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/mutation-hardening-core-frontmatter-py.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/mutation-hardening-core-gitutil-py.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/mutation-hardening-core-store-py.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/mutation-shard-floor-is-one-file.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/mypy-strict-mode-for-commands.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/negative-limit-inverts-log-pulse-output.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/onboard-overwrites-generated-files-without-confirm.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/one-command-every-env-nox.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/one-commit-can-cut-two-releases.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/one-shared-atomic-write-helper.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/package-metadata-validation-twine-check-friends.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/patch-coverage-gate-diff-cover.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/path-traversal-unsanitized-rule-workflow-name.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/performance-regression-gate-pytest-benchmark.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/pin-the-lint-toolchain.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/post-deploy-always-defeated-by-skipped-setup.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/post-deploy-smoke-headless-load-check.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/pre-release-python-canary-3-14t-free-threaded.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/prerequisites-and-semantic-search-setup.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/project-scope-across-every-command.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/project-scope-symlink-escape-on-write.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/promote-nav-footer-into-the-shared-style-system.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/prompt-injection-scanning-of-skill-markdown.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/property-based-tests-hypothesis-on-the-parsers.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/prose-and-terminology-linting-vale.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/prune-ignored-dirs-during-scan-dir-walk.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/publish-gate-ignores-pip-audit-and-metadata.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/publish-trigger-was-reachable-from-a-fork.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/pytest-tmpdir-cve-blocked-by-py39-floor.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/quality-dashboard-sonarcloud-free-for-oss.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/ranx-significance-monitor.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/reconcile-the-theme-drift.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/refresh-the-marketing-surface.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/rel-time-tests-race-the-wall-clock.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/release-verifies-the-wrong-commit.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/required-checks-can-declare-a-check-that-deadlocks-prs.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/required-checks-config-drift.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/retrieval-quality-eval-harness.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/reuse-helpers-kill-minor-dead-work.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/roadmap-html-goes-stale-on-every-rebase.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/robust-tag-argument-parsing.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/ruff-016-widens-the-default-rule-set.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/rule-install-native-materialization.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/runner-egress-monitoring-stepsecurity-harden-run.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/runtime-explain-faithfulness-guardrail.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/sbom-aware-scanning-osv-scanner.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/sbom-declares-the-wrong-version.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/sbom-on-every-release-cyclonedx-syft.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/sbom-release-event-never-fires.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/scan-and-sync-rules-and-workflows.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/scheduled-toolchain-lock-regeneration.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/scorecard-findings-triage.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/second-type-checker-pyright.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/secret-and-pii-scanning-of-installed-skills.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/secret-scanning-gitleaks-push-protection.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/security-linting-bandit-via-ruff-s-rules.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/self-update-broken-for-pip-pipx-installs.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/serve-py-traversal-guards-untested.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/shift-left-gate-pre-commit-pre-commit-ci.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/single-imperative-rule-extractor.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/single-tech-stack-prober.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/split-oversized-command-modules.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/startup-and-import-time-budget-x-importtime.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/stop-re-serializing-entry-meta-on-every-search.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/structured-json-log-output-mode.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/supply-chain-posture-openssf-scorecard.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/surface-every-docs-page-from-the-guide.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/sync-deletes-unowned-broken-symlinks.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/sync-relinks-into-every-agent-ignoring-scope.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/tap-signing-and-provenance-sigstore-minisign.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/theme-asset-linting-stylelint-eslint.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/typo-detection-codespell.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/typosquat-and-name-confusion-detection.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/unify-tilde-two-copies-have-a-boundary-bug.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/uninstall-has-no-confirmation-prompt.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/untrack-generated-build-noise.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/update-diff-before-apply.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/update-refreshes-rules-and-workflows.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/update-reinstall-widens-agent-scope.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/visual-regression-pass-on-the-guide.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/widen-the-ruff-rule-surface-b-sim-c4-perf-ruf.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/windows-in-the-ci-matrix.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/wire-bdd-suite-into-ci.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/workflow-install-native-materialization.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/workflow-linting-actionlint.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/workflow-sast-zizmor.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/workspace-scope-install.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/docs/roadmap/items/zizmor-pin-is-a-yanked-release.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/eslint.config.mjs +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/evals/README.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/evals/__init__.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/evals/baseline.json +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/evals/faithfulness.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/evals/golden_set.json +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/evals/make_corpus.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/evals/make_golden.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/evals/metrics.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/evals/run_evals.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/examples/README.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/examples/adapt-demo.sh +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/examples/boost-run-prototype.sh +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/index.html +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/noxfile.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/pyproject.toml +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/requirements/coverage-tools.in +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/requirements/coverage-tools.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/requirements/lint-tools.in +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/requirements/lint-tools.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/requirements/mutation-tools.in +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/requirements/mutation-tools.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/requirements/platform-pins.lock +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/requirements/release-tools.in +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/requirements/release-tools.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/requirements/test-tools.in +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/requirements/test-tools.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/a11y_check.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/bench_cli.sh +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/build_command_reference.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/build_registries.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/build_roadmap.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/check_anchors.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/check_licenses.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/check_required_checks.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/ensure_eval_corpus.sh +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/eval_explain.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/eval_gate.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/eval_recommend.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/eval_retrieval.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/eval_stats_summary.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/import_budget.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/lock_toolchain.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/mutation_gate.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/mutation_shards.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/mutation_weights.json +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/perf_gate.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/post_deploy_smoke.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/release_guard.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/scripts/release_preflight.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/setup.cfg +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/sonar-project.properties +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/style/README.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/style/boost.css +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/style/boost.js +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/style/demo.html +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/bdd/features/adapt.feature +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/bdd/features/browse.feature +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/bdd/features/count.feature +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/bdd/features/discover.feature +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/bdd/features/doctor.feature +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/bdd/features/environment.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/bdd/features/explain.feature +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/bdd/features/index.feature +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/bdd/features/install.feature +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/bdd/features/list.feature +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/bdd/features/mcp.feature +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/bdd/features/search.feature +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/bdd/features/steps/cli_steps.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/bdd/features/steps/discovery_steps.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/bdd/features/steps/mcp_steps.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/bdd/features/steps/quality_steps.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/conftest.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/eval/baseline.json +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/eval/explain.jsonl +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/eval/golden-natural.jsonl +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/eval/golden.jsonl +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/eval/recommend.jsonl +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/eval/taps.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/functional/test_capabilities_policy.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/functional/test_cli_adapt.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/functional/test_cli_bmad.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/functional/test_cli_configuration.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/functional/test_cli_discovery.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/functional/test_cli_hooks.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/functional/test_cli_info.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/functional/test_cli_intelligence.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/functional/test_cli_pkg.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/functional/test_cli_quality.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/functional/test_cli_taps.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/functional/test_cli_team.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/functional/test_everyday_loop.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/functional/test_integrity_enforce.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/functional/test_property_parsers.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/functional/test_run.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/functional/test_tap_signing.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/functional/test_workspace_scope.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/README.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/frontmatter/01-typical.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/frontmatter/02-lossy-version.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/frontmatter/03-lossy-numbers.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/frontmatter/04-lists.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/frontmatter/05-nested.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/frontmatter/06-empty-block.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/frontmatter/07-unterminated.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/frontmatter/08-none.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/frontmatter/09-unbalanced-quote.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/frontmatter/10-keywords.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/frontmatter/11-crlf.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/frontmatter/12-bom.md +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/registry/01-shorthand.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/registry/02-https.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/registry/03-ssh.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/registry/04-traversal.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/registry/05-empty.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/registry/06-space.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/registry/07-slash.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/corpus/registry/08-bare-host.txt +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/fuzz_frontmatter.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/fuzz/fuzz_registry.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/make_fixture.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/perf/test_benchmarks.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/smoke.sh +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_a11y_check.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_adapters.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_adapters_multi.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_adapters_runner.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_agents.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_ai.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_capabilities.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_catalog.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_check_anchors.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_check_licenses.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_claude_settings.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_cliparse.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_codeql_analysis_key.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_command_reference_fresh.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_community_health_files.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_config.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_dense.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_dense_fallback.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_dense_fix_hint.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_dense_status.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_dependabot_config.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_docs_pages_linked.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_docsite_chrome.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_ed25519.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_embed.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_embed_local.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_entry_key.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_errors_and_cli_table.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_eval_baseline.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_eval_faithfulness.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_eval_gate.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_eval_metrics.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_eval_stats_summary.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_faithfulness.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_frontmatter.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_fuzz_targets.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_gitleaks_config.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_gitutil.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_imperative.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_import_budget.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_injectscan.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_installscan.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_integrity.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_journal.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_localembed.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_lockfile.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_logs.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_marketing_counts.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_mcp.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_mcp_install_scan.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_mcpdecl.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_mcphost.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_minisign.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_mutation_hardening.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_mutation_shards.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_mutation_subfile_shards.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_mutmut_source_paths.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_nethttp.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_output.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_paths.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_perf_gate.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_pkg_confusions.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_pkg_injection.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_pkg_secrets.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_platform_pins.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_policy.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_post_deploy_smoke.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_project_mcp_sidecar.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_projectlock.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_provenance.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_rag_fusion.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_registries_fresh.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_registry.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_registry_categories.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_release_guard.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_release_preflight.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_required_checks.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_resolve.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_roadmap_fresh.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_rules.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_scopes.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_secretscan.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_selfupdate.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_serve.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_spin.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_stackprobe.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_staleness.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_store.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_token_parity.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_toolchain_lock.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_trustaudit.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_typosquat.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_updatediff.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_util.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_version.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_workflow_concurrency.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_workflow_timeouts.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/unit/test_workflows.py +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/visual/a11y_check.mjs +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/visual/console_check.mjs +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/visual/package-lock.json +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/visual/package.json +0 -0
- {boost_skill_cli-1.0.322 → boost_skill_cli-1.0.324}/tests/visual/visual_check.mjs +0 -0
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.0.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 0,
|
|
21
|
+
__version__ = version = '1.0.324'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 0, 324)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -15,13 +15,16 @@ no AI -> the BM25 order is returned as-is.
|
|
|
15
15
|
"""
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
+
import contextlib
|
|
19
|
+
import hashlib
|
|
18
20
|
import json
|
|
19
21
|
import math
|
|
20
22
|
import re
|
|
23
|
+
import sqlite3
|
|
21
24
|
from collections import defaultdict
|
|
22
25
|
from collections.abc import Sequence
|
|
23
26
|
from pathlib import Path
|
|
24
|
-
from typing import Dict, List, Optional, Tuple
|
|
27
|
+
from typing import Dict, List, Optional, Tuple, cast
|
|
25
28
|
|
|
26
29
|
try: # TypedDict lives in typing on 3.9+, kept optional for safety
|
|
27
30
|
from typing import TypedDict
|
|
@@ -33,7 +36,7 @@ from . import ai, catalog, frontmatter, gitutil, paths, registry, util
|
|
|
33
36
|
# v2: `snip` stores a larger head of the matched chunk (not just SNIP_WIDTH
|
|
34
37
|
# chars) so `retrieve` can window it onto the query terms; bump forces a
|
|
35
38
|
# one-time reindex.
|
|
36
|
-
INDEX_VERSION =
|
|
39
|
+
INDEX_VERSION = 6
|
|
37
40
|
ENGINE = "bm25"
|
|
38
41
|
|
|
39
42
|
# Chunking defaults (documented in docs/rag-architecture.md §4).
|
|
@@ -62,10 +65,13 @@ _STOPWORDS = frozenset({
|
|
|
62
65
|
})
|
|
63
66
|
|
|
64
67
|
|
|
65
|
-
class Hit(TypedDict): # type: ignore[misc]
|
|
68
|
+
class Hit(TypedDict, total=False): # type: ignore[misc]
|
|
66
69
|
entry: dict # the live catalog entry (name, tap, kind, skill_md, ...)
|
|
67
70
|
score: float # BM25 relevance (higher = better)
|
|
68
71
|
snippet: str # best-matching passage, for display + rerank context
|
|
72
|
+
content: str # digest of the body, for collapsing byte-identical copies.
|
|
73
|
+
# Optional: dense hits arrive without one and have it
|
|
74
|
+
# attached from the BM25 index (see `_with_content`).
|
|
69
75
|
|
|
70
76
|
|
|
71
77
|
# --------------------------------------------------------------- tokenizing
|
|
@@ -193,13 +199,16 @@ def _make_docs(entries: List[dict], tap_paths: Dict[str, Path]) -> List[dict]:
|
|
|
193
199
|
docs: List[dict] = []
|
|
194
200
|
for e in entries:
|
|
195
201
|
body = read_body(e, tap_paths)
|
|
202
|
+
# Hashed here because the body is already in hand; doing it at query
|
|
203
|
+
# time would mean re-reading ~30k files to answer one search.
|
|
204
|
+
digest = hashlib.sha256(body.strip().encode("utf-8", "replace")).hexdigest()[:16]
|
|
196
205
|
tf: Dict[str, int] = defaultdict(int)
|
|
197
206
|
for tok in tokenize(surface(e) + "\n" + body):
|
|
198
207
|
tf[tok] += 1
|
|
199
208
|
if not tf:
|
|
200
209
|
continue
|
|
201
210
|
docs.append({
|
|
202
|
-
"n": e["name"], "t": e["tap"], "f": e["skill_md"],
|
|
211
|
+
"n": e["name"], "t": e["tap"], "f": e["skill_md"], "h": digest,
|
|
203
212
|
"k": e.get("kind", "skill"),
|
|
204
213
|
"c": 0, "l": sum(tf.values()),
|
|
205
214
|
"snip": body[:SNIP_STORE].strip(), # windowed at retrieve time
|
|
@@ -211,7 +220,7 @@ def _make_docs(entries: List[dict], tap_paths: Dict[str, Path]) -> List[dict]:
|
|
|
211
220
|
def _postings_to_doc_tf(raw: dict) -> Dict[int, Dict[str, int]]:
|
|
212
221
|
"""Invert persisted postings back to per-doc term frequencies."""
|
|
213
222
|
doc_tf: Dict[int, Dict[str, int]] = defaultdict(dict)
|
|
214
|
-
for term, plist in raw.get("postings"
|
|
223
|
+
for term, plist in (raw.get("postings") or _all_postings()).items():
|
|
215
224
|
for doc_id, tf in plist:
|
|
216
225
|
doc_tf[doc_id][term] = tf
|
|
217
226
|
return doc_tf
|
|
@@ -259,6 +268,94 @@ def build(entries: Optional[List[dict]] = None, force: bool = False) -> dict:
|
|
|
259
268
|
}
|
|
260
269
|
|
|
261
270
|
|
|
271
|
+
def postings_path() -> Path:
|
|
272
|
+
return paths.cache_dir() / "rag_postings.sqlite"
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _write_postings(postings: Dict[str, List[List[int]]]) -> None:
|
|
276
|
+
"""Persist the term -> [(doc, tf)] map to SQLite, replacing any existing one.
|
|
277
|
+
|
|
278
|
+
Postings were the largest part of the JSON index — measured 64% of it after
|
|
279
|
+
unchunking — and `json.loads` had to materialise every one of them to answer
|
|
280
|
+
a query that touches a handful of terms. On a real 83-tap install that was
|
|
281
|
+
8-13 s and multiple GB resident per cold `boost search`, against 31-70 ms of
|
|
282
|
+
actual scoring.
|
|
283
|
+
|
|
284
|
+
SQLite is used purely as an indexed key-value store here, not as a search
|
|
285
|
+
engine: BM25 scoring stays in `_bm25` byte for byte, so this changes cold
|
|
286
|
+
start cost and nothing about which results come back.
|
|
287
|
+
"""
|
|
288
|
+
paths.ensure_dirs()
|
|
289
|
+
final = postings_path()
|
|
290
|
+
tmp = final.with_suffix(".sqlite.tmp")
|
|
291
|
+
with contextlib.suppress(OSError):
|
|
292
|
+
tmp.unlink()
|
|
293
|
+
con = sqlite3.connect(str(tmp))
|
|
294
|
+
try:
|
|
295
|
+
# No durability needed: this file is a derived cache, and a crash
|
|
296
|
+
# mid-build leaves the .tmp behind rather than a torn index.
|
|
297
|
+
con.execute("PRAGMA journal_mode=OFF")
|
|
298
|
+
con.execute("PRAGMA synchronous=OFF")
|
|
299
|
+
con.execute("CREATE TABLE postings (term TEXT, doc INTEGER, tf INTEGER)")
|
|
300
|
+
con.executemany(
|
|
301
|
+
"INSERT INTO postings (term, doc, tf) VALUES (?, ?, ?)",
|
|
302
|
+
((term, doc, tf) for term, plist in postings.items()
|
|
303
|
+
for doc, tf in plist))
|
|
304
|
+
# Built after the insert: maintaining it per-row is far slower.
|
|
305
|
+
con.execute("CREATE INDEX postings_term ON postings(term)")
|
|
306
|
+
con.commit()
|
|
307
|
+
finally:
|
|
308
|
+
con.close()
|
|
309
|
+
tmp.replace(final) # atomic swap, same as the JSON half
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def read_postings(terms: Sequence[str]) -> Dict[str, List[List[int]]]:
|
|
313
|
+
"""Postings for just these terms — the whole point of the SQLite store.
|
|
314
|
+
|
|
315
|
+
A query touches a handful of terms out of tens of thousands, so this reads
|
|
316
|
+
kilobytes where the JSON index read the entire file.
|
|
317
|
+
"""
|
|
318
|
+
uniq = sorted(set(terms))
|
|
319
|
+
if not uniq:
|
|
320
|
+
return {}
|
|
321
|
+
try:
|
|
322
|
+
con = sqlite3.connect("file:%s?mode=ro" % postings_path(), uri=True)
|
|
323
|
+
except sqlite3.Error:
|
|
324
|
+
return {}
|
|
325
|
+
out: Dict[str, List[List[int]]] = defaultdict(list)
|
|
326
|
+
try:
|
|
327
|
+
# Chunked: SQLite caps host parameters (999 on older builds), and a
|
|
328
|
+
# long query can exceed it.
|
|
329
|
+
for i in range(0, len(uniq), 500):
|
|
330
|
+
batch = uniq[i:i + 500]
|
|
331
|
+
q = ("SELECT term, doc, tf FROM postings WHERE term IN (%s)" # noqa: S608 interpolates only `?` placeholders; terms are bound params
|
|
332
|
+
% ",".join("?" * len(batch)))
|
|
333
|
+
for term, doc, tf in con.execute(q, batch):
|
|
334
|
+
out[term].append([doc, tf])
|
|
335
|
+
except sqlite3.Error:
|
|
336
|
+
return {}
|
|
337
|
+
finally:
|
|
338
|
+
con.close()
|
|
339
|
+
return dict(out) # noqa: FURB123 out is a defaultdict; .copy() would keep the factory
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _all_postings() -> Dict[str, List[List[int]]]:
|
|
343
|
+
"""Every posting — only the incremental rebuild path needs this."""
|
|
344
|
+
try:
|
|
345
|
+
con = sqlite3.connect("file:%s?mode=ro" % postings_path(), uri=True)
|
|
346
|
+
except sqlite3.Error:
|
|
347
|
+
return {}
|
|
348
|
+
out: Dict[str, List[List[int]]] = defaultdict(list)
|
|
349
|
+
try:
|
|
350
|
+
for term, doc, tf in con.execute("SELECT term, doc, tf FROM postings"):
|
|
351
|
+
out[term].append([doc, tf])
|
|
352
|
+
except sqlite3.Error:
|
|
353
|
+
return {}
|
|
354
|
+
finally:
|
|
355
|
+
con.close()
|
|
356
|
+
return dict(out) # noqa: FURB123 out is a defaultdict; .copy() would keep the factory
|
|
357
|
+
|
|
358
|
+
|
|
262
359
|
def _save(docs: List[dict], commits: Dict[str, str]) -> None:
|
|
263
360
|
postings: Dict[str, List[List[int]]] = defaultdict(list)
|
|
264
361
|
meta_docs: List[dict] = []
|
|
@@ -268,6 +365,7 @@ def _save(docs: List[dict], commits: Dict[str, str]) -> None:
|
|
|
268
365
|
postings[term].append([doc_id, tf])
|
|
269
366
|
total_len += d["l"]
|
|
270
367
|
meta_docs.append({"n": d["n"], "t": d["t"], "f": d["f"], "k": d["k"],
|
|
368
|
+
"h": d.get("h", ""),
|
|
271
369
|
"c": d["c"], "l": d["l"], "snip": d["snip"]})
|
|
272
370
|
payload = {
|
|
273
371
|
"version": INDEX_VERSION,
|
|
@@ -279,12 +377,12 @@ def _save(docs: List[dict], commits: Dict[str, str]) -> None:
|
|
|
279
377
|
"stats": {"docs": len(docs),
|
|
280
378
|
"avg_len": (total_len / len(docs)) if docs else 0.0},
|
|
281
379
|
"docs": meta_docs,
|
|
282
|
-
"postings": dict(postings.items()),
|
|
283
380
|
}
|
|
284
381
|
paths.ensure_dirs()
|
|
285
382
|
p = index_path()
|
|
286
383
|
# Atomic swap: a bare write_text here can leave a truncated/corrupt index
|
|
287
384
|
# on disk if the process dies mid-write or a query reads concurrently.
|
|
385
|
+
_write_postings(postings)
|
|
288
386
|
util.atomic_write_text(p, json.dumps(payload))
|
|
289
387
|
# Drop the mtime-keyed cache so a reindex is visible to an immediately
|
|
290
388
|
# following query even when the filesystem mtime granularity is coarse.
|
|
@@ -322,9 +420,14 @@ def _load_raw() -> Optional[dict]:
|
|
|
322
420
|
|
|
323
421
|
|
|
324
422
|
def ready() -> bool:
|
|
325
|
-
"""True when a usable index exists on disk.
|
|
423
|
+
"""True when a usable index exists on disk.
|
|
424
|
+
|
|
425
|
+
Both halves are required: the JSON carries the documents, the SQLite file
|
|
426
|
+
carries the postings, and scoring needs both. A docs-only index reports not
|
|
427
|
+
ready rather than silently returning zero hits for every query.
|
|
428
|
+
"""
|
|
326
429
|
raw = _load_raw()
|
|
327
|
-
return bool(raw and raw.get("docs"))
|
|
430
|
+
return bool(raw and raw.get("docs") and postings_path().exists())
|
|
328
431
|
|
|
329
432
|
|
|
330
433
|
def ensure() -> bool:
|
|
@@ -346,11 +449,17 @@ def ensure() -> bool:
|
|
|
346
449
|
return ready()
|
|
347
450
|
|
|
348
451
|
|
|
349
|
-
def _bm25(query_terms: List[str], raw: dict
|
|
452
|
+
def _bm25(query_terms: List[str], raw: dict,
|
|
453
|
+
postings: Dict[str, List[List[int]]]) -> Dict[int, float]:
|
|
454
|
+
"""Score documents for ``query_terms``. Pure: postings are passed in.
|
|
455
|
+
|
|
456
|
+
Reading them inside would hide I/O in the one function whose arithmetic is
|
|
457
|
+
pinned exactly (`TestBm25Math`), and would tie the scorer to where postings
|
|
458
|
+
happen to live — which is the thing this change moved.
|
|
459
|
+
"""
|
|
350
460
|
docs = raw["docs"]
|
|
351
461
|
n = len(docs)
|
|
352
462
|
avg = raw.get("stats", {}).get("avg_len") or 1.0
|
|
353
|
-
postings = raw["postings"]
|
|
354
463
|
scores: Dict[int, float] = defaultdict(float)
|
|
355
464
|
for term in set(query_terms):
|
|
356
465
|
plist = postings.get(term)
|
|
@@ -418,6 +527,54 @@ def entry_key(entry: dict) -> Tuple[str, str]:
|
|
|
418
527
|
return (entry["tap"], entry["skill_md"])
|
|
419
528
|
|
|
420
529
|
|
|
530
|
+
def dedupe_by_content(hits: List[Hit], limit: int) -> List[Hit]:
|
|
531
|
+
"""Collapse byte-identical copies, then take ``limit``.
|
|
532
|
+
|
|
533
|
+
Measured over 77 tapped registries: 78.3% of 29,938 entries are
|
|
534
|
+
byte-identical duplicates (largest cluster 40 copies), and the
|
|
535
|
+
natural-language query set averaged 4.94 of 10 result slots consumed by a
|
|
536
|
+
repeat — worst case 9 of 10. Registries mirror each other, so N copies of one
|
|
537
|
+
rule across N taps ate N of the ten slots a user sees.
|
|
538
|
+
|
|
539
|
+
Clustering is on the **body**, never the name. 82.7% of entries share a name
|
|
540
|
+
but only 78.3% share a body, and that ~4.4% gap is real distinct content —
|
|
541
|
+
the ``admin-interface-rule`` pair fixed in #366 is two different rules under
|
|
542
|
+
one name. Clustering on content cannot make that mistake: of 14,153 distinct
|
|
543
|
+
bodies, the number of clusters spanning more than one name is zero.
|
|
544
|
+
|
|
545
|
+
Within a cluster every copy is byte-identical, so *which* one survives is
|
|
546
|
+
purely about where the user would rather install from: a ``curated`` tap wins
|
|
547
|
+
over a better-ranked uncurated one. The cluster keeps its best score either
|
|
548
|
+
way, so promoting the trusted copy never demotes the result.
|
|
549
|
+
|
|
550
|
+
A hit with no recorded content hash is always kept — two unknowns must not
|
|
551
|
+
collapse into each other, which would hide real results.
|
|
552
|
+
"""
|
|
553
|
+
best: Dict[str, int] = {} # content hash -> index into `out`
|
|
554
|
+
out: List[Hit] = []
|
|
555
|
+
for hit in hits:
|
|
556
|
+
digest = hit.get("content")
|
|
557
|
+
if not digest:
|
|
558
|
+
out.append(hit) # unknown content is never a match
|
|
559
|
+
continue
|
|
560
|
+
seen = best.get(digest)
|
|
561
|
+
if seen is None:
|
|
562
|
+
best[digest] = len(out)
|
|
563
|
+
out.append(hit)
|
|
564
|
+
continue
|
|
565
|
+
# Same body already shown. Keep its rank, but prefer a curated source.
|
|
566
|
+
kept = out[seen]
|
|
567
|
+
if hit["entry"].get("curated") and not kept["entry"].get("curated"):
|
|
568
|
+
# cast, not a literal annotation: `Hit` is total=False for the
|
|
569
|
+
# optional `content` key, and NotRequired needs 3.11 while boost
|
|
570
|
+
# targets 3.9.
|
|
571
|
+
out[seen] = cast(Hit, {"entry": hit["entry"],
|
|
572
|
+
"score": kept["score"],
|
|
573
|
+
"snippet": kept["snippet"],
|
|
574
|
+
"content": digest})
|
|
575
|
+
return out[:limit]
|
|
576
|
+
|
|
577
|
+
|
|
421
578
|
def retrieve(query: str, k: int = 60, kind: Optional[str] = None,
|
|
422
579
|
entries: Optional[List[dict]] = None) -> List[Hit]:
|
|
423
580
|
"""Top-k full-content hits for ``query``. ``[]`` if the index is empty."""
|
|
@@ -430,8 +587,8 @@ def retrieve(query: str, k: int = 60, kind: Optional[str] = None,
|
|
|
430
587
|
entries = catalog.all_entries() if entries is None else entries
|
|
431
588
|
live = {entry_key(e): e for e in entries}
|
|
432
589
|
docs = raw["docs"]
|
|
433
|
-
best: Dict[Tuple[str, str], Tuple[float, str]] = {}
|
|
434
|
-
for doc_id, score in _bm25(terms, raw).items():
|
|
590
|
+
best: Dict[Tuple[str, str], Tuple[float, str, str]] = {}
|
|
591
|
+
for doc_id, score in _bm25(terms, raw, read_postings(terms)).items():
|
|
435
592
|
d = docs[doc_id]
|
|
436
593
|
if kind is not None and d["k"] != kind:
|
|
437
594
|
continue
|
|
@@ -440,17 +597,20 @@ def retrieve(query: str, k: int = 60, kind: Optional[str] = None,
|
|
|
440
597
|
continue
|
|
441
598
|
prev = best.get(key)
|
|
442
599
|
if prev is None or score > prev[0]:
|
|
443
|
-
best[key] = (score, d["snip"])
|
|
600
|
+
best[key] = (score, d["snip"], d.get("h", ""))
|
|
444
601
|
# Tie-break on the displayed name, not the identity key: the key is now
|
|
445
602
|
# (tap, path), and ordering by it would silently reshuffle equal-scoring
|
|
446
603
|
# results relative to every previous release.
|
|
447
604
|
ranked = sorted(best.items(),
|
|
448
605
|
key=lambda kv: (-kv[1][0], live[kv[0]]["name"], kv[0]))
|
|
606
|
+
# Over-fetch, then collapse byte-identical copies before taking k — the
|
|
607
|
+
# duplicates have to be removed from the pool, not from the visible page,
|
|
608
|
+
# or they still consume the slots they were removed from.
|
|
449
609
|
hits: List[Hit] = [
|
|
450
|
-
{"entry": live[key], "score": score,
|
|
610
|
+
{"entry": live[key], "score": score, "content": digest,
|
|
451
611
|
"snippet": _passage(snip, terms)} # type: ignore[typeddict-item]
|
|
452
|
-
for key, (score, snip) in ranked
|
|
453
|
-
return hits
|
|
612
|
+
for key, (score, snip, digest) in ranked]
|
|
613
|
+
return dedupe_by_content(hits, k)
|
|
454
614
|
|
|
455
615
|
|
|
456
616
|
def rerank(query: str, hits: List[Hit], limit: int = 10) -> Tuple[List[Hit], str]:
|
|
@@ -516,6 +676,39 @@ def rrf_fuse(rankings: List[List[Hit]], limit: int = 60) -> List[Hit]:
|
|
|
516
676
|
return fused
|
|
517
677
|
|
|
518
678
|
|
|
679
|
+
def content_hashes() -> Dict[Tuple[str, str], str]:
|
|
680
|
+
"""``entry_key`` -> content hash, read from the BM25 index.
|
|
681
|
+
|
|
682
|
+
The dense store does not carry the hash and does not need to: BM25 indexes
|
|
683
|
+
the same entries and auto-builds on first search, so one map serves every
|
|
684
|
+
engine. Keeping it in one place also means the two engines can never
|
|
685
|
+
disagree about which copies are identical.
|
|
686
|
+
"""
|
|
687
|
+
raw = _load_raw()
|
|
688
|
+
if not raw:
|
|
689
|
+
return {}
|
|
690
|
+
return {(d["t"], d["f"]): d["h"]
|
|
691
|
+
for d in raw.get("docs", []) if d.get("h")}
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
def _with_content(hits: List[Hit]) -> List[Hit]:
|
|
695
|
+
"""Attach content hashes to hits that lack them (dense, and fused output)."""
|
|
696
|
+
if all(h.get("content") for h in hits):
|
|
697
|
+
return hits
|
|
698
|
+
hashes = content_hashes()
|
|
699
|
+
if not hashes:
|
|
700
|
+
return hits
|
|
701
|
+
filled: List[Hit] = []
|
|
702
|
+
for h in hits:
|
|
703
|
+
if h.get("content"):
|
|
704
|
+
filled.append(h)
|
|
705
|
+
continue
|
|
706
|
+
filled.append(cast(Hit, {
|
|
707
|
+
"entry": h["entry"], "score": h["score"], "snippet": h["snippet"],
|
|
708
|
+
"content": hashes.get(entry_key(h["entry"]), "")}))
|
|
709
|
+
return filled
|
|
710
|
+
|
|
711
|
+
|
|
519
712
|
def retrieve_any(query: str, k: int = 60, kind: Optional[str] = None,
|
|
520
713
|
entries: Optional[List[dict]] = None
|
|
521
714
|
) -> Tuple[Optional[List[Hit]], str]:
|
|
@@ -551,13 +744,18 @@ def retrieve_any(query: str, k: int = 60, kind: Optional[str] = None,
|
|
|
551
744
|
# counts as a ranking to fuse.
|
|
552
745
|
bm25_hits = retrieve(query, k=pool, kind=kind,
|
|
553
746
|
entries=entries) if ready() else None
|
|
747
|
+
# Dedupe at this seam as well as inside `retrieve`, because dense hits carry
|
|
748
|
+
# no hash of their own and fusion can reintroduce a duplicate that BM25
|
|
749
|
+
# already dropped — the copies are distinct `(tap, path)` keys, so RRF has
|
|
750
|
+
# no reason to treat them as one.
|
|
554
751
|
if dense_hits and bm25_hits:
|
|
555
|
-
|
|
752
|
+
fused = rrf_fuse([bm25_hits, dense_hits], limit=max(k, RRF_K))
|
|
753
|
+
return (dedupe_by_content(_with_content(fused), k),
|
|
556
754
|
"hybrid RRF (BM25 + dense)")
|
|
557
755
|
if dense_hits:
|
|
558
|
-
return dense_hits
|
|
756
|
+
return dedupe_by_content(_with_content(dense_hits), k), "dense vectors"
|
|
559
757
|
if bm25_hits is not None:
|
|
560
|
-
return bm25_hits[:k], "BM25 full-content"
|
|
758
|
+
return bm25_hits[:k], "BM25 full-content" # already deduped
|
|
561
759
|
return None, ""
|
|
562
760
|
|
|
563
761
|
|
|
@@ -483,6 +483,7 @@ tests/unit/test_codeql_analysis_key.py
|
|
|
483
483
|
tests/unit/test_command_reference_fresh.py
|
|
484
484
|
tests/unit/test_community_health_files.py
|
|
485
485
|
tests/unit/test_config.py
|
|
486
|
+
tests/unit/test_content_dedupe.py
|
|
486
487
|
tests/unit/test_dense.py
|
|
487
488
|
tests/unit/test_dense_fallback.py
|
|
488
489
|
tests/unit/test_dense_fix_hint.py
|
|
@@ -9,7 +9,7 @@ impact: High
|
|
|
9
9
|
wow: 5
|
|
10
10
|
note: 2.5 GB RSS per search at 50k items
|
|
11
11
|
order: 72
|
|
12
|
-
owner: loop/
|
|
12
|
+
owner: loop/postings-out-of-json
|
|
13
13
|
pr:
|
|
14
14
|
title: The BM25 index is one JSON blob, and it stops working between 10k and 50k items
|
|
15
15
|
---
|
|
@@ -99,3 +99,34 @@ memory but not the scan, and in the common case (one tap of many changed) nearly
|
|
|
99
99
|
kept, so it saves little of that either. Denormalising <code>tf</code> back into the documents would
|
|
100
100
|
fix it directly but roughly duplicates the 64% that postings already occupy. The real fix is the
|
|
101
101
|
storage format, which is win (2) — worth treating them as one item rather than two.
|
|
102
|
+
|
|
103
|
+
<b>Win (2) is shipped: postings now live in SQLite, and a query reads its terms rather than the whole
|
|
104
|
+
index.</b> Measured on the pinned corpus — JSON 2.05 MB → <b>0.76 MB</b>, load
|
|
105
|
+
0.015 s → <b>0.0013 s</b>, cold retrieve <b>0.0003 s</b>.
|
|
106
|
+
|
|
107
|
+
<b>SQLite as a key-value store, not as FTS5.</b> The card offered FTS5 or a binary format, and FTS5
|
|
108
|
+
was the tempting one — it is compiled in, verified on CPython 3.14 (SQLite 3.53.4) and system
|
|
109
|
+
3.9 (3.54.0). But FTS5 replaces BM25 with <em>its own</em> ranking: different tokenizer, different
|
|
110
|
+
parameters, different results. The measured problem is cold-start cost, not scoring quality, so
|
|
111
|
+
adopting FTS5 would risk what works to fix what does not. Here <code>_bm25</code> is unchanged byte
|
|
112
|
+
for byte, and the eval gate proves the change is behaviour-neutral: 0.978 / 0.791 / 0.854 / 0.882,
|
|
113
|
+
identical before and after. <code>_bm25</code> also now takes postings as an <em>argument</em>, so
|
|
114
|
+
the one function whose arithmetic is pinned exactly contains no I/O and is not coupled to where
|
|
115
|
+
postings are stored.
|
|
116
|
+
|
|
117
|
+
<b>The honest cost: total disk goes up.</b> The store is 3.89 MB against 1.31 MB of JSON postings,
|
|
118
|
+
because SQLite keeps the term string on every row plus an index over it. Total on-disk is 4.65 MB
|
|
119
|
+
versus 2.05 MB. This card says <b>“target RSS, not bytes”</b> and that is why the trade
|
|
120
|
+
is the right way round — the hot path now reads kilobytes instead of materialising megabytes of
|
|
121
|
+
Python objects — but it is a real cost, not a free win. Interning terms into a
|
|
122
|
+
<code>term_id</code> table would recover most of it and is the obvious follow-up if the size matters.
|
|
123
|
+
|
|
124
|
+
<b>Both halves are now required.</b> <code>ready()</code> checks for the postings store as well as
|
|
125
|
+
the documents, so a docs-only index reports not-ready instead of silently scoring every query to
|
|
126
|
+
zero hits. <code>INDEX_VERSION</code> 5 → 6 forces the one-time rebuild.
|
|
127
|
+
|
|
128
|
+
<b>Still open:</b> <code>snip</code> text off the hot path — now only 14% of the JSON half, so
|
|
129
|
+
much smaller than this card assumed — and the O(entire-corpus) incremental reindex. The latter
|
|
130
|
+
is closer than it was: per-document term frequencies are now queryable by document rather than only
|
|
131
|
+
recoverable by inverting every posting, so scoping the rebuild to the changed taps is a tractable
|
|
132
|
+
follow-up rather than a format change.
|
|
@@ -9,7 +9,7 @@ impact: High
|
|
|
9
9
|
wow: 4
|
|
10
10
|
note: 56.3% of entries share a name
|
|
11
11
|
order: 71
|
|
12
|
-
owner: loop/
|
|
12
|
+
owner: loop/content-hash-dedup
|
|
13
13
|
pr:
|
|
14
14
|
title: Near-duplicate items consume the top-10, and it gets worse with every tap
|
|
15
15
|
---
|
|
@@ -123,3 +123,39 @@ genuinely differ (the <code>admin-interface-rule</code> pair fixed in <code>#366
|
|
|
123
123
|
here as duplicate slots but must <em>not</em> be merged. That is precisely why the card asks for
|
|
124
124
|
clustering on content hash rather than on name, and this curve is an upper bound on what dedup could
|
|
125
125
|
reclaim, not a target.
|
|
126
|
+
|
|
127
|
+
<b>Shipped: content-hash dedup, measured 4.94 → 0.60 duplicate slots per query</b> over the
|
|
128
|
+
77-tap corpus (worst case 9 of 10 → 4). The residual 0.60 is correct rather than leftover: those
|
|
129
|
+
are entries sharing a <em>name</em> whose bodies genuinely differ, which must stay separate.
|
|
130
|
+
|
|
131
|
+
Two measurements settled the design the card left open (“content hash <em>or</em> canonical
|
|
132
|
+
name”). Of 29,938 entries, <b>78.3% are byte-identical duplicates</b> — 14,153 distinct
|
|
133
|
+
bodies, largest cluster <b>40 copies</b> — while 82.7% share a name. That ~4.4% gap is real
|
|
134
|
+
distinct content, so name clustering would merge exactly the pairs <code>#366</code> proved must not
|
|
135
|
+
be merged. And clustering on content cannot make the opposite mistake here: the number of content
|
|
136
|
+
clusters spanning more than one name is <b>0</b>, so collapsing by body never merges two
|
|
137
|
+
differently-named items. Content hashing is strictly the safer of the two options the card offered,
|
|
138
|
+
which was not obvious before measuring.
|
|
139
|
+
|
|
140
|
+
The quality prior is <code>curated</code>: inside a cluster every copy is byte-identical, so the one
|
|
141
|
+
worth surfacing is the one from a tap the user marked trusted. The cluster keeps its best score when
|
|
142
|
+
that swap happens, so promoting the trusted copy never demotes the result.
|
|
143
|
+
|
|
144
|
+
Mechanically, the body hash is computed in <code>_make_docs</code> where the body is already read
|
|
145
|
+
— doing it at query time would mean re-reading ~30k files to answer one search — and
|
|
146
|
+
persisted per document (<code>INDEX_VERSION</code> 4 → 5). Dense hits carry no hash of their own
|
|
147
|
+
and do not need a second schema: <code>content_hashes()</code> serves one map to every engine from
|
|
148
|
+
the BM25 index, so the engines can never disagree about which copies are identical. Dedup runs inside
|
|
149
|
+
<code>retrieve</code> <em>and</em> at the <code>retrieve_any</code> seam, because fusion can
|
|
150
|
+
reintroduce a copy BM25 already dropped — the copies are distinct <code>(tap, path)</code>
|
|
151
|
+
keys, so RRF has no reason to treat them as one. Collapsing happens before <code>k</code> is applied,
|
|
152
|
+
or the duplicates would still consume the slots they were removed from.
|
|
153
|
+
|
|
154
|
+
Cost on the pinned eval corpus: <b>none</b> — 0.978 / 0.791 / 0.854 / 0.882, identical to
|
|
155
|
+
before, all four floors passing. That corpus has little content duplication, which is exactly why the
|
|
156
|
+
83-tap measurement had to exist.
|
|
157
|
+
|
|
158
|
+
<b>Still open:</b> a richer quality prior than <code>curated</code> (the card names stars, recency,
|
|
159
|
+
maintenance; only <code>curated</code> is available on an entry today), and
|
|
160
|
+
<code>core/typosquat.py</code>'s confusion machinery for near-identical rather than byte-identical
|
|
161
|
+
bodies. Both are refinements — the byte-identical case is 78.3% of the problem.
|
|
@@ -3472,7 +3472,43 @@ symptom the card names, not content-identical bodies — two rules that shar
|
|
|
3472
3472
|
genuinely differ (the <code>admin-interface-rule</code> pair fixed in <code>#366</code>) are counted
|
|
3473
3473
|
here as duplicate slots but must <em>not</em> be merged. That is precisely why the card asks for
|
|
3474
3474
|
clustering on content hash rather than on name, and this curve is an upper bound on what dedup could
|
|
3475
|
-
reclaim, not a target
|
|
3475
|
+
reclaim, not a target.
|
|
3476
|
+
|
|
3477
|
+
<b>Shipped: content-hash dedup, measured 4.94 → 0.60 duplicate slots per query</b> over the
|
|
3478
|
+
77-tap corpus (worst case 9 of 10 → 4). The residual 0.60 is correct rather than leftover: those
|
|
3479
|
+
are entries sharing a <em>name</em> whose bodies genuinely differ, which must stay separate.
|
|
3480
|
+
|
|
3481
|
+
Two measurements settled the design the card left open (“content hash <em>or</em> canonical
|
|
3482
|
+
name”). Of 29,938 entries, <b>78.3% are byte-identical duplicates</b> — 14,153 distinct
|
|
3483
|
+
bodies, largest cluster <b>40 copies</b> — while 82.7% share a name. That ~4.4% gap is real
|
|
3484
|
+
distinct content, so name clustering would merge exactly the pairs <code>#366</code> proved must not
|
|
3485
|
+
be merged. And clustering on content cannot make the opposite mistake here: the number of content
|
|
3486
|
+
clusters spanning more than one name is <b>0</b>, so collapsing by body never merges two
|
|
3487
|
+
differently-named items. Content hashing is strictly the safer of the two options the card offered,
|
|
3488
|
+
which was not obvious before measuring.
|
|
3489
|
+
|
|
3490
|
+
The quality prior is <code>curated</code>: inside a cluster every copy is byte-identical, so the one
|
|
3491
|
+
worth surfacing is the one from a tap the user marked trusted. The cluster keeps its best score when
|
|
3492
|
+
that swap happens, so promoting the trusted copy never demotes the result.
|
|
3493
|
+
|
|
3494
|
+
Mechanically, the body hash is computed in <code>_make_docs</code> where the body is already read
|
|
3495
|
+
— doing it at query time would mean re-reading ~30k files to answer one search — and
|
|
3496
|
+
persisted per document (<code>INDEX_VERSION</code> 4 → 5). Dense hits carry no hash of their own
|
|
3497
|
+
and do not need a second schema: <code>content_hashes()</code> serves one map to every engine from
|
|
3498
|
+
the BM25 index, so the engines can never disagree about which copies are identical. Dedup runs inside
|
|
3499
|
+
<code>retrieve</code> <em>and</em> at the <code>retrieve_any</code> seam, because fusion can
|
|
3500
|
+
reintroduce a copy BM25 already dropped — the copies are distinct <code>(tap, path)</code>
|
|
3501
|
+
keys, so RRF has no reason to treat them as one. Collapsing happens before <code>k</code> is applied,
|
|
3502
|
+
or the duplicates would still consume the slots they were removed from.
|
|
3503
|
+
|
|
3504
|
+
Cost on the pinned eval corpus: <b>none</b> — 0.978 / 0.791 / 0.854 / 0.882, identical to
|
|
3505
|
+
before, all four floors passing. That corpus has little content duplication, which is exactly why the
|
|
3506
|
+
83-tap measurement had to exist.
|
|
3507
|
+
|
|
3508
|
+
<b>Still open:</b> a richer quality prior than <code>curated</code> (the card names stars, recency,
|
|
3509
|
+
maintenance; only <code>curated</code> is available on an entry today), and
|
|
3510
|
+
<code>core/typosquat.py</code>'s confusion machinery for near-identical rather than byte-identical
|
|
3511
|
+
bodies. Both are refinements — the byte-identical case is 78.3% of the problem.</p>
|
|
3476
3512
|
<div class="meta">
|
|
3477
3513
|
<span class="m">Complexity <b>M</b></span>
|
|
3478
3514
|
<span class="m hi">Impact <b>High</b></span>
|
|
@@ -3569,7 +3605,38 @@ taps means scanning every posting whatever the filter. Narrowing the inversion t
|
|
|
3569
3605
|
memory but not the scan, and in the common case (one tap of many changed) nearly every document is
|
|
3570
3606
|
kept, so it saves little of that either. Denormalising <code>tf</code> back into the documents would
|
|
3571
3607
|
fix it directly but roughly duplicates the 64% that postings already occupy. The real fix is the
|
|
3572
|
-
storage format, which is win (2) — worth treating them as one item rather than two
|
|
3608
|
+
storage format, which is win (2) — worth treating them as one item rather than two.
|
|
3609
|
+
|
|
3610
|
+
<b>Win (2) is shipped: postings now live in SQLite, and a query reads its terms rather than the whole
|
|
3611
|
+
index.</b> Measured on the pinned corpus — JSON 2.05 MB → <b>0.76 MB</b>, load
|
|
3612
|
+
0.015 s → <b>0.0013 s</b>, cold retrieve <b>0.0003 s</b>.
|
|
3613
|
+
|
|
3614
|
+
<b>SQLite as a key-value store, not as FTS5.</b> The card offered FTS5 or a binary format, and FTS5
|
|
3615
|
+
was the tempting one — it is compiled in, verified on CPython 3.14 (SQLite 3.53.4) and system
|
|
3616
|
+
3.9 (3.54.0). But FTS5 replaces BM25 with <em>its own</em> ranking: different tokenizer, different
|
|
3617
|
+
parameters, different results. The measured problem is cold-start cost, not scoring quality, so
|
|
3618
|
+
adopting FTS5 would risk what works to fix what does not. Here <code>_bm25</code> is unchanged byte
|
|
3619
|
+
for byte, and the eval gate proves the change is behaviour-neutral: 0.978 / 0.791 / 0.854 / 0.882,
|
|
3620
|
+
identical before and after. <code>_bm25</code> also now takes postings as an <em>argument</em>, so
|
|
3621
|
+
the one function whose arithmetic is pinned exactly contains no I/O and is not coupled to where
|
|
3622
|
+
postings are stored.
|
|
3623
|
+
|
|
3624
|
+
<b>The honest cost: total disk goes up.</b> The store is 3.89 MB against 1.31 MB of JSON postings,
|
|
3625
|
+
because SQLite keeps the term string on every row plus an index over it. Total on-disk is 4.65 MB
|
|
3626
|
+
versus 2.05 MB. This card says <b>“target RSS, not bytes”</b> and that is why the trade
|
|
3627
|
+
is the right way round — the hot path now reads kilobytes instead of materialising megabytes of
|
|
3628
|
+
Python objects — but it is a real cost, not a free win. Interning terms into a
|
|
3629
|
+
<code>term_id</code> table would recover most of it and is the obvious follow-up if the size matters.
|
|
3630
|
+
|
|
3631
|
+
<b>Both halves are now required.</b> <code>ready()</code> checks for the postings store as well as
|
|
3632
|
+
the documents, so a docs-only index reports not-ready instead of silently scoring every query to
|
|
3633
|
+
zero hits. <code>INDEX_VERSION</code> 5 → 6 forces the one-time rebuild.
|
|
3634
|
+
|
|
3635
|
+
<b>Still open:</b> <code>snip</code> text off the hot path — now only 14% of the JSON half, so
|
|
3636
|
+
much smaller than this card assumed — and the O(entire-corpus) incremental reindex. The latter
|
|
3637
|
+
is closer than it was: per-document term frequencies are now queryable by document rather than only
|
|
3638
|
+
recoverable by inverting every posting, so scoping the rebuild to the changed taps is a tractable
|
|
3639
|
+
follow-up rather than a format change.</p>
|
|
3573
3640
|
<div class="meta">
|
|
3574
3641
|
<span class="m">Complexity <b>L</b></span>
|
|
3575
3642
|
<span class="m hi">Impact <b>High</b></span>
|