boost-skill-cli 1.0.67__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 (229) hide show
  1. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/PKG-INFO +1 -1
  2. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/_version.py +2 -2
  3. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/commands/configuration.py +14 -7
  4. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/commands/discovery.py +198 -21
  5. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_skill_cli.egg-info/PKG-INFO +1 -1
  6. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_skill_cli.egg-info/SOURCES.txt +0 -1
  7. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/design-roadmap.html +10 -2
  8. boost_skill_cli-1.0.69/docs/roadmap/items/fix-self-update-version-detection-dead-branch.md +25 -0
  9. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap.html +23 -24
  10. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/functional/test_cli_configuration.py +37 -3
  11. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/functional/test_cli_discovery.py +107 -1
  12. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_output.py +15 -0
  13. boost_skill_cli-1.0.67/docs/roadmap/items/fix-self-update-version-detection-dead-branch.md +0 -16
  14. boost_skill_cli-1.0.67/docs/roadmap/items/guard-output-table-against-ragged-rows.md +0 -16
  15. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/.gitattributes +0 -0
  16. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/.gitignore +0 -0
  17. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/CLAUDE.md +0 -0
  18. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/CONTRIBUTING.md +0 -0
  19. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/LICENSE +0 -0
  20. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/MANIFEST.in +0 -0
  21. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/Makefile +0 -0
  22. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/README.md +0 -0
  23. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/SECURITY.md +0 -0
  24. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost +0 -0
  25. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/__init__.py +0 -0
  26. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/__main__.py +0 -0
  27. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/cli.py +0 -0
  28. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/cliparse.py +0 -0
  29. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/commands/__init__.py +0 -0
  30. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/commands/bmad.py +0 -0
  31. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/commands/hooks.py +0 -0
  32. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/commands/info.py +0 -0
  33. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/commands/intelligence.py +0 -0
  34. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/commands/pkg.py +0 -0
  35. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/commands/quality.py +0 -0
  36. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/commands/taps.py +0 -0
  37. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/commands/team.py +0 -0
  38. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/__init__.py +0 -0
  39. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/agents.py +0 -0
  40. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/ai.py +0 -0
  41. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/catalog.py +0 -0
  42. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/claude_settings.py +0 -0
  43. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/config.py +0 -0
  44. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/dense.py +0 -0
  45. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/embed.py +0 -0
  46. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/frontmatter.py +0 -0
  47. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/gitutil.py +0 -0
  48. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/journal.py +0 -0
  49. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/lockfile.py +0 -0
  50. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/logs.py +0 -0
  51. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/mcp.py +0 -0
  52. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/output.py +0 -0
  53. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/paths.py +0 -0
  54. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/policy.py +0 -0
  55. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/rag.py +0 -0
  56. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/registry.py +0 -0
  57. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/store.py +0 -0
  58. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/core/util.py +0 -0
  59. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/data/registries.json +0 -0
  60. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/errors.py +0 -0
  61. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_cli/spin.py +0 -0
  62. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_skill_cli.egg-info/dependency_links.txt +0 -0
  63. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_skill_cli.egg-info/entry_points.txt +0 -0
  64. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_skill_cli.egg-info/requires.txt +0 -0
  65. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/boost_skill_cli.egg-info/top_level.txt +0 -0
  66. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/.claude/settings.local.json +0 -0
  67. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/DEBUGGING.md +0 -0
  68. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/demo.gif +0 -0
  69. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/demo.tape +0 -0
  70. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/index.html +0 -0
  71. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/mcp-hub.html +0 -0
  72. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/rag-architecture.md +0 -0
  73. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D01.md +0 -0
  74. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D02.md +0 -0
  75. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D03.md +0 -0
  76. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D04.md +0 -0
  77. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D05.md +0 -0
  78. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D06.md +0 -0
  79. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D07.md +0 -0
  80. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D08.md +0 -0
  81. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D09.md +0 -0
  82. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D10.md +0 -0
  83. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D11.md +0 -0
  84. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D12.md +0 -0
  85. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D13.md +0 -0
  86. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D14.md +0 -0
  87. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D15.md +0 -0
  88. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D16.md +0 -0
  89. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D17.md +0 -0
  90. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D18.md +0 -0
  91. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D19.md +0 -0
  92. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D20.md +0 -0
  93. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D21.md +0 -0
  94. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D22.md +0 -0
  95. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D23.md +0 -0
  96. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D24.md +0 -0
  97. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/BOOST-D25.md +0 -0
  98. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/accessibility-audit-pa11y-ci-axe-core.md +0 -0
  99. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/atomic-corruption-safe-lock-file-writes.md +0 -0
  100. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/atomic-skill-install-temp-dir-swap.md +0 -0
  101. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/autonomous-ship-workflow-and-isolated-worktree.md +0 -0
  102. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/bring-commands-under-mutation-testing.md +0 -0
  103. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/broken-link-and-anchor-checking-lychee.md +0 -0
  104. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/build-provenance-slsa-attestations.md +0 -0
  105. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/cache-the-catalog-entry-set-across-rag-queries.md +0 -0
  106. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/capability-manifest-and-least-privilege-policy.md +0 -0
  107. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/clean-env-install-smoke-pip-and-pipx.md +0 -0
  108. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/command-reference-docs-site.md +0 -0
  109. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/complexity-and-dead-code-radar-xenon-vulture.md +0 -0
  110. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/consolidate-skill-staleness-drift-logic-into-cor.md +0 -0
  111. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/coverage-dashboard-codecov-free-for-oss.md +0 -0
  112. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/coverage-guided-fuzzing-atheris-oss-fuzz.md +0 -0
  113. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/crash-recorder-error-paths-core-logs-py.md +0 -0
  114. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/dependency-cve-gate-pip-audit.md +0 -0
  115. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/docstring-coverage-interrogate.md +0 -0
  116. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/end-of-options-guard-on-git-commands.md +0 -0
  117. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/extension-free-tests-core-dense-py.md +0 -0
  118. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/extract-mcp-http-servers-out-of-configuration-py.md +0 -0
  119. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/finish-mutation-hardening-across-core.md +0 -0
  120. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/frontmatter-scalar-over-coercion.md +0 -0
  121. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/github-pages-deploy-is-broken-every-push.md +0 -0
  122. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/hash-pinned-reproducible-toolchain-uv-lock.md +0 -0
  123. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/html-validation-html-validate.md +0 -0
  124. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/integrity-verification-boost-verify.md +0 -0
  125. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/layering-guard-import-linter.md +0 -0
  126. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/lighthouse-ci-on-the-pages-site.md +0 -0
  127. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/localize-the-stored-bm25-snippet.md +0 -0
  128. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/lockfile-enforcement-and-commit-pinning.md +0 -0
  129. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/lowest-version-resolution-uv-resolution-lowest-d.md +0 -0
  130. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/make-the-roadmaps-discoverable.md +0 -0
  131. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/markdown-consistency-markdownlint-cli2.md +0 -0
  132. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/memoize-config-load-in-process.md +0 -0
  133. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/modernization-smells-refurb-pyupgrade.md +0 -0
  134. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/mutation-hardening-core-frontmatter-py.md +0 -0
  135. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/mutation-hardening-core-gitutil-py.md +0 -0
  136. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/mutation-hardening-core-store-py.md +0 -0
  137. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/one-command-every-env-nox.md +0 -0
  138. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/one-shared-atomic-write-helper.md +0 -0
  139. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/package-metadata-validation-twine-check-friends.md +0 -0
  140. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/patch-coverage-gate-diff-cover.md +0 -0
  141. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/performance-regression-gate-pytest-benchmark.md +0 -0
  142. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/post-deploy-smoke-headless-load-check.md +0 -0
  143. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/pre-release-python-canary-3-14t-free-threaded.md +0 -0
  144. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/promote-nav-footer-into-the-shared-style-system.md +0 -0
  145. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/prompt-injection-scanning-of-skill-markdown.md +0 -0
  146. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/property-based-tests-hypothesis-on-the-parsers.md +0 -0
  147. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/prose-and-terminology-linting-vale.md +0 -0
  148. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/prune-ignored-dirs-during-scan-dir-walk.md +0 -0
  149. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/quality-dashboard-sonarcloud-free-for-oss.md +0 -0
  150. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/reconcile-the-theme-drift.md +0 -0
  151. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/refresh-the-marketing-surface.md +0 -0
  152. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/reuse-helpers-kill-minor-dead-work.md +0 -0
  153. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/robust-tag-argument-parsing.md +0 -0
  154. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/runner-egress-monitoring-stepsecurity-harden-run.md +0 -0
  155. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/sbom-aware-scanning-osv-scanner.md +0 -0
  156. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/sbom-on-every-release-cyclonedx-syft.md +0 -0
  157. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/second-type-checker-pyright.md +0 -0
  158. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/secret-and-pii-scanning-of-installed-skills.md +0 -0
  159. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/secret-scanning-gitleaks-push-protection.md +0 -0
  160. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/security-linting-bandit-via-ruff-s-rules.md +0 -0
  161. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/shift-left-gate-pre-commit-pre-commit-ci.md +0 -0
  162. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/single-imperative-rule-extractor.md +0 -0
  163. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/single-tech-stack-prober.md +0 -0
  164. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/split-oversized-command-modules.md +0 -0
  165. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/startup-and-import-time-budget-x-importtime.md +0 -0
  166. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/stop-re-serializing-entry-meta-on-every-search.md +0 -0
  167. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/supply-chain-posture-openssf-scorecard.md +0 -0
  168. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/tap-signing-and-provenance-sigstore-minisign.md +0 -0
  169. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/theme-asset-linting-stylelint-eslint.md +0 -0
  170. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/typo-detection-codespell.md +0 -0
  171. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/typosquat-and-name-confusion-detection.md +0 -0
  172. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/unify-tilde-two-copies-have-a-boundary-bug.md +0 -0
  173. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/untrack-generated-build-noise.md +0 -0
  174. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/update-diff-before-apply.md +0 -0
  175. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/visual-regression-pass-on-the-guide.md +0 -0
  176. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/widen-the-ruff-rule-surface-b-sim-c4-perf-ruf.md +0 -0
  177. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/windows-in-the-ci-matrix.md +0 -0
  178. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/workflow-linting-actionlint.md +0 -0
  179. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/docs/roadmap/items/workflow-sast-zizmor.md +0 -0
  180. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/index.html +0 -0
  181. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/pyproject.toml +0 -0
  182. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/scripts/build_registries.py +0 -0
  183. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/scripts/build_roadmap.py +0 -0
  184. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/scripts/mutation_gate.py +0 -0
  185. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/setup.cfg +0 -0
  186. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/style/README.md +0 -0
  187. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/style/boost.css +0 -0
  188. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/style/boost.js +0 -0
  189. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/style/demo.html +0 -0
  190. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/conftest.py +0 -0
  191. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/functional/test_cli_bmad.py +0 -0
  192. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/functional/test_cli_hooks.py +0 -0
  193. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/functional/test_cli_info.py +0 -0
  194. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/functional/test_cli_intelligence.py +0 -0
  195. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/functional/test_cli_pkg.py +0 -0
  196. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/functional/test_cli_quality.py +0 -0
  197. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/functional/test_cli_taps.py +0 -0
  198. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/functional/test_cli_team.py +0 -0
  199. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/functional/test_everyday_loop.py +0 -0
  200. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/make_fixture.py +0 -0
  201. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/smoke.sh +0 -0
  202. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_agents.py +0 -0
  203. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_ai.py +0 -0
  204. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_catalog.py +0 -0
  205. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_claude_settings.py +0 -0
  206. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_cliparse.py +0 -0
  207. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_config.py +0 -0
  208. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_dense.py +0 -0
  209. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_dense_fallback.py +0 -0
  210. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_embed.py +0 -0
  211. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_errors_and_cli_table.py +0 -0
  212. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_frontmatter.py +0 -0
  213. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_gitutil.py +0 -0
  214. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_journal.py +0 -0
  215. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_lockfile.py +0 -0
  216. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_logs.py +0 -0
  217. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_mcp.py +0 -0
  218. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_mutation_hardening.py +0 -0
  219. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_paths.py +0 -0
  220. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_policy.py +0 -0
  221. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_rag.py +0 -0
  222. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_registries_fresh.py +0 -0
  223. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_registry.py +0 -0
  224. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_roadmap_fresh.py +0 -0
  225. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_spin.py +0 -0
  226. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_store.py +0 -0
  227. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_token_parity.py +0 -0
  228. {boost_skill_cli-1.0.67 → boost_skill_cli-1.0.69}/tests/unit/test_util.py +0 -0
  229. {boost_skill_cli-1.0.67 → 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.67
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.67'
22
- __version_tuple__ = version_tuple = (1, 0, 67)
21
+ __version__ = version = '1.0.69'
22
+ __version_tuple__ = version_tuple = (1, 0, 69)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -1147,14 +1147,21 @@ def cmd_self_update(argv) -> int:
1147
1147
  raise BoostError("boost is not running from a git checkout",
1148
1148
  hint="git clone the boost repo and symlink bin")
1149
1149
  old = __version__
1150
+ before = gitutil.head_commit(root)
1150
1151
  gitutil.run(["-C", str(root), "pull", "--ff-only"], timeout=120)
1151
- new = old
1152
- m = re.search(r"__version__\s*=\s*[\"']([^\"']+)[\"']",
1153
- (root / "boost_cli" / "__init__.py").read_text())
1154
- if m:
1155
- new = m.group(1)
1156
- journal.log("self-update", new, previous=old)
1157
- if new != old:
1152
+ after = gitutil.head_commit(root)
1153
+ # The version is setuptools-scm derived — there is no `__version__ = "…"`
1154
+ # literal to grep, and the build-time _version.py / installed metadata are
1155
+ # stale until a reinstall. Ask git for the freshly-pulled version directly
1156
+ # (mirrors boost_cli._detect_version()'s git-checkout fallback), and treat a
1157
+ # moved HEAD as the unambiguous "an update landed" signal — independent of
1158
+ # how the two version strings happen to be formatted.
1159
+ new = gitutil.run(
1160
+ ["-C", str(root), "describe", "--tags", "--always", "--dirty"],
1161
+ check=False, timeout=10).stdout.strip().lstrip("v") or old
1162
+ updated = bool(after) and after != before
1163
+ journal.log("self-update", new if updated else old, previous=old)
1164
+ if updated:
1158
1165
  out.ok("boost v%s → v%s" % (old, new))
1159
1166
  else:
1160
1167
  out.ok("already up to date (v%s)" % old)
@@ -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.67
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
 
@@ -0,0 +1,25 @@
1
+ ---
2
+ id: fix-self-update-version-detection-dead-branch
3
+ board: code
4
+ section: shipped
5
+ status: shipped
6
+ category: Correctness
7
+ complexity: S
8
+ impact: High
9
+ wow: 3
10
+ note: broken headline command, now works
11
+ order: 7
12
+ owner: loop/self-update-fix
13
+ pr:
14
+ title: Fix <code>self-update</code> version detection (dead branch)
15
+ ---
16
+ <code>boost self-update</code> greped <code>__init__.py</code> for a
17
+ <code>__version__ = "…"</code> literal that setuptools-scm never
18
+ writes, so the regex never matched and it always reported
19
+ <b>"already up to date"</b> — even after a pull brought a newer tag.
20
+ The success path was <em>unreachable</em>. It now derives the pulled
21
+ version straight from git (<code>describe --tags</code>, mirroring
22
+ <code>_detect_version()</code>) and treats a moved <code>HEAD</code>
23
+ as the "an update landed" signal, so the version-bump path finally
24
+ fires. Regression tests cover both the up-to-date and update-landed
25
+ branches.
@@ -115,10 +115,10 @@
115
115
  <section class="block reveal">
116
116
  <div class="stats">
117
117
  <!-- ROADMAP:stats -->
118
- <div class="stat"><b>6</b><span>Shipped</span></div>
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>
@@ -227,6 +227,27 @@
227
227
  <span class="m">Wow <b class="wow">★★★★</b></span>
228
228
  </div>
229
229
  </article>
230
+
231
+ <article class="cap rcard">
232
+ <div class="head"><span class="pill shipped">Shipped</span><span class="cat">Correctness</span></div>
233
+ <h3>Fix <code>self-update</code> version detection (dead branch)</h3>
234
+ <p><code>boost self-update</code> greped <code>__init__.py</code> for a
235
+ <code>__version__ = "…"</code> literal that setuptools-scm never
236
+ writes, so the regex never matched and it always reported
237
+ <b>"already up to date"</b> — even after a pull brought a newer tag.
238
+ The success path was <em>unreachable</em>. It now derives the pulled
239
+ version straight from git (<code>describe --tags</code>, mirroring
240
+ <code>_detect_version()</code>) and treats a moved <code>HEAD</code>
241
+ as the "an update landed" signal, so the version-bump path finally
242
+ fires. Regression tests cover both the up-to-date and update-landed
243
+ branches.</p>
244
+ <div class="meta">
245
+ <span class="m">Complexity <b>S</b></span>
246
+ <span class="m hi">Impact <b>High</b></span>
247
+ <span class="m">Wow <b class="wow">★★★</b></span>
248
+ <span class="m">broken headline command, now works</span>
249
+ </div>
250
+ </article>
230
251
  <!-- /ROADMAP:cards -->
231
252
 
232
253
  </div>
@@ -377,17 +398,6 @@
377
398
  </div>
378
399
  </article>
379
400
 
380
- <article class="cap rcard">
381
- <div class="head"><span class="pill next">Next</span><span class="cat">Correctness</span></div>
382
- <h3>Fix <code>self-update</code> version detection (dead branch)</h3>
383
- <p><code>cmd_self_update</code> greps <code>__init__.py</code> for a <code>__version__ = "…"</code> literal (<code>commands/configuration.py:1090</code>), but the constant is <code>_detect_version()</code> (setuptools-scm — no literal). The regex never matches, so it reports <b>"already up to date"</b> even after a pull brings a newer tag. The success path is unreachable.</p>
384
- <div class="meta">
385
- <span class="m">Complexity <b>S</b></span>
386
- <span class="m hi">Impact <b>High</b></span>
387
- <span class="m">Wow <b class="wow">★★★</b></span>
388
- </div>
389
- </article>
390
-
391
401
  <article class="cap rcard">
392
402
  <div class="head"><span class="pill next">Next</span><span class="cat">Correctness</span></div>
393
403
  <h3>Consolidate skill-staleness / drift logic into core</h3>
@@ -564,17 +574,6 @@
564
574
  </div>
565
575
  </article>
566
576
 
567
- <article class="cap rcard">
568
- <div class="head"><span class="pill planned">Planned</span><span class="cat">Robustness</span></div>
569
- <h3>Guard <code>output.table</code> against ragged rows</h3>
570
- <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>
571
- <div class="meta">
572
- <span class="m">Complexity <b>S</b></span>
573
- <span class="m">Impact <b>Low</b></span>
574
- <span class="m">Wow <b class="wow">★</b></span>
575
- </div>
576
- </article>
577
-
578
577
  <article class="cap rcard">
579
578
  <div class="head"><span class="pill planned">Planned</span><span class="cat">Tech-debt</span></div>
580
579
  <h3>Reuse helpers; kill minor dead work</h3>
@@ -809,20 +809,54 @@ class TestSelfUpdate:
809
809
  assert "boost is not running from a git checkout" in r.err
810
810
  assert "git clone the boost repo" in r.err
811
811
 
812
- def test_git_path_pulls_and_reports(self, boost, sandbox, monkeypatch):
812
+ def _fake_git(self, monkeypatch, *, before, after, described):
813
+ """Stub gitutil so self-update sees a controlled HEAD move + describe.
814
+
815
+ ``rev-parse HEAD`` returns ``before`` until the pull runs, ``after``
816
+ afterwards; ``describe`` returns ``described``; ``pull`` is a no-op.
817
+ """
813
818
  calls = []
819
+ pulled = {"done": False}
814
820
  monkeypatch.setattr("boost_cli.core.gitutil.is_repo", lambda p: True)
815
821
 
816
822
  def fake_run(args, **kw):
817
823
  calls.append(list(args))
824
+ if "pull" in args:
825
+ pulled["done"] = True
826
+ return _proc(args, 0)
827
+ if "rev-parse" in args:
828
+ return _proc(args, 0, out=(after if pulled["done"] else before))
829
+ if "describe" in args:
830
+ return _proc(args, 0, out=described)
818
831
  return _proc(args, 0)
819
832
 
820
833
  monkeypatch.setattr("boost_cli.core.gitutil.run", fake_run)
834
+ return calls
835
+
836
+ def test_reports_already_up_to_date_when_head_unchanged(
837
+ self, boost, sandbox, monkeypatch):
838
+ # HEAD is identical before and after the pull -> nothing landed
839
+ calls = self._fake_git(monkeypatch, before="sha1\n", after="sha1\n",
840
+ described="v2.3.3\n")
821
841
  r = boost("self-update")
822
842
  root = paths.repo_root()
823
- assert calls == [["-C", str(root), "pull", "--ff-only"]]
824
843
  from boost_cli import __version__
844
+ assert ["-C", str(root), "pull", "--ff-only"] in calls
825
845
  assert ("already up to date (v%s)" % __version__) in r.out
846
+ assert "→" not in r.out
847
+ ev = journal.events(action="self-update")[0]
848
+ assert ev["subject"] == __version__ and ev["previous"] == __version__
849
+
850
+ def test_reports_new_version_when_pull_advances_head(
851
+ self, boost, sandbox, monkeypatch):
852
+ # the pull fast-forwards HEAD -> report the git-described new version.
853
+ # This path was unreachable before the fix (the old code grepped
854
+ # __init__.py for a __version__ literal setuptools-scm never writes).
855
+ self._fake_git(monkeypatch, before="oldsha\n", after="newsha\n",
856
+ described="v9.9.9\n")
857
+ r = boost("self-update")
858
+ from boost_cli import __version__
859
+ assert ("boost v%s → v9.9.9" % __version__) in r.out
826
860
  ev = journal.events(action="self-update")[0]
827
- assert ev["subject"] == __version__
861
+ assert ev["subject"] == "9.9.9" # git-derived, not the stale const
828
862
  assert ev["previous"] == __version__
@@ -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: