boost-skill-cli 1.0.149__tar.gz → 1.0.150__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.149 → boost_skill_cli-1.0.150}/PKG-INFO +1 -1
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/_version.py +2 -2
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/commands/discovery.py +170 -42
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_skill_cli.egg-info/PKG-INFO +1 -1
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/functional/test_cli_discovery.py +142 -44
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/.gitattributes +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/.gitignore +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/CLAUDE.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/CONTRIBUTING.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/LICENSE +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/MANIFEST.in +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/Makefile +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/README.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/SECURITY.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/__init__.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/__main__.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/cli.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/cliparse.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/commands/__init__.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/commands/bmad.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/commands/configuration.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/commands/hooks.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/commands/info.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/commands/intelligence.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/commands/pkg.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/commands/quality.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/commands/taps.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/commands/team.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/__init__.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/adapters.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/agents.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/ai.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/catalog.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/claude_settings.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/config.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/dense.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/embed.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/frontmatter.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/gitutil.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/imperative.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/injectscan.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/journal.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/lockfile.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/logs.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/mcp.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/nethttp.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/output.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/paths.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/policy.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/rag.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/registry.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/rules.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/secretscan.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/serve.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/stackprobe.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/staleness.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/store.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/typosquat.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/updatediff.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/util.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/core/workflows.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/data/registries.json +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/errors.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_cli/spin.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_skill_cli.egg-info/SOURCES.txt +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_skill_cli.egg-info/dependency_links.txt +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_skill_cli.egg-info/entry_points.txt +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_skill_cli.egg-info/requires.txt +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/boost_skill_cli.egg-info/top_level.txt +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/.claude/settings.local.json +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/DEBUGGING.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/adapters.html +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/demo.gif +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/demo.tape +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/design-roadmap.html +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/eval.html +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/index.html +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/mcp-hub.html +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/rag-architecture.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D01.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D02.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D03.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D04.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D05.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D06.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D07.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D08.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D09.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D10.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D11.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D12.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D13.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D14.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D15.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D16.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D17.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D18.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D19.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D20.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D21.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D22.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D23.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D24.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D25.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/BOOST-D26.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/accessibility-audit-pa11y-ci-axe-core.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/atomic-corruption-safe-lock-file-writes.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/atomic-skill-install-temp-dir-swap.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/autonomous-ship-workflow-and-isolated-worktree.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/boost-run-live-agents.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/bring-commands-under-mutation-testing.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/broken-link-and-anchor-checking-lychee.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/browse-crashes-selecting-a-rule-or-workflow.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/build-provenance-slsa-attestations.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/cache-the-catalog-entry-set-across-rag-queries.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/capability-manifest-and-least-privilege-policy.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/clean-env-install-smoke-pip-and-pipx.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/command-reference-docs-site.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/complexity-and-dead-code-radar-xenon-vulture.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/consolidate-skill-staleness-drift-logic-into-cor.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/coverage-dashboard-codecov-free-for-oss.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/coverage-guided-fuzzing-atheris-oss-fuzz.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/crash-recorder-error-paths-core-logs-py.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/dependency-cve-gate-pip-audit.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/docstring-coverage-interrogate.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/doctor-checks-rules-and-workflows.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/end-of-options-guard-on-git-commands.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/extension-free-tests-core-dense-py.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/extract-mcp-http-servers-out-of-configuration-py.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/finish-mutation-hardening-across-core.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/fix-self-update-version-detection-dead-branch.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/fork-safe-network-proxy-handler.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/framework-adapter-langgraph.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/framework-adapter-multi-agent.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/framework-adapters-boost-adapt.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/frontmatter-scalar-over-coercion.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/github-pages-deploy-is-broken-every-push.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/golden-set-statistical-power.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/hash-pinned-reproducible-toolchain-uv-lock.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/html-validation-html-validate.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/install-scope-user-or-project.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/integrity-verification-boost-verify.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/invocation-pid-logging-for-crash-correlation.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/layering-guard-import-linter.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/lighthouse-ci-on-the-pages-site.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/list-rules-and-workflows.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/localize-the-stored-bm25-snippet.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/lockfile-enforcement-and-commit-pinning.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/log-timestamps-mislabeled-utc.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/lowest-version-resolution-uv-resolution-lowest-d.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/make-the-roadmaps-discoverable.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/markdown-consistency-markdownlint-cli2.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/mcp-hub-diagram-node-text-overflow.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/mcp-launch-objc-fork-safety.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/mcp-register-names-server-before-env.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/mcp-startup-self-harden-fork-safety.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/memoize-config-load-in-process.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/modernization-smells-refurb-pyupgrade.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/mutation-hardening-core-frontmatter-py.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/mutation-hardening-core-gitutil-py.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/mutation-hardening-core-store-py.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/one-command-every-env-nox.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/one-shared-atomic-write-helper.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/package-metadata-validation-twine-check-friends.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/patch-coverage-gate-diff-cover.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/performance-regression-gate-pytest-benchmark.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/post-deploy-smoke-headless-load-check.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/pre-release-python-canary-3-14t-free-threaded.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/promote-nav-footer-into-the-shared-style-system.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/prompt-injection-scanning-of-skill-markdown.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/property-based-tests-hypothesis-on-the-parsers.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/prose-and-terminology-linting-vale.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/prune-ignored-dirs-during-scan-dir-walk.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/quality-dashboard-sonarcloud-free-for-oss.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/ranx-significance-monitor.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/reconcile-the-theme-drift.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/refresh-the-marketing-surface.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/retrieval-quality-eval-harness.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/reuse-helpers-kill-minor-dead-work.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/robust-tag-argument-parsing.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/rule-install-native-materialization.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/runner-egress-monitoring-stepsecurity-harden-run.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/runtime-explain-faithfulness-guardrail.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/sbom-aware-scanning-osv-scanner.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/sbom-on-every-release-cyclonedx-syft.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/scan-and-sync-rules-and-workflows.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/second-type-checker-pyright.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/secret-and-pii-scanning-of-installed-skills.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/secret-scanning-gitleaks-push-protection.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/security-linting-bandit-via-ruff-s-rules.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/shift-left-gate-pre-commit-pre-commit-ci.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/single-imperative-rule-extractor.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/single-tech-stack-prober.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/split-oversized-command-modules.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/startup-and-import-time-budget-x-importtime.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/stop-re-serializing-entry-meta-on-every-search.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/supply-chain-posture-openssf-scorecard.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/surface-every-docs-page-from-the-guide.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/tap-signing-and-provenance-sigstore-minisign.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/theme-asset-linting-stylelint-eslint.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/typo-detection-codespell.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/typosquat-and-name-confusion-detection.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/unify-tilde-two-copies-have-a-boundary-bug.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/untrack-generated-build-noise.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/update-diff-before-apply.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/update-refreshes-rules-and-workflows.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/visual-regression-pass-on-the-guide.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/widen-the-ruff-rule-surface-b-sim-c4-perf-ruf.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/windows-in-the-ci-matrix.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/workflow-install-native-materialization.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/workflow-linting-actionlint.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/workflow-sast-zizmor.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap/items/workspace-scope-install.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/docs/roadmap.html +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/examples/README.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/examples/adapt-demo.sh +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/examples/boost-run-prototype.sh +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/index.html +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/pyproject.toml +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/scripts/bench_cli.sh +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/scripts/build_registries.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/scripts/build_roadmap.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/scripts/ensure_eval_corpus.sh +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/scripts/eval_explain.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/scripts/eval_recommend.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/scripts/eval_retrieval.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/scripts/mutation_gate.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/setup.cfg +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/style/README.md +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/style/boost.css +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/style/boost.js +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/style/demo.html +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/bdd/features/adapt.feature +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/bdd/features/browse.feature +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/bdd/features/count.feature +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/bdd/features/discover.feature +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/bdd/features/doctor.feature +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/bdd/features/environment.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/bdd/features/explain.feature +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/bdd/features/index.feature +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/bdd/features/install.feature +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/bdd/features/list.feature +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/bdd/features/mcp.feature +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/bdd/features/search.feature +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/bdd/features/steps/cli_steps.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/bdd/features/steps/discovery_steps.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/bdd/features/steps/mcp_steps.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/bdd/features/steps/quality_steps.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/conftest.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/eval/baseline.json +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/eval/explain.jsonl +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/eval/golden.jsonl +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/eval/recommend.jsonl +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/eval/taps.txt +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/functional/test_cli_adapt.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/functional/test_cli_bmad.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/functional/test_cli_configuration.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/functional/test_cli_hooks.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/functional/test_cli_info.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/functional/test_cli_intelligence.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/functional/test_cli_pkg.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/functional/test_cli_quality.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/functional/test_cli_taps.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/functional/test_cli_team.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/functional/test_everyday_loop.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/functional/test_property_parsers.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/make_fixture.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/perf/test_benchmarks.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/smoke.sh +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_adapters.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_agents.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_ai.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_catalog.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_claude_settings.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_cliparse.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_config.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_dense.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_dense_fallback.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_docs_pages_linked.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_embed.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_errors_and_cli_table.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_frontmatter.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_gitutil.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_imperative.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_injectscan.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_journal.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_lockfile.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_logs.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_mcp.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_mutation_hardening.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_nethttp.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_output.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_paths.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_pkg_confusions.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_pkg_injection.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_pkg_secrets.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_policy.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_rag.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_registries_fresh.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_registry.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_roadmap_fresh.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_rules.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_secretscan.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_serve.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_spin.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_stackprobe.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_staleness.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_store.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_token_parity.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_typosquat.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_updatediff.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_util.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_version.py +0 -0
- {boost_skill_cli-1.0.149 → boost_skill_cli-1.0.150}/tests/unit/test_workflows.py +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.150'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 0, 150)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -11,12 +11,14 @@ import re
|
|
|
11
11
|
import shutil
|
|
12
12
|
import subprocess
|
|
13
13
|
import sys
|
|
14
|
+
import threading
|
|
14
15
|
import time
|
|
15
16
|
import urllib.parse
|
|
16
17
|
from pathlib import Path
|
|
17
18
|
|
|
18
19
|
from .. import cliparse, spin
|
|
19
|
-
from ..core import agents, ai, catalog, dense, embed, gitutil,
|
|
20
|
+
from ..core import (agents, ai, catalog, config, dense, embed, gitutil,
|
|
21
|
+
journal, lockfile, paths, rag, registry, store, util)
|
|
20
22
|
from ..core import output as out
|
|
21
23
|
from ..core.stackprobe import detect_stack # re-exported: shared with Quality
|
|
22
24
|
from ..errors import BoostError
|
|
@@ -451,6 +453,77 @@ def _browse_plain(entries, why: str):
|
|
|
451
453
|
return 0
|
|
452
454
|
|
|
453
455
|
|
|
456
|
+
def _tap_categories() -> dict:
|
|
457
|
+
"""tap name ('owner/repo') -> curated category, from the bundled registry
|
|
458
|
+
catalog (data/registries.json) — the only place "category" lives; catalog
|
|
459
|
+
entries themselves carry no category, only their tap does."""
|
|
460
|
+
return {e["name"]: e["category"] for e in config.load_registry_catalog()
|
|
461
|
+
if e.get("category")}
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
_KIND_BADGE_KEYS = {"skill": "badge_skill", "rule": "badge_rule",
|
|
465
|
+
"workflow": "badge_workflow"}
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def _kind_theme_key(kind: str) -> str:
|
|
469
|
+
return _KIND_BADGE_KEYS.get(kind, "badge_skill")
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _row_badges(e: dict, categories: dict):
|
|
473
|
+
"""Ordered (text, theme-key) badges for a browse row: kind, version, tap,
|
|
474
|
+
and — when the tap is one of the curated registries — its category.
|
|
475
|
+
Most-important-first, so a narrow terminal drops from the tail without
|
|
476
|
+
losing the essentials (kind and version survive; category goes first)."""
|
|
477
|
+
badges = [("[%s]" % e.get("kind", "skill"),
|
|
478
|
+
_kind_theme_key(e.get("kind", "skill"))),
|
|
479
|
+
("v" + e["version"], "version"),
|
|
480
|
+
("[%s]" % e["tap"], "tap")]
|
|
481
|
+
category = categories.get(e["tap"])
|
|
482
|
+
if category:
|
|
483
|
+
badges.append(("[%s]" % category, "badge_category"))
|
|
484
|
+
return badges
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def _desc_key(e: dict):
|
|
488
|
+
return (e.get("tap", ""), e["name"])
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def _entry_source_path(e: dict):
|
|
492
|
+
"""On-disk path of the file backing a catalog entry, or None when its tap
|
|
493
|
+
isn't cloned/known — used to fall back to a `boost explain`-style summary
|
|
494
|
+
for entries whose frontmatter has no description."""
|
|
495
|
+
try:
|
|
496
|
+
tap = registry.get(e["tap"])
|
|
497
|
+
except BoostError:
|
|
498
|
+
return None
|
|
499
|
+
path = tap.path / e.get("skill_md", "")
|
|
500
|
+
return path if path.is_file() else None
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _lazy_description(e: dict) -> str:
|
|
504
|
+
"""A one-line fallback description for an entry with no frontmatter
|
|
505
|
+
description — the `boost explain` idea, sized for a picker row. Meant to
|
|
506
|
+
run off the UI thread since it may shell out to `claude`/the Anthropic API."""
|
|
507
|
+
path = _entry_source_path(e)
|
|
508
|
+
text = ""
|
|
509
|
+
if path is not None:
|
|
510
|
+
try:
|
|
511
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
512
|
+
except OSError:
|
|
513
|
+
text = ""
|
|
514
|
+
if text and ai.available():
|
|
515
|
+
reply = ai.ask(
|
|
516
|
+
"In ONE short plain sentence (max 18 words, no markdown), describe "
|
|
517
|
+
"what this %s named %r does, based on its file:\n\n%s"
|
|
518
|
+
% (e.get("kind", "skill"), e["name"], text[:4000]),
|
|
519
|
+
system="You summarize AI coding skills, rules, and workflows in "
|
|
520
|
+
"one plain sentence for a picker UI.",
|
|
521
|
+
max_tokens=60)
|
|
522
|
+
if reply:
|
|
523
|
+
return reply.strip().splitlines()[0][:200]
|
|
524
|
+
return "no description available"
|
|
525
|
+
|
|
526
|
+
|
|
454
527
|
# --- Aurora palette for the curses browser -----------------------------------
|
|
455
528
|
# The full-screen browser paints itself in the same Aurora tokens as the rest
|
|
456
529
|
# of the CLI. curses can't reuse the ANSI escapes from core.output, so these
|
|
@@ -561,7 +634,10 @@ def _aurora_theme(curses):
|
|
|
561
634
|
"scroll": curses.A_DIM, "detail_name": curses.A_BOLD,
|
|
562
635
|
"detail_meta": curses.A_DIM, "detail_tag": curses.A_NORMAL,
|
|
563
636
|
"dot_r": curses.A_NORMAL, "dot_y": curses.A_NORMAL,
|
|
564
|
-
"dot_g": curses.A_NORMAL
|
|
637
|
+
"dot_g": curses.A_NORMAL, "check": curses.A_BOLD,
|
|
638
|
+
"badge_skill": curses.A_NORMAL, "badge_rule": curses.A_NORMAL,
|
|
639
|
+
"badge_workflow": curses.A_NORMAL, "badge_category": curses.A_NORMAL,
|
|
640
|
+
"desc": curses.A_DIM}
|
|
565
641
|
if not hasattr(curses, "start_color"):
|
|
566
642
|
return t
|
|
567
643
|
try:
|
|
@@ -581,19 +657,49 @@ def _aurora_theme(curses):
|
|
|
581
657
|
"tap": P("violet"), "rule": [P("cyan"), P("violet"), P("pink")],
|
|
582
658
|
"scroll": P("cyan"), "detail_name": P("cyan", True),
|
|
583
659
|
"detail_tag": P("pink"), "dot_r": P("red"), "dot_y": P("yellow"),
|
|
584
|
-
"dot_g": P("green")
|
|
660
|
+
"dot_g": P("green"), "check": P("green", True),
|
|
661
|
+
"badge_skill": P("cyan"), "badge_rule": P("violet"),
|
|
662
|
+
"badge_workflow": P("pink"), "badge_category": P("yellow")})
|
|
585
663
|
return t
|
|
586
664
|
|
|
587
665
|
|
|
666
|
+
_NAME_X = 4 # column the name (and everything under it) starts at
|
|
667
|
+
|
|
668
|
+
|
|
588
669
|
def _browse_tui(curses, entries):
|
|
589
|
-
"""Run the curses UI. Returns the
|
|
590
|
-
|
|
670
|
+
"""Run the curses UI. Returns the list of entries picked for install
|
|
671
|
+
(one or more, via multi-select), or None if the user quit without picking.
|
|
672
|
+
"""
|
|
673
|
+
state = {"picks": None}
|
|
674
|
+
categories = _tap_categories()
|
|
675
|
+
desc_cache: dict = {}
|
|
676
|
+
loading: set = set()
|
|
677
|
+
|
|
678
|
+
def describe(e):
|
|
679
|
+
"""Description text for a row, kicking off a background lazy-load
|
|
680
|
+
(the `boost explain` fallback) the first time an entry with no
|
|
681
|
+
frontmatter description is shown — never blocks the draw loop."""
|
|
682
|
+
if e.get("description"):
|
|
683
|
+
return e["description"]
|
|
684
|
+
key = _desc_key(e)
|
|
685
|
+
if key in desc_cache:
|
|
686
|
+
return desc_cache[key]
|
|
687
|
+
if key not in loading:
|
|
688
|
+
loading.add(key)
|
|
689
|
+
|
|
690
|
+
def worker(entry=e, cache_key=key):
|
|
691
|
+
desc_cache[cache_key] = _lazy_description(entry)
|
|
692
|
+
loading.discard(cache_key)
|
|
693
|
+
|
|
694
|
+
threading.Thread(target=worker, daemon=True).start()
|
|
695
|
+
return "loading description…"
|
|
591
696
|
|
|
592
697
|
def ui(scr):
|
|
593
698
|
try:
|
|
594
699
|
curses.curs_set(0)
|
|
595
700
|
except curses.error:
|
|
596
701
|
pass
|
|
702
|
+
scr.timeout(80) # short poll so a finished lazy-load redraws promptly
|
|
597
703
|
th = _aurora_theme(curses)
|
|
598
704
|
|
|
599
705
|
def put(y, x, s, attr=0): # clip-safe cell writer
|
|
@@ -604,6 +710,7 @@ def _browse_tui(curses, entries):
|
|
|
604
710
|
pass
|
|
605
711
|
|
|
606
712
|
filt, sel, detail = "", 0, False
|
|
713
|
+
selected: set = set()
|
|
607
714
|
while True:
|
|
608
715
|
q = filt.lower()
|
|
609
716
|
matches = [e for e in entries
|
|
@@ -627,35 +734,46 @@ def _browse_tui(curses, entries):
|
|
|
627
734
|
put(1, 0, "❯", th["prompt"])
|
|
628
735
|
put(1, 2, filt, th["query"])
|
|
629
736
|
put(1, 2 + len(filt), "▏", th["prompt"])
|
|
630
|
-
|
|
737
|
+
sel_txt = "%d selected · " % len(selected) if selected else ""
|
|
738
|
+
count = "%s%d/%d" % (sel_txt, len(matches), len(entries))
|
|
631
739
|
put(1, max(2 + len(filt) + 2, _w - 1 - len(count)), count,
|
|
632
740
|
th["count"])
|
|
633
|
-
put(2, 0, "↑↓ move ↵ detail i install q quit",
|
|
741
|
+
put(2, 0, "↑↓ move space select ↵ detail i install q quit",
|
|
634
742
|
th["help"])
|
|
635
|
-
|
|
743
|
+
# each result is a two-row card: name/badges, then description
|
|
744
|
+
avail = max(0, _h - 3 - pane)
|
|
745
|
+
rows = max(1, avail // 2)
|
|
636
746
|
top = max(0, sel - rows + 1)
|
|
637
747
|
view = matches[top:top + rows]
|
|
638
748
|
namew = min(max((len(e["name"]) for e in view), default=4),
|
|
639
|
-
max(
|
|
749
|
+
max(8, _w - _NAME_X - 30))
|
|
640
750
|
bar = _scrollbar(len(matches), rows, top)
|
|
641
751
|
for i, e in enumerate(view):
|
|
642
|
-
y = 3 + i
|
|
752
|
+
y = 3 + i * 2
|
|
643
753
|
chosen = top + i == sel
|
|
754
|
+
checked = _desc_key(e) in selected
|
|
644
755
|
put(y, 0, "▎" if chosen else " ", th["sel_bar"])
|
|
645
|
-
put(y, 1, "
|
|
756
|
+
put(y, 1, "✓" if checked else " ", th["check"])
|
|
757
|
+
put(y, 2, "★" if e.get("curated") else " ", th["star"])
|
|
646
758
|
nm = out.truncate(e["name"], namew)
|
|
647
|
-
put(y,
|
|
759
|
+
put(y, _NAME_X, nm.ljust(namew),
|
|
648
760
|
th["sel_name"] if chosen else th["name"])
|
|
649
761
|
for pos in _match_positions(q, nm.lower()):
|
|
650
|
-
put(y,
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
762
|
+
put(y, _NAME_X + pos, nm[pos], th["match"])
|
|
763
|
+
bx = _NAME_X + namew + 2
|
|
764
|
+
for text, tkey in _row_badges(e, categories):
|
|
765
|
+
if bx + len(text) > _w - 2:
|
|
766
|
+
break
|
|
767
|
+
put(y, bx, text, th[tkey])
|
|
768
|
+
bx += len(text) + 1
|
|
769
|
+
put(y + 1, _NAME_X, out.truncate(describe(e), _w - 1 - _NAME_X),
|
|
770
|
+
th["desc"])
|
|
654
771
|
if bar:
|
|
655
772
|
bstart, blen = bar
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
773
|
+
thumb = bstart <= i < bstart + blen
|
|
774
|
+
ch = "█" if thumb else "│"
|
|
775
|
+
put(y, _w - 2, ch, th["scroll"])
|
|
776
|
+
put(y + 1, _w - 2, ch, th["scroll"])
|
|
659
777
|
if pane and matches:
|
|
660
778
|
e = matches[sel]
|
|
661
779
|
y0 = _h - pane
|
|
@@ -666,7 +784,7 @@ def _browse_tui(curses, entries):
|
|
|
666
784
|
(e.get("meta", {}).get("tags") or []))
|
|
667
785
|
put(y0 + 1, 1, "%s v%s" % (e["name"], e["version"]),
|
|
668
786
|
th["detail_name"])
|
|
669
|
-
put(y0 + 2, 1, out.truncate(e
|
|
787
|
+
put(y0 + 2, 1, out.truncate(describe(e), _w - 3),
|
|
670
788
|
th["name"])
|
|
671
789
|
put(y0 + 3, 1, "tap %s dir %s" % (e["tap"], e["rel_dir"]),
|
|
672
790
|
th["detail_meta"])
|
|
@@ -675,12 +793,21 @@ def _browse_tui(curses, entries):
|
|
|
675
793
|
except curses.error:
|
|
676
794
|
pass # terminal too small mid-draw; retry on next key
|
|
677
795
|
key = scr.getch()
|
|
796
|
+
if key == -1: # poll tick, no key
|
|
797
|
+
continue
|
|
678
798
|
if key in (ord("q"), 27): # q / ESC
|
|
679
799
|
return
|
|
680
800
|
if key == ord("i") and matches:
|
|
681
|
-
state["
|
|
801
|
+
state["picks"] = ([e for e in entries if _desc_key(e) in selected]
|
|
802
|
+
if selected else [matches[sel]])
|
|
682
803
|
return
|
|
683
|
-
if key ==
|
|
804
|
+
if key == ord(" ") and matches:
|
|
805
|
+
k = _desc_key(matches[sel])
|
|
806
|
+
if k in selected:
|
|
807
|
+
selected.discard(k)
|
|
808
|
+
else:
|
|
809
|
+
selected.add(k)
|
|
810
|
+
elif key == curses.KEY_UP:
|
|
684
811
|
sel = max(0, sel - 1)
|
|
685
812
|
elif key == curses.KEY_DOWN:
|
|
686
813
|
sel = min(sel + 1, max(0, len(matches) - 1))
|
|
@@ -688,11 +815,11 @@ def _browse_tui(curses, entries):
|
|
|
688
815
|
detail = not detail
|
|
689
816
|
elif key in (curses.KEY_BACKSPACE, 127, 8):
|
|
690
817
|
filt = filt[:-1]
|
|
691
|
-
elif
|
|
818
|
+
elif 33 <= key <= 126: # printable, not space
|
|
692
819
|
filt += chr(key)
|
|
693
820
|
|
|
694
821
|
curses.wrapper(ui) # wrapper guards drawing with try/finally endwin()
|
|
695
|
-
return state["
|
|
822
|
+
return state["picks"]
|
|
696
823
|
|
|
697
824
|
|
|
698
825
|
def cmd_browse(argv):
|
|
@@ -712,25 +839,26 @@ def cmd_browse(argv):
|
|
|
712
839
|
except ImportError:
|
|
713
840
|
return _browse_plain(entries, "curses is unavailable on this Python")
|
|
714
841
|
picked = _browse_tui(curses, entries)
|
|
715
|
-
if picked
|
|
842
|
+
if not picked:
|
|
716
843
|
return 0
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
844
|
+
for entry in picked:
|
|
845
|
+
try:
|
|
846
|
+
res = store.install(entry)
|
|
847
|
+
except BoostError as e:
|
|
848
|
+
# browse lists every catalog kind (skills, rules, workflows), but
|
|
849
|
+
# only skills install; a rule/workflow pick — or an already-
|
|
850
|
+
# installed/pinned skill — must not abort the rest of the batch.
|
|
851
|
+
out.warn(e.message)
|
|
852
|
+
if e.hint:
|
|
853
|
+
out.info(out.c(e.hint, out.DIM))
|
|
854
|
+
continue
|
|
855
|
+
out.ok("installed %s v%s → %s"
|
|
856
|
+
% (entry["name"], entry["version"], _tilde(res.dest)))
|
|
857
|
+
if res.linked:
|
|
858
|
+
out.info("linked into: "
|
|
859
|
+
+ ", ".join(agents.display_name(a) for a in res.linked))
|
|
860
|
+
for conflict in res.conflicts:
|
|
861
|
+
out.warn("conflict: %s exists and is not a symlink" % _tilde(conflict))
|
|
734
862
|
return 0
|
|
735
863
|
|
|
736
864
|
|
|
@@ -569,6 +569,49 @@ class TestRecommend:
|
|
|
569
569
|
|
|
570
570
|
# ---------------------------------------------------------------- browse
|
|
571
571
|
|
|
572
|
+
class _FakeCurses:
|
|
573
|
+
"""Minimal curses stand-in for driving _browse_tui without a real TTY."""
|
|
574
|
+
|
|
575
|
+
A_BOLD, A_DIM, A_REVERSE, A_NORMAL = 1, 2, 4, 0
|
|
576
|
+
KEY_UP, KEY_DOWN, KEY_ENTER, KEY_BACKSPACE = 259, 258, 343, 263
|
|
577
|
+
ACS_HLINE = ord("-")
|
|
578
|
+
|
|
579
|
+
class error(Exception):
|
|
580
|
+
pass
|
|
581
|
+
|
|
582
|
+
def __init__(self, keys):
|
|
583
|
+
self.keys = list(keys)
|
|
584
|
+
self.drawn = []
|
|
585
|
+
|
|
586
|
+
def curs_set(self, n):
|
|
587
|
+
raise self.error("no cursor support")
|
|
588
|
+
|
|
589
|
+
def timeout(self, ms):
|
|
590
|
+
pass
|
|
591
|
+
|
|
592
|
+
def wrapper(self, fn):
|
|
593
|
+
fn(self)
|
|
594
|
+
|
|
595
|
+
# screen protocol
|
|
596
|
+
def getmaxyx(self):
|
|
597
|
+
return (24, 80)
|
|
598
|
+
|
|
599
|
+
def erase(self):
|
|
600
|
+
pass
|
|
601
|
+
|
|
602
|
+
def addnstr(self, y, x, s, n, attr=0):
|
|
603
|
+
self.drawn.append(s)
|
|
604
|
+
|
|
605
|
+
def hline(self, *a):
|
|
606
|
+
pass
|
|
607
|
+
|
|
608
|
+
def refresh(self):
|
|
609
|
+
pass
|
|
610
|
+
|
|
611
|
+
def getch(self):
|
|
612
|
+
return self.keys.pop(0) if self.keys else ord("q")
|
|
613
|
+
|
|
614
|
+
|
|
572
615
|
class TestBrowse:
|
|
573
616
|
def test_non_tty_prints_full_catalog(self, boost, tapped):
|
|
574
617
|
r = boost("browse")
|
|
@@ -589,8 +632,8 @@ class TestBrowse:
|
|
|
589
632
|
types.SimpleNamespace(stdin=tty, stdout=tty))
|
|
590
633
|
monkeypatch.setattr(
|
|
591
634
|
discovery, "_browse_tui",
|
|
592
|
-
lambda curses, entries: next(e for e in entries
|
|
593
|
-
|
|
635
|
+
lambda curses, entries: [next(e for e in entries
|
|
636
|
+
if e["name"] == "brainstorming")])
|
|
594
637
|
r = boost("browse")
|
|
595
638
|
assert "installed brainstorming v1.4.0" in r.out
|
|
596
639
|
assert "linked into: Claude Code, Windsurf, Cursor" in r.out
|
|
@@ -618,7 +661,7 @@ class TestBrowse:
|
|
|
618
661
|
workflow = {"name": "AGENT-playbook", "kind": "workflow",
|
|
619
662
|
"version": "1.0", "tap": "fixture-tap"}
|
|
620
663
|
monkeypatch.setattr(discovery, "_browse_tui",
|
|
621
|
-
lambda curses, entries: workflow)
|
|
664
|
+
lambda curses, entries: [workflow])
|
|
622
665
|
r = boost("browse") # default expect=0 → no crash
|
|
623
666
|
assert "vanished from tap" in r.out # friendly message, not Error:
|
|
624
667
|
assert "boost update" in r.out # the hint is shown
|
|
@@ -628,53 +671,45 @@ class TestBrowse:
|
|
|
628
671
|
from boost_cli.commands import discovery
|
|
629
672
|
from boost_cli.core import catalog
|
|
630
673
|
|
|
631
|
-
class FakeCurses:
|
|
632
|
-
A_BOLD, A_DIM, A_REVERSE, A_NORMAL = 1, 2, 4, 0
|
|
633
|
-
KEY_UP, KEY_DOWN, KEY_ENTER, KEY_BACKSPACE = 259, 258, 343, 263
|
|
634
|
-
ACS_HLINE = ord("-")
|
|
635
|
-
|
|
636
|
-
class error(Exception):
|
|
637
|
-
pass
|
|
638
|
-
|
|
639
|
-
def __init__(self, keys):
|
|
640
|
-
self.keys = list(keys)
|
|
641
|
-
self.drawn = []
|
|
642
|
-
|
|
643
|
-
def curs_set(self, n):
|
|
644
|
-
raise self.error("no cursor support")
|
|
645
|
-
|
|
646
|
-
def wrapper(self, fn):
|
|
647
|
-
fn(self)
|
|
648
|
-
|
|
649
|
-
# screen protocol
|
|
650
|
-
def getmaxyx(self):
|
|
651
|
-
return (24, 80)
|
|
652
|
-
|
|
653
|
-
def erase(self):
|
|
654
|
-
pass
|
|
655
|
-
|
|
656
|
-
def addnstr(self, y, x, s, n, attr=0):
|
|
657
|
-
self.drawn.append(s)
|
|
658
|
-
|
|
659
|
-
def hline(self, *a):
|
|
660
|
-
pass
|
|
661
|
-
|
|
662
|
-
def refresh(self):
|
|
663
|
-
pass
|
|
664
|
-
|
|
665
|
-
def getch(self):
|
|
666
|
-
return self.keys.pop(0) if self.keys else ord("q")
|
|
667
|
-
|
|
668
674
|
entries = sorted(catalog.all_entries(), key=lambda e: e["name"])
|
|
669
|
-
fake =
|
|
670
|
-
|
|
675
|
+
fake = _FakeCurses([ord("t"), _FakeCurses.KEY_DOWN, 10, _FakeCurses.KEY_UP,
|
|
676
|
+
_FakeCurses.KEY_BACKSPACE, ord("i")])
|
|
671
677
|
picked = discovery._browse_tui(fake, entries)
|
|
672
|
-
|
|
678
|
+
# a plain "i" with nothing space-selected installs just the highlighted pick
|
|
679
|
+
assert [e["name"] for e in picked] == ["brainstorming"]
|
|
673
680
|
# the typed query is echoed after the "❯" prompt
|
|
674
681
|
assert "t" in fake.drawn
|
|
675
682
|
assert any("boost browse" in s for s in fake.drawn)
|
|
676
683
|
# ESC quits without a pick
|
|
677
|
-
assert discovery._browse_tui(
|
|
684
|
+
assert discovery._browse_tui(_FakeCurses([27]), entries) is None
|
|
685
|
+
|
|
686
|
+
def test_tui_space_selects_multiple_for_batch_install(self, boost, tapped):
|
|
687
|
+
from boost_cli.commands import discovery
|
|
688
|
+
from boost_cli.core import catalog
|
|
689
|
+
|
|
690
|
+
# alphabetical: brainstorming, commit-messages, cowboy-coding, ...
|
|
691
|
+
entries = sorted(catalog.all_entries(), key=lambda e: e["name"])
|
|
692
|
+
keys = [ord(" "), # check brainstorming
|
|
693
|
+
_FakeCurses.KEY_DOWN, _FakeCurses.KEY_DOWN, # -> cowboy-coding
|
|
694
|
+
ord(" "), # check cowboy-coding
|
|
695
|
+
ord("i")] # install the batch
|
|
696
|
+
fake = _FakeCurses(keys)
|
|
697
|
+
picked = discovery._browse_tui(fake, entries)
|
|
698
|
+
assert [e["name"] for e in picked] == ["brainstorming", "cowboy-coding"]
|
|
699
|
+
assert any("2 selected" in s for s in fake.drawn)
|
|
700
|
+
|
|
701
|
+
def test_tui_cards_show_type_tap_badges_and_descriptions(self, boost, tapped):
|
|
702
|
+
from boost_cli.commands import discovery
|
|
703
|
+
from boost_cli.core import catalog
|
|
704
|
+
|
|
705
|
+
entries = sorted(catalog.all_entries(), key=lambda e: e["name"])
|
|
706
|
+
fake = _FakeCurses([]) # no keys queued -> one draw, then default "q" quits
|
|
707
|
+
picked = discovery._browse_tui(fake, entries)
|
|
708
|
+
assert picked is None
|
|
709
|
+
assert "[skill]" in fake.drawn
|
|
710
|
+
assert "[fixture-tap]" in fake.drawn
|
|
711
|
+
assert "v1.0.2" in fake.drawn # commit-messages' version
|
|
712
|
+
assert "Conventional, atomic commit message discipline" in fake.drawn
|
|
678
713
|
|
|
679
714
|
|
|
680
715
|
class TestBrowseAurora:
|
|
@@ -781,6 +816,69 @@ class TestBrowseAurora:
|
|
|
781
816
|
assert th["rule"] == [MonoCurses.A_NORMAL] * 3
|
|
782
817
|
|
|
783
818
|
|
|
819
|
+
class TestBrowseCards:
|
|
820
|
+
"""Row badges, tap categories, and the boost-explain lazy description
|
|
821
|
+
fallback that power the browse card list."""
|
|
822
|
+
|
|
823
|
+
def test_tap_categories_reads_curated_registry_catalog(self, monkeypatch):
|
|
824
|
+
from boost_cli.commands import discovery
|
|
825
|
+
from boost_cli.core import config
|
|
826
|
+
monkeypatch.setattr(config, "load_registry_catalog", lambda: [
|
|
827
|
+
{"name": "a/b", "category": "framework"},
|
|
828
|
+
{"name": "c/d", "category": ""},
|
|
829
|
+
{"name": "e/f"},
|
|
830
|
+
])
|
|
831
|
+
assert discovery._tap_categories() == {"a/b": "framework"}
|
|
832
|
+
|
|
833
|
+
def test_kind_theme_key_maps_known_kinds_and_falls_back(self):
|
|
834
|
+
from boost_cli.commands import discovery
|
|
835
|
+
assert discovery._kind_theme_key("skill") == "badge_skill"
|
|
836
|
+
assert discovery._kind_theme_key("rule") == "badge_rule"
|
|
837
|
+
assert discovery._kind_theme_key("workflow") == "badge_workflow"
|
|
838
|
+
assert discovery._kind_theme_key("whatever") == "badge_skill"
|
|
839
|
+
|
|
840
|
+
def test_row_badges_order_and_category_only_when_known(self):
|
|
841
|
+
from boost_cli.commands import discovery
|
|
842
|
+
e = {"name": "x", "version": "1.0.0", "tap": "a/b", "kind": "rule"}
|
|
843
|
+
with_category = discovery._row_badges(e, {"a/b": "framework"})
|
|
844
|
+
assert with_category == [
|
|
845
|
+
("[rule]", "badge_rule"), ("v1.0.0", "version"),
|
|
846
|
+
("[a/b]", "tap"), ("[framework]", "badge_category")]
|
|
847
|
+
without_category = discovery._row_badges(e, {})
|
|
848
|
+
assert without_category == with_category[:3] # category badge dropped
|
|
849
|
+
|
|
850
|
+
def test_entry_source_path_resolves_within_the_tap(self, boost, tapped):
|
|
851
|
+
from boost_cli.commands import discovery
|
|
852
|
+
from boost_cli.core import catalog
|
|
853
|
+
e = next(x for x in catalog.all_entries()
|
|
854
|
+
if x["name"] == "commit-messages")
|
|
855
|
+
path = discovery._entry_source_path(e)
|
|
856
|
+
assert path is not None and path.is_file()
|
|
857
|
+
assert path.name == "SKILL.md"
|
|
858
|
+
|
|
859
|
+
def test_entry_source_path_none_for_unknown_tap(self):
|
|
860
|
+
from boost_cli.commands import discovery
|
|
861
|
+
e = {"name": "x", "tap": "no/such-tap", "skill_md": "skills/x/SKILL.md"}
|
|
862
|
+
assert discovery._entry_source_path(e) is None
|
|
863
|
+
|
|
864
|
+
def test_lazy_description_falls_back_without_ai(self, boost, tapped):
|
|
865
|
+
# the `sandbox` fixture sets BOOST_NO_AI=1, so ai.available() is False.
|
|
866
|
+
from boost_cli.commands import discovery
|
|
867
|
+
from boost_cli.core import catalog
|
|
868
|
+
e = next(x for x in catalog.all_entries()
|
|
869
|
+
if x["name"] == "commit-messages")
|
|
870
|
+
assert discovery._lazy_description(e) == "no description available"
|
|
871
|
+
|
|
872
|
+
def test_lazy_description_uses_ai_when_available(self, boost, tapped, monkeypatch):
|
|
873
|
+
from boost_cli.commands import discovery
|
|
874
|
+
from boost_cli.core import ai, catalog
|
|
875
|
+
monkeypatch.setattr(ai, "available", lambda: True)
|
|
876
|
+
monkeypatch.setattr(ai, "ask", lambda *a, **k: "Writes tidy commits.\nextra")
|
|
877
|
+
e = next(x for x in catalog.all_entries()
|
|
878
|
+
if x["name"] == "commit-messages")
|
|
879
|
+
assert discovery._lazy_description(e) == "Writes tidy commits."
|
|
880
|
+
|
|
881
|
+
|
|
784
882
|
# ---------------------------------------------------------------- trending
|
|
785
883
|
|
|
786
884
|
class TestTrending:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|