boost-skill-cli 1.0.206__tar.gz → 1.0.207__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.206 → boost_skill_cli-1.0.207}/CONTRIBUTING.md +1 -1
- {boost_skill_cli-1.0.206/boost_skill_cli.egg-info → boost_skill_cli-1.0.207}/PKG-INFO +1 -1
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/_version.py +2 -2
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/commands/quality.py +4 -1
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/commands/safety.py +158 -2
- boost_skill_cli-1.0.207/boost_cli/core/trustaudit.py +224 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207/boost_skill_cli.egg-info}/PKG-INFO +1 -1
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_skill_cli.egg-info/SOURCES.txt +2 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/commands.html +4 -3
- boost_skill_cli-1.0.207/docs/roadmap/items/installed-skill-trust-audit.md +29 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap.html +15 -11
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/functional/test_cli_quality.py +65 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/smoke.sh +8 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_output.py +30 -0
- boost_skill_cli-1.0.207/tests/unit/test_trustaudit.py +369 -0
- boost_skill_cli-1.0.206/docs/roadmap/items/installed-skill-trust-audit.md +0 -25
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/.gitattributes +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/.gitignore +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/.gitleaks.toml +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/.htmlvalidate.json +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/.lighthouserc.json +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/.lycheeignore +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/.markdownlint-cli2.jsonc +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/.pre-commit-config.yaml +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/.stylelintrc.json +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/.vale/styles/boost/Terminology.yml +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/.vale/styles/config/vocabularies/boost/accept.txt +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/.vale.ini +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/CLAUDE.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/LICENSE +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/MANIFEST.in +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/Makefile +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/README.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/SECURITY.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/__init__.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/__main__.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/cli.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/cliparse.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/commands/__init__.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/commands/_common.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/commands/bmad.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/commands/configuration.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/commands/discovery.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/commands/hooks.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/commands/info.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/commands/intelligence.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/commands/pkg.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/commands/run.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/commands/taps.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/commands/team.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/__init__.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/adapters.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/agents.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/ai.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/capabilities.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/catalog.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/claude_settings.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/config.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/dense.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/ed25519.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/embed.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/faithfulness.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/frontmatter.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/gitutil.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/imperative.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/injectscan.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/integrity.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/journal.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/lockfile.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/logs.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/mcp.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/minisign.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/nethttp.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/output.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/paths.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/policy.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/projectlock.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/provenance.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/rag.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/registry.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/resolve.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/rules.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/scopes.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/secretscan.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/serve.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/stackprobe.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/staleness.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/store.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/typosquat.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/updatediff.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/util.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/core/workflows.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/data/registries.json +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/errors.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_cli/spin.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_skill_cli.egg-info/dependency_links.txt +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_skill_cli.egg-info/entry_points.txt +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_skill_cli.egg-info/requires.txt +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/boost_skill_cli.egg-info/top_level.txt +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/.claude/settings.local.json +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/DEBUGGING.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/adapters.html +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/demo.gif +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/demo.tape +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/design-roadmap.html +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/eval.html +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/index.html +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/mcp-hub.html +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/rag-architecture.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D01.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D02.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D03.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D04.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D05.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D06.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D07.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D08.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D09.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D10.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D11.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D12.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D13.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D14.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D15.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D16.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D17.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D18.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D19.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D20.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D21.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D22.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D23.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D24.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D25.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/BOOST-D26.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/accessibility-audit-pa11y-ci-axe-core.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/atomic-corruption-safe-lock-file-writes.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/atomic-skill-install-temp-dir-swap.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/autonomous-ship-workflow-and-isolated-worktree.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/boost-run-live-agents.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/bring-commands-under-mutation-testing.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/broken-link-and-anchor-checking-lychee.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/browse-crashes-selecting-a-rule-or-workflow.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/build-provenance-slsa-attestations.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/cache-the-catalog-entry-set-across-rag-queries.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/capability-manifest-and-least-privilege-policy.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/clean-env-install-smoke-pip-and-pipx.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/command-reference-docs-site.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/complexity-and-dead-code-radar-xenon-vulture.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/consolidate-skill-staleness-drift-logic-into-cor.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/coverage-dashboard-codecov-free-for-oss.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/coverage-guided-fuzzing-atheris-oss-fuzz.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/crash-recorder-error-paths-core-logs-py.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/dependency-cve-gate-pip-audit.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/docsite-chrome-and-content-audit.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/docstring-coverage-interrogate.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/doctor-checks-rules-and-workflows.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/end-of-options-guard-on-git-commands.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/extension-free-tests-core-dense-py.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/extract-mcp-http-servers-out-of-configuration-py.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/finish-mutation-hardening-across-core.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/fix-self-update-version-detection-dead-branch.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/fork-safe-network-proxy-handler.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/framework-adapter-langgraph.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/framework-adapter-multi-agent.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/framework-adapters-boost-adapt.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/frontmatter-scalar-over-coercion.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/github-pages-deploy-is-broken-every-push.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/golden-set-statistical-power.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/hash-pinned-reproducible-toolchain-uv-lock.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/html-validation-html-validate.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/install-resolves-skill-dependencies.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/install-scope-user-or-project.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/integrity-verification-boost-verify.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/invocation-pid-logging-for-crash-correlation.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/layering-guard-import-linter.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/lighthouse-ci-on-the-pages-site.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/list-rules-and-workflows.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/localize-the-stored-bm25-snippet.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/lockfile-enforcement-and-commit-pinning.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/log-timestamps-mislabeled-utc.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/lowest-version-resolution-uv-resolution-lowest-d.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/make-the-roadmaps-discoverable.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/markdown-consistency-markdownlint-cli2.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/mcp-aware-skills.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/mcp-hub-diagram-node-text-overflow.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/mcp-launch-objc-fork-safety.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/mcp-register-names-server-before-env.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/mcp-search-before-reinventing.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/mcp-startup-self-harden-fork-safety.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/memoize-config-load-in-process.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/modernization-smells-refurb-pyupgrade.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/mutation-hardening-core-frontmatter-py.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/mutation-hardening-core-gitutil-py.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/mutation-hardening-core-store-py.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/one-command-every-env-nox.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/one-shared-atomic-write-helper.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/package-metadata-validation-twine-check-friends.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/patch-coverage-gate-diff-cover.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/performance-regression-gate-pytest-benchmark.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/pin-the-lint-toolchain.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/post-deploy-smoke-headless-load-check.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/pre-release-python-canary-3-14t-free-threaded.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/project-scope-across-every-command.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/project-scope-symlink-escape-on-write.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/promote-nav-footer-into-the-shared-style-system.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/prompt-injection-scanning-of-skill-markdown.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/property-based-tests-hypothesis-on-the-parsers.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/prose-and-terminology-linting-vale.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/prune-ignored-dirs-during-scan-dir-walk.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/quality-dashboard-sonarcloud-free-for-oss.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/ranx-significance-monitor.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/reconcile-the-theme-drift.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/refresh-the-marketing-surface.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/retrieval-quality-eval-harness.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/reuse-helpers-kill-minor-dead-work.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/robust-tag-argument-parsing.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/rule-install-native-materialization.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/runner-egress-monitoring-stepsecurity-harden-run.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/runtime-explain-faithfulness-guardrail.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/sbom-aware-scanning-osv-scanner.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/sbom-on-every-release-cyclonedx-syft.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/scan-and-sync-rules-and-workflows.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/second-type-checker-pyright.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/secret-and-pii-scanning-of-installed-skills.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/secret-scanning-gitleaks-push-protection.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/security-linting-bandit-via-ruff-s-rules.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/shift-left-gate-pre-commit-pre-commit-ci.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/single-imperative-rule-extractor.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/single-tech-stack-prober.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/split-oversized-command-modules.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/startup-and-import-time-budget-x-importtime.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/stop-re-serializing-entry-meta-on-every-search.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/supply-chain-posture-openssf-scorecard.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/surface-every-docs-page-from-the-guide.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/tap-signing-and-provenance-sigstore-minisign.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/theme-asset-linting-stylelint-eslint.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/typo-detection-codespell.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/typosquat-and-name-confusion-detection.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/unify-tilde-two-copies-have-a-boundary-bug.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/untrack-generated-build-noise.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/update-diff-before-apply.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/update-refreshes-rules-and-workflows.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/visual-regression-pass-on-the-guide.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/widen-the-ruff-rule-surface-b-sim-c4-perf-ruf.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/windows-in-the-ci-matrix.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/workflow-install-native-materialization.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/workflow-linting-actionlint.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/workflow-sast-zizmor.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/docs/roadmap/items/workspace-scope-install.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/eslint.config.mjs +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/examples/README.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/examples/adapt-demo.sh +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/examples/boost-run-prototype.sh +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/index.html +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/noxfile.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/pyproject.toml +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/requirements/lint-tools.txt +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/scripts/bench_cli.sh +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/scripts/build_command_reference.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/scripts/build_registries.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/scripts/build_roadmap.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/scripts/check_anchors.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/scripts/ensure_eval_corpus.sh +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/scripts/eval_explain.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/scripts/eval_recommend.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/scripts/eval_retrieval.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/scripts/eval_stats_summary.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/scripts/import_budget.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/scripts/mutation_gate.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/scripts/perf_gate.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/setup.cfg +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/style/README.md +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/style/boost.css +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/style/boost.js +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/style/demo.html +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/bdd/features/adapt.feature +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/bdd/features/browse.feature +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/bdd/features/count.feature +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/bdd/features/discover.feature +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/bdd/features/doctor.feature +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/bdd/features/environment.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/bdd/features/explain.feature +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/bdd/features/index.feature +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/bdd/features/install.feature +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/bdd/features/list.feature +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/bdd/features/mcp.feature +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/bdd/features/search.feature +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/bdd/features/steps/cli_steps.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/bdd/features/steps/discovery_steps.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/bdd/features/steps/mcp_steps.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/bdd/features/steps/quality_steps.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/conftest.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/eval/baseline.json +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/eval/explain.jsonl +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/eval/golden.jsonl +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/eval/recommend.jsonl +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/eval/taps.txt +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/functional/test_capabilities_policy.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/functional/test_cli_adapt.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/functional/test_cli_bmad.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/functional/test_cli_configuration.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/functional/test_cli_discovery.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/functional/test_cli_hooks.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/functional/test_cli_info.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/functional/test_cli_intelligence.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/functional/test_cli_pkg.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/functional/test_cli_taps.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/functional/test_cli_team.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/functional/test_everyday_loop.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/functional/test_integrity_enforce.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/functional/test_property_parsers.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/functional/test_run.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/functional/test_tap_signing.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/functional/test_workspace_scope.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/make_fixture.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/perf/test_benchmarks.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_adapters.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_adapters_multi.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_adapters_runner.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_agents.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_ai.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_capabilities.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_catalog.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_check_anchors.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_claude_settings.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_cliparse.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_command_reference_fresh.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_config.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_dense.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_dense_fallback.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_docs_pages_linked.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_docsite_chrome.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_ed25519.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_embed.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_errors_and_cli_table.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_eval_stats_summary.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_faithfulness.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_frontmatter.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_gitleaks_config.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_gitutil.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_imperative.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_import_budget.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_injectscan.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_integrity.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_journal.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_lockfile.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_logs.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_mcp.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_minisign.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_mutation_hardening.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_nethttp.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_paths.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_perf_gate.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_pkg_confusions.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_pkg_injection.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_pkg_secrets.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_policy.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_projectlock.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_provenance.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_rag.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_registries_fresh.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_registry.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_resolve.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_roadmap_fresh.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_rules.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_scopes.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_secretscan.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_serve.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_spin.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_stackprobe.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_staleness.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_store.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_token_parity.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_typosquat.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_updatediff.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_util.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_version.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/unit/test_workflows.py +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/visual/package.json +0 -0
- {boost_skill_cli-1.0.206 → boost_skill_cli-1.0.207}/tests/visual/visual_check.mjs +0 -0
|
@@ -50,7 +50,7 @@ auto-fixes straight back onto the branch.
|
|
|
50
50
|
|---|---|
|
|
51
51
|
| `make test` | unit + functional suites, **≥80% line coverage** |
|
|
52
52
|
| `make patch-coverage` | changed-line coverage vs `main`, **≥80% of the diff** (PRs) |
|
|
53
|
-
| `bash tests/smoke.sh` |
|
|
53
|
+
| `bash tests/smoke.sh` | 170 end-to-end checks through the real `./boost` shim |
|
|
54
54
|
| `make mutation` | mutmut over `boost_cli/core`, **≥80% mutants killed** |
|
|
55
55
|
| `ruff check boost_cli tests` | lint, zero findings |
|
|
56
56
|
| `mypy` | type check, zero errors |
|
|
@@ -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.207'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 0, 207)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -848,7 +848,10 @@ def cmd_health(argv):
|
|
|
848
848
|
_PROVENANCE_STYLE = {
|
|
849
849
|
provenance.VERIFIED: "success",
|
|
850
850
|
provenance.UNTRUSTED: "warn",
|
|
851
|
-
|
|
851
|
+
# "danger", not "err": out.role() looks the name up in output.ROLES, which
|
|
852
|
+
# has no "err" key — so an invalid tap signature raised KeyError on any
|
|
853
|
+
# color terminal. The suite never caught it because conftest sets NO_COLOR.
|
|
854
|
+
provenance.INVALID: "danger",
|
|
852
855
|
provenance.UNSIGNED: "muted",
|
|
853
856
|
}
|
|
854
857
|
|
|
@@ -10,11 +10,13 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
import json
|
|
12
12
|
import re
|
|
13
|
+
import time
|
|
14
|
+
from contextlib import suppress
|
|
13
15
|
|
|
14
16
|
from .. import cliparse
|
|
15
|
-
from ..core import integrity, journal, lockfile
|
|
17
|
+
from ..core import catalog, frontmatter, gitutil, integrity, journal, lockfile
|
|
16
18
|
from ..core import output as out
|
|
17
|
-
from ..core import policy, store, util
|
|
19
|
+
from ..core import policy, provenance, registry, staleness, store, trustaudit, util
|
|
18
20
|
from ..errors import BoostError
|
|
19
21
|
from ._common import _iter_installed, _s
|
|
20
22
|
|
|
@@ -42,8 +44,14 @@ def cmd_audit(argv):
|
|
|
42
44
|
prog="boost audit",
|
|
43
45
|
description="Check installed skills against a safety blocklist")
|
|
44
46
|
ap.add_argument("--json", action="store_true")
|
|
47
|
+
ap.add_argument("--skills", action="store_true",
|
|
48
|
+
help="trust/staleness report for installed skills instead "
|
|
49
|
+
"of the content scan")
|
|
45
50
|
args = ap.parse_args(argv)
|
|
46
51
|
|
|
52
|
+
if args.skills:
|
|
53
|
+
return _trust_audit(args.json)
|
|
54
|
+
|
|
47
55
|
pol = policy.load()
|
|
48
56
|
installed = _iter_installed()
|
|
49
57
|
findings: dict = {}
|
|
@@ -110,6 +118,154 @@ def cmd_audit(argv):
|
|
|
110
118
|
return 1 if counts["HIGH"] or counts["MED"] else 0
|
|
111
119
|
|
|
112
120
|
|
|
121
|
+
# --- audit --skills: trust & staleness across the whole installed set ------
|
|
122
|
+
#
|
|
123
|
+
# The signals already existed, one per command: `trust` reports tap signing,
|
|
124
|
+
# `outdated` reports drift vs the tap, `deps` shows the conflicts: graph. This
|
|
125
|
+
# gathers all three over the installed set and hands the decision to
|
|
126
|
+
# core/trustaudit.py, which is where every branch of it is unit-tested.
|
|
127
|
+
|
|
128
|
+
_TRUST_ROLE = {trustaudit.HIGH: "danger", trustaudit.MED: "warn",
|
|
129
|
+
trustaudit.LOW: "muted"}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _tap_age_days(tap):
|
|
133
|
+
"""Days since a tap clone's last commit, or ``None`` when unknowable."""
|
|
134
|
+
try:
|
|
135
|
+
proc = gitutil.run(["-C", str(tap.path), "log", "-1", "--format=%ct"],
|
|
136
|
+
check=False)
|
|
137
|
+
except BoostError:
|
|
138
|
+
return None
|
|
139
|
+
if proc.returncode != 0 or not proc.stdout.strip():
|
|
140
|
+
return None
|
|
141
|
+
try:
|
|
142
|
+
committed = int(proc.stdout.strip())
|
|
143
|
+
except ValueError:
|
|
144
|
+
return None
|
|
145
|
+
return max(0, int((time.time() - committed) // 86400))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _tap_signals(tap_name, cache):
|
|
149
|
+
"""``(provenance status, age in days, HEAD commit)`` for a tap, memoized.
|
|
150
|
+
|
|
151
|
+
A tap boost cannot read — unknown name, never cloned — yields
|
|
152
|
+
``(None, None, "")``, which ``trustaudit`` renders as "signature cannot be
|
|
153
|
+
checked" rather than silently passing.
|
|
154
|
+
"""
|
|
155
|
+
if tap_name not in cache:
|
|
156
|
+
try:
|
|
157
|
+
tap = registry.get(tap_name)
|
|
158
|
+
except BoostError:
|
|
159
|
+
cache[tap_name] = (None, None, "")
|
|
160
|
+
else:
|
|
161
|
+
cache[tap_name] = ((provenance.verify_dir(tap.path).status,
|
|
162
|
+
_tap_age_days(tap), gitutil.head_commit(tap.path))
|
|
163
|
+
if tap.is_cloned else (None, None, ""))
|
|
164
|
+
return cache[tap_name]
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _upstream_reason(name, lk, tap_name, head):
|
|
168
|
+
"""``staleness.upstream_reason`` for one installed skill, or ``None``.
|
|
169
|
+
|
|
170
|
+
Same signal-gathering order as ``cmd_outdated`` so the two commands can
|
|
171
|
+
never disagree about whether a skill is behind its tap.
|
|
172
|
+
"""
|
|
173
|
+
matches = [e for e in catalog.find(name) if e["tap"] == tap_name]
|
|
174
|
+
if not matches:
|
|
175
|
+
return None
|
|
176
|
+
entry = matches[0]
|
|
177
|
+
latest = str(entry.get("version") or "0.0.0")
|
|
178
|
+
installed_v = str(lk.get("version") or "0.0.0")
|
|
179
|
+
src_sha = None
|
|
180
|
+
if not util.semver_gt(latest, installed_v) and head and head != lk.get("commit"):
|
|
181
|
+
try:
|
|
182
|
+
src_sha = util.sha256_dir(store.source_dir_for(entry))
|
|
183
|
+
except BoostError:
|
|
184
|
+
return None # source checkout is gone; `verify` owns that finding
|
|
185
|
+
return staleness.upstream_reason(installed_v, latest, lk.get("commit", ""),
|
|
186
|
+
head, lk.get("sha256", ""), src_sha)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _installed_conflicts(names):
|
|
190
|
+
"""A ``conflicts_of`` callable reading each installed skill's own copy.
|
|
191
|
+
|
|
192
|
+
Reads the store copy rather than the catalog on purpose: the audit is about
|
|
193
|
+
what is *installed*, which can be an older revision than the tap now
|
|
194
|
+
advertises.
|
|
195
|
+
"""
|
|
196
|
+
cache: dict = {}
|
|
197
|
+
|
|
198
|
+
def conflicts_of(name):
|
|
199
|
+
if name not in cache:
|
|
200
|
+
meta: dict = {}
|
|
201
|
+
# unreadable store copy: `verify` owns that finding, not this
|
|
202
|
+
with suppress(OSError, BoostError):
|
|
203
|
+
text = (store.skill_store_dir(name) / "SKILL.md").read_text(
|
|
204
|
+
encoding="utf-8", errors="replace")
|
|
205
|
+
meta, _body = frontmatter.parse(text)
|
|
206
|
+
cache[name] = trustaudit.relation_list(meta, "conflicts")
|
|
207
|
+
return cache[name]
|
|
208
|
+
|
|
209
|
+
return conflicts_of
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _trust_audit(as_json: bool) -> int:
|
|
213
|
+
"""`boost audit --skills` — one trust-health answer for the installed set."""
|
|
214
|
+
installed = dict(_iter_installed())
|
|
215
|
+
peers: dict = {}
|
|
216
|
+
for name, peer in trustaudit.conflict_pairs(list(installed),
|
|
217
|
+
_installed_conflicts(installed)):
|
|
218
|
+
peers.setdefault(name, []).append(peer)
|
|
219
|
+
|
|
220
|
+
taps: dict = {}
|
|
221
|
+
findings: dict = {}
|
|
222
|
+
for name, lk in sorted(installed.items()):
|
|
223
|
+
tap_name = str(lk.get("tap") or "local")
|
|
224
|
+
is_local = tap_name == "local"
|
|
225
|
+
status, age, head = ((None, None, "") if is_local
|
|
226
|
+
else _tap_signals(tap_name, taps))
|
|
227
|
+
rows = trustaudit.skill_findings(
|
|
228
|
+
is_local=is_local, provenance_status=status, tap_age_days=age,
|
|
229
|
+
upstream_reason=(None if is_local
|
|
230
|
+
else _upstream_reason(name, lk, tap_name, head)),
|
|
231
|
+
conflicts_with=peers.get(name, ()))
|
|
232
|
+
if rows:
|
|
233
|
+
findings[name] = rows
|
|
234
|
+
|
|
235
|
+
counts = trustaudit.count_severities(findings)
|
|
236
|
+
rc = trustaudit.exit_code(counts)
|
|
237
|
+
if as_json:
|
|
238
|
+
print(json.dumps({"skills_scanned": len(installed),
|
|
239
|
+
"findings": findings, "counts": counts}, indent=2))
|
|
240
|
+
return rc
|
|
241
|
+
return _render_trust_audit(installed, findings, counts, rc)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _render_trust_audit(installed, findings, counts, rc: int) -> int:
|
|
245
|
+
"""Human rendering for `boost audit --skills`."""
|
|
246
|
+
total = len(installed)
|
|
247
|
+
out.heading("trust audit — %d skill%s" % (total, _s(total)))
|
|
248
|
+
if not total:
|
|
249
|
+
out.info("nothing installed yet — `boost install <skill>` to start")
|
|
250
|
+
return rc
|
|
251
|
+
if not findings:
|
|
252
|
+
out.ok("all %d installed skill%s signed, current and conflict-free"
|
|
253
|
+
% (total, _s(total)))
|
|
254
|
+
return rc
|
|
255
|
+
for name in sorted(findings):
|
|
256
|
+
print(" " + out.c(name, out.BOLD))
|
|
257
|
+
for f in findings[name]:
|
|
258
|
+
print(" %s %s %s"
|
|
259
|
+
% (out.role(f["severity"].ljust(4), _TRUST_ROLE[f["severity"]]),
|
|
260
|
+
f["label"], out.role(f["detail"], "muted")))
|
|
261
|
+
print()
|
|
262
|
+
out.verdict(trustaudit.is_healthy(counts),
|
|
263
|
+
"%d high · %d medium · %d low across %d of %d skill%s"
|
|
264
|
+
% (counts[trustaudit.HIGH], counts[trustaudit.MED],
|
|
265
|
+
counts[trustaudit.LOW], len(findings), total, _s(total)))
|
|
266
|
+
return rc
|
|
267
|
+
|
|
268
|
+
|
|
113
269
|
def cmd_verify(argv):
|
|
114
270
|
ap = cliparse.parser(
|
|
115
271
|
prog="boost verify",
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""Single source of truth for "is the set of skills I actually run healthy?".
|
|
2
|
+
|
|
3
|
+
boost already computes every individual trust signal, each in its own command:
|
|
4
|
+
``verify`` checks lock-file integrity, ``outdated`` compares an installed skill
|
|
5
|
+
against its tap, ``trust`` reports tap-level signing provenance, ``deps`` shows
|
|
6
|
+
the ``requires:``/``conflicts:`` graph. What nothing answered is the aggregate
|
|
7
|
+
question — *of the skills I have installed, which ones should I stop trusting?*
|
|
8
|
+
|
|
9
|
+
These functions are the decision layer for ``boost audit --skills``. They are
|
|
10
|
+
pure and I/O-free on purpose, exactly like :mod:`boost_cli.core.staleness`: the
|
|
11
|
+
command gathers the raw signals (``provenance.verify_dir``, a tap's last-sync
|
|
12
|
+
timestamp, ``staleness.upstream_reason``, ``resolve.resolve``) and passes them
|
|
13
|
+
in, so every branch of the *decision* is trivially testable and stays reachable
|
|
14
|
+
by the mutation gate.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Dict, List, Optional, Sequence, Tuple
|
|
19
|
+
|
|
20
|
+
from . import provenance
|
|
21
|
+
|
|
22
|
+
# ── severity vocabulary ────────────────────────────────────────────────────
|
|
23
|
+
# Mirrors cmd_audit's content scan so one `boost audit` mental model covers
|
|
24
|
+
# both halves: HIGH means stop and look, MED means decide, LOW is context.
|
|
25
|
+
HIGH = "HIGH"
|
|
26
|
+
MED = "MED"
|
|
27
|
+
LOW = "LOW"
|
|
28
|
+
|
|
29
|
+
# ── finding labels ─────────────────────────────────────────────────────────
|
|
30
|
+
INVALID_SIGNATURE = "invalid-signature" # signed, but the signature fails crypto
|
|
31
|
+
UNTRUSTED_TAP = "untrusted-tap" # signed by a key you have not trusted
|
|
32
|
+
UNSIGNED_TAP = "unsigned-tap" # the tap publishes no signature at all
|
|
33
|
+
LOCAL_SOURCE = "local-source" # imported from a path; no tap to trust
|
|
34
|
+
STALE_TAP = "stale-tap" # the tap clone has not been synced
|
|
35
|
+
BEHIND_TAP = "behind-tap" # the tap has a newer copy than yours
|
|
36
|
+
CONFLICT = "conflict" # declares conflicts: against a peer
|
|
37
|
+
|
|
38
|
+
# A tap clone older than this many days is reported as stale. Chosen to be
|
|
39
|
+
# quiet: a month of no upstream sync is long enough to be a real signal and
|
|
40
|
+
# long enough that a healthy weekly-ish `boost update` never trips it.
|
|
41
|
+
STALE_TAP_DAYS = 30
|
|
42
|
+
|
|
43
|
+
# label -> severity. A malformed signature is the only HIGH: it means the tap
|
|
44
|
+
# is actively lying about its provenance, as opposed to merely not claiming any.
|
|
45
|
+
SEVERITY = {
|
|
46
|
+
INVALID_SIGNATURE: HIGH,
|
|
47
|
+
UNTRUSTED_TAP: MED,
|
|
48
|
+
CONFLICT: MED,
|
|
49
|
+
UNSIGNED_TAP: LOW,
|
|
50
|
+
LOCAL_SOURCE: LOW,
|
|
51
|
+
STALE_TAP: LOW,
|
|
52
|
+
BEHIND_TAP: LOW,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# provenance status -> our label. VERIFIED maps to nothing: a trusted signature
|
|
56
|
+
# is the healthy case and must not produce a finding.
|
|
57
|
+
_PROVENANCE_LABEL = {
|
|
58
|
+
provenance.INVALID: INVALID_SIGNATURE,
|
|
59
|
+
provenance.UNTRUSTED: UNTRUSTED_TAP,
|
|
60
|
+
provenance.UNSIGNED: UNSIGNED_TAP,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def signing_label(status: Optional[str], is_local: bool = False) -> Optional[str]:
|
|
65
|
+
"""The trust finding for a tap's signing ``status``, or ``None`` if healthy.
|
|
66
|
+
|
|
67
|
+
``is_local`` marks a skill imported from a path rather than tapped: there is
|
|
68
|
+
no tap to sign it, so it reports :data:`LOCAL_SOURCE` rather than being
|
|
69
|
+
silently treated as unsigned upstream content. A ``status`` of ``None``
|
|
70
|
+
(tap recorded but its clone is gone) reports as unsigned — boost cannot
|
|
71
|
+
show a signature it cannot read.
|
|
72
|
+
"""
|
|
73
|
+
if is_local:
|
|
74
|
+
return LOCAL_SOURCE
|
|
75
|
+
if status == provenance.VERIFIED:
|
|
76
|
+
return None
|
|
77
|
+
if status is None:
|
|
78
|
+
return UNSIGNED_TAP
|
|
79
|
+
return _PROVENANCE_LABEL.get(status, UNSIGNED_TAP)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def stale_tap_label(age_days: Optional[int],
|
|
83
|
+
limit: int = STALE_TAP_DAYS) -> Optional[str]:
|
|
84
|
+
""":data:`STALE_TAP` when a tap clone's last sync is older than ``limit``.
|
|
85
|
+
|
|
86
|
+
``age_days`` is ``None`` when the age could not be determined (no clone, no
|
|
87
|
+
cache timestamp); that is treated as "no signal", not as stale, so a
|
|
88
|
+
missing timestamp never manufactures a finding. The comparison is strictly
|
|
89
|
+
greater-than, so a tap synced exactly ``limit`` days ago is still fresh.
|
|
90
|
+
"""
|
|
91
|
+
if age_days is None:
|
|
92
|
+
return None
|
|
93
|
+
return STALE_TAP if age_days > limit else None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def skill_findings(*, is_local: bool, provenance_status: Optional[str],
|
|
97
|
+
tap_age_days: Optional[int],
|
|
98
|
+
upstream_reason: Optional[str],
|
|
99
|
+
conflicts_with: Sequence[str] = (),
|
|
100
|
+
stale_after: int = STALE_TAP_DAYS) -> List[dict]:
|
|
101
|
+
"""Every trust finding for one installed skill, worst severity first.
|
|
102
|
+
|
|
103
|
+
Each finding is ``{"severity", "label", "detail"}``. The caller supplies
|
|
104
|
+
pre-computed signals:
|
|
105
|
+
|
|
106
|
+
* ``is_local`` — imported from a path (no tap).
|
|
107
|
+
* ``provenance_status`` — a :mod:`boost_cli.core.provenance` status for the
|
|
108
|
+
skill's tap clone, or ``None`` when it could not be read.
|
|
109
|
+
* ``tap_age_days`` — days since the tap clone last synced, or ``None``.
|
|
110
|
+
* ``upstream_reason`` — :func:`staleness.upstream_reason`'s verdict.
|
|
111
|
+
* ``conflicts_with`` — peer skills this one declares a ``conflicts:``
|
|
112
|
+
against, already filtered to skills that are actually installed.
|
|
113
|
+
|
|
114
|
+
A locally-imported skill is deliberately not checked for tap staleness or
|
|
115
|
+
upstream drift: it has no upstream, so those axes are not signals about it.
|
|
116
|
+
"""
|
|
117
|
+
found: List[dict] = []
|
|
118
|
+
label = signing_label(provenance_status, is_local=is_local)
|
|
119
|
+
if label:
|
|
120
|
+
found.append({"severity": SEVERITY[label], "label": label,
|
|
121
|
+
"detail": _signing_detail(label, provenance_status)})
|
|
122
|
+
if not is_local:
|
|
123
|
+
# The `is not None` guard is what stale_tap_label already encodes, but
|
|
124
|
+
# spelling it here keeps the %d interpolation below provably non-None.
|
|
125
|
+
if tap_age_days is not None and stale_tap_label(tap_age_days, stale_after):
|
|
126
|
+
found.append({"severity": SEVERITY[STALE_TAP], "label": STALE_TAP,
|
|
127
|
+
"detail": "tap last synced %d days ago" % tap_age_days})
|
|
128
|
+
if upstream_reason:
|
|
129
|
+
found.append({"severity": SEVERITY[BEHIND_TAP], "label": BEHIND_TAP,
|
|
130
|
+
"detail": "tap has a newer copy (%s)" % upstream_reason})
|
|
131
|
+
found.extend({"severity": SEVERITY[CONFLICT], "label": CONFLICT,
|
|
132
|
+
"detail": "declares a conflict with %s" % peer}
|
|
133
|
+
for peer in conflicts_with)
|
|
134
|
+
return sort_findings(found)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _signing_detail(label: str, status: Optional[str]) -> str:
|
|
138
|
+
"""Human detail for a signing finding."""
|
|
139
|
+
if label == LOCAL_SOURCE:
|
|
140
|
+
return "imported from a path — no tap signature to check"
|
|
141
|
+
if label == UNSIGNED_TAP and status is None:
|
|
142
|
+
return "tap clone is missing — signature cannot be checked"
|
|
143
|
+
if label == UNSIGNED_TAP:
|
|
144
|
+
return "tap publishes no signature"
|
|
145
|
+
if label == UNTRUSTED_TAP:
|
|
146
|
+
return "signed, but by a key that is not in your trusted set"
|
|
147
|
+
return "signature is present but does not validate"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# Sort order for rendering: severity descending, then label, then detail, so
|
|
151
|
+
# output is deterministic (two runs of the same state print byte-identically).
|
|
152
|
+
_SEVERITY_RANK = {HIGH: 0, MED: 1, LOW: 2}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def sort_findings(findings: Sequence[dict]) -> List[dict]:
|
|
156
|
+
"""Findings ordered worst-first, then alphabetically for determinism."""
|
|
157
|
+
return sorted(findings, key=lambda f: (_SEVERITY_RANK[f["severity"]],
|
|
158
|
+
f["label"], f["detail"]))
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def count_severities(findings_by_skill: Dict[str, List[dict]]) -> Dict[str, int]:
|
|
162
|
+
"""Total findings per severity across every skill."""
|
|
163
|
+
counts = {HIGH: 0, MED: 0, LOW: 0}
|
|
164
|
+
for findings in findings_by_skill.values():
|
|
165
|
+
for finding in findings:
|
|
166
|
+
counts[finding["severity"]] += 1
|
|
167
|
+
return counts
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def is_healthy(counts: Dict[str, int]) -> bool:
|
|
171
|
+
"""True when nothing needs a decision — no HIGH and no MED findings.
|
|
172
|
+
|
|
173
|
+
LOW findings alone stay healthy: an unsigned tap is the norm for most of
|
|
174
|
+
the catalog today, so treating it as unhealthy would make the command cry
|
|
175
|
+
wolf on a perfectly ordinary install and train users to ignore it.
|
|
176
|
+
"""
|
|
177
|
+
return not counts[HIGH] and not counts[MED]
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def exit_code(counts: Dict[str, int]) -> int:
|
|
181
|
+
"""``0`` when healthy, ``1`` when a HIGH or MED finding needs attention.
|
|
182
|
+
|
|
183
|
+
Matches ``cmd_audit``'s content-scan contract, so ``--skills`` composes in
|
|
184
|
+
a CI pipeline the same way the default scan already does.
|
|
185
|
+
"""
|
|
186
|
+
return 0 if is_healthy(counts) else 1
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def relation_list(meta: Optional[dict], key: str) -> List[str]:
|
|
190
|
+
"""A skill's ``requires:``/``conflicts:`` frontmatter as a clean name list.
|
|
191
|
+
|
|
192
|
+
Accepts a YAML list or a comma-separated string, drops blanks, and treats a
|
|
193
|
+
missing/empty/``False`` value as "no relations" — an author writing
|
|
194
|
+
``conflicts:`` with nothing after it must not read as a conflict with "".
|
|
195
|
+
|
|
196
|
+
``pkg._rel_list`` and ``info._as_list`` are older command-layer copies of
|
|
197
|
+
this that disagree on edge cases; folding them onto this one is a
|
|
198
|
+
worthwhile follow-up, but retargeting three call sites is out of scope here.
|
|
199
|
+
"""
|
|
200
|
+
val = (meta or {}).get(key)
|
|
201
|
+
if val in (None, "", False):
|
|
202
|
+
return []
|
|
203
|
+
if isinstance(val, list):
|
|
204
|
+
return [str(x).strip() for x in val if str(x).strip()]
|
|
205
|
+
return [s.strip() for s in str(val).split(",") if s.strip()]
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def conflict_pairs(installed: Sequence[str],
|
|
209
|
+
conflicts_of) -> List[Tuple[str, str]]:
|
|
210
|
+
"""``(skill, peer)`` pairs where both sides are installed.
|
|
211
|
+
|
|
212
|
+
Deliberately independent of :func:`resolve.resolve`: that answers "what
|
|
213
|
+
would break if I installed this", keyed off an install plan, while this
|
|
214
|
+
audits a set that is *already* installed — every member is both a root and
|
|
215
|
+
already present, which ``resolve`` prunes. Self-conflicts and conflicts
|
|
216
|
+
against something not installed are dropped; a mutual conflict is reported
|
|
217
|
+
from both sides, because both skills are equally implicated.
|
|
218
|
+
"""
|
|
219
|
+
present = set(installed)
|
|
220
|
+
pairs: List[Tuple[str, str]] = []
|
|
221
|
+
for name in sorted(present):
|
|
222
|
+
pairs.extend((name, peer) for peer in conflicts_of(name)
|
|
223
|
+
if peer and peer != name and peer in present)
|
|
224
|
+
return sorted(set(pairs))
|
|
@@ -82,6 +82,7 @@ boost_cli/core/serve.py
|
|
|
82
82
|
boost_cli/core/stackprobe.py
|
|
83
83
|
boost_cli/core/staleness.py
|
|
84
84
|
boost_cli/core/store.py
|
|
85
|
+
boost_cli/core/trustaudit.py
|
|
85
86
|
boost_cli/core/typosquat.py
|
|
86
87
|
boost_cli/core/updatediff.py
|
|
87
88
|
boost_cli/core/util.py
|
|
@@ -369,6 +370,7 @@ tests/unit/test_stackprobe.py
|
|
|
369
370
|
tests/unit/test_staleness.py
|
|
370
371
|
tests/unit/test_store.py
|
|
371
372
|
tests/unit/test_token_parity.py
|
|
373
|
+
tests/unit/test_trustaudit.py
|
|
372
374
|
tests/unit/test_typosquat.py
|
|
373
375
|
tests/unit/test_updatediff.py
|
|
374
376
|
tests/unit/test_util.py
|
|
@@ -161,7 +161,7 @@
|
|
|
161
161
|
<div class="group-h">Health & integrity</div>
|
|
162
162
|
<a class="cmd-link" href="#cmd-doctor" data-search="doctor check installation health & report issues ">doctor</a>
|
|
163
163
|
<a class="cmd-link" href="#cmd-lint" data-search="lint validate skill.md frontmatter & quality --tap tap --min n --json name">lint</a>
|
|
164
|
-
<a class="cmd-link" href="#cmd-audit" data-search="audit check installed skills against a safety blocklist --json">audit</a>
|
|
164
|
+
<a class="cmd-link" href="#cmd-audit" data-search="audit check installed skills against a safety blocklist --json --skills">audit</a>
|
|
165
165
|
<a class="cmd-link" href="#cmd-verify" data-search="verify validate skill quality & lock-file integrity --json name">verify</a>
|
|
166
166
|
<a class="cmd-link" href="#cmd-drift" data-search="drift detect installed skills diverging from source --json name">drift</a>
|
|
167
167
|
<a class="cmd-link" href="#cmd-test" data-search="test validate installed skills against quality checks name">test</a>
|
|
@@ -790,13 +790,14 @@
|
|
|
790
790
|
<div class="arg"><code>--json</code><span></span></div>
|
|
791
791
|
</div>
|
|
792
792
|
</section>
|
|
793
|
-
<section class="cmd" id="cmd-audit" data-search="audit check installed skills against a safety blocklist --json">
|
|
793
|
+
<section class="cmd" id="cmd-audit" data-search="audit check installed skills against a safety blocklist --json --skills">
|
|
794
794
|
<h3><span class="cname">audit</span><span class="gtag">chk</span></h3>
|
|
795
795
|
<p class="summary">Check installed skills against a safety blocklist</p>
|
|
796
|
-
<pre class="syn">boost audit [--json]</pre>
|
|
796
|
+
<pre class="syn">boost audit [--json] [--skills]</pre>
|
|
797
797
|
<div class="args-h">Options</div>
|
|
798
798
|
<div class="args">
|
|
799
799
|
<div class="arg"><code>--json</code><span></span></div>
|
|
800
|
+
<div class="arg"><code>--skills</code><span>trust/staleness report for installed skills instead of the content scan</span></div>
|
|
800
801
|
</div>
|
|
801
802
|
</section>
|
|
802
803
|
<section class="cmd" id="cmd-verify" data-search="verify validate skill quality & lock-file integrity --json name">
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: installed-skill-trust-audit
|
|
3
|
+
board: code
|
|
4
|
+
section: trust
|
|
5
|
+
status: shipped
|
|
6
|
+
category: Trust · Health
|
|
7
|
+
complexity: M
|
|
8
|
+
impact: Med
|
|
9
|
+
wow: 3
|
|
10
|
+
note: audit what you run
|
|
11
|
+
order: 47
|
|
12
|
+
owner: loop/trust-audit
|
|
13
|
+
pr: 233
|
|
14
|
+
title: <code>boost audit --skills</code> — a trust/staleness report for installed skills
|
|
15
|
+
---
|
|
16
|
+
boost already computes every individual trust signal, each in its own command:
|
|
17
|
+
<code>verify</code> checks lock-file integrity, <code>outdated</code> compares an
|
|
18
|
+
installed skill against its tap, <code>trust</code> reports tap-level signing
|
|
19
|
+
provenance, <code>deps</code> shows the <code>requires:</code>/<code>conflicts:</code>
|
|
20
|
+
graph. What nothing answered is the aggregate question — <em>of the skills I
|
|
21
|
+
actually run, which ones should I stop trusting?</em> <code>boost audit --skills</code>
|
|
22
|
+
gathers all of it into one report: every installed skill that is unsigned, signed
|
|
23
|
+
by an untrusted key, signed but failing verification, sitting on a tap nobody has
|
|
24
|
+
synced in a month, behind its tap, or conflicting with another installed skill.
|
|
25
|
+
The decision layer is <code>core/trustaudit.py</code> — pure and I/O-free like
|
|
26
|
+
<code>core/staleness.py</code>, so every branch is unit-tested and reachable by
|
|
27
|
+
the mutation gate. Only a malformed signature is HIGH; an unsigned tap is the norm
|
|
28
|
+
for most of the catalog today and stays LOW, so the command never cries wolf on an
|
|
29
|
+
ordinary install.
|
|
@@ -2300,18 +2300,22 @@ paths.</p>
|
|
|
2300
2300
|
</article>
|
|
2301
2301
|
|
|
2302
2302
|
<article class="cap rcard" id="installed-skill-trust-audit">
|
|
2303
|
-
<div class="head"><span class="pill
|
|
2303
|
+
<div class="head"><span class="pill shipped">Shipped</span><span class="cat">Trust · Health</span></div>
|
|
2304
2304
|
<h3><code>boost audit --skills</code> — a trust/staleness report for installed skills</h3>
|
|
2305
|
-
<p
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
is the
|
|
2310
|
-
<code>boost audit --skills</code>
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2305
|
+
<p>boost already computes every individual trust signal, each in its own command:
|
|
2306
|
+
<code>verify</code> checks lock-file integrity, <code>outdated</code> compares an
|
|
2307
|
+
installed skill against its tap, <code>trust</code> reports tap-level signing
|
|
2308
|
+
provenance, <code>deps</code> shows the <code>requires:</code>/<code>conflicts:</code>
|
|
2309
|
+
graph. What nothing answered is the aggregate question — <em>of the skills I
|
|
2310
|
+
actually run, which ones should I stop trusting?</em> <code>boost audit --skills</code>
|
|
2311
|
+
gathers all of it into one report: every installed skill that is unsigned, signed
|
|
2312
|
+
by an untrusted key, signed but failing verification, sitting on a tap nobody has
|
|
2313
|
+
synced in a month, behind its tap, or conflicting with another installed skill.
|
|
2314
|
+
The decision layer is <code>core/trustaudit.py</code> — pure and I/O-free like
|
|
2315
|
+
<code>core/staleness.py</code>, so every branch is unit-tested and reachable by
|
|
2316
|
+
the mutation gate. Only a malformed signature is HIGH; an unsigned tap is the norm
|
|
2317
|
+
for most of the catalog today and stays LOW, so the command never cries wolf on an
|
|
2318
|
+
ordinary install.</p>
|
|
2315
2319
|
<div class="meta">
|
|
2316
2320
|
<span class="m">Complexity <b>M</b></span>
|
|
2317
2321
|
<span class="m">Impact <b>Med</b></span>
|
|
@@ -217,6 +217,71 @@ class TestAudit:
|
|
|
217
217
|
assert all(f["severity"] == "HIGH"
|
|
218
218
|
for f in data["findings"]["danger-skill"])
|
|
219
219
|
|
|
220
|
+
def test_skills_flag_reports_the_unsigned_fixture_tap(self, boost, installed):
|
|
221
|
+
# the fixture tap publishes no signature: one LOW finding, still rc 0 —
|
|
222
|
+
# LOW alone must not fail, or the command cries wolf on every install.
|
|
223
|
+
r = boost("audit", "--skills")
|
|
224
|
+
assert "trust audit — 1 skill" in r.out
|
|
225
|
+
assert "brainstorming" in r.out
|
|
226
|
+
assert "LOW" in r.out
|
|
227
|
+
assert "unsigned-tap" in r.out
|
|
228
|
+
assert "tap publishes no signature" in r.out
|
|
229
|
+
assert "0 high · 0 medium · 1 low across 1 of 1 skill" in r.out
|
|
230
|
+
|
|
231
|
+
def test_skills_json_shape(self, boost, installed):
|
|
232
|
+
data = json.loads(boost("audit", "--skills", "--json").out)
|
|
233
|
+
assert data["skills_scanned"] == 1
|
|
234
|
+
assert data["counts"] == {"HIGH": 0, "MED": 0, "LOW": 1}
|
|
235
|
+
assert [f["label"] for f in data["findings"]["brainstorming"]] \
|
|
236
|
+
== ["unsigned-tap"]
|
|
237
|
+
|
|
238
|
+
def test_skills_nothing_installed(self, boost, sandbox):
|
|
239
|
+
r = boost("audit", "--skills")
|
|
240
|
+
assert "nothing installed yet" in r.out
|
|
241
|
+
|
|
242
|
+
def test_skills_local_import_reports_local_source(self, boost, tmp_path):
|
|
243
|
+
_import_skill(boost, tmp_path, "local-skill", "# Local\n\nNothing here.\n")
|
|
244
|
+
data = json.loads(boost("audit", "--skills", "--json").out)
|
|
245
|
+
assert [f["label"] for f in data["findings"]["local-skill"]] \
|
|
246
|
+
== ["local-source"]
|
|
247
|
+
assert data["counts"]["LOW"] == 1
|
|
248
|
+
|
|
249
|
+
def test_skills_conflict_between_installed_is_medium_rc1(self, boost, tmp_path):
|
|
250
|
+
_import_skill(boost, tmp_path, "alpha", "# Alpha\n",
|
|
251
|
+
extra_fm="conflicts: beta\n")
|
|
252
|
+
_import_skill(boost, tmp_path, "beta", "# Beta\n")
|
|
253
|
+
r = boost("audit", "--skills", expect=1)
|
|
254
|
+
assert "conflict" in r.out
|
|
255
|
+
assert "declares a conflict with beta" in r.out
|
|
256
|
+
assert "1 high · 0 medium" not in r.out # a conflict is MED, not HIGH
|
|
257
|
+
|
|
258
|
+
data = json.loads(boost("audit", "--skills", "--json", expect=1).out)
|
|
259
|
+
assert data["counts"]["MED"] == 1
|
|
260
|
+
labels = [f["label"] for f in data["findings"]["alpha"]]
|
|
261
|
+
assert "conflict" in labels
|
|
262
|
+
# beta never declared anything, so it gets no conflict finding
|
|
263
|
+
assert "conflict" not in [f["label"] for f in data["findings"]["beta"]]
|
|
264
|
+
|
|
265
|
+
def test_skills_conflict_against_uninstalled_peer_is_silent(self, boost, tmp_path):
|
|
266
|
+
_import_skill(boost, tmp_path, "alpha", "# Alpha\n",
|
|
267
|
+
extra_fm="conflicts: never-installed\n")
|
|
268
|
+
data = json.loads(boost("audit", "--skills", "--json").out)
|
|
269
|
+
assert [f["label"] for f in data["findings"]["alpha"]] == ["local-source"]
|
|
270
|
+
|
|
271
|
+
def test_skills_behind_tap(self, boost, fixture_tap_src, tmp_path):
|
|
272
|
+
tap_dir = _copy_tap(fixture_tap_src, tmp_path / "bumped-tap")
|
|
273
|
+
boost("tap", tap_dir)
|
|
274
|
+
boost("install", "brainstorming")
|
|
275
|
+
_bump(tap_dir, "brainstorming", "1.4.0", "1.5.0")
|
|
276
|
+
boost("update", "--taps-only")
|
|
277
|
+
|
|
278
|
+
data = json.loads(boost("audit", "--skills", "--json").out)
|
|
279
|
+
labels = [f["label"] for f in data["findings"]["brainstorming"]]
|
|
280
|
+
assert "behind-tap" in labels
|
|
281
|
+
detail = next(f["detail"] for f in data["findings"]["brainstorming"]
|
|
282
|
+
if f["label"] == "behind-tap")
|
|
283
|
+
assert detail == "tap has a newer copy (version)"
|
|
284
|
+
|
|
220
285
|
def test_blocked_skills_policy_hit(self, boost, installed):
|
|
221
286
|
(paths.state_dir() / "policy.json").write_text(
|
|
222
287
|
json.dumps({"blocked_skills": ["brainstorming"]}), encoding="utf-8")
|