boost-skill-cli 1.0.68__tar.gz → 1.0.69__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/PKG-INFO +1 -1
  2. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/_version.py +2 -2
  3. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/commands/discovery.py +198 -21
  4. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_skill_cli.egg-info/PKG-INFO +1 -1
  5. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_skill_cli.egg-info/SOURCES.txt +0 -1
  6. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/design-roadmap.html +10 -2
  7. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap.html +1 -12
  8. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/functional/test_cli_discovery.py +107 -1
  9. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_output.py +15 -0
  10. boost_skill_cli-1.0.68/docs/roadmap/items/guard-output-table-against-ragged-rows.md +0 -16
  11. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/.gitattributes +0 -0
  12. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/.gitignore +0 -0
  13. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/CLAUDE.md +0 -0
  14. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/CONTRIBUTING.md +0 -0
  15. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/LICENSE +0 -0
  16. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/MANIFEST.in +0 -0
  17. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/Makefile +0 -0
  18. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/README.md +0 -0
  19. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/SECURITY.md +0 -0
  20. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost +0 -0
  21. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/__init__.py +0 -0
  22. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/__main__.py +0 -0
  23. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/cli.py +0 -0
  24. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/cliparse.py +0 -0
  25. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/commands/__init__.py +0 -0
  26. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/commands/bmad.py +0 -0
  27. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/commands/configuration.py +0 -0
  28. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/commands/hooks.py +0 -0
  29. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/commands/info.py +0 -0
  30. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/commands/intelligence.py +0 -0
  31. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/commands/pkg.py +0 -0
  32. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/commands/quality.py +0 -0
  33. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/commands/taps.py +0 -0
  34. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/commands/team.py +0 -0
  35. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/__init__.py +0 -0
  36. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/agents.py +0 -0
  37. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/ai.py +0 -0
  38. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/catalog.py +0 -0
  39. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/claude_settings.py +0 -0
  40. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/config.py +0 -0
  41. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/dense.py +0 -0
  42. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/embed.py +0 -0
  43. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/frontmatter.py +0 -0
  44. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/gitutil.py +0 -0
  45. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/journal.py +0 -0
  46. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/lockfile.py +0 -0
  47. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/logs.py +0 -0
  48. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/mcp.py +0 -0
  49. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/output.py +0 -0
  50. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/paths.py +0 -0
  51. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/policy.py +0 -0
  52. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/rag.py +0 -0
  53. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/registry.py +0 -0
  54. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/store.py +0 -0
  55. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/core/util.py +0 -0
  56. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/data/registries.json +0 -0
  57. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/errors.py +0 -0
  58. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_cli/spin.py +0 -0
  59. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_skill_cli.egg-info/dependency_links.txt +0 -0
  60. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_skill_cli.egg-info/entry_points.txt +0 -0
  61. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_skill_cli.egg-info/requires.txt +0 -0
  62. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/boost_skill_cli.egg-info/top_level.txt +0 -0
  63. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/.claude/settings.local.json +0 -0
  64. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/DEBUGGING.md +0 -0
  65. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/demo.gif +0 -0
  66. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/demo.tape +0 -0
  67. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/index.html +0 -0
  68. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/mcp-hub.html +0 -0
  69. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/rag-architecture.md +0 -0
  70. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D01.md +0 -0
  71. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D02.md +0 -0
  72. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D03.md +0 -0
  73. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D04.md +0 -0
  74. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D05.md +0 -0
  75. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D06.md +0 -0
  76. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D07.md +0 -0
  77. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D08.md +0 -0
  78. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D09.md +0 -0
  79. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D10.md +0 -0
  80. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D11.md +0 -0
  81. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D12.md +0 -0
  82. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D13.md +0 -0
  83. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D14.md +0 -0
  84. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D15.md +0 -0
  85. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D16.md +0 -0
  86. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D17.md +0 -0
  87. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D18.md +0 -0
  88. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D19.md +0 -0
  89. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D20.md +0 -0
  90. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D21.md +0 -0
  91. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D22.md +0 -0
  92. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D23.md +0 -0
  93. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D24.md +0 -0
  94. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D25.md +0 -0
  95. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/accessibility-audit-pa11y-ci-axe-core.md +0 -0
  96. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/atomic-corruption-safe-lock-file-writes.md +0 -0
  97. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/atomic-skill-install-temp-dir-swap.md +0 -0
  98. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/autonomous-ship-workflow-and-isolated-worktree.md +0 -0
  99. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/bring-commands-under-mutation-testing.md +0 -0
  100. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/broken-link-and-anchor-checking-lychee.md +0 -0
  101. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/build-provenance-slsa-attestations.md +0 -0
  102. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/cache-the-catalog-entry-set-across-rag-queries.md +0 -0
  103. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/capability-manifest-and-least-privilege-policy.md +0 -0
  104. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/clean-env-install-smoke-pip-and-pipx.md +0 -0
  105. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/command-reference-docs-site.md +0 -0
  106. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/complexity-and-dead-code-radar-xenon-vulture.md +0 -0
  107. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/consolidate-skill-staleness-drift-logic-into-cor.md +0 -0
  108. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/coverage-dashboard-codecov-free-for-oss.md +0 -0
  109. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/coverage-guided-fuzzing-atheris-oss-fuzz.md +0 -0
  110. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/crash-recorder-error-paths-core-logs-py.md +0 -0
  111. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/dependency-cve-gate-pip-audit.md +0 -0
  112. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/docstring-coverage-interrogate.md +0 -0
  113. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/end-of-options-guard-on-git-commands.md +0 -0
  114. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/extension-free-tests-core-dense-py.md +0 -0
  115. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/extract-mcp-http-servers-out-of-configuration-py.md +0 -0
  116. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/finish-mutation-hardening-across-core.md +0 -0
  117. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/fix-self-update-version-detection-dead-branch.md +0 -0
  118. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/frontmatter-scalar-over-coercion.md +0 -0
  119. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/github-pages-deploy-is-broken-every-push.md +0 -0
  120. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/hash-pinned-reproducible-toolchain-uv-lock.md +0 -0
  121. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/html-validation-html-validate.md +0 -0
  122. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/integrity-verification-boost-verify.md +0 -0
  123. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/layering-guard-import-linter.md +0 -0
  124. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/lighthouse-ci-on-the-pages-site.md +0 -0
  125. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/localize-the-stored-bm25-snippet.md +0 -0
  126. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/lockfile-enforcement-and-commit-pinning.md +0 -0
  127. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/lowest-version-resolution-uv-resolution-lowest-d.md +0 -0
  128. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/make-the-roadmaps-discoverable.md +0 -0
  129. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/markdown-consistency-markdownlint-cli2.md +0 -0
  130. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/memoize-config-load-in-process.md +0 -0
  131. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/modernization-smells-refurb-pyupgrade.md +0 -0
  132. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/mutation-hardening-core-frontmatter-py.md +0 -0
  133. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/mutation-hardening-core-gitutil-py.md +0 -0
  134. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/mutation-hardening-core-store-py.md +0 -0
  135. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/one-command-every-env-nox.md +0 -0
  136. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/one-shared-atomic-write-helper.md +0 -0
  137. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/package-metadata-validation-twine-check-friends.md +0 -0
  138. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/patch-coverage-gate-diff-cover.md +0 -0
  139. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/performance-regression-gate-pytest-benchmark.md +0 -0
  140. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/post-deploy-smoke-headless-load-check.md +0 -0
  141. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/pre-release-python-canary-3-14t-free-threaded.md +0 -0
  142. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/promote-nav-footer-into-the-shared-style-system.md +0 -0
  143. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/prompt-injection-scanning-of-skill-markdown.md +0 -0
  144. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/property-based-tests-hypothesis-on-the-parsers.md +0 -0
  145. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/prose-and-terminology-linting-vale.md +0 -0
  146. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/prune-ignored-dirs-during-scan-dir-walk.md +0 -0
  147. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/quality-dashboard-sonarcloud-free-for-oss.md +0 -0
  148. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/reconcile-the-theme-drift.md +0 -0
  149. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/refresh-the-marketing-surface.md +0 -0
  150. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/reuse-helpers-kill-minor-dead-work.md +0 -0
  151. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/robust-tag-argument-parsing.md +0 -0
  152. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/runner-egress-monitoring-stepsecurity-harden-run.md +0 -0
  153. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/sbom-aware-scanning-osv-scanner.md +0 -0
  154. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/sbom-on-every-release-cyclonedx-syft.md +0 -0
  155. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/second-type-checker-pyright.md +0 -0
  156. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/secret-and-pii-scanning-of-installed-skills.md +0 -0
  157. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/secret-scanning-gitleaks-push-protection.md +0 -0
  158. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/security-linting-bandit-via-ruff-s-rules.md +0 -0
  159. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/shift-left-gate-pre-commit-pre-commit-ci.md +0 -0
  160. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/single-imperative-rule-extractor.md +0 -0
  161. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/single-tech-stack-prober.md +0 -0
  162. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/split-oversized-command-modules.md +0 -0
  163. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/startup-and-import-time-budget-x-importtime.md +0 -0
  164. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/stop-re-serializing-entry-meta-on-every-search.md +0 -0
  165. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/supply-chain-posture-openssf-scorecard.md +0 -0
  166. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/tap-signing-and-provenance-sigstore-minisign.md +0 -0
  167. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/theme-asset-linting-stylelint-eslint.md +0 -0
  168. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/typo-detection-codespell.md +0 -0
  169. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/typosquat-and-name-confusion-detection.md +0 -0
  170. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/unify-tilde-two-copies-have-a-boundary-bug.md +0 -0
  171. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/untrack-generated-build-noise.md +0 -0
  172. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/update-diff-before-apply.md +0 -0
  173. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/visual-regression-pass-on-the-guide.md +0 -0
  174. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/widen-the-ruff-rule-surface-b-sim-c4-perf-ruf.md +0 -0
  175. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/windows-in-the-ci-matrix.md +0 -0
  176. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/workflow-linting-actionlint.md +0 -0
  177. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/docs/roadmap/items/workflow-sast-zizmor.md +0 -0
  178. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/index.html +0 -0
  179. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/pyproject.toml +0 -0
  180. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/scripts/build_registries.py +0 -0
  181. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/scripts/build_roadmap.py +0 -0
  182. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/scripts/mutation_gate.py +0 -0
  183. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/setup.cfg +0 -0
  184. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/style/README.md +0 -0
  185. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/style/boost.css +0 -0
  186. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/style/boost.js +0 -0
  187. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/style/demo.html +0 -0
  188. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/conftest.py +0 -0
  189. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/functional/test_cli_bmad.py +0 -0
  190. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/functional/test_cli_configuration.py +0 -0
  191. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/functional/test_cli_hooks.py +0 -0
  192. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/functional/test_cli_info.py +0 -0
  193. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/functional/test_cli_intelligence.py +0 -0
  194. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/functional/test_cli_pkg.py +0 -0
  195. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/functional/test_cli_quality.py +0 -0
  196. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/functional/test_cli_taps.py +0 -0
  197. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/functional/test_cli_team.py +0 -0
  198. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/functional/test_everyday_loop.py +0 -0
  199. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/make_fixture.py +0 -0
  200. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/smoke.sh +0 -0
  201. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_agents.py +0 -0
  202. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_ai.py +0 -0
  203. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_catalog.py +0 -0
  204. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_claude_settings.py +0 -0
  205. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_cliparse.py +0 -0
  206. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_config.py +0 -0
  207. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_dense.py +0 -0
  208. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_dense_fallback.py +0 -0
  209. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_embed.py +0 -0
  210. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_errors_and_cli_table.py +0 -0
  211. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_frontmatter.py +0 -0
  212. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_gitutil.py +0 -0
  213. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_journal.py +0 -0
  214. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_lockfile.py +0 -0
  215. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_logs.py +0 -0
  216. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_mcp.py +0 -0
  217. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_mutation_hardening.py +0 -0
  218. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_paths.py +0 -0
  219. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_policy.py +0 -0
  220. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_rag.py +0 -0
  221. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_registries_fresh.py +0 -0
  222. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_registry.py +0 -0
  223. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_roadmap_fresh.py +0 -0
  224. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_spin.py +0 -0
  225. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_store.py +0 -0
  226. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_token_parity.py +0 -0
  227. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_util.py +0 -0
  228. {boost_skill_cli-1.0.68 → boost_skill_cli-1.0.69}/tests/unit/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boost-skill-cli
3
- Version: 1.0.68
3
+ Version: 1.0.69
4
4
  Summary: boost — Homebrew for AI coding skills
5
5
  Author: Jonathan Reyes
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -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.68'
22
- __version_tuple__ = version_tuple = (1, 0, 68)
21
+ __version__ = version = '1.0.69'
22
+ __version_tuple__ = version_tuple = (1, 0, 69)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -544,6 +544,140 @@ def _browse_plain(entries, why: str):
544
544
  return 0
545
545
 
546
546
 
547
+ # --- Aurora palette for the curses browser -----------------------------------
548
+ # The full-screen browser paints itself in the same Aurora tokens as the rest
549
+ # of the CLI. curses can't reuse the ANSI escapes from core.output, so these
550
+ # helpers translate the single-source palette (out.TOKENS) into curses colour
551
+ # pairs, degrading to the 8 base colours — then to monochrome — when a terminal
552
+ # (or the test fake) can't do better.
553
+
554
+ def _curses_rgb1000():
555
+ """out.TOKENS scaled from 0..255 to curses' 0..1000 RGB range.
556
+
557
+ Parity-locked to the web palette: whatever style/boost.css defines, the
558
+ browser renders, so the TUI can never drift from the design system.
559
+ """
560
+ return {name: tuple(round(c / 255 * 1000) for c in rgb)
561
+ for name, rgb in out.TOKENS.items()}
562
+
563
+
564
+ def _nearest_base(curses):
565
+ """Each Aurora token mapped to its closest of the 8 base curses colours.
566
+
567
+ Used when the terminal can't redefine colours (mirrors the 16-colour
568
+ fallback in core.output._AURORA).
569
+ """
570
+ return {"cyan": curses.COLOR_CYAN, "violet": curses.COLOR_MAGENTA,
571
+ "pink": curses.COLOR_MAGENTA, "green": curses.COLOR_GREEN,
572
+ "yellow": curses.COLOR_YELLOW}
573
+
574
+
575
+ def _match_positions(needle, hay):
576
+ """Indices in hay consumed by a left-to-right subsequence match of needle.
577
+
578
+ Drives fuzzy-match highlighting; returns [] when needle isn't a
579
+ subsequence of hay (so nothing is highlighted).
580
+ """
581
+ positions, i = [], 0
582
+ for j, ch in enumerate(hay):
583
+ if i < len(needle) and ch == needle[i]:
584
+ positions.append(j)
585
+ i += 1
586
+ return positions if i == len(needle) else []
587
+
588
+
589
+ def _scrollbar(total, rows, top):
590
+ """(thumb_start, thumb_len) for a rows-tall scrollbar; None when all fits."""
591
+ if rows <= 0 or total <= rows:
592
+ return None
593
+ thumb = max(1, rows * rows // total)
594
+ max_top = total - rows
595
+ start = round((rows - thumb) * top / max_top) if max_top else 0
596
+ return (start, thumb)
597
+
598
+
599
+ def _grad_segments(width, n=3):
600
+ """Split a width-wide rule into n near-equal [(start, length)] runs.
601
+
602
+ Lets the browser paint a cyan -> violet -> pink gradient bar the same way
603
+ out.gradient() lerps the wordmark.
604
+ """
605
+ if width <= 0:
606
+ return []
607
+ n = min(n, width)
608
+ base, extra = divmod(width, n)
609
+ segs, pos = [], 0
610
+ for k in range(n):
611
+ length = base + (1 if k < extra else 0)
612
+ segs.append((pos, length))
613
+ pos += length
614
+ return segs
615
+
616
+
617
+ def _aurora_pairs(curses):
618
+ """Allocate Aurora curses colour pairs; token-name -> attr int (0 if none)."""
619
+ names = ("cyan", "violet", "pink", "green", "yellow")
620
+ try:
621
+ custom = curses.can_change_color() and curses.COLORS >= 16 + len(names)
622
+ except curses.error:
623
+ custom = False
624
+ fg = dict(_nearest_base(curses))
625
+ if custom:
626
+ rgb = _curses_rgb1000()
627
+ for idx, nm in enumerate(names):
628
+ slot = 16 + idx
629
+ try:
630
+ curses.init_color(slot, *rgb[nm])
631
+ fg[nm] = slot
632
+ except curses.error:
633
+ pass
634
+ fg["red"] = curses.COLOR_RED
635
+ pairs = {}
636
+ for i, nm in enumerate(("cyan", "violet", "pink", "green", "yellow", "red"),
637
+ start=1):
638
+ try:
639
+ curses.init_pair(i, fg[nm], -1)
640
+ pairs[nm] = curses.color_pair(i)
641
+ except curses.error:
642
+ pairs[nm] = 0
643
+ return pairs
644
+
645
+
646
+ def _aurora_theme(curses):
647
+ """role -> curses attr. Monochrome baseline, upgraded to Aurora where able."""
648
+ t = {"title": curses.A_BOLD, "prompt": curses.A_BOLD, "query": curses.A_BOLD,
649
+ "count": curses.A_DIM, "help": curses.A_DIM, "star": curses.A_NORMAL,
650
+ "name": curses.A_NORMAL, "sel_bar": curses.A_REVERSE,
651
+ "sel_name": curses.A_BOLD, "match": curses.A_BOLD,
652
+ "version": curses.A_DIM, "tap": curses.A_NORMAL,
653
+ "rule": [curses.A_NORMAL, curses.A_NORMAL, curses.A_NORMAL],
654
+ "scroll": curses.A_DIM, "detail_name": curses.A_BOLD,
655
+ "detail_meta": curses.A_DIM, "detail_tag": curses.A_NORMAL,
656
+ "dot_r": curses.A_NORMAL, "dot_y": curses.A_NORMAL,
657
+ "dot_g": curses.A_NORMAL}
658
+ if not hasattr(curses, "start_color"):
659
+ return t
660
+ try:
661
+ curses.start_color()
662
+ curses.use_default_colors()
663
+ except curses.error:
664
+ return t
665
+ p = _aurora_pairs(curses)
666
+
667
+ def P(nm, bold=False):
668
+ a = p.get(nm, 0)
669
+ return (a | curses.A_BOLD) if bold else a
670
+
671
+ t.update({"title": P("cyan", True), "prompt": P("cyan", True),
672
+ "star": P("yellow"), "sel_bar": P("cyan", True),
673
+ "sel_name": P("cyan", True), "match": P("pink", True),
674
+ "tap": P("violet"), "rule": [P("cyan"), P("violet"), P("pink")],
675
+ "scroll": P("cyan"), "detail_name": P("cyan", True),
676
+ "detail_tag": P("pink"), "dot_r": P("red"), "dot_y": P("yellow"),
677
+ "dot_g": P("green")})
678
+ return t
679
+
680
+
547
681
  def _browse_tui(curses, entries):
548
682
  """Run the curses UI. Returns the entry picked for install, or None."""
549
683
  state = {"pick": None}
@@ -553,40 +687,83 @@ def _browse_tui(curses, entries):
553
687
  curses.curs_set(0)
554
688
  except curses.error:
555
689
  pass
690
+ th = _aurora_theme(curses)
691
+
692
+ def put(y, x, s, attr=0): # clip-safe cell writer
693
+ if 0 <= y < _h and 0 <= x < _w - 1 and s:
694
+ try:
695
+ scr.addnstr(y, x, s, _w - 1 - x, attr)
696
+ except curses.error:
697
+ pass
698
+
556
699
  filt, sel, detail = "", 0, False
557
700
  while True:
558
701
  q = filt.lower()
559
702
  matches = [e for e in entries
560
703
  if _subseq(q, (e["name"] + " " + e["tap"]).lower())]
561
704
  sel = max(0, min(sel, len(matches) - 1))
562
- h, w = scr.getmaxyx()
705
+ _h, _w = scr.getmaxyx()
563
706
  pane = 6 if detail and matches else 0
564
707
  try:
565
708
  scr.erase()
566
- scr.addnstr(0, 0, "filter: " + filt, w - 1, curses.A_BOLD)
567
- scr.addnstr(1, 0, "up/down move · enter detail · i install · "
568
- "q quit · %d/%d" % (len(matches), len(entries)),
569
- w - 1, curses.A_DIM)
570
- rows = max(1, h - 3 - pane)
709
+ # titlebar: macOS traffic dots + wordmark + gradient rule
710
+ put(0, 0, "●", th["dot_r"])
711
+ put(0, 2, "●", th["dot_y"])
712
+ put(0, 4, "●", th["dot_g"])
713
+ title = " boost browse"
714
+ put(0, 6, title, th["title"])
715
+ rx = 6 + len(title) + 1
716
+ for (start, length), attr in zip(
717
+ _grad_segments(max(0, _w - 1 - rx)), th["rule"]):
718
+ put(0, rx + start, "─" * length, attr)
719
+ # prompt line: cyan chevron, query, cursor, right-aligned count
720
+ put(1, 0, "❯", th["prompt"])
721
+ put(1, 2, filt, th["query"])
722
+ put(1, 2 + len(filt), "▏", th["prompt"])
723
+ count = "%d/%d" % (len(matches), len(entries))
724
+ put(1, max(2 + len(filt) + 2, _w - 1 - len(count)), count,
725
+ th["count"])
726
+ put(2, 0, "↑↓ move ↵ detail i install q quit",
727
+ th["help"])
728
+ rows = max(1, _h - 3 - pane)
571
729
  top = max(0, sel - rows + 1)
572
- for i, e in enumerate(matches[top:top + rows]):
573
- line = "%s %s v%s %s" % ("★" if e.get("curated") else " ",
574
- e["name"], e["version"], e["tap"])
575
- attr = curses.A_REVERSE if top + i == sel else curses.A_NORMAL
576
- scr.addnstr(2 + i, 0, line.ljust(w - 1), w - 1, attr)
730
+ view = matches[top:top + rows]
731
+ namew = min(max((len(e["name"]) for e in view), default=4),
732
+ max(4, _w - 26))
733
+ bar = _scrollbar(len(matches), rows, top)
734
+ for i, e in enumerate(view):
735
+ y = 3 + i
736
+ chosen = top + i == sel
737
+ put(y, 0, "▎" if chosen else " ", th["sel_bar"])
738
+ put(y, 1, "★" if e.get("curated") else " ", th["star"])
739
+ nm = out.truncate(e["name"], namew)
740
+ put(y, 3, nm.ljust(namew),
741
+ th["sel_name"] if chosen else th["name"])
742
+ for pos in _match_positions(q, nm.lower()):
743
+ put(y, 3 + pos, nm[pos], th["match"])
744
+ vx = 3 + namew + 2
745
+ put(y, vx, "v" + e["version"], th["version"])
746
+ put(y, vx + len(e["version"]) + 3, e["tap"], th["tap"])
747
+ if bar:
748
+ bstart, blen = bar
749
+ put(y, _w - 2,
750
+ "█" if bstart <= i < bstart + blen else "│",
751
+ th["scroll"])
577
752
  if pane and matches:
578
753
  e = matches[sel]
579
- y0 = h - pane
580
- scr.hline(y0, 0, curses.ACS_HLINE, w - 1)
581
- tags = ", ".join(str(t) for t in
754
+ y0 = _h - pane
755
+ for (start, length), attr in zip(
756
+ _grad_segments(_w - 1), th["rule"]):
757
+ put(y0, start, "─" * length, attr)
758
+ tags = " ".join("#" + str(t) for t in
582
759
  (e.get("meta", {}).get("tags") or []))
583
- for j, ln in enumerate((
584
- "%s v%s" % (e["name"], e["version"]),
585
- (e["description"] or "")[:w - 3],
586
- "tap: %s dir: %s" % (e["tap"], e["rel_dir"]),
587
- "tags: %s" % (tags or "-"))):
588
- if y0 + 1 + j < h:
589
- scr.addnstr(y0 + 1 + j, 1, ln, w - 2)
760
+ put(y0 + 1, 1, "%s v%s" % (e["name"], e["version"]),
761
+ th["detail_name"])
762
+ put(y0 + 2, 1, out.truncate(e["description"] or "", _w - 3),
763
+ th["name"])
764
+ put(y0 + 3, 1, "tap %s dir %s" % (e["tap"], e["rel_dir"]),
765
+ th["detail_meta"])
766
+ put(y0 + 4, 1, tags or "no tags", th["detail_tag"])
590
767
  scr.refresh()
591
768
  except curses.error:
592
769
  pass # terminal too small mid-draw; retry on next key
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boost-skill-cli
3
- Version: 1.0.68
3
+ Version: 1.0.69
4
4
  Summary: boost — Homebrew for AI coding skills
5
5
  Author: Jonathan Reyes
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -116,7 +116,6 @@ docs/roadmap/items/finish-mutation-hardening-across-core.md
116
116
  docs/roadmap/items/fix-self-update-version-detection-dead-branch.md
117
117
  docs/roadmap/items/frontmatter-scalar-over-coercion.md
118
118
  docs/roadmap/items/github-pages-deploy-is-broken-every-push.md
119
- docs/roadmap/items/guard-output-table-against-ragged-rows.md
120
119
  docs/roadmap/items/hash-pinned-reproducible-toolchain-uv-lock.md
121
120
  docs/roadmap/items/html-validation-html-validate.md
122
121
  docs/roadmap/items/integrity-verification-boost-verify.md
@@ -142,7 +142,7 @@
142
142
  <code>NO_COLOR</code> and non-TTY are always respected.
143
143
  </p>
144
144
  <div class="stats">
145
- <div class="stat reveal"><b>25</b><span>Design opportunities</span></div>
145
+ <div class="stat reveal"><b>26</b><span>Design opportunities</span></div>
146
146
  <div class="stat reveal"><b>5</b><span>Design tracks</span></div>
147
147
  <div class="stat reveal"><b>0</b><span>New dependencies</span></div>
148
148
  <div class="stat reveal"><b>1:1</b><span>Web ↔ CLI parity goal</span></div>
@@ -395,7 +395,7 @@
395
395
  <div class="track-icon"></div>
396
396
  <div>
397
397
  <h3>Command-by-command Polish</h3>
398
- <p>The screens users actually stare at — front door, doctor, list, info.</p>
398
+ <p>The screens users actually stare at — front door, doctor, list, info, browse.</p>
399
399
  </div>
400
400
  <span class="n"><!-- ROADMAP:count track=commands -->6 items<!-- /ROADMAP:count --></span>
401
401
  </div>
@@ -451,6 +451,14 @@
451
451
  </article>
452
452
  <!-- /ROADMAP:cards -->
453
453
 
454
+ <article class="ritem" data-track="commands" data-impact="high">
455
+ <div class="ritem-top"><span class="rid">BOOST-D26</span><span class="rstatus done">Shipped</span></div>
456
+ <h4><code>browse</code> — Aurora the full-screen TUI</h4>
457
+ <p>The interactive <code>browse</code> pane was the <b>last off-theme surface</b>: a curses screen drawn in raw <code>A_BOLD</code>/<code>A_DIM</code>/<code>A_REVERSE</code> — zero brand color while every other command spoke Aurora. Rebuilt it as a first-class TUI: a macOS <b>titlebar</b> (traffic-light dots + <code>boost browse</code> wordmark under a cyan→violet→pink gradient rule), an <code>❯</code> chevron prompt with a live cursor, <b>fuzzy-match highlighting</b> (matched chars glow pink as you type), a cyan selection bar, yellow curated <code>★</code>, violet tap labels, a right-edge scrollbar, and a gradient-topped detail pane. Colors come from the single-source <code>out.TOKENS</code> via <code>init_color</code> — parity-locked to <code>style/boost.css</code> — and degrade truecolor → 8-color → monochrome. Stdlib only.</p>
458
+ <div class="rmeta"><span class="m-cat">commands</span><span>Complexity L</span><span class="m-imp high">Impact High</span><span class="m-wow">★★★★★</span></div>
459
+ <div class="rref"><code>commands/discovery.py · _browse_tui / _aurora_theme</code></div>
460
+ </article>
461
+
454
462
  </div>
455
463
  </section>
456
464
 
@@ -118,7 +118,7 @@
118
118
  <div class="stat"><b>7</b><span>Shipped</span></div>
119
119
  <div class="stat"><b>3</b><span>Next up</span></div>
120
120
  <div class="stat"><b>4</b><span>Planned</span></div>
121
- <div class="stat"><b>84</b><span>Loop finds</span></div>
121
+ <div class="stat"><b>83</b><span>Loop finds</span></div>
122
122
  <!-- /ROADMAP:stats -->
123
123
  </div>
124
124
  </section>
@@ -574,17 +574,6 @@
574
574
  </div>
575
575
  </article>
576
576
 
577
- <article class="cap rcard">
578
- <div class="head"><span class="pill planned">Planned</span><span class="cat">Robustness</span></div>
579
- <h3>Guard <code>output.table</code> against ragged rows</h3>
580
- <p>Column-width computation does <code>max(… for r in rows if i &lt; len(r))</code> (<code>core/output.py:300</code>), which raises <code>max() arg is empty</code> when a header column has no matching cell in any row. Add <code>default=0</code>.</p>
581
- <div class="meta">
582
- <span class="m">Complexity <b>S</b></span>
583
- <span class="m">Impact <b>Low</b></span>
584
- <span class="m">Wow <b class="wow">★</b></span>
585
- </div>
586
- </article>
587
-
588
577
  <article class="cap rcard">
589
578
  <div class="head"><span class="pill planned">Planned</span><span class="cat">Tech-debt</span></div>
590
579
  <h3>Reuse helpers; kill minor dead work</h3>
@@ -627,11 +627,117 @@ class TestBrowse:
627
627
  FakeCurses.KEY_BACKSPACE, ord("i")])
628
628
  picked = discovery._browse_tui(fake, entries)
629
629
  assert picked["name"] == "brainstorming"
630
- assert any(s.startswith("filter: t") for s in fake.drawn)
630
+ # the typed query is echoed after the "❯" prompt
631
+ assert "t" in fake.drawn
632
+ assert any("boost browse" in s for s in fake.drawn)
631
633
  # ESC quits without a pick
632
634
  assert discovery._browse_tui(FakeCurses([27]), entries) is None
633
635
 
634
636
 
637
+ class TestBrowseAurora:
638
+ """The curses browser paints itself in the single-source Aurora palette."""
639
+
640
+ def test_curses_rgb1000_parity_with_tokens(self):
641
+ from boost_cli.commands import discovery
642
+ from boost_cli.core import output as out
643
+ rgb = discovery._curses_rgb1000()
644
+ assert set(rgb) == set(out.TOKENS)
645
+ # cyan #22d3ee scaled 0..255 -> 0..1000
646
+ r, g, b = out.TOKENS["cyan"]
647
+ assert rgb["cyan"] == (round(r / 255 * 1000), round(g / 255 * 1000),
648
+ round(b / 255 * 1000))
649
+ for triple in rgb.values():
650
+ assert all(0 <= c <= 1000 for c in triple)
651
+
652
+ def test_nearest_base_covers_every_token(self):
653
+ import curses
654
+ from boost_cli.commands import discovery
655
+ from boost_cli.core import output as out
656
+ base = discovery._nearest_base(curses)
657
+ assert set(base) == set(out.TOKENS)
658
+ assert base["cyan"] == curses.COLOR_CYAN
659
+ assert base["violet"] == base["pink"] == curses.COLOR_MAGENTA
660
+
661
+ def test_match_positions(self):
662
+ from boost_cli.commands import discovery
663
+ assert discovery._match_positions("bs", "brainstorm") == [0, 5]
664
+ assert discovery._match_positions("", "anything") == []
665
+ assert discovery._match_positions("zzz", "brainstorm") == []
666
+ # left-to-right greedy: first 'o', then trailing 'm'
667
+ assert discovery._match_positions("om", "brainstorm") == [7, 9]
668
+
669
+ def test_scrollbar_math(self):
670
+ from boost_cli.commands import discovery
671
+ assert discovery._scrollbar(5, 10, 0) is None # all fits
672
+ assert discovery._scrollbar(10, 0, 0) is None # no rows
673
+ start, length = discovery._scrollbar(100, 10, 0)
674
+ assert start == 0 and 1 <= length <= 10
675
+ end_start, _ = discovery._scrollbar(100, 10, 90) # scrolled to bottom
676
+ assert end_start == 10 - length
677
+
678
+ def test_grad_segments_partition(self):
679
+ from boost_cli.commands import discovery
680
+ assert discovery._grad_segments(0) == []
681
+ segs = discovery._grad_segments(10, 3)
682
+ assert [s[1] for s in segs] == [4, 3, 3] # sums to width
683
+ assert sum(s[1] for s in segs) == 10
684
+ assert [s[0] for s in segs] == [0, 4, 7] # contiguous
685
+ assert len(discovery._grad_segments(2, 3)) == 2 # n clamps to width
686
+
687
+ def test_theme_upgrades_to_colour(self):
688
+ from boost_cli.commands import discovery
689
+
690
+ class ColorCurses:
691
+ COLOR_CYAN, COLOR_MAGENTA, COLOR_GREEN = 6, 5, 2
692
+ COLOR_YELLOW, COLOR_RED = 3, 1
693
+ COLORS = 256
694
+ A_BOLD, A_DIM, A_REVERSE, A_NORMAL = 1, 2, 4, 0
695
+
696
+ class error(Exception):
697
+ pass
698
+
699
+ def __init__(self):
700
+ self.colors, self.pairs = {}, {}
701
+
702
+ def start_color(self):
703
+ pass
704
+
705
+ def use_default_colors(self):
706
+ pass
707
+
708
+ def can_change_color(self):
709
+ return True
710
+
711
+ def init_color(self, slot, r, g, b):
712
+ self.colors[slot] = (r, g, b)
713
+
714
+ def init_pair(self, i, fg, bg):
715
+ self.pairs[i] = (fg, bg)
716
+
717
+ def color_pair(self, i):
718
+ return i << 8
719
+
720
+ cc = ColorCurses()
721
+ th = discovery._aurora_theme(cc)
722
+ # custom colours were defined from the parity-locked palette
723
+ assert cc.colors[16] == discovery._curses_rgb1000()["cyan"]
724
+ # title carries a colour pair plus bold, not bare monochrome
725
+ assert th["title"] & cc.A_BOLD
726
+ assert th["title"] & ~cc.A_BOLD
727
+ assert len(th["rule"]) == 3
728
+ assert th["rule"][0] != th["rule"][1] != th["rule"][2]
729
+
730
+ def test_theme_falls_back_without_colour(self):
731
+ from boost_cli.commands import discovery
732
+
733
+ class MonoCurses:
734
+ A_BOLD, A_DIM, A_REVERSE, A_NORMAL = 1, 2, 4, 0
735
+ th = discovery._aurora_theme(MonoCurses)
736
+ assert th["title"] == MonoCurses.A_BOLD
737
+ assert th["sel_bar"] == MonoCurses.A_REVERSE
738
+ assert th["rule"] == [MonoCurses.A_NORMAL] * 3
739
+
740
+
635
741
  # ---------------------------------------------------------------- trending
636
742
 
637
743
  class TestTrending:
@@ -584,6 +584,21 @@ class TestTable:
584
584
  output.table([("a",), ("bb", "c")])
585
585
  assert capsys.readouterr().out == "a\nbb c\n"
586
586
 
587
+ def test_header_arity_exceeds_every_row(self, capsys):
588
+ # a header column with no data cell in any row must not raise: the
589
+ # width generator is guarded by `i < len(r)`, and the header row itself
590
+ # (part of all_rows) always supplies its own columns.
591
+ output.table([("1",)], headers=["A", "B", "C"])
592
+ assert capsys.readouterr().out == "A B C\n1\n"
593
+
594
+ def test_degenerate_row_shapes_never_raise(self, capsys):
595
+ # empty tuples and all-empty rows are the shapes the (non-)bug feared;
596
+ # assert they render without a `max() arg is empty` crash.
597
+ for rows in ([(), ("a",)], [(), ()], [("a", "b"), ()]):
598
+ output.table(rows) # must not raise
599
+ output.table([(), ()], headers=["A", "B"])
600
+ assert "A B" in capsys.readouterr().out
601
+
587
602
  def test_non_string_cells_coerced(self, capsys):
588
603
  output.table([(1, 22.5)])
589
604
  assert capsys.readouterr().out == "1 22.5\n"
@@ -1,16 +0,0 @@
1
- ---
2
- id: guard-output-table-against-ragged-rows
3
- board: code
4
- section: internals
5
- status: planned
6
- category: Robustness
7
- complexity: S
8
- impact: Low
9
- wow: 1
10
- note:
11
- order: 19
12
- owner:
13
- pr:
14
- title: Guard <code>output.table</code> against ragged rows
15
- ---
16
- Column-width computation does <code>max(… for r in rows if i &lt; len(r))</code> (<code>core/output.py:300</code>), which raises <code>max() arg is empty</code> when a header column has no matching cell in any row. Add <code>default=0</code>.
File without changes